@mariozechner/pi-coding-agent 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,28 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## [0.19.1] - 2025-12-12
4
-
5
- ### Fixed
3
+ ## [0.20.0] - 2025-12-13
6
4
 
7
- - Documentation: Added skills system documentation to README (setup, usage, CLI flags, settings)
5
+ ### Breaking Changes
8
6
 
9
- ## [Unreleased]
7
+ - **Pi skills now use `SKILL.md` convention**: Pi skills must now be named `SKILL.md` inside a directory, matching Codex CLI format. Previously any `*.md` file was treated as a skill. Migrate by renaming `~/.pi/agent/skills/foo.md` to `~/.pi/agent/skills/foo/SKILL.md`.
10
8
 
11
9
  ### Added
12
10
 
13
11
  - Display loaded skills on startup in interactive mode
14
12
 
15
- ### Fixed
16
-
17
- ### Changed
18
-
19
-
20
- ### Added
13
+ ## [0.19.1] - 2025-12-12
21
14
 
22
15
  ### Fixed
23
16
 
24
- ### Changed
25
-
17
+ - Documentation: Added skills system documentation to README (setup, usage, CLI flags, settings)
26
18
 
27
19
  ## [0.19.0] - 2025-12-12
28
20
 
package/README.md CHANGED
@@ -470,14 +470,16 @@ Usage: `/component Button "onClick handler" "disabled support"`
470
470
  Skills are instruction files loaded on-demand when tasks match their descriptions. Compatible with Claude Code and Codex CLI skill formats.
471
471
 
472
472
  **Skill locations:**
473
- - Pi user: `~/.pi/agent/skills/**/*.md` (recursive)
474
- - Pi project: `.pi/skills/**/*.md` (recursive)
473
+ - Pi user: `~/.pi/agent/skills/**/SKILL.md` (recursive, colon-separated names)
474
+ - Pi project: `.pi/skills/**/SKILL.md` (recursive, colon-separated names)
475
475
  - Claude Code user: `~/.claude/skills/*/SKILL.md` (one level)
476
476
  - Claude Code project: `.claude/skills/*/SKILL.md` (one level)
477
477
  - Codex CLI: `~/.codex/skills/**/SKILL.md` (recursive)
478
478
 
479
479
  Later locations win on name collisions (Pi skills override Claude/Codex).
480
480
 
481
+ Pi skills in subdirectories use colon-separated names: `~/.pi/agent/skills/db/migrate/SKILL.md` → `db:migrate`
482
+
481
483
  **Format:**
482
484
 
483
485
  ```markdown
@@ -494,8 +496,8 @@ Helper scripts: {baseDir}/scripts/
494
496
  ```
495
497
 
496
498
  - `description`: Required. Shown in system prompt for agent to decide when to load.
497
- - `name`: Optional. Overrides filename/directory name.
498
- - `{baseDir}`: Replaced with skill's directory path.
499
+ - `name`: Optional. Overrides directory name.
500
+ - `{baseDir}`: Placeholder for the skill's directory. Agent substitutes it when following instructions.
499
501
 
500
502
  **How it works:**
501
503
 
@@ -504,8 +506,8 @@ Skills are listed in the system prompt with descriptions:
504
506
  ```
505
507
  <available_skills>
506
508
  - pdf-extract: Extract text and tables from PDF files
507
- File: ~/.pi/agent/skills/pdf-extract.md
508
- Base directory: ~/.pi/agent/skills
509
+ File: ~/.pi/agent/skills/pdf-extract/SKILL.md
510
+ Base directory: ~/.pi/agent/skills/pdf-extract
509
511
  </available_skills>
510
512
  ```
511
513
 
@@ -1 +1 @@
1
- {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,gBAAgB,GAAG,YAAY,CAAC;AAE/F,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,CAAC;CACpB;AAuJD,wBAAgB,UAAU,IAAI,KAAK,EAAE,CA6BpC","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription: string;\n}\n\nexport type SkillSource = \"user\" | \"project\" | \"claude-user\" | \"claude-project\" | \"codex-user\";\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsource: SkillSource;\n}\n\ntype SkillFormat = \"pi\" | \"claude\" | \"codex\";\n\nfunction stripQuotes(value: string): string {\n\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nfunction parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string } {\n\tconst frontmatter: SkillFrontmatter = { description: \"\" };\n\n\tconst normalizedContent = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\tif (!normalizedContent.startsWith(\"---\")) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst endIndex = normalizedContent.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst frontmatterBlock = normalizedContent.slice(4, endIndex);\n\tconst body = normalizedContent.slice(endIndex + 4).trim();\n\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w+):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tconst key = match[1];\n\t\t\tconst value = stripQuotes(match[2].trim());\n\t\t\tif (key === \"name\") {\n\t\t\t\tfrontmatter.name = value;\n\t\t\t} else if (key === \"description\") {\n\t\t\t\tfrontmatter.description = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { frontmatter, body };\n}\n\nfunction loadSkillsFromDir(dir: string, source: SkillSource, format: SkillFormat, subdir: string = \"\"): Skill[] {\n\tconst skills: Skill[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn skills;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (format === \"pi\") {\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tconst newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;\n\t\t\t\t\tskills.push(...loadSkillsFromDir(fullPath, source, format, newSubdir));\n\t\t\t\t} else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst nameFromFile = entry.name.slice(0, -3);\n\t\t\t\t\t\tconst name = frontmatter.name || (subdir ? `${subdir}:${nameFromFile}` : nameFromFile);\n\n\t\t\t\t\t\tskills.push({\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\t\tfilePath: fullPath,\n\t\t\t\t\t\t\tbaseDir: dirname(fullPath),\n\t\t\t\t\t\t\tsource,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t} else if (format === \"claude\") {\n\t\t\t\tif (!entry.isDirectory()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst skillDir = fullPath;\n\t\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\t\tif (!existsSync(skillFile)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst rawContent = readFileSync(skillFile, \"utf-8\");\n\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst name = frontmatter.name || entry.name;\n\n\t\t\t\t\tskills.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\tfilePath: skillFile,\n\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\tsource,\n\t\t\t\t\t});\n\t\t\t\t} catch {}\n\t\t\t} else if (format === \"codex\") {\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tskills.push(...loadSkillsFromDir(fullPath, source, format));\n\t\t\t\t} else if (entry.isFile() && entry.name === \"SKILL.md\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst skillDir = dirname(fullPath);\n\t\t\t\t\t\tconst name = frontmatter.name || basename(skillDir);\n\n\t\t\t\t\t\tskills.push({\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\t\tfilePath: fullPath,\n\t\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\t\tsource,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {}\n\n\treturn skills;\n}\n\nexport function loadSkills(): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\n\tconst codexUserDir = join(homedir(), \".codex\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(codexUserDir, \"codex-user\", \"codex\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst claudeUserDir = join(homedir(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeUserDir, \"claude-user\", \"claude\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst claudeProjectDir = resolve(process.cwd(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeProjectDir, \"claude-project\", \"claude\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, \"agent\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(globalSkillsDir, \"user\", \"pi\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"skills\");\n\tfor (const skill of loadSkillsFromDir(projectSkillsDir, \"project\", \"pi\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n"]}
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,gBAAgB,GAAG,YAAY,CAAC;AAE/F,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,WAAW,CAAC;CACpB;AAyID,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAgCpC","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription: string;\n}\n\nexport type SkillSource = \"user\" | \"project\" | \"claude-user\" | \"claude-project\" | \"codex-user\";\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsource: SkillSource;\n}\n\ntype SkillFormat = \"recursive\" | \"claude\";\n\nfunction stripQuotes(value: string): string {\n\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nfunction parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string } {\n\tconst frontmatter: SkillFrontmatter = { description: \"\" };\n\n\tconst normalizedContent = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\tif (!normalizedContent.startsWith(\"---\")) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst endIndex = normalizedContent.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst frontmatterBlock = normalizedContent.slice(4, endIndex);\n\tconst body = normalizedContent.slice(endIndex + 4).trim();\n\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w+):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tconst key = match[1];\n\t\t\tconst value = stripQuotes(match[2].trim());\n\t\t\tif (key === \"name\") {\n\t\t\t\tfrontmatter.name = value;\n\t\t\t} else if (key === \"description\") {\n\t\t\t\tfrontmatter.description = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { frontmatter, body };\n}\n\nfunction loadSkillsFromDir(\n\tdir: string,\n\tsource: SkillSource,\n\tformat: SkillFormat,\n\tuseColonPath: boolean = false,\n\tsubdir: string = \"\",\n): Skill[] {\n\tconst skills: Skill[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn skills;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (format === \"recursive\") {\n\t\t\t\t// Recursive format: scan directories, look for SKILL.md files\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tconst newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;\n\t\t\t\t\tskills.push(...loadSkillsFromDir(fullPath, source, format, useColonPath, newSubdir));\n\t\t\t\t} else if (entry.isFile() && entry.name === \"SKILL.md\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst skillDir = dirname(fullPath);\n\t\t\t\t\t\t// useColonPath: db:migrate (pi), otherwise just: migrate (codex)\n\t\t\t\t\t\tconst nameFromPath = useColonPath ? subdir || basename(skillDir) : basename(skillDir);\n\t\t\t\t\t\tconst name = frontmatter.name || nameFromPath;\n\n\t\t\t\t\t\tskills.push({\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\t\tfilePath: fullPath,\n\t\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\t\tsource,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t} else if (format === \"claude\") {\n\t\t\t\t// Claude format: only one level deep, each directory must contain SKILL.md\n\t\t\t\tif (!entry.isDirectory()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst skillDir = fullPath;\n\t\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\t\tif (!existsSync(skillFile)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst rawContent = readFileSync(skillFile, \"utf-8\");\n\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst name = frontmatter.name || entry.name;\n\n\t\t\t\t\tskills.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\tfilePath: skillFile,\n\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\tsource,\n\t\t\t\t\t});\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t} catch {}\n\n\treturn skills;\n}\n\nexport function loadSkills(): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\n\t// Codex: recursive, simple directory name\n\tconst codexUserDir = join(homedir(), \".codex\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(codexUserDir, \"codex-user\", \"recursive\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Claude: single level only\n\tconst claudeUserDir = join(homedir(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeUserDir, \"claude-user\", \"claude\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst claudeProjectDir = resolve(process.cwd(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeProjectDir, \"claude-project\", \"claude\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Pi: recursive, colon-separated path names\n\tconst globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, \"agent\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(globalSkillsDir, \"user\", \"recursive\", true)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"skills\");\n\tfor (const skill of loadSkillsFromDir(projectSkillsDir, \"project\", \"recursive\", true)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n"]}
@@ -35,7 +35,7 @@ function parseFrontmatter(content) {
35
35
  }
36
36
  return { frontmatter, body };
37
37
  }
38
- function loadSkillsFromDir(dir, source, format, subdir = "") {
38
+ function loadSkillsFromDir(dir, source, format, useColonPath = false, subdir = "") {
39
39
  const skills = [];
40
40
  if (!existsSync(dir)) {
41
41
  return skills;
@@ -50,25 +50,28 @@ function loadSkillsFromDir(dir, source, format, subdir = "") {
50
50
  continue;
51
51
  }
52
52
  const fullPath = join(dir, entry.name);
53
- if (format === "pi") {
53
+ if (format === "recursive") {
54
+ // Recursive format: scan directories, look for SKILL.md files
54
55
  if (entry.isDirectory()) {
55
56
  const newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;
56
- skills.push(...loadSkillsFromDir(fullPath, source, format, newSubdir));
57
+ skills.push(...loadSkillsFromDir(fullPath, source, format, useColonPath, newSubdir));
57
58
  }
58
- else if (entry.isFile() && entry.name.endsWith(".md")) {
59
+ else if (entry.isFile() && entry.name === "SKILL.md") {
59
60
  try {
60
61
  const rawContent = readFileSync(fullPath, "utf-8");
61
62
  const { frontmatter } = parseFrontmatter(rawContent);
62
63
  if (!frontmatter.description) {
63
64
  continue;
64
65
  }
65
- const nameFromFile = entry.name.slice(0, -3);
66
- const name = frontmatter.name || (subdir ? `${subdir}:${nameFromFile}` : nameFromFile);
66
+ const skillDir = dirname(fullPath);
67
+ // useColonPath: db:migrate (pi), otherwise just: migrate (codex)
68
+ const nameFromPath = useColonPath ? subdir || basename(skillDir) : basename(skillDir);
69
+ const name = frontmatter.name || nameFromPath;
67
70
  skills.push({
68
71
  name,
69
72
  description: frontmatter.description,
70
73
  filePath: fullPath,
71
- baseDir: dirname(fullPath),
74
+ baseDir: skillDir,
72
75
  source,
73
76
  });
74
77
  }
@@ -76,6 +79,7 @@ function loadSkillsFromDir(dir, source, format, subdir = "") {
76
79
  }
77
80
  }
78
81
  else if (format === "claude") {
82
+ // Claude format: only one level deep, each directory must contain SKILL.md
79
83
  if (!entry.isDirectory()) {
80
84
  continue;
81
85
  }
@@ -101,30 +105,6 @@ function loadSkillsFromDir(dir, source, format, subdir = "") {
101
105
  }
102
106
  catch { }
103
107
  }
104
- else if (format === "codex") {
105
- if (entry.isDirectory()) {
106
- skills.push(...loadSkillsFromDir(fullPath, source, format));
107
- }
108
- else if (entry.isFile() && entry.name === "SKILL.md") {
109
- try {
110
- const rawContent = readFileSync(fullPath, "utf-8");
111
- const { frontmatter } = parseFrontmatter(rawContent);
112
- if (!frontmatter.description) {
113
- continue;
114
- }
115
- const skillDir = dirname(fullPath);
116
- const name = frontmatter.name || basename(skillDir);
117
- skills.push({
118
- name,
119
- description: frontmatter.description,
120
- filePath: fullPath,
121
- baseDir: skillDir,
122
- source,
123
- });
124
- }
125
- catch { }
126
- }
127
- }
128
108
  }
129
109
  }
130
110
  catch { }
@@ -132,24 +112,27 @@ function loadSkillsFromDir(dir, source, format, subdir = "") {
132
112
  }
133
113
  export function loadSkills() {
134
114
  const skillMap = new Map();
115
+ // Codex: recursive, simple directory name
135
116
  const codexUserDir = join(homedir(), ".codex", "skills");
136
- for (const skill of loadSkillsFromDir(codexUserDir, "codex-user", "codex")) {
117
+ for (const skill of loadSkillsFromDir(codexUserDir, "codex-user", "recursive", false)) {
137
118
  skillMap.set(skill.name, skill);
138
119
  }
120
+ // Claude: single level only
139
121
  const claudeUserDir = join(homedir(), ".claude", "skills");
140
- for (const skill of loadSkillsFromDir(claudeUserDir, "claude-user", "claude")) {
122
+ for (const skill of loadSkillsFromDir(claudeUserDir, "claude-user", "claude", false)) {
141
123
  skillMap.set(skill.name, skill);
142
124
  }
143
125
  const claudeProjectDir = resolve(process.cwd(), ".claude", "skills");
144
- for (const skill of loadSkillsFromDir(claudeProjectDir, "claude-project", "claude")) {
126
+ for (const skill of loadSkillsFromDir(claudeProjectDir, "claude-project", "claude", false)) {
145
127
  skillMap.set(skill.name, skill);
146
128
  }
129
+ // Pi: recursive, colon-separated path names
147
130
  const globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, "agent", "skills");
148
- for (const skill of loadSkillsFromDir(globalSkillsDir, "user", "pi")) {
131
+ for (const skill of loadSkillsFromDir(globalSkillsDir, "user", "recursive", true)) {
149
132
  skillMap.set(skill.name, skill);
150
133
  }
151
134
  const projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, "skills");
152
- for (const skill of loadSkillsFromDir(projectSkillsDir, "project", "pi")) {
135
+ for (const skill of loadSkillsFromDir(projectSkillsDir, "project", "recursive", true)) {
153
136
  skillMap.set(skill.name, skill);
154
137
  }
155
138
  return Array.from(skillMap.values());
@@ -1 +1 @@
1
- {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription: string;\n}\n\nexport type SkillSource = \"user\" | \"project\" | \"claude-user\" | \"claude-project\" | \"codex-user\";\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsource: SkillSource;\n}\n\ntype SkillFormat = \"pi\" | \"claude\" | \"codex\";\n\nfunction stripQuotes(value: string): string {\n\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nfunction parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string } {\n\tconst frontmatter: SkillFrontmatter = { description: \"\" };\n\n\tconst normalizedContent = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\tif (!normalizedContent.startsWith(\"---\")) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst endIndex = normalizedContent.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst frontmatterBlock = normalizedContent.slice(4, endIndex);\n\tconst body = normalizedContent.slice(endIndex + 4).trim();\n\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w+):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tconst key = match[1];\n\t\t\tconst value = stripQuotes(match[2].trim());\n\t\t\tif (key === \"name\") {\n\t\t\t\tfrontmatter.name = value;\n\t\t\t} else if (key === \"description\") {\n\t\t\t\tfrontmatter.description = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { frontmatter, body };\n}\n\nfunction loadSkillsFromDir(dir: string, source: SkillSource, format: SkillFormat, subdir: string = \"\"): Skill[] {\n\tconst skills: Skill[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn skills;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (format === \"pi\") {\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tconst newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;\n\t\t\t\t\tskills.push(...loadSkillsFromDir(fullPath, source, format, newSubdir));\n\t\t\t\t} else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst nameFromFile = entry.name.slice(0, -3);\n\t\t\t\t\t\tconst name = frontmatter.name || (subdir ? `${subdir}:${nameFromFile}` : nameFromFile);\n\n\t\t\t\t\t\tskills.push({\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\t\tfilePath: fullPath,\n\t\t\t\t\t\t\tbaseDir: dirname(fullPath),\n\t\t\t\t\t\t\tsource,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t} else if (format === \"claude\") {\n\t\t\t\tif (!entry.isDirectory()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst skillDir = fullPath;\n\t\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\t\tif (!existsSync(skillFile)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst rawContent = readFileSync(skillFile, \"utf-8\");\n\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst name = frontmatter.name || entry.name;\n\n\t\t\t\t\tskills.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\tfilePath: skillFile,\n\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\tsource,\n\t\t\t\t\t});\n\t\t\t\t} catch {}\n\t\t\t} else if (format === \"codex\") {\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tskills.push(...loadSkillsFromDir(fullPath, source, format));\n\t\t\t\t} else if (entry.isFile() && entry.name === \"SKILL.md\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst skillDir = dirname(fullPath);\n\t\t\t\t\t\tconst name = frontmatter.name || basename(skillDir);\n\n\t\t\t\t\t\tskills.push({\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\t\tfilePath: fullPath,\n\t\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\t\tsource,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {}\n\n\treturn skills;\n}\n\nexport function loadSkills(): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\n\tconst codexUserDir = join(homedir(), \".codex\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(codexUserDir, \"codex-user\", \"codex\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst claudeUserDir = join(homedir(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeUserDir, \"claude-user\", \"claude\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst claudeProjectDir = resolve(process.cwd(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeProjectDir, \"claude-project\", \"claude\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, \"agent\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(globalSkillsDir, \"user\", \"pi\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"skills\");\n\tfor (const skill of loadSkillsFromDir(projectSkillsDir, \"project\", \"pi\")) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n"]}
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription: string;\n}\n\nexport type SkillSource = \"user\" | \"project\" | \"claude-user\" | \"claude-project\" | \"codex-user\";\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsource: SkillSource;\n}\n\ntype SkillFormat = \"recursive\" | \"claude\";\n\nfunction stripQuotes(value: string): string {\n\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nfunction parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string } {\n\tconst frontmatter: SkillFrontmatter = { description: \"\" };\n\n\tconst normalizedContent = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\tif (!normalizedContent.startsWith(\"---\")) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst endIndex = normalizedContent.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst frontmatterBlock = normalizedContent.slice(4, endIndex);\n\tconst body = normalizedContent.slice(endIndex + 4).trim();\n\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w+):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tconst key = match[1];\n\t\t\tconst value = stripQuotes(match[2].trim());\n\t\t\tif (key === \"name\") {\n\t\t\t\tfrontmatter.name = value;\n\t\t\t} else if (key === \"description\") {\n\t\t\t\tfrontmatter.description = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { frontmatter, body };\n}\n\nfunction loadSkillsFromDir(\n\tdir: string,\n\tsource: SkillSource,\n\tformat: SkillFormat,\n\tuseColonPath: boolean = false,\n\tsubdir: string = \"\",\n): Skill[] {\n\tconst skills: Skill[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn skills;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (format === \"recursive\") {\n\t\t\t\t// Recursive format: scan directories, look for SKILL.md files\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tconst newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;\n\t\t\t\t\tskills.push(...loadSkillsFromDir(fullPath, source, format, useColonPath, newSubdir));\n\t\t\t\t} else if (entry.isFile() && entry.name === \"SKILL.md\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst skillDir = dirname(fullPath);\n\t\t\t\t\t\t// useColonPath: db:migrate (pi), otherwise just: migrate (codex)\n\t\t\t\t\t\tconst nameFromPath = useColonPath ? subdir || basename(skillDir) : basename(skillDir);\n\t\t\t\t\t\tconst name = frontmatter.name || nameFromPath;\n\n\t\t\t\t\t\tskills.push({\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\t\tfilePath: fullPath,\n\t\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\t\tsource,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t} else if (format === \"claude\") {\n\t\t\t\t// Claude format: only one level deep, each directory must contain SKILL.md\n\t\t\t\tif (!entry.isDirectory()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst skillDir = fullPath;\n\t\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\t\tif (!existsSync(skillFile)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst rawContent = readFileSync(skillFile, \"utf-8\");\n\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst name = frontmatter.name || entry.name;\n\n\t\t\t\t\tskills.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\tfilePath: skillFile,\n\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\tsource,\n\t\t\t\t\t});\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t} catch {}\n\n\treturn skills;\n}\n\nexport function loadSkills(): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\n\t// Codex: recursive, simple directory name\n\tconst codexUserDir = join(homedir(), \".codex\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(codexUserDir, \"codex-user\", \"recursive\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Claude: single level only\n\tconst claudeUserDir = join(homedir(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeUserDir, \"claude-user\", \"claude\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst claudeProjectDir = resolve(process.cwd(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(claudeProjectDir, \"claude-project\", \"claude\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Pi: recursive, colon-separated path names\n\tconst globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, \"agent\", \"skills\");\n\tfor (const skill of loadSkillsFromDir(globalSkillsDir, \"user\", \"recursive\", true)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"skills\");\n\tfor (const skill of loadSkillsFromDir(projectSkillsDir, \"project\", \"recursive\", true)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n"]}
package/docs/skills.md CHANGED
@@ -9,8 +9,8 @@ Skills are discovered from these locations (in order of priority, later wins on
9
9
  1. `~/.codex/skills/**/SKILL.md` (Codex CLI user skills, recursive)
10
10
  2. `~/.claude/skills/*/SKILL.md` (Claude Code user skills)
11
11
  3. `<cwd>/.claude/skills/*/SKILL.md` (Claude Code project skills)
12
- 4. `~/.pi/agent/skills/**/*.md` (Pi user skills, recursive)
13
- 5. `<cwd>/.pi/skills/**/*.md` (Pi project skills, recursive)
12
+ 4. `~/.pi/agent/skills/**/SKILL.md` (Pi user skills, recursive)
13
+ 5. `<cwd>/.pi/skills/**/SKILL.md` (Pi project skills, recursive)
14
14
 
15
15
  Skill names and descriptions are listed in the system prompt. When a task matches a skill's description, the agent uses the `read` tool to load it.
16
16
 
@@ -43,13 +43,13 @@ The parser only supports single-line `key: value` syntax. Multiline YAML blocks
43
43
 
44
44
  ### Variables
45
45
 
46
- `{baseDir}` is replaced with the directory containing the skill file. Use it to reference bundled scripts or resources.
46
+ Use `{baseDir}` as a placeholder for the skill's directory. The agent is told each skill's base directory and will substitute it when following the instructions.
47
47
 
48
- ### Subdirectories (Pi Skills)
48
+ ### Subdirectories
49
49
 
50
- Pi skills in subdirectories use colon-separated names:
51
- - `~/.pi/agent/skills/db/migrate.md` → `db:migrate`
52
- - `<cwd>/.pi/skills/aws/s3/upload.md` → `aws:s3:upload`
50
+ Pi and Codex skills in subdirectories use colon-separated names:
51
+ - `~/.pi/agent/skills/db/migrate/SKILL.md` → `db:migrate`
52
+ - `<cwd>/.pi/skills/aws/s3/upload/SKILL.md` → `aws:s3:upload`
53
53
 
54
54
  ## Claude Code Compatibility
55
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mariozechner/pi-coding-agent",
3
- "version": "0.19.2",
3
+ "version": "0.20.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -39,9 +39,9 @@
39
39
  "prepublishOnly": "npm run clean && npm run build"
40
40
  },
41
41
  "dependencies": {
42
- "@mariozechner/pi-agent-core": "^0.19.2",
43
- "@mariozechner/pi-ai": "^0.19.2",
44
- "@mariozechner/pi-tui": "^0.19.2",
42
+ "@mariozechner/pi-agent-core": "^0.20.0",
43
+ "@mariozechner/pi-ai": "^0.20.0",
44
+ "@mariozechner/pi-tui": "^0.20.0",
45
45
  "chalk": "^5.5.0",
46
46
  "diff": "^8.0.2",
47
47
  "glob": "^11.0.3",