chainlesschain 0.162.78 → 0.162.79
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/README.md +37 -1
- package/bin/chainlesschain.js +20 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BhKMd38k.js → AIOps-D89fD_7T.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BzhKY5C_.js → ActionButton-d75flwX8.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CATIz2Jc.js → Analytics-gvvBEb4T.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-eCx64YWg.js → AppLayout-DzHOVS64.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-CGOHfCHj.js → Audit-15K7MhRC.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Dyr6R0Ra.js → Backup-BgVHsglI.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CVhBu7NZ.js → BaseInput-BRnvd4sF.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DOCCKp2k.js → Chat-B2vtXu7s.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DcXCCnjN.js → ChatBubbleRenderer-d1EFM3dh.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-3yjnENud.js → Checkbox-B5uV5Jhr.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D06sn8kB.js → Codegen-cVDMBVGV.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Bjn5vFES.js → Col-DtL6q7Hw.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BYHpQHmf.js → Community-DSyPZkqQ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-MKRmnUDQ.js → Compact-CzfV_q4O.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CVtPe8dh.js → Compliance-B_xhhLE1.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BRu5M3dv.js → Cowork-Y49Qf0JX.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-D_7eU5Ut.js → Cron-B7JG-oLj.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BFyVp_e9.js → Crosschain-CukEkQtf.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DRoG7638.js → DID-Bd4UVKdn.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-NfM_v-Oe.js → Dashboard-DRCQl7H1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CGN1Ksu0.js → Dropdown-De47CuRY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DhsY_z_a.js → EmailListRenderer-C-wAkceB.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-vW_VsKUG.js → FamilyGuardDashboard-DhLF_E0k.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D2ob_c7h.js → Federation-DAipUS1m.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cpem15Pr.js → FormItemContext-BCFyAd2j.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-P6XfmXwT.js → GenericCardRenderer-wMdxrEV9.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CfSeec1R.js → Git-CcwP7HUN.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-4ipIpqNl.js → Governance-VF760JcB.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CiQY_P9Y.js → Inference-DnR-GIn2.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B3Qem78R.js → KnowledgeGraph-j-ewVKWO.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-De2zWVy6.js → Logs-DgB7wSor.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CTNu4c1A.js → Marketplace-fiKjzVWE.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Xx25EA2f.js → McpTools-BjXSMQrd.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-YzzCCrch.js → Memory-C0L5lnSS.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CJZY98f0.js → MobileBridge-C0Rgg1Su.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-Dw7yl4KN.js → MobileProjects-BPA50hQb.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D7TzGJhH.js → Mtc-BUsHuAjB.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-THyGhf0h.js → MtcAudit-dlH8Q_7U.js} +6 -6
- package/src/assets/web-panel/assets/{Multisig-RVxuGPUR.js → Multisig-L3_9beuW.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DK7TCzKF.js → NLProgramming-BG4sXy27.js} +1 -1
- package/src/assets/web-panel/assets/Notes-felgIvGS.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-BMivJy85.js → NotificationSettings-DHDT96AK.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BYrkEfvR.js +1 -0
- package/src/assets/web-panel/assets/{Organization-zVRtW1n_.js → Organization-DdtOLkHG.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C0YLldFH.js → Overflow-DLw5Pni7.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Xxgzghqp.js → P2P-B-mZ5EXz.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DH_LO13b.js → PdhVaultBrowser-DeOmNCwK.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-CvAd1VBw.js → Permissions-BeQMX71K.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CWQGgCAK.js → PersonalDataHub-D5PoqtQI.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-BNAoh-Lb.js → Pipeline-B0f5sqKF.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CNO5pFq-.js → Privacy-Dj_gcxio.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-WaVVDsm3.js → ProjectInit-DmfPgied.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D1WfkuJ3.js → ProjectSettings-BmBfocrv.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BUif48cc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-IOOJ4_wy.js → Providers-B0ztEAOV.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-ChHZqVZy.js → QuickAsk-BtMG4jH5.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CSiW6Qv9.js → Recommend-DQKN1En8.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BQe0rkfF.js → Reputation-D34CvUxg.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dem0Wxxb.js → Row-nnuDNx31.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-pBY5G41C.js → RssFeed-DHx9x8_B.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CtRepO6B.js → Search-CH-JYtn_.js} +1 -1
- package/src/assets/web-panel/assets/{Security-nrSlKpWq.js → Security-nNKBmzm4.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DeaDBASi.js → Services-xYk_lDxy.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cz9R-Wjb.js → Skeleton-DTYyRduA.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B3U-XLH3.js → Skills-DS19-9sF.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Bu46dIA_.js → Sla-Cr_ir42Y.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C9Z0V0pk.js → SpeechSettings-Ch5Iq7Wy.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Ctj9KHHr.js → SyncSettings-CTblfa_E.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D4upQgR_.js → Tasks-z7lz3hjG.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-JHsPGU_c.js → Templates-DGoMFd4r.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-uoaQL3fB.js → Tenant-DQUPvHxx.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CWRWr8bq.js → Terminal-Wt2wrbE6.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BTicmSAV.js → TimelineRenderer-DGTc2UqL.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bp3BUe2K.js → Tokens-DPVnuARF.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CgoISw5d.js → Trigger-DV5MPXC1.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CC29awNT.js → Trust-BBkMKqwS.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CB1SB6Nc.js → UkeySign-B_2E0qev.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D7vptDUg.js → VideoEditing-CA-qKeVb.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BWfjzF7p.js → Wallet-Ds4WURh-.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Dzz5OnPc.js → WebAuthn-BApiHjzz.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CiDeVmsG.js → WorkflowEditor-DFzmAnzV.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DQbciNb5.js → chat-DZ6sHPit.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DcLbPJzb.js → colors-DgbwhHtE.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CvYrR3rc.js → compact-item-C4QVYSzd.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BR4P7Rgm.js → createContext-yXIs4TwU.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-C-HfIeF7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-IQ88RNRJ.js → hasIn-CPtuUtBl.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5W1vQHV.js → index-4g6pGxnX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHIp5msb.js → index-B91hax9S.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUTN1VlO.js → index-B9DUiQ24.js} +3 -3
- package/src/assets/web-panel/assets/index-BBgWatYO.js +1 -0
- package/src/assets/web-panel/assets/{index-DgaCUxpi.js → index-BCb7MYMS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmPuR0aA.js → index-BIAxMDe9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dpmnk2qv.js → index-BJiGrT6V.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8OJdOc_.js → index-BOKVSmKh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Mn8_ryOe.js → index-BWJGry74.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiMlLIZ-.js → index-BaVYCMJa.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPOupQSX.js → index-Bd0JznCS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bo7HAK6G.js → index-BpBCKK6W.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db5LFFCN.js → index-BsXF9cn5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQXs-5db.js → index-BxclR5gc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVb6RI7f.js → index-C0LyE6R6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiOZ_Whh.js → index-CBAgy5pf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdQBxvpt.js → index-CJ550XXg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cm74AosZ.js → index-CTlz3MP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw0Dm_P6.js → index-CaC4gaQZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxY0ozve.js → index-CnP2ftM5.js} +1 -1
- package/src/assets/web-panel/assets/index-CvCTol_u.js +1 -0
- package/src/assets/web-panel/assets/{index-eKd1n8pw.js → index-D2KRfjEI.js} +1 -1
- package/src/assets/web-panel/assets/{index-b6FjzfoJ.js → index-DA0ePxNn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE2mqX8w.js → index-DC5iP1VB.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bl1TSbTE.js → index-DCGmeXfl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BH2RT15D.js → index-DHnWI0jj.js} +1 -1
- package/src/assets/web-panel/assets/{index-UbB2IcFR.js → index-DN4qpkKQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgP5aQmA.js → index-DTLaRwKx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHxJnExB.js → index-DdFjcgqH.js} +1 -1
- package/src/assets/web-panel/assets/{index-JqOP7puJ.js → index-DopuoTzG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dox9vEhP.js → index-DpDAGvyU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGwa8mnJ.js → index-EGVlUtH-.js} +1 -1
- package/src/assets/web-panel/assets/{index-bRT7u-51.js → index-IFh9qCi0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu8931Yi.js → index-UEeNpaDD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCO8yc1h.js → index-ZCyyy3Zt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzERBV9P.js → index-aKZjRwjv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeCWyiFl.js → index-jBlY6-bF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhkZZXtI.js → index-kbcTc6Pf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvQpTO67.js → index-x9bXmSNB.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C0arzCLE.js → initDefaultProps-BDsxioXk.js} +1 -1
- package/src/assets/web-panel/assets/{motion-C1K6JxwD.js → motion-erZ2fiIQ.js} +1 -1
- package/src/assets/web-panel/assets/{move-DREsRLHj.js → move-BFiiw4WK.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BtPS3EDq.js → omit-BRMVd4pM.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BPz6tHoT.js → pickAttrs-CGjoFXsq.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B0CR_CSI.js → placementArrow-DU_Bnf7w.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ch2ojiNn.js → responsiveObserve-DPdkYupg.js} +1 -1
- package/src/assets/web-panel/assets/{slide-9qU9vOhj.js → slide-JfHiG96y.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Cr4fICjV.js → statusUtils-Dy-1guyd.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cor2-FwV.js → styleChecker-Chaljnux.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BINo_rNH.js → useFlexGapSupport-DOHIHBgY.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Dm1tDNYC.js → useFs-BxSoQhIY.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-__JgBEkX.js → usePersonalDataHub-MOR76PYB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1hQKpRgP.js → vnode-BBGIyeYD.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C1EY9X2J.js → zoom-C1oFENec.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/audit.js +4 -3
- package/src/commands/automation.js +6 -14
- package/src/commands/bi.js +10 -9
- package/src/commands/codegen.js +5 -13
- package/src/commands/dao.js +8 -6
- package/src/commands/dbevo.js +13 -14
- package/src/commands/economy.js +3 -2
- package/src/commands/evolution.js +3 -2
- package/src/commands/federation.js +4 -3
- package/src/commands/governance.js +9 -4
- package/src/commands/hardening.js +5 -4
- package/src/commands/incentive.js +6 -5
- package/src/commands/kg.js +17 -10
- package/src/commands/lowcode.js +23 -11
- package/src/commands/marketplace.js +4 -3
- package/src/commands/mcp.js +17 -5
- package/src/commands/ops.js +9 -4
- package/src/commands/recommend.js +7 -5
- package/src/commands/scim.js +3 -2
- package/src/commands/session.js +9 -6
- package/src/commands/social.js +4 -3
- package/src/commands/sync.js +3 -2
- package/src/commands/tenant.js +11 -6
- package/src/commands/zkp.js +8 -9
- package/src/gateways/ws/ws-agent-handler.js +12 -3
- package/src/gateways/ws/ws-server.js +6 -0
- package/src/harness/background-task-manager.js +44 -18
- package/src/harness/mcp-client.js +125 -46
- package/src/lib/chat-core.js +209 -107
- package/src/lib/claude-code-bridge.js +13 -1
- package/src/lib/dao-governance.js +3 -3
- package/src/lib/downloader.js +82 -25
- package/src/lib/headless-config-command.js +62 -0
- package/src/lib/json-schema-output.js +55 -11
- package/src/lib/mcp-oauth.js +110 -21
- package/src/lib/mcp-serve.js +70 -11
- package/src/lib/multisig-runtime.js +22 -3
- package/src/lib/parse-json-option.js +35 -0
- package/src/lib/parse-number-option.js +27 -0
- package/src/lib/runnable-provider.js +97 -0
- package/src/repl/agent-repl.js +76 -17
- package/src/repl/config-summary.js +66 -0
- package/src/runtime/agent-core.js +189 -36
- package/src/runtime/headless-runner.js +49 -1
- package/src/runtime/headless-stream.js +34 -0
- package/src/assets/web-panel/assets/Notes-BaOU0psj.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-BZOiY8Yw.js +0 -1
- package/src/assets/web-panel/assets/Projects-BzjvJYMW.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CnV02N63.js +0 -1
- package/src/assets/web-panel/assets/index-DJ2gkaIH.js +0 -1
- package/src/assets/web-panel/assets/index-Dvm_-AOi.js +0 -1
|
@@ -34,21 +34,32 @@ export function validateAgainstSchema(value, schema, path = "$") {
|
|
|
34
34
|
if (schema.type) {
|
|
35
35
|
const want = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
36
36
|
const got = typeOf(value);
|
|
37
|
-
const ok = want.some(
|
|
37
|
+
const ok = want.some(
|
|
38
|
+
(t) => t === got || (t === "number" && got === "integer"),
|
|
39
|
+
);
|
|
38
40
|
if (!ok) {
|
|
39
41
|
errors.push(`${path}: expected type ${want.join("|")}, got ${got}`);
|
|
40
42
|
return errors; // type mismatch — deeper checks are noise
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
+
if (
|
|
46
|
+
schema.enum &&
|
|
47
|
+
!schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))
|
|
48
|
+
) {
|
|
49
|
+
errors.push(
|
|
50
|
+
`${path}: value not in enum [${schema.enum.map((e) => JSON.stringify(e)).join(", ")}]`,
|
|
51
|
+
);
|
|
45
52
|
}
|
|
46
|
-
if (
|
|
53
|
+
if (
|
|
54
|
+
schema.const !== undefined &&
|
|
55
|
+
JSON.stringify(schema.const) !== JSON.stringify(value)
|
|
56
|
+
) {
|
|
47
57
|
errors.push(`${path}: must equal const ${JSON.stringify(schema.const)}`);
|
|
48
58
|
}
|
|
49
59
|
if (typeOf(value) === "object" && !Array.isArray(value)) {
|
|
50
60
|
for (const req of schema.required || []) {
|
|
51
|
-
if (!(req in value))
|
|
61
|
+
if (!(req in value))
|
|
62
|
+
errors.push(`${path}: missing required property "${req}"`);
|
|
52
63
|
}
|
|
53
64
|
const props = schema.properties || {};
|
|
54
65
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -61,7 +72,9 @@ export function validateAgainstSchema(value, schema, path = "$") {
|
|
|
61
72
|
}
|
|
62
73
|
if (Array.isArray(value) && schema.items) {
|
|
63
74
|
value.forEach((item, i) => {
|
|
64
|
-
errors.push(
|
|
75
|
+
errors.push(
|
|
76
|
+
...validateAgainstSchema(item, schema.items, `${path}[${i}]`),
|
|
77
|
+
);
|
|
65
78
|
});
|
|
66
79
|
}
|
|
67
80
|
return errors;
|
|
@@ -76,10 +89,12 @@ export function extractJsonPayload(text) {
|
|
|
76
89
|
if (fence) tries.push(fence[1].trim());
|
|
77
90
|
const firstObj = raw.indexOf("{");
|
|
78
91
|
const lastObj = raw.lastIndexOf("}");
|
|
79
|
-
if (firstObj !== -1 && lastObj > firstObj)
|
|
92
|
+
if (firstObj !== -1 && lastObj > firstObj)
|
|
93
|
+
tries.push(raw.slice(firstObj, lastObj + 1));
|
|
80
94
|
const firstArr = raw.indexOf("[");
|
|
81
95
|
const lastArr = raw.lastIndexOf("]");
|
|
82
|
-
if (firstArr !== -1 && lastArr > firstArr)
|
|
96
|
+
if (firstArr !== -1 && lastArr > firstArr)
|
|
97
|
+
tries.push(raw.slice(firstArr, lastArr + 1));
|
|
83
98
|
for (const candidate of tries) {
|
|
84
99
|
if (!candidate) continue;
|
|
85
100
|
try {
|
|
@@ -111,6 +126,35 @@ export function buildRetryPrompt(originalPrompt, raw, errors) {
|
|
|
111
126
|
].join("\n");
|
|
112
127
|
}
|
|
113
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Load + parse a --json-schema file, raising errors that name the file and
|
|
131
|
+
* the underlying cause instead of a bare `ENOENT …` or `Unexpected token …`
|
|
132
|
+
* stack (which is all the user would otherwise see).
|
|
133
|
+
*
|
|
134
|
+
* @param {{ readFileSync: Function }} fs
|
|
135
|
+
* @param {string} schemaFile
|
|
136
|
+
*/
|
|
137
|
+
export function loadSchemaFile(fs, schemaFile) {
|
|
138
|
+
if (!schemaFile) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"No schema provided: pass --json-schema <file> or a schema object",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
let raw;
|
|
144
|
+
try {
|
|
145
|
+
raw = fs.readFileSync(schemaFile, "utf-8");
|
|
146
|
+
} catch (e) {
|
|
147
|
+
throw new Error(`Cannot read schema file "${schemaFile}": ${e.message}`);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(raw);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Invalid JSON in schema file "${schemaFile}": ${e.message}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
114
158
|
/**
|
|
115
159
|
* Run a headless turn constrained to a schema, retrying on validation
|
|
116
160
|
* failure. Prints the validated JSON to writeOut; returns the exit code.
|
|
@@ -124,8 +168,7 @@ export async function runJsonSchemaConstrained(cfg = {}) {
|
|
|
124
168
|
const writeErr = cfg.writeErr || ((s) => process.stderr.write(s));
|
|
125
169
|
const maxAttempts = cfg.maxAttempts || MAX_ATTEMPTS;
|
|
126
170
|
|
|
127
|
-
const schema =
|
|
128
|
-
cfg.schema || JSON.parse(fs.readFileSync(cfg.schemaFile, "utf-8"));
|
|
171
|
+
const schema = cfg.schema || loadSchemaFile(fs, cfg.schemaFile);
|
|
129
172
|
const instruction = buildSchemaInstruction(schema);
|
|
130
173
|
const base = cfg.baseOptions || {};
|
|
131
174
|
|
|
@@ -151,7 +194,8 @@ export async function runJsonSchemaConstrained(cfg = {}) {
|
|
|
151
194
|
writeErr,
|
|
152
195
|
},
|
|
153
196
|
);
|
|
154
|
-
const raw =
|
|
197
|
+
const raw =
|
|
198
|
+
String(outcome?.result ?? captured ?? "").trim() || captured.trim();
|
|
155
199
|
lastRaw = raw;
|
|
156
200
|
const parsed = extractJsonPayload(raw);
|
|
157
201
|
if (parsed.ok) {
|
package/src/lib/mcp-oauth.js
CHANGED
|
@@ -39,7 +39,11 @@ export const _deps = {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
const base64url = (buf) =>
|
|
42
|
-
Buffer.from(buf)
|
|
42
|
+
Buffer.from(buf)
|
|
43
|
+
.toString("base64")
|
|
44
|
+
.replace(/\+/g, "-")
|
|
45
|
+
.replace(/\//g, "_")
|
|
46
|
+
.replace(/=+$/, "");
|
|
43
47
|
|
|
44
48
|
/** RFC 7636 PKCE pair (S256). */
|
|
45
49
|
export function generatePkce() {
|
|
@@ -53,6 +57,52 @@ export function randomState(bytes = 16) {
|
|
|
53
57
|
return base64url(_deps.randomBytes(bytes));
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
/** Minimal HTML-escape for values reflected into the callback page. */
|
|
61
|
+
function escapeHtml(s) {
|
|
62
|
+
return String(s).replace(
|
|
63
|
+
/[&<>"']/g,
|
|
64
|
+
(c) =>
|
|
65
|
+
({
|
|
66
|
+
"&": "&",
|
|
67
|
+
"<": "<",
|
|
68
|
+
">": ">",
|
|
69
|
+
'"': """,
|
|
70
|
+
"'": "'",
|
|
71
|
+
})[c],
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The HTML shown in the user's browser after the OAuth redirect hits our
|
|
77
|
+
* localhost callback. On success it AUTO-CLOSES the tab (Claude-Code 2.1.181
|
|
78
|
+
* parity) — best-effort, since window.close() only acts on script-opened
|
|
79
|
+
* windows, so the page still tells the user they may close it manually. The
|
|
80
|
+
* provider-supplied `error` is HTML-escaped (it is reflected into the page).
|
|
81
|
+
* Pure → unit-testable.
|
|
82
|
+
*
|
|
83
|
+
* @param {string|null|undefined} error the `error` query param, if any
|
|
84
|
+
*/
|
|
85
|
+
export function renderCallbackPage(error) {
|
|
86
|
+
const ok = !error;
|
|
87
|
+
const title = ok ? "Authorized" : "Authorization failed";
|
|
88
|
+
const body = ok
|
|
89
|
+
? "You can close this tab and return to the terminal."
|
|
90
|
+
: `The provider returned an error: ${escapeHtml(error)}`;
|
|
91
|
+
const autoClose = ok
|
|
92
|
+
? "<script>setTimeout(function(){try{window.close();}catch(e){}},800);</script>"
|
|
93
|
+
: "";
|
|
94
|
+
return (
|
|
95
|
+
'<!doctype html><html><head><meta charset="utf-8"><title>' +
|
|
96
|
+
title +
|
|
97
|
+
"</title></head>" +
|
|
98
|
+
'<body style="font-family:system-ui,-apple-system,sans-serif;text-align:center;margin-top:18vh;color:#222">' +
|
|
99
|
+
`<h2 style="color:${ok ? "#16794a" : "#b00020"}">${title}</h2>` +
|
|
100
|
+
`<p style="color:#555">${body}</p>` +
|
|
101
|
+
autoClose +
|
|
102
|
+
"</body></html>"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
56
106
|
async function fetchJson(url, opts) {
|
|
57
107
|
const res = await _deps.fetch(url, opts);
|
|
58
108
|
if (!res || !res.ok) {
|
|
@@ -63,7 +113,9 @@ async function fetchJson(url, opts) {
|
|
|
63
113
|
} catch {
|
|
64
114
|
/* ignore */
|
|
65
115
|
}
|
|
66
|
-
const err = new Error(
|
|
116
|
+
const err = new Error(
|
|
117
|
+
`HTTP ${status}${body ? `: ${body.slice(0, 200)}` : ""}`,
|
|
118
|
+
);
|
|
67
119
|
err.status = res ? res.status : null;
|
|
68
120
|
throw err;
|
|
69
121
|
}
|
|
@@ -76,7 +128,10 @@ async function fetchJson(url, opts) {
|
|
|
76
128
|
* the authorization-server doc directly at the origin.
|
|
77
129
|
* @returns {Promise<{issuer?,authorization_endpoint,token_endpoint,registration_endpoint?,scopes_supported?}>}
|
|
78
130
|
*/
|
|
79
|
-
export async function discoverAuthMetadata(
|
|
131
|
+
export async function discoverAuthMetadata(
|
|
132
|
+
serverUrl,
|
|
133
|
+
{ resourceMetadataUrl } = {},
|
|
134
|
+
) {
|
|
80
135
|
const origin = new URL(serverUrl).origin;
|
|
81
136
|
// 1. protected-resource metadata (RFC 9728) → authorization_servers[]
|
|
82
137
|
let authServer = origin;
|
|
@@ -84,7 +139,10 @@ export async function discoverAuthMetadata(serverUrl, { resourceMetadataUrl } =
|
|
|
84
139
|
const prUrl =
|
|
85
140
|
resourceMetadataUrl || `${origin}/.well-known/oauth-protected-resource`;
|
|
86
141
|
const pr = await fetchJson(prUrl);
|
|
87
|
-
if (
|
|
142
|
+
if (
|
|
143
|
+
Array.isArray(pr.authorization_servers) &&
|
|
144
|
+
pr.authorization_servers[0]
|
|
145
|
+
) {
|
|
88
146
|
authServer = String(pr.authorization_servers[0]).replace(/\/$/, "");
|
|
89
147
|
}
|
|
90
148
|
} catch {
|
|
@@ -118,9 +176,14 @@ export async function discoverAuthMetadata(serverUrl, { resourceMetadataUrl } =
|
|
|
118
176
|
}
|
|
119
177
|
|
|
120
178
|
/** RFC 7591 dynamic client registration → client_id (public client). */
|
|
121
|
-
export async function registerClient(
|
|
179
|
+
export async function registerClient(
|
|
180
|
+
metadata,
|
|
181
|
+
{ redirectUri, clientName = "chainlesschain-cli" } = {},
|
|
182
|
+
) {
|
|
122
183
|
if (!metadata.registration_endpoint) {
|
|
123
|
-
throw new Error(
|
|
184
|
+
throw new Error(
|
|
185
|
+
"server has no registration_endpoint and no --client-id was given",
|
|
186
|
+
);
|
|
124
187
|
}
|
|
125
188
|
const reg = await fetchJson(metadata.registration_endpoint, {
|
|
126
189
|
method: "POST",
|
|
@@ -133,12 +196,16 @@ export async function registerClient(metadata, { redirectUri, clientName = "chai
|
|
|
133
196
|
token_endpoint_auth_method: "none",
|
|
134
197
|
}),
|
|
135
198
|
});
|
|
136
|
-
if (!reg.client_id)
|
|
199
|
+
if (!reg.client_id)
|
|
200
|
+
throw new Error("registration did not return a client_id");
|
|
137
201
|
return { clientId: reg.client_id, clientSecret: reg.client_secret || null };
|
|
138
202
|
}
|
|
139
203
|
|
|
140
204
|
/** Build the authorize URL (Authorization Code + PKCE). */
|
|
141
|
-
export function buildAuthorizeUrl(
|
|
205
|
+
export function buildAuthorizeUrl(
|
|
206
|
+
metadata,
|
|
207
|
+
{ clientId, redirectUri, scope, codeChallenge, state, resource },
|
|
208
|
+
) {
|
|
142
209
|
const u = new URL(metadata.authorization_endpoint);
|
|
143
210
|
u.searchParams.set("response_type", "code");
|
|
144
211
|
u.searchParams.set("client_id", clientId);
|
|
@@ -157,7 +224,10 @@ function _tokenExpiresAt(tok) {
|
|
|
157
224
|
}
|
|
158
225
|
|
|
159
226
|
/** Exchange an authorization code for tokens. */
|
|
160
|
-
export async function exchangeCodeForToken(
|
|
227
|
+
export async function exchangeCodeForToken(
|
|
228
|
+
metadata,
|
|
229
|
+
{ code, codeVerifier, clientId, clientSecret, redirectUri, resource },
|
|
230
|
+
) {
|
|
161
231
|
const body = new URLSearchParams({
|
|
162
232
|
grant_type: "authorization_code",
|
|
163
233
|
code,
|
|
@@ -182,7 +252,10 @@ export async function exchangeCodeForToken(metadata, { code, codeVerifier, clien
|
|
|
182
252
|
}
|
|
183
253
|
|
|
184
254
|
/** Refresh an access token. */
|
|
185
|
-
export async function refreshAccessToken(
|
|
255
|
+
export async function refreshAccessToken(
|
|
256
|
+
metadata,
|
|
257
|
+
{ refreshToken, clientId, clientSecret, resource },
|
|
258
|
+
) {
|
|
186
259
|
const body = new URLSearchParams({
|
|
187
260
|
grant_type: "refresh_token",
|
|
188
261
|
refresh_token: refreshToken,
|
|
@@ -249,7 +322,11 @@ export function deleteStoredToken(serverUrl) {
|
|
|
249
322
|
if (!(key in store)) return false;
|
|
250
323
|
delete store[key];
|
|
251
324
|
try {
|
|
252
|
-
_deps.fs.writeFileSync(
|
|
325
|
+
_deps.fs.writeFileSync(
|
|
326
|
+
file,
|
|
327
|
+
JSON.stringify(store, null, 2) + "\n",
|
|
328
|
+
"utf-8",
|
|
329
|
+
);
|
|
253
330
|
} catch {
|
|
254
331
|
/* best-effort */
|
|
255
332
|
}
|
|
@@ -292,8 +369,7 @@ function defaultOpenBrowser(url) {
|
|
|
292
369
|
const platform = process.platform;
|
|
293
370
|
const cmd =
|
|
294
371
|
platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
|
|
295
|
-
const args =
|
|
296
|
-
platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
372
|
+
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
297
373
|
try {
|
|
298
374
|
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
299
375
|
child.unref?.();
|
|
@@ -304,7 +380,12 @@ function defaultOpenBrowser(url) {
|
|
|
304
380
|
}
|
|
305
381
|
|
|
306
382
|
/** Wait for the OAuth redirect on a localhost callback server; resolve {code,state}. */
|
|
307
|
-
function waitForCallback({
|
|
383
|
+
function waitForCallback({
|
|
384
|
+
port,
|
|
385
|
+
host = "127.0.0.1",
|
|
386
|
+
path = "/callback",
|
|
387
|
+
timeout = 300_000,
|
|
388
|
+
}) {
|
|
308
389
|
return new Promise((resolve, reject) => {
|
|
309
390
|
const server = _deps.createServer((req, res) => {
|
|
310
391
|
let u;
|
|
@@ -323,10 +404,8 @@ function waitForCallback({ port, host = "127.0.0.1", path = "/callback", timeout
|
|
|
323
404
|
const code = u.searchParams.get("code");
|
|
324
405
|
const state = u.searchParams.get("state");
|
|
325
406
|
const error = u.searchParams.get("error");
|
|
326
|
-
res.writeHead(200, { "content-type": "text/html" });
|
|
327
|
-
res.end(
|
|
328
|
-
`<html><body style="font-family:sans-serif"><h3>${error ? "Authorization failed" : "Authorized — you can close this tab."}</h3></body></html>`,
|
|
329
|
-
);
|
|
407
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
408
|
+
res.end(renderCallbackPage(error));
|
|
330
409
|
server.close();
|
|
331
410
|
clearTimeout(timer);
|
|
332
411
|
if (error) reject(new Error(`authorization error: ${error}`));
|
|
@@ -377,13 +456,22 @@ export async function authorizeInteractive(serverUrl, opts = {}) {
|
|
|
377
456
|
const authorizeUrl = buildAuthorizeUrl(metadata, {
|
|
378
457
|
clientId,
|
|
379
458
|
redirectUri,
|
|
380
|
-
scope:
|
|
459
|
+
scope:
|
|
460
|
+
scope ||
|
|
461
|
+
(metadata.scopes_supported
|
|
462
|
+
? metadata.scopes_supported.join(" ")
|
|
463
|
+
: undefined),
|
|
381
464
|
codeChallenge: pkce.challenge,
|
|
382
465
|
state,
|
|
383
466
|
resource: serverUrl,
|
|
384
467
|
});
|
|
385
468
|
|
|
386
|
-
const callbackPromise = waitForCallback({
|
|
469
|
+
const callbackPromise = waitForCallback({
|
|
470
|
+
port,
|
|
471
|
+
host,
|
|
472
|
+
path: redirectPath,
|
|
473
|
+
timeout,
|
|
474
|
+
});
|
|
387
475
|
const opened = _deps.openBrowser(authorizeUrl);
|
|
388
476
|
writeOut(
|
|
389
477
|
(opened
|
|
@@ -393,7 +481,8 @@ export async function authorizeInteractive(serverUrl, opts = {}) {
|
|
|
393
481
|
);
|
|
394
482
|
|
|
395
483
|
const { code, state: returnedState } = await callbackPromise;
|
|
396
|
-
if (returnedState !== state)
|
|
484
|
+
if (returnedState !== state)
|
|
485
|
+
throw new Error("OAuth state mismatch (possible CSRF)");
|
|
397
486
|
|
|
398
487
|
const tok = await exchangeCodeForToken(metadata, {
|
|
399
488
|
code,
|
package/src/lib/mcp-serve.js
CHANGED
|
@@ -62,11 +62,14 @@ export function buildTools({ root, readOnly = false, deps = _deps }) {
|
|
|
62
62
|
const text = (truncated ? buf.slice(0, MAX_READ_BYTES) : buf).toString(
|
|
63
63
|
"utf-8",
|
|
64
64
|
);
|
|
65
|
-
return ok(
|
|
65
|
+
return ok(
|
|
66
|
+
truncated ? `${text}\n… [truncated ${buf.length} bytes]` : text,
|
|
67
|
+
);
|
|
66
68
|
},
|
|
67
69
|
},
|
|
68
70
|
list_dir: {
|
|
69
|
-
description:
|
|
71
|
+
description:
|
|
72
|
+
"List a directory under the serve root (dirs get trailing /)",
|
|
70
73
|
inputSchema: {
|
|
71
74
|
type: "object",
|
|
72
75
|
properties: { path: { type: "string" } },
|
|
@@ -106,12 +109,14 @@ export function buildTools({ root, readOnly = false, deps = _deps }) {
|
|
|
106
109
|
return;
|
|
107
110
|
}
|
|
108
111
|
for (const e of list) {
|
|
109
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
hits.length >= MAX_SEARCH_RESULTS ||
|
|
114
|
+
++seen >= MAX_SEARCH_ENTRIES
|
|
115
|
+
)
|
|
110
116
|
return;
|
|
111
117
|
const abs = deps.path.join(d, e.name);
|
|
112
118
|
if (e.isDirectory()) {
|
|
113
|
-
if (!SKIP_DIRS.has(e.name) && !e.name.startsWith("."))
|
|
114
|
-
walk(abs);
|
|
119
|
+
if (!SKIP_DIRS.has(e.name) && !e.name.startsWith(".")) walk(abs);
|
|
115
120
|
} else {
|
|
116
121
|
const rel = deps.path.relative(root, abs).replace(/\\/g, "/");
|
|
117
122
|
if (rel.toLowerCase().includes(q)) hits.push(rel);
|
|
@@ -125,7 +130,8 @@ export function buildTools({ root, readOnly = false, deps = _deps }) {
|
|
|
125
130
|
};
|
|
126
131
|
if (!readOnly) {
|
|
127
132
|
tools.write_file = {
|
|
128
|
-
description:
|
|
133
|
+
description:
|
|
134
|
+
"Write a UTF-8 file under the serve root (creates parent dirs)",
|
|
129
135
|
inputSchema: {
|
|
130
136
|
type: "object",
|
|
131
137
|
properties: {
|
|
@@ -138,7 +144,9 @@ export function buildTools({ root, readOnly = false, deps = _deps }) {
|
|
|
138
144
|
const abs = confine(root, rel, deps);
|
|
139
145
|
fs.mkdirSync(deps.path.dirname(abs), { recursive: true });
|
|
140
146
|
fs.writeFileSync(abs, String(content), "utf-8");
|
|
141
|
-
return ok(
|
|
147
|
+
return ok(
|
|
148
|
+
`wrote ${Buffer.byteLength(String(content))} bytes to ${rel}`,
|
|
149
|
+
);
|
|
142
150
|
},
|
|
143
151
|
};
|
|
144
152
|
}
|
|
@@ -163,11 +171,20 @@ export function startMcpServe(opts = {}) {
|
|
|
163
171
|
const root = deps.path.resolve(opts.root || process.cwd());
|
|
164
172
|
const readOnly = Boolean(opts.readOnly);
|
|
165
173
|
const token =
|
|
166
|
-
opts.token === false
|
|
167
|
-
? null
|
|
168
|
-
: opts.token || randomBytes(16).toString("hex");
|
|
174
|
+
opts.token === false ? null : opts.token || randomBytes(16).toString("hex");
|
|
169
175
|
const tools = buildTools({ root, readOnly, deps });
|
|
170
176
|
|
|
177
|
+
// Guardrails for the request-collection phase: a JSON-RPC request is small,
|
|
178
|
+
// so cap the body and bound how long we wait for it. Without these a large
|
|
179
|
+
// body grows `raw` unbounded (memory) and a stalled client holds the socket
|
|
180
|
+
// forever (req.on("end") never fires). Both overridable for tests / tuning.
|
|
181
|
+
const maxRequestBytes = Number.isFinite(opts.maxRequestBytes)
|
|
182
|
+
? opts.maxRequestBytes
|
|
183
|
+
: 1024 * 1024; // 1 MB
|
|
184
|
+
const requestTimeoutMs = Number.isFinite(opts.requestTimeoutMs)
|
|
185
|
+
? opts.requestTimeoutMs
|
|
186
|
+
: 30000;
|
|
187
|
+
|
|
171
188
|
const server = http.createServer((req, res) => {
|
|
172
189
|
const send = (status, body) => {
|
|
173
190
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
@@ -183,10 +200,49 @@ export function startMcpServe(opts = {}) {
|
|
|
183
200
|
}
|
|
184
201
|
}
|
|
185
202
|
let raw = "";
|
|
203
|
+
let bytes = 0;
|
|
204
|
+
let aborted = false;
|
|
205
|
+
const collectTimer = setTimeout(() => {
|
|
206
|
+
if (aborted) return;
|
|
207
|
+
aborted = true;
|
|
208
|
+
try {
|
|
209
|
+
send(408, rpcError(null, -32001, "request timeout"));
|
|
210
|
+
} catch {
|
|
211
|
+
/* socket already gone */
|
|
212
|
+
}
|
|
213
|
+
req.destroy();
|
|
214
|
+
}, requestTimeoutMs);
|
|
215
|
+
req.on("error", () => {
|
|
216
|
+
if (aborted) return;
|
|
217
|
+
aborted = true;
|
|
218
|
+
clearTimeout(collectTimer);
|
|
219
|
+
// Request stream errored (client reset/dropped): no response is
|
|
220
|
+
// deliverable on a broken socket — just stop, don't crash the process.
|
|
221
|
+
try {
|
|
222
|
+
if (!res.writableEnded) res.destroy();
|
|
223
|
+
} catch {
|
|
224
|
+
/* ignore */
|
|
225
|
+
}
|
|
226
|
+
});
|
|
186
227
|
req.on("data", (c) => {
|
|
228
|
+
if (aborted) return;
|
|
229
|
+
bytes += c.length;
|
|
230
|
+
if (bytes > maxRequestBytes) {
|
|
231
|
+
aborted = true;
|
|
232
|
+
clearTimeout(collectTimer);
|
|
233
|
+
try {
|
|
234
|
+
send(413, rpcError(null, -32600, "request too large"));
|
|
235
|
+
} catch {
|
|
236
|
+
/* socket already gone */
|
|
237
|
+
}
|
|
238
|
+
req.destroy();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
187
241
|
raw += c;
|
|
188
242
|
});
|
|
189
243
|
req.on("end", () => {
|
|
244
|
+
if (aborted) return;
|
|
245
|
+
clearTimeout(collectTimer);
|
|
190
246
|
let msg;
|
|
191
247
|
try {
|
|
192
248
|
msg = JSON.parse(raw);
|
|
@@ -224,7 +280,10 @@ export function startMcpServe(opts = {}) {
|
|
|
224
280
|
if (method === "tools/call") {
|
|
225
281
|
const tool = tools[params?.name];
|
|
226
282
|
if (!tool) {
|
|
227
|
-
return send(
|
|
283
|
+
return send(
|
|
284
|
+
200,
|
|
285
|
+
rpcResult(id, fail(`unknown tool: ${params?.name}`)),
|
|
286
|
+
);
|
|
228
287
|
}
|
|
229
288
|
let result;
|
|
230
289
|
try {
|
|
@@ -150,11 +150,30 @@ export function readSecretKey(arg) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
|
-
* Read JSON from inline string or file path.
|
|
153
|
+
* Read JSON from an inline string or a file path. Errors name the file (or
|
|
154
|
+
* say it was inline) and carry the underlying parser reason, instead of a
|
|
155
|
+
* bare "Unexpected token …" / "ENOENT …" with no context.
|
|
154
156
|
*/
|
|
155
157
|
export function readJsonArg(arg) {
|
|
158
|
+
if (arg == null || arg === "") {
|
|
159
|
+
throw new Error("Expected inline JSON or a path to a JSON file");
|
|
160
|
+
}
|
|
156
161
|
if (fs.existsSync(arg)) {
|
|
157
|
-
|
|
162
|
+
let raw;
|
|
163
|
+
try {
|
|
164
|
+
raw = fs.readFileSync(arg, "utf-8");
|
|
165
|
+
} catch (e) {
|
|
166
|
+
throw new Error(`Cannot read JSON file "${arg}": ${e.message}`);
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(raw);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
throw new Error(`Invalid JSON in file "${arg}": ${e.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
return JSON.parse(arg);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
throw new Error(`Invalid inline JSON argument: ${e.message}`);
|
|
158
178
|
}
|
|
159
|
-
return JSON.parse(arg);
|
|
160
179
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a user-supplied JSON CLI option value.
|
|
3
|
+
*
|
|
4
|
+
* Many command actions pass raw `--flag` strings straight to `JSON.parse`,
|
|
5
|
+
* so malformed JSON surfaces as a raw `SyntaxError` — an ugly stack trace for
|
|
6
|
+
* actions without a try/catch, and a cryptic "Unexpected token …" for the rest.
|
|
7
|
+
* This helper turns that into a single, friendly `Invalid JSON for <label>: …`
|
|
8
|
+
* error, and consolidates the duplicated `_parseJsonArg` / `_parseMetaV2`
|
|
9
|
+
* helpers that several command files grew independently.
|
|
10
|
+
*
|
|
11
|
+
* @param {string|undefined|null} value raw option string (e.g. `options.input`)
|
|
12
|
+
* @param {string} label user-facing label for errors (e.g. `"--input"`)
|
|
13
|
+
* @param {*} [fallback] returned when `value` is empty (default `undefined`)
|
|
14
|
+
* @returns the parsed JSON, or `fallback` when `value` is empty
|
|
15
|
+
* @throws {Error} `Invalid JSON for <label>: <reason>` when `value` is non-empty but unparseable
|
|
16
|
+
*/
|
|
17
|
+
export function parseJsonOption(value, label, fallback = undefined) {
|
|
18
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
19
|
+
// Defensive: a Commander custom parser may have already produced an object.
|
|
20
|
+
if (typeof value !== "string") return value;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
const reason = e && e.message ? e.message : String(e);
|
|
25
|
+
// Friendly one-line message for the non-verbose error boundary, but keep the
|
|
26
|
+
// original SyntaxError reachable: chain it as `cause`, and append its stack
|
|
27
|
+
// to ours so `--verbose` (which prints err.stack) still surfaces the root
|
|
28
|
+
// SyntaxError type + location instead of swallowing it behind the wrapper.
|
|
29
|
+
const err = new Error(`Invalid JSON for ${label}: ${reason}`, { cause: e });
|
|
30
|
+
if (e && e.stack) err.stack += `\nCaused by: ${e.stack}`;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default parseJsonOption;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a user-supplied numeric CLI option value.
|
|
3
|
+
*
|
|
4
|
+
* Command actions often do `parseFloat(options.x)` / `parseInt(options.x)`
|
|
5
|
+
* straight into domain logic. When the flag is malformed (`--weight abc`,
|
|
6
|
+
* `--amount 1,5`) those yield `NaN`, which then flows silently into stored
|
|
7
|
+
* records and math (vote weights, amounts, thresholds) — corrupt data with no
|
|
8
|
+
* error. This helper validates the value is a finite number and otherwise
|
|
9
|
+
* throws a single friendly `Invalid number for <label>: <value>` error
|
|
10
|
+
* (which reads cleanly through the CLI entry boundary).
|
|
11
|
+
*
|
|
12
|
+
* @param {string|number|undefined|null} value raw option (e.g. `options.weight`)
|
|
13
|
+
* @param {string} label user-facing label (e.g. `"--weight"`)
|
|
14
|
+
* @param {*} [fallback] returned when `value` is empty (default `undefined`)
|
|
15
|
+
* @returns {number|*} the parsed finite number, or `fallback` when empty
|
|
16
|
+
* @throws {Error} when `value` is non-empty but not a finite number
|
|
17
|
+
*/
|
|
18
|
+
export function parseNumberOption(value, label, fallback = undefined) {
|
|
19
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
20
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
21
|
+
if (!Number.isFinite(n)) {
|
|
22
|
+
throw new Error(`Invalid number for ${label}: ${JSON.stringify(value)}`);
|
|
23
|
+
}
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default parseNumberOption;
|