diff --git a/.github/screenshots/issue-6-fix.png b/.github/screenshots/issue-6-fix.png new file mode 100644 index 0000000..17e82cd Binary files /dev/null and b/.github/screenshots/issue-6-fix.png differ diff --git a/daemon/agents.js b/daemon/agents.js index 1b35b32..08d81c6 100644 --- a/daemon/agents.js +++ b/daemon/agents.js @@ -7,7 +7,12 @@ import path from 'node:path'; const execFileP = promisify(execFile); // Each entry defines how to invoke the agent in non-interactive "one-shot" mode. -// `buildArgs(prompt, imagePaths)` returns argv for the child process. +// `buildArgs(prompt, imagePaths, extraAllowedDirs)` returns argv for the child +// process. `extraAllowedDirs` is a list of absolute directories the agent must +// be permitted to read files from (skill seeds, design-system specs) that live +// outside the project cwd. Currently only Claude Code wires this through +// (`--add-dir`); other agents either inherit broader access or run with cwd +// boundaries we can't widen via flags. // `streamFormat` hints to the daemon how to interpret stdout: // - 'claude-stream-json' : line-delimited JSON emitted by Claude Code's // `--output-format stream-json`. Daemon parses it into typed events @@ -19,14 +24,23 @@ export const AGENT_DEFS = [ name: 'Claude Code', bin: 'claude', versionArgs: ['--version'], - buildArgs: (prompt) => [ - '-p', - prompt, - '--output-format', - 'stream-json', - '--verbose', - '--include-partial-messages', - ], + buildArgs: (prompt, _imagePaths, extraAllowedDirs = []) => { + const args = [ + '-p', + prompt, + '--output-format', + 'stream-json', + '--verbose', + '--include-partial-messages', + ]; + const dirs = (extraAllowedDirs || []).filter( + (d) => typeof d === 'string' && d.length > 0, + ); + if (dirs.length > 0) { + args.push('--add-dir', ...dirs); + } + return args; + }, streamFormat: 'claude-stream-json', }, { diff --git a/daemon/server.js b/daemon/server.js index f3a6246..c12311f 100644 --- a/daemon/server.js +++ b/daemon/server.js @@ -769,7 +769,17 @@ export async function startServer({ port = 7456 } = {}) { safeImages.length ? `\n\n${safeImages.map((p) => `@${p}`).join(' ')}` : '', ].join(''); - const args = def.buildArgs(composed, safeImages); + // Skill seeds (`skills//assets/template.html`) and design-system + // specs (`design-systems//DESIGN.md`) live outside the project cwd. + // The composed system prompt asks the agent to Read them via absolute + // paths in the skill-root preamble — without an explicit allowlist, + // Claude Code blocks those reads (issue #6: "no permission to read + // skills template"). We surface both roots so any agent that honours + // `--add-dir` can resolve those side files. + const extraAllowedDirs = [SKILLS_DIR, DESIGN_SYSTEMS_DIR].filter( + (d) => fs.existsSync(d), + ); + const args = def.buildArgs(composed, safeImages, extraAllowedDirs); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache, no-transform');