Files
claw3d/src/lib/skills/packaged.ts
T
Luke The Dev 3da1694085 feat: add SOUNDCLAW jukebox skill integration (#67)
Add the office jukebox flow so Spotify can be controlled from the SOUNDCLAW skill, manual jukebox UI, and local browser auth bridge during development.

Made-with: Cursor
2026-03-26 18:35:19 -05:00

221 lines
6.8 KiB
TypeScript

type PackagedSkillFile = {
relativePath: string;
content: string;
};
// Keep this string synchronized with assets/skills/todo-board/SKILL.md.
const TODO_BOARD_SKILL_MD = `---
name: todo
description: Maintain a shared workspace TODO list with blocked tasks.
metadata: {"openclaw":{"skillKey":"todo-board"}}
---
# TODO Board
Use this skill when the user wants to manage a shared task list for the current workspace.
## Trigger
\`\`\`json
{
"activation": {
"anyPhrases": [
"todo",
"todo list",
"blocked task",
"blocked tasks",
"add to my todo",
"show my todo"
]
},
"movement": {
"target": "desk",
"skipIfAlreadyThere": true
}
}
\`\`\`
When this skill is activated, the agent should return to its assigned desk before handling the request.
- If the user asks from Telegram or any other external surface to add, block, unblock, remove, or read TODO items, treat that as a trigger for this skill.
- The physical behavior for this skill is: go sit at the assigned desk, then perform the TODO board workflow.
- If the agent is already at the desk, continue without adding extra movement narration.
## Storage location
The authoritative task file is \`todo-skill/todo-list.json\` in the workspace root.
- Always treat that file as the source of truth.
- Never rely on chat memory alone for the latest task state.
- Create the \`todo-skill\` directory and \`todo-list.json\` file if they do not exist.
## Required workflow
1. Read \`todo-skill/todo-list.json\` before answering any task-management request.
2. If the file does not exist, create it with the schema in this document before continuing.
3. After every add, remove, block, or unblock action, write the full updated JSON back to disk.
4. If the file exists but is invalid JSON or does not match the schema, repair it into a valid structure, preserve any recoverable items, and mention that repair in your response.
5. If the user request is ambiguous, ask a clarifying question instead of guessing.
## Supported actions
- Add a task.
Create a new item unless an equivalent active item already exists.
- Block a task.
Change the matching item to \`status: "blocked"\`. If the task does not exist and the request is clear, create it directly as blocked.
- Unblock a task.
Change the matching item back to \`status: "todo"\` and clear \`blockReason\`.
- Remove a task.
Delete only the matching item. If multiple items could match, ask for clarification.
- Read the list.
Summarize tasks grouped into \`TODO\` and \`BLOCKED\`.
## File format
Use this JSON shape:
\`\`\`json
{
"version": 1,
"updatedAt": "2026-03-22T00:00:00.000Z",
"items": [
{
"id": "task-1",
"title": "Example task",
"status": "todo",
"createdAt": "2026-03-22T00:00:00.000Z",
"updatedAt": "2026-03-22T00:00:00.000Z",
"blockReason": null
}
]
}
\`\`\`
## Field rules
- Keep \`version\` at \`1\`.
- Generate stable, human-readable IDs such as \`prepare-demo\` or \`task-2\`.
- Keep titles concise and preserve the user's intent.
- Use only \`todo\` or \`blocked\` for \`status\`.
- Use ISO timestamps for \`createdAt\`, item \`updatedAt\`, and top-level \`updatedAt\`.
- Keep \`blockReason\` as \`null\` unless the user gave a reason or a short precise reason is clearly implied.
## Mutation rules
- Avoid duplicate active items that describe the same work.
- Preserve existing IDs and \`createdAt\` values for unchanged items.
- Update the touched item's \`updatedAt\` whenever you modify it.
- Update the top-level \`updatedAt\` on every write.
- Keep untouched items in their original order unless there is a strong reason to reorder them.
## Response style
- After each mutation, say what changed.
- When showing the list, group tasks into \`TODO\` and \`BLOCKED\`.
- Include each blocked task's reason when one exists.
`;
// Keep this string synchronized with assets/skills/todo-board/todo-list.example.json.
const TODO_BOARD_EXAMPLE_JSON = `{
"version": 1,
"updatedAt": "2026-03-22T00:00:00.000Z",
"items": [
{
"id": "draft-roadmap",
"title": "Draft the TODO skill roadmap",
"status": "todo",
"createdAt": "2026-03-22T00:00:00.000Z",
"updatedAt": "2026-03-22T00:00:00.000Z",
"blockReason": null
},
{
"id": "gateway-access",
"title": "Confirm gateway install access",
"status": "blocked",
"createdAt": "2026-03-22T00:00:00.000Z",
"updatedAt": "2026-03-22T00:00:00.000Z",
"blockReason": "Waiting for gateway credentials"
}
]
}
`;
// Keep this string synchronized with assets/skills/soundclaw/SKILL.md.
const SOUNDCLAW_SKILL_MD = `---
name: soundclaw
description: Control Spotify playback, search music, and return shareable music links.
metadata: {"openclaw":{"skillKey":"soundclaw"}}
---
# SOUNDCLAW
Use this skill when the user wants an agent to search for music, play a song or playlist, control Spotify playback, or send back a shareable Spotify link on the same channel the request came from.
## Trigger
\`\`\`json
{
"activation": {
"anyPhrases": [
"spotify",
"play a song",
"play this song",
"play music",
"play a playlist",
"find a song",
"queue this song",
"music link"
]
},
"movement": {
"target": "jukebox",
"skipIfAlreadyThere": true
}
}
\`\`\`
When this skill is activated, the agent should walk to the office jukebox before handling the request.
- Treat requests from Telegram or any other external surface as valid triggers when they ask for Spotify playback, search, queueing, or music-link sharing.
- The physical behavior for this skill is: go to the jukebox, perform the music-selection workflow, then report the result.
- If the agent is already at the jukebox, continue without adding extra movement narration.
## Channel behavior
- Reply on the same active channel or session that received the request.
- If playback cannot start but a matching track, album, or playlist is found, send back the best Spotify link instead of failing silently.
- If multiple matches are plausible, ask a clarifying question instead of guessing.
`;
const PACKAGED_SKILL_FILES: Record<string, PackagedSkillFile[]> = {
"todo-board": [
{
relativePath: "SKILL.md",
content: TODO_BOARD_SKILL_MD,
},
{
relativePath: "todo-list.example.json",
content: TODO_BOARD_EXAMPLE_JSON,
},
],
soundclaw: [
{
relativePath: "SKILL.md",
content: SOUNDCLAW_SKILL_MD,
},
],
};
export const readPackagedSkillFiles = (
packageId: string,
): PackagedSkillFile[] => {
const files = PACKAGED_SKILL_FILES[packageId];
if (!files || files.length === 0) {
throw new Error(`Packaged skill assets are missing: ${packageId}`);
}
if (!files.some((file) => file.relativePath === "SKILL.md")) {
throw new Error(`Packaged skill is missing SKILL.md: ${packageId}`);
}
return files.map((file) => ({ ...file }));
};