@dokobot/cli 1.0.2 → 1.1.0
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 +79 -43
- package/dist/src/cli.js +8 -214
- package/dist/src/cli.js.map +1 -1
- package/dist/src/command-poller.d.ts +5 -1
- package/dist/src/command-poller.js +9 -1
- package/dist/src/command-poller.js.map +1 -1
- package/dist/src/crawl.d.ts +2 -0
- package/dist/src/crawl.js +326 -0
- package/dist/src/crawl.js.map +1 -0
- package/dist/src/doko.d.ts +2 -0
- package/dist/src/doko.js +223 -0
- package/dist/src/doko.js.map +1 -0
- package/dist/src/executor.d.ts +14 -1
- package/dist/src/executor.js +85 -2
- package/dist/src/executor.js.map +1 -1
- package/dist/src/server-client.d.ts +71 -0
- package/dist/src/server-client.js +74 -0
- package/dist/src/server-client.js.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,79 +1,121 @@
|
|
|
1
1
|
# @dokobot/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Extract structured data from any web page using AI. Powered by [Dokobot](https://dokobot.ai).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g @dokobot/cli
|
|
9
|
+
```
|
|
4
10
|
|
|
5
11
|
## Quick Start
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
|
-
|
|
14
|
+
# Extract data from a web page
|
|
15
|
+
dokobot crawl create \
|
|
16
|
+
--url https://news.ycombinator.com \
|
|
17
|
+
--name "HN Front Page" \
|
|
18
|
+
--goal "Extract article titles, points, and comment counts" \
|
|
19
|
+
--fields '[{"name":"title","type":"string","required":true},{"name":"points","type":"number","required":true}]' \
|
|
20
|
+
--format csv
|
|
21
|
+
|
|
22
|
+
# Check task progress
|
|
23
|
+
dokobot crawl status <taskId>
|
|
24
|
+
|
|
25
|
+
# Download results
|
|
26
|
+
dokobot crawl result <taskId> --output data.csv
|
|
9
27
|
```
|
|
10
28
|
|
|
11
|
-
|
|
29
|
+
Requires a connected doko (browser extension or `dokobot doko connect`). See [Doko](#doko) below.
|
|
12
30
|
|
|
13
|
-
##
|
|
31
|
+
## Commands
|
|
14
32
|
|
|
15
|
-
|
|
16
|
-
- **Chrome** with remote debugging enabled:
|
|
17
|
-
1. Open `chrome://inspect/#remote-debugging`
|
|
18
|
-
2. Check **"Allow remote debugging for this browser instance"**
|
|
33
|
+
### `crawl`
|
|
19
34
|
|
|
20
|
-
|
|
35
|
+
Extract structured data from web pages using AI.
|
|
21
36
|
|
|
22
|
-
|
|
23
|
-
# Run directly (no install needed)
|
|
24
|
-
npx @dokobot/cli connect
|
|
37
|
+
#### `crawl create`
|
|
25
38
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
Create a new crawl task.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
dokobot crawl create \
|
|
43
|
+
--url https://example.com \
|
|
44
|
+
--name "My Task" \
|
|
45
|
+
--goal "Extract product names and prices" \
|
|
46
|
+
--fields '[{"name":"product","type":"string","required":true},{"name":"price","type":"number","required":true}]' \
|
|
47
|
+
--format csv
|
|
29
48
|
```
|
|
30
49
|
|
|
31
|
-
|
|
50
|
+
| Option | Description |
|
|
51
|
+
|--------|-------------|
|
|
52
|
+
| `--url <urls...>` | Target URLs (multiple allowed) |
|
|
53
|
+
| `--name <text>` | Task name |
|
|
54
|
+
| `--goal <text>` | What to extract |
|
|
55
|
+
| `--fields <json>` | Extraction field definitions |
|
|
56
|
+
| `--format <type>` | Output format: `csv`, `json`, or `markdown` (default: `csv`) |
|
|
57
|
+
| `--max-items <n>` | Max items per page (default: `50`) |
|
|
58
|
+
| `--device <id>` | Specific doko device ID |
|
|
32
59
|
|
|
33
|
-
|
|
60
|
+
All options can also be provided interactively when omitted.
|
|
34
61
|
|
|
35
|
-
|
|
62
|
+
#### `crawl list`
|
|
36
63
|
|
|
37
64
|
```bash
|
|
38
|
-
dokobot
|
|
39
|
-
dokobot
|
|
40
|
-
dokobot connect --browser-url http://127.0.0.1:9222 # Custom Chrome instance
|
|
41
|
-
dokobot connect --device-id <id> # Reconnect an existing doko
|
|
65
|
+
dokobot crawl list # List recent tasks
|
|
66
|
+
dokobot crawl list --status running --limit 5
|
|
42
67
|
```
|
|
43
68
|
|
|
44
|
-
|
|
69
|
+
#### `crawl status <taskId>`
|
|
70
|
+
|
|
71
|
+
Show task details, progress, and per-page extraction results.
|
|
45
72
|
|
|
46
|
-
|
|
73
|
+
#### `crawl result <taskId>`
|
|
74
|
+
|
|
75
|
+
Download extracted data.
|
|
47
76
|
|
|
48
77
|
```bash
|
|
49
|
-
dokobot
|
|
78
|
+
dokobot crawl result <taskId> # Print to stdout
|
|
79
|
+
dokobot crawl result <taskId> --output data.csv # Save to file
|
|
50
80
|
```
|
|
51
81
|
|
|
52
|
-
|
|
82
|
+
#### `crawl cancel <taskId>`
|
|
53
83
|
|
|
54
|
-
|
|
84
|
+
Cancel a running task.
|
|
55
85
|
|
|
56
|
-
|
|
57
|
-
dokobot remove <device-id>
|
|
58
|
-
```
|
|
86
|
+
### `doko`
|
|
59
87
|
|
|
60
|
-
|
|
88
|
+
Manage doko devices — connect your Chrome browser so AI agents can access real web pages, including SPAs, login-gated sites, and dynamic content.
|
|
61
89
|
|
|
62
|
-
|
|
90
|
+
#### `doko connect`
|
|
63
91
|
|
|
64
92
|
```bash
|
|
65
|
-
dokobot
|
|
93
|
+
dokobot doko connect # Interactive setup
|
|
94
|
+
dokobot doko connect --name "Work Chrome" # Name your doko
|
|
95
|
+
dokobot doko connect --browser-url http://127.0.0.1:9222 # Custom Chrome instance
|
|
96
|
+
dokobot doko connect --device-id <id> # Reconnect an existing doko
|
|
66
97
|
```
|
|
67
98
|
|
|
68
|
-
|
|
99
|
+
**Prerequisites:** Chrome with remote debugging enabled:
|
|
100
|
+
1. Open `chrome://inspect/#remote-debugging`
|
|
101
|
+
2. Check **"Allow remote debugging for this browser instance"**
|
|
69
102
|
|
|
70
|
-
|
|
103
|
+
#### `doko list`
|
|
104
|
+
|
|
105
|
+
List all registered doko devices on this machine.
|
|
106
|
+
|
|
107
|
+
#### `doko remove <id>`
|
|
108
|
+
|
|
109
|
+
Remove a registered doko device.
|
|
110
|
+
|
|
111
|
+
### Other commands
|
|
71
112
|
|
|
72
113
|
```bash
|
|
73
|
-
dokobot
|
|
114
|
+
dokobot config # Configure API key and server URL
|
|
115
|
+
dokobot update # Check for updates
|
|
74
116
|
```
|
|
75
117
|
|
|
76
|
-
## Options
|
|
118
|
+
## Global Options
|
|
77
119
|
|
|
78
120
|
| Option | Description |
|
|
79
121
|
|--------|-------------|
|
|
@@ -81,12 +123,6 @@ dokobot update
|
|
|
81
123
|
| `--server <url>` | Server URL (default: `https://dokobot.ai`) |
|
|
82
124
|
| `--verbose` | Enable verbose logging |
|
|
83
125
|
|
|
84
|
-
## How It Works
|
|
85
|
-
|
|
86
|
-
1. The CLI connects to your local Chrome via [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
|
|
87
|
-
2. It registers your browser as a **doko** on the Dokobot server
|
|
88
|
-
3. AI agents can then send commands to your browser — navigating pages, clicking elements, extracting content, and more
|
|
89
|
-
|
|
90
126
|
## License
|
|
91
127
|
|
|
92
128
|
MIT
|
package/dist/src/cli.js
CHANGED
|
@@ -1,61 +1,19 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { input
|
|
3
|
-
import {
|
|
4
|
-
import { ServerClient } from './server-client.js';
|
|
5
|
-
import { CommandPoller } from './command-poller.js';
|
|
6
|
-
import { Heartbeat } from './heartbeat.js';
|
|
7
|
-
import { Executor } from './executor.js';
|
|
8
|
-
import { getApiKey, getServerUrl, loadConfig, saveConfig, saveDevice, listDevices, removeDevice, findDeviceByName, } from './config.js';
|
|
9
|
-
import { setLogLevel } from './logger.js';
|
|
2
|
+
import { input } from '@inquirer/prompts';
|
|
3
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
10
4
|
import { getCurrentVersion } from './version.js';
|
|
11
|
-
import {
|
|
5
|
+
import { checkForUpdateNow, formatUpdateNotification } from './update-checker.js';
|
|
6
|
+
import { registerCrawlCommands } from './crawl.js';
|
|
7
|
+
import { registerDokoCommands } from './doko.js';
|
|
12
8
|
export function createProgram() {
|
|
13
9
|
const program = new Command();
|
|
14
10
|
program
|
|
15
11
|
.name('dokobot')
|
|
16
|
-
.description('Dokobot CLI -
|
|
12
|
+
.description('Dokobot CLI - Extract structured data from any web page')
|
|
17
13
|
.version(getCurrentVersion())
|
|
18
14
|
.option('--api-key <key>', 'API key (or set DOKO_API_KEY env)')
|
|
19
15
|
.option('--server <url>', 'Server URL (default: https://dokobot.ai)')
|
|
20
16
|
.option('--verbose', 'Enable verbose logging');
|
|
21
|
-
program
|
|
22
|
-
.command('connect')
|
|
23
|
-
.description('Connect a Chrome browser via Chrome DevTools MCP')
|
|
24
|
-
.option('--name <name>', 'Name for the doko')
|
|
25
|
-
.option('--device-id <id>', 'Reuse an existing device ID')
|
|
26
|
-
.option('--browser-url <url>', 'Connect to a running Chrome instance (e.g. http://127.0.0.1:9222)')
|
|
27
|
-
.option('--mcp-args <args>', 'Extra arguments for chrome-devtools-mcp (comma-separated)')
|
|
28
|
-
.action(async (options) => {
|
|
29
|
-
const globalOpts = program.opts();
|
|
30
|
-
if (globalOpts.verbose)
|
|
31
|
-
setLogLevel('debug');
|
|
32
|
-
await connectCommand(globalOpts, options);
|
|
33
|
-
});
|
|
34
|
-
program
|
|
35
|
-
.command('list')
|
|
36
|
-
.description('List registered devices')
|
|
37
|
-
.action(() => {
|
|
38
|
-
const devices = listDevices();
|
|
39
|
-
if (devices.length === 0) {
|
|
40
|
-
console.log('No registered devices.');
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
console.log('Registered devices:');
|
|
44
|
-
for (const d of devices) {
|
|
45
|
-
console.log(` ${d.name} (${d.id}) [${d.type}]`);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
program
|
|
49
|
-
.command('remove <id>')
|
|
50
|
-
.description('Remove a registered device')
|
|
51
|
-
.action((id) => {
|
|
52
|
-
if (removeDevice(id)) {
|
|
53
|
-
console.log(`Removed device: ${id}`);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
console.log(`Device not found: ${id}`);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
17
|
program
|
|
60
18
|
.command('config')
|
|
61
19
|
.description('Configure API key and server URL')
|
|
@@ -85,172 +43,8 @@ export function createProgram() {
|
|
|
85
43
|
console.log(`You're on the latest version (${getCurrentVersion()}).`);
|
|
86
44
|
}
|
|
87
45
|
});
|
|
46
|
+
registerCrawlCommands(program);
|
|
47
|
+
registerDokoCommands(program);
|
|
88
48
|
return program;
|
|
89
49
|
}
|
|
90
|
-
const G = '\x1b[32m';
|
|
91
|
-
const W = '\x1b[97;1m';
|
|
92
|
-
const B = '\x1b[1m';
|
|
93
|
-
const D = '\x1b[2m';
|
|
94
|
-
const R = '\x1b[0m';
|
|
95
|
-
const BANNER = `
|
|
96
|
-
${G}▄████████▄${R}
|
|
97
|
-
${G}███${W}██${R}${G}█${W}██${R}${G}███${R} ${B}Dokobot CLI${R} ${D}v${getCurrentVersion()}${R}
|
|
98
|
-
${G}███████████${R} ${D}Open the full web to AI agent${R}
|
|
99
|
-
${G}▀████████▀${R}
|
|
100
|
-
`;
|
|
101
|
-
async function connectCommand(globalOpts, options) {
|
|
102
|
-
console.log(BANNER);
|
|
103
|
-
const serverUrl = getServerUrl(globalOpts.server);
|
|
104
|
-
let deviceId = options.deviceId;
|
|
105
|
-
let deviceName = options.name;
|
|
106
|
-
let apiKey = getApiKey(globalOpts.apiKey);
|
|
107
|
-
if (!apiKey) {
|
|
108
|
-
if (deviceId) {
|
|
109
|
-
const guideUrl = `${serverUrl}/guide?source=cli&deviceId=${deviceId}`;
|
|
110
|
-
console.log(` No API key found. Opening setup guide...`);
|
|
111
|
-
console.log(` ${D}${guideUrl}${R}`);
|
|
112
|
-
console.log('');
|
|
113
|
-
const { exec } = await import('child_process');
|
|
114
|
-
exec(`open "${guideUrl}"`);
|
|
115
|
-
apiKey = await input({ message: 'Paste your API key:' });
|
|
116
|
-
if (!apiKey) {
|
|
117
|
-
console.error('API key is required.');
|
|
118
|
-
process.exit(1);
|
|
119
|
-
}
|
|
120
|
-
const config = loadConfig();
|
|
121
|
-
saveConfig({ ...config, apiKey });
|
|
122
|
-
console.log(' ✓ API key saved');
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
const { randomUUID } = await import('crypto');
|
|
126
|
-
const preferredId = randomUUID();
|
|
127
|
-
const guideUrl = `${serverUrl}/guide?source=cli&deviceId=${preferredId}`;
|
|
128
|
-
console.log(` No API key found. Opening setup guide...`);
|
|
129
|
-
console.log(` ${D}${guideUrl}${R}`);
|
|
130
|
-
console.log('');
|
|
131
|
-
console.log(' Follow the guide to complete the setup.');
|
|
132
|
-
const { exec } = await import('child_process');
|
|
133
|
-
exec(`open "${guideUrl}"`);
|
|
134
|
-
process.exit(0);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
const serverClient = new ServerClient(serverUrl, apiKey);
|
|
138
|
-
if (!deviceId && deviceName) {
|
|
139
|
-
const existing = findDeviceByName(deviceName);
|
|
140
|
-
if (existing) {
|
|
141
|
-
deviceId = existing.id;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (!deviceId && !deviceName) {
|
|
145
|
-
const devices = listDevices();
|
|
146
|
-
const choices = [
|
|
147
|
-
{ name: '+ Create new doko', value: '__new__' },
|
|
148
|
-
...devices.map(d => ({ name: `${d.name} (${d.id})`, value: d.id })),
|
|
149
|
-
];
|
|
150
|
-
const selected = await select({
|
|
151
|
-
message: 'Choose a doko:',
|
|
152
|
-
choices,
|
|
153
|
-
});
|
|
154
|
-
if (selected === '__new__') {
|
|
155
|
-
deviceName = await input({
|
|
156
|
-
message: 'Name your new doko:',
|
|
157
|
-
default: 'My Chrome',
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
deviceId = selected;
|
|
162
|
-
const existing = devices.find(d => d.id === deviceId);
|
|
163
|
-
deviceName = existing?.name;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const mcpExtraArgs = options.mcpArgs ? options.mcpArgs.split(',') : [];
|
|
167
|
-
const mcpClient = new McpClient();
|
|
168
|
-
console.log(' Connecting to Chrome...');
|
|
169
|
-
try {
|
|
170
|
-
await mcpClient.connect({
|
|
171
|
-
browserUrl: options.browserUrl,
|
|
172
|
-
extraArgs: mcpExtraArgs,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
console.error('Failed to connect to Chrome DevTools MCP:', error instanceof Error ? error.message : error);
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
let tools;
|
|
180
|
-
try {
|
|
181
|
-
tools = await mcpClient.listTools();
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
console.error(' ✗ Failed to connect to Chrome DevTools MCP:', error instanceof Error ? error.message : error);
|
|
185
|
-
await mcpClient.disconnect();
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
const check = await mcpClient.callTool('list_pages', {});
|
|
190
|
-
if (check.isError) {
|
|
191
|
-
const msg = check.content?.[0]?.text ?? '';
|
|
192
|
-
console.error(' ✗ Chrome is not reachable.');
|
|
193
|
-
console.error('');
|
|
194
|
-
console.error(' Please enable remote debugging in Chrome:');
|
|
195
|
-
console.error(' 1. Open chrome://inspect/#remote-debugging');
|
|
196
|
-
console.error(' 2. Check "Allow remote debugging for this browser instance"');
|
|
197
|
-
console.error(' 3. Re-run this command');
|
|
198
|
-
console.error('');
|
|
199
|
-
console.error(` Detail: ${msg}`);
|
|
200
|
-
await mcpClient.disconnect();
|
|
201
|
-
process.exit(1);
|
|
202
|
-
}
|
|
203
|
-
console.log(` ✓ Connected to Chrome (${tools.length} tools available)`);
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
console.error(' ✗ Chrome health check failed:', error instanceof Error ? error.message : error);
|
|
207
|
-
await mcpClient.disconnect();
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
try {
|
|
211
|
-
const result = await serverClient.connect({
|
|
212
|
-
type: 'chrome',
|
|
213
|
-
deviceId,
|
|
214
|
-
name: deviceName,
|
|
215
|
-
tools,
|
|
216
|
-
});
|
|
217
|
-
deviceId = result.deviceId;
|
|
218
|
-
deviceName = result.name;
|
|
219
|
-
saveDevice({
|
|
220
|
-
id: result.deviceId,
|
|
221
|
-
name: result.name,
|
|
222
|
-
type: result.type,
|
|
223
|
-
createdAt: Date.now(),
|
|
224
|
-
});
|
|
225
|
-
console.log(` ✓ Registered "${deviceName}" (${deviceId})`);
|
|
226
|
-
}
|
|
227
|
-
catch (error) {
|
|
228
|
-
console.error('Registration failed:', error instanceof Error ? error.message : error);
|
|
229
|
-
await mcpClient.disconnect();
|
|
230
|
-
process.exit(1);
|
|
231
|
-
}
|
|
232
|
-
const executor = new Executor(mcpClient);
|
|
233
|
-
const heartbeat = new Heartbeat(serverClient, deviceId);
|
|
234
|
-
const poller = new CommandPoller(serverClient, executor, deviceId);
|
|
235
|
-
heartbeat.start();
|
|
236
|
-
poller.start();
|
|
237
|
-
const updateInfo = getUpdateResult();
|
|
238
|
-
if (updateInfo) {
|
|
239
|
-
console.error(formatUpdateNotification(updateInfo));
|
|
240
|
-
}
|
|
241
|
-
console.log('');
|
|
242
|
-
process.stdout.write(` ${G}●${R} Listening for commands... (Ctrl+C to quit)`);
|
|
243
|
-
const shutdown = async () => {
|
|
244
|
-
process.stdout.write(`\r\x1b[2K`);
|
|
245
|
-
console.log(' Shutting down...');
|
|
246
|
-
poller.stop();
|
|
247
|
-
heartbeat.stop();
|
|
248
|
-
await serverClient.disconnect(deviceId);
|
|
249
|
-
await mcpClient.disconnect();
|
|
250
|
-
process.exit(0);
|
|
251
|
-
};
|
|
252
|
-
process.on('SIGINT', shutdown);
|
|
253
|
-
process.on('SIGTERM', shutdown);
|
|
254
|
-
await new Promise(() => { });
|
|
255
|
-
}
|
|
256
50
|
//# sourceMappingURL=cli.js.map
|
package/dist/src/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,SAAS,CAAC;SACf,WAAW,CAAC,yDAAyD,CAAC;SACtE,OAAO,CAAC,iBAAiB,EAAE,CAAC;SAC5B,MAAM,CAAC,iBAAiB,EAAE,mCAAmC,CAAC;SAC9D,MAAM,CAAC,gBAAgB,EAAE,0CAA0C,CAAC;SACpE,MAAM,CAAC,WAAW,EAAE,wBAAwB,CAAC,CAAC;IAEjD,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,kCAAkC,CAAC;SAC/C,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;YACzB,OAAO,EAAE,UAAU;YACnB,OAAO,EAAE,MAAM,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC;YAC5B,OAAO,EAAE,aAAa;YACtB,OAAO,EAAE,MAAM,CAAC,SAAS,IAAI,oBAAoB;SAClD,CAAC,CAAC;QACH,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,uBAAuB,CAAC;SACpC,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,iBAAiB,EAAE,CAAC;QACvC,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,iCAAiC,iBAAiB,EAAE,IAAI,CAAC,CAAC;QACxE,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAE9B,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -7,7 +7,11 @@ export declare class CommandPoller {
|
|
|
7
7
|
private polling;
|
|
8
8
|
private abortController;
|
|
9
9
|
private backoffMs;
|
|
10
|
-
|
|
10
|
+
private hadError;
|
|
11
|
+
private onReconnect?;
|
|
12
|
+
constructor(serverClient: ServerClient, executor: Executor, deviceId: string, options?: {
|
|
13
|
+
onReconnect?: () => void;
|
|
14
|
+
});
|
|
11
15
|
start(): void;
|
|
12
16
|
stop(): void;
|
|
13
17
|
private pollLoop;
|
|
@@ -7,10 +7,13 @@ export class CommandPoller {
|
|
|
7
7
|
polling = false;
|
|
8
8
|
abortController = null;
|
|
9
9
|
backoffMs = 1000;
|
|
10
|
-
|
|
10
|
+
hadError = false;
|
|
11
|
+
onReconnect;
|
|
12
|
+
constructor(serverClient, executor, deviceId, options) {
|
|
11
13
|
this.serverClient = serverClient;
|
|
12
14
|
this.executor = executor;
|
|
13
15
|
this.deviceId = deviceId;
|
|
16
|
+
this.onReconnect = options?.onReconnect;
|
|
14
17
|
}
|
|
15
18
|
start() {
|
|
16
19
|
if (this.polling)
|
|
@@ -33,6 +36,10 @@ export class CommandPoller {
|
|
|
33
36
|
const command = await this.serverClient.pollCommand(this.deviceId, this.abortController.signal);
|
|
34
37
|
if (!this.polling)
|
|
35
38
|
break;
|
|
39
|
+
if (this.hadError) {
|
|
40
|
+
this.hadError = false;
|
|
41
|
+
this.onReconnect?.();
|
|
42
|
+
}
|
|
36
43
|
if (command === null) {
|
|
37
44
|
this.backoffMs = 1000;
|
|
38
45
|
continue;
|
|
@@ -49,6 +56,7 @@ export class CommandPoller {
|
|
|
49
56
|
continue;
|
|
50
57
|
const msg = error instanceof Error ? error.message : String(error);
|
|
51
58
|
const isTimeout = msg.includes('TimeoutError') || msg.includes('Timeout') || msg.includes('fetch failed');
|
|
59
|
+
this.hadError = true;
|
|
52
60
|
if (isTimeout) {
|
|
53
61
|
logger.warn(`Poll timeout, retrying in ${this.backoffMs}ms`);
|
|
54
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-poller.js","sourceRoot":"","sources":["../../src/command-poller.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,MAAM,OAAO,aAAa;
|
|
1
|
+
{"version":3,"file":"command-poller.js","sourceRoot":"","sources":["../../src/command-poller.ts"],"names":[],"mappings":"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"}
|