@a3t/rapid 0.1.6 → 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-MNZPKPED.js → chunk-FICHP3SD.js} +537 -47
- package/dist/chunk-FICHP3SD.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/dist/chunk-MNZPKPED.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,
|
|
@@ -22,7 +22,180 @@ import {
|
|
|
22
22
|
GIT_GUIDELINES
|
|
23
23
|
} from "@a3t/rapid-core";
|
|
24
24
|
import ora from "ora";
|
|
25
|
-
|
|
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) => {
|
|
26
199
|
const spinner = ora("Initializing RAPID...").start();
|
|
27
200
|
try {
|
|
28
201
|
const cwd = process.cwd();
|
|
@@ -35,8 +208,51 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
35
208
|
} catch {
|
|
36
209
|
}
|
|
37
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
|
+
}
|
|
38
254
|
const mcpServers = options.mcp === false ? [] : options.mcp.split(",").map((s) => s.trim());
|
|
39
|
-
let config = createConfig(options);
|
|
255
|
+
let config = createConfig(options, detectedProject);
|
|
40
256
|
if (mcpServers.length > 0) {
|
|
41
257
|
spinner.text = "Configuring MCP servers...";
|
|
42
258
|
for (const serverName of mcpServers) {
|
|
@@ -69,12 +285,23 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
69
285
|
if (config.agents.available.claude) {
|
|
70
286
|
spinner.text = "Creating CLAUDE.md...";
|
|
71
287
|
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
72
|
-
await writeFile(claudeMdPath, getClaudeMdTemplate(cwd));
|
|
288
|
+
await writeFile(claudeMdPath, getClaudeMdTemplate(cwd, detectedProject));
|
|
73
289
|
}
|
|
74
290
|
spinner.text = "Creating AGENTS.md...";
|
|
75
291
|
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
76
|
-
await writeFile(agentsMdPath, getAgentsMdTemplate(cwd));
|
|
292
|
+
await writeFile(agentsMdPath, getAgentsMdTemplate(cwd, detectedProject));
|
|
77
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
|
+
}
|
|
78
305
|
logger.blank();
|
|
79
306
|
logger.info("Created files:");
|
|
80
307
|
console.log(` ${logger.dim("\u2022")} rapid.json`);
|
|
@@ -110,9 +337,9 @@ var initCommand = new Command("init").description("Initialize RAPID in a project
|
|
|
110
337
|
process.exit(1);
|
|
111
338
|
}
|
|
112
339
|
});
|
|
113
|
-
function createConfig(options) {
|
|
340
|
+
function createConfig(options, detectedProject) {
|
|
114
341
|
const defaults = getDefaultConfig();
|
|
115
|
-
|
|
342
|
+
const config = {
|
|
116
343
|
$schema: "https://getrapid.dev/schema/v1/rapid.json",
|
|
117
344
|
version: "1.0",
|
|
118
345
|
agents: {
|
|
@@ -128,9 +355,26 @@ function createConfig(options) {
|
|
|
128
355
|
// We already created them
|
|
129
356
|
}
|
|
130
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;
|
|
131
365
|
}
|
|
132
|
-
function getClaudeMdTemplate(projectPath) {
|
|
366
|
+
function getClaudeMdTemplate(projectPath, detectedProject) {
|
|
133
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
|
+
}
|
|
134
378
|
return `# Claude Instructions
|
|
135
379
|
|
|
136
380
|
## Project: ${projectName}
|
|
@@ -141,7 +385,7 @@ This file contains instructions for Claude Code when working on this project.
|
|
|
141
385
|
|
|
142
386
|
<!-- Describe your project here -->
|
|
143
387
|
|
|
144
|
-
${RAPID_METHODOLOGY}
|
|
388
|
+
${languageSection}${RAPID_METHODOLOGY}
|
|
145
389
|
${MCP_USAGE_GUIDELINES}
|
|
146
390
|
${GIT_GUIDELINES}
|
|
147
391
|
## Key Files
|
|
@@ -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,7 +426,7 @@ This file contains instructions for AI coding agents working on this project.
|
|
|
172
426
|
|
|
173
427
|
<!-- Describe your project here -->
|
|
174
428
|
|
|
175
|
-
${RAPID_METHODOLOGY}
|
|
429
|
+
${languageSection}${RAPID_METHODOLOGY}
|
|
176
430
|
${MCP_USAGE_GUIDELINES}
|
|
177
431
|
${GIT_GUIDELINES}
|
|
178
432
|
## Project Structure
|
|
@@ -208,10 +462,17 @@ import {
|
|
|
208
462
|
hasDevcontainerCli,
|
|
209
463
|
loadSecrets,
|
|
210
464
|
hasOpCli,
|
|
211
|
-
isOpAuthenticated
|
|
465
|
+
isOpAuthenticated,
|
|
466
|
+
hasVaultCli,
|
|
467
|
+
isVaultAuthenticated,
|
|
468
|
+
buildAgentArgs,
|
|
469
|
+
agentSupportsRuntimeInjection
|
|
212
470
|
} from "@a3t/rapid-core";
|
|
213
471
|
import ora2 from "ora";
|
|
214
|
-
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) => {
|
|
215
476
|
try {
|
|
216
477
|
const spinner = ora2("Loading configuration...").start();
|
|
217
478
|
const loaded = await loadConfig();
|
|
@@ -225,6 +486,10 @@ var devCommand = new Command2("dev").description("Launch AI coding session in th
|
|
|
225
486
|
listAgents(config);
|
|
226
487
|
return;
|
|
227
488
|
}
|
|
489
|
+
if (options.multi !== void 0) {
|
|
490
|
+
await runMultiAgent(config, rootDir, options);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
228
493
|
const agentName = options.agent || config.agents.default;
|
|
229
494
|
const agent = getAgent(config, agentName);
|
|
230
495
|
if (!agent) {
|
|
@@ -240,7 +505,7 @@ var devCommand = new Command2("dev").description("Launch AI coding session in th
|
|
|
240
505
|
logger2.warn("Running locally instead of in container");
|
|
241
506
|
logger2.dim("This bypasses the isolated dev environment");
|
|
242
507
|
logger2.blank();
|
|
243
|
-
await runLocally(agent, agentName, rootDir);
|
|
508
|
+
await runLocally(agent, agentName, rootDir, config);
|
|
244
509
|
return;
|
|
245
510
|
}
|
|
246
511
|
const hasDevCli = await hasDevcontainerCli();
|
|
@@ -272,33 +537,77 @@ var devCommand = new Command2("dev").description("Launch AI coding session in th
|
|
|
272
537
|
spinner.succeed(`Container running (${status.containerName})`);
|
|
273
538
|
}
|
|
274
539
|
let secrets = {};
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
spinner.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
spinner.warn("1Password not authenticated - secrets will not be loaded");
|
|
285
|
-
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");
|
|
286
549
|
} else {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
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
|
+
}
|
|
294
585
|
}
|
|
295
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");
|
|
596
|
+
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
spinner.warn("Failed to load secrets from environment");
|
|
599
|
+
logger2.debug(err instanceof Error ? err.message : String(err));
|
|
600
|
+
}
|
|
296
601
|
}
|
|
297
602
|
}
|
|
298
603
|
logger2.blank();
|
|
299
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
|
+
}
|
|
300
609
|
logger2.blank();
|
|
301
|
-
const agentArgs = [agent.cli, ...
|
|
610
|
+
const agentArgs = [agent.cli, ...builtArgs];
|
|
302
611
|
const mcpEnv = await prepareMcpEnv(rootDir, config.mcp);
|
|
303
612
|
const mergedEnv = { ...secrets, ...mcpEnv ?? {} };
|
|
304
613
|
await execInContainer(rootDir, agentArgs, config, {
|
|
@@ -337,19 +646,94 @@ async function prepareMcpEnv(rootDir, mcp) {
|
|
|
337
646
|
MCP_CONFIG_FILE: configFile
|
|
338
647
|
};
|
|
339
648
|
}
|
|
340
|
-
async function runLocally(agent, agentName, rootDir) {
|
|
649
|
+
async function runLocally(agent, agentName, rootDir, config) {
|
|
341
650
|
const { execa } = await import("execa");
|
|
342
651
|
const status = await checkAgentAvailable(agent);
|
|
343
652
|
if (!status.available) {
|
|
344
653
|
logger2.error(`${agentName} CLI not found locally`);
|
|
345
654
|
process.exit(1);
|
|
346
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 ?? {} };
|
|
347
723
|
logger2.info(`Launching ${logger2.brand(agentName)}...`);
|
|
348
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
|
+
}
|
|
349
729
|
logger2.blank();
|
|
350
|
-
await execa(agent.cli,
|
|
730
|
+
await execa(agent.cli, builtArgs, {
|
|
351
731
|
cwd: rootDir,
|
|
352
|
-
stdio: "inherit"
|
|
732
|
+
stdio: "inherit",
|
|
733
|
+
env: {
|
|
734
|
+
...process.env,
|
|
735
|
+
...mergedEnv
|
|
736
|
+
}
|
|
353
737
|
});
|
|
354
738
|
}
|
|
355
739
|
function listAgents(config) {
|
|
@@ -363,6 +747,112 @@ function listAgents(config) {
|
|
|
363
747
|
logger2.blank();
|
|
364
748
|
logger2.dim("Use --agent <name> to select a specific agent");
|
|
365
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
|
+
}
|
|
366
856
|
|
|
367
857
|
// src/commands/status.ts
|
|
368
858
|
import { Command as Command3 } from "commander";
|
|
@@ -376,9 +866,9 @@ import {
|
|
|
376
866
|
loadDevcontainerConfig,
|
|
377
867
|
verifySecrets,
|
|
378
868
|
hasOpCli as hasOpCli2,
|
|
379
|
-
hasVaultCli,
|
|
869
|
+
hasVaultCli as hasVaultCli2,
|
|
380
870
|
isOpAuthenticated as isOpAuthenticated2,
|
|
381
|
-
isVaultAuthenticated,
|
|
871
|
+
isVaultAuthenticated as isVaultAuthenticated2,
|
|
382
872
|
hasEnvrc,
|
|
383
873
|
getProviderInfo,
|
|
384
874
|
getAuthStatus
|
|
@@ -414,8 +904,8 @@ var statusCommand = new Command3("status").description("Show environment status"
|
|
|
414
904
|
cliInstalled = await hasOpCli2();
|
|
415
905
|
authenticated = cliInstalled && await isOpAuthenticated2();
|
|
416
906
|
} else if (provider === "vault") {
|
|
417
|
-
cliInstalled = await
|
|
418
|
-
authenticated = cliInstalled && await
|
|
907
|
+
cliInstalled = await hasVaultCli2();
|
|
908
|
+
authenticated = cliInstalled && await isVaultAuthenticated2();
|
|
419
909
|
}
|
|
420
910
|
const envrcExists = await hasEnvrc(rootDir, secretsConfig);
|
|
421
911
|
const verified = await verifySecrets(secretsConfig);
|
|
@@ -757,9 +1247,9 @@ import {
|
|
|
757
1247
|
verifySecrets as verifySecrets2,
|
|
758
1248
|
loadSecrets as loadSecrets2,
|
|
759
1249
|
hasOpCli as hasOpCli3,
|
|
760
|
-
hasVaultCli as
|
|
1250
|
+
hasVaultCli as hasVaultCli3,
|
|
761
1251
|
isOpAuthenticated as isOpAuthenticated3,
|
|
762
|
-
isVaultAuthenticated as
|
|
1252
|
+
isVaultAuthenticated as isVaultAuthenticated3,
|
|
763
1253
|
getOpAuthStatus,
|
|
764
1254
|
hasOpServiceAccountToken,
|
|
765
1255
|
generateEnvrc,
|
|
@@ -802,14 +1292,14 @@ secretsCommand.command("verify").description("Verify all secrets are accessible"
|
|
|
802
1292
|
process.exit(1);
|
|
803
1293
|
}
|
|
804
1294
|
} else if (provider === "vault") {
|
|
805
|
-
const hasVault = await
|
|
1295
|
+
const hasVault = await hasVaultCli3();
|
|
806
1296
|
if (!hasVault) {
|
|
807
1297
|
spinner.fail("Vault CLI not found");
|
|
808
1298
|
console.log();
|
|
809
1299
|
logger7.info("Install from: https://developer.hashicorp.com/vault/docs/install");
|
|
810
1300
|
process.exit(1);
|
|
811
1301
|
}
|
|
812
|
-
const authenticated = await
|
|
1302
|
+
const authenticated = await isVaultAuthenticated3();
|
|
813
1303
|
if (!authenticated) {
|
|
814
1304
|
spinner.fail("Vault CLI not authenticated");
|
|
815
1305
|
console.log();
|
|
@@ -965,8 +1455,8 @@ secretsCommand.command("info").description("Show secrets provider information an
|
|
|
965
1455
|
authenticated = false;
|
|
966
1456
|
}
|
|
967
1457
|
} else if (provider === "vault") {
|
|
968
|
-
cliInstalled = await
|
|
969
|
-
authenticated = cliInstalled && await
|
|
1458
|
+
cliInstalled = await hasVaultCli3();
|
|
1459
|
+
authenticated = cliInstalled && await isVaultAuthenticated3();
|
|
970
1460
|
}
|
|
971
1461
|
const envrcExists = await hasEnvrc2(rootDir, secretsConfig);
|
|
972
1462
|
const hasServiceToken = hasOpServiceAccountToken();
|
|
@@ -1635,4 +2125,4 @@ program.action(() => {
|
|
|
1635
2125
|
export {
|
|
1636
2126
|
program
|
|
1637
2127
|
};
|
|
1638
|
-
//# sourceMappingURL=chunk-
|
|
2128
|
+
//# sourceMappingURL=chunk-FICHP3SD.js.map
|