@braid-cloud/cli 0.1.5 → 0.1.6

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