chainlesschain 0.156.6 → 0.156.7

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 (113) hide show
  1. package/README.md +24 -0
  2. package/package.json +2 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Dme4LGax.js → ActionButton-Cs4QdjYb.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-B3-5BjRm.js → Analytics-Xot0e9TT.js} +1 -1
  5. package/src/assets/web-panel/assets/{AppLayout-DvVLRyPs.js → AppLayout-3qsE1-pz.js} +1 -1
  6. package/src/assets/web-panel/assets/{BaseInput-wLmjCc9u.js → BaseInput-Tg40P4JM.js} +1 -1
  7. package/src/assets/web-panel/assets/{Checkbox-BfbEUJDW.js → Checkbox-CheA2Ety.js} +1 -1
  8. package/src/assets/web-panel/assets/{Col-HJI40OzO.js → Col-Cdfsmnaq.js} +1 -1
  9. package/src/assets/web-panel/assets/{Compact-ADVAwcbQ.js → Compact-D3LSgEpW.js} +1 -1
  10. package/src/assets/web-panel/assets/{Cron-DcNB5TYu.js → Cron-9MV6k-MV.js} +1 -1
  11. package/src/assets/web-panel/assets/{Dashboard-p_Wuj0Un.js → Dashboard-A1TZC5_t.js} +1 -1
  12. package/src/assets/web-panel/assets/{Dropdown-CrXGzreQ.js → Dropdown-DZxUTZvw.js} +1 -1
  13. package/src/assets/web-panel/assets/{FormItemContext-B97Dibo2.js → FormItemContext-C3_-j_SR.js} +1 -1
  14. package/src/assets/web-panel/assets/{Git-90CPsOOr.js → Git-Cw-gW-kh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Memory-B_3zNQNB.js → Memory-CMweTJyn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Notes-DNQz9UXh.js → Notes-B_W3BfZF.js} +1 -1
  17. package/src/assets/web-panel/assets/{Organization-CgXUnp-W.js → Organization-Dz_jGbAM.js} +1 -1
  18. package/src/assets/web-panel/assets/{Overflow-BVsn6SM5.js → Overflow-Dka3nWV9.js} +1 -1
  19. package/src/assets/web-panel/assets/{Permissions-DIFqcnjU.js → Permissions-DvXVIlHX.js} +1 -1
  20. package/src/assets/web-panel/assets/{Providers-mscN7CK5.js → Providers-DUfX_ynl.js} +1 -1
  21. package/src/assets/web-panel/assets/{Row-BFUWxIkx.js → Row-DZhDSo2Q.js} +1 -1
  22. package/src/assets/web-panel/assets/{RssFeed-Dpa4h-q_.js → RssFeed-CHQpUl3h.js} +1 -1
  23. package/src/assets/web-panel/assets/{Security-DR6HKo_S.js → Security-FUSOn89T.js} +1 -1
  24. package/src/assets/web-panel/assets/{Skeleton-VNikEgM4.js → Skeleton-DxmZ7zRw.js} +1 -1
  25. package/src/assets/web-panel/assets/{Templates-Ny_4GO6a.js → Templates-BVbmyn38.js} +1 -1
  26. package/src/assets/web-panel/assets/{Trigger-C7MTh_xj.js → Trigger-xAvohiq9.js} +1 -1
  27. package/src/assets/web-panel/assets/{VideoEditing-BNRFHgJ9.js → VideoEditing-BUWYQv2y.js} +1 -1
  28. package/src/assets/web-panel/assets/{Wallet-BUfg4IAx.js → Wallet-BDYdEwFf.js} +1 -1
  29. package/src/assets/web-panel/assets/{WebAuthn-Cia89OyQ.js → WebAuthn-CvpuagtK.js} +1 -1
  30. package/src/assets/web-panel/assets/{WorkflowEditor-C1OsMtqv.js → WorkflowEditor-BR7W5cjw.js} +1 -1
  31. package/src/assets/web-panel/assets/{colors-C_wDMX2Q.js → colors-C5kDbQCi.js} +1 -1
  32. package/src/assets/web-panel/assets/{compact-item-C1ikzEN-.js → compact-item-Bo_1zDrX.js} +1 -1
  33. package/src/assets/web-panel/assets/{createContext-XExBTk9v.js → createContext-CniPpJsG.js} +1 -1
  34. package/src/assets/web-panel/assets/{hasIn-mXvd_Kdq.js → hasIn-ClDc6Sz8.js} +1 -1
  35. package/src/assets/web-panel/assets/{index-D6Hyy0Bc.js → index--lcO-bOn.js} +1 -1
  36. package/src/assets/web-panel/assets/{index-lPIeHtHE.js → index-6tQekF0Y.js} +1 -1
  37. package/src/assets/web-panel/assets/{index-BfncNR8d.js → index-8qWxPHSb.js} +1 -1
  38. package/src/assets/web-panel/assets/{index-C53dnYiq.js → index-B7nGNm_C.js} +1 -1
  39. package/src/assets/web-panel/assets/{index-CMYADk0v.js → index-B8y0NO-M.js} +1 -1
  40. package/src/assets/web-panel/assets/{index-DMcLOtIo.js → index-BAlSSCbs.js} +1 -1
  41. package/src/assets/web-panel/assets/{index-DJkIheU6.js → index-BCXFoTAw.js} +1 -1
  42. package/src/assets/web-panel/assets/{index-kLUQdSDJ.js → index-BHruTebo.js} +1 -1
  43. package/src/assets/web-panel/assets/{index-1ZqkTPt2.js → index-BJx6C3J8.js} +1 -1
  44. package/src/assets/web-panel/assets/{index-CbpKJ2W0.js → index-BUTCJTbj.js} +1 -1
  45. package/src/assets/web-panel/assets/{index-B6P9mWuk.js → index-BYShDlZ0.js} +1 -1
  46. package/src/assets/web-panel/assets/{index-D9tzxSFs.js → index-Bv2OmZAS.js} +1 -1
  47. package/src/assets/web-panel/assets/{index-BFFb9yPd.js → index-C92K4iDE.js} +1 -1
  48. package/src/assets/web-panel/assets/{index-v4Oi0d0l.js → index-CCdb36il.js} +1 -1
  49. package/src/assets/web-panel/assets/{index-B-TI0cZ2.js → index-CKR2ITFk.js} +1 -1
  50. package/src/assets/web-panel/assets/{index-fLUJs2Sr.js → index-CMcGcbea.js} +1 -1
  51. package/src/assets/web-panel/assets/{index-BirLVqrC.js → index-CTetsi8W.js} +1 -1
  52. package/src/assets/web-panel/assets/{index-LpE6Six-.js → index-CXxLp7Aw.js} +1 -1
  53. package/src/assets/web-panel/assets/{index-qtDQSqTG.js → index-CeSV8f3b.js} +1 -1
  54. package/src/assets/web-panel/assets/{index-DwMlStra.js → index-Ch5mAXeh.js} +1 -1
  55. package/src/assets/web-panel/assets/{index-BOqmUcij.js → index-CwhWEkmA.js} +1 -1
  56. package/src/assets/web-panel/assets/{index-D_oSE2Nk.js → index-D2fe9a6f.js} +1 -1
  57. package/src/assets/web-panel/assets/index-D3UDIt7h.js +1 -0
  58. package/src/assets/web-panel/assets/{index-CxwU-EjS.js → index-D90sLw5Q.js} +1 -1
  59. package/src/assets/web-panel/assets/{index-D1eekAaa.js → index-D9bolkbl.js} +1 -1
  60. package/src/assets/web-panel/assets/{index-BL27IhbN.js → index-DNY0K7iI.js} +1 -1
  61. package/src/assets/web-panel/assets/{index-Du7KGlCP.js → index-DSiHmo4b.js} +1 -1
  62. package/src/assets/web-panel/assets/{index-CttcpCq_.js → index-DTYnvYqB.js} +1 -1
  63. package/src/assets/web-panel/assets/{index-jg5cpQg9.js → index-DaLYbr0E.js} +1 -1
  64. package/src/assets/web-panel/assets/{index-DYLE4bnY.js → index-DkSNIJhM.js} +1 -1
  65. package/src/assets/web-panel/assets/{index-DZjQgmBq.js → index-DnQkqOZj.js} +1 -1
  66. package/src/assets/web-panel/assets/{index-DaMG8ksh.js → index-Dn_OQQaV.js} +3 -3
  67. package/src/assets/web-panel/assets/{index-Dz6RDRcu.js → index-Dtfrhky9.js} +1 -1
  68. package/src/assets/web-panel/assets/{index-CTle6zcb.js → index-JNbd08FN.js} +1 -1
  69. package/src/assets/web-panel/assets/index-PT376OZM.js +1 -0
  70. package/src/assets/web-panel/assets/{index-BBOVB9YK.js → index-cIgCeEqo.js} +1 -1
  71. package/src/assets/web-panel/assets/{index-a0qENb5U.js → index-vBi4x_6g.js} +1 -1
  72. package/src/assets/web-panel/assets/{index-C5Zv4fBx.js → index-xL8gcpmy.js} +1 -1
  73. package/src/assets/web-panel/assets/{initDefaultProps-DOj2K4bh.js → initDefaultProps-DMfJaUzk.js} +1 -1
  74. package/src/assets/web-panel/assets/{motion-joGf7r-l.js → motion-sEbWmOWo.js} +1 -1
  75. package/src/assets/web-panel/assets/{move-Cwb6tumJ.js → move-DIWXVs--.js} +1 -1
  76. package/src/assets/web-panel/assets/{omit-CPycjJ8C.js → omit-D7mkMPhu.js} +1 -1
  77. package/src/assets/web-panel/assets/{pickAttrs-CnibXC3T.js → pickAttrs-B25NUX4k.js} +1 -1
  78. package/src/assets/web-panel/assets/{placementArrow-DWcIO1y4.js → placementArrow-By1Bkq1d.js} +1 -1
  79. package/src/assets/web-panel/assets/{responsiveObserve-C5giLhLf.js → responsiveObserve-B3aCQz5r.js} +1 -1
  80. package/src/assets/web-panel/assets/{slide-zwgmm7vM.js → slide-eR-f56FQ.js} +1 -1
  81. package/src/assets/web-panel/assets/{statusUtils-CK8tJSHq.js → statusUtils-zcNWczhN.js} +1 -1
  82. package/src/assets/web-panel/assets/{styleChecker-BzLSEXyu.js → styleChecker-u9Z0IfRy.js} +1 -1
  83. package/src/assets/web-panel/assets/{transition-D4AbuDdO.js → transition-gRK4XSlW.js} +1 -1
  84. package/src/assets/web-panel/assets/{useConfigInject-ImjEZhXr.js → useConfigInject-ZEunuNHN.js} +1 -1
  85. package/src/assets/web-panel/assets/{useFlexGapSupport-Cd-PoTMl.js → useFlexGapSupport-BpbEJfeh.js} +1 -1
  86. package/src/assets/web-panel/assets/{vnode-DAWimP6X.js → vnode-DVHvXn9F.js} +1 -1
  87. package/src/assets/web-panel/assets/{zoom-u6SXbmzZ.js → zoom-ByzgJIn6.js} +1 -1
  88. package/src/assets/web-panel/index.html +1 -1
  89. package/src/commands/pack.js +463 -0
  90. package/src/commands/ui.js +6 -0
  91. package/src/gateways/ws/ws-server.js +29 -3
  92. package/src/index.js +34 -1
  93. package/src/lib/governance-v2-helpers.js +109 -0
  94. package/src/lib/packer/config-template-builder.js +151 -0
  95. package/src/lib/packer/errors.js +30 -0
  96. package/src/lib/packer/index.js +383 -0
  97. package/src/lib/packer/manifest-writer.js +93 -0
  98. package/src/lib/packer/native-prebuild-collector.js +305 -0
  99. package/src/lib/packer/pack-update-applier.js +203 -0
  100. package/src/lib/packer/pack-update-checker.js +185 -0
  101. package/src/lib/packer/pack-update-downloader.js +162 -0
  102. package/src/lib/packer/pkg-config-generator.js +325 -0
  103. package/src/lib/packer/pkg-runner.js +139 -0
  104. package/src/lib/packer/precheck.js +273 -0
  105. package/src/lib/packer/project-assets-collector.js +0 -0
  106. package/src/lib/packer/smoke-runner.js +339 -0
  107. package/src/lib/packer/web-panel-builder.js +157 -0
  108. package/src/lib/web-ui-server.js +95 -2
  109. package/src/lib/ws-server.js +1 -0
  110. package/src/runtime/agent-runtime.js +1 -0
  111. package/src/runtime/policies/agent-policy.js +1 -0
  112. package/src/assets/web-panel/assets/index-DQgS_8Fd.js +0 -1
  113. package/src/assets/web-panel/assets/index-f4W8Sok0.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;
@@ -499,7 +525,7 @@ export class ChainlessChainWSServer extends EventEmitter {
499
525
 
500
526
  // Block dangerous/interactive commands
501
527
  const baseCmd = args[0];
502
- if (BLOCKED_COMMANDS.has(baseCmd)) {
528
+ if (isCommandBlocked(baseCmd)) {
503
529
  this._send(ws, {
504
530
  id,
505
531
  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";
@@ -355,7 +356,14 @@ import { registerKgV2Commands } from "./commands/kg.js";
355
356
  import { registerPmodeV2Commands } from "./commands/planmode.js";
356
357
  import { registerPipoV2Commands } from "./commands/pipeline.js";
357
358
 
358
- export function createProgram() {
359
+ /**
360
+ * @param {object} [opts]
361
+ * @param {Set<string>} [opts.allowedCommands] When set, only these top-level
362
+ * command names survive. Env var CC_PROJECT_ALLOWED_SUBCOMMANDS provides the
363
+ * same filter at runtime (packed exe project mode — comma-separated list).
364
+ * opts.allowedCommands takes precedence over the env var.
365
+ */
366
+ export function createProgram(opts = {}) {
359
367
  const program = new Command();
360
368
 
361
369
  program
@@ -510,6 +518,9 @@ export function createProgram() {
510
518
  // Web UI
511
519
  registerUiCommand(program);
512
520
 
521
+ // Standalone Executable Bundling
522
+ registerPackCommand(program);
523
+
513
524
  // Orchestration Layer
514
525
  registerOrchestrateCommand(program);
515
526
 
@@ -720,5 +731,27 @@ export function createProgram() {
720
731
  registerPmodeV2Commands(program);
721
732
  registerPipoV2Commands(program);
722
733
 
734
+ // Phase 3a: project-mode command whitelist.
735
+ // In a packed project-mode exe, CC_PROJECT_ALLOWED_SUBCOMMANDS (set by the
736
+ // entry script from BAKED.projectAllowedSubcommands) restricts which top-level
737
+ // commands are visible. opts.allowedCommands (a Set<string>) is preferred for
738
+ // programmatic/test use and takes precedence over the env var.
739
+ const allowedSet =
740
+ opts.allowedCommands instanceof Set
741
+ ? opts.allowedCommands
742
+ : process.env.CC_PROJECT_ALLOWED_SUBCOMMANDS
743
+ ? new Set(
744
+ process.env.CC_PROJECT_ALLOWED_SUBCOMMANDS.split(",")
745
+ .map((s) => s.trim())
746
+ .filter(Boolean),
747
+ )
748
+ : null;
749
+
750
+ if (allowedSet && allowedSet.size > 0) {
751
+ program.commands = program.commands.filter((cmd) =>
752
+ allowedSet.has(cmd.name()),
753
+ );
754
+ }
755
+
723
756
  return program;
724
757
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Shared helpers for the "Governance V2" pattern used across 86+ surfaces
3
+ * (iter16–iter28 V2 ports) in packages/cli/src/lib/*.js.
4
+ *
5
+ * Every gov-V2 module declares roughly the same scaffolding:
6
+ * - two Maps (profiles + jobs/sessions/etc.)
7
+ * - four numeric caps (maxActivePerOwner, maxPendingPerProfile,
8
+ * idleMs, stuckMs) with matching get/set pairs
9
+ * - a positive-integer validator
10
+ * - 4-state and 5-state transition tables + validators
11
+ * - a _resetStateXxxGovV2() that re-initialises everything
12
+ *
13
+ * The shapes vary only in names and defaults, so these helpers let new
14
+ * (and migrated) modules replace ~50 lines of boilerplate with ~10.
15
+ *
16
+ * Migration guide: see GOV_V2_MIGRATION.md at repo root.
17
+ * `packages/cli` is an ESM package, so this helper exports ESM bindings.
18
+ */
19
+
20
+ /**
21
+ * Validate that `n` is a finite positive integer. Throws with a label.
22
+ */
23
+ function positiveInteger(n, label) {
24
+ const v = Math.floor(Number(n));
25
+ if (!Number.isFinite(v) || v <= 0) {
26
+ throw new Error(`${label} must be positive integer`);
27
+ }
28
+ return v;
29
+ }
30
+
31
+ /**
32
+ * Build a `checkTransition(from, to)` fn from a state-transition map.
33
+ * Throws with a domain-specific message when the transition is invalid.
34
+ *
35
+ * @param {Map<string, Set<string>>} transitions
36
+ * @param {string} label e.g. "shgov profile"
37
+ */
38
+ function createTransitionChecker(transitions, label) {
39
+ return function checkTransition(from, to) {
40
+ const allowed = transitions.get(from);
41
+ if (!allowed || !allowed.has(to)) {
42
+ throw new Error(`invalid ${label} transition ${from} → ${to}`);
43
+ }
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Build a suite of cap (limit) accessors wired to a single mutable state
49
+ * object. Returns an object with `get<Name>V2()` / `set<Name>V2(n)` pairs
50
+ * for every field in `defaults`. The set variants run `positiveInteger`
51
+ * validation and apply the new value; reset() re-applies the defaults.
52
+ *
53
+ * @param {Object<string, number>} defaults map of capName → default value
54
+ * @returns {{ caps, resetCaps, setters, getters }}
55
+ */
56
+ function createCapRegistry(defaults) {
57
+ const caps = { ...defaults };
58
+ const setters = {};
59
+ const getters = {};
60
+ for (const [name, defaultValue] of Object.entries(defaults)) {
61
+ setters[name] = (n) => {
62
+ caps[name] = positiveInteger(n, name);
63
+ };
64
+ getters[name] = () => caps[name];
65
+ // sanity: reject non-numeric defaults
66
+ if (!Number.isFinite(defaultValue) || defaultValue <= 0) {
67
+ throw new Error(
68
+ `governance-v2-helpers: default for ${name} must be a positive number`,
69
+ );
70
+ }
71
+ }
72
+ function resetCaps() {
73
+ for (const [name, defaultValue] of Object.entries(defaults)) {
74
+ caps[name] = defaultValue;
75
+ }
76
+ }
77
+ return { caps, resetCaps, setters, getters };
78
+ }
79
+
80
+ /**
81
+ * Count entries in a Map whose values satisfy `predicate`.
82
+ */
83
+ function countBy(map, predicate) {
84
+ let c = 0;
85
+ for (const v of map.values()) {
86
+ if (predicate(v)) c++;
87
+ }
88
+ return c;
89
+ }
90
+
91
+ /**
92
+ * Build a state-transition Map from a plain object
93
+ * { active: ["paused", "archived"], paused: ["active"] }
94
+ */
95
+ function buildTransitionMap(table) {
96
+ const m = new Map();
97
+ for (const [from, tos] of Object.entries(table)) {
98
+ m.set(from, new Set(tos));
99
+ }
100
+ return m;
101
+ }
102
+
103
+ export {
104
+ positiveInteger,
105
+ createTransitionChecker,
106
+ createCapRegistry,
107
+ countBy,
108
+ buildTransitionMap,
109
+ };