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