@braingrid/cli 0.2.35 → 0.2.36

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,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.36] - 2026-02-14
11
+
12
+ ### Added
13
+
14
+ - **Add `.braingrid/temp/` to `.gitignore` during init**
15
+ - `braingrid init` now automatically appends `.braingrid/temp/` to the project's `.gitignore`
16
+ - Prevents session-specific temp files (e.g., acceptance criteria checklists) from being committed
17
+ - Skips if the entry already exists; creates `.gitignore` if none exists
18
+
19
+ ### Fixed
20
+
21
+ - **Always offer Claude Code setup updates during init**
22
+ - `braingrid init` now prompts to update Claude Code integration even when it's already installed
23
+ - Ensures users get the latest slash commands, skills, and status line configuration
24
+
10
25
  ## [0.2.35] - 2026-02-14
11
26
 
12
27
  ### 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.36" : "0.0.0-test";
226
226
  var PRODUCTION_CONFIG = {
227
227
  apiUrl: "https://app.braingrid.ai",
228
228
  workosAuthUrl: "https://auth.braingrid.ai",
@@ -2198,7 +2198,8 @@ async function handleCompletion(shellArg, opts) {
2198
2198
  }
2199
2199
 
2200
2200
  // src/handlers/init.handlers.ts
2201
- import { access as access3 } from "fs/promises";
2201
+ import { access as access3, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
2202
+ import path6 from "path";
2202
2203
  import { confirm as confirm2, input, select as select3 } from "@inquirer/prompts";
2203
2204
  import chalk10 from "chalk";
2204
2205
 
@@ -2543,17 +2544,17 @@ function parseGitHubError(error) {
2543
2544
  }
2544
2545
  return errorMessage;
2545
2546
  }
2546
- async function fetchFileFromGitHub(path6) {
2547
+ async function fetchFileFromGitHub(path7) {
2547
2548
  return withRetry(async () => {
2548
2549
  try {
2549
- const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
2550
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path7}`;
2550
2551
  const { stdout } = await execAsync(command);
2551
2552
  const response = JSON.parse(stdout);
2552
2553
  if (response.type !== "file") {
2553
- throw new Error(`Path ${path6} is not a file`);
2554
+ throw new Error(`Path ${path7} is not a file`);
2554
2555
  }
2555
2556
  if (!response.content || !response.encoding) {
2556
- throw new Error(`No content found for file ${path6}`);
2557
+ throw new Error(`No content found for file ${path7}`);
2557
2558
  }
2558
2559
  if (response.encoding !== "base64") {
2559
2560
  throw new Error(`Unexpected encoding: ${response.encoding}`);
@@ -2562,18 +2563,18 @@ async function fetchFileFromGitHub(path6) {
2562
2563
  return content;
2563
2564
  } catch (error) {
2564
2565
  const parsedError = parseGitHubError(error);
2565
- throw new Error(`Failed to fetch file ${path6}: ${parsedError}`);
2566
+ throw new Error(`Failed to fetch file ${path7}: ${parsedError}`);
2566
2567
  }
2567
2568
  });
2568
2569
  }
2569
- async function listGitHubDirectory(path6) {
2570
+ async function listGitHubDirectory(path7) {
2570
2571
  return withRetry(async () => {
2571
2572
  try {
2572
- const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
2573
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path7}`;
2573
2574
  const { stdout } = await execAsync(command);
2574
2575
  const response = JSON.parse(stdout);
2575
2576
  if (!Array.isArray(response)) {
2576
- throw new Error(`Path ${path6} is not a directory`);
2577
+ throw new Error(`Path ${path7} is not a directory`);
2577
2578
  }
2578
2579
  return response.map((item) => ({
2579
2580
  name: item.name,
@@ -2582,7 +2583,7 @@ async function listGitHubDirectory(path6) {
2582
2583
  }));
2583
2584
  } catch (error) {
2584
2585
  const parsedError = parseGitHubError(error);
2585
- throw new Error(`Failed to list directory ${path6}: ${parsedError}`);
2586
+ throw new Error(`Failed to list directory ${path7}: ${parsedError}`);
2586
2587
  }
2587
2588
  });
2588
2589
  }
@@ -3409,101 +3410,132 @@ async function installFiles(operations, force) {
3409
3410
  return { installed, skipped, cancelled: false };
3410
3411
  }
3411
3412
  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...
3413
+ const prerequisiteError = await checkPrerequisites();
3414
+ if (prerequisiteError) {
3415
+ return prerequisiteError;
3416
+ }
3417
+ console.log(chalk8.bold(`\u{1F680} Setting up ${config2.name} integration...
3418
3418
  `));
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
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
3430
+ );
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
+ };
3436
+ }
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
+ };
3445
+ }
3446
+ try {
3447
+ const content = await fetchFileFromGitHub(config2.injection.sourceFile);
3448
+ await injectContentIntoFile(config2.injection.targetFile, content);
3449
+ } catch (error) {
3450
+ console.error(
3451
+ chalk8.red(`Failed to inject content into ${config2.injection.targetFile}:`),
3452
+ error instanceof Error ? error.message : String(error)
3430
3453
  );
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
- };
3454
+ }
3455
+ await copyBraingridReadme();
3456
+ return {
3457
+ success: true,
3458
+ data: {
3459
+ installed: result.installed,
3460
+ skipped: result.skipped
3436
3461
  }
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
- };
3462
+ };
3463
+ }
3464
+ function buildSuccessMessage(config2, installed, extras) {
3465
+ return chalk8.green(`\u2705 ${config2.name} integration installed successfully!
3466
+
3467
+ `) + chalk8.dim("Files installed:\n") + chalk8.dim(` Commands: ${installed} files
3468
+ `) + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3469
+
3470
+ `) + chalk8.dim("Next steps:\n") + chalk8.dim(" 1. Review the integration files\n") + chalk8.dim(` 2. Open ${config2.name}
3471
+ `) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl);
3472
+ }
3473
+ function isSetupResult(result) {
3474
+ return "data" in result && result.success === true && !("message" in result);
3475
+ }
3476
+ async function handleSetupClaudeCode(opts) {
3477
+ const config2 = {
3478
+ name: "Claude Code",
3479
+ sourceDirs: ["claude-code/commands", "claude-code/skills/braingrid-cli"],
3480
+ targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
3481
+ injection: {
3482
+ sourceFile: "claude-code/CLAUDE.md",
3483
+ targetFile: "CLAUDE.md"
3484
+ },
3485
+ docsUrl: "https://docs.braingrid.ai/claude-code"
3486
+ };
3487
+ try {
3488
+ const setupResult = await _handleSetup(config2, opts);
3489
+ if (!isSetupResult(setupResult)) {
3490
+ return setupResult;
3445
3491
  }
3492
+ const { installed } = setupResult.data;
3493
+ let statusLineInstalled = false;
3446
3494
  try {
3447
- const content = await fetchFileFromGitHub(config2.injection.sourceFile);
3448
- await injectContentIntoFile(config2.injection.targetFile, content);
3495
+ const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
3496
+ await installStatusLineScript(scriptContent);
3497
+ statusLineInstalled = true;
3449
3498
  } catch (error) {
3450
3499
  console.error(
3451
- chalk8.red(`Failed to inject content into ${config2.injection.targetFile}:`),
3500
+ chalk8.yellow("\u26A0\uFE0F Failed to install status line script:"),
3452
3501
  error instanceof Error ? error.message : String(error)
3453
3502
  );
3454
3503
  }
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
- }
3504
+ try {
3505
+ await updateClaudeSettings();
3506
+ } catch (error) {
3507
+ console.error(
3508
+ chalk8.yellow("\u26A0\uFE0F Failed to update Claude settings:"),
3509
+ error instanceof Error ? error.message : String(error)
3510
+ );
3468
3511
  }
3469
3512
  let syncHookInstalled = false;
3513
+ try {
3514
+ const syncContent = await fetchFileFromGitHub("claude-code/hooks/sync-braingrid-task.sh");
3515
+ await installHookScript(syncContent);
3516
+ syncHookInstalled = true;
3517
+ } catch (error) {
3518
+ console.error(
3519
+ chalk8.yellow("\u26A0\uFE0F Failed to install sync hook:"),
3520
+ error instanceof Error ? error.message : String(error)
3521
+ );
3522
+ }
3470
3523
  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
- }
3524
+ try {
3525
+ const createContent = await fetchFileFromGitHub("claude-code/hooks/create-braingrid-task.sh");
3526
+ await installHookScript(createContent, ".claude/hooks/create-braingrid-task.sh");
3527
+ createHookInstalled = true;
3528
+ } catch (error) {
3529
+ console.error(
3530
+ chalk8.yellow("\u26A0\uFE0F Failed to install create hook:"),
3531
+ error instanceof Error ? error.message : String(error)
3532
+ );
3494
3533
  }
3495
- await copyBraingridReadme();
3496
3534
  const statusLineMessage = statusLineInstalled ? chalk8.dim(" Status line: .claude/statusline.sh\n") : "";
3497
3535
  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
3536
  return {
3499
3537
  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)
3538
+ message: buildSuccessMessage(config2, installed, statusLineMessage + hooksMessage)
3507
3539
  };
3508
3540
  } catch (error) {
3509
3541
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3513,19 +3545,6 @@ async function _handleSetup(config2, opts) {
3513
3545
  };
3514
3546
  }
3515
3547
  }
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
3548
  async function handleSetupCursor(opts) {
3530
3549
  const config2 = {
3531
3550
  name: "Cursor",
@@ -3537,7 +3556,23 @@ async function handleSetupCursor(opts) {
3537
3556
  },
3538
3557
  docsUrl: "https://docs.braingrid.ai/cursor"
3539
3558
  };
3540
- return _handleSetup(config2, opts);
3559
+ try {
3560
+ const setupResult = await _handleSetup(config2, opts);
3561
+ if (!isSetupResult(setupResult)) {
3562
+ return setupResult;
3563
+ }
3564
+ const { installed } = setupResult.data;
3565
+ return {
3566
+ success: true,
3567
+ message: buildSuccessMessage(config2, installed, "")
3568
+ };
3569
+ } catch (error) {
3570
+ const errorMessage = error instanceof Error ? error.message : String(error);
3571
+ return {
3572
+ success: false,
3573
+ message: chalk8.red(`\u274C Setup failed: ${errorMessage}`)
3574
+ };
3575
+ }
3541
3576
  }
3542
3577
 
3543
3578
  // src/handlers/update.handlers.ts
@@ -3763,6 +3798,26 @@ async function fileExists2(filePath) {
3763
3798
  return false;
3764
3799
  }
3765
3800
  }
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
+ }
3766
3821
  function getServices() {
3767
3822
  const config2 = getConfig();
3768
3823
  const auth = new BraingridAuth(config2.apiUrl);
@@ -4290,6 +4345,7 @@ async function handleInit(opts) {
4290
4345
  created_at: project2.created_at
4291
4346
  };
4292
4347
  await saveProjectConfig(localConfig);
4348
+ await addBraingridTempToGitignore();
4293
4349
  await copyBraingridReadme();
4294
4350
  console.log(
4295
4351
  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 +4353,62 @@ async function handleInit(opts) {
4297
4353
  const installedIDEs = await detectInstalledIDEs();
4298
4354
  if (installedIDEs.claudeCode) {
4299
4355
  const claudeSetupExists = await fileExists2(".claude/commands/specify.md");
4300
- if (!claudeSetupExists) {
4356
+ console.log("");
4357
+ const setupClaude = await confirm2({
4358
+ message: claudeSetupExists ? "Claude Code detected. Update BrainGrid integration?" : "Claude Code detected. Install BrainGrid integration? (slash commands, skills, status line)",
4359
+ default: true
4360
+ });
4361
+ if (setupClaude) {
4301
4362
  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."));
4363
+ try {
4364
+ const result = await handleSetupClaudeCode({
4365
+ force: claudeSetupExists
4366
+ });
4367
+ if (result.success) {
4368
+ console.log(result.message);
4369
+ } else {
4370
+ console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup was not completed."));
4320
4371
  console.log(
4321
4372
  chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4322
4373
  );
4323
4374
  }
4324
- console.log("");
4375
+ } catch {
4376
+ console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup encountered an error."));
4377
+ console.log(
4378
+ chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4379
+ );
4325
4380
  }
4381
+ console.log("");
4326
4382
  }
4327
4383
  }
4328
4384
  if (installedIDEs.cursor) {
4329
4385
  const cursorSetupExists = await fileExists2(".cursor/commands/specify.md");
4330
- if (!cursorSetupExists) {
4386
+ console.log("");
4387
+ const setupCursor = await confirm2({
4388
+ message: cursorSetupExists ? "Cursor detected. Update BrainGrid integration?" : "Cursor detected. Install BrainGrid integration? (slash commands, rules, context)",
4389
+ default: true
4390
+ });
4391
+ if (setupCursor) {
4331
4392
  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."));
4393
+ try {
4394
+ const result = await handleSetupCursor({
4395
+ force: cursorSetupExists
4396
+ });
4397
+ if (result.success) {
4398
+ console.log(result.message);
4399
+ } else {
4400
+ console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup was not completed."));
4350
4401
  console.log(
4351
4402
  chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4352
4403
  );
4353
4404
  }
4354
- console.log("");
4405
+ } catch {
4406
+ console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup encountered an error."));
4407
+ console.log(
4408
+ chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4409
+ );
4355
4410
  }
4411
+ console.log("");
4356
4412
  }
4357
4413
  }
4358
4414
  return {