@@ -7,113 +7,7 @@ import { bootstrap } from "../bootstrap"
77import { cmd } from "./cmd"
88import { Instance } from "../../project/instance"
99import { Global } from "@/global"
10-
11- // ---------------------------------------------------------------------------
12- // Helpers
13- // ---------------------------------------------------------------------------
14-
15- /** Shell builtins, common utilities, and agent tool names to filter when detecting CLI tool references. */
16- const SHELL_BUILTINS = new Set ( [
17- // Shell builtins
18- "echo" , "cd" , "export" , "set" , "if" , "then" , "else" , "fi" , "for" , "do" , "done" ,
19- "case" , "esac" , "printf" , "source" , "alias" , "read" , "local" , "return" , "exit" ,
20- "break" , "continue" , "shift" , "trap" , "type" , "command" , "builtin" , "eval" , "exec" ,
21- "test" , "true" , "false" ,
22- // Common CLI utilities (not user tools)
23- "cat" , "grep" , "awk" , "sed" , "rm" , "cp" , "mv" , "mkdir" , "ls" , "chmod" , "which" ,
24- "curl" , "wget" , "pwd" , "touch" , "head" , "tail" , "sort" , "uniq" , "wc" , "tee" ,
25- "xargs" , "find" , "tar" , "gzip" , "unzip" , "git" , "npm" , "yarn" , "bun" , "pip" ,
26- "python" , "python3" , "node" , "bash" , "sh" , "zsh" , "docker" , "make" ,
27- // System utilities unlikely to be user tools
28- "sudo" , "kill" , "ps" , "env" , "whoami" , "id" , "date" , "sleep" , "diff" , "less" , "more" ,
29- // Agent tool names that appear in skill content but aren't CLI tools
30- "glob" , "write" , "edit" ,
31- ] )
32-
33- /** Detect CLI tool references inside a skill's content (bash code blocks mentioning executables). */
34- function detectToolReferences ( content : string ) : string [ ] {
35- const tools = new Set < string > ( )
36-
37- // Match "Tools used: bash (runs `altimate-dbt` commands), ..."
38- const toolsUsedMatch = content . match ( / T o o l s u s e d : \s * ( .+ ) / i)
39- if ( toolsUsedMatch ) {
40- const refs = toolsUsedMatch [ 1 ] . matchAll ( / ` ( [ a - z ] [ \w - ] * ) ` / gi)
41- for ( const m of refs ) {
42- if ( ! SHELL_BUILTINS . has ( m [ 1 ] ) ) tools . add ( m [ 1 ] )
43- }
44- }
45-
46- // Match executable names in bash code blocks: lines starting with an executable name
47- const bashBlocks = content . matchAll ( / ` ` ` (?: b a s h | s h ) \r ? \n ( [ \s \S ] * ?) ` ` ` / g)
48- for ( const block of bashBlocks ) {
49- const lines = block [ 1 ] . split ( "\n" )
50- for ( const line of lines ) {
51- const trimmed = line . trim ( )
52- if ( ! trimmed || trimmed . startsWith ( "#" ) ) continue
53- // Extract the first word (the command)
54- const cmdMatch = trimmed . match ( / ^ (?: \$ \s + ) ? ( [ a - z ] [ \w . - ] * (?: - [ \w ] + ) * ) / i)
55- if ( cmdMatch ) {
56- const cmd = cmdMatch [ 1 ]
57- // Filter out common shell builtins and generic commands
58- if ( ! SHELL_BUILTINS . has ( cmd ) ) {
59- tools . add ( cmd )
60- }
61- }
62- }
63- }
64-
65- return Array . from ( tools )
66- }
67-
68- /** Determine the source label for a skill based on its location. */
69- function skillSource ( location : string ) : string {
70- if ( location . startsWith ( "builtin:" ) ) return "builtin"
71- const home = Global . Path . home
72- // Builtin skills shipped with altimate-code
73- if ( location . startsWith ( path . join ( home , ".altimate" , "builtin" ) ) ) return "builtin"
74- // Global user skills (~/.claude/skills/, ~/.agents/skills/, ~/.config/altimate-code/skills/)
75- const globalDirs = [
76- path . join ( home , ".claude" , "skills" ) ,
77- path . join ( home , ".agents" , "skills" ) ,
78- path . join ( home , ".altimate-code" , "skills" ) ,
79- path . join ( Global . Path . config , "skills" ) ,
80- ]
81- if ( globalDirs . some ( ( dir ) => location . startsWith ( dir ) ) ) return "global"
82- // Everything else is project-level
83- return "project"
84- }
85-
86- /** Check if a tool is available on the current PATH (including .opencode/tools/). */
87- async function isToolOnPath ( toolName : string , cwd : string ) : Promise < boolean > {
88- // Check .opencode/tools/ in both cwd and worktree (they may differ in monorepos)
89- const dirsToCheck = new Set ( [
90- path . join ( cwd , ".opencode" , "tools" ) ,
91- path . join ( Instance . worktree !== "/" ? Instance . worktree : cwd , ".opencode" , "tools" ) ,
92- path . join ( Global . Path . config , "tools" ) ,
93- ] )
94-
95- for ( const dir of dirsToCheck ) {
96- try {
97- await fs . access ( path . join ( dir , toolName ) , fs . constants . X_OK )
98- return true
99- } catch { }
100- }
101-
102- // Check system PATH
103- const sep = process . platform === "win32" ? ";" : ":"
104- const binDir = process . env . ALTIMATE_BIN_DIR
105- const pathDirs = ( process . env . PATH ?? "" ) . split ( sep ) . filter ( Boolean )
106- if ( binDir ) pathDirs . unshift ( binDir )
107-
108- for ( const dir of pathDirs ) {
109- try {
110- await fs . access ( path . join ( dir , toolName ) , fs . constants . X_OK )
111- return true
112- } catch { }
113- }
114-
115- return false
116- }
10+ import { detectToolReferences , skillSource , isToolOnPath } from "./skill-helpers"
11711
11812// ---------------------------------------------------------------------------
11913// Templates
@@ -475,33 +369,34 @@ const SkillTestCommand = cmd({
475369 }
476370
477371 // 4. Detect and check paired tools
372+ const projectDir = Instance . directory
478373 const tools = detectToolReferences ( skill . content )
479374 if ( tools . length === 0 ) {
480375 warn ( `No CLI tool references detected in skill content` )
481376 } else {
482377 process . stdout . write ( EOL + ` Paired tools:` + EOL )
483378 for ( const tool of tools ) {
484- const available = await isToolOnPath ( tool , cwd )
379+ const available = await isToolOnPath ( tool , projectDir )
485380 if ( available ) {
486381 pass ( `"${ tool } " found on PATH` )
487382
488383 // Try running --help (with 5s timeout to prevent hangs)
489384 try {
490- const worktreeDir = Instance . worktree !== "/" ? Instance . worktree : cwd
385+ const worktreeDir = Instance . worktree !== "/" ? Instance . worktree : projectDir
491386 const toolEnv = {
492387 ...process . env ,
493388 PATH : [
494389 process . env . ALTIMATE_BIN_DIR ,
495390 path . join ( worktreeDir , ".opencode" , "tools" ) ,
496- path . join ( cwd , ".opencode" , "tools" ) ,
391+ path . join ( projectDir , ".opencode" , "tools" ) ,
497392 path . join ( Global . Path . config , "tools" ) ,
498393 process . env . PATH ,
499394 ]
500395 . filter ( Boolean )
501396 . join ( process . platform === "win32" ? ";" : ":" ) ,
502397 }
503398 const proc = Bun . spawn ( [ tool , "--help" ] , {
504- cwd,
399+ cwd : projectDir ,
505400 stdout : "pipe" ,
506401 stderr : "pipe" ,
507402 env : toolEnv ,
@@ -512,12 +407,12 @@ const SkillTestCommand = cmd({
512407 if ( exitCode === 0 ) {
513408 pass ( `"${ tool } --help" exits cleanly` )
514409 } else if ( exitCode === null || exitCode === 137 || exitCode === 143 ) {
515- warn ( `"${ tool } --help" timed out after 5s` )
410+ fail ( `"${ tool } --help" timed out after 5s` )
516411 } else {
517- warn ( `"${ tool } --help" exited with code ${ exitCode } ` )
412+ fail ( `"${ tool } --help" exited with code ${ exitCode } ` )
518413 }
519414 } catch {
520- warn ( `"${ tool } --help" failed to execute` )
415+ fail ( `"${ tool } --help" failed to execute` )
521416 }
522417 } else {
523418 fail ( `"${ tool } " not found on PATH` )
0 commit comments