@rigkit/cli 0.2.1 → 0.2.3
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 +1 -1
- package/package.json +4 -4
- package/src/cli.test.ts +8 -12
- package/src/cli.ts +30 -71
- package/src/completion.test.ts +59 -25
- package/src/completion.ts +59 -20
- package/src/init.ts +10 -9
- package/src/run-presenter.ts +1 -1
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Interactive terminals use Inquirer prompts and a chalk/log-update run timeline.
|
|
|
16
16
|
|
|
17
17
|
Interactive providers can ask the CLI to open provider-owned URLs. For example, Freestyle terminal sessions are served by the Freestyle provider, while the CLI only opens the presented URL in a browser.
|
|
18
18
|
|
|
19
|
-
`rig
|
|
19
|
+
Workspace-specific operations run as `rig <workspace>/<operation>`, for example `rig website-workspace/open-cmux` or `rig website-workspace/remove -y`.
|
|
20
20
|
|
|
21
21
|
`rig ls` lists workspaces for the selected project. `rig ls snapshots` lists cached snapshot runs, and `rig ls config` shows the resolved project paths.
|
|
22
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigkit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"log-symbols": "^7.0.1",
|
|
26
26
|
"log-update": "^8.0.0",
|
|
27
27
|
"ora": "^9.4.0",
|
|
28
|
-
"@rigkit/
|
|
29
|
-
"@rigkit/
|
|
30
|
-
"@rigkit/
|
|
28
|
+
"@rigkit/engine": "0.2.3",
|
|
29
|
+
"@rigkit/runtime-client": "0.2.3",
|
|
30
|
+
"@rigkit/provider-cmux": "0.2.3"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/bun": "latest",
|
package/src/cli.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { projectIdFor, runtimePaths, SUPPORTED_RUNTIME_API_VERSION } from "@rigkit/runtime-client";
|
|
5
|
+
import { projectIdFor, runtimeFingerprintFor, runtimePaths, SUPPORTED_RUNTIME_API_VERSION } from "@rigkit/runtime-client";
|
|
6
6
|
import { RIGKIT_CLI_VERSION } from "./version.ts";
|
|
7
7
|
|
|
8
8
|
const cliPath = join(import.meta.dir, "cli.ts");
|
|
@@ -83,8 +83,8 @@ describe("CLI entrypoint", () => {
|
|
|
83
83
|
|
|
84
84
|
expect(result.exitCode).toBe(0);
|
|
85
85
|
expect(result.stderr).toBe("");
|
|
86
|
-
expect(result.stdout).toContain("name
|
|
87
|
-
expect(result.stdout).toContain("api
|
|
86
|
+
expect(result.stdout).toContain("name workflow");
|
|
87
|
+
expect(result.stdout).toContain("api smoke");
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -100,8 +100,7 @@ describe("CLI entrypoint", () => {
|
|
|
100
100
|
workspaces: [{
|
|
101
101
|
name: "api",
|
|
102
102
|
workflow: "smoke",
|
|
103
|
-
|
|
104
|
-
snapshotId: "snap-api",
|
|
103
|
+
ctx: {},
|
|
105
104
|
}],
|
|
106
105
|
});
|
|
107
106
|
});
|
|
@@ -177,6 +176,7 @@ async function withWorkspaceRuntime(
|
|
|
177
176
|
mkdirSync(input.projectDir, { recursive: true });
|
|
178
177
|
writeFileSync(configPath, "export default {}\n");
|
|
179
178
|
const projectId = projectIdFor({ projectDir: input.projectDir, configPath });
|
|
179
|
+
const runtimeFingerprint = runtimeFingerprintFor({ projectDir: input.projectDir, configPath });
|
|
180
180
|
const paths = runtimePaths(projectId, rigkitHome);
|
|
181
181
|
mkdirSync(paths.root, { recursive: true });
|
|
182
182
|
writeFileSync(paths.tokenPath, `${token}\n`);
|
|
@@ -195,6 +195,7 @@ async function withWorkspaceRuntime(
|
|
|
195
195
|
return runtimeJson({
|
|
196
196
|
ok: true,
|
|
197
197
|
projectId,
|
|
198
|
+
runtimeFingerprint,
|
|
198
199
|
projectDir: input.projectDir,
|
|
199
200
|
configPath,
|
|
200
201
|
statePath: join(input.projectDir, ".rigkit", "state.sqlite"),
|
|
@@ -208,14 +209,8 @@ async function withWorkspaceRuntime(
|
|
|
208
209
|
workspaces: [{
|
|
209
210
|
id: "workspace-api",
|
|
210
211
|
name: "api",
|
|
211
|
-
providerId: "freestyle",
|
|
212
212
|
workflow: "smoke",
|
|
213
|
-
|
|
214
|
-
snapshotId: "snap-api",
|
|
215
|
-
sourceRef: null,
|
|
216
|
-
context: {},
|
|
217
|
-
metadata: {},
|
|
218
|
-
data: {},
|
|
213
|
+
ctx: {},
|
|
219
214
|
createdAt: now,
|
|
220
215
|
updatedAt: now,
|
|
221
216
|
}],
|
|
@@ -229,6 +224,7 @@ async function withWorkspaceRuntime(
|
|
|
229
224
|
paths.handlePath,
|
|
230
225
|
`${JSON.stringify({
|
|
231
226
|
projectId,
|
|
227
|
+
runtimeFingerprint,
|
|
232
228
|
projectDir: input.projectDir,
|
|
233
229
|
configPath,
|
|
234
230
|
pid: process.pid,
|
package/src/cli.ts
CHANGED
|
@@ -94,15 +94,8 @@ type EngineProjectInfo = {
|
|
|
94
94
|
};
|
|
95
95
|
|
|
96
96
|
type RuntimeOperationManifest = {
|
|
97
|
-
hostMethods?: {
|
|
98
|
-
known?: Array<{ id: string; modes?: string[] }>;
|
|
99
|
-
requiredByOperations?: Record<string, string[]>;
|
|
100
|
-
};
|
|
101
|
-
hostCapabilities?: {
|
|
102
|
-
optional?: Array<{ id: string; schemaHash?: string }>;
|
|
103
|
-
requiredByOperations?: Record<string, string[]>;
|
|
104
|
-
};
|
|
105
97
|
operations: RuntimeOperationDefinition[];
|
|
98
|
+
workspaceOperations?: RuntimeOperationDefinition[];
|
|
106
99
|
};
|
|
107
100
|
|
|
108
101
|
type RuntimeOperationDefinition = {
|
|
@@ -111,8 +104,6 @@ type RuntimeOperationDefinition = {
|
|
|
111
104
|
title?: string;
|
|
112
105
|
description?: string;
|
|
113
106
|
createsWorkspace?: boolean;
|
|
114
|
-
requiredHostMethods?: Array<{ id: string; modes?: string[] }>;
|
|
115
|
-
requiredHostCapabilities?: Array<{ id: string; schemaHash?: string }>;
|
|
116
107
|
cli?: {
|
|
117
108
|
positionals?: Array<{ name: string; index: number }>;
|
|
118
109
|
options?: Array<{
|
|
@@ -801,18 +792,18 @@ async function executeRuntimeOperation(
|
|
|
801
792
|
result: unknown;
|
|
802
793
|
}> {
|
|
803
794
|
const manifest = await readRuntimeOperations(runtime);
|
|
804
|
-
const
|
|
805
|
-
if (!
|
|
795
|
+
const resolved = findRuntimeOperation(manifest, requestedOperation);
|
|
796
|
+
if (!resolved) {
|
|
806
797
|
throw new Error(`This project does not define a Rigkit operation named "${requestedOperation}".`);
|
|
807
798
|
}
|
|
799
|
+
const { operation, runOperation } = resolved;
|
|
808
800
|
|
|
809
|
-
preflightHostSupport(operation);
|
|
810
801
|
const parsed = parseOperationArgs(operation, args);
|
|
811
802
|
enforceHostOnlyBooleanGuards(operation, parsed);
|
|
812
803
|
|
|
813
804
|
const result = await runRuntimeOperation<unknown>(
|
|
814
805
|
runtime,
|
|
815
|
-
|
|
806
|
+
runOperation,
|
|
816
807
|
parsed.input,
|
|
817
808
|
{ renderEvents: !wantsJson(invocation) },
|
|
818
809
|
);
|
|
@@ -821,50 +812,28 @@ async function executeRuntimeOperation(
|
|
|
821
812
|
}
|
|
822
813
|
|
|
823
814
|
function findRuntimeOperation(
|
|
824
|
-
|
|
815
|
+
manifest: RuntimeOperationManifest,
|
|
825
816
|
requestedOperation: string,
|
|
826
|
-
): RuntimeOperationDefinition | undefined {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
function preflightHostSupport(operation: RuntimeOperationDefinition): void {
|
|
833
|
-
const unsupportedMethod = operation.requiredHostMethods?.find((method) => {
|
|
834
|
-
const supported = CLI_HOST_METHODS.find((item) => item.id === method.id);
|
|
835
|
-
return !supported || !supportsModes(supported.modes, method.modes);
|
|
836
|
-
});
|
|
837
|
-
if (unsupportedMethod) {
|
|
838
|
-
throw new Error(
|
|
839
|
-
`Operation "${operation.id}" requires host method "${formatMethodRequirement(unsupportedMethod)}". ` +
|
|
840
|
-
`Upgrade Rigkit or use a host that supports this method.`,
|
|
841
|
-
);
|
|
817
|
+
): { operation: RuntimeOperationDefinition; runOperation: string } | undefined {
|
|
818
|
+
const workspaceOperation = parseWorkspaceOperationId(requestedOperation);
|
|
819
|
+
if (workspaceOperation) {
|
|
820
|
+
const operation = (manifest.workspaceOperations ?? []).find((item) => item.id === workspaceOperation.operation);
|
|
821
|
+
return operation ? { operation, runOperation: requestedOperation } : undefined;
|
|
842
822
|
}
|
|
843
823
|
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
if (unsupportedCapability) {
|
|
849
|
-
throw new Error(
|
|
850
|
-
`Operation "${operation.id}" requires host capability "${formatCapabilityRequirement(unsupportedCapability)}". ` +
|
|
851
|
-
`Install or enable a local host capability handler to use it from this host.`,
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
function supportsModes(hostModes: string[] | undefined, requiredModes: string[] | undefined): boolean {
|
|
857
|
-
if (!requiredModes?.length) return true;
|
|
858
|
-
const supported = new Set(hostModes ?? []);
|
|
859
|
-
return requiredModes.every((mode) => supported.has(mode));
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
function formatMethodRequirement(method: { id: string; modes?: string[] }): string {
|
|
863
|
-
return method.modes?.length ? `${method.id}:${method.modes.join("|")}` : method.id;
|
|
824
|
+
const operation = manifest.operations.find((operation) =>
|
|
825
|
+
operation.id === requestedOperation || operation.aliases?.includes(requestedOperation)
|
|
826
|
+
);
|
|
827
|
+
return operation ? { operation, runOperation: operation.id } : undefined;
|
|
864
828
|
}
|
|
865
829
|
|
|
866
|
-
function
|
|
867
|
-
|
|
830
|
+
function parseWorkspaceOperationId(value: string): { workspace: string; operation: string } | undefined {
|
|
831
|
+
const slash = value.indexOf("/");
|
|
832
|
+
if (slash <= 0 || slash === value.length - 1) return undefined;
|
|
833
|
+
return {
|
|
834
|
+
workspace: value.slice(0, slash),
|
|
835
|
+
operation: value.slice(slash + 1),
|
|
836
|
+
};
|
|
868
837
|
}
|
|
869
838
|
|
|
870
839
|
function parseOperationArgs(operation: RuntimeOperationDefinition, args: string[]): ParsedOperationInput {
|
|
@@ -1016,12 +985,12 @@ async function renderOperationResult(
|
|
|
1016
985
|
console.log("No changes applied.");
|
|
1017
986
|
return;
|
|
1018
987
|
}
|
|
1019
|
-
console.log(`resolved ${result.plan.workflow}
|
|
988
|
+
console.log(`resolved ${result.plan.workflow}`);
|
|
1020
989
|
return;
|
|
1021
990
|
}
|
|
1022
991
|
|
|
1023
992
|
if (operation.createsWorkspace && isWorkspaceRecord(result)) {
|
|
1024
|
-
console.log(
|
|
993
|
+
console.log(result.name);
|
|
1025
994
|
return;
|
|
1026
995
|
}
|
|
1027
996
|
|
|
@@ -1036,16 +1005,8 @@ async function renderOperationResult(
|
|
|
1036
1005
|
return;
|
|
1037
1006
|
}
|
|
1038
1007
|
|
|
1039
|
-
if (isRecord(result)) {
|
|
1040
|
-
const metadata = isRecord(result.metadata) ? result.metadata : {};
|
|
1041
|
-
if (typeof metadata.snapshotId === "string") {
|
|
1042
|
-
console.log(metadata.snapshotId);
|
|
1043
|
-
return;
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
1008
|
if (isWorkspaceRecord(result)) {
|
|
1048
|
-
console.log(
|
|
1009
|
+
console.log(result.name);
|
|
1049
1010
|
return;
|
|
1050
1011
|
}
|
|
1051
1012
|
|
|
@@ -1738,8 +1699,8 @@ function isWorkflowPlan(value: unknown): value is WorkflowPlan {
|
|
|
1738
1699
|
function isWorkspaceRecord(value: unknown): value is WorkspaceRecord {
|
|
1739
1700
|
return isRecord(value) &&
|
|
1740
1701
|
typeof value.name === "string" &&
|
|
1741
|
-
typeof value.
|
|
1742
|
-
|
|
1702
|
+
typeof value.workflow === "string" &&
|
|
1703
|
+
isRecord(value.ctx);
|
|
1743
1704
|
}
|
|
1744
1705
|
|
|
1745
1706
|
function isDevMachineEvent(value: unknown): value is DevMachineEvent {
|
|
@@ -1791,7 +1752,7 @@ function printPlan(plan: WorkflowPlan): void {
|
|
|
1791
1752
|
}
|
|
1792
1753
|
|
|
1793
1754
|
function printWorkspaces(
|
|
1794
|
-
workspaces: ReadonlyArray<Pick<WorkspaceRecord, "name" | "workflow" | "
|
|
1755
|
+
workspaces: ReadonlyArray<Pick<WorkspaceRecord, "name" | "workflow" | "createdAt">>,
|
|
1795
1756
|
): void {
|
|
1796
1757
|
if (workspaces.length === 0) {
|
|
1797
1758
|
console.log("No workspaces.");
|
|
@@ -1799,11 +1760,9 @@ function printWorkspaces(
|
|
|
1799
1760
|
}
|
|
1800
1761
|
|
|
1801
1762
|
printTable(
|
|
1802
|
-
["name", "
|
|
1763
|
+
["name", "workflow", "created"],
|
|
1803
1764
|
workspaces.map((workspace) => [
|
|
1804
1765
|
workspace.name,
|
|
1805
|
-
workspace.resourceId ?? "",
|
|
1806
|
-
workspace.snapshotId ?? "",
|
|
1807
1766
|
workspace.workflow,
|
|
1808
1767
|
workspace.createdAt,
|
|
1809
1768
|
]),
|
|
@@ -1903,7 +1862,7 @@ function renderEvent(event: DevMachineEvent): void {
|
|
|
1903
1862
|
console.error(`artifact ${event.providerId}:${event.kind}`);
|
|
1904
1863
|
return;
|
|
1905
1864
|
case "workspace.ready":
|
|
1906
|
-
console.error(`workspace ${event.workspaceId}
|
|
1865
|
+
console.error(`workspace ${event.workspaceId} ready`);
|
|
1907
1866
|
return;
|
|
1908
1867
|
default:
|
|
1909
1868
|
return;
|
package/src/completion.test.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { projectIdFor, runtimePaths, SUPPORTED_RUNTIME_API_VERSION } from "@rigkit/runtime-client";
|
|
5
|
+
import { projectIdFor, runtimeFingerprintFor, runtimePaths, SUPPORTED_RUNTIME_API_VERSION } from "@rigkit/runtime-client";
|
|
6
6
|
import { completeRig, formatCompletionItems, renderCompletionScript } from "./completion.ts";
|
|
7
7
|
|
|
8
8
|
describe("CLI completion", () => {
|
|
9
|
-
test("completes
|
|
9
|
+
test("completes workspace targets from the runtime", async () => {
|
|
10
10
|
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-completion-"));
|
|
11
11
|
await withWorkspaceRuntime({ projectDir }, async () => {
|
|
12
12
|
const items = await completeRig({
|
|
@@ -16,11 +16,11 @@ describe("CLI completion", () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
expect(items.map((item) => item.value)).toEqual(["api", "web"]);
|
|
19
|
-
expect(items[0]?.description).toBe("
|
|
19
|
+
expect(items[0]?.description).toBe("smoke");
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
test("
|
|
23
|
+
test("does not complete provider resource ids as workspace targets", async () => {
|
|
24
24
|
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-completion-"));
|
|
25
25
|
await withWorkspaceRuntime({ projectDir }, async () => {
|
|
26
26
|
const items = await completeRig({
|
|
@@ -29,7 +29,7 @@ describe("CLI completion", () => {
|
|
|
29
29
|
currentIndex: 2,
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
expect(items
|
|
32
|
+
expect(items).toEqual([]);
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -47,6 +47,26 @@ describe("CLI completion", () => {
|
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
test("completes workspace operation targets", async () => {
|
|
51
|
+
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-completion-"));
|
|
52
|
+
await withWorkspaceRuntime({ projectDir }, async () => {
|
|
53
|
+
const roots = await completeRig({
|
|
54
|
+
cwd: projectDir,
|
|
55
|
+
words: ["rig", "run", ""],
|
|
56
|
+
currentIndex: 2,
|
|
57
|
+
});
|
|
58
|
+
expect(roots.map((item) => item.value)).toContain("api/");
|
|
59
|
+
expect(roots.map((item) => item.value)).toContain("ssh");
|
|
60
|
+
|
|
61
|
+
const operations = await completeRig({
|
|
62
|
+
cwd: projectDir,
|
|
63
|
+
words: ["rig", "run", "api/"],
|
|
64
|
+
currentIndex: 2,
|
|
65
|
+
});
|
|
66
|
+
expect(operations.map((item) => item.value)).toEqual(["api/remove", "api/open-cmux"]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
50
70
|
test("formats shell completion items", () => {
|
|
51
71
|
const items = [{ value: "api", description: "vm-api" }];
|
|
52
72
|
|
|
@@ -77,6 +97,7 @@ async function withWorkspaceRuntime(
|
|
|
77
97
|
mkdirSync(input.projectDir, { recursive: true });
|
|
78
98
|
writeFileSync(configPath, "export default {}\n");
|
|
79
99
|
const projectId = projectIdFor({ projectDir: input.projectDir, configPath });
|
|
100
|
+
const runtimeFingerprint = runtimeFingerprintFor({ projectDir: input.projectDir, configPath });
|
|
80
101
|
const paths = runtimePaths(projectId, rigkitHome);
|
|
81
102
|
mkdirSync(paths.root, { recursive: true });
|
|
82
103
|
writeFileSync(paths.tokenPath, `${token}\n`);
|
|
@@ -94,6 +115,7 @@ async function withWorkspaceRuntime(
|
|
|
94
115
|
return runtimeJson({
|
|
95
116
|
ok: true,
|
|
96
117
|
projectId,
|
|
118
|
+
runtimeFingerprint,
|
|
97
119
|
projectDir: input.projectDir,
|
|
98
120
|
configPath,
|
|
99
121
|
engineVersion: "engine-test",
|
|
@@ -108,26 +130,16 @@ async function withWorkspaceRuntime(
|
|
|
108
130
|
{
|
|
109
131
|
id: "workspace-api",
|
|
110
132
|
name: "api",
|
|
111
|
-
providerId: "freestyle",
|
|
112
133
|
workflow: "smoke",
|
|
113
|
-
|
|
114
|
-
sourceRef: null,
|
|
115
|
-
context: {},
|
|
116
|
-
metadata: {},
|
|
117
|
-
data: {},
|
|
134
|
+
ctx: {},
|
|
118
135
|
createdAt: now,
|
|
119
136
|
updatedAt: now,
|
|
120
137
|
},
|
|
121
138
|
{
|
|
122
139
|
id: "workspace-web",
|
|
123
140
|
name: "web",
|
|
124
|
-
providerId: "freestyle",
|
|
125
141
|
workflow: "smoke",
|
|
126
|
-
|
|
127
|
-
sourceRef: null,
|
|
128
|
-
context: {},
|
|
129
|
-
metadata: {},
|
|
130
|
-
data: {},
|
|
142
|
+
ctx: {},
|
|
131
143
|
createdAt: now,
|
|
132
144
|
updatedAt: now,
|
|
133
145
|
},
|
|
@@ -136,14 +148,6 @@ async function withWorkspaceRuntime(
|
|
|
136
148
|
}
|
|
137
149
|
if (pathname === "/operations") {
|
|
138
150
|
return runtimeJson({
|
|
139
|
-
hostMethods: {
|
|
140
|
-
known: [],
|
|
141
|
-
requiredByOperations: {},
|
|
142
|
-
},
|
|
143
|
-
hostCapabilities: {
|
|
144
|
-
optional: [],
|
|
145
|
-
requiredByOperations: {},
|
|
146
|
-
},
|
|
147
151
|
operations: [
|
|
148
152
|
{
|
|
149
153
|
id: "ssh",
|
|
@@ -164,6 +168,35 @@ async function withWorkspaceRuntime(
|
|
|
164
168
|
},
|
|
165
169
|
},
|
|
166
170
|
],
|
|
171
|
+
workspaceOperations: [
|
|
172
|
+
{
|
|
173
|
+
id: "remove",
|
|
174
|
+
kind: "workspace-action",
|
|
175
|
+
source: "core",
|
|
176
|
+
title: "Remove",
|
|
177
|
+
description: "remove workspace",
|
|
178
|
+
cli: {
|
|
179
|
+
options: [{ name: "yes", flag: "--yes", aliases: ["-y"], type: "boolean", runtime: false }],
|
|
180
|
+
},
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
additionalProperties: false,
|
|
184
|
+
properties: {},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "open-cmux",
|
|
189
|
+
kind: "workspace-action",
|
|
190
|
+
source: "config",
|
|
191
|
+
title: "Open cmux",
|
|
192
|
+
description: "open cmux",
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: "object",
|
|
195
|
+
additionalProperties: false,
|
|
196
|
+
properties: {},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
167
200
|
});
|
|
168
201
|
}
|
|
169
202
|
return runtimeJson({ error: { message: "Not found" } }, { status: 404 });
|
|
@@ -174,6 +207,7 @@ async function withWorkspaceRuntime(
|
|
|
174
207
|
paths.handlePath,
|
|
175
208
|
`${JSON.stringify({
|
|
176
209
|
projectId,
|
|
210
|
+
runtimeFingerprint,
|
|
177
211
|
projectDir: input.projectDir,
|
|
178
212
|
configPath,
|
|
179
213
|
pid: process.pid,
|
package/src/completion.ts
CHANGED
|
@@ -17,6 +17,7 @@ type CompleteRigInput = {
|
|
|
17
17
|
const COMMANDS: CompletionItem[] = [
|
|
18
18
|
{ value: "help", description: "show CLI help" },
|
|
19
19
|
{ value: "init", description: "initialize a Rigkit project" },
|
|
20
|
+
{ value: "run", description: "run a project operation" },
|
|
20
21
|
{ value: "ls", description: "list project workspaces" },
|
|
21
22
|
{ value: "projects", description: "discover Rigkit projects" },
|
|
22
23
|
{ value: "doctor", description: "show runtime diagnostics" },
|
|
@@ -77,6 +78,7 @@ const OPTIONS_WITH_VALUES = new Set([
|
|
|
77
78
|
|
|
78
79
|
type RuntimeOperationManifest = {
|
|
79
80
|
operations: RuntimeOperationDefinition[];
|
|
81
|
+
workspaceOperations?: RuntimeOperationDefinition[];
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
type RuntimeOperationDefinition = {
|
|
@@ -121,7 +123,12 @@ export async function completeRig(input: CompleteRigInput): Promise<CompletionIt
|
|
|
121
123
|
}
|
|
122
124
|
return [];
|
|
123
125
|
}
|
|
124
|
-
return filterItems(
|
|
126
|
+
return filterItems(
|
|
127
|
+
current.startsWith("-")
|
|
128
|
+
? GLOBAL_OPTIONS
|
|
129
|
+
: [...COMMANDS, ...await safeOperationTargets(resolveProjectDir(words, cwd), current), ...GLOBAL_OPTIONS],
|
|
130
|
+
current,
|
|
131
|
+
);
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
if (current.startsWith("-")) {
|
|
@@ -147,7 +154,7 @@ export async function completeRig(input: CompleteRigInput): Promise<CompletionIt
|
|
|
147
154
|
if (command === "run") {
|
|
148
155
|
const run = parseRunCommand(before);
|
|
149
156
|
if (!run.operation) {
|
|
150
|
-
return filterItems(await safeOperationTargets(resolveProjectDir(words, cwd)), current);
|
|
157
|
+
return filterItems(await safeOperationTargets(resolveProjectDir(words, cwd), current), current);
|
|
151
158
|
}
|
|
152
159
|
const operation = await safeResolveRuntimeOperation(resolveProjectDir(words, cwd), run.operation);
|
|
153
160
|
const operationPositionalCount = countRunOperationPositionals(run.args);
|
|
@@ -355,48 +362,65 @@ function projectPaths(projectDir: string): { projectDir: string; configPath: str
|
|
|
355
362
|
|
|
356
363
|
async function workspaceTargets(
|
|
357
364
|
paths: { projectDir: string; configPath: string },
|
|
358
|
-
|
|
359
|
-
|
|
365
|
+
_current: string,
|
|
366
|
+
_includeVmIds: boolean,
|
|
360
367
|
): Promise<CompletionItem[]> {
|
|
361
368
|
const workspaces = await readWorkspaces(paths);
|
|
362
369
|
const items = workspaces.map((workspace) => ({
|
|
363
370
|
value: workspace.name,
|
|
364
|
-
description: workspace.
|
|
371
|
+
description: workspace.workflow,
|
|
365
372
|
}));
|
|
366
373
|
|
|
367
|
-
if (includeVmIds && current.length > 0) {
|
|
368
|
-
for (const workspace of workspaces) {
|
|
369
|
-
if (!workspace.resourceId) continue;
|
|
370
|
-
items.push({
|
|
371
|
-
value: workspace.resourceId,
|
|
372
|
-
description: workspace.name,
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
374
|
return dedupeItems(items);
|
|
378
375
|
}
|
|
379
376
|
|
|
380
|
-
async function readWorkspaces(paths: { projectDir: string; configPath: string }): Promise<Array<{ name: string;
|
|
377
|
+
async function readWorkspaces(paths: { projectDir: string; configPath: string }): Promise<Array<{ name: string; workflow: string }>> {
|
|
381
378
|
const runtime = await getOrStartRuntime(paths);
|
|
382
379
|
const { workspaces } = await runtime.control.workspaces();
|
|
383
380
|
return workspaces.map((workspace) => ({
|
|
384
381
|
name: workspace.name,
|
|
385
|
-
|
|
382
|
+
workflow: workspace.workflow,
|
|
386
383
|
}));
|
|
387
384
|
}
|
|
388
385
|
|
|
389
|
-
async function operationTargets(
|
|
386
|
+
async function operationTargets(
|
|
387
|
+
paths: { projectDir: string; configPath: string },
|
|
388
|
+
current: string,
|
|
389
|
+
): Promise<CompletionItem[]> {
|
|
390
390
|
const manifest = await readOperations(paths);
|
|
391
|
+
if (current.includes("/")) {
|
|
392
|
+
return workspaceOperationTargets(manifest, current);
|
|
393
|
+
}
|
|
394
|
+
const workspaces = await readWorkspaces(paths).catch(() => []);
|
|
391
395
|
return manifest.operations.flatMap((operation) => [
|
|
392
396
|
{ value: operation.id, description: operation.description },
|
|
393
397
|
...(operation.aliases ?? []).map((alias) => ({ value: alias, description: operation.description })),
|
|
398
|
+
]).concat(workspaces.map((workspace) => ({
|
|
399
|
+
value: `${workspace.name}/`,
|
|
400
|
+
description: `workspace ${workspace.workflow}`,
|
|
401
|
+
})));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function workspaceOperationTargets(manifest: RuntimeOperationManifest, current: string): CompletionItem[] {
|
|
405
|
+
const slash = current.indexOf("/");
|
|
406
|
+
if (slash < 0) return [];
|
|
407
|
+
const workspace = current.slice(0, slash);
|
|
408
|
+
if (!workspace) return [];
|
|
409
|
+
return (manifest.workspaceOperations ?? []).flatMap((operation) => [
|
|
410
|
+
{ value: `${workspace}/${operation.id}`, description: operation.description ?? "workspace operation" },
|
|
411
|
+
...(operation.aliases ?? []).map((alias) => ({
|
|
412
|
+
value: `${workspace}/${alias}`,
|
|
413
|
+
description: operation.description ?? "workspace operation",
|
|
414
|
+
})),
|
|
394
415
|
]);
|
|
395
416
|
}
|
|
396
417
|
|
|
397
|
-
async function safeOperationTargets(
|
|
418
|
+
async function safeOperationTargets(
|
|
419
|
+
paths: { projectDir: string; configPath: string },
|
|
420
|
+
current: string,
|
|
421
|
+
): Promise<CompletionItem[]> {
|
|
398
422
|
try {
|
|
399
|
-
return await operationTargets(paths);
|
|
423
|
+
return await operationTargets(paths, current);
|
|
400
424
|
} catch {
|
|
401
425
|
return [];
|
|
402
426
|
}
|
|
@@ -407,6 +431,12 @@ async function resolveRuntimeOperation(
|
|
|
407
431
|
operationId: string,
|
|
408
432
|
): Promise<RuntimeOperationDefinition | undefined> {
|
|
409
433
|
const manifest = await readOperations(paths);
|
|
434
|
+
const workspaceOperation = parseWorkspaceOperationId(operationId);
|
|
435
|
+
if (workspaceOperation) {
|
|
436
|
+
return (manifest.workspaceOperations ?? []).find((operation) =>
|
|
437
|
+
operation.id === workspaceOperation.operation || operation.aliases?.includes(workspaceOperation.operation)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
410
440
|
return manifest.operations.find((operation) =>
|
|
411
441
|
operation.id === operationId || operation.aliases?.includes(operationId)
|
|
412
442
|
);
|
|
@@ -428,6 +458,15 @@ async function readOperations(paths: { projectDir: string; configPath: string })
|
|
|
428
458
|
return await runtime.control.operations() as unknown as RuntimeOperationManifest;
|
|
429
459
|
}
|
|
430
460
|
|
|
461
|
+
function parseWorkspaceOperationId(value: string): { workspace: string; operation: string } | undefined {
|
|
462
|
+
const slash = value.indexOf("/");
|
|
463
|
+
if (slash <= 0 || slash === value.length - 1) return undefined;
|
|
464
|
+
return {
|
|
465
|
+
workspace: value.slice(0, slash),
|
|
466
|
+
operation: value.slice(slash + 1),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
431
470
|
function filterItems(items: CompletionItem[], current: string): CompletionItem[] {
|
|
432
471
|
return dedupeItems(items).filter((item) => item.value.startsWith(current));
|
|
433
472
|
}
|
package/src/init.ts
CHANGED
|
@@ -111,15 +111,16 @@ const dev = sequence(${workflowName})
|
|
|
111
111
|
}
|
|
112
112
|
return { vm: await vm.snapshotRef() };
|
|
113
113
|
})
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
.workspace({
|
|
115
|
+
create: async ({ workflow, providers }) => {
|
|
116
|
+
const vm = await providers.freestyle.vms.fromSnapshot(workflow.ctx.vm);
|
|
117
|
+
return {
|
|
118
|
+
vmId: vm.vmId,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
remove: async ({ providers, workspace }) => {
|
|
122
|
+
await providers.freestyle.vms.delete(workspace.ctx.vmId);
|
|
123
|
+
},
|
|
123
124
|
});
|
|
124
125
|
|
|
125
126
|
export default defineConfig({
|
package/src/run-presenter.ts
CHANGED
|
@@ -146,7 +146,7 @@ export function createRunPresenter(operation: string): RunPresenter | undefined
|
|
|
146
146
|
);
|
|
147
147
|
break;
|
|
148
148
|
case "workspace.ready":
|
|
149
|
-
phase = `Workspace ${String(event.
|
|
149
|
+
phase = `Workspace ${String(event.workspaceId ?? "ready")} ready`;
|
|
150
150
|
break;
|
|
151
151
|
case "run.completed":
|
|
152
152
|
finalStatus = "completed";
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RIGKIT_CLI_VERSION = "0.2.
|
|
1
|
+
export const RIGKIT_CLI_VERSION = "0.2.3";
|