@asterxsk/kiln 0.1.0 → 0.3.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.
Files changed (491) hide show
  1. package/README.md +155 -170
  2. package/agent/AGENTS.md +67 -67
  3. package/agent/README.md +5 -5
  4. package/agent/extensions/ask-user/index.ts +418 -418
  5. package/agent/extensions/ask-user/install.ps1 +4 -0
  6. package/agent/extensions/ask-user/install.sh +3 -0
  7. package/agent/extensions/ask-user/package-lock.json +769 -769
  8. package/agent/extensions/ask-user/package.json +19 -19
  9. package/agent/extensions/ask-user/prompt.ts +45 -45
  10. package/agent/extensions/ask-user/tsconfig.json +7 -7
  11. package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
  12. package/agent/extensions/background-terminals/index.ts +627 -627
  13. package/agent/extensions/background-terminals/install.ps1 +4 -0
  14. package/agent/extensions/background-terminals/install.sh +3 -0
  15. package/agent/extensions/background-terminals/manager.test.ts +735 -735
  16. package/agent/extensions/background-terminals/output.test.ts +109 -109
  17. package/agent/extensions/background-terminals/package-lock.json +769 -769
  18. package/agent/extensions/background-terminals/package.json +17 -17
  19. package/agent/extensions/background-terminals/prompt.test.ts +125 -125
  20. package/agent/extensions/background-terminals/ps.test.ts +82 -82
  21. package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
  22. package/agent/extensions/background-terminals/src/domain.ts +87 -87
  23. package/agent/extensions/background-terminals/src/manager.ts +907 -907
  24. package/agent/extensions/background-terminals/src/output.ts +84 -84
  25. package/agent/extensions/background-terminals/src/prompt.ts +142 -142
  26. package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
  27. package/agent/extensions/background-terminals/src/runtime.ts +36 -36
  28. package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
  29. package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
  30. package/agent/extensions/background-terminals/tsconfig.json +7 -7
  31. package/agent/extensions/destructive/install.ps1 +4 -0
  32. package/agent/extensions/destructive/install.sh +3 -0
  33. package/agent/extensions/file-search/index.spec.ts +443 -443
  34. package/agent/extensions/file-search/index.ts +459 -459
  35. package/agent/extensions/file-search/install.ps1 +4 -0
  36. package/agent/extensions/file-search/install.sh +3 -0
  37. package/agent/extensions/file-search/package-lock.json +2253 -2253
  38. package/agent/extensions/file-search/package.json +23 -23
  39. package/agent/extensions/file-search/src/args.ts +122 -122
  40. package/agent/extensions/file-search/src/binaries.ts +422 -422
  41. package/agent/extensions/file-search/src/output.ts +126 -126
  42. package/agent/extensions/file-search/src/process.ts +146 -146
  43. package/agent/extensions/file-search/src/prompt.ts +52 -52
  44. package/agent/extensions/file-search/tsconfig.json +7 -7
  45. package/agent/extensions/goal/install.ps1 +4 -0
  46. package/agent/extensions/goal/install.sh +3 -0
  47. package/agent/extensions/herdr-agent-state.ts +257 -0
  48. package/agent/extensions/kiln-update/README.md +25 -0
  49. package/agent/extensions/kiln-update/index.ts +99 -0
  50. package/agent/extensions/kiln-update/install.ps1 +27 -0
  51. package/agent/extensions/kiln-update/install.sh +24 -0
  52. package/agent/extensions/modelconf/PLAN.md +915 -915
  53. package/agent/extensions/modelconf/index.ts +296 -296
  54. package/agent/extensions/modelconf/install.ps1 +4 -0
  55. package/agent/extensions/modelconf/install.sh +3 -0
  56. package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
  57. package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
  58. package/agent/extensions/pi-web-access/LICENSE +21 -21
  59. package/agent/extensions/pi-web-access/README.md +470 -470
  60. package/agent/extensions/pi-web-access/SECURITY.md +5 -5
  61. package/agent/extensions/pi-web-access/activity.ts +101 -101
  62. package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
  63. package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
  64. package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
  65. package/agent/extensions/pi-web-access/content-find.ts +139 -139
  66. package/agent/extensions/pi-web-access/credential-source.ts +191 -191
  67. package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
  68. package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
  69. package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
  70. package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
  71. package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
  72. package/agent/extensions/pi-web-access/exa.ts +526 -526
  73. package/agent/extensions/pi-web-access/extract.ts +1196 -1196
  74. package/agent/extensions/pi-web-access/feature-config.ts +29 -29
  75. package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
  76. package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
  77. package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
  78. package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
  79. package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
  80. package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
  81. package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
  82. package/agent/extensions/pi-web-access/github-api.ts +197 -197
  83. package/agent/extensions/pi-web-access/github-extract.ts +746 -746
  84. package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
  85. package/agent/extensions/pi-web-access/index.ts +1737 -1737
  86. package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
  87. package/agent/extensions/pi-web-access/package.json +64 -64
  88. package/agent/extensions/pi-web-access/page-query.ts +96 -96
  89. package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
  90. package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
  91. package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
  92. package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
  93. package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
  94. package/agent/extensions/pi-web-access/source-check.ts +282 -282
  95. package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
  96. package/agent/extensions/pi-web-access/storage.ts +521 -521
  97. package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
  98. package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
  99. package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
  100. package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
  101. package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
  102. package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
  103. package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
  104. package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
  105. package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
  106. package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
  107. package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
  108. package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
  109. package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
  110. package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
  111. package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
  112. package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
  113. package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
  114. package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
  115. package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
  116. package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
  117. package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
  118. package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
  119. package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
  120. package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
  121. package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
  122. package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
  123. package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
  124. package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
  125. package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
  126. package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
  127. package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
  128. package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
  129. package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
  130. package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
  131. package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
  132. package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
  133. package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
  134. package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
  135. package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
  136. package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
  137. package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
  138. package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
  139. package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
  140. package/agent/extensions/pi-web-access/tsconfig.json +11 -11
  141. package/agent/extensions/pi-web-access/utils.ts +451 -451
  142. package/agent/extensions/pi-web-access/video-extract.ts +392 -392
  143. package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
  144. package/agent/extensions/shared/activity-status.ts +31 -31
  145. package/agent/extensions/shared/child-session.test.ts +270 -270
  146. package/agent/extensions/shared/child-session.ts +148 -148
  147. package/agent/extensions/shared/context-utilization.test.ts +48 -48
  148. package/agent/extensions/shared/context-utilization.ts +47 -47
  149. package/agent/extensions/shared/dashboard-state.ts +99 -99
  150. package/agent/extensions/shared/install.ps1 +4 -0
  151. package/agent/extensions/shared/install.sh +3 -0
  152. package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
  153. package/agent/extensions/shared/tool-call-timeout.ts +104 -104
  154. package/agent/extensions/skillsconf/install.ps1 +4 -0
  155. package/agent/extensions/skillsconf/install.sh +3 -0
  156. package/agent/extensions/statusline/index.ts +282 -0
  157. package/agent/extensions/statusline/install.ps1 +27 -0
  158. package/agent/extensions/statusline/install.sh +24 -0
  159. package/agent/extensions/subagents/by-the-way.test.ts +29 -29
  160. package/agent/extensions/subagents/claude.test.ts +119 -119
  161. package/agent/extensions/subagents/codex.test.ts +102 -102
  162. package/agent/extensions/subagents/context-usage.test.ts +107 -107
  163. package/agent/extensions/subagents/docs/design-plan.md +568 -568
  164. package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
  165. package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
  166. package/agent/extensions/subagents/index.ts +779 -779
  167. package/agent/extensions/subagents/install.ps1 +4 -0
  168. package/agent/extensions/subagents/install.sh +3 -0
  169. package/agent/extensions/subagents/manager.test.ts +276 -276
  170. package/agent/extensions/subagents/package-lock.json +2244 -2244
  171. package/agent/extensions/subagents/package.json +19 -19
  172. package/agent/extensions/subagents/result-delivery.test.ts +27 -27
  173. package/agent/extensions/subagents/src/backend.ts +73 -73
  174. package/agent/extensions/subagents/src/backends/claude.ts +701 -701
  175. package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
  176. package/agent/extensions/subagents/src/backends/pi.ts +575 -575
  177. package/agent/extensions/subagents/src/backends/stub.ts +300 -300
  178. package/agent/extensions/subagents/src/by-the-way.ts +21 -21
  179. package/agent/extensions/subagents/src/domain.ts +253 -253
  180. package/agent/extensions/subagents/src/format.ts +74 -74
  181. package/agent/extensions/subagents/src/manager.ts +736 -736
  182. package/agent/extensions/subagents/src/prompt.ts +92 -92
  183. package/agent/extensions/subagents/src/result-delivery.ts +20 -20
  184. package/agent/extensions/subagents/src/runtime.ts +53 -53
  185. package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
  186. package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
  187. package/agent/extensions/subagents/takeover.test.ts +29 -29
  188. package/agent/extensions/subagents/tsconfig.json +7 -7
  189. package/agent/extensions/taste/install.ps1 +4 -0
  190. package/agent/extensions/taste/install.sh +3 -0
  191. package/agent/extensions/todo/AGENTS.md +38 -38
  192. package/agent/extensions/todo/LICENSE +21 -21
  193. package/agent/extensions/todo/config.ts +55 -55
  194. package/agent/extensions/todo/index.ts +151 -151
  195. package/agent/extensions/todo/install.ps1 +4 -0
  196. package/agent/extensions/todo/install.sh +3 -0
  197. package/agent/extensions/todo/locales/de.json +17 -17
  198. package/agent/extensions/todo/locales/en.json +15 -15
  199. package/agent/extensions/todo/locales/es.json +17 -17
  200. package/agent/extensions/todo/locales/fr.json +17 -17
  201. package/agent/extensions/todo/locales/pt-BR.json +17 -17
  202. package/agent/extensions/todo/locales/pt.json +17 -17
  203. package/agent/extensions/todo/locales/ru.json +17 -17
  204. package/agent/extensions/todo/locales/uk.json +17 -17
  205. package/agent/extensions/todo/locales/zh.json +17 -17
  206. package/agent/extensions/todo/package-lock.json +3358 -3358
  207. package/agent/extensions/todo/package.json +67 -67
  208. package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
  209. package/agent/extensions/todo/state/invariants.ts +20 -20
  210. package/agent/extensions/todo/state/replay.ts +38 -38
  211. package/agent/extensions/todo/state/selectors.ts +107 -107
  212. package/agent/extensions/todo/state/state-reducer.ts +326 -326
  213. package/agent/extensions/todo/state/state.ts +18 -18
  214. package/agent/extensions/todo/state/store.ts +82 -82
  215. package/agent/extensions/todo/state/task-graph.ts +57 -57
  216. package/agent/extensions/todo/todo-overlay.ts +200 -200
  217. package/agent/extensions/todo/todo.ts +155 -155
  218. package/agent/extensions/todo/tool/response-envelope.ts +109 -109
  219. package/agent/extensions/todo/tool/types.ts +206 -206
  220. package/agent/extensions/todo/view/format.ts +177 -177
  221. package/agent/extensions/trim-context/install.ps1 +4 -0
  222. package/agent/extensions/trim-context/install.sh +3 -0
  223. package/agent/keybindings.json +7 -7
  224. package/agent/version.txt +1 -0
  225. package/agent.bak.20260905-190020/AGENTS.md +67 -0
  226. package/agent.bak.20260905-190020/README.md +5 -0
  227. package/agent.bak.20260905-190020/extensions/ask-user/index.ts +418 -0
  228. package/agent.bak.20260905-190020/extensions/ask-user/package-lock.json +769 -0
  229. package/agent.bak.20260905-190020/extensions/ask-user/package.json +19 -0
  230. package/agent.bak.20260905-190020/extensions/ask-user/prompt.ts +45 -0
  231. package/agent.bak.20260905-190020/extensions/ask-user/tsconfig.json +7 -0
  232. package/agent.bak.20260905-190020/extensions/background-terminals/docs/implementation-guide.md +942 -0
  233. package/agent.bak.20260905-190020/extensions/background-terminals/index.ts +629 -0
  234. package/agent.bak.20260905-190020/extensions/background-terminals/install.ps1 +23 -0
  235. package/agent.bak.20260905-190020/extensions/background-terminals/install.sh +21 -0
  236. package/agent.bak.20260905-190020/extensions/background-terminals/package-lock.json +769 -0
  237. package/agent.bak.20260905-190020/extensions/background-terminals/package.json +17 -0
  238. package/agent.bak.20260905-190020/extensions/background-terminals/src/domain.ts +87 -0
  239. package/agent.bak.20260905-190020/extensions/background-terminals/src/manager.ts +907 -0
  240. package/agent.bak.20260905-190020/extensions/background-terminals/src/output.ts +84 -0
  241. package/agent.bak.20260905-190020/extensions/background-terminals/src/prompt.ts +142 -0
  242. package/agent.bak.20260905-190020/extensions/background-terminals/src/result-delivery.ts +27 -0
  243. package/agent.bak.20260905-190020/extensions/background-terminals/src/runtime.ts +36 -0
  244. package/agent.bak.20260905-190020/extensions/background-terminals/src/ui/output-view.ts +79 -0
  245. package/agent.bak.20260905-190020/extensions/background-terminals/src/ui/ps.ts +621 -0
  246. package/agent.bak.20260905-190020/extensions/background-terminals/src/widget-order.ts +121 -0
  247. package/agent.bak.20260905-190020/extensions/background-terminals/test/manager.test.ts +735 -0
  248. package/agent.bak.20260905-190020/extensions/background-terminals/test/output.test.ts +109 -0
  249. package/agent.bak.20260905-190020/extensions/background-terminals/test/prompt.test.ts +125 -0
  250. package/agent.bak.20260905-190020/extensions/background-terminals/test/ps.test.ts +82 -0
  251. package/agent.bak.20260905-190020/extensions/background-terminals/test/result-delivery.test.ts +44 -0
  252. package/agent.bak.20260905-190020/extensions/background-terminals/tsconfig.json +7 -0
  253. package/agent.bak.20260905-190020/extensions/destructive/README.md +34 -0
  254. package/agent.bak.20260905-190020/extensions/destructive/index.ts +124 -0
  255. package/agent.bak.20260905-190020/extensions/destructive/install.ps1 +23 -0
  256. package/agent.bak.20260905-190020/extensions/destructive/install.sh +21 -0
  257. package/agent.bak.20260905-190020/extensions/file-search/index.ts +459 -0
  258. package/agent.bak.20260905-190020/extensions/file-search/install.ps1 +23 -0
  259. package/agent.bak.20260905-190020/extensions/file-search/install.sh +21 -0
  260. package/agent.bak.20260905-190020/extensions/file-search/package-lock.json +2253 -0
  261. package/agent.bak.20260905-190020/extensions/file-search/package.json +23 -0
  262. package/agent.bak.20260905-190020/extensions/file-search/src/args.ts +122 -0
  263. package/agent.bak.20260905-190020/extensions/file-search/src/binaries.ts +422 -0
  264. package/agent.bak.20260905-190020/extensions/file-search/src/output.ts +126 -0
  265. package/agent.bak.20260905-190020/extensions/file-search/src/process.ts +146 -0
  266. package/agent.bak.20260905-190020/extensions/file-search/src/prompt.ts +52 -0
  267. package/agent.bak.20260905-190020/extensions/file-search/test/index.spec.ts +443 -0
  268. package/agent.bak.20260905-190020/extensions/file-search/tsconfig.json +7 -0
  269. package/agent.bak.20260905-190020/extensions/goal/README.md +50 -0
  270. package/agent.bak.20260905-190020/extensions/goal/index.ts +155 -0
  271. package/agent.bak.20260905-190020/extensions/goal/install.ps1 +23 -0
  272. package/agent.bak.20260905-190020/extensions/goal/install.sh +21 -0
  273. package/agent.bak.20260905-190020/extensions/kiln-update/README.md +25 -0
  274. package/agent.bak.20260905-190020/extensions/kiln-update/index.ts +99 -0
  275. package/agent.bak.20260905-190020/extensions/kiln-update/install.ps1 +23 -0
  276. package/agent.bak.20260905-190020/extensions/kiln-update/install.sh +21 -0
  277. package/agent.bak.20260905-190020/extensions/modelconf/PLAN.md +915 -0
  278. package/agent.bak.20260905-190020/extensions/modelconf/README.md +66 -0
  279. package/agent.bak.20260905-190020/extensions/modelconf/index.ts +296 -0
  280. package/agent.bak.20260905-190020/extensions/modelconf/install.ps1 +23 -0
  281. package/agent.bak.20260905-190020/extensions/modelconf/install.sh +21 -0
  282. package/agent.bak.20260905-190020/extensions/modelconf/package-lock.json +1809 -0
  283. package/agent.bak.20260905-190020/extensions/modelconf/package.json +13 -0
  284. package/agent.bak.20260905-190020/extensions/modelconf/src/fuzzy.ts +26 -0
  285. package/agent.bak.20260905-190020/extensions/modelconf/src/glob.ts +13 -0
  286. package/agent.bak.20260905-190020/extensions/modelconf/src/persistence.test.ts +46 -0
  287. package/agent.bak.20260905-190020/extensions/modelconf/src/persistence.ts +164 -0
  288. package/agent.bak.20260905-190020/extensions/modelconf/src/ui/ModelConfView.test.ts +75 -0
  289. package/agent.bak.20260905-190020/extensions/modelconf/src/ui/ModelConfView.ts +1101 -0
  290. package/agent.bak.20260905-190020/extensions/modelconf/tsconfig.json +15 -0
  291. package/agent.bak.20260905-190020/extensions/pi-web-access/CHANGELOG.md +690 -0
  292. package/agent.bak.20260905-190020/extensions/pi-web-access/LICENSE +21 -0
  293. package/agent.bak.20260905-190020/extensions/pi-web-access/README.md +470 -0
  294. package/agent.bak.20260905-190020/extensions/pi-web-access/SECURITY.md +5 -0
  295. package/agent.bak.20260905-190020/extensions/pi-web-access/activity.ts +101 -0
  296. package/agent.bak.20260905-190020/extensions/pi-web-access/auth-fetch.ts +148 -0
  297. package/agent.bak.20260905-190020/extensions/pi-web-access/banner.png +0 -0
  298. package/agent.bak.20260905-190020/extensions/pi-web-access/brightdata-unlocker.ts +272 -0
  299. package/agent.bak.20260905-190020/extensions/pi-web-access/chrome-cookies.ts +669 -0
  300. package/agent.bak.20260905-190020/extensions/pi-web-access/content-find.ts +139 -0
  301. package/agent.bak.20260905-190020/extensions/pi-web-access/credential-source.ts +191 -0
  302. package/agent.bak.20260905-190020/extensions/pi-web-access/data-uri-sanitize.ts +406 -0
  303. package/agent.bak.20260905-190020/extensions/pi-web-access/datalab-pdf-extract.ts +568 -0
  304. package/agent.bak.20260905-190020/extensions/pi-web-access/declared-web-links.ts +173 -0
  305. package/agent.bak.20260905-190020/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -0
  306. package/agent.bak.20260905-190020/extensions/pi-web-access/evidence/contract-probe.mjs +140 -0
  307. package/agent.bak.20260905-190020/extensions/pi-web-access/exa.ts +526 -0
  308. package/agent.bak.20260905-190020/extensions/pi-web-access/extract.ts +1196 -0
  309. package/agent.bak.20260905-190020/extensions/pi-web-access/feature-config.ts +29 -0
  310. package/agent.bak.20260905-190020/extensions/pi-web-access/fetch-params.ts +111 -0
  311. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-adc.ts +298 -0
  312. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-api.ts +353 -0
  313. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-pdf-extract.ts +108 -0
  314. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-search.ts +21 -0
  315. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-url-context.ts +128 -0
  316. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-web-config.ts +101 -0
  317. package/agent.bak.20260905-190020/extensions/pi-web-access/gemini-web.ts +487 -0
  318. package/agent.bak.20260905-190020/extensions/pi-web-access/github-api.ts +197 -0
  319. package/agent.bak.20260905-190020/extensions/pi-web-access/github-extract.ts +746 -0
  320. package/agent.bak.20260905-190020/extensions/pi-web-access/github-issue-pr.ts +700 -0
  321. package/agent.bak.20260905-190020/extensions/pi-web-access/index.ts +1737 -0
  322. package/agent.bak.20260905-190020/extensions/pi-web-access/package-lock.json +5808 -0
  323. package/agent.bak.20260905-190020/extensions/pi-web-access/package.json +64 -0
  324. package/agent.bak.20260905-190020/extensions/pi-web-access/page-query.ts +96 -0
  325. package/agent.bak.20260905-190020/extensions/pi-web-access/pdf-extract.ts +409 -0
  326. package/agent.bak.20260905-190020/extensions/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
  327. package/agent.bak.20260905-190020/extensions/pi-web-access/promise-try.d.ts +7 -0
  328. package/agent.bak.20260905-190020/extensions/pi-web-access/query-rewrite.ts +51 -0
  329. package/agent.bak.20260905-190020/extensions/pi-web-access/render-search-error.ts +170 -0
  330. package/agent.bak.20260905-190020/extensions/pi-web-access/rsc-extract.ts +338 -0
  331. package/agent.bak.20260905-190020/extensions/pi-web-access/search-types.ts +20 -0
  332. package/agent.bak.20260905-190020/extensions/pi-web-access/source-check.ts +282 -0
  333. package/agent.bak.20260905-190020/extensions/pi-web-access/ssrf-protection.ts +526 -0
  334. package/agent.bak.20260905-190020/extensions/pi-web-access/storage.ts +521 -0
  335. package/agent.bak.20260905-190020/extensions/pi-web-access/summary-model-scope.ts +125 -0
  336. package/agent.bak.20260905-190020/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -0
  337. package/agent.bak.20260905-190020/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -0
  338. package/agent.bak.20260905-190020/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -0
  339. package/agent.bak.20260905-190020/extensions/pi-web-access/test/config-path.test.mjs +283 -0
  340. package/agent.bak.20260905-190020/extensions/pi-web-access/test/content-find.test.mjs +25 -0
  341. package/agent.bak.20260905-190020/extensions/pi-web-access/test/credential-source.test.mjs +118 -0
  342. package/agent.bak.20260905-190020/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -0
  343. package/agent.bak.20260905-190020/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -0
  344. package/agent.bak.20260905-190020/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -0
  345. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -0
  346. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -0
  347. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -0
  348. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -0
  349. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -0
  350. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-params.test.mjs +86 -0
  351. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -0
  352. package/agent.bak.20260905-190020/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -0
  353. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -0
  354. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -0
  355. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -0
  356. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -0
  357. package/agent.bak.20260905-190020/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -0
  358. package/agent.bak.20260905-190020/extensions/pi-web-access/test/get-search-content.test.mjs +223 -0
  359. package/agent.bak.20260905-190020/extensions/pi-web-access/test/github-extract.test.mjs +378 -0
  360. package/agent.bak.20260905-190020/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -0
  361. package/agent.bak.20260905-190020/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -0
  362. package/agent.bak.20260905-190020/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -0
  363. package/agent.bak.20260905-190020/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -0
  364. package/agent.bak.20260905-190020/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -0
  365. package/agent.bak.20260905-190020/extensions/pi-web-access/test/page-query.test.mjs +51 -0
  366. package/agent.bak.20260905-190020/extensions/pi-web-access/test/pdf-config.test.mjs +140 -0
  367. package/agent.bak.20260905-190020/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -0
  368. package/agent.bak.20260905-190020/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -0
  369. package/agent.bak.20260905-190020/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -0
  370. package/agent.bak.20260905-190020/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -0
  371. package/agent.bak.20260905-190020/extensions/pi-web-access/test/search-error-render.test.mjs +152 -0
  372. package/agent.bak.20260905-190020/extensions/pi-web-access/test/search-providers.test.mjs +274 -0
  373. package/agent.bak.20260905-190020/extensions/pi-web-access/test/source-check.test.mjs +179 -0
  374. package/agent.bak.20260905-190020/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -0
  375. package/agent.bak.20260905-190020/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -0
  376. package/agent.bak.20260905-190020/extensions/pi-web-access/test/summary-model-scope.test.mjs +106 -0
  377. package/agent.bak.20260905-190020/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -0
  378. package/agent.bak.20260905-190020/extensions/pi-web-access/test/web-search-answer-render.test.mjs +66 -0
  379. package/agent.bak.20260905-190020/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -0
  380. package/agent.bak.20260905-190020/extensions/pi-web-access/tsconfig.json +11 -0
  381. package/agent.bak.20260905-190020/extensions/pi-web-access/utils.ts +451 -0
  382. package/agent.bak.20260905-190020/extensions/pi-web-access/video-extract.ts +392 -0
  383. package/agent.bak.20260905-190020/extensions/pi-web-access/youtube-extract.ts +328 -0
  384. package/agent.bak.20260905-190020/extensions/shared/activity-status.ts +31 -0
  385. package/agent.bak.20260905-190020/extensions/shared/child-session.ts +148 -0
  386. package/agent.bak.20260905-190020/extensions/shared/context-utilization.ts +47 -0
  387. package/agent.bak.20260905-190020/extensions/shared/dashboard-state.ts +99 -0
  388. package/agent.bak.20260905-190020/extensions/shared/install.ps1 +23 -0
  389. package/agent.bak.20260905-190020/extensions/shared/install.sh +21 -0
  390. package/agent.bak.20260905-190020/extensions/shared/test/child-session.test.ts +270 -0
  391. package/agent.bak.20260905-190020/extensions/shared/test/context-utilization.test.ts +48 -0
  392. package/agent.bak.20260905-190020/extensions/shared/test/tool-call-timeout.test.ts +117 -0
  393. package/agent.bak.20260905-190020/extensions/shared/test/widget-order.test.ts +167 -0
  394. package/agent.bak.20260905-190020/extensions/shared/tool-call-timeout.ts +104 -0
  395. package/agent.bak.20260905-190020/extensions/skillsconf/README.md +74 -0
  396. package/agent.bak.20260905-190020/extensions/skillsconf/index.ts +92 -0
  397. package/agent.bak.20260905-190020/extensions/skillsconf/install.ps1 +23 -0
  398. package/agent.bak.20260905-190020/extensions/skillsconf/install.sh +21 -0
  399. package/agent.bak.20260905-190020/extensions/skillsconf/package-lock.json +1809 -0
  400. package/agent.bak.20260905-190020/extensions/skillsconf/package.json +18 -0
  401. package/agent.bak.20260905-190020/extensions/skillsconf/src/delete-skill.test.ts +64 -0
  402. package/agent.bak.20260905-190020/extensions/skillsconf/src/delete-skill.ts +45 -0
  403. package/agent.bak.20260905-190020/extensions/skillsconf/src/filter.test.ts +80 -0
  404. package/agent.bak.20260905-190020/extensions/skillsconf/src/filter.ts +74 -0
  405. package/agent.bak.20260905-190020/extensions/skillsconf/src/fuzzy.ts +26 -0
  406. package/agent.bak.20260905-190020/extensions/skillsconf/src/persistence.test.ts +72 -0
  407. package/agent.bak.20260905-190020/extensions/skillsconf/src/persistence.ts +169 -0
  408. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/SkillConfView.test.ts +337 -0
  409. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/SkillConfView.ts +758 -0
  410. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/text-input.ts +91 -0
  411. package/agent.bak.20260905-190020/extensions/skillsconf/src/ui/tui-helpers.ts +144 -0
  412. package/agent.bak.20260905-190020/extensions/skillsconf/tsconfig.json +15 -0
  413. package/agent.bak.20260905-190020/extensions/statusline/install.ps1 +23 -0
  414. package/agent.bak.20260905-190020/extensions/statusline/install.sh +21 -0
  415. package/agent.bak.20260905-190020/extensions/subagents/docs/design-plan.md +568 -0
  416. package/agent.bak.20260905-190020/extensions/subagents/docs/effect-v4-extension-guide.md +354 -0
  417. package/agent.bak.20260905-190020/extensions/subagents/docs/effect-v4-notes.md +571 -0
  418. package/agent.bak.20260905-190020/extensions/subagents/index.ts +826 -0
  419. package/agent.bak.20260905-190020/extensions/subagents/install.ps1 +23 -0
  420. package/agent.bak.20260905-190020/extensions/subagents/install.sh +21 -0
  421. package/agent.bak.20260905-190020/extensions/subagents/package-lock.json +2244 -0
  422. package/agent.bak.20260905-190020/extensions/subagents/package.json +19 -0
  423. package/agent.bak.20260905-190020/extensions/subagents/src/backend.ts +73 -0
  424. package/agent.bak.20260905-190020/extensions/subagents/src/backends/claude.ts +701 -0
  425. package/agent.bak.20260905-190020/extensions/subagents/src/backends/codex.ts +1060 -0
  426. package/agent.bak.20260905-190020/extensions/subagents/src/backends/pi.ts +575 -0
  427. package/agent.bak.20260905-190020/extensions/subagents/src/backends/stub.ts +300 -0
  428. package/agent.bak.20260905-190020/extensions/subagents/src/by-the-way.ts +21 -0
  429. package/agent.bak.20260905-190020/extensions/subagents/src/domain.ts +253 -0
  430. package/agent.bak.20260905-190020/extensions/subagents/src/format.ts +74 -0
  431. package/agent.bak.20260905-190020/extensions/subagents/src/manager.ts +736 -0
  432. package/agent.bak.20260905-190020/extensions/subagents/src/prompt.ts +92 -0
  433. package/agent.bak.20260905-190020/extensions/subagents/src/result-delivery.ts +20 -0
  434. package/agent.bak.20260905-190020/extensions/subagents/src/runtime.ts +53 -0
  435. package/agent.bak.20260905-190020/extensions/subagents/src/ui/takeover.ts +583 -0
  436. package/agent.bak.20260905-190020/extensions/subagents/src/ui/transcript.ts +201 -0
  437. package/agent.bak.20260905-190020/extensions/subagents/src/widget-order.ts +121 -0
  438. package/agent.bak.20260905-190020/extensions/subagents/test/by-the-way.test.ts +29 -0
  439. package/agent.bak.20260905-190020/extensions/subagents/test/claude.test.ts +119 -0
  440. package/agent.bak.20260905-190020/extensions/subagents/test/codex.test.ts +102 -0
  441. package/agent.bak.20260905-190020/extensions/subagents/test/context-usage.test.ts +107 -0
  442. package/agent.bak.20260905-190020/extensions/subagents/test/manager.test.ts +276 -0
  443. package/agent.bak.20260905-190020/extensions/subagents/test/result-delivery.test.ts +27 -0
  444. package/agent.bak.20260905-190020/extensions/subagents/test/takeover.test.ts +29 -0
  445. package/agent.bak.20260905-190020/extensions/subagents/tsconfig.json +7 -0
  446. package/agent.bak.20260905-190020/extensions/taste/index.ts +443 -0
  447. package/agent.bak.20260905-190020/extensions/taste/install.ps1 +23 -0
  448. package/agent.bak.20260905-190020/extensions/taste/install.sh +21 -0
  449. package/agent.bak.20260905-190020/extensions/todo/AGENTS.md +38 -0
  450. package/agent.bak.20260905-190020/extensions/todo/LICENSE +21 -0
  451. package/agent.bak.20260905-190020/extensions/todo/config.ts +55 -0
  452. package/agent.bak.20260905-190020/extensions/todo/index.ts +151 -0
  453. package/agent.bak.20260905-190020/extensions/todo/install.ps1 +23 -0
  454. package/agent.bak.20260905-190020/extensions/todo/install.sh +21 -0
  455. package/agent.bak.20260905-190020/extensions/todo/locales/de.json +17 -0
  456. package/agent.bak.20260905-190020/extensions/todo/locales/en.json +15 -0
  457. package/agent.bak.20260905-190020/extensions/todo/locales/es.json +17 -0
  458. package/agent.bak.20260905-190020/extensions/todo/locales/fr.json +17 -0
  459. package/agent.bak.20260905-190020/extensions/todo/locales/pt-BR.json +17 -0
  460. package/agent.bak.20260905-190020/extensions/todo/locales/pt.json +17 -0
  461. package/agent.bak.20260905-190020/extensions/todo/locales/ru.json +17 -0
  462. package/agent.bak.20260905-190020/extensions/todo/locales/uk.json +17 -0
  463. package/agent.bak.20260905-190020/extensions/todo/locales/zh.json +17 -0
  464. package/agent.bak.20260905-190020/extensions/todo/package-lock.json +3358 -0
  465. package/agent.bak.20260905-190020/extensions/todo/package.json +67 -0
  466. package/agent.bak.20260905-190020/extensions/todo/state/i18n-bridge.ts +64 -0
  467. package/agent.bak.20260905-190020/extensions/todo/state/invariants.ts +20 -0
  468. package/agent.bak.20260905-190020/extensions/todo/state/replay.ts +38 -0
  469. package/agent.bak.20260905-190020/extensions/todo/state/selectors.ts +107 -0
  470. package/agent.bak.20260905-190020/extensions/todo/state/state-reducer.ts +326 -0
  471. package/agent.bak.20260905-190020/extensions/todo/state/state.ts +18 -0
  472. package/agent.bak.20260905-190020/extensions/todo/state/store.ts +82 -0
  473. package/agent.bak.20260905-190020/extensions/todo/state/task-graph.ts +57 -0
  474. package/agent.bak.20260905-190020/extensions/todo/todo-overlay.ts +204 -0
  475. package/agent.bak.20260905-190020/extensions/todo/todo.ts +155 -0
  476. package/agent.bak.20260905-190020/extensions/todo/tool/response-envelope.ts +109 -0
  477. package/agent.bak.20260905-190020/extensions/todo/tool/types.ts +206 -0
  478. package/agent.bak.20260905-190020/extensions/todo/view/format.ts +177 -0
  479. package/agent.bak.20260905-190020/extensions/todo/widget-order.ts +121 -0
  480. package/agent.bak.20260905-190020/extensions/trim-context/README.md +54 -0
  481. package/agent.bak.20260905-190020/extensions/trim-context/index.ts +487 -0
  482. package/agent.bak.20260905-190020/extensions/trim-context/install.ps1 +23 -0
  483. package/agent.bak.20260905-190020/extensions/trim-context/install.sh +21 -0
  484. package/agent.bak.20260905-190020/keybindings.json +7 -0
  485. package/bin/kiln.js +588 -358
  486. package/package.json +27 -21
  487. package/agent/install.ps1 +0 -527
  488. package/agent/install.sh +0 -511
  489. /package/{agent/extensions/status line → agent.bak.20260905-190020/extensions/ask-user}/install.ps1 +0 -0
  490. /package/{agent/extensions/status line → agent.bak.20260905-190020/extensions/ask-user}/install.sh +0 -0
  491. /package/{agent/extensions/status line → agent.bak.20260905-190020/extensions/statusline}/index.ts +0 -0
package/bin/kiln.js CHANGED
@@ -1,359 +1,589 @@
1
- #!/usr/bin/env node
2
- // kiln installer — cross-platform Node port of agent/install.sh + agent/install.ps1.
3
- // 1) Install/update pi (@earendil-works/pi-coding-agent)
4
- // 2) Install pi packages (pi-context-usage, @baretread/pi-forge)
5
- // 3) Install custom config (AGENTS.md, keybindings, extensions …)
6
- // 4) Install per-extension npm deps via each extension's own installer
7
- //
8
- // Usage:
9
- // npx @asterxsk/kiln --yes
10
- // npm i -g @asterxsk/kiln && kiln --yes
11
- // bunx @asterxsk/kiln --yes
12
- // node bin/kiln.js --target /tmp/pi-test --skip-pi --skip-packages --yes
13
- //
14
- // Safe to re-run. Managed files are force-overwritten; per-user files
15
- // (settings.json, taste.md, auth.json, sessions, etc.) are preserved.
16
- "use strict";
17
-
18
- const { spawn, spawnSync } = require("node:child_process");
19
- const fs = require("node:fs");
20
- const os = require("node:os");
21
- const path = require("node:path");
22
- const crypto = require("node:crypto");
23
-
24
- const PI_PACKAGE = "@earendil-works/pi-coding-agent";
25
- const NODE_MIN = [22, 19, 0];
26
- const DEFAULT_REPO = "https://github.com/asterxsk/kiln";
27
- const DEFAULT_BRANCH = "main";
28
- const PACKAGES = ["pi-context-usage@latest", "@baretread/pi-forge@latest"];
29
- const SETTINGS_PACKAGES = ["npm:pi-context-usage", "npm:@baretread/pi-forge"];
30
-
31
- const startTime = Date.now();
32
- let clonedTmp = "";
33
- let tmpLog = "";
34
- let ok = true; // hard failures flip this; warnings don't
35
-
36
- // ── args ────────────────────────────────────────────────────────────────
37
- function parseArgs(argv) {
38
- const o = {
39
- repo: process.env.PI_CONFIG_REPO || "",
40
- branch: process.env.PI_CONFIG_BRANCH || DEFAULT_BRANCH,
41
- target: process.env.PI_AGENT_DIR || "",
42
- skipPi: false, skipPackages: false, yes: false, local: false,
43
- };
44
- for (let i = 0; i < argv.length; i++) {
45
- const a = argv[i];
46
- if (a === "--repo") o.repo = argv[++i] ?? "";
47
- else if (a === "--branch") o.branch = argv[++i] ?? "";
48
- else if (a === "--target") o.target = argv[++i] ?? "";
49
- else if (a === "--skip-pi") o.skipPi = true;
50
- else if (a === "--skip-packages") o.skipPackages = true;
51
- else if (a === "--local") o.local = true;
52
- else if (a === "--yes" || a === "-y") o.yes = true;
53
- else if (a === "--help" || a === "-h") {
54
- console.log("Usage: kiln [--repo URL] [--branch BRANCH] [--target DIR] [--local] [--skip-pi] [--skip-packages] [--yes]");
55
- process.exit(0);
56
- } else if (a === "--") break;
57
- else if (a.startsWith("-")) { console.error(`unknown arg: ${a}`); process.exit(1); }
58
- }
59
- if (!o.repo) o.repo = DEFAULT_REPO;
60
- return o;
61
- }
62
-
63
- // ── output ──────────────────────────────────────────────────────────────
64
- const isTTY = !!process.stdout.isTTY && process.env.TERM !== "dumb";
65
- const C = isTTY
66
- ? { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m", gray: "\x1b[90m" }
67
- : { reset: "", bold: "", dim: "", green: "", yellow: "", red: "", cyan: "", gray: "" };
68
- const line = (s) => process.stdout.write(s + "\n");
69
- const log = (s) => { try { fs.appendFileSync(tmpLog, s + "\n"); } catch {} };
70
-
71
- function title() {
72
- line("");
73
- line(` ${C.bold}◆ Pi Setup${C.reset}`);
74
- line(` ${C.dim}custom agent config · pi + forge + extensions${C.reset}`);
75
- line("");
76
- }
77
-
78
- // Run a command: spinner on TTY, quiet otherwise. Output always → log.
79
- function runStep(step, label, cmd, args, opts = {}) {
80
- return new Promise((resolve) => {
81
- const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], shell: false, ...opts });
82
- let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠰", "⠠", "⠦", "⠧", "⠇", "⠏"];
83
- let i = 0, timer = null;
84
- if (isTTY) {
85
- timer = setInterval(() => {
86
- process.stdout.write(`\r\x1b[K ${C.yellow}${frames[i++ % frames.length]}${C.reset} ${C.dim}[${step}/4]${C.reset} ${label}`);
87
- }, 80);
88
- }
89
- child.stdout.on("data", (d) => log(d.toString().trimEnd()));
90
- child.stderr.on("data", (d) => log(d.toString().trimEnd()));
91
- child.on("error", (e) => { log(`spawn failed: ${e.message}`); if (timer) clearInterval(timer); if (isTTY) process.stdout.write("\r\x1b[K"); resolve(1); });
92
- child.on("close", (code) => { if (timer) clearInterval(timer); if (isTTY) process.stdout.write("\r\x1b[K"); resolve(code ?? 1); });
93
- });
94
- }
95
-
96
- function have(cmd) {
97
- try {
98
- if (process.platform === "win32") return spawnSync("where", [cmd], { stdio: "ignore" }).status === 0;
99
- return spawnSync("sh", ["-c", `command -v "${cmd}"`], { stdio: "ignore" }).status === 0;
100
- } catch { return false; }
101
- }
102
- function cmdOut(cmd, args) {
103
- try { return spawnSync(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).stdout.trim(); }
104
- catch { return ""; }
105
- }
106
- function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
107
- // Quote one shell word (cmd.exe + sh compatible for our controlled args).
108
- const q = (a) => (/[\s"']/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a);
109
- function cmdOutShell(command) {
110
- try { return spawnSync(command, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], shell: true }).stdout.trim(); }
111
- catch { return ""; }
112
- }
113
- // npm on Windows is npm.cmd (a batch shim) — it cannot be spawned directly,
114
- // so run it through the shell as a single pre-quoted command string.
115
- function npmStep(step, label, args) {
116
- if (process.platform === "win32") return runStep(step, label, "npm.cmd " + args.map(q).join(" "), [], { shell: true });
117
- return runStep(step, label, "npm", args);
118
- }
119
- const npmVer = process.platform === "win32" ? cmdOutShell("npm.cmd --version") : cmdOut("npm", ["--version"]);
120
-
121
- // Recursive copy, excluding names (node_modules, .git). Returns false on error.
122
- function copyTree(src, dst, exclude = new Set(["node_modules", ".git"])) {
123
- try {
124
- if (path.resolve(src) === path.resolve(dst)) return true;
125
- fs.rmSync(dst, { recursive: true, force: true });
126
- fs.mkdirSync(dst, { recursive: true });
127
- for (const e of fs.readdirSync(src, { withFileTypes: true })) {
128
- if (exclude.has(e.name)) continue;
129
- const s = path.join(src, e.name), d = path.join(dst, e.name);
130
- if (e.isDirectory()) { if (!copyTree(s, d, exclude)) return false; }
131
- else if (e.isFile()) fs.copyFileSync(s, d);
132
- }
133
- return true;
134
- } catch (e) { log(`copy failed ${src} → ${dst}: ${e.message}`); return false; }
135
- }
136
-
137
- function resolveTarget(opt) {
138
- if (opt) return opt;
139
- return path.join(os.homedir(), ".pi", "agent");
140
- }
141
-
142
- // Source root: bundled payload (npm tarball or dev checkout) → else git clone.
143
- function resolveSourceRoot(args) {
144
- const pkgRoot = path.resolve(__dirname, ".."); // bin/ → package root
145
- const cands = [path.join(pkgRoot, "agent"), pkgRoot, process.cwd(), path.join(process.cwd(), "agent")];
146
- for (const r of cands) {
147
- if (fs.existsSync(path.join(r, "extensions")) && fs.existsSync(path.join(r, "AGENTS.md"))) return r;
148
- }
149
- if (args.local) throw new Error(`local checkout not found (tried: ${cands.join(", ")})`);
150
- const base = path.join(os.homedir(), ".pi", "tmp");
151
- fs.mkdirSync(base, { recursive: true });
152
- const tmp = path.join(base, "kiln-" + crypto.randomBytes(4).toString("hex"));
153
- fs.mkdirSync(tmp, { recursive: true });
154
- line(`${C.dim} → cloning ${args.repo} (branch ${args.branch}) → ${tmp}${C.reset}`);
155
- const r = spawnSync("git", ["clone", "--depth", "1", "--branch", args.branch, args.repo, tmp],
156
- { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8", env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never", GIT_ASKPASS: "echo" } });
157
- if (r.stdout) log(r.stdout.trimEnd());
158
- if (r.stderr) log(r.stderr.trimEnd());
159
- if (r.status !== 0) throw new Error(`git clone failed (exit ${r.status}). See ${tmpLog}`);
160
- clonedTmp = tmp;
161
- if (fs.existsSync(path.join(tmp, "agent"))) return path.join(tmp, "agent");
162
- return tmp;
163
- }
164
-
165
- async function main() {
166
- const args = parseArgs(process.argv.slice(2));
167
- const tmpBase = path.join(os.homedir(), ".pi", "tmp");
168
- fs.mkdirSync(tmpBase, { recursive: true });
169
- tmpLog = path.join(tmpBase, `pi-setup-${crypto.randomBytes(4).toString("hex")}.log`);
170
- fs.writeFileSync(tmpLog, "");
171
- process.on("exit", () => { if (clonedTmp && clonedTmp.startsWith(path.join(tmpBase, "kiln-"))) fs.rmSync(clonedTmp, { recursive: true, force: true }); });
172
-
173
- title();
174
- const targetDir = resolveTarget(args.target);
175
- line(`${C.dim} target ${C.reset}${targetDir}`);
176
- line(`${C.dim} repo ${C.reset}${args.repo} ${C.dim}(${args.branch})${C.reset}`);
177
- line(`${C.dim} log ${C.reset}${tmpLog}\n`);
178
-
179
- // ── preflight ──
180
- const nodeVer = process.versions.node.split("-")[0];
181
- const nv = nodeVer.split(".").map(Number);
182
- const nodeOk = nv[0] > NODE_MIN[0] || (nv[0] === NODE_MIN[0] && (nv[1] > NODE_MIN[1] || (nv[1] === NODE_MIN[1] && nv[2] >= NODE_MIN[2])));
183
- if (!nodeOk) {
184
- line(`${C.yellow} ⚠ Node ${nodeVer} < ${NODE_MIN.join(".")} — please upgrade to Node ${NODE_MIN.join(".")}+${C.reset}`);
185
- if (!args.yes) { line(" continue anyway in 3s…"); await sleep(3000); }
186
- }
187
- if (!have("npm")) { line(`${C.red} ✖ npm not found (Node installed but npm missing)${C.reset}`); process.exit(1); }
188
- line(`${C.dim} node ${nodeVer} · npm ${npmVer} · ${cmdOut("git", ["--version"]) || "git n/a"}${C.reset}\n`);
189
-
190
- // git is only needed when we must clone (no bundled payload and not --local)
191
- const pkgAgent = path.join(path.resolve(__dirname, ".."), "agent");
192
- const haveSource = fs.existsSync(path.join(pkgAgent, "extensions")) && fs.existsSync(path.join(pkgAgent, "AGENTS.md"));
193
- if (!have("git") && !haveSource && !args.local) { line(`${C.red} ✖ git not found — required to fetch config${C.reset}`); process.exit(1); }
194
-
195
- // ── [1/4] pi ──
196
- if (args.skipPi) {
197
- line(` ${C.green}✔${C.reset} ${C.dim}[1/4]${C.reset} Install pi ${C.dim}— skipped${C.reset}`);
198
- } else {
199
- const label = have("pi") ? `updating pi (${PI_PACKAGE})` : `installing pi (${PI_PACKAGE})`;
200
- const code = await npmStep("1", label, ["install", "-g", `${PI_PACKAGE}@latest`, "--no-audit", "--no-fund", "--min-release-age=0"]);
201
- if (code === 0) {
202
- line(` ${C.green}✔${C.reset} ${C.dim}[1/4]${C.reset} ${label} ${C.dim}— ${cmdOut("pi", ["--version"]) || "?"}${C.reset}`);
203
- } else {
204
- line(` ${C.red}✖${C.reset} ${C.dim}[1/4]${C.reset} ${label} ${C.red}failed (exit ${code})${C.reset} ${C.dim}see ${tmpLog}${C.reset}`);
205
- ok = false;
206
- }
207
- }
208
- // ── [2/4] pi packages ──
209
- if (args.skipPackages) {
210
- line(` ${C.green}✔${C.reset} ${C.dim}[2/4]${C.reset} pi packages ${C.dim}— skipped${C.reset}`);
211
- } else {
212
- const label = "installing pi-context-usage + @baretread/pi-forge";
213
- const code = await npmStep("2", label, ["install", "-g", ...PACKAGES, "--no-audit", "--no-fund"]);
214
- if (code === 0) line(` ${C.green}✔${C.reset} ${C.dim}[2/4]${C.reset} pi packages ${C.dim}— done${C.reset}`);
215
- else {
216
- line(` ${C.yellow}⚠${C.reset} ${C.dim}[2/4]${C.reset} ${label} ${C.yellow}exit ${code} — continuing (pi will auto-install)${C.reset}`);
217
- line(` ${C.dim}see ${tmpLog}${C.reset}`);
218
- }
219
- }
220
- // ── [3/4] custom config ──
221
- try {
222
- const sourceRoot = resolveSourceRoot(args);
223
- line(`${C.dim} source ${sourceRoot}${C.reset}`);
224
- fs.mkdirSync(targetDir, { recursive: true });
225
-
226
- if (fs.existsSync(path.join(targetDir, "AGENTS.md")) || fs.existsSync(path.join(targetDir, "extensions"))) {
227
- const d = new Date(), p2 = (n) => String(n).padStart(2, "0");
228
- const bak = `${targetDir}.bak.${d.getFullYear()}${p2(d.getMonth() + 1)}${p2(d.getDate())}-${p2(d.getHours())}${p2(d.getMinutes())}${p2(d.getSeconds())}`;
229
- line(`${C.dim} backup ${targetDir}/extensions → ${bak}${C.reset}`);
230
- fs.mkdirSync(bak, { recursive: true });
231
- for (const f of ["AGENTS.md", "keybindings.json", "README.md"]) {
232
- const s = path.join(targetDir, f);
233
- if (fs.existsSync(s)) try { fs.copyFileSync(s, path.join(bak, f)); } catch {}
234
- }
235
- const extSrc = path.join(targetDir, "extensions");
236
- if (fs.existsSync(extSrc)) {
237
- fs.mkdirSync(path.join(bak, "extensions"), { recursive: true });
238
- for (const e of fs.readdirSync(extSrc, { withFileTypes: true })) {
239
- if (!e.isDirectory()) continue;
240
- copyTree(path.join(extSrc, e.name), path.join(bak, "extensions", e.name));
241
- }
242
- }
243
- }
244
-
245
- let copied = 0;
246
- const selfInstall = path.resolve(sourceRoot) === path.resolve(targetDir);
247
- if (selfInstall) line(`${C.dim} source == target — skipping file copy (self-install)${C.reset}`);
248
- for (const f of ["AGENTS.md", "keybindings.json", "README.md"]) {
249
- if (!fs.existsSync(path.join(sourceRoot, f))) continue;
250
- if (!selfInstall) fs.copyFileSync(path.join(sourceRoot, f), path.join(targetDir, f));
251
- copied++;
252
- }
253
- let freshSettings = false;
254
- if (!fs.existsSync(path.join(targetDir, "settings.json")) && fs.existsSync(path.join(sourceRoot, "settings.json"))) {
255
- fs.copyFileSync(path.join(sourceRoot, "settings.json"), path.join(targetDir, "settings.json"));
256
- line(`${C.dim} created settings.json from repo defaults${C.reset}`);
257
- freshSettings = true;
258
- } else if (fs.existsSync(path.join(targetDir, "settings.json"))) {
259
- line(`${C.dim} kept existing settings.json${C.reset}`);
260
- }
261
- if (freshSettings) {
262
- try {
263
- const p = path.join(targetDir, "settings.json");
264
- const j = JSON.parse(fs.readFileSync(p, "utf8"));
265
- j.packages = j.packages || [];
266
- let changed = false;
267
- for (const pkg of SETTINGS_PACKAGES) if (!j.packages.includes(pkg)) { j.packages.push(pkg); changed = true; }
268
- if (changed) { fs.writeFileSync(p, JSON.stringify(j, null, 2) + "\n"); line(" patched settings.json packages → forge + context"); }
269
- } catch {}
270
- }
271
- for (const t of ["taste.md", "taste", "taste.json"]) {
272
- const tp = path.join(targetDir, t);
273
- if (fs.existsSync(tp)) line(`${C.dim} kept existing ${t}${C.reset}`);
274
- else if (fs.existsSync(path.join(sourceRoot, t))) {
275
- const s = path.join(sourceRoot, t);
276
- if (fs.statSync(s).isDirectory()) copyTree(s, tp, new Set());
277
- else fs.copyFileSync(s, tp);
278
- }
279
- }
280
-
281
- if (fs.existsSync(path.join(sourceRoot, "extensions"))) {
282
- fs.mkdirSync(path.join(targetDir, "extensions"), { recursive: true });
283
- for (const e of fs.readdirSync(path.join(sourceRoot, "extensions"), { withFileTypes: true })) {
284
- if (!e.isDirectory() || e.name.endsWith(".md")) continue;
285
- if (copyTree(path.join(sourceRoot, "extensions", e.name), path.join(targetDir, "extensions", e.name))) copied++;
286
- else line(`${C.yellow} ⚠ copy extensions/${e.name} failed${C.reset}`);
287
- }
288
- }
289
-
290
- if (!selfInstall && fs.existsSync(path.join(sourceRoot, "themes"))) {
291
- fs.rmSync(path.join(targetDir, "themes"), { recursive: true, force: true });
292
- fs.mkdirSync(path.join(targetDir, "themes"), { recursive: true });
293
- for (const e of fs.readdirSync(path.join(sourceRoot, "themes"), { withFileTypes: true })) {
294
- if (e.name === ".pi" || e.name === "nul") continue;
295
- const s = path.join(sourceRoot, "themes", e.name), dd = path.join(targetDir, "themes", e.name);
296
- if (e.isDirectory()) copyTree(s, dd, new Set());
297
- else if (e.isFile()) try { fs.copyFileSync(s, dd); } catch {}
298
- }
299
- }
300
-
301
- line(` ${C.green}✔${C.reset} ${C.dim}[3/4]${C.reset} custom config ${C.dim}— ${copied} items → ${targetDir}${C.reset}`);
302
- if (clonedTmp) { fs.rmSync(clonedTmp, { recursive: true, force: true }); clonedTmp = ""; }
303
- const repoReadme = path.resolve(sourceRoot, "..", "README.md");
304
- const parentReadme = path.resolve(targetDir, "..", "README.md");
305
- if (repoReadme !== parentReadme && fs.existsSync(repoReadme)) {
306
- try { fs.copyFileSync(repoReadme, parentReadme); } catch {}
307
- }
308
- } catch (e) {
309
- line(` ${C.red}✖${C.reset} ${C.dim}[3/4]${C.reset} custom config ${C.red}failed:${C.reset} ${e.message}`);
310
- ok = false;
311
- }
312
- // ── [4/4] per-extension deps ──
313
- const extRoot = path.join(targetDir, "extensions");
314
- if (!fs.existsSync(extRoot)) {
315
- line(` ${C.yellow}⚠${C.reset} ${C.dim}[4/4]${C.reset} no extensions found at ${extRoot}`);
316
- } else {
317
- const failed = [];
318
- let installed = 0;
319
- for (const e of fs.readdirSync(extRoot, { withFileTypes: true })) {
320
- if (!e.isDirectory() || e.name.endsWith(".md")) continue;
321
- const dir = path.join(extRoot, e.name), label = `extensions/${e.name}`;
322
- const hasPkg = fs.existsSync(path.join(dir, "package.json"));
323
- const sh = path.join(dir, "install.sh"), ps1 = path.join(dir, "install.ps1");
324
- let code;
325
- if (process.platform === "win32" && fs.existsSync(ps1)) {
326
- const shell = have("pwsh") ? "pwsh" : "powershell";
327
- code = await runStep("4", label, shell, ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ps1]);
328
- } else if (process.platform !== "win32" && fs.existsSync(sh)) {
329
- code = await runStep("4", label, "sh", [sh]);
330
- } else if (hasPkg) {
331
- const a = ["--prefix", dir, fs.existsSync(path.join(dir, "package-lock.json")) ? "ci" : "install", "--no-audit", "--no-fund"];
332
- code = await npmStep("4", label, a);
333
- } else {
334
- line(` ${C.dim} · ${label} — no package.json, skipping${C.reset}`);
335
- continue;
336
- }
337
- if (code === 0) { line(` ${C.green}✔${C.reset} ${C.dim}[4/4]${C.reset} ${label}`); installed++; }
338
- else { line(` ${C.red}✖${C.reset} ${C.dim}[4/4]${C.reset} ${label} ${C.red}failed (exit ${code})${C.reset}`); failed.push(e.name); }
339
- }
340
- if (!failed.length) line(` ${C.green}✔${C.reset} ${C.dim}[4/4]${C.reset} extension deps ${C.dim}— ${installed} installed${C.reset}`);
341
- else line(` ${C.yellow}⚠${C.reset} ${C.dim}[4/4]${C.reset} extension deps ${C.yellow}${failed.length} failed:${C.reset} ${failed.join(", ")} ${C.dim}see ${tmpLog}${C.reset}`);
342
- }
343
-
344
- // ── summary ──
345
- const secs = Math.round((Date.now() - startTime) / 1000);
346
- const elapsed = secs >= 60 ? `${Math.floor(secs / 60)}m ${secs % 60}s` : `${secs}s`;
347
- line("");
348
- if (ok) {
349
- line(`${C.green}${C.bold} ✓ Setup complete${C.reset} ${C.dim}→ ${targetDir} · Kiln installed in ${elapsed}${C.reset}`);
350
- const pv = cmdOut("pi", ["--version"]);
351
- if (pv) line(`${C.dim} pi ${pv} · run: pi${C.reset}`);
352
- } else {
353
- line(`${C.yellow}${C.bold} ⚠ Setup finished with warnings${C.reset} ${C.dim}in ${elapsed} · see ${tmpLog}${C.reset}`);
354
- }
355
- line(`${C.dim} log: ${tmpLog}${C.reset}\n`);
356
- process.exitCode = ok ? 0 : 1;
357
- }
358
-
1
+ #!/usr/bin/env node
2
+ // kiln installer (npm) — installs pi, the forge + context packages, and the curated agent config.
3
+ // 1) Install/update pi (@earendil-works/pi-coding-agent)
4
+ // 2) Install pi packages (pi-context-usage, @baretread/pi-forge)
5
+ // 3) Install custom config (AGENTS.md, keybindings, extensions …)
6
+ // 4) Install per-extension npm deps via each extension's own installer
7
+ //
8
+ // Usage:
9
+ // npx @asterxsk/kiln --yes
10
+ // npm i -g @asterxsk/kiln && kiln --yes
11
+ // bunx @asterxsk/kiln --yes
12
+ // node bin/kiln.js --target /tmp/pi-test --skip-pi --skip-packages --yes
13
+ // node bin/kiln.js --overwrite-settings # replace settings.json (asks by default)
14
+ //
15
+ // Already-installed items are skipped; outdated global packages are updated.
16
+ // Only one timestamped backup (<target>.bak.*) is kept; the clone temp dir
17
+ // under ~/.pi/tmp/kiln-* is always removed.
18
+ //
19
+ // Safe to re-run. Managed files are force-overwritten; per-user files
20
+ // (settings.json, taste.md, auth.json, sessions, etc.) are preserved.
21
+ "use strict";
22
+
23
+ const { spawn, spawnSync } = require("node:child_process");
24
+ const fs = require("node:fs");
25
+ const os = require("node:os");
26
+ const path = require("node:path");
27
+ const crypto = require("node:crypto");
28
+
29
+ const PI_PACKAGE = "@earendil-works/pi-coding-agent";
30
+ const NODE_MIN = [22, 19, 0];
31
+ const DEFAULT_REPO = "https://github.com/asterxsk/kiln";
32
+ const DEFAULT_BRANCH = "main";
33
+ const PACKAGES = ["pi-context-usage@latest", "@baretread/pi-forge@latest"];
34
+ const SETTINGS_PACKAGES = ["npm:pi-context-usage", "npm:@baretread/pi-forge"];
35
+
36
+ const startTime = Date.now();
37
+ let clonedTmp = "";
38
+ let tmpLog = "";
39
+ let ok = true; // hard failures flip this; warnings don't
40
+
41
+ // npm >=11 rejects `npm_config_allow_scripts` inherited from a parent npm/npx
42
+ // process ("--allow-scripts is not allowed in project-scoped installs"). This
43
+ // happens when kiln itself runs under npx/npm exec and the user has
44
+ // `allow-scripts` in ~/.npmrc: npm forwards it as env to every child, so each
45
+ // per-extension `npm ci` fails with EALLOWSCRIPTS. Drop it; file-based config
46
+ // (~/.npmrc, per-package `allowScripts`) still applies to child npm processes.
47
+ for (const k of ["npm_config_allow_scripts", "NPM_CONFIG_ALLOW_SCRIPTS"]) delete process.env[k];
48
+
49
+ // ── summary buckets ─────────────────────────────────────────────────────
50
+ // Every completed item is recorded here; the final Summary prints them all.
51
+ const buckets = { skipped: [], updated: [], installed: [], "npm modules": [] };
52
+ const FRIENDLY = { "pi-context-usage": "context extension", "@baretread/pi-forge": "forge extension" };
53
+ function note(bucket, name) { buckets[bucket].push(name); }
54
+ function prettyPkg(spec) { return FRIENDLY[spec.replace(/^npm:/, "")] || spec; }
55
+
56
+ // ── args ────────────────────────────────────────────────────────────────
57
+ function parseArgs(argv) {
58
+ const o = {
59
+ repo: process.env.PI_CONFIG_REPO || "",
60
+ branch: process.env.PI_CONFIG_BRANCH || DEFAULT_BRANCH,
61
+ target: process.env.PI_AGENT_DIR || "",
62
+ skipPi: false, skipPackages: false, yes: false, local: false,
63
+ settingsMode: "ask", // ask | overwrite | keep
64
+ };
65
+ for (let i = 0; i < argv.length; i++) {
66
+ const a = argv[i];
67
+ if (a === "--repo") o.repo = argv[++i] ?? "";
68
+ else if (a === "--branch") o.branch = argv[++i] ?? "";
69
+ else if (a === "--target") o.target = argv[++i] ?? "";
70
+ else if (a === "--skip-pi") o.skipPi = true;
71
+ else if (a === "--skip-packages") o.skipPackages = true;
72
+ else if (a === "--local") o.local = true;
73
+ else if (a === "--overwrite-settings") o.settingsMode = "overwrite";
74
+ else if (a === "--keep-settings") o.settingsMode = "keep";
75
+ else if (a === "--yes" || a === "-y") o.yes = true;
76
+ else if (a === "--help" || a === "-h") {
77
+ console.log("Usage: kiln [--repo URL] [--branch BRANCH] [--target DIR] [--local] [--skip-pi] [--skip-packages] [--overwrite-settings|--keep-settings] [--yes]");
78
+ process.exit(0);
79
+ } else if (a === "--") break;
80
+ else if (a.startsWith("-")) { console.error(`unknown arg: ${a}`); process.exit(1); }
81
+ }
82
+ if (!o.repo) o.repo = DEFAULT_REPO;
83
+ return o;
84
+ }
85
+
86
+ // ── output ──────────────────────────────────────────────────────────────
87
+ const isTTY = !!process.stdout.isTTY && process.env.TERM !== "dumb";
88
+ const C = isTTY
89
+ ? { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", cyan: "\x1b[36m", gray: "\x1b[90m" }
90
+ : { reset: "", bold: "", dim: "", green: "", yellow: "", red: "", cyan: "", gray: "" };
91
+ const line = (s) => process.stdout.write(s + "\n");
92
+ const log = (s) => { try { fs.appendFileSync(tmpLog, s + "\n"); } catch {} };
93
+ // Mid-run output: plain lines when piped, swallowed (→ log + summary) when live.
94
+ function detail(s) { try { log(String(s).replace(/\x1b\[[0-9;]*m/g, "")); } catch {} if (!isTTY) line(s); }
95
+
96
+ // ── forge art + live status ─────────────────────────────────────────────
97
+ const ART = [
98
+ " █▌ █▌",
99
+ " ██████████████",
100
+ " ██ ██",
101
+ " ██ ██",
102
+ " ██ ██",
103
+ " ██ ██ ",
104
+ ];
105
+ // Phase 0 = ember (dim), phase 1 = stoked (bright). Non-TTY → plain art.
106
+ function artLines(phase) {
107
+ if (!isTTY) return ART.slice();
108
+ const m = phase ? 1 : 2; // bold vs dim
109
+ const cream = `\x1b[${m};38;5;230m`, orange = `\x1b[${m};38;5;208m`, brown = `\x1b[${m};38;5;94m`, R = "\x1b[0m";
110
+ return [
111
+ ` ${cream}█▌${R} ${cream}█▌${R}`,
112
+ ` ${orange}██████████████${R}`,
113
+ ` ${orange}██${R} ${orange}██${R}`,
114
+ ` ${orange}██${R} ${orange}██${R}`,
115
+ ` ${orange}██${R} ${orange}██${R}`,
116
+ ` ${brown}██ ██${R} `,
117
+ ];
118
+ }
119
+ const SPIN = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠰", "⠠", "⠦", "⠧", "⠇", "⠏"];
120
+ let liveLabel = "", liveTimer = null, liveTickN = 0;
121
+ function liveDraw() {
122
+ if (!isTTY) return;
123
+ const rows = [...artLines(liveTickN % 10 < 5 ? 0 : 1), ` ${C.yellow}${SPIN[liveTickN % SPIN.length]}${C.reset} ${C.dim}${liveLabel}${C.reset}`];
124
+ process.stdout.write("\x1b[7A");
125
+ for (const r of rows) process.stdout.write(`\r\x1b[K${r}\n`);
126
+ }
127
+ function liveStart() {
128
+ if (!isTTY) return;
129
+ process.stdout.write("\n".repeat(7));
130
+ liveTickN = 0;
131
+ liveDraw();
132
+ liveTimer = setInterval(() => { liveTickN++; liveDraw(); }, 100);
133
+ }
134
+ function liveSet(label) { liveLabel = label; }
135
+ function liveStop() {
136
+ if (!isTTY) return;
137
+ if (liveTimer) { clearInterval(liveTimer); liveTimer = null; }
138
+ liveTickN = 5; // stoked final frame
139
+ liveDraw();
140
+ }
141
+
142
+ function title() {
143
+ line("");
144
+ line(` ${C.bold}◆ Pi Setup${C.reset}`);
145
+ line(` ${C.dim}custom agent config · pi + forge + extensions${C.reset}`);
146
+ if (!isTTY) for (const r of artLines(0)) line(` ${r}`);
147
+ line("");
148
+ }
149
+
150
+ // Run a command: the live status line shows it on TTY, plain lines when piped. Output always → log.
151
+ function runStep(step, label, cmd, args, opts = {}) {
152
+ return new Promise((resolve) => {
153
+ liveSet(`[${step}] ${label}`);
154
+ if (!isTTY) line(` · ${label}`);
155
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"], shell: false, ...opts });
156
+ child.stdout.on("data", (d) => log(d.toString().trimEnd()));
157
+ child.stderr.on("data", (d) => log(d.toString().trimEnd()));
158
+ child.on("error", (e) => { log(`spawn failed: ${e.message}`); resolve(1); });
159
+ child.on("close", (code) => { resolve(code ?? 1); });
160
+ });
161
+ }
162
+
163
+ function have(cmd) {
164
+ try {
165
+ if (process.platform === "win32") return spawnSync("where", [cmd], { stdio: "ignore" }).status === 0;
166
+ return spawnSync("sh", ["-c", `command -v "${cmd}"`], { stdio: "ignore" }).status === 0;
167
+ } catch { return false; }
168
+ }
169
+ function cmdOut(cmd, args) {
170
+ try { return spawnSync(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).stdout.trim(); }
171
+ catch { return ""; }
172
+ }
173
+ function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
174
+ // Quote one shell word (cmd.exe + sh compatible for our controlled args).
175
+ const q = (a) => (/[\s"']/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a);
176
+ function cmdOutShell(command) {
177
+ try { return spawnSync(command, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], shell: true }).stdout.trim(); }
178
+ catch { return ""; }
179
+ }
180
+ // npm on Windows is npm.cmd (a batch shim) — it cannot be spawned directly,
181
+ // so run it through the shell as a single pre-quoted command string.
182
+ function npmStep(step, label, args) {
183
+ if (process.platform === "win32") return runStep(step, label, "npm.cmd " + args.map(q).join(" "), [], { shell: true });
184
+ return runStep(step, label, "npm", args);
185
+ }
186
+ // pi is also a cmd shim on Windows — same shell treatment.
187
+ function piOut(args) {
188
+ if (process.platform === "win32") return cmdOutShell("pi " + args.map(q).join(" "));
189
+ return cmdOut("pi", args);
190
+ }
191
+ function piStep(label, args) {
192
+ if (process.platform === "win32") return runStep("pkg", label, "pi " + args.map(q).join(" "), [], { shell: true });
193
+ return runStep("pkg", label, "pi", args);
194
+ }
195
+ const npmVer = process.platform === "win32" ? cmdOutShell("npm.cmd --version") : cmdOut("npm", ["--version"]);
196
+
197
+ // Installed global version of a package ("" when not installed).
198
+ function globalPkgVersion(name) {
199
+ const out = process.platform === "win32"
200
+ ? cmdOutShell(`npm.cmd ls -g "${name}" --depth=0`)
201
+ : cmdOut("npm", ["ls", "-g", name, "--depth=0"]);
202
+ const m = out.match(new RegExp(name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "@(\\S+)"));
203
+ return m ? m[1].replace(/[^0-9A-Za-z.+-].*$/, "") : "";
204
+ }
205
+ // Latest registry version ("" when offline / unknown).
206
+ function latestPkgVersion(name) {
207
+ const out = process.platform === "win32"
208
+ ? cmdOutShell(`npm.cmd view "${name}" version`)
209
+ : cmdOut("npm", ["view", name, "version"]);
210
+ const v = (out || "").split(/\s+/)[0].trim();
211
+ return /^[0-9][0-9A-Za-z.+-]*$/.test(v) ? v : "";
212
+ }
213
+ // Synchronous Y/N prompt (default N). False when non-interactive.
214
+ function askYN(question) {
215
+ try {
216
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
217
+ process.stdout.write(question);
218
+ const buf = Buffer.alloc(16);
219
+ const n = fs.readSync(0, buf, 0, 16);
220
+ const ans = buf.slice(0, n).toString().trim().toLowerCase();
221
+ return ans === "y" || ans === "yes";
222
+ } catch { return false; }
223
+ }
224
+ // Keep only the newest sibling backup (<target>.bak.*); delete the rest.
225
+ function pruneBackups(targetDir, keep) {
226
+ try {
227
+ const parent = path.dirname(targetDir);
228
+ const base = path.basename(targetDir);
229
+ const keepBase = keep ? path.basename(keep) : "";
230
+ for (const b of fs.readdirSync(parent)) {
231
+ if (!b.startsWith(base + ".bak.")) continue;
232
+ if (b === keepBase) continue;
233
+ try { fs.rmSync(path.join(parent, b), { recursive: true, force: true }); } catch {}
234
+ }
235
+ } catch (e) { log(`prune backups failed: ${e.message}`); }
236
+ }
237
+ // Remove stale clone dirs (~/.pi/tmp/kiln-*) except the active one.
238
+ function sweepStaleCloneTmp(tmpBase, active) {
239
+ try {
240
+ for (const e of fs.readdirSync(tmpBase)) {
241
+ if (!e.startsWith("kiln-")) continue;
242
+ if (active && e === path.basename(active)) continue;
243
+ try { fs.rmSync(path.join(tmpBase, e), { recursive: true, force: true }); } catch {}
244
+ }
245
+ } catch {}
246
+ }
247
+
248
+ // Recursive copy, excluding names (node_modules, .git). Returns false on error.
249
+ function copyTree(src, dst, exclude = new Set(["node_modules", ".git"])) {
250
+ try {
251
+ if (path.resolve(src) === path.resolve(dst)) return true;
252
+ fs.rmSync(dst, { recursive: true, force: true });
253
+ fs.mkdirSync(dst, { recursive: true });
254
+ for (const e of fs.readdirSync(src, { withFileTypes: true })) {
255
+ if (exclude.has(e.name)) continue;
256
+ const s = path.join(src, e.name), d = path.join(dst, e.name);
257
+ if (e.isDirectory()) { if (!copyTree(s, d, exclude)) return false; }
258
+ else if (e.isFile()) fs.copyFileSync(s, d);
259
+ }
260
+ return true;
261
+ } catch (e) { log(`copy failed ${src} → ${dst}: ${e.message}`); return false; }
262
+ }
263
+
264
+ function resolveTarget(opt) {
265
+ if (opt) return opt;
266
+ return path.join(os.homedir(), ".pi", "agent");
267
+ }
268
+
269
+ // Source root: bundled payload (npm tarball or dev checkout) → else git clone.
270
+ function resolveSourceRoot(args) {
271
+ const pkgRoot = path.resolve(__dirname, ".."); // bin/ → package root
272
+ const cands = [path.join(pkgRoot, "agent"), pkgRoot, process.cwd(), path.join(process.cwd(), "agent")];
273
+ for (const r of cands) {
274
+ if (fs.existsSync(path.join(r, "extensions")) && fs.existsSync(path.join(r, "AGENTS.md"))) return r;
275
+ }
276
+ if (args.local) throw new Error(`local checkout not found (tried: ${cands.join(", ")})`);
277
+ const base = path.join(os.homedir(), ".pi", "tmp");
278
+ fs.mkdirSync(base, { recursive: true });
279
+ const tmp = path.join(base, "kiln-" + crypto.randomBytes(4).toString("hex"));
280
+ fs.mkdirSync(tmp, { recursive: true });
281
+ liveSet(`cloning repo…`);
282
+ detail(` · cloning ${args.repo} (branch ${args.branch})`);
283
+ const r = spawnSync("git", ["clone", "--depth", "1", "--branch", args.branch, args.repo, tmp],
284
+ { stdio: ["ignore", "pipe", "pipe"], encoding: "utf8", env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never", GIT_ASKPASS: "echo" } });
285
+ if (r.stdout) log(r.stdout.trimEnd());
286
+ if (r.stderr) log(r.stderr.trimEnd());
287
+ if (r.status !== 0) throw new Error(`git clone failed (exit ${r.status}). See ${tmpLog}`);
288
+ clonedTmp = tmp;
289
+ if (fs.existsSync(path.join(tmp, "agent"))) return path.join(tmp, "agent");
290
+ return tmp;
291
+ }
292
+
293
+ async function main() {
294
+ const args = parseArgs(process.argv.slice(2));
295
+ const tmpBase = path.join(os.homedir(), ".pi", "tmp");
296
+ fs.mkdirSync(tmpBase, { recursive: true });
297
+ tmpLog = path.join(tmpBase, `pi-setup-${crypto.randomBytes(4).toString("hex")}.log`);
298
+ fs.writeFileSync(tmpLog, "");
299
+ process.on("exit", () => { if (clonedTmp && clonedTmp.startsWith(path.join(tmpBase, "kiln-"))) fs.rmSync(clonedTmp, { recursive: true, force: true }); });
300
+
301
+ title();
302
+ const targetDir = resolveTarget(args.target);
303
+ // Settings decision up front (only when interactive with no explicit flag).
304
+ if (args.settingsMode === "ask" && !args.yes && process.stdin.isTTY && process.stdout.isTTY
305
+ && fs.existsSync(path.join(targetDir, "settings.json"))) {
306
+ args.settingsMode = askYN(" Overwrite settings (y/n): ") ? "overwrite" : "keep";
307
+ } else if (args.settingsMode === "ask" && (args.yes || !process.stdin.isTTY)) {
308
+ args.settingsMode = "keep";
309
+ }
310
+ line(`${C.dim} target ${C.reset}${targetDir}`);
311
+ line(`${C.dim} repo ${C.reset}${args.repo} ${C.dim}(${args.branch})${C.reset}`);
312
+ line(`${C.dim} log ${C.reset}${tmpLog}\n`);
313
+
314
+ // ── preflight ──
315
+ const nodeVer = process.versions.node.split("-")[0];
316
+ const nv = nodeVer.split(".").map(Number);
317
+ const nodeOk = nv[0] > NODE_MIN[0] || (nv[0] === NODE_MIN[0] && (nv[1] > NODE_MIN[1] || (nv[1] === NODE_MIN[1] && nv[2] >= NODE_MIN[2])));
318
+ if (!nodeOk) {
319
+ line(`${C.yellow} ⚠ Node ${nodeVer} < ${NODE_MIN.join(".")} — please upgrade to Node ${NODE_MIN.join(".")}+${C.reset}`);
320
+ if (!args.yes) { line(" continue anyway in 3s…"); await sleep(3000); }
321
+ }
322
+ if (!have("npm")) { line(`${C.red} ✖ npm not found (Node installed but npm missing)${C.reset}`); process.exit(1); }
323
+ line(`${C.dim} node ${nodeVer} · npm ${npmVer} · ${cmdOut("git", ["--version"]) || "git n/a"}${C.reset}\n`);
324
+
325
+ // git is only needed when we must clone (no bundled payload and not --local)
326
+ const pkgAgent = path.join(path.resolve(__dirname, ".."), "agent");
327
+ const haveSource = fs.existsSync(path.join(pkgAgent, "extensions")) && fs.existsSync(path.join(pkgAgent, "AGENTS.md"));
328
+ if (!have("git") && !haveSource && !args.local) { line(`${C.red} ✖ git not found — required to fetch config${C.reset}`); process.exit(1); }
329
+ liveStart();
330
+
331
+ // ── [1/4] pi ──
332
+ if (args.skipPi) {
333
+ detail(` · Install pi — skipped`);
334
+ note("skipped", "pi");
335
+ } else if (have("pi")) {
336
+ const installed = globalPkgVersion(PI_PACKAGE) || cmdOut("pi", ["--version"]).replace(/^[vV]/, "").trim();
337
+ const latest = latestPkgVersion(PI_PACKAGE);
338
+ if (installed && latest && installed === latest) {
339
+ detail(` · pi ${installed} — already installed, skipping`);
340
+ note("skipped", "pi");
341
+ } else if (installed && !latest) {
342
+ detail(` · pi ${installed} — already installed, skipping version check (offline)`);
343
+ note("skipped", "pi");
344
+ } else {
345
+ const label = installed ? `updating pi ${installed} → ${latest || "latest"}` : `installing pi`;
346
+ const code = await npmStep("1/4", label, ["install", "-g", `${PI_PACKAGE}@latest`, "--no-audit", "--no-fund", "--min-release-age=0"]);
347
+ if (code === 0) {
348
+ detail(` · ${label} — ${cmdOut("pi", ["--version"]) || "?"}`);
349
+ note(installed ? "updated" : "installed", "pi");
350
+ } else {
351
+ detail(` ✖ ${label} failed (exit ${code}) — see log`);
352
+ note("updated", "pi (failed — see log)");
353
+ ok = false;
354
+ }
355
+ }
356
+ } else {
357
+ const label = `installing pi`;
358
+ const code = await npmStep("1/4", label, ["install", "-g", `${PI_PACKAGE}@latest`, "--no-audit", "--no-fund", "--min-release-age=0"]);
359
+ if (code === 0) {
360
+ detail(` · ${label} — ${cmdOut("pi", ["--version"]) || "?"}`);
361
+ note("installed", "pi");
362
+ } else {
363
+ detail(` ✖ ${label} failed (exit ${code}) — see log`);
364
+ note("installed", "pi (failed — see log)");
365
+ ok = false;
366
+ }
367
+ }
368
+ // ── [2/4] pi packages (global best-effort; pi-side ensure runs after step 3) ──
369
+ if (args.skipPackages) {
370
+ detail(` · pi packages — skipped`);
371
+ } else {
372
+ const bare = PACKAGES.map((p) => p.replace(/@latest$/, ""));
373
+ const need = [];
374
+ for (const name of bare) {
375
+ const inst = globalPkgVersion(name);
376
+ const latest = latestPkgVersion(name);
377
+ if (inst && latest && inst === latest) {
378
+ detail(` · ${name}@${inst} already installed globally, skipping`);
379
+ } else if (inst && !latest) {
380
+ detail(` · ${name}@${inst} already installed globally, skipping version check (offline)`);
381
+ } else {
382
+ if (inst) detail(` · ${name} ${inst} → ${latest || "latest"} — will update globally`);
383
+ need.push(`${name}@latest`);
384
+ }
385
+ }
386
+ if (!need.length) {
387
+ detail(` · pi packages — already installed globally, skipping`);
388
+ } else {
389
+ const label = `installing ${need.join(" ")} (-g)`;
390
+ const code = await npmStep("2/4", label, ["install", "-g", ...need, "--no-audit", "--no-fund"]);
391
+ if (code === 0) detail(` · pi packages — global install done`);
392
+ else detail(` ⚠ ${label} exit ${code} — continuing (pi-side ensure runs later)`);
393
+ }
394
+ }
395
+ // ── [3/4] custom config ──
396
+ try {
397
+ const sourceRoot = resolveSourceRoot(args);
398
+ detail(` · source ${sourceRoot}`);
399
+ fs.mkdirSync(targetDir, { recursive: true });
400
+ let bak = "";
401
+ if (fs.existsSync(path.join(targetDir, "AGENTS.md")) || fs.existsSync(path.join(targetDir, "extensions"))) {
402
+ const d = new Date(), p2 = (n) => String(n).padStart(2, "0");
403
+ bak = `${targetDir}.bak.${d.getFullYear()}${p2(d.getMonth() + 1)}${p2(d.getDate())}-${p2(d.getHours())}${p2(d.getMinutes())}${p2(d.getSeconds())}`;
404
+ detail(` · backup ${targetDir}/extensions → ${bak}`);
405
+ fs.mkdirSync(bak, { recursive: true });
406
+ for (const f of ["AGENTS.md", "keybindings.json", "README.md"]) {
407
+ const s = path.join(targetDir, f);
408
+ if (fs.existsSync(s)) try { fs.copyFileSync(s, path.join(bak, f)); } catch {}
409
+ }
410
+ const extSrc = path.join(targetDir, "extensions");
411
+ if (fs.existsSync(extSrc)) {
412
+ fs.mkdirSync(path.join(bak, "extensions"), { recursive: true });
413
+ for (const e of fs.readdirSync(extSrc, { withFileTypes: true })) {
414
+ if (!e.isDirectory()) continue;
415
+ copyTree(path.join(extSrc, e.name), path.join(bak, "extensions", e.name));
416
+ }
417
+ }
418
+ pruneBackups(targetDir, bak); // only one backup at a time
419
+ }
420
+
421
+ let copied = 0;
422
+ const selfInstall = path.resolve(sourceRoot) === path.resolve(targetDir);
423
+ if (selfInstall) detail(` · source == target — skipping file copy (self-install)`);
424
+ for (const f of ["AGENTS.md", "keybindings.json", "README.md"]) {
425
+ if (!fs.existsSync(path.join(sourceRoot, f))) continue;
426
+ if (!selfInstall) fs.copyFileSync(path.join(sourceRoot, f), path.join(targetDir, f));
427
+ copied++;
428
+ }
429
+ let freshSettings = false;
430
+ const settingsPath = path.join(targetDir, "settings.json");
431
+ const repoSettings = path.join(sourceRoot, "settings.json");
432
+ if (!fs.existsSync(settingsPath) && fs.existsSync(repoSettings)) {
433
+ fs.copyFileSync(repoSettings, settingsPath);
434
+ detail(` · created settings.json from repo defaults`);
435
+ note("installed", "settings.json");
436
+ freshSettings = true;
437
+ } else if (fs.existsSync(settingsPath)) {
438
+ const mode = args.settingsMode === "ask" ? "keep" : args.settingsMode; // decided up front
439
+ if (mode === "overwrite" && fs.existsSync(repoSettings)) {
440
+ if (bak) { try { fs.copyFileSync(settingsPath, path.join(bak, "settings.json")); } catch {} }
441
+ fs.copyFileSync(repoSettings, settingsPath);
442
+ detail(` · overwrote settings.json from repo defaults`);
443
+ note("updated", "settings.json");
444
+ freshSettings = true;
445
+ } else {
446
+ detail(` · kept existing settings.json`);
447
+ note("skipped", "settings.json");
448
+ }
449
+ }
450
+ if (freshSettings) {
451
+ try {
452
+ const p = path.join(targetDir, "settings.json");
453
+ const j = JSON.parse(fs.readFileSync(p, "utf8"));
454
+ j.packages = j.packages || [];
455
+ let changed = false;
456
+ for (const pkg of SETTINGS_PACKAGES) if (!j.packages.includes(pkg)) { j.packages.push(pkg); changed = true; }
457
+ if (changed) { fs.writeFileSync(p, JSON.stringify(j, null, 2) + "\n"); detail(" · patched settings.json packages → forge + context"); }
458
+ } catch {}
459
+ }
460
+ for (const t of ["taste.md", "taste", "taste.json"]) {
461
+ const tp = path.join(targetDir, t);
462
+ if (fs.existsSync(tp)) detail(` · kept existing ${t}`);
463
+ else if (fs.existsSync(path.join(sourceRoot, t))) {
464
+ const s = path.join(sourceRoot, t);
465
+ if (fs.statSync(s).isDirectory()) copyTree(s, tp, new Set());
466
+ else fs.copyFileSync(s, tp);
467
+ }
468
+ }
469
+
470
+ if (fs.existsSync(path.join(sourceRoot, "extensions"))) {
471
+ fs.mkdirSync(path.join(targetDir, "extensions"), { recursive: true });
472
+ for (const e of fs.readdirSync(path.join(sourceRoot, "extensions"), { withFileTypes: true })) {
473
+ if (!e.isDirectory() || e.name.endsWith(".md")) continue;
474
+ const dst = path.join(targetDir, "extensions", e.name);
475
+ const existed = fs.existsSync(dst);
476
+ if (copyTree(path.join(sourceRoot, "extensions", e.name), dst)) { copied++; note(existed ? "updated" : "installed", e.name); }
477
+ else detail(` ⚠ copy extensions/${e.name} failed`);
478
+ }
479
+ }
480
+
481
+ if (!selfInstall && fs.existsSync(path.join(sourceRoot, "themes"))) {
482
+ fs.rmSync(path.join(targetDir, "themes"), { recursive: true, force: true });
483
+ fs.mkdirSync(path.join(targetDir, "themes"), { recursive: true });
484
+ for (const e of fs.readdirSync(path.join(sourceRoot, "themes"), { withFileTypes: true })) {
485
+ if (e.name === ".pi" || e.name === "nul") continue;
486
+ const s = path.join(sourceRoot, "themes", e.name), dd = path.join(targetDir, "themes", e.name);
487
+ if (e.isDirectory()) copyTree(s, dd, new Set());
488
+ else if (e.isFile()) try { fs.copyFileSync(s, dd); } catch {}
489
+ }
490
+ }
491
+
492
+ detail(` · custom config — ${copied} items → ${targetDir}`);
493
+ if (clonedTmp) { fs.rmSync(clonedTmp, { recursive: true, force: true }); clonedTmp = ""; }
494
+ sweepStaleCloneTmp(tmpBase, ""); // delete any leftover kiln-* temp dirs
495
+ const repoReadme = path.resolve(sourceRoot, "..", "README.md");
496
+ const parentReadme = path.resolve(targetDir, "..", "README.md");
497
+ if (repoReadme !== parentReadme && fs.existsSync(repoReadme)) {
498
+ try { fs.copyFileSync(repoReadme, parentReadme); } catch {}
499
+ }
500
+ } catch (e) {
501
+ detail(` ✖ custom config failed: ${e.message}`);
502
+ ok = false;
503
+ }
504
+ // ── [4/4] per-extension deps ──
505
+ const extRoot = path.join(targetDir, "extensions");
506
+ if (!fs.existsSync(extRoot)) {
507
+ detail(` ⚠ no extensions found at ${extRoot}`);
508
+ } else {
509
+ const failed = [];
510
+ let installed = 0, skipped = 0;
511
+ for (const e of fs.readdirSync(extRoot, { withFileTypes: true })) {
512
+ if (!e.isDirectory() || e.name.endsWith(".md")) continue;
513
+ const dir = path.join(extRoot, e.name), label = `extensions/${e.name}`;
514
+ if (fs.existsSync(path.join(dir, "node_modules"))) {
515
+ detail(` · ${label} — already installed, skipping`);
516
+ note("skipped", e.name);
517
+ skipped++;
518
+ continue;
519
+ }
520
+ const hasPkg = fs.existsSync(path.join(dir, "package.json"));
521
+ const sh = path.join(dir, "install.sh"), ps1 = path.join(dir, "install.ps1");
522
+ let code;
523
+ if (process.platform === "win32" && fs.existsSync(ps1)) {
524
+ const shell = have("pwsh") ? "pwsh" : "powershell";
525
+ code = await runStep("4/4", label, shell, ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", ps1]);
526
+ } else if (process.platform !== "win32" && fs.existsSync(sh)) {
527
+ code = await runStep("4/4", label, "sh", [sh]);
528
+ } else if (hasPkg) {
529
+ const a = ["--prefix", dir, fs.existsSync(path.join(dir, "package-lock.json")) ? "ci" : "install", "--no-audit", "--no-fund"];
530
+ code = await npmStep("4/4", label, a);
531
+ } else {
532
+ detail(` · ${label} — no package.json, skipping`);
533
+ continue;
534
+ }
535
+ if (code === 0) { detail(` · ${label} — done`); if (hasPkg) note("npm modules", e.name); installed++; }
536
+ else { detail(` ✖ ${label} failed (exit ${code}) — see log`); note("npm modules", `${e.name} (failed — see log)`); failed.push(e.name); }
537
+ }
538
+ if (!failed.length) detail(` · extension deps — ${installed} installed, ${skipped} skipped`);
539
+ else detail(` ⚠ extension deps ${failed.length} failed: ${failed.join(", ")} — see log`);
540
+ }
541
+
542
+ // ── pi package ensure (forge + context actually registered with pi) ──
543
+ // `pi list` is truth: global installs mean nothing to pi (it uses its own dir).
544
+ const defaultDir = path.join(os.homedir(), ".pi", "agent");
545
+ if (args.skipPackages) {
546
+ for (const s of SETTINGS_PACKAGES) note("skipped", prettyPkg(s));
547
+ } else if (!have("pi")) {
548
+ detail(` ⚠ pi binary not found — packages registered for next launch`);
549
+ for (const s of SETTINGS_PACKAGES) note("skipped", prettyPkg(s));
550
+ } else if (path.resolve(targetDir) !== path.resolve(defaultDir)) {
551
+ detail(` · custom target — packages registered for next launch`);
552
+ for (const s of SETTINGS_PACKAGES) note("skipped", prettyPkg(s));
553
+ } else {
554
+ let list = "";
555
+ try { list = piOut(["list"]); } catch { list = ""; }
556
+ for (const spec of SETTINGS_PACKAGES) {
557
+ const pretty = prettyPkg(spec);
558
+ if (list.includes(spec)) { note("skipped", pretty); continue; }
559
+ const code = await piStep(`pi install ${spec}`, ["install", spec]);
560
+ let now = "";
561
+ try { now = piOut(["list"]); } catch { now = ""; }
562
+ if (code === 0 && now.includes(spec)) { detail(` · ${pretty} — registered with pi`); note("installed", pretty); }
563
+ else { detail(` ✖ ${pretty} not registered — see log (pi retries on next launch)`); note("updated", `${pretty} (failed — see log)`); }
564
+ }
565
+ }
566
+
567
+ // ── summary ──
568
+ liveStop();
569
+ const secs = Math.round((Date.now() - startTime) / 1000);
570
+ const elapsed = secs >= 60 ? `${Math.floor(secs / 60)}m ${secs % 60}s` : `${secs}s`;
571
+ line("");
572
+ if (ok) line(`${C.green}${C.bold} ✓ Setup complete${C.reset} ${C.dim}→ ${targetDir}${C.reset}`);
573
+ else line(`${C.yellow}${C.bold} ⚠ Setup finished with warnings${C.reset} ${C.dim}see ${tmpLog}${C.reset}`);
574
+ line("");
575
+ line("Summary:");
576
+ line("");
577
+ for (const name of ["skipped", "updated", "installed", "npm modules"]) {
578
+ line(`[${name}]`);
579
+ const items = buckets[name];
580
+ if (!items.length) line("(none)");
581
+ else for (const item of items) line(item);
582
+ line("");
583
+ }
584
+ line(`Done in ${elapsed}`);
585
+ line(`${C.dim} log: ${tmpLog}${C.reset}\n`);
586
+ process.exitCode = ok ? 0 : 1;
587
+ }
588
+
359
589
  main().catch((e) => { console.error(`\n ✖ ${e.message}`); process.exit(1); });