@braid-cloud/cli 0.1.0 → 0.1.1

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
@@ -56,12 +56,12 @@ import { homedir } from "os";
56
56
  import { dirname, join, parse } from "path";
57
57
  import process2 from "process";
58
58
  import { Data, Effect, pipe } from "effect";
59
- var CONFIG_DIR, CONFIG_FILE, PROJECT_CONFIG_FILENAME, USER_CONFIG_FILENAME, ConfigReadError, ConfigWriteError, findConfigFile, findProjectConfigFile, findUserConfigFile, loadProjectConfig, loadUserConfig, resolveServerUrlFromConfig, applyConfigSource, applyEnvOverrides, createDefaultMergedConfig, loadMergedConfig, loadConfig, saveConfig, getApiKey, setApiKey, getServerUrl, clearApiKey, loadConfigAsync, loadProjectConfigAsync, loadUserConfigAsync, loadMergedConfigAsync, findProjectConfigFileAsync, findUserConfigFileAsync, saveConfigAsync, getApiKeyAsync, setApiKeyAsync, getServerUrlAsync, clearApiKeyAsync;
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;
60
60
  var init_config = __esm({
61
61
  "src/lib/config.ts"() {
62
62
  "use strict";
63
63
  init_esm_shims();
64
- CONFIG_DIR = join(homedir(), ".config", "braid-skills");
64
+ CONFIG_DIR = join(homedir(), ".config", "braid");
65
65
  CONFIG_FILE = join(CONFIG_DIR, "config.json");
66
66
  PROJECT_CONFIG_FILENAME = "braid.json";
67
67
  USER_CONFIG_FILENAME = "braid.user.json";
@@ -107,11 +107,23 @@ var init_config = __esm({
107
107
  },
108
108
  catch: () => void 0
109
109
  }).pipe(Effect.orElseSucceed(() => void 0));
110
+ isValidServerUrl = (url) => {
111
+ try {
112
+ const parsed = new URL(url);
113
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
114
+ } catch {
115
+ return false;
116
+ }
117
+ };
110
118
  resolveServerUrlFromConfig = (config) => {
111
119
  if (!config) {
112
120
  return void 0;
113
121
  }
114
- return config.skills?.serverUrl ?? config.serverUrl;
122
+ const url = config.skills?.serverUrl ?? config.serverUrl;
123
+ if (url && !isValidServerUrl(url)) {
124
+ return void 0;
125
+ }
126
+ return url;
115
127
  };
116
128
  applyConfigSource = (merged, config) => {
117
129
  if (!config) {
@@ -130,11 +142,11 @@ var init_config = __esm({
130
142
  if (config.personalProjects) {
131
143
  merged.personalProjects = config.personalProjects;
132
144
  }
133
- if (config.includeUserGlobalRules !== void 0) {
134
- merged.includeUserGlobalRules = config.includeUserGlobalRules;
145
+ if (config.includeUserGlobal !== void 0) {
146
+ merged.includeUserGlobal = config.includeUserGlobal;
135
147
  }
136
- if (config.includeOrgGlobalRules !== void 0) {
137
- merged.includeOrgGlobalRules = config.includeOrgGlobalRules;
148
+ if (config.includeOrgGlobal !== void 0) {
149
+ merged.includeOrgGlobal = config.includeOrgGlobal;
138
150
  }
139
151
  if (config.agents) {
140
152
  merged.agents = config.agents;
@@ -148,14 +160,14 @@ var init_config = __esm({
148
160
  merged.token = process2.env.BRAID_API_KEY;
149
161
  }
150
162
  const envServerUrl = process2.env.BRAID_SKILLS_SERVER_URL ?? process2.env.BRAID_SERVER_URL;
151
- if (envServerUrl) {
163
+ if (envServerUrl && isValidServerUrl(envServerUrl)) {
152
164
  merged.serverUrl = envServerUrl;
153
165
  }
154
166
  };
155
167
  createDefaultMergedConfig = () => ({
156
168
  serverUrl: "https://braid.cloud",
157
- includeUserGlobalRules: true,
158
- includeOrgGlobalRules: true
169
+ includeUserGlobal: true,
170
+ includeOrgGlobal: true
159
171
  });
160
172
  loadMergedConfig = () => pipe(
161
173
  Effect.all({
@@ -192,8 +204,11 @@ var init_config = __esm({
192
204
  saveConfig = (config) => pipe(
193
205
  Effect.tryPromise({
194
206
  try: async () => {
195
- await mkdir(dirname(CONFIG_FILE), { recursive: true });
196
- await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
207
+ await mkdir(dirname(CONFIG_FILE), { recursive: true, mode: 448 });
208
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {
209
+ encoding: "utf-8",
210
+ mode: 384
211
+ });
197
212
  },
198
213
  catch: (e) => new ConfigWriteError({ path: CONFIG_FILE, cause: e })
199
214
  })
@@ -347,17 +362,14 @@ var buildExportUrl = (serverUrl, options) => {
347
362
  options.personalProjects.join(",")
348
363
  );
349
364
  }
350
- if (options.includeUserGlobalRules !== void 0) {
365
+ if (options.includeUserGlobal !== void 0) {
351
366
  url.searchParams.set(
352
367
  "includeUserGlobal",
353
- String(options.includeUserGlobalRules)
368
+ String(options.includeUserGlobal)
354
369
  );
355
370
  }
356
- if (options.includeOrgGlobalRules !== void 0) {
357
- url.searchParams.set(
358
- "includeOrgGlobal",
359
- String(options.includeOrgGlobalRules)
360
- );
371
+ if (options.includeOrgGlobal !== void 0) {
372
+ url.searchParams.set("includeOrgGlobal", String(options.includeOrgGlobal));
361
373
  }
362
374
  return url;
363
375
  };
@@ -422,7 +434,7 @@ var validateApiKeyAsync = (apiKey, serverUrl) => Effect2.runPromise(validateApiK
422
434
  // src/commands/auth.ts
423
435
  init_config();
424
436
  async function authCommand(options) {
425
- intro("braid-skills auth");
437
+ intro("braid auth");
426
438
  const config = await loadMergedConfigAsync();
427
439
  if (config.token) {
428
440
  const shouldReplace = await confirm({
@@ -466,7 +478,7 @@ async function authCommand(options) {
466
478
  authSpinner.stop("API key validated and saved");
467
479
  log.success(`Config saved to ${CONFIG_FILE}`);
468
480
  outro(
469
- "You're authenticated! Run 'braid-skills install --profile <name>' to install skills."
481
+ "You're authenticated! Run 'braid install --profile <name>' to install skills."
470
482
  );
471
483
  } catch (error) {
472
484
  authSpinner.stop("Validation failed");
@@ -514,9 +526,7 @@ function displayResolvedSettings(config) {
514
526
  async function authStatusCommand() {
515
527
  const config = await loadMergedConfigAsync();
516
528
  if (!config.token) {
517
- log.warn(
518
- "Not authenticated. Run 'braid-skills auth' to configure your API key."
519
- );
529
+ log.warn("Not authenticated. Run 'braid auth' to configure your API key.");
520
530
  log.info(
521
531
  `Or create a ${USER_CONFIG_FILENAME} file with: { "token": "br_xxx" }`
522
532
  );
@@ -567,7 +577,10 @@ var AGENTS = [
567
577
  id: "claude-code",
568
578
  name: "Claude Code",
569
579
  projectPath: ".claude/skills",
570
- globalPath: join2(home, ".claude", "skills")
580
+ globalPath: join2(home, ".claude", "skills"),
581
+ rulesProjectPath: ".claude/rules",
582
+ rulesGlobalPath: join2(home, ".claude", "rules"),
583
+ ruleFormat: "markdown-dir"
571
584
  },
572
585
  {
573
586
  id: "moltbot",
@@ -579,7 +592,9 @@ var AGENTS = [
579
592
  id: "cline",
580
593
  name: "Cline",
581
594
  projectPath: ".cline/skills",
582
- globalPath: join2(home, ".cline", "skills")
595
+ globalPath: join2(home, ".cline", "skills"),
596
+ rulesProjectPath: ".clinerules",
597
+ ruleFormat: "append-single"
583
598
  },
584
599
  {
585
600
  id: "codebuddy",
@@ -615,7 +630,9 @@ var AGENTS = [
615
630
  id: "cursor",
616
631
  name: "Cursor",
617
632
  projectPath: ".cursor/skills",
618
- globalPath: join2(home, ".cursor", "skills")
633
+ globalPath: join2(home, ".cursor", "skills"),
634
+ rulesProjectPath: ".cursor/rules",
635
+ ruleFormat: "mdc"
619
636
  },
620
637
  {
621
638
  id: "droid",
@@ -633,7 +650,9 @@ var AGENTS = [
633
650
  id: "github-copilot",
634
651
  name: "GitHub Copilot",
635
652
  projectPath: ".github/skills",
636
- globalPath: join2(home, ".copilot", "skills")
653
+ globalPath: join2(home, ".copilot", "skills"),
654
+ rulesProjectPath: ".github/copilot-instructions.md",
655
+ ruleFormat: "append-single"
637
656
  },
638
657
  {
639
658
  id: "goose",
@@ -711,7 +730,10 @@ var AGENTS = [
711
730
  id: "roo",
712
731
  name: "Roo Code",
713
732
  projectPath: ".roo/skills",
714
- globalPath: join2(home, ".roo", "skills")
733
+ globalPath: join2(home, ".roo", "skills"),
734
+ rulesProjectPath: ".roo/rules",
735
+ rulesGlobalPath: join2(home, ".roo", "rules"),
736
+ ruleFormat: "markdown-dir"
715
737
  },
716
738
  {
717
739
  id: "trae",
@@ -723,7 +745,9 @@ var AGENTS = [
723
745
  id: "windsurf",
724
746
  name: "Windsurf",
725
747
  projectPath: ".windsurf/skills",
726
- globalPath: join2(home, ".codeium", "windsurf", "skills")
748
+ globalPath: join2(home, ".codeium", "windsurf", "skills"),
749
+ rulesProjectPath: ".windsurfrules",
750
+ ruleFormat: "append-single"
727
751
  },
728
752
  {
729
753
  id: "zencoder",
@@ -742,6 +766,15 @@ var AGENTS = [
742
766
  name: "Pochi",
743
767
  projectPath: ".pochi/skills",
744
768
  globalPath: join2(home, ".pochi", "skills")
769
+ },
770
+ {
771
+ id: "zed",
772
+ name: "Zed",
773
+ projectPath: "",
774
+ globalPath: "",
775
+ rulesProjectPath: ".zed/rules",
776
+ rulesGlobalPath: join2(home, ".config", "zed", "rules"),
777
+ ruleFormat: "markdown-dir"
745
778
  }
746
779
  ];
747
780
  var directoryExists = (path2) => pipe3(
@@ -752,10 +785,6 @@ var directoryExists = (path2) => pipe3(
752
785
  Effect3.map(() => true),
753
786
  Effect3.orElseSucceed(() => false)
754
787
  );
755
- var getAgentConfigDir = (skillsPath) => {
756
- const parts = skillsPath.split("/");
757
- return parts.slice(0, -1).join("/");
758
- };
759
788
  var detectAgents = (projectRoot) => {
760
789
  const cwd = projectRoot ?? process4.cwd();
761
790
  return pipe3(
@@ -763,19 +792,25 @@ var detectAgents = (projectRoot) => {
763
792
  AGENTS,
764
793
  (agent) => pipe3(
765
794
  Effect3.all({
766
- hasProjectConfig: directoryExists(
767
- join2(cwd, getAgentConfigDir(agent.projectPath))
768
- ),
769
- hasGlobalConfig: directoryExists(getAgentConfigDir(agent.globalPath))
795
+ hasProjectConfig: agent.projectPath ? directoryExists(join2(cwd, agent.projectPath)) : Effect3.succeed(false),
796
+ hasGlobalConfig: agent.globalPath ? directoryExists(agent.globalPath) : Effect3.succeed(false),
797
+ hasRulesProjectConfig: agent.rulesProjectPath ? directoryExists(join2(cwd, agent.rulesProjectPath)) : Effect3.succeed(false),
798
+ hasRulesGlobalConfig: agent.rulesGlobalPath ? directoryExists(agent.rulesGlobalPath) : Effect3.succeed(false)
770
799
  }),
771
800
  Effect3.map(
772
- ({ hasProjectConfig, hasGlobalConfig }) => ({
773
- ...agent,
801
+ ({
774
802
  hasProjectConfig,
775
- hasGlobalConfig
803
+ hasGlobalConfig,
804
+ hasRulesProjectConfig,
805
+ hasRulesGlobalConfig
806
+ }) => ({
807
+ ...agent,
808
+ hasProjectConfig: hasProjectConfig || hasRulesProjectConfig,
809
+ hasGlobalConfig: hasGlobalConfig || hasRulesGlobalConfig
776
810
  })
777
811
  )
778
- )
812
+ ),
813
+ { concurrency: "unbounded" }
779
814
  ),
780
815
  Effect3.map(
781
816
  (agents) => agents.filter((a) => a.hasProjectConfig || a.hasGlobalConfig)
@@ -787,11 +822,24 @@ var detectAgentsAsync = (projectRoot) => Effect3.runPromise(detectAgents(project
787
822
  var directoryExistsAsync = (path2) => Effect3.runPromise(directoryExists(path2));
788
823
  var resolveInstallPath = (agent, options) => {
789
824
  if (options.global) {
790
- return agent.globalPath;
825
+ return agent.globalPath || void 0;
826
+ }
827
+ if (!agent.projectPath) {
828
+ return void 0;
791
829
  }
792
830
  const cwd = options.projectRoot ?? process4.cwd();
793
831
  return join2(cwd, agent.projectPath);
794
832
  };
833
+ var resolveRulesInstallPath = (agent, options) => {
834
+ if (options.global) {
835
+ return agent.rulesGlobalPath;
836
+ }
837
+ if (!agent.rulesProjectPath) {
838
+ return void 0;
839
+ }
840
+ const cwd = options.projectRoot ?? process4.cwd();
841
+ return join2(cwd, agent.rulesProjectPath);
842
+ };
795
843
 
796
844
  // src/commands/install.ts
797
845
  init_config();
@@ -867,12 +915,204 @@ var readMetadataAsync = (skillsDir) => Effect4.runPromise(readMetadata(skillsDir
867
915
  var updateMetadataAsync = (skillsDir, newSkills) => Effect4.runPromise(updateMetadata(skillsDir, newSkills));
868
916
  var removeFromMetadataAsync = (skillsDir, skillName) => Effect4.runPromise(removeFromMetadata(skillsDir, skillName));
869
917
 
870
- // src/lib/skill-writer.ts
918
+ // src/lib/rule-writer.ts
871
919
  init_esm_shims();
872
- import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
873
- import { dirname as dirname2, join as join4 } from "path";
920
+ import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
921
+ import { dirname as dirname2, resolve, sep } from "path";
874
922
  import { Data as Data4, Effect as Effect5, pipe as pipe5 } from "effect";
875
- var WriteError = class extends Data4.TaggedError("WriteError") {
923
+ var RuleWriteError = class extends Data4.TaggedError("RuleWriteError") {
924
+ };
925
+ var createDirectory = (dir) => Effect5.tryPromise({
926
+ try: () => mkdir2(dir, { recursive: true }),
927
+ catch: (e) => new RuleWriteError({ path: dir, operation: "mkdir", cause: e })
928
+ });
929
+ var writeTextFile = (fullPath, content) => Effect5.tryPromise({
930
+ try: () => writeFile3(fullPath, content, "utf-8"),
931
+ catch: (e) => new RuleWriteError({ path: fullPath, operation: "write", cause: e })
932
+ });
933
+ var readTextFile = (fullPath) => Effect5.tryPromise({
934
+ try: () => readFile3(fullPath, "utf-8"),
935
+ catch: (e) => new RuleWriteError({ path: fullPath, operation: "read", cause: e })
936
+ });
937
+ var assertRulePathWithinBase = (basePath, ruleName) => {
938
+ const resolvedBase = resolve(basePath);
939
+ const resolvedFull = resolve(basePath, ruleName);
940
+ if (resolvedFull !== resolvedBase && !resolvedFull.startsWith(resolvedBase + sep)) {
941
+ return Effect5.fail(
942
+ new RuleWriteError({
943
+ path: ruleName,
944
+ operation: "write",
945
+ cause: new Error(
946
+ `Path traversal detected: "${ruleName}" resolves outside base directory`
947
+ )
948
+ })
949
+ );
950
+ }
951
+ return Effect5.succeed(resolvedFull);
952
+ };
953
+ var BRAID_SECTION_START = "<!-- braid:rules:start -->";
954
+ var BRAID_SECTION_END = "<!-- braid:rules:end -->";
955
+ var YAML_SPECIAL_CHARS = /[\n\r:#{}[\],&*?|>!'"%@`]/;
956
+ function escapeYamlValue(value) {
957
+ if (YAML_SPECIAL_CHARS.test(value) || value.startsWith(" ") || value.endsWith(" ")) {
958
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
959
+ }
960
+ return value;
961
+ }
962
+ function buildMdcContent(rule) {
963
+ const lines = ["---"];
964
+ lines.push(`description: ${escapeYamlValue(rule.title)}`);
965
+ lines.push("alwaysApply: true");
966
+ lines.push("---");
967
+ lines.push("");
968
+ lines.push(`# ${rule.title}`);
969
+ lines.push("");
970
+ lines.push(rule.content);
971
+ return lines.join("\n");
972
+ }
973
+ function buildAppendContent(rules) {
974
+ const lines = [BRAID_SECTION_START, ""];
975
+ for (const rule of rules) {
976
+ lines.push(`## ${rule.title}`);
977
+ lines.push("");
978
+ lines.push(rule.content);
979
+ lines.push("");
980
+ }
981
+ lines.push(BRAID_SECTION_END);
982
+ return lines.join("\n");
983
+ }
984
+ var writeMdcRules = (basePath, rules) => pipe5(
985
+ createDirectory(basePath),
986
+ Effect5.flatMap(
987
+ () => Effect5.forEach(
988
+ rules,
989
+ (rule) => pipe5(
990
+ assertRulePathWithinBase(basePath, `${rule.name}.mdc`),
991
+ Effect5.flatMap(
992
+ (filePath) => writeTextFile(filePath, buildMdcContent(rule))
993
+ )
994
+ ),
995
+ { concurrency: "unbounded" }
996
+ )
997
+ ),
998
+ Effect5.asVoid
999
+ );
1000
+ var writeMarkdownDirRules = (basePath, rules) => pipe5(
1001
+ createDirectory(basePath),
1002
+ Effect5.flatMap(
1003
+ () => Effect5.forEach(
1004
+ rules,
1005
+ (rule) => pipe5(
1006
+ assertRulePathWithinBase(basePath, `${rule.name}.md`),
1007
+ Effect5.flatMap(
1008
+ (filePath) => writeTextFile(filePath, `# ${rule.title}
1009
+
1010
+ ${rule.content}`)
1011
+ )
1012
+ ),
1013
+ { concurrency: "unbounded" }
1014
+ )
1015
+ ),
1016
+ Effect5.asVoid
1017
+ );
1018
+ var writeAppendSingleRules = (filePath, rules) => pipe5(
1019
+ createDirectory(dirname2(filePath)),
1020
+ Effect5.flatMap(
1021
+ () => pipe5(
1022
+ readTextFile(filePath),
1023
+ Effect5.orElseSucceed(() => "")
1024
+ )
1025
+ ),
1026
+ Effect5.flatMap((existing) => {
1027
+ const braidContent = buildAppendContent(rules);
1028
+ const startIdx = existing.indexOf(BRAID_SECTION_START);
1029
+ const endIdx = existing.indexOf(BRAID_SECTION_END);
1030
+ let newContent;
1031
+ if (startIdx !== -1 && endIdx !== -1) {
1032
+ newContent = existing.slice(0, startIdx) + braidContent + existing.slice(endIdx + BRAID_SECTION_END.length);
1033
+ } else {
1034
+ newContent = existing ? `${existing}
1035
+
1036
+ ${braidContent}` : braidContent;
1037
+ }
1038
+ return writeTextFile(filePath, newContent);
1039
+ })
1040
+ );
1041
+ var writeRulesForFormat = (basePath, rules, format) => {
1042
+ switch (format) {
1043
+ case "mdc":
1044
+ return writeMdcRules(basePath, rules);
1045
+ case "markdown-dir":
1046
+ return writeMarkdownDirRules(basePath, rules);
1047
+ case "append-single":
1048
+ return writeAppendSingleRules(basePath, rules);
1049
+ default:
1050
+ return Effect5.void;
1051
+ }
1052
+ };
1053
+ var writeRulesForAgent = (agent, rules, rulesPath) => {
1054
+ if (!agent.ruleFormat || rules.length === 0) {
1055
+ return Effect5.succeed({ written: 0, errors: [] });
1056
+ }
1057
+ return pipe5(
1058
+ writeRulesForFormat(rulesPath, rules, agent.ruleFormat),
1059
+ Effect5.map(() => ({
1060
+ written: rules.length,
1061
+ errors: []
1062
+ })),
1063
+ Effect5.catchAll(
1064
+ (error) => Effect5.succeed({
1065
+ written: 0,
1066
+ errors: [
1067
+ {
1068
+ agent: agent.name,
1069
+ error: error.cause instanceof Error ? error.cause.message : String(error.cause)
1070
+ }
1071
+ ]
1072
+ })
1073
+ )
1074
+ );
1075
+ };
1076
+ var writeRulesForAgentAsync = (agent, rules, rulesPath) => Effect5.runPromise(writeRulesForAgent(agent, rules, rulesPath));
1077
+
1078
+ // src/lib/skill-writer.ts
1079
+ init_esm_shims();
1080
+ import { chmod, mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
1081
+ import { dirname as dirname3, resolve as resolve2, sep as sep2 } from "path";
1082
+ import { Data as Data5, Effect as Effect6, pipe as pipe6 } from "effect";
1083
+ var WriteError = class extends Data5.TaggedError("WriteError") {
1084
+ };
1085
+ var createDirectory2 = (dir, fullPath) => Effect6.tryPromise({
1086
+ try: () => mkdir3(dir, { recursive: true }),
1087
+ catch: (e) => new WriteError({ path: fullPath, operation: "mkdir", cause: e })
1088
+ });
1089
+ var writeTextFile2 = (fullPath, content) => Effect6.tryPromise({
1090
+ try: () => writeFile4(fullPath, content, "utf-8"),
1091
+ catch: (e) => new WriteError({ path: fullPath, operation: "write", cause: e })
1092
+ });
1093
+ var writeBinaryFile = (fullPath, content) => Effect6.tryPromise({
1094
+ try: () => writeFile4(fullPath, content),
1095
+ catch: (e) => new WriteError({ path: fullPath, operation: "write", cause: e })
1096
+ });
1097
+ var makeExecutable = (fullPath) => Effect6.tryPromise({
1098
+ try: () => chmod(fullPath, 493),
1099
+ catch: (e) => new WriteError({ path: fullPath, operation: "chmod", cause: e })
1100
+ });
1101
+ var assertWithinBase = (basePath, untrustedPath) => {
1102
+ const resolvedBase = resolve2(basePath);
1103
+ const resolvedFull = resolve2(basePath, untrustedPath);
1104
+ if (resolvedFull !== resolvedBase && !resolvedFull.startsWith(resolvedBase + sep2)) {
1105
+ return Effect6.fail(
1106
+ new WriteError({
1107
+ path: untrustedPath,
1108
+ operation: "write",
1109
+ cause: new Error(
1110
+ `Path traversal detected: "${untrustedPath}" resolves outside base directory`
1111
+ )
1112
+ })
1113
+ );
1114
+ }
1115
+ return Effect6.succeed(resolvedFull);
876
1116
  };
877
1117
  var COMPATIBILITY_REGEX = /^compatibility:\s*.+$/m;
878
1118
  var rewriteCompatibility = (content, agentId) => {
@@ -908,42 +1148,48 @@ var isBinaryFile = (path2) => {
908
1148
  ];
909
1149
  return binaryExtensions.some((ext) => path2.toLowerCase().endsWith(ext));
910
1150
  };
911
- var writeSkillFile = (basePath, file, agentId) => pipe5(
912
- Effect5.tryPromise({
913
- try: async () => {
914
- const fullPath = join4(basePath, file.path);
915
- const dir = dirname2(fullPath);
916
- await mkdir2(dir, { recursive: true });
917
- if (isBinaryFile(file.path)) {
918
- await writeFile3(fullPath, decodeFileContentBinary(file));
919
- } else {
920
- await writeFile3(fullPath, decodeFileContent(file, agentId), "utf-8");
921
- }
922
- },
923
- catch: (e) => new WriteError({ path: join4(basePath, file.path), cause: e })
1151
+ var isScriptFile = (path2) => {
1152
+ const scriptExtensions = [".sh", ".bash", ".py", ".js", ".mjs", ".rb"];
1153
+ const lowerPath = path2.toLowerCase();
1154
+ const inScriptsDir = lowerPath.includes("/scripts/") || lowerPath.startsWith("scripts/");
1155
+ return inScriptsDir && scriptExtensions.some((ext) => lowerPath.endsWith(ext));
1156
+ };
1157
+ var writeFileContent = (fullPath, file, agentId) => isBinaryFile(file.path) ? writeBinaryFile(fullPath, decodeFileContentBinary(file)) : writeTextFile2(fullPath, decodeFileContent(file, agentId));
1158
+ var setExecutableIfScript = (fullPath, filePath) => isScriptFile(filePath) ? makeExecutable(fullPath) : Effect6.void;
1159
+ var writeSkillFile = (basePath, file, agentId) => pipe6(
1160
+ assertWithinBase(basePath, file.path),
1161
+ Effect6.flatMap((fullPath) => {
1162
+ const dir = dirname3(fullPath);
1163
+ return pipe6(
1164
+ createDirectory2(dir, fullPath),
1165
+ Effect6.flatMap(() => writeFileContent(fullPath, file, agentId)),
1166
+ Effect6.flatMap(() => setExecutableIfScript(fullPath, file.path))
1167
+ );
924
1168
  })
925
1169
  );
926
- var writeSkill = (basePath, skill, agentId) => {
927
- const skillDir = join4(basePath, skill.name);
928
- return pipe5(
929
- Effect5.forEach(
930
- skill.files,
931
- (file) => writeSkillFile(skillDir, file, agentId),
932
- {
933
- concurrency: "unbounded"
934
- }
935
- ),
936
- Effect5.map(() => void 0)
937
- );
938
- };
939
- var writeSkills = (basePath, skills, agentId) => pipe5(
940
- Effect5.forEach(
1170
+ var writeSkill = (basePath, skill, agentId) => pipe6(
1171
+ assertWithinBase(basePath, skill.name),
1172
+ Effect6.flatMap(
1173
+ (skillDir) => pipe6(
1174
+ Effect6.forEach(
1175
+ skill.files,
1176
+ (file) => writeSkillFile(skillDir, file, agentId),
1177
+ {
1178
+ concurrency: "unbounded"
1179
+ }
1180
+ ),
1181
+ Effect6.map(() => void 0)
1182
+ )
1183
+ )
1184
+ );
1185
+ var writeSkills = (basePath, skills, agentId) => pipe6(
1186
+ Effect6.forEach(
941
1187
  skills,
942
- (skill) => pipe5(
1188
+ (skill) => pipe6(
943
1189
  writeSkill(basePath, skill, agentId),
944
- Effect5.map(() => ({ success: true, skill: skill.name })),
945
- Effect5.catchAll(
946
- (error) => Effect5.succeed({
1190
+ Effect6.map(() => ({ success: true, skill: skill.name })),
1191
+ Effect6.catchAll(
1192
+ (error) => Effect6.succeed({
947
1193
  success: false,
948
1194
  skill: skill.name,
949
1195
  error: error.cause instanceof Error ? error.cause.message : String(error.cause)
@@ -952,14 +1198,14 @@ var writeSkills = (basePath, skills, agentId) => pipe5(
952
1198
  ),
953
1199
  { concurrency: "unbounded" }
954
1200
  ),
955
- Effect5.map((results) => ({
1201
+ Effect6.map((results) => ({
956
1202
  written: results.filter((r) => r.success).map((r) => r.skill),
957
1203
  errors: results.filter(
958
1204
  (r) => !r.success
959
1205
  ).map((r) => ({ skill: r.skill, error: r.error }))
960
1206
  }))
961
1207
  );
962
- var writeSkillsAsync = (basePath, skills, agentId) => Effect5.runPromise(writeSkills(basePath, skills, agentId));
1208
+ var writeSkillsAsync = (basePath, skills, agentId) => Effect6.runPromise(writeSkills(basePath, skills, agentId));
963
1209
 
964
1210
  // src/lib/tui.ts
965
1211
  init_esm_shims();
@@ -1038,8 +1284,8 @@ function resolveInstallConfig(options, config) {
1038
1284
  return {
1039
1285
  profile: options.profile ?? config.profile,
1040
1286
  serverUrl: options.server ?? config.serverUrl,
1041
- includeUserGlobalRules: options.includeUserGlobals ?? config.includeUserGlobalRules,
1042
- includeOrgGlobalRules: options.includeOrgGlobals ?? config.includeOrgGlobalRules,
1287
+ includeUserGlobal: options.includeUserGlobals ?? config.includeUserGlobal,
1288
+ includeOrgGlobal: options.includeOrgGlobals ?? config.includeOrgGlobal,
1043
1289
  orgProjects: orgProjectsFromFlag ?? config.orgProjects,
1044
1290
  personalProjects: personalProjectsFromFlag ?? config.personalProjects
1045
1291
  };
@@ -1057,9 +1303,9 @@ function validateInstallOptions(resolved) {
1057
1303
  log2.info(" - Use --profile, --org-projects, or --personal-projects flags");
1058
1304
  log2.info("");
1059
1305
  log2.info("Examples:");
1060
- log2.info(" braid-skills install --profile coding-standards");
1061
- log2.info(" braid-skills install --org-projects proj123,proj456");
1062
- log2.info(" braid-skills install --personal-projects myproj1");
1306
+ log2.info(" braid install --profile coding-standards");
1307
+ log2.info(" braid install --org-projects proj123,proj456");
1308
+ log2.info(" braid install --personal-projects myproj1");
1063
1309
  process.exit(1);
1064
1310
  }
1065
1311
  }
@@ -1079,8 +1325,8 @@ function buildSourceDescription(resolved) {
1079
1325
  function buildFetchOptions(resolved) {
1080
1326
  const fetchOptions = {
1081
1327
  serverUrl: resolved.serverUrl,
1082
- includeUserGlobalRules: resolved.includeUserGlobalRules,
1083
- includeOrgGlobalRules: resolved.includeOrgGlobalRules
1328
+ includeUserGlobal: resolved.includeUserGlobal,
1329
+ includeOrgGlobal: resolved.includeOrgGlobal
1084
1330
  };
1085
1331
  if (resolved.profile) {
1086
1332
  fetchOptions.profile = resolved.profile;
@@ -1125,15 +1371,25 @@ async function resolveAgents(options, installSpinner) {
1125
1371
  return selectedAgents;
1126
1372
  }
1127
1373
  installSpinner.start("Detecting installed agents...");
1128
- const detectedAgents = await detectAgentsAsync();
1129
- installSpinner.stop(`Detected ${detectedAgents.length} agent(s)`);
1374
+ const allDetectedAgents = await detectAgentsAsync();
1375
+ const detectedAgents = allDetectedAgents.filter(
1376
+ (agent) => options.global ? agent.hasGlobalConfig : agent.hasProjectConfig
1377
+ );
1378
+ const targetType = options.global ? "global" : "project";
1379
+ installSpinner.stop(
1380
+ `Detected ${detectedAgents.length} agent(s) with ${targetType} config`
1381
+ );
1382
+ for (const agent of detectedAgents) {
1383
+ const targetPath = options.global ? agent.globalPath : agent.projectPath;
1384
+ log2.info(` ${agent.name} \u2192 ${targetPath}`);
1385
+ }
1130
1386
  if (detectedAgents.length === 0) {
1131
1387
  log2.warn("No AI coding agents detected.");
1132
1388
  log2.info(
1133
1389
  "Supported agents: claude-code, opencode, cursor, windsurf, cline, and more."
1134
1390
  );
1135
1391
  log2.info(
1136
- "Use --agents to specify agents manually: braid-skills install -p my-profile -a claude-code"
1392
+ "Use --agents to specify agents manually: braid install -p my-profile -a claude-code"
1137
1393
  );
1138
1394
  process.exit(1);
1139
1395
  }
@@ -1160,50 +1416,95 @@ async function selectAgents(detectedAgents, options) {
1160
1416
  }
1161
1417
  return selected;
1162
1418
  }
1419
+ async function installSkillsToAgent(agent, response, installPath) {
1420
+ if (response.skills.length === 0 || !agent.projectPath) {
1421
+ return { written: 0, errors: 0 };
1422
+ }
1423
+ const result = await writeSkillsAsync(installPath, response.skills, agent.id);
1424
+ for (const err of result.errors) {
1425
+ log2.warn(` Failed skill: ${err.skill} - ${err.error}`);
1426
+ }
1427
+ return { written: result.written.length, errors: result.errors.length };
1428
+ }
1429
+ async function installRulesToAgent(agent, response, options) {
1430
+ const rules = response.rules ?? [];
1431
+ const rulesPath = resolveRulesInstallPath(agent, {
1432
+ global: options.global === true
1433
+ });
1434
+ if (rules.length === 0 || !rulesPath || !agent.ruleFormat) {
1435
+ return { written: 0, errors: 0 };
1436
+ }
1437
+ const result = await writeRulesForAgentAsync(agent, rules, rulesPath);
1438
+ for (const err of result.errors) {
1439
+ log2.warn(` Failed rules: ${err.agent} - ${err.error}`);
1440
+ }
1441
+ return { written: result.written, errors: result.errors.length };
1442
+ }
1443
+ function formatInstallSummary(agentName, skillsWritten, rulesWritten) {
1444
+ const parts = [];
1445
+ if (skillsWritten > 0) {
1446
+ parts.push(`${skillsWritten} skills`);
1447
+ }
1448
+ if (rulesWritten > 0) {
1449
+ parts.push(`${rulesWritten} rules`);
1450
+ }
1451
+ return `${agentName}: ${parts.join(", ")} installed`;
1452
+ }
1163
1453
  async function installToAgent(agent, response, serverUrl, options, installSpinner) {
1164
1454
  const installPath = resolveInstallPath(agent, {
1165
1455
  global: options.global === true
1166
1456
  });
1167
1457
  installSpinner.start(`Installing to ${agent.name}...`);
1168
- const result = await writeSkillsAsync(installPath, response.skills, agent.id);
1169
- if (result.errors.length > 0) {
1458
+ const skills = installPath ? await installSkillsToAgent(agent, response, installPath) : { written: 0, errors: 0 };
1459
+ const rules = await installRulesToAgent(agent, response, options);
1460
+ const totalWritten = skills.written + rules.written;
1461
+ const totalErrors = skills.errors + rules.errors;
1462
+ if (totalErrors > 0) {
1170
1463
  installSpinner.stop(
1171
- `${agent.name}: ${result.written.length} installed, ${result.errors.length} failed`
1464
+ `${agent.name}: ${totalWritten} installed, ${totalErrors} failed`
1172
1465
  );
1173
- for (const err of result.errors) {
1174
- log2.warn(` Failed: ${err.skill} - ${err.error}`);
1175
- }
1176
1466
  } else {
1177
1467
  installSpinner.stop(
1178
- `${agent.name}: ${result.written.length} skills installed`
1468
+ formatInstallSummary(agent.name, skills.written, rules.written)
1179
1469
  );
1180
1470
  }
1181
- await updateMetadataAsync(
1182
- installPath,
1183
- response.skills.map((s) => ({
1184
- name: s.name,
1185
- source: response.source,
1186
- version: response.version,
1187
- serverUrl
1188
- }))
1189
- );
1190
- return { written: result.written.length, errors: result.errors.length };
1471
+ if (response.skills.length > 0 && installPath) {
1472
+ await updateMetadataAsync(
1473
+ installPath,
1474
+ response.skills.map((s) => ({
1475
+ name: s.name,
1476
+ source: response.source,
1477
+ version: response.version,
1478
+ serverUrl
1479
+ }))
1480
+ );
1481
+ }
1482
+ return { written: totalWritten, errors: totalErrors };
1191
1483
  }
1192
1484
  async function installCommand(options) {
1193
1485
  const config = await loadMergedConfigAsync();
1194
1486
  const resolved = resolveInstallConfig(options, config);
1195
1487
  validateInstallOptions(resolved);
1196
1488
  const sourceDesc = buildSourceDescription(resolved);
1197
- intro2(`Installing skills from ${sourceDesc}`);
1489
+ intro2(`Installing from ${sourceDesc}`);
1198
1490
  const installSpinner = spinner2();
1199
- installSpinner.start("Fetching skills from Braid...");
1491
+ installSpinner.start("Fetching from Braid...");
1200
1492
  try {
1201
1493
  const fetchOptions = buildFetchOptions(resolved);
1202
1494
  const response = await fetchSkillsAsync(fetchOptions);
1203
- installSpinner.stop(`Found ${response.skills.length} skills`);
1204
- if (response.skills.length === 0) {
1495
+ const ruleCount = response.rules?.length ?? 0;
1496
+ const skillCount = response.skills.length;
1497
+ const foundParts = [];
1498
+ if (skillCount > 0) {
1499
+ foundParts.push(`${skillCount} skills`);
1500
+ }
1501
+ if (ruleCount > 0) {
1502
+ foundParts.push(`${ruleCount} rules`);
1503
+ }
1504
+ installSpinner.stop(`Found ${foundParts.join(", ") || "nothing"}`);
1505
+ if (skillCount === 0 && ruleCount === 0) {
1205
1506
  log2.warn(
1206
- "No skills found. Check that your profile/project has enabled rules."
1507
+ "No skills or rules found. Check that your profile/project has enabled prompts."
1207
1508
  );
1208
1509
  process.exit(0);
1209
1510
  }
@@ -1228,13 +1529,13 @@ async function installCommand(options) {
1228
1529
  totalErrors += result.errors;
1229
1530
  }
1230
1531
  if (totalErrors > 0) {
1231
- outro2(`Installed ${totalWritten} skills with ${totalErrors} errors.`);
1532
+ outro2(`Installed ${totalWritten} items with ${totalErrors} errors.`);
1232
1533
  } else {
1233
1534
  outro2(
1234
- `Successfully installed ${totalWritten} skills to ${selectedAgents.length} agent(s).`
1535
+ `Successfully installed ${totalWritten} items to ${selectedAgents.length} agent(s).`
1235
1536
  );
1236
1537
  }
1237
- log2.info("Run 'braid-skills list' to see installed skills.");
1538
+ log2.info("Run 'braid list' to see installed skills.");
1238
1539
  } catch (error) {
1239
1540
  installSpinner.stop("Install failed");
1240
1541
  const message = error instanceof Error ? error.message : String(error);
@@ -1307,6 +1608,9 @@ async function listCommand(options) {
1307
1608
  const installPath = resolveInstallPath(agent, {
1308
1609
  global: options.global === true
1309
1610
  });
1611
+ if (!installPath) {
1612
+ continue;
1613
+ }
1310
1614
  const exists = await directoryExistsAsync(installPath);
1311
1615
  if (!exists) {
1312
1616
  continue;
@@ -1320,10 +1624,8 @@ async function listCommand(options) {
1320
1624
  displayAgentSkills(agent, installPath, braidSkills);
1321
1625
  }
1322
1626
  if (totalSkills === 0) {
1323
- log3.warn("\nNo skills installed via braid-skills.");
1324
- log3.info(
1325
- "Run 'braid-skills install --profile <name>' to install skills."
1326
- );
1627
+ log3.warn("\nNo skills installed via braid.");
1628
+ log3.info("Run 'braid install --profile <name>' to install skills.");
1327
1629
  } else {
1328
1630
  log3.info("");
1329
1631
  log3.info(`Total: ${totalSkills} skill(s) installed`);
@@ -1339,7 +1641,7 @@ async function listCommand(options) {
1339
1641
  // src/commands/remove.ts
1340
1642
  init_esm_shims();
1341
1643
  import { rm } from "fs/promises";
1342
- import { join as join5 } from "path";
1644
+ import { join as join4, resolve as resolve3 } from "path";
1343
1645
  import process6 from "process";
1344
1646
  async function collectInstalledSkills(detectedAgents, options) {
1345
1647
  const skillsToRemove = [];
@@ -1347,6 +1649,9 @@ async function collectInstalledSkills(detectedAgents, options) {
1347
1649
  const installPath = resolveInstallPath(agent, {
1348
1650
  global: options.global === true
1349
1651
  });
1652
+ if (!installPath) {
1653
+ continue;
1654
+ }
1350
1655
  const exists = await directoryExistsAsync(installPath);
1351
1656
  if (!exists) {
1352
1657
  continue;
@@ -1357,7 +1662,7 @@ async function collectInstalledSkills(detectedAgents, options) {
1357
1662
  name: skill.name,
1358
1663
  agentName: agent.name,
1359
1664
  installPath,
1360
- skillPath: join5(installPath, skill.name)
1665
+ skillPath: join4(installPath, skill.name)
1361
1666
  });
1362
1667
  }
1363
1668
  }
@@ -1368,7 +1673,7 @@ async function selectSkillsToRemove(skillsToRemove, options) {
1368
1673
  const selected = skillsToRemove.filter((s) => s.name === options.skill);
1369
1674
  if (selected.length === 0) {
1370
1675
  log2.error(`Skill '${options.skill}' not found.`);
1371
- log2.info("Run 'braid-skills list' to see installed skills.");
1676
+ log2.info("Run 'braid list' to see installed skills.");
1372
1677
  process6.exit(1);
1373
1678
  }
1374
1679
  return selected;
@@ -1408,7 +1713,14 @@ async function confirmRemoval(selectedCount, options) {
1408
1713
  async function removeSkill(skill, removeSpinner) {
1409
1714
  removeSpinner.start(`Removing ${skill.name} from ${skill.agentName}...`);
1410
1715
  try {
1411
- await rm(skill.skillPath, { recursive: true, force: true });
1716
+ const resolvedSkillPath = resolve3(skill.skillPath);
1717
+ const resolvedInstallPath = resolve3(skill.installPath);
1718
+ if (!resolvedSkillPath.startsWith(`${resolvedInstallPath}/`)) {
1719
+ removeSpinner.stop(`Unsafe path for ${skill.name}`);
1720
+ log2.warn(" Skill path escapes install directory, skipping.");
1721
+ return false;
1722
+ }
1723
+ await rm(resolvedSkillPath, { recursive: true, force: true });
1412
1724
  await removeFromMetadataAsync(skill.installPath, skill.name);
1413
1725
  removeSpinner.stop(`Removed ${skill.name} from ${skill.agentName}`);
1414
1726
  return true;
@@ -1435,7 +1747,7 @@ async function removeCommand(options) {
1435
1747
  );
1436
1748
  removeSpinner.stop(`Found ${skillsToRemove.length} installed skill(s)`);
1437
1749
  if (skillsToRemove.length === 0) {
1438
- log2.warn("No skills installed via braid-skills.");
1750
+ log2.warn("No skills installed via braid.");
1439
1751
  return;
1440
1752
  }
1441
1753
  const selected = await selectSkillsToRemove(skillsToRemove, options);
@@ -1473,18 +1785,25 @@ import {
1473
1785
  outro as outro3,
1474
1786
  spinner as spinner4
1475
1787
  } from "@clack/prompts";
1476
- import { Data as Data5, Effect as Effect6, pipe as pipe6 } from "effect";
1477
- var UpdateError = class extends Data5.TaggedError("UpdateError") {
1788
+ import { Data as Data6, Effect as Effect7, pipe as pipe7 } from "effect";
1789
+ var UpdateError = class extends Data6.TaggedError("UpdateError") {
1478
1790
  };
1479
- var UserCancelledError = class extends Data5.TaggedError("UserCancelledError") {
1791
+ var UserCancelledError = class extends Data6.TaggedError("UserCancelledError") {
1480
1792
  };
1481
- var collectSourcesFromAgent = (agent, options, sourcesToUpdate) => Effect6.tryPromise({
1793
+ async function resolveValidInstallPath(agent, options) {
1794
+ const installPath = resolveInstallPath(agent, {
1795
+ global: options.global === true
1796
+ });
1797
+ if (!installPath) {
1798
+ return null;
1799
+ }
1800
+ const exists = await directoryExistsAsync(installPath);
1801
+ return exists ? installPath : null;
1802
+ }
1803
+ var collectSourcesFromAgent = (agent, options, sourcesToUpdate) => Effect7.tryPromise({
1482
1804
  try: async () => {
1483
- const installPath = resolveInstallPath(agent, {
1484
- global: options.global === true
1485
- });
1486
- const exists = await directoryExistsAsync(installPath);
1487
- if (!exists) {
1805
+ const installPath = await resolveValidInstallPath(agent, options);
1806
+ if (!installPath) {
1488
1807
  return;
1489
1808
  }
1490
1809
  const metadata = await readMetadataAsync(installPath);
@@ -1512,10 +1831,10 @@ var collectSourcesFromAgent = (agent, options, sourcesToUpdate) => Effect6.tryPr
1512
1831
  },
1513
1832
  catch: () => new UpdateError({ message: "Failed to collect sources" })
1514
1833
  });
1515
- var collectSources = (detectedAgents, options) => pipe6(
1516
- Effect6.succeed(/* @__PURE__ */ new Map()),
1517
- Effect6.tap(
1518
- (sourcesToUpdate) => Effect6.forEach(
1834
+ var collectSources = (detectedAgents, options) => pipe7(
1835
+ Effect7.succeed(/* @__PURE__ */ new Map()),
1836
+ Effect7.tap(
1837
+ (sourcesToUpdate) => Effect7.forEach(
1519
1838
  detectedAgents,
1520
1839
  (agent) => collectSourcesFromAgent(agent, options, sourcesToUpdate),
1521
1840
  { concurrency: 1 }
@@ -1524,9 +1843,9 @@ var collectSources = (detectedAgents, options) => pipe6(
1524
1843
  );
1525
1844
  var selectSources = (sourcesToUpdate, options) => {
1526
1845
  if (options.yes) {
1527
- return Effect6.succeed(sourcesToUpdate);
1846
+ return Effect7.succeed(sourcesToUpdate);
1528
1847
  }
1529
- return Effect6.tryPromise({
1848
+ return Effect7.tryPromise({
1530
1849
  try: async () => {
1531
1850
  const sources = Array.from(sourcesToUpdate.entries()).map(
1532
1851
  ([key, source]) => ({
@@ -1577,7 +1896,7 @@ var buildFetchOptionsForSource = (source, options) => {
1577
1896
  }
1578
1897
  return fetchOptions;
1579
1898
  };
1580
- var updateAgentSkills = (agentId, agentName, installPath, response, serverUrl, updateSpinner) => Effect6.tryPromise({
1899
+ var updateAgentSkills = (agentId, agentName, installPath, response, serverUrl, updateSpinner) => Effect7.tryPromise({
1581
1900
  try: async () => {
1582
1901
  updateSpinner.start(`Updating ${agentName}...`);
1583
1902
  const result = await writeSkillsAsync(
@@ -1615,15 +1934,15 @@ var updateSource = (source, options, updateSpinner) => {
1615
1934
  const serverUrl = options.server ?? source.serverUrl;
1616
1935
  const fetchOptions = buildFetchOptionsForSource(source, options);
1617
1936
  if (fetchOptions === null) {
1618
- return Effect6.fail(
1937
+ return Effect7.fail(
1619
1938
  new UpdateError({
1620
- message: "Skills installed with legacy metadata format. Please reinstall using 'braid-skills install --profile <name>' or 'braid-skills install --projects <names>'.",
1939
+ message: "Skills installed with legacy metadata format. Please reinstall using 'braid install --profile <name>' or 'braid install --projects <names>'.",
1621
1940
  source: sourceDesc
1622
1941
  })
1623
1942
  );
1624
1943
  }
1625
- return pipe6(
1626
- Effect6.tryPromise({
1944
+ return pipe7(
1945
+ Effect7.tryPromise({
1627
1946
  try: async () => {
1628
1947
  updateSpinner.start(`Fetching latest skills from ${sourceDesc}...`);
1629
1948
  const response = await fetchSkillsAsync(fetchOptions);
@@ -1637,9 +1956,9 @@ var updateSource = (source, options, updateSpinner) => {
1637
1956
  source: sourceDesc
1638
1957
  })
1639
1958
  }),
1640
- Effect6.flatMap(
1641
- (response) => pipe6(
1642
- Effect6.forEach(
1959
+ Effect7.flatMap(
1960
+ (response) => pipe7(
1961
+ Effect7.forEach(
1643
1962
  source.agents,
1644
1963
  ({ agentId, agentName, installPath }) => updateAgentSkills(
1645
1964
  agentId,
@@ -1651,7 +1970,7 @@ var updateSource = (source, options, updateSpinner) => {
1651
1970
  ),
1652
1971
  { concurrency: 1 }
1653
1972
  ),
1654
- Effect6.map((results) => ({
1973
+ Effect7.map((results) => ({
1655
1974
  updated: results.reduce((sum, r) => sum + r.updated, 0),
1656
1975
  errors: results.reduce((sum, r) => sum + r.errors, 0)
1657
1976
  }))
@@ -1659,22 +1978,22 @@ var updateSource = (source, options, updateSpinner) => {
1659
1978
  )
1660
1979
  );
1661
1980
  };
1662
- var updateAllSources = (sources, options, updateSpinner) => pipe6(
1663
- Effect6.forEach(
1981
+ var updateAllSources = (sources, options, updateSpinner) => pipe7(
1982
+ Effect7.forEach(
1664
1983
  Array.from(sources.values()),
1665
- (source) => pipe6(
1984
+ (source) => pipe7(
1666
1985
  updateSource(source, options, updateSpinner),
1667
- Effect6.catchAll((error) => {
1986
+ Effect7.catchAll((error) => {
1668
1987
  updateSpinner.stop(
1669
1988
  `Failed to update from ${getSourceDesc(source)}`
1670
1989
  );
1671
1990
  log4.error(` ${error.message}`);
1672
- return Effect6.succeed({ updated: 0, errors: 1 });
1991
+ return Effect7.succeed({ updated: 0, errors: 1 });
1673
1992
  })
1674
1993
  ),
1675
1994
  { concurrency: 1 }
1676
1995
  ),
1677
- Effect6.map((results) => ({
1996
+ Effect7.map((results) => ({
1678
1997
  totalUpdated: results.reduce((sum, r) => sum + r.updated, 0),
1679
1998
  totalErrors: results.reduce((sum, r) => sum + r.errors, 0)
1680
1999
  }))
@@ -1685,11 +2004,9 @@ var handleUpdateError = (error, updateSpinner) => {
1685
2004
  log4.warn(error.message);
1686
2005
  return;
1687
2006
  }
1688
- if (error.message === "No skills installed via braid-skills.") {
2007
+ if (error.message === "No skills installed via braid.") {
1689
2008
  log4.warn(error.message);
1690
- log4.info(
1691
- "Run 'braid-skills install --profile <name>' to install skills first."
1692
- );
2009
+ log4.info("Run 'braid install --profile <name>' to install skills first.");
1693
2010
  return;
1694
2011
  }
1695
2012
  log4.error(error.message);
@@ -1715,28 +2032,28 @@ var handleProgramExit = (result, updateSpinner) => {
1715
2032
  async function updateCommand(options) {
1716
2033
  const updateSpinner = spinner4();
1717
2034
  updateSpinner.start("Scanning for installed skills...");
1718
- const program2 = pipe6(
1719
- Effect6.tryPromise({
2035
+ const program2 = pipe7(
2036
+ Effect7.tryPromise({
1720
2037
  try: () => detectAgentsAsync(),
1721
2038
  catch: () => new UpdateError({ message: "Failed to detect agents" })
1722
2039
  }),
1723
- Effect6.filterOrFail(
2040
+ Effect7.filterOrFail(
1724
2041
  (agents) => agents.length > 0,
1725
2042
  () => new UpdateError({ message: "No AI coding agents detected." })
1726
2043
  ),
1727
- Effect6.flatMap((detectedAgents) => collectSources(detectedAgents, options)),
1728
- Effect6.tap((sources) => {
2044
+ Effect7.flatMap((detectedAgents) => collectSources(detectedAgents, options)),
2045
+ Effect7.tap((sources) => {
1729
2046
  updateSpinner.stop(`Found ${sources.size} source(s) to update`);
1730
2047
  }),
1731
- Effect6.filterOrFail(
2048
+ Effect7.filterOrFail(
1732
2049
  (sources) => sources.size > 0,
1733
- () => new UpdateError({ message: "No skills installed via braid-skills." })
2050
+ () => new UpdateError({ message: "No skills installed via braid." })
1734
2051
  ),
1735
- Effect6.flatMap((sources) => selectSources(sources, options)),
1736
- Effect6.flatMap(
2052
+ Effect7.flatMap((sources) => selectSources(sources, options)),
2053
+ Effect7.flatMap(
1737
2054
  (selectedSources) => updateAllSources(selectedSources, options, updateSpinner)
1738
2055
  ),
1739
- Effect6.tap(({ totalUpdated, totalErrors }) => {
2056
+ Effect7.tap(({ totalUpdated, totalErrors }) => {
1740
2057
  if (totalErrors > 0) {
1741
2058
  outro3(`Updated ${totalUpdated} skills with ${totalErrors} errors.`);
1742
2059
  } else {
@@ -1744,14 +2061,14 @@ async function updateCommand(options) {
1744
2061
  }
1745
2062
  })
1746
2063
  );
1747
- const result = await Effect6.runPromiseExit(program2);
2064
+ const result = await Effect7.runPromiseExit(program2);
1748
2065
  handleProgramExit(result, updateSpinner);
1749
2066
  }
1750
2067
 
1751
2068
  // src/index.ts
1752
2069
  var program = new Command();
1753
- program.name("braid-skills").description(
1754
- "Install Braid rules as agent skills to your local development environment"
2070
+ program.name("braid").description(
2071
+ "Install Braid prompts as agent skills to your local development environment"
1755
2072
  ).version("0.1.0");
1756
2073
  var auth = program.command("auth").description("Configure API key for Braid authentication");
1757
2074
  auth.command("login", { isDefault: true }).description("Configure API key").option("-s, --server <url>", "Braid server URL (for review apps, local dev)").action(authCommand);
@@ -1765,11 +2082,11 @@ program.command("install").alias("add").description("Install skills from a profi
1765
2082
  "Comma-separated personal project IDs to install from"
1766
2083
  ).option(
1767
2084
  "--include-user-globals",
1768
- "Include user's personal global rules (default: true)"
1769
- ).option("--no-include-user-globals", "Exclude user's personal global rules").option(
2085
+ "Include user's personal global prompts (default: true)"
2086
+ ).option("--no-include-user-globals", "Exclude user's personal global prompts").option(
1770
2087
  "--include-org-globals",
1771
- "Include organization's global rules (default: true)"
1772
- ).option("--no-include-org-globals", "Exclude organization's global rules").option(
2088
+ "Include organization's global prompts (default: true)"
2089
+ ).option("--no-include-org-globals", "Exclude organization's global prompts").option(
1773
2090
  "-a, --agents <list>",
1774
2091
  "Comma-separated list of agents (e.g., claude-code,opencode)"
1775
2092
  ).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);