@h-rig/cli 0.0.6-alpha.65 → 0.0.6-alpha.67
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 +19 -96
- package/dist/src/commands/_connection-state.js +5 -1
- package/dist/src/commands/run.js +17 -7
- package/dist/src/commands/stats.js +293 -89
- package/dist/src/commands.js +19 -96
- package/dist/src/index.js +19 -96
- package/package.json +8 -8
package/dist/bin/rig.js
CHANGED
|
@@ -2923,6 +2923,9 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
2923
2923
|
return;
|
|
2924
2924
|
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
2925
2925
|
}
|
|
2926
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
2927
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
2928
|
+
}
|
|
2926
2929
|
|
|
2927
2930
|
// packages/cli/src/commands/_server-client.ts
|
|
2928
2931
|
init_runner();
|
|
@@ -7849,9 +7852,6 @@ function normalizeRemoteRunDetails(payload) {
|
|
|
7849
7852
|
};
|
|
7850
7853
|
}
|
|
7851
7854
|
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
7852
|
-
function isRemoteConnectionSelected(projectRoot) {
|
|
7853
|
-
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
7854
|
-
}
|
|
7855
7855
|
async function listRunsForSelectedConnection(context, options = {}) {
|
|
7856
7856
|
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
7857
7857
|
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
@@ -8201,28 +8201,38 @@ async function executeRun(context, args) {
|
|
|
8201
8201
|
};
|
|
8202
8202
|
}
|
|
8203
8203
|
case "resume": {
|
|
8204
|
-
|
|
8204
|
+
let pending = rest;
|
|
8205
|
+
const runOpt = takeOption(pending, "--run");
|
|
8206
|
+
pending = runOpt.rest;
|
|
8207
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
8208
|
+
requireNoExtraArgs(pending, "rig run resume [<run-id>] [--run <id>]");
|
|
8209
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
8205
8210
|
if (context.dryRun) {
|
|
8206
8211
|
if (context.outputMode === "text") {
|
|
8207
8212
|
console.log("[dry-run] rig run resume");
|
|
8208
8213
|
}
|
|
8209
8214
|
return { ok: true, group: "run", command };
|
|
8210
8215
|
}
|
|
8211
|
-
const resumed = await runResume(context.projectRoot, runtimeContext);
|
|
8216
|
+
const resumed = await runResume(context.projectRoot, runtimeContext, targetRunId);
|
|
8212
8217
|
if (context.outputMode === "text") {
|
|
8213
8218
|
console.log(`Resumed run: ${resumed.runId}`);
|
|
8214
8219
|
}
|
|
8215
8220
|
return { ok: true, group: "run", command, details: resumed };
|
|
8216
8221
|
}
|
|
8217
8222
|
case "restart": {
|
|
8218
|
-
|
|
8223
|
+
let pending = rest;
|
|
8224
|
+
const runOpt = takeOption(pending, "--run");
|
|
8225
|
+
pending = runOpt.rest;
|
|
8226
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
8227
|
+
requireNoExtraArgs(pending, "rig run restart [<run-id>] [--run <id>]");
|
|
8228
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
8219
8229
|
if (context.dryRun) {
|
|
8220
8230
|
if (context.outputMode === "text") {
|
|
8221
8231
|
console.log("[dry-run] rig run restart");
|
|
8222
8232
|
}
|
|
8223
8233
|
return { ok: true, group: "run", command };
|
|
8224
8234
|
}
|
|
8225
|
-
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
8235
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext, targetRunId);
|
|
8226
8236
|
if (context.outputMode === "text") {
|
|
8227
8237
|
console.log(`Restarted run: ${restarted.runId}`);
|
|
8228
8238
|
}
|
|
@@ -8517,10 +8527,7 @@ async function executeServer(context, args, options) {
|
|
|
8517
8527
|
// packages/cli/src/commands/stats.ts
|
|
8518
8528
|
init_runner();
|
|
8519
8529
|
import pc5 from "picocolors";
|
|
8520
|
-
import {
|
|
8521
|
-
listAuthorityRuns as listAuthorityRuns3,
|
|
8522
|
-
projectRunFromJournal
|
|
8523
|
-
} from "@rig/runtime/control-plane/authority-files";
|
|
8530
|
+
import { computeRigStats } from "@rig/runtime/control-plane/native/run-stats";
|
|
8524
8531
|
|
|
8525
8532
|
// packages/cli/src/commands/_help-catalog.ts
|
|
8526
8533
|
import { intro as intro3, log as log4, note as note4, outro as outro3 } from "@clack/prompts";
|
|
@@ -9009,90 +9016,6 @@ function parseSinceOption(value, now = new Date) {
|
|
|
9009
9016
|
}
|
|
9010
9017
|
return new Date(parsed);
|
|
9011
9018
|
}
|
|
9012
|
-
function median(values) {
|
|
9013
|
-
if (values.length === 0)
|
|
9014
|
-
return null;
|
|
9015
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
9016
|
-
const middle = Math.floor(sorted.length / 2);
|
|
9017
|
-
const upper = sorted[middle];
|
|
9018
|
-
if (sorted.length % 2 === 1)
|
|
9019
|
-
return upper;
|
|
9020
|
-
return (sorted[middle - 1] + upper) / 2;
|
|
9021
|
-
}
|
|
9022
|
-
function rate(part, total) {
|
|
9023
|
-
if (total === 0)
|
|
9024
|
-
return null;
|
|
9025
|
-
return part / total;
|
|
9026
|
-
}
|
|
9027
|
-
function parseTimestamp(value) {
|
|
9028
|
-
if (!value)
|
|
9029
|
-
return null;
|
|
9030
|
-
const parsed = Date.parse(value);
|
|
9031
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
9032
|
-
}
|
|
9033
|
-
function computeRigStats(projectRoot, options = {}) {
|
|
9034
|
-
const since = options.since ?? null;
|
|
9035
|
-
const sinceMs = since ? since.getTime() : null;
|
|
9036
|
-
const runs = listAuthorityRuns3(projectRoot).filter((run) => {
|
|
9037
|
-
if (sinceMs === null)
|
|
9038
|
-
return true;
|
|
9039
|
-
const createdAt = parseTimestamp(run.createdAt) ?? parseTimestamp(run.updatedAt);
|
|
9040
|
-
return createdAt !== null && createdAt >= sinceMs;
|
|
9041
|
-
});
|
|
9042
|
-
const statusCounts = {};
|
|
9043
|
-
const completionDurations = [];
|
|
9044
|
-
let steeringTotal = 0;
|
|
9045
|
-
let stallTotal = 0;
|
|
9046
|
-
let approvalsApproved = 0;
|
|
9047
|
-
let approvalsRejected = 0;
|
|
9048
|
-
let approvalsPending = 0;
|
|
9049
|
-
for (const run of runs) {
|
|
9050
|
-
const status = run.status ?? "unknown";
|
|
9051
|
-
statusCounts[status] = (statusCounts[status] ?? 0) + 1;
|
|
9052
|
-
if (status === "completed") {
|
|
9053
|
-
const createdAt = parseTimestamp(run.createdAt);
|
|
9054
|
-
const completedAt = parseTimestamp(run.completedAt);
|
|
9055
|
-
if (createdAt !== null && completedAt !== null && completedAt >= createdAt) {
|
|
9056
|
-
completionDurations.push(completedAt - createdAt);
|
|
9057
|
-
}
|
|
9058
|
-
}
|
|
9059
|
-
const projection = projectRunFromJournal(projectRoot, run.runId);
|
|
9060
|
-
if (!projection)
|
|
9061
|
-
continue;
|
|
9062
|
-
steeringTotal += projection.steeringCount;
|
|
9063
|
-
stallTotal += projection.stallCount;
|
|
9064
|
-
approvalsPending += projection.pendingApprovals.length;
|
|
9065
|
-
for (const resolved of projection.resolvedApprovals) {
|
|
9066
|
-
if (resolved.decision === "approve")
|
|
9067
|
-
approvalsApproved += 1;
|
|
9068
|
-
else
|
|
9069
|
-
approvalsRejected += 1;
|
|
9070
|
-
}
|
|
9071
|
-
}
|
|
9072
|
-
const totalRuns = runs.length;
|
|
9073
|
-
const completedRuns = statusCounts["completed"] ?? 0;
|
|
9074
|
-
const failedRuns = statusCounts["failed"] ?? 0;
|
|
9075
|
-
const needsAttentionRuns = statusCounts["needs-attention"] ?? 0;
|
|
9076
|
-
return {
|
|
9077
|
-
since: since ? since.toISOString() : null,
|
|
9078
|
-
totalRuns,
|
|
9079
|
-
statusCounts,
|
|
9080
|
-
completedRuns,
|
|
9081
|
-
failedRuns,
|
|
9082
|
-
needsAttentionRuns,
|
|
9083
|
-
completionRate: rate(completedRuns, totalRuns),
|
|
9084
|
-
failureRate: rate(failedRuns, totalRuns),
|
|
9085
|
-
needsAttentionRate: rate(needsAttentionRuns, totalRuns),
|
|
9086
|
-
medianCompletionMs: median(completionDurations),
|
|
9087
|
-
steeringTotal,
|
|
9088
|
-
steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
|
|
9089
|
-
stallTotal,
|
|
9090
|
-
approvalsRequested: approvalsApproved + approvalsRejected + approvalsPending,
|
|
9091
|
-
approvalsApproved,
|
|
9092
|
-
approvalsRejected,
|
|
9093
|
-
approvalsPending
|
|
9094
|
-
};
|
|
9095
|
-
}
|
|
9096
9019
|
function formatPercent(value) {
|
|
9097
9020
|
if (value === null)
|
|
9098
9021
|
return "\u2014";
|
|
@@ -9150,7 +9073,7 @@ async function executeStats(context, args) {
|
|
|
9150
9073
|
const sinceResult = takeOption(pending, "--since");
|
|
9151
9074
|
requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
|
|
9152
9075
|
const since = parseSinceOption(sinceResult.value);
|
|
9153
|
-
const stats = computeRigStats(context.projectRoot, { since });
|
|
9076
|
+
const stats = isRemoteConnectionSelected(context.projectRoot) ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since });
|
|
9154
9077
|
if (context.outputMode === "text") {
|
|
9155
9078
|
printFormattedOutput(formatStatsReport(stats));
|
|
9156
9079
|
}
|
|
@@ -125,6 +125,9 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
125
125
|
return;
|
|
126
126
|
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
127
127
|
}
|
|
128
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
129
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
130
|
+
}
|
|
128
131
|
export {
|
|
129
132
|
writeRepoServerProjectRoot,
|
|
130
133
|
writeRepoConnection,
|
|
@@ -134,5 +137,6 @@ export {
|
|
|
134
137
|
resolveRepoConnectionPath,
|
|
135
138
|
resolveGlobalConnectionsPath,
|
|
136
139
|
readRepoConnection,
|
|
137
|
-
readGlobalConnections
|
|
140
|
+
readGlobalConnections,
|
|
141
|
+
isRemoteConnectionSelected
|
|
138
142
|
};
|
package/dist/src/commands/run.js
CHANGED
|
@@ -188,6 +188,9 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
188
188
|
return;
|
|
189
189
|
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
190
190
|
}
|
|
191
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
192
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
193
|
+
}
|
|
191
194
|
|
|
192
195
|
// packages/cli/src/commands/_server-client.ts
|
|
193
196
|
var scopedGitHubBearerTokens = new Map;
|
|
@@ -1167,9 +1170,6 @@ function normalizeRemoteRunDetails(payload) {
|
|
|
1167
1170
|
};
|
|
1168
1171
|
}
|
|
1169
1172
|
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
1170
|
-
function isRemoteConnectionSelected(projectRoot) {
|
|
1171
|
-
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
1172
|
-
}
|
|
1173
1173
|
async function listRunsForSelectedConnection(context, options = {}) {
|
|
1174
1174
|
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
1175
1175
|
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
@@ -1519,28 +1519,38 @@ async function executeRun(context, args) {
|
|
|
1519
1519
|
};
|
|
1520
1520
|
}
|
|
1521
1521
|
case "resume": {
|
|
1522
|
-
|
|
1522
|
+
let pending = rest;
|
|
1523
|
+
const runOpt = takeOption(pending, "--run");
|
|
1524
|
+
pending = runOpt.rest;
|
|
1525
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
1526
|
+
requireNoExtraArgs(pending, "rig run resume [<run-id>] [--run <id>]");
|
|
1527
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
1523
1528
|
if (context.dryRun) {
|
|
1524
1529
|
if (context.outputMode === "text") {
|
|
1525
1530
|
console.log("[dry-run] rig run resume");
|
|
1526
1531
|
}
|
|
1527
1532
|
return { ok: true, group: "run", command };
|
|
1528
1533
|
}
|
|
1529
|
-
const resumed = await runResume(context.projectRoot, runtimeContext);
|
|
1534
|
+
const resumed = await runResume(context.projectRoot, runtimeContext, targetRunId);
|
|
1530
1535
|
if (context.outputMode === "text") {
|
|
1531
1536
|
console.log(`Resumed run: ${resumed.runId}`);
|
|
1532
1537
|
}
|
|
1533
1538
|
return { ok: true, group: "run", command, details: resumed };
|
|
1534
1539
|
}
|
|
1535
1540
|
case "restart": {
|
|
1536
|
-
|
|
1541
|
+
let pending = rest;
|
|
1542
|
+
const runOpt = takeOption(pending, "--run");
|
|
1543
|
+
pending = runOpt.rest;
|
|
1544
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
1545
|
+
requireNoExtraArgs(pending, "rig run restart [<run-id>] [--run <id>]");
|
|
1546
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
1537
1547
|
if (context.dryRun) {
|
|
1538
1548
|
if (context.outputMode === "text") {
|
|
1539
1549
|
console.log("[dry-run] rig run restart");
|
|
1540
1550
|
}
|
|
1541
1551
|
return { ok: true, group: "run", command };
|
|
1542
1552
|
}
|
|
1543
|
-
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
1553
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext, targetRunId);
|
|
1544
1554
|
if (context.outputMode === "text") {
|
|
1545
1555
|
console.log(`Restarted run: ${restarted.runId}`);
|
|
1546
1556
|
}
|
|
@@ -45,10 +45,298 @@ Usage: ${usage}`);
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// packages/cli/src/commands/stats.ts
|
|
48
|
-
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} from "
|
|
48
|
+
import { computeRigStats } from "@rig/runtime/control-plane/native/run-stats";
|
|
49
|
+
|
|
50
|
+
// packages/cli/src/commands/_connection-state.ts
|
|
51
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
52
|
+
import { homedir } from "os";
|
|
53
|
+
import { dirname, resolve } from "path";
|
|
54
|
+
function resolveGlobalConnectionsPath(env = process.env) {
|
|
55
|
+
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
56
|
+
if (explicit)
|
|
57
|
+
return resolve(explicit);
|
|
58
|
+
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
59
|
+
if (stateDir)
|
|
60
|
+
return resolve(stateDir, "connections.json");
|
|
61
|
+
return resolve(homedir(), ".rig", "connections.json");
|
|
62
|
+
}
|
|
63
|
+
function resolveRepoConnectionPath(projectRoot) {
|
|
64
|
+
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
65
|
+
}
|
|
66
|
+
function readJsonFile(path) {
|
|
67
|
+
if (!existsSync(path))
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
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>`." });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function writeJsonFile(path, value) {
|
|
76
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
77
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
78
|
+
`, "utf8");
|
|
79
|
+
}
|
|
80
|
+
function normalizeConnection(value) {
|
|
81
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
82
|
+
return null;
|
|
83
|
+
const record = value;
|
|
84
|
+
if (record.kind === "local")
|
|
85
|
+
return { kind: "local", mode: "auto" };
|
|
86
|
+
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
87
|
+
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
88
|
+
return { kind: "remote", baseUrl };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function readGlobalConnections(options = {}) {
|
|
93
|
+
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
94
|
+
const payload = readJsonFile(path);
|
|
95
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
96
|
+
return { connections: {} };
|
|
97
|
+
}
|
|
98
|
+
const rawConnections = payload.connections;
|
|
99
|
+
const connections = {};
|
|
100
|
+
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
101
|
+
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
102
|
+
const connection = normalizeConnection(raw);
|
|
103
|
+
if (connection)
|
|
104
|
+
connections[alias] = connection;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { connections };
|
|
108
|
+
}
|
|
109
|
+
function readRepoConnection(projectRoot) {
|
|
110
|
+
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
111
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
112
|
+
return null;
|
|
113
|
+
const record = payload;
|
|
114
|
+
const selected = typeof record.selected === "string" ? record.selected.trim() : "";
|
|
115
|
+
if (!selected)
|
|
116
|
+
return null;
|
|
117
|
+
return {
|
|
118
|
+
selected,
|
|
119
|
+
project: typeof record.project === "string" ? record.project : undefined,
|
|
120
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
121
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function writeRepoConnection(projectRoot, state) {
|
|
125
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
126
|
+
}
|
|
127
|
+
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
128
|
+
const repo = readRepoConnection(projectRoot);
|
|
129
|
+
if (!repo)
|
|
130
|
+
return null;
|
|
131
|
+
if (repo.selected === "local")
|
|
132
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
133
|
+
const global = readGlobalConnections(options);
|
|
134
|
+
const connection = global.connections[repo.selected];
|
|
135
|
+
if (!connection) {
|
|
136
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
137
|
+
}
|
|
138
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
139
|
+
}
|
|
140
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
141
|
+
const repo = readRepoConnection(projectRoot);
|
|
142
|
+
if (!repo)
|
|
143
|
+
return;
|
|
144
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
145
|
+
}
|
|
146
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
147
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// packages/cli/src/commands/_server-client.ts
|
|
151
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
152
|
+
import { resolve as resolve2 } from "path";
|
|
153
|
+
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
154
|
+
var scopedGitHubBearerTokens = new Map;
|
|
155
|
+
function cleanToken(value) {
|
|
156
|
+
const trimmed = value?.trim();
|
|
157
|
+
return trimmed ? trimmed : null;
|
|
158
|
+
}
|
|
159
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
160
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
161
|
+
if (!existsSync2(path))
|
|
162
|
+
return null;
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
165
|
+
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
171
|
+
const scopedKey = resolve2(projectRoot);
|
|
172
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
173
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
174
|
+
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
175
|
+
if (privateSession)
|
|
176
|
+
return privateSession;
|
|
177
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
178
|
+
}
|
|
179
|
+
function readStoredGitHubAuthToken(projectRoot) {
|
|
180
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
181
|
+
if (!existsSync2(path))
|
|
182
|
+
return null;
|
|
183
|
+
try {
|
|
184
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
185
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function readLocalConnectionFallbackToken(projectRoot) {
|
|
191
|
+
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
192
|
+
}
|
|
193
|
+
async function ensureServerForCli(projectRoot) {
|
|
194
|
+
try {
|
|
195
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
196
|
+
if (selected?.connection.kind === "remote") {
|
|
197
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
198
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
199
|
+
return {
|
|
200
|
+
baseUrl: selected.connection.baseUrl,
|
|
201
|
+
authToken,
|
|
202
|
+
connectionKind: "remote",
|
|
203
|
+
serverProjectRoot
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
207
|
+
return {
|
|
208
|
+
baseUrl: connection.baseUrl,
|
|
209
|
+
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
210
|
+
connectionKind: "local",
|
|
211
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
212
|
+
};
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (error instanceof Error) {
|
|
215
|
+
throw new CliError(error.message, 1);
|
|
216
|
+
}
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
221
|
+
const repo = readRepoConnection(projectRoot);
|
|
222
|
+
const slug = repo?.project?.trim();
|
|
223
|
+
if (!slug)
|
|
224
|
+
return null;
|
|
225
|
+
try {
|
|
226
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
227
|
+
headers: mergeHeaders(undefined, authToken)
|
|
228
|
+
});
|
|
229
|
+
if (!response.ok)
|
|
230
|
+
return null;
|
|
231
|
+
const payload = await response.json();
|
|
232
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
233
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
234
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
235
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
236
|
+
if (path)
|
|
237
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
238
|
+
return path;
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function mergeHeaders(headers, authToken) {
|
|
244
|
+
const merged = new Headers(headers);
|
|
245
|
+
if (authToken) {
|
|
246
|
+
merged.set("authorization", `Bearer ${authToken}`);
|
|
247
|
+
}
|
|
248
|
+
return merged;
|
|
249
|
+
}
|
|
250
|
+
function diagnosticMessage(payload) {
|
|
251
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
252
|
+
return null;
|
|
253
|
+
const record = payload;
|
|
254
|
+
const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
|
|
255
|
+
const messages = diagnostics.flatMap((entry) => {
|
|
256
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
257
|
+
return [];
|
|
258
|
+
const diagnostic = entry;
|
|
259
|
+
const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
|
|
260
|
+
const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
|
|
261
|
+
return message ? [`${kind}: ${message}`] : [];
|
|
262
|
+
});
|
|
263
|
+
return messages.length > 0 ? messages.join("; ") : null;
|
|
264
|
+
}
|
|
265
|
+
var serverReachabilityCache = new Map;
|
|
266
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
267
|
+
try {
|
|
268
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
269
|
+
headers: mergeHeaders(undefined, authToken),
|
|
270
|
+
signal: AbortSignal.timeout(1500)
|
|
271
|
+
});
|
|
272
|
+
return response.ok;
|
|
273
|
+
} catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
278
|
+
const key = resolve2(projectRoot);
|
|
279
|
+
const cached = serverReachabilityCache.get(key);
|
|
280
|
+
if (cached)
|
|
281
|
+
return cached;
|
|
282
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
283
|
+
serverReachabilityCache.set(key, probe);
|
|
284
|
+
return probe;
|
|
285
|
+
}
|
|
286
|
+
function describeSelectedServer(projectRoot, server) {
|
|
287
|
+
try {
|
|
288
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
289
|
+
if (selected) {
|
|
290
|
+
return {
|
|
291
|
+
alias: selected.alias,
|
|
292
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
} catch {}
|
|
296
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
297
|
+
}
|
|
298
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
299
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
300
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
301
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
302
|
+
return {
|
|
303
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
304
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async function requestServerJson(context, pathname, init = {}) {
|
|
308
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
309
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
310
|
+
if (server.serverProjectRoot)
|
|
311
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
312
|
+
let response;
|
|
313
|
+
try {
|
|
314
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
315
|
+
...init,
|
|
316
|
+
headers
|
|
317
|
+
});
|
|
318
|
+
} catch (error) {
|
|
319
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
320
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
321
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
322
|
+
}
|
|
323
|
+
const text = await response.text();
|
|
324
|
+
const payload = text.trim().length > 0 ? (() => {
|
|
325
|
+
try {
|
|
326
|
+
return JSON.parse(text);
|
|
327
|
+
} catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
})() : null;
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
const diagnostics = diagnosticMessage(payload);
|
|
333
|
+
const detail = diagnostics ?? (text || response.statusText);
|
|
334
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
335
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
336
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
337
|
+
}
|
|
338
|
+
return payload;
|
|
339
|
+
}
|
|
52
340
|
|
|
53
341
|
// packages/cli/src/commands/_cli-format.ts
|
|
54
342
|
import { log, note } from "@clack/prompts";
|
|
@@ -512,90 +800,6 @@ function parseSinceOption(value, now = new Date) {
|
|
|
512
800
|
}
|
|
513
801
|
return new Date(parsed);
|
|
514
802
|
}
|
|
515
|
-
function median(values) {
|
|
516
|
-
if (values.length === 0)
|
|
517
|
-
return null;
|
|
518
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
519
|
-
const middle = Math.floor(sorted.length / 2);
|
|
520
|
-
const upper = sorted[middle];
|
|
521
|
-
if (sorted.length % 2 === 1)
|
|
522
|
-
return upper;
|
|
523
|
-
return (sorted[middle - 1] + upper) / 2;
|
|
524
|
-
}
|
|
525
|
-
function rate(part, total) {
|
|
526
|
-
if (total === 0)
|
|
527
|
-
return null;
|
|
528
|
-
return part / total;
|
|
529
|
-
}
|
|
530
|
-
function parseTimestamp(value) {
|
|
531
|
-
if (!value)
|
|
532
|
-
return null;
|
|
533
|
-
const parsed = Date.parse(value);
|
|
534
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
535
|
-
}
|
|
536
|
-
function computeRigStats(projectRoot, options = {}) {
|
|
537
|
-
const since = options.since ?? null;
|
|
538
|
-
const sinceMs = since ? since.getTime() : null;
|
|
539
|
-
const runs = listAuthorityRuns(projectRoot).filter((run) => {
|
|
540
|
-
if (sinceMs === null)
|
|
541
|
-
return true;
|
|
542
|
-
const createdAt = parseTimestamp(run.createdAt) ?? parseTimestamp(run.updatedAt);
|
|
543
|
-
return createdAt !== null && createdAt >= sinceMs;
|
|
544
|
-
});
|
|
545
|
-
const statusCounts = {};
|
|
546
|
-
const completionDurations = [];
|
|
547
|
-
let steeringTotal = 0;
|
|
548
|
-
let stallTotal = 0;
|
|
549
|
-
let approvalsApproved = 0;
|
|
550
|
-
let approvalsRejected = 0;
|
|
551
|
-
let approvalsPending = 0;
|
|
552
|
-
for (const run of runs) {
|
|
553
|
-
const status = run.status ?? "unknown";
|
|
554
|
-
statusCounts[status] = (statusCounts[status] ?? 0) + 1;
|
|
555
|
-
if (status === "completed") {
|
|
556
|
-
const createdAt = parseTimestamp(run.createdAt);
|
|
557
|
-
const completedAt = parseTimestamp(run.completedAt);
|
|
558
|
-
if (createdAt !== null && completedAt !== null && completedAt >= createdAt) {
|
|
559
|
-
completionDurations.push(completedAt - createdAt);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
const projection = projectRunFromJournal(projectRoot, run.runId);
|
|
563
|
-
if (!projection)
|
|
564
|
-
continue;
|
|
565
|
-
steeringTotal += projection.steeringCount;
|
|
566
|
-
stallTotal += projection.stallCount;
|
|
567
|
-
approvalsPending += projection.pendingApprovals.length;
|
|
568
|
-
for (const resolved of projection.resolvedApprovals) {
|
|
569
|
-
if (resolved.decision === "approve")
|
|
570
|
-
approvalsApproved += 1;
|
|
571
|
-
else
|
|
572
|
-
approvalsRejected += 1;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
const totalRuns = runs.length;
|
|
576
|
-
const completedRuns = statusCounts["completed"] ?? 0;
|
|
577
|
-
const failedRuns = statusCounts["failed"] ?? 0;
|
|
578
|
-
const needsAttentionRuns = statusCounts["needs-attention"] ?? 0;
|
|
579
|
-
return {
|
|
580
|
-
since: since ? since.toISOString() : null,
|
|
581
|
-
totalRuns,
|
|
582
|
-
statusCounts,
|
|
583
|
-
completedRuns,
|
|
584
|
-
failedRuns,
|
|
585
|
-
needsAttentionRuns,
|
|
586
|
-
completionRate: rate(completedRuns, totalRuns),
|
|
587
|
-
failureRate: rate(failedRuns, totalRuns),
|
|
588
|
-
needsAttentionRate: rate(needsAttentionRuns, totalRuns),
|
|
589
|
-
medianCompletionMs: median(completionDurations),
|
|
590
|
-
steeringTotal,
|
|
591
|
-
steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
|
|
592
|
-
stallTotal,
|
|
593
|
-
approvalsRequested: approvalsApproved + approvalsRejected + approvalsPending,
|
|
594
|
-
approvalsApproved,
|
|
595
|
-
approvalsRejected,
|
|
596
|
-
approvalsPending
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
803
|
function formatPercent(value) {
|
|
600
804
|
if (value === null)
|
|
601
805
|
return "\u2014";
|
|
@@ -653,7 +857,7 @@ async function executeStats(context, args) {
|
|
|
653
857
|
const sinceResult = takeOption(pending, "--since");
|
|
654
858
|
requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
|
|
655
859
|
const since = parseSinceOption(sinceResult.value);
|
|
656
|
-
const stats = computeRigStats(context.projectRoot, { since });
|
|
860
|
+
const stats = isRemoteConnectionSelected(context.projectRoot) ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since });
|
|
657
861
|
if (context.outputMode === "text") {
|
|
658
862
|
printFormattedOutput(formatStatsReport(stats));
|
|
659
863
|
}
|
package/dist/src/commands.js
CHANGED
|
@@ -2730,6 +2730,9 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
2730
2730
|
return;
|
|
2731
2731
|
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
2732
2732
|
}
|
|
2733
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
2734
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
2735
|
+
}
|
|
2733
2736
|
|
|
2734
2737
|
// packages/cli/src/commands/_server-client.ts
|
|
2735
2738
|
init_runner();
|
|
@@ -7656,9 +7659,6 @@ function normalizeRemoteRunDetails(payload) {
|
|
|
7656
7659
|
};
|
|
7657
7660
|
}
|
|
7658
7661
|
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
7659
|
-
function isRemoteConnectionSelected(projectRoot) {
|
|
7660
|
-
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
7661
|
-
}
|
|
7662
7662
|
async function listRunsForSelectedConnection(context, options = {}) {
|
|
7663
7663
|
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
7664
7664
|
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
@@ -8008,28 +8008,38 @@ async function executeRun(context, args) {
|
|
|
8008
8008
|
};
|
|
8009
8009
|
}
|
|
8010
8010
|
case "resume": {
|
|
8011
|
-
|
|
8011
|
+
let pending = rest;
|
|
8012
|
+
const runOpt = takeOption(pending, "--run");
|
|
8013
|
+
pending = runOpt.rest;
|
|
8014
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
8015
|
+
requireNoExtraArgs(pending, "rig run resume [<run-id>] [--run <id>]");
|
|
8016
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
8012
8017
|
if (context.dryRun) {
|
|
8013
8018
|
if (context.outputMode === "text") {
|
|
8014
8019
|
console.log("[dry-run] rig run resume");
|
|
8015
8020
|
}
|
|
8016
8021
|
return { ok: true, group: "run", command };
|
|
8017
8022
|
}
|
|
8018
|
-
const resumed = await runResume(context.projectRoot, runtimeContext);
|
|
8023
|
+
const resumed = await runResume(context.projectRoot, runtimeContext, targetRunId);
|
|
8019
8024
|
if (context.outputMode === "text") {
|
|
8020
8025
|
console.log(`Resumed run: ${resumed.runId}`);
|
|
8021
8026
|
}
|
|
8022
8027
|
return { ok: true, group: "run", command, details: resumed };
|
|
8023
8028
|
}
|
|
8024
8029
|
case "restart": {
|
|
8025
|
-
|
|
8030
|
+
let pending = rest;
|
|
8031
|
+
const runOpt = takeOption(pending, "--run");
|
|
8032
|
+
pending = runOpt.rest;
|
|
8033
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
8034
|
+
requireNoExtraArgs(pending, "rig run restart [<run-id>] [--run <id>]");
|
|
8035
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
8026
8036
|
if (context.dryRun) {
|
|
8027
8037
|
if (context.outputMode === "text") {
|
|
8028
8038
|
console.log("[dry-run] rig run restart");
|
|
8029
8039
|
}
|
|
8030
8040
|
return { ok: true, group: "run", command };
|
|
8031
8041
|
}
|
|
8032
|
-
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
8042
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext, targetRunId);
|
|
8033
8043
|
if (context.outputMode === "text") {
|
|
8034
8044
|
console.log(`Restarted run: ${restarted.runId}`);
|
|
8035
8045
|
}
|
|
@@ -8324,10 +8334,7 @@ async function executeServer(context, args, options) {
|
|
|
8324
8334
|
// packages/cli/src/commands/stats.ts
|
|
8325
8335
|
init_runner();
|
|
8326
8336
|
import pc5 from "picocolors";
|
|
8327
|
-
import {
|
|
8328
|
-
listAuthorityRuns as listAuthorityRuns3,
|
|
8329
|
-
projectRunFromJournal
|
|
8330
|
-
} from "@rig/runtime/control-plane/authority-files";
|
|
8337
|
+
import { computeRigStats } from "@rig/runtime/control-plane/native/run-stats";
|
|
8331
8338
|
|
|
8332
8339
|
// packages/cli/src/commands/_help-catalog.ts
|
|
8333
8340
|
import { intro as intro3, log as log4, note as note4, outro as outro3 } from "@clack/prompts";
|
|
@@ -8816,90 +8823,6 @@ function parseSinceOption(value, now = new Date) {
|
|
|
8816
8823
|
}
|
|
8817
8824
|
return new Date(parsed);
|
|
8818
8825
|
}
|
|
8819
|
-
function median(values) {
|
|
8820
|
-
if (values.length === 0)
|
|
8821
|
-
return null;
|
|
8822
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
8823
|
-
const middle = Math.floor(sorted.length / 2);
|
|
8824
|
-
const upper = sorted[middle];
|
|
8825
|
-
if (sorted.length % 2 === 1)
|
|
8826
|
-
return upper;
|
|
8827
|
-
return (sorted[middle - 1] + upper) / 2;
|
|
8828
|
-
}
|
|
8829
|
-
function rate(part, total) {
|
|
8830
|
-
if (total === 0)
|
|
8831
|
-
return null;
|
|
8832
|
-
return part / total;
|
|
8833
|
-
}
|
|
8834
|
-
function parseTimestamp(value) {
|
|
8835
|
-
if (!value)
|
|
8836
|
-
return null;
|
|
8837
|
-
const parsed = Date.parse(value);
|
|
8838
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
8839
|
-
}
|
|
8840
|
-
function computeRigStats(projectRoot, options = {}) {
|
|
8841
|
-
const since = options.since ?? null;
|
|
8842
|
-
const sinceMs = since ? since.getTime() : null;
|
|
8843
|
-
const runs = listAuthorityRuns3(projectRoot).filter((run) => {
|
|
8844
|
-
if (sinceMs === null)
|
|
8845
|
-
return true;
|
|
8846
|
-
const createdAt = parseTimestamp(run.createdAt) ?? parseTimestamp(run.updatedAt);
|
|
8847
|
-
return createdAt !== null && createdAt >= sinceMs;
|
|
8848
|
-
});
|
|
8849
|
-
const statusCounts = {};
|
|
8850
|
-
const completionDurations = [];
|
|
8851
|
-
let steeringTotal = 0;
|
|
8852
|
-
let stallTotal = 0;
|
|
8853
|
-
let approvalsApproved = 0;
|
|
8854
|
-
let approvalsRejected = 0;
|
|
8855
|
-
let approvalsPending = 0;
|
|
8856
|
-
for (const run of runs) {
|
|
8857
|
-
const status = run.status ?? "unknown";
|
|
8858
|
-
statusCounts[status] = (statusCounts[status] ?? 0) + 1;
|
|
8859
|
-
if (status === "completed") {
|
|
8860
|
-
const createdAt = parseTimestamp(run.createdAt);
|
|
8861
|
-
const completedAt = parseTimestamp(run.completedAt);
|
|
8862
|
-
if (createdAt !== null && completedAt !== null && completedAt >= createdAt) {
|
|
8863
|
-
completionDurations.push(completedAt - createdAt);
|
|
8864
|
-
}
|
|
8865
|
-
}
|
|
8866
|
-
const projection = projectRunFromJournal(projectRoot, run.runId);
|
|
8867
|
-
if (!projection)
|
|
8868
|
-
continue;
|
|
8869
|
-
steeringTotal += projection.steeringCount;
|
|
8870
|
-
stallTotal += projection.stallCount;
|
|
8871
|
-
approvalsPending += projection.pendingApprovals.length;
|
|
8872
|
-
for (const resolved of projection.resolvedApprovals) {
|
|
8873
|
-
if (resolved.decision === "approve")
|
|
8874
|
-
approvalsApproved += 1;
|
|
8875
|
-
else
|
|
8876
|
-
approvalsRejected += 1;
|
|
8877
|
-
}
|
|
8878
|
-
}
|
|
8879
|
-
const totalRuns = runs.length;
|
|
8880
|
-
const completedRuns = statusCounts["completed"] ?? 0;
|
|
8881
|
-
const failedRuns = statusCounts["failed"] ?? 0;
|
|
8882
|
-
const needsAttentionRuns = statusCounts["needs-attention"] ?? 0;
|
|
8883
|
-
return {
|
|
8884
|
-
since: since ? since.toISOString() : null,
|
|
8885
|
-
totalRuns,
|
|
8886
|
-
statusCounts,
|
|
8887
|
-
completedRuns,
|
|
8888
|
-
failedRuns,
|
|
8889
|
-
needsAttentionRuns,
|
|
8890
|
-
completionRate: rate(completedRuns, totalRuns),
|
|
8891
|
-
failureRate: rate(failedRuns, totalRuns),
|
|
8892
|
-
needsAttentionRate: rate(needsAttentionRuns, totalRuns),
|
|
8893
|
-
medianCompletionMs: median(completionDurations),
|
|
8894
|
-
steeringTotal,
|
|
8895
|
-
steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
|
|
8896
|
-
stallTotal,
|
|
8897
|
-
approvalsRequested: approvalsApproved + approvalsRejected + approvalsPending,
|
|
8898
|
-
approvalsApproved,
|
|
8899
|
-
approvalsRejected,
|
|
8900
|
-
approvalsPending
|
|
8901
|
-
};
|
|
8902
|
-
}
|
|
8903
8826
|
function formatPercent(value) {
|
|
8904
8827
|
if (value === null)
|
|
8905
8828
|
return "\u2014";
|
|
@@ -8957,7 +8880,7 @@ async function executeStats(context, args) {
|
|
|
8957
8880
|
const sinceResult = takeOption(pending, "--since");
|
|
8958
8881
|
requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
|
|
8959
8882
|
const since = parseSinceOption(sinceResult.value);
|
|
8960
|
-
const stats = computeRigStats(context.projectRoot, { since });
|
|
8883
|
+
const stats = isRemoteConnectionSelected(context.projectRoot) ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since });
|
|
8961
8884
|
if (context.outputMode === "text") {
|
|
8962
8885
|
printFormattedOutput(formatStatsReport(stats));
|
|
8963
8886
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -2919,6 +2919,9 @@ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
|
2919
2919
|
return;
|
|
2920
2920
|
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
2921
2921
|
}
|
|
2922
|
+
function isRemoteConnectionSelected(projectRoot) {
|
|
2923
|
+
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
2924
|
+
}
|
|
2922
2925
|
|
|
2923
2926
|
// packages/cli/src/commands/_server-client.ts
|
|
2924
2927
|
init_runner();
|
|
@@ -7845,9 +7848,6 @@ function normalizeRemoteRunDetails(payload) {
|
|
|
7845
7848
|
};
|
|
7846
7849
|
}
|
|
7847
7850
|
var REMOTE_TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged"]);
|
|
7848
|
-
function isRemoteConnectionSelected(projectRoot) {
|
|
7849
|
-
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
7850
|
-
}
|
|
7851
7851
|
async function listRunsForSelectedConnection(context, options = {}) {
|
|
7852
7852
|
if (isRemoteConnectionSelected(context.projectRoot)) {
|
|
7853
7853
|
return { runs: await listRunsViaServer(context, options), source: "server" };
|
|
@@ -8197,28 +8197,38 @@ async function executeRun(context, args) {
|
|
|
8197
8197
|
};
|
|
8198
8198
|
}
|
|
8199
8199
|
case "resume": {
|
|
8200
|
-
|
|
8200
|
+
let pending = rest;
|
|
8201
|
+
const runOpt = takeOption(pending, "--run");
|
|
8202
|
+
pending = runOpt.rest;
|
|
8203
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
8204
|
+
requireNoExtraArgs(pending, "rig run resume [<run-id>] [--run <id>]");
|
|
8205
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
8201
8206
|
if (context.dryRun) {
|
|
8202
8207
|
if (context.outputMode === "text") {
|
|
8203
8208
|
console.log("[dry-run] rig run resume");
|
|
8204
8209
|
}
|
|
8205
8210
|
return { ok: true, group: "run", command };
|
|
8206
8211
|
}
|
|
8207
|
-
const resumed = await runResume(context.projectRoot, runtimeContext);
|
|
8212
|
+
const resumed = await runResume(context.projectRoot, runtimeContext, targetRunId);
|
|
8208
8213
|
if (context.outputMode === "text") {
|
|
8209
8214
|
console.log(`Resumed run: ${resumed.runId}`);
|
|
8210
8215
|
}
|
|
8211
8216
|
return { ok: true, group: "run", command, details: resumed };
|
|
8212
8217
|
}
|
|
8213
8218
|
case "restart": {
|
|
8214
|
-
|
|
8219
|
+
let pending = rest;
|
|
8220
|
+
const runOpt = takeOption(pending, "--run");
|
|
8221
|
+
pending = runOpt.rest;
|
|
8222
|
+
const positional = pending[0] && !pending[0].startsWith("-") ? pending.shift() : undefined;
|
|
8223
|
+
requireNoExtraArgs(pending, "rig run restart [<run-id>] [--run <id>]");
|
|
8224
|
+
const targetRunId = runOpt.value ?? positional ?? null;
|
|
8215
8225
|
if (context.dryRun) {
|
|
8216
8226
|
if (context.outputMode === "text") {
|
|
8217
8227
|
console.log("[dry-run] rig run restart");
|
|
8218
8228
|
}
|
|
8219
8229
|
return { ok: true, group: "run", command };
|
|
8220
8230
|
}
|
|
8221
|
-
const restarted = await runRestart(context.projectRoot, runtimeContext);
|
|
8231
|
+
const restarted = await runRestart(context.projectRoot, runtimeContext, targetRunId);
|
|
8222
8232
|
if (context.outputMode === "text") {
|
|
8223
8233
|
console.log(`Restarted run: ${restarted.runId}`);
|
|
8224
8234
|
}
|
|
@@ -8513,10 +8523,7 @@ async function executeServer(context, args, options) {
|
|
|
8513
8523
|
// packages/cli/src/commands/stats.ts
|
|
8514
8524
|
init_runner();
|
|
8515
8525
|
import pc5 from "picocolors";
|
|
8516
|
-
import {
|
|
8517
|
-
listAuthorityRuns as listAuthorityRuns3,
|
|
8518
|
-
projectRunFromJournal
|
|
8519
|
-
} from "@rig/runtime/control-plane/authority-files";
|
|
8526
|
+
import { computeRigStats } from "@rig/runtime/control-plane/native/run-stats";
|
|
8520
8527
|
|
|
8521
8528
|
// packages/cli/src/commands/_help-catalog.ts
|
|
8522
8529
|
import { intro as intro3, log as log4, note as note4, outro as outro3 } from "@clack/prompts";
|
|
@@ -9005,90 +9012,6 @@ function parseSinceOption(value, now = new Date) {
|
|
|
9005
9012
|
}
|
|
9006
9013
|
return new Date(parsed);
|
|
9007
9014
|
}
|
|
9008
|
-
function median(values) {
|
|
9009
|
-
if (values.length === 0)
|
|
9010
|
-
return null;
|
|
9011
|
-
const sorted = [...values].sort((left, right) => left - right);
|
|
9012
|
-
const middle = Math.floor(sorted.length / 2);
|
|
9013
|
-
const upper = sorted[middle];
|
|
9014
|
-
if (sorted.length % 2 === 1)
|
|
9015
|
-
return upper;
|
|
9016
|
-
return (sorted[middle - 1] + upper) / 2;
|
|
9017
|
-
}
|
|
9018
|
-
function rate(part, total) {
|
|
9019
|
-
if (total === 0)
|
|
9020
|
-
return null;
|
|
9021
|
-
return part / total;
|
|
9022
|
-
}
|
|
9023
|
-
function parseTimestamp(value) {
|
|
9024
|
-
if (!value)
|
|
9025
|
-
return null;
|
|
9026
|
-
const parsed = Date.parse(value);
|
|
9027
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
9028
|
-
}
|
|
9029
|
-
function computeRigStats(projectRoot, options = {}) {
|
|
9030
|
-
const since = options.since ?? null;
|
|
9031
|
-
const sinceMs = since ? since.getTime() : null;
|
|
9032
|
-
const runs = listAuthorityRuns3(projectRoot).filter((run) => {
|
|
9033
|
-
if (sinceMs === null)
|
|
9034
|
-
return true;
|
|
9035
|
-
const createdAt = parseTimestamp(run.createdAt) ?? parseTimestamp(run.updatedAt);
|
|
9036
|
-
return createdAt !== null && createdAt >= sinceMs;
|
|
9037
|
-
});
|
|
9038
|
-
const statusCounts = {};
|
|
9039
|
-
const completionDurations = [];
|
|
9040
|
-
let steeringTotal = 0;
|
|
9041
|
-
let stallTotal = 0;
|
|
9042
|
-
let approvalsApproved = 0;
|
|
9043
|
-
let approvalsRejected = 0;
|
|
9044
|
-
let approvalsPending = 0;
|
|
9045
|
-
for (const run of runs) {
|
|
9046
|
-
const status = run.status ?? "unknown";
|
|
9047
|
-
statusCounts[status] = (statusCounts[status] ?? 0) + 1;
|
|
9048
|
-
if (status === "completed") {
|
|
9049
|
-
const createdAt = parseTimestamp(run.createdAt);
|
|
9050
|
-
const completedAt = parseTimestamp(run.completedAt);
|
|
9051
|
-
if (createdAt !== null && completedAt !== null && completedAt >= createdAt) {
|
|
9052
|
-
completionDurations.push(completedAt - createdAt);
|
|
9053
|
-
}
|
|
9054
|
-
}
|
|
9055
|
-
const projection = projectRunFromJournal(projectRoot, run.runId);
|
|
9056
|
-
if (!projection)
|
|
9057
|
-
continue;
|
|
9058
|
-
steeringTotal += projection.steeringCount;
|
|
9059
|
-
stallTotal += projection.stallCount;
|
|
9060
|
-
approvalsPending += projection.pendingApprovals.length;
|
|
9061
|
-
for (const resolved of projection.resolvedApprovals) {
|
|
9062
|
-
if (resolved.decision === "approve")
|
|
9063
|
-
approvalsApproved += 1;
|
|
9064
|
-
else
|
|
9065
|
-
approvalsRejected += 1;
|
|
9066
|
-
}
|
|
9067
|
-
}
|
|
9068
|
-
const totalRuns = runs.length;
|
|
9069
|
-
const completedRuns = statusCounts["completed"] ?? 0;
|
|
9070
|
-
const failedRuns = statusCounts["failed"] ?? 0;
|
|
9071
|
-
const needsAttentionRuns = statusCounts["needs-attention"] ?? 0;
|
|
9072
|
-
return {
|
|
9073
|
-
since: since ? since.toISOString() : null,
|
|
9074
|
-
totalRuns,
|
|
9075
|
-
statusCounts,
|
|
9076
|
-
completedRuns,
|
|
9077
|
-
failedRuns,
|
|
9078
|
-
needsAttentionRuns,
|
|
9079
|
-
completionRate: rate(completedRuns, totalRuns),
|
|
9080
|
-
failureRate: rate(failedRuns, totalRuns),
|
|
9081
|
-
needsAttentionRate: rate(needsAttentionRuns, totalRuns),
|
|
9082
|
-
medianCompletionMs: median(completionDurations),
|
|
9083
|
-
steeringTotal,
|
|
9084
|
-
steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
|
|
9085
|
-
stallTotal,
|
|
9086
|
-
approvalsRequested: approvalsApproved + approvalsRejected + approvalsPending,
|
|
9087
|
-
approvalsApproved,
|
|
9088
|
-
approvalsRejected,
|
|
9089
|
-
approvalsPending
|
|
9090
|
-
};
|
|
9091
|
-
}
|
|
9092
9015
|
function formatPercent(value) {
|
|
9093
9016
|
if (value === null)
|
|
9094
9017
|
return "\u2014";
|
|
@@ -9146,7 +9069,7 @@ async function executeStats(context, args) {
|
|
|
9146
9069
|
const sinceResult = takeOption(pending, "--since");
|
|
9147
9070
|
requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
|
|
9148
9071
|
const since = parseSinceOption(sinceResult.value);
|
|
9149
|
-
const stats = computeRigStats(context.projectRoot, { since });
|
|
9072
|
+
const stats = isRemoteConnectionSelected(context.projectRoot) ? await requestServerJson(context, `/api/stats${since ? `?since=${encodeURIComponent(since.toISOString())}` : ""}`) : computeRigStats(context.projectRoot, { since });
|
|
9150
9073
|
if (context.outputMode === "text") {
|
|
9151
9074
|
printFormattedOutput(formatStatsReport(stats));
|
|
9152
9075
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-rig/cli",
|
|
3
|
-
"version": "0.0.6-alpha.
|
|
3
|
+
"version": "0.0.6-alpha.67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rig package",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@clack/prompts": "^1.2.0",
|
|
26
26
|
"@earendil-works/pi-coding-agent": "0.79.0",
|
|
27
|
-
"@rig/client": "npm:@h-rig/client@0.0.6-alpha.
|
|
28
|
-
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.
|
|
29
|
-
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.
|
|
30
|
-
"@rig/pi-rig": "npm:@h-rig/pi-rig@0.0.6-alpha.
|
|
31
|
-
"@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.
|
|
32
|
-
"@rig/server": "npm:@h-rig/server@0.0.6-alpha.
|
|
33
|
-
"@rig/standard-plugin": "npm:@h-rig/standard-plugin@0.0.6-alpha.
|
|
27
|
+
"@rig/client": "npm:@h-rig/client@0.0.6-alpha.67",
|
|
28
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.67",
|
|
29
|
+
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.67",
|
|
30
|
+
"@rig/pi-rig": "npm:@h-rig/pi-rig@0.0.6-alpha.67",
|
|
31
|
+
"@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.67",
|
|
32
|
+
"@rig/server": "npm:@h-rig/server@0.0.6-alpha.67",
|
|
33
|
+
"@rig/standard-plugin": "npm:@h-rig/standard-plugin@0.0.6-alpha.67",
|
|
34
34
|
"effect": "4.0.0-beta.78",
|
|
35
35
|
"picocolors": "^1.1.1"
|
|
36
36
|
}
|