kroki 1.0.3 → 1.0.5
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/bin/kroki.js +199 -24
- package/package.json +1 -1
package/bin/kroki.js
CHANGED
|
@@ -160,10 +160,18 @@ class RealtimeConnection {
|
|
|
160
160
|
|
|
161
161
|
if (onEvent) onEvent(envelope);
|
|
162
162
|
|
|
163
|
-
if (
|
|
163
|
+
if (
|
|
164
|
+
envelope.kind === 'chat_task_complete' ||
|
|
165
|
+
envelope.kind === 'workflow_complete' ||
|
|
166
|
+
envelope.kind === 'pair_approved'
|
|
167
|
+
) {
|
|
164
168
|
clearTimeout(timer);
|
|
165
169
|
this.ws.removeEventListener('message', handler);
|
|
166
170
|
resolve(envelope.payload);
|
|
171
|
+
} else if (envelope.kind === 'pair_rejected') {
|
|
172
|
+
clearTimeout(timer);
|
|
173
|
+
this.ws.removeEventListener('message', handler);
|
|
174
|
+
reject(new Error('Pairing request was rejected in Kroki browser.'));
|
|
167
175
|
} else if (envelope.kind === 'error') {
|
|
168
176
|
clearTimeout(timer);
|
|
169
177
|
this.ws.removeEventListener('message', handler);
|
|
@@ -227,6 +235,28 @@ Requires an active \x1b[33mUltra\x1b[0m plan.
|
|
|
227
235
|
const conn = new RealtimeConnection(payload);
|
|
228
236
|
await conn.connect();
|
|
229
237
|
|
|
238
|
+
console.log(`\x1b[33mWaiting for confirmation in Kroki browser (click "Разрешить")...\x1b[0m`);
|
|
239
|
+
|
|
240
|
+
const pairCmd = {
|
|
241
|
+
v: 1,
|
|
242
|
+
id: `pair-${Date.now()}`,
|
|
243
|
+
kind: 'pair_request',
|
|
244
|
+
targetDeviceId: payload.deviceId,
|
|
245
|
+
taskId: `pair-${Date.now()}`,
|
|
246
|
+
issuedAt: Date.now(),
|
|
247
|
+
payload: {
|
|
248
|
+
clientName: 'Antigravity CLI',
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
await conn.sendCommand(pairCmd, undefined, 60000);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
conn.close();
|
|
256
|
+
console.error(`\x1b[31m✖ ${err.message}\x1b[0m`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
230
260
|
saveActiveSession({
|
|
231
261
|
...payload,
|
|
232
262
|
pairedAt: Date.now(),
|
|
@@ -240,7 +270,54 @@ Requires an active \x1b[33mUltra\x1b[0m plan.
|
|
|
240
270
|
console.log(' \x1b[33mnpx kroki run "My Workflow Name"\x1b[0m\n');
|
|
241
271
|
}
|
|
242
272
|
|
|
243
|
-
|
|
273
|
+
function createProgressTracker({ verbose = false, isJson = false, initialText = '' }) {
|
|
274
|
+
const steps = [];
|
|
275
|
+
let currentStatus = initialText;
|
|
276
|
+
let timer = null;
|
|
277
|
+
const isTTY = Boolean(process.stdout.isTTY) && !isJson && !verbose;
|
|
278
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
279
|
+
let frameIdx = 0;
|
|
280
|
+
|
|
281
|
+
if (isTTY) {
|
|
282
|
+
timer = setInterval(() => {
|
|
283
|
+
const frame = frames[frameIdx++ % frames.length];
|
|
284
|
+
const text = currentStatus ? ` \x1b[90m${currentStatus.slice(0, 65)}\x1b[0m` : '';
|
|
285
|
+
process.stdout.write(`\r\x1b[36m${frame}\x1b[0m${text}\x1b[K`);
|
|
286
|
+
}, 80);
|
|
287
|
+
} else if (!isJson && !verbose) {
|
|
288
|
+
if (initialText) console.log(initialText);
|
|
289
|
+
} else if (verbose && initialText) {
|
|
290
|
+
console.log(initialText);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
onStep(stepText) {
|
|
295
|
+
if (!stepText || typeof stepText !== 'string') return;
|
|
296
|
+
const firstLine = stepText.split('\n')[0].trim();
|
|
297
|
+
if (!firstLine) return;
|
|
298
|
+
steps.push(firstLine);
|
|
299
|
+
if (verbose) {
|
|
300
|
+
console.log(` \x1b[90m→ ${firstLine.slice(0, 100)}\x1b[0m`);
|
|
301
|
+
} else {
|
|
302
|
+
currentStatus = firstLine;
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
stop() {
|
|
306
|
+
if (timer) {
|
|
307
|
+
clearInterval(timer);
|
|
308
|
+
timer = null;
|
|
309
|
+
}
|
|
310
|
+
if (isTTY) {
|
|
311
|
+
process.stdout.write('\r\x1b[K');
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
getSteps() {
|
|
315
|
+
return steps;
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function taskCommand(taskText, { verbose = false, json = false } = {}) {
|
|
244
321
|
const session = getActiveSession();
|
|
245
322
|
if (!session) {
|
|
246
323
|
console.error('\x1b[31mError: Not paired.\x1b[0m');
|
|
@@ -264,25 +341,52 @@ async function taskCommand(taskText) {
|
|
|
264
341
|
},
|
|
265
342
|
};
|
|
266
343
|
|
|
344
|
+
const initialMsg = `\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Running: "${taskText}"...`;
|
|
345
|
+
const tracker = createProgressTracker({ verbose, isJson: json, initialText: initialMsg });
|
|
346
|
+
|
|
267
347
|
try {
|
|
268
|
-
console.log(`\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Running: "${taskText}"...`);
|
|
269
348
|
const result = await conn.sendCommand(command, ev => {
|
|
270
349
|
if (ev.kind === 'execution') {
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
350
|
+
const details = ev.payload?.data?.details || ev.payload?.title || ev.payload?.type;
|
|
351
|
+
if (details) tracker.onStep(details);
|
|
273
352
|
}
|
|
274
353
|
});
|
|
275
354
|
|
|
276
|
-
|
|
277
|
-
|
|
355
|
+
tracker.stop();
|
|
356
|
+
|
|
357
|
+
if (json) {
|
|
278
358
|
console.log(JSON.stringify(result, null, 2));
|
|
359
|
+
} else {
|
|
360
|
+
if (verbose) {
|
|
361
|
+
console.log('\x1b[32m✔ Task completed.\x1b[0m\n');
|
|
362
|
+
}
|
|
363
|
+
if (result && typeof result === 'object' && result.message) {
|
|
364
|
+
console.log(result.message);
|
|
365
|
+
} else if (result && typeof result === 'object') {
|
|
366
|
+
console.log(JSON.stringify(result, null, 2));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
tracker.stop();
|
|
371
|
+
if (!verbose && tracker.getSteps().length > 0) {
|
|
372
|
+
console.error('\x1b[90mRecent steps before failure:\x1b[0m');
|
|
373
|
+
for (const step of tracker.getSteps().slice(-10)) {
|
|
374
|
+
console.error(` \x1b[90m→ ${step}\x1b[0m`);
|
|
375
|
+
}
|
|
376
|
+
console.error('');
|
|
279
377
|
}
|
|
378
|
+
if (json) {
|
|
379
|
+
console.error(JSON.stringify({ success: false, error: err.message, steps: tracker.getSteps() }, null, 2));
|
|
380
|
+
} else {
|
|
381
|
+
console.error(`\x1b[31m✖ Error: ${err.message}\x1b[0m`);
|
|
382
|
+
}
|
|
383
|
+
process.exit(1);
|
|
280
384
|
} finally {
|
|
281
385
|
conn.close();
|
|
282
386
|
}
|
|
283
387
|
}
|
|
284
388
|
|
|
285
|
-
async function workflowCommand(nameOrId, varsJson) {
|
|
389
|
+
async function workflowCommand(nameOrId, varsJson, { verbose = false, json = false } = {}) {
|
|
286
390
|
const session = getActiveSession();
|
|
287
391
|
if (!session) {
|
|
288
392
|
console.error('\x1b[31mError: Not paired.\x1b[0m');
|
|
@@ -316,29 +420,75 @@ async function workflowCommand(nameOrId, varsJson) {
|
|
|
316
420
|
},
|
|
317
421
|
};
|
|
318
422
|
|
|
423
|
+
const initialMsg = `\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Executing workflow "${nameOrId}"...`;
|
|
424
|
+
const tracker = createProgressTracker({ verbose, isJson: json, initialText: initialMsg });
|
|
425
|
+
|
|
319
426
|
try {
|
|
320
|
-
console.log(`\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Executing workflow "${nameOrId}"...`);
|
|
321
427
|
const result = await conn.sendCommand(command, ev => {
|
|
322
428
|
if (ev.kind === 'workflow_step_start') {
|
|
323
|
-
|
|
429
|
+
const stepDesc = `[step] ${ev.payload?.stepType || 'action'}: ${ev.payload?.comment || ''}`;
|
|
430
|
+
tracker.onStep(stepDesc);
|
|
324
431
|
}
|
|
325
432
|
});
|
|
326
433
|
|
|
327
|
-
|
|
328
|
-
|
|
434
|
+
tracker.stop();
|
|
435
|
+
|
|
436
|
+
if (json) {
|
|
437
|
+
console.log(JSON.stringify(result, null, 2));
|
|
438
|
+
} else {
|
|
439
|
+
if (verbose) {
|
|
440
|
+
console.log('\x1b[32m✔ Workflow finished.\x1b[0m\n');
|
|
441
|
+
}
|
|
442
|
+
if (result) console.log(JSON.stringify(result, null, 2));
|
|
443
|
+
}
|
|
444
|
+
} catch (err) {
|
|
445
|
+
tracker.stop();
|
|
446
|
+
if (!verbose && tracker.getSteps().length > 0) {
|
|
447
|
+
console.error('\x1b[90mRecent steps before failure:\x1b[0m');
|
|
448
|
+
for (const step of tracker.getSteps().slice(-10)) {
|
|
449
|
+
console.error(` \x1b[90m→ ${step}\x1b[0m`);
|
|
450
|
+
}
|
|
451
|
+
console.error('');
|
|
452
|
+
}
|
|
453
|
+
if (json) {
|
|
454
|
+
console.error(JSON.stringify({ success: false, error: err.message, steps: tracker.getSteps() }, null, 2));
|
|
455
|
+
} else {
|
|
456
|
+
console.error(`\x1b[31m✖ Error: ${err.message}\x1b[0m`);
|
|
457
|
+
}
|
|
458
|
+
process.exit(1);
|
|
329
459
|
} finally {
|
|
330
460
|
conn.close();
|
|
331
461
|
}
|
|
332
462
|
}
|
|
333
463
|
|
|
334
|
-
function statusCommand() {
|
|
464
|
+
function statusCommand({ json = false } = {}) {
|
|
335
465
|
const session = getActiveSession();
|
|
336
466
|
if (!session) {
|
|
467
|
+
if (json) {
|
|
468
|
+
console.log(JSON.stringify({ paired: false }, null, 2));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
337
471
|
console.log('No active paired profile.');
|
|
338
472
|
console.log('To pair, get a code from https://kroki.ai/#/devices and run:');
|
|
339
473
|
console.log(' npx kroki pair <CODE>');
|
|
340
474
|
return;
|
|
341
475
|
}
|
|
476
|
+
if (json) {
|
|
477
|
+
console.log(
|
|
478
|
+
JSON.stringify(
|
|
479
|
+
{
|
|
480
|
+
paired: true,
|
|
481
|
+
deviceId: session.deviceId,
|
|
482
|
+
deviceName: session.deviceName,
|
|
483
|
+
platform: session.platform,
|
|
484
|
+
pairedAt: session.pairedAt,
|
|
485
|
+
},
|
|
486
|
+
null,
|
|
487
|
+
2,
|
|
488
|
+
),
|
|
489
|
+
);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
342
492
|
console.log(
|
|
343
493
|
`\x1b[32m✔ Paired\x1b[0m with "${session.deviceName || session.deviceId}" (${session.platform || 'browser'})`,
|
|
344
494
|
);
|
|
@@ -350,11 +500,16 @@ function helpCommand() {
|
|
|
350
500
|
\x1b[1mKroki CLI\x1b[0m — Browser Automation & AI Agent Bridge for Google Chrome
|
|
351
501
|
|
|
352
502
|
\x1b[1mUsage:\x1b[0m
|
|
353
|
-
npx kroki
|
|
354
|
-
npx kroki "<
|
|
355
|
-
npx kroki
|
|
356
|
-
npx kroki status
|
|
357
|
-
npx kroki clear
|
|
503
|
+
npx kroki "<task>" [options] Send an AI task to the live browser
|
|
504
|
+
npx kroki run "<workflow>" [json] [opts] Execute a saved workflow
|
|
505
|
+
npx kroki pair <CODE> Pair with a browser profile (from https://kroki.ai/#/devices)
|
|
506
|
+
npx kroki status [options] View paired profile and status
|
|
507
|
+
npx kroki clear Disconnect active pairing session
|
|
508
|
+
|
|
509
|
+
\x1b[1mOptions:\x1b[0m
|
|
510
|
+
-v, --verbose Show real-time execution steps and logs
|
|
511
|
+
-j, --json Output result as JSON
|
|
512
|
+
-h, --help Show help
|
|
358
513
|
|
|
359
514
|
\x1b[1mDashboard:\x1b[0m
|
|
360
515
|
https://kroki.ai/#/devices
|
|
@@ -364,24 +519,38 @@ function helpCommand() {
|
|
|
364
519
|
// ── Entry Point ─────────────────────────────────────────────────────────────
|
|
365
520
|
|
|
366
521
|
const rawArgs = process.argv.slice(2);
|
|
367
|
-
|
|
522
|
+
let verbose = false;
|
|
523
|
+
let json = false;
|
|
524
|
+
const filteredArgs = [];
|
|
525
|
+
|
|
526
|
+
for (const arg of rawArgs) {
|
|
527
|
+
if (arg === '--verbose' || arg === '-v') {
|
|
528
|
+
verbose = true;
|
|
529
|
+
} else if (arg === '--json' || arg === '-j') {
|
|
530
|
+
json = true;
|
|
531
|
+
} else {
|
|
532
|
+
filteredArgs.push(arg);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const first = filteredArgs[0] || 'help';
|
|
368
537
|
|
|
369
538
|
switch (first) {
|
|
370
539
|
case 'pair':
|
|
371
540
|
case 'connect':
|
|
372
|
-
pairCommand(
|
|
541
|
+
pairCommand(filteredArgs[1]).catch(e => {
|
|
373
542
|
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
374
543
|
process.exit(1);
|
|
375
544
|
});
|
|
376
545
|
break;
|
|
377
546
|
|
|
378
547
|
case 'status':
|
|
379
|
-
statusCommand();
|
|
548
|
+
statusCommand({ json });
|
|
380
549
|
break;
|
|
381
550
|
|
|
382
551
|
case 'run':
|
|
383
552
|
case 'workflow':
|
|
384
|
-
workflowCommand(
|
|
553
|
+
workflowCommand(filteredArgs[1], filteredArgs[2], { verbose, json }).catch(e => {
|
|
385
554
|
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
386
555
|
process.exit(1);
|
|
387
556
|
});
|
|
@@ -399,10 +568,16 @@ switch (first) {
|
|
|
399
568
|
helpCommand();
|
|
400
569
|
break;
|
|
401
570
|
|
|
402
|
-
default:
|
|
403
|
-
|
|
571
|
+
default: {
|
|
572
|
+
const taskText = filteredArgs.join(' ').trim();
|
|
573
|
+
if (!taskText) {
|
|
574
|
+
helpCommand();
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
taskCommand(taskText, { verbose, json }).catch(e => {
|
|
404
578
|
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
405
579
|
process.exit(1);
|
|
406
580
|
});
|
|
407
581
|
break;
|
|
582
|
+
}
|
|
408
583
|
}
|