From ade61b82c709d5f117d0a079ccbcc5701369fada Mon Sep 17 00:00:00 2001 From: Daniel Marques Date: Sun, 28 Jun 2026 11:55:52 -0300 Subject: [PATCH 1/6] feat: add pi package publishing to release workflow - Add scripts/build-pi-package.mjs to build little-coder-pi package from source - Add build:pi-package npm script for local builds - Add publish-pi-package job to GitHub Actions workflow - Package includes 23 extensions and 3 skill categories - Published as little-coder-pi to npm on tagged releases --- .github/workflows/publish.yml | 27 +++++ .gitignore | 1 + package.json | 3 +- scripts/build-pi-package.mjs | 188 ++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 scripts/build-pi-package.mjs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3e53aa56..01193bad 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -94,3 +94,30 @@ jobs: --generate-notes \ --latest \ --verify-tag + + publish-pi-package: + name: Publish pi package + runs-on: ubuntu-latest + needs: publish + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-node@v5 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - run: npm ci + + - name: Build pi package + run: node scripts/build-pi-package.mjs + + - name: Publish pi package to npm + run: npm publish --provenance --access public + working-directory: ./dist/pi-package + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index a2bf7fc9..c6e4e185 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ __pycache__/ *.log debug_payload.json brainstorm_outputs/ +dist/ diff --git a/package.json b/package.json index 06e07376..0bc007d4 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "test": "vitest run", "test:py": "python3 -m pytest benchmarks/test_rpc_client.py -q", "typecheck": "tsc --noEmit", - "postinstall": "node scripts/patch-pi.mjs" + "postinstall": "node scripts/patch-pi.mjs", + "build:pi-package": "node scripts/build-pi-package.mjs" }, "dependencies": { "@earendil-works/pi-coding-agent": "^0.79.4", diff --git a/scripts/build-pi-package.mjs b/scripts/build-pi-package.mjs new file mode 100644 index 00000000..d3eddad7 --- /dev/null +++ b/scripts/build-pi-package.mjs @@ -0,0 +1,188 @@ +#!/usr/bin/env node +/** + * Build a pi package from the little-coder source tree. + * This creates a package that can be published as `little-coder-pi` (or similar) + * and installed via `npm install little-coder-pi` to get the extensions/skills. + */ +import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); +const root = resolve(here, ".."); +let pkg; +try { + pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); +} catch (e) { + console.error("Failed to read package.json:", e); + process.exit(1); +} +const version = pkg.version; + +// Extensions to include in the pi package (subset of all little-coder extensions) +// These are the ones most useful for vanilla pi users +const PI_PACKAGE_EXTENSIONS = [ + "_shared", + "extra-tools", + "output-parser", + "permission-gate", + "prompt-history", + "quality-monitor", + "read-guard", + "read-guard-edit", + "skill-inject", + "thinking-budget", + "write-guard", + "checkpoint", + "evidence", + "evidence-compact", + "knowledge-inject", + "tool-gating", + "turn-cap", + "browser", + "browser-extract-retention", + "shell-session", + "subagent", + "plan-mode", + "finalize-warn", +]; + +// Skills to include +const PI_PACKAGE_SKILLS = [ + "tools", + "knowledge", + "protocols", +]; + +const outDir = join(root, "dist", "pi-package"); +const extSrcDir = join(root, ".pi", "extensions"); +const skillsSrcDir = join(root, "skills"); + +// Clean and create output directory +rmSync(outDir, { recursive: true, force: true }); +mkdirSync(outDir, { recursive: true }); +mkdirSync(join(outDir, "extensions"), { recursive: true }); +mkdirSync(join(outDir, "skills"), { recursive: true }); + +// Copy extensions +for (const ext of PI_PACKAGE_EXTENSIONS) { + const src = join(extSrcDir, ext); + const dest = join(outDir, "extensions", ext); + if (!existsSync(src)) { + console.warn(`Warning: Extension not found: ${ext}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied extension: ${ext}`); +} + +// Copy skills +for (const skillDir of PI_PACKAGE_SKILLS) { + const src = join(skillsSrcDir, skillDir); + const dest = join(outDir, "skills", skillDir); + if (!existsSync(src)) { + console.warn(`Warning: Skills directory not found: ${skillDir}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied skills: ${skillDir}`); +} + +// Create package.json for the pi package +const piPackageJson = { + name: "little-coder-pi", + version, + description: "Pi package providing little-coder extensions and skills for vanilla pi", + type: "module", + license: "Apache-2.0", + repository: { + type: "git", + url: "git+https://github.com/itayinbarr/little-coder.git", + }, + keywords: [ + "pi-package", + "pi", + "little-coder", + "extensions", + "skills", + "coding-agent", + ], + files: [ + "extensions/", + "skills/", + "UPSTREAM.json", + "README.md", + ], + peerDependencies: { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + }, + pi: { + extensions: PI_PACKAGE_EXTENSIONS.map((e) => `./extensions/${e}`), + skills: PI_PACKAGE_SKILLS.map((s) => `./skills/${s}`), + }, + devDependencies: { + "@earendil-works/pi-ai": "^0.80.0", + "@earendil-works/pi-coding-agent": "^0.80.0", + "@earendil-works/pi-tui": "^0.80.0", + }, +}; + +writeFileSync( + join(outDir, "package.json"), + JSON.stringify(piPackageJson, null, 2) + "\n" +); + +// Create UPSTREAM.json with provenance +writeFileSync( + join(outDir, "UPSTREAM.json"), + JSON.stringify( + { + source: pkg.repository.url, + version, + builtAt: new Date().toISOString(), + included: { + extensions: PI_PACKAGE_EXTENSIONS, + skills: PI_PACKAGE_SKILLS, + }, + }, + null, + 2 + ) + "\n" +); + +// Create README.md +const readme = `# little-coder-pi + +Pi package providing [little-coder](https://github.com/itayinbarr/little-coder) extensions and skills for vanilla pi. + +## Install + +\`\`\`bash +npm install little-coder-pi +\`\`\` + +This will auto-load all included extensions and skills via pi's package system. + +## What's Included + +### Extensions (${PI_PACKAGE_EXTENSIONS.length}) +${PI_PACKAGE_EXTENSIONS.map((e) => `- ${e}`).join("\n")} + +### Skills (${PI_PACKAGE_SKILLS.length}) +${PI_PACKAGE_SKILLS.map((s) => `- ${s}`).join("\n")} + +## Upstream + +Built from little-coder v${version}. See [UPSTREAM.json](./UPSTREAM.json) for details. + +## License + +Apache-2.0 — see little-coder [LICENSE](https://github.com/itayinbarr/little-coder/blob/main/LICENSE). +`; + +writeFileSync(join(outDir, "README.md"), readme); + +console.log(`\nPi package built at: ${outDir}`); +console.log(`Run 'npm publish' from that directory to publish.`); \ No newline at end of file From b7ad2b891a154ad22f300551703ebaa452c2274f Mon Sep 17 00:00:00 2001 From: Daniel Marques Date: Sun, 28 Jun 2026 18:46:00 -0300 Subject: [PATCH 2/6] chore: format workflow YAML (double quotes, spacing) --- .github/workflows/publish.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 01193bad..db506bdc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,24 +5,24 @@ name: Publish to npm # version, so a typo in either place is caught before anything reaches npm. on: push: - tags: ['v[0-9]*'] + tags: ["v[0-9]*"] jobs: publish: name: Publish on tag runs-on: ubuntu-latest permissions: - contents: write # required to create the GitHub Release - id-token: write # required for npm provenance via GitHub OIDC + contents: write # required to create the GitHub Release + id-token: write # required for npm provenance via GitHub OIDC steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: # Must match package.json#engines.node (>= 22.19.0); pi 0.75+ requires it. - node-version: '22' - registry-url: 'https://registry.npmjs.org' - cache: 'npm' + node-version: "22" + registry-url: "https://registry.npmjs.org" + cache: "npm" - name: Verify tag matches package.json version run: | @@ -107,9 +107,9 @@ jobs: - uses: actions/setup-node@v5 with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' - cache: 'npm' + node-version: "22" + registry-url: "https://registry.npmjs.org" + cache: "npm" - run: npm ci From 2629dff612ad9da40fbd788e464c80763cea6112 Mon Sep 17 00:00:00 2001 From: Daniel Marques Date: Sun, 28 Jun 2026 18:59:08 -0300 Subject: [PATCH 3/6] fix: clean up extension list in pi package build script Remove duplicate entries and ensure only generally-useful extensions are included. Excludes: benchmark-profiles, branding, clear-command, hello, llama-cpp-provider, shortcuts-help, turn-cap (little-coder/benchmark specific) --- scripts/build-pi-package.mjs | 197 +++++++++++++++++------------------ 1 file changed, 98 insertions(+), 99 deletions(-) diff --git a/scripts/build-pi-package.mjs b/scripts/build-pi-package.mjs index d3eddad7..33e58495 100644 --- a/scripts/build-pi-package.mjs +++ b/scripts/build-pi-package.mjs @@ -12,46 +12,45 @@ const here = dirname(fileURLToPath(import.meta.url)); const root = resolve(here, ".."); let pkg; try { - pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); + pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); } catch (e) { - console.error("Failed to read package.json:", e); - process.exit(1); + console.error("Failed to read package.json:", e); + process.exit(1); } const version = pkg.version; // Extensions to include in the pi package (subset of all little-coder extensions) // These are the ones most useful for vanilla pi users const PI_PACKAGE_EXTENSIONS = [ - "_shared", - "extra-tools", - "output-parser", - "permission-gate", - "prompt-history", - "quality-monitor", - "read-guard", - "read-guard-edit", - "skill-inject", - "thinking-budget", - "write-guard", - "checkpoint", - "evidence", - "evidence-compact", - "knowledge-inject", - "tool-gating", - "turn-cap", - "browser", - "browser-extract-retention", - "shell-session", - "subagent", - "plan-mode", - "finalize-warn", + "_shared", + "extra-tools", + "output-parser", + "permission-gate", + "prompt-history", + "quality-monitor", + "read-guard", + "read-guard-edit", + "skill-inject", + "thinking-budget", + "write-guard", + "checkpoint", + "evidence", + "evidence-compact", + "knowledge-inject", + "tool-gating", + "browser", + "browser-extract-retention", + "shell-session", + "subagent", + "plan-mode", + "finalize-warn" ]; // Skills to include const PI_PACKAGE_SKILLS = [ - "tools", - "knowledge", - "protocols", + "tools", + "knowledge", + "protocols", ]; const outDir = join(root, "dist", "pi-package"); @@ -66,90 +65,90 @@ mkdirSync(join(outDir, "skills"), { recursive: true }); // Copy extensions for (const ext of PI_PACKAGE_EXTENSIONS) { - const src = join(extSrcDir, ext); - const dest = join(outDir, "extensions", ext); - if (!existsSync(src)) { - console.warn(`Warning: Extension not found: ${ext}`); - continue; - } - cpSync(src, dest, { recursive: true }); - console.log(`Copied extension: ${ext}`); + const src = join(extSrcDir, ext); + const dest = join(outDir, "extensions", ext); + if (!existsSync(src)) { + console.warn(`Warning: Extension not found: ${ext}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied extension: ${ext}`); } // Copy skills for (const skillDir of PI_PACKAGE_SKILLS) { - const src = join(skillsSrcDir, skillDir); - const dest = join(outDir, "skills", skillDir); - if (!existsSync(src)) { - console.warn(`Warning: Skills directory not found: ${skillDir}`); - continue; - } - cpSync(src, dest, { recursive: true }); - console.log(`Copied skills: ${skillDir}`); + const src = join(skillsSrcDir, skillDir); + const dest = join(outDir, "skills", skillDir); + if (!existsSync(src)) { + console.warn(`Warning: Skills directory not found: ${skillDir}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied skills: ${skillDir}`); } // Create package.json for the pi package const piPackageJson = { - name: "little-coder-pi", - version, - description: "Pi package providing little-coder extensions and skills for vanilla pi", - type: "module", - license: "Apache-2.0", - repository: { - type: "git", - url: "git+https://github.com/itayinbarr/little-coder.git", - }, - keywords: [ - "pi-package", - "pi", - "little-coder", - "extensions", - "skills", - "coding-agent", - ], - files: [ - "extensions/", - "skills/", - "UPSTREAM.json", - "README.md", - ], - peerDependencies: { - "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*", - "@earendil-works/pi-tui": "*", - }, - pi: { - extensions: PI_PACKAGE_EXTENSIONS.map((e) => `./extensions/${e}`), - skills: PI_PACKAGE_SKILLS.map((s) => `./skills/${s}`), - }, - devDependencies: { - "@earendil-works/pi-ai": "^0.80.0", - "@earendil-works/pi-coding-agent": "^0.80.0", - "@earendil-works/pi-tui": "^0.80.0", - }, + name: "little-coder-pi", + version, + description: "Pi package providing little-coder extensions and skills for vanilla pi", + type: "module", + license: "Apache-2.0", + repository: { + type: "git", + url: "git+https://github.com/itayinbarr/little-coder.git", + }, + keywords: [ + "pi-package", + "pi", + "little-coder", + "extensions", + "skills", + "coding-agent", + ], + files: [ + "extensions/", + "skills/", + "UPSTREAM.json", + "README.md", + ], + peerDependencies: { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + }, + pi: { + extensions: PI_PACKAGE_EXTENSIONS.map((e) => `./extensions/${e}`), + skills: PI_PACKAGE_SKILLS.map((s) => `./skills/${s}`), + }, + devDependencies: { + "@earendil-works/pi-ai": "^0.80.0", + "@earendil-works/pi-coding-agent": "^0.80.0", + "@earendil-works/pi-tui": "^0.80.0", + }, }; writeFileSync( - join(outDir, "package.json"), - JSON.stringify(piPackageJson, null, 2) + "\n" + join(outDir, "package.json"), + JSON.stringify(piPackageJson, null, 2) + "\n" ); // Create UPSTREAM.json with provenance writeFileSync( - join(outDir, "UPSTREAM.json"), - JSON.stringify( - { - source: pkg.repository.url, - version, - builtAt: new Date().toISOString(), - included: { - extensions: PI_PACKAGE_EXTENSIONS, - skills: PI_PACKAGE_SKILLS, - }, - }, - null, - 2 - ) + "\n" + join(outDir, "UPSTREAM.json"), + JSON.stringify( + { + source: pkg.repository.url, + version, + builtAt: new Date().toISOString(), + included: { + extensions: PI_PACKAGE_EXTENSIONS, + skills: PI_PACKAGE_SKILLS, + }, + }, + null, + 2 + ) + "\n" ); // Create README.md @@ -185,4 +184,4 @@ Apache-2.0 — see little-coder [LICENSE](https://github.com/itayinbarr/little-c writeFileSync(join(outDir, "README.md"), readme); console.log(`\nPi package built at: ${outDir}`); -console.log(`Run 'npm publish' from that directory to publish.`); \ No newline at end of file +console.log(`Run 'npm publish' from that directory to publish.`); From f8629d3558a493ddb00acf56af602dd5aa7f0c7c Mon Sep 17 00:00:00 2001 From: Daniel Marques Date: Sun, 28 Jun 2026 19:22:26 -0300 Subject: [PATCH 4/6] fix: prune test files from pi package build The build script now removes *.test.ts files after copying extensions, so they don't get loaded as invalid extensions by pi. --- scripts/build-pi-package.mjs | 212 +++++++++++++++++++---------------- 1 file changed, 113 insertions(+), 99 deletions(-) diff --git a/scripts/build-pi-package.mjs b/scripts/build-pi-package.mjs index 33e58495..406f3158 100644 --- a/scripts/build-pi-package.mjs +++ b/scripts/build-pi-package.mjs @@ -4,7 +4,7 @@ * This creates a package that can be published as `little-coder-pi` (or similar) * and installed via `npm install little-coder-pi` to get the extensions/skills. */ -import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -12,51 +12,63 @@ const here = dirname(fileURLToPath(import.meta.url)); const root = resolve(here, ".."); let pkg; try { - pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); + pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); } catch (e) { - console.error("Failed to read package.json:", e); - process.exit(1); + console.error("Failed to read package.json:", e); + process.exit(1); } const version = pkg.version; // Extensions to include in the pi package (subset of all little-coder extensions) // These are the ones most useful for vanilla pi users const PI_PACKAGE_EXTENSIONS = [ - "_shared", - "extra-tools", - "output-parser", - "permission-gate", - "prompt-history", - "quality-monitor", - "read-guard", - "read-guard-edit", - "skill-inject", - "thinking-budget", - "write-guard", - "checkpoint", - "evidence", - "evidence-compact", - "knowledge-inject", - "tool-gating", - "browser", - "browser-extract-retention", - "shell-session", - "subagent", - "plan-mode", - "finalize-warn" + "_shared", + "extra-tools", + "output-parser", + "permission-gate", + "prompt-history", + "quality-monitor", + "read-guard", + "read-guard-edit", + "skill-inject", + "thinking-budget", + "write-guard", + "checkpoint", + "evidence", + "evidence-compact", + "knowledge-inject", + "tool-gating", + "browser", + "browser-extract-retention", + "shell-session", + "subagent", + "plan-mode", + "finalize-warn" ]; // Skills to include const PI_PACKAGE_SKILLS = [ - "tools", - "knowledge", - "protocols", + "tools", + "knowledge", + "protocols" ]; const outDir = join(root, "dist", "pi-package"); const extSrcDir = join(root, ".pi", "extensions"); const skillsSrcDir = join(root, "skills"); +function pruneTests(dir) { + if (!existsSync(dir)) return; + for (const name of readdirSync(dir)) { + const full = join(dir, name); + if (statSync(full).isDirectory()) { + pruneTests(full); + continue; + } + if (name.endsWith(".test.ts")) rmSync(full, { force: true }); + } +} + // Clean and create output directory rmSync(outDir, { recursive: true, force: true }); mkdirSync(outDir, { recursive: true }); @@ -65,90 +77,92 @@ mkdirSync(join(outDir, "skills"), { recursive: true }); // Copy extensions for (const ext of PI_PACKAGE_EXTENSIONS) { - const src = join(extSrcDir, ext); - const dest = join(outDir, "extensions", ext); - if (!existsSync(src)) { - console.warn(`Warning: Extension not found: ${ext}`); - continue; - } - cpSync(src, dest, { recursive: true }); - console.log(`Copied extension: ${ext}`); + const src = join(extSrcDir, ext); + const dest = join(outDir, "extensions", ext); + if (!existsSync(src)) { + console.warn(`Warning: Extension not found: ${ext}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied extension: ${ext}`); + // Prune test files after copy + pruneTests(dest); } // Copy skills for (const skillDir of PI_PACKAGE_SKILLS) { - const src = join(skillsSrcDir, skillDir); - const dest = join(outDir, "skills", skillDir); - if (!existsSync(src)) { - console.warn(`Warning: Skills directory not found: ${skillDir}`); - continue; - } - cpSync(src, dest, { recursive: true }); - console.log(`Copied skills: ${skillDir}`); + const src = join(skillsSrcDir, skillDir); + const dest = join(outDir, "skills", skillDir); + if (!existsSync(src)) { + console.warn(`Warning: Skills directory not found: ${skillDir}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied skills: ${skillDir}`); } // Create package.json for the pi package const piPackageJson = { - name: "little-coder-pi", - version, - description: "Pi package providing little-coder extensions and skills for vanilla pi", - type: "module", - license: "Apache-2.0", - repository: { - type: "git", - url: "git+https://github.com/itayinbarr/little-coder.git", - }, - keywords: [ - "pi-package", - "pi", - "little-coder", - "extensions", - "skills", - "coding-agent", - ], - files: [ - "extensions/", - "skills/", - "UPSTREAM.json", - "README.md", - ], - peerDependencies: { - "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*", - "@earendil-works/pi-tui": "*", - }, - pi: { - extensions: PI_PACKAGE_EXTENSIONS.map((e) => `./extensions/${e}`), - skills: PI_PACKAGE_SKILLS.map((s) => `./skills/${s}`), - }, - devDependencies: { - "@earendil-works/pi-ai": "^0.80.0", - "@earendil-works/pi-coding-agent": "^0.80.0", - "@earendil-works/pi-tui": "^0.80.0", - }, + name: "little-coder-pi", + version, + description: "Pi package providing little-coder extensions and skills for vanilla pi", + type: "module", + license: "Apache-2.0", + repository: { + type: "git", + url: "git+https://github.com/itayinbarr/little-coder.git" + }, + keywords: [ + "pi-package", + "pi", + "little-coder", + "extensions", + "skills", + "coding-agent" + ], + files: [ + "extensions/", + "skills/", + "UPSTREAM.json", + "README.md" + ], + peerDependencies: { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*" + }, + pi: { + extensions: PI_PACKAGE_EXTENSIONS.map((e) => `./extensions/${e}`), + skills: PI_PACKAGE_SKILLS.map((s) => `./skills/${s}`) + }, + devDependencies: { + "@earendil-works/pi-ai": "^0.80.0", + "@earendil-works/pi-coding-agent": "^0.80.0", + "@earendil-works/pi-tui": "^0.80.0" + } }; writeFileSync( - join(outDir, "package.json"), - JSON.stringify(piPackageJson, null, 2) + "\n" + join(outDir, "package.json"), + JSON.stringify(piPackageJson, null, 2) + "\n" ); // Create UPSTREAM.json with provenance writeFileSync( - join(outDir, "UPSTREAM.json"), - JSON.stringify( - { - source: pkg.repository.url, - version, - builtAt: new Date().toISOString(), - included: { - extensions: PI_PACKAGE_EXTENSIONS, - skills: PI_PACKAGE_SKILLS, - }, - }, - null, - 2 - ) + "\n" + join(outDir, "UPSTREAM.json"), + JSON.stringify( + { + source: pkg.repository.url, + version, + builtAt: new Date().toISOString(), + included: { + extensions: PI_PACKAGE_EXTENSIONS, + skills: PI_PACKAGE_SKILLS + } + }, + null, + 2 + ) + "\n" ); // Create README.md @@ -184,4 +198,4 @@ Apache-2.0 — see little-coder [LICENSE](https://github.com/itayinbarr/little-c writeFileSync(join(outDir, "README.md"), readme); console.log(`\nPi package built at: ${outDir}`); -console.log(`Run 'npm publish' from that directory to publish.`); +console.log(`Run 'npm publish' from that directory to publish.`); \ No newline at end of file From ebee13b69649c768863f13a5c694ad0be65048be Mon Sep 17 00:00:00 2001 From: Daniel Marques Date: Sun, 28 Jun 2026 19:45:59 -0300 Subject: [PATCH 5/6] fix: exclude _shared from pi package (shared module, not extension) --- scripts/build-pi-package.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/build-pi-package.mjs b/scripts/build-pi-package.mjs index 406f3158..c9c1bb29 100644 --- a/scripts/build-pi-package.mjs +++ b/scripts/build-pi-package.mjs @@ -46,6 +46,9 @@ const PI_PACKAGE_EXTENSIONS = [ "finalize-warn" ]; +// Extensions to register as pi extensions (excludes _shared which is a utility module) +const PI_PACKAGE_PI_EXTENSIONS = PI_PACKAGE_EXTENSIONS.filter((e) => e !== "_shared"); + // Skills to include const PI_PACKAGE_SKILLS = [ "tools", @@ -132,7 +135,7 @@ const piPackageJson = { "@earendil-works/pi-tui": "*" }, pi: { - extensions: PI_PACKAGE_EXTENSIONS.map((e) => `./extensions/${e}`), + extensions: PI_PACKAGE_PI_EXTENSIONS.map((e) => `./extensions/${e}`), skills: PI_PACKAGE_SKILLS.map((s) => `./skills/${s}`) }, devDependencies: { From 7d7c9d416d6a084ec6fcd9f4a3b99b4602ad4ff8 Mon Sep 17 00:00:00 2001 From: Daniel Marques Date: Sun, 28 Jun 2026 20:12:01 -0300 Subject: [PATCH 6/6] fix: exclude internal skill dirs from pi.skills (loaded by extensions, not pi) --- scripts/build-pi-package.mjs | 241 ++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 117 deletions(-) diff --git a/scripts/build-pi-package.mjs b/scripts/build-pi-package.mjs index c9c1bb29..33cde1aa 100644 --- a/scripts/build-pi-package.mjs +++ b/scripts/build-pi-package.mjs @@ -4,7 +4,16 @@ * This creates a package that can be published as `little-coder-pi` (or similar) * and installed via `npm install little-coder-pi` to get the extensions/skills. */ -import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs"; +import { + cpSync, + existsSync, + mkdirSync, + readFileSync, + readdirSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -12,64 +21,66 @@ const here = dirname(fileURLToPath(import.meta.url)); const root = resolve(here, ".."); let pkg; try { - pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); + pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); } catch (e) { - console.error("Failed to read package.json:", e); - process.exit(1); + console.error("Failed to read package.json:", e); + process.exit(1); } const version = pkg.version; // Extensions to include in the pi package (subset of all little-coder extensions) // These are the ones most useful for vanilla pi users const PI_PACKAGE_EXTENSIONS = [ - "_shared", - "extra-tools", - "output-parser", - "permission-gate", - "prompt-history", - "quality-monitor", - "read-guard", - "read-guard-edit", - "skill-inject", - "thinking-budget", - "write-guard", - "checkpoint", - "evidence", - "evidence-compact", - "knowledge-inject", - "tool-gating", - "browser", - "browser-extract-retention", - "shell-session", - "subagent", - "plan-mode", - "finalize-warn" + "_shared", + "extra-tools", + "output-parser", + "permission-gate", + "prompt-history", + "quality-monitor", + "read-guard", + "read-guard-edit", + "skill-inject", + "thinking-budget", + "write-guard", + "checkpoint", + "evidence", + "evidence-compact", + "knowledge-inject", + "tool-gating", + "browser", + "browser-extract-retention", + "shell-session", + "subagent", + "plan-mode", + "finalize-warn", ]; // Extensions to register as pi extensions (excludes _shared which is a utility module) -const PI_PACKAGE_PI_EXTENSIONS = PI_PACKAGE_EXTENSIONS.filter((e) => e !== "_shared"); +const PI_PACKAGE_PI_EXTENSIONS = PI_PACKAGE_EXTENSIONS.filter( + (e) => e !== "_shared", +); -// Skills to include -const PI_PACKAGE_SKILLS = [ - "tools", - "knowledge", - "protocols" -]; +// Skills to include (copied for extensions to load internally) +// These are NOT standalone pi skills - they're loaded by their extensions +// tools/* -> skill-inject extension (type: tool-guidance) +// knowledge/* -> knowledge-inject extension (type: domain-knowledge) +// protocols/* -> protocols extension (type: workflow) +const PI_PACKAGE_SKILL_DIRS = ["tools", "knowledge", "protocols"]; const outDir = join(root, "dist", "pi-package"); const extSrcDir = join(root, ".pi", "extensions"); const skillsSrcDir = join(root, "skills"); function pruneTests(dir) { - if (!existsSync(dir)) return; - for (const name of readdirSync(dir)) { - const full = join(dir, name); - if (statSync(full).isDirectory()) { - pruneTests(full); - continue; - } - if (name.endsWith(".test.ts")) rmSync(full, { force: true }); - } + if (!existsSync(dir)) return; + for (const name of readdirSync(dir)) { + const full = join(dir, name); + if (statSync(full).isDirectory()) { + pruneTests(full); + continue; + } + if (name.endsWith(".test.ts")) rmSync(full, { force: true }); + } } // Clean and create output directory @@ -80,92 +91,88 @@ mkdirSync(join(outDir, "skills"), { recursive: true }); // Copy extensions for (const ext of PI_PACKAGE_EXTENSIONS) { - const src = join(extSrcDir, ext); - const dest = join(outDir, "extensions", ext); - if (!existsSync(src)) { - console.warn(`Warning: Extension not found: ${ext}`); - continue; - } - cpSync(src, dest, { recursive: true }); - console.log(`Copied extension: ${ext}`); - // Prune test files after copy - pruneTests(dest); + const src = join(extSrcDir, ext); + const dest = join(outDir, "extensions", ext); + if (!existsSync(src)) { + console.warn(`Warning: Extension not found: ${ext}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied extension: ${ext}`); + // Prune test files after copy + pruneTests(dest); } // Copy skills -for (const skillDir of PI_PACKAGE_SKILLS) { - const src = join(skillsSrcDir, skillDir); - const dest = join(outDir, "skills", skillDir); - if (!existsSync(src)) { - console.warn(`Warning: Skills directory not found: ${skillDir}`); - continue; - } - cpSync(src, dest, { recursive: true }); - console.log(`Copied skills: ${skillDir}`); +for (const skillDir of PI_PACKAGE_SKILL_DIRS) { + const src = join(skillsSrcDir, skillDir); + const dest = join(outDir, "skills", skillDir); + if (!existsSync(src)) { + console.warn(`Warning: Skills directory not found: ${skillDir}`); + continue; + } + cpSync(src, dest, { recursive: true }); + console.log(`Copied skills: ${skillDir}`); } // Create package.json for the pi package const piPackageJson = { - name: "little-coder-pi", - version, - description: "Pi package providing little-coder extensions and skills for vanilla pi", - type: "module", - license: "Apache-2.0", - repository: { - type: "git", - url: "git+https://github.com/itayinbarr/little-coder.git" - }, - keywords: [ - "pi-package", - "pi", - "little-coder", - "extensions", - "skills", - "coding-agent" - ], - files: [ - "extensions/", - "skills/", - "UPSTREAM.json", - "README.md" - ], - peerDependencies: { - "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*", - "@earendil-works/pi-tui": "*" - }, - pi: { - extensions: PI_PACKAGE_PI_EXTENSIONS.map((e) => `./extensions/${e}`), - skills: PI_PACKAGE_SKILLS.map((s) => `./skills/${s}`) - }, - devDependencies: { - "@earendil-works/pi-ai": "^0.80.0", - "@earendil-works/pi-coding-agent": "^0.80.0", - "@earendil-works/pi-tui": "^0.80.0" - } + name: "little-coder-pi", + version, + description: + "Pi package providing little-coder extensions and skills for vanilla pi", + type: "module", + license: "Apache-2.0", + repository: { + type: "git", + url: "git+https://github.com/itayinbarr/little-coder.git", + }, + keywords: [ + "pi-package", + "pi", + "little-coder", + "extensions", + "skills", + "coding-agent", + ], + files: ["extensions/", "skills/", "UPSTREAM.json", "README.md"], + peerDependencies: { + "@earendil-works/pi-ai": "*", + "@earendil-works/pi-coding-agent": "*", + "@earendil-works/pi-tui": "*", + }, + pi: { + extensions: PI_PACKAGE_PI_EXTENSIONS.map((e) => `./extensions/${e}`), + skills: [], + }, + devDependencies: { + "@earendil-works/pi-ai": "^0.80.0", + "@earendil-works/pi-coding-agent": "^0.80.0", + "@earendil-works/pi-tui": "^0.80.0", + }, }; writeFileSync( - join(outDir, "package.json"), - JSON.stringify(piPackageJson, null, 2) + "\n" + join(outDir, "package.json"), + JSON.stringify(piPackageJson, null, 2) + "\n", ); // Create UPSTREAM.json with provenance writeFileSync( - join(outDir, "UPSTREAM.json"), - JSON.stringify( - { - source: pkg.repository.url, - version, - builtAt: new Date().toISOString(), - included: { - extensions: PI_PACKAGE_EXTENSIONS, - skills: PI_PACKAGE_SKILLS - } - }, - null, - 2 - ) + "\n" + join(outDir, "UPSTREAM.json"), + JSON.stringify( + { + source: pkg.repository.url, + version, + builtAt: new Date().toISOString(), + included: { + extensions: PI_PACKAGE_EXTENSIONS, + skills: PI_PACKAGE_SKILL_DIRS, + }, + }, + null, + 2, + ) + "\n", ); // Create README.md @@ -186,8 +193,8 @@ This will auto-load all included extensions and skills via pi's package system. ### Extensions (${PI_PACKAGE_EXTENSIONS.length}) ${PI_PACKAGE_EXTENSIONS.map((e) => `- ${e}`).join("\n")} -### Skills (${PI_PACKAGE_SKILLS.length}) -${PI_PACKAGE_SKILLS.map((s) => `- ${s}`).join("\n")} +### Skills (${PI_PACKAGE_SKILL_DIRS.length}) +${PI_PACKAGE_SKILL_DIRS.map((s) => `- ${s}`).join("\n")} ## Upstream @@ -201,4 +208,4 @@ Apache-2.0 — see little-coder [LICENSE](https://github.com/itayinbarr/little-c writeFileSync(join(outDir, "README.md"), readme); console.log(`\nPi package built at: ${outDir}`); -console.log(`Run 'npm publish' from that directory to publish.`); \ No newline at end of file +console.log(`Run 'npm publish' from that directory to publish.`);