@braingrid/cli 0.2.3 → 0.2.4
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 +47 -0
- package/README.md +137 -0
- package/dist/cli.js +444 -7
- package/dist/cli.js.map +1 -1
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -343,7 +343,7 @@ var DEFAULT_OPTIONS = {
|
|
|
343
343
|
// No-op by default
|
|
344
344
|
};
|
|
345
345
|
async function sleep(ms) {
|
|
346
|
-
return new Promise((
|
|
346
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
347
347
|
}
|
|
348
348
|
function calculateDelay(attempt, initialDelay, maxDelay, backoffMultiplier) {
|
|
349
349
|
const exponentialDelay = initialDelay * Math.pow(backoffMultiplier, attempt - 1);
|
|
@@ -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.4" : "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",
|
|
@@ -531,7 +531,7 @@ var OAuth2Handler = class {
|
|
|
531
531
|
* Start local HTTP server to handle OAuth callback
|
|
532
532
|
*/
|
|
533
533
|
async startCallbackServer(port) {
|
|
534
|
-
return new Promise((
|
|
534
|
+
return new Promise((resolve2) => {
|
|
535
535
|
this.server = createServer((req, res) => {
|
|
536
536
|
const url = new URL(req.url || "", `http://127.0.0.1:${port}`);
|
|
537
537
|
if (url.pathname === "/callback") {
|
|
@@ -634,7 +634,7 @@ var OAuth2Handler = class {
|
|
|
634
634
|
});
|
|
635
635
|
this.server.listen(port, "127.0.0.1", () => {
|
|
636
636
|
logger2.debug(`Callback server listening on http://127.0.0.1:${port}`);
|
|
637
|
-
|
|
637
|
+
resolve2();
|
|
638
638
|
});
|
|
639
639
|
});
|
|
640
640
|
}
|
|
@@ -699,7 +699,7 @@ var OAuth2Handler = class {
|
|
|
699
699
|
* Wait for the authorization code from the callback
|
|
700
700
|
*/
|
|
701
701
|
async waitForAuthorizationCode(timeoutMs = 3e5) {
|
|
702
|
-
return new Promise((
|
|
702
|
+
return new Promise((resolve2, reject) => {
|
|
703
703
|
const startTime = Date.now();
|
|
704
704
|
const checkInterval = setInterval(() => {
|
|
705
705
|
if (this.abortController?.signal.aborted) {
|
|
@@ -714,7 +714,7 @@ var OAuth2Handler = class {
|
|
|
714
714
|
}
|
|
715
715
|
if (this.authorizationCode) {
|
|
716
716
|
clearInterval(checkInterval);
|
|
717
|
-
|
|
717
|
+
resolve2(this.authorizationCode);
|
|
718
718
|
return;
|
|
719
719
|
}
|
|
720
720
|
if (Date.now() - startTime > timeoutMs) {
|
|
@@ -2091,7 +2091,7 @@ async function waitWithSpinner(message, checkFn, intervalMs = 3e3, maxAttempts =
|
|
|
2091
2091
|
return true;
|
|
2092
2092
|
}
|
|
2093
2093
|
if (attempt < maxAttempts - 1) {
|
|
2094
|
-
await new Promise((
|
|
2094
|
+
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
2095
2095
|
}
|
|
2096
2096
|
}
|
|
2097
2097
|
if (spinnerInterval) clearInterval(spinnerInterval);
|
|
@@ -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
|
+
import { fileURLToPath } from "url";
|
|
6122
|
+
|
|
6123
|
+
// src/utils/command-execution.ts
|
|
6124
|
+
import { exec as exec4, spawn } from "child_process";
|
|
6125
|
+
import { promisify as promisify4 } from "util";
|
|
6126
|
+
var execAsyncReal = promisify4(exec4);
|
|
6127
|
+
async function execAsync4(command, options, isTestMode = false, mockExecHandler) {
|
|
6128
|
+
if (isTestMode && mockExecHandler) {
|
|
6129
|
+
return mockExecHandler(command);
|
|
6130
|
+
}
|
|
6131
|
+
const defaultOptions = {
|
|
6132
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
6133
|
+
// 10MB default
|
|
6134
|
+
timeout: 3e5,
|
|
6135
|
+
// 5 minutes
|
|
6136
|
+
...options
|
|
6137
|
+
};
|
|
6138
|
+
if (command.includes("claude")) {
|
|
6139
|
+
defaultOptions.maxBuffer = 1024 * 1024 * 50;
|
|
6140
|
+
defaultOptions.timeout = 6e5;
|
|
6141
|
+
}
|
|
6142
|
+
return execAsyncReal(command, defaultOptions);
|
|
6143
|
+
}
|
|
6144
|
+
|
|
6145
|
+
// src/services/setup-service.ts
|
|
6146
|
+
import * as fs3 from "fs/promises";
|
|
6147
|
+
import * as path4 from "path";
|
|
6148
|
+
var GITHUB_OWNER = "BrainGridAI";
|
|
6149
|
+
var GITHUB_REPO = "braingrid";
|
|
6150
|
+
var MAX_RETRIES = 3;
|
|
6151
|
+
var INITIAL_RETRY_DELAY = 100;
|
|
6152
|
+
var BEGIN_MARKER = "<!-- BEGIN BRAINGRID INTEGRATION -->";
|
|
6153
|
+
var END_MARKER = "<!-- END BRAINGRID INTEGRATION -->";
|
|
6154
|
+
async function withRetry(fn, retries = MAX_RETRIES, delay = INITIAL_RETRY_DELAY) {
|
|
6155
|
+
try {
|
|
6156
|
+
return await fn();
|
|
6157
|
+
} catch (error) {
|
|
6158
|
+
if (retries === 0) {
|
|
6159
|
+
throw error;
|
|
6160
|
+
}
|
|
6161
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6162
|
+
const isNetworkError = errorMessage.includes("ECONNRESET") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("ENOTFOUND") || errorMessage.includes("network");
|
|
6163
|
+
if (!isNetworkError) {
|
|
6164
|
+
throw error;
|
|
6165
|
+
}
|
|
6166
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
6167
|
+
return withRetry(fn, retries - 1, delay * 2);
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
function parseGitHubError(error) {
|
|
6171
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6172
|
+
if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
|
|
6173
|
+
return "File or directory not found in BrainGrid repository";
|
|
6174
|
+
}
|
|
6175
|
+
if (errorMessage.includes("403") || errorMessage.includes("rate limit")) {
|
|
6176
|
+
return "GitHub API rate limit exceeded. Please wait a few minutes and try again.\nCheck rate limit status: gh api rate_limit";
|
|
6177
|
+
}
|
|
6178
|
+
if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
|
|
6179
|
+
return "GitHub CLI is not authenticated. Run: gh auth login";
|
|
6180
|
+
}
|
|
6181
|
+
try {
|
|
6182
|
+
const match = errorMessage.match(/\{.*\}/s);
|
|
6183
|
+
if (match) {
|
|
6184
|
+
const errorData = JSON.parse(match[0]);
|
|
6185
|
+
return errorData.message || errorMessage;
|
|
6186
|
+
}
|
|
6187
|
+
} catch {
|
|
6188
|
+
}
|
|
6189
|
+
return errorMessage;
|
|
6190
|
+
}
|
|
6191
|
+
async function fetchFileFromGitHub(path6) {
|
|
6192
|
+
return withRetry(async () => {
|
|
6193
|
+
try {
|
|
6194
|
+
const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
|
|
6195
|
+
const { stdout } = await execAsync4(command);
|
|
6196
|
+
const response = JSON.parse(stdout);
|
|
6197
|
+
if (response.type !== "file") {
|
|
6198
|
+
throw new Error(`Path ${path6} is not a file`);
|
|
6199
|
+
}
|
|
6200
|
+
if (!response.content || !response.encoding) {
|
|
6201
|
+
throw new Error(`No content found for file ${path6}`);
|
|
6202
|
+
}
|
|
6203
|
+
if (response.encoding !== "base64") {
|
|
6204
|
+
throw new Error(`Unexpected encoding: ${response.encoding}`);
|
|
6205
|
+
}
|
|
6206
|
+
const content = Buffer.from(response.content, "base64").toString("utf8");
|
|
6207
|
+
return content;
|
|
6208
|
+
} catch (error) {
|
|
6209
|
+
const parsedError = parseGitHubError(error);
|
|
6210
|
+
throw new Error(`Failed to fetch file ${path6}: ${parsedError}`);
|
|
6211
|
+
}
|
|
6212
|
+
});
|
|
6213
|
+
}
|
|
6214
|
+
async function listGitHubDirectory(path6) {
|
|
6215
|
+
return withRetry(async () => {
|
|
6216
|
+
try {
|
|
6217
|
+
const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
|
|
6218
|
+
const { stdout } = await execAsync4(command);
|
|
6219
|
+
const response = JSON.parse(stdout);
|
|
6220
|
+
if (!Array.isArray(response)) {
|
|
6221
|
+
throw new Error(`Path ${path6} is not a directory`);
|
|
6222
|
+
}
|
|
6223
|
+
return response.map((item) => ({
|
|
6224
|
+
name: item.name,
|
|
6225
|
+
type: item.type,
|
|
6226
|
+
path: item.path
|
|
6227
|
+
}));
|
|
6228
|
+
} catch (error) {
|
|
6229
|
+
const parsedError = parseGitHubError(error);
|
|
6230
|
+
throw new Error(`Failed to list directory ${path6}: ${parsedError}`);
|
|
6231
|
+
}
|
|
6232
|
+
});
|
|
6233
|
+
}
|
|
6234
|
+
async function copyFileFromGitHub(sourcePath, targetPath) {
|
|
6235
|
+
try {
|
|
6236
|
+
const content = await fetchFileFromGitHub(sourcePath);
|
|
6237
|
+
const parentDir = path4.dirname(targetPath);
|
|
6238
|
+
await fs3.mkdir(parentDir, { recursive: true });
|
|
6239
|
+
await fs3.writeFile(targetPath, content, { encoding: "utf8", mode: 420 });
|
|
6240
|
+
} catch (error) {
|
|
6241
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6242
|
+
throw new Error(`Failed to copy file ${sourcePath} to ${targetPath}: ${errorMessage}`);
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
async function injectContentIntoFile(targetPath, content) {
|
|
6246
|
+
try {
|
|
6247
|
+
let fileContent;
|
|
6248
|
+
let fileExists2 = false;
|
|
6249
|
+
try {
|
|
6250
|
+
fileContent = await fs3.readFile(targetPath, "utf8");
|
|
6251
|
+
fileExists2 = true;
|
|
6252
|
+
} catch {
|
|
6253
|
+
fileContent = "";
|
|
6254
|
+
}
|
|
6255
|
+
if (fileExists2) {
|
|
6256
|
+
const beginIndex = fileContent.indexOf(BEGIN_MARKER);
|
|
6257
|
+
const endIndex = fileContent.indexOf(END_MARKER);
|
|
6258
|
+
if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
|
|
6259
|
+
const before = fileContent.substring(0, beginIndex);
|
|
6260
|
+
const after = fileContent.substring(endIndex + END_MARKER.length);
|
|
6261
|
+
const newContent = `${before}${BEGIN_MARKER}
|
|
6262
|
+
${content}
|
|
6263
|
+
${END_MARKER}${after}`;
|
|
6264
|
+
await fs3.writeFile(targetPath, newContent, { encoding: "utf8" });
|
|
6265
|
+
} else {
|
|
6266
|
+
const newContent = `${fileContent}
|
|
6267
|
+
|
|
6268
|
+
${BEGIN_MARKER}
|
|
6269
|
+
${content}
|
|
6270
|
+
${END_MARKER}
|
|
6271
|
+
`;
|
|
6272
|
+
await fs3.writeFile(targetPath, newContent, { encoding: "utf8" });
|
|
6273
|
+
}
|
|
6274
|
+
} else {
|
|
6275
|
+
const parentDir = path4.dirname(targetPath);
|
|
6276
|
+
await fs3.mkdir(parentDir, { recursive: true });
|
|
6277
|
+
const newContent = `${BEGIN_MARKER}
|
|
6278
|
+
${content}
|
|
6279
|
+
${END_MARKER}
|
|
6280
|
+
`;
|
|
6281
|
+
await fs3.writeFile(targetPath, newContent, { encoding: "utf8", mode: 420 });
|
|
6282
|
+
}
|
|
6283
|
+
} catch (error) {
|
|
6284
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6285
|
+
throw new Error(`Failed to inject content into ${targetPath}: ${errorMessage}`);
|
|
6286
|
+
}
|
|
6287
|
+
}
|
|
6288
|
+
async function installStatusLineScript(scriptContent, targetPath = ".claude/statusline.sh") {
|
|
6289
|
+
try {
|
|
6290
|
+
const parentDir = path4.dirname(targetPath);
|
|
6291
|
+
await fs3.mkdir(parentDir, { recursive: true });
|
|
6292
|
+
await fs3.writeFile(targetPath, scriptContent, { encoding: "utf8", mode: 493 });
|
|
6293
|
+
} catch (error) {
|
|
6294
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6295
|
+
throw new Error(`Failed to install status line script to ${targetPath}: ${errorMessage}`);
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
async function updateClaudeSettings(settingsPath = ".claude/settings.json", scriptPath = ".claude/statusline.sh") {
|
|
6299
|
+
try {
|
|
6300
|
+
let settings = {};
|
|
6301
|
+
try {
|
|
6302
|
+
const content2 = await fs3.readFile(settingsPath, "utf8");
|
|
6303
|
+
settings = JSON.parse(content2);
|
|
6304
|
+
} catch {
|
|
6305
|
+
}
|
|
6306
|
+
settings.statusLine = scriptPath;
|
|
6307
|
+
const parentDir = path4.dirname(settingsPath);
|
|
6308
|
+
await fs3.mkdir(parentDir, { recursive: true });
|
|
6309
|
+
const content = JSON.stringify(settings, null, 2);
|
|
6310
|
+
await fs3.writeFile(settingsPath, content, { encoding: "utf8" });
|
|
6311
|
+
} catch (error) {
|
|
6312
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6313
|
+
throw new Error(`Failed to update Claude settings at ${settingsPath}: ${errorMessage}`);
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
|
|
6317
|
+
// src/handlers/setup.handlers.ts
|
|
6318
|
+
async function fileExists(filePath) {
|
|
6319
|
+
try {
|
|
6320
|
+
await fs4.access(filePath);
|
|
6321
|
+
return true;
|
|
6322
|
+
} catch {
|
|
6323
|
+
return false;
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
async function checkPrerequisites() {
|
|
6327
|
+
try {
|
|
6328
|
+
await execAsync4("gh --version");
|
|
6329
|
+
} catch {
|
|
6330
|
+
return {
|
|
6331
|
+
success: false,
|
|
6332
|
+
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")
|
|
6333
|
+
};
|
|
6334
|
+
}
|
|
6335
|
+
try {
|
|
6336
|
+
await execAsync4("gh auth status");
|
|
6337
|
+
} catch {
|
|
6338
|
+
return {
|
|
6339
|
+
success: false,
|
|
6340
|
+
message: chalk14.red("\u274C Not authenticated with GitHub CLI.\n\n") + chalk14.dim("Please run: ") + chalk14.cyan("gh auth login")
|
|
6341
|
+
};
|
|
6342
|
+
}
|
|
6343
|
+
return null;
|
|
6344
|
+
}
|
|
6345
|
+
async function getFileList(sourcePaths, targetPaths) {
|
|
6346
|
+
const operations = [];
|
|
6347
|
+
for (let i = 0; i < sourcePaths.length; i++) {
|
|
6348
|
+
const sourcePath = sourcePaths[i];
|
|
6349
|
+
const targetPath = targetPaths[i];
|
|
6350
|
+
try {
|
|
6351
|
+
const items = await listGitHubDirectory(sourcePath);
|
|
6352
|
+
for (const item of items) {
|
|
6353
|
+
if (item.type === "file") {
|
|
6354
|
+
const itemTargetPath = path5.join(targetPath, item.name);
|
|
6355
|
+
const exists = await fileExists(itemTargetPath);
|
|
6356
|
+
operations.push({
|
|
6357
|
+
type: "copy",
|
|
6358
|
+
sourcePath: item.path,
|
|
6359
|
+
targetPath: itemTargetPath,
|
|
6360
|
+
exists
|
|
6361
|
+
});
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
} catch (error) {
|
|
6365
|
+
console.warn(
|
|
6366
|
+
chalk14.yellow(`\u26A0\uFE0F Could not list directory: ${sourcePath}`),
|
|
6367
|
+
error instanceof Error ? error.message : String(error)
|
|
6368
|
+
);
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6371
|
+
return operations;
|
|
6372
|
+
}
|
|
6373
|
+
function displayInstallationPlan(operations, injectionFile) {
|
|
6374
|
+
console.log(chalk14.bold("\n\u{1F4CB} Installation Plan:\n"));
|
|
6375
|
+
console.log(chalk14.cyan(" Content Injection:"));
|
|
6376
|
+
console.log(chalk14.dim(` ${injectionFile}`));
|
|
6377
|
+
const newFiles = operations.filter((op) => !op.exists);
|
|
6378
|
+
const existingFiles = operations.filter((op) => op.exists);
|
|
6379
|
+
if (newFiles.length > 0) {
|
|
6380
|
+
console.log(chalk14.cyan("\n New Files:"));
|
|
6381
|
+
for (const op of newFiles) {
|
|
6382
|
+
console.log(chalk14.dim(` ${op.targetPath}`));
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6385
|
+
if (existingFiles.length > 0) {
|
|
6386
|
+
console.log(chalk14.yellow("\n Existing Files (will prompt):"));
|
|
6387
|
+
for (const op of existingFiles) {
|
|
6388
|
+
console.log(chalk14.dim(` ${op.targetPath}`));
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
console.log("");
|
|
6392
|
+
}
|
|
6393
|
+
async function promptForConflict(filePath) {
|
|
6394
|
+
const answer = await select3({
|
|
6395
|
+
message: chalk14.yellow(`File exists: ${filePath}`),
|
|
6396
|
+
choices: [
|
|
6397
|
+
{ name: "[O]verwrite - Replace this file", value: "overwrite" },
|
|
6398
|
+
{ name: "[S]kip - Keep existing file", value: "skip" },
|
|
6399
|
+
{ name: "[A]ll - Overwrite all remaining", value: "all" },
|
|
6400
|
+
{ name: "[Q]uit - Cancel installation", value: "quit" }
|
|
6401
|
+
]
|
|
6402
|
+
});
|
|
6403
|
+
return answer;
|
|
6404
|
+
}
|
|
6405
|
+
async function installFiles(operations, force) {
|
|
6406
|
+
let installed = 0;
|
|
6407
|
+
let skipped = 0;
|
|
6408
|
+
let overwriteAll = force;
|
|
6409
|
+
for (const operation of operations) {
|
|
6410
|
+
if (operation.exists && !overwriteAll) {
|
|
6411
|
+
const response = await promptForConflict(operation.targetPath);
|
|
6412
|
+
if (response === "quit") {
|
|
6413
|
+
return { installed, skipped, cancelled: true };
|
|
6414
|
+
} else if (response === "skip") {
|
|
6415
|
+
skipped++;
|
|
6416
|
+
continue;
|
|
6417
|
+
} else if (response === "all") {
|
|
6418
|
+
overwriteAll = true;
|
|
6419
|
+
}
|
|
6420
|
+
}
|
|
6421
|
+
try {
|
|
6422
|
+
await copyFileFromGitHub(operation.sourcePath, operation.targetPath);
|
|
6423
|
+
installed++;
|
|
6424
|
+
} catch (error) {
|
|
6425
|
+
console.error(
|
|
6426
|
+
chalk14.red(`Failed to copy ${operation.targetPath}:`),
|
|
6427
|
+
error instanceof Error ? error.message : String(error)
|
|
6428
|
+
);
|
|
6429
|
+
skipped++;
|
|
6430
|
+
}
|
|
6431
|
+
}
|
|
6432
|
+
return { installed, skipped, cancelled: false };
|
|
6433
|
+
}
|
|
6434
|
+
async function _handleSetup(config, opts) {
|
|
6435
|
+
try {
|
|
6436
|
+
const prerequisiteError = await checkPrerequisites();
|
|
6437
|
+
if (prerequisiteError) {
|
|
6438
|
+
return prerequisiteError;
|
|
6439
|
+
}
|
|
6440
|
+
console.log(chalk14.bold(`\u{1F680} Setting up ${config.name} integration...
|
|
6441
|
+
`));
|
|
6442
|
+
const operations = await getFileList(config.sourceDirs, config.targetDirs);
|
|
6443
|
+
const injectionFileExists = await fileExists(config.injection.targetFile);
|
|
6444
|
+
operations.push({
|
|
6445
|
+
type: "inject",
|
|
6446
|
+
sourcePath: config.injection.sourceFile,
|
|
6447
|
+
targetPath: config.injection.targetFile,
|
|
6448
|
+
exists: injectionFileExists
|
|
6449
|
+
});
|
|
6450
|
+
displayInstallationPlan(
|
|
6451
|
+
operations.filter((op) => op.type === "copy"),
|
|
6452
|
+
config.injection.targetFile
|
|
6453
|
+
);
|
|
6454
|
+
if (opts.dryRun) {
|
|
6455
|
+
return {
|
|
6456
|
+
success: true,
|
|
6457
|
+
message: chalk14.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk14.dim(`Would install ${operations.length} files.`)
|
|
6458
|
+
};
|
|
6459
|
+
}
|
|
6460
|
+
const copyOps = operations.filter((op) => op.type === "copy");
|
|
6461
|
+
const result = await installFiles(copyOps, opts.force || false);
|
|
6462
|
+
if (result.cancelled) {
|
|
6463
|
+
return {
|
|
6464
|
+
success: false,
|
|
6465
|
+
message: chalk14.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk14.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
|
|
6466
|
+
code: "CANCELLED"
|
|
6467
|
+
};
|
|
6468
|
+
}
|
|
6469
|
+
try {
|
|
6470
|
+
const content = await fetchFileFromGitHub(config.injection.sourceFile);
|
|
6471
|
+
await injectContentIntoFile(config.injection.targetFile, content);
|
|
6472
|
+
} catch (error) {
|
|
6473
|
+
console.error(
|
|
6474
|
+
chalk14.red(`Failed to inject content into ${config.injection.targetFile}:`),
|
|
6475
|
+
error instanceof Error ? error.message : String(error)
|
|
6476
|
+
);
|
|
6477
|
+
}
|
|
6478
|
+
let statusLineInstalled = false;
|
|
6479
|
+
if (config.name === "Claude Code") {
|
|
6480
|
+
try {
|
|
6481
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6482
|
+
const __dirname = path5.dirname(__filename);
|
|
6483
|
+
const scriptPath = path5.resolve(__dirname, "../../.claude/statusline.sh");
|
|
6484
|
+
const scriptContent = await fs4.readFile(scriptPath, "utf8");
|
|
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/README.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);
|