tapflow 0.0.1 → 0.1.0-alpha.2

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -0
  3. package/bin/tapflow.js +2 -1
  4. package/dist/commands/agent-start.d.ts +7 -0
  5. package/dist/commands/agent-start.d.ts.map +1 -0
  6. package/dist/commands/agent-start.js +78 -0
  7. package/dist/commands/agent-start.js.map +1 -0
  8. package/dist/commands/boot.d.ts +2 -0
  9. package/dist/commands/boot.d.ts.map +1 -0
  10. package/dist/commands/boot.js +47 -0
  11. package/dist/commands/boot.js.map +1 -0
  12. package/dist/commands/devices.d.ts +2 -0
  13. package/dist/commands/devices.d.ts.map +1 -0
  14. package/dist/commands/devices.js +85 -0
  15. package/dist/commands/devices.js.map +1 -0
  16. package/dist/commands/doctor.d.ts +2 -0
  17. package/dist/commands/doctor.d.ts.map +1 -0
  18. package/dist/commands/doctor.js +41 -0
  19. package/dist/commands/doctor.js.map +1 -0
  20. package/dist/commands/init.d.ts +5 -0
  21. package/dist/commands/init.d.ts.map +1 -0
  22. package/dist/commands/init.js +64 -0
  23. package/dist/commands/init.js.map +1 -0
  24. package/dist/commands/logs.d.ts +5 -0
  25. package/dist/commands/logs.d.ts.map +1 -0
  26. package/dist/commands/logs.js +22 -0
  27. package/dist/commands/logs.js.map +1 -0
  28. package/dist/commands/relay-start.d.ts +5 -0
  29. package/dist/commands/relay-start.d.ts.map +1 -0
  30. package/dist/commands/relay-start.js +24 -0
  31. package/dist/commands/relay-start.js.map +1 -0
  32. package/dist/commands/reset.d.ts +2 -0
  33. package/dist/commands/reset.d.ts.map +1 -0
  34. package/dist/commands/reset.js +49 -0
  35. package/dist/commands/reset.js.map +1 -0
  36. package/dist/commands/start.d.ts +6 -0
  37. package/dist/commands/start.d.ts.map +1 -0
  38. package/dist/commands/start.js +77 -0
  39. package/dist/commands/start.js.map +1 -0
  40. package/dist/commands/status.d.ts +4 -0
  41. package/dist/commands/status.d.ts.map +1 -0
  42. package/dist/commands/status.js +56 -0
  43. package/dist/commands/status.js.map +1 -0
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +56 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/lib/doctor.d.ts +12 -0
  49. package/dist/lib/doctor.d.ts.map +1 -0
  50. package/dist/lib/doctor.js +118 -0
  51. package/dist/lib/doctor.js.map +1 -0
  52. package/dist/lib/ios-boot.d.ts +3 -0
  53. package/dist/lib/ios-boot.d.ts.map +1 -0
  54. package/dist/lib/ios-boot.js +44 -0
  55. package/dist/lib/ios-boot.js.map +1 -0
  56. package/dist/lib/platform.d.ts +2 -0
  57. package/dist/lib/platform.d.ts.map +1 -0
  58. package/dist/lib/platform.js +10 -0
  59. package/dist/lib/platform.js.map +1 -0
  60. package/dist/lib/print.d.ts +13 -0
  61. package/dist/lib/print.d.ts.map +1 -0
  62. package/dist/lib/print.js +55 -0
  63. package/dist/lib/print.js.map +1 -0
  64. package/dist/lib/tapflow-dir.d.ts +3 -0
  65. package/dist/lib/tapflow-dir.d.ts.map +1 -0
  66. package/dist/lib/tapflow-dir.js +8 -0
  67. package/dist/lib/tapflow-dir.js.map +1 -0
  68. package/package.json +58 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AAE7C,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAwC;IACpE,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,uBAAuB,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAA;IAC/B,MAAM,GAAG,GAAG,GAAG,IAAI,sBAAsB,KAAK,EAAE,CAAA;IAEhD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IAE9C,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,8BAA8B,IAAI,qCAAqC,CAAC,CAAA;QACrG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,OAAO,GAAa,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;IAE1C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAA;QAClD,OAAM;IACR,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,CAAA;IACpC,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface RelayStartOptions {
2
+ port?: number;
3
+ }
4
+ export declare function cmdRelayStart(opts: RelayStartOptions): Promise<void>;
5
+ //# sourceMappingURL=relay-start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-start.d.ts","sourceRoot":"","sources":["../../src/commands/relay-start.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAMD,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB1E"}
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import { RelayServer } from '@tapflowio/relay';
3
+ import { banner, step } from '../lib/print.js';
4
+ const DEFAULT_PORT = 4000;
5
+ const portSchema = z.number().int().min(1).max(65535, 'port must be between 1 and 65535');
6
+ export async function cmdRelayStart(opts) {
7
+ const rawPort = opts.port ?? DEFAULT_PORT;
8
+ const portResult = portSchema.safeParse(rawPort);
9
+ if (!portResult.success) {
10
+ banner('error', 'INVALID CONFIG', [`--port: ${portResult.error.issues[0].message}`]);
11
+ process.exit(1);
12
+ }
13
+ const port = portResult.data;
14
+ const server = new RelayServer({ port });
15
+ await server.start();
16
+ step(`Relay started on ws://localhost:${port}`);
17
+ banner('success', 'TAPFLOW RELAY READY', [
18
+ `Relay : http://localhost:${port}`,
19
+ `Connect Mac agents: tapflow start --relay ws://<host>:${port}`,
20
+ 'Press Ctrl+C to stop.',
21
+ ]);
22
+ process.on('SIGINT', () => process.exit(0));
23
+ }
24
+ //# sourceMappingURL=relay-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-start.js","sourceRoot":"","sources":["../../src/commands/relay-start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAM9C,MAAM,YAAY,GAAG,IAAI,CAAA;AAEzB,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,kCAAkC,CAAC,CAAA;AAEzF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAuB;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,YAAY,CAAA;IACzC,MAAM,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IAChD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,WAAW,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QACpF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAA;IAC5B,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;IACxC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;IACpB,IAAI,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAA;IAE/C,MAAM,CAAC,SAAS,EAAE,qBAAqB,EAAE;QACvC,6BAA6B,IAAI,EAAE;QACnC,0DAA0D,IAAI,EAAE;QAChE,uBAAuB;KACxB,CAAC,CAAA;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdReset(): Promise<void>;
2
+ //# sourceMappingURL=reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/commands/reset.ts"],"names":[],"mappings":"AAKA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA8C9C"}
@@ -0,0 +1,49 @@
1
+ import { execSync } from 'node:child_process';
2
+ import * as readline from 'node:readline/promises';
3
+ import { stdin as input, stdout as output } from 'node:process';
4
+ import { banner, step } from '../lib/print.js';
5
+ export async function cmdReset() {
6
+ const rl = readline.createInterface({ input, output });
7
+ const answer = await rl.question('This will shut down all simulators and emulators (no data loss). Continue? [y/N] ');
8
+ rl.close();
9
+ if (!answer.trim().toLowerCase().startsWith('y')) {
10
+ console.log('\n Aborted.\n');
11
+ return;
12
+ }
13
+ // iOS
14
+ if (process.platform === 'darwin') {
15
+ try {
16
+ execSync('xcrun simctl shutdown all', { stdio: 'pipe' });
17
+ step('iOS: all simulators shut down.');
18
+ }
19
+ catch {
20
+ step('iOS: no simulators to shut down.');
21
+ }
22
+ }
23
+ // Android
24
+ try {
25
+ execSync('which adb', { stdio: 'pipe' });
26
+ const out = execSync('adb devices', { encoding: 'utf8', stdio: 'pipe' });
27
+ const emulators = out.trim().split('\n').slice(1)
28
+ .filter((l) => l.startsWith('emulator-'))
29
+ .map((l) => l.split('\t')[0]?.trim() ?? '')
30
+ .filter(Boolean);
31
+ if (emulators.length === 0) {
32
+ step('Android: no running emulators.');
33
+ }
34
+ else {
35
+ for (const serial of emulators) {
36
+ try {
37
+ execSync(`adb -s ${serial} emu kill`, { stdio: 'pipe' });
38
+ step(`Android: ${serial} shut down.`);
39
+ }
40
+ catch {
41
+ step(`Android: could not shut down ${serial}.`);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ catch { /* adb not available */ }
47
+ banner('success', 'RESET COMPLETE');
48
+ }
49
+ //# sourceMappingURL=reset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/commands/reset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,KAAK,QAAQ,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE9C,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IACtD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAC9B,mFAAmF,CACpF,CAAA;IACD,EAAE,CAAC,KAAK,EAAE,CAAA;IAEV,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;QAC7B,OAAM;IACR,CAAC;IAED,MAAM;IACN,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,QAAQ,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YACxD,IAAI,CAAC,gCAAgC,CAAC,CAAA;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,kCAAkC,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC;IAED,UAAU;IACV,IAAI,CAAC;QACH,QAAQ,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACxC,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QACxE,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;aAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;aACxC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;aAC1C,MAAM,CAAC,OAAO,CAAC,CAAA;QAElB,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,gCAAgC,CAAC,CAAA;QACxC,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,QAAQ,CAAC,UAAU,MAAM,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;oBACxD,IAAI,CAAC,YAAY,MAAM,aAAa,CAAC,CAAA;gBACvC,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,CAAC,gCAAgC,MAAM,GAAG,CAAC,CAAA;gBACjD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,uBAAuB,CAAC,CAAC;IAEnC,MAAM,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAA;AACrC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface StartOptions {
2
+ device?: string;
3
+ platform?: 'ios' | 'android' | 'all';
4
+ }
5
+ export declare function cmdStart(opts: StartOptions): Promise<void>;
6
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAA;CACrC;AAID,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EhE"}
@@ -0,0 +1,77 @@
1
+ import { RelayServer } from '@tapflowio/relay';
2
+ import { AndroidAgent } from '@tapflowio/android-agent';
3
+ import { banner, createSpinner, step } from '../lib/print.js';
4
+ import { hasAdb } from '../lib/platform.js';
5
+ import { resolveAndBootIOSDevice } from '../lib/ios-boot.js';
6
+ const RELAY_PORT = 4000;
7
+ export async function cmdStart(opts) {
8
+ const isMac = process.platform === 'darwin';
9
+ const relayUrl = `ws://localhost:${RELAY_PORT}`;
10
+ const explicit = opts.platform;
11
+ const runIOS = explicit === 'ios' || explicit === 'all' || (!explicit && isMac);
12
+ const runAndroid = explicit === 'android' || explicit === 'all' || (!explicit && hasAdb());
13
+ // ── 1. Relay (always local) ───────────────────────────────────────────────
14
+ const server = new RelayServer({ port: RELAY_PORT });
15
+ await server.start();
16
+ step(`Relay started on ws://localhost:${RELAY_PORT}`);
17
+ // ── 2. Agent availability check ───────────────────────────────────────────
18
+ if (!runIOS && !runAndroid) {
19
+ banner('success', 'TAPFLOW RELAY READY', [
20
+ `Relay : http://localhost:${RELAY_PORT}`,
21
+ 'No agent environment detected — running relay only.',
22
+ `Connect a Mac agent: tapflow agent start --relay ws://<this-ip>:${RELAY_PORT}`,
23
+ 'Press Ctrl+C to stop.',
24
+ ]);
25
+ process.on('SIGINT', () => process.exit(0));
26
+ return;
27
+ }
28
+ const agents = [];
29
+ // ── 3. iOS Agent ──────────────────────────────────────────────────────────
30
+ if (runIOS) {
31
+ const iosAgent = await resolveAndBootIOSDevice(opts.device);
32
+ const iosSpinner = createSpinner('Connecting iOS agent…');
33
+ iosSpinner.start();
34
+ try {
35
+ await iosAgent.connect(relayUrl);
36
+ iosSpinner.stop(true);
37
+ agents.push(iosAgent);
38
+ }
39
+ catch (e) {
40
+ iosSpinner.stop(false);
41
+ banner('error', 'IOS CONNECTION FAILED', [e.message]);
42
+ process.exit(1);
43
+ }
44
+ }
45
+ // ── 4. Android Agent ──────────────────────────────────────────────────────
46
+ if (runAndroid) {
47
+ const androidAgent = new AndroidAgent({ deviceFilter: opts.device });
48
+ const androidSpinner = createSpinner('Connecting Android agent…');
49
+ androidSpinner.start();
50
+ try {
51
+ await androidAgent.connect(relayUrl);
52
+ androidSpinner.stop(true);
53
+ agents.push(androidAgent);
54
+ }
55
+ catch (e) {
56
+ androidSpinner.stop(false);
57
+ // Android 연결 실패 시 iOS가 이미 연결됐으면 경고만, 아니면 종료
58
+ if (runIOS && agents.length > 0) {
59
+ console.log(` ⚠ Android: ${e.message}`);
60
+ }
61
+ else {
62
+ banner('error', 'ANDROID CONNECTION FAILED', [e.message]);
63
+ process.exit(1);
64
+ }
65
+ }
66
+ }
67
+ banner('success', 'TAPFLOW READY', [
68
+ `Relay : http://localhost:${RELAY_PORT}`,
69
+ `Open http://localhost:${RELAY_PORT} in your browser.`,
70
+ 'Press Ctrl+C to stop.',
71
+ ]);
72
+ process.on('SIGINT', () => {
73
+ agents.forEach((a) => a.disconnect());
74
+ process.exit(0);
75
+ });
76
+ }
77
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function cmdStatus(opts: {
2
+ relay?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAGA,wBAAsB,SAAS,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2DvE"}
@@ -0,0 +1,56 @@
1
+ import { WebSocket } from 'ws';
2
+ import { DIM, BOLD, GREEN, YELLOW, RED, R } from '../lib/print.js';
3
+ export async function cmdStatus(opts) {
4
+ const relayUrl = (opts.relay ?? 'ws://localhost:4000').replace(/^http/, 'ws');
5
+ console.log(`\n Connecting to ${relayUrl}…\n`);
6
+ console.log(` ${DIM}● agent ◉ in use ○ idle${R}\n`);
7
+ await new Promise((resolve, reject) => {
8
+ const ws = new WebSocket(relayUrl);
9
+ const timeout = setTimeout(() => {
10
+ ws.terminate();
11
+ reject(new Error(`Could not connect to relay at ${relayUrl} (5s timeout)`));
12
+ }, 5000);
13
+ ws.on('open', () => ws.send(JSON.stringify({ type: 'agents:list' })));
14
+ ws.on('message', (data) => {
15
+ try {
16
+ const msg = JSON.parse(data.toString());
17
+ if (msg.type !== 'agents:listed')
18
+ return;
19
+ clearTimeout(timeout);
20
+ ws.close();
21
+ const sessions = msg.sessions ?? [];
22
+ if (sessions.length === 0) {
23
+ console.log(` ${DIM}No agents connected.${R}\n`);
24
+ }
25
+ else {
26
+ let totalDevices = 0;
27
+ let activeSessions = 0;
28
+ for (const session of sessions) {
29
+ console.log(` ${BOLD}${GREEN}●${R} ${BOLD}${session.agentName}${R}`);
30
+ for (const device of session.devices ?? []) {
31
+ const occupied = !!device.joinedBy;
32
+ const dot = occupied ? `${YELLOW}◉${R}` : `${DIM}○${R}`;
33
+ const who = occupied ? ` ${DIM}← ${device.joinedBy}${R}` : '';
34
+ console.log(` ${dot} ${device.name}${who}`);
35
+ totalDevices++;
36
+ if (occupied)
37
+ activeSessions++;
38
+ }
39
+ console.log();
40
+ }
41
+ console.log(` ${DIM}${sessions.length} agent(s) · ${totalDevices} device(s) · ${activeSessions} active session(s)${R}\n`);
42
+ }
43
+ resolve();
44
+ }
45
+ catch { /* ignore non-agents:listed messages */ }
46
+ });
47
+ ws.on('error', (err) => {
48
+ clearTimeout(timeout);
49
+ reject(err);
50
+ });
51
+ }).catch((err) => {
52
+ console.error(`\n ${RED}✗${R} ${err.message}\n`);
53
+ process.exit(1);
54
+ });
55
+ }
56
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AAElE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAwB;IACtD,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,qBAAqB,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;IAE7E,OAAO,CAAC,GAAG,CAAC,qBAAqB,QAAQ,KAAK,CAAC,CAAA;IAC/C,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,CAAA;IAEtD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAA;QAClC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,EAAE,CAAC,SAAS,EAAE,CAAA;YACd,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,QAAQ,eAAe,CAAC,CAAC,CAAA;QAC7E,CAAC,EAAE,IAAI,CAAC,CAAA;QAER,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAA;QAErE,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;gBACvC,IAAI,GAAG,CAAC,IAAI,KAAK,eAAe;oBAAE,OAAM;gBAExC,YAAY,CAAC,OAAO,CAAC,CAAA;gBACrB,EAAE,CAAC,KAAK,EAAE,CAAA;gBAEV,MAAM,QAAQ,GAAsG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAA;gBAEtI,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAA;gBACnD,CAAC;qBAAM,CAAC;oBACN,IAAI,YAAY,GAAG,CAAC,CAAA;oBACpB,IAAI,cAAc,GAAG,CAAC,CAAA;oBAEtB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;wBAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC,CAAA;wBACrE,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;4BAC3C,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAA;4BAClC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,CAAA;4BACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,MAAM,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;4BAC9D,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,KAAK,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC,CAAA;4BACjD,YAAY,EAAE,CAAA;4BACd,IAAI,QAAQ;gCAAE,cAAc,EAAE,CAAA;wBAChC,CAAC;wBACD,OAAO,CAAC,GAAG,EAAE,CAAA;oBACf,CAAC;oBAED,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,GAAG,QAAQ,CAAC,MAAM,eAAe,YAAY,gBAAgB,cAAc,qBAAqB,CAAC,IAAI,CAAC,CAAA;gBAC5H,CAAC;gBAED,OAAO,EAAE,CAAA;YACX,CAAC;YAAC,MAAM,CAAC,CAAC,uCAAuC,CAAC,CAAC;QACrD,CAAC,CAAC,CAAA;QAEF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACrB,YAAY,CAAC,OAAO,CAAC,CAAA;YACrB,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;QACtB,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,OAAO,IAAI,CAAC,CAAA;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,56 @@
1
+ import { cac } from 'cac';
2
+ import { cmdInit } from './commands/init.js';
3
+ import { cmdDoctor } from './commands/doctor.js';
4
+ import { cmdDevices } from './commands/devices.js';
5
+ import { cmdBoot } from './commands/boot.js';
6
+ import { cmdStart } from './commands/start.js';
7
+ import { cmdRelayStart } from './commands/relay-start.js';
8
+ import { cmdAgentStart } from './commands/agent-start.js';
9
+ import { cmdReset } from './commands/reset.js';
10
+ import { cmdStatus } from './commands/status.js';
11
+ import { cmdLogs } from './commands/logs.js';
12
+ const cli = cac('tapflow');
13
+ cli
14
+ .command('init', 'Create the first admin account on the relay')
15
+ .option('--relay <url>', 'Relay URL (default: http://localhost:4000)')
16
+ .action((opts) => cmdInit(opts));
17
+ cli
18
+ .command('doctor', 'Check system prerequisites')
19
+ .action(() => cmdDoctor());
20
+ cli
21
+ .command('devices', 'List available iOS simulators and Android AVDs')
22
+ .action(() => cmdDevices());
23
+ cli
24
+ .command('boot <name>', 'Boot a simulator by name or UDID')
25
+ .action((name) => cmdBoot(name));
26
+ cli
27
+ .command('start', 'Start relay and available agents locally (local dev shortcut)')
28
+ .option('--platform <platform>', 'Platform to start: ios | android | all (default: auto-detect)')
29
+ .option('--device <name>', 'iOS Simulator name or UDID to use')
30
+ .action((opts) => cmdStart(opts));
31
+ cli
32
+ .command('relay start', 'Start the relay server only (no agents)')
33
+ .option('--port <n>', 'Port to listen on (default: 4000)')
34
+ .action((opts) => cmdRelayStart(opts));
35
+ cli
36
+ .command('agent start', 'Start agents and connect to a relay (no local relay spawned)')
37
+ .option('--relay <url>', 'Relay WebSocket URL (default: ws://localhost:4000)')
38
+ .option('--platform <platform>', 'Platform to start: ios | android | all (default: auto-detect)')
39
+ .option('--device <name>', 'iOS Simulator name or UDID to use')
40
+ .action((opts) => cmdAgentStart(opts));
41
+ cli
42
+ .command('reset', 'Shut down all simulators')
43
+ .action(() => cmdReset());
44
+ cli
45
+ .command('status', 'Show connected agents, devices, and active sessions')
46
+ .option('--relay <url>', 'Relay URL (default: ws://localhost:4000)')
47
+ .action((opts) => cmdStatus(opts));
48
+ cli
49
+ .command('logs', 'Show recent relay log entries')
50
+ .option('--relay <url>', 'Relay URL (default: http://localhost:4000)')
51
+ .option('--lines <n>', 'Number of lines to show (default: 100)', { default: 100 })
52
+ .action((opts) => cmdLogs(opts));
53
+ cli.help();
54
+ cli.version('0.1.0');
55
+ cli.parse();
56
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface DoctorCheck {
2
+ label: string;
3
+ ok: boolean;
4
+ detail?: string;
5
+ }
6
+ export interface DoctorResult {
7
+ common: DoctorCheck[];
8
+ ios: DoctorCheck[] | null;
9
+ android: DoctorCheck[] | null;
10
+ }
11
+ export declare function runDoctorChecks(): Promise<DoctorResult>;
12
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,GAAG,EAAE,WAAW,EAAE,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,CAAA;CAC9B;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAS7D"}
@@ -0,0 +1,118 @@
1
+ import { execSync } from 'node:child_process';
2
+ export async function runDoctorChecks() {
3
+ const isMac = process.platform === 'darwin';
4
+ const adbPath = resolveAdb();
5
+ return {
6
+ common: [checkNodeVersion()],
7
+ ios: isMac ? [checkXcode(), checkSimctl(), checkBootedSimulator()] : null,
8
+ android: adbPath !== null ? [checkAdb(adbPath), checkBootedAvd()] : null,
9
+ };
10
+ }
11
+ function checkXcode() {
12
+ try {
13
+ const out = execSync('xcodebuild -version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
14
+ const version = out.split('\n')[0]?.replace('Xcode ', '') ?? '';
15
+ return { label: `Xcode ${version}`, ok: true };
16
+ }
17
+ catch {
18
+ return {
19
+ label: 'Xcode',
20
+ ok: false,
21
+ detail: 'Install Xcode from https://developer.apple.com/xcode/ or the Mac App Store.',
22
+ };
23
+ }
24
+ }
25
+ function checkSimctl() {
26
+ try {
27
+ execSync('xcrun simctl list --json', { stdio: 'pipe' });
28
+ return { label: 'xcrun simctl', ok: true };
29
+ }
30
+ catch {
31
+ return {
32
+ label: 'xcrun simctl',
33
+ ok: false,
34
+ detail: 'Run: xcode-select --install',
35
+ };
36
+ }
37
+ }
38
+ function checkBootedSimulator() {
39
+ try {
40
+ const raw = execSync('xcrun simctl list devices --json', { encoding: 'utf8', stdio: 'pipe' });
41
+ const data = JSON.parse(raw);
42
+ const allDevices = Object.values(data.devices).flat();
43
+ const booted = allDevices.find((d) => d.state === 'Booted');
44
+ if (booted) {
45
+ return { label: `Simulator booted: ${booted.name}`, ok: true };
46
+ }
47
+ const available = allDevices.find((d) => d.state === 'Shutdown');
48
+ const hint = available
49
+ ? `Run: tapflow boot "${available.name}"`
50
+ : 'Run: tapflow devices to see available simulators, then: tapflow boot "<name>"';
51
+ return { label: 'Simulator', ok: false, detail: hint };
52
+ }
53
+ catch {
54
+ return { label: 'Simulator', ok: false, detail: 'Could not query simulators. Is Xcode installed?' };
55
+ }
56
+ }
57
+ function checkNodeVersion() {
58
+ const version = process.version;
59
+ const [, major] = version.match(/^v(\d+)/) ?? [];
60
+ const ok = Number(major) >= 20;
61
+ return {
62
+ label: `Node ${version}`,
63
+ ok,
64
+ detail: ok ? undefined : 'Node ≥ 20 required. Install from https://nodejs.org/',
65
+ };
66
+ }
67
+ function resolveAdb() {
68
+ try {
69
+ const path = execSync('which adb', { encoding: 'utf8', stdio: 'pipe' }).trim();
70
+ return path || null;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ function checkAdb(path) {
77
+ return { label: `adb found: ${path}`, ok: true };
78
+ }
79
+ function checkBootedAvd() {
80
+ try {
81
+ const out = execSync('adb devices', { encoding: 'utf8', stdio: 'pipe' });
82
+ const lines = out.trim().split('\n').slice(1).filter(Boolean);
83
+ const emulator = lines.find((l) => l.startsWith('emulator-'));
84
+ if (!emulator) {
85
+ const hint = listAvdHint();
86
+ return {
87
+ label: 'AVD',
88
+ ok: false,
89
+ detail: hint
90
+ ? `No running emulator. Run: emulator @${hint}`
91
+ : 'No running emulator. Start an AVD from Android Studio > Device Manager.',
92
+ };
93
+ }
94
+ const serial = emulator.split('\t')[0]?.trim() ?? '';
95
+ try {
96
+ const avdName = execSync(`adb -s ${serial} emu avd name`, { encoding: 'utf8', stdio: 'pipe' })
97
+ .split('\n')[0]
98
+ ?.trim() ?? serial;
99
+ return { label: `AVD: ${avdName}`, ok: true };
100
+ }
101
+ catch {
102
+ return { label: `AVD: ${serial}`, ok: true };
103
+ }
104
+ }
105
+ catch {
106
+ return { label: 'AVD', ok: false, detail: 'Could not query running emulators.' };
107
+ }
108
+ }
109
+ function listAvdHint() {
110
+ try {
111
+ const out = execSync('emulator -list-avds', { encoding: 'utf8', stdio: 'pipe' }).trim();
112
+ return out.split('\n')[0]?.trim() || null;
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { IOSAgent } from '@tapflowio/ios-agent';
2
+ export declare function resolveAndBootIOSDevice(deviceFilter?: string): Promise<IOSAgent>;
3
+ //# sourceMappingURL=ios-boot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ios-boot.d.ts","sourceRoot":"","sources":["../../src/lib/ios-boot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAG/C,wBAAsB,uBAAuB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuCtF"}
@@ -0,0 +1,44 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { IOSAgent } from '@tapflowio/ios-agent';
3
+ import { banner, createSpinner, step } from './print.js';
4
+ export async function resolveAndBootIOSDevice(deviceFilter) {
5
+ const agent = new IOSAgent();
6
+ const devices = await agent.listDevices();
7
+ if (deviceFilter) {
8
+ const match = devices.find((d) => d.name === deviceFilter || d.id === deviceFilter);
9
+ if (!match) {
10
+ banner('error', 'DEVICE NOT FOUND', [
11
+ `"${deviceFilter}" does not match any simulator.`,
12
+ 'Run `tapflow devices` to see available simulators.',
13
+ ]);
14
+ process.exit(1);
15
+ }
16
+ if (match.status !== 'booted') {
17
+ const spinner = createSpinner(`Booting ${match.name}…`);
18
+ spinner.start();
19
+ execSync(`xcrun simctl boot ${match.id}`, { stdio: 'pipe' });
20
+ spinner.stop(true);
21
+ }
22
+ step(`iOS Simulator: ${match.name}`);
23
+ }
24
+ else {
25
+ const booted = devices.find((d) => d.status === 'booted');
26
+ if (booted) {
27
+ step(`iOS Simulator: ${booted.name}`);
28
+ }
29
+ else {
30
+ const first = devices[0];
31
+ if (!first) {
32
+ banner('error', 'NO SIMULATOR FOUND', ['Create one in Xcode → Window → Devices and Simulators.']);
33
+ process.exit(1);
34
+ }
35
+ const spinner = createSpinner(`Booting ${first.name}…`);
36
+ spinner.start();
37
+ execSync(`xcrun simctl boot ${first.id}`, { stdio: 'pipe' });
38
+ spinner.stop(true);
39
+ step(`iOS Simulator: ${first.name}`);
40
+ }
41
+ }
42
+ return agent;
43
+ }
44
+ //# sourceMappingURL=ios-boot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ios-boot.js","sourceRoot":"","sources":["../../src/lib/ios-boot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAExD,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,YAAqB;IACjE,MAAM,KAAK,GAAG,IAAI,QAAQ,EAAE,CAAA;IAC5B,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,WAAW,EAAE,CAAA;IAEzC,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,EAAE,KAAK,YAAY,CAAC,CAAA;QACnF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,CAAC,OAAO,EAAE,kBAAkB,EAAE;gBAClC,IAAI,YAAY,iCAAiC;gBACjD,oDAAoD;aACrD,CAAC,CAAA;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,KAAK,CAAC,IAAI,GAAG,CAAC,CAAA;YACvD,OAAO,CAAC,KAAK,EAAE,CAAA;YACf,QAAQ,CAAC,qBAAqB,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YAC5D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;QACD,IAAI,CAAC,kBAAkB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;IACtC,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAA;QACzD,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,kBAAkB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;QACvC,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;YACxB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,OAAO,EAAE,oBAAoB,EAAE,CAAC,wDAAwD,CAAC,CAAC,CAAA;gBACjG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;YACD,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,KAAK,CAAC,IAAI,GAAG,CAAC,CAAA;YACvD,OAAO,CAAC,KAAK,EAAE,CAAA;YACf,QAAQ,CAAC,qBAAqB,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YAC5D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAClB,IAAI,CAAC,kBAAkB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function hasAdb(): boolean;
2
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/lib/platform.ts"],"names":[],"mappings":"AAEA,wBAAgB,MAAM,IAAI,OAAO,CAMhC"}
@@ -0,0 +1,10 @@
1
+ import { execSync } from 'node:child_process';
2
+ export function hasAdb() {
3
+ try {
4
+ return execSync('which adb', { encoding: 'utf8', stdio: 'pipe' }).trim().length > 0;
5
+ }
6
+ catch {
7
+ return false;
8
+ }
9
+ }
10
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/lib/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAE7C,MAAM,UAAU,MAAM;IACpB,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAA;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ export declare const R = "\u001B[0m";
2
+ export declare const BOLD = "\u001B[1m";
3
+ export declare const DIM = "\u001B[2m";
4
+ export declare const GREEN = "\u001B[32m";
5
+ export declare const RED = "\u001B[31m";
6
+ export declare const YELLOW = "\u001B[33m";
7
+ export declare function banner(type: 'success' | 'error', title: string, lines?: string[]): void;
8
+ export declare function createSpinner(msg: string): {
9
+ start(): void;
10
+ stop(ok: boolean): void;
11
+ };
12
+ export declare function step(msg: string): void;
13
+ //# sourceMappingURL=print.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print.d.ts","sourceRoot":"","sources":["../../src/lib/print.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,CAAC,cAAY,CAAA;AAC1B,eAAO,MAAM,IAAI,cAAY,CAAA;AAC7B,eAAO,MAAM,GAAG,cAAY,CAAA;AAC5B,eAAO,MAAM,KAAK,eAAa,CAAA;AAC/B,eAAO,MAAM,GAAG,eAAa,CAAA;AAC7B,eAAO,MAAM,MAAM,eAAa,CAAA;AAahC,wBAAgB,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,EAAO,GAAG,IAAI,CAkB3F;AAID,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM;;aAY5B,OAAO;EAMnB;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtC"}