From 0ac30afb2987d493a9e8daf241dfbdbdffdb6075 Mon Sep 17 00:00:00 2001 From: Sreekaran Srinath Date: Fri, 2 Jan 2026 15:40:03 -0800 Subject: [PATCH] feat: add coding-agent skill and anyBins gating Co-authored-by: Sreekaran Srinath --- docs/skills.md | 1 + skills/coding-agent/SKILL.md | 78 ++++++++++++++++++++++++++++++++++++ src/agents/skills.test.ts | 8 ++++ src/agents/skills.ts | 7 ++++ 4 files changed, 94 insertions(+) create mode 100644 skills/coding-agent/SKILL.md diff --git a/docs/skills.md b/docs/skills.md index c55ffeeaf..09863e487 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -61,6 +61,7 @@ Fields under `metadata.clawdis`: - `homepage` — optional URL shown as “Website” in the macOS Skills UI. - `os` — optional list of platforms (`darwin`, `linux`, `win32`). If set, the skill is only eligible on those OSes. - `requires.bins` — list; each must exist on `PATH`. +- `requires.anyBins` — list; at least one must exist on `PATH`. - `requires.env` — list; env var must exist **or** be provided in config. - `requires.config` — list of `clawdis.json` paths that must be truthy. - `primaryEnv` — env var name associated with `skills.entries..apiKey`. diff --git a/skills/coding-agent/SKILL.md b/skills/coding-agent/SKILL.md new file mode 100644 index 000000000..3e545ec81 --- /dev/null +++ b/skills/coding-agent/SKILL.md @@ -0,0 +1,78 @@ +--- +name: coding-agent +description: Run Claude Code, Codex CLI, or OpenCode via tmux for resilient coding sessions. +metadata: {"clawdis":{"emoji":"🧩","requires":{"bins":["tmux"],"anyBins":["claude","codex","opencode"]}}} +--- + +# Coding Agent (tmux-first) + +Use **tmux** for all coding-agent CLIs. Keep sessions resumable and logs visible. + +## Quick preflight + +```bash +command -v claude codex opencode tmux +``` + +If none of `claude`, `codex`, `opencode` exist, stop and ask to install. + +## tmux baseline + +```bash +# Create or attach +tmux new -A -s coding-agent + +# Split panes +tmux split-window -h +tmux split-window -v + +# Leave running, detach +tmux detach +``` + +## Claude Code + +Interactive (preferred in tmux): +- `claude` — start session +- `claude -c` — continue most recent +- `claude -r ""` — picker +- `claude -r ` — resume specific + +## Codex CLI + +One-shot (safe in tmux): +- `codex exec "Write a Python function that ..."` +- `codex exec --model gpt-4o "Complex task"` +- `codex exec --model o3 "Reasoning-heavy task"` + +Interactive: +- `codex "Your prompt"` +- `codex resume` +- `codex resume --last` +- `codex resume --session ` + +Apply changes: +- `codex apply` + +## OpenCode + +One-shot: +- `opencode run "Write a Python function that ..."` +- `opencode run -m anthropic/claude-sonnet-4-5 "Complex task"` +- `opencode run -m openai/gpt-5.2 "Coding task"` +- `opencode run -m google/gemini-2.5-pro "Research task"` + +Interactive: +- `opencode` +- `opencode -c` +- `opencode -s ` + +Session management: +- `opencode session list` +- `opencode export [sessionID]` +- `opencode import ` + +## Notes + +- Prefer **tmux** even for one-shot runs; keep history + recovery. +- For auth, run the tool’s login flow in tmux (`claude`, `codex login`, `opencode auth`). diff --git a/src/agents/skills.test.ts b/src/agents/skills.test.ts index 3ecde188f..81f9f7dbf 100644 --- a/src/agents/skills.test.ts +++ b/src/agents/skills.test.ts @@ -215,6 +215,12 @@ describe("buildWorkspaceSkillsPrompt", () => { description: "Needs a bin", metadata: '{"clawdis":{"requires":{"bins":["fakebin"]}}}', }); + await writeSkill({ + dir: path.join(skillsDir, "anybin-skill"), + name: "anybin-skill", + description: "Needs any bin", + metadata: '{"clawdis":{"requires":{"anyBins":["missingbin","fakebin"]}}}', + }); await writeSkill({ dir: path.join(skillsDir, "config-skill"), name: "config-skill", @@ -242,6 +248,7 @@ describe("buildWorkspaceSkillsPrompt", () => { expect(defaultPrompt).toContain("always-skill"); expect(defaultPrompt).toContain("config-skill"); expect(defaultPrompt).not.toContain("bin-skill"); + expect(defaultPrompt).not.toContain("anybin-skill"); expect(defaultPrompt).not.toContain("env-skill"); await fs.mkdir(binDir, { recursive: true }); @@ -258,6 +265,7 @@ describe("buildWorkspaceSkillsPrompt", () => { }, }); expect(gatedPrompt).toContain("bin-skill"); + expect(gatedPrompt).toContain("anybin-skill"); expect(gatedPrompt).toContain("env-skill"); expect(gatedPrompt).toContain("always-skill"); expect(gatedPrompt).not.toContain("config-skill"); diff --git a/src/agents/skills.ts b/src/agents/skills.ts index d9e2bb7a5..1b1ab5e33 100644 --- a/src/agents/skills.ts +++ b/src/agents/skills.ts @@ -30,6 +30,7 @@ export type ClawdisSkillMetadata = { os?: string[]; requires?: { bins?: string[]; + anyBins?: string[]; env?: string[]; config?: string[]; }; @@ -307,6 +308,7 @@ function resolveClawdisMetadata( requires: requiresRaw ? { bins: normalizeStringList(requiresRaw.bins), + anyBins: normalizeStringList(requiresRaw.anyBins), env: normalizeStringList(requiresRaw.env), config: normalizeStringList(requiresRaw.config), } @@ -347,6 +349,11 @@ function shouldIncludeSkill(params: { if (!hasBinary(bin)) return false; } } + const requiredAnyBins = entry.clawdis?.requires?.anyBins ?? []; + if (requiredAnyBins.length > 0) { + const anyFound = requiredAnyBins.some((bin) => hasBinary(bin)); + if (!anyFound) return false; + } const requiredEnv = entry.clawdis?.requires?.env ?? []; if (requiredEnv.length > 0) {