quotacap 0.0.22 → 0.0.24

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 (298) hide show
  1. package/README.md +30 -9
  2. package/dist/adapters/grok.js +2 -2
  3. package/dist/adapters/index.js +3 -0
  4. package/dist/adapters/muse.d.ts +8 -0
  5. package/dist/adapters/muse.js +96 -0
  6. package/dist/adapters/pty.d.ts +53 -0
  7. package/dist/adapters/pty.js +402 -74
  8. package/dist/advisory/provider-names.d.ts +14 -0
  9. package/dist/advisory/provider-names.js +98 -0
  10. package/dist/advisory/snapshot.d.ts +7 -0
  11. package/dist/advisory/snapshot.js +7 -0
  12. package/dist/advisory/types.d.ts +12 -0
  13. package/dist/advisory/validation.d.ts +9 -0
  14. package/dist/advisory/validation.js +39 -0
  15. package/dist/cli/advise.js +74 -7
  16. package/dist/cli/clients.d.ts +10 -0
  17. package/dist/cli/clients.js +2 -0
  18. package/dist/cli/index.js +13 -8
  19. package/dist/cli/init.d.ts +2 -0
  20. package/dist/cli/init.js +29 -0
  21. package/dist/cli/providers.d.ts +3 -0
  22. package/dist/cli/providers.js +174 -0
  23. package/dist/cli/runtime.d.ts +21 -0
  24. package/dist/cli/runtime.js +169 -42
  25. package/dist/cli/snapshot-source.d.ts +4 -1
  26. package/dist/cli/snapshot-source.js +52 -4
  27. package/dist/cli/status.js +78 -4
  28. package/dist/cli/takeover.d.ts +64 -0
  29. package/dist/cli/takeover.js +175 -0
  30. package/dist/cli/update.d.ts +61 -0
  31. package/dist/cli/update.js +392 -0
  32. package/dist/config.d.ts +18 -0
  33. package/dist/config.js +106 -1
  34. package/dist/diagnostics/failure.d.ts +26 -0
  35. package/dist/diagnostics/failure.js +417 -0
  36. package/dist/format/issues.d.ts +2 -0
  37. package/dist/format/issues.js +17 -0
  38. package/dist/format/markdown.js +1 -1
  39. package/dist/format/rows.js +2 -0
  40. package/dist/format/terminal.js +6 -6
  41. package/dist/http/server.d.ts +10 -0
  42. package/dist/http/server.js +115 -2
  43. package/dist/mcp/server.js +2 -0
  44. package/dist/runtime/client.d.ts +1 -0
  45. package/dist/runtime/client.js +21 -1
  46. package/dist/runtime/poll.d.ts +14 -4
  47. package/dist/runtime/poll.js +56 -39
  48. package/dist/runtime/service.js +26 -3
  49. package/dist/runtime/spawn.js +14 -4
  50. package/dist/runtime/updates.d.ts +74 -0
  51. package/dist/runtime/updates.js +445 -0
  52. package/dist/runtime/versions.d.ts +2 -0
  53. package/dist/runtime/versions.js +26 -0
  54. package/dist/service/index.d.ts +7 -2
  55. package/dist/service/index.js +67 -11
  56. package/dist/service/macos.d.ts +4 -0
  57. package/dist/service/macos.js +62 -11
  58. package/dist/service/systemd.d.ts +40 -0
  59. package/dist/service/systemd.js +387 -0
  60. package/dist/store/attempts.d.ts +6 -0
  61. package/dist/store/attempts.js +27 -20
  62. package/dist/store/db.js +6 -1
  63. package/dist/version.js +1 -1
  64. package/dist/web/src/App.js +1 -1
  65. package/dist/web/src/api.d.ts +2 -0
  66. package/dist/web/src/api.js +20 -0
  67. package/dist/web/src/components/AdviceDrawer.js +3 -4
  68. package/dist/web/src/components/FaultBanner.js +2 -3
  69. package/dist/web/src/components/Header.js +5 -1
  70. package/dist/web/src/components/ProviderCard.js +1 -2
  71. package/dist/web/src/components/ProviderDrawer.d.ts +8 -1
  72. package/dist/web/src/components/ProviderDrawer.js +103 -7
  73. package/dist/web/src/components/ProviderIcon.js +1 -0
  74. package/dist/web/src/components/ProviderRow.js +1 -2
  75. package/dist/web/src/components/Recommendation.js +2 -3
  76. package/dist/web/src/components/ResetRail.js +4 -5
  77. package/dist/web/src/components/SubscriptionList.js +1 -2
  78. package/dist/web/src/components/settings/ProvidersPanel.js +1 -2
  79. package/dist/web/src/pages/Onboarding.js +4 -5
  80. package/dist/web/src/state.d.ts +18 -0
  81. package/dist/web/src/state.js +18 -0
  82. package/dist/webAssets.js +1 -1
  83. package/dist/webHtml.js +1 -1
  84. package/package.json +4 -3
  85. package/web/dist/assets/index-CzObzbxb.js +12 -0
  86. package/web/dist/assets/index-VioQmN7J.css +1 -0
  87. package/web/dist/index.html +2 -2
  88. package/dist/src/adapters/agy.d.ts +0 -3
  89. package/dist/src/adapters/agy.js +0 -81
  90. package/dist/src/adapters/claude.d.ts +0 -9
  91. package/dist/src/adapters/claude.js +0 -30
  92. package/dist/src/adapters/codex.d.ts +0 -7
  93. package/dist/src/adapters/codex.js +0 -140
  94. package/dist/src/adapters/grok.d.ts +0 -7
  95. package/dist/src/adapters/grok.js +0 -126
  96. package/dist/src/adapters/index.d.ts +0 -21
  97. package/dist/src/adapters/index.js +0 -66
  98. package/dist/src/adapters/kimi.d.ts +0 -7
  99. package/dist/src/adapters/kimi.js +0 -76
  100. package/dist/src/adapters/manual.d.ts +0 -8
  101. package/dist/src/adapters/manual.js +0 -28
  102. package/dist/src/adapters/parse.d.ts +0 -1
  103. package/dist/src/adapters/parse.js +0 -81
  104. package/dist/src/adapters/pty.d.ts +0 -23
  105. package/dist/src/adapters/pty.js +0 -487
  106. package/dist/src/adapters/types.d.ts +0 -21
  107. package/dist/src/adapters/types.js +0 -1
  108. package/dist/src/advisory/engine.d.ts +0 -28
  109. package/dist/src/advisory/engine.js +0 -149
  110. package/dist/src/advisory/snapshot.d.ts +0 -16
  111. package/dist/src/advisory/snapshot.js +0 -213
  112. package/dist/src/advisory/types.d.ts +0 -56
  113. package/dist/src/advisory/types.js +0 -1
  114. package/dist/src/advisory/validate.d.ts +0 -5
  115. package/dist/src/advisory/validate.js +0 -18
  116. package/dist/src/cli/advise.d.ts +0 -3
  117. package/dist/src/cli/advise.js +0 -62
  118. package/dist/src/cli/clients.d.ts +0 -18
  119. package/dist/src/cli/clients.js +0 -11
  120. package/dist/src/cli/index.d.ts +0 -2
  121. package/dist/src/cli/index.js +0 -22
  122. package/dist/src/cli/ingest.d.ts +0 -3
  123. package/dist/src/cli/ingest.js +0 -31
  124. package/dist/src/cli/runtime.d.ts +0 -12
  125. package/dist/src/cli/runtime.js +0 -125
  126. package/dist/src/cli/snapshot-source.d.ts +0 -25
  127. package/dist/src/cli/snapshot-source.js +0 -223
  128. package/dist/src/cli/status.d.ts +0 -3
  129. package/dist/src/cli/status.js +0 -84
  130. package/dist/src/config.d.ts +0 -40
  131. package/dist/src/config.js +0 -157
  132. package/dist/src/daemon.d.ts +0 -4
  133. package/dist/src/daemon.js +0 -15
  134. package/dist/src/format/markdown.d.ts +0 -3
  135. package/dist/src/format/markdown.js +0 -24
  136. package/dist/src/format/rows.d.ts +0 -27
  137. package/dist/src/format/rows.js +0 -241
  138. package/dist/src/format/terminal.d.ts +0 -12
  139. package/dist/src/format/terminal.js +0 -144
  140. package/dist/src/http/server.d.ts +0 -25
  141. package/dist/src/http/server.js +0 -274
  142. package/dist/src/mcp/server.d.ts +0 -46
  143. package/dist/src/mcp/server.js +0 -163
  144. package/dist/src/runtime/client.d.ts +0 -24
  145. package/dist/src/runtime/client.js +0 -72
  146. package/dist/src/runtime/owner.d.ts +0 -35
  147. package/dist/src/runtime/owner.js +0 -393
  148. package/dist/src/runtime/poll.d.ts +0 -48
  149. package/dist/src/runtime/poll.js +0 -222
  150. package/dist/src/runtime/service.d.ts +0 -24
  151. package/dist/src/runtime/service.js +0 -251
  152. package/dist/src/runtime/spawn.d.ts +0 -17
  153. package/dist/src/runtime/spawn.js +0 -97
  154. package/dist/src/runtime/token.d.ts +0 -3
  155. package/dist/src/runtime/token.js +0 -64
  156. package/dist/src/service/index.d.ts +0 -4
  157. package/dist/src/service/index.js +0 -47
  158. package/dist/src/service/macos.d.ts +0 -96
  159. package/dist/src/service/macos.js +0 -551
  160. package/dist/src/store/attempts.d.ts +0 -12
  161. package/dist/src/store/attempts.js +0 -29
  162. package/dist/src/store/db.d.ts +0 -2
  163. package/dist/src/store/db.js +0 -74
  164. package/dist/src/store/quotas.d.ts +0 -6
  165. package/dist/src/store/quotas.js +0 -98
  166. package/dist/src/version.d.ts +0 -1
  167. package/dist/src/version.js +0 -2
  168. package/dist/src/webAssets.d.ts +0 -1
  169. package/dist/src/webAssets.js +0 -2
  170. package/dist/src/webHtml.d.ts +0 -1
  171. package/dist/src/webHtml.js +0 -2
  172. package/dist/tests/adapters/agy.test.d.ts +0 -1
  173. package/dist/tests/adapters/agy.test.js +0 -337
  174. package/dist/tests/adapters/claude.test.d.ts +0 -1
  175. package/dist/tests/adapters/claude.test.js +0 -83
  176. package/dist/tests/adapters/codex.test.d.ts +0 -1
  177. package/dist/tests/adapters/codex.test.js +0 -246
  178. package/dist/tests/adapters/credential-free.test.d.ts +0 -1
  179. package/dist/tests/adapters/credential-free.test.js +0 -211
  180. package/dist/tests/adapters/grok.test.d.ts +0 -1
  181. package/dist/tests/adapters/grok.test.js +0 -259
  182. package/dist/tests/adapters/kimi.test.d.ts +0 -1
  183. package/dist/tests/adapters/kimi.test.js +0 -235
  184. package/dist/tests/adapters/manual.test.d.ts +0 -1
  185. package/dist/tests/adapters/manual.test.js +0 -28
  186. package/dist/tests/adapters/pty.test.d.ts +0 -1
  187. package/dist/tests/adapters/pty.test.js +0 -240
  188. package/dist/tests/adapters/registry.test.d.ts +0 -1
  189. package/dist/tests/adapters/registry.test.js +0 -25
  190. package/dist/tests/advisory/engine.test.d.ts +0 -1
  191. package/dist/tests/advisory/engine.test.js +0 -222
  192. package/dist/tests/advisory/pace.test.d.ts +0 -1
  193. package/dist/tests/advisory/pace.test.js +0 -60
  194. package/dist/tests/advisory/snapshot.test.d.ts +0 -1
  195. package/dist/tests/advisory/snapshot.test.js +0 -65
  196. package/dist/tests/advisory/validate.test.d.ts +0 -1
  197. package/dist/tests/advisory/validate.test.js +0 -19
  198. package/dist/tests/bootstrap.test.d.ts +0 -1
  199. package/dist/tests/bootstrap.test.js +0 -14
  200. package/dist/tests/cli/advise.test.d.ts +0 -1
  201. package/dist/tests/cli/advise.test.js +0 -294
  202. package/dist/tests/cli/cli.test.d.ts +0 -1
  203. package/dist/tests/cli/cli.test.js +0 -46
  204. package/dist/tests/cli/helpers.d.ts +0 -9
  205. package/dist/tests/cli/helpers.js +0 -93
  206. package/dist/tests/cli/ingest.test.d.ts +0 -1
  207. package/dist/tests/cli/ingest.test.js +0 -188
  208. package/dist/tests/cli/parity.test.d.ts +0 -1
  209. package/dist/tests/cli/parity.test.js +0 -225
  210. package/dist/tests/cli/snapshot-source.test.d.ts +0 -1
  211. package/dist/tests/cli/snapshot-source.test.js +0 -305
  212. package/dist/tests/cli/status.test.d.ts +0 -1
  213. package/dist/tests/cli/status.test.js +0 -423
  214. package/dist/tests/daemon.test.d.ts +0 -1
  215. package/dist/tests/daemon.test.js +0 -164
  216. package/dist/tests/fixtures/stable-state.d.ts +0 -22
  217. package/dist/tests/fixtures/stable-state.js +0 -152
  218. package/dist/tests/fixtures/stable-state.test.d.ts +0 -1
  219. package/dist/tests/fixtures/stable-state.test.js +0 -17
  220. package/dist/tests/format/markdown.test.d.ts +0 -1
  221. package/dist/tests/format/markdown.test.js +0 -49
  222. package/dist/tests/format/terminal.test.d.ts +0 -1
  223. package/dist/tests/format/terminal.test.js +0 -476
  224. package/dist/tests/http/api.test.d.ts +0 -1
  225. package/dist/tests/http/api.test.js +0 -236
  226. package/dist/tests/integration.test.d.ts +0 -1
  227. package/dist/tests/integration.test.js +0 -61
  228. package/dist/tests/mcp/protocol.test.d.ts +0 -1
  229. package/dist/tests/mcp/protocol.test.js +0 -134
  230. package/dist/tests/mcp/quotas.test.d.ts +0 -1
  231. package/dist/tests/mcp/quotas.test.js +0 -56
  232. package/dist/tests/mcp/server.test.d.ts +0 -1
  233. package/dist/tests/mcp/server.test.js +0 -7
  234. package/dist/tests/mcp/tools.test.d.ts +0 -1
  235. package/dist/tests/mcp/tools.test.js +0 -291
  236. package/dist/tests/runtime/cancel.test.d.ts +0 -1
  237. package/dist/tests/runtime/cancel.test.js +0 -264
  238. package/dist/tests/runtime/cli.test.d.ts +0 -1
  239. package/dist/tests/runtime/cli.test.js +0 -226
  240. package/dist/tests/runtime/config.test.d.ts +0 -1
  241. package/dist/tests/runtime/config.test.js +0 -144
  242. package/dist/tests/runtime/http.test.d.ts +0 -1
  243. package/dist/tests/runtime/http.test.js +0 -267
  244. package/dist/tests/runtime/lifecycle.test.d.ts +0 -1
  245. package/dist/tests/runtime/lifecycle.test.js +0 -303
  246. package/dist/tests/runtime/ownership.test.d.ts +0 -1
  247. package/dist/tests/runtime/ownership.test.js +0 -355
  248. package/dist/tests/runtime/poll.test.d.ts +0 -1
  249. package/dist/tests/runtime/poll.test.js +0 -410
  250. package/dist/tests/runtime/status.test.d.ts +0 -1
  251. package/dist/tests/runtime/status.test.js +0 -232
  252. package/dist/tests/service/macos.test.d.ts +0 -1
  253. package/dist/tests/service/macos.test.js +0 -349
  254. package/dist/tests/store/attempts.test.d.ts +0 -1
  255. package/dist/tests/store/attempts.test.js +0 -29
  256. package/dist/tests/store/db.test.d.ts +0 -1
  257. package/dist/tests/store/db.test.js +0 -96
  258. package/dist/tests/store/migration.test.d.ts +0 -1
  259. package/dist/tests/store/migration.test.js +0 -41
  260. package/dist/tests/web/advice-drawer.test.d.ts +0 -1
  261. package/dist/tests/web/advice-drawer.test.js +0 -45
  262. package/dist/tests/web/build.test.d.ts +0 -1
  263. package/dist/tests/web/build.test.js +0 -8
  264. package/dist/tests/web/dashboard.e2e.d.ts +0 -1
  265. package/dist/tests/web/dashboard.e2e.js +0 -670
  266. package/dist/tests/web/header.test.d.ts +0 -1
  267. package/dist/tests/web/header.test.js +0 -56
  268. package/dist/tests/web/names.test.d.ts +0 -1
  269. package/dist/tests/web/names.test.js +0 -19
  270. package/dist/tests/web/onboarding.test.d.ts +0 -1
  271. package/dist/tests/web/onboarding.test.js +0 -21
  272. package/dist/tests/web/pacing.test.d.ts +0 -1
  273. package/dist/tests/web/pacing.test.js +0 -62
  274. package/dist/tests/web/playwright.config.d.ts +0 -2
  275. package/dist/tests/web/playwright.config.js +0 -10
  276. package/dist/tests/web/provider-drawer.test.d.ts +0 -1
  277. package/dist/tests/web/provider-drawer.test.js +0 -68
  278. package/dist/tests/web/recommendation.test.d.ts +0 -1
  279. package/dist/tests/web/recommendation.test.js +0 -38
  280. package/dist/tests/web/reset-rail.test.d.ts +0 -1
  281. package/dist/tests/web/reset-rail.test.js +0 -63
  282. package/dist/tests/web/responsive.test.d.ts +0 -1
  283. package/dist/tests/web/responsive.test.js +0 -21
  284. package/dist/tests/web/settings.test.d.ts +0 -1
  285. package/dist/tests/web/settings.test.js +0 -32
  286. package/dist/tests/web/state-catalog.test.d.ts +0 -1
  287. package/dist/tests/web/state-catalog.test.js +0 -206
  288. package/dist/tests/web/state.test.d.ts +0 -1
  289. package/dist/tests/web/state.test.js +0 -27
  290. package/dist/tests/web/stub-server.d.ts +0 -37
  291. package/dist/tests/web/stub-server.js +0 -132
  292. package/dist/tests/web/theme.test.d.ts +0 -1
  293. package/dist/tests/web/theme.test.js +0 -12
  294. package/dist/web/src/names.d.ts +0 -3
  295. package/dist/web/src/names.js +0 -19
  296. package/scripts/fix-pty-perms.mjs +0 -21
  297. package/web/dist/assets/index-CyUkWdwJ.css +0 -1
  298. package/web/dist/assets/index-PN4RpMKG.js +0 -12
@@ -3,6 +3,7 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { createRequire } from "node:module";
5
5
  import { registerTrackedChild } from "../runtime/spawn.js";
6
+ import { diagnosticError, DiagnosticError } from "../diagnostics/failure.js";
6
7
  export function stripAnsi(s) {
7
8
  return s
8
9
  .replace(/\x1b\[[^A-Za-z]*[A-Za-z]/g, "")
@@ -18,17 +19,116 @@ function regexTest(re, s) {
18
19
  const fresh = new RegExp(re.source, re.flags.replace("g", ""));
19
20
  return fresh.test(s);
20
21
  }
22
+ /**
23
+ * Canned answers to terminal capability queries, table-driven so a future
24
+ * query type is a one-line row. Replies are static: colour answers never
25
+ * affect parsing because styling is stripped before matching.
26
+ */
27
+ export const TERMINAL_QUERY_RESPONSES = [
28
+ // DSR (device status report): cursor position request. Unanswered, some
29
+ // TUIs exit outright ("cursor position could not be read").
30
+ { match: /\x1b\[[0-9;]*6n/, reply: () => "\x1b[1;1R" },
31
+ // DA1 (primary device attributes): `CSI c` is exclusively a DA request.
32
+ { match: /\x1b\[[0-9;]*c/, reply: () => "\x1b[?1;2c" },
33
+ // OSC 4/10/11 colour queries, BEL- or ST-terminated. The palette index is
34
+ // echoed back; 16 OSC 4 queries were measured in a single Muse startup.
35
+ { match: /\x1b\]4;(\d+);\?(?:\x07|\x1b\\)/, reply: (m) => `\x1b]4;${m[1]};rgb:0000/0000/0000\x07` },
36
+ { match: /\x1b\]10;\?(?:\x07|\x1b\\)/, reply: () => "\x1b]10;rgb:ffff/ffff/ffff\x07" },
37
+ { match: /\x1b\]11;\?(?:\x07|\x1b\\)/, reply: () => "\x1b]11;rgb:0000/0000/0000\x07" },
38
+ ];
39
+ /**
40
+ * Stateful feeder for `TERMINAL_QUERY_RESPONSES`. Consumes each matched query
41
+ * from an internal buffer, so a query split across chunks is answered once it
42
+ * completes and never answered twice. Plain output passes through silently.
43
+ */
44
+ export function createTerminalQueryResponder(write) {
45
+ let buf = "";
46
+ return (chunk) => {
47
+ buf += chunk;
48
+ let progress = true;
49
+ while (progress) {
50
+ progress = false;
51
+ for (const rule of TERMINAL_QUERY_RESPONSES) {
52
+ const m = rule.match.exec(buf);
53
+ if (m?.index !== undefined) {
54
+ write(rule.reply(m));
55
+ buf = buf.slice(0, m.index) + buf.slice(m.index + m[0].length);
56
+ progress = true;
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ // Bounded tail: a partial query can only straddle the end of the stream,
62
+ // and the longest query is well under 64 bytes.
63
+ if (buf.length > 64)
64
+ buf = buf.slice(-64);
65
+ };
66
+ }
67
+ /**
68
+ * Lazy replacement for the removed postinstall: best-effort `chmod 0o755`
69
+ * over every prebuilds entry `spawn-helper` under the given node-pty roots.
70
+ * Missing roots, missing prebuild dirs, and missing helpers are skipped
71
+ * silently; only attempted repairs are recorded.
72
+ */
73
+ export function repairSpawnHelpers(roots, chmod = fs.chmodSync) {
74
+ const outcomes = [];
75
+ const seen = new Set();
76
+ for (const root of roots) {
77
+ let entries;
78
+ try {
79
+ entries = fs.readdirSync(path.join(root, "prebuilds"));
80
+ }
81
+ catch {
82
+ continue;
83
+ }
84
+ for (const entry of entries) {
85
+ const helper = path.join(root, "prebuilds", entry, "spawn-helper");
86
+ if (seen.has(helper))
87
+ continue;
88
+ seen.add(helper);
89
+ try {
90
+ if (!fs.existsSync(helper))
91
+ continue;
92
+ }
93
+ catch {
94
+ continue;
95
+ }
96
+ try {
97
+ chmod(helper, 0o755);
98
+ outcomes.push({ helper, ok: true });
99
+ }
100
+ catch (e) {
101
+ outcomes.push({ helper, ok: false, error: String(e?.message ?? e) });
102
+ }
103
+ }
104
+ }
105
+ return outcomes;
106
+ }
107
+ /**
108
+ * Human diagnostic for failed repairs, or null when nothing failed.
109
+ * Load-bearing for root-owned trees (e.g. `sudo` global installs) where the
110
+ * invoking user cannot repair: without it the failure would go opaque again.
111
+ */
112
+ export function describeSpawnHelperRepairs(outcomes) {
113
+ const failed = outcomes.filter((o) => !o.ok);
114
+ if (failed.length === 0)
115
+ return null;
116
+ const lines = failed.map((o) => `could not make ${o.helper} executable (${o.error ?? "unknown error"})`);
117
+ return (`${lines.join("; ")} — the install tree is likely root-owned (e.g. installed with sudo); ` +
118
+ `reinstall without sudo or fix the install's ownership so the invoking user can write it`);
119
+ }
120
+ function nodePtyPackageRoot(resolvedEntry) {
121
+ const dir = path.dirname(resolvedEntry);
122
+ // node-pty's main entry is lib/index.js; the package root is two levels up.
123
+ if (path.basename(dir) === "lib" && /^index\.[cm]?js$/.test(path.basename(resolvedEntry))) {
124
+ return path.dirname(dir);
125
+ }
126
+ return dir;
127
+ }
21
128
  let ptyMod = null;
22
129
  function getPty() {
23
130
  if (ptyMod)
24
131
  return ptyMod;
25
- try {
26
- const require = createRequire(import.meta.url);
27
- ptyMod = require("node-pty");
28
- if (ptyMod?.spawn)
29
- return ptyMod;
30
- }
31
- catch { }
32
132
  // Compiled binary (bun --compile) has no node_modules; try sidecar locations
33
133
  const execDir = path.dirname(process.execPath);
34
134
  const platformArch = `${process.platform}-${process.arch}`;
@@ -40,6 +140,29 @@ function getPty() {
40
140
  path.join(os.homedir(), ".local", "share", "quotacap", "pty", platformArch, "node-pty"),
41
141
  path.join(os.homedir(), ".quotacap", "pty", "node-pty"),
42
142
  ];
143
+ // Best-effort +x before requiring: the resolvable npm root (when present)
144
+ // plus every existing sidecar root. Successful repairs stay silent.
145
+ const repairRoots = [];
146
+ try {
147
+ const require0 = createRequire(import.meta.url);
148
+ repairRoots.push(nodePtyPackageRoot(require0.resolve("node-pty")));
149
+ }
150
+ catch { }
151
+ for (const c of candidates) {
152
+ try {
153
+ if (fs.existsSync(c))
154
+ repairRoots.push(c);
155
+ }
156
+ catch { }
157
+ }
158
+ const repairs = repairSpawnHelpers(repairRoots);
159
+ try {
160
+ const require = createRequire(import.meta.url);
161
+ ptyMod = require("node-pty");
162
+ if (ptyMod?.spawn)
163
+ return ptyMod;
164
+ }
165
+ catch { }
43
166
  for (const c of candidates) {
44
167
  try {
45
168
  if (!fs.existsSync(c))
@@ -65,7 +188,9 @@ function getPty() {
65
188
  }
66
189
  catch { }
67
190
  }
68
- throw new Error("pty: node-pty not available — install with build tools (Xcode on macOS, build-essential + python3 on Linux) or use exec-based adapters only; for compiled binaries, ensure the pty sidecar is installed alongside the binary (see install.sh)");
191
+ const repairNote = describeSpawnHelperRepairs(repairs);
192
+ throw diagnosticError(new Error("pty: node-pty not available — install with build tools (Xcode on macOS, build-essential + python3 on Linux) or use exec-based adapters only; for compiled binaries, ensure the pty sidecar is installed alongside the binary (see install.sh)" +
193
+ (repairNote ? `; ${repairNote}` : "")), { source: "pty" });
69
194
  }
70
195
  function isBunRuntime() {
71
196
  return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.spawn === "function";
@@ -76,9 +201,9 @@ async function runPtyBun(opts) {
76
201
  const rows = opts.rows ?? 50;
77
202
  const readyTimeoutMs = opts.readyTimeoutMs ?? 6000;
78
203
  if (opts.timeoutMs <= 0)
79
- throw new Error("pty: timeoutMs must be > 0");
204
+ throw diagnosticError(new Error("pty: timeoutMs must be > 0"), { source: "pty" });
80
205
  if (opts.signal?.aborted)
81
- throw new Error("pty aborted");
206
+ throw diagnosticError(new Error("pty aborted"), { source: "pty", checkpoint: "abort" });
82
207
  let aborted = false;
83
208
  const onAbort = () => {
84
209
  aborted = true;
@@ -87,7 +212,7 @@ async function runPtyBun(opts) {
87
212
  opts.signal.addEventListener("abort", onAbort, { once: true });
88
213
  const checkAborted = () => {
89
214
  if (aborted)
90
- throw new Error("pty aborted");
215
+ throw diagnosticError(new Error("pty aborted"), { source: "pty", checkpoint: "abort", stdout: transcript });
91
216
  };
92
217
  const teardownAbort = () => {
93
218
  if (opts.signal)
@@ -97,6 +222,12 @@ async function runPtyBun(opts) {
97
222
  let transcript = "";
98
223
  let exited = false;
99
224
  let exitCode;
225
+ // Genuine PTY via the Bun.spawn `terminal` option (verified on Bun 1.3.11):
226
+ // the child's stdin, stdout, and stderr are all the PTY slave, input goes
227
+ // through proc.terminal.write, and merged output arrives in `data`.
228
+ // proc.stdin/stdout/stderr are null in this mode.
229
+ const decoder = new TextDecoder();
230
+ let feedQueries = null;
100
231
  const proc = BunGlobal.spawn([opts.file, ...(opts.args ?? [])], {
101
232
  cwd: opts.cwd ?? process.cwd(),
102
233
  env: {
@@ -104,25 +235,36 @@ async function runPtyBun(opts) {
104
235
  ...(opts.env ?? {}),
105
236
  TERM: "xterm-256color",
106
237
  },
107
- stdin: "pipe",
108
- pty: { cols, rows },
238
+ terminal: {
239
+ cols,
240
+ rows,
241
+ data(_term, chunk) {
242
+ try {
243
+ const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
244
+ transcript += text;
245
+ feedQueries?.(text);
246
+ }
247
+ catch { }
248
+ },
249
+ },
109
250
  });
110
- // Capture stdout (pty merges stdout+stderr)
111
- const decoder = new TextDecoder();
112
- const reader = proc.stdout.getReader();
113
- let reading = true;
114
- const readLoop = (async () => {
251
+ if (!proc.terminal) {
252
+ // Old Bun runtimes silently ignore the unknown `terminal` option, so an
253
+ // absent proc.terminal is the fail-loud signal — never run half-attached.
115
254
  try {
116
- while (reading) {
117
- const { value, done } = await reader.read();
118
- if (done)
119
- break;
120
- if (value)
121
- transcript += decoder.decode(value);
122
- }
255
+ proc.kill();
123
256
  }
124
257
  catch { }
125
- })();
258
+ throw diagnosticError(new Error("pty: Bun runtime lacks terminal (PTY) support (Bun.spawn terminal option unavailable) — rebuild the standalone binary with a newer Bun version"), { source: "pty" });
259
+ }
260
+ if (opts.respondToQueries) {
261
+ feedQueries = createTerminalQueryResponder((s) => {
262
+ try {
263
+ proc.terminal.write(s);
264
+ }
265
+ catch { }
266
+ });
267
+ }
126
268
  // Poll for exit
127
269
  const exitPoll = (async () => {
128
270
  try {
@@ -132,14 +274,23 @@ async function runPtyBun(opts) {
132
274
  }
133
275
  catch { }
134
276
  })();
277
+ void exitPoll;
135
278
  const checkCap = () => {
136
279
  if (Buffer.byteLength(transcript, "utf8") > maxBytes) {
137
- throw new Error(`pty transcript exceeds ${maxBytes} bytes`);
280
+ throw diagnosticError(new Error(`pty transcript exceeds ${maxBytes} bytes`), {
281
+ source: "pty",
282
+ checkpoint: "transcript overflow",
283
+ stdout: transcript,
284
+ });
138
285
  }
139
286
  };
140
287
  const checkAbort = (clean) => {
141
288
  if (opts.abortOn && regexTest(opts.abortOn, clean)) {
142
- throw new Error(`pty: untrusted workspace — trust prompt detected in ${opts.cwd ?? process.cwd()} — run \`${opts.file}\` there and select Trust this folder`);
289
+ throw diagnosticError(new Error(`pty: untrusted workspace — trust prompt detected in ${opts.cwd ?? process.cwd()} — run \`${opts.file}\` there and select Trust this folder`), {
290
+ source: "pty",
291
+ checkpoint: "trust prompt",
292
+ stdout: transcript,
293
+ });
143
294
  }
144
295
  };
145
296
  const kill = async () => {
@@ -169,6 +320,10 @@ async function runPtyBun(opts) {
169
320
  proc.kill();
170
321
  }
171
322
  catch { }
323
+ try {
324
+ proc.terminal?.close();
325
+ }
326
+ catch { }
172
327
  }, opts.label ?? opts.file);
173
328
  try {
174
329
  if (opts.readyRegex) {
@@ -177,8 +332,14 @@ async function runPtyBun(opts) {
177
332
  while (Date.now() < deadline) {
178
333
  checkCap();
179
334
  checkAborted();
180
- if (exited)
181
- throw new Error(`pty exited before ready (code ${exitCode})`);
335
+ if (exited) {
336
+ throw diagnosticError(new Error(`pty exited before ready (code ${exitCode})`), {
337
+ source: "pty",
338
+ checkpoint: "before ready",
339
+ exitCode,
340
+ stdout: transcript,
341
+ });
342
+ }
182
343
  const clean = stripAnsi(transcript);
183
344
  checkAbort(clean);
184
345
  if (regexTest(opts.readyRegex, clean)) {
@@ -189,13 +350,24 @@ async function runPtyBun(opts) {
189
350
  }
190
351
  if (!matched) {
191
352
  await kill();
192
- throw new Error(`pty ready timeout after ${readyTimeoutMs}ms`);
353
+ throw diagnosticError(new Error(`pty ready timeout after ${readyTimeoutMs}ms`), {
354
+ source: "pty",
355
+ checkpoint: "ready timeout",
356
+ durationMs: readyTimeoutMs,
357
+ stdout: transcript,
358
+ });
193
359
  }
194
- await delay(200);
360
+ await delay(opts.settleDelayMs ?? 200);
195
361
  checkCap();
196
362
  checkAborted();
197
- if (exited)
198
- throw new Error(`pty exited before input (code ${exitCode})`);
363
+ if (exited) {
364
+ throw diagnosticError(new Error(`pty exited before input (code ${exitCode})`), {
365
+ source: "pty",
366
+ checkpoint: "before input",
367
+ exitCode,
368
+ stdout: transcript,
369
+ });
370
+ }
199
371
  const postReadyClean = stripAnsi(transcript);
200
372
  checkAbort(postReadyClean);
201
373
  }
@@ -203,20 +375,57 @@ async function runPtyBun(opts) {
203
375
  await delay(opts.settleDelayMs);
204
376
  checkCap();
205
377
  checkAborted();
206
- if (exited)
207
- throw new Error(`pty exited during settle (code ${exitCode})`);
378
+ if (exited) {
379
+ throw diagnosticError(new Error(`pty exited during settle (code ${exitCode})`), {
380
+ source: "pty",
381
+ checkpoint: "during settle",
382
+ exitCode,
383
+ stdout: transcript,
384
+ });
385
+ }
208
386
  const c = stripAnsi(transcript);
209
387
  checkAbort(c);
210
388
  }
211
- if (exited)
212
- throw new Error(`pty exited before input (code ${exitCode})`);
389
+ if (exited) {
390
+ throw diagnosticError(new Error(`pty exited before input (code ${exitCode})`), {
391
+ source: "pty",
392
+ checkpoint: "before input",
393
+ exitCode,
394
+ stdout: transcript,
395
+ });
396
+ }
213
397
  try {
214
- proc.stdin.write(opts.input);
215
- if (typeof proc.stdin.flush === "function")
216
- proc.stdin.flush();
398
+ proc.terminal.write(opts.input);
217
399
  }
218
400
  catch (e) {
219
- throw new Error(`pty write failed: ${e.message}`);
401
+ throw diagnosticError(new Error(`pty write failed: ${e.message}`), {
402
+ source: "pty",
403
+ checkpoint: "write",
404
+ stdout: transcript,
405
+ });
406
+ }
407
+ if (opts.submitInput !== undefined && opts.submitAfterMs !== undefined) {
408
+ await delay(opts.submitAfterMs);
409
+ checkCap();
410
+ checkAborted();
411
+ if (exited) {
412
+ throw diagnosticError(new Error(`pty exited before input (code ${exitCode})`), {
413
+ source: "pty",
414
+ checkpoint: "before input",
415
+ exitCode,
416
+ stdout: transcript,
417
+ });
418
+ }
419
+ try {
420
+ proc.terminal.write(opts.submitInput);
421
+ }
422
+ catch (e) {
423
+ throw diagnosticError(new Error(`pty write failed: ${e.message}`), {
424
+ source: "pty",
425
+ checkpoint: "write",
426
+ stdout: transcript,
427
+ });
428
+ }
220
429
  }
221
430
  if (opts.completionRegex) {
222
431
  const deadline = Date.now() + opts.timeoutMs;
@@ -232,7 +441,12 @@ async function runPtyBun(opts) {
232
441
  }
233
442
  if (exited) {
234
443
  if (!regexTest(opts.completionRegex, clean)) {
235
- throw new Error(`pty exited before completion (code ${exitCode})`);
444
+ throw diagnosticError(new Error(`pty exited before completion (code ${exitCode})`), {
445
+ source: "pty",
446
+ checkpoint: "before completion",
447
+ exitCode,
448
+ stdout: transcript,
449
+ });
236
450
  }
237
451
  done = true;
238
452
  break;
@@ -241,7 +455,12 @@ async function runPtyBun(opts) {
241
455
  }
242
456
  if (!done) {
243
457
  await kill();
244
- throw new Error(`pty completion timeout after ${opts.timeoutMs}ms`);
458
+ throw diagnosticError(new Error(`pty completion timeout after ${opts.timeoutMs}ms`), {
459
+ source: "pty",
460
+ checkpoint: "completion timeout",
461
+ durationMs: opts.timeoutMs,
462
+ stdout: transcript,
463
+ });
245
464
  }
246
465
  }
247
466
  else {
@@ -257,12 +476,17 @@ async function runPtyBun(opts) {
257
476
  }
258
477
  }
259
478
  await kill();
260
- reading = false;
479
+ // Drain late `data` callbacks, then close the terminal and flush the
480
+ // streamed decoder before the final checks.
481
+ await delay(200);
261
482
  try {
262
- reader.cancel();
483
+ proc.terminal.close();
484
+ }
485
+ catch { }
486
+ try {
487
+ transcript += decoder.decode();
263
488
  }
264
489
  catch { }
265
- await Promise.race([readLoop, delay(200)]);
266
490
  checkCap();
267
491
  checkAborted();
268
492
  const finalClean = stripAnsi(transcript);
@@ -272,15 +496,16 @@ async function runPtyBun(opts) {
272
496
  return transcript;
273
497
  }
274
498
  catch (e) {
275
- reading = false;
499
+ await kill();
276
500
  try {
277
- reader.cancel();
501
+ proc.terminal.close();
278
502
  }
279
503
  catch { }
280
- await kill();
281
504
  unregisterChild();
282
505
  teardownAbort();
283
- throw e;
506
+ if (e instanceof DiagnosticError)
507
+ throw e;
508
+ throw diagnosticError(e, { source: "pty", stdout: transcript, exitCode });
284
509
  }
285
510
  }
286
511
  async function runPtyNode(opts) {
@@ -289,9 +514,9 @@ async function runPtyNode(opts) {
289
514
  const rows = opts.rows ?? 50;
290
515
  const readyTimeoutMs = opts.readyTimeoutMs ?? 6000;
291
516
  if (opts.timeoutMs <= 0)
292
- throw new Error("pty: timeoutMs must be > 0");
517
+ throw diagnosticError(new Error("pty: timeoutMs must be > 0"), { source: "pty" });
293
518
  if (opts.signal?.aborted)
294
- throw new Error("pty aborted");
519
+ throw diagnosticError(new Error("pty aborted"), { source: "pty", checkpoint: "abort" });
295
520
  let aborted = false;
296
521
  const onAbort = () => {
297
522
  aborted = true;
@@ -300,7 +525,7 @@ async function runPtyNode(opts) {
300
525
  opts.signal.addEventListener("abort", onAbort, { once: true });
301
526
  const checkAborted = () => {
302
527
  if (aborted)
303
- throw new Error("pty aborted");
528
+ throw diagnosticError(new Error("pty aborted"), { source: "pty", checkpoint: "abort", stdout: transcript });
304
529
  };
305
530
  const teardownAbort = () => {
306
531
  if (opts.signal)
@@ -325,7 +550,10 @@ async function runPtyNode(opts) {
325
550
  });
326
551
  }
327
552
  catch (e) {
328
- throw new Error(`pty spawn failed for ${opts.file}: ${e.message}`);
553
+ throw diagnosticError(new Error(`pty spawn failed for ${opts.file}: ${e.message}`), {
554
+ source: "pty",
555
+ checkpoint: "spawn",
556
+ });
329
557
  }
330
558
  const unregisterChild = registerTrackedChild(() => {
331
559
  try {
@@ -333,9 +561,21 @@ async function runPtyNode(opts) {
333
561
  }
334
562
  catch { }
335
563
  }, opts.label ?? opts.file);
564
+ const feedQueries = opts.respondToQueries
565
+ ? createTerminalQueryResponder((s) => {
566
+ try {
567
+ ptyProcess.write(s);
568
+ }
569
+ catch { }
570
+ })
571
+ : null;
336
572
  const disposables = [];
337
573
  disposables.push(ptyProcess.onData((data) => {
338
574
  transcript += data;
575
+ try {
576
+ feedQueries?.(data);
577
+ }
578
+ catch { }
339
579
  }));
340
580
  disposables.push(ptyProcess.onExit(({ exitCode: c }) => {
341
581
  exited = true;
@@ -373,12 +613,20 @@ async function runPtyNode(opts) {
373
613
  };
374
614
  const checkCap = () => {
375
615
  if (Buffer.byteLength(transcript, "utf8") > maxBytes) {
376
- throw new Error(`pty transcript exceeds ${maxBytes} bytes`);
616
+ throw diagnosticError(new Error(`pty transcript exceeds ${maxBytes} bytes`), {
617
+ source: "pty",
618
+ checkpoint: "transcript overflow",
619
+ stdout: transcript,
620
+ });
377
621
  }
378
622
  };
379
623
  const checkAbort = (clean) => {
380
624
  if (opts.abortOn && regexTest(opts.abortOn, clean)) {
381
- throw new Error(`pty: untrusted workspace — trust prompt detected in ${opts.cwd ?? process.cwd()} — run \`${opts.file}\` there and select Trust this folder`);
625
+ throw diagnosticError(new Error(`pty: untrusted workspace — trust prompt detected in ${opts.cwd ?? process.cwd()} — run \`${opts.file}\` there and select Trust this folder`), {
626
+ source: "pty",
627
+ checkpoint: "trust prompt",
628
+ stdout: transcript,
629
+ });
382
630
  }
383
631
  };
384
632
  try {
@@ -388,8 +636,14 @@ async function runPtyNode(opts) {
388
636
  while (Date.now() < deadline) {
389
637
  checkCap();
390
638
  checkAborted();
391
- if (exited)
392
- throw new Error(`pty exited before ready (code ${exitCode})`);
639
+ if (exited) {
640
+ throw diagnosticError(new Error(`pty exited before ready (code ${exitCode})`), {
641
+ source: "pty",
642
+ checkpoint: "before ready",
643
+ exitCode,
644
+ stdout: transcript,
645
+ });
646
+ }
393
647
  const clean = stripAnsi(transcript);
394
648
  checkAbort(clean);
395
649
  if (regexTest(opts.readyRegex, clean)) {
@@ -400,13 +654,24 @@ async function runPtyNode(opts) {
400
654
  }
401
655
  if (!matched) {
402
656
  await kill();
403
- throw new Error(`pty ready timeout after ${readyTimeoutMs}ms`);
657
+ throw diagnosticError(new Error(`pty ready timeout after ${readyTimeoutMs}ms`), {
658
+ source: "pty",
659
+ checkpoint: "ready timeout",
660
+ durationMs: readyTimeoutMs,
661
+ stdout: transcript,
662
+ });
404
663
  }
405
- await delay(200);
664
+ await delay(opts.settleDelayMs ?? 200);
406
665
  checkCap();
407
666
  checkAborted();
408
- if (exited)
409
- throw new Error(`pty exited before input (code ${exitCode})`);
667
+ if (exited) {
668
+ throw diagnosticError(new Error(`pty exited before input (code ${exitCode})`), {
669
+ source: "pty",
670
+ checkpoint: "before input",
671
+ exitCode,
672
+ stdout: transcript,
673
+ });
674
+ }
410
675
  const postReadyClean = stripAnsi(transcript);
411
676
  checkAbort(postReadyClean);
412
677
  }
@@ -414,14 +679,58 @@ async function runPtyNode(opts) {
414
679
  await delay(opts.settleDelayMs);
415
680
  checkCap();
416
681
  checkAborted();
417
- if (exited)
418
- throw new Error(`pty exited during settle (code ${exitCode})`);
682
+ if (exited) {
683
+ throw diagnosticError(new Error(`pty exited during settle (code ${exitCode})`), {
684
+ source: "pty",
685
+ checkpoint: "during settle",
686
+ exitCode,
687
+ stdout: transcript,
688
+ });
689
+ }
419
690
  const c = stripAnsi(transcript);
420
691
  checkAbort(c);
421
692
  }
422
- if (exited)
423
- throw new Error(`pty exited before input (code ${exitCode})`);
424
- ptyProcess.write(opts.input);
693
+ if (exited) {
694
+ throw diagnosticError(new Error(`pty exited before input (code ${exitCode})`), {
695
+ source: "pty",
696
+ checkpoint: "before input",
697
+ exitCode,
698
+ stdout: transcript,
699
+ });
700
+ }
701
+ try {
702
+ ptyProcess.write(opts.input);
703
+ }
704
+ catch (e) {
705
+ throw diagnosticError(new Error(`pty write failed: ${e.message}`), {
706
+ source: "pty",
707
+ checkpoint: "write",
708
+ stdout: transcript,
709
+ });
710
+ }
711
+ if (opts.submitInput !== undefined && opts.submitAfterMs !== undefined) {
712
+ await delay(opts.submitAfterMs);
713
+ checkCap();
714
+ checkAborted();
715
+ if (exited) {
716
+ throw diagnosticError(new Error(`pty exited before input (code ${exitCode})`), {
717
+ source: "pty",
718
+ checkpoint: "before input",
719
+ exitCode,
720
+ stdout: transcript,
721
+ });
722
+ }
723
+ try {
724
+ ptyProcess.write(opts.submitInput);
725
+ }
726
+ catch (e) {
727
+ throw diagnosticError(new Error(`pty write failed: ${e.message}`), {
728
+ source: "pty",
729
+ checkpoint: "write",
730
+ stdout: transcript,
731
+ });
732
+ }
733
+ }
425
734
  if (opts.completionRegex) {
426
735
  const deadline = Date.now() + opts.timeoutMs;
427
736
  let done = false;
@@ -436,7 +745,12 @@ async function runPtyNode(opts) {
436
745
  }
437
746
  if (exited) {
438
747
  if (!regexTest(opts.completionRegex, clean)) {
439
- throw new Error(`pty exited before completion (code ${exitCode})`);
748
+ throw diagnosticError(new Error(`pty exited before completion (code ${exitCode})`), {
749
+ source: "pty",
750
+ checkpoint: "before completion",
751
+ exitCode,
752
+ stdout: transcript,
753
+ });
440
754
  }
441
755
  done = true;
442
756
  break;
@@ -445,7 +759,12 @@ async function runPtyNode(opts) {
445
759
  }
446
760
  if (!done) {
447
761
  await kill();
448
- throw new Error(`pty completion timeout after ${opts.timeoutMs}ms`);
762
+ throw diagnosticError(new Error(`pty completion timeout after ${opts.timeoutMs}ms`), {
763
+ source: "pty",
764
+ checkpoint: "completion timeout",
765
+ durationMs: opts.timeoutMs,
766
+ stdout: transcript,
767
+ });
449
768
  }
450
769
  }
451
770
  else {
@@ -471,7 +790,9 @@ async function runPtyNode(opts) {
471
790
  }
472
791
  catch (e) {
473
792
  await kill();
474
- throw e;
793
+ if (e instanceof DiagnosticError)
794
+ throw e;
795
+ throw diagnosticError(e, { source: "pty", stdout: transcript, exitCode });
475
796
  }
476
797
  finally {
477
798
  cleanup();
@@ -480,8 +801,15 @@ async function runPtyNode(opts) {
480
801
  }
481
802
  }
482
803
  export async function runPty(opts) {
483
- if (isBunRuntime()) {
484
- return runPtyBun(opts);
804
+ try {
805
+ if (isBunRuntime()) {
806
+ return await runPtyBun(opts);
807
+ }
808
+ return await runPtyNode(opts);
809
+ }
810
+ catch (e) {
811
+ if (e instanceof DiagnosticError)
812
+ throw e;
813
+ throw diagnosticError(e, { source: "pty" });
485
814
  }
486
- return runPtyNode(opts);
487
815
  }