tapflow 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (66) hide show
  1. package/README.md +29 -35
  2. package/dist/commands/agent-start.d.ts +7 -0
  3. package/dist/commands/agent-start.d.ts.map +1 -0
  4. package/dist/commands/agent-start.js +78 -0
  5. package/dist/commands/agent-start.js.map +1 -0
  6. package/dist/commands/boot.d.ts +2 -0
  7. package/dist/commands/boot.d.ts.map +1 -0
  8. package/dist/commands/boot.js +47 -0
  9. package/dist/commands/boot.js.map +1 -0
  10. package/dist/commands/devices.d.ts +2 -0
  11. package/dist/commands/devices.d.ts.map +1 -0
  12. package/dist/commands/devices.js +83 -0
  13. package/dist/commands/devices.js.map +1 -0
  14. package/dist/commands/doctor.d.ts +2 -0
  15. package/dist/commands/doctor.d.ts.map +1 -0
  16. package/dist/commands/doctor.js +46 -0
  17. package/dist/commands/doctor.js.map +1 -0
  18. package/dist/commands/init.d.ts +5 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +64 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/logs.d.ts +5 -0
  23. package/dist/commands/logs.d.ts.map +1 -0
  24. package/dist/commands/logs.js +22 -0
  25. package/dist/commands/logs.js.map +1 -0
  26. package/dist/commands/relay-start.d.ts +5 -0
  27. package/dist/commands/relay-start.d.ts.map +1 -0
  28. package/dist/commands/relay-start.js +26 -0
  29. package/dist/commands/relay-start.js.map +1 -0
  30. package/dist/commands/reset.d.ts +2 -0
  31. package/dist/commands/reset.d.ts.map +1 -0
  32. package/dist/commands/reset.js +49 -0
  33. package/dist/commands/reset.js.map +1 -0
  34. package/dist/commands/start.d.ts +6 -0
  35. package/dist/commands/start.d.ts.map +1 -0
  36. package/dist/commands/start.js +79 -0
  37. package/dist/commands/start.js.map +1 -0
  38. package/dist/commands/status.d.ts +4 -0
  39. package/dist/commands/status.d.ts.map +1 -0
  40. package/dist/commands/status.js +58 -0
  41. package/dist/commands/status.js.map +1 -0
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +70 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/lib/doctor.d.ts +13 -0
  47. package/dist/lib/doctor.d.ts.map +1 -0
  48. package/dist/lib/doctor.js +119 -0
  49. package/dist/lib/doctor.js.map +1 -0
  50. package/dist/lib/ios-boot.d.ts +3 -0
  51. package/dist/lib/ios-boot.d.ts.map +1 -0
  52. package/dist/lib/ios-boot.js +44 -0
  53. package/dist/lib/ios-boot.js.map +1 -0
  54. package/dist/lib/platform.d.ts +2 -0
  55. package/dist/lib/platform.d.ts.map +1 -0
  56. package/dist/lib/platform.js +10 -0
  57. package/dist/lib/platform.js.map +1 -0
  58. package/dist/lib/print.d.ts +13 -0
  59. package/dist/lib/print.d.ts.map +1 -0
  60. package/dist/lib/print.js +55 -0
  61. package/dist/lib/print.js.map +1 -0
  62. package/dist/lib/tapflow-dir.d.ts +3 -0
  63. package/dist/lib/tapflow-dir.d.ts.map +1 -0
  64. package/dist/lib/tapflow-dir.js +8 -0
  65. package/dist/lib/tapflow-dir.js.map +1 -0
  66. package/package.json +7 -5
package/README.md CHANGED
@@ -20,29 +20,23 @@
20
20
 
21
21
  ## Demo
22
22
 
23
- <!--
24
- Demo video placeholder.
25
- When the recording is ready, replace this block with:
26
-
27
- [![tapflow demo](docs/public/demo-thumbnail.png)](https://YOUR_VIDEO_URL)
28
-
29
- Recommended thumbnail size: 1280×720, saved to docs/public/demo-thumbnail.png
30
- -->
31
-
32
23
  <div align="center">
33
- <strong>🎬 Demo video coming soon</strong>
24
+ <a href="https://github.com/user-attachments/assets/01914ed2-f35c-4230-ae01-166ffe6af395" target="_blank" rel="noopener noreferrer">
25
+ <img src="https://raw.githubusercontent.com/jo-duchan/tapflow/main/docs/public/demo-thumbnail.png" alt="tapflow demo — click to play" width="100%" />
26
+ </a>
27
+ <p><em>Click to play</em></p>
34
28
  </div>
35
29
 
36
30
  ---
37
31
 
38
32
  ## Why tapflow?
39
33
 
40
- | Solution | Problem |
41
- |----------|---------|
42
- | Appetize / BrowserStack | Expensive — app data leaves your network |
43
- | Physical devices | Cost, loss, management overhead |
44
- | Xcode / Android Studio | Every QA member needs their own Mac + full toolchain setup |
45
- | **tapflow** | Use the Mac you already own — data stays on-prem, QA team uses a browser |
34
+ | Solution | Problem |
35
+ | ----------------------- | ------------------------------------------------------------------------ |
36
+ | Appetize / BrowserStack | Expensive — app data leaves your network |
37
+ | Physical devices | Cost, loss, management overhead |
38
+ | Xcode / Android Studio | Every QA member needs their own Mac + full toolchain setup |
39
+ | **tapflow** | Use the Mac you already own — data stays on-prem, QA team uses a browser |
46
40
 
47
41
  ## Features
48
42
 
@@ -59,11 +53,11 @@
59
53
  ## How it works
60
54
 
61
55
  ```
62
- Browser (QA team) ←—WebSocket—→ Relay Server ←—WebSocket (outbound)—→ Mac Agent
56
+ Browser (QA team) WebSocket Relay Server WebSocket (outbound) Mac Agent
63
57
  (Linux / Mac) (iOS · Android)
64
58
  ```
65
59
 
66
- 1. The **Mac Agent** connects *outbound* to the relay — no inbound firewall rules needed.
60
+ 1. The **Mac Agent** connects _outbound_ to the relay — no inbound firewall rules needed.
67
61
  2. QA opens the **dashboard** in any browser and sees all available devices.
68
62
  3. Touch events are forwarded in real time; the screen streams back to the browser.
69
63
  4. The **relay** also serves the dashboard SPA on the same port — no separate web server needed.
@@ -104,12 +98,12 @@ Navigate to `http://localhost:4000` and sign in with the account you just create
104
98
 
105
99
  ## Requirements
106
100
 
107
- | Component | Requirements |
108
- |-----------|-------------|
109
- | **Relay server** | Node.js ≥ 20, any OS (Linux/macOS), ~512 MB RAM |
110
- | **iOS Agent** | macOS, Xcode with iOS Simulator Runtime, Node.js ≥ 20 |
101
+ | Component | Requirements |
102
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
103
+ | **Relay server** | Node.js ≥ 20, any OS (Linux/macOS), ~512 MB RAM |
104
+ | **iOS Agent** | macOS, Xcode with iOS Simulator Runtime, Node.js ≥ 20 |
111
105
  | **Android Agent** | macOS, Android SDK (`adb` in `$PATH` or `$ANDROID_HOME` set), AVD with `google_apis/arm64-v8a` (android-34), Node.js ≥ 20 |
112
- | **Browser (QA)** | Any modern browser — Chrome, Firefox, Safari, Edge |
106
+ | **Browser (QA)** | Any modern browser — Chrome, Firefox, Safari, Edge |
113
107
 
114
108
  ## Self-Hosting
115
109
 
@@ -144,18 +138,18 @@ tapflow agent start --relay wss://your-relay-url
144
138
 
145
139
  ## CLI Reference
146
140
 
147
- | Command | Description |
148
- |---------|-------------|
149
- | `tapflow start` | Start relay + agent together (local mode) |
150
- | `tapflow relay start` | Start relay only |
151
- | `tapflow agent start --relay <url>` | Start agent and connect to a relay |
152
- | `tapflow init` | Create the first admin account |
153
- | `tapflow doctor` | Diagnose environment (Node, Xcode, adb…) |
154
- | `tapflow devices` | List available simulators and emulators |
155
- | `tapflow boot <name\|udid>` | Boot a simulator or emulator |
156
- | `tapflow status` | Show connected agents, devices, active sessions |
157
- | `tapflow reset` | Shut down all simulators and emulators |
158
- | `tapflow logs` | Show recent relay log entries |
141
+ | Command | Description |
142
+ | ----------------------------------- | ----------------------------------------------- |
143
+ | `tapflow start` | Start relay + agent together (local mode) |
144
+ | `tapflow relay start` | Start relay only |
145
+ | `tapflow agent start --relay <url>` | Start agent and connect to a relay |
146
+ | `tapflow init` | Create the first admin account |
147
+ | `tapflow doctor` | Diagnose environment (Node, Xcode, adb…) |
148
+ | `tapflow devices` | List available simulators and emulators |
149
+ | `tapflow boot <name\|udid>` | Boot a simulator or emulator |
150
+ | `tapflow status` | Show connected agents, devices, active sessions |
151
+ | `tapflow reset` | Shut down all simulators and emulators |
152
+ | `tapflow logs` | Show recent relay log entries |
159
153
 
160
154
  Full reference → [CLI docs](https://jo-duchan.github.io/tapflow/reference/cli)
161
155
 
@@ -0,0 +1,7 @@
1
+ export interface AgentStartOptions {
2
+ device?: string;
3
+ relay?: string;
4
+ platform?: 'ios' | 'android' | 'all';
5
+ }
6
+ export declare function cmdAgentStart(opts: AgentStartOptions): Promise<void>;
7
+ //# sourceMappingURL=agent-start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-start.d.ts","sourceRoot":"","sources":["../../src/commands/agent-start.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAA;CACrC;AAUD,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqE1E"}
@@ -0,0 +1,78 @@
1
+ import { z } from 'zod';
2
+ import { AndroidAgent } from '@tapflowio/android-agent';
3
+ import { banner, createSpinner } from '../lib/print.js';
4
+ import { hasAdb } from '../lib/platform.js';
5
+ import { resolveAndBootIOSDevice } from '../lib/ios-boot.js';
6
+ const DEFAULT_RELAY = 'ws://localhost:4000';
7
+ const relayUrlSchema = z
8
+ .string()
9
+ .refine((v) => v.startsWith('ws://') || v.startsWith('wss://'), {
10
+ message: '--relay must start with ws:// or wss://',
11
+ });
12
+ export async function cmdAgentStart(opts) {
13
+ const isMac = process.platform === 'darwin';
14
+ const rawRelay = opts.relay ?? DEFAULT_RELAY;
15
+ const relayResult = relayUrlSchema.safeParse(rawRelay);
16
+ if (!relayResult.success) {
17
+ banner('error', 'INVALID CONFIG', [relayResult.error.issues[0].message]);
18
+ process.exit(1);
19
+ }
20
+ const relayUrl = relayResult.data;
21
+ const explicit = opts.platform;
22
+ const runIOS = explicit === 'ios' || explicit === 'all' || (!explicit && isMac);
23
+ const runAndroid = explicit === 'android' || explicit === 'all' || (!explicit && hasAdb());
24
+ if (!runIOS && !runAndroid) {
25
+ banner('error', 'NO PLATFORM AVAILABLE', [
26
+ 'No iOS simulator or Android adb found.',
27
+ 'Run `tapflow doctor` to diagnose.',
28
+ ]);
29
+ process.exit(1);
30
+ }
31
+ const agents = [];
32
+ // ── iOS Agent ─────────────────────────────────────────────────────────────
33
+ if (runIOS) {
34
+ const iosAgent = await resolveAndBootIOSDevice(opts.device);
35
+ const iosSpinner = createSpinner('Connecting iOS agent…');
36
+ iosSpinner.start();
37
+ try {
38
+ await iosAgent.connect(relayUrl);
39
+ iosSpinner.stop(true);
40
+ agents.push(iosAgent);
41
+ }
42
+ catch (e) {
43
+ iosSpinner.stop(false);
44
+ banner('error', 'IOS CONNECTION FAILED', [e.message]);
45
+ process.exit(1);
46
+ }
47
+ }
48
+ // ── Android Agent ─────────────────────────────────────────────────────────
49
+ if (runAndroid) {
50
+ const androidAgent = new AndroidAgent({ deviceFilter: opts.device });
51
+ const androidSpinner = createSpinner('Connecting Android agent…');
52
+ androidSpinner.start();
53
+ try {
54
+ await androidAgent.connect(relayUrl);
55
+ androidSpinner.stop(true);
56
+ agents.push(androidAgent);
57
+ }
58
+ catch (e) {
59
+ androidSpinner.stop(false);
60
+ if (runIOS && agents.length > 0) {
61
+ console.log(` ⚠ Android: ${e.message}`);
62
+ }
63
+ else {
64
+ banner('error', 'ANDROID CONNECTION FAILED', [e.message]);
65
+ process.exit(1);
66
+ }
67
+ }
68
+ }
69
+ banner('success', 'TAPFLOW AGENT READY', [
70
+ `Relay : ${relayUrl}`,
71
+ 'Press Ctrl+C to stop.',
72
+ ]);
73
+ process.on('SIGINT', () => {
74
+ agents.forEach((a) => a.disconnect());
75
+ process.exit(0);
76
+ });
77
+ }
78
+ //# sourceMappingURL=agent-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-start.js","sourceRoot":"","sources":["../../src/commands/agent-start.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdBoot(nameOrUdid: string): Promise<void>;
2
+ //# sourceMappingURL=boot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../src/commands/boot.ts"],"names":[],"mappings":"AAGA,wBAAsB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C/D"}
@@ -0,0 +1,47 @@
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import { banner, createSpinner, step } from '../lib/print.js';
3
+ export async function cmdBoot(nameOrUdid) {
4
+ // iOS 먼저 탐색
5
+ if (process.platform === 'darwin') {
6
+ try {
7
+ const raw = execSync('xcrun simctl list devices --json', { encoding: 'utf8', stdio: 'pipe' });
8
+ const data = JSON.parse(raw);
9
+ const target = Object.values(data.devices).flat().find((d) => d.udid === nameOrUdid || d.name === nameOrUdid);
10
+ if (target) {
11
+ if (target.state === 'Booted') {
12
+ step(`${target.name} is already booted.`);
13
+ return;
14
+ }
15
+ const spinner = createSpinner(`Booting iOS Simulator: ${target.name}…`);
16
+ spinner.start();
17
+ execSync(`xcrun simctl boot ${target.udid}`, { stdio: 'pipe' });
18
+ spinner.stop(true);
19
+ step(`${target.name} booted.`);
20
+ return;
21
+ }
22
+ }
23
+ catch { /* xcrun unavailable */ }
24
+ }
25
+ // Android AVD 탐색
26
+ try {
27
+ execSync('which emulator', { stdio: 'pipe' });
28
+ const avdList = execSync('emulator -list-avds', { encoding: 'utf8', stdio: 'pipe' })
29
+ .trim().split('\n').map((l) => l.trim()).filter(Boolean);
30
+ const target = avdList.find((avd) => avd === nameOrUdid);
31
+ if (target) {
32
+ // emulator는 실행 후 종료되지 않으므로 detached로 백그라운드 기동
33
+ const child = spawn('emulator', [`@${target}`], { detached: true, stdio: 'ignore' });
34
+ child.unref();
35
+ step(`Android AVD: ${target} is starting in the background.`);
36
+ step('Run `tapflow devices` to check boot status.');
37
+ return;
38
+ }
39
+ }
40
+ catch { /* emulator unavailable */ }
41
+ banner('error', 'DEVICE NOT FOUND', [
42
+ `"${nameOrUdid}" does not match any simulator or AVD.`,
43
+ 'Run `tapflow devices` to see available devices.',
44
+ ]);
45
+ process.exit(1);
46
+ }
47
+ //# sourceMappingURL=boot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot.js","sourceRoot":"","sources":["../../src/commands/boot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE7D,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,UAAkB;IAC9C,YAAY;IACZ,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,QAAQ,CAAC,kCAAkC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7F,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAE1B,CAAA;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CACpD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,CACtD,CAAA;YACD,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC9B,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,qBAAqB,CAAC,CAAA;oBACzC,OAAM;gBACR,CAAC;gBACD,MAAM,OAAO,GAAG,aAAa,CAAC,0BAA0B,MAAM,CAAC,IAAI,GAAG,CAAC,CAAA;gBACvE,OAAO,CAAC,KAAK,EAAE,CAAA;gBACf,QAAQ,CAAC,qBAAqB,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;gBAC/D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAClB,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,UAAU,CAAC,CAAA;gBAC9B,OAAM;YACR,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,uBAAuB,CAAC,CAAC;IACrC,CAAC;IAED,iBAAiB;IACjB,IAAI,CAAC;QACH,QAAQ,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC7C,MAAM,OAAO,GAAG,QAAQ,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;aACjF,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,UAAU,CAAC,CAAA;QACxD,IAAI,MAAM,EAAE,CAAC;YACX,8CAA8C;YAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;YACpF,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,IAAI,CAAC,gBAAgB,MAAM,iCAAiC,CAAC,CAAA;YAC7D,IAAI,CAAC,6CAA6C,CAAC,CAAA;YACnD,OAAM;QACR,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;IAEtC,MAAM,CAAC,OAAO,EAAE,kBAAkB,EAAE;QAClC,IAAI,UAAU,wCAAwC;QACtD,iDAAiD;KAClD,CAAC,CAAA;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdDevices(): void;
2
+ //# sourceMappingURL=devices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["../../src/commands/devices.ts"],"names":[],"mappings":"AASA,wBAAgB,UAAU,IAAI,IAAI,CA8EjC"}
@@ -0,0 +1,83 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { banner } from '../lib/print.js';
3
+ export function cmdDevices() {
4
+ const sections = [];
5
+ // ── iOS ───────────────────────────────────────────────────────────────────
6
+ try {
7
+ const raw = execSync('xcrun simctl list devices --json', { encoding: 'utf8', stdio: 'pipe' });
8
+ const data = JSON.parse(raw);
9
+ const all = Object.values(data.devices).flat().filter((d) => d.udid);
10
+ const booted = all.filter((d) => d.state === 'Booted');
11
+ const available = all.filter((d) => d.state === 'Shutdown');
12
+ const lines = [];
13
+ if (booted.length > 0) {
14
+ lines.push(' Booted:');
15
+ for (const d of booted)
16
+ lines.push(` ● ${d.name} ${d.udid}`);
17
+ }
18
+ if (available.length > 0) {
19
+ lines.push(' Available:');
20
+ for (const d of available)
21
+ lines.push(` ○ ${d.name} ${d.udid}`);
22
+ }
23
+ if (lines.length > 0)
24
+ sections.push('iOS Simulators:\n' + lines.join('\n'));
25
+ }
26
+ catch {
27
+ // xcrun not available — skip iOS section
28
+ }
29
+ // ── Android ───────────────────────────────────────────────────────────────
30
+ try {
31
+ execSync('which adb', { stdio: 'pipe' });
32
+ const runningLines = [];
33
+ try {
34
+ const adbOut = execSync('adb devices', { encoding: 'utf8', stdio: 'pipe' });
35
+ const running = adbOut.trim().split('\n').slice(1).filter((l) => l.startsWith('emulator-'));
36
+ for (const line of running) {
37
+ const serial = line.split('\t')[0]?.trim() ?? '';
38
+ try {
39
+ const name = execSync(`adb -s ${serial} emu avd name`, { encoding: 'utf8', stdio: 'pipe' })
40
+ .split('\n')[0]?.trim() ?? serial;
41
+ runningLines.push(` ● ${name} ${serial}`);
42
+ }
43
+ catch {
44
+ runningLines.push(` ● ${serial}`);
45
+ }
46
+ }
47
+ }
48
+ catch { /* adb devices failed */ }
49
+ const availableLines = [];
50
+ try {
51
+ const avdOut = execSync('emulator -list-avds', { encoding: 'utf8', stdio: 'pipe' }).trim();
52
+ const allAvds = avdOut ? avdOut.split('\n').map((l) => l.trim()).filter(Boolean) : [];
53
+ const runningNames = runningLines.map((l) => l.split(' ')[0]?.replace(' ● ', '') ?? '');
54
+ for (const avd of allAvds) {
55
+ if (!runningNames.includes(avd))
56
+ availableLines.push(` ○ ${avd}`);
57
+ }
58
+ }
59
+ catch { /* emulator not in PATH */ }
60
+ const lines = [];
61
+ if (runningLines.length > 0) {
62
+ lines.push(' Running:');
63
+ lines.push(...runningLines);
64
+ }
65
+ if (availableLines.length > 0) {
66
+ lines.push(' Available:');
67
+ lines.push(...availableLines);
68
+ }
69
+ if (lines.length > 0)
70
+ sections.push('Android AVDs:\n' + lines.join('\n'));
71
+ }
72
+ catch {
73
+ // adb not in PATH — skip Android section
74
+ }
75
+ if (sections.length === 0) {
76
+ banner('error', 'NO DEVICES FOUND', ['Run `tapflow doctor` to diagnose your environment.']);
77
+ return;
78
+ }
79
+ console.log();
80
+ console.log(sections.join('\n\n'));
81
+ console.log();
82
+ }
83
+ //# sourceMappingURL=devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/commands/devices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAQxC,MAAM,UAAU,UAAU;IACxB,MAAM,QAAQ,GAAa,EAAE,CAAA;IAE7B,6EAA6E;IAC7E,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,kCAAkC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC7F,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6C,CAAA;QACxE,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAEpE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAA;QACtD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAA;QAE3D,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACvB,KAAK,MAAM,CAAC,IAAI,MAAM;gBAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;QAClE,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YAC1B,KAAK,MAAM,CAAC,IAAI,SAAS;gBAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;QACrE,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;IAED,6EAA6E;IAC7E,IAAI,CAAC;QACH,QAAQ,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAExC,MAAM,YAAY,GAAa,EAAE,CAAA;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAA;YAC3F,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;gBAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;gBAChD,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,MAAM,eAAe,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;yBACxF,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,MAAM,CAAA;oBACnC,YAAY,CAAC,IAAI,CAAC,SAAS,IAAI,KAAK,MAAM,EAAE,CAAC,CAAA;gBAC/C,CAAC;gBAAC,MAAM,CAAC;oBACP,YAAY,CAAC,IAAI,CAAC,SAAS,MAAM,EAAE,CAAC,CAAA;gBACtC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,wBAAwB,CAAC,CAAC;QAEpC,MAAM,cAAc,GAAa,EAAE,CAAA;QACnC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,CAAC,qBAAqB,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YAC1F,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YACrF,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;YAC3F,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC;oBAAE,cAAc,CAAC,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC,CAAA;YACtE,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;QAEtC,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAA;QAC7B,CAAC;QACD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YAC1B,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,OAAO,EAAE,kBAAkB,EAAE,CAAC,oDAAoD,CAAC,CAAC,CAAA;QAC3F,OAAM;IACR,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;IAClC,OAAO,CAAC,GAAG,EAAE,CAAA;AACf,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdDoctor(): Promise<void>;
2
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAoBA,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAyB/C"}
@@ -0,0 +1,46 @@
1
+ import { runDoctorChecks } from '../lib/doctor.js';
2
+ import { banner, step, GREEN, RED, YELLOW, BOLD, DIM, R } from '../lib/print.js';
3
+ function printChecks(checks) {
4
+ let hasFailure = false;
5
+ for (const check of checks) {
6
+ if (check.ok) {
7
+ console.log(` ${GREEN}✓${R} ${check.label}`);
8
+ }
9
+ else if (check.warn) {
10
+ console.log(` ${YELLOW}⚠${R} ${check.label}`);
11
+ if (check.detail)
12
+ console.log(`${DIM} → ${check.detail}${R}`);
13
+ }
14
+ else {
15
+ console.log(` ${RED}✗${R} ${check.label}`);
16
+ if (check.detail)
17
+ console.log(`${DIM} → ${check.detail}${R}`);
18
+ hasFailure = true;
19
+ }
20
+ }
21
+ return hasFailure;
22
+ }
23
+ export async function cmdDoctor() {
24
+ console.log('\ntapflow doctor\n');
25
+ const result = await runDoctorChecks();
26
+ let hasFailure = false;
27
+ hasFailure = printChecks(result.common) || hasFailure;
28
+ if (result.ios) {
29
+ console.log(`\n ${BOLD}iOS${R}`);
30
+ hasFailure = printChecks(result.ios) || hasFailure;
31
+ }
32
+ if (result.android) {
33
+ console.log(`\n ${BOLD}Android${R}`);
34
+ hasFailure = printChecks(result.android) || hasFailure;
35
+ }
36
+ console.log();
37
+ if (hasFailure) {
38
+ banner('error', 'SOME CHECKS FAILED', ['Fix the issues above before running `tapflow start`.']);
39
+ process.exit(1);
40
+ }
41
+ else {
42
+ step('All checks passed.');
43
+ console.log();
44
+ }
45
+ }
46
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAoB,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AAEhF,SAAS,WAAW,CAAC,MAAqB;IACxC,IAAI,UAAU,GAAG,KAAK,CAAA;IACtB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QAChD,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;YAC/C,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;QACrE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;YAC5C,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,YAAY,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;YACnE,UAAU,GAAG,IAAI,CAAA;QACnB,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IACjC,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAA;IACtC,IAAI,UAAU,GAAG,KAAK,CAAA;IAEtB,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,UAAU,CAAA;IAErD,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC,CAAA;QACjC,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,UAAU,CAAA;IACpD,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,UAAU,CAAC,EAAE,CAAC,CAAA;QACrC,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,UAAU,CAAA;IACxD,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAA;IACb,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,OAAO,EAAE,oBAAoB,EAAE,CAAC,sDAAsD,CAAC,CAAC,CAAA;QAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,oBAAoB,CAAC,CAAA;QAC1B,OAAO,CAAC,GAAG,EAAE,CAAA;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface InitOptions {
2
+ relay?: string;
3
+ }
4
+ export declare function cmdInit(opts: InitOptions): Promise<void>;
5
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgE9D"}
@@ -0,0 +1,64 @@
1
+ import * as readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import { createSpinner, banner, step } from '../lib/print.js';
4
+ export async function cmdInit(opts) {
5
+ const baseUrl = (opts.relay ?? 'http://localhost:4000').replace(/^wss?:\/\//, 'http://');
6
+ const rl = readline.createInterface({ input, output });
7
+ const email = (await rl.question(' ? Admin email: ')).trim();
8
+ // Node.js has no native password-input API. Suppress echo via readline's internal
9
+ // _writeToOutput hook — a widely used convention that remains stable across versions.
10
+ process.stdout.write(' ? Password: ');
11
+ const rlAny = rl;
12
+ const origWriteToOutput = rlAny._writeToOutput;
13
+ if (origWriteToOutput)
14
+ rlAny._writeToOutput = () => { };
15
+ const password = (await rl.question('')).trim();
16
+ if (origWriteToOutput)
17
+ rlAny._writeToOutput = origWriteToOutput;
18
+ process.stdout.write('\n');
19
+ rl.close();
20
+ if (!email) {
21
+ banner('error', 'INVALID INPUT', ['Email is required.']);
22
+ process.exit(1);
23
+ }
24
+ if (!password || password.length < 8) {
25
+ banner('error', 'INVALID INPUT', ['Password must be at least 8 characters.']);
26
+ process.exit(1);
27
+ }
28
+ const spinner = createSpinner('Creating admin account...');
29
+ spinner.start();
30
+ let res = null;
31
+ try {
32
+ res = await fetch(`${baseUrl}/api/v1/auth/init`, {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ email, password }),
36
+ });
37
+ }
38
+ catch {
39
+ spinner.stop(false);
40
+ banner('error', 'Could not connect to relay', [
41
+ `Relay not found at ${baseUrl}`,
42
+ 'Run tapflow start first, then tapflow init.',
43
+ ]);
44
+ process.exit(1);
45
+ }
46
+ if (!res.ok) {
47
+ spinner.stop(false);
48
+ const data = await res.json();
49
+ if (res.status === 403) {
50
+ banner('error', 'Already initialized', [
51
+ 'An admin account already exists.',
52
+ 'Use Settings → Team in the dashboard to manage users.',
53
+ ]);
54
+ }
55
+ else {
56
+ banner('error', 'Failed to create admin', [data.error ?? `HTTP ${res.status}`]);
57
+ }
58
+ process.exit(1);
59
+ }
60
+ spinner.stop(true);
61
+ banner('success', 'Admin account created', [`Email: ${email}`]);
62
+ step(`Open ${baseUrl} to sign in`);
63
+ }
64
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function cmdLogs(opts: {
2
+ relay?: string;
3
+ lines?: number;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAEA,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBrF"}
@@ -0,0 +1,22 @@
1
+ import { DIM, RED, R } from '../lib/print.js';
2
+ export async function cmdLogs(opts) {
3
+ const base = (opts.relay ?? 'http://localhost:4000').replace(/^ws/, 'http');
4
+ const lines = opts.lines ?? 100;
5
+ const url = `${base}/api/v1/logs?lines=${lines}`;
6
+ const res = await fetch(url).catch(() => null);
7
+ if (!res || !res.ok) {
8
+ console.error(`\n ${RED}✗${R} Could not reach relay at ${base}\n Make sure tapflow is running.\n`);
9
+ process.exit(1);
10
+ }
11
+ const entries = await res.json();
12
+ if (entries.length === 0) {
13
+ console.log(`\n ${DIM}No log entries yet.${R}\n`);
14
+ return;
15
+ }
16
+ console.log();
17
+ for (const line of entries) {
18
+ console.log(` ${DIM}${line}${R}`);
19
+ }
20
+ console.log();
21
+ }
22
+ //# sourceMappingURL=logs.js.map
@@ -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":"AAKA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAMD,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB1E"}
@@ -0,0 +1,26 @@
1
+ import path from 'path';
2
+ import { z } from 'zod';
3
+ import { RelayServer, initDb, config } from '@tapflowio/relay';
4
+ import { banner, step } from '../lib/print.js';
5
+ const DEFAULT_PORT = 4000;
6
+ const portSchema = z.number().int().min(1).max(65535, 'port must be between 1 and 65535');
7
+ export async function cmdRelayStart(opts) {
8
+ const rawPort = opts.port ?? DEFAULT_PORT;
9
+ const portResult = portSchema.safeParse(rawPort);
10
+ if (!portResult.success) {
11
+ banner('error', 'INVALID CONFIG', [`--port: ${portResult.error.issues[0].message}`]);
12
+ process.exit(1);
13
+ }
14
+ const port = portResult.data;
15
+ initDb(path.join(config.server.dataDir, 'tapflow.db'));
16
+ const server = new RelayServer({ port });
17
+ await server.start();
18
+ step(`Relay started on ws://localhost:${port}`);
19
+ banner('success', 'TAPFLOW RELAY READY', [
20
+ `Relay : http://localhost:${port}`,
21
+ `Connect Mac agents: tapflow start --relay ws://<host>:${port}`,
22
+ 'Press Ctrl+C to stop.',
23
+ ]);
24
+ process.on('SIGINT', () => process.exit(0));
25
+ }
26
+ //# 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,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9D,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,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAA;IACtD,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"}