@braingrid/cli 0.2.35 → 0.2.37

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
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.37] - 2026-02-14
11
+
12
+ ### Added
13
+
14
+ - **Add `.braingrid/temp/` to `.gitignore` during update**
15
+ - `braingrid update` now ensures `.braingrid/temp/` is in `.gitignore` (previously only `init` did this)
16
+ - Runs in both the "already up-to-date" and "after successful update" code paths
17
+
18
+ ### Changed
19
+
20
+ - **Per-directory install counts in setup success message**
21
+ - Setup success message now shows "Commands: 4" and "Skills: 1" instead of "Commands: 6 files"
22
+ - Each skill directory counts as 1 skill regardless of supporting files inside it
23
+ - Cursor setup shows separate "Commands" and "Rules" counts
24
+
25
+ ### Refactored
26
+
27
+ - **Extract `addBraingridTempToGitignore` to shared utility**
28
+ - Moved from `init.handlers.ts` to `src/utils/gitignore.ts` for reuse across handlers
29
+
30
+ ## [0.2.36] - 2026-02-14
31
+
32
+ ### Added
33
+
34
+ - **Add `.braingrid/temp/` to `.gitignore` during init**
35
+ - `braingrid init` now automatically appends `.braingrid/temp/` to the project's `.gitignore`
36
+ - Prevents session-specific temp files (e.g., acceptance criteria checklists) from being committed
37
+ - Skips if the entry already exists; creates `.gitignore` if none exists
38
+
39
+ ### Fixed
40
+
41
+ - **Always offer Claude Code setup updates during init**
42
+ - `braingrid init` now prompts to update Claude Code integration even when it's already installed
43
+ - Ensures users get the latest slash commands, skills, and status line configuration
44
+
10
45
  ## [0.2.35] - 2026-02-14
11
46
 
12
47
  ### Added
package/dist/cli.js CHANGED
@@ -222,7 +222,7 @@ async function axiosWithRetry(config2, options) {
222
222
 
223
223
  // src/build-config.ts
224
224
  var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
225
- var CLI_VERSION = true ? "0.2.35" : "0.0.0-test";
225
+ var CLI_VERSION = true ? "0.2.37" : "0.0.0-test";
226
226
  var PRODUCTION_CONFIG = {
227
227
  apiUrl: "https://app.braingrid.ai",
228
228
  workosAuthUrl: "https://auth.braingrid.ai",
@@ -2543,17 +2543,17 @@ function parseGitHubError(error) {
2543
2543
  }
2544
2544
  return errorMessage;
2545
2545
  }
2546
- async function fetchFileFromGitHub(path6) {
2546
+ async function fetchFileFromGitHub(path7) {
2547
2547
  return withRetry(async () => {
2548
2548
  try {
2549
- const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
2549
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path7}`;
2550
2550
  const { stdout } = await execAsync(command);
2551
2551
  const response = JSON.parse(stdout);
2552
2552
  if (response.type !== "file") {
2553
- throw new Error(`Path ${path6} is not a file`);
2553
+ throw new Error(`Path ${path7} is not a file`);
2554
2554
  }
2555
2555
  if (!response.content || !response.encoding) {
2556
- throw new Error(`No content found for file ${path6}`);
2556
+ throw new Error(`No content found for file ${path7}`);
2557
2557
  }
2558
2558
  if (response.encoding !== "base64") {
2559
2559
  throw new Error(`Unexpected encoding: ${response.encoding}`);
@@ -2562,18 +2562,18 @@ async function fetchFileFromGitHub(path6) {
2562
2562
  return content;
2563
2563
  } catch (error) {
2564
2564
  const parsedError = parseGitHubError(error);
2565
- throw new Error(`Failed to fetch file ${path6}: ${parsedError}`);
2565
+ throw new Error(`Failed to fetch file ${path7}: ${parsedError}`);
2566
2566
  }
2567
2567
  });
2568
2568
  }
2569
- async function listGitHubDirectory(path6) {
2569
+ async function listGitHubDirectory(path7) {
2570
2570
  return withRetry(async () => {
2571
2571
  try {
2572
- const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
2572
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path7}`;
2573
2573
  const { stdout } = await execAsync(command);
2574
2574
  const response = JSON.parse(stdout);
2575
2575
  if (!Array.isArray(response)) {
2576
- throw new Error(`Path ${path6} is not a directory`);
2576
+ throw new Error(`Path ${path7} is not a directory`);
2577
2577
  }
2578
2578
  return response.map((item) => ({
2579
2579
  name: item.name,
@@ -2582,7 +2582,7 @@ async function listGitHubDirectory(path6) {
2582
2582
  }));
2583
2583
  } catch (error) {
2584
2584
  const parsedError = parseGitHubError(error);
2585
- throw new Error(`Failed to list directory ${path6}: ${parsedError}`);
2585
+ throw new Error(`Failed to list directory ${path7}: ${parsedError}`);
2586
2586
  }
2587
2587
  });
2588
2588
  }
@@ -3032,9 +3032,33 @@ async function canUseGhAutomation() {
3032
3032
  return isGhAuthenticated();
3033
3033
  }
3034
3034
 
3035
+ // src/utils/gitignore.ts
3036
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3037
+ import path4 from "path";
3038
+ async function addBraingridTempToGitignore() {
3039
+ const gitRoot = await getGitRoot();
3040
+ if (!gitRoot) return;
3041
+ const gitignorePath = path4.join(gitRoot, ".gitignore");
3042
+ const entry = ".braingrid/temp/";
3043
+ let content = "";
3044
+ try {
3045
+ content = await readFile2(gitignorePath, "utf8");
3046
+ } catch {
3047
+ }
3048
+ const lines = content.split("\n");
3049
+ if (lines.some((line) => line.trim() === entry || line.trim() === ".braingrid/temp")) {
3050
+ return;
3051
+ }
3052
+ const addition = content.endsWith("\n") || content === "" ? "" : "\n";
3053
+ const newContent = `${content}${addition}# BrainGrid temp files
3054
+ ${entry}
3055
+ `;
3056
+ await writeFile2(gitignorePath, newContent, "utf8");
3057
+ }
3058
+
3035
3059
  // src/utils/local-store.ts
3036
3060
  import fs3 from "fs";
3037
- import path4 from "path";
3061
+ import path5 from "path";
3038
3062
 
3039
3063
  // src/types/local-project.ts
3040
3064
  import { z } from "zod";
@@ -3064,11 +3088,11 @@ async function getDefaultCwd() {
3064
3088
  }
3065
3089
  async function getBraingridDir(cwd) {
3066
3090
  const dir = cwd ?? await getDefaultCwd();
3067
- return path4.join(dir, BRAINGRID_DIR);
3091
+ return path5.join(dir, BRAINGRID_DIR);
3068
3092
  }
3069
3093
  async function getProjectConfigPath(cwd) {
3070
3094
  const braingridDir = await getBraingridDir(cwd);
3071
- return path4.join(braingridDir, PROJECT_CONFIG_FILE);
3095
+ return path5.join(braingridDir, PROJECT_CONFIG_FILE);
3072
3096
  }
3073
3097
  async function projectConfigExists(cwd) {
3074
3098
  const configPath = await getProjectConfigPath(cwd);
@@ -3285,7 +3309,7 @@ async function checkAndShowUpdateWarning() {
3285
3309
 
3286
3310
  // src/handlers/setup.handlers.ts
3287
3311
  import * as fs4 from "fs/promises";
3288
- import * as path5 from "path";
3312
+ import * as path6 from "path";
3289
3313
  import { select } from "@inquirer/prompts";
3290
3314
  import chalk8 from "chalk";
3291
3315
  async function fileExists(filePath) {
@@ -3317,22 +3341,23 @@ async function checkPrerequisites() {
3317
3341
  }
3318
3342
  async function getFileList(sourcePaths, targetPaths) {
3319
3343
  const operations = [];
3320
- async function processDirectory(sourceDir, targetDir) {
3344
+ async function processDirectory(sourceDir, targetDir, dirIndex) {
3321
3345
  try {
3322
3346
  const items = await listGitHubDirectory(sourceDir);
3323
3347
  for (const item of items) {
3324
3348
  if (item.type === "file") {
3325
- const itemTargetPath = path5.join(targetDir, item.name);
3349
+ const itemTargetPath = path6.join(targetDir, item.name);
3326
3350
  const exists = await fileExists(itemTargetPath);
3327
3351
  operations.push({
3328
3352
  type: "copy",
3329
3353
  sourcePath: item.path,
3330
3354
  targetPath: itemTargetPath,
3331
- exists
3355
+ exists,
3356
+ dirIndex
3332
3357
  });
3333
3358
  } else if (item.type === "dir") {
3334
- const itemTargetPath = path5.join(targetDir, item.name);
3335
- await processDirectory(item.path, itemTargetPath);
3359
+ const itemTargetPath = path6.join(targetDir, item.name);
3360
+ await processDirectory(item.path, itemTargetPath, dirIndex);
3336
3361
  }
3337
3362
  }
3338
3363
  } catch (error) {
@@ -3343,7 +3368,7 @@ async function getFileList(sourcePaths, targetPaths) {
3343
3368
  }
3344
3369
  }
3345
3370
  for (let i = 0; i < sourcePaths.length; i++) {
3346
- await processDirectory(sourcePaths[i], targetPaths[i]);
3371
+ await processDirectory(sourcePaths[i], targetPaths[i], i);
3347
3372
  }
3348
3373
  return operations;
3349
3374
  }
@@ -3379,15 +3404,15 @@ async function promptForConflict(filePath) {
3379
3404
  });
3380
3405
  return answer;
3381
3406
  }
3382
- async function installFiles(operations, force) {
3383
- let installed = 0;
3407
+ async function installFiles(operations, force, dirCount) {
3408
+ const installedPerDir = new Array(dirCount).fill(0);
3384
3409
  let skipped = 0;
3385
3410
  let overwriteAll = force;
3386
3411
  for (const operation of operations) {
3387
3412
  if (operation.exists && !overwriteAll) {
3388
3413
  const response = await promptForConflict(operation.targetPath);
3389
3414
  if (response === "quit") {
3390
- return { installed, skipped, cancelled: true };
3415
+ return { installedPerDir, skipped, cancelled: true };
3391
3416
  } else if (response === "skip") {
3392
3417
  skipped++;
3393
3418
  continue;
@@ -3397,7 +3422,9 @@ async function installFiles(operations, force) {
3397
3422
  }
3398
3423
  try {
3399
3424
  await copyFileFromGitHub(operation.sourcePath, operation.targetPath);
3400
- installed++;
3425
+ if (operation.dirIndex !== void 0) {
3426
+ installedPerDir[operation.dirIndex]++;
3427
+ }
3401
3428
  } catch (error) {
3402
3429
  console.error(
3403
3430
  chalk8.red(`Failed to copy ${operation.targetPath}:`),
@@ -3406,104 +3433,150 @@ async function installFiles(operations, force) {
3406
3433
  skipped++;
3407
3434
  }
3408
3435
  }
3409
- return { installed, skipped, cancelled: false };
3436
+ return { installedPerDir, skipped, cancelled: false };
3410
3437
  }
3411
3438
  async function _handleSetup(config2, opts) {
3412
- try {
3413
- const prerequisiteError = await checkPrerequisites();
3414
- if (prerequisiteError) {
3415
- return prerequisiteError;
3416
- }
3417
- console.log(chalk8.bold(`\u{1F680} Setting up ${config2.name} integration...
3439
+ const prerequisiteError = await checkPrerequisites();
3440
+ if (prerequisiteError) {
3441
+ return prerequisiteError;
3442
+ }
3443
+ console.log(chalk8.bold(`\u{1F680} Setting up ${config2.name} integration...
3418
3444
  `));
3419
- const operations = await getFileList(config2.sourceDirs, config2.targetDirs);
3420
- const injectionFileExists = await fileExists(config2.injection.targetFile);
3421
- operations.push({
3422
- type: "inject",
3423
- sourcePath: config2.injection.sourceFile,
3424
- targetPath: config2.injection.targetFile,
3425
- exists: injectionFileExists
3426
- });
3427
- displayInstallationPlan(
3428
- operations.filter((op) => op.type === "copy"),
3429
- config2.injection.targetFile
3445
+ const operations = await getFileList(config2.sourceDirs, config2.targetDirs);
3446
+ const injectionFileExists = await fileExists(config2.injection.targetFile);
3447
+ operations.push({
3448
+ type: "inject",
3449
+ sourcePath: config2.injection.sourceFile,
3450
+ targetPath: config2.injection.targetFile,
3451
+ exists: injectionFileExists
3452
+ });
3453
+ displayInstallationPlan(
3454
+ operations.filter((op) => op.type === "copy"),
3455
+ config2.injection.targetFile
3456
+ );
3457
+ if (opts.dryRun) {
3458
+ return {
3459
+ success: true,
3460
+ message: chalk8.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk8.dim(`Would install ${operations.length} files.`)
3461
+ };
3462
+ }
3463
+ const copyOps = operations.filter((op) => op.type === "copy");
3464
+ const result = await installFiles(copyOps, opts.force || false, config2.sourceDirs.length);
3465
+ if (result.cancelled) {
3466
+ const totalInstalled = result.installedPerDir.reduce((a, b) => a + b, 0);
3467
+ return {
3468
+ success: false,
3469
+ message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${totalInstalled}, Skipped: ${result.skipped}`),
3470
+ code: "CANCELLED"
3471
+ };
3472
+ }
3473
+ try {
3474
+ const content = await fetchFileFromGitHub(config2.injection.sourceFile);
3475
+ await injectContentIntoFile(config2.injection.targetFile, content);
3476
+ } catch (error) {
3477
+ console.error(
3478
+ chalk8.red(`Failed to inject content into ${config2.injection.targetFile}:`),
3479
+ error instanceof Error ? error.message : String(error)
3430
3480
  );
3431
- if (opts.dryRun) {
3432
- return {
3433
- success: true,
3434
- message: chalk8.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk8.dim(`Would install ${operations.length} files.`)
3435
- };
3481
+ }
3482
+ await copyBraingridReadme();
3483
+ return {
3484
+ success: true,
3485
+ data: {
3486
+ installedPerDir: result.installedPerDir,
3487
+ skipped: result.skipped
3436
3488
  }
3437
- const copyOps = operations.filter((op) => op.type === "copy");
3438
- const result = await installFiles(copyOps, opts.force || false);
3439
- if (result.cancelled) {
3440
- return {
3441
- success: false,
3442
- message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
3443
- code: "CANCELLED"
3444
- };
3489
+ };
3490
+ }
3491
+ function buildSuccessMessage(config2, installedPerDir, extras) {
3492
+ let dirLines = "";
3493
+ for (let i = 0; i < config2.dirLabels.length; i++) {
3494
+ const count = installedPerDir[i] ?? 0;
3495
+ if (count === 0) continue;
3496
+ const { label } = config2.dirLabels[i];
3497
+ dirLines += chalk8.dim(` ${label}: ${count}
3498
+ `);
3499
+ }
3500
+ return chalk8.green(`\u2705 ${config2.name} integration installed successfully!
3501
+
3502
+ `) + chalk8.dim("Files installed:\n") + dirLines + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3503
+
3504
+ `) + chalk8.dim("Next steps:\n") + chalk8.dim(" 1. Review the integration files\n") + chalk8.dim(` 2. Open ${config2.name}
3505
+ `) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl);
3506
+ }
3507
+ function isSetupResult(result) {
3508
+ return "data" in result && result.success === true && !("message" in result);
3509
+ }
3510
+ async function handleSetupClaudeCode(opts) {
3511
+ const config2 = {
3512
+ name: "Claude Code",
3513
+ sourceDirs: ["claude-code/commands", "claude-code/skills/braingrid-cli"],
3514
+ targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
3515
+ dirLabels: [
3516
+ { label: "Commands", countMode: "files" },
3517
+ { label: "Skills", countMode: "single" }
3518
+ ],
3519
+ injection: {
3520
+ sourceFile: "claude-code/CLAUDE.md",
3521
+ targetFile: "CLAUDE.md"
3522
+ },
3523
+ docsUrl: "https://docs.braingrid.ai/claude-code"
3524
+ };
3525
+ try {
3526
+ const setupResult = await _handleSetup(config2, opts);
3527
+ if (!isSetupResult(setupResult)) {
3528
+ return setupResult;
3445
3529
  }
3530
+ const { installedPerDir } = setupResult.data;
3531
+ const displayPerDir = installedPerDir.map(
3532
+ (count, i) => config2.dirLabels[i].countMode === "single" ? Math.min(count, 1) : count
3533
+ );
3534
+ let statusLineInstalled = false;
3446
3535
  try {
3447
- const content = await fetchFileFromGitHub(config2.injection.sourceFile);
3448
- await injectContentIntoFile(config2.injection.targetFile, content);
3536
+ const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
3537
+ await installStatusLineScript(scriptContent);
3538
+ statusLineInstalled = true;
3449
3539
  } catch (error) {
3450
3540
  console.error(
3451
- chalk8.red(`Failed to inject content into ${config2.injection.targetFile}:`),
3541
+ chalk8.yellow("\u26A0\uFE0F Failed to install status line script:"),
3452
3542
  error instanceof Error ? error.message : String(error)
3453
3543
  );
3454
3544
  }
3455
- let statusLineInstalled = false;
3456
- if (config2.name === "Claude Code") {
3457
- try {
3458
- const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
3459
- await installStatusLineScript(scriptContent);
3460
- await updateClaudeSettings();
3461
- statusLineInstalled = true;
3462
- } catch (error) {
3463
- console.error(
3464
- chalk8.yellow("\u26A0\uFE0F Failed to install status line script:"),
3465
- error instanceof Error ? error.message : String(error)
3466
- );
3467
- }
3545
+ try {
3546
+ await updateClaudeSettings();
3547
+ } catch (error) {
3548
+ console.error(
3549
+ chalk8.yellow("\u26A0\uFE0F Failed to update Claude settings:"),
3550
+ error instanceof Error ? error.message : String(error)
3551
+ );
3468
3552
  }
3469
3553
  let syncHookInstalled = false;
3554
+ try {
3555
+ const syncContent = await fetchFileFromGitHub("claude-code/hooks/sync-braingrid-task.sh");
3556
+ await installHookScript(syncContent);
3557
+ syncHookInstalled = true;
3558
+ } catch (error) {
3559
+ console.error(
3560
+ chalk8.yellow("\u26A0\uFE0F Failed to install sync hook:"),
3561
+ error instanceof Error ? error.message : String(error)
3562
+ );
3563
+ }
3470
3564
  let createHookInstalled = false;
3471
- if (config2.name === "Claude Code") {
3472
- try {
3473
- const syncContent = await fetchFileFromGitHub("claude-code/hooks/sync-braingrid-task.sh");
3474
- await installHookScript(syncContent);
3475
- syncHookInstalled = true;
3476
- } catch (error) {
3477
- console.error(
3478
- chalk8.yellow("\u26A0\uFE0F Failed to install sync hook:"),
3479
- error instanceof Error ? error.message : String(error)
3480
- );
3481
- }
3482
- try {
3483
- const createContent = await fetchFileFromGitHub(
3484
- "claude-code/hooks/create-braingrid-task.sh"
3485
- );
3486
- await installHookScript(createContent, ".claude/hooks/create-braingrid-task.sh");
3487
- createHookInstalled = true;
3488
- } catch (error) {
3489
- console.error(
3490
- chalk8.yellow("\u26A0\uFE0F Failed to install create hook:"),
3491
- error instanceof Error ? error.message : String(error)
3492
- );
3493
- }
3565
+ try {
3566
+ const createContent = await fetchFileFromGitHub("claude-code/hooks/create-braingrid-task.sh");
3567
+ await installHookScript(createContent, ".claude/hooks/create-braingrid-task.sh");
3568
+ createHookInstalled = true;
3569
+ } catch (error) {
3570
+ console.error(
3571
+ chalk8.yellow("\u26A0\uFE0F Failed to install create hook:"),
3572
+ error instanceof Error ? error.message : String(error)
3573
+ );
3494
3574
  }
3495
- await copyBraingridReadme();
3496
3575
  const statusLineMessage = statusLineInstalled ? chalk8.dim(" Status line: .claude/statusline.sh\n") : "";
3497
3576
  const hooksMessage = (syncHookInstalled ? chalk8.dim(" Hook script: .claude/hooks/sync-braingrid-task.sh\n") : "") + (createHookInstalled ? chalk8.dim(" Hook script: .claude/hooks/create-braingrid-task.sh\n") : "");
3498
3577
  return {
3499
3578
  success: true,
3500
- message: chalk8.green(`\u2705 ${config2.name} integration installed successfully!
3501
-
3502
- `) + chalk8.dim("Files installed:\n") + chalk8.dim(` Commands: ${result.installed} files
3503
- `) + statusLineMessage + hooksMessage + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3504
-
3505
- `) + chalk8.dim("Next steps:\n") + chalk8.dim(" 1. Review the integration files\n") + chalk8.dim(` 2. Open ${config2.name}
3506
- `) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl)
3579
+ message: buildSuccessMessage(config2, displayPerDir, statusLineMessage + hooksMessage)
3507
3580
  };
3508
3581
  } catch (error) {
3509
3582
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3513,31 +3586,38 @@ async function _handleSetup(config2, opts) {
3513
3586
  };
3514
3587
  }
3515
3588
  }
3516
- async function handleSetupClaudeCode(opts) {
3517
- const config2 = {
3518
- name: "Claude Code",
3519
- sourceDirs: ["claude-code/commands", "claude-code/skills/braingrid-cli"],
3520
- targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
3521
- injection: {
3522
- sourceFile: "claude-code/CLAUDE.md",
3523
- targetFile: "CLAUDE.md"
3524
- },
3525
- docsUrl: "https://docs.braingrid.ai/claude-code"
3526
- };
3527
- return _handleSetup(config2, opts);
3528
- }
3529
3589
  async function handleSetupCursor(opts) {
3530
3590
  const config2 = {
3531
3591
  name: "Cursor",
3532
3592
  sourceDirs: ["cursor/commands", "cursor/rules"],
3533
3593
  targetDirs: [".cursor/commands", ".cursor/rules"],
3594
+ dirLabels: [
3595
+ { label: "Commands", countMode: "files" },
3596
+ { label: "Rules", countMode: "files" }
3597
+ ],
3534
3598
  injection: {
3535
3599
  sourceFile: "cursor/AGENTS.md",
3536
3600
  targetFile: "AGENTS.md"
3537
3601
  },
3538
3602
  docsUrl: "https://docs.braingrid.ai/cursor"
3539
3603
  };
3540
- return _handleSetup(config2, opts);
3604
+ try {
3605
+ const setupResult = await _handleSetup(config2, opts);
3606
+ if (!isSetupResult(setupResult)) {
3607
+ return setupResult;
3608
+ }
3609
+ const { installedPerDir } = setupResult.data;
3610
+ return {
3611
+ success: true,
3612
+ message: buildSuccessMessage(config2, installedPerDir, "")
3613
+ };
3614
+ } catch (error) {
3615
+ const errorMessage = error instanceof Error ? error.message : String(error);
3616
+ return {
3617
+ success: false,
3618
+ message: chalk8.red(`\u274C Setup failed: ${errorMessage}`)
3619
+ };
3620
+ }
3541
3621
  }
3542
3622
 
3543
3623
  // src/handlers/update.handlers.ts
@@ -3694,6 +3774,7 @@ async function handleUpdate(opts) {
3694
3774
  output += chalk9.green("\u2705 You are already on the latest version!\n");
3695
3775
  console.log(output);
3696
3776
  await copyBraingridReadme();
3777
+ await addBraingridTempToGitignore();
3697
3778
  const setupOutput2 = await promptForIdeUpdates();
3698
3779
  return {
3699
3780
  success: true,
@@ -3733,6 +3814,7 @@ async function handleUpdate(opts) {
3733
3814
  console.log(output);
3734
3815
  executeUpdate(packageManager, PACKAGE_NAME);
3735
3816
  await copyBraingridReadme();
3817
+ await addBraingridTempToGitignore();
3736
3818
  const setupOutput = await promptForIdeUpdates();
3737
3819
  return {
3738
3820
  success: true,
@@ -4290,6 +4372,7 @@ async function handleInit(opts) {
4290
4372
  created_at: project2.created_at
4291
4373
  };
4292
4374
  await saveProjectConfig(localConfig);
4375
+ await addBraingridTempToGitignore();
4293
4376
  await copyBraingridReadme();
4294
4377
  console.log(
4295
4378
  chalk10.green("\u2705 Repository initialized successfully!\n\n") + chalk10.dim("Project: ") + chalk10.cyan(project2.name) + chalk10.dim(` (${project2.short_id})`) + "\n" + chalk10.dim("Config: ") + chalk10.gray(".braingrid/project.json") + "\n"
@@ -4297,62 +4380,62 @@ async function handleInit(opts) {
4297
4380
  const installedIDEs = await detectInstalledIDEs();
4298
4381
  if (installedIDEs.claudeCode) {
4299
4382
  const claudeSetupExists = await fileExists2(".claude/commands/specify.md");
4300
- if (!claudeSetupExists) {
4383
+ console.log("");
4384
+ const setupClaude = await confirm2({
4385
+ message: claudeSetupExists ? "Claude Code detected. Update BrainGrid integration?" : "Claude Code detected. Install BrainGrid integration? (slash commands, skills, status line)",
4386
+ default: true
4387
+ });
4388
+ if (setupClaude) {
4301
4389
  console.log("");
4302
- const setupClaude = await confirm2({
4303
- message: "Claude Code detected. Install BrainGrid integration? (slash commands, skills, status line)",
4304
- default: true
4305
- });
4306
- if (setupClaude) {
4307
- console.log("");
4308
- try {
4309
- const result = await handleSetupClaudeCode({ force: false });
4310
- if (result.success) {
4311
- console.log(result.message);
4312
- } else {
4313
- console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup was not completed."));
4314
- console.log(
4315
- chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4316
- );
4317
- }
4318
- } catch {
4319
- console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup encountered an error."));
4390
+ try {
4391
+ const result = await handleSetupClaudeCode({
4392
+ force: claudeSetupExists
4393
+ });
4394
+ if (result.success) {
4395
+ console.log(result.message);
4396
+ } else {
4397
+ console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup was not completed."));
4320
4398
  console.log(
4321
4399
  chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4322
4400
  );
4323
4401
  }
4324
- console.log("");
4402
+ } catch {
4403
+ console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup encountered an error."));
4404
+ console.log(
4405
+ chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4406
+ );
4325
4407
  }
4408
+ console.log("");
4326
4409
  }
4327
4410
  }
4328
4411
  if (installedIDEs.cursor) {
4329
4412
  const cursorSetupExists = await fileExists2(".cursor/commands/specify.md");
4330
- if (!cursorSetupExists) {
4413
+ console.log("");
4414
+ const setupCursor = await confirm2({
4415
+ message: cursorSetupExists ? "Cursor detected. Update BrainGrid integration?" : "Cursor detected. Install BrainGrid integration? (slash commands, rules, context)",
4416
+ default: true
4417
+ });
4418
+ if (setupCursor) {
4331
4419
  console.log("");
4332
- const setupCursor = await confirm2({
4333
- message: "Cursor detected. Install BrainGrid integration? (slash commands, rules, context)",
4334
- default: true
4335
- });
4336
- if (setupCursor) {
4337
- console.log("");
4338
- try {
4339
- const result = await handleSetupCursor({ force: false });
4340
- if (result.success) {
4341
- console.log(result.message);
4342
- } else {
4343
- console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup was not completed."));
4344
- console.log(
4345
- chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4346
- );
4347
- }
4348
- } catch {
4349
- console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup encountered an error."));
4420
+ try {
4421
+ const result = await handleSetupCursor({
4422
+ force: cursorSetupExists
4423
+ });
4424
+ if (result.success) {
4425
+ console.log(result.message);
4426
+ } else {
4427
+ console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup was not completed."));
4350
4428
  console.log(
4351
4429
  chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4352
4430
  );
4353
4431
  }
4354
- console.log("");
4432
+ } catch {
4433
+ console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup encountered an error."));
4434
+ console.log(
4435
+ chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4436
+ );
4355
4437
  }
4438
+ console.log("");
4356
4439
  }
4357
4440
  }
4358
4441
  return {