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.
- package/README.md +29 -35
- package/dist/commands/agent-start.d.ts +7 -0
- package/dist/commands/agent-start.d.ts.map +1 -0
- package/dist/commands/agent-start.js +78 -0
- package/dist/commands/agent-start.js.map +1 -0
- package/dist/commands/boot.d.ts +2 -0
- package/dist/commands/boot.d.ts.map +1 -0
- package/dist/commands/boot.js +47 -0
- package/dist/commands/boot.js.map +1 -0
- package/dist/commands/devices.d.ts +2 -0
- package/dist/commands/devices.d.ts.map +1 -0
- package/dist/commands/devices.js +83 -0
- package/dist/commands/devices.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +46 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +64 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/logs.d.ts +5 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +22 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/relay-start.d.ts +5 -0
- package/dist/commands/relay-start.d.ts.map +1 -0
- package/dist/commands/relay-start.js +26 -0
- package/dist/commands/relay-start.js.map +1 -0
- package/dist/commands/reset.d.ts +2 -0
- package/dist/commands/reset.d.ts.map +1 -0
- package/dist/commands/reset.js +49 -0
- package/dist/commands/reset.js.map +1 -0
- package/dist/commands/start.d.ts +6 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +79 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +58 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/doctor.d.ts +13 -0
- package/dist/lib/doctor.d.ts.map +1 -0
- package/dist/lib/doctor.js +119 -0
- package/dist/lib/doctor.js.map +1 -0
- package/dist/lib/ios-boot.d.ts +3 -0
- package/dist/lib/ios-boot.d.ts.map +1 -0
- package/dist/lib/ios-boot.js +44 -0
- package/dist/lib/ios-boot.js.map +1 -0
- package/dist/lib/platform.d.ts +2 -0
- package/dist/lib/platform.d.ts.map +1 -0
- package/dist/lib/platform.js +10 -0
- package/dist/lib/platform.js.map +1 -0
- package/dist/lib/print.d.ts +13 -0
- package/dist/lib/print.d.ts.map +1 -0
- package/dist/lib/print.js +55 -0
- package/dist/lib/print.js.map +1 -0
- package/dist/lib/tapflow-dir.d.ts +3 -0
- package/dist/lib/tapflow-dir.d.ts.map +1 -0
- package/dist/lib/tapflow-dir.js +8 -0
- package/dist/lib/tapflow-dir.js.map +1 -0
- 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
|
-
[](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
|
-
<
|
|
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
|
|
41
|
-
|
|
42
|
-
| Appetize / BrowserStack | Expensive — app data leaves your network
|
|
43
|
-
| Physical devices
|
|
44
|
-
| Xcode / Android Studio
|
|
45
|
-
| **tapflow**
|
|
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)
|
|
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
|
|
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
|
|
108
|
-
|
|
109
|
-
| **Relay server**
|
|
110
|
-
| **iOS Agent**
|
|
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)**
|
|
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
|
|
148
|
-
|
|
149
|
-
| `tapflow start`
|
|
150
|
-
| `tapflow relay start`
|
|
151
|
-
| `tapflow agent start --relay <url>` | Start agent and connect to a relay
|
|
152
|
-
| `tapflow init`
|
|
153
|
-
| `tapflow doctor`
|
|
154
|
-
| `tapflow devices`
|
|
155
|
-
| `tapflow boot <name\|udid>`
|
|
156
|
-
| `tapflow status`
|
|
157
|
-
| `tapflow reset`
|
|
158
|
-
| `tapflow logs`
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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":"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"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/commands/reset.ts"],"names":[],"mappings":"AAKA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA8C9C"}
|