@h-rig/cli 0.0.6-alpha.74 → 0.0.6-alpha.76

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.
@@ -574,98 +574,6 @@ import { tmpdir } from "os";
574
574
  import { join } from "path";
575
575
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
576
576
  import createPiRigExtension from "@rig/pi-rig";
577
- function setTemporaryEnv(updates) {
578
- const previous = new Map;
579
- for (const [key, value] of Object.entries(updates)) {
580
- previous.set(key, process.env[key]);
581
- process.env[key] = value;
582
- }
583
- return () => {
584
- for (const [key, value] of previous) {
585
- if (value === undefined)
586
- delete process.env[key];
587
- else
588
- process.env[key] = value;
589
- }
590
- };
591
- }
592
- function buildOperatorPiEnv(input) {
593
- return {
594
- PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
595
- PI_SKIP_VERSION_CHECK: "1",
596
- RIG_PI_OPERATOR_SESSION: "1",
597
- RIG_RUN_ID: input.runId,
598
- RIG_SERVER_URL: input.serverUrl,
599
- ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
600
- ...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
601
- };
602
- }
603
- async function attachRunBundledPiFrontend(context, input) {
604
- const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
605
- const server = await ensureServerForCli(context.projectRoot);
606
- let sessionFileArg = [];
607
- try {
608
- const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(input.runId)}/session-file`);
609
- if (payload?.ok && typeof payload.content === "string" && payload.content.trim()) {
610
- const fileName = typeof payload.fileName === "string" && payload.fileName.trim() ? payload.fileName.replace(/[^A-Za-z0-9._-]/g, "_") : `rig-run-${input.runId}.jsonl`;
611
- const localSessionPath = join(tempSessionDir, fileName);
612
- const content = payload.content.split(`
613
- `).map((line, index) => {
614
- if (index > 0 || !line.trim())
615
- return line;
616
- try {
617
- const header = JSON.parse(line);
618
- if (header.type === "session" && typeof header.cwd === "string") {
619
- return JSON.stringify({ ...header, cwd: process.cwd() });
620
- }
621
- } catch {}
622
- return line;
623
- }).join(`
624
- `);
625
- writeFileSync2(localSessionPath, content);
626
- sessionFileArg = ["--session", localSessionPath];
627
- }
628
- } catch {}
629
- const restoreEnv = setTemporaryEnv(buildOperatorPiEnv({
630
- runId: input.runId,
631
- serverUrl: server.baseUrl,
632
- authToken: server.authToken,
633
- serverProjectRoot: server.serverProjectRoot,
634
- sessionDir: tempSessionDir
635
- }));
636
- const piRigExtensionFactory = (pi) => {
637
- createPiRigExtension(pi);
638
- };
639
- let detached = false;
640
- try {
641
- await runPiMain([
642
- "--no-extensions",
643
- "--no-skills",
644
- "--no-prompt-templates",
645
- "--no-context-files",
646
- ...sessionFileArg
647
- ], {
648
- extensionFactories: [piRigExtensionFactory]
649
- });
650
- detached = true;
651
- } finally {
652
- restoreEnv();
653
- rmSync(tempSessionDir, { recursive: true, force: true });
654
- }
655
- let run = { runId: input.runId, status: "unknown" };
656
- try {
657
- run = await getRunDetailsViaServer(context, input.runId);
658
- } catch {}
659
- return {
660
- run,
661
- logs: [],
662
- timeline: [],
663
- timelineCursor: null,
664
- steered: input.steered === true,
665
- detached,
666
- rendered: "stock Pi operator console with the pi-rig extension"
667
- };
668
- }
669
577
 
670
578
  // packages/cli/src/commands/_async-ui.ts
671
579
  import pc from "picocolors";
@@ -788,6 +696,104 @@ async function withSpinner(label, work, options = {}) {
788
696
  }
789
697
  }
790
698
 
699
+ // packages/cli/src/commands/_pi-frontend.ts
700
+ function setTemporaryEnv(updates) {
701
+ const previous = new Map;
702
+ for (const [key, value] of Object.entries(updates)) {
703
+ previous.set(key, process.env[key]);
704
+ process.env[key] = value;
705
+ }
706
+ return () => {
707
+ for (const [key, value] of previous) {
708
+ if (value === undefined)
709
+ delete process.env[key];
710
+ else
711
+ process.env[key] = value;
712
+ }
713
+ };
714
+ }
715
+ function buildOperatorPiEnv(input) {
716
+ return {
717
+ PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
718
+ PI_SKIP_VERSION_CHECK: "1",
719
+ RIG_PI_OPERATOR_SESSION: "1",
720
+ RIG_RUN_ID: input.runId,
721
+ RIG_SERVER_URL: input.serverUrl,
722
+ ...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
723
+ ...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
724
+ };
725
+ }
726
+ async function prepareOperatorConsole(context, runId, tempSessionDir) {
727
+ const server = await ensureServerForCli(context.projectRoot);
728
+ let sessionFileArg = [];
729
+ try {
730
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/session-file`);
731
+ if (payload?.ok && typeof payload.content === "string" && payload.content.trim()) {
732
+ const fileName = typeof payload.fileName === "string" && payload.fileName.trim() ? payload.fileName.replace(/[^A-Za-z0-9._-]/g, "_") : `rig-run-${runId}.jsonl`;
733
+ const localSessionPath = join(tempSessionDir, fileName);
734
+ const content = payload.content.split(`
735
+ `).map((line, index) => {
736
+ if (index > 0 || !line.trim())
737
+ return line;
738
+ try {
739
+ const header = JSON.parse(line);
740
+ if (header.type === "session" && typeof header.cwd === "string") {
741
+ return JSON.stringify({ ...header, cwd: process.cwd() });
742
+ }
743
+ } catch {}
744
+ return line;
745
+ }).join(`
746
+ `);
747
+ writeFileSync2(localSessionPath, content);
748
+ sessionFileArg = ["--session", localSessionPath];
749
+ }
750
+ } catch {}
751
+ return { server, sessionFileArg };
752
+ }
753
+ async function attachRunBundledPiFrontend(context, input) {
754
+ const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
755
+ const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
756
+ const restoreEnv = setTemporaryEnv(buildOperatorPiEnv({
757
+ runId: input.runId,
758
+ serverUrl: server.baseUrl,
759
+ authToken: server.authToken,
760
+ serverProjectRoot: server.serverProjectRoot,
761
+ sessionDir: tempSessionDir
762
+ }));
763
+ const piRigExtensionFactory = (pi) => {
764
+ createPiRigExtension(pi);
765
+ };
766
+ let detached = false;
767
+ try {
768
+ await runPiMain([
769
+ "--no-extensions",
770
+ "--no-skills",
771
+ "--no-prompt-templates",
772
+ "--no-context-files",
773
+ ...sessionFileArg
774
+ ], {
775
+ extensionFactories: [piRigExtensionFactory]
776
+ });
777
+ detached = true;
778
+ } finally {
779
+ restoreEnv();
780
+ rmSync(tempSessionDir, { recursive: true, force: true });
781
+ }
782
+ let run = { runId: input.runId, status: "unknown" };
783
+ try {
784
+ run = await getRunDetailsViaServer(context, input.runId);
785
+ } catch {}
786
+ return {
787
+ run,
788
+ logs: [],
789
+ timeline: [],
790
+ timelineCursor: null,
791
+ steered: input.steered === true,
792
+ detached,
793
+ rendered: "stock Pi operator console with the pi-rig extension"
794
+ };
795
+ }
796
+
791
797
  // packages/cli/src/commands/_operator-view.ts
792
798
  var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
793
799
  function runStatusFromPayload(payload) {
@@ -129,6 +129,11 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
129
129
  // packages/cli/src/commands/_server-client.ts
130
130
  var scopedGitHubBearerTokens = new Map;
131
131
  var serverPhaseListener = null;
132
+ function setServerPhaseListener(listener) {
133
+ const previous = serverPhaseListener;
134
+ serverPhaseListener = listener;
135
+ return previous;
136
+ }
132
137
  function reportServerPhase(label) {
133
138
  serverPhaseListener?.(label);
134
139
  }
@@ -336,6 +341,127 @@ var RESUMABLE_RUN_STATUSES = new Set([
336
341
  "needs_attention"
337
342
  ]);
338
343
 
344
+ // packages/cli/src/commands/_async-ui.ts
345
+ import pc from "picocolors";
346
+
347
+ // packages/cli/src/commands/_spinner.ts
348
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
349
+ function createTtySpinner(input) {
350
+ const output = input.output ?? process.stdout;
351
+ const isTty = output.isTTY === true;
352
+ const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
353
+ let label = input.label;
354
+ let frame = 0;
355
+ let paused = false;
356
+ let stopped = false;
357
+ let lastPrintedLabel = "";
358
+ const render = () => {
359
+ if (stopped || paused)
360
+ return;
361
+ if (!isTty) {
362
+ if (label !== lastPrintedLabel) {
363
+ output.write(`${label}
364
+ `);
365
+ lastPrintedLabel = label;
366
+ }
367
+ return;
368
+ }
369
+ frame = (frame + 1) % frames.length;
370
+ const glyph = frames[frame] ?? frames[0] ?? "";
371
+ output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
372
+ };
373
+ const clearLine = () => {
374
+ if (isTty)
375
+ output.write("\r\x1B[2K");
376
+ };
377
+ render();
378
+ const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
379
+ return {
380
+ setLabel(next) {
381
+ label = next;
382
+ render();
383
+ },
384
+ pause() {
385
+ paused = true;
386
+ clearLine();
387
+ },
388
+ resume() {
389
+ if (stopped)
390
+ return;
391
+ paused = false;
392
+ render();
393
+ },
394
+ stop(finalLine) {
395
+ if (stopped)
396
+ return;
397
+ stopped = true;
398
+ if (timer)
399
+ clearInterval(timer);
400
+ clearLine();
401
+ if (finalLine)
402
+ output.write(`${finalLine}
403
+ `);
404
+ }
405
+ };
406
+ }
407
+
408
+ // packages/cli/src/commands/_async-ui.ts
409
+ var CLACK_SPINNER_FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
410
+ var DONE_SYMBOL = pc.green("\u25C7");
411
+ var FAIL_SYMBOL = pc.red("\u25A0");
412
+ var activeUpdate = null;
413
+ async function withSpinner(label, work, options = {}) {
414
+ if (options.outputMode === "json") {
415
+ return work(() => {});
416
+ }
417
+ if (activeUpdate) {
418
+ const outer = activeUpdate;
419
+ outer(label);
420
+ return work(outer);
421
+ }
422
+ const output = options.output ?? process.stderr;
423
+ const isTty = output.isTTY === true;
424
+ let lastLabel = label;
425
+ if (!isTty) {
426
+ output.write(`${label}
427
+ `);
428
+ const update2 = (next) => {
429
+ lastLabel = next;
430
+ };
431
+ activeUpdate = update2;
432
+ const previousListener2 = setServerPhaseListener(update2);
433
+ try {
434
+ return await work(update2);
435
+ } finally {
436
+ activeUpdate = null;
437
+ setServerPhaseListener(previousListener2);
438
+ }
439
+ }
440
+ const spinner = createTtySpinner({
441
+ label,
442
+ output,
443
+ frames: CLACK_SPINNER_FRAMES,
444
+ styleFrame: (frame) => pc.magenta(frame)
445
+ });
446
+ const update = (next) => {
447
+ lastLabel = next;
448
+ spinner.setLabel(next);
449
+ };
450
+ activeUpdate = update;
451
+ const previousListener = setServerPhaseListener(update);
452
+ try {
453
+ const result = await work(update);
454
+ spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
455
+ return result;
456
+ } catch (error) {
457
+ spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
458
+ throw error;
459
+ } finally {
460
+ activeUpdate = null;
461
+ setServerPhaseListener(previousListener);
462
+ }
463
+ }
464
+
339
465
  // packages/cli/src/commands/_pi-frontend.ts
340
466
  function setTemporaryEnv(updates) {
341
467
  const previous = new Map;
@@ -363,14 +489,13 @@ function buildOperatorPiEnv(input) {
363
489
  ...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
364
490
  };
365
491
  }
366
- async function attachRunBundledPiFrontend(context, input) {
367
- const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
492
+ async function prepareOperatorConsole(context, runId, tempSessionDir) {
368
493
  const server = await ensureServerForCli(context.projectRoot);
369
494
  let sessionFileArg = [];
370
495
  try {
371
- const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(input.runId)}/session-file`);
496
+ const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/session-file`);
372
497
  if (payload?.ok && typeof payload.content === "string" && payload.content.trim()) {
373
- const fileName = typeof payload.fileName === "string" && payload.fileName.trim() ? payload.fileName.replace(/[^A-Za-z0-9._-]/g, "_") : `rig-run-${input.runId}.jsonl`;
498
+ const fileName = typeof payload.fileName === "string" && payload.fileName.trim() ? payload.fileName.replace(/[^A-Za-z0-9._-]/g, "_") : `rig-run-${runId}.jsonl`;
374
499
  const localSessionPath = join(tempSessionDir, fileName);
375
500
  const content = payload.content.split(`
376
501
  `).map((line, index) => {
@@ -389,6 +514,11 @@ async function attachRunBundledPiFrontend(context, input) {
389
514
  sessionFileArg = ["--session", localSessionPath];
390
515
  }
391
516
  } catch {}
517
+ return { server, sessionFileArg };
518
+ }
519
+ async function attachRunBundledPiFrontend(context, input) {
520
+ const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
521
+ const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
392
522
  const restoreEnv = setTemporaryEnv(buildOperatorPiEnv({
393
523
  runId: input.runId,
394
524
  serverUrl: server.baseUrl,
@@ -513,6 +513,19 @@ async function resumeRunViaServer(context, runId, options) {
513
513
  }
514
514
  return { ok: true, runId: targetRunId, ...record };
515
515
  }
516
+ async function resolveServerConnectionLabel(projectRoot) {
517
+ try {
518
+ const selected = resolveSelectedConnection(projectRoot);
519
+ if (!selected)
520
+ return "no server selected";
521
+ if (selected.connection.kind === "remote") {
522
+ return selected.connection.baseUrl.replace(/^https?:\/\//, "");
523
+ }
524
+ return `local (${selected.alias})`;
525
+ } catch {
526
+ return "no server selected";
527
+ }
528
+ }
516
529
  async function stopRunViaServer(context, runId) {
517
530
  const payload = await requestServerJson(context, "/api/runs/stop", {
518
531
  method: "POST",
@@ -640,6 +653,7 @@ export {
640
653
  runRunPiCommandViaServer,
641
654
  resumeRunViaServer,
642
655
  respondRunPiExtensionUiViaServer,
656
+ resolveServerConnectionLabel,
643
657
  requestServerJson,
644
658
  registerProjectViaServer,
645
659
  prepareRemoteCheckoutViaServer,
@@ -0,0 +1,135 @@
1
+ // @bun
2
+ // packages/cli/src/commands/_tui-theme.ts
3
+ var RIG_PALETTE = {
4
+ ink: "#f2f3f6",
5
+ ink2: "#aeb0ba",
6
+ ink3: "#6c6e79",
7
+ ink4: "#44464f",
8
+ accent: "#ccff4d",
9
+ accentDim: "#a9d63f",
10
+ cyan: "#56d8ff",
11
+ red: "#ff5d5d",
12
+ yellow: "#ffd24d"
13
+ };
14
+ function hexToRgb(hex) {
15
+ const value = hex.replace("#", "");
16
+ return [
17
+ Number.parseInt(value.slice(0, 2), 16),
18
+ Number.parseInt(value.slice(2, 4), 16),
19
+ Number.parseInt(value.slice(4, 6), 16)
20
+ ];
21
+ }
22
+ function fg(hex) {
23
+ const [r, g, b] = hexToRgb(hex);
24
+ return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
25
+ }
26
+ var ink = fg(RIG_PALETTE.ink);
27
+ var ink2 = fg(RIG_PALETTE.ink2);
28
+ var ink3 = fg(RIG_PALETTE.ink3);
29
+ var ink4 = fg(RIG_PALETTE.ink4);
30
+ var accent = fg(RIG_PALETTE.accent);
31
+ var accentDim = fg(RIG_PALETTE.accentDim);
32
+ var cyan = fg(RIG_PALETTE.cyan);
33
+ var red = fg(RIG_PALETTE.red);
34
+ var yellow = fg(RIG_PALETTE.yellow);
35
+ function bold(text) {
36
+ return `\x1B[1m${text}\x1B[22m`;
37
+ }
38
+ function statusColor(status) {
39
+ switch (status) {
40
+ case "running":
41
+ return accent;
42
+ case "preparing":
43
+ case "created":
44
+ case "validating":
45
+ case "reviewing":
46
+ case "closing-out":
47
+ return cyan;
48
+ case "needs-attention":
49
+ case "needs_attention":
50
+ return yellow;
51
+ case "failed":
52
+ return red;
53
+ default:
54
+ return ink3;
55
+ }
56
+ }
57
+ var RIG_SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
58
+ var DRONE_ART = [
59
+ " .-=-. .-=-. ",
60
+ " ( !!! ) ( !!! ) ",
61
+ " '-=-'._ _.'-=-' ",
62
+ " '._ _.' ",
63
+ " '=$$$$$$$=.' ",
64
+ " =$$$$$$$$$$$= ",
65
+ " $$$@@@@@@@@@@$$$ ",
66
+ " $$$@@ @@$$$ ",
67
+ " $$@ ? @$$$ ",
68
+ " $$$@ '-' @$$$ ",
69
+ " $$$@@ @@$$$ ",
70
+ " $$$@@@@@@@@@@$$$ ",
71
+ " =$$$$$$$$$$$= ",
72
+ " '=$$$$$$$=.' ",
73
+ " _.' '._ ",
74
+ " .-=-.' '.-=-. ",
75
+ " ( !!! ) ( !!! ) ",
76
+ " '-=-' '-=-' "
77
+ ];
78
+ var BLADE_FRAMES = ["---", "\\\\\\", "|||", "///"];
79
+ var EYE_FRAMES = ["@", "o", "."];
80
+ function droneCharColor(char) {
81
+ if (char === "$" || char === "@")
82
+ return accent;
83
+ if (char === "=" || char === "%")
84
+ return accentDim;
85
+ if (char === "\\" || char === "/")
86
+ return ink3;
87
+ return ink4;
88
+ }
89
+ function renderDroneFrame(tick) {
90
+ const blade = BLADE_FRAMES[Math.floor(tick / 4) % BLADE_FRAMES.length];
91
+ const pulse = Math.sin(tick * 0.07);
92
+ const eye = pulse > 0.45 ? EYE_FRAMES[0] : pulse > -0.1 ? EYE_FRAMES[1] : EYE_FRAMES[2];
93
+ return DRONE_ART.map((line) => {
94
+ let out = "";
95
+ let index = 0;
96
+ while (index < line.length) {
97
+ if (line.startsWith("!!!", index)) {
98
+ out += cyan(blade);
99
+ index += 3;
100
+ continue;
101
+ }
102
+ const char = line[index];
103
+ if (char === "?") {
104
+ out += bold(cyan(eye));
105
+ } else if (char !== " ") {
106
+ out += droneCharColor(char)(char);
107
+ } else {
108
+ out += " ";
109
+ }
110
+ index += 1;
111
+ }
112
+ return out;
113
+ });
114
+ }
115
+ var DRONE_WIDTH = DRONE_ART[0].length;
116
+ var DRONE_HEIGHT = DRONE_ART.length;
117
+ export {
118
+ yellow,
119
+ statusColor,
120
+ renderDroneFrame,
121
+ red,
122
+ ink4,
123
+ ink3,
124
+ ink2,
125
+ ink,
126
+ fg,
127
+ cyan,
128
+ bold,
129
+ accentDim,
130
+ accent,
131
+ RIG_SPINNER_FRAMES,
132
+ RIG_PALETTE,
133
+ DRONE_WIDTH,
134
+ DRONE_HEIGHT
135
+ };