@h-rig/cli 0.0.6-alpha.24 → 0.0.6-alpha.26
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/rig.js +406 -226
- package/dist/src/commands/_cli-format.js +71 -13
- package/dist/src/commands/_help-catalog.js +174 -0
- package/dist/src/commands/connect.js +131 -23
- package/dist/src/commands/run.js +20 -6
- package/dist/src/commands/server.js +206 -8
- package/dist/src/commands/task.js +32 -10
- package/dist/src/commands.js +406 -226
- package/dist/src/index.js +406 -226
- package/package.json +6 -6
|
@@ -60,10 +60,8 @@ function normalizeRuntimeAdapter(value) {
|
|
|
60
60
|
return "claude-code";
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// packages/cli/src/commands/
|
|
64
|
-
import {
|
|
65
|
-
import { resolve as resolve2 } from "path";
|
|
66
|
-
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
63
|
+
// packages/cli/src/commands/connect.ts
|
|
64
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
67
65
|
|
|
68
66
|
// packages/cli/src/commands/_connection-state.ts
|
|
69
67
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -90,6 +88,11 @@ function readJsonFile(path) {
|
|
|
90
88
|
throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
91
89
|
}
|
|
92
90
|
}
|
|
91
|
+
function writeJsonFile2(path, value) {
|
|
92
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
93
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
94
|
+
`, "utf8");
|
|
95
|
+
}
|
|
93
96
|
function normalizeConnection(value) {
|
|
94
97
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
95
98
|
return null;
|
|
@@ -119,6 +122,18 @@ function readGlobalConnections(options = {}) {
|
|
|
119
122
|
}
|
|
120
123
|
return { connections };
|
|
121
124
|
}
|
|
125
|
+
function writeGlobalConnections(state, options = {}) {
|
|
126
|
+
writeJsonFile2(resolveGlobalConnectionsPath(options.env ?? process.env), state);
|
|
127
|
+
}
|
|
128
|
+
function upsertGlobalConnection(alias, connection, options = {}) {
|
|
129
|
+
const cleanAlias = alias.trim();
|
|
130
|
+
if (!cleanAlias)
|
|
131
|
+
throw new CliError2("Connection alias is required.", 1);
|
|
132
|
+
const state = readGlobalConnections(options);
|
|
133
|
+
state.connections[cleanAlias] = connection;
|
|
134
|
+
writeGlobalConnections(state, options);
|
|
135
|
+
return state;
|
|
136
|
+
}
|
|
122
137
|
function readRepoConnection(projectRoot) {
|
|
123
138
|
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
124
139
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
@@ -133,6 +148,9 @@ function readRepoConnection(projectRoot) {
|
|
|
133
148
|
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
134
149
|
};
|
|
135
150
|
}
|
|
151
|
+
function writeRepoConnection(projectRoot, state) {
|
|
152
|
+
writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
|
|
153
|
+
}
|
|
136
154
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
137
155
|
const repo = readRepoConnection(projectRoot);
|
|
138
156
|
if (!repo)
|
|
@@ -147,7 +165,184 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
147
165
|
return { alias: repo.selected, connection };
|
|
148
166
|
}
|
|
149
167
|
|
|
168
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
169
|
+
import pc from "picocolors";
|
|
170
|
+
function truncate(value, width) {
|
|
171
|
+
if (value.length <= width)
|
|
172
|
+
return value;
|
|
173
|
+
if (width <= 1)
|
|
174
|
+
return "\u2026";
|
|
175
|
+
return `${value.slice(0, width - 1)}\u2026`;
|
|
176
|
+
}
|
|
177
|
+
function pad(value, width) {
|
|
178
|
+
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
179
|
+
}
|
|
180
|
+
function statusColor(status) {
|
|
181
|
+
const normalized = status.toLowerCase();
|
|
182
|
+
if (["completed", "merged", "closed", "done", "accepted", "pass", "selected"].includes(normalized))
|
|
183
|
+
return pc.green;
|
|
184
|
+
if (["failed", "needs_attention", "needs-attention", "blocked", "error"].includes(normalized))
|
|
185
|
+
return pc.red;
|
|
186
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
|
|
187
|
+
return pc.cyan;
|
|
188
|
+
if (["ready", "open", "queued", "created", "preparing", "local"].includes(normalized))
|
|
189
|
+
return pc.yellow;
|
|
190
|
+
return pc.dim;
|
|
191
|
+
}
|
|
192
|
+
function formatStatusPill(status) {
|
|
193
|
+
const label = status || "unknown";
|
|
194
|
+
return statusColor(label)(`\u25CF ${label}`);
|
|
195
|
+
}
|
|
196
|
+
function formatSection(title, subtitle) {
|
|
197
|
+
return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
|
|
198
|
+
}
|
|
199
|
+
function formatSuccessCard(title, rows = []) {
|
|
200
|
+
const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(9))} ${value}`);
|
|
201
|
+
return [formatSection(title), ...body].join(`
|
|
202
|
+
`);
|
|
203
|
+
}
|
|
204
|
+
function formatNextSteps(steps) {
|
|
205
|
+
if (steps.length === 0)
|
|
206
|
+
return [];
|
|
207
|
+
return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
|
|
208
|
+
}
|
|
209
|
+
function formatConnectionList(connections) {
|
|
210
|
+
const rows = [["local", { kind: "local", mode: "auto" }], ...Object.entries(connections)];
|
|
211
|
+
const aliasWidth = Math.min(24, Math.max(5, ...rows.map(([alias]) => alias.length)));
|
|
212
|
+
const lines = rows.map(([alias, connection]) => [
|
|
213
|
+
pc.bold(pad(truncate(alias, aliasWidth), aliasWidth)),
|
|
214
|
+
formatStatusPill(connection.kind),
|
|
215
|
+
connection.kind === "remote" ? connection.baseUrl ?? "" : connection.mode ?? "local"
|
|
216
|
+
].join(" "));
|
|
217
|
+
return [formatSection("Rig servers", `${rows.length} available`), `${pc.bold(pad("ALIAS", aliasWidth))} ${pc.bold("KIND")} ${pc.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
|
|
218
|
+
`);
|
|
219
|
+
}
|
|
220
|
+
function formatConnectionStatus(selected, connections) {
|
|
221
|
+
const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
|
|
222
|
+
const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
|
|
223
|
+
return [
|
|
224
|
+
formatSection("Rig server", "selected for this repo"),
|
|
225
|
+
`${pc.dim("\u2502")} ${pc.dim("selected ")} ${pc.bold(selected)}`,
|
|
226
|
+
`${pc.dim("\u2502")} ${pc.dim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
|
|
227
|
+
`${pc.dim("\u2502")} ${pc.dim("target ")} ${target ?? "not configured"}`,
|
|
228
|
+
"",
|
|
229
|
+
...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
230
|
+
].join(`
|
|
231
|
+
`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// packages/cli/src/commands/connect.ts
|
|
235
|
+
function usageName(options) {
|
|
236
|
+
return `rig ${options.group}`;
|
|
237
|
+
}
|
|
238
|
+
function parseConnection(alias, value, options) {
|
|
239
|
+
if (alias === "local" && !value)
|
|
240
|
+
return { kind: "local", mode: "auto" };
|
|
241
|
+
if (!value)
|
|
242
|
+
throw new CliError2(`Missing remote server URL. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
243
|
+
let parsed;
|
|
244
|
+
try {
|
|
245
|
+
parsed = new URL(value);
|
|
246
|
+
} catch {
|
|
247
|
+
throw new CliError2(`Invalid Rig server URL: ${value}`, 1);
|
|
248
|
+
}
|
|
249
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
250
|
+
throw new CliError2("Rig remote server URL must be http(s).", 1);
|
|
251
|
+
}
|
|
252
|
+
return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
|
|
253
|
+
}
|
|
254
|
+
function printJsonOrText(context, payload, text) {
|
|
255
|
+
if (context.outputMode === "json") {
|
|
256
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
257
|
+
} else {
|
|
258
|
+
console.log(text);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function promptForConnectionAlias(context) {
|
|
262
|
+
const state = readGlobalConnections();
|
|
263
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
264
|
+
const options = [
|
|
265
|
+
{ value: "local", label: "local", hint: "Use/start a local Rig server" },
|
|
266
|
+
...Object.entries(state.connections).map(([alias, connection]) => ({
|
|
267
|
+
value: alias,
|
|
268
|
+
label: alias,
|
|
269
|
+
hint: connection.kind === "remote" ? connection.baseUrl : "local"
|
|
270
|
+
}))
|
|
271
|
+
].filter((option, index, all) => all.findIndex((candidate) => candidate.value === option.value) === index);
|
|
272
|
+
const answer = await select({
|
|
273
|
+
message: "Select Rig server for this repo",
|
|
274
|
+
initialValue: repo?.selected ?? "local",
|
|
275
|
+
options
|
|
276
|
+
});
|
|
277
|
+
if (isCancel(answer)) {
|
|
278
|
+
cancel("No server selected.");
|
|
279
|
+
throw new CliError2("No server selected.", 3);
|
|
280
|
+
}
|
|
281
|
+
return String(answer);
|
|
282
|
+
}
|
|
283
|
+
async function executeConnectionCommand(context, args, options) {
|
|
284
|
+
const [command, ...rest] = args;
|
|
285
|
+
switch (command ?? "status") {
|
|
286
|
+
case "list": {
|
|
287
|
+
requireNoExtraArgs(rest, `${usageName(options)} list`);
|
|
288
|
+
const state = readGlobalConnections();
|
|
289
|
+
printJsonOrText(context, state, formatConnectionList(state.connections));
|
|
290
|
+
return { ok: true, group: options.group, command: "list", details: state };
|
|
291
|
+
}
|
|
292
|
+
case "add": {
|
|
293
|
+
const [alias, url, ...extra] = rest;
|
|
294
|
+
if (!alias)
|
|
295
|
+
throw new CliError2(`Missing alias. Usage: ${usageName(options)} add <alias> <url>`, 1);
|
|
296
|
+
requireNoExtraArgs(extra, `${usageName(options)} add <alias> <url>`);
|
|
297
|
+
const connection = parseConnection(alias, url, options);
|
|
298
|
+
const state = upsertGlobalConnection(alias, connection);
|
|
299
|
+
printJsonOrText(context, { alias, connection }, formatSuccessCard("Rig server saved", [
|
|
300
|
+
["alias", alias],
|
|
301
|
+
["target", connection.kind === "remote" ? connection.baseUrl : "local"],
|
|
302
|
+
["next", `${usageName(options)} use ${alias}`]
|
|
303
|
+
]));
|
|
304
|
+
return { ok: true, group: options.group, command: "add", details: { alias, connection, count: Object.keys(state.connections).length } };
|
|
305
|
+
}
|
|
306
|
+
case "use": {
|
|
307
|
+
let [alias, ...extra] = rest;
|
|
308
|
+
requireNoExtraArgs(extra, `${usageName(options)} use <alias|local>`);
|
|
309
|
+
if (!alias && options.interactiveUse && context.outputMode === "text" && process.stdin.isTTY) {
|
|
310
|
+
alias = await promptForConnectionAlias(context);
|
|
311
|
+
}
|
|
312
|
+
if (!alias)
|
|
313
|
+
throw new CliError2(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
|
|
314
|
+
if (alias !== "local") {
|
|
315
|
+
const state = readGlobalConnections();
|
|
316
|
+
if (!state.connections[alias])
|
|
317
|
+
throw new CliError2(`Unknown Rig server: ${alias}`, 1);
|
|
318
|
+
}
|
|
319
|
+
const repoState = { selected: alias, linkedAt: new Date().toISOString() };
|
|
320
|
+
writeRepoConnection(context.projectRoot, repoState);
|
|
321
|
+
printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
|
|
322
|
+
["selected", alias],
|
|
323
|
+
["scope", "this repo"],
|
|
324
|
+
["next", "rig task list"]
|
|
325
|
+
]));
|
|
326
|
+
return { ok: true, group: options.group, command: "use", details: repoState };
|
|
327
|
+
}
|
|
328
|
+
case "status": {
|
|
329
|
+
requireNoExtraArgs(rest, `${usageName(options)} status`);
|
|
330
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
331
|
+
const global = readGlobalConnections();
|
|
332
|
+
const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
|
|
333
|
+
printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
|
|
334
|
+
return { ok: true, group: options.group, command: "status", details };
|
|
335
|
+
}
|
|
336
|
+
default:
|
|
337
|
+
throw new CliError2(`Unknown ${options.group} command: ${String(command)}
|
|
338
|
+
Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
150
342
|
// packages/cli/src/commands/_server-client.ts
|
|
343
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
344
|
+
import { resolve as resolve2 } from "path";
|
|
345
|
+
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
151
346
|
var scopedGitHubBearerTokens = new Map;
|
|
152
347
|
function cleanToken(value) {
|
|
153
348
|
const trimmed = value?.trim();
|
|
@@ -273,7 +468,10 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
273
468
|
|
|
274
469
|
// packages/cli/src/commands/server.ts
|
|
275
470
|
async function executeServer(context, args, options) {
|
|
276
|
-
const [command = "
|
|
471
|
+
const [command = "status", ...rest] = args;
|
|
472
|
+
if (["status", "list", "add", "use"].includes(command)) {
|
|
473
|
+
return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
|
|
474
|
+
}
|
|
277
475
|
switch (command) {
|
|
278
476
|
case "start": {
|
|
279
477
|
let pending = rest;
|
|
@@ -285,7 +483,7 @@ async function executeServer(context, args, options) {
|
|
|
285
483
|
pending = pollResult.rest;
|
|
286
484
|
const authTokenResult = takeOption(pending, "--auth-token");
|
|
287
485
|
pending = authTokenResult.rest;
|
|
288
|
-
requireNoExtraArgs(pending, "
|
|
486
|
+
requireNoExtraArgs(pending, "rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
|
|
289
487
|
const commandParts = ["rig-server", "start"];
|
|
290
488
|
if (hostResult.value) {
|
|
291
489
|
commandParts.push("--host", hostResult.value);
|
|
@@ -308,7 +506,7 @@ async function executeServer(context, args, options) {
|
|
|
308
506
|
let pending = rest;
|
|
309
507
|
const eventResult = takeOption(pending, "--event");
|
|
310
508
|
pending = eventResult.rest;
|
|
311
|
-
requireNoExtraArgs(pending, "
|
|
509
|
+
requireNoExtraArgs(pending, "rig server notify-test [--event <type>]");
|
|
312
510
|
const commandParts = ["rig-server", "notify-test"];
|
|
313
511
|
if (eventResult.value) {
|
|
314
512
|
commandParts.push("--event", eventResult.value);
|
|
@@ -336,7 +534,7 @@ async function executeServer(context, args, options) {
|
|
|
336
534
|
pending = dirtyBaselineResult.rest;
|
|
337
535
|
const prResult = takeOption(pending, "--pr");
|
|
338
536
|
pending = prResult.rest;
|
|
339
|
-
requireNoExtraArgs(pending, "
|
|
537
|
+
requireNoExtraArgs(pending, "rig --run-id <run-id> server task-run [--task <id>] [--title <text>] [--runtime-adapter claude-code|codex|pi] [--model <model>] [--runtime-mode <mode>] [--interaction-mode <mode>] [--initial-prompt <text>] [--dirty-baseline head|dirty-snapshot] [--pr auto|ask|off]");
|
|
340
538
|
if (!taskResult.value && !initialPromptResult.value && !titleResult.value) {
|
|
341
539
|
throw new CliError2("server task-run requires either --task <id> or --initial-prompt/--title for an ad hoc run.", 2);
|
|
342
540
|
}
|
|
@@ -1647,21 +1647,39 @@ function pad(value, width) {
|
|
|
1647
1647
|
}
|
|
1648
1648
|
function statusColor(status) {
|
|
1649
1649
|
const normalized = status.toLowerCase();
|
|
1650
|
-
if (["completed", "merged", "closed", "done", "accepted"].includes(normalized))
|
|
1650
|
+
if (["completed", "merged", "closed", "done", "accepted", "pass", "selected"].includes(normalized))
|
|
1651
1651
|
return pc.green;
|
|
1652
|
-
if (["failed", "needs_attention", "needs-attention", "blocked"].includes(normalized))
|
|
1652
|
+
if (["failed", "needs_attention", "needs-attention", "blocked", "error"].includes(normalized))
|
|
1653
1653
|
return pc.red;
|
|
1654
|
-
if (["running", "reviewing", "validating", "in_progress", "in-progress"].includes(normalized))
|
|
1654
|
+
if (["running", "reviewing", "validating", "in_progress", "in-progress", "remote"].includes(normalized))
|
|
1655
1655
|
return pc.cyan;
|
|
1656
|
-
if (["ready", "open", "queued", "created", "preparing"].includes(normalized))
|
|
1656
|
+
if (["ready", "open", "queued", "created", "preparing", "local"].includes(normalized))
|
|
1657
1657
|
return pc.yellow;
|
|
1658
1658
|
return pc.dim;
|
|
1659
1659
|
}
|
|
1660
|
+
function formatStatusPill(status) {
|
|
1661
|
+
const label = status || "unknown";
|
|
1662
|
+
return statusColor(label)(`\u25CF ${label}`);
|
|
1663
|
+
}
|
|
1664
|
+
function formatSection(title, subtitle) {
|
|
1665
|
+
return `${pc.bold(pc.cyan("\u25C6"))} ${pc.bold(title)}${subtitle ? pc.dim(` \u2014 ${subtitle}`) : ""}`;
|
|
1666
|
+
}
|
|
1667
|
+
function formatSuccessCard(title, rows = []) {
|
|
1668
|
+
const body = rows.filter(([, value]) => value !== undefined && value !== null && String(value).length > 0).map(([key, value]) => `${pc.dim("\u2502")} ${pc.dim(key.padEnd(9))} ${value}`);
|
|
1669
|
+
return [formatSection(title), ...body].join(`
|
|
1670
|
+
`);
|
|
1671
|
+
}
|
|
1672
|
+
function formatNextSteps(steps) {
|
|
1673
|
+
if (steps.length === 0)
|
|
1674
|
+
return [];
|
|
1675
|
+
return [pc.bold("Next"), ...steps.map((step) => `${pc.dim("\u203A")} ${step}`)];
|
|
1676
|
+
}
|
|
1660
1677
|
function formatTaskList(tasks, options = {}) {
|
|
1661
|
-
if (tasks.length === 0)
|
|
1662
|
-
return pc.dim("No matching tasks.");
|
|
1663
1678
|
if (options.raw)
|
|
1664
1679
|
return tasks.map((task) => JSON.stringify(task)).join(`
|
|
1680
|
+
`);
|
|
1681
|
+
if (tasks.length === 0)
|
|
1682
|
+
return [formatSection("Tasks", "none found"), ...formatNextSteps(["Try `rig server status` to confirm the selected server.", "Relax filters or run `rig task run --title ... --initial-prompt ...` for ad hoc work."])].join(`
|
|
1665
1683
|
`);
|
|
1666
1684
|
const rows = tasks.map((task) => {
|
|
1667
1685
|
const raw = rawObject(task);
|
|
@@ -1684,18 +1702,22 @@ function formatTaskList(tasks, options = {}) {
|
|
|
1684
1702
|
`${row.title}${labels}${source}`
|
|
1685
1703
|
].join(" ");
|
|
1686
1704
|
});
|
|
1687
|
-
return [
|
|
1705
|
+
return [formatSection("Tasks", `${rows.length} shown`), header, ...body, "", ...formatNextSteps(["Run one: `rig task run <id>` or `rig task run --next`", "Attach later: `rig run attach <run-id> --follow`"])].join(`
|
|
1688
1706
|
`);
|
|
1689
1707
|
}
|
|
1690
1708
|
function formatSubmittedRun(input) {
|
|
1691
|
-
const
|
|
1709
|
+
const rows = [["run", pc.bold(input.runId)]];
|
|
1692
1710
|
if (input.task) {
|
|
1693
1711
|
const id = stringField(input.task, "id", "<unknown>");
|
|
1694
1712
|
const status = stringField(input.task, "status", "unknown");
|
|
1695
1713
|
const title = stringField(input.task, "title", "Untitled task");
|
|
1696
|
-
|
|
1714
|
+
rows.push(["task", `${pc.bold(id)} ${formatStatusPill(status)} ${title}`]);
|
|
1697
1715
|
}
|
|
1698
|
-
return
|
|
1716
|
+
return [
|
|
1717
|
+
formatSuccessCard("Run submitted", rows),
|
|
1718
|
+
"",
|
|
1719
|
+
...formatNextSteps([`Attach: \`rig run attach ${input.runId} --follow\``, `Inspect: \`rig run show --run ${input.runId}\``])
|
|
1720
|
+
].join(`
|
|
1699
1721
|
`);
|
|
1700
1722
|
}
|
|
1701
1723
|
|