@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.
@@ -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
- var initCommand = new Command("init").description("Initialize RAPID in a project").option("-t, --template <name>", "Template to use", "default").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").action(async (options) => {
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
- return {
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("--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) => {
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
- if (config.secrets?.provider === "1password" && config.secrets.items) {
276
- spinner.start("Loading secrets from 1Password...");
277
- const hasOp = await hasOpCli();
278
- if (!hasOp) {
279
- spinner.warn("1Password CLI not found - secrets will not be loaded");
280
- logger2.info("Install with: brew install 1password-cli");
281
- } else {
282
- const authenticated = await isOpAuthenticated();
283
- if (!authenticated) {
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
- try {
288
- secrets = await loadSecrets(config.secrets);
289
- const count = Object.keys(secrets).length;
290
- spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from 1Password`);
291
- } catch (err) {
292
- spinner.warn("Failed to load secrets from 1Password");
293
- logger2.debug(err instanceof Error ? err.message : String(err));
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, ...agent.args || []];
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, agent.args || [], {
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 hasVaultCli();
418
- authenticated = cliInstalled && await isVaultAuthenticated();
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 hasVaultCli2,
1250
+ hasVaultCli as hasVaultCli3,
761
1251
  isOpAuthenticated as isOpAuthenticated3,
762
- isVaultAuthenticated as isVaultAuthenticated2,
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 hasVaultCli2();
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 isVaultAuthenticated2();
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 hasVaultCli2();
969
- authenticated = cliInstalled && await isVaultAuthenticated2();
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-MNZPKPED.js.map
2128
+ //# sourceMappingURL=chunk-FICHP3SD.js.map