Skip to content

Commit d6a1e6b

Browse files
anandgupta42claude
andauthored
feat: add skill CLI command and .opencode/tools/ auto-discovery (#342)
* feat: add `skill` CLI command and `.opencode/tools/` auto-discovery (#341) Add a top-level `altimate-code skill` command with `list`, `create`, and `test` subcommands, plus auto-discovery of user CLI tools on PATH. - `skill list` — shows skills with paired CLI tools, source, description - `skill create <name>` — scaffolds SKILL.md + CLI tool stub (bash/python/node) - `skill test <name>` — validates frontmatter, checks tool on PATH, runs `--help` - Auto-prepend `.opencode/tools/` (project) and `~/.config/altimate-code/tools/` (global) to PATH in `BashTool` and PTY sessions - Worktree-aware: `skill create` uses git root, PATH includes worktree tools - Input validation: regex + length limits, 5s timeout on tool `--help` - 14 unit tests (42 assertions) covering tool detection, templates, adversarial inputs - Updated docs: skills.md, tools/custom.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review findings for skill CLI (#341) - Extract `detectToolReferences`, `SHELL_BUILTINS`, `skillSource`, `isToolOnPath` into `skill-helpers.ts` so tests import production code instead of duplicating it - Anchor PATH tool dirs to `Instance.directory` (not `cwd`) to prevent external_directory workdirs from shadowing project tools - Change `skill test` to FAIL (not warn) on broken paired tools: timeouts, non-zero exits, and spawn failures now set `hasErrors` - Add `.opencode/skills/` to the Discovery Paths doc section for consistency with `skill create` and the Skill Paths section - Use `tmpdir()` fixture from `test/fixture/fixture.ts` in tests instead of manual `fs.mkdtemp` + `afterAll` cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: enrich TUI skill dialog with source and paired tools (#341) Enhance the `/skills` dialog in the TUI to show: - Source category (Project / Global / Built-in) instead of flat "Skills" - Paired CLI tools in the footer (e.g., "Tools: altimate-dbt") - Reuses `detectToolReferences` and `skillSource` from `skill-helpers.ts` Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: improve skill list UX and TUI dialog (#341) CLI `skill list`: - Sort skills alphabetically - Remove SOURCE column (noisy, not useful) - Truncate descriptions on word boundaries instead of mid-word TUI `/skills` dialog: - Group skills by domain (dbt, SQL, Schema, FinOps, etc.) instead of source - Truncate descriptions to 80 chars for readability - Show paired tools compactly in footer (max 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add edit and test actions to TUI skills dialog (#341) The `/skills` dialog now supports keybind actions on the selected skill: - `ctrl+e` — open the SKILL.md in `$EDITOR` (skips built-in skills) - `ctrl+t` — run `skill test` and show PASS/FAIL result as a toast This makes the TUI a complete skill management surface (browse, use, edit, test) without dropping to the CLI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add `skill install` and `skill show` commands (#341) New subcommands for the skill CLI: `skill install <source>`: - Install skills from GitHub repos: `altimate-code skill install anthropics/skills` - Install from URL: `altimate-code skill install https://github.com/owner/repo.git` - Install from local path: `altimate-code skill install ./my-skills` - `--global` / `-g` flag for user-wide installation - Duplicate protection (skips already-installed skills) - Copies full skill directories (SKILL.md + references/ + scripts/) - Cleans up temp clones after install `skill show <name>`: - Display full skill content (name, description, location, tools, content) - Quick inspection without opening an editor Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: align TUI skill operations with CLI commands (#341) Add all CLI skill operations to the TUI for feature parity: `/skills` dialog keybinds: - `ctrl+o` — view skill content (maps to `skill show`) - `ctrl+e` — edit SKILL.md in $EDITOR - `ctrl+t` — test skill and show PASS/FAIL toast (maps to `skill test`) Slash commands (command palette): - `/skill-create <name>` — scaffolds a new skill (maps to `skill create`) - `/skill-install <source>` — install from GitHub/URL (maps to `skill install`) CLI ↔ TUI mapping: skill list → /skills dialog skill show → ctrl+o in dialog skill create → /skill-create skill test → ctrl+t in dialog skill install → /skill-install Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: final validation fixes from release testing (#341) - Fix name regex: allow single-char first segment (e.g., `a-tool`, `x-ray`) by changing `[a-z][a-z0-9]+` to `[a-z][a-z0-9]*` with separate length check - Validate empty/whitespace source in `skill install` (was treating `""` as cwd) - Add test cases for valid hyphenated names and adversarial install inputs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace fake slash commands with real TUI dialogs for create/install (#341) Remove non-functional `/skill-create` and `/skill-install` slash commands that only pre-filled input text. Replace with real keybind actions in the `/skills` dialog: - `ctrl+n` (create) — opens `DialogPrompt` for skill name, then runs `altimate-code skill create` and shows result as toast - `ctrl+i` (install) — opens `DialogPrompt` for source (owner/repo, URL, or path), then runs `altimate-code skill install` and shows result Full CLI ↔ TUI parity: skill list → /skills dialog skill show → ctrl+o in dialog skill create → ctrl+n in dialog (input prompt) skill test → ctrl+t in dialog skill install → ctrl+i in dialog (input prompt) skill edit → ctrl+e in dialog Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use `process.argv[0]` instead of `"altimate-code"` in TUI subprocess spawns (#341) TUI keybind actions (create, test, install) were spawning `"altimate-code"` which resolves to the system-installed binary, not the currently running one. This causes failures when running a local build or a different version than what's installed globally. Fix: use `process.argv[0]` which is the path to the current running binary in all three spawn sites. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: improve TUI error reporting for skill create/install/test (#341) - Add explicit `env: { ...process.env }` to all Bun.spawn calls to ensure NODE_PATH and other env vars are forwarded to subprocess - Improve error messages: show both stderr and stdout on failure, truncate to 200 chars, include error class name for catch blocks - Consistent error format across create, install, and test actions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: inline skill operations in TUI instead of spawning subprocess (#341) The TUI dialog was spawning `process.argv[0] skill install ...` which failed because `process.argv[0]` in the compiled Bun binary doesn't resolve correctly for self-invocation (error: Script not found "skill"). Fix: inline all skill operations directly in the TUI component: - `createSkillDirect()` — creates SKILL.md + tool via `fs` operations - `installSkillDirect()` — clones repo via `git clone`, copies SKILL.md files via `fs`, no subprocess self-invocation needed - `testSkillDirect()` — spawns the tool's `--help` directly (not via the altimate-code binary) This eliminates the subprocess spawning problem entirely. The TUI now uses the same `Instance`, `Global`, and `fs` APIs as the CLI commands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use async `Bun.spawn` for git clone in TUI install (#341) `Bun.spawnSync` blocks the TUI event loop during `git clone`, causing the install toast to show briefly then disappear with nothing installed. Fix: switch to async `Bun.spawn` with `await proc.exited` so the TUI stays responsive while cloning. Read stderr via `new Response().text()` for error messages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: invalidate skill cache after TUI create/install so new skills appear (#341) The TUI skill dialog showed stale data after installing or creating skills because `Skill.state` is a singleton cache (via `Instance.state`) that never re-scans. Fix: - Add `State.invalidate(key, init)` to clear a single cached state entry without disposing all state for the directory - Add `Skill.invalidate()` that clears the skill cache, so the next call to `Skill.all()` re-scans all skill directories - Call `Skill.invalidate()` after successful create/install in the TUI dialog, so reopening `/skills` shows the newly installed skills Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: invalidate server-side skill cache via API after TUI install/create (#341) The TUI runs in the main thread but the server (which serves the skills API) runs in a worker thread. Calling `Skill.invalidate()` from the TUI component had no effect because it cleared the cache in the wrong JS context. Fix: - Add `?reload=true` query param support to `GET /skill` endpoint that calls `Skill.invalidate()` in the server/worker context - TUI calls `GET /skill?reload=true` via `sdk.fetch()` after successful install/create to invalidate the worker's cache - Next `/skills` dialog open fetches fresh data from server Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: improve TUI install/create confirmation UX (#341) Problems: - "Installing..." toast disappeared immediately (short default duration) - No verification that skills actually loaded after install - No guidance on what to do next Fix: - Set 30s duration on "Installing..." toast so it persists during clone - After install/create, call `GET /skill?reload=true` and verify each installed skill appears in the returned list - Show detailed confirmation toast (8s duration) with: - Count and bullet list of installed skill names - Guidance: "Open /skills to browse, or type /<skill-name> to use" - Show explicit error if install succeeded but skills didn't load - Same pattern for create: confirms name and location Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add try/catch and input sanitization to TUI install/create (#341) Root cause of silent failures: `onConfirm` async callbacks had no try/catch, so any thrown error was swallowed and no result toast shown. Fixes: - Wrap all install/create logic in try/catch with error toast - Strip trailing dots from input (textarea was appending `.`) - Strip `.git` suffix from URLs (users paste from browser) - Trim whitespace and validate before proceeding - "Installing..." toast now shows 60s duration with helpful text ("This may take a moment while the repo is cloned") - Empty input shows immediate error instead of proceeding Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use 10min timeout for in-progress toast so it never disappears before completion (#341) The "Installing..." toast used a 60s timeout which could expire on slow connections. Changed to 600s (10 min) — effectively infinite since the result toast (`toast.show`) always replaces it on completion, clearing the old timeout automatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: show live progress log in TUI install toast (#341) Instead of a static "Installing..." message, the toast now updates in real-time as each step completes: Installing from dagster-io/skills Cloning dagster-io/skills... → Cloned. Scanning for skills... → Found 2 skill(s). Installing... → Installed 1/2: dignified-python → Installed 2/2: dagster-expert → Verifying skills loaded... Each step calls `toast.show()` which replaces the previous message, keeping the toast visible throughout. The user always knows what's happening and how far along the operation is. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove Instance/Global usage from TUI dialog — use sdk.directory (#341) `Instance` and `Global` only exist in the worker thread context. The TUI dialog runs in the main thread, so accessing them throws "No context found for instance". Fix: pass `sdk.directory` (available from the SDK context in the main thread) to all skill operations instead of calling `Instance.worktree` or `Global.Path.cache`. Use `os.homedir()` for cache directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use git root for TUI skill install target directory (#341) `sdk.directory` is the cwd where the TUI was launched (e.g. `packages/opencode/`), not the git worktree root. Skills installed via TUI went to the wrong `.opencode/skills/` directory. Fix: add `gitRoot()` helper that runs `git rev-parse --show-toplevel` to resolve the actual project root, matching what Instance.worktree does in the worker thread. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add `skill remove` command and ctrl+d in TUI (#341) New CLI command: altimate-code skill remove <name> TUI keybind: ctrl+d in /skills dialog Safety: - Cannot remove skills tracked by git (part of the repo) - Cannot remove built-in skills (builtin: prefix) - Removes both skill directory and paired CLI tool Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: parse GitHub web URLs and reduce TUI keybind clutter (#341) 1. GitHub web URLs like `https://github.com/owner/repo/tree/main/skills/foo` now correctly extract `owner/repo` and clone the full repo. Previously tried to clone the `/tree/main` path which isn't a repo. 2. Reduced TUI dialog keybinds from 7 to 4 (edit, create, install, remove) so the footer isn't cramped. View and test are available via CLI (`skill show`, `skill test`). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: restore show/test keybinds and wrap footer to fit (#341) Add `flexWrap="wrap"` to the DialogSelect keybind footer so it flows to two rows when there are many keybinds, instead of overflowing off-screen. Restore all 6 keybinds in /skills dialog: show ctrl+o | edit ctrl+e | test ctrl+t create ctrl+n | install ctrl+i | remove ctrl+d Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: replace 6 keybinds with single ctrl+a action picker (#341) Instead of 6 individual keybinds cramped in the footer, the /skills dialog now shows a single clean footer: actions ctrl+a Pressing ctrl+a on any skill opens a second-level action picker: ┌ Actions: dbt-develop ──────────────────────┐ │ Show details view info, tools, location │ │ Edit in $EDITOR open SKILL.md │ │ Test paired tool run --help on CLI tool │ │ Create new skill scaffold skill + tool │ │ Install from GitHub │ │ Remove skill delete skill + tool │ └────────────────────────────────────────────────┘ - Edit and Remove are hidden for git-tracked/builtin skills - Enter on the main list still inserts /<skill> (most common action) - Revert flexWrap on DialogSelect (no longer needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: return to skill list after action picker completes (#341) Actions (show, test, remove) were calling `dialog.clear()` which closed everything. Now they call `reopenSkillList()` which replaces the action picker with a fresh skill list dialog, so the user stays in the /skills flow. - Show: toast shows, skill list reopens behind it - Test: toast shows result, skill list reopens - Remove: skill removed, skill list reopens (with updated list) - Edit: still closes dialog (external editor takes over) - Create/Install: already used dialog.replace() (unchanged) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: open skill editor in system app instead of inline terminal (#341) Opening $EDITOR with `stdio: "inherit"` conflicts with the TUI rendering, corrupts the display, and leaves the user stranded. Fix: use `open` (macOS) / `xdg-open` (Linux) to open the SKILL.md in the system default editor as a separate window. The TUI stays intact, shows a toast with the file path, and returns to the skill list. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: separate per-skill actions from global actions in TUI (#341) ctrl+a action picker now only shows per-skill actions: Show details, Edit, Test, Remove Global actions moved to main skill list footer keybinds: actions ctrl+a | new ctrl+n | install ctrl+i This avoids confusion where "Create new skill" and "Install from GitHub" appeared under "Actions: dbt-develop" even though they have nothing to do with that skill. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Esc on action picker goes back to skill list (#341) Pressing Esc on the action picker was closing the dialog entirely. Now uses the `onClose` callback of `dialog.replace()` to reopen the skill list when the action picker is dismissed. Uses `setTimeout(0)` to defer the reopen so the dialog stack pop completes first. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update skills and custom tools docs with all new commands (#341) skills.md: - Add `skill show`, `skill install`, `skill remove` to CLI section - Add TUI keybind reference table (ctrl+a actions, ctrl+n, ctrl+i) - Document GitHub URL support (web URLs, shorthand, --global) tools/custom.md: - Add "Installing Community Skills" section with install/remove examples - Document TUI install/remove flow Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review findings from 6-model consensus review (#341) Security: - Use `fs.lstat` instead of `fs.stat` during skill install to skip symlinks (prevents file disclosure from malicious repos) - Pass `dereference: false` to `fs.cp` for directory copies Bugs: - Create cache directory (`~/.cache/altimate-code`) before git clone so installs work on fresh systems - TUI `createSkillDirect` now checks if tool already exists before writing (matches CLI behavior, prevents clobbering user tools) - Add global tools dir to TUI test PATH (fixes false negatives for globally installed tools) UX: - Allow editing git-tracked skills (only block Remove, not Edit) - Split `isBuiltinOrTracked` into `isBuiltin` + `isRemovable` Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add telemetry for skill create/install/remove (#341) New telemetry event types: - `skill_created` — tracks name, language, source (cli/tui) - `skill_installed` — tracks install_source, skill_count, skill_names - `skill_removed` — tracks skill_name, source All events follow existing patterns: - Wrapped in try/catch (telemetry never breaks operations) - Use Telemetry.getContext().sessionId (empty for CLI-only) - Include timestamp and source discriminator CLI commands instrumented: create, install, remove. TUI operations not instrumented (Telemetry runs in worker thread, TUI in main thread — would need a server endpoint to bridge). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: strip .git suffix from CLI install source to prevent double-append (#341) `altimate-code skill install owner/repo.git` produced `https://github.com/owner/repo.git.git`. Now strips `.git` suffix from the source string before processing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 623d1ac commit d6a1e6b

File tree

13 files changed

+2015
-26
lines changed

13 files changed

+2015
-26
lines changed

docs/docs/configure/skills.md

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Focus on the query: $ARGUMENTS
3434

3535
Skills are loaded from these locations (in priority order):
3636

37-
1. **altimate-code directories** (project-scoped, highest priority):
37+
1. **Project directories** (project-scoped, highest priority):
38+
- `.opencode/skills/`
3839
- `.altimate-code/skill/`
3940
- `.altimate-code/skills/`
4041

@@ -88,9 +89,96 @@ altimate ships with built-in skills for common data engineering tasks. Type `/`
8889
| `/train` | Learn standards from documents/style guides |
8990
| `/training-status` | Dashboard of all learned knowledge |
9091

92+
## CLI Commands
93+
94+
Manage skills from the command line:
95+
96+
```bash
97+
# Browse skills
98+
altimate-code skill list # table view
99+
altimate-code skill list --json # JSON (for scripting)
100+
altimate-code skill show dbt-develop # view full skill content
101+
102+
# Create
103+
altimate-code skill create my-tool # scaffold skill + bash tool
104+
altimate-code skill create my-tool --language python # python tool stub
105+
altimate-code skill create my-tool --language node # node tool stub
106+
altimate-code skill create my-tool --skill-only # skill only, no CLI tool
107+
108+
# Validate
109+
altimate-code skill test my-tool # check frontmatter + tool --help
110+
111+
# Install from GitHub
112+
altimate-code skill install owner/repo # GitHub shorthand
113+
altimate-code skill install https://github.com/... # full URL (web URLs work too)
114+
altimate-code skill install ./local-path # local directory
115+
altimate-code skill install owner/repo --global # install globally
116+
117+
# Remove
118+
altimate-code skill remove my-tool # remove skill + paired tool
119+
```
120+
121+
### TUI
122+
123+
Type `/skills` in the TUI prompt to open the skill browser. From there:
124+
125+
| Key | Action |
126+
|-----|--------|
127+
| Enter | Use — inserts `/<skill-name>` into the prompt |
128+
| `ctrl+a` | Actions — show, edit, test, or remove the selected skill |
129+
| `ctrl+n` | New — scaffold a new skill + CLI tool |
130+
| `ctrl+i` | Install — install skills from a GitHub repo or URL |
131+
| Esc | Back — returns to previous screen |
132+
91133
## Adding Custom Skills
92134

93-
Add your own skills as Markdown files in `.altimate-code/skill/`:
135+
The fastest way to create a custom skill is with the scaffolder:
136+
137+
```bash
138+
altimate-code skill create freshness-check
139+
```
140+
141+
This creates two files:
142+
143+
- `.opencode/skills/freshness-check/SKILL.md` — teaches the agent when and how to use your tool
144+
- `.opencode/tools/freshness-check` — executable CLI tool stub
145+
146+
### Pairing Skills with CLI Tools
147+
148+
Skills become powerful when paired with CLI tools. Drop any executable into `.opencode/tools/` and it's automatically available on the agent's PATH:
149+
150+
```
151+
.opencode/tools/ # Project-level tools (auto-discovered)
152+
~/.config/altimate-code/tools/ # Global tools (shared across projects)
153+
```
154+
155+
A skill references its paired CLI tool through bash code blocks:
156+
157+
```markdown
158+
---
159+
name: freshness-check
160+
description: Check data freshness across tables
161+
---
162+
163+
# Freshness Check
164+
165+
## CLI Reference
166+
\`\`\`bash
167+
freshness-check --table users --threshold 24h
168+
freshness-check --all --report
169+
\`\`\`
170+
171+
## Workflow
172+
1. Ask the user which tables to check
173+
2. Run `freshness-check` with appropriate flags
174+
3. Interpret the output and suggest fixes
175+
```
176+
177+
The tool can be written in any language (bash, Python, Node.js, etc.) — as long as it's executable.
178+
179+
### Skill-Only (No CLI Tool)
180+
181+
You can also create skills as plain prompt templates:
94182

95183
```markdown
96184
---
@@ -104,9 +192,11 @@ Focus on: $ARGUMENTS
104192

105193
`$ARGUMENTS` is replaced with whatever the user types after the skill name (e.g., `/cost-review SELECT * FROM orders` passes `SELECT * FROM orders`).
106194

195+
### Skill Paths
196+
107197
Skills are loaded from these paths (highest priority first):
108198

109-
1. `.altimate-code/skill/` (project)
199+
1. `.opencode/skills/` and `.altimate-code/skill/` (project)
110200
2. `~/.altimate-code/skills/` (global)
111201
3. Custom paths via config:
112202

docs/docs/configure/tools/custom.md

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,90 @@
11
# Custom Tools
22

3-
Create custom tools using TypeScript and the altimate plugin system.
3+
There are two ways to extend altimate-code with custom tools:
44

5-
## Quick Start
5+
1. **CLI tools** (recommended) — simple executables paired with skills
6+
2. **Plugin tools** — TypeScript-based tools using the plugin API
7+
8+
## CLI Tools (Recommended)
9+
10+
The simplest way to add custom functionality. Drop any executable into `.opencode/tools/` and it's automatically available to the agent via bash.
11+
12+
### Quick Start
13+
14+
```bash
15+
# Scaffold a skill + CLI tool pair
16+
altimate-code skill create my-tool
17+
18+
# Or create manually:
19+
mkdir -p .opencode/tools
20+
cat > .opencode/tools/my-tool << 'EOF'
21+
#!/usr/bin/env bash
22+
set -euo pipefail
23+
echo "Hello from my-tool!"
24+
EOF
25+
chmod +x .opencode/tools/my-tool
26+
```
27+
28+
Tools in `.opencode/tools/` are automatically prepended to PATH when the agent runs bash commands. No configuration needed.
29+
30+
### Tool Locations
31+
32+
| Location | Scope | Auto-discovered |
33+
|----------|-------|-----------------|
34+
| `.opencode/tools/` | Project | Yes |
35+
| `~/.config/altimate-code/tools/` | Global (all projects) | Yes |
36+
37+
### Pairing with Skills
38+
39+
Create a `SKILL.md` that teaches the agent when and how to use your tool:
40+
41+
```bash
42+
altimate-code skill create my-tool --language python
43+
```
44+
45+
This creates both `.opencode/skills/my-tool/SKILL.md` and `.opencode/tools/my-tool`. Edit both files to implement your tool.
46+
47+
### Validating
48+
49+
```bash
50+
altimate-code skill test my-tool
51+
```
52+
53+
This checks that the SKILL.md is valid and the paired tool is executable.
54+
55+
### Installing Community Skills
56+
57+
Install skills (with their paired tools) from GitHub:
58+
59+
```bash
60+
# From a GitHub repo
61+
altimate-code skill install anthropics/skills
62+
altimate-code skill install dagster-io/skills
63+
64+
# From a GitHub web URL (pasted from browser)
65+
altimate-code skill install https://github.com/owner/repo/tree/main/skills/my-skill
66+
67+
# Remove an installed skill
68+
altimate-code skill remove my-skill
69+
```
70+
71+
Or use the TUI: type `/skills`, then `ctrl+i` to install or `ctrl+a` → Remove to delete.
72+
73+
### Output Conventions
74+
75+
For best results with the AI agent:
76+
77+
- **Default output:** Human-readable text (the agent reads this well)
78+
- **`--json` flag:** Structured JSON for scripting
79+
- **Summary first:** "Found 12 matches:" or "3 issues detected:"
80+
- **Errors to stderr**, results to stdout
81+
- **Exit code 0** = success, **1** = error
82+
83+
## Plugin Tools (Advanced)
84+
85+
For more complex tools that need access to the altimate-code runtime, use the TypeScript plugin system.
86+
87+
### Quick Start
688

789
1. Create a tools directory:
890

packages/opencode/src/altimate/telemetry/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,32 @@ export namespace Telemetry {
350350
skill_source: "builtin" | "global" | "project"
351351
duration_ms: number
352352
}
353+
// altimate_change start — telemetry for skill management operations
354+
| {
355+
type: "skill_created"
356+
timestamp: number
357+
session_id: string
358+
skill_name: string
359+
language: string
360+
source: "cli" | "tui"
361+
}
362+
| {
363+
type: "skill_installed"
364+
timestamp: number
365+
session_id: string
366+
install_source: string
367+
skill_count: number
368+
skill_names: string[]
369+
source: "cli" | "tui"
370+
}
371+
| {
372+
type: "skill_removed"
373+
timestamp: number
374+
session_id: string
375+
skill_name: string
376+
source: "cli" | "tui"
377+
}
378+
// altimate_change end
353379
| {
354380
type: "sql_execute_failure"
355381
timestamp: number
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// altimate_change start — shared helpers for skill CLI commands
2+
import path from "path"
3+
import fs from "fs/promises"
4+
import { Global } from "@/global"
5+
import { Instance } from "../../project/instance"
6+
7+
/** Shell builtins, common utilities, and agent tool names to filter when detecting CLI tool references. */
8+
export const SHELL_BUILTINS = new Set([
9+
// Shell builtins
10+
"echo", "cd", "export", "set", "if", "then", "else", "fi", "for", "do", "done",
11+
"case", "esac", "printf", "source", "alias", "read", "local", "return", "exit",
12+
"break", "continue", "shift", "trap", "type", "command", "builtin", "eval", "exec",
13+
"test", "true", "false",
14+
// Common CLI utilities (not user tools)
15+
"cat", "grep", "awk", "sed", "rm", "cp", "mv", "mkdir", "ls", "chmod", "which",
16+
"curl", "wget", "pwd", "touch", "head", "tail", "sort", "uniq", "wc", "tee",
17+
"xargs", "find", "tar", "gzip", "unzip", "git", "npm", "yarn", "bun", "pip",
18+
"python", "python3", "node", "bash", "sh", "zsh", "docker", "make",
19+
// System utilities unlikely to be user tools
20+
"sudo", "kill", "ps", "env", "whoami", "id", "date", "sleep", "diff", "less", "more",
21+
// Agent tool names that appear in skill content but aren't CLI tools
22+
"glob", "write", "edit",
23+
])
24+
25+
/** Detect CLI tool references inside a skill's content (bash code blocks mentioning executables). */
26+
export function detectToolReferences(content: string): string[] {
27+
const tools = new Set<string>()
28+
29+
// Match "Tools used: bash (runs `altimate-dbt` commands), ..."
30+
const toolsUsedMatch = content.match(/Tools used:\s*(.+)/i)
31+
if (toolsUsedMatch) {
32+
const refs = toolsUsedMatch[1].matchAll(/`([a-z][\w-]*)`/gi)
33+
for (const m of refs) {
34+
if (!SHELL_BUILTINS.has(m[1])) tools.add(m[1])
35+
}
36+
}
37+
38+
// Match executable names in bash code blocks: lines starting with an executable name
39+
const bashBlocks = content.matchAll(/```(?:bash|sh)\r?\n([\s\S]*?)```/g)
40+
for (const block of bashBlocks) {
41+
const lines = block[1].split("\n")
42+
for (const line of lines) {
43+
const trimmed = line.trim()
44+
if (!trimmed || trimmed.startsWith("#")) continue
45+
// Extract the first word (the command)
46+
const cmdMatch = trimmed.match(/^(?:\$\s+)?([a-z][\w.-]*(?:-[\w]+)*)/i)
47+
if (cmdMatch) {
48+
const cmd = cmdMatch[1]
49+
if (!SHELL_BUILTINS.has(cmd)) {
50+
tools.add(cmd)
51+
}
52+
}
53+
}
54+
}
55+
56+
return Array.from(tools)
57+
}
58+
59+
/** Determine the source label for a skill based on its location. */
60+
export function skillSource(location: string): string {
61+
if (location.startsWith("builtin:")) return "builtin"
62+
const home = Global.Path.home
63+
// Builtin skills shipped with altimate-code
64+
if (location.startsWith(path.join(home, ".altimate", "builtin"))) return "builtin"
65+
// Global user skills (~/.claude/skills/, ~/.agents/skills/, ~/.config/altimate-code/skills/)
66+
const globalDirs = [
67+
path.join(home, ".claude", "skills"),
68+
path.join(home, ".agents", "skills"),
69+
path.join(home, ".altimate-code", "skills"),
70+
path.join(Global.Path.config, "skills"),
71+
]
72+
if (globalDirs.some((dir) => location.startsWith(dir))) return "global"
73+
// Everything else is project-level
74+
return "project"
75+
}
76+
77+
/** Check if a tool is available on the current PATH (including .opencode/tools/). */
78+
export async function isToolOnPath(toolName: string, cwd: string): Promise<boolean> {
79+
// Check .opencode/tools/ in both cwd and worktree (they may differ in monorepos)
80+
const dirsToCheck = new Set([
81+
path.join(cwd, ".opencode", "tools"),
82+
path.join(Instance.worktree !== "/" ? Instance.worktree : cwd, ".opencode", "tools"),
83+
path.join(Global.Path.config, "tools"),
84+
])
85+
86+
for (const dir of dirsToCheck) {
87+
try {
88+
await fs.access(path.join(dir, toolName), fs.constants.X_OK)
89+
return true
90+
} catch {}
91+
}
92+
93+
// Check system PATH
94+
const sep = process.platform === "win32" ? ";" : ":"
95+
const binDir = process.env.ALTIMATE_BIN_DIR
96+
const pathDirs = (process.env.PATH ?? "").split(sep).filter(Boolean)
97+
if (binDir) pathDirs.unshift(binDir)
98+
99+
for (const dir of pathDirs) {
100+
try {
101+
await fs.access(path.join(dir, toolName), fs.constants.X_OK)
102+
return true
103+
} catch {}
104+
}
105+
106+
return false
107+
}
108+
// altimate_change end

0 commit comments

Comments
 (0)