@h-rig/cli 0.0.6-alpha.0
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/README.md +30 -0
- package/dist/bin/build-rig-binaries.js +107 -0
- package/dist/bin/rig.js +9330 -0
- package/dist/src/commands/_authority-runs.js +110 -0
- package/dist/src/commands/_connection-state.js +123 -0
- package/dist/src/commands/_doctor-checks.js +501 -0
- package/dist/src/commands/_operator-view.js +322 -0
- package/dist/src/commands/_parsers.js +107 -0
- package/dist/src/commands/_paths.js +50 -0
- package/dist/src/commands/_pi-install.js +184 -0
- package/dist/src/commands/_policy.js +79 -0
- package/dist/src/commands/_preflight.js +460 -0
- package/dist/src/commands/_probes.js +13 -0
- package/dist/src/commands/_run-driver-helpers.js +289 -0
- package/dist/src/commands/_server-client.js +364 -0
- package/dist/src/commands/_snapshot-upload.js +313 -0
- package/dist/src/commands/_task-picker.js +48 -0
- package/dist/src/commands/agent.js +497 -0
- package/dist/src/commands/browser.js +890 -0
- package/dist/src/commands/connect.js +180 -0
- package/dist/src/commands/dist.js +402 -0
- package/dist/src/commands/doctor.js +511 -0
- package/dist/src/commands/github.js +276 -0
- package/dist/src/commands/inbox.js +160 -0
- package/dist/src/commands/init.js +1254 -0
- package/dist/src/commands/inspect.js +174 -0
- package/dist/src/commands/inspector.js +256 -0
- package/dist/src/commands/plugin.js +167 -0
- package/dist/src/commands/profile-and-review.js +178 -0
- package/dist/src/commands/queue.js +197 -0
- package/dist/src/commands/remote.js +507 -0
- package/dist/src/commands/repo-git-harness.js +221 -0
- package/dist/src/commands/run.js +753 -0
- package/dist/src/commands/server.js +368 -0
- package/dist/src/commands/setup.js +681 -0
- package/dist/src/commands/task-report-bug.js +1083 -0
- package/dist/src/commands/task-run-driver.js +1933 -0
- package/dist/src/commands/task.js +1325 -0
- package/dist/src/commands/test.js +39 -0
- package/dist/src/commands/workspace.js +123 -0
- package/dist/src/commands.js +9012 -0
- package/dist/src/index.js +9348 -0
- package/dist/src/launcher.js +131 -0
- package/dist/src/report-bug.js +260 -0
- package/dist/src/runner.js +272 -0
- package/dist/src/withMutedConsole.js +42 -0
- package/package.json +31 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/launcher.ts
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
import { loadDotEnvSecrets } from "@rig/runtime/baked-secrets";
|
|
6
|
+
import { RIG_DEFINITION_DIRNAME, RIG_STATE_DIRNAME, resolveNearestRigProjectRoot } from "@rig/runtime/layout";
|
|
7
|
+
function parsePolicyMode(value) {
|
|
8
|
+
if (!value) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (value === "off" || value === "observe" || value === "enforce") {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Invalid --policy-mode value: ${value}. Use off|observe|enforce.`);
|
|
15
|
+
}
|
|
16
|
+
function hasRigProjectMarker(candidate) {
|
|
17
|
+
return existsSync(resolve(candidate, RIG_DEFINITION_DIRNAME)) || existsSync(resolve(candidate, RIG_STATE_DIRNAME)) || existsSync(resolve(candidate, "rig.config.ts")) || existsSync(resolve(candidate, "rig.config.json"));
|
|
18
|
+
}
|
|
19
|
+
function resolveProjectRoot({
|
|
20
|
+
envProjectRoot,
|
|
21
|
+
execPath = process.execPath,
|
|
22
|
+
importDir,
|
|
23
|
+
cwd = process.cwd()
|
|
24
|
+
}) {
|
|
25
|
+
if (envProjectRoot) {
|
|
26
|
+
return resolve(cwd, envProjectRoot);
|
|
27
|
+
}
|
|
28
|
+
const fallbackImportDir = importDir ?? cwd;
|
|
29
|
+
const candidates = [cwd, resolve(execPath, "..", ".."), resolve(fallbackImportDir, "..")];
|
|
30
|
+
for (const candidate of candidates) {
|
|
31
|
+
const nearest = resolveNearestRigProjectRoot(candidate);
|
|
32
|
+
if (hasRigProjectMarker(nearest)) {
|
|
33
|
+
return nearest;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return resolve(cwd);
|
|
37
|
+
}
|
|
38
|
+
function normalizeCliErrorCode(message, isCliError) {
|
|
39
|
+
if (message.startsWith("Invalid --policy-mode value:")) {
|
|
40
|
+
return "INVALID_POLICY_MODE";
|
|
41
|
+
}
|
|
42
|
+
if (message.startsWith("Missing value for --")) {
|
|
43
|
+
return "MISSING_OPTION_VALUE";
|
|
44
|
+
}
|
|
45
|
+
return isCliError ? "CLI_ERROR" : "UNEXPECTED_ERROR";
|
|
46
|
+
}
|
|
47
|
+
function normalizeCliErrorPayload(error, CliErrorClass, options = {}) {
|
|
48
|
+
const isCliError = error instanceof CliErrorClass;
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
const exitCode = isCliError ? error.exitCode : 1;
|
|
51
|
+
const payload = {
|
|
52
|
+
ok: false,
|
|
53
|
+
code: normalizeCliErrorCode(message, isCliError),
|
|
54
|
+
message,
|
|
55
|
+
exitCode
|
|
56
|
+
};
|
|
57
|
+
if (options.debug && error instanceof Error && error.stack) {
|
|
58
|
+
return { ...payload, stack: error.stack };
|
|
59
|
+
}
|
|
60
|
+
return payload;
|
|
61
|
+
}
|
|
62
|
+
async function runRigCli(module, options = {}) {
|
|
63
|
+
const io = options.io ?? {
|
|
64
|
+
log: (message) => console.log(message),
|
|
65
|
+
error: (message) => console.error(message)
|
|
66
|
+
};
|
|
67
|
+
const args = [...options.argv ?? process.argv.slice(2)];
|
|
68
|
+
const debugErrors = process.env.RIG_DEBUG === "1" || process.env.RIG_CLI_DEBUG === "1";
|
|
69
|
+
let jsonOutput = args.includes("--json");
|
|
70
|
+
try {
|
|
71
|
+
const dryRunResult = module.takeFlag(args, "--dry-run");
|
|
72
|
+
const jsonResult = module.takeFlag(dryRunResult.rest, "--json");
|
|
73
|
+
jsonOutput = jsonResult.value;
|
|
74
|
+
const projectResult = module.takeOption(jsonResult.rest, "--project");
|
|
75
|
+
const cwd = options.cwd ?? process.cwd();
|
|
76
|
+
const projectRoot = resolveProjectRoot(Object.fromEntries(Object.entries({
|
|
77
|
+
envProjectRoot: projectResult.value ?? options.envProjectRoot ?? process.env.PROJECT_RIG_ROOT,
|
|
78
|
+
execPath: options.execPath ?? process.execPath,
|
|
79
|
+
importDir: options.importDir,
|
|
80
|
+
cwd
|
|
81
|
+
}).filter(([, value]) => value !== undefined)));
|
|
82
|
+
hydrateProcessEnvFromDotEnv(projectRoot);
|
|
83
|
+
module.withProjectRoot(projectRoot);
|
|
84
|
+
const policyModeResult = module.takeOption(projectResult.rest, "--policy-mode");
|
|
85
|
+
const runIdResult = module.takeOption(policyModeResult.rest, "--run-id");
|
|
86
|
+
const policyMode = parsePolicyMode(policyModeResult.value);
|
|
87
|
+
const outputMode = jsonResult.value ? "json" : "text";
|
|
88
|
+
const runtimeOptions = {
|
|
89
|
+
projectRoot,
|
|
90
|
+
dryRun: dryRunResult.value,
|
|
91
|
+
outputMode,
|
|
92
|
+
runId: runIdResult.value,
|
|
93
|
+
policyMode
|
|
94
|
+
};
|
|
95
|
+
const context = await module.initializeRuntime(runtimeOptions);
|
|
96
|
+
const outcome = await module.execute(context, runIdResult.rest);
|
|
97
|
+
if (outputMode === "json") {
|
|
98
|
+
io.log(JSON.stringify({
|
|
99
|
+
ok: true,
|
|
100
|
+
runId: context.runId,
|
|
101
|
+
outcome,
|
|
102
|
+
eventsFile: context.eventBus.getEventsFile(),
|
|
103
|
+
policyFile: resolve(projectRoot, "rig", "policy", "policy.json"),
|
|
104
|
+
policyMode: context.policyMode ?? policyMode ?? module.loadPolicy(projectRoot).mode
|
|
105
|
+
}, null, 2));
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const payload = normalizeCliErrorPayload(error, module.CliError, { debug: debugErrors });
|
|
109
|
+
if (jsonOutput) {
|
|
110
|
+
io.error(JSON.stringify(payload, null, 2));
|
|
111
|
+
} else {
|
|
112
|
+
io.error(debugErrors && payload.stack ? payload.stack : payload.message);
|
|
113
|
+
}
|
|
114
|
+
process.exit(payload.exitCode);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function hydrateProcessEnvFromDotEnv(projectRoot) {
|
|
118
|
+
const secrets = loadDotEnvSecrets(projectRoot, process.env);
|
|
119
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
120
|
+
if (value && !process.env[key]) {
|
|
121
|
+
process.env[key] = value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
runRigCli,
|
|
127
|
+
resolveProjectRoot,
|
|
128
|
+
parsePolicyMode,
|
|
129
|
+
normalizeCliErrorPayload,
|
|
130
|
+
hydrateProcessEnvFromDotEnv
|
|
131
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/report-bug.ts
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
4
|
+
import { basename, extname, join, resolve } from "path";
|
|
5
|
+
function slugifyBugTitle(value) {
|
|
6
|
+
const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
|
|
7
|
+
return slug || "bug-report";
|
|
8
|
+
}
|
|
9
|
+
function defaultBrowserBugProfile(title) {
|
|
10
|
+
return `hp-next-${slugifyBugTitle(title)}`;
|
|
11
|
+
}
|
|
12
|
+
function defaultBrowserBugOptions(environment) {
|
|
13
|
+
if (environment === "shared-dev") {
|
|
14
|
+
return {
|
|
15
|
+
preset: "hp-next-shared",
|
|
16
|
+
attachUrl: "http://127.0.0.1:9341",
|
|
17
|
+
stateDir: ".tmp/rig-browser/hp-next-shared"
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
preset: "hp-next-local",
|
|
22
|
+
attachUrl: "http://127.0.0.1:9333",
|
|
23
|
+
stateDir: ".tmp/rig-browser/hp-next"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createBugReportFiles(input) {
|
|
27
|
+
const slug = slugifyBugTitle(input.slug || input.title);
|
|
28
|
+
const outputDir = resolve(input.projectRoot, input.outputRoot);
|
|
29
|
+
const reportDir = resolve(outputDir, slug);
|
|
30
|
+
const assetDir = join(reportDir, "assets");
|
|
31
|
+
const screenshotDir = join(reportDir, "screenshots");
|
|
32
|
+
const taskPath = join(reportDir, "task.md");
|
|
33
|
+
const browserRequired = input.browserRequired ?? true;
|
|
34
|
+
const browserPath = browserRequired ? join(reportDir, "browser.json") : null;
|
|
35
|
+
const manifestPath = join(reportDir, "manifest.json");
|
|
36
|
+
if (existsSync(reportDir)) {
|
|
37
|
+
if (!input.overwrite) {
|
|
38
|
+
throw new Error(`Bug report directory already exists: ${reportDir}`);
|
|
39
|
+
}
|
|
40
|
+
rmSync(reportDir, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
mkdirSync(assetDir, { recursive: true });
|
|
43
|
+
mkdirSync(screenshotDir, { recursive: true });
|
|
44
|
+
const copiedScreenshots = copyEvidenceFiles(input.projectRoot, screenshotDir, input.screenshots ?? [], "screenshot");
|
|
45
|
+
const copiedAssets = copyEvidenceFiles(input.projectRoot, assetDir, input.assets ?? [], "asset");
|
|
46
|
+
const manifestAssets = [
|
|
47
|
+
...copiedScreenshots.map((name) => ({
|
|
48
|
+
path: `screenshots/${name}`,
|
|
49
|
+
type: mediaTypeForFileName(name)
|
|
50
|
+
})),
|
|
51
|
+
...copiedAssets.map((name) => ({
|
|
52
|
+
path: `assets/${name}`,
|
|
53
|
+
type: mediaTypeForFileName(name)
|
|
54
|
+
}))
|
|
55
|
+
];
|
|
56
|
+
const browser = browserRequired ? buildBrowserBlock(input) : null;
|
|
57
|
+
const manifest = {
|
|
58
|
+
schema_version: 1,
|
|
59
|
+
slug,
|
|
60
|
+
issue_id: input.issueId ?? null,
|
|
61
|
+
title: input.title,
|
|
62
|
+
environment: input.environment,
|
|
63
|
+
url: input.url,
|
|
64
|
+
viewport: input.viewport,
|
|
65
|
+
task_file: "task.md",
|
|
66
|
+
browser_file: browserRequired ? "browser.json" : null,
|
|
67
|
+
screenshots: copiedScreenshots.map((name) => `screenshots/${name}`),
|
|
68
|
+
assets: manifestAssets,
|
|
69
|
+
created_at: new Date().toISOString(),
|
|
70
|
+
platform: process.platform,
|
|
71
|
+
arch: process.arch
|
|
72
|
+
};
|
|
73
|
+
writeFileSync(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
|
|
74
|
+
writeFileSync(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
|
|
75
|
+
if (browserPath && browser) {
|
|
76
|
+
writeFileSync(browserPath, `${JSON.stringify(browser, null, 2)}
|
|
77
|
+
`, "utf8");
|
|
78
|
+
}
|
|
79
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
80
|
+
`, "utf8");
|
|
81
|
+
writeFileSync(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
|
|
82
|
+
return {
|
|
83
|
+
slug,
|
|
84
|
+
reportDir,
|
|
85
|
+
taskPath,
|
|
86
|
+
browserPath,
|
|
87
|
+
manifestPath,
|
|
88
|
+
assetDir,
|
|
89
|
+
screenshotDir,
|
|
90
|
+
copiedAssets,
|
|
91
|
+
copiedScreenshots
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function buildBrowserBlock(input) {
|
|
95
|
+
return {
|
|
96
|
+
browser: {
|
|
97
|
+
required: true,
|
|
98
|
+
preset: input.preset,
|
|
99
|
+
profile: input.profile,
|
|
100
|
+
attach_url: input.attachUrl,
|
|
101
|
+
state_dir: input.stateDir,
|
|
102
|
+
dev_command: "bun run app:dev:browser:hp-next",
|
|
103
|
+
launch_command: "bun run app:start:browser:hp-next",
|
|
104
|
+
check_command: "bun run app:check:browser:hp-next",
|
|
105
|
+
e2e_command: "bun run app:e2e:browser:hp-next",
|
|
106
|
+
mode: input.mode
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function buildBugReportMarkdown(input, browser, screenshots, assets) {
|
|
111
|
+
const browserRequired = input.browserRequired ?? true;
|
|
112
|
+
const assetEntries = [
|
|
113
|
+
...screenshots.map((name) => ({ name, path: `screenshots/${name}` })),
|
|
114
|
+
...assets.map((name) => ({ name, path: `assets/${name}` }))
|
|
115
|
+
];
|
|
116
|
+
const assetLines = assetEntries.length > 0 ? assetEntries.map(({ name, path }) => formatAssetMarkdown(name, path)) : ["- No assets attached yet. Drag screenshots or videos into `assets/` and link them here."];
|
|
117
|
+
const evidenceLines = input.evidence.length > 0 ? input.evidence.map((item) => `- ${item}`) : ["- Console errors: not captured yet.", "- Failed network requests: not captured yet."];
|
|
118
|
+
return [
|
|
119
|
+
`# ${input.title}`,
|
|
120
|
+
"",
|
|
121
|
+
...input.issueId ? ["## Task", "", `Task ID: \`${input.issueId}\``, ""] : [],
|
|
122
|
+
"## Summary",
|
|
123
|
+
"",
|
|
124
|
+
input.summary || "TODO: describe the user-visible bug.",
|
|
125
|
+
"",
|
|
126
|
+
...browser ? [
|
|
127
|
+
"## Browser Block",
|
|
128
|
+
"",
|
|
129
|
+
"```json",
|
|
130
|
+
JSON.stringify(browser, null, 2),
|
|
131
|
+
"```",
|
|
132
|
+
""
|
|
133
|
+
] : [],
|
|
134
|
+
"## Environment",
|
|
135
|
+
"",
|
|
136
|
+
`- URL: \`${input.url}\``,
|
|
137
|
+
`- Environment: \`${input.environment}\``,
|
|
138
|
+
...browserRequired ? [
|
|
139
|
+
`- Preset: \`${input.preset}\``,
|
|
140
|
+
`- Profile: \`${input.profile}\``,
|
|
141
|
+
`- Viewport: \`${input.viewport}\``
|
|
142
|
+
] : ["- Browser: not required for this report."],
|
|
143
|
+
"",
|
|
144
|
+
"## Reproduction",
|
|
145
|
+
"",
|
|
146
|
+
...numberedLines(input.steps.length > 0 ? input.steps : ["TODO: add reproduction step."]),
|
|
147
|
+
"",
|
|
148
|
+
"## Expected",
|
|
149
|
+
"",
|
|
150
|
+
input.expected || "TODO: expected behavior.",
|
|
151
|
+
"",
|
|
152
|
+
"## Actual",
|
|
153
|
+
"",
|
|
154
|
+
input.actual || "TODO: actual behavior.",
|
|
155
|
+
"",
|
|
156
|
+
"## Assets",
|
|
157
|
+
"",
|
|
158
|
+
...assetLines,
|
|
159
|
+
"",
|
|
160
|
+
"## Evidence",
|
|
161
|
+
"",
|
|
162
|
+
...evidenceLines,
|
|
163
|
+
"",
|
|
164
|
+
"## Agent Handoff",
|
|
165
|
+
"",
|
|
166
|
+
...input.issueId ? [
|
|
167
|
+
`- Canonical task assets live under \`artifacts/${input.issueId}/bug-report/\`.`,
|
|
168
|
+
`- Start with \`artifacts/${input.issueId}/bug-report/task.md\` and the files in \`artifacts/${input.issueId}/bug-report/assets/\`.`,
|
|
169
|
+
browserRequired ? `- Run \`bun run rig task info --task ${input.issueId}\` to confirm browser wiring before debugging.` : `- Run \`bun run rig task info --task ${input.issueId}\` to confirm scope and artifact links before debugging.`
|
|
170
|
+
] : [
|
|
171
|
+
"- Draft-only report: convert this into a beads task before assigning it to an agent run."
|
|
172
|
+
],
|
|
173
|
+
"",
|
|
174
|
+
"## Validation",
|
|
175
|
+
"",
|
|
176
|
+
"```bash",
|
|
177
|
+
...input.issueId ? [`bun run rig task validate --task ${input.issueId}`] : [],
|
|
178
|
+
...browserRequired ? [
|
|
179
|
+
"bun run app:check:browser:hp-next",
|
|
180
|
+
"bun run app:e2e:browser:hp-next"
|
|
181
|
+
] : [],
|
|
182
|
+
"```",
|
|
183
|
+
""
|
|
184
|
+
].join(`
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
function numberedLines(items) {
|
|
188
|
+
return items.map((item, index) => `${index + 1}. ${item}`);
|
|
189
|
+
}
|
|
190
|
+
function buildAssetReadme(title) {
|
|
191
|
+
return [
|
|
192
|
+
`# Evidence Assets For ${title}`,
|
|
193
|
+
"",
|
|
194
|
+
"Drag and drop screenshots or videos for this bug report into this directory.",
|
|
195
|
+
"Use stable, descriptive file names such as `login-loading.png`, `chunk-404-network.png`, or `otp-flow.webm`.",
|
|
196
|
+
"Reference images from `../task.md` with Markdown image links and videos with normal file links.",
|
|
197
|
+
""
|
|
198
|
+
].join(`
|
|
199
|
+
`);
|
|
200
|
+
}
|
|
201
|
+
function buildScreenshotReadme(title) {
|
|
202
|
+
return [
|
|
203
|
+
`# Screenshots For ${title}`,
|
|
204
|
+
"",
|
|
205
|
+
"Drop legacy screenshot files for this bug report into this directory.",
|
|
206
|
+
"Use `assets/` for new screenshot, video, and mixed evidence attachments.",
|
|
207
|
+
"Reference screenshots from `../task.md` with Markdown image links.",
|
|
208
|
+
""
|
|
209
|
+
].join(`
|
|
210
|
+
`);
|
|
211
|
+
}
|
|
212
|
+
function copyEvidenceFiles(projectRoot, targetDir, paths, label) {
|
|
213
|
+
const copied = [];
|
|
214
|
+
const used = new Set;
|
|
215
|
+
for (const rawPath of paths.map((path) => path.trim()).filter(Boolean)) {
|
|
216
|
+
const source = resolve(projectRoot, rawPath);
|
|
217
|
+
if (!existsSync(source)) {
|
|
218
|
+
throw new Error(`${label} does not exist: ${source}`);
|
|
219
|
+
}
|
|
220
|
+
const targetName = uniqueEvidenceName(rawPath, used, label);
|
|
221
|
+
copyFileSync(source, join(targetDir, targetName));
|
|
222
|
+
copied.push(targetName);
|
|
223
|
+
}
|
|
224
|
+
return copied;
|
|
225
|
+
}
|
|
226
|
+
function uniqueEvidenceName(path, used, fallback) {
|
|
227
|
+
const original = sanitizeFileName(basename(path)) || fallback;
|
|
228
|
+
const extension = extname(original);
|
|
229
|
+
const stem = extension ? original.slice(0, -extension.length) : original;
|
|
230
|
+
let candidate = original;
|
|
231
|
+
let index = 2;
|
|
232
|
+
while (used.has(candidate)) {
|
|
233
|
+
candidate = `${stem}-${index}${extension}`;
|
|
234
|
+
index += 1;
|
|
235
|
+
}
|
|
236
|
+
used.add(candidate);
|
|
237
|
+
return candidate;
|
|
238
|
+
}
|
|
239
|
+
function sanitizeFileName(value) {
|
|
240
|
+
return value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
241
|
+
}
|
|
242
|
+
function mediaTypeForFileName(fileName) {
|
|
243
|
+
const extension = extname(fileName).toLowerCase();
|
|
244
|
+
if ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"].includes(extension)) {
|
|
245
|
+
return "image";
|
|
246
|
+
}
|
|
247
|
+
if ([".mp4", ".mov", ".m4v", ".webm", ".avi", ".mkv"].includes(extension)) {
|
|
248
|
+
return "video";
|
|
249
|
+
}
|
|
250
|
+
return "file";
|
|
251
|
+
}
|
|
252
|
+
function formatAssetMarkdown(name, path) {
|
|
253
|
+
return mediaTypeForFileName(name) === "image" ? `- ` : `- [${name}](${path})`;
|
|
254
|
+
}
|
|
255
|
+
export {
|
|
256
|
+
slugifyBugTitle,
|
|
257
|
+
defaultBrowserBugProfile,
|
|
258
|
+
defaultBrowserBugOptions,
|
|
259
|
+
createBugReportFiles
|
|
260
|
+
};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/runner.ts
|
|
3
|
+
var {$ } = globalThis.Bun;
|
|
4
|
+
import { existsSync, mkdirSync } from "fs";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
7
|
+
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
8
|
+
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
9
|
+
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
10
|
+
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
11
|
+
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
12
|
+
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
13
|
+
function withProjectRoot(projectRoot) {
|
|
14
|
+
$.cwd(projectRoot);
|
|
15
|
+
}
|
|
16
|
+
function formatCommand(parts) {
|
|
17
|
+
return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
|
|
18
|
+
}
|
|
19
|
+
var AGENT_DISPATCH_SOURCE = "packages/runtime/bin/rig-agent-dispatch.ts";
|
|
20
|
+
function hasAgentDispatchSource(root) {
|
|
21
|
+
return existsSync(resolve(root, AGENT_DISPATCH_SOURCE));
|
|
22
|
+
}
|
|
23
|
+
function resolveAgentMaterializationSourceRoot(projectRoot, options = {}) {
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
const candidates = [
|
|
26
|
+
env.RIG_HOST_PROJECT_ROOT?.trim(),
|
|
27
|
+
env.PROJECT_RIG_ROOT?.trim(),
|
|
28
|
+
options.cwd ?? process.cwd(),
|
|
29
|
+
resolve(options.moduleDir ?? import.meta.dir, "../../.."),
|
|
30
|
+
projectRoot
|
|
31
|
+
].filter((value) => Boolean(value));
|
|
32
|
+
for (const candidate of candidates) {
|
|
33
|
+
const root = resolve(candidate);
|
|
34
|
+
if (hasAgentDispatchSource(root)) {
|
|
35
|
+
return root;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return resolve(projectRoot);
|
|
39
|
+
}
|
|
40
|
+
async function ensureAgentShellBinary(projectRoot, options = {}) {
|
|
41
|
+
const agentBinary = resolve(projectRoot, ".rig", "bin", "rig-agent");
|
|
42
|
+
if (existsSync(agentBinary)) {
|
|
43
|
+
return agentBinary;
|
|
44
|
+
}
|
|
45
|
+
const sourceRoot = resolveAgentMaterializationSourceRoot(projectRoot, options);
|
|
46
|
+
if (!hasAgentDispatchSource(sourceRoot)) {
|
|
47
|
+
throw new CliError(`Missing compiled agent dispatch binary at ${agentBinary}, and no Rig source checkout with ${AGENT_DISPATCH_SOURCE} was found to materialize it automatically.`, 2);
|
|
48
|
+
}
|
|
49
|
+
mkdirSync(resolve(agentBinary, ".."), { recursive: true });
|
|
50
|
+
try {
|
|
51
|
+
await (options.build ?? buildBinary)(AGENT_DISPATCH_SOURCE, agentBinary, sourceRoot, {
|
|
52
|
+
AGENT_PROJECT_ROOT: projectRoot
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new CliError(`Missing compiled agent dispatch binary at ${agentBinary}, and automatic materialization from ${sourceRoot} failed: ${error instanceof Error ? error.message : String(error)}`, 2);
|
|
56
|
+
}
|
|
57
|
+
if (!existsSync(agentBinary)) {
|
|
58
|
+
throw new CliError(`Automatic agent dispatch materialization from ${sourceRoot} did not create ${agentBinary}.`, 2);
|
|
59
|
+
}
|
|
60
|
+
return agentBinary;
|
|
61
|
+
}
|
|
62
|
+
async function initializeRuntime(options) {
|
|
63
|
+
const eventBus = new EventBus({ projectRoot: options.projectRoot, runId: options.runId });
|
|
64
|
+
const runtimeContext = loadRuntimeContextFromEnv() ?? undefined;
|
|
65
|
+
const plugins = await PluginManager.load({
|
|
66
|
+
projectRoot: options.projectRoot,
|
|
67
|
+
runId: eventBus.getRunId(),
|
|
68
|
+
eventBus,
|
|
69
|
+
runtimeContext
|
|
70
|
+
});
|
|
71
|
+
const context = {
|
|
72
|
+
projectRoot: options.projectRoot,
|
|
73
|
+
dryRun: options.dryRun,
|
|
74
|
+
outputMode: options.outputMode,
|
|
75
|
+
runId: eventBus.getRunId(),
|
|
76
|
+
policyMode: options.policyMode,
|
|
77
|
+
eventBus,
|
|
78
|
+
plugins,
|
|
79
|
+
emitEvent: async (type, payload) => {
|
|
80
|
+
const event = await eventBus.emit(type, payload);
|
|
81
|
+
await plugins.onEvent(event);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
context.runCommand = async (parts) => runCommand(context, parts);
|
|
85
|
+
await context.emitEvent("runtime.init", {
|
|
86
|
+
runId: context.runId,
|
|
87
|
+
outputMode: context.outputMode,
|
|
88
|
+
dryRun: context.dryRun,
|
|
89
|
+
policyMode: context.policyMode ?? loadPolicy(options.projectRoot).mode,
|
|
90
|
+
policyFile: resolve(options.projectRoot, "rig/policy/policy.json"),
|
|
91
|
+
plugins: context.plugins.list()
|
|
92
|
+
});
|
|
93
|
+
return context;
|
|
94
|
+
}
|
|
95
|
+
async function runCommand(context, parts) {
|
|
96
|
+
if (parts.length === 0) {
|
|
97
|
+
throw new CliError("Cannot execute an empty command.");
|
|
98
|
+
}
|
|
99
|
+
const envMode = process.env.RIG_BASH_MODE;
|
|
100
|
+
const effectiveMode = context.policyMode || (envMode === "off" || envMode === "observe" || envMode === "enforce" ? envMode : loadPolicy(context.projectRoot).mode);
|
|
101
|
+
const controlledPath = `${resolve(context.projectRoot, ".rig", "bin")}:${context.projectRoot}/rig/tools:${process.env.PATH ?? ""}`;
|
|
102
|
+
const controlledBash = await ensureAgentShellBinary(context.projectRoot);
|
|
103
|
+
const commandEnv = [
|
|
104
|
+
"env",
|
|
105
|
+
`PATH=${controlledPath}`,
|
|
106
|
+
`PROJECT_RIG_ROOT=${context.projectRoot}`,
|
|
107
|
+
`BASH=${controlledBash}`,
|
|
108
|
+
`RIG_BASH_MODE=${effectiveMode}`,
|
|
109
|
+
`RIG_POLICY_FILE=${resolve(context.projectRoot, "rig/policy/policy.json")}`,
|
|
110
|
+
...context.eventBus.getEventsFile() ? [`RIG_EVENTS_FILE=${context.eventBus.getEventsFile()}`] : []
|
|
111
|
+
];
|
|
112
|
+
const commandWithEnv = [...commandEnv, ...parts];
|
|
113
|
+
const formatted = formatCommand(commandWithEnv);
|
|
114
|
+
await context.emitEvent("command.received", {
|
|
115
|
+
command: commandWithEnv,
|
|
116
|
+
formattedCommand: formatted
|
|
117
|
+
});
|
|
118
|
+
const guardDecision = evaluate({
|
|
119
|
+
projectRoot: context.projectRoot,
|
|
120
|
+
evaluation: { type: "command", command: formatted }
|
|
121
|
+
});
|
|
122
|
+
const guardAction = resolveAction(effectiveMode, guardDecision.matchedRules);
|
|
123
|
+
await context.emitEvent("policy.decision", {
|
|
124
|
+
target: "command",
|
|
125
|
+
command: formatted,
|
|
126
|
+
mode: effectiveMode,
|
|
127
|
+
allowed: guardAction !== "block",
|
|
128
|
+
matchedRuleIds: guardDecision.matchedRules.map((r) => r.id),
|
|
129
|
+
reasons: guardDecision.matchedRules.map((r) => r.reason)
|
|
130
|
+
});
|
|
131
|
+
if (guardAction === "block") {
|
|
132
|
+
await context.emitEvent("command.failed", {
|
|
133
|
+
command: commandWithEnv,
|
|
134
|
+
formattedCommand: formatted,
|
|
135
|
+
reason: "Policy denied command",
|
|
136
|
+
matchedRuleIds: guardDecision.matchedRules.map((r) => r.id),
|
|
137
|
+
mode: effectiveMode
|
|
138
|
+
});
|
|
139
|
+
throw new CliError(`Policy blocked command: ${formatted}`, 126);
|
|
140
|
+
}
|
|
141
|
+
await context.plugins.beforeCommand({ command: commandWithEnv, formattedCommand: formatted });
|
|
142
|
+
const startedAt = new Date;
|
|
143
|
+
await context.emitEvent("command.started", {
|
|
144
|
+
command: commandWithEnv,
|
|
145
|
+
formattedCommand: formatted,
|
|
146
|
+
startedAt: startedAt.toISOString(),
|
|
147
|
+
dryRun: context.dryRun
|
|
148
|
+
});
|
|
149
|
+
if (context.dryRun) {
|
|
150
|
+
const dryResult = {
|
|
151
|
+
command: commandWithEnv,
|
|
152
|
+
formattedCommand: formatted,
|
|
153
|
+
exitCode: 0,
|
|
154
|
+
durationMs: 0,
|
|
155
|
+
startedAt: startedAt.toISOString(),
|
|
156
|
+
finishedAt: startedAt.toISOString()
|
|
157
|
+
};
|
|
158
|
+
if (context.outputMode === "text") {
|
|
159
|
+
console.log(`$ ${formatted}`);
|
|
160
|
+
}
|
|
161
|
+
await context.plugins.afterCommand(dryResult);
|
|
162
|
+
await context.emitEvent("command.finished", {
|
|
163
|
+
...dryResult,
|
|
164
|
+
dryRun: true
|
|
165
|
+
});
|
|
166
|
+
return dryResult;
|
|
167
|
+
}
|
|
168
|
+
let exitCode = 0;
|
|
169
|
+
let stdout = "";
|
|
170
|
+
let stderr = "";
|
|
171
|
+
if (context.outputMode === "json") {
|
|
172
|
+
const child = Bun.spawn(commandWithEnv, {
|
|
173
|
+
cwd: context.projectRoot,
|
|
174
|
+
stdin: "inherit",
|
|
175
|
+
stdout: "pipe",
|
|
176
|
+
stderr: "pipe"
|
|
177
|
+
});
|
|
178
|
+
exitCode = await child.exited;
|
|
179
|
+
stdout = await new Response(child.stdout).text();
|
|
180
|
+
stderr = await new Response(child.stderr).text();
|
|
181
|
+
} else {
|
|
182
|
+
const child = Bun.spawn(commandWithEnv, {
|
|
183
|
+
cwd: context.projectRoot,
|
|
184
|
+
stdin: "inherit",
|
|
185
|
+
stdout: "inherit",
|
|
186
|
+
stderr: "inherit"
|
|
187
|
+
});
|
|
188
|
+
exitCode = await child.exited;
|
|
189
|
+
}
|
|
190
|
+
const finishedAt = new Date;
|
|
191
|
+
const result = {
|
|
192
|
+
command: commandWithEnv,
|
|
193
|
+
formattedCommand: formatted,
|
|
194
|
+
exitCode,
|
|
195
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
196
|
+
startedAt: startedAt.toISOString(),
|
|
197
|
+
finishedAt: finishedAt.toISOString(),
|
|
198
|
+
stdout: context.outputMode === "json" ? stdout : undefined,
|
|
199
|
+
stderr: context.outputMode === "json" ? stderr : undefined
|
|
200
|
+
};
|
|
201
|
+
await context.plugins.afterCommand(result);
|
|
202
|
+
if (exitCode !== 0) {
|
|
203
|
+
await context.emitEvent("command.failed", {
|
|
204
|
+
...result
|
|
205
|
+
});
|
|
206
|
+
const errorSuffix = context.outputMode === "json" && stderr ? `
|
|
207
|
+
${stderr.trim()}` : "";
|
|
208
|
+
throw new CliError(`Command failed (${exitCode}): ${formatted}${errorSuffix}`, exitCode);
|
|
209
|
+
}
|
|
210
|
+
await context.emitEvent("command.finished", {
|
|
211
|
+
...result
|
|
212
|
+
});
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
function takeFlag(args, flag) {
|
|
216
|
+
const rest = [];
|
|
217
|
+
let value = false;
|
|
218
|
+
for (const arg of args) {
|
|
219
|
+
if (arg === flag) {
|
|
220
|
+
value = true;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
rest.push(arg);
|
|
224
|
+
}
|
|
225
|
+
return { value, rest };
|
|
226
|
+
}
|
|
227
|
+
function takeOption(args, option) {
|
|
228
|
+
const rest = [];
|
|
229
|
+
let value;
|
|
230
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
231
|
+
const current = args[index];
|
|
232
|
+
if (current === option) {
|
|
233
|
+
const next = args[index + 1];
|
|
234
|
+
if (!next || next.startsWith("-")) {
|
|
235
|
+
throw new CliError(`Missing value for ${option}`);
|
|
236
|
+
}
|
|
237
|
+
value = next;
|
|
238
|
+
index += 1;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (current !== undefined) {
|
|
242
|
+
rest.push(current);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return { value, rest };
|
|
246
|
+
}
|
|
247
|
+
function requireNoExtraArgs(args, usage) {
|
|
248
|
+
if (args.length > 0) {
|
|
249
|
+
throw new CliError(`Unexpected arguments: ${args.join(" ")}
|
|
250
|
+
Usage: ${usage}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function requireTask(taskId, usage) {
|
|
254
|
+
if (!taskId) {
|
|
255
|
+
throw new CliError(`Missing --task option.
|
|
256
|
+
Usage: ${usage}`);
|
|
257
|
+
}
|
|
258
|
+
return taskId;
|
|
259
|
+
}
|
|
260
|
+
export {
|
|
261
|
+
withProjectRoot,
|
|
262
|
+
takeOption,
|
|
263
|
+
takeFlag,
|
|
264
|
+
runCommand,
|
|
265
|
+
resolveAgentMaterializationSourceRoot,
|
|
266
|
+
requireTask,
|
|
267
|
+
requireNoExtraArgs,
|
|
268
|
+
initializeRuntime,
|
|
269
|
+
formatCommand,
|
|
270
|
+
ensureAgentShellBinary,
|
|
271
|
+
CliError2 as CliError
|
|
272
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/withMutedConsole.ts
|
|
3
|
+
function isPromise(value) {
|
|
4
|
+
if (typeof value !== "object" && typeof value !== "function") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return value !== null && typeof value.then === "function";
|
|
8
|
+
}
|
|
9
|
+
function withMutedConsole(mute, fn) {
|
|
10
|
+
if (!mute) {
|
|
11
|
+
return fn();
|
|
12
|
+
}
|
|
13
|
+
const originalLog = console.log;
|
|
14
|
+
const originalWarn = console.warn;
|
|
15
|
+
const originalInfo = console.info;
|
|
16
|
+
const restore = () => {
|
|
17
|
+
console.log = originalLog;
|
|
18
|
+
console.warn = originalWarn;
|
|
19
|
+
console.info = originalInfo;
|
|
20
|
+
};
|
|
21
|
+
console.log = () => {};
|
|
22
|
+
console.warn = () => {};
|
|
23
|
+
console.info = () => {};
|
|
24
|
+
try {
|
|
25
|
+
const result = fn();
|
|
26
|
+
if (isPromise(result)) {
|
|
27
|
+
return result.finally(restore);
|
|
28
|
+
}
|
|
29
|
+
restore();
|
|
30
|
+
return result;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
restore();
|
|
33
|
+
throw error;
|
|
34
|
+
} finally {
|
|
35
|
+
if (console.log === originalLog) {
|
|
36
|
+
restore();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
withMutedConsole
|
|
42
|
+
};
|