@braingrid/cli 0.2.5 → 0.2.6

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/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  isGhInstalled,
9
9
  isHomebrewInstalled,
10
10
  isWingetAvailable
11
- } from "./chunk-KNVWIF3L.js";
11
+ } from "./chunk-RAV3B4UY.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { Command } from "commander";
@@ -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.5" : "0.0.0-test";
425
+ var CLI_VERSION = true ? "0.2.6" : "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",
@@ -4956,18 +4956,14 @@ async function handleStatus() {
4956
4956
  output += chalk10.bold("Installed Coding Tools\n");
4957
4957
  output += chalk10.dim("\u2500".repeat(50)) + "\n";
4958
4958
  const cliToolsUnauthenticated = await checkInstalledCliTools();
4959
- if (cliToolsUnauthenticated.every((tool) => !tool.installed)) {
4959
+ const installedTools2 = cliToolsUnauthenticated.filter((tool) => tool.installed);
4960
+ if (installedTools2.length === 0) {
4960
4961
  output += chalk10.yellow("\u26A0\uFE0F No coding tools detected\n\n");
4961
4962
  } else {
4962
- cliToolsUnauthenticated.forEach((tool) => {
4963
- if (tool.installed) {
4964
- const versionInfo = tool.version ? chalk10.dim(` (${tool.version})`) : "";
4965
- output += `${chalk10.green("\u2705")} ${tool.name}${versionInfo}
4966
- `;
4967
- } else {
4968
- output += `${chalk10.dim("\u25CB")} ${tool.name} ${chalk10.dim("(not installed)")}
4963
+ installedTools2.forEach((tool) => {
4964
+ const versionInfo = tool.version ? chalk10.dim(` (${tool.version})`) : "";
4965
+ output += `${chalk10.green("\u2705")} ${tool.name}${versionInfo}
4969
4966
  `;
4970
- }
4971
4967
  });
4972
4968
  output += "\n";
4973
4969
  }
@@ -5054,18 +5050,14 @@ async function handleStatus() {
5054
5050
  output += chalk10.bold("Installed Coding Tools\n");
5055
5051
  output += chalk10.dim("\u2500".repeat(50)) + "\n";
5056
5052
  const cliTools = await checkInstalledCliTools();
5057
- if (cliTools.every((tool) => !tool.installed)) {
5053
+ const installedTools = cliTools.filter((tool) => tool.installed);
5054
+ if (installedTools.length === 0) {
5058
5055
  output += chalk10.yellow("\u26A0\uFE0F No coding tools detected\n\n");
5059
5056
  } else {
5060
- cliTools.forEach((tool) => {
5061
- if (tool.installed) {
5062
- const versionInfo = tool.version ? chalk10.dim(` (${tool.version})`) : "";
5063
- output += `${chalk10.green("\u2705")} ${tool.name}${versionInfo}
5064
- `;
5065
- } else {
5066
- output += `${chalk10.dim("\u25CB")} ${tool.name} ${chalk10.dim("(not installed)")}
5057
+ installedTools.forEach((tool) => {
5058
+ const versionInfo = tool.version ? chalk10.dim(` (${tool.version})`) : "";
5059
+ output += `${chalk10.green("\u2705")} ${tool.name}${versionInfo}
5067
5060
  `;
5068
- }
5069
5061
  });
5070
5062
  output += "\n";
5071
5063
  }
@@ -5095,8 +5087,8 @@ async function handleStatus() {
5095
5087
  }
5096
5088
 
5097
5089
  // src/handlers/init.handlers.ts
5098
- import chalk12 from "chalk";
5099
- import { confirm, select, input } from "@inquirer/prompts";
5090
+ import chalk13 from "chalk";
5091
+ import { confirm, select as select2, input } from "@inquirer/prompts";
5100
5092
 
5101
5093
  // src/services/internal/github-service.ts
5102
5094
  var GitHubService = class {
@@ -5363,108 +5355,551 @@ function getManualInstallInstructions() {
5363
5355
  }
5364
5356
  }
5365
5357
 
5366
- // src/utils/github-repo.ts
5367
- import { exec as exec3 } from "child_process";
5358
+ // src/handlers/setup.handlers.ts
5359
+ import chalk12 from "chalk";
5360
+ import { select } from "@inquirer/prompts";
5361
+ import * as path4 from "path";
5362
+ import * as fs4 from "fs/promises";
5363
+
5364
+ // src/utils/command-execution.ts
5365
+ import { exec as exec3, spawn } from "child_process";
5368
5366
  import { promisify as promisify3 } from "util";
5369
- import path3 from "path";
5370
- var execAsync3 = promisify3(exec3);
5371
- async function initGitRepo() {
5367
+ var execAsyncReal = promisify3(exec3);
5368
+ async function execAsync3(command, options, isTestMode = false, mockExecHandler) {
5369
+ if (isTestMode && mockExecHandler) {
5370
+ return mockExecHandler(command);
5371
+ }
5372
+ const defaultOptions = {
5373
+ maxBuffer: 1024 * 1024 * 10,
5374
+ // 10MB default
5375
+ timeout: 3e5,
5376
+ // 5 minutes
5377
+ ...options
5378
+ };
5379
+ if (command.includes("claude")) {
5380
+ defaultOptions.maxBuffer = 1024 * 1024 * 50;
5381
+ defaultOptions.timeout = 6e5;
5382
+ }
5383
+ return execAsyncReal(command, defaultOptions);
5384
+ }
5385
+
5386
+ // src/services/setup-service.ts
5387
+ import * as fs3 from "fs/promises";
5388
+ import * as path3 from "path";
5389
+ var GITHUB_OWNER = "BrainGridAI";
5390
+ var GITHUB_REPO = "braingrid";
5391
+ var MAX_RETRIES = 3;
5392
+ var INITIAL_RETRY_DELAY = 100;
5393
+ var BEGIN_MARKER = "<!-- BEGIN BRAINGRID INTEGRATION -->";
5394
+ var END_MARKER = "<!-- END BRAINGRID INTEGRATION -->";
5395
+ async function withRetry(fn, retries = MAX_RETRIES, delay = INITIAL_RETRY_DELAY) {
5372
5396
  try {
5373
- await execAsync3("git init");
5374
- return true;
5375
- } catch {
5376
- return false;
5397
+ return await fn();
5398
+ } catch (error) {
5399
+ if (retries === 0) {
5400
+ throw error;
5401
+ }
5402
+ const errorMessage = error instanceof Error ? error.message : String(error);
5403
+ const isNetworkError = errorMessage.includes("ECONNRESET") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("ENOTFOUND") || errorMessage.includes("network");
5404
+ if (!isNetworkError) {
5405
+ throw error;
5406
+ }
5407
+ await new Promise((resolve) => setTimeout(resolve, delay));
5408
+ return withRetry(fn, retries - 1, delay * 2);
5377
5409
  }
5378
5410
  }
5379
- async function isGhAuthenticated() {
5411
+ function parseGitHubError(error) {
5412
+ const errorMessage = error instanceof Error ? error.message : String(error);
5413
+ if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
5414
+ return "File or directory not found in BrainGrid repository";
5415
+ }
5416
+ if (errorMessage.includes("403") || errorMessage.includes("rate limit")) {
5417
+ return "GitHub API rate limit exceeded. Please wait a few minutes and try again.\nCheck rate limit status: gh api rate_limit";
5418
+ }
5419
+ if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
5420
+ return "GitHub CLI is not authenticated. Run: gh auth login";
5421
+ }
5380
5422
  try {
5381
- await execAsync3("gh auth status");
5382
- return true;
5423
+ const match = errorMessage.match(/\{.*\}/s);
5424
+ if (match) {
5425
+ const errorData = JSON.parse(match[0]);
5426
+ return errorData.message || errorMessage;
5427
+ }
5383
5428
  } catch {
5384
- return false;
5385
5429
  }
5430
+ return errorMessage;
5386
5431
  }
5387
- function getCurrentDirectoryName() {
5388
- return path3.basename(process.cwd());
5432
+ async function fetchFileFromGitHub(path6) {
5433
+ return withRetry(async () => {
5434
+ try {
5435
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
5436
+ const { stdout } = await execAsync3(command);
5437
+ const response = JSON.parse(stdout);
5438
+ if (response.type !== "file") {
5439
+ throw new Error(`Path ${path6} is not a file`);
5440
+ }
5441
+ if (!response.content || !response.encoding) {
5442
+ throw new Error(`No content found for file ${path6}`);
5443
+ }
5444
+ if (response.encoding !== "base64") {
5445
+ throw new Error(`Unexpected encoding: ${response.encoding}`);
5446
+ }
5447
+ const content = Buffer.from(response.content, "base64").toString("utf8");
5448
+ return content;
5449
+ } catch (error) {
5450
+ const parsedError = parseGitHubError(error);
5451
+ throw new Error(`Failed to fetch file ${path6}: ${parsedError}`);
5452
+ }
5453
+ });
5389
5454
  }
5390
- async function createGitHubRepoWithGh(name, isPrivate) {
5455
+ async function listGitHubDirectory(path6) {
5456
+ return withRetry(async () => {
5457
+ try {
5458
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
5459
+ const { stdout } = await execAsync3(command);
5460
+ const response = JSON.parse(stdout);
5461
+ if (!Array.isArray(response)) {
5462
+ throw new Error(`Path ${path6} is not a directory`);
5463
+ }
5464
+ return response.map((item) => ({
5465
+ name: item.name,
5466
+ type: item.type,
5467
+ path: item.path
5468
+ }));
5469
+ } catch (error) {
5470
+ const parsedError = parseGitHubError(error);
5471
+ throw new Error(`Failed to list directory ${path6}: ${parsedError}`);
5472
+ }
5473
+ });
5474
+ }
5475
+ async function copyFileFromGitHub(sourcePath, targetPath) {
5391
5476
  try {
5392
- const visibility = isPrivate ? "--private" : "--public";
5393
- const { stdout } = await execAsync3(
5394
- `gh repo create ${name} ${visibility} --source=. --remote=origin`,
5395
- { timeout: 6e4 }
5396
- );
5397
- const urlMatch = stdout.match(/https:\/\/github\.com\/([^/]+)\/([^/\s]+)/);
5398
- if (!urlMatch) {
5399
- return null;
5477
+ const content = await fetchFileFromGitHub(sourcePath);
5478
+ const parentDir = path3.dirname(targetPath);
5479
+ await fs3.mkdir(parentDir, { recursive: true });
5480
+ await fs3.writeFile(targetPath, content, { encoding: "utf8", mode: 420 });
5481
+ } catch (error) {
5482
+ const errorMessage = error instanceof Error ? error.message : String(error);
5483
+ throw new Error(`Failed to copy file ${sourcePath} to ${targetPath}: ${errorMessage}`);
5484
+ }
5485
+ }
5486
+ async function injectContentIntoFile(targetPath, content) {
5487
+ try {
5488
+ let fileContent;
5489
+ let fileExists3 = false;
5490
+ try {
5491
+ fileContent = await fs3.readFile(targetPath, "utf8");
5492
+ fileExists3 = true;
5493
+ } catch {
5494
+ fileContent = "";
5400
5495
  }
5401
- return {
5402
- owner: urlMatch[1],
5403
- name: urlMatch[2],
5404
- url: urlMatch[0]
5405
- };
5406
- } catch {
5407
- return null;
5496
+ if (fileExists3) {
5497
+ const beginIndex = fileContent.indexOf(BEGIN_MARKER);
5498
+ const endIndex = fileContent.indexOf(END_MARKER);
5499
+ if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
5500
+ const before = fileContent.substring(0, beginIndex);
5501
+ const after = fileContent.substring(endIndex + END_MARKER.length);
5502
+ const newContent = `${before}${BEGIN_MARKER}
5503
+ ${content}
5504
+ ${END_MARKER}${after}`;
5505
+ await fs3.writeFile(targetPath, newContent, { encoding: "utf8" });
5506
+ } else {
5507
+ const newContent = `${fileContent}
5508
+
5509
+ ${BEGIN_MARKER}
5510
+ ${content}
5511
+ ${END_MARKER}
5512
+ `;
5513
+ await fs3.writeFile(targetPath, newContent, { encoding: "utf8" });
5514
+ }
5515
+ } else {
5516
+ const parentDir = path3.dirname(targetPath);
5517
+ await fs3.mkdir(parentDir, { recursive: true });
5518
+ const newContent = `${BEGIN_MARKER}
5519
+ ${content}
5520
+ ${END_MARKER}
5521
+ `;
5522
+ await fs3.writeFile(targetPath, newContent, { encoding: "utf8", mode: 420 });
5523
+ }
5524
+ } catch (error) {
5525
+ const errorMessage = error instanceof Error ? error.message : String(error);
5526
+ throw new Error(`Failed to inject content into ${targetPath}: ${errorMessage}`);
5408
5527
  }
5409
5528
  }
5410
- async function canUseGhAutomation() {
5411
- const { isGhInstalled: isGhInstalled2 } = await import("./gh-installer-B65ZOOC4.js");
5412
- const ghInstalled = await isGhInstalled2();
5413
- if (!ghInstalled) {
5414
- return false;
5529
+ async function installStatusLineScript(scriptContent, targetPath = ".claude/statusline.sh") {
5530
+ try {
5531
+ const parentDir = path3.dirname(targetPath);
5532
+ await fs3.mkdir(parentDir, { recursive: true });
5533
+ await fs3.writeFile(targetPath, scriptContent, { encoding: "utf8", mode: 493 });
5534
+ } catch (error) {
5535
+ const errorMessage = error instanceof Error ? error.message : String(error);
5536
+ throw new Error(`Failed to install status line script to ${targetPath}: ${errorMessage}`);
5415
5537
  }
5416
- return isGhAuthenticated();
5417
5538
  }
5418
-
5419
- // src/handlers/init.handlers.ts
5420
- function getServices4() {
5421
- const config = getConfig();
5422
- const auth = new BraingridAuth(config.apiUrl);
5423
- const projectService = new ProjectService(config.apiUrl, auth);
5424
- const githubService = new GitHubService(config.apiUrl, auth);
5425
- const repositoryService = new RepositoryService(config.apiUrl, auth);
5426
- return { projectService, githubService, repositoryService, auth };
5539
+ async function updateClaudeSettings(settingsPath = ".claude/settings.json", scriptPath = ".claude/statusline.sh") {
5540
+ try {
5541
+ let settings = {};
5542
+ try {
5543
+ const content2 = await fs3.readFile(settingsPath, "utf8");
5544
+ settings = JSON.parse(content2);
5545
+ } catch {
5546
+ }
5547
+ settings.statusLine = {
5548
+ type: "command",
5549
+ command: scriptPath,
5550
+ padding: 0
5551
+ };
5552
+ const parentDir = path3.dirname(settingsPath);
5553
+ await fs3.mkdir(parentDir, { recursive: true });
5554
+ const content = JSON.stringify(settings, null, 2);
5555
+ await fs3.writeFile(settingsPath, content, { encoding: "utf8" });
5556
+ } catch (error) {
5557
+ const errorMessage = error instanceof Error ? error.message : String(error);
5558
+ throw new Error(`Failed to update Claude settings at ${settingsPath}: ${errorMessage}`);
5559
+ }
5427
5560
  }
5428
- function promptToAddOrganization(owner, webUrl) {
5429
- return {
5430
- success: false,
5431
- message: chalk12.yellow("\u26A0\uFE0F No projects found for this repository.\n\n") + chalk12.dim(`Repository: ${owner}/*
5432
-
5433
- `) + chalk12.dim(`You have GitHub connected, but not for the "${owner}" organization.
5434
5561
 
5435
- `) + chalk12.dim("To connect ") + chalk12.cyan(owner) + chalk12.dim(":\n") + chalk12.dim(" 1. Visit: ") + chalk12.cyan(`${webUrl}/integrations`) + chalk12.dim("\n") + chalk12.dim(' 2. Click "Add GitHub Organization"\n') + chalk12.dim(` 3. Select "${owner}"
5436
- `) + chalk12.dim(" 4. Run ") + chalk12.cyan("braingrid init") + chalk12.dim(" again")
5437
- };
5562
+ // src/handlers/setup.handlers.ts
5563
+ async function fileExists(filePath) {
5564
+ try {
5565
+ await fs4.access(filePath);
5566
+ return true;
5567
+ } catch {
5568
+ return false;
5569
+ }
5438
5570
  }
5439
- async function promptToCreateProject(gitInfo, projectService, repositoryService) {
5440
- const webUrl = getConfig().getWebAppUrl();
5441
- if (!gitInfo.owner || !gitInfo.name) {
5571
+ async function checkPrerequisites() {
5572
+ try {
5573
+ await execAsync3("gh --version");
5574
+ } catch {
5442
5575
  return {
5443
5576
  success: false,
5444
- message: chalk12.red("\u274C Repository information is incomplete")
5577
+ message: chalk12.red("\u274C GitHub CLI is not installed.\n\n") + chalk12.dim("Install instructions:\n") + chalk12.dim(" macOS: ") + chalk12.cyan("brew install gh") + chalk12.dim("\n") + chalk12.dim(" Windows: ") + chalk12.cyan("winget install GitHub.CLI") + chalk12.dim("\n") + chalk12.dim(" Linux: See ") + chalk12.cyan("https://cli.github.com/manual/installation") + chalk12.dim("\n\n") + chalk12.dim("After installing, run: ") + chalk12.cyan("gh auth login")
5445
5578
  };
5446
5579
  }
5447
- const repositoryId = await getRepositoryId(repositoryService, gitInfo.owner, gitInfo.name);
5448
- if (!repositoryId) {
5580
+ try {
5581
+ await execAsync3("gh auth status");
5582
+ } catch {
5449
5583
  return {
5450
5584
  success: false,
5451
- message: chalk12.yellow("\u26A0\uFE0F Repository accessible but could not retrieve details.\n\n") + chalk12.dim(`Repository: ${gitInfo.owner}/${gitInfo.name}
5452
-
5453
- `) + chalk12.dim("Please try again or create a project manually at: ") + chalk12.cyan(webUrl)
5585
+ message: chalk12.red("\u274C Not authenticated with GitHub CLI.\n\n") + chalk12.dim("Please run: ") + chalk12.cyan("gh auth login")
5454
5586
  };
5455
5587
  }
5456
- console.log(
5457
- chalk12.yellow("\u26A0\uFE0F Repository accessible but no project exists.\n\n") + chalk12.dim(`Repository: ${gitInfo.owner}/${gitInfo.name}
5458
- `)
5459
- );
5460
- const shouldCreate = await confirm({
5461
- message: `Would you like to create a project for ${gitInfo.name} now?`,
5462
- default: true
5463
- });
5588
+ return null;
5589
+ }
5590
+ async function getFileList(sourcePaths, targetPaths) {
5591
+ const operations = [];
5592
+ for (let i = 0; i < sourcePaths.length; i++) {
5593
+ const sourcePath = sourcePaths[i];
5594
+ const targetPath = targetPaths[i];
5595
+ try {
5596
+ const items = await listGitHubDirectory(sourcePath);
5597
+ for (const item of items) {
5598
+ if (item.type === "file") {
5599
+ const itemTargetPath = path4.join(targetPath, item.name);
5600
+ const exists = await fileExists(itemTargetPath);
5601
+ operations.push({
5602
+ type: "copy",
5603
+ sourcePath: item.path,
5604
+ targetPath: itemTargetPath,
5605
+ exists
5606
+ });
5607
+ }
5608
+ }
5609
+ } catch (error) {
5610
+ console.warn(
5611
+ chalk12.yellow(`\u26A0\uFE0F Could not list directory: ${sourcePath}`),
5612
+ error instanceof Error ? error.message : String(error)
5613
+ );
5614
+ }
5615
+ }
5616
+ return operations;
5617
+ }
5618
+ function displayInstallationPlan(operations, injectionFile) {
5619
+ console.log(chalk12.bold("\n\u{1F4CB} Installation Plan:\n"));
5620
+ console.log(chalk12.cyan(" Content Injection:"));
5621
+ console.log(chalk12.dim(` ${injectionFile}`));
5622
+ const newFiles = operations.filter((op) => !op.exists);
5623
+ const existingFiles = operations.filter((op) => op.exists);
5624
+ if (newFiles.length > 0) {
5625
+ console.log(chalk12.cyan("\n New Files:"));
5626
+ for (const op of newFiles) {
5627
+ console.log(chalk12.dim(` ${op.targetPath}`));
5628
+ }
5629
+ }
5630
+ if (existingFiles.length > 0) {
5631
+ console.log(chalk12.yellow("\n Existing Files (will prompt):"));
5632
+ for (const op of existingFiles) {
5633
+ console.log(chalk12.dim(` ${op.targetPath}`));
5634
+ }
5635
+ }
5636
+ console.log("");
5637
+ }
5638
+ async function promptForConflict(filePath) {
5639
+ const answer = await select({
5640
+ message: chalk12.yellow(`File exists: ${filePath}`),
5641
+ choices: [
5642
+ { name: "[O]verwrite - Replace this file", value: "overwrite" },
5643
+ { name: "[S]kip - Keep existing file", value: "skip" },
5644
+ { name: "[A]ll - Overwrite all remaining", value: "all" },
5645
+ { name: "[Q]uit - Cancel installation", value: "quit" }
5646
+ ]
5647
+ });
5648
+ return answer;
5649
+ }
5650
+ async function installFiles(operations, force) {
5651
+ let installed = 0;
5652
+ let skipped = 0;
5653
+ let overwriteAll = force;
5654
+ for (const operation of operations) {
5655
+ if (operation.exists && !overwriteAll) {
5656
+ const response = await promptForConflict(operation.targetPath);
5657
+ if (response === "quit") {
5658
+ return { installed, skipped, cancelled: true };
5659
+ } else if (response === "skip") {
5660
+ skipped++;
5661
+ continue;
5662
+ } else if (response === "all") {
5663
+ overwriteAll = true;
5664
+ }
5665
+ }
5666
+ try {
5667
+ await copyFileFromGitHub(operation.sourcePath, operation.targetPath);
5668
+ installed++;
5669
+ } catch (error) {
5670
+ console.error(
5671
+ chalk12.red(`Failed to copy ${operation.targetPath}:`),
5672
+ error instanceof Error ? error.message : String(error)
5673
+ );
5674
+ skipped++;
5675
+ }
5676
+ }
5677
+ return { installed, skipped, cancelled: false };
5678
+ }
5679
+ async function _handleSetup(config, opts) {
5680
+ try {
5681
+ const prerequisiteError = await checkPrerequisites();
5682
+ if (prerequisiteError) {
5683
+ return prerequisiteError;
5684
+ }
5685
+ console.log(chalk12.bold(`\u{1F680} Setting up ${config.name} integration...
5686
+ `));
5687
+ const operations = await getFileList(config.sourceDirs, config.targetDirs);
5688
+ const injectionFileExists = await fileExists(config.injection.targetFile);
5689
+ operations.push({
5690
+ type: "inject",
5691
+ sourcePath: config.injection.sourceFile,
5692
+ targetPath: config.injection.targetFile,
5693
+ exists: injectionFileExists
5694
+ });
5695
+ displayInstallationPlan(
5696
+ operations.filter((op) => op.type === "copy"),
5697
+ config.injection.targetFile
5698
+ );
5699
+ if (opts.dryRun) {
5700
+ return {
5701
+ success: true,
5702
+ message: chalk12.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk12.dim(`Would install ${operations.length} files.`)
5703
+ };
5704
+ }
5705
+ const copyOps = operations.filter((op) => op.type === "copy");
5706
+ const result = await installFiles(copyOps, opts.force || false);
5707
+ if (result.cancelled) {
5708
+ return {
5709
+ success: false,
5710
+ message: chalk12.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk12.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
5711
+ code: "CANCELLED"
5712
+ };
5713
+ }
5714
+ try {
5715
+ const content = await fetchFileFromGitHub(config.injection.sourceFile);
5716
+ await injectContentIntoFile(config.injection.targetFile, content);
5717
+ } catch (error) {
5718
+ console.error(
5719
+ chalk12.red(`Failed to inject content into ${config.injection.targetFile}:`),
5720
+ error instanceof Error ? error.message : String(error)
5721
+ );
5722
+ }
5723
+ let statusLineInstalled = false;
5724
+ if (config.name === "Claude Code") {
5725
+ try {
5726
+ const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
5727
+ await installStatusLineScript(scriptContent);
5728
+ await updateClaudeSettings();
5729
+ statusLineInstalled = true;
5730
+ } catch (error) {
5731
+ console.error(
5732
+ chalk12.yellow("\u26A0\uFE0F Failed to install status line script:"),
5733
+ error instanceof Error ? error.message : String(error)
5734
+ );
5735
+ }
5736
+ }
5737
+ const statusLineMessage = statusLineInstalled ? chalk12.dim(" Status line: .claude/statusline.sh\n") : "";
5738
+ return {
5739
+ success: true,
5740
+ message: chalk12.green(`\u2705 ${config.name} integration installed successfully!
5741
+
5742
+ `) + chalk12.dim("Files installed:\n") + chalk12.dim(` Commands: ${result.installed} files
5743
+ `) + statusLineMessage + chalk12.dim(` Content injected into: ${config.injection.targetFile}
5744
+
5745
+ `) + chalk12.dim("Next steps:\n") + chalk12.dim(" 1. Review the integration files\n") + chalk12.dim(` 2. Open ${config.name}
5746
+ `) + chalk12.dim(" 3. Try the /specify or /breakdown commands\n") + chalk12.dim(" 4. Learn more: ") + chalk12.cyan(config.docsUrl)
5747
+ };
5748
+ } catch (error) {
5749
+ const errorMessage = error instanceof Error ? error.message : String(error);
5750
+ return {
5751
+ success: false,
5752
+ message: chalk12.red(`\u274C Setup failed: ${errorMessage}`)
5753
+ };
5754
+ }
5755
+ }
5756
+ async function handleSetupClaudeCode(opts) {
5757
+ const config = {
5758
+ name: "Claude Code",
5759
+ sourceDirs: ["claude-code/commands", "claude-code/skills"],
5760
+ targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
5761
+ injection: {
5762
+ sourceFile: "claude-code/CLAUDE.md",
5763
+ targetFile: "CLAUDE.md"
5764
+ },
5765
+ docsUrl: "https://braingrid.ai/docs/claude-code"
5766
+ };
5767
+ return _handleSetup(config, opts);
5768
+ }
5769
+ async function handleSetupCursor(opts) {
5770
+ const config = {
5771
+ name: "Cursor",
5772
+ sourceDirs: ["cursor/commands", "cursor/rules"],
5773
+ targetDirs: [".cursor/commands", ".cursor/rules"],
5774
+ injection: {
5775
+ sourceFile: "cursor/AGENTS.md",
5776
+ targetFile: "AGENTS.md"
5777
+ },
5778
+ docsUrl: "https://braingrid.ai/docs/cursor"
5779
+ };
5780
+ return _handleSetup(config, opts);
5781
+ }
5782
+
5783
+ // src/handlers/init.handlers.ts
5784
+ import { access as access3 } from "fs/promises";
5785
+
5786
+ // src/utils/github-repo.ts
5787
+ import { exec as exec4 } from "child_process";
5788
+ import { promisify as promisify4 } from "util";
5789
+ import path5 from "path";
5790
+ var execAsync4 = promisify4(exec4);
5791
+ async function initGitRepo() {
5792
+ try {
5793
+ await execAsync4("git init");
5794
+ return true;
5795
+ } catch {
5796
+ return false;
5797
+ }
5798
+ }
5799
+ async function isGhAuthenticated() {
5800
+ try {
5801
+ await execAsync4("gh auth status");
5802
+ return true;
5803
+ } catch {
5804
+ return false;
5805
+ }
5806
+ }
5807
+ function getCurrentDirectoryName() {
5808
+ return path5.basename(process.cwd());
5809
+ }
5810
+ async function createGitHubRepoWithGh(name, isPrivate) {
5811
+ try {
5812
+ const visibility = isPrivate ? "--private" : "--public";
5813
+ const { stdout } = await execAsync4(
5814
+ `gh repo create ${name} ${visibility} --source=. --remote=origin`,
5815
+ { timeout: 6e4 }
5816
+ );
5817
+ const urlMatch = stdout.match(/https:\/\/github\.com\/([^/]+)\/([^/\s]+)/);
5818
+ if (!urlMatch) {
5819
+ return null;
5820
+ }
5821
+ return {
5822
+ owner: urlMatch[1],
5823
+ name: urlMatch[2],
5824
+ url: urlMatch[0]
5825
+ };
5826
+ } catch {
5827
+ return null;
5828
+ }
5829
+ }
5830
+ async function canUseGhAutomation() {
5831
+ const { isGhInstalled: isGhInstalled2 } = await import("./gh-installer-LNR25TO7.js");
5832
+ const ghInstalled = await isGhInstalled2();
5833
+ if (!ghInstalled) {
5834
+ return false;
5835
+ }
5836
+ return isGhAuthenticated();
5837
+ }
5838
+
5839
+ // src/handlers/init.handlers.ts
5840
+ async function detectInstalledIDEs() {
5841
+ const tools = await checkInstalledCliTools();
5842
+ return {
5843
+ claudeCode: tools.find((t) => t.name === "Claude Code")?.installed || false,
5844
+ cursor: tools.find((t) => t.name === "Cursor")?.installed || false
5845
+ };
5846
+ }
5847
+ async function fileExists2(filePath) {
5848
+ try {
5849
+ await access3(filePath);
5850
+ return true;
5851
+ } catch {
5852
+ return false;
5853
+ }
5854
+ }
5855
+ function getServices4() {
5856
+ const config = getConfig();
5857
+ const auth = new BraingridAuth(config.apiUrl);
5858
+ const projectService = new ProjectService(config.apiUrl, auth);
5859
+ const githubService = new GitHubService(config.apiUrl, auth);
5860
+ const repositoryService = new RepositoryService(config.apiUrl, auth);
5861
+ return { projectService, githubService, repositoryService, auth };
5862
+ }
5863
+ function promptToAddOrganization(owner, webUrl) {
5864
+ return {
5865
+ success: false,
5866
+ message: chalk13.yellow("\u26A0\uFE0F No projects found for this repository.\n\n") + chalk13.dim(`Repository: ${owner}/*
5867
+
5868
+ `) + chalk13.dim(`You have GitHub connected, but not for the "${owner}" organization.
5869
+
5870
+ `) + chalk13.dim("To connect ") + chalk13.cyan(owner) + chalk13.dim(":\n") + chalk13.dim(" 1. Visit: ") + chalk13.cyan(`${webUrl}/integrations`) + chalk13.dim("\n") + chalk13.dim(' 2. Click "Add GitHub Organization"\n') + chalk13.dim(` 3. Select "${owner}"
5871
+ `) + chalk13.dim(" 4. Run ") + chalk13.cyan("braingrid init") + chalk13.dim(" again")
5872
+ };
5873
+ }
5874
+ async function promptToCreateProject(gitInfo, projectService, repositoryService) {
5875
+ const webUrl = getConfig().getWebAppUrl();
5876
+ if (!gitInfo.owner || !gitInfo.name) {
5877
+ return {
5878
+ success: false,
5879
+ message: chalk13.red("\u274C Repository information is incomplete")
5880
+ };
5881
+ }
5882
+ const repositoryId = await getRepositoryId(repositoryService, gitInfo.owner, gitInfo.name);
5883
+ if (!repositoryId) {
5884
+ return {
5885
+ success: false,
5886
+ message: chalk13.yellow("\u26A0\uFE0F Repository accessible but could not retrieve details.\n\n") + chalk13.dim(`Repository: ${gitInfo.owner}/${gitInfo.name}
5887
+
5888
+ `) + chalk13.dim("Please try again or create a project manually at: ") + chalk13.cyan(webUrl)
5889
+ };
5890
+ }
5891
+ console.log(
5892
+ chalk13.yellow("\u26A0\uFE0F Repository accessible but no project exists.\n\n") + chalk13.dim(`Repository: ${gitInfo.owner}/${gitInfo.name}
5893
+ `)
5894
+ );
5895
+ const shouldCreate = await confirm({
5896
+ message: `Would you like to create a project for ${gitInfo.name} now?`,
5897
+ default: true
5898
+ });
5464
5899
  if (!shouldCreate) {
5465
5900
  return {
5466
5901
  success: false,
5467
- message: chalk12.dim("\nProject creation cancelled.\n\n") + chalk12.dim("Create a project at ") + chalk12.cyan(webUrl) + chalk12.dim(" and link it to this repository, or use:\n") + chalk12.cyan(
5902
+ message: chalk13.dim("\nProject creation cancelled.\n\n") + chalk13.dim("Create a project at ") + chalk13.cyan(webUrl) + chalk13.dim(" and link it to this repository, or use:\n") + chalk13.cyan(
5468
5903
  `braingrid project create --name "${gitInfo.name}" --repositories "${gitInfo.owner}/${gitInfo.name}"`
5469
5904
  )
5470
5905
  };
@@ -5475,9 +5910,9 @@ async function promptToCreateProject(gitInfo, projectService, repositoryService)
5475
5910
  description: `Project for ${gitInfo.owner}/${gitInfo.name}`,
5476
5911
  repository_id: repositoryId
5477
5912
  });
5478
- console.log(chalk12.green(`
5913
+ console.log(chalk13.green(`
5479
5914
  \u2705 Created project ${project2.short_id}: ${project2.name}`));
5480
- console.log(chalk12.green(`\u2705 Linked repository ${gitInfo.owner}/${gitInfo.name}
5915
+ console.log(chalk13.green(`\u2705 Linked repository ${gitInfo.owner}/${gitInfo.name}
5481
5916
  `));
5482
5917
  return { success: true, message: "", data: project2 };
5483
5918
  } catch (error) {
@@ -5491,18 +5926,18 @@ async function promptToGrantRepositoryAccess(gitInfo, webUrl, repositoryService,
5491
5926
  if (!gitInfo.owner || !gitInfo.name) {
5492
5927
  return {
5493
5928
  success: false,
5494
- message: chalk12.red("\u274C Repository information is incomplete")
5929
+ message: chalk13.red("\u274C Repository information is incomplete")
5495
5930
  };
5496
5931
  }
5497
5932
  const owner = gitInfo.owner;
5498
5933
  const name = gitInfo.name;
5499
5934
  console.log(
5500
- chalk12.yellow("\u26A0\uFE0F Repository found but BrainGrid needs access.\n\n") + chalk12.dim(`Repository: ${owner}/${name}
5935
+ chalk13.yellow("\u26A0\uFE0F Repository found but BrainGrid needs access.\n\n") + chalk13.dim(`Repository: ${owner}/${name}
5501
5936
 
5502
- `) + chalk12.dim("Please grant BrainGrid access to this repository:\n") + chalk12.dim(" 1. Visit: ") + chalk12.cyan(`${webUrl}/integrations`) + chalk12.dim("\n") + chalk12.dim(
5937
+ `) + chalk13.dim("Please grant BrainGrid access to this repository:\n") + chalk13.dim(" 1. Visit: ") + chalk13.cyan(`${webUrl}/integrations`) + chalk13.dim("\n") + chalk13.dim(
5503
5938
  ` 2. Click on your "${owner}" installation "Add/Remove" to grant BrainGrid access to your repository
5504
5939
  `
5505
- ) + chalk12.dim(` 3. Select "${name}" and save
5940
+ ) + chalk13.dim(` 3. Select "${name}" and save
5506
5941
 
5507
5942
  `)
5508
5943
  );
@@ -5515,10 +5950,10 @@ async function promptToGrantRepositoryAccess(gitInfo, webUrl, repositoryService,
5515
5950
  if (!accessGranted) {
5516
5951
  return {
5517
5952
  success: false,
5518
- message: chalk12.yellow("\n\u26A0\uFE0F Repository access not detected within 3 minutes.\n\n") + chalk12.dim("Please grant access at: ") + chalk12.cyan(`${webUrl}/integrations`) + chalk12.dim(" and run ") + chalk12.cyan("braingrid init") + chalk12.dim(" again.")
5953
+ message: chalk13.yellow("\n\u26A0\uFE0F Repository access not detected within 3 minutes.\n\n") + chalk13.dim("Please grant access at: ") + chalk13.cyan(`${webUrl}/integrations`) + chalk13.dim(" and run ") + chalk13.cyan("braingrid init") + chalk13.dim(" again.")
5519
5954
  };
5520
5955
  }
5521
- console.log(chalk12.green("\u2705 Repository access granted!\n"));
5956
+ console.log(chalk13.green("\u2705 Repository access granted!\n"));
5522
5957
  return promptToCreateProject(gitInfo, projectService, repositoryService);
5523
5958
  }
5524
5959
  async function handleNoProjectForRepository(owner, name, gitInfo, githubService, repositoryService, projectService, config) {
@@ -5533,9 +5968,9 @@ async function handleNoProjectForRepository(owner, name, gitInfo, githubService,
5533
5968
  if (allInstallations.length === 0) {
5534
5969
  return {
5535
5970
  success: false,
5536
- message: chalk12.yellow("\u26A0\uFE0F No projects found for this repository.\n\n") + chalk12.dim(`Repository: ${owner}/${name}
5971
+ message: chalk13.yellow("\u26A0\uFE0F No projects found for this repository.\n\n") + chalk13.dim(`Repository: ${owner}/${name}
5537
5972
 
5538
- `) + chalk12.dim("It looks like you haven't connected your GitHub account yet.\n") + chalk12.dim("Please connect GitHub at: ") + chalk12.cyan(`${webUrl}/integrations`) + chalk12.dim("\n\nOnce connected, create a project and link it to this repository.")
5973
+ `) + chalk13.dim("It looks like you haven't connected your GitHub account yet.\n") + chalk13.dim("Please connect GitHub at: ") + chalk13.cyan(`${webUrl}/integrations`) + chalk13.dim("\n\nOnce connected, create a project and link it to this repository.")
5539
5974
  };
5540
5975
  }
5541
5976
  const ownerInstallation = findInstallationForOwner(owner, allInstallations);
@@ -5551,28 +5986,28 @@ async function handleNoProjectForRepository(owner, name, gitInfo, githubService,
5551
5986
  function showSetupInstructions(scenario) {
5552
5987
  let message = "";
5553
5988
  if (scenario === "no-git") {
5554
- message += chalk12.dim("To initialize BrainGrid locally:\n\n");
5555
- message += chalk12.dim(" 1. Initialize git:\n");
5556
- message += chalk12.cyan(" git init\n\n");
5989
+ message += chalk13.dim("To initialize BrainGrid locally:\n\n");
5990
+ message += chalk13.dim(" 1. Initialize git:\n");
5991
+ message += chalk13.cyan(" git init\n\n");
5557
5992
  } else {
5558
- message += chalk12.dim("To connect to GitHub:\n\n");
5993
+ message += chalk13.dim("To connect to GitHub:\n\n");
5559
5994
  }
5560
- message += chalk12.dim(" 2. Create GitHub repository:\n");
5561
- message += chalk12.dim(" \u2022 Install GitHub CLI: ") + chalk12.cyan("https://cli.github.com\n");
5562
- message += chalk12.dim(" Then: ") + chalk12.cyan("gh repo create --private --source=.\n");
5563
- message += chalk12.dim(" \u2022 Or manually: ") + chalk12.cyan("https://github.com/new\n\n");
5995
+ message += chalk13.dim(" 2. Create GitHub repository:\n");
5996
+ message += chalk13.dim(" \u2022 Install GitHub CLI: ") + chalk13.cyan("https://cli.github.com\n");
5997
+ message += chalk13.dim(" Then: ") + chalk13.cyan("gh repo create --private --source=.\n");
5998
+ message += chalk13.dim(" \u2022 Or manually: ") + chalk13.cyan("https://github.com/new\n\n");
5564
5999
  if (scenario === "no-git") {
5565
- message += chalk12.dim(" 3. Run: ") + chalk12.cyan("braingrid init\n\n");
6000
+ message += chalk13.dim(" 3. Run: ") + chalk13.cyan("braingrid init\n\n");
5566
6001
  } else {
5567
- message += chalk12.dim(" 3. Add remote and run init:\n");
5568
- message += chalk12.cyan(" git remote add origin <url>\n");
5569
- message += chalk12.cyan(" braingrid init\n\n");
5570
- }
5571
- message += chalk12.bold("Or use BrainGrid without local initialization:\n\n");
5572
- message += chalk12.dim(" All commands support the --project flag:\n");
5573
- message += chalk12.cyan(" braingrid requirement list --project PROJ-123\n");
5574
- message += chalk12.cyan(' braingrid task create --project PROJ-123 --title "Task"\n\n');
5575
- message += chalk12.dim(" Note: Without local init, you must specify --project for each command.") + "\n";
6002
+ message += chalk13.dim(" 3. Add remote and run init:\n");
6003
+ message += chalk13.cyan(" git remote add origin <url>\n");
6004
+ message += chalk13.cyan(" braingrid init\n\n");
6005
+ }
6006
+ message += chalk13.bold("Or use BrainGrid without local initialization:\n\n");
6007
+ message += chalk13.dim(" All commands support the --project flag:\n");
6008
+ message += chalk13.cyan(" braingrid requirement list --project PROJ-123\n");
6009
+ message += chalk13.cyan(' braingrid task create --project PROJ-123 --title "Task"\n\n');
6010
+ message += chalk13.dim(" Note: Without local init, you must specify --project for each command.") + "\n";
5576
6011
  return message;
5577
6012
  }
5578
6013
  async function handleNoGitRepository() {
@@ -5588,12 +6023,12 @@ async function handleNoGitRepository() {
5588
6023
  if (!gitInitSuccess) {
5589
6024
  return {
5590
6025
  success: false,
5591
- message: chalk12.red("\u274C Failed to initialize git repository")
6026
+ message: chalk13.red("\u274C Failed to initialize git repository")
5592
6027
  };
5593
6028
  }
5594
- console.log(chalk12.green("\u2705 Initialized git repository"));
6029
+ console.log(chalk13.green("\u2705 Initialized git repository"));
5595
6030
  const dirName = getCurrentDirectoryName();
5596
- const isPrivate = await select({
6031
+ const isPrivate = await select2({
5597
6032
  message: "Repository visibility:",
5598
6033
  choices: [
5599
6034
  { value: true, name: "Private" },
@@ -5605,16 +6040,16 @@ async function handleNoGitRepository() {
5605
6040
  message: "Repository name:",
5606
6041
  default: dirName
5607
6042
  });
5608
- console.log(chalk12.dim("\nCreating repository...\n"));
6043
+ console.log(chalk13.dim("\nCreating repository...\n"));
5609
6044
  const repo = await createGitHubRepoWithGh(repoName, isPrivate);
5610
6045
  if (!repo) {
5611
6046
  return {
5612
6047
  success: false,
5613
- message: chalk12.red("\u274C Failed to create GitHub repository\n\n") + showSetupInstructions("no-git")
6048
+ message: chalk13.red("\u274C Failed to create GitHub repository\n\n") + showSetupInstructions("no-git")
5614
6049
  };
5615
6050
  }
5616
- console.log(chalk12.green(`\u2705 Created repository: ${repo.url}`));
5617
- console.log(chalk12.green("\u2705 Added remote origin\n"));
6051
+ console.log(chalk13.green(`\u2705 Created repository: ${repo.url}`));
6052
+ console.log(chalk13.green("\u2705 Added remote origin\n"));
5618
6053
  return {
5619
6054
  success: true,
5620
6055
  message: "continue-init",
@@ -5625,7 +6060,7 @@ async function handleNoGitRepository() {
5625
6060
  }
5626
6061
  return {
5627
6062
  success: false,
5628
- message: chalk12.yellow("\u26A0\uFE0F This directory is not a git repository.\n\n") + showSetupInstructions("no-git")
6063
+ message: chalk13.yellow("\u26A0\uFE0F This directory is not a git repository.\n\n") + showSetupInstructions("no-git")
5629
6064
  };
5630
6065
  }
5631
6066
  async function handleNoGitRemote() {
@@ -5637,7 +6072,7 @@ async function handleNoGitRemote() {
5637
6072
  });
5638
6073
  if (shouldCreate) {
5639
6074
  const dirName = getCurrentDirectoryName();
5640
- const isPrivate = await select({
6075
+ const isPrivate = await select2({
5641
6076
  message: "Repository visibility:",
5642
6077
  choices: [
5643
6078
  { value: true, name: "Private" },
@@ -5649,16 +6084,16 @@ async function handleNoGitRemote() {
5649
6084
  message: "Repository name:",
5650
6085
  default: dirName
5651
6086
  });
5652
- console.log(chalk12.dim("\nCreating repository...\n"));
6087
+ console.log(chalk13.dim("\nCreating repository...\n"));
5653
6088
  const repo = await createGitHubRepoWithGh(repoName, isPrivate);
5654
6089
  if (!repo) {
5655
6090
  return {
5656
6091
  success: false,
5657
- message: chalk12.red("\u274C Failed to create GitHub repository\n\n") + showSetupInstructions("no-remote")
6092
+ message: chalk13.red("\u274C Failed to create GitHub repository\n\n") + showSetupInstructions("no-remote")
5658
6093
  };
5659
6094
  }
5660
- console.log(chalk12.green(`\u2705 Created repository: ${repo.url}`));
5661
- console.log(chalk12.green("\u2705 Added remote origin\n"));
6095
+ console.log(chalk13.green(`\u2705 Created repository: ${repo.url}`));
6096
+ console.log(chalk13.green("\u2705 Added remote origin\n"));
5662
6097
  return {
5663
6098
  success: true,
5664
6099
  message: "continue-init",
@@ -5669,7 +6104,7 @@ async function handleNoGitRemote() {
5669
6104
  }
5670
6105
  return {
5671
6106
  success: false,
5672
- message: chalk12.yellow("\u26A0\uFE0F Git repository detected but no GitHub remote configured.\n\n") + showSetupInstructions("no-remote")
6107
+ message: chalk13.yellow("\u26A0\uFE0F Git repository detected but no GitHub remote configured.\n\n") + showSetupInstructions("no-remote")
5673
6108
  };
5674
6109
  }
5675
6110
  async function handleInit(opts) {
@@ -5684,7 +6119,7 @@ async function handleInit(opts) {
5684
6119
  if (!shouldInstall) {
5685
6120
  return {
5686
6121
  success: false,
5687
- message: chalk12.yellow("\u26A0\uFE0F Git installation cancelled.\n\n") + getManualInstallInstructions()
6122
+ message: chalk13.yellow("\u26A0\uFE0F Git installation cancelled.\n\n") + getManualInstallInstructions()
5688
6123
  };
5689
6124
  }
5690
6125
  console.log();
@@ -5700,14 +6135,14 @@ async function handleInit(opts) {
5700
6135
  if (!await isGitInstalled()) {
5701
6136
  return {
5702
6137
  success: false,
5703
- message: chalk12.red("\u274C Git installation completed but git command not found\n\n") + chalk12.dim("You may need to restart your terminal or add Git to your PATH.\n") + getManualInstallInstructions()
6138
+ message: chalk13.red("\u274C Git installation completed but git command not found\n\n") + chalk13.dim("You may need to restart your terminal or add Git to your PATH.\n") + getManualInstallInstructions()
5704
6139
  };
5705
6140
  }
5706
6141
  }
5707
6142
  if (!await isGhInstalled()) {
5708
- console.log(chalk12.blue("\n\u{1F4A1} GitHub CLI is highly recommended for working with BrainGrid."));
6143
+ console.log(chalk13.blue("\n\u{1F4A1} GitHub CLI is highly recommended for working with BrainGrid."));
5709
6144
  console.log(
5710
- chalk12.dim(" It enables seamless GitHub integration and repository management.\n")
6145
+ chalk13.dim(" It enables seamless GitHub integration and repository management.\n")
5711
6146
  );
5712
6147
  const shouldInstallGh = await confirm({
5713
6148
  message: "Would you like to install GitHub CLI now?",
@@ -5721,10 +6156,10 @@ async function handleInit(opts) {
5721
6156
  console.log();
5722
6157
  } else {
5723
6158
  console.log(ghInstallResult.message);
5724
- console.log(chalk12.dim("You can install it manually later.\n"));
6159
+ console.log(chalk13.dim("You can install it manually later.\n"));
5725
6160
  }
5726
6161
  } else {
5727
- console.log(chalk12.dim("Skipping GitHub CLI installation.\n"));
6162
+ console.log(chalk13.dim("Skipping GitHub CLI installation.\n"));
5728
6163
  }
5729
6164
  }
5730
6165
  if (await projectConfigExists() && !opts.force) {
@@ -5732,15 +6167,15 @@ async function handleInit(opts) {
5732
6167
  const existing = await loadProjectConfig();
5733
6168
  return {
5734
6169
  success: false,
5735
- message: chalk12.yellow("\u26A0\uFE0F Already initialized.\n\n") + chalk12.dim(`Project: ${existing.project_name} (${existing.project_short_id})
5736
- `) + chalk12.dim(`Repository: ${existing.repository?.full_name || "N/A"}
6170
+ message: chalk13.yellow("\u26A0\uFE0F Already initialized.\n\n") + chalk13.dim(`Project: ${existing.project_name} (${existing.project_short_id})
6171
+ `) + chalk13.dim(`Repository: ${existing.repository?.full_name || "N/A"}
5737
6172
 
5738
- `) + chalk12.dim("Use --force to reinitialize")
6173
+ `) + chalk13.dim("Use --force to reinitialize")
5739
6174
  };
5740
6175
  } catch {
5741
6176
  return {
5742
6177
  success: false,
5743
- message: chalk12.yellow("\u26A0\uFE0F Invalid project configuration found.\n") + chalk12.dim("Use --force to reinitialize")
6178
+ message: chalk13.yellow("\u26A0\uFE0F Invalid project configuration found.\n") + chalk13.dim("Use --force to reinitialize")
5744
6179
  };
5745
6180
  }
5746
6181
  }
@@ -5753,7 +6188,7 @@ async function handleInit(opts) {
5753
6188
  if (!shouldLogin) {
5754
6189
  return {
5755
6190
  success: false,
5756
- message: chalk12.yellow("\u26A0\uFE0F Authentication required.\n") + chalk12.dim("Run ") + chalk12.cyan("braingrid login") + chalk12.dim(" when you're ready to authenticate.")
6191
+ message: chalk13.yellow("\u26A0\uFE0F Authentication required.\n") + chalk13.dim("Run ") + chalk13.cyan("braingrid login") + chalk13.dim(" when you're ready to authenticate.")
5757
6192
  };
5758
6193
  }
5759
6194
  console.log();
@@ -5761,13 +6196,13 @@ async function handleInit(opts) {
5761
6196
  if (!loginResult.success) {
5762
6197
  return {
5763
6198
  success: false,
5764
- message: chalk12.red("\u274C Login failed.\n") + chalk12.dim("Please try running ") + chalk12.cyan("braingrid login") + chalk12.dim(" again.")
6199
+ message: chalk13.red("\u274C Login failed.\n") + chalk13.dim("Please try running ") + chalk13.cyan("braingrid login") + chalk13.dim(" again.")
5765
6200
  };
5766
6201
  }
5767
6202
  if (!await auth.isAuthenticated()) {
5768
6203
  return {
5769
6204
  success: false,
5770
- message: chalk12.red("\u274C Login was not completed.\n") + chalk12.dim("Please try running ") + chalk12.cyan("braingrid login") + chalk12.dim(" again.")
6205
+ message: chalk13.red("\u274C Login was not completed.\n") + chalk13.dim("Please try running ") + chalk13.cyan("braingrid login") + chalk13.dim(" again.")
5771
6206
  };
5772
6207
  }
5773
6208
  console.log();
@@ -5776,7 +6211,7 @@ async function handleInit(opts) {
5776
6211
  if (!session) {
5777
6212
  return {
5778
6213
  success: false,
5779
- message: chalk12.red("\u274C No session found. Please run `braingrid login` first.")
6214
+ message: chalk13.red("\u274C No session found. Please run `braingrid login` first.")
5780
6215
  };
5781
6216
  }
5782
6217
  let gitInfo = await getGitRepositoryInfo();
@@ -5787,9 +6222,9 @@ async function handleInit(opts) {
5787
6222
  } catch {
5788
6223
  return {
5789
6224
  success: false,
5790
- message: chalk12.red(`\u274C Project not found: ${opts.project}
6225
+ message: chalk13.red(`\u274C Project not found: ${opts.project}
5791
6226
 
5792
- `) + chalk12.dim("Make sure the project ID is correct and you have access to it.")
6227
+ `) + chalk13.dim("Make sure the project ID is correct and you have access to it.")
5793
6228
  };
5794
6229
  }
5795
6230
  } else {
@@ -5800,7 +6235,7 @@ async function handleInit(opts) {
5800
6235
  if (!gitInfo || !gitInfo.owner || !gitInfo.name) {
5801
6236
  return {
5802
6237
  success: false,
5803
- message: chalk12.red("\u274C Failed to get repository information after setup")
6238
+ message: chalk13.red("\u274C Failed to get repository information after setup")
5804
6239
  };
5805
6240
  }
5806
6241
  } else {
@@ -5814,7 +6249,7 @@ async function handleInit(opts) {
5814
6249
  if (!gitInfo || !gitInfo.owner || !gitInfo.name) {
5815
6250
  return {
5816
6251
  success: false,
5817
- message: chalk12.red("\u274C Failed to get repository information after setup")
6252
+ message: chalk13.red("\u274C Failed to get repository information after setup")
5818
6253
  };
5819
6254
  }
5820
6255
  } else {
@@ -5824,7 +6259,7 @@ async function handleInit(opts) {
5824
6259
  if (!gitInfo) {
5825
6260
  return {
5826
6261
  success: false,
5827
- message: chalk12.red("\u274C Repository information is missing")
6262
+ message: chalk13.red("\u274C Repository information is missing")
5828
6263
  };
5829
6264
  }
5830
6265
  const owner = gitInfo.owner;
@@ -5832,7 +6267,7 @@ async function handleInit(opts) {
5832
6267
  if (!owner || !name) {
5833
6268
  return {
5834
6269
  success: false,
5835
- message: chalk12.red("\u274C Repository information is incomplete")
6270
+ message: chalk13.red("\u274C Repository information is incomplete")
5836
6271
  };
5837
6272
  }
5838
6273
  let response;
@@ -5871,7 +6306,7 @@ async function handleInit(opts) {
5871
6306
  }
5872
6307
  project2 = response.projects[0];
5873
6308
  }
5874
- const projectInfo = chalk12.bold("\n\u{1F4E6} BrainGrid Project Found\n\n") + chalk12.dim("Project: ") + chalk12.cyan(project2.name) + "\n" + chalk12.dim("ID: ") + chalk12.gray(project2.short_id) + "\n" + (project2.description ? chalk12.dim("Description: ") + chalk12.gray(project2.description) + "\n" : "") + chalk12.dim("Repository: ") + chalk12.gray(project2.repository?.full_name || "N/A") + "\n\n";
6309
+ const projectInfo = chalk13.bold("\n\u{1F4E6} BrainGrid Project Found\n\n") + chalk13.dim("Project: ") + chalk13.cyan(project2.name) + "\n" + chalk13.dim("ID: ") + chalk13.gray(project2.short_id) + "\n" + (project2.description ? chalk13.dim("Description: ") + chalk13.gray(project2.description) + "\n" : "") + chalk13.dim("Repository: ") + chalk13.gray(project2.repository?.full_name || "N/A") + "\n\n";
5875
6310
  console.log(projectInfo);
5876
6311
  if (!opts.force) {
5877
6312
  const shouldInit = await confirm({
@@ -5881,7 +6316,7 @@ async function handleInit(opts) {
5881
6316
  if (!shouldInit) {
5882
6317
  return {
5883
6318
  success: false,
5884
- message: chalk12.yellow("Initialization cancelled.")
6319
+ message: chalk13.yellow("Initialization cancelled.")
5885
6320
  };
5886
6321
  }
5887
6322
  }
@@ -5892,651 +6327,292 @@ async function handleInit(opts) {
5892
6327
  project_name: project2.name,
5893
6328
  project_description: project2.description || null,
5894
6329
  repository: project2.repository ? {
5895
- id: project2.repository.id,
5896
- owner: project2.repository.full_name.split("/")[0],
5897
- // Parse owner from API full_name
5898
- name: project2.repository.name,
5899
- // Use API name, not git name
5900
- full_name: project2.repository.full_name,
5901
- url: gitInfo?.remoteUrl || void 0
5902
- } : null,
5903
- created_at: project2.created_at
5904
- };
5905
- await saveProjectConfig(localConfig);
5906
- return {
5907
- success: true,
5908
- message: chalk12.green("\u2705 Repository initialized successfully!\n\n") + chalk12.dim("Project: ") + chalk12.cyan(project2.name) + chalk12.dim(` (${project2.short_id})`) + "\n" + chalk12.dim("Config: ") + chalk12.gray(".braingrid/project.json") + "\n\n" + chalk12.dim("You can now use project-scoped commands without specifying a project ID."),
5909
- data: localConfig
5910
- };
5911
- } catch (error) {
5912
- return {
5913
- success: false,
5914
- message: formatError(error, "initializing repository")
5915
- };
5916
- }
5917
- }
5918
-
5919
- // src/handlers/update.handlers.ts
5920
- import chalk13 from "chalk";
5921
- import axios5 from "axios";
5922
-
5923
- // src/utils/package-manager.ts
5924
- import { execSync } from "child_process";
5925
- import { select as select2 } from "@inquirer/prompts";
5926
- function isPackageManagerInstalled(pm) {
5927
- try {
5928
- execSync(`which ${pm}`, { stdio: "ignore", timeout: 2e3 });
5929
- return true;
5930
- } catch {
5931
- return false;
5932
- }
5933
- }
5934
- function checkGlobalInstallation(pm, packageName) {
5935
- try {
5936
- let command;
5937
- switch (pm) {
5938
- case "npm": {
5939
- command = `npm list -g ${packageName} 2>&1`;
5940
- break;
5941
- }
5942
- case "pnpm": {
5943
- command = `pnpm list -g ${packageName} 2>&1`;
5944
- break;
5945
- }
5946
- case "yarn": {
5947
- command = `yarn global list 2>&1`;
5948
- break;
5949
- }
5950
- }
5951
- const output = execSync(command, {
5952
- encoding: "utf-8",
5953
- timeout: 5e3
5954
- });
5955
- if (pm === "npm" || pm === "pnpm") {
5956
- return output.includes(packageName) && !output.includes("(empty)");
5957
- }
5958
- if (pm === "yarn") {
5959
- return output.includes(packageName);
5960
- }
5961
- return false;
5962
- } catch {
5963
- return false;
5964
- }
5965
- }
5966
- async function getPackageManagerInfo(packageName) {
5967
- const packageManagers = ["npm", "pnpm", "yarn"];
5968
- return packageManagers.map((pm) => {
5969
- const installed = isPackageManagerInstalled(pm);
5970
- return {
5971
- name: pm,
5972
- installed,
5973
- hasPackage: installed ? checkGlobalInstallation(pm, packageName) : false
5974
- };
5975
- });
5976
- }
5977
- async function detectPackageManager(packageName) {
5978
- const pmInfo = await getPackageManagerInfo(packageName);
5979
- const installedWith = pmInfo.find((pm) => pm.hasPackage);
5980
- if (installedWith) {
5981
- return installedWith.name;
5982
- }
5983
- const availablePMs = pmInfo.filter((pm) => pm.installed);
5984
- if (availablePMs.length === 0) {
5985
- throw new Error("No package manager found. Please install npm, pnpm, or yarn.");
5986
- }
5987
- if (availablePMs.length === 1) {
5988
- return availablePMs[0].name;
5989
- }
5990
- const selected = await select2({
5991
- message: "Unable to detect which package manager was used. Please select one:",
5992
- choices: availablePMs.map((pm) => ({
5993
- name: pm.name,
5994
- value: pm.name
5995
- }))
5996
- });
5997
- return selected;
5998
- }
5999
- function getUpdateCommand(pm, packageName) {
6000
- switch (pm) {
6001
- case "npm": {
6002
- return `npm install -g ${packageName}@latest`;
6003
- }
6004
- case "pnpm": {
6005
- return `pnpm add -g ${packageName}@latest`;
6006
- }
6007
- case "yarn": {
6008
- return `yarn global add ${packageName}@latest`;
6009
- }
6010
- }
6011
- }
6012
- function executeUpdate(pm, packageName) {
6013
- const command = getUpdateCommand(pm, packageName);
6014
- try {
6015
- execSync(command, {
6016
- stdio: "inherit",
6017
- timeout: 12e4
6018
- // 2 minutes timeout
6019
- });
6020
- } catch (error) {
6021
- throw new Error(
6022
- `Failed to update package: ${error instanceof Error ? error.message : String(error)}`
6023
- );
6024
- }
6025
- }
6026
-
6027
- // src/handlers/update.handlers.ts
6028
- var PACKAGE_NAME = "@braingrid/cli";
6029
- var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
6030
- function getCurrentVersion() {
6031
- return CLI_VERSION;
6032
- }
6033
- async function getLatestVersion() {
6034
- try {
6035
- const response = await axios5.get(NPM_REGISTRY_URL, {
6036
- timeout: 1e4
6037
- });
6038
- return response.data["dist-tags"].latest;
6039
- } catch (error) {
6040
- throw new Error(`Failed to fetch latest version: ${formatError(error)}`);
6041
- }
6042
- }
6043
- function compareVersions(v1, v2) {
6044
- const v1Parts = v1.split(".").map(Number);
6045
- const v2Parts = v2.split(".").map(Number);
6046
- for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
6047
- const v1Part = v1Parts[i] || 0;
6048
- const v2Part = v2Parts[i] || 0;
6049
- if (v1Part < v2Part) return -1;
6050
- if (v1Part > v2Part) return 1;
6051
- }
6052
- return 0;
6053
- }
6054
- async function handleUpdate(opts) {
6055
- try {
6056
- const currentVersion = getCurrentVersion();
6057
- let output = chalk13.bold.cyan("\n\u{1F504} BrainGrid CLI Update\n\n");
6058
- output += `${chalk13.bold("Current version:")} ${currentVersion}
6059
- `;
6060
- output += chalk13.dim("Checking for updates...\n");
6061
- const latestVersion = await getLatestVersion();
6062
- output += `${chalk13.bold("Latest version:")} ${latestVersion}
6063
-
6064
- `;
6065
- const comparison = compareVersions(currentVersion, latestVersion);
6066
- if (comparison === 0) {
6067
- output += chalk13.green("\u2705 You are already on the latest version!\n");
6068
- return {
6069
- success: true,
6070
- message: output,
6071
- data: { currentVersion, latestVersion, upToDate: true }
6072
- };
6073
- }
6074
- if (comparison > 0) {
6075
- output += chalk13.yellow("\u26A0\uFE0F You are on a newer version than what is published.\n");
6076
- output += chalk13.dim(" This is expected if you are developing locally.\n");
6077
- return {
6078
- success: true,
6079
- message: output,
6080
- data: { currentVersion, latestVersion, upToDate: false }
6081
- };
6330
+ id: project2.repository.id,
6331
+ owner: project2.repository.full_name.split("/")[0],
6332
+ // Parse owner from API full_name
6333
+ name: project2.repository.name,
6334
+ // Use API name, not git name
6335
+ full_name: project2.repository.full_name,
6336
+ url: gitInfo?.remoteUrl || void 0
6337
+ } : null,
6338
+ created_at: project2.created_at
6339
+ };
6340
+ await saveProjectConfig(localConfig);
6341
+ console.log(
6342
+ chalk13.green("\u2705 Repository initialized successfully!\n\n") + chalk13.dim("Project: ") + chalk13.cyan(project2.name) + chalk13.dim(` (${project2.short_id})`) + "\n" + chalk13.dim("Config: ") + chalk13.gray(".braingrid/project.json") + "\n"
6343
+ );
6344
+ const installedIDEs = await detectInstalledIDEs();
6345
+ if (installedIDEs.claudeCode) {
6346
+ const claudeSetupExists = await fileExists2(".claude/commands/specify.md");
6347
+ if (!claudeSetupExists) {
6348
+ console.log("");
6349
+ const setupClaude = await confirm({
6350
+ message: "Claude Code detected. Install BrainGrid integration? (slash commands, skills, status line)",
6351
+ default: true
6352
+ });
6353
+ if (setupClaude) {
6354
+ console.log("");
6355
+ try {
6356
+ const result = await handleSetupClaudeCode({ force: false });
6357
+ if (result.success) {
6358
+ console.log(result.message);
6359
+ } else {
6360
+ console.log(chalk13.yellow("\u26A0\uFE0F Claude Code setup was not completed."));
6361
+ console.log(
6362
+ chalk13.dim("You can run ") + chalk13.cyan("braingrid setup claude-code") + chalk13.dim(" later.")
6363
+ );
6364
+ }
6365
+ } catch {
6366
+ console.log(chalk13.yellow("\u26A0\uFE0F Claude Code setup encountered an error."));
6367
+ console.log(
6368
+ chalk13.dim("You can run ") + chalk13.cyan("braingrid setup claude-code") + chalk13.dim(" later.")
6369
+ );
6370
+ }
6371
+ console.log("");
6372
+ }
6373
+ }
6082
6374
  }
6083
- output += chalk13.yellow(`\u2B06\uFE0F Update available: ${currentVersion} \u2192 ${latestVersion}
6084
-
6085
- `);
6086
- if (opts.check) {
6087
- output += chalk13.dim("Run ") + chalk13.cyan("braingrid update") + chalk13.dim(" to update\n");
6088
- return {
6089
- success: true,
6090
- message: output,
6091
- data: { currentVersion, latestVersion, upToDate: false }
6092
- };
6375
+ if (installedIDEs.cursor) {
6376
+ const cursorSetupExists = await fileExists2(".cursor/commands/specify.md");
6377
+ if (!cursorSetupExists) {
6378
+ console.log("");
6379
+ const setupCursor = await confirm({
6380
+ message: "Cursor detected. Install BrainGrid integration? (slash commands, rules, context)",
6381
+ default: true
6382
+ });
6383
+ if (setupCursor) {
6384
+ console.log("");
6385
+ try {
6386
+ const result = await handleSetupCursor({ force: false });
6387
+ if (result.success) {
6388
+ console.log(result.message);
6389
+ } else {
6390
+ console.log(chalk13.yellow("\u26A0\uFE0F Cursor setup was not completed."));
6391
+ console.log(
6392
+ chalk13.dim("You can run ") + chalk13.cyan("braingrid setup cursor") + chalk13.dim(" later.")
6393
+ );
6394
+ }
6395
+ } catch {
6396
+ console.log(chalk13.yellow("\u26A0\uFE0F Cursor setup encountered an error."));
6397
+ console.log(
6398
+ chalk13.dim("You can run ") + chalk13.cyan("braingrid setup cursor") + chalk13.dim(" later.")
6399
+ );
6400
+ }
6401
+ console.log("");
6402
+ }
6403
+ }
6093
6404
  }
6094
- output += chalk13.dim("Detecting package manager...\n");
6095
- const packageManager = await detectPackageManager(PACKAGE_NAME);
6096
- output += `${chalk13.bold("Package manager:")} ${packageManager}
6097
-
6098
- `;
6099
- const updateCommand = getUpdateCommand(packageManager, PACKAGE_NAME);
6100
- output += chalk13.dim("Running: ") + chalk13.cyan(updateCommand) + "\n\n";
6101
- console.log(output);
6102
- executeUpdate(packageManager, PACKAGE_NAME);
6103
6405
  return {
6104
6406
  success: true,
6105
- message: chalk13.green("\n\u2705 Successfully updated BrainGrid CLI!\n"),
6106
- data: { currentVersion, latestVersion, packageManager }
6407
+ message: chalk13.dim(
6408
+ "You can now use project-scoped commands without specifying a project ID."
6409
+ ),
6410
+ data: localConfig
6107
6411
  };
6108
6412
  } catch (error) {
6109
6413
  return {
6110
6414
  success: false,
6111
- message: formatError(error)
6415
+ message: formatError(error, "initializing repository")
6112
6416
  };
6113
6417
  }
6114
6418
  }
6115
6419
 
6116
- // src/handlers/setup.handlers.ts
6420
+ // src/handlers/update.handlers.ts
6117
6421
  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
- }
6422
+ import axios5 from "axios";
6143
6423
 
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
- }
6424
+ // src/utils/package-manager.ts
6425
+ import { execSync } from "child_process";
6426
+ import { select as select3 } from "@inquirer/prompts";
6427
+ function isPackageManagerInstalled(pm) {
6180
6428
  try {
6181
- const match = errorMessage.match(/\{.*\}/s);
6182
- if (match) {
6183
- const errorData = JSON.parse(match[0]);
6184
- return errorData.message || errorMessage;
6185
- }
6429
+ execSync(`which ${pm}`, { stdio: "ignore", timeout: 2e3 });
6430
+ return true;
6186
6431
  } catch {
6432
+ return false;
6187
6433
  }
6188
- return errorMessage;
6189
6434
  }
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}`);
6435
+ function checkGlobalInstallation(pm, packageName) {
6436
+ try {
6437
+ let command;
6438
+ switch (pm) {
6439
+ case "npm": {
6440
+ command = `npm list -g ${packageName} 2>&1`;
6441
+ break;
6201
6442
  }
6202
- if (response.encoding !== "base64") {
6203
- throw new Error(`Unexpected encoding: ${response.encoding}`);
6443
+ case "pnpm": {
6444
+ command = `pnpm list -g ${packageName} 2>&1`;
6445
+ break;
6204
6446
  }
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`);
6447
+ case "yarn": {
6448
+ command = `yarn global list 2>&1`;
6449
+ break;
6221
6450
  }
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
6451
  }
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 });
6452
+ const output = execSync(command, {
6453
+ encoding: "utf-8",
6454
+ timeout: 5e3
6455
+ });
6456
+ if (pm === "npm" || pm === "pnpm") {
6457
+ return output.includes(packageName) && !output.includes("(empty)");
6281
6458
  }
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 {
6459
+ if (pm === "yarn") {
6460
+ return output.includes(packageName);
6304
6461
  }
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;
6462
+ return false;
6325
6463
  } catch {
6326
6464
  return false;
6327
6465
  }
6328
6466
  }
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 {
6467
+ async function getPackageManagerInfo(packageName) {
6468
+ const packageManagers = ["npm", "pnpm", "yarn"];
6469
+ return packageManagers.map((pm) => {
6470
+ const installed = isPackageManagerInstalled(pm);
6341
6471
  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")
6472
+ name: pm,
6473
+ installed,
6474
+ hasPackage: installed ? checkGlobalInstallation(pm, packageName) : false
6344
6475
  };
6345
- }
6346
- return null;
6476
+ });
6347
6477
  }
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
- }
6478
+ async function detectPackageManager(packageName) {
6479
+ const pmInfo = await getPackageManagerInfo(packageName);
6480
+ const installedWith = pmInfo.find((pm) => pm.hasPackage);
6481
+ if (installedWith) {
6482
+ return installedWith.name;
6373
6483
  }
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
- }
6484
+ const availablePMs = pmInfo.filter((pm) => pm.installed);
6485
+ if (availablePMs.length === 0) {
6486
+ throw new Error("No package manager found. Please install npm, pnpm, or yarn.");
6387
6487
  }
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
- }
6488
+ if (availablePMs.length === 1) {
6489
+ return availablePMs[0].name;
6393
6490
  }
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
- ]
6491
+ const selected = await select3({
6492
+ message: "Unable to detect which package manager was used. Please select one:",
6493
+ choices: availablePMs.map((pm) => ({
6494
+ name: pm.name,
6495
+ value: pm.name
6496
+ }))
6405
6497
  });
6406
- return answer;
6498
+ return selected;
6407
6499
  }
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
- }
6500
+ function getUpdateCommand(pm, packageName) {
6501
+ switch (pm) {
6502
+ case "npm": {
6503
+ return `npm install -g ${packageName}@latest`;
6423
6504
  }
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++;
6505
+ case "pnpm": {
6506
+ return `pnpm add -g ${packageName}@latest`;
6507
+ }
6508
+ case "yarn": {
6509
+ return `yarn global add ${packageName}@latest`;
6433
6510
  }
6434
6511
  }
6435
- return { installed, skipped, cancelled: false };
6436
6512
  }
6437
- async function _handleSetup(config, opts) {
6513
+ function executeUpdate(pm, packageName) {
6514
+ const command = getUpdateCommand(pm, packageName);
6438
6515
  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
6516
+ execSync(command, {
6517
+ stdio: "inherit",
6518
+ timeout: 12e4
6519
+ // 2 minutes timeout
6452
6520
  });
6453
- displayInstallationPlan(
6454
- operations.filter((op) => op.type === "copy"),
6455
- config.injection.targetFile
6521
+ } catch (error) {
6522
+ throw new Error(
6523
+ `Failed to update package: ${error instanceof Error ? error.message : String(error)}`
6456
6524
  );
6457
- if (opts.dryRun) {
6525
+ }
6526
+ }
6527
+
6528
+ // src/handlers/update.handlers.ts
6529
+ var PACKAGE_NAME = "@braingrid/cli";
6530
+ var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
6531
+ function getCurrentVersion() {
6532
+ return CLI_VERSION;
6533
+ }
6534
+ async function getLatestVersion() {
6535
+ try {
6536
+ const response = await axios5.get(NPM_REGISTRY_URL, {
6537
+ timeout: 1e4
6538
+ });
6539
+ return response.data["dist-tags"].latest;
6540
+ } catch (error) {
6541
+ throw new Error(`Failed to fetch latest version: ${formatError(error)}`);
6542
+ }
6543
+ }
6544
+ function compareVersions(v1, v2) {
6545
+ const v1Parts = v1.split(".").map(Number);
6546
+ const v2Parts = v2.split(".").map(Number);
6547
+ for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
6548
+ const v1Part = v1Parts[i] || 0;
6549
+ const v2Part = v2Parts[i] || 0;
6550
+ if (v1Part < v2Part) return -1;
6551
+ if (v1Part > v2Part) return 1;
6552
+ }
6553
+ return 0;
6554
+ }
6555
+ async function handleUpdate(opts) {
6556
+ try {
6557
+ const currentVersion = getCurrentVersion();
6558
+ let output = chalk14.bold.cyan("\n\u{1F504} BrainGrid CLI Update\n\n");
6559
+ output += `${chalk14.bold("Current version:")} ${currentVersion}
6560
+ `;
6561
+ output += chalk14.dim("Checking for updates...\n");
6562
+ const latestVersion = await getLatestVersion();
6563
+ output += `${chalk14.bold("Latest version:")} ${latestVersion}
6564
+
6565
+ `;
6566
+ const comparison = compareVersions(currentVersion, latestVersion);
6567
+ if (comparison === 0) {
6568
+ output += chalk14.green("\u2705 You are already on the latest version!\n");
6458
6569
  return {
6459
6570
  success: true,
6460
- message: chalk14.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk14.dim(`Would install ${operations.length} files.`)
6571
+ message: output,
6572
+ data: { currentVersion, latestVersion, upToDate: true }
6461
6573
  };
6462
6574
  }
6463
- const copyOps = operations.filter((op) => op.type === "copy");
6464
- const result = await installFiles(copyOps, opts.force || false);
6465
- if (result.cancelled) {
6575
+ if (comparison > 0) {
6576
+ output += chalk14.yellow("\u26A0\uFE0F You are on a newer version than what is published.\n");
6577
+ output += chalk14.dim(" This is expected if you are developing locally.\n");
6466
6578
  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"
6579
+ success: true,
6580
+ message: output,
6581
+ data: { currentVersion, latestVersion, upToDate: false }
6470
6582
  };
6471
6583
  }
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
- }
6584
+ output += chalk14.yellow(`\u2B06\uFE0F Update available: ${currentVersion} \u2192 ${latestVersion}
6585
+
6586
+ `);
6587
+ if (opts.check) {
6588
+ output += chalk14.dim("Run ") + chalk14.cyan("braingrid update") + chalk14.dim(" to update\n");
6589
+ return {
6590
+ success: true,
6591
+ message: output,
6592
+ data: { currentVersion, latestVersion, upToDate: false }
6593
+ };
6494
6594
  }
6495
- const statusLineMessage = statusLineInstalled ? chalk14.dim(" Status line: .claude/statusline.sh\n") : "";
6595
+ output += chalk14.dim("Detecting package manager...\n");
6596
+ const packageManager = await detectPackageManager(PACKAGE_NAME);
6597
+ output += `${chalk14.bold("Package manager:")} ${packageManager}
6598
+
6599
+ `;
6600
+ const updateCommand = getUpdateCommand(packageManager, PACKAGE_NAME);
6601
+ output += chalk14.dim("Running: ") + chalk14.cyan(updateCommand) + "\n\n";
6602
+ console.log(output);
6603
+ executeUpdate(packageManager, PACKAGE_NAME);
6496
6604
  return {
6497
6605
  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)
6606
+ message: chalk14.green("\n\u2705 Successfully updated BrainGrid CLI!\n"),
6607
+ data: { currentVersion, latestVersion, packageManager }
6505
6608
  };
6506
6609
  } catch (error) {
6507
- const errorMessage = error instanceof Error ? error.message : String(error);
6508
6610
  return {
6509
6611
  success: false,
6510
- message: chalk14.red(`\u274C Setup failed: ${errorMessage}`)
6612
+ message: formatError(error)
6511
6613
  };
6512
6614
  }
6513
6615
  }
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
6616
 
6541
6617
  // src/cli.ts
6542
6618
  var require2 = createRequire(import.meta.url);