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.
Files changed (2) hide show
  1. package/bin/kroki.js +167 -28
  2. 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
- async function taskCommand(taskText) {
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 && typeof details === 'string') {
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
- console.log('\x1b[32m✔ Task completed.\x1b[0m');
310
- if (result && typeof result === 'object') {
311
- if (result.message) {
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
- console.log(` \x1b[90m[step] ${ev.payload?.stepType || 'action'}: ${ev.payload?.comment || ''}\x1b[0m`);
429
+ const stepDesc = `[step] ${ev.payload?.stepType || 'action'}: ${ev.payload?.comment || ''}`;
430
+ tracker.onStep(stepDesc);
360
431
  }
361
432
  });
362
433
 
363
- console.log('\x1b[32m✔ Workflow finished.\x1b[0m');
364
- if (result) console.log(JSON.stringify(result, null, 2));
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 pair <CODE> Pair with a browser profile (from https://kroki.ai/#/devices)
390
- npx kroki "<task>" Send an AI task to the live browser
391
- npx kroki run "<workflow>" [json] Execute a saved workflow
392
- npx kroki status View paired profile and status
393
- npx kroki clear Disconnect active pairing session
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
- const first = rawArgs[0] || 'help';
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(rawArgs[1]).catch(e => {
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(rawArgs[1], rawArgs[2]).catch(e => {
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
- taskCommand(rawArgs.join(' ')).catch(e => {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kroki",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Kroki Browser Automation CLI — Control Chrome from terminal and AI agents",
5
5
  "keywords": [
6
6
  "kroki",