@braid-cloud/cli 0.1.0
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 +283 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1780 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1780 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// ../../node_modules/tsup/assets/esm_shims.js
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
var init_esm_shims = __esm({
|
|
16
|
+
"../../node_modules/tsup/assets/esm_shims.js"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/lib/config.ts
|
|
22
|
+
var config_exports = {};
|
|
23
|
+
__export(config_exports, {
|
|
24
|
+
CONFIG_DIR: () => CONFIG_DIR,
|
|
25
|
+
CONFIG_FILE: () => CONFIG_FILE,
|
|
26
|
+
ConfigReadError: () => ConfigReadError,
|
|
27
|
+
ConfigWriteError: () => ConfigWriteError,
|
|
28
|
+
PROJECT_CONFIG_FILENAME: () => PROJECT_CONFIG_FILENAME,
|
|
29
|
+
USER_CONFIG_FILENAME: () => USER_CONFIG_FILENAME,
|
|
30
|
+
clearApiKey: () => clearApiKey,
|
|
31
|
+
clearApiKeyAsync: () => clearApiKeyAsync,
|
|
32
|
+
findProjectConfigFile: () => findProjectConfigFile,
|
|
33
|
+
findProjectConfigFileAsync: () => findProjectConfigFileAsync,
|
|
34
|
+
findUserConfigFile: () => findUserConfigFile,
|
|
35
|
+
findUserConfigFileAsync: () => findUserConfigFileAsync,
|
|
36
|
+
getApiKey: () => getApiKey,
|
|
37
|
+
getApiKeyAsync: () => getApiKeyAsync,
|
|
38
|
+
getServerUrl: () => getServerUrl,
|
|
39
|
+
getServerUrlAsync: () => getServerUrlAsync,
|
|
40
|
+
loadConfig: () => loadConfig,
|
|
41
|
+
loadConfigAsync: () => loadConfigAsync,
|
|
42
|
+
loadMergedConfig: () => loadMergedConfig,
|
|
43
|
+
loadMergedConfigAsync: () => loadMergedConfigAsync,
|
|
44
|
+
loadProjectConfig: () => loadProjectConfig,
|
|
45
|
+
loadProjectConfigAsync: () => loadProjectConfigAsync,
|
|
46
|
+
loadUserConfig: () => loadUserConfig,
|
|
47
|
+
loadUserConfigAsync: () => loadUserConfigAsync,
|
|
48
|
+
saveConfig: () => saveConfig,
|
|
49
|
+
saveConfigAsync: () => saveConfigAsync,
|
|
50
|
+
setApiKey: () => setApiKey,
|
|
51
|
+
setApiKeyAsync: () => setApiKeyAsync
|
|
52
|
+
});
|
|
53
|
+
import { existsSync } from "fs";
|
|
54
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
55
|
+
import { homedir } from "os";
|
|
56
|
+
import { dirname, join, parse } from "path";
|
|
57
|
+
import process2 from "process";
|
|
58
|
+
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, resolveServerUrlFromConfig, applyConfigSource, applyEnvOverrides, createDefaultMergedConfig, loadMergedConfig, loadConfig, saveConfig, getApiKey, setApiKey, getServerUrl, clearApiKey, loadConfigAsync, loadProjectConfigAsync, loadUserConfigAsync, loadMergedConfigAsync, findProjectConfigFileAsync, findUserConfigFileAsync, saveConfigAsync, getApiKeyAsync, setApiKeyAsync, getServerUrlAsync, clearApiKeyAsync;
|
|
60
|
+
var init_config = __esm({
|
|
61
|
+
"src/lib/config.ts"() {
|
|
62
|
+
"use strict";
|
|
63
|
+
init_esm_shims();
|
|
64
|
+
CONFIG_DIR = join(homedir(), ".config", "braid-skills");
|
|
65
|
+
CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
66
|
+
PROJECT_CONFIG_FILENAME = "braid.json";
|
|
67
|
+
USER_CONFIG_FILENAME = "braid.user.json";
|
|
68
|
+
ConfigReadError = class extends Data.TaggedError("ConfigReadError") {
|
|
69
|
+
};
|
|
70
|
+
ConfigWriteError = class extends Data.TaggedError("ConfigWriteError") {
|
|
71
|
+
};
|
|
72
|
+
findConfigFile = (filename, startDir = process2.cwd()) => {
|
|
73
|
+
let currentDir = startDir;
|
|
74
|
+
while (true) {
|
|
75
|
+
const configPath = join(currentDir, filename);
|
|
76
|
+
if (existsSync(configPath)) {
|
|
77
|
+
return configPath;
|
|
78
|
+
}
|
|
79
|
+
const parsed = parse(currentDir);
|
|
80
|
+
if (parsed.root === currentDir) {
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
currentDir = parsed.dir;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
findProjectConfigFile = (startDir = process2.cwd()) => findConfigFile(PROJECT_CONFIG_FILENAME, startDir);
|
|
87
|
+
findUserConfigFile = (startDir = process2.cwd()) => findConfigFile(USER_CONFIG_FILENAME, startDir);
|
|
88
|
+
loadProjectConfig = () => Effect.tryPromise({
|
|
89
|
+
try: async () => {
|
|
90
|
+
const configPath = await findProjectConfigFile();
|
|
91
|
+
if (!configPath) {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
const content = await readFile(configPath, "utf-8");
|
|
95
|
+
return JSON.parse(content);
|
|
96
|
+
},
|
|
97
|
+
catch: () => void 0
|
|
98
|
+
}).pipe(Effect.orElseSucceed(() => void 0));
|
|
99
|
+
loadUserConfig = () => Effect.tryPromise({
|
|
100
|
+
try: async () => {
|
|
101
|
+
const configPath = await findUserConfigFile();
|
|
102
|
+
if (!configPath) {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
const content = await readFile(configPath, "utf-8");
|
|
106
|
+
return JSON.parse(content);
|
|
107
|
+
},
|
|
108
|
+
catch: () => void 0
|
|
109
|
+
}).pipe(Effect.orElseSucceed(() => void 0));
|
|
110
|
+
resolveServerUrlFromConfig = (config) => {
|
|
111
|
+
if (!config) {
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
return config.skills?.serverUrl ?? config.serverUrl;
|
|
115
|
+
};
|
|
116
|
+
applyConfigSource = (merged, config) => {
|
|
117
|
+
if (!config) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const serverUrl = resolveServerUrlFromConfig(config);
|
|
121
|
+
if (serverUrl) {
|
|
122
|
+
merged.serverUrl = serverUrl;
|
|
123
|
+
}
|
|
124
|
+
if (config.profile) {
|
|
125
|
+
merged.profile = config.profile;
|
|
126
|
+
}
|
|
127
|
+
if (config.orgProjects) {
|
|
128
|
+
merged.orgProjects = config.orgProjects;
|
|
129
|
+
}
|
|
130
|
+
if (config.personalProjects) {
|
|
131
|
+
merged.personalProjects = config.personalProjects;
|
|
132
|
+
}
|
|
133
|
+
if (config.includeUserGlobalRules !== void 0) {
|
|
134
|
+
merged.includeUserGlobalRules = config.includeUserGlobalRules;
|
|
135
|
+
}
|
|
136
|
+
if (config.includeOrgGlobalRules !== void 0) {
|
|
137
|
+
merged.includeOrgGlobalRules = config.includeOrgGlobalRules;
|
|
138
|
+
}
|
|
139
|
+
if (config.agents) {
|
|
140
|
+
merged.agents = config.agents;
|
|
141
|
+
}
|
|
142
|
+
if ("token" in config && config.token) {
|
|
143
|
+
merged.token = config.token;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
applyEnvOverrides = (merged) => {
|
|
147
|
+
if (process2.env.BRAID_API_KEY) {
|
|
148
|
+
merged.token = process2.env.BRAID_API_KEY;
|
|
149
|
+
}
|
|
150
|
+
const envServerUrl = process2.env.BRAID_SKILLS_SERVER_URL ?? process2.env.BRAID_SERVER_URL;
|
|
151
|
+
if (envServerUrl) {
|
|
152
|
+
merged.serverUrl = envServerUrl;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
createDefaultMergedConfig = () => ({
|
|
156
|
+
serverUrl: "https://braid.cloud",
|
|
157
|
+
includeUserGlobalRules: true,
|
|
158
|
+
includeOrgGlobalRules: true
|
|
159
|
+
});
|
|
160
|
+
loadMergedConfig = () => pipe(
|
|
161
|
+
Effect.all({
|
|
162
|
+
projectConfig: loadProjectConfig(),
|
|
163
|
+
userConfig: loadUserConfig(),
|
|
164
|
+
globalConfig: loadConfig().pipe(
|
|
165
|
+
Effect.orElseSucceed(() => ({}))
|
|
166
|
+
)
|
|
167
|
+
}),
|
|
168
|
+
Effect.map(({ projectConfig, userConfig, globalConfig }) => {
|
|
169
|
+
const merged = createDefaultMergedConfig();
|
|
170
|
+
applyConfigSource(merged, projectConfig);
|
|
171
|
+
applyConfigSource(merged, userConfig);
|
|
172
|
+
applyEnvOverrides(merged);
|
|
173
|
+
if (!merged.token && globalConfig.apiKey) {
|
|
174
|
+
merged.token = globalConfig.apiKey;
|
|
175
|
+
}
|
|
176
|
+
return merged;
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
loadConfig = () => pipe(
|
|
180
|
+
Effect.tryPromise({
|
|
181
|
+
try: () => readFile(CONFIG_FILE, "utf-8"),
|
|
182
|
+
catch: (e) => new ConfigReadError({ path: CONFIG_FILE, cause: e })
|
|
183
|
+
}),
|
|
184
|
+
Effect.flatMap(
|
|
185
|
+
(content) => Effect.try({
|
|
186
|
+
try: () => JSON.parse(content),
|
|
187
|
+
catch: () => ({})
|
|
188
|
+
})
|
|
189
|
+
),
|
|
190
|
+
Effect.orElseSucceed(() => ({}))
|
|
191
|
+
);
|
|
192
|
+
saveConfig = (config) => pipe(
|
|
193
|
+
Effect.tryPromise({
|
|
194
|
+
try: async () => {
|
|
195
|
+
await mkdir(dirname(CONFIG_FILE), { recursive: true });
|
|
196
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
197
|
+
},
|
|
198
|
+
catch: (e) => new ConfigWriteError({ path: CONFIG_FILE, cause: e })
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
getApiKey = () => pipe(
|
|
202
|
+
Effect.succeed(process2.env.BRAID_API_KEY),
|
|
203
|
+
Effect.flatMap(
|
|
204
|
+
(envKey) => envKey ? Effect.succeed(envKey) : pipe(
|
|
205
|
+
loadUserConfig(),
|
|
206
|
+
Effect.flatMap(
|
|
207
|
+
(userConfig) => userConfig?.token ? Effect.succeed(userConfig.token) : pipe(
|
|
208
|
+
loadConfig(),
|
|
209
|
+
Effect.map((config) => config.apiKey)
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
setApiKey = (apiKey) => pipe(
|
|
216
|
+
loadConfig(),
|
|
217
|
+
Effect.flatMap((config) => saveConfig({ ...config, apiKey }))
|
|
218
|
+
);
|
|
219
|
+
getServerUrl = () => pipe(
|
|
220
|
+
Effect.succeed(process2.env.BRAID_SERVER_URL),
|
|
221
|
+
Effect.flatMap(
|
|
222
|
+
(envUrl) => envUrl ? Effect.succeed(envUrl) : pipe(
|
|
223
|
+
loadUserConfig(),
|
|
224
|
+
Effect.flatMap(
|
|
225
|
+
(userConfig) => userConfig?.serverUrl ? Effect.succeed(userConfig.serverUrl) : pipe(
|
|
226
|
+
loadConfig(),
|
|
227
|
+
Effect.map(
|
|
228
|
+
(config) => config.serverUrl ?? "https://braid.cloud"
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
clearApiKey = () => pipe(
|
|
236
|
+
loadConfig(),
|
|
237
|
+
Effect.flatMap((config) => {
|
|
238
|
+
const { apiKey: _, ...rest } = config;
|
|
239
|
+
return saveConfig(rest);
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
loadConfigAsync = () => Effect.runPromise(loadConfig());
|
|
243
|
+
loadProjectConfigAsync = () => Effect.runPromise(loadProjectConfig());
|
|
244
|
+
loadUserConfigAsync = () => Effect.runPromise(loadUserConfig());
|
|
245
|
+
loadMergedConfigAsync = () => Effect.runPromise(loadMergedConfig());
|
|
246
|
+
findProjectConfigFileAsync = (startDir) => findProjectConfigFile(startDir);
|
|
247
|
+
findUserConfigFileAsync = (startDir) => findUserConfigFile(startDir);
|
|
248
|
+
saveConfigAsync = (config) => Effect.runPromise(saveConfig(config));
|
|
249
|
+
getApiKeyAsync = () => Effect.runPromise(getApiKey());
|
|
250
|
+
setApiKeyAsync = (apiKey) => Effect.runPromise(setApiKey(apiKey));
|
|
251
|
+
getServerUrlAsync = () => Effect.runPromise(getServerUrl());
|
|
252
|
+
clearApiKeyAsync = () => Effect.runPromise(clearApiKey());
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// src/index.ts
|
|
257
|
+
init_esm_shims();
|
|
258
|
+
import { Command } from "commander";
|
|
259
|
+
|
|
260
|
+
// src/commands/auth.ts
|
|
261
|
+
init_esm_shims();
|
|
262
|
+
import process3 from "process";
|
|
263
|
+
import {
|
|
264
|
+
cancel,
|
|
265
|
+
confirm,
|
|
266
|
+
intro,
|
|
267
|
+
isCancel,
|
|
268
|
+
log,
|
|
269
|
+
outro,
|
|
270
|
+
password,
|
|
271
|
+
spinner
|
|
272
|
+
} from "@clack/prompts";
|
|
273
|
+
|
|
274
|
+
// src/lib/api.ts
|
|
275
|
+
init_esm_shims();
|
|
276
|
+
init_config();
|
|
277
|
+
import { Data as Data2, Effect as Effect2, pipe as pipe2 } from "effect";
|
|
278
|
+
var TRAILING_SLASH_REGEX = /\/$/;
|
|
279
|
+
var ApiError = class extends Data2.TaggedError("ApiError") {
|
|
280
|
+
};
|
|
281
|
+
var AuthenticationError = class extends Data2.TaggedError("AuthenticationError") {
|
|
282
|
+
};
|
|
283
|
+
var NetworkError = class extends Data2.TaggedError("NetworkError") {
|
|
284
|
+
};
|
|
285
|
+
var resolveApiKey = (optionsApiKey) => {
|
|
286
|
+
if (optionsApiKey) {
|
|
287
|
+
return Effect2.succeed(optionsApiKey);
|
|
288
|
+
}
|
|
289
|
+
return pipe2(
|
|
290
|
+
Effect2.tryPromise({
|
|
291
|
+
try: () => getApiKeyAsync(),
|
|
292
|
+
catch: () => new NetworkError({ message: "Failed to read config" })
|
|
293
|
+
}),
|
|
294
|
+
Effect2.flatMap(
|
|
295
|
+
(key) => key ? Effect2.succeed(key) : Effect2.fail(
|
|
296
|
+
new AuthenticationError({
|
|
297
|
+
message: 'No API key configured. Run "pvskills auth" to authenticate.'
|
|
298
|
+
})
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
var resolveServerUrl = (optionsServerUrl) => {
|
|
304
|
+
if (optionsServerUrl) {
|
|
305
|
+
return Effect2.succeed(optionsServerUrl);
|
|
306
|
+
}
|
|
307
|
+
return Effect2.tryPromise({
|
|
308
|
+
try: () => getServerUrlAsync(),
|
|
309
|
+
catch: () => new NetworkError({ message: "Failed to read config" })
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
var parseResponse = (response, json) => {
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
const errorResponse = json;
|
|
315
|
+
if (response.status === 401) {
|
|
316
|
+
return Effect2.fail(
|
|
317
|
+
new AuthenticationError({
|
|
318
|
+
message: errorResponse.error || "Invalid or expired API key. Run 'pvskills auth' to re-authenticate."
|
|
319
|
+
})
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return Effect2.fail(
|
|
323
|
+
new ApiError({
|
|
324
|
+
message: errorResponse.error || "API request failed",
|
|
325
|
+
code: errorResponse.code || "UNKNOWN_ERROR",
|
|
326
|
+
status: response.status
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return Effect2.succeed(json);
|
|
331
|
+
};
|
|
332
|
+
var buildApiUrl = (serverUrl, path2) => {
|
|
333
|
+
const baseUrl = serverUrl.replace(TRAILING_SLASH_REGEX, "");
|
|
334
|
+
return new URL(`${baseUrl}${path2}`);
|
|
335
|
+
};
|
|
336
|
+
var buildExportUrl = (serverUrl, options) => {
|
|
337
|
+
const url = buildApiUrl(serverUrl, "/api/skills/export");
|
|
338
|
+
if (options.profile) {
|
|
339
|
+
url.searchParams.set("profile", options.profile);
|
|
340
|
+
}
|
|
341
|
+
if (options.orgProjects && options.orgProjects.length > 0) {
|
|
342
|
+
url.searchParams.set("orgProjects", options.orgProjects.join(","));
|
|
343
|
+
}
|
|
344
|
+
if (options.personalProjects && options.personalProjects.length > 0) {
|
|
345
|
+
url.searchParams.set(
|
|
346
|
+
"personalProjects",
|
|
347
|
+
options.personalProjects.join(",")
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (options.includeUserGlobalRules !== void 0) {
|
|
351
|
+
url.searchParams.set(
|
|
352
|
+
"includeUserGlobal",
|
|
353
|
+
String(options.includeUserGlobalRules)
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (options.includeOrgGlobalRules !== void 0) {
|
|
357
|
+
url.searchParams.set(
|
|
358
|
+
"includeOrgGlobal",
|
|
359
|
+
String(options.includeOrgGlobalRules)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return url;
|
|
363
|
+
};
|
|
364
|
+
var executeApiRequest = (url, apiKey, serverUrl) => pipe2(
|
|
365
|
+
Effect2.tryPromise({
|
|
366
|
+
try: () => fetch(url.toString(), {
|
|
367
|
+
method: "GET",
|
|
368
|
+
headers: {
|
|
369
|
+
Authorization: `Bearer ${apiKey}`,
|
|
370
|
+
"Content-Type": "application/json"
|
|
371
|
+
}
|
|
372
|
+
}),
|
|
373
|
+
catch: (e) => new NetworkError({
|
|
374
|
+
message: `Failed to connect to ${serverUrl}`,
|
|
375
|
+
cause: e
|
|
376
|
+
})
|
|
377
|
+
}),
|
|
378
|
+
Effect2.flatMap(
|
|
379
|
+
(response) => pipe2(
|
|
380
|
+
Effect2.tryPromise({
|
|
381
|
+
try: () => response.json(),
|
|
382
|
+
catch: () => new NetworkError({ message: "Failed to parse API response" })
|
|
383
|
+
}),
|
|
384
|
+
Effect2.flatMap((json) => parseResponse(response, json))
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
);
|
|
388
|
+
var fetchSkills = (options) => pipe2(
|
|
389
|
+
Effect2.all({
|
|
390
|
+
apiKey: resolveApiKey(options.apiKey),
|
|
391
|
+
serverUrl: resolveServerUrl(options.serverUrl)
|
|
392
|
+
}),
|
|
393
|
+
Effect2.flatMap(({ apiKey, serverUrl }) => {
|
|
394
|
+
const url = buildExportUrl(serverUrl, options);
|
|
395
|
+
return executeApiRequest(url, apiKey, serverUrl);
|
|
396
|
+
})
|
|
397
|
+
);
|
|
398
|
+
var validateApiKey = (apiKey, serverUrl) => pipe2(
|
|
399
|
+
Effect2.tryPromise({
|
|
400
|
+
try: async () => {
|
|
401
|
+
const baseUrl = serverUrl ?? "https://braid.cloud";
|
|
402
|
+
const url = buildApiUrl(baseUrl, "/api/skills/export");
|
|
403
|
+
url.searchParams.set("profile", "default");
|
|
404
|
+
const response = await fetch(url.toString(), {
|
|
405
|
+
method: "GET",
|
|
406
|
+
headers: {
|
|
407
|
+
Authorization: `Bearer ${apiKey}`,
|
|
408
|
+
"Content-Type": "application/json"
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
return response.status !== 401;
|
|
412
|
+
},
|
|
413
|
+
catch: (e) => new NetworkError({
|
|
414
|
+
message: `Failed to connect to ${serverUrl ?? "https://braid.cloud"}`,
|
|
415
|
+
cause: e
|
|
416
|
+
})
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
var fetchSkillsAsync = (options) => Effect2.runPromise(fetchSkills(options));
|
|
420
|
+
var validateApiKeyAsync = (apiKey, serverUrl) => Effect2.runPromise(validateApiKey(apiKey, serverUrl));
|
|
421
|
+
|
|
422
|
+
// src/commands/auth.ts
|
|
423
|
+
init_config();
|
|
424
|
+
async function authCommand(options) {
|
|
425
|
+
intro("braid-skills auth");
|
|
426
|
+
const config = await loadMergedConfigAsync();
|
|
427
|
+
if (config.token) {
|
|
428
|
+
const shouldReplace = await confirm({
|
|
429
|
+
message: "An API key is already configured. Replace it?",
|
|
430
|
+
initialValue: false
|
|
431
|
+
});
|
|
432
|
+
if (isCancel(shouldReplace) || !shouldReplace) {
|
|
433
|
+
outro("Auth cancelled.");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const apiKey = await password({
|
|
438
|
+
message: "Enter your Braid API key:",
|
|
439
|
+
validate: (value) => {
|
|
440
|
+
if (!value) {
|
|
441
|
+
return "API key is required";
|
|
442
|
+
}
|
|
443
|
+
if (!value.startsWith("br_")) {
|
|
444
|
+
return "API key should start with 'br_'";
|
|
445
|
+
}
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
if (isCancel(apiKey)) {
|
|
450
|
+
cancel("Auth cancelled.");
|
|
451
|
+
process3.exit(0);
|
|
452
|
+
}
|
|
453
|
+
const authSpinner = spinner();
|
|
454
|
+
authSpinner.start("Validating API key...");
|
|
455
|
+
try {
|
|
456
|
+
const serverUrl = options.server ?? "https://braid.cloud";
|
|
457
|
+
const isValid = await validateApiKeyAsync(apiKey, serverUrl);
|
|
458
|
+
if (!isValid) {
|
|
459
|
+
authSpinner.stop("Invalid API key");
|
|
460
|
+
log.error(
|
|
461
|
+
"The API key could not be validated. Please check your key and try again."
|
|
462
|
+
);
|
|
463
|
+
process3.exit(1);
|
|
464
|
+
}
|
|
465
|
+
await setApiKeyAsync(apiKey);
|
|
466
|
+
authSpinner.stop("API key validated and saved");
|
|
467
|
+
log.success(`Config saved to ${CONFIG_FILE}`);
|
|
468
|
+
outro(
|
|
469
|
+
"You're authenticated! Run 'braid-skills install --profile <name>' to install skills."
|
|
470
|
+
);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
authSpinner.stop("Validation failed");
|
|
473
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
474
|
+
log.error(`Failed to validate API key: ${message}`);
|
|
475
|
+
process3.exit(1);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function displayTokenSource(masked) {
|
|
479
|
+
if (process3.env.BRAID_API_KEY) {
|
|
480
|
+
log.info(`Authenticated with key: ${masked}`);
|
|
481
|
+
log.info("Source: BRAID_API_KEY environment variable");
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const userConfigPath = await findUserConfigFileAsync();
|
|
485
|
+
log.info(`Authenticated with key: ${masked}`);
|
|
486
|
+
log.info(`Source: ${userConfigPath ?? CONFIG_FILE}`);
|
|
487
|
+
}
|
|
488
|
+
async function displayProjectConfig() {
|
|
489
|
+
const projectConfigPath = await findProjectConfigFileAsync();
|
|
490
|
+
if (projectConfigPath) {
|
|
491
|
+
log.info(`Project config: ${projectConfigPath}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function displayResolvedSettings(config) {
|
|
495
|
+
const hasOrgProjects = config.orgProjects && config.orgProjects.length > 0;
|
|
496
|
+
const hasPersonalProjects = config.personalProjects && config.personalProjects.length > 0;
|
|
497
|
+
if (!(config.profile || hasOrgProjects || hasPersonalProjects)) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
log.info("");
|
|
501
|
+
if (config.profile) {
|
|
502
|
+
log.info(`Default profile: ${config.profile}`);
|
|
503
|
+
}
|
|
504
|
+
if (hasOrgProjects) {
|
|
505
|
+
log.info(`Org projects: ${config.orgProjects?.join(", ")}`);
|
|
506
|
+
}
|
|
507
|
+
if (hasPersonalProjects) {
|
|
508
|
+
log.info(`Personal projects: ${config.personalProjects?.join(", ")}`);
|
|
509
|
+
}
|
|
510
|
+
if (config.serverUrl !== "https://braid.cloud") {
|
|
511
|
+
log.info(`Server: ${config.serverUrl}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async function authStatusCommand() {
|
|
515
|
+
const config = await loadMergedConfigAsync();
|
|
516
|
+
if (!config.token) {
|
|
517
|
+
log.warn(
|
|
518
|
+
"Not authenticated. Run 'braid-skills auth' to configure your API key."
|
|
519
|
+
);
|
|
520
|
+
log.info(
|
|
521
|
+
`Or create a ${USER_CONFIG_FILENAME} file with: { "token": "br_xxx" }`
|
|
522
|
+
);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const masked = `${config.token.slice(0, 7)}...${config.token.slice(-4)}`;
|
|
526
|
+
await displayTokenSource(masked);
|
|
527
|
+
await displayProjectConfig();
|
|
528
|
+
displayResolvedSettings(config);
|
|
529
|
+
}
|
|
530
|
+
async function authLogoutCommand() {
|
|
531
|
+
const { clearApiKeyAsync: clearApiKeyAsync2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
532
|
+
await clearApiKeyAsync2();
|
|
533
|
+
log.success("Logged out. API key removed from config.");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/commands/install.ts
|
|
537
|
+
init_esm_shims();
|
|
538
|
+
|
|
539
|
+
// src/lib/agents.ts
|
|
540
|
+
init_esm_shims();
|
|
541
|
+
import { access, constants } from "fs/promises";
|
|
542
|
+
import { homedir as homedir2 } from "os";
|
|
543
|
+
import { join as join2 } from "path";
|
|
544
|
+
import process4 from "process";
|
|
545
|
+
import { Effect as Effect3, pipe as pipe3 } from "effect";
|
|
546
|
+
var home = homedir2();
|
|
547
|
+
var AGENTS = [
|
|
548
|
+
{
|
|
549
|
+
id: "amp",
|
|
550
|
+
name: "Amp, Kimi Code CLI",
|
|
551
|
+
projectPath: ".agents/skills",
|
|
552
|
+
globalPath: join2(home, ".config", "agents", "skills")
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
id: "kimi-cli",
|
|
556
|
+
name: "Kimi Code CLI",
|
|
557
|
+
projectPath: ".agents/skills",
|
|
558
|
+
globalPath: join2(home, ".config", "agents", "skills")
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: "antigravity",
|
|
562
|
+
name: "Antigravity",
|
|
563
|
+
projectPath: ".agent/skills",
|
|
564
|
+
globalPath: join2(home, ".gemini", "antigravity", "global_skills")
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "claude-code",
|
|
568
|
+
name: "Claude Code",
|
|
569
|
+
projectPath: ".claude/skills",
|
|
570
|
+
globalPath: join2(home, ".claude", "skills")
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: "moltbot",
|
|
574
|
+
name: "Moltbot",
|
|
575
|
+
projectPath: "skills",
|
|
576
|
+
globalPath: join2(home, ".moltbot", "skills")
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
id: "cline",
|
|
580
|
+
name: "Cline",
|
|
581
|
+
projectPath: ".cline/skills",
|
|
582
|
+
globalPath: join2(home, ".cline", "skills")
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
id: "codebuddy",
|
|
586
|
+
name: "CodeBuddy",
|
|
587
|
+
projectPath: ".codebuddy/skills",
|
|
588
|
+
globalPath: join2(home, ".codebuddy", "skills")
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
id: "codex",
|
|
592
|
+
name: "Codex",
|
|
593
|
+
projectPath: ".codex/skills",
|
|
594
|
+
globalPath: join2(home, ".codex", "skills")
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
id: "command-code",
|
|
598
|
+
name: "Command Code",
|
|
599
|
+
projectPath: ".commandcode/skills",
|
|
600
|
+
globalPath: join2(home, ".commandcode", "skills")
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
id: "continue",
|
|
604
|
+
name: "Continue",
|
|
605
|
+
projectPath: ".continue/skills",
|
|
606
|
+
globalPath: join2(home, ".continue", "skills")
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
id: "crush",
|
|
610
|
+
name: "Crush",
|
|
611
|
+
projectPath: ".crush/skills",
|
|
612
|
+
globalPath: join2(home, ".config", "crush", "skills")
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
id: "cursor",
|
|
616
|
+
name: "Cursor",
|
|
617
|
+
projectPath: ".cursor/skills",
|
|
618
|
+
globalPath: join2(home, ".cursor", "skills")
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
id: "droid",
|
|
622
|
+
name: "Droid",
|
|
623
|
+
projectPath: ".factory/skills",
|
|
624
|
+
globalPath: join2(home, ".factory", "skills")
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
id: "gemini-cli",
|
|
628
|
+
name: "Gemini CLI",
|
|
629
|
+
projectPath: ".gemini/skills",
|
|
630
|
+
globalPath: join2(home, ".gemini", "skills")
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
id: "github-copilot",
|
|
634
|
+
name: "GitHub Copilot",
|
|
635
|
+
projectPath: ".github/skills",
|
|
636
|
+
globalPath: join2(home, ".copilot", "skills")
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
id: "goose",
|
|
640
|
+
name: "Goose",
|
|
641
|
+
projectPath: ".goose/skills",
|
|
642
|
+
globalPath: join2(home, ".config", "goose", "skills")
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
id: "junie",
|
|
646
|
+
name: "Junie",
|
|
647
|
+
projectPath: ".junie/skills",
|
|
648
|
+
globalPath: join2(home, ".junie", "skills")
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
id: "kilo",
|
|
652
|
+
name: "Kilo Code",
|
|
653
|
+
projectPath: ".kilocode/skills",
|
|
654
|
+
globalPath: join2(home, ".kilocode", "skills")
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
id: "kiro-cli",
|
|
658
|
+
name: "Kiro CLI",
|
|
659
|
+
projectPath: ".kiro/skills",
|
|
660
|
+
globalPath: join2(home, ".kiro", "skills")
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
id: "kode",
|
|
664
|
+
name: "Kode",
|
|
665
|
+
projectPath: ".kode/skills",
|
|
666
|
+
globalPath: join2(home, ".kode", "skills")
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
id: "mcpjam",
|
|
670
|
+
name: "MCPJam",
|
|
671
|
+
projectPath: ".mcpjam/skills",
|
|
672
|
+
globalPath: join2(home, ".mcpjam", "skills")
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
id: "mux",
|
|
676
|
+
name: "Mux",
|
|
677
|
+
projectPath: ".mux/skills",
|
|
678
|
+
globalPath: join2(home, ".mux", "skills")
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
id: "opencode",
|
|
682
|
+
name: "OpenCode",
|
|
683
|
+
projectPath: ".opencode/skills",
|
|
684
|
+
globalPath: join2(home, ".config", "opencode", "skills")
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
id: "openhands",
|
|
688
|
+
name: "OpenHands",
|
|
689
|
+
projectPath: ".openhands/skills",
|
|
690
|
+
globalPath: join2(home, ".openhands", "skills")
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
id: "pi",
|
|
694
|
+
name: "Pi",
|
|
695
|
+
projectPath: ".pi/skills",
|
|
696
|
+
globalPath: join2(home, ".pi", "agent", "skills")
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
id: "qoder",
|
|
700
|
+
name: "Qoder",
|
|
701
|
+
projectPath: ".qoder/skills",
|
|
702
|
+
globalPath: join2(home, ".qoder", "skills")
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
id: "qwen-code",
|
|
706
|
+
name: "Qwen Code",
|
|
707
|
+
projectPath: ".qwen/skills",
|
|
708
|
+
globalPath: join2(home, ".qwen", "skills")
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
id: "roo",
|
|
712
|
+
name: "Roo Code",
|
|
713
|
+
projectPath: ".roo/skills",
|
|
714
|
+
globalPath: join2(home, ".roo", "skills")
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
id: "trae",
|
|
718
|
+
name: "Trae",
|
|
719
|
+
projectPath: ".trae/skills",
|
|
720
|
+
globalPath: join2(home, ".trae", "skills")
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
id: "windsurf",
|
|
724
|
+
name: "Windsurf",
|
|
725
|
+
projectPath: ".windsurf/skills",
|
|
726
|
+
globalPath: join2(home, ".codeium", "windsurf", "skills")
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
id: "zencoder",
|
|
730
|
+
name: "Zencoder",
|
|
731
|
+
projectPath: ".zencoder/skills",
|
|
732
|
+
globalPath: join2(home, ".zencoder", "skills")
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
id: "neovate",
|
|
736
|
+
name: "Neovate",
|
|
737
|
+
projectPath: ".neovate/skills",
|
|
738
|
+
globalPath: join2(home, ".neovate", "skills")
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
id: "pochi",
|
|
742
|
+
name: "Pochi",
|
|
743
|
+
projectPath: ".pochi/skills",
|
|
744
|
+
globalPath: join2(home, ".pochi", "skills")
|
|
745
|
+
}
|
|
746
|
+
];
|
|
747
|
+
var directoryExists = (path2) => pipe3(
|
|
748
|
+
Effect3.tryPromise({
|
|
749
|
+
try: () => access(path2, constants.F_OK),
|
|
750
|
+
catch: () => false
|
|
751
|
+
}),
|
|
752
|
+
Effect3.map(() => true),
|
|
753
|
+
Effect3.orElseSucceed(() => false)
|
|
754
|
+
);
|
|
755
|
+
var getAgentConfigDir = (skillsPath) => {
|
|
756
|
+
const parts = skillsPath.split("/");
|
|
757
|
+
return parts.slice(0, -1).join("/");
|
|
758
|
+
};
|
|
759
|
+
var detectAgents = (projectRoot) => {
|
|
760
|
+
const cwd = projectRoot ?? process4.cwd();
|
|
761
|
+
return pipe3(
|
|
762
|
+
Effect3.forEach(
|
|
763
|
+
AGENTS,
|
|
764
|
+
(agent) => pipe3(
|
|
765
|
+
Effect3.all({
|
|
766
|
+
hasProjectConfig: directoryExists(
|
|
767
|
+
join2(cwd, getAgentConfigDir(agent.projectPath))
|
|
768
|
+
),
|
|
769
|
+
hasGlobalConfig: directoryExists(getAgentConfigDir(agent.globalPath))
|
|
770
|
+
}),
|
|
771
|
+
Effect3.map(
|
|
772
|
+
({ hasProjectConfig, hasGlobalConfig }) => ({
|
|
773
|
+
...agent,
|
|
774
|
+
hasProjectConfig,
|
|
775
|
+
hasGlobalConfig
|
|
776
|
+
})
|
|
777
|
+
)
|
|
778
|
+
)
|
|
779
|
+
),
|
|
780
|
+
Effect3.map(
|
|
781
|
+
(agents) => agents.filter((a) => a.hasProjectConfig || a.hasGlobalConfig)
|
|
782
|
+
)
|
|
783
|
+
);
|
|
784
|
+
};
|
|
785
|
+
var getAgentById = (id) => AGENTS.find((a) => a.id === id);
|
|
786
|
+
var detectAgentsAsync = (projectRoot) => Effect3.runPromise(detectAgents(projectRoot));
|
|
787
|
+
var directoryExistsAsync = (path2) => Effect3.runPromise(directoryExists(path2));
|
|
788
|
+
var resolveInstallPath = (agent, options) => {
|
|
789
|
+
if (options.global) {
|
|
790
|
+
return agent.globalPath;
|
|
791
|
+
}
|
|
792
|
+
const cwd = options.projectRoot ?? process4.cwd();
|
|
793
|
+
return join2(cwd, agent.projectPath);
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// src/commands/install.ts
|
|
797
|
+
init_config();
|
|
798
|
+
|
|
799
|
+
// src/lib/metadata.ts
|
|
800
|
+
init_esm_shims();
|
|
801
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
802
|
+
import { join as join3 } from "path";
|
|
803
|
+
import { Data as Data3, Effect as Effect4, pipe as pipe4 } from "effect";
|
|
804
|
+
var METADATA_FILENAME = ".braidskills-metadata.json";
|
|
805
|
+
var MetadataReadError = class extends Data3.TaggedError("MetadataReadError") {
|
|
806
|
+
};
|
|
807
|
+
var MetadataWriteError = class extends Data3.TaggedError("MetadataWriteError") {
|
|
808
|
+
};
|
|
809
|
+
var getMetadataPath = (skillsDir) => join3(skillsDir, METADATA_FILENAME);
|
|
810
|
+
var readMetadata = (skillsDir) => {
|
|
811
|
+
const metadataPath = getMetadataPath(skillsDir);
|
|
812
|
+
return pipe4(
|
|
813
|
+
Effect4.tryPromise({
|
|
814
|
+
try: () => readFile2(metadataPath, "utf-8"),
|
|
815
|
+
catch: (e) => new MetadataReadError({ path: metadataPath, cause: e })
|
|
816
|
+
}),
|
|
817
|
+
Effect4.flatMap(
|
|
818
|
+
(content) => Effect4.try({
|
|
819
|
+
try: () => JSON.parse(content),
|
|
820
|
+
catch: () => ({ skills: [] })
|
|
821
|
+
})
|
|
822
|
+
),
|
|
823
|
+
Effect4.orElseSucceed(() => ({ skills: [] }))
|
|
824
|
+
);
|
|
825
|
+
};
|
|
826
|
+
var writeMetadata = (skillsDir, metadata) => {
|
|
827
|
+
const metadataPath = getMetadataPath(skillsDir);
|
|
828
|
+
return Effect4.tryPromise({
|
|
829
|
+
try: () => writeFile2(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"),
|
|
830
|
+
catch: (e) => new MetadataWriteError({ path: metadataPath, cause: e })
|
|
831
|
+
});
|
|
832
|
+
};
|
|
833
|
+
var updateMetadata = (skillsDir, newSkills) => pipe4(
|
|
834
|
+
readMetadata(skillsDir),
|
|
835
|
+
Effect4.map((existing) => {
|
|
836
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
837
|
+
const updatedSkills = [...existing.skills];
|
|
838
|
+
for (const skill of newSkills) {
|
|
839
|
+
const existingIndex = updatedSkills.findIndex(
|
|
840
|
+
(s) => s.name === skill.name
|
|
841
|
+
);
|
|
842
|
+
const newEntry = {
|
|
843
|
+
name: skill.name,
|
|
844
|
+
source: skill.source,
|
|
845
|
+
version: skill.version,
|
|
846
|
+
installedAt: now,
|
|
847
|
+
serverUrl: skill.serverUrl
|
|
848
|
+
};
|
|
849
|
+
if (existingIndex >= 0) {
|
|
850
|
+
updatedSkills[existingIndex] = newEntry;
|
|
851
|
+
} else {
|
|
852
|
+
updatedSkills.push(newEntry);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return { skills: updatedSkills };
|
|
856
|
+
}),
|
|
857
|
+
Effect4.flatMap((metadata) => writeMetadata(skillsDir, metadata))
|
|
858
|
+
);
|
|
859
|
+
var removeFromMetadata = (skillsDir, skillName) => pipe4(
|
|
860
|
+
readMetadata(skillsDir),
|
|
861
|
+
Effect4.map((metadata) => ({
|
|
862
|
+
skills: metadata.skills.filter((s) => s.name !== skillName)
|
|
863
|
+
})),
|
|
864
|
+
Effect4.flatMap((metadata) => writeMetadata(skillsDir, metadata))
|
|
865
|
+
);
|
|
866
|
+
var readMetadataAsync = (skillsDir) => Effect4.runPromise(readMetadata(skillsDir));
|
|
867
|
+
var updateMetadataAsync = (skillsDir, newSkills) => Effect4.runPromise(updateMetadata(skillsDir, newSkills));
|
|
868
|
+
var removeFromMetadataAsync = (skillsDir, skillName) => Effect4.runPromise(removeFromMetadata(skillsDir, skillName));
|
|
869
|
+
|
|
870
|
+
// src/lib/skill-writer.ts
|
|
871
|
+
init_esm_shims();
|
|
872
|
+
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
873
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
874
|
+
import { Data as Data4, Effect as Effect5, pipe as pipe5 } from "effect";
|
|
875
|
+
var WriteError = class extends Data4.TaggedError("WriteError") {
|
|
876
|
+
};
|
|
877
|
+
var COMPATIBILITY_REGEX = /^compatibility:\s*.+$/m;
|
|
878
|
+
var rewriteCompatibility = (content, agentId) => {
|
|
879
|
+
return content.replace(COMPATIBILITY_REGEX, `compatibility: ${agentId}`);
|
|
880
|
+
};
|
|
881
|
+
var decodeFileContent = (file, agentId) => {
|
|
882
|
+
let content;
|
|
883
|
+
if (file.encoding === "base64") {
|
|
884
|
+
content = Buffer.from(file.content, "base64").toString("utf-8");
|
|
885
|
+
} else {
|
|
886
|
+
content = file.content;
|
|
887
|
+
}
|
|
888
|
+
if (agentId && file.path === "SKILL.md") {
|
|
889
|
+
content = rewriteCompatibility(content, agentId);
|
|
890
|
+
}
|
|
891
|
+
return content;
|
|
892
|
+
};
|
|
893
|
+
var decodeFileContentBinary = (file) => {
|
|
894
|
+
if (file.encoding === "base64") {
|
|
895
|
+
return Buffer.from(file.content, "base64");
|
|
896
|
+
}
|
|
897
|
+
return Buffer.from(file.content, "utf-8");
|
|
898
|
+
};
|
|
899
|
+
var isBinaryFile = (path2) => {
|
|
900
|
+
const binaryExtensions = [
|
|
901
|
+
".png",
|
|
902
|
+
".jpg",
|
|
903
|
+
".jpeg",
|
|
904
|
+
".gif",
|
|
905
|
+
".webp",
|
|
906
|
+
".ico",
|
|
907
|
+
".bmp"
|
|
908
|
+
];
|
|
909
|
+
return binaryExtensions.some((ext) => path2.toLowerCase().endsWith(ext));
|
|
910
|
+
};
|
|
911
|
+
var writeSkillFile = (basePath, file, agentId) => pipe5(
|
|
912
|
+
Effect5.tryPromise({
|
|
913
|
+
try: async () => {
|
|
914
|
+
const fullPath = join4(basePath, file.path);
|
|
915
|
+
const dir = dirname2(fullPath);
|
|
916
|
+
await mkdir2(dir, { recursive: true });
|
|
917
|
+
if (isBinaryFile(file.path)) {
|
|
918
|
+
await writeFile3(fullPath, decodeFileContentBinary(file));
|
|
919
|
+
} else {
|
|
920
|
+
await writeFile3(fullPath, decodeFileContent(file, agentId), "utf-8");
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
catch: (e) => new WriteError({ path: join4(basePath, file.path), cause: e })
|
|
924
|
+
})
|
|
925
|
+
);
|
|
926
|
+
var writeSkill = (basePath, skill, agentId) => {
|
|
927
|
+
const skillDir = join4(basePath, skill.name);
|
|
928
|
+
return pipe5(
|
|
929
|
+
Effect5.forEach(
|
|
930
|
+
skill.files,
|
|
931
|
+
(file) => writeSkillFile(skillDir, file, agentId),
|
|
932
|
+
{
|
|
933
|
+
concurrency: "unbounded"
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
Effect5.map(() => void 0)
|
|
937
|
+
);
|
|
938
|
+
};
|
|
939
|
+
var writeSkills = (basePath, skills, agentId) => pipe5(
|
|
940
|
+
Effect5.forEach(
|
|
941
|
+
skills,
|
|
942
|
+
(skill) => pipe5(
|
|
943
|
+
writeSkill(basePath, skill, agentId),
|
|
944
|
+
Effect5.map(() => ({ success: true, skill: skill.name })),
|
|
945
|
+
Effect5.catchAll(
|
|
946
|
+
(error) => Effect5.succeed({
|
|
947
|
+
success: false,
|
|
948
|
+
skill: skill.name,
|
|
949
|
+
error: error.cause instanceof Error ? error.cause.message : String(error.cause)
|
|
950
|
+
})
|
|
951
|
+
)
|
|
952
|
+
),
|
|
953
|
+
{ concurrency: "unbounded" }
|
|
954
|
+
),
|
|
955
|
+
Effect5.map((results) => ({
|
|
956
|
+
written: results.filter((r) => r.success).map((r) => r.skill),
|
|
957
|
+
errors: results.filter(
|
|
958
|
+
(r) => !r.success
|
|
959
|
+
).map((r) => ({ skill: r.skill, error: r.error }))
|
|
960
|
+
}))
|
|
961
|
+
);
|
|
962
|
+
var writeSkillsAsync = (basePath, skills, agentId) => Effect5.runPromise(writeSkills(basePath, skills, agentId));
|
|
963
|
+
|
|
964
|
+
// src/lib/tui.ts
|
|
965
|
+
init_esm_shims();
|
|
966
|
+
import process5 from "process";
|
|
967
|
+
import {
|
|
968
|
+
cancel as clackCancel,
|
|
969
|
+
confirm as clackConfirm,
|
|
970
|
+
group as clackGroup,
|
|
971
|
+
groupMultiselect as clackGroupMultiselect,
|
|
972
|
+
intro as clackIntro,
|
|
973
|
+
isCancel as clackIsCancel,
|
|
974
|
+
log as clackLog,
|
|
975
|
+
multiselect as clackMultiselect,
|
|
976
|
+
note as clackNote,
|
|
977
|
+
outro as clackOutro,
|
|
978
|
+
password as clackPassword,
|
|
979
|
+
select as clackSelect,
|
|
980
|
+
selectKey as clackSelectKey,
|
|
981
|
+
spinner as clackSpinner,
|
|
982
|
+
text as clackText
|
|
983
|
+
} from "@clack/prompts";
|
|
984
|
+
var isTTY = Boolean(process5.stdout.isTTY);
|
|
985
|
+
function spinner2() {
|
|
986
|
+
if (isTTY) {
|
|
987
|
+
return clackSpinner();
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
start: (message) => {
|
|
991
|
+
if (message) {
|
|
992
|
+
process5.stdout.write(`${message}
|
|
993
|
+
`);
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
stop: (message) => {
|
|
997
|
+
if (message) {
|
|
998
|
+
process5.stdout.write(`${message}
|
|
999
|
+
`);
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
message: (message) => {
|
|
1003
|
+
if (message) {
|
|
1004
|
+
process5.stdout.write(`${message}
|
|
1005
|
+
`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function intro2(message) {
|
|
1011
|
+
if (isTTY) {
|
|
1012
|
+
clackIntro(message);
|
|
1013
|
+
} else {
|
|
1014
|
+
process5.stdout.write(`
|
|
1015
|
+
${message}
|
|
1016
|
+
`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
function outro2(message) {
|
|
1020
|
+
if (isTTY) {
|
|
1021
|
+
clackOutro(message);
|
|
1022
|
+
} else {
|
|
1023
|
+
process5.stdout.write(`${message}
|
|
1024
|
+
|
|
1025
|
+
`);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
var cancel2 = clackCancel;
|
|
1029
|
+
var confirm2 = clackConfirm;
|
|
1030
|
+
var isCancel2 = clackIsCancel;
|
|
1031
|
+
var log2 = clackLog;
|
|
1032
|
+
var multiselect = clackMultiselect;
|
|
1033
|
+
|
|
1034
|
+
// src/commands/install.ts
|
|
1035
|
+
function resolveInstallConfig(options, config) {
|
|
1036
|
+
const orgProjectsFromFlag = options.orgProjects ? options.orgProjects.split(",").map((s) => s.trim()) : void 0;
|
|
1037
|
+
const personalProjectsFromFlag = options.personalProjects ? options.personalProjects.split(",").map((s) => s.trim()) : void 0;
|
|
1038
|
+
return {
|
|
1039
|
+
profile: options.profile ?? config.profile,
|
|
1040
|
+
serverUrl: options.server ?? config.serverUrl,
|
|
1041
|
+
includeUserGlobalRules: options.includeUserGlobals ?? config.includeUserGlobalRules,
|
|
1042
|
+
includeOrgGlobalRules: options.includeOrgGlobals ?? config.includeOrgGlobalRules,
|
|
1043
|
+
orgProjects: orgProjectsFromFlag ?? config.orgProjects,
|
|
1044
|
+
personalProjects: personalProjectsFromFlag ?? config.personalProjects
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function validateInstallOptions(resolved) {
|
|
1048
|
+
const { profile, orgProjects, personalProjects } = resolved;
|
|
1049
|
+
const hasOrgProjects = orgProjects && orgProjects.length > 0;
|
|
1050
|
+
const hasPersonalProjects = personalProjects && personalProjects.length > 0;
|
|
1051
|
+
if (!(profile || hasOrgProjects || hasPersonalProjects)) {
|
|
1052
|
+
log2.error("No profile or project(s) specified.");
|
|
1053
|
+
log2.info("Either:");
|
|
1054
|
+
log2.info(
|
|
1055
|
+
" - Add 'profile', 'orgProjects', or 'personalProjects' to braid.json/braid.user.json"
|
|
1056
|
+
);
|
|
1057
|
+
log2.info(" - Use --profile, --org-projects, or --personal-projects flags");
|
|
1058
|
+
log2.info("");
|
|
1059
|
+
log2.info("Examples:");
|
|
1060
|
+
log2.info(" braid-skills install --profile coding-standards");
|
|
1061
|
+
log2.info(" braid-skills install --org-projects proj123,proj456");
|
|
1062
|
+
log2.info(" braid-skills install --personal-projects myproj1");
|
|
1063
|
+
process.exit(1);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
function buildSourceDescription(resolved) {
|
|
1067
|
+
const { profile, orgProjects, personalProjects } = resolved;
|
|
1068
|
+
if (profile) {
|
|
1069
|
+
return `profile '${profile}'`;
|
|
1070
|
+
}
|
|
1071
|
+
const hasOrgProjects = orgProjects && orgProjects.length > 0;
|
|
1072
|
+
const hasPersonalProjects = personalProjects && personalProjects.length > 0;
|
|
1073
|
+
if (hasOrgProjects || hasPersonalProjects) {
|
|
1074
|
+
const totalProjects = (orgProjects?.length ?? 0) + (personalProjects?.length ?? 0);
|
|
1075
|
+
return totalProjects === 1 ? `project ${orgProjects?.[0] ?? personalProjects?.[0]}` : `${totalProjects} projects`;
|
|
1076
|
+
}
|
|
1077
|
+
return "unknown source";
|
|
1078
|
+
}
|
|
1079
|
+
function buildFetchOptions(resolved) {
|
|
1080
|
+
const fetchOptions = {
|
|
1081
|
+
serverUrl: resolved.serverUrl,
|
|
1082
|
+
includeUserGlobalRules: resolved.includeUserGlobalRules,
|
|
1083
|
+
includeOrgGlobalRules: resolved.includeOrgGlobalRules
|
|
1084
|
+
};
|
|
1085
|
+
if (resolved.profile) {
|
|
1086
|
+
fetchOptions.profile = resolved.profile;
|
|
1087
|
+
}
|
|
1088
|
+
if (resolved.orgProjects && resolved.orgProjects.length > 0) {
|
|
1089
|
+
fetchOptions.orgProjects = resolved.orgProjects;
|
|
1090
|
+
}
|
|
1091
|
+
if (resolved.personalProjects && resolved.personalProjects.length > 0) {
|
|
1092
|
+
fetchOptions.personalProjects = resolved.personalProjects;
|
|
1093
|
+
}
|
|
1094
|
+
return fetchOptions;
|
|
1095
|
+
}
|
|
1096
|
+
function displaySkillsAndExit(skills) {
|
|
1097
|
+
log2.info("\nSkills:");
|
|
1098
|
+
for (const skill of skills) {
|
|
1099
|
+
const fileCount = skill.files.length;
|
|
1100
|
+
log2.info(
|
|
1101
|
+
` ${skill.name} (${fileCount} file${fileCount !== 1 ? "s" : ""})`
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
process.exit(0);
|
|
1105
|
+
}
|
|
1106
|
+
async function resolveAgents(options, installSpinner) {
|
|
1107
|
+
if (options.agents) {
|
|
1108
|
+
const agentIds = options.agents.split(",").map((s) => s.trim());
|
|
1109
|
+
const selectedAgents = agentIds.map((id) => {
|
|
1110
|
+
const agentConfig = getAgentById(id);
|
|
1111
|
+
if (!agentConfig) {
|
|
1112
|
+
log2.warn(`Unknown agent: ${id}`);
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
...agentConfig,
|
|
1117
|
+
hasProjectConfig: true,
|
|
1118
|
+
hasGlobalConfig: true
|
|
1119
|
+
};
|
|
1120
|
+
}).filter((a) => a !== null);
|
|
1121
|
+
if (selectedAgents.length === 0) {
|
|
1122
|
+
log2.error("No valid agents specified.");
|
|
1123
|
+
process.exit(1);
|
|
1124
|
+
}
|
|
1125
|
+
return selectedAgents;
|
|
1126
|
+
}
|
|
1127
|
+
installSpinner.start("Detecting installed agents...");
|
|
1128
|
+
const detectedAgents = await detectAgentsAsync();
|
|
1129
|
+
installSpinner.stop(`Detected ${detectedAgents.length} agent(s)`);
|
|
1130
|
+
if (detectedAgents.length === 0) {
|
|
1131
|
+
log2.warn("No AI coding agents detected.");
|
|
1132
|
+
log2.info(
|
|
1133
|
+
"Supported agents: claude-code, opencode, cursor, windsurf, cline, and more."
|
|
1134
|
+
);
|
|
1135
|
+
log2.info(
|
|
1136
|
+
"Use --agents to specify agents manually: braid-skills install -p my-profile -a claude-code"
|
|
1137
|
+
);
|
|
1138
|
+
process.exit(1);
|
|
1139
|
+
}
|
|
1140
|
+
return detectedAgents;
|
|
1141
|
+
}
|
|
1142
|
+
async function selectAgents(detectedAgents, options) {
|
|
1143
|
+
if (options.yes) {
|
|
1144
|
+
return detectedAgents;
|
|
1145
|
+
}
|
|
1146
|
+
const agentChoices = detectedAgents.map((agent) => ({
|
|
1147
|
+
value: agent,
|
|
1148
|
+
label: agent.name,
|
|
1149
|
+
hint: options.global ? agent.globalPath : agent.projectPath
|
|
1150
|
+
}));
|
|
1151
|
+
const selected = await multiselect({
|
|
1152
|
+
message: "Select agents to install to:",
|
|
1153
|
+
options: agentChoices,
|
|
1154
|
+
initialValues: detectedAgents,
|
|
1155
|
+
required: true
|
|
1156
|
+
});
|
|
1157
|
+
if (isCancel2(selected)) {
|
|
1158
|
+
cancel2("Install cancelled.");
|
|
1159
|
+
process.exit(0);
|
|
1160
|
+
}
|
|
1161
|
+
return selected;
|
|
1162
|
+
}
|
|
1163
|
+
async function installToAgent(agent, response, serverUrl, options, installSpinner) {
|
|
1164
|
+
const installPath = resolveInstallPath(agent, {
|
|
1165
|
+
global: options.global === true
|
|
1166
|
+
});
|
|
1167
|
+
installSpinner.start(`Installing to ${agent.name}...`);
|
|
1168
|
+
const result = await writeSkillsAsync(installPath, response.skills, agent.id);
|
|
1169
|
+
if (result.errors.length > 0) {
|
|
1170
|
+
installSpinner.stop(
|
|
1171
|
+
`${agent.name}: ${result.written.length} installed, ${result.errors.length} failed`
|
|
1172
|
+
);
|
|
1173
|
+
for (const err of result.errors) {
|
|
1174
|
+
log2.warn(` Failed: ${err.skill} - ${err.error}`);
|
|
1175
|
+
}
|
|
1176
|
+
} else {
|
|
1177
|
+
installSpinner.stop(
|
|
1178
|
+
`${agent.name}: ${result.written.length} skills installed`
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
await updateMetadataAsync(
|
|
1182
|
+
installPath,
|
|
1183
|
+
response.skills.map((s) => ({
|
|
1184
|
+
name: s.name,
|
|
1185
|
+
source: response.source,
|
|
1186
|
+
version: response.version,
|
|
1187
|
+
serverUrl
|
|
1188
|
+
}))
|
|
1189
|
+
);
|
|
1190
|
+
return { written: result.written.length, errors: result.errors.length };
|
|
1191
|
+
}
|
|
1192
|
+
async function installCommand(options) {
|
|
1193
|
+
const config = await loadMergedConfigAsync();
|
|
1194
|
+
const resolved = resolveInstallConfig(options, config);
|
|
1195
|
+
validateInstallOptions(resolved);
|
|
1196
|
+
const sourceDesc = buildSourceDescription(resolved);
|
|
1197
|
+
intro2(`Installing skills from ${sourceDesc}`);
|
|
1198
|
+
const installSpinner = spinner2();
|
|
1199
|
+
installSpinner.start("Fetching skills from Braid...");
|
|
1200
|
+
try {
|
|
1201
|
+
const fetchOptions = buildFetchOptions(resolved);
|
|
1202
|
+
const response = await fetchSkillsAsync(fetchOptions);
|
|
1203
|
+
installSpinner.stop(`Found ${response.skills.length} skills`);
|
|
1204
|
+
if (response.skills.length === 0) {
|
|
1205
|
+
log2.warn(
|
|
1206
|
+
"No skills found. Check that your profile/project has enabled rules."
|
|
1207
|
+
);
|
|
1208
|
+
process.exit(0);
|
|
1209
|
+
}
|
|
1210
|
+
if (options.list) {
|
|
1211
|
+
displaySkillsAndExit(response.skills);
|
|
1212
|
+
}
|
|
1213
|
+
let selectedAgents = await resolveAgents(options, installSpinner);
|
|
1214
|
+
if (!options.agents) {
|
|
1215
|
+
selectedAgents = await selectAgents(selectedAgents, options);
|
|
1216
|
+
}
|
|
1217
|
+
let totalWritten = 0;
|
|
1218
|
+
let totalErrors = 0;
|
|
1219
|
+
for (const agent of selectedAgents) {
|
|
1220
|
+
const result = await installToAgent(
|
|
1221
|
+
agent,
|
|
1222
|
+
response,
|
|
1223
|
+
resolved.serverUrl,
|
|
1224
|
+
options,
|
|
1225
|
+
installSpinner
|
|
1226
|
+
);
|
|
1227
|
+
totalWritten += result.written;
|
|
1228
|
+
totalErrors += result.errors;
|
|
1229
|
+
}
|
|
1230
|
+
if (totalErrors > 0) {
|
|
1231
|
+
outro2(`Installed ${totalWritten} skills with ${totalErrors} errors.`);
|
|
1232
|
+
} else {
|
|
1233
|
+
outro2(
|
|
1234
|
+
`Successfully installed ${totalWritten} skills to ${selectedAgents.length} agent(s).`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
log2.info("Run 'braid-skills list' to see installed skills.");
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
installSpinner.stop("Install failed");
|
|
1240
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1241
|
+
log2.error(message);
|
|
1242
|
+
process.exit(1);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/commands/list.ts
|
|
1247
|
+
init_esm_shims();
|
|
1248
|
+
import { log as log3, spinner as spinner3 } from "@clack/prompts";
|
|
1249
|
+
function formatRelativeTime(isoDate) {
|
|
1250
|
+
const date = new Date(isoDate);
|
|
1251
|
+
const now = /* @__PURE__ */ new Date();
|
|
1252
|
+
const diffMs = now.getTime() - date.getTime();
|
|
1253
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
1254
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
1255
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
1256
|
+
if (diffMins < 1) {
|
|
1257
|
+
return "just now";
|
|
1258
|
+
}
|
|
1259
|
+
if (diffMins < 60) {
|
|
1260
|
+
return `${diffMins} minute${diffMins !== 1 ? "s" : ""} ago`;
|
|
1261
|
+
}
|
|
1262
|
+
if (diffHours < 24) {
|
|
1263
|
+
return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
|
|
1264
|
+
}
|
|
1265
|
+
if (diffDays < 30) {
|
|
1266
|
+
return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`;
|
|
1267
|
+
}
|
|
1268
|
+
return date.toLocaleDateString();
|
|
1269
|
+
}
|
|
1270
|
+
function displayAgentSkills(agent, installPath, braidSkills) {
|
|
1271
|
+
const nameWidth = 25;
|
|
1272
|
+
const sourceWidth = 20;
|
|
1273
|
+
const installedWidth = 15;
|
|
1274
|
+
log3.info("");
|
|
1275
|
+
log3.info(`Agent: ${agent.name} (${installPath})`);
|
|
1276
|
+
log3.info("\u2500".repeat(60));
|
|
1277
|
+
const header = [
|
|
1278
|
+
"Skill".padEnd(nameWidth),
|
|
1279
|
+
"Source".padEnd(sourceWidth),
|
|
1280
|
+
"Installed".padEnd(installedWidth)
|
|
1281
|
+
].join(" ");
|
|
1282
|
+
log3.info(header);
|
|
1283
|
+
log3.info("\u2500".repeat(60));
|
|
1284
|
+
for (const skill of braidSkills) {
|
|
1285
|
+
const sourceName = skill.source.name;
|
|
1286
|
+
const row = [
|
|
1287
|
+
skill.name.slice(0, nameWidth).padEnd(nameWidth),
|
|
1288
|
+
sourceName.slice(0, sourceWidth).padEnd(sourceWidth),
|
|
1289
|
+
formatRelativeTime(skill.installedAt).padEnd(installedWidth)
|
|
1290
|
+
].join(" ");
|
|
1291
|
+
log3.info(row);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
async function listCommand(options) {
|
|
1295
|
+
const listSpinner = spinner3();
|
|
1296
|
+
listSpinner.start("Scanning for installed skills...");
|
|
1297
|
+
try {
|
|
1298
|
+
const detectedAgents = await detectAgentsAsync();
|
|
1299
|
+
if (detectedAgents.length === 0) {
|
|
1300
|
+
listSpinner.stop("No agents detected");
|
|
1301
|
+
log3.warn("No AI coding agents detected.");
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
listSpinner.stop(`Found ${detectedAgents.length} agent(s)`);
|
|
1305
|
+
let totalSkills = 0;
|
|
1306
|
+
for (const agent of detectedAgents) {
|
|
1307
|
+
const installPath = resolveInstallPath(agent, {
|
|
1308
|
+
global: options.global === true
|
|
1309
|
+
});
|
|
1310
|
+
const exists = await directoryExistsAsync(installPath);
|
|
1311
|
+
if (!exists) {
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const metadata = await readMetadataAsync(installPath);
|
|
1315
|
+
const braidSkills = metadata.skills;
|
|
1316
|
+
if (braidSkills.length === 0) {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
totalSkills += braidSkills.length;
|
|
1320
|
+
displayAgentSkills(agent, installPath, braidSkills);
|
|
1321
|
+
}
|
|
1322
|
+
if (totalSkills === 0) {
|
|
1323
|
+
log3.warn("\nNo skills installed via braid-skills.");
|
|
1324
|
+
log3.info(
|
|
1325
|
+
"Run 'braid-skills install --profile <name>' to install skills."
|
|
1326
|
+
);
|
|
1327
|
+
} else {
|
|
1328
|
+
log3.info("");
|
|
1329
|
+
log3.info(`Total: ${totalSkills} skill(s) installed`);
|
|
1330
|
+
}
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
listSpinner.stop("List failed");
|
|
1333
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1334
|
+
log3.error(message);
|
|
1335
|
+
process.exit(1);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// src/commands/remove.ts
|
|
1340
|
+
init_esm_shims();
|
|
1341
|
+
import { rm } from "fs/promises";
|
|
1342
|
+
import { join as join5 } from "path";
|
|
1343
|
+
import process6 from "process";
|
|
1344
|
+
async function collectInstalledSkills(detectedAgents, options) {
|
|
1345
|
+
const skillsToRemove = [];
|
|
1346
|
+
for (const agent of detectedAgents) {
|
|
1347
|
+
const installPath = resolveInstallPath(agent, {
|
|
1348
|
+
global: options.global === true
|
|
1349
|
+
});
|
|
1350
|
+
const exists = await directoryExistsAsync(installPath);
|
|
1351
|
+
if (!exists) {
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
const metadata = await readMetadataAsync(installPath);
|
|
1355
|
+
for (const skill of metadata.skills) {
|
|
1356
|
+
skillsToRemove.push({
|
|
1357
|
+
name: skill.name,
|
|
1358
|
+
agentName: agent.name,
|
|
1359
|
+
installPath,
|
|
1360
|
+
skillPath: join5(installPath, skill.name)
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return skillsToRemove;
|
|
1365
|
+
}
|
|
1366
|
+
async function selectSkillsToRemove(skillsToRemove, options) {
|
|
1367
|
+
if (options.skill) {
|
|
1368
|
+
const selected = skillsToRemove.filter((s) => s.name === options.skill);
|
|
1369
|
+
if (selected.length === 0) {
|
|
1370
|
+
log2.error(`Skill '${options.skill}' not found.`);
|
|
1371
|
+
log2.info("Run 'braid-skills list' to see installed skills.");
|
|
1372
|
+
process6.exit(1);
|
|
1373
|
+
}
|
|
1374
|
+
return selected;
|
|
1375
|
+
}
|
|
1376
|
+
if (options.all || options.yes) {
|
|
1377
|
+
return skillsToRemove;
|
|
1378
|
+
}
|
|
1379
|
+
const choices = skillsToRemove.map((skill) => ({
|
|
1380
|
+
value: skill,
|
|
1381
|
+
label: skill.name,
|
|
1382
|
+
hint: skill.agentName
|
|
1383
|
+
}));
|
|
1384
|
+
const result = await multiselect({
|
|
1385
|
+
message: "Select skills to remove:",
|
|
1386
|
+
options: choices,
|
|
1387
|
+
required: true
|
|
1388
|
+
});
|
|
1389
|
+
if (isCancel2(result)) {
|
|
1390
|
+
cancel2("Remove cancelled.");
|
|
1391
|
+
process6.exit(0);
|
|
1392
|
+
}
|
|
1393
|
+
return result;
|
|
1394
|
+
}
|
|
1395
|
+
async function confirmRemoval(selectedCount, options) {
|
|
1396
|
+
if (options.yes || options.skill || options.all) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const confirmed = await confirm2({
|
|
1400
|
+
message: `Remove ${selectedCount} skill(s)?`,
|
|
1401
|
+
initialValue: false
|
|
1402
|
+
});
|
|
1403
|
+
if (isCancel2(confirmed) || !confirmed) {
|
|
1404
|
+
cancel2("Remove cancelled.");
|
|
1405
|
+
process6.exit(0);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
async function removeSkill(skill, removeSpinner) {
|
|
1409
|
+
removeSpinner.start(`Removing ${skill.name} from ${skill.agentName}...`);
|
|
1410
|
+
try {
|
|
1411
|
+
await rm(skill.skillPath, { recursive: true, force: true });
|
|
1412
|
+
await removeFromMetadataAsync(skill.installPath, skill.name);
|
|
1413
|
+
removeSpinner.stop(`Removed ${skill.name} from ${skill.agentName}`);
|
|
1414
|
+
return true;
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
removeSpinner.stop(`Failed to remove ${skill.name}`);
|
|
1417
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1418
|
+
log2.warn(` ${message}`);
|
|
1419
|
+
return false;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
async function removeCommand(options) {
|
|
1423
|
+
const removeSpinner = spinner2();
|
|
1424
|
+
removeSpinner.start("Scanning for installed skills...");
|
|
1425
|
+
try {
|
|
1426
|
+
const detectedAgents = await detectAgentsAsync();
|
|
1427
|
+
if (detectedAgents.length === 0) {
|
|
1428
|
+
removeSpinner.stop("No agents detected");
|
|
1429
|
+
log2.warn("No AI coding agents detected.");
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
const skillsToRemove = await collectInstalledSkills(
|
|
1433
|
+
detectedAgents,
|
|
1434
|
+
options
|
|
1435
|
+
);
|
|
1436
|
+
removeSpinner.stop(`Found ${skillsToRemove.length} installed skill(s)`);
|
|
1437
|
+
if (skillsToRemove.length === 0) {
|
|
1438
|
+
log2.warn("No skills installed via braid-skills.");
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
const selected = await selectSkillsToRemove(skillsToRemove, options);
|
|
1442
|
+
await confirmRemoval(selected.length, options);
|
|
1443
|
+
let removed = 0;
|
|
1444
|
+
let errors = 0;
|
|
1445
|
+
for (const skill of selected) {
|
|
1446
|
+
const success = await removeSkill(skill, removeSpinner);
|
|
1447
|
+
if (success) {
|
|
1448
|
+
removed++;
|
|
1449
|
+
} else {
|
|
1450
|
+
errors++;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (errors > 0) {
|
|
1454
|
+
outro2(`Removed ${removed} skill(s) with ${errors} error(s).`);
|
|
1455
|
+
} else {
|
|
1456
|
+
outro2(`Successfully removed ${removed} skill(s).`);
|
|
1457
|
+
}
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
removeSpinner.stop("Remove failed");
|
|
1460
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1461
|
+
log2.error(message);
|
|
1462
|
+
process6.exit(1);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
// src/commands/update.ts
|
|
1467
|
+
init_esm_shims();
|
|
1468
|
+
import {
|
|
1469
|
+
cancel as cancel3,
|
|
1470
|
+
isCancel as isCancel3,
|
|
1471
|
+
log as log4,
|
|
1472
|
+
multiselect as multiselect2,
|
|
1473
|
+
outro as outro3,
|
|
1474
|
+
spinner as spinner4
|
|
1475
|
+
} from "@clack/prompts";
|
|
1476
|
+
import { Data as Data5, Effect as Effect6, pipe as pipe6 } from "effect";
|
|
1477
|
+
var UpdateError = class extends Data5.TaggedError("UpdateError") {
|
|
1478
|
+
};
|
|
1479
|
+
var UserCancelledError = class extends Data5.TaggedError("UserCancelledError") {
|
|
1480
|
+
};
|
|
1481
|
+
var collectSourcesFromAgent = (agent, options, sourcesToUpdate) => Effect6.tryPromise({
|
|
1482
|
+
try: async () => {
|
|
1483
|
+
const installPath = resolveInstallPath(agent, {
|
|
1484
|
+
global: options.global === true
|
|
1485
|
+
});
|
|
1486
|
+
const exists = await directoryExistsAsync(installPath);
|
|
1487
|
+
if (!exists) {
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const metadata = await readMetadataAsync(installPath);
|
|
1491
|
+
for (const skill of metadata.skills) {
|
|
1492
|
+
const key = skill.source.type === "profile" ? `profile:${skill.source.name}` : `projects:${skill.source.name}`;
|
|
1493
|
+
if (!sourcesToUpdate.has(key)) {
|
|
1494
|
+
sourcesToUpdate.set(key, {
|
|
1495
|
+
type: skill.source.type,
|
|
1496
|
+
name: skill.source.name,
|
|
1497
|
+
orgProjects: skill.source.orgProjects,
|
|
1498
|
+
personalProjects: skill.source.personalProjects,
|
|
1499
|
+
serverUrl: skill.serverUrl,
|
|
1500
|
+
agents: []
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
const source = sourcesToUpdate.get(key);
|
|
1504
|
+
if (source && !source.agents.some((a) => a.installPath === installPath)) {
|
|
1505
|
+
source.agents.push({
|
|
1506
|
+
agentId: agent.id,
|
|
1507
|
+
agentName: agent.name,
|
|
1508
|
+
installPath
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
},
|
|
1513
|
+
catch: () => new UpdateError({ message: "Failed to collect sources" })
|
|
1514
|
+
});
|
|
1515
|
+
var collectSources = (detectedAgents, options) => pipe6(
|
|
1516
|
+
Effect6.succeed(/* @__PURE__ */ new Map()),
|
|
1517
|
+
Effect6.tap(
|
|
1518
|
+
(sourcesToUpdate) => Effect6.forEach(
|
|
1519
|
+
detectedAgents,
|
|
1520
|
+
(agent) => collectSourcesFromAgent(agent, options, sourcesToUpdate),
|
|
1521
|
+
{ concurrency: 1 }
|
|
1522
|
+
)
|
|
1523
|
+
)
|
|
1524
|
+
);
|
|
1525
|
+
var selectSources = (sourcesToUpdate, options) => {
|
|
1526
|
+
if (options.yes) {
|
|
1527
|
+
return Effect6.succeed(sourcesToUpdate);
|
|
1528
|
+
}
|
|
1529
|
+
return Effect6.tryPromise({
|
|
1530
|
+
try: async () => {
|
|
1531
|
+
const sources = Array.from(sourcesToUpdate.entries()).map(
|
|
1532
|
+
([key, source]) => ({
|
|
1533
|
+
value: key,
|
|
1534
|
+
label: source.name,
|
|
1535
|
+
hint: `${source.agents.length} agent(s)`
|
|
1536
|
+
})
|
|
1537
|
+
);
|
|
1538
|
+
const selected = await multiselect2({
|
|
1539
|
+
message: "Select sources to update:",
|
|
1540
|
+
options: sources,
|
|
1541
|
+
initialValues: sources.map((s) => s.value),
|
|
1542
|
+
required: true
|
|
1543
|
+
});
|
|
1544
|
+
if (isCancel3(selected)) {
|
|
1545
|
+
throw new Error("cancelled");
|
|
1546
|
+
}
|
|
1547
|
+
for (const key of sourcesToUpdate.keys()) {
|
|
1548
|
+
if (!selected.includes(key)) {
|
|
1549
|
+
sourcesToUpdate.delete(key);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return sourcesToUpdate;
|
|
1553
|
+
},
|
|
1554
|
+
catch: () => new UserCancelledError({ message: "Update cancelled." })
|
|
1555
|
+
});
|
|
1556
|
+
};
|
|
1557
|
+
var getSourceDesc = (source) => source.name;
|
|
1558
|
+
var buildFetchOptionsForSource = (source, options) => {
|
|
1559
|
+
const serverUrl = options.server ?? source.serverUrl;
|
|
1560
|
+
const fetchOptions = { serverUrl };
|
|
1561
|
+
if (source.type === "profile") {
|
|
1562
|
+
fetchOptions.profile = source.name;
|
|
1563
|
+
} else {
|
|
1564
|
+
const orgProjects = source.orgProjects;
|
|
1565
|
+
const personalProjects = source.personalProjects;
|
|
1566
|
+
const hasOrgProjects = orgProjects !== void 0 && orgProjects.length > 0;
|
|
1567
|
+
const hasPersonalProjects = personalProjects !== void 0 && personalProjects.length > 0;
|
|
1568
|
+
if (!(hasOrgProjects || hasPersonalProjects)) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
if (hasOrgProjects) {
|
|
1572
|
+
fetchOptions.orgProjects = orgProjects;
|
|
1573
|
+
}
|
|
1574
|
+
if (hasPersonalProjects) {
|
|
1575
|
+
fetchOptions.personalProjects = personalProjects;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return fetchOptions;
|
|
1579
|
+
};
|
|
1580
|
+
var updateAgentSkills = (agentId, agentName, installPath, response, serverUrl, updateSpinner) => Effect6.tryPromise({
|
|
1581
|
+
try: async () => {
|
|
1582
|
+
updateSpinner.start(`Updating ${agentName}...`);
|
|
1583
|
+
const result = await writeSkillsAsync(
|
|
1584
|
+
installPath,
|
|
1585
|
+
response.skills,
|
|
1586
|
+
agentId
|
|
1587
|
+
);
|
|
1588
|
+
if (result.errors.length > 0) {
|
|
1589
|
+
updateSpinner.stop(
|
|
1590
|
+
`${agentName}: ${result.written.length} updated, ${result.errors.length} failed`
|
|
1591
|
+
);
|
|
1592
|
+
} else {
|
|
1593
|
+
updateSpinner.stop(
|
|
1594
|
+
`${agentName}: ${result.written.length} skills updated`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
await updateMetadataAsync(
|
|
1598
|
+
installPath,
|
|
1599
|
+
response.skills.map((s) => ({
|
|
1600
|
+
name: s.name,
|
|
1601
|
+
source: response.source,
|
|
1602
|
+
version: response.version,
|
|
1603
|
+
serverUrl
|
|
1604
|
+
}))
|
|
1605
|
+
);
|
|
1606
|
+
return { updated: result.written.length, errors: result.errors.length };
|
|
1607
|
+
},
|
|
1608
|
+
catch: (e) => new UpdateError({
|
|
1609
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1610
|
+
source: agentName
|
|
1611
|
+
})
|
|
1612
|
+
});
|
|
1613
|
+
var updateSource = (source, options, updateSpinner) => {
|
|
1614
|
+
const sourceDesc = getSourceDesc(source);
|
|
1615
|
+
const serverUrl = options.server ?? source.serverUrl;
|
|
1616
|
+
const fetchOptions = buildFetchOptionsForSource(source, options);
|
|
1617
|
+
if (fetchOptions === null) {
|
|
1618
|
+
return Effect6.fail(
|
|
1619
|
+
new UpdateError({
|
|
1620
|
+
message: "Skills installed with legacy metadata format. Please reinstall using 'braid-skills install --profile <name>' or 'braid-skills install --projects <names>'.",
|
|
1621
|
+
source: sourceDesc
|
|
1622
|
+
})
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
return pipe6(
|
|
1626
|
+
Effect6.tryPromise({
|
|
1627
|
+
try: async () => {
|
|
1628
|
+
updateSpinner.start(`Fetching latest skills from ${sourceDesc}...`);
|
|
1629
|
+
const response = await fetchSkillsAsync(fetchOptions);
|
|
1630
|
+
updateSpinner.stop(
|
|
1631
|
+
`Fetched ${response.skills.length} skills from ${sourceDesc}`
|
|
1632
|
+
);
|
|
1633
|
+
return response;
|
|
1634
|
+
},
|
|
1635
|
+
catch: (e) => new UpdateError({
|
|
1636
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1637
|
+
source: sourceDesc
|
|
1638
|
+
})
|
|
1639
|
+
}),
|
|
1640
|
+
Effect6.flatMap(
|
|
1641
|
+
(response) => pipe6(
|
|
1642
|
+
Effect6.forEach(
|
|
1643
|
+
source.agents,
|
|
1644
|
+
({ agentId, agentName, installPath }) => updateAgentSkills(
|
|
1645
|
+
agentId,
|
|
1646
|
+
agentName,
|
|
1647
|
+
installPath,
|
|
1648
|
+
response,
|
|
1649
|
+
serverUrl,
|
|
1650
|
+
updateSpinner
|
|
1651
|
+
),
|
|
1652
|
+
{ concurrency: 1 }
|
|
1653
|
+
),
|
|
1654
|
+
Effect6.map((results) => ({
|
|
1655
|
+
updated: results.reduce((sum, r) => sum + r.updated, 0),
|
|
1656
|
+
errors: results.reduce((sum, r) => sum + r.errors, 0)
|
|
1657
|
+
}))
|
|
1658
|
+
)
|
|
1659
|
+
)
|
|
1660
|
+
);
|
|
1661
|
+
};
|
|
1662
|
+
var updateAllSources = (sources, options, updateSpinner) => pipe6(
|
|
1663
|
+
Effect6.forEach(
|
|
1664
|
+
Array.from(sources.values()),
|
|
1665
|
+
(source) => pipe6(
|
|
1666
|
+
updateSource(source, options, updateSpinner),
|
|
1667
|
+
Effect6.catchAll((error) => {
|
|
1668
|
+
updateSpinner.stop(
|
|
1669
|
+
`Failed to update from ${getSourceDesc(source)}`
|
|
1670
|
+
);
|
|
1671
|
+
log4.error(` ${error.message}`);
|
|
1672
|
+
return Effect6.succeed({ updated: 0, errors: 1 });
|
|
1673
|
+
})
|
|
1674
|
+
),
|
|
1675
|
+
{ concurrency: 1 }
|
|
1676
|
+
),
|
|
1677
|
+
Effect6.map((results) => ({
|
|
1678
|
+
totalUpdated: results.reduce((sum, r) => sum + r.updated, 0),
|
|
1679
|
+
totalErrors: results.reduce((sum, r) => sum + r.errors, 0)
|
|
1680
|
+
}))
|
|
1681
|
+
);
|
|
1682
|
+
var handleUpdateError = (error, updateSpinner) => {
|
|
1683
|
+
updateSpinner.stop("Update failed");
|
|
1684
|
+
if (error.message === "No AI coding agents detected.") {
|
|
1685
|
+
log4.warn(error.message);
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
if (error.message === "No skills installed via braid-skills.") {
|
|
1689
|
+
log4.warn(error.message);
|
|
1690
|
+
log4.info(
|
|
1691
|
+
"Run 'braid-skills install --profile <name>' to install skills first."
|
|
1692
|
+
);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
log4.error(error.message);
|
|
1696
|
+
process.exit(1);
|
|
1697
|
+
};
|
|
1698
|
+
var handleProgramExit = (result, updateSpinner) => {
|
|
1699
|
+
if (result._tag !== "Failure") {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
const cause = result.cause;
|
|
1703
|
+
if (cause._tag !== "Fail") {
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
const error = cause.error;
|
|
1707
|
+
if (error._tag === "UserCancelledError") {
|
|
1708
|
+
cancel3(error.message);
|
|
1709
|
+
process.exit(0);
|
|
1710
|
+
}
|
|
1711
|
+
if (error._tag === "UpdateError") {
|
|
1712
|
+
handleUpdateError(error, updateSpinner);
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
async function updateCommand(options) {
|
|
1716
|
+
const updateSpinner = spinner4();
|
|
1717
|
+
updateSpinner.start("Scanning for installed skills...");
|
|
1718
|
+
const program2 = pipe6(
|
|
1719
|
+
Effect6.tryPromise({
|
|
1720
|
+
try: () => detectAgentsAsync(),
|
|
1721
|
+
catch: () => new UpdateError({ message: "Failed to detect agents" })
|
|
1722
|
+
}),
|
|
1723
|
+
Effect6.filterOrFail(
|
|
1724
|
+
(agents) => agents.length > 0,
|
|
1725
|
+
() => new UpdateError({ message: "No AI coding agents detected." })
|
|
1726
|
+
),
|
|
1727
|
+
Effect6.flatMap((detectedAgents) => collectSources(detectedAgents, options)),
|
|
1728
|
+
Effect6.tap((sources) => {
|
|
1729
|
+
updateSpinner.stop(`Found ${sources.size} source(s) to update`);
|
|
1730
|
+
}),
|
|
1731
|
+
Effect6.filterOrFail(
|
|
1732
|
+
(sources) => sources.size > 0,
|
|
1733
|
+
() => new UpdateError({ message: "No skills installed via braid-skills." })
|
|
1734
|
+
),
|
|
1735
|
+
Effect6.flatMap((sources) => selectSources(sources, options)),
|
|
1736
|
+
Effect6.flatMap(
|
|
1737
|
+
(selectedSources) => updateAllSources(selectedSources, options, updateSpinner)
|
|
1738
|
+
),
|
|
1739
|
+
Effect6.tap(({ totalUpdated, totalErrors }) => {
|
|
1740
|
+
if (totalErrors > 0) {
|
|
1741
|
+
outro3(`Updated ${totalUpdated} skills with ${totalErrors} errors.`);
|
|
1742
|
+
} else {
|
|
1743
|
+
outro3(`Successfully updated ${totalUpdated} skills.`);
|
|
1744
|
+
}
|
|
1745
|
+
})
|
|
1746
|
+
);
|
|
1747
|
+
const result = await Effect6.runPromiseExit(program2);
|
|
1748
|
+
handleProgramExit(result, updateSpinner);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// src/index.ts
|
|
1752
|
+
var program = new Command();
|
|
1753
|
+
program.name("braid-skills").description(
|
|
1754
|
+
"Install Braid rules as agent skills to your local development environment"
|
|
1755
|
+
).version("0.1.0");
|
|
1756
|
+
var auth = program.command("auth").description("Configure API key for Braid authentication");
|
|
1757
|
+
auth.command("login", { isDefault: true }).description("Configure API key").option("-s, --server <url>", "Braid server URL (for review apps, local dev)").action(authCommand);
|
|
1758
|
+
auth.command("status").description("Show current authentication status").action(authStatusCommand);
|
|
1759
|
+
auth.command("logout").description("Remove stored API key").action(authLogoutCommand);
|
|
1760
|
+
program.command("install").alias("add").description("Install skills from a profile or project").option("-p, --profile <name>", "Profile name to install from").option(
|
|
1761
|
+
"--org-projects <ids>",
|
|
1762
|
+
"Comma-separated organization project IDs to install from"
|
|
1763
|
+
).option(
|
|
1764
|
+
"--personal-projects <ids>",
|
|
1765
|
+
"Comma-separated personal project IDs to install from"
|
|
1766
|
+
).option(
|
|
1767
|
+
"--include-user-globals",
|
|
1768
|
+
"Include user's personal global rules (default: true)"
|
|
1769
|
+
).option("--no-include-user-globals", "Exclude user's personal global rules").option(
|
|
1770
|
+
"--include-org-globals",
|
|
1771
|
+
"Include organization's global rules (default: true)"
|
|
1772
|
+
).option("--no-include-org-globals", "Exclude organization's global rules").option(
|
|
1773
|
+
"-a, --agents <list>",
|
|
1774
|
+
"Comma-separated list of agents (e.g., claude-code,opencode)"
|
|
1775
|
+
).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);
|
|
1776
|
+
program.command("list").alias("ls").description("List installed skills").option("-g, --global", "List skills in global directories only").action(listCommand);
|
|
1777
|
+
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);
|
|
1778
|
+
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);
|
|
1779
|
+
program.parse();
|
|
1780
|
+
//# sourceMappingURL=index.js.map
|