chainlesschain 0.162.12 → 0.162.14

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 (170) hide show
  1. package/README.md +29 -24
  2. package/package.json +5 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-C3TDNq29.js → AIOps-D34d_Nh1.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-C9fE18pE.js → ActionButton-Br7HxCnl.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-wnZF602C.js → Analytics-bVKq79Xd.js} +1 -1
  7. package/src/assets/web-panel/assets/{AppLayout-BjgTMK7O.js → AppLayout-CWSLIbAz.js} +2 -2
  8. package/src/assets/web-panel/assets/{Audit-BBL0BW5_.js → Audit-Cmnu1qqa.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BKLqYCWU.js → Backup-Rok20-TL.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BGSzMCZs.js → BaseInput-BJzs_ZtT.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CQWzZWEY.js → Chat-CSYapbcq.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-BkTri12Q.js → Checkbox-BEa7Sr7e.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-BH1m09EO.js → Codegen-C9M4e7ne.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-BXnBuqIa.js → Col-DU9NoUIi.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-C_Nr4XCx.js → Community-DA9uz_jP.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-Du6GwLrj.js → Compact-3_bEraVw.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compliance-66M0oi1Q.js → Compliance-BtF8jWUQ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-DQrkZRNd.js → Cowork-BqvA7oaM.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cron-CwdIFH_v.js → Cron-CxZy7Mzg.js} +1 -1
  20. package/src/assets/web-panel/assets/{Crosschain-DqlcrQ9L.js → Crosschain-1DB-XRGu.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-OmPLKf7L.js → DID-B6Ezp1pt.js} +1 -1
  22. package/src/assets/web-panel/assets/{Dashboard-D_dampTL.js → Dashboard-QDJ6VVsn.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-CA1W7jAn.js → Dropdown-CovsWjxG.js} +1 -1
  24. package/src/assets/web-panel/assets/{Federation-Chlk9a7s.js → Federation-DbRxS4Y4.js} +1 -1
  25. package/src/assets/web-panel/assets/{FormItemContext-t0UqYFLq.js → FormItemContext-9E9dNGtx.js} +1 -1
  26. package/src/assets/web-panel/assets/{Git-CEq0raYm.js → Git-CqEpyxRZ.js} +2 -2
  27. package/src/assets/web-panel/assets/{Governance-C06CX7Ge.js → Governance-On47KtGq.js} +1 -1
  28. package/src/assets/web-panel/assets/{Inference-6VIFHxIP.js → Inference-RZcjcyaq.js} +1 -1
  29. package/src/assets/web-panel/assets/{KnowledgeGraph-BCJPjMBQ.js → KnowledgeGraph-C-1rRAM9.js} +1 -1
  30. package/src/assets/web-panel/assets/{Logs-BBpOYFct.js → Logs-BuunmG_r.js} +1 -1
  31. package/src/assets/web-panel/assets/{Marketplace-BFH6jMWt.js → Marketplace-CromymyA.js} +1 -1
  32. package/src/assets/web-panel/assets/{McpTools-uCFvRqGs.js → McpTools-5XlFExh7.js} +1 -1
  33. package/src/assets/web-panel/assets/{Memory-B0Kux_KT.js → Memory-DjnUT7YM.js} +1 -1
  34. package/src/assets/web-panel/assets/{MobileBridge-DHow2jiK.js → MobileBridge-BrYIgLg6.js} +1 -1
  35. package/src/assets/web-panel/assets/{MobileProjects-BFo9YQZp.js → MobileProjects-CL5V3fTm.js} +1 -1
  36. package/src/assets/web-panel/assets/{Mtc-riOh1G_F.js → Mtc-CHYJq6zK.js} +1 -1
  37. package/src/assets/web-panel/assets/{MtcAudit-Bm-hE2SP.js → MtcAudit-BZxUO0qt.js} +1 -1
  38. package/src/assets/web-panel/assets/{Multisig-DfUQxh5a.js → Multisig-FZTmJgW1.js} +2 -2
  39. package/src/assets/web-panel/assets/{NLProgramming-DuNvLBEq.js → NLProgramming-C9Mhefph.js} +1 -1
  40. package/src/assets/web-panel/assets/{Notes-DB20wd3c.js → Notes-W7usj-Ar.js} +1 -1
  41. package/src/assets/web-panel/assets/{NotificationSettings-CB-GkOWR.js → NotificationSettings-PBuYv_Bh.js} +1 -1
  42. package/src/assets/web-panel/assets/{Organization-3bU7PZuG.js → Organization-CuYCE-rF.js} +4 -4
  43. package/src/assets/web-panel/assets/{Overflow-BGCPP_0Y.js → Overflow-Dojx-kzE.js} +1 -1
  44. package/src/assets/web-panel/assets/{OverrideContext-x9ZzjLwk.js → OverrideContext-C_4H9tGA.js} +1 -1
  45. package/src/assets/web-panel/assets/{P2P-BHgAe1oC.js → P2P-BgIaSrLX.js} +1 -1
  46. package/src/assets/web-panel/assets/{Permissions-BuOD4xwc.js → Permissions-Byj2dkF_.js} +1 -1
  47. package/src/assets/web-panel/assets/PersonalDataHub-CMOOI13-.js +1 -0
  48. package/src/assets/web-panel/assets/PersonalDataHub-Dvaa8niQ.css +1 -0
  49. package/src/assets/web-panel/assets/{Pipeline-DBS5U4LB.js → Pipeline-CWwEOF09.js} +1 -1
  50. package/src/assets/web-panel/assets/{Privacy-UNjIc5El.js → Privacy-VT7gldcN.js} +1 -1
  51. package/src/assets/web-panel/assets/{ProjectInit-CicqCJGy.js → ProjectInit-7UH3c3p7.js} +1 -1
  52. package/src/assets/web-panel/assets/{ProjectSettings-CIxAbt4Y.js → ProjectSettings-DqLp-72a.js} +1 -1
  53. package/src/assets/web-panel/assets/{Projects-BJycZScO.js → Projects-B_54eDhH.js} +1 -1
  54. package/src/assets/web-panel/assets/{Providers-DxXvprme.js → Providers-BIrNfNpc.js} +1 -1
  55. package/src/assets/web-panel/assets/{QuickAsk-rrqjU8_Y.js → QuickAsk-BbYPwCso.js} +1 -1
  56. package/src/assets/web-panel/assets/{Recommend-BEwHMhI7.js → Recommend-BF4qBssF.js} +1 -1
  57. package/src/assets/web-panel/assets/{Reputation-DoVKCCMn.js → Reputation-DPEzlC2V.js} +1 -1
  58. package/src/assets/web-panel/assets/{Row-F5XcDhHr.js → Row-DjHxhH1L.js} +1 -1
  59. package/src/assets/web-panel/assets/{RssFeed-cZrRG7k8.js → RssFeed-D0_j678P.js} +1 -1
  60. package/src/assets/web-panel/assets/{Search-B9ctZjqx.js → Search-DctfGehu.js} +1 -1
  61. package/src/assets/web-panel/assets/{Security-Z62hl1mc.js → Security-BFHggeYM.js} +1 -1
  62. package/src/assets/web-panel/assets/{Services-CQf5XqgZ.js → Services-CmrFMukV.js} +1 -1
  63. package/src/assets/web-panel/assets/{Skeleton-DuCKw2Eh.js → Skeleton-DR4vn_nS.js} +1 -1
  64. package/src/assets/web-panel/assets/{Skills-qVkhva0s.js → Skills-DlXG2yyV.js} +1 -1
  65. package/src/assets/web-panel/assets/{Sla-BQbatr7s.js → Sla-4PPGL3SE.js} +1 -1
  66. package/src/assets/web-panel/assets/{SpeechSettings-DLFBzAgD.js → SpeechSettings-D9EhJOqm.js} +1 -1
  67. package/src/assets/web-panel/assets/{SyncSettings-CrzETZMW.js → SyncSettings-Dasmbi0p.js} +1 -1
  68. package/src/assets/web-panel/assets/{Tasks-D_EQ1nJ7.js → Tasks-vilEiuPA.js} +1 -1
  69. package/src/assets/web-panel/assets/{Templates-D4y-dGRc.js → Templates-Ca9Rvktn.js} +1 -1
  70. package/src/assets/web-panel/assets/{Tenant-2XI0jkPn.js → Tenant-CEZb9gfK.js} +1 -1
  71. package/src/assets/web-panel/assets/{Terminal-fUi5V2Z9.js → Terminal-DanCBdbD.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tokens-BuUNB2mg.js → Tokens-SPkClW2d.js} +1 -1
  73. package/src/assets/web-panel/assets/{Trigger-7DqLLuej.js → Trigger-B645yL7g.js} +1 -1
  74. package/src/assets/web-panel/assets/{Trust-CeACvTYx.js → Trust-D9sM_Ig0.js} +1 -1
  75. package/src/assets/web-panel/assets/{UkeySign-mDP9EXHq.js → UkeySign-B_Nr2K-u.js} +1 -1
  76. package/src/assets/web-panel/assets/{VideoEditing-veWlKclv.js → VideoEditing-U01Lea8j.js} +1 -1
  77. package/src/assets/web-panel/assets/{Wallet-Cd2Hheb8.js → Wallet-6xBySVV8.js} +1 -1
  78. package/src/assets/web-panel/assets/{WebAuthn-DyL7ZiHX.js → WebAuthn-DbgMoBu6.js} +3 -3
  79. package/src/assets/web-panel/assets/{WorkflowEditor-C7-7LJH9.js → WorkflowEditor-Bz-Y6IR2.js} +1 -1
  80. package/src/assets/web-panel/assets/{chat-DXomZMuo.js → chat-BC_O9hag.js} +1 -1
  81. package/src/assets/web-panel/assets/{collapseMotion-CjFH_Jop.js → collapseMotion-DfnRZex1.js} +1 -1
  82. package/src/assets/web-panel/assets/{colors-DlU92QNs.js → colors-ChlOGOvr.js} +1 -1
  83. package/src/assets/web-panel/assets/{compact-item-sBiTL8mX.js → compact-item-BSbAYGGF.js} +1 -1
  84. package/src/assets/web-panel/assets/{createContext-DZXEnzum.js → createContext-CFcZly5M.js} +1 -1
  85. package/src/assets/web-panel/assets/{echarts-Bq-n0MtJ.js → echarts-Dj_pBaVI.js} +1 -1
  86. package/src/assets/web-panel/assets/{hasIn-CpCHBZ2M.js → hasIn-BomYwwYE.js} +1 -1
  87. package/src/assets/web-panel/assets/{icons-CLQTHa5-.js → icons-BOPtEWK4.js} +4 -4
  88. package/src/assets/web-panel/assets/{index-B0Qbxr57.js → index-5Ewm6KZA.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-CjXSvceY.js → index-B6LJHQoE.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-CD3iljXs.js → index-BEJ6YiLI.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BK2AFy44.js → index-BGUbtM3R.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-Di1_EQ-X.js → index-BHGsFwYW.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-B23tuoo9.js → index-BHi69MHF.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DUlPMzoM.js → index-BI1jAWcc.js} +1 -1
  95. package/src/assets/web-panel/assets/index-BIRYt1of.js +1 -0
  96. package/src/assets/web-panel/assets/{index-B3k9UPHc.js → index-BYDvb1pi.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CwbWZubA.js → index-BZGdjNLA.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-ThrAiEF9.js → index-BwFykZ5U.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CUe5t5Aa.js → index-ByZQNO0A.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Dn8m1d1f.js → index-C0rr1X9W.js} +2 -2
  101. package/src/assets/web-panel/assets/{index-C6SDf50u.js → index-CBhoZhCO.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-ClN_JuFa.js → index-CCRSz2cR.js} +1 -1
  103. package/src/assets/web-panel/assets/index-CZfySmWX.js +1 -0
  104. package/src/assets/web-panel/assets/{index-Dq5Rn5VS.js → index-Cj47XwJQ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-ChahjdYE.js → index-Cmzh8gKL.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Dyg6ikIL.js → index-CnxlKTDK.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Dj9Nvz6S.js → index-CoF95pYK.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-TwQZkVGh.js → index-Ctx97mH-.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-s8tvk-fF.js → index-D0vX9jQA.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-X48zYgZ6.js → index-DNkth8dM.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DygNvCeR.js → index-DRp5_Xns.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D_MzScPM.js → index-DW-Ji07y.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BqnhEJls.js → index-DXgE2VW6.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CDyzZ8_O.js → index-D_0B3CiU.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C59FgSkU.js → index-Dbf5YmDX.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-_zyXBoS7.js → index-DsNQ2hqI.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CJ7XYa5K.js → index-EY733h9z.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-ibFHnqHz.js → index-QD_n54XT.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CivbS-57.js → index-T5Y_9IPv.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-C52udT0_.js → index-b8GbH2Yi.js} +4 -4
  121. package/src/assets/web-panel/assets/{index-XI6772AD.js → index-gUACAWbM.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-F9cBucYf.js → index-onW325hZ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DKe9jmKG.js → index-ozVPr1gj.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D_IgY63-.js → index-slYX2rCE.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B_-RETt0.js → index-t9u2bHpH.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DeGnHcp5.js → index-za1GUJBG.js} +1 -1
  127. package/src/assets/web-panel/assets/{initDefaultProps-DEi92ZnZ.js → initDefaultProps-DnadEaxu.js} +1 -1
  128. package/src/assets/web-panel/assets/{motion-BtYKzpOc.js → motion-CC_Na0Tl.js} +1 -1
  129. package/src/assets/web-panel/assets/{move-Cb3A1-v-.js → move-C2d9Mkk9.js} +1 -1
  130. package/src/assets/web-panel/assets/{omit-B6qPDdOf.js → omit-QvpKbF8p.js} +1 -1
  131. package/src/assets/web-panel/assets/{pickAttrs-DDyeQMUc.js → pickAttrs-Dm8r3X1_.js} +1 -1
  132. package/src/assets/web-panel/assets/{placementArrow-BPV6VO47.js → placementArrow-DaqaVfoX.js} +1 -1
  133. package/src/assets/web-panel/assets/{responsiveObserve-DJ1ra4dT.js → responsiveObserve-Iida9fIn.js} +1 -1
  134. package/src/assets/web-panel/assets/{slide-D6v8tHvB.js → slide-YqHexXQD.js} +1 -1
  135. package/src/assets/web-panel/assets/{statusUtils-DulKcQLZ.js → statusUtils-BGKLoeEt.js} +1 -1
  136. package/src/assets/web-panel/assets/{styleChecker-Bne7zwMt.js → styleChecker-aI-gsQO8.js} +1 -1
  137. package/src/assets/web-panel/assets/useFlexGapSupport-BiOsz4rc.js +1 -0
  138. package/src/assets/web-panel/assets/{useFs-CR-iLa4Z.js → useFs-CZy7Zo2X.js} +1 -1
  139. package/src/assets/web-panel/assets/{useMergedState-O7QXt4P5.js → useMergedState-WwedrFR0.js} +1 -1
  140. package/src/assets/web-panel/assets/{useRefs-0J6m8UWN.js → useRefs-Cdq8EWeF.js} +1 -1
  141. package/src/assets/web-panel/assets/{useState-CSzR8F8O.js → useState-DGS1NOyn.js} +1 -1
  142. package/src/assets/web-panel/assets/{vendor-M5lGV-wr.js → vendor-DhFY8mDK.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-yL9axxBy.js → vnode-B6WqjmE4.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-B-VCMXSD.js → zoom-DTeTrJ2z.js} +1 -1
  145. package/src/assets/web-panel/index.html +3 -3
  146. package/src/commands/__tests__/android.test.js +260 -0
  147. package/src/commands/__tests__/hub-aichat.test.js +277 -0
  148. package/src/commands/__tests__/hub-wechat.test.js +243 -0
  149. package/src/commands/android.js +284 -0
  150. package/src/commands/hub.js +457 -0
  151. package/src/commands/sync-providers.js +436 -0
  152. package/src/gateways/ws/personal-data-hub-protocol.js +88 -0
  153. package/src/index.js +6 -0
  154. package/src/lib/__tests__/personal-data-hub-aichat-wizard.test.js +209 -0
  155. package/src/lib/__tests__/sync-credentials.test.js +265 -0
  156. package/src/lib/__tests__/sync-engine-cli.test.js +293 -0
  157. package/src/lib/cc-android-bridge.js +162 -0
  158. package/src/lib/personal-data-hub-aichat-wizard.js +242 -0
  159. package/src/lib/personal-data-hub-wiring.js +258 -13
  160. package/src/lib/sync-cli-db.js +194 -0
  161. package/src/lib/sync-credentials.js +225 -0
  162. package/src/lib/sync-engine-cli.js +406 -0
  163. package/src/lib/sync-oss-client.js +273 -0
  164. package/src/lib/sync-webdav-client.js +194 -0
  165. package/src/lib/web-ui-server.js +2 -1
  166. package/src/assets/web-panel/assets/PersonalDataHub--WA-aZAJ.js +0 -1
  167. package/src/assets/web-panel/assets/PersonalDataHub-BK7I0Rsb.css +0 -1
  168. package/src/assets/web-panel/assets/index-CcRX6BlT.js +0 -1
  169. package/src/assets/web-panel/assets/index-z6h6tqP3.js +0 -1
  170. package/src/assets/web-panel/assets/useFlexGapSupport-C1miTomM.js +0 -1
@@ -0,0 +1,209 @@
1
+ /**
2
+ * cli-side AIChat WebView 鉴权向导 (Phase 10.3.4) unit tests.
3
+ *
4
+ * `personal-data-hub-aichat-wizard.js` is ESM. We test the bridge + paste
5
+ * mode + factory wiring without touching disk by using temp dirs and
6
+ * dependency injection.
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach } from "vitest";
10
+ import { mkdtempSync, rmSync, readFileSync, existsSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+
14
+ import {
15
+ createAccountsStore,
16
+ createVendorAdapterBridge,
17
+ getAIChatWizard,
18
+ _jarToArray,
19
+ _resetForTests,
20
+ ACCOUNTS_FILE,
21
+ } from "../personal-data-hub-aichat-wizard.js";
22
+
23
+ // ─── createAccountsStore (real fs, temp dir) ──────────────────────────────
24
+
25
+ describe("createAccountsStore (ESM, real fs)", () => {
26
+ let hubDir;
27
+ beforeEach(() => {
28
+ hubDir = mkdtempSync(join(tmpdir(), "aichat-store-"));
29
+ });
30
+
31
+ it("returns null for missing file", async () => {
32
+ const store = createAccountsStore({ hubDir });
33
+ expect(await store.get("deepseek")).toBeNull();
34
+ expect(await store.list()).toEqual([]);
35
+ rmSync(hubDir, { recursive: true, force: true });
36
+ });
37
+
38
+ it("put / get round-trips and creates the 0600 file", async () => {
39
+ const store = createAccountsStore({ hubDir });
40
+ await store.put("deepseek", {
41
+ vendor: "deepseek",
42
+ cookies: { userToken: "x" },
43
+ });
44
+ expect(existsSync(join(hubDir, ACCOUNTS_FILE))).toBe(true);
45
+ const stored = await store.get("deepseek");
46
+ expect(stored).toMatchObject({
47
+ vendor: "deepseek",
48
+ cookies: { userToken: "x" },
49
+ });
50
+ rmSync(hubDir, { recursive: true, force: true });
51
+ });
52
+
53
+ it("delete drops the file when no entries remain", async () => {
54
+ const store = createAccountsStore({ hubDir });
55
+ await store.put("kimi", { vendor: "kimi", cookies: { access_token: "x" } });
56
+ await store.delete("kimi");
57
+ expect(existsSync(join(hubDir, ACCOUNTS_FILE))).toBe(false);
58
+ rmSync(hubDir, { recursive: true, force: true });
59
+ });
60
+
61
+ it("throws when hubDir missing", () => {
62
+ expect(() => createAccountsStore({})).toThrow(/hubDir/);
63
+ });
64
+ });
65
+
66
+ // ─── createVendorAdapterBridge ─────────────────────────────────────────────
67
+
68
+ function fakeSpec(name, validateImpl) {
69
+ return {
70
+ name,
71
+ displayName: name,
72
+ rateLimits: { perMinute: 10, minIntervalMs: 1000 },
73
+ cookieDomains: [name + ".example"],
74
+ validateCookie: validateImpl,
75
+ };
76
+ }
77
+
78
+ describe("createVendorAdapterBridge (cli)", () => {
79
+ it("dispatches to validateCookie and returns its result", async () => {
80
+ const bridge = createVendorAdapterBridge({
81
+ specs: [
82
+ fakeSpec("deepseek", async ({ session }) => ({
83
+ ok: true,
84
+ userId: session.cookies[0].name,
85
+ })),
86
+ ],
87
+ _httpClientFactory: () => ({}),
88
+ });
89
+ const r = await bridge.registerVendor("deepseek", { userToken: "abc" });
90
+ expect(r.ok).toBe(true);
91
+ expect(r.userId).toBe("userToken");
92
+ });
93
+
94
+ it("UNKNOWN_VENDOR for unrecognized", async () => {
95
+ const bridge = createVendorAdapterBridge({
96
+ specs: [fakeSpec("deepseek", async () => ({ ok: true }))],
97
+ _httpClientFactory: () => ({}),
98
+ });
99
+ const r = await bridge.registerVendor("ghost", { x: "y" });
100
+ expect(r.reason).toBe("UNKNOWN_VENDOR");
101
+ });
102
+
103
+ it("VALIDATE_THREW when validateCookie rejects", async () => {
104
+ const bridge = createVendorAdapterBridge({
105
+ specs: [
106
+ fakeSpec("deepseek", async () => {
107
+ throw new Error("boom");
108
+ }),
109
+ ],
110
+ _httpClientFactory: () => ({}),
111
+ });
112
+ const r = await bridge.registerVendor("deepseek", { x: "y" });
113
+ expect(r.reason).toBe("VALIDATE_THREW");
114
+ expect(r.error).toMatch(/boom/);
115
+ });
116
+
117
+ it("rejects empty specs", () => {
118
+ expect(() => createVendorAdapterBridge({ specs: [] })).toThrow(/specs/);
119
+ });
120
+ });
121
+
122
+ // ─── _jarToArray ──────────────────────────────────────────────────────────
123
+
124
+ describe("_jarToArray (cli)", () => {
125
+ it("array passthrough", () => {
126
+ expect(_jarToArray([{ name: "a", value: "1" }])).toEqual([
127
+ { name: "a", value: "1" },
128
+ ]);
129
+ });
130
+ it("object → array", () => {
131
+ expect(_jarToArray({ a: "1", b: "2" })).toEqual([
132
+ { name: "a", value: "1" },
133
+ { name: "b", value: "2" },
134
+ ]);
135
+ });
136
+ it("string → array, splits on ;", () => {
137
+ expect(_jarToArray("a=1; b=2")).toEqual([
138
+ { name: "a", value: "1" },
139
+ { name: "b", value: "2" },
140
+ ]);
141
+ });
142
+ it("nullish → empty", () => {
143
+ expect(_jarToArray(null)).toEqual([]);
144
+ expect(_jarToArray(undefined)).toEqual([]);
145
+ });
146
+ });
147
+
148
+ // ─── getAIChatWizard — paste-mode is the default ──────────────────────────
149
+
150
+ describe("getAIChatWizard — paste-mode wiring", () => {
151
+ let hubDir;
152
+ beforeEach(() => {
153
+ _resetForTests();
154
+ hubDir = mkdtempSync(join(tmpdir(), "aichat-wiz-"));
155
+ });
156
+
157
+ it("openVendorLogin returns paste fallback by default", async () => {
158
+ const wiz = getAIChatWizard({ hubDir });
159
+ const r = await wiz.openVendorLogin({ vendor: "deepseek" });
160
+ expect(r.ok).toBe(true);
161
+ expect(r.fallbackMode).toBe("paste");
162
+ expect(r.loginUrl).toMatch(/chat\.deepseek\.com/);
163
+ expect(r.helpText).toMatch(/外部浏览器/);
164
+ rmSync(hubDir, { recursive: true, force: true });
165
+ });
166
+
167
+ it("probeCookies with raw cookieHeader succeeds without disk write", async () => {
168
+ const wiz = getAIChatWizard({ hubDir });
169
+ const r = await wiz.probeCookies({
170
+ vendor: "doubao",
171
+ cookieHeader: "sessionid=abc; sid_guard=xyz",
172
+ });
173
+ expect(r.ok).toBe(true);
174
+ expect(r.source).toBe("paste");
175
+ expect(r.cookies.sessionid).toBe("abc");
176
+ rmSync(hubDir, { recursive: true, force: true });
177
+ });
178
+
179
+ it("probeCookies without cookieHeader returns PASTE_REQUIRED", async () => {
180
+ const wiz = getAIChatWizard({ hubDir });
181
+ const r = await wiz.probeCookies({ vendor: "doubao" });
182
+ expect(r.ok).toBe(false);
183
+ expect(r.reason).toBe("PASTE_REQUIRED");
184
+ rmSync(hubDir, { recursive: true, force: true });
185
+ });
186
+
187
+ it("registerVendor persists to file when vendorAdapter validates ok", async () => {
188
+ const _vendorAdapter = {
189
+ registerVendor: async () => ({ ok: true, userId: "u_42" }),
190
+ };
191
+ const wiz = getAIChatWizard({ hubDir, _vendorAdapter });
192
+ const r = await wiz.registerVendor({
193
+ vendor: "doubao",
194
+ cookies: { sessionid: "abc" },
195
+ });
196
+ expect(r.ok).toBe(true);
197
+ expect(r.accountId).toBe("doubao:u_42");
198
+ const onDisk = JSON.parse(
199
+ readFileSync(join(hubDir, ACCOUNTS_FILE), "utf-8"),
200
+ );
201
+ expect(onDisk.doubao).toBeTruthy();
202
+ expect(onDisk.doubao.userId).toBe("u_42");
203
+ rmSync(hubDir, { recursive: true, force: true });
204
+ });
205
+
206
+ it("throws when hubDir is missing", () => {
207
+ expect(() => getAIChatWizard({})).toThrow(/hubDir/);
208
+ });
209
+ });
@@ -0,0 +1,265 @@
1
+ /**
2
+ * sync-credentials 单元测试 — Phase 3c follow-up CLI safeStorage.
3
+ *
4
+ * 用 tmp dir + _setCcDirForTest 避开真实 ~/.chainlesschain/,
5
+ * 单测互不污染。
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import os from "node:os";
12
+
13
+ import {
14
+ ALLOWED_PROVIDER_IDS,
15
+ SENSITIVE_FIELDS,
16
+ MASK,
17
+ getCredentials,
18
+ getCredentialsSanitized,
19
+ hasCredentials,
20
+ setCredentials,
21
+ clearCredentials,
22
+ _setCcDirForTest,
23
+ _resetCcDirForTest,
24
+ _keyPath,
25
+ _vaultPath,
26
+ } from "../sync-credentials.js";
27
+
28
+ let tmpDir;
29
+
30
+ beforeEach(() => {
31
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cc-sync-cred-test-"));
32
+ _setCcDirForTest(tmpDir);
33
+ });
34
+
35
+ afterEach(() => {
36
+ _resetCcDirForTest();
37
+ try {
38
+ fs.rmSync(tmpDir, { recursive: true, force: true });
39
+ } catch (_e) {
40
+ /* test cleanup best-effort */
41
+ }
42
+ });
43
+
44
+ describe("sync-credentials · constants", () => {
45
+ it("ALLOWED_PROVIDER_IDS includes webdav + oss", () => {
46
+ expect(ALLOWED_PROVIDER_IDS).toEqual(
47
+ expect.arrayContaining(["webdav", "oss"]),
48
+ );
49
+ });
50
+
51
+ it("SENSITIVE_FIELDS includes password + secretAccessKey paths", () => {
52
+ expect(SENSITIVE_FIELDS).toEqual(
53
+ expect.arrayContaining([
54
+ "sync.webdav.password",
55
+ "sync.oss.secretAccessKey",
56
+ ]),
57
+ );
58
+ });
59
+ });
60
+
61
+ describe("sync-credentials · master key auto-gen", () => {
62
+ it("first setCredentials creates key + vault files", () => {
63
+ expect(fs.existsSync(_keyPath())).toBe(false);
64
+ expect(fs.existsSync(_vaultPath())).toBe(false);
65
+ setCredentials("webdav", {
66
+ url: "https://x",
67
+ username: "u",
68
+ password: "p",
69
+ });
70
+ expect(fs.existsSync(_keyPath())).toBe(true);
71
+ expect(fs.existsSync(_vaultPath())).toBe(true);
72
+ });
73
+
74
+ it("key file is 32 bytes", () => {
75
+ setCredentials("webdav", { url: "https://x", password: "p" });
76
+ const buf = fs.readFileSync(_keyPath());
77
+ expect(buf.length).toBe(32);
78
+ });
79
+
80
+ it("vault file is encrypted (NOT plaintext JSON)", () => {
81
+ // Use a randomly-generated value so security-check.js doesn't flag
82
+ // this test file as committing a literal password.
83
+ const probe = "probe-" + Math.random().toString(36).slice(2);
84
+ setCredentials("webdav", { url: "https://x", password: probe });
85
+ const buf = fs.readFileSync(_vaultPath());
86
+ const text = buf.toString("utf-8");
87
+ expect(text).not.toContain(probe);
88
+ expect(() => JSON.parse(text)).toThrow();
89
+ });
90
+
91
+ it("reuses same key across calls (deterministic decrypt)", () => {
92
+ setCredentials("webdav", { url: "https://x", password: "p1" });
93
+ const keyBefore = fs.readFileSync(_keyPath());
94
+ setCredentials("webdav", { url: "https://x", password: "p2" });
95
+ const keyAfter = fs.readFileSync(_keyPath());
96
+ expect(keyAfter.equals(keyBefore)).toBe(true);
97
+ });
98
+ });
99
+
100
+ describe("sync-credentials · round-trip", () => {
101
+ it("setCredentials → getCredentials returns plain values", () => {
102
+ setCredentials("webdav", {
103
+ url: "https://nas",
104
+ username: "alice",
105
+ password: "secret",
106
+ remotePath: "/cc",
107
+ });
108
+ const got = getCredentials("webdav");
109
+ expect(got).toEqual({
110
+ url: "https://nas",
111
+ username: "alice",
112
+ password: "secret",
113
+ remotePath: "/cc",
114
+ });
115
+ });
116
+
117
+ it("OSS round-trip preserves all 7 fields", () => {
118
+ const oss = {
119
+ endpoint: "https://oss.example.com",
120
+ region: "us-east-1",
121
+ bucket: "b",
122
+ accessKeyId: "AKI",
123
+ secretAccessKey: "SK",
124
+ remotePath: "cc/",
125
+ forcePathStyle: true,
126
+ };
127
+ setCredentials("oss", oss);
128
+ expect(getCredentials("oss")).toEqual(oss);
129
+ });
130
+
131
+ it("multi-provider isolation", () => {
132
+ setCredentials("webdav", { url: "https://x", password: "p1" });
133
+ setCredentials("oss", {
134
+ endpoint: "https://o",
135
+ bucket: "b",
136
+ accessKeyId: "k",
137
+ secretAccessKey: "s",
138
+ });
139
+ expect(getCredentials("webdav").password).toBe("p1");
140
+ expect(getCredentials("oss").secretAccessKey).toBe("s");
141
+ // Crossover check
142
+ expect(getCredentials("webdav").secretAccessKey).toBeUndefined();
143
+ expect(getCredentials("oss").password).toBeUndefined();
144
+ });
145
+
146
+ it("getCredentials before any set returns {}", () => {
147
+ expect(getCredentials("webdav")).toEqual({});
148
+ });
149
+ });
150
+
151
+ describe("sync-credentials · sanitize", () => {
152
+ it("getCredentialsSanitized masks password field", () => {
153
+ // Probe value is random to avoid security-check.js pattern hits
154
+ const probe = "probe-" + Math.random().toString(36).slice(2);
155
+ setCredentials("webdav", {
156
+ url: "https://x",
157
+ username: "u",
158
+ password: probe,
159
+ remotePath: "/",
160
+ });
161
+ const masked = getCredentialsSanitized("webdav");
162
+ expect(masked.password).toBe(MASK);
163
+ expect(masked.url).toBe("https://x"); // unmasked
164
+ expect(masked.username).toBe("u");
165
+ });
166
+
167
+ it("getCredentialsSanitized masks secretAccessKey", () => {
168
+ setCredentials("oss", {
169
+ endpoint: "https://o",
170
+ bucket: "b",
171
+ accessKeyId: "AKI",
172
+ secretAccessKey: "SUPERSECRET",
173
+ });
174
+ const masked = getCredentialsSanitized("oss");
175
+ expect(masked.secretAccessKey).toBe(MASK);
176
+ expect(masked.accessKeyId).toBe("AKI"); // unmasked
177
+ });
178
+
179
+ it("empty/missing sensitive field not masked", () => {
180
+ setCredentials("webdav", { url: "https://x", password: "" });
181
+ const masked = getCredentialsSanitized("webdav");
182
+ expect(masked.password).toBe(""); // empty stays empty, no mask
183
+ });
184
+ });
185
+
186
+ describe("sync-credentials · hasCredentials", () => {
187
+ it("false before configuration", () => {
188
+ expect(hasCredentials("webdav")).toBe(false);
189
+ });
190
+
191
+ it("true after any non-empty field saved", () => {
192
+ setCredentials("webdav", { url: "https://x", password: "p" });
193
+ expect(hasCredentials("webdav")).toBe(true);
194
+ });
195
+
196
+ it("true even when only sensitive field set", () => {
197
+ setCredentials("oss", {
198
+ endpoint: "",
199
+ bucket: "",
200
+ accessKeyId: "",
201
+ secretAccessKey: "s",
202
+ });
203
+ expect(hasCredentials("oss")).toBe(true);
204
+ });
205
+ });
206
+
207
+ describe("sync-credentials · clearCredentials", () => {
208
+ it("removes provider creds; other providers untouched", () => {
209
+ setCredentials("webdav", { url: "https://x", password: "p" });
210
+ setCredentials("oss", {
211
+ endpoint: "https://o",
212
+ bucket: "b",
213
+ accessKeyId: "k",
214
+ secretAccessKey: "s",
215
+ });
216
+ clearCredentials("webdav");
217
+ expect(hasCredentials("webdav")).toBe(false);
218
+ expect(hasCredentials("oss")).toBe(true);
219
+ });
220
+
221
+ it("clearing non-configured provider is a no-op (returns true)", () => {
222
+ expect(clearCredentials("webdav")).toBe(true);
223
+ });
224
+ });
225
+
226
+ describe("sync-credentials · validation", () => {
227
+ it("setCredentials rejects unknown provider", () => {
228
+ expect(() => setCredentials("dropbox", { token: "t" })).toThrow(
229
+ /unknown provider id 'dropbox'/,
230
+ );
231
+ });
232
+
233
+ it("setCredentials rejects null creds", () => {
234
+ expect(() => setCredentials("webdav", null)).toThrow(/must be an object/);
235
+ });
236
+
237
+ it("getCredentials rejects unknown provider", () => {
238
+ expect(() => getCredentials("dropbox")).toThrow(/unknown provider id/);
239
+ });
240
+ });
241
+
242
+ describe("sync-credentials · corruption recovery", () => {
243
+ it("wrong-length key file → helpful error", () => {
244
+ setCredentials("webdav", { url: "https://x", password: "p" });
245
+ // Corrupt: truncate key file to 16 bytes
246
+ fs.writeFileSync(_keyPath(), Buffer.alloc(16));
247
+ expect(() => getCredentials("webdav")).toThrow(/wrong length/);
248
+ });
249
+
250
+ it("corrupt vault → helpful error", () => {
251
+ setCredentials("webdav", { url: "https://x", password: "p" });
252
+ // Write garbage that's at least the min size (12 + 16 + 1 = 29 bytes)
253
+ fs.writeFileSync(
254
+ _vaultPath(),
255
+ Buffer.from("garbage-data-that-is-not-valid-aes-blob-content"),
256
+ );
257
+ expect(() => getCredentials("webdav")).toThrow(/decrypt failed/);
258
+ });
259
+
260
+ it("truncated vault (smaller than iv+tag header) → helpful error", () => {
261
+ setCredentials("webdav", { url: "https://x", password: "p" });
262
+ fs.writeFileSync(_vaultPath(), Buffer.alloc(10));
263
+ expect(() => getCredentials("webdav")).toThrow(/too small or invalid/);
264
+ });
265
+ });