@braingrid/cli 0.2.36 → 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,26 @@ 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
+
10
30
  ## [0.2.36] - 2026-02-14
11
31
 
12
32
  ### 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.36" : "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",
@@ -2198,8 +2198,7 @@ async function handleCompletion(shellArg, opts) {
2198
2198
  }
2199
2199
 
2200
2200
  // src/handlers/init.handlers.ts
2201
- import { access as access3, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
2202
- import path6 from "path";
2201
+ import { access as access3 } from "fs/promises";
2203
2202
  import { confirm as confirm2, input, select as select3 } from "@inquirer/prompts";
2204
2203
  import chalk10 from "chalk";
2205
2204
 
@@ -3033,9 +3032,33 @@ async function canUseGhAutomation() {
3033
3032
  return isGhAuthenticated();
3034
3033
  }
3035
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
+
3036
3059
  // src/utils/local-store.ts
3037
3060
  import fs3 from "fs";
3038
- import path4 from "path";
3061
+ import path5 from "path";
3039
3062
 
3040
3063
  // src/types/local-project.ts
3041
3064
  import { z } from "zod";
@@ -3065,11 +3088,11 @@ async function getDefaultCwd() {
3065
3088
  }
3066
3089
  async function getBraingridDir(cwd) {
3067
3090
  const dir = cwd ?? await getDefaultCwd();
3068
- return path4.join(dir, BRAINGRID_DIR);
3091
+ return path5.join(dir, BRAINGRID_DIR);
3069
3092
  }
3070
3093
  async function getProjectConfigPath(cwd) {
3071
3094
  const braingridDir = await getBraingridDir(cwd);
3072
- return path4.join(braingridDir, PROJECT_CONFIG_FILE);
3095
+ return path5.join(braingridDir, PROJECT_CONFIG_FILE);
3073
3096
  }
3074
3097
  async function projectConfigExists(cwd) {
3075
3098
  const configPath = await getProjectConfigPath(cwd);
@@ -3286,7 +3309,7 @@ async function checkAndShowUpdateWarning() {
3286
3309
 
3287
3310
  // src/handlers/setup.handlers.ts
3288
3311
  import * as fs4 from "fs/promises";
3289
- import * as path5 from "path";
3312
+ import * as path6 from "path";
3290
3313
  import { select } from "@inquirer/prompts";
3291
3314
  import chalk8 from "chalk";
3292
3315
  async function fileExists(filePath) {
@@ -3318,22 +3341,23 @@ async function checkPrerequisites() {
3318
3341
  }
3319
3342
  async function getFileList(sourcePaths, targetPaths) {
3320
3343
  const operations = [];
3321
- async function processDirectory(sourceDir, targetDir) {
3344
+ async function processDirectory(sourceDir, targetDir, dirIndex) {
3322
3345
  try {
3323
3346
  const items = await listGitHubDirectory(sourceDir);
3324
3347
  for (const item of items) {
3325
3348
  if (item.type === "file") {
3326
- const itemTargetPath = path5.join(targetDir, item.name);
3349
+ const itemTargetPath = path6.join(targetDir, item.name);
3327
3350
  const exists = await fileExists(itemTargetPath);
3328
3351
  operations.push({
3329
3352
  type: "copy",
3330
3353
  sourcePath: item.path,
3331
3354
  targetPath: itemTargetPath,
3332
- exists
3355
+ exists,
3356
+ dirIndex
3333
3357
  });
3334
3358
  } else if (item.type === "dir") {
3335
- const itemTargetPath = path5.join(targetDir, item.name);
3336
- await processDirectory(item.path, itemTargetPath);
3359
+ const itemTargetPath = path6.join(targetDir, item.name);
3360
+ await processDirectory(item.path, itemTargetPath, dirIndex);
3337
3361
  }
3338
3362
  }
3339
3363
  } catch (error) {
@@ -3344,7 +3368,7 @@ async function getFileList(sourcePaths, targetPaths) {
3344
3368
  }
3345
3369
  }
3346
3370
  for (let i = 0; i < sourcePaths.length; i++) {
3347
- await processDirectory(sourcePaths[i], targetPaths[i]);
3371
+ await processDirectory(sourcePaths[i], targetPaths[i], i);
3348
3372
  }
3349
3373
  return operations;
3350
3374
  }
@@ -3380,15 +3404,15 @@ async function promptForConflict(filePath) {
3380
3404
  });
3381
3405
  return answer;
3382
3406
  }
3383
- async function installFiles(operations, force) {
3384
- let installed = 0;
3407
+ async function installFiles(operations, force, dirCount) {
3408
+ const installedPerDir = new Array(dirCount).fill(0);
3385
3409
  let skipped = 0;
3386
3410
  let overwriteAll = force;
3387
3411
  for (const operation of operations) {
3388
3412
  if (operation.exists && !overwriteAll) {
3389
3413
  const response = await promptForConflict(operation.targetPath);
3390
3414
  if (response === "quit") {
3391
- return { installed, skipped, cancelled: true };
3415
+ return { installedPerDir, skipped, cancelled: true };
3392
3416
  } else if (response === "skip") {
3393
3417
  skipped++;
3394
3418
  continue;
@@ -3398,7 +3422,9 @@ async function installFiles(operations, force) {
3398
3422
  }
3399
3423
  try {
3400
3424
  await copyFileFromGitHub(operation.sourcePath, operation.targetPath);
3401
- installed++;
3425
+ if (operation.dirIndex !== void 0) {
3426
+ installedPerDir[operation.dirIndex]++;
3427
+ }
3402
3428
  } catch (error) {
3403
3429
  console.error(
3404
3430
  chalk8.red(`Failed to copy ${operation.targetPath}:`),
@@ -3407,7 +3433,7 @@ async function installFiles(operations, force) {
3407
3433
  skipped++;
3408
3434
  }
3409
3435
  }
3410
- return { installed, skipped, cancelled: false };
3436
+ return { installedPerDir, skipped, cancelled: false };
3411
3437
  }
3412
3438
  async function _handleSetup(config2, opts) {
3413
3439
  const prerequisiteError = await checkPrerequisites();
@@ -3435,11 +3461,12 @@ async function _handleSetup(config2, opts) {
3435
3461
  };
3436
3462
  }
3437
3463
  const copyOps = operations.filter((op) => op.type === "copy");
3438
- const result = await installFiles(copyOps, opts.force || false);
3464
+ const result = await installFiles(copyOps, opts.force || false, config2.sourceDirs.length);
3439
3465
  if (result.cancelled) {
3466
+ const totalInstalled = result.installedPerDir.reduce((a, b) => a + b, 0);
3440
3467
  return {
3441
3468
  success: false,
3442
- message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
3469
+ message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${totalInstalled}, Skipped: ${result.skipped}`),
3443
3470
  code: "CANCELLED"
3444
3471
  };
3445
3472
  }
@@ -3456,16 +3483,23 @@ async function _handleSetup(config2, opts) {
3456
3483
  return {
3457
3484
  success: true,
3458
3485
  data: {
3459
- installed: result.installed,
3486
+ installedPerDir: result.installedPerDir,
3460
3487
  skipped: result.skipped
3461
3488
  }
3462
3489
  };
3463
3490
  }
3464
- function buildSuccessMessage(config2, installed, extras) {
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
+ }
3465
3500
  return chalk8.green(`\u2705 ${config2.name} integration installed successfully!
3466
3501
 
3467
- `) + chalk8.dim("Files installed:\n") + chalk8.dim(` Commands: ${installed} files
3468
- `) + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3502
+ `) + chalk8.dim("Files installed:\n") + dirLines + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3469
3503
 
3470
3504
  `) + chalk8.dim("Next steps:\n") + chalk8.dim(" 1. Review the integration files\n") + chalk8.dim(` 2. Open ${config2.name}
3471
3505
  `) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl);
@@ -3478,6 +3512,10 @@ async function handleSetupClaudeCode(opts) {
3478
3512
  name: "Claude Code",
3479
3513
  sourceDirs: ["claude-code/commands", "claude-code/skills/braingrid-cli"],
3480
3514
  targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
3515
+ dirLabels: [
3516
+ { label: "Commands", countMode: "files" },
3517
+ { label: "Skills", countMode: "single" }
3518
+ ],
3481
3519
  injection: {
3482
3520
  sourceFile: "claude-code/CLAUDE.md",
3483
3521
  targetFile: "CLAUDE.md"
@@ -3489,7 +3527,10 @@ async function handleSetupClaudeCode(opts) {
3489
3527
  if (!isSetupResult(setupResult)) {
3490
3528
  return setupResult;
3491
3529
  }
3492
- const { installed } = setupResult.data;
3530
+ const { installedPerDir } = setupResult.data;
3531
+ const displayPerDir = installedPerDir.map(
3532
+ (count, i) => config2.dirLabels[i].countMode === "single" ? Math.min(count, 1) : count
3533
+ );
3493
3534
  let statusLineInstalled = false;
3494
3535
  try {
3495
3536
  const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
@@ -3535,7 +3576,7 @@ async function handleSetupClaudeCode(opts) {
3535
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") : "");
3536
3577
  return {
3537
3578
  success: true,
3538
- message: buildSuccessMessage(config2, installed, statusLineMessage + hooksMessage)
3579
+ message: buildSuccessMessage(config2, displayPerDir, statusLineMessage + hooksMessage)
3539
3580
  };
3540
3581
  } catch (error) {
3541
3582
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3550,6 +3591,10 @@ async function handleSetupCursor(opts) {
3550
3591
  name: "Cursor",
3551
3592
  sourceDirs: ["cursor/commands", "cursor/rules"],
3552
3593
  targetDirs: [".cursor/commands", ".cursor/rules"],
3594
+ dirLabels: [
3595
+ { label: "Commands", countMode: "files" },
3596
+ { label: "Rules", countMode: "files" }
3597
+ ],
3553
3598
  injection: {
3554
3599
  sourceFile: "cursor/AGENTS.md",
3555
3600
  targetFile: "AGENTS.md"
@@ -3561,10 +3606,10 @@ async function handleSetupCursor(opts) {
3561
3606
  if (!isSetupResult(setupResult)) {
3562
3607
  return setupResult;
3563
3608
  }
3564
- const { installed } = setupResult.data;
3609
+ const { installedPerDir } = setupResult.data;
3565
3610
  return {
3566
3611
  success: true,
3567
- message: buildSuccessMessage(config2, installed, "")
3612
+ message: buildSuccessMessage(config2, installedPerDir, "")
3568
3613
  };
3569
3614
  } catch (error) {
3570
3615
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3729,6 +3774,7 @@ async function handleUpdate(opts) {
3729
3774
  output += chalk9.green("\u2705 You are already on the latest version!\n");
3730
3775
  console.log(output);
3731
3776
  await copyBraingridReadme();
3777
+ await addBraingridTempToGitignore();
3732
3778
  const setupOutput2 = await promptForIdeUpdates();
3733
3779
  return {
3734
3780
  success: true,
@@ -3768,6 +3814,7 @@ async function handleUpdate(opts) {
3768
3814
  console.log(output);
3769
3815
  executeUpdate(packageManager, PACKAGE_NAME);
3770
3816
  await copyBraingridReadme();
3817
+ await addBraingridTempToGitignore();
3771
3818
  const setupOutput = await promptForIdeUpdates();
3772
3819
  return {
3773
3820
  success: true,
@@ -3798,26 +3845,6 @@ async function fileExists2(filePath) {
3798
3845
  return false;
3799
3846
  }
3800
3847
  }
3801
- async function addBraingridTempToGitignore() {
3802
- const gitRoot = await getGitRoot();
3803
- if (!gitRoot) return;
3804
- const gitignorePath = path6.join(gitRoot, ".gitignore");
3805
- const entry = ".braingrid/temp/";
3806
- let content = "";
3807
- try {
3808
- content = await readFile2(gitignorePath, "utf8");
3809
- } catch {
3810
- }
3811
- const lines = content.split("\n");
3812
- if (lines.some((line) => line.trim() === entry || line.trim() === ".braingrid/temp")) {
3813
- return;
3814
- }
3815
- const addition = content.endsWith("\n") || content === "" ? "" : "\n";
3816
- const newContent = `${content}${addition}# BrainGrid temp files
3817
- ${entry}
3818
- `;
3819
- await writeFile2(gitignorePath, newContent, "utf8");
3820
- }
3821
3848
  function getServices() {
3822
3849
  const config2 = getConfig();
3823
3850
  const auth = new BraingridAuth(config2.apiUrl);