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.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BgQaSGlp.js → AIOps-ebtJGjAG.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BhTVuWPn.js → ActionButton-CypkRN-G.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BO1jPw9V.js → Analytics-B2JMlIng.js} +1 -1
- package/src/assets/web-panel/assets/{AppLayout-VX_9bmhs.js → AppLayout-B8QQ4pk7.js} +2 -2
- package/src/assets/web-panel/assets/{Audit-Qvf0oq_Y.js → Audit-BoYaAyFa.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-TjzfYOeC.js → Backup-BfackGZ5.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BLuumXTZ.js → BaseInput-C06FUpDz.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CXrCvi7a.js → Chat-BWxMkBYZ.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox--DOdo_Nz.js → Checkbox-XJMvS3PV.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DAJaraZ7.js → Codegen-CzR462RK.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CezPMM8r.js → Col-BQHpLNCA.js} +1 -1
- package/src/assets/web-panel/assets/{Community-95cmfh28.js → Community-BWRRbJYd.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-GlEOCt_z.js → Compact-BunoKIy9.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Np9rHlBD.js → Compliance-CtJfZctm.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DhobloKt.js → Cowork-ER5-_bod.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-DsvEYSze.js → Cron-C80jYBw1.js} +1 -1
- package/src/assets/web-panel/assets/{Crosschain-D5h0edtx.js → Crosschain-fEMlCNsL.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BgEIvDQh.js → DID-BZpctKmU.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-BQ9z2Vs6.js → Dashboard-RQhZmLi4.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-zDSAIpGs.js → Dropdown-CrpwS84l.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Bhn4LZO4.js → Federation-BEQyZtdR.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-D_NMXINy.js → FormItemContext-DCwvl6Vh.js} +1 -1
- package/src/assets/web-panel/assets/{Git-D6iEbQtW.js → Git-6FihOxMK.js} +1 -1
- package/src/assets/web-panel/assets/{Governance-DXZrVh5v.js → Governance-DlBLHSlJ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DxgJQVdT.js → Inference-DdSokzV0.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B6iB6jMD.js → KnowledgeGraph-bg8GBHMr.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Bam2v_rb.js → Logs-DdFYdLQ-.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-hmoE5rhf.js → Marketplace-DjnlAeYF.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-XLZTBQ9E.js → McpTools-Czs41YUh.js} +1 -1
- package/src/assets/web-panel/assets/{Memory-C-NXlOEE.js → Memory-CX0b3c8D.js} +1 -1
- package/src/assets/web-panel/assets/{MobileBridge-Xi_Xzm7u.js → MobileBridge-BoFGb9Mm.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BVxyrtNp.js → MobileProjects-B8qQ9H-0.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DY8puwdE.js → Mtc-CRF1NLae.js} +1 -1
- package/src/assets/web-panel/assets/{MtcAudit-DJShrgkU.js → MtcAudit-CdCm70cJ.js} +1 -1
- package/src/assets/web-panel/assets/{Multisig-CsNe5Aor.js → Multisig-yoZlpq2Y.js} +1 -1
- package/src/assets/web-panel/assets/{NLProgramming-Me0yNI2U.js → NLProgramming-hFCgqDxJ.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-D_g-p2Tg.js → Notes-DC8pnxs-.js} +1 -1
- package/src/assets/web-panel/assets/{NotificationSettings-DT7K69Vl.js → NotificationSettings-DDVg5Nc8.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-D4hlrApo.js → Organization-0YCtAFMS.js} +1 -1
- package/src/assets/web-panel/assets/{Overflow-BAvaHETK.js → Overflow-DhPLoAdz.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D570BCdt.js → P2P-BWpuJhkD.js} +1 -1
- package/src/assets/web-panel/assets/{Permissions-5lYj-mLS.js → Permissions-B4IrizO9.js} +1 -1
- package/src/assets/web-panel/assets/{PersonalDataHub-DwFzyawJ.js → PersonalDataHub-ZbziiUr6.js} +1 -1
- package/src/assets/web-panel/assets/{Pipeline-Ce72g3py.js → Pipeline-M65jR6sq.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BsLyD5gE.js → Privacy-BeO8zLup.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CE0ATemT.js → ProjectInit-Ck_ZjrVZ.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectSettings-Cnevuf69.js → ProjectSettings-ijn-97s0.js} +1 -1
- package/src/assets/web-panel/assets/{Projects-Bwe8FTDK.js → Projects-BsNBemeh.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BlN2_n0e.js → Providers-CI7UxKVO.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-D90_0612.js → QuickAsk-dE2M1KOB.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CAdqobLh.js → Recommend-B30EgbKS.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-gAeuZfsd.js → Reputation-CV6n7wMx.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dsz4KRaU.js → Row-DSaoTjlN.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BuTu0HKl.js → RssFeed-_NgBmHaC.js} +1 -1
- package/src/assets/web-panel/assets/{Search-B6jcG3iF.js → Search-B341ooTV.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CcEX8TPI.js → Security-CqCQD8hf.js} +1 -1
- package/src/assets/web-panel/assets/{Services-BBbLFK_e.js → Services-Cju_95rB.js} +1 -1
- package/src/assets/web-panel/assets/{Skeleton-CARhezYN.js → Skeleton-kh_uW22l.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B9dCFkON.js → Skills-BVRPgciI.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Ds2N-wGY.js → Sla-y-vKFYkI.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-flimA105.js → SpeechSettings-BXJm9zyo.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B1tJs8pr.js → SyncSettings-BmHZR-Kv.js} +1 -1
- package/src/assets/web-panel/assets/{Tasks-Cs7_NAOr.js → Tasks-C7zmYW9f.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D3iXQ2gE.js → Templates-DVEG7FdA.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Z6mUmAVT.js → Tenant-CpvjzPCo.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-PIR1l-39.js → Terminal-D_Wpp2iE.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BLjbrTa1.js → Tokens-QBrjdNqi.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CIJzC6pS.js → Trigger-BhR_VEvQ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-D_-71Gjt.js → Trust-C0xhM2lC.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CwjoYze6.js → UkeySign-BnyP-W3-.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BUvHAwqx.js → VideoEditing-C5Y8MyEK.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Ds-Mgv8V.js → Wallet-DzCPCQNF.js} +1 -1
- package/src/assets/web-panel/assets/{WebAuthn-B1HCLfzA.js → WebAuthn-6X5bLtHU.js} +1 -1
- package/src/assets/web-panel/assets/{WorkflowEditor-BiJ0uOBt.js → WorkflowEditor-ekS27G9f.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CRWoVJL9.js → chat-BikodUwh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D90H7N7U.js → colors-8yIg5K7E.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C6U27ikN.js → compact-item-MLWo5-GY.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-dR8PtJ-p.js → createContext-nir7ccDv.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-CFe-8hdI.js → hasIn-DxUIHW2P.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbm7lyTa.js → index-4SFekeAy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DrPvpDNw.js → index-78olN7S9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWUElbLB.js → index-8qrwsaKy.js} +1 -1
- package/src/assets/web-panel/assets/{index-ml1s2nd5.js → index-9Y0IyfeM.js} +1 -1
- package/src/assets/web-panel/assets/{index-w5t-vMNO.js → index-BSIaRmzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZopu374.js → index-BURKtxBq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlIs_MhZ.js → index-BcyG-9vV.js} +1 -1
- package/src/assets/web-panel/assets/index-BeA3spHc.js +1 -0
- package/src/assets/web-panel/assets/{index-DOW32kkX.js → index-BgHPrMXP.js} +1 -1
- package/src/assets/web-panel/assets/{index-nrBwYuLT.js → index-BgQtoOHc.js} +1 -1
- package/src/assets/web-panel/assets/{index-gLrDgd8C.js → index-BkMtxzcM.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5fuCdMf.js → index-Bn5VWKW1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKAuW9ip.js → index-Bpa9senE.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFbyZ4VS.js → index-C-UB9bYd.js} +1 -1
- package/src/assets/web-panel/assets/{index-rhwSri9e.js → index-CCGf6IJj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DN4iOH17.js → index-C_W1kVtY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCKkZgvL.js → index-C_Xi08tu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du2kakr1.js → index-CeX-HLIi.js} +1 -1
- package/src/assets/web-panel/assets/{index-_UAOyaYh.js → index-CfeuuE7v.js} +1 -1
- package/src/assets/web-panel/assets/{index-cMms0yaN.js → index-CkSN2Ki_.js} +1 -1
- package/src/assets/web-panel/assets/{index-zDn5JJTC.js → index-Cp-YnzHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5x3uBml.js → index-DDmc4cig.js} +1 -1
- package/src/assets/web-panel/assets/{index-cERDyhbA.js → index-DLiexKJ2.js} +1 -1
- package/src/assets/web-panel/assets/{index-pOwjlfcg.js → index-DOIryna2.js} +1 -1
- package/src/assets/web-panel/assets/{index-WmmdmN6b.js → index-DSjWvxVr.js} +3 -3
- package/src/assets/web-panel/assets/{index-66g2WGdx.js → index-DVLJ1iGu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DibOj-Y8.js → index-DkIon-Gv.js} +1 -1
- package/src/assets/web-panel/assets/{index-l3oIS_5E.js → index-Dl-O2OkQ.js} +1 -1
- package/src/assets/web-panel/assets/index-DoLRjAoc.js +1 -0
- package/src/assets/web-panel/assets/{index-BOOxNqWQ.js → index-Dx5xZmzt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DEEu_VP1.js → index-S8mYImvf.js} +1 -1
- package/src/assets/web-panel/assets/{index-D43N1wTf.js → index-SUYLhwZI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSIH2KxQ.js → index-TB5vrA0Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvJ3UZ52.js → index-dgZZAsxo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbQx_BA_.js → index-h05fIj9Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-BL7VlSvq.js → index-hu-wjfWv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE1ONO_z.js → index-q3Lr2UzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-5JpRRs-N.js → index-vVrIg9Jk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCKut9LD.js → index-yfNusVbo.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BYQdbBmy.js → initDefaultProps-xUjF_bq0.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BtpQTgfW.js → motion-B019-Q6h.js} +1 -1
- package/src/assets/web-panel/assets/{move-BXl4Tr-C.js → move-D2XYj_gA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D83siT_K.js → omit-AIzzlguv.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DAo4jR1m.js → pickAttrs-CRkEQaLs.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BroqhS4u.js → placementArrow-s4kAStH6.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CQ1ZgLXP.js → responsiveObserve-BCsWrTkb.js} +1 -1
- package/src/assets/web-panel/assets/{slide-tkfTw6kp.js → slide-B_Hggtvv.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CGzZbaVH.js → statusUtils-DnNf15VW.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CDEoUvcb.js → styleChecker-CSQdy9SQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Ba76kVFx.js → useFlexGapSupport-Bt-T27Pf.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-B4iO8Fmn.js → useFs-D78PlgeG.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-vHVV16AJ.js → vnode-BTMmpsWu.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DifdtHK7.js → zoom-CwOTbvKc.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/init.js +10 -0
- package/src/commands/notification.js +260 -0
- package/src/commands/pack.js +319 -0
- package/src/commands/persona.js +160 -1
- package/src/index.js +2 -0
- package/src/lib/packer/pkg-config-generator.js +111 -5
- package/src/runtime/agent-core.js +26 -2
- package/src/assets/web-panel/assets/index-EFyvtYMT.js +0 -1
- 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
|
+
}
|
package/src/commands/pack.js
CHANGED
|
@@ -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
|
+
}
|