@h-rig/cli 0.0.6-alpha.9 → 0.0.6-alpha.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/rig.d.ts +2 -0
  4. package/dist/bin/rig.js +11052 -6668
  5. package/dist/src/app/board.d.ts +23 -0
  6. package/dist/src/app/board.js +1786 -0
  7. package/dist/src/app/drone-ui.d.ts +37 -0
  8. package/dist/src/app/drone-ui.js +294 -0
  9. package/dist/src/app/theme.d.ts +47 -0
  10. package/dist/src/app/theme.js +150 -0
  11. package/dist/src/commands/_async-ui.d.ts +13 -0
  12. package/dist/src/commands/_async-ui.js +223 -0
  13. package/dist/src/commands/_authority-runs.d.ts +22 -0
  14. package/dist/src/commands/_authority-runs.js +2 -3
  15. package/dist/src/commands/_cli-format.d.ts +49 -0
  16. package/dist/src/commands/_cli-format.js +447 -0
  17. package/dist/src/commands/_connection-state.d.ts +44 -0
  18. package/dist/src/commands/_connection-state.js +30 -11
  19. package/dist/src/commands/_doctor-checks.d.ts +52 -0
  20. package/dist/src/commands/_doctor-checks.js +180 -44
  21. package/dist/src/commands/_help-catalog.d.ts +51 -0
  22. package/dist/src/commands/_help-catalog.js +442 -0
  23. package/dist/src/commands/_json-output.d.ts +11 -0
  24. package/dist/src/commands/_json-output.js +56 -0
  25. package/dist/src/commands/_operator-surface.d.ts +34 -0
  26. package/dist/src/commands/_operator-surface.js +220 -0
  27. package/dist/src/commands/_operator-view.d.ts +30 -0
  28. package/dist/src/commands/_operator-view.js +803 -73
  29. package/dist/src/commands/_parsers.d.ts +15 -0
  30. package/dist/src/commands/_parsers.js +18 -11
  31. package/dist/src/commands/_paths.d.ts +11 -0
  32. package/dist/src/commands/_pi-frontend.d.ts +27 -0
  33. package/dist/src/commands/_pi-frontend.js +742 -0
  34. package/dist/src/commands/_pi-install.d.ts +42 -0
  35. package/dist/src/commands/_pi-install.js +5 -4
  36. package/dist/src/commands/_policy.d.ts +8 -0
  37. package/dist/src/commands/_policy.js +12 -5
  38. package/dist/src/commands/_preflight.d.ts +22 -0
  39. package/dist/src/commands/_preflight.js +190 -128
  40. package/dist/src/commands/_probes.d.ts +1 -0
  41. package/dist/src/commands/_run-driver-helpers.d.ts +99 -0
  42. package/dist/src/commands/_run-driver-helpers.js +75 -22
  43. package/dist/src/commands/_run-replay.d.ts +24 -0
  44. package/dist/src/commands/_run-replay.js +142 -0
  45. package/dist/src/commands/_server-client.d.ts +186 -0
  46. package/dist/src/commands/_server-client.js +360 -61
  47. package/dist/src/commands/_snapshot-upload.d.ts +39 -0
  48. package/dist/src/commands/_snapshot-upload.js +163 -39
  49. package/dist/src/commands/_spinner.d.ts +25 -0
  50. package/dist/src/commands/_spinner.js +65 -0
  51. package/dist/src/commands/_task-picker.d.ts +9 -0
  52. package/dist/src/commands/_task-picker.js +172 -19
  53. package/dist/src/commands/agent.d.ts +3 -0
  54. package/dist/src/commands/agent.js +39 -20
  55. package/dist/src/commands/browser.d.ts +65 -0
  56. package/dist/src/commands/browser.js +334 -51
  57. package/dist/src/commands/connect.d.ts +7 -0
  58. package/dist/src/commands/connect.js +272 -33
  59. package/dist/src/commands/dist.d.ts +28 -0
  60. package/dist/src/commands/dist.js +19 -12
  61. package/dist/src/commands/doctor.d.ts +3 -0
  62. package/dist/src/commands/doctor.js +378 -45
  63. package/dist/src/commands/github.d.ts +3 -0
  64. package/dist/src/commands/github.js +375 -53
  65. package/dist/src/commands/inbox.d.ts +30 -0
  66. package/dist/src/commands/inbox.js +760 -70
  67. package/dist/src/commands/init.d.ts +74 -0
  68. package/dist/src/commands/init.js +963 -119
  69. package/dist/src/commands/inspect.d.ts +3 -0
  70. package/dist/src/commands/inspect.js +588 -32
  71. package/dist/src/commands/inspector.d.ts +3 -0
  72. package/dist/src/commands/inspector.js +20 -13
  73. package/dist/src/commands/pi.d.ts +3 -0
  74. package/dist/src/commands/pi.js +177 -0
  75. package/dist/src/commands/plugin.d.ts +16 -0
  76. package/dist/src/commands/plugin.js +95 -27
  77. package/dist/src/commands/profile-and-review.d.ts +4 -0
  78. package/dist/src/commands/profile-and-review.js +26 -19
  79. package/dist/src/commands/queue.d.ts +3 -0
  80. package/dist/src/commands/queue.js +32 -12
  81. package/dist/src/commands/remote.d.ts +3 -0
  82. package/dist/src/commands/remote.js +43 -36
  83. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  84. package/dist/src/commands/repo-git-harness.js +22 -15
  85. package/dist/src/commands/run.d.ts +3 -0
  86. package/dist/src/commands/run.js +1379 -159
  87. package/dist/src/commands/server.d.ts +7 -0
  88. package/dist/src/commands/server.js +502 -57
  89. package/dist/src/commands/setup.d.ts +16 -0
  90. package/dist/src/commands/setup.js +390 -63
  91. package/dist/src/commands/stats.d.ts +12 -0
  92. package/dist/src/commands/stats.js +1051 -0
  93. package/dist/src/commands/task-report-bug.d.ts +19 -0
  94. package/dist/src/commands/task-report-bug.js +260 -62
  95. package/dist/src/commands/task-run-driver.d.ts +132 -0
  96. package/dist/src/commands/task-run-driver.js +886 -130
  97. package/dist/src/commands/task.d.ts +14 -0
  98. package/dist/src/commands/task.js +1644 -314
  99. package/dist/src/commands/test.d.ts +3 -0
  100. package/dist/src/commands/test.js +15 -8
  101. package/dist/src/commands/workspace.d.ts +3 -0
  102. package/dist/src/commands/workspace.js +18 -11
  103. package/dist/src/commands.d.ts +29 -0
  104. package/dist/src/commands.js +10967 -6637
  105. package/dist/src/index.d.ts +4 -0
  106. package/dist/src/index.js +10168 -5787
  107. package/dist/src/launcher.d.ts +61 -0
  108. package/dist/src/launcher.js +77 -13
  109. package/dist/src/report-bug.d.ts +44 -0
  110. package/dist/src/report-bug.js +3 -3
  111. package/dist/src/runner.d.ts +47 -0
  112. package/dist/src/runner.js +16 -22
  113. package/dist/src/withMutedConsole.d.ts +2 -0
  114. package/package.json +13 -6
@@ -0,0 +1,3 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
3
+ export declare function executeInspector(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
@@ -4,12 +4,19 @@ import { iterateServerSentEvents } from "@rig/client";
4
4
 
5
5
  // packages/cli/src/runner.ts
6
6
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
7
- import { CliError } from "@rig/runtime/control-plane/errors";
7
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
8
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
9
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
12
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
10
+
11
+ class CliError extends RuntimeCliError {
12
+ hint;
13
+ constructor(message, exitCode = 1, options = {}) {
14
+ super(message, exitCode);
15
+ if (options.hint?.trim()) {
16
+ this.hint = options.hint.trim();
17
+ }
18
+ }
19
+ }
13
20
  function takeFlag(args, flag) {
14
21
  const rest = [];
15
22
  let value = false;
@@ -30,7 +37,7 @@ function takeOption(args, option) {
30
37
  if (current === option) {
31
38
  const next = args[index + 1];
32
39
  if (!next || next.startsWith("-")) {
33
- throw new CliError(`Missing value for ${option}`);
40
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
34
41
  }
35
42
  value = next;
36
43
  index += 1;
@@ -55,11 +62,11 @@ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
55
62
  // packages/cli/src/commands/_parsers.ts
56
63
  function parseRequiredPositiveInt(value, option) {
57
64
  if (!value) {
58
- throw new CliError2(`Missing value for ${option}.`);
65
+ throw new CliError(`Missing value for ${option}.`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} 10\`.` });
59
66
  }
60
67
  const parsed = Number.parseInt(value, 10);
61
68
  if (!Number.isFinite(parsed) || parsed <= 0) {
62
- throw new CliError2(`Invalid ${option} value: ${value}`);
69
+ throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
63
70
  }
64
71
  return parsed;
65
72
  }
@@ -104,11 +111,11 @@ async function executeInspector(context, args) {
104
111
  pending = secondsResult.rest;
105
112
  const pollMsResult = takeOption(pending, "--poll-ms");
106
113
  pending = pollMsResult.rest;
107
- requireNoExtraArgs(pending, "bun run rig inspector stream [--once] [--seconds <n>] [--poll-ms <n>]");
114
+ requireNoExtraArgs(pending, "rig inspector stream [--once] [--seconds <n>] [--poll-ms <n>]");
108
115
  const seconds = secondsResult.value ? parseRequiredPositiveInt(secondsResult.value, "--seconds") : null;
109
116
  const pollMs = pollMsResult.value ? parseRequiredPositiveInt(pollMsResult.value, "--poll-ms") : null;
110
117
  if (context.outputMode === "json" && !onceResult.value && !seconds) {
111
- throw new CliError2("--json inspector stream requires --once or --seconds <n>.", 2);
118
+ throw new CliError("--json inspector stream requires --once or --seconds <n>.", 2, { hint: "Re-run as `rig inspector stream --once --json` or `rig inspector stream --seconds 10 --json`." });
112
119
  }
113
120
  const connection = await ensureLocalRigServerConnection(context.projectRoot);
114
121
  const streamUrl = new URL("/api/inspector/stream", connection.baseUrl);
@@ -135,7 +142,7 @@ async function executeInspector(context, args) {
135
142
  signal: controller.signal
136
143
  });
137
144
  if (!response.ok) {
138
- throw new CliError2(`Inspector stream request failed (${response.status}).`, response.status);
145
+ throw new CliError(`Inspector stream request failed (${response.status}).`, response.status);
139
146
  }
140
147
  let received = 0;
141
148
  let lastEvent = null;
@@ -198,7 +205,7 @@ async function executeInspector(context, args) {
198
205
  let pending = rest;
199
206
  const scanIdResult = takeOption(pending, "--scan-id");
200
207
  pending = scanIdResult.rest;
201
- requireNoExtraArgs(pending, "bun run rig inspector scan-upstream-drift [--scan-id <id>]");
208
+ requireNoExtraArgs(pending, "rig inspector scan-upstream-drift [--scan-id <id>]");
202
209
  const connection = await ensureLocalRigServerConnection(context.projectRoot);
203
210
  const response = await fetch(new URL("/api/inspector/tools/invoke", connection.baseUrl), {
204
211
  method: "POST",
@@ -212,7 +219,7 @@ async function executeInspector(context, args) {
212
219
  })
213
220
  });
214
221
  if (!response.ok) {
215
- throw new CliError2(`Upstream drift scan request failed (${response.status}).`, response.status);
222
+ throw new CliError(`Upstream drift scan request failed (${response.status}).`, response.status);
216
223
  }
217
224
  const result = await response.json();
218
225
  if (context.outputMode === "text") {
@@ -248,7 +255,7 @@ async function executeInspector(context, args) {
248
255
  };
249
256
  }
250
257
  default:
251
- throw new CliError2(`Unknown inspector command: ${command}`);
258
+ throw new CliError(`Unknown inspector command: ${command}`, 1, { hint: "Run `rig inspector --help` \u2014 commands are stream|scan-upstream-drift." });
252
259
  }
253
260
  }
254
261
  export {
@@ -0,0 +1,3 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
3
+ export declare function executePi(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
@@ -0,0 +1,177 @@
1
+ // @bun
2
+ // packages/cli/src/commands/pi.ts
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
+ import { homedir } from "os";
5
+ import { dirname, resolve } from "path";
6
+
7
+ // packages/cli/src/runner.ts
8
+ import { EventBus } from "@rig/runtime/control-plane/runtime/events";
9
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
10
+ import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
11
+ import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
12
+
13
+ class CliError extends RuntimeCliError {
14
+ hint;
15
+ constructor(message, exitCode = 1, options = {}) {
16
+ super(message, exitCode);
17
+ if (options.hint?.trim()) {
18
+ this.hint = options.hint.trim();
19
+ }
20
+ }
21
+ }
22
+ function requireNoExtraArgs(args, usage) {
23
+ if (args.length > 0) {
24
+ throw new CliError(`Unexpected arguments: ${args.join(" ")}
25
+ Usage: ${usage}`);
26
+ }
27
+ }
28
+
29
+ // packages/cli/src/commands/pi.ts
30
+ function settingsPath(root) {
31
+ return resolve(root, ".pi", "settings.json");
32
+ }
33
+ function userSettingsPath() {
34
+ return resolve(homedir(), ".pi", "agent", "settings.json");
35
+ }
36
+ function readJson(path, fallback) {
37
+ if (!existsSync(path))
38
+ return fallback;
39
+ try {
40
+ return JSON.parse(readFileSync(path, "utf-8"));
41
+ } catch {
42
+ return fallback;
43
+ }
44
+ }
45
+ function packageKey(entry) {
46
+ if (typeof entry === "string")
47
+ return entry;
48
+ if (entry && typeof entry === "object" && typeof entry.source === "string") {
49
+ return entry.source;
50
+ }
51
+ return JSON.stringify(entry);
52
+ }
53
+ function writeSettings(path, settings) {
54
+ mkdirSync(dirname(path), { recursive: true });
55
+ writeFileSync(path, `${JSON.stringify(settings, null, 2)}
56
+ `, "utf-8");
57
+ }
58
+ async function searchNpmForPiExtensions(term) {
59
+ const query = encodeURIComponent(term ? `${term} pi extension` : "pi extension");
60
+ const url = `https://registry.npmjs.org/-/v1/search?text=${query}&size=20`;
61
+ const response = await fetch(url);
62
+ if (!response.ok) {
63
+ throw new CliError(`npm registry search failed (${response.status}).`, 2, { hint: "Check network access to registry.npmjs.org, then retry `rig pi search <term>`." });
64
+ }
65
+ const payload = await response.json();
66
+ const results = [];
67
+ for (const entry of payload.objects ?? []) {
68
+ const pkg = entry.package;
69
+ if (!pkg?.name)
70
+ continue;
71
+ const keywords = (pkg.keywords ?? []).map((k) => k.toLowerCase());
72
+ const piLike = pkg.name.startsWith("pi-") || pkg.name.includes("-pi") || keywords.includes("pi") || keywords.includes("pi-extension") || (pkg.description ?? "").toLowerCase().includes("pi extension") || (pkg.description ?? "").toLowerCase().includes("pi coding agent");
73
+ if (!piLike)
74
+ continue;
75
+ results.push({ name: pkg.name, version: pkg.version ?? "", description: pkg.description ?? "" });
76
+ }
77
+ return results;
78
+ }
79
+ async function executePi(context, args) {
80
+ const [command = "list", ...rest] = args;
81
+ const projectSettingsPath = settingsPath(context.projectRoot);
82
+ const managedRecordPath = resolve(context.projectRoot, ".rig", "state", "pi-managed-packages.json");
83
+ switch (command) {
84
+ case "list": {
85
+ requireNoExtraArgs(rest, "rig pi list");
86
+ const project = readJson(projectSettingsPath, {});
87
+ const managed = new Set(readJson(managedRecordPath, []));
88
+ const user = readJson(userSettingsPath(), {});
89
+ const projectPackages = (Array.isArray(project.packages) ? project.packages : []).map((entry) => ({
90
+ source: packageKey(entry),
91
+ managedByRigConfig: managed.has(packageKey(entry))
92
+ }));
93
+ const userPackages = (Array.isArray(user.packages) ? user.packages : []).map(packageKey);
94
+ if (context.outputMode === "text") {
95
+ console.log("Project Pi packages (.pi/settings.json):");
96
+ if (projectPackages.length === 0)
97
+ console.log(" (none)");
98
+ for (const pkg of projectPackages) {
99
+ console.log(` ${pkg.source}${pkg.managedByRigConfig ? " [from rig.config runtime.pi.packages]" : ""}`);
100
+ }
101
+ console.log("User Pi packages (~/.pi/agent/settings.json):");
102
+ if (userPackages.length === 0)
103
+ console.log(" (none)");
104
+ for (const pkg of userPackages)
105
+ console.log(` ${pkg}`);
106
+ console.log("Add more: `rig pi add <npm-package>` \xB7 discover: `rig pi search <term>`");
107
+ }
108
+ return { ok: true, group: "pi", command, details: { projectPackages, userPackages } };
109
+ }
110
+ case "add": {
111
+ const [source, ...extra] = rest;
112
+ requireNoExtraArgs(extra, "rig pi add <package-source>");
113
+ if (!source) {
114
+ throw new CliError("Usage: rig pi add <package-source> (npm name, name@version, or git URL)", 2);
115
+ }
116
+ const settings = readJson(projectSettingsPath, {});
117
+ const packages = Array.isArray(settings.packages) ? settings.packages : [];
118
+ if (packages.some((entry) => packageKey(entry) === source)) {
119
+ throw new CliError(`"${source}" is already in ${projectSettingsPath}.`, 2, { hint: "Run `rig pi list` to see installed extensions." });
120
+ }
121
+ writeSettings(projectSettingsPath, { ...settings, packages: [...packages, source] });
122
+ if (context.outputMode === "text") {
123
+ console.log(`Added ${source} to ${projectSettingsPath}.`);
124
+ console.log("Pi installs missing packages automatically at the next session start (local and worker).");
125
+ }
126
+ return { ok: true, group: "pi", command, details: { source, settingsPath: projectSettingsPath } };
127
+ }
128
+ case "remove": {
129
+ const [source, ...extra] = rest;
130
+ requireNoExtraArgs(extra, "rig pi remove <package-source>");
131
+ if (!source) {
132
+ throw new CliError("Usage: rig pi remove <package-source>", 2);
133
+ }
134
+ const managed = new Set(readJson(managedRecordPath, []));
135
+ if (managed.has(source)) {
136
+ throw new CliError(`"${source}" is managed by rig.config.ts (runtime.pi.packages); remove it there instead.`, 2);
137
+ }
138
+ const settings = readJson(projectSettingsPath, {});
139
+ const packages = Array.isArray(settings.packages) ? settings.packages : [];
140
+ const next = packages.filter((entry) => packageKey(entry) !== source);
141
+ if (next.length === packages.length) {
142
+ throw new CliError(`"${source}" is not in ${projectSettingsPath}.`, 2, { hint: "Run `rig pi list` to see what is installed, then `rig pi remove <source>` with an exact entry." });
143
+ }
144
+ const nextSettings = { ...settings };
145
+ if (next.length > 0)
146
+ nextSettings.packages = next;
147
+ else
148
+ delete nextSettings.packages;
149
+ writeSettings(projectSettingsPath, nextSettings);
150
+ if (context.outputMode === "text") {
151
+ console.log(`Removed ${source} from ${projectSettingsPath}.`);
152
+ }
153
+ return { ok: true, group: "pi", command, details: { source } };
154
+ }
155
+ case "search": {
156
+ const term = rest.join(" ").trim();
157
+ const results = await searchNpmForPiExtensions(term);
158
+ if (context.outputMode === "text") {
159
+ if (results.length === 0) {
160
+ console.log(`No Pi extension packages found on npm${term ? ` for "${term}"` : ""}.`);
161
+ } else {
162
+ console.log(`Pi extension packages on npm${term ? ` matching "${term}"` : ""}:`);
163
+ for (const pkg of results) {
164
+ console.log(` ${pkg.name}@${pkg.version} ${pkg.description.slice(0, 80)}`);
165
+ }
166
+ console.log("Install one: `rig pi add <name>`");
167
+ }
168
+ }
169
+ return { ok: true, group: "pi", command, details: { term, results } };
170
+ }
171
+ default:
172
+ throw new CliError(`Unknown pi command: ${command}. Use list|add|remove|search.`, 1, { hint: "Run `rig pi --help` for usage." });
173
+ }
174
+ }
175
+ export {
176
+ executePi
177
+ };
@@ -0,0 +1,16 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
3
+ export declare function executePlugin(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
4
+ /**
5
+ * Resolve a plugin CLI command by exact id ("my-plugin:deploy") or by the
6
+ * unambiguous local part after the namespace colon ("deploy").
7
+ */
8
+ export declare function resolvePluginCliCommand(commands: readonly {
9
+ id: string;
10
+ command: string;
11
+ description?: string;
12
+ }[], requested: string): {
13
+ id: string;
14
+ command: string;
15
+ description?: string;
16
+ } | undefined;
@@ -1,14 +1,25 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
+ // packages/cli/src/commands/plugin.ts
5
+ import { existsSync } from "fs";
6
+ import { resolve } from "path";
7
+
4
8
  // packages/cli/src/runner.ts
5
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
6
- import { CliError } from "@rig/runtime/control-plane/errors";
10
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
7
11
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
8
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
9
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
10
12
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
11
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
13
+
14
+ class CliError extends RuntimeCliError {
15
+ hint;
16
+ constructor(message, exitCode = 1, options = {}) {
17
+ super(message, exitCode);
18
+ if (options.hint?.trim()) {
19
+ this.hint = options.hint.trim();
20
+ }
21
+ }
22
+ }
12
23
  function takeOption(args, option) {
13
24
  const rest = [];
14
25
  let value;
@@ -17,7 +28,7 @@ function takeOption(args, option) {
17
28
  if (current === option) {
18
29
  const next = args[index + 1];
19
30
  if (!next || next.startsWith("-")) {
20
- throw new CliError(`Missing value for ${option}`);
31
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
21
32
  }
22
33
  value = next;
23
34
  index += 1;
@@ -43,6 +54,9 @@ Usage: ${usage}`);
43
54
  return taskId;
44
55
  }
45
56
 
57
+ // packages/cli/src/commands/plugin.ts
58
+ import { buildPluginHostContext } from "@rig/runtime/control-plane/plugin-host-context";
59
+
46
60
  // packages/cli/src/commands/_parsers.ts
47
61
  async function loadRigConfigOrNull(projectRoot) {
48
62
  try {
@@ -58,8 +72,7 @@ async function executePlugin(context, args) {
58
72
  const [command = "list", ...rest] = args;
59
73
  switch (command) {
60
74
  case "list": {
61
- requireNoExtraArgs(rest, "bun run rig plugin list");
62
- const legacyPlugins = context.plugins.list();
75
+ requireNoExtraArgs(rest, "rig plugin list");
63
76
  const declarative = [];
64
77
  const config = await loadRigConfigOrNull(context.projectRoot);
65
78
  if (config && Array.isArray(config.plugins)) {
@@ -75,16 +88,16 @@ async function executePlugin(context, args) {
75
88
  taskSources: (c.taskSources ?? []).map((s) => s.id),
76
89
  skills: (c.skills ?? []).map((s) => s.id),
77
90
  taskFieldExtensions: (c.taskFieldSchemas ?? []).map((f) => f.id),
78
- cliCommands: (c.cliCommands ?? []).map((c2) => c2.id)
91
+ cliCommands: (c.cliCommands ?? []).map((entry) => entry.id)
79
92
  });
80
93
  }
81
94
  }
82
95
  if (context.outputMode === "text") {
83
- if (legacyPlugins.length === 0 && declarative.length === 0) {
84
- console.log("No plugins loaded.");
85
- }
86
- if (declarative.length > 0) {
87
- console.log("Declarative plugins (rig.config.ts):");
96
+ if (declarative.length === 0) {
97
+ const configExists = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync(resolve(context.projectRoot, name)));
98
+ console.log(configExists ? "rig.config found but no plugins could be loaded. If the config imports @rig/* packages, run `bun install` in this project first." : "No plugins loaded. Declare plugins in rig.config.ts.");
99
+ } else {
100
+ console.log("Plugins (rig.config.ts):");
88
101
  for (const p of declarative) {
89
102
  console.log(` ${p.name}@${p.version}`);
90
103
  const lines = [];
@@ -103,31 +116,45 @@ async function executePlugin(context, args) {
103
116
  if (p.taskFieldExtensions.length)
104
117
  lines.push(` task-fields: ${p.taskFieldExtensions.join(", ")}`);
105
118
  if (p.cliCommands.length)
106
- lines.push(` cli-commands: ${p.cliCommands.join(", ")}`);
119
+ lines.push(` cli-commands: ${p.cliCommands.join(", ")} (run with \`rig plugin run <id>\`)`);
107
120
  for (const line of lines)
108
121
  console.log(line);
109
122
  }
110
123
  }
111
- if (legacyPlugins.length > 0) {
112
- console.log("Legacy disk-scan plugins (rig/plugins/):");
113
- for (const plugin of legacyPlugins) {
114
- const validators = plugin.validators.length > 0 ? plugin.validators.join(", ") : "none";
115
- console.log(` ${plugin.name} (validators: ${validators})`);
116
- }
117
- }
118
124
  }
119
125
  return {
120
126
  ok: true,
121
127
  group: "plugin",
122
128
  command,
123
- details: { declarative, legacy: legacyPlugins }
129
+ details: { declarative }
124
130
  };
125
131
  }
126
132
  case "validate": {
127
133
  const { value: task, rest: remaining } = takeOption(rest, "--task");
128
- requireNoExtraArgs(remaining, "bun run rig plugin validate --task <beads-id>");
129
- const taskId = requireTask(task, "bun run rig plugin validate --task <beads-id>");
130
- const results = await context.plugins.runValidators(taskId);
134
+ requireNoExtraArgs(remaining, "rig plugin validate --task <task-id>");
135
+ const taskId = requireTask(task, "rig plugin validate --task <task-id>");
136
+ const hostCtx = await buildPluginHostContext(context.projectRoot);
137
+ if (!hostCtx) {
138
+ throw new CliError(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
139
+ }
140
+ const validators = hostCtx.validatorRegistry.list();
141
+ const results = [];
142
+ for (const validator of validators) {
143
+ try {
144
+ results.push(await validator.run({
145
+ taskId,
146
+ workspaceRoot: context.projectRoot,
147
+ scope: []
148
+ }));
149
+ } catch (error) {
150
+ results.push({
151
+ id: validator.id,
152
+ passed: false,
153
+ summary: `${validator.id} failed unexpectedly`,
154
+ details: `${error}`
155
+ });
156
+ }
157
+ }
131
158
  const passed = results.filter((result) => result.passed).length;
132
159
  const failed = results.length - passed;
133
160
  if (context.outputMode === "text") {
@@ -144,7 +171,7 @@ async function executePlugin(context, args) {
144
171
  }
145
172
  }
146
173
  if (failed > 0) {
147
- throw new CliError2(`Plugin validation failed for ${failed} validator(s).`, 2);
174
+ throw new CliError(`Plugin validation failed for ${failed} validator(s).`, 2, { hint: "Fix the reported validators, then re-run `rig plugin validate --task <id>`." });
148
175
  }
149
176
  return {
150
177
  ok: true,
@@ -158,10 +185,51 @@ async function executePlugin(context, args) {
158
185
  }
159
186
  };
160
187
  }
188
+ case "run": {
189
+ const [commandId, ...commandArgs] = rest;
190
+ if (!commandId) {
191
+ throw new CliError("Usage: rig plugin run <command-id> [args...]", 1, { hint: "Run `rig plugin list` to see plugin-contributed command ids." });
192
+ }
193
+ const hostCtx = await buildPluginHostContext(context.projectRoot);
194
+ if (!hostCtx) {
195
+ throw new CliError(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
196
+ }
197
+ const registration = resolvePluginCliCommand(hostCtx.pluginHost.listCliCommands(), commandId);
198
+ if (!registration) {
199
+ const available = hostCtx.pluginHost.listCliCommands().map((entry) => entry.id);
200
+ throw new CliError(available.length > 0 ? `Unknown plugin command "${commandId}". Available: ${available.join(", ")}` : `No plugin CLI commands are registered. Plugins contribute them via contributes.cliCommands.`, 2);
201
+ }
202
+ if (context.dryRun) {
203
+ if (context.outputMode === "text") {
204
+ console.log(`[dry-run] ${registration.command}${commandArgs.length ? ` ${commandArgs.join(" ")}` : ""}`);
205
+ }
206
+ return { ok: true, group: "plugin", command, details: { id: registration.id, dryRun: true } };
207
+ }
208
+ const proc = Bun.spawn(["bash", "-c", `${registration.command} "$@"`, registration.id, ...commandArgs], {
209
+ cwd: context.projectRoot,
210
+ env: process.env,
211
+ stdin: "inherit",
212
+ stdout: "inherit",
213
+ stderr: "inherit"
214
+ });
215
+ const exitCode = await proc.exited;
216
+ if (exitCode !== 0) {
217
+ throw new CliError(`Plugin command "${registration.id}" exited with code ${exitCode}.`, exitCode, { hint: "Run `rig plugin list` to inspect the contributing plugin, or re-run with --json for details." });
218
+ }
219
+ return { ok: true, group: "plugin", command, details: { id: registration.id, exitCode } };
220
+ }
161
221
  default:
162
- throw new CliError2(`Unknown plugin command: ${command}`);
222
+ throw new CliError(`Unknown plugin command: ${command}`, 1, { hint: "Run `rig plugin --help` \u2014 commands are list|validate|run." });
163
223
  }
164
224
  }
225
+ function resolvePluginCliCommand(commands, requested) {
226
+ const exact = commands.find((entry) => entry.id === requested);
227
+ if (exact)
228
+ return exact;
229
+ const byLocalPart = commands.filter((entry) => entry.id.split(":").slice(1).join(":") === requested);
230
+ return byLocalPart.length === 1 ? byLocalPart[0] : undefined;
231
+ }
165
232
  export {
233
+ resolvePluginCliCommand,
166
234
  executePlugin
167
235
  };
@@ -0,0 +1,4 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
3
+ export declare function executeProfile(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
4
+ export declare function executeReview(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
@@ -1,12 +1,19 @@
1
1
  // @bun
2
2
  // packages/cli/src/runner.ts
3
3
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
- import { CliError } from "@rig/runtime/control-plane/errors";
4
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
5
5
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
6
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
7
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
8
6
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
9
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
7
+
8
+ class CliError extends RuntimeCliError {
9
+ hint;
10
+ constructor(message, exitCode = 1, options = {}) {
11
+ super(message, exitCode);
12
+ if (options.hint?.trim()) {
13
+ this.hint = options.hint.trim();
14
+ }
15
+ }
16
+ }
10
17
  function takeOption(args, option) {
11
18
  const rest = [];
12
19
  let value;
@@ -15,7 +22,7 @@ function takeOption(args, option) {
15
22
  if (current === option) {
16
23
  const next = args[index + 1];
17
24
  if (!next || next.startsWith("-")) {
18
- throw new CliError(`Missing value for ${option}`);
25
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
19
26
  }
20
27
  value = next;
21
28
  index += 1;
@@ -86,22 +93,22 @@ async function executeProfile(context, args) {
86
93
  const [command = "show", ...rest] = args;
87
94
  switch (command) {
88
95
  case "show":
89
- requireNoExtraArgs(rest, "bun run rig profile show");
96
+ requireNoExtraArgs(rest, "rig profile show");
90
97
  await withMutedConsole(context.outputMode === "json", () => showProfile(context.projectRoot));
91
98
  return { ok: true, group: "profile", command };
92
99
  case "set": {
93
100
  if (rest.length === 0) {
94
- throw new CliError2("Usage: bun run rig profile set <claude-code|codex-cli|pi> or set [--model ...] [--runtime ...] [--plugin ...]");
101
+ throw new CliError("Usage: rig profile set <claude-code|codex-cli|pi> or set [--model ...] [--runtime ...] [--plugin ...]");
95
102
  }
96
103
  const preset = rest[0];
97
104
  if (preset && !preset.startsWith("-")) {
98
105
  if (rest.length !== 1) {
99
- throw new CliError2("Usage: bun run rig profile set <claude-code|codex-cli|pi>");
106
+ throw new CliError("Usage: rig profile set <claude-code|codex-cli|pi>");
100
107
  }
101
108
  try {
102
109
  await withMutedConsole(context.outputMode === "json", () => setProfile(context.projectRoot, { preset }));
103
110
  } catch (error) {
104
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
111
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
105
112
  }
106
113
  return { ok: true, group: "profile", command, details: { preset } };
107
114
  }
@@ -112,9 +119,9 @@ async function executeProfile(context, args) {
112
119
  pending = runtimeResult.rest;
113
120
  const pluginResult = takeOption(pending, "--plugin");
114
121
  pending = pluginResult.rest;
115
- requireNoExtraArgs(pending, "bun run rig profile set [--model ...] [--runtime ...] [--plugin ...]");
122
+ requireNoExtraArgs(pending, "rig profile set [--model ...] [--runtime ...] [--plugin ...]");
116
123
  if (!modelResult.value && !runtimeResult.value && !pluginResult.value) {
117
- throw new CliError2("Provide at least one of --model, --runtime, or --plugin.");
124
+ throw new CliError("Provide at least one of --model, --runtime, or --plugin.", 1, { hint: "Example: `rig profile set --model claude-sonnet`. See current values with `rig profile show`." });
118
125
  }
119
126
  try {
120
127
  await withMutedConsole(context.outputMode === "json", () => setProfile(context.projectRoot, {
@@ -123,7 +130,7 @@ async function executeProfile(context, args) {
123
130
  plugin: pluginResult.value
124
131
  }));
125
132
  } catch (error) {
126
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
133
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
127
134
  }
128
135
  return {
129
136
  ok: true,
@@ -137,39 +144,39 @@ async function executeProfile(context, args) {
137
144
  };
138
145
  }
139
146
  default:
140
- throw new CliError2(`Unknown profile command: ${command}`);
147
+ throw new CliError(`Unknown profile command: ${command}`, 1, { hint: "Run `rig profile --help` \u2014 commands are show|set." });
141
148
  }
142
149
  }
143
150
  async function executeReview(context, args) {
144
151
  const [command = "show", ...rest] = args;
145
152
  switch (command) {
146
153
  case "show":
147
- requireNoExtraArgs(rest, "bun run rig review show");
154
+ requireNoExtraArgs(rest, "rig review show");
148
155
  await withMutedConsole(context.outputMode === "json", () => showReviewProfile(context.projectRoot));
149
156
  return { ok: true, group: "review", command };
150
157
  case "set": {
151
158
  if (rest.length === 0) {
152
- throw new CliError2("Usage: bun run rig review set <off|advisory|required> [--provider greptile]");
159
+ throw new CliError("Usage: rig review set <off|advisory|required> [--provider greptile]");
153
160
  }
154
161
  const mode = rest[0];
155
162
  if (!mode) {
156
- throw new CliError2("Usage: bun run rig review set <off|advisory|required> [--provider greptile]");
163
+ throw new CliError("Usage: rig review set <off|advisory|required> [--provider greptile]");
157
164
  }
158
165
  let pending = rest.slice(1);
159
166
  const providerResult = takeOption(pending, "--provider");
160
167
  pending = providerResult.rest;
161
- requireNoExtraArgs(pending, "bun run rig review set <off|advisory|required> [--provider greptile]");
168
+ requireNoExtraArgs(pending, "rig review set <off|advisory|required> [--provider greptile]");
162
169
  try {
163
170
  await withMutedConsole(context.outputMode === "json", () => {
164
171
  return setReviewProfile(context.projectRoot, mode, providerResult.value);
165
172
  });
166
173
  } catch (error) {
167
- throw new CliError2(error instanceof Error ? error.message : String(error), 2);
174
+ throw new CliError(error instanceof Error ? error.message : String(error), 2);
168
175
  }
169
176
  return { ok: true, group: "review", command, details: { mode, provider: providerResult.value || null } };
170
177
  }
171
178
  default:
172
- throw new CliError2(`Unknown review command: ${command}`);
179
+ throw new CliError(`Unknown review command: ${command}`, 1, { hint: "Run `rig review --help` \u2014 commands are show|set." });
173
180
  }
174
181
  }
175
182
  export {
@@ -0,0 +1,3 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime/control-plane/runtime/types";
3
+ export declare function executeQueue(context: RunnerContext, args: string[]): Promise<CommandOutcome>;