diff --git a/.claude/commands/doctor.md b/.claude/commands/doctor.md index 7f58184..471af94 100644 --- a/.claude/commands/doctor.md +++ b/.claude/commands/doctor.md @@ -310,7 +310,82 @@ If `config.env` is missing, report FAIL — the plugin commands won't work witho --- -### Check 7: Security Audit +### Check 7: Task Event Channel + +Check that the Claude Code channel for push-based task notifications is set up and working. + +**Steps:** + +1. Check if the channel files exist in the GM directory: +```bash +test -f "$LOCAL_PROJECT_DIR/channel/taskyou-channel.ts" && echo "CHANNEL_EXISTS" || echo "CHANNEL_MISSING" +test -f "$LOCAL_PROJECT_DIR/.mcp.json" && echo "MCP_JSON_EXISTS" || echo "MCP_JSON_MISSING" +test -d "$LOCAL_PROJECT_DIR/channel/node_modules" && echo "DEPS_INSTALLED" || echo "DEPS_MISSING" +``` + +2. **If channel files are missing**, deploy them from the plugin templates: + + a. Find the TaskYou-OS plugin directory (or repo checkout): + ```bash + TASKYOU_OS_DIR="" + if [ -f "./templates/channel/taskyou-channel.ts.tmpl" ]; then + TASKYOU_OS_DIR="." + else + PLUGIN_DIR=$(python3 -c "import json; d=json.load(open('$HOME/.claude/plugins/installed_plugins.json')); entries=d.get('plugins',{}).get('taskyou-os@taskyou-os',[]); print(entries[0]['installPath'] if entries else '')" 2>/dev/null) + if [ -n "$PLUGIN_DIR" ] && [ -f "$PLUGIN_DIR/templates/channel/taskyou-channel.ts.tmpl" ]; then + TASKYOU_OS_DIR="$PLUGIN_DIR" + fi + fi + ``` + + b. If templates are available, render and deploy them. Use `config.env` to substitute variables: + ```bash + source "$LOCAL_PROJECT_DIR/config.env" + mkdir -p "$LOCAL_PROJECT_DIR/channel" + ``` + - Read `$TASKYOU_OS_DIR/templates/channel/taskyou-channel.ts.tmpl`, substitute `{{SERVER_HOST}}` and `{{SERVER_HOME}}` with values from config.env, write to `$LOCAL_PROJECT_DIR/channel/taskyou-channel.ts` + - Read `$TASKYOU_OS_DIR/templates/channel/package.json.tmpl`, write to `$LOCAL_PROJECT_DIR/channel/package.json` + - Read `$TASKYOU_OS_DIR/templates/mcp.json.tmpl`, write to `$LOCAL_PROJECT_DIR/.mcp.json` + + c. Install dependencies: + ```bash + cd "$LOCAL_PROJECT_DIR/channel" && bun install --silent + ``` + + d. Report WARN: "Deployed task event channel. Restart Claude Code to activate." + +3. **If channel files exist but deps are missing**, install them: + ```bash + cd "$LOCAL_PROJECT_DIR/channel" && bun install --silent + ``` + Report WARN: "Installed missing channel dependencies." + +4. **If channel exists, check for drift** — compare deployed channel against the plugin template (same approach as nono drift detection in Check 8). If the template is newer, update the deployed file and report WARN. + +5. **Check the shell alias** includes `--dangerously-load-development-channels server:taskyou`: + ```bash + grep "$GM_ALIAS" ~/.zshrc 2>/dev/null || grep "$GM_ALIAS" ~/.bashrc 2>/dev/null + ``` + - If the alias exists but doesn't include `--dangerously-load-development-channels server:taskyou`, report WARN and show the user the updated alias line they should use: + ``` + alias ='cd && CLAUDE_CONFIG_DIR= claude --dangerously-load-development-channels server:taskyou' + ``` + - If the alias already includes the flag, report PASS. + +6. **Check the CLAUDE.md** has the channel-based monitoring section (not the old background-agent approach): + ```bash + grep -c "Task event channel" "$LOCAL_PROJECT_DIR/CLAUDE.md" + grep -c "background monitoring agent" "$LOCAL_PROJECT_DIR/CLAUDE.md" + ``` + - If it has "background monitoring agent" but not "Task event channel", the CLAUDE.md needs updating. Render the Task Tracking section from the template and show the user the diff, offering to update it. + +**If all channel files exist, deps installed, alias correct:** Report PASS with "Task event channel active." +**If deployed or fixed anything:** Report WARN with summary. +**If templates not found:** Report FAIL with "Channel templates not found. Update the TaskYou-OS plugin first." + +--- + +### Check 8: Security Audit Run the server-side security audit script to check credentials, permissions, and exposed services. @@ -340,7 +415,7 @@ ssh -o ConnectTimeout=5 "$SERVER_HOST" '$HOME/.local/bin/audit.sh' 2>/dev/null --- -## Check 8: Credential Isolation (nono) +## Check 9: Credential Isolation (nono) This check verifies if nono is set up, and if not, strongly recommends it. Always run this check regardless of whether credentials are currently configured. @@ -441,6 +516,7 @@ TaskYou-OS Doctor Daemon mode PASS/WARN/FAIL Executor health PASS/WARN/FAIL GM templates PASS/WARN/FAIL + Task event channel PASS/WARN/FAIL Security audit PASS/WARN/FAIL Credential isolation PASS/WARN ───────────────────────────────── diff --git a/.claude/commands/gm-babysit.md b/.claude/commands/gm-babysit.md index a866698..c6e6936 100644 --- a/.claude/commands/gm-babysit.md +++ b/.claude/commands/gm-babysit.md @@ -5,6 +5,8 @@ description: Check on all tracked tasks for an immediate status update Check on all tasks you're currently tracking. Use this for an immediate status update. +Note: Task events normally arrive automatically via the taskyou channel. This command is for a manual spot-check when you want an immediate snapshot. + First, load the project configuration: ```bash source ./config.env @@ -29,8 +31,6 @@ source ./config.env - **Blocked**: Explain what's blocking it. Suggest next steps (retry, send input, review output). - **Still processing**: Note it's still running — no action needed unless it's been unusually long. -5. **Re-launch the background notification watcher** if there are tasks still in progress and no background agent is currently watching. - -6. **If all tracked tasks are done**, let the user know there's nothing left to monitor. +5. **If all tracked tasks are done**, let the user know there's nothing left to monitor. Keep updates brief — one line per task. diff --git a/config.example.env b/config.example.env index 2df7d2e..047fef4 100644 --- a/config.example.env +++ b/config.example.env @@ -90,3 +90,19 @@ PROJECT_DESCRIPTION="My Project does X, Y, and Z." # Hosts agents can reach through the credential proxy. # Only these hosts receive injected credentials. # NONO_PROXY_HOSTS="api.linear.app,api.github.com" + +# === Optional: Per-GM event scoping === +# When multiple GMs share one TaskYou daemon, the channel can scope task +# events to a single GM. The channel stamps tasks it creates with +# `--assigned-gm ` (consuming ty's assigned_gm field) and only +# surfaces events whose assigned_gm matches this GM. +# +# GM_SLUG defaults to GM_ALIAS (set above), so no extra config is needed for +# the common single-GM case. Override it only if you want the assignment slug +# to differ from the launch alias. +# GM_SLUG="mygm" +# +# SEE_UNASSIGNED controls whether events with no assigned_gm (tasks created +# outside the channel, or emitted by an older ty without the field) are still +# shown to this GM. Defaults to "true". Set to "false" for strict isolation. +# SEE_UNASSIGNED="true" diff --git a/setup.sh b/setup.sh index 97e204a..ad3e1d2 100755 --- a/setup.sh +++ b/setup.sh @@ -301,6 +301,20 @@ setup_local() { chmod +x "$LOCAL_PROJECT_DIR/bin/${PROJECT_NAME}-open-board" ok "bin/${PROJECT_NAME}-open-board" + # Channel (push-based task event notifications) + log "Setting up task event channel" + mkdir -p "$LOCAL_PROJECT_DIR/channel" + render_file "$TEMPLATES_DIR/channel/taskyou-channel.ts.tmpl" "$LOCAL_PROJECT_DIR/channel/taskyou-channel.ts" + ok "channel/taskyou-channel.ts" + render_file "$TEMPLATES_DIR/channel/package.json.tmpl" "$LOCAL_PROJECT_DIR/channel/package.json" + ok "channel/package.json" + # Install channel dependencies + (cd "$LOCAL_PROJECT_DIR/channel" && bun install --silent 2>/dev/null) || warn "bun install failed — run 'cd $LOCAL_PROJECT_DIR/channel && bun install' manually" + + # .mcp.json (registers channel with Claude Code) + render_file "$TEMPLATES_DIR/mcp.json.tmpl" "$LOCAL_PROJECT_DIR/.mcp.json" + ok ".mcp.json" + # R2 wrangler.toml if [[ "$R2_ENABLED" == "true" ]]; then log "Setting up R2" @@ -311,7 +325,7 @@ setup_local() { # Shell alias log "Shell alias" - local alias_line="alias ${GM_ALIAS}='cd ${LOCAL_PROJECT_DIR} && CLAUDE_CONFIG_DIR=${CLAUDE_CONFIG_DIR} claude'" + local alias_line="alias ${GM_ALIAS}='cd ${LOCAL_PROJECT_DIR} && CLAUDE_CONFIG_DIR=${CLAUDE_CONFIG_DIR} claude --dangerously-load-development-channels server:taskyou'" echo " Add this to your shell profile (~/.zshrc or ~/.bashrc):" echo "" echo " $alias_line" diff --git a/templates/CLAUDE.md.tmpl b/templates/CLAUDE.md.tmpl index c46db1a..5cd3bad 100644 --- a/templates/CLAUDE.md.tmpl +++ b/templates/CLAUDE.md.tmpl @@ -279,25 +279,29 @@ Humans can request revisions on agent-delivered work by commenting `@agent` (or You are responsible for following up on tasks you execute. This is automatic — {{OWNER_NAME}} should never have to ask "what happened with that task?" -### Automatic monitoring +### Task event channel + +A channel server pushes task events directly into this session. When a remote agent completes or gets blocked, you'll see a `` event appear automatically — no polling or background agents needed. When you execute a task (`ty execute `): 1. **Add it to your todos** via TodoWrite with the task ID, title, and current status. -2. **Launch a background monitoring agent** to watch for task events. The agent should: - - Watch the server's notification stream: `./bin/ssh-remote "tail -n 0 -f {{SERVER_HOME}}/notifications.jsonl"` - - The server hooks automatically write to this file when tasks complete or get blocked - - When a line appears matching a tracked task ID, return immediately with the event details - - Time out after 20 minutes and return (you can re-launch if needed) - - One background agent can watch for ALL tracked tasks — no need for one per task -3. **When the background agent returns with an event**, update your todo and inform {{OWNER_NAME}}: - - **Completed**: Briefly note it finished, offer to show output (`ty output `). Mark todo done. - - **Blocked**: Note what's blocking it, suggest next steps (retry, send input, review). - - Then re-launch the background agent if there are still tasks being tracked. +2. **Wait for the channel event.** Events arrive automatically: + - `event="completed"` — The agent finished. Briefly note it, offer to show output (`ty output `). Mark todo done. + - `event="blocked"` — The agent is stuck. Check output, suggest next steps (retry, send input, review). + +### Channel tools + +The channel also provides tools you can use instead of `./bin/ty-remote` and `./bin/ssh-remote`: + +- `ty_command` — Run any TaskYou command on the server (e.g. `ty_command("list")`, `ty_command("execute 42")`) +- `ssh_command` — Run any shell command on the server (e.g. `ssh_command("git -C projects/marketing log --oneline -5")`) + +You can use either the channel tools or the bin/ scripts — they do the same thing. ### Be non-disruptive -When a background agent notifies you of a task update, be smart about surfacing it: +When a task event arrives, be smart about surfacing it: - If {{OWNER_NAME}} is mid-thought or you're in the middle of a complex discussion, hold the update and surface it at a natural pause. - Keep updates brief — one line is ideal. "Task #12 (competitor research) just finished. Want to see the output?" - Don't pile up multiple updates at once. Space them out if several tasks complete simultaneously. @@ -311,7 +315,7 @@ When a background agent notifies you of a task update, be smart about surfacing ### Manual check -{{OWNER_NAME}} can use `/gm-babysit` for an immediate status check on all tracked tasks without waiting for background agents. +{{OWNER_NAME}} can use `/gm-babysit` for an immediate status check on all tracked tasks. ## Advanced: Raw SSH Access diff --git a/templates/channel/package.json.tmpl b/templates/channel/package.json.tmpl new file mode 100644 index 0000000..49afb67 --- /dev/null +++ b/templates/channel/package.json.tmpl @@ -0,0 +1,8 @@ +{ + "name": "taskyou-channel", + "version": "0.1.0", + "private": true, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1" + } +} diff --git a/templates/channel/smoke-test.ts b/templates/channel/smoke-test.ts new file mode 100644 index 0000000..44d5f73 --- /dev/null +++ b/templates/channel/smoke-test.ts @@ -0,0 +1,111 @@ +#!/usr/bin/env bun +// Smoke test: spawn the channel server, do the MCP handshake, verify capabilities and tools. +// Usage: bun run smoke-test.ts +// Requires: taskyou-channel.ts in the same directory (rendered with any server values — they don't matter for this test). + +import { spawn } from "child_process"; + +const proc = spawn("bun", ["run", "taskyou-channel.ts"], { + cwd: import.meta.dir, + stdio: ["pipe", "pipe", "pipe"], +}); + +let stdout = ""; +proc.stdout.on("data", (d: Buffer) => { + stdout += d.toString(); + const lines = stdout.split("\n"); + for (const line of lines.slice(0, -1)) { + if (!line.trim()) continue; + try { + const msg = JSON.parse(line); + if (msg.id === 1) { + handleInitResponse(msg); + } else if (msg.id === 2) { + handleToolsResponse(msg); + } + } catch {} + } + stdout = lines[lines.length - 1]; +}); + +let stderr = ""; +proc.stderr.on("data", (d: Buffer) => { + stderr += d.toString(); +}); + +proc.on("close", (code) => { + if (code !== 0 && code !== null) { + console.log(`Process exited with code ${code}`); + if (stderr) console.log("stderr:", stderr); + process.exit(1); + } +}); + +function handleInitResponse(msg: any) { + const caps = msg.result?.capabilities?.experimental; + const hasChannel = !!caps?.["claude/channel"]; + const hasTools = !!msg.result?.capabilities?.tools; + const hasInstructions = msg.result?.instructions?.includes("taskyou"); + + console.log(`${hasChannel ? "✓" : "✗"} claude/channel capability`); + console.log(`${hasTools ? "✓" : "✗"} tools capability`); + console.log(`${hasInstructions ? "✓" : "✗"} instructions`); + + if (!hasChannel || !hasTools || !hasInstructions) { + console.log("\nFAILED — missing capabilities"); + proc.kill(); + process.exit(1); + } + + // Request tool list + proc.stdin.write( + JSON.stringify({ + jsonrpc: "2.0", + id: 2, + method: "tools/list", + params: {}, + }) + "\n" + ); +} + +function handleToolsResponse(msg: any) { + const tools = msg.result?.tools || []; + const names = tools.map((t: any) => t.name); + + const hasTy = names.includes("ty_command"); + const hasSsh = names.includes("ssh_command"); + + console.log(`${hasTy ? "✓" : "✗"} ty_command tool`); + console.log(`${hasSsh ? "✓" : "✗"} ssh_command tool`); + + if (!hasTy || !hasSsh) { + console.log("\nFAILED — missing tools"); + proc.kill(); + process.exit(1); + } + + console.log("\nPASSED"); + proc.kill(); + process.exit(0); +} + +// Send MCP initialize +proc.stdin.write( + JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { + protocolVersion: "2024-11-05", + capabilities: {}, + clientInfo: { name: "smoke-test", version: "0.0.1" }, + }, + }) + "\n" +); + +setTimeout(() => { + console.log("FAILED — timed out"); + if (stderr) console.log("stderr:", stderr); + proc.kill(); + process.exit(1); +}, 10_000); diff --git a/templates/channel/taskyou-channel.ts.tmpl b/templates/channel/taskyou-channel.ts.tmpl new file mode 100644 index 0000000..c0509d9 --- /dev/null +++ b/templates/channel/taskyou-channel.ts.tmpl @@ -0,0 +1,222 @@ +#!/usr/bin/env bun +// TaskYou Channel — pushes task events into the GM's Claude Code session +// Generated by TaskYouOS setup. Do not edit — edit the template instead. + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + ListToolsRequestSchema, + CallToolRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { spawn } from "child_process"; + +const SERVER_HOST = "{{SERVER_HOST}}"; +const SERVER_HOME = "{{SERVER_HOME}}"; +const POLL_INTERVAL_MS = 10_000; // 10 seconds + +// Per-GM event scoping. GM_SLUG identifies this GM (defaults to the GM_ALIAS +// chosen at setup time). New tasks created through this channel are stamped +// with --assigned-gm , and emitted notifications are filtered to this +// GM. SEE_UNASSIGNED controls whether events with no assigned_gm (e.g. tasks +// created outside the channel, or older ty versions) are still surfaced here. +const GM_SLUG = process.env.GM_SLUG || process.env.GM_ALIAS || "{{GM_ALIAS}}"; +const SEE_UNASSIGNED = (process.env.SEE_UNASSIGNED ?? "true") !== "false"; + +// Track where we are in the notifications file +let lastLineCount = 0; + +const mcp = new Server( + { name: "taskyou", version: "0.1.0" }, + { + capabilities: { + experimental: { "claude/channel": {} }, + tools: {}, + }, + instructions: [ + 'Task events from remote agents arrive as .', + "Events:", + ' - event="completed": An agent finished its task. Offer to show output (ty output ). Mark your todo done.', + ' - event="blocked": An agent is stuck. Check output, suggest retry or escalate.', + "When you see a task event, update your todos and briefly inform the user.", + "If the user is mid-thought, hold the update until a natural pause.", + "", + "You can also use the channel tools to run TaskYou commands without shelling out:", + " - ty_command: Run any ty command on the server (e.g. list, board, create, execute, retry, output, close)", + " - ssh_command: Run any shell command on the server", + ].join("\n"), + } +); + +// ── Tools: let the GM run ty/ssh commands through the channel ──────────────── + +mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: "ty_command", + description: + "Run a TaskYou command on the remote server (e.g. list, board, create, execute, retry, output, close)", + inputSchema: { + type: "object", + properties: { + args: { + type: "string", + description: + 'The ty command and arguments, e.g. "list", "execute 42", "output 42 --tail 50"', + }, + }, + required: ["args"], + }, + }, + { + name: "ssh_command", + description: + "Run a shell command on the remote server (e.g. git log, tail logs)", + inputSchema: { + type: "object", + properties: { + command: { + type: "string", + description: "The shell command to run on the server", + }, + }, + required: ["command"], + }, + }, + ], +})); + +mcp.setRequestHandler(CallToolRequestSchema, async (req) => { + const { name, arguments: args } = req.params; + + if (name === "ty_command") { + let { args: tyArgs } = args as { args: string }; + // Scope new tasks to this GM: inject --assigned-gm on `create` (only), + // unless the caller already specified one. Idempotent. + if ( + /^\s*create(\s|$)/.test(tyArgs) && + !/--assigned-gm(\s|=)/.test(tyArgs) + ) { + tyArgs = `${tyArgs} --assigned-gm ${GM_SLUG}`; + } + const result = await runRemote(`ty ${tyArgs}`); + return { content: [{ type: "text", text: result }] }; + } + + if (name === "ssh_command") { + const { command } = args as { command: string }; + const result = await runRemote(command); + return { content: [{ type: "text", text: result }] }; + } + + throw new Error(`unknown tool: ${name}`); +}); + +// ── SSH helper ─────────────────────────────────────────────────────────────── + +function runRemote(command: string): Promise { + return new Promise((resolve) => { + const sshCmd = `export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && export PATH="${SERVER_HOME}/.local/bin:${SERVER_HOME}/bin:${SERVER_HOME}/.npm-global/bin:$PATH" && cd ${SERVER_HOME} && ${command}`; + + const proc = spawn("ssh", [SERVER_HOST, sshCmd], { + stdio: ["ignore", "pipe", "pipe"], + }); + + let stdout = ""; + let stderr = ""; + + proc.stdout.on("data", (d: Buffer) => (stdout += d.toString())); + proc.stderr.on("data", (d: Buffer) => (stderr += d.toString())); + + proc.on("close", (code) => { + if (code !== 0 && stderr) { + resolve(`[exit ${code}] ${stderr.trim()}\n${stdout.trim()}`.trim()); + } else { + resolve(stdout.trim()); + } + }); + + // Timeout after 30 seconds + setTimeout(() => { + proc.kill(); + resolve("[timeout] command took longer than 30 seconds"); + }, 30_000); + }); +} + +// ── Notification polling ───────────────────────────────────────────────────── + +async function pollNotifications() { + try { + // Get current line count + const wcResult = await runRemote( + `wc -l < ${SERVER_HOME}/notifications.jsonl 2>/dev/null || echo 0` + ); + const currentLines = parseInt(wcResult.trim(), 10) || 0; + + if (currentLines > lastLineCount && lastLineCount > 0) { + // Read only new lines + const newLines = await runRemote( + `tail -n +${lastLineCount + 1} ${SERVER_HOME}/notifications.jsonl` + ); + + for (const line of newLines.split("\n")) { + const trimmed = line.trim(); + if (!trimmed) continue; + + let event: any; + try { + event = JSON.parse(trimmed); + } catch { + // Malformed line → treat as unassigned. The cursor still advances + // below (we iterate every line); only the emit is gated here. + if (SEE_UNASSIGNED) { + await mcp.notification({ + method: "notifications/claude/channel", + params: { content: trimmed, meta: {} }, + }); + } + continue; + } + + // Per-GM scoping: emit if assigned to this GM, or if unassigned and + // this session opts into seeing unassigned events. A missing/blank + // assigned_gm is treated as unassigned. + const assignedGm = event.assigned_gm; + const isMine = assignedGm === GM_SLUG; + const isUnassigned = + assignedGm === undefined || assignedGm === null || assignedGm === ""; + if (!isMine && !(isUnassigned && SEE_UNASSIGNED)) continue; + + await mcp.notification({ + method: "notifications/claude/channel", + params: { + content: trimmed, + meta: { + event: event.event || "unknown", + task_id: event.task_id || "", + title: event.title || "", + project: event.project || "", + assigned_gm: event.assigned_gm || "", + timestamp: event.timestamp || "", + }, + }, + }); + } + } + + // On first run, just record the position (don't replay old events) + lastLineCount = currentLines; + } catch { + // SSH failure — silently retry next interval + } +} + +// ── Start ──────────────────────────────────────────────────────────────────── + +await mcp.connect(new StdioServerTransport()); + +// Initialize position before starting poll loop +await pollNotifications(); + +// Poll for new notifications +setInterval(pollNotifications, POLL_INTERVAL_MS); diff --git a/templates/hooks/task.blocked.tmpl b/templates/hooks/task.blocked.tmpl index 714ee6c..004aa95 100644 --- a/templates/hooks/task.blocked.tmpl +++ b/templates/hooks/task.blocked.tmpl @@ -7,6 +7,7 @@ NOTIFICATIONS_FILE="{{SERVER_HOME}}/notifications.jsonl" TASK_ID="${TASK_ID:-unknown}" TASK_TITLE="${TASK_TITLE:-}" TASK_PROJECT="${TASK_PROJECT:-}" +TASK_ASSIGNED_GM="${TASK_ASSIGNED_GM:-}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -echo "{\"event\":\"blocked\",\"task_id\":\"${TASK_ID}\",\"title\":$(printf '%s' "$TASK_TITLE" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),\"project\":\"${TASK_PROJECT}\",\"timestamp\":\"${TIMESTAMP}\"}" >> "$NOTIFICATIONS_FILE" +echo "{\"event\":\"blocked\",\"task_id\":\"${TASK_ID}\",\"title\":$(printf '%s' "$TASK_TITLE" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),\"project\":\"${TASK_PROJECT}\",\"assigned_gm\":\"${TASK_ASSIGNED_GM}\",\"timestamp\":\"${TIMESTAMP}\"}" >> "$NOTIFICATIONS_FILE" diff --git a/templates/hooks/task.completed.tmpl b/templates/hooks/task.completed.tmpl index 93cfb0e..db6d935 100644 --- a/templates/hooks/task.completed.tmpl +++ b/templates/hooks/task.completed.tmpl @@ -7,6 +7,7 @@ NOTIFICATIONS_FILE="{{SERVER_HOME}}/notifications.jsonl" TASK_ID="${TASK_ID:-unknown}" TASK_TITLE="${TASK_TITLE:-}" TASK_PROJECT="${TASK_PROJECT:-}" +TASK_ASSIGNED_GM="${TASK_ASSIGNED_GM:-}" TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -echo "{\"event\":\"completed\",\"task_id\":\"${TASK_ID}\",\"title\":$(printf '%s' "$TASK_TITLE" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),\"project\":\"${TASK_PROJECT}\",\"timestamp\":\"${TIMESTAMP}\"}" >> "$NOTIFICATIONS_FILE" +echo "{\"event\":\"completed\",\"task_id\":\"${TASK_ID}\",\"title\":$(printf '%s' "$TASK_TITLE" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),\"project\":\"${TASK_PROJECT}\",\"assigned_gm\":\"${TASK_ASSIGNED_GM}\",\"timestamp\":\"${TIMESTAMP}\"}" >> "$NOTIFICATIONS_FILE" diff --git a/templates/mcp.json.tmpl b/templates/mcp.json.tmpl new file mode 100644 index 0000000..7ad227b --- /dev/null +++ b/templates/mcp.json.tmpl @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "taskyou": { + "command": "bun", + "args": ["./channel/taskyou-channel.ts"] + } + } +} diff --git a/templates/settings.json.tmpl b/templates/settings.json.tmpl index d9b8f47..0e423c5 100644 --- a/templates/settings.json.tmpl +++ b/templates/settings.json.tmpl @@ -6,7 +6,9 @@ "Read", "WebFetch", "WebSearch", - "Task(*)" + "Task(*)", + "mcp__taskyou__ty_command", + "mcp__taskyou__ssh_command" ], "deny": [ "Bash(rm *)",