@asterxsk/kiln 0.1.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 (263) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/agent/AGENTS.md +67 -0
  4. package/agent/README.md +5 -0
  5. package/agent/extensions/AGENTS.md +68 -0
  6. package/agent/extensions/ask-user/index.ts +418 -0
  7. package/agent/extensions/ask-user/install.ps1 +23 -0
  8. package/agent/extensions/ask-user/install.sh +21 -0
  9. package/agent/extensions/ask-user/package-lock.json +769 -0
  10. package/agent/extensions/ask-user/package.json +19 -0
  11. package/agent/extensions/ask-user/prompt.ts +45 -0
  12. package/agent/extensions/ask-user/tsconfig.json +7 -0
  13. package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -0
  14. package/agent/extensions/background-terminals/index.ts +627 -0
  15. package/agent/extensions/background-terminals/install.ps1 +23 -0
  16. package/agent/extensions/background-terminals/install.sh +21 -0
  17. package/agent/extensions/background-terminals/manager.test.ts +735 -0
  18. package/agent/extensions/background-terminals/output.test.ts +109 -0
  19. package/agent/extensions/background-terminals/package-lock.json +769 -0
  20. package/agent/extensions/background-terminals/package.json +17 -0
  21. package/agent/extensions/background-terminals/prompt.test.ts +125 -0
  22. package/agent/extensions/background-terminals/ps.test.ts +82 -0
  23. package/agent/extensions/background-terminals/result-delivery.test.ts +44 -0
  24. package/agent/extensions/background-terminals/src/domain.ts +87 -0
  25. package/agent/extensions/background-terminals/src/manager.ts +907 -0
  26. package/agent/extensions/background-terminals/src/output.ts +84 -0
  27. package/agent/extensions/background-terminals/src/prompt.ts +142 -0
  28. package/agent/extensions/background-terminals/src/result-delivery.ts +27 -0
  29. package/agent/extensions/background-terminals/src/runtime.ts +36 -0
  30. package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -0
  31. package/agent/extensions/background-terminals/src/ui/ps.ts +621 -0
  32. package/agent/extensions/background-terminals/tsconfig.json +7 -0
  33. package/agent/extensions/destructive/README.md +31 -0
  34. package/agent/extensions/destructive/index.ts +88 -0
  35. package/agent/extensions/destructive/install.ps1 +23 -0
  36. package/agent/extensions/destructive/install.sh +21 -0
  37. package/agent/extensions/file-search/index.spec.ts +443 -0
  38. package/agent/extensions/file-search/index.ts +459 -0
  39. package/agent/extensions/file-search/install.ps1 +23 -0
  40. package/agent/extensions/file-search/install.sh +21 -0
  41. package/agent/extensions/file-search/package-lock.json +2253 -0
  42. package/agent/extensions/file-search/package.json +23 -0
  43. package/agent/extensions/file-search/src/args.ts +122 -0
  44. package/agent/extensions/file-search/src/binaries.ts +422 -0
  45. package/agent/extensions/file-search/src/output.ts +126 -0
  46. package/agent/extensions/file-search/src/process.ts +146 -0
  47. package/agent/extensions/file-search/src/prompt.ts +52 -0
  48. package/agent/extensions/file-search/tsconfig.json +7 -0
  49. package/agent/extensions/goal/README.md +50 -0
  50. package/agent/extensions/goal/index.ts +155 -0
  51. package/agent/extensions/goal/install.ps1 +23 -0
  52. package/agent/extensions/goal/install.sh +21 -0
  53. package/agent/extensions/modelconf/PLAN.md +915 -0
  54. package/agent/extensions/modelconf/README.md +66 -0
  55. package/agent/extensions/modelconf/index.ts +296 -0
  56. package/agent/extensions/modelconf/install.ps1 +23 -0
  57. package/agent/extensions/modelconf/install.sh +21 -0
  58. package/agent/extensions/modelconf/package-lock.json +1809 -0
  59. package/agent/extensions/modelconf/package.json +13 -0
  60. package/agent/extensions/modelconf/src/fuzzy.ts +26 -0
  61. package/agent/extensions/modelconf/src/glob.ts +13 -0
  62. package/agent/extensions/modelconf/src/persistence.test.ts +46 -0
  63. package/agent/extensions/modelconf/src/persistence.ts +164 -0
  64. package/agent/extensions/modelconf/src/ui/ModelConfView.test.ts +75 -0
  65. package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -0
  66. package/agent/extensions/modelconf/tsconfig.json +15 -0
  67. package/agent/extensions/pi-web-access/CHANGELOG.md +690 -0
  68. package/agent/extensions/pi-web-access/LICENSE +21 -0
  69. package/agent/extensions/pi-web-access/README.md +470 -0
  70. package/agent/extensions/pi-web-access/SECURITY.md +5 -0
  71. package/agent/extensions/pi-web-access/activity.ts +101 -0
  72. package/agent/extensions/pi-web-access/auth-fetch.ts +148 -0
  73. package/agent/extensions/pi-web-access/banner.png +0 -0
  74. package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -0
  75. package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -0
  76. package/agent/extensions/pi-web-access/content-find.ts +139 -0
  77. package/agent/extensions/pi-web-access/credential-source.ts +191 -0
  78. package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -0
  79. package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -0
  80. package/agent/extensions/pi-web-access/declared-web-links.ts +173 -0
  81. package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -0
  82. package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -0
  83. package/agent/extensions/pi-web-access/exa.ts +526 -0
  84. package/agent/extensions/pi-web-access/extract.ts +1196 -0
  85. package/agent/extensions/pi-web-access/feature-config.ts +29 -0
  86. package/agent/extensions/pi-web-access/fetch-params.ts +111 -0
  87. package/agent/extensions/pi-web-access/gemini-adc.ts +298 -0
  88. package/agent/extensions/pi-web-access/gemini-api.ts +353 -0
  89. package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -0
  90. package/agent/extensions/pi-web-access/gemini-search.ts +21 -0
  91. package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -0
  92. package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -0
  93. package/agent/extensions/pi-web-access/gemini-web.ts +487 -0
  94. package/agent/extensions/pi-web-access/github-api.ts +197 -0
  95. package/agent/extensions/pi-web-access/github-extract.ts +746 -0
  96. package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -0
  97. package/agent/extensions/pi-web-access/index.ts +1737 -0
  98. package/agent/extensions/pi-web-access/package-lock.json +5808 -0
  99. package/agent/extensions/pi-web-access/package.json +64 -0
  100. package/agent/extensions/pi-web-access/page-query.ts +96 -0
  101. package/agent/extensions/pi-web-access/pdf-extract.ts +409 -0
  102. package/agent/extensions/pi-web-access/pi-web-fetch-demo.mp4 +0 -0
  103. package/agent/extensions/pi-web-access/promise-try.d.ts +7 -0
  104. package/agent/extensions/pi-web-access/query-rewrite.ts +51 -0
  105. package/agent/extensions/pi-web-access/render-search-error.ts +170 -0
  106. package/agent/extensions/pi-web-access/rsc-extract.ts +338 -0
  107. package/agent/extensions/pi-web-access/search-types.ts +20 -0
  108. package/agent/extensions/pi-web-access/source-check.ts +282 -0
  109. package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -0
  110. package/agent/extensions/pi-web-access/storage.ts +521 -0
  111. package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -0
  112. package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -0
  113. package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -0
  114. package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -0
  115. package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -0
  116. package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -0
  117. package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -0
  118. package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -0
  119. package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -0
  120. package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -0
  121. package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -0
  122. package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -0
  123. package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -0
  124. package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -0
  125. package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -0
  126. package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -0
  127. package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -0
  128. package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -0
  129. package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -0
  130. package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -0
  131. package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -0
  132. package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -0
  133. package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -0
  134. package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -0
  135. package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -0
  136. package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -0
  137. package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -0
  138. package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -0
  139. package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -0
  140. package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -0
  141. package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -0
  142. package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -0
  143. package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -0
  144. package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -0
  145. package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -0
  146. package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -0
  147. package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -0
  148. package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -0
  149. package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -0
  150. package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -0
  151. package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -0
  152. package/agent/extensions/pi-web-access/test/summary-model-scope.test.mjs +106 -0
  153. package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -0
  154. package/agent/extensions/pi-web-access/test/web-search-answer-render.test.mjs +66 -0
  155. package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -0
  156. package/agent/extensions/pi-web-access/tsconfig.json +11 -0
  157. package/agent/extensions/pi-web-access/utils.ts +451 -0
  158. package/agent/extensions/pi-web-access/video-extract.ts +392 -0
  159. package/agent/extensions/pi-web-access/youtube-extract.ts +328 -0
  160. package/agent/extensions/shared/activity-status.ts +31 -0
  161. package/agent/extensions/shared/child-session.test.ts +270 -0
  162. package/agent/extensions/shared/child-session.ts +148 -0
  163. package/agent/extensions/shared/context-utilization.test.ts +48 -0
  164. package/agent/extensions/shared/context-utilization.ts +47 -0
  165. package/agent/extensions/shared/dashboard-state.ts +99 -0
  166. package/agent/extensions/shared/install.ps1 +23 -0
  167. package/agent/extensions/shared/install.sh +21 -0
  168. package/agent/extensions/shared/tool-call-timeout.test.ts +117 -0
  169. package/agent/extensions/shared/tool-call-timeout.ts +104 -0
  170. package/agent/extensions/skillsconf/README.md +74 -0
  171. package/agent/extensions/skillsconf/index.ts +92 -0
  172. package/agent/extensions/skillsconf/install.ps1 +23 -0
  173. package/agent/extensions/skillsconf/install.sh +21 -0
  174. package/agent/extensions/skillsconf/package-lock.json +1809 -0
  175. package/agent/extensions/skillsconf/package.json +18 -0
  176. package/agent/extensions/skillsconf/src/delete-skill.test.ts +64 -0
  177. package/agent/extensions/skillsconf/src/delete-skill.ts +45 -0
  178. package/agent/extensions/skillsconf/src/filter.test.ts +80 -0
  179. package/agent/extensions/skillsconf/src/filter.ts +74 -0
  180. package/agent/extensions/skillsconf/src/fuzzy.ts +26 -0
  181. package/agent/extensions/skillsconf/src/persistence.test.ts +72 -0
  182. package/agent/extensions/skillsconf/src/persistence.ts +169 -0
  183. package/agent/extensions/skillsconf/src/ui/SkillConfView.test.ts +337 -0
  184. package/agent/extensions/skillsconf/src/ui/SkillConfView.ts +758 -0
  185. package/agent/extensions/skillsconf/src/ui/text-input.ts +91 -0
  186. package/agent/extensions/skillsconf/src/ui/tui-helpers.ts +144 -0
  187. package/agent/extensions/skillsconf/tsconfig.json +15 -0
  188. package/agent/extensions/status line/index.ts +282 -0
  189. package/agent/extensions/status line/install.ps1 +23 -0
  190. package/agent/extensions/status line/install.sh +21 -0
  191. package/agent/extensions/subagents/by-the-way.test.ts +29 -0
  192. package/agent/extensions/subagents/claude.test.ts +119 -0
  193. package/agent/extensions/subagents/codex.test.ts +102 -0
  194. package/agent/extensions/subagents/context-usage.test.ts +107 -0
  195. package/agent/extensions/subagents/docs/design-plan.md +568 -0
  196. package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -0
  197. package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -0
  198. package/agent/extensions/subagents/index.ts +779 -0
  199. package/agent/extensions/subagents/install.ps1 +23 -0
  200. package/agent/extensions/subagents/install.sh +21 -0
  201. package/agent/extensions/subagents/manager.test.ts +276 -0
  202. package/agent/extensions/subagents/package-lock.json +2244 -0
  203. package/agent/extensions/subagents/package.json +19 -0
  204. package/agent/extensions/subagents/result-delivery.test.ts +27 -0
  205. package/agent/extensions/subagents/src/backend.ts +73 -0
  206. package/agent/extensions/subagents/src/backends/claude.ts +701 -0
  207. package/agent/extensions/subagents/src/backends/codex.ts +1060 -0
  208. package/agent/extensions/subagents/src/backends/pi.ts +575 -0
  209. package/agent/extensions/subagents/src/backends/stub.ts +300 -0
  210. package/agent/extensions/subagents/src/by-the-way.ts +21 -0
  211. package/agent/extensions/subagents/src/domain.ts +253 -0
  212. package/agent/extensions/subagents/src/format.ts +74 -0
  213. package/agent/extensions/subagents/src/manager.ts +736 -0
  214. package/agent/extensions/subagents/src/prompt.ts +92 -0
  215. package/agent/extensions/subagents/src/result-delivery.ts +20 -0
  216. package/agent/extensions/subagents/src/runtime.ts +53 -0
  217. package/agent/extensions/subagents/src/ui/takeover.ts +583 -0
  218. package/agent/extensions/subagents/src/ui/transcript.ts +201 -0
  219. package/agent/extensions/subagents/takeover.test.ts +29 -0
  220. package/agent/extensions/subagents/tsconfig.json +7 -0
  221. package/agent/extensions/taste/index.ts +443 -0
  222. package/agent/extensions/taste/install.ps1 +23 -0
  223. package/agent/extensions/taste/install.sh +21 -0
  224. package/agent/extensions/todo/AGENTS.md +38 -0
  225. package/agent/extensions/todo/LICENSE +21 -0
  226. package/agent/extensions/todo/config.ts +55 -0
  227. package/agent/extensions/todo/index.ts +151 -0
  228. package/agent/extensions/todo/install.ps1 +23 -0
  229. package/agent/extensions/todo/install.sh +21 -0
  230. package/agent/extensions/todo/locales/de.json +17 -0
  231. package/agent/extensions/todo/locales/en.json +15 -0
  232. package/agent/extensions/todo/locales/es.json +17 -0
  233. package/agent/extensions/todo/locales/fr.json +17 -0
  234. package/agent/extensions/todo/locales/pt-BR.json +17 -0
  235. package/agent/extensions/todo/locales/pt.json +17 -0
  236. package/agent/extensions/todo/locales/ru.json +17 -0
  237. package/agent/extensions/todo/locales/uk.json +17 -0
  238. package/agent/extensions/todo/locales/zh.json +17 -0
  239. package/agent/extensions/todo/package-lock.json +3358 -0
  240. package/agent/extensions/todo/package.json +67 -0
  241. package/agent/extensions/todo/state/i18n-bridge.ts +64 -0
  242. package/agent/extensions/todo/state/invariants.ts +20 -0
  243. package/agent/extensions/todo/state/replay.ts +38 -0
  244. package/agent/extensions/todo/state/selectors.ts +107 -0
  245. package/agent/extensions/todo/state/state-reducer.ts +326 -0
  246. package/agent/extensions/todo/state/state.ts +18 -0
  247. package/agent/extensions/todo/state/store.ts +82 -0
  248. package/agent/extensions/todo/state/task-graph.ts +57 -0
  249. package/agent/extensions/todo/todo-overlay.ts +200 -0
  250. package/agent/extensions/todo/todo.ts +155 -0
  251. package/agent/extensions/todo/tool/response-envelope.ts +109 -0
  252. package/agent/extensions/todo/tool/types.ts +206 -0
  253. package/agent/extensions/todo/verify-ref-system.js +0 -0
  254. package/agent/extensions/todo/view/format.ts +177 -0
  255. package/agent/extensions/trim-context/README.md +54 -0
  256. package/agent/extensions/trim-context/index.ts +487 -0
  257. package/agent/extensions/trim-context/install.ps1 +23 -0
  258. package/agent/extensions/trim-context/install.sh +21 -0
  259. package/agent/install.ps1 +527 -0
  260. package/agent/install.sh +511 -0
  261. package/agent/keybindings.json +7 -0
  262. package/bin/kiln.js +359 -0
  263. package/package.json +21 -0
package/bin/kiln.js ADDED
@@ -0,0 +1,359 @@
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
+
359
+ main().catch((e) => { console.error(`\n ✖ ${e.message}`); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@asterxsk/kiln",
3
+ "version": "0.1.0",
4
+ "description": "Kiln — versioned pi agent configuration (extensions, defaults, keybindings) with an idempotent cross-platform installer.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/asterxsk/kiln.git"
9
+ },
10
+ "homepage": "https://github.com/asterxsk/kiln#readme",
11
+ "keywords": ["pi", "agent", "coding-agent", "installer", "config"],
12
+ "engines": {
13
+ "node": ">=22.19.0"
14
+ },
15
+ "bin": {
16
+ "kiln": "./bin/kiln.js"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ }
21
+ }