@braid-cloud/cli 0.1.16 → 0.1.17

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
@@ -20,7 +20,7 @@ var init_esm_shims = __esm({
20
20
 
21
21
  // src/lib/braid-workspace.ts
22
22
  import { existsSync, readFileSync } from "fs";
23
- import { dirname as dirname2, join as join2 } from "path";
23
+ import { dirname as dirname3, join as join2 } from "path";
24
24
  import process3 from "process";
25
25
  function readPackageName(packagePath) {
26
26
  if (!existsSync(packagePath)) {
@@ -44,7 +44,7 @@ function findBraidWorkspaceRoot(startDir = process3.cwd()) {
44
44
  if (isBraidWorkspaceRoot(currentDir)) {
45
45
  return currentDir;
46
46
  }
47
- const parentDir = dirname2(currentDir);
47
+ const parentDir = dirname3(currentDir);
48
48
  if (parentDir === currentDir) {
49
49
  return void 0;
50
50
  }
@@ -107,7 +107,7 @@ __export(config_exports, {
107
107
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
108
108
  import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
109
109
  import { homedir as homedir2 } from "os";
110
- import { dirname as dirname3, join as join3, parse } from "path";
110
+ import { dirname as dirname4, join as join3, parse } from "path";
111
111
  import process4 from "process";
112
112
  import { Data as Data2, Effect as Effect3, pipe as pipe3 } from "effect";
113
113
  var CONFIG_DIR, CONFIG_FILE, PROJECT_CONFIG_FILENAME, USER_CONFIG_FILENAME, CONVEX_CLOUD_SUFFIX_REGEX, LOCAL_CONVEX_PORT_REGEX, LOCAL_SERVER_ENV_FILES, LOOPBACK_HOSTS, NEWLINE_REGEX, TRAILING_SLASHES, ConfigReadError, ConfigWriteError, findConfigFile, findProjectConfigFile, findUserConfigFile, stripQuotes, parseDotenv, normalizeBaseUrl, resolveConvexSiteUrl, resolveLocalServerUrlFromEnv, resolveLocalServerUrlFromFiles, resolveLocalServerUrl, loadProjectConfig, loadUserConfig, resolveUserConfigWritePath, resolveProjectConfigWritePath, saveUserConfig, saveProjectConfig, isValidServerUrl, resolveServerUrlFromConfig, applyConfigSource, applyEnvOverrides, createDefaultMergedConfig, loadMergedConfig, loadConfig, saveConfig, getApiKey, setApiKey, getServerUrl, clearApiKey, getDemoContext, setDemoContext, clearDemoContext, loadConfigAsync, loadProjectConfigAsync, loadUserConfigAsync, loadMergedConfigAsync, findProjectConfigFileAsync, findUserConfigFileAsync, saveConfigAsync, saveUserConfigAsync, saveProjectConfigAsync, getApiKeyAsync, setApiKeyAsync, persistApiKeyAsync, getServerUrlAsync, clearApiKeyAsync, getDemoContextAsync, setDemoContextAsync, clearDemoContextAsync;
@@ -256,7 +256,7 @@ var init_config = __esm({
256
256
  return pipe3(
257
257
  Effect3.tryPromise({
258
258
  try: async () => {
259
- await mkdir2(dirname3(targetPath), { recursive: true, mode: 448 });
259
+ await mkdir2(dirname4(targetPath), { recursive: true, mode: 448 });
260
260
  await writeFile2(targetPath, JSON.stringify(config, null, 2), {
261
261
  encoding: "utf-8",
262
262
  mode: 384
@@ -272,7 +272,7 @@ var init_config = __esm({
272
272
  return pipe3(
273
273
  Effect3.tryPromise({
274
274
  try: async () => {
275
- await mkdir2(dirname3(targetPath), { recursive: true, mode: 448 });
275
+ await mkdir2(dirname4(targetPath), { recursive: true, mode: 448 });
276
276
  await writeFile2(targetPath, JSON.stringify(config, null, 2), {
277
277
  encoding: "utf-8",
278
278
  mode: 384
@@ -392,7 +392,7 @@ var init_config = __esm({
392
392
  saveConfig = (config) => pipe3(
393
393
  Effect3.tryPromise({
394
394
  try: async () => {
395
- await mkdir2(dirname3(CONFIG_FILE), { recursive: true, mode: 448 });
395
+ await mkdir2(dirname4(CONFIG_FILE), { recursive: true, mode: 448 });
396
396
  await writeFile2(CONFIG_FILE, JSON.stringify(config, null, 2), {
397
397
  encoding: "utf-8",
398
398
  mode: 384
@@ -1941,7 +1941,7 @@ var writeAgentsForPlatformAsync = (platform2, specs, installPath) => Effect.runP
1941
1941
  init_esm_shims();
1942
1942
  import { access, constants } from "fs/promises";
1943
1943
  import { homedir } from "os";
1944
- import { join } from "path";
1944
+ import { dirname as dirname2, join } from "path";
1945
1945
  import process2 from "process";
1946
1946
  import { Effect as Effect2, pipe as pipe2 } from "effect";
1947
1947
  var home = homedir();
@@ -2585,7 +2585,7 @@ var DeviceAuthExpiredError = class extends Error {
2585
2585
  this.name = "DeviceAuthExpiredError";
2586
2586
  }
2587
2587
  };
2588
- var sleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
2588
+ var sleep = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
2589
2589
  var normalizeBaseUrl2 = (rawUrl) => {
2590
2590
  const trimmed = rawUrl.replace(TRAILING_SLASHES2, "");
2591
2591
  try {
@@ -3288,7 +3288,7 @@ import {
3288
3288
  writeFile as writeFile3
3289
3289
  } from "fs/promises";
3290
3290
  import { homedir as homedir3 } from "os";
3291
- import { basename, dirname as dirname4, join as join5, resolve as resolve2 } from "path";
3291
+ import { basename, dirname as dirname5, join as join5, resolve as resolve2 } from "path";
3292
3292
  var BRAID_CONFIG_DIR = join5(homedir3(), ".config", "braid");
3293
3293
  var DEFAULT_STORE_ROOT = join5(BRAID_CONFIG_DIR, "store", "skills");
3294
3294
  var DEFAULT_DISABLED_ROOT = join5(BRAID_CONFIG_DIR, ".disabled");
@@ -3397,7 +3397,7 @@ var enableBundleAsync = async (originalPath, options = {}) => {
3397
3397
  if (!record) {
3398
3398
  throw new Error(`No disabled bundle found for ${originalPath}`);
3399
3399
  }
3400
- await mkdir3(dirname4(record.originalPath), { recursive: true });
3400
+ await mkdir3(dirname5(record.originalPath), { recursive: true });
3401
3401
  await rm(record.originalPath, { recursive: true, force: true });
3402
3402
  await rename(record.payloadPath, record.originalPath);
3403
3403
  await rm(record.disabledPath, { recursive: true, force: true });
@@ -3414,9 +3414,9 @@ init_lockfile();
3414
3414
  // src/lib/metadata.ts
3415
3415
  init_esm_shims();
3416
3416
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
3417
- import { join as join7 } from "path";
3417
+ import { dirname as dirname6, join as join7 } from "path";
3418
3418
  import { Data as Data5, Effect as Effect6, pipe as pipe6 } from "effect";
3419
- var METADATA_FILENAME2 = ".braidskills-metadata.json";
3419
+ var METADATA_FILENAME2 = ".braid-metadata.json";
3420
3420
  var MetadataReadError = class extends Data5.TaggedError("MetadataReadError") {
3421
3421
  };
3422
3422
  var MetadataWriteError = class extends Data5.TaggedError("MetadataWriteError") {
@@ -3432,7 +3432,7 @@ var normalizeInstalledSkill = (skill) => ({
3432
3432
  var normalizeMetadata = (metadata) => ({
3433
3433
  skills: Array.isArray(metadata?.skills) ? metadata.skills.map(normalizeInstalledSkill) : []
3434
3434
  });
3435
- var getMetadataPath = (skillsDir) => join7(skillsDir, METADATA_FILENAME2);
3435
+ var getMetadataPath = (skillsDir) => join7(dirname6(skillsDir), METADATA_FILENAME2);
3436
3436
  var readMetadata = (skillsDir) => {
3437
3437
  const metadataPath = getMetadataPath(skillsDir);
3438
3438
  return pipe6(
@@ -3449,10 +3449,26 @@ var readMetadata = (skillsDir) => {
3449
3449
  Effect6.orElseSucceed(() => normalizeMetadata(void 0))
3450
3450
  );
3451
3451
  };
3452
+ var readRawJson = async (path2) => {
3453
+ try {
3454
+ const content = await readFile4(path2, "utf-8");
3455
+ const parsed = JSON.parse(content);
3456
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3457
+ return parsed;
3458
+ }
3459
+ } catch (_) {
3460
+ return {};
3461
+ }
3462
+ return {};
3463
+ };
3452
3464
  var writeMetadata = (skillsDir, metadata) => {
3453
3465
  const metadataPath = getMetadataPath(skillsDir);
3454
3466
  return Effect6.tryPromise({
3455
- try: () => writeFile5(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"),
3467
+ try: async () => {
3468
+ const existing = await readRawJson(metadataPath);
3469
+ const merged = { ...existing, skills: metadata.skills };
3470
+ await writeFile5(metadataPath, JSON.stringify(merged, null, 2), "utf-8");
3471
+ },
3456
3472
  catch: (e) => new MetadataWriteError({ path: metadataPath, cause: e })
3457
3473
  });
3458
3474
  };
@@ -3497,7 +3513,7 @@ var removeFromMetadataAsync = (skillsDir, skillName) => Effect6.runPromise(remov
3497
3513
  // src/lib/rule-writer.ts
3498
3514
  init_esm_shims();
3499
3515
  import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
3500
- import { dirname as dirname5, resolve as resolve3, sep as sep2 } from "path";
3516
+ import { dirname as dirname7, resolve as resolve3, sep as sep2 } from "path";
3501
3517
  import { Data as Data6, Effect as Effect7, pipe as pipe7 } from "effect";
3502
3518
  var RuleWriteError = class extends Data6.TaggedError("RuleWriteError") {
3503
3519
  };
@@ -3595,7 +3611,7 @@ ${rule.content}`)
3595
3611
  Effect7.asVoid
3596
3612
  );
3597
3613
  var writeAppendSingleRules = (filePath, rules2) => pipe7(
3598
- createDirectory(dirname5(filePath)),
3614
+ createDirectory(dirname7(filePath)),
3599
3615
  Effect7.flatMap(
3600
3616
  () => pipe7(
3601
3617
  readTextFile(filePath),
@@ -3658,7 +3674,7 @@ var writeRulesForAgentAsync = (agent, rules2, rulesPath) => Effect7.runPromise(w
3658
3674
  init_esm_shims();
3659
3675
  import { createHash as createHash2 } from "crypto";
3660
3676
  import { chmod, mkdir as mkdir5, rm as rm2, symlink, writeFile as writeFile7 } from "fs/promises";
3661
- import { dirname as dirname6, join as join8, resolve as resolve4, sep as sep3 } from "path";
3677
+ import { dirname as dirname8, join as join8, resolve as resolve4, sep as sep3 } from "path";
3662
3678
  import { Data as Data7, Effect as Effect8, pipe as pipe8 } from "effect";
3663
3679
  var WriteError = class extends Data7.TaggedError("WriteError") {
3664
3680
  };
@@ -3739,7 +3755,7 @@ var setExecutableIfScript = (fullPath, file, agentId) => isScriptFile(file.path)
3739
3755
  var writeSkillFile = (basePath, file, agentId) => pipe8(
3740
3756
  assertWithinBase2(basePath, file.path),
3741
3757
  Effect8.flatMap((fullPath) => {
3742
- const dir = dirname6(fullPath);
3758
+ const dir = dirname8(fullPath);
3743
3759
  return pipe8(
3744
3760
  createDirectory2(dir, fullPath),
3745
3761
  Effect8.flatMap(() => writeFileContent(fullPath, file, agentId)),
@@ -3786,7 +3802,7 @@ var writeSkillSymlink = (basePath, skill, agentId, options) => pipe8(
3786
3802
  disabledRoot: options.disabledRoot
3787
3803
  }
3788
3804
  );
3789
- await mkdir5(dirname6(destinationPath), { recursive: true });
3805
+ await mkdir5(dirname8(destinationPath), { recursive: true });
3790
3806
  await rm2(destinationPath, { recursive: true, force: true });
3791
3807
  await symlink(
3792
3808
  storedBundlePath,
@@ -4531,17 +4547,17 @@ init_config();
4531
4547
 
4532
4548
  // src/lib/manage-actions.ts
4533
4549
  init_esm_shims();
4534
- import { rm as rm4 } from "fs/promises";
4535
- import { basename as basename2, dirname as dirname8, join as join10, relative } from "path";
4550
+ import { rm as rm5 } from "fs/promises";
4551
+ import { basename as basename2, dirname as dirname10, join as join11, relative } from "path";
4536
4552
 
4537
4553
  // src/lib/hook-writer.ts
4538
4554
  init_esm_shims();
4539
4555
  import { mkdir as mkdir6, readFile as readFile6, rm as rm3, writeFile as writeFile8 } from "fs/promises";
4540
- import { dirname as dirname7, join as join9 } from "path";
4556
+ import { dirname as dirname9, join as join9 } from "path";
4541
4557
  import { Data as Data8, Effect as Effect9 } from "effect";
4542
4558
  var HookConfigWriteError = class extends Data8.TaggedError("HookConfigWriteError") {
4543
4559
  };
4544
- var HOOK_METADATA_FILENAME = ".braid-hooks-metadata.json";
4560
+ var HOOK_METADATA_FILENAME = ".braid-metadata.json";
4545
4561
  function stableStringify(value) {
4546
4562
  return JSON.stringify(value, (_key, currentValue) => {
4547
4563
  if (currentValue && typeof currentValue === "object" && !Array.isArray(currentValue)) {
@@ -4582,7 +4598,7 @@ async function readJsonFile(path2) {
4582
4598
  }
4583
4599
  }
4584
4600
  async function writeJsonFile(path2, value) {
4585
- await mkdir6(dirname7(path2), { recursive: true, mode: 448 });
4601
+ await mkdir6(dirname9(path2), { recursive: true, mode: 448 });
4586
4602
  await writeFile8(path2, `${JSON.stringify(value, null, 2)}
4587
4603
  `, {
4588
4604
  encoding: "utf-8",
@@ -4681,7 +4697,7 @@ function addHookHandlers(config, hook) {
4681
4697
  return writeMatcherGroups(config, hook.event, groups);
4682
4698
  }
4683
4699
  function getHookMetadataPath(settingsPath) {
4684
- return join9(dirname7(settingsPath), HOOK_METADATA_FILENAME);
4700
+ return join9(dirname9(settingsPath), HOOK_METADATA_FILENAME);
4685
4701
  }
4686
4702
  async function readHookMetadata(settingsPath) {
4687
4703
  const metadataPath = getHookMetadataPath(settingsPath);
@@ -4690,9 +4706,9 @@ async function readHookMetadata(settingsPath) {
4690
4706
  return { hooks };
4691
4707
  }
4692
4708
  async function writeHookMetadata(settingsPath, metadata) {
4693
- await writeJsonFile(getHookMetadataPath(settingsPath), {
4694
- hooks: metadata.hooks
4695
- });
4709
+ const metadataPath = getHookMetadataPath(settingsPath);
4710
+ const existing = await readJsonFile(metadataPath);
4711
+ await writeJsonFile(metadataPath, { ...existing, hooks: metadata.hooks });
4696
4712
  }
4697
4713
  function buildMarketplaceHookSource(sourceSlug) {
4698
4714
  return {
@@ -4790,7 +4806,7 @@ async function listInstalledMarketplaceHookSources(settingsPath) {
4790
4806
  );
4791
4807
  }
4792
4808
  function buildMarketplaceHookBundlePath(settingsPath, sourceSlug) {
4793
- return join9(dirname7(settingsPath), ".braid-hooks", `${sourceSlug}.json`);
4809
+ return join9(dirname9(settingsPath), ".braid-hooks", `${sourceSlug}.json`);
4794
4810
  }
4795
4811
  function buildMarketplaceHookBundle(hooks, options) {
4796
4812
  return {
@@ -5042,6 +5058,8 @@ var fetchMarketplaceInstallManifestAsync = (slug, options) => Effect10.runPromis
5042
5058
 
5043
5059
  // src/lib/marketplace-installer.ts
5044
5060
  init_esm_shims();
5061
+ import { rm as rm4 } from "fs/promises";
5062
+ import { join as join10 } from "path";
5045
5063
  function parseAgentIds(value) {
5046
5064
  if (!value) {
5047
5065
  return [];
@@ -5184,6 +5202,58 @@ async function installHooksForAgent(args) {
5184
5202
  args.targets.push(`${args.agent.name} hooks -> ${hookConfigPath}`);
5185
5203
  return true;
5186
5204
  }
5205
+ async function pruneStaleMarketplaceSkills(installPath, slug, currentSkillNames) {
5206
+ const metadata = await readMetadataAsync(installPath);
5207
+ const stale = metadata.skills.filter(
5208
+ (skill) => skill.source.type === "marketplace" && skill.source.slug === slug && !currentSkillNames.has(skill.name)
5209
+ );
5210
+ for (const skill of stale) {
5211
+ const skillPath = join10(installPath, skill.name);
5212
+ await rm4(skillPath, { recursive: true, force: true });
5213
+ await removeFromMetadataAsync(installPath, skill.name);
5214
+ }
5215
+ }
5216
+ async function installAllForAgent(args) {
5217
+ await installSkillsForAgent({
5218
+ agent: args.agent,
5219
+ manifest: args.manifest,
5220
+ slug: args.slug,
5221
+ skills: args.skills,
5222
+ global: args.global,
5223
+ installedSkills: args.installedSkills,
5224
+ targets: args.targets
5225
+ });
5226
+ await installRulesForAgent({
5227
+ agent: args.agent,
5228
+ rules: args.rules,
5229
+ global: args.global,
5230
+ targets: args.targets
5231
+ });
5232
+ await installAgentsForAgent({
5233
+ agent: args.agent,
5234
+ agents: args.agents,
5235
+ global: args.global,
5236
+ targets: args.targets
5237
+ });
5238
+ if (args.installHooks) {
5239
+ await installHooksForAgent({
5240
+ agent: args.agent,
5241
+ hooks: args.hooks,
5242
+ slug: args.slug,
5243
+ manifest: args.manifest,
5244
+ global: args.global,
5245
+ targets: args.targets
5246
+ });
5247
+ }
5248
+ const installPath = resolveInstallPath(args.agent, { global: args.global });
5249
+ if (installPath) {
5250
+ await pruneStaleMarketplaceSkills(
5251
+ installPath,
5252
+ args.slug,
5253
+ args.installedSkills
5254
+ );
5255
+ }
5256
+ }
5187
5257
  async function installMarketplaceSkillSet(options) {
5188
5258
  if (options.manifest.blocked) {
5189
5259
  throw new Error(
@@ -5195,12 +5265,8 @@ async function installMarketplaceSkillSet(options) {
5195
5265
  const hooks = options.manifest.manifest?.hooks ?? [];
5196
5266
  const agents2 = options.manifest.manifest?.agents ?? [];
5197
5267
  const workflows = options.manifest.manifest?.workflows ?? [];
5198
- if (hooks.length > 0 && !options.allowHooks) {
5199
- throw new Error(
5200
- "Marketplace packs with hooks require explicit opt-in. Re-run with --allow-hooks after reviewing the hook commands."
5201
- );
5202
- }
5203
- if (resolvedSkills.length === 0 && rules2.length === 0 && hooks.length === 0 && agents2.length === 0 && workflows.length === 0) {
5268
+ const installHooks = hooks.length > 0 && options.allowHooks === true;
5269
+ if (resolvedSkills.length === 0 && rules2.length === 0 && (!installHooks || hooks.length === 0) && agents2.length === 0 && workflows.length === 0) {
5204
5270
  throw new Error("No installable content found in manifest");
5205
5271
  }
5206
5272
  const targetAgents = await resolveTargetAgents(options.agents);
@@ -5212,33 +5278,17 @@ async function installMarketplaceSkillSet(options) {
5212
5278
  const targets = [];
5213
5279
  const installedSkills = /* @__PURE__ */ new Set();
5214
5280
  for (const agent of targetAgents) {
5215
- await installSkillsForAgent({
5281
+ await installAllForAgent({
5216
5282
  agent,
5217
5283
  manifest: options.manifest,
5218
5284
  slug: options.slug,
5219
5285
  skills: resolvedSkills,
5220
- global: options.global,
5221
- installedSkills,
5222
- targets
5223
- });
5224
- await installRulesForAgent({
5225
- agent,
5226
5286
  rules: rules2,
5227
- global: options.global,
5228
- targets
5229
- });
5230
- await installAgentsForAgent({
5231
- agent,
5232
5287
  agents: agents2,
5233
- global: options.global,
5234
- targets
5235
- });
5236
- await installHooksForAgent({
5237
- agent,
5238
5288
  hooks,
5239
- slug: options.slug,
5240
- manifest: options.manifest,
5289
+ installHooks,
5241
5290
  global: options.global,
5291
+ installedSkills,
5242
5292
  targets
5243
5293
  });
5244
5294
  }
@@ -5257,12 +5307,12 @@ function isMarketplaceHookBundlePath(originalPath) {
5257
5307
  return getHookBundleRoot(originalPath) !== null;
5258
5308
  }
5259
5309
  function getHookBundleRoot(originalPath) {
5260
- let currentPath = dirname8(originalPath);
5310
+ let currentPath = dirname10(originalPath);
5261
5311
  while (true) {
5262
5312
  if (basename2(currentPath) === ".braid-hooks") {
5263
- return dirname8(currentPath);
5313
+ return dirname10(currentPath);
5264
5314
  }
5265
- const parentPath = dirname8(currentPath);
5315
+ const parentPath = dirname10(currentPath);
5266
5316
  if (parentPath === currentPath) {
5267
5317
  return null;
5268
5318
  }
@@ -5274,7 +5324,7 @@ function getHookBundleRelativePath(originalPath) {
5274
5324
  if (!bundleRoot) {
5275
5325
  throw new Error(`Invalid marketplace hook bundle path: ${originalPath}`);
5276
5326
  }
5277
- return relative(join10(bundleRoot, ".braid-hooks"), originalPath);
5327
+ return relative(join11(bundleRoot, ".braid-hooks"), originalPath);
5278
5328
  }
5279
5329
  function getHookSourceSlug(originalPath) {
5280
5330
  const relativePath = getHookBundleRelativePath(originalPath);
@@ -5293,7 +5343,7 @@ function getHookSettingsPath(originalPath) {
5293
5343
  if (!bundleRoot) {
5294
5344
  throw new Error(`Invalid marketplace hook bundle path: ${originalPath}`);
5295
5345
  }
5296
- return join10(bundleRoot, "settings.json");
5346
+ return join11(bundleRoot, "settings.json");
5297
5347
  }
5298
5348
  async function disableManagedBundleAsync(options) {
5299
5349
  if (options.surface === "hooks") {
@@ -5350,7 +5400,7 @@ async function removeManagedBundleAsync(options) {
5350
5400
  }
5351
5401
  );
5352
5402
  await removeMarketplaceHooksBySourceAsync(
5353
- join10(options.installRoot, "settings.json"),
5403
+ join11(options.installRoot, "settings.json"),
5354
5404
  options.bundleName
5355
5405
  );
5356
5406
  if (disabledRecord2) {
@@ -5364,7 +5414,7 @@ async function removeManagedBundleAsync(options) {
5364
5414
  disabledRoot: options.disabledRoot
5365
5415
  }
5366
5416
  );
5367
- await rm4(options.originalPath, { recursive: true, force: true });
5417
+ await rm5(options.originalPath, { recursive: true, force: true });
5368
5418
  if (disabledRecord) {
5369
5419
  await removePathAsync(disabledRecord.disabledPath);
5370
5420
  }
@@ -5388,7 +5438,7 @@ async function installLibraryPackAsync(options) {
5388
5438
  // src/lib/manage-inventory.ts
5389
5439
  init_esm_shims();
5390
5440
  import { access as access2, constants as constants2, lstat, readdir as readdir2 } from "fs/promises";
5391
- import { basename as basename3, delimiter, dirname as dirname9, join as join11, resolve as resolve5 } from "path";
5441
+ import { basename as basename3, delimiter, dirname as dirname11, join as join12, resolve as resolve5 } from "path";
5392
5442
  var MANAGE_PROVIDER_COMMANDS = {
5393
5443
  amp: ["amp"],
5394
5444
  "claude-code": ["claude"],
@@ -5436,7 +5486,7 @@ function getCommandExtensions() {
5436
5486
  function getCommandCandidates(command, pathValue) {
5437
5487
  return pathValue.split(delimiter).flatMap(
5438
5488
  (segment) => segment ? getCommandExtensions().map(
5439
- (extension) => join11(segment, `${command}${extension}`)
5489
+ (extension) => join12(segment, `${command}${extension}`)
5440
5490
  ) : []
5441
5491
  );
5442
5492
  }
@@ -5552,9 +5602,9 @@ async function listSurfaceEntries(rootPath, surface) {
5552
5602
  }
5553
5603
  return entry.isDirectory() || entry.isFile() || entry.isSymbolicLink();
5554
5604
  }).map((entry) => ({
5555
- currentPath: join11(rootPath, entry.name),
5605
+ currentPath: join12(rootPath, entry.name),
5556
5606
  name: entry.name,
5557
- originalPath: join11(rootPath, entry.name)
5607
+ originalPath: join12(rootPath, entry.name)
5558
5608
  }));
5559
5609
  }
5560
5610
  function toBundleKey(path2) {
@@ -5603,7 +5653,7 @@ async function collectSkillBundles(args) {
5603
5653
  });
5604
5654
  }
5605
5655
  for (const metadataEntry of metadata.skills) {
5606
- const originalPath = join11(args.rootPath, metadataEntry.name);
5656
+ const originalPath = join12(args.rootPath, metadataEntry.name);
5607
5657
  const bundleKey = toBundleKey(originalPath);
5608
5658
  if (bundles.has(bundleKey)) {
5609
5659
  continue;
@@ -5684,7 +5734,7 @@ async function collectHookBundles(args) {
5684
5734
  return [];
5685
5735
  }
5686
5736
  const settingsPath = args.settingsPath;
5687
- const installRoot = dirname9(settingsPath);
5737
+ const installRoot = dirname11(settingsPath);
5688
5738
  const sources = await listInstalledMarketplaceHookSourcesAsync(settingsPath);
5689
5739
  const disabledRecords = getDisabledRecordsForSurface({
5690
5740
  agent: args.agent,
@@ -5788,7 +5838,7 @@ async function collectScopeInventory(args) {
5788
5838
  },
5789
5839
  hooks: {
5790
5840
  bundles: hooks,
5791
- rootPath: hookSettingsPath ? dirname9(hookSettingsPath) : null,
5841
+ rootPath: hookSettingsPath ? dirname11(hookSettingsPath) : null,
5792
5842
  surface: "hooks"
5793
5843
  },
5794
5844
  rules: {
@@ -6389,39 +6439,288 @@ async function manageCommand(options) {
6389
6439
 
6390
6440
  // src/commands/marketplace.ts
6391
6441
  init_esm_shims();
6442
+ import { rm as rm6 } from "fs/promises";
6443
+ import { join as join13, resolve as resolve7 } from "path";
6444
+ import process11 from "process";
6392
6445
  init_tui();
6393
- async function marketplaceLibraryCommand(options) {
6394
- const items = await fetchMarketplaceLibraryAsync({
6395
- server: options.server,
6396
- apiKey: options.apiKey
6446
+ async function selectInstallScope(options) {
6447
+ if (options.global != null) {
6448
+ return options.global;
6449
+ }
6450
+ if (options.yes) {
6451
+ return false;
6452
+ }
6453
+ const result = await select({
6454
+ message: "Install location:",
6455
+ options: [
6456
+ { value: false, label: "Project", hint: "local to this directory" },
6457
+ { value: true, label: "Global", hint: "available everywhere" }
6458
+ ],
6459
+ initialValue: false
6397
6460
  });
6398
- if (items.length === 0) {
6399
- log.info("No marketplace packs in your library yet.");
6400
- return;
6461
+ if (isCancel(result)) {
6462
+ cancel("Install cancelled.");
6463
+ process11.exit(0);
6401
6464
  }
6402
- for (const item of items) {
6403
- const scan = item.scanVerdict ?? "clean";
6404
- const commit = item.commitSha ?? "unknown";
6405
- log.info(
6406
- `${item.slug} | ${item.title} | scan=${scan} | commit=${commit} | install=braid marketplace install ${item.slug}`
6465
+ return result;
6466
+ }
6467
+ async function selectInstallAgents(options, global) {
6468
+ const detected = await detectAgentsAsync();
6469
+ const available = detected.map((d) => getAgentById(d.id)).filter((a) => a != null);
6470
+ if (available.length === 0) {
6471
+ throw new Error(
6472
+ "No compatible agents found. Use --agents to specify targets."
6407
6473
  );
6408
6474
  }
6475
+ if (options.agents) {
6476
+ const ids = options.agents.split(",").map((s) => s.trim());
6477
+ const matched = ids.map((id) => getAgentById(id)).filter((a) => a != null);
6478
+ if (matched.length === 0) {
6479
+ throw new Error(`No agents matched: ${options.agents}`);
6480
+ }
6481
+ return matched;
6482
+ }
6483
+ if (options.yes || available.length === 1) {
6484
+ return available.length === 1 ? available : available;
6485
+ }
6486
+ const selected = await multiselect({
6487
+ message: "Select agents to install to:",
6488
+ options: available.map((agent) => ({
6489
+ value: agent,
6490
+ label: agent.name,
6491
+ hint: global ? agent.globalPath : agent.projectPath
6492
+ })),
6493
+ initialValues: [],
6494
+ required: true
6495
+ });
6496
+ if (isCancel(selected)) {
6497
+ cancel("Install cancelled.");
6498
+ process11.exit(0);
6499
+ }
6500
+ return selected;
6409
6501
  }
6410
- async function marketplaceInstallCommand(slug, options) {
6502
+ async function confirmHooksOptIn(hookCount, options) {
6503
+ if (options.allowHooks) {
6504
+ return true;
6505
+ }
6506
+ if (options.yes) {
6507
+ return false;
6508
+ }
6509
+ const result = await confirm({
6510
+ message: `This pack includes ${hookCount} hook${hookCount === 1 ? "" : "s"} that run shell commands. Allow hooks?`,
6511
+ initialValue: false
6512
+ });
6513
+ if (isCancel(result)) {
6514
+ cancel("Install cancelled.");
6515
+ process11.exit(0);
6516
+ }
6517
+ return result;
6518
+ }
6519
+ async function marketplaceLibraryCommand(options) {
6520
+ try {
6521
+ const items = await fetchMarketplaceLibraryAsync({
6522
+ server: options.server,
6523
+ apiKey: options.apiKey
6524
+ });
6525
+ if (items.length === 0) {
6526
+ log.info("No marketplace packs in your library yet.");
6527
+ return;
6528
+ }
6529
+ for (const item of items) {
6530
+ const scan = item.scanVerdict ?? "clean";
6531
+ const commit = item.commitSha ?? "unknown";
6532
+ log.info(
6533
+ `${item.slug} | ${item.title} | scan=${scan} | commit=${commit} | install=braid marketplace install ${item.slug}`
6534
+ );
6535
+ }
6536
+ } catch (error) {
6537
+ log.error(
6538
+ error instanceof Error ? error.message : "Failed to fetch library"
6539
+ );
6540
+ process11.exit(1);
6541
+ }
6542
+ }
6543
+ async function resolveAndInstallPack(slug, options, overrides) {
6411
6544
  const manifest = await fetchMarketplaceInstallManifestAsync(slug, {
6412
6545
  server: options.server,
6413
6546
  apiKey: options.apiKey
6414
6547
  });
6415
- const installResult = await installMarketplaceSkillSet({
6548
+ const global = overrides?.global ?? await selectInstallScope(options);
6549
+ const hooks = manifest.manifest?.hooks ?? [];
6550
+ if (hooks.length > 0) {
6551
+ const allowed = await confirmHooksOptIn(hooks.length, options);
6552
+ if (!allowed) {
6553
+ log.warn("Hooks skipped. Re-run with --allow-hooks to include them.");
6554
+ }
6555
+ options.allowHooks = allowed;
6556
+ }
6557
+ const agents2 = overrides?.agents ?? (await selectInstallAgents(options, global)).map((a) => a.id).join(",");
6558
+ await installMarketplaceSkillSet({
6416
6559
  slug,
6417
6560
  manifest,
6418
- agents: options.agents,
6419
- global: options.global,
6561
+ agents: agents2,
6562
+ global,
6420
6563
  allowHooks: options.allowHooks
6421
6564
  });
6422
- log.success(`Installed marketplace pack ${slug}`);
6423
- for (const target of installResult.targets) {
6424
- log.info(` ${target}`);
6565
+ }
6566
+ async function marketplaceInstallCommand(slug, options) {
6567
+ try {
6568
+ await resolveAndInstallPack(slug, options);
6569
+ log.success(`Installed marketplace pack ${slug}`);
6570
+ } catch (error) {
6571
+ log.error(
6572
+ error instanceof Error ? error.message : "Failed to install pack"
6573
+ );
6574
+ process11.exit(1);
6575
+ }
6576
+ }
6577
+ function addSkillToPack(packMap, slug, versionId, skillName, agent, installPath) {
6578
+ let pack = packMap.get(slug);
6579
+ if (!pack) {
6580
+ pack = { slug, versionId, skills: [], agents: [] };
6581
+ packMap.set(slug, pack);
6582
+ }
6583
+ if (!pack.skills.includes(skillName)) {
6584
+ pack.skills.push(skillName);
6585
+ }
6586
+ if (!pack.agents.some((a) => a.agent.id === agent.id)) {
6587
+ pack.agents.push({ agent, installPath });
6588
+ }
6589
+ }
6590
+ async function findInstalledMarketplacePacks(options) {
6591
+ const detected = await detectAgentsAsync();
6592
+ const agents2 = detected.map((d) => getAgentById(d.id)).filter((a) => a != null);
6593
+ const packMap = /* @__PURE__ */ new Map();
6594
+ for (const agent of agents2) {
6595
+ const installPath = resolveInstallPath(agent, {
6596
+ global: options.global ?? false
6597
+ });
6598
+ if (!installPath) {
6599
+ continue;
6600
+ }
6601
+ const metadata = await readMetadataAsync(installPath);
6602
+ for (const skill of metadata.skills) {
6603
+ if (skill.source.type !== "marketplace") {
6604
+ continue;
6605
+ }
6606
+ addSkillToPack(
6607
+ packMap,
6608
+ skill.source.slug,
6609
+ skill.source.versionId ?? skill.version,
6610
+ skill.name,
6611
+ agent,
6612
+ installPath
6613
+ );
6614
+ }
6615
+ }
6616
+ return [...packMap.values()];
6617
+ }
6618
+ async function marketplaceUpdateCommand(slug, options) {
6619
+ try {
6620
+ const installed = await findInstalledMarketplacePacks(options);
6621
+ if (installed.length === 0) {
6622
+ log.info("No marketplace packs installed.");
6623
+ return;
6624
+ }
6625
+ const toUpdate = slug ? installed.filter((p) => p.slug === slug) : installed;
6626
+ if (toUpdate.length === 0) {
6627
+ log.error(`Pack "${slug}" is not installed.`);
6628
+ process11.exit(1);
6629
+ }
6630
+ let updated = 0;
6631
+ for (const pack of toUpdate) {
6632
+ const agentIds = pack.agents.map((a) => a.agent.id).join(",");
6633
+ const isGlobal = options.global ?? false;
6634
+ await resolveAndInstallPack(pack.slug, options, {
6635
+ agents: agentIds,
6636
+ global: isGlobal
6637
+ });
6638
+ log.success(`Updated ${pack.slug}`);
6639
+ updated++;
6640
+ }
6641
+ if (updated === 0) {
6642
+ log.info("All packs are up to date.");
6643
+ }
6644
+ } catch (error) {
6645
+ log.error(
6646
+ error instanceof Error ? error.message : "Failed to update packs"
6647
+ );
6648
+ process11.exit(1);
6649
+ }
6650
+ }
6651
+ async function selectPacksToRemove(installed, slug, options) {
6652
+ if (slug) {
6653
+ const matched = installed.filter((p) => p.slug === slug);
6654
+ if (matched.length === 0) {
6655
+ throw new Error(`Pack "${slug}" is not installed.`);
6656
+ }
6657
+ return matched;
6658
+ }
6659
+ if (options.yes) {
6660
+ return installed;
6661
+ }
6662
+ const selected = await multiselect({
6663
+ message: "Select packs to remove:",
6664
+ options: installed.map((p) => ({
6665
+ value: p,
6666
+ label: p.slug,
6667
+ hint: `${p.skills.length} skill${p.skills.length === 1 ? "" : "s"}`
6668
+ })),
6669
+ initialValues: [],
6670
+ required: true
6671
+ });
6672
+ if (isCancel(selected)) {
6673
+ cancel("Remove cancelled.");
6674
+ process11.exit(0);
6675
+ }
6676
+ return selected;
6677
+ }
6678
+ async function removePackFiles(pack, options) {
6679
+ for (const { agent, installPath } of pack.agents) {
6680
+ for (const skillName of pack.skills) {
6681
+ const skillPath = resolve7(join13(installPath, skillName));
6682
+ if (!skillPath.startsWith(`${resolve7(installPath)}/`)) {
6683
+ continue;
6684
+ }
6685
+ await rm6(skillPath, { recursive: true, force: true });
6686
+ await removeFromMetadataAsync(installPath, skillName);
6687
+ }
6688
+ const hookConfigPath = resolveHookConfigPath(agent, {
6689
+ global: options.global ?? false
6690
+ });
6691
+ if (hookConfigPath) {
6692
+ await removeMarketplaceHooksBySourceAsync(hookConfigPath, pack.slug);
6693
+ }
6694
+ }
6695
+ }
6696
+ async function marketplaceRemoveCommand(slug, options) {
6697
+ try {
6698
+ const installed = await findInstalledMarketplacePacks(options);
6699
+ if (installed.length === 0) {
6700
+ log.info("No marketplace packs installed.");
6701
+ return;
6702
+ }
6703
+ const toRemove = await selectPacksToRemove(installed, slug, options);
6704
+ if (!options.yes) {
6705
+ const slugList = toRemove.map((p) => p.slug).join(", ");
6706
+ const confirmed = await confirm({
6707
+ message: `Remove ${toRemove.length} pack${toRemove.length === 1 ? "" : "s"} (${slugList})?`,
6708
+ initialValue: false
6709
+ });
6710
+ if (isCancel(confirmed) || !confirmed) {
6711
+ cancel("Remove cancelled.");
6712
+ process11.exit(0);
6713
+ }
6714
+ }
6715
+ for (const pack of toRemove) {
6716
+ await removePackFiles(pack, options);
6717
+ log.success(`Removed ${pack.slug}`);
6718
+ }
6719
+ } catch (error) {
6720
+ log.error(
6721
+ error instanceof Error ? error.message : "Failed to remove packs"
6722
+ );
6723
+ process11.exit(1);
6425
6724
  }
6426
6725
  }
6427
6726
 
@@ -6429,15 +6728,15 @@ async function marketplaceInstallCommand(slug, options) {
6429
6728
  init_esm_shims();
6430
6729
  init_api();
6431
6730
  init_tui();
6432
- import process11 from "process";
6731
+ import process12 from "process";
6433
6732
  var writeJson4 = (value) => {
6434
- process11.stdout.write(`${JSON.stringify(value, null, 2)}
6733
+ process12.stdout.write(`${JSON.stringify(value, null, 2)}
6435
6734
  `);
6436
6735
  };
6437
6736
  var exitWithError3 = (error) => {
6438
6737
  const message = error instanceof Error ? error.message : String(error);
6439
6738
  log.error(message);
6440
- process11.exit(1);
6739
+ process12.exit(1);
6441
6740
  };
6442
6741
  var fail2 = (message) => {
6443
6742
  throw new Error(message);
@@ -6536,9 +6835,9 @@ async function profilesSetDefaultCommand(options) {
6536
6835
  init_esm_shims();
6537
6836
  init_api();
6538
6837
  init_tui();
6539
- import process12 from "process";
6838
+ import process13 from "process";
6540
6839
  var writeJson5 = (value) => {
6541
- process12.stdout.write(`${JSON.stringify(value, null, 2)}
6840
+ process13.stdout.write(`${JSON.stringify(value, null, 2)}
6542
6841
  `);
6543
6842
  };
6544
6843
  var fail3 = (message) => {
@@ -6566,7 +6865,7 @@ var run3 = async (command, args, options) => {
6566
6865
  var exitWithError4 = (error) => {
6567
6866
  const message = error instanceof Error ? error.message : String(error);
6568
6867
  log.error(message);
6569
- process12.exit(1);
6868
+ process13.exit(1);
6570
6869
  };
6571
6870
  async function projectsGetCommand(options) {
6572
6871
  try {
@@ -6620,9 +6919,9 @@ async function projectsRemoveCommand(options) {
6620
6919
  init_esm_shims();
6621
6920
  init_api();
6622
6921
  init_tui();
6623
- import process13 from "process";
6922
+ import process14 from "process";
6624
6923
  var writeJson6 = (value) => {
6625
- process13.stdout.write(`${JSON.stringify(value, null, 2)}
6924
+ process14.stdout.write(`${JSON.stringify(value, null, 2)}
6626
6925
  `);
6627
6926
  };
6628
6927
  var parseCsv3 = (input) => {
@@ -6635,7 +6934,7 @@ var parseCsv3 = (input) => {
6635
6934
  var exitWithError5 = (error) => {
6636
6935
  const message = error instanceof Error ? error.message : String(error);
6637
6936
  log.error(message);
6638
- process13.exit(1);
6937
+ process14.exit(1);
6639
6938
  };
6640
6939
  var fail4 = (message) => {
6641
6940
  throw new Error(message);
@@ -6722,9 +7021,9 @@ async function referencesReorderCommand(options) {
6722
7021
 
6723
7022
  // src/commands/remove.ts
6724
7023
  init_esm_shims();
6725
- import { rm as rm5 } from "fs/promises";
6726
- import { join as join12, resolve as resolve7 } from "path";
6727
- import process14 from "process";
7024
+ import { rm as rm7 } from "fs/promises";
7025
+ import { join as join14, resolve as resolve8 } from "path";
7026
+ import process15 from "process";
6728
7027
  init_tui();
6729
7028
  async function collectInstalledSkills(detectedAgents, options) {
6730
7029
  const skillsToRemove = [];
@@ -6745,7 +7044,7 @@ async function collectInstalledSkills(detectedAgents, options) {
6745
7044
  name: skill.name,
6746
7045
  agentName: agent.name,
6747
7046
  installPath,
6748
- skillPath: join12(installPath, skill.name)
7047
+ skillPath: join14(installPath, skill.name)
6749
7048
  });
6750
7049
  }
6751
7050
  }
@@ -6757,7 +7056,7 @@ async function selectSkillsToRemove(skillsToRemove, options) {
6757
7056
  if (selected.length === 0) {
6758
7057
  log.error(`Skill '${options.skill}' not found.`);
6759
7058
  log.info("Run 'braid list' to see installed skills.");
6760
- process14.exit(1);
7059
+ process15.exit(1);
6761
7060
  }
6762
7061
  return selected;
6763
7062
  }
@@ -6776,7 +7075,7 @@ async function selectSkillsToRemove(skillsToRemove, options) {
6776
7075
  });
6777
7076
  if (isCancel(result)) {
6778
7077
  cancel("Remove cancelled.");
6779
- process14.exit(0);
7078
+ process15.exit(0);
6780
7079
  }
6781
7080
  return result;
6782
7081
  }
@@ -6790,20 +7089,20 @@ async function confirmRemoval(selectedCount, options) {
6790
7089
  });
6791
7090
  if (isCancel(confirmed) || !confirmed) {
6792
7091
  cancel("Remove cancelled.");
6793
- process14.exit(0);
7092
+ process15.exit(0);
6794
7093
  }
6795
7094
  }
6796
7095
  async function removeSkill(skill, removeSpinner) {
6797
7096
  removeSpinner.start(`Removing ${skill.name} from ${skill.agentName}...`);
6798
7097
  try {
6799
- const resolvedSkillPath = resolve7(skill.skillPath);
6800
- const resolvedInstallPath = resolve7(skill.installPath);
7098
+ const resolvedSkillPath = resolve8(skill.skillPath);
7099
+ const resolvedInstallPath = resolve8(skill.installPath);
6801
7100
  if (!resolvedSkillPath.startsWith(`${resolvedInstallPath}/`)) {
6802
7101
  removeSpinner.stop(`Unsafe path for ${skill.name}`);
6803
7102
  log.warn(" Skill path escapes install directory, skipping.");
6804
7103
  return false;
6805
7104
  }
6806
- await rm5(resolvedSkillPath, { recursive: true, force: true });
7105
+ await rm7(resolvedSkillPath, { recursive: true, force: true });
6807
7106
  await removeFromMetadataAsync(skill.installPath, skill.name);
6808
7107
  removeSpinner.stop(`Removed ${skill.name} from ${skill.agentName}`);
6809
7108
  return true;
@@ -6854,7 +7153,7 @@ async function removeCommand(options) {
6854
7153
  removeSpinner.stop("Remove failed");
6855
7154
  const message = error instanceof Error ? error.message : String(error);
6856
7155
  log.error(message);
6857
- process14.exit(1);
7156
+ process15.exit(1);
6858
7157
  }
6859
7158
  }
6860
7159
 
@@ -7052,7 +7351,7 @@ async function rollbackCommand(source, options) {
7052
7351
  init_esm_shims();
7053
7352
  init_api();
7054
7353
  init_tui();
7055
- import process15 from "process";
7354
+ import process16 from "process";
7056
7355
  var parseCsv4 = (input) => {
7057
7356
  if (!input) {
7058
7357
  return void 0;
@@ -7061,13 +7360,13 @@ var parseCsv4 = (input) => {
7061
7360
  return values.length > 0 ? values : void 0;
7062
7361
  };
7063
7362
  var writeJson7 = (value) => {
7064
- process15.stdout.write(`${JSON.stringify(value, null, 2)}
7363
+ process16.stdout.write(`${JSON.stringify(value, null, 2)}
7065
7364
  `);
7066
7365
  };
7067
7366
  var exitWithError6 = (error) => {
7068
7367
  const message = error instanceof Error ? error.message : String(error);
7069
7368
  log.error(message);
7070
- process15.exit(1);
7369
+ process16.exit(1);
7071
7370
  };
7072
7371
  var fail5 = (message) => {
7073
7372
  throw new Error(message);
@@ -7246,7 +7545,7 @@ async function rulesSyncNowCommand(options) {
7246
7545
 
7247
7546
  // src/commands/scaffold.ts
7248
7547
  init_esm_shims();
7249
- import process16 from "process";
7548
+ import process17 from "process";
7250
7549
 
7251
7550
  // src/lib/scaffold.ts
7252
7551
  init_esm_shims();
@@ -7256,11 +7555,11 @@ import {
7256
7555
  mkdir as mkdir7,
7257
7556
  readdir as readdir3,
7258
7557
  readFile as readFile8,
7259
- rm as rm6,
7558
+ rm as rm8,
7260
7559
  stat,
7261
7560
  writeFile as writeFile9
7262
7561
  } from "fs/promises";
7263
- import { dirname as dirname10, join as join13 } from "path";
7562
+ import { dirname as dirname12, join as join15 } from "path";
7264
7563
  var BRAID_PACK_FILENAME = "braid-pack.json";
7265
7564
  var DEFAULT_TIMEOUT_MS = 3e5;
7266
7565
  var MAX_DURATION_MS = 18e5;
@@ -7392,7 +7691,7 @@ var directoryContainsMatchingFile = async (absoluteDirectoryPath, matcher) => {
7392
7691
  );
7393
7692
  }
7394
7693
  for (const entry of entries) {
7395
- const absoluteEntryPath = join13(absoluteDirectoryPath, entry.name);
7694
+ const absoluteEntryPath = join15(absoluteDirectoryPath, entry.name);
7396
7695
  if (entry.isDirectory()) {
7397
7696
  if (await directoryContainsMatchingFile(absoluteEntryPath, matcher)) {
7398
7697
  return true;
@@ -7448,19 +7747,19 @@ var directoryContainsHookArtifact = async (absoluteDirectoryPath) => directoryCo
7448
7747
  }
7449
7748
  );
7450
7749
  var hasUnmanifestedPackContent = async (cwd) => {
7451
- if (await manifestExists(join13(cwd, "SKILL.md"))) {
7750
+ if (await manifestExists(join15(cwd, "SKILL.md"))) {
7452
7751
  return true;
7453
7752
  }
7454
- if (await directoryContainsSkillArtifact(join13(cwd, "skills"))) {
7753
+ if (await directoryContainsSkillArtifact(join15(cwd, "skills"))) {
7455
7754
  return true;
7456
7755
  }
7457
- if (await directoryContainsAgentArtifact(join13(cwd, "agents"))) {
7756
+ if (await directoryContainsAgentArtifact(join15(cwd, "agents"))) {
7458
7757
  return true;
7459
7758
  }
7460
- if (await directoryContainsHookArtifact(join13(cwd, "hooks"))) {
7759
+ if (await directoryContainsHookArtifact(join15(cwd, "hooks"))) {
7461
7760
  return true;
7462
7761
  }
7463
- return directoryContainsWorkflowArtifact(join13(cwd, "workflows"));
7762
+ return directoryContainsWorkflowArtifact(join15(cwd, "workflows"));
7464
7763
  };
7465
7764
  var parseManifestEntries = (manifest, key, parser) => {
7466
7765
  const value = manifest[key];
@@ -7986,7 +8285,7 @@ var writeTextFile3 = async (absolutePath, relativePath, content) => {
7986
8285
  var restoreManifest = async (absoluteManifestPath, previousContent) => {
7987
8286
  try {
7988
8287
  if (previousContent === void 0) {
7989
- await rm6(absoluteManifestPath, { force: true });
8288
+ await rm8(absoluteManifestPath, { force: true });
7990
8289
  return;
7991
8290
  }
7992
8291
  await writeFile9(absoluteManifestPath, previousContent, "utf-8");
@@ -8010,8 +8309,8 @@ var prepareScaffoldOperation = async (normalizedInput) => {
8010
8309
  normalizedInput.type,
8011
8310
  normalizedInput.name
8012
8311
  );
8013
- const absoluteManifestPath = join13(normalizedInput.cwd, BRAID_PACK_FILENAME);
8014
- const absoluteArtifactPath = join13(normalizedInput.cwd, filePath);
8312
+ const absoluteManifestPath = join15(normalizedInput.cwd, BRAID_PACK_FILENAME);
8313
+ const absoluteArtifactPath = join15(normalizedInput.cwd, filePath);
8015
8314
  const manifestKey = MANIFEST_KEY_BY_TYPE[normalizedInput.type];
8016
8315
  const previousManifestContent = context.state === "manifest-pack" ? await readManifestFile(absoluteManifestPath) : void 0;
8017
8316
  const existingEntry = context.manifest[manifestKey].find(
@@ -8063,7 +8362,7 @@ var writeArtifactWithRollback = async ({
8063
8362
  previousManifestContent
8064
8363
  }) => {
8065
8364
  try {
8066
- await mkdir7(dirname10(absoluteArtifactPath), { recursive: true });
8365
+ await mkdir7(dirname12(absoluteArtifactPath), { recursive: true });
8067
8366
  await writeTextFile3(absoluteArtifactPath, filePath, artifactContent);
8068
8367
  } catch (error) {
8069
8368
  const artifactError = error instanceof ScaffoldError ? error : toIoError(filePath, error);
@@ -8090,10 +8389,10 @@ var ScaffoldError = class extends Error {
8090
8389
  }
8091
8390
  };
8092
8391
  var inspectScaffoldDirectory = async (cwd) => {
8093
- const manifestPath = join13(cwd, BRAID_PACK_FILENAME);
8392
+ const manifestPath = join15(cwd, BRAID_PACK_FILENAME);
8094
8393
  const hasManifest = await manifestExists(manifestPath);
8095
8394
  if (!hasManifest) {
8096
- if (await manifestExists(join13(cwd, "SKILL.md"))) {
8395
+ if (await manifestExists(join15(cwd, "SKILL.md"))) {
8097
8396
  throw new ScaffoldError(
8098
8397
  "invalid_repo_state",
8099
8398
  "Invalid repo state: root SKILL.md requires migration to braid-pack.json before scaffolding."
@@ -8206,11 +8505,11 @@ var normalizeReferenceList = (referenceLabel, availableNamesLabel, values, avail
8206
8505
  };
8207
8506
  var exitCancelled2 = () => {
8208
8507
  cancel("Scaffold cancelled.");
8209
- process16.exit(0);
8508
+ process17.exit(0);
8210
8509
  };
8211
8510
  var exitWithError7 = (message) => {
8212
8511
  log.error(message);
8213
- process16.exit(1);
8512
+ process17.exit(1);
8214
8513
  };
8215
8514
  var requirePromptValue = (value) => {
8216
8515
  if (isCancel(value)) {
@@ -8339,7 +8638,7 @@ var resolveOptionalReference = async (label, flagValue, options, availableNames)
8339
8638
  return selected || void 0;
8340
8639
  };
8341
8640
  var buildScaffoldInput = async (options) => {
8342
- const context = await inspectScaffoldDirectory(process16.cwd());
8641
+ const context = await inspectScaffoldDirectory(process17.cwd());
8343
8642
  const availableSkillNames = context.manifest.skills.map(
8344
8643
  (entry) => entry.name
8345
8644
  );
@@ -8393,7 +8692,7 @@ var buildScaffoldInput = async (options) => {
8393
8692
  availableWorkflowNames
8394
8693
  ) : void 0;
8395
8694
  return {
8396
- cwd: process16.cwd(),
8695
+ cwd: process17.cwd(),
8397
8696
  type,
8398
8697
  name,
8399
8698
  title,
@@ -8485,8 +8784,8 @@ init_scope();
8485
8784
 
8486
8785
  // src/commands/update.ts
8487
8786
  init_esm_shims();
8488
- import { rm as rm7 } from "fs/promises";
8489
- import { join as join14, resolve as resolve8 } from "path";
8787
+ import { rm as rm9 } from "fs/promises";
8788
+ import { join as join16, resolve as resolve9 } from "path";
8490
8789
  import {
8491
8790
  cancel as cancel2,
8492
8791
  isCancel as isCancel2,
@@ -8631,12 +8930,12 @@ var areSameSourceProjects = (left, right) => {
8631
8930
  };
8632
8931
  var isSameInstalledSource = (source, target) => source.type === target.type && source.name === target.name && areSameSourceProjects(source.orgProjects, target.orgProjects) && areSameSourceProjects(source.personalProjects, target.personalProjects);
8633
8932
  var removeInstalledBundle = async (installPath, bundleName) => {
8634
- const resolvedInstallPath = resolve8(installPath);
8635
- const resolvedBundlePath = resolve8(join14(installPath, bundleName));
8933
+ const resolvedInstallPath = resolve9(installPath);
8934
+ const resolvedBundlePath = resolve9(join16(installPath, bundleName));
8636
8935
  if (!resolvedBundlePath.startsWith(`${resolvedInstallPath}/`)) {
8637
8936
  throw new Error(`Unsafe bundle path for ${bundleName}`);
8638
8937
  }
8639
- await rm7(resolvedBundlePath, { recursive: true, force: true });
8938
+ await rm9(resolvedBundlePath, { recursive: true, force: true });
8640
8939
  const disabledRecord = await findDisabledBundleByOriginalPathAsync(
8641
8940
  resolvedBundlePath,
8642
8941
  {
@@ -8954,6 +9253,11 @@ marketplace.command("install").description("Install a marketplace pack by slug")
8954
9253
  "--allow-hooks",
8955
9254
  "Allow marketplace packs to install executable Claude Code hooks"
8956
9255
  ).option("-y, --yes", "Skip confirmation prompts").action((slug, options) => marketplaceInstallCommand(slug, options));
9256
+ marketplace.command("update").description("Update installed marketplace packs to their latest version").argument("[slug]", "Pack slug (updates all if omitted)").option("-s, --server <url>", "braid server URL (for review apps, local dev)").option("--api-key <token>", "API key override").option("-g, --global", "Update packs in global agent directories").option(
9257
+ "--allow-hooks",
9258
+ "Allow marketplace packs to install executable Claude Code hooks"
9259
+ ).option("-y, --yes", "Skip confirmation prompts").action((slug, options) => marketplaceUpdateCommand(slug, options));
9260
+ marketplace.command("remove").description("Remove installed marketplace packs").argument("[slug]", "Pack slug (prompts for selection if omitted)").option("-g, --global", "Remove from global agent directories").option("-y, --yes", "Skip confirmation prompts").action((slug, options) => marketplaceRemoveCommand(slug, options));
8957
9261
  var projects = program.command("projects").description("Discover available projects from braid");
8958
9262
  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);
8959
9263
  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);