@tekmidian/pai 1.0.0 → 1.0.2

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.
Files changed (218) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +67 -5
  3. package/dist/aibroker-client-BxzeEHPF.mjs +3 -0
  4. package/dist/{aibroker-client-CXEYBzbf.mjs → aibroker-client-CQPGqLxL.mjs} +1 -1
  5. package/dist/atomic-file-BorMa4T3.mjs +21 -0
  6. package/dist/{auto-route-D5SXrMpB.mjs → auto-route-B4azPk4l.mjs} +3 -3
  7. package/dist/auto-route-BjoMHKyI.mjs +11 -0
  8. package/dist/budget-Dod6rnA9.mjs +15 -0
  9. package/dist/{caller-identity-CVTuiLSs.mjs → caller-identity-yLFb7CKq.mjs} +6 -5
  10. package/dist/{capacity-DaXrVj3J.mjs → capacity-DEveQdFV.mjs} +3 -3
  11. package/dist/{chain-BcQlIlEd.mjs → chain-D-yvET_y.mjs} +5 -5
  12. package/dist/cli/index.mjs +1621 -357
  13. package/dist/cockpit-actions-BWunx3rY.mjs +20 -0
  14. package/dist/{cockpit-actions-ODmt8yF9.mjs → cockpit-actions-C8GlTzts.mjs} +84 -42
  15. package/dist/cockpit-memory-vTlFWV7F.mjs +20 -0
  16. package/dist/{config-BSXKyeJ1.mjs → config-Bu1_vbY8.mjs} +42 -3
  17. package/dist/{config-B955yYmo.mjs → config-Cxi-aBf4.mjs} +2 -2
  18. package/dist/{config-TaWmdTLx.mjs → config-DtENJ6cL.mjs} +1 -1
  19. package/dist/{config-D5chi2LO.mjs → config-XpZWOYOG.mjs} +2 -2
  20. package/dist/{controls-BGkK_Bdi.mjs → controls-BcKFHeG1.mjs} +37 -22
  21. package/dist/daemon/index.mjs +43 -42
  22. package/dist/{daemon-CorGh6Ub.mjs → daemon-ColVHwGZ.mjs} +103 -55
  23. package/dist/daemon-N7WhYi6I.mjs +45 -0
  24. package/dist/daemon-mcp/index.mjs +40 -39
  25. package/dist/{define-DGFu-sCf.mjs → define-CjJsmq-6.mjs} +1 -1
  26. package/dist/{detect-C1d5cUkT.mjs → detect-9CZNO0yf.mjs} +2 -2
  27. package/dist/detect-D1ziFFfA.mjs +10 -0
  28. package/dist/{embed-pass-CeQD8kRE.mjs → embed-pass-DIWjA68P.mjs} +5 -5
  29. package/dist/{embedding-gate-BHuBsgHD.mjs → embedding-gate-6rZrWRXa.mjs} +5 -5
  30. package/dist/{embedding-gate-Z7CJSOJk.mjs → embedding-gate-Cnk4YCyX.mjs} +3 -3
  31. package/dist/{embeddings-D4t6L-EH.mjs → embeddings-BVq3esBF.mjs} +1 -1
  32. package/dist/{embeddings-OF9Pt4by.mjs → embeddings-CIkJBKd-.mjs} +1 -1
  33. package/dist/{endreason-x1KOSrcO.mjs → endreason-C577xIQz.mjs} +7 -6
  34. package/dist/{env-DArqGq-Z.mjs → env-BE8mrsxr.mjs} +1 -1
  35. package/dist/factory-Bsib-qDG.mjs +9 -0
  36. package/dist/{factory-BtjbQ0np.mjs → factory-DjAAsqr_.mjs} +15 -13
  37. package/dist/{fallback-BIyEpPsQ.mjs → fallback-DFlItw1Z.mjs} +5 -5
  38. package/dist/{fmt-age-BiSHrTKN.mjs → fmt-age-ByDkvkoU.mjs} +1 -1
  39. package/dist/{health-C6jZWvRG.mjs → health-Dnym-qcT.mjs} +4 -4
  40. package/dist/{helpers-DFmYRCOa.mjs → helpers-BD6V-p9w.mjs} +2 -2
  41. package/dist/helpers-CMGjy_0N.mjs +10 -0
  42. package/dist/{helpers-CJpb_MNB.mjs → helpers-CRuBUas3.mjs} +87 -38
  43. package/dist/hooks/capture-all-events.mjs +52 -11
  44. package/dist/hooks/capture-session-summary.mjs +64 -47
  45. package/dist/hooks/capture-tool-output.mjs +49 -8
  46. package/dist/hooks/context-compression-hook.mjs +30 -10
  47. package/dist/hooks/initialize-session.mjs +1 -0
  48. package/dist/hooks/load-core-context.mjs +1 -1
  49. package/dist/hooks/load-project-context.mjs +32 -29
  50. package/dist/hooks/security-validator.mjs +45 -13
  51. package/dist/hooks/stop-hook.mjs +54 -16
  52. package/dist/hooks/supervision-banner.mjs +9 -0
  53. package/dist/hooks/sync-todo-to-md.mjs +147 -113
  54. package/dist/hooks/worker-edit-repair.mjs +2 -1
  55. package/dist/hooks/worker-guard.mjs +40 -16
  56. package/dist/hooks/worker-note.mjs +12 -5
  57. package/dist/hooks/worker-path-repair.mjs +1 -1
  58. package/dist/hooks/worker-pool-host.mjs +214 -116
  59. package/dist/hooks/worker-status-line.mjs +195 -114
  60. package/dist/index.d.mts +18 -0
  61. package/dist/index.mjs +12 -12
  62. package/dist/{indexer-backend-BM2CUSu8.mjs → indexer-backend-_a6UNhXJ.mjs} +149 -96
  63. package/dist/{ipc-client-BMqWw_gv.mjs → ipc-client-CaiGJnSM.mjs} +1 -1
  64. package/dist/{ipc-client-YYDD6zzU.mjs → ipc-client-ZRmDKd5Z.mjs} +1 -1
  65. package/dist/kill-tree-BG7dRVtp.mjs +10 -0
  66. package/dist/{kill-tree-BjYz5BxK.mjs → kill-tree-EWYaEk5v.mjs} +8 -8
  67. package/dist/{launch-CLp0OJxV.mjs → launch-CXVlZjHt.mjs} +8 -8
  68. package/dist/{launch-DgqQdAjI.mjs → launch-Jh5NVeNe.mjs} +3 -3
  69. package/dist/local-candidate-DLdwQt0s.mjs +112 -0
  70. package/dist/local-instances-DOxSgT_L.mjs +210 -0
  71. package/dist/local-instances-ZeRkkuS5.mjs +6 -0
  72. package/dist/{local-tiers-DPZYqv9m.mjs → local-tiers-BE4jGaMa.mjs} +2 -2
  73. package/dist/{local-tiers-DnBIAkbK.mjs → local-tiers-DNMI5W86.mjs} +3 -3
  74. package/dist/{main-config-ops-C0SflOWk.mjs → main-config-ops-BEMpuhDz.mjs} +3 -3
  75. package/dist/{main-resolver-DVPpSWVy.mjs → main-resolver-3Ns1HPmS.mjs} +18 -17
  76. package/dist/{migrate-BUJHadA8.mjs → migrate-DrA_OwlZ.mjs} +45 -14
  77. package/dist/min-max-DrSRUaWQ.mjs +16 -0
  78. package/dist/{model-inventory-Dasber3S.mjs → model-inventory-BFVSNFT3.mjs} +13 -12
  79. package/dist/{models-add-Bt7aUHDs.mjs → models-add-BtB3Se6f.mjs} +39 -38
  80. package/dist/operator-BTqvTIc8.mjs +7 -0
  81. package/dist/{operator-DZxcP-g8.mjs → operator-CyuB3aVw.mjs} +3 -3
  82. package/dist/{pai-marker-kIYuNt3c.mjs → pai-marker-DurzIbw8.mjs} +7 -6
  83. package/dist/{pane-audit-OGYrOAik.mjs → pane-audit-BQytYa60.mjs} +7 -7
  84. package/dist/{paths-DoRuFc60.mjs → paths-CFHTloSe.mjs} +3 -3
  85. package/dist/{paths-DoIyRnhx.mjs → paths-CHifubDh.mjs} +3 -3
  86. package/dist/{planner-DLGOYlVb.mjs → planner-BM27GBfC.mjs} +31 -30
  87. package/dist/{pool-CZ5MZaXg.mjs → pool-BRvoOkv6.mjs} +3 -3
  88. package/dist/{pool-cli-BxNSPzzY.mjs → pool-cli-B_YR8_Gl.mjs} +5 -5
  89. package/dist/{pool-cli-CbJfl37y.mjs → pool-cli-DkZTAlSW.mjs} +6 -6
  90. package/dist/postgres-BdBNkL_l.mjs +7 -0
  91. package/dist/{postgres-CDnd_k5f.mjs → postgres-C6ti4haA.mjs} +55 -34
  92. package/dist/{project-setup-DM4npnL3.mjs → project-setup-CfpApz5o.mjs} +131 -74
  93. package/dist/providers-BeDiVEb-.mjs +16 -0
  94. package/dist/{providers-UsKtLcUR.mjs → providers-CblTFrhz.mjs} +6 -6
  95. package/dist/{query-feedback-I-txSnld.mjs → query-feedback-BRfPtqki.mjs} +2 -2
  96. package/dist/registrable-C7b6givx.mjs +9 -0
  97. package/dist/{registrable-LrRIVYQc.mjs → registrable-Uzgmfyhz.mjs} +4 -4
  98. package/dist/render-Cc2j5QG9.mjs +13 -0
  99. package/dist/{render-BDbb4CF-.mjs → render-CwIrgxLi.mjs} +8 -8
  100. package/dist/{reranker-B4r4vykM.mjs → reranker-8g6xMwP7.mjs} +1 -1
  101. package/dist/{reranker-DxxMCtA0.mjs → reranker-CqCbpGnX.mjs} +1 -1
  102. package/dist/{root-BMeJuHOb.mjs → root-BWy0c2Lq.mjs} +11 -11
  103. package/dist/root-wC_6Fx0B.mjs +36 -0
  104. package/dist/route-table-B7zpoypi.mjs +15 -0
  105. package/dist/{router-L8Nu-OHF.mjs → router-CQSc3lk3.mjs} +17 -7
  106. package/dist/{router-D6Ec6Fid.mjs → router-DwHJRgG9.mjs} +1 -1
  107. package/dist/{routing-CUyiglfu.mjs → routing-BAAST9vT.mjs} +27 -308
  108. package/dist/{routing-ZuGlc0ge.mjs → routing-CENt4HH1.mjs} +13 -12
  109. package/dist/{run-ulAi5dFD.mjs → run-6Q9kCPem.mjs} +35 -33
  110. package/dist/{scan-B7I0gPe-.mjs → scan-ZQWm-_se.mjs} +6 -6
  111. package/dist/{local-candidate-jOjTNlOL.mjs → scheduler-CtUyWJ3O.mjs} +7 -109
  112. package/dist/scheduler-dmHXoRvm.mjs +3 -0
  113. package/dist/{scope-D5VSQxe4.mjs → scope-D-h6F6DN.mjs} +4 -4
  114. package/dist/{search-CPoE0830.mjs → search-RSyeQqj1.mjs} +18 -18
  115. package/dist/session-Bok2PqRS.mjs +35 -0
  116. package/dist/{session-CNXF6DLP.mjs → session-DVUExUnX.mjs} +55 -31
  117. package/dist/session-scan-BJI_ZO8M.mjs +9 -0
  118. package/dist/{session-scan-BDLEc4w1.mjs → session-scan-W-gbwJHM.mjs} +6 -6
  119. package/dist/{sources-CjhUKKtO.mjs → sources-ESUkZaWv.mjs} +1 -1
  120. package/dist/{status-DYydKQUI.mjs → status-Dm4dYciN.mjs} +4 -4
  121. package/dist/{status-CvVi9cDf.mjs → status-ggJ7lgVg.mjs} +17 -9
  122. package/dist/{stop-words-SdSL1WKH.mjs → stop-words-DSEYQdNe.mjs} +1 -1
  123. package/dist/{test-isolation-CoDeOVXb.mjs → test-isolation-lqSmYxr0.mjs} +1 -1
  124. package/dist/{timeouts-OwEONmc8.mjs → timeouts-1MLooEBB.mjs} +1 -1
  125. package/dist/{tool-errors-AeRQuG3Y.mjs → tool-errors-7E13StWd.mjs} +4 -3
  126. package/dist/{tool-errors-R5E0fCgf.mjs → tool-errors-CJWODvlo.mjs} +6 -6
  127. package/dist/{viewer-DtWj5Vti.mjs → viewer-DcpJwGKE.mjs} +15 -15
  128. package/dist/viewer-MWBVJP3u.mjs +22 -0
  129. package/dist/{wait-BMUR-U-U.mjs → wait-ByV0OyMZ.mjs} +3 -3
  130. package/dist/{worker-config-BLqa1yiD.mjs → worker-config-BDYNxbYz.mjs} +294 -92
  131. package/dist/worker-config-gmAq_-rI.mjs +41 -0
  132. package/dist/{workers-config-DEI9lrV4.mjs → workers-config-f8KrkY4L.mjs} +2 -2
  133. package/dist/{worktree-zmRf556h.mjs → worktree-CrnOqTBW.mjs} +37 -7
  134. package/dist/{zettelkasten-nigq7fqs.mjs → zettelkasten-B_rRRRvD.mjs} +5 -5
  135. package/dist/{zettelkasten-Cr_Xj0mG.mjs → zettelkasten-BzMm8REH.mjs} +29 -19
  136. package/docs/README.md +2 -0
  137. package/docs/cockpit.md +10 -4
  138. package/docs/commands/README.md +4 -4
  139. package/docs/commands/backup.md +2 -0
  140. package/docs/commands/install.md +29 -4
  141. package/docs/commands/providers.md +1 -1
  142. package/docs/commands/restore.md +4 -0
  143. package/docs/commands/setup.md +29 -4
  144. package/docs/commands/worker.md +1 -1
  145. package/docs/config.md +2 -0
  146. package/docs/features.md +299 -0
  147. package/docs/images/architecture.drawio +51 -0
  148. package/docs/images/architecture.svg +3 -0
  149. package/docs/images/cockpit-01-system-tokens.jpg +0 -0
  150. package/docs/images/cockpit-02-totals-cache.jpg +0 -0
  151. package/docs/images/cockpit-03-local-servers-models-pool.jpg +0 -0
  152. package/docs/images/cockpit-04-workers-memory.jpg +0 -0
  153. package/docs/images/cockpit-05-performance.jpg +0 -0
  154. package/docs/images/cockpit-06-runs-speed-routing.jpg +0 -0
  155. package/docs/images/cockpit-07-settings-registry.jpg +0 -0
  156. package/docs/install/index.md +29 -0
  157. package/docs/three-tier-coding.md +1 -1
  158. package/gemini-extension.json +1 -1
  159. package/package.json +1 -5
  160. package/pai-plugin.json +1 -1
  161. package/plugins/context-preservation/plugin.json +1 -1
  162. package/plugins/core/plugin.json +1 -1
  163. package/plugins/core/skills/setup/SKILL.md +41 -9
  164. package/plugins/creative/plugin.json +1 -1
  165. package/plugins/observability/plugin.json +1 -1
  166. package/plugins/productivity/plugin.json +1 -1
  167. package/plugins/semantic-search/plugin.json +1 -1
  168. package/plugins/ui/plugin.json +1 -1
  169. package/plugins/zettelkasten/plugin.json +1 -1
  170. package/src/hooks/session-autosave.sh +8 -1
  171. package/src/hooks/session-stop.sh +8 -1
  172. package/src/hooks/ts/capture-all-events.ts +7 -12
  173. package/src/hooks/ts/lib/context-fill.ts +2 -1
  174. package/src/hooks/ts/lib/path-repair.ts +1 -1
  175. package/src/hooks/ts/lib/project-utils/notify.ts +1 -0
  176. package/src/hooks/ts/lib/project-utils/session-notes.ts +23 -6
  177. package/src/hooks/ts/lib/raw-capture.ts +36 -0
  178. package/src/hooks/ts/lib/session-summary.ts +129 -0
  179. package/src/hooks/ts/lib/todo-sync.ts +120 -0
  180. package/src/hooks/ts/lib/worker-git-guard.ts +1 -1
  181. package/src/hooks/ts/post-tool-use/capture-tool-output.ts +6 -10
  182. package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +5 -105
  183. package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
  184. package/src/hooks/ts/pre-tool-use/security-validator.ts +35 -14
  185. package/src/hooks/ts/session-end/capture-session-summary.ts +20 -128
  186. package/src/hooks/ts/session-start/load-core-context.ts +3 -3
  187. package/src/hooks/ts/session-start/load-project-context.ts +18 -22
  188. package/src/hooks/ts/stop/stop-hook.ts +13 -5
  189. package/src/hooks/ztk-auto.sh +7 -2
  190. package/dist/aibroker-client-JjE74jo0.mjs +0 -3
  191. package/dist/auto-route-BRk8NEFv.mjs +0 -11
  192. package/dist/budget-DuhQ2zzG.mjs +0 -14
  193. package/dist/cockpit-actions-DcQ-ucU8.mjs +0 -19
  194. package/dist/cockpit-memory-KV45PPO2.mjs +0 -19
  195. package/dist/daemon-C7yt_ptC.mjs +0 -44
  196. package/dist/detect-DRUkmHnb.mjs +0 -10
  197. package/dist/factory-DAGG9_n8.mjs +0 -9
  198. package/dist/helpers-DPJEHPq6.mjs +0 -10
  199. package/dist/kill-tree-LV0Q7Mte.mjs +0 -10
  200. package/dist/operator-CfL0HiBK.mjs +0 -7
  201. package/dist/postgres-DgqWyqhN.mjs +0 -7
  202. package/dist/providers-CTu2s1z2.mjs +0 -15
  203. package/dist/registrable-D9aNZOTh.mjs +0 -9
  204. package/dist/render-BSfVlYHj.mjs +0 -13
  205. package/dist/root-xI9DqVIe.mjs +0 -35
  206. package/dist/route-table-HjvnLVEU.mjs +0 -14
  207. package/dist/session-BJHzmUg2.mjs +0 -34
  208. package/dist/session-scan-YORKE1NO.mjs +0 -9
  209. package/dist/viewer-B8709jDN.mjs +0 -22
  210. package/dist/worker-config-D8tRuvo7.mjs +0 -40
  211. package/docs/images/cockpit-limits-routing.jpg +0 -0
  212. package/docs/images/cockpit-models.jpg +0 -0
  213. package/docs/images/cockpit-usage.jpg +0 -0
  214. package/docs/images/cockpit-workers-system.jpg +0 -0
  215. package/scripts/build-docs.mjs +0 -41
  216. package/scripts/build-hooks.mjs +0 -311
  217. package/scripts/build-skill-stubs.mjs +0 -216
  218. package/scripts/copy-defaults.mjs +0 -46
@@ -1,311 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Build TypeScript hooks into standalone .mjs files using esbuild.
4
- *
5
- * Each hook is fully self-contained — lib/ dependencies are inlined.
6
- * Output: dist/hooks/<name>.mjs with #!/usr/bin/env node shebang.
7
- *
8
- * With --sync: also creates/updates symlinks (or copies on Windows) from
9
- * ~/.claude/Hooks/ and ~/.claude/ to the built/source files, plus the
10
- * PAI_HOME/agents and PAI_HOME/commands adapter symlinks at ~/.claude/agents
11
- * and ~/.claude/commands (once `pai config migrate` has populated them once).
12
- * This ensures that `bun run build` is the only step needed to deploy updates.
13
- */
14
-
15
- import { buildSync } from "esbuild";
16
- import {
17
- readdirSync,
18
- statSync,
19
- chmodSync,
20
- existsSync,
21
- mkdirSync,
22
- symlinkSync,
23
- lstatSync,
24
- readlinkSync,
25
- unlinkSync,
26
- copyFileSync,
27
- readFileSync,
28
- writeFileSync,
29
- renameSync,
30
- rmSync,
31
- } from "fs";
32
- import { join, resolve, basename } from "path";
33
- import { homedir, platform } from "os";
34
- import { paiHomeDir as resolvePaiHomeDir, syncSkipReason } from "./lib/sync-guard.mjs";
35
-
36
- const HOOKS_SRC = "src/hooks/ts";
37
- const HOOKS_OUT = "dist/hooks";
38
- const doSync = process.argv.includes("--sync");
39
-
40
- // Bundled CJS deps (e.g. yaml) call require() for Node builtins like
41
- // "process". esbuild's ESM output leaves those calls as a `require` that
42
- // only exists at runtime if the module defines it — plain .mjs run via node
43
- // has no `require`, so the call throws "Dynamic require of ... is not
44
- // supported". createRequire gives every bundle a real one.
45
- //
46
- // PAI_QUIET_NOTICES=1 silences one-time stderr notices (e.g. the PAI_DIR
47
- // deprecation warning in pai-paths.ts) for every hook and standalone script
48
- // built here — their stderr is machinery Claude Code inspects for errors and
49
- // the statusline pipes through, never a human terminal, so it must stay
50
- // clean. Set only if unset, so a caller that deliberately wants the notices
51
- // (debugging) can still get them.
52
- const REQUIRE_SHIM = {
53
- js: "import { createRequire as __paiCreateRequire } from 'node:module';\nconst require = __paiCreateRequire(import.meta.url);\nif (!process.env.PAI_QUIET_NOTICES) process.env.PAI_QUIET_NOTICES = '1';",
54
- };
55
-
56
- // Collect all .ts entry points (skip lib/ — those are bundled into each hook)
57
- function collectEntryPoints(dir) {
58
- const entries = [];
59
- for (const name of readdirSync(dir)) {
60
- const full = join(dir, name);
61
- if (name === "lib") continue;
62
- if (statSync(full).isDirectory()) {
63
- entries.push(...collectEntryPoints(full));
64
- } else if (name.endsWith(".ts") && !name.endsWith(".test.ts")) {
65
- entries.push(full);
66
- }
67
- }
68
- return entries;
69
- }
70
-
71
- const entryPoints = collectEntryPoints(HOOKS_SRC);
72
-
73
- console.log(`Building ${entryPoints.length} hooks with esbuild...`);
74
-
75
- // Build into a staging directory, then rename each artefact into place.
76
- //
77
- // ~/.claude/Hooks/*.mjs are symlinks to these files, and every LIVE Claude Code
78
- // session executes them on each tool call. esbuild truncates-then-writes, so
79
- // building straight to the output path leaves a window in which the file does
80
- // not exist — any hook firing during a build in another session dies with
81
- // "No such file or directory". That is not hypothetical: a rebuild here breaks
82
- // hooks in every other session that happens to fire during it.
83
- //
84
- // rename(2) within one filesystem is atomic, so a concurrent session sees
85
- // either the previous build or the new one, never a partial or absent file.
86
- const STAGING = join(HOOKS_OUT, ".staging");
87
- rmSync(STAGING, { recursive: true, force: true });
88
- mkdirSync(STAGING, { recursive: true });
89
-
90
- for (const entry of entryPoints) {
91
- const name = basename(entry).replace(/\.ts$/, ".mjs");
92
- const staged = join(STAGING, name);
93
-
94
- buildSync({
95
- entryPoints: [entry],
96
- bundle: true,
97
- platform: "node",
98
- target: "node20",
99
- format: "esm",
100
- outfile: staged,
101
- sourcemap: true,
102
- banner: REQUIRE_SHIM,
103
- });
104
-
105
- chmodSync(staged, 0o755);
106
-
107
- // The sourceMappingURL comment is a bare filename, so the map must land in
108
- // the same directory as the module. Move it first: a stale map for a moment
109
- // is harmless, a missing module is not.
110
- const stagedMap = `${staged}.map`;
111
- if (existsSync(stagedMap)) {
112
- renameSync(stagedMap, join(HOOKS_OUT, `${name}.map`));
113
- }
114
- renameSync(staged, join(HOOKS_OUT, name));
115
- }
116
-
117
- rmSync(STAGING, { recursive: true, force: true });
118
-
119
- console.log(`✔ ${entryPoints.length} hooks built to ${HOOKS_OUT}/`);
120
-
121
- // ---------------------------------------------------------------------------
122
- // Standalone worker status-line: src/workers/standalone/status-line.ts →
123
- // dist/worker-status-line.mjs (same atomic-staging rules; it runs on every
124
- // statusline refresh of every live session).
125
- // ---------------------------------------------------------------------------
126
-
127
- // [entry, output name in dist/hooks/]
128
- const STANDALONE_ENTRIES = [
129
- ["src/workers/standalone/status-line.ts", "worker-status-line.mjs"],
130
- // detached proxy for openai-protocol providers; spawned from dist, not
131
- // symlinked into ~/.claude, so it is NOT in the --sync list below
132
- ["src/workers/standalone/proxy.ts", "worker-proxy.mjs"],
133
- // detached worker session pool host (src/workers/pool-host.ts); spawned
134
- // from dist like the proxy, NOT symlinked into ~/.claude
135
- ["src/workers/standalone/pool-host.ts", "worker-pool-host.mjs"],
136
- ];
137
-
138
- mkdirSync(STAGING, { recursive: true });
139
- for (const [entry, name] of STANDALONE_ENTRIES) {
140
- const staged = join(STAGING, name);
141
- buildSync({
142
- entryPoints: [entry],
143
- bundle: true,
144
- platform: "node",
145
- target: "node20",
146
- format: "esm",
147
- outfile: staged,
148
- sourcemap: true,
149
- banner: REQUIRE_SHIM,
150
- // the pool host pulls the storage factory (via run.ts → project-config),
151
- // whose embeddings backend needs the native onnx binding — native .node
152
- // files can never be bundled; resolve it at runtime like the rest of
153
- // node_modules (the host runs from dist/, inside the repo)
154
- external: ["onnxruntime-node"],
155
- });
156
- chmodSync(staged, 0o755);
157
- const stagedMap = `${staged}.map`;
158
- if (existsSync(stagedMap)) {
159
- renameSync(stagedMap, join(HOOKS_OUT, `${name}.map`));
160
- }
161
- mkdirSync(HOOKS_OUT, { recursive: true });
162
- renameSync(staged, join(HOOKS_OUT, name));
163
- }
164
-
165
- rmSync(STAGING, { recursive: true, force: true });
166
-
167
- console.log(
168
- `✔ ${STANDALONE_ENTRIES.length} standalone script(s) built to ${HOOKS_OUT}/ (${STANDALONE_ENTRIES.map(([, n]) => n).join(", ")})`
169
- );
170
-
171
- // ---------------------------------------------------------------------------
172
- // --sync: Symlink (or copy on Windows) all deployable files to ~/.claude/
173
- // ---------------------------------------------------------------------------
174
-
175
- const skipReason = doSync ? syncSkipReason() : null;
176
- if (skipReason) {
177
- // Only the canonical install repoints the live ~/.claude symlinks — see
178
- // scripts/lib/sync-guard.mjs.
179
- console.log(skipReason.startsWith("skipping") ? skipReason : `✔ Hook symlinks skipped: ${skipReason}`);
180
- } else if (doSync) {
181
- const useSymlinks = platform() !== "win32";
182
- const claudeDir = join(homedir(), ".claude");
183
- const hooksTarget = join(claudeDir, "Hooks");
184
- mkdirSync(hooksTarget, { recursive: true });
185
-
186
- let created = 0;
187
- let updated = 0;
188
- let current = 0;
189
-
190
- /**
191
- * Ensure `target` is a symlink (or copy on Windows) pointing to `source`.
192
- * Replaces stale symlinks and plain-file copies with correct symlinks.
193
- * Never overwrites non-symlink, non-PAI files (user's own scripts).
194
- */
195
- function syncFile(source, target) {
196
- const absSource = resolve(source);
197
-
198
- if (!existsSync(absSource)) {
199
- console.warn(` ⚠ Source not found: ${source}`);
200
- return;
201
- }
202
-
203
- // Check existing target (lstat doesn't follow symlinks)
204
- let isUpdate = false;
205
- try {
206
- const stat = lstatSync(target);
207
- if (stat.isSymbolicLink()) {
208
- if (resolve(readlinkSync(target)) === absSource) {
209
- current++;
210
- return;
211
- }
212
- unlinkSync(target);
213
- isUpdate = true;
214
- } else if (stat.isFile()) {
215
- unlinkSync(target);
216
- isUpdate = true;
217
- } else {
218
- return; // Directory or something unexpected — don't touch
219
- }
220
- } catch {
221
- // Target doesn't exist — fresh install
222
- }
223
-
224
- if (useSymlinks) {
225
- symlinkSync(absSource, target);
226
- } else {
227
- copyFileSync(absSource, target);
228
- chmodSync(target, 0o755);
229
- }
230
-
231
- if (isUpdate) {
232
- updated++;
233
- } else {
234
- created++;
235
- }
236
- }
237
-
238
- // 1. TypeScript hooks: dist/hooks/*.mjs → ~/.claude/Hooks/*.mjs
239
- // (worker-proxy.mjs and worker-pool-host.mjs are spawned from dist, not
240
- // hooks — skip them)
241
- const mjsFiles = readdirSync(HOOKS_OUT).filter(
242
- (f) => f.endsWith(".mjs") && f !== "worker-proxy.mjs" && f !== "worker-pool-host.mjs"
243
- );
244
- for (const filename of mjsFiles) {
245
- syncFile(join(HOOKS_OUT, filename), join(hooksTarget, filename));
246
- }
247
-
248
- // 2. Shell hooks: src/hooks/*.sh → ~/.claude/Hooks/pai-*.sh
249
- const shellHooks = [
250
- ["src/hooks/pre-compact.sh", "pai-pre-compact.sh"],
251
- ["src/hooks/session-stop.sh", "pai-session-stop.sh"],
252
- ["src/hooks/session-autosave.sh", "pai-session-autosave.sh"],
253
- ["src/hooks/ztk-auto.sh", "ztk-auto.sh"],
254
- ["src/hooks/ztk-passthrough.mjs", "ztk-passthrough.mjs"],
255
- ];
256
- for (const [src, destName] of shellHooks) {
257
- if (existsSync(src)) {
258
- syncFile(src, join(hooksTarget, destName));
259
- }
260
- }
261
-
262
- // 3. Root scripts: statusline + tab-color → ~/.claude/
263
- const rootScripts = ["statusline-command.sh", "tab-color-command.sh"];
264
- for (const script of rootScripts) {
265
- if (existsSync(script)) {
266
- syncFile(script, join(claudeDir, script));
267
- }
268
- }
269
-
270
- // 4. Standalone worker status-line: dist/worker-status-line.mjs → ~/.claude/
271
- syncFile(join(HOOKS_OUT, "worker-status-line.mjs"), join(claudeDir, "worker-status-line.mjs"));
272
-
273
- // 5. Content dirs with an adapter symlink: PAI_HOME/agents, PAI_HOME/commands
274
- // → ~/.claude/agents, ~/.claude/commands. Only acts once `pai config migrate`
275
- // has populated the PAI_HOME copy on this machine — a real (non-symlink)
276
- // agents/commands dir is the operator's un-migrated authored content and is
277
- // never touched here, same rule as syncFile() above.
278
- function syncContentDirSymlink(paiHomeSubdir, targetDir) {
279
- if (!existsSync(paiHomeSubdir)) return; // not migrated yet — nothing to link to
280
-
281
- try {
282
- const stat = lstatSync(targetDir);
283
- if (stat.isSymbolicLink()) {
284
- if (resolve(readlinkSync(targetDir)) === resolve(paiHomeSubdir)) {
285
- current++;
286
- return;
287
- }
288
- unlinkSync(targetDir);
289
- symlinkSync(resolve(paiHomeSubdir), targetDir);
290
- updated++;
291
- return;
292
- }
293
- // Real directory — leave it alone (`pai config migrate` is what moves it).
294
- return;
295
- } catch {
296
- // Target doesn't exist — fresh symlink.
297
- symlinkSync(resolve(paiHomeSubdir), targetDir);
298
- created++;
299
- }
300
- }
301
-
302
- const paiHomeDir = resolvePaiHomeDir();
303
- syncContentDirSymlink(join(paiHomeDir, "agents"), join(claudeDir, "agents"));
304
- syncContentDirSymlink(join(paiHomeDir, "commands"), join(claudeDir, "commands"));
305
-
306
- const parts = [];
307
- if (created > 0) parts.push(`${created} created`);
308
- if (updated > 0) parts.push(`${updated} updated`);
309
- if (current > 0) parts.push(`${current} current`);
310
- console.log(`✔ Hook symlinks synced: ${parts.join(", ")}`);
311
- }
@@ -1,216 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Generate SKILL.md files from MCP prompt definitions.
4
- *
5
- * Sources (in order):
6
- * 1. src/daemon-mcp/prompts/*.ts — PAI built-in prompts (tracked in git)
7
- * 2. src/daemon-mcp/prompts/custom/*.ts — User-created prompts (gitignored)
8
- *
9
- * Each prompt becomes a discoverable Claude Code skill at:
10
- * dist/skills/<TitleCase>/SKILL.md
11
- *
12
- * Installation: `pai setup` or `pai skills sync` symlinks (macOS/Linux)
13
- * or copies (Windows) each skill directory into ~/.claude/skills/.
14
- *
15
- * With --sync: also creates/updates symlinks in ~/.claude/skills/ after
16
- * generating stubs. This is called automatically during `bun run build`.
17
- *
18
- * Source of truth: the TypeScript prompt files. Skills are regenerated on
19
- * every build — never edit the generated SKILL.md files by hand.
20
- */
21
-
22
- import {
23
- readFileSync,
24
- writeFileSync,
25
- mkdirSync,
26
- existsSync,
27
- readdirSync,
28
- symlinkSync,
29
- lstatSync,
30
- readlinkSync,
31
- unlinkSync,
32
- } from "fs";
33
- import { join, resolve } from "path";
34
- import { homedir, platform } from "os";
35
- import { syncSkipReason } from "./lib/sync-guard.mjs";
36
-
37
- const PROMPTS_DIR = "src/daemon-mcp/prompts";
38
- const CUSTOM_DIR = join(PROMPTS_DIR, "custom");
39
- const STUBS_OUT = "dist/skills";
40
- const doSync = process.argv.includes("--sync");
41
-
42
- // kebab-case → TitleCase for Claude Code's skill scanner
43
- function toTitleCase(promptName) {
44
- return promptName
45
- .split("-")
46
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
47
- .join("");
48
- }
49
-
50
- // Extract description and full content from a prompt .ts file
51
- function parsePrompt(filePath) {
52
- const src = readFileSync(filePath, "utf-8");
53
-
54
- // Extract the description field
55
- const descMatch = src.match(/description:\s*["'`]([^"'`]+)["'`]/);
56
- if (!descMatch) return null;
57
- const description = descMatch[1];
58
-
59
- // Extract USE WHEN line
60
- const useWhenMatch = src.match(/USE WHEN[^\n]+/);
61
- const useWhen = useWhenMatch ? useWhenMatch[0] : "";
62
-
63
- // Extract the full content template string
64
- // Match content: `...` handling escaped backticks (\`) inside
65
- const contentMatch = src.match(/content:\s*`((?:[^`\\]|\\[\s\S])*)`/);
66
- if (!contentMatch) return null;
67
- // Unescape template literal escapes
68
- const content = contentMatch[1]
69
- .replace(/\\`/g, "`")
70
- .replace(/\\\$/g, "$")
71
- .replace(/\\'/g, "'")
72
- .replace(/\\n/g, "\n");
73
-
74
- return { description, useWhen, content };
75
- }
76
-
77
- // Read export list from index.ts to get built-in prompt file names
78
- function getBuiltinPromptNames() {
79
- const indexSrc = readFileSync(join(PROMPTS_DIR, "index.ts"), "utf-8");
80
- const names = [];
81
- for (const match of indexSrc.matchAll(
82
- /export\s+\{[^}]+\}\s+from\s+["']\.\/([^"']+)\.js["']/g
83
- )) {
84
- names.push(match[1]);
85
- }
86
- return names;
87
- }
88
-
89
- // Scan custom/ directory for user-created prompt files
90
- function getCustomPromptNames() {
91
- if (!existsSync(CUSTOM_DIR)) return [];
92
- return readdirSync(CUSTOM_DIR)
93
- .filter((f) => f.endsWith(".ts") && f !== "index.ts")
94
- .map((f) => f.replace(/\.ts$/, ""));
95
- }
96
-
97
- // Sync symlinks: create/update symlinks in ~/.claude/skills/
98
- function syncSymlinks(generatedNames) {
99
- const skillsDir = join(homedir(), ".claude", "skills");
100
- mkdirSync(skillsDir, { recursive: true });
101
-
102
- const useSymlinks = platform() !== "win32";
103
- let created = 0;
104
- let updated = 0;
105
- let current = 0;
106
-
107
- for (const name of generatedNames) {
108
- const source = resolve(join(STUBS_OUT, name));
109
- const target = join(skillsDir, name);
110
-
111
- // lstat once: dangling links must count as symlinks (existsSync misses them)
112
- let entry;
113
- try {
114
- entry = lstatSync(target);
115
- } catch {
116
- // absent — fresh install
117
- }
118
-
119
- // Never overwrite non-symlink directories (user's own skills)
120
- if (entry && !entry.isSymbolicLink()) {
121
- continue;
122
- }
123
-
124
- // Check existing symlink (may be dangling)
125
- if (entry) {
126
- if (resolve(readlinkSync(target)) === source) {
127
- current++;
128
- continue;
129
- }
130
- unlinkSync(target);
131
- updated++;
132
- }
133
-
134
- if (useSymlinks) {
135
- symlinkSync(source, target);
136
- } else {
137
- mkdirSync(target, { recursive: true });
138
- writeFileSync(
139
- join(target, "SKILL.md"),
140
- readFileSync(join(source, "SKILL.md")),
141
- );
142
- }
143
- created++;
144
- }
145
-
146
- // Clean up stale symlinks from old user/ location
147
- const oldUserDir = join(skillsDir, "user");
148
- if (existsSync(oldUserDir)) {
149
- for (const name of generatedNames) {
150
- const oldTarget = join(oldUserDir, name);
151
- if (existsSync(oldTarget) && lstatSync(oldTarget).isSymbolicLink()) {
152
- unlinkSync(oldTarget);
153
- }
154
- }
155
- }
156
-
157
- const parts = [];
158
- if (created > 0) parts.push(`${created} created`);
159
- if (updated > 0) parts.push(`${updated} updated`);
160
- if (current > 0) parts.push(`${current} current`);
161
- console.log(`✔ Skill symlinks synced: ${parts.join(", ")}`);
162
- }
163
-
164
- // --- Main ---
165
-
166
- const builtinNames = getBuiltinPromptNames();
167
- const customNames = getCustomPromptNames();
168
- let generated = 0;
169
- const generatedDirNames = [];
170
-
171
- mkdirSync(STUBS_OUT, { recursive: true });
172
-
173
- // Process all prompts (built-in + custom)
174
- for (const [fileName, dir] of [
175
- ...builtinNames.map((n) => [n, PROMPTS_DIR]),
176
- ...customNames.map((n) => [n, CUSTOM_DIR]),
177
- ]) {
178
- const filePath = join(dir, `${fileName}.ts`);
179
- const parsed = parsePrompt(filePath);
180
- if (!parsed) {
181
- console.warn(`⚠ Skipping ${fileName}: could not parse`);
182
- continue;
183
- }
184
-
185
- const dirName = toTitleCase(fileName);
186
- const outDir = join(STUBS_OUT, dirName);
187
- const outFile = join(outDir, "SKILL.md");
188
-
189
- mkdirSync(outDir, { recursive: true });
190
-
191
- const skill = [
192
- "---",
193
- `name: ${dirName}`,
194
- `description: "${parsed.description}. ${parsed.useWhen}"`,
195
- "---",
196
- "",
197
- parsed.content.trim(),
198
- "",
199
- ].join("\n");
200
-
201
- writeFileSync(outFile, skill);
202
- generatedDirNames.push(dirName);
203
- generated++;
204
- }
205
-
206
- const customLabel = customNames.length > 0 ? ` (${builtinNames.length} built-in + ${customNames.length} custom)` : "";
207
- console.log(`✔ ${generated} skill stubs generated in ${STUBS_OUT}/${customLabel}`);
208
-
209
- const skipReason = doSync ? syncSkipReason() : null;
210
- if (skipReason) {
211
- // Only the canonical install repoints the live ~/.claude/skills symlinks —
212
- // see scripts/lib/sync-guard.mjs.
213
- console.log(skipReason.startsWith("skipping") ? skipReason : `✔ Skill symlinks skipped: ${skipReason}`);
214
- } else if (doSync) {
215
- syncSymlinks(generatedDirNames);
216
- }
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * copy-defaults.mjs — ship the defaults files into dist.
4
- *
5
- * src/config/defaults/config.yaml and workers.yaml are the shipped policy
6
- * values (thresholds, limits, intervals, lists, mappings) that
7
- * src/config/shipped-defaults.ts loads first and deep-merges UNDER the user's
8
- * ~/.pai/config.yaml and workers.yaml. tsdown bundles code only, so this step
9
- * copies the YAML files to dist/config/defaults/ — the same directory the
10
- * bundled loader module resolves them from (dirname(import.meta.url)).
11
- */
12
-
13
- import { copyFileSync, mkdirSync, existsSync, readFileSync } from "node:fs";
14
- import { join, resolve } from "node:path";
15
-
16
- const SRC_DIR = resolve("src/config/defaults");
17
- const DIST_DIR = resolve(process.argv[2] ?? "dist", "config/defaults");
18
- const FILES = ["config.yaml", "workers.yaml"];
19
-
20
- mkdirSync(DIST_DIR, { recursive: true });
21
- for (const name of FILES) {
22
- const src = join(SRC_DIR, name);
23
- if (!existsSync(src)) {
24
- console.error(`copy-defaults: ${src} is missing — the shipped defaults must ship with the build`);
25
- process.exit(1);
26
- }
27
- // sanity: the file must parse as a YAML mapping (cheap check, no yaml dep here)
28
- const text = readFileSync(src, "utf8");
29
- if (!text.trim().length) {
30
- console.error(`copy-defaults: ${src} is empty`);
31
- process.exit(1);
32
- }
33
- copyFileSync(src, join(DIST_DIR, name));
34
- console.log(`copy-defaults: ${src} → ${join(DIST_DIR, name)}`);
35
- }
36
-
37
- // the saved cockpit layout is optional: it exists once `pai cockpit layout export` has written it
38
- const LAYOUT = join(SRC_DIR, "cockpit-layout.json");
39
- if (existsSync(LAYOUT)) {
40
- copyFileSync(LAYOUT, join(DIST_DIR, "cockpit-layout.json"));
41
- console.log(`copy-defaults: ${LAYOUT} → ${join(DIST_DIR, "cockpit-layout.json")}`);
42
- }
43
-
44
- // esbuild bundles (tests, hooks) emit into directories next to where the
45
- // loader module sits; a bundle's shippedDefaultsDir() falls back to
46
- // <cwd>/src/config/defaults, which is SRC_DIR itself — nothing extra needed.