@braingrid/cli 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,77 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.5] - 2025-11-11
9
+
10
+ ### Fixed
11
+
12
+ - **Status line installation in setup command** (#38)
13
+ - Fixed critical bug where `braingrid setup claude-code` failed to install status line
14
+ - Changed to fetch `statusline.sh` from GitHub repository instead of local package
15
+ - Fixed configuration format to use object with `type`, `command`, and `padding` fields
16
+ - Removed unused `fileURLToPath` import from setup handlers
17
+ - Status line now correctly installs and displays BrainGrid context in Claude Code
18
+
19
+ ### Changed
20
+
21
+ - **CLAUDE.md injection source** (#38)
22
+ - Setup command now fetches `claude-code/CLAUDE.md` instead of `claude-code/README.md`
23
+ - Provides cleaner, more focused BrainGrid workflow instructions for injection
24
+ - Injected content matches the curated `.claude/CLAUDE.md` format
25
+
26
+ - **AGENTS.md injection consistency** (#38)
27
+ - Created `.cursor/AGENTS.md` with same BrainGrid workflow content as `.claude/CLAUDE.md`
28
+ - Updated `cursor/AGENTS.md` in braingrid repo to match injection pattern (44 lines)
29
+ - Both Claude Code and Cursor now follow consistent injection pattern
30
+ - Provides unified BrainGrid workflow experience across both IDEs
31
+
32
+ ## [0.2.4] - 2025-11-10
33
+
34
+ ### Added
35
+
36
+ - **BrainGrid context in Claude Code status line** (#36)
37
+ - Two-line status display showing project/requirement/task progress
38
+ - Auto-detects from `.braingrid/project.json` and git branch names
39
+ - Color-coded output: cyan for project, green for requirement, yellow for tasks
40
+ - Displays current task completion (e.g., `PROJ-130 > REQ-128 [2/5]`)
41
+ - Always shows workspace path, model name, and token usage
42
+ - Installed automatically with `braingrid setup claude-code`
43
+
44
+ - **Setup commands for Claude Code and Cursor integrations** (#35)
45
+ - New `braingrid setup claude-code` command installs slash commands and skills
46
+ - New `braingrid setup cursor` command installs Cursor rules and commands
47
+ - Fetches integration files from BrainGridAI/braingrid repository
48
+ - Injects content into CLAUDE.md or AGENTS.md with markers
49
+ - Interactive prompts for file conflicts with overwrite/skip/all/quit options
50
+ - Dry-run mode with `--dry-run` flag
51
+ - Force mode with `--force` flag to skip all prompts
52
+
53
+ - **Cursor IDE integration** (#34)
54
+ - Added AGENTS.md standard for Cursor AI agent configuration
55
+ - Supports auto-detection of project and requirement context
56
+ - Works with agent mode for autonomous task execution
57
+
58
+ - **Claude Code sync automation** (#33)
59
+ - New `pnpm docs:sync-claude` command syncs skills and slash commands
60
+ - Automatically updates BrainGridAI/braingrid repository
61
+ - Keeps Claude Code integration files in sync across repos
62
+
63
+ ### Fixed
64
+
65
+ - **Status line script path resolution** (code review fixes)
66
+ - Fixed critical bug using `import.meta.url` instead of `process.cwd()`
67
+ - Script now loads from package location, not user's directory
68
+ - Prevents "file not found" errors during setup
69
+
70
+ - **Shell script error handling**
71
+ - Added `set -o pipefail` to status line script
72
+ - Properly detects braingrid command failures in pipelines
73
+ - More robust JSON parsing with better error detection
74
+
75
+ - **Token display formatting**
76
+ - Fixed bug showing incorrect token counts (e.g., "24000k/200000k")
77
+ - Now correctly displays as "24k/200k tokens"
78
+
8
79
  ## [0.2.3] - 2025-01-10
9
80
 
10
81
  ### Added
package/README.md CHANGED
@@ -37,6 +37,80 @@ npm install -g @braingrid/cli
37
37
 
38
38
  ---
39
39
 
40
+ ## AI Coding Tool Integration
41
+
42
+ BrainGrid integrates natively with AI coding tools like **Cursor IDE** and **Claude Code** through the [AGENTS.md standard](https://agents.md/), providing seamless access to spec-driven development workflows directly in your editor.
43
+
44
+ ### What is AGENTS.md?
45
+
46
+ [AGENTS.md](https://agents.md/) is an open standard that allows AI coding assistants to automatically load project-specific guidance and commands. BrainGrid uses this standard to provide consistent CLI guidance across multiple tools:
47
+
48
+ - ✅ **Cursor IDE** - Automatic loading via AGENTS.md + slash commands
49
+ - ✅ **Claude Code** - Automatic loading via AGENTS.md or .claude/CLAUDE.md
50
+ - ✅ **GitHub Copilot** - Context from AGENTS.md
51
+ - ✅ **Growing ecosystem** of AGENTS.md-compatible tools
52
+
53
+ ### Setup for Cursor IDE
54
+
55
+ **Prerequisites:**
56
+
57
+ - Cursor IDE installed ([download here](https://cursor.sh/))
58
+ - BrainGrid CLI installed and authenticated
59
+
60
+ **How it works:**
61
+
62
+ 1. **AGENTS.md** at your repository root is automatically loaded by Cursor
63
+ 2. **.cursor/rules/braingrid.mdc** provides Cursor-specific enhancements (always-on rules)
64
+ 3. **Slash commands** available via `/` in Cursor's Agent input
65
+
66
+ **Available Commands:**
67
+
68
+ Type `/` in Cursor's Agent input to access:
69
+
70
+ - `/specify` - Create AI-refined requirements from natural language
71
+ - `/breakdown` - Break requirements into implementation tasks
72
+ - `/save-requirement` - Save requirement content to BrainGrid
73
+ - `/build` - Generate complete implementation plan
74
+
75
+ **Quick Start Example:**
76
+
77
+ ```bash
78
+ # In Cursor IDE Agent input
79
+ /specify "Add user authentication with OAuth2"
80
+ # → Creates REQ-123
81
+
82
+ /breakdown REQ-123
83
+ # → Generates 5-10 implementation tasks
84
+
85
+ /build REQ-123
86
+ # → Shows complete requirement with all task prompts
87
+ ```
88
+
89
+ ### Setup for Claude Code
90
+
91
+ BrainGrid's AGENTS.md file also works with Claude Code. You can either:
92
+
93
+ 1. **Use AGENTS.md directly** (Claude Code automatically reads it)
94
+ 2. **Symlink for compatibility**: `ln -s AGENTS.md .claude/CLAUDE.md`
95
+
96
+ Claude Code also provides its own slash commands in `.claude/commands/`.
97
+
98
+ ### Cross-IDE Compatibility
99
+
100
+ The AGENTS.md standard ensures your BrainGrid setup works across tools:
101
+
102
+ - **AGENTS.md** provides CLI guidance for all compatible tools
103
+ - **Tool-specific directories** (.cursor/, .claude/) add enhanced features
104
+ - **Single source of truth** for BrainGrid workflows
105
+
106
+ **Learn more:**
107
+
108
+ - [AGENTS.md standard](https://agents.md/)
109
+ - [BrainGrid documentation](https://www.braingrid.ai)
110
+ - Full CLI command reference in [AGENTS.md](./AGENTS.md)
111
+
112
+ ---
113
+
40
114
  ## QuickStart: One-Minute Flow
41
115
 
42
116
  ```bash
@@ -87,6 +161,69 @@ The `init` command creates a `.braingrid/project.json` file in the `.braingrid/`
87
161
 
88
162
  > **Note:** The init command always asks for confirmation before initializing unless you use the `--force` flag.
89
163
 
164
+ ### Setup Commands
165
+
166
+ Install BrainGrid integration files for AI coding tools. These commands fetch integration files from the [BrainGrid repository](https://github.com/BrainGridAI/braingrid) and install them in your project.
167
+
168
+ **Prerequisites:**
169
+
170
+ - **GitHub CLI** must be installed and authenticated
171
+ - macOS: `brew install gh`
172
+ - Windows: `winget install GitHub.CLI`
173
+ - Linux: See [GitHub CLI installation](https://cli.github.com/manual/installation)
174
+ - After installing: `gh auth login`
175
+
176
+ **Commands:**
177
+
178
+ ```bash
179
+ # Install Claude Code integration
180
+ braingrid setup claude-code
181
+
182
+ # Install Cursor integration
183
+ braingrid setup cursor
184
+
185
+ # Skip prompts and overwrite all existing files
186
+ braingrid setup claude-code --force
187
+
188
+ # Preview changes without making modifications
189
+ braingrid setup cursor --dry-run
190
+ ```
191
+
192
+ **What gets installed:**
193
+
194
+ - **Claude Code integration:**
195
+ - Commands in `.claude/commands/` (specify, breakdown, build, save-requirement)
196
+ - Skills in `.claude/skills/braingrid-cli/`
197
+ - Content injected into `CLAUDE.md` (or creates it if it doesn't exist)
198
+
199
+ - **Cursor integration:**
200
+ - Commands in `.cursor/commands/` (specify, breakdown, build, save-requirement)
201
+ - Rules in `.cursor/rules/`
202
+ - Content injected into `AGENTS.md` (or creates it if it doesn't exist)
203
+
204
+ **Options:**
205
+
206
+ - `--force` - Skip prompts and overwrite all existing files without asking
207
+ - `--dry-run` - Show what would be done without making any changes
208
+
209
+ **File Conflicts:**
210
+
211
+ When files already exist, you'll be prompted to choose:
212
+
213
+ - `[O]verwrite` - Replace this file
214
+ - `[S]kip` - Keep existing file
215
+ - `[A]ll` - Overwrite all remaining files
216
+ - `[Q]uit` - Cancel installation
217
+
218
+ **Marker-based Updates:**
219
+
220
+ Running setup again will update the BrainGrid sections in `CLAUDE.md` or `AGENTS.md` without duplicating content. The CLI uses HTML comment markers to identify and replace existing BrainGrid content.
221
+
222
+ **Learn more:**
223
+
224
+ - [Claude Code integration guide](https://braingrid.ai/docs/integrations/claude-code)
225
+ - [Cursor integration guide](https://braingrid.ai/docs/integrations/cursor)
226
+
90
227
  ### Project Commands
91
228
 
92
229
  ```bash
package/dist/cli.js CHANGED
@@ -422,7 +422,7 @@ import axios3, { AxiosError as AxiosError2 } from "axios";
422
422
 
423
423
  // src/build-config.ts
424
424
  var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
425
- var CLI_VERSION = true ? "0.2.3" : "0.0.0-test";
425
+ var CLI_VERSION = true ? "0.2.5" : "0.0.0-test";
426
426
  var PRODUCTION_CONFIG = {
427
427
  apiUrl: "https://app.braingrid.ai",
428
428
  workosAuthUrl: "https://sensitive-harvest-60.authkit.app",
@@ -6113,6 +6113,431 @@ async function handleUpdate(opts) {
6113
6113
  }
6114
6114
  }
6115
6115
 
6116
+ // src/handlers/setup.handlers.ts
6117
+ import chalk14 from "chalk";
6118
+ import { select as select3 } from "@inquirer/prompts";
6119
+ import * as path5 from "path";
6120
+ import * as fs4 from "fs/promises";
6121
+
6122
+ // src/utils/command-execution.ts
6123
+ import { exec as exec4, spawn } from "child_process";
6124
+ import { promisify as promisify4 } from "util";
6125
+ var execAsyncReal = promisify4(exec4);
6126
+ async function execAsync4(command, options, isTestMode = false, mockExecHandler) {
6127
+ if (isTestMode && mockExecHandler) {
6128
+ return mockExecHandler(command);
6129
+ }
6130
+ const defaultOptions = {
6131
+ maxBuffer: 1024 * 1024 * 10,
6132
+ // 10MB default
6133
+ timeout: 3e5,
6134
+ // 5 minutes
6135
+ ...options
6136
+ };
6137
+ if (command.includes("claude")) {
6138
+ defaultOptions.maxBuffer = 1024 * 1024 * 50;
6139
+ defaultOptions.timeout = 6e5;
6140
+ }
6141
+ return execAsyncReal(command, defaultOptions);
6142
+ }
6143
+
6144
+ // src/services/setup-service.ts
6145
+ import * as fs3 from "fs/promises";
6146
+ import * as path4 from "path";
6147
+ var GITHUB_OWNER = "BrainGridAI";
6148
+ var GITHUB_REPO = "braingrid";
6149
+ var MAX_RETRIES = 3;
6150
+ var INITIAL_RETRY_DELAY = 100;
6151
+ var BEGIN_MARKER = "<!-- BEGIN BRAINGRID INTEGRATION -->";
6152
+ var END_MARKER = "<!-- END BRAINGRID INTEGRATION -->";
6153
+ async function withRetry(fn, retries = MAX_RETRIES, delay = INITIAL_RETRY_DELAY) {
6154
+ try {
6155
+ return await fn();
6156
+ } catch (error) {
6157
+ if (retries === 0) {
6158
+ throw error;
6159
+ }
6160
+ const errorMessage = error instanceof Error ? error.message : String(error);
6161
+ const isNetworkError = errorMessage.includes("ECONNRESET") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("ENOTFOUND") || errorMessage.includes("network");
6162
+ if (!isNetworkError) {
6163
+ throw error;
6164
+ }
6165
+ await new Promise((resolve) => setTimeout(resolve, delay));
6166
+ return withRetry(fn, retries - 1, delay * 2);
6167
+ }
6168
+ }
6169
+ function parseGitHubError(error) {
6170
+ const errorMessage = error instanceof Error ? error.message : String(error);
6171
+ if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
6172
+ return "File or directory not found in BrainGrid repository";
6173
+ }
6174
+ if (errorMessage.includes("403") || errorMessage.includes("rate limit")) {
6175
+ return "GitHub API rate limit exceeded. Please wait a few minutes and try again.\nCheck rate limit status: gh api rate_limit";
6176
+ }
6177
+ if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
6178
+ return "GitHub CLI is not authenticated. Run: gh auth login";
6179
+ }
6180
+ try {
6181
+ const match = errorMessage.match(/\{.*\}/s);
6182
+ if (match) {
6183
+ const errorData = JSON.parse(match[0]);
6184
+ return errorData.message || errorMessage;
6185
+ }
6186
+ } catch {
6187
+ }
6188
+ return errorMessage;
6189
+ }
6190
+ async function fetchFileFromGitHub(path6) {
6191
+ return withRetry(async () => {
6192
+ try {
6193
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
6194
+ const { stdout } = await execAsync4(command);
6195
+ const response = JSON.parse(stdout);
6196
+ if (response.type !== "file") {
6197
+ throw new Error(`Path ${path6} is not a file`);
6198
+ }
6199
+ if (!response.content || !response.encoding) {
6200
+ throw new Error(`No content found for file ${path6}`);
6201
+ }
6202
+ if (response.encoding !== "base64") {
6203
+ throw new Error(`Unexpected encoding: ${response.encoding}`);
6204
+ }
6205
+ const content = Buffer.from(response.content, "base64").toString("utf8");
6206
+ return content;
6207
+ } catch (error) {
6208
+ const parsedError = parseGitHubError(error);
6209
+ throw new Error(`Failed to fetch file ${path6}: ${parsedError}`);
6210
+ }
6211
+ });
6212
+ }
6213
+ async function listGitHubDirectory(path6) {
6214
+ return withRetry(async () => {
6215
+ try {
6216
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
6217
+ const { stdout } = await execAsync4(command);
6218
+ const response = JSON.parse(stdout);
6219
+ if (!Array.isArray(response)) {
6220
+ throw new Error(`Path ${path6} is not a directory`);
6221
+ }
6222
+ return response.map((item) => ({
6223
+ name: item.name,
6224
+ type: item.type,
6225
+ path: item.path
6226
+ }));
6227
+ } catch (error) {
6228
+ const parsedError = parseGitHubError(error);
6229
+ throw new Error(`Failed to list directory ${path6}: ${parsedError}`);
6230
+ }
6231
+ });
6232
+ }
6233
+ async function copyFileFromGitHub(sourcePath, targetPath) {
6234
+ try {
6235
+ const content = await fetchFileFromGitHub(sourcePath);
6236
+ const parentDir = path4.dirname(targetPath);
6237
+ await fs3.mkdir(parentDir, { recursive: true });
6238
+ await fs3.writeFile(targetPath, content, { encoding: "utf8", mode: 420 });
6239
+ } catch (error) {
6240
+ const errorMessage = error instanceof Error ? error.message : String(error);
6241
+ throw new Error(`Failed to copy file ${sourcePath} to ${targetPath}: ${errorMessage}`);
6242
+ }
6243
+ }
6244
+ async function injectContentIntoFile(targetPath, content) {
6245
+ try {
6246
+ let fileContent;
6247
+ let fileExists2 = false;
6248
+ try {
6249
+ fileContent = await fs3.readFile(targetPath, "utf8");
6250
+ fileExists2 = true;
6251
+ } catch {
6252
+ fileContent = "";
6253
+ }
6254
+ if (fileExists2) {
6255
+ const beginIndex = fileContent.indexOf(BEGIN_MARKER);
6256
+ const endIndex = fileContent.indexOf(END_MARKER);
6257
+ if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
6258
+ const before = fileContent.substring(0, beginIndex);
6259
+ const after = fileContent.substring(endIndex + END_MARKER.length);
6260
+ const newContent = `${before}${BEGIN_MARKER}
6261
+ ${content}
6262
+ ${END_MARKER}${after}`;
6263
+ await fs3.writeFile(targetPath, newContent, { encoding: "utf8" });
6264
+ } else {
6265
+ const newContent = `${fileContent}
6266
+
6267
+ ${BEGIN_MARKER}
6268
+ ${content}
6269
+ ${END_MARKER}
6270
+ `;
6271
+ await fs3.writeFile(targetPath, newContent, { encoding: "utf8" });
6272
+ }
6273
+ } else {
6274
+ const parentDir = path4.dirname(targetPath);
6275
+ await fs3.mkdir(parentDir, { recursive: true });
6276
+ const newContent = `${BEGIN_MARKER}
6277
+ ${content}
6278
+ ${END_MARKER}
6279
+ `;
6280
+ await fs3.writeFile(targetPath, newContent, { encoding: "utf8", mode: 420 });
6281
+ }
6282
+ } catch (error) {
6283
+ const errorMessage = error instanceof Error ? error.message : String(error);
6284
+ throw new Error(`Failed to inject content into ${targetPath}: ${errorMessage}`);
6285
+ }
6286
+ }
6287
+ async function installStatusLineScript(scriptContent, targetPath = ".claude/statusline.sh") {
6288
+ try {
6289
+ const parentDir = path4.dirname(targetPath);
6290
+ await fs3.mkdir(parentDir, { recursive: true });
6291
+ await fs3.writeFile(targetPath, scriptContent, { encoding: "utf8", mode: 493 });
6292
+ } catch (error) {
6293
+ const errorMessage = error instanceof Error ? error.message : String(error);
6294
+ throw new Error(`Failed to install status line script to ${targetPath}: ${errorMessage}`);
6295
+ }
6296
+ }
6297
+ async function updateClaudeSettings(settingsPath = ".claude/settings.json", scriptPath = ".claude/statusline.sh") {
6298
+ try {
6299
+ let settings = {};
6300
+ try {
6301
+ const content2 = await fs3.readFile(settingsPath, "utf8");
6302
+ settings = JSON.parse(content2);
6303
+ } catch {
6304
+ }
6305
+ settings.statusLine = {
6306
+ type: "command",
6307
+ command: scriptPath,
6308
+ padding: 0
6309
+ };
6310
+ const parentDir = path4.dirname(settingsPath);
6311
+ await fs3.mkdir(parentDir, { recursive: true });
6312
+ const content = JSON.stringify(settings, null, 2);
6313
+ await fs3.writeFile(settingsPath, content, { encoding: "utf8" });
6314
+ } catch (error) {
6315
+ const errorMessage = error instanceof Error ? error.message : String(error);
6316
+ throw new Error(`Failed to update Claude settings at ${settingsPath}: ${errorMessage}`);
6317
+ }
6318
+ }
6319
+
6320
+ // src/handlers/setup.handlers.ts
6321
+ async function fileExists(filePath) {
6322
+ try {
6323
+ await fs4.access(filePath);
6324
+ return true;
6325
+ } catch {
6326
+ return false;
6327
+ }
6328
+ }
6329
+ async function checkPrerequisites() {
6330
+ try {
6331
+ await execAsync4("gh --version");
6332
+ } catch {
6333
+ return {
6334
+ success: false,
6335
+ message: chalk14.red("\u274C GitHub CLI is not installed.\n\n") + chalk14.dim("Install instructions:\n") + chalk14.dim(" macOS: ") + chalk14.cyan("brew install gh") + chalk14.dim("\n") + chalk14.dim(" Windows: ") + chalk14.cyan("winget install GitHub.CLI") + chalk14.dim("\n") + chalk14.dim(" Linux: See ") + chalk14.cyan("https://cli.github.com/manual/installation") + chalk14.dim("\n\n") + chalk14.dim("After installing, run: ") + chalk14.cyan("gh auth login")
6336
+ };
6337
+ }
6338
+ try {
6339
+ await execAsync4("gh auth status");
6340
+ } catch {
6341
+ return {
6342
+ success: false,
6343
+ message: chalk14.red("\u274C Not authenticated with GitHub CLI.\n\n") + chalk14.dim("Please run: ") + chalk14.cyan("gh auth login")
6344
+ };
6345
+ }
6346
+ return null;
6347
+ }
6348
+ async function getFileList(sourcePaths, targetPaths) {
6349
+ const operations = [];
6350
+ for (let i = 0; i < sourcePaths.length; i++) {
6351
+ const sourcePath = sourcePaths[i];
6352
+ const targetPath = targetPaths[i];
6353
+ try {
6354
+ const items = await listGitHubDirectory(sourcePath);
6355
+ for (const item of items) {
6356
+ if (item.type === "file") {
6357
+ const itemTargetPath = path5.join(targetPath, item.name);
6358
+ const exists = await fileExists(itemTargetPath);
6359
+ operations.push({
6360
+ type: "copy",
6361
+ sourcePath: item.path,
6362
+ targetPath: itemTargetPath,
6363
+ exists
6364
+ });
6365
+ }
6366
+ }
6367
+ } catch (error) {
6368
+ console.warn(
6369
+ chalk14.yellow(`\u26A0\uFE0F Could not list directory: ${sourcePath}`),
6370
+ error instanceof Error ? error.message : String(error)
6371
+ );
6372
+ }
6373
+ }
6374
+ return operations;
6375
+ }
6376
+ function displayInstallationPlan(operations, injectionFile) {
6377
+ console.log(chalk14.bold("\n\u{1F4CB} Installation Plan:\n"));
6378
+ console.log(chalk14.cyan(" Content Injection:"));
6379
+ console.log(chalk14.dim(` ${injectionFile}`));
6380
+ const newFiles = operations.filter((op) => !op.exists);
6381
+ const existingFiles = operations.filter((op) => op.exists);
6382
+ if (newFiles.length > 0) {
6383
+ console.log(chalk14.cyan("\n New Files:"));
6384
+ for (const op of newFiles) {
6385
+ console.log(chalk14.dim(` ${op.targetPath}`));
6386
+ }
6387
+ }
6388
+ if (existingFiles.length > 0) {
6389
+ console.log(chalk14.yellow("\n Existing Files (will prompt):"));
6390
+ for (const op of existingFiles) {
6391
+ console.log(chalk14.dim(` ${op.targetPath}`));
6392
+ }
6393
+ }
6394
+ console.log("");
6395
+ }
6396
+ async function promptForConflict(filePath) {
6397
+ const answer = await select3({
6398
+ message: chalk14.yellow(`File exists: ${filePath}`),
6399
+ choices: [
6400
+ { name: "[O]verwrite - Replace this file", value: "overwrite" },
6401
+ { name: "[S]kip - Keep existing file", value: "skip" },
6402
+ { name: "[A]ll - Overwrite all remaining", value: "all" },
6403
+ { name: "[Q]uit - Cancel installation", value: "quit" }
6404
+ ]
6405
+ });
6406
+ return answer;
6407
+ }
6408
+ async function installFiles(operations, force) {
6409
+ let installed = 0;
6410
+ let skipped = 0;
6411
+ let overwriteAll = force;
6412
+ for (const operation of operations) {
6413
+ if (operation.exists && !overwriteAll) {
6414
+ const response = await promptForConflict(operation.targetPath);
6415
+ if (response === "quit") {
6416
+ return { installed, skipped, cancelled: true };
6417
+ } else if (response === "skip") {
6418
+ skipped++;
6419
+ continue;
6420
+ } else if (response === "all") {
6421
+ overwriteAll = true;
6422
+ }
6423
+ }
6424
+ try {
6425
+ await copyFileFromGitHub(operation.sourcePath, operation.targetPath);
6426
+ installed++;
6427
+ } catch (error) {
6428
+ console.error(
6429
+ chalk14.red(`Failed to copy ${operation.targetPath}:`),
6430
+ error instanceof Error ? error.message : String(error)
6431
+ );
6432
+ skipped++;
6433
+ }
6434
+ }
6435
+ return { installed, skipped, cancelled: false };
6436
+ }
6437
+ async function _handleSetup(config, opts) {
6438
+ try {
6439
+ const prerequisiteError = await checkPrerequisites();
6440
+ if (prerequisiteError) {
6441
+ return prerequisiteError;
6442
+ }
6443
+ console.log(chalk14.bold(`\u{1F680} Setting up ${config.name} integration...
6444
+ `));
6445
+ const operations = await getFileList(config.sourceDirs, config.targetDirs);
6446
+ const injectionFileExists = await fileExists(config.injection.targetFile);
6447
+ operations.push({
6448
+ type: "inject",
6449
+ sourcePath: config.injection.sourceFile,
6450
+ targetPath: config.injection.targetFile,
6451
+ exists: injectionFileExists
6452
+ });
6453
+ displayInstallationPlan(
6454
+ operations.filter((op) => op.type === "copy"),
6455
+ config.injection.targetFile
6456
+ );
6457
+ if (opts.dryRun) {
6458
+ return {
6459
+ success: true,
6460
+ message: chalk14.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk14.dim(`Would install ${operations.length} files.`)
6461
+ };
6462
+ }
6463
+ const copyOps = operations.filter((op) => op.type === "copy");
6464
+ const result = await installFiles(copyOps, opts.force || false);
6465
+ if (result.cancelled) {
6466
+ return {
6467
+ success: false,
6468
+ message: chalk14.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk14.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
6469
+ code: "CANCELLED"
6470
+ };
6471
+ }
6472
+ try {
6473
+ const content = await fetchFileFromGitHub(config.injection.sourceFile);
6474
+ await injectContentIntoFile(config.injection.targetFile, content);
6475
+ } catch (error) {
6476
+ console.error(
6477
+ chalk14.red(`Failed to inject content into ${config.injection.targetFile}:`),
6478
+ error instanceof Error ? error.message : String(error)
6479
+ );
6480
+ }
6481
+ let statusLineInstalled = false;
6482
+ if (config.name === "Claude Code") {
6483
+ try {
6484
+ const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
6485
+ await installStatusLineScript(scriptContent);
6486
+ await updateClaudeSettings();
6487
+ statusLineInstalled = true;
6488
+ } catch (error) {
6489
+ console.error(
6490
+ chalk14.yellow("\u26A0\uFE0F Failed to install status line script:"),
6491
+ error instanceof Error ? error.message : String(error)
6492
+ );
6493
+ }
6494
+ }
6495
+ const statusLineMessage = statusLineInstalled ? chalk14.dim(" Status line: .claude/statusline.sh\n") : "";
6496
+ return {
6497
+ success: true,
6498
+ message: chalk14.green(`\u2705 ${config.name} integration installed successfully!
6499
+
6500
+ `) + chalk14.dim("Files installed:\n") + chalk14.dim(` Commands: ${result.installed} files
6501
+ `) + statusLineMessage + chalk14.dim(` Content injected into: ${config.injection.targetFile}
6502
+
6503
+ `) + chalk14.dim("Next steps:\n") + chalk14.dim(" 1. Review the integration files\n") + chalk14.dim(` 2. Open ${config.name}
6504
+ `) + chalk14.dim(" 3. Try the /specify or /breakdown commands\n") + chalk14.dim(" 4. Learn more: ") + chalk14.cyan(config.docsUrl)
6505
+ };
6506
+ } catch (error) {
6507
+ const errorMessage = error instanceof Error ? error.message : String(error);
6508
+ return {
6509
+ success: false,
6510
+ message: chalk14.red(`\u274C Setup failed: ${errorMessage}`)
6511
+ };
6512
+ }
6513
+ }
6514
+ async function handleSetupClaudeCode(opts) {
6515
+ const config = {
6516
+ name: "Claude Code",
6517
+ sourceDirs: ["claude-code/commands", "claude-code/skills"],
6518
+ targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
6519
+ injection: {
6520
+ sourceFile: "claude-code/CLAUDE.md",
6521
+ targetFile: "CLAUDE.md"
6522
+ },
6523
+ docsUrl: "https://braingrid.ai/docs/claude-code"
6524
+ };
6525
+ return _handleSetup(config, opts);
6526
+ }
6527
+ async function handleSetupCursor(opts) {
6528
+ const config = {
6529
+ name: "Cursor",
6530
+ sourceDirs: ["cursor/commands", "cursor/rules"],
6531
+ targetDirs: [".cursor/commands", ".cursor/rules"],
6532
+ injection: {
6533
+ sourceFile: "cursor/AGENTS.md",
6534
+ targetFile: "AGENTS.md"
6535
+ },
6536
+ docsUrl: "https://braingrid.ai/docs/cursor"
6537
+ };
6538
+ return _handleSetup(config, opts);
6539
+ }
6540
+
6116
6541
  // src/cli.ts
6117
6542
  var require2 = createRequire(import.meta.url);
6118
6543
  var packageJson = require2("../package.json");
@@ -6170,6 +6595,18 @@ program.command("specify").description("Create AI-refined requirement from promp
6170
6595
  process.exit(1);
6171
6596
  }
6172
6597
  });
6598
+ var createSetupAction = (handler) => {
6599
+ return async (opts) => {
6600
+ const result = await handler(opts);
6601
+ console.log(result.message);
6602
+ if (!result.success) {
6603
+ process.exit(1);
6604
+ }
6605
+ };
6606
+ };
6607
+ var setup = program.command("setup").description("Setup BrainGrid integrations for AI coding tools");
6608
+ setup.command("claude-code").description("Install Claude Code integration").option("--force", "overwrite existing files without prompting").option("--dry-run", "show what would be done without making changes").action(createSetupAction(handleSetupClaudeCode));
6609
+ setup.command("cursor").description("Install Cursor integration").option("--force", "overwrite existing files without prompting").option("--dry-run", "show what would be done without making changes").action(createSetupAction(handleSetupCursor));
6173
6610
  var project = program.command("project").description("Manage projects");
6174
6611
  project.command("list").description("List all projects").option("--format <format>", "output format (table, json, xml, markdown)", "table").option("--page <page>", "page number for pagination", "1").option("--limit <limit>", "number of projects per page", "20").action(async (opts) => {
6175
6612
  const result = await handleProjectList(opts);