kroki 1.0.4 → 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 +167 -28
- package/package.json +1 -1
package/bin/kroki.js
CHANGED
|
@@ -270,7 +270,54 @@ Requires an active \x1b[33mUltra\x1b[0m plan.
|
|
|
270
270
|
console.log(' \x1b[33mnpx kroki run "My Workflow Name"\x1b[0m\n');
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
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 } = {}) {
|
|
274
321
|
const session = getActiveSession();
|
|
275
322
|
if (!session) {
|
|
276
323
|
console.error('\x1b[31mError: Not paired.\x1b[0m');
|
|
@@ -294,31 +341,52 @@ async function taskCommand(taskText) {
|
|
|
294
341
|
},
|
|
295
342
|
};
|
|
296
343
|
|
|
344
|
+
const initialMsg = `\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Running: "${taskText}"...`;
|
|
345
|
+
const tracker = createProgressTracker({ verbose, isJson: json, initialText: initialMsg });
|
|
346
|
+
|
|
297
347
|
try {
|
|
298
|
-
console.log(`\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Running: "${taskText}"...`);
|
|
299
348
|
const result = await conn.sendCommand(command, ev => {
|
|
300
349
|
if (ev.kind === 'execution') {
|
|
301
350
|
const details = ev.payload?.data?.details || ev.payload?.title || ev.payload?.type;
|
|
302
|
-
if (details
|
|
303
|
-
const firstLine = details.split('\n')[0].trim();
|
|
304
|
-
if (firstLine) console.log(` \x1b[90m→ ${firstLine.slice(0, 80)}\x1b[0m`);
|
|
305
|
-
}
|
|
351
|
+
if (details) tracker.onStep(details);
|
|
306
352
|
}
|
|
307
353
|
});
|
|
308
354
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
console.log(`\n\x1b[1m${result.message}\x1b[0m\n`);
|
|
313
|
-
}
|
|
355
|
+
tracker.stop();
|
|
356
|
+
|
|
357
|
+
if (json) {
|
|
314
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
|
+
}
|
|
315
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('');
|
|
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);
|
|
316
384
|
} finally {
|
|
317
385
|
conn.close();
|
|
318
386
|
}
|
|
319
387
|
}
|
|
320
388
|
|
|
321
|
-
async function workflowCommand(nameOrId, varsJson) {
|
|
389
|
+
async function workflowCommand(nameOrId, varsJson, { verbose = false, json = false } = {}) {
|
|
322
390
|
const session = getActiveSession();
|
|
323
391
|
if (!session) {
|
|
324
392
|
console.error('\x1b[31mError: Not paired.\x1b[0m');
|
|
@@ -352,29 +420,75 @@ async function workflowCommand(nameOrId, varsJson) {
|
|
|
352
420
|
},
|
|
353
421
|
};
|
|
354
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
|
+
|
|
355
426
|
try {
|
|
356
|
-
console.log(`\x1b[36m[Kroki: ${session.deviceName || 'Browser'}]\x1b[0m Executing workflow "${nameOrId}"...`);
|
|
357
427
|
const result = await conn.sendCommand(command, ev => {
|
|
358
428
|
if (ev.kind === 'workflow_step_start') {
|
|
359
|
-
|
|
429
|
+
const stepDesc = `[step] ${ev.payload?.stepType || 'action'}: ${ev.payload?.comment || ''}`;
|
|
430
|
+
tracker.onStep(stepDesc);
|
|
360
431
|
}
|
|
361
432
|
});
|
|
362
433
|
|
|
363
|
-
|
|
364
|
-
|
|
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);
|
|
365
459
|
} finally {
|
|
366
460
|
conn.close();
|
|
367
461
|
}
|
|
368
462
|
}
|
|
369
463
|
|
|
370
|
-
function statusCommand() {
|
|
464
|
+
function statusCommand({ json = false } = {}) {
|
|
371
465
|
const session = getActiveSession();
|
|
372
466
|
if (!session) {
|
|
467
|
+
if (json) {
|
|
468
|
+
console.log(JSON.stringify({ paired: false }, null, 2));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
373
471
|
console.log('No active paired profile.');
|
|
374
472
|
console.log('To pair, get a code from https://kroki.ai/#/devices and run:');
|
|
375
473
|
console.log(' npx kroki pair <CODE>');
|
|
376
474
|
return;
|
|
377
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
|
+
}
|
|
378
492
|
console.log(
|
|
379
493
|
`\x1b[32m✔ Paired\x1b[0m with "${session.deviceName || session.deviceId}" (${session.platform || 'browser'})`,
|
|
380
494
|
);
|
|
@@ -386,11 +500,16 @@ function helpCommand() {
|
|
|
386
500
|
\x1b[1mKroki CLI\x1b[0m — Browser Automation & AI Agent Bridge for Google Chrome
|
|
387
501
|
|
|
388
502
|
\x1b[1mUsage:\x1b[0m
|
|
389
|
-
npx kroki
|
|
390
|
-
npx kroki "<
|
|
391
|
-
npx kroki
|
|
392
|
-
npx kroki status
|
|
393
|
-
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
|
|
394
513
|
|
|
395
514
|
\x1b[1mDashboard:\x1b[0m
|
|
396
515
|
https://kroki.ai/#/devices
|
|
@@ -400,24 +519,38 @@ function helpCommand() {
|
|
|
400
519
|
// ── Entry Point ─────────────────────────────────────────────────────────────
|
|
401
520
|
|
|
402
521
|
const rawArgs = process.argv.slice(2);
|
|
403
|
-
|
|
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';
|
|
404
537
|
|
|
405
538
|
switch (first) {
|
|
406
539
|
case 'pair':
|
|
407
540
|
case 'connect':
|
|
408
|
-
pairCommand(
|
|
541
|
+
pairCommand(filteredArgs[1]).catch(e => {
|
|
409
542
|
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
410
543
|
process.exit(1);
|
|
411
544
|
});
|
|
412
545
|
break;
|
|
413
546
|
|
|
414
547
|
case 'status':
|
|
415
|
-
statusCommand();
|
|
548
|
+
statusCommand({ json });
|
|
416
549
|
break;
|
|
417
550
|
|
|
418
551
|
case 'run':
|
|
419
552
|
case 'workflow':
|
|
420
|
-
workflowCommand(
|
|
553
|
+
workflowCommand(filteredArgs[1], filteredArgs[2], { verbose, json }).catch(e => {
|
|
421
554
|
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
422
555
|
process.exit(1);
|
|
423
556
|
});
|
|
@@ -435,10 +568,16 @@ switch (first) {
|
|
|
435
568
|
helpCommand();
|
|
436
569
|
break;
|
|
437
570
|
|
|
438
|
-
default:
|
|
439
|
-
|
|
571
|
+
default: {
|
|
572
|
+
const taskText = filteredArgs.join(' ').trim();
|
|
573
|
+
if (!taskText) {
|
|
574
|
+
helpCommand();
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
taskCommand(taskText, { verbose, json }).catch(e => {
|
|
440
578
|
console.error(`\x1b[31mError:\x1b[0m ${e.message}`);
|
|
441
579
|
process.exit(1);
|
|
442
580
|
});
|
|
443
581
|
break;
|
|
582
|
+
}
|
|
444
583
|
}
|