@h-rig/cli 0.0.6-alpha.89 → 0.0.6-alpha.90
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/dist/bin/build-rig-binaries.js +8 -40
- package/dist/bin/rig.js +12519 -26675
- package/dist/src/app/board.js +74 -693
- package/dist/src/app/drone-ui.js +1 -1
- package/dist/src/app/theme.js +0 -1
- package/dist/src/commands/_async-ui.js +1 -1
- package/dist/src/commands/_authority-runs.d.ts +1 -1
- package/dist/src/commands/_authority-runs.js +12 -2
- package/dist/src/commands/_cli-format.d.ts +1 -10
- package/dist/src/commands/_cli-format.js +2 -5
- package/dist/src/commands/_connection-state.d.ts +1 -11
- package/dist/src/commands/_connection-state.js +5 -50
- package/dist/src/commands/_doctor-checks.js +40 -501
- package/dist/src/commands/_help-catalog.js +17 -99
- package/dist/src/commands/_json-output.js +0 -4
- package/dist/src/commands/_operator-view.js +54 -575
- package/dist/src/commands/_pi-frontend.d.ts +0 -27
- package/dist/src/commands/_pi-frontend.js +52 -576
- package/dist/src/commands/_preflight.js +66 -553
- package/dist/src/commands/_server-client.d.ts +0 -33
- package/dist/src/commands/_server-client.js +46 -541
- package/dist/src/commands/_snapshot-upload.js +42 -503
- package/dist/src/commands/_spinner.js +1 -1
- package/dist/src/commands/agent.js +12 -2
- package/dist/src/commands/browser.js +1 -1
- package/dist/src/commands/connect.js +15 -626
- package/dist/src/commands/doctor.js +41 -502
- package/dist/src/commands/github.js +51 -584
- package/dist/src/commands/inbox.js +41 -502
- package/dist/src/commands/init.js +74 -561
- package/dist/src/commands/inspect.js +41 -502
- package/dist/src/commands/run.js +54 -578
- package/dist/src/commands/server.js +163 -692
- package/dist/src/commands/setup.js +46 -507
- package/dist/src/commands/stats.js +58 -601
- package/dist/src/commands/task-run-driver.js +58 -509
- package/dist/src/commands/task.js +110 -729
- package/dist/src/commands.js +237 -1112
- package/dist/src/index.js +244 -1123
- package/dist/src/launcher.js +0 -4
- package/package.json +9 -13
- package/dist/src/app-opentui/adapters/command.d.ts +0 -2
- package/dist/src/app-opentui/adapters/command.js +0 -329
- package/dist/src/app-opentui/adapters/common.d.ts +0 -56
- package/dist/src/app-opentui/adapters/common.js +0 -153
- package/dist/src/app-opentui/adapters/doctor.d.ts +0 -9
- package/dist/src/app-opentui/adapters/doctor.js +0 -1217
- package/dist/src/app-opentui/adapters/family.d.ts +0 -62
- package/dist/src/app-opentui/adapters/family.js +0 -14855
- package/dist/src/app-opentui/adapters/fleet.d.ts +0 -14
- package/dist/src/app-opentui/adapters/fleet.js +0 -1335
- package/dist/src/app-opentui/adapters/inbox.d.ts +0 -43
- package/dist/src/app-opentui/adapters/inbox.js +0 -1944
- package/dist/src/app-opentui/adapters/init.d.ts +0 -11
- package/dist/src/app-opentui/adapters/init.js +0 -2818
- package/dist/src/app-opentui/adapters/inspect.d.ts +0 -52
- package/dist/src/app-opentui/adapters/inspect.js +0 -1455
- package/dist/src/app-opentui/adapters/pi-attach.d.ts +0 -32
- package/dist/src/app-opentui/adapters/pi-attach.js +0 -2101
- package/dist/src/app-opentui/adapters/pi.d.ts +0 -23
- package/dist/src/app-opentui/adapters/pi.js +0 -363
- package/dist/src/app-opentui/adapters/plugin.d.ts +0 -84
- package/dist/src/app-opentui/adapters/plugin.js +0 -544
- package/dist/src/app-opentui/adapters/repo.d.ts +0 -37
- package/dist/src/app-opentui/adapters/repo.js +0 -186
- package/dist/src/app-opentui/adapters/run-detail.d.ts +0 -27
- package/dist/src/app-opentui/adapters/run-detail.js +0 -1441
- package/dist/src/app-opentui/adapters/server.d.ts +0 -48
- package/dist/src/app-opentui/adapters/server.js +0 -1561
- package/dist/src/app-opentui/adapters/tasks.d.ts +0 -64
- package/dist/src/app-opentui/adapters/tasks.js +0 -4524
- package/dist/src/app-opentui/adapters/workspace.d.ts +0 -49
- package/dist/src/app-opentui/adapters/workspace.js +0 -333
- package/dist/src/app-opentui/autocomplete.d.ts +0 -20
- package/dist/src/app-opentui/autocomplete.js +0 -578
- package/dist/src/app-opentui/bootstrap.d.ts +0 -11
- package/dist/src/app-opentui/bootstrap.js +0 -27833
- package/dist/src/app-opentui/command-palette.d.ts +0 -3
- package/dist/src/app-opentui/command-palette.js +0 -1038
- package/dist/src/app-opentui/command-pty-host.d.ts +0 -62
- package/dist/src/app-opentui/command-pty-host.js +0 -248
- package/dist/src/app-opentui/drone.d.ts +0 -12
- package/dist/src/app-opentui/drone.js +0 -230
- package/dist/src/app-opentui/events.d.ts +0 -7
- package/dist/src/app-opentui/events.js +0 -28
- package/dist/src/app-opentui/fleet-stats.d.ts +0 -32
- package/dist/src/app-opentui/fleet-stats.js +0 -114
- package/dist/src/app-opentui/focus-manager.d.ts +0 -14
- package/dist/src/app-opentui/focus-manager.js +0 -24
- package/dist/src/app-opentui/index.d.ts +0 -8
- package/dist/src/app-opentui/index.js +0 -5260
- package/dist/src/app-opentui/intent.d.ts +0 -3
- package/dist/src/app-opentui/intent.js +0 -365
- package/dist/src/app-opentui/keymap.d.ts +0 -21
- package/dist/src/app-opentui/keymap.js +0 -1377
- package/dist/src/app-opentui/launch-routing.d.ts +0 -16
- package/dist/src/app-opentui/launch-routing.js +0 -55
- package/dist/src/app-opentui/layout.d.ts +0 -22
- package/dist/src/app-opentui/layout.js +0 -51
- package/dist/src/app-opentui/list-search.d.ts +0 -36
- package/dist/src/app-opentui/list-search.js +0 -130
- package/dist/src/app-opentui/pi-host-child.d.ts +0 -2
- package/dist/src/app-opentui/pi-host-child.js +0 -1299
- package/dist/src/app-opentui/pi-pty-host.d.ts +0 -28
- package/dist/src/app-opentui/pi-pty-host.js +0 -10
- package/dist/src/app-opentui/react/App.d.ts +0 -9
- package/dist/src/app-opentui/react/App.js +0 -3866
- package/dist/src/app-opentui/react/Backdrop.d.ts +0 -5
- package/dist/src/app-opentui/react/Backdrop.js +0 -823
- package/dist/src/app-opentui/react/ChromeHost.d.ts +0 -5
- package/dist/src/app-opentui/react/ChromeHost.js +0 -1539
- package/dist/src/app-opentui/react/SceneFrameView.d.ts +0 -7
- package/dist/src/app-opentui/react/SceneFrameView.js +0 -579
- package/dist/src/app-opentui/react/context.d.ts +0 -17
- package/dist/src/app-opentui/react/context.js +0 -37
- package/dist/src/app-opentui/react/launch.d.ts +0 -2
- package/dist/src/app-opentui/react/launch.js +0 -4897
- package/dist/src/app-opentui/react/nav.d.ts +0 -18
- package/dist/src/app-opentui/react/nav.js +0 -54
- package/dist/src/app-opentui/react/scroll.d.ts +0 -12
- package/dist/src/app-opentui/react/scroll.js +0 -21
- package/dist/src/app-opentui/react/syntax.d.ts +0 -2
- package/dist/src/app-opentui/react/syntax.js +0 -65
- package/dist/src/app-opentui/registry.d.ts +0 -4
- package/dist/src/app-opentui/registry.js +0 -22876
- package/dist/src/app-opentui/remote-link.d.ts +0 -10
- package/dist/src/app-opentui/remote-link.js +0 -47
- package/dist/src/app-opentui/render/ascii-fleet.d.ts +0 -15
- package/dist/src/app-opentui/render/ascii-fleet.js +0 -82
- package/dist/src/app-opentui/render/constants.d.ts +0 -30
- package/dist/src/app-opentui/render/constants.js +0 -64
- package/dist/src/app-opentui/render/graphics.d.ts +0 -40
- package/dist/src/app-opentui/render/graphics.js +0 -713
- package/dist/src/app-opentui/render/hover.d.ts +0 -11
- package/dist/src/app-opentui/render/hover.js +0 -34
- package/dist/src/app-opentui/render/native-host.d.ts +0 -37
- package/dist/src/app-opentui/render/native-host.js +0 -180
- package/dist/src/app-opentui/render/panel-layout.d.ts +0 -38
- package/dist/src/app-opentui/render/panel-layout.js +0 -48
- package/dist/src/app-opentui/render/panels.d.ts +0 -12
- package/dist/src/app-opentui/render/panels.js +0 -288
- package/dist/src/app-opentui/render/preloader.d.ts +0 -10
- package/dist/src/app-opentui/render/preloader.js +0 -166
- package/dist/src/app-opentui/render/scene.d.ts +0 -68
- package/dist/src/app-opentui/render/scene.js +0 -300
- package/dist/src/app-opentui/render/terminal-handoff.d.ts +0 -16
- package/dist/src/app-opentui/render/terminal-handoff.js +0 -14
- package/dist/src/app-opentui/render/text.d.ts +0 -29
- package/dist/src/app-opentui/render/text.js +0 -177
- package/dist/src/app-opentui/render/type-bar.d.ts +0 -15
- package/dist/src/app-opentui/render/type-bar.js +0 -212
- package/dist/src/app-opentui/runtime-resources.d.ts +0 -16
- package/dist/src/app-opentui/runtime-resources.js +0 -62
- package/dist/src/app-opentui/runtime.d.ts +0 -45
- package/dist/src/app-opentui/runtime.js +0 -5232
- package/dist/src/app-opentui/scenes/command.d.ts +0 -3
- package/dist/src/app-opentui/scenes/command.js +0 -118
- package/dist/src/app-opentui/scenes/doctor.d.ts +0 -3
- package/dist/src/app-opentui/scenes/doctor.js +0 -296
- package/dist/src/app-opentui/scenes/error.d.ts +0 -2
- package/dist/src/app-opentui/scenes/error.js +0 -277
- package/dist/src/app-opentui/scenes/family-domains/agent.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/agent.js +0 -349
- package/dist/src/app-opentui/scenes/family-domains/browser.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/browser.js +0 -196
- package/dist/src/app-opentui/scenes/family-domains/dist.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/dist.js +0 -244
- package/dist/src/app-opentui/scenes/family-domains/git.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/git.js +0 -196
- package/dist/src/app-opentui/scenes/family-domains/github.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/github.js +0 -275
- package/dist/src/app-opentui/scenes/family-domains/harness.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/harness.js +0 -153
- package/dist/src/app-opentui/scenes/family-domains/index.d.ts +0 -4
- package/dist/src/app-opentui/scenes/family-domains/index.js +0 -1680
- package/dist/src/app-opentui/scenes/family-domains/kit.d.ts +0 -76
- package/dist/src/app-opentui/scenes/family-domains/kit.js +0 -306
- package/dist/src/app-opentui/scenes/family-domains/profile.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/profile.js +0 -213
- package/dist/src/app-opentui/scenes/family-domains/queue.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/queue.js +0 -147
- package/dist/src/app-opentui/scenes/family-domains/remote.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/remote.js +0 -519
- package/dist/src/app-opentui/scenes/family-domains/review.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/review.js +0 -281
- package/dist/src/app-opentui/scenes/family-domains/setup.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/setup.js +0 -268
- package/dist/src/app-opentui/scenes/family-domains/stats.d.ts +0 -2
- package/dist/src/app-opentui/scenes/family-domains/stats.js +0 -371
- package/dist/src/app-opentui/scenes/family.d.ts +0 -3
- package/dist/src/app-opentui/scenes/family.js +0 -2199
- package/dist/src/app-opentui/scenes/fleet.d.ts +0 -3
- package/dist/src/app-opentui/scenes/fleet.js +0 -766
- package/dist/src/app-opentui/scenes/handoff.d.ts +0 -2
- package/dist/src/app-opentui/scenes/handoff.js +0 -455
- package/dist/src/app-opentui/scenes/help.d.ts +0 -3
- package/dist/src/app-opentui/scenes/help.js +0 -725
- package/dist/src/app-opentui/scenes/inbox.d.ts +0 -3
- package/dist/src/app-opentui/scenes/inbox.js +0 -427
- package/dist/src/app-opentui/scenes/init.d.ts +0 -3
- package/dist/src/app-opentui/scenes/init.js +0 -181
- package/dist/src/app-opentui/scenes/inspect.d.ts +0 -3
- package/dist/src/app-opentui/scenes/inspect.js +0 -794
- package/dist/src/app-opentui/scenes/main.d.ts +0 -3
- package/dist/src/app-opentui/scenes/main.js +0 -332
- package/dist/src/app-opentui/scenes/pi.d.ts +0 -3
- package/dist/src/app-opentui/scenes/pi.js +0 -509
- package/dist/src/app-opentui/scenes/plugin.d.ts +0 -3
- package/dist/src/app-opentui/scenes/plugin.js +0 -487
- package/dist/src/app-opentui/scenes/repo.d.ts +0 -3
- package/dist/src/app-opentui/scenes/repo.js +0 -425
- package/dist/src/app-opentui/scenes/run-detail.d.ts +0 -3
- package/dist/src/app-opentui/scenes/run-detail.js +0 -467
- package/dist/src/app-opentui/scenes/server.d.ts +0 -3
- package/dist/src/app-opentui/scenes/server.js +0 -378
- package/dist/src/app-opentui/scenes/tasks.d.ts +0 -3
- package/dist/src/app-opentui/scenes/tasks.js +0 -768
- package/dist/src/app-opentui/scenes/workspace.d.ts +0 -3
- package/dist/src/app-opentui/scenes/workspace.js +0 -427
- package/dist/src/app-opentui/selectable.d.ts +0 -19
- package/dist/src/app-opentui/selectable.js +0 -80
- package/dist/src/app-opentui/state.d.ts +0 -4
- package/dist/src/app-opentui/state.js +0 -399
- package/dist/src/app-opentui/surface-catalog.d.ts +0 -20
- package/dist/src/app-opentui/surface-catalog.js +0 -542
- package/dist/src/app-opentui/theme.d.ts +0 -59
- package/dist/src/app-opentui/theme.js +0 -142
- package/dist/src/app-opentui/types.d.ts +0 -313
- package/dist/src/app-opentui/types.js +0 -1
- package/dist/src/commands/_server-events.d.ts +0 -26
- package/dist/src/commands/_server-events.js +0 -715
|
@@ -1,2818 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
var __require = import.meta.require;
|
|
18
|
-
|
|
19
|
-
// packages/cli/src/runner.ts
|
|
20
|
-
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
21
|
-
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
22
|
-
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
23
|
-
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
24
|
-
function takeFlag(args, flag) {
|
|
25
|
-
const rest = [];
|
|
26
|
-
let value = false;
|
|
27
|
-
for (const arg of args) {
|
|
28
|
-
if (arg === flag) {
|
|
29
|
-
value = true;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
rest.push(arg);
|
|
33
|
-
}
|
|
34
|
-
return { value, rest };
|
|
35
|
-
}
|
|
36
|
-
function takeOption(args, option) {
|
|
37
|
-
const rest = [];
|
|
38
|
-
let value;
|
|
39
|
-
for (let index = 0;index < args.length; index += 1) {
|
|
40
|
-
const current = args[index];
|
|
41
|
-
if (current === option) {
|
|
42
|
-
const next = args[index + 1];
|
|
43
|
-
if (!next || next.startsWith("-")) {
|
|
44
|
-
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
45
|
-
}
|
|
46
|
-
value = next;
|
|
47
|
-
index += 1;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (current !== undefined) {
|
|
51
|
-
rest.push(current);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return { value, rest };
|
|
55
|
-
}
|
|
56
|
-
var CliError;
|
|
57
|
-
var init_runner = __esm(() => {
|
|
58
|
-
CliError = class CliError extends RuntimeCliError {
|
|
59
|
-
hint;
|
|
60
|
-
constructor(message, exitCode = 1, options = {}) {
|
|
61
|
-
super(message, exitCode);
|
|
62
|
-
if (options.hint?.trim()) {
|
|
63
|
-
this.hint = options.hint.trim();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// packages/cli/src/commands/_connection-state.ts
|
|
70
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
71
|
-
import { homedir } from "os";
|
|
72
|
-
import { dirname, resolve } from "path";
|
|
73
|
-
function resolveGlobalConnectionsPath(env = process.env) {
|
|
74
|
-
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
75
|
-
if (explicit)
|
|
76
|
-
return resolve(explicit);
|
|
77
|
-
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
78
|
-
if (stateDir)
|
|
79
|
-
return resolve(stateDir, "connections.json");
|
|
80
|
-
return resolve(homedir(), ".rig", "connections.json");
|
|
81
|
-
}
|
|
82
|
-
function resolveRepoConnectionPath(projectRoot) {
|
|
83
|
-
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
84
|
-
}
|
|
85
|
-
function readJsonFile(path) {
|
|
86
|
-
if (!existsSync(path))
|
|
87
|
-
return null;
|
|
88
|
-
try {
|
|
89
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
90
|
-
} catch (error) {
|
|
91
|
-
throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function writeJsonFile(path, value) {
|
|
95
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
96
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
97
|
-
`, "utf8");
|
|
98
|
-
}
|
|
99
|
-
function normalizeConnection(value) {
|
|
100
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
101
|
-
return null;
|
|
102
|
-
const record = value;
|
|
103
|
-
if (record.kind === "local")
|
|
104
|
-
return { kind: "local", mode: "auto" };
|
|
105
|
-
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
106
|
-
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
107
|
-
return { kind: "remote", baseUrl };
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
function readGlobalConnections(options = {}) {
|
|
112
|
-
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
113
|
-
const payload = readJsonFile(path);
|
|
114
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
115
|
-
return { connections: {} };
|
|
116
|
-
}
|
|
117
|
-
const rawConnections = payload.connections;
|
|
118
|
-
const connections = {};
|
|
119
|
-
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
120
|
-
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
121
|
-
const connection = normalizeConnection(raw);
|
|
122
|
-
if (connection)
|
|
123
|
-
connections[alias] = connection;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return { connections };
|
|
127
|
-
}
|
|
128
|
-
function writeGlobalConnections(state, options = {}) {
|
|
129
|
-
writeJsonFile(resolveGlobalConnectionsPath(options.env ?? process.env), state);
|
|
130
|
-
}
|
|
131
|
-
function upsertGlobalConnection(alias, connection, options = {}) {
|
|
132
|
-
const cleanAlias = alias.trim();
|
|
133
|
-
if (!cleanAlias)
|
|
134
|
-
throw new CliError("Connection alias is required.", 1, { hint: "Save a server with `rig server add <alias> <url>`." });
|
|
135
|
-
const state = readGlobalConnections(options);
|
|
136
|
-
state.connections[cleanAlias] = connection;
|
|
137
|
-
writeGlobalConnections(state, options);
|
|
138
|
-
return state;
|
|
139
|
-
}
|
|
140
|
-
function readRepoConnection(projectRoot) {
|
|
141
|
-
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
142
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
143
|
-
return null;
|
|
144
|
-
const record = payload;
|
|
145
|
-
const selected = typeof record.selected === "string" ? record.selected.trim() : "";
|
|
146
|
-
if (!selected)
|
|
147
|
-
return null;
|
|
148
|
-
return {
|
|
149
|
-
selected,
|
|
150
|
-
project: typeof record.project === "string" ? record.project : undefined,
|
|
151
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
152
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
|
|
153
|
-
serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
|
|
154
|
-
serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
function writeRepoConnection(projectRoot, state) {
|
|
158
|
-
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
159
|
-
}
|
|
160
|
-
function rootAllowedForSelection(repo, connection) {
|
|
161
|
-
const root = repo.serverProjectRoot?.trim();
|
|
162
|
-
if (!root)
|
|
163
|
-
return;
|
|
164
|
-
if (connection.kind === "remote") {
|
|
165
|
-
if (repo.serverProjectRootAlias !== repo.selected)
|
|
166
|
-
return;
|
|
167
|
-
if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
|
|
168
|
-
return;
|
|
169
|
-
} else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
return root;
|
|
173
|
-
}
|
|
174
|
-
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
175
|
-
const repo = readRepoConnection(projectRoot);
|
|
176
|
-
if (!repo)
|
|
177
|
-
return null;
|
|
178
|
-
if (repo.selected === "local")
|
|
179
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
180
|
-
const global = readGlobalConnections(options);
|
|
181
|
-
const connection = global.connections[repo.selected];
|
|
182
|
-
if (!connection) {
|
|
183
|
-
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
184
|
-
}
|
|
185
|
-
return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
|
|
186
|
-
}
|
|
187
|
-
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
|
|
188
|
-
const repo = readRepoConnection(projectRoot);
|
|
189
|
-
if (!repo)
|
|
190
|
-
return;
|
|
191
|
-
let inferred = metadata;
|
|
192
|
-
if (!inferred.alias || !inferred.baseUrl) {
|
|
193
|
-
try {
|
|
194
|
-
const selected = resolveSelectedConnection(projectRoot);
|
|
195
|
-
if (selected?.connection.kind === "remote") {
|
|
196
|
-
inferred = {
|
|
197
|
-
alias: inferred.alias ?? selected.alias,
|
|
198
|
-
baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
} catch {}
|
|
202
|
-
}
|
|
203
|
-
writeRepoConnection(projectRoot, {
|
|
204
|
-
...repo,
|
|
205
|
-
...metadata.project ? { project: metadata.project } : {},
|
|
206
|
-
serverProjectRoot,
|
|
207
|
-
...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
|
|
208
|
-
...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
function clearRepoServerProjectRoot(projectRoot) {
|
|
212
|
-
const repo = readRepoConnection(projectRoot);
|
|
213
|
-
if (!repo)
|
|
214
|
-
return;
|
|
215
|
-
writeRepoConnection(projectRoot, {
|
|
216
|
-
selected: repo.selected,
|
|
217
|
-
...repo.project ? { project: repo.project } : {},
|
|
218
|
-
...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
var init__connection_state = __esm(() => {
|
|
222
|
-
init_runner();
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// packages/cli/src/commands/_server-client.ts
|
|
226
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
227
|
-
import { dirname as dirname2, isAbsolute, resolve as resolve2 } from "path";
|
|
228
|
-
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
229
|
-
function reportServerPhase(label) {
|
|
230
|
-
serverPhaseListener?.(label);
|
|
231
|
-
}
|
|
232
|
-
function cleanToken(value) {
|
|
233
|
-
const trimmed = value?.trim();
|
|
234
|
-
return trimmed ? trimmed : null;
|
|
235
|
-
}
|
|
236
|
-
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
237
|
-
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
238
|
-
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
239
|
-
}
|
|
240
|
-
function readRemoteAuthState(projectRoot) {
|
|
241
|
-
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
242
|
-
if (!existsSync2(path))
|
|
243
|
-
return null;
|
|
244
|
-
try {
|
|
245
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
246
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
247
|
-
} catch {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function readPrivateRemoteSessionToken(projectRoot) {
|
|
252
|
-
const parsed = readRemoteAuthState(projectRoot);
|
|
253
|
-
return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
|
|
254
|
-
}
|
|
255
|
-
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
256
|
-
const scopedKey = resolve2(projectRoot);
|
|
257
|
-
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
258
|
-
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
259
|
-
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
260
|
-
if (privateSession)
|
|
261
|
-
return privateSession;
|
|
262
|
-
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
263
|
-
}
|
|
264
|
-
function readStoredGitHubAuthToken(projectRoot) {
|
|
265
|
-
const parsed = readRemoteAuthState(projectRoot);
|
|
266
|
-
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
267
|
-
}
|
|
268
|
-
function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
|
|
269
|
-
if (!/^[^/]+\/[^/]+$/.test(repoSlug))
|
|
270
|
-
return null;
|
|
271
|
-
const repo = readRepoConnection(projectRoot);
|
|
272
|
-
const auth = readRemoteAuthState(projectRoot);
|
|
273
|
-
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
274
|
-
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
275
|
-
if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
|
|
276
|
-
return null;
|
|
277
|
-
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
278
|
-
if (!checkoutBaseDir)
|
|
279
|
-
return null;
|
|
280
|
-
return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
|
|
281
|
-
}
|
|
282
|
-
function readLocalConnectionFallbackToken(projectRoot) {
|
|
283
|
-
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
284
|
-
}
|
|
285
|
-
function normalizeRepoSlug(value) {
|
|
286
|
-
const slug = value?.trim();
|
|
287
|
-
return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
288
|
-
}
|
|
289
|
-
function readProjectLinkSlug(projectRoot) {
|
|
290
|
-
const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
|
|
291
|
-
if (!existsSync2(path))
|
|
292
|
-
return null;
|
|
293
|
-
try {
|
|
294
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
295
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
296
|
-
return null;
|
|
297
|
-
return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
|
|
298
|
-
} catch {
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
|
|
303
|
-
const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
|
|
304
|
-
mkdirSync2(dirname2(path), { recursive: true });
|
|
305
|
-
writeFileSync2(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
306
|
-
`, "utf8");
|
|
307
|
-
}
|
|
308
|
-
function remoteLinkRepairCommand(repoSlug) {
|
|
309
|
-
return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
|
|
310
|
-
}
|
|
311
|
-
function formatRemoteProjectLinkHint(resolution) {
|
|
312
|
-
const repair = remoteLinkRepairCommand(resolution.repoSlug);
|
|
313
|
-
if (resolution.status === "auth_required") {
|
|
314
|
-
return `Authenticate the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}, then run \`${repair}\`. Use \`rig github auth import-gh\` or \`rig init --repair --server remote --github-auth device${resolution.repoSlug ? ` --repo ${resolution.repoSlug}` : ""}\`.`;
|
|
315
|
-
}
|
|
316
|
-
if (resolution.status === "missing_project") {
|
|
317
|
-
return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
|
|
318
|
-
}
|
|
319
|
-
if (resolution.status === "not_remote")
|
|
320
|
-
return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
|
|
321
|
-
return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
|
|
322
|
-
}
|
|
323
|
-
function remoteProjectLinkFailure(input) {
|
|
324
|
-
const partial = {
|
|
325
|
-
status: input.status,
|
|
326
|
-
alias: input.alias,
|
|
327
|
-
baseUrl: input.baseUrl,
|
|
328
|
-
repoSlug: input.repoSlug
|
|
329
|
-
};
|
|
330
|
-
return {
|
|
331
|
-
ok: false,
|
|
332
|
-
...input,
|
|
333
|
-
hint: formatRemoteProjectLinkHint(partial),
|
|
334
|
-
next: remoteLinkRepairCommand(input.repoSlug)
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
function remoteProjectLinkSuccess(input) {
|
|
338
|
-
return {
|
|
339
|
-
ok: true,
|
|
340
|
-
status: input.status,
|
|
341
|
-
alias: input.alias,
|
|
342
|
-
baseUrl: input.baseUrl,
|
|
343
|
-
repoSlug: input.repoSlug,
|
|
344
|
-
serverProjectRoot: input.serverProjectRoot,
|
|
345
|
-
source: input.source,
|
|
346
|
-
prepared: input.prepared ?? input.status === "prepared",
|
|
347
|
-
validated: input.validated ?? false,
|
|
348
|
-
message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
|
|
349
|
-
hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
|
|
350
|
-
next: "rig task list"
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
function localCheckoutPathCandidate(projectRoot, candidate) {
|
|
354
|
-
try {
|
|
355
|
-
const local = resolve2(projectRoot);
|
|
356
|
-
const resolved = resolve2(candidate);
|
|
357
|
-
return resolved === local || resolved.startsWith(`${local}/`);
|
|
358
|
-
} catch {
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
|
|
363
|
-
const requestUrl = new URL(`${baseUrl}${pathname}`);
|
|
364
|
-
if (authToken && queryAuthFallbackEnabled())
|
|
365
|
-
requestUrl.searchParams.set("rt", authToken);
|
|
366
|
-
const response = await fetch(requestUrl, {
|
|
367
|
-
...init,
|
|
368
|
-
headers: mergeHeaders(init.headers, authToken)
|
|
369
|
-
});
|
|
370
|
-
const text = await response.text();
|
|
371
|
-
const payload = text.trim().length > 0 ? (() => {
|
|
372
|
-
try {
|
|
373
|
-
return JSON.parse(text);
|
|
374
|
-
} catch {
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
})() : null;
|
|
378
|
-
return { ok: response.ok, status: response.status, payload, text };
|
|
379
|
-
}
|
|
380
|
-
function payloadError(payload, fallback) {
|
|
381
|
-
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
382
|
-
const record = payload;
|
|
383
|
-
const value = record.error ?? record.message ?? record.reason;
|
|
384
|
-
if (typeof value === "string" && value.trim())
|
|
385
|
-
return value.trim();
|
|
386
|
-
}
|
|
387
|
-
return fallback;
|
|
388
|
-
}
|
|
389
|
-
function checkoutPathsFromProjectPayload(payload) {
|
|
390
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
391
|
-
return [];
|
|
392
|
-
const project = payload.project;
|
|
393
|
-
if (!project || typeof project !== "object" || Array.isArray(project))
|
|
394
|
-
return [];
|
|
395
|
-
const checkouts = project.checkouts;
|
|
396
|
-
if (!Array.isArray(checkouts))
|
|
397
|
-
return [];
|
|
398
|
-
return [...checkouts].reverse().flatMap((entry) => {
|
|
399
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
400
|
-
return [];
|
|
401
|
-
const path = entry.path;
|
|
402
|
-
return typeof path === "string" && path.trim() ? [path.trim()] : [];
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
function checkoutPathFromPreparePayload(payload) {
|
|
406
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
407
|
-
return null;
|
|
408
|
-
const checkout = payload.checkout;
|
|
409
|
-
if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
|
|
410
|
-
return null;
|
|
411
|
-
const path = checkout.path;
|
|
412
|
-
return typeof path === "string" && path.trim() ? path.trim() : null;
|
|
413
|
-
}
|
|
414
|
-
async function validateAndPersistRemoteRoot(input) {
|
|
415
|
-
const candidate = input.candidate.trim();
|
|
416
|
-
if (!candidate || !isAbsolute(candidate)) {
|
|
417
|
-
return remoteProjectLinkFailure({
|
|
418
|
-
status: "invalid_root",
|
|
419
|
-
alias: input.alias,
|
|
420
|
-
baseUrl: input.baseUrl,
|
|
421
|
-
repoSlug: input.repoSlug,
|
|
422
|
-
message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
|
|
426
|
-
return remoteProjectLinkFailure({
|
|
427
|
-
status: "invalid_root",
|
|
428
|
-
alias: input.alias,
|
|
429
|
-
baseUrl: input.baseUrl,
|
|
430
|
-
repoSlug: input.repoSlug,
|
|
431
|
-
message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
let response;
|
|
435
|
-
try {
|
|
436
|
-
response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
|
|
437
|
-
method: "POST",
|
|
438
|
-
headers: { "content-type": "application/json" },
|
|
439
|
-
body: JSON.stringify({ projectRoot: candidate })
|
|
440
|
-
});
|
|
441
|
-
} catch (error) {
|
|
442
|
-
return remoteProjectLinkFailure({
|
|
443
|
-
status: "error",
|
|
444
|
-
alias: input.alias,
|
|
445
|
-
baseUrl: input.baseUrl,
|
|
446
|
-
repoSlug: input.repoSlug,
|
|
447
|
-
message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
if (response.status === 401 || response.status === 403) {
|
|
451
|
-
return remoteProjectLinkFailure({
|
|
452
|
-
status: "auth_required",
|
|
453
|
-
alias: input.alias,
|
|
454
|
-
baseUrl: input.baseUrl,
|
|
455
|
-
repoSlug: input.repoSlug,
|
|
456
|
-
statusCode: response.status,
|
|
457
|
-
message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
if (!response.ok) {
|
|
461
|
-
return remoteProjectLinkFailure({
|
|
462
|
-
status: "invalid_root",
|
|
463
|
-
alias: input.alias,
|
|
464
|
-
baseUrl: input.baseUrl,
|
|
465
|
-
repoSlug: input.repoSlug,
|
|
466
|
-
statusCode: response.status,
|
|
467
|
-
message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
|
|
471
|
-
if (record.ok !== true) {
|
|
472
|
-
return remoteProjectLinkFailure({
|
|
473
|
-
status: "invalid_root",
|
|
474
|
-
alias: input.alias,
|
|
475
|
-
baseUrl: input.baseUrl,
|
|
476
|
-
repoSlug: input.repoSlug,
|
|
477
|
-
message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
|
|
481
|
-
writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
|
|
482
|
-
writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
|
|
483
|
-
const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
|
|
484
|
-
return remoteProjectLinkSuccess({
|
|
485
|
-
status,
|
|
486
|
-
alias: input.alias,
|
|
487
|
-
baseUrl: input.baseUrl,
|
|
488
|
-
repoSlug: input.repoSlug,
|
|
489
|
-
serverProjectRoot: accepted,
|
|
490
|
-
source: input.source,
|
|
491
|
-
prepared: input.prepared,
|
|
492
|
-
validated: true,
|
|
493
|
-
message: status === "ready" ? `Remote project link already points to ${accepted} and was validated on ${input.alias}.` : input.prepared ? `Prepared and linked remote checkout ${accepted} for ${input.repoSlug}.` : `Backfilled remote checkout ${accepted} for ${input.repoSlug}.`
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
|
|
497
|
-
let selected;
|
|
498
|
-
try {
|
|
499
|
-
selected = resolveSelectedConnection(projectRoot);
|
|
500
|
-
} catch (error) {
|
|
501
|
-
return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
|
|
502
|
-
}
|
|
503
|
-
if (!selected || selected.connection.kind !== "remote") {
|
|
504
|
-
return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
|
|
505
|
-
}
|
|
506
|
-
const repo = readRepoConnection(projectRoot);
|
|
507
|
-
const explicitRepoRequested = options.repoSlug !== undefined;
|
|
508
|
-
const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
|
|
509
|
-
const repoProjectSlug = normalizeRepoSlug(repo?.project);
|
|
510
|
-
const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
|
|
511
|
-
const alias = selected.alias;
|
|
512
|
-
const baseUrl = selected.connection.baseUrl;
|
|
513
|
-
const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
|
|
514
|
-
const mode = options.mode ?? "backfill-only";
|
|
515
|
-
if (explicitRepoRequested && !explicitRepoSlug) {
|
|
516
|
-
return remoteProjectLinkFailure({
|
|
517
|
-
status: "missing_project",
|
|
518
|
-
alias,
|
|
519
|
-
baseUrl,
|
|
520
|
-
message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
if (!repoSlug) {
|
|
524
|
-
return remoteProjectLinkFailure({
|
|
525
|
-
status: "missing_project",
|
|
526
|
-
alias,
|
|
527
|
-
baseUrl,
|
|
528
|
-
message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
const skippedCandidates = [];
|
|
532
|
-
const storedRoot = selected.serverProjectRoot?.trim() || null;
|
|
533
|
-
const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
|
|
534
|
-
if (storedRoot && storedRootMatchesResolvedRepo) {
|
|
535
|
-
if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
|
|
536
|
-
clearRepoServerProjectRoot(projectRoot);
|
|
537
|
-
skippedCandidates.push(storedRoot);
|
|
538
|
-
} else {
|
|
539
|
-
const storedResult = await validateAndPersistRemoteRoot({
|
|
540
|
-
projectRoot,
|
|
541
|
-
alias,
|
|
542
|
-
baseUrl,
|
|
543
|
-
authToken,
|
|
544
|
-
repoSlug,
|
|
545
|
-
candidate: storedRoot,
|
|
546
|
-
source: "stored",
|
|
547
|
-
status: "ready"
|
|
548
|
-
});
|
|
549
|
-
if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
|
|
550
|
-
return storedResult;
|
|
551
|
-
clearRepoServerProjectRoot(projectRoot);
|
|
552
|
-
skippedCandidates.push(storedRoot);
|
|
553
|
-
}
|
|
554
|
-
} else if (storedRoot) {
|
|
555
|
-
skippedCandidates.push(storedRoot);
|
|
556
|
-
}
|
|
557
|
-
const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
|
|
558
|
-
if (authCandidate) {
|
|
559
|
-
const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
|
|
560
|
-
if (authResult.ok || authResult.status === "auth_required")
|
|
561
|
-
return authResult;
|
|
562
|
-
}
|
|
563
|
-
let registryResponse;
|
|
564
|
-
try {
|
|
565
|
-
registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
|
|
566
|
-
} catch (error) {
|
|
567
|
-
return remoteProjectLinkFailure({
|
|
568
|
-
status: "error",
|
|
569
|
-
alias,
|
|
570
|
-
baseUrl,
|
|
571
|
-
repoSlug,
|
|
572
|
-
message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
if (registryResponse.status === 401 || registryResponse.status === 403) {
|
|
576
|
-
return remoteProjectLinkFailure({
|
|
577
|
-
status: "auth_required",
|
|
578
|
-
alias,
|
|
579
|
-
baseUrl,
|
|
580
|
-
repoSlug,
|
|
581
|
-
statusCode: registryResponse.status,
|
|
582
|
-
message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
if (registryResponse.ok) {
|
|
586
|
-
const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
|
|
587
|
-
for (const candidate of candidates) {
|
|
588
|
-
const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
|
|
589
|
-
if (result.ok || result.status === "auth_required")
|
|
590
|
-
return result;
|
|
591
|
-
skippedCandidates.push(candidate);
|
|
592
|
-
}
|
|
593
|
-
if (mode === "backfill-only") {
|
|
594
|
-
return remoteProjectLinkFailure({
|
|
595
|
-
status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
|
|
596
|
-
alias,
|
|
597
|
-
baseUrl,
|
|
598
|
-
repoSlug,
|
|
599
|
-
message: candidates.length > 0 ? `Remote registry has checkout candidates for ${repoSlug}, but none validated for ${alias}.` : `Remote registry has ${repoSlug}, but no server checkout path is linked yet.`,
|
|
600
|
-
skippedCandidates
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
} else if (registryResponse.status === 404) {
|
|
604
|
-
if (mode === "backfill-only") {
|
|
605
|
-
return remoteProjectLinkFailure({
|
|
606
|
-
status: "project_not_registered",
|
|
607
|
-
alias,
|
|
608
|
-
baseUrl,
|
|
609
|
-
repoSlug,
|
|
610
|
-
statusCode: registryResponse.status,
|
|
611
|
-
message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
} else {
|
|
615
|
-
return remoteProjectLinkFailure({
|
|
616
|
-
status: "error",
|
|
617
|
-
alias,
|
|
618
|
-
baseUrl,
|
|
619
|
-
repoSlug,
|
|
620
|
-
statusCode: registryResponse.status,
|
|
621
|
-
message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
|
|
622
|
-
skippedCandidates
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
let prepareResponse;
|
|
626
|
-
try {
|
|
627
|
-
prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
|
|
628
|
-
method: "POST",
|
|
629
|
-
headers: { "content-type": "application/json" },
|
|
630
|
-
body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
|
|
631
|
-
});
|
|
632
|
-
} catch (error) {
|
|
633
|
-
return remoteProjectLinkFailure({
|
|
634
|
-
status: "error",
|
|
635
|
-
alias,
|
|
636
|
-
baseUrl,
|
|
637
|
-
repoSlug,
|
|
638
|
-
message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
if (prepareResponse.status === 401 || prepareResponse.status === 403) {
|
|
642
|
-
return remoteProjectLinkFailure({
|
|
643
|
-
status: "auth_required",
|
|
644
|
-
alias,
|
|
645
|
-
baseUrl,
|
|
646
|
-
repoSlug,
|
|
647
|
-
statusCode: prepareResponse.status,
|
|
648
|
-
message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
if (!prepareResponse.ok) {
|
|
652
|
-
return remoteProjectLinkFailure({
|
|
653
|
-
status: "error",
|
|
654
|
-
alias,
|
|
655
|
-
baseUrl,
|
|
656
|
-
repoSlug,
|
|
657
|
-
statusCode: prepareResponse.status,
|
|
658
|
-
message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
|
|
659
|
-
skippedCandidates
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
|
|
663
|
-
if (!preparedPath) {
|
|
664
|
-
return remoteProjectLinkFailure({
|
|
665
|
-
status: "invalid_root",
|
|
666
|
-
alias,
|
|
667
|
-
baseUrl,
|
|
668
|
-
repoSlug,
|
|
669
|
-
message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
|
|
670
|
-
skippedCandidates
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
|
|
674
|
-
}
|
|
675
|
-
async function ensureServerForCli(projectRoot) {
|
|
676
|
-
try {
|
|
677
|
-
const selected = resolveSelectedConnection(projectRoot);
|
|
678
|
-
if (selected?.connection.kind === "remote") {
|
|
679
|
-
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
680
|
-
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
681
|
-
const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
|
|
682
|
-
return {
|
|
683
|
-
baseUrl: selected.connection.baseUrl,
|
|
684
|
-
authToken,
|
|
685
|
-
connectionKind: "remote",
|
|
686
|
-
serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
reportServerPhase("Starting local Rig server\u2026");
|
|
690
|
-
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
691
|
-
return {
|
|
692
|
-
baseUrl: connection.baseUrl,
|
|
693
|
-
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
694
|
-
connectionKind: "local",
|
|
695
|
-
serverProjectRoot: resolve2(projectRoot)
|
|
696
|
-
};
|
|
697
|
-
} catch (error) {
|
|
698
|
-
if (error instanceof Error) {
|
|
699
|
-
throw new CliError(error.message, 1);
|
|
700
|
-
}
|
|
701
|
-
throw error;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
function mergeCookie(existing, name, value) {
|
|
705
|
-
const encoded = `${name}=${encodeURIComponent(value)}`;
|
|
706
|
-
if (!existing?.trim())
|
|
707
|
-
return encoded;
|
|
708
|
-
const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
|
|
709
|
-
return [...parts, encoded].join("; ");
|
|
710
|
-
}
|
|
711
|
-
function queryAuthFallbackEnabled(env = process.env) {
|
|
712
|
-
return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
|
|
713
|
-
}
|
|
714
|
-
function mergeHeaders(headers, authToken) {
|
|
715
|
-
const merged = new Headers(headers);
|
|
716
|
-
if (authToken) {
|
|
717
|
-
const bearer = `Bearer ${authToken}`;
|
|
718
|
-
merged.set("authorization", bearer);
|
|
719
|
-
merged.set("x-auth", bearer);
|
|
720
|
-
merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
|
|
721
|
-
}
|
|
722
|
-
return merged;
|
|
723
|
-
}
|
|
724
|
-
function diagnosticMessage(payload) {
|
|
725
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
726
|
-
return null;
|
|
727
|
-
const record = payload;
|
|
728
|
-
const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
|
|
729
|
-
const messages = diagnostics.flatMap((entry) => {
|
|
730
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
731
|
-
return [];
|
|
732
|
-
const diagnostic = entry;
|
|
733
|
-
const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
|
|
734
|
-
const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
|
|
735
|
-
return message ? [`${kind}: ${message}`] : [];
|
|
736
|
-
});
|
|
737
|
-
return messages.length > 0 ? messages.join("; ") : null;
|
|
738
|
-
}
|
|
739
|
-
async function probeServerReachability(baseUrl, authToken) {
|
|
740
|
-
try {
|
|
741
|
-
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
742
|
-
headers: mergeHeaders(undefined, authToken),
|
|
743
|
-
signal: AbortSignal.timeout(1500)
|
|
744
|
-
});
|
|
745
|
-
return response.ok;
|
|
746
|
-
} catch {
|
|
747
|
-
return false;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
751
|
-
const key = resolve2(projectRoot);
|
|
752
|
-
const cached = serverReachabilityCache.get(key);
|
|
753
|
-
if (cached)
|
|
754
|
-
return cached;
|
|
755
|
-
const probe = probeServerReachability(baseUrl, authToken);
|
|
756
|
-
serverReachabilityCache.set(key, probe);
|
|
757
|
-
return probe;
|
|
758
|
-
}
|
|
759
|
-
function describeSelectedServer(projectRoot, server) {
|
|
760
|
-
try {
|
|
761
|
-
const selected = resolveSelectedConnection(projectRoot);
|
|
762
|
-
if (selected) {
|
|
763
|
-
return {
|
|
764
|
-
alias: selected.alias,
|
|
765
|
-
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
} catch {}
|
|
769
|
-
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
770
|
-
}
|
|
771
|
-
async function buildServerFailureContext(projectRoot, server) {
|
|
772
|
-
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
773
|
-
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
774
|
-
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
775
|
-
return {
|
|
776
|
-
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
777
|
-
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
function isLoopbackRemoteBaseUrl(baseUrl) {
|
|
781
|
-
try {
|
|
782
|
-
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
783
|
-
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
784
|
-
} catch {
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
function canUseRemoteWithoutProjectRoot(pathname) {
|
|
789
|
-
return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
|
|
790
|
-
}
|
|
791
|
-
async function requestServerJson(context, pathname, init = {}) {
|
|
792
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
793
|
-
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
794
|
-
let scopedServerProjectRoot = server.serverProjectRoot;
|
|
795
|
-
if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
796
|
-
const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
|
|
797
|
-
if (link.ok && link.serverProjectRoot) {
|
|
798
|
-
scopedServerProjectRoot = link.serverProjectRoot;
|
|
799
|
-
} else {
|
|
800
|
-
const repo = readRepoConnection(context.projectRoot);
|
|
801
|
-
const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
|
|
802
|
-
throw new CliError(`Remote server ${target} is selected for ${link.repoSlug ?? repo?.project ?? "this repo"}, but this checkout has no server-host project root link. ${link.message}`, 1, { hint: link.hint });
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
const headers = mergeHeaders(init.headers, server.authToken);
|
|
806
|
-
if (scopedServerProjectRoot)
|
|
807
|
-
headers.set("x-rig-project-root", scopedServerProjectRoot);
|
|
808
|
-
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
809
|
-
requestUrl.searchParams.set("rt", server.authToken);
|
|
810
|
-
}
|
|
811
|
-
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
812
|
-
let response;
|
|
813
|
-
try {
|
|
814
|
-
response = await fetch(requestUrl, {
|
|
815
|
-
...init,
|
|
816
|
-
headers
|
|
817
|
-
});
|
|
818
|
-
} catch (error) {
|
|
819
|
-
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
820
|
-
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
821
|
-
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
822
|
-
}
|
|
823
|
-
const text = await response.text();
|
|
824
|
-
const payload = text.trim().length > 0 ? (() => {
|
|
825
|
-
try {
|
|
826
|
-
return JSON.parse(text);
|
|
827
|
-
} catch {
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
})() : null;
|
|
831
|
-
if (!response.ok) {
|
|
832
|
-
const diagnostics = diagnosticMessage(payload);
|
|
833
|
-
const rawDetail = diagnostics ?? (text || response.statusText);
|
|
834
|
-
const detail = diagnostics ? rawDetail : rawDetail.split(`
|
|
835
|
-
`).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
|
|
836
|
-
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
837
|
-
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
838
|
-
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
839
|
-
}
|
|
840
|
-
return payload;
|
|
841
|
-
}
|
|
842
|
-
async function postGitHubTokenViaServer(context, token, options = {}) {
|
|
843
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
844
|
-
const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
|
|
845
|
-
reportServerPhase("POST /api/github/auth/token\u2026");
|
|
846
|
-
let response;
|
|
847
|
-
try {
|
|
848
|
-
response = await fetch(requestUrl, {
|
|
849
|
-
method: "POST",
|
|
850
|
-
headers: { "content-type": "application/json" },
|
|
851
|
-
body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
|
|
852
|
-
});
|
|
853
|
-
} catch (error) {
|
|
854
|
-
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
855
|
-
throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
|
|
856
|
-
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
857
|
-
}
|
|
858
|
-
const payload = await response.json().catch(() => null);
|
|
859
|
-
if (!response.ok) {
|
|
860
|
-
const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
|
|
861
|
-
throw new CliError(`Rig server auth bootstrap failed (${response.status}): ${detail}`, 1, { hint: "Re-run `rig github auth import-gh`, or check the selected server with `rig server status`." });
|
|
862
|
-
}
|
|
863
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
864
|
-
}
|
|
865
|
-
async function prepareRemoteCheckoutViaServer(context, input) {
|
|
866
|
-
const payload = await requestServerJson(context, `/api/projects/${encodeURIComponent(input.repoSlug)}/prepare-checkout`, {
|
|
867
|
-
method: "POST",
|
|
868
|
-
headers: { "content-type": "application/json" },
|
|
869
|
-
body: JSON.stringify({ checkout: input.checkout, repoUrl: input.repoUrl, baseDir: input.baseDir })
|
|
870
|
-
});
|
|
871
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
872
|
-
}
|
|
873
|
-
async function registerProjectViaServer(context, input) {
|
|
874
|
-
const payload = await requestServerJson(context, "/api/projects", {
|
|
875
|
-
method: "POST",
|
|
876
|
-
headers: { "content-type": "application/json" },
|
|
877
|
-
body: JSON.stringify(input)
|
|
878
|
-
});
|
|
879
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
880
|
-
}
|
|
881
|
-
function sleep(ms) {
|
|
882
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
883
|
-
}
|
|
884
|
-
function isRetryableProjectRootSwitchError(error) {
|
|
885
|
-
if (!(error instanceof Error))
|
|
886
|
-
return false;
|
|
887
|
-
const message = error.message.toLowerCase();
|
|
888
|
-
return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
|
|
889
|
-
}
|
|
890
|
-
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
891
|
-
const timeoutMs = options.timeoutMs ?? 30000;
|
|
892
|
-
const pollMs = options.pollMs ?? 1000;
|
|
893
|
-
const deadline = Date.now() + timeoutMs;
|
|
894
|
-
let lastError;
|
|
895
|
-
let switched = null;
|
|
896
|
-
while (Date.now() < deadline) {
|
|
897
|
-
try {
|
|
898
|
-
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
899
|
-
method: "POST",
|
|
900
|
-
headers: { "content-type": "application/json" },
|
|
901
|
-
body: JSON.stringify({ projectRoot })
|
|
902
|
-
});
|
|
903
|
-
break;
|
|
904
|
-
} catch (error) {
|
|
905
|
-
lastError = error;
|
|
906
|
-
if (!isRetryableProjectRootSwitchError(error))
|
|
907
|
-
throw error;
|
|
908
|
-
await sleep(pollMs);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
if (!switched) {
|
|
912
|
-
throw new CliError(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
|
|
913
|
-
}
|
|
914
|
-
const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
|
|
915
|
-
if (record.ok === true) {
|
|
916
|
-
writeRepoServerProjectRoot(context.projectRoot, projectRoot);
|
|
917
|
-
return { ok: true, switched: record };
|
|
918
|
-
}
|
|
919
|
-
throw new CliError(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
|
|
920
|
-
}
|
|
921
|
-
async function ensureTaskLabelsViaServer(context) {
|
|
922
|
-
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
923
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
924
|
-
}
|
|
925
|
-
async function listGitHubProjectsViaServer(context, owner) {
|
|
926
|
-
const url = new URL("http://rig.local/api/github/projects");
|
|
927
|
-
url.searchParams.set("owner", owner);
|
|
928
|
-
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
929
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
930
|
-
}
|
|
931
|
-
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
932
|
-
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
933
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
934
|
-
}
|
|
935
|
-
var scopedGitHubBearerTokens, serverPhaseListener = null, serverReachabilityCache, RESUMABLE_RUN_STATUSES;
|
|
936
|
-
var init__server_client = __esm(() => {
|
|
937
|
-
init_runner();
|
|
938
|
-
init__connection_state();
|
|
939
|
-
scopedGitHubBearerTokens = new Map;
|
|
940
|
-
serverReachabilityCache = new Map;
|
|
941
|
-
RESUMABLE_RUN_STATUSES = new Set([
|
|
942
|
-
"created",
|
|
943
|
-
"preparing",
|
|
944
|
-
"running",
|
|
945
|
-
"validating",
|
|
946
|
-
"reviewing",
|
|
947
|
-
"stopped",
|
|
948
|
-
"failed",
|
|
949
|
-
"needs-attention",
|
|
950
|
-
"needs_attention"
|
|
951
|
-
]);
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
// packages/cli/src/commands/_pi-install.ts
|
|
955
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
956
|
-
import { homedir as homedir2 } from "os";
|
|
957
|
-
import { resolve as resolve3 } from "path";
|
|
958
|
-
async function defaultCommandRunner(command, options = {}) {
|
|
959
|
-
const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
|
|
960
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
961
|
-
new Response(proc.stdout).text(),
|
|
962
|
-
new Response(proc.stderr).text(),
|
|
963
|
-
proc.exited
|
|
964
|
-
]);
|
|
965
|
-
return { exitCode, stdout, stderr };
|
|
966
|
-
}
|
|
967
|
-
function resolvePiRigExtensionPath(homeDir) {
|
|
968
|
-
return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
|
|
969
|
-
}
|
|
970
|
-
function resolvePiRigPackageSource(projectRoot, exists = existsSync3) {
|
|
971
|
-
const localPackage = resolve3(projectRoot, "packages", "pi-rig");
|
|
972
|
-
if (exists(resolve3(localPackage, "package.json")))
|
|
973
|
-
return localPackage;
|
|
974
|
-
return `npm:${PI_RIG_PACKAGE_NAME}`;
|
|
975
|
-
}
|
|
976
|
-
function resolvePiHomeDir(inputHomeDir) {
|
|
977
|
-
return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir2();
|
|
978
|
-
}
|
|
979
|
-
function piListContainsPiRig(output) {
|
|
980
|
-
return output.split(/\r?\n/).some((line) => {
|
|
981
|
-
const normalized = line.trim();
|
|
982
|
-
return normalized.includes(PI_RIG_PACKAGE_NAME) || normalized.includes(LEGACY_PI_RIG_PACKAGE_NAME) || /(?:^|[\\/])packages[\\/]pi-rig(?:$|\s)/.test(normalized);
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
async function safeRun(runner, command, options) {
|
|
986
|
-
try {
|
|
987
|
-
return await runner(command, options);
|
|
988
|
-
} catch (error) {
|
|
989
|
-
return { exitCode: 1, stdout: "", stderr: error instanceof Error ? error.message : String(error) };
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
function splitInstallCommand(value) {
|
|
993
|
-
return value.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((part) => part.replace(/^['"]|['"]$/g, "")) ?? [];
|
|
994
|
-
}
|
|
995
|
-
async function ensurePiBinaryAvailable(input) {
|
|
996
|
-
const current = await safeRun(input.runner, ["pi", "--version"]);
|
|
997
|
-
if (current.exitCode === 0) {
|
|
998
|
-
const updateCommand = process.env.RIG_PI_UPDATE_COMMAND?.trim();
|
|
999
|
-
if (updateCommand) {
|
|
1000
|
-
const parts2 = splitInstallCommand(updateCommand);
|
|
1001
|
-
if (parts2.length > 0)
|
|
1002
|
-
await safeRun(input.runner, parts2, input.projectRoot ? { cwd: input.projectRoot } : undefined);
|
|
1003
|
-
}
|
|
1004
|
-
return { ok: true, detail: (current.stdout || current.stderr).trim() || undefined };
|
|
1005
|
-
}
|
|
1006
|
-
const installCommand = process.env.RIG_PI_INSTALL_COMMAND?.trim() || "bunx @earendil-works/pi-coding-agent@latest install";
|
|
1007
|
-
const parts = splitInstallCommand(installCommand);
|
|
1008
|
-
if (parts.length === 0) {
|
|
1009
|
-
return { ok: false, error: (current.stderr || current.stdout).trim() || "pi --version failed" };
|
|
1010
|
-
}
|
|
1011
|
-
const install = await safeRun(input.runner, parts, input.projectRoot ? { cwd: input.projectRoot } : undefined);
|
|
1012
|
-
if (install.exitCode !== 0) {
|
|
1013
|
-
return { ok: false, installedOrUpdated: true, error: (install.stderr || install.stdout).trim() || `Pi install command failed (${install.exitCode})` };
|
|
1014
|
-
}
|
|
1015
|
-
const next = await safeRun(input.runner, ["pi", "--version"]);
|
|
1016
|
-
return {
|
|
1017
|
-
ok: next.exitCode === 0,
|
|
1018
|
-
installedOrUpdated: true,
|
|
1019
|
-
detail: (next.stdout || next.stderr).trim() || undefined,
|
|
1020
|
-
...next.exitCode === 0 ? {} : { error: (next.stderr || next.stdout).trim() || "pi --version failed after install" }
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
function removeManagedLegacyPiRigBridge(homeDir, exists = existsSync3) {
|
|
1024
|
-
const extensionPath = resolvePiRigExtensionPath(homeDir);
|
|
1025
|
-
const indexPath = resolve3(extensionPath, "index.ts");
|
|
1026
|
-
if (!exists(indexPath))
|
|
1027
|
-
return;
|
|
1028
|
-
try {
|
|
1029
|
-
const content = readFileSync3(indexPath, "utf8");
|
|
1030
|
-
if (content === LEGACY_PI_RIG_MARKER || content.includes("Managed by Rig. Source package: @rig/pi-rig")) {
|
|
1031
|
-
rmSync(extensionPath, { recursive: true, force: true });
|
|
1032
|
-
}
|
|
1033
|
-
} catch {}
|
|
1034
|
-
}
|
|
1035
|
-
async function checkPiRigInstall(input = {}) {
|
|
1036
|
-
const home = resolvePiHomeDir(input.homeDir);
|
|
1037
|
-
const extensionPath = resolvePiRigExtensionPath(home);
|
|
1038
|
-
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
1039
|
-
return {
|
|
1040
|
-
extensionPath,
|
|
1041
|
-
pi: { ok: true, label: "pi", detail: "fake-pi" },
|
|
1042
|
-
piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
const exists = input.exists ?? existsSync3;
|
|
1046
|
-
const runner = input.commandRunner ?? defaultCommandRunner;
|
|
1047
|
-
const piResult = await safeRun(runner, ["pi", "--version"]);
|
|
1048
|
-
const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
|
|
1049
|
-
const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
|
|
1050
|
-
${piListResult.stderr}`);
|
|
1051
|
-
const legacyBridge = exists(resolve3(extensionPath, "index.ts"));
|
|
1052
|
-
const hasPiRig = listedPiRig;
|
|
1053
|
-
return {
|
|
1054
|
-
extensionPath,
|
|
1055
|
-
pi: {
|
|
1056
|
-
ok: piResult.exitCode === 0,
|
|
1057
|
-
label: "pi",
|
|
1058
|
-
detail: (piResult.stdout || piResult.stderr).trim() || undefined,
|
|
1059
|
-
hint: piResult.exitCode === 0 ? undefined : "Install Pi or run `rig init --yes` to install/update the Pi runtime."
|
|
1060
|
-
},
|
|
1061
|
-
piRig: {
|
|
1062
|
-
ok: hasPiRig,
|
|
1063
|
-
label: "pi-rig global extension",
|
|
1064
|
-
detail: hasPiRig ? piListResult.stdout.trim() || PI_RIG_PACKAGE_NAME : legacyBridge ? `${extensionPath} (legacy bridge; reinstall required)` : undefined,
|
|
1065
|
-
hint: hasPiRig ? undefined : "Run `rig init --yes` to install/enable the global pi-rig package with `pi install`."
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
}
|
|
1069
|
-
async function ensurePiRigInstalled(input) {
|
|
1070
|
-
const home = resolvePiHomeDir(input.homeDir);
|
|
1071
|
-
if (process.env.RIG_TEST_FAKE_PI_INSTALL === "1") {
|
|
1072
|
-
const status2 = await checkPiRigInstall({ homeDir: home, commandRunner: input.commandRunner });
|
|
1073
|
-
return { ...status2, installedPath: status2.extensionPath };
|
|
1074
|
-
}
|
|
1075
|
-
const runner = input.commandRunner ?? defaultCommandRunner;
|
|
1076
|
-
const piAvailable = await ensurePiBinaryAvailable({ runner, projectRoot: input.projectRoot });
|
|
1077
|
-
const status = piAvailable.ok ? await checkPiRigInstall({ homeDir: home, commandRunner: runner }) : {
|
|
1078
|
-
extensionPath: resolvePiRigExtensionPath(home),
|
|
1079
|
-
pi: { ok: false, label: "pi", detail: piAvailable.error, hint: "Install/update Pi with RIG_PI_INSTALL_COMMAND or install Pi manually." },
|
|
1080
|
-
piRig: { ok: false, label: "pi-rig global extension", hint: "Pi is required before pi-rig can be installed." }
|
|
1081
|
-
};
|
|
1082
|
-
if (!piAvailable.ok) {
|
|
1083
|
-
throw new Error(`Pi install/update failed: ${piAvailable.error ?? "pi unavailable"}`);
|
|
1084
|
-
}
|
|
1085
|
-
const packageSource = resolvePiRigPackageSource(input.projectRoot);
|
|
1086
|
-
removeManagedLegacyPiRigBridge(home);
|
|
1087
|
-
const install = await runner(["pi", "install", packageSource], { cwd: input.projectRoot });
|
|
1088
|
-
if (install.exitCode !== 0) {
|
|
1089
|
-
throw new Error(`pi-rig install failed: ${(install.stderr || install.stdout).trim() || `exit ${install.exitCode}`}`);
|
|
1090
|
-
}
|
|
1091
|
-
const next = await checkPiRigInstall({ homeDir: home, commandRunner: runner });
|
|
1092
|
-
return { ...next, installedPath: packageSource };
|
|
1093
|
-
}
|
|
1094
|
-
async function ensureRemotePiRigInstalled(input) {
|
|
1095
|
-
const payload = await input.requestJson("/api/pi-rig/install", {
|
|
1096
|
-
method: "POST",
|
|
1097
|
-
headers: { "content-type": "application/json" },
|
|
1098
|
-
body: JSON.stringify({ package: PI_RIG_PACKAGE_NAME, scope: "global" })
|
|
1099
|
-
});
|
|
1100
|
-
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
1101
|
-
const piOk = record.piOk === true || record.ok === true;
|
|
1102
|
-
const piRigOk = record.piRigOk === true || record.installed === true || record.ok === true;
|
|
1103
|
-
const extensionPath = typeof record.extensionPath === "string" ? record.extensionPath : "remote:~/.pi/agent/extensions/pi-rig";
|
|
1104
|
-
return {
|
|
1105
|
-
remote: true,
|
|
1106
|
-
extensionPath,
|
|
1107
|
-
pi: {
|
|
1108
|
-
ok: piOk,
|
|
1109
|
-
label: "pi",
|
|
1110
|
-
detail: typeof record.piVersion === "string" ? record.piVersion : undefined,
|
|
1111
|
-
hint: piOk ? undefined : "Install/update Pi on the selected remote Rig server."
|
|
1112
|
-
},
|
|
1113
|
-
piRig: {
|
|
1114
|
-
ok: piRigOk,
|
|
1115
|
-
label: "pi-rig global extension",
|
|
1116
|
-
detail: extensionPath,
|
|
1117
|
-
hint: piRigOk ? undefined : "Install/enable pi-rig on the selected remote Rig server."
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
async function buildPiSetupChecks(input = {}) {
|
|
1122
|
-
const status = await checkPiRigInstall(input);
|
|
1123
|
-
return [status.pi, status.piRig];
|
|
1124
|
-
}
|
|
1125
|
-
var PI_RIG_PACKAGE_NAME = "@h-rig/pi-rig", LEGACY_PI_RIG_PACKAGE_NAME = "@rig/pi-rig", LEGACY_PI_RIG_MARKER = `// Managed by Rig. Source package: @rig/pi-rig.
|
|
1126
|
-
export { default } from '@rig/pi-rig';
|
|
1127
|
-
`;
|
|
1128
|
-
var init__pi_install = () => {};
|
|
1129
|
-
|
|
1130
|
-
// packages/cli/src/commands/_snapshot-upload.ts
|
|
1131
|
-
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
1132
|
-
import { dirname as dirname3, resolve as resolve4, relative, sep } from "path";
|
|
1133
|
-
function toPosixPath(path) {
|
|
1134
|
-
return path.split(sep).join("/");
|
|
1135
|
-
}
|
|
1136
|
-
function assertManifestPath(root, relativePath) {
|
|
1137
|
-
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\x00")) {
|
|
1138
|
-
throw new Error(`Invalid snapshot path: ${relativePath}`);
|
|
1139
|
-
}
|
|
1140
|
-
const resolved = resolve4(root, relativePath);
|
|
1141
|
-
const relativeToRoot = relative(root, resolved);
|
|
1142
|
-
if (relativeToRoot.startsWith("..") || relativeToRoot === ".." || resolve4(relativeToRoot) === resolved) {
|
|
1143
|
-
throw new Error(`Snapshot path escapes project root: ${relativePath}`);
|
|
1144
|
-
}
|
|
1145
|
-
return resolved;
|
|
1146
|
-
}
|
|
1147
|
-
async function buildSnapshotUploadManifest(projectRoot, options = {}) {
|
|
1148
|
-
const root = resolve4(projectRoot);
|
|
1149
|
-
const excludedDirectories = [...new Set([
|
|
1150
|
-
...DEFAULT_EXCLUDED_DIRECTORIES,
|
|
1151
|
-
...options.excludedDirectories ?? []
|
|
1152
|
-
])];
|
|
1153
|
-
const excludedSet = new Set(excludedDirectories);
|
|
1154
|
-
const files = [];
|
|
1155
|
-
async function visit(dir) {
|
|
1156
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
1157
|
-
for (const entry of entries) {
|
|
1158
|
-
if (entry.isDirectory() && excludedSet.has(entry.name))
|
|
1159
|
-
continue;
|
|
1160
|
-
const fullPath = resolve4(dir, entry.name);
|
|
1161
|
-
if (entry.isDirectory()) {
|
|
1162
|
-
await visit(fullPath);
|
|
1163
|
-
continue;
|
|
1164
|
-
}
|
|
1165
|
-
if (!entry.isFile())
|
|
1166
|
-
continue;
|
|
1167
|
-
files.push(toPosixPath(relative(root, fullPath)));
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
await visit(root);
|
|
1171
|
-
files.sort();
|
|
1172
|
-
return { root, files, excludedDirectories };
|
|
1173
|
-
}
|
|
1174
|
-
async function createSnapshotUploadArchive(projectRoot, options = {}) {
|
|
1175
|
-
const manifest = await buildSnapshotUploadManifest(projectRoot, options);
|
|
1176
|
-
const files = await Promise.all(manifest.files.map(async (path) => {
|
|
1177
|
-
const fullPath = assertManifestPath(manifest.root, path);
|
|
1178
|
-
return {
|
|
1179
|
-
path,
|
|
1180
|
-
contentBase64: (await readFile(fullPath)).toString("base64")
|
|
1181
|
-
};
|
|
1182
|
-
}));
|
|
1183
|
-
return {
|
|
1184
|
-
version: SNAPSHOT_ARCHIVE_VERSION,
|
|
1185
|
-
root: manifest.root,
|
|
1186
|
-
files,
|
|
1187
|
-
excludedDirectories: manifest.excludedDirectories,
|
|
1188
|
-
createdAt: (options.now?.() ?? new Date).toISOString()
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
function encodeSnapshotUploadArchive(archive) {
|
|
1192
|
-
return Buffer.from(JSON.stringify(archive), "utf8").toString("base64");
|
|
1193
|
-
}
|
|
1194
|
-
async function uploadSnapshotArchiveViaServer(context, input) {
|
|
1195
|
-
const payload = await requestServerJson(context, `/api/projects/${encodeURIComponent(input.repoSlug)}/upload-snapshot`, {
|
|
1196
|
-
method: "POST",
|
|
1197
|
-
headers: { "content-type": "application/json" },
|
|
1198
|
-
body: JSON.stringify({
|
|
1199
|
-
archiveContentBase64: encodeSnapshotUploadArchive(input.archive),
|
|
1200
|
-
contentType: SNAPSHOT_ARCHIVE_CONTENT_TYPE,
|
|
1201
|
-
baseDir: input.baseDir
|
|
1202
|
-
})
|
|
1203
|
-
});
|
|
1204
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
1205
|
-
}
|
|
1206
|
-
var SNAPSHOT_ARCHIVE_VERSION = 1, SNAPSHOT_ARCHIVE_CONTENT_TYPE = "application/vnd.rig.snapshot+json", DEFAULT_EXCLUDED_DIRECTORIES;
|
|
1207
|
-
var init__snapshot_upload = __esm(() => {
|
|
1208
|
-
init__server_client();
|
|
1209
|
-
DEFAULT_EXCLUDED_DIRECTORIES = new Set([
|
|
1210
|
-
".git",
|
|
1211
|
-
".rig",
|
|
1212
|
-
"node_modules",
|
|
1213
|
-
".turbo",
|
|
1214
|
-
".next",
|
|
1215
|
-
".cache",
|
|
1216
|
-
"coverage",
|
|
1217
|
-
"dist",
|
|
1218
|
-
"build",
|
|
1219
|
-
"out"
|
|
1220
|
-
]);
|
|
1221
|
-
});
|
|
1222
|
-
|
|
1223
|
-
// packages/cli/src/commands/_parsers.ts
|
|
1224
|
-
async function loadRigConfigOrNull(projectRoot) {
|
|
1225
|
-
try {
|
|
1226
|
-
const { loadConfig } = await import("@rig/core/load-config");
|
|
1227
|
-
return await loadConfig(projectRoot);
|
|
1228
|
-
} catch {
|
|
1229
|
-
return null;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
var init__parsers = __esm(() => {
|
|
1233
|
-
init_runner();
|
|
1234
|
-
});
|
|
1235
|
-
|
|
1236
|
-
// packages/cli/src/commands/_doctor-checks.ts
|
|
1237
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
1238
|
-
import { resolve as resolve5 } from "path";
|
|
1239
|
-
import { isSupportedBunVersion, MIN_SUPPORTED_BUN_VERSION } from "@rig/runtime/control-plane/setup-version";
|
|
1240
|
-
function check(id, label, status, detail, remediation) {
|
|
1241
|
-
return {
|
|
1242
|
-
id,
|
|
1243
|
-
label,
|
|
1244
|
-
status,
|
|
1245
|
-
...detail ? { detail } : {},
|
|
1246
|
-
...remediation ? { remediation } : {}
|
|
1247
|
-
};
|
|
1248
|
-
}
|
|
1249
|
-
function errorMessage(error) {
|
|
1250
|
-
return error instanceof Error ? error.message : String(error);
|
|
1251
|
-
}
|
|
1252
|
-
function isAuthenticated(payload) {
|
|
1253
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1254
|
-
return false;
|
|
1255
|
-
const record = payload;
|
|
1256
|
-
return record.signedIn === true || record.authenticated === true || record.status === "authenticated" || record.ok === true && typeof record.login === "string" && record.login.trim().length > 0;
|
|
1257
|
-
}
|
|
1258
|
-
function repoSlugFromConfig(config) {
|
|
1259
|
-
const project = config?.project;
|
|
1260
|
-
if (project && typeof project === "object" && !Array.isArray(project)) {
|
|
1261
|
-
const record = project;
|
|
1262
|
-
if (typeof record.repo === "string" && /^([^/\s]+)\/([^/\s]+)$/.test(record.repo))
|
|
1263
|
-
return record.repo;
|
|
1264
|
-
if (typeof record.name === "string" && /^([^/\s]+)\/([^/\s]+)$/.test(record.name))
|
|
1265
|
-
return record.name;
|
|
1266
|
-
}
|
|
1267
|
-
const taskSource = config?.taskSource;
|
|
1268
|
-
if (taskSource && typeof taskSource === "object" && !Array.isArray(taskSource)) {
|
|
1269
|
-
const source = taskSource;
|
|
1270
|
-
if (typeof source.owner === "string" && typeof source.repo === "string")
|
|
1271
|
-
return `${source.owner}/${source.repo}`;
|
|
1272
|
-
}
|
|
1273
|
-
return null;
|
|
1274
|
-
}
|
|
1275
|
-
function loadFallbackConfig(projectRoot) {
|
|
1276
|
-
const candidates = ["rig.config.ts", "rig.config.mts", "rig.config.json"];
|
|
1277
|
-
for (const name of candidates) {
|
|
1278
|
-
const path = resolve5(projectRoot, name);
|
|
1279
|
-
if (!existsSync4(path))
|
|
1280
|
-
continue;
|
|
1281
|
-
try {
|
|
1282
|
-
const source = readFileSync4(path, "utf8");
|
|
1283
|
-
if (name.endsWith(".json"))
|
|
1284
|
-
return JSON.parse(source);
|
|
1285
|
-
const owner = source.match(/owner\s*:\s*["']([^"']+)["']/)?.[1];
|
|
1286
|
-
const repo = source.match(/repo\s*:\s*["']([^"']+)["']/)?.[1];
|
|
1287
|
-
const projectRepo = source.match(/project\s*:\s*\{[^}]*repo\s*:\s*["']([^"']+)["']/s)?.[1] ?? (owner && repo ? `${owner}/${repo}` : undefined);
|
|
1288
|
-
const taskKind = source.match(/taskSource\s*:\s*\{[^}]*kind\s*:\s*["']([^"']+)["']/s)?.[1];
|
|
1289
|
-
if (projectRepo || taskKind) {
|
|
1290
|
-
return {
|
|
1291
|
-
...projectRepo ? { project: { name: projectRepo, repo: projectRepo } } : {},
|
|
1292
|
-
...taskKind ? { taskSource: { kind: taskKind, ...owner ? { owner } : {}, ...repo ? { repo } : {} } } : {}
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
} catch {
|
|
1296
|
-
return null;
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
return null;
|
|
1300
|
-
}
|
|
1301
|
-
function projectStatusSlug(projectRoot, config) {
|
|
1302
|
-
return readRepoConnection(projectRoot)?.project ?? repoSlugFromConfig(config);
|
|
1303
|
-
}
|
|
1304
|
-
function githubProjectsCheck(config) {
|
|
1305
|
-
const github = config?.github;
|
|
1306
|
-
const projects = github?.projects;
|
|
1307
|
-
if (!projects?.enabled) {
|
|
1308
|
-
return check("github-projects", "GitHub Projects status sync", "warn", "disabled or not configured", "Run `rig init --github-project <project>` or configure github.projects when Project status sync should be authoritative.");
|
|
1309
|
-
}
|
|
1310
|
-
if (projects.projectId && projects.statusFieldId) {
|
|
1311
|
-
return check("github-projects", "GitHub Projects status sync", "pass", `project ${projects.projectId}`);
|
|
1312
|
-
}
|
|
1313
|
-
return check("github-projects", "GitHub Projects status sync", "fail", "enabled but projectId/statusFieldId is incomplete", "Configure github.projects.projectId and github.projects.statusFieldId, or disable github.projects.enabled.");
|
|
1314
|
-
}
|
|
1315
|
-
function permissionAllowsPr(payload) {
|
|
1316
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1317
|
-
return null;
|
|
1318
|
-
const record = payload;
|
|
1319
|
-
if (record.canOpenPullRequest === true || record.pullRequests === true || record.push === true || record.maintain === true || record.admin === true)
|
|
1320
|
-
return true;
|
|
1321
|
-
if (record.canOpenPullRequest === false || record.pullRequests === false || record.push === false)
|
|
1322
|
-
return false;
|
|
1323
|
-
const permissions = record.permissions;
|
|
1324
|
-
if (permissions && typeof permissions === "object" && !Array.isArray(permissions)) {
|
|
1325
|
-
const p = permissions;
|
|
1326
|
-
if (p.push === true || p.maintain === true || p.admin === true)
|
|
1327
|
-
return true;
|
|
1328
|
-
if (p.push === false && p.maintain !== true && p.admin !== true)
|
|
1329
|
-
return false;
|
|
1330
|
-
}
|
|
1331
|
-
return null;
|
|
1332
|
-
}
|
|
1333
|
-
function labelsReady(payload) {
|
|
1334
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1335
|
-
return null;
|
|
1336
|
-
const record = payload;
|
|
1337
|
-
if (record.ok === true || record.ready === true || record.labelsReady === true)
|
|
1338
|
-
return true;
|
|
1339
|
-
if (record.ok === false || record.ready === false || record.labelsReady === false)
|
|
1340
|
-
return false;
|
|
1341
|
-
return null;
|
|
1342
|
-
}
|
|
1343
|
-
function prMergeCheck(config) {
|
|
1344
|
-
const pr = config?.pr;
|
|
1345
|
-
const merge = config?.merge;
|
|
1346
|
-
if (pr?.mode === "off" || merge?.mode === "off") {
|
|
1347
|
-
return check("pr-merge", "PR/merge automation", "warn", "automatic PR or merge is disabled", "Set pr.mode and merge.mode to auto for autonomous YOLO runs.");
|
|
1348
|
-
}
|
|
1349
|
-
return check("pr-merge", "PR/merge automation", "pass", `pr=${pr?.mode ?? "auto"}, merge=${merge?.mode ?? "auto"}, method=${merge?.method ?? "repo-default"}`);
|
|
1350
|
-
}
|
|
1351
|
-
async function runRigDoctorChecks(options) {
|
|
1352
|
-
const projectRoot = options.projectRoot;
|
|
1353
|
-
const checks = [];
|
|
1354
|
-
const which = options.which ?? ((binary) => Bun.which(binary));
|
|
1355
|
-
const bunVersion = options.bunVersion ?? Bun.version;
|
|
1356
|
-
const request = options.requestJson ?? ((pathname, init) => requestServerJson({ projectRoot }, pathname, init));
|
|
1357
|
-
const loadConfig = options.loadConfig ?? loadRigConfigOrNull;
|
|
1358
|
-
const progress = options.onProgress ?? (() => {});
|
|
1359
|
-
progress("Checking local toolchain\u2026");
|
|
1360
|
-
checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
|
|
1361
|
-
progress("Loading rig.config\u2026");
|
|
1362
|
-
const loadedConfig = await loadConfig(projectRoot).catch(() => null);
|
|
1363
|
-
const config = loadedConfig ?? loadFallbackConfig(projectRoot);
|
|
1364
|
-
const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
|
|
1365
|
-
checks.push(config ? check("config", "rig.config loadable", "pass") : check("config", "rig.config loadable", hasConfigFile ? "fail" : "fail", hasConfigFile ? "config file exists but failed to load" : "missing rig.config.ts/json", "Run `rig init` or fix the config error."));
|
|
1366
|
-
const taskSourceKind = config?.taskSource?.kind;
|
|
1367
|
-
checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
|
|
1368
|
-
const repo = readRepoConnection(projectRoot);
|
|
1369
|
-
checks.push(repo ? check("project-link", "repo selected Rig server", repo.project ? "pass" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : ""}`, "Run `rig init --yes --repo owner/repo` to link this checkout to a GitHub repo slug.") : check("project-link", "repo selected Rig server", "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
|
|
1370
|
-
const selected = (() => {
|
|
1371
|
-
try {
|
|
1372
|
-
return resolveSelectedConnection(projectRoot);
|
|
1373
|
-
} catch {
|
|
1374
|
-
return null;
|
|
1375
|
-
}
|
|
1376
|
-
})();
|
|
1377
|
-
checks.push(selected ? check("connection", "selected server connection", "pass", selected.connection.kind === "remote" ? selected.connection.baseUrl : "local auto") : check("connection", "selected server", repo ? "fail" : "warn", repo ? "selected alias is missing" : "will auto-start local server", repo ? "Run `rig server list` and `rig server use <alias|local>`." : undefined));
|
|
1378
|
-
let server = null;
|
|
1379
|
-
try {
|
|
1380
|
-
progress("Connecting to the selected Rig server\u2026");
|
|
1381
|
-
server = await (options.resolveServer ?? ensureServerForCli)(projectRoot);
|
|
1382
|
-
checks.push(check("server", "Rig server reachable", "pass", `${server.connectionKind} ${server.baseUrl}`));
|
|
1383
|
-
} catch (error) {
|
|
1384
|
-
checks.push(check("server", "Rig server reachable", "fail", errorMessage(error), "Start the local Rig server or fix the selected remote connection."));
|
|
1385
|
-
}
|
|
1386
|
-
if (server || options.requestJson) {
|
|
1387
|
-
try {
|
|
1388
|
-
progress("Checking server status\u2026");
|
|
1389
|
-
const status = await request("/api/server/status");
|
|
1390
|
-
checks.push(check("server-status", "server project status", "pass", JSON.stringify(status).slice(0, 180)));
|
|
1391
|
-
} catch (error) {
|
|
1392
|
-
checks.push(check("server-status", "server project status", "fail", errorMessage(error), "Run `rig doctor` after the selected server is reachable."));
|
|
1393
|
-
}
|
|
1394
|
-
try {
|
|
1395
|
-
progress("Checking GitHub auth\u2026");
|
|
1396
|
-
const auth = await request("/api/github/auth/status");
|
|
1397
|
-
checks.push(isAuthenticated(auth) ? check("github-auth", "GitHub auth", "pass") : check("github-auth", "GitHub auth", "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
|
|
1398
|
-
} catch (error) {
|
|
1399
|
-
checks.push(check("github-auth", "GitHub auth", "fail", errorMessage(error), "Authenticate GitHub through Rig and ensure the server exposes auth status."));
|
|
1400
|
-
}
|
|
1401
|
-
try {
|
|
1402
|
-
progress("Checking GitHub repo permissions\u2026");
|
|
1403
|
-
const permissions = await request("/api/github/repo/permissions");
|
|
1404
|
-
const allowed = permissionAllowsPr(permissions);
|
|
1405
|
-
checks.push(allowed === true ? check("github-repo-permissions", "GitHub repo PR permissions", "pass", JSON.stringify(permissions).slice(0, 180)) : allowed === false ? check("github-repo-permissions", "GitHub repo PR permissions", "fail", JSON.stringify(permissions).slice(0, 180), "Grant the selected GitHub token permission to push branches, open PRs, and merge according to repo rules.") : check("github-repo-permissions", "GitHub repo PR permissions", "warn", JSON.stringify(permissions).slice(0, 180), "Confirm the selected token can push branches and open PRs."));
|
|
1406
|
-
} catch (error) {
|
|
1407
|
-
checks.push(check("github-repo-permissions", "GitHub repo PR permissions", "warn", errorMessage(error), "Ensure the server exposes repo permission checks and the token can open PRs."));
|
|
1408
|
-
}
|
|
1409
|
-
try {
|
|
1410
|
-
progress("Checking GitHub issue labels\u2026");
|
|
1411
|
-
const labels = await request("/api/workspace/task-labels");
|
|
1412
|
-
const ready = labelsReady(labels);
|
|
1413
|
-
checks.push(ready === false ? check("task-labels", "GitHub issue labels", "fail", JSON.stringify(labels).slice(0, 180), "Let Rig create required labels or create the configured lifecycle labels manually.") : check("task-labels", "GitHub issue labels", ready === true ? "pass" : "warn", JSON.stringify(labels).slice(0, 180), "Confirm required Rig lifecycle labels exist."));
|
|
1414
|
-
} catch (error) {
|
|
1415
|
-
checks.push(check("task-labels", "GitHub issue labels", "warn", errorMessage(error), "Run `rig init`/`rig doctor` after label setup is wired on the server."));
|
|
1416
|
-
}
|
|
1417
|
-
try {
|
|
1418
|
-
progress("Checking task projection\u2026");
|
|
1419
|
-
const projection = await request("/api/workspace/task-projection");
|
|
1420
|
-
checks.push(check("task-projection", "task projection", "pass", JSON.stringify(projection).slice(0, 180)));
|
|
1421
|
-
} catch (error) {
|
|
1422
|
-
checks.push(check("task-projection", "task projection", "warn", errorMessage(error), "Refresh task projection with `rig task list` or fix the task source."));
|
|
1423
|
-
}
|
|
1424
|
-
const slug = projectStatusSlug(projectRoot, config);
|
|
1425
|
-
if (slug) {
|
|
1426
|
-
try {
|
|
1427
|
-
progress("Checking server project checkout\u2026");
|
|
1428
|
-
const project = await request(`/api/projects/${encodeURIComponent(slug)}`);
|
|
1429
|
-
checks.push(check("remote-checkout", "server project checkout", "pass", JSON.stringify(project).slice(0, 180)));
|
|
1430
|
-
} catch (error) {
|
|
1431
|
-
checks.push(check("remote-checkout", "server project checkout", "warn", errorMessage(error), "Run `rig init --yes --repo owner/repo` to register/link the server project checkout."));
|
|
1432
|
-
}
|
|
1433
|
-
} else {
|
|
1434
|
-
checks.push(check("remote-checkout", "server project checkout", "warn", "repo slug unknown", "Set project.repo or run `rig init --repo owner/repo`."));
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
if (taskSourceKind === "github-issues") {
|
|
1438
|
-
checks.push(check("gh", "gh CLI fallback", which("gh") ? "pass" : "warn", which("gh") ?? undefined, "Install gh for local/dev GitHub fallback operations."));
|
|
1439
|
-
}
|
|
1440
|
-
checks.push(githubProjectsCheck(config));
|
|
1441
|
-
checks.push(prMergeCheck(config));
|
|
1442
|
-
progress("Checking Pi installation\u2026");
|
|
1443
|
-
const piChecks = await (options.piChecks ?? (() => buildPiSetupChecks()))().catch((error) => [{
|
|
1444
|
-
ok: false,
|
|
1445
|
-
label: "pi/pi-rig checks",
|
|
1446
|
-
hint: errorMessage(error)
|
|
1447
|
-
}]);
|
|
1448
|
-
for (const pi of piChecks) {
|
|
1449
|
-
checks.push(check(pi.label === "pi" ? "pi" : "pi-rig", pi.label, pi.ok ? "pass" : "warn", pi.detail, pi.hint ?? (pi.ok ? undefined : "Run `rig init --yes` to install/update Pi and enable pi-rig.")));
|
|
1450
|
-
}
|
|
1451
|
-
return checks;
|
|
1452
|
-
}
|
|
1453
|
-
function countDoctorFailures(checks) {
|
|
1454
|
-
return checks.filter((entry) => entry.status === "fail").length;
|
|
1455
|
-
}
|
|
1456
|
-
var init__doctor_checks = __esm(() => {
|
|
1457
|
-
init_runner();
|
|
1458
|
-
init__connection_state();
|
|
1459
|
-
init__server_client();
|
|
1460
|
-
init__parsers();
|
|
1461
|
-
init__pi_install();
|
|
1462
|
-
});
|
|
1463
|
-
|
|
1464
|
-
// packages/cli/src/app/theme.ts
|
|
1465
|
-
function hexToRgb(hex) {
|
|
1466
|
-
const value = hex.replace("#", "");
|
|
1467
|
-
return [
|
|
1468
|
-
Number.parseInt(value.slice(0, 2), 16),
|
|
1469
|
-
Number.parseInt(value.slice(2, 4), 16),
|
|
1470
|
-
Number.parseInt(value.slice(4, 6), 16)
|
|
1471
|
-
];
|
|
1472
|
-
}
|
|
1473
|
-
function fg(hex) {
|
|
1474
|
-
const [r, g, b] = hexToRgb(hex);
|
|
1475
|
-
return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
|
|
1476
|
-
}
|
|
1477
|
-
function bold(text) {
|
|
1478
|
-
return `\x1B[1m${text}\x1B[22m`;
|
|
1479
|
-
}
|
|
1480
|
-
function microDroneFrame(tick) {
|
|
1481
|
-
const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
|
|
1482
|
-
const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
|
|
1483
|
-
return `(${blade})${eye}(${blade})`;
|
|
1484
|
-
}
|
|
1485
|
-
function renderMicroDroneFrame(tick) {
|
|
1486
|
-
const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
|
|
1487
|
-
const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
|
|
1488
|
-
return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
|
|
1489
|
-
}
|
|
1490
|
-
var RIG_PALETTE, ink, ink2, ink3, ink4, accent, accentDim, cyan, red, yellow, DRONE_ART, EYE_FRAMES, DRONE_WIDTH, DRONE_HEIGHT, MICRO_BLADES, MICRO_DRONE_FRAMES;
|
|
1491
|
-
var init_theme = __esm(() => {
|
|
1492
|
-
RIG_PALETTE = {
|
|
1493
|
-
ink: "#f2f3f6",
|
|
1494
|
-
ink2: "#aeb0ba",
|
|
1495
|
-
ink3: "#6c6e79",
|
|
1496
|
-
ink4: "#44464f",
|
|
1497
|
-
accent: "#ccff4d",
|
|
1498
|
-
accentDim: "#a9d63f",
|
|
1499
|
-
cyan: "#56d8ff",
|
|
1500
|
-
red: "#ff5d5d",
|
|
1501
|
-
yellow: "#ffd24d"
|
|
1502
|
-
};
|
|
1503
|
-
ink = fg(RIG_PALETTE.ink);
|
|
1504
|
-
ink2 = fg(RIG_PALETTE.ink2);
|
|
1505
|
-
ink3 = fg(RIG_PALETTE.ink3);
|
|
1506
|
-
ink4 = fg(RIG_PALETTE.ink4);
|
|
1507
|
-
accent = fg(RIG_PALETTE.accent);
|
|
1508
|
-
accentDim = fg(RIG_PALETTE.accentDim);
|
|
1509
|
-
cyan = fg(RIG_PALETTE.cyan);
|
|
1510
|
-
red = fg(RIG_PALETTE.red);
|
|
1511
|
-
yellow = fg(RIG_PALETTE.yellow);
|
|
1512
|
-
DRONE_ART = [
|
|
1513
|
-
" .-=-. .-=-. ",
|
|
1514
|
-
" ( !!! ) ( !!! ) ",
|
|
1515
|
-
" '-=-'._ _.'-=-' ",
|
|
1516
|
-
" '._ _.' ",
|
|
1517
|
-
" '=$$$$$$$=.' ",
|
|
1518
|
-
" =$$$$$$$$$$$= ",
|
|
1519
|
-
" $$$@@@@@@@@@@$$$ ",
|
|
1520
|
-
" $$$@@ @@$$$ ",
|
|
1521
|
-
" $$@ ? @$$$ ",
|
|
1522
|
-
" $$$@ '-' @$$$ ",
|
|
1523
|
-
" $$$@@ @@$$$ ",
|
|
1524
|
-
" $$$@@@@@@@@@@$$$ ",
|
|
1525
|
-
" =$$$$$$$$$$$= ",
|
|
1526
|
-
" '=$$$$$$$=.' ",
|
|
1527
|
-
" _.' '._ ",
|
|
1528
|
-
" .-=-.' '.-=-. ",
|
|
1529
|
-
" ( !!! ) ( !!! ) ",
|
|
1530
|
-
" '-=-' '-=-' "
|
|
1531
|
-
];
|
|
1532
|
-
EYE_FRAMES = ["@", "o", "."];
|
|
1533
|
-
DRONE_WIDTH = DRONE_ART[0].length;
|
|
1534
|
-
DRONE_HEIGHT = DRONE_ART.length;
|
|
1535
|
-
MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
|
|
1536
|
-
MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
|
|
1537
|
-
});
|
|
1538
|
-
|
|
1539
|
-
// packages/cli/src/commands/_spinner.ts
|
|
1540
|
-
function createTtySpinner(input) {
|
|
1541
|
-
const output = input.output ?? process.stdout;
|
|
1542
|
-
const isTty = output.isTTY === true;
|
|
1543
|
-
const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
|
|
1544
|
-
let label = input.label;
|
|
1545
|
-
let frame = 0;
|
|
1546
|
-
let paused = false;
|
|
1547
|
-
let stopped = false;
|
|
1548
|
-
let lastPrintedLabel = "";
|
|
1549
|
-
const render = () => {
|
|
1550
|
-
if (stopped || paused)
|
|
1551
|
-
return;
|
|
1552
|
-
if (!isTty) {
|
|
1553
|
-
if (label !== lastPrintedLabel) {
|
|
1554
|
-
output.write(`${label}
|
|
1555
|
-
`);
|
|
1556
|
-
lastPrintedLabel = label;
|
|
1557
|
-
}
|
|
1558
|
-
return;
|
|
1559
|
-
}
|
|
1560
|
-
frame = (frame + 1) % frames.length;
|
|
1561
|
-
const glyph = frames[frame] ?? frames[0] ?? "";
|
|
1562
|
-
output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
|
|
1563
|
-
};
|
|
1564
|
-
const clearLine = () => {
|
|
1565
|
-
if (isTty)
|
|
1566
|
-
output.write("\r\x1B[2K");
|
|
1567
|
-
};
|
|
1568
|
-
render();
|
|
1569
|
-
const timer = isTty ? setInterval(render, input.intervalMs ?? 16) : null;
|
|
1570
|
-
return {
|
|
1571
|
-
setLabel(next) {
|
|
1572
|
-
label = next;
|
|
1573
|
-
render();
|
|
1574
|
-
},
|
|
1575
|
-
pause() {
|
|
1576
|
-
paused = true;
|
|
1577
|
-
clearLine();
|
|
1578
|
-
},
|
|
1579
|
-
resume() {
|
|
1580
|
-
if (stopped)
|
|
1581
|
-
return;
|
|
1582
|
-
paused = false;
|
|
1583
|
-
render();
|
|
1584
|
-
},
|
|
1585
|
-
stop(finalLine) {
|
|
1586
|
-
if (stopped)
|
|
1587
|
-
return;
|
|
1588
|
-
stopped = true;
|
|
1589
|
-
if (timer)
|
|
1590
|
-
clearInterval(timer);
|
|
1591
|
-
clearLine();
|
|
1592
|
-
if (finalLine)
|
|
1593
|
-
output.write(`${finalLine}
|
|
1594
|
-
`);
|
|
1595
|
-
}
|
|
1596
|
-
};
|
|
1597
|
-
}
|
|
1598
|
-
var SPINNER_FRAMES;
|
|
1599
|
-
var init__spinner = __esm(() => {
|
|
1600
|
-
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1601
|
-
});
|
|
1602
|
-
|
|
1603
|
-
// packages/cli/src/app/drone-ui.ts
|
|
1604
|
-
var exports_drone_ui = {};
|
|
1605
|
-
__export(exports_drone_ui, {
|
|
1606
|
-
droneWarn: () => droneWarn,
|
|
1607
|
-
droneText: () => droneText,
|
|
1608
|
-
droneStep: () => droneStep,
|
|
1609
|
-
droneSpinner: () => droneSpinner,
|
|
1610
|
-
droneSelect: () => droneSelect,
|
|
1611
|
-
droneOutro: () => droneOutro,
|
|
1612
|
-
droneNote: () => droneNote,
|
|
1613
|
-
droneIntro: () => droneIntro,
|
|
1614
|
-
droneInfo: () => droneInfo,
|
|
1615
|
-
droneError: () => droneError,
|
|
1616
|
-
droneConfirm: () => droneConfirm,
|
|
1617
|
-
droneCancel: () => droneCancel
|
|
1618
|
-
});
|
|
1619
|
-
import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
|
|
1620
|
-
function hairline(width = Math.min(process.stdout.columns ?? 80, 100)) {
|
|
1621
|
-
return ink4("\u2500".repeat(Math.max(10, width)));
|
|
1622
|
-
}
|
|
1623
|
-
function droneIntro(title, subtitle) {
|
|
1624
|
-
console.log("");
|
|
1625
|
-
console.log(` ${accent("\u258D")}${bold(ink(title))}${subtitle ? ink3(` \u2014 ${subtitle}`) : ""}`);
|
|
1626
|
-
console.log(hairline());
|
|
1627
|
-
}
|
|
1628
|
-
function droneOutro(text) {
|
|
1629
|
-
console.log(hairline());
|
|
1630
|
-
console.log(` ${accent("\u25C6")} ${ink2(text)}`);
|
|
1631
|
-
console.log("");
|
|
1632
|
-
}
|
|
1633
|
-
function droneNote(message, title) {
|
|
1634
|
-
if (title)
|
|
1635
|
-
console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
|
|
1636
|
-
for (const line of message.split(`
|
|
1637
|
-
`)) {
|
|
1638
|
-
console.log(` ${ink4("\u2502")} ${line}`);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
function droneStep(text) {
|
|
1642
|
-
console.log(` ${accent("\u203A")} ${ink(text)}`);
|
|
1643
|
-
}
|
|
1644
|
-
function droneInfo(text) {
|
|
1645
|
-
console.log(` ${cyan("\xB7")} ${ink2(text)}`);
|
|
1646
|
-
}
|
|
1647
|
-
function droneWarn(text) {
|
|
1648
|
-
console.log(` ${yellow("\u25B2")} ${ink2(text)}`);
|
|
1649
|
-
}
|
|
1650
|
-
function droneError(text) {
|
|
1651
|
-
console.log(` ${red("\u2716")} ${ink2(text)}`);
|
|
1652
|
-
}
|
|
1653
|
-
function droneCancel(text) {
|
|
1654
|
-
console.log(` ${red("\u2716")} ${ink3(text)}`);
|
|
1655
|
-
}
|
|
1656
|
-
function droneSpinner() {
|
|
1657
|
-
let active = null;
|
|
1658
|
-
return {
|
|
1659
|
-
start(message) {
|
|
1660
|
-
active = createTtySpinner({
|
|
1661
|
-
label: message,
|
|
1662
|
-
frames: MICRO_DRONE_FRAMES,
|
|
1663
|
-
styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
|
|
1664
|
-
});
|
|
1665
|
-
},
|
|
1666
|
-
stop(message) {
|
|
1667
|
-
active?.stop(message ? ` ${accent("\u25C6")} ${ink2(message)}` : undefined);
|
|
1668
|
-
active = null;
|
|
1669
|
-
},
|
|
1670
|
-
error(message) {
|
|
1671
|
-
active?.stop(message ? ` ${red("\u2716")} ${ink2(message)}` : undefined);
|
|
1672
|
-
active = null;
|
|
1673
|
-
}
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
async function runMiniTui(build) {
|
|
1677
|
-
const terminal = new ProcessTerminal;
|
|
1678
|
-
const tui = new TUI(terminal);
|
|
1679
|
-
let settled = false;
|
|
1680
|
-
return await new Promise((resolve6) => {
|
|
1681
|
-
const finish = (result) => {
|
|
1682
|
-
if (settled)
|
|
1683
|
-
return;
|
|
1684
|
-
settled = true;
|
|
1685
|
-
tui.stop();
|
|
1686
|
-
resolve6(result);
|
|
1687
|
-
};
|
|
1688
|
-
build(tui, finish);
|
|
1689
|
-
tui.start();
|
|
1690
|
-
});
|
|
1691
|
-
}
|
|
1692
|
-
async function droneSelect(input) {
|
|
1693
|
-
if (!isTty() || input.options.length === 0) {
|
|
1694
|
-
return input.initialValue ?? input.options[0]?.value ?? null;
|
|
1695
|
-
}
|
|
1696
|
-
return runMiniTui((tui, finish) => {
|
|
1697
|
-
tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}`));
|
|
1698
|
-
const items = input.options.map((option) => ({
|
|
1699
|
-
value: option.value,
|
|
1700
|
-
label: option.label,
|
|
1701
|
-
...option.hint ? { description: option.hint } : {}
|
|
1702
|
-
}));
|
|
1703
|
-
const list = new SelectList(items, Math.min(items.length, 12), SELECT_THEME);
|
|
1704
|
-
const initialIndex = input.initialValue ? items.findIndex((item) => item.value === input.initialValue) : -1;
|
|
1705
|
-
if (initialIndex > 0)
|
|
1706
|
-
list.setSelectedIndex(initialIndex);
|
|
1707
|
-
list.onSelect = (item) => finish(item.value);
|
|
1708
|
-
list.onCancel = () => finish(null);
|
|
1709
|
-
tui.addChild(list);
|
|
1710
|
-
tui.addChild(new Text(ink4(" \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel")));
|
|
1711
|
-
tui.setFocus(list);
|
|
1712
|
-
tui.addInputListener((data) => {
|
|
1713
|
-
if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
|
|
1714
|
-
finish(null);
|
|
1715
|
-
return { consume: true };
|
|
1716
|
-
}
|
|
1717
|
-
return;
|
|
1718
|
-
});
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
async function droneText(input) {
|
|
1722
|
-
if (!isTty())
|
|
1723
|
-
return input.initialValue ?? null;
|
|
1724
|
-
return runMiniTui((tui, finish) => {
|
|
1725
|
-
tui.addChild(new Text(` ${accent("\u258D")}${bold(ink(input.message))}${input.placeholder ? ink4(` (${input.placeholder})`) : ""}`));
|
|
1726
|
-
const field = new Input;
|
|
1727
|
-
if (input.initialValue)
|
|
1728
|
-
field.setValue(input.initialValue);
|
|
1729
|
-
field.onSubmit = (value) => finish(value);
|
|
1730
|
-
field.onEscape = () => finish(null);
|
|
1731
|
-
tui.addChild(field);
|
|
1732
|
-
tui.addChild(new Text(ink4(" enter submit \xB7 esc cancel")));
|
|
1733
|
-
tui.setFocus(field);
|
|
1734
|
-
tui.addInputListener((data) => {
|
|
1735
|
-
if (matchesKey(data, "ctrl+c")) {
|
|
1736
|
-
finish(null);
|
|
1737
|
-
return { consume: true };
|
|
1738
|
-
}
|
|
1739
|
-
return;
|
|
1740
|
-
});
|
|
1741
|
-
});
|
|
1742
|
-
}
|
|
1743
|
-
async function droneConfirm(input) {
|
|
1744
|
-
const answer = await droneSelect({
|
|
1745
|
-
message: input.message,
|
|
1746
|
-
options: [
|
|
1747
|
-
{ value: "yes", label: "Yes" },
|
|
1748
|
-
{ value: "no", label: "No" }
|
|
1749
|
-
],
|
|
1750
|
-
initialValue: input.initialValue === false ? "no" : "yes"
|
|
1751
|
-
});
|
|
1752
|
-
return answer === null ? null : answer === "yes";
|
|
1753
|
-
}
|
|
1754
|
-
var isTty = () => Boolean(process.stdout.isTTY), SELECT_THEME;
|
|
1755
|
-
var init_drone_ui = __esm(() => {
|
|
1756
|
-
init_theme();
|
|
1757
|
-
init__spinner();
|
|
1758
|
-
SELECT_THEME = {
|
|
1759
|
-
selectedPrefix: (text) => accent(text),
|
|
1760
|
-
selectedText: (text) => bold(ink(text)),
|
|
1761
|
-
description: (text) => ink3(text),
|
|
1762
|
-
scrollInfo: (text) => ink4(text),
|
|
1763
|
-
noMatch: (text) => ink3(text)
|
|
1764
|
-
};
|
|
1765
|
-
});
|
|
1766
|
-
|
|
1767
|
-
// packages/cli/src/commands/init.ts
|
|
1768
|
-
var exports_init = {};
|
|
1769
|
-
__export(exports_init, {
|
|
1770
|
-
runInteractiveControlPlaneInit: () => runInteractiveControlPlaneInit,
|
|
1771
|
-
runDemoInit: () => runDemoInit,
|
|
1772
|
-
executeInit: () => executeInit,
|
|
1773
|
-
buildRigInitConfigSource: () => buildRigInitConfigSource,
|
|
1774
|
-
DEMO_TASKS_RELATIVE_DIR: () => DEMO_TASKS_RELATIVE_DIR,
|
|
1775
|
-
DEMO_TASKS: () => DEMO_TASKS
|
|
1776
|
-
});
|
|
1777
|
-
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1778
|
-
import { spawnSync } from "child_process";
|
|
1779
|
-
import { basename, resolve as resolve6 } from "path";
|
|
1780
|
-
import { buildRigInitConfigSource } from "@rig/core";
|
|
1781
|
-
import { listGitHubProjects as listGitHubProjectsDirect, resolveProjectStatusField as resolveProjectStatusFieldDirect } from "@rig/server";
|
|
1782
|
-
function parseRepoSlugFromRemote(remoteUrl) {
|
|
1783
|
-
const trimmed = remoteUrl.trim();
|
|
1784
|
-
const gitHubMatch = trimmed.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/i);
|
|
1785
|
-
return gitHubMatch ? `${gitHubMatch[1]}/${gitHubMatch[2]}` : null;
|
|
1786
|
-
}
|
|
1787
|
-
function detectOriginRepoSlug(projectRoot) {
|
|
1788
|
-
const result = spawnSync("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
|
|
1789
|
-
if (result.status !== 0)
|
|
1790
|
-
return null;
|
|
1791
|
-
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
1792
|
-
}
|
|
1793
|
-
function parseRepoSlug(value) {
|
|
1794
|
-
const match = value.trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
1795
|
-
if (!match)
|
|
1796
|
-
throw new CliError(`Invalid GitHub repo slug: ${value}. Expected owner/repo.`, 1, { hint: "Pass the slug as owner/repo, e.g. `rig init --repo acme/widgets`." });
|
|
1797
|
-
return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
|
|
1798
|
-
}
|
|
1799
|
-
function ensureRigPrivateDirs(projectRoot) {
|
|
1800
|
-
const rigDir = resolve6(projectRoot, ".rig");
|
|
1801
|
-
mkdirSync3(resolve6(rigDir, "state"), { recursive: true });
|
|
1802
|
-
mkdirSync3(resolve6(rigDir, "logs"), { recursive: true });
|
|
1803
|
-
mkdirSync3(resolve6(rigDir, "runs"), { recursive: true });
|
|
1804
|
-
mkdirSync3(resolve6(rigDir, "tmp"), { recursive: true });
|
|
1805
|
-
mkdirSync3(resolve6(projectRoot, "artifacts"), { recursive: true });
|
|
1806
|
-
const taskConfigPath = resolve6(rigDir, "task-config.json");
|
|
1807
|
-
if (!existsSync5(taskConfigPath))
|
|
1808
|
-
writeFileSync3(taskConfigPath, `{}
|
|
1809
|
-
`, "utf-8");
|
|
1810
|
-
}
|
|
1811
|
-
function ensureGitignoreEntries(projectRoot) {
|
|
1812
|
-
const path = resolve6(projectRoot, ".gitignore");
|
|
1813
|
-
const existing = existsSync5(path) ? readFileSync5(path, "utf8") : "";
|
|
1814
|
-
const entries = [".rig/state/", ".rig/logs/", ".rig/runs/", ".rig/tmp/"];
|
|
1815
|
-
const missing = entries.filter((entry) => !existing.split(/\r?\n/).includes(entry));
|
|
1816
|
-
if (missing.length === 0)
|
|
1817
|
-
return;
|
|
1818
|
-
const prefix = existing.length > 0 && !existing.endsWith(`
|
|
1819
|
-
`) ? `
|
|
1820
|
-
` : "";
|
|
1821
|
-
appendFileSync(path, `${prefix}${missing.join(`
|
|
1822
|
-
`)}
|
|
1823
|
-
`, "utf8");
|
|
1824
|
-
}
|
|
1825
|
-
function ensureRigConfigPackageDependencies(projectRoot) {
|
|
1826
|
-
const path = resolve6(projectRoot, "package.json");
|
|
1827
|
-
const existing = existsSync5(path) ? JSON.parse(readFileSync5(path, "utf8")) : {};
|
|
1828
|
-
const devDependencies = existing.devDependencies && typeof existing.devDependencies === "object" && !Array.isArray(existing.devDependencies) ? { ...existing.devDependencies } : {};
|
|
1829
|
-
for (const [name, spec] of Object.entries(RIG_CONFIG_DEV_DEPENDENCIES)) {
|
|
1830
|
-
devDependencies[name] = spec;
|
|
1831
|
-
}
|
|
1832
|
-
const next = {
|
|
1833
|
-
...existsSync5(path) ? existing : { name: "rig-project", private: true },
|
|
1834
|
-
devDependencies
|
|
1835
|
-
};
|
|
1836
|
-
writeFileSync3(path, `${JSON.stringify(next, null, 2)}
|
|
1837
|
-
`, "utf8");
|
|
1838
|
-
}
|
|
1839
|
-
function applyGitHubProjectConfig(source, options) {
|
|
1840
|
-
if (!options.githubProject || options.githubProject === "off")
|
|
1841
|
-
return source;
|
|
1842
|
-
const projectId = JSON.stringify(options.githubProject);
|
|
1843
|
-
const statusFieldId = JSON.stringify(options.githubProjectStatusField ?? "Status");
|
|
1844
|
-
const statuses = options.githubProjectStatuses && Object.keys(options.githubProjectStatuses).length > 0 ? `
|
|
1845
|
-
statuses: ${JSON.stringify(options.githubProjectStatuses, null, 8).replace(/\n/g, `
|
|
1846
|
-
`)},` : "";
|
|
1847
|
-
return source.replace(` projects: { enabled: false },`, [
|
|
1848
|
-
` projects: {`,
|
|
1849
|
-
` enabled: true,`,
|
|
1850
|
-
` projectId: ${projectId},`,
|
|
1851
|
-
` statusFieldId: ${statusFieldId},${statuses}`,
|
|
1852
|
-
` },`
|
|
1853
|
-
].join(`
|
|
1854
|
-
`));
|
|
1855
|
-
}
|
|
1856
|
-
function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
1857
|
-
if (serverKind === "local")
|
|
1858
|
-
return { kind: "local", path: projectRoot };
|
|
1859
|
-
const selected = strategy ?? { kind: "managed-clone" };
|
|
1860
|
-
switch (selected.kind) {
|
|
1861
|
-
case "managed-clone":
|
|
1862
|
-
return { kind: "managed-clone" };
|
|
1863
|
-
case "current-ref":
|
|
1864
|
-
return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
|
|
1865
|
-
case "uploaded-snapshot":
|
|
1866
|
-
return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
|
|
1867
|
-
case "existing-path":
|
|
1868
|
-
return { kind: "existing-path", path: selected.path };
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
function detectGhLogin() {
|
|
1872
|
-
const result = spawnSync("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
1873
|
-
return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : null;
|
|
1874
|
-
}
|
|
1875
|
-
function readGhAuthToken() {
|
|
1876
|
-
const result = spawnSync("gh", ["auth", "token"], { encoding: "utf8" });
|
|
1877
|
-
if (result.status !== 0 || !result.stdout.trim()) {
|
|
1878
|
-
throw new CliError(result.stderr.trim() || "Could not read GitHub token from `gh auth token`.", result.status || 1, { hint: "Sign in with `gh auth login`, or pass a token directly: `rig init --github-auth token --github-token <token>`." });
|
|
1879
|
-
}
|
|
1880
|
-
return result.stdout.trim();
|
|
1881
|
-
}
|
|
1882
|
-
function refreshGhProjectScopesAndReadToken() {
|
|
1883
|
-
const result = spawnSync("gh", ["auth", "refresh", "--scopes", "read:project"], {
|
|
1884
|
-
encoding: "utf8",
|
|
1885
|
-
stdio: ["inherit", "pipe", "pipe"]
|
|
1886
|
-
});
|
|
1887
|
-
if (result.status !== 0)
|
|
1888
|
-
return null;
|
|
1889
|
-
try {
|
|
1890
|
-
return readGhAuthToken();
|
|
1891
|
-
} catch {
|
|
1892
|
-
return null;
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
async function loadClackPrompts() {
|
|
1896
|
-
const drone = await Promise.resolve().then(() => (init_drone_ui(), exports_drone_ui));
|
|
1897
|
-
return {
|
|
1898
|
-
intro: (message) => drone.droneIntro(message),
|
|
1899
|
-
outro: (message) => drone.droneOutro(message),
|
|
1900
|
-
cancel: (message) => drone.droneCancel(message),
|
|
1901
|
-
isCancel: (value) => value === DRONE_CANCEL,
|
|
1902
|
-
text: async (options) => {
|
|
1903
|
-
const value = await drone.droneText({
|
|
1904
|
-
message: options.message,
|
|
1905
|
-
...options.placeholder ? { placeholder: options.placeholder } : {},
|
|
1906
|
-
...(options.initialValue ?? options.defaultValue)?.trim() ? { initialValue: (options.initialValue ?? options.defaultValue).trim() } : {}
|
|
1907
|
-
});
|
|
1908
|
-
return value === null ? DRONE_CANCEL : value;
|
|
1909
|
-
},
|
|
1910
|
-
select: async (options) => {
|
|
1911
|
-
const value = await drone.droneSelect({
|
|
1912
|
-
message: options.message,
|
|
1913
|
-
options: options.options,
|
|
1914
|
-
...options.initialValue ? { initialValue: options.initialValue } : {}
|
|
1915
|
-
});
|
|
1916
|
-
return value === null ? DRONE_CANCEL : value;
|
|
1917
|
-
},
|
|
1918
|
-
confirm: async (options) => {
|
|
1919
|
-
const value = await drone.droneConfirm(options);
|
|
1920
|
-
return value === null ? DRONE_CANCEL : value;
|
|
1921
|
-
}
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
|
-
async function promptRequiredText(prompts, options) {
|
|
1925
|
-
const value = await prompts.text(options);
|
|
1926
|
-
if (prompts.isCancel(value))
|
|
1927
|
-
throw new CliError("Init cancelled.", 1);
|
|
1928
|
-
const text = String(value ?? "").trim();
|
|
1929
|
-
if (!text)
|
|
1930
|
-
throw new CliError(`${options.message} is required.`, 1);
|
|
1931
|
-
return text;
|
|
1932
|
-
}
|
|
1933
|
-
async function promptOptionalText(prompts, options) {
|
|
1934
|
-
const value = await prompts.text(options);
|
|
1935
|
-
if (prompts.isCancel(value))
|
|
1936
|
-
throw new CliError("Init cancelled.", 1);
|
|
1937
|
-
return String(value ?? "").trim();
|
|
1938
|
-
}
|
|
1939
|
-
async function promptSelect(prompts, options) {
|
|
1940
|
-
const value = await prompts.select(options);
|
|
1941
|
-
if (prompts.isCancel(value))
|
|
1942
|
-
throw new CliError("Init cancelled.", 1);
|
|
1943
|
-
return String(value);
|
|
1944
|
-
}
|
|
1945
|
-
function repoOwnerFromSlug(repoSlug) {
|
|
1946
|
-
return repoSlug.trim().match(/^([^/]+)\/[^/]+$/)?.[1] ?? null;
|
|
1947
|
-
}
|
|
1948
|
-
function recordArray(value, key) {
|
|
1949
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1950
|
-
return [];
|
|
1951
|
-
const raw = value[key];
|
|
1952
|
-
return Array.isArray(raw) ? raw.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
1953
|
-
}
|
|
1954
|
-
async function listGitHubProjectsForInit(context, owner, token) {
|
|
1955
|
-
if (token?.trim()) {
|
|
1956
|
-
try {
|
|
1957
|
-
return { ok: true, projects: await listGitHubProjectsDirect({ owner, token: token.trim() }) };
|
|
1958
|
-
} catch (directError) {
|
|
1959
|
-
const serverPayload = await listGitHubProjectsViaServer(context, owner).catch(() => null);
|
|
1960
|
-
if (recordArray(serverPayload, "projects").length > 0)
|
|
1961
|
-
return serverPayload;
|
|
1962
|
-
return { ok: false, error: directError instanceof Error ? directError.message : String(directError), projects: [] };
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
return listGitHubProjectsViaServer(context, owner);
|
|
1966
|
-
}
|
|
1967
|
-
async function getGitHubProjectStatusFieldForInit(context, projectId, token) {
|
|
1968
|
-
if (token?.trim()) {
|
|
1969
|
-
try {
|
|
1970
|
-
return { ok: true, field: await resolveProjectStatusFieldDirect({ projectId, token: token.trim() }) };
|
|
1971
|
-
} catch (directError) {
|
|
1972
|
-
const serverPayload = await getGitHubProjectStatusFieldViaServer(context, projectId).catch(() => null);
|
|
1973
|
-
if (serverPayload && typeof serverPayload === "object" && !Array.isArray(serverPayload) && "field" in serverPayload) {
|
|
1974
|
-
return serverPayload;
|
|
1975
|
-
}
|
|
1976
|
-
return { ok: false, error: directError instanceof Error ? directError.message : String(directError) };
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
return getGitHubProjectStatusFieldViaServer(context, projectId);
|
|
1980
|
-
}
|
|
1981
|
-
async function promptManualProjectStatusMapping(prompts) {
|
|
1982
|
-
const statuses = {};
|
|
1983
|
-
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
1984
|
-
const defaultLabel = DEFAULT_PROJECT_STATUS_OPTIONS[key] ?? label;
|
|
1985
|
-
const value = await promptOptionalText(prompts, {
|
|
1986
|
-
message: `Project status option id/name for ${label} (blank for ${defaultLabel})`,
|
|
1987
|
-
placeholder: defaultLabel
|
|
1988
|
-
});
|
|
1989
|
-
statuses[key] = value || defaultLabel;
|
|
1990
|
-
}
|
|
1991
|
-
return statuses;
|
|
1992
|
-
}
|
|
1993
|
-
function projectScopeError(value) {
|
|
1994
|
-
const text = typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
1995
|
-
return /INSUFFICIENT_SCOPES|read:project|required scopes/i.test(text);
|
|
1996
|
-
}
|
|
1997
|
-
function optionName(option) {
|
|
1998
|
-
return String(option.name ?? option.label ?? option.id ?? "").trim();
|
|
1999
|
-
}
|
|
2000
|
-
function autoProjectStatusValue(options, key, label) {
|
|
2001
|
-
const candidates = [DEFAULT_PROJECT_STATUS_OPTIONS[key], label].filter((value) => Boolean(value)).map((value) => value.trim().toLowerCase());
|
|
2002
|
-
const match = options.find((option) => candidates.includes(optionName(option).toLowerCase()));
|
|
2003
|
-
if (!match)
|
|
2004
|
-
return null;
|
|
2005
|
-
return String(match.id ?? match.name);
|
|
2006
|
-
}
|
|
2007
|
-
async function promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, refreshProjectToken) {
|
|
2008
|
-
const projectChoice = await promptSelect(prompts, {
|
|
2009
|
-
message: "GitHub Projects status sync",
|
|
2010
|
-
initialValue: "select",
|
|
2011
|
-
options: [
|
|
2012
|
-
{ value: "select", label: "Select accessible ProjectV2" },
|
|
2013
|
-
{ value: "off", label: "Off" },
|
|
2014
|
-
{ value: "manual", label: "Enter ProjectV2 ids manually" }
|
|
2015
|
-
]
|
|
2016
|
-
});
|
|
2017
|
-
if (projectChoice === "off")
|
|
2018
|
-
return { githubProject: "off" };
|
|
2019
|
-
if (projectChoice === "manual") {
|
|
2020
|
-
return {
|
|
2021
|
-
githubProject: await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }),
|
|
2022
|
-
githubProjectStatusField: await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" }),
|
|
2023
|
-
githubProjectStatuses: await promptManualProjectStatusMapping(prompts)
|
|
2024
|
-
};
|
|
2025
|
-
}
|
|
2026
|
-
const owner = repoOwnerFromSlug(repoSlug);
|
|
2027
|
-
if (!owner)
|
|
2028
|
-
throw new CliError(`Cannot derive GitHub owner from repo slug ${repoSlug}.`, 1, { hint: "Pass the slug as owner/repo, e.g. `rig init --repo acme/widgets`." });
|
|
2029
|
-
let activeToken = githubToken?.trim() || null;
|
|
2030
|
-
let projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
2031
|
-
let projects = recordArray(projectsPayload, "projects");
|
|
2032
|
-
if (projects.length === 0 && projectScopeError(projectsPayload.error) && refreshProjectToken) {
|
|
2033
|
-
prompts.outro?.("GitHub token is missing read:project; refreshing gh auth scopes and retrying Projects.");
|
|
2034
|
-
const refreshedToken = refreshProjectToken();
|
|
2035
|
-
if (refreshedToken) {
|
|
2036
|
-
activeToken = refreshedToken;
|
|
2037
|
-
projectsPayload = await listGitHubProjectsForInit(context, owner, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error), projects: [] }));
|
|
2038
|
-
projects = recordArray(projectsPayload, "projects");
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
if (projects.length === 0) {
|
|
2042
|
-
const error = typeof projectsPayload.error === "string" ? ` (${String(projectsPayload.error).replace(/\s+/g, " ").slice(0, 240)})` : "";
|
|
2043
|
-
prompts.outro?.(`No accessible GitHub Projects were returned${error}; continuing with GitHub Projects status sync off.`);
|
|
2044
|
-
return { githubProject: "off", ...activeToken ? { githubToken: activeToken } : {} };
|
|
2045
|
-
}
|
|
2046
|
-
const selectedProjectId = await promptSelect(prompts, {
|
|
2047
|
-
message: "GitHub ProjectV2 project",
|
|
2048
|
-
options: [
|
|
2049
|
-
...projects.map((project) => ({
|
|
2050
|
-
value: String(project.id),
|
|
2051
|
-
label: `${String(project.title ?? "Untitled project")} (#${String(project.number ?? "?")})`,
|
|
2052
|
-
hint: typeof project.url === "string" ? project.url : undefined
|
|
2053
|
-
})),
|
|
2054
|
-
{ value: "manual", label: "Enter ProjectV2 id manually" }
|
|
2055
|
-
]
|
|
2056
|
-
});
|
|
2057
|
-
const projectId = selectedProjectId === "manual" ? await promptRequiredText(prompts, { message: "GitHub ProjectV2 id", placeholder: "PVT_..." }) : selectedProjectId;
|
|
2058
|
-
const fieldPayload = await getGitHubProjectStatusFieldForInit(context, projectId, activeToken).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
2059
|
-
const fieldPayloadRecord = fieldPayload && typeof fieldPayload === "object" && !Array.isArray(fieldPayload) ? fieldPayload : {};
|
|
2060
|
-
const rawField = fieldPayloadRecord.field;
|
|
2061
|
-
const field = rawField && typeof rawField === "object" && !Array.isArray(rawField) ? rawField : null;
|
|
2062
|
-
const fieldId = typeof field?.id === "string" && field.id.trim() ? field.id : await promptRequiredText(prompts, { message: "Project Status field id", placeholder: "field_status" });
|
|
2063
|
-
const options = Array.isArray(field?.options) ? field.options.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
2064
|
-
if (options.length === 0) {
|
|
2065
|
-
return {
|
|
2066
|
-
githubProject: projectId,
|
|
2067
|
-
githubProjectStatusField: fieldId,
|
|
2068
|
-
githubProjectStatuses: await promptManualProjectStatusMapping(prompts),
|
|
2069
|
-
...activeToken ? { githubToken: activeToken } : {}
|
|
2070
|
-
};
|
|
2071
|
-
}
|
|
2072
|
-
const statuses = {};
|
|
2073
|
-
for (const [key, label] of Object.entries(PROJECT_STATUS_PROMPTS)) {
|
|
2074
|
-
const auto = autoProjectStatusValue(options, key, label);
|
|
2075
|
-
statuses[key] = auto ?? await promptSelect(prompts, {
|
|
2076
|
-
message: `Project status option for ${label}`,
|
|
2077
|
-
options: options.map((option) => ({ value: String(option.id ?? option.name), label: optionName(option) }))
|
|
2078
|
-
});
|
|
2079
|
-
}
|
|
2080
|
-
return {
|
|
2081
|
-
githubProject: projectId,
|
|
2082
|
-
githubProjectStatusField: fieldId,
|
|
2083
|
-
githubProjectStatuses: Object.keys(statuses).length > 0 ? statuses : undefined,
|
|
2084
|
-
...activeToken ? { githubToken: activeToken } : {}
|
|
2085
|
-
};
|
|
2086
|
-
}
|
|
2087
|
-
function sleep2(ms) {
|
|
2088
|
-
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
2089
|
-
}
|
|
2090
|
-
function positiveIntFromEnv(name, fallback) {
|
|
2091
|
-
const value = Number.parseInt(process.env[name] ?? "", 10);
|
|
2092
|
-
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
2093
|
-
}
|
|
2094
|
-
function apiSessionTokenFrom(payload) {
|
|
2095
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
2096
|
-
return null;
|
|
2097
|
-
const token = payload.apiSessionToken;
|
|
2098
|
-
return typeof token === "string" && token.trim() ? token.trim() : null;
|
|
2099
|
-
}
|
|
2100
|
-
function cleanPayloadString(value) {
|
|
2101
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
2102
|
-
}
|
|
2103
|
-
function remoteGitHubAuthMetadata(payload) {
|
|
2104
|
-
if (!payload)
|
|
2105
|
-
return {};
|
|
2106
|
-
const userNamespace = payload.userNamespace && typeof payload.userNamespace === "object" && !Array.isArray(payload.userNamespace) ? payload.userNamespace : null;
|
|
2107
|
-
return {
|
|
2108
|
-
...cleanPayloadString(payload.login) ? { login: cleanPayloadString(payload.login) } : {},
|
|
2109
|
-
...cleanPayloadString(payload.userId) ? { userId: cleanPayloadString(payload.userId) } : {},
|
|
2110
|
-
...cleanPayloadString(userNamespace?.key) ? { userNamespaceKey: cleanPayloadString(userNamespace?.key) } : {},
|
|
2111
|
-
...cleanPayloadString(userNamespace?.root) ? { userNamespaceRoot: cleanPayloadString(userNamespace?.root) } : {},
|
|
2112
|
-
...cleanPayloadString(userNamespace?.checkoutBaseDir) ? { checkoutBaseDir: cleanPayloadString(userNamespace?.checkoutBaseDir) } : {},
|
|
2113
|
-
...cleanPayloadString(userNamespace?.snapshotBaseDir) ? { snapshotBaseDir: cleanPayloadString(userNamespace?.snapshotBaseDir) } : {}
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
2117
|
-
writeFileSync3(resolve6(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
2118
|
-
authenticated: true,
|
|
2119
|
-
source: input.source,
|
|
2120
|
-
storedOnServer: true,
|
|
2121
|
-
selectedRepo: input.selectedRepo,
|
|
2122
|
-
...remoteGitHubAuthMetadata(input.authPayload ?? null),
|
|
2123
|
-
...input.apiSessionToken ? { apiSessionToken: input.apiSessionToken } : {},
|
|
2124
|
-
updatedAt: new Date().toISOString()
|
|
2125
|
-
}, null, 2)}
|
|
2126
|
-
`, "utf8");
|
|
2127
|
-
}
|
|
2128
|
-
async function pollDeviceAuthUntilComplete(context, pollId, firstPayload) {
|
|
2129
|
-
if (typeof pollId !== "string" || !pollId.trim())
|
|
2130
|
-
return null;
|
|
2131
|
-
const intervalSeconds = typeof firstPayload.interval === "number" && Number.isFinite(firstPayload.interval) && firstPayload.interval > 0 ? firstPayload.interval : 5;
|
|
2132
|
-
const timeoutMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_TIMEOUT_MS", 300000);
|
|
2133
|
-
const intervalMs = positiveIntFromEnv("RIG_DEVICE_AUTH_POLL_INTERVAL_MS", Math.max(1000, intervalSeconds * 1000));
|
|
2134
|
-
const deadline = Date.now() + timeoutMs;
|
|
2135
|
-
let last = null;
|
|
2136
|
-
do {
|
|
2137
|
-
const payload = await requestServerJson(context, "/api/github/auth/device/poll", {
|
|
2138
|
-
method: "POST",
|
|
2139
|
-
headers: { "content-type": "application/json" },
|
|
2140
|
-
body: JSON.stringify({ pollId })
|
|
2141
|
-
}).catch(() => null);
|
|
2142
|
-
last = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
|
|
2143
|
-
const status = typeof last?.status === "string" ? last.status : null;
|
|
2144
|
-
if (status === "signed-in" || status === "expired" || status === "cancelled" || status === "failed") {
|
|
2145
|
-
return last;
|
|
2146
|
-
}
|
|
2147
|
-
if (timeoutMs <= 0)
|
|
2148
|
-
return last;
|
|
2149
|
-
await sleep2(intervalMs);
|
|
2150
|
-
} while (Date.now() < deadline);
|
|
2151
|
-
return last;
|
|
2152
|
-
}
|
|
2153
|
-
function runLocalFilesInit(context, options) {
|
|
2154
|
-
const projectRoot = context.projectRoot;
|
|
2155
|
-
ensureRigPrivateDirs(projectRoot);
|
|
2156
|
-
ensureGitignoreEntries(projectRoot);
|
|
2157
|
-
writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
|
|
2158
|
-
const configTsPath = resolve6(projectRoot, "rig.config.ts");
|
|
2159
|
-
const configExists = existsSync5(configTsPath) || existsSync5(resolve6(projectRoot, "rig.config.json"));
|
|
2160
|
-
if (configExists && !options.repair) {
|
|
2161
|
-
if (context.outputMode !== "json")
|
|
2162
|
-
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
|
|
2163
|
-
} else {
|
|
2164
|
-
const projectName = basename(projectRoot) || "rig-project";
|
|
2165
|
-
writeFileSync3(configTsPath, buildRigInitConfigSource({
|
|
2166
|
-
projectName,
|
|
2167
|
-
taskSource: { kind: "files", path: "tasks" },
|
|
2168
|
-
useStandardPlugin: true
|
|
2169
|
-
}), "utf-8");
|
|
2170
|
-
}
|
|
2171
|
-
ensureRigConfigPackageDependencies(projectRoot);
|
|
2172
|
-
const tasksDir = resolve6(projectRoot, "tasks");
|
|
2173
|
-
if (!existsSync5(tasksDir)) {
|
|
2174
|
-
mkdirSync3(tasksDir, { recursive: true });
|
|
2175
|
-
writeFileSync3(resolve6(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
|
|
2176
|
-
`, "utf-8");
|
|
2177
|
-
}
|
|
2178
|
-
if (context.outputMode !== "json") {
|
|
2179
|
-
console.log("Initialized a local files-source Rig project (no GitHub).");
|
|
2180
|
-
console.log(" tasks live in tasks/*.json \xB7 server: local");
|
|
2181
|
-
console.log("Next: `rig task list`, then `rig task run T-1`.");
|
|
2182
|
-
console.log("To wire GitHub later: `rig init --repair --repo owner/repo --github-auth gh`.");
|
|
2183
|
-
}
|
|
2184
|
-
return { ok: true, group: "init", command: "init", details: { mode: "local-files", projectRoot, taskSourcePath: "tasks" } };
|
|
2185
|
-
}
|
|
2186
|
-
function runDemoInit(context, options) {
|
|
2187
|
-
const projectRoot = context.projectRoot;
|
|
2188
|
-
ensureRigPrivateDirs(projectRoot);
|
|
2189
|
-
ensureGitignoreEntries(projectRoot);
|
|
2190
|
-
writeRepoConnection(projectRoot, { selected: "local", linkedAt: new Date().toISOString() });
|
|
2191
|
-
const configTsPath = resolve6(projectRoot, "rig.config.ts");
|
|
2192
|
-
const configExists = existsSync5(configTsPath) || existsSync5(resolve6(projectRoot, "rig.config.json"));
|
|
2193
|
-
let configWritten = false;
|
|
2194
|
-
if (configExists && !options.repair) {
|
|
2195
|
-
if (context.outputMode !== "json") {
|
|
2196
|
-
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
|
|
2197
|
-
}
|
|
2198
|
-
} else {
|
|
2199
|
-
writeFileSync3(configTsPath, buildRigInitConfigSource({
|
|
2200
|
-
projectName: basename(projectRoot) || "rig-demo",
|
|
2201
|
-
taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
|
|
2202
|
-
useStandardPlugin: true
|
|
2203
|
-
}), "utf-8");
|
|
2204
|
-
configWritten = true;
|
|
2205
|
-
}
|
|
2206
|
-
ensureRigConfigPackageDependencies(projectRoot);
|
|
2207
|
-
const demoTasksDir = resolve6(projectRoot, DEMO_TASKS_RELATIVE_DIR);
|
|
2208
|
-
mkdirSync3(demoTasksDir, { recursive: true });
|
|
2209
|
-
const taskIds = [];
|
|
2210
|
-
for (const task of DEMO_TASKS) {
|
|
2211
|
-
const id = String(task.id);
|
|
2212
|
-
taskIds.push(id);
|
|
2213
|
-
const taskPath = resolve6(demoTasksDir, `${id}.json`);
|
|
2214
|
-
if (!existsSync5(taskPath)) {
|
|
2215
|
-
writeFileSync3(taskPath, `${JSON.stringify(task, null, 2)}
|
|
2216
|
-
`, "utf-8");
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
if (context.outputMode !== "json") {
|
|
2220
|
-
console.log(`Demo Rig project ready (offline, no GitHub).`);
|
|
2221
|
-
console.log(` config: rig.config.ts (files task source -> ${DEMO_TASKS_RELATIVE_DIR}/)`);
|
|
2222
|
-
console.log(` tasks: ${taskIds.join(", ")}`);
|
|
2223
|
-
console.log("Next steps:");
|
|
2224
|
-
console.log(" 1. rig task list");
|
|
2225
|
-
console.log(" 2. rig task run --next");
|
|
2226
|
-
}
|
|
2227
|
-
return {
|
|
2228
|
-
ok: true,
|
|
2229
|
-
group: "init",
|
|
2230
|
-
command: "init",
|
|
2231
|
-
details: {
|
|
2232
|
-
mode: "demo",
|
|
2233
|
-
projectRoot,
|
|
2234
|
-
taskSourcePath: DEMO_TASKS_RELATIVE_DIR,
|
|
2235
|
-
demoTasksDir,
|
|
2236
|
-
tasks: taskIds,
|
|
2237
|
-
configWritten
|
|
2238
|
-
}
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
async function runControlPlaneInit(context, options) {
|
|
2242
|
-
const projectRoot = context.projectRoot;
|
|
2243
|
-
const existingRepoConnection = readRepoConnection(projectRoot);
|
|
2244
|
-
const selectedConnection = (() => {
|
|
2245
|
-
try {
|
|
2246
|
-
return resolveSelectedConnection(projectRoot);
|
|
2247
|
-
} catch {
|
|
2248
|
-
return null;
|
|
2249
|
-
}
|
|
2250
|
-
})();
|
|
2251
|
-
const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
|
|
2252
|
-
const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
|
|
2253
|
-
const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
|
|
2254
|
-
if (!detectedSlug) {
|
|
2255
|
-
const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
2256
|
-
if (serverKind === "local" && authMethod2 === "skip") {
|
|
2257
|
-
return runLocalFilesInit(context, options);
|
|
2258
|
-
}
|
|
2259
|
-
throw new CliError("Could not detect GitHub repo slug from origin. Pass --repo owner/repo \u2014 or run `rig init --yes --server local --github-auth skip` for a local files-source project without GitHub.", 1);
|
|
2260
|
-
}
|
|
2261
|
-
const repo = parseRepoSlug(detectedSlug);
|
|
2262
|
-
const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
|
|
2263
|
-
const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
|
|
2264
|
-
const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
|
|
2265
|
-
if (serverKind === "remote") {
|
|
2266
|
-
if (!remoteUrl)
|
|
2267
|
-
throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`, or first select a saved remote with `rig server use <alias>`." });
|
|
2268
|
-
upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
|
|
2269
|
-
}
|
|
2270
|
-
writeRepoConnection(projectRoot, {
|
|
2271
|
-
selected: connectionAlias,
|
|
2272
|
-
project: repo.slug,
|
|
2273
|
-
linkedAt: new Date().toISOString()
|
|
2274
|
-
});
|
|
2275
|
-
ensureRigPrivateDirs(projectRoot);
|
|
2276
|
-
ensureGitignoreEntries(projectRoot);
|
|
2277
|
-
const configTsPath = resolve6(projectRoot, "rig.config.ts");
|
|
2278
|
-
const configJsonPath = resolve6(projectRoot, "rig.config.json");
|
|
2279
|
-
const configExists = existsSync5(configTsPath) || existsSync5(configJsonPath);
|
|
2280
|
-
if (!options.privateStateOnly) {
|
|
2281
|
-
if (configExists && !options.repair) {
|
|
2282
|
-
if (context.outputMode !== "json")
|
|
2283
|
-
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
|
|
2284
|
-
} else {
|
|
2285
|
-
const source = applyGitHubProjectConfig(buildRigInitConfigSource({
|
|
2286
|
-
projectName: repo.slug,
|
|
2287
|
-
projectRepo: repo.slug,
|
|
2288
|
-
taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
|
|
2289
|
-
useStandardPlugin: true
|
|
2290
|
-
}), options);
|
|
2291
|
-
writeFileSync3(configTsPath, source, "utf-8");
|
|
2292
|
-
}
|
|
2293
|
-
ensureRigConfigPackageDependencies(projectRoot);
|
|
2294
|
-
}
|
|
2295
|
-
writeFileSync3(resolve6(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
2296
|
-
`, "utf8");
|
|
2297
|
-
const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
|
|
2298
|
-
let uploadedSnapshot = null;
|
|
2299
|
-
if (serverKind === "remote" && options.remoteCheckout?.kind === "uploaded-snapshot") {
|
|
2300
|
-
const archive = await createSnapshotUploadArchive(projectRoot);
|
|
2301
|
-
uploadedSnapshot = await uploadSnapshotArchiveViaServer(context, { repoSlug: repo.slug, archive });
|
|
2302
|
-
const uploadedCheckout = uploadedSnapshot.checkout;
|
|
2303
|
-
if (uploadedCheckout && typeof uploadedCheckout === "object" && !Array.isArray(uploadedCheckout)) {
|
|
2304
|
-
Object.assign(checkout, uploadedCheckout);
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
let githubAuth = null;
|
|
2308
|
-
let deviceAuth = null;
|
|
2309
|
-
const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
2310
|
-
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
|
|
2311
|
-
if (remoteGhTokenWarning && !options.yes) {
|
|
2312
|
-
throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
|
|
2313
|
-
}
|
|
2314
|
-
const token = authMethod === "gh" && !options.githubToken ? readGhAuthToken() : options.githubToken?.trim();
|
|
2315
|
-
if (token) {
|
|
2316
|
-
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug });
|
|
2317
|
-
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
2318
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
2319
|
-
if (serverKind === "remote") {
|
|
2320
|
-
writeRemoteGitHubAuthState(projectRoot, {
|
|
2321
|
-
source: authMethod === "gh" ? "gh" : "init-token",
|
|
2322
|
-
selectedRepo: repo.slug,
|
|
2323
|
-
apiSessionToken,
|
|
2324
|
-
authPayload: githubAuth
|
|
2325
|
-
});
|
|
2326
|
-
}
|
|
2327
|
-
} else if (authMethod === "device") {
|
|
2328
|
-
const payload = await requestServerJson(context, "/api/github/auth/device/start", {
|
|
2329
|
-
method: "POST",
|
|
2330
|
-
headers: { "content-type": "application/json" },
|
|
2331
|
-
body: JSON.stringify({ repoSlug: repo.slug })
|
|
2332
|
-
});
|
|
2333
|
-
deviceAuth = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
2334
|
-
if (context.outputMode !== "json") {
|
|
2335
|
-
const verificationUri = String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server");
|
|
2336
|
-
const userCode = String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code");
|
|
2337
|
-
console.log(`GitHub device flow: open ${verificationUri} and enter ${userCode}. Waiting for authorization...`);
|
|
2338
|
-
}
|
|
2339
|
-
const completed = await pollDeviceAuthUntilComplete(context, deviceAuth.pollId, deviceAuth);
|
|
2340
|
-
if (completed) {
|
|
2341
|
-
const apiSessionToken = apiSessionTokenFrom(completed);
|
|
2342
|
-
if (apiSessionToken) {
|
|
2343
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken, projectRoot);
|
|
2344
|
-
if (serverKind === "remote") {
|
|
2345
|
-
writeRemoteGitHubAuthState(projectRoot, { source: "device", selectedRepo: repo.slug, apiSessionToken, authPayload: completed });
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
deviceAuth = { ...deviceAuth, poll: completed, completed: completed.status === "signed-in" };
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
let remoteCheckoutPreparation = null;
|
|
2352
|
-
if (serverKind === "remote" && options.remoteCheckout?.kind !== "uploaded-snapshot") {
|
|
2353
|
-
remoteCheckoutPreparation = await prepareRemoteCheckoutViaServer(context, {
|
|
2354
|
-
repoSlug: repo.slug,
|
|
2355
|
-
checkout,
|
|
2356
|
-
repoUrl: `https://github.com/${repo.slug}.git`
|
|
2357
|
-
});
|
|
2358
|
-
const preparedCheckout = remoteCheckoutPreparation.checkout;
|
|
2359
|
-
if (preparedCheckout && typeof preparedCheckout === "object" && !Array.isArray(preparedCheckout)) {
|
|
2360
|
-
Object.assign(checkout, preparedCheckout);
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
const checkoutPath = typeof checkout.path === "string" ? checkout.path : null;
|
|
2364
|
-
if (serverKind === "remote" && checkoutPath && token) {
|
|
2365
|
-
githubAuth = await postGitHubTokenViaServer(context, token, { selectedRepo: repo.slug, projectRoot: checkoutPath });
|
|
2366
|
-
const apiSessionToken = apiSessionTokenFrom(githubAuth);
|
|
2367
|
-
setGitHubBearerTokenForCurrentProcess(apiSessionToken ?? token, projectRoot);
|
|
2368
|
-
writeRemoteGitHubAuthState(projectRoot, { source: authMethod === "gh" ? "gh" : "init-token", selectedRepo: repo.slug, apiSessionToken, authPayload: githubAuth });
|
|
2369
|
-
}
|
|
2370
|
-
const registered = await registerProjectViaServer(context, {
|
|
2371
|
-
repoSlug: repo.slug,
|
|
2372
|
-
checkout
|
|
2373
|
-
});
|
|
2374
|
-
const serverRootSwitch = serverKind === "remote" && checkoutPath ? await switchServerProjectRootViaServer(context, checkoutPath) : null;
|
|
2375
|
-
const activeProjectRegistration = serverRootSwitch ? await registerProjectViaServer(context, { repoSlug: repo.slug, checkout }) : null;
|
|
2376
|
-
const labelSetup = await ensureTaskLabelsViaServer(context).catch((error) => ({
|
|
2377
|
-
ok: false,
|
|
2378
|
-
ready: false,
|
|
2379
|
-
labelsReady: false,
|
|
2380
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2381
|
-
}));
|
|
2382
|
-
const pi = serverKind === "remote" ? await ensureRemotePiRigInstalled({ requestJson: (pathname, init) => requestServerJson(context, pathname, init) }).catch((error) => ({
|
|
2383
|
-
remote: true,
|
|
2384
|
-
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
2385
|
-
piRig: { ok: false, label: "pi-rig global extension", hint: "Remote server did not complete pi-rig installation." },
|
|
2386
|
-
extensionPath: "remote:~/.pi/agent/extensions/pi-rig"
|
|
2387
|
-
})) : await ensurePiRigInstalled({ projectRoot, homeDir: process.env.RIG_PI_HOME_DIR }).catch((error) => ({
|
|
2388
|
-
pi: { ok: false, label: "pi", hint: error instanceof Error ? error.message : String(error) },
|
|
2389
|
-
piRig: { ok: false, label: "pi-rig global extension", hint: "Local pi-rig installation failed." },
|
|
2390
|
-
extensionPath: null,
|
|
2391
|
-
installedPath: null
|
|
2392
|
-
}));
|
|
2393
|
-
const doctor = await runRigDoctorChecks({ projectRoot }).then((checks) => ({
|
|
2394
|
-
ok: countDoctorFailures(checks) === 0,
|
|
2395
|
-
failures: countDoctorFailures(checks),
|
|
2396
|
-
checks
|
|
2397
|
-
}));
|
|
2398
|
-
const details = {
|
|
2399
|
-
repoSlug: repo.slug,
|
|
2400
|
-
server: serverKind,
|
|
2401
|
-
connection: connectionAlias,
|
|
2402
|
-
githubProject: options.githubProject ?? "off",
|
|
2403
|
-
checkout,
|
|
2404
|
-
remoteCheckoutPreparation,
|
|
2405
|
-
uploadedSnapshot,
|
|
2406
|
-
projectRegistration: registered,
|
|
2407
|
-
activeProjectRegistration,
|
|
2408
|
-
serverRootSwitch,
|
|
2409
|
-
githubAuth,
|
|
2410
|
-
deviceAuth,
|
|
2411
|
-
githubAuthWarning: remoteGhTokenWarning,
|
|
2412
|
-
labelSetup,
|
|
2413
|
-
pi,
|
|
2414
|
-
doctor
|
|
2415
|
-
};
|
|
2416
|
-
if (context.outputMode === "json")
|
|
2417
|
-
console.log(JSON.stringify(details, null, 2));
|
|
2418
|
-
else
|
|
2419
|
-
console.log(`Initialized Rig control-plane project ${repo.slug}. Next: rig doctor && rig task list`);
|
|
2420
|
-
return { ok: true, group: "init", command: "init", details };
|
|
2421
|
-
}
|
|
2422
|
-
function parseInitOptions(args) {
|
|
2423
|
-
let rest = [...args];
|
|
2424
|
-
const demo = takeFlag(rest, "--demo");
|
|
2425
|
-
rest = demo.rest;
|
|
2426
|
-
const yes = takeFlag(rest, "--yes");
|
|
2427
|
-
rest = yes.rest;
|
|
2428
|
-
const repair = takeFlag(rest, "--repair");
|
|
2429
|
-
rest = repair.rest;
|
|
2430
|
-
const privateStateOnly = takeFlag(rest, "--private-state-only");
|
|
2431
|
-
rest = privateStateOnly.rest;
|
|
2432
|
-
const server = takeOption(rest, "--server");
|
|
2433
|
-
rest = server.rest;
|
|
2434
|
-
const remoteUrl = takeOption(rest, "--remote-url");
|
|
2435
|
-
rest = remoteUrl.rest;
|
|
2436
|
-
const connectionAlias = takeOption(rest, "--connection");
|
|
2437
|
-
rest = connectionAlias.rest;
|
|
2438
|
-
const repoSlug = takeOption(rest, "--repo");
|
|
2439
|
-
rest = repoSlug.rest;
|
|
2440
|
-
const githubToken = takeOption(rest, "--github-token");
|
|
2441
|
-
rest = githubToken.rest;
|
|
2442
|
-
const githubProject = takeOption(rest, "--github-project");
|
|
2443
|
-
rest = githubProject.rest;
|
|
2444
|
-
const githubProjectStatusField = takeOption(rest, "--github-project-status-field");
|
|
2445
|
-
rest = githubProjectStatusField.rest;
|
|
2446
|
-
const githubAuth = takeOption(rest, "--github-auth");
|
|
2447
|
-
rest = githubAuth.rest;
|
|
2448
|
-
const remoteCheckout = takeOption(rest, "--remote-checkout");
|
|
2449
|
-
rest = remoteCheckout.rest;
|
|
2450
|
-
const existingPath = takeOption(rest, "--existing-path");
|
|
2451
|
-
rest = existingPath.rest;
|
|
2452
|
-
const ref = takeOption(rest, "--ref");
|
|
2453
|
-
rest = ref.rest;
|
|
2454
|
-
const options = {
|
|
2455
|
-
demo: demo.value,
|
|
2456
|
-
yes: yes.value,
|
|
2457
|
-
repair: repair.value,
|
|
2458
|
-
privateStateOnly: privateStateOnly.value,
|
|
2459
|
-
server: server.value === "remote" ? "remote" : server.value === "local" ? "local" : undefined,
|
|
2460
|
-
remoteUrl: remoteUrl.value,
|
|
2461
|
-
connectionAlias: connectionAlias.value,
|
|
2462
|
-
repoSlug: repoSlug.value,
|
|
2463
|
-
githubToken: githubToken.value,
|
|
2464
|
-
githubAuthMethod: githubAuth.value,
|
|
2465
|
-
githubProject: githubProject.value,
|
|
2466
|
-
githubProjectStatusField: githubProjectStatusField.value
|
|
2467
|
-
};
|
|
2468
|
-
if (server.value && options.server === undefined) {
|
|
2469
|
-
throw new CliError("--server must be local or remote.", 1);
|
|
2470
|
-
}
|
|
2471
|
-
if (githubAuth.value && !["gh", "token", "device", "skip"].includes(githubAuth.value)) {
|
|
2472
|
-
throw new CliError("--github-auth must be gh, token, device, or skip.", 1);
|
|
2473
|
-
}
|
|
2474
|
-
if (remoteCheckout.value) {
|
|
2475
|
-
if (remoteCheckout.value === "managed-clone")
|
|
2476
|
-
options.remoteCheckout = { kind: "managed-clone" };
|
|
2477
|
-
else if (remoteCheckout.value === "current-ref")
|
|
2478
|
-
options.remoteCheckout = { kind: "current-ref", ref: ref.value };
|
|
2479
|
-
else if (remoteCheckout.value === "uploaded-snapshot")
|
|
2480
|
-
options.remoteCheckout = { kind: "uploaded-snapshot" };
|
|
2481
|
-
else if (remoteCheckout.value === "existing-path") {
|
|
2482
|
-
if (!existingPath.value)
|
|
2483
|
-
throw new CliError("--remote-checkout existing-path requires --existing-path <path>.", 1);
|
|
2484
|
-
options.remoteCheckout = { kind: "existing-path", path: existingPath.value };
|
|
2485
|
-
} else {
|
|
2486
|
-
throw new CliError("--remote-checkout must be managed-clone, current-ref, uploaded-snapshot, or existing-path.", 1);
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
return { options, rest };
|
|
2490
|
-
}
|
|
2491
|
-
async function runInteractiveControlPlaneInit(context, prompts) {
|
|
2492
|
-
prompts.intro?.("Initialize a Rig control-plane project");
|
|
2493
|
-
const projectRoot = context.projectRoot;
|
|
2494
|
-
const existingConfig = existsSync5(resolve6(projectRoot, "rig.config.ts")) || existsSync5(resolve6(projectRoot, "rig.config.json"));
|
|
2495
|
-
let repair = false;
|
|
2496
|
-
let privateStateOnly = false;
|
|
2497
|
-
if (existingConfig) {
|
|
2498
|
-
const action = await promptSelect(prompts, {
|
|
2499
|
-
message: "rig.config already exists. What should rig init do?",
|
|
2500
|
-
options: [
|
|
2501
|
-
{ value: "repair", label: "Verify/repair generated config" },
|
|
2502
|
-
{ value: "reconfigure", label: "Reconfigure project and rewrite config" },
|
|
2503
|
-
{ value: "private-state-only", label: "Leave config unchanged; update private connection/auth state only" },
|
|
2504
|
-
{ value: "cancel", label: "Cancel" }
|
|
2505
|
-
]
|
|
2506
|
-
});
|
|
2507
|
-
if (action === "cancel") {
|
|
2508
|
-
prompts.cancel?.("Init cancelled.");
|
|
2509
|
-
return { ok: false, group: "init", command: "init", details: { cancelled: true } };
|
|
2510
|
-
}
|
|
2511
|
-
repair = action === "repair" || action === "reconfigure";
|
|
2512
|
-
privateStateOnly = action === "private-state-only";
|
|
2513
|
-
}
|
|
2514
|
-
const detectedRepo = detectOriginRepoSlug(projectRoot) ?? undefined;
|
|
2515
|
-
const repoSlug = await promptRequiredText(prompts, {
|
|
2516
|
-
message: "GitHub repo slug",
|
|
2517
|
-
placeholder: "owner/repo",
|
|
2518
|
-
defaultValue: detectedRepo
|
|
2519
|
-
});
|
|
2520
|
-
const serverChoice = await promptSelect(prompts, {
|
|
2521
|
-
message: "Rig server",
|
|
2522
|
-
initialValue: "remote",
|
|
2523
|
-
options: [
|
|
2524
|
-
{ value: "remote", label: "Remote server", hint: "connect to an HTTPS Rig server" },
|
|
2525
|
-
{ value: "local", label: "Local server", hint: "run on this machine" }
|
|
2526
|
-
]
|
|
2527
|
-
});
|
|
2528
|
-
const remoteUrl = serverChoice === "remote" ? await promptRequiredText(prompts, { message: "Remote Rig server URL", placeholder: DEFAULT_REMOTE_RIG_URL, initialValue: DEFAULT_REMOTE_RIG_URL }) : undefined;
|
|
2529
|
-
let remoteCheckout;
|
|
2530
|
-
if (serverChoice === "remote") {
|
|
2531
|
-
const checkout = await promptSelect(prompts, {
|
|
2532
|
-
message: "Remote checkout strategy",
|
|
2533
|
-
options: [
|
|
2534
|
-
{ value: "managed-clone", label: "Server-managed clone (recommended)" },
|
|
2535
|
-
{ value: "current-ref", label: "Clone current branch/ref" },
|
|
2536
|
-
{ value: "uploaded-snapshot", label: "Upload current working-tree snapshot" },
|
|
2537
|
-
{ value: "existing-path", label: "Use existing server path" }
|
|
2538
|
-
]
|
|
2539
|
-
});
|
|
2540
|
-
if (checkout === "existing-path") {
|
|
2541
|
-
remoteCheckout = { kind: "existing-path", path: await promptRequiredText(prompts, { message: "Existing server checkout path", placeholder: "/srv/rig/checkouts/repo" }) };
|
|
2542
|
-
} else if (checkout === "current-ref") {
|
|
2543
|
-
remoteCheckout = { kind: "current-ref", ref: await promptOptionalText(prompts, { message: "Branch/ref to clone (blank for current HEAD)", placeholder: "main" }) || undefined };
|
|
2544
|
-
} else if (checkout === "uploaded-snapshot") {
|
|
2545
|
-
remoteCheckout = { kind: "uploaded-snapshot" };
|
|
2546
|
-
} else {
|
|
2547
|
-
remoteCheckout = { kind: "managed-clone" };
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
const detectedGhLogin = detectGhLogin();
|
|
2551
|
-
const authMethod = await promptSelect(prompts, {
|
|
2552
|
-
message: `GitHub auth method${detectedGhLogin ? ` (detected gh login: ${detectedGhLogin})` : ""}`,
|
|
2553
|
-
options: [
|
|
2554
|
-
{ value: "gh", label: "Import token from gh auth token", hint: serverChoice === "local" ? "recommended for local" : "sends this machine's token to the remote server" },
|
|
2555
|
-
{ value: "device", label: "Start server GitHub device flow", hint: serverChoice === "remote" ? "recommended for remote" : undefined },
|
|
2556
|
-
{ value: "token", label: "Paste token" },
|
|
2557
|
-
{ value: "skip", label: "Skip for now" }
|
|
2558
|
-
]
|
|
2559
|
-
});
|
|
2560
|
-
let remoteGhTokenConfirmed = false;
|
|
2561
|
-
if (serverChoice === "remote" && authMethod === "gh") {
|
|
2562
|
-
if (!prompts.confirm)
|
|
2563
|
-
throw new CliError("Remote gh-token import requires explicit confirmation.", 1);
|
|
2564
|
-
const confirmed = await prompts.confirm({
|
|
2565
|
-
message: `This sends a GitHub token from this machine to ${remoteUrl}. Continue?`,
|
|
2566
|
-
initialValue: true
|
|
2567
|
-
});
|
|
2568
|
-
if (prompts.isCancel(confirmed) || confirmed !== true) {
|
|
2569
|
-
throw new CliError("Remote gh-token import cancelled.", 1);
|
|
2570
|
-
}
|
|
2571
|
-
remoteGhTokenConfirmed = true;
|
|
2572
|
-
}
|
|
2573
|
-
const githubToken = authMethod === "token" ? await promptRequiredText(prompts, { message: "GitHub token", placeholder: "ghp_..." }) : authMethod === "gh" ? readGhAuthToken() : undefined;
|
|
2574
|
-
const projectConfig = await promptGitHubProjectConfig(context, prompts, repoSlug, githubToken, authMethod === "gh" ? refreshGhProjectScopesAndReadToken : undefined);
|
|
2575
|
-
const effectiveGithubToken = projectConfig.githubToken ?? githubToken;
|
|
2576
|
-
const result = await runControlPlaneInit(context, {
|
|
2577
|
-
server: serverChoice,
|
|
2578
|
-
remoteUrl,
|
|
2579
|
-
repoSlug,
|
|
2580
|
-
githubToken: effectiveGithubToken,
|
|
2581
|
-
githubAuthMethod: authMethod,
|
|
2582
|
-
githubProject: projectConfig.githubProject,
|
|
2583
|
-
githubProjectStatusField: projectConfig.githubProjectStatusField,
|
|
2584
|
-
githubProjectStatuses: projectConfig.githubProjectStatuses,
|
|
2585
|
-
remoteCheckout,
|
|
2586
|
-
repair,
|
|
2587
|
-
privateStateOnly,
|
|
2588
|
-
yes: remoteGhTokenConfirmed || undefined
|
|
2589
|
-
});
|
|
2590
|
-
const details = result.details && typeof result.details === "object" && !Array.isArray(result.details) ? result.details : {};
|
|
2591
|
-
const deviceAuth = details.deviceAuth && typeof details.deviceAuth === "object" && !Array.isArray(details.deviceAuth) ? details.deviceAuth : null;
|
|
2592
|
-
const deviceMessage = deviceAuth ? ` GitHub device flow: open ${String(deviceAuth.verificationUri ?? deviceAuth.verification_uri ?? deviceAuth.verification_uri_complete ?? "the verification URL returned by the server")} and enter ${String(deviceAuth.userCode ?? deviceAuth.user_code ?? "the returned user code")}.` : "";
|
|
2593
|
-
prompts.outro?.(`Rig project initialized.${deviceMessage} Next: rig doctor && rig task list`);
|
|
2594
|
-
return result;
|
|
2595
|
-
}
|
|
2596
|
-
async function executeInit(context, args) {
|
|
2597
|
-
const parsed = parseInitOptions(args);
|
|
2598
|
-
if (parsed.options.demo) {
|
|
2599
|
-
if (parsed.rest.length > 0) {
|
|
2600
|
-
throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
|
|
2601
|
-
Usage: rig init --demo [--yes] [--repair]`, 1, { hint: "Run `rig init --demo` (optionally with --repair to rewrite an existing rig.config)." });
|
|
2602
|
-
}
|
|
2603
|
-
return runDemoInit(context, parsed.options);
|
|
2604
|
-
}
|
|
2605
|
-
if (parsed.options.yes || parsed.options.server || parsed.options.repoSlug || parsed.options.githubToken || parsed.options.privateStateOnly || parsed.options.repair || parsed.options.githubAuthMethod || parsed.options.remoteCheckout) {
|
|
2606
|
-
if (parsed.rest.length > 0)
|
|
2607
|
-
throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
|
|
2608
|
-
Usage: rig init [--demo] [--server local|remote] [--remote-url <url>] [--repo owner/repo] [--github-auth gh|token|device|skip] [--github-token <token>] [--github-project off|<project-id>] [--remote-checkout managed-clone|current-ref|uploaded-snapshot|existing-path] [--yes]`, 1);
|
|
2609
|
-
return runControlPlaneInit(context, parsed.options);
|
|
2610
|
-
}
|
|
2611
|
-
if (parsed.rest.length > 0)
|
|
2612
|
-
throw new CliError(`Unexpected arguments: ${parsed.rest.join(" ")}
|
|
2613
|
-
Usage: rig init`, 1);
|
|
2614
|
-
if (!process.stdin.isTTY) {
|
|
2615
|
-
throw new CliError("rig init is interactive and needs a terminal. For scripts, pass flags: rig init --yes --server local --github-auth skip [--repo owner/repo].", 1);
|
|
2616
|
-
}
|
|
2617
|
-
return runInteractiveControlPlaneInit(context, await loadClackPrompts());
|
|
2618
|
-
}
|
|
2619
|
-
var RIG_CONFIG_PACKAGE_DIST_TAG = "latest", DEFAULT_REMOTE_RIG_URL = "https://where.rig-does.work", RIG_CONFIG_DEV_DEPENDENCIES, DRONE_CANCEL, PROJECT_STATUS_PROMPTS, DEFAULT_PROJECT_STATUS_OPTIONS, DEMO_TASKS_RELATIVE_DIR = ".rig/demo-tasks", DEMO_TASKS;
|
|
2620
|
-
var init_init = __esm(() => {
|
|
2621
|
-
init_runner();
|
|
2622
|
-
init__connection_state();
|
|
2623
|
-
init__server_client();
|
|
2624
|
-
init__pi_install();
|
|
2625
|
-
init__snapshot_upload();
|
|
2626
|
-
init__doctor_checks();
|
|
2627
|
-
RIG_CONFIG_DEV_DEPENDENCIES = {
|
|
2628
|
-
"@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_DIST_TAG}`,
|
|
2629
|
-
"@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_DIST_TAG}`
|
|
2630
|
-
};
|
|
2631
|
-
DRONE_CANCEL = Symbol("drone-cancel");
|
|
2632
|
-
PROJECT_STATUS_PROMPTS = {
|
|
2633
|
-
running: "Running/In progress",
|
|
2634
|
-
prOpen: "PR open/review",
|
|
2635
|
-
ciFixing: "CI/review fixing",
|
|
2636
|
-
merging: "Merging",
|
|
2637
|
-
done: "Done",
|
|
2638
|
-
needsAttention: "Needs attention"
|
|
2639
|
-
};
|
|
2640
|
-
DEFAULT_PROJECT_STATUS_OPTIONS = {
|
|
2641
|
-
running: "In Progress",
|
|
2642
|
-
prOpen: "In Review",
|
|
2643
|
-
ciFixing: "In Review",
|
|
2644
|
-
merging: "Merging",
|
|
2645
|
-
done: "Done",
|
|
2646
|
-
needsAttention: "Needs Attention"
|
|
2647
|
-
};
|
|
2648
|
-
DEMO_TASKS = [
|
|
2649
|
-
{
|
|
2650
|
-
id: "demo-1",
|
|
2651
|
-
title: "Add a hello CLI script",
|
|
2652
|
-
body: [
|
|
2653
|
-
"Create `scripts/hello.ts` that prints `Hello from Rig!` plus the current date,",
|
|
2654
|
-
"and add a `hello` script entry to package.json that runs it with bun.",
|
|
2655
|
-
"Keep it dependency-free."
|
|
2656
|
-
].join(`
|
|
2657
|
-
`),
|
|
2658
|
-
status: "ready",
|
|
2659
|
-
labels: ["demo"]
|
|
2660
|
-
},
|
|
2661
|
-
{
|
|
2662
|
-
id: "demo-2",
|
|
2663
|
-
title: "Write a README section about this project",
|
|
2664
|
-
body: [
|
|
2665
|
-
"Add (or extend) README.md with a short `## What this is` section:",
|
|
2666
|
-
"two or three sentences describing the repository and how to run it.",
|
|
2667
|
-
"Plain prose, no badges."
|
|
2668
|
-
].join(`
|
|
2669
|
-
`),
|
|
2670
|
-
status: "ready",
|
|
2671
|
-
labels: ["demo"]
|
|
2672
|
-
},
|
|
2673
|
-
{
|
|
2674
|
-
id: "demo-3",
|
|
2675
|
-
title: "Add a unit test for the hello script",
|
|
2676
|
-
body: [
|
|
2677
|
-
"Add `scripts/hello.test.ts` with a bun test that imports the greeting",
|
|
2678
|
-
"helper from the hello script and asserts it contains `Hello from Rig!`.",
|
|
2679
|
-
"Refactor the script to export that helper if needed."
|
|
2680
|
-
].join(`
|
|
2681
|
-
`),
|
|
2682
|
-
status: "ready",
|
|
2683
|
-
labels: ["demo"]
|
|
2684
|
-
}
|
|
2685
|
-
];
|
|
2686
|
-
});
|
|
2687
|
-
|
|
2688
|
-
// packages/cli/src/app-opentui/adapters/init.ts
|
|
2689
|
-
import { existsSync as existsSync6 } from "fs";
|
|
2690
|
-
import { resolve as resolve7 } from "path";
|
|
2691
|
-
|
|
2692
|
-
// packages/cli/src/app-opentui/adapters/common.ts
|
|
2693
|
-
function projectRootOf(ctx) {
|
|
2694
|
-
const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
|
|
2695
|
-
if (typeof root === "string" && root.trim())
|
|
2696
|
-
return root;
|
|
2697
|
-
throw new Error("App adapter requires a projectRoot.");
|
|
2698
|
-
}
|
|
2699
|
-
async function runtimeOf(ctx) {
|
|
2700
|
-
if (ctx.rig && "runId" in ctx.rig && ctx.ensureRuntime === undefined) {
|
|
2701
|
-
return ctx.rig;
|
|
2702
|
-
}
|
|
2703
|
-
if (ctx.ensureRuntime)
|
|
2704
|
-
return ctx.ensureRuntime();
|
|
2705
|
-
throw new Error("App adapter requires ensureRuntime() before this action can run.");
|
|
2706
|
-
}
|
|
2707
|
-
function normalizeAppError(error) {
|
|
2708
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2709
|
-
const rawHint = error instanceof Error ? error.hint : undefined;
|
|
2710
|
-
return {
|
|
2711
|
-
message,
|
|
2712
|
-
...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {}
|
|
2713
|
-
};
|
|
2714
|
-
}
|
|
2715
|
-
function emitStarted(ctx, label, optimistic) {
|
|
2716
|
-
ctx.emit({ type: "action.started", label, ...optimistic ? { optimistic } : {} });
|
|
2717
|
-
}
|
|
2718
|
-
function emitProgress(ctx, label, detail, data) {
|
|
2719
|
-
ctx.emit({ type: "action.progress", label, ...detail ? { detail } : {}, ...data ? { data } : {} });
|
|
2720
|
-
}
|
|
2721
|
-
function emitCompleted(ctx, label, data) {
|
|
2722
|
-
ctx.emit({ type: "action.completed", label, ...data ? { data } : {} });
|
|
2723
|
-
}
|
|
2724
|
-
function emitFailed(ctx, label, error, data) {
|
|
2725
|
-
if (data)
|
|
2726
|
-
patchData(ctx, data);
|
|
2727
|
-
const normalized = normalizeAppError(error);
|
|
2728
|
-
ctx.emit({
|
|
2729
|
-
type: "action.failed",
|
|
2730
|
-
label,
|
|
2731
|
-
message: normalized.message,
|
|
2732
|
-
...normalized.hint ? { hint: normalized.hint } : {},
|
|
2733
|
-
cause: error
|
|
2734
|
-
});
|
|
2735
|
-
}
|
|
2736
|
-
function patchData(ctx, data) {
|
|
2737
|
-
ctx.emit({ type: "data.patch", data });
|
|
2738
|
-
}
|
|
2739
|
-
async function captureConsole(fn) {
|
|
2740
|
-
const output = [];
|
|
2741
|
-
const original = {
|
|
2742
|
-
log: console.log,
|
|
2743
|
-
info: console.info,
|
|
2744
|
-
warn: console.warn,
|
|
2745
|
-
error: console.error
|
|
2746
|
-
};
|
|
2747
|
-
const capture = (...parts) => {
|
|
2748
|
-
output.push(parts.map((part) => typeof part === "string" ? part : JSON.stringify(part)).join(" "));
|
|
2749
|
-
};
|
|
2750
|
-
console.log = capture;
|
|
2751
|
-
console.info = capture;
|
|
2752
|
-
console.warn = capture;
|
|
2753
|
-
console.error = capture;
|
|
2754
|
-
try {
|
|
2755
|
-
const result = await fn();
|
|
2756
|
-
return { result, output };
|
|
2757
|
-
} finally {
|
|
2758
|
-
console.log = original.log;
|
|
2759
|
-
console.info = original.info;
|
|
2760
|
-
console.warn = original.warn;
|
|
2761
|
-
console.error = original.error;
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
|
|
2765
|
-
// packages/cli/src/app-opentui/adapters/init.ts
|
|
2766
|
-
function readInitFacts(ctx) {
|
|
2767
|
-
const projectRoot = projectRootOf(ctx);
|
|
2768
|
-
const configNames = ["rig.config.ts", "rig.config.mts", "rig.config.json"];
|
|
2769
|
-
const configPath = configNames.map((name) => resolve7(projectRoot, name)).find((path) => existsSync6(path));
|
|
2770
|
-
return {
|
|
2771
|
-
hasConfig: Boolean(configPath),
|
|
2772
|
-
...configPath ? { configPath } : {},
|
|
2773
|
-
hasGit: existsSync6(resolve7(projectRoot, ".git")),
|
|
2774
|
-
suggestedCommand: configPath ? "doctor" : "init"
|
|
2775
|
-
};
|
|
2776
|
-
}
|
|
2777
|
-
async function loadInitFacts(ctx) {
|
|
2778
|
-
const label = "Inspecting project";
|
|
2779
|
-
emitStarted(ctx, label);
|
|
2780
|
-
try {
|
|
2781
|
-
const facts = readInitFacts(ctx);
|
|
2782
|
-
patchData(ctx, { init: { facts } });
|
|
2783
|
-
emitCompleted(ctx, label, facts);
|
|
2784
|
-
return facts;
|
|
2785
|
-
} catch (error) {
|
|
2786
|
-
emitFailed(ctx, label, error);
|
|
2787
|
-
throw error;
|
|
2788
|
-
}
|
|
2789
|
-
}
|
|
2790
|
-
async function runInitYes(ctx, args = ["--yes"]) {
|
|
2791
|
-
const label = "Initializing project";
|
|
2792
|
-
emitStarted(ctx, label, { init: { status: "writing-config" } });
|
|
2793
|
-
try {
|
|
2794
|
-
const runtime = await runtimeOf(ctx);
|
|
2795
|
-
emitProgress(ctx, label, "running existing init side effects without legacy output");
|
|
2796
|
-
const { executeInit: executeInit2 } = await Promise.resolve().then(() => (init_init(), exports_init));
|
|
2797
|
-
const { result, output } = await captureConsole(() => executeInit2({ ...runtime, outputMode: "json" }, [...args]));
|
|
2798
|
-
const record = result && typeof result === "object" && !Array.isArray(result) ? result : { ok: true };
|
|
2799
|
-
patchData(ctx, { init: { status: "done", result: record, capturedOutput: output } });
|
|
2800
|
-
emitCompleted(ctx, label, { result: record });
|
|
2801
|
-
return record;
|
|
2802
|
-
} catch (error) {
|
|
2803
|
-
patchData(ctx, { init: { status: "failed", error: error instanceof Error ? error.message : String(error) } });
|
|
2804
|
-
emitFailed(ctx, label, error);
|
|
2805
|
-
throw error;
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
function buildInitTypeBarSuggestions(facts) {
|
|
2809
|
-
if (facts.hasConfig)
|
|
2810
|
-
return ["doctor", "tasks", "server"];
|
|
2811
|
-
return ["init --yes", "init --server remote", "help init"];
|
|
2812
|
-
}
|
|
2813
|
-
export {
|
|
2814
|
-
runInitYes,
|
|
2815
|
-
readInitFacts,
|
|
2816
|
-
loadInitFacts,
|
|
2817
|
-
buildInitTypeBarSuggestions
|
|
2818
|
-
};
|