chainlesschain 0.162.13 → 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.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-Bq_zxhCr.js → AIOps-D34d_Nh1.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CaevDm9t.js → ActionButton-Br7HxCnl.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-B0gOmwPw.js → Analytics-bVKq79Xd.js} +1 -1
- package/src/assets/web-panel/assets/{AppLayout-DWhZiV0Q.js → AppLayout-CWSLIbAz.js} +2 -2
- package/src/assets/web-panel/assets/{Audit-ZuZJBCxo.js → Audit-Cmnu1qqa.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CX7jhH5l.js → Backup-Rok20-TL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CVLx7HVq.js → BaseInput-BJzs_ZtT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C6yL5tRD.js → Chat-CSYapbcq.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BePhbqVq.js → Checkbox-BEa7Sr7e.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B5E4x1Lm.js → Codegen-C9M4e7ne.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CWhNU6A7.js → Col-DU9NoUIi.js} +1 -1
- package/src/assets/web-panel/assets/{Community-mSEAuJhp.js → Community-DA9uz_jP.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DSudHzX3.js → Compact-3_bEraVw.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CxJLYjyn.js → Compliance-BtF8jWUQ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-C-trppQj.js → Cowork-BqvA7oaM.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-_Ij4v5fY.js → Cron-CxZy7Mzg.js} +1 -1
- package/src/assets/web-panel/assets/{Crosschain-eLHXzp5T.js → Crosschain-1DB-XRGu.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BP04AUUB.js → DID-B6Ezp1pt.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-CzEeQv62.js → Dashboard-QDJ6VVsn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-C_SGOB22.js → Dropdown-CovsWjxG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BgaP4BOv.js → Federation-DbRxS4Y4.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BxGLLt9r.js → FormItemContext-9E9dNGtx.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Bt_uM_Gw.js → Git-CqEpyxRZ.js} +1 -1
- package/src/assets/web-panel/assets/{Governance-N2-0RG_o.js → Governance-On47KtGq.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-eS3g-CzP.js → Inference-RZcjcyaq.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CUuvNVah.js → KnowledgeGraph-C-1rRAM9.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-wPbwEt2r.js → Logs-BuunmG_r.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DH91kTwo.js → Marketplace-CromymyA.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D-GyyBrI.js → McpTools-5XlFExh7.js} +1 -1
- package/src/assets/web-panel/assets/{Memory-BOtUy-tw.js → Memory-DjnUT7YM.js} +1 -1
- package/src/assets/web-panel/assets/{MobileBridge-DIP__XQd.js → MobileBridge-BrYIgLg6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-1nqr1UsU.js → MobileProjects-CL5V3fTm.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CCE0x7h2.js → Mtc-CHYJq6zK.js} +1 -1
- package/src/assets/web-panel/assets/{MtcAudit-BBkz0XUO.js → MtcAudit-BZxUO0qt.js} +1 -1
- package/src/assets/web-panel/assets/{Multisig-CIKSJvTY.js → Multisig-FZTmJgW1.js} +1 -1
- package/src/assets/web-panel/assets/{NLProgramming-BDkgeFcq.js → NLProgramming-C9Mhefph.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-ONiUxfN1.js → Notes-W7usj-Ar.js} +1 -1
- package/src/assets/web-panel/assets/{NotificationSettings-CGcyKEIe.js → NotificationSettings-PBuYv_Bh.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BZtMYBJt.js → Organization-CuYCE-rF.js} +1 -1
- package/src/assets/web-panel/assets/{Overflow-C4LfFZAI.js → Overflow-Dojx-kzE.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D71Cpk-m.js → P2P-BgIaSrLX.js} +1 -1
- package/src/assets/web-panel/assets/{Permissions-DRGYF29v.js → Permissions-Byj2dkF_.js} +1 -1
- package/src/assets/web-panel/assets/{PersonalDataHub-CfRYoIua.js → PersonalDataHub-CMOOI13-.js} +1 -1
- package/src/assets/web-panel/assets/{Pipeline-BOyp0_Qo.js → Pipeline-CWwEOF09.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-a_AcphvF.js → Privacy-VT7gldcN.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CFu1grYt.js → ProjectInit-7UH3c3p7.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectSettings-p54Eivhh.js → ProjectSettings-DqLp-72a.js} +1 -1
- package/src/assets/web-panel/assets/{Projects-DkB88XAu.js → Projects-B_54eDhH.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-EK7f8DEd.js → Providers-BIrNfNpc.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CpO0w3iP.js → QuickAsk-BbYPwCso.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BUJVQdv9.js → Recommend-BF4qBssF.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-kU2fOuZt.js → Reputation-DPEzlC2V.js} +1 -1
- package/src/assets/web-panel/assets/{Row-oGWRbk6g.js → Row-DjHxhH1L.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-saC_46Yo.js → RssFeed-D0_j678P.js} +1 -1
- package/src/assets/web-panel/assets/{Search-CjtRqFUu.js → Search-DctfGehu.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BX0NBVfQ.js → Security-BFHggeYM.js} +1 -1
- package/src/assets/web-panel/assets/{Services-Bgxsnei_.js → Services-CmrFMukV.js} +1 -1
- package/src/assets/web-panel/assets/{Skeleton-CwBb3k2a.js → Skeleton-DR4vn_nS.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CZCMYH6Z.js → Skills-DlXG2yyV.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Djy1uHnZ.js → Sla-4PPGL3SE.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CGUI_Uyh.js → SpeechSettings-D9EhJOqm.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DK2CKHRD.js → SyncSettings-Dasmbi0p.js} +1 -1
- package/src/assets/web-panel/assets/{Tasks-BKiOzeIO.js → Tasks-vilEiuPA.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CnQpleXj.js → Templates-Ca9Rvktn.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DwKz0cjm.js → Tenant-CEZb9gfK.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-A7t_wsR8.js → Terminal-DanCBdbD.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BqYY9l44.js → Tokens-SPkClW2d.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BI4bXFmi.js → Trigger-B645yL7g.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-yMynKTRG.js → Trust-D9sM_Ig0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Br4IScM6.js → UkeySign-B_Nr2K-u.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CWcThGsP.js → VideoEditing-U01Lea8j.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CZcAtjxj.js → Wallet-6xBySVV8.js} +1 -1
- package/src/assets/web-panel/assets/{WebAuthn-BnTZFMA0.js → WebAuthn-DbgMoBu6.js} +1 -1
- package/src/assets/web-panel/assets/{WorkflowEditor-N7gGz3_n.js → WorkflowEditor-Bz-Y6IR2.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D175ZIO0.js → chat-BC_O9hag.js} +1 -1
- package/src/assets/web-panel/assets/{colors-LKhZyttv.js → colors-ChlOGOvr.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CsJSebxT.js → compact-item-BSbAYGGF.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BJ_CPYFC.js → createContext-CFcZly5M.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-CzD3IqH8.js → hasIn-BomYwwYE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSNibAqz.js → index-5Ewm6KZA.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8kltMTW.js → index-B6LJHQoE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvF2tC6C.js → index-BEJ6YiLI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjOrt4vw.js → index-BGUbtM3R.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm-3kvtD.js → index-BHGsFwYW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D401L3yx.js → index-BHi69MHF.js} +1 -1
- package/src/assets/web-panel/assets/{index-R1cFADfk.js → index-BI1jAWcc.js} +1 -1
- package/src/assets/web-panel/assets/index-BIRYt1of.js +1 -0
- package/src/assets/web-panel/assets/{index-Dd7dICwB.js → index-BYDvb1pi.js} +1 -1
- package/src/assets/web-panel/assets/{index-Kn-Of5ew.js → index-BZGdjNLA.js} +1 -1
- package/src/assets/web-panel/assets/{index-uTEVWPYA.js → index-BwFykZ5U.js} +1 -1
- package/src/assets/web-panel/assets/{index-JTX9A7w0.js → index-ByZQNO0A.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmJdof_c.js → index-C0rr1X9W.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsirlkJ0.js → index-CBhoZhCO.js} +1 -1
- package/src/assets/web-panel/assets/{index-6qPbrYF7.js → index-CCRSz2cR.js} +1 -1
- package/src/assets/web-panel/assets/index-CZfySmWX.js +1 -0
- package/src/assets/web-panel/assets/{index-DJVkBmSc.js → index-Cj47XwJQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOO73rHE.js → index-Cmzh8gKL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNVLVzN5.js → index-CnxlKTDK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTIkCKav.js → index-CoF95pYK.js} +1 -1
- package/src/assets/web-panel/assets/{index-RIO4JKMP.js → index-Ctx97mH-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEDFHKO3.js → index-D0vX9jQA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRGNuUIM.js → index-DNkth8dM.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnPt5OdD.js → index-DRp5_Xns.js} +1 -1
- package/src/assets/web-panel/assets/{index-E7t1hAnk.js → index-DW-Ji07y.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhiZDGg7.js → index-DXgE2VW6.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7UYymse.js → index-D_0B3CiU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyHdYUeZ.js → index-Dbf5YmDX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKjBAdm0.js → index-DsNQ2hqI.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2HBKw07.js → index-EY733h9z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM3uBEWD.js → index-QD_n54XT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXp1jVsK.js → index-T5Y_9IPv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDSZDDb2.js → index-b8GbH2Yi.js} +3 -3
- package/src/assets/web-panel/assets/{index-BZluCuTH.js → index-gUACAWbM.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7ZcBI5s.js → index-onW325hZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5IZCkZG.js → index-ozVPr1gj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMvdoiFr.js → index-slYX2rCE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXH9ujMW.js → index-t9u2bHpH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BV-__mlC.js → index-za1GUJBG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CBW0okek.js → initDefaultProps-DnadEaxu.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DGAffQ0Z.js → motion-CC_Na0Tl.js} +1 -1
- package/src/assets/web-panel/assets/{move-DFJ0-5IW.js → move-C2d9Mkk9.js} +1 -1
- package/src/assets/web-panel/assets/{omit-AvrDghg1.js → omit-QvpKbF8p.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D7csw9i1.js → pickAttrs-Dm8r3X1_.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-hZ6Lg6kG.js → placementArrow-DaqaVfoX.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DLLx5VvS.js → responsiveObserve-Iida9fIn.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BaRIT3ev.js → slide-YqHexXQD.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Cdjyuhrz.js → statusUtils-BGKLoeEt.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CbrNybTt.js → styleChecker-aI-gsQO8.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B8gAhiRC.js → useFlexGapSupport-BiOsz4rc.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BZPy4ICP.js → useFs-CZy7Zo2X.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-6Y0NDMVv.js → vnode-B6WqjmE4.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DTbMGsSH.js → zoom-DTeTrJ2z.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/__tests__/android.test.js +260 -0
- package/src/commands/android.js +284 -0
- package/src/commands/hub.js +57 -17
- package/src/gateways/ws/personal-data-hub-protocol.js +28 -8
- package/src/index.js +2 -0
- package/src/lib/cc-android-bridge.js +162 -0
- package/src/lib/personal-data-hub-wiring.js +74 -18
- package/src/lib/web-ui-server.js +2 -1
- package/src/assets/web-panel/assets/index-CY1mQA2I.js +0 -1
- package/src/assets/web-panel/assets/index-D9nXHfUB.js +0 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc android` command + cc-android-bridge unit tests (Plan A A7 scaffold).
|
|
3
|
+
*
|
|
4
|
+
* These exercise the command routing without a real Android device. The
|
|
5
|
+
* `_deps` injection seam swaps the bridge module with a mock so we hit both
|
|
6
|
+
* the success path (bridge resolves) and error path (bridge throws
|
|
7
|
+
* AndroidBridgeUnavailableError, which on a real non-Android host is what
|
|
8
|
+
* actually happens).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
12
|
+
|
|
13
|
+
import * as androidCmd from "../android.js";
|
|
14
|
+
import * as bridge from "../../lib/cc-android-bridge.js";
|
|
15
|
+
|
|
16
|
+
let logSpy, errSpy, exitSpy, jsonSpy;
|
|
17
|
+
|
|
18
|
+
function withMockBridge(invokeResult) {
|
|
19
|
+
const mock = {
|
|
20
|
+
invoke: vi.fn(async (method, params) => {
|
|
21
|
+
if (invokeResult instanceof Error) throw invokeResult;
|
|
22
|
+
if (typeof invokeResult === "function")
|
|
23
|
+
return await invokeResult(method, params);
|
|
24
|
+
return invokeResult;
|
|
25
|
+
}),
|
|
26
|
+
caps: vi.fn(() => ({ available: true, reason: "mock" })),
|
|
27
|
+
AndroidBridgeUnavailableError: bridge.AndroidBridgeUnavailableError,
|
|
28
|
+
};
|
|
29
|
+
androidCmd._deps.bridge = mock;
|
|
30
|
+
return mock;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
35
|
+
errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
36
|
+
exitSpy = vi.spyOn(process, "exit").mockImplementation((code) => {
|
|
37
|
+
throw new Error(`__exit:${code}`);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
androidCmd._deps.bridge = bridge;
|
|
43
|
+
vi.restoreAllMocks();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ─── cc-android-bridge ────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
describe("cc-android-bridge", () => {
|
|
49
|
+
it("detectAndroid returns false on a vanilla non-Android host", () => {
|
|
50
|
+
// Test runs on win32 / linux / darwin — not android.
|
|
51
|
+
expect(bridge.detectAndroid()).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("detectAndroid returns true under CC_ANDROID_BRIDGE_OVERRIDE=1", () => {
|
|
55
|
+
process.env.CC_ANDROID_BRIDGE_OVERRIDE = "1";
|
|
56
|
+
try {
|
|
57
|
+
expect(bridge.detectAndroid()).toBe(true);
|
|
58
|
+
} finally {
|
|
59
|
+
delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("invoke rejects with ANDROID_BRIDGE_NOT_AVAILABLE on non-Android host", async () => {
|
|
64
|
+
await expect(bridge.invoke("contacts.query", {})).rejects.toMatchObject({
|
|
65
|
+
code: "ANDROID_BRIDGE_NOT_AVAILABLE",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("invoke routes through testInvoke when override is set", async () => {
|
|
70
|
+
process.env.CC_ANDROID_BRIDGE_OVERRIDE = "1";
|
|
71
|
+
const original = bridge._deps.testInvoke;
|
|
72
|
+
bridge._deps.testInvoke = vi.fn(async (m, p) => ({ echo: m, params: p }));
|
|
73
|
+
try {
|
|
74
|
+
const r = await bridge.invoke("foo.bar", { x: 1 });
|
|
75
|
+
expect(r).toEqual({ echo: "foo.bar", params: { x: 1 } });
|
|
76
|
+
expect(bridge._deps.testInvoke).toHaveBeenCalledWith("foo.bar", { x: 1 });
|
|
77
|
+
} finally {
|
|
78
|
+
bridge._deps.testInvoke = original;
|
|
79
|
+
delete process.env.CC_ANDROID_BRIDGE_OVERRIDE;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("invoke rejects empty method", async () => {
|
|
84
|
+
await expect(bridge.invoke("")).rejects.toThrow(/non-empty string/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("caps reports unavailable on non-Android host", () => {
|
|
88
|
+
const c = bridge.caps();
|
|
89
|
+
expect(c.available).toBe(false);
|
|
90
|
+
expect(c.reason).toMatch(/not-on-android/);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ─── cmd routing — bridge success path ─────────────────────────────────
|
|
95
|
+
|
|
96
|
+
describe("cc android commands — success path (mocked bridge)", () => {
|
|
97
|
+
it("cmdCaps prints available when bridge.caps returns available", async () => {
|
|
98
|
+
withMockBridge({});
|
|
99
|
+
await androidCmd._cmds.cmdCaps({ json: true });
|
|
100
|
+
expect(logSpy).toHaveBeenCalled();
|
|
101
|
+
const out = logSpy.mock.calls[0][0];
|
|
102
|
+
expect(JSON.parse(out).available).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("cmdContactsPull invokes contacts.query with --since", async () => {
|
|
106
|
+
const mock = withMockBridge({ ingested: 42, events: [] });
|
|
107
|
+
await androidCmd._cmds.cmdContactsPull({
|
|
108
|
+
json: true,
|
|
109
|
+
since: "1700000000000",
|
|
110
|
+
});
|
|
111
|
+
expect(mock.invoke).toHaveBeenCalledWith("contacts.query", {
|
|
112
|
+
since: 1700000000000,
|
|
113
|
+
});
|
|
114
|
+
const out = JSON.parse(logSpy.mock.calls[0][0]);
|
|
115
|
+
expect(out.ingested).toBe(42);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("cmdSmsPull invokes sms.query", async () => {
|
|
119
|
+
const mock = withMockBridge({ ok: true });
|
|
120
|
+
await androidCmd._cmds.cmdSmsPull({ json: true });
|
|
121
|
+
expect(mock.invoke).toHaveBeenCalledWith("sms.query", { since: undefined });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("cmdCallsPull invokes calls.query", async () => {
|
|
125
|
+
const mock = withMockBridge({ ok: true });
|
|
126
|
+
await androidCmd._cmds.cmdCallsPull({ json: true });
|
|
127
|
+
expect(mock.invoke).toHaveBeenCalledWith("calls.query", {
|
|
128
|
+
since: undefined,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("cmdAppList passes includeSystem flag", async () => {
|
|
133
|
+
const mock = withMockBridge([{ pkg: "com.foo" }]);
|
|
134
|
+
await androidCmd._cmds.cmdAppList({ json: true, system: true });
|
|
135
|
+
expect(mock.invoke).toHaveBeenCalledWith("app.list", {
|
|
136
|
+
includeSystem: true,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("cmdAppLaunch forwards pkg", async () => {
|
|
141
|
+
const mock = withMockBridge({ ok: true });
|
|
142
|
+
await androidCmd._cmds.cmdAppLaunch("com.tencent.mm", { json: true });
|
|
143
|
+
expect(mock.invoke).toHaveBeenCalledWith("app.launch", {
|
|
144
|
+
pkg: "com.tencent.mm",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("cmdAppIntent parses --extra K=V repeatable", async () => {
|
|
149
|
+
const mock = withMockBridge({ ok: true });
|
|
150
|
+
await androidCmd._cmds.cmdAppIntent("com.foo", "VIEW", {
|
|
151
|
+
json: true,
|
|
152
|
+
extra: ["url=https://x", "id=42"],
|
|
153
|
+
});
|
|
154
|
+
expect(mock.invoke).toHaveBeenCalledWith("app.intent", {
|
|
155
|
+
pkg: "com.foo",
|
|
156
|
+
action: "VIEW",
|
|
157
|
+
extras: { url: "https://x", id: "42" },
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("cmdFsRead forwards SAF tree URI", async () => {
|
|
162
|
+
const mock = withMockBridge({ bytes: 100 });
|
|
163
|
+
await androidCmd._cmds.cmdFsRead(
|
|
164
|
+
"content://com.android.externalstorage.documents/tree/abc",
|
|
165
|
+
{
|
|
166
|
+
json: true,
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
expect(mock.invoke).toHaveBeenCalledWith("fs.read", {
|
|
170
|
+
target: "content://com.android.externalstorage.documents/tree/abc",
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("cmdFsList invokes fs.list", async () => {
|
|
175
|
+
const mock = withMockBridge([]);
|
|
176
|
+
await androidCmd._cmds.cmdFsList("/sdcard/Download/", { json: true });
|
|
177
|
+
expect(mock.invoke).toHaveBeenCalledWith("fs.list", {
|
|
178
|
+
target: "/sdcard/Download/",
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("cmdA11yQuery forwards --filter", async () => {
|
|
183
|
+
const mock = withMockBridge({ nodes: [] });
|
|
184
|
+
await androidCmd._cmds.cmdA11yQuery({ json: true, filter: "button" });
|
|
185
|
+
expect(mock.invoke).toHaveBeenCalledWith("a11y.query", {
|
|
186
|
+
filter: "button",
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("cmdA11yClick forwards nodeId", async () => {
|
|
191
|
+
const mock = withMockBridge({ ok: true });
|
|
192
|
+
await androidCmd._cmds.cmdA11yClick("node-7", { json: true });
|
|
193
|
+
expect(mock.invoke).toHaveBeenCalledWith("a11y.click", {
|
|
194
|
+
nodeId: "node-7",
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("cmdA11yType forwards text", async () => {
|
|
199
|
+
const mock = withMockBridge({ ok: true });
|
|
200
|
+
await androidCmd._cmds.cmdA11yType("hello", { json: true });
|
|
201
|
+
expect(mock.invoke).toHaveBeenCalledWith("a11y.type", { text: "hello" });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("cmdShizukuExec forwards cmd", async () => {
|
|
205
|
+
const mock = withMockBridge({ stdout: "ok" });
|
|
206
|
+
await androidCmd._cmds.cmdShizukuExec("pm list packages", { json: true });
|
|
207
|
+
expect(mock.invoke).toHaveBeenCalledWith("shizuku.exec", {
|
|
208
|
+
cmd: "pm list packages",
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("cmdRootExec forwards cmd", async () => {
|
|
213
|
+
const mock = withMockBridge({ stdout: "" });
|
|
214
|
+
await androidCmd._cmds.cmdRootExec("ls /data/data", { json: true });
|
|
215
|
+
expect(mock.invoke).toHaveBeenCalledWith("root.exec", {
|
|
216
|
+
cmd: "ls /data/data",
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("cmdPerms forwards permission name", async () => {
|
|
221
|
+
const mock = withMockBridge({ granted: false });
|
|
222
|
+
await androidCmd._cmds.cmdPerms("READ_CONTACTS", { json: true });
|
|
223
|
+
expect(mock.invoke).toHaveBeenCalledWith("perms.check", {
|
|
224
|
+
name: "READ_CONTACTS",
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ─── cmd routing — bridge error path ──────────────────────────────────
|
|
230
|
+
|
|
231
|
+
describe("cc android commands — error path", () => {
|
|
232
|
+
it("propagates ANDROID_BRIDGE_NOT_AVAILABLE as JSON when --json", async () => {
|
|
233
|
+
withMockBridge(
|
|
234
|
+
new bridge.AndroidBridgeUnavailableError("test-not-on-android"),
|
|
235
|
+
);
|
|
236
|
+
await expect(
|
|
237
|
+
androidCmd._cmds.cmdContactsPull({ json: true }),
|
|
238
|
+
).rejects.toThrow(/__exit:1/);
|
|
239
|
+
const out = JSON.parse(logSpy.mock.calls[0][0]);
|
|
240
|
+
expect(out.code).toBe("ANDROID_BRIDGE_NOT_AVAILABLE");
|
|
241
|
+
expect(out.error).toMatch(/test-not-on-android/);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("propagates error human-readable on non-JSON", async () => {
|
|
245
|
+
withMockBridge(new bridge.AndroidBridgeUnavailableError("nope"));
|
|
246
|
+
await expect(androidCmd._cmds.cmdSmsPull({ json: false })).rejects.toThrow(
|
|
247
|
+
/__exit:1/,
|
|
248
|
+
);
|
|
249
|
+
expect(errSpy).toHaveBeenCalled();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("non-bridge errors still surface", async () => {
|
|
253
|
+
withMockBridge(new Error("network timeout"));
|
|
254
|
+
await expect(androidCmd._cmds.cmdAppList({ json: true })).rejects.toThrow(
|
|
255
|
+
/__exit:1/,
|
|
256
|
+
);
|
|
257
|
+
const out = JSON.parse(logSpy.mock.calls[0][0]);
|
|
258
|
+
expect(out.error).toMatch(/network timeout/);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
@@ -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
|
+
};
|
package/src/commands/hub.js
CHANGED
|
@@ -191,6 +191,9 @@ async function cmdSyncAdapter(name, options) {
|
|
|
191
191
|
if (options.since) opts.since = Number(options.since);
|
|
192
192
|
if (options.until) opts.until = Number(options.until);
|
|
193
193
|
if (options.limit) opts.limit = Number(options.limit);
|
|
194
|
+
// Plan A v0.1 — system-data-android needs a snapshot file path. Generic
|
|
195
|
+
// pass-through so other input-driven adapters reuse the same flag.
|
|
196
|
+
if (options.input) opts.inputPath = String(options.input);
|
|
194
197
|
const report = await hub.registry.syncAdapter(name, opts);
|
|
195
198
|
if (spinner) spinner.succeed(`synced ${name}`);
|
|
196
199
|
if (options.json) {
|
|
@@ -566,13 +569,25 @@ async function cmdWechatEnvProbe(options) {
|
|
|
566
569
|
return;
|
|
567
570
|
}
|
|
568
571
|
logger.log(chalk.bold("WeChat env-probe:"));
|
|
569
|
-
logger.log(
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
logger.log(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
572
|
+
logger.log(
|
|
573
|
+
` ${probe.ok ? chalk.green("✓") : chalk.red("✗")} suggested: ${chalk.cyan(probe.suggestedKeyProvider)}`,
|
|
574
|
+
);
|
|
575
|
+
logger.log(
|
|
576
|
+
` device: ${probe.device.reachable ? chalk.green("reachable") : chalk.red("unreachable")}${probe.device.serial ? " (" + probe.device.serial + ")" : ""} abi=${probe.device.abi || "?"}`,
|
|
577
|
+
);
|
|
578
|
+
logger.log(
|
|
579
|
+
` root: ${probe.root.detected ? chalk.green("yes") : chalk.gray("no")} magisk=${probe.root.magiskInstalled ? "yes" : "no"}`,
|
|
580
|
+
);
|
|
581
|
+
logger.log(
|
|
582
|
+
` frida-server: ${probe.frida.serverRunning ? chalk.green("running") : chalk.gray("not running")}${probe.frida.port ? " :" + probe.frida.port : ""}`,
|
|
583
|
+
);
|
|
584
|
+
logger.log(
|
|
585
|
+
` wechat: ${probe.wechat.installed ? chalk.green(probe.wechat.versionName) : chalk.gray("not installed")}`,
|
|
586
|
+
);
|
|
587
|
+
for (const reason of probe.reasons || [])
|
|
588
|
+
logger.log(` · ${chalk.gray(reason)}`);
|
|
589
|
+
for (const w of probe.warnings || [])
|
|
590
|
+
logger.log(` ${chalk.yellow("!")} ${chalk.yellow(w)}`);
|
|
576
591
|
} catch (err) {
|
|
577
592
|
fail(null, err, options.json);
|
|
578
593
|
}
|
|
@@ -589,16 +604,21 @@ async function cmdWechatRegister(options) {
|
|
|
589
604
|
dbPath: options.db || null,
|
|
590
605
|
wechatDataPath: options.wechatDataPath || null,
|
|
591
606
|
keyProviderOverride: options.forceProvider || null,
|
|
592
|
-
fridaOpts: options.fridaDeviceId
|
|
607
|
+
fridaOpts: options.fridaDeviceId
|
|
608
|
+
? { deviceId: options.fridaDeviceId }
|
|
609
|
+
: null,
|
|
593
610
|
});
|
|
594
611
|
if (options.json) {
|
|
595
612
|
printJson(r);
|
|
596
613
|
return;
|
|
597
614
|
}
|
|
598
615
|
if (!r.ok) {
|
|
599
|
-
logger.error(
|
|
616
|
+
logger.error(
|
|
617
|
+
chalk.red(`✗ ${r.reason || "register failed"}: ${r.message || ""}`),
|
|
618
|
+
);
|
|
600
619
|
if (r.probe) {
|
|
601
|
-
for (const reason of r.probe.reasons || [])
|
|
620
|
+
for (const reason of r.probe.reasons || [])
|
|
621
|
+
logger.error(chalk.gray(" · " + reason));
|
|
602
622
|
}
|
|
603
623
|
process.exit(1);
|
|
604
624
|
}
|
|
@@ -624,7 +644,9 @@ async function cmdWechatList(options) {
|
|
|
624
644
|
}
|
|
625
645
|
logger.log(chalk.bold("Registered WeChat accounts:"));
|
|
626
646
|
for (const row of rows) {
|
|
627
|
-
logger.log(
|
|
647
|
+
logger.log(
|
|
648
|
+
` • uin=${chalk.cyan(row.uin)} provider=${row.chosenKeyProvider || "?"} db=${row.dbPath || "(none)"} regAt=${row.registeredAt ? new Date(row.registeredAt).toISOString() : "?"}`,
|
|
649
|
+
);
|
|
628
650
|
}
|
|
629
651
|
} catch (err) {
|
|
630
652
|
fail(null, err, options.json);
|
|
@@ -644,8 +666,12 @@ async function cmdWechatUnregister(uin, options) {
|
|
|
644
666
|
logger.error(chalk.red(`✗ ${r.reason || "unregister failed"}`));
|
|
645
667
|
process.exit(1);
|
|
646
668
|
}
|
|
647
|
-
if (r.removed)
|
|
648
|
-
|
|
669
|
+
if (r.removed)
|
|
670
|
+
logger.log(chalk.green(`✓ removed wechat account (uin=${uin})`));
|
|
671
|
+
else
|
|
672
|
+
logger.log(
|
|
673
|
+
chalk.gray(`(uin=${uin} was not registered — nothing removed)`),
|
|
674
|
+
);
|
|
649
675
|
} catch (err) {
|
|
650
676
|
fail(null, err, options.json);
|
|
651
677
|
}
|
|
@@ -712,6 +738,10 @@ export function registerHubCommand(program) {
|
|
|
712
738
|
.option("--since <ms>", "Override watermark — sync from this unix-ms")
|
|
713
739
|
.option("--until <ms>", "Stop at this unix-ms")
|
|
714
740
|
.option("--limit <n>", "Cap ingested events")
|
|
741
|
+
.option(
|
|
742
|
+
"--input <path>",
|
|
743
|
+
"Path to a snapshot file produced by a UI layer (system-data-android, etc.)",
|
|
744
|
+
)
|
|
715
745
|
.option("--json", "Output JSON")
|
|
716
746
|
.action(cmdSyncAdapter);
|
|
717
747
|
|
|
@@ -833,7 +863,9 @@ export function registerHubCommand(program) {
|
|
|
833
863
|
|
|
834
864
|
wechat
|
|
835
865
|
.command("env-probe")
|
|
836
|
-
.description(
|
|
866
|
+
.description(
|
|
867
|
+
"Probe attached Android device for adb / root / frida-server / WeChat version",
|
|
868
|
+
)
|
|
837
869
|
.option("--json", "Output JSON")
|
|
838
870
|
.action(cmdWechatEnvProbe);
|
|
839
871
|
|
|
@@ -844,9 +876,15 @@ export function registerHubCommand(program) {
|
|
|
844
876
|
)
|
|
845
877
|
.requiredOption("--uin <id>", "WeChat numeric UIN (≤ 8.0) or wxid (8.0+)")
|
|
846
878
|
.option("--db <path>", "Local path to the already-pulled EnMicroMsg.db")
|
|
847
|
-
.option(
|
|
879
|
+
.option(
|
|
880
|
+
"--wechat-data-path <dir>",
|
|
881
|
+
"Local pulled /data/data/com.tencent.mm/ tree (required for md5 path)",
|
|
882
|
+
)
|
|
848
883
|
.option("--force-provider <md5|frida>", "Override env-probe suggestion")
|
|
849
|
-
.option(
|
|
884
|
+
.option(
|
|
885
|
+
"--frida-device-id <id>",
|
|
886
|
+
"Frida device id (defaults to first USB device)",
|
|
887
|
+
)
|
|
850
888
|
.option("--json", "Output JSON")
|
|
851
889
|
.action(cmdWechatRegister);
|
|
852
890
|
|
|
@@ -858,7 +896,9 @@ export function registerHubCommand(program) {
|
|
|
858
896
|
|
|
859
897
|
wechat
|
|
860
898
|
.command("unregister <uin>")
|
|
861
|
-
.description(
|
|
899
|
+
.description(
|
|
900
|
+
"Remove a registered WeChat account (does not touch vault data)",
|
|
901
|
+
)
|
|
862
902
|
.option("--json", "Output JSON")
|
|
863
903
|
.action(cmdWechatUnregister);
|
|
864
904
|
}
|