chainlesschain 0.162.78 → 0.162.80
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-CwebRiRI.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BzhKY5C_.js → ActionButton-C7h2xsW3.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CATIz2Jc.js → Analytics-BRdOQzzK.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-eCx64YWg.js → AppLayout-D_i-Jbsu.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-CGOHfCHj.js → Audit-AgCF_nLK.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Dyr6R0Ra.js → Backup-Be9up1Uo.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CVhBu7NZ.js → BaseInput-2j-7gyTU.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DOCCKp2k.js → Chat-ZyYadHdk.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DcXCCnjN.js → ChatBubbleRenderer-CwnbmcAg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-3yjnENud.js → Checkbox-Di0bejSO.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D06sn8kB.js → Codegen-BgEwqrVh.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Bjn5vFES.js → Col-FIduoerd.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BYHpQHmf.js → Community-Bf7olKXg.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-MKRmnUDQ.js → Compact-CsjIF6B3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CVtPe8dh.js → Compliance-CQchRaQh.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BRu5M3dv.js → Cowork-gJzDgX9E.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-D_7eU5Ut.js → Cron-CDjeagXb.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BFyVp_e9.js → Crosschain-D4UE5bt0.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DRoG7638.js → DID-OwvsxTzD.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-NfM_v-Oe.js → Dashboard-CbmOlZ1l.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CGN1Ksu0.js → Dropdown-B6tBUMev.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DhsY_z_a.js → EmailListRenderer-DnM2-O3n.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-vW_VsKUG.js → FamilyGuardDashboard-DPD9znJH.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D2ob_c7h.js → Federation-DjUj87Vr.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cpem15Pr.js → FormItemContext-DyPgrKLf.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-P6XfmXwT.js → GenericCardRenderer-D3fmbO1W.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CfSeec1R.js → Git-IvwC_R2h.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-4ipIpqNl.js → Governance-CTVEvxpW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CiQY_P9Y.js → Inference-DQlp7Rf1.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B3Qem78R.js → KnowledgeGraph-Cg8pVh5j.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-De2zWVy6.js → Logs-D7kLXyaK.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CTNu4c1A.js → Marketplace-QOj6g-oT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Xx25EA2f.js → McpTools-PO9yrTD6.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-YzzCCrch.js → Memory-B1FwSFmt.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CJZY98f0.js → MobileBridge-CYiQSoKu.js} +3 -3
- package/src/assets/web-panel/assets/{MobileProjects-Dw7yl4KN.js → MobileProjects-BThUga4r.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D7TzGJhH.js → Mtc-B4pMzmr7.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-THyGhf0h.js → MtcAudit-D6-KGsvR.js} +6 -6
- package/src/assets/web-panel/assets/{Multisig-RVxuGPUR.js → Multisig-BlZigJMj.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DK7TCzKF.js → NLProgramming-Dhy92OLw.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BaOU0psj.js → Notes-DOSv2Lb0.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-BMivJy85.js → NotificationSettings-FRWsHQfi.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BZOiY8Yw.js → OrderTableRenderer-xRSRWRZg.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-zVRtW1n_.js → Organization-DApwYY-B.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C0YLldFH.js → Overflow-DyJQHxIY.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Xxgzghqp.js → P2P-NKTPd_Bn.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DH_LO13b.js → PdhVaultBrowser-DCgGt1BE.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-CvAd1VBw.js → Permissions-BX9MCb2z.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CWQGgCAK.js → PersonalDataHub-Bcg4az84.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-BNAoh-Lb.js → Pipeline-DjsbfwbG.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CNO5pFq-.js → Privacy-kDWl06vo.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-WaVVDsm3.js → ProjectInit-yCe-Imkv.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D1WfkuJ3.js → ProjectSettings-tVcxlGJ5.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BzjvJYMW.js → Projects-DGL4Za4o.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-IOOJ4_wy.js → Providers-CciGxskW.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-ChHZqVZy.js → QuickAsk-It17rT4F.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CSiW6Qv9.js → Recommend-CixKdpBl.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BQe0rkfF.js → Reputation-LB0_D2lx.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dem0Wxxb.js → Row-BoiDd-zb.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-pBY5G41C.js → RssFeed-yA5FdTgh.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CtRepO6B.js → Search-xYldUFeJ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-nrSlKpWq.js → Security-BtRMnkFm.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DeaDBASi.js → Services-CNKTgE2v.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cz9R-Wjb.js → Skeleton-B9goiUo_.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B3U-XLH3.js → Skills-CUf2Z5Ge.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Bu46dIA_.js → Sla-TPga8c2I.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C9Z0V0pk.js → SpeechSettings-B8zrmLP6.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Ctj9KHHr.js → SyncSettings-DGyAbZ84.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D4upQgR_.js → Tasks-DPeVrQNM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-JHsPGU_c.js → Templates-CNLco6pc.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-uoaQL3fB.js → Tenant-CL9Eczo8.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-DGLvbp97.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-BTicmSAV.js → TimelineRenderer-Cv0LxMwd.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bp3BUe2K.js → Tokens-DLhHgtcS.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CgoISw5d.js → Trigger-DzDaE-An.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CC29awNT.js → Trust-CRh-fhYe.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CB1SB6Nc.js → UkeySign-_xBJ16UC.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D7vptDUg.js → VideoEditing-Z5m_edIa.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BWfjzF7p.js → Wallet-DgmchNit.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Dzz5OnPc.js → WebAuthn-BxuKxjuf.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CiDeVmsG.js → WorkflowEditor-luJ180aM.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DQbciNb5.js → chat-DzglnTps.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DcLbPJzb.js → colors-DelLNoxZ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CvYrR3rc.js → compact-item-BZaabUge.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BR4P7Rgm.js → createContext-DqTSTjmk.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CJLMPKYL.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-IQ88RNRJ.js → hasIn-CAeHUQj2.js} +1 -1
- package/src/assets/web-panel/assets/index-7CJalvEf.js +1 -0
- package/src/assets/web-panel/assets/{index-Bw0Dm_P6.js → index-7FxBHcH8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db5LFFCN.js → index-BAcpfWwI.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5W1vQHV.js → index-BAfdWN9t.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUTN1VlO.js → index-BR-DF81e.js} +3 -3
- package/src/assets/web-panel/assets/{index-BmPuR0aA.js → index-BTbN0V4A.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8OJdOc_.js → index-Bv2Tp7kz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhkZZXtI.js → index-BzLgm3Jm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzERBV9P.js → index-CBZPDGTg.js} +1 -1
- package/src/assets/web-panel/assets/{index-UbB2IcFR.js → index-CBtnHlYF.js} +1 -1
- package/src/assets/web-panel/assets/{index-eKd1n8pw.js → index-CDw1am9U.js} +1 -1
- package/src/assets/web-panel/assets/{index-JqOP7puJ.js → index-CI5cynRw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiMlLIZ-.js → index-CKZQVcH1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgP5aQmA.js → index-CV4FisuU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BH2RT15D.js → index-CaSLz8-6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgaCUxpi.js → index-Cj7oeTxA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdQBxvpt.js → index-Clq1OP4B.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bl1TSbTE.js → index-CppTZ4SW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQXs-5db.js → index-D-QuIaEh.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGwa8mnJ.js → index-D-lVDXUg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHIp5msb.js → index-DE4-6oHW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCO8yc1h.js → index-DM7xncnU.js} +1 -1
- package/src/assets/web-panel/assets/{index-b6FjzfoJ.js → index-DRt2lx0X.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bo7HAK6G.js → index-DSATjRyg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dpmnk2qv.js → index-DU9QWJO5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Mn8_ryOe.js → index-DXvcxNo5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dox9vEhP.js → index-DhMSUhbW.js} +1 -1
- package/src/assets/web-panel/assets/{index-bRT7u-51.js → index-Dk1R9vFq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHxJnExB.js → index-DrSuq6t6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPOupQSX.js → index-DtfTElxo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxY0ozve.js → index-KeadEGaZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvQpTO67.js → index-NZBXGj64.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiOZ_Whh.js → index-OGKhEFZZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeCWyiFl.js → index-RZ23Wlp8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVb6RI7f.js → index-WzAdJ0PX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cm74AosZ.js → index-Xo2WWPZ4.js} +1 -1
- package/src/assets/web-panel/assets/index-_hLbeSOT.js +1 -0
- package/src/assets/web-panel/assets/{index-CE2mqX8w.js → index-kJ30C4m8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu8931Yi.js → index-vLR-ssxc.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C0arzCLE.js → initDefaultProps-BiHvIjo1.js} +1 -1
- package/src/assets/web-panel/assets/{motion-C1K6JxwD.js → motion-COD0OBOe.js} +1 -1
- package/src/assets/web-panel/assets/{move-DREsRLHj.js → move-DJNLMhIj.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BtPS3EDq.js → omit-4qrDRhlN.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BPz6tHoT.js → pickAttrs-3jv8tAgW.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B0CR_CSI.js → placementArrow-N1UVUOH_.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ch2ojiNn.js → responsiveObserve-D67_gjCH.js} +1 -1
- package/src/assets/web-panel/assets/{slide-9qU9vOhj.js → slide-DiDh7_u4.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Cr4fICjV.js → statusUtils-Dzz3tSiz.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cor2-FwV.js → styleChecker-L-tgt7xx.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BINo_rNH.js → useFlexGapSupport-vAgElNal.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Dm1tDNYC.js → useFs-af0c_HYI.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-__JgBEkX.js → usePersonalDataHub-V9U2Mbny.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1hQKpRgP.js → vnode-C7zS_LLr.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C1EY9X2J.js → zoom-DdXBDemd.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 +216 -0
- package/src/repl/agent-repl.js +76 -17
- package/src/repl/config-summary.js +66 -0
- package/src/runtime/agent-core.js +210 -37
- package/src/runtime/headless-runner.js +49 -1
- package/src/runtime/headless-stream.js +34 -0
- package/src/assets/web-panel/assets/Terminal-CWRWr8bq.js +0 -3
- 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
|
@@ -437,7 +437,7 @@ export function castVote(db, opts) {
|
|
|
437
437
|
`Invalid voteType: ${voteType}. Must be one of: ${Object.values(VOTE_TYPE).join(", ")}`,
|
|
438
438
|
);
|
|
439
439
|
}
|
|
440
|
-
if (
|
|
440
|
+
if (!Number.isFinite(voteCount) || voteCount <= 0) {
|
|
441
441
|
throw new Error("voteCount must be a positive number");
|
|
442
442
|
}
|
|
443
443
|
|
|
@@ -686,7 +686,7 @@ export function allocateFundsV2(db, opts) {
|
|
|
686
686
|
const { proposalId, recipient, amount, asset = "native", memo } = opts || {};
|
|
687
687
|
if (!proposalId) throw new Error("proposalId is required");
|
|
688
688
|
if (!recipient) throw new Error("recipient is required");
|
|
689
|
-
if (
|
|
689
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
690
690
|
throw new Error("amount must be a positive number");
|
|
691
691
|
}
|
|
692
692
|
if (amount > _configV2.maxSingleAllocation) {
|
|
@@ -807,7 +807,7 @@ export function getConfigV2() {
|
|
|
807
807
|
|
|
808
808
|
export function depositToTreasuryV2(db, opts) {
|
|
809
809
|
const { amount, asset = "native", memo, depositorDid } = opts || {};
|
|
810
|
-
if (
|
|
810
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
811
811
|
throw new Error("amount must be a positive number");
|
|
812
812
|
}
|
|
813
813
|
|
package/src/lib/downloader.js
CHANGED
|
@@ -24,6 +24,67 @@ const ASSET_PATTERNS = {
|
|
|
24
24
|
linux: { x64: /chainlesschain.*linux.*x64.*\.zip$/i },
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Stream a fetch Response body to `destPath` with two robustness guards for the
|
|
29
|
+
* download/install path:
|
|
30
|
+
* - idle timeout: if no bytes arrive for `idleTimeoutMs`, abort `controller`
|
|
31
|
+
* (which cancels the underlying fetch) so a hung server can't freeze the
|
|
32
|
+
* download forever. The timer resets on every chunk — a stall detector, not
|
|
33
|
+
* a total-time cap — and is unref'd so it never holds the loop open.
|
|
34
|
+
* - size verification: if the server advertised a Content-Length and fewer
|
|
35
|
+
* bytes arrived, throw. A silently truncated download must not look like a
|
|
36
|
+
* successful one (the caller would otherwise extract/install a corrupt file).
|
|
37
|
+
*
|
|
38
|
+
* Exported for tests. Returns { downloadedBytes, totalBytes }.
|
|
39
|
+
*/
|
|
40
|
+
export async function streamToFileVerified(
|
|
41
|
+
response,
|
|
42
|
+
destPath,
|
|
43
|
+
{
|
|
44
|
+
controller = null,
|
|
45
|
+
idleTimeoutMs = 60000,
|
|
46
|
+
onProgress = null,
|
|
47
|
+
createWriteStreamImpl = createWriteStream,
|
|
48
|
+
} = {},
|
|
49
|
+
) {
|
|
50
|
+
const totalBytes = parseInt(
|
|
51
|
+
response.headers.get("content-length") || "0",
|
|
52
|
+
10,
|
|
53
|
+
);
|
|
54
|
+
let downloadedBytes = 0;
|
|
55
|
+
let idleTimer = null;
|
|
56
|
+
const armIdle = () => {
|
|
57
|
+
if (!controller || !(idleTimeoutMs > 0)) return;
|
|
58
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
59
|
+
idleTimer = setTimeout(() => controller.abort(), idleTimeoutMs);
|
|
60
|
+
if (idleTimer && typeof idleTimer.unref === "function") idleTimer.unref();
|
|
61
|
+
};
|
|
62
|
+
const reader = response.body.getReader();
|
|
63
|
+
async function* track() {
|
|
64
|
+
try {
|
|
65
|
+
while (true) {
|
|
66
|
+
const { done, value } = await reader.read();
|
|
67
|
+
if (done) return;
|
|
68
|
+
armIdle();
|
|
69
|
+
downloadedBytes += value.byteLength;
|
|
70
|
+
if (onProgress) onProgress(downloadedBytes, totalBytes);
|
|
71
|
+
yield value;
|
|
72
|
+
}
|
|
73
|
+
} finally {
|
|
74
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
armIdle();
|
|
78
|
+
await pipeline(track(), createWriteStreamImpl(destPath));
|
|
79
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
80
|
+
if (totalBytes > 0 && downloadedBytes !== totalBytes) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Download truncated: received ${downloadedBytes} of ${totalBytes} bytes`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return { downloadedBytes, totalBytes };
|
|
86
|
+
}
|
|
87
|
+
|
|
27
88
|
export async function downloadRelease(version, options = {}) {
|
|
28
89
|
const binDir = ensureDir(getBinDir());
|
|
29
90
|
|
|
@@ -41,9 +102,14 @@ export async function downloadRelease(version, options = {}) {
|
|
|
41
102
|
const spinner = ora(`Downloading ${assetName}...`).start();
|
|
42
103
|
|
|
43
104
|
try {
|
|
105
|
+
// Abort the fetch if the download stalls (no bytes for idleTimeoutMs) so a
|
|
106
|
+
// hung mirror can't freeze `cc setup` forever; streamToFileVerified also
|
|
107
|
+
// verifies the full body landed (truncation guard).
|
|
108
|
+
const controller = new AbortController();
|
|
44
109
|
const response = await fetch(assetUrl, {
|
|
45
110
|
headers: { Accept: "application/octet-stream" },
|
|
46
111
|
redirect: "follow",
|
|
112
|
+
signal: controller.signal,
|
|
47
113
|
});
|
|
48
114
|
|
|
49
115
|
if (!response.ok) {
|
|
@@ -52,30 +118,16 @@ export async function downloadRelease(version, options = {}) {
|
|
|
52
118
|
);
|
|
53
119
|
}
|
|
54
120
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// avoids the experimental stream.Readable.fromWeb); `pipeline` keeps the
|
|
63
|
-
// backpressure + error handling. Progress is tracked as chunks flow through.
|
|
64
|
-
const reader = response.body.getReader();
|
|
65
|
-
async function* trackProgress() {
|
|
66
|
-
while (true) {
|
|
67
|
-
const { done, value } = await reader.read();
|
|
68
|
-
if (done) return;
|
|
69
|
-
downloadedBytes += value.byteLength;
|
|
70
|
-
if (totalBytes > 0) {
|
|
71
|
-
const pct = ((downloadedBytes / totalBytes) * 100).toFixed(1);
|
|
72
|
-
spinner.text = `Downloading ${assetName}... ${pct}% (${formatBytes(downloadedBytes)}/${formatBytes(totalBytes)})`;
|
|
121
|
+
const { downloadedBytes } = await streamToFileVerified(response, destPath, {
|
|
122
|
+
controller,
|
|
123
|
+
idleTimeoutMs: options.idleTimeoutMs,
|
|
124
|
+
onProgress: (downloaded, total) => {
|
|
125
|
+
if (total > 0) {
|
|
126
|
+
const pct = ((downloaded / total) * 100).toFixed(1);
|
|
127
|
+
spinner.text = `Downloading ${assetName}... ${pct}% (${formatBytes(downloaded)}/${formatBytes(total)})`;
|
|
73
128
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
await pipeline(trackProgress(), createWriteStream(destPath));
|
|
129
|
+
},
|
|
130
|
+
});
|
|
79
131
|
|
|
80
132
|
spinner.succeed(
|
|
81
133
|
`Downloaded ${assetName} (${formatBytes(downloadedBytes)})`,
|
|
@@ -96,11 +148,16 @@ export async function downloadRelease(version, options = {}) {
|
|
|
96
148
|
|
|
97
149
|
return binDir;
|
|
98
150
|
} catch (err) {
|
|
99
|
-
|
|
151
|
+
const stalled =
|
|
152
|
+
err && (err.name === "AbortError" || err.code === "ABORT_ERR");
|
|
153
|
+
const msg = stalled
|
|
154
|
+
? `Download stalled (no data received): ${assetName}`
|
|
155
|
+
: `Download failed: ${err.message}`;
|
|
156
|
+
spinner.fail(msg);
|
|
100
157
|
if (existsSync(destPath)) {
|
|
101
158
|
unlinkSync(destPath);
|
|
102
159
|
}
|
|
103
|
-
throw err;
|
|
160
|
+
throw stalled ? new Error(msg) : err;
|
|
104
161
|
}
|
|
105
162
|
}
|
|
106
163
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless `/config` directive (Claude-Code 2.1.181 parity — "/config key=value
|
|
3
|
+
* in interactive AND -p modes"). The REPL already handles `/config` for the
|
|
4
|
+
* interactive half; this is the `-p` / piped-stdin half: when the headless
|
|
5
|
+
* prompt is a leading `/config …` slash command, treat it as a one-shot config
|
|
6
|
+
* get / set / show instead of a task for the LLM — no model call, no session,
|
|
7
|
+
* no bootstrap. (`cc config get|set` covers the same ground for pure scripting;
|
|
8
|
+
* this gives the symmetric single-interface form Claude Code exposes.)
|
|
9
|
+
*
|
|
10
|
+
* Pure detection + execution; config-manager does the disk I/O (injected for
|
|
11
|
+
* tests). Secrets stay masked on read and write — the same invariant the REPL
|
|
12
|
+
* `/config` upholds.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
parseConfigCommand,
|
|
16
|
+
renderConfigGet,
|
|
17
|
+
renderConfigSet,
|
|
18
|
+
renderConfigSummary,
|
|
19
|
+
} from "../repl/config-summary.js";
|
|
20
|
+
|
|
21
|
+
/** Does this headless prompt start with the `/config` slash command? */
|
|
22
|
+
export function isHeadlessConfigCommand(prompt) {
|
|
23
|
+
return /^\/config(?:\s|$)/.test((prompt || "").trim());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute a `/config …` directive. Returns `{ text, isError }`.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} prompt the full prompt (leading `/config …`)
|
|
30
|
+
* @param {object} deps
|
|
31
|
+
* - configManager: { loadConfig, getConfigValue, setConfigValue } (required)
|
|
32
|
+
* - getConfigPath?: () => string|null (shown in the `show` summary)
|
|
33
|
+
*/
|
|
34
|
+
export function runConfigDirective(prompt, deps = {}) {
|
|
35
|
+
const cm = deps.configManager;
|
|
36
|
+
if (!cm) return { text: "/config: no config manager", isError: true };
|
|
37
|
+
const getConfigPath = deps.getConfigPath || (() => null);
|
|
38
|
+
const argStr = (prompt || "").trim().replace(/^\/config\b/, "");
|
|
39
|
+
const cmd = parseConfigCommand(argStr);
|
|
40
|
+
|
|
41
|
+
if (cmd.action === "error") {
|
|
42
|
+
return { text: `/config: ${cmd.message}`, isError: true };
|
|
43
|
+
}
|
|
44
|
+
if (cmd.action === "get") {
|
|
45
|
+
return {
|
|
46
|
+
text: renderConfigGet(cmd.key, cm.getConfigValue(cmd.key)),
|
|
47
|
+
isError: false,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (cmd.action === "set") {
|
|
51
|
+
cm.setConfigValue(cmd.key, cmd.value);
|
|
52
|
+
return {
|
|
53
|
+
text: renderConfigSet(cmd.key, cm.getConfigValue(cmd.key)),
|
|
54
|
+
isError: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// show
|
|
58
|
+
return {
|
|
59
|
+
text: renderConfigSummary(cm.loadConfig(), { path: getConfigPath() }),
|
|
60
|
+
isError: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -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,
|