chainlesschain 0.162.9 → 0.162.11

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 (143) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BgQaSGlp.js → AIOps-ebtJGjAG.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BhTVuWPn.js → ActionButton-CypkRN-G.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BO1jPw9V.js → Analytics-B2JMlIng.js} +1 -1
  5. package/src/assets/web-panel/assets/{AppLayout-VX_9bmhs.js → AppLayout-B8QQ4pk7.js} +2 -2
  6. package/src/assets/web-panel/assets/{Audit-Qvf0oq_Y.js → Audit-BoYaAyFa.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-TjzfYOeC.js → Backup-BfackGZ5.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BLuumXTZ.js → BaseInput-C06FUpDz.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-CXrCvi7a.js → Chat-BWxMkBYZ.js} +1 -1
  10. package/src/assets/web-panel/assets/{Checkbox--DOdo_Nz.js → Checkbox-XJMvS3PV.js} +1 -1
  11. package/src/assets/web-panel/assets/{Codegen-DAJaraZ7.js → Codegen-CzR462RK.js} +1 -1
  12. package/src/assets/web-panel/assets/{Col-CezPMM8r.js → Col-BQHpLNCA.js} +1 -1
  13. package/src/assets/web-panel/assets/{Community-95cmfh28.js → Community-BWRRbJYd.js} +1 -1
  14. package/src/assets/web-panel/assets/{Compact-GlEOCt_z.js → Compact-BunoKIy9.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compliance-Np9rHlBD.js → Compliance-CtJfZctm.js} +1 -1
  16. package/src/assets/web-panel/assets/{Cowork-DhobloKt.js → Cowork-ER5-_bod.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cron-DsvEYSze.js → Cron-C80jYBw1.js} +1 -1
  18. package/src/assets/web-panel/assets/{Crosschain-D5h0edtx.js → Crosschain-fEMlCNsL.js} +1 -1
  19. package/src/assets/web-panel/assets/{DID-BgEIvDQh.js → DID-BZpctKmU.js} +1 -1
  20. package/src/assets/web-panel/assets/{Dashboard-BQ9z2Vs6.js → Dashboard-RQhZmLi4.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dropdown-zDSAIpGs.js → Dropdown-CrpwS84l.js} +1 -1
  22. package/src/assets/web-panel/assets/{Federation-Bhn4LZO4.js → Federation-BEQyZtdR.js} +1 -1
  23. package/src/assets/web-panel/assets/{FormItemContext-D_NMXINy.js → FormItemContext-DCwvl6Vh.js} +1 -1
  24. package/src/assets/web-panel/assets/{Git-D6iEbQtW.js → Git-6FihOxMK.js} +1 -1
  25. package/src/assets/web-panel/assets/{Governance-DXZrVh5v.js → Governance-DlBLHSlJ.js} +1 -1
  26. package/src/assets/web-panel/assets/{Inference-DxgJQVdT.js → Inference-DdSokzV0.js} +1 -1
  27. package/src/assets/web-panel/assets/{KnowledgeGraph-B6iB6jMD.js → KnowledgeGraph-bg8GBHMr.js} +1 -1
  28. package/src/assets/web-panel/assets/{Logs-Bam2v_rb.js → Logs-DdFYdLQ-.js} +1 -1
  29. package/src/assets/web-panel/assets/{Marketplace-hmoE5rhf.js → Marketplace-DjnlAeYF.js} +1 -1
  30. package/src/assets/web-panel/assets/{McpTools-XLZTBQ9E.js → McpTools-Czs41YUh.js} +1 -1
  31. package/src/assets/web-panel/assets/{Memory-C-NXlOEE.js → Memory-CX0b3c8D.js} +1 -1
  32. package/src/assets/web-panel/assets/{MobileBridge-Xi_Xzm7u.js → MobileBridge-BoFGb9Mm.js} +1 -1
  33. package/src/assets/web-panel/assets/{MobileProjects-BVxyrtNp.js → MobileProjects-B8qQ9H-0.js} +1 -1
  34. package/src/assets/web-panel/assets/{Mtc-DY8puwdE.js → Mtc-CRF1NLae.js} +1 -1
  35. package/src/assets/web-panel/assets/{MtcAudit-DJShrgkU.js → MtcAudit-CdCm70cJ.js} +1 -1
  36. package/src/assets/web-panel/assets/{Multisig-CsNe5Aor.js → Multisig-yoZlpq2Y.js} +1 -1
  37. package/src/assets/web-panel/assets/{NLProgramming-Me0yNI2U.js → NLProgramming-hFCgqDxJ.js} +1 -1
  38. package/src/assets/web-panel/assets/{Notes-D_g-p2Tg.js → Notes-DC8pnxs-.js} +1 -1
  39. package/src/assets/web-panel/assets/{NotificationSettings-DT7K69Vl.js → NotificationSettings-DDVg5Nc8.js} +1 -1
  40. package/src/assets/web-panel/assets/{Organization-D4hlrApo.js → Organization-0YCtAFMS.js} +1 -1
  41. package/src/assets/web-panel/assets/{Overflow-BAvaHETK.js → Overflow-DhPLoAdz.js} +1 -1
  42. package/src/assets/web-panel/assets/{P2P-D570BCdt.js → P2P-BWpuJhkD.js} +1 -1
  43. package/src/assets/web-panel/assets/{Permissions-5lYj-mLS.js → Permissions-B4IrizO9.js} +1 -1
  44. package/src/assets/web-panel/assets/{PersonalDataHub-DwFzyawJ.js → PersonalDataHub-ZbziiUr6.js} +1 -1
  45. package/src/assets/web-panel/assets/{Pipeline-Ce72g3py.js → Pipeline-M65jR6sq.js} +1 -1
  46. package/src/assets/web-panel/assets/{Privacy-BsLyD5gE.js → Privacy-BeO8zLup.js} +1 -1
  47. package/src/assets/web-panel/assets/{ProjectInit-CE0ATemT.js → ProjectInit-Ck_ZjrVZ.js} +1 -1
  48. package/src/assets/web-panel/assets/{ProjectSettings-Cnevuf69.js → ProjectSettings-ijn-97s0.js} +1 -1
  49. package/src/assets/web-panel/assets/{Projects-Bwe8FTDK.js → Projects-BsNBemeh.js} +1 -1
  50. package/src/assets/web-panel/assets/{Providers-BlN2_n0e.js → Providers-CI7UxKVO.js} +1 -1
  51. package/src/assets/web-panel/assets/{QuickAsk-D90_0612.js → QuickAsk-dE2M1KOB.js} +1 -1
  52. package/src/assets/web-panel/assets/{Recommend-CAdqobLh.js → Recommend-B30EgbKS.js} +1 -1
  53. package/src/assets/web-panel/assets/{Reputation-gAeuZfsd.js → Reputation-CV6n7wMx.js} +1 -1
  54. package/src/assets/web-panel/assets/{Row-Dsz4KRaU.js → Row-DSaoTjlN.js} +1 -1
  55. package/src/assets/web-panel/assets/{RssFeed-BuTu0HKl.js → RssFeed-_NgBmHaC.js} +1 -1
  56. package/src/assets/web-panel/assets/{Search-B6jcG3iF.js → Search-B341ooTV.js} +1 -1
  57. package/src/assets/web-panel/assets/{Security-CcEX8TPI.js → Security-CqCQD8hf.js} +1 -1
  58. package/src/assets/web-panel/assets/{Services-BBbLFK_e.js → Services-Cju_95rB.js} +1 -1
  59. package/src/assets/web-panel/assets/{Skeleton-CARhezYN.js → Skeleton-kh_uW22l.js} +1 -1
  60. package/src/assets/web-panel/assets/{Skills-B9dCFkON.js → Skills-BVRPgciI.js} +1 -1
  61. package/src/assets/web-panel/assets/{Sla-Ds2N-wGY.js → Sla-y-vKFYkI.js} +1 -1
  62. package/src/assets/web-panel/assets/{SpeechSettings-flimA105.js → SpeechSettings-BXJm9zyo.js} +1 -1
  63. package/src/assets/web-panel/assets/{SyncSettings-B1tJs8pr.js → SyncSettings-BmHZR-Kv.js} +1 -1
  64. package/src/assets/web-panel/assets/{Tasks-Cs7_NAOr.js → Tasks-C7zmYW9f.js} +1 -1
  65. package/src/assets/web-panel/assets/{Templates-D3iXQ2gE.js → Templates-DVEG7FdA.js} +1 -1
  66. package/src/assets/web-panel/assets/{Tenant-Z6mUmAVT.js → Tenant-CpvjzPCo.js} +1 -1
  67. package/src/assets/web-panel/assets/{Terminal-PIR1l-39.js → Terminal-D_Wpp2iE.js} +1 -1
  68. package/src/assets/web-panel/assets/{Tokens-BLjbrTa1.js → Tokens-QBrjdNqi.js} +1 -1
  69. package/src/assets/web-panel/assets/{Trigger-CIJzC6pS.js → Trigger-BhR_VEvQ.js} +1 -1
  70. package/src/assets/web-panel/assets/{Trust-D_-71Gjt.js → Trust-C0xhM2lC.js} +1 -1
  71. package/src/assets/web-panel/assets/{UkeySign-CwjoYze6.js → UkeySign-BnyP-W3-.js} +1 -1
  72. package/src/assets/web-panel/assets/{VideoEditing-BUvHAwqx.js → VideoEditing-C5Y8MyEK.js} +1 -1
  73. package/src/assets/web-panel/assets/{Wallet-Ds-Mgv8V.js → Wallet-DzCPCQNF.js} +1 -1
  74. package/src/assets/web-panel/assets/{WebAuthn-B1HCLfzA.js → WebAuthn-6X5bLtHU.js} +1 -1
  75. package/src/assets/web-panel/assets/{WorkflowEditor-BiJ0uOBt.js → WorkflowEditor-ekS27G9f.js} +1 -1
  76. package/src/assets/web-panel/assets/{chat-CRWoVJL9.js → chat-BikodUwh.js} +1 -1
  77. package/src/assets/web-panel/assets/{colors-D90H7N7U.js → colors-8yIg5K7E.js} +1 -1
  78. package/src/assets/web-panel/assets/{compact-item-C6U27ikN.js → compact-item-MLWo5-GY.js} +1 -1
  79. package/src/assets/web-panel/assets/{createContext-dR8PtJ-p.js → createContext-nir7ccDv.js} +1 -1
  80. package/src/assets/web-panel/assets/{hasIn-CFe-8hdI.js → hasIn-DxUIHW2P.js} +1 -1
  81. package/src/assets/web-panel/assets/{index-Cbm7lyTa.js → index-4SFekeAy.js} +1 -1
  82. package/src/assets/web-panel/assets/{index-DrPvpDNw.js → index-78olN7S9.js} +1 -1
  83. package/src/assets/web-panel/assets/{index-BWUElbLB.js → index-8qrwsaKy.js} +1 -1
  84. package/src/assets/web-panel/assets/{index-ml1s2nd5.js → index-9Y0IyfeM.js} +1 -1
  85. package/src/assets/web-panel/assets/{index-w5t-vMNO.js → index-BSIaRmzU.js} +1 -1
  86. package/src/assets/web-panel/assets/{index-BZopu374.js → index-BURKtxBq.js} +1 -1
  87. package/src/assets/web-panel/assets/{index-DlIs_MhZ.js → index-BcyG-9vV.js} +1 -1
  88. package/src/assets/web-panel/assets/index-BeA3spHc.js +1 -0
  89. package/src/assets/web-panel/assets/{index-DOW32kkX.js → index-BgHPrMXP.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-nrBwYuLT.js → index-BgQtoOHc.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-gLrDgd8C.js → index-BkMtxzcM.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-B5fuCdMf.js → index-Bn5VWKW1.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BKAuW9ip.js → index-Bpa9senE.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CFbyZ4VS.js → index-C-UB9bYd.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-rhwSri9e.js → index-CCGf6IJj.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DN4iOH17.js → index-C_W1kVtY.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BCKkZgvL.js → index-C_Xi08tu.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Du2kakr1.js → index-CeX-HLIi.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-_UAOyaYh.js → index-CfeuuE7v.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-cMms0yaN.js → index-CkSN2Ki_.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-zDn5JJTC.js → index-Cp-YnzHN.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-D5x3uBml.js → index-DDmc4cig.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-cERDyhbA.js → index-DLiexKJ2.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-pOwjlfcg.js → index-DOIryna2.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-WmmdmN6b.js → index-DSjWvxVr.js} +3 -3
  106. package/src/assets/web-panel/assets/{index-66g2WGdx.js → index-DVLJ1iGu.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DibOj-Y8.js → index-DkIon-Gv.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-l3oIS_5E.js → index-Dl-O2OkQ.js} +1 -1
  109. package/src/assets/web-panel/assets/index-DoLRjAoc.js +1 -0
  110. package/src/assets/web-panel/assets/{index-BOOxNqWQ.js → index-Dx5xZmzt.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DEEu_VP1.js → index-S8mYImvf.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D43N1wTf.js → index-SUYLhwZI.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CSIH2KxQ.js → index-TB5vrA0Z.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CvJ3UZ52.js → index-dgZZAsxo.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BbQx_BA_.js → index-h05fIj9Q.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BL7VlSvq.js → index-hu-wjfWv.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CE1ONO_z.js → index-q3Lr2UzW.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-5JpRRs-N.js → index-vVrIg9Jk.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BCKut9LD.js → index-yfNusVbo.js} +1 -1
  120. package/src/assets/web-panel/assets/{initDefaultProps-BYQdbBmy.js → initDefaultProps-xUjF_bq0.js} +1 -1
  121. package/src/assets/web-panel/assets/{motion-BtpQTgfW.js → motion-B019-Q6h.js} +1 -1
  122. package/src/assets/web-panel/assets/{move-BXl4Tr-C.js → move-D2XYj_gA.js} +1 -1
  123. package/src/assets/web-panel/assets/{omit-D83siT_K.js → omit-AIzzlguv.js} +1 -1
  124. package/src/assets/web-panel/assets/{pickAttrs-DAo4jR1m.js → pickAttrs-CRkEQaLs.js} +1 -1
  125. package/src/assets/web-panel/assets/{placementArrow-BroqhS4u.js → placementArrow-s4kAStH6.js} +1 -1
  126. package/src/assets/web-panel/assets/{responsiveObserve-CQ1ZgLXP.js → responsiveObserve-BCsWrTkb.js} +1 -1
  127. package/src/assets/web-panel/assets/{slide-tkfTw6kp.js → slide-B_Hggtvv.js} +1 -1
  128. package/src/assets/web-panel/assets/{statusUtils-CGzZbaVH.js → statusUtils-DnNf15VW.js} +1 -1
  129. package/src/assets/web-panel/assets/{styleChecker-CDEoUvcb.js → styleChecker-CSQdy9SQ.js} +1 -1
  130. package/src/assets/web-panel/assets/{useFlexGapSupport-Ba76kVFx.js → useFlexGapSupport-Bt-T27Pf.js} +1 -1
  131. package/src/assets/web-panel/assets/{useFs-B4iO8Fmn.js → useFs-D78PlgeG.js} +1 -1
  132. package/src/assets/web-panel/assets/{vnode-vHVV16AJ.js → vnode-BTMmpsWu.js} +1 -1
  133. package/src/assets/web-panel/assets/{zoom-DifdtHK7.js → zoom-CwOTbvKc.js} +1 -1
  134. package/src/assets/web-panel/index.html +1 -1
  135. package/src/commands/init.js +10 -0
  136. package/src/commands/notification.js +260 -0
  137. package/src/commands/pack.js +319 -0
  138. package/src/commands/persona.js +160 -1
  139. package/src/index.js +2 -0
  140. package/src/lib/packer/pkg-config-generator.js +111 -5
  141. package/src/runtime/agent-core.js +26 -2
  142. package/src/assets/web-panel/assets/index-EFyvtYMT.js +0 -1
  143. package/src/assets/web-panel/assets/index-ohea9DVW.js +0 -1
@@ -0,0 +1,260 @@
1
+ /**
2
+ * `cc notification send` — push a notification to a paired mobile device.
3
+ *
4
+ * Wire model: the desktop Electron app (when running with --web-shell) starts
5
+ * a ws-bridge on an OS-assigned port and writes the bound URL into
6
+ * `~/.chainlesschain/desktop.port`. We read that file, open a WebSocket, and
7
+ * invoke the `notification.send-mobile` topic which routes to
8
+ * remoteGateway.handlers.notification.sendToMobile inside the desktop
9
+ * process, which in turn pushes over the P2P DC to the paired iPhone/Android.
10
+ *
11
+ * Exit codes:
12
+ * 0 success
13
+ * 2 desktop port file missing or stale (desktop app not running)
14
+ * 3 desktop returned ok:false / handler unavailable / network error
15
+ * 4 invalid args
16
+ *
17
+ * Implemented as part of #21 v1.3+ to unblock plan
18
+ * `iOS_Phase_6_0_RealDevice_E2E_Plan.md` §6 D2/D3/D6 reproducer.
19
+ */
20
+
21
+ import fs from "fs";
22
+ import path from "path";
23
+ import os from "os";
24
+ import crypto from "crypto";
25
+ import WebSocket from "ws";
26
+
27
+ const PORT_FILE = path.join(os.homedir(), ".chainlesschain", "desktop.port");
28
+ const TOPIC = "notification.send-mobile";
29
+ const RESULT_TOPIC = "notification.send-mobile.result";
30
+
31
+ export function registerNotificationCommand(program) {
32
+ const cmd = program
33
+ .command("notification")
34
+ .alias("notif")
35
+ .description("Push notifications to paired mobile devices");
36
+
37
+ cmd
38
+ .command("send")
39
+ .description("Send a push notification to a paired iPhone/Android")
40
+ .requiredOption(
41
+ "--target <did>",
42
+ "Target device DID (paired iPhone/Android)",
43
+ )
44
+ .requiredOption("--title <title>", "Notification title")
45
+ .option("--body <body>", "Notification body", "")
46
+ .option(
47
+ "--silenced",
48
+ "Send envelope without ringing (delivery-only, for quiet hours test)",
49
+ )
50
+ .option("--type <type>", "Notification type", "app")
51
+ .option("--timeout <ms>", "RPC timeout in milliseconds", "10000")
52
+ .option("--json", "Emit machine-readable JSON instead of human text")
53
+ .action(async (options) => {
54
+ const exitCode = await runSendNotification(options);
55
+ process.exit(exitCode);
56
+ });
57
+ }
58
+
59
+ export async function runSendNotification(options, _deps = {}) {
60
+ const fsDep = _deps.fs || fs;
61
+ const wsDep = _deps.WebSocket || WebSocket;
62
+ const portFilePath = _deps.portFilePath || PORT_FILE;
63
+ const log = _deps.log || console.log;
64
+ const err = _deps.err || ((m) => console.error(m));
65
+
66
+ let descriptor;
67
+ try {
68
+ const raw = fsDep.readFileSync(portFilePath, "utf-8");
69
+ descriptor = JSON.parse(raw);
70
+ } catch (e) {
71
+ if (e.code === "ENOENT") {
72
+ err(
73
+ options.json
74
+ ? JSON.stringify({
75
+ success: false,
76
+ error: "desktop_not_running",
77
+ portFile: portFilePath,
78
+ })
79
+ : `桌面未运行(找不到 ${portFilePath})。请先启动 ChainlessChain 桌面 app (\`cd desktop-app-vue && npm run dev\`)。`,
80
+ );
81
+ return 2;
82
+ }
83
+ err(
84
+ options.json
85
+ ? JSON.stringify({
86
+ success: false,
87
+ error: "port_file_unreadable",
88
+ detail: e.message,
89
+ })
90
+ : `读取桌面 port 文件失败: ${e.message}`,
91
+ );
92
+ return 2;
93
+ }
94
+
95
+ if (!descriptor || typeof descriptor.wsUrl !== "string") {
96
+ err(
97
+ options.json
98
+ ? JSON.stringify({
99
+ success: false,
100
+ error: "port_file_malformed",
101
+ descriptor,
102
+ })
103
+ : `桌面 port 文件格式错误: ${JSON.stringify(descriptor)}`,
104
+ );
105
+ return 2;
106
+ }
107
+
108
+ // Stale-pid check: if the writing process is gone, the port is dead.
109
+ if (typeof descriptor.pid === "number" && descriptor.pid > 0) {
110
+ try {
111
+ // process.kill with signal 0 doesn't actually send anything — it only
112
+ // tests if the pid exists and we can signal it.
113
+ process.kill(descriptor.pid, 0);
114
+ } catch (e) {
115
+ if (e.code === "ESRCH") {
116
+ err(
117
+ options.json
118
+ ? JSON.stringify({
119
+ success: false,
120
+ error: "desktop_stale_pid",
121
+ pid: descriptor.pid,
122
+ })
123
+ : `桌面 port 文件存在但 pid=${descriptor.pid} 已退出(残留文件,可手动 rm ${portFilePath} 或重启桌面)。`,
124
+ );
125
+ return 2;
126
+ }
127
+ // EPERM = process exists but we can't signal it (still alive, fine)
128
+ }
129
+ }
130
+
131
+ const requestId = crypto.randomUUID();
132
+ // ws-bridge protocol: frame.type is the topic name; notification semantic
133
+ // type rides on frame.notificationType.
134
+ const frame = {
135
+ type: TOPIC,
136
+ id: requestId,
137
+ title: options.title,
138
+ body: options.body || "",
139
+ target: options.target,
140
+ silent: !!options.silenced,
141
+ notificationType: options.type,
142
+ };
143
+
144
+ const timeoutMs = Math.max(
145
+ 1000,
146
+ Math.min(120000, parseInt(options.timeout, 10) || 10000),
147
+ );
148
+
149
+ const result = await new Promise((resolve) => {
150
+ let resolved = false;
151
+ const finish = (value) => {
152
+ if (resolved) return;
153
+ resolved = true;
154
+ resolve(value);
155
+ };
156
+
157
+ let socket;
158
+ try {
159
+ socket = new wsDep(descriptor.wsUrl);
160
+ } catch (e) {
161
+ finish({
162
+ success: false,
163
+ error: "ws_construct_failed",
164
+ detail: e.message,
165
+ });
166
+ return;
167
+ }
168
+
169
+ const timer = setTimeout(() => {
170
+ try {
171
+ socket.terminate();
172
+ } catch {
173
+ /* ignore */
174
+ }
175
+ finish({ success: false, error: "rpc_timeout", timeoutMs });
176
+ }, timeoutMs);
177
+
178
+ socket.on("open", () => {
179
+ try {
180
+ socket.send(JSON.stringify(frame));
181
+ } catch (e) {
182
+ clearTimeout(timer);
183
+ finish({ success: false, error: "ws_send_failed", detail: e.message });
184
+ }
185
+ });
186
+
187
+ socket.on("message", (raw) => {
188
+ let response;
189
+ try {
190
+ response = JSON.parse(raw.toString("utf-8"));
191
+ } catch (e) {
192
+ clearTimeout(timer);
193
+ try {
194
+ socket.close();
195
+ } catch {
196
+ /* ignore */
197
+ }
198
+ finish({
199
+ success: false,
200
+ error: "invalid_response_json",
201
+ detail: e.message,
202
+ });
203
+ return;
204
+ }
205
+ if (response?.id !== requestId) {
206
+ // Ignore unrelated frames (ws-bridge may have other subscribers).
207
+ return;
208
+ }
209
+ clearTimeout(timer);
210
+ try {
211
+ socket.close();
212
+ } catch {
213
+ /* ignore */
214
+ }
215
+ if (response.type !== RESULT_TOPIC) {
216
+ finish({
217
+ success: false,
218
+ error: "unexpected_response_topic",
219
+ response,
220
+ });
221
+ return;
222
+ }
223
+ if (response.ok === true) {
224
+ finish({ success: true, result: response.result });
225
+ } else {
226
+ finish({ success: false, error: response.error || "unknown_error" });
227
+ }
228
+ });
229
+
230
+ socket.on("error", (e) => {
231
+ clearTimeout(timer);
232
+ finish({
233
+ success: false,
234
+ error: "ws_error",
235
+ detail: e?.message || String(e),
236
+ });
237
+ });
238
+ });
239
+
240
+ if (result.success) {
241
+ if (options.json) {
242
+ log(JSON.stringify(result));
243
+ } else {
244
+ log(`✔ 通知已推送 (target=${options.target}, title="${options.title}")`);
245
+ if (result.result && typeof result.result === "object") {
246
+ log(` 详情: ${JSON.stringify(result.result)}`);
247
+ }
248
+ }
249
+ return 0;
250
+ }
251
+
252
+ if (options.json) {
253
+ err(JSON.stringify(result));
254
+ } else {
255
+ err(
256
+ `✖ 推送失败: ${result.error}${result.detail ? ` — ${result.detail}` : ""}`,
257
+ );
258
+ }
259
+ return 3;
260
+ }
@@ -23,6 +23,7 @@ import {
23
23
  scheduleReplace,
24
24
  ApplyError,
25
25
  } from "../lib/packer/pack-update-applier.js";
26
+ import { askConfirm } from "../lib/prompts.js";
26
27
  import { VERSION } from "../constants.js";
27
28
 
28
29
  /**
@@ -221,6 +222,9 @@ export function registerPackCommand(program) {
221
222
  false,
222
223
  )
223
224
  .action(async (opts) => {
225
+ // The shared resolver lives in runAutoUpdate too; kept duplicated here
226
+ // because check-update's behavior tree (no download, --download alone,
227
+ // --download --apply) is distinct enough from auto-update's one-shot.
224
228
  const manifestUrl =
225
229
  opts.manifestUrl ||
226
230
  process.env.CC_PACK_UPDATE_MANIFEST ||
@@ -456,8 +460,323 @@ export function registerPackCommand(program) {
456
460
  process.exit(4);
457
461
  }
458
462
  });
463
+
464
+ // Phase 3g (2026-05-20): one-shot OTA orchestration. Same chain as
465
+ // `check-update --download --apply --restart` but with a confirm prompt
466
+ // before the destructive apply and a friendlier default flag set.
467
+ packCmd
468
+ .command("auto-update")
469
+ .description(
470
+ "One-shot OTA: check → confirm → download → apply (Phase 5a+5b+5c)",
471
+ )
472
+ .option(
473
+ "--manifest-url <url>",
474
+ "Manifest URL (falls back to BAKED.updateManifestUrl or CC_PACK_UPDATE_MANIFEST env)",
475
+ )
476
+ .option(
477
+ "--target <slug>",
478
+ "pkg target to match against manifest.latest.artifacts (defaults to current host)",
479
+ )
480
+ .option(
481
+ "--current <version>",
482
+ "Override the current version (defaults to BAKED.packedCliVersion or CLI VERSION)",
483
+ )
484
+ .option(
485
+ "--target-exe <path>",
486
+ "Path of the exe to replace (defaults to process.execPath)",
487
+ )
488
+ .option(
489
+ "--dest <path>",
490
+ "Download destination (defaults to `<currentExePath>.new` inside a packed exe)",
491
+ )
492
+ .option("-y, --yes", "Skip interactive confirm before applying", false)
493
+ .option(
494
+ "--no-restart",
495
+ "Don't restart the new exe after applying (default: restart)",
496
+ )
497
+ .option("--dry-run", "Check only; don't download or apply", false)
498
+ .option("--json", "Emit JSON instead of human-readable output", false)
499
+ .action(async (opts) => {
500
+ await runAutoUpdate({ opts });
501
+ });
459
502
  }
460
503
 
461
504
  function formatMB(bytes) {
462
505
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
463
506
  }
507
+
508
+ /**
509
+ * `cc pack auto-update` — one-shot OTA orchestration. Chains Phase 5a (check)
510
+ * → Phase 5b (download+verify) → Phase 5c (apply) with an interactive confirm
511
+ * between the check and the destructive apply step.
512
+ *
513
+ * Compared to `cc pack check-update --download --apply --restart`:
514
+ * - Default --restart=true (one-shot UX expects the new exe to come up)
515
+ * - Interactive confirm before apply (skip with -y / --yes)
516
+ * - --json implies --yes (can't prompt in JSON mode)
517
+ * - --dry-run stops after the check stage, just like `check-update` alone
518
+ *
519
+ * Exported separately so tests can call it without standing up a full
520
+ * commander program. The real registration in registerPackCommand below just
521
+ * wires options + delegates here.
522
+ *
523
+ * @param {object} ctx
524
+ * @param {object} ctx.opts parsed commander flags
525
+ * @param {typeof checkPackUpdate} [ctx.checkImpl]
526
+ * @param {typeof downloadAndVerify} [ctx.downloadImpl]
527
+ * @param {typeof scheduleReplace} [ctx.applyImpl]
528
+ * @param {typeof askConfirm} [ctx.confirmImpl]
529
+ * @param {object} [ctx.logger] defaults to module logger
530
+ * @param {(code:number)=>void} [ctx.exit] defaults to process.exit
531
+ * @returns {Promise<{action:"check"|"up-to-date"|"declined"|"applied", result?:object}>}
532
+ */
533
+ export async function runAutoUpdate({
534
+ opts,
535
+ checkImpl = checkPackUpdate,
536
+ downloadImpl = downloadAndVerify,
537
+ applyImpl = scheduleReplace,
538
+ confirmImpl = askConfirm,
539
+ logger: log = logger,
540
+ exit = (code) => process.exit(code),
541
+ }) {
542
+ const manifestUrl =
543
+ opts.manifestUrl ||
544
+ process.env.CC_PACK_UPDATE_MANIFEST ||
545
+ (typeof globalThis.BAKED === "object" &&
546
+ globalThis.BAKED?.updateManifestUrl) ||
547
+ null;
548
+
549
+ if (!manifestUrl) {
550
+ const msg =
551
+ "No manifest URL. Provide --manifest-url, set CC_PACK_UPDATE_MANIFEST, or bake one via `cc pack --update-manifest-url`.";
552
+ if (opts.json) {
553
+ log.log(JSON.stringify({ error: msg, code: "NO_MANIFEST_URL" }));
554
+ } else {
555
+ log.error(msg);
556
+ }
557
+ exit(2);
558
+ return { action: "check" };
559
+ }
560
+
561
+ const currentVersion =
562
+ opts.current ||
563
+ (typeof globalThis.BAKED === "object" &&
564
+ globalThis.BAKED?.packedCliVersion) ||
565
+ VERSION;
566
+ const target = opts.target || defaultPkgTarget();
567
+
568
+ // ── Stage 1: check ──────────────────────────────────────────────────────
569
+ let result;
570
+ try {
571
+ result = await checkImpl({ manifestUrl, currentVersion, target });
572
+ } catch (err) {
573
+ const code = err instanceof PackUpdateError ? err.code : "UNKNOWN";
574
+ if (opts.json) {
575
+ log.log(JSON.stringify({ error: err.message, code }));
576
+ } else {
577
+ log.error(`check failed [${code}]: ${err.message}`);
578
+ }
579
+ exit(3);
580
+ return { action: "check" };
581
+ }
582
+
583
+ if (!result.updateAvailable) {
584
+ if (opts.json) {
585
+ log.log(JSON.stringify({ action: "up-to-date", ...result }));
586
+ } else {
587
+ log.log(
588
+ chalk.green(
589
+ ` ✓ You are on the latest version (${result.currentVersion}).`,
590
+ ),
591
+ );
592
+ }
593
+ return { action: "up-to-date", result };
594
+ }
595
+ if (!result.artifact) {
596
+ const msg = `No artifact for target "${target}" in this manifest.`;
597
+ if (opts.json) {
598
+ log.log(JSON.stringify({ error: msg, code: "NO_ARTIFACT" }));
599
+ } else {
600
+ log.error(msg);
601
+ }
602
+ exit(3);
603
+ return { action: "check" };
604
+ }
605
+
606
+ // Surface the check result before prompting for the destructive step.
607
+ if (!opts.json) {
608
+ log.log(
609
+ chalk.bold(
610
+ `\n Update available: ${result.currentVersion} → ${chalk.green(result.latestVersion)}`,
611
+ ),
612
+ );
613
+ log.log(` Artifact (${target}):`);
614
+ log.log(` ${result.artifact.url}`);
615
+ log.log(chalk.dim(` sha256: ${result.artifact.sha256}`));
616
+ if (result.releaseNotes) {
617
+ log.log(chalk.dim(` Release notes: ${result.releaseNotes}`));
618
+ }
619
+ }
620
+
621
+ if (opts.dryRun) {
622
+ if (opts.json) {
623
+ log.log(JSON.stringify({ action: "check", ...result }, null, 2));
624
+ } else {
625
+ log.log(chalk.dim(`\n --dry-run: skipping download and apply.`));
626
+ }
627
+ return { action: "check", result };
628
+ }
629
+
630
+ // ── Stage 2: confirm ────────────────────────────────────────────────────
631
+ // JSON mode skips the interactive prompt — callers in non-TTY contexts
632
+ // must opt in explicitly via --yes (which also works in human mode).
633
+ const needsConfirm = !opts.yes && !opts.json;
634
+ if (needsConfirm) {
635
+ const ok = await confirmImpl(
636
+ `Download + apply update ${result.currentVersion} → ${result.latestVersion}?`,
637
+ true,
638
+ );
639
+ if (!ok) {
640
+ log.log(chalk.dim(` Cancelled by user.`));
641
+ return { action: "declined", result };
642
+ }
643
+ }
644
+
645
+ // ── Stage 3: download ───────────────────────────────────────────────────
646
+ // Same default as check-update --download: inside packed exe drop next to
647
+ // current; outside, drop into cwd so dev-mode invocations are visible.
648
+ const outputPath = opts.dest
649
+ ? path.resolve(opts.dest)
650
+ : process.pkg
651
+ ? process.execPath + ".new"
652
+ : path.resolve(
653
+ process.cwd(),
654
+ path.basename(new URL(result.artifact.url).pathname) ||
655
+ "pack-update-artifact",
656
+ );
657
+
658
+ if (!opts.json) {
659
+ log.log(
660
+ chalk.bold(
661
+ `\n Downloading ${result.currentVersion} → ${chalk.green(result.latestVersion)}`,
662
+ ),
663
+ );
664
+ log.log(chalk.dim(` → ${outputPath}`));
665
+ }
666
+
667
+ let dl;
668
+ let lastPercent = -1;
669
+ try {
670
+ dl = await downloadImpl({
671
+ url: result.artifact.url,
672
+ sha256: result.artifact.sha256,
673
+ outputPath,
674
+ onProgress: opts.json
675
+ ? undefined
676
+ : ({ bytes, total }) => {
677
+ if (!total) return;
678
+ const pct = Math.floor((bytes / total) * 100);
679
+ if (pct !== lastPercent && pct % 10 === 0) {
680
+ lastPercent = pct;
681
+ log.log(
682
+ chalk.dim(
683
+ ` ${pct}% (${formatMB(bytes)}/${formatMB(total)})`,
684
+ ),
685
+ );
686
+ }
687
+ },
688
+ });
689
+ } catch (err) {
690
+ const code = err instanceof DownloadError ? err.code : "UNKNOWN";
691
+ if (opts.json) {
692
+ log.log(JSON.stringify({ error: err.message, code }));
693
+ } else {
694
+ log.error(`download failed [${code}]: ${err.message}`);
695
+ }
696
+ exit(4);
697
+ return { action: "check", result };
698
+ }
699
+
700
+ if (!opts.json) {
701
+ log.log(
702
+ chalk.green(
703
+ `\n ✓ Downloaded + verified ${formatMB(dl.bytes)} to ${dl.outputPath}`,
704
+ ),
705
+ );
706
+ }
707
+
708
+ // ── Stage 4: apply ──────────────────────────────────────────────────────
709
+ // auto-update defaults restart=true (one-shot UX); --no-restart flips it.
710
+ const restart = opts.restart !== false;
711
+ const targetExe = opts.targetExe || process.execPath;
712
+
713
+ if (!opts.json) {
714
+ log.log(chalk.bold(`\n Applying update → ${targetExe}`));
715
+ if (process.platform === "win32") {
716
+ log.log(
717
+ chalk.yellow(
718
+ ` [Windows] This process will exit after scheduling a sidecar cmd; the replacement runs detached. ${restart ? "The new exe will start automatically." : "Re-launch the exe yourself after exit."}`,
719
+ ),
720
+ );
721
+ } else {
722
+ log.log(
723
+ chalk.dim(
724
+ ` [POSIX] Atomic rename; the running inode survives until exit. ${restart ? "A detached copy will be started now." : "Next launch picks up the new bytes."}`,
725
+ ),
726
+ );
727
+ }
728
+ }
729
+
730
+ let plan;
731
+ try {
732
+ plan = await applyImpl({
733
+ newExePath: dl.outputPath,
734
+ targetExePath: targetExe,
735
+ restart,
736
+ });
737
+ } catch (err) {
738
+ const code = err instanceof ApplyError ? err.code : "UNKNOWN";
739
+ if (opts.json) {
740
+ log.log(JSON.stringify({ error: err.message, code }));
741
+ } else {
742
+ log.error(`apply failed [${code}]: ${err.message}`);
743
+ }
744
+ exit(5);
745
+ return { action: "check", result };
746
+ }
747
+
748
+ if (opts.json) {
749
+ log.log(
750
+ JSON.stringify(
751
+ {
752
+ action: "applied",
753
+ currentVersion: result.currentVersion,
754
+ latestVersion: result.latestVersion,
755
+ download: {
756
+ outputPath: dl.outputPath,
757
+ bytes: dl.bytes,
758
+ sha256: dl.sha256,
759
+ },
760
+ apply: plan,
761
+ },
762
+ null,
763
+ 2,
764
+ ),
765
+ );
766
+ } else {
767
+ log.log(
768
+ chalk.green(
769
+ ` ✓ Apply scheduled: action=${plan.action}${plan.sidecarPath ? `, sidecar=${plan.sidecarPath}` : ""}`,
770
+ ),
771
+ );
772
+ }
773
+
774
+ // Windows sidecar needs us to exit so its tasklist wait can complete. Give
775
+ // it a beat to start polling before we vanish (same dance as check-update
776
+ // --apply at L432-438; harmless on POSIX where action is replace-in-place).
777
+ if (plan.action === "sidecar-cmd") {
778
+ setTimeout(() => exit(0), 500).unref?.();
779
+ }
780
+
781
+ return { action: "applied", result, download: dl, apply: plan };
782
+ }