chainlesschain 0.156.6 → 0.157.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +26 -2
  2. package/bin/chainlesschain.js +13 -0
  3. package/package.json +3 -1
  4. package/src/assets/web-panel/.build-hash +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Dme4LGax.js → ActionButton-CCj9oE5_.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-B3-5BjRm.js → Analytics-RMqXlyHE.js} +2 -2
  7. package/src/assets/web-panel/assets/AppLayout-CSmBboZB.css +1 -0
  8. package/src/assets/web-panel/assets/AppLayout-CV6gWn1r.js +1 -0
  9. package/src/assets/web-panel/assets/{Backup-Cih5dXcD.js → Backup-bes7wE_k.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-wLmjCc9u.js → BaseInput-BKgAovqI.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CRfTuSl8.js → Chat-Duckao6i.js} +2 -2
  12. package/src/assets/web-panel/assets/{Checkbox-BfbEUJDW.js → Checkbox-BaBBUZnH.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-HJI40OzO.js → Col-B6iQKzFs.js} +1 -1
  14. package/src/assets/web-panel/assets/{Compact-ADVAwcbQ.js → Compact-Fwt0CbI5.js} +1 -1
  15. package/src/assets/web-panel/assets/{Cowork-BLRUoSoO.js → Cowork-DNy50_Cp.js} +3 -3
  16. package/src/assets/web-panel/assets/{Cron-DcNB5TYu.js → Cron-CBREJypB.js} +2 -2
  17. package/src/assets/web-panel/assets/{Dashboard-p_Wuj0Un.js → Dashboard-DnJ1aZvj.js} +2 -2
  18. package/src/assets/web-panel/assets/{Dropdown-CrXGzreQ.js → Dropdown-B3vdFzOi.js} +1 -1
  19. package/src/assets/web-panel/assets/{FormItemContext-B97Dibo2.js → FormItemContext-RYn2zMn5.js} +1 -1
  20. package/src/assets/web-panel/assets/{Git-90CPsOOr.js → Git-7TolKe3c.js} +2 -2
  21. package/src/assets/web-panel/assets/KnowledgeGraph-4b9v3wYV.js +19 -0
  22. package/src/assets/web-panel/assets/KnowledgeGraph-U8ps3aGJ.css +1 -0
  23. package/src/assets/web-panel/assets/{Logs-0SXs6Eyx.js → Logs-BZgM3Q4b.js} +2 -2
  24. package/src/assets/web-panel/assets/{McpTools-VVSCkpV2.js → McpTools-5Wrif1R_.js} +2 -2
  25. package/src/assets/web-panel/assets/{Memory-B_3zNQNB.js → Memory-D8YRnt51.js} +2 -2
  26. package/src/assets/web-panel/assets/{Notes-DNQz9UXh.js → Notes-CWTUG8hk.js} +2 -2
  27. package/src/assets/web-panel/assets/{Organization-CgXUnp-W.js → Organization-DvJzrIES.js} +4 -4
  28. package/src/assets/web-panel/assets/{Overflow-BVsn6SM5.js → Overflow-tpvXbZkh.js} +1 -1
  29. package/src/assets/web-panel/assets/{OverrideContext-7M2Kv4Ru.js → OverrideContext-9ePmgwvW.js} +1 -1
  30. package/src/assets/web-panel/assets/{P2P-DGPIG-9j.js → P2P-C-AoKbTs.js} +2 -2
  31. package/src/assets/web-panel/assets/{Permissions-DIFqcnjU.js → Permissions-cIclKlQB.js} +3 -3
  32. package/src/assets/web-panel/assets/Portal-DXIqogG2.js +1 -0
  33. package/src/assets/web-panel/assets/{Projects-Bzn-dJ59.js → Projects-CDKdsdit.js} +2 -2
  34. package/src/assets/web-panel/assets/{Providers-mscN7CK5.js → Providers-C33u4Mka.js} +2 -2
  35. package/src/assets/web-panel/assets/{Row-BFUWxIkx.js → Row-DVscVTtp.js} +1 -1
  36. package/src/assets/web-panel/assets/{RssFeed-Dpa4h-q_.js → RssFeed-CGC4w7VD.js} +3 -3
  37. package/src/assets/web-panel/assets/{Security-DR6HKo_S.js → Security-ymR0We-P.js} +3 -3
  38. package/src/assets/web-panel/assets/{Services-CDh7r75R.js → Services-Gydt4uVC.js} +2 -2
  39. package/src/assets/web-panel/assets/{Skeleton-VNikEgM4.js → Skeleton-CfLdvEpu.js} +1 -1
  40. package/src/assets/web-panel/assets/{Skills-Dk9Cp1NG.js → Skills-C_SrPIFZ.js} +1 -1
  41. package/src/assets/web-panel/assets/Tasks-BchvT4YD.js +1 -0
  42. package/src/assets/web-panel/assets/{Templates-Ny_4GO6a.js → Templates-Bj1wDexb.js} +1 -1
  43. package/src/assets/web-panel/assets/Trigger-CIW_GVYA.js +1 -0
  44. package/src/assets/web-panel/assets/{VideoEditing-BNRFHgJ9.js → VideoEditing-CQOwjQFu.js} +1 -1
  45. package/src/assets/web-panel/assets/{Wallet-BUfg4IAx.js → Wallet-0kSZ-ENs.js} +4 -4
  46. package/src/assets/web-panel/assets/{WebAuthn-Cia89OyQ.js → WebAuthn-CrZlAr6l.js} +5 -5
  47. package/src/assets/web-panel/assets/{WorkflowEditor-C1OsMtqv.js → WorkflowEditor-DbdF8700.js} +1 -1
  48. package/src/assets/web-panel/assets/{chat-B2uGA8wN.js → chat-BNXQJRrX.js} +1 -1
  49. package/src/assets/web-panel/assets/{collapseMotion-DnZigkzG.js → collapseMotion-CSS8MlIE.js} +1 -1
  50. package/src/assets/web-panel/assets/{colors-C_wDMX2Q.js → colors-BgoKrIXh.js} +1 -1
  51. package/src/assets/web-panel/assets/{compact-item-C1ikzEN-.js → compact-item-COAuztwB.js} +1 -1
  52. package/src/assets/web-panel/assets/{createContext-XExBTk9v.js → createContext-BTceykzK.js} +1 -1
  53. package/src/assets/web-panel/assets/{hasIn-mXvd_Kdq.js → hasIn-BNooGgXx.js} +1 -1
  54. package/src/assets/web-panel/assets/{icons-CpgFsfkd.js → icons-DzieZiVh.js} +4 -4
  55. package/src/assets/web-panel/assets/index-4C1c4hkZ.js +36 -0
  56. package/src/assets/web-panel/assets/{index-Dz6RDRcu.js → index-81NIDNcI.js} +2 -2
  57. package/src/assets/web-panel/assets/{index-a0qENb5U.js → index-B7gZm05C.js} +1 -1
  58. package/src/assets/web-panel/assets/{index-CTle6zcb.js → index-BAPulPv1.js} +2 -2
  59. package/src/assets/web-panel/assets/{index-qtDQSqTG.js → index-BEbmwv5T.js} +2 -2
  60. package/src/assets/web-panel/assets/{index-D1eekAaa.js → index-BLzkplpf.js} +3 -3
  61. package/src/assets/web-panel/assets/{index-BBOVB9YK.js → index-BMU9I-F6.js} +1 -1
  62. package/src/assets/web-panel/assets/{index-D6Hyy0Bc.js → index-BRBeeCRz.js} +2 -2
  63. package/src/assets/web-panel/assets/index-BbBjevdP.js +1 -0
  64. package/src/assets/web-panel/assets/{index-kLUQdSDJ.js → index-Bjf02LQY.js} +2 -2
  65. package/src/assets/web-panel/assets/index-Bn7dXbEh.js +1 -0
  66. package/src/assets/web-panel/assets/index-BqJS8Rje.js +1 -0
  67. package/src/assets/web-panel/assets/{index-LpE6Six-.js → index-C1BBkQIj.js} +4 -4
  68. package/src/assets/web-panel/assets/{index-CxwU-EjS.js → index-C4YPr8e8.js} +1 -1
  69. package/src/assets/web-panel/assets/{index-lPIeHtHE.js → index-C7uW3zj4.js} +1 -1
  70. package/src/assets/web-panel/assets/{index-DMcLOtIo.js → index-CZ3YetO8.js} +1 -1
  71. package/src/assets/web-panel/assets/{index-Du7KGlCP.js → index-Cf1n_3VC.js} +1 -1
  72. package/src/assets/web-panel/assets/{index-DwMlStra.js → index-CjOtB4wg.js} +3 -3
  73. package/src/assets/web-panel/assets/{index-DYLE4bnY.js → index-CpFrvhnj.js} +1 -1
  74. package/src/assets/web-panel/assets/{index-v4Oi0d0l.js → index-D1SCFE25.js} +1 -1
  75. package/src/assets/web-panel/assets/{index-BOqmUcij.js → index-D5wFryiJ.js} +2 -2
  76. package/src/assets/web-panel/assets/{index-CbpKJ2W0.js → index-D6w1kIHg.js} +1 -1
  77. package/src/assets/web-panel/assets/{index-CttcpCq_.js → index-DC-nDpH_.js} +2 -2
  78. package/src/assets/web-panel/assets/index-DDgwb3KP.js +1 -0
  79. package/src/assets/web-panel/assets/index-DYDvGZbQ.js +1 -0
  80. package/src/assets/web-panel/assets/index-DbxkO9Uy.js +1 -0
  81. package/src/assets/web-panel/assets/index-DeN7D3FZ.js +1 -0
  82. package/src/assets/web-panel/assets/{index-DZjQgmBq.js → index-DnoNK5Gb.js} +1 -1
  83. package/src/assets/web-panel/assets/{index-D_oSE2Nk.js → index-KEQgDCwj.js} +5 -5
  84. package/src/assets/web-panel/assets/{index-C53dnYiq.js → index-O-dqdn3q.js} +2 -2
  85. package/src/assets/web-panel/assets/{index-DJkIheU6.js → index-Z5CuKqcS.js} +1 -1
  86. package/src/assets/web-panel/assets/{index-fLUJs2Sr.js → index-Z9wFemG0.js} +2 -2
  87. package/src/assets/web-panel/assets/{index-D9tzxSFs.js → index-cF6gfPY3.js} +1 -1
  88. package/src/assets/web-panel/assets/{index-BL27IhbN.js → index-eVLTVVvL.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-CMYADk0v.js → index-g7FAcG7B.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-BirLVqrC.js → index-lrfnKtkl.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-jg5cpQg9.js → index-nw5SqpgZ.js} +2 -2
  92. package/src/assets/web-panel/assets/{index-BFFb9yPd.js → index-vHj3UTBK.js} +1 -1
  93. package/src/assets/web-panel/assets/{initDefaultProps-DOj2K4bh.js → initDefaultProps-_t-PG0bz.js} +1 -1
  94. package/src/assets/web-panel/assets/{motion-joGf7r-l.js → motion-DPnqtODq.js} +2 -2
  95. package/src/assets/web-panel/assets/{move-Cwb6tumJ.js → move-kO9NQRyb.js} +1 -1
  96. package/src/assets/web-panel/assets/{omit-CPycjJ8C.js → omit-DD9MqVt0.js} +1 -1
  97. package/src/assets/web-panel/assets/{pickAttrs-CnibXC3T.js → pickAttrs-D4u5AYq1.js} +1 -1
  98. package/src/assets/web-panel/assets/{placementArrow-DWcIO1y4.js → placementArrow-BUkUxlJc.js} +1 -1
  99. package/src/assets/web-panel/assets/{responsiveObserve-C5giLhLf.js → responsiveObserve-DA_o8FHw.js} +1 -1
  100. package/src/assets/web-panel/assets/{slide-zwgmm7vM.js → slide-DSmAtCrK.js} +1 -1
  101. package/src/assets/web-panel/assets/{statusUtils-CK8tJSHq.js → statusUtils-Bn00xQ4D.js} +1 -1
  102. package/src/assets/web-panel/assets/{styleChecker-BzLSEXyu.js → styleChecker-phGTLjuN.js} +1 -1
  103. package/src/assets/web-panel/assets/{transition-D4AbuDdO.js → transition-exl3w1iN.js} +1 -1
  104. package/src/assets/web-panel/assets/{useConfigInject-ImjEZhXr.js → useConfigInject-C2E3Qsop.js} +2 -2
  105. package/src/assets/web-panel/assets/useFlexGapSupport-tlovsMBV.js +1 -0
  106. package/src/assets/web-panel/assets/{useMergedState-CXfbNKuO.js → useMergedState-DUMpRiCy.js} +1 -1
  107. package/src/assets/web-panel/assets/{useRefs-DwsdQTxa.js → useRefs-C8A7zAB_.js} +1 -1
  108. package/src/assets/web-panel/assets/{useState-DRbnp348.js → useState-CSbzOa8O.js} +1 -1
  109. package/src/assets/web-panel/assets/{vendor-C5RM7MZO.js → vendor-aH7YaPZi.js} +1 -1
  110. package/src/assets/web-panel/assets/{vnode-DAWimP6X.js → vnode-CIbqhcnZ.js} +1 -1
  111. package/src/assets/web-panel/assets/{ws-D-sl0vsW.js → ws-BTS8ehTm.js} +1 -1
  112. package/src/assets/web-panel/assets/{zoom-u6SXbmzZ.js → zoom-Ck9WbYsZ.js} +1 -1
  113. package/src/assets/web-panel/index.html +3 -3
  114. package/src/commands/mtc.js +786 -0
  115. package/src/commands/pack.js +463 -0
  116. package/src/commands/ui.js +6 -0
  117. package/src/gateways/ws/ws-server.js +49 -5
  118. package/src/index.js +37 -1
  119. package/src/lib/governance-v2-helpers.js +109 -0
  120. package/src/lib/packer/config-template-builder.js +151 -0
  121. package/src/lib/packer/errors.js +30 -0
  122. package/src/lib/packer/index.js +383 -0
  123. package/src/lib/packer/manifest-writer.js +93 -0
  124. package/src/lib/packer/native-prebuild-collector.js +305 -0
  125. package/src/lib/packer/pack-update-applier.js +203 -0
  126. package/src/lib/packer/pack-update-checker.js +185 -0
  127. package/src/lib/packer/pack-update-downloader.js +162 -0
  128. package/src/lib/packer/pkg-config-generator.js +325 -0
  129. package/src/lib/packer/pkg-runner.js +193 -0
  130. package/src/lib/packer/precheck.js +273 -0
  131. package/src/lib/packer/project-assets-collector.js +0 -0
  132. package/src/lib/packer/smoke-runner.js +339 -0
  133. package/src/lib/packer/web-panel-builder.js +157 -0
  134. package/src/lib/web-ui-server.js +95 -2
  135. package/src/lib/ws-server.js +1 -0
  136. package/src/runtime/agent-runtime.js +1 -0
  137. package/src/runtime/policies/agent-policy.js +1 -0
  138. package/src/assets/web-panel/assets/AppLayout-DvVLRyPs.js +0 -1
  139. package/src/assets/web-panel/assets/AppLayout-nff99EgA.css +0 -1
  140. package/src/assets/web-panel/assets/Portal-CFB5Y97t.js +0 -1
  141. package/src/assets/web-panel/assets/Tasks-CfHL1NrP.js +0 -1
  142. package/src/assets/web-panel/assets/Trigger-C7MTh_xj.js +0 -1
  143. package/src/assets/web-panel/assets/index-1ZqkTPt2.js +0 -1
  144. package/src/assets/web-panel/assets/index-B-TI0cZ2.js +0 -1
  145. package/src/assets/web-panel/assets/index-B6P9mWuk.js +0 -1
  146. package/src/assets/web-panel/assets/index-BfncNR8d.js +0 -1
  147. package/src/assets/web-panel/assets/index-C5Zv4fBx.js +0 -1
  148. package/src/assets/web-panel/assets/index-DQgS_8Fd.js +0 -1
  149. package/src/assets/web-panel/assets/index-DaMG8ksh.js +0 -36
  150. package/src/assets/web-panel/assets/index-f4W8Sok0.js +0 -1
  151. package/src/assets/web-panel/assets/useFlexGapSupport-Cd-PoTMl.js +0 -1
@@ -0,0 +1,463 @@
1
+ /**
2
+ * `cc pack` — Bundle the current project environment into a standalone executable.
3
+ *
4
+ * See docs/design/CC_PACK_打包指令设计文档.md for the full design.
5
+ *
6
+ * Phase 1 scope: Windows x64 single target, dry-run + minimal pkg invocation,
7
+ * no native module prebuild collection or smoke test yet.
8
+ */
9
+
10
+ import chalk from "chalk";
11
+ import { logger } from "../lib/logger.js";
12
+ import { runPack } from "../lib/packer/index.js";
13
+ import path from "node:path";
14
+ import {
15
+ checkPackUpdate,
16
+ PackUpdateError,
17
+ } from "../lib/packer/pack-update-checker.js";
18
+ import {
19
+ downloadAndVerify,
20
+ DownloadError,
21
+ } from "../lib/packer/pack-update-downloader.js";
22
+ import {
23
+ scheduleReplace,
24
+ ApplyError,
25
+ } from "../lib/packer/pack-update-applier.js";
26
+ import { VERSION } from "../constants.js";
27
+
28
+ /**
29
+ * Default pkg target string for the host platform+arch. Falls back to
30
+ * `node20-win-x64` for unknown combos so users on uncommon hosts still get
31
+ * a sensible string to edit with `--targets`.
32
+ */
33
+ export function defaultPkgTarget() {
34
+ const osSlug =
35
+ process.platform === "win32"
36
+ ? "win"
37
+ : process.platform === "darwin"
38
+ ? "macos"
39
+ : process.platform === "linux"
40
+ ? "linux"
41
+ : null;
42
+ const archSlug =
43
+ process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : null;
44
+ if (!osSlug || !archSlug) return "node20-win-x64";
45
+ return `node20-${osSlug}-${archSlug}`;
46
+ }
47
+
48
+ export function registerPackCommand(program) {
49
+ const packCmd = program
50
+ .command("pack")
51
+ .description(
52
+ "Bundle the current project environment into a standalone executable",
53
+ );
54
+
55
+ packCmd
56
+ .option(
57
+ "-o, --output <path>",
58
+ "Output path without extension. pkg appends .exe for win targets only.",
59
+ )
60
+ .option(
61
+ "-t, --targets <list>",
62
+ "Comma-separated pkg targets (defaults to current host platform)",
63
+ defaultPkgTarget(),
64
+ )
65
+ .option("--ws-port <n>", "Default WS port baked into the artifact", "18800")
66
+ .option(
67
+ "--ui-port <n>",
68
+ "Default Web UI port baked into the artifact",
69
+ "18810",
70
+ )
71
+ .option(
72
+ "--token <str>",
73
+ 'Default access token; "auto" generates a fresh one at first run',
74
+ "auto",
75
+ )
76
+ .option(
77
+ "--preset-config <path>",
78
+ "Pre-bundled config.json template (will be scanned for secrets)",
79
+ )
80
+ .option(
81
+ "--allow-secrets",
82
+ "Skip secret scanning of preset config (DANGEROUS)",
83
+ false,
84
+ )
85
+ .option("--include-db", "Bundle the DB initialization template", true)
86
+ .option("--no-include-db", "Skip bundling the DB template")
87
+ .option(
88
+ "--include-models",
89
+ "Bundle local LLM models (HUGE artifact size warning)",
90
+ false,
91
+ )
92
+ .option("--compress", "Enable pkg GZip compression", true)
93
+ .option("--no-compress", "Disable pkg compression")
94
+ .option("--sign <cert>", "Code-signing certificate (Phase 2)")
95
+ .option(
96
+ "--sign-password <pass>",
97
+ "Code-signing certificate password; supports env:NAME",
98
+ )
99
+ .option("--smoke-test", "Run smoke test on the produced artifact", true)
100
+ .option("--no-smoke-test", "Skip smoke test")
101
+ .option("--dry-run", "Print the build plan without invoking pkg", false)
102
+ .option(
103
+ "--bind-host <host>",
104
+ "Default bind host baked into the artifact",
105
+ "127.0.0.1",
106
+ )
107
+ .option("--allow-remote", "Default to 0.0.0.0 binding at runtime", false)
108
+ .option("--enable-tls", "Enable TLS in the artifact runtime", false)
109
+ .option("--tls-cert <path>", "TLS certificate path (PEM)")
110
+ .option("--tls-key <path>", "TLS private key path (PEM)")
111
+ .option("--access-policy <path>", "Pre-bundled access policy JSON")
112
+ .option(
113
+ "--skip-web-panel-build",
114
+ "Reuse existing web-panel/dist without rebuilding",
115
+ false,
116
+ )
117
+ .option(
118
+ "--allow-dirty",
119
+ "Allow packing with uncommitted changes in the working tree",
120
+ false,
121
+ )
122
+ .option(
123
+ "--cwd <dir>",
124
+ "Override the project root (defaults to process.cwd())",
125
+ )
126
+ .option(
127
+ "--project",
128
+ "Project mode: bundle .chainlesschain/ from cwd into the artifact",
129
+ )
130
+ .option(
131
+ "--no-project",
132
+ "Disable project-mode auto-detection; pack CLI-only (legacy behavior)",
133
+ )
134
+ .option(
135
+ "--project-config-override <path>",
136
+ "Use an alternate config.json instead of cwd/.chainlesschain/config.json",
137
+ )
138
+ .option(
139
+ "--force-large-project",
140
+ "Bypass the 50MB .chainlesschain/ size cap (DANGEROUS — bloats the exe)",
141
+ false,
142
+ )
143
+ .option(
144
+ "--entry <subcommand>",
145
+ "Override the default subcommand from config.pack.entry (project mode only)",
146
+ )
147
+ .option(
148
+ "--force-refresh-on-launch",
149
+ "Re-materialize project assets on every launch (project mode only)",
150
+ false,
151
+ )
152
+ .option(
153
+ "--update-manifest-url <url>",
154
+ "OTA manifest URL to bake into the artifact (enables `cc pack check-update`)",
155
+ )
156
+ .action(async (opts) => {
157
+ try {
158
+ const result = await runPack(opts, { logger });
159
+ if (opts.dryRun) {
160
+ logger.log(
161
+ chalk.green(
162
+ `\n ✓ Dry-run complete. ${result.steps?.length || 0} step(s) planned.`,
163
+ ),
164
+ );
165
+ } else if (result.outputPath) {
166
+ logger.log(chalk.green(`\n ✓ Pack succeeded: ${result.outputPath}`));
167
+ if (result.sha256) {
168
+ logger.log(chalk.dim(` SHA-256: ${result.sha256}`));
169
+ }
170
+ }
171
+ } catch (err) {
172
+ logger.error(`pack failed: ${err.message}`);
173
+ if (err.exitCode) process.exit(err.exitCode);
174
+ process.exit(1);
175
+ }
176
+ });
177
+
178
+ // Phase 5a: check-only OTA probe. Packed exes can't use `cc update`
179
+ // (that runs `npm install -g`), so this subcommand fetches a manifest
180
+ // and reports whether a newer packed artifact exists. Download +
181
+ // self-replace are Phase 5b/5c. See design doc §17.5-17.7.
182
+ packCmd
183
+ .command("check-update")
184
+ .description(
185
+ "Check whether a newer packed artifact is published (no download)",
186
+ )
187
+ .option(
188
+ "--manifest-url <url>",
189
+ "Manifest URL (falls back to BAKED.updateManifestUrl or CC_PACK_UPDATE_MANIFEST env)",
190
+ )
191
+ .option(
192
+ "--target <slug>",
193
+ "pkg target to match against manifest.latest.artifacts (e.g. node20-win-x64); defaults to current host",
194
+ )
195
+ .option(
196
+ "--current <version>",
197
+ "Override the current version (defaults to CLI VERSION or BAKED.packedCliVersion)",
198
+ )
199
+ .option("--json", "Emit JSON instead of human-readable output", false)
200
+ .option(
201
+ "--download",
202
+ "Phase 5b: after the check, download + SHA-256 verify the artifact (no install)",
203
+ false,
204
+ )
205
+ .option(
206
+ "--dest <path>",
207
+ "Phase 5b: download destination. Defaults to `<currentExePath>.new` inside a packed exe, or `./<basename(artifactUrl)>` otherwise. (Named `--dest`, not `--output`, to avoid colliding with the parent `pack -o`.)",
208
+ )
209
+ .option(
210
+ "--apply",
211
+ "Phase 5c: after download, replace the current exe with the new one. Requires --download. On Windows a sidecar cmd runs the move after this process exits",
212
+ false,
213
+ )
214
+ .option(
215
+ "--target-exe <path>",
216
+ "Phase 5c: path of the exe to replace. Defaults to process.execPath (the currently-running binary)",
217
+ )
218
+ .option(
219
+ "--restart",
220
+ "Phase 5c: spawn the new exe after replacement (default: exit without restart)",
221
+ false,
222
+ )
223
+ .action(async (opts) => {
224
+ const manifestUrl =
225
+ opts.manifestUrl ||
226
+ process.env.CC_PACK_UPDATE_MANIFEST ||
227
+ (typeof globalThis.BAKED === "object" &&
228
+ globalThis.BAKED?.updateManifestUrl) ||
229
+ null;
230
+
231
+ if (!manifestUrl) {
232
+ const msg =
233
+ "No manifest URL. Provide --manifest-url, set CC_PACK_UPDATE_MANIFEST, or bake one via `cc pack --update-manifest-url`.";
234
+ if (opts.json) {
235
+ logger.log(JSON.stringify({ error: msg, code: "NO_MANIFEST_URL" }));
236
+ } else {
237
+ logger.error(msg);
238
+ }
239
+ process.exit(2);
240
+ }
241
+
242
+ const currentVersion =
243
+ opts.current ||
244
+ (typeof globalThis.BAKED === "object" &&
245
+ globalThis.BAKED?.packedCliVersion) ||
246
+ VERSION;
247
+
248
+ const target = opts.target || defaultPkgTarget();
249
+
250
+ let result;
251
+ try {
252
+ result = await checkPackUpdate({
253
+ manifestUrl,
254
+ currentVersion,
255
+ target,
256
+ });
257
+ } catch (err) {
258
+ const code = err instanceof PackUpdateError ? err.code : "UNKNOWN";
259
+ if (opts.json) {
260
+ logger.log(JSON.stringify({ error: err.message, code }));
261
+ } else {
262
+ logger.error(`check-update failed [${code}]: ${err.message}`);
263
+ }
264
+ // Network / schema issues are non-zero but distinct from "pack failed"
265
+ process.exit(3);
266
+ }
267
+
268
+ // Human or JSON report for the check result.
269
+ if (!opts.download) {
270
+ if (opts.json) {
271
+ logger.log(JSON.stringify(result, null, 2));
272
+ return;
273
+ }
274
+ if (result.updateAvailable) {
275
+ logger.log(
276
+ chalk.bold(
277
+ `\n Update available: ${result.currentVersion} → ${chalk.green(result.latestVersion)}\n`,
278
+ ),
279
+ );
280
+ if (result.artifact) {
281
+ logger.log(` Artifact (${target}):`);
282
+ logger.log(` ${result.artifact.url}`);
283
+ logger.log(chalk.dim(` sha256: ${result.artifact.sha256}`));
284
+ } else {
285
+ logger.log(
286
+ chalk.yellow(
287
+ ` No artifact for target "${target}" in this manifest.`,
288
+ ),
289
+ );
290
+ }
291
+ if (result.releaseNotes) {
292
+ logger.log(chalk.dim(` Release notes: ${result.releaseNotes}`));
293
+ }
294
+ } else {
295
+ logger.log(
296
+ chalk.green(
297
+ ` ✓ You are on the latest version (${result.currentVersion}).`,
298
+ ),
299
+ );
300
+ }
301
+ return;
302
+ }
303
+
304
+ // ── --download branch (Phase 5b) ─────────────────────────────────────
305
+ if (!result.updateAvailable) {
306
+ const msg = `Already on the latest version (${result.currentVersion}); nothing to download.`;
307
+ if (opts.json) logger.log(JSON.stringify({ skipped: msg, ...result }));
308
+ else logger.log(chalk.green(` ✓ ${msg}`));
309
+ return;
310
+ }
311
+ if (!result.artifact) {
312
+ const msg = `No artifact for target "${target}" in this manifest.`;
313
+ if (opts.json)
314
+ logger.log(JSON.stringify({ error: msg, code: "NO_ARTIFACT" }));
315
+ else logger.error(msg);
316
+ process.exit(3);
317
+ }
318
+
319
+ // Default output path. Inside a pkg-packed exe, `process.execPath` is the
320
+ // exe itself — writing `<exe>.new` alongside is conventional for the
321
+ // future Phase 5c self-replacer. Outside pkg, fall back to a filename
322
+ // under cwd so `cc pack check-update --download` from a dev checkout
323
+ // drops the artifact where the user can find it.
324
+ const outputPath = opts.dest
325
+ ? path.resolve(opts.dest)
326
+ : process.pkg
327
+ ? process.execPath + ".new"
328
+ : path.resolve(
329
+ process.cwd(),
330
+ path.basename(new URL(result.artifact.url).pathname) ||
331
+ "pack-update-artifact",
332
+ );
333
+
334
+ if (!opts.json) {
335
+ logger.log(
336
+ chalk.bold(
337
+ `\n Downloading ${result.currentVersion} → ${chalk.green(result.latestVersion)}`,
338
+ ),
339
+ );
340
+ logger.log(` ${result.artifact.url}`);
341
+ logger.log(chalk.dim(` → ${outputPath}`));
342
+ }
343
+
344
+ let lastPercent = -1;
345
+ try {
346
+ const dl = await downloadAndVerify({
347
+ url: result.artifact.url,
348
+ sha256: result.artifact.sha256,
349
+ outputPath,
350
+ onProgress: opts.json
351
+ ? undefined
352
+ : ({ bytes, total }) => {
353
+ if (!total) return;
354
+ const pct = Math.floor((bytes / total) * 100);
355
+ if (pct !== lastPercent && pct % 10 === 0) {
356
+ lastPercent = pct;
357
+ logger.log(
358
+ chalk.dim(
359
+ ` ${pct}% (${formatMB(bytes)}/${formatMB(total)})`,
360
+ ),
361
+ );
362
+ }
363
+ },
364
+ });
365
+ if (opts.json) {
366
+ logger.log(
367
+ JSON.stringify(
368
+ {
369
+ downloaded: true,
370
+ outputPath: dl.outputPath,
371
+ bytes: dl.bytes,
372
+ sha256: dl.sha256,
373
+ latestVersion: result.latestVersion,
374
+ },
375
+ null,
376
+ 2,
377
+ ),
378
+ );
379
+ } else {
380
+ logger.log(
381
+ chalk.green(
382
+ `\n ✓ Downloaded + verified ${formatMB(dl.bytes)} to ${dl.outputPath}`,
383
+ ),
384
+ );
385
+ if (!opts.apply) {
386
+ logger.log(
387
+ chalk.dim(
388
+ ` sha256 OK. Pass --apply to replace the running exe automatically.`,
389
+ ),
390
+ );
391
+ }
392
+ }
393
+
394
+ // ── --apply branch (Phase 5c) ──────────────────────────────────────
395
+ if (!opts.apply) return;
396
+
397
+ const targetExe = opts.targetExe || process.execPath;
398
+ if (!opts.json) {
399
+ logger.log(chalk.bold(`\n Applying update → ${targetExe}`));
400
+ if (process.platform === "win32") {
401
+ logger.log(
402
+ chalk.yellow(
403
+ ` [Windows] This process will exit after scheduling a sidecar cmd; the replacement runs detached. ${opts.restart ? "The new exe will start automatically." : "Re-launch the exe yourself after exit."}`,
404
+ ),
405
+ );
406
+ } else {
407
+ logger.log(
408
+ chalk.dim(
409
+ ` [POSIX] Atomic rename; the currently-running inode survives until exit. ${opts.restart ? "A detached copy will be started now." : "Next launch picks up the new bytes."}`,
410
+ ),
411
+ );
412
+ }
413
+ }
414
+
415
+ try {
416
+ const plan = await scheduleReplace({
417
+ newExePath: dl.outputPath,
418
+ targetExePath: targetExe,
419
+ restart: Boolean(opts.restart),
420
+ });
421
+ if (opts.json) {
422
+ logger.log(JSON.stringify({ applied: true, ...plan }, null, 2));
423
+ } else {
424
+ logger.log(
425
+ chalk.green(
426
+ ` ✓ Apply scheduled: action=${plan.action}${plan.sidecarPath ? `, sidecar=${plan.sidecarPath}` : ""}`,
427
+ ),
428
+ );
429
+ }
430
+ // On Windows the sidecar waits for us to exit. On POSIX the rename
431
+ // already happened; if we were asked to restart, the detached child
432
+ // is running. Either way the parent's job is done.
433
+ if (plan.action === "sidecar-cmd") {
434
+ // Give the sidecar a moment to start waiting on our PID before we
435
+ // actually exit. Without this, process.exit(0) can race ahead and
436
+ // the sidecar's tasklist check may spuriously see us as gone
437
+ // before it has even polled once (harmless, but nicer to avoid).
438
+ setTimeout(() => process.exit(0), 500).unref?.();
439
+ }
440
+ } catch (err) {
441
+ const code = err instanceof ApplyError ? err.code : "UNKNOWN";
442
+ if (opts.json) {
443
+ logger.log(JSON.stringify({ error: err.message, code }));
444
+ } else {
445
+ logger.error(`apply failed [${code}]: ${err.message}`);
446
+ }
447
+ process.exit(5);
448
+ }
449
+ } catch (err) {
450
+ const code = err instanceof DownloadError ? err.code : "UNKNOWN";
451
+ if (opts.json) {
452
+ logger.log(JSON.stringify({ error: err.message, code }));
453
+ } else {
454
+ logger.error(`download failed [${code}]: ${err.message}`);
455
+ }
456
+ process.exit(4);
457
+ }
458
+ });
459
+ }
460
+
461
+ function formatMB(bytes) {
462
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
463
+ }
@@ -17,6 +17,11 @@ export function registerUiCommand(program) {
17
17
  "--web-panel-dir <dir>",
18
18
  "Path to built web-panel dist/ directory (auto-detected by default)",
19
19
  )
20
+ .option(
21
+ "--ui-mode <mode>",
22
+ 'UI rendering mode: "auto" (default), "full" (require Vue panel), or "minimal" (embedded HTML only)',
23
+ "auto",
24
+ )
20
25
  .action(async (opts) => {
21
26
  try {
22
27
  const runtime = createAgentRuntimeFactory().createUiRuntime({
@@ -26,6 +31,7 @@ export function registerUiCommand(program) {
26
31
  open: opts.open,
27
32
  token: opts.token || null,
28
33
  webPanelDir: opts.webPanelDir || null,
34
+ uiMode: opts.uiMode || "auto",
29
35
  });
30
36
  await runtime.startUiServer();
31
37
  } catch (err) {
@@ -81,8 +81,34 @@ const __dirname = dirname(__filename);
81
81
  /** Absolute path to the CLI entry point */
82
82
  const BIN_PATH = join(__dirname, "..", "..", "..", "bin", "chainlesschain.js");
83
83
 
84
- /** Commands that must not be executed via WebSocket */
85
- const BLOCKED_COMMANDS = new Set(["serve", "chat", "agent", "setup"]);
84
+ /**
85
+ * Commands always blocked over WebSocket (any mode):
86
+ * - serve: would recursively spawn another WS server
87
+ * - setup: needs interactive TTY
88
+ * - pack: meaningless self-bundling from inside running instance
89
+ */
90
+ const ALWAYS_BLOCKED_COMMANDS = new Set(["serve", "setup", "pack"]);
91
+
92
+ /**
93
+ * Commands blocked by default but unlocked when running inside a pack
94
+ * artifact (CC_PACK_MODE=1). The Web UI may then expose these via a
95
+ * shell-like surface for advanced users.
96
+ */
97
+ const PACK_UNLOCKABLE_COMMANDS = new Set(["chat", "agent"]);
98
+
99
+ /**
100
+ * Decide if a command is currently blocked over WebSocket.
101
+ * Reads CC_PACK_MODE at call time so tests can flip it per-case.
102
+ * @param {string} baseCmd
103
+ * @param {object} [env=process.env]
104
+ * @returns {boolean}
105
+ */
106
+ export function isCommandBlocked(baseCmd, env = process.env) {
107
+ if (ALWAYS_BLOCKED_COMMANDS.has(baseCmd)) return true;
108
+ const packMode = env.CC_PACK_MODE === "1" || env.CC_PACK_MODE === "true";
109
+ if (PACK_UNLOCKABLE_COMMANDS.has(baseCmd) && !packMode) return true;
110
+ return false;
111
+ }
86
112
 
87
113
  /** Heartbeat interval (ms) */
88
114
  const HEARTBEAT_INTERVAL = 30_000;
@@ -180,6 +206,14 @@ export class ChainlessChainWSServer extends EventEmitter {
180
206
  });
181
207
 
182
208
  this.wss.on("listening", () => {
209
+ // When port 0 was requested the OS assigns an ephemeral port;
210
+ // read it back so callers + the listening event see the actual
211
+ // bound port instead of 0. Tests rely on this for collision-free
212
+ // parallel runs.
213
+ const addr = this.wss.address();
214
+ if (addr && typeof addr === "object" && addr.port) {
215
+ this.port = addr.port;
216
+ }
183
217
  this._startHeartbeat();
184
218
  this.emit("listening", { port: this.port, host: this.host });
185
219
  resolve();
@@ -236,10 +270,20 @@ export class ChainlessChainWSServer extends EventEmitter {
236
270
  }
237
271
  this.clients.clear();
238
272
 
239
- // Close the server
273
+ // Close the server. ws.WebSocketServer.close() waits for all underlying
274
+ // sockets to fully terminate before invoking its callback — on slow CI
275
+ // runners (especially GH Linux/macOS) sockets can linger in TIME_WAIT /
276
+ // CLOSE_WAIT longer than Vitest's task timeout, causing the callback to
277
+ // never fire and afterEach to hang forever. Hard 2s ceiling here lets
278
+ // the test suite reclaim the worker even if a lingering socket exists;
279
+ // the underlying handle is GC'd by node shortly after anyway.
240
280
  if (this.wss) {
241
281
  await new Promise((resolve) => {
242
- this.wss.close(() => resolve());
282
+ const ceiling = setTimeout(() => resolve(), 5000);
283
+ this.wss.close(() => {
284
+ clearTimeout(ceiling);
285
+ resolve();
286
+ });
243
287
  });
244
288
  this.wss = null;
245
289
  }
@@ -499,7 +543,7 @@ export class ChainlessChainWSServer extends EventEmitter {
499
543
 
500
544
  // Block dangerous/interactive commands
501
545
  const baseCmd = args[0];
502
- if (BLOCKED_COMMANDS.has(baseCmd)) {
546
+ if (isCommandBlocked(baseCmd)) {
503
547
  this._send(ws, {
504
548
  id,
505
549
  type: "error",
package/src/index.js CHANGED
@@ -127,6 +127,7 @@ import { registerServeCommand } from "./commands/serve.js";
127
127
 
128
128
  // Web UI
129
129
  import { registerUiCommand } from "./commands/ui.js";
130
+ import { registerPackCommand } from "./commands/pack.js";
130
131
 
131
132
  // Video Editing Agent (CutClaw-inspired)
132
133
  import { registerVideoCommand } from "./commands/video.js";
@@ -192,6 +193,8 @@ import { registerQuantizationCommand } from "./commands/quantization.js";
192
193
  import { registerRuntimeCommand } from "./commands/runtime.js";
193
194
  // Phase 17: IPFS decentralized storage
194
195
  import { registerIpfsCommand } from "./commands/ipfs.js";
196
+ // MTC: Merkle Tree Certificates (Phase 1 Week 3)
197
+ import { registerMtcCommand } from "./commands/mtc.js";
195
198
  // Phase 27: Multimodal Collaboration
196
199
  import { registerMultimodalCommand } from "./commands/multimodal.js";
197
200
  // Phase 22: Performance auto-tuning
@@ -355,7 +358,14 @@ import { registerKgV2Commands } from "./commands/kg.js";
355
358
  import { registerPmodeV2Commands } from "./commands/planmode.js";
356
359
  import { registerPipoV2Commands } from "./commands/pipeline.js";
357
360
 
358
- export function createProgram() {
361
+ /**
362
+ * @param {object} [opts]
363
+ * @param {Set<string>} [opts.allowedCommands] When set, only these top-level
364
+ * command names survive. Env var CC_PROJECT_ALLOWED_SUBCOMMANDS provides the
365
+ * same filter at runtime (packed exe project mode — comma-separated list).
366
+ * opts.allowedCommands takes precedence over the env var.
367
+ */
368
+ export function createProgram(opts = {}) {
359
369
  const program = new Command();
360
370
 
361
371
  program
@@ -510,6 +520,9 @@ export function createProgram() {
510
520
  // Web UI
511
521
  registerUiCommand(program);
512
522
 
523
+ // Standalone Executable Bundling
524
+ registerPackCommand(program);
525
+
513
526
  // Orchestration Layer
514
527
  registerOrchestrateCommand(program);
515
528
 
@@ -565,6 +578,7 @@ export function createProgram() {
565
578
  registerQuantizationCommand(program);
566
579
  registerRuntimeCommand(program);
567
580
  registerIpfsCommand(program);
581
+ registerMtcCommand(program);
568
582
  registerMultimodalCommand(program);
569
583
  registerPerfCommand(program);
570
584
  registerAgentNetworkCommand(program);
@@ -720,5 +734,27 @@ export function createProgram() {
720
734
  registerPmodeV2Commands(program);
721
735
  registerPipoV2Commands(program);
722
736
 
737
+ // Phase 3a: project-mode command whitelist.
738
+ // In a packed project-mode exe, CC_PROJECT_ALLOWED_SUBCOMMANDS (set by the
739
+ // entry script from BAKED.projectAllowedSubcommands) restricts which top-level
740
+ // commands are visible. opts.allowedCommands (a Set<string>) is preferred for
741
+ // programmatic/test use and takes precedence over the env var.
742
+ const allowedSet =
743
+ opts.allowedCommands instanceof Set
744
+ ? opts.allowedCommands
745
+ : process.env.CC_PROJECT_ALLOWED_SUBCOMMANDS
746
+ ? new Set(
747
+ process.env.CC_PROJECT_ALLOWED_SUBCOMMANDS.split(",")
748
+ .map((s) => s.trim())
749
+ .filter(Boolean),
750
+ )
751
+ : null;
752
+
753
+ if (allowedSet && allowedSet.size > 0) {
754
+ program.commands = program.commands.filter((cmd) =>
755
+ allowedSet.has(cmd.name()),
756
+ );
757
+ }
758
+
723
759
  return program;
724
760
  }