chainlesschain 0.162.11 → 0.162.13

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 (166) hide show
  1. package/README.md +30 -25
  2. package/package.json +4 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-ebtJGjAG.js → AIOps-Bq_zxhCr.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-CypkRN-G.js → ActionButton-CaevDm9t.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-B2JMlIng.js → Analytics-B0gOmwPw.js} +1 -1
  7. package/src/assets/web-panel/assets/{AppLayout-B8QQ4pk7.js → AppLayout-DWhZiV0Q.js} +2 -2
  8. package/src/assets/web-panel/assets/{Audit-BoYaAyFa.js → Audit-ZuZJBCxo.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BfackGZ5.js → Backup-CX7jhH5l.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C06FUpDz.js → BaseInput-CVLx7HVq.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-BWxMkBYZ.js → Chat-C6yL5tRD.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-XJMvS3PV.js → Checkbox-BePhbqVq.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-CzR462RK.js → Codegen-B5E4x1Lm.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-BQHpLNCA.js → Col-CWhNU6A7.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-BWRRbJYd.js → Community-mSEAuJhp.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-BunoKIy9.js → Compact-DSudHzX3.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compliance-CtJfZctm.js → Compliance-CxJLYjyn.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-ER5-_bod.js → Cowork-C-trppQj.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cron-C80jYBw1.js → Cron-_Ij4v5fY.js} +1 -1
  20. package/src/assets/web-panel/assets/{Crosschain-fEMlCNsL.js → Crosschain-eLHXzp5T.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-BZpctKmU.js → DID-BP04AUUB.js} +1 -1
  22. package/src/assets/web-panel/assets/{Dashboard-RQhZmLi4.js → Dashboard-CzEeQv62.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-CrpwS84l.js → Dropdown-C_SGOB22.js} +1 -1
  24. package/src/assets/web-panel/assets/{Federation-BEQyZtdR.js → Federation-BgaP4BOv.js} +1 -1
  25. package/src/assets/web-panel/assets/{FormItemContext-DCwvl6Vh.js → FormItemContext-BxGLLt9r.js} +1 -1
  26. package/src/assets/web-panel/assets/{Git-6FihOxMK.js → Git-Bt_uM_Gw.js} +2 -2
  27. package/src/assets/web-panel/assets/{Governance-DlBLHSlJ.js → Governance-N2-0RG_o.js} +1 -1
  28. package/src/assets/web-panel/assets/{Inference-DdSokzV0.js → Inference-eS3g-CzP.js} +1 -1
  29. package/src/assets/web-panel/assets/{KnowledgeGraph-bg8GBHMr.js → KnowledgeGraph-CUuvNVah.js} +1 -1
  30. package/src/assets/web-panel/assets/{Logs-DdFYdLQ-.js → Logs-wPbwEt2r.js} +1 -1
  31. package/src/assets/web-panel/assets/{Marketplace-DjnlAeYF.js → Marketplace-DH91kTwo.js} +1 -1
  32. package/src/assets/web-panel/assets/{McpTools-Czs41YUh.js → McpTools-D-GyyBrI.js} +1 -1
  33. package/src/assets/web-panel/assets/{Memory-CX0b3c8D.js → Memory-BOtUy-tw.js} +1 -1
  34. package/src/assets/web-panel/assets/{MobileBridge-BoFGb9Mm.js → MobileBridge-DIP__XQd.js} +1 -1
  35. package/src/assets/web-panel/assets/{MobileProjects-B8qQ9H-0.js → MobileProjects-1nqr1UsU.js} +1 -1
  36. package/src/assets/web-panel/assets/{Mtc-CRF1NLae.js → Mtc-CCE0x7h2.js} +1 -1
  37. package/src/assets/web-panel/assets/{MtcAudit-CdCm70cJ.js → MtcAudit-BBkz0XUO.js} +1 -1
  38. package/src/assets/web-panel/assets/{Multisig-yoZlpq2Y.js → Multisig-CIKSJvTY.js} +2 -2
  39. package/src/assets/web-panel/assets/{NLProgramming-hFCgqDxJ.js → NLProgramming-BDkgeFcq.js} +1 -1
  40. package/src/assets/web-panel/assets/{Notes-DC8pnxs-.js → Notes-ONiUxfN1.js} +1 -1
  41. package/src/assets/web-panel/assets/{NotificationSettings-DDVg5Nc8.js → NotificationSettings-CGcyKEIe.js} +1 -1
  42. package/src/assets/web-panel/assets/{Organization-0YCtAFMS.js → Organization-BZtMYBJt.js} +4 -4
  43. package/src/assets/web-panel/assets/{Overflow-DhPLoAdz.js → Overflow-C4LfFZAI.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-BWpuJhkD.js → P2P-D71Cpk-m.js} +1 -1
  46. package/src/assets/web-panel/assets/{Permissions-B4IrizO9.js → Permissions-DRGYF29v.js} +1 -1
  47. package/src/assets/web-panel/assets/PersonalDataHub-CfRYoIua.js +1 -0
  48. package/src/assets/web-panel/assets/PersonalDataHub-Dvaa8niQ.css +1 -0
  49. package/src/assets/web-panel/assets/{Pipeline-M65jR6sq.js → Pipeline-BOyp0_Qo.js} +1 -1
  50. package/src/assets/web-panel/assets/{Privacy-BeO8zLup.js → Privacy-a_AcphvF.js} +1 -1
  51. package/src/assets/web-panel/assets/{ProjectInit-Ck_ZjrVZ.js → ProjectInit-CFu1grYt.js} +1 -1
  52. package/src/assets/web-panel/assets/{ProjectSettings-ijn-97s0.js → ProjectSettings-p54Eivhh.js} +1 -1
  53. package/src/assets/web-panel/assets/{Projects-BsNBemeh.js → Projects-DkB88XAu.js} +1 -1
  54. package/src/assets/web-panel/assets/{Providers-CI7UxKVO.js → Providers-EK7f8DEd.js} +1 -1
  55. package/src/assets/web-panel/assets/{QuickAsk-dE2M1KOB.js → QuickAsk-CpO0w3iP.js} +1 -1
  56. package/src/assets/web-panel/assets/{Recommend-B30EgbKS.js → Recommend-BUJVQdv9.js} +1 -1
  57. package/src/assets/web-panel/assets/{Reputation-CV6n7wMx.js → Reputation-kU2fOuZt.js} +1 -1
  58. package/src/assets/web-panel/assets/{Row-DSaoTjlN.js → Row-oGWRbk6g.js} +1 -1
  59. package/src/assets/web-panel/assets/{RssFeed-_NgBmHaC.js → RssFeed-saC_46Yo.js} +1 -1
  60. package/src/assets/web-panel/assets/{Search-B341ooTV.js → Search-CjtRqFUu.js} +1 -1
  61. package/src/assets/web-panel/assets/{Security-CqCQD8hf.js → Security-BX0NBVfQ.js} +1 -1
  62. package/src/assets/web-panel/assets/{Services-Cju_95rB.js → Services-Bgxsnei_.js} +1 -1
  63. package/src/assets/web-panel/assets/{Skeleton-kh_uW22l.js → Skeleton-CwBb3k2a.js} +1 -1
  64. package/src/assets/web-panel/assets/{Skills-BVRPgciI.js → Skills-CZCMYH6Z.js} +1 -1
  65. package/src/assets/web-panel/assets/{Sla-y-vKFYkI.js → Sla-Djy1uHnZ.js} +1 -1
  66. package/src/assets/web-panel/assets/{SpeechSettings-BXJm9zyo.js → SpeechSettings-CGUI_Uyh.js} +1 -1
  67. package/src/assets/web-panel/assets/{SyncSettings-BmHZR-Kv.js → SyncSettings-DK2CKHRD.js} +1 -1
  68. package/src/assets/web-panel/assets/{Tasks-C7zmYW9f.js → Tasks-BKiOzeIO.js} +1 -1
  69. package/src/assets/web-panel/assets/{Templates-DVEG7FdA.js → Templates-CnQpleXj.js} +1 -1
  70. package/src/assets/web-panel/assets/{Tenant-CpvjzPCo.js → Tenant-DwKz0cjm.js} +1 -1
  71. package/src/assets/web-panel/assets/{Terminal-D_Wpp2iE.js → Terminal-A7t_wsR8.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tokens-QBrjdNqi.js → Tokens-BqYY9l44.js} +1 -1
  73. package/src/assets/web-panel/assets/{Trigger-BhR_VEvQ.js → Trigger-BI4bXFmi.js} +1 -1
  74. package/src/assets/web-panel/assets/{Trust-C0xhM2lC.js → Trust-yMynKTRG.js} +1 -1
  75. package/src/assets/web-panel/assets/{UkeySign-BnyP-W3-.js → UkeySign-Br4IScM6.js} +1 -1
  76. package/src/assets/web-panel/assets/{VideoEditing-C5Y8MyEK.js → VideoEditing-CWcThGsP.js} +1 -1
  77. package/src/assets/web-panel/assets/{Wallet-DzCPCQNF.js → Wallet-CZcAtjxj.js} +1 -1
  78. package/src/assets/web-panel/assets/{WebAuthn-6X5bLtHU.js → WebAuthn-BnTZFMA0.js} +3 -3
  79. package/src/assets/web-panel/assets/{WorkflowEditor-ekS27G9f.js → WorkflowEditor-N7gGz3_n.js} +1 -1
  80. package/src/assets/web-panel/assets/{chat-BikodUwh.js → chat-D175ZIO0.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-8yIg5K7E.js → colors-LKhZyttv.js} +1 -1
  83. package/src/assets/web-panel/assets/{compact-item-MLWo5-GY.js → compact-item-CsJSebxT.js} +1 -1
  84. package/src/assets/web-panel/assets/{createContext-nir7ccDv.js → createContext-BJ_CPYFC.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-DxUIHW2P.js → hasIn-CzD3IqH8.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-vVrIg9Jk.js → index-6qPbrYF7.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-CCGf6IJj.js → index-B7UYymse.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DSjWvxVr.js → index-BDSZDDb2.js} +4 -4
  91. package/src/assets/web-panel/assets/{index-BURKtxBq.js → index-BEDFHKO3.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-q3Lr2UzW.js → index-BMvdoiFr.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BgQtoOHc.js → index-BNVLVzN5.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Bn5VWKW1.js → index-BSNibAqz.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-4SFekeAy.js → index-BV-__mlC.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CfeuuE7v.js → index-BXH9ujMW.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-h05fIj9Q.js → index-BZluCuTH.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-9Y0IyfeM.js → index-BhiZDGg7.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BkMtxzcM.js → index-BjOrt4vw.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DOIryna2.js → index-BmJdof_c.js} +2 -2
  101. package/src/assets/web-panel/assets/{index-TB5vrA0Z.js → index-BsirlkJ0.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BgHPrMXP.js → index-BvF2tC6C.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BSIaRmzU.js → index-C2HBKw07.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CkSN2Ki_.js → index-CKjBAdm0.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DLiexKJ2.js → index-CRGNuUIM.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CeX-HLIi.js → index-CTIkCKav.js} +1 -1
  107. package/src/assets/web-panel/assets/index-CY1mQA2I.js +1 -0
  108. package/src/assets/web-panel/assets/{index-78olN7S9.js → index-CyHdYUeZ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BcyG-9vV.js → index-D401L3yx.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C_W1kVtY.js → index-D5IZCkZG.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Bpa9senE.js → index-D7ZcBI5s.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-dgZZAsxo.js → index-D8kltMTW.js} +1 -1
  113. package/src/assets/web-panel/assets/index-D9nXHfUB.js +1 -0
  114. package/src/assets/web-panel/assets/{index-DkIon-Gv.js → index-DJVkBmSc.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C-UB9bYd.js → index-DM3uBEWD.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Dx5xZmzt.js → index-DOO73rHE.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DVLJ1iGu.js → index-DXp1jVsK.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-S8mYImvf.js → index-Dd7dICwB.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-SUYLhwZI.js → index-Dm-3kvtD.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-C_Xi08tu.js → index-DnPt5OdD.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-hu-wjfWv.js → index-E7t1hAnk.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Cp-YnzHN.js → index-JTX9A7w0.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-8qrwsaKy.js → index-Kn-Of5ew.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Dl-O2OkQ.js → index-R1cFADfk.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-yfNusVbo.js → index-RIO4JKMP.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DDmc4cig.js → index-uTEVWPYA.js} +1 -1
  127. package/src/assets/web-panel/assets/{initDefaultProps-xUjF_bq0.js → initDefaultProps-CBW0okek.js} +1 -1
  128. package/src/assets/web-panel/assets/{motion-B019-Q6h.js → motion-DGAffQ0Z.js} +1 -1
  129. package/src/assets/web-panel/assets/{move-D2XYj_gA.js → move-DFJ0-5IW.js} +1 -1
  130. package/src/assets/web-panel/assets/{omit-AIzzlguv.js → omit-AvrDghg1.js} +1 -1
  131. package/src/assets/web-panel/assets/{pickAttrs-CRkEQaLs.js → pickAttrs-D7csw9i1.js} +1 -1
  132. package/src/assets/web-panel/assets/{placementArrow-s4kAStH6.js → placementArrow-hZ6Lg6kG.js} +1 -1
  133. package/src/assets/web-panel/assets/{responsiveObserve-BCsWrTkb.js → responsiveObserve-DLLx5VvS.js} +1 -1
  134. package/src/assets/web-panel/assets/{slide-B_Hggtvv.js → slide-BaRIT3ev.js} +1 -1
  135. package/src/assets/web-panel/assets/{statusUtils-DnNf15VW.js → statusUtils-Cdjyuhrz.js} +1 -1
  136. package/src/assets/web-panel/assets/{styleChecker-CSQdy9SQ.js → styleChecker-CbrNybTt.js} +1 -1
  137. package/src/assets/web-panel/assets/useFlexGapSupport-B8gAhiRC.js +1 -0
  138. package/src/assets/web-panel/assets/{useFs-D78PlgeG.js → useFs-BZPy4ICP.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-BTMmpsWu.js → vnode-6Y0NDMVv.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-CwOTbvKc.js → zoom-DTbMGsSH.js} +1 -1
  145. package/src/assets/web-panel/index.html +3 -3
  146. package/src/commands/__tests__/hub-aichat.test.js +277 -0
  147. package/src/commands/__tests__/hub-wechat.test.js +243 -0
  148. package/src/commands/hub.js +881 -0
  149. package/src/commands/sync-providers.js +436 -0
  150. package/src/gateways/ws/personal-data-hub-protocol.js +68 -0
  151. package/src/index.js +6 -0
  152. package/src/lib/__tests__/personal-data-hub-aichat-wizard.test.js +209 -0
  153. package/src/lib/__tests__/sync-credentials.test.js +265 -0
  154. package/src/lib/__tests__/sync-engine-cli.test.js +293 -0
  155. package/src/lib/personal-data-hub-aichat-wizard.js +242 -0
  156. package/src/lib/personal-data-hub-wiring.js +189 -0
  157. package/src/lib/sync-cli-db.js +194 -0
  158. package/src/lib/sync-credentials.js +225 -0
  159. package/src/lib/sync-engine-cli.js +406 -0
  160. package/src/lib/sync-oss-client.js +273 -0
  161. package/src/lib/sync-webdav-client.js +194 -0
  162. package/src/assets/web-panel/assets/PersonalDataHub-BK7I0Rsb.css +0 -1
  163. package/src/assets/web-panel/assets/PersonalDataHub-ZbziiUr6.js +0 -1
  164. package/src/assets/web-panel/assets/index-BeA3spHc.js +0 -1
  165. package/src/assets/web-panel/assets/index-DoLRjAoc.js +0 -1
  166. package/src/assets/web-panel/assets/useFlexGapSupport-Bt-T27Pf.js +0 -1
@@ -30,6 +30,8 @@ import { mkdirSync } from "node:fs";
30
30
  // won't let us name-import a CJS module unless it ships a separate ESM
31
31
  // shim, which we don't).
32
32
  import hub from "@chainlesschain/personal-data-hub";
33
+ import wechatAdapterModule from "@chainlesschain/personal-data-hub/adapters/wechat";
34
+ const { bootstrapWechatAdapter, probeWeChatEnv } = wechatAdapterModule;
33
35
  const {
34
36
  LocalVault,
35
37
  AdapterRegistry,
@@ -51,6 +53,18 @@ const {
51
53
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
52
54
  import { getElectronUserDataDir } from "./paths.js";
53
55
 
56
+ import {
57
+ getAIChatWizard,
58
+ createAccountsStore as createAIChatAccountsStore,
59
+ createVendorAdapterBridge as createAIChatVendorAdapterBridge,
60
+ } from "./personal-data-hub-aichat-wizard.js";
61
+
62
+ async function loadAIChatHealthChecker() {
63
+ const mod =
64
+ await import("@chainlesschain/personal-data-hub/adapters/ai-chat-history/health-checker");
65
+ return (mod.default || mod).createAIChatHealthChecker;
66
+ }
67
+
54
68
  // ─── Lazy ESM imports of cli KG / BM25 ───────────────────────────────────
55
69
 
56
70
  let _kgMod = null;
@@ -211,6 +225,24 @@ async function initHub() {
211
225
  }
212
226
  }
213
227
 
228
+ // Phase 10.3.5 — AIChat HealthChecker (mirror of desktop wiring). cc ui
229
+ // typically only runs while the user has the web-shell open, but the
230
+ // periodic loop still validates persisted cookies so list-aichat-accounts
231
+ // can surface lastHealth reliably.
232
+ const aichatAccountsStore = createAIChatAccountsStore({ hubDir });
233
+ const aichatVendorAdapter = createAIChatVendorAdapterBridge();
234
+ const createAIChatHealthChecker = await loadAIChatHealthChecker();
235
+ const aichatHealthChecker = createAIChatHealthChecker({
236
+ accountsStore: aichatAccountsStore,
237
+ vendorAdapter: aichatVendorAdapter,
238
+ });
239
+ try {
240
+ aichatHealthChecker.start();
241
+ } catch (_err) {
242
+ // Continue boot even if checker fails to schedule
243
+ }
244
+ const aichatWizard = getAIChatWizard({ hubDir });
245
+
214
246
  return {
215
247
  vault,
216
248
  registry,
@@ -223,11 +255,50 @@ async function initHub() {
223
255
  emailAccountsPath,
224
256
  alipayAccountsPath,
225
257
  entityResolver,
258
+ aichatAccountsStore,
259
+ aichatWizard,
260
+ aichatHealthChecker,
226
261
  analysisSkillNames: ANALYSIS_SKILL_NAMES,
227
262
  async runSkill(name, options = {}) {
228
263
  return await runAnalysisSkill({ vault, llm }, name, options);
229
264
  },
230
265
  bm25: _bm25,
266
+
267
+ /** Phase 10.3.5 — see desktop wiring for full doc. */
268
+ async listAIChatAccounts() {
269
+ const entries = await aichatAccountsStore.list();
270
+ return (entries || []).map((e) => ({
271
+ vendor: e.vendor,
272
+ displayName: e.displayName || e.vendor,
273
+ registeredAt: e.registeredAt || null,
274
+ userId: e.userId || null,
275
+ lastSyncAt: e.lastSyncAt || null,
276
+ lastHealth: e.lastHealth || null,
277
+ cookieSpecVersion: e.cookieSpecVersion || null,
278
+ cookieNames: Object.keys(e.cookies || {}),
279
+ }));
280
+ },
281
+
282
+ async unregisterAIChatVendor(vendor) {
283
+ if (!vendor || typeof vendor !== "string") {
284
+ return { ok: false, reason: "VENDOR_REQUIRED" };
285
+ }
286
+ const before = await aichatAccountsStore.get(vendor);
287
+ if (!before) {
288
+ return { ok: false, reason: "NOT_REGISTERED", vendor };
289
+ }
290
+ await aichatAccountsStore.delete(vendor);
291
+ try {
292
+ await aichatWizard.rotateLoginPartition({ vendor });
293
+ } catch (_err) {
294
+ // best-effort
295
+ }
296
+ return { ok: true, vendor };
297
+ },
298
+
299
+ async runAIChatHealthCheckOnce() {
300
+ return await aichatHealthChecker.runOnce();
301
+ },
231
302
  registerMockAdapter(opts = {}) {
232
303
  if (registry.has(opts.name || "mock"))
233
304
  return registry.get(opts.name || "mock");
@@ -358,9 +429,122 @@ async function initHub() {
358
429
  zipPassword,
359
430
  });
360
431
  },
432
+
433
+ // ─── Phase 12.6.8 — WeChat env-probe + register / unregister / list ──
434
+ //
435
+ // cc ui mirror of the desktop wiring. Same JSON file layout
436
+ // (wechat-accounts.json under hubDir, mode 0o600) so the two shells
437
+ // share registrations when run side-by-side on the same machine.
438
+
439
+ async probeWechatEnv(opts = {}) {
440
+ return await probeWeChatEnv({ exec: opts.exec });
441
+ },
442
+
443
+ async registerWechatAdapter(opts = {}) {
444
+ if (!opts || !opts.account || !opts.account.uin) {
445
+ return { ok: false, reason: "UIN_REQUIRED", message: "opts.account.uin required" };
446
+ }
447
+ let r;
448
+ try {
449
+ r = await bootstrapWechatAdapter({
450
+ account: opts.account,
451
+ dbPath: opts.dbPath || null,
452
+ wechatDataPath: opts.wechatDataPath || null,
453
+ fridaOpts: opts.fridaOpts || null,
454
+ keyProviderOverride: opts.keyProviderOverride || null,
455
+ exec: opts.exec,
456
+ _probe: opts._probe,
457
+ });
458
+ } catch (err) {
459
+ return {
460
+ ok: false,
461
+ reason: "BOOTSTRAP_THREW",
462
+ message: err && err.message ? err.message : String(err),
463
+ };
464
+ }
465
+ if (!r.ok) return r;
466
+
467
+ if (registry.has(r.adapter.name)) registry.unregister(r.adapter.name);
468
+ registry.register(r.adapter);
469
+
470
+ const wechatAccountsPath = join(hubDir, "wechat-accounts.json");
471
+ const accounts = loadWechatAccounts(wechatAccountsPath);
472
+ const next = accounts.filter(
473
+ (c) => !(c.account && c.account.uin === opts.account.uin),
474
+ );
475
+ next.push({
476
+ account: { uin: opts.account.uin },
477
+ dbPath: opts.dbPath || null,
478
+ wechatDataPath: opts.wechatDataPath || null,
479
+ chosenKeyProvider: r.keyProvider && r.keyProvider.name ? r.keyProvider.name : null,
480
+ registeredAt: Date.now(),
481
+ lastSyncAt: null,
482
+ });
483
+ saveWechatAccounts(wechatAccountsPath, next);
484
+
485
+ return {
486
+ ok: true,
487
+ name: r.adapter.name,
488
+ version: r.adapter.version,
489
+ capabilities: r.adapter.capabilities,
490
+ sensitivity: r.adapter.dataDisclosure.sensitivity,
491
+ chosenKeyProvider: r.keyProvider && r.keyProvider.name,
492
+ probe: r.probe,
493
+ registeredAt: next[next.length - 1].registeredAt,
494
+ };
495
+ },
496
+
497
+ async unregisterWechatAdapter(uin) {
498
+ if (!uin || typeof uin !== "string") {
499
+ return { ok: false, reason: "UIN_REQUIRED" };
500
+ }
501
+ const wechatAccountsPath = join(hubDir, "wechat-accounts.json");
502
+ const accounts = loadWechatAccounts(wechatAccountsPath);
503
+ const target = accounts.find(
504
+ (c) => c.account && c.account.uin === uin,
505
+ );
506
+ const next = accounts.filter(
507
+ (c) => !(c.account && c.account.uin === uin),
508
+ );
509
+ saveWechatAccounts(wechatAccountsPath, next);
510
+ if (target && registry.has("wechat")) registry.unregister("wechat");
511
+ return { ok: true, removed: !!target, uin };
512
+ },
513
+
514
+ listWechatAccounts() {
515
+ const wechatAccountsPath = join(hubDir, "wechat-accounts.json");
516
+ return loadWechatAccounts(wechatAccountsPath).map((row) => ({
517
+ uin: row.account ? row.account.uin : null,
518
+ dbPath: row.dbPath || null,
519
+ hasWechatDataPath: !!row.wechatDataPath,
520
+ chosenKeyProvider: row.chosenKeyProvider || null,
521
+ registeredAt: row.registeredAt || null,
522
+ lastSyncAt: row.lastSyncAt || null,
523
+ }));
524
+ },
361
525
  };
362
526
  }
363
527
 
528
+ // ─── Phase 12.6.8 — WeChat account persistence helpers (cli mirror) ──────
529
+
530
+ function loadWechatAccounts(filePath) {
531
+ try {
532
+ if (!existsSync(filePath)) return [];
533
+ const raw = readFileSync(filePath, "utf-8");
534
+ const parsed = JSON.parse(raw);
535
+ return Array.isArray(parsed) ? parsed : [];
536
+ } catch (_err) {
537
+ return [];
538
+ }
539
+ }
540
+
541
+ function saveWechatAccounts(filePath, accounts) {
542
+ writeFileSync(filePath, JSON.stringify(accounts, null, 2), {
543
+ encoding: "utf-8",
544
+ mode: 0o600,
545
+ });
546
+ }
547
+
364
548
  // ─── Email account persistence (Phase 5.6) ───────────────────────────────
365
549
 
366
550
  function loadEmailAccounts(filePath) {
@@ -418,6 +602,11 @@ export async function getHub() {
418
602
  }
419
603
 
420
604
  export function close() {
605
+ if (_hub && _hub.aichatHealthChecker) {
606
+ try {
607
+ _hub.aichatHealthChecker.stop();
608
+ } catch (_e) {}
609
+ }
421
610
  if (_hub && _hub.vault) {
422
611
  try {
423
612
  _hub.vault.close();
@@ -0,0 +1,194 @@
1
+ /**
2
+ * CLI sync vault — better-sqlite3 wrapper exposing dbManager.run/all/get
3
+ * interface matching desktop's engine deps signature.
4
+ *
5
+ * Phase 3c follow-up Phase 2: cc sync run requires SQLite store for cursors,
6
+ * tombstones, and knowledge_items. CLI vault lives at
7
+ * `~/.chainlesschain/cli-vault.db` and is plain SQLite (no SQLCipher) —
8
+ * encryption is at the OS file-permission layer; users wanting strong
9
+ * crypto should use desktop with hardware U-Key.
10
+ *
11
+ * Schema mirrors desktop `database-schema.js` for the 3 sync-relevant
12
+ * tables (auto-initialized on first open):
13
+ * - knowledge_items user notes (source of truth for sync)
14
+ * - sync_external_provider_cursor per-provider state
15
+ * - sync_external_tombstones per-provider delete queue
16
+ * - trg_sync_ext_tombstone_on_delete trigger (auto-fan tombstone on delete)
17
+ */
18
+
19
+ "use strict";
20
+
21
+ import fs from "node:fs";
22
+ import path from "node:path";
23
+ import os from "node:os";
24
+
25
+ function _ccDir() {
26
+ return (
27
+ process.env.CHAINLESSCHAIN_HOME ||
28
+ path.join(os.homedir(), ".chainlesschain")
29
+ );
30
+ }
31
+
32
+ function _vaultPath() {
33
+ return path.join(_ccDir(), "cli-vault.db");
34
+ }
35
+
36
+ const CLI_VAULT_SCHEMA = `
37
+ CREATE TABLE IF NOT EXISTS knowledge_items (
38
+ id TEXT PRIMARY KEY,
39
+ title TEXT NOT NULL,
40
+ type TEXT NOT NULL DEFAULT 'note',
41
+ content TEXT,
42
+ tags TEXT,
43
+ created_at INTEGER NOT NULL,
44
+ updated_at INTEGER NOT NULL
45
+ );
46
+
47
+ CREATE TABLE IF NOT EXISTS sync_external_provider_cursor (
48
+ provider_id TEXT NOT NULL,
49
+ account_key TEXT NOT NULL DEFAULT '',
50
+ last_sync_at INTEGER NOT NULL DEFAULT 0,
51
+ last_item_id TEXT,
52
+ remote_etag_map TEXT NOT NULL DEFAULT '{}',
53
+ remote_filename_map TEXT NOT NULL DEFAULT '{}',
54
+ last_run_status TEXT,
55
+ last_run_error TEXT,
56
+ last_run_duration_ms INTEGER,
57
+ items_pushed INTEGER NOT NULL DEFAULT 0,
58
+ items_skipped INTEGER NOT NULL DEFAULT 0,
59
+ items_deleted INTEGER NOT NULL DEFAULT 0,
60
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),
61
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000),
62
+ PRIMARY KEY (provider_id, account_key)
63
+ );
64
+
65
+ CREATE TABLE IF NOT EXISTS sync_external_tombstones (
66
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
67
+ provider_id TEXT NOT NULL,
68
+ account_key TEXT NOT NULL DEFAULT '',
69
+ item_id TEXT NOT NULL,
70
+ resource_type TEXT,
71
+ deleted_at INTEGER NOT NULL,
72
+ retry_count INTEGER NOT NULL DEFAULT 0,
73
+ last_error TEXT,
74
+ UNIQUE(provider_id, account_key, item_id)
75
+ );
76
+
77
+ CREATE TRIGGER IF NOT EXISTS trg_sync_ext_tombstone_on_delete
78
+ AFTER DELETE ON knowledge_items
79
+ FOR EACH ROW
80
+ BEGIN
81
+ INSERT OR IGNORE INTO sync_external_tombstones
82
+ (provider_id, account_key, item_id, resource_type, deleted_at)
83
+ SELECT c.provider_id, c.account_key, OLD.id, 'KNOWLEDGE_ITEM',
84
+ (strftime('%s','now') * 1000)
85
+ FROM sync_external_provider_cursor c;
86
+ END;
87
+ `;
88
+
89
+ /**
90
+ * Wraps better-sqlite3 Database with the {run, all, get} surface that
91
+ * the desktop sync engine's deps.dbManager expects.
92
+ */
93
+ class CliVaultDbManager {
94
+ constructor(db) {
95
+ this.db = db;
96
+ }
97
+
98
+ /** Match desktop dbManager.run(sql, params=[]) */
99
+ run(sql, params = []) {
100
+ this.db.prepare(sql).run(...params);
101
+ }
102
+
103
+ /** Match desktop dbManager.get(sql, params=[]) → row or undefined */
104
+ get(sql, params = []) {
105
+ const row = this.db.prepare(sql).get(...params);
106
+ return row ?? undefined;
107
+ }
108
+
109
+ /** Match desktop dbManager.all(sql, params=[]) → rows */
110
+ all(sql, params = []) {
111
+ return this.db.prepare(sql).all(...params);
112
+ }
113
+
114
+ close() {
115
+ if (this.db && this.db.open) this.db.close();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Open (or create) the CLI vault. Auto-initializes schema on first open.
121
+ *
122
+ * @returns {Promise<{dbManager: CliVaultDbManager, vaultPath: string}>}
123
+ */
124
+ async function openCliVault() {
125
+ const dir = _ccDir();
126
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
127
+
128
+ // Lazy-import better-sqlite3 — heavy native dep, defer until run-time
129
+ // (so the CLI doesn't even pay the require cost for non-sync commands).
130
+ let Database;
131
+ try {
132
+ Database = (await import("better-sqlite3")).default;
133
+ } catch (err) {
134
+ const e = new Error(
135
+ "CLI vault requires better-sqlite3 npm package. " +
136
+ "Run `npm install -g chainlesschain` to fetch the prebuilt binary, " +
137
+ "or `cd packages/cli && npm install` for the dev workspace. " +
138
+ "Underlying error: " +
139
+ (err?.message || String(err)),
140
+ );
141
+ e.code = "BETTER_SQLITE3_MISSING";
142
+ throw e;
143
+ }
144
+
145
+ const vp = _vaultPath();
146
+ const db = new Database(vp);
147
+ db.exec(CLI_VAULT_SCHEMA);
148
+ return { dbManager: new CliVaultDbManager(db), vaultPath: vp };
149
+ }
150
+
151
+ let _ccDirOverride = null;
152
+ function _setCcDirForTest(dir) {
153
+ _ccDirOverride = dir;
154
+ }
155
+ function _resetCcDirForTest() {
156
+ _ccDirOverride = null;
157
+ }
158
+
159
+ // Re-wrap _ccDir/_vaultPath to honor override (test seam)
160
+ const _originalCcDir = _ccDir;
161
+ function _ccDirEffective() {
162
+ return _ccDirOverride ?? _originalCcDir();
163
+ }
164
+ function _vaultPathEffective() {
165
+ return path.join(_ccDirEffective(), "cli-vault.db");
166
+ }
167
+
168
+ async function openCliVaultT() {
169
+ const dir = _ccDirEffective();
170
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
171
+ let Database;
172
+ try {
173
+ Database = (await import("better-sqlite3")).default;
174
+ } catch (err) {
175
+ const e = new Error(
176
+ "CLI vault requires better-sqlite3 npm package: " + (err?.message || err),
177
+ );
178
+ e.code = "BETTER_SQLITE3_MISSING";
179
+ throw e;
180
+ }
181
+ const vp = _vaultPathEffective();
182
+ const db = new Database(vp);
183
+ db.exec(CLI_VAULT_SCHEMA);
184
+ return { dbManager: new CliVaultDbManager(db), vaultPath: vp };
185
+ }
186
+
187
+ export {
188
+ openCliVaultT as openCliVault,
189
+ CliVaultDbManager,
190
+ CLI_VAULT_SCHEMA,
191
+ _setCcDirForTest,
192
+ _resetCcDirForTest,
193
+ _vaultPathEffective as _vaultPath,
194
+ };
@@ -0,0 +1,225 @@
1
+ /**
2
+ * CLI sync provider credentials store — Phase 3c follow-up.
3
+ *
4
+ * Electron 的 `safeStorage` 在 CLI 没法用,所以这里搭一个 file-based equivalent:
5
+ *
6
+ * - Master key 自动生成 (32 字节 random) 落 `~/.chainlesschain/sync-credentials.key`
7
+ * 文件 mode 0600 (Unix) / NTFS ACL (Win — fs.chmodSync 容错)
8
+ * - 凭据 JSON 编 AES-256-GCM (iv 12B + auth tag 16B)
9
+ * 落 `~/.chainlesschain/sync-credentials.enc`
10
+ * - SENSITIVE_FIELDS 镜像 desktop secure-config-storage.js 用于 sanitize
11
+ *
12
+ * 与 desktop sync-credentials.js 同 surface:
13
+ * getCredentials / setCredentials / clearCredentials / hasCredentials /
14
+ * getCredentialsSanitized / ALLOWED_PROVIDER_IDS
15
+ *
16
+ * 威胁模型:root/admin 能读 master key 文件即破,是 CLI 工具合理 baseline
17
+ * (同 git ~/.netrc / ~/.aws/credentials)。OS keyring 强加密留 v0.2。
18
+ */
19
+
20
+ "use strict";
21
+
22
+ import fs from "node:fs";
23
+ import path from "node:path";
24
+ import os from "node:os";
25
+ import crypto from "node:crypto";
26
+
27
+ const SENSITIVE_FIELDS = ["sync.webdav.password", "sync.oss.secretAccessKey"];
28
+ const ALLOWED_PROVIDER_IDS = ["webdav", "oss"];
29
+ const MASK = "********";
30
+ const AES_ALG = "aes-256-gcm";
31
+ const IV_LEN = 12;
32
+ const TAG_LEN = 16;
33
+ const KEY_LEN = 32;
34
+
35
+ let _ccDirOverride = null;
36
+
37
+ function _ccDir() {
38
+ if (_ccDirOverride) return _ccDirOverride;
39
+ return (
40
+ process.env.CHAINLESSCHAIN_HOME ||
41
+ path.join(os.homedir(), ".chainlesschain")
42
+ );
43
+ }
44
+
45
+ function _keyPath() {
46
+ return path.join(_ccDir(), "sync-credentials.key");
47
+ }
48
+
49
+ function _vaultPath() {
50
+ return path.join(_ccDir(), "sync-credentials.enc");
51
+ }
52
+
53
+ function _ensureDir() {
54
+ const dir = _ccDir();
55
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
56
+ }
57
+
58
+ function _loadOrCreateMasterKey() {
59
+ _ensureDir();
60
+ const kp = _keyPath();
61
+ if (fs.existsSync(kp)) {
62
+ const buf = fs.readFileSync(kp);
63
+ if (buf.length !== KEY_LEN) {
64
+ throw new Error(
65
+ `sync-credentials: master key file ${kp} has wrong length ` +
66
+ `(${buf.length} bytes, expected ${KEY_LEN}). Delete + regenerate manually if intentional.`,
67
+ );
68
+ }
69
+ return buf;
70
+ }
71
+ const key = crypto.randomBytes(KEY_LEN);
72
+ fs.writeFileSync(kp, key);
73
+ try {
74
+ fs.chmodSync(kp, 0o600);
75
+ } catch (_e) {
76
+ /* non-fatal: NTFS / older fs */
77
+ }
78
+ return key;
79
+ }
80
+
81
+ function _encryptBlob(plainJson) {
82
+ const key = _loadOrCreateMasterKey();
83
+ const iv = crypto.randomBytes(IV_LEN);
84
+ const cipher = crypto.createCipheriv(AES_ALG, key, iv);
85
+ const enc = Buffer.concat([
86
+ cipher.update(plainJson, "utf-8"),
87
+ cipher.final(),
88
+ ]);
89
+ const tag = cipher.getAuthTag();
90
+ // file layout: [iv (12)] [tag (16)] [ciphertext]
91
+ return Buffer.concat([iv, tag, enc]);
92
+ }
93
+
94
+ function _decryptBlob(buf) {
95
+ if (!Buffer.isBuffer(buf) || buf.length < IV_LEN + TAG_LEN + 1) {
96
+ throw new Error("sync-credentials: enc file too small or invalid");
97
+ }
98
+ const key = _loadOrCreateMasterKey();
99
+ const iv = buf.subarray(0, IV_LEN);
100
+ const tag = buf.subarray(IV_LEN, IV_LEN + TAG_LEN);
101
+ const enc = buf.subarray(IV_LEN + TAG_LEN);
102
+ const decipher = crypto.createDecipheriv(AES_ALG, key, iv);
103
+ decipher.setAuthTag(tag);
104
+ return Buffer.concat([decipher.update(enc), decipher.final()]).toString(
105
+ "utf-8",
106
+ );
107
+ }
108
+
109
+ function loadAll() {
110
+ const vp = _vaultPath();
111
+ if (!fs.existsSync(vp)) return {};
112
+ const buf = fs.readFileSync(vp);
113
+ try {
114
+ return JSON.parse(_decryptBlob(buf));
115
+ } catch (err) {
116
+ throw new Error(
117
+ `sync-credentials: decrypt failed (${err?.message}). ` +
118
+ `Vault may be corrupted or master key changed. ` +
119
+ `Remove ${vp} + reconfigure to recover.`,
120
+ );
121
+ }
122
+ }
123
+
124
+ function saveAll(all) {
125
+ _ensureDir();
126
+ fs.writeFileSync(_vaultPath(), _encryptBlob(JSON.stringify(all)));
127
+ return true;
128
+ }
129
+
130
+ function assertProviderId(providerId) {
131
+ if (!ALLOWED_PROVIDER_IDS.includes(providerId)) {
132
+ throw new Error(
133
+ `sync-credentials: unknown provider id '${providerId}' ` +
134
+ `(allowed: ${ALLOWED_PROVIDER_IDS.join(", ")})`,
135
+ );
136
+ }
137
+ }
138
+
139
+ function sanitize(all) {
140
+ if (!all || typeof all !== "object") return all;
141
+ const clone = JSON.parse(JSON.stringify(all));
142
+ for (const dotPath of SENSITIVE_FIELDS) {
143
+ const parts = dotPath.split(".");
144
+ let cur = clone;
145
+ for (let i = 0; i < parts.length - 1; i++) {
146
+ if (cur && typeof cur === "object" && parts[i] in cur) {
147
+ cur = cur[parts[i]];
148
+ } else {
149
+ cur = null;
150
+ break;
151
+ }
152
+ }
153
+ const last = parts[parts.length - 1];
154
+ if (
155
+ cur &&
156
+ typeof cur === "object" &&
157
+ cur[last] != null &&
158
+ cur[last] !== ""
159
+ ) {
160
+ cur[last] = MASK;
161
+ }
162
+ }
163
+ return clone;
164
+ }
165
+
166
+ function getCredentials(providerId) {
167
+ assertProviderId(providerId);
168
+ return loadAll()?.sync?.[providerId] ?? {};
169
+ }
170
+
171
+ function getCredentialsSanitized(providerId) {
172
+ assertProviderId(providerId);
173
+ return sanitize(loadAll())?.sync?.[providerId] ?? {};
174
+ }
175
+
176
+ function hasCredentials(providerId) {
177
+ return Object.values(getCredentials(providerId)).some(
178
+ (v) => v !== null && v !== undefined && v !== "",
179
+ );
180
+ }
181
+
182
+ function setCredentials(providerId, creds) {
183
+ assertProviderId(providerId);
184
+ if (!creds || typeof creds !== "object") {
185
+ throw new Error("sync-credentials: creds must be an object");
186
+ }
187
+ const all = loadAll();
188
+ if (!all.sync || typeof all.sync !== "object") all.sync = {};
189
+ all.sync[providerId] = { ...creds };
190
+ return saveAll(all);
191
+ }
192
+
193
+ function clearCredentials(providerId) {
194
+ assertProviderId(providerId);
195
+ const all = loadAll();
196
+ if (all?.sync?.[providerId]) {
197
+ delete all.sync[providerId];
198
+ return saveAll(all);
199
+ }
200
+ return true;
201
+ }
202
+
203
+ /** Test seam: override the resolved chainlesschain dir without env leak. */
204
+ function _setCcDirForTest(dir) {
205
+ _ccDirOverride = dir;
206
+ }
207
+
208
+ function _resetCcDirForTest() {
209
+ _ccDirOverride = null;
210
+ }
211
+
212
+ export {
213
+ ALLOWED_PROVIDER_IDS,
214
+ SENSITIVE_FIELDS,
215
+ MASK,
216
+ getCredentials,
217
+ getCredentialsSanitized,
218
+ hasCredentials,
219
+ setCredentials,
220
+ clearCredentials,
221
+ _setCcDirForTest,
222
+ _resetCcDirForTest,
223
+ _keyPath,
224
+ _vaultPath,
225
+ };