@braid-cloud/cli 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/dist/index.js +1083 -324
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47,6 +47,10 @@ __export(config_exports, {
|
|
|
47
47
|
loadUserConfigAsync: () => loadUserConfigAsync,
|
|
48
48
|
saveConfig: () => saveConfig,
|
|
49
49
|
saveConfigAsync: () => saveConfigAsync,
|
|
50
|
+
saveProjectConfig: () => saveProjectConfig,
|
|
51
|
+
saveProjectConfigAsync: () => saveProjectConfigAsync,
|
|
52
|
+
saveUserConfig: () => saveUserConfig,
|
|
53
|
+
saveUserConfigAsync: () => saveUserConfigAsync,
|
|
50
54
|
setApiKey: () => setApiKey,
|
|
51
55
|
setApiKeyAsync: () => setApiKeyAsync
|
|
52
56
|
});
|
|
@@ -56,7 +60,7 @@ import { homedir } from "os";
|
|
|
56
60
|
import { dirname, join, parse } from "path";
|
|
57
61
|
import process2 from "process";
|
|
58
62
|
import { Data, Effect, pipe } from "effect";
|
|
59
|
-
var CONFIG_DIR, CONFIG_FILE, PROJECT_CONFIG_FILENAME, USER_CONFIG_FILENAME, ConfigReadError, ConfigWriteError, findConfigFile, findProjectConfigFile, findUserConfigFile, loadProjectConfig, loadUserConfig, isValidServerUrl, resolveServerUrlFromConfig, applyConfigSource, applyEnvOverrides, createDefaultMergedConfig, loadMergedConfig, loadConfig, saveConfig, getApiKey, setApiKey, getServerUrl, clearApiKey, loadConfigAsync, loadProjectConfigAsync, loadUserConfigAsync, loadMergedConfigAsync, findProjectConfigFileAsync, findUserConfigFileAsync, saveConfigAsync, getApiKeyAsync, setApiKeyAsync, getServerUrlAsync, clearApiKeyAsync;
|
|
63
|
+
var CONFIG_DIR, CONFIG_FILE, PROJECT_CONFIG_FILENAME, USER_CONFIG_FILENAME, ConfigReadError, ConfigWriteError, findConfigFile, findProjectConfigFile, findUserConfigFile, loadProjectConfig, loadUserConfig, resolveUserConfigWritePath, saveUserConfig, saveProjectConfig, isValidServerUrl, resolveServerUrlFromConfig, applyConfigSource, applyEnvOverrides, createDefaultMergedConfig, loadMergedConfig, loadConfig, saveConfig, getApiKey, setApiKey, getServerUrl, clearApiKey, loadConfigAsync, loadProjectConfigAsync, loadUserConfigAsync, loadMergedConfigAsync, findProjectConfigFileAsync, findUserConfigFileAsync, saveConfigAsync, saveUserConfigAsync, saveProjectConfigAsync, getApiKeyAsync, setApiKeyAsync, getServerUrlAsync, clearApiKeyAsync;
|
|
60
64
|
var init_config = __esm({
|
|
61
65
|
"src/lib/config.ts"() {
|
|
62
66
|
"use strict";
|
|
@@ -107,6 +111,39 @@ var init_config = __esm({
|
|
|
107
111
|
},
|
|
108
112
|
catch: () => void 0
|
|
109
113
|
}).pipe(Effect.orElseSucceed(() => void 0));
|
|
114
|
+
resolveUserConfigWritePath = (startDir = process2.cwd()) => findUserConfigFile(startDir) ?? join(startDir, USER_CONFIG_FILENAME);
|
|
115
|
+
saveUserConfig = (config, startDir = process2.cwd()) => {
|
|
116
|
+
const targetPath = resolveUserConfigWritePath(startDir);
|
|
117
|
+
return pipe(
|
|
118
|
+
Effect.tryPromise({
|
|
119
|
+
try: async () => {
|
|
120
|
+
await mkdir(dirname(targetPath), { recursive: true, mode: 448 });
|
|
121
|
+
await writeFile(targetPath, JSON.stringify(config, null, 2), {
|
|
122
|
+
encoding: "utf-8",
|
|
123
|
+
mode: 384
|
|
124
|
+
});
|
|
125
|
+
return targetPath;
|
|
126
|
+
},
|
|
127
|
+
catch: (e) => new ConfigWriteError({ path: targetPath, cause: e })
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
saveProjectConfig = (config, startDir = process2.cwd()) => {
|
|
132
|
+
const targetPath = join(startDir, PROJECT_CONFIG_FILENAME);
|
|
133
|
+
return pipe(
|
|
134
|
+
Effect.tryPromise({
|
|
135
|
+
try: async () => {
|
|
136
|
+
await mkdir(dirname(targetPath), { recursive: true, mode: 448 });
|
|
137
|
+
await writeFile(targetPath, JSON.stringify(config, null, 2), {
|
|
138
|
+
encoding: "utf-8",
|
|
139
|
+
mode: 384
|
|
140
|
+
});
|
|
141
|
+
return targetPath;
|
|
142
|
+
},
|
|
143
|
+
catch: (e) => new ConfigWriteError({ path: targetPath, cause: e })
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
};
|
|
110
147
|
isValidServerUrl = (url) => {
|
|
111
148
|
try {
|
|
112
149
|
const parsed = new URL(url);
|
|
@@ -136,12 +173,24 @@ var init_config = __esm({
|
|
|
136
173
|
if (config.profile) {
|
|
137
174
|
merged.profile = config.profile;
|
|
138
175
|
}
|
|
176
|
+
if (config.org) {
|
|
177
|
+
merged.org = config.org;
|
|
178
|
+
}
|
|
139
179
|
if (config.orgProjects) {
|
|
140
180
|
merged.orgProjects = config.orgProjects;
|
|
141
181
|
}
|
|
142
182
|
if (config.personalProjects) {
|
|
143
183
|
merged.personalProjects = config.personalProjects;
|
|
144
184
|
}
|
|
185
|
+
if (config.ruleIds) {
|
|
186
|
+
merged.ruleIds = config.ruleIds;
|
|
187
|
+
}
|
|
188
|
+
if (config.excludedRuleIds) {
|
|
189
|
+
merged.excludedRuleIds = config.excludedRuleIds;
|
|
190
|
+
}
|
|
191
|
+
if (config.resolveOverlays !== void 0) {
|
|
192
|
+
merged.resolveOverlays = config.resolveOverlays;
|
|
193
|
+
}
|
|
145
194
|
if (config.includeUserGlobal !== void 0) {
|
|
146
195
|
merged.includeUserGlobal = config.includeUserGlobal;
|
|
147
196
|
}
|
|
@@ -261,6 +310,8 @@ var init_config = __esm({
|
|
|
261
310
|
findProjectConfigFileAsync = (startDir) => findProjectConfigFile(startDir);
|
|
262
311
|
findUserConfigFileAsync = (startDir) => findUserConfigFile(startDir);
|
|
263
312
|
saveConfigAsync = (config) => Effect.runPromise(saveConfig(config));
|
|
313
|
+
saveUserConfigAsync = (config, startDir) => Effect.runPromise(saveUserConfig(config, startDir));
|
|
314
|
+
saveProjectConfigAsync = (config, startDir) => Effect.runPromise(saveProjectConfig(config, startDir));
|
|
264
315
|
getApiKeyAsync = () => Effect.runPromise(getApiKey());
|
|
265
316
|
setApiKeyAsync = (apiKey) => Effect.runPromise(setApiKey(apiKey));
|
|
266
317
|
getServerUrlAsync = () => Effect.runPromise(getServerUrl());
|
|
@@ -268,172 +319,864 @@ var init_config = __esm({
|
|
|
268
319
|
}
|
|
269
320
|
});
|
|
270
321
|
|
|
271
|
-
// src/
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
322
|
+
// src/lib/api.ts
|
|
323
|
+
import { Data as Data2, Effect as Effect2, pipe as pipe2 } from "effect";
|
|
324
|
+
var TRAILING_SLASH_REGEX, ApiError, AuthenticationError, NetworkError, resolveApiKey, resolveServerUrl, parseResponse, buildApiUrl, buildExportUrl, executeApiRequest, parseScopeOptionsResponse, fetchScopeOptions, buildRuleOptionsUrl, fetchRuleOptions, fetchSkills, validateApiKey, fetchSkillsAsync, validateApiKeyAsync, fetchScopeOptionsAsync, fetchRuleOptionsAsync;
|
|
325
|
+
var init_api = __esm({
|
|
326
|
+
"src/lib/api.ts"() {
|
|
327
|
+
"use strict";
|
|
328
|
+
init_esm_shims();
|
|
329
|
+
init_config();
|
|
330
|
+
TRAILING_SLASH_REGEX = /\/$/;
|
|
331
|
+
ApiError = class extends Data2.TaggedError("ApiError") {
|
|
332
|
+
};
|
|
333
|
+
AuthenticationError = class extends Data2.TaggedError("AuthenticationError") {
|
|
334
|
+
};
|
|
335
|
+
NetworkError = class extends Data2.TaggedError("NetworkError") {
|
|
336
|
+
};
|
|
337
|
+
resolveApiKey = (optionsApiKey) => {
|
|
338
|
+
if (optionsApiKey) {
|
|
339
|
+
return Effect2.succeed(optionsApiKey);
|
|
340
|
+
}
|
|
341
|
+
return pipe2(
|
|
342
|
+
Effect2.tryPromise({
|
|
343
|
+
try: () => getApiKeyAsync(),
|
|
344
|
+
catch: () => new NetworkError({ message: "Failed to read config" })
|
|
345
|
+
}),
|
|
346
|
+
Effect2.flatMap(
|
|
347
|
+
(key) => key ? Effect2.succeed(key) : Effect2.fail(
|
|
348
|
+
new AuthenticationError({
|
|
349
|
+
message: 'No API key configured. Run "braid auth" to authenticate.'
|
|
350
|
+
})
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
resolveServerUrl = (optionsServerUrl) => {
|
|
356
|
+
if (optionsServerUrl) {
|
|
357
|
+
return Effect2.succeed(optionsServerUrl);
|
|
358
|
+
}
|
|
359
|
+
return Effect2.tryPromise({
|
|
360
|
+
try: () => getServerUrlAsync(),
|
|
361
|
+
catch: () => new NetworkError({ message: "Failed to read config" })
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
parseResponse = (response, json) => {
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
const errorResponse = json;
|
|
367
|
+
if (response.status === 401) {
|
|
368
|
+
return Effect2.fail(
|
|
369
|
+
new AuthenticationError({
|
|
370
|
+
message: errorResponse.error || "Invalid or expired API key. Run 'braid auth' to re-authenticate."
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return Effect2.fail(
|
|
375
|
+
new ApiError({
|
|
376
|
+
message: errorResponse.error || "API request failed",
|
|
377
|
+
code: errorResponse.code || "UNKNOWN_ERROR",
|
|
378
|
+
status: response.status
|
|
379
|
+
})
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return Effect2.succeed(json);
|
|
383
|
+
};
|
|
384
|
+
buildApiUrl = (serverUrl, path2) => {
|
|
385
|
+
const baseUrl = serverUrl.replace(TRAILING_SLASH_REGEX, "");
|
|
386
|
+
return new URL(`${baseUrl}${path2}`);
|
|
387
|
+
};
|
|
388
|
+
buildExportUrl = (serverUrl, options) => {
|
|
389
|
+
const url = buildApiUrl(serverUrl, "/api/skills/export");
|
|
390
|
+
if (options.profile) {
|
|
391
|
+
url.searchParams.set("profile", options.profile);
|
|
392
|
+
}
|
|
393
|
+
if (options.orgProjects && options.orgProjects.length > 0) {
|
|
394
|
+
url.searchParams.set("orgProjects", options.orgProjects.join(","));
|
|
395
|
+
}
|
|
396
|
+
if (options.personalProjects && options.personalProjects.length > 0) {
|
|
397
|
+
url.searchParams.set(
|
|
398
|
+
"personalProjects",
|
|
399
|
+
options.personalProjects.join(",")
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (options.includeUserGlobal !== void 0) {
|
|
403
|
+
url.searchParams.set(
|
|
404
|
+
"includeUserGlobal",
|
|
405
|
+
String(options.includeUserGlobal)
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (options.includeOrgGlobal !== void 0) {
|
|
409
|
+
url.searchParams.set("includeOrgGlobal", String(options.includeOrgGlobal));
|
|
410
|
+
}
|
|
411
|
+
if (options.ruleIds && options.ruleIds.length > 0) {
|
|
412
|
+
url.searchParams.set("ruleIds", options.ruleIds.join(","));
|
|
413
|
+
}
|
|
414
|
+
if (options.excludedRuleIds && options.excludedRuleIds.length > 0) {
|
|
415
|
+
url.searchParams.set("excludedRuleIds", options.excludedRuleIds.join(","));
|
|
416
|
+
}
|
|
417
|
+
if (options.resolveOverlays !== void 0) {
|
|
418
|
+
url.searchParams.set("resolveOverlays", String(options.resolveOverlays));
|
|
419
|
+
}
|
|
420
|
+
return url;
|
|
421
|
+
};
|
|
422
|
+
executeApiRequest = (url, apiKey, serverUrl) => pipe2(
|
|
423
|
+
Effect2.tryPromise({
|
|
424
|
+
try: () => fetch(url.toString(), {
|
|
425
|
+
method: "GET",
|
|
426
|
+
headers: {
|
|
427
|
+
Authorization: `Bearer ${apiKey}`,
|
|
428
|
+
"Content-Type": "application/json"
|
|
429
|
+
}
|
|
430
|
+
}),
|
|
431
|
+
catch: (e) => new NetworkError({
|
|
432
|
+
message: `Failed to connect to ${serverUrl}`,
|
|
433
|
+
cause: e
|
|
434
|
+
})
|
|
435
|
+
}),
|
|
436
|
+
Effect2.flatMap(
|
|
437
|
+
(response) => pipe2(
|
|
438
|
+
Effect2.tryPromise({
|
|
439
|
+
try: () => response.json(),
|
|
440
|
+
catch: () => new NetworkError({ message: "Failed to parse API response" })
|
|
441
|
+
}),
|
|
442
|
+
Effect2.flatMap((json) => parseResponse(response, json))
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
);
|
|
446
|
+
parseScopeOptionsResponse = (response, json) => {
|
|
447
|
+
if (!response.ok) {
|
|
448
|
+
const errorResponse = json;
|
|
449
|
+
if (response.status === 401) {
|
|
450
|
+
return Effect2.fail(
|
|
451
|
+
new AuthenticationError({
|
|
452
|
+
message: errorResponse.error || "Invalid or expired API key. Run 'braid auth' to re-authenticate."
|
|
453
|
+
})
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return Effect2.fail(
|
|
457
|
+
new ApiError({
|
|
458
|
+
message: errorResponse.error || "API request failed",
|
|
459
|
+
code: errorResponse.code || "UNKNOWN_ERROR",
|
|
460
|
+
status: response.status
|
|
461
|
+
})
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return Effect2.succeed(json);
|
|
465
|
+
};
|
|
466
|
+
fetchScopeOptions = (options = {}) => pipe2(
|
|
467
|
+
Effect2.all({
|
|
468
|
+
apiKey: resolveApiKey(options.apiKey),
|
|
469
|
+
serverUrl: resolveServerUrl(options.serverUrl)
|
|
470
|
+
}),
|
|
471
|
+
Effect2.flatMap(({ apiKey, serverUrl }) => {
|
|
472
|
+
const url = buildApiUrl(serverUrl, "/api/skills/scope-options");
|
|
473
|
+
return pipe2(
|
|
474
|
+
Effect2.tryPromise({
|
|
475
|
+
try: () => fetch(url.toString(), {
|
|
476
|
+
method: "GET",
|
|
477
|
+
headers: {
|
|
478
|
+
Authorization: `Bearer ${apiKey}`,
|
|
479
|
+
"Content-Type": "application/json"
|
|
480
|
+
}
|
|
481
|
+
}),
|
|
482
|
+
catch: (e) => new NetworkError({
|
|
483
|
+
message: `Failed to connect to ${serverUrl}`,
|
|
484
|
+
cause: e
|
|
485
|
+
})
|
|
486
|
+
}),
|
|
487
|
+
Effect2.flatMap(
|
|
488
|
+
(response) => pipe2(
|
|
489
|
+
Effect2.tryPromise({
|
|
490
|
+
try: () => response.json(),
|
|
491
|
+
catch: () => new NetworkError({ message: "Failed to parse API response" })
|
|
492
|
+
}),
|
|
493
|
+
Effect2.flatMap((json) => parseScopeOptionsResponse(response, json))
|
|
494
|
+
)
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
})
|
|
498
|
+
);
|
|
499
|
+
buildRuleOptionsUrl = (serverUrl, options) => {
|
|
500
|
+
const url = buildApiUrl(serverUrl, "/api/skills/rule-options");
|
|
501
|
+
if (options.orgId) {
|
|
502
|
+
url.searchParams.set("orgId", options.orgId);
|
|
503
|
+
}
|
|
504
|
+
if (options.orgProjects && options.orgProjects.length > 0) {
|
|
505
|
+
url.searchParams.set("orgProjects", options.orgProjects.join(","));
|
|
506
|
+
}
|
|
507
|
+
if (options.personalProjects && options.personalProjects.length > 0) {
|
|
508
|
+
url.searchParams.set(
|
|
509
|
+
"personalProjects",
|
|
510
|
+
options.personalProjects.join(",")
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
if (options.includeUserGlobal !== void 0) {
|
|
514
|
+
url.searchParams.set(
|
|
515
|
+
"includeUserGlobal",
|
|
516
|
+
String(options.includeUserGlobal)
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
if (options.includeOrgGlobal !== void 0) {
|
|
520
|
+
url.searchParams.set("includeOrgGlobal", String(options.includeOrgGlobal));
|
|
521
|
+
}
|
|
522
|
+
return url;
|
|
523
|
+
};
|
|
524
|
+
fetchRuleOptions = (options = {}) => pipe2(
|
|
525
|
+
Effect2.all({
|
|
526
|
+
apiKey: resolveApiKey(options.apiKey),
|
|
527
|
+
serverUrl: resolveServerUrl(options.serverUrl)
|
|
528
|
+
}),
|
|
529
|
+
Effect2.flatMap(
|
|
530
|
+
({ apiKey, serverUrl }) => pipe2(
|
|
531
|
+
Effect2.tryPromise({
|
|
532
|
+
try: () => fetch(buildRuleOptionsUrl(serverUrl, options).toString(), {
|
|
533
|
+
method: "GET",
|
|
534
|
+
headers: {
|
|
535
|
+
Authorization: `Bearer ${apiKey}`,
|
|
536
|
+
"Content-Type": "application/json"
|
|
537
|
+
}
|
|
538
|
+
}),
|
|
539
|
+
catch: (e) => new NetworkError({
|
|
540
|
+
message: `Failed to connect to ${serverUrl}`,
|
|
541
|
+
cause: e
|
|
542
|
+
})
|
|
543
|
+
}),
|
|
544
|
+
Effect2.flatMap(
|
|
545
|
+
(response) => pipe2(
|
|
546
|
+
Effect2.tryPromise({
|
|
547
|
+
try: () => response.json(),
|
|
548
|
+
catch: () => new NetworkError({ message: "Failed to parse API response" })
|
|
549
|
+
}),
|
|
550
|
+
Effect2.flatMap((json) => {
|
|
551
|
+
if (!response.ok) {
|
|
552
|
+
return parseResponse(response, json).pipe(
|
|
553
|
+
Effect2.flatMap(
|
|
554
|
+
() => Effect2.fail(
|
|
555
|
+
new ApiError({
|
|
556
|
+
message: "API request failed",
|
|
557
|
+
code: "UNKNOWN_ERROR",
|
|
558
|
+
status: response.status
|
|
559
|
+
})
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return Effect2.succeed(json);
|
|
565
|
+
})
|
|
566
|
+
)
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
fetchSkills = (options) => pipe2(
|
|
572
|
+
Effect2.all({
|
|
573
|
+
apiKey: resolveApiKey(options.apiKey),
|
|
574
|
+
serverUrl: resolveServerUrl(options.serverUrl)
|
|
575
|
+
}),
|
|
576
|
+
Effect2.flatMap(({ apiKey, serverUrl }) => {
|
|
577
|
+
const url = buildExportUrl(serverUrl, options);
|
|
578
|
+
return executeApiRequest(url, apiKey, serverUrl);
|
|
579
|
+
})
|
|
580
|
+
);
|
|
581
|
+
validateApiKey = (apiKey, serverUrl) => pipe2(
|
|
582
|
+
Effect2.tryPromise({
|
|
583
|
+
try: async () => {
|
|
584
|
+
const baseUrl = serverUrl ?? "https://braid.cloud";
|
|
585
|
+
const url = buildApiUrl(baseUrl, "/api/skills/export");
|
|
586
|
+
url.searchParams.set("profile", "default");
|
|
587
|
+
const response = await fetch(url.toString(), {
|
|
588
|
+
method: "GET",
|
|
589
|
+
headers: {
|
|
590
|
+
Authorization: `Bearer ${apiKey}`,
|
|
591
|
+
"Content-Type": "application/json"
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
return response.status !== 401;
|
|
595
|
+
},
|
|
596
|
+
catch: (e) => new NetworkError({
|
|
597
|
+
message: `Failed to connect to ${serverUrl ?? "https://braid.cloud"}`,
|
|
598
|
+
cause: e
|
|
599
|
+
})
|
|
600
|
+
})
|
|
601
|
+
);
|
|
602
|
+
fetchSkillsAsync = (options) => Effect2.runPromise(fetchSkills(options));
|
|
603
|
+
validateApiKeyAsync = (apiKey, serverUrl) => Effect2.runPromise(validateApiKey(apiKey, serverUrl));
|
|
604
|
+
fetchScopeOptionsAsync = (options = {}) => Effect2.runPromise(fetchScopeOptions(options));
|
|
605
|
+
fetchRuleOptionsAsync = (options = {}) => Effect2.runPromise(fetchRuleOptions(options));
|
|
606
|
+
}
|
|
607
|
+
});
|
|
275
608
|
|
|
276
|
-
// src/
|
|
277
|
-
init_esm_shims();
|
|
609
|
+
// src/lib/tui.ts
|
|
278
610
|
import process3 from "process";
|
|
279
611
|
import {
|
|
280
|
-
cancel,
|
|
281
|
-
confirm,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
612
|
+
cancel as clackCancel,
|
|
613
|
+
confirm as clackConfirm,
|
|
614
|
+
group as clackGroup,
|
|
615
|
+
groupMultiselect as clackGroupMultiselect,
|
|
616
|
+
intro as clackIntro,
|
|
617
|
+
isCancel as clackIsCancel,
|
|
618
|
+
log as clackLog,
|
|
619
|
+
multiselect as clackMultiselect,
|
|
620
|
+
note as clackNote,
|
|
621
|
+
outro as clackOutro,
|
|
622
|
+
password as clackPassword,
|
|
623
|
+
select as clackSelect,
|
|
624
|
+
selectKey as clackSelectKey,
|
|
625
|
+
spinner as clackSpinner,
|
|
626
|
+
text as clackText
|
|
288
627
|
} from "@clack/prompts";
|
|
628
|
+
function spinner() {
|
|
629
|
+
if (isTTY) {
|
|
630
|
+
return clackSpinner();
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
start: (message) => {
|
|
634
|
+
if (message) {
|
|
635
|
+
process3.stdout.write(`${message}
|
|
636
|
+
`);
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
stop: (message) => {
|
|
640
|
+
if (message) {
|
|
641
|
+
process3.stdout.write(`${message}
|
|
642
|
+
`);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
message: (message) => {
|
|
646
|
+
if (message) {
|
|
647
|
+
process3.stdout.write(`${message}
|
|
648
|
+
`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function intro(message) {
|
|
654
|
+
if (isTTY) {
|
|
655
|
+
clackIntro(message);
|
|
656
|
+
} else {
|
|
657
|
+
process3.stdout.write(`
|
|
658
|
+
${message}
|
|
659
|
+
`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function outro(message) {
|
|
663
|
+
if (isTTY) {
|
|
664
|
+
clackOutro(message);
|
|
665
|
+
} else {
|
|
666
|
+
process3.stdout.write(`${message}
|
|
289
667
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
var
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
668
|
+
`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
var isTTY, cancel, confirm, isCancel, log, multiselect, password, select, text;
|
|
672
|
+
var init_tui = __esm({
|
|
673
|
+
"src/lib/tui.ts"() {
|
|
674
|
+
"use strict";
|
|
675
|
+
init_esm_shims();
|
|
676
|
+
isTTY = Boolean(process3.stdout.isTTY);
|
|
677
|
+
cancel = clackCancel;
|
|
678
|
+
confirm = clackConfirm;
|
|
679
|
+
isCancel = clackIsCancel;
|
|
680
|
+
log = clackLog;
|
|
681
|
+
multiselect = clackMultiselect;
|
|
682
|
+
password = clackPassword;
|
|
683
|
+
select = clackSelect;
|
|
684
|
+
text = clackText;
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// src/lib/scope-args.ts
|
|
689
|
+
function parseProjectIds(input) {
|
|
690
|
+
return input.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
691
|
+
}
|
|
692
|
+
function inferScopeSource(flags) {
|
|
693
|
+
if (flags.profile) {
|
|
694
|
+
return "profile";
|
|
695
|
+
}
|
|
696
|
+
if (flags.projects || flags.ruleIds || flags.excludedRuleIds) {
|
|
697
|
+
return "manual";
|
|
698
|
+
}
|
|
699
|
+
return void 0;
|
|
700
|
+
}
|
|
701
|
+
function buildManualSelection(flags) {
|
|
702
|
+
const projectIds = parseProjectIds(flags.projects ?? "");
|
|
703
|
+
const ruleIds = parseProjectIds(flags.ruleIds ?? "");
|
|
704
|
+
const excludedRuleIds = parseProjectIds(flags.excludedRuleIds ?? "");
|
|
705
|
+
return {
|
|
706
|
+
organization: flags.organization,
|
|
707
|
+
source: "manual",
|
|
708
|
+
...projectIds.length > 0 ? { projectIds } : {},
|
|
709
|
+
...ruleIds.length > 0 ? { ruleIds } : {},
|
|
710
|
+
...excludedRuleIds.length > 0 ? { excludedRuleIds } : {},
|
|
711
|
+
includeUserGlobal: flags.includeUserGlobal ?? flags.organization === "personal",
|
|
712
|
+
includeOrgGlobal: flags.includeOrgGlobal ?? flags.organization === "organization"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function resolveSelectionFromFlags(flags) {
|
|
716
|
+
if (!flags.organization) {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
const source = flags.source ?? inferScopeSource(flags);
|
|
720
|
+
if (!source) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
if (source === "profile") {
|
|
724
|
+
const profile = flags.profile?.trim();
|
|
725
|
+
if (!profile) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
return { organization: flags.organization, source, profile };
|
|
729
|
+
}
|
|
730
|
+
return buildManualSelection(flags);
|
|
731
|
+
}
|
|
732
|
+
var init_scope_args = __esm({
|
|
733
|
+
"src/lib/scope-args.ts"() {
|
|
734
|
+
"use strict";
|
|
735
|
+
init_esm_shims();
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// src/lib/scope-config.ts
|
|
740
|
+
function buildManualScopeConfig(base, selection) {
|
|
741
|
+
let projectScope = {};
|
|
742
|
+
if (selection.projectIds && selection.projectIds.length > 0) {
|
|
743
|
+
projectScope = selection.organization === "organization" ? { orgProjects: selection.projectIds } : { personalProjects: selection.projectIds };
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
...base,
|
|
747
|
+
...projectScope,
|
|
748
|
+
...selection.ruleIds && selection.ruleIds.length > 0 ? { ruleIds: selection.ruleIds } : {},
|
|
749
|
+
...selection.excludedRuleIds && selection.excludedRuleIds.length > 0 ? { excludedRuleIds: selection.excludedRuleIds } : {},
|
|
750
|
+
includeUserGlobal: selection.includeUserGlobal ?? selection.organization === "personal",
|
|
751
|
+
includeOrgGlobal: selection.includeOrgGlobal ?? selection.organization === "organization"
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function buildScopedConfig(existing, selection) {
|
|
755
|
+
const {
|
|
756
|
+
profile: _oldProfile,
|
|
757
|
+
orgProjects: _oldOrgProjects,
|
|
758
|
+
personalProjects: _oldPersonalProjects,
|
|
759
|
+
...base
|
|
760
|
+
} = existing;
|
|
761
|
+
if (selection.source === "profile") {
|
|
762
|
+
const profile = selection.profile?.trim();
|
|
763
|
+
return {
|
|
764
|
+
...base,
|
|
765
|
+
...profile ? { profile } : {},
|
|
766
|
+
includeUserGlobal: selection.organization === "personal",
|
|
767
|
+
includeOrgGlobal: selection.organization === "organization"
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
if (selection.source === "manual") {
|
|
771
|
+
return buildManualScopeConfig(base, selection);
|
|
772
|
+
}
|
|
773
|
+
return base;
|
|
774
|
+
}
|
|
775
|
+
var init_scope_config = __esm({
|
|
776
|
+
"src/lib/scope-config.ts"() {
|
|
777
|
+
"use strict";
|
|
778
|
+
init_esm_shims();
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// src/commands/scope.ts
|
|
783
|
+
var scope_exports = {};
|
|
784
|
+
__export(scope_exports, {
|
|
785
|
+
scopeCommand: () => scopeCommand
|
|
786
|
+
});
|
|
787
|
+
import process4 from "process";
|
|
788
|
+
function exitCancelled(message) {
|
|
789
|
+
cancel(message);
|
|
790
|
+
process4.exit(0);
|
|
791
|
+
}
|
|
792
|
+
async function selectTargetFile(options) {
|
|
793
|
+
if (options.file) {
|
|
794
|
+
return options.file;
|
|
795
|
+
}
|
|
796
|
+
const target = await select({
|
|
797
|
+
message: "Where should this scope be saved?",
|
|
798
|
+
options: [
|
|
799
|
+
{
|
|
800
|
+
value: "user",
|
|
801
|
+
label: "braid.user.json",
|
|
802
|
+
hint: "Personal machine defaults"
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
value: "project",
|
|
806
|
+
label: "braid.json",
|
|
807
|
+
hint: "Shared project defaults"
|
|
808
|
+
}
|
|
809
|
+
],
|
|
810
|
+
initialValue: "user"
|
|
811
|
+
});
|
|
812
|
+
if (isCancel(target)) {
|
|
813
|
+
exitCancelled("Scope update cancelled.");
|
|
814
|
+
}
|
|
815
|
+
return target;
|
|
816
|
+
}
|
|
817
|
+
async function selectOrganization(scopeOptions) {
|
|
818
|
+
const options = [
|
|
819
|
+
{ value: "personal", label: "Personal", hint: "Your personal rules" }
|
|
820
|
+
];
|
|
821
|
+
for (const org of scopeOptions.organizations.filter(
|
|
822
|
+
(item) => !item.isPersonal
|
|
823
|
+
)) {
|
|
824
|
+
options.push({ value: `org:${org.id}`, label: org.name, hint: org.id });
|
|
825
|
+
}
|
|
826
|
+
const selected = await select({
|
|
827
|
+
message: "Select organization context:",
|
|
828
|
+
options,
|
|
829
|
+
initialValue: "personal"
|
|
830
|
+
});
|
|
831
|
+
if (isCancel(selected)) {
|
|
832
|
+
exitCancelled("Scope update cancelled.");
|
|
833
|
+
}
|
|
834
|
+
if (selected === "personal") {
|
|
835
|
+
return { organization: "personal" };
|
|
836
|
+
}
|
|
837
|
+
return { organization: "organization", orgId: selected.replace("org:", "") };
|
|
838
|
+
}
|
|
839
|
+
async function selectScopeSource() {
|
|
840
|
+
const source = await select({
|
|
841
|
+
message: "Select scope source:",
|
|
842
|
+
options: [
|
|
843
|
+
{ value: "profile", label: "Profile", hint: "Use an MCP profile" },
|
|
844
|
+
{
|
|
845
|
+
value: "manual",
|
|
846
|
+
label: "Manual",
|
|
847
|
+
hint: "Mix globals, projects, and rules"
|
|
848
|
+
}
|
|
849
|
+
],
|
|
850
|
+
initialValue: "profile"
|
|
851
|
+
});
|
|
852
|
+
if (isCancel(source)) {
|
|
853
|
+
exitCancelled("Scope update cancelled.");
|
|
854
|
+
}
|
|
855
|
+
return source;
|
|
856
|
+
}
|
|
857
|
+
async function selectProfileName(scopeOptions) {
|
|
858
|
+
if (scopeOptions.profiles.length === 0) {
|
|
859
|
+
const profileName = await text({
|
|
860
|
+
message: "Profile name:",
|
|
861
|
+
placeholder: "default",
|
|
862
|
+
validate: (value) => (value ?? "").trim().length > 0 ? void 0 : "Profile name is required"
|
|
863
|
+
});
|
|
864
|
+
if (isCancel(profileName)) {
|
|
865
|
+
exitCancelled("Scope update cancelled.");
|
|
866
|
+
}
|
|
867
|
+
return profileName.trim();
|
|
868
|
+
}
|
|
869
|
+
const profile = await select({
|
|
870
|
+
message: "Select profile:",
|
|
871
|
+
options: scopeOptions.profiles.map((p) => ({
|
|
872
|
+
value: p.name,
|
|
873
|
+
label: p.name,
|
|
874
|
+
hint: p.id
|
|
875
|
+
})),
|
|
876
|
+
...scopeOptions.profiles[0]?.name ? { initialValue: scopeOptions.profiles[0].name } : {}
|
|
877
|
+
});
|
|
878
|
+
if (isCancel(profile)) {
|
|
879
|
+
exitCancelled("Scope update cancelled.");
|
|
880
|
+
}
|
|
881
|
+
return profile;
|
|
882
|
+
}
|
|
883
|
+
function projectsForContext(organizationContext, scopeOptions) {
|
|
884
|
+
if (organizationContext.organization === "personal") {
|
|
885
|
+
return scopeOptions.personalProjects;
|
|
886
|
+
}
|
|
887
|
+
const orgProjects = scopeOptions.orgProjects.find(
|
|
888
|
+
(o) => o.orgId === organizationContext.orgId
|
|
317
889
|
);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
890
|
+
return orgProjects?.projects ?? [];
|
|
891
|
+
}
|
|
892
|
+
async function selectProjects(organizationContext, scopeOptions) {
|
|
893
|
+
const availableProjects = projectsForContext(
|
|
894
|
+
organizationContext,
|
|
895
|
+
scopeOptions
|
|
896
|
+
);
|
|
897
|
+
if (availableProjects.length === 0) {
|
|
898
|
+
return void 0;
|
|
322
899
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
900
|
+
const shouldUseProjects = await confirm({
|
|
901
|
+
message: "Include project rules too?",
|
|
902
|
+
initialValue: true
|
|
326
903
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
904
|
+
if (isCancel(shouldUseProjects)) {
|
|
905
|
+
exitCancelled("Scope update cancelled.");
|
|
906
|
+
}
|
|
907
|
+
if (!shouldUseProjects) {
|
|
908
|
+
return void 0;
|
|
909
|
+
}
|
|
910
|
+
const selected = await multiselect({
|
|
911
|
+
message: "Select projects:",
|
|
912
|
+
options: availableProjects.map((p) => ({
|
|
913
|
+
value: p.id,
|
|
914
|
+
label: p.name,
|
|
915
|
+
hint: p.id
|
|
916
|
+
})),
|
|
917
|
+
required: false
|
|
918
|
+
});
|
|
919
|
+
if (isCancel(selected)) {
|
|
920
|
+
exitCancelled("Scope update cancelled.");
|
|
921
|
+
}
|
|
922
|
+
return selected.length > 0 ? selected : void 0;
|
|
923
|
+
}
|
|
924
|
+
async function pickRuleIds(message, rules) {
|
|
925
|
+
const selected = await multiselect({
|
|
926
|
+
message,
|
|
927
|
+
options: rules.map((rule) => ({
|
|
928
|
+
value: rule.id,
|
|
929
|
+
label: rule.title,
|
|
930
|
+
hint: rule.id
|
|
931
|
+
})),
|
|
932
|
+
required: false
|
|
933
|
+
});
|
|
934
|
+
if (isCancel(selected)) {
|
|
935
|
+
exitCancelled("Scope update cancelled.");
|
|
936
|
+
}
|
|
937
|
+
return selected.length > 0 ? selected : void 0;
|
|
938
|
+
}
|
|
939
|
+
function manualInputsFromFlag(flagSelection) {
|
|
940
|
+
return {
|
|
941
|
+
includeUserGlobal: flagSelection.includeUserGlobal,
|
|
942
|
+
includeOrgGlobal: flagSelection.includeOrgGlobal,
|
|
943
|
+
...flagSelection.projectIds ? { projectIds: flagSelection.projectIds } : {},
|
|
944
|
+
...flagSelection.ruleIds ? { ruleIds: flagSelection.ruleIds } : {},
|
|
945
|
+
...flagSelection.excludedRuleIds ? { excludedRuleIds: flagSelection.excludedRuleIds } : {}
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
async function promptRuleFilters(organizationContext, projectIds, includeUserGlobal, includeOrgGlobal, serverUrl, apiKey) {
|
|
949
|
+
const filterMode = await select({
|
|
950
|
+
message: "Rule filtering:",
|
|
951
|
+
options: [
|
|
952
|
+
{ value: "none", label: "None", hint: "Use all matched rules" },
|
|
953
|
+
{
|
|
954
|
+
value: "include",
|
|
955
|
+
label: "Only include",
|
|
956
|
+
hint: "Pick explicit rule IDs"
|
|
957
|
+
},
|
|
958
|
+
{ value: "exclude", label: "Exclude", hint: "Remove specific rule IDs" },
|
|
959
|
+
{ value: "both", label: "Include + exclude", hint: "Both filters" }
|
|
960
|
+
],
|
|
961
|
+
initialValue: "none"
|
|
962
|
+
});
|
|
963
|
+
if (isCancel(filterMode) || filterMode === "none") {
|
|
964
|
+
if (isCancel(filterMode)) {
|
|
965
|
+
exitCancelled("Scope update cancelled.");
|
|
337
966
|
}
|
|
338
|
-
return
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
967
|
+
return {};
|
|
968
|
+
}
|
|
969
|
+
const ruleOptions = await fetchRuleOptionsAsync(
|
|
970
|
+
buildRuleOptionsRequest(
|
|
971
|
+
organizationContext,
|
|
972
|
+
projectIds,
|
|
973
|
+
includeUserGlobal,
|
|
974
|
+
includeOrgGlobal,
|
|
975
|
+
serverUrl,
|
|
976
|
+
apiKey
|
|
977
|
+
)
|
|
978
|
+
);
|
|
979
|
+
const ruleIds = filterMode === "include" || filterMode === "both" ? await pickRuleIds("Select rules to include:", ruleOptions.rules) : void 0;
|
|
980
|
+
const excludedRuleIds = filterMode === "exclude" || filterMode === "both" ? await pickRuleIds("Select rules to exclude:", ruleOptions.rules) : void 0;
|
|
981
|
+
return {
|
|
982
|
+
...ruleIds ? { ruleIds } : {},
|
|
983
|
+
...excludedRuleIds ? { excludedRuleIds } : {}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function buildRuleOptionsRequest(organizationContext, projectIds, includeUserGlobal, includeOrgGlobal, serverUrl, apiKey) {
|
|
987
|
+
let projectScope = {};
|
|
988
|
+
if (projectIds && organizationContext.organization === "organization") {
|
|
989
|
+
projectScope = { orgProjects: projectIds };
|
|
345
990
|
}
|
|
346
|
-
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
991
|
+
if (projectIds && organizationContext.organization === "personal") {
|
|
992
|
+
projectScope = { personalProjects: projectIds };
|
|
993
|
+
}
|
|
994
|
+
return {
|
|
995
|
+
serverUrl,
|
|
996
|
+
...apiKey ? { apiKey } : {},
|
|
997
|
+
...organizationContext.organization === "organization" ? { orgId: organizationContext.orgId } : {},
|
|
998
|
+
...projectScope,
|
|
999
|
+
includeUserGlobal,
|
|
1000
|
+
includeOrgGlobal
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
async function resolveManualInputs(organizationContext, scopeOptions, serverUrl, apiKey) {
|
|
1004
|
+
const { includeUserGlobal, includeOrgGlobal } = await promptGlobalInclusions(organizationContext);
|
|
1005
|
+
const projectIds = await selectProjects(organizationContext, scopeOptions);
|
|
1006
|
+
const filters = await promptRuleFilters(
|
|
1007
|
+
organizationContext,
|
|
1008
|
+
projectIds,
|
|
1009
|
+
includeUserGlobal,
|
|
1010
|
+
includeOrgGlobal,
|
|
1011
|
+
serverUrl,
|
|
1012
|
+
apiKey
|
|
1013
|
+
);
|
|
1014
|
+
return {
|
|
1015
|
+
includeUserGlobal,
|
|
1016
|
+
includeOrgGlobal,
|
|
1017
|
+
...projectIds ? { projectIds } : {},
|
|
1018
|
+
...filters
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
async function promptGlobalInclusions(organizationContext) {
|
|
1022
|
+
const includeUserGlobal = await confirm({
|
|
1023
|
+
message: "Include personal global rules?",
|
|
1024
|
+
initialValue: organizationContext.organization === "personal"
|
|
1025
|
+
});
|
|
1026
|
+
if (isCancel(includeUserGlobal)) {
|
|
1027
|
+
exitCancelled("Scope update cancelled.");
|
|
1028
|
+
}
|
|
1029
|
+
if (organizationContext.organization === "personal") {
|
|
1030
|
+
return { includeUserGlobal, includeOrgGlobal: false };
|
|
365
1031
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
1032
|
+
const includeOrgGlobal = await confirm({
|
|
1033
|
+
message: "Include organization global rules?",
|
|
1034
|
+
initialValue: true
|
|
1035
|
+
});
|
|
1036
|
+
if (isCancel(includeOrgGlobal)) {
|
|
1037
|
+
exitCancelled("Scope update cancelled.");
|
|
1038
|
+
}
|
|
1039
|
+
return { includeUserGlobal, includeOrgGlobal };
|
|
1040
|
+
}
|
|
1041
|
+
async function resolveManualSelection(source, flagSelection, organizationContext, scopeOptions, serverUrl, apiKey) {
|
|
1042
|
+
if (source !== "manual") {
|
|
1043
|
+
return void 0;
|
|
1044
|
+
}
|
|
1045
|
+
if (flagSelection?.source === "manual") {
|
|
1046
|
+
return manualInputsFromFlag(flagSelection);
|
|
1047
|
+
}
|
|
1048
|
+
return await resolveManualInputs(
|
|
1049
|
+
organizationContext,
|
|
1050
|
+
scopeOptions,
|
|
1051
|
+
serverUrl,
|
|
1052
|
+
apiKey
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
async function resolveOrganizationContext(flagSelection, scopeOptions) {
|
|
1056
|
+
if (flagSelection?.organization === "personal") {
|
|
1057
|
+
return { organization: "personal" };
|
|
1058
|
+
}
|
|
1059
|
+
return await selectOrganization(scopeOptions);
|
|
1060
|
+
}
|
|
1061
|
+
async function resolveProfileSelection(source, flagSelection, scopeOptions) {
|
|
1062
|
+
if (source !== "profile") {
|
|
1063
|
+
return void 0;
|
|
1064
|
+
}
|
|
1065
|
+
if (flagSelection?.source === "profile") {
|
|
1066
|
+
return flagSelection.profile;
|
|
1067
|
+
}
|
|
1068
|
+
return await selectProfileName(scopeOptions);
|
|
1069
|
+
}
|
|
1070
|
+
function resolveFlagSelection(options) {
|
|
1071
|
+
const flags = {
|
|
1072
|
+
...options.organization ? { organization: options.organization } : {},
|
|
1073
|
+
...options.source ? { source: options.source } : {},
|
|
1074
|
+
...options.profile ? { profile: options.profile } : {},
|
|
1075
|
+
...options.projects ? { projects: options.projects } : {},
|
|
1076
|
+
...options.ruleIds ? { ruleIds: options.ruleIds } : {},
|
|
1077
|
+
...options.excludedRuleIds ? { excludedRuleIds: options.excludedRuleIds } : {},
|
|
1078
|
+
...options.includeUserGlobal !== void 0 ? { includeUserGlobal: options.includeUserGlobal } : {},
|
|
1079
|
+
...options.includeOrgGlobal !== void 0 ? { includeOrgGlobal: options.includeOrgGlobal } : {}
|
|
1080
|
+
};
|
|
1081
|
+
return resolveSelectionFromFlags(flags);
|
|
1082
|
+
}
|
|
1083
|
+
async function scopeCommand(options) {
|
|
1084
|
+
intro("braid scope");
|
|
1085
|
+
const config = await loadMergedConfigAsync();
|
|
1086
|
+
const targetFile = await selectTargetFile(options);
|
|
1087
|
+
const loadSpinner = spinner();
|
|
1088
|
+
loadSpinner.start("Loading scope options from Braid...");
|
|
1089
|
+
try {
|
|
1090
|
+
const serverUrl = options.server ?? config.serverUrl;
|
|
1091
|
+
const apiKey = options.apiKey ?? config.token;
|
|
1092
|
+
const scopeOptions = await fetchScopeOptionsAsync({
|
|
1093
|
+
serverUrl,
|
|
1094
|
+
...apiKey ? { apiKey } : {}
|
|
1095
|
+
});
|
|
1096
|
+
loadSpinner.stop("Scope options loaded");
|
|
1097
|
+
const flagSelection = resolveFlagSelection(options);
|
|
1098
|
+
const organizationContext = await resolveOrganizationContext(
|
|
1099
|
+
flagSelection,
|
|
1100
|
+
scopeOptions
|
|
1101
|
+
);
|
|
1102
|
+
const source = flagSelection?.source ?? await selectScopeSource();
|
|
1103
|
+
const profile = await resolveProfileSelection(
|
|
1104
|
+
source,
|
|
1105
|
+
flagSelection,
|
|
1106
|
+
scopeOptions
|
|
1107
|
+
);
|
|
1108
|
+
const manualInputs = await resolveManualSelection(
|
|
1109
|
+
source,
|
|
1110
|
+
flagSelection,
|
|
1111
|
+
organizationContext,
|
|
1112
|
+
scopeOptions,
|
|
1113
|
+
serverUrl,
|
|
1114
|
+
apiKey
|
|
1115
|
+
);
|
|
1116
|
+
const selection = {
|
|
1117
|
+
organization: organizationContext.organization,
|
|
1118
|
+
source,
|
|
1119
|
+
...profile ? { profile } : {},
|
|
1120
|
+
...manualInputs ?? {}
|
|
1121
|
+
};
|
|
1122
|
+
if (targetFile === "user") {
|
|
1123
|
+
const existing2 = await loadUserConfigAsync() ?? {};
|
|
1124
|
+
const path3 = await saveUserConfigAsync(
|
|
1125
|
+
buildScopedConfig(existing2, selection)
|
|
1126
|
+
);
|
|
1127
|
+
outro(`Updated ${path3}`);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const existing = await loadProjectConfigAsync() ?? {};
|
|
1131
|
+
const path2 = await saveProjectConfigAsync(
|
|
1132
|
+
buildScopedConfig(existing, selection)
|
|
370
1133
|
);
|
|
1134
|
+
outro(`Updated ${path2}`);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
loadSpinner.stop("Failed to load scope options");
|
|
1137
|
+
log.error(error instanceof Error ? error.message : String(error));
|
|
1138
|
+
process4.exit(1);
|
|
371
1139
|
}
|
|
372
|
-
|
|
373
|
-
|
|
1140
|
+
}
|
|
1141
|
+
var init_scope = __esm({
|
|
1142
|
+
"src/commands/scope.ts"() {
|
|
1143
|
+
"use strict";
|
|
1144
|
+
init_esm_shims();
|
|
1145
|
+
init_api();
|
|
1146
|
+
init_config();
|
|
1147
|
+
init_scope_args();
|
|
1148
|
+
init_scope_config();
|
|
1149
|
+
init_tui();
|
|
374
1150
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
headers: {
|
|
382
|
-
Authorization: `Bearer ${apiKey}`,
|
|
383
|
-
"Content-Type": "application/json"
|
|
384
|
-
}
|
|
385
|
-
}),
|
|
386
|
-
catch: (e) => new NetworkError({
|
|
387
|
-
message: `Failed to connect to ${serverUrl}`,
|
|
388
|
-
cause: e
|
|
389
|
-
})
|
|
390
|
-
}),
|
|
391
|
-
Effect2.flatMap(
|
|
392
|
-
(response) => pipe2(
|
|
393
|
-
Effect2.tryPromise({
|
|
394
|
-
try: () => response.json(),
|
|
395
|
-
catch: () => new NetworkError({ message: "Failed to parse API response" })
|
|
396
|
-
}),
|
|
397
|
-
Effect2.flatMap((json) => parseResponse(response, json))
|
|
398
|
-
)
|
|
399
|
-
)
|
|
400
|
-
);
|
|
401
|
-
var fetchSkills = (options) => pipe2(
|
|
402
|
-
Effect2.all({
|
|
403
|
-
apiKey: resolveApiKey(options.apiKey),
|
|
404
|
-
serverUrl: resolveServerUrl(options.serverUrl)
|
|
405
|
-
}),
|
|
406
|
-
Effect2.flatMap(({ apiKey, serverUrl }) => {
|
|
407
|
-
const url = buildExportUrl(serverUrl, options);
|
|
408
|
-
return executeApiRequest(url, apiKey, serverUrl);
|
|
409
|
-
})
|
|
410
|
-
);
|
|
411
|
-
var validateApiKey = (apiKey, serverUrl) => pipe2(
|
|
412
|
-
Effect2.tryPromise({
|
|
413
|
-
try: async () => {
|
|
414
|
-
const baseUrl = serverUrl ?? "https://braid.cloud";
|
|
415
|
-
const url = buildApiUrl(baseUrl, "/api/skills/export");
|
|
416
|
-
url.searchParams.set("profile", "default");
|
|
417
|
-
const response = await fetch(url.toString(), {
|
|
418
|
-
method: "GET",
|
|
419
|
-
headers: {
|
|
420
|
-
Authorization: `Bearer ${apiKey}`,
|
|
421
|
-
"Content-Type": "application/json"
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
return response.status !== 401;
|
|
425
|
-
},
|
|
426
|
-
catch: (e) => new NetworkError({
|
|
427
|
-
message: `Failed to connect to ${serverUrl ?? "https://braid.cloud"}`,
|
|
428
|
-
cause: e
|
|
429
|
-
})
|
|
430
|
-
})
|
|
431
|
-
);
|
|
432
|
-
var fetchSkillsAsync = (options) => Effect2.runPromise(fetchSkills(options));
|
|
433
|
-
var validateApiKeyAsync = (apiKey, serverUrl) => Effect2.runPromise(validateApiKey(apiKey, serverUrl));
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
// src/index.ts
|
|
1154
|
+
init_esm_shims();
|
|
1155
|
+
import { createRequire } from "module";
|
|
1156
|
+
import { Command } from "commander";
|
|
434
1157
|
|
|
435
1158
|
// src/commands/auth.ts
|
|
1159
|
+
init_esm_shims();
|
|
1160
|
+
init_api();
|
|
436
1161
|
init_config();
|
|
1162
|
+
init_tui();
|
|
1163
|
+
import process5 from "process";
|
|
1164
|
+
async function configureDefaultScopeAsync(serverUrl) {
|
|
1165
|
+
const shouldConfigureScope = await confirm({
|
|
1166
|
+
message: "Configure organization and scope defaults now?",
|
|
1167
|
+
initialValue: true
|
|
1168
|
+
});
|
|
1169
|
+
if (isCancel(shouldConfigureScope) || !shouldConfigureScope) {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
const { scopeCommand: scopeCommand2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
|
|
1173
|
+
const config = await loadMergedConfigAsync();
|
|
1174
|
+
await scopeCommand2({
|
|
1175
|
+
file: "user",
|
|
1176
|
+
server: serverUrl,
|
|
1177
|
+
...config.token ? { apiKey: config.token } : {}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
437
1180
|
async function authCommand(options) {
|
|
438
1181
|
intro("braid auth");
|
|
439
1182
|
const config = await loadMergedConfigAsync();
|
|
@@ -461,35 +1204,48 @@ async function authCommand(options) {
|
|
|
461
1204
|
});
|
|
462
1205
|
if (isCancel(apiKey)) {
|
|
463
1206
|
cancel("Auth cancelled.");
|
|
464
|
-
|
|
1207
|
+
process5.exit(0);
|
|
465
1208
|
}
|
|
466
1209
|
const authSpinner = spinner();
|
|
1210
|
+
let authSpinnerActive = true;
|
|
1211
|
+
const stopAuthSpinner = (message) => {
|
|
1212
|
+
if (!authSpinnerActive) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
authSpinner.stop(message);
|
|
1216
|
+
authSpinnerActive = false;
|
|
1217
|
+
};
|
|
467
1218
|
authSpinner.start("Validating API key...");
|
|
468
1219
|
try {
|
|
469
1220
|
const serverUrl = options.server ?? "https://braid.cloud";
|
|
470
1221
|
const isValid = await validateApiKeyAsync(apiKey, serverUrl);
|
|
471
1222
|
if (!isValid) {
|
|
472
|
-
|
|
1223
|
+
stopAuthSpinner("Invalid API key");
|
|
473
1224
|
log.error(
|
|
474
1225
|
"The API key could not be validated. Please check your key and try again."
|
|
475
1226
|
);
|
|
476
|
-
|
|
1227
|
+
process5.exit(1);
|
|
477
1228
|
}
|
|
478
1229
|
await setApiKeyAsync(apiKey);
|
|
479
|
-
|
|
1230
|
+
const existingUserConfig = await loadUserConfigAsync();
|
|
1231
|
+
if (existingUserConfig?.token) {
|
|
1232
|
+
await saveUserConfigAsync({ ...existingUserConfig, token: apiKey });
|
|
1233
|
+
}
|
|
1234
|
+
stopAuthSpinner("API key validated and saved");
|
|
1235
|
+
await configureDefaultScopeAsync(serverUrl);
|
|
480
1236
|
log.success(`Config saved to ${CONFIG_FILE}`);
|
|
481
1237
|
outro(
|
|
482
1238
|
"You're authenticated! Run 'braid install --profile <name>' to install skills."
|
|
483
1239
|
);
|
|
484
1240
|
} catch (error) {
|
|
485
|
-
|
|
1241
|
+
stopAuthSpinner("Validation failed");
|
|
486
1242
|
const message = error instanceof Error ? error.message : String(error);
|
|
487
1243
|
log.error(`Failed to validate API key: ${message}`);
|
|
488
|
-
|
|
1244
|
+
process5.exit(1);
|
|
489
1245
|
}
|
|
490
1246
|
}
|
|
491
1247
|
async function displayTokenSource(masked) {
|
|
492
|
-
if (
|
|
1248
|
+
if (process5.env.BRAID_API_KEY) {
|
|
493
1249
|
log.info(`Authenticated with key: ${masked}`);
|
|
494
1250
|
log.info("Source: BRAID_API_KEY environment variable");
|
|
495
1251
|
return;
|
|
@@ -552,7 +1308,7 @@ init_esm_shims();
|
|
|
552
1308
|
import { access, constants } from "fs/promises";
|
|
553
1309
|
import { homedir as homedir2 } from "os";
|
|
554
1310
|
import { join as join2 } from "path";
|
|
555
|
-
import
|
|
1311
|
+
import process6 from "process";
|
|
556
1312
|
import { Effect as Effect3, pipe as pipe3 } from "effect";
|
|
557
1313
|
var home = homedir2();
|
|
558
1314
|
var AGENTS = [
|
|
@@ -579,6 +1335,8 @@ var AGENTS = [
|
|
|
579
1335
|
name: "Claude Code",
|
|
580
1336
|
projectPath: ".claude/skills",
|
|
581
1337
|
globalPath: join2(home, ".claude", "skills"),
|
|
1338
|
+
projectMarkerPath: ".claude",
|
|
1339
|
+
globalMarkerPath: join2(home, ".claude"),
|
|
582
1340
|
rulesProjectPath: ".claude/rules",
|
|
583
1341
|
rulesGlobalPath: join2(home, ".claude", "rules"),
|
|
584
1342
|
ruleFormat: "markdown-dir"
|
|
@@ -773,6 +1531,8 @@ var AGENTS = [
|
|
|
773
1531
|
name: "Zed",
|
|
774
1532
|
projectPath: "",
|
|
775
1533
|
globalPath: "",
|
|
1534
|
+
projectMarkerPath: ".zed",
|
|
1535
|
+
globalMarkerPath: join2(home, ".config", "zed"),
|
|
776
1536
|
rulesProjectPath: ".zed/rules",
|
|
777
1537
|
rulesGlobalPath: join2(home, ".config", "zed", "rules"),
|
|
778
1538
|
ruleFormat: "markdown-dir"
|
|
@@ -787,7 +1547,7 @@ var directoryExists = (path2) => pipe3(
|
|
|
787
1547
|
Effect3.orElseSucceed(() => false)
|
|
788
1548
|
);
|
|
789
1549
|
var detectAgents = (projectRoot) => {
|
|
790
|
-
const cwd = projectRoot ??
|
|
1550
|
+
const cwd = projectRoot ?? process6.cwd();
|
|
791
1551
|
return pipe3(
|
|
792
1552
|
Effect3.forEach(
|
|
793
1553
|
AGENTS,
|
|
@@ -795,6 +1555,8 @@ var detectAgents = (projectRoot) => {
|
|
|
795
1555
|
Effect3.all({
|
|
796
1556
|
hasProjectConfig: agent.projectPath ? directoryExists(join2(cwd, agent.projectPath)) : Effect3.succeed(false),
|
|
797
1557
|
hasGlobalConfig: agent.globalPath ? directoryExists(agent.globalPath) : Effect3.succeed(false),
|
|
1558
|
+
hasProjectMarker: agent.projectMarkerPath ? directoryExists(join2(cwd, agent.projectMarkerPath)) : Effect3.succeed(false),
|
|
1559
|
+
hasGlobalMarker: agent.globalMarkerPath ? directoryExists(agent.globalMarkerPath) : Effect3.succeed(false),
|
|
798
1560
|
hasRulesProjectConfig: agent.rulesProjectPath ? directoryExists(join2(cwd, agent.rulesProjectPath)) : Effect3.succeed(false),
|
|
799
1561
|
hasRulesGlobalConfig: agent.rulesGlobalPath ? directoryExists(agent.rulesGlobalPath) : Effect3.succeed(false)
|
|
800
1562
|
}),
|
|
@@ -802,12 +1564,14 @@ var detectAgents = (projectRoot) => {
|
|
|
802
1564
|
({
|
|
803
1565
|
hasProjectConfig,
|
|
804
1566
|
hasGlobalConfig,
|
|
1567
|
+
hasProjectMarker,
|
|
1568
|
+
hasGlobalMarker,
|
|
805
1569
|
hasRulesProjectConfig,
|
|
806
1570
|
hasRulesGlobalConfig
|
|
807
1571
|
}) => ({
|
|
808
1572
|
...agent,
|
|
809
|
-
hasProjectConfig: hasProjectConfig || hasRulesProjectConfig,
|
|
810
|
-
hasGlobalConfig: hasGlobalConfig || hasRulesGlobalConfig
|
|
1573
|
+
hasProjectConfig: hasProjectConfig || hasRulesProjectConfig || hasProjectMarker,
|
|
1574
|
+
hasGlobalConfig: hasGlobalConfig || hasRulesGlobalConfig || hasGlobalMarker
|
|
811
1575
|
})
|
|
812
1576
|
)
|
|
813
1577
|
),
|
|
@@ -828,7 +1592,7 @@ var resolveInstallPath = (agent, options) => {
|
|
|
828
1592
|
if (!agent.projectPath) {
|
|
829
1593
|
return void 0;
|
|
830
1594
|
}
|
|
831
|
-
const cwd = options.projectRoot ??
|
|
1595
|
+
const cwd = options.projectRoot ?? process6.cwd();
|
|
832
1596
|
return join2(cwd, agent.projectPath);
|
|
833
1597
|
};
|
|
834
1598
|
var resolveRulesInstallPath = (agent, options) => {
|
|
@@ -838,11 +1602,12 @@ var resolveRulesInstallPath = (agent, options) => {
|
|
|
838
1602
|
if (!agent.rulesProjectPath) {
|
|
839
1603
|
return void 0;
|
|
840
1604
|
}
|
|
841
|
-
const cwd = options.projectRoot ??
|
|
1605
|
+
const cwd = options.projectRoot ?? process6.cwd();
|
|
842
1606
|
return join2(cwd, agent.rulesProjectPath);
|
|
843
1607
|
};
|
|
844
1608
|
|
|
845
1609
|
// src/commands/install.ts
|
|
1610
|
+
init_api();
|
|
846
1611
|
init_config();
|
|
847
1612
|
|
|
848
1613
|
// src/lib/metadata.ts
|
|
@@ -1208,77 +1973,8 @@ var writeSkills = (basePath, skills, agentId) => pipe6(
|
|
|
1208
1973
|
);
|
|
1209
1974
|
var writeSkillsAsync = (basePath, skills, agentId) => Effect6.runPromise(writeSkills(basePath, skills, agentId));
|
|
1210
1975
|
|
|
1211
|
-
// src/lib/tui.ts
|
|
1212
|
-
init_esm_shims();
|
|
1213
|
-
import process5 from "process";
|
|
1214
|
-
import {
|
|
1215
|
-
cancel as clackCancel,
|
|
1216
|
-
confirm as clackConfirm,
|
|
1217
|
-
group as clackGroup,
|
|
1218
|
-
groupMultiselect as clackGroupMultiselect,
|
|
1219
|
-
intro as clackIntro,
|
|
1220
|
-
isCancel as clackIsCancel,
|
|
1221
|
-
log as clackLog,
|
|
1222
|
-
multiselect as clackMultiselect,
|
|
1223
|
-
note as clackNote,
|
|
1224
|
-
outro as clackOutro,
|
|
1225
|
-
password as clackPassword,
|
|
1226
|
-
select as clackSelect,
|
|
1227
|
-
selectKey as clackSelectKey,
|
|
1228
|
-
spinner as clackSpinner,
|
|
1229
|
-
text as clackText
|
|
1230
|
-
} from "@clack/prompts";
|
|
1231
|
-
var isTTY = Boolean(process5.stdout.isTTY);
|
|
1232
|
-
function spinner2() {
|
|
1233
|
-
if (isTTY) {
|
|
1234
|
-
return clackSpinner();
|
|
1235
|
-
}
|
|
1236
|
-
return {
|
|
1237
|
-
start: (message) => {
|
|
1238
|
-
if (message) {
|
|
1239
|
-
process5.stdout.write(`${message}
|
|
1240
|
-
`);
|
|
1241
|
-
}
|
|
1242
|
-
},
|
|
1243
|
-
stop: (message) => {
|
|
1244
|
-
if (message) {
|
|
1245
|
-
process5.stdout.write(`${message}
|
|
1246
|
-
`);
|
|
1247
|
-
}
|
|
1248
|
-
},
|
|
1249
|
-
message: (message) => {
|
|
1250
|
-
if (message) {
|
|
1251
|
-
process5.stdout.write(`${message}
|
|
1252
|
-
`);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
};
|
|
1256
|
-
}
|
|
1257
|
-
function intro2(message) {
|
|
1258
|
-
if (isTTY) {
|
|
1259
|
-
clackIntro(message);
|
|
1260
|
-
} else {
|
|
1261
|
-
process5.stdout.write(`
|
|
1262
|
-
${message}
|
|
1263
|
-
`);
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
function outro2(message) {
|
|
1267
|
-
if (isTTY) {
|
|
1268
|
-
clackOutro(message);
|
|
1269
|
-
} else {
|
|
1270
|
-
process5.stdout.write(`${message}
|
|
1271
|
-
|
|
1272
|
-
`);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
var cancel2 = clackCancel;
|
|
1276
|
-
var confirm2 = clackConfirm;
|
|
1277
|
-
var isCancel2 = clackIsCancel;
|
|
1278
|
-
var log2 = clackLog;
|
|
1279
|
-
var multiselect = clackMultiselect;
|
|
1280
|
-
|
|
1281
1976
|
// src/commands/install.ts
|
|
1977
|
+
init_tui();
|
|
1282
1978
|
function resolveInstallConfig(options, config) {
|
|
1283
1979
|
const orgProjectsFromFlag = options.orgProjects ? options.orgProjects.split(",").map((s) => s.trim()) : void 0;
|
|
1284
1980
|
const personalProjectsFromFlag = options.personalProjects ? options.personalProjects.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -1288,7 +1984,10 @@ function resolveInstallConfig(options, config) {
|
|
|
1288
1984
|
includeUserGlobal: options.includeUserGlobals ?? config.includeUserGlobal,
|
|
1289
1985
|
includeOrgGlobal: options.includeOrgGlobals ?? config.includeOrgGlobal,
|
|
1290
1986
|
orgProjects: orgProjectsFromFlag ?? config.orgProjects,
|
|
1291
|
-
personalProjects: personalProjectsFromFlag ?? config.personalProjects
|
|
1987
|
+
personalProjects: personalProjectsFromFlag ?? config.personalProjects,
|
|
1988
|
+
ruleIds: config.ruleIds,
|
|
1989
|
+
excludedRuleIds: config.excludedRuleIds,
|
|
1990
|
+
resolveOverlays: config.resolveOverlays
|
|
1292
1991
|
};
|
|
1293
1992
|
}
|
|
1294
1993
|
function validateInstallOptions(resolved) {
|
|
@@ -1296,17 +1995,17 @@ function validateInstallOptions(resolved) {
|
|
|
1296
1995
|
const hasOrgProjects = orgProjects && orgProjects.length > 0;
|
|
1297
1996
|
const hasPersonalProjects = personalProjects && personalProjects.length > 0;
|
|
1298
1997
|
if (!(profile || hasOrgProjects || hasPersonalProjects)) {
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1998
|
+
log.error("No profile or project(s) specified.");
|
|
1999
|
+
log.info("Either:");
|
|
2000
|
+
log.info(
|
|
1302
2001
|
" - Add 'profile', 'orgProjects', or 'personalProjects' to braid.json/braid.user.json"
|
|
1303
2002
|
);
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
2003
|
+
log.info(" - Use --profile, --org-projects, or --personal-projects flags");
|
|
2004
|
+
log.info("");
|
|
2005
|
+
log.info("Examples:");
|
|
2006
|
+
log.info(" braid install --profile coding-standards");
|
|
2007
|
+
log.info(" braid install --org-projects proj123,proj456");
|
|
2008
|
+
log.info(" braid install --personal-projects myproj1");
|
|
1310
2009
|
process.exit(1);
|
|
1311
2010
|
}
|
|
1312
2011
|
}
|
|
@@ -1338,25 +2037,45 @@ function buildFetchOptions(resolved) {
|
|
|
1338
2037
|
if (resolved.personalProjects && resolved.personalProjects.length > 0) {
|
|
1339
2038
|
fetchOptions.personalProjects = resolved.personalProjects;
|
|
1340
2039
|
}
|
|
2040
|
+
if (resolved.ruleIds && resolved.ruleIds.length > 0) {
|
|
2041
|
+
fetchOptions.ruleIds = resolved.ruleIds;
|
|
2042
|
+
}
|
|
2043
|
+
if (resolved.excludedRuleIds && resolved.excludedRuleIds.length > 0) {
|
|
2044
|
+
fetchOptions.excludedRuleIds = resolved.excludedRuleIds;
|
|
2045
|
+
}
|
|
2046
|
+
if (resolved.resolveOverlays !== void 0) {
|
|
2047
|
+
fetchOptions.resolveOverlays = resolved.resolveOverlays;
|
|
2048
|
+
}
|
|
1341
2049
|
return fetchOptions;
|
|
1342
2050
|
}
|
|
1343
2051
|
function displaySkillsAndExit(skills) {
|
|
1344
|
-
|
|
2052
|
+
log.info("\nSkills:");
|
|
1345
2053
|
for (const skill of skills) {
|
|
1346
2054
|
const fileCount = skill.files.length;
|
|
1347
|
-
|
|
2055
|
+
log.info(
|
|
1348
2056
|
` ${skill.name} (${fileCount} file${fileCount !== 1 ? "s" : ""})`
|
|
1349
2057
|
);
|
|
1350
2058
|
}
|
|
1351
2059
|
process.exit(0);
|
|
1352
2060
|
}
|
|
2061
|
+
function getSelectableAgents(options) {
|
|
2062
|
+
return AGENTS.filter(
|
|
2063
|
+
(agent) => options.global ? Boolean(agent.globalPath) : Boolean(agent.projectPath)
|
|
2064
|
+
).map(
|
|
2065
|
+
(agent) => ({
|
|
2066
|
+
...agent,
|
|
2067
|
+
hasProjectConfig: false,
|
|
2068
|
+
hasGlobalConfig: false
|
|
2069
|
+
})
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
1353
2072
|
async function resolveAgents(options, installSpinner) {
|
|
1354
2073
|
if (options.agents) {
|
|
1355
2074
|
const agentIds = options.agents.split(",").map((s) => s.trim());
|
|
1356
2075
|
const selectedAgents = agentIds.map((id) => {
|
|
1357
2076
|
const agentConfig = getAgentById(id);
|
|
1358
2077
|
if (!agentConfig) {
|
|
1359
|
-
|
|
2078
|
+
log.warn(`Unknown agent: ${id}`);
|
|
1360
2079
|
return null;
|
|
1361
2080
|
}
|
|
1362
2081
|
return {
|
|
@@ -1366,10 +2085,13 @@ async function resolveAgents(options, installSpinner) {
|
|
|
1366
2085
|
};
|
|
1367
2086
|
}).filter((a) => a !== null);
|
|
1368
2087
|
if (selectedAgents.length === 0) {
|
|
1369
|
-
|
|
2088
|
+
log.error("No valid agents specified.");
|
|
1370
2089
|
process.exit(1);
|
|
1371
2090
|
}
|
|
1372
|
-
return
|
|
2091
|
+
return {
|
|
2092
|
+
availableAgents: selectedAgents,
|
|
2093
|
+
preselectedAgents: selectedAgents
|
|
2094
|
+
};
|
|
1373
2095
|
}
|
|
1374
2096
|
installSpinner.start("Detecting installed agents...");
|
|
1375
2097
|
const allDetectedAgents = await detectAgentsAsync();
|
|
@@ -1382,37 +2104,45 @@ async function resolveAgents(options, installSpinner) {
|
|
|
1382
2104
|
);
|
|
1383
2105
|
for (const agent of detectedAgents) {
|
|
1384
2106
|
const targetPath = options.global ? agent.globalPath : agent.projectPath;
|
|
1385
|
-
|
|
2107
|
+
log.info(` ${agent.name} \u2192 ${targetPath}`);
|
|
1386
2108
|
}
|
|
1387
2109
|
if (detectedAgents.length === 0) {
|
|
1388
|
-
|
|
1389
|
-
|
|
2110
|
+
log.warn("No AI coding agents detected.");
|
|
2111
|
+
log.info(
|
|
1390
2112
|
"Supported agents: claude-code, opencode, cursor, windsurf, cline, and more."
|
|
1391
2113
|
);
|
|
1392
|
-
|
|
1393
|
-
"
|
|
2114
|
+
log.info(
|
|
2115
|
+
"Select agents manually below. Detected agents are pre-selected when available."
|
|
1394
2116
|
);
|
|
1395
|
-
|
|
2117
|
+
return {
|
|
2118
|
+
availableAgents: getSelectableAgents(options),
|
|
2119
|
+
preselectedAgents: []
|
|
2120
|
+
};
|
|
1396
2121
|
}
|
|
1397
|
-
return
|
|
2122
|
+
return {
|
|
2123
|
+
availableAgents: getSelectableAgents(options),
|
|
2124
|
+
preselectedAgents: detectedAgents
|
|
2125
|
+
};
|
|
1398
2126
|
}
|
|
1399
|
-
async function selectAgents(
|
|
2127
|
+
async function selectAgents(availableAgents, preselectedAgents, options) {
|
|
1400
2128
|
if (options.yes) {
|
|
1401
|
-
return
|
|
2129
|
+
return preselectedAgents.length > 0 ? preselectedAgents : availableAgents;
|
|
1402
2130
|
}
|
|
1403
|
-
const agentChoices =
|
|
2131
|
+
const agentChoices = availableAgents.map((agent) => ({
|
|
1404
2132
|
value: agent,
|
|
1405
2133
|
label: agent.name,
|
|
1406
|
-
|
|
2134
|
+
...(options.global ? agent.globalPath || agent.rulesGlobalPath : agent.projectPath || agent.rulesProjectPath) ? {
|
|
2135
|
+
hint: options.global ? agent.globalPath || agent.rulesGlobalPath : agent.projectPath || agent.rulesProjectPath
|
|
2136
|
+
} : {}
|
|
1407
2137
|
}));
|
|
1408
2138
|
const selected = await multiselect({
|
|
1409
2139
|
message: "Select agents to install to:",
|
|
1410
2140
|
options: agentChoices,
|
|
1411
|
-
initialValues:
|
|
2141
|
+
initialValues: preselectedAgents,
|
|
1412
2142
|
required: true
|
|
1413
2143
|
});
|
|
1414
|
-
if (
|
|
1415
|
-
|
|
2144
|
+
if (isCancel(selected)) {
|
|
2145
|
+
cancel("Install cancelled.");
|
|
1416
2146
|
process.exit(0);
|
|
1417
2147
|
}
|
|
1418
2148
|
return selected;
|
|
@@ -1423,7 +2153,7 @@ async function installSkillsToAgent(agent, response, installPath) {
|
|
|
1423
2153
|
}
|
|
1424
2154
|
const result = await writeSkillsAsync(installPath, response.skills, agent.id);
|
|
1425
2155
|
for (const err of result.errors) {
|
|
1426
|
-
|
|
2156
|
+
log.warn(` Failed skill: ${err.skill} - ${err.error}`);
|
|
1427
2157
|
}
|
|
1428
2158
|
return { written: result.written.length, errors: result.errors.length };
|
|
1429
2159
|
}
|
|
@@ -1437,7 +2167,7 @@ async function installRulesToAgent(agent, response, options) {
|
|
|
1437
2167
|
}
|
|
1438
2168
|
const result = await writeRulesForAgentAsync(agent, rules, rulesPath);
|
|
1439
2169
|
for (const err of result.errors) {
|
|
1440
|
-
|
|
2170
|
+
log.warn(` Failed rules: ${err.agent} - ${err.error}`);
|
|
1441
2171
|
}
|
|
1442
2172
|
return { written: result.written, errors: result.errors.length };
|
|
1443
2173
|
}
|
|
@@ -1487,8 +2217,8 @@ async function installCommand(options) {
|
|
|
1487
2217
|
const resolved = resolveInstallConfig(options, config);
|
|
1488
2218
|
validateInstallOptions(resolved);
|
|
1489
2219
|
const sourceDesc = buildSourceDescription(resolved);
|
|
1490
|
-
|
|
1491
|
-
const installSpinner =
|
|
2220
|
+
intro(`Installing from ${sourceDesc}`);
|
|
2221
|
+
const installSpinner = spinner();
|
|
1492
2222
|
installSpinner.start("Fetching from Braid...");
|
|
1493
2223
|
try {
|
|
1494
2224
|
const fetchOptions = buildFetchOptions(resolved);
|
|
@@ -1504,7 +2234,7 @@ async function installCommand(options) {
|
|
|
1504
2234
|
}
|
|
1505
2235
|
installSpinner.stop(`Found ${foundParts.join(", ") || "nothing"}`);
|
|
1506
2236
|
if (skillCount === 0 && ruleCount === 0) {
|
|
1507
|
-
|
|
2237
|
+
log.warn(
|
|
1508
2238
|
"No skills or rules found. Check that your profile/project has enabled prompts."
|
|
1509
2239
|
);
|
|
1510
2240
|
process.exit(0);
|
|
@@ -1512,10 +2242,12 @@ async function installCommand(options) {
|
|
|
1512
2242
|
if (options.list) {
|
|
1513
2243
|
displaySkillsAndExit(response.skills);
|
|
1514
2244
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
2245
|
+
const agentResolution = await resolveAgents(options, installSpinner);
|
|
2246
|
+
const selectedAgents = await selectAgents(
|
|
2247
|
+
agentResolution.availableAgents,
|
|
2248
|
+
agentResolution.preselectedAgents,
|
|
2249
|
+
options
|
|
2250
|
+
);
|
|
1519
2251
|
let totalWritten = 0;
|
|
1520
2252
|
let totalErrors = 0;
|
|
1521
2253
|
for (const agent of selectedAgents) {
|
|
@@ -1530,24 +2262,24 @@ async function installCommand(options) {
|
|
|
1530
2262
|
totalErrors += result.errors;
|
|
1531
2263
|
}
|
|
1532
2264
|
if (totalErrors > 0) {
|
|
1533
|
-
|
|
2265
|
+
outro(`Installed ${totalWritten} items with ${totalErrors} errors.`);
|
|
1534
2266
|
} else {
|
|
1535
|
-
|
|
2267
|
+
outro(
|
|
1536
2268
|
`Successfully installed ${totalWritten} items to ${selectedAgents.length} agent(s).`
|
|
1537
2269
|
);
|
|
1538
2270
|
}
|
|
1539
|
-
|
|
2271
|
+
log.info("Run 'braid list' to see installed skills.");
|
|
1540
2272
|
} catch (error) {
|
|
1541
2273
|
installSpinner.stop("Install failed");
|
|
1542
2274
|
const message = error instanceof Error ? error.message : String(error);
|
|
1543
|
-
|
|
2275
|
+
log.error(message);
|
|
1544
2276
|
process.exit(1);
|
|
1545
2277
|
}
|
|
1546
2278
|
}
|
|
1547
2279
|
|
|
1548
2280
|
// src/commands/list.ts
|
|
1549
2281
|
init_esm_shims();
|
|
1550
|
-
import { log as
|
|
2282
|
+
import { log as log2, spinner as spinner2 } from "@clack/prompts";
|
|
1551
2283
|
function formatRelativeTime(isoDate) {
|
|
1552
2284
|
const date = new Date(isoDate);
|
|
1553
2285
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1573,16 +2305,16 @@ function displayAgentSkills(agent, installPath, braidSkills) {
|
|
|
1573
2305
|
const nameWidth = 25;
|
|
1574
2306
|
const sourceWidth = 20;
|
|
1575
2307
|
const installedWidth = 15;
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
2308
|
+
log2.info("");
|
|
2309
|
+
log2.info(`Agent: ${agent.name} (${installPath})`);
|
|
2310
|
+
log2.info("\u2500".repeat(60));
|
|
1579
2311
|
const header = [
|
|
1580
2312
|
"Skill".padEnd(nameWidth),
|
|
1581
2313
|
"Source".padEnd(sourceWidth),
|
|
1582
2314
|
"Installed".padEnd(installedWidth)
|
|
1583
2315
|
].join(" ");
|
|
1584
|
-
|
|
1585
|
-
|
|
2316
|
+
log2.info(header);
|
|
2317
|
+
log2.info("\u2500".repeat(60));
|
|
1586
2318
|
for (const skill of braidSkills) {
|
|
1587
2319
|
const sourceName = skill.source.name;
|
|
1588
2320
|
const row = [
|
|
@@ -1590,17 +2322,17 @@ function displayAgentSkills(agent, installPath, braidSkills) {
|
|
|
1590
2322
|
sourceName.slice(0, sourceWidth).padEnd(sourceWidth),
|
|
1591
2323
|
formatRelativeTime(skill.installedAt).padEnd(installedWidth)
|
|
1592
2324
|
].join(" ");
|
|
1593
|
-
|
|
2325
|
+
log2.info(row);
|
|
1594
2326
|
}
|
|
1595
2327
|
}
|
|
1596
2328
|
async function listCommand(options) {
|
|
1597
|
-
const listSpinner =
|
|
2329
|
+
const listSpinner = spinner2();
|
|
1598
2330
|
listSpinner.start("Scanning for installed skills...");
|
|
1599
2331
|
try {
|
|
1600
2332
|
const detectedAgents = await detectAgentsAsync();
|
|
1601
2333
|
if (detectedAgents.length === 0) {
|
|
1602
2334
|
listSpinner.stop("No agents detected");
|
|
1603
|
-
|
|
2335
|
+
log2.warn("No AI coding agents detected.");
|
|
1604
2336
|
return;
|
|
1605
2337
|
}
|
|
1606
2338
|
listSpinner.stop(`Found ${detectedAgents.length} agent(s)`);
|
|
@@ -1625,16 +2357,16 @@ async function listCommand(options) {
|
|
|
1625
2357
|
displayAgentSkills(agent, installPath, braidSkills);
|
|
1626
2358
|
}
|
|
1627
2359
|
if (totalSkills === 0) {
|
|
1628
|
-
|
|
1629
|
-
|
|
2360
|
+
log2.warn("\nNo skills installed via braid.");
|
|
2361
|
+
log2.info("Run 'braid install --profile <name>' to install skills.");
|
|
1630
2362
|
} else {
|
|
1631
|
-
|
|
1632
|
-
|
|
2363
|
+
log2.info("");
|
|
2364
|
+
log2.info(`Total: ${totalSkills} skill(s) installed`);
|
|
1633
2365
|
}
|
|
1634
2366
|
} catch (error) {
|
|
1635
2367
|
listSpinner.stop("List failed");
|
|
1636
2368
|
const message = error instanceof Error ? error.message : String(error);
|
|
1637
|
-
|
|
2369
|
+
log2.error(message);
|
|
1638
2370
|
process.exit(1);
|
|
1639
2371
|
}
|
|
1640
2372
|
}
|
|
@@ -1643,7 +2375,8 @@ async function listCommand(options) {
|
|
|
1643
2375
|
init_esm_shims();
|
|
1644
2376
|
import { rm } from "fs/promises";
|
|
1645
2377
|
import { join as join4, resolve as resolve3 } from "path";
|
|
1646
|
-
import
|
|
2378
|
+
import process7 from "process";
|
|
2379
|
+
init_tui();
|
|
1647
2380
|
async function collectInstalledSkills(detectedAgents, options) {
|
|
1648
2381
|
const skillsToRemove = [];
|
|
1649
2382
|
for (const agent of detectedAgents) {
|
|
@@ -1673,9 +2406,9 @@ async function selectSkillsToRemove(skillsToRemove, options) {
|
|
|
1673
2406
|
if (options.skill) {
|
|
1674
2407
|
const selected = skillsToRemove.filter((s) => s.name === options.skill);
|
|
1675
2408
|
if (selected.length === 0) {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2409
|
+
log.error(`Skill '${options.skill}' not found.`);
|
|
2410
|
+
log.info("Run 'braid list' to see installed skills.");
|
|
2411
|
+
process7.exit(1);
|
|
1679
2412
|
}
|
|
1680
2413
|
return selected;
|
|
1681
2414
|
}
|
|
@@ -1692,9 +2425,9 @@ async function selectSkillsToRemove(skillsToRemove, options) {
|
|
|
1692
2425
|
options: choices,
|
|
1693
2426
|
required: true
|
|
1694
2427
|
});
|
|
1695
|
-
if (
|
|
1696
|
-
|
|
1697
|
-
|
|
2428
|
+
if (isCancel(result)) {
|
|
2429
|
+
cancel("Remove cancelled.");
|
|
2430
|
+
process7.exit(0);
|
|
1698
2431
|
}
|
|
1699
2432
|
return result;
|
|
1700
2433
|
}
|
|
@@ -1702,13 +2435,13 @@ async function confirmRemoval(selectedCount, options) {
|
|
|
1702
2435
|
if (options.yes || options.skill || options.all) {
|
|
1703
2436
|
return;
|
|
1704
2437
|
}
|
|
1705
|
-
const confirmed = await
|
|
2438
|
+
const confirmed = await confirm({
|
|
1706
2439
|
message: `Remove ${selectedCount} skill(s)?`,
|
|
1707
2440
|
initialValue: false
|
|
1708
2441
|
});
|
|
1709
|
-
if (
|
|
1710
|
-
|
|
1711
|
-
|
|
2442
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
2443
|
+
cancel("Remove cancelled.");
|
|
2444
|
+
process7.exit(0);
|
|
1712
2445
|
}
|
|
1713
2446
|
}
|
|
1714
2447
|
async function removeSkill(skill, removeSpinner) {
|
|
@@ -1718,7 +2451,7 @@ async function removeSkill(skill, removeSpinner) {
|
|
|
1718
2451
|
const resolvedInstallPath = resolve3(skill.installPath);
|
|
1719
2452
|
if (!resolvedSkillPath.startsWith(`${resolvedInstallPath}/`)) {
|
|
1720
2453
|
removeSpinner.stop(`Unsafe path for ${skill.name}`);
|
|
1721
|
-
|
|
2454
|
+
log.warn(" Skill path escapes install directory, skipping.");
|
|
1722
2455
|
return false;
|
|
1723
2456
|
}
|
|
1724
2457
|
await rm(resolvedSkillPath, { recursive: true, force: true });
|
|
@@ -1728,18 +2461,18 @@ async function removeSkill(skill, removeSpinner) {
|
|
|
1728
2461
|
} catch (error) {
|
|
1729
2462
|
removeSpinner.stop(`Failed to remove ${skill.name}`);
|
|
1730
2463
|
const message = error instanceof Error ? error.message : String(error);
|
|
1731
|
-
|
|
2464
|
+
log.warn(` ${message}`);
|
|
1732
2465
|
return false;
|
|
1733
2466
|
}
|
|
1734
2467
|
}
|
|
1735
2468
|
async function removeCommand(options) {
|
|
1736
|
-
const removeSpinner =
|
|
2469
|
+
const removeSpinner = spinner();
|
|
1737
2470
|
removeSpinner.start("Scanning for installed skills...");
|
|
1738
2471
|
try {
|
|
1739
2472
|
const detectedAgents = await detectAgentsAsync();
|
|
1740
2473
|
if (detectedAgents.length === 0) {
|
|
1741
2474
|
removeSpinner.stop("No agents detected");
|
|
1742
|
-
|
|
2475
|
+
log.warn("No AI coding agents detected.");
|
|
1743
2476
|
return;
|
|
1744
2477
|
}
|
|
1745
2478
|
const skillsToRemove = await collectInstalledSkills(
|
|
@@ -1748,7 +2481,7 @@ async function removeCommand(options) {
|
|
|
1748
2481
|
);
|
|
1749
2482
|
removeSpinner.stop(`Found ${skillsToRemove.length} installed skill(s)`);
|
|
1750
2483
|
if (skillsToRemove.length === 0) {
|
|
1751
|
-
|
|
2484
|
+
log.warn("No skills installed via braid.");
|
|
1752
2485
|
return;
|
|
1753
2486
|
}
|
|
1754
2487
|
const selected = await selectSkillsToRemove(skillsToRemove, options);
|
|
@@ -1764,29 +2497,33 @@ async function removeCommand(options) {
|
|
|
1764
2497
|
}
|
|
1765
2498
|
}
|
|
1766
2499
|
if (errors > 0) {
|
|
1767
|
-
|
|
2500
|
+
outro(`Removed ${removed} skill(s) with ${errors} error(s).`);
|
|
1768
2501
|
} else {
|
|
1769
|
-
|
|
2502
|
+
outro(`Successfully removed ${removed} skill(s).`);
|
|
1770
2503
|
}
|
|
1771
2504
|
} catch (error) {
|
|
1772
2505
|
removeSpinner.stop("Remove failed");
|
|
1773
2506
|
const message = error instanceof Error ? error.message : String(error);
|
|
1774
|
-
|
|
1775
|
-
|
|
2507
|
+
log.error(message);
|
|
2508
|
+
process7.exit(1);
|
|
1776
2509
|
}
|
|
1777
2510
|
}
|
|
1778
2511
|
|
|
2512
|
+
// src/index.ts
|
|
2513
|
+
init_scope();
|
|
2514
|
+
|
|
1779
2515
|
// src/commands/update.ts
|
|
1780
2516
|
init_esm_shims();
|
|
1781
2517
|
import {
|
|
1782
|
-
cancel as
|
|
1783
|
-
isCancel as
|
|
1784
|
-
log as
|
|
2518
|
+
cancel as cancel2,
|
|
2519
|
+
isCancel as isCancel2,
|
|
2520
|
+
log as log3,
|
|
1785
2521
|
multiselect as multiselect2,
|
|
1786
|
-
outro as
|
|
1787
|
-
spinner as
|
|
2522
|
+
outro as outro2,
|
|
2523
|
+
spinner as spinner3
|
|
1788
2524
|
} from "@clack/prompts";
|
|
1789
2525
|
import { Data as Data6, Effect as Effect7, pipe as pipe7 } from "effect";
|
|
2526
|
+
init_api();
|
|
1790
2527
|
var UpdateError = class extends Data6.TaggedError("UpdateError") {
|
|
1791
2528
|
};
|
|
1792
2529
|
var UserCancelledError = class extends Data6.TaggedError("UserCancelledError") {
|
|
@@ -1861,7 +2598,7 @@ var selectSources = (sourcesToUpdate, options) => {
|
|
|
1861
2598
|
initialValues: sources.map((s) => s.value),
|
|
1862
2599
|
required: true
|
|
1863
2600
|
});
|
|
1864
|
-
if (
|
|
2601
|
+
if (isCancel2(selected)) {
|
|
1865
2602
|
throw new Error("cancelled");
|
|
1866
2603
|
}
|
|
1867
2604
|
for (const key of sourcesToUpdate.keys()) {
|
|
@@ -1988,7 +2725,7 @@ var updateAllSources = (sources, options, updateSpinner) => pipe7(
|
|
|
1988
2725
|
updateSpinner.stop(
|
|
1989
2726
|
`Failed to update from ${getSourceDesc(source)}`
|
|
1990
2727
|
);
|
|
1991
|
-
|
|
2728
|
+
log3.error(` ${error.message}`);
|
|
1992
2729
|
return Effect7.succeed({ updated: 0, errors: 1 });
|
|
1993
2730
|
})
|
|
1994
2731
|
),
|
|
@@ -2002,15 +2739,15 @@ var updateAllSources = (sources, options, updateSpinner) => pipe7(
|
|
|
2002
2739
|
var handleUpdateError = (error, updateSpinner) => {
|
|
2003
2740
|
updateSpinner.stop("Update failed");
|
|
2004
2741
|
if (error.message === "No AI coding agents detected.") {
|
|
2005
|
-
|
|
2742
|
+
log3.warn(error.message);
|
|
2006
2743
|
return;
|
|
2007
2744
|
}
|
|
2008
2745
|
if (error.message === "No skills installed via braid.") {
|
|
2009
|
-
|
|
2010
|
-
|
|
2746
|
+
log3.warn(error.message);
|
|
2747
|
+
log3.info("Run 'braid install --profile <name>' to install skills first.");
|
|
2011
2748
|
return;
|
|
2012
2749
|
}
|
|
2013
|
-
|
|
2750
|
+
log3.error(error.message);
|
|
2014
2751
|
process.exit(1);
|
|
2015
2752
|
};
|
|
2016
2753
|
var handleProgramExit = (result, updateSpinner) => {
|
|
@@ -2023,7 +2760,7 @@ var handleProgramExit = (result, updateSpinner) => {
|
|
|
2023
2760
|
}
|
|
2024
2761
|
const error = cause.error;
|
|
2025
2762
|
if (error._tag === "UserCancelledError") {
|
|
2026
|
-
|
|
2763
|
+
cancel2(error.message);
|
|
2027
2764
|
process.exit(0);
|
|
2028
2765
|
}
|
|
2029
2766
|
if (error._tag === "UpdateError") {
|
|
@@ -2031,7 +2768,7 @@ var handleProgramExit = (result, updateSpinner) => {
|
|
|
2031
2768
|
}
|
|
2032
2769
|
};
|
|
2033
2770
|
async function updateCommand(options) {
|
|
2034
|
-
const updateSpinner =
|
|
2771
|
+
const updateSpinner = spinner3();
|
|
2035
2772
|
updateSpinner.start("Scanning for installed skills...");
|
|
2036
2773
|
const program2 = pipe7(
|
|
2037
2774
|
Effect7.tryPromise({
|
|
@@ -2056,9 +2793,9 @@ async function updateCommand(options) {
|
|
|
2056
2793
|
),
|
|
2057
2794
|
Effect7.tap(({ totalUpdated, totalErrors }) => {
|
|
2058
2795
|
if (totalErrors > 0) {
|
|
2059
|
-
|
|
2796
|
+
outro2(`Updated ${totalUpdated} skills with ${totalErrors} errors.`);
|
|
2060
2797
|
} else {
|
|
2061
|
-
|
|
2798
|
+
outro2(`Successfully updated ${totalUpdated} skills.`);
|
|
2062
2799
|
}
|
|
2063
2800
|
})
|
|
2064
2801
|
);
|
|
@@ -2093,6 +2830,28 @@ program.command("install").alias("add").description("Install skills from a profi
|
|
|
2093
2830
|
"-a, --agents <list>",
|
|
2094
2831
|
"Comma-separated list of agents (e.g., claude-code,opencode)"
|
|
2095
2832
|
).option("-g, --global", "Install to global agent directories").option("-y, --yes", "Skip confirmation prompts").option("-l, --list", "Preview skills without installing").option("-s, --server <url>", "Braid server URL (for review apps, local dev)").action(installCommand);
|
|
2833
|
+
program.command("scope").description("Interactively configure braid.json or braid.user.json scope").option("--file <target>", "Config file target: user or project").option(
|
|
2834
|
+
"--organization <type>",
|
|
2835
|
+
"Scope organization for non-interactive mode: personal or organization"
|
|
2836
|
+
).option(
|
|
2837
|
+
"--source <source>",
|
|
2838
|
+
"Scope source for non-interactive mode: profile or manual"
|
|
2839
|
+
).option("--profile <name>", "Profile name for non-interactive mode").option(
|
|
2840
|
+
"--projects <ids>",
|
|
2841
|
+
"Comma-separated project IDs for non-interactive mode"
|
|
2842
|
+
).option(
|
|
2843
|
+
"--rule-ids <ids>",
|
|
2844
|
+
"Comma-separated included rule IDs for non-interactive mode"
|
|
2845
|
+
).option(
|
|
2846
|
+
"--excluded-rule-ids <ids>",
|
|
2847
|
+
"Comma-separated excluded rule IDs for non-interactive mode"
|
|
2848
|
+
).option(
|
|
2849
|
+
"--include-user-global",
|
|
2850
|
+
"Include personal global rules in non-interactive mode"
|
|
2851
|
+
).option(
|
|
2852
|
+
"--include-org-global",
|
|
2853
|
+
"Include org global rules in non-interactive mode"
|
|
2854
|
+
).option("-s, --server <url>", "Braid server URL (for review apps, local dev)").action(scopeCommand);
|
|
2096
2855
|
program.command("list").alias("ls").description("List installed skills").option("-g, --global", "List skills in global directories only").action(listCommand);
|
|
2097
2856
|
program.command("update").alias("up").description("Update installed skills to the latest version").option("-g, --global", "Update skills in global directories only").option("-y, --yes", "Skip confirmation prompts").option("-s, --server <url>", "Braid server URL (for review apps, local dev)").action(updateCommand);
|
|
2098
2857
|
program.command("remove").alias("rm").description("Remove installed skills").option("-a, --all", "Remove all installed skills").option("-g, --global", "Remove skills from global directories only").option("-y, --yes", "Skip confirmation prompts").option("--skill <name>", "Remove a specific skill by name").action(removeCommand);
|