@h-rig/cli 0.0.6-alpha.77 → 0.0.6-alpha.78

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.
@@ -311,7 +311,9 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
311
311
  })() : null;
312
312
  if (!response.ok) {
313
313
  const diagnostics = diagnosticMessage(payload);
314
- const detail = diagnostics ?? (text || response.statusText);
314
+ const rawDetail = diagnostics ?? (text || response.statusText);
315
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
316
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
315
317
  const failure = await buildServerFailureContext(context.projectRoot, server);
316
318
  throw new CliError(`Rig server request failed (${response.status}): ${detail}
317
319
  ${failure.contextLine}`, 1, { hint: failure.hint });
@@ -569,8 +571,8 @@ function createOperatorSurface(options = {}) {
569
571
  }
570
572
 
571
573
  // packages/cli/src/commands/_pi-frontend.ts
572
- import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
573
- import { tmpdir } from "os";
574
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
575
+ import { homedir as homedir2, tmpdir } from "os";
574
576
  import { join } from "path";
575
577
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
576
578
  import createPiRigExtension from "@rig/pi-rig";
@@ -639,8 +641,79 @@ function createTtySpinner(input) {
639
641
  };
640
642
  }
641
643
 
644
+ // packages/cli/src/app/theme.ts
645
+ var RIG_PALETTE = {
646
+ ink: "#f2f3f6",
647
+ ink2: "#aeb0ba",
648
+ ink3: "#6c6e79",
649
+ ink4: "#44464f",
650
+ accent: "#ccff4d",
651
+ accentDim: "#a9d63f",
652
+ cyan: "#56d8ff",
653
+ red: "#ff5d5d",
654
+ yellow: "#ffd24d"
655
+ };
656
+ function hexToRgb(hex) {
657
+ const value = hex.replace("#", "");
658
+ return [
659
+ Number.parseInt(value.slice(0, 2), 16),
660
+ Number.parseInt(value.slice(2, 4), 16),
661
+ Number.parseInt(value.slice(4, 6), 16)
662
+ ];
663
+ }
664
+ function fg(hex) {
665
+ const [r, g, b] = hexToRgb(hex);
666
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
667
+ }
668
+ var ink = fg(RIG_PALETTE.ink);
669
+ var ink2 = fg(RIG_PALETTE.ink2);
670
+ var ink3 = fg(RIG_PALETTE.ink3);
671
+ var ink4 = fg(RIG_PALETTE.ink4);
672
+ var accent = fg(RIG_PALETTE.accent);
673
+ var accentDim = fg(RIG_PALETTE.accentDim);
674
+ var cyan = fg(RIG_PALETTE.cyan);
675
+ var red = fg(RIG_PALETTE.red);
676
+ var yellow = fg(RIG_PALETTE.yellow);
677
+ function bold(text) {
678
+ return `\x1B[1m${text}\x1B[22m`;
679
+ }
680
+ var DRONE_ART = [
681
+ " .-=-. .-=-. ",
682
+ " ( !!! ) ( !!! ) ",
683
+ " '-=-'._ _.'-=-' ",
684
+ " '._ _.' ",
685
+ " '=$$$$$$$=.' ",
686
+ " =$$$$$$$$$$$= ",
687
+ " $$$@@@@@@@@@@$$$ ",
688
+ " $$$@@ @@$$$ ",
689
+ " $$@ ? @$$$ ",
690
+ " $$$@ '-' @$$$ ",
691
+ " $$$@@ @@$$$ ",
692
+ " $$$@@@@@@@@@@$$$ ",
693
+ " =$$$$$$$$$$$= ",
694
+ " '=$$$$$$$=.' ",
695
+ " _.' '._ ",
696
+ " .-=-.' '.-=-. ",
697
+ " ( !!! ) ( !!! ) ",
698
+ " '-=-' '-=-' "
699
+ ];
700
+ var EYE_FRAMES = ["@", "o", "."];
701
+ var DRONE_WIDTH = DRONE_ART[0].length;
702
+ var DRONE_HEIGHT = DRONE_ART.length;
703
+ var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
704
+ function microDroneFrame(tick) {
705
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
706
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
707
+ return `(${blade})${eye}(${blade})`;
708
+ }
709
+ var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
710
+ function renderMicroDroneFrame(tick) {
711
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
712
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
713
+ return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
714
+ }
715
+
642
716
  // packages/cli/src/commands/_async-ui.ts
643
- var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
644
717
  var DONE_SYMBOL = pc.green("\u25C7");
645
718
  var FAIL_SYMBOL = pc.red("\u25A0");
646
719
  var activeUpdate = null;
@@ -674,8 +747,8 @@ async function withSpinner(label, work, options = {}) {
674
747
  const spinner = createTtySpinner({
675
748
  label,
676
749
  output,
677
- frames: CLACK_SPINNER_FRAMES,
678
- styleFrame: (frame) => pc.magenta(frame)
750
+ frames: MICRO_DRONE_FRAMES,
751
+ styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
679
752
  });
680
753
  const update = (next) => {
681
754
  lastLabel = next;
@@ -716,6 +789,7 @@ function buildOperatorPiEnv(input) {
716
789
  return {
717
790
  PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
718
791
  PI_SKIP_VERSION_CHECK: "1",
792
+ PI_HIDDEN_COMMANDS: "import,fork,clone,tree,new,resume,trust",
719
793
  RIG_PI_OPERATOR_SESSION: "1",
720
794
  RIG_RUN_ID: input.runId,
721
795
  RIG_SERVER_URL: input.serverUrl,
@@ -725,6 +799,10 @@ function buildOperatorPiEnv(input) {
725
799
  }
726
800
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
727
801
  const server = await ensureServerForCli(context.projectRoot);
802
+ const localCwd = join(homedir2(), ".rig", "drones", runId.slice(0, 8));
803
+ mkdirSync2(localCwd, { recursive: true });
804
+ trustDroneCwd(localCwd);
805
+ installRigPiTheme();
728
806
  let sessionFileArg = [];
729
807
  try {
730
808
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/session-file`);
@@ -738,7 +816,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
738
816
  try {
739
817
  const header = JSON.parse(line);
740
818
  if (header.type === "session" && typeof header.cwd === "string") {
741
- return JSON.stringify({ ...header, cwd: process.cwd() });
819
+ return JSON.stringify({ ...header, cwd: localCwd });
742
820
  }
743
821
  } catch {}
744
822
  return line;
@@ -750,6 +828,105 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
750
828
  } catch {}
751
829
  return { server, sessionFileArg };
752
830
  }
831
+ var RIG_PI_THEME = {
832
+ $schema: "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
833
+ name: "rig",
834
+ vars: {
835
+ acid: "#ccff4d",
836
+ acidDim: "#a9d63f",
837
+ cyan: "#56d8ff",
838
+ red: "#ff5d5d",
839
+ yellow: "#ffd24d",
840
+ ink: "#f2f3f6",
841
+ ink2: "#aeb0ba",
842
+ ink3: "#6c6e79",
843
+ ink4: "#44464f",
844
+ panel: "#101115",
845
+ panelUser: "#14161b",
846
+ toolPending: "#0e1013",
847
+ toolSuccess: "#10150c",
848
+ toolError: "#1a0f0f",
849
+ customMsg: "#0f1410"
850
+ },
851
+ colors: {
852
+ accent: "acid",
853
+ border: "ink4",
854
+ borderAccent: "acid",
855
+ borderMuted: "ink4",
856
+ success: "acid",
857
+ error: "red",
858
+ warning: "yellow",
859
+ muted: "ink3",
860
+ dim: "ink4",
861
+ text: "ink",
862
+ thinkingText: "ink3",
863
+ selectedBg: "panel",
864
+ userMessageBg: "panelUser",
865
+ userMessageText: "ink",
866
+ customMessageBg: "customMsg",
867
+ customMessageText: "ink2",
868
+ customMessageLabel: "acidDim",
869
+ toolPendingBg: "toolPending",
870
+ toolSuccessBg: "toolSuccess",
871
+ toolErrorBg: "toolError",
872
+ toolTitle: "ink",
873
+ toolOutput: "ink3",
874
+ mdHeading: "acid",
875
+ mdLink: "cyan",
876
+ mdLinkUrl: "ink4",
877
+ mdCode: "acidDim",
878
+ mdCodeBlock: "ink2",
879
+ mdCodeBlockBorder: "ink4",
880
+ mdQuote: "ink3",
881
+ mdQuoteBorder: "ink4",
882
+ mdHr: "ink4",
883
+ mdListBullet: "acid",
884
+ toolDiffAdded: "acid",
885
+ toolDiffRemoved: "red",
886
+ toolDiffContext: "ink3",
887
+ syntaxComment: "ink3",
888
+ syntaxKeyword: "cyan",
889
+ syntaxFunction: "acid",
890
+ syntaxVariable: "ink",
891
+ syntaxString: "acidDim",
892
+ syntaxNumber: "yellow",
893
+ syntaxType: "cyan",
894
+ syntaxOperator: "ink2",
895
+ syntaxPunctuation: "ink3",
896
+ thinkingOff: "ink4",
897
+ thinkingMinimal: "ink3",
898
+ thinkingLow: "ink2",
899
+ thinkingMedium: "cyan",
900
+ thinkingHigh: "acidDim",
901
+ thinkingXhigh: "acid",
902
+ bashMode: "cyan"
903
+ }
904
+ };
905
+ function trustDroneCwd(localCwd) {
906
+ try {
907
+ const agentDir = join(homedir2(), ".pi", "agent");
908
+ mkdirSync2(agentDir, { recursive: true });
909
+ const trustPath = join(agentDir, "trust.json");
910
+ const store = existsSync3(trustPath) ? JSON.parse(readFileSync3(trustPath, "utf8")) : {};
911
+ if (store[localCwd] !== true) {
912
+ store[localCwd] = true;
913
+ writeFileSync2(trustPath, `${JSON.stringify(store, null, "\t")}
914
+ `);
915
+ }
916
+ } catch {}
917
+ }
918
+ function installRigPiTheme() {
919
+ try {
920
+ const themesDir = join(homedir2(), ".pi", "agent", "themes");
921
+ mkdirSync2(themesDir, { recursive: true });
922
+ const themePath = join(themesDir, "rig.json");
923
+ const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
924
+ `;
925
+ if (!existsSync3(themePath) || readFileSync3(themePath, "utf8") !== next) {
926
+ writeFileSync2(themePath, next);
927
+ }
928
+ } catch {}
929
+ }
753
930
  async function attachRunBundledPiFrontend(context, input) {
754
931
  const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
755
932
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/_pi-frontend.ts
3
- import { mkdtempSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
4
- import { tmpdir } from "os";
3
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "fs";
4
+ import { homedir as homedir2, tmpdir } from "os";
5
5
  import { join } from "path";
6
6
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
7
7
  import createPiRigExtension from "@rig/pi-rig";
@@ -318,7 +318,9 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
318
318
  })() : null;
319
319
  if (!response.ok) {
320
320
  const diagnostics = diagnosticMessage(payload);
321
- const detail = diagnostics ?? (text || response.statusText);
321
+ const rawDetail = diagnostics ?? (text || response.statusText);
322
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
323
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
322
324
  const failure = await buildServerFailureContext(context.projectRoot, server);
323
325
  throw new CliError(`Rig server request failed (${response.status}): ${detail}
324
326
  ${failure.contextLine}`, 1, { hint: failure.hint });
@@ -405,8 +407,79 @@ function createTtySpinner(input) {
405
407
  };
406
408
  }
407
409
 
410
+ // packages/cli/src/app/theme.ts
411
+ var RIG_PALETTE = {
412
+ ink: "#f2f3f6",
413
+ ink2: "#aeb0ba",
414
+ ink3: "#6c6e79",
415
+ ink4: "#44464f",
416
+ accent: "#ccff4d",
417
+ accentDim: "#a9d63f",
418
+ cyan: "#56d8ff",
419
+ red: "#ff5d5d",
420
+ yellow: "#ffd24d"
421
+ };
422
+ function hexToRgb(hex) {
423
+ const value = hex.replace("#", "");
424
+ return [
425
+ Number.parseInt(value.slice(0, 2), 16),
426
+ Number.parseInt(value.slice(2, 4), 16),
427
+ Number.parseInt(value.slice(4, 6), 16)
428
+ ];
429
+ }
430
+ function fg(hex) {
431
+ const [r, g, b] = hexToRgb(hex);
432
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
433
+ }
434
+ var ink = fg(RIG_PALETTE.ink);
435
+ var ink2 = fg(RIG_PALETTE.ink2);
436
+ var ink3 = fg(RIG_PALETTE.ink3);
437
+ var ink4 = fg(RIG_PALETTE.ink4);
438
+ var accent = fg(RIG_PALETTE.accent);
439
+ var accentDim = fg(RIG_PALETTE.accentDim);
440
+ var cyan = fg(RIG_PALETTE.cyan);
441
+ var red = fg(RIG_PALETTE.red);
442
+ var yellow = fg(RIG_PALETTE.yellow);
443
+ function bold(text) {
444
+ return `\x1B[1m${text}\x1B[22m`;
445
+ }
446
+ var DRONE_ART = [
447
+ " .-=-. .-=-. ",
448
+ " ( !!! ) ( !!! ) ",
449
+ " '-=-'._ _.'-=-' ",
450
+ " '._ _.' ",
451
+ " '=$$$$$$$=.' ",
452
+ " =$$$$$$$$$$$= ",
453
+ " $$$@@@@@@@@@@$$$ ",
454
+ " $$$@@ @@$$$ ",
455
+ " $$@ ? @$$$ ",
456
+ " $$$@ '-' @$$$ ",
457
+ " $$$@@ @@$$$ ",
458
+ " $$$@@@@@@@@@@$$$ ",
459
+ " =$$$$$$$$$$$= ",
460
+ " '=$$$$$$$=.' ",
461
+ " _.' '._ ",
462
+ " .-=-.' '.-=-. ",
463
+ " ( !!! ) ( !!! ) ",
464
+ " '-=-' '-=-' "
465
+ ];
466
+ var EYE_FRAMES = ["@", "o", "."];
467
+ var DRONE_WIDTH = DRONE_ART[0].length;
468
+ var DRONE_HEIGHT = DRONE_ART.length;
469
+ var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
470
+ function microDroneFrame(tick) {
471
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
472
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
473
+ return `(${blade})${eye}(${blade})`;
474
+ }
475
+ var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
476
+ function renderMicroDroneFrame(tick) {
477
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
478
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
479
+ return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
480
+ }
481
+
408
482
  // packages/cli/src/commands/_async-ui.ts
409
- var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
410
483
  var DONE_SYMBOL = pc.green("\u25C7");
411
484
  var FAIL_SYMBOL = pc.red("\u25A0");
412
485
  var activeUpdate = null;
@@ -440,8 +513,8 @@ async function withSpinner(label, work, options = {}) {
440
513
  const spinner = createTtySpinner({
441
514
  label,
442
515
  output,
443
- frames: CLACK_SPINNER_FRAMES,
444
- styleFrame: (frame) => pc.magenta(frame)
516
+ frames: MICRO_DRONE_FRAMES,
517
+ styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
445
518
  });
446
519
  const update = (next) => {
447
520
  lastLabel = next;
@@ -482,6 +555,7 @@ function buildOperatorPiEnv(input) {
482
555
  return {
483
556
  PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
484
557
  PI_SKIP_VERSION_CHECK: "1",
558
+ PI_HIDDEN_COMMANDS: "import,fork,clone,tree,new,resume,trust",
485
559
  RIG_PI_OPERATOR_SESSION: "1",
486
560
  RIG_RUN_ID: input.runId,
487
561
  RIG_SERVER_URL: input.serverUrl,
@@ -491,6 +565,10 @@ function buildOperatorPiEnv(input) {
491
565
  }
492
566
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
493
567
  const server = await ensureServerForCli(context.projectRoot);
568
+ const localCwd = join(homedir2(), ".rig", "drones", runId.slice(0, 8));
569
+ mkdirSync2(localCwd, { recursive: true });
570
+ trustDroneCwd(localCwd);
571
+ installRigPiTheme();
494
572
  let sessionFileArg = [];
495
573
  try {
496
574
  const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/session-file`);
@@ -504,7 +582,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
504
582
  try {
505
583
  const header = JSON.parse(line);
506
584
  if (header.type === "session" && typeof header.cwd === "string") {
507
- return JSON.stringify({ ...header, cwd: process.cwd() });
585
+ return JSON.stringify({ ...header, cwd: localCwd });
508
586
  }
509
587
  } catch {}
510
588
  return line;
@@ -516,6 +594,105 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
516
594
  } catch {}
517
595
  return { server, sessionFileArg };
518
596
  }
597
+ var RIG_PI_THEME = {
598
+ $schema: "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
599
+ name: "rig",
600
+ vars: {
601
+ acid: "#ccff4d",
602
+ acidDim: "#a9d63f",
603
+ cyan: "#56d8ff",
604
+ red: "#ff5d5d",
605
+ yellow: "#ffd24d",
606
+ ink: "#f2f3f6",
607
+ ink2: "#aeb0ba",
608
+ ink3: "#6c6e79",
609
+ ink4: "#44464f",
610
+ panel: "#101115",
611
+ panelUser: "#14161b",
612
+ toolPending: "#0e1013",
613
+ toolSuccess: "#10150c",
614
+ toolError: "#1a0f0f",
615
+ customMsg: "#0f1410"
616
+ },
617
+ colors: {
618
+ accent: "acid",
619
+ border: "ink4",
620
+ borderAccent: "acid",
621
+ borderMuted: "ink4",
622
+ success: "acid",
623
+ error: "red",
624
+ warning: "yellow",
625
+ muted: "ink3",
626
+ dim: "ink4",
627
+ text: "ink",
628
+ thinkingText: "ink3",
629
+ selectedBg: "panel",
630
+ userMessageBg: "panelUser",
631
+ userMessageText: "ink",
632
+ customMessageBg: "customMsg",
633
+ customMessageText: "ink2",
634
+ customMessageLabel: "acidDim",
635
+ toolPendingBg: "toolPending",
636
+ toolSuccessBg: "toolSuccess",
637
+ toolErrorBg: "toolError",
638
+ toolTitle: "ink",
639
+ toolOutput: "ink3",
640
+ mdHeading: "acid",
641
+ mdLink: "cyan",
642
+ mdLinkUrl: "ink4",
643
+ mdCode: "acidDim",
644
+ mdCodeBlock: "ink2",
645
+ mdCodeBlockBorder: "ink4",
646
+ mdQuote: "ink3",
647
+ mdQuoteBorder: "ink4",
648
+ mdHr: "ink4",
649
+ mdListBullet: "acid",
650
+ toolDiffAdded: "acid",
651
+ toolDiffRemoved: "red",
652
+ toolDiffContext: "ink3",
653
+ syntaxComment: "ink3",
654
+ syntaxKeyword: "cyan",
655
+ syntaxFunction: "acid",
656
+ syntaxVariable: "ink",
657
+ syntaxString: "acidDim",
658
+ syntaxNumber: "yellow",
659
+ syntaxType: "cyan",
660
+ syntaxOperator: "ink2",
661
+ syntaxPunctuation: "ink3",
662
+ thinkingOff: "ink4",
663
+ thinkingMinimal: "ink3",
664
+ thinkingLow: "ink2",
665
+ thinkingMedium: "cyan",
666
+ thinkingHigh: "acidDim",
667
+ thinkingXhigh: "acid",
668
+ bashMode: "cyan"
669
+ }
670
+ };
671
+ function trustDroneCwd(localCwd) {
672
+ try {
673
+ const agentDir = join(homedir2(), ".pi", "agent");
674
+ mkdirSync2(agentDir, { recursive: true });
675
+ const trustPath = join(agentDir, "trust.json");
676
+ const store = existsSync3(trustPath) ? JSON.parse(readFileSync3(trustPath, "utf8")) : {};
677
+ if (store[localCwd] !== true) {
678
+ store[localCwd] = true;
679
+ writeFileSync2(trustPath, `${JSON.stringify(store, null, "\t")}
680
+ `);
681
+ }
682
+ } catch {}
683
+ }
684
+ function installRigPiTheme() {
685
+ try {
686
+ const themesDir = join(homedir2(), ".pi", "agent", "themes");
687
+ mkdirSync2(themesDir, { recursive: true });
688
+ const themePath = join(themesDir, "rig.json");
689
+ const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
690
+ `;
691
+ if (!existsSync3(themePath) || readFileSync3(themePath, "utf8") !== next) {
692
+ writeFileSync2(themePath, next);
693
+ }
694
+ } catch {}
695
+ }
519
696
  async function attachRunBundledPiFrontend(context, input) {
520
697
  const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
521
698
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -305,7 +305,9 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
305
305
  })() : null;
306
306
  if (!response.ok) {
307
307
  const diagnostics = diagnosticMessage(payload);
308
- const detail = diagnostics ?? (text || response.statusText);
308
+ const rawDetail = diagnostics ?? (text || response.statusText);
309
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
310
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
309
311
  const failure = await buildServerFailureContext(context.projectRoot, server);
310
312
  throw new CliError(`Rig server request failed (${response.status}): ${detail}
311
313
  ${failure.contextLine}`, 1, { hint: failure.hint });
@@ -325,7 +325,9 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
325
325
  })() : null;
326
326
  if (!response.ok) {
327
327
  const diagnostics = diagnosticMessage(payload);
328
- const detail = diagnostics ?? (text || response.statusText);
328
+ const rawDetail = diagnostics ?? (text || response.statusText);
329
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
330
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
329
331
  const failure = await buildServerFailureContext(context.projectRoot, server);
330
332
  throw new CliError(`Rig server request failed (${response.status}): ${detail}
331
333
  ${failure.contextLine}`, 1, { hint: failure.hint });
@@ -310,7 +310,9 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
310
310
  })() : null;
311
311
  if (!response.ok) {
312
312
  const diagnostics = diagnosticMessage(payload);
313
- const detail = diagnostics ?? (text || response.statusText);
313
+ const rawDetail = diagnostics ?? (text || response.statusText);
314
+ const detail = diagnostics ? rawDetail : rawDetail.split(`
315
+ `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
314
316
  const failure = await buildServerFailureContext(context.projectRoot, server);
315
317
  throw new CliError(`Rig server request failed (${response.status}): ${detail}
316
318
  ${failure.contextLine}`, 1, { hint: failure.hint });
@@ -1,6 +1,131 @@
1
1
  // @bun
2
- // packages/cli/src/commands/_task-picker.ts
3
- import { cancel, isCancel, select } from "@clack/prompts";
2
+ // packages/cli/src/app/drone-ui.ts
3
+ import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
4
+
5
+ // packages/cli/src/app/theme.ts
6
+ var RIG_PALETTE = {
7
+ ink: "#f2f3f6",
8
+ ink2: "#aeb0ba",
9
+ ink3: "#6c6e79",
10
+ ink4: "#44464f",
11
+ accent: "#ccff4d",
12
+ accentDim: "#a9d63f",
13
+ cyan: "#56d8ff",
14
+ red: "#ff5d5d",
15
+ yellow: "#ffd24d"
16
+ };
17
+ function hexToRgb(hex) {
18
+ const value = hex.replace("#", "");
19
+ return [
20
+ Number.parseInt(value.slice(0, 2), 16),
21
+ Number.parseInt(value.slice(2, 4), 16),
22
+ Number.parseInt(value.slice(4, 6), 16)
23
+ ];
24
+ }
25
+ function fg(hex) {
26
+ const [r, g, b] = hexToRgb(hex);
27
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
28
+ }
29
+ var ink = fg(RIG_PALETTE.ink);
30
+ var ink2 = fg(RIG_PALETTE.ink2);
31
+ var ink3 = fg(RIG_PALETTE.ink3);
32
+ var ink4 = fg(RIG_PALETTE.ink4);
33
+ var accent = fg(RIG_PALETTE.accent);
34
+ var accentDim = fg(RIG_PALETTE.accentDim);
35
+ var cyan = fg(RIG_PALETTE.cyan);
36
+ var red = fg(RIG_PALETTE.red);
37
+ var yellow = fg(RIG_PALETTE.yellow);
38
+ function bold(text) {
39
+ return `\x1B[1m${text}\x1B[22m`;
40
+ }
41
+ var DRONE_ART = [
42
+ " .-=-. .-=-. ",
43
+ " ( !!! ) ( !!! ) ",
44
+ " '-=-'._ _.'-=-' ",
45
+ " '._ _.' ",
46
+ " '=$$$$$$$=.' ",
47
+ " =$$$$$$$$$$$= ",
48
+ " $$$@@@@@@@@@@$$$ ",
49
+ " $$$@@ @@$$$ ",
50
+ " $$@ ? @$$$ ",
51
+ " $$$@ '-' @$$$ ",
52
+ " $$$@@ @@$$$ ",
53
+ " $$$@@@@@@@@@@$$$ ",
54
+ " =$$$$$$$$$$$= ",
55
+ " '=$$$$$$$=.' ",
56
+ " _.' '._ ",
57
+ " .-=-.' '.-=-. ",
58
+ " ( !!! ) ( !!! ) ",
59
+ " '-=-' '-=-' "
60
+ ];
61
+ var EYE_FRAMES = ["@", "o", "."];
62
+ var DRONE_WIDTH = DRONE_ART[0].length;
63
+ var DRONE_HEIGHT = DRONE_ART.length;
64
+ var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
65
+ function microDroneFrame(tick) {
66
+ const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
67
+ const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
68
+ return `(${blade})${eye}(${blade})`;
69
+ }
70
+ var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
71
+
72
+ // packages/cli/src/app/drone-ui.ts
73
+ var isTty = () => Boolean(process.stdout.isTTY);
74
+ function droneCancel(text) {
75
+ console.log(` ${red("\u2716")} ${ink3(text)}`);
76
+ }
77
+ var SELECT_THEME = {
78
+ selectedPrefix: (text) => accent(text),
79
+ selectedText: (text) => bold(ink(text)),
80
+ description: (text) => ink3(text),
81
+ scrollInfo: (text) => ink4(text),
82
+ noMatch: (text) => ink3(text)
83
+ };
84
+ async function runMiniTui(build) {
85
+ const terminal = new ProcessTerminal;
86
+ const tui = new TUI(terminal);
87
+ let settled = false;
88
+ return await new Promise((resolve) => {
89
+ const finish = (result) => {
90
+ if (settled)
91
+ return;
92
+ settled = true;
93
+ tui.stop();
94
+ resolve(result);
95
+ };
96
+ build(tui, finish);
97
+ tui.start();
98
+ });
99
+ }
100
+ async function droneSelect(input) {
101
+ if (!isTty() || input.options.length === 0) {
102
+ return input.initialValue ?? input.options[0]?.value ?? null;
103
+ }
104
+ return runMiniTui((tui, finish) => {
105
+ tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
106
+ const items = input.options.map((option) => ({
107
+ value: option.value,
108
+ label: option.label,
109
+ ...option.hint ? { description: option.hint } : {}
110
+ }));
111
+ const list = new SelectList(items, Math.min(items.length, 12), SELECT_THEME);
112
+ const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
113
+ if (initialIndex > 0)
114
+ list.setSelectedIndex(initialIndex);
115
+ list.onSelect = (item) => finish(item.value);
116
+ list.onCancel = () => finish(null);
117
+ tui.addChild(list);
118
+ tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
119
+ tui.setFocus(list);
120
+ tui.addInputListener((data) => {
121
+ if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
122
+ finish(null);
123
+ return { consume: true };
124
+ }
125
+ return;
126
+ });
127
+ });
128
+ }
4
129
 
5
130
  // packages/cli/src/commands/_operator-surface.ts
6
131
  import { createInterface as createPromptInterface } from "readline/promises";
@@ -34,8 +159,8 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
34
159
  return null;
35
160
  if (tasks.length === 1)
36
161
  return tasks[0];
37
- const isTty = io.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
38
- if (!isTty) {
162
+ const isTty2 = io.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
163
+ if (!isTty2) {
39
164
  throw new Error("task run requires an interactive terminal to pick a task; pass --task <id>, --next, or --detach with a task id.");
40
165
  }
41
166
  if (io.prompt || io.renderer) {
@@ -59,12 +184,12 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
59
184
  label: `${taskId2(task)} \xB7 ${typeof task.title === "string" && task.title.trim() ? task.title.trim() : "Untitled task"}`,
60
185
  hint: typeof task.status === "string" && task.status.trim() ? task.status.trim() : undefined
61
186
  }));
62
- const answer = await select({
187
+ const answer = await droneSelect({
63
188
  message: "Select Rig task",
64
189
  options
65
190
  });
66
- if (isCancel(answer)) {
67
- cancel("No task selected.");
191
+ if (answer === null) {
192
+ droneCancel("No task selected.");
68
193
  return null;
69
194
  }
70
195
  const index = Number.parseInt(String(answer), 10);