@braingrid/cli 0.2.24 → 0.2.26
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 +38 -0
- package/README.md +7 -1
- package/dist/chunk-6GC3UJCM.js +230 -0
- package/dist/chunk-6GC3UJCM.js.map +1 -0
- package/dist/cli.js +602 -118
- package/dist/cli.js.map +1 -1
- package/dist/command-execution-7JMH2DK4.js +11 -0
- package/dist/command-execution-7JMH2DK4.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
execAsync
|
|
4
|
+
} from "./chunk-6GC3UJCM.js";
|
|
2
5
|
import {
|
|
3
6
|
checkInstalledCliTools,
|
|
4
7
|
detectLinuxPackageManager,
|
|
@@ -20,6 +23,81 @@ import chalk6 from "chalk";
|
|
|
20
23
|
// src/utils/axios-with-auth.ts
|
|
21
24
|
import axios from "axios";
|
|
22
25
|
|
|
26
|
+
// src/build-config.ts
|
|
27
|
+
var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
|
|
28
|
+
var CLI_VERSION = true ? "0.2.26" : "0.0.0-test";
|
|
29
|
+
var PRODUCTION_CONFIG = {
|
|
30
|
+
apiUrl: "https://app.braingrid.ai",
|
|
31
|
+
workosAuthUrl: "https://auth.braingrid.ai",
|
|
32
|
+
workosClientId: "client_01K6H010C9K69HSDPM9CQM85S7"
|
|
33
|
+
};
|
|
34
|
+
var DEVELOPMENT_CONFIG = {
|
|
35
|
+
apiUrl: "https://app.dev.braingrid.ai",
|
|
36
|
+
workosAuthUrl: "https://balanced-celebration-78-staging.authkit.app",
|
|
37
|
+
workosClientId: "client_01K6H04GF21T4JXNS3JDQM3YNE"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/utils/config.ts
|
|
41
|
+
var BRAINGRID_API_TOKEN = process.env.BRAINGRID_API_TOKEN;
|
|
42
|
+
function getConfig() {
|
|
43
|
+
const baseConfig = BUILD_ENV === "production" ? PRODUCTION_CONFIG : DEVELOPMENT_CONFIG;
|
|
44
|
+
let apiUrl = baseConfig.apiUrl;
|
|
45
|
+
if (BUILD_ENV === "development") {
|
|
46
|
+
if (process.env.NODE_ENV === "local" || process.env.NODE_ENV === "test") {
|
|
47
|
+
apiUrl = "http://localhost:3377";
|
|
48
|
+
} else if (process.env.NODE_ENV === "development") {
|
|
49
|
+
apiUrl = DEVELOPMENT_CONFIG.apiUrl;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const getWorkOSAuthUrl = () => {
|
|
53
|
+
if (process.env.WORKOS_AUTH_URL) {
|
|
54
|
+
return process.env.WORKOS_AUTH_URL;
|
|
55
|
+
}
|
|
56
|
+
if (BUILD_ENV === "production") {
|
|
57
|
+
return PRODUCTION_CONFIG.workosAuthUrl;
|
|
58
|
+
}
|
|
59
|
+
const env = process.env.NODE_ENV || "development";
|
|
60
|
+
if (env === "local" || env === "test" || env === "development" || env === "staging") {
|
|
61
|
+
return DEVELOPMENT_CONFIG.workosAuthUrl;
|
|
62
|
+
}
|
|
63
|
+
return PRODUCTION_CONFIG.workosAuthUrl;
|
|
64
|
+
};
|
|
65
|
+
const getOAuthClientId = () => {
|
|
66
|
+
if (process.env.WORKOS_CLIENT_ID) {
|
|
67
|
+
return process.env.WORKOS_CLIENT_ID;
|
|
68
|
+
}
|
|
69
|
+
if (BUILD_ENV === "production") {
|
|
70
|
+
return PRODUCTION_CONFIG.workosClientId;
|
|
71
|
+
}
|
|
72
|
+
const env = process.env.NODE_ENV || "development";
|
|
73
|
+
if (env === "local" || env === "test" || env === "development" || env === "staging") {
|
|
74
|
+
return DEVELOPMENT_CONFIG.workosClientId;
|
|
75
|
+
}
|
|
76
|
+
return PRODUCTION_CONFIG.workosClientId;
|
|
77
|
+
};
|
|
78
|
+
const getWebAppUrl = () => {
|
|
79
|
+
if (process.env.BRAINGRID_WEB_URL) {
|
|
80
|
+
return process.env.BRAINGRID_WEB_URL;
|
|
81
|
+
}
|
|
82
|
+
if (BUILD_ENV === "production") {
|
|
83
|
+
return PRODUCTION_CONFIG.apiUrl;
|
|
84
|
+
}
|
|
85
|
+
const env = process.env.NODE_ENV || "development";
|
|
86
|
+
if (env === "local" || env === "test") {
|
|
87
|
+
return "http://localhost:3377";
|
|
88
|
+
}
|
|
89
|
+
return DEVELOPMENT_CONFIG.apiUrl;
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
apiUrl: process.env.BRAINGRID_API_URL || apiUrl,
|
|
93
|
+
organizationId: process.env.BRAINGRID_ORG_ID,
|
|
94
|
+
clientId: process.env.BRAINGRID_CLIENT_ID || "braingrid-cli",
|
|
95
|
+
oauthClientId: getOAuthClientId(),
|
|
96
|
+
getWorkOSAuthUrl,
|
|
97
|
+
getWebAppUrl
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
23
101
|
// src/utils/logger.ts
|
|
24
102
|
import fs from "fs";
|
|
25
103
|
import path from "path";
|
|
@@ -208,6 +286,10 @@ function createAuthenticatedAxios(auth) {
|
|
|
208
286
|
});
|
|
209
287
|
instance.interceptors.request.use(
|
|
210
288
|
async (config) => {
|
|
289
|
+
if (BRAINGRID_API_TOKEN) {
|
|
290
|
+
config.headers.Authorization = `Bearer ${BRAINGRID_API_TOKEN}`;
|
|
291
|
+
return config;
|
|
292
|
+
}
|
|
211
293
|
const session = await auth.getStoredSession();
|
|
212
294
|
if (session) {
|
|
213
295
|
config.headers.Authorization = `Bearer ${session.sealed_session}`;
|
|
@@ -248,6 +330,11 @@ function createAuthenticatedAxios(auth) {
|
|
|
248
330
|
const isRedirectToAuth = isAuthRedirect(error);
|
|
249
331
|
if ((is401 || isRedirectToAuth) && !originalRequest._retry) {
|
|
250
332
|
originalRequest._retry = true;
|
|
333
|
+
if (BRAINGRID_API_TOKEN) {
|
|
334
|
+
logger.warn("[AUTH] Sandbox token expired or invalid");
|
|
335
|
+
const sandboxError = new Error(auth.getSandboxExpiredMessage());
|
|
336
|
+
return Promise.reject(sandboxError);
|
|
337
|
+
}
|
|
251
338
|
if (isRedirectToAuth) {
|
|
252
339
|
logger.debug("[AUTH] Received redirect to auth endpoint - token likely expired");
|
|
253
340
|
} else {
|
|
@@ -419,82 +506,6 @@ import { createHash, randomBytes } from "crypto";
|
|
|
419
506
|
import { createServer } from "http";
|
|
420
507
|
import open from "open";
|
|
421
508
|
import axios3, { AxiosError as AxiosError2 } from "axios";
|
|
422
|
-
|
|
423
|
-
// src/build-config.ts
|
|
424
|
-
var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
|
|
425
|
-
var CLI_VERSION = true ? "0.2.24" : "0.0.0-test";
|
|
426
|
-
var PRODUCTION_CONFIG = {
|
|
427
|
-
apiUrl: "https://app.braingrid.ai",
|
|
428
|
-
workosAuthUrl: "https://auth.braingrid.ai",
|
|
429
|
-
workosClientId: "client_01K6H010C9K69HSDPM9CQM85S7"
|
|
430
|
-
};
|
|
431
|
-
var DEVELOPMENT_CONFIG = {
|
|
432
|
-
apiUrl: "https://app.dev.braingrid.ai",
|
|
433
|
-
workosAuthUrl: "https://balanced-celebration-78-staging.authkit.app",
|
|
434
|
-
workosClientId: "client_01K6H04GF21T4JXNS3JDQM3YNE"
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
// src/utils/config.ts
|
|
438
|
-
function getConfig() {
|
|
439
|
-
const baseConfig = BUILD_ENV === "production" ? PRODUCTION_CONFIG : DEVELOPMENT_CONFIG;
|
|
440
|
-
let apiUrl = baseConfig.apiUrl;
|
|
441
|
-
if (BUILD_ENV === "development") {
|
|
442
|
-
if (process.env.NODE_ENV === "local" || process.env.NODE_ENV === "test") {
|
|
443
|
-
apiUrl = "http://localhost:3377";
|
|
444
|
-
} else if (process.env.NODE_ENV === "development") {
|
|
445
|
-
apiUrl = DEVELOPMENT_CONFIG.apiUrl;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
const getWorkOSAuthUrl = () => {
|
|
449
|
-
if (process.env.WORKOS_AUTH_URL) {
|
|
450
|
-
return process.env.WORKOS_AUTH_URL;
|
|
451
|
-
}
|
|
452
|
-
if (BUILD_ENV === "production") {
|
|
453
|
-
return PRODUCTION_CONFIG.workosAuthUrl;
|
|
454
|
-
}
|
|
455
|
-
const env = process.env.NODE_ENV || "development";
|
|
456
|
-
if (env === "local" || env === "test" || env === "development" || env === "staging") {
|
|
457
|
-
return DEVELOPMENT_CONFIG.workosAuthUrl;
|
|
458
|
-
}
|
|
459
|
-
return PRODUCTION_CONFIG.workosAuthUrl;
|
|
460
|
-
};
|
|
461
|
-
const getOAuthClientId = () => {
|
|
462
|
-
if (process.env.WORKOS_CLIENT_ID) {
|
|
463
|
-
return process.env.WORKOS_CLIENT_ID;
|
|
464
|
-
}
|
|
465
|
-
if (BUILD_ENV === "production") {
|
|
466
|
-
return PRODUCTION_CONFIG.workosClientId;
|
|
467
|
-
}
|
|
468
|
-
const env = process.env.NODE_ENV || "development";
|
|
469
|
-
if (env === "local" || env === "test" || env === "development" || env === "staging") {
|
|
470
|
-
return DEVELOPMENT_CONFIG.workosClientId;
|
|
471
|
-
}
|
|
472
|
-
return PRODUCTION_CONFIG.workosClientId;
|
|
473
|
-
};
|
|
474
|
-
const getWebAppUrl = () => {
|
|
475
|
-
if (process.env.BRAINGRID_WEB_URL) {
|
|
476
|
-
return process.env.BRAINGRID_WEB_URL;
|
|
477
|
-
}
|
|
478
|
-
if (BUILD_ENV === "production") {
|
|
479
|
-
return PRODUCTION_CONFIG.apiUrl;
|
|
480
|
-
}
|
|
481
|
-
const env = process.env.NODE_ENV || "development";
|
|
482
|
-
if (env === "local" || env === "test") {
|
|
483
|
-
return "http://localhost:3377";
|
|
484
|
-
}
|
|
485
|
-
return DEVELOPMENT_CONFIG.apiUrl;
|
|
486
|
-
};
|
|
487
|
-
return {
|
|
488
|
-
apiUrl: process.env.BRAINGRID_API_URL || apiUrl,
|
|
489
|
-
organizationId: process.env.BRAINGRID_ORG_ID,
|
|
490
|
-
clientId: process.env.BRAINGRID_CLIENT_ID || "braingrid-cli",
|
|
491
|
-
oauthClientId: getOAuthClientId(),
|
|
492
|
-
getWorkOSAuthUrl,
|
|
493
|
-
getWebAppUrl
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// src/services/oauth2-auth.ts
|
|
498
509
|
var logger2 = getLogger();
|
|
499
510
|
var OAuth2Handler = class {
|
|
500
511
|
/**
|
|
@@ -997,6 +1008,7 @@ var KEYCHAIN_SERVICE = "braingrid-cli";
|
|
|
997
1008
|
var KEYCHAIN_ACCOUNT = "session";
|
|
998
1009
|
var GITHUB_KEYCHAIN_ACCOUNT = "github-token";
|
|
999
1010
|
var BraingridAuth = class {
|
|
1011
|
+
// Cached session from env token
|
|
1000
1012
|
constructor(baseUrl) {
|
|
1001
1013
|
this.lastValidationTime = 0;
|
|
1002
1014
|
this.lastValidationResult = false;
|
|
@@ -1008,11 +1020,103 @@ var BraingridAuth = class {
|
|
|
1008
1020
|
this.oauthHandler = null;
|
|
1009
1021
|
// Store refresh token separately
|
|
1010
1022
|
this.logger = getLogger();
|
|
1023
|
+
this.envTokenSession = null;
|
|
1011
1024
|
const config = getConfig();
|
|
1012
1025
|
this.baseUrl = baseUrl || config.apiUrl || "https://app.braingrid.ai";
|
|
1013
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Check if CLI is using BRAINGRID_API_TOKEN environment variable
|
|
1029
|
+
* This is used in sandbox environments where tokens are injected
|
|
1030
|
+
*/
|
|
1031
|
+
isUsingEnvToken() {
|
|
1032
|
+
return Boolean(BRAINGRID_API_TOKEN);
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Get the env token value (for use in axios interceptor)
|
|
1036
|
+
*/
|
|
1037
|
+
getEnvToken() {
|
|
1038
|
+
return BRAINGRID_API_TOKEN;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Fetch user profile from server using the env token
|
|
1042
|
+
* This is used to populate session data when using BRAINGRID_API_TOKEN
|
|
1043
|
+
*/
|
|
1044
|
+
async fetchProfileFromServer() {
|
|
1045
|
+
if (!BRAINGRID_API_TOKEN) {
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
try {
|
|
1049
|
+
this.logger.debug("[AUTH] Fetching profile using BRAINGRID_API_TOKEN");
|
|
1050
|
+
const response = await axiosWithRetry(
|
|
1051
|
+
{
|
|
1052
|
+
url: `${this.baseUrl}/api/v1/profile`,
|
|
1053
|
+
method: "POST",
|
|
1054
|
+
headers: {
|
|
1055
|
+
"Content-Type": "application/json",
|
|
1056
|
+
Authorization: `Bearer ${BRAINGRID_API_TOKEN}`
|
|
1057
|
+
},
|
|
1058
|
+
maxRedirects: 0,
|
|
1059
|
+
validateStatus: (status) => status < 500
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
maxRetries: 2,
|
|
1063
|
+
initialDelay: 500
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1066
|
+
if (response.status !== 200) {
|
|
1067
|
+
this.logger.warn(`[AUTH] Profile fetch failed with status ${response.status}`);
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
const profileData = response.data;
|
|
1071
|
+
if (profileData.error || !profileData.user || !profileData.organization) {
|
|
1072
|
+
this.logger.warn("[AUTH] Profile response missing user or organization data");
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
const user = {
|
|
1076
|
+
object: "user",
|
|
1077
|
+
id: profileData.user.id,
|
|
1078
|
+
email: profileData.user.email,
|
|
1079
|
+
emailVerified: true,
|
|
1080
|
+
firstName: profileData.user.firstName || "",
|
|
1081
|
+
lastName: profileData.user.lastName || "",
|
|
1082
|
+
profilePictureUrl: profileData.user.avatar || "",
|
|
1083
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1084
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1085
|
+
lastSignInAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1086
|
+
externalId: null,
|
|
1087
|
+
metadata: {
|
|
1088
|
+
username: profileData.user.username,
|
|
1089
|
+
organizationId: profileData.organization.id,
|
|
1090
|
+
organizationName: profileData.organization.name
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
const session = {
|
|
1094
|
+
user,
|
|
1095
|
+
sealed_session: BRAINGRID_API_TOKEN,
|
|
1096
|
+
organization_id: profileData.organization.id,
|
|
1097
|
+
created_at: /* @__PURE__ */ new Date(),
|
|
1098
|
+
updated_at: /* @__PURE__ */ new Date(),
|
|
1099
|
+
login_time: /* @__PURE__ */ new Date()
|
|
1100
|
+
};
|
|
1101
|
+
this.envTokenSession = session;
|
|
1102
|
+
this.logger.debug("[AUTH] Successfully fetched profile from env token");
|
|
1103
|
+
return session;
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
this.logger.error("[AUTH] Error fetching profile with env token:", { error });
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1014
1109
|
async isAuthenticated(forceValidation = false) {
|
|
1015
1110
|
try {
|
|
1111
|
+
if (BRAINGRID_API_TOKEN) {
|
|
1112
|
+
this.logger.debug("[AUTH] Using BRAINGRID_API_TOKEN (sandbox mode)");
|
|
1113
|
+
if (isJWTExpired(BRAINGRID_API_TOKEN)) {
|
|
1114
|
+
this.logger.warn("[AUTH] Sandbox session expired");
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
const session2 = await this.getStoredSession();
|
|
1118
|
+
return session2 !== null;
|
|
1119
|
+
}
|
|
1016
1120
|
const session = await this.getStoredSession();
|
|
1017
1121
|
if (!session) {
|
|
1018
1122
|
this.logger.debug("[AUTH] No stored session found");
|
|
@@ -1146,6 +1250,12 @@ var BraingridAuth = class {
|
|
|
1146
1250
|
}
|
|
1147
1251
|
}
|
|
1148
1252
|
async getStoredSession() {
|
|
1253
|
+
if (BRAINGRID_API_TOKEN) {
|
|
1254
|
+
if (this.envTokenSession) {
|
|
1255
|
+
return this.envTokenSession;
|
|
1256
|
+
}
|
|
1257
|
+
return await this.fetchProfileFromServer();
|
|
1258
|
+
}
|
|
1149
1259
|
try {
|
|
1150
1260
|
const sessionData = await credentialStore.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
1151
1261
|
if (!sessionData) return null;
|
|
@@ -1174,12 +1284,23 @@ var BraingridAuth = class {
|
|
|
1174
1284
|
this.loginTime = nowMs;
|
|
1175
1285
|
}
|
|
1176
1286
|
async clearSession() {
|
|
1287
|
+
if (BRAINGRID_API_TOKEN) {
|
|
1288
|
+
this.logger.debug("[AUTH] Session managed by sandbox environment - clear is no-op");
|
|
1289
|
+
this.envTokenSession = null;
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1177
1292
|
await credentialStore.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
1178
1293
|
await credentialStore.deletePassword(KEYCHAIN_SERVICE, "refresh-token");
|
|
1179
1294
|
this.refreshTokenValue = void 0;
|
|
1180
1295
|
this.lastValidationTime = 0;
|
|
1181
1296
|
this.lastValidationResult = false;
|
|
1182
1297
|
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Get sandbox-specific error message for expired/invalid tokens
|
|
1300
|
+
*/
|
|
1301
|
+
getSandboxExpiredMessage() {
|
|
1302
|
+
return "Sandbox session expired. Your sandbox environment has a 5-hour limit.\nPlease create a new sandbox to continue.";
|
|
1303
|
+
}
|
|
1183
1304
|
async handleAuthenticationError() {
|
|
1184
1305
|
this.lastValidationTime = 0;
|
|
1185
1306
|
this.lastValidationResult = false;
|
|
@@ -1837,10 +1958,10 @@ var LocalProjectConfigSchema = z.object({
|
|
|
1837
1958
|
// src/utils/git.ts
|
|
1838
1959
|
import { exec } from "child_process";
|
|
1839
1960
|
import { promisify } from "util";
|
|
1840
|
-
var
|
|
1961
|
+
var execAsync2 = promisify(exec);
|
|
1841
1962
|
async function isGitRepository() {
|
|
1842
1963
|
try {
|
|
1843
|
-
const { stdout } = await
|
|
1964
|
+
const { stdout } = await execAsync2("git rev-parse --is-inside-work-tree");
|
|
1844
1965
|
return stdout.trim() === "true";
|
|
1845
1966
|
} catch {
|
|
1846
1967
|
return false;
|
|
@@ -1848,7 +1969,7 @@ async function isGitRepository() {
|
|
|
1848
1969
|
}
|
|
1849
1970
|
async function getRemoteUrl() {
|
|
1850
1971
|
try {
|
|
1851
|
-
const { stdout } = await
|
|
1972
|
+
const { stdout } = await execAsync2("git config --get remote.origin.url");
|
|
1852
1973
|
return stdout.trim() || null;
|
|
1853
1974
|
} catch {
|
|
1854
1975
|
return null;
|
|
@@ -1874,7 +1995,7 @@ function parseGitHubRepo(url) {
|
|
|
1874
1995
|
}
|
|
1875
1996
|
async function getCurrentBranch() {
|
|
1876
1997
|
try {
|
|
1877
|
-
const { stdout } = await
|
|
1998
|
+
const { stdout } = await execAsync2("git rev-parse --abbrev-ref HEAD");
|
|
1878
1999
|
return stdout.trim() || null;
|
|
1879
2000
|
} catch {
|
|
1880
2001
|
return null;
|
|
@@ -1890,7 +2011,7 @@ function parseRequirementFromBranch(branchName) {
|
|
|
1890
2011
|
}
|
|
1891
2012
|
async function getGitRoot() {
|
|
1892
2013
|
try {
|
|
1893
|
-
const { stdout } = await
|
|
2014
|
+
const { stdout } = await execAsync2("git rev-parse --show-toplevel");
|
|
1894
2015
|
return stdout.trim() || null;
|
|
1895
2016
|
} catch {
|
|
1896
2017
|
return null;
|
|
@@ -1900,12 +2021,12 @@ async function getGitUser() {
|
|
|
1900
2021
|
let name = null;
|
|
1901
2022
|
let email = null;
|
|
1902
2023
|
try {
|
|
1903
|
-
const { stdout: nameStdout } = await
|
|
2024
|
+
const { stdout: nameStdout } = await execAsync2("git config --get user.name");
|
|
1904
2025
|
name = nameStdout.trim() || null;
|
|
1905
2026
|
} catch {
|
|
1906
2027
|
}
|
|
1907
2028
|
try {
|
|
1908
|
-
const { stdout: emailStdout } = await
|
|
2029
|
+
const { stdout: emailStdout } = await execAsync2("git config --get user.email");
|
|
1909
2030
|
email = emailStdout.trim() || null;
|
|
1910
2031
|
} catch {
|
|
1911
2032
|
}
|
|
@@ -3828,6 +3949,31 @@ var RequirementService = class {
|
|
|
3828
3949
|
const response = await this.axios.post(url, data, { headers });
|
|
3829
3950
|
return response.data;
|
|
3830
3951
|
}
|
|
3952
|
+
async createGitBranch(projectId, requirementId, data) {
|
|
3953
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/create-git-branch`;
|
|
3954
|
+
const headers = this.getHeaders();
|
|
3955
|
+
const response = await this.axios.post(url, data, { headers });
|
|
3956
|
+
return response.data;
|
|
3957
|
+
}
|
|
3958
|
+
async reviewAcceptance(projectId, requirementId, data, onChunk) {
|
|
3959
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/review/acceptance`;
|
|
3960
|
+
const headers = this.getHeaders();
|
|
3961
|
+
const response = await this.axios.post(url, data, {
|
|
3962
|
+
headers,
|
|
3963
|
+
responseType: "stream"
|
|
3964
|
+
});
|
|
3965
|
+
return new Promise((resolve, reject) => {
|
|
3966
|
+
response.data.on("data", (chunk) => {
|
|
3967
|
+
onChunk(chunk.toString());
|
|
3968
|
+
});
|
|
3969
|
+
response.data.on("end", () => {
|
|
3970
|
+
resolve();
|
|
3971
|
+
});
|
|
3972
|
+
response.data.on("error", (error) => {
|
|
3973
|
+
reject(error);
|
|
3974
|
+
});
|
|
3975
|
+
});
|
|
3976
|
+
}
|
|
3831
3977
|
};
|
|
3832
3978
|
|
|
3833
3979
|
// src/handlers/requirement.handlers.ts
|
|
@@ -4464,6 +4610,190 @@ async function handleRequirementBuild(opts) {
|
|
|
4464
4610
|
};
|
|
4465
4611
|
}
|
|
4466
4612
|
}
|
|
4613
|
+
function slugify(text) {
|
|
4614
|
+
return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 40);
|
|
4615
|
+
}
|
|
4616
|
+
async function handleCreateGitBranch(opts) {
|
|
4617
|
+
let stopSpinner = null;
|
|
4618
|
+
try {
|
|
4619
|
+
const { requirementService, auth } = getServices2();
|
|
4620
|
+
const isAuthenticated = await auth.isAuthenticated();
|
|
4621
|
+
if (!isAuthenticated) {
|
|
4622
|
+
return {
|
|
4623
|
+
success: false,
|
|
4624
|
+
message: chalk7.red("\u274C Not authenticated. Please run `braingrid login` first.")
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
const format = opts.format || "table";
|
|
4628
|
+
if (!["table", "json", "markdown"].includes(format)) {
|
|
4629
|
+
return {
|
|
4630
|
+
success: false,
|
|
4631
|
+
message: chalk7.red(
|
|
4632
|
+
`\u274C Invalid format: ${format}. Supported formats: table, json, markdown`
|
|
4633
|
+
)
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
4636
|
+
const requirementResult = await workspaceManager.getRequirement(opts.id);
|
|
4637
|
+
if (!requirementResult.success) {
|
|
4638
|
+
return {
|
|
4639
|
+
success: false,
|
|
4640
|
+
message: requirementResult.error
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4643
|
+
const requirementId = requirementResult.requirementId;
|
|
4644
|
+
const workspace = await workspaceManager.getProject(opts.project);
|
|
4645
|
+
if (!workspace.success) {
|
|
4646
|
+
return {
|
|
4647
|
+
success: false,
|
|
4648
|
+
message: workspace.error
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4651
|
+
const projectId = workspace.projectId;
|
|
4652
|
+
const normalizedId = normalizeRequirementId(requirementId);
|
|
4653
|
+
let branchName = opts.name;
|
|
4654
|
+
if (!branchName) {
|
|
4655
|
+
const user = await auth.getCurrentUser();
|
|
4656
|
+
if (!user) {
|
|
4657
|
+
return {
|
|
4658
|
+
success: false,
|
|
4659
|
+
message: chalk7.red("\u274C Could not get current user for branch name generation")
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
const requirement2 = await requirementService.getProjectRequirement(projectId, normalizedId);
|
|
4663
|
+
const username = slugify(user.email.split("@")[0]);
|
|
4664
|
+
const reqShortId = requirement2.short_id || normalizedId;
|
|
4665
|
+
const sluggedName = slugify(requirement2.name);
|
|
4666
|
+
branchName = `${username}/${reqShortId}-${sluggedName}`;
|
|
4667
|
+
}
|
|
4668
|
+
stopSpinner = showSpinner("Creating GitHub branch...", chalk7.gray);
|
|
4669
|
+
const response = await requirementService.createGitBranch(projectId, normalizedId, {
|
|
4670
|
+
branchName,
|
|
4671
|
+
baseBranch: opts.base
|
|
4672
|
+
});
|
|
4673
|
+
stopSpinner();
|
|
4674
|
+
stopSpinner = null;
|
|
4675
|
+
let output;
|
|
4676
|
+
switch (format) {
|
|
4677
|
+
case "json": {
|
|
4678
|
+
output = JSON.stringify(response, null, 2);
|
|
4679
|
+
break;
|
|
4680
|
+
}
|
|
4681
|
+
case "markdown": {
|
|
4682
|
+
output = `# Branch Created
|
|
4683
|
+
|
|
4684
|
+
`;
|
|
4685
|
+
output += `**Branch:** \`${response.branch.name}\`
|
|
4686
|
+
`;
|
|
4687
|
+
output += `**SHA:** \`${response.branch.sha.substring(0, 7)}\`
|
|
4688
|
+
|
|
4689
|
+
`;
|
|
4690
|
+
output += `## Checkout Command
|
|
4691
|
+
|
|
4692
|
+
`;
|
|
4693
|
+
output += `\`\`\`bash
|
|
4694
|
+
git fetch origin && git checkout ${response.branch.name}
|
|
4695
|
+
\`\`\`
|
|
4696
|
+
`;
|
|
4697
|
+
break;
|
|
4698
|
+
}
|
|
4699
|
+
case "table":
|
|
4700
|
+
default: {
|
|
4701
|
+
output = chalk7.green(`\u2705 Created branch: ${response.branch.name}
|
|
4702
|
+
|
|
4703
|
+
`);
|
|
4704
|
+
output += `${chalk7.bold("SHA:")} ${response.branch.sha.substring(0, 7)}
|
|
4705
|
+
`;
|
|
4706
|
+
output += `
|
|
4707
|
+
${chalk7.dim("To checkout:")} git fetch origin && git checkout ${response.branch.name}`;
|
|
4708
|
+
break;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return {
|
|
4712
|
+
success: true,
|
|
4713
|
+
message: output,
|
|
4714
|
+
data: response
|
|
4715
|
+
};
|
|
4716
|
+
} catch (error) {
|
|
4717
|
+
if (stopSpinner) {
|
|
4718
|
+
stopSpinner();
|
|
4719
|
+
}
|
|
4720
|
+
return {
|
|
4721
|
+
success: false,
|
|
4722
|
+
message: formatError(error, getResourceContext("requirement", opts.id || "unknown"))
|
|
4723
|
+
};
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
async function handleReviewAcceptance(opts) {
|
|
4727
|
+
try {
|
|
4728
|
+
const { requirementService, auth } = getServices2();
|
|
4729
|
+
const isAuthenticated = await auth.isAuthenticated();
|
|
4730
|
+
if (!isAuthenticated) {
|
|
4731
|
+
return {
|
|
4732
|
+
success: false,
|
|
4733
|
+
message: chalk7.red("\u274C Not authenticated. Please run `braingrid login` first.")
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
const requirementResult = await workspaceManager.getRequirement(opts.id);
|
|
4737
|
+
if (!requirementResult.success) {
|
|
4738
|
+
return {
|
|
4739
|
+
success: false,
|
|
4740
|
+
message: requirementResult.error
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
const requirementId = requirementResult.requirementId;
|
|
4744
|
+
const workspace = await workspaceManager.getProject(opts.project);
|
|
4745
|
+
if (!workspace.success) {
|
|
4746
|
+
return {
|
|
4747
|
+
success: false,
|
|
4748
|
+
message: workspace.error
|
|
4749
|
+
};
|
|
4750
|
+
}
|
|
4751
|
+
const projectId = workspace.projectId;
|
|
4752
|
+
const normalizedId = normalizeRequirementId(requirementId);
|
|
4753
|
+
let prNumber = opts.pr;
|
|
4754
|
+
if (!prNumber) {
|
|
4755
|
+
const { execAsync: execAsync5 } = await import("./command-execution-7JMH2DK4.js");
|
|
4756
|
+
try {
|
|
4757
|
+
const { stdout } = await execAsync5("gh pr view --json number -q .number");
|
|
4758
|
+
const detectedPrNumber = stdout.trim();
|
|
4759
|
+
if (!detectedPrNumber) {
|
|
4760
|
+
throw new Error("No PR number returned from gh CLI");
|
|
4761
|
+
}
|
|
4762
|
+
prNumber = detectedPrNumber;
|
|
4763
|
+
} catch {
|
|
4764
|
+
return {
|
|
4765
|
+
success: false,
|
|
4766
|
+
message: chalk7.red(
|
|
4767
|
+
"\u274C No PR specified and could not detect PR for current branch.\n Use --pr <number> or ensure you have an open PR for this branch."
|
|
4768
|
+
)
|
|
4769
|
+
};
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
console.log(chalk7.bold("\n\u{1F50D} AI Code Review\n"));
|
|
4773
|
+
let fullOutput = "";
|
|
4774
|
+
await requirementService.reviewAcceptance(
|
|
4775
|
+
projectId,
|
|
4776
|
+
normalizedId,
|
|
4777
|
+
{ pullRequest: prNumber },
|
|
4778
|
+
(chunk) => {
|
|
4779
|
+
process.stdout.write(chunk);
|
|
4780
|
+
fullOutput += chunk;
|
|
4781
|
+
}
|
|
4782
|
+
);
|
|
4783
|
+
console.log("\n");
|
|
4784
|
+
return {
|
|
4785
|
+
success: true,
|
|
4786
|
+
message: "",
|
|
4787
|
+
// Already printed via streaming
|
|
4788
|
+
data: { review: fullOutput }
|
|
4789
|
+
};
|
|
4790
|
+
} catch (error) {
|
|
4791
|
+
return {
|
|
4792
|
+
success: false,
|
|
4793
|
+
message: formatError(error, getResourceContext("requirement", opts.id || "unknown"))
|
|
4794
|
+
};
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4467
4797
|
|
|
4468
4798
|
// src/handlers/task.handlers.ts
|
|
4469
4799
|
import chalk8 from "chalk";
|
|
@@ -4512,6 +4842,12 @@ var TaskService = class {
|
|
|
4512
4842
|
const headers = this.getHeaders();
|
|
4513
4843
|
await this.axios.delete(url, { headers });
|
|
4514
4844
|
}
|
|
4845
|
+
async specifyTask(projectId, requirementId, data) {
|
|
4846
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/tasks/specify`;
|
|
4847
|
+
const headers = this.getHeaders();
|
|
4848
|
+
const response = await this.axios.post(url, data, { headers });
|
|
4849
|
+
return response.data;
|
|
4850
|
+
}
|
|
4515
4851
|
};
|
|
4516
4852
|
|
|
4517
4853
|
// src/handlers/task.handlers.ts
|
|
@@ -4977,6 +5313,117 @@ async function handleTaskDelete(id, opts) {
|
|
|
4977
5313
|
};
|
|
4978
5314
|
}
|
|
4979
5315
|
}
|
|
5316
|
+
async function handleTaskSpecify(opts) {
|
|
5317
|
+
let stopSpinner = null;
|
|
5318
|
+
try {
|
|
5319
|
+
const { taskService, auth } = getServices3();
|
|
5320
|
+
const isAuthenticated = await auth.isAuthenticated();
|
|
5321
|
+
if (!isAuthenticated) {
|
|
5322
|
+
return {
|
|
5323
|
+
success: false,
|
|
5324
|
+
message: chalk8.red("\u274C Not authenticated. Please run `braingrid login` first.")
|
|
5325
|
+
};
|
|
5326
|
+
}
|
|
5327
|
+
const format = opts.format || "table";
|
|
5328
|
+
if (!["table", "json", "markdown"].includes(format)) {
|
|
5329
|
+
return {
|
|
5330
|
+
success: false,
|
|
5331
|
+
message: chalk8.red(
|
|
5332
|
+
`\u274C Invalid format: ${format}. Supported formats: table, json, markdown`
|
|
5333
|
+
)
|
|
5334
|
+
};
|
|
5335
|
+
}
|
|
5336
|
+
const workspace = await workspaceManager.getProject(opts.project);
|
|
5337
|
+
if (!workspace.success) {
|
|
5338
|
+
return {
|
|
5339
|
+
success: false,
|
|
5340
|
+
message: workspace.error
|
|
5341
|
+
};
|
|
5342
|
+
}
|
|
5343
|
+
const projectId = workspace.projectId;
|
|
5344
|
+
const requirementResult = await workspaceManager.getRequirement(opts.requirement);
|
|
5345
|
+
if (!requirementResult.success) {
|
|
5346
|
+
return {
|
|
5347
|
+
success: false,
|
|
5348
|
+
message: requirementResult.error
|
|
5349
|
+
};
|
|
5350
|
+
}
|
|
5351
|
+
const requirementId = requirementResult.requirementId;
|
|
5352
|
+
if (opts.prompt.length < 10) {
|
|
5353
|
+
return {
|
|
5354
|
+
success: false,
|
|
5355
|
+
message: chalk8.red("\u274C Prompt must be at least 10 characters long")
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
if (opts.prompt.length > 5e3) {
|
|
5359
|
+
return {
|
|
5360
|
+
success: false,
|
|
5361
|
+
message: chalk8.red("\u274C Prompt must be no more than 5000 characters long")
|
|
5362
|
+
};
|
|
5363
|
+
}
|
|
5364
|
+
stopSpinner = showSpinner("Creating task from prompt...", chalk8.gray);
|
|
5365
|
+
const response = await taskService.specifyTask(projectId, requirementId, {
|
|
5366
|
+
prompt: opts.prompt
|
|
5367
|
+
});
|
|
5368
|
+
stopSpinner();
|
|
5369
|
+
stopSpinner = null;
|
|
5370
|
+
const config = getConfig();
|
|
5371
|
+
let output;
|
|
5372
|
+
switch (format) {
|
|
5373
|
+
case "json": {
|
|
5374
|
+
output = JSON.stringify(response, null, 2);
|
|
5375
|
+
break;
|
|
5376
|
+
}
|
|
5377
|
+
case "markdown": {
|
|
5378
|
+
output = formatTaskSpecifyMarkdown(response, config.apiUrl);
|
|
5379
|
+
break;
|
|
5380
|
+
}
|
|
5381
|
+
case "table":
|
|
5382
|
+
default: {
|
|
5383
|
+
output = formatTaskSpecifyTable(response);
|
|
5384
|
+
break;
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
return {
|
|
5388
|
+
success: true,
|
|
5389
|
+
message: output,
|
|
5390
|
+
data: response
|
|
5391
|
+
};
|
|
5392
|
+
} catch (error) {
|
|
5393
|
+
if (stopSpinner) {
|
|
5394
|
+
stopSpinner();
|
|
5395
|
+
}
|
|
5396
|
+
return {
|
|
5397
|
+
success: false,
|
|
5398
|
+
message: formatError(error, "specifying task")
|
|
5399
|
+
};
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
function formatTaskSpecifyTable(response) {
|
|
5403
|
+
const task2 = response.task;
|
|
5404
|
+
const lines = [];
|
|
5405
|
+
lines.push(chalk8.green(`\u2705 Created task ${response.requirement_short_id}/TASK-${task2.number}`));
|
|
5406
|
+
lines.push("");
|
|
5407
|
+
lines.push(`${chalk8.bold("Title:")} ${task2.title}`);
|
|
5408
|
+
lines.push(`${chalk8.bold("Status:")} ${task2.status}`);
|
|
5409
|
+
return lines.join("\n");
|
|
5410
|
+
}
|
|
5411
|
+
function formatTaskSpecifyMarkdown(response, _apiUrl) {
|
|
5412
|
+
const task2 = response.task;
|
|
5413
|
+
const lines = [];
|
|
5414
|
+
lines.push(`# Task ${response.requirement_short_id}/TASK-${task2.number}: ${task2.title}`);
|
|
5415
|
+
lines.push("");
|
|
5416
|
+
lines.push(`**Status:** ${task2.status}`);
|
|
5417
|
+
lines.push(`**Requirement:** ${response.requirement_short_id}`);
|
|
5418
|
+
lines.push(`**Project:** ${response.project_short_id}`);
|
|
5419
|
+
lines.push("");
|
|
5420
|
+
if (task2.content) {
|
|
5421
|
+
lines.push("## Content");
|
|
5422
|
+
lines.push("");
|
|
5423
|
+
lines.push(task2.content);
|
|
5424
|
+
}
|
|
5425
|
+
return lines.join("\n");
|
|
5426
|
+
}
|
|
4980
5427
|
|
|
4981
5428
|
// src/handlers/auth.handlers.ts
|
|
4982
5429
|
import chalk9 from "chalk";
|
|
@@ -4988,6 +5435,14 @@ function getAuth() {
|
|
|
4988
5435
|
async function handleLogin() {
|
|
4989
5436
|
try {
|
|
4990
5437
|
const auth = getAuth();
|
|
5438
|
+
if (BRAINGRID_API_TOKEN) {
|
|
5439
|
+
return {
|
|
5440
|
+
success: true,
|
|
5441
|
+
message: chalk9.blue(
|
|
5442
|
+
"\u2139\uFE0F Using BRAINGRID_API_TOKEN - already authenticated via sandbox environment."
|
|
5443
|
+
)
|
|
5444
|
+
};
|
|
5445
|
+
}
|
|
4991
5446
|
console.log(chalk9.blue("\u{1F510} Starting OAuth2 authentication flow..."));
|
|
4992
5447
|
console.log(chalk9.dim("Your browser will open to complete authentication.\n"));
|
|
4993
5448
|
const gitUser = await getGitUser();
|
|
@@ -5021,6 +5476,12 @@ async function handleLogin() {
|
|
|
5021
5476
|
async function handleLogout() {
|
|
5022
5477
|
try {
|
|
5023
5478
|
const auth = getAuth();
|
|
5479
|
+
if (BRAINGRID_API_TOKEN) {
|
|
5480
|
+
return {
|
|
5481
|
+
success: true,
|
|
5482
|
+
message: chalk9.blue("\u2139\uFE0F Session managed by sandbox environment - logout not required.")
|
|
5483
|
+
};
|
|
5484
|
+
}
|
|
5024
5485
|
await auth.clearSession();
|
|
5025
5486
|
return {
|
|
5026
5487
|
success: true,
|
|
@@ -5038,6 +5499,12 @@ async function handleWhoami() {
|
|
|
5038
5499
|
const auth = getAuth();
|
|
5039
5500
|
const isAuthenticated = await auth.isAuthenticated();
|
|
5040
5501
|
if (!isAuthenticated) {
|
|
5502
|
+
if (BRAINGRID_API_TOKEN) {
|
|
5503
|
+
return {
|
|
5504
|
+
success: false,
|
|
5505
|
+
message: chalk9.red(auth.getSandboxExpiredMessage())
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5041
5508
|
return {
|
|
5042
5509
|
success: false,
|
|
5043
5510
|
message: chalk9.yellow("\u26A0\uFE0F Not logged in. Run `braingrid login` to authenticate.")
|
|
@@ -5060,8 +5527,13 @@ async function handleWhoami() {
|
|
|
5060
5527
|
`;
|
|
5061
5528
|
output += `${chalk9.bold("Org ID:")} ${session.organization_id}
|
|
5062
5529
|
`;
|
|
5063
|
-
|
|
5530
|
+
if (BRAINGRID_API_TOKEN) {
|
|
5531
|
+
output += `${chalk9.bold("Auth:")} ${chalk9.cyan("Sandbox API Token")}
|
|
5532
|
+
`;
|
|
5533
|
+
} else {
|
|
5534
|
+
output += `${chalk9.bold("Session:")} ${new Date(session.created_at).toLocaleString()}
|
|
5064
5535
|
`;
|
|
5536
|
+
}
|
|
5065
5537
|
return {
|
|
5066
5538
|
success: true,
|
|
5067
5539
|
message: output,
|
|
@@ -5332,7 +5804,7 @@ var GitHubService = class {
|
|
|
5332
5804
|
import { exec as exec2 } from "child_process";
|
|
5333
5805
|
import { promisify as promisify2 } from "util";
|
|
5334
5806
|
import chalk11 from "chalk";
|
|
5335
|
-
var
|
|
5807
|
+
var execAsync3 = promisify2(exec2);
|
|
5336
5808
|
async function isGitInstalled() {
|
|
5337
5809
|
return isCliInstalled("git");
|
|
5338
5810
|
}
|
|
@@ -5345,7 +5817,7 @@ async function getGitVersion() {
|
|
|
5345
5817
|
async function installViaHomebrew() {
|
|
5346
5818
|
console.log(chalk11.blue("\u{1F4E6} Installing Git via Homebrew..."));
|
|
5347
5819
|
try {
|
|
5348
|
-
await
|
|
5820
|
+
await execAsync3("brew install git", {
|
|
5349
5821
|
timeout: 3e5
|
|
5350
5822
|
// 5 minutes
|
|
5351
5823
|
});
|
|
@@ -5373,7 +5845,7 @@ async function installViaXcodeSelect() {
|
|
|
5373
5845
|
console.log(chalk11.blue("\u{1F4E6} Installing Git via Xcode Command Line Tools..."));
|
|
5374
5846
|
console.log(chalk11.dim('A system dialog will appear - click "Install" to continue.\n'));
|
|
5375
5847
|
try {
|
|
5376
|
-
await
|
|
5848
|
+
await execAsync3("xcode-select --install", {
|
|
5377
5849
|
timeout: 6e5
|
|
5378
5850
|
// 10 minutes (user interaction required)
|
|
5379
5851
|
});
|
|
@@ -5414,7 +5886,7 @@ async function installGitWindows() {
|
|
|
5414
5886
|
}
|
|
5415
5887
|
console.log(chalk11.blue("\u{1F4E6} Installing Git via winget..."));
|
|
5416
5888
|
try {
|
|
5417
|
-
await
|
|
5889
|
+
await execAsync3("winget install --id Git.Git -e --source winget --silent", {
|
|
5418
5890
|
timeout: 3e5
|
|
5419
5891
|
// 5 minutes
|
|
5420
5892
|
});
|
|
@@ -5469,7 +5941,7 @@ async function installGitLinux() {
|
|
|
5469
5941
|
message: chalk11.red(`\u274C Unsupported package manager: ${packageManager.name}`)
|
|
5470
5942
|
};
|
|
5471
5943
|
}
|
|
5472
|
-
await
|
|
5944
|
+
await execAsync3(installCommand, {
|
|
5473
5945
|
timeout: 3e5
|
|
5474
5946
|
// 5 minutes
|
|
5475
5947
|
});
|
|
@@ -5671,28 +6143,6 @@ import { select as select2 } from "@inquirer/prompts";
|
|
|
5671
6143
|
import * as path4 from "path";
|
|
5672
6144
|
import * as fs4 from "fs/promises";
|
|
5673
6145
|
|
|
5674
|
-
// src/utils/command-execution.ts
|
|
5675
|
-
import { exec as exec3, spawn } from "child_process";
|
|
5676
|
-
import { promisify as promisify3 } from "util";
|
|
5677
|
-
var execAsyncReal = promisify3(exec3);
|
|
5678
|
-
async function execAsync3(command, options, isTestMode = false, mockExecHandler) {
|
|
5679
|
-
if (isTestMode && mockExecHandler) {
|
|
5680
|
-
return mockExecHandler(command);
|
|
5681
|
-
}
|
|
5682
|
-
const defaultOptions = {
|
|
5683
|
-
maxBuffer: 1024 * 1024 * 10,
|
|
5684
|
-
// 10MB default
|
|
5685
|
-
timeout: 3e5,
|
|
5686
|
-
// 5 minutes
|
|
5687
|
-
...options
|
|
5688
|
-
};
|
|
5689
|
-
if (command.includes("claude")) {
|
|
5690
|
-
defaultOptions.maxBuffer = 1024 * 1024 * 50;
|
|
5691
|
-
defaultOptions.timeout = 6e5;
|
|
5692
|
-
}
|
|
5693
|
-
return execAsyncReal(command, defaultOptions);
|
|
5694
|
-
}
|
|
5695
|
-
|
|
5696
6146
|
// src/services/setup-service.ts
|
|
5697
6147
|
import * as fs3 from "fs/promises";
|
|
5698
6148
|
import * as path3 from "path";
|
|
@@ -5743,7 +6193,7 @@ async function fetchFileFromGitHub(path6) {
|
|
|
5743
6193
|
return withRetry(async () => {
|
|
5744
6194
|
try {
|
|
5745
6195
|
const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
|
|
5746
|
-
const { stdout } = await
|
|
6196
|
+
const { stdout } = await execAsync(command);
|
|
5747
6197
|
const response = JSON.parse(stdout);
|
|
5748
6198
|
if (response.type !== "file") {
|
|
5749
6199
|
throw new Error(`Path ${path6} is not a file`);
|
|
@@ -5766,7 +6216,7 @@ async function listGitHubDirectory(path6) {
|
|
|
5766
6216
|
return withRetry(async () => {
|
|
5767
6217
|
try {
|
|
5768
6218
|
const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
|
|
5769
|
-
const { stdout } = await
|
|
6219
|
+
const { stdout } = await execAsync(command);
|
|
5770
6220
|
const response = JSON.parse(stdout);
|
|
5771
6221
|
if (!Array.isArray(response)) {
|
|
5772
6222
|
throw new Error(`Path ${path6} is not a directory`);
|
|
@@ -5899,7 +6349,7 @@ async function fileExists(filePath) {
|
|
|
5899
6349
|
}
|
|
5900
6350
|
async function checkPrerequisites() {
|
|
5901
6351
|
try {
|
|
5902
|
-
await
|
|
6352
|
+
await execAsync("gh --version");
|
|
5903
6353
|
} catch {
|
|
5904
6354
|
return {
|
|
5905
6355
|
success: false,
|
|
@@ -5907,7 +6357,7 @@ async function checkPrerequisites() {
|
|
|
5907
6357
|
};
|
|
5908
6358
|
}
|
|
5909
6359
|
try {
|
|
5910
|
-
await
|
|
6360
|
+
await execAsync("gh auth status");
|
|
5911
6361
|
} catch {
|
|
5912
6362
|
return {
|
|
5913
6363
|
success: false,
|
|
@@ -6281,10 +6731,10 @@ async function checkAndShowUpdateWarning() {
|
|
|
6281
6731
|
import { access as access3 } from "fs/promises";
|
|
6282
6732
|
|
|
6283
6733
|
// src/utils/github-repo.ts
|
|
6284
|
-
import { exec as
|
|
6285
|
-
import { promisify as
|
|
6734
|
+
import { exec as exec3 } from "child_process";
|
|
6735
|
+
import { promisify as promisify3 } from "util";
|
|
6286
6736
|
import path5 from "path";
|
|
6287
|
-
var execAsync4 =
|
|
6737
|
+
var execAsync4 = promisify3(exec3);
|
|
6288
6738
|
async function initGitRepo() {
|
|
6289
6739
|
try {
|
|
6290
6740
|
await execAsync4("git init");
|
|
@@ -7293,6 +7743,30 @@ requirement.command("build [id]").description(
|
|
|
7293
7743
|
process.exit(1);
|
|
7294
7744
|
}
|
|
7295
7745
|
});
|
|
7746
|
+
requirement.command("create-branch [id]").description(
|
|
7747
|
+
"Create a GitHub branch for a requirement (auto-detects ID from git branch if not provided)"
|
|
7748
|
+
).option(
|
|
7749
|
+
"-p, --project <id>",
|
|
7750
|
+
"project ID (auto-detects from .braingrid/project.json if not provided)"
|
|
7751
|
+
).option("--name <name>", "branch name (auto-generates {username}/REQ-123-slug if not provided)").option("--base <branch>", "base branch to create from (defaults to repository default branch)").option("--format <format>", "output format (table, json, markdown)", "table").action(async (id, opts) => {
|
|
7752
|
+
const result = await handleCreateGitBranch({ ...opts, id });
|
|
7753
|
+
console.log(result.message);
|
|
7754
|
+
if (!result.success) {
|
|
7755
|
+
process.exit(1);
|
|
7756
|
+
}
|
|
7757
|
+
});
|
|
7758
|
+
requirement.command("review [id]").description("Run AI code review for a requirement PR (auto-detects ID and PR from git branch)").option(
|
|
7759
|
+
"-p, --project <id>",
|
|
7760
|
+
"project ID (auto-detects from .braingrid/project.json if not provided)"
|
|
7761
|
+
).option("--pr <number>", "PR number or URL (auto-detects from current branch if not provided)").action(async (id, opts) => {
|
|
7762
|
+
const result = await handleReviewAcceptance({ ...opts, id });
|
|
7763
|
+
if (result.message) {
|
|
7764
|
+
console.log(result.message);
|
|
7765
|
+
}
|
|
7766
|
+
if (!result.success) {
|
|
7767
|
+
process.exit(1);
|
|
7768
|
+
}
|
|
7769
|
+
});
|
|
7296
7770
|
var task = program.command("task").description("Manage tasks");
|
|
7297
7771
|
task.command("list").description("List tasks for a requirement").option("-r, --requirement <id>", "requirement ID (REQ-456, auto-detects project if initialized)").option("-p, --project <id>", "project ID (PROJ-123, optional if project is initialized)").option("--format <format>", "output format (table, json, xml, markdown)", "markdown").option("--page <page>", "page number for pagination", "1").option("--limit <limit>", "number of tasks per page", "20").action(async (opts) => {
|
|
7298
7772
|
const result = await handleTaskList(opts);
|
|
@@ -7336,6 +7810,16 @@ task.command("delete <id>").description("Delete a task").option("-r, --requireme
|
|
|
7336
7810
|
process.exit(1);
|
|
7337
7811
|
}
|
|
7338
7812
|
});
|
|
7813
|
+
task.command("specify").description("Create a single task from a prompt using AI").option(
|
|
7814
|
+
"-r, --requirement <id>",
|
|
7815
|
+
"requirement ID (REQ-456, auto-detects from git branch if not provided)"
|
|
7816
|
+
).option("-p, --project <id>", "project ID (PROJ-123, optional if project is initialized)").requiredOption("--prompt <prompt>", "task description (10-5000 characters)").option("--format <format>", "output format (table, json, markdown)", "table").action(async (opts) => {
|
|
7817
|
+
const result = await handleTaskSpecify(opts);
|
|
7818
|
+
console.log(result.message);
|
|
7819
|
+
if (!result.success) {
|
|
7820
|
+
process.exit(1);
|
|
7821
|
+
}
|
|
7822
|
+
});
|
|
7339
7823
|
program.command("completion [shell]").description("Generate shell completion script (bash, zsh)").option("--setup", "automatically install completion for current shell").option("-s, --shell <shell>", "shell type (bash, zsh)").action(async (shell, opts) => {
|
|
7340
7824
|
const result = await handleCompletion(shell, opts);
|
|
7341
7825
|
console.log(result.message);
|