beth-copilot 1.0.16 → 1.0.18

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.
Files changed (41) hide show
  1. package/bin/cli.js +355 -47
  2. package/dist/cli/commands/client-config.d.ts +31 -0
  3. package/dist/cli/commands/client-config.d.ts.map +1 -0
  4. package/dist/cli/commands/client-config.e2e.test.d.ts +15 -0
  5. package/dist/cli/commands/client-config.e2e.test.d.ts.map +1 -0
  6. package/dist/cli/commands/client-config.e2e.test.js +556 -0
  7. package/dist/cli/commands/client-config.e2e.test.js.map +1 -0
  8. package/dist/cli/commands/client-config.js +73 -0
  9. package/dist/cli/commands/client-config.js.map +1 -0
  10. package/dist/cli/commands/client-config.test.d.ts +6 -0
  11. package/dist/cli/commands/client-config.test.d.ts.map +1 -0
  12. package/dist/cli/commands/client-config.test.js +133 -0
  13. package/dist/cli/commands/client-config.test.js.map +1 -0
  14. package/dist/cli/commands/help.e2e.test.js +4 -4
  15. package/dist/cli/commands/help.e2e.test.js.map +1 -1
  16. package/dist/cli/commands/init-quickstart.e2e.test.d.ts +11 -0
  17. package/dist/cli/commands/init-quickstart.e2e.test.d.ts.map +1 -0
  18. package/dist/cli/commands/init-quickstart.e2e.test.js +221 -0
  19. package/dist/cli/commands/init-quickstart.e2e.test.js.map +1 -0
  20. package/dist/cli/commands/mcp.e2e.test.js +29 -22
  21. package/dist/cli/commands/mcp.e2e.test.js.map +1 -1
  22. package/dist/cli/commands/pipeline.e2e.test.js +19 -19
  23. package/dist/cli/commands/pipeline.e2e.test.js.map +1 -1
  24. package/dist/cli/commands/quickstart.d.ts.map +1 -1
  25. package/dist/cli/commands/quickstart.js +23 -7
  26. package/dist/cli/commands/quickstart.js.map +1 -1
  27. package/dist/cli/commands/quickstart.test.js +65 -0
  28. package/dist/cli/commands/quickstart.test.js.map +1 -1
  29. package/package.json +1 -1
  30. package/sbom.json +209 -209
  31. package/templates/.github/agents/beth.agent.md +24 -1
  32. package/templates/.github/agents/developer.agent.md +25 -18
  33. package/templates/.github/agents/product-manager.agent.md +11 -0
  34. package/templates/.github/agents/researcher.agent.md +11 -0
  35. package/templates/.github/agents/security-reviewer.agent.md +11 -0
  36. package/templates/.github/agents/tester.agent.md +11 -0
  37. package/templates/.github/agents/ux-designer.agent.md +11 -0
  38. package/templates/.github/copilot-instructions.md +21 -0
  39. package/templates/.vscode/mcp.json +20 -0
  40. package/templates/CLAUDE.md +129 -0
  41. package/templates/mcp.json.example +3 -0
package/bin/cli.js CHANGED
@@ -788,34 +788,59 @@ ${COLORS.bright}Usage:${COLORS.reset}
788
788
  ${COLORS.bright}Options:${COLORS.reset}
789
789
  --force Overwrite existing files
790
790
  --skip-backlog Don't create Backlog.md
791
- --skip-mcp Don't create mcp.json.example
791
+ --skip-mcp Don't install MCP server configs
792
792
  --skip-beads Skip beads check (not recommended)
793
793
  --verbose Show detailed diagnostics on errors
794
+ --client <type> Skip interactive prompt. Values:
795
+ vscode, copilot-cli, claude-code, all
794
796
 
795
797
  ${COLORS.bright}Examples:${COLORS.reset}
796
- npx beth-copilot init Set up Beth in current project
798
+ npx beth-copilot init Set up Beth (interactive client selection)
799
+ npx beth-copilot init --client vscode VS Code + Copilot only
800
+ npx beth-copilot init --client claude-code Claude Code only
801
+ npx beth-copilot init --client all All clients
797
802
  npx beth-copilot init --force Overwrite existing Beth files
798
803
  npx beth-copilot doctor Verify installation health
799
804
 
800
805
  ${COLORS.bright}What gets installed:${COLORS.reset}
801
- .github/agents/ 7 specialized AI agents
802
- .github/skills/ 8 domain knowledge modules
803
- .github/copilot-instructions.md Copilot configuration
804
- .vscode/settings.json Recommended VS Code settings
805
- AGENTS.md Workflow documentation
806
- Backlog.md Task tracking file
807
- mcp.json.example Optional MCP server config
808
-
809
- ${COLORS.bright}After installation:${COLORS.reset}
810
- 1. Open project in VS Code
811
- 2. Open Copilot Chat (Ctrl+Alt+I / Cmd+Alt+I)
812
- 3. Type @Beth to start working
806
+ ${COLORS.dim}Shared (all clients):${COLORS.reset}
807
+ .github/skills/ Domain knowledge modules
808
+ AGENTS.md Workflow documentation
809
+ Backlog.md Task tracking file
810
+
811
+ ${COLORS.dim}VS Code + GitHub Copilot:${COLORS.reset}
812
+ .github/agents/ 7 specialized AI agents
813
+ .github/copilot-instructions.md Copilot configuration
814
+ .vscode/settings.json Recommended VS Code settings
815
+ .vscode/mcp.json MCP servers (beads, shadcn, playwright, deepwiki)
816
+
817
+ ${COLORS.dim}GitHub Copilot CLI:${COLORS.reset}
818
+ .github/copilot-instructions.md Copilot configuration
819
+
820
+ ${COLORS.dim}Claude Code:${COLORS.reset}
821
+ CLAUDE.md Claude Code instructions
822
+
823
+ ${COLORS.bright}Supported clients:${COLORS.reset}
824
+ VS Code with GitHub Copilot Full agent orchestration with MCP
825
+ GitHub Copilot CLI Terminal-based with bd CLI
826
+ Claude Code CLAUDE.md + bd setup claude hooks
813
827
 
814
828
  ${COLORS.bright}Documentation:${COLORS.reset}
815
829
  https://github.com/stephschofield/beth
816
830
  `);
817
831
  }
818
832
 
833
+ /**
834
+ * Persist client selection to .github/.beth-client.json
835
+ * So quickstart and other commands know which client was configured.
836
+ */
837
+ function persistClientConfig(cwd, clients) {
838
+ const configDir = join(cwd, '.github');
839
+ const configPath = join(configDir, '.beth-client.json');
840
+ mkdirSync(configDir, { recursive: true });
841
+ writeFileSync(configPath, JSON.stringify(clients, null, 2) + '\n');
842
+ }
843
+
819
844
  function copyDirRecursive(src, dest, options = {}) {
820
845
  const { force = false, copiedFiles = [] } = options;
821
846
 
@@ -864,8 +889,149 @@ function copyDirRecursive(src, dest, options = {}) {
864
889
  return copiedFiles;
865
890
  }
866
891
 
892
+ /**
893
+ * Prompt the user to select their AI coding client(s).
894
+ * Returns an object with boolean flags for each client.
895
+ */
896
+ async function promptForClient() {
897
+ console.log('');
898
+ log('Which AI coding tool are you using?', COLORS.bright);
899
+ console.log('');
900
+ console.log(` ${COLORS.cyan}[1]${COLORS.reset} VS Code with GitHub Copilot`);
901
+ console.log(` ${COLORS.cyan}[2]${COLORS.reset} GitHub Copilot CLI (terminal)`);
902
+ console.log(` ${COLORS.cyan}[3]${COLORS.reset} Claude Code`);
903
+ console.log(` ${COLORS.cyan}[a]${COLORS.reset} All of the above`);
904
+ console.log('');
905
+
906
+ const answer = await promptForInput('Enter selection (1/2/3/a, or comma-separated e.g. 1,3):');
907
+
908
+ if (!answer || answer.toLowerCase() === 'a') {
909
+ return { vscode: true, copilotCli: true, claudeCode: true };
910
+ }
911
+
912
+ const selections = answer.split(',').map(s => s.trim());
913
+ return {
914
+ vscode: selections.includes('1'),
915
+ copilotCli: selections.includes('2'),
916
+ claudeCode: selections.includes('3'),
917
+ };
918
+ }
919
+
920
+ /**
921
+ * Parse --client flag value into client selection object.
922
+ */
923
+ function parseClientFlag(clientArg) {
924
+ if (!clientArg || clientArg === 'all') {
925
+ return { vscode: true, copilotCli: true, claudeCode: true };
926
+ }
927
+ return {
928
+ vscode: clientArg === 'vscode',
929
+ copilotCli: clientArg === 'copilot-cli',
930
+ claudeCode: clientArg === 'claude-code',
931
+ };
932
+ }
933
+
934
+ /**
935
+ * Install beads-mcp (MCP server) for VS Code integration.
936
+ *
937
+ * SECURITY NOTE - shell:true usage:
938
+ * - Required for cross-platform uv/pip execution
939
+ * - Arguments are HARDCODED - no user input is passed to the shell
940
+ * - Command injection risk: NONE (no dynamic/user-supplied values)
941
+ */
942
+ async function installBeadsMcp() {
943
+ log('\nInstalling beads-mcp (MCP server for VS Code)...', COLORS.cyan);
944
+
945
+ // Try uv first, then pip
946
+ const installers = [
947
+ { cmd: 'uv', args: ['tool', 'install', 'beads-mcp'], label: 'uv tool install beads-mcp' },
948
+ { cmd: 'pip', args: ['install', 'beads-mcp'], label: 'pip install beads-mcp' },
949
+ ];
950
+
951
+ for (const installer of installers) {
952
+ try {
953
+ execSync(`${installer.cmd} --version`, { stdio: 'ignore' });
954
+ } catch {
955
+ logDebug(`${installer.cmd} not found, trying next installer...`);
956
+ continue;
957
+ }
958
+
959
+ logInfo(installer.label);
960
+
961
+ // SECURITY: All arguments are hardcoded constants.
962
+ return new Promise((resolve) => {
963
+ const child = spawn(installer.cmd, installer.args, {
964
+ stdio: 'inherit',
965
+ shell: true,
966
+ });
967
+
968
+ child.on('close', (code) => {
969
+ if (code === 0) {
970
+ logSuccess('beads-mcp installed!');
971
+ resolve(true);
972
+ } else {
973
+ logWarning(`${installer.label} failed.`);
974
+ resolve(false);
975
+ }
976
+ });
977
+
978
+ child.on('error', () => {
979
+ logWarning(`Failed to run ${installer.cmd}.`);
980
+ resolve(false);
981
+ });
982
+ });
983
+ }
984
+
985
+ logWarning('Neither uv nor pip found. Install beads-mcp manually:');
986
+ logInfo(' uv tool install beads-mcp');
987
+ logInfo(' OR: pip install beads-mcp');
988
+ return false;
989
+ }
990
+
991
+ /**
992
+ * Run `bd setup claude` to configure Claude Code integration.
993
+ *
994
+ * SECURITY NOTE - shell:true usage:
995
+ * - bdPath is validated via getBeadsPath()
996
+ * - Arguments are HARDCODED ('setup', 'claude')
997
+ * - Command injection risk: LOW (bdPath validated, no user input in args)
998
+ */
999
+ async function runBdSetupClaude() {
1000
+ log('\nConfiguring beads for Claude Code...', COLORS.cyan);
1001
+
1002
+ const bdPath = getBeadsPath();
1003
+ if (!bdPath) {
1004
+ logWarning('Cannot run bd setup claude: bd not found.');
1005
+ logInfo('Run manually after installing beads: bd setup claude');
1006
+ return false;
1007
+ }
1008
+
1009
+ // SECURITY: bdPath is validated, only hardcoded args.
1010
+ return new Promise((resolve) => {
1011
+ const child = spawn(bdPath, ['setup', 'claude'], {
1012
+ stdio: 'inherit',
1013
+ shell: true,
1014
+ });
1015
+
1016
+ child.on('close', (code) => {
1017
+ if (code === 0) {
1018
+ logSuccess('Claude Code integration configured!');
1019
+ resolve(true);
1020
+ } else {
1021
+ logWarning('bd setup claude failed. Run manually: bd setup claude');
1022
+ resolve(false);
1023
+ }
1024
+ });
1025
+
1026
+ child.on('error', () => {
1027
+ logWarning('Failed to run bd setup claude. Run manually: bd setup claude');
1028
+ resolve(false);
1029
+ });
1030
+ });
1031
+ }
1032
+
867
1033
  async function init(options = {}) {
868
- const { force = false, skipBacklog = false, skipMcp = false, skipBeads = false } = options;
1034
+ const { force = false, skipBacklog = false, skipMcp = false, skipBeads = false, client: clientArg } = options;
869
1035
  const cwd = process.cwd();
870
1036
 
871
1037
  // Check for updates
@@ -888,6 +1054,27 @@ ${COLORS.yellow}╔════════════════════
888
1054
 
889
1055
  log(`${COLORS.yellow}Tip: Run with --verbose for detailed diagnostics if you hit issues.${COLORS.reset}`);
890
1056
 
1057
+ // Determine which client(s) to configure
1058
+ let clients;
1059
+ if (clientArg) {
1060
+ clients = parseClientFlag(clientArg);
1061
+ } else {
1062
+ clients = await promptForClient();
1063
+ }
1064
+
1065
+ // Validate at least one client selected
1066
+ if (!clients.vscode && !clients.copilotCli && !clients.claudeCode) {
1067
+ logWarning('No client selected. Defaulting to VS Code with GitHub Copilot.');
1068
+ clients.vscode = true;
1069
+ }
1070
+
1071
+ const selectedNames = [];
1072
+ if (clients.vscode) selectedNames.push('VS Code + Copilot');
1073
+ if (clients.copilotCli) selectedNames.push('Copilot CLI');
1074
+ if (clients.claudeCode) selectedNames.push('Claude Code');
1075
+ log(`\nConfiguring for: ${COLORS.cyan}${selectedNames.join(', ')}${COLORS.reset}`);
1076
+
1077
+
891
1078
  // Check if templates exist
892
1079
  if (!existsSync(TEMPLATES_DIR)) {
893
1080
  logError('Templates directory not found. Package may be corrupted.');
@@ -896,13 +1083,15 @@ ${COLORS.yellow}╔════════════════════
896
1083
 
897
1084
  const copiedFiles = [];
898
1085
 
899
- // Copy .github directory (agents, skills, copilot-instructions.md)
900
- const githubSrc = join(TEMPLATES_DIR, '.github');
901
- const githubDest = join(cwd, '.github');
1086
+ // === SHARED FILES (all clients) ===
902
1087
 
903
- if (existsSync(githubSrc)) {
904
- log('\nInstalling agents and skills...');
905
- copyDirRecursive(githubSrc, githubDest, { force, copiedFiles });
1088
+ // Copy .github/skills/ (domain knowledge - useful for all clients)
1089
+ const skillsSrc = join(TEMPLATES_DIR, '.github', 'skills');
1090
+ const skillsDest = join(cwd, '.github', 'skills');
1091
+
1092
+ if (existsSync(skillsSrc)) {
1093
+ log('\nInstalling skills (domain knowledge)...');
1094
+ copyDirRecursive(skillsSrc, skillsDest, { force, copiedFiles });
906
1095
  }
907
1096
 
908
1097
  // Copy AGENTS.md
@@ -933,33 +1122,38 @@ ${COLORS.yellow}╔════════════════════
933
1122
  }
934
1123
  }
935
1124
 
936
- // Copy mcp.json.example (unless skipped)
937
- if (!skipMcp) {
938
- const mcpSrc = join(TEMPLATES_DIR, 'mcp.json.example');
939
- const mcpDest = join(cwd, 'mcp.json.example');
1125
+ // === VS CODE + COPILOT FILES ===
1126
+ if (clients.vscode) {
1127
+ log('\nInstalling VS Code + Copilot configuration...');
940
1128
 
941
- if (existsSync(mcpSrc)) {
942
- if (existsSync(mcpDest) && !force) {
943
- logWarning('Skipped (exists): mcp.json.example');
1129
+ // .github/agents/ (agent definitions with frontmatter)
1130
+ const agentsSrc = join(TEMPLATES_DIR, '.github', 'agents');
1131
+ const agentsDest = join(cwd, '.github', 'agents');
1132
+ if (existsSync(agentsSrc)) {
1133
+ copyDirRecursive(agentsSrc, agentsDest, { force, copiedFiles });
1134
+ }
1135
+
1136
+ // .github/copilot-instructions.md
1137
+ const copilotInstructionsSrc = join(TEMPLATES_DIR, '.github', 'copilot-instructions.md');
1138
+ const copilotInstructionsDest = join(cwd, '.github', 'copilot-instructions.md');
1139
+ if (existsSync(copilotInstructionsSrc)) {
1140
+ if (existsSync(copilotInstructionsDest) && !force) {
1141
+ logWarning('Skipped (exists): .github/copilot-instructions.md');
944
1142
  } else {
945
- copyFileSync(mcpSrc, mcpDest);
946
- copiedFiles.push('mcp.json.example');
1143
+ mkdirSync(join(cwd, '.github'), { recursive: true });
1144
+ copyFileSync(copilotInstructionsSrc, copilotInstructionsDest);
1145
+ copiedFiles.push('.github/copilot-instructions.md');
947
1146
  }
948
1147
  }
949
- }
950
-
951
- // Copy .vscode/settings.json (recommended settings for agent mode)
952
- const vscodeSrc = join(TEMPLATES_DIR, '.vscode');
953
- const vscodeDest = join(cwd, '.vscode');
954
-
955
- if (existsSync(vscodeSrc)) {
1148
+
1149
+ // .vscode/settings.json
1150
+ const vscodeDest = join(cwd, '.vscode');
956
1151
  if (!existsSync(vscodeDest)) {
957
1152
  mkdirSync(vscodeDest, { recursive: true });
958
1153
  }
959
1154
 
960
- const settingsSrc = join(vscodeSrc, 'settings.json');
1155
+ const settingsSrc = join(TEMPLATES_DIR, '.vscode', 'settings.json');
961
1156
  const settingsDest = join(vscodeDest, 'settings.json');
962
-
963
1157
  if (existsSync(settingsSrc)) {
964
1158
  if (existsSync(settingsDest) && !force) {
965
1159
  logWarning('Skipped (exists): .vscode/settings.json');
@@ -968,8 +1162,61 @@ ${COLORS.yellow}╔════════════════════
968
1162
  copiedFiles.push('.vscode/settings.json');
969
1163
  }
970
1164
  }
1165
+
1166
+ // .vscode/mcp.json (beads + shadcn + playwright + deepwiki)
1167
+ if (!skipMcp) {
1168
+ const mcpJsonSrc = join(TEMPLATES_DIR, '.vscode', 'mcp.json');
1169
+ const mcpJsonDest = join(vscodeDest, 'mcp.json');
1170
+ if (existsSync(mcpJsonSrc)) {
1171
+ if (existsSync(mcpJsonDest) && !force) {
1172
+ logWarning('Skipped (exists): .vscode/mcp.json');
1173
+ } else {
1174
+ copyFileSync(mcpJsonSrc, mcpJsonDest);
1175
+ copiedFiles.push('.vscode/mcp.json');
1176
+ }
1177
+ }
1178
+ }
1179
+ }
1180
+
1181
+ // === COPILOT CLI FILES ===
1182
+ if (clients.copilotCli && !clients.vscode) {
1183
+ // Only install copilot-instructions.md if VS Code didn't already do it
1184
+ log('\nInstalling Copilot CLI configuration...');
1185
+
1186
+ const copilotInstructionsSrc = join(TEMPLATES_DIR, '.github', 'copilot-instructions.md');
1187
+ const copilotInstructionsDest = join(cwd, '.github', 'copilot-instructions.md');
1188
+ if (existsSync(copilotInstructionsSrc)) {
1189
+ if (existsSync(copilotInstructionsDest) && !force) {
1190
+ logWarning('Skipped (exists): .github/copilot-instructions.md');
1191
+ } else {
1192
+ mkdirSync(join(cwd, '.github'), { recursive: true });
1193
+ copyFileSync(copilotInstructionsSrc, copilotInstructionsDest);
1194
+ copiedFiles.push('.github/copilot-instructions.md');
1195
+ }
1196
+ }
971
1197
  }
972
1198
 
1199
+ // === CLAUDE CODE FILES ===
1200
+ if (clients.claudeCode) {
1201
+ log('\nInstalling Claude Code configuration...');
1202
+
1203
+ // CLAUDE.md
1204
+ const claudeMdSrc = join(TEMPLATES_DIR, 'CLAUDE.md');
1205
+ const claudeMdDest = join(cwd, 'CLAUDE.md');
1206
+ if (existsSync(claudeMdSrc)) {
1207
+ if (existsSync(claudeMdDest) && !force) {
1208
+ logWarning('Skipped (exists): CLAUDE.md');
1209
+ } else {
1210
+ copyFileSync(claudeMdSrc, claudeMdDest);
1211
+ copiedFiles.push('CLAUDE.md');
1212
+ }
1213
+ }
1214
+ }
1215
+
1216
+ // Persist client selection for quickstart and other commands
1217
+ persistClientConfig(cwd, clients);
1218
+ copiedFiles.push('.github/.beth-client.json');
1219
+
973
1220
  // Summary
974
1221
  console.log('');
975
1222
  if (copiedFiles.length > 0) {
@@ -1095,6 +1342,29 @@ ${COLORS.yellow}╔════════════════════
1095
1342
  await runBeadsDoctor();
1096
1343
  }
1097
1344
 
1345
+ // === CLIENT-SPECIFIC BEADS INTEGRATION ===
1346
+ if (!skipBeads && getBeadsPath() && isBeadsInitialized(cwd)) {
1347
+ // VS Code: install beads-mcp (MCP server)
1348
+ if (clients.vscode) {
1349
+ const shouldInstallMcp = await promptYesNo('Install beads-mcp for VS Code MCP integration?');
1350
+ if (shouldInstallMcp) {
1351
+ await installBeadsMcp();
1352
+ } else {
1353
+ logInfo('Skipped beads-mcp. Install later with: uv tool install beads-mcp');
1354
+ }
1355
+ }
1356
+
1357
+ // Claude Code: run bd setup claude
1358
+ if (clients.claudeCode) {
1359
+ const shouldSetupClaude = await promptYesNo('Configure beads for Claude Code? (bd setup claude)');
1360
+ if (shouldSetupClaude) {
1361
+ await runBdSetupClaude();
1362
+ } else {
1363
+ logInfo('Skipped Claude Code setup. Run later: bd setup claude');
1364
+ }
1365
+ }
1366
+ }
1367
+
1098
1368
  // Final verification
1099
1369
  console.log('');
1100
1370
  log('Verifying installation...', COLORS.cyan);
@@ -1111,15 +1381,35 @@ ${COLORS.yellow}╔════════════════════
1111
1381
  process.exit(1);
1112
1382
  }
1113
1383
 
1114
- // Next steps
1115
- console.log(`
1116
- ${COLORS.bright}Next steps:${COLORS.reset}
1384
+ // Next steps (client-specific)
1385
+ console.log('');
1386
+ log('Next steps:', COLORS.bright);
1387
+
1388
+ if (clients.vscode) {
1389
+ console.log(`
1390
+ ${COLORS.bright}VS Code + Copilot:${COLORS.reset}
1117
1391
  1. Open this project in VS Code
1118
1392
  2. Open Copilot Chat (${COLORS.cyan}Ctrl+Alt+I${COLORS.reset} / ${COLORS.cyan}Cmd+Alt+I${COLORS.reset})
1119
- 3. Type ${COLORS.cyan}@Beth${COLORS.reset} to start - she's your orchestrator
1120
-
1121
- ${COLORS.bright}Pro tip:${COLORS.reset} Start every session with ${COLORS.cyan}@Beth${COLORS.reset} and let her route work to the right specialists.
1393
+ 3. Type ${COLORS.cyan}@Beth${COLORS.reset} to start she's your orchestrator`);
1394
+ }
1395
+
1396
+ if (clients.copilotCli) {
1397
+ console.log(`
1398
+ ${COLORS.bright}Copilot CLI:${COLORS.reset}
1399
+ 1. Run ${COLORS.cyan}copilot${COLORS.reset} in your project directory
1400
+ 2. Beth's instructions are in ${COLORS.cyan}.github/copilot-instructions.md${COLORS.reset}
1401
+ 3. Use ${COLORS.cyan}bd ready${COLORS.reset} to find work, ${COLORS.cyan}bd create${COLORS.reset} to track tasks`);
1402
+ }
1403
+
1404
+ if (clients.claudeCode) {
1405
+ console.log(`
1406
+ ${COLORS.bright}Claude Code:${COLORS.reset}
1407
+ 1. Run ${COLORS.cyan}claude${COLORS.reset} in your project directory
1408
+ 2. Beth's instructions are in ${COLORS.cyan}CLAUDE.md${COLORS.reset}
1409
+ 3. Use ${COLORS.cyan}bd ready${COLORS.reset} to find work, ${COLORS.cyan}bd prime${COLORS.reset} for session context`);
1410
+ }
1122
1411
 
1412
+ console.log(`
1123
1413
  ${COLORS.bright}Documentation:${COLORS.reset}
1124
1414
  https://github.com/stephschofield/beth
1125
1415
 
@@ -1129,7 +1419,8 @@ ${COLORS.cyan}"They broke my wings and forgot I had claws."${COLORS.reset}
1129
1419
 
1130
1420
  // Input validation constants
1131
1421
  const ALLOWED_COMMANDS = ['init', 'help', '--help', '-h', 'doctor', 'quickstart'];
1132
- const ALLOWED_FLAGS = ['--force', '--skip-backlog', '--skip-mcp', '--skip-beads', '--verbose'];
1422
+ const ALLOWED_FLAGS = ['--force', '--skip-backlog', '--skip-mcp', '--skip-beads', '--verbose', '--client'];
1423
+ const ALLOWED_CLIENTS = ['vscode', 'copilot-cli', 'claude-code', 'all'];
1133
1424
  const MAX_ARG_LENGTH = 50;
1134
1425
 
1135
1426
  // Validate and sanitize input
@@ -1154,19 +1445,36 @@ validateArgs(args);
1154
1445
 
1155
1446
  const command = args[0]?.toLowerCase();
1156
1447
 
1448
+ // Parse --client flag value (e.g. --client vscode)
1449
+ let clientArg = null;
1450
+ const clientFlagIndex = args.indexOf('--client');
1451
+ if (clientFlagIndex !== -1 && clientFlagIndex + 1 < args.length) {
1452
+ clientArg = args[clientFlagIndex + 1].toLowerCase();
1453
+ if (!ALLOWED_CLIENTS.includes(clientArg)) {
1454
+ logError(`Invalid client: ${clientArg.slice(0, MAX_ARG_LENGTH)}`);
1455
+ console.log(`Valid clients: ${ALLOWED_CLIENTS.join(', ')}`);
1456
+ process.exit(1);
1457
+ }
1458
+ }
1459
+
1157
1460
  const options = {
1158
1461
  force: args.includes('--force'),
1159
1462
  skipBacklog: args.includes('--skip-backlog'),
1160
1463
  skipMcp: args.includes('--skip-mcp'),
1161
1464
  skipBeads: args.includes('--skip-beads'),
1162
1465
  verbose: args.includes('--verbose'),
1466
+ client: clientArg,
1163
1467
  };
1164
1468
 
1165
1469
  // Set global verbose flag for logDebug
1166
1470
  globalThis.VERBOSE = options.verbose;
1167
1471
 
1168
1472
  // Validate unknown flags (exclude --help which is handled as a command)
1169
- const unknownFlags = args.filter(arg => arg.startsWith('--') && !ALLOWED_FLAGS.includes(arg) && arg !== '--help');
1473
+ // Also exclude the value after --client since it's not a flag
1474
+ const clientValueIndex = clientFlagIndex !== -1 ? clientFlagIndex + 1 : -1;
1475
+ const unknownFlags = args.filter((arg, i) =>
1476
+ arg.startsWith('--') && !ALLOWED_FLAGS.includes(arg) && arg !== '--help' && i !== clientValueIndex
1477
+ );
1170
1478
  if (unknownFlags.length > 0) {
1171
1479
  logError(`Unknown flag: ${unknownFlags[0].slice(0, MAX_ARG_LENGTH)}`);
1172
1480
  console.log('Run "npx beth-copilot help" for usage information.');
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Client Configuration Persistence and Detection
3
+ *
4
+ * Persists the user's AI client selection (VS Code, Copilot CLI, Claude Code)
5
+ * to `.github/.beth-client.json` so other commands can detect which client
6
+ * was chosen during `init`.
7
+ *
8
+ * Falls back to marker file detection when no config file exists.
9
+ */
10
+ export interface ClientSelection {
11
+ vscode: boolean;
12
+ copilotCli: boolean;
13
+ claudeCode: boolean;
14
+ }
15
+ export declare const CLIENT_CONFIG_FILE = ".beth-client.json";
16
+ export declare const CLIENT_CONFIG_DIR = ".github";
17
+ /**
18
+ * Persist the client selection to `.github/.beth-client.json`.
19
+ * Creates the `.github/` directory if it doesn't exist.
20
+ * Overwrites any existing config file.
21
+ */
22
+ export declare function persistClientConfig(cwd: string, clients: ClientSelection): void;
23
+ /**
24
+ * Detect the client configuration.
25
+ *
26
+ * 1. Tries to read `.github/.beth-client.json`
27
+ * 2. Falls back to marker file detection if config is missing or invalid
28
+ * 3. Defaults to `{ vscode: true, copilotCli: false, claudeCode: false }` if nothing detected
29
+ */
30
+ export declare function detectClientConfig(cwd: string): ClientSelection;
31
+ //# sourceMappingURL=client-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/client-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AACtD,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAO/E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAiB/D"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * E2E tests for each Beth client configuration.
3
+ *
4
+ * Tests the three supported client modes:
5
+ * - VS Code GitHub Copilot Chat (--client vscode)
6
+ * - GitHub Copilot CLI (--client copilot-cli)
7
+ * - Claude Code (--client claude-code)
8
+ *
9
+ * Each configuration installs a different set of files. These tests verify
10
+ * that each mode installs exactly what it should — and nothing extra.
11
+ *
12
+ * Run with: node --test dist/cli/commands/client-config.e2e.test.js
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=client-config.e2e.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-config.e2e.test.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/client-config.e2e.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}