3da1694085
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
221 lines
6.8 KiB
TypeScript
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 }));
|
|
};
|