feat(cli): add skills add command to install the /orchestrate skill#1205
Conversation
`agent-relay skills add` downloads the /orchestrate skill from agentrelay.com/skill.md and installs it into one or more coding harnesses. An interactive TUI asks whether to install for the current project or globally, then which harnesses to target. A dependency-free keypress picker (single-select scope + multi-select harnesses) is used; no prompt library is added. Non-interactive shells are driven by --global/--local, --harness <ids>, and --all flags. Each harness maps onto its native convention: - Claude Code: .claude/skills/orchestrate/SKILL.md (full skill) - Codex: .codex/prompts/orchestrate.md (body-only prompt) - Cursor: .cursor/commands/orchestrate.md (full, frontmatter kept) - Gemini: .gemini/commands/orchestrate.toml (TOML prompt block) - OpenCode: .opencode/command/orchestrate.md (body-only prompt) Core install logic, the TUI, and command wiring are split into separately unit-tested modules (29 tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bgEtgqAsEJTLddt5ZuuBz
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds ChangesSkills add command
Sequence Diagram(s)sequenceDiagram
participant registerSkillsCommands
participant selectScope
participant selectHarnesses
participant fetchSkill
participant installSkill
participant reportResults
participant SkillWriter
registerSkillsCommands->>selectScope: resolve scope when flags are missing
registerSkillsCommands->>selectHarnesses: resolve harnesses when flags are missing
registerSkillsCommands->>fetchSkill: download agentrelay.com/skill.md
fetchSkill-->>registerSkillsCommands: raw markdown
registerSkillsCommands->>installSkill: install /orchestrate
installSkill->>SkillWriter: write rendered harness files
installSkill-->>registerSkillsCommands: per-harness results
registerSkillsCommands->>reportResults: log successes and failures
reportResults-->>registerSkillsCommands: exit code
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces the agent-relay skills add command, allowing users to install the /orchestrate skill from agentrelay.com/skill.md into coding harnesses like Claude Code, Codex, Cursor, Gemini, and OpenCode, either interactively via a dependency-free TUI or non-interactively using CLI flags. Feedback focuses on improving robustness, including adding a network timeout and unified error handling to fetchSkill, implementing defensive checks for getProjectPaths() and setRawMode to prevent runtime crashes, and supporting optional leading whitespace in the frontmatter regex parser.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
I am having trouble creating individual review comments. Click here to see my feedback.
packages/cli/src/cli/lib/skills-install.ts (243-259)
Wrap the entire network fetch and body reading in a single try-catch block to gracefully handle stream reading errors (e.g., from res.text()), and add a network timeout to prevent the CLI from hanging indefinitely on slow or unresponsive connections.
export async function fetchSkill(url: string = ORCHESTRATE_SKILL.url): Promise<string> {
try {
const res = await fetch(url, {
headers: { accept: 'text/markdown, text/plain, */*' },
signal: AbortSignal.timeout(10000),
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const text = await res.text();
if (!text.trim()) {
throw new Error('Response body was empty');
}
return text;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Failed to download skill from ${url}: ${message}`);
}
}
packages/cli/src/cli/commands/skills.ts (45-48)
Add a defensive check for paths in case getProjectPaths() returns null or undefined to prevent potential runtime TypeError crashes.
function resolveProjectRoot(): string {
const paths = getProjectPaths() as { projectRoot?: string } | null | undefined;
return paths?.projectRoot ?? process.cwd();
}
packages/cli/src/cli/lib/skills-install.ts (57-62)
Allow optional leading whitespace in the regex to support indented YAML frontmatter keys.
function readScalar(frontmatter: string, key: string): string | undefined {
const re = new RegExp("`^\\\\s*\\${key}\\\\s*:\\\\s*(.+)$`", 'm');
const m = re.exec(frontmatter);
if (!m) return undefined;
return m[1].trim().replace(/^["']|["']$/g, '');
}
packages/cli/src/cli/lib/skills-tui.ts (48-49)
Check if setRawMode is a function before calling it to prevent runtime crashes in environments where input is a custom stream or mock that doesn't implement it.
const wasRaw = input.isRaw ?? false;
if (input.isTTY && typeof input.setRawMode === 'function') {
input.setRawMode(true);
}
packages/cli/src/cli/lib/skills-tui.ts (53-58)
Check if setRawMode is a function before calling it in the cleanup handler to prevent runtime crashes.
const cleanup = (): void => {
input.off('keypress', handler);
if (input.isTTY && typeof input.setRawMode === 'function') {
input.setRawMode(wasRaw);
}
output.write(SHOW_CURSOR);
input.pause();
};There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9eb7aa1072
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| '.codex', | ||
| 'prompts', | ||
| `${ORCHESTRATE_SKILL.slug}.md` |
There was a problem hiding this comment.
Install Codex skills in Codex skill directories
I checked the current Codex skills docs (https://developers.openai.com/codex/skills): Codex discovers repo/user skills under .agents/skills / $HOME/.agents/skills, while .codex/prompts is only the deprecated custom-prompt location and project-local .codex/prompts is not scanned. When a user selects the Codex harness—especially with the default project scope—the command reports success but Codex's /skills/skill mention flow will not find the installed /orchestrate skill; write the full SKILL.md into the Codex skill locations instead.
Useful? React with 👍 / 👎.
| ? path.join(ctx.homeDir, '.config', 'opencode', 'command', `${ORCHESTRATE_SKILL.slug}.md`) | ||
| : path.join(ctx.projectRoot, '.opencode', 'command', `${ORCHESTRATE_SKILL.slug}.md`), |
There was a problem hiding this comment.
Use OpenCode's plural commands directory
I checked the OpenCode commands docs (https://opencode.ai/docs/commands/), which load markdown commands from ~/.config/opencode/commands/ or .opencode/commands/. These paths use singular command, so choosing the OpenCode harness reports an install but OpenCode will not discover /orchestrate; change both global and project paths to commands.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli/commands/skills.ts`:
- Around line 41-43: The fallback in resolveProjectRoot() is unreachable because
getProjectPaths() throws before returning, so the current `?? process.cwd()`
never applies. Update resolveProjectRoot() to catch failures from
getProjectPaths() and only fall back to process.cwd() when the project lookup
fails, while still preserving the resolved projectRoot when available. Make sure
the skills command flow that uses deps.getContext() can reach this fallback for
global operations like `skills add --global` from outside an Agent Relay
project.
In `@packages/cli/src/cli/lib/skills-install.ts`:
- Line 44: The regex in skills-install’s normalization step currently contains a
literal BOM character, which triggers the irregular whitespace lint rule. Update
the `normalized` assignment in `skills-install.ts` to use an escaped Unicode
form in the `replace` pattern (for example, `^\uFEFF`) instead of the raw
character so the `replace` call remains equivalent and lint-safe.
- Around line 242-249: The fetchSkill download can hang indefinitely because
fetch() is currently unbounded; update fetchSkill to use an AbortController
timeout around the fetch call. Keep the existing error handling in fetchSkill,
and when the timeout triggers, abort the request and throw a clear
timeout-specific error message so stalled skill downloads fail fast in
agent-relay skills add.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2ff8d043-d609-4b7b-8762-ae9d0d30d95c
📒 Files selected for processing (11)
.agentworkforce/trajectories/completed/2026-06/traj_813j4f2lfyff/summary.md.agentworkforce/trajectories/completed/2026-06/traj_813j4f2lfyff/trajectory.jsonCHANGELOG.mdpackages/cli/src/cli/bootstrap.test.tspackages/cli/src/cli/bootstrap.tspackages/cli/src/cli/commands/skills.test.tspackages/cli/src/cli/commands/skills.tspackages/cli/src/cli/lib/skills-install.test.tspackages/cli/src/cli/lib/skills-install.tspackages/cli/src/cli/lib/skills-tui.test.tspackages/cli/src/cli/lib/skills-tui.ts
- Fix lint error: replace literal U+FEFF BOM in parseSkill's regex with the `` escape (no-irregular-whitespace). - Codex: install to `.agents/skills/orchestrate/SKILL.md` (the portable SKILL.md standard Codex scans from cwd→repo root and $HOME), with the full skill, instead of the unscanned/deprecated `.codex/prompts`. - OpenCode: use the plural `commands/` directory (`.opencode/commands/` and `~/.config/opencode/commands/`) so OpenCode discovers `/orchestrate`. - Bound the skill download with an AbortController timeout so a stalled connection can't hang `skills add` in CI; report timeouts clearly. - Defensive try/catch in resolveProjectRoot so a global install can't be blocked by project resolution. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bgEtgqAsEJTLddt5ZuuBz
|
Addressed the review feedback in 2e805e3 (lint is now green locally — 0 errors):
Tests updated for the new paths plus a timeout test; 30 tests pass. Generated by Claude Code |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/cli/lib/skills-install.test.ts`:
- Around line 187-197: The fetch stub in skills-install.test.ts is not always
restored if the expectation fails, which can leave the global mocked for later
tests. Update the test around fetchSkill to ensure vi.unstubAllGlobals() always
runs by wrapping the assertion in a try/finally block, or move the cleanup into
a shared afterEach hook, so the stubbed fetch is reliably reset even when the
timeout assertion throws.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8541678e-08fe-43d1-a3e3-131755416cb8
📒 Files selected for processing (3)
packages/cli/src/cli/commands/skills.tspackages/cli/src/cli/lib/skills-install.test.tspackages/cli/src/cli/lib/skills-install.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/cli/src/cli/lib/skills-install.ts
- packages/cli/src/cli/commands/skills.ts
Move `vi.unstubAllGlobals()` into an afterEach so the stubbed global `fetch` is always restored, even if an assertion throws — preventing a failed test from leaking the mock into later tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bgEtgqAsEJTLddt5ZuuBz
Summary
Adds
agent-relay skills add, which downloads the/orchestrateskill fromagentrelay.com/skill.mdand installs it into one or more coding harnesses.afor all, enter to confirm).--global/--local,--harness <ids>,--all. A non-TTY shell with missing flags errors with usage guidance instead of hanging.Each harness maps onto its native custom-command / skill convention:
.claude/skills/orchestrate/SKILL.md.codex/prompts/orchestrate.md.cursor/commands/orchestrate.md.gemini/commands/orchestrate.tomlpromptblock (escaped).opencode/command/orchestrate.md(project) /~/.config/opencode/command/orchestrate.md(global)Core install logic (
lib/skills-install.ts), the TUI (lib/skills-tui.ts), and command wiring (commands/skills.ts) are split into separately unit-tested modules. Registered inbootstrap.ts;CHANGELOG.mdupdated underAdded.Test Plan
--helpoutput and the--harness/scope validation guards via the built CLINote: the live
skill.mdfetch is blocked by the build sandbox's proxy, so the network path is covered by mocked tests; it resolves normally in a standard environment.Screenshots
🤖 Generated with Claude Code
Generated by Claude Code