feat: add coding-agent skill and anyBins gating

Co-authored-by: Sreekaran Srinath <ss@sreekaran.com>
This commit is contained in:
Sreekaran Srinath
2026-01-02 15:40:03 -08:00
committed by GitHub
parent 59601eb99c
commit 0ac30afb29
4 changed files with 94 additions and 0 deletions

View File

@@ -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.<name>.apiKey`.

View File

@@ -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 <session_id>` — 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 <id>`
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-id>`
Session management:
- `opencode session list`
- `opencode export [sessionID]`
- `opencode import <file>`
## Notes
- Prefer **tmux** even for one-shot runs; keep history + recovery.
- For auth, run the tools login flow in tmux (`claude`, `codex login`, `opencode auth`).

View File

@@ -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");

View File

@@ -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) {