Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1c5d575
feat: add `skill` CLI command and `.opencode/tools/` auto-discovery (…
anandgupta42 Mar 20, 2026
ca6ad23
fix: address code review findings for skill CLI (#341)
anandgupta42 Mar 21, 2026
4ff95df
feat: enrich TUI skill dialog with source and paired tools (#341)
anandgupta42 Mar 21, 2026
0beca8d
fix: improve skill list UX and TUI dialog (#341)
anandgupta42 Mar 21, 2026
50b7098
feat: add edit and test actions to TUI skills dialog (#341)
anandgupta42 Mar 21, 2026
4bb5474
feat: add `skill install` and `skill show` commands (#341)
anandgupta42 Mar 21, 2026
91dccd1
feat: align TUI skill operations with CLI commands (#341)
anandgupta42 Mar 21, 2026
16050f3
fix: final validation fixes from release testing (#341)
anandgupta42 Mar 21, 2026
c5e6055
fix: replace fake slash commands with real TUI dialogs for create/ins…
anandgupta42 Mar 22, 2026
461fefa
fix: use `process.argv[0]` instead of `"altimate-code"` in TUI subpro…
anandgupta42 Mar 22, 2026
f219664
fix: improve TUI error reporting for skill create/install/test (#341)
anandgupta42 Mar 22, 2026
1301169
fix: inline skill operations in TUI instead of spawning subprocess (#…
anandgupta42 Mar 22, 2026
4cf5800
fix: use async `Bun.spawn` for git clone in TUI install (#341)
anandgupta42 Mar 22, 2026
a581e6f
fix: invalidate skill cache after TUI create/install so new skills ap…
anandgupta42 Mar 22, 2026
c5c6acf
fix: invalidate server-side skill cache via API after TUI install/cre…
anandgupta42 Mar 22, 2026
eb0b88c
fix: improve TUI install/create confirmation UX (#341)
anandgupta42 Mar 22, 2026
72f342b
fix: add try/catch and input sanitization to TUI install/create (#341)
anandgupta42 Mar 22, 2026
f31d0da
fix: use 10min timeout for in-progress toast so it never disappears b…
anandgupta42 Mar 22, 2026
8e48701
feat: show live progress log in TUI install toast (#341)
anandgupta42 Mar 22, 2026
6a27faa
fix: remove Instance/Global usage from TUI dialog — use sdk.directory…
anandgupta42 Mar 22, 2026
0096aeb
fix: use git root for TUI skill install target directory (#341)
anandgupta42 Mar 22, 2026
4c7f19b
feat: add `skill remove` command and ctrl+d in TUI (#341)
anandgupta42 Mar 22, 2026
8b1d44d
fix: parse GitHub web URLs and reduce TUI keybind clutter (#341)
anandgupta42 Mar 22, 2026
01c9f9b
fix: restore show/test keybinds and wrap footer to fit (#341)
anandgupta42 Mar 22, 2026
879f84c
feat: replace 6 keybinds with single ctrl+a action picker (#341)
anandgupta42 Mar 22, 2026
1c14ff7
fix: return to skill list after action picker completes (#341)
anandgupta42 Mar 22, 2026
3355b6e
fix: open skill editor in system app instead of inline terminal (#341)
anandgupta42 Mar 22, 2026
0cb0829
fix: separate per-skill actions from global actions in TUI (#341)
anandgupta42 Mar 22, 2026
21e00b1
fix: Esc on action picker goes back to skill list (#341)
anandgupta42 Mar 22, 2026
fce05fd
docs: update skills and custom tools docs with all new commands (#341)
anandgupta42 Mar 22, 2026
8f918d9
fix: address code review findings from 6-model consensus review (#341)
anandgupta42 Mar 22, 2026
8a21f5d
feat: add telemetry for skill create/install/remove (#341)
anandgupta42 Mar 22, 2026
e9f7603
fix: strip .git suffix from CLI install source to prevent double-appe…
anandgupta42 Mar 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 93 additions & 3 deletions docs/docs/configure/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ Focus on the query: $ARGUMENTS

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

1. **altimate-code directories** (project-scoped, highest priority):
1. **Project directories** (project-scoped, highest priority):
- `.opencode/skills/`
- `.altimate-code/skill/`
- `.altimate-code/skills/`

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

## CLI Commands

Manage skills from the command line:

```bash
# Browse skills
altimate-code skill list # table view
altimate-code skill list --json # JSON (for scripting)
altimate-code skill show dbt-develop # view full skill content

# Create
altimate-code skill create my-tool # scaffold skill + bash tool
altimate-code skill create my-tool --language python # python tool stub
altimate-code skill create my-tool --language node # node tool stub
altimate-code skill create my-tool --skill-only # skill only, no CLI tool

# Validate
altimate-code skill test my-tool # check frontmatter + tool --help

# Install from GitHub
altimate-code skill install owner/repo # GitHub shorthand
altimate-code skill install https://github.com/... # full URL (web URLs work too)
altimate-code skill install ./local-path # local directory
altimate-code skill install owner/repo --global # install globally

# Remove
altimate-code skill remove my-tool # remove skill + paired tool
```

### TUI

Type `/skills` in the TUI prompt to open the skill browser. From there:

| Key | Action |
|-----|--------|
| Enter | Use — inserts `/<skill-name>` into the prompt |
| `ctrl+a` | Actions — show, edit, test, or remove the selected skill |
| `ctrl+n` | New — scaffold a new skill + CLI tool |
| `ctrl+i` | Install — install skills from a GitHub repo or URL |
| Esc | Back — returns to previous screen |

## Adding Custom Skills

Add your own skills as Markdown files in `.altimate-code/skill/`:
The fastest way to create a custom skill is with the scaffolder:

```bash
altimate-code skill create freshness-check
```

This creates two files:

- `.opencode/skills/freshness-check/SKILL.md` — teaches the agent when and how to use your tool
- `.opencode/tools/freshness-check` — executable CLI tool stub

### Pairing Skills with CLI Tools

Skills become powerful when paired with CLI tools. Drop any executable into `.opencode/tools/` and it's automatically available on the agent's PATH:

```
.opencode/tools/ # Project-level tools (auto-discovered)
~/.config/altimate-code/tools/ # Global tools (shared across projects)
```
Comment on lines +150 to +153
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add a language to this fenced block.

markdownlint is already flagging this fence. Mark it as text (or the appropriate language) so the docs stay lint-clean.

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 130-130: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/configure/skills.md` around lines 130 - 133, The fenced code block
containing the two paths (".opencode/tools/           # Project-level tools
(auto-discovered" and "~/.config/altimate-code/tools/  # Global tools (shared
across projects)") needs an explicit language to satisfy markdownlint; change
the opening fence from ``` to ```text (or another appropriate language) so the
block is annotated (e.g., use ```text) and save the change in the
docs/docs/configure/skills.md file where that fenced block appears.


A skill references its paired CLI tool through bash code blocks:

```markdown
---
name: freshness-check
description: Check data freshness across tables
---

# Freshness Check

## CLI Reference
\`\`\`bash
freshness-check --table users --threshold 24h
freshness-check --all --report
\`\`\`

## Workflow
1. Ask the user which tables to check
2. Run `freshness-check` with appropriate flags
3. Interpret the output and suggest fixes
```

The tool can be written in any language (bash, Python, Node.js, etc.) — as long as it's executable.

### Skill-Only (No CLI Tool)

You can also create skills as plain prompt templates:

```markdown
---
Expand All @@ -104,9 +192,11 @@ Focus on: $ARGUMENTS

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

### Skill Paths

Skills are loaded from these paths (highest priority first):

1. `.altimate-code/skill/` (project)
1. `.opencode/skills/` and `.altimate-code/skill/` (project)
2. `~/.altimate-code/skills/` (global)
3. Custom paths via config:

Expand Down
86 changes: 84 additions & 2 deletions docs/docs/configure/tools/custom.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,90 @@
# Custom Tools

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

## Quick Start
1. **CLI tools** (recommended) — simple executables paired with skills
2. **Plugin tools** — TypeScript-based tools using the plugin API

## CLI Tools (Recommended)

The simplest way to add custom functionality. Drop any executable into `.opencode/tools/` and it's automatically available to the agent via bash.

### Quick Start

```bash
# Scaffold a skill + CLI tool pair
altimate-code skill create my-tool

# Or create manually:
mkdir -p .opencode/tools
cat > .opencode/tools/my-tool << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "Hello from my-tool!"
EOF
chmod +x .opencode/tools/my-tool
```

Tools in `.opencode/tools/` are automatically prepended to PATH when the agent runs bash commands. No configuration needed.

### Tool Locations

| Location | Scope | Auto-discovered |
|----------|-------|-----------------|
| `.opencode/tools/` | Project | Yes |
| `~/.config/altimate-code/tools/` | Global (all projects) | Yes |

### Pairing with Skills

Create a `SKILL.md` that teaches the agent when and how to use your tool:

```bash
altimate-code skill create my-tool --language python
```

This creates both `.opencode/skills/my-tool/SKILL.md` and `.opencode/tools/my-tool`. Edit both files to implement your tool.

### Validating

```bash
altimate-code skill test my-tool
```

This checks that the SKILL.md is valid and the paired tool is executable.

### Installing Community Skills

Install skills (with their paired tools) from GitHub:

```bash
# From a GitHub repo
altimate-code skill install anthropics/skills
altimate-code skill install dagster-io/skills

# From a GitHub web URL (pasted from browser)
altimate-code skill install https://github.com/owner/repo/tree/main/skills/my-skill

# Remove an installed skill
altimate-code skill remove my-skill
```

Or use the TUI: type `/skills`, then `ctrl+i` to install or `ctrl+a` → Remove to delete.

### Output Conventions

For best results with the AI agent:

- **Default output:** Human-readable text (the agent reads this well)
- **`--json` flag:** Structured JSON for scripting
- **Summary first:** "Found 12 matches:" or "3 issues detected:"
- **Errors to stderr**, results to stdout
- **Exit code 0** = success, **1** = error

## Plugin Tools (Advanced)

For more complex tools that need access to the altimate-code runtime, use the TypeScript plugin system.

### Quick Start

1. Create a tools directory:

Expand Down
26 changes: 26 additions & 0 deletions packages/opencode/src/altimate/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,32 @@ export namespace Telemetry {
skill_source: "builtin" | "global" | "project"
duration_ms: number
}
// altimate_change start — telemetry for skill management operations
| {
type: "skill_created"
timestamp: number
session_id: string
skill_name: string
language: string
source: "cli" | "tui"
}
| {
type: "skill_installed"
timestamp: number
session_id: string
install_source: string
skill_count: number
skill_names: string[]
source: "cli" | "tui"
}
| {
type: "skill_removed"
timestamp: number
session_id: string
skill_name: string
source: "cli" | "tui"
}
// altimate_change end
| {
type: "sql_execute_failure"
timestamp: number
Expand Down
108 changes: 108 additions & 0 deletions packages/opencode/src/cli/cmd/skill-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// altimate_change start — shared helpers for skill CLI commands
import path from "path"
import fs from "fs/promises"
import { Global } from "@/global"
import { Instance } from "../../project/instance"

/** Shell builtins, common utilities, and agent tool names to filter when detecting CLI tool references. */
export const SHELL_BUILTINS = new Set([
// Shell builtins
"echo", "cd", "export", "set", "if", "then", "else", "fi", "for", "do", "done",
"case", "esac", "printf", "source", "alias", "read", "local", "return", "exit",
"break", "continue", "shift", "trap", "type", "command", "builtin", "eval", "exec",
"test", "true", "false",
// Common CLI utilities (not user tools)
"cat", "grep", "awk", "sed", "rm", "cp", "mv", "mkdir", "ls", "chmod", "which",
"curl", "wget", "pwd", "touch", "head", "tail", "sort", "uniq", "wc", "tee",
"xargs", "find", "tar", "gzip", "unzip", "git", "npm", "yarn", "bun", "pip",
"python", "python3", "node", "bash", "sh", "zsh", "docker", "make",
// System utilities unlikely to be user tools
"sudo", "kill", "ps", "env", "whoami", "id", "date", "sleep", "diff", "less", "more",
// Agent tool names that appear in skill content but aren't CLI tools
"glob", "write", "edit",
])

/** Detect CLI tool references inside a skill's content (bash code blocks mentioning executables). */
export function detectToolReferences(content: string): string[] {
const tools = new Set<string>()

// Match "Tools used: bash (runs `altimate-dbt` commands), ..."
const toolsUsedMatch = content.match(/Tools used:\s*(.+)/i)
if (toolsUsedMatch) {
const refs = toolsUsedMatch[1].matchAll(/`([a-z][\w-]*)`/gi)
for (const m of refs) {
if (!SHELL_BUILTINS.has(m[1])) tools.add(m[1])
}
}

// Match executable names in bash code blocks: lines starting with an executable name
const bashBlocks = content.matchAll(/```(?:bash|sh)\r?\n([\s\S]*?)```/g)
for (const block of bashBlocks) {
const lines = block[1].split("\n")
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed || trimmed.startsWith("#")) continue
// Extract the first word (the command)
const cmdMatch = trimmed.match(/^(?:\$\s+)?([a-z][\w.-]*(?:-[\w]+)*)/i)
if (cmdMatch) {
const cmd = cmdMatch[1]
if (!SHELL_BUILTINS.has(cmd)) {
tools.add(cmd)
}
}
}
}

return Array.from(tools)
}

/** Determine the source label for a skill based on its location. */
export function skillSource(location: string): string {
if (location.startsWith("builtin:")) return "builtin"
const home = Global.Path.home
// Builtin skills shipped with altimate-code
if (location.startsWith(path.join(home, ".altimate", "builtin"))) return "builtin"
// Global user skills (~/.claude/skills/, ~/.agents/skills/, ~/.config/altimate-code/skills/)
const globalDirs = [
path.join(home, ".claude", "skills"),
path.join(home, ".agents", "skills"),
path.join(home, ".altimate-code", "skills"),
path.join(Global.Path.config, "skills"),
]
if (globalDirs.some((dir) => location.startsWith(dir))) return "global"
// Everything else is project-level
return "project"
}

/** Check if a tool is available on the current PATH (including .opencode/tools/). */
export async function isToolOnPath(toolName: string, cwd: string): Promise<boolean> {
// Check .opencode/tools/ in both cwd and worktree (they may differ in monorepos)
const dirsToCheck = new Set([
path.join(cwd, ".opencode", "tools"),
path.join(Instance.worktree !== "/" ? Instance.worktree : cwd, ".opencode", "tools"),
path.join(Global.Path.config, "tools"),
])

for (const dir of dirsToCheck) {
try {
await fs.access(path.join(dir, toolName), fs.constants.X_OK)
return true
} catch {}
}

// Check system PATH
const sep = process.platform === "win32" ? ";" : ":"
const binDir = process.env.ALTIMATE_BIN_DIR
const pathDirs = (process.env.PATH ?? "").split(sep).filter(Boolean)
if (binDir) pathDirs.unshift(binDir)

for (const dir of pathDirs) {
try {
await fs.access(path.join(dir, toolName), fs.constants.X_OK)
return true
} catch {}
}

return false
}
// altimate_change end
Loading
Loading