chainlesschain 0.161.8 → 0.161.9
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 +2 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-Uls1XIO4.js → AIOps-Cbnj25uU.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BpVrefl1.js → ActionButton-DY69UkxV.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Z1Rgg9PA.js → Analytics-DUzoL75H.js} +2 -2
- package/src/assets/web-panel/assets/AppLayout-DATUppSv.js +1 -0
- package/src/assets/web-panel/assets/{AppLayout-P7jhSfLy.css → AppLayout-qr7ghxW7.css} +1 -1
- package/src/assets/web-panel/assets/{Audit-Co6yYgmX.js → Audit-CFJh6huz.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-1IZDB3QD.js → Backup-CMTir4tV.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-LYB_wIZP.js → BaseInput-BaWXBDdp.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D-6LDWGl.js → Chat-DjToexEx.js} +3 -3
- package/src/assets/web-panel/assets/{Checkbox-dLnnw7OE.js → Checkbox-DJnhKXhi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Wh6xypMx.js → Codegen-CgsgvRJX.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BT8lkjSw.js → Col-MvMGDJAB.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CdAeMHde.js → Community-q26VGOGu.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-VsImrjgV.js → Compact-DyLYzWoX.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-PScuVx8d.js → Compliance-D_TF9eBc.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-qB8hRW0r.js → Cowork-B9bg988c.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CuSjuE-h.js → Cron-6k9vTFfd.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BqIRd8bv.js → Crosschain-B7fk2aHi.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BT5BJ0DF.js → DID-XhCfTFm2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BX-06zEm.js → Dashboard-Bqk7vtZM.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Pjo3G3Vf.js → Dropdown-CDOOZGv7.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbnYcYhr.js → Federation-NajN4oRO.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C3hf4K1_.js → FormItemContext-DgMHYlMB.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Cmw6_HXW.js → Git-CZC5n1Zu.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BFBvB2G6.js → Governance-Br131eUW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BBdX3YjM.js → Inference-BHlUfcT-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BC-bcvNY.js → KnowledgeGraph-CByROhg-.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B9DuqFeW.js → Logs-D8Ghzk4B.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-Bff1ja4R.js → Marketplace-CnjHPFm7.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DnWqjtkb.js → McpTools-C_vszCBU.js} +1 -1
- package/src/assets/web-panel/assets/{Memory-C6YXBuM7.js → Memory-Cl5c_8In.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-DkkMCDx9.css +1 -0
- package/src/assets/web-panel/assets/MobileBridge-K3KwwCKl.js +3 -0
- package/src/assets/web-panel/assets/{Mtc-r-J4WRfp.js → Mtc-NfZ8xzyu.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-DFqXLHn6.js → MtcAudit-BkktYSXj.js} +1 -1
- package/src/assets/web-panel/assets/Multisig-BLRYDn7_.css +1 -0
- package/src/assets/web-panel/assets/Multisig-qP51bs1T.js +1 -0
- package/src/assets/web-panel/assets/{NLProgramming-s5CrjYto.js → NLProgramming-BhFRuXyi.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-TAbnvDCd.js → Notes-BpcAFy8i.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BZIKI219.js → NotificationSettings-DKNtYYeo.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BXbSljYa.js → Organization-C61VwQEW.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-O2RWHZxH.js → Overflow-WBloac1x.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-B8jBZLh6.js → P2P-DGm7EvDg.js} +2 -2
- package/src/assets/web-panel/assets/{Permissions-Cexi8Jr6.js → Permissions-KFPtoOV6.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-BwIBnNzj.js → Pipeline-CFjFtzZT.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D-LOR9um.js → Privacy-T_Kewe_f.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectSettings-DZ_fdRRJ.js → ProjectSettings-BQl8qTPQ.js} +1 -1
- package/src/assets/web-panel/assets/{Projects-Bis42Cb6.js → Projects-xGNn4Ov9.js} +2 -2
- package/src/assets/web-panel/assets/{Providers-DH6hY4Gx.js → Providers-ByuUztXe.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Ur0bUXHK.js → QuickAsk-BwSXmXvL.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CLDQjSf6.js → Recommend-Cp433_5Z.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-StG-zGil.js → Reputation-DE_JSjyi.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DAWA1cwa.js → Row-DI36Fic1.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-rb2CP-IS.js → RssFeed-DswpSUiI.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Y9qsWaAE.js → Search-Crs2UKhh.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BanZDWBe.js → Security-W8ViXXbq.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DFOMZwqJ.js → Services-BIQdyLJM.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cy48VmyC.js → Skeleton-DDiOevKX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BqdkGRyO.js → Skills-v-Vw_jGa.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-B8lXq9rJ.js → Sla-B-ULmz5m.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CmIkYpWT.js → SpeechSettings--8IIPHri.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-tZDaJZ0S.js → SyncSettings-D0QiEwJn.js} +1 -1
- package/src/assets/web-panel/assets/{Tasks-DxBlrylF.js → Tasks-BNSR9FlM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-ByshgJtu.js → Templates-CFVkaS-d.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DCpEpRf1.js → Tenant-quU-tldS.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BIUug_M_.js → Tokens-DTqFjS4k.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DY4iT_Sv.js → Trigger-ZWVirxhN.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-fAtm0ohe.js → Trust-DmaYf5tg.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CwbV_ITc.js → UkeySign-BeYyXtZW.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CBJ5dw8E.js → VideoEditing-CRdDwD9C.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B1fyWeNC.js → Wallet-H-38f7Pv.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-by6IZMeB.js → WebAuthn-CCQVzZ0W.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DZkBKsSU.js → WorkflowEditor-B1RFE2oN.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CKAWhBMH.js → chat-Z4UoBAeF.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DgRS7Mb2.js → colors-AecMhEAZ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CQUZCSRU.js → compact-item-Dfhm05kA.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DgOciZHM.js → createContext-C7gBVuRm.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-DEXVOypF.js → hasIn-DQarK3iI.js} +1 -1
- package/src/assets/web-panel/assets/icons-B2mcO3YM.js +57 -0
- package/src/assets/web-panel/assets/{index-DjQzFo-O.js → index-1d6L3YWV.js} +1 -1
- package/src/assets/web-panel/assets/{index-jLpDpunJ.js → index-5I_kEh5L.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjpS0vcp.js → index-5P_X26-m.js} +1 -1
- package/src/assets/web-panel/assets/{index-nMw96bEM.js → index-78eTUfD9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYrBEkTv.js → index-B1XdBIuG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQDGHgXQ.js → index-B52lOAOU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bo43zHIV.js → index-BLYMiBRw.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8S-7oDQ.js → index-BxjOmfVA.js} +9 -9
- package/src/assets/web-panel/assets/{index-D2PDzVV6.js → index-C-4d1ug2.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1dIizuf.js → index-C0kyGID6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CS3fI6_d.js → index-C7nHW5ZW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ce57JGJq.js → index-CCUfW7hO.js} +1 -1
- package/src/assets/web-panel/assets/{index-nIXYg_tf.js → index-CFR1Scu8.js} +1 -1
- package/src/assets/web-panel/assets/{index-hfGZzUzb.js → index-CS7_x1br.js} +1 -1
- package/src/assets/web-panel/assets/{index-1EDbawpk.js → index-Ch9ExDNs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEt0RfAY.js → index-CiVezMja.js} +1 -1
- package/src/assets/web-panel/assets/index-Cp5G30tN.js +1 -0
- package/src/assets/web-panel/assets/index-CuPmKar9.js +1 -0
- package/src/assets/web-panel/assets/{index-CCuIY7Ff.js → index-CxDBoEkf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-g1My7q.js → index-Cy_80Fj7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CH5YkJC9.js → index-D3Pxfwcc.js} +1 -1
- package/src/assets/web-panel/assets/{index-_Oe8Zr99.js → index-D4_1yL1c.js} +1 -1
- package/src/assets/web-panel/assets/{index-HE37tYPs.js → index-D841tepe.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZOpqRGzA.js → index-DWbRHjgk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkBSIb9k.js → index-DX4CKtlL.js} +1 -1
- package/src/assets/web-panel/assets/{index-wPVZuwWm.js → index-DdtxWGjo.js} +2 -2
- package/src/assets/web-panel/assets/{index-DW_-28b4.js → index-DfA9S79c.js} +1 -1
- package/src/assets/web-panel/assets/{index-CetlLBjh.js → index-DfAZSzZc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1USa9q3.js → index-Dkh_4r1F.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBRA1wFF.js → index-DtrMx44i.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEnMJ3rw.js → index-Dy2fLm3i.js} +1 -1
- package/src/assets/web-panel/assets/index-L6kKxFAO.js +21 -0
- package/src/assets/web-panel/assets/{index-DpL-43E_.js → index-OqY41c_z.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3tqZAdL.js → index-QgJGoGAK.js} +1 -1
- package/src/assets/web-panel/assets/{index-o6vVX9C_.js → index-YJxjR7t9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQepaPId.js → index-aHqD7CZz.js} +1 -1
- package/src/assets/web-panel/assets/{index-bRI-SvZ-.js → index-d0Iy6r2J.js} +1 -1
- package/src/assets/web-panel/assets/index-eoNKQdFC.js +1 -0
- package/src/assets/web-panel/assets/{index-BMx0R9Vw.js → index-nqKLHlmq.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BSTz5E8D.js → initDefaultProps-CDOGbz6v.js} +1 -1
- package/src/assets/web-panel/assets/{markdown-xjUYbPSW.js → markdown-CsiA8-E5.js} +1 -1
- package/src/assets/web-panel/assets/{markdown-vYH_ziAO.js → markdown-Yo_c6Krv.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B10pjDA-.js → motion-ChIpIzUN.js} +1 -1
- package/src/assets/web-panel/assets/{move-B_PFOXXn.js → move-DbH3dyN8.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Bf31gUOR.js → omit-BJ1K-IDI.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CTOOvnV9.js → pickAttrs-B0q7EpvV.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DfXqIy1B.js → placementArrow-D7XkOUjs.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BonUzZVT.js → responsiveObserve-efcdE4ju.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DtkoQrl3.js → slide-BgAktMzM.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BJlmtjwo.js → statusUtils-CV83LVbI.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CoQ2DFN_.js → styleChecker-D1npYi6G.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BD9H_ICR.js → useFlexGapSupport-DTH3yXTD.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DNCH6P7V.js → useFs-BEvSw9aX.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CAFNU-3N.js → vnode-Duw65sZ1.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Beg44Pk9.js → zoom-CLMtA8PG.js} +1 -1
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/marketplace.js +286 -0
- package/src/commands/multisig.js +471 -0
- package/src/commands/p2p.js +114 -7
- package/src/index.js +2 -0
- package/src/lib/multisig-runtime.js +160 -0
- package/src/lib/p2p-manager.js +96 -2
- package/src/assets/web-panel/assets/AppLayout-GvY1b95g.js +0 -1
- package/src/assets/web-panel/assets/icons-B2G69bhT.js +0 -57
- package/src/assets/web-panel/assets/index-DLnvncqr.js +0 -1
- package/src/assets/web-panel/assets/index-DsmrJv8Y.js +0 -21
- package/src/assets/web-panel/assets/index-Rvjbak1o.js +0 -1
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.2 m-of-n Phase 1d — `cc multisig` CLI surface.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* cc multisig propose <domain> --payload-file <path> [--initiator <did>] [--key <hex|path>]
|
|
6
|
+
* cc multisig sign <proposalId> --signer <did> [--key <hex|path>]
|
|
7
|
+
* cc multisig cancel <proposalId> [--reason <text>]
|
|
8
|
+
* cc multisig list [--state <s>] [--domain <d>] [--limit <n>] [--json]
|
|
9
|
+
* cc multisig show <proposalId> [--json]
|
|
10
|
+
* cc multisig finalize <proposalId> # 业务方完成后标 consumed
|
|
11
|
+
* cc multisig policy show <domain> [--json]
|
|
12
|
+
* cc multisig policy set <domain> --m <M> --members <json|file>
|
|
13
|
+
* cc multisig sweep # expire stale pending
|
|
14
|
+
*
|
|
15
|
+
* 设计:CLI 端持 better-sqlite3 DB (默认 ~/.chainlesschain/multisig.db) +
|
|
16
|
+
* governance log (默认 ~/.chainlesschain/multisig.governance.log)。
|
|
17
|
+
* secretKey 来源:--key <hex> 直接读 / --key-file <path> 读文件 hex。
|
|
18
|
+
* Phase 1d 内 keystore 集成留 v1.3 (与 core-did/UnifiedKeyStore 接通)。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import chalk from "chalk";
|
|
22
|
+
import multisig from "@chainlesschain/core-multisig";
|
|
23
|
+
import {
|
|
24
|
+
openMultisigManager,
|
|
25
|
+
defaultMultisigDbPath,
|
|
26
|
+
defaultMultisigLogPath,
|
|
27
|
+
readSecretKey,
|
|
28
|
+
readJsonArg,
|
|
29
|
+
} from "../lib/multisig-runtime.js";
|
|
30
|
+
|
|
31
|
+
const { normalizePolicy } = multisig;
|
|
32
|
+
|
|
33
|
+
// Phase 2 refactor: open / readKey / readJsonArg moved to lib/multisig-runtime.js
|
|
34
|
+
// so commands/marketplace.js can reuse the same SQLite cascade + manager loader.
|
|
35
|
+
// Aliases below preserve Phase 1 internal names without rewriting every callsite.
|
|
36
|
+
const _openManager = openMultisigManager;
|
|
37
|
+
const _defaultDbPath = defaultMultisigDbPath;
|
|
38
|
+
const _defaultLogPath = defaultMultisigLogPath;
|
|
39
|
+
const _readKey = readSecretKey;
|
|
40
|
+
const _readJsonArg = readJsonArg;
|
|
41
|
+
|
|
42
|
+
function _formatProposalTable(proposal, sigs = []) {
|
|
43
|
+
const lines = [
|
|
44
|
+
`${chalk.bold("ID")} ${proposal.id}`,
|
|
45
|
+
`${chalk.bold("Domain")} ${proposal.domain}`,
|
|
46
|
+
`${chalk.bold("State")} ${_colorState(proposal.state)}`,
|
|
47
|
+
`${chalk.bold("M / N")} ${proposal.thresholdM} / ${proposal.memberSet.length}`,
|
|
48
|
+
`${chalk.bold("Sigs")} ${sigs.length} / ${proposal.thresholdM}`,
|
|
49
|
+
`${chalk.bold("Initiator")} ${proposal.initiatorDid}`,
|
|
50
|
+
`${chalk.bold("Created")} ${new Date(proposal.createdAtMs).toISOString()}`,
|
|
51
|
+
`${chalk.bold("Expires")} ${new Date(proposal.expiresAtMs).toISOString()}`,
|
|
52
|
+
];
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function _colorState(state) {
|
|
57
|
+
switch (state) {
|
|
58
|
+
case "pending":
|
|
59
|
+
return chalk.yellow(state);
|
|
60
|
+
case "reached":
|
|
61
|
+
return chalk.cyan(state);
|
|
62
|
+
case "consumed":
|
|
63
|
+
return chalk.green(state);
|
|
64
|
+
case "cancelled":
|
|
65
|
+
return chalk.gray(state);
|
|
66
|
+
case "expired":
|
|
67
|
+
return chalk.red(state);
|
|
68
|
+
default:
|
|
69
|
+
return state;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function registerMultisigCommand(program) {
|
|
74
|
+
const cmd = program
|
|
75
|
+
.command("multisig")
|
|
76
|
+
.description(
|
|
77
|
+
"M-of-N multi-signature proposals beyond MTC publisher_signature",
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// ===== propose =====
|
|
81
|
+
cmd
|
|
82
|
+
.command("propose <domain>")
|
|
83
|
+
.description("Create a new M-of-N proposal and sign as initiator")
|
|
84
|
+
.requiredOption(
|
|
85
|
+
"--payload-file <path>",
|
|
86
|
+
"Path to JSON file containing the payload to be signed",
|
|
87
|
+
)
|
|
88
|
+
.requiredOption(
|
|
89
|
+
"--initiator <did>",
|
|
90
|
+
"Initiator DID — must be in policy.members",
|
|
91
|
+
)
|
|
92
|
+
.option("--alg <alg>", "Initiator signing alg", "Ed25519")
|
|
93
|
+
.option(
|
|
94
|
+
"--key <hex|path>",
|
|
95
|
+
"Initiator secret key hex string or path to hex file",
|
|
96
|
+
)
|
|
97
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
98
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
99
|
+
.option("--json", "Output JSON instead of human-readable")
|
|
100
|
+
.action(async (domain, options) => {
|
|
101
|
+
const { db, store, mgr, close } = await _openManager(
|
|
102
|
+
options.db,
|
|
103
|
+
options.log,
|
|
104
|
+
);
|
|
105
|
+
try {
|
|
106
|
+
const policy = store.getPolicy(domain);
|
|
107
|
+
if (!policy) {
|
|
108
|
+
console.error(
|
|
109
|
+
chalk.red(
|
|
110
|
+
`No policy set for domain "${domain}". Run: cc multisig policy set ${domain} --m <M> --members <json>`,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
process.exit(2);
|
|
114
|
+
}
|
|
115
|
+
const payload = _readJsonArg(options.payloadFile);
|
|
116
|
+
const secretKey = _readKey(options.key);
|
|
117
|
+
const result = mgr.propose({
|
|
118
|
+
domain,
|
|
119
|
+
payload,
|
|
120
|
+
policy,
|
|
121
|
+
initiator: { did: options.initiator, alg: options.alg, secretKey },
|
|
122
|
+
});
|
|
123
|
+
if (options.json) {
|
|
124
|
+
console.log(
|
|
125
|
+
JSON.stringify(
|
|
126
|
+
{
|
|
127
|
+
proposalId: result.proposal.id,
|
|
128
|
+
reachedThreshold: result.reachedThreshold,
|
|
129
|
+
},
|
|
130
|
+
null,
|
|
131
|
+
2,
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
console.log(chalk.green("✓ Proposal created"));
|
|
136
|
+
console.log(_formatProposalTable(result.proposal, [{}]));
|
|
137
|
+
if (result.reachedThreshold) {
|
|
138
|
+
console.log(
|
|
139
|
+
chalk.cyan("\nThreshold reached on first signature (1-of-N)."),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} finally {
|
|
144
|
+
close();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ===== sign =====
|
|
149
|
+
cmd
|
|
150
|
+
.command("sign <proposalId>")
|
|
151
|
+
.description("Add a signature to an existing pending proposal")
|
|
152
|
+
.requiredOption(
|
|
153
|
+
"--signer <did>",
|
|
154
|
+
"Signer DID — must be in proposal.memberSet",
|
|
155
|
+
)
|
|
156
|
+
.option("--alg <alg>", "Signer signing alg", "Ed25519")
|
|
157
|
+
.option("--key <hex|path>", "Signer secret key hex or path")
|
|
158
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
159
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
160
|
+
.option("--json", "Output JSON")
|
|
161
|
+
.action(async (proposalId, options) => {
|
|
162
|
+
const { mgr, close } = await _openManager(options.db, options.log);
|
|
163
|
+
try {
|
|
164
|
+
const secretKey = _readKey(options.key);
|
|
165
|
+
const r = mgr.sign({
|
|
166
|
+
proposalId,
|
|
167
|
+
signer: { did: options.signer, alg: options.alg, secretKey },
|
|
168
|
+
});
|
|
169
|
+
if (options.json) {
|
|
170
|
+
console.log(JSON.stringify(r, null, 2));
|
|
171
|
+
} else if (r.accepted) {
|
|
172
|
+
console.log(chalk.green("✓ Signature accepted"));
|
|
173
|
+
if (r.reachedThreshold) {
|
|
174
|
+
console.log(
|
|
175
|
+
chalk.cyan("✓ Threshold reached — proposal ready for finalize"),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
console.log(chalk.red(`✗ Signature rejected: ${r.reason}`));
|
|
180
|
+
}
|
|
181
|
+
if (!r.accepted) process.exit(1);
|
|
182
|
+
} finally {
|
|
183
|
+
close();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ===== cancel =====
|
|
188
|
+
cmd
|
|
189
|
+
.command("cancel <proposalId>")
|
|
190
|
+
.description("Cancel a pending or reached proposal")
|
|
191
|
+
.option("--reason <text>", "Free-text reason logged to governance.log")
|
|
192
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
193
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
194
|
+
.option("--json", "Output JSON")
|
|
195
|
+
.action(async (proposalId, options) => {
|
|
196
|
+
const { mgr, close } = await _openManager(options.db, options.log);
|
|
197
|
+
try {
|
|
198
|
+
const r = mgr.cancel(proposalId, options.reason);
|
|
199
|
+
if (options.json) {
|
|
200
|
+
console.log(JSON.stringify(r, null, 2));
|
|
201
|
+
} else if (r.ok) {
|
|
202
|
+
console.log(chalk.gray("✓ Proposal cancelled"));
|
|
203
|
+
} else {
|
|
204
|
+
console.log(chalk.red(`✗ Cancel rejected: ${r.reason}`));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
} finally {
|
|
208
|
+
close();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// ===== finalize =====
|
|
213
|
+
cmd
|
|
214
|
+
.command("finalize <proposalId>")
|
|
215
|
+
.description(
|
|
216
|
+
"Mark a reached proposal as consumed (business operation complete)",
|
|
217
|
+
)
|
|
218
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
219
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
220
|
+
.option("--json", "Output JSON")
|
|
221
|
+
.action(async (proposalId, options) => {
|
|
222
|
+
const { mgr, close } = await _openManager(options.db, options.log);
|
|
223
|
+
try {
|
|
224
|
+
const r = mgr.finalize(proposalId);
|
|
225
|
+
if (options.json) {
|
|
226
|
+
console.log(JSON.stringify(r, null, 2));
|
|
227
|
+
} else if (r.ok) {
|
|
228
|
+
console.log(chalk.green("✓ Proposal finalized (state=consumed)"));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(chalk.red(`✗ Finalize rejected: ${r.reason}`));
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
} finally {
|
|
234
|
+
close();
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ===== list =====
|
|
239
|
+
cmd
|
|
240
|
+
.command("list")
|
|
241
|
+
.description("List proposals")
|
|
242
|
+
.option(
|
|
243
|
+
"--state <s>",
|
|
244
|
+
"Filter by state (pending|reached|consumed|cancelled|expired)",
|
|
245
|
+
)
|
|
246
|
+
.option("--domain <d>", "Filter by domain")
|
|
247
|
+
.option("--limit <n>", "Max rows", (v) => parseInt(v, 10), 50)
|
|
248
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
249
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
250
|
+
.option("--json", "Output JSON")
|
|
251
|
+
.action(async (options) => {
|
|
252
|
+
const { store, close } = await _openManager(options.db, options.log);
|
|
253
|
+
try {
|
|
254
|
+
const proposals = store.listProposals({
|
|
255
|
+
state: options.state,
|
|
256
|
+
domain: options.domain,
|
|
257
|
+
limit: options.limit,
|
|
258
|
+
});
|
|
259
|
+
if (options.json) {
|
|
260
|
+
console.log(
|
|
261
|
+
JSON.stringify(
|
|
262
|
+
proposals.map((p) => ({
|
|
263
|
+
id: p.id,
|
|
264
|
+
domain: p.domain,
|
|
265
|
+
state: p.state,
|
|
266
|
+
m: p.thresholdM,
|
|
267
|
+
n: p.memberSet.length,
|
|
268
|
+
initiatorDid: p.initiatorDid,
|
|
269
|
+
createdAtMs: p.createdAtMs,
|
|
270
|
+
expiresAtMs: p.expiresAtMs,
|
|
271
|
+
})),
|
|
272
|
+
null,
|
|
273
|
+
2,
|
|
274
|
+
),
|
|
275
|
+
);
|
|
276
|
+
} else {
|
|
277
|
+
if (proposals.length === 0) {
|
|
278
|
+
console.log(chalk.gray("(no proposals)"));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
for (const p of proposals) {
|
|
282
|
+
const sigCount = store.getSignatures(p.id).length;
|
|
283
|
+
console.log(
|
|
284
|
+
`${chalk.gray(p.id.padEnd(28))} ${_colorState(p.state.padEnd(10))} ${chalk.bold(
|
|
285
|
+
p.domain.padEnd(24),
|
|
286
|
+
)} ${sigCount}/${p.thresholdM} ${new Date(p.createdAtMs).toISOString()}`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} finally {
|
|
291
|
+
close();
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// ===== show =====
|
|
296
|
+
cmd
|
|
297
|
+
.command("show <proposalId>")
|
|
298
|
+
.description("Show detailed proposal with all signatures")
|
|
299
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
300
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
301
|
+
.option("--json", "Output JSON")
|
|
302
|
+
.action(async (proposalId, options) => {
|
|
303
|
+
const { mgr, close } = await _openManager(options.db, options.log);
|
|
304
|
+
try {
|
|
305
|
+
const got = mgr.get(proposalId);
|
|
306
|
+
if (!got) {
|
|
307
|
+
console.error(chalk.red(`No proposal: ${proposalId}`));
|
|
308
|
+
process.exit(2);
|
|
309
|
+
}
|
|
310
|
+
if (options.json) {
|
|
311
|
+
console.log(
|
|
312
|
+
JSON.stringify(
|
|
313
|
+
{
|
|
314
|
+
proposal: {
|
|
315
|
+
...got.proposal,
|
|
316
|
+
payloadHash: got.proposal.payloadHash.toString("hex"),
|
|
317
|
+
payload: JSON.parse(got.proposal.payloadJcs),
|
|
318
|
+
},
|
|
319
|
+
signatures: got.signatures.map((s) => ({
|
|
320
|
+
signerDid: s.signerDid,
|
|
321
|
+
alg: s.alg,
|
|
322
|
+
signedAtMs: s.signedAtMs,
|
|
323
|
+
sigBytes: s.sig.length,
|
|
324
|
+
})),
|
|
325
|
+
},
|
|
326
|
+
null,
|
|
327
|
+
2,
|
|
328
|
+
),
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
console.log(_formatProposalTable(got.proposal, got.signatures));
|
|
332
|
+
console.log(`\n${chalk.bold("Payload:")}`);
|
|
333
|
+
console.log(got.proposal.payloadJcs);
|
|
334
|
+
console.log(`\n${chalk.bold("Signatures:")}`);
|
|
335
|
+
for (const s of got.signatures) {
|
|
336
|
+
console.log(
|
|
337
|
+
` ${chalk.green("✓")} ${s.signerDid.padEnd(36)} ${s.alg.padEnd(20)} ${new Date(
|
|
338
|
+
s.signedAtMs,
|
|
339
|
+
).toISOString()}`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} finally {
|
|
344
|
+
close();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ===== sweep =====
|
|
349
|
+
cmd
|
|
350
|
+
.command("sweep")
|
|
351
|
+
.description("Expire all pending proposals past their deadline")
|
|
352
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
353
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
354
|
+
.option("--json", "Output JSON")
|
|
355
|
+
.action(async (options) => {
|
|
356
|
+
const { mgr, close } = await _openManager(options.db, options.log);
|
|
357
|
+
try {
|
|
358
|
+
const count = mgr.expireStale();
|
|
359
|
+
if (options.json) {
|
|
360
|
+
console.log(JSON.stringify({ expired: count }, null, 2));
|
|
361
|
+
} else {
|
|
362
|
+
console.log(
|
|
363
|
+
count === 0
|
|
364
|
+
? chalk.gray("Nothing to expire")
|
|
365
|
+
: chalk.yellow(`✓ Expired ${count} stale proposal(s)`),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
} finally {
|
|
369
|
+
close();
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// ===== policy =====
|
|
374
|
+
const policy = cmd
|
|
375
|
+
.command("policy")
|
|
376
|
+
.description("Manage per-domain multisig policies");
|
|
377
|
+
|
|
378
|
+
policy
|
|
379
|
+
.command("show <domain>")
|
|
380
|
+
.description("Show the policy for a domain")
|
|
381
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
382
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
383
|
+
.option("--json", "Output JSON")
|
|
384
|
+
.action(async (domain, options) => {
|
|
385
|
+
const { store, close } = await _openManager(options.db, options.log);
|
|
386
|
+
try {
|
|
387
|
+
const p = store.getPolicy(domain);
|
|
388
|
+
if (!p) {
|
|
389
|
+
console.error(chalk.red(`No policy: ${domain}`));
|
|
390
|
+
process.exit(2);
|
|
391
|
+
}
|
|
392
|
+
if (options.json) {
|
|
393
|
+
console.log(JSON.stringify(p, null, 2));
|
|
394
|
+
} else {
|
|
395
|
+
console.log(`${chalk.bold("Domain:")} ${p.domain}`);
|
|
396
|
+
console.log(`${chalk.bold("M / N:")} ${p.m} / ${p.members.length}`);
|
|
397
|
+
console.log(
|
|
398
|
+
`${chalk.bold("requirePqc:")} ${p.requirePqc === true ? "yes" : "no"}`,
|
|
399
|
+
);
|
|
400
|
+
console.log(`${chalk.bold("Members:")}`);
|
|
401
|
+
for (const m of p.members) {
|
|
402
|
+
console.log(` ${m.did.padEnd(36)} ${m.alg}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} finally {
|
|
406
|
+
close();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
policy
|
|
411
|
+
.command("set <domain>")
|
|
412
|
+
.description("Set or update the policy for a domain")
|
|
413
|
+
.requiredOption("--m <M>", "Threshold M", (v) => parseInt(v, 10))
|
|
414
|
+
.requiredOption(
|
|
415
|
+
"--members <json|file>",
|
|
416
|
+
"JSON array of {did, alg, pubkeyJwk} or path to file",
|
|
417
|
+
)
|
|
418
|
+
.option("--require-pqc", "Require at least one SLH-DSA signature", false)
|
|
419
|
+
.option("--expiry-ms <ms>", "Default proposal expiry in ms", (v) =>
|
|
420
|
+
parseInt(v, 10),
|
|
421
|
+
)
|
|
422
|
+
.option("--db <path>", "SQLite DB path", _defaultDbPath())
|
|
423
|
+
.option("--log <path>", "Governance log path", _defaultLogPath())
|
|
424
|
+
.option("--json", "Output JSON")
|
|
425
|
+
.action(async (domain, options) => {
|
|
426
|
+
const { store, close } = await _openManager(options.db, options.log);
|
|
427
|
+
try {
|
|
428
|
+
const members = _readJsonArg(options.members);
|
|
429
|
+
if (!Array.isArray(members) || members.length === 0) {
|
|
430
|
+
console.error(chalk.red("--members must be a non-empty JSON array"));
|
|
431
|
+
process.exit(2);
|
|
432
|
+
}
|
|
433
|
+
const policy = {
|
|
434
|
+
domain,
|
|
435
|
+
m: options.m,
|
|
436
|
+
n: members.length,
|
|
437
|
+
members,
|
|
438
|
+
requirePqc: options.requirePqc === true,
|
|
439
|
+
};
|
|
440
|
+
if (options.expiryMs) policy.defaultExpiryMs = options.expiryMs;
|
|
441
|
+
const normalized = normalizePolicy(policy); // validate + fill defaults
|
|
442
|
+
store.setPolicy(domain, JSON.stringify(normalized), Date.now());
|
|
443
|
+
if (options.json) {
|
|
444
|
+
console.log(
|
|
445
|
+
JSON.stringify(
|
|
446
|
+
{ ok: true, domain, m: normalized.m, n: normalized.n },
|
|
447
|
+
null,
|
|
448
|
+
2,
|
|
449
|
+
),
|
|
450
|
+
);
|
|
451
|
+
} else {
|
|
452
|
+
console.log(
|
|
453
|
+
chalk.green(
|
|
454
|
+
`✓ Policy set: ${domain} (${normalized.m}-of-${normalized.n})`,
|
|
455
|
+
),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
} finally {
|
|
459
|
+
close();
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 暴露给单测 / 集成测试
|
|
465
|
+
export const _multisigInternals = {
|
|
466
|
+
_defaultDbPath,
|
|
467
|
+
_defaultLogPath,
|
|
468
|
+
_openManager,
|
|
469
|
+
_readKey,
|
|
470
|
+
_readJsonArg,
|
|
471
|
+
};
|
package/src/commands/p2p.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
markMessageRead,
|
|
16
16
|
getMessageCount,
|
|
17
17
|
pairDevice,
|
|
18
|
+
pairDeviceFromQr,
|
|
18
19
|
confirmPairing,
|
|
19
20
|
getPairedDevices,
|
|
20
21
|
unpairDevice,
|
|
@@ -234,27 +235,112 @@ export function registerP2pCommand(program) {
|
|
|
234
235
|
}
|
|
235
236
|
});
|
|
236
237
|
|
|
238
|
+
// p2p pair-from-qr (v1.1 W3.5 issue #19)
|
|
239
|
+
p2p
|
|
240
|
+
.command("pair-from-qr")
|
|
241
|
+
.description("Pair a device from scanned QR JSON payload")
|
|
242
|
+
.argument(
|
|
243
|
+
"<json>",
|
|
244
|
+
"QR payload JSON string (from mobile DesktopPairingScreen)",
|
|
245
|
+
)
|
|
246
|
+
.option("--json", "Output result as JSON")
|
|
247
|
+
.action(async (jsonString, options) => {
|
|
248
|
+
const respondJson = options.json;
|
|
249
|
+
try {
|
|
250
|
+
let payload;
|
|
251
|
+
try {
|
|
252
|
+
payload = JSON.parse(jsonString);
|
|
253
|
+
} catch (e) {
|
|
254
|
+
if (respondJson) {
|
|
255
|
+
console.log(
|
|
256
|
+
JSON.stringify({
|
|
257
|
+
success: false,
|
|
258
|
+
error: `JSON parse: ${e.message}`,
|
|
259
|
+
}),
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
logger.error(`Failed to parse QR JSON: ${e.message}`);
|
|
263
|
+
}
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
267
|
+
if (!ctx.db) {
|
|
268
|
+
if (respondJson) {
|
|
269
|
+
console.log(
|
|
270
|
+
JSON.stringify({ success: false, error: "db unavailable" }),
|
|
271
|
+
);
|
|
272
|
+
} else {
|
|
273
|
+
logger.error("Database not available");
|
|
274
|
+
}
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
const db = ctx.db.getDatabase();
|
|
278
|
+
const result = pairDeviceFromQr(db, payload);
|
|
279
|
+
|
|
280
|
+
if (respondJson) {
|
|
281
|
+
console.log(JSON.stringify(result));
|
|
282
|
+
} else if (result.success) {
|
|
283
|
+
logger.success("Device paired from QR");
|
|
284
|
+
logger.log(
|
|
285
|
+
` ${chalk.bold("Device ID:")} ${chalk.cyan(result.deviceId)}`,
|
|
286
|
+
);
|
|
287
|
+
logger.log(` ${chalk.bold("Device Name:")} ${result.deviceName}`);
|
|
288
|
+
logger.log(
|
|
289
|
+
` ${chalk.bold("DID:")} ${chalk.gray(result.did)}`,
|
|
290
|
+
);
|
|
291
|
+
} else {
|
|
292
|
+
logger.error(`Pair failed: ${result.error}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
await shutdown();
|
|
296
|
+
if (!result.success) process.exit(1);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (respondJson) {
|
|
299
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
300
|
+
} else {
|
|
301
|
+
logger.error(`Failed: ${err.message}`);
|
|
302
|
+
}
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
237
307
|
// p2p devices
|
|
238
308
|
p2p
|
|
239
309
|
.command("devices")
|
|
240
310
|
.description("List paired devices")
|
|
241
311
|
.option("--json", "Output as JSON")
|
|
312
|
+
.option("--type <type>", "Filter by device type (desktop|mobile|tablet)")
|
|
242
313
|
.action(async (options) => {
|
|
243
314
|
try {
|
|
315
|
+
// v1.1 W3.4a: validate --type before DB query (避免 SQL 注入 + 友好报错)
|
|
316
|
+
const validTypes = ["desktop", "mobile", "tablet"];
|
|
317
|
+
if (options.type && !validTypes.includes(options.type)) {
|
|
318
|
+
logger.error(
|
|
319
|
+
`Invalid --type "${options.type}". Must be: ${validTypes.join(", ")}`,
|
|
320
|
+
);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
244
323
|
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
245
324
|
if (!ctx.db) {
|
|
246
325
|
logger.error("Database not available");
|
|
247
326
|
process.exit(1);
|
|
248
327
|
}
|
|
249
328
|
const db = ctx.db.getDatabase();
|
|
250
|
-
const devices = getPairedDevices(db);
|
|
329
|
+
const devices = getPairedDevices(db, options.type || null);
|
|
251
330
|
|
|
252
331
|
if (options.json) {
|
|
253
332
|
console.log(JSON.stringify(devices, null, 2));
|
|
254
333
|
} else if (devices.length === 0) {
|
|
255
|
-
logger.info(
|
|
334
|
+
logger.info(
|
|
335
|
+
options.type
|
|
336
|
+
? `No paired devices of type "${options.type}"`
|
|
337
|
+
: "No paired devices",
|
|
338
|
+
);
|
|
256
339
|
} else {
|
|
257
|
-
|
|
340
|
+
const header = options.type
|
|
341
|
+
? `Paired ${options.type} Devices (${devices.length})`
|
|
342
|
+
: `Paired Devices (${devices.length})`;
|
|
343
|
+
logger.log(chalk.bold(`${header}:\n`));
|
|
258
344
|
for (const d of devices) {
|
|
259
345
|
const status =
|
|
260
346
|
d.status === "active"
|
|
@@ -278,17 +364,32 @@ export function registerP2pCommand(program) {
|
|
|
278
364
|
.command("unpair")
|
|
279
365
|
.description("Unpair a device")
|
|
280
366
|
.argument("<device-id>", "Device ID to unpair")
|
|
281
|
-
.
|
|
367
|
+
.option("--json", "Output result as JSON")
|
|
368
|
+
.action(async (deviceId, options) => {
|
|
282
369
|
try {
|
|
283
370
|
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
284
371
|
if (!ctx.db) {
|
|
285
|
-
|
|
372
|
+
if (options.json) {
|
|
373
|
+
console.log(JSON.stringify({ ok: false, error: "db unavailable" }));
|
|
374
|
+
} else {
|
|
375
|
+
logger.error("Database not available");
|
|
376
|
+
}
|
|
286
377
|
process.exit(1);
|
|
287
378
|
}
|
|
288
379
|
const db = ctx.db.getDatabase();
|
|
289
380
|
const ok = unpairDevice(db, deviceId);
|
|
290
381
|
|
|
291
|
-
|
|
382
|
+
// v1.1 W3.4a: --json shape `{ok, deviceId, error?}` 让 web-panel
|
|
383
|
+
// MobileBridge.vue 解析可靠。device 不存在不 exit(1),由 ok:false 传达。
|
|
384
|
+
if (options.json) {
|
|
385
|
+
console.log(
|
|
386
|
+
JSON.stringify(
|
|
387
|
+
ok
|
|
388
|
+
? { ok: true, deviceId }
|
|
389
|
+
: { ok: false, deviceId, error: "not_found" },
|
|
390
|
+
),
|
|
391
|
+
);
|
|
392
|
+
} else if (ok) {
|
|
292
393
|
logger.success("Device unpaired");
|
|
293
394
|
} else {
|
|
294
395
|
logger.error(`Device not found: ${deviceId}`);
|
|
@@ -296,7 +397,13 @@ export function registerP2pCommand(program) {
|
|
|
296
397
|
|
|
297
398
|
await shutdown();
|
|
298
399
|
} catch (err) {
|
|
299
|
-
|
|
400
|
+
if (options.json) {
|
|
401
|
+
console.log(
|
|
402
|
+
JSON.stringify({ ok: false, deviceId, error: err.message }),
|
|
403
|
+
);
|
|
404
|
+
} else {
|
|
405
|
+
logger.error(`Failed: ${err.message}`);
|
|
406
|
+
}
|
|
300
407
|
process.exit(1);
|
|
301
408
|
}
|
|
302
409
|
});
|
package/src/index.js
CHANGED
|
@@ -195,6 +195,7 @@ import { registerRuntimeCommand } from "./commands/runtime.js";
|
|
|
195
195
|
import { registerIpfsCommand } from "./commands/ipfs.js";
|
|
196
196
|
// MTC: Merkle Tree Certificates (Phase 1 Week 3)
|
|
197
197
|
import { registerMtcCommand } from "./commands/mtc.js";
|
|
198
|
+
import { registerMultisigCommand } from "./commands/multisig.js";
|
|
198
199
|
// Phase 27: Multimodal Collaboration
|
|
199
200
|
import { registerMultimodalCommand } from "./commands/multimodal.js";
|
|
200
201
|
// Phase 22: Performance auto-tuning
|
|
@@ -579,6 +580,7 @@ export function createProgram(opts = {}) {
|
|
|
579
580
|
registerRuntimeCommand(program);
|
|
580
581
|
registerIpfsCommand(program);
|
|
581
582
|
registerMtcCommand(program);
|
|
583
|
+
registerMultisigCommand(program);
|
|
582
584
|
registerMultimodalCommand(program);
|
|
583
585
|
registerPerfCommand(program);
|
|
584
586
|
registerAgentNetworkCommand(program);
|