@a3t/rapid 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1 -1
- package/dist/{chunk-FICHP3SD.js → chunk-C6XSZWFT.js} +1535 -39
- package/dist/chunk-C6XSZWFT.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +10 -2
- package/dist/chunk-FICHP3SD.js.map +0 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { Command as
|
|
3
|
-
import { setLogLevel, logger as
|
|
4
|
-
import { readFileSync } from "fs";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import { dirname, join as
|
|
2
|
+
import { Command as Command13 } from "commander";
|
|
3
|
+
import { setLogLevel, logger as logger14 } from "@a3t/rapid-core";
|
|
4
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
9
9
|
import { Command } from "commander";
|
|
10
|
-
import { writeFile, access, readFile, readdir } from "fs/promises";
|
|
10
|
+
import { writeFile, access, readFile, readdir, mkdir } from "fs/promises";
|
|
11
11
|
import { join } from "path";
|
|
12
|
+
import { existsSync } from "fs";
|
|
12
13
|
import {
|
|
13
14
|
getDefaultConfig,
|
|
14
15
|
logger,
|
|
@@ -195,7 +196,283 @@ async function downloadRemoteTemplate(parsed, destDir, spinner) {
|
|
|
195
196
|
return false;
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
|
-
var
|
|
199
|
+
var PREBUILT_IMAGE_REGISTRY = "ghcr.io/a3tai/rapid-devcontainer";
|
|
200
|
+
var PREBUILT_IMAGES = {
|
|
201
|
+
typescript: `${PREBUILT_IMAGE_REGISTRY}-typescript:latest`,
|
|
202
|
+
javascript: `${PREBUILT_IMAGE_REGISTRY}-typescript:latest`,
|
|
203
|
+
python: `${PREBUILT_IMAGE_REGISTRY}-python:latest`,
|
|
204
|
+
rust: `${PREBUILT_IMAGE_REGISTRY}-rust:latest`,
|
|
205
|
+
go: `${PREBUILT_IMAGE_REGISTRY}-go:latest`,
|
|
206
|
+
universal: `${PREBUILT_IMAGE_REGISTRY}-universal:latest`,
|
|
207
|
+
infrastructure: `${PREBUILT_IMAGE_REGISTRY}-infrastructure:latest`
|
|
208
|
+
};
|
|
209
|
+
var TEMPLATE_CUSTOMIZATIONS = {
|
|
210
|
+
typescript: {
|
|
211
|
+
vscode: {
|
|
212
|
+
extensions: [
|
|
213
|
+
"dbaeumer.vscode-eslint",
|
|
214
|
+
"esbenp.prettier-vscode",
|
|
215
|
+
"bradlc.vscode-tailwindcss",
|
|
216
|
+
"prisma.prisma",
|
|
217
|
+
"mikestead.dotenv"
|
|
218
|
+
],
|
|
219
|
+
settings: {
|
|
220
|
+
"editor.formatOnSave": true,
|
|
221
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
python: {
|
|
226
|
+
vscode: {
|
|
227
|
+
extensions: [
|
|
228
|
+
"ms-python.python",
|
|
229
|
+
"ms-python.vscode-pylance",
|
|
230
|
+
"charliermarsh.ruff",
|
|
231
|
+
"ms-toolsai.jupyter"
|
|
232
|
+
],
|
|
233
|
+
settings: {
|
|
234
|
+
"[python]": {
|
|
235
|
+
"editor.formatOnSave": true,
|
|
236
|
+
"editor.defaultFormatter": "charliermarsh.ruff"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
rust: {
|
|
242
|
+
vscode: {
|
|
243
|
+
extensions: ["rust-lang.rust-analyzer", "tamasfe.even-better-toml", "vadimcn.vscode-lldb"],
|
|
244
|
+
settings: { "[rust]": { "editor.formatOnSave": true } }
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
go: {
|
|
248
|
+
vscode: {
|
|
249
|
+
extensions: ["golang.go", "zxh404.vscode-proto3"],
|
|
250
|
+
settings: { "[go]": { "editor.formatOnSave": true } }
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
universal: {
|
|
254
|
+
vscode: {
|
|
255
|
+
extensions: [
|
|
256
|
+
"dbaeumer.vscode-eslint",
|
|
257
|
+
"esbenp.prettier-vscode",
|
|
258
|
+
"ms-python.python",
|
|
259
|
+
"golang.go"
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
infrastructure: {
|
|
264
|
+
vscode: {
|
|
265
|
+
extensions: [
|
|
266
|
+
"hashicorp.terraform",
|
|
267
|
+
"ms-kubernetes-tools.vscode-kubernetes-tools",
|
|
268
|
+
"redhat.vscode-yaml"
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
function getPrebuiltConfig(templateName, containerEnv, postStartCommand) {
|
|
274
|
+
const image = PREBUILT_IMAGES[templateName] ?? PREBUILT_IMAGES.universal;
|
|
275
|
+
const customizations = TEMPLATE_CUSTOMIZATIONS[templateName] ?? TEMPLATE_CUSTOMIZATIONS.universal;
|
|
276
|
+
const remoteUser = templateName === "typescript" ? "node" : "vscode";
|
|
277
|
+
return {
|
|
278
|
+
name: `RAPID ${templateName.charAt(0).toUpperCase() + templateName.slice(1)} (Pre-built)`,
|
|
279
|
+
image,
|
|
280
|
+
customizations,
|
|
281
|
+
containerEnv,
|
|
282
|
+
postStartCommand,
|
|
283
|
+
remoteUser
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function getDevContainerConfig(detected, usePrebuilt = false) {
|
|
287
|
+
const baseFeatures = {
|
|
288
|
+
"ghcr.io/devcontainers/features/git:1": {},
|
|
289
|
+
"ghcr.io/devcontainers-contrib/features/direnv:1": {},
|
|
290
|
+
"ghcr.io/devcontainers-contrib/features/starship:1": {},
|
|
291
|
+
"ghcr.io/devcontainers-contrib/features/1password-cli:1": {}
|
|
292
|
+
};
|
|
293
|
+
const containerEnv = {
|
|
294
|
+
OP_SERVICE_ACCOUNT_TOKEN: "${localEnv:OP_SERVICE_ACCOUNT_TOKEN}"
|
|
295
|
+
};
|
|
296
|
+
const postCreateBase = "npm install -g @anthropic-ai/claude-code && curl -fsSL https://opencode.ai/install | bash";
|
|
297
|
+
const postStartCommand = "direnv allow 2>/dev/null || true";
|
|
298
|
+
const language = detected?.language || "unknown";
|
|
299
|
+
const templateName = language === "javascript" ? "typescript" : language === "unknown" ? "universal" : language;
|
|
300
|
+
if (usePrebuilt && PREBUILT_IMAGES[templateName]) {
|
|
301
|
+
return getPrebuiltConfig(templateName, containerEnv, postStartCommand);
|
|
302
|
+
}
|
|
303
|
+
switch (language) {
|
|
304
|
+
case "typescript":
|
|
305
|
+
case "javascript":
|
|
306
|
+
return {
|
|
307
|
+
name: "RAPID TypeScript",
|
|
308
|
+
image: "mcr.microsoft.com/devcontainers/typescript-node:22",
|
|
309
|
+
features: {
|
|
310
|
+
...baseFeatures,
|
|
311
|
+
"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
|
|
312
|
+
},
|
|
313
|
+
customizations: {
|
|
314
|
+
vscode: {
|
|
315
|
+
extensions: [
|
|
316
|
+
"dbaeumer.vscode-eslint",
|
|
317
|
+
"esbenp.prettier-vscode",
|
|
318
|
+
"bradlc.vscode-tailwindcss",
|
|
319
|
+
"prisma.prisma",
|
|
320
|
+
"mikestead.dotenv"
|
|
321
|
+
],
|
|
322
|
+
settings: {
|
|
323
|
+
"editor.formatOnSave": true,
|
|
324
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
325
|
+
"editor.codeActionsOnSave": {
|
|
326
|
+
"source.fixAll.eslint": "explicit"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
containerEnv,
|
|
332
|
+
postCreateCommand: postCreateBase,
|
|
333
|
+
postStartCommand,
|
|
334
|
+
remoteUser: "node"
|
|
335
|
+
};
|
|
336
|
+
case "python":
|
|
337
|
+
return {
|
|
338
|
+
name: "RAPID Python",
|
|
339
|
+
image: "mcr.microsoft.com/devcontainers/python:3.12",
|
|
340
|
+
features: {
|
|
341
|
+
...baseFeatures,
|
|
342
|
+
"ghcr.io/devcontainers/features/node:1": { version: "22" },
|
|
343
|
+
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
|
344
|
+
"ghcr.io/devcontainers-contrib/features/uv:1": {}
|
|
345
|
+
},
|
|
346
|
+
customizations: {
|
|
347
|
+
vscode: {
|
|
348
|
+
extensions: [
|
|
349
|
+
"ms-python.python",
|
|
350
|
+
"ms-python.vscode-pylance",
|
|
351
|
+
"ms-python.debugpy",
|
|
352
|
+
"charliermarsh.ruff",
|
|
353
|
+
"ms-toolsai.jupyter",
|
|
354
|
+
"tamasfe.even-better-toml"
|
|
355
|
+
],
|
|
356
|
+
settings: {
|
|
357
|
+
"python.defaultInterpreterPath": "/usr/local/bin/python",
|
|
358
|
+
"[python]": {
|
|
359
|
+
"editor.formatOnSave": true,
|
|
360
|
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
|
361
|
+
"editor.codeActionsOnSave": {
|
|
362
|
+
"source.fixAll": "explicit",
|
|
363
|
+
"source.organizeImports": "explicit"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
containerEnv,
|
|
370
|
+
postCreateCommand: `${postCreateBase} && pip install aider-chat`,
|
|
371
|
+
postStartCommand,
|
|
372
|
+
remoteUser: "vscode"
|
|
373
|
+
};
|
|
374
|
+
case "rust":
|
|
375
|
+
return {
|
|
376
|
+
name: "RAPID Rust",
|
|
377
|
+
image: "mcr.microsoft.com/devcontainers/rust:latest",
|
|
378
|
+
features: {
|
|
379
|
+
...baseFeatures,
|
|
380
|
+
"ghcr.io/devcontainers/features/node:1": { version: "22" }
|
|
381
|
+
},
|
|
382
|
+
customizations: {
|
|
383
|
+
vscode: {
|
|
384
|
+
extensions: [
|
|
385
|
+
"rust-lang.rust-analyzer",
|
|
386
|
+
"tamasfe.even-better-toml",
|
|
387
|
+
"serayuzgur.crates",
|
|
388
|
+
"vadimcn.vscode-lldb"
|
|
389
|
+
],
|
|
390
|
+
settings: {
|
|
391
|
+
"rust-analyzer.checkOnSave.command": "clippy",
|
|
392
|
+
"[rust]": {
|
|
393
|
+
"editor.formatOnSave": true,
|
|
394
|
+
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
containerEnv,
|
|
400
|
+
postCreateCommand: `${postCreateBase} && rustup component add clippy rustfmt`,
|
|
401
|
+
postStartCommand,
|
|
402
|
+
remoteUser: "vscode"
|
|
403
|
+
};
|
|
404
|
+
case "go":
|
|
405
|
+
return {
|
|
406
|
+
name: "RAPID Go",
|
|
407
|
+
image: "mcr.microsoft.com/devcontainers/go:1.23",
|
|
408
|
+
features: {
|
|
409
|
+
...baseFeatures,
|
|
410
|
+
"ghcr.io/devcontainers/features/node:1": { version: "22" }
|
|
411
|
+
},
|
|
412
|
+
customizations: {
|
|
413
|
+
vscode: {
|
|
414
|
+
extensions: ["golang.go", "zxh404.vscode-proto3", "tamasfe.even-better-toml"],
|
|
415
|
+
settings: {
|
|
416
|
+
"go.useLanguageServer": true,
|
|
417
|
+
"go.lintTool": "golangci-lint",
|
|
418
|
+
"go.lintFlags": ["--fast"],
|
|
419
|
+
"[go]": {
|
|
420
|
+
"editor.formatOnSave": true,
|
|
421
|
+
"editor.codeActionsOnSave": {
|
|
422
|
+
"source.organizeImports": "explicit"
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
containerEnv,
|
|
429
|
+
postCreateCommand: `${postCreateBase} && go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest && go install github.com/air-verse/air@latest`,
|
|
430
|
+
postStartCommand,
|
|
431
|
+
remoteUser: "vscode"
|
|
432
|
+
};
|
|
433
|
+
default:
|
|
434
|
+
return {
|
|
435
|
+
name: "RAPID Universal",
|
|
436
|
+
image: "mcr.microsoft.com/devcontainers/base:ubuntu",
|
|
437
|
+
features: {
|
|
438
|
+
...baseFeatures,
|
|
439
|
+
"ghcr.io/devcontainers/features/node:1": { version: "22" },
|
|
440
|
+
"ghcr.io/devcontainers/features/python:1": { version: "3.12" },
|
|
441
|
+
"ghcr.io/devcontainers/features/go:1": { version: "1.23" },
|
|
442
|
+
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
|
|
443
|
+
},
|
|
444
|
+
customizations: {
|
|
445
|
+
vscode: {
|
|
446
|
+
extensions: [
|
|
447
|
+
"dbaeumer.vscode-eslint",
|
|
448
|
+
"esbenp.prettier-vscode",
|
|
449
|
+
"ms-python.python",
|
|
450
|
+
"ms-python.vscode-pylance",
|
|
451
|
+
"golang.go",
|
|
452
|
+
"tamasfe.even-better-toml",
|
|
453
|
+
"redhat.vscode-yaml"
|
|
454
|
+
]
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
containerEnv,
|
|
458
|
+
postCreateCommand: `${postCreateBase} && pip install aider-chat`,
|
|
459
|
+
postStartCommand,
|
|
460
|
+
remoteUser: "vscode"
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async function createDevContainer(dir, detected, force = false, usePrebuilt = false) {
|
|
465
|
+
const devcontainerDir = join(dir, ".devcontainer");
|
|
466
|
+
const devcontainerJsonPath = join(devcontainerDir, "devcontainer.json");
|
|
467
|
+
if (!force && existsSync(devcontainerJsonPath)) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
await mkdir(devcontainerDir, { recursive: true });
|
|
471
|
+
const config = getDevContainerConfig(detected, usePrebuilt);
|
|
472
|
+
await writeFile(devcontainerJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
var initCommand = new Command("init").description("Initialize RAPID in a project").argument("[template]", "Template: builtin name, github:user/repo, npm:package, or URL").option("--force", "Overwrite existing files", false).option("--agent <name>", "Default agent to configure", "claude").option("--no-devcontainer", "Skip devcontainer creation").option("--prebuilt", "Use pre-built devcontainer images from ghcr.io (faster startup)", false).option("--mcp <servers>", "MCP servers to enable (comma-separated)", "context7,tavily").option("--no-mcp", "Skip MCP server configuration").option("--no-detect", "Skip auto-detection of project type").action(async (templateArg, options) => {
|
|
199
476
|
const spinner = ora("Initializing RAPID...").start();
|
|
200
477
|
try {
|
|
201
478
|
const cwd = process.cwd();
|
|
@@ -290,6 +567,17 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
290
567
|
spinner.text = "Creating AGENTS.md...";
|
|
291
568
|
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
292
569
|
await writeFile(agentsMdPath, getAgentsMdTemplate(cwd, detectedProject));
|
|
570
|
+
let devcontainerCreated = false;
|
|
571
|
+
const usePrebuilt = options.prebuilt === true;
|
|
572
|
+
if (options.devcontainer !== false) {
|
|
573
|
+
spinner.text = usePrebuilt ? "Creating devcontainer configuration (using pre-built image)..." : "Creating devcontainer configuration...";
|
|
574
|
+
devcontainerCreated = await createDevContainer(
|
|
575
|
+
cwd,
|
|
576
|
+
detectedProject,
|
|
577
|
+
options.force,
|
|
578
|
+
usePrebuilt
|
|
579
|
+
);
|
|
580
|
+
}
|
|
293
581
|
spinner.succeed("RAPID initialized successfully!");
|
|
294
582
|
if (detectedProject && detectedProject.language !== "unknown") {
|
|
295
583
|
logger.blank();
|
|
@@ -309,6 +597,9 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
309
597
|
console.log(` ${logger.dim("\u2022")} .mcp.json`);
|
|
310
598
|
console.log(` ${logger.dim("\u2022")} opencode.json`);
|
|
311
599
|
}
|
|
600
|
+
if (devcontainerCreated) {
|
|
601
|
+
console.log(` ${logger.dim("\u2022")} .devcontainer/devcontainer.json`);
|
|
602
|
+
}
|
|
312
603
|
console.log(` ${logger.dim("\u2022")} CLAUDE.md`);
|
|
313
604
|
console.log(` ${logger.dim("\u2022")} AGENTS.md`);
|
|
314
605
|
if (mcpServers.length > 0) {
|
|
@@ -323,11 +614,21 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
323
614
|
}
|
|
324
615
|
logger.blank();
|
|
325
616
|
logger.info("Next steps:");
|
|
326
|
-
|
|
327
|
-
console.log(
|
|
617
|
+
let stepNum = 1;
|
|
618
|
+
console.log(
|
|
619
|
+
` ${logger.dim(`${stepNum++}.`)} Run ${logger.brand("rapid dev")} to start coding`
|
|
620
|
+
);
|
|
621
|
+
console.log(
|
|
622
|
+
` ${logger.dim(`${stepNum++}.`)} Edit ${logger.dim("rapid.json")} to customize your setup`
|
|
623
|
+
);
|
|
328
624
|
if (mcpServers.length > 0) {
|
|
329
625
|
console.log(
|
|
330
|
-
` ${logger.dim(
|
|
626
|
+
` ${logger.dim(`${stepNum++}.`)} Add API keys to ${logger.dim("secrets.items")} in rapid.json`
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (devcontainerCreated) {
|
|
630
|
+
console.log(
|
|
631
|
+
` ${logger.dim(`${stepNum++}.`)} Set ${logger.dim("OP_SERVICE_ACCOUNT_TOKEN")} env var for 1Password secrets`
|
|
331
632
|
);
|
|
332
633
|
}
|
|
333
634
|
logger.blank();
|
|
@@ -449,8 +750,8 @@ ${GIT_GUIDELINES}
|
|
|
449
750
|
|
|
450
751
|
// src/commands/dev.ts
|
|
451
752
|
import { Command as Command2 } from "commander";
|
|
452
|
-
import { writeFile as
|
|
453
|
-
import { isAbsolute, join as
|
|
753
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
754
|
+
import { isAbsolute, join as join4 } from "path";
|
|
454
755
|
import {
|
|
455
756
|
loadConfig,
|
|
456
757
|
getAgent,
|
|
@@ -469,10 +770,547 @@ import {
|
|
|
469
770
|
agentSupportsRuntimeInjection
|
|
470
771
|
} from "@a3t/rapid-core";
|
|
471
772
|
import ora2 from "ora";
|
|
773
|
+
|
|
774
|
+
// src/utils/worktree.ts
|
|
775
|
+
import { execa } from "execa";
|
|
776
|
+
import { access as access2 } from "fs/promises";
|
|
777
|
+
import { basename, dirname, join as join2, resolve } from "path";
|
|
778
|
+
function getErrorMessage(err) {
|
|
779
|
+
if (typeof err.stderr === "string" && err.stderr) {
|
|
780
|
+
return err.stderr;
|
|
781
|
+
}
|
|
782
|
+
return err.message;
|
|
783
|
+
}
|
|
784
|
+
async function isGitRepo(dir) {
|
|
785
|
+
try {
|
|
786
|
+
await execa("git", ["rev-parse", "--git-dir"], { cwd: dir });
|
|
787
|
+
return true;
|
|
788
|
+
} catch {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async function getGitRoot(dir) {
|
|
793
|
+
const { stdout } = await execa("git", ["rev-parse", "--show-toplevel"], { cwd: dir });
|
|
794
|
+
return stdout.trim();
|
|
795
|
+
}
|
|
796
|
+
async function getCurrentBranch(dir) {
|
|
797
|
+
try {
|
|
798
|
+
const { stdout: symbolicRef } = await execa("git", ["symbolic-ref", "-q", "HEAD"], {
|
|
799
|
+
cwd: dir,
|
|
800
|
+
reject: false
|
|
801
|
+
});
|
|
802
|
+
if (!symbolicRef) {
|
|
803
|
+
return { name: null, isDefault: false, detached: true };
|
|
804
|
+
}
|
|
805
|
+
const branchName = symbolicRef.trim().replace("refs/heads/", "");
|
|
806
|
+
const isDefault = branchName === "main" || branchName === "master";
|
|
807
|
+
return { name: branchName, isDefault, detached: false };
|
|
808
|
+
} catch {
|
|
809
|
+
return { name: null, isDefault: false, detached: true };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function getDefaultBranch(dir) {
|
|
813
|
+
try {
|
|
814
|
+
const { stdout } = await execa("git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], {
|
|
815
|
+
cwd: dir,
|
|
816
|
+
reject: false
|
|
817
|
+
});
|
|
818
|
+
if (stdout) {
|
|
819
|
+
return stdout.trim().replace("origin/", "");
|
|
820
|
+
}
|
|
821
|
+
const { stdout: branches } = await execa("git", ["branch", "--list", "main", "master"], {
|
|
822
|
+
cwd: dir
|
|
823
|
+
});
|
|
824
|
+
if (branches.includes("main")) return "main";
|
|
825
|
+
if (branches.includes("master")) return "master";
|
|
826
|
+
return "main";
|
|
827
|
+
} catch {
|
|
828
|
+
return "main";
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
async function listWorktrees(dir) {
|
|
832
|
+
const { stdout } = await execa("git", ["worktree", "list", "--porcelain"], { cwd: dir });
|
|
833
|
+
const worktrees = [];
|
|
834
|
+
let current = {};
|
|
835
|
+
for (const line of stdout.split("\n")) {
|
|
836
|
+
if (line.startsWith("worktree ")) {
|
|
837
|
+
if (current.path) {
|
|
838
|
+
worktrees.push(current);
|
|
839
|
+
}
|
|
840
|
+
current = {
|
|
841
|
+
path: line.substring(9),
|
|
842
|
+
isMain: false,
|
|
843
|
+
locked: false,
|
|
844
|
+
exists: true,
|
|
845
|
+
prunable: false
|
|
846
|
+
};
|
|
847
|
+
} else if (line.startsWith("HEAD ")) {
|
|
848
|
+
current.head = line.substring(5);
|
|
849
|
+
} else if (line.startsWith("branch ")) {
|
|
850
|
+
current.branch = line.substring(7).replace("refs/heads/", "");
|
|
851
|
+
} else if (line === "bare") {
|
|
852
|
+
current.isMain = true;
|
|
853
|
+
} else if (line === "locked") {
|
|
854
|
+
current.locked = true;
|
|
855
|
+
} else if (line === "prunable") {
|
|
856
|
+
current.prunable = true;
|
|
857
|
+
current.exists = false;
|
|
858
|
+
} else if (line === "detached") {
|
|
859
|
+
current.branch = null;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (current.path) {
|
|
863
|
+
worktrees.push(current);
|
|
864
|
+
}
|
|
865
|
+
if (worktrees.length > 0 && worktrees[0]) {
|
|
866
|
+
worktrees[0].isMain = true;
|
|
867
|
+
}
|
|
868
|
+
for (const wt of worktrees) {
|
|
869
|
+
try {
|
|
870
|
+
await access2(wt.path);
|
|
871
|
+
wt.exists = true;
|
|
872
|
+
} catch {
|
|
873
|
+
wt.exists = false;
|
|
874
|
+
wt.prunable = true;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return worktrees;
|
|
878
|
+
}
|
|
879
|
+
async function findWorktreeByBranch(dir, branch) {
|
|
880
|
+
const worktrees = await listWorktrees(dir);
|
|
881
|
+
return worktrees.find((wt) => wt.branch === branch) ?? null;
|
|
882
|
+
}
|
|
883
|
+
function generateWorktreePath(repoRoot, branchName) {
|
|
884
|
+
const projectName = basename(repoRoot);
|
|
885
|
+
const parentDir = dirname(repoRoot);
|
|
886
|
+
const safeBranchName = branchName.replace(/\//g, "-").replace(/[^a-zA-Z0-9-_]/g, "").toLowerCase();
|
|
887
|
+
return join2(parentDir, `${projectName}-${safeBranchName}`);
|
|
888
|
+
}
|
|
889
|
+
async function createWorktree(repoRoot, worktreePath, branch, options = {}) {
|
|
890
|
+
try {
|
|
891
|
+
const existing = await listWorktrees(repoRoot);
|
|
892
|
+
const existingAtPath = existing.find((wt) => resolve(wt.path) === resolve(worktreePath));
|
|
893
|
+
if (existingAtPath && existingAtPath.exists && !options.force) {
|
|
894
|
+
return { success: true, path: worktreePath };
|
|
895
|
+
}
|
|
896
|
+
if (existingAtPath && !existingAtPath.exists) {
|
|
897
|
+
await execa("git", ["worktree", "remove", "--force", worktreePath], {
|
|
898
|
+
cwd: repoRoot,
|
|
899
|
+
reject: false
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
const args = ["worktree", "add"];
|
|
903
|
+
if (options.force) {
|
|
904
|
+
args.push("--force");
|
|
905
|
+
}
|
|
906
|
+
if (options.newBranch) {
|
|
907
|
+
args.push("-b", options.newBranch);
|
|
908
|
+
}
|
|
909
|
+
args.push(worktreePath);
|
|
910
|
+
if (!options.newBranch) {
|
|
911
|
+
args.push(branch);
|
|
912
|
+
} else if (options.startPoint) {
|
|
913
|
+
args.push(options.startPoint);
|
|
914
|
+
}
|
|
915
|
+
await execa("git", args, { cwd: repoRoot });
|
|
916
|
+
return { success: true, path: worktreePath };
|
|
917
|
+
} catch (err) {
|
|
918
|
+
const error = err;
|
|
919
|
+
return {
|
|
920
|
+
success: false,
|
|
921
|
+
path: worktreePath,
|
|
922
|
+
error: getErrorMessage(error)
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
async function removeWorktree(repoRoot, worktreePath, options = {}) {
|
|
927
|
+
try {
|
|
928
|
+
const args = ["worktree", "remove"];
|
|
929
|
+
if (options.force) {
|
|
930
|
+
args.push("--force");
|
|
931
|
+
}
|
|
932
|
+
args.push(worktreePath);
|
|
933
|
+
await execa("git", args, { cwd: repoRoot });
|
|
934
|
+
return { success: true };
|
|
935
|
+
} catch (err) {
|
|
936
|
+
const error = err;
|
|
937
|
+
return {
|
|
938
|
+
success: false,
|
|
939
|
+
error: getErrorMessage(error)
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
async function pruneWorktrees(repoRoot) {
|
|
944
|
+
try {
|
|
945
|
+
const worktrees = await listWorktrees(repoRoot);
|
|
946
|
+
const prunable = worktrees.filter((wt) => wt.prunable).map((wt) => wt.path);
|
|
947
|
+
await execa("git", ["worktree", "prune"], { cwd: repoRoot });
|
|
948
|
+
return { success: true, pruned: prunable };
|
|
949
|
+
} catch (err) {
|
|
950
|
+
const error = err;
|
|
951
|
+
return {
|
|
952
|
+
success: false,
|
|
953
|
+
pruned: [],
|
|
954
|
+
error: getErrorMessage(error)
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
async function getOrCreateWorktreeForBranch(dir) {
|
|
959
|
+
const gitRoot = await getGitRoot(dir);
|
|
960
|
+
const branch = await getCurrentBranch(dir);
|
|
961
|
+
if (branch.isDefault || branch.detached || !branch.name) {
|
|
962
|
+
return { path: gitRoot, created: false, isMain: true };
|
|
963
|
+
}
|
|
964
|
+
const existing = await findWorktreeByBranch(gitRoot, branch.name);
|
|
965
|
+
if (existing && existing.exists) {
|
|
966
|
+
return { path: existing.path, created: false, isMain: existing.isMain };
|
|
967
|
+
}
|
|
968
|
+
const worktreePath = generateWorktreePath(gitRoot, branch.name);
|
|
969
|
+
const result = await createWorktree(gitRoot, worktreePath, branch.name);
|
|
970
|
+
if (!result.success) {
|
|
971
|
+
return { path: gitRoot, created: false, isMain: true };
|
|
972
|
+
}
|
|
973
|
+
return { path: worktreePath, created: true, isMain: false };
|
|
974
|
+
}
|
|
975
|
+
async function cleanupMergedWorktrees(repoRoot) {
|
|
976
|
+
const removed = [];
|
|
977
|
+
const errors = [];
|
|
978
|
+
const defaultBranch = await getDefaultBranch(repoRoot);
|
|
979
|
+
const { stdout } = await execa("git", ["branch", "--merged", defaultBranch], { cwd: repoRoot });
|
|
980
|
+
const mergedBranches = stdout.split("\n").map((b) => b.trim().replace(/^\*\s*/, "")).filter((b) => b && b !== defaultBranch);
|
|
981
|
+
const worktrees = await listWorktrees(repoRoot);
|
|
982
|
+
for (const wt of worktrees) {
|
|
983
|
+
if (wt.isMain || !wt.branch) continue;
|
|
984
|
+
if (mergedBranches.includes(wt.branch)) {
|
|
985
|
+
const result = await removeWorktree(repoRoot, wt.path, { force: true });
|
|
986
|
+
if (result.success) {
|
|
987
|
+
removed.push(wt.path);
|
|
988
|
+
} else if (result.error) {
|
|
989
|
+
errors.push(`${wt.path}: ${result.error}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return { removed, errors };
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// src/isolation/lima.ts
|
|
997
|
+
import { execa as execa2 } from "execa";
|
|
998
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
999
|
+
import { homedir, platform } from "os";
|
|
1000
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
1001
|
+
import { fileURLToPath } from "url";
|
|
1002
|
+
function getErrorMessage2(err) {
|
|
1003
|
+
if (typeof err.stderr === "string" && err.stderr) {
|
|
1004
|
+
return err.stderr;
|
|
1005
|
+
}
|
|
1006
|
+
return err.message;
|
|
1007
|
+
}
|
|
1008
|
+
function getOutputString(output) {
|
|
1009
|
+
if (typeof output === "string") {
|
|
1010
|
+
return output;
|
|
1011
|
+
}
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
var RAPID_LIMA_INSTANCE = "rapid";
|
|
1015
|
+
var RAPID_LIMA_DIR = join3(homedir(), ".rapid", "lima");
|
|
1016
|
+
async function hasLima() {
|
|
1017
|
+
try {
|
|
1018
|
+
await execa2("limactl", ["--version"]);
|
|
1019
|
+
return true;
|
|
1020
|
+
} catch {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function isMacOS() {
|
|
1025
|
+
return platform() === "darwin";
|
|
1026
|
+
}
|
|
1027
|
+
function getLimaTemplatePath() {
|
|
1028
|
+
const __dirname3 = dirname2(fileURLToPath(import.meta.url));
|
|
1029
|
+
const possiblePaths = [
|
|
1030
|
+
join3(__dirname3, "../../../../templates/lima.yaml"),
|
|
1031
|
+
// From dist/isolation/
|
|
1032
|
+
join3(__dirname3, "../../../templates/lima.yaml"),
|
|
1033
|
+
// From src/isolation/
|
|
1034
|
+
join3(homedir(), ".rapid", "lima.yaml")
|
|
1035
|
+
// User config
|
|
1036
|
+
];
|
|
1037
|
+
return possiblePaths[0];
|
|
1038
|
+
}
|
|
1039
|
+
async function listInstances() {
|
|
1040
|
+
try {
|
|
1041
|
+
const { stdout } = await execa2("limactl", ["list", "--json"]);
|
|
1042
|
+
const instances = JSON.parse(stdout);
|
|
1043
|
+
return instances.map((inst) => {
|
|
1044
|
+
const result = {
|
|
1045
|
+
name: inst.name,
|
|
1046
|
+
status: inst.status,
|
|
1047
|
+
arch: inst.arch,
|
|
1048
|
+
cpus: inst.cpus,
|
|
1049
|
+
memory: `${Math.round(inst.memory / 1024 / 1024 / 1024)}GiB`,
|
|
1050
|
+
disk: `${Math.round(inst.disk / 1024 / 1024 / 1024)}GiB`,
|
|
1051
|
+
dir: inst.dir
|
|
1052
|
+
};
|
|
1053
|
+
if (inst.sshLocalPort !== void 0) {
|
|
1054
|
+
result.sshLocalPort = inst.sshLocalPort;
|
|
1055
|
+
}
|
|
1056
|
+
return result;
|
|
1057
|
+
});
|
|
1058
|
+
} catch {
|
|
1059
|
+
return [];
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async function getInstance(name = RAPID_LIMA_INSTANCE) {
|
|
1063
|
+
const instances = await listInstances();
|
|
1064
|
+
return instances.find((i) => i.name === name) ?? null;
|
|
1065
|
+
}
|
|
1066
|
+
async function instanceExists(name = RAPID_LIMA_INSTANCE) {
|
|
1067
|
+
const instance = await getInstance(name);
|
|
1068
|
+
return instance !== null;
|
|
1069
|
+
}
|
|
1070
|
+
async function isRunning(name = RAPID_LIMA_INSTANCE) {
|
|
1071
|
+
const instance = await getInstance(name);
|
|
1072
|
+
return instance?.status === "Running";
|
|
1073
|
+
}
|
|
1074
|
+
function insertProjectMount(config, projectDir) {
|
|
1075
|
+
const projectMount = ` - location: "${projectDir}"
|
|
1076
|
+
writable: true
|
|
1077
|
+
`;
|
|
1078
|
+
const homeMountRegex = /mounts:\s*\n(\s*- location: ['"]~['"]\n(?:\s{2,}[^-][^\n]*\n)*)/;
|
|
1079
|
+
return config.replace(homeMountRegex, (_match, homeMount) => {
|
|
1080
|
+
return `mounts:
|
|
1081
|
+
${homeMount}${projectMount}`;
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
async function createLimaConfig(projectDir, options = {}) {
|
|
1085
|
+
const templatePath = getLimaTemplatePath();
|
|
1086
|
+
const configDir = RAPID_LIMA_DIR;
|
|
1087
|
+
const configPath = join3(configDir, "lima.yaml");
|
|
1088
|
+
await mkdir2(configDir, { recursive: true });
|
|
1089
|
+
let template;
|
|
1090
|
+
try {
|
|
1091
|
+
template = await readFile2(templatePath, "utf-8");
|
|
1092
|
+
} catch {
|
|
1093
|
+
template = getMinimalLimaConfig();
|
|
1094
|
+
}
|
|
1095
|
+
let config = template;
|
|
1096
|
+
config = insertProjectMount(config, projectDir);
|
|
1097
|
+
if (options.cpus) {
|
|
1098
|
+
config = config.replace(/cpus: \d+/, `cpus: ${options.cpus}`);
|
|
1099
|
+
}
|
|
1100
|
+
if (options.memory) {
|
|
1101
|
+
config = config.replace(/memory: "[^"]*"/, `memory: "${options.memory}"`);
|
|
1102
|
+
}
|
|
1103
|
+
if (options.disk) {
|
|
1104
|
+
config = config.replace(/disk: "[^"]*"/, `disk: "${options.disk}"`);
|
|
1105
|
+
}
|
|
1106
|
+
if (options.env) {
|
|
1107
|
+
const envLines = Object.entries(options.env).map(([key, value]) => ` ${key}: "${value}"`).join("\n");
|
|
1108
|
+
config = config.replace(/env:[\s\S]*?(?=\n\w|\n#|$)/, `env:
|
|
1109
|
+
${envLines}
|
|
1110
|
+
`);
|
|
1111
|
+
}
|
|
1112
|
+
await writeFile2(configPath, config);
|
|
1113
|
+
return configPath;
|
|
1114
|
+
}
|
|
1115
|
+
function getMinimalLimaConfig() {
|
|
1116
|
+
return `
|
|
1117
|
+
vmType: "vz"
|
|
1118
|
+
vmOpts:
|
|
1119
|
+
vz:
|
|
1120
|
+
rosetta:
|
|
1121
|
+
enabled: true
|
|
1122
|
+
binfmt: true
|
|
1123
|
+
|
|
1124
|
+
cpus: 4
|
|
1125
|
+
memory: "8GiB"
|
|
1126
|
+
disk: "50GiB"
|
|
1127
|
+
images:
|
|
1128
|
+
- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img"
|
|
1129
|
+
arch: "aarch64"
|
|
1130
|
+
- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
|
|
1131
|
+
arch: "x86_64"
|
|
1132
|
+
mountType: "virtiofs"
|
|
1133
|
+
mounts:
|
|
1134
|
+
- location: "~"
|
|
1135
|
+
writable: true
|
|
1136
|
+
mountInotify: true
|
|
1137
|
+
ssh:
|
|
1138
|
+
forwardAgent: true
|
|
1139
|
+
localPort: 0
|
|
1140
|
+
networks:
|
|
1141
|
+
- vzNAT: true
|
|
1142
|
+
containerd:
|
|
1143
|
+
system: true
|
|
1144
|
+
user: false
|
|
1145
|
+
portForwards:
|
|
1146
|
+
- guestPort: 3000
|
|
1147
|
+
hostPort: 3000
|
|
1148
|
+
- guestPort: 8080
|
|
1149
|
+
hostPort: 8080
|
|
1150
|
+
provision:
|
|
1151
|
+
- mode: system
|
|
1152
|
+
script: |
|
|
1153
|
+
#!/bin/bash
|
|
1154
|
+
set -eux
|
|
1155
|
+
apt-get update
|
|
1156
|
+
apt-get install -y build-essential curl git jq
|
|
1157
|
+
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
|
1158
|
+
apt-get install -y nodejs
|
|
1159
|
+
npm install -g pnpm
|
|
1160
|
+
- mode: user
|
|
1161
|
+
script: |
|
|
1162
|
+
#!/bin/bash
|
|
1163
|
+
set -eux
|
|
1164
|
+
npm install -g @anthropic-ai/claude-code || true
|
|
1165
|
+
curl -fsSL https://opencode.ai/install | bash || true
|
|
1166
|
+
`;
|
|
1167
|
+
}
|
|
1168
|
+
async function startInstance(projectDir, options = {}) {
|
|
1169
|
+
const name = RAPID_LIMA_INSTANCE;
|
|
1170
|
+
if (!await hasLima()) {
|
|
1171
|
+
return {
|
|
1172
|
+
success: false,
|
|
1173
|
+
error: "Lima is not installed. Install with: brew install lima"
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
if (!isMacOS()) {
|
|
1177
|
+
return {
|
|
1178
|
+
success: false,
|
|
1179
|
+
error: "Lima is only available on macOS"
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
try {
|
|
1183
|
+
const exists = await instanceExists(name);
|
|
1184
|
+
if (exists) {
|
|
1185
|
+
if (await isRunning(name)) {
|
|
1186
|
+
return { success: true };
|
|
1187
|
+
}
|
|
1188
|
+
await execa2("limactl", ["start", name], {
|
|
1189
|
+
timeout: (options.timeout ?? 300) * 1e3
|
|
1190
|
+
});
|
|
1191
|
+
} else {
|
|
1192
|
+
const configPath = await createLimaConfig(projectDir, options);
|
|
1193
|
+
await execa2("limactl", ["start", "--name", name, configPath], {
|
|
1194
|
+
timeout: (options.timeout ?? 600) * 1e3,
|
|
1195
|
+
stdio: "inherit"
|
|
1196
|
+
// Show progress
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
return { success: true };
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
const error = err;
|
|
1202
|
+
return {
|
|
1203
|
+
success: false,
|
|
1204
|
+
error: getErrorMessage2(error)
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
async function stopInstance(name = RAPID_LIMA_INSTANCE, options = {}) {
|
|
1209
|
+
try {
|
|
1210
|
+
const args = ["stop"];
|
|
1211
|
+
if (options.force) {
|
|
1212
|
+
args.push("--force");
|
|
1213
|
+
}
|
|
1214
|
+
args.push(name);
|
|
1215
|
+
await execa2("limactl", args);
|
|
1216
|
+
return { success: true };
|
|
1217
|
+
} catch (err) {
|
|
1218
|
+
const error = err;
|
|
1219
|
+
return {
|
|
1220
|
+
success: false,
|
|
1221
|
+
error: getErrorMessage2(error)
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
async function deleteInstance(name = RAPID_LIMA_INSTANCE, options = {}) {
|
|
1226
|
+
try {
|
|
1227
|
+
const args = ["delete"];
|
|
1228
|
+
if (options.force) {
|
|
1229
|
+
args.push("--force");
|
|
1230
|
+
}
|
|
1231
|
+
args.push(name);
|
|
1232
|
+
await execa2("limactl", args);
|
|
1233
|
+
return { success: true };
|
|
1234
|
+
} catch (err) {
|
|
1235
|
+
const error = err;
|
|
1236
|
+
return {
|
|
1237
|
+
success: false,
|
|
1238
|
+
error: getErrorMessage2(error)
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
async function execInLima(command, options = {}) {
|
|
1243
|
+
const name = options.name ?? RAPID_LIMA_INSTANCE;
|
|
1244
|
+
try {
|
|
1245
|
+
let fullCommand = command.join(" ");
|
|
1246
|
+
if (options.cwd) {
|
|
1247
|
+
fullCommand = `cd "${options.cwd}" && ${fullCommand}`;
|
|
1248
|
+
}
|
|
1249
|
+
if (options.env) {
|
|
1250
|
+
const exports = Object.entries(options.env).map(([key, value]) => `export ${key}="${value}"`).join(" && ");
|
|
1251
|
+
fullCommand = `${exports} && ${fullCommand}`;
|
|
1252
|
+
}
|
|
1253
|
+
const useInheritStdio = options.interactive || options.tty;
|
|
1254
|
+
const result = await execa2(
|
|
1255
|
+
"limactl",
|
|
1256
|
+
["shell", name, "--", "bash", "-c", fullCommand],
|
|
1257
|
+
useInheritStdio ? { stdio: "inherit" } : {}
|
|
1258
|
+
);
|
|
1259
|
+
const successResult = {
|
|
1260
|
+
success: true
|
|
1261
|
+
};
|
|
1262
|
+
const stdout = getOutputString(result.stdout);
|
|
1263
|
+
const stderr = getOutputString(result.stderr);
|
|
1264
|
+
if (stdout) successResult.stdout = stdout;
|
|
1265
|
+
if (stderr) successResult.stderr = stderr;
|
|
1266
|
+
return successResult;
|
|
1267
|
+
} catch (err) {
|
|
1268
|
+
const error = err;
|
|
1269
|
+
const errorResult = {
|
|
1270
|
+
success: false,
|
|
1271
|
+
error: error.message
|
|
1272
|
+
};
|
|
1273
|
+
const stdout = getOutputString(error.stdout);
|
|
1274
|
+
const stderr = getOutputString(error.stderr);
|
|
1275
|
+
if (stdout) errorResult.stdout = stdout;
|
|
1276
|
+
if (stderr) errorResult.stderr = stderr;
|
|
1277
|
+
return errorResult;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
async function shellInLima(options = {}) {
|
|
1281
|
+
const name = options.name ?? RAPID_LIMA_INSTANCE;
|
|
1282
|
+
const args = ["shell", name];
|
|
1283
|
+
if (options.cwd) {
|
|
1284
|
+
args.push("--workdir", options.cwd);
|
|
1285
|
+
}
|
|
1286
|
+
if (options.command) {
|
|
1287
|
+
args.push("--", options.command);
|
|
1288
|
+
}
|
|
1289
|
+
await execa2("limactl", args, { stdio: "inherit" });
|
|
1290
|
+
}
|
|
1291
|
+
async function setupGitSsh(name = RAPID_LIMA_INSTANCE) {
|
|
1292
|
+
try {
|
|
1293
|
+
const result = await execInLima(["ssh-add", "-l"], { name });
|
|
1294
|
+
if (!result.success) {
|
|
1295
|
+
return {
|
|
1296
|
+
success: false,
|
|
1297
|
+
error: "SSH agent forwarding is not working. Make sure ssh-agent is running on the host."
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
return { success: true };
|
|
1301
|
+
} catch (err) {
|
|
1302
|
+
return {
|
|
1303
|
+
success: false,
|
|
1304
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// src/commands/dev.ts
|
|
472
1310
|
var devCommand = new Command2("dev").description("Launch AI coding session in the dev container").option("-a, --agent <name>", "Agent to use").option(
|
|
473
1311
|
"--multi [agents]",
|
|
474
1312
|
"Launch multiple agents (comma-separated, or interactive if no value)"
|
|
475
|
-
).option("--list", "List available agents without launching").option("--local", "Run locally instead of in container (not recommended)").option("--no-start", "Do not auto-start container if stopped").action(async (options) => {
|
|
1313
|
+
).option("--list", "List available agents without launching").option("--local", "Run locally instead of in container (not recommended)").option("--no-start", "Do not auto-start container if stopped").option("--no-worktree", "Skip automatic worktree creation for feature branches").action(async (options) => {
|
|
476
1314
|
try {
|
|
477
1315
|
const spinner = ora2("Loading configuration...").start();
|
|
478
1316
|
const loaded = await loadConfig();
|
|
@@ -480,12 +1318,34 @@ var devCommand = new Command2("dev").description("Launch AI coding session in th
|
|
|
480
1318
|
spinner.fail("No rapid.json found. Run `rapid init` first.");
|
|
481
1319
|
process.exit(1);
|
|
482
1320
|
}
|
|
483
|
-
const { config
|
|
1321
|
+
const { config } = loaded;
|
|
1322
|
+
let { rootDir } = loaded;
|
|
484
1323
|
spinner.succeed("Configuration loaded");
|
|
485
1324
|
if (options.list) {
|
|
486
1325
|
listAgents(config);
|
|
487
1326
|
return;
|
|
488
1327
|
}
|
|
1328
|
+
if (options.worktree !== false && await isGitRepo(rootDir)) {
|
|
1329
|
+
const branch = await getCurrentBranch(rootDir);
|
|
1330
|
+
if (!branch.isDefault && !branch.detached && branch.name) {
|
|
1331
|
+
spinner.start(`Checking worktree for branch: ${branch.name}...`);
|
|
1332
|
+
try {
|
|
1333
|
+
const worktree = await getOrCreateWorktreeForBranch(rootDir);
|
|
1334
|
+
if (worktree.created) {
|
|
1335
|
+
spinner.succeed(`Created worktree: ${worktree.path}`);
|
|
1336
|
+
rootDir = worktree.path;
|
|
1337
|
+
} else if (!worktree.isMain) {
|
|
1338
|
+
spinner.succeed(`Using worktree: ${worktree.path}`);
|
|
1339
|
+
rootDir = worktree.path;
|
|
1340
|
+
} else {
|
|
1341
|
+
spinner.info(`Using main directory (branch: ${branch.name})`);
|
|
1342
|
+
}
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
spinner.warn("Could not create worktree, using main directory");
|
|
1345
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
489
1349
|
if (options.multi !== void 0) {
|
|
490
1350
|
await runMultiAgent(config, rootDir, options);
|
|
491
1351
|
return;
|
|
@@ -625,7 +1485,7 @@ async function prepareMcpEnv(rootDir, mcp) {
|
|
|
625
1485
|
return void 0;
|
|
626
1486
|
}
|
|
627
1487
|
const configFile = mcp.configFile ?? ".mcp.json";
|
|
628
|
-
const configPath = isAbsolute(configFile) ? configFile :
|
|
1488
|
+
const configPath = isAbsolute(configFile) ? configFile : join4(rootDir, configFile);
|
|
629
1489
|
const servers = {};
|
|
630
1490
|
for (const [name, serverConfig] of Object.entries(mcp.servers)) {
|
|
631
1491
|
if (!serverConfig || typeof serverConfig !== "object") {
|
|
@@ -640,14 +1500,14 @@ async function prepareMcpEnv(rootDir, mcp) {
|
|
|
640
1500
|
if (Object.keys(servers).length === 0) {
|
|
641
1501
|
return void 0;
|
|
642
1502
|
}
|
|
643
|
-
await
|
|
1503
|
+
await writeFile3(configPath, `${JSON.stringify({ servers }, null, 2)}
|
|
644
1504
|
`, "utf-8");
|
|
645
1505
|
return {
|
|
646
1506
|
MCP_CONFIG_FILE: configFile
|
|
647
1507
|
};
|
|
648
1508
|
}
|
|
649
1509
|
async function runLocally(agent, agentName, rootDir, config) {
|
|
650
|
-
const { execa } = await import("execa");
|
|
1510
|
+
const { execa: execa4 } = await import("execa");
|
|
651
1511
|
const status = await checkAgentAvailable(agent);
|
|
652
1512
|
if (!status.available) {
|
|
653
1513
|
logger2.error(`${agentName} CLI not found locally`);
|
|
@@ -720,14 +1580,18 @@ async function runLocally(agent, agentName, rootDir, config) {
|
|
|
720
1580
|
}
|
|
721
1581
|
const mcpEnv = await prepareMcpEnv(rootDir, config.mcp);
|
|
722
1582
|
const mergedEnv = { ...secrets, ...mcpEnv ?? {} };
|
|
1583
|
+
const builtArgs = buildAgentArgs(agent, { injectSystemPrompt: true });
|
|
1584
|
+
if (isMacOS() && await hasLima()) {
|
|
1585
|
+
await runInLimaVm(agent, agentName, rootDir, builtArgs, mergedEnv);
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
723
1588
|
logger2.info(`Launching ${logger2.brand(agentName)}...`);
|
|
724
1589
|
logger2.dim(`Working directory: ${rootDir}`);
|
|
725
|
-
const builtArgs = buildAgentArgs(agent, { injectSystemPrompt: true });
|
|
726
1590
|
if (agentSupportsRuntimeInjection(agent)) {
|
|
727
1591
|
logger2.dim("Injecting RAPID methodology via CLI args");
|
|
728
1592
|
}
|
|
729
1593
|
logger2.blank();
|
|
730
|
-
await
|
|
1594
|
+
await execa4(agent.cli, builtArgs, {
|
|
731
1595
|
cwd: rootDir,
|
|
732
1596
|
stdio: "inherit",
|
|
733
1597
|
env: {
|
|
@@ -736,6 +1600,47 @@ async function runLocally(agent, agentName, rootDir, config) {
|
|
|
736
1600
|
}
|
|
737
1601
|
});
|
|
738
1602
|
}
|
|
1603
|
+
async function runInLimaVm(agent, agentName, rootDir, args, env) {
|
|
1604
|
+
const spinner = ora2();
|
|
1605
|
+
if (!await isRunning()) {
|
|
1606
|
+
spinner.start(`Starting Lima VM (${RAPID_LIMA_INSTANCE})...`);
|
|
1607
|
+
const result = await startInstance(rootDir, {
|
|
1608
|
+
env,
|
|
1609
|
+
timeout: 600
|
|
1610
|
+
// 10 minutes for first-time setup
|
|
1611
|
+
});
|
|
1612
|
+
if (!result.success) {
|
|
1613
|
+
spinner.fail("Failed to start Lima VM");
|
|
1614
|
+
logger2.error(result.error ?? "Unknown error");
|
|
1615
|
+
logger2.blank();
|
|
1616
|
+
logger2.info("Falling back to running directly on host...");
|
|
1617
|
+
logger2.blank();
|
|
1618
|
+
const { execa: execa4 } = await import("execa");
|
|
1619
|
+
await execa4(agent.cli, args, {
|
|
1620
|
+
cwd: rootDir,
|
|
1621
|
+
stdio: "inherit",
|
|
1622
|
+
env: {
|
|
1623
|
+
...process.env,
|
|
1624
|
+
...env
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
spinner.succeed("Lima VM started");
|
|
1630
|
+
} else {
|
|
1631
|
+
logger2.info(`Lima VM (${RAPID_LIMA_INSTANCE}) is running`);
|
|
1632
|
+
}
|
|
1633
|
+
logger2.info(`Launching ${logger2.brand(agentName)} in Lima VM...`);
|
|
1634
|
+
logger2.dim(`Working directory: ${rootDir}`);
|
|
1635
|
+
logger2.dim("SSH agent forwarded for commit signing");
|
|
1636
|
+
logger2.blank();
|
|
1637
|
+
await execInLima([agent.cli, ...args], {
|
|
1638
|
+
cwd: rootDir,
|
|
1639
|
+
env,
|
|
1640
|
+
interactive: true,
|
|
1641
|
+
tty: true
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
739
1644
|
function listAgents(config) {
|
|
740
1645
|
logger2.header("Available Agents");
|
|
741
1646
|
Object.keys(config.agents.available).forEach((name) => {
|
|
@@ -802,10 +1707,10 @@ async function runMultiAgent(config, rootDir, options) {
|
|
|
802
1707
|
console.log(` ${logger2.brand("\u2022")} ${name}`);
|
|
803
1708
|
}
|
|
804
1709
|
console.log();
|
|
805
|
-
const { execa } = await import("execa");
|
|
1710
|
+
const { execa: execa4 } = await import("execa");
|
|
806
1711
|
let hasTmux = false;
|
|
807
1712
|
try {
|
|
808
|
-
await
|
|
1713
|
+
await execa4("tmux", ["-V"]);
|
|
809
1714
|
hasTmux = true;
|
|
810
1715
|
} catch {
|
|
811
1716
|
hasTmux = false;
|
|
@@ -816,23 +1721,23 @@ async function runMultiAgent(config, rootDir, options) {
|
|
|
816
1721
|
const sessionName = `rapid-${Date.now()}`;
|
|
817
1722
|
const firstAgent = selectedAgents[0];
|
|
818
1723
|
const firstCmd = options.local ? `rapid dev --agent ${firstAgent} --local` : `rapid dev --agent ${firstAgent}`;
|
|
819
|
-
await
|
|
1724
|
+
await execa4("tmux", ["new-session", "-d", "-s", sessionName, "-n", "rapid", firstCmd], {
|
|
820
1725
|
cwd: rootDir
|
|
821
1726
|
});
|
|
822
1727
|
for (let i = 1; i < selectedAgents.length; i++) {
|
|
823
1728
|
const agentName = selectedAgents[i];
|
|
824
1729
|
const cmd = options.local ? `rapid dev --agent ${agentName} --local` : `rapid dev --agent ${agentName}`;
|
|
825
|
-
await
|
|
1730
|
+
await execa4("tmux", ["split-window", "-t", sessionName, "-h", cmd], {
|
|
826
1731
|
cwd: rootDir
|
|
827
1732
|
});
|
|
828
|
-
await
|
|
1733
|
+
await execa4("tmux", ["select-layout", "-t", sessionName, "tiled"]);
|
|
829
1734
|
}
|
|
830
1735
|
logger2.success(`Started ${selectedAgents.length} agents in tmux session: ${sessionName}`);
|
|
831
1736
|
console.log();
|
|
832
1737
|
logger2.info("Attaching to tmux session...");
|
|
833
1738
|
logger2.dim("Press Ctrl+B then D to detach, or Ctrl+B then arrow keys to switch panes");
|
|
834
1739
|
console.log();
|
|
835
|
-
await
|
|
1740
|
+
await execa4("tmux", ["attach-session", "-t", sessionName], {
|
|
836
1741
|
stdio: "inherit"
|
|
837
1742
|
});
|
|
838
1743
|
} else {
|
|
@@ -1574,13 +2479,13 @@ secretsCommand.command("run").description("Run a command with secrets loaded int
|
|
|
1574
2479
|
}
|
|
1575
2480
|
console.log();
|
|
1576
2481
|
}
|
|
1577
|
-
const { execa } = await import("execa");
|
|
2482
|
+
const { execa: execa4 } = await import("execa");
|
|
1578
2483
|
const [cmd, ...args] = commandArgs;
|
|
1579
2484
|
if (!cmd) {
|
|
1580
2485
|
logger7.error("No command specified");
|
|
1581
2486
|
process.exit(1);
|
|
1582
2487
|
}
|
|
1583
|
-
await
|
|
2488
|
+
await execa4(cmd, args, {
|
|
1584
2489
|
stdio: "inherit",
|
|
1585
2490
|
env: {
|
|
1586
2491
|
...process.env,
|
|
@@ -1783,8 +2688,8 @@ authCommand.command("env").description("Show environment variables for detected
|
|
|
1783
2688
|
|
|
1784
2689
|
// src/commands/mcp.ts
|
|
1785
2690
|
import { Command as Command9 } from "commander";
|
|
1786
|
-
import { writeFile as
|
|
1787
|
-
import { join as
|
|
2691
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
2692
|
+
import { join as join5 } from "path";
|
|
1788
2693
|
import {
|
|
1789
2694
|
loadConfig as loadConfig7,
|
|
1790
2695
|
logger as logger9,
|
|
@@ -1805,8 +2710,8 @@ var mcpCommand = new Command9("mcp").description(
|
|
|
1805
2710
|
"Manage MCP (Model Context Protocol) servers"
|
|
1806
2711
|
);
|
|
1807
2712
|
async function saveConfig(rootDir, config) {
|
|
1808
|
-
const configPath =
|
|
1809
|
-
await
|
|
2713
|
+
const configPath = join5(rootDir, "rapid.json");
|
|
2714
|
+
await writeFile4(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
1810
2715
|
}
|
|
1811
2716
|
mcpCommand.command("list").description("List configured MCP servers").option("--json", "Output as JSON").option("--templates", "Show available templates instead of configured servers").action(async (options) => {
|
|
1812
2717
|
try {
|
|
@@ -2092,18 +2997,606 @@ function collectHeaders(value, previous) {
|
|
|
2092
2997
|
return previous;
|
|
2093
2998
|
}
|
|
2094
2999
|
|
|
3000
|
+
// src/commands/update.ts
|
|
3001
|
+
import { Command as Command10 } from "commander";
|
|
3002
|
+
|
|
3003
|
+
// src/utils/update-checker.ts
|
|
3004
|
+
import updateNotifier from "update-notifier";
|
|
3005
|
+
import semver from "semver";
|
|
3006
|
+
import { execa as execa3 } from "execa";
|
|
3007
|
+
import { logger as logger10 } from "@a3t/rapid-core";
|
|
3008
|
+
import chalk from "chalk";
|
|
3009
|
+
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
3010
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3011
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
3012
|
+
import prompts from "prompts";
|
|
3013
|
+
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
3014
|
+
function loadPackageJson() {
|
|
3015
|
+
const paths = [
|
|
3016
|
+
join6(__dirname, "../package.json"),
|
|
3017
|
+
// bundled: dist/ -> package root
|
|
3018
|
+
join6(__dirname, "../../package.json")
|
|
3019
|
+
// source: src/utils/ -> package root
|
|
3020
|
+
];
|
|
3021
|
+
for (const p of paths) {
|
|
3022
|
+
if (existsSync2(p)) {
|
|
3023
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
return { name: "@a3t/rapid", version: "0.0.0" };
|
|
3027
|
+
}
|
|
3028
|
+
var packageJson = loadPackageJson();
|
|
3029
|
+
var UpdateChecker = class {
|
|
3030
|
+
notifier;
|
|
3031
|
+
packageName = packageJson.name;
|
|
3032
|
+
constructor() {
|
|
3033
|
+
this.notifier = updateNotifier({
|
|
3034
|
+
pkg: packageJson,
|
|
3035
|
+
updateCheckInterval: 1e3 * 60 * 60 * 24,
|
|
3036
|
+
// Check daily
|
|
3037
|
+
shouldNotifyInNpmScript: true
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Check if an update is available
|
|
3042
|
+
*/
|
|
3043
|
+
hasUpdate() {
|
|
3044
|
+
return !!this.notifier.update;
|
|
3045
|
+
}
|
|
3046
|
+
/**
|
|
3047
|
+
* Get update information
|
|
3048
|
+
*/
|
|
3049
|
+
getUpdateInfo() {
|
|
3050
|
+
if (!this.notifier.update) return null;
|
|
3051
|
+
return {
|
|
3052
|
+
current: this.notifier.update.current,
|
|
3053
|
+
latest: this.notifier.update.latest,
|
|
3054
|
+
type: this.getUpdateType(this.notifier.update.current, this.notifier.update.latest)
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Determine the type of update (major, minor, patch)
|
|
3059
|
+
*/
|
|
3060
|
+
getUpdateType(current, latest) {
|
|
3061
|
+
const diff = semver.diff(current, latest);
|
|
3062
|
+
if (diff === "premajor" || diff === "preminor" || diff === "prepatch") {
|
|
3063
|
+
return "prerelease";
|
|
3064
|
+
}
|
|
3065
|
+
return diff || "patch";
|
|
3066
|
+
}
|
|
3067
|
+
/**
|
|
3068
|
+
* Show update notification
|
|
3069
|
+
*/
|
|
3070
|
+
showNotification() {
|
|
3071
|
+
if (!this.notifier.update) return;
|
|
3072
|
+
const { current, latest } = this.notifier.update;
|
|
3073
|
+
const updateType = this.getUpdateType(current, latest);
|
|
3074
|
+
logger10.info(`Update available: ${logger10.dim(current)} \u2192 ${chalk.green(latest)}`);
|
|
3075
|
+
if (updateType === "major") {
|
|
3076
|
+
logger10.warn("This is a major version update with breaking changes.");
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
/**
|
|
3080
|
+
* Verify package signatures using npm audit signatures
|
|
3081
|
+
*/
|
|
3082
|
+
async verifySignatures() {
|
|
3083
|
+
try {
|
|
3084
|
+
logger10.info("Verifying package signatures...");
|
|
3085
|
+
const result = await execa3("npm", ["audit", "signatures"], {
|
|
3086
|
+
reject: false
|
|
3087
|
+
});
|
|
3088
|
+
if (result.exitCode === 0) {
|
|
3089
|
+
logger10.success("Package signatures verified successfully");
|
|
3090
|
+
return true;
|
|
3091
|
+
} else {
|
|
3092
|
+
logger10.warn("Package signature verification returned warnings");
|
|
3093
|
+
logger10.debug(result.stdout || result.stderr);
|
|
3094
|
+
return true;
|
|
3095
|
+
}
|
|
3096
|
+
} catch {
|
|
3097
|
+
logger10.debug("Signature verification not available (requires npm >= 9)");
|
|
3098
|
+
return true;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
/**
|
|
3102
|
+
* Perform the update with signature verification
|
|
3103
|
+
*/
|
|
3104
|
+
async performUpdate() {
|
|
3105
|
+
try {
|
|
3106
|
+
logger10.info("Updating RAPID CLI...");
|
|
3107
|
+
await execa3("npm", ["install", "-g", `${this.packageName}@latest`], {
|
|
3108
|
+
stdio: "inherit"
|
|
3109
|
+
});
|
|
3110
|
+
await this.verifySignatures();
|
|
3111
|
+
logger10.success("RAPID CLI updated successfully!");
|
|
3112
|
+
logger10.info("Package published with npm provenance - cryptographically verified");
|
|
3113
|
+
return true;
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
logger10.error("Failed to update RAPID CLI:", error);
|
|
3116
|
+
return false;
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* Check for updates and handle them according to version type
|
|
3121
|
+
*/
|
|
3122
|
+
async checkAndUpdate() {
|
|
3123
|
+
if (!this.hasUpdate()) return;
|
|
3124
|
+
const updateInfo = this.getUpdateInfo();
|
|
3125
|
+
if (!updateInfo) return;
|
|
3126
|
+
this.showNotification();
|
|
3127
|
+
if (updateInfo.type === "major") {
|
|
3128
|
+
logger10.warn(
|
|
3129
|
+
`This is a major version update (${updateInfo.current} \u2192 ${updateInfo.latest}) that may contain breaking changes.`
|
|
3130
|
+
);
|
|
3131
|
+
try {
|
|
3132
|
+
const response = await prompts({
|
|
3133
|
+
type: "confirm",
|
|
3134
|
+
name: "shouldUpdate",
|
|
3135
|
+
message: "Would you like to update to this major version?",
|
|
3136
|
+
initial: false
|
|
3137
|
+
});
|
|
3138
|
+
if (response.shouldUpdate) {
|
|
3139
|
+
logger10.info(`Updating to ${updateInfo.latest} (major version)...`);
|
|
3140
|
+
await this.performUpdate();
|
|
3141
|
+
} else {
|
|
3142
|
+
logger10.info("Skipping major version update.");
|
|
3143
|
+
logger10.info('You can update later with "rapid update --force"');
|
|
3144
|
+
}
|
|
3145
|
+
} catch {
|
|
3146
|
+
logger10.info(
|
|
3147
|
+
'Run "rapid update" to update manually, or use "rapid update --force" to update automatically.'
|
|
3148
|
+
);
|
|
3149
|
+
}
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
logger10.info(`Auto-updating to ${updateInfo.latest} (${updateInfo.type} version)...`);
|
|
3153
|
+
await this.performUpdate();
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Force update regardless of version type
|
|
3157
|
+
*/
|
|
3158
|
+
async forceUpdate() {
|
|
3159
|
+
if (!this.hasUpdate()) {
|
|
3160
|
+
logger10.info("No updates available.");
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
const updateInfo = this.getUpdateInfo();
|
|
3164
|
+
if (updateInfo) {
|
|
3165
|
+
this.showNotification();
|
|
3166
|
+
}
|
|
3167
|
+
await this.performUpdate();
|
|
3168
|
+
}
|
|
3169
|
+
};
|
|
3170
|
+
var updateChecker = new UpdateChecker();
|
|
3171
|
+
|
|
3172
|
+
// src/commands/update.ts
|
|
3173
|
+
import { logger as logger11 } from "@a3t/rapid-core";
|
|
3174
|
+
var updateCommand = new Command10("update").description("Check for and apply updates").option("--check", "Check for updates only").option("--force", "Force update even for major versions").action(async (options) => {
|
|
3175
|
+
try {
|
|
3176
|
+
if (options.check) {
|
|
3177
|
+
logger11.header("Checking for updates...");
|
|
3178
|
+
if (!updateChecker.hasUpdate()) {
|
|
3179
|
+
logger11.success("You are using the latest version!");
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
const updateInfo2 = updateChecker.getUpdateInfo();
|
|
3183
|
+
if (updateInfo2) {
|
|
3184
|
+
updateChecker.showNotification();
|
|
3185
|
+
if (updateInfo2.type === "major") {
|
|
3186
|
+
logger11.warn("This is a major version update with breaking changes.");
|
|
3187
|
+
logger11.info('Use "rapid update --force" to update.');
|
|
3188
|
+
} else {
|
|
3189
|
+
logger11.info('Use "rapid update" to apply the update.');
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
if (!updateChecker.hasUpdate()) {
|
|
3195
|
+
logger11.success("You are already using the latest version!");
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
const updateInfo = updateChecker.getUpdateInfo();
|
|
3199
|
+
if (updateInfo && updateInfo.type === "major" && !options.force) {
|
|
3200
|
+
logger11.warn("This is a major version update with breaking changes.");
|
|
3201
|
+
logger11.info("Use --force to update anyway.");
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
await updateChecker.forceUpdate();
|
|
3205
|
+
} catch (error) {
|
|
3206
|
+
logger11.error("Update failed:", error);
|
|
3207
|
+
process.exit(1);
|
|
3208
|
+
}
|
|
3209
|
+
});
|
|
3210
|
+
|
|
3211
|
+
// src/commands/worktree.ts
|
|
3212
|
+
import { Command as Command11 } from "commander";
|
|
3213
|
+
import { logger as logger12 } from "@a3t/rapid-core";
|
|
3214
|
+
import ora9 from "ora";
|
|
3215
|
+
function formatWorktree(wt, currentPath) {
|
|
3216
|
+
const isCurrent = wt.path === currentPath;
|
|
3217
|
+
const marker = isCurrent ? logger12.brand("*") : " ";
|
|
3218
|
+
const status = [];
|
|
3219
|
+
if (wt.isMain) status.push("main");
|
|
3220
|
+
if (wt.locked) status.push("locked");
|
|
3221
|
+
if (wt.prunable) status.push("prunable");
|
|
3222
|
+
if (!wt.exists) status.push("missing");
|
|
3223
|
+
const statusStr = status.length > 0 ? logger12.dim(` (${status.join(", ")})`) : "";
|
|
3224
|
+
const branchStr = wt.branch ? logger12.brand(wt.branch) : logger12.dim("detached");
|
|
3225
|
+
const headShort = wt.head?.substring(0, 7) ?? "";
|
|
3226
|
+
return `${marker} ${branchStr}${statusStr}
|
|
3227
|
+
${logger12.dim(wt.path)}
|
|
3228
|
+
${logger12.dim(`HEAD: ${headShort}`)}`;
|
|
3229
|
+
}
|
|
3230
|
+
var listCommand = new Command11("list").alias("ls").description("List all git worktrees").option("--json", "Output as JSON").action(async (options) => {
|
|
3231
|
+
try {
|
|
3232
|
+
const cwd = process.cwd();
|
|
3233
|
+
if (!await isGitRepo(cwd)) {
|
|
3234
|
+
logger12.error("Not a git repository");
|
|
3235
|
+
process.exit(1);
|
|
3236
|
+
}
|
|
3237
|
+
const gitRoot = await getGitRoot(cwd);
|
|
3238
|
+
const worktrees = await listWorktrees(gitRoot);
|
|
3239
|
+
if (options.json) {
|
|
3240
|
+
console.log(JSON.stringify(worktrees, null, 2));
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
if (worktrees.length === 0) {
|
|
3244
|
+
logger12.info("No worktrees found");
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
logger12.header("Git Worktrees");
|
|
3248
|
+
console.log();
|
|
3249
|
+
for (const wt of worktrees) {
|
|
3250
|
+
console.log(formatWorktree(wt, gitRoot));
|
|
3251
|
+
console.log();
|
|
3252
|
+
}
|
|
3253
|
+
const prunable = worktrees.filter((wt) => wt.prunable);
|
|
3254
|
+
if (prunable.length > 0) {
|
|
3255
|
+
logger12.warn(`${prunable.length} worktree(s) can be pruned. Run: rapid worktree prune`);
|
|
3256
|
+
}
|
|
3257
|
+
} catch (error) {
|
|
3258
|
+
logger12.error(error instanceof Error ? error.message : String(error));
|
|
3259
|
+
process.exit(1);
|
|
3260
|
+
}
|
|
3261
|
+
});
|
|
3262
|
+
var pruneCommand = new Command11("prune").description("Remove stale worktree references").option("--dry-run", "Show what would be pruned without removing").action(async (options) => {
|
|
3263
|
+
const spinner = ora9("Checking worktrees...").start();
|
|
3264
|
+
try {
|
|
3265
|
+
const cwd = process.cwd();
|
|
3266
|
+
if (!await isGitRepo(cwd)) {
|
|
3267
|
+
spinner.fail("Not a git repository");
|
|
3268
|
+
process.exit(1);
|
|
3269
|
+
}
|
|
3270
|
+
const gitRoot = await getGitRoot(cwd);
|
|
3271
|
+
const worktrees = await listWorktrees(gitRoot);
|
|
3272
|
+
const prunable = worktrees.filter((wt) => wt.prunable);
|
|
3273
|
+
if (prunable.length === 0) {
|
|
3274
|
+
spinner.succeed("No stale worktrees to prune");
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
if (options.dryRun) {
|
|
3278
|
+
spinner.info(`Would prune ${prunable.length} worktree(s):`);
|
|
3279
|
+
for (const wt of prunable) {
|
|
3280
|
+
console.log(` ${logger12.dim("\u2022")} ${wt.path}`);
|
|
3281
|
+
}
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
spinner.text = `Pruning ${prunable.length} worktree(s)...`;
|
|
3285
|
+
const result = await pruneWorktrees(gitRoot);
|
|
3286
|
+
if (result.success) {
|
|
3287
|
+
spinner.succeed(`Pruned ${result.pruned.length} worktree(s)`);
|
|
3288
|
+
for (const path of result.pruned) {
|
|
3289
|
+
console.log(` ${logger12.dim("\u2022")} ${path}`);
|
|
3290
|
+
}
|
|
3291
|
+
} else {
|
|
3292
|
+
spinner.fail(`Failed to prune: ${result.error}`);
|
|
3293
|
+
process.exit(1);
|
|
3294
|
+
}
|
|
3295
|
+
} catch (error) {
|
|
3296
|
+
spinner.fail(error instanceof Error ? error.message : String(error));
|
|
3297
|
+
process.exit(1);
|
|
3298
|
+
}
|
|
3299
|
+
});
|
|
3300
|
+
var removeCommand = new Command11("remove").alias("rm").description("Remove a worktree").argument("<path-or-branch>", "Worktree path or branch name").option("-f, --force", "Force removal even if worktree is dirty").action(async (pathOrBranch, options) => {
|
|
3301
|
+
const spinner = ora9("Finding worktree...").start();
|
|
3302
|
+
try {
|
|
3303
|
+
const cwd = process.cwd();
|
|
3304
|
+
if (!await isGitRepo(cwd)) {
|
|
3305
|
+
spinner.fail("Not a git repository");
|
|
3306
|
+
process.exit(1);
|
|
3307
|
+
}
|
|
3308
|
+
const gitRoot = await getGitRoot(cwd);
|
|
3309
|
+
const worktrees = await listWorktrees(gitRoot);
|
|
3310
|
+
const worktree = worktrees.find(
|
|
3311
|
+
(wt) => wt.path === pathOrBranch || wt.path.endsWith(pathOrBranch) || wt.branch === pathOrBranch
|
|
3312
|
+
);
|
|
3313
|
+
if (!worktree) {
|
|
3314
|
+
spinner.fail(`Worktree not found: ${pathOrBranch}`);
|
|
3315
|
+
logger12.info("Available worktrees:");
|
|
3316
|
+
for (const wt of worktrees) {
|
|
3317
|
+
console.log(` ${wt.branch || wt.path}`);
|
|
3318
|
+
}
|
|
3319
|
+
process.exit(1);
|
|
3320
|
+
}
|
|
3321
|
+
if (worktree.isMain) {
|
|
3322
|
+
spinner.fail("Cannot remove the main worktree");
|
|
3323
|
+
process.exit(1);
|
|
3324
|
+
}
|
|
3325
|
+
if (worktree.locked && !options.force) {
|
|
3326
|
+
spinner.fail("Worktree is locked. Use --force to remove anyway.");
|
|
3327
|
+
process.exit(1);
|
|
3328
|
+
}
|
|
3329
|
+
spinner.text = `Removing worktree: ${worktree.path}...`;
|
|
3330
|
+
const result = await removeWorktree(gitRoot, worktree.path, { force: options.force });
|
|
3331
|
+
if (result.success) {
|
|
3332
|
+
spinner.succeed(`Removed worktree: ${worktree.path}`);
|
|
3333
|
+
} else {
|
|
3334
|
+
spinner.fail(`Failed to remove: ${result.error}`);
|
|
3335
|
+
process.exit(1);
|
|
3336
|
+
}
|
|
3337
|
+
} catch (error) {
|
|
3338
|
+
spinner.fail(error instanceof Error ? error.message : String(error));
|
|
3339
|
+
process.exit(1);
|
|
3340
|
+
}
|
|
3341
|
+
});
|
|
3342
|
+
var cleanupCommand = new Command11("cleanup").description("Remove worktrees for branches that have been merged").option("--dry-run", "Show what would be removed without removing").action(async (options) => {
|
|
3343
|
+
const spinner = ora9("Analyzing worktrees...").start();
|
|
3344
|
+
try {
|
|
3345
|
+
const cwd = process.cwd();
|
|
3346
|
+
if (!await isGitRepo(cwd)) {
|
|
3347
|
+
spinner.fail("Not a git repository");
|
|
3348
|
+
process.exit(1);
|
|
3349
|
+
}
|
|
3350
|
+
const gitRoot = await getGitRoot(cwd);
|
|
3351
|
+
if (options.dryRun) {
|
|
3352
|
+
const worktrees = await listWorktrees(gitRoot);
|
|
3353
|
+
spinner.info("Dry run - would remove worktrees for merged branches");
|
|
3354
|
+
const nonMain = worktrees.filter((wt) => !wt.isMain && wt.branch);
|
|
3355
|
+
if (nonMain.length === 0) {
|
|
3356
|
+
console.log(" No feature branch worktrees found");
|
|
3357
|
+
} else {
|
|
3358
|
+
console.log(" Feature branch worktrees:");
|
|
3359
|
+
for (const wt of nonMain) {
|
|
3360
|
+
console.log(` ${logger12.dim("\u2022")} ${wt.branch} - ${wt.path}`);
|
|
3361
|
+
}
|
|
3362
|
+
console.log();
|
|
3363
|
+
logger12.info("Run without --dry-run to remove worktrees for merged branches");
|
|
3364
|
+
}
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
spinner.text = "Removing worktrees for merged branches...";
|
|
3368
|
+
const result = await cleanupMergedWorktrees(gitRoot);
|
|
3369
|
+
if (result.removed.length === 0) {
|
|
3370
|
+
spinner.succeed("No worktrees to clean up");
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
spinner.succeed(`Cleaned up ${result.removed.length} worktree(s)`);
|
|
3374
|
+
for (const path of result.removed) {
|
|
3375
|
+
console.log(` ${logger12.dim("\u2022")} ${path}`);
|
|
3376
|
+
}
|
|
3377
|
+
if (result.errors.length > 0) {
|
|
3378
|
+
console.log();
|
|
3379
|
+
logger12.warn("Some worktrees could not be removed:");
|
|
3380
|
+
for (const err of result.errors) {
|
|
3381
|
+
console.log(` ${logger12.dim("\u2022")} ${err}`);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
} catch (error) {
|
|
3385
|
+
spinner.fail(error instanceof Error ? error.message : String(error));
|
|
3386
|
+
process.exit(1);
|
|
3387
|
+
}
|
|
3388
|
+
});
|
|
3389
|
+
var worktreeCommand = new Command11("worktree").alias("wt").description("Manage git worktrees for isolated development").addCommand(listCommand).addCommand(pruneCommand).addCommand(removeCommand).addCommand(cleanupCommand);
|
|
3390
|
+
worktreeCommand.action(async () => {
|
|
3391
|
+
await listCommand.parseAsync([], { from: "user" });
|
|
3392
|
+
});
|
|
3393
|
+
|
|
3394
|
+
// src/commands/lima.ts
|
|
3395
|
+
import { Command as Command12 } from "commander";
|
|
3396
|
+
import { logger as logger13 } from "@a3t/rapid-core";
|
|
3397
|
+
import ora10 from "ora";
|
|
3398
|
+
async function checkLimaAvailable() {
|
|
3399
|
+
if (!isMacOS()) {
|
|
3400
|
+
logger13.error("Lima is only available on macOS");
|
|
3401
|
+
return false;
|
|
3402
|
+
}
|
|
3403
|
+
if (!await hasLima()) {
|
|
3404
|
+
logger13.error("Lima is not installed");
|
|
3405
|
+
logger13.blank();
|
|
3406
|
+
logger13.info("Install Lima with:");
|
|
3407
|
+
console.log(` ${logger13.dim("$")} brew install lima`);
|
|
3408
|
+
logger13.blank();
|
|
3409
|
+
logger13.info("For more information: https://lima-vm.io");
|
|
3410
|
+
return false;
|
|
3411
|
+
}
|
|
3412
|
+
return true;
|
|
3413
|
+
}
|
|
3414
|
+
var statusCommand2 = new Command12("status").description("Show Lima VM status").option("--json", "Output as JSON").action(async (options) => {
|
|
3415
|
+
if (!await checkLimaAvailable()) {
|
|
3416
|
+
process.exit(1);
|
|
3417
|
+
}
|
|
3418
|
+
const instance = await getInstance();
|
|
3419
|
+
if (options.json) {
|
|
3420
|
+
console.log(JSON.stringify(instance, null, 2));
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
if (!instance) {
|
|
3424
|
+
logger13.info(`Lima VM (${RAPID_LIMA_INSTANCE}) is not created`);
|
|
3425
|
+
logger13.blank();
|
|
3426
|
+
logger13.info("Start the VM with:");
|
|
3427
|
+
console.log(` ${logger13.dim("$")} rapid lima start`);
|
|
3428
|
+
logger13.blank();
|
|
3429
|
+
logger13.info("Or use:");
|
|
3430
|
+
console.log(` ${logger13.dim("$")} rapid dev --local`);
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
logger13.header("Lima VM Status");
|
|
3434
|
+
console.log();
|
|
3435
|
+
console.log(` ${logger13.dim("Name:")} ${instance.name}`);
|
|
3436
|
+
console.log(
|
|
3437
|
+
` ${logger13.dim("Status:")} ${instance.status === "Running" ? logger13.brand(instance.status) : instance.status}`
|
|
3438
|
+
);
|
|
3439
|
+
console.log(` ${logger13.dim("Arch:")} ${instance.arch}`);
|
|
3440
|
+
console.log(` ${logger13.dim("CPUs:")} ${instance.cpus}`);
|
|
3441
|
+
console.log(` ${logger13.dim("Memory:")} ${instance.memory}`);
|
|
3442
|
+
console.log(` ${logger13.dim("Disk:")} ${instance.disk}`);
|
|
3443
|
+
if (instance.sshLocalPort) {
|
|
3444
|
+
console.log(` ${logger13.dim("SSH Port:")} ${instance.sshLocalPort}`);
|
|
3445
|
+
}
|
|
3446
|
+
console.log();
|
|
3447
|
+
if (instance.status === "Running") {
|
|
3448
|
+
logger13.info("To open a shell:");
|
|
3449
|
+
console.log(` ${logger13.dim("$")} rapid lima shell`);
|
|
3450
|
+
} else {
|
|
3451
|
+
logger13.info("To start the VM:");
|
|
3452
|
+
console.log(` ${logger13.dim("$")} rapid lima start`);
|
|
3453
|
+
}
|
|
3454
|
+
console.log();
|
|
3455
|
+
});
|
|
3456
|
+
var startCommand2 = new Command12("start").description("Start the Lima VM").option("--cpus <n>", "Number of CPUs", "4").option("--memory <size>", "Memory size", "8GiB").option("--disk <size>", "Disk size", "50GiB").action(async (options) => {
|
|
3457
|
+
if (!await checkLimaAvailable()) {
|
|
3458
|
+
process.exit(1);
|
|
3459
|
+
}
|
|
3460
|
+
const spinner = ora10("Starting Lima VM...").start();
|
|
3461
|
+
const projectDir = process.cwd();
|
|
3462
|
+
const result = await startInstance(projectDir, {
|
|
3463
|
+
cpus: parseInt(options.cpus, 10),
|
|
3464
|
+
memory: options.memory,
|
|
3465
|
+
disk: options.disk,
|
|
3466
|
+
timeout: 600
|
|
3467
|
+
});
|
|
3468
|
+
if (!result.success) {
|
|
3469
|
+
spinner.fail("Failed to start Lima VM");
|
|
3470
|
+
logger13.error(result.error ?? "Unknown error");
|
|
3471
|
+
process.exit(1);
|
|
3472
|
+
}
|
|
3473
|
+
spinner.succeed("Lima VM started");
|
|
3474
|
+
logger13.blank();
|
|
3475
|
+
const sshSpinner = ora10("Checking SSH agent forwarding...").start();
|
|
3476
|
+
const sshResult = await setupGitSsh();
|
|
3477
|
+
if (sshResult.success) {
|
|
3478
|
+
sshSpinner.succeed("SSH agent forwarding is working");
|
|
3479
|
+
} else {
|
|
3480
|
+
sshSpinner.warn("SSH agent forwarding may not be working");
|
|
3481
|
+
logger13.dim(sshResult.error ?? "Make sure ssh-agent is running on the host");
|
|
3482
|
+
}
|
|
3483
|
+
logger13.blank();
|
|
3484
|
+
logger13.info("To open a shell:");
|
|
3485
|
+
console.log(` ${logger13.dim("$")} rapid lima shell`);
|
|
3486
|
+
console.log();
|
|
3487
|
+
});
|
|
3488
|
+
var stopCommand2 = new Command12("stop").description("Stop the Lima VM").option("-f, --force", "Force stop").action(async (options) => {
|
|
3489
|
+
if (!await checkLimaAvailable()) {
|
|
3490
|
+
process.exit(1);
|
|
3491
|
+
}
|
|
3492
|
+
const instance = await getInstance();
|
|
3493
|
+
if (!instance) {
|
|
3494
|
+
logger13.info("Lima VM is not created");
|
|
3495
|
+
return;
|
|
3496
|
+
}
|
|
3497
|
+
if (instance.status !== "Running") {
|
|
3498
|
+
logger13.info("Lima VM is already stopped");
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
const spinner = ora10("Stopping Lima VM...").start();
|
|
3502
|
+
const result = await stopInstance(RAPID_LIMA_INSTANCE, {
|
|
3503
|
+
force: options.force
|
|
3504
|
+
});
|
|
3505
|
+
if (!result.success) {
|
|
3506
|
+
spinner.fail("Failed to stop Lima VM");
|
|
3507
|
+
logger13.error(result.error ?? "Unknown error");
|
|
3508
|
+
process.exit(1);
|
|
3509
|
+
}
|
|
3510
|
+
spinner.succeed("Lima VM stopped");
|
|
3511
|
+
});
|
|
3512
|
+
var shellCommand = new Command12("shell").description("Open a shell in the Lima VM").option("-c, --command <cmd>", "Command to run instead of interactive shell").action(async (options) => {
|
|
3513
|
+
if (!await checkLimaAvailable()) {
|
|
3514
|
+
process.exit(1);
|
|
3515
|
+
}
|
|
3516
|
+
const instance = await getInstance();
|
|
3517
|
+
if (!instance || instance.status !== "Running") {
|
|
3518
|
+
logger13.error("Lima VM is not running");
|
|
3519
|
+
logger13.info("Start with: rapid lima start");
|
|
3520
|
+
process.exit(1);
|
|
3521
|
+
}
|
|
3522
|
+
await shellInLima({
|
|
3523
|
+
cwd: process.cwd(),
|
|
3524
|
+
command: options.command
|
|
3525
|
+
});
|
|
3526
|
+
});
|
|
3527
|
+
var deleteCommand = new Command12("delete").description("Delete the Lima VM").option("-f, --force", "Force delete without confirmation").action(async (options) => {
|
|
3528
|
+
if (!await checkLimaAvailable()) {
|
|
3529
|
+
process.exit(1);
|
|
3530
|
+
}
|
|
3531
|
+
const instance = await getInstance();
|
|
3532
|
+
if (!instance) {
|
|
3533
|
+
logger13.info("Lima VM does not exist");
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
if (!options.force) {
|
|
3537
|
+
logger13.warn("This will permanently delete the Lima VM and all its data.");
|
|
3538
|
+
logger13.info(`Use ${logger13.brand("--force")} to confirm deletion.`);
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
const spinner = ora10("Deleting Lima VM...").start();
|
|
3542
|
+
const result = await deleteInstance(RAPID_LIMA_INSTANCE, { force: true });
|
|
3543
|
+
if (!result.success) {
|
|
3544
|
+
spinner.fail("Failed to delete Lima VM");
|
|
3545
|
+
logger13.error(result.error ?? "Unknown error");
|
|
3546
|
+
process.exit(1);
|
|
3547
|
+
}
|
|
3548
|
+
spinner.succeed("Lima VM deleted");
|
|
3549
|
+
});
|
|
3550
|
+
var listCommand2 = new Command12("list").alias("ls").description("List all Lima instances").option("--json", "Output as JSON").action(async (options) => {
|
|
3551
|
+
if (!await checkLimaAvailable()) {
|
|
3552
|
+
process.exit(1);
|
|
3553
|
+
}
|
|
3554
|
+
const instances = await listInstances();
|
|
3555
|
+
if (options.json) {
|
|
3556
|
+
console.log(JSON.stringify(instances, null, 2));
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
if (instances.length === 0) {
|
|
3560
|
+
logger13.info("No Lima instances found");
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
logger13.header("Lima Instances");
|
|
3564
|
+
console.log();
|
|
3565
|
+
for (const inst of instances) {
|
|
3566
|
+
const isRapid = inst.name === RAPID_LIMA_INSTANCE;
|
|
3567
|
+
const statusColor = inst.status === "Running" ? logger13.brand : logger13.dim;
|
|
3568
|
+
console.log(
|
|
3569
|
+
` ${isRapid ? logger13.brand("*") : " "} ${inst.name} ${statusColor(`(${inst.status})`)}`
|
|
3570
|
+
);
|
|
3571
|
+
console.log(` ${logger13.dim(`${inst.cpus} CPUs, ${inst.memory}, ${inst.disk}`)}`);
|
|
3572
|
+
}
|
|
3573
|
+
console.log();
|
|
3574
|
+
});
|
|
3575
|
+
var limaCommand = new Command12("lima").description("Manage Lima VM for local development (macOS)").addCommand(statusCommand2).addCommand(startCommand2).addCommand(stopCommand2).addCommand(shellCommand).addCommand(deleteCommand).addCommand(listCommand2);
|
|
3576
|
+
limaCommand.action(async () => {
|
|
3577
|
+
await statusCommand2.parseAsync([], { from: "user" });
|
|
3578
|
+
});
|
|
3579
|
+
|
|
2095
3580
|
// src/index.ts
|
|
2096
|
-
var
|
|
2097
|
-
var
|
|
2098
|
-
var VERSION =
|
|
2099
|
-
var program = new
|
|
2100
|
-
program.name("rapid").description("AI-assisted development with dev containers").version(VERSION, "-v, --version", "Show version").option("--verbose", "Verbose output").option("-q, --quiet", "Minimal output").option("--config <path>", "Path to rapid.json").hook("preAction", (thisCommand) => {
|
|
3581
|
+
var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
3582
|
+
var packageJson2 = JSON.parse(readFileSync2(join7(__dirname2, "../package.json"), "utf-8"));
|
|
3583
|
+
var VERSION = packageJson2.version;
|
|
3584
|
+
var program = new Command13();
|
|
3585
|
+
program.name("rapid").description("AI-assisted development with dev containers").version(VERSION, "-v, --version", "Show version").option("--verbose", "Verbose output").option("-q, --quiet", "Minimal output").option("--config <path>", "Path to rapid.json").hook("preAction", async (thisCommand) => {
|
|
2101
3586
|
const opts = thisCommand.opts();
|
|
2102
3587
|
if (opts.verbose) {
|
|
2103
3588
|
setLogLevel("debug");
|
|
2104
3589
|
} else if (opts.quiet) {
|
|
2105
3590
|
setLogLevel("error");
|
|
2106
3591
|
}
|
|
3592
|
+
if (thisCommand.name() === "update" || thisCommand.name() === "version") {
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
try {
|
|
3596
|
+
await updateChecker.checkAndUpdate();
|
|
3597
|
+
} catch (error) {
|
|
3598
|
+
logger14.debug("Update check failed:", error);
|
|
3599
|
+
}
|
|
2107
3600
|
});
|
|
2108
3601
|
program.addCommand(initCommand);
|
|
2109
3602
|
program.addCommand(startCommand);
|
|
@@ -2114,10 +3607,13 @@ program.addCommand(agentCommand);
|
|
|
2114
3607
|
program.addCommand(secretsCommand);
|
|
2115
3608
|
program.addCommand(authCommand);
|
|
2116
3609
|
program.addCommand(mcpCommand);
|
|
3610
|
+
program.addCommand(updateCommand);
|
|
3611
|
+
program.addCommand(worktreeCommand);
|
|
3612
|
+
program.addCommand(limaCommand);
|
|
2117
3613
|
program.action(() => {
|
|
2118
3614
|
console.log();
|
|
2119
|
-
console.log(` ${
|
|
2120
|
-
console.log(` ${
|
|
3615
|
+
console.log(` ${logger14.brand("RAPID")} ${logger14.dim(`v${VERSION}`)}`);
|
|
3616
|
+
console.log(` ${logger14.dim("AI-assisted development with dev containers")}`);
|
|
2121
3617
|
console.log();
|
|
2122
3618
|
program.help();
|
|
2123
3619
|
});
|
|
@@ -2125,4 +3621,4 @@ program.action(() => {
|
|
|
2125
3621
|
export {
|
|
2126
3622
|
program
|
|
2127
3623
|
};
|
|
2128
|
-
//# sourceMappingURL=chunk-
|
|
3624
|
+
//# sourceMappingURL=chunk-C6XSZWFT.js.map
|