@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.
@@ -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
- 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) => {
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
- return {
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
- ## Development Guidelines
142
-
143
- - Follow existing code patterns and conventions
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
- ## Development Guidelines
176
-
177
- - Follow existing code patterns and conventions
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 guidelines above when making changes
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("--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) => {
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
- if (config.secrets?.provider === "1password" && config.secrets.items) {
280
- spinner.start("Loading secrets from 1Password...");
281
- const hasOp = await hasOpCli();
282
- if (!hasOp) {
283
- spinner.warn("1Password CLI not found - secrets will not be loaded");
284
- logger2.info("Install with: brew install 1password-cli");
285
- } else {
286
- const authenticated = await isOpAuthenticated();
287
- if (!authenticated) {
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
- try {
292
- secrets = await loadSecrets(config.secrets);
293
- const count = Object.keys(secrets).length;
294
- spinner.succeed(`Loaded ${count} secret${count !== 1 ? "s" : ""} from 1Password`);
295
- } catch (err) {
296
- spinner.warn("Failed to load secrets from 1Password");
297
- 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
+ }
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, ...agent.args || []];
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, agent.args || [], {
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 hasVaultCli();
422
- authenticated = cliInstalled && await isVaultAuthenticated();
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 hasVaultCli2,
1250
+ hasVaultCli as hasVaultCli3,
765
1251
  isOpAuthenticated as isOpAuthenticated3,
766
- isVaultAuthenticated as isVaultAuthenticated2,
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 hasVaultCli2();
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 isVaultAuthenticated2();
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 hasVaultCli2();
973
- authenticated = cliInstalled && await isVaultAuthenticated2();
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-B3WZZMOG.js.map
2128
+ //# sourceMappingURL=chunk-FICHP3SD.js.map