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,243 @@
1
+ /**
2
+ * cc hub wechat <verb> CLI command unit tests (Phase 12.6.9 CLI surface).
3
+ *
4
+ * The command handlers accept `_getHub` test seam so this suite never
5
+ * starts a real hub. We synthesize a tiny fake hub whose methods return
6
+ * canned responses matching the real ones (Phase 12.6.8 wiring methods).
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
10
+
11
+ import { _internal } from "../hub.js";
12
+
13
+ let logSpy, errSpy, exitSpy;
14
+
15
+ function fakeHub(overrides = {}) {
16
+ return {
17
+ probeWechatEnv: vi.fn(async () => ({
18
+ ok: true,
19
+ suggestedKeyProvider: "md5",
20
+ reasons: ["WeChat 7.0.22 — md5 path"],
21
+ device: { reachable: true, serial: "DEVICE_A", abi: "arm64-v8a" },
22
+ root: { detected: false, magiskInstalled: false },
23
+ frida: { serverRunning: false, port: null },
24
+ wechat: { installed: true, versionName: "7.0.22", majorVersion: 7 },
25
+ warnings: [],
26
+ ...overrides.probe,
27
+ })),
28
+ registerWechatAdapter: vi.fn(async (opts) => {
29
+ if (!opts.account || !opts.account.uin) {
30
+ return { ok: false, reason: "UIN_REQUIRED" };
31
+ }
32
+ return {
33
+ ok: true,
34
+ name: "wechat",
35
+ version: "0.5.0",
36
+ capabilities: ["sync:sqlite"],
37
+ sensitivity: "high",
38
+ chosenKeyProvider: opts.keyProviderOverride || "md5",
39
+ probe: { suggestedKeyProvider: "md5", reasons: [] },
40
+ registeredAt: 1716280000000,
41
+ ...overrides.register,
42
+ };
43
+ }),
44
+ listWechatAccounts: vi.fn(() => overrides.list || [
45
+ {
46
+ uin: "1234567890",
47
+ dbPath: "/tmp/EnMicroMsg.db",
48
+ hasWechatDataPath: true,
49
+ chosenKeyProvider: "md5",
50
+ registeredAt: 1716280000000,
51
+ lastSyncAt: null,
52
+ },
53
+ ]),
54
+ unregisterWechatAdapter: vi.fn(async (uin) => {
55
+ if (!uin) return { ok: false, reason: "UIN_REQUIRED" };
56
+ return { ok: true, removed: uin === "1234567890", uin, ...overrides.unregister };
57
+ }),
58
+ };
59
+ }
60
+
61
+ beforeEach(() => {
62
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
63
+ errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
64
+ exitSpy = vi.spyOn(process, "exit").mockImplementation((_code) => {
65
+ throw new Error("process.exit called");
66
+ });
67
+ });
68
+
69
+ afterEach(() => {
70
+ logSpy.mockRestore();
71
+ errSpy.mockRestore();
72
+ exitSpy.mockRestore();
73
+ });
74
+
75
+ describe("cc hub wechat env-probe", () => {
76
+ it("prints human-readable summary by default", async () => {
77
+ const hub = fakeHub();
78
+ await _internal.cmdWechatEnvProbe({ _getHub: async () => hub });
79
+ expect(hub.probeWechatEnv).toHaveBeenCalledOnce();
80
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
81
+ expect(out).toMatch(/suggested.*md5/);
82
+ expect(out).toMatch(/device.*reachable.*DEVICE_A/);
83
+ expect(out).toMatch(/wechat.*7\.0\.22/);
84
+ });
85
+
86
+ it("--json prints raw probe shape", async () => {
87
+ const hub = fakeHub();
88
+ await _internal.cmdWechatEnvProbe({ _getHub: async () => hub, json: true });
89
+ const out = logSpy.mock.calls.map((c) => c[0]).join("\n");
90
+ expect(JSON.parse(out)).toEqual(
91
+ expect.objectContaining({
92
+ suggestedKeyProvider: "md5",
93
+ device: expect.objectContaining({ reachable: true }),
94
+ }),
95
+ );
96
+ });
97
+ });
98
+
99
+ describe("cc hub wechat register", () => {
100
+ it("requires --uin (exits 1 via fail())", async () => {
101
+ const hub = fakeHub();
102
+ // With --json, fail() writes { error: "..." } to console.log; without
103
+ // --json, it writes a red ✗ to logger.error. We test both paths and
104
+ // assert at least one captured the missing-uin reason.
105
+ await expect(
106
+ _internal.cmdWechatRegister({ _getHub: async () => hub, json: true }),
107
+ ).rejects.toThrow(/process\.exit/);
108
+ const allOut =
109
+ logSpy.mock.calls.map((c) => c.map((p) => (typeof p === "string" ? p : JSON.stringify(p))).join(" ")).join("\n") +
110
+ "\n" +
111
+ errSpy.mock.calls.map((c) => c.map((p) => (typeof p === "string" ? p : JSON.stringify(p))).join(" ")).join("\n");
112
+ expect(allOut).toMatch(/--uin|uin/i);
113
+ });
114
+
115
+ it("forwards --uin / --db / --wechat-data-path / --force-provider to hub", async () => {
116
+ const hub = fakeHub();
117
+ await _internal.cmdWechatRegister({
118
+ _getHub: async () => hub,
119
+ uin: "1234567890",
120
+ db: "/tmp/EnMicroMsg.db",
121
+ wechatDataPath: "/tmp/com.tencent.mm",
122
+ forceProvider: "md5",
123
+ });
124
+ const callArg = hub.registerWechatAdapter.mock.calls[0][0];
125
+ expect(callArg.account.uin).toBe("1234567890");
126
+ expect(callArg.dbPath).toBe("/tmp/EnMicroMsg.db");
127
+ expect(callArg.wechatDataPath).toBe("/tmp/com.tencent.mm");
128
+ expect(callArg.keyProviderOverride).toBe("md5");
129
+ });
130
+
131
+ it("forwards --frida-device-id into fridaOpts", async () => {
132
+ const hub = fakeHub();
133
+ await _internal.cmdWechatRegister({
134
+ _getHub: async () => hub,
135
+ uin: "wxid_abc",
136
+ forceProvider: "frida",
137
+ fridaDeviceId: "EMU_X",
138
+ });
139
+ const callArg = hub.registerWechatAdapter.mock.calls[0][0];
140
+ expect(callArg.fridaOpts).toEqual({ deviceId: "EMU_X" });
141
+ });
142
+
143
+ it("prints provider + sensitivity on success", async () => {
144
+ const hub = fakeHub();
145
+ await _internal.cmdWechatRegister({
146
+ _getHub: async () => hub,
147
+ uin: "1234567890",
148
+ });
149
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
150
+ expect(out).toMatch(/wechat registered.*1234567890/);
151
+ expect(out).toMatch(/provider.*md5/);
152
+ expect(out).toMatch(/sensitivity.*high/);
153
+ });
154
+
155
+ it("exits 1 and surfaces reason + probe.reasons on ENV_UNSUPPORTED", async () => {
156
+ const hub = fakeHub({
157
+ register: {
158
+ ok: false,
159
+ reason: "ENV_UNSUPPORTED",
160
+ message: "No root",
161
+ probe: { reasons: ["WeChat 8.0.50 needs root", "frida not running"] },
162
+ },
163
+ });
164
+ await expect(
165
+ _internal.cmdWechatRegister({
166
+ _getHub: async () => hub,
167
+ uin: "wxid_abc",
168
+ }),
169
+ ).rejects.toThrow(/process\.exit/);
170
+ const errOut = errSpy.mock.calls.map((c) => c.join(" ")).join("\n");
171
+ expect(errOut).toMatch(/ENV_UNSUPPORTED/);
172
+ expect(errOut).toMatch(/needs root/);
173
+ expect(errOut).toMatch(/frida not running/);
174
+ });
175
+
176
+ it("--json prints raw register response without ANSI noise", async () => {
177
+ const hub = fakeHub();
178
+ await _internal.cmdWechatRegister({
179
+ _getHub: async () => hub,
180
+ uin: "1234567890",
181
+ json: true,
182
+ });
183
+ const out = logSpy.mock.calls.map((c) => c[0]).join("\n");
184
+ const parsed = JSON.parse(out);
185
+ expect(parsed.ok).toBe(true);
186
+ expect(parsed.chosenKeyProvider).toBe("md5");
187
+ expect(parsed.sensitivity).toBe("high");
188
+ });
189
+ });
190
+
191
+ describe("cc hub wechat list", () => {
192
+ it("prints account rows", async () => {
193
+ const hub = fakeHub();
194
+ await _internal.cmdWechatList({ _getHub: async () => hub });
195
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
196
+ expect(out).toMatch(/uin=1234567890/);
197
+ expect(out).toMatch(/provider=md5/);
198
+ });
199
+
200
+ it("prints '(no registered)' when empty", async () => {
201
+ const hub = fakeHub({ list: [] });
202
+ await _internal.cmdWechatList({ _getHub: async () => hub });
203
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
204
+ expect(out).toMatch(/no registered/);
205
+ });
206
+
207
+ it("--json prints accounts array", async () => {
208
+ const hub = fakeHub();
209
+ await _internal.cmdWechatList({ _getHub: async () => hub, json: true });
210
+ const out = logSpy.mock.calls.map((c) => c[0]).join("\n");
211
+ const parsed = JSON.parse(out);
212
+ expect(Array.isArray(parsed.accounts)).toBe(true);
213
+ expect(parsed.accounts[0].uin).toBe("1234567890");
214
+ });
215
+ });
216
+
217
+ describe("cc hub wechat unregister", () => {
218
+ it("calls hub with uin + reports removed", async () => {
219
+ const hub = fakeHub();
220
+ await _internal.cmdWechatUnregister("1234567890", { _getHub: async () => hub });
221
+ expect(hub.unregisterWechatAdapter).toHaveBeenCalledWith("1234567890");
222
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
223
+ expect(out).toMatch(/removed wechat account.*1234567890/);
224
+ });
225
+
226
+ it("reports nothing-removed for missing uin (still ok:true)", async () => {
227
+ const hub = fakeHub({ unregister: { removed: false } });
228
+ await _internal.cmdWechatUnregister("ghost", { _getHub: async () => hub });
229
+ const out = logSpy.mock.calls.map((c) => c.join(" ")).join("\n");
230
+ expect(out).toMatch(/was not registered/);
231
+ });
232
+
233
+ it("--json prints raw response", async () => {
234
+ const hub = fakeHub();
235
+ await _internal.cmdWechatUnregister("1234567890", {
236
+ _getHub: async () => hub,
237
+ json: true,
238
+ });
239
+ const out = logSpy.mock.calls.map((c) => c[0]).join("\n");
240
+ const parsed = JSON.parse(out);
241
+ expect(parsed).toEqual(expect.objectContaining({ ok: true, removed: true, uin: "1234567890" }));
242
+ });
243
+ });
@@ -0,0 +1,284 @@
1
+ /**
2
+ * `cc android` — Android-side data + control commands.
3
+ *
4
+ * Plan A Sub-Phase A7 scaffold. Exposes the bridge surface from
5
+ * `lib/cc-android-bridge.js` as commander subcommands. Method shapes mirror
6
+ * `docs/design/Personal_Data_Hub_Android_Standalone_Cc.md` §6.1.
7
+ *
8
+ * **v0.1 status**: native JNI bridge (`cc-android-bridge.node`) not yet
9
+ * bundled (A6 in progress). On non-Android hosts every command exits non-zero
10
+ * with `ANDROID_BRIDGE_NOT_AVAILABLE`. On Android with a missing binding,
11
+ * same error with a different reason string. This lets the surface land and
12
+ * be tested ahead of the JNI shipping work.
13
+ *
14
+ * All commands support `--json` for scripting and exit non-zero on error.
15
+ */
16
+
17
+ import chalk from "chalk";
18
+ import { logger } from "../lib/logger.js";
19
+ import * as bridgeModule from "../lib/cc-android-bridge.js";
20
+
21
+ function printJson(obj) {
22
+ console.log(JSON.stringify(obj, null, 2));
23
+ }
24
+
25
+ function fail(err, asJson) {
26
+ const msg = err && err.message ? err.message : String(err);
27
+ const code = err && err.code ? err.code : null;
28
+ if (asJson) {
29
+ printJson({ error: msg, code });
30
+ } else {
31
+ logger.error(chalk.red(`✗ ${msg}`));
32
+ }
33
+ process.exit(1);
34
+ }
35
+
36
+ async function run(method, params, options) {
37
+ try {
38
+ const result = await _deps.bridge.invoke(method, params);
39
+ if (options.json) {
40
+ printJson(result);
41
+ } else {
42
+ console.log(JSON.stringify(result, null, 2));
43
+ }
44
+ } catch (err) {
45
+ fail(err, options.json);
46
+ }
47
+ }
48
+
49
+ // ─── caps ────────────────────────────────────────────────────────────
50
+
51
+ async function cmdCaps(options) {
52
+ const capsResult = _deps.bridge.caps();
53
+ if (options.json) {
54
+ printJson(capsResult);
55
+ return;
56
+ }
57
+ if (capsResult.available) {
58
+ logger.log(chalk.green("✓ android bridge available"));
59
+ } else {
60
+ logger.log(
61
+ chalk.yellow(`⚠ android bridge unavailable: ${capsResult.reason}`),
62
+ );
63
+ }
64
+ }
65
+
66
+ // ─── contacts / sms / calls — P1 ContentResolver ─────────────────────
67
+
68
+ function cmdContactsPull(options) {
69
+ return run(
70
+ "contacts.query",
71
+ { since: options.since ? Number(options.since) : undefined },
72
+ options,
73
+ );
74
+ }
75
+ function cmdSmsPull(options) {
76
+ return run(
77
+ "sms.query",
78
+ { since: options.since ? Number(options.since) : undefined },
79
+ options,
80
+ );
81
+ }
82
+ function cmdCallsPull(options) {
83
+ return run(
84
+ "calls.query",
85
+ { since: options.since ? Number(options.since) : undefined },
86
+ options,
87
+ );
88
+ }
89
+
90
+ // ─── app — PackageManager ────────────────────────────────────────────
91
+
92
+ function cmdAppList(options) {
93
+ return run("app.list", { includeSystem: !!options.system }, options);
94
+ }
95
+ function cmdAppLaunch(pkg, options) {
96
+ return run("app.launch", { pkg }, options);
97
+ }
98
+ function cmdAppIntent(pkg, action, options) {
99
+ const extras = {};
100
+ if (Array.isArray(options.extra)) {
101
+ for (const kv of options.extra) {
102
+ const idx = kv.indexOf("=");
103
+ if (idx > 0) extras[kv.slice(0, idx)] = kv.slice(idx + 1);
104
+ }
105
+ }
106
+ return run("app.intent", { pkg, action, extras }, options);
107
+ }
108
+
109
+ // ─── fs — SAF / sandbox ──────────────────────────────────────────────
110
+
111
+ function cmdFsRead(target, options) {
112
+ return run("fs.read", { target }, options);
113
+ }
114
+ function cmdFsList(target, options) {
115
+ return run("fs.list", { target }, options);
116
+ }
117
+
118
+ // ─── a11y — Accessibility Service ────────────────────────────────────
119
+
120
+ function cmdA11yQuery(options) {
121
+ return run("a11y.query", { filter: options.filter }, options);
122
+ }
123
+ function cmdA11yClick(nodeId, options) {
124
+ return run("a11y.click", { nodeId }, options);
125
+ }
126
+ function cmdA11yType(text, options) {
127
+ return run("a11y.type", { text }, options);
128
+ }
129
+
130
+ // ─── shizuku / root ──────────────────────────────────────────────────
131
+
132
+ function cmdShizukuExec(cmd, options) {
133
+ return run("shizuku.exec", { cmd }, options);
134
+ }
135
+ function cmdRootExec(cmd, options) {
136
+ return run("root.exec", { cmd }, options);
137
+ }
138
+
139
+ // ─── perms ────────────────────────────────────────────────────────────
140
+
141
+ function cmdPerms(name, options) {
142
+ return run("perms.check", { name }, options);
143
+ }
144
+
145
+ // ─── Commander wire-up ───────────────────────────────────────────────
146
+
147
+ export function registerAndroidCommand(program) {
148
+ const android = program
149
+ .command("android")
150
+ .description(
151
+ "Android-native bridge: ContentResolver / SAF / Accessibility / Shizuku / root (Plan A A7)",
152
+ );
153
+
154
+ android
155
+ .command("caps")
156
+ .description("Probe what bridge capabilities are available on this device")
157
+ .option("--json", "Output JSON")
158
+ .action(cmdCaps);
159
+
160
+ const contacts = android
161
+ .command("contacts")
162
+ .description("ContentResolver: contacts");
163
+ contacts
164
+ .command("pull")
165
+ .description("Pull contacts (READ_CONTACTS runtime permission required)")
166
+ .option("--since <ms>", "Watermark — only newer entries")
167
+ .option("--json", "Output JSON")
168
+ .action(cmdContactsPull);
169
+
170
+ const sms = android.command("sms").description("ContentResolver: SMS");
171
+ sms
172
+ .command("pull")
173
+ .description("Pull SMS (READ_SMS runtime permission required)")
174
+ .option("--since <ms>", "Watermark")
175
+ .option("--json", "Output JSON")
176
+ .action(cmdSmsPull);
177
+
178
+ const calls = android
179
+ .command("calls")
180
+ .description("ContentResolver: call log");
181
+ calls
182
+ .command("pull")
183
+ .description("Pull call log (READ_CALL_LOG runtime permission required)")
184
+ .option("--since <ms>", "Watermark")
185
+ .option("--json", "Output JSON")
186
+ .action(cmdCallsPull);
187
+
188
+ const app = android
189
+ .command("app")
190
+ .description("PackageManager: app inventory + Intent");
191
+ app
192
+ .command("list")
193
+ .description("List installed packages")
194
+ .option("--system", "Include system packages")
195
+ .option("--json", "Output JSON")
196
+ .action(cmdAppList);
197
+ app
198
+ .command("launch <pkg>")
199
+ .description("Launch app by package id (default activity)")
200
+ .option("--json", "Output JSON")
201
+ .action(cmdAppLaunch);
202
+ app
203
+ .command("intent <pkg> <action>")
204
+ .description("Fire an Intent at <pkg> with <action>")
205
+ .option(
206
+ "--extra <kv...>",
207
+ "Repeatable extras as KEY=VAL (e.g. --extra foo=bar --extra n=1)",
208
+ )
209
+ .option("--json", "Output JSON")
210
+ .action(cmdAppIntent);
211
+
212
+ const fs = android.command("fs").description("SAF / sandbox filesystem");
213
+ fs.command("read <target>")
214
+ .description("Read file (sandbox path OR SAF tree URI)")
215
+ .option("--json", "Output JSON")
216
+ .action(cmdFsRead);
217
+ fs.command("list <target>")
218
+ .description("List directory")
219
+ .option("--json", "Output JSON")
220
+ .action(cmdFsList);
221
+
222
+ const a11y = android.command("a11y").description("Accessibility Service");
223
+ a11y
224
+ .command("query")
225
+ .description("Dump current screen's node tree")
226
+ .option("--filter <css>", "css-like filter")
227
+ .option("--json", "Output JSON")
228
+ .action(cmdA11yQuery);
229
+ a11y
230
+ .command("click <nodeId>")
231
+ .description("Click a node")
232
+ .option("--json", "Output JSON")
233
+ .action(cmdA11yClick);
234
+ a11y
235
+ .command("type <text>")
236
+ .description("Type text into focused field")
237
+ .option("--json", "Output JSON")
238
+ .action(cmdA11yType);
239
+
240
+ const shizuku = android
241
+ .command("shizuku")
242
+ .description("Shizuku ADB-like privileges");
243
+ shizuku
244
+ .command("exec <cmd>")
245
+ .description("Run shell via Shizuku")
246
+ .option("--json", "Output JSON")
247
+ .action(cmdShizukuExec);
248
+
249
+ const root = android.command("root").description("Magisk root su");
250
+ root
251
+ .command("exec <cmd>")
252
+ .description("Run shell as root")
253
+ .option("--json", "Output JSON")
254
+ .action(cmdRootExec);
255
+
256
+ android
257
+ .command("perms <name>")
258
+ .description("Check (and request) a runtime permission")
259
+ .option("--json", "Output JSON")
260
+ .action(cmdPerms);
261
+ }
262
+
263
+ // _deps injection seam — tests reach in and replace `bridge` with a mock so
264
+ // the cmd* functions exercise the routing code without a real bridge.
265
+ export const _deps = { bridge: bridgeModule };
266
+
267
+ // Test-only exports.
268
+ export const _cmds = {
269
+ cmdCaps,
270
+ cmdContactsPull,
271
+ cmdSmsPull,
272
+ cmdCallsPull,
273
+ cmdAppList,
274
+ cmdAppLaunch,
275
+ cmdAppIntent,
276
+ cmdFsRead,
277
+ cmdFsList,
278
+ cmdA11yQuery,
279
+ cmdA11yClick,
280
+ cmdA11yType,
281
+ cmdShizukuExec,
282
+ cmdRootExec,
283
+ cmdPerms,
284
+ };