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.
Files changed (194) hide show
  1. package/README.md +37 -1
  2. package/bin/chainlesschain.js +20 -1
  3. package/package.json +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-BhKMd38k.js → AIOps-CwebRiRI.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BzhKY5C_.js → ActionButton-C7h2xsW3.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CATIz2Jc.js → Analytics-BRdOQzzK.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-eCx64YWg.js → AppLayout-D_i-Jbsu.js} +5 -5
  8. package/src/assets/web-panel/assets/{Audit-CGOHfCHj.js → Audit-AgCF_nLK.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Dyr6R0Ra.js → Backup-Be9up1Uo.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CVhBu7NZ.js → BaseInput-2j-7gyTU.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DOCCKp2k.js → Chat-ZyYadHdk.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DcXCCnjN.js → ChatBubbleRenderer-CwnbmcAg.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-3yjnENud.js → Checkbox-Di0bejSO.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-D06sn8kB.js → Codegen-BgEwqrVh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-Bjn5vFES.js → Col-FIduoerd.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-BYHpQHmf.js → Community-Bf7olKXg.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-MKRmnUDQ.js → Compact-CsjIF6B3.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CVtPe8dh.js → Compliance-CQchRaQh.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BRu5M3dv.js → Cowork-gJzDgX9E.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-D_7eU5Ut.js → Cron-CDjeagXb.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BFyVp_e9.js → Crosschain-D4UE5bt0.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DRoG7638.js → DID-OwvsxTzD.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-NfM_v-Oe.js → Dashboard-CbmOlZ1l.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CGN1Ksu0.js → Dropdown-B6tBUMev.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DhsY_z_a.js → EmailListRenderer-DnM2-O3n.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-vW_VsKUG.js → FamilyGuardDashboard-DPD9znJH.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D2ob_c7h.js → Federation-DjUj87Vr.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cpem15Pr.js → FormItemContext-DyPgrKLf.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-P6XfmXwT.js → GenericCardRenderer-D3fmbO1W.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-CfSeec1R.js → Git-IvwC_R2h.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-4ipIpqNl.js → Governance-CTVEvxpW.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CiQY_P9Y.js → Inference-DQlp7Rf1.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-B3Qem78R.js → KnowledgeGraph-Cg8pVh5j.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-De2zWVy6.js → Logs-D7kLXyaK.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-CTNu4c1A.js → Marketplace-QOj6g-oT.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Xx25EA2f.js → McpTools-PO9yrTD6.js} +5 -5
  37. package/src/assets/web-panel/assets/{Memory-YzzCCrch.js → Memory-B1FwSFmt.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileBridge-CJZY98f0.js → MobileBridge-CYiQSoKu.js} +3 -3
  39. package/src/assets/web-panel/assets/{MobileProjects-Dw7yl4KN.js → MobileProjects-BThUga4r.js} +1 -1
  40. package/src/assets/web-panel/assets/{Mtc-D7TzGJhH.js → Mtc-B4pMzmr7.js} +4 -4
  41. package/src/assets/web-panel/assets/{MtcAudit-THyGhf0h.js → MtcAudit-D6-KGsvR.js} +6 -6
  42. package/src/assets/web-panel/assets/{Multisig-RVxuGPUR.js → Multisig-BlZigJMj.js} +3 -3
  43. package/src/assets/web-panel/assets/{NLProgramming-DK7TCzKF.js → NLProgramming-Dhy92OLw.js} +1 -1
  44. package/src/assets/web-panel/assets/{Notes-BaOU0psj.js → Notes-DOSv2Lb0.js} +4 -4
  45. package/src/assets/web-panel/assets/{NotificationSettings-BMivJy85.js → NotificationSettings-FRWsHQfi.js} +1 -1
  46. package/src/assets/web-panel/assets/{OrderTableRenderer-BZOiY8Yw.js → OrderTableRenderer-xRSRWRZg.js} +1 -1
  47. package/src/assets/web-panel/assets/{Organization-zVRtW1n_.js → Organization-DApwYY-B.js} +4 -4
  48. package/src/assets/web-panel/assets/{Overflow-C0YLldFH.js → Overflow-DyJQHxIY.js} +1 -1
  49. package/src/assets/web-panel/assets/{P2P-Xxgzghqp.js → P2P-NKTPd_Bn.js} +2 -2
  50. package/src/assets/web-panel/assets/{PdhVaultBrowser-DH_LO13b.js → PdhVaultBrowser-DCgGt1BE.js} +5 -5
  51. package/src/assets/web-panel/assets/{Permissions-CvAd1VBw.js → Permissions-BX9MCb2z.js} +4 -4
  52. package/src/assets/web-panel/assets/{PersonalDataHub-CWQGgCAK.js → PersonalDataHub-Bcg4az84.js} +3 -3
  53. package/src/assets/web-panel/assets/{Pipeline-BNAoh-Lb.js → Pipeline-DjsbfwbG.js} +1 -1
  54. package/src/assets/web-panel/assets/{Privacy-CNO5pFq-.js → Privacy-kDWl06vo.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectInit-WaVVDsm3.js → ProjectInit-yCe-Imkv.js} +2 -2
  56. package/src/assets/web-panel/assets/{ProjectSettings-D1WfkuJ3.js → ProjectSettings-tVcxlGJ5.js} +2 -2
  57. package/src/assets/web-panel/assets/{Projects-BzjvJYMW.js → Projects-DGL4Za4o.js} +1 -1
  58. package/src/assets/web-panel/assets/{Providers-IOOJ4_wy.js → Providers-CciGxskW.js} +1 -1
  59. package/src/assets/web-panel/assets/{QuickAsk-ChHZqVZy.js → QuickAsk-It17rT4F.js} +1 -1
  60. package/src/assets/web-panel/assets/{Recommend-CSiW6Qv9.js → Recommend-CixKdpBl.js} +1 -1
  61. package/src/assets/web-panel/assets/{Reputation-BQe0rkfF.js → Reputation-LB0_D2lx.js} +1 -1
  62. package/src/assets/web-panel/assets/{Row-Dem0Wxxb.js → Row-BoiDd-zb.js} +1 -1
  63. package/src/assets/web-panel/assets/{RssFeed-pBY5G41C.js → RssFeed-yA5FdTgh.js} +2 -2
  64. package/src/assets/web-panel/assets/{Search-CtRepO6B.js → Search-xYldUFeJ.js} +1 -1
  65. package/src/assets/web-panel/assets/{Security-nrSlKpWq.js → Security-BtRMnkFm.js} +4 -4
  66. package/src/assets/web-panel/assets/{Services-DeaDBASi.js → Services-CNKTgE2v.js} +2 -2
  67. package/src/assets/web-panel/assets/{Skeleton-Cz9R-Wjb.js → Skeleton-B9goiUo_.js} +1 -1
  68. package/src/assets/web-panel/assets/{Skills-B3U-XLH3.js → Skills-CUf2Z5Ge.js} +1 -1
  69. package/src/assets/web-panel/assets/{Sla-Bu46dIA_.js → Sla-TPga8c2I.js} +1 -1
  70. package/src/assets/web-panel/assets/{SpeechSettings-C9Z0V0pk.js → SpeechSettings-B8zrmLP6.js} +1 -1
  71. package/src/assets/web-panel/assets/{SyncSettings-Ctj9KHHr.js → SyncSettings-DGyAbZ84.js} +2 -2
  72. package/src/assets/web-panel/assets/{Tasks-D4upQgR_.js → Tasks-DPeVrQNM.js} +1 -1
  73. package/src/assets/web-panel/assets/{Templates-JHsPGU_c.js → Templates-CNLco6pc.js} +1 -1
  74. package/src/assets/web-panel/assets/{Tenant-uoaQL3fB.js → Tenant-CL9Eczo8.js} +1 -1
  75. package/src/assets/web-panel/assets/Terminal-DGLvbp97.js +3 -0
  76. package/src/assets/web-panel/assets/{TimelineRenderer-BTicmSAV.js → TimelineRenderer-Cv0LxMwd.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tokens-Bp3BUe2K.js → Tokens-DLhHgtcS.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trigger-CgoISw5d.js → Trigger-DzDaE-An.js} +1 -1
  79. package/src/assets/web-panel/assets/{Trust-CC29awNT.js → Trust-CRh-fhYe.js} +1 -1
  80. package/src/assets/web-panel/assets/{UkeySign-CB1SB6Nc.js → UkeySign-_xBJ16UC.js} +1 -1
  81. package/src/assets/web-panel/assets/{VideoEditing-D7vptDUg.js → VideoEditing-Z5m_edIa.js} +1 -1
  82. package/src/assets/web-panel/assets/{Wallet-BWfjzF7p.js → Wallet-DgmchNit.js} +4 -4
  83. package/src/assets/web-panel/assets/{WebAuthn-Dzz5OnPc.js → WebAuthn-BxuKxjuf.js} +5 -5
  84. package/src/assets/web-panel/assets/{WorkflowEditor-CiDeVmsG.js → WorkflowEditor-luJ180aM.js} +1 -1
  85. package/src/assets/web-panel/assets/{chat-DQbciNb5.js → chat-DzglnTps.js} +1 -1
  86. package/src/assets/web-panel/assets/{colors-DcLbPJzb.js → colors-DelLNoxZ.js} +1 -1
  87. package/src/assets/web-panel/assets/{compact-item-CvYrR3rc.js → compact-item-BZaabUge.js} +1 -1
  88. package/src/assets/web-panel/assets/{createContext-BR4P7Rgm.js → createContext-DqTSTjmk.js} +1 -1
  89. package/src/assets/web-panel/assets/devWarning-CJLMPKYL.js +1 -0
  90. package/src/assets/web-panel/assets/{hasIn-IQ88RNRJ.js → hasIn-CAeHUQj2.js} +1 -1
  91. package/src/assets/web-panel/assets/index-7CJalvEf.js +1 -0
  92. package/src/assets/web-panel/assets/{index-Bw0Dm_P6.js → index-7FxBHcH8.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-Db5LFFCN.js → index-BAcpfWwI.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B5W1vQHV.js → index-BAfdWN9t.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BUTN1VlO.js → index-BR-DF81e.js} +3 -3
  96. package/src/assets/web-panel/assets/{index-BmPuR0aA.js → index-BTbN0V4A.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-D8OJdOc_.js → index-Bv2Tp7kz.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BhkZZXtI.js → index-BzLgm3Jm.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CzERBV9P.js → index-CBZPDGTg.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-UbB2IcFR.js → index-CBtnHlYF.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-eKd1n8pw.js → index-CDw1am9U.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-JqOP7puJ.js → index-CI5cynRw.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BiMlLIZ-.js → index-CKZQVcH1.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CgP5aQmA.js → index-CV4FisuU.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BH2RT15D.js → index-CaSLz8-6.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DgaCUxpi.js → index-Cj7oeTxA.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DdQBxvpt.js → index-Clq1OP4B.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Bl1TSbTE.js → index-CppTZ4SW.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BQXs-5db.js → index-D-QuIaEh.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DGwa8mnJ.js → index-D-lVDXUg.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DHIp5msb.js → index-DE4-6oHW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CCO8yc1h.js → index-DM7xncnU.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-b6FjzfoJ.js → index-DRt2lx0X.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Bo7HAK6G.js → index-DSATjRyg.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Dpmnk2qv.js → index-DU9QWJO5.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Mn8_ryOe.js → index-DXvcxNo5.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Dox9vEhP.js → index-DhMSUhbW.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-bRT7u-51.js → index-Dk1R9vFq.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BHxJnExB.js → index-DrSuq6t6.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CPOupQSX.js → index-DtfTElxo.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BxY0ozve.js → index-KeadEGaZ.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BvQpTO67.js → index-NZBXGj64.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CiOZ_Whh.js → index-OGKhEFZZ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CeCWyiFl.js → index-RZ23Wlp8.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BVb6RI7f.js → index-WzAdJ0PX.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Cm74AosZ.js → index-Xo2WWPZ4.js} +1 -1
  127. package/src/assets/web-panel/assets/index-_hLbeSOT.js +1 -0
  128. package/src/assets/web-panel/assets/{index-CE2mqX8w.js → index-kJ30C4m8.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Bu8931Yi.js → index-vLR-ssxc.js} +1 -1
  130. package/src/assets/web-panel/assets/{initDefaultProps-C0arzCLE.js → initDefaultProps-BiHvIjo1.js} +1 -1
  131. package/src/assets/web-panel/assets/{motion-C1K6JxwD.js → motion-COD0OBOe.js} +1 -1
  132. package/src/assets/web-panel/assets/{move-DREsRLHj.js → move-DJNLMhIj.js} +1 -1
  133. package/src/assets/web-panel/assets/{omit-BtPS3EDq.js → omit-4qrDRhlN.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-BPz6tHoT.js → pickAttrs-3jv8tAgW.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-B0CR_CSI.js → placementArrow-N1UVUOH_.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-Ch2ojiNn.js → responsiveObserve-D67_gjCH.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-9qU9vOhj.js → slide-DiDh7_u4.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-Cr4fICjV.js → statusUtils-Dzz3tSiz.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-Cor2-FwV.js → styleChecker-L-tgt7xx.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-BINo_rNH.js → useFlexGapSupport-vAgElNal.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-Dm1tDNYC.js → useFs-af0c_HYI.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-__JgBEkX.js → usePersonalDataHub-V9U2Mbny.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-1hQKpRgP.js → vnode-C7zS_LLr.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-C1EY9X2J.js → zoom-DdXBDemd.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/commands/audit.js +4 -3
  147. package/src/commands/automation.js +6 -14
  148. package/src/commands/bi.js +10 -9
  149. package/src/commands/codegen.js +5 -13
  150. package/src/commands/dao.js +8 -6
  151. package/src/commands/dbevo.js +13 -14
  152. package/src/commands/economy.js +3 -2
  153. package/src/commands/evolution.js +3 -2
  154. package/src/commands/federation.js +4 -3
  155. package/src/commands/governance.js +9 -4
  156. package/src/commands/hardening.js +5 -4
  157. package/src/commands/incentive.js +6 -5
  158. package/src/commands/kg.js +17 -10
  159. package/src/commands/lowcode.js +23 -11
  160. package/src/commands/marketplace.js +4 -3
  161. package/src/commands/mcp.js +17 -5
  162. package/src/commands/ops.js +9 -4
  163. package/src/commands/recommend.js +7 -5
  164. package/src/commands/scim.js +3 -2
  165. package/src/commands/session.js +9 -6
  166. package/src/commands/social.js +4 -3
  167. package/src/commands/sync.js +3 -2
  168. package/src/commands/tenant.js +11 -6
  169. package/src/commands/zkp.js +8 -9
  170. package/src/gateways/ws/ws-agent-handler.js +12 -3
  171. package/src/gateways/ws/ws-server.js +6 -0
  172. package/src/harness/background-task-manager.js +44 -18
  173. package/src/harness/mcp-client.js +125 -46
  174. package/src/lib/chat-core.js +209 -107
  175. package/src/lib/claude-code-bridge.js +13 -1
  176. package/src/lib/dao-governance.js +3 -3
  177. package/src/lib/downloader.js +82 -25
  178. package/src/lib/headless-config-command.js +62 -0
  179. package/src/lib/json-schema-output.js +55 -11
  180. package/src/lib/mcp-oauth.js +110 -21
  181. package/src/lib/mcp-serve.js +70 -11
  182. package/src/lib/multisig-runtime.js +22 -3
  183. package/src/lib/parse-json-option.js +35 -0
  184. package/src/lib/parse-number-option.js +27 -0
  185. package/src/lib/runnable-provider.js +216 -0
  186. package/src/repl/agent-repl.js +76 -17
  187. package/src/repl/config-summary.js +66 -0
  188. package/src/runtime/agent-core.js +210 -37
  189. package/src/runtime/headless-runner.js +49 -1
  190. package/src/runtime/headless-stream.js +34 -0
  191. package/src/assets/web-panel/assets/Terminal-CWRWr8bq.js +0 -3
  192. package/src/assets/web-panel/assets/devWarning-CnV02N63.js +0 -1
  193. package/src/assets/web-panel/assets/index-DJ2gkaIH.js +0 -1
  194. 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 (typeof voteCount !== "number" || voteCount <= 0) {
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 (typeof amount !== "number" || amount <= 0) {
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 (typeof amount !== "number" || amount <= 0) {
810
+ if (!Number.isFinite(amount) || amount <= 0) {
811
811
  throw new Error("amount must be a positive number");
812
812
  }
813
813
 
@@ -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 totalBytes = parseInt(
56
- response.headers.get("content-length") || "0",
57
- 10,
58
- );
59
- let downloadedBytes = 0;
60
-
61
- // Stream the fetch body to disk via an async generator (stable APIs only —
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
- yield value;
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
- spinner.fail(`Download failed: ${err.message}`);
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((t) => t === got || (t === "number" && got === "integer"));
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 (schema.enum && !schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))) {
44
- errors.push(`${path}: value not in enum [${schema.enum.map((e) => JSON.stringify(e)).join(", ")}]`);
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 (schema.const !== undefined && JSON.stringify(schema.const) !== JSON.stringify(value)) {
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)) errors.push(`${path}: missing required property "${req}"`);
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(...validateAgainstSchema(item, schema.items, `${path}[${i}]`));
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) tries.push(raw.slice(firstObj, lastObj + 1));
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) tries.push(raw.slice(firstArr, lastArr + 1));
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 = String(outcome?.result ?? captured ?? "").trim() || captured.trim();
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) {
@@ -39,7 +39,11 @@ export const _deps = {
39
39
  };
40
40
 
41
41
  const base64url = (buf) =>
42
- Buffer.from(buf).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
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
+ "&": "&amp;",
67
+ "<": "&lt;",
68
+ ">": "&gt;",
69
+ '"': "&quot;",
70
+ "'": "&#39;",
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(`HTTP ${status}${body ? `: ${body.slice(0, 200)}` : ""}`);
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(serverUrl, { resourceMetadataUrl } = {}) {
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 (Array.isArray(pr.authorization_servers) && pr.authorization_servers[0]) {
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(metadata, { redirectUri, clientName = "chainlesschain-cli" } = {}) {
179
+ export async function registerClient(
180
+ metadata,
181
+ { redirectUri, clientName = "chainlesschain-cli" } = {},
182
+ ) {
122
183
  if (!metadata.registration_endpoint) {
123
- throw new Error("server has no registration_endpoint and no --client-id was given");
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) throw new Error("registration did not return a 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(metadata, { clientId, redirectUri, scope, codeChallenge, state, resource }) {
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(metadata, { code, codeVerifier, clientId, clientSecret, redirectUri, resource }) {
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(metadata, { refreshToken, clientId, clientSecret, resource }) {
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(file, JSON.stringify(store, null, 2) + "\n", "utf-8");
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({ port, host = "127.0.0.1", path = "/callback", timeout = 300_000 }) {
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: scope || (metadata.scopes_supported ? metadata.scopes_supported.join(" ") : undefined),
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({ port, host, path: redirectPath, timeout });
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) throw new Error("OAuth state mismatch (possible CSRF)");
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,