kroki 1.0.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 +114 -0
- package/bin/kroki.js +390 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Kroki CLI
|
|
2
|
+
|
|
3
|
+
> **Browser Automation & AI Agent Bridge for Google Chrome & Dolphin{anty}**
|
|
4
|
+
> Control live browser tabs, run deterministic workflows, and extract web data from your terminal or any AI Coding Agent (Antigravity, Cursor, Claude Code, Windsurf).
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/kroki)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ⚡ Quick Start
|
|
13
|
+
|
|
14
|
+
No installation required. You can run it directly with `npx`:
|
|
15
|
+
|
|
16
|
+
### 1. Pair with your browser profile
|
|
17
|
+
|
|
18
|
+
Go to your [Kroki Devices Dashboard](https://kroki.one/devices) and click **"Скопировать код для AI"** next to the active Chrome or Dolphin{anty} profile you want to control.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx kroki pair <YOUR_PAIRING_CODE>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Connecting to profile [Dolphin WB-Bot]...
|
|
26
|
+
✔ Successfully paired with "Dolphin WB-Bot".
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Send an AI task to the live browser
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx kroki "open wildberries.ru, search for 'iPhone 16 cases' and list the top 5 prices"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Kroki's built-in Planner & Navigator subagent will navigate to the page, interact with elements, extract the requested information, and stream the result back to your terminal.
|
|
36
|
+
|
|
37
|
+
### 3. Run a saved workflow
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx kroki run "Export Leads" '{"query": "CTO", "limit": 20}'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 📦 Global Installation (Optional)
|
|
46
|
+
|
|
47
|
+
If you prefer using `kroki` without typing `npx` every time:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install -g kroki
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Now you can just run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
kroki pair <CODE>
|
|
57
|
+
kroki "scrape the current page"
|
|
58
|
+
kroki run "My Workflow"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🤖 Using with AI Agents (Antigravity, Claude Code, Cursor)
|
|
64
|
+
|
|
65
|
+
Kroki CLI works out-of-the-box with AI pair-programming agents.
|
|
66
|
+
|
|
67
|
+
### In Antigravity CLI
|
|
68
|
+
|
|
69
|
+
Whenever you mention **kroki** in the chat, the agent automatically knows how to use this CLI:
|
|
70
|
+
|
|
71
|
+
> **You:** `kroki pair kroki_eyJh...`
|
|
72
|
+
> **Agent:** Pairs with the specified profile and confirms readiness.
|
|
73
|
+
>
|
|
74
|
+
> **You:** `kroki найди на Avito Mac mini M4 и выпиши самые дешевые цены`
|
|
75
|
+
> **Agent:** Executes `kroki "<task>"`, waits for Kroki in the browser to finish, and summarizes the findings.
|
|
76
|
+
|
|
77
|
+
### In Cursor / Claude Code
|
|
78
|
+
|
|
79
|
+
Add the following to your agent prompt or rule file (`.cursorrules` or `CLAUDE.md`):
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
When the user requests live browser interaction or running Kroki workflows:
|
|
83
|
+
1. Ensure the profile is paired: run `npx kroki status`. If not paired, ask the user for their pairing code from https://kroki.one/devices.
|
|
84
|
+
2. Pair via `npx kroki pair <CODE>`.
|
|
85
|
+
3. Dispatch tasks via `npx kroki "<task>"` or workflows via `npx kroki run "<workflow>" '[vars]'`.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 🛠 Commands Reference
|
|
91
|
+
|
|
92
|
+
| Command | Description |
|
|
93
|
+
| :--- | :--- |
|
|
94
|
+
| `npx kroki pair <code_from_devices>` | Connect to a specific browser profile (Chrome or Dolphin{anty}) |
|
|
95
|
+
| `npx kroki "<browser_task>"` | Send an autonomous agent task to the active browser profile |
|
|
96
|
+
| `npx kroki run "<workflow_name>" [json]` | Execute a saved Kroki deterministic workflow |
|
|
97
|
+
| `npx kroki status` | View the currently paired profile and device info |
|
|
98
|
+
| `npx kroki clear` | Clear active pairing session |
|
|
99
|
+
| `npx kroki help` | Show usage options and help |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🔒 Security Architecture
|
|
104
|
+
|
|
105
|
+
- **Zero Global Credentials**: Pairing codes are session-bound and scoped strictly to your selected browser window (`targetDeviceId`).
|
|
106
|
+
- **Confirmation Gate**: The extension prompts you for confirmation on your computer when a connection attempt is made.
|
|
107
|
+
- **Ultra Plan Scoped**: Cloud control channels require an active Ultra entitlement.
|
|
108
|
+
- **Pure Native Node.js**: Zero third-party npm dependencies. Clean, audited, and lightweight.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 📄 License
|
|
113
|
+
|
|
114
|
+
MIT © [Kroki Team](https://kroki.one)
|
package/bin/kroki.js
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Kroki CLI — Control Kroki browser automation from terminal & AI agents.
|
|
5
|
+
* Zero external dependencies. Uses native Node.js 18+ WebSocket & Fetch.
|
|
6
|
+
*
|
|
7
|
+
* https://kroki.one
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
11
|
+
import { resolve, join } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
|
|
14
|
+
const CONFIG_DIR = join(homedir(), '.kroki');
|
|
15
|
+
const SESSION_FILE = join(CONFIG_DIR, 'session.json');
|
|
16
|
+
|
|
17
|
+
const DEFAULT_SUPABASE_URL = 'https://vjkzlvtvnmorokocnbhp.supabase.co';
|
|
18
|
+
const DEFAULT_ANON_KEY =
|
|
19
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZqa3psdnR2bm1vcm9rb2NuYmhwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTY4NTI3NTcsImV4cCI6MjA3MjQyODc1N30.NZ4nMq2z8rFQZ2oMlYH0XJX3FrWI2T1j1vmF6vu1-HE';
|
|
20
|
+
|
|
21
|
+
function ensureConfigDir() {
|
|
22
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
23
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function decodePairingCode(code) {
|
|
28
|
+
const trimmed = code.trim();
|
|
29
|
+
const raw = trimmed.startsWith('kroki_') ? trimmed.slice(6) : trimmed;
|
|
30
|
+
try {
|
|
31
|
+
const jsonStr = Buffer.from(raw, 'base64url').toString('utf8');
|
|
32
|
+
const parsed = JSON.parse(jsonStr);
|
|
33
|
+
if (!parsed.googleId || !parsed.token || !parsed.deviceId) {
|
|
34
|
+
throw new Error('Missing required fields in pairing code');
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
} catch {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'Invalid Kroki pairing code.\nPlease copy a valid code from your Kroki Devices panel (https://kroki.one/devices).',
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getActiveSession() {
|
|
45
|
+
if (!existsSync(SESSION_FILE)) return null;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(readFileSync(SESSION_FILE, 'utf8'));
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function saveActiveSession(session) {
|
|
54
|
+
ensureConfigDir();
|
|
55
|
+
writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2), { mode: 0o600 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function clearActiveSession() {
|
|
59
|
+
if (existsSync(SESSION_FILE)) {
|
|
60
|
+
unlinkSync(SESSION_FILE);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class RealtimeConnection {
|
|
65
|
+
constructor(config) {
|
|
66
|
+
this.url = config.supabaseUrl || DEFAULT_SUPABASE_URL;
|
|
67
|
+
this.anonKey = config.anonKey || DEFAULT_ANON_KEY;
|
|
68
|
+
this.token = config.token;
|
|
69
|
+
this.googleId = config.googleId;
|
|
70
|
+
this.deviceId = config.deviceId;
|
|
71
|
+
this.topic = `realtime:control:${this.googleId}`;
|
|
72
|
+
this.ref = 0;
|
|
73
|
+
this.ws = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
nextRef() {
|
|
77
|
+
return String(++this.ref);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async connect(timeoutMs = 15000) {
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const wsUrl = `${this.url.replace(/^http/, 'ws')}/realtime/v1/websocket?apikey=${this.anonKey}&vsn=1.0.0`;
|
|
83
|
+
this.ws = new globalThis.WebSocket(wsUrl);
|
|
84
|
+
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
if (this.ws) this.ws.close();
|
|
87
|
+
reject(new Error('Connection to Kroki Realtime timed out. Check your internet connection.'));
|
|
88
|
+
}, timeoutMs);
|
|
89
|
+
|
|
90
|
+
this.ws.onopen = () => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
this.joinChannel()
|
|
93
|
+
.then(() => resolve(this))
|
|
94
|
+
.catch(reject);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
this.ws.onerror = err => {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
reject(new Error(`WebSocket connection failed: ${err.message || 'network error'}`));
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
joinChannel() {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const joinRef = this.nextRef();
|
|
107
|
+
const payload = {
|
|
108
|
+
topic: this.topic,
|
|
109
|
+
event: 'phx_join',
|
|
110
|
+
payload: {
|
|
111
|
+
config: { broadcast: { self: false } },
|
|
112
|
+
access_token: this.token,
|
|
113
|
+
},
|
|
114
|
+
ref: joinRef,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handler = event => {
|
|
118
|
+
try {
|
|
119
|
+
const msg = JSON.parse(event.data);
|
|
120
|
+
if (msg.ref === joinRef) {
|
|
121
|
+
this.ws.removeEventListener('message', handler);
|
|
122
|
+
if (msg.payload?.status === 'ok') {
|
|
123
|
+
resolve();
|
|
124
|
+
} else {
|
|
125
|
+
reject(new Error(`Channel join rejected: ${JSON.stringify(msg.payload || {})}`));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch {}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
this.ws.addEventListener('message', handler);
|
|
132
|
+
this.ws.send(JSON.stringify(payload));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
sendCommand(command, onEvent, timeoutMs = 240000) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const msgRef = this.nextRef();
|
|
139
|
+
const timer = setTimeout(() => {
|
|
140
|
+
reject(new Error('Command timed out while waiting for browser response (4 min limit).'));
|
|
141
|
+
}, timeoutMs);
|
|
142
|
+
|
|
143
|
+
const handler = event => {
|
|
144
|
+
try {
|
|
145
|
+
const msg = JSON.parse(event.data);
|
|
146
|
+
if (msg.topic === this.topic && msg.event === 'broadcast') {
|
|
147
|
+
const envelope = msg.payload?.payload;
|
|
148
|
+
if (!envelope || envelope.deviceId !== this.deviceId) return;
|
|
149
|
+
|
|
150
|
+
if (onEvent) onEvent(envelope);
|
|
151
|
+
|
|
152
|
+
if (envelope.kind === 'chat_task_complete' || envelope.kind === 'workflow_complete') {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
this.ws.removeEventListener('message', handler);
|
|
155
|
+
resolve(envelope.payload);
|
|
156
|
+
} else if (envelope.kind === 'error') {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
this.ws.removeEventListener('message', handler);
|
|
159
|
+
reject(new Error(envelope.payload?.message || 'Remote error from Kroki browser'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch {}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
this.ws.addEventListener('message', handler);
|
|
166
|
+
|
|
167
|
+
const packet = {
|
|
168
|
+
topic: this.topic,
|
|
169
|
+
event: 'broadcast',
|
|
170
|
+
payload: {
|
|
171
|
+
type: 'broadcast',
|
|
172
|
+
event: 'command',
|
|
173
|
+
payload: command,
|
|
174
|
+
},
|
|
175
|
+
ref: msgRef,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
this.ws.send(JSON.stringify(packet));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
close() {
|
|
183
|
+
if (this.ws) {
|
|
184
|
+
try {
|
|
185
|
+
this.ws.close();
|
|
186
|
+
} catch {}
|
|
187
|
+
this.ws = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function pairCommand(code) {
|
|
193
|
+
if (!code) {
|
|
194
|
+
console.error('Error: Pairing code required.\nUsage: npx kroki pair <kroki_code>\n');
|
|
195
|
+
console.error('Get your code from: https://kroki.one/devices');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const payload = decodePairingCode(code);
|
|
200
|
+
const name = payload.deviceName || payload.deviceId;
|
|
201
|
+
console.log(`\x1b[36mConnecting to profile [${name}]...\x1b[0m`);
|
|
202
|
+
|
|
203
|
+
const conn = new RealtimeConnection(payload);
|
|
204
|
+
await conn.connect();
|
|
205
|
+
|
|
206
|
+
saveActiveSession({
|
|
207
|
+
...payload,
|
|
208
|
+
pairedAt: Date.now(),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
conn.close();
|
|
212
|
+
|
|
213
|
+
console.log(`\x1b[32m✔ Successfully paired with "${name}".\x1b[0m`);
|
|
214
|
+
console.log('You can now run tasks without entering the code:');
|
|
215
|
+
console.log(' \x1b[33mnpx kroki "open example.com and extract titles"\x1b[0m');
|
|
216
|
+
console.log(' \x1b[33mnpx kroki run "My Workflow Name"\x1b[0m\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function taskCommand(taskText) {
|
|
220
|
+
const session = getActiveSession();
|
|
221
|
+
if (!session) {
|
|
222
|
+
console.error('\x1b[31mError: Not paired.\x1b[0m');
|
|
223
|
+
console.error('First pair with your browser profile:\n \x1b[33mnpx kroki pair <CODE_FROM_DEVICES>\x1b[0m\n');
|
|
224
|
+
console.error('Get your code from: https://kroki.one/devices');
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const conn = new RealtimeConnection(session);
|
|
229
|
+
await conn.connect();
|
|
230
|
+
|
|
231
|
+
const command = {
|
|
232
|
+
v: 1,
|
|
233
|
+
id: `task-${Date.now()}`,
|
|
234
|
+
kind: 'chat_task',
|
|
235
|
+
targetDeviceId: session.deviceId,
|
|
236
|
+
taskId: `task-${Date.now()}`,
|
|
237
|
+
issuedAt: Date.now(),
|
|
238
|
+
payload: {
|
|
239
|
+
task: taskText,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
console.log(`\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Running: "${taskText}"...`);
|
|
245
|
+
const result = await conn.sendCommand(command, ev => {
|
|
246
|
+
if (ev.kind === 'execution') {
|
|
247
|
+
const title = ev.payload?.title || ev.payload?.type;
|
|
248
|
+
if (title) console.log(` \x1b[90m→ ${title}\x1b[0m`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
console.log('\x1b[32m✔ Task completed.\x1b[0m');
|
|
253
|
+
if (result && typeof result === 'object') {
|
|
254
|
+
console.log(JSON.stringify(result, null, 2));
|
|
255
|
+
}
|
|
256
|
+
} finally {
|
|
257
|
+
conn.close();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function workflowCommand(nameOrId, varsJson) {
|
|
262
|
+
const session = getActiveSession();
|
|
263
|
+
if (!session) {
|
|
264
|
+
console.error('\x1b[31mError: Not paired.\x1b[0m');
|
|
265
|
+
console.error('Pair first with:\n \x1b[33mnpx kroki pair <CODE>\x1b[0m\n');
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let variables = {};
|
|
270
|
+
if (varsJson) {
|
|
271
|
+
try {
|
|
272
|
+
variables = JSON.parse(varsJson);
|
|
273
|
+
} catch {
|
|
274
|
+
console.error('Warning: Could not parse variables JSON, passing empty object.');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const conn = new RealtimeConnection(session);
|
|
279
|
+
await conn.connect();
|
|
280
|
+
|
|
281
|
+
const command = {
|
|
282
|
+
v: 1,
|
|
283
|
+
id: `wf-${Date.now()}`,
|
|
284
|
+
kind: 'workflow_execute',
|
|
285
|
+
targetDeviceId: session.deviceId,
|
|
286
|
+
taskId: `wf-${Date.now()}`,
|
|
287
|
+
issuedAt: Date.now(),
|
|
288
|
+
payload: {
|
|
289
|
+
workflowId: nameOrId,
|
|
290
|
+
variables,
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
console.log(`\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Executing workflow "${nameOrId}"...`);
|
|
296
|
+
const result = await conn.sendCommand(command, ev => {
|
|
297
|
+
if (ev.kind === 'workflow_step_start') {
|
|
298
|
+
console.log(` \x1b[90m[step] ${ev.payload?.stepType || 'action'}: ${ev.payload?.comment || ''}\x1b[0m`);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
console.log('\x1b[32m✔ Workflow finished.\x1b[0m');
|
|
303
|
+
if (result) console.log(JSON.stringify(result, null, 2));
|
|
304
|
+
} finally {
|
|
305
|
+
conn.close();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function statusCommand() {
|
|
310
|
+
const session = getActiveSession();
|
|
311
|
+
if (!session) {
|
|
312
|
+
console.log('\x1b[33mStatus: Not paired with any browser profile.\x1b[0m');
|
|
313
|
+
console.log('Pair with:\n npx kroki pair <CODE_FROM_DEVICES>');
|
|
314
|
+
} else {
|
|
315
|
+
console.log('\x1b[32mStatus: Paired and Active\x1b[0m');
|
|
316
|
+
console.log(`Profile: ${session.deviceName || session.deviceId}`);
|
|
317
|
+
console.log(`Device ID: ${session.deviceId}`);
|
|
318
|
+
console.log(`Platform: ${session.platform || 'unknown'}`);
|
|
319
|
+
console.log(`Paired At: ${new Date(session.pairedAt).toLocaleString()}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function helpCommand() {
|
|
324
|
+
console.log(`
|
|
325
|
+
\x1b[1mKroki CLI\x1b[0m — Control Kroki browser automation from terminal & AI agents
|
|
326
|
+
|
|
327
|
+
\x1b[1mUsage:\x1b[0m
|
|
328
|
+
npx kroki pair <code_from_devices> Pair with a browser profile (Chrome / Dolphin{anty})
|
|
329
|
+
npx kroki "<browser_task>" Send an AI task to the paired browser
|
|
330
|
+
npx kroki run "<workflow_name>" [json] Execute a deterministic workflow by name
|
|
331
|
+
npx kroki status Show currently paired profile
|
|
332
|
+
npx kroki clear Clear active pairing session
|
|
333
|
+
npx kroki help Show this help message
|
|
334
|
+
|
|
335
|
+
\x1b[1mExamples:\x1b[0m
|
|
336
|
+
npx kroki pair kroki_eyJh...
|
|
337
|
+
npx kroki "find top 5 mechanical keyboards on Amazon and return prices"
|
|
338
|
+
npx kroki run "Export Leads" '{"limit": 50}'
|
|
339
|
+
|
|
340
|
+
\x1b[1mDocs & Dashboard:\x1b[0m
|
|
341
|
+
https://kroki.one/devices
|
|
342
|
+
`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Entry Point ─────────────────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
const rawArgs = process.argv.slice(2);
|
|
348
|
+
const first = rawArgs[0] || 'help';
|
|
349
|
+
|
|
350
|
+
switch (first) {
|
|
351
|
+
case 'pair':
|
|
352
|
+
case 'connect':
|
|
353
|
+
pairCommand(rawArgs[1]).catch(e => {
|
|
354
|
+
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
});
|
|
357
|
+
break;
|
|
358
|
+
|
|
359
|
+
case 'run':
|
|
360
|
+
case 'workflow':
|
|
361
|
+
workflowCommand(rawArgs[1], rawArgs[2]).catch(e => {
|
|
362
|
+
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
});
|
|
365
|
+
break;
|
|
366
|
+
|
|
367
|
+
case 'status':
|
|
368
|
+
statusCommand();
|
|
369
|
+
break;
|
|
370
|
+
|
|
371
|
+
case 'clear':
|
|
372
|
+
case 'disconnect':
|
|
373
|
+
clearActiveSession();
|
|
374
|
+
console.log('\x1b[32m✔ Session cleared.\x1b[0m');
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case 'help':
|
|
378
|
+
case '--help':
|
|
379
|
+
case '-h':
|
|
380
|
+
helpCommand();
|
|
381
|
+
break;
|
|
382
|
+
|
|
383
|
+
default:
|
|
384
|
+
// If user ran `npx kroki "do something..."` directly
|
|
385
|
+
taskCommand(rawArgs.join(' ')).catch(e => {
|
|
386
|
+
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
});
|
|
389
|
+
break;
|
|
390
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kroki",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Kroki Browser Automation CLI — Control Chrome & Dolphin{anty} from terminal and AI agents",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kroki",
|
|
7
|
+
"browser-automation",
|
|
8
|
+
"ai-agent",
|
|
9
|
+
"antigravity",
|
|
10
|
+
"dolphin-anty",
|
|
11
|
+
"chrome",
|
|
12
|
+
"mcp",
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"author": "Kroki Team",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"bin": {
|
|
19
|
+
"kroki": "./bin/kroki.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"bin",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18.0.0"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/one-focus/kroki-cli.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/one-focus/kroki-cli/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://kroki.one"
|
|
37
|
+
}
|