stitchkit 0.103.13 → 0.104.1

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 (223) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/darwin-native.cjs +1 -3
  3. package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
  4. package/dist/agent-runtime/store-conformance-support.d.ts +5 -5
  5. package/dist/application/directory-inbox-accept.d.ts +13 -0
  6. package/dist/application/directory-inbox-accept.d.ts.map +1 -0
  7. package/dist/application/directory-inbox-contract.d.ts +42 -5
  8. package/dist/application/directory-inbox-contract.d.ts.map +1 -1
  9. package/dist/application/directory-inbox-delivery.d.ts +14 -0
  10. package/dist/application/directory-inbox-delivery.d.ts.map +1 -0
  11. package/dist/application/directory-inbox-files.d.ts +28 -0
  12. package/dist/application/directory-inbox-files.d.ts.map +1 -0
  13. package/dist/application/directory-inbox-state.d.ts +14 -0
  14. package/dist/application/directory-inbox-state.d.ts.map +1 -0
  15. package/dist/application/directory-inbox.d.ts +2 -1
  16. package/dist/application/directory-inbox.d.ts.map +1 -1
  17. package/dist/application/file-state-store.d.ts +2 -8
  18. package/dist/application/file-state-store.d.ts.map +1 -1
  19. package/dist/application/state-store.d.ts +6 -1
  20. package/dist/application/state-store.d.ts.map +1 -1
  21. package/dist/browser/client-arguments.d.ts +4 -0
  22. package/dist/browser/client-arguments.d.ts.map +1 -0
  23. package/dist/browser/client-executors.d.ts.map +1 -1
  24. package/dist/browser/client.d.ts.map +1 -1
  25. package/dist/browser/contract-stream.d.ts.map +1 -1
  26. package/dist/browser/http.d.ts +2 -1
  27. package/dist/browser/http.d.ts.map +1 -1
  28. package/dist/browser/native-fetch.d.ts +8 -0
  29. package/dist/browser/native-fetch.d.ts.map +1 -0
  30. package/dist/contract/errors.d.ts +16 -12
  31. package/dist/contract/errors.d.ts.map +1 -1
  32. package/dist/contract/normalize.d.ts +5 -1
  33. package/dist/contract/normalize.d.ts.map +1 -1
  34. package/dist/contract/stream.d.ts +1 -0
  35. package/dist/contract/stream.d.ts.map +1 -1
  36. package/dist/entrypoints/agent-runtime/browser.js +3 -3
  37. package/dist/entrypoints/agent-runtime/coding-tools.js +6 -6
  38. package/dist/entrypoints/agent-runtime/harness-tools.js +8 -8
  39. package/dist/entrypoints/agent-runtime/harness.js +11 -11
  40. package/dist/entrypoints/agent-runtime/realtime.js +3 -3
  41. package/dist/entrypoints/agent-runtime/sandbox.js +6 -6
  42. package/dist/entrypoints/agent-runtime.js +13 -13
  43. package/dist/entrypoints/application/diagnostic-journal.js +1 -1
  44. package/dist/entrypoints/application/directory-inbox.js +424 -119
  45. package/dist/entrypoints/application/grammy.js +1 -1
  46. package/dist/entrypoints/application.d.ts +2 -2
  47. package/dist/entrypoints/application.d.ts.map +1 -1
  48. package/dist/entrypoints/application.js +7 -3
  49. package/dist/entrypoints/cli.d.ts +2 -0
  50. package/dist/entrypoints/cli.d.ts.map +1 -1
  51. package/dist/entrypoints/cli.js +757 -136
  52. package/dist/entrypoints/contract.js +3 -3
  53. package/dist/entrypoints/files/packaging.d.ts +3 -0
  54. package/dist/entrypoints/files/packaging.d.ts.map +1 -0
  55. package/dist/entrypoints/files/packaging.js +133 -0
  56. package/dist/entrypoints/files.js +10 -107
  57. package/dist/entrypoints/index.js +7 -7
  58. package/dist/entrypoints/node.js +8 -8
  59. package/dist/entrypoints/observability.js +2 -2
  60. package/dist/entrypoints/react.js +1 -1
  61. package/dist/entrypoints/remote.js +19 -6
  62. package/dist/entrypoints/server.d.ts +1 -1
  63. package/dist/entrypoints/server.d.ts.map +1 -1
  64. package/dist/entrypoints/server.js +23 -17
  65. package/dist/entrypoints/telegram.d.ts +1 -0
  66. package/dist/entrypoints/telegram.d.ts.map +1 -1
  67. package/dist/entrypoints/telegram.js +214 -98
  68. package/dist/entrypoints/testing.js +7 -7
  69. package/dist/entrypoints/tools/connections.d.ts +2 -2
  70. package/dist/entrypoints/tools/connections.d.ts.map +1 -1
  71. package/dist/entrypoints/tools/connections.js +427 -294
  72. package/dist/entrypoints/tools/invoker.js +6 -6
  73. package/dist/entrypoints/tools/mcp.d.ts +12 -0
  74. package/dist/entrypoints/tools/mcp.d.ts.map +1 -0
  75. package/dist/entrypoints/tools/mcp.js +45 -0
  76. package/dist/entrypoints/tools.d.ts +1 -0
  77. package/dist/entrypoints/tools.d.ts.map +1 -1
  78. package/dist/entrypoints/tools.js +82 -1147
  79. package/dist/entrypoints/tracking.js +1 -1
  80. package/dist/files/boundary.d.ts +2 -8
  81. package/dist/files/boundary.d.ts.map +1 -1
  82. package/dist/files/file-io.d.ts +0 -5
  83. package/dist/files/file-io.d.ts.map +1 -1
  84. package/dist/files/native-packaging-layout.d.ts +12 -0
  85. package/dist/files/native-packaging-layout.d.ts.map +1 -0
  86. package/dist/files/native-packaging.d.ts +72 -0
  87. package/dist/files/native-packaging.d.ts.map +1 -0
  88. package/dist/files/read.d.ts +2 -2
  89. package/dist/files/read.d.ts.map +1 -1
  90. package/dist/index-1a6jtnd9.js +88 -0
  91. package/dist/index-269zqj8m.js +77 -0
  92. package/dist/{index-r34mvse1.js → index-27a2pj09.js} +1 -1
  93. package/dist/{index-r956rndn.js → index-2hyavjjj.js} +3 -3
  94. package/dist/{index-m2xqkv55.js → index-2vnh05w4.js} +83 -14
  95. package/dist/{index-pw99k9h5.js → index-43p08g7t.js} +1 -1
  96. package/dist/{index-xwq9j1sf.js → index-4e0ah203.js} +9 -7
  97. package/dist/{index-qapzv4p5.js → index-5z6jn897.js} +1 -1
  98. package/dist/{index-ahvsksw5.js → index-6d9xaxw1.js} +1 -1
  99. package/dist/{index-8zfsgar0.js → index-6drst9sg.js} +11 -5
  100. package/dist/{index-x87j7csh.js → index-77nxk6ay.js} +2 -2
  101. package/dist/{index-dnn9z96q.js → index-a0zzh8ak.js} +3 -3
  102. package/dist/{index-kmpzj1cq.js → index-a3wma1hk.js} +1 -1
  103. package/dist/{index-j8aw2c0e.js → index-bakgedh8.js} +35 -7
  104. package/dist/index-bhb950qs.js +1145 -0
  105. package/dist/{index-tgzhq07n.js → index-cvda079a.js} +1 -1
  106. package/dist/{index-n2gre124.js → index-fa4wg312.js} +4 -4
  107. package/dist/{index-zp4jpkp0.js → index-fk18k56k.js} +2 -2
  108. package/dist/{index-afjvjyck.js → index-g2j7pnak.js} +2 -134
  109. package/dist/index-g95fk660.js +116 -0
  110. package/dist/{index-r5c4fedv.js → index-h2fhjqy7.js} +2 -1
  111. package/dist/{index-sybedc8b.js → index-hbd32c6a.js} +23 -94
  112. package/dist/{index-fyphpknh.js → index-hp9d77wk.js} +1 -1
  113. package/dist/index-jd470k6q.js +109 -0
  114. package/dist/{index-b2bz0kdm.js → index-kb8styz3.js} +4 -3
  115. package/dist/{index-0c419yx7.js → index-kemm0ej5.js} +12 -9
  116. package/dist/{index-db2spkv1.js → index-ptn0g0gj.js} +3 -3
  117. package/dist/{index-w575evhq.js → index-r4r70zmh.js} +1 -1
  118. package/dist/{index-12m2v2a9.js → index-s4evrmp5.js} +1 -1
  119. package/dist/{index-1khy8y7n.js → index-t1sr4aks.js} +3 -3
  120. package/dist/{index-45tdvqa3.js → index-wh0ecvyk.js} +1 -1
  121. package/dist/{index-n53mswt3.js → index-x928baz2.js} +8 -1
  122. package/dist/index-yk4g8805.js +133 -0
  123. package/dist/{index-ygkka6zn.js → index-ywppsaby.js} +28 -5
  124. package/dist/{index-9853ymwb.js → index-za6j7nwy.js} +18 -4
  125. package/dist/internal/bounded-file-read.d.ts +41 -0
  126. package/dist/internal/bounded-file-read.d.ts.map +1 -0
  127. package/dist/internal/exclusive-lock.d.ts +2 -0
  128. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  129. package/dist/internal/with-exclusive-lock.d.ts +4 -2
  130. package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
  131. package/dist/telegram/broadcast-delivery.d.ts +23 -0
  132. package/dist/telegram/broadcast-delivery.d.ts.map +1 -0
  133. package/dist/telegram/broadcast-failure.d.ts +22 -0
  134. package/dist/telegram/broadcast-failure.d.ts.map +1 -0
  135. package/dist/telegram/broadcast-state.d.ts +6 -3
  136. package/dist/telegram/broadcast-state.d.ts.map +1 -1
  137. package/dist/telegram/broadcast.d.ts +10 -5
  138. package/dist/telegram/broadcast.d.ts.map +1 -1
  139. package/dist/tools/cli/format.d.ts.map +1 -1
  140. package/dist/tools/cli/invoke.d.ts +2 -2
  141. package/dist/tools/cli/invoke.d.ts.map +1 -1
  142. package/dist/tools/cli/publication-assets.d.ts +10 -0
  143. package/dist/tools/cli/publication-assets.d.ts.map +1 -0
  144. package/dist/tools/cli/publication-contract.d.ts +75 -0
  145. package/dist/tools/cli/publication-contract.d.ts.map +1 -0
  146. package/dist/tools/cli/publication-control.d.ts +4 -0
  147. package/dist/tools/cli/publication-control.d.ts.map +1 -0
  148. package/dist/tools/cli/publication-storage.d.ts +32 -0
  149. package/dist/tools/cli/publication-storage.d.ts.map +1 -0
  150. package/dist/tools/cli/publication.d.ts +35 -0
  151. package/dist/tools/cli/publication.d.ts.map +1 -0
  152. package/dist/tools/cli/update.d.ts.map +1 -1
  153. package/dist/tools/connections/define.d.ts.map +1 -1
  154. package/dist/tools/connections/error-projection.d.ts +4 -0
  155. package/dist/tools/connections/error-projection.d.ts.map +1 -0
  156. package/dist/tools/connections/errors.d.ts +24 -8
  157. package/dist/tools/connections/errors.d.ts.map +1 -1
  158. package/dist/tools/connections/limits.d.ts +11 -31
  159. package/dist/tools/connections/limits.d.ts.map +1 -1
  160. package/dist/tools/connections/mcp-client.d.ts +6 -4
  161. package/dist/tools/connections/mcp-client.d.ts.map +1 -1
  162. package/dist/tools/connections/mcp.d.ts.map +1 -1
  163. package/dist/tools/connections/openapi.d.ts.map +1 -1
  164. package/dist/tools/connections/operation-limits.d.ts +25 -0
  165. package/dist/tools/connections/operation-limits.d.ts.map +1 -0
  166. package/dist/tools/connections/sse-frames.d.ts +11 -2
  167. package/dist/tools/connections/sse-frames.d.ts.map +1 -1
  168. package/dist/tools/connections/sse-session.d.ts +15 -2
  169. package/dist/tools/connections/sse-session.d.ts.map +1 -1
  170. package/dist/tools/connections/types.d.ts +19 -1
  171. package/dist/tools/connections/types.d.ts.map +1 -1
  172. package/dist/tools/execute-result.d.ts.map +1 -1
  173. package/dist/tools/execute.d.ts.map +1 -1
  174. package/dist/tools/internal/observe-hook.d.ts +3 -0
  175. package/dist/tools/internal/observe-hook.d.ts.map +1 -0
  176. package/dist/tools/internal/surface-projector.d.ts +2 -1
  177. package/dist/tools/internal/surface-projector.d.ts.map +1 -1
  178. package/dist/tools/mcp/mount.d.ts +2 -2
  179. package/dist/tools/mcp/mount.d.ts.map +1 -1
  180. package/dist/tools/mcp/prepare.d.ts +4 -5
  181. package/dist/tools/mcp/prepare.d.ts.map +1 -1
  182. package/dist/tools/mcp/register.d.ts +4 -1
  183. package/dist/tools/mcp/register.d.ts.map +1 -1
  184. package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
  185. package/dist/tools/remote.d.ts.map +1 -1
  186. package/dist/tools/runtime-tool-execution.d.ts +65 -0
  187. package/dist/tools/runtime-tool-execution.d.ts.map +1 -0
  188. package/dist/tools/runtime-tool-mcp.d.ts +27 -0
  189. package/dist/tools/runtime-tool-mcp.d.ts.map +1 -0
  190. package/dist/tools/runtime-tool.d.ts +9 -60
  191. package/dist/tools/runtime-tool.d.ts.map +1 -1
  192. package/dist/tools/surface.d.ts +12 -14
  193. package/dist/tools/surface.d.ts.map +1 -1
  194. package/llms/application.2.txt +221 -44
  195. package/llms/application.3.txt +3 -1
  196. package/llms/application.txt +1 -1
  197. package/llms/cli.2.txt +144 -1
  198. package/llms/cli.txt +123 -104
  199. package/llms/contract.txt +1 -1
  200. package/llms/files-packaging.txt +27 -0
  201. package/llms/process.txt +63 -1
  202. package/llms/server.4.txt +4 -1
  203. package/llms/server.5.txt +1 -0
  204. package/llms/stitchkit.2.txt +14 -0
  205. package/llms/stitchkit.txt +6 -4
  206. package/llms/telegram.txt +64 -2
  207. package/llms/tools-connections.txt +3 -0
  208. package/llms/tools-mcp.txt +29 -0
  209. package/llms/tools.2.txt +59 -0
  210. package/llms/tools.3.txt +1 -0
  211. package/llms/tools.txt +11 -52
  212. package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
  213. package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
  214. package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.80.0.txt} +217 -140
  215. package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.81.0-0.104.0.txt} +154 -217
  216. package/llms/upgrading.txt +4 -4
  217. package/llms-full.txt +745 -60
  218. package/llms.txt +14 -12
  219. package/native-assets.json +8 -0
  220. package/package.json +12 -3
  221. package/dist/index-wykebpn9.js +0 -221
  222. package/llms/upgrading-0.37.0.txt +0 -176
  223. package/dist/{index-69nvze1q.js → index-pptyv13k.js} +3 -3
@@ -18,25 +18,40 @@ import {
18
18
  renderCliView2,
19
19
  pollUntilDone2,
20
20
  createCli2
21
- } from "../index-8zfsgar0.js";
21
+ } from "../index-6drst9sg.js";
22
22
  import {
23
23
  AppError2
24
- } from "../index-n53mswt3.js";
25
- import"../index-b2bz0kdm.js";
26
- import"../index-m2xqkv55.js";
24
+ } from "../index-x928baz2.js";
25
+ import"../index-kb8styz3.js";
26
+ import"../index-2vnh05w4.js";
27
27
  import"../index-ct75c3at.js";
28
- import"../index-x87j7csh.js";
28
+ import"../index-77nxk6ay.js";
29
29
  import"../index-yex7eg1v.js";
30
- import"../index-r956rndn.js";
30
+ import"../index-2hyavjjj.js";
31
+ import"../index-xqhede6x.js";
31
32
  import {
33
+ ManagedFileError2,
34
+ createManagedFileBoundary2
35
+ } from "../index-hbd32c6a.js";
36
+ import"../index-za6j7nwy.js";
37
+ import"../index-5csmzesf.js";
38
+ import"../index-v5ee8ath.js";
39
+ import {
40
+ withExclusiveLock2
41
+ } from "../index-g95fk660.js";
42
+ import {
43
+ writeFileAtomic2,
32
44
  writeFileAtomicSync2
33
- } from "../index-12m2v2a9.js";
34
- import"../index-dnn9z96q.js";
45
+ } from "../index-s4evrmp5.js";
46
+ import {
47
+ AtomicFilePublicationError2
48
+ } from "../index-8a9ksm91.js";
49
+ import"../index-a0zzh8ak.js";
50
+ import"../index-cvda079a.js";
35
51
  import {
36
52
  argumentsDigest
37
53
  } from "../index-r7a7r7ha.js";
38
54
  import"../index-7n68nmc6.js";
39
- import"../index-tgzhq07n.js";
40
55
  import {
41
56
  serializeCanonicalJson
42
57
  } from "../index-zsvzhjtk.js";
@@ -409,142 +424,378 @@ function missingMessage(name, path, config) {
409
424
  const hint = config.createHint ? ` — ${config.createHint(name, path)}` : "";
410
425
  return `profile "${name}" is not configured; expected ${path}${hint}`;
411
426
  }
412
- // src/tools/cli/stream.ts
427
+ // src/tools/cli/publication.ts
428
+ import { createHash as createHash3 } from "node:crypto";
429
+ import { lstat as lstat2, mkdtemp, rename, rm as rm2 } from "node:fs/promises";
430
+ import { join as join4 } from "node:path";
431
+ import { z as z5 } from "zod";
432
+
433
+ // src/tools/cli/publication-assets.ts
434
+ import { createHash } from "node:crypto";
435
+ import { Readable } from "node:stream";
436
+ import { setImmediate as nextTurn } from "node:timers/promises";
437
+ import { createGunzip, createGzip } from "node:zlib";
438
+
439
+ // src/tools/cli/publication-control.ts
440
+ function waitForPublication(operation, signal) {
441
+ return new Promise((resolve, reject) => {
442
+ const abort = () => {
443
+ signal.removeEventListener("abort", abort);
444
+ reject(signal.reason);
445
+ };
446
+ signal.addEventListener("abort", abort, { once: true });
447
+ operation.then((value) => {
448
+ signal.removeEventListener("abort", abort);
449
+ if (signal.aborted) {
450
+ if (value instanceof ReadableStream && !value.locked)
451
+ value.cancel(signal.reason).catch(() => {
452
+ return;
453
+ });
454
+ reject(signal.reason);
455
+ } else
456
+ resolve(value);
457
+ }, (error) => {
458
+ signal.removeEventListener("abort", abort);
459
+ reject(error);
460
+ });
461
+ if (signal.aborted)
462
+ abort();
463
+ });
464
+ }
465
+ async function withPublicationDeadline(timeoutMs, caller, body) {
466
+ caller?.throwIfAborted();
467
+ const controller = new AbortController;
468
+ const abort = () => controller.abort(caller?.reason);
469
+ caller?.addEventListener("abort", abort, { once: true });
470
+ const timer = setTimeout(() => controller.abort(new Error(`CLI publication exceeded ${timeoutMs} ms`)), timeoutMs);
471
+ try {
472
+ return await body(controller.signal);
473
+ } finally {
474
+ clearTimeout(timer);
475
+ caller?.removeEventListener("abort", abort);
476
+ }
477
+ }
478
+
479
+ // src/tools/cli/publication-assets.ts
480
+ async function collectCliBytes(source, maxBytes, signal) {
481
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)
482
+ throw new RangeError("CLI asset byte cap must be a positive safe integer");
483
+ const blocks = [];
484
+ let total = 0;
485
+ let filled = 0;
486
+ let block;
487
+ const consume = (chunk) => {
488
+ signal?.throwIfAborted();
489
+ if (!(chunk instanceof Uint8Array))
490
+ throw new TypeError("CLI asset stream must contain Uint8Array chunks");
491
+ if (total + chunk.byteLength > maxBytes)
492
+ throw new RangeError(`CLI asset exceeds ${maxBytes} bytes`);
493
+ for (let offset = 0;offset < chunk.byteLength; ) {
494
+ if (!block || filled === block.length) {
495
+ block = new Uint8Array(Math.min(64 * 1024, maxBytes - total));
496
+ blocks.push(block);
497
+ filled = 0;
498
+ }
499
+ const count = Math.min(chunk.byteLength - offset, block.length - filled);
500
+ block.set(chunk.subarray(offset, offset + count), filled);
501
+ filled += count;
502
+ total += count;
503
+ offset += count;
504
+ }
505
+ };
506
+ if (source instanceof Uint8Array)
507
+ consume(source);
508
+ else {
509
+ const reader = source.getReader();
510
+ let chunks = 0;
511
+ try {
512
+ for (;; ) {
513
+ const next = signal ? await waitForPublication(reader.read(), signal) : await reader.read();
514
+ if (next.done)
515
+ break;
516
+ consume(next.value);
517
+ if (++chunks % 128 === 0) {
518
+ if (signal)
519
+ await waitForPublication(nextTurn(), signal);
520
+ else
521
+ await nextTurn();
522
+ }
523
+ }
524
+ } catch (error) {
525
+ reader.cancel(error).catch(() => {
526
+ return;
527
+ });
528
+ throw error;
529
+ } finally {
530
+ reader.releaseLock();
531
+ }
532
+ }
533
+ signal?.throwIfAborted();
534
+ const bytes = new Uint8Array(total);
535
+ let offset = 0;
536
+ for (const chunk of blocks) {
537
+ const count = Math.min(chunk.length, total - offset);
538
+ bytes.set(chunk.subarray(0, count), offset);
539
+ offset += count;
540
+ }
541
+ return bytes;
542
+ }
543
+ async function transformAsset(bytes, kind, maxBytes, signal) {
544
+ const codec = kind === "gzip" ? createGzip() : createGunzip();
545
+ const output = Readable.toWeb(codec);
546
+ codec.end(bytes);
547
+ try {
548
+ return await collectCliBytes(output, maxBytes, signal);
549
+ } finally {
550
+ codec.destroy();
551
+ }
552
+ }
553
+ function gzipCliAsset(bytes, maxBytes, signal) {
554
+ return transformAsset(bytes, "gzip", maxBytes, signal);
555
+ }
556
+ async function decodeCliAsset(transferred, asset, maxBytes, signal) {
557
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || !Number.isSafeInteger(asset.size) || asset.size <= 0)
558
+ throw new RangeError("CLI asset size and cap must be positive safe integers");
559
+ if (asset.size > maxBytes)
560
+ throw new RangeError(`CLI asset declared size exceeds ${maxBytes} bytes`);
561
+ const bytes = asset.compression === "gzip" ? await transformAsset(transferred, "gunzip", asset.size, signal) : transferred;
562
+ if (bytes.length !== asset.size)
563
+ throw new Error(`CLI asset is ${bytes.length} bytes, manifest says ${asset.size}`);
564
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
565
+ if (sha256 !== asset.sha256)
566
+ throw new Error("CLI asset digest does not match the manifest (checksum mismatch) — refusing to install it");
567
+ return { bytes, sha256 };
568
+ }
569
+
570
+ // src/tools/cli/publication-contract.ts
571
+ import { isAbsolute } from "node:path";
413
572
  import { z as z3 } from "zod";
414
- var LineSchema = z3.object({
415
- id: z3.string().min(1),
416
- command: z3.string().min(1),
417
- args: z3.record(z3.string(), z3.unknown()).default({})
573
+ var positive = z3.int().positive().max(Number.MAX_SAFE_INTEGER);
574
+ var timer = positive.max(2147483647);
575
+ var component = z3.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/);
576
+ var PublicationTargetSchema = CliBuildTargetSchema.extend({
577
+ platform: component,
578
+ arch: component
418
579
  });
419
- async function* readStdinLines(source = process.stdin) {
420
- const decoder = new TextDecoder;
421
- let pending = "";
422
- for await (const chunk of source) {
423
- pending += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
424
- let newline = pending.indexOf(`
425
- `);
426
- while (newline !== -1) {
427
- const line = pending.slice(0, newline);
428
- pending = pending.slice(newline + 1);
429
- yield line.endsWith("\r") ? line.slice(0, -1) : line;
430
- newline = pending.indexOf(`
431
- `);
432
- }
580
+ var PublicationVersionSchema = z3.string().max(128).regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?(?:\+[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$/).refine((value) => {
581
+ const core = value.split(/[+-]/)[0];
582
+ if (!core || core.split(".").some((part) => !Number.isSafeInteger(Number(part))))
583
+ return false;
584
+ const prerelease = value.split("+")[0]?.split("-").slice(1).join("-");
585
+ return !prerelease || prerelease.split(".").every((part) => !/^\d+$/.test(part) || part === "0" || !part.startsWith("0"));
586
+ }, "version must use finite canonical semver numbers");
587
+ var limitsSchema = z3.object({
588
+ maxTargets: positive.default(16),
589
+ maxManifestBytes: positive.default(256 * 1024),
590
+ maxAssetBytes: positive.default(256 * 1024 * 1024),
591
+ maxCompressedBytes: positive.default(256 * 1024 * 1024),
592
+ maxDirectoryEntries: positive.default(128),
593
+ maxStoredVersions: positive.min(3).default(16),
594
+ lockTimeoutMs: timer.default(1e4),
595
+ timeoutMs: timer.default(600000)
596
+ });
597
+ var dataSchema = CliBuildManifestSchema.pick({
598
+ name: true,
599
+ version: true,
600
+ commit: true
601
+ }).extend({
602
+ name: component,
603
+ version: PublicationVersionSchema,
604
+ commit: z3.string().min(1).max(128),
605
+ storageRoot: z3.string().min(1).refine(isAbsolute, "storageRoot must be absolute"),
606
+ baseUrl: z3.url().refine((raw) => {
607
+ const url = new URL(raw);
608
+ return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash;
609
+ }, "baseUrl must be an http(s) directory URL without credentials/query/fragment"),
610
+ targets: z3.array(PublicationTargetSchema).min(1),
611
+ limits: limitsSchema.prefault({}),
612
+ retention: positive.min(2).default(2)
613
+ });
614
+ var phaseSchema = z3.enum(["prepare", "build", "commit", "promote"]);
615
+ function publicationData(options) {
616
+ const data = dataSchema.parse(options);
617
+ if (data.targets.length > data.limits.maxTargets)
618
+ throw new RangeError("CLI publication target cap exceeded");
619
+ if (data.targets.length + 1 > data.limits.maxDirectoryEntries)
620
+ throw new RangeError("CLI version layout exceeds directory entry cap");
621
+ if (data.retention >= data.limits.maxStoredVersions)
622
+ throw new RangeError("retention must leave one recovery slot below maxStoredVersions");
623
+ const names = data.targets.map((target) => assetFilename(data.name, target));
624
+ if (new Set(names).size !== names.length)
625
+ throw new TypeError("CLI publication targets must be unique");
626
+ if (typeof options.admit !== "function" || typeof options.build !== "function")
627
+ throw new TypeError("CLI publication needs build and admit callbacks");
628
+ return data;
629
+ }
630
+ function assetFilename(name, target) {
631
+ return `${name}-${target.platform}-${target.arch}.gz`;
632
+ }
633
+ function assetUrl(baseUrl, version, filename) {
634
+ return new URL(`${encodeURIComponent(version)}/${encodeURIComponent(filename)}`, `${baseUrl.replace(/\/+$/, "")}/`).toString();
635
+ }
636
+
637
+ // src/tools/cli/publication-storage.ts
638
+ import { lstat, mkdir, open, opendir, realpath, rm } from "node:fs/promises";
639
+ import { join as join2, resolve } from "node:path";
640
+ import { z as z4 } from "zod";
641
+ async function directoryGeneration(path) {
642
+ const info = await lstat(path);
643
+ if (!info.isDirectory() || info.isSymbolicLink())
644
+ throw new Error("CLI storage directory must not be a link or non-directory");
645
+ return { dev: info.dev, ino: info.ino };
646
+ }
647
+ async function assertDirectory(path, generation) {
648
+ const current = await directoryGeneration(path);
649
+ if (current.dev !== generation.dev || current.ino !== generation.ino)
650
+ throw new Error("CLI storage directory generation changed");
651
+ }
652
+ async function bindPublicationStorage(data, signal) {
653
+ signal.throwIfAborted();
654
+ if (process.platform === "win32")
655
+ throw new Error("CLI publication needs directory durability, unsupported on Windows");
656
+ const requested = resolve(data.storageRoot);
657
+ try {
658
+ await mkdir(requested, { mode: 448 });
659
+ } catch (error) {
660
+ if (!(error instanceof Error && ("code" in error) && error.code === "EEXIST"))
661
+ throw error;
433
662
  }
434
- if (pending !== "")
435
- yield pending.endsWith("\r") ? pending.slice(0, -1) : pending;
663
+ await directoryGeneration(requested);
664
+ const root = await realpath(requested);
665
+ const generation = await directoryGeneration(root);
666
+ const files = await createManagedFileBoundary2({
667
+ root,
668
+ maxReadBytes: Math.max(data.limits.maxManifestBytes, data.limits.maxCompressedBytes),
669
+ maxWriteBytes: Math.max(data.limits.maxManifestBytes, data.limits.maxCompressedBytes)
670
+ });
671
+ return {
672
+ root,
673
+ files,
674
+ async assertRoot() {
675
+ await assertDirectory(requested, generation);
676
+ await assertDirectory(root, generation);
677
+ }
678
+ };
436
679
  }
437
- function parseLine(line) {
438
- let parsed;
680
+ async function directoryNames(path, maxEntries, signal) {
681
+ const names = [];
682
+ const directory = await opendir(path);
439
683
  try {
440
- parsed = safeJsonParse(line);
441
- } catch {
442
- return { ok: false, answer: malformed("the line is not JSON") };
684
+ for await (const entry of directory) {
685
+ signal.throwIfAborted();
686
+ names.push(entry.name);
687
+ if (names.length > maxEntries)
688
+ throw new RangeError("CLI publication directory entry cap exceeded");
689
+ }
690
+ } finally {
691
+ await directory.close().catch(() => {
692
+ return;
693
+ });
443
694
  }
444
- const result = LineSchema.safeParse(parsed);
445
- if (!result.success) {
446
- const id = isRecord(parsed) && typeof parsed.id === "string" ? parsed.id : undefined;
695
+ return names.sort();
696
+ }
697
+ async function readPublicationManifest(storage, path, data, signal) {
698
+ try {
699
+ const read = await storage.files.read(path, {
700
+ rejectSymlinks: true,
701
+ singleLink: true,
702
+ stable: true,
703
+ maxBytes: data.limits.maxManifestBytes,
704
+ signal
705
+ });
447
706
  return {
448
- ok: false,
449
- answer: {
450
- ...malformed("the line needs `id` and `command`"),
451
- ...id !== undefined && { id }
452
- }
707
+ manifest: CliBuildManifestSchema.parse(JSON.parse(new TextDecoder().decode(read.bytes))),
708
+ bytes: read.bytes
453
709
  };
710
+ } catch (error) {
711
+ if (error instanceof ManagedFileError2 && error.code === "FILE_NOT_FOUND")
712
+ return;
713
+ throw error;
454
714
  }
455
- return { ok: true, value: result.data };
456
715
  }
457
- function malformed(message) {
458
- return {
459
- id: "",
460
- ok: false,
461
- exitCode: 2,
462
- error: { code: "BAD_REQUEST", message }
463
- };
716
+ async function verifyPublicationVersion(storage, record, data, signal, exactTargets) {
717
+ const { manifest } = record;
718
+ PublicationVersionSchema.parse(manifest.version);
719
+ if (manifest.name !== data.name || manifest.assets.length === 0 || manifest.assets.length > data.limits.maxTargets)
720
+ throw new Error("CLI version identity or target count is invalid");
721
+ const directory = join2(storage.root, manifest.version);
722
+ const generation = await directoryGeneration(directory);
723
+ const expected = manifest.assets.map((asset) => assetFilename(data.name, asset));
724
+ if (new Set(expected).size !== expected.length || manifest.assets.some((asset) => !PublicationTargetSchema.safeParse(asset).success || asset.compression !== "gzip"))
725
+ throw new Error("CLI version targets or compression do not match publisher layout");
726
+ if (exactTargets) {
727
+ const requested = data.targets.map((target) => assetFilename(data.name, target)).sort();
728
+ if (JSON.stringify(requested) !== JSON.stringify([...expected].sort()))
729
+ throw new Error("CLI publication target set differs from existing version");
730
+ }
731
+ const names = await directoryNames(directory, data.limits.maxDirectoryEntries, signal);
732
+ if (JSON.stringify(names) !== JSON.stringify([...expected, "manifest.json"].sort()))
733
+ throw new Error("CLI version has unknown or missing files");
734
+ const saved = await readPublicationManifest(storage, `${manifest.version}/manifest.json`, data, signal);
735
+ if (!saved || !Buffer.from(saved.bytes).equals(Buffer.from(record.bytes)))
736
+ throw new Error("CLI version manifest differs from publication");
737
+ for (const asset of manifest.assets) {
738
+ const file = assetFilename(data.name, asset);
739
+ const url = new URL(asset.url);
740
+ const suffix = `/${encodeURIComponent(manifest.version)}/${encodeURIComponent(file)}`;
741
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || !url.pathname.endsWith(suffix) || url.search || url.hash || exactTargets && asset.url !== assetUrl(data.baseUrl, manifest.version, file))
742
+ throw new Error("CLI asset URL differs from owned version layout");
743
+ const archived = await storage.files.read(`${manifest.version}/${file}`, {
744
+ rejectSymlinks: true,
745
+ singleLink: true,
746
+ stable: true,
747
+ maxBytes: data.limits.maxCompressedBytes,
748
+ signal
749
+ });
750
+ await decodeCliAsset(archived.bytes, asset, data.limits.maxAssetBytes, signal);
751
+ }
752
+ await assertDirectory(directory, generation);
464
753
  }
465
- async function runCliStream(invoker, lines, onAnswer, hooks = {}) {
466
- for await (const line of lines) {
467
- if (line.trim() === "")
754
+ async function publicationHistory(storage, data, signal) {
755
+ const history = [];
756
+ for (const name of await directoryNames(storage.root, data.limits.maxDirectoryEntries, signal)) {
757
+ if (!PublicationVersionSchema.safeParse(name).success)
468
758
  continue;
469
- const parsed = parseLine(line);
470
- if (!parsed.ok) {
471
- onAnswer(parsed.answer);
759
+ const path = join2(storage.root, name);
760
+ const info = await lstat(path);
761
+ if (!info.isDirectory() || info.isSymbolicLink())
472
762
  continue;
763
+ let record;
764
+ try {
765
+ record = await readPublicationManifest(storage, `${name}/manifest.json`, data, signal);
766
+ } catch (error) {
767
+ if (error instanceof SyntaxError || error instanceof z4.ZodError)
768
+ continue;
769
+ throw error;
473
770
  }
474
- const { id, command, args } = parsed.value;
475
- const answered = hooks.before?.(id, { command, args });
476
- if (answered) {
477
- onAnswer(answered);
771
+ if (!record || record.manifest.name !== data.name || record.manifest.version !== name)
478
772
  continue;
479
- }
480
- const outcome = await invoker.invoke(command, args);
481
- hooks.after?.(id, { command, args }, outcome);
482
- onAnswer({ id, ...outcome });
773
+ await verifyPublicationVersion(storage, record, data, signal, false);
774
+ history.push({ ...record, directory: path });
775
+ if (history.length > data.limits.maxStoredVersions)
776
+ throw new RangeError("CLI publication stored version cap exceeded");
483
777
  }
778
+ return history;
484
779
  }
485
- function defineCliStreamCommand(config) {
486
- return defineCliCommand2({
487
- name: config.name ?? "stream",
488
- description: config.description ?? "Run one operation per JSON line of stdin, answering each",
489
- input: z3.object({}),
490
- handler: async (context) => {
491
- const invoker = typeof config.invoker === "function" ? await config.invoker() : config.invoker;
492
- await runCliStream(invoker, (config.readLines ?? readStdinLines)(), (answer) => {
493
- context.stdout(`${JSON.stringify(answer)}
494
- `);
495
- });
496
- }
497
- });
780
+ async function syncPublicationDirectory(path) {
781
+ const handle = await open(path, "r");
782
+ try {
783
+ await handle.sync();
784
+ } finally {
785
+ await handle.close();
786
+ }
498
787
  }
499
- function defineCliBatchCommand(config) {
500
- return defineCliCommand2({
501
- name: config.name ?? "batch",
502
- description: config.description ?? "Run one operation per JSON line of stdin, resuming from a checkpoint",
503
- input: z3.object({
504
- checkpoint: z3.string().optional()
505
- }),
506
- handler: async (context) => {
507
- const invoker = typeof config.invoker === "function" ? await config.invoker() : config.invoker;
508
- const path = context.input.checkpoint ?? config.checkpointPath ?? ".stitchkit-batch.json";
509
- const checkpoint = readCliCheckpoint(path);
510
- await runCliStream(invoker, (config.readLines ?? readStdinLines)(), (answer) => {
511
- context.stdout(`${JSON.stringify(answer)}
512
- `);
513
- }, {
514
- before: (id, line) => {
515
- const digest = checkpoint.digestOf(line);
516
- const recorded = checkpoint.entries[id];
517
- if (!recorded)
518
- return;
519
- if (recorded.digest !== digest) {
520
- return {
521
- id,
522
- ok: false,
523
- exitCode: 2,
524
- error: {
525
- code: "CONFLICT",
526
- message: "this id was already run with different content — change the id or start a new checkpoint"
527
- }
528
- };
529
- }
530
- const replay = isRecord(recorded.answer) ? recorded.answer : {};
531
- return { ...replay, id, replayed: true };
532
- },
533
- after: (id, line, outcome) => {
534
- if (!outcome.ok)
535
- return;
536
- checkpoint.record(id, checkpoint.digestOf(line), outcome);
537
- writeCliCheckpoint(path, checkpoint);
538
- }
539
- });
540
- }
541
- });
788
+ async function removePublicationVersion(storage, version, data, signal) {
789
+ await storage.assertRoot();
790
+ await verifyPublicationVersion(storage, version, data, signal, false);
791
+ await rm(version.directory, { recursive: true });
792
+ await syncPublicationDirectory(storage.root);
542
793
  }
794
+
543
795
  // src/tools/cli/update.ts
544
- import { createHash, randomBytes } from "node:crypto";
796
+ import { createHash as createHash2, randomBytes } from "node:crypto";
545
797
  import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync2, unlinkSync } from "node:fs";
546
- import { basename, dirname, extname, join as join2 } from "node:path";
547
- import { gunzipSync } from "node:zlib";
798
+ import { basename, dirname, extname, join as join3 } from "node:path";
548
799
  var DEFAULT_CHECK_TIMEOUT_MS = 2000;
549
800
  var DEFAULT_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
550
801
  var DEFAULT_DOWNLOAD_TIMEOUT_MS = 120000;
@@ -576,7 +827,33 @@ function compareCliVersions(left, right) {
576
827
  return 1;
577
828
  if (b.prerelease === undefined)
578
829
  return -1;
579
- return a.prerelease < b.prerelease ? -1 : 1;
830
+ const leftParts = a.prerelease.split(".");
831
+ const rightParts = b.prerelease.split(".");
832
+ for (let index = 0;index < Math.max(leftParts.length, rightParts.length); index++) {
833
+ const leftPart = leftParts[index];
834
+ const rightPart = rightParts[index];
835
+ if (leftPart === rightPart)
836
+ continue;
837
+ if (leftPart === undefined)
838
+ return -1;
839
+ if (rightPart === undefined)
840
+ return 1;
841
+ const leftNumeric = /^\d+$/.test(leftPart);
842
+ const rightNumeric = /^\d+$/.test(rightPart);
843
+ if (leftNumeric !== rightNumeric)
844
+ return leftNumeric ? -1 : 1;
845
+ if (leftNumeric) {
846
+ const leftDigits = leftPart.replace(/^0+(?=\d)/, "");
847
+ const rightDigits = rightPart.replace(/^0+(?=\d)/, "");
848
+ if (leftDigits.length !== rightDigits.length)
849
+ return leftDigits.length < rightDigits.length ? -1 : 1;
850
+ if (leftDigits === rightDigits)
851
+ continue;
852
+ return leftDigits < rightDigits ? -1 : 1;
853
+ }
854
+ return leftPart < rightPart ? -1 : 1;
855
+ }
856
+ return 0;
580
857
  }
581
858
  async function checkCliUpdate(config) {
582
859
  const now = config.now ?? Date.now();
@@ -656,14 +933,7 @@ async function applyCliUpdate(config) {
656
933
  const transferred = await readCapped(response, config.maxBytes ?? DEFAULT_MAX_ASSET_BYTES);
657
934
  if (!transferred)
658
935
  throw new Error("[stitchkit] update download exceeded the size ceiling");
659
- const bytes = config.asset.compression === "gzip" ? gunzipSync(transferred, { maxOutputLength: config.asset.size }) : transferred;
660
- if (bytes.length !== config.asset.size) {
661
- throw new Error(`[stitchkit] update is ${bytes.length} bytes, manifest says ${config.asset.size} — refusing to install it`);
662
- }
663
- const sha256 = createHash("sha256").update(bytes).digest("hex");
664
- if (sha256 !== config.asset.sha256) {
665
- throw new Error(`[stitchkit] update digest ${sha256} does not match the manifest — refusing to install it`);
666
- }
936
+ const { bytes, sha256 } = await decodeCliAsset(transferred, config.asset, config.maxBytes ?? DEFAULT_MAX_ASSET_BYTES);
667
937
  if (config.verify)
668
938
  await verifyCandidate(target, bytes, config.verify, config.verifyTimeoutMs);
669
939
  const backup = config.backupPath ? keepReplacedBinary(target, config.backupPath) : undefined;
@@ -679,7 +949,7 @@ async function verifyCandidate(target, bytes, verify, timeoutMs = DEFAULT_VERIFY
679
949
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
680
950
  throw new RangeError("[stitchkit] update: verifyTimeoutMs must be a positive number");
681
951
  }
682
- const candidate = join2(dirname(target), `.${basename(target)}.candidate-${randomBytes(8).toString("hex")}${extname(target)}`);
952
+ const candidate = join3(dirname(target), `.${basename(target)}.candidate-${randomBytes(8).toString("hex")}${extname(target)}`);
683
953
  writeFileAtomicSync2(candidate, bytes, { mode: 493 });
684
954
  const controller = new AbortController;
685
955
  let timer;
@@ -712,7 +982,7 @@ function keepReplacedBinary(target, backupPath) {
712
982
  return;
713
983
  const previous = readFileSync3(target);
714
984
  writeFileAtomicSync2(backupPath, previous, { mode: statSync2(target).mode & 511 });
715
- return { path: backupPath, sha256: createHash("sha256").update(previous).digest("hex") };
985
+ return { path: backupPath, sha256: createHash2("sha256").update(previous).digest("hex") };
716
986
  }
717
987
  function rollbackCliUpdate(config) {
718
988
  const target = config.targetPath ?? process.execPath;
@@ -720,13 +990,363 @@ function rollbackCliUpdate(config) {
720
990
  throw new Error(`[stitchkit] rollback: no backup at ${config.backupPath}`);
721
991
  }
722
992
  const bytes = readFileSync3(config.backupPath);
723
- const sha256 = createHash("sha256").update(bytes).digest("hex");
993
+ const sha256 = createHash2("sha256").update(bytes).digest("hex");
724
994
  if (sha256 !== config.expectedSha256) {
725
995
  throw new Error(`[stitchkit] rollback: backup digest ${sha256} does not match the expected ${config.expectedSha256} — refusing to restore it`);
726
996
  }
727
997
  writeFileAtomicSync2(target, bytes, { mode: statSync2(config.backupPath).mode & 511 });
728
998
  return { path: target, bytes: bytes.length, sha256 };
729
999
  }
1000
+
1001
+ // src/tools/cli/publication.ts
1002
+ var resultSchema = z5.object({
1003
+ outcome: z5.enum(["published", "existing"]),
1004
+ manifest: CliBuildManifestSchema
1005
+ });
1006
+ function trustPublication(manifest, options) {
1007
+ const verdict = verifyCliManifest(manifest, manifest.signature, options.trust);
1008
+ if (!cliSignatureAccepted(verdict))
1009
+ throw new Error(`CLI publication manifest signature ${verdict}`);
1010
+ if (options.signing && !manifest.signature)
1011
+ throw new Error("CLI publication requires a signed manifest");
1012
+ }
1013
+ function sameManifest(left, right) {
1014
+ return left === undefined ? right === undefined : right !== undefined && Buffer.from(left.bytes).equals(Buffer.from(right.bytes));
1015
+ }
1016
+ async function admission(options, data, phase, signal) {
1017
+ signal.throwIfAborted();
1018
+ await waitForPublication(Promise.resolve().then(() => {
1019
+ signal.throwIfAborted();
1020
+ return options.admit({
1021
+ phase,
1022
+ identity: Object.freeze({
1023
+ name: data.name,
1024
+ version: data.version,
1025
+ commit: data.commit
1026
+ }),
1027
+ signal
1028
+ });
1029
+ }), signal);
1030
+ }
1031
+ async function unchanged(storage, prior, data, lock, signal) {
1032
+ signal.throwIfAborted();
1033
+ await lock.assertHeld();
1034
+ await storage.assertRoot();
1035
+ if (!sameManifest(prior, await readPublicationManifest(storage, "manifest.json", data, signal)))
1036
+ throw new Error("CLI public manifest changed during publication");
1037
+ }
1038
+ function newest(versions) {
1039
+ return [...versions].sort((a, b) => compareCliVersions(b.manifest.version, a.manifest.version) ?? 0);
1040
+ }
1041
+ async function retainVersions(storage, history, keep, count, data, lock, signal) {
1042
+ const retained = newest(history).filter((item) => !keep.has(item.manifest.version));
1043
+ const spare = Math.max(0, count - keep.size);
1044
+ for (const version of retained.slice(spare)) {
1045
+ await lock.assertHeld();
1046
+ await removePublicationVersion(storage, version, data, signal);
1047
+ }
1048
+ }
1049
+ async function promote(storage, record, prior, data, options, lock, signal) {
1050
+ await admission(options, data, "promote", signal);
1051
+ await verifyPublicationVersion(storage, record, data, signal, true);
1052
+ trustPublication(record.manifest, options);
1053
+ await unchanged(storage, prior, data, lock, signal);
1054
+ const names = await directoryNames(storage.root, data.limits.maxDirectoryEntries, signal);
1055
+ if (names.length + 1 > data.limits.maxDirectoryEntries)
1056
+ throw new RangeError("CLI publication directory entry cap leaves no pointer space");
1057
+ signal.throwIfAborted();
1058
+ await writeFileAtomic2(join4(storage.root, "manifest.json"), record.bytes, {
1059
+ mode: 420,
1060
+ durability: "directory"
1061
+ });
1062
+ const current = await readPublicationManifest(storage, "manifest.json", data, signal);
1063
+ if (!sameManifest(record, current))
1064
+ throw new Error("CLI public manifest readback differs after promotion");
1065
+ }
1066
+ async function buildVersion(storage, data, options, lock, prior, signal) {
1067
+ const names = await directoryNames(storage.root, data.limits.maxDirectoryEntries, signal);
1068
+ if (names.length + 2 > data.limits.maxDirectoryEntries)
1069
+ throw new RangeError("CLI publication directory entry cap leaves no commit space");
1070
+ const staged = await mkdtemp(join4(storage.root, ".publish-"));
1071
+ const generation = await directoryGeneration(staged);
1072
+ let committed = false;
1073
+ try {
1074
+ const files = await createManagedFileBoundary2({
1075
+ root: staged,
1076
+ maxWriteBytes: Math.max(data.limits.maxCompressedBytes, data.limits.maxManifestBytes)
1077
+ });
1078
+ const stamp = Object.freeze({
1079
+ version: data.version,
1080
+ commit: data.commit,
1081
+ builtAt: new Date().toISOString()
1082
+ });
1083
+ const assets = [];
1084
+ for (const target of data.targets) {
1085
+ await admission(options, data, "build", signal);
1086
+ signal.throwIfAborted();
1087
+ const source = await waitForPublication(Promise.resolve().then(() => {
1088
+ signal.throwIfAborted();
1089
+ return options.build({ target: Object.freeze({ ...target }), stamp, signal });
1090
+ }), signal);
1091
+ if (!(source instanceof Uint8Array) && !(source instanceof ReadableStream))
1092
+ throw new TypeError("CLI builder must return bytes or a stream");
1093
+ const bytes = await collectCliBytes(source, data.limits.maxAssetBytes, signal);
1094
+ if (bytes.byteLength === 0)
1095
+ throw new Error("CLI asset must not be empty");
1096
+ const archived = await gzipCliAsset(bytes, data.limits.maxCompressedBytes, signal);
1097
+ await lock.assertHeld();
1098
+ await storage.assertRoot();
1099
+ await assertDirectory(staged, generation);
1100
+ const file = assetFilename(data.name, target);
1101
+ await files.write(file, archived, { durable: true, signal });
1102
+ assets.push({
1103
+ ...target,
1104
+ compression: "gzip",
1105
+ size: bytes.byteLength,
1106
+ sha256: createHash3("sha256").update(bytes).digest("hex"),
1107
+ url: assetUrl(data.baseUrl, data.version, file)
1108
+ });
1109
+ }
1110
+ const manifest = CliBuildManifestSchema.parse({ name: data.name, ...stamp, assets });
1111
+ if (options.signing)
1112
+ manifest.signature = signCliManifest(manifest, options.signing);
1113
+ trustPublication(manifest, options);
1114
+ const bytes = new TextEncoder().encode(`${JSON.stringify(manifest)}
1115
+ `);
1116
+ if (bytes.byteLength > data.limits.maxManifestBytes)
1117
+ throw new RangeError("CLI publication manifest byte cap exceeded");
1118
+ await assertDirectory(staged, generation);
1119
+ await files.write("manifest.json", bytes, {
1120
+ durable: true,
1121
+ maxBytes: data.limits.maxManifestBytes,
1122
+ signal
1123
+ });
1124
+ await admission(options, data, "commit", signal);
1125
+ await unchanged(storage, prior, data, lock, signal);
1126
+ await assertDirectory(staged, generation);
1127
+ try {
1128
+ await lstat2(join4(storage.root, data.version));
1129
+ throw new Error("CLI version directory already exists");
1130
+ } catch (error) {
1131
+ if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
1132
+ throw error;
1133
+ }
1134
+ signal.throwIfAborted();
1135
+ await rename(staged, join4(storage.root, data.version));
1136
+ committed = true;
1137
+ await syncPublicationDirectory(storage.root);
1138
+ return { manifest, bytes };
1139
+ } catch (error) {
1140
+ if (!committed) {
1141
+ try {
1142
+ await storage.assertRoot();
1143
+ await assertDirectory(staged, generation);
1144
+ await rm2(staged, { recursive: true });
1145
+ } catch (cleanupError) {
1146
+ throw new AggregateError([error, cleanupError], "CLI publication failed and safe staging cleanup was refused");
1147
+ }
1148
+ }
1149
+ if (committed && !(error instanceof AtomicFilePublicationError2))
1150
+ throw new Error("CLI version committed; publication outcome must be checked", {
1151
+ cause: error
1152
+ });
1153
+ throw error;
1154
+ }
1155
+ }
1156
+ async function publishLocked(storage, data, options, lock, signal) {
1157
+ await lock.assertHeld();
1158
+ await admission(options, data, "prepare", signal);
1159
+ const prior = await readPublicationManifest(storage, "manifest.json", data, signal);
1160
+ if (prior && prior.manifest.name !== data.name)
1161
+ throw new Error("CLI public manifest belongs to another CLI");
1162
+ const history = await publicationHistory(storage, data, signal);
1163
+ const candidate = {
1164
+ name: data.name,
1165
+ version: data.version,
1166
+ commit: data.commit,
1167
+ builtAt: new Date().toISOString(),
1168
+ assets: []
1169
+ };
1170
+ assertCliPublishable([...prior ? [prior.manifest] : [], ...history.map((item) => item.manifest)], candidate);
1171
+ if (prior) {
1172
+ await verifyPublicationVersion(storage, prior, data, signal, prior.manifest.version === data.version);
1173
+ trustPublication(prior.manifest, options);
1174
+ const order = compareCliVersions(data.version, prior.manifest.version) ?? -1;
1175
+ if (order < 0 || order === 0 && data.version !== prior.manifest.version)
1176
+ throw new Error("CLI publication downgrade or non-advancing version refused");
1177
+ if (prior.manifest.version === data.version) {
1178
+ await admission(options, data, "promote", signal);
1179
+ await verifyPublicationVersion(storage, prior, data, signal, true);
1180
+ await unchanged(storage, prior, data, lock, signal);
1181
+ return { outcome: "existing", manifest: prior.manifest };
1182
+ }
1183
+ }
1184
+ let record = history.find((item) => item.manifest.version === data.version);
1185
+ if (record) {
1186
+ await verifyPublicationVersion(storage, record, data, signal, true);
1187
+ trustPublication(record.manifest, options);
1188
+ } else {
1189
+ const protect = new Set(prior ? [prior.manifest.version] : []);
1190
+ const previous = newest(history).find((item) => prior && (compareCliVersions(item.manifest.version, prior.manifest.version) ?? 0) < 0);
1191
+ if (previous)
1192
+ protect.add(previous.manifest.version);
1193
+ await retainVersions(storage, history, protect, data.limits.maxStoredVersions - 1, data, lock, signal);
1194
+ record = {
1195
+ ...await buildVersion(storage, data, options, lock, prior, signal),
1196
+ directory: join4(storage.root, data.version)
1197
+ };
1198
+ }
1199
+ try {
1200
+ await promote(storage, record, prior, data, options, lock, signal);
1201
+ const updated = await publicationHistory(storage, data, signal);
1202
+ await retainVersions(storage, updated, new Set([data.version, ...prior ? [prior.manifest.version] : []]), data.retention, data, lock, signal);
1203
+ } catch (error) {
1204
+ if (error instanceof AtomicFilePublicationError2)
1205
+ throw error;
1206
+ throw new Error("CLI version committed; publication outcome must be checked", {
1207
+ cause: error
1208
+ });
1209
+ }
1210
+ return { outcome: "published", manifest: record.manifest };
1211
+ }
1212
+ async function publishCli(options) {
1213
+ const data = publicationData(options);
1214
+ return withPublicationDeadline(data.limits.timeoutMs, options.signal, async (signal) => {
1215
+ const storage = await bindPublicationStorage(data, signal);
1216
+ return withExclusiveLock2(join4(storage.root, ".publication.lock"), (lock) => publishLocked(storage, data, options, lock, signal), { timeoutMs: data.limits.lockTimeoutMs, signal, label: "CLI publication" });
1217
+ });
1218
+ }
1219
+ // src/tools/cli/stream.ts
1220
+ import { z as z6 } from "zod";
1221
+ var LineSchema = z6.object({
1222
+ id: z6.string().min(1),
1223
+ command: z6.string().min(1),
1224
+ args: z6.record(z6.string(), z6.unknown()).default({})
1225
+ });
1226
+ async function* readStdinLines(source = process.stdin) {
1227
+ const decoder = new TextDecoder;
1228
+ let pending = "";
1229
+ for await (const chunk of source) {
1230
+ pending += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
1231
+ let newline = pending.indexOf(`
1232
+ `);
1233
+ while (newline !== -1) {
1234
+ const line = pending.slice(0, newline);
1235
+ pending = pending.slice(newline + 1);
1236
+ yield line.endsWith("\r") ? line.slice(0, -1) : line;
1237
+ newline = pending.indexOf(`
1238
+ `);
1239
+ }
1240
+ }
1241
+ if (pending !== "")
1242
+ yield pending.endsWith("\r") ? pending.slice(0, -1) : pending;
1243
+ }
1244
+ function parseLine(line) {
1245
+ let parsed;
1246
+ try {
1247
+ parsed = safeJsonParse(line);
1248
+ } catch {
1249
+ return { ok: false, answer: malformed("the line is not JSON") };
1250
+ }
1251
+ const result = LineSchema.safeParse(parsed);
1252
+ if (!result.success) {
1253
+ const id = isRecord(parsed) && typeof parsed.id === "string" ? parsed.id : undefined;
1254
+ return {
1255
+ ok: false,
1256
+ answer: {
1257
+ ...malformed("the line needs `id` and `command`"),
1258
+ ...id !== undefined && { id }
1259
+ }
1260
+ };
1261
+ }
1262
+ return { ok: true, value: result.data };
1263
+ }
1264
+ function malformed(message) {
1265
+ return {
1266
+ id: "",
1267
+ ok: false,
1268
+ exitCode: 2,
1269
+ error: { code: "BAD_REQUEST", message }
1270
+ };
1271
+ }
1272
+ async function runCliStream(invoker, lines, onAnswer, hooks = {}) {
1273
+ for await (const line of lines) {
1274
+ if (line.trim() === "")
1275
+ continue;
1276
+ const parsed = parseLine(line);
1277
+ if (!parsed.ok) {
1278
+ onAnswer(parsed.answer);
1279
+ continue;
1280
+ }
1281
+ const { id, command, args } = parsed.value;
1282
+ const answered = hooks.before?.(id, { command, args });
1283
+ if (answered) {
1284
+ onAnswer(answered);
1285
+ continue;
1286
+ }
1287
+ const outcome = await invoker.invoke(command, args);
1288
+ hooks.after?.(id, { command, args }, outcome);
1289
+ onAnswer({ id, ...outcome });
1290
+ }
1291
+ }
1292
+ function defineCliStreamCommand(config) {
1293
+ return defineCliCommand2({
1294
+ name: config.name ?? "stream",
1295
+ description: config.description ?? "Run one operation per JSON line of stdin, answering each",
1296
+ input: z6.object({}),
1297
+ handler: async (context) => {
1298
+ const invoker = typeof config.invoker === "function" ? await config.invoker() : config.invoker;
1299
+ await runCliStream(invoker, (config.readLines ?? readStdinLines)(), (answer) => {
1300
+ context.stdout(`${JSON.stringify(answer)}
1301
+ `);
1302
+ });
1303
+ }
1304
+ });
1305
+ }
1306
+ function defineCliBatchCommand(config) {
1307
+ return defineCliCommand2({
1308
+ name: config.name ?? "batch",
1309
+ description: config.description ?? "Run one operation per JSON line of stdin, resuming from a checkpoint",
1310
+ input: z6.object({
1311
+ checkpoint: z6.string().optional()
1312
+ }),
1313
+ handler: async (context) => {
1314
+ const invoker = typeof config.invoker === "function" ? await config.invoker() : config.invoker;
1315
+ const path = context.input.checkpoint ?? config.checkpointPath ?? ".stitchkit-batch.json";
1316
+ const checkpoint = readCliCheckpoint(path);
1317
+ await runCliStream(invoker, (config.readLines ?? readStdinLines)(), (answer) => {
1318
+ context.stdout(`${JSON.stringify(answer)}
1319
+ `);
1320
+ }, {
1321
+ before: (id, line) => {
1322
+ const digest = checkpoint.digestOf(line);
1323
+ const recorded = checkpoint.entries[id];
1324
+ if (!recorded)
1325
+ return;
1326
+ if (recorded.digest !== digest) {
1327
+ return {
1328
+ id,
1329
+ ok: false,
1330
+ exitCode: 2,
1331
+ error: {
1332
+ code: "CONFLICT",
1333
+ message: "this id was already run with different content — change the id or start a new checkpoint"
1334
+ }
1335
+ };
1336
+ }
1337
+ const replay = isRecord(recorded.answer) ? recorded.answer : {};
1338
+ return { ...replay, id, replayed: true };
1339
+ },
1340
+ after: (id, line, outcome) => {
1341
+ if (!outcome.ok)
1342
+ return;
1343
+ checkpoint.record(id, checkpoint.digestOf(line), outcome);
1344
+ writeCliCheckpoint(path, checkpoint);
1345
+ }
1346
+ });
1347
+ }
1348
+ });
1349
+ }
730
1350
  export {
731
1351
  CliArgumentError2 as CliArgumentError,
732
1352
  CliBuildAssetSchema,
@@ -757,6 +1377,7 @@ export {
757
1377
  formatCliBuildStamp,
758
1378
  parseCliArgs2 as parseCliArgs,
759
1379
  pollUntilDone2 as pollUntilDone,
1380
+ publishCli,
760
1381
  readCliCheckpoint,
761
1382
  renderCliInstaller,
762
1383
  renderCliView2 as renderCliView,