@rigkit/cli 0.1.8
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 +25 -0
- package/package.json +45 -0
- package/src/cli.test.ts +253 -0
- package/src/cli.ts +1911 -0
- package/src/completion.test.ts +204 -0
- package/src/completion.ts +444 -0
- package/src/init.test.ts +83 -0
- package/src/init.ts +242 -0
- package/src/interaction.test.ts +28 -0
- package/src/interaction.ts +33 -0
- package/src/project.test.ts +51 -0
- package/src/project.ts +94 -0
- package/src/remote-project.test.ts +55 -0
- package/src/remote-project.ts +225 -0
- package/src/run-presenter.ts +373 -0
- package/src/version.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @rigkit/cli
|
|
2
|
+
|
|
3
|
+
Global `rig` CLI.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i -g @rigkit/cli
|
|
7
|
+
rig init
|
|
8
|
+
rig plan
|
|
9
|
+
rig ls
|
|
10
|
+
rig plan github:owner/repo
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`rig init` asks for a project name, Freestyle API key, and package manager. It creates a project folder containing a workflow-based `rig.config.ts`, `.env`, `.env.example`, `package.json`, and local ignore rules.
|
|
14
|
+
|
|
15
|
+
Interactive terminals use Inquirer prompts and a chalk/log-update run timeline. Set `RIGKIT_RENDER=0` to force the plain text renderer; `--json` and flag-driven flows remain suitable for agents and scripts.
|
|
16
|
+
|
|
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
|
+
|
|
19
|
+
`rig ssh <workspace>` runs the workflow's uncached `workspace.onOpen` hook before attaching or printing the SSH command.
|
|
20
|
+
|
|
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
|
+
|
|
23
|
+
Remote GitHub targets are materialized into `~/.rigkit/projects`, use state outside the checkout, and require explicit trust before installing dependencies or executing the remote config.
|
|
24
|
+
|
|
25
|
+
Projects should install matching `@rigkit/sdk` versions locally.
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rigkit/cli",
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/freestyle-sh/rigkit.git",
|
|
8
|
+
"directory": "packages/cli"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"rig": "./src/cli.ts"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/cli.ts",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"chalk": "^5.6.2",
|
|
23
|
+
"commander": "^14.0.3",
|
|
24
|
+
"inquirer": "^13.4.3",
|
|
25
|
+
"log-symbols": "^7.0.1",
|
|
26
|
+
"log-update": "^8.0.0",
|
|
27
|
+
"ora": "^9.4.0",
|
|
28
|
+
"@rigkit/engine": "0.1.8",
|
|
29
|
+
"@rigkit/runtime-client": "0.1.8",
|
|
30
|
+
"@rigkit/provider-cmux": "0.1.8"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/bun": "latest",
|
|
34
|
+
"typescript": "latest"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"rig": "bun src/cli.ts",
|
|
41
|
+
"build": "tsc --noEmit",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "bun test"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/cli.test.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { projectIdFor, runtimePaths, SUPPORTED_RUNTIME_API_VERSION } from "@rigkit/runtime-client";
|
|
6
|
+
import { RIGKIT_CLI_VERSION } from "./version.ts";
|
|
7
|
+
|
|
8
|
+
const cliPath = join(import.meta.dir, "cli.ts");
|
|
9
|
+
|
|
10
|
+
describe("CLI entrypoint", () => {
|
|
11
|
+
test("renders CLI diagnostics as JSON", async () => {
|
|
12
|
+
const result = await runCli(["doctor", "--cli", "--json"]);
|
|
13
|
+
|
|
14
|
+
expect(result.exitCode).toBe(0);
|
|
15
|
+
expect(result.stderr).toBe("");
|
|
16
|
+
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
17
|
+
cliVersion: RIGKIT_CLI_VERSION,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("exposes static help and version bootstrap commands", async () => {
|
|
22
|
+
const rootHelp = await runCli([]);
|
|
23
|
+
expect(rootHelp.exitCode).toBe(0);
|
|
24
|
+
expect(rootHelp.stderr).toBe("");
|
|
25
|
+
expect(rootHelp.stdout).toContain("rig ");
|
|
26
|
+
expect(rootHelp.stdout).toContain("<operation> Run a project operation exposed by the runtime");
|
|
27
|
+
|
|
28
|
+
const version = await runCli(["version"]);
|
|
29
|
+
expect(version.exitCode).toBe(0);
|
|
30
|
+
expect(version.stderr).toBe("");
|
|
31
|
+
expect(version.stdout.trim()).toBe(RIGKIT_CLI_VERSION);
|
|
32
|
+
|
|
33
|
+
const help = await runCli(["help"]);
|
|
34
|
+
expect(help.exitCode).toBe(0);
|
|
35
|
+
expect(help.stderr).toBe("");
|
|
36
|
+
expect(help.stdout).toContain("rig ");
|
|
37
|
+
expect(help.stdout).toContain("<operation> Run a project operation exposed by the runtime");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("treats unknown root tokens as project operations", async () => {
|
|
41
|
+
const result = await runCli(["unknown"]);
|
|
42
|
+
|
|
43
|
+
expect(result.exitCode).toBe(1);
|
|
44
|
+
expect(result.stdout).toBe("");
|
|
45
|
+
expect(result.stderr).toContain("No Rigkit config found");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("serves dynamic shell completion endpoint", async () => {
|
|
49
|
+
const result = await runCli(["__complete", "--shell", "zsh", "--index", "1", "--", "rig", "v"]);
|
|
50
|
+
|
|
51
|
+
expect(result.exitCode).toBe(0);
|
|
52
|
+
expect(result.stderr).toBe("");
|
|
53
|
+
expect(result.stdout.trim()).toBe("version\tshow CLI version");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("discovers projects without starting a runtime", async () => {
|
|
57
|
+
const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-projects-"));
|
|
58
|
+
mkdirSync(join(cwd, "api"));
|
|
59
|
+
writeFileSync(join(cwd, "api", "rig.config.ts"), "export default {}\n");
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const result = await runCli(["projects", "--json"], { cwd });
|
|
63
|
+
const realCwd = realpathSync(cwd);
|
|
64
|
+
|
|
65
|
+
expect(result.exitCode).toBe(0);
|
|
66
|
+
expect(result.stderr).toBe("");
|
|
67
|
+
expect(JSON.parse(result.stdout)).toEqual({
|
|
68
|
+
projects: [{
|
|
69
|
+
projectDir: join(realCwd, "api"),
|
|
70
|
+
configPath: join(realCwd, "api", "rig.config.ts"),
|
|
71
|
+
}],
|
|
72
|
+
});
|
|
73
|
+
} finally {
|
|
74
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("lists workspaces from the project runtime", async () => {
|
|
79
|
+
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-cli-ls-"));
|
|
80
|
+
|
|
81
|
+
await withWorkspaceRuntime({ projectDir }, async ({ env }) => {
|
|
82
|
+
const result = await runCli(["-C", projectDir, "ls"], { env });
|
|
83
|
+
|
|
84
|
+
expect(result.exitCode).toBe(0);
|
|
85
|
+
expect(result.stderr).toBe("");
|
|
86
|
+
expect(result.stdout).toContain("name resource snapshot workflow");
|
|
87
|
+
expect(result.stdout).toContain("api vm-api snap-api smoke");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("lists workspaces as JSON", async () => {
|
|
92
|
+
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-cli-ls-json-"));
|
|
93
|
+
|
|
94
|
+
await withWorkspaceRuntime({ projectDir }, async ({ env }) => {
|
|
95
|
+
const result = await runCli(["-C", projectDir, "ls", "--json"], { env });
|
|
96
|
+
|
|
97
|
+
expect(result.exitCode).toBe(0);
|
|
98
|
+
expect(result.stderr).toBe("");
|
|
99
|
+
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
100
|
+
workspaces: [{
|
|
101
|
+
name: "api",
|
|
102
|
+
workflow: "smoke",
|
|
103
|
+
resourceId: "vm-api",
|
|
104
|
+
snapshotId: "snap-api",
|
|
105
|
+
}],
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("requires discovered projects for operation --all", async () => {
|
|
111
|
+
const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-run-all-"));
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const result = await runCli(["plan", "--all", "--json"], { cwd });
|
|
115
|
+
|
|
116
|
+
expect(result.exitCode).toBe(1);
|
|
117
|
+
expect(result.stdout).toBe("");
|
|
118
|
+
expect(result.stderr).toContain("No Rigkit projects found.");
|
|
119
|
+
} finally {
|
|
120
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("requires selection for operation --discover with multiple projects", async () => {
|
|
125
|
+
const cwd = mkdtempSync(join(tmpdir(), "rigkit-cli-run-discover-"));
|
|
126
|
+
mkdirSync(join(cwd, "api"));
|
|
127
|
+
mkdirSync(join(cwd, "web"));
|
|
128
|
+
writeFileSync(join(cwd, "api", "rig.config.ts"), "export default {}\n");
|
|
129
|
+
writeFileSync(join(cwd, "web", "rig.config.ts"), "export default {}\n");
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const result = await runCli(["plan", "--discover", "--json"], { cwd });
|
|
133
|
+
|
|
134
|
+
expect(result.exitCode).toBe(1);
|
|
135
|
+
expect(result.stdout).toBe("");
|
|
136
|
+
expect(result.stderr).toContain("Multiple Rigkit projects found.");
|
|
137
|
+
expect(result.stderr).toContain("pass --all");
|
|
138
|
+
expect(result.stderr).toContain(join(realpathSync(cwd), "api", "rig.config.ts"));
|
|
139
|
+
expect(result.stderr).toContain(join(realpathSync(cwd), "web", "rig.config.ts"));
|
|
140
|
+
} finally {
|
|
141
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
async function runCli(
|
|
147
|
+
args: string[],
|
|
148
|
+
options: { cwd?: string; env?: Record<string, string | undefined> } = {},
|
|
149
|
+
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
150
|
+
const proc = Bun.spawn(["bun", cliPath, ...args], {
|
|
151
|
+
cwd: options.cwd,
|
|
152
|
+
stdout: "pipe",
|
|
153
|
+
stderr: "pipe",
|
|
154
|
+
env: {
|
|
155
|
+
...process.env,
|
|
156
|
+
...options.env,
|
|
157
|
+
FORCE_COLOR: "0",
|
|
158
|
+
NO_COLOR: "1",
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
163
|
+
proc.exited,
|
|
164
|
+
new Response(proc.stdout).text(),
|
|
165
|
+
new Response(proc.stderr).text(),
|
|
166
|
+
]);
|
|
167
|
+
return { exitCode, stdout, stderr };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function withWorkspaceRuntime(
|
|
171
|
+
input: { projectDir: string },
|
|
172
|
+
run: (context: { env: Record<string, string> }) => Promise<void>,
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
const rigkitHome = mkdtempSync(join(tmpdir(), "rigkit-home-"));
|
|
175
|
+
const token = "test-token";
|
|
176
|
+
const configPath = join(input.projectDir, "rig.config.ts");
|
|
177
|
+
mkdirSync(input.projectDir, { recursive: true });
|
|
178
|
+
writeFileSync(configPath, "export default {}\n");
|
|
179
|
+
const projectId = projectIdFor({ projectDir: input.projectDir, configPath });
|
|
180
|
+
const paths = runtimePaths(projectId, rigkitHome);
|
|
181
|
+
mkdirSync(paths.root, { recursive: true });
|
|
182
|
+
writeFileSync(paths.tokenPath, `${token}\n`);
|
|
183
|
+
|
|
184
|
+
const now = new Date(0).toISOString();
|
|
185
|
+
const server = Bun.serve({
|
|
186
|
+
hostname: "127.0.0.1",
|
|
187
|
+
port: 0,
|
|
188
|
+
fetch(request) {
|
|
189
|
+
if (request.headers.get("authorization") !== `Bearer ${token}`) {
|
|
190
|
+
return runtimeJson({ error: { message: "Unauthorized" } }, { status: 401 });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const { pathname } = new URL(request.url);
|
|
194
|
+
if (pathname === "/health") {
|
|
195
|
+
return runtimeJson({
|
|
196
|
+
ok: true,
|
|
197
|
+
projectId,
|
|
198
|
+
projectDir: input.projectDir,
|
|
199
|
+
configPath,
|
|
200
|
+
statePath: join(input.projectDir, ".rigkit", "state.sqlite"),
|
|
201
|
+
engineVersion: "engine-test",
|
|
202
|
+
runtimeVersion: "runtime-test",
|
|
203
|
+
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (pathname === "/workspaces") {
|
|
207
|
+
return runtimeJson({
|
|
208
|
+
workspaces: [{
|
|
209
|
+
id: "workspace-api",
|
|
210
|
+
name: "api",
|
|
211
|
+
providerId: "freestyle",
|
|
212
|
+
workflow: "smoke",
|
|
213
|
+
resourceId: "vm-api",
|
|
214
|
+
snapshotId: "snap-api",
|
|
215
|
+
sourceRef: null,
|
|
216
|
+
context: {},
|
|
217
|
+
metadata: {},
|
|
218
|
+
data: {},
|
|
219
|
+
createdAt: now,
|
|
220
|
+
updatedAt: now,
|
|
221
|
+
}],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return runtimeJson({ error: { message: "Not found" } }, { status: 404 });
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
writeFileSync(
|
|
229
|
+
paths.handlePath,
|
|
230
|
+
`${JSON.stringify({
|
|
231
|
+
projectId,
|
|
232
|
+
projectDir: input.projectDir,
|
|
233
|
+
configPath,
|
|
234
|
+
pid: process.pid,
|
|
235
|
+
url: `http://127.0.0.1:${server.port}`,
|
|
236
|
+
tokenPath: paths.tokenPath,
|
|
237
|
+
}, null, 2)}\n`,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
await run({ env: { RIGKIT_HOME: rigkitHome } });
|
|
242
|
+
} finally {
|
|
243
|
+
server.stop(true);
|
|
244
|
+
rmSync(rigkitHome, { recursive: true, force: true });
|
|
245
|
+
rmSync(input.projectDir, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function runtimeJson(body: unknown, init: ResponseInit = {}): Response {
|
|
250
|
+
const headers = new Headers(init.headers);
|
|
251
|
+
headers.set("x-rigkit-api-version", String(SUPPORTED_RUNTIME_API_VERSION));
|
|
252
|
+
return Response.json(body, { ...init, headers });
|
|
253
|
+
}
|