@a3t/rapid 0.1.5 → 0.1.7
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 +5 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-B3WZZMOG.js → chunk-FICHP3SD.js} +546 -60
- package/dist/chunk-FICHP3SD.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/dist/chunk-B3WZZMOG.js.map +0 -1
|
@@ -7,7 +7,7 @@ import { dirname, join as join4 } from "path";
|
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
9
9
|
import { Command } from "commander";
|
|
10
|
-
import { writeFile, access } from "fs/promises";
|
|
10
|
+
import { writeFile, access, readFile, readdir } from "fs/promises";
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
import {
|
|
13
13
|
getDefaultConfig,
|
|
@@ -16,10 +16,186 @@ import {
|
|
|
16
16
|
addMcpServerFromTemplate,
|
|
17
17
|
getSecretReferences,
|
|
18
18
|
writeMcpConfig,
|
|
19
|
-
writeOpenCodeConfig
|
|
19
|
+
writeOpenCodeConfig,
|
|
20
|
+
RAPID_METHODOLOGY,
|
|
21
|
+
MCP_USAGE_GUIDELINES,
|
|
22
|
+
GIT_GUIDELINES
|
|
20
23
|
} from "@a3t/rapid-core";
|
|
21
24
|
import ora from "ora";
|
|
22
|
-
|
|
25
|
+
async function detectProjectType(dir) {
|
|
26
|
+
const files = await readdir(dir).catch(() => []);
|
|
27
|
+
const fileSet = new Set(files);
|
|
28
|
+
if (fileSet.has("Cargo.toml")) {
|
|
29
|
+
return { language: "rust", confidence: "high" };
|
|
30
|
+
}
|
|
31
|
+
if (fileSet.has("go.mod")) {
|
|
32
|
+
return { language: "go", confidence: "high" };
|
|
33
|
+
}
|
|
34
|
+
if (fileSet.has("pyproject.toml")) {
|
|
35
|
+
const content = await readFile(join(dir, "pyproject.toml"), "utf-8").catch(() => "");
|
|
36
|
+
const framework = content.includes("fastapi") ? "fastapi" : content.includes("django") ? "django" : content.includes("flask") ? "flask" : void 0;
|
|
37
|
+
if (framework) {
|
|
38
|
+
return { language: "python", framework, confidence: "high" };
|
|
39
|
+
}
|
|
40
|
+
return { language: "python", confidence: "high" };
|
|
41
|
+
}
|
|
42
|
+
if (fileSet.has("requirements.txt") || fileSet.has("setup.py") || fileSet.has("Pipfile")) {
|
|
43
|
+
return { language: "python", confidence: "medium" };
|
|
44
|
+
}
|
|
45
|
+
if (fileSet.has("Gemfile")) {
|
|
46
|
+
return { language: "ruby", confidence: "high" };
|
|
47
|
+
}
|
|
48
|
+
if (fileSet.has("pom.xml") || fileSet.has("build.gradle") || fileSet.has("build.gradle.kts")) {
|
|
49
|
+
return { language: "java", confidence: "high" };
|
|
50
|
+
}
|
|
51
|
+
if (fileSet.has("tsconfig.json")) {
|
|
52
|
+
const pkgManager = fileSet.has("pnpm-lock.yaml") ? "pnpm" : fileSet.has("yarn.lock") ? "yarn" : fileSet.has("bun.lockb") || fileSet.has("bun.lock") ? "bun" : "npm";
|
|
53
|
+
let framework;
|
|
54
|
+
if (fileSet.has("package.json")) {
|
|
55
|
+
const pkg = await readFile(join(dir, "package.json"), "utf-8").catch(() => "{}");
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(pkg);
|
|
58
|
+
const deps = { ...parsed.dependencies, ...parsed.devDependencies };
|
|
59
|
+
if (deps.next) framework = "nextjs";
|
|
60
|
+
else if (deps.nuxt) framework = "nuxt";
|
|
61
|
+
else if (deps.react) framework = "react";
|
|
62
|
+
else if (deps.vue) framework = "vue";
|
|
63
|
+
else if (deps.svelte) framework = "svelte";
|
|
64
|
+
else if (deps.express) framework = "express";
|
|
65
|
+
else if (deps.fastify) framework = "fastify";
|
|
66
|
+
else if (deps.hono) framework = "hono";
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const result = {
|
|
71
|
+
language: "typescript",
|
|
72
|
+
packageManager: pkgManager,
|
|
73
|
+
confidence: "high"
|
|
74
|
+
};
|
|
75
|
+
if (framework) {
|
|
76
|
+
result.framework = framework;
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
if (fileSet.has("package.json")) {
|
|
81
|
+
const pkgManager = fileSet.has("pnpm-lock.yaml") ? "pnpm" : fileSet.has("yarn.lock") ? "yarn" : "npm";
|
|
82
|
+
return { language: "javascript", packageManager: pkgManager, confidence: "medium" };
|
|
83
|
+
}
|
|
84
|
+
return { language: "unknown", confidence: "low" };
|
|
85
|
+
}
|
|
86
|
+
function getSuggestedTemplate(detected) {
|
|
87
|
+
switch (detected.language) {
|
|
88
|
+
case "typescript":
|
|
89
|
+
return "typescript";
|
|
90
|
+
case "javascript":
|
|
91
|
+
return "typescript";
|
|
92
|
+
// Use TS template for JS too
|
|
93
|
+
case "python":
|
|
94
|
+
return "python";
|
|
95
|
+
case "rust":
|
|
96
|
+
return "rust";
|
|
97
|
+
case "go":
|
|
98
|
+
return "go";
|
|
99
|
+
default:
|
|
100
|
+
return "universal";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function parseTemplateSource(input) {
|
|
104
|
+
const builtinTemplates = [
|
|
105
|
+
"typescript",
|
|
106
|
+
"python",
|
|
107
|
+
"rust",
|
|
108
|
+
"go",
|
|
109
|
+
"universal",
|
|
110
|
+
"default",
|
|
111
|
+
"infrastructure"
|
|
112
|
+
];
|
|
113
|
+
if (builtinTemplates.includes(input)) {
|
|
114
|
+
return { type: "builtin", source: input };
|
|
115
|
+
}
|
|
116
|
+
if (input.startsWith("github:") || input.startsWith("gh:")) {
|
|
117
|
+
const source = input.replace(/^(github|gh):/, "");
|
|
118
|
+
const parsed = parseRepoPath(source);
|
|
119
|
+
const result = { type: "github", source: parsed.repo };
|
|
120
|
+
if (parsed.subdir) result.subdir = parsed.subdir;
|
|
121
|
+
if (parsed.ref) result.ref = parsed.ref;
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
if (input.startsWith("gitlab:")) {
|
|
125
|
+
const source = input.replace(/^gitlab:/, "");
|
|
126
|
+
const parsed = parseRepoPath(source);
|
|
127
|
+
const result = { type: "gitlab", source: parsed.repo };
|
|
128
|
+
if (parsed.subdir) result.subdir = parsed.subdir;
|
|
129
|
+
if (parsed.ref) result.ref = parsed.ref;
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
if (input.startsWith("npm:")) {
|
|
133
|
+
return { type: "npm", source: input.replace(/^npm:/, "") };
|
|
134
|
+
}
|
|
135
|
+
if (input.startsWith("https://") || input.startsWith("http://")) {
|
|
136
|
+
return { type: "url", source: input };
|
|
137
|
+
}
|
|
138
|
+
if (/^[\w.-]+\/[\w.-]+/.test(input)) {
|
|
139
|
+
const parsed = parseRepoPath(input);
|
|
140
|
+
const result = { type: "github", source: parsed.repo };
|
|
141
|
+
if (parsed.subdir) result.subdir = parsed.subdir;
|
|
142
|
+
if (parsed.ref) result.ref = parsed.ref;
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
return { type: "builtin", source: "universal" };
|
|
146
|
+
}
|
|
147
|
+
function parseRepoPath(input) {
|
|
148
|
+
let ref;
|
|
149
|
+
let path = input;
|
|
150
|
+
if (path.includes("#")) {
|
|
151
|
+
const parts = path.split("#");
|
|
152
|
+
path = parts[0];
|
|
153
|
+
ref = parts[1];
|
|
154
|
+
}
|
|
155
|
+
const segments = path.split("/");
|
|
156
|
+
if (segments.length >= 2) {
|
|
157
|
+
const repo = `${segments[0]}/${segments[1]}`;
|
|
158
|
+
const subdir = segments.length > 2 ? segments.slice(2).join("/") : void 0;
|
|
159
|
+
const result2 = { repo };
|
|
160
|
+
if (subdir) result2.subdir = subdir;
|
|
161
|
+
if (ref) result2.ref = ref;
|
|
162
|
+
return result2;
|
|
163
|
+
}
|
|
164
|
+
const result = { repo: path };
|
|
165
|
+
if (ref) result.ref = ref;
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
async function downloadRemoteTemplate(parsed, destDir, spinner) {
|
|
169
|
+
try {
|
|
170
|
+
const { downloadTemplate } = await import("giget");
|
|
171
|
+
let source;
|
|
172
|
+
switch (parsed.type) {
|
|
173
|
+
case "github":
|
|
174
|
+
source = `github:${parsed.source}${parsed.subdir ? "/" + parsed.subdir : ""}${parsed.ref ? "#" + parsed.ref : ""}`;
|
|
175
|
+
break;
|
|
176
|
+
case "gitlab":
|
|
177
|
+
source = `gitlab:${parsed.source}${parsed.subdir ? "/" + parsed.subdir : ""}${parsed.ref ? "#" + parsed.ref : ""}`;
|
|
178
|
+
break;
|
|
179
|
+
case "url":
|
|
180
|
+
source = parsed.source;
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
spinner.text = `Downloading template from ${source}...`;
|
|
186
|
+
await downloadTemplate(source, {
|
|
187
|
+
dir: destDir,
|
|
188
|
+
force: true
|
|
189
|
+
});
|
|
190
|
+
return true;
|
|
191
|
+
} catch (error) {
|
|
192
|
+
logger.debug(
|
|
193
|
+
`Failed to download template: ${error instanceof Error ? error.message : String(error)}`
|
|
194
|
+
);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
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("--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) => {
|
|
23
199
|
const spinner = ora("Initializing RAPID...").start();
|
|
24
200
|
try {
|
|
25
201
|
const cwd = process.cwd();
|
|
@@ -32,8 +208,51 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
32
208
|
} catch {
|
|
33
209
|
}
|
|
34
210
|
}
|
|
211
|
+
let detectedProject;
|
|
212
|
+
let templateSource = templateArg;
|
|
213
|
+
if (!templateArg && options.detect !== false) {
|
|
214
|
+
spinner.text = "Detecting project type...";
|
|
215
|
+
detectedProject = await detectProjectType(cwd);
|
|
216
|
+
if (detectedProject.language !== "unknown") {
|
|
217
|
+
const suggested = getSuggestedTemplate(detectedProject);
|
|
218
|
+
spinner.succeed(
|
|
219
|
+
`Detected ${detectedProject.language}${detectedProject.framework ? ` (${detectedProject.framework})` : ""} project`
|
|
220
|
+
);
|
|
221
|
+
templateSource = suggested;
|
|
222
|
+
logger.info(`Using ${logger.brand(suggested)} template`);
|
|
223
|
+
} else {
|
|
224
|
+
spinner.info("Could not detect project type, using universal template");
|
|
225
|
+
templateSource = "universal";
|
|
226
|
+
}
|
|
227
|
+
spinner.start("Initializing RAPID...");
|
|
228
|
+
}
|
|
229
|
+
const parsed = parseTemplateSource(templateSource || "universal");
|
|
230
|
+
if (parsed.type !== "builtin") {
|
|
231
|
+
spinner.text = `Fetching template from ${parsed.source}...`;
|
|
232
|
+
if (parsed.type === "npm") {
|
|
233
|
+
spinner.fail("npm template support coming soon. Use github:user/repo instead.");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
const downloaded = await downloadRemoteTemplate(parsed, cwd, spinner);
|
|
237
|
+
if (!downloaded) {
|
|
238
|
+
spinner.fail(`Failed to download template from ${parsed.source}`);
|
|
239
|
+
logger.info("Make sure the repository exists and is accessible.");
|
|
240
|
+
logger.info("For private repos, set GIGET_AUTH environment variable.");
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
spinner.succeed(`Downloaded template from ${parsed.source}`);
|
|
244
|
+
try {
|
|
245
|
+
await access(join(cwd, "rapid.json"));
|
|
246
|
+
logger.blank();
|
|
247
|
+
logger.info("Template includes rapid.json configuration.");
|
|
248
|
+
logger.info("Run `rapid dev` to start coding!");
|
|
249
|
+
return;
|
|
250
|
+
} catch {
|
|
251
|
+
spinner.start("Creating RAPID configuration...");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
35
254
|
const mcpServers = options.mcp === false ? [] : options.mcp.split(",").map((s) => s.trim());
|
|
36
|
-
let config = createConfig(options);
|
|
255
|
+
let config = createConfig(options, detectedProject);
|
|
37
256
|
if (mcpServers.length > 0) {
|
|
38
257
|
spinner.text = "Configuring MCP servers...";
|
|
39
258
|
for (const serverName of mcpServers) {
|
|
@@ -66,12 +285,23 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
66
285
|
if (config.agents.available.claude) {
|
|
67
286
|
spinner.text = "Creating CLAUDE.md...";
|
|
68
287
|
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
69
|
-
await writeFile(claudeMdPath, getClaudeMdTemplate(cwd));
|
|
288
|
+
await writeFile(claudeMdPath, getClaudeMdTemplate(cwd, detectedProject));
|
|
70
289
|
}
|
|
71
290
|
spinner.text = "Creating AGENTS.md...";
|
|
72
291
|
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
73
|
-
await writeFile(agentsMdPath, getAgentsMdTemplate(cwd));
|
|
292
|
+
await writeFile(agentsMdPath, getAgentsMdTemplate(cwd, detectedProject));
|
|
74
293
|
spinner.succeed("RAPID initialized successfully!");
|
|
294
|
+
if (detectedProject && detectedProject.language !== "unknown") {
|
|
295
|
+
logger.blank();
|
|
296
|
+
logger.info("Project detected:");
|
|
297
|
+
console.log(` ${logger.dim("Language:")} ${detectedProject.language}`);
|
|
298
|
+
if (detectedProject.framework) {
|
|
299
|
+
console.log(` ${logger.dim("Framework:")} ${detectedProject.framework}`);
|
|
300
|
+
}
|
|
301
|
+
if (detectedProject.packageManager) {
|
|
302
|
+
console.log(` ${logger.dim("Package Mgr:")} ${detectedProject.packageManager}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
75
305
|
logger.blank();
|
|
76
306
|
logger.info("Created files:");
|
|
77
307
|
console.log(` ${logger.dim("\u2022")} rapid.json`);
|
|
@@ -107,9 +337,9 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
107
337
|
process.exit(1);
|
|
108
338
|
}
|
|
109
339
|
});
|
|
110
|
-
function createConfig(options) {
|
|
340
|
+
function createConfig(options, detectedProject) {
|
|
111
341
|
const defaults = getDefaultConfig();
|
|
112
|
-
|
|
342
|
+
const config = {
|
|
113
343
|
$schema: "https://getrapid.dev/schema/v1/rapid.json",
|
|
114
344
|
version: "1.0",
|
|
115
345
|
agents: {
|
|
@@ -125,9 +355,26 @@ function createConfig(options) {
|
|
|
125
355
|
// We already created them
|
|
126
356
|
}
|
|
127
357
|
};
|
|
358
|
+
if (detectedProject && detectedProject.language !== "unknown") {
|
|
359
|
+
config.context = {
|
|
360
|
+
...config.context
|
|
361
|
+
// Store detected info for potential future use
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return config;
|
|
128
365
|
}
|
|
129
|
-
function getClaudeMdTemplate(projectPath) {
|
|
366
|
+
function getClaudeMdTemplate(projectPath, detectedProject) {
|
|
130
367
|
const projectName = projectPath.split("/").pop() || "project";
|
|
368
|
+
let languageSection = "";
|
|
369
|
+
if (detectedProject && detectedProject.language !== "unknown") {
|
|
370
|
+
languageSection = `## Technology Stack
|
|
371
|
+
|
|
372
|
+
- **Language**: ${detectedProject.language}${detectedProject.framework ? `
|
|
373
|
+
- **Framework**: ${detectedProject.framework}` : ""}${detectedProject.packageManager ? `
|
|
374
|
+
- **Package Manager**: ${detectedProject.packageManager}` : ""}
|
|
375
|
+
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
131
378
|
return `# Claude Instructions
|
|
132
379
|
|
|
133
380
|
## Project: ${projectName}
|
|
@@ -138,12 +385,9 @@ This file contains instructions for Claude Code when working on this project.
|
|
|
138
385
|
|
|
139
386
|
<!-- Describe your project here -->
|
|
140
387
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- Write tests for new functionality
|
|
145
|
-
- Update documentation when making changes
|
|
146
|
-
|
|
388
|
+
${languageSection}${RAPID_METHODOLOGY}
|
|
389
|
+
${MCP_USAGE_GUIDELINES}
|
|
390
|
+
${GIT_GUIDELINES}
|
|
147
391
|
## Key Files
|
|
148
392
|
|
|
149
393
|
- \`rapid.json\` - RAPID configuration
|
|
@@ -160,8 +404,18 @@ rapid status
|
|
|
160
404
|
\`\`\`
|
|
161
405
|
`;
|
|
162
406
|
}
|
|
163
|
-
function getAgentsMdTemplate(projectPath) {
|
|
407
|
+
function getAgentsMdTemplate(projectPath, detectedProject) {
|
|
164
408
|
const projectName = projectPath.split("/").pop() || "project";
|
|
409
|
+
let languageSection = "";
|
|
410
|
+
if (detectedProject && detectedProject.language !== "unknown") {
|
|
411
|
+
languageSection = `## Technology Stack
|
|
412
|
+
|
|
413
|
+
- **Language**: ${detectedProject.language}${detectedProject.framework ? `
|
|
414
|
+
- **Framework**: ${detectedProject.framework}` : ""}${detectedProject.packageManager ? `
|
|
415
|
+
- **Package Manager**: ${detectedProject.packageManager}` : ""}
|
|
416
|
+
|
|
417
|
+
`;
|
|
418
|
+
}
|
|
165
419
|
return `# Agent Instructions
|
|
166
420
|
|
|
167
421
|
## Project: ${projectName}
|
|
@@ -172,13 +426,9 @@ This file contains instructions for AI coding agents working on this project.
|
|
|
172
426
|
|
|
173
427
|
<!-- Describe your project here -->
|
|
174
428
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- Write tests for new functionality
|
|
179
|
-
- Update documentation when making changes
|
|
180
|
-
- Commit changes with clear, descriptive messages
|
|
181
|
-
|
|
429
|
+
${languageSection}${RAPID_METHODOLOGY}
|
|
430
|
+
${MCP_USAGE_GUIDELINES}
|
|
431
|
+
${GIT_GUIDELINES}
|
|
182
432
|
## Project Structure
|
|
183
433
|
|
|
184
434
|
\`\`\`
|
|
@@ -193,7 +443,7 @@ This file contains instructions for AI coding agents working on this project.
|
|
|
193
443
|
|
|
194
444
|
1. Review the project structure
|
|
195
445
|
2. Check \`rapid.json\` for configuration
|
|
196
|
-
3. Follow the
|
|
446
|
+
3. Follow the RAPID methodology above when making changes
|
|
197
447
|
`;
|
|
198
448
|
}
|
|
199
449
|
|
|
@@ -212,10 +462,17 @@ import {
|
|
|
212
462
|
hasDevcontainerCli,
|
|
213
463
|
loadSecrets,
|
|
214
464
|
hasOpCli,
|
|
215
|
-
isOpAuthenticated
|
|
465
|
+
isOpAuthenticated,
|
|
466
|
+
hasVaultCli,
|
|
467
|
+
isVaultAuthenticated,
|
|
468
|
+
buildAgentArgs,
|
|
469
|
+
agentSupportsRuntimeInjection
|
|
216
470
|
} from "@a3t/rapid-core";
|
|
217
471
|
import ora2 from "ora";
|
|
218
|
-
var devCommand = new Command2("dev").description("Launch AI coding session in the dev container").option("-a, --agent <name>", "Agent to use").option(
|
|
472
|
+
var devCommand = new Command2("dev").description("Launch AI coding session in the dev container").option("-a, --agent <name>", "Agent to use").option(
|
|
473
|
+
"--multi [agents]",
|
|
474
|
+
"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) => {
|
|
219
476
|
try {
|
|
220
477
|
const spinner = ora2("Loading configuration...").start();
|
|
221
478
|
const loaded = await loadConfig();
|
|
@@ -229,6 +486,10 @@ var devCommand = new Command2("dev").description("Launch AI coding session in th
|
|
|
229
486
|
listAgents(config);
|
|
230
487
|
return;
|
|
231
488
|
}
|
|
489
|
+
if (options.multi !== void 0) {
|
|
490
|
+
await runMultiAgent(config, rootDir, options);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
232
493
|
const agentName = options.agent || config.agents.default;
|
|
233
494
|
const agent = getAgent(config, agentName);
|
|
234
495
|
if (!agent) {
|
|
@@ -244,7 +505,7 @@ var devCommand = new Command2("dev").description("Launch AI coding session in th
|
|
|
244
505
|
logger2.warn("Running locally instead of in container");
|
|
245
506
|
logger2.dim("This bypasses the isolated dev environment");
|
|
246
507
|
logger2.blank();
|
|
247
|
-
await runLocally(agent, agentName, rootDir);
|
|
508
|
+
await runLocally(agent, agentName, rootDir, config);
|
|
248
509
|
return;
|
|
249
510
|
}
|
|
250
511
|
const hasDevCli = await hasDevcontainerCli();
|
|
@@ -276,33 +537,77 @@ var devCommand = new Command2("dev").description("Launch AI coding session in th
|
|
|
276
537
|
spinner.succeed(`Container running (${status.containerName})`);
|
|
277
538
|
}
|
|
278
539
|
let secrets = {};
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
if (
|
|
283
|
-
spinner.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
spinner.warn("1Password not authenticated - secrets will not be loaded");
|
|
289
|
-
logger2.info("Run: eval $(op signin)");
|
|
540
|
+
const secretsConfig = config.secrets;
|
|
541
|
+
if (secretsConfig?.items && Object.keys(secretsConfig.items).length > 0) {
|
|
542
|
+
const provider = secretsConfig.provider || "env";
|
|
543
|
+
if (provider === "1password") {
|
|
544
|
+
spinner.start("Loading secrets from 1Password...");
|
|
545
|
+
const hasOp = await hasOpCli();
|
|
546
|
+
if (!hasOp) {
|
|
547
|
+
spinner.warn("1Password CLI not found - secrets will not be loaded");
|
|
548
|
+
logger2.info("Install with: brew install 1password-cli");
|
|
290
549
|
} else {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
550
|
+
const authenticated = await isOpAuthenticated();
|
|
551
|
+
if (!authenticated) {
|
|
552
|
+
spinner.warn("1Password not authenticated - secrets will not be loaded");
|
|
553
|
+
logger2.info("Run: eval $(op signin)");
|
|
554
|
+
} else {
|
|
555
|
+
try {
|
|
556
|
+
secrets = await loadSecrets(secretsConfig);
|
|
557
|
+
const count = Object.keys(secrets).length;
|
|
558
|
+
spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from 1Password`);
|
|
559
|
+
} catch (err) {
|
|
560
|
+
spinner.warn("Failed to load secrets from 1Password");
|
|
561
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
} else if (provider === "vault") {
|
|
566
|
+
spinner.start("Loading secrets from Vault...");
|
|
567
|
+
const hasVault = await hasVaultCli();
|
|
568
|
+
if (!hasVault) {
|
|
569
|
+
spinner.warn("Vault CLI not found - secrets will not be loaded");
|
|
570
|
+
logger2.info("Install from: https://developer.hashicorp.com/vault/docs/install");
|
|
571
|
+
} else {
|
|
572
|
+
const authenticated = await isVaultAuthenticated();
|
|
573
|
+
if (!authenticated) {
|
|
574
|
+
spinner.warn("Vault not authenticated - secrets will not be loaded");
|
|
575
|
+
logger2.info("Run: vault login");
|
|
576
|
+
} else {
|
|
577
|
+
try {
|
|
578
|
+
secrets = await loadSecrets(secretsConfig);
|
|
579
|
+
const count = Object.keys(secrets).length;
|
|
580
|
+
spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from Vault`);
|
|
581
|
+
} catch (err) {
|
|
582
|
+
spinner.warn("Failed to load secrets from Vault");
|
|
583
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
} else if (provider === "env") {
|
|
588
|
+
spinner.start("Loading secrets from environment...");
|
|
589
|
+
try {
|
|
590
|
+
secrets = await loadSecrets(secretsConfig);
|
|
591
|
+
const count = Object.keys(secrets).length;
|
|
592
|
+
if (count > 0) {
|
|
593
|
+
spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from environment`);
|
|
594
|
+
} else {
|
|
595
|
+
spinner.warn("No secrets found in environment");
|
|
298
596
|
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
spinner.warn("Failed to load secrets from environment");
|
|
599
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
299
600
|
}
|
|
300
601
|
}
|
|
301
602
|
}
|
|
302
603
|
logger2.blank();
|
|
303
604
|
logger2.info(`Launching ${logger2.brand(agentName)} in container...`);
|
|
605
|
+
const builtArgs = buildAgentArgs(agent, { injectSystemPrompt: true });
|
|
606
|
+
if (agentSupportsRuntimeInjection(agent)) {
|
|
607
|
+
logger2.dim("Injecting RAPID methodology via CLI args");
|
|
608
|
+
}
|
|
304
609
|
logger2.blank();
|
|
305
|
-
const agentArgs = [agent.cli, ...
|
|
610
|
+
const agentArgs = [agent.cli, ...builtArgs];
|
|
306
611
|
const mcpEnv = await prepareMcpEnv(rootDir, config.mcp);
|
|
307
612
|
const mergedEnv = { ...secrets, ...mcpEnv ?? {} };
|
|
308
613
|
await execInContainer(rootDir, agentArgs, config, {
|
|
@@ -341,19 +646,94 @@ async function prepareMcpEnv(rootDir, mcp) {
|
|
|
341
646
|
MCP_CONFIG_FILE: configFile
|
|
342
647
|
};
|
|
343
648
|
}
|
|
344
|
-
async function runLocally(agent, agentName, rootDir) {
|
|
649
|
+
async function runLocally(agent, agentName, rootDir, config) {
|
|
345
650
|
const { execa } = await import("execa");
|
|
346
651
|
const status = await checkAgentAvailable(agent);
|
|
347
652
|
if (!status.available) {
|
|
348
653
|
logger2.error(`${agentName} CLI not found locally`);
|
|
349
654
|
process.exit(1);
|
|
350
655
|
}
|
|
656
|
+
let secrets = {};
|
|
657
|
+
const secretsConfig = config.secrets;
|
|
658
|
+
if (secretsConfig?.items && Object.keys(secretsConfig.items).length > 0) {
|
|
659
|
+
const provider = secretsConfig.provider || "env";
|
|
660
|
+
const spinner = ora2();
|
|
661
|
+
if (provider === "1password") {
|
|
662
|
+
spinner.start("Loading secrets from 1Password...");
|
|
663
|
+
const hasOp = await hasOpCli();
|
|
664
|
+
if (!hasOp) {
|
|
665
|
+
spinner.warn("1Password CLI not found - secrets will not be loaded");
|
|
666
|
+
logger2.info("Install with: brew install 1password-cli");
|
|
667
|
+
} else {
|
|
668
|
+
const authenticated = await isOpAuthenticated();
|
|
669
|
+
if (!authenticated) {
|
|
670
|
+
spinner.warn("1Password not authenticated - secrets will not be loaded");
|
|
671
|
+
logger2.info("Run: eval $(op signin)");
|
|
672
|
+
} else {
|
|
673
|
+
try {
|
|
674
|
+
secrets = await loadSecrets(secretsConfig);
|
|
675
|
+
const count = Object.keys(secrets).length;
|
|
676
|
+
spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from 1Password`);
|
|
677
|
+
} catch (err) {
|
|
678
|
+
spinner.warn("Failed to load secrets from 1Password");
|
|
679
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
} else if (provider === "vault") {
|
|
684
|
+
spinner.start("Loading secrets from Vault...");
|
|
685
|
+
const hasVault = await hasVaultCli();
|
|
686
|
+
if (!hasVault) {
|
|
687
|
+
spinner.warn("Vault CLI not found - secrets will not be loaded");
|
|
688
|
+
logger2.info("Install from: https://developer.hashicorp.com/vault/docs/install");
|
|
689
|
+
} else {
|
|
690
|
+
const authenticated = await isVaultAuthenticated();
|
|
691
|
+
if (!authenticated) {
|
|
692
|
+
spinner.warn("Vault not authenticated - secrets will not be loaded");
|
|
693
|
+
logger2.info("Run: vault login");
|
|
694
|
+
} else {
|
|
695
|
+
try {
|
|
696
|
+
secrets = await loadSecrets(secretsConfig);
|
|
697
|
+
const count = Object.keys(secrets).length;
|
|
698
|
+
spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from Vault`);
|
|
699
|
+
} catch (err) {
|
|
700
|
+
spinner.warn("Failed to load secrets from Vault");
|
|
701
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
} else if (provider === "env") {
|
|
706
|
+
spinner.start("Loading secrets from environment...");
|
|
707
|
+
try {
|
|
708
|
+
secrets = await loadSecrets(secretsConfig);
|
|
709
|
+
const count = Object.keys(secrets).length;
|
|
710
|
+
if (count > 0) {
|
|
711
|
+
spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from environment`);
|
|
712
|
+
} else {
|
|
713
|
+
spinner.warn("No secrets found in environment");
|
|
714
|
+
}
|
|
715
|
+
} catch (err) {
|
|
716
|
+
spinner.warn("Failed to load secrets from environment");
|
|
717
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const mcpEnv = await prepareMcpEnv(rootDir, config.mcp);
|
|
722
|
+
const mergedEnv = { ...secrets, ...mcpEnv ?? {} };
|
|
351
723
|
logger2.info(`Launching ${logger2.brand(agentName)}...`);
|
|
352
724
|
logger2.dim(`Working directory: ${rootDir}`);
|
|
725
|
+
const builtArgs = buildAgentArgs(agent, { injectSystemPrompt: true });
|
|
726
|
+
if (agentSupportsRuntimeInjection(agent)) {
|
|
727
|
+
logger2.dim("Injecting RAPID methodology via CLI args");
|
|
728
|
+
}
|
|
353
729
|
logger2.blank();
|
|
354
|
-
await execa(agent.cli,
|
|
730
|
+
await execa(agent.cli, builtArgs, {
|
|
355
731
|
cwd: rootDir,
|
|
356
|
-
stdio: "inherit"
|
|
732
|
+
stdio: "inherit",
|
|
733
|
+
env: {
|
|
734
|
+
...process.env,
|
|
735
|
+
...mergedEnv
|
|
736
|
+
}
|
|
357
737
|
});
|
|
358
738
|
}
|
|
359
739
|
function listAgents(config) {
|
|
@@ -367,6 +747,112 @@ function listAgents(config) {
|
|
|
367
747
|
logger2.blank();
|
|
368
748
|
logger2.dim("Use --agent <name> to select a specific agent");
|
|
369
749
|
}
|
|
750
|
+
async function runMultiAgent(config, rootDir, options) {
|
|
751
|
+
const availableAgents = Object.keys(config.agents.available);
|
|
752
|
+
if (availableAgents.length === 0) {
|
|
753
|
+
logger2.error("No agents configured");
|
|
754
|
+
process.exit(1);
|
|
755
|
+
}
|
|
756
|
+
let selectedAgents;
|
|
757
|
+
if (typeof options.multi === "string") {
|
|
758
|
+
selectedAgents = options.multi.split(",").map((a) => a.trim()).filter(Boolean);
|
|
759
|
+
for (const name of selectedAgents) {
|
|
760
|
+
if (!config.agents.available[name]) {
|
|
761
|
+
logger2.error(`Agent "${name}" not found in configuration`);
|
|
762
|
+
logger2.info("Available agents: " + availableAgents.join(", "));
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
} else {
|
|
767
|
+
logger2.header("Multi-Agent Mode");
|
|
768
|
+
console.log();
|
|
769
|
+
console.log(" Available agents:");
|
|
770
|
+
for (const name of availableAgents) {
|
|
771
|
+
const isDefault = name === config.agents.default;
|
|
772
|
+
console.log(` ${logger2.brand("\u2022")} ${name}${isDefault ? logger2.dim(" (default)") : ""}`);
|
|
773
|
+
}
|
|
774
|
+
console.log();
|
|
775
|
+
logger2.info("To run multiple agents, specify them with:");
|
|
776
|
+
console.log(` ${logger2.brand("rapid dev --multi claude,aider")}`);
|
|
777
|
+
console.log();
|
|
778
|
+
logger2.info("Or run agents in separate terminals:");
|
|
779
|
+
for (const name of availableAgents) {
|
|
780
|
+
console.log(` ${logger2.dim("$")} rapid dev --agent ${name}`);
|
|
781
|
+
}
|
|
782
|
+
console.log();
|
|
783
|
+
logger2.warn("Note: Running multiple agents simultaneously requires separate terminal windows.");
|
|
784
|
+
logger2.info("Each agent maintains its own session and context.");
|
|
785
|
+
console.log();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (selectedAgents.length === 0) {
|
|
789
|
+
logger2.error("No agents specified");
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
if (selectedAgents.length === 1) {
|
|
793
|
+
logger2.info(
|
|
794
|
+
`Only one agent specified. Use ${logger2.brand("rapid dev --agent " + selectedAgents[0])} instead.`
|
|
795
|
+
);
|
|
796
|
+
process.exit(0);
|
|
797
|
+
}
|
|
798
|
+
logger2.header("Multi-Agent Session");
|
|
799
|
+
console.log();
|
|
800
|
+
console.log(" Selected agents:");
|
|
801
|
+
for (const name of selectedAgents) {
|
|
802
|
+
console.log(` ${logger2.brand("\u2022")} ${name}`);
|
|
803
|
+
}
|
|
804
|
+
console.log();
|
|
805
|
+
const { execa } = await import("execa");
|
|
806
|
+
let hasTmux = false;
|
|
807
|
+
try {
|
|
808
|
+
await execa("tmux", ["-V"]);
|
|
809
|
+
hasTmux = true;
|
|
810
|
+
} catch {
|
|
811
|
+
hasTmux = false;
|
|
812
|
+
}
|
|
813
|
+
if (hasTmux) {
|
|
814
|
+
logger2.info("Launching agents in tmux panes...");
|
|
815
|
+
console.log();
|
|
816
|
+
const sessionName = `rapid-${Date.now()}`;
|
|
817
|
+
const firstAgent = selectedAgents[0];
|
|
818
|
+
const firstCmd = options.local ? `rapid dev --agent ${firstAgent} --local` : `rapid dev --agent ${firstAgent}`;
|
|
819
|
+
await execa("tmux", ["new-session", "-d", "-s", sessionName, "-n", "rapid", firstCmd], {
|
|
820
|
+
cwd: rootDir
|
|
821
|
+
});
|
|
822
|
+
for (let i = 1; i < selectedAgents.length; i++) {
|
|
823
|
+
const agentName = selectedAgents[i];
|
|
824
|
+
const cmd = options.local ? `rapid dev --agent ${agentName} --local` : `rapid dev --agent ${agentName}`;
|
|
825
|
+
await execa("tmux", ["split-window", "-t", sessionName, "-h", cmd], {
|
|
826
|
+
cwd: rootDir
|
|
827
|
+
});
|
|
828
|
+
await execa("tmux", ["select-layout", "-t", sessionName, "tiled"]);
|
|
829
|
+
}
|
|
830
|
+
logger2.success(`Started ${selectedAgents.length} agents in tmux session: ${sessionName}`);
|
|
831
|
+
console.log();
|
|
832
|
+
logger2.info("Attaching to tmux session...");
|
|
833
|
+
logger2.dim("Press Ctrl+B then D to detach, or Ctrl+B then arrow keys to switch panes");
|
|
834
|
+
console.log();
|
|
835
|
+
await execa("tmux", ["attach-session", "-t", sessionName], {
|
|
836
|
+
stdio: "inherit"
|
|
837
|
+
});
|
|
838
|
+
} else {
|
|
839
|
+
logger2.warn("tmux not found. Multi-agent mode works best with tmux installed.");
|
|
840
|
+
console.log();
|
|
841
|
+
logger2.info("To run multiple agents, open separate terminal windows and run:");
|
|
842
|
+
console.log();
|
|
843
|
+
for (const name of selectedAgents) {
|
|
844
|
+
const cmd = options.local ? `--local` : "";
|
|
845
|
+
console.log(
|
|
846
|
+
` ${logger2.dim("Terminal " + (selectedAgents.indexOf(name) + 1) + ":")} rapid dev --agent ${name} ${cmd}`.trim()
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
console.log();
|
|
850
|
+
logger2.info("Install tmux for integrated multi-pane support:");
|
|
851
|
+
console.log(` ${logger2.dim("macOS:")} brew install tmux`);
|
|
852
|
+
console.log(` ${logger2.dim("Ubuntu:")} sudo apt install tmux`);
|
|
853
|
+
console.log();
|
|
854
|
+
}
|
|
855
|
+
}
|
|
370
856
|
|
|
371
857
|
// src/commands/status.ts
|
|
372
858
|
import { Command as Command3 } from "commander";
|
|
@@ -380,9 +866,9 @@ import {
|
|
|
380
866
|
loadDevcontainerConfig,
|
|
381
867
|
verifySecrets,
|
|
382
868
|
hasOpCli as hasOpCli2,
|
|
383
|
-
hasVaultCli,
|
|
869
|
+
hasVaultCli as hasVaultCli2,
|
|
384
870
|
isOpAuthenticated as isOpAuthenticated2,
|
|
385
|
-
isVaultAuthenticated,
|
|
871
|
+
isVaultAuthenticated as isVaultAuthenticated2,
|
|
386
872
|
hasEnvrc,
|
|
387
873
|
getProviderInfo,
|
|
388
874
|
getAuthStatus
|
|
@@ -418,8 +904,8 @@ var statusCommand = new Command3("status").description("Show environment status"
|
|
|
418
904
|
cliInstalled = await hasOpCli2();
|
|
419
905
|
authenticated = cliInstalled && await isOpAuthenticated2();
|
|
420
906
|
} else if (provider === "vault") {
|
|
421
|
-
cliInstalled = await
|
|
422
|
-
authenticated = cliInstalled && await
|
|
907
|
+
cliInstalled = await hasVaultCli2();
|
|
908
|
+
authenticated = cliInstalled && await isVaultAuthenticated2();
|
|
423
909
|
}
|
|
424
910
|
const envrcExists = await hasEnvrc(rootDir, secretsConfig);
|
|
425
911
|
const verified = await verifySecrets(secretsConfig);
|
|
@@ -761,9 +1247,9 @@ import {
|
|
|
761
1247
|
verifySecrets as verifySecrets2,
|
|
762
1248
|
loadSecrets as loadSecrets2,
|
|
763
1249
|
hasOpCli as hasOpCli3,
|
|
764
|
-
hasVaultCli as
|
|
1250
|
+
hasVaultCli as hasVaultCli3,
|
|
765
1251
|
isOpAuthenticated as isOpAuthenticated3,
|
|
766
|
-
isVaultAuthenticated as
|
|
1252
|
+
isVaultAuthenticated as isVaultAuthenticated3,
|
|
767
1253
|
getOpAuthStatus,
|
|
768
1254
|
hasOpServiceAccountToken,
|
|
769
1255
|
generateEnvrc,
|
|
@@ -806,14 +1292,14 @@ secretsCommand.command("verify").description("Verify all secrets are accessible"
|
|
|
806
1292
|
process.exit(1);
|
|
807
1293
|
}
|
|
808
1294
|
} else if (provider === "vault") {
|
|
809
|
-
const hasVault = await
|
|
1295
|
+
const hasVault = await hasVaultCli3();
|
|
810
1296
|
if (!hasVault) {
|
|
811
1297
|
spinner.fail("Vault CLI not found");
|
|
812
1298
|
console.log();
|
|
813
1299
|
logger7.info("Install from: https://developer.hashicorp.com/vault/docs/install");
|
|
814
1300
|
process.exit(1);
|
|
815
1301
|
}
|
|
816
|
-
const authenticated = await
|
|
1302
|
+
const authenticated = await isVaultAuthenticated3();
|
|
817
1303
|
if (!authenticated) {
|
|
818
1304
|
spinner.fail("Vault CLI not authenticated");
|
|
819
1305
|
console.log();
|
|
@@ -969,8 +1455,8 @@ secretsCommand.command("info").description("Show secrets provider information an
|
|
|
969
1455
|
authenticated = false;
|
|
970
1456
|
}
|
|
971
1457
|
} else if (provider === "vault") {
|
|
972
|
-
cliInstalled = await
|
|
973
|
-
authenticated = cliInstalled && await
|
|
1458
|
+
cliInstalled = await hasVaultCli3();
|
|
1459
|
+
authenticated = cliInstalled && await isVaultAuthenticated3();
|
|
974
1460
|
}
|
|
975
1461
|
const envrcExists = await hasEnvrc2(rootDir, secretsConfig);
|
|
976
1462
|
const hasServiceToken = hasOpServiceAccountToken();
|
|
@@ -1639,4 +2125,4 @@ program.action(() => {
|
|
|
1639
2125
|
export {
|
|
1640
2126
|
program
|
|
1641
2127
|
};
|
|
1642
|
-
//# sourceMappingURL=chunk-
|
|
2128
|
+
//# sourceMappingURL=chunk-FICHP3SD.js.map
|