@braid-cloud/cli 0.1.9 → 0.1.12

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/dist/index.js CHANGED
@@ -55,28 +55,28 @@ __export(config_exports, {
55
55
  setApiKeyAsync: () => setApiKeyAsync
56
56
  });
57
57
  import { existsSync } from "fs";
58
- import { mkdir, readFile, writeFile } from "fs/promises";
59
- import { homedir } from "os";
60
- import { dirname, join, parse } from "path";
61
- import process2 from "process";
62
- import { Data, Effect, pipe } from "effect";
58
+ import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
59
+ import { homedir as homedir2 } from "os";
60
+ import { dirname as dirname2, join as join2, parse } from "path";
61
+ import process3 from "process";
62
+ import { Data as Data2, Effect as Effect3, pipe as pipe3 } from "effect";
63
63
  var CONFIG_DIR, CONFIG_FILE, PROJECT_CONFIG_FILENAME, USER_CONFIG_FILENAME, ConfigReadError, ConfigWriteError, findConfigFile, findProjectConfigFile, findUserConfigFile, loadProjectConfig, loadUserConfig, resolveUserConfigWritePath, resolveProjectConfigWritePath, 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;
64
64
  var init_config = __esm({
65
65
  "src/lib/config.ts"() {
66
66
  "use strict";
67
67
  init_esm_shims();
68
- CONFIG_DIR = join(homedir(), ".config", "braid");
69
- CONFIG_FILE = join(CONFIG_DIR, "config.json");
68
+ CONFIG_DIR = join2(homedir2(), ".config", "braid");
69
+ CONFIG_FILE = join2(CONFIG_DIR, "config.json");
70
70
  PROJECT_CONFIG_FILENAME = "braid.json";
71
71
  USER_CONFIG_FILENAME = "braid.user.json";
72
- ConfigReadError = class extends Data.TaggedError("ConfigReadError") {
72
+ ConfigReadError = class extends Data2.TaggedError("ConfigReadError") {
73
73
  };
74
- ConfigWriteError = class extends Data.TaggedError("ConfigWriteError") {
74
+ ConfigWriteError = class extends Data2.TaggedError("ConfigWriteError") {
75
75
  };
76
- findConfigFile = (filename, startDir = process2.cwd()) => {
76
+ findConfigFile = (filename, startDir = process3.cwd()) => {
77
77
  let currentDir = startDir;
78
78
  while (true) {
79
- const configPath = join(currentDir, filename);
79
+ const configPath = join2(currentDir, filename);
80
80
  if (existsSync(configPath)) {
81
81
  return configPath;
82
82
  }
@@ -87,9 +87,9 @@ var init_config = __esm({
87
87
  currentDir = parsed.dir;
88
88
  }
89
89
  };
90
- findProjectConfigFile = (startDir = process2.cwd()) => findConfigFile(PROJECT_CONFIG_FILENAME, startDir);
91
- findUserConfigFile = (startDir = process2.cwd()) => findConfigFile(USER_CONFIG_FILENAME, startDir);
92
- loadProjectConfig = () => Effect.tryPromise({
90
+ findProjectConfigFile = (startDir = process3.cwd()) => findConfigFile(PROJECT_CONFIG_FILENAME, startDir);
91
+ findUserConfigFile = (startDir = process3.cwd()) => findConfigFile(USER_CONFIG_FILENAME, startDir);
92
+ loadProjectConfig = () => Effect3.tryPromise({
93
93
  try: async () => {
94
94
  const configPath = await findProjectConfigFile();
95
95
  if (!configPath) {
@@ -99,8 +99,8 @@ var init_config = __esm({
99
99
  return JSON.parse(content);
100
100
  },
101
101
  catch: () => void 0
102
- }).pipe(Effect.orElseSucceed(() => void 0));
103
- loadUserConfig = () => Effect.tryPromise({
102
+ }).pipe(Effect3.orElseSucceed(() => void 0));
103
+ loadUserConfig = () => Effect3.tryPromise({
104
104
  try: async () => {
105
105
  const configPath = await findUserConfigFile();
106
106
  if (!configPath) {
@@ -110,16 +110,16 @@ var init_config = __esm({
110
110
  return JSON.parse(content);
111
111
  },
112
112
  catch: () => void 0
113
- }).pipe(Effect.orElseSucceed(() => void 0));
114
- resolveUserConfigWritePath = (startDir = process2.cwd()) => findUserConfigFile(startDir) ?? join(startDir, USER_CONFIG_FILENAME);
115
- resolveProjectConfigWritePath = (startDir = process2.cwd()) => findProjectConfigFile(startDir) ?? join(startDir, PROJECT_CONFIG_FILENAME);
116
- saveUserConfig = (config, startDir = process2.cwd()) => {
113
+ }).pipe(Effect3.orElseSucceed(() => void 0));
114
+ resolveUserConfigWritePath = (startDir = process3.cwd()) => findUserConfigFile(startDir) ?? join2(startDir, USER_CONFIG_FILENAME);
115
+ resolveProjectConfigWritePath = (startDir = process3.cwd()) => findProjectConfigFile(startDir) ?? join2(startDir, PROJECT_CONFIG_FILENAME);
116
+ saveUserConfig = (config, startDir = process3.cwd()) => {
117
117
  const targetPath = resolveUserConfigWritePath(startDir);
118
- return pipe(
119
- Effect.tryPromise({
118
+ return pipe3(
119
+ Effect3.tryPromise({
120
120
  try: async () => {
121
- await mkdir(dirname(targetPath), { recursive: true, mode: 448 });
122
- await writeFile(targetPath, JSON.stringify(config, null, 2), {
121
+ await mkdir2(dirname2(targetPath), { recursive: true, mode: 448 });
122
+ await writeFile2(targetPath, JSON.stringify(config, null, 2), {
123
123
  encoding: "utf-8",
124
124
  mode: 384
125
125
  });
@@ -129,13 +129,13 @@ var init_config = __esm({
129
129
  })
130
130
  );
131
131
  };
132
- saveProjectConfig = (config, startDir = process2.cwd()) => {
132
+ saveProjectConfig = (config, startDir = process3.cwd()) => {
133
133
  const targetPath = resolveProjectConfigWritePath(startDir);
134
- return pipe(
135
- Effect.tryPromise({
134
+ return pipe3(
135
+ Effect3.tryPromise({
136
136
  try: async () => {
137
- await mkdir(dirname(targetPath), { recursive: true, mode: 448 });
138
- await writeFile(targetPath, JSON.stringify(config, null, 2), {
137
+ await mkdir2(dirname2(targetPath), { recursive: true, mode: 448 });
138
+ await writeFile2(targetPath, JSON.stringify(config, null, 2), {
139
139
  encoding: "utf-8",
140
140
  mode: 384
141
141
  });
@@ -206,10 +206,10 @@ var init_config = __esm({
206
206
  }
207
207
  };
208
208
  applyEnvOverrides = (merged) => {
209
- if (process2.env.BRAID_API_KEY) {
210
- merged.token = process2.env.BRAID_API_KEY;
209
+ if (process3.env.BRAID_API_KEY) {
210
+ merged.token = process3.env.BRAID_API_KEY;
211
211
  }
212
- const envServerUrl = process2.env.BRAID_SKILLS_SERVER_URL ?? process2.env.BRAID_SERVER_URL;
212
+ const envServerUrl = process3.env.BRAID_SKILLS_SERVER_URL ?? process3.env.BRAID_SERVER_URL;
213
213
  if (envServerUrl && isValidServerUrl(envServerUrl)) {
214
214
  merged.serverUrl = envServerUrl;
215
215
  }
@@ -219,15 +219,15 @@ var init_config = __esm({
219
219
  includeUserGlobal: true,
220
220
  includeOrgGlobal: true
221
221
  });
222
- loadMergedConfig = () => pipe(
223
- Effect.all({
222
+ loadMergedConfig = () => pipe3(
223
+ Effect3.all({
224
224
  projectConfig: loadProjectConfig(),
225
225
  userConfig: loadUserConfig(),
226
226
  globalConfig: loadConfig().pipe(
227
- Effect.orElseSucceed(() => ({}))
227
+ Effect3.orElseSucceed(() => ({}))
228
228
  )
229
229
  }),
230
- Effect.map(({ projectConfig, userConfig, globalConfig }) => {
230
+ Effect3.map(({ projectConfig, userConfig, globalConfig }) => {
231
231
  const merged = createDefaultMergedConfig();
232
232
  applyConfigSource(merged, projectConfig);
233
233
  applyConfigSource(merged, userConfig);
@@ -238,24 +238,24 @@ var init_config = __esm({
238
238
  return merged;
239
239
  })
240
240
  );
241
- loadConfig = () => pipe(
242
- Effect.tryPromise({
241
+ loadConfig = () => pipe3(
242
+ Effect3.tryPromise({
243
243
  try: () => readFile(CONFIG_FILE, "utf-8"),
244
244
  catch: (e) => new ConfigReadError({ path: CONFIG_FILE, cause: e })
245
245
  }),
246
- Effect.flatMap(
247
- (content) => Effect.try({
246
+ Effect3.flatMap(
247
+ (content) => Effect3.try({
248
248
  try: () => JSON.parse(content),
249
249
  catch: () => ({})
250
250
  })
251
251
  ),
252
- Effect.orElseSucceed(() => ({}))
252
+ Effect3.orElseSucceed(() => ({}))
253
253
  );
254
- saveConfig = (config) => pipe(
255
- Effect.tryPromise({
254
+ saveConfig = (config) => pipe3(
255
+ Effect3.tryPromise({
256
256
  try: async () => {
257
- await mkdir(dirname(CONFIG_FILE), { recursive: true, mode: 448 });
258
- await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {
257
+ await mkdir2(dirname2(CONFIG_FILE), { recursive: true, mode: 448 });
258
+ await writeFile2(CONFIG_FILE, JSON.stringify(config, null, 2), {
259
259
  encoding: "utf-8",
260
260
  mode: 384
261
261
  });
@@ -263,33 +263,33 @@ var init_config = __esm({
263
263
  catch: (e) => new ConfigWriteError({ path: CONFIG_FILE, cause: e })
264
264
  })
265
265
  );
266
- getApiKey = () => pipe(
267
- Effect.succeed(process2.env.BRAID_API_KEY),
268
- Effect.flatMap(
269
- (envKey) => envKey ? Effect.succeed(envKey) : pipe(
266
+ getApiKey = () => pipe3(
267
+ Effect3.succeed(process3.env.BRAID_API_KEY),
268
+ Effect3.flatMap(
269
+ (envKey) => envKey ? Effect3.succeed(envKey) : pipe3(
270
270
  loadUserConfig(),
271
- Effect.flatMap(
272
- (userConfig) => userConfig?.token ? Effect.succeed(userConfig.token) : pipe(
271
+ Effect3.flatMap(
272
+ (userConfig) => userConfig?.token ? Effect3.succeed(userConfig.token) : pipe3(
273
273
  loadConfig(),
274
- Effect.map((config) => config.apiKey)
274
+ Effect3.map((config) => config.apiKey)
275
275
  )
276
276
  )
277
277
  )
278
278
  )
279
279
  );
280
- setApiKey = (apiKey) => pipe(
280
+ setApiKey = (apiKey) => pipe3(
281
281
  loadConfig(),
282
- Effect.flatMap((config) => saveConfig({ ...config, apiKey }))
282
+ Effect3.flatMap((config) => saveConfig({ ...config, apiKey }))
283
283
  );
284
- getServerUrl = () => pipe(
285
- Effect.succeed(process2.env.BRAID_SERVER_URL),
286
- Effect.flatMap(
287
- (envUrl) => envUrl ? Effect.succeed(envUrl) : pipe(
284
+ getServerUrl = () => pipe3(
285
+ Effect3.succeed(process3.env.BRAID_SERVER_URL),
286
+ Effect3.flatMap(
287
+ (envUrl) => envUrl ? Effect3.succeed(envUrl) : pipe3(
288
288
  loadUserConfig(),
289
- Effect.flatMap(
290
- (userConfig) => userConfig?.serverUrl ? Effect.succeed(userConfig.serverUrl) : pipe(
289
+ Effect3.flatMap(
290
+ (userConfig) => userConfig?.serverUrl ? Effect3.succeed(userConfig.serverUrl) : pipe3(
291
291
  loadConfig(),
292
- Effect.map(
292
+ Effect3.map(
293
293
  (config) => config.serverUrl ?? "https://braid.cloud"
294
294
  )
295
295
  )
@@ -297,55 +297,74 @@ var init_config = __esm({
297
297
  )
298
298
  )
299
299
  );
300
- clearApiKey = () => pipe(
300
+ clearApiKey = () => pipe3(
301
301
  loadConfig(),
302
- Effect.flatMap((config) => {
302
+ Effect3.flatMap((config) => {
303
303
  const { apiKey: _, ...rest } = config;
304
304
  return saveConfig(rest);
305
305
  })
306
306
  );
307
- loadConfigAsync = () => Effect.runPromise(loadConfig());
308
- loadProjectConfigAsync = () => Effect.runPromise(loadProjectConfig());
309
- loadUserConfigAsync = () => Effect.runPromise(loadUserConfig());
310
- loadMergedConfigAsync = () => Effect.runPromise(loadMergedConfig());
307
+ loadConfigAsync = () => Effect3.runPromise(loadConfig());
308
+ loadProjectConfigAsync = () => Effect3.runPromise(loadProjectConfig());
309
+ loadUserConfigAsync = () => Effect3.runPromise(loadUserConfig());
310
+ loadMergedConfigAsync = () => Effect3.runPromise(loadMergedConfig());
311
311
  findProjectConfigFileAsync = (startDir) => findProjectConfigFile(startDir);
312
312
  findUserConfigFileAsync = (startDir) => findUserConfigFile(startDir);
313
- saveConfigAsync = (config) => Effect.runPromise(saveConfig(config));
314
- saveUserConfigAsync = (config, startDir) => Effect.runPromise(saveUserConfig(config, startDir));
315
- saveProjectConfigAsync = (config, startDir) => Effect.runPromise(saveProjectConfig(config, startDir));
316
- getApiKeyAsync = () => Effect.runPromise(getApiKey());
317
- setApiKeyAsync = (apiKey) => Effect.runPromise(setApiKey(apiKey));
318
- getServerUrlAsync = () => Effect.runPromise(getServerUrl());
319
- clearApiKeyAsync = () => Effect.runPromise(clearApiKey());
313
+ saveConfigAsync = (config) => Effect3.runPromise(saveConfig(config));
314
+ saveUserConfigAsync = (config, startDir) => Effect3.runPromise(saveUserConfig(config, startDir));
315
+ saveProjectConfigAsync = (config, startDir) => Effect3.runPromise(saveProjectConfig(config, startDir));
316
+ getApiKeyAsync = () => Effect3.runPromise(getApiKey());
317
+ setApiKeyAsync = (apiKey) => Effect3.runPromise(setApiKey(apiKey));
318
+ getServerUrlAsync = () => Effect3.runPromise(getServerUrl());
319
+ clearApiKeyAsync = () => Effect3.runPromise(clearApiKey());
320
320
  }
321
321
  });
322
322
 
323
323
  // src/lib/api.ts
324
- import { Data as Data2, Effect as Effect2, pipe as pipe2 } from "effect";
325
- var TRAILING_SLASH_REGEX, ApiError, AuthenticationError, NetworkError, resolveApiKey, resolveServerUrl, parseResponse, buildApiUrl, buildExportUrl, executeApiRequest, parseScopeOptionsResponse, fetchScopeOptions, buildRuleOptionsUrl, fetchRuleOptions, fetchSkills, validateApiKey, fetchSkillsAsync, validateApiKeyAsync, fetchScopeOptionsAsync, fetchRuleOptionsAsync;
324
+ import { Data as Data3, Effect as Effect4, pipe as pipe4 } from "effect";
325
+ var TRAILING_SLASH_REGEX, ALLOW_UNTRUSTED_SERVER_ENV, isTruthy, isLocalHost, isTrustedApiServerUrl, ApiError, AuthenticationError, NetworkError, resolveApiKey, resolveServerUrl, parseResponse, buildApiUrl, buildExportUrl, executeApiRequest, parseScopeOptionsResponse, fetchScopeOptions, buildRuleOptionsUrl, fetchRuleOptions, fetchSkills, runLifecycleCommand, DEFAULT_PUBLIC_SERVER_URL, NotFoundError, RateLimitedError, handlePublicApiResponse, fetchPublicMetadata, fetchPublicExport, fetchPublicMetadataAsync, fetchPublicExportAsync, validateApiKey, fetchSkillsAsync, validateApiKeyAsync, fetchScopeOptionsAsync, fetchRuleOptionsAsync, runLifecycleCommandAsync;
326
326
  var init_api = __esm({
327
327
  "src/lib/api.ts"() {
328
328
  "use strict";
329
329
  init_esm_shims();
330
330
  init_config();
331
331
  TRAILING_SLASH_REGEX = /\/$/;
332
- ApiError = class extends Data2.TaggedError("ApiError") {
332
+ ALLOW_UNTRUSTED_SERVER_ENV = "BRAID_ALLOW_UNTRUSTED_SERVER_URL";
333
+ isTruthy = (value) => value === "1" || value === "true" || value === "yes";
334
+ isLocalHost = (hostname2) => hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1";
335
+ isTrustedApiServerUrl = (serverUrl) => {
336
+ try {
337
+ const parsed = new URL(serverUrl);
338
+ const hostname2 = parsed.hostname;
339
+ const isBraidHost = hostname2 === "braid.cloud" || hostname2.endsWith(".braid.cloud");
340
+ if (parsed.protocol === "https:" && isBraidHost) {
341
+ return true;
342
+ }
343
+ if (parsed.protocol === "http:" && isLocalHost(hostname2)) {
344
+ return true;
345
+ }
346
+ return false;
347
+ } catch {
348
+ return false;
349
+ }
350
+ };
351
+ ApiError = class extends Data3.TaggedError("ApiError") {
333
352
  };
334
- AuthenticationError = class extends Data2.TaggedError("AuthenticationError") {
353
+ AuthenticationError = class extends Data3.TaggedError("AuthenticationError") {
335
354
  };
336
- NetworkError = class extends Data2.TaggedError("NetworkError") {
355
+ NetworkError = class extends Data3.TaggedError("NetworkError") {
337
356
  };
338
357
  resolveApiKey = (optionsApiKey) => {
339
358
  if (optionsApiKey) {
340
- return Effect2.succeed(optionsApiKey);
359
+ return Effect4.succeed(optionsApiKey);
341
360
  }
342
- return pipe2(
343
- Effect2.tryPromise({
361
+ return pipe4(
362
+ Effect4.tryPromise({
344
363
  try: () => getApiKeyAsync(),
345
364
  catch: () => new NetworkError({ message: "Failed to read config" })
346
365
  }),
347
- Effect2.flatMap(
348
- (key) => key ? Effect2.succeed(key) : Effect2.fail(
366
+ Effect4.flatMap(
367
+ (key) => key ? Effect4.succeed(key) : Effect4.fail(
349
368
  new AuthenticationError({
350
369
  message: 'No API key configured. Run "braid auth" to authenticate.'
351
370
  })
@@ -355,24 +374,43 @@ var init_api = __esm({
355
374
  };
356
375
  resolveServerUrl = (optionsServerUrl) => {
357
376
  if (optionsServerUrl) {
358
- return Effect2.succeed(optionsServerUrl);
377
+ if (!(isTruthy(process.env[ALLOW_UNTRUSTED_SERVER_ENV]) || isTrustedApiServerUrl(optionsServerUrl))) {
378
+ return Effect4.fail(
379
+ new NetworkError({
380
+ message: `Untrusted server URL '${optionsServerUrl}'. Set ${ALLOW_UNTRUSTED_SERVER_ENV}=true to override.`
381
+ })
382
+ );
383
+ }
384
+ return Effect4.succeed(optionsServerUrl);
359
385
  }
360
- return Effect2.tryPromise({
361
- try: () => getServerUrlAsync(),
362
- catch: () => new NetworkError({ message: "Failed to read config" })
363
- });
386
+ return pipe4(
387
+ Effect4.tryPromise({
388
+ try: () => getServerUrlAsync(),
389
+ catch: () => new NetworkError({ message: "Failed to read config" })
390
+ }),
391
+ Effect4.flatMap((serverUrl) => {
392
+ if (isTruthy(process.env[ALLOW_UNTRUSTED_SERVER_ENV]) || isTrustedApiServerUrl(serverUrl)) {
393
+ return Effect4.succeed(serverUrl);
394
+ }
395
+ return Effect4.fail(
396
+ new NetworkError({
397
+ message: `Untrusted server URL '${serverUrl}'. Set ${ALLOW_UNTRUSTED_SERVER_ENV}=true to override.`
398
+ })
399
+ );
400
+ })
401
+ );
364
402
  };
365
403
  parseResponse = (response, json) => {
366
404
  if (!response.ok) {
367
405
  const errorResponse = json;
368
406
  if (response.status === 401) {
369
- return Effect2.fail(
407
+ return Effect4.fail(
370
408
  new AuthenticationError({
371
409
  message: errorResponse.error || "Invalid or expired API key. Run 'braid auth' to re-authenticate."
372
410
  })
373
411
  );
374
412
  }
375
- return Effect2.fail(
413
+ return Effect4.fail(
376
414
  new ApiError({
377
415
  message: errorResponse.error || "API request failed",
378
416
  code: errorResponse.code || "UNKNOWN_ERROR",
@@ -380,7 +418,7 @@ var init_api = __esm({
380
418
  })
381
419
  );
382
420
  }
383
- return Effect2.succeed(json);
421
+ return Effect4.succeed(json);
384
422
  };
385
423
  buildApiUrl = (serverUrl, path2) => {
386
424
  const baseUrl = serverUrl.replace(TRAILING_SLASH_REGEX, "");
@@ -420,8 +458,8 @@ var init_api = __esm({
420
458
  }
421
459
  return url;
422
460
  };
423
- executeApiRequest = (url, apiKey, serverUrl) => pipe2(
424
- Effect2.tryPromise({
461
+ executeApiRequest = (url, apiKey, serverUrl) => pipe4(
462
+ Effect4.tryPromise({
425
463
  try: () => fetch(url.toString(), {
426
464
  method: "GET",
427
465
  headers: {
@@ -434,13 +472,13 @@ var init_api = __esm({
434
472
  cause: e
435
473
  })
436
474
  }),
437
- Effect2.flatMap(
438
- (response) => pipe2(
439
- Effect2.tryPromise({
475
+ Effect4.flatMap(
476
+ (response) => pipe4(
477
+ Effect4.tryPromise({
440
478
  try: () => response.json(),
441
479
  catch: () => new NetworkError({ message: "Failed to parse API response" })
442
480
  }),
443
- Effect2.flatMap((json) => parseResponse(response, json))
481
+ Effect4.flatMap((json) => parseResponse(response, json))
444
482
  )
445
483
  )
446
484
  );
@@ -448,13 +486,13 @@ var init_api = __esm({
448
486
  if (!response.ok) {
449
487
  const errorResponse = json;
450
488
  if (response.status === 401) {
451
- return Effect2.fail(
489
+ return Effect4.fail(
452
490
  new AuthenticationError({
453
491
  message: errorResponse.error || "Invalid or expired API key. Run 'braid auth' to re-authenticate."
454
492
  })
455
493
  );
456
494
  }
457
- return Effect2.fail(
495
+ return Effect4.fail(
458
496
  new ApiError({
459
497
  message: errorResponse.error || "API request failed",
460
498
  code: errorResponse.code || "UNKNOWN_ERROR",
@@ -462,17 +500,17 @@ var init_api = __esm({
462
500
  })
463
501
  );
464
502
  }
465
- return Effect2.succeed(json);
503
+ return Effect4.succeed(json);
466
504
  };
467
- fetchScopeOptions = (options = {}) => pipe2(
468
- Effect2.all({
505
+ fetchScopeOptions = (options = {}) => pipe4(
506
+ Effect4.all({
469
507
  apiKey: resolveApiKey(options.apiKey),
470
508
  serverUrl: resolveServerUrl(options.serverUrl)
471
509
  }),
472
- Effect2.flatMap(({ apiKey, serverUrl }) => {
510
+ Effect4.flatMap(({ apiKey, serverUrl }) => {
473
511
  const url = buildApiUrl(serverUrl, "/api/skills/scope-options");
474
- return pipe2(
475
- Effect2.tryPromise({
512
+ return pipe4(
513
+ Effect4.tryPromise({
476
514
  try: () => fetch(url.toString(), {
477
515
  method: "GET",
478
516
  headers: {
@@ -485,13 +523,13 @@ var init_api = __esm({
485
523
  cause: e
486
524
  })
487
525
  }),
488
- Effect2.flatMap(
489
- (response) => pipe2(
490
- Effect2.tryPromise({
526
+ Effect4.flatMap(
527
+ (response) => pipe4(
528
+ Effect4.tryPromise({
491
529
  try: () => response.json(),
492
530
  catch: () => new NetworkError({ message: "Failed to parse API response" })
493
531
  }),
494
- Effect2.flatMap((json) => parseScopeOptionsResponse(response, json))
532
+ Effect4.flatMap((json) => parseScopeOptionsResponse(response, json))
495
533
  )
496
534
  )
497
535
  );
@@ -522,14 +560,14 @@ var init_api = __esm({
522
560
  }
523
561
  return url;
524
562
  };
525
- fetchRuleOptions = (options = {}) => pipe2(
526
- Effect2.all({
563
+ fetchRuleOptions = (options = {}) => pipe4(
564
+ Effect4.all({
527
565
  apiKey: resolveApiKey(options.apiKey),
528
566
  serverUrl: resolveServerUrl(options.serverUrl)
529
567
  }),
530
- Effect2.flatMap(
531
- ({ apiKey, serverUrl }) => pipe2(
532
- Effect2.tryPromise({
568
+ Effect4.flatMap(
569
+ ({ apiKey, serverUrl }) => pipe4(
570
+ Effect4.tryPromise({
533
571
  try: () => fetch(buildRuleOptionsUrl(serverUrl, options).toString(), {
534
572
  method: "GET",
535
573
  headers: {
@@ -542,17 +580,17 @@ var init_api = __esm({
542
580
  cause: e
543
581
  })
544
582
  }),
545
- Effect2.flatMap(
546
- (response) => pipe2(
547
- Effect2.tryPromise({
583
+ Effect4.flatMap(
584
+ (response) => pipe4(
585
+ Effect4.tryPromise({
548
586
  try: () => response.json(),
549
587
  catch: () => new NetworkError({ message: "Failed to parse API response" })
550
588
  }),
551
- Effect2.flatMap((json) => {
589
+ Effect4.flatMap((json) => {
552
590
  if (!response.ok) {
553
591
  return parseResponse(response, json).pipe(
554
- Effect2.flatMap(
555
- () => Effect2.fail(
592
+ Effect4.flatMap(
593
+ () => Effect4.fail(
556
594
  new ApiError({
557
595
  message: "API request failed",
558
596
  code: "UNKNOWN_ERROR",
@@ -562,53 +600,206 @@ var init_api = __esm({
562
600
  )
563
601
  );
564
602
  }
565
- return Effect2.succeed(json);
603
+ return Effect4.succeed(json);
566
604
  })
567
605
  )
568
606
  )
569
607
  )
570
608
  )
571
609
  );
572
- fetchSkills = (options) => pipe2(
573
- Effect2.all({
610
+ fetchSkills = (options) => pipe4(
611
+ Effect4.all({
574
612
  apiKey: resolveApiKey(options.apiKey),
575
613
  serverUrl: resolveServerUrl(options.serverUrl)
576
614
  }),
577
- Effect2.flatMap(({ apiKey, serverUrl }) => {
615
+ Effect4.flatMap(({ apiKey, serverUrl }) => {
578
616
  const url = buildExportUrl(serverUrl, options);
579
617
  return executeApiRequest(url, apiKey, serverUrl);
580
618
  })
581
619
  );
582
- validateApiKey = (apiKey, serverUrl) => pipe2(
583
- Effect2.tryPromise({
584
- try: async () => {
585
- const baseUrl = serverUrl ?? "https://braid.cloud";
586
- const url = buildApiUrl(baseUrl, "/api/skills/export");
587
- url.searchParams.set("profile", "default");
588
- const response = await fetch(url.toString(), {
589
- method: "GET",
590
- headers: {
591
- Authorization: `Bearer ${apiKey}`,
592
- "Content-Type": "application/json"
620
+ runLifecycleCommand = (request, options = {}) => pipe4(
621
+ Effect4.all({
622
+ apiKey: resolveApiKey(options.apiKey),
623
+ serverUrl: resolveServerUrl(options.serverUrl)
624
+ }),
625
+ Effect4.flatMap(
626
+ ({ apiKey, serverUrl }) => Effect4.tryPromise({
627
+ try: async () => {
628
+ const response = await fetch(
629
+ buildApiUrl(serverUrl, "/api/lifecycle").toString(),
630
+ {
631
+ method: "POST",
632
+ headers: {
633
+ Authorization: `Bearer ${apiKey}`,
634
+ "Content-Type": "application/json"
635
+ },
636
+ body: JSON.stringify(request)
637
+ }
638
+ );
639
+ const json = await response.json();
640
+ if (!response.ok) {
641
+ const errorResponse = json;
642
+ if (response.status === 401) {
643
+ throw new AuthenticationError({
644
+ message: errorResponse.error || "Invalid or expired API key. Run 'braid auth' to re-authenticate."
645
+ });
646
+ }
647
+ throw new ApiError({
648
+ message: errorResponse.error || "API request failed",
649
+ code: errorResponse.code || "UNKNOWN_ERROR",
650
+ status: response.status
651
+ });
593
652
  }
594
- });
595
- return response.status !== 401;
653
+ return json;
654
+ },
655
+ catch: (error) => {
656
+ if (error instanceof ApiError || error instanceof AuthenticationError || error instanceof NetworkError) {
657
+ return error;
658
+ }
659
+ return new NetworkError({
660
+ message: `Failed to connect to ${serverUrl}`,
661
+ cause: error
662
+ });
663
+ }
664
+ })
665
+ )
666
+ );
667
+ DEFAULT_PUBLIC_SERVER_URL = "https://braid.cloud";
668
+ NotFoundError = class extends Data3.TaggedError("NotFoundError") {
669
+ };
670
+ RateLimitedError = class extends Data3.TaggedError("RateLimitedError") {
671
+ };
672
+ handlePublicApiResponse = (response, json) => {
673
+ if (response.ok) {
674
+ return Effect4.succeed(json);
675
+ }
676
+ if (response.status === 404) {
677
+ return Effect4.fail(
678
+ new NotFoundError({ message: "Public content not found" })
679
+ );
680
+ }
681
+ if (response.status === 429) {
682
+ const retryAfter = response.headers.get("Retry-After");
683
+ const retryMs = retryAfter ? Number(retryAfter) * 1e3 : null;
684
+ return Effect4.fail(
685
+ new RateLimitedError({
686
+ message: "Rate limited. Please try again later.",
687
+ ...retryMs !== null ? { retryAfterMs: retryMs } : {}
688
+ })
689
+ );
690
+ }
691
+ const errorResponse = json;
692
+ return Effect4.fail(
693
+ new ApiError({
694
+ message: errorResponse.error || "API request failed",
695
+ code: errorResponse.code || "UNKNOWN_ERROR",
696
+ status: response.status
697
+ })
698
+ );
699
+ };
700
+ fetchPublicMetadata = (handle, slug, serverUrl) => {
701
+ const baseUrl = (serverUrl ?? DEFAULT_PUBLIC_SERVER_URL).replace(
702
+ TRAILING_SLASH_REGEX,
703
+ ""
704
+ );
705
+ const url = `${baseUrl}/api/public/@${handle}/${slug}`;
706
+ return pipe4(
707
+ Effect4.tryPromise({
708
+ try: () => fetch(url, { method: "GET" }),
709
+ catch: (e) => new NetworkError({
710
+ message: `Failed to connect to ${baseUrl}`,
711
+ cause: e
712
+ })
713
+ }),
714
+ Effect4.flatMap(
715
+ (response) => pipe4(
716
+ Effect4.tryPromise({
717
+ try: () => response.json(),
718
+ catch: () => new NetworkError({ message: "Failed to parse API response" })
719
+ }),
720
+ Effect4.flatMap(
721
+ (json) => handlePublicApiResponse(response, json)
722
+ )
723
+ )
724
+ )
725
+ );
726
+ };
727
+ fetchPublicExport = (handle, slug, ruleIds, serverUrl) => {
728
+ const baseUrl = (serverUrl ?? DEFAULT_PUBLIC_SERVER_URL).replace(
729
+ TRAILING_SLASH_REGEX,
730
+ ""
731
+ );
732
+ const url = new URL(`${baseUrl}/api/public/@${handle}/${slug}/export`);
733
+ if (ruleIds && ruleIds.length > 0) {
734
+ url.searchParams.set("ruleIds", ruleIds.join(","));
735
+ }
736
+ return pipe4(
737
+ Effect4.tryPromise({
738
+ try: () => fetch(url.toString(), { method: "GET" }),
739
+ catch: (e) => new NetworkError({
740
+ message: `Failed to connect to ${baseUrl}`,
741
+ cause: e
742
+ })
743
+ }),
744
+ Effect4.flatMap(
745
+ (response) => pipe4(
746
+ Effect4.tryPromise({
747
+ try: () => response.json(),
748
+ catch: () => new NetworkError({ message: "Failed to parse API response" })
749
+ }),
750
+ Effect4.flatMap(
751
+ (json) => handlePublicApiResponse(response, json)
752
+ )
753
+ )
754
+ )
755
+ );
756
+ };
757
+ fetchPublicMetadataAsync = (handle, slug, serverUrl) => Effect4.runPromise(fetchPublicMetadata(handle, slug, serverUrl));
758
+ fetchPublicExportAsync = (handle, slug, ruleIds, serverUrl) => Effect4.runPromise(fetchPublicExport(handle, slug, ruleIds, serverUrl));
759
+ validateApiKey = (apiKey, serverUrl) => pipe4(
760
+ Effect4.try({
761
+ try: () => {
762
+ const baseUrl = serverUrl ?? "https://braid.cloud";
763
+ if (!(isTruthy(process.env[ALLOW_UNTRUSTED_SERVER_ENV]) || isTrustedApiServerUrl(baseUrl))) {
764
+ throw new NetworkError({
765
+ message: `Untrusted server URL '${baseUrl}'. Set ${ALLOW_UNTRUSTED_SERVER_ENV}=true to override.`
766
+ });
767
+ }
768
+ return baseUrl;
596
769
  },
597
- catch: (e) => new NetworkError({
598
- message: `Failed to connect to ${serverUrl ?? "https://braid.cloud"}`,
599
- cause: e
770
+ catch: (e) => e instanceof NetworkError ? e : new NetworkError({ message: "Invalid server URL" })
771
+ }),
772
+ Effect4.flatMap(
773
+ (baseUrl) => Effect4.tryPromise({
774
+ try: async () => {
775
+ const url = buildApiUrl(baseUrl, "/api/skills/export");
776
+ url.searchParams.set("profile", "default");
777
+ const response = await fetch(url.toString(), {
778
+ method: "GET",
779
+ headers: {
780
+ Authorization: `Bearer ${apiKey}`,
781
+ "Content-Type": "application/json"
782
+ }
783
+ });
784
+ return response.status !== 401;
785
+ },
786
+ catch: (e) => new NetworkError({
787
+ message: `Failed to connect to ${baseUrl}`,
788
+ cause: e
789
+ })
600
790
  })
601
- })
791
+ )
602
792
  );
603
- fetchSkillsAsync = (options) => Effect2.runPromise(fetchSkills(options));
604
- validateApiKeyAsync = (apiKey, serverUrl) => Effect2.runPromise(validateApiKey(apiKey, serverUrl));
605
- fetchScopeOptionsAsync = (options = {}) => Effect2.runPromise(fetchScopeOptions(options));
606
- fetchRuleOptionsAsync = (options = {}) => Effect2.runPromise(fetchRuleOptions(options));
793
+ fetchSkillsAsync = (options) => Effect4.runPromise(fetchSkills(options));
794
+ validateApiKeyAsync = (apiKey, serverUrl) => Effect4.runPromise(validateApiKey(apiKey, serverUrl));
795
+ fetchScopeOptionsAsync = (options = {}) => Effect4.runPromise(fetchScopeOptions(options));
796
+ fetchRuleOptionsAsync = (options = {}) => Effect4.runPromise(fetchRuleOptions(options));
797
+ runLifecycleCommandAsync = (request, options = {}) => Effect4.runPromise(runLifecycleCommand(request, options));
607
798
  }
608
799
  });
609
800
 
610
801
  // src/lib/tui.ts
611
- import process3 from "process";
802
+ import process4 from "process";
612
803
  import {
613
804
  cancel as clackCancel,
614
805
  confirm as clackConfirm,
@@ -633,19 +824,19 @@ function spinner() {
633
824
  return {
634
825
  start: (message) => {
635
826
  if (message) {
636
- process3.stdout.write(`${message}
827
+ process4.stdout.write(`${message}
637
828
  `);
638
829
  }
639
830
  },
640
831
  stop: (message) => {
641
832
  if (message) {
642
- process3.stdout.write(`${message}
833
+ process4.stdout.write(`${message}
643
834
  `);
644
835
  }
645
836
  },
646
837
  message: (message) => {
647
838
  if (message) {
648
- process3.stdout.write(`${message}
839
+ process4.stdout.write(`${message}
649
840
  `);
650
841
  }
651
842
  }
@@ -655,7 +846,7 @@ function intro(message) {
655
846
  if (isTTY) {
656
847
  clackIntro(message);
657
848
  } else {
658
- process3.stdout.write(`
849
+ process4.stdout.write(`
659
850
  ${message}
660
851
  `);
661
852
  }
@@ -664,7 +855,7 @@ function outro(message) {
664
855
  if (isTTY) {
665
856
  clackOutro(message);
666
857
  } else {
667
- process3.stdout.write(`${message}
858
+ process4.stdout.write(`${message}
668
859
 
669
860
  `);
670
861
  }
@@ -674,7 +865,7 @@ var init_tui = __esm({
674
865
  "src/lib/tui.ts"() {
675
866
  "use strict";
676
867
  init_esm_shims();
677
- isTTY = Boolean(process3.stdout.isTTY);
868
+ isTTY = Boolean(process4.stdout.isTTY);
678
869
  cancel = clackCancel;
679
870
  confirm = clackConfirm;
680
871
  isCancel = clackIsCancel;
@@ -785,10 +976,10 @@ var scope_exports = {};
785
976
  __export(scope_exports, {
786
977
  scopeCommand: () => scopeCommand
787
978
  });
788
- import process4 from "process";
979
+ import process5 from "process";
789
980
  function exitCancelled(message) {
790
981
  cancel(message);
791
- process4.exit(0);
982
+ process5.exit(0);
792
983
  }
793
984
  async function selectTargetFile(options) {
794
985
  if (options.file) {
@@ -922,10 +1113,10 @@ async function selectProjects(organizationContext, scopeOptions) {
922
1113
  }
923
1114
  return selected.length > 0 ? selected : void 0;
924
1115
  }
925
- async function pickRuleIds(message, rules) {
1116
+ async function pickRuleIds(message, rules2) {
926
1117
  const selected = await multiselect({
927
1118
  message,
928
- options: rules.map((rule) => ({
1119
+ options: rules2.map((rule) => ({
929
1120
  value: rule.id,
930
1121
  label: rule.title,
931
1122
  hint: rule.id
@@ -1136,7 +1327,7 @@ async function scopeCommand(options) {
1136
1327
  } catch (error) {
1137
1328
  loadSpinner.stop("Failed to load scope options");
1138
1329
  log.error(error instanceof Error ? error.message : String(error));
1139
- process4.exit(1);
1330
+ process5.exit(1);
1140
1331
  }
1141
1332
  }
1142
1333
  var init_scope = __esm({
@@ -1156,169 +1347,260 @@ init_esm_shims();
1156
1347
  import { createRequire } from "module";
1157
1348
  import { Command } from "commander";
1158
1349
 
1159
- // src/commands/auth.ts
1350
+ // src/commands/agents.ts
1160
1351
  init_esm_shims();
1161
- init_api();
1162
- init_config();
1163
- init_tui();
1164
- import process5 from "process";
1165
- async function configureDefaultScopeAsync(serverUrl) {
1166
- const shouldConfigureScope = await confirm({
1167
- message: "Configure organization and scope defaults now?",
1168
- initialValue: true
1169
- });
1170
- if (isCancel(shouldConfigureScope) || !shouldConfigureScope) {
1171
- return;
1352
+
1353
+ // src/lib/agent-writer.ts
1354
+ init_esm_shims();
1355
+ import { mkdir, writeFile } from "fs/promises";
1356
+ import { dirname, resolve, sep } from "path";
1357
+ import { Data, Effect, pipe } from "effect";
1358
+
1359
+ // src/lib/agent-adapters.ts
1360
+ init_esm_shims();
1361
+ var NATIVE_AGENT_IDS = /* @__PURE__ */ new Set(["claude-code", "opencode"]);
1362
+ var MAPPED_AGENT_IDS = /* @__PURE__ */ new Set([
1363
+ "cursor",
1364
+ "windsurf",
1365
+ "cline",
1366
+ "roo",
1367
+ "codex"
1368
+ ]);
1369
+ var resolveProfile = (agent) => {
1370
+ if (NATIVE_AGENT_IDS.has(agent.id)) {
1371
+ return {
1372
+ tier: "native",
1373
+ supportsGlobalInstall: Boolean(agent.agentsGlobalPath),
1374
+ supportsProjectInstall: Boolean(agent.agentsProjectPath),
1375
+ notes: "Native subagent/frontmatter format supported."
1376
+ };
1172
1377
  }
1173
- const { scopeCommand: scopeCommand2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
1174
- const config = await loadMergedConfigAsync();
1175
- await scopeCommand2({
1176
- file: "user",
1177
- server: serverUrl,
1178
- ...config.token ? { apiKey: config.token } : {}
1179
- });
1180
- }
1181
- async function authCommand(options) {
1182
- intro("braid auth");
1183
- const config = await loadMergedConfigAsync();
1184
- if (config.token) {
1185
- const shouldReplace = await confirm({
1186
- message: "An API key is already configured. Replace it?",
1187
- initialValue: false
1188
- });
1189
- if (isCancel(shouldReplace) || !shouldReplace) {
1190
- outro("Auth cancelled.");
1191
- return;
1192
- }
1378
+ if (MAPPED_AGENT_IDS.has(agent.id)) {
1379
+ return {
1380
+ tier: "mapped",
1381
+ supportsGlobalInstall: Boolean(agent.globalPath),
1382
+ supportsProjectInstall: Boolean(agent.projectPath),
1383
+ notes: "No native agent file format confirmed; install as compatibility artifacts."
1384
+ };
1193
1385
  }
1194
- const apiKey = await password({
1195
- message: "Enter your braid API key:",
1196
- validate: (value) => {
1197
- if (!value) {
1198
- return "API key is required";
1199
- }
1200
- if (!value.startsWith("br_")) {
1201
- return "API key should start with 'br_'";
1202
- }
1203
- return void 0;
1386
+ return {
1387
+ tier: "compat",
1388
+ supportsGlobalInstall: Boolean(agent.globalPath),
1389
+ supportsProjectInstall: Boolean(agent.projectPath),
1390
+ notes: "Install compatibility output only, with capability warning surfaced to user."
1391
+ };
1392
+ };
1393
+
1394
+ // src/lib/agent-writer.ts
1395
+ var AgentWriteError = class extends Data.TaggedError("AgentWriteError") {
1396
+ };
1397
+ var isObjectRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1398
+ var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1399
+ var escapeYaml = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1400
+ var toYamlLines = (key, value, indent = 0) => {
1401
+ const prefix = " ".repeat(indent);
1402
+ const nestedPrefix = " ".repeat(indent + 2);
1403
+ if (typeof value === "string") {
1404
+ return [`${prefix}${key}: "${escapeYaml(value)}"`];
1405
+ }
1406
+ if (typeof value === "number" || typeof value === "boolean") {
1407
+ return [`${prefix}${key}: ${value}`];
1408
+ }
1409
+ if (Array.isArray(value)) {
1410
+ if (value.length === 0) {
1411
+ return [`${prefix}${key}: []`];
1204
1412
  }
1205
- });
1206
- if (isCancel(apiKey)) {
1207
- cancel("Auth cancelled.");
1208
- process5.exit(0);
1413
+ return [
1414
+ `${prefix}${key}:`,
1415
+ ...value.map((item) => {
1416
+ if (typeof item === "string" || typeof item === "number" || typeof item === "boolean") {
1417
+ return `${nestedPrefix}- ${typeof item === "string" ? `"${escapeYaml(item)}"` : item}`;
1418
+ }
1419
+ if (isObjectRecord(item)) {
1420
+ const nested = Object.entries(item).flatMap(
1421
+ ([nestedKey, nestedValue]) => toYamlLines(nestedKey, nestedValue, indent + 4)
1422
+ );
1423
+ return `${nestedPrefix}-
1424
+ ${nested.join("\n")}`;
1425
+ }
1426
+ return `${nestedPrefix}- null`;
1427
+ })
1428
+ ];
1209
1429
  }
1210
- const authSpinner = spinner();
1211
- let authSpinnerActive = true;
1212
- const stopAuthSpinner = (message) => {
1213
- if (!authSpinnerActive) {
1214
- return;
1215
- }
1216
- authSpinner.stop(message);
1217
- authSpinnerActive = false;
1218
- };
1219
- authSpinner.start("Validating API key...");
1220
- try {
1221
- const serverUrl = options.server ?? "https://braid.cloud";
1222
- const isValid = await validateApiKeyAsync(apiKey, serverUrl);
1223
- if (!isValid) {
1224
- stopAuthSpinner("Invalid API key");
1225
- log.error(
1226
- "The API key could not be validated. Please check your key and try again."
1227
- );
1228
- process5.exit(1);
1430
+ if (isObjectRecord(value)) {
1431
+ const entries = Object.entries(value);
1432
+ if (entries.length === 0) {
1433
+ return [`${prefix}${key}: {}`];
1229
1434
  }
1230
- await setApiKeyAsync(apiKey);
1231
- const existingUserConfig = await loadUserConfigAsync();
1232
- if (existingUserConfig?.token) {
1233
- await saveUserConfigAsync({ ...existingUserConfig, token: apiKey });
1435
+ return [
1436
+ `${prefix}${key}:`,
1437
+ ...entries.flatMap(
1438
+ ([nestedKey, nestedValue]) => toYamlLines(nestedKey, nestedValue, indent + 2)
1439
+ )
1440
+ ];
1441
+ }
1442
+ return [];
1443
+ };
1444
+ var buildClaudeMarkdown = (spec) => {
1445
+ const lines = ["---"];
1446
+ lines.push(...toYamlLines("name", spec.name));
1447
+ lines.push(...toYamlLines("description", spec.description));
1448
+ if (spec.model) {
1449
+ lines.push(...toYamlLines("model", spec.model));
1450
+ }
1451
+ if (spec.tools !== void 0) {
1452
+ lines.push(...toYamlLines("tools", spec.tools));
1453
+ }
1454
+ if (spec.permission !== void 0) {
1455
+ lines.push(...toYamlLines("permissionMode", spec.permission));
1456
+ }
1457
+ if (spec.steps !== void 0) {
1458
+ lines.push(...toYamlLines("maxTurns", spec.steps));
1459
+ }
1460
+ if (spec.temperature !== void 0) {
1461
+ lines.push(...toYamlLines("temperature", spec.temperature));
1462
+ }
1463
+ if (spec.linkedSkillNames && spec.linkedSkillNames.length > 0) {
1464
+ lines.push(...toYamlLines("skills", spec.linkedSkillNames));
1465
+ }
1466
+ if (spec.additional) {
1467
+ for (const [key, value] of Object.entries(spec.additional)) {
1468
+ lines.push(...toYamlLines(key, value));
1234
1469
  }
1235
- stopAuthSpinner("API key validated and saved");
1236
- await configureDefaultScopeAsync(serverUrl);
1237
- log.success(`Config saved to ${CONFIG_FILE}`);
1238
- outro(
1239
- "You're authenticated! Run 'braid install --profile <name>' to install skills."
1240
- );
1241
- } catch (error) {
1242
- stopAuthSpinner("Validation failed");
1243
- const message = error instanceof Error ? error.message : String(error);
1244
- log.error(`Failed to validate API key: ${message}`);
1245
- process5.exit(1);
1246
1470
  }
1247
- }
1248
- async function displayTokenSource(masked) {
1249
- if (process5.env.BRAID_API_KEY) {
1250
- log.info(`Authenticated with key: ${masked}`);
1251
- log.info("Source: BRAID_API_KEY environment variable");
1252
- return;
1471
+ lines.push("---", "", spec.prompt.trim(), "");
1472
+ return lines.join("\n");
1473
+ };
1474
+ var buildOpenCodeMarkdown = (spec) => {
1475
+ const lines = ["---"];
1476
+ lines.push(...toYamlLines("description", spec.description));
1477
+ if (spec.mode) {
1478
+ lines.push(...toYamlLines("mode", spec.mode));
1253
1479
  }
1254
- const userConfigPath = await findUserConfigFileAsync();
1255
- log.info(`Authenticated with key: ${masked}`);
1256
- log.info(`Source: ${userConfigPath ?? CONFIG_FILE}`);
1257
- }
1258
- async function displayProjectConfig() {
1259
- const projectConfigPath = await findProjectConfigFileAsync();
1260
- if (projectConfigPath) {
1261
- log.info(`Project config: ${projectConfigPath}`);
1480
+ if (spec.model) {
1481
+ lines.push(...toYamlLines("model", spec.model));
1262
1482
  }
1263
- }
1264
- function displayResolvedSettings(config) {
1265
- const hasOrgProjects = config.orgProjects && config.orgProjects.length > 0;
1266
- const hasPersonalProjects = config.personalProjects && config.personalProjects.length > 0;
1267
- if (!(config.profile || hasOrgProjects || hasPersonalProjects)) {
1268
- return;
1483
+ if (spec.tools !== void 0) {
1484
+ lines.push(...toYamlLines("tools", spec.tools));
1269
1485
  }
1270
- log.info("");
1271
- if (config.profile) {
1272
- log.info(`Default profile: ${config.profile}`);
1486
+ if (spec.permission !== void 0) {
1487
+ lines.push(...toYamlLines("permission", spec.permission));
1273
1488
  }
1274
- if (hasOrgProjects) {
1275
- log.info(`Org projects: ${config.orgProjects?.join(", ")}`);
1489
+ if (spec.steps !== void 0) {
1490
+ lines.push(...toYamlLines("steps", spec.steps));
1276
1491
  }
1277
- if (hasPersonalProjects) {
1278
- log.info(`Personal projects: ${config.personalProjects?.join(", ")}`);
1492
+ if (spec.temperature !== void 0) {
1493
+ lines.push(...toYamlLines("temperature", spec.temperature));
1279
1494
  }
1280
- if (config.serverUrl !== "https://braid.cloud") {
1281
- log.info(`Server: ${config.serverUrl}`);
1495
+ if (spec.topP !== void 0) {
1496
+ lines.push(...toYamlLines("top_p", spec.topP));
1282
1497
  }
1283
- }
1284
- async function authStatusCommand() {
1285
- const config = await loadMergedConfigAsync();
1286
- if (!config.token) {
1287
- log.warn("Not authenticated. Run 'braid auth' to configure your API key.");
1288
- log.info(
1289
- `Or create a ${USER_CONFIG_FILENAME} file with: { "token": "br_xxx" }`
1290
- );
1291
- return;
1498
+ if (spec.hidden !== void 0) {
1499
+ lines.push(...toYamlLines("hidden", spec.hidden));
1292
1500
  }
1293
- const masked = `${config.token.slice(0, 7)}...${config.token.slice(-4)}`;
1294
- await displayTokenSource(masked);
1295
- await displayProjectConfig();
1296
- displayResolvedSettings(config);
1297
- }
1298
- async function authLogoutCommand() {
1299
- const { clearApiKeyAsync: clearApiKeyAsync2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1300
- await clearApiKeyAsync2();
1301
- log.success("Logged out. API key removed from config.");
1302
- }
1303
-
1304
- // src/commands/install.ts
1305
- init_esm_shims();
1306
-
1307
- // src/lib/agents.ts
1308
- init_esm_shims();
1309
- import { access, constants } from "fs/promises";
1310
- import { homedir as homedir2 } from "os";
1311
- import { join as join2 } from "path";
1312
- import process6 from "process";
1313
- import { Effect as Effect3, pipe as pipe3 } from "effect";
1314
- var home = homedir2();
1315
- var vscodeExtSettingsPath = (extensionId, filename) => {
1316
- if (process6.platform === "darwin") {
1317
- return join2(
1318
- home,
1319
- "Library",
1320
- "Application Support",
1321
- "Code",
1501
+ if (spec.color) {
1502
+ lines.push(...toYamlLines("color", spec.color));
1503
+ }
1504
+ if (spec.additional) {
1505
+ for (const [key, value] of Object.entries(spec.additional)) {
1506
+ lines.push(...toYamlLines(key, value));
1507
+ }
1508
+ }
1509
+ lines.push("---", "", spec.prompt.trim(), "");
1510
+ return lines.join("\n");
1511
+ };
1512
+ var buildCompatMarkdown = (spec, source) => [
1513
+ "# Compatibility Agent",
1514
+ "",
1515
+ `Source: ${source}`,
1516
+ `Name: ${spec.name}`,
1517
+ "",
1518
+ spec.prompt.trim(),
1519
+ ""
1520
+ ].join("\n");
1521
+ var assertWithinBase = (basePath, filename) => {
1522
+ const resolvedBase = resolve(basePath);
1523
+ const resolvedPath = resolve(basePath, filename);
1524
+ if (resolvedPath !== resolvedBase && !resolvedPath.startsWith(`${resolvedBase}${sep}`)) {
1525
+ throw new Error(`Path traversal detected: ${filename}`);
1526
+ }
1527
+ return resolvedPath;
1528
+ };
1529
+ var writeSingleAgent = (installPath, platform2, spec) => Effect.tryPromise({
1530
+ try: async () => {
1531
+ const profile = resolveProfile(platform2);
1532
+ const fileStem = slugify(spec.name) || "agent";
1533
+ const filename = `${fileStem}.md`;
1534
+ const fullPath = assertWithinBase(installPath, filename);
1535
+ await mkdir(dirname(fullPath), { recursive: true });
1536
+ const warnings = [];
1537
+ let content;
1538
+ if (platform2.id === "claude-code") {
1539
+ if (spec.mode === "primary") {
1540
+ warnings.push(
1541
+ "Claude adapter does not support primary mode; using subagent semantics."
1542
+ );
1543
+ }
1544
+ content = buildClaudeMarkdown(spec);
1545
+ } else if (platform2.id === "opencode") {
1546
+ content = buildOpenCodeMarkdown(spec);
1547
+ } else {
1548
+ warnings.push(
1549
+ `Platform ${platform2.id} uses ${profile.tier} adapter with compatibility output.`
1550
+ );
1551
+ content = buildCompatMarkdown(spec, platform2.name);
1552
+ }
1553
+ await writeFile(fullPath, content, "utf-8");
1554
+ return { written: fullPath, warnings };
1555
+ },
1556
+ catch: (cause) => new AgentWriteError({ path: installPath, operation: "write", cause })
1557
+ });
1558
+ var writeAgentsForPlatform = (platform2, specs, installPath) => pipe(
1559
+ Effect.forEach(
1560
+ specs,
1561
+ (spec) => pipe(
1562
+ writeSingleAgent(installPath, platform2, spec),
1563
+ Effect.map((result) => ({
1564
+ success: true,
1565
+ written: result.written,
1566
+ warnings: result.warnings
1567
+ })),
1568
+ Effect.catchAll(
1569
+ (error) => Effect.succeed({
1570
+ success: false,
1571
+ error: error.cause instanceof Error ? error.cause.message : String(error.cause),
1572
+ specName: spec.name
1573
+ })
1574
+ )
1575
+ ),
1576
+ { concurrency: "unbounded" }
1577
+ ),
1578
+ Effect.map((results) => {
1579
+ const written = results.filter((r) => r.success).map((r) => r.written).sort();
1580
+ const warnings = results.filter((r) => r.success).flatMap((r) => r.warnings);
1581
+ const errors = results.filter(
1582
+ (r) => !r.success
1583
+ ).map((r) => ({ agent: r.specName, error: r.error }));
1584
+ return { written, warnings, errors };
1585
+ })
1586
+ );
1587
+ var writeAgentsForPlatformAsync = (platform2, specs, installPath) => Effect.runPromise(writeAgentsForPlatform(platform2, specs, installPath));
1588
+
1589
+ // src/lib/agents.ts
1590
+ init_esm_shims();
1591
+ import { access, constants } from "fs/promises";
1592
+ import { homedir } from "os";
1593
+ import { join } from "path";
1594
+ import process2 from "process";
1595
+ import { Effect as Effect2, pipe as pipe2 } from "effect";
1596
+ var home = homedir();
1597
+ var vscodeExtSettingsPath = (extensionId, filename) => {
1598
+ if (process2.platform === "darwin") {
1599
+ return join(
1600
+ home,
1601
+ "Library",
1602
+ "Application Support",
1603
+ "Code",
1322
1604
  "User",
1323
1605
  "globalStorage",
1324
1606
  extensionId,
@@ -1326,9 +1608,9 @@ var vscodeExtSettingsPath = (extensionId, filename) => {
1326
1608
  filename
1327
1609
  );
1328
1610
  }
1329
- if (process6.platform === "win32") {
1330
- const appData = process6.env.APPDATA ?? join2(home, "AppData", "Roaming");
1331
- return join2(
1611
+ if (process2.platform === "win32") {
1612
+ const appData = process2.env.APPDATA ?? join(home, "AppData", "Roaming");
1613
+ return join(
1332
1614
  appData,
1333
1615
  "Code",
1334
1616
  "User",
@@ -1338,7 +1620,7 @@ var vscodeExtSettingsPath = (extensionId, filename) => {
1338
1620
  filename
1339
1621
  );
1340
1622
  }
1341
- return join2(
1623
+ return join(
1342
1624
  home,
1343
1625
  ".config",
1344
1626
  "Code",
@@ -1350,8 +1632,8 @@ var vscodeExtSettingsPath = (extensionId, filename) => {
1350
1632
  );
1351
1633
  };
1352
1634
  var claudeDesktopConfigPath = () => {
1353
- if (process6.platform === "darwin") {
1354
- return join2(
1635
+ if (process2.platform === "darwin") {
1636
+ return join(
1355
1637
  home,
1356
1638
  "Library",
1357
1639
  "Application Support",
@@ -1359,46 +1641,48 @@ var claudeDesktopConfigPath = () => {
1359
1641
  "claude_desktop_config.json"
1360
1642
  );
1361
1643
  }
1362
- if (process6.platform === "win32") {
1363
- const appData = process6.env.APPDATA ?? join2(home, "AppData", "Roaming");
1364
- return join2(appData, "Claude", "claude_desktop_config.json");
1644
+ if (process2.platform === "win32") {
1645
+ const appData = process2.env.APPDATA ?? join(home, "AppData", "Roaming");
1646
+ return join(appData, "Claude", "claude_desktop_config.json");
1365
1647
  }
1366
- return join2(home, ".config", "Claude", "claude_desktop_config.json");
1648
+ return join(home, ".config", "Claude", "claude_desktop_config.json");
1367
1649
  };
1368
1650
  var AGENTS = [
1369
1651
  {
1370
1652
  id: "amp",
1371
1653
  name: "Amp",
1372
1654
  projectPath: ".agents/skills",
1373
- globalPath: join2(home, ".config", "agents", "skills"),
1655
+ globalPath: join(home, ".config", "agents", "skills"),
1374
1656
  mcpProjectConfigPath: ".amp/mcp.json",
1375
- mcpGlobalConfigPath: join2(home, ".amp", "mcp.json")
1657
+ mcpGlobalConfigPath: join(home, ".amp", "mcp.json")
1376
1658
  },
1377
1659
  {
1378
1660
  id: "kimi-cli",
1379
1661
  name: "Kimi Code CLI",
1380
1662
  projectPath: ".agents/skills",
1381
- globalPath: join2(home, ".config", "agents", "skills"),
1663
+ globalPath: join(home, ".config", "agents", "skills"),
1382
1664
  mcpProjectConfigPath: ".agents/mcp.json",
1383
- mcpGlobalConfigPath: join2(home, ".config", "agents", "mcp.json")
1665
+ mcpGlobalConfigPath: join(home, ".config", "agents", "mcp.json")
1384
1666
  },
1385
1667
  {
1386
1668
  id: "antigravity",
1387
1669
  name: "Antigravity",
1388
1670
  projectPath: ".agent/skills",
1389
- globalPath: join2(home, ".gemini", "antigravity", "global_skills"),
1671
+ globalPath: join(home, ".gemini", "antigravity", "global_skills"),
1390
1672
  mcpProjectConfigPath: ".agent/mcp.json",
1391
- mcpGlobalConfigPath: join2(home, ".gemini", "antigravity", "mcp.json")
1673
+ mcpGlobalConfigPath: join(home, ".gemini", "antigravity", "mcp.json")
1392
1674
  },
1393
1675
  {
1394
1676
  id: "claude-code",
1395
1677
  name: "Claude Code",
1396
1678
  projectPath: ".claude/skills",
1397
- globalPath: join2(home, ".claude", "skills"),
1679
+ globalPath: join(home, ".claude", "skills"),
1680
+ agentsProjectPath: ".claude/agents",
1681
+ agentsGlobalPath: join(home, ".claude", "agents"),
1398
1682
  projectMarkerPath: ".claude",
1399
- globalMarkerPath: join2(home, ".claude"),
1683
+ globalMarkerPath: join(home, ".claude"),
1400
1684
  rulesProjectPath: ".claude/rules",
1401
- rulesGlobalPath: join2(home, ".claude", "rules"),
1685
+ rulesGlobalPath: join(home, ".claude", "rules"),
1402
1686
  ruleFormat: "markdown-dir",
1403
1687
  mcpProjectConfigPath: ".mcp.json",
1404
1688
  mcpEntryStyle: "typed-stdio"
@@ -1414,15 +1698,15 @@ var AGENTS = [
1414
1698
  id: "moltbot",
1415
1699
  name: "Moltbot",
1416
1700
  projectPath: "skills",
1417
- globalPath: join2(home, ".moltbot", "skills"),
1701
+ globalPath: join(home, ".moltbot", "skills"),
1418
1702
  mcpProjectConfigPath: "mcp.json",
1419
- mcpGlobalConfigPath: join2(home, ".moltbot", "mcp.json")
1703
+ mcpGlobalConfigPath: join(home, ".moltbot", "mcp.json")
1420
1704
  },
1421
1705
  {
1422
1706
  id: "cline",
1423
1707
  name: "Cline",
1424
1708
  projectPath: ".cline/skills",
1425
- globalPath: join2(home, ".cline", "skills"),
1709
+ globalPath: join(home, ".cline", "skills"),
1426
1710
  rulesProjectPath: ".clinerules",
1427
1711
  ruleFormat: "append-single",
1428
1712
  mcpGlobalConfigPath: vscodeExtSettingsPath(
@@ -1435,73 +1719,73 @@ var AGENTS = [
1435
1719
  id: "codebuddy",
1436
1720
  name: "CodeBuddy",
1437
1721
  projectPath: ".codebuddy/skills",
1438
- globalPath: join2(home, ".codebuddy", "skills"),
1722
+ globalPath: join(home, ".codebuddy", "skills"),
1439
1723
  mcpProjectConfigPath: ".codebuddy/mcp.json",
1440
- mcpGlobalConfigPath: join2(home, ".codebuddy", "mcp.json")
1724
+ mcpGlobalConfigPath: join(home, ".codebuddy", "mcp.json")
1441
1725
  },
1442
1726
  {
1443
1727
  id: "codex",
1444
1728
  name: "Codex",
1445
1729
  projectPath: ".codex/skills",
1446
- globalPath: join2(home, ".codex", "skills"),
1730
+ globalPath: join(home, ".codex", "skills"),
1447
1731
  mcpProjectConfigPath: ".codex/mcp.json",
1448
- mcpGlobalConfigPath: join2(home, ".codex", "mcp.json")
1732
+ mcpGlobalConfigPath: join(home, ".codex", "mcp.json")
1449
1733
  },
1450
1734
  {
1451
1735
  id: "command-code",
1452
1736
  name: "Command Code",
1453
1737
  projectPath: ".commandcode/skills",
1454
- globalPath: join2(home, ".commandcode", "skills"),
1738
+ globalPath: join(home, ".commandcode", "skills"),
1455
1739
  mcpProjectConfigPath: ".commandcode/mcp.json",
1456
- mcpGlobalConfigPath: join2(home, ".commandcode", "mcp.json")
1740
+ mcpGlobalConfigPath: join(home, ".commandcode", "mcp.json")
1457
1741
  },
1458
1742
  {
1459
1743
  id: "continue",
1460
1744
  name: "Continue",
1461
1745
  projectPath: ".continue/skills",
1462
- globalPath: join2(home, ".continue", "skills"),
1746
+ globalPath: join(home, ".continue", "skills"),
1463
1747
  mcpProjectConfigPath: ".continue/mcp.json",
1464
- mcpGlobalConfigPath: join2(home, ".continue", "mcp.json")
1748
+ mcpGlobalConfigPath: join(home, ".continue", "mcp.json")
1465
1749
  },
1466
1750
  {
1467
1751
  id: "crush",
1468
1752
  name: "Crush",
1469
1753
  projectPath: ".crush/skills",
1470
- globalPath: join2(home, ".config", "crush", "skills"),
1754
+ globalPath: join(home, ".config", "crush", "skills"),
1471
1755
  mcpProjectConfigPath: ".crush/mcp.json",
1472
- mcpGlobalConfigPath: join2(home, ".config", "crush", "mcp.json")
1756
+ mcpGlobalConfigPath: join(home, ".config", "crush", "mcp.json")
1473
1757
  },
1474
1758
  {
1475
1759
  id: "cursor",
1476
1760
  name: "Cursor",
1477
1761
  projectPath: ".cursor/skills",
1478
- globalPath: join2(home, ".cursor", "skills"),
1762
+ globalPath: join(home, ".cursor", "skills"),
1479
1763
  rulesProjectPath: ".cursor/rules",
1480
1764
  ruleFormat: "mdc",
1481
1765
  mcpProjectConfigPath: ".cursor/mcp.json",
1482
- mcpGlobalConfigPath: join2(home, ".cursor", "mcp.json")
1766
+ mcpGlobalConfigPath: join(home, ".cursor", "mcp.json")
1483
1767
  },
1484
1768
  {
1485
1769
  id: "droid",
1486
1770
  name: "Droid",
1487
1771
  projectPath: ".factory/skills",
1488
- globalPath: join2(home, ".factory", "skills"),
1772
+ globalPath: join(home, ".factory", "skills"),
1489
1773
  mcpProjectConfigPath: ".factory/mcp.json",
1490
- mcpGlobalConfigPath: join2(home, ".factory", "mcp.json")
1774
+ mcpGlobalConfigPath: join(home, ".factory", "mcp.json")
1491
1775
  },
1492
1776
  {
1493
1777
  id: "gemini-cli",
1494
1778
  name: "Gemini CLI",
1495
1779
  projectPath: ".gemini/skills",
1496
- globalPath: join2(home, ".gemini", "skills"),
1780
+ globalPath: join(home, ".gemini", "skills"),
1497
1781
  mcpProjectConfigPath: ".gemini/mcp.json",
1498
- mcpGlobalConfigPath: join2(home, ".gemini", "mcp.json")
1782
+ mcpGlobalConfigPath: join(home, ".gemini", "mcp.json")
1499
1783
  },
1500
1784
  {
1501
1785
  id: "github-copilot",
1502
1786
  name: "GitHub Copilot",
1503
1787
  projectPath: ".github/skills",
1504
- globalPath: join2(home, ".copilot", "skills"),
1788
+ globalPath: join(home, ".copilot", "skills"),
1505
1789
  rulesProjectPath: ".github/copilot-instructions.md",
1506
1790
  ruleFormat: "append-single",
1507
1791
  mcpProjectConfigPath: ".vscode/mcp.json",
@@ -1512,22 +1796,22 @@ var AGENTS = [
1512
1796
  id: "goose",
1513
1797
  name: "Goose",
1514
1798
  projectPath: ".goose/skills",
1515
- globalPath: join2(home, ".config", "goose", "skills"),
1516
- mcpGlobalConfigPath: join2(home, ".config", "goose", "mcp.json")
1799
+ globalPath: join(home, ".config", "goose", "skills"),
1800
+ mcpGlobalConfigPath: join(home, ".config", "goose", "mcp.json")
1517
1801
  },
1518
1802
  {
1519
1803
  id: "junie",
1520
1804
  name: "Junie",
1521
1805
  projectPath: ".junie/skills",
1522
- globalPath: join2(home, ".junie", "skills"),
1806
+ globalPath: join(home, ".junie", "skills"),
1523
1807
  mcpProjectConfigPath: ".junie/mcp.json",
1524
- mcpGlobalConfigPath: join2(home, ".junie", "mcp.json")
1808
+ mcpGlobalConfigPath: join(home, ".junie", "mcp.json")
1525
1809
  },
1526
1810
  {
1527
1811
  id: "kilo",
1528
1812
  name: "Kilo Code",
1529
1813
  projectPath: ".kilocode/skills",
1530
- globalPath: join2(home, ".kilocode", "skills"),
1814
+ globalPath: join(home, ".kilocode", "skills"),
1531
1815
  mcpGlobalConfigPath: vscodeExtSettingsPath(
1532
1816
  "kilocode.kilo-code",
1533
1817
  "mcp_settings.json"
@@ -1538,81 +1822,83 @@ var AGENTS = [
1538
1822
  id: "kiro-cli",
1539
1823
  name: "Kiro CLI",
1540
1824
  projectPath: ".kiro/skills",
1541
- globalPath: join2(home, ".kiro", "skills"),
1825
+ globalPath: join(home, ".kiro", "skills"),
1542
1826
  mcpProjectConfigPath: ".kiro/mcp.json",
1543
- mcpGlobalConfigPath: join2(home, ".kiro", "mcp.json")
1827
+ mcpGlobalConfigPath: join(home, ".kiro", "mcp.json")
1544
1828
  },
1545
1829
  {
1546
1830
  id: "kode",
1547
1831
  name: "Kode",
1548
1832
  projectPath: ".kode/skills",
1549
- globalPath: join2(home, ".kode", "skills"),
1833
+ globalPath: join(home, ".kode", "skills"),
1550
1834
  mcpProjectConfigPath: ".kode/mcp.json",
1551
- mcpGlobalConfigPath: join2(home, ".kode", "mcp.json")
1835
+ mcpGlobalConfigPath: join(home, ".kode", "mcp.json")
1552
1836
  },
1553
1837
  {
1554
1838
  id: "mcpjam",
1555
1839
  name: "MCPJam",
1556
1840
  projectPath: ".mcpjam/skills",
1557
- globalPath: join2(home, ".mcpjam", "skills"),
1841
+ globalPath: join(home, ".mcpjam", "skills"),
1558
1842
  mcpProjectConfigPath: ".mcpjam/mcp.json",
1559
- mcpGlobalConfigPath: join2(home, ".mcpjam", "mcp.json")
1843
+ mcpGlobalConfigPath: join(home, ".mcpjam", "mcp.json")
1560
1844
  },
1561
1845
  {
1562
1846
  id: "mux",
1563
1847
  name: "Mux",
1564
1848
  projectPath: ".mux/skills",
1565
- globalPath: join2(home, ".mux", "skills"),
1849
+ globalPath: join(home, ".mux", "skills"),
1566
1850
  mcpProjectConfigPath: ".mux/mcp.json",
1567
- mcpGlobalConfigPath: join2(home, ".mux", "mcp.json")
1851
+ mcpGlobalConfigPath: join(home, ".mux", "mcp.json")
1568
1852
  },
1569
1853
  {
1570
1854
  id: "opencode",
1571
1855
  name: "OpenCode",
1572
1856
  projectPath: ".opencode/skills",
1573
- globalPath: join2(home, ".config", "opencode", "skills"),
1857
+ globalPath: join(home, ".config", "opencode", "skills"),
1858
+ agentsProjectPath: ".opencode/agents",
1859
+ agentsGlobalPath: join(home, ".config", "opencode", "agents"),
1574
1860
  mcpProjectConfigPath: ".opencode/mcp.json",
1575
- mcpGlobalConfigPath: join2(home, ".config", "opencode", "mcp.json")
1861
+ mcpGlobalConfigPath: join(home, ".config", "opencode", "mcp.json")
1576
1862
  },
1577
1863
  {
1578
1864
  id: "openhands",
1579
1865
  name: "OpenHands",
1580
1866
  projectPath: ".openhands/skills",
1581
- globalPath: join2(home, ".openhands", "skills"),
1867
+ globalPath: join(home, ".openhands", "skills"),
1582
1868
  mcpProjectConfigPath: ".openhands/mcp.json",
1583
- mcpGlobalConfigPath: join2(home, ".openhands", "mcp.json")
1869
+ mcpGlobalConfigPath: join(home, ".openhands", "mcp.json")
1584
1870
  },
1585
1871
  {
1586
1872
  id: "pi",
1587
1873
  name: "Pi",
1588
1874
  projectPath: ".pi/skills",
1589
- globalPath: join2(home, ".pi", "agent", "skills"),
1875
+ globalPath: join(home, ".pi", "agent", "skills"),
1590
1876
  mcpProjectConfigPath: ".pi/mcp.json",
1591
- mcpGlobalConfigPath: join2(home, ".pi", "agent", "mcp.json")
1877
+ mcpGlobalConfigPath: join(home, ".pi", "agent", "mcp.json")
1592
1878
  },
1593
1879
  {
1594
1880
  id: "qoder",
1595
1881
  name: "Qoder",
1596
1882
  projectPath: ".qoder/skills",
1597
- globalPath: join2(home, ".qoder", "skills"),
1883
+ globalPath: join(home, ".qoder", "skills"),
1598
1884
  mcpProjectConfigPath: ".qoder/mcp.json",
1599
- mcpGlobalConfigPath: join2(home, ".qoder", "mcp.json")
1885
+ mcpGlobalConfigPath: join(home, ".qoder", "mcp.json")
1600
1886
  },
1601
1887
  {
1602
1888
  id: "qwen-code",
1603
1889
  name: "Qwen Code",
1604
1890
  projectPath: ".qwen/skills",
1605
- globalPath: join2(home, ".qwen", "skills"),
1891
+ globalPath: join(home, ".qwen", "skills"),
1606
1892
  mcpProjectConfigPath: ".qwen/mcp.json",
1607
- mcpGlobalConfigPath: join2(home, ".qwen", "mcp.json")
1893
+ mcpGlobalConfigPath: join(home, ".qwen", "mcp.json")
1608
1894
  },
1609
1895
  {
1610
1896
  id: "roo",
1611
1897
  name: "Roo Code",
1612
1898
  projectPath: ".roo/skills",
1613
- globalPath: join2(home, ".roo", "skills"),
1899
+ globalPath: join(home, ".roo", "skills"),
1614
1900
  rulesProjectPath: ".roo/rules",
1615
- rulesGlobalPath: join2(home, ".roo", "rules"),
1901
+ rulesGlobalPath: join(home, ".roo", "rules"),
1616
1902
  ruleFormat: "markdown-dir",
1617
1903
  mcpGlobalConfigPath: vscodeExtSettingsPath(
1618
1904
  "rooveterinaryinc.roo-cline",
@@ -1624,42 +1910,42 @@ var AGENTS = [
1624
1910
  id: "trae",
1625
1911
  name: "Trae",
1626
1912
  projectPath: ".trae/skills",
1627
- globalPath: join2(home, ".trae", "skills"),
1913
+ globalPath: join(home, ".trae", "skills"),
1628
1914
  mcpProjectConfigPath: ".trae/mcp.json",
1629
- mcpGlobalConfigPath: join2(home, ".trae", "mcp.json")
1915
+ mcpGlobalConfigPath: join(home, ".trae", "mcp.json")
1630
1916
  },
1631
1917
  {
1632
1918
  id: "windsurf",
1633
1919
  name: "Windsurf",
1634
1920
  projectPath: ".windsurf/skills",
1635
- globalPath: join2(home, ".codeium", "windsurf", "skills"),
1921
+ globalPath: join(home, ".codeium", "windsurf", "skills"),
1636
1922
  rulesProjectPath: ".windsurfrules",
1637
1923
  ruleFormat: "append-single",
1638
- mcpGlobalConfigPath: join2(home, ".codeium", "windsurf", "mcp_config.json")
1924
+ mcpGlobalConfigPath: join(home, ".codeium", "windsurf", "mcp_config.json")
1639
1925
  },
1640
1926
  {
1641
1927
  id: "zencoder",
1642
1928
  name: "Zencoder",
1643
1929
  projectPath: ".zencoder/skills",
1644
- globalPath: join2(home, ".zencoder", "skills"),
1930
+ globalPath: join(home, ".zencoder", "skills"),
1645
1931
  mcpProjectConfigPath: ".zencoder/mcp.json",
1646
- mcpGlobalConfigPath: join2(home, ".zencoder", "mcp.json")
1932
+ mcpGlobalConfigPath: join(home, ".zencoder", "mcp.json")
1647
1933
  },
1648
1934
  {
1649
1935
  id: "neovate",
1650
1936
  name: "Neovate",
1651
1937
  projectPath: ".neovate/skills",
1652
- globalPath: join2(home, ".neovate", "skills"),
1938
+ globalPath: join(home, ".neovate", "skills"),
1653
1939
  mcpProjectConfigPath: ".neovate/mcp.json",
1654
- mcpGlobalConfigPath: join2(home, ".neovate", "mcp.json")
1940
+ mcpGlobalConfigPath: join(home, ".neovate", "mcp.json")
1655
1941
  },
1656
1942
  {
1657
1943
  id: "pochi",
1658
1944
  name: "Pochi",
1659
1945
  projectPath: ".pochi/skills",
1660
- globalPath: join2(home, ".pochi", "skills"),
1946
+ globalPath: join(home, ".pochi", "skills"),
1661
1947
  mcpProjectConfigPath: ".pochi/mcp.json",
1662
- mcpGlobalConfigPath: join2(home, ".pochi", "mcp.json")
1948
+ mcpGlobalConfigPath: join(home, ".pochi", "mcp.json")
1663
1949
  },
1664
1950
  {
1665
1951
  id: "zed",
@@ -1667,38 +1953,38 @@ var AGENTS = [
1667
1953
  projectPath: "",
1668
1954
  globalPath: "",
1669
1955
  projectMarkerPath: ".zed",
1670
- globalMarkerPath: join2(home, ".config", "zed"),
1956
+ globalMarkerPath: join(home, ".config", "zed"),
1671
1957
  rulesProjectPath: ".zed/rules",
1672
- rulesGlobalPath: join2(home, ".config", "zed", "rules"),
1958
+ rulesGlobalPath: join(home, ".config", "zed", "rules"),
1673
1959
  ruleFormat: "markdown-dir",
1674
- mcpGlobalConfigPath: join2(home, ".config", "zed", "settings.json"),
1960
+ mcpGlobalConfigPath: join(home, ".config", "zed", "settings.json"),
1675
1961
  mcpRootKey: "context_servers",
1676
1962
  mcpEntryStyle: "zed"
1677
1963
  }
1678
1964
  ];
1679
- var directoryExists = (path2) => pipe3(
1680
- Effect3.tryPromise({
1965
+ var directoryExists = (path2) => pipe2(
1966
+ Effect2.tryPromise({
1681
1967
  try: () => access(path2, constants.F_OK),
1682
1968
  catch: () => false
1683
1969
  }),
1684
- Effect3.map(() => true),
1685
- Effect3.orElseSucceed(() => false)
1970
+ Effect2.map(() => true),
1971
+ Effect2.orElseSucceed(() => false)
1686
1972
  );
1687
1973
  var detectAgents = (projectRoot) => {
1688
- const cwd = projectRoot ?? process6.cwd();
1689
- return pipe3(
1690
- Effect3.forEach(
1974
+ const cwd = projectRoot ?? process2.cwd();
1975
+ return pipe2(
1976
+ Effect2.forEach(
1691
1977
  AGENTS,
1692
- (agent) => pipe3(
1693
- Effect3.all({
1694
- hasProjectConfig: agent.projectPath ? directoryExists(join2(cwd, agent.projectPath)) : Effect3.succeed(false),
1695
- hasGlobalConfig: agent.globalPath ? directoryExists(agent.globalPath) : Effect3.succeed(false),
1696
- hasProjectMarker: agent.projectMarkerPath ? directoryExists(join2(cwd, agent.projectMarkerPath)) : Effect3.succeed(false),
1697
- hasGlobalMarker: agent.globalMarkerPath ? directoryExists(agent.globalMarkerPath) : Effect3.succeed(false),
1698
- hasRulesProjectConfig: agent.rulesProjectPath ? directoryExists(join2(cwd, agent.rulesProjectPath)) : Effect3.succeed(false),
1699
- hasRulesGlobalConfig: agent.rulesGlobalPath ? directoryExists(agent.rulesGlobalPath) : Effect3.succeed(false)
1978
+ (agent) => pipe2(
1979
+ Effect2.all({
1980
+ hasProjectConfig: agent.projectPath ? directoryExists(join(cwd, agent.projectPath)) : Effect2.succeed(false),
1981
+ hasGlobalConfig: agent.globalPath ? directoryExists(agent.globalPath) : Effect2.succeed(false),
1982
+ hasProjectMarker: agent.projectMarkerPath ? directoryExists(join(cwd, agent.projectMarkerPath)) : Effect2.succeed(false),
1983
+ hasGlobalMarker: agent.globalMarkerPath ? directoryExists(agent.globalMarkerPath) : Effect2.succeed(false),
1984
+ hasRulesProjectConfig: agent.rulesProjectPath ? directoryExists(join(cwd, agent.rulesProjectPath)) : Effect2.succeed(false),
1985
+ hasRulesGlobalConfig: agent.rulesGlobalPath ? directoryExists(agent.rulesGlobalPath) : Effect2.succeed(false)
1700
1986
  }),
1701
- Effect3.map(
1987
+ Effect2.map(
1702
1988
  ({
1703
1989
  hasProjectConfig,
1704
1990
  hasGlobalConfig,
@@ -1715,14 +2001,14 @@ var detectAgents = (projectRoot) => {
1715
2001
  ),
1716
2002
  { concurrency: "unbounded" }
1717
2003
  ),
1718
- Effect3.map(
1719
- (agents) => agents.filter((a) => a.hasProjectConfig || a.hasGlobalConfig)
2004
+ Effect2.map(
2005
+ (agents2) => agents2.filter((a) => a.hasProjectConfig || a.hasGlobalConfig)
1720
2006
  )
1721
2007
  );
1722
2008
  };
1723
2009
  var getAgentById = (id) => AGENTS.find((a) => a.id === id);
1724
- var detectAgentsAsync = (projectRoot) => Effect3.runPromise(detectAgents(projectRoot));
1725
- var directoryExistsAsync = (path2) => Effect3.runPromise(directoryExists(path2));
2010
+ var detectAgentsAsync = (projectRoot) => Effect2.runPromise(detectAgents(projectRoot));
2011
+ var directoryExistsAsync = (path2) => Effect2.runPromise(directoryExists(path2));
1726
2012
  var resolveInstallPath = (agent, options) => {
1727
2013
  if (options.global) {
1728
2014
  return agent.globalPath || void 0;
@@ -1730,8 +2016,18 @@ var resolveInstallPath = (agent, options) => {
1730
2016
  if (!agent.projectPath) {
1731
2017
  return void 0;
1732
2018
  }
1733
- const cwd = options.projectRoot ?? process6.cwd();
1734
- return join2(cwd, agent.projectPath);
2019
+ const cwd = options.projectRoot ?? process2.cwd();
2020
+ return join(cwd, agent.projectPath);
2021
+ };
2022
+ var resolveAgentsInstallPath = (agent, options) => {
2023
+ if (options.global) {
2024
+ return agent.agentsGlobalPath;
2025
+ }
2026
+ if (!agent.agentsProjectPath) {
2027
+ return void 0;
2028
+ }
2029
+ const cwd = options.projectRoot ?? process2.cwd();
2030
+ return join(cwd, agent.agentsProjectPath);
1735
2031
  };
1736
2032
  var resolveRulesInstallPath = (agent, options) => {
1737
2033
  if (options.global) {
@@ -1740,23 +2036,24 @@ var resolveRulesInstallPath = (agent, options) => {
1740
2036
  if (!agent.rulesProjectPath) {
1741
2037
  return void 0;
1742
2038
  }
1743
- const cwd = options.projectRoot ?? process6.cwd();
1744
- return join2(cwd, agent.rulesProjectPath);
2039
+ const cwd = options.projectRoot ?? process2.cwd();
2040
+ return join(cwd, agent.rulesProjectPath);
1745
2041
  };
1746
2042
  var hasMcpConfig = (agent) => Boolean(agent.mcpProjectConfigPath) || Boolean(agent.mcpGlobalConfigPath);
2043
+ var MCP_PACKAGE_SPEC = "@braid-cloud/mcp@0.1.11";
1747
2044
  var resolveMcpConfigPath = (agent, options) => {
1748
2045
  if (options.global) {
1749
2046
  return agent.mcpGlobalConfigPath;
1750
2047
  }
1751
2048
  if (agent.mcpProjectConfigPath) {
1752
- const cwd = options.projectRoot ?? process6.cwd();
1753
- return join2(cwd, agent.mcpProjectConfigPath);
2049
+ const cwd = options.projectRoot ?? process2.cwd();
2050
+ return join(cwd, agent.mcpProjectConfigPath);
1754
2051
  }
1755
2052
  return agent.mcpGlobalConfigPath;
1756
2053
  };
1757
2054
  var buildMcpEntry = (style, env) => {
1758
2055
  const command = "npx";
1759
- const args = ["-y", "@braid-cloud/mcp"];
2056
+ const args = ["-y", MCP_PACKAGE_SPEC];
1760
2057
  const envObj = Object.keys(env).length > 0 ? env : void 0;
1761
2058
  switch (style) {
1762
2059
  case "standard":
@@ -1771,178 +2068,1012 @@ var buildMcpEntry = (style, env) => {
1771
2068
  return envObj ? { command, args, env: envObj } : { command, args };
1772
2069
  }
1773
2070
  };
1774
- var detectMcpAgents = (projectRoot) => Effect3.runPromise(
1775
- pipe3(
2071
+ var detectMcpAgents = (projectRoot) => Effect2.runPromise(
2072
+ pipe2(
1776
2073
  detectAgents(projectRoot),
1777
- Effect3.map((detected) => detected.filter(hasMcpConfig))
2074
+ Effect2.map((detected) => detected.filter(hasMcpConfig))
1778
2075
  )
1779
2076
  );
1780
2077
 
1781
- // src/commands/install.ts
2078
+ // src/commands/agents.ts
1782
2079
  init_api();
1783
- init_config();
1784
-
1785
- // src/lib/metadata.ts
1786
- init_esm_shims();
1787
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
1788
- import { join as join3 } from "path";
1789
- import { Data as Data3, Effect as Effect4, pipe as pipe4 } from "effect";
1790
- var METADATA_FILENAME = ".braidskills-metadata.json";
1791
- var MetadataReadError = class extends Data3.TaggedError("MetadataReadError") {
2080
+ init_tui();
2081
+ var parseCsv = (input) => {
2082
+ if (!input) {
2083
+ return void 0;
2084
+ }
2085
+ const values = input.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
2086
+ return values.length > 0 ? values : void 0;
1792
2087
  };
1793
- var MetadataWriteError = class extends Data3.TaggedError("MetadataWriteError") {
2088
+ var writeJson = (value) => {
2089
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
2090
+ `);
1794
2091
  };
1795
- var getMetadataPath = (skillsDir) => join3(skillsDir, METADATA_FILENAME);
1796
- var readMetadata = (skillsDir) => {
1797
- const metadataPath = getMetadataPath(skillsDir);
1798
- return pipe4(
1799
- Effect4.tryPromise({
1800
- try: () => readFile2(metadataPath, "utf-8"),
1801
- catch: (e) => new MetadataReadError({ path: metadataPath, cause: e })
1802
- }),
1803
- Effect4.flatMap(
1804
- (content) => Effect4.try({
1805
- try: () => JSON.parse(content),
1806
- catch: () => ({ skills: [] })
1807
- })
1808
- ),
1809
- Effect4.orElseSucceed(() => ({ skills: [] }))
2092
+ var fail = (message) => {
2093
+ throw new Error(message);
2094
+ };
2095
+ var exitWithError = (error) => {
2096
+ const message = error instanceof Error ? error.message : String(error);
2097
+ log.error(message);
2098
+ process.exit(1);
2099
+ };
2100
+ var run = (command, args, options) => {
2101
+ const apiOptions = {
2102
+ ...options.server ? { serverUrl: options.server } : {},
2103
+ ...options.apiKey ? { apiKey: options.apiKey } : {}
2104
+ };
2105
+ return runLifecycleCommandAsync(
2106
+ {
2107
+ domain: "agents",
2108
+ command,
2109
+ args
2110
+ },
2111
+ apiOptions
1810
2112
  );
1811
2113
  };
1812
- var writeMetadata = (skillsDir, metadata) => {
1813
- const metadataPath = getMetadataPath(skillsDir);
1814
- return Effect4.tryPromise({
1815
- try: () => writeFile2(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"),
1816
- catch: (e) => new MetadataWriteError({ path: metadataPath, cause: e })
1817
- });
2114
+ var getAgentCandidate = (value) => {
2115
+ if (typeof value === "object" && value !== null && "agent" in value) {
2116
+ return value.agent;
2117
+ }
2118
+ return value;
1818
2119
  };
1819
- var updateMetadata = (skillsDir, newSkills) => pipe4(
1820
- readMetadata(skillsDir),
1821
- Effect4.map((existing) => {
1822
- const now = (/* @__PURE__ */ new Date()).toISOString();
1823
- const updatedSkills = [...existing.skills];
1824
- for (const skill of newSkills) {
1825
- const existingIndex = updatedSkills.findIndex(
1826
- (s) => s.name === skill.name
2120
+ var getMode = (mode) => {
2121
+ if (mode === "primary" || mode === "subagent" || mode === "all") {
2122
+ return mode;
2123
+ }
2124
+ return void 0;
2125
+ };
2126
+ var getRawSkills = (agent) => {
2127
+ if (Array.isArray(agent.skillNames)) {
2128
+ return agent.skillNames;
2129
+ }
2130
+ if (Array.isArray(agent.skills)) {
2131
+ return agent.skills;
2132
+ }
2133
+ return void 0;
2134
+ };
2135
+ var normalizeSpec = (value) => {
2136
+ const candidate = getAgentCandidate(value);
2137
+ if (typeof candidate !== "object" || candidate === null) {
2138
+ throw new Error("Invalid agent payload from API");
2139
+ }
2140
+ const agent = candidate;
2141
+ const name = typeof agent.name === "string" ? agent.name : void 0;
2142
+ const description = typeof agent.description === "string" ? agent.description : void 0;
2143
+ const promptFromPrompt = typeof agent.prompt === "string" ? agent.prompt : void 0;
2144
+ const promptFromContent = typeof agent.content === "string" ? agent.content : void 0;
2145
+ const prompt = promptFromPrompt ?? promptFromContent;
2146
+ if (!(name && description && prompt)) {
2147
+ throw new Error(
2148
+ "Agent payload missing required fields: name, description, prompt"
2149
+ );
2150
+ }
2151
+ const result = {
2152
+ name,
2153
+ description,
2154
+ prompt
2155
+ };
2156
+ const mode = getMode(agent.mode);
2157
+ if (mode) {
2158
+ result.mode = mode;
2159
+ }
2160
+ if (typeof agent.model === "string") {
2161
+ result.model = agent.model;
2162
+ }
2163
+ const rawSkills = getRawSkills(agent);
2164
+ if (rawSkills) {
2165
+ result.linkedSkillNames = rawSkills.filter(
2166
+ (item) => typeof item === "string"
2167
+ );
2168
+ }
2169
+ return result;
2170
+ };
2171
+ var resolveInstallTargets = async (options) => {
2172
+ if (options.agents) {
2173
+ const ids = parseCsv(options.agents) ?? [];
2174
+ const selected = ids.map((id) => getAgentById(id)).filter(
2175
+ (agent) => agent !== void 0
2176
+ );
2177
+ if (selected.length === 0) {
2178
+ fail("No valid target agents selected");
2179
+ }
2180
+ return selected;
2181
+ }
2182
+ const detected = await detectAgentsAsync();
2183
+ const filtered = detected.filter(
2184
+ (agent) => options.global ? agent.hasGlobalConfig : agent.hasProjectConfig
2185
+ );
2186
+ if (filtered.length === 0) {
2187
+ fail(
2188
+ "No supported local agent installations detected. Use --agents to select targets."
2189
+ );
2190
+ }
2191
+ return filtered;
2192
+ };
2193
+ async function agentsListCommand(options) {
2194
+ try {
2195
+ const result = await run("list", {}, options);
2196
+ if (options.json) {
2197
+ writeJson(result);
2198
+ return;
2199
+ }
2200
+ log.success("agents list completed");
2201
+ writeJson(result);
2202
+ } catch (error) {
2203
+ exitWithError(error);
2204
+ }
2205
+ }
2206
+ async function agentsGetCommand(options) {
2207
+ try {
2208
+ const id = options.id ?? fail("agents get requires --id");
2209
+ const result = await run("get", { id }, options);
2210
+ if (options.json) {
2211
+ writeJson(result);
2212
+ return;
2213
+ }
2214
+ log.success("agents get completed");
2215
+ writeJson(result);
2216
+ } catch (error) {
2217
+ exitWithError(error);
2218
+ }
2219
+ }
2220
+ async function agentsCreateCommand(options) {
2221
+ try {
2222
+ const name = options.name ?? fail("agents create requires --name");
2223
+ const description = options.description ?? fail("agents create requires --description");
2224
+ const prompt = options.prompt ?? fail("agents create requires --prompt");
2225
+ const result = await run(
2226
+ "create",
2227
+ {
2228
+ name,
2229
+ description,
2230
+ prompt,
2231
+ scope: options.scope,
2232
+ projectId: options.projectId,
2233
+ model: options.model,
2234
+ mode: options.mode,
2235
+ skills: parseCsv(options.skills)
2236
+ },
2237
+ options
2238
+ );
2239
+ if (options.json) {
2240
+ writeJson(result);
2241
+ return;
2242
+ }
2243
+ log.success("agents create completed");
2244
+ } catch (error) {
2245
+ exitWithError(error);
2246
+ }
2247
+ }
2248
+ async function agentsUpdateCommand(options) {
2249
+ try {
2250
+ const id = options.id ?? fail("agents update requires --id");
2251
+ const result = await run(
2252
+ "update",
2253
+ {
2254
+ id,
2255
+ name: options.name,
2256
+ description: options.description,
2257
+ prompt: options.prompt,
2258
+ model: options.model,
2259
+ mode: options.mode,
2260
+ skills: parseCsv(options.skills)
2261
+ },
2262
+ options
2263
+ );
2264
+ if (options.json) {
2265
+ writeJson(result);
2266
+ return;
2267
+ }
2268
+ log.success("agents update completed");
2269
+ } catch (error) {
2270
+ exitWithError(error);
2271
+ }
2272
+ }
2273
+ async function agentsRemoveCommand(options) {
2274
+ try {
2275
+ const id = options.id ?? fail("agents remove requires --id");
2276
+ if (!options.yes) {
2277
+ fail("agents remove requires --yes");
2278
+ }
2279
+ const result = await run("remove", { id, yes: true }, options);
2280
+ if (options.json) {
2281
+ writeJson(result);
2282
+ return;
2283
+ }
2284
+ log.success("agents remove completed");
2285
+ } catch (error) {
2286
+ exitWithError(error);
2287
+ }
2288
+ }
2289
+ async function agentsInstallCommand(options) {
2290
+ try {
2291
+ const id = options.id ?? fail("agents install requires --id");
2292
+ const payload = await run("get", { id }, options);
2293
+ const spec = normalizeSpec(payload);
2294
+ const targets = await resolveInstallTargets(options);
2295
+ const summary = [];
2296
+ for (const target of targets) {
2297
+ const installPath = resolveAgentsInstallPath(target, { global: options.global === true }) ?? resolveInstallPath(target, { global: options.global === true });
2298
+ if (!installPath) {
2299
+ summary.push({
2300
+ platform: target.id,
2301
+ written: 0,
2302
+ warnings: ["No install path available for this platform"],
2303
+ errors: []
2304
+ });
2305
+ continue;
2306
+ }
2307
+ const result = await writeAgentsForPlatformAsync(
2308
+ target,
2309
+ [spec],
2310
+ installPath
1827
2311
  );
1828
- const newEntry = {
1829
- name: skill.name,
1830
- source: skill.source,
1831
- version: skill.version,
1832
- installedAt: now,
1833
- serverUrl: skill.serverUrl
1834
- };
1835
- if (existingIndex >= 0) {
1836
- updatedSkills[existingIndex] = newEntry;
1837
- } else {
1838
- updatedSkills.push(newEntry);
2312
+ summary.push({
2313
+ platform: target.id,
2314
+ installPath,
2315
+ written: result.written.length,
2316
+ warnings: result.warnings,
2317
+ errors: result.errors
2318
+ });
2319
+ }
2320
+ if (options.json) {
2321
+ writeJson(summary);
2322
+ return;
2323
+ }
2324
+ for (const item of summary) {
2325
+ log.info(
2326
+ `${item.platform}: ${item.written} file(s) written${item.installPath ? ` to ${item.installPath}` : ""}`
2327
+ );
2328
+ for (const warning of item.warnings) {
2329
+ log.warn(` warning: ${warning}`);
2330
+ }
2331
+ for (const error of item.errors) {
2332
+ log.error(` error (${error.agent}): ${error.error}`);
1839
2333
  }
1840
2334
  }
1841
- return { skills: updatedSkills };
1842
- }),
1843
- Effect4.flatMap((metadata) => writeMetadata(skillsDir, metadata))
1844
- );
1845
- var removeFromMetadata = (skillsDir, skillName) => pipe4(
1846
- readMetadata(skillsDir),
1847
- Effect4.map((metadata) => ({
1848
- skills: metadata.skills.filter((s) => s.name !== skillName)
1849
- })),
1850
- Effect4.flatMap((metadata) => writeMetadata(skillsDir, metadata))
1851
- );
1852
- var readMetadataAsync = (skillsDir) => Effect4.runPromise(readMetadata(skillsDir));
1853
- var updateMetadataAsync = (skillsDir, newSkills) => Effect4.runPromise(updateMetadata(skillsDir, newSkills));
1854
- var removeFromMetadataAsync = (skillsDir, skillName) => Effect4.runPromise(removeFromMetadata(skillsDir, skillName));
2335
+ log.success("agents install completed");
2336
+ } catch (error) {
2337
+ exitWithError(error);
2338
+ }
2339
+ }
1855
2340
 
1856
- // src/lib/rule-writer.ts
2341
+ // src/commands/auth.ts
1857
2342
  init_esm_shims();
1858
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1859
- import { dirname as dirname2, resolve, sep } from "path";
1860
- import { Data as Data4, Effect as Effect5, pipe as pipe5 } from "effect";
1861
- var RuleWriteError = class extends Data4.TaggedError("RuleWriteError") {
2343
+ init_api();
2344
+ init_config();
2345
+ import process6 from "process";
2346
+
2347
+ // src/lib/device-auth.ts
2348
+ init_esm_shims();
2349
+ import { execFile } from "child_process";
2350
+ import { hostname, platform } from "os";
2351
+ var TRAILING_SLASHES = /\/+$/;
2352
+ var DeviceAuthTimeoutError = class extends Error {
2353
+ constructor() {
2354
+ super("Device authorization timed out");
2355
+ this.name = "DeviceAuthTimeoutError";
2356
+ }
1862
2357
  };
1863
- var createDirectory = (dir) => Effect5.tryPromise({
1864
- try: () => mkdir2(dir, { recursive: true }),
1865
- catch: (e) => new RuleWriteError({ path: dir, operation: "mkdir", cause: e })
1866
- });
1867
- var writeTextFile = (fullPath, content) => Effect5.tryPromise({
1868
- try: () => writeFile3(fullPath, content, "utf-8"),
1869
- catch: (e) => new RuleWriteError({ path: fullPath, operation: "write", cause: e })
1870
- });
1871
- var readTextFile = (fullPath) => Effect5.tryPromise({
1872
- try: () => readFile3(fullPath, "utf-8"),
1873
- catch: (e) => new RuleWriteError({ path: fullPath, operation: "read", cause: e })
1874
- });
1875
- var assertRulePathWithinBase = (basePath, ruleName) => {
1876
- const resolvedBase = resolve(basePath);
1877
- const resolvedFull = resolve(basePath, ruleName);
1878
- if (resolvedFull !== resolvedBase && !resolvedFull.startsWith(resolvedBase + sep)) {
1879
- return Effect5.fail(
1880
- new RuleWriteError({
1881
- path: ruleName,
1882
- operation: "write",
1883
- cause: new Error(
1884
- `Path traversal detected: "${ruleName}" resolves outside base directory`
1885
- )
1886
- })
1887
- );
2358
+ var DeviceAuthDeniedError = class extends Error {
2359
+ constructor() {
2360
+ super("Device authorization was denied");
2361
+ this.name = "DeviceAuthDeniedError";
1888
2362
  }
1889
- return Effect5.succeed(resolvedFull);
1890
2363
  };
1891
- var BRAID_SECTION_START = "<!-- braid:rules:start -->";
1892
- var BRAID_SECTION_END = "<!-- braid:rules:end -->";
1893
- var YAML_SPECIAL_CHARS = /[\n\r:#{}[\],&*?|>!'"%@`]/;
1894
- function escapeYamlValue(value) {
1895
- if (YAML_SPECIAL_CHARS.test(value) || value.startsWith(" ") || value.endsWith(" ")) {
1896
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
2364
+ var DeviceAuthExpiredError = class extends Error {
2365
+ constructor() {
2366
+ super("Device authorization code has expired");
2367
+ this.name = "DeviceAuthExpiredError";
1897
2368
  }
1898
- return value;
2369
+ };
2370
+ var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
2371
+ async function fetchAuthConfig(serverUrl) {
2372
+ const url = `${serverUrl.replace(TRAILING_SLASHES, "")}/api/cli/auth-config`;
2373
+ const response = await fetch(url);
2374
+ if (!response.ok) {
2375
+ const body = await response.text();
2376
+ throw new Error(
2377
+ `Failed to fetch auth config (${response.status}): ${body}`
2378
+ );
2379
+ }
2380
+ return response.json();
1899
2381
  }
1900
- function buildMdcContent(rule) {
1901
- const lines = ["---"];
1902
- lines.push(`description: ${escapeYamlValue(rule.title)}`);
1903
- lines.push("alwaysApply: true");
1904
- lines.push("---");
1905
- lines.push("");
1906
- lines.push(`# ${rule.title}`);
1907
- lines.push("");
1908
- lines.push(rule.content);
1909
- return lines.join("\n");
2382
+ async function initiateDeviceAuth(convexSiteUrl, deviceInfo) {
2383
+ const url = `${convexSiteUrl.replace(TRAILING_SLASHES, "")}/api/cli/device/authorize`;
2384
+ const response = await fetch(url, {
2385
+ method: "POST",
2386
+ headers: { "Content-Type": "application/json" },
2387
+ body: JSON.stringify(deviceInfo)
2388
+ });
2389
+ if (!response.ok) {
2390
+ const body = await response.text();
2391
+ throw new Error(
2392
+ `Failed to initiate device authorization (${response.status}): ${body}`
2393
+ );
2394
+ }
2395
+ return response.json();
1910
2396
  }
1911
- function buildAppendContent(rules) {
1912
- const lines = [BRAID_SECTION_START, ""];
1913
- for (const rule of rules) {
1914
- lines.push(`## ${rule.title}`);
1915
- lines.push("");
1916
- lines.push(rule.content);
1917
- lines.push("");
2397
+ async function pollForSession(convexSiteUrl, deviceCode, interval, expiresIn, timeoutSeconds) {
2398
+ const tokenUrl = `${convexSiteUrl.replace(TRAILING_SLASHES, "")}/api/cli/device/token`;
2399
+ const deadline = Date.now() + Math.min(expiresIn, timeoutSeconds) * 1e3;
2400
+ let currentInterval = interval;
2401
+ while (Date.now() < deadline) {
2402
+ await sleep(currentInterval * 1e3);
2403
+ const response = await fetch(tokenUrl, {
2404
+ method: "POST",
2405
+ headers: { "Content-Type": "application/json" },
2406
+ body: JSON.stringify({ device_code: deviceCode })
2407
+ });
2408
+ if (response.ok) {
2409
+ const result = await response.json();
2410
+ return {
2411
+ sessionToken: result.session_token,
2412
+ expiresAt: result.expires_at,
2413
+ user: result.user
2414
+ };
2415
+ }
2416
+ let errorCode;
2417
+ try {
2418
+ const errorBody = await response.json();
2419
+ errorCode = errorBody.error;
2420
+ } catch {
2421
+ throw new Error(`Device auth polling error: HTTP ${response.status}`);
2422
+ }
2423
+ if (errorCode === "authorization_pending") {
2424
+ continue;
2425
+ }
2426
+ if (errorCode === "slow_down") {
2427
+ currentInterval += 5;
2428
+ continue;
2429
+ }
2430
+ if (errorCode === "expired_token") {
2431
+ throw new DeviceAuthExpiredError();
2432
+ }
2433
+ if (errorCode === "access_denied") {
2434
+ throw new DeviceAuthDeniedError();
2435
+ }
2436
+ throw new Error(
2437
+ `Device auth polling error: ${errorCode ?? `HTTP ${response.status}`}`
2438
+ );
1918
2439
  }
1919
- lines.push(BRAID_SECTION_END);
1920
- return lines.join("\n");
2440
+ throw new DeviceAuthTimeoutError();
1921
2441
  }
1922
- var writeMdcRules = (basePath, rules) => pipe5(
1923
- createDirectory(basePath),
1924
- Effect5.flatMap(
1925
- () => Effect5.forEach(
1926
- rules,
1927
- (rule) => pipe5(
1928
- assertRulePathWithinBase(basePath, `${rule.name}.mdc`),
1929
- Effect5.flatMap(
1930
- (filePath) => writeTextFile(filePath, buildMdcContent(rule))
1931
- )
1932
- ),
1933
- { concurrency: "unbounded" }
1934
- )
1935
- ),
1936
- Effect5.asVoid
2442
+ async function fetchSessionInfo(convexSiteUrl, sessionToken) {
2443
+ const url = `${convexSiteUrl.replace(TRAILING_SLASHES, "")}/api/cli/sessions/me`;
2444
+ const response = await fetch(url, {
2445
+ method: "GET",
2446
+ headers: {
2447
+ Authorization: `Bearer ${sessionToken}`
2448
+ }
2449
+ });
2450
+ if (response.status === 404) {
2451
+ return null;
2452
+ }
2453
+ if (!response.ok) {
2454
+ const body = await response.text();
2455
+ throw new Error(
2456
+ `Failed to fetch session info (${response.status}): ${body}`
2457
+ );
2458
+ }
2459
+ return response.json();
2460
+ }
2461
+ async function revokeSession(convexSiteUrl, sessionToken) {
2462
+ const url = `${convexSiteUrl.replace(TRAILING_SLASHES, "")}/api/cli/sessions/me`;
2463
+ const response = await fetch(url, {
2464
+ method: "DELETE",
2465
+ headers: {
2466
+ Authorization: `Bearer ${sessionToken}`
2467
+ }
2468
+ });
2469
+ if (response.status === 404) {
2470
+ return false;
2471
+ }
2472
+ if (!response.ok) {
2473
+ const body = await response.text();
2474
+ throw new Error(`Failed to revoke session (${response.status}): ${body}`);
2475
+ }
2476
+ return true;
2477
+ }
2478
+ var noop = () => {
2479
+ };
2480
+ function openBrowser(url) {
2481
+ const currentPlatform = platform();
2482
+ if (currentPlatform === "darwin") {
2483
+ execFile("open", [url], noop);
2484
+ } else if (currentPlatform === "win32") {
2485
+ execFile("cmd", ["/c", "start", "", url], noop);
2486
+ } else {
2487
+ execFile("xdg-open", [url], noop);
2488
+ }
2489
+ }
2490
+ function getDeviceInfo() {
2491
+ return {
2492
+ deviceName: hostname(),
2493
+ deviceOs: platform(),
2494
+ deviceHostname: hostname()
2495
+ };
2496
+ }
2497
+
2498
+ // src/commands/auth.ts
2499
+ init_tui();
2500
+ var SESSION_TOKEN_PREFIX = "brs_";
2501
+ var TRAILING_SLASHES2 = /\/+$/;
2502
+ var DEFAULT_TIMEOUT_SECONDS = 300;
2503
+ async function configureDefaultScopeAsync(serverUrl) {
2504
+ const shouldConfigureScope = await confirm({
2505
+ message: "Configure organization and scope defaults now?",
2506
+ initialValue: true
2507
+ });
2508
+ if (isCancel(shouldConfigureScope) || !shouldConfigureScope) {
2509
+ return;
2510
+ }
2511
+ const { scopeCommand: scopeCommand2 } = await Promise.resolve().then(() => (init_scope(), scope_exports));
2512
+ const config = await loadMergedConfigAsync();
2513
+ await scopeCommand2({
2514
+ file: "user",
2515
+ server: serverUrl,
2516
+ ...config.token ? { apiKey: config.token } : {}
2517
+ });
2518
+ }
2519
+ async function manualTokenFlow(apiKey, serverUrl, options) {
2520
+ const authSpinner = spinner();
2521
+ let authSpinnerActive = true;
2522
+ const stopAuthSpinner = (message) => {
2523
+ if (!authSpinnerActive) {
2524
+ return;
2525
+ }
2526
+ authSpinner.stop(message);
2527
+ authSpinnerActive = false;
2528
+ };
2529
+ authSpinner.start("Validating API key...");
2530
+ try {
2531
+ const isValid = await validateApiKeyAsync(apiKey, serverUrl);
2532
+ if (!isValid) {
2533
+ stopAuthSpinner("Invalid API key");
2534
+ log.error(
2535
+ "The API key could not be validated. Please check your key and try again."
2536
+ );
2537
+ process6.exit(1);
2538
+ }
2539
+ await setApiKeyAsync(apiKey);
2540
+ const existingUserConfig = await loadUserConfigAsync();
2541
+ if (existingUserConfig?.token) {
2542
+ await saveUserConfigAsync({ ...existingUserConfig, token: apiKey });
2543
+ }
2544
+ stopAuthSpinner("API key validated and saved");
2545
+ if (options.scope !== false) {
2546
+ await configureDefaultScopeAsync(serverUrl);
2547
+ }
2548
+ log.success(`Config saved to ${CONFIG_FILE}`);
2549
+ outro(
2550
+ "You're authenticated! Run 'braid install' to use your saved scope (or pass '--profile <name>' to override)."
2551
+ );
2552
+ } catch (error) {
2553
+ stopAuthSpinner("Validation failed");
2554
+ const message = error instanceof Error ? error.message : String(error);
2555
+ log.error(`Failed to validate API key: ${message}`);
2556
+ process6.exit(1);
2557
+ }
2558
+ }
2559
+ function handlePollingError(error) {
2560
+ if (error instanceof DeviceAuthTimeoutError) {
2561
+ log.error("The device authorization timed out. Please try again.");
2562
+ } else if (error instanceof DeviceAuthExpiredError) {
2563
+ log.error("The device code has expired. Please try again.");
2564
+ } else if (error instanceof DeviceAuthDeniedError) {
2565
+ log.error("The authorization request was denied.");
2566
+ } else {
2567
+ const message = error instanceof Error ? error.message : String(error);
2568
+ log.error(`Authentication failed: ${message}`);
2569
+ }
2570
+ process6.exit(1);
2571
+ }
2572
+ async function deviceFlow(serverUrl, timeoutSeconds, options) {
2573
+ const configSpinner = spinner();
2574
+ configSpinner.start("Fetching auth configuration...");
2575
+ let authConfig;
2576
+ try {
2577
+ authConfig = await fetchAuthConfig(serverUrl);
2578
+ configSpinner.stop("Auth configuration loaded");
2579
+ } catch (error) {
2580
+ configSpinner.stop("Failed to fetch auth configuration");
2581
+ const message = error instanceof Error ? error.message : String(error);
2582
+ log.error(`Could not connect to server: ${message}`);
2583
+ process6.exit(1);
2584
+ }
2585
+ const deviceSpinner = spinner();
2586
+ deviceSpinner.start("Initiating device authorization...");
2587
+ let deviceAuth;
2588
+ try {
2589
+ deviceAuth = await initiateDeviceAuth(
2590
+ authConfig.convexSiteUrl,
2591
+ getDeviceInfo()
2592
+ );
2593
+ deviceSpinner.stop("Device authorization initiated");
2594
+ } catch (error) {
2595
+ deviceSpinner.stop("Failed to initiate device authorization");
2596
+ const message = error instanceof Error ? error.message : String(error);
2597
+ log.error(`Device authorization failed: ${message}`);
2598
+ process6.exit(1);
2599
+ }
2600
+ const verificationUrl = `${serverUrl.replace(TRAILING_SLASHES2, "")}/cli/authorize?user_code=${deviceAuth.user_code}`;
2601
+ log.info("");
2602
+ log.info(" To authenticate, visit:");
2603
+ log.info(` ${verificationUrl}`);
2604
+ log.info("");
2605
+ log.info(` Your code: ${deviceAuth.user_code}`);
2606
+ log.info("");
2607
+ openBrowser(verificationUrl);
2608
+ const expiresMinutes = Math.ceil(
2609
+ Math.min(deviceAuth.expires_in, timeoutSeconds) / 60
2610
+ );
2611
+ const pollSpinner = spinner();
2612
+ pollSpinner.start(
2613
+ `Waiting for authentication... (expires in ${expiresMinutes} minutes)`
2614
+ );
2615
+ let session;
2616
+ try {
2617
+ session = await pollForSession(
2618
+ authConfig.convexSiteUrl,
2619
+ deviceAuth.device_code,
2620
+ deviceAuth.interval,
2621
+ deviceAuth.expires_in,
2622
+ timeoutSeconds
2623
+ );
2624
+ pollSpinner.stop(`Authenticated as ${session.user.email}`);
2625
+ } catch (error) {
2626
+ pollSpinner.stop("Authentication failed");
2627
+ handlePollingError(error);
2628
+ }
2629
+ await setApiKeyAsync(session.sessionToken);
2630
+ if (options.scope !== false) {
2631
+ await configureDefaultScopeAsync(authConfig.convexSiteUrl);
2632
+ }
2633
+ log.success(`Session saved to ${CONFIG_FILE}`);
2634
+ outro(
2635
+ "You're authenticated! Run 'braid install' to use your saved scope (or pass '--profile <name>' to override)."
2636
+ );
2637
+ }
2638
+ async function authCommand(options) {
2639
+ intro("braid auth");
2640
+ const serverUrl = options.server ?? "https://braid.cloud";
2641
+ const config = await loadMergedConfigAsync();
2642
+ if (config.token) {
2643
+ const shouldReplace = await confirm({
2644
+ message: "An API key is already configured. Replace it?",
2645
+ initialValue: false
2646
+ });
2647
+ if (isCancel(shouldReplace) || !shouldReplace) {
2648
+ outro("Auth cancelled.");
2649
+ return;
2650
+ }
2651
+ }
2652
+ if (options.token) {
2653
+ await manualTokenFlow(options.token, serverUrl, options);
2654
+ return;
2655
+ }
2656
+ const timeoutSeconds = options.timeout ? Number.parseInt(options.timeout, 10) : DEFAULT_TIMEOUT_SECONDS;
2657
+ if (Number.isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
2658
+ log.error("Invalid timeout value. Must be a positive number of seconds.");
2659
+ process6.exit(1);
2660
+ }
2661
+ await deviceFlow(serverUrl, timeoutSeconds, options);
2662
+ }
2663
+ async function displayTokenSource(masked) {
2664
+ if (process6.env.BRAID_API_KEY) {
2665
+ log.info(`Authenticated with key: ${masked}`);
2666
+ log.info("Source: BRAID_API_KEY environment variable");
2667
+ return;
2668
+ }
2669
+ const userConfigPath = await findUserConfigFileAsync();
2670
+ log.info(`Authenticated with key: ${masked}`);
2671
+ log.info(`Source: ${userConfigPath ?? CONFIG_FILE}`);
2672
+ }
2673
+ async function displayProjectConfig() {
2674
+ const projectConfigPath = await findProjectConfigFileAsync();
2675
+ if (projectConfigPath) {
2676
+ log.info(`Project config: ${projectConfigPath}`);
2677
+ }
2678
+ }
2679
+ function displayResolvedSettings(config) {
2680
+ const hasOrgProjects = config.orgProjects && config.orgProjects.length > 0;
2681
+ const hasPersonalProjects = config.personalProjects && config.personalProjects.length > 0;
2682
+ if (!(config.profile || hasOrgProjects || hasPersonalProjects)) {
2683
+ return;
2684
+ }
2685
+ log.info("");
2686
+ if (config.profile) {
2687
+ log.info(`Default profile: ${config.profile}`);
2688
+ }
2689
+ if (hasOrgProjects) {
2690
+ log.info(`Org projects: ${config.orgProjects?.join(", ")}`);
2691
+ }
2692
+ if (hasPersonalProjects) {
2693
+ log.info(`Personal projects: ${config.personalProjects?.join(", ")}`);
2694
+ }
2695
+ if (config.serverUrl !== "https://braid.cloud") {
2696
+ log.info(`Server: ${config.serverUrl}`);
2697
+ }
2698
+ }
2699
+ async function displaySessionInfo(token) {
2700
+ const serverUrl = await getServerUrlAsync();
2701
+ try {
2702
+ const authConfig = await fetchAuthConfig(serverUrl);
2703
+ const info = await fetchSessionInfo(authConfig.convexSiteUrl, token);
2704
+ if (!info) {
2705
+ log.warn(
2706
+ "Session is expired or revoked. Run 'braid auth' to re-authenticate."
2707
+ );
2708
+ return;
2709
+ }
2710
+ log.info(
2711
+ `Authenticated as: ${info.email}${info.name ? ` (${info.name})` : ""}`
2712
+ );
2713
+ log.info("Session type: CLI session");
2714
+ if (info.deviceName || info.deviceHostname) {
2715
+ log.info(
2716
+ `Device: ${info.deviceName ?? info.deviceHostname ?? "unknown"}`
2717
+ );
2718
+ }
2719
+ const expiresDate = new Date(info.expiresAt);
2720
+ const daysRemaining = Math.ceil(
2721
+ (info.expiresAt - Date.now()) / (24 * 60 * 60 * 1e3)
2722
+ );
2723
+ log.info(
2724
+ `Expires: ${expiresDate.toLocaleDateString()} (${daysRemaining} days)`
2725
+ );
2726
+ if (info.lastActiveAt) {
2727
+ log.info(`Last active: ${new Date(info.lastActiveAt).toLocaleString()}`);
2728
+ }
2729
+ } catch (error) {
2730
+ const message = error instanceof Error ? error.message : String(error);
2731
+ log.warn(`Could not fetch session details: ${message}`);
2732
+ const masked = `${token.slice(0, 7)}...${token.slice(-4)}`;
2733
+ log.info(`Session token: ${masked}`);
2734
+ }
2735
+ }
2736
+ async function authStatusCommand() {
2737
+ const config = await loadMergedConfigAsync();
2738
+ if (!config.token) {
2739
+ log.warn("Not authenticated. Run 'braid auth' to configure your API key.");
2740
+ log.info(
2741
+ `Or create a ${USER_CONFIG_FILENAME} file with: { "token": "br_xxx" }`
2742
+ );
2743
+ return;
2744
+ }
2745
+ if (config.token.startsWith(SESSION_TOKEN_PREFIX)) {
2746
+ await displaySessionInfo(config.token);
2747
+ await displayProjectConfig();
2748
+ displayResolvedSettings(config);
2749
+ return;
2750
+ }
2751
+ const masked = `${config.token.slice(0, 7)}...${config.token.slice(-4)}`;
2752
+ await displayTokenSource(masked);
2753
+ await displayProjectConfig();
2754
+ displayResolvedSettings(config);
2755
+ }
2756
+ async function revokeSessionIfActive(token) {
2757
+ const serverUrl = await getServerUrlAsync();
2758
+ try {
2759
+ const authConfig = await fetchAuthConfig(serverUrl);
2760
+ await revokeSession(authConfig.convexSiteUrl, token);
2761
+ } catch {
2762
+ return;
2763
+ }
2764
+ }
2765
+ async function authLogoutCommand() {
2766
+ const config = await loadMergedConfigAsync();
2767
+ const { clearApiKeyAsync: clearApiKeyAsync2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2768
+ if (config.token?.startsWith(SESSION_TOKEN_PREFIX)) {
2769
+ await revokeSessionIfActive(config.token);
2770
+ }
2771
+ await clearApiKeyAsync2();
2772
+ log.success("Logged out. API key removed from config.");
2773
+ }
2774
+
2775
+ // src/commands/discover.ts
2776
+ init_esm_shims();
2777
+ init_api();
2778
+ init_config();
2779
+ init_tui();
2780
+ import process7 from "process";
2781
+ var parseCsv2 = (input) => {
2782
+ if (!input) {
2783
+ return void 0;
2784
+ }
2785
+ const values = input.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
2786
+ return values.length > 0 ? values : void 0;
2787
+ };
2788
+ var writeJson2 = (value) => {
2789
+ process7.stdout.write(`${JSON.stringify(value, null, 2)}
2790
+ `);
2791
+ };
2792
+ var exitWithError2 = (error) => {
2793
+ const message = error instanceof Error ? error.message : String(error);
2794
+ log.error(message);
2795
+ process7.exit(1);
2796
+ };
2797
+ var applyCommonOptions = (base, options) => ({
2798
+ ...base,
2799
+ ...options.server ? { serverUrl: options.server } : {},
2800
+ ...options.apiKey ? { apiKey: options.apiKey } : {}
2801
+ });
2802
+ var applyOptionalString = (target, key, value) => {
2803
+ if (value) {
2804
+ target[key] = value;
2805
+ }
2806
+ };
2807
+ var applyOptionalArray = (target, key, value) => {
2808
+ if (value && value.length > 0) {
2809
+ target[key] = value;
2810
+ }
2811
+ };
2812
+ var applyOptionalBoolean = (target, key, value) => {
2813
+ if (value !== void 0) {
2814
+ target[key] = value;
2815
+ }
2816
+ };
2817
+ async function projectsListCommand(options) {
2818
+ try {
2819
+ const result = await fetchScopeOptionsAsync(
2820
+ applyCommonOptions({}, options)
2821
+ );
2822
+ if (options.json) {
2823
+ writeJson2(result);
2824
+ return;
2825
+ }
2826
+ log.info("Personal projects:");
2827
+ if (result.personalProjects.length === 0) {
2828
+ log.info(" (none)");
2829
+ }
2830
+ for (const project of result.personalProjects) {
2831
+ log.info(` ${project.id} ${project.name}`);
2832
+ }
2833
+ for (const orgProjects of result.orgProjects) {
2834
+ log.info(`
2835
+ Organization: ${orgProjects.orgName} (${orgProjects.orgId})`);
2836
+ if (orgProjects.projects.length === 0) {
2837
+ log.info(" (none)");
2838
+ continue;
2839
+ }
2840
+ for (const project of orgProjects.projects) {
2841
+ log.info(` ${project.id} ${project.name}`);
2842
+ }
2843
+ }
2844
+ } catch (error) {
2845
+ exitWithError2(error);
2846
+ }
2847
+ }
2848
+ var buildRulesRequest = async (options) => {
2849
+ const config = await loadMergedConfigAsync();
2850
+ const orgProjects = parseCsv2(options.orgProjects) ?? config.orgProjects;
2851
+ const personalProjects = parseCsv2(options.personalProjects) ?? config.personalProjects;
2852
+ const request = applyCommonOptions({}, options);
2853
+ applyOptionalString(request, "orgId", options.orgId ?? config.org);
2854
+ applyOptionalArray(request, "orgProjects", orgProjects);
2855
+ applyOptionalArray(request, "personalProjects", personalProjects);
2856
+ applyOptionalBoolean(request, "includeUserGlobal", options.includeUserGlobal);
2857
+ applyOptionalBoolean(request, "includeOrgGlobal", options.includeOrgGlobal);
2858
+ return request;
2859
+ };
2860
+ async function rulesListCommand(options) {
2861
+ try {
2862
+ const result = await fetchRuleOptionsAsync(
2863
+ await buildRulesRequest(options)
2864
+ );
2865
+ if (options.json) {
2866
+ writeJson2(result);
2867
+ return;
2868
+ }
2869
+ if (result.rules.length === 0) {
2870
+ log.info("No rules found.");
2871
+ return;
2872
+ }
2873
+ for (const rule of result.rules) {
2874
+ log.info(`${rule.id} ${rule.title}`);
2875
+ }
2876
+ } catch (error) {
2877
+ exitWithError2(error);
2878
+ }
2879
+ }
2880
+ var buildSkillsRequest = async (options) => {
2881
+ const config = await loadMergedConfigAsync();
2882
+ const orgProjects = parseCsv2(options.orgProjects) ?? config.orgProjects;
2883
+ const personalProjects = parseCsv2(options.personalProjects) ?? config.personalProjects;
2884
+ const request = applyCommonOptions({}, options);
2885
+ applyOptionalString(request, "profile", options.profile ?? config.profile);
2886
+ applyOptionalArray(request, "orgProjects", orgProjects);
2887
+ applyOptionalArray(request, "personalProjects", personalProjects);
2888
+ applyOptionalBoolean(request, "includeUserGlobal", options.includeUserGlobal);
2889
+ applyOptionalBoolean(request, "includeOrgGlobal", options.includeOrgGlobal);
2890
+ return request;
2891
+ };
2892
+ async function skillsListCommand(options) {
2893
+ try {
2894
+ const result = await fetchSkillsAsync(await buildSkillsRequest(options));
2895
+ if (options.json) {
2896
+ writeJson2(result);
2897
+ return;
2898
+ }
2899
+ if (result.skills.length === 0) {
2900
+ log.info("No skills found.");
2901
+ return;
2902
+ }
2903
+ for (const skill of result.skills) {
2904
+ log.info(`${skill.name}`);
2905
+ }
2906
+ } catch (error) {
2907
+ exitWithError2(error);
2908
+ }
2909
+ }
2910
+
2911
+ // src/commands/install.ts
2912
+ init_esm_shims();
2913
+ init_api();
2914
+ init_config();
2915
+
2916
+ // src/lib/metadata.ts
2917
+ init_esm_shims();
2918
+ import { readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
2919
+ import { join as join3 } from "path";
2920
+ import { Data as Data4, Effect as Effect5, pipe as pipe5 } from "effect";
2921
+ var METADATA_FILENAME = ".braidskills-metadata.json";
2922
+ var MetadataReadError = class extends Data4.TaggedError("MetadataReadError") {
2923
+ };
2924
+ var MetadataWriteError = class extends Data4.TaggedError("MetadataWriteError") {
2925
+ };
2926
+ var getMetadataPath = (skillsDir) => join3(skillsDir, METADATA_FILENAME);
2927
+ var readMetadata = (skillsDir) => {
2928
+ const metadataPath = getMetadataPath(skillsDir);
2929
+ return pipe5(
2930
+ Effect5.tryPromise({
2931
+ try: () => readFile2(metadataPath, "utf-8"),
2932
+ catch: (e) => new MetadataReadError({ path: metadataPath, cause: e })
2933
+ }),
2934
+ Effect5.flatMap(
2935
+ (content) => Effect5.try({
2936
+ try: () => JSON.parse(content),
2937
+ catch: () => ({ skills: [] })
2938
+ })
2939
+ ),
2940
+ Effect5.orElseSucceed(() => ({ skills: [] }))
2941
+ );
2942
+ };
2943
+ var writeMetadata = (skillsDir, metadata) => {
2944
+ const metadataPath = getMetadataPath(skillsDir);
2945
+ return Effect5.tryPromise({
2946
+ try: () => writeFile3(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"),
2947
+ catch: (e) => new MetadataWriteError({ path: metadataPath, cause: e })
2948
+ });
2949
+ };
2950
+ var updateMetadata = (skillsDir, newSkills) => pipe5(
2951
+ readMetadata(skillsDir),
2952
+ Effect5.map((existing) => {
2953
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2954
+ const updatedSkills = [...existing.skills];
2955
+ for (const skill of newSkills) {
2956
+ const existingIndex = updatedSkills.findIndex(
2957
+ (s) => s.name === skill.name
2958
+ );
2959
+ const newEntry = {
2960
+ name: skill.name,
2961
+ source: skill.source,
2962
+ version: skill.version,
2963
+ installedAt: now,
2964
+ serverUrl: skill.serverUrl
2965
+ };
2966
+ if (existingIndex >= 0) {
2967
+ updatedSkills[existingIndex] = newEntry;
2968
+ } else {
2969
+ updatedSkills.push(newEntry);
2970
+ }
2971
+ }
2972
+ return { skills: updatedSkills };
2973
+ }),
2974
+ Effect5.flatMap((metadata) => writeMetadata(skillsDir, metadata))
1937
2975
  );
1938
- var writeMarkdownDirRules = (basePath, rules) => pipe5(
2976
+ var removeFromMetadata = (skillsDir, skillName) => pipe5(
2977
+ readMetadata(skillsDir),
2978
+ Effect5.map((metadata) => ({
2979
+ skills: metadata.skills.filter((s) => s.name !== skillName)
2980
+ })),
2981
+ Effect5.flatMap((metadata) => writeMetadata(skillsDir, metadata))
2982
+ );
2983
+ var readMetadataAsync = (skillsDir) => Effect5.runPromise(readMetadata(skillsDir));
2984
+ var updateMetadataAsync = (skillsDir, newSkills) => Effect5.runPromise(updateMetadata(skillsDir, newSkills));
2985
+ var removeFromMetadataAsync = (skillsDir, skillName) => Effect5.runPromise(removeFromMetadata(skillsDir, skillName));
2986
+
2987
+ // src/lib/rule-writer.ts
2988
+ init_esm_shims();
2989
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
2990
+ import { dirname as dirname3, resolve as resolve2, sep as sep2 } from "path";
2991
+ import { Data as Data5, Effect as Effect6, pipe as pipe6 } from "effect";
2992
+ var RuleWriteError = class extends Data5.TaggedError("RuleWriteError") {
2993
+ };
2994
+ var createDirectory = (dir) => Effect6.tryPromise({
2995
+ try: () => mkdir3(dir, { recursive: true }),
2996
+ catch: (e) => new RuleWriteError({ path: dir, operation: "mkdir", cause: e })
2997
+ });
2998
+ var writeTextFile = (fullPath, content) => Effect6.tryPromise({
2999
+ try: () => writeFile4(fullPath, content, "utf-8"),
3000
+ catch: (e) => new RuleWriteError({ path: fullPath, operation: "write", cause: e })
3001
+ });
3002
+ var readTextFile = (fullPath) => Effect6.tryPromise({
3003
+ try: () => readFile3(fullPath, "utf-8"),
3004
+ catch: (e) => new RuleWriteError({ path: fullPath, operation: "read", cause: e })
3005
+ });
3006
+ var assertRulePathWithinBase = (basePath, ruleName) => {
3007
+ const resolvedBase = resolve2(basePath);
3008
+ const resolvedFull = resolve2(basePath, ruleName);
3009
+ if (resolvedFull !== resolvedBase && !resolvedFull.startsWith(resolvedBase + sep2)) {
3010
+ return Effect6.fail(
3011
+ new RuleWriteError({
3012
+ path: ruleName,
3013
+ operation: "write",
3014
+ cause: new Error(
3015
+ `Path traversal detected: "${ruleName}" resolves outside base directory`
3016
+ )
3017
+ })
3018
+ );
3019
+ }
3020
+ return Effect6.succeed(resolvedFull);
3021
+ };
3022
+ var BRAID_SECTION_START = "<!-- braid:rules:start -->";
3023
+ var BRAID_SECTION_END = "<!-- braid:rules:end -->";
3024
+ var YAML_SPECIAL_CHARS = /[\n\r:#{}[\],&*?|>!'"%@`]/;
3025
+ function escapeYamlValue(value) {
3026
+ if (YAML_SPECIAL_CHARS.test(value) || value.startsWith(" ") || value.endsWith(" ")) {
3027
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
3028
+ }
3029
+ return value;
3030
+ }
3031
+ function buildMdcContent(rule) {
3032
+ const lines = ["---"];
3033
+ lines.push(`description: ${escapeYamlValue(rule.title)}`);
3034
+ lines.push("alwaysApply: true");
3035
+ lines.push("---");
3036
+ lines.push("");
3037
+ lines.push(`# ${rule.title}`);
3038
+ lines.push("");
3039
+ lines.push(rule.content);
3040
+ return lines.join("\n");
3041
+ }
3042
+ function buildAppendContent(rules2) {
3043
+ const lines = [BRAID_SECTION_START, ""];
3044
+ for (const rule of rules2) {
3045
+ lines.push(`## ${rule.title}`);
3046
+ lines.push("");
3047
+ lines.push(rule.content);
3048
+ lines.push("");
3049
+ }
3050
+ lines.push(BRAID_SECTION_END);
3051
+ return lines.join("\n");
3052
+ }
3053
+ var writeMdcRules = (basePath, rules2) => pipe6(
3054
+ createDirectory(basePath),
3055
+ Effect6.flatMap(
3056
+ () => Effect6.forEach(
3057
+ rules2,
3058
+ (rule) => pipe6(
3059
+ assertRulePathWithinBase(basePath, `${rule.name}.mdc`),
3060
+ Effect6.flatMap(
3061
+ (filePath) => writeTextFile(filePath, buildMdcContent(rule))
3062
+ )
3063
+ ),
3064
+ { concurrency: "unbounded" }
3065
+ )
3066
+ ),
3067
+ Effect6.asVoid
3068
+ );
3069
+ var writeMarkdownDirRules = (basePath, rules2) => pipe6(
1939
3070
  createDirectory(basePath),
1940
- Effect5.flatMap(
1941
- () => Effect5.forEach(
1942
- rules,
1943
- (rule) => pipe5(
3071
+ Effect6.flatMap(
3072
+ () => Effect6.forEach(
3073
+ rules2,
3074
+ (rule) => pipe6(
1944
3075
  assertRulePathWithinBase(basePath, `${rule.name}.md`),
1945
- Effect5.flatMap(
3076
+ Effect6.flatMap(
1946
3077
  (filePath) => writeTextFile(filePath, `# ${rule.title}
1947
3078
 
1948
3079
  ${rule.content}`)
@@ -1951,18 +3082,18 @@ ${rule.content}`)
1951
3082
  { concurrency: "unbounded" }
1952
3083
  )
1953
3084
  ),
1954
- Effect5.asVoid
3085
+ Effect6.asVoid
1955
3086
  );
1956
- var writeAppendSingleRules = (filePath, rules) => pipe5(
1957
- createDirectory(dirname2(filePath)),
1958
- Effect5.flatMap(
1959
- () => pipe5(
3087
+ var writeAppendSingleRules = (filePath, rules2) => pipe6(
3088
+ createDirectory(dirname3(filePath)),
3089
+ Effect6.flatMap(
3090
+ () => pipe6(
1960
3091
  readTextFile(filePath),
1961
- Effect5.orElseSucceed(() => "")
3092
+ Effect6.orElseSucceed(() => "")
1962
3093
  )
1963
3094
  ),
1964
- Effect5.flatMap((existing) => {
1965
- const braidContent = buildAppendContent(rules);
3095
+ Effect6.flatMap((existing) => {
3096
+ const braidContent = buildAppendContent(rules2);
1966
3097
  const startIdx = existing.indexOf(BRAID_SECTION_START);
1967
3098
  const endIdx = existing.indexOf(BRAID_SECTION_END);
1968
3099
  let newContent;
@@ -1976,30 +3107,30 @@ ${braidContent}` : braidContent;
1976
3107
  return writeTextFile(filePath, newContent);
1977
3108
  })
1978
3109
  );
1979
- var writeRulesForFormat = (basePath, rules, format) => {
3110
+ var writeRulesForFormat = (basePath, rules2, format) => {
1980
3111
  switch (format) {
1981
3112
  case "mdc":
1982
- return writeMdcRules(basePath, rules);
3113
+ return writeMdcRules(basePath, rules2);
1983
3114
  case "markdown-dir":
1984
- return writeMarkdownDirRules(basePath, rules);
3115
+ return writeMarkdownDirRules(basePath, rules2);
1985
3116
  case "append-single":
1986
- return writeAppendSingleRules(basePath, rules);
3117
+ return writeAppendSingleRules(basePath, rules2);
1987
3118
  default:
1988
- return Effect5.void;
3119
+ return Effect6.void;
1989
3120
  }
1990
3121
  };
1991
- var writeRulesForAgent = (agent, rules, rulesPath) => {
1992
- if (!agent.ruleFormat || rules.length === 0) {
1993
- return Effect5.succeed({ written: 0, errors: [] });
1994
- }
1995
- return pipe5(
1996
- writeRulesForFormat(rulesPath, rules, agent.ruleFormat),
1997
- Effect5.map(() => ({
1998
- written: rules.length,
3122
+ var writeRulesForAgent = (agent, rules2, rulesPath) => {
3123
+ if (!agent.ruleFormat || rules2.length === 0) {
3124
+ return Effect6.succeed({ written: 0, errors: [] });
3125
+ }
3126
+ return pipe6(
3127
+ writeRulesForFormat(rulesPath, rules2, agent.ruleFormat),
3128
+ Effect6.map(() => ({
3129
+ written: rules2.length,
1999
3130
  errors: []
2000
3131
  })),
2001
- Effect5.catchAll(
2002
- (error) => Effect5.succeed({
3132
+ Effect6.catchAll(
3133
+ (error) => Effect6.succeed({
2003
3134
  written: 0,
2004
3135
  errors: [
2005
3136
  {
@@ -2011,36 +3142,36 @@ var writeRulesForAgent = (agent, rules, rulesPath) => {
2011
3142
  )
2012
3143
  );
2013
3144
  };
2014
- var writeRulesForAgentAsync = (agent, rules, rulesPath) => Effect5.runPromise(writeRulesForAgent(agent, rules, rulesPath));
3145
+ var writeRulesForAgentAsync = (agent, rules2, rulesPath) => Effect6.runPromise(writeRulesForAgent(agent, rules2, rulesPath));
2015
3146
 
2016
3147
  // src/lib/skill-writer.ts
2017
3148
  init_esm_shims();
2018
- import { chmod, mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
2019
- import { dirname as dirname3, resolve as resolve2, sep as sep2 } from "path";
2020
- import { Data as Data5, Effect as Effect6, pipe as pipe6 } from "effect";
2021
- var WriteError = class extends Data5.TaggedError("WriteError") {
3149
+ import { chmod, mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
3150
+ import { dirname as dirname4, resolve as resolve3, sep as sep3 } from "path";
3151
+ import { Data as Data6, Effect as Effect7, pipe as pipe7 } from "effect";
3152
+ var WriteError = class extends Data6.TaggedError("WriteError") {
2022
3153
  };
2023
- var createDirectory2 = (dir, fullPath) => Effect6.tryPromise({
2024
- try: () => mkdir3(dir, { recursive: true }),
3154
+ var createDirectory2 = (dir, fullPath) => Effect7.tryPromise({
3155
+ try: () => mkdir4(dir, { recursive: true }),
2025
3156
  catch: (e) => new WriteError({ path: fullPath, operation: "mkdir", cause: e })
2026
3157
  });
2027
- var writeTextFile2 = (fullPath, content) => Effect6.tryPromise({
2028
- try: () => writeFile4(fullPath, content, "utf-8"),
3158
+ var writeTextFile2 = (fullPath, content) => Effect7.tryPromise({
3159
+ try: () => writeFile5(fullPath, content, "utf-8"),
2029
3160
  catch: (e) => new WriteError({ path: fullPath, operation: "write", cause: e })
2030
3161
  });
2031
- var writeBinaryFile = (fullPath, content) => Effect6.tryPromise({
2032
- try: () => writeFile4(fullPath, content),
3162
+ var writeBinaryFile = (fullPath, content) => Effect7.tryPromise({
3163
+ try: () => writeFile5(fullPath, content),
2033
3164
  catch: (e) => new WriteError({ path: fullPath, operation: "write", cause: e })
2034
3165
  });
2035
- var makeExecutable = (fullPath) => Effect6.tryPromise({
3166
+ var makeExecutable = (fullPath) => Effect7.tryPromise({
2036
3167
  try: () => chmod(fullPath, 493),
2037
3168
  catch: (e) => new WriteError({ path: fullPath, operation: "chmod", cause: e })
2038
3169
  });
2039
- var assertWithinBase = (basePath, untrustedPath) => {
2040
- const resolvedBase = resolve2(basePath);
2041
- const resolvedFull = resolve2(basePath, untrustedPath);
2042
- if (resolvedFull !== resolvedBase && !resolvedFull.startsWith(resolvedBase + sep2)) {
2043
- return Effect6.fail(
3170
+ var assertWithinBase2 = (basePath, untrustedPath) => {
3171
+ const resolvedBase = resolve3(basePath);
3172
+ const resolvedFull = resolve3(basePath, untrustedPath);
3173
+ if (resolvedFull !== resolvedBase && !resolvedFull.startsWith(resolvedBase + sep3)) {
3174
+ return Effect7.fail(
2044
3175
  new WriteError({
2045
3176
  path: untrustedPath,
2046
3177
  operation: "write",
@@ -2050,7 +3181,7 @@ var assertWithinBase = (basePath, untrustedPath) => {
2050
3181
  })
2051
3182
  );
2052
3183
  }
2053
- return Effect6.succeed(resolvedFull);
3184
+ return Effect7.succeed(resolvedFull);
2054
3185
  };
2055
3186
  var COMPATIBILITY_REGEX = /^compatibility:\s*.+$/m;
2056
3187
  var rewriteCompatibility = (content, agentId) => {
@@ -2093,41 +3224,41 @@ var isScriptFile = (path2) => {
2093
3224
  return inScriptsDir && scriptExtensions.some((ext) => lowerPath.endsWith(ext));
2094
3225
  };
2095
3226
  var writeFileContent = (fullPath, file, agentId) => isBinaryFile(file.path) ? writeBinaryFile(fullPath, decodeFileContentBinary(file)) : writeTextFile2(fullPath, decodeFileContent(file, agentId));
2096
- var setExecutableIfScript = (fullPath, filePath) => isScriptFile(filePath) ? makeExecutable(fullPath) : Effect6.void;
2097
- var writeSkillFile = (basePath, file, agentId) => pipe6(
2098
- assertWithinBase(basePath, file.path),
2099
- Effect6.flatMap((fullPath) => {
2100
- const dir = dirname3(fullPath);
2101
- return pipe6(
3227
+ var setExecutableIfScript = (fullPath, file, agentId) => isScriptFile(file.path) && decodeFileContent(file, agentId).startsWith("#!") ? makeExecutable(fullPath) : Effect7.void;
3228
+ var writeSkillFile = (basePath, file, agentId) => pipe7(
3229
+ assertWithinBase2(basePath, file.path),
3230
+ Effect7.flatMap((fullPath) => {
3231
+ const dir = dirname4(fullPath);
3232
+ return pipe7(
2102
3233
  createDirectory2(dir, fullPath),
2103
- Effect6.flatMap(() => writeFileContent(fullPath, file, agentId)),
2104
- Effect6.flatMap(() => setExecutableIfScript(fullPath, file.path))
3234
+ Effect7.flatMap(() => writeFileContent(fullPath, file, agentId)),
3235
+ Effect7.flatMap(() => setExecutableIfScript(fullPath, file, agentId))
2105
3236
  );
2106
3237
  })
2107
3238
  );
2108
- var writeSkill = (basePath, skill, agentId) => pipe6(
2109
- assertWithinBase(basePath, skill.name),
2110
- Effect6.flatMap(
2111
- (skillDir) => pipe6(
2112
- Effect6.forEach(
3239
+ var writeSkill = (basePath, skill, agentId) => pipe7(
3240
+ assertWithinBase2(basePath, skill.name),
3241
+ Effect7.flatMap(
3242
+ (skillDir) => pipe7(
3243
+ Effect7.forEach(
2113
3244
  skill.files,
2114
3245
  (file) => writeSkillFile(skillDir, file, agentId),
2115
3246
  {
2116
3247
  concurrency: "unbounded"
2117
3248
  }
2118
3249
  ),
2119
- Effect6.map(() => void 0)
3250
+ Effect7.map(() => void 0)
2120
3251
  )
2121
3252
  )
2122
3253
  );
2123
- var writeSkills = (basePath, skills, agentId) => pipe6(
2124
- Effect6.forEach(
2125
- skills,
2126
- (skill) => pipe6(
3254
+ var writeSkills = (basePath, skills2, agentId) => pipe7(
3255
+ Effect7.forEach(
3256
+ skills2,
3257
+ (skill) => pipe7(
2127
3258
  writeSkill(basePath, skill, agentId),
2128
- Effect6.map(() => ({ success: true, skill: skill.name })),
2129
- Effect6.catchAll(
2130
- (error) => Effect6.succeed({
3259
+ Effect7.map(() => ({ success: true, skill: skill.name })),
3260
+ Effect7.catchAll(
3261
+ (error) => Effect7.succeed({
2131
3262
  success: false,
2132
3263
  skill: skill.name,
2133
3264
  error: error.cause instanceof Error ? error.cause.message : String(error.cause)
@@ -2136,14 +3267,14 @@ var writeSkills = (basePath, skills, agentId) => pipe6(
2136
3267
  ),
2137
3268
  { concurrency: "unbounded" }
2138
3269
  ),
2139
- Effect6.map((results) => ({
3270
+ Effect7.map((results) => ({
2140
3271
  written: results.filter((r) => r.success).map((r) => r.skill),
2141
3272
  errors: results.filter(
2142
3273
  (r) => !r.success
2143
3274
  ).map((r) => ({ skill: r.skill, error: r.error }))
2144
3275
  }))
2145
3276
  );
2146
- var writeSkillsAsync = (basePath, skills, agentId) => Effect6.runPromise(writeSkills(basePath, skills, agentId));
3277
+ var writeSkillsAsync = (basePath, skills2, agentId) => Effect7.runPromise(writeSkills(basePath, skills2, agentId));
2147
3278
 
2148
3279
  // src/commands/install.ts
2149
3280
  init_tui();
@@ -2220,9 +3351,9 @@ function buildFetchOptions(resolved) {
2220
3351
  }
2221
3352
  return fetchOptions;
2222
3353
  }
2223
- function displaySkillsAndExit(skills) {
3354
+ function displaySkillsAndExit(skills2) {
2224
3355
  log.info("\nSkills:");
2225
- for (const skill of skills) {
3356
+ for (const skill of skills2) {
2226
3357
  const fileCount = skill.files.length;
2227
3358
  log.info(
2228
3359
  ` ${skill.name} (${fileCount} file${fileCount !== 1 ? "s" : ""})`
@@ -2330,14 +3461,14 @@ async function installSkillsToAgent(agent, response, installPath) {
2330
3461
  return { written: result.written.length, errors: result.errors.length };
2331
3462
  }
2332
3463
  async function installRulesToAgent(agent, response, options) {
2333
- const rules = response.rules ?? [];
3464
+ const rules2 = response.rules ?? [];
2334
3465
  const rulesPath = resolveRulesInstallPath(agent, {
2335
3466
  global: options.global === true
2336
3467
  });
2337
- if (rules.length === 0 || !rulesPath || !agent.ruleFormat) {
3468
+ if (rules2.length === 0 || !rulesPath || !agent.ruleFormat) {
2338
3469
  return { written: 0, errors: 0 };
2339
3470
  }
2340
- const result = await writeRulesForAgentAsync(agent, rules, rulesPath);
3471
+ const result = await writeRulesForAgentAsync(agent, rules2, rulesPath);
2341
3472
  for (const err of result.errors) {
2342
3473
  log.warn(` Failed rules: ${err.agent} - ${err.error}`);
2343
3474
  }
@@ -2358,33 +3489,206 @@ async function installToAgent(agent, response, serverUrl, options, installSpinne
2358
3489
  global: options.global === true
2359
3490
  });
2360
3491
  installSpinner.start(`Installing to ${agent.name}...`);
2361
- const skills = installPath ? await installSkillsToAgent(agent, response, installPath) : { written: 0, errors: 0 };
2362
- const rules = await installRulesToAgent(agent, response, options);
2363
- const totalWritten = skills.written + rules.written;
2364
- const totalErrors = skills.errors + rules.errors;
3492
+ const skills2 = installPath ? await installSkillsToAgent(agent, response, installPath) : { written: 0, errors: 0 };
3493
+ const rules2 = await installRulesToAgent(agent, response, options);
3494
+ const totalWritten = skills2.written + rules2.written;
3495
+ const totalErrors = skills2.errors + rules2.errors;
2365
3496
  if (totalErrors > 0) {
2366
3497
  installSpinner.stop(
2367
3498
  `${agent.name}: ${totalWritten} installed, ${totalErrors} failed`
2368
3499
  );
2369
3500
  } else {
2370
3501
  installSpinner.stop(
2371
- formatInstallSummary(agent.name, skills.written, rules.written)
3502
+ formatInstallSummary(agent.name, skills2.written, rules2.written)
3503
+ );
3504
+ }
3505
+ if (response.skills.length > 0 && installPath) {
3506
+ await updateMetadataAsync(
3507
+ installPath,
3508
+ response.skills.map((s) => ({
3509
+ name: s.name,
3510
+ source: response.source,
3511
+ version: response.version,
3512
+ serverUrl
3513
+ }))
3514
+ );
3515
+ }
3516
+ return { written: totalWritten, errors: totalErrors };
3517
+ }
3518
+ var PUBLIC_SOURCE_REGEX = /^@([a-z0-9-]+)\/([a-z0-9-]+)$/;
3519
+ function parsePublicSource(source) {
3520
+ const match = source.match(PUBLIC_SOURCE_REGEX);
3521
+ if (!(match?.[1] && match[2])) {
3522
+ return null;
3523
+ }
3524
+ return { handle: match[1], slug: match[2] };
3525
+ }
3526
+ function displayPublicMetadata(metadata) {
3527
+ if (metadata.type === "project") {
3528
+ const { project, rules: rules2 } = metadata;
3529
+ log.info(`
3530
+ Project: ${project.name}`);
3531
+ if (project.description) {
3532
+ log.info(` ${project.description}`);
3533
+ }
3534
+ log.info(` ${rules2.length} rule(s) available`);
3535
+ } else {
3536
+ const { rule } = metadata;
3537
+ log.info(`
3538
+ Rule: ${rule.title}`);
3539
+ log.info(` Type: ${rule.type}`);
3540
+ if (rule.tags.length > 0) {
3541
+ log.info(` Tags: ${rule.tags.join(", ")}`);
3542
+ }
3543
+ if (rule.description) {
3544
+ log.info(` ${rule.description}`);
3545
+ }
3546
+ }
3547
+ }
3548
+ async function selectPublicRules(metadata, options) {
3549
+ if (metadata.type !== "project") {
3550
+ return null;
3551
+ }
3552
+ if (options.yes) {
3553
+ return metadata.rules.map((r) => r.id);
3554
+ }
3555
+ const selected = await multiselect({
3556
+ message: "Select rules to install:",
3557
+ options: metadata.rules.map((r) => ({
3558
+ value: r.id,
3559
+ label: r.title,
3560
+ hint: r.type
3561
+ })),
3562
+ initialValues: metadata.rules.map((r) => r.id),
3563
+ required: true
3564
+ });
3565
+ if (isCancel(selected)) {
3566
+ cancel("Install cancelled.");
3567
+ process.exit(0);
3568
+ }
3569
+ return selected;
3570
+ }
3571
+ async function confirmRuleInstall(metadata, options) {
3572
+ if (metadata.type !== "rule") {
3573
+ return true;
3574
+ }
3575
+ if (options.yes) {
3576
+ return true;
3577
+ }
3578
+ const confirmed = await confirm({
3579
+ message: `Install "${metadata.rule.title}"?`
3580
+ });
3581
+ if (isCancel(confirmed)) {
3582
+ cancel("Install cancelled.");
3583
+ process.exit(0);
3584
+ }
3585
+ return confirmed === true;
3586
+ }
3587
+ function summarizeContent(response) {
3588
+ const ruleCount = response.rules?.length ?? 0;
3589
+ const skillCount = response.skills.length;
3590
+ const parts = [];
3591
+ if (skillCount > 0) {
3592
+ parts.push(`${skillCount} skills`);
3593
+ }
3594
+ if (ruleCount > 0) {
3595
+ parts.push(`${ruleCount} rules`);
3596
+ }
3597
+ return parts.join(", ") || "nothing";
3598
+ }
3599
+ function hasNoContent(response) {
3600
+ return response.skills.length === 0 && (response.rules?.length ?? 0) === 0;
3601
+ }
3602
+ async function installToSelectedAgents(response, serverUrl, options, installSpinner) {
3603
+ const agentResolution = await resolveAgents(options, installSpinner);
3604
+ const selectedAgents = await selectAgents(
3605
+ agentResolution.availableAgents,
3606
+ agentResolution.preselectedAgents,
3607
+ options
3608
+ );
3609
+ let totalWritten = 0;
3610
+ let totalErrors = 0;
3611
+ for (const agent of selectedAgents) {
3612
+ const result = await installToAgent(
3613
+ agent,
3614
+ response,
3615
+ serverUrl,
3616
+ options,
3617
+ installSpinner
3618
+ );
3619
+ totalWritten += result.written;
3620
+ totalErrors += result.errors;
3621
+ }
3622
+ return { totalWritten, totalErrors };
3623
+ }
3624
+ function showInstallOutro(totalWritten, totalErrors, suffix) {
3625
+ if (totalErrors > 0) {
3626
+ outro(`Installed ${totalWritten} items with ${totalErrors} errors.`);
3627
+ } else {
3628
+ outro(`Successfully installed ${totalWritten} items${suffix}.`);
3629
+ }
3630
+ }
3631
+ async function fetchPublicContent(handle, slug, options, installSpinner) {
3632
+ const metadata = await fetchPublicMetadataAsync(handle, slug, options.server);
3633
+ installSpinner.stop("Found public content");
3634
+ displayPublicMetadata(metadata);
3635
+ let ruleIds;
3636
+ if (metadata.type === "project") {
3637
+ const selected = await selectPublicRules(metadata, options);
3638
+ ruleIds = selected ?? void 0;
3639
+ } else {
3640
+ const confirmed = await confirmRuleInstall(metadata, options);
3641
+ if (!confirmed) {
3642
+ outro("Install cancelled.");
3643
+ process.exit(0);
3644
+ }
3645
+ }
3646
+ installSpinner.start("Downloading content...");
3647
+ return fetchPublicExportAsync(handle, slug, ruleIds, options.server);
3648
+ }
3649
+ async function publicInstallCommand(source, options) {
3650
+ const parsed = parsePublicSource(source);
3651
+ if (!parsed) {
3652
+ log.error(`Invalid public source: ${source}`);
3653
+ log.info("Expected format: @handle/slug (e.g., @acme/coding-standards)");
3654
+ process.exit(1);
3655
+ }
3656
+ const { handle, slug } = parsed;
3657
+ intro(`Installing from @${handle}/${slug}`);
3658
+ const installSpinner = spinner();
3659
+ installSpinner.start("Fetching public content...");
3660
+ try {
3661
+ const response = await fetchPublicContent(
3662
+ handle,
3663
+ slug,
3664
+ options,
3665
+ installSpinner
2372
3666
  );
2373
- }
2374
- if (response.skills.length > 0 && installPath) {
2375
- await updateMetadataAsync(
2376
- installPath,
2377
- response.skills.map((s) => ({
2378
- name: s.name,
2379
- source: response.source,
2380
- version: response.version,
2381
- serverUrl
2382
- }))
3667
+ installSpinner.stop(`Downloaded ${summarizeContent(response)}`);
3668
+ if (hasNoContent(response)) {
3669
+ log.warn("No content to install.");
3670
+ process.exit(0);
3671
+ }
3672
+ const serverUrl = options.server ?? "https://braid.cloud";
3673
+ const { totalWritten, totalErrors } = await installToSelectedAgents(
3674
+ response,
3675
+ serverUrl,
3676
+ options,
3677
+ installSpinner
2383
3678
  );
3679
+ showInstallOutro(totalWritten, totalErrors, ` from @${handle}/${slug}`);
3680
+ } catch (error) {
3681
+ installSpinner.stop("Install failed");
3682
+ const message = error instanceof Error ? error.message : String(error);
3683
+ log.error(message);
3684
+ process.exit(1);
2384
3685
  }
2385
- return { written: totalWritten, errors: totalErrors };
2386
3686
  }
2387
- async function installCommand(options) {
3687
+ async function installCommand(sourceOrOptions, maybeOptions) {
3688
+ if (typeof sourceOrOptions === "string") {
3689
+ return handlePublicSource(sourceOrOptions, maybeOptions ?? {});
3690
+ }
3691
+ const options = sourceOrOptions;
2388
3692
  const config = await loadMergedConfigAsync();
2389
3693
  const resolved = resolveInstallConfig(options, config);
2390
3694
  validateInstallOptions(resolved);
@@ -2395,17 +3699,8 @@ async function installCommand(options) {
2395
3699
  try {
2396
3700
  const fetchOptions = buildFetchOptions(resolved);
2397
3701
  const response = await fetchSkillsAsync(fetchOptions);
2398
- const ruleCount = response.rules?.length ?? 0;
2399
- const skillCount = response.skills.length;
2400
- const foundParts = [];
2401
- if (skillCount > 0) {
2402
- foundParts.push(`${skillCount} skills`);
2403
- }
2404
- if (ruleCount > 0) {
2405
- foundParts.push(`${ruleCount} rules`);
2406
- }
2407
- installSpinner.stop(`Found ${foundParts.join(", ") || "nothing"}`);
2408
- if (skillCount === 0 && ruleCount === 0) {
3702
+ installSpinner.stop(`Found ${summarizeContent(response)}`);
3703
+ if (hasNoContent(response)) {
2409
3704
  log.warn(
2410
3705
  "No skills or rules found. Check that your profile/project has enabled prompts."
2411
3706
  );
@@ -2414,32 +3709,13 @@ async function installCommand(options) {
2414
3709
  if (options.list) {
2415
3710
  displaySkillsAndExit(response.skills);
2416
3711
  }
2417
- const agentResolution = await resolveAgents(options, installSpinner);
2418
- const selectedAgents = await selectAgents(
2419
- agentResolution.availableAgents,
2420
- agentResolution.preselectedAgents,
2421
- options
3712
+ const { totalWritten, totalErrors } = await installToSelectedAgents(
3713
+ response,
3714
+ resolved.serverUrl,
3715
+ options,
3716
+ installSpinner
2422
3717
  );
2423
- let totalWritten = 0;
2424
- let totalErrors = 0;
2425
- for (const agent of selectedAgents) {
2426
- const result = await installToAgent(
2427
- agent,
2428
- response,
2429
- resolved.serverUrl,
2430
- options,
2431
- installSpinner
2432
- );
2433
- totalWritten += result.written;
2434
- totalErrors += result.errors;
2435
- }
2436
- if (totalErrors > 0) {
2437
- outro(`Installed ${totalWritten} items with ${totalErrors} errors.`);
2438
- } else {
2439
- outro(
2440
- `Successfully installed ${totalWritten} items to ${selectedAgents.length} agent(s).`
2441
- );
2442
- }
3718
+ showInstallOutro(totalWritten, totalErrors, ` to ${totalWritten} agent(s)`);
2443
3719
  log.info("Run 'braid list' to see installed skills.");
2444
3720
  } catch (error) {
2445
3721
  installSpinner.stop("Install failed");
@@ -2448,6 +3724,15 @@ async function installCommand(options) {
2448
3724
  process.exit(1);
2449
3725
  }
2450
3726
  }
3727
+ function handlePublicSource(source, options) {
3728
+ const publicParsed = parsePublicSource(source);
3729
+ if (publicParsed) {
3730
+ return publicInstallCommand(source, options);
3731
+ }
3732
+ log.error(`Unknown source: ${source}`);
3733
+ log.info("Public sources use format: @handle/slug");
3734
+ process.exit(1);
3735
+ }
2451
3736
 
2452
3737
  // src/commands/list.ts
2453
3738
  init_esm_shims();
@@ -2545,36 +3830,39 @@ async function listCommand(options) {
2545
3830
 
2546
3831
  // src/commands/mcp.ts
2547
3832
  init_esm_shims();
2548
- import process7 from "process";
3833
+ import process8 from "process";
2549
3834
  init_config();
2550
3835
 
2551
3836
  // src/lib/mcp-config.ts
2552
3837
  init_esm_shims();
2553
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
2554
- import { dirname as dirname4 } from "path";
2555
- import { Data as Data6, Effect as Effect7, pipe as pipe7 } from "effect";
2556
- var McpConfigReadError = class extends Data6.TaggedError("McpConfigReadError") {
3838
+ import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile6 } from "fs/promises";
3839
+ import { dirname as dirname5 } from "path";
3840
+ import { Data as Data7, Effect as Effect8, pipe as pipe8 } from "effect";
3841
+ var McpConfigReadError = class extends Data7.TaggedError("McpConfigReadError") {
2557
3842
  };
2558
- var McpConfigWriteError = class extends Data6.TaggedError("McpConfigWriteError") {
3843
+ var McpConfigWriteError = class extends Data7.TaggedError("McpConfigWriteError") {
2559
3844
  };
2560
- var readMcpConfig = (path2) => pipe7(
2561
- Effect7.tryPromise({
3845
+ var readMcpConfig = (path2) => pipe8(
3846
+ Effect8.tryPromise({
2562
3847
  try: () => readFile4(path2, "utf-8"),
2563
3848
  catch: (e) => new McpConfigReadError({ path: path2, cause: e })
2564
3849
  }),
2565
- Effect7.flatMap(
2566
- (content) => Effect7.try({
3850
+ Effect8.flatMap(
3851
+ (content) => Effect8.try({
2567
3852
  try: () => JSON.parse(content),
2568
3853
  catch: () => ({})
2569
3854
  })
2570
3855
  ),
2571
- Effect7.orElseSucceed(() => ({}))
3856
+ Effect8.orElseSucceed(() => ({}))
2572
3857
  );
2573
- var writeMcpConfig = (path2, config) => Effect7.tryPromise({
3858
+ var writeMcpConfig = (path2, config) => Effect8.tryPromise({
2574
3859
  try: async () => {
2575
- await mkdir4(dirname4(path2), { recursive: true });
2576
- await writeFile5(path2, `${JSON.stringify(config, null, 2)}
2577
- `, "utf-8");
3860
+ await mkdir5(dirname5(path2), { recursive: true, mode: 448 });
3861
+ await writeFile6(path2, `${JSON.stringify(config, null, 2)}
3862
+ `, {
3863
+ encoding: "utf-8",
3864
+ mode: 384
3865
+ });
2578
3866
  },
2579
3867
  catch: (e) => new McpConfigWriteError({ path: path2, cause: e })
2580
3868
  });
@@ -2600,8 +3888,8 @@ var hasbraidEntry = (config, rootKey, entryName) => {
2600
3888
  const servers = config[rootKey];
2601
3889
  return servers !== void 0 && entryName in servers;
2602
3890
  };
2603
- var readMcpConfigAsync = (path2) => Effect7.runPromise(readMcpConfig(path2));
2604
- var writeMcpConfigAsync = (path2, config) => Effect7.runPromise(writeMcpConfig(path2, config));
3891
+ var readMcpConfigAsync = (path2) => Effect8.runPromise(readMcpConfig(path2));
3892
+ var writeMcpConfigAsync = (path2, config) => Effect8.runPromise(writeMcpConfig(path2, config));
2605
3893
 
2606
3894
  // src/commands/mcp.ts
2607
3895
  init_tui();
@@ -2630,7 +3918,7 @@ async function selectTool(mcpAgents, toolFlag) {
2630
3918
  if (!agent) {
2631
3919
  log.error(`Unknown or unsupported tool: ${toolFlag}`);
2632
3920
  log.info(`Supported tools: ${mcpAgents.map((a) => a.id).join(", ")}`);
2633
- process7.exit(1);
3921
+ process8.exit(1);
2634
3922
  }
2635
3923
  return agent;
2636
3924
  }
@@ -2649,12 +3937,12 @@ async function selectTool(mcpAgents, toolFlag) {
2649
3937
  });
2650
3938
  if (isCancel(toolChoice)) {
2651
3939
  cancel("MCP setup cancelled.");
2652
- process7.exit(0);
3940
+ process8.exit(0);
2653
3941
  }
2654
3942
  const found = mcpAgents.find((a) => a.id === toolChoice);
2655
3943
  if (!found) {
2656
3944
  log.error("Agent not found.");
2657
- process7.exit(1);
3945
+ process8.exit(1);
2658
3946
  }
2659
3947
  return found;
2660
3948
  }
@@ -2680,7 +3968,7 @@ async function selectScope(agent, options) {
2680
3968
  });
2681
3969
  if (isCancel(scopeChoice)) {
2682
3970
  cancel("MCP setup cancelled.");
2683
- process7.exit(0);
3971
+ process8.exit(0);
2684
3972
  }
2685
3973
  return scopeChoice;
2686
3974
  }
@@ -2711,7 +3999,7 @@ async function resolveToken(options, existingToken) {
2711
3999
  });
2712
4000
  if (isCancel(authChoice)) {
2713
4001
  cancel("MCP setup cancelled.");
2714
- process7.exit(0);
4002
+ process8.exit(0);
2715
4003
  }
2716
4004
  if (authChoice === "existing") {
2717
4005
  return existingToken;
@@ -2734,7 +4022,7 @@ async function resolveToken(options, existingToken) {
2734
4022
  });
2735
4023
  if (isCancel(token)) {
2736
4024
  cancel("MCP setup cancelled.");
2737
- process7.exit(0);
4025
+ process8.exit(0);
2738
4026
  }
2739
4027
  return token;
2740
4028
  }
@@ -2765,7 +4053,7 @@ async function addFlow(options) {
2765
4053
  const mcpAgents = getMcpAgents();
2766
4054
  if (mcpAgents.length === 0) {
2767
4055
  log.error("No agents with MCP configuration support found.");
2768
- process7.exit(1);
4056
+ process8.exit(1);
2769
4057
  }
2770
4058
  const selectedAgent = await selectTool(mcpAgents, options.tool);
2771
4059
  const scope = await selectScope(selectedAgent, options);
@@ -2774,7 +4062,7 @@ async function addFlow(options) {
2774
4062
  });
2775
4063
  if (!configPath) {
2776
4064
  log.error(`No ${scope} config path available for ${selectedAgent.name}.`);
2777
- process7.exit(1);
4065
+ process8.exit(1);
2778
4066
  }
2779
4067
  const token = await resolveToken(options, config.token);
2780
4068
  const env = buildEnvVars(token, options.server);
@@ -2785,115 +4073,430 @@ async function addFlow(options) {
2785
4073
  ...config.token ? { apiKey: config.token } : {}
2786
4074
  });
2787
4075
  }
2788
- const mcpSpinner = spinner();
2789
- mcpSpinner.start(`Configuring ${selectedAgent.name}...`);
4076
+ const mcpSpinner = spinner();
4077
+ mcpSpinner.start(`Configuring ${selectedAgent.name}...`);
4078
+ try {
4079
+ await writeEntry(selectedAgent, configPath, env);
4080
+ mcpSpinner.stop(`Configured ${selectedAgent.name}`);
4081
+ log.info(`Config written to ${configPath}`);
4082
+ if (!token) {
4083
+ log.info("Set BRAID_TOKEN environment variable before using.");
4084
+ }
4085
+ outro(`Done! Restart ${selectedAgent.name} to activate.`);
4086
+ } catch (error) {
4087
+ mcpSpinner.stop("Configuration failed");
4088
+ const message = error instanceof Error ? error.message : String(error);
4089
+ log.error(`Failed to write config: ${message}`);
4090
+ process8.exit(1);
4091
+ }
4092
+ }
4093
+ async function removeFlow(options) {
4094
+ const mcpAgents = getMcpAgents();
4095
+ const selectedAgent = await selectTool(mcpAgents, options.tool);
4096
+ const scope = resolveScope(selectedAgent, options);
4097
+ const configPath = resolveMcpConfigPath(selectedAgent, {
4098
+ global: scope === "global"
4099
+ });
4100
+ if (!configPath) {
4101
+ log.error(`No ${scope} config path available for ${selectedAgent.name}.`);
4102
+ process8.exit(1);
4103
+ }
4104
+ const removeSpinner = spinner();
4105
+ removeSpinner.start(`Removing braid from ${selectedAgent.name}...`);
4106
+ try {
4107
+ const rootKey = selectedAgent.mcpRootKey ?? "mcpServers";
4108
+ const existingConfig = await readMcpConfigAsync(configPath);
4109
+ if (!hasbraidEntry(existingConfig, rootKey, BRAID_ENTRY_NAME)) {
4110
+ removeSpinner.stop("No braid MCP entry found");
4111
+ log.info(
4112
+ `${selectedAgent.name} doesn't have a braid MCP entry at ${configPath}`
4113
+ );
4114
+ return;
4115
+ }
4116
+ const updatedConfig = removebraidEntry(
4117
+ existingConfig,
4118
+ rootKey,
4119
+ BRAID_ENTRY_NAME
4120
+ );
4121
+ await writeMcpConfigAsync(configPath, updatedConfig);
4122
+ removeSpinner.stop(`Removed braid from ${selectedAgent.name}`);
4123
+ outro(`braid MCP removed from ${configPath}`);
4124
+ } catch (error) {
4125
+ removeSpinner.stop("Removal failed");
4126
+ const message = error instanceof Error ? error.message : String(error);
4127
+ log.error(`Failed to remove config: ${message}`);
4128
+ process8.exit(1);
4129
+ }
4130
+ }
4131
+ async function statusFlow() {
4132
+ const mcpAgents = getMcpAgents();
4133
+ const statusSpinner = spinner();
4134
+ statusSpinner.start("Checking MCP configuration status...");
4135
+ const results = [];
4136
+ for (const agent of mcpAgents) {
4137
+ const rootKey = agent.mcpRootKey ?? "mcpServers";
4138
+ for (const scope of ["project", "global"]) {
4139
+ const configPath = resolveMcpConfigPath(agent, {
4140
+ global: scope === "global"
4141
+ });
4142
+ if (!configPath) {
4143
+ continue;
4144
+ }
4145
+ const config = await readMcpConfigAsync(configPath);
4146
+ if (hasbraidEntry(config, rootKey, BRAID_ENTRY_NAME)) {
4147
+ results.push({
4148
+ name: agent.name,
4149
+ path: configPath,
4150
+ scope
4151
+ });
4152
+ }
4153
+ }
4154
+ }
4155
+ statusSpinner.stop(`Found ${results.length} configured tool(s)`);
4156
+ if (results.length === 0) {
4157
+ log.info("No tools have braid MCP configured.");
4158
+ log.info("Run 'braid mcp' to set up MCP for your AI tools.");
4159
+ return;
4160
+ }
4161
+ for (const result of results) {
4162
+ log.info(` ${result.name} (${result.scope}) \u2192 ${result.path}`);
4163
+ }
4164
+ }
4165
+ function helpCommandsFlow() {
4166
+ log.info("MCP manual command reference:");
4167
+ log.info(" braid projects list --json");
4168
+ log.info(" braid rules list --json");
4169
+ log.info(" braid skills list --profile <name> --json");
4170
+ log.info("");
4171
+ log.info("Common local management commands:");
4172
+ log.info(" braid install --yes --profile <name>");
4173
+ log.info(" braid update --yes");
4174
+ log.info(" braid list");
4175
+ log.info(" braid remove --yes --all");
4176
+ log.info(
4177
+ " braid scope --file user --organization personal --source profile --profile <name>"
4178
+ );
4179
+ }
4180
+ async function mcpCommand(options) {
4181
+ if (options.helpCommands) {
4182
+ intro("braid mcp help");
4183
+ helpCommandsFlow();
4184
+ return;
4185
+ }
4186
+ if (options.status) {
4187
+ intro("braid mcp status");
4188
+ await statusFlow();
4189
+ return;
4190
+ }
4191
+ if (options.remove) {
4192
+ intro("braid mcp remove");
4193
+ await removeFlow(options);
4194
+ return;
4195
+ }
4196
+ intro("braid mcp");
4197
+ await addFlow(options);
4198
+ }
4199
+
4200
+ // src/commands/profiles.ts
4201
+ init_esm_shims();
4202
+ init_api();
4203
+ init_tui();
4204
+ import process9 from "process";
4205
+ var writeJson3 = (value) => {
4206
+ process9.stdout.write(`${JSON.stringify(value, null, 2)}
4207
+ `);
4208
+ };
4209
+ var exitWithError3 = (error) => {
4210
+ const message = error instanceof Error ? error.message : String(error);
4211
+ log.error(message);
4212
+ process9.exit(1);
4213
+ };
4214
+ var fail2 = (message) => {
4215
+ throw new Error(message);
4216
+ };
4217
+ var parseContext = (contextJson) => {
4218
+ if (!contextJson) {
4219
+ return void 0;
4220
+ }
4221
+ return JSON.parse(contextJson);
4222
+ };
4223
+ var run2 = async (command, args, options) => {
4224
+ const apiOptions = {
4225
+ ...options.server ? { serverUrl: options.server } : {},
4226
+ ...options.apiKey ? { apiKey: options.apiKey } : {}
4227
+ };
4228
+ const result = await runLifecycleCommandAsync(
4229
+ {
4230
+ domain: "profiles",
4231
+ command,
4232
+ args
4233
+ },
4234
+ apiOptions
4235
+ );
4236
+ if (options.json) {
4237
+ writeJson3(result);
4238
+ return;
4239
+ }
4240
+ log.success(`profiles ${command} completed`);
4241
+ };
4242
+ async function profilesListCommand(options) {
4243
+ try {
4244
+ await run2("list", {}, options);
4245
+ } catch (error) {
4246
+ exitWithError3(error);
4247
+ }
4248
+ }
4249
+ async function profilesGetCommand(options) {
4250
+ try {
4251
+ if (!(options.id || options.name)) {
4252
+ fail2("profiles get requires --id or --name");
4253
+ }
4254
+ await run2("get", { id: options.id, name: options.name }, options);
4255
+ } catch (error) {
4256
+ exitWithError3(error);
4257
+ }
4258
+ }
4259
+ async function profilesCreateCommand(options) {
4260
+ try {
4261
+ const name = options.name ?? fail2("profiles create requires --name");
4262
+ await run2(
4263
+ "create",
4264
+ { name, context: parseContext(options.contextJson) },
4265
+ options
4266
+ );
4267
+ } catch (error) {
4268
+ exitWithError3(error);
4269
+ }
4270
+ }
4271
+ async function profilesUpdateCommand(options) {
4272
+ try {
4273
+ const id = options.id ?? fail2("profiles update requires --id");
4274
+ await run2(
4275
+ "update",
4276
+ {
4277
+ id,
4278
+ name: options.name,
4279
+ context: parseContext(options.contextJson)
4280
+ },
4281
+ options
4282
+ );
4283
+ } catch (error) {
4284
+ exitWithError3(error);
4285
+ }
4286
+ }
4287
+ async function profilesRemoveCommand(options) {
4288
+ try {
4289
+ const id = options.id ?? fail2("profiles remove requires --id");
4290
+ if (!options.yes) {
4291
+ fail2("profiles remove requires --yes");
4292
+ }
4293
+ await run2("remove", { id, yes: true }, options);
4294
+ } catch (error) {
4295
+ exitWithError3(error);
4296
+ }
4297
+ }
4298
+ async function profilesSetDefaultCommand(options) {
4299
+ try {
4300
+ const id = options.id ?? fail2("profiles set-default requires --id");
4301
+ await run2("set-default", { id }, options);
4302
+ } catch (error) {
4303
+ exitWithError3(error);
4304
+ }
4305
+ }
4306
+
4307
+ // src/commands/projects.ts
4308
+ init_esm_shims();
4309
+ init_api();
4310
+ init_tui();
4311
+ import process10 from "process";
4312
+ var writeJson4 = (value) => {
4313
+ process10.stdout.write(`${JSON.stringify(value, null, 2)}
4314
+ `);
4315
+ };
4316
+ var fail3 = (message) => {
4317
+ throw new Error(message);
4318
+ };
4319
+ var run3 = async (command, args, options) => {
4320
+ const apiOptions = {
4321
+ ...options.server ? { serverUrl: options.server } : {},
4322
+ ...options.apiKey ? { apiKey: options.apiKey } : {}
4323
+ };
4324
+ const result = await runLifecycleCommandAsync(
4325
+ {
4326
+ domain: "projects",
4327
+ command,
4328
+ args
4329
+ },
4330
+ apiOptions
4331
+ );
4332
+ if (options.json) {
4333
+ writeJson4(result);
4334
+ return;
4335
+ }
4336
+ log.success(`projects ${command} completed`);
4337
+ };
4338
+ var exitWithError4 = (error) => {
4339
+ const message = error instanceof Error ? error.message : String(error);
4340
+ log.error(message);
4341
+ process10.exit(1);
4342
+ };
4343
+ async function projectsGetCommand(options) {
4344
+ try {
4345
+ const id = options.id ?? fail3("projects get requires --id");
4346
+ await run3("get", { id }, options);
4347
+ } catch (error) {
4348
+ exitWithError4(error);
4349
+ }
4350
+ }
4351
+ async function projectsCreateCommand(options) {
4352
+ try {
4353
+ const name = options.name ?? fail3("projects create requires --name");
4354
+ await run3(
4355
+ "create",
4356
+ { name, description: options.description, orgId: options.orgId },
4357
+ options
4358
+ );
4359
+ } catch (error) {
4360
+ exitWithError4(error);
4361
+ }
4362
+ }
4363
+ async function projectsUpdateCommand(options) {
4364
+ try {
4365
+ const id = options.id ?? fail3("projects update requires --id");
4366
+ await run3(
4367
+ "update",
4368
+ {
4369
+ id,
4370
+ name: options.name,
4371
+ description: options.description
4372
+ },
4373
+ options
4374
+ );
4375
+ } catch (error) {
4376
+ exitWithError4(error);
4377
+ }
4378
+ }
4379
+ async function projectsRemoveCommand(options) {
4380
+ try {
4381
+ const id = options.id ?? fail3("projects remove requires --id");
4382
+ if (!options.yes) {
4383
+ fail3("projects remove requires --yes");
4384
+ }
4385
+ await run3("remove", { id, yes: true }, options);
4386
+ } catch (error) {
4387
+ exitWithError4(error);
4388
+ }
4389
+ }
4390
+
4391
+ // src/commands/references.ts
4392
+ init_esm_shims();
4393
+ init_api();
4394
+ init_tui();
4395
+ import process11 from "process";
4396
+ var writeJson5 = (value) => {
4397
+ process11.stdout.write(`${JSON.stringify(value, null, 2)}
4398
+ `);
4399
+ };
4400
+ var parseCsv3 = (input) => {
4401
+ if (!input) {
4402
+ return void 0;
4403
+ }
4404
+ const values = input.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
4405
+ return values.length > 0 ? values : void 0;
4406
+ };
4407
+ var exitWithError5 = (error) => {
4408
+ const message = error instanceof Error ? error.message : String(error);
4409
+ log.error(message);
4410
+ process11.exit(1);
4411
+ };
4412
+ var fail4 = (message) => {
4413
+ throw new Error(message);
4414
+ };
4415
+ var run4 = async (command, args, options) => {
4416
+ const apiOptions = {
4417
+ ...options.server ? { serverUrl: options.server } : {},
4418
+ ...options.apiKey ? { apiKey: options.apiKey } : {}
4419
+ };
4420
+ const result = await runLifecycleCommandAsync(
4421
+ {
4422
+ domain: "references",
4423
+ command,
4424
+ args
4425
+ },
4426
+ apiOptions
4427
+ );
4428
+ if (options.json) {
4429
+ writeJson5(result);
4430
+ return;
4431
+ }
4432
+ log.success(`references ${command} completed`);
4433
+ };
4434
+ async function referencesListCommand(options) {
2790
4435
  try {
2791
- await writeEntry(selectedAgent, configPath, env);
2792
- mcpSpinner.stop(`Configured ${selectedAgent.name}`);
2793
- log.info(`Config written to ${configPath}`);
2794
- if (!token) {
2795
- log.info("Set BRAID_TOKEN environment variable before using.");
2796
- }
2797
- outro(`Done! Restart ${selectedAgent.name} to activate.`);
4436
+ const ruleId = options.ruleId ?? fail4("references list requires --rule-id");
4437
+ await run4("list", { ruleId }, options);
2798
4438
  } catch (error) {
2799
- mcpSpinner.stop("Configuration failed");
2800
- const message = error instanceof Error ? error.message : String(error);
2801
- log.error(`Failed to write config: ${message}`);
2802
- process7.exit(1);
4439
+ exitWithError5(error);
2803
4440
  }
2804
4441
  }
2805
- async function removeFlow(options) {
2806
- const mcpAgents = getMcpAgents();
2807
- const selectedAgent = await selectTool(mcpAgents, options.tool);
2808
- const scope = resolveScope(selectedAgent, options);
2809
- const configPath = resolveMcpConfigPath(selectedAgent, {
2810
- global: scope === "global"
2811
- });
2812
- if (!configPath) {
2813
- log.error(`No ${scope} config path available for ${selectedAgent.name}.`);
2814
- process7.exit(1);
4442
+ async function referencesGetCommand(options) {
4443
+ try {
4444
+ const id = options.id ?? fail4("references get requires --id");
4445
+ await run4("get", { id }, options);
4446
+ } catch (error) {
4447
+ exitWithError5(error);
2815
4448
  }
2816
- const removeSpinner = spinner();
2817
- removeSpinner.start(`Removing braid from ${selectedAgent.name}...`);
4449
+ }
4450
+ async function referencesCreateCommand(options) {
2818
4451
  try {
2819
- const rootKey = selectedAgent.mcpRootKey ?? "mcpServers";
2820
- const existingConfig = await readMcpConfigAsync(configPath);
2821
- if (!hasbraidEntry(existingConfig, rootKey, BRAID_ENTRY_NAME)) {
2822
- removeSpinner.stop("No braid MCP entry found");
2823
- log.info(
2824
- `${selectedAgent.name} doesn't have a braid MCP entry at ${configPath}`
2825
- );
2826
- return;
2827
- }
2828
- const updatedConfig = removebraidEntry(
2829
- existingConfig,
2830
- rootKey,
2831
- BRAID_ENTRY_NAME
4452
+ const ruleId = options.ruleId ?? fail4("references create requires --rule-id");
4453
+ const file = options.file ?? fail4("references create requires --file");
4454
+ await run4("create", { ruleId, file }, options);
4455
+ } catch (error) {
4456
+ exitWithError5(error);
4457
+ }
4458
+ }
4459
+ async function referencesUpdateCommand(options) {
4460
+ try {
4461
+ const id = options.id ?? fail4("references update requires --id");
4462
+ await run4(
4463
+ "update",
4464
+ { id, label: options.label, replaceFile: options.replaceFile },
4465
+ options
2832
4466
  );
2833
- await writeMcpConfigAsync(configPath, updatedConfig);
2834
- removeSpinner.stop(`Removed braid from ${selectedAgent.name}`);
2835
- outro(`braid MCP removed from ${configPath}`);
2836
4467
  } catch (error) {
2837
- removeSpinner.stop("Removal failed");
2838
- const message = error instanceof Error ? error.message : String(error);
2839
- log.error(`Failed to remove config: ${message}`);
2840
- process7.exit(1);
4468
+ exitWithError5(error);
2841
4469
  }
2842
4470
  }
2843
- async function statusFlow() {
2844
- const mcpAgents = getMcpAgents();
2845
- const statusSpinner = spinner();
2846
- statusSpinner.start("Checking MCP configuration status...");
2847
- const results = [];
2848
- for (const agent of mcpAgents) {
2849
- const rootKey = agent.mcpRootKey ?? "mcpServers";
2850
- for (const scope of ["project", "global"]) {
2851
- const configPath = resolveMcpConfigPath(agent, {
2852
- global: scope === "global"
2853
- });
2854
- if (!configPath) {
2855
- continue;
2856
- }
2857
- const config = await readMcpConfigAsync(configPath);
2858
- if (hasbraidEntry(config, rootKey, BRAID_ENTRY_NAME)) {
2859
- results.push({
2860
- name: agent.name,
2861
- path: configPath,
2862
- scope
2863
- });
2864
- }
4471
+ async function referencesRemoveCommand(options) {
4472
+ try {
4473
+ const id = options.id ?? fail4("references remove requires --id");
4474
+ if (!options.yes) {
4475
+ fail4("references remove requires --yes");
2865
4476
  }
2866
- }
2867
- statusSpinner.stop(`Found ${results.length} configured tool(s)`);
2868
- if (results.length === 0) {
2869
- log.info("No tools have braid MCP configured.");
2870
- log.info("Run 'braid mcp' to set up MCP for your AI tools.");
2871
- return;
2872
- }
2873
- for (const result of results) {
2874
- log.info(` ${result.name} (${result.scope}) \u2192 ${result.path}`);
4477
+ await run4("remove", { id, yes: true }, options);
4478
+ } catch (error) {
4479
+ exitWithError5(error);
2875
4480
  }
2876
4481
  }
2877
- async function mcpCommand(options) {
2878
- if (options.status) {
2879
- intro("braid mcp status");
2880
- await statusFlow();
2881
- return;
2882
- }
2883
- if (options.remove) {
2884
- intro("braid mcp remove");
2885
- await removeFlow(options);
2886
- return;
4482
+ async function referencesReorderCommand(options) {
4483
+ try {
4484
+ const ruleId = options.ruleId ?? fail4("references reorder requires --rule-id");
4485
+ const orderedIds = parseCsv3(options.orderedIds);
4486
+ if (!orderedIds || orderedIds.length === 0) {
4487
+ fail4("references reorder requires --ordered-ids");
4488
+ }
4489
+ await run4("reorder", { ruleId, orderedIds }, options);
4490
+ } catch (error) {
4491
+ exitWithError5(error);
2887
4492
  }
2888
- intro("braid mcp");
2889
- await addFlow(options);
2890
4493
  }
2891
4494
 
2892
4495
  // src/commands/remove.ts
2893
4496
  init_esm_shims();
2894
4497
  import { rm } from "fs/promises";
2895
- import { join as join4, resolve as resolve3 } from "path";
2896
- import process8 from "process";
4498
+ import { join as join4, resolve as resolve4 } from "path";
4499
+ import process12 from "process";
2897
4500
  init_tui();
2898
4501
  async function collectInstalledSkills(detectedAgents, options) {
2899
4502
  const skillsToRemove = [];
@@ -2926,7 +4529,7 @@ async function selectSkillsToRemove(skillsToRemove, options) {
2926
4529
  if (selected.length === 0) {
2927
4530
  log.error(`Skill '${options.skill}' not found.`);
2928
4531
  log.info("Run 'braid list' to see installed skills.");
2929
- process8.exit(1);
4532
+ process12.exit(1);
2930
4533
  }
2931
4534
  return selected;
2932
4535
  }
@@ -2945,7 +4548,7 @@ async function selectSkillsToRemove(skillsToRemove, options) {
2945
4548
  });
2946
4549
  if (isCancel(result)) {
2947
4550
  cancel("Remove cancelled.");
2948
- process8.exit(0);
4551
+ process12.exit(0);
2949
4552
  }
2950
4553
  return result;
2951
4554
  }
@@ -2959,14 +4562,14 @@ async function confirmRemoval(selectedCount, options) {
2959
4562
  });
2960
4563
  if (isCancel(confirmed) || !confirmed) {
2961
4564
  cancel("Remove cancelled.");
2962
- process8.exit(0);
4565
+ process12.exit(0);
2963
4566
  }
2964
4567
  }
2965
4568
  async function removeSkill(skill, removeSpinner) {
2966
4569
  removeSpinner.start(`Removing ${skill.name} from ${skill.agentName}...`);
2967
4570
  try {
2968
- const resolvedSkillPath = resolve3(skill.skillPath);
2969
- const resolvedInstallPath = resolve3(skill.installPath);
4571
+ const resolvedSkillPath = resolve4(skill.skillPath);
4572
+ const resolvedInstallPath = resolve4(skill.installPath);
2970
4573
  if (!resolvedSkillPath.startsWith(`${resolvedInstallPath}/`)) {
2971
4574
  removeSpinner.stop(`Unsafe path for ${skill.name}`);
2972
4575
  log.warn(" Skill path escapes install directory, skipping.");
@@ -3023,7 +4626,203 @@ async function removeCommand(options) {
3023
4626
  removeSpinner.stop("Remove failed");
3024
4627
  const message = error instanceof Error ? error.message : String(error);
3025
4628
  log.error(message);
3026
- process8.exit(1);
4629
+ process12.exit(1);
4630
+ }
4631
+ }
4632
+
4633
+ // src/commands/rules.ts
4634
+ init_esm_shims();
4635
+ init_api();
4636
+ init_tui();
4637
+ import process13 from "process";
4638
+ var parseCsv4 = (input) => {
4639
+ if (!input) {
4640
+ return void 0;
4641
+ }
4642
+ const values = input.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
4643
+ return values.length > 0 ? values : void 0;
4644
+ };
4645
+ var writeJson6 = (value) => {
4646
+ process13.stdout.write(`${JSON.stringify(value, null, 2)}
4647
+ `);
4648
+ };
4649
+ var exitWithError6 = (error) => {
4650
+ const message = error instanceof Error ? error.message : String(error);
4651
+ log.error(message);
4652
+ process13.exit(1);
4653
+ };
4654
+ var fail5 = (message) => {
4655
+ throw new Error(message);
4656
+ };
4657
+ var run5 = async (command, args, options) => {
4658
+ const apiOptions = {
4659
+ ...options.server ? { serverUrl: options.server } : {},
4660
+ ...options.apiKey ? { apiKey: options.apiKey } : {}
4661
+ };
4662
+ const result = await runLifecycleCommandAsync(
4663
+ {
4664
+ domain: "rules",
4665
+ command,
4666
+ args
4667
+ },
4668
+ apiOptions
4669
+ );
4670
+ if (options.json) {
4671
+ writeJson6(result);
4672
+ return;
4673
+ }
4674
+ log.success(`rules ${command} completed`);
4675
+ };
4676
+ async function rulesGetCommand(options) {
4677
+ try {
4678
+ const id = options.id ?? fail5("rules get requires --id");
4679
+ await run5("get", { id }, options);
4680
+ } catch (error) {
4681
+ exitWithError6(error);
4682
+ }
4683
+ }
4684
+ async function rulesCreateCommand(options) {
4685
+ try {
4686
+ const title = options.title ?? fail5("rules create requires --title");
4687
+ const content = options.content ?? fail5("rules create requires --content");
4688
+ await run5(
4689
+ "create",
4690
+ {
4691
+ title,
4692
+ content,
4693
+ projectId: options.projectId,
4694
+ tags: parseCsv4(options.tags),
4695
+ priority: options.priority
4696
+ },
4697
+ options
4698
+ );
4699
+ } catch (error) {
4700
+ exitWithError6(error);
4701
+ }
4702
+ }
4703
+ async function rulesUpdateCommand(options) {
4704
+ try {
4705
+ const id = options.id ?? fail5("rules update requires --id");
4706
+ await run5(
4707
+ "update",
4708
+ {
4709
+ id,
4710
+ title: options.title,
4711
+ content: options.content,
4712
+ tags: parseCsv4(options.tags),
4713
+ priority: options.priority
4714
+ },
4715
+ options
4716
+ );
4717
+ } catch (error) {
4718
+ exitWithError6(error);
4719
+ }
4720
+ }
4721
+ async function rulesRemoveCommand(options) {
4722
+ try {
4723
+ const id = options.id ?? fail5("rules remove requires --id");
4724
+ if (!options.yes) {
4725
+ fail5("rules remove requires --yes");
4726
+ }
4727
+ await run5("remove", { id, yes: true }, options);
4728
+ } catch (error) {
4729
+ exitWithError6(error);
4730
+ }
4731
+ }
4732
+ async function rulesEnableCommand(options) {
4733
+ try {
4734
+ const id = options.id ?? fail5("rules enable requires --id");
4735
+ await run5("enable", { id }, options);
4736
+ } catch (error) {
4737
+ exitWithError6(error);
4738
+ }
4739
+ }
4740
+ async function rulesDisableCommand(options) {
4741
+ try {
4742
+ const id = options.id ?? fail5("rules disable requires --id");
4743
+ await run5("disable", { id }, options);
4744
+ } catch (error) {
4745
+ exitWithError6(error);
4746
+ }
4747
+ }
4748
+ async function rulesMoveCommand(options) {
4749
+ try {
4750
+ const id = options.id ?? fail5("rules move requires --id");
4751
+ const projectId = options.projectId ?? fail5("rules move requires --project-id");
4752
+ await run5("move", { id, projectId }, options);
4753
+ } catch (error) {
4754
+ exitWithError6(error);
4755
+ }
4756
+ }
4757
+ async function rulesDuplicateCommand(options) {
4758
+ try {
4759
+ const id = options.id ?? fail5("rules duplicate requires --id");
4760
+ await run5(
4761
+ "duplicate",
4762
+ { id, targetProjectId: options.targetProjectId },
4763
+ options
4764
+ );
4765
+ } catch (error) {
4766
+ exitWithError6(error);
4767
+ }
4768
+ }
4769
+ async function rulesForkCommand(options) {
4770
+ try {
4771
+ const id = options.id ?? fail5("rules fork requires --id");
4772
+ await run5(
4773
+ "fork",
4774
+ { id, targetProjectId: options.targetProjectId },
4775
+ options
4776
+ );
4777
+ } catch (error) {
4778
+ exitWithError6(error);
4779
+ }
4780
+ }
4781
+ async function rulesSyncStatusCommand(options) {
4782
+ try {
4783
+ await run5("sync-status", { id: options.id }, options);
4784
+ } catch (error) {
4785
+ exitWithError6(error);
4786
+ }
4787
+ }
4788
+ async function rulesSyncHistoryCommand(options) {
4789
+ try {
4790
+ const id = options.id ?? fail5("rules sync-history requires --id");
4791
+ await run5("sync-history", { id }, options);
4792
+ } catch (error) {
4793
+ exitWithError6(error);
4794
+ }
4795
+ }
4796
+ async function rulesSyncEnableCommand(options) {
4797
+ try {
4798
+ const id = options.id ?? fail5("rules sync-enable requires --id");
4799
+ await run5("sync-enable", { id }, options);
4800
+ } catch (error) {
4801
+ exitWithError6(error);
4802
+ }
4803
+ }
4804
+ async function rulesSyncDisableCommand(options) {
4805
+ try {
4806
+ const id = options.id ?? fail5("rules sync-disable requires --id");
4807
+ await run5("sync-disable", { id }, options);
4808
+ } catch (error) {
4809
+ exitWithError6(error);
4810
+ }
4811
+ }
4812
+ async function rulesSyncCheckCommand(options) {
4813
+ try {
4814
+ const id = options.id ?? fail5("rules sync-check requires --id");
4815
+ await run5("sync-check", { id }, options);
4816
+ } catch (error) {
4817
+ exitWithError6(error);
4818
+ }
4819
+ }
4820
+ async function rulesSyncNowCommand(options) {
4821
+ try {
4822
+ const id = options.id ?? fail5("rules sync-now requires --id");
4823
+ await run5("sync-now", { id }, options);
4824
+ } catch (error) {
4825
+ exitWithError6(error);
3027
4826
  }
3028
4827
  }
3029
4828
 
@@ -3040,11 +4839,11 @@ import {
3040
4839
  outro as outro2,
3041
4840
  spinner as spinner3
3042
4841
  } from "@clack/prompts";
3043
- import { Data as Data7, Effect as Effect8, pipe as pipe8 } from "effect";
4842
+ import { Data as Data8, Effect as Effect9, pipe as pipe9 } from "effect";
3044
4843
  init_api();
3045
- var UpdateError = class extends Data7.TaggedError("UpdateError") {
4844
+ var UpdateError = class extends Data8.TaggedError("UpdateError") {
3046
4845
  };
3047
- var UserCancelledError = class extends Data7.TaggedError("UserCancelledError") {
4846
+ var UserCancelledError = class extends Data8.TaggedError("UserCancelledError") {
3048
4847
  };
3049
4848
  async function resolveValidInstallPath(agent, options) {
3050
4849
  const installPath = resolveInstallPath(agent, {
@@ -3056,7 +4855,7 @@ async function resolveValidInstallPath(agent, options) {
3056
4855
  const exists = await directoryExistsAsync(installPath);
3057
4856
  return exists ? installPath : null;
3058
4857
  }
3059
- var collectSourcesFromAgent = (agent, options, sourcesToUpdate) => Effect8.tryPromise({
4858
+ var collectSourcesFromAgent = (agent, options, sourcesToUpdate) => Effect9.tryPromise({
3060
4859
  try: async () => {
3061
4860
  const installPath = await resolveValidInstallPath(agent, options);
3062
4861
  if (!installPath) {
@@ -3087,10 +4886,10 @@ var collectSourcesFromAgent = (agent, options, sourcesToUpdate) => Effect8.tryPr
3087
4886
  },
3088
4887
  catch: () => new UpdateError({ message: "Failed to collect sources" })
3089
4888
  });
3090
- var collectSources = (detectedAgents, options) => pipe8(
3091
- Effect8.succeed(/* @__PURE__ */ new Map()),
3092
- Effect8.tap(
3093
- (sourcesToUpdate) => Effect8.forEach(
4889
+ var collectSources = (detectedAgents, options) => pipe9(
4890
+ Effect9.succeed(/* @__PURE__ */ new Map()),
4891
+ Effect9.tap(
4892
+ (sourcesToUpdate) => Effect9.forEach(
3094
4893
  detectedAgents,
3095
4894
  (agent) => collectSourcesFromAgent(agent, options, sourcesToUpdate),
3096
4895
  { concurrency: 1 }
@@ -3099,9 +4898,9 @@ var collectSources = (detectedAgents, options) => pipe8(
3099
4898
  );
3100
4899
  var selectSources = (sourcesToUpdate, options) => {
3101
4900
  if (options.yes) {
3102
- return Effect8.succeed(sourcesToUpdate);
4901
+ return Effect9.succeed(sourcesToUpdate);
3103
4902
  }
3104
- return Effect8.tryPromise({
4903
+ return Effect9.tryPromise({
3105
4904
  try: async () => {
3106
4905
  const sources = Array.from(sourcesToUpdate.entries()).map(
3107
4906
  ([key, source]) => ({
@@ -3152,7 +4951,7 @@ var buildFetchOptionsForSource = (source, options) => {
3152
4951
  }
3153
4952
  return fetchOptions;
3154
4953
  };
3155
- var updateAgentSkills = (agentId, agentName, installPath, response, serverUrl, updateSpinner) => Effect8.tryPromise({
4954
+ var updateAgentSkills = (agentId, agentName, installPath, response, serverUrl, updateSpinner) => Effect9.tryPromise({
3156
4955
  try: async () => {
3157
4956
  updateSpinner.start(`Updating ${agentName}...`);
3158
4957
  const result = await writeSkillsAsync(
@@ -3190,15 +4989,15 @@ var updateSource = (source, options, updateSpinner) => {
3190
4989
  const serverUrl = options.server ?? source.serverUrl;
3191
4990
  const fetchOptions = buildFetchOptionsForSource(source, options);
3192
4991
  if (fetchOptions === null) {
3193
- return Effect8.fail(
4992
+ return Effect9.fail(
3194
4993
  new UpdateError({
3195
4994
  message: "Skills installed with legacy metadata format. Please reinstall using 'braid install --profile <name>' or 'braid install --projects <names>'.",
3196
4995
  source: sourceDesc
3197
4996
  })
3198
4997
  );
3199
4998
  }
3200
- return pipe8(
3201
- Effect8.tryPromise({
4999
+ return pipe9(
5000
+ Effect9.tryPromise({
3202
5001
  try: async () => {
3203
5002
  updateSpinner.start(`Fetching latest skills from ${sourceDesc}...`);
3204
5003
  const response = await fetchSkillsAsync(fetchOptions);
@@ -3212,9 +5011,9 @@ var updateSource = (source, options, updateSpinner) => {
3212
5011
  source: sourceDesc
3213
5012
  })
3214
5013
  }),
3215
- Effect8.flatMap(
3216
- (response) => pipe8(
3217
- Effect8.forEach(
5014
+ Effect9.flatMap(
5015
+ (response) => pipe9(
5016
+ Effect9.forEach(
3218
5017
  source.agents,
3219
5018
  ({ agentId, agentName, installPath }) => updateAgentSkills(
3220
5019
  agentId,
@@ -3226,7 +5025,7 @@ var updateSource = (source, options, updateSpinner) => {
3226
5025
  ),
3227
5026
  { concurrency: 1 }
3228
5027
  ),
3229
- Effect8.map((results) => ({
5028
+ Effect9.map((results) => ({
3230
5029
  updated: results.reduce((sum, r) => sum + r.updated, 0),
3231
5030
  errors: results.reduce((sum, r) => sum + r.errors, 0)
3232
5031
  }))
@@ -3234,22 +5033,22 @@ var updateSource = (source, options, updateSpinner) => {
3234
5033
  )
3235
5034
  );
3236
5035
  };
3237
- var updateAllSources = (sources, options, updateSpinner) => pipe8(
3238
- Effect8.forEach(
5036
+ var updateAllSources = (sources, options, updateSpinner) => pipe9(
5037
+ Effect9.forEach(
3239
5038
  Array.from(sources.values()),
3240
- (source) => pipe8(
5039
+ (source) => pipe9(
3241
5040
  updateSource(source, options, updateSpinner),
3242
- Effect8.catchAll((error) => {
5041
+ Effect9.catchAll((error) => {
3243
5042
  updateSpinner.stop(
3244
5043
  `Failed to update from ${getSourceDesc(source)}`
3245
5044
  );
3246
5045
  log3.error(` ${error.message}`);
3247
- return Effect8.succeed({ updated: 0, errors: 1 });
5046
+ return Effect9.succeed({ updated: 0, errors: 1 });
3248
5047
  })
3249
5048
  ),
3250
5049
  { concurrency: 1 }
3251
5050
  ),
3252
- Effect8.map((results) => ({
5051
+ Effect9.map((results) => ({
3253
5052
  totalUpdated: results.reduce((sum, r) => sum + r.updated, 0),
3254
5053
  totalErrors: results.reduce((sum, r) => sum + r.errors, 0)
3255
5054
  }))
@@ -3288,28 +5087,28 @@ var handleProgramExit = (result, updateSpinner) => {
3288
5087
  async function updateCommand(options) {
3289
5088
  const updateSpinner = spinner3();
3290
5089
  updateSpinner.start("Scanning for installed skills...");
3291
- const program2 = pipe8(
3292
- Effect8.tryPromise({
5090
+ const program2 = pipe9(
5091
+ Effect9.tryPromise({
3293
5092
  try: () => detectAgentsAsync(),
3294
5093
  catch: () => new UpdateError({ message: "Failed to detect agents" })
3295
5094
  }),
3296
- Effect8.filterOrFail(
3297
- (agents) => agents.length > 0,
5095
+ Effect9.filterOrFail(
5096
+ (agents2) => agents2.length > 0,
3298
5097
  () => new UpdateError({ message: "No AI coding agents detected." })
3299
5098
  ),
3300
- Effect8.flatMap((detectedAgents) => collectSources(detectedAgents, options)),
3301
- Effect8.tap((sources) => {
5099
+ Effect9.flatMap((detectedAgents) => collectSources(detectedAgents, options)),
5100
+ Effect9.tap((sources) => {
3302
5101
  updateSpinner.stop(`Found ${sources.size} source(s) to update`);
3303
5102
  }),
3304
- Effect8.filterOrFail(
5103
+ Effect9.filterOrFail(
3305
5104
  (sources) => sources.size > 0,
3306
5105
  () => new UpdateError({ message: "No skills installed via braid." })
3307
5106
  ),
3308
- Effect8.flatMap((sources) => selectSources(sources, options)),
3309
- Effect8.flatMap(
5107
+ Effect9.flatMap((sources) => selectSources(sources, options)),
5108
+ Effect9.flatMap(
3310
5109
  (selectedSources) => updateAllSources(selectedSources, options, updateSpinner)
3311
5110
  ),
3312
- Effect8.tap(({ totalUpdated, totalErrors }) => {
5111
+ Effect9.tap(({ totalUpdated, totalErrors }) => {
3313
5112
  if (totalErrors > 0) {
3314
5113
  outro2(`Updated ${totalUpdated} skills with ${totalErrors} errors.`);
3315
5114
  } else {
@@ -3317,10 +5116,136 @@ async function updateCommand(options) {
3317
5116
  }
3318
5117
  })
3319
5118
  );
3320
- const result = await Effect8.runPromiseExit(program2);
5119
+ const result = await Effect9.runPromiseExit(program2);
3321
5120
  handleProgramExit(result, updateSpinner);
3322
5121
  }
3323
5122
 
5123
+ // src/commands/workflows.ts
5124
+ init_esm_shims();
5125
+ init_api();
5126
+ init_tui();
5127
+ import process14 from "process";
5128
+ var writeJson7 = (value) => {
5129
+ process14.stdout.write(`${JSON.stringify(value, null, 2)}
5130
+ `);
5131
+ };
5132
+ var fail6 = (message) => {
5133
+ throw new Error(message);
5134
+ };
5135
+ var parseMetadata = (metadata) => {
5136
+ if (!metadata) {
5137
+ return void 0;
5138
+ }
5139
+ try {
5140
+ return JSON.parse(metadata);
5141
+ } catch {
5142
+ throw new Error("metadata must be valid JSON");
5143
+ }
5144
+ };
5145
+ var run6 = async (command, args, options) => {
5146
+ const apiOptions = {
5147
+ ...options.server ? { serverUrl: options.server } : {},
5148
+ ...options.apiKey ? { apiKey: options.apiKey } : {}
5149
+ };
5150
+ const result = await runLifecycleCommandAsync(
5151
+ {
5152
+ domain: "workflows",
5153
+ command,
5154
+ args
5155
+ },
5156
+ apiOptions
5157
+ );
5158
+ if (options.json) {
5159
+ writeJson7(result);
5160
+ return;
5161
+ }
5162
+ log.success(`workflows ${command} completed`);
5163
+ };
5164
+ var exitWithError7 = (error) => {
5165
+ const message = error instanceof Error ? error.message : String(error);
5166
+ log.error(message);
5167
+ process14.exit(1);
5168
+ };
5169
+ async function workflowsListCommand(options) {
5170
+ try {
5171
+ await run6("list", { projectId: options.projectId }, options);
5172
+ } catch (error) {
5173
+ exitWithError7(error);
5174
+ }
5175
+ }
5176
+ async function workflowsStartCommand(options) {
5177
+ try {
5178
+ const sessionId = options.sessionId ?? fail6("workflows start requires --session-id");
5179
+ const flowId = options.flowId ?? fail6("workflows start requires --flow-id");
5180
+ await run6(
5181
+ "start",
5182
+ {
5183
+ sessionId,
5184
+ flowId,
5185
+ currentNodeId: options.currentNodeId,
5186
+ currentStepLabel: options.currentStepLabel,
5187
+ metadata: parseMetadata(options.metadata)
5188
+ },
5189
+ options
5190
+ );
5191
+ } catch (error) {
5192
+ exitWithError7(error);
5193
+ }
5194
+ }
5195
+ async function workflowsProgressCommand(options) {
5196
+ try {
5197
+ const executionId = options.executionId ?? fail6("workflows progress requires --execution-id");
5198
+ await run6(
5199
+ "progress",
5200
+ {
5201
+ executionId,
5202
+ currentNodeId: options.currentNodeId,
5203
+ currentStepLabel: options.currentStepLabel,
5204
+ metadata: parseMetadata(options.metadata)
5205
+ },
5206
+ options
5207
+ );
5208
+ } catch (error) {
5209
+ exitWithError7(error);
5210
+ }
5211
+ }
5212
+ async function workflowsActiveCommand(options) {
5213
+ try {
5214
+ const sessionId = options.sessionId ?? fail6("workflows active requires --session-id");
5215
+ await run6("active", { sessionId }, options);
5216
+ } catch (error) {
5217
+ exitWithError7(error);
5218
+ }
5219
+ }
5220
+ async function workflowsCompleteCommand(options) {
5221
+ try {
5222
+ const executionId = options.executionId ?? fail6("workflows complete requires --execution-id");
5223
+ await run6("complete", { executionId }, options);
5224
+ } catch (error) {
5225
+ exitWithError7(error);
5226
+ }
5227
+ }
5228
+ async function workflowsFailCommand(options) {
5229
+ try {
5230
+ const executionId = options.executionId ?? fail6("workflows fail requires --execution-id");
5231
+ await run6(
5232
+ "fail",
5233
+ { executionId, errorMessage: options.errorMessage },
5234
+ options
5235
+ );
5236
+ } catch (error) {
5237
+ exitWithError7(error);
5238
+ }
5239
+ }
5240
+ async function workflowsCancelCommand(options) {
5241
+ try {
5242
+ const executionId = options.executionId ?? fail6("workflows cancel requires --execution-id");
5243
+ await run6("cancel", { executionId }, options);
5244
+ } catch (error) {
5245
+ exitWithError7(error);
5246
+ }
5247
+ }
5248
+
3324
5249
  // src/index.ts
3325
5250
  var require2 = createRequire(import.meta.url);
3326
5251
  var { version: PACKAGE_VERSION } = require2("../package.json");
@@ -3329,10 +5254,18 @@ program.name("braid").description(
3329
5254
  "Install braid prompts as agent skills to your local development environment"
3330
5255
  ).version(PACKAGE_VERSION);
3331
5256
  var auth = program.command("auth").description("Configure API key for braid authentication");
3332
- auth.command("login", { isDefault: true }).description("Configure API key").option("-s, --server <url>", "braid server URL (for review apps, local dev)").action(authCommand);
5257
+ auth.command("login", { isDefault: true }).description("Authenticate with braid via browser login or API key").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option(
5258
+ "--token <token>",
5259
+ "API key for non-interactive login (skips device flow)"
5260
+ ).option(
5261
+ "--timeout <seconds>",
5262
+ "Device flow timeout in seconds (default: 300)"
5263
+ ).option("--no-scope", "Skip scope setup after login").action(authCommand);
3333
5264
  auth.command("status").description("Show current authentication status").action(authStatusCommand);
3334
5265
  auth.command("logout").description("Remove stored API key").action(authLogoutCommand);
3335
- program.command("install").alias("add").description("Install skills from a profile or project").option("-p, --profile <name>", "Profile name to install from").option(
5266
+ program.command("install").alias("add").description(
5267
+ "Install skills from a profile, project, or public source (@handle/slug)"
5268
+ ).argument("[source]", "Public source to install from (e.g., @handle/slug)").option("-p, --profile <name>", "Profile name to install from").option(
3336
5269
  "--org-projects <ids>",
3337
5270
  "Comma-separated organization project IDs to install from"
3338
5271
  ).option(
@@ -3347,7 +5280,12 @@ program.command("install").alias("add").description("Install skills from a profi
3347
5280
  ).option("--no-include-org-globals", "Exclude organization's global prompts").option(
3348
5281
  "-a, --agents <list>",
3349
5282
  "Comma-separated list of agents (e.g., claude-code,opencode)"
3350
- ).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);
5283
+ ).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((source, options) => {
5284
+ if (source) {
5285
+ return installCommand(source, options);
5286
+ }
5287
+ return installCommand(options);
5288
+ });
3351
5289
  program.command("scope").description("Interactively configure braid.json or braid.user.json scope").option("--file <target>", "Config file target: user or project").option(
3352
5290
  "--organization <type>",
3353
5291
  "Scope organization for non-interactive mode: personal or organization"
@@ -3371,8 +5309,65 @@ program.command("scope").description("Interactively configure braid.json or brai
3371
5309
  "Include org global rules in non-interactive mode"
3372
5310
  ).option("-s, --server <url>", "braid server URL (for review apps, local dev)").action(scopeCommand);
3373
5311
  program.command("list").alias("ls").description("List installed skills").option("-g, --global", "List skills in global directories only").action(listCommand);
5312
+ var projects = program.command("projects").description("Discover available projects from braid");
5313
+ projects.command("list").description("List available personal and org projects").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(projectsListCommand);
5314
+ projects.command("get").description("Get a project by id").requiredOption("--id <id>", "Project ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(projectsGetCommand);
5315
+ projects.command("create").description("Create a project").requiredOption("--name <name>", "Project name").option("--description <description>", "Project description").option("--org-id <id>", "Organization ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(projectsCreateCommand);
5316
+ projects.command("update").description("Update a project").requiredOption("--id <id>", "Project ID").option("--name <name>", "Project name").option("--description <description>", "Project description").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(projectsUpdateCommand);
5317
+ projects.command("remove").description("Remove a project").requiredOption("--id <id>", "Project ID").option("-y, --yes", "Confirm destructive action").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(projectsRemoveCommand);
5318
+ var rules = program.command("rules").description("Discover available rules from braid");
5319
+ rules.command("list").description("List available rules for a scope").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--org-id <id>", "Organization ID").option("--org-projects <ids>", "Comma-separated organization project IDs").option("--personal-projects <ids>", "Comma-separated personal project IDs").option("--include-user-global", "Include personal global rules").option("--include-org-global", "Include org global rules").option("--json", "Output JSON").action(rulesListCommand);
5320
+ rules.command("get").description("Get a rule by id").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesGetCommand);
5321
+ rules.command("create").description("Create a rule").requiredOption("--title <title>", "Rule title").requiredOption("--content <content>", "Rule content").option("--project-id <id>", "Project ID").option("--tags <list>", "Comma-separated tags").option("--priority <priority>", "Priority").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesCreateCommand);
5322
+ rules.command("update").description("Update a rule").requiredOption("--id <id>", "Rule ID").option("--title <title>", "Rule title").option("--content <content>", "Rule content").option("--tags <list>", "Comma-separated tags").option("--priority <priority>", "Priority").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesUpdateCommand);
5323
+ rules.command("remove").description("Remove a rule").requiredOption("--id <id>", "Rule ID").option("-y, --yes", "Confirm destructive action").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesRemoveCommand);
5324
+ rules.command("enable").description("Enable a rule").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesEnableCommand);
5325
+ rules.command("disable").description("Disable a rule").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesDisableCommand);
5326
+ rules.command("move").description("Move a rule to a project").requiredOption("--id <id>", "Rule ID").requiredOption("--project-id <id>", "Project ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesMoveCommand);
5327
+ rules.command("duplicate").description("Duplicate a rule").requiredOption("--id <id>", "Rule ID").option("--target-project-id <id>", "Target project ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesDuplicateCommand);
5328
+ rules.command("fork").description("Fork a rule").requiredOption("--id <id>", "Rule ID").option("--target-project-id <id>", "Target project ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesForkCommand);
5329
+ rules.command("sync-status").description("Show sync status for one rule or all").option("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesSyncStatusCommand);
5330
+ rules.command("sync-history").description("Show sync history for a rule").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesSyncHistoryCommand);
5331
+ rules.command("sync-enable").description("Enable sync for a rule").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesSyncEnableCommand);
5332
+ rules.command("sync-disable").description("Disable sync for a rule").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesSyncDisableCommand);
5333
+ rules.command("sync-check").description("Check for upstream updates").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesSyncCheckCommand);
5334
+ rules.command("sync-now").description("Run sync now").requiredOption("--id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(rulesSyncNowCommand);
5335
+ var skills = program.command("skills").description("Discover available skills from braid");
5336
+ skills.command("list").description("List available skills for a profile or project scope").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("-p, --profile <name>", "Profile name").option("--org-projects <ids>", "Comma-separated organization project IDs").option("--personal-projects <ids>", "Comma-separated personal project IDs").option("--include-user-global", "Include personal global rules").option("--include-org-global", "Include org global rules").option("--json", "Output JSON").action(skillsListCommand);
5337
+ var profiles = program.command("profiles").description("Manage scope profiles");
5338
+ profiles.command("list").description("List profiles").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(profilesListCommand);
5339
+ profiles.command("get").description("Get profile by id or name").option("--id <id>", "Profile ID").option("--name <name>", "Profile name").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(profilesGetCommand);
5340
+ profiles.command("create").description("Create profile").requiredOption("--name <name>", "Profile name").option("--context-json <json>", "Context JSON").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(profilesCreateCommand);
5341
+ profiles.command("update").description("Update profile").requiredOption("--id <id>", "Profile ID").option("--name <name>", "Profile name").option("--context-json <json>", "Context JSON").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(profilesUpdateCommand);
5342
+ profiles.command("remove").description("Remove profile").requiredOption("--id <id>", "Profile ID").option("-y, --yes", "Confirm destructive action").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(profilesRemoveCommand);
5343
+ profiles.command("set-default").description("Set default profile").requiredOption("--id <id>", "Profile ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(profilesSetDefaultCommand);
5344
+ var agents = program.command("agents").description("Manage agent artifacts");
5345
+ var workflows = program.command("workflows").description("Manage local workflow execution lifecycle");
5346
+ workflows.command("list").description("List control flows available for execution").option("--project-id <id>", "Project ID filter").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(workflowsListCommand);
5347
+ workflows.command("start").description("Start a workflow execution for a CLI session").requiredOption("--session-id <id>", "CLI session ID").requiredOption("--flow-id <id>", "Control flow ID").option("--current-node-id <id>", "Current node ID").option("--current-step-label <label>", "Current step label").option("--metadata <json>", "JSON metadata payload").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(workflowsStartCommand);
5348
+ workflows.command("progress").description("Update workflow execution progress").requiredOption("--execution-id <id>", "Workflow execution ID").option("--current-node-id <id>", "Current node ID").option("--current-step-label <label>", "Current step label").option("--metadata <json>", "JSON metadata payload").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(workflowsProgressCommand);
5349
+ workflows.command("active").description("Get active workflow execution for a CLI session").requiredOption("--session-id <id>", "CLI session ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(workflowsActiveCommand);
5350
+ workflows.command("complete").description("Mark a workflow execution complete").requiredOption("--execution-id <id>", "Workflow execution ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(workflowsCompleteCommand);
5351
+ workflows.command("fail").description("Mark a workflow execution failed").requiredOption("--execution-id <id>", "Workflow execution ID").option("--error-message <message>", "Failure message").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(workflowsFailCommand);
5352
+ workflows.command("cancel").description("Cancel a workflow execution").requiredOption("--execution-id <id>", "Workflow execution ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(workflowsCancelCommand);
5353
+ agents.command("list").description("List saved agents").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(agentsListCommand);
5354
+ agents.command("get").description("Get an agent by id").requiredOption("--id <id>", "Agent ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(agentsGetCommand);
5355
+ agents.command("create").description("Create an agent").requiredOption("--name <name>", "Agent name").requiredOption("--description <description>", "Agent description").requiredOption("--prompt <prompt>", "Agent system prompt").option("--scope <scope>", "Scope: global or project").option("--project-id <id>", "Project ID for project-scoped agent").option("--mode <mode>", "Mode: primary, subagent, all").option("--model <model>", "Model override").option("--skills <list>", "Comma-separated skill names").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(agentsCreateCommand);
5356
+ agents.command("update").description("Update an agent").requiredOption("--id <id>", "Agent ID").option("--name <name>", "Agent name").option("--description <description>", "Agent description").option("--prompt <prompt>", "Agent system prompt").option("--mode <mode>", "Mode: primary, subagent, all").option("--model <model>", "Model override").option("--skills <list>", "Comma-separated skill names").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(agentsUpdateCommand);
5357
+ agents.command("remove").description("Remove an agent").requiredOption("--id <id>", "Agent ID").option("-y, --yes", "Confirm destructive action").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(agentsRemoveCommand);
5358
+ agents.command("install").description("Install a saved agent to local coding tools").requiredOption("--id <id>", "Agent ID").option(
5359
+ "-a, --agents <list>",
5360
+ "Comma-separated list of target coding tools (eg. claude-code,opencode)"
5361
+ ).option("-g, --global", "Install to global agent directories").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(agentsInstallCommand);
5362
+ var references = program.command("references").description("Manage rule references");
5363
+ references.command("list").description("List references for a rule").requiredOption("--rule-id <id>", "Rule ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(referencesListCommand);
5364
+ references.command("get").description("Get a reference by id").requiredOption("--id <id>", "Reference ID").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(referencesGetCommand);
5365
+ references.command("create").description("Create a reference from file").requiredOption("--rule-id <id>", "Rule ID").requiredOption("--file <path>", "Reference file path").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(referencesCreateCommand);
5366
+ references.command("update").description("Update reference metadata").requiredOption("--id <id>", "Reference ID").option("--label <label>", "Reference label").option("--replace-file <path>", "Replace reference with file").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(referencesUpdateCommand);
5367
+ references.command("remove").description("Remove reference").requiredOption("--id <id>", "Reference ID").option("-y, --yes", "Confirm destructive action").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(referencesRemoveCommand);
5368
+ references.command("reorder").description("Reorder references").requiredOption("--rule-id <id>", "Rule ID").requiredOption("--ordered-ids <ids>", "Comma-separated reference IDs").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("--json", "Output JSON").action(referencesReorderCommand);
3374
5369
  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);
3375
5370
  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);
3376
- program.command("mcp").description("Configure braid MCP in your AI coding tools").option("-t, --tool <name>", "Tool to configure").option("-g, --global", "Use global config instead of project").option("--token <token>", "API token to embed").option("--no-auth", "Skip authentication setup").option("--scope", "Run interactive scope configuration").option("--remove", "Remove braid MCP from a tool").option("--status", "Show which tools have braid MCP configured").option("-y, --yes", "Skip confirmation prompts").option("-s, --server <url>", "Custom MCP server URL").action(mcpCommand);
5371
+ program.command("mcp").description("Configure braid MCP in your AI coding tools").option("-t, --tool <name>", "Tool to configure").option("-g, --global", "Use global config instead of project").option("--token <token>", "API token to embed").option("--no-auth", "Skip authentication setup").option("--scope", "Run interactive scope configuration").option("--remove", "Remove braid MCP from a tool").option("--status", "Show which tools have braid MCP configured").option("--help-commands", "Show MCP manual command reference").option("-y, --yes", "Skip confirmation prompts").option("-s, --server <url>", "Custom MCP server URL").action(mcpCommand);
3377
5372
  program.parse();
3378
5373
  //# sourceMappingURL=index.js.map