aiblueprint-cli 1.1.3 → 1.1.7

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/cli.js CHANGED
@@ -32360,10 +32360,9 @@ var inquirer = {
32360
32360
  var lib_default = inquirer;
32361
32361
 
32362
32362
  // src/commands/setup.ts
32363
- var import_fs_extra = __toESM(require_lib4(), 1);
32364
- import path2 from "path";
32365
- import os3 from "os";
32366
- import { execSync } from "child_process";
32363
+ var import_fs_extra5 = __toESM(require_lib4(), 1);
32364
+ import path6 from "path";
32365
+ import os5 from "os";
32367
32366
 
32368
32367
  // node_modules/chalk/source/vendor/ansi-styles/index.js
32369
32368
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -32858,8 +32857,278 @@ var source_default = chalk;
32858
32857
  // src/commands/setup.ts
32859
32858
  import { fileURLToPath } from "url";
32860
32859
  import { dirname } from "path";
32861
- var __filename2 = fileURLToPath(import.meta.url);
32862
- var __dirname2 = dirname(__filename2);
32860
+
32861
+ // src/commands/setup/shell-shortcuts.ts
32862
+ var import_fs_extra = __toESM(require_lib4(), 1);
32863
+ import path2 from "path";
32864
+ import os3 from "os";
32865
+ async function setupShellShortcuts() {
32866
+ try {
32867
+ const platform = os3.platform();
32868
+ let shellConfigFile;
32869
+ if (platform === "darwin") {
32870
+ shellConfigFile = path2.join(os3.homedir(), ".zshenv");
32871
+ } else if (platform === "linux") {
32872
+ const shell = process.env.SHELL || "";
32873
+ if (shell.includes("zsh")) {
32874
+ shellConfigFile = path2.join(os3.homedir(), ".zshrc");
32875
+ } else {
32876
+ shellConfigFile = path2.join(os3.homedir(), ".bashrc");
32877
+ }
32878
+ } else {
32879
+ console.log(source_default.yellow("Shell shortcuts are only supported on macOS and Linux"));
32880
+ return;
32881
+ }
32882
+ const aliases = `
32883
+ # AIBlueprint Claude Code aliases
32884
+ alias cc="claude --dangerously-skip-permissions"
32885
+ alias ccc="claude --dangerously-skip-permissions -c"
32886
+ `;
32887
+ const existingContent = await import_fs_extra.default.readFile(shellConfigFile, "utf-8").catch(() => "");
32888
+ if (!existingContent.includes("AIBlueprint Claude Code aliases")) {
32889
+ await import_fs_extra.default.appendFile(shellConfigFile, aliases);
32890
+ }
32891
+ } catch (error) {
32892
+ console.error(source_default.red("Error setting up shell shortcuts:"), error);
32893
+ throw error;
32894
+ }
32895
+ }
32896
+
32897
+ // src/commands/setup/symlinks.ts
32898
+ var import_fs_extra2 = __toESM(require_lib4(), 1);
32899
+ import path3 from "path";
32900
+ import os4 from "os";
32901
+ async function getToolPaths(tool, customFolder) {
32902
+ let baseDir;
32903
+ switch (tool) {
32904
+ case "claude-code":
32905
+ baseDir = customFolder ? path3.resolve(customFolder) : path3.join(os4.homedir(), ".claude");
32906
+ return {
32907
+ baseDir,
32908
+ commandsPath: path3.join(baseDir, "commands"),
32909
+ agentsPath: path3.join(baseDir, "agents")
32910
+ };
32911
+ case "codex":
32912
+ baseDir = customFolder ? path3.resolve(customFolder) : path3.join(os4.homedir(), ".codex");
32913
+ return {
32914
+ baseDir,
32915
+ commandsPath: path3.join(baseDir, "prompts")
32916
+ };
32917
+ case "opencode":
32918
+ baseDir = customFolder ? path3.resolve(customFolder) : path3.join(os4.homedir(), ".config", "opencode");
32919
+ return {
32920
+ baseDir,
32921
+ commandsPath: path3.join(baseDir, "command")
32922
+ };
32923
+ case "factoryai":
32924
+ baseDir = customFolder ? path3.resolve(customFolder) : path3.join(os4.homedir(), ".factory");
32925
+ return {
32926
+ baseDir,
32927
+ commandsPath: path3.join(baseDir, "commands"),
32928
+ agentsPath: path3.join(baseDir, "droids")
32929
+ };
32930
+ default:
32931
+ throw new Error(`Unknown tool type: ${tool}`);
32932
+ }
32933
+ }
32934
+ async function createSymlink(sourcePath, targetPath, options = {}) {
32935
+ try {
32936
+ const sourceExists = await import_fs_extra2.default.pathExists(sourcePath);
32937
+ if (!sourceExists) {
32938
+ console.log(source_default.yellow(` Source path ${sourcePath} does not exist. Skipping symlink creation...`));
32939
+ return false;
32940
+ }
32941
+ const targetDir = path3.dirname(targetPath);
32942
+ await import_fs_extra2.default.ensureDir(targetDir);
32943
+ const targetExists = await import_fs_extra2.default.pathExists(targetPath);
32944
+ if (targetExists) {
32945
+ const stat = await import_fs_extra2.default.lstat(targetPath);
32946
+ if (stat.isSymbolicLink()) {
32947
+ await import_fs_extra2.default.remove(targetPath);
32948
+ } else {
32949
+ console.log(source_default.yellow(options.skipMessage || ` ${targetPath} already exists and is not a symlink. Skipping...`));
32950
+ return false;
32951
+ }
32952
+ }
32953
+ await import_fs_extra2.default.symlink(sourcePath, targetPath);
32954
+ return true;
32955
+ } catch (error) {
32956
+ console.error(source_default.red(options.errorPrefix || "Error creating symlink:"), error);
32957
+ throw error;
32958
+ }
32959
+ }
32960
+ async function setupCodexSymlink(claudeDir, customCodexFolder, customClaudeCodeFolder) {
32961
+ try {
32962
+ let codexDir;
32963
+ if (customCodexFolder) {
32964
+ codexDir = path3.resolve(customCodexFolder);
32965
+ } else if (customClaudeCodeFolder) {
32966
+ const parentDir = path3.dirname(claudeDir);
32967
+ codexDir = path3.join(parentDir, "codex");
32968
+ } else {
32969
+ codexDir = path3.join(os4.homedir(), ".codex");
32970
+ }
32971
+ const promptsPath = path3.join(codexDir, "prompts");
32972
+ const commandsPath = path3.join(claudeDir, "commands");
32973
+ await createSymlink(commandsPath, promptsPath, {
32974
+ skipMessage: " ~/.codex/prompts already exists and is not a symlink. Skipping...",
32975
+ errorPrefix: "Error setting up Codex symlink:"
32976
+ });
32977
+ } catch (error) {
32978
+ console.error(source_default.red("Error setting up Codex symlink:"), error);
32979
+ }
32980
+ }
32981
+ async function setupOpenCodeSymlink(claudeDir, customOpenCodeFolder, customClaudeCodeFolder) {
32982
+ try {
32983
+ let openCodeDir;
32984
+ if (customOpenCodeFolder) {
32985
+ openCodeDir = path3.resolve(customOpenCodeFolder);
32986
+ } else if (customClaudeCodeFolder) {
32987
+ const parentDir = path3.dirname(claudeDir);
32988
+ openCodeDir = path3.join(parentDir, ".opencode");
32989
+ } else {
32990
+ openCodeDir = path3.join(os4.homedir(), ".config", "opencode");
32991
+ }
32992
+ const commandPath = path3.join(openCodeDir, "command");
32993
+ const commandsPath = path3.join(claudeDir, "commands");
32994
+ await createSymlink(commandsPath, commandPath, {
32995
+ skipMessage: " ~/.config/opencode/command already exists and is not a symlink. Skipping...",
32996
+ errorPrefix: "Error setting up OpenCode symlink:"
32997
+ });
32998
+ } catch (error) {
32999
+ console.error(source_default.red("Error setting up OpenCode symlink:"), error);
33000
+ }
33001
+ }
33002
+
33003
+ // src/commands/setup/dependencies.ts
33004
+ import { execSync } from "child_process";
33005
+ async function checkAndInstallDependencies() {
33006
+ const checkCommand = (cmd) => {
33007
+ try {
33008
+ execSync(`which ${cmd}`, { stdio: "ignore" });
33009
+ return true;
33010
+ } catch {
33011
+ return false;
33012
+ }
33013
+ };
33014
+ if (!checkCommand("bun")) {
33015
+ console.log(source_default.yellow(`
33016
+ Installing bun...`));
33017
+ try {
33018
+ execSync("npm install -g bun", { stdio: "inherit" });
33019
+ } catch (error) {
33020
+ console.log(source_default.red(" Failed to install bun. Please install it manually: npm install -g bun"));
33021
+ }
33022
+ }
33023
+ if (!checkCommand("ccusage")) {
33024
+ console.log(source_default.yellow(`
33025
+ Installing ccusage...`));
33026
+ try {
33027
+ execSync("npm install -g ccusage", { stdio: "inherit" });
33028
+ } catch (error) {
33029
+ console.log(source_default.red(" Failed to install ccusage. Please install it manually: npm install -g ccusage"));
33030
+ }
33031
+ }
33032
+ }
33033
+
33034
+ // src/commands/setup/settings.ts
33035
+ var import_fs_extra3 = __toESM(require_lib4(), 1);
33036
+ import path4 from "path";
33037
+ async function updateSettings(options, claudeDir) {
33038
+ const settingsPath = path4.join(claudeDir, "settings.json");
33039
+ let settings = {};
33040
+ try {
33041
+ const existingSettings = await import_fs_extra3.default.readFile(settingsPath, "utf-8");
33042
+ settings = JSON.parse(existingSettings);
33043
+ } catch {
33044
+ }
33045
+ if (options.customStatusline) {
33046
+ if (settings.statusLine) {
33047
+ const confirmAnswer = await lib_default.prompt([
33048
+ {
33049
+ type: "confirm",
33050
+ name: "replace",
33051
+ message: "You already have a statusLine configuration. Replace it?"
33052
+ }
33053
+ ]);
33054
+ if (!confirmAnswer.replace) {
33055
+ console.log(source_default.yellow(" Keeping existing statusLine configuration"));
33056
+ } else {
33057
+ settings.statusLine = {
33058
+ type: "command",
33059
+ command: `bash ${path4.join(claudeDir, "scripts/statusline-ccusage.sh")}`,
33060
+ padding: 0
33061
+ };
33062
+ }
33063
+ } else {
33064
+ settings.statusLine = {
33065
+ type: "command",
33066
+ command: `bash ${path4.join(claudeDir, "scripts/statusline-ccusage.sh")}`,
33067
+ padding: 0
33068
+ };
33069
+ }
33070
+ }
33071
+ if (!settings.hooks) {
33072
+ settings.hooks = {};
33073
+ }
33074
+ if (options.commandValidation) {
33075
+ if (!settings.hooks.PreToolUse) {
33076
+ settings.hooks.PreToolUse = [];
33077
+ }
33078
+ const bashHook = {
33079
+ matcher: "Bash",
33080
+ hooks: [
33081
+ {
33082
+ type: "command",
33083
+ command: `bun ${path4.join(claudeDir, "scripts/validate-command.js")}`
33084
+ }
33085
+ ]
33086
+ };
33087
+ const existingBashHook = settings.hooks.PreToolUse.find((h) => h.matcher === "Bash");
33088
+ if (!existingBashHook) {
33089
+ settings.hooks.PreToolUse.push(bashHook);
33090
+ }
33091
+ }
33092
+ if (options.notificationSounds) {
33093
+ if (!settings.hooks.Stop) {
33094
+ settings.hooks.Stop = [];
33095
+ }
33096
+ const stopHook = {
33097
+ matcher: "",
33098
+ hooks: [
33099
+ {
33100
+ type: "command",
33101
+ command: `afplay -v 0.1 ${path4.join(claudeDir, "song/finish.mp3")}`
33102
+ }
33103
+ ]
33104
+ };
33105
+ const existingStopHook = settings.hooks.Stop.find((h) => h.hooks?.some((hook) => hook.command?.includes("finish.mp3")));
33106
+ if (!existingStopHook) {
33107
+ settings.hooks.Stop.push(stopHook);
33108
+ }
33109
+ if (!settings.hooks.Notification) {
33110
+ settings.hooks.Notification = [];
33111
+ }
33112
+ const notificationHook = {
33113
+ matcher: "",
33114
+ hooks: [
33115
+ {
33116
+ type: "command",
33117
+ command: `afplay -v 0.1 ${path4.join(claudeDir, "song/need-human.mp3")}`
33118
+ }
33119
+ ]
33120
+ };
33121
+ const existingNotificationHook = settings.hooks.Notification.find((h) => h.hooks?.some((hook) => hook.command?.includes("need-human.mp3")));
33122
+ if (!existingNotificationHook) {
33123
+ settings.hooks.Notification.push(notificationHook);
33124
+ }
33125
+ }
33126
+ await import_fs_extra3.default.writeJson(settingsPath, settings, { spaces: 2 });
33127
+ }
33128
+
33129
+ // src/commands/setup/utils.ts
33130
+ var import_fs_extra4 = __toESM(require_lib4(), 1);
33131
+ import path5 from "path";
32863
33132
 
32864
33133
  class SimpleSpinner {
32865
33134
  message = "";
@@ -32880,8 +33149,8 @@ async function downloadFromGitHub(relativePath, targetPath) {
32880
33149
  return false;
32881
33150
  }
32882
33151
  const content = await response.arrayBuffer();
32883
- await import_fs_extra.default.ensureDir(path2.dirname(targetPath));
32884
- await import_fs_extra.default.writeFile(targetPath, Buffer.from(content));
33152
+ await import_fs_extra4.default.ensureDir(path5.dirname(targetPath));
33153
+ await import_fs_extra4.default.writeFile(targetPath, Buffer.from(content));
32885
33154
  return true;
32886
33155
  } catch (error) {
32887
33156
  return false;
@@ -32898,10 +33167,10 @@ async function downloadDirectoryFromGitHub(dirPath, targetDir) {
32898
33167
  if (!Array.isArray(files)) {
32899
33168
  return false;
32900
33169
  }
32901
- await import_fs_extra.default.ensureDir(targetDir);
33170
+ await import_fs_extra4.default.ensureDir(targetDir);
32902
33171
  for (const file of files) {
32903
33172
  const relativePath = `${dirPath}/${file.name}`;
32904
- const targetPath = path2.join(targetDir, file.name);
33173
+ const targetPath = path5.join(targetDir, file.name);
32905
33174
  if (file.type === "file") {
32906
33175
  await downloadFromGitHub(relativePath, targetPath);
32907
33176
  } else if (file.type === "dir") {
@@ -32913,7 +33182,18 @@ async function downloadDirectoryFromGitHub(dirPath, targetDir) {
32913
33182
  return false;
32914
33183
  }
32915
33184
  }
32916
- async function setupCommand(customFolder, skipInteractive) {
33185
+
33186
+ // src/commands/setup.ts
33187
+ var __filename2 = fileURLToPath(import.meta.url);
33188
+ var __dirname2 = dirname(__filename2);
33189
+ var GITHUB_RAW_BASE2 = "https://raw.githubusercontent.com/Melvynx/aiblueprint-cli/main/claude-code-config";
33190
+ async function setupCommand(params = {}) {
33191
+ const {
33192
+ claudeCodeFolder: customClaudeCodeFolder,
33193
+ codexFolder: customCodexFolder,
33194
+ openCodeFolder: customOpenCodeFolder,
33195
+ skipInteractive
33196
+ } = params;
32917
33197
  try {
32918
33198
  console.log(source_default.blue.bold(`
32919
33199
  \uD83D\uDE80 AIBlueprint Claude Code Setup
@@ -32921,23 +33201,73 @@ async function setupCommand(customFolder, skipInteractive) {
32921
33201
  console.log(source_default.bgBlue(" Setting up your Claude Code environment "));
32922
33202
  let features;
32923
33203
  if (skipInteractive) {
32924
- features = ["shellShortcuts", "commandValidation", "customStatusline", "aiblueprintCommands", "aiblueprintAgents", "outputStyles", "notificationSounds"];
33204
+ features = [
33205
+ "shellShortcuts",
33206
+ "commandValidation",
33207
+ "customStatusline",
33208
+ "aiblueprintCommands",
33209
+ "aiblueprintAgents",
33210
+ "outputStyles",
33211
+ "notificationSounds",
33212
+ "codexSymlink",
33213
+ "openCodeSymlink"
33214
+ ];
32925
33215
  console.log(source_default.green("✓ Installing all features (--skip mode)"));
32926
33216
  } else {
32927
- const answers = await lib_default.prompt([{
32928
- type: "checkbox",
32929
- name: "features",
32930
- message: "Which features would you like to install?",
32931
- choices: [
32932
- { value: "shellShortcuts", name: "Shell shortcuts (cc, ccc aliases) - Quick access to Claude Code", checked: true },
32933
- { value: "commandValidation", name: "Command validation - Security hook for bash commands", checked: true },
32934
- { value: "customStatusline", name: "Custom statusline - Shows git, costs, tokens info", checked: true },
32935
- { value: "aiblueprintCommands", name: "AIBlueprint commands - Pre-configured command templates", checked: true },
32936
- { value: "aiblueprintAgents", name: "AIBlueprint agents - Specialized AI agents", checked: true },
32937
- { value: "outputStyles", name: "Output styles - Custom output formatting", checked: true },
32938
- { value: "notificationSounds", name: "Notification sounds - Audio alerts for events", checked: true }
32939
- ]
32940
- }]);
33217
+ const answers = await lib_default.prompt([
33218
+ {
33219
+ type: "checkbox",
33220
+ name: "features",
33221
+ message: "Which features would you like to install?",
33222
+ choices: [
33223
+ {
33224
+ value: "shellShortcuts",
33225
+ name: "Shell shortcuts (cc, ccc aliases) - Quick access to Claude Code",
33226
+ checked: true
33227
+ },
33228
+ {
33229
+ value: "commandValidation",
33230
+ name: "Command validation - Security hook for bash commands",
33231
+ checked: true
33232
+ },
33233
+ {
33234
+ value: "customStatusline",
33235
+ name: "Custom statusline - Shows git, costs, tokens info",
33236
+ checked: true
33237
+ },
33238
+ {
33239
+ value: "aiblueprintCommands",
33240
+ name: "AIBlueprint commands - Pre-configured command templates",
33241
+ checked: true
33242
+ },
33243
+ {
33244
+ value: "aiblueprintAgents",
33245
+ name: "AIBlueprint agents - Specialized AI agents",
33246
+ checked: true
33247
+ },
33248
+ {
33249
+ value: "outputStyles",
33250
+ name: "Output styles - Custom output formatting",
33251
+ checked: true
33252
+ },
33253
+ {
33254
+ value: "notificationSounds",
33255
+ name: "Notification sounds - Audio alerts for events",
33256
+ checked: true
33257
+ },
33258
+ {
33259
+ value: "codexSymlink",
33260
+ name: "Codex symlink - Link commands to ~/.codex/prompts",
33261
+ checked: false
33262
+ },
33263
+ {
33264
+ value: "openCodeSymlink",
33265
+ name: "OpenCode symlink - Link commands to ~/.config/opencode/command",
33266
+ checked: false
33267
+ }
33268
+ ]
33269
+ }
33270
+ ]);
32941
33271
  features = answers.features;
32942
33272
  if (!features || features.length === 0) {
32943
33273
  console.log(source_default.yellow("Setup cancelled - no features selected"));
@@ -32951,15 +33281,17 @@ async function setupCommand(customFolder, skipInteractive) {
32951
33281
  aiblueprintCommands: features.includes("aiblueprintCommands"),
32952
33282
  aiblueprintAgents: features.includes("aiblueprintAgents"),
32953
33283
  outputStyles: features.includes("outputStyles"),
32954
- notificationSounds: features.includes("notificationSounds")
33284
+ notificationSounds: features.includes("notificationSounds"),
33285
+ codexSymlink: features.includes("codexSymlink"),
33286
+ openCodeSymlink: features.includes("openCodeSymlink")
32955
33287
  };
32956
33288
  const s = new SimpleSpinner;
32957
- const claudeDir = customFolder ? path2.resolve(customFolder) : path2.join(os3.homedir(), ".claude");
33289
+ const claudeDir = customClaudeCodeFolder ? path6.resolve(customClaudeCodeFolder) : path6.join(os5.homedir(), ".claude");
32958
33290
  console.log(source_default.gray(`Installing to: ${claudeDir}`));
32959
- await import_fs_extra.default.ensureDir(claudeDir);
33291
+ await import_fs_extra5.default.ensureDir(claudeDir);
32960
33292
  let useGitHub = true;
32961
33293
  let sourceDir;
32962
- const testUrl = `${GITHUB_RAW_BASE}/scripts/validate-command.js`;
33294
+ const testUrl = `${GITHUB_RAW_BASE2}/scripts/validate-command.js`;
32963
33295
  try {
32964
33296
  const testResponse = await fetch(testUrl);
32965
33297
  useGitHub = testResponse.ok;
@@ -32969,14 +33301,14 @@ async function setupCommand(customFolder, skipInteractive) {
32969
33301
  if (!useGitHub) {
32970
33302
  const currentDir = process.cwd();
32971
33303
  const possiblePaths = [
32972
- path2.join(currentDir, "claude-code-config"),
32973
- path2.join(__dirname2, "../../claude-code-config"),
32974
- path2.join(__dirname2, "../claude-code-config"),
32975
- path2.join(path2.dirname(process.argv[1]), "../claude-code-config")
33304
+ path6.join(currentDir, "claude-code-config"),
33305
+ path6.join(__dirname2, "../../claude-code-config"),
33306
+ path6.join(__dirname2, "../claude-code-config"),
33307
+ path6.join(path6.dirname(process.argv[1]), "../claude-code-config")
32976
33308
  ];
32977
33309
  sourceDir = possiblePaths.find((p) => {
32978
33310
  try {
32979
- return import_fs_extra.default.existsSync(p);
33311
+ return import_fs_extra5.default.existsSync(p);
32980
33312
  } catch {
32981
33313
  return false;
32982
33314
  }
@@ -32996,53 +33328,68 @@ async function setupCommand(customFolder, skipInteractive) {
32996
33328
  if (options.commandValidation || options.customStatusline || options.notificationSounds) {
32997
33329
  s.start("Setting up scripts");
32998
33330
  if (useGitHub) {
32999
- const scriptsDir = path2.join(claudeDir, "scripts");
33000
- await import_fs_extra.default.ensureDir(scriptsDir);
33001
- const scriptFiles = ["validate-command.js", "statusline-ccusage.sh", "validate-command.readme.md", "statusline.readme.md"];
33331
+ const scriptsDir = path6.join(claudeDir, "scripts");
33332
+ await import_fs_extra5.default.ensureDir(scriptsDir);
33333
+ const scriptFiles = [
33334
+ "validate-command.js",
33335
+ "statusline-ccusage.sh",
33336
+ "validate-command.readme.md",
33337
+ "statusline.readme.md"
33338
+ ];
33002
33339
  for (const file of scriptFiles) {
33003
- await downloadFromGitHub(`scripts/${file}`, path2.join(scriptsDir, file));
33340
+ await downloadFromGitHub(`scripts/${file}`, path6.join(scriptsDir, file));
33004
33341
  }
33005
33342
  } else {
33006
- await import_fs_extra.default.copy(path2.join(sourceDir, "scripts"), path2.join(claudeDir, "scripts"), { overwrite: true });
33343
+ await import_fs_extra5.default.copy(path6.join(sourceDir, "scripts"), path6.join(claudeDir, "scripts"), { overwrite: true });
33007
33344
  }
33008
33345
  s.stop("Scripts installed");
33009
33346
  }
33010
33347
  if (options.aiblueprintCommands) {
33011
33348
  s.start("Setting up AIBlueprint commands");
33012
33349
  if (useGitHub) {
33013
- await downloadDirectoryFromGitHub("commands", path2.join(claudeDir, "commands"));
33350
+ await downloadDirectoryFromGitHub("commands", path6.join(claudeDir, "commands"));
33014
33351
  } else {
33015
- await import_fs_extra.default.copy(path2.join(sourceDir, "commands"), path2.join(claudeDir, "commands"), { overwrite: true });
33352
+ await import_fs_extra5.default.copy(path6.join(sourceDir, "commands"), path6.join(claudeDir, "commands"), { overwrite: true });
33016
33353
  }
33017
33354
  s.stop("Commands installed");
33018
33355
  }
33356
+ if (options.codexSymlink && options.aiblueprintCommands) {
33357
+ s.start("Setting up Codex symlink");
33358
+ await setupCodexSymlink(claudeDir, customCodexFolder, customClaudeCodeFolder);
33359
+ s.stop("Codex symlink configured");
33360
+ }
33361
+ if (options.openCodeSymlink && options.aiblueprintCommands) {
33362
+ s.start("Setting up OpenCode symlink");
33363
+ await setupOpenCodeSymlink(claudeDir, customOpenCodeFolder, customClaudeCodeFolder);
33364
+ s.stop("OpenCode symlink configured");
33365
+ }
33019
33366
  if (options.aiblueprintAgents) {
33020
33367
  s.start("Setting up AIBlueprint agents");
33021
33368
  if (useGitHub) {
33022
- await downloadDirectoryFromGitHub("agents", path2.join(claudeDir, "agents"));
33369
+ await downloadDirectoryFromGitHub("agents", path6.join(claudeDir, "agents"));
33023
33370
  } else {
33024
- await import_fs_extra.default.copy(path2.join(sourceDir, "agents"), path2.join(claudeDir, "agents"), { overwrite: true });
33371
+ await import_fs_extra5.default.copy(path6.join(sourceDir, "agents"), path6.join(claudeDir, "agents"), { overwrite: true });
33025
33372
  }
33026
33373
  s.stop("Agents installed");
33027
33374
  }
33028
33375
  if (options.outputStyles) {
33029
33376
  s.start("Setting up output styles");
33030
33377
  if (useGitHub) {
33031
- await downloadDirectoryFromGitHub("output-styles", path2.join(claudeDir, "output-styles"));
33378
+ await downloadDirectoryFromGitHub("output-styles", path6.join(claudeDir, "output-styles"));
33032
33379
  } else {
33033
- await import_fs_extra.default.copy(path2.join(sourceDir, "output-styles"), path2.join(claudeDir, "output-styles"), { overwrite: true });
33380
+ await import_fs_extra5.default.copy(path6.join(sourceDir, "output-styles"), path6.join(claudeDir, "output-styles"), { overwrite: true });
33034
33381
  }
33035
33382
  s.stop("Output styles installed");
33036
33383
  }
33037
33384
  if (options.notificationSounds) {
33038
33385
  s.start("Setting up notification sounds");
33039
33386
  if (useGitHub) {
33040
- const songDir = path2.join(claudeDir, "song");
33041
- await import_fs_extra.default.ensureDir(songDir);
33042
- await downloadFromGitHub("song/finish.mp3", path2.join(songDir, "finish.mp3"));
33043
- await downloadFromGitHub("song/need-human.mp3", path2.join(songDir, "need-human.mp3"));
33387
+ const songDir = path6.join(claudeDir, "song");
33388
+ await import_fs_extra5.default.ensureDir(songDir);
33389
+ await downloadFromGitHub("song/finish.mp3", path6.join(songDir, "finish.mp3"));
33390
+ await downloadFromGitHub("song/need-human.mp3", path6.join(songDir, "need-human.mp3"));
33044
33391
  } else {
33045
- await import_fs_extra.default.copy(path2.join(sourceDir, "song"), path2.join(claudeDir, "song"), { overwrite: true });
33392
+ await import_fs_extra5.default.copy(path6.join(sourceDir, "song"), path6.join(claudeDir, "song"), { overwrite: true });
33046
33393
  }
33047
33394
  s.stop("Notification sounds installed");
33048
33395
  }
@@ -33070,164 +33417,16 @@ Next steps:`));
33070
33417
  process.exit(1);
33071
33418
  }
33072
33419
  }
33073
- async function setupShellShortcuts() {
33074
- try {
33075
- const platform = os3.platform();
33076
- let shellConfigFile;
33077
- if (platform === "darwin") {
33078
- shellConfigFile = path2.join(os3.homedir(), ".zshenv");
33079
- } else if (platform === "linux") {
33080
- const shell = process.env.SHELL || "";
33081
- if (shell.includes("zsh")) {
33082
- shellConfigFile = path2.join(os3.homedir(), ".zshrc");
33083
- } else {
33084
- shellConfigFile = path2.join(os3.homedir(), ".bashrc");
33085
- }
33086
- } else {
33087
- console.log(source_default.yellow("Shell shortcuts are only supported on macOS and Linux"));
33088
- return;
33089
- }
33090
- const aliases = `
33091
- # AIBlueprint Claude Code aliases
33092
- alias cc="claude --dangerously-skip-permissions"
33093
- alias ccc="claude --dangerously-skip-permissions -c"
33094
- `;
33095
- const existingContent = await import_fs_extra.default.readFile(shellConfigFile, "utf-8").catch(() => "");
33096
- if (!existingContent.includes("AIBlueprint Claude Code aliases")) {
33097
- await import_fs_extra.default.appendFile(shellConfigFile, aliases);
33098
- }
33099
- } catch (error) {
33100
- console.error(source_default.red("Error setting up shell shortcuts:"), error);
33101
- throw error;
33102
- }
33103
- }
33104
- async function checkAndInstallDependencies() {
33105
- const checkCommand = (cmd) => {
33106
- try {
33107
- execSync(`which ${cmd}`, { stdio: "ignore" });
33108
- return true;
33109
- } catch {
33110
- return false;
33111
- }
33112
- };
33113
- if (!checkCommand("bun")) {
33114
- console.log(source_default.yellow(`
33115
- Installing bun...`));
33116
- try {
33117
- execSync("npm install -g bun", { stdio: "inherit" });
33118
- } catch (error) {
33119
- console.log(source_default.red(" Failed to install bun. Please install it manually: npm install -g bun"));
33120
- }
33121
- }
33122
- if (!checkCommand("ccusage")) {
33123
- console.log(source_default.yellow(`
33124
- Installing ccusage...`));
33125
- try {
33126
- execSync("npm install -g ccusage", { stdio: "inherit" });
33127
- } catch (error) {
33128
- console.log(source_default.red(" Failed to install ccusage. Please install it manually: npm install -g ccusage"));
33129
- }
33130
- }
33131
- }
33132
- async function updateSettings(options, claudeDir) {
33133
- const settingsPath = path2.join(claudeDir, "settings.json");
33134
- let settings = {};
33135
- try {
33136
- const existingSettings = await import_fs_extra.default.readFile(settingsPath, "utf-8");
33137
- settings = JSON.parse(existingSettings);
33138
- } catch {
33139
- }
33140
- if (options.customStatusline) {
33141
- if (settings.statusLine) {
33142
- const confirmAnswer = await lib_default.prompt([{
33143
- type: "confirm",
33144
- name: "replace",
33145
- message: "You already have a statusLine configuration. Replace it?"
33146
- }]);
33147
- if (!confirmAnswer.replace) {
33148
- console.log(source_default.yellow(" Keeping existing statusLine configuration"));
33149
- } else {
33150
- settings.statusLine = {
33151
- type: "command",
33152
- command: `bash ${path2.join(claudeDir, "scripts/statusline-ccusage.sh")}`,
33153
- padding: 0
33154
- };
33155
- }
33156
- } else {
33157
- settings.statusLine = {
33158
- type: "command",
33159
- command: `bash ${path2.join(claudeDir, "scripts/statusline-ccusage.sh")}`,
33160
- padding: 0
33161
- };
33162
- }
33163
- }
33164
- if (!settings.hooks) {
33165
- settings.hooks = {};
33166
- }
33167
- if (options.commandValidation) {
33168
- if (!settings.hooks.PreToolUse) {
33169
- settings.hooks.PreToolUse = [];
33170
- }
33171
- const bashHook = {
33172
- matcher: "Bash",
33173
- hooks: [
33174
- {
33175
- type: "command",
33176
- command: `bun ${path2.join(claudeDir, "scripts/validate-command.js")}`
33177
- }
33178
- ]
33179
- };
33180
- const existingBashHook = settings.hooks.PreToolUse.find((h) => h.matcher === "Bash");
33181
- if (!existingBashHook) {
33182
- settings.hooks.PreToolUse.push(bashHook);
33183
- }
33184
- }
33185
- if (options.notificationSounds) {
33186
- if (!settings.hooks.Stop) {
33187
- settings.hooks.Stop = [];
33188
- }
33189
- const stopHook = {
33190
- matcher: "",
33191
- hooks: [
33192
- {
33193
- type: "command",
33194
- command: `afplay -v 0.1 ${path2.join(claudeDir, "song/finish.mp3")}`
33195
- }
33196
- ]
33197
- };
33198
- const existingStopHook = settings.hooks.Stop.find((h) => h.hooks?.some((hook) => hook.command?.includes("finish.mp3")));
33199
- if (!existingStopHook) {
33200
- settings.hooks.Stop.push(stopHook);
33201
- }
33202
- if (!settings.hooks.Notification) {
33203
- settings.hooks.Notification = [];
33204
- }
33205
- const notificationHook = {
33206
- matcher: "",
33207
- hooks: [
33208
- {
33209
- type: "command",
33210
- command: `afplay -v 0.1 ${path2.join(claudeDir, "song/need-human.mp3")}`
33211
- }
33212
- ]
33213
- };
33214
- const existingNotificationHook = settings.hooks.Notification.find((h) => h.hooks?.some((hook) => hook.command?.includes("need-human.mp3")));
33215
- if (!existingNotificationHook) {
33216
- settings.hooks.Notification.push(notificationHook);
33217
- }
33218
- }
33219
- await import_fs_extra.default.writeJson(settingsPath, settings, { spaces: 2 });
33220
- }
33221
33420
 
33222
33421
  // src/commands/addHook.ts
33223
- var import_fs_extra3 = __toESM(require_lib4(), 1);
33224
- import path4 from "path";
33422
+ var import_fs_extra9 = __toESM(require_lib4(), 1);
33423
+ import path10 from "path";
33225
33424
  import { fileURLToPath as fileURLToPath3 } from "url";
33226
33425
  import { dirname as dirname3 } from "path";
33227
33426
 
33228
33427
  // src/utils/claude-config.ts
33229
- var import_fs_extra2 = __toESM(require_lib4(), 1);
33230
- import path3 from "path";
33428
+ var import_fs_extra6 = __toESM(require_lib4(), 1);
33429
+ import path7 from "path";
33231
33430
  import { fileURLToPath as fileURLToPath2 } from "url";
33232
33431
  import { dirname as dirname2 } from "path";
33233
33432
  var __filename3 = fileURLToPath2(import.meta.url);
@@ -33258,14 +33457,14 @@ function parseYamlFrontmatter(content) {
33258
33457
  }
33259
33458
  function getLocalConfigPaths(subDir) {
33260
33459
  return [
33261
- path3.join(__dirname3, `../claude-code-config/${subDir}`),
33262
- path3.join(__dirname3, `../../claude-code-config/${subDir}`)
33460
+ path7.join(__dirname3, `../claude-code-config/${subDir}`),
33461
+ path7.join(__dirname3, `../../claude-code-config/${subDir}`)
33263
33462
  ];
33264
33463
  }
33265
33464
  async function findLocalConfigDir(subDir) {
33266
33465
  const possiblePaths = getLocalConfigPaths(subDir);
33267
33466
  for (const testPath of possiblePaths) {
33268
- if (await import_fs_extra2.default.pathExists(testPath)) {
33467
+ if (await import_fs_extra6.default.pathExists(testPath)) {
33269
33468
  return testPath;
33270
33469
  }
33271
33470
  }
@@ -33276,13 +33475,108 @@ async function getTargetDirectory(options) {
33276
33475
  return options.folder;
33277
33476
  }
33278
33477
  const cwd = process.cwd();
33279
- const localClaudeDir = path3.join(cwd, ".claude");
33280
- const isGitRepo = await import_fs_extra2.default.pathExists(path3.join(cwd, ".git"));
33281
- const hasClaudeConfig = await import_fs_extra2.default.pathExists(localClaudeDir);
33478
+ const localClaudeDir = path7.join(cwd, ".claude");
33479
+ const isGitRepo = await import_fs_extra6.default.pathExists(path7.join(cwd, ".git"));
33480
+ const hasClaudeConfig = await import_fs_extra6.default.pathExists(localClaudeDir);
33282
33481
  if (isGitRepo || hasClaudeConfig) {
33283
33482
  return localClaudeDir;
33284
33483
  }
33285
- return path3.join(process.env.HOME || process.env.USERPROFILE || "~", ".claude");
33484
+ return path7.join(process.env.HOME || process.env.USERPROFILE || "~", ".claude");
33485
+ }
33486
+
33487
+ // src/utils/file-installer.ts
33488
+ var import_fs_extra8 = __toESM(require_lib4(), 1);
33489
+ import path9 from "path";
33490
+
33491
+ // src/utils/github.ts
33492
+ var import_fs_extra7 = __toESM(require_lib4(), 1);
33493
+ import path8 from "path";
33494
+ var GITHUB_RAW_BASE3 = "https://raw.githubusercontent.com/Melvynx/aiblueprint-cli/main/claude-code-config";
33495
+ async function downloadFromGitHub2(relativePath) {
33496
+ try {
33497
+ const url = `${GITHUB_RAW_BASE3}/${relativePath}`;
33498
+ const response = await fetch(url);
33499
+ if (!response.ok) {
33500
+ return null;
33501
+ }
33502
+ return await response.text();
33503
+ } catch (error) {
33504
+ return null;
33505
+ }
33506
+ }
33507
+ async function listFilesFromGitHub(dirPath) {
33508
+ try {
33509
+ const apiUrl = `https://api.github.com/repos/Melvynx/aiblueprint-cli/contents/claude-code-config/${dirPath}`;
33510
+ const response = await fetch(apiUrl);
33511
+ if (!response.ok) {
33512
+ return [];
33513
+ }
33514
+ const files = await response.json();
33515
+ return files.filter((file) => file.type === "file").map((file) => file.name);
33516
+ } catch (error) {
33517
+ return [];
33518
+ }
33519
+ }
33520
+ async function isGitHubAvailable() {
33521
+ try {
33522
+ const testUrl = `${GITHUB_RAW_BASE3}/commands/commit.md`;
33523
+ const testResponse = await fetch(testUrl);
33524
+ return testResponse.ok;
33525
+ } catch {
33526
+ return false;
33527
+ }
33528
+ }
33529
+ async function downloadAndWriteFile(relativePath, targetPath) {
33530
+ const content = await downloadFromGitHub2(relativePath);
33531
+ if (content) {
33532
+ await import_fs_extra7.default.ensureDir(path8.dirname(targetPath));
33533
+ await import_fs_extra7.default.writeFile(targetPath, content);
33534
+ return true;
33535
+ }
33536
+ return false;
33537
+ }
33538
+
33539
+ // src/utils/file-installer.ts
33540
+ async function installFileWithGitHubFallback(options) {
33541
+ const { sourceDir, targetPath, fileName } = options;
33542
+ await import_fs_extra8.default.ensureDir(path9.dirname(targetPath));
33543
+ const useGitHub = options.useGitHub ?? await isGitHubAvailable();
33544
+ if (useGitHub) {
33545
+ const relativePath = `${sourceDir}/${fileName}`;
33546
+ const success = await downloadAndWriteFile(relativePath, targetPath);
33547
+ if (success) {
33548
+ return;
33549
+ }
33550
+ console.log(source_default.yellow(`⚠️ GitHub download failed for ${fileName}, falling back to local files`));
33551
+ }
33552
+ const localConfigDir = await findLocalConfigDir(sourceDir);
33553
+ if (!localConfigDir) {
33554
+ throw new Error(`Neither GitHub nor local ${sourceDir} directory found`);
33555
+ }
33556
+ const localFilePath = path9.join(localConfigDir, fileName);
33557
+ if (!await import_fs_extra8.default.pathExists(localFilePath)) {
33558
+ throw new Error(`File not found: ${fileName}`);
33559
+ }
33560
+ await import_fs_extra8.default.copy(localFilePath, targetPath);
33561
+ }
33562
+ async function getFileContentWithGitHubFallback(sourceDir, fileName) {
33563
+ const useGitHub = await isGitHubAvailable();
33564
+ if (useGitHub) {
33565
+ const content = await downloadFromGitHub2(`${sourceDir}/${fileName}`);
33566
+ if (content) {
33567
+ return content;
33568
+ }
33569
+ console.log(source_default.yellow(`⚠️ GitHub download failed for ${fileName}, falling back to local files`));
33570
+ }
33571
+ const localConfigDir = await findLocalConfigDir(sourceDir);
33572
+ if (!localConfigDir) {
33573
+ throw new Error(`Neither GitHub nor local ${sourceDir} directory found`);
33574
+ }
33575
+ const localFilePath = path9.join(localConfigDir, fileName);
33576
+ if (!await import_fs_extra8.default.pathExists(localFilePath)) {
33577
+ throw new Error(`File not found: ${fileName}`);
33578
+ }
33579
+ return await import_fs_extra8.default.readFile(localFilePath, "utf-8");
33286
33580
  }
33287
33581
 
33288
33582
  // src/commands/addHook.ts
@@ -33322,10 +33616,10 @@ async function addHookCommand(hookType, options) {
33322
33616
  const s = new SimpleSpinner2;
33323
33617
  const targetDir = await getTargetDirectory(options);
33324
33618
  const claudeDir = targetDir;
33325
- const hooksDir = path4.join(claudeDir, "hooks");
33326
- const hookFilePath = path4.join(hooksDir, hook.hookFile);
33327
- const settingsPath = path4.join(claudeDir, "settings.json");
33328
- if (await import_fs_extra3.default.pathExists(hookFilePath)) {
33619
+ const hooksDir = path10.join(claudeDir, "hooks");
33620
+ const hookFilePath = path10.join(hooksDir, hook.hookFile);
33621
+ const settingsPath = path10.join(claudeDir, "settings.json");
33622
+ if (await import_fs_extra9.default.pathExists(hookFilePath)) {
33329
33623
  const overwriteAnswer = await lib_default.prompt([{
33330
33624
  type: "confirm",
33331
33625
  name: "overwrite",
@@ -33338,15 +33632,18 @@ async function addHookCommand(hookType, options) {
33338
33632
  }
33339
33633
  try {
33340
33634
  s.start("Installing hook...");
33341
- await import_fs_extra3.default.ensureDir(hooksDir);
33342
- const templatePath = path4.join(__dirname4, "../../claude-code-config/hooks", hook.hookFile);
33343
- await import_fs_extra3.default.copy(templatePath, hookFilePath);
33344
- await import_fs_extra3.default.chmod(hookFilePath, 493);
33635
+ await import_fs_extra9.default.ensureDir(hooksDir);
33636
+ await installFileWithGitHubFallback({
33637
+ sourceDir: "hooks",
33638
+ targetPath: hookFilePath,
33639
+ fileName: hook.hookFile
33640
+ });
33641
+ await import_fs_extra9.default.chmod(hookFilePath, 493);
33345
33642
  s.stop("Hook file installed");
33346
33643
  s.start("Updating settings.json...");
33347
33644
  let settings = {};
33348
33645
  try {
33349
- const existingSettings = await import_fs_extra3.default.readFile(settingsPath, "utf-8");
33646
+ const existingSettings = await import_fs_extra9.default.readFile(settingsPath, "utf-8");
33350
33647
  settings = JSON.parse(existingSettings);
33351
33648
  } catch {
33352
33649
  settings = {};
@@ -33383,7 +33680,7 @@ async function addHookCommand(hookType, options) {
33383
33680
  } else {
33384
33681
  settings.hooks[hook.event].push(newHook);
33385
33682
  }
33386
- await import_fs_extra3.default.writeFile(settingsPath, JSON.stringify(settings, null, 2));
33683
+ await import_fs_extra9.default.writeFile(settingsPath, JSON.stringify(settings, null, 2));
33387
33684
  s.stop("Settings updated");
33388
33685
  console.log(source_default.green("✨ Hook installed successfully!"));
33389
33686
  console.log(source_default.gray(`
@@ -33402,103 +33699,8 @@ The hook will run automatically when you edit TypeScript files with Claude Code.
33402
33699
  }
33403
33700
 
33404
33701
  // src/commands/addCommand.ts
33405
- var import_fs_extra6 = __toESM(require_lib4(), 1);
33406
- import path7 from "path";
33407
-
33408
- // src/utils/github.ts
33409
- var import_fs_extra4 = __toESM(require_lib4(), 1);
33410
- import path5 from "path";
33411
- var GITHUB_RAW_BASE2 = "https://raw.githubusercontent.com/Melvynx/aiblueprint-cli/main/claude-code-config";
33412
- async function downloadFromGitHub2(relativePath) {
33413
- try {
33414
- const url = `${GITHUB_RAW_BASE2}/${relativePath}`;
33415
- const response = await fetch(url);
33416
- if (!response.ok) {
33417
- return null;
33418
- }
33419
- return await response.text();
33420
- } catch (error) {
33421
- return null;
33422
- }
33423
- }
33424
- async function listFilesFromGitHub(dirPath) {
33425
- try {
33426
- const apiUrl = `https://api.github.com/repos/Melvynx/aiblueprint-cli/contents/claude-code-config/${dirPath}`;
33427
- const response = await fetch(apiUrl);
33428
- if (!response.ok) {
33429
- return [];
33430
- }
33431
- const files = await response.json();
33432
- return files.filter((file) => file.type === "file").map((file) => file.name);
33433
- } catch (error) {
33434
- return [];
33435
- }
33436
- }
33437
- async function isGitHubAvailable() {
33438
- try {
33439
- const testUrl = `${GITHUB_RAW_BASE2}/commands/commit.md`;
33440
- const testResponse = await fetch(testUrl);
33441
- return testResponse.ok;
33442
- } catch {
33443
- return false;
33444
- }
33445
- }
33446
- async function downloadAndWriteFile(relativePath, targetPath) {
33447
- const content = await downloadFromGitHub2(relativePath);
33448
- if (content) {
33449
- await import_fs_extra4.default.ensureDir(path5.dirname(targetPath));
33450
- await import_fs_extra4.default.writeFile(targetPath, content);
33451
- return true;
33452
- }
33453
- return false;
33454
- }
33455
-
33456
- // src/utils/file-installer.ts
33457
- var import_fs_extra5 = __toESM(require_lib4(), 1);
33458
- import path6 from "path";
33459
- async function installFileWithGitHubFallback(options) {
33460
- const { sourceDir, targetPath, fileName } = options;
33461
- await import_fs_extra5.default.ensureDir(path6.dirname(targetPath));
33462
- const useGitHub = options.useGitHub ?? await isGitHubAvailable();
33463
- if (useGitHub) {
33464
- const relativePath = `${sourceDir}/${fileName}`;
33465
- const success = await downloadAndWriteFile(relativePath, targetPath);
33466
- if (success) {
33467
- return;
33468
- }
33469
- console.log(source_default.yellow(`⚠️ GitHub download failed for ${fileName}, falling back to local files`));
33470
- }
33471
- const localConfigDir = await findLocalConfigDir(sourceDir);
33472
- if (!localConfigDir) {
33473
- throw new Error(`Neither GitHub nor local ${sourceDir} directory found`);
33474
- }
33475
- const localFilePath = path6.join(localConfigDir, fileName);
33476
- if (!await import_fs_extra5.default.pathExists(localFilePath)) {
33477
- throw new Error(`File not found: ${fileName}`);
33478
- }
33479
- await import_fs_extra5.default.copy(localFilePath, targetPath);
33480
- }
33481
- async function getFileContentWithGitHubFallback(sourceDir, fileName) {
33482
- const useGitHub = await isGitHubAvailable();
33483
- if (useGitHub) {
33484
- const content = await downloadFromGitHub2(`${sourceDir}/${fileName}`);
33485
- if (content) {
33486
- return content;
33487
- }
33488
- console.log(source_default.yellow(`⚠️ GitHub download failed for ${fileName}, falling back to local files`));
33489
- }
33490
- const localConfigDir = await findLocalConfigDir(sourceDir);
33491
- if (!localConfigDir) {
33492
- throw new Error(`Neither GitHub nor local ${sourceDir} directory found`);
33493
- }
33494
- const localFilePath = path6.join(localConfigDir, fileName);
33495
- if (!await import_fs_extra5.default.pathExists(localFilePath)) {
33496
- throw new Error(`File not found: ${fileName}`);
33497
- }
33498
- return await import_fs_extra5.default.readFile(localFilePath, "utf-8");
33499
- }
33500
-
33501
- // src/commands/addCommand.ts
33702
+ var import_fs_extra10 = __toESM(require_lib4(), 1);
33703
+ import path11 from "path";
33502
33704
  class SimpleSpinner3 {
33503
33705
  message = "";
33504
33706
  start(message) {
@@ -33515,12 +33717,13 @@ async function discoverAvailableCommands() {
33515
33717
  let mdFiles = [];
33516
33718
  if (useGitHub) {
33517
33719
  mdFiles = (await listFilesFromGitHub("commands")).filter((file) => file.endsWith(".md"));
33518
- } else {
33720
+ }
33721
+ if (mdFiles.length === 0) {
33519
33722
  const commandsDir = await findLocalConfigDir("commands");
33520
33723
  if (!commandsDir) {
33521
33724
  throw new Error("Commands directory not found");
33522
33725
  }
33523
- const files = await import_fs_extra6.default.readdir(commandsDir);
33726
+ const files = await import_fs_extra10.default.readdir(commandsDir);
33524
33727
  mdFiles = files.filter((file) => file.endsWith(".md"));
33525
33728
  }
33526
33729
  for (const file of mdFiles) {
@@ -33581,9 +33784,9 @@ async function addCommandCommand(commandName, options = {}) {
33581
33784
  if (options.folder) {
33582
33785
  console.log(source_default.gray(`Using custom folder: ${targetDir}`));
33583
33786
  }
33584
- const commandsDir = path7.join(targetDir, "commands");
33585
- const commandFilePath = path7.join(commandsDir, command.commandFile);
33586
- if (await import_fs_extra6.default.pathExists(commandFilePath)) {
33787
+ const commandsDir = path11.join(targetDir, "commands");
33788
+ const commandFilePath = path11.join(commandsDir, command.commandFile);
33789
+ if (await import_fs_extra10.default.pathExists(commandFilePath)) {
33587
33790
  const overwriteAnswer = await lib_default.prompt([{
33588
33791
  type: "confirm",
33589
33792
  name: "overwrite",
@@ -33623,6 +33826,172 @@ The command will be available immediately in Claude Code.`));
33623
33826
  }
33624
33827
  }
33625
33828
 
33829
+ // src/commands/symlink.ts
33830
+ var TOOLS = [
33831
+ {
33832
+ name: "Claude Code",
33833
+ value: "claude-code",
33834
+ supportsCommands: true,
33835
+ supportsAgents: true
33836
+ },
33837
+ {
33838
+ name: "Codex",
33839
+ value: "codex",
33840
+ supportsCommands: true,
33841
+ supportsAgents: false
33842
+ },
33843
+ {
33844
+ name: "OpenCode",
33845
+ value: "opencode",
33846
+ supportsCommands: true,
33847
+ supportsAgents: false
33848
+ },
33849
+ {
33850
+ name: "FactoryAI",
33851
+ value: "factoryai",
33852
+ supportsCommands: true,
33853
+ supportsAgents: true
33854
+ }
33855
+ ];
33856
+ async function symlinkCommand(params = {}) {
33857
+ try {
33858
+ console.log(source_default.blue.bold(`
33859
+ \uD83D\uDD17 Symlink Manager
33860
+ `));
33861
+ console.log(source_default.gray("Create symlinks between different CLI tool configurations"));
33862
+ const sourceAnswer = await lib_default.prompt([
33863
+ {
33864
+ type: "list",
33865
+ name: "source",
33866
+ message: "Select source tool:",
33867
+ choices: TOOLS.map((tool) => ({
33868
+ name: tool.name,
33869
+ value: tool.value
33870
+ }))
33871
+ }
33872
+ ]);
33873
+ const sourceTool = sourceAnswer.source;
33874
+ const sourceConfig = TOOLS.find((t) => t.value === sourceTool);
33875
+ const contentTypeChoices = [];
33876
+ if (sourceConfig.supportsCommands) {
33877
+ contentTypeChoices.push({ name: "Commands only", value: "commands" });
33878
+ }
33879
+ if (sourceConfig.supportsAgents) {
33880
+ contentTypeChoices.push({ name: "Agents only", value: "agents" });
33881
+ }
33882
+ if (sourceConfig.supportsCommands && sourceConfig.supportsAgents) {
33883
+ contentTypeChoices.push({ name: "Both", value: "both" });
33884
+ }
33885
+ if (contentTypeChoices.length === 0) {
33886
+ console.log(source_default.red(`
33887
+ ❌ Error: ${sourceConfig.name} doesn't support any syncable content`));
33888
+ process.exit(1);
33889
+ }
33890
+ const contentAnswer = await lib_default.prompt([
33891
+ {
33892
+ type: "list",
33893
+ name: "contentType",
33894
+ message: "What would you like to sync?",
33895
+ choices: contentTypeChoices
33896
+ }
33897
+ ]);
33898
+ const syncType = contentAnswer.contentType;
33899
+ const syncCommands = syncType === "commands" || syncType === "both";
33900
+ const syncAgents = syncType === "agents" || syncType === "both";
33901
+ const destinationChoices = [];
33902
+ for (const tool of TOOLS) {
33903
+ if (tool.value === sourceTool)
33904
+ continue;
33905
+ if (syncCommands && tool.supportsCommands) {
33906
+ destinationChoices.push({
33907
+ name: syncAgents ? `${tool.name} (commands)` : tool.name,
33908
+ value: `${tool.value}-commands`,
33909
+ tool: tool.value,
33910
+ contentType: "commands"
33911
+ });
33912
+ }
33913
+ if (syncAgents && tool.supportsAgents) {
33914
+ destinationChoices.push({
33915
+ name: syncCommands ? `${tool.name} (agents)` : tool.name,
33916
+ value: `${tool.value}-agents`,
33917
+ tool: tool.value,
33918
+ contentType: "agents"
33919
+ });
33920
+ }
33921
+ }
33922
+ if (destinationChoices.length === 0) {
33923
+ console.log(source_default.yellow(`
33924
+ ⚠️ No compatible destination tools found for the selected sync type`));
33925
+ process.exit(0);
33926
+ }
33927
+ const destinationAnswer = await lib_default.prompt([
33928
+ {
33929
+ type: "checkbox",
33930
+ name: "destinations",
33931
+ message: "Select destination tools (multi-select):",
33932
+ choices: destinationChoices.map((choice) => ({
33933
+ name: choice.name,
33934
+ value: choice.value,
33935
+ checked: false
33936
+ })),
33937
+ validate: (answer) => {
33938
+ if (answer.length === 0) {
33939
+ return "Please select at least one destination";
33940
+ }
33941
+ return true;
33942
+ }
33943
+ }
33944
+ ]);
33945
+ const selectedDestinations = destinationAnswer.destinations;
33946
+ const customFolders = {
33947
+ "claude-code": params.claudeCodeFolder,
33948
+ codex: params.codexFolder,
33949
+ opencode: params.openCodeFolder,
33950
+ factoryai: params.factoryAiFolder
33951
+ };
33952
+ const sourcePaths = await getToolPaths(sourceTool, customFolders[sourceTool]);
33953
+ console.log(source_default.blue(`
33954
+ \uD83D\uDCE6 Creating symlinks...
33955
+ `));
33956
+ let successCount = 0;
33957
+ let skipCount = 0;
33958
+ for (const destValue of selectedDestinations) {
33959
+ const destChoice = destinationChoices.find((c) => c.value === destValue);
33960
+ const destPaths = await getToolPaths(destChoice.tool, customFolders[destChoice.tool]);
33961
+ let sourcePath;
33962
+ let targetPath;
33963
+ if (destChoice.contentType === "commands") {
33964
+ sourcePath = sourcePaths.commandsPath;
33965
+ targetPath = destPaths.commandsPath;
33966
+ } else {
33967
+ sourcePath = sourcePaths.agentsPath;
33968
+ targetPath = destPaths.agentsPath;
33969
+ }
33970
+ const toolName = TOOLS.find((t) => t.value === destChoice.tool)?.name || destChoice.tool;
33971
+ const contentLabel = destChoice.contentType === "commands" ? "commands" : "agents";
33972
+ try {
33973
+ const success = await createSymlink(sourcePath, targetPath, {
33974
+ skipMessage: source_default.yellow(` ⚠️ ${toolName} ${contentLabel} path already exists and is not a symlink. Skipping...`)
33975
+ });
33976
+ if (success) {
33977
+ console.log(source_default.green(` ✓ ${toolName} (${contentLabel}) symlink created`));
33978
+ successCount++;
33979
+ } else {
33980
+ skipCount++;
33981
+ }
33982
+ } catch (error) {
33983
+ console.error(source_default.red(` ✗ Failed to create ${toolName} (${contentLabel}) symlink:`), error);
33984
+ }
33985
+ }
33986
+ console.log(source_default.green(`
33987
+ ✨ Symlink setup complete! ${successCount} created, ${skipCount} skipped`));
33988
+ } catch (error) {
33989
+ console.error(source_default.red(`
33990
+ ❌ Symlink setup failed:`), error);
33991
+ process.exit(1);
33992
+ }
33993
+ }
33994
+
33626
33995
  // src/cli.ts
33627
33996
  import { readFileSync as readFileSync2 } from "fs";
33628
33997
  import { dirname as dirname4, join } from "path";
@@ -33631,10 +34000,15 @@ var __dirname5 = dirname4(fileURLToPath4(import.meta.url));
33631
34000
  var packageJson = JSON.parse(readFileSync2(join(__dirname5, "../package.json"), "utf8"));
33632
34001
  var program2 = new Command;
33633
34002
  program2.name("aiblueprint").description("AIBlueprint CLI for setting up Claude Code configurations").version(packageJson.version);
33634
- var claudeCodeCmd = program2.command("claude-code").description("Claude Code configuration commands").option("-f, --folder <path>", "Specify custom folder path (default: ~/.claude)").option("-s, --skip", "Skip interactive prompts and install all features");
34003
+ var claudeCodeCmd = program2.command("claude-code").description("Claude Code configuration commands").option("-f, --folder <path>", "Specify custom Claude Code folder path (default: ~/.claude) - alias for --claudeCodeFolder").option("--claudeCodeFolder <path>", "Specify custom Claude Code folder path (default: ~/.claude)").option("--codexFolder <path>", "Specify custom Codex folder path (default: ~/.codex)").option("--openCodeFolder <path>", "Specify custom OpenCode folder path (default: ~/.config/opencode)").option("--factoryAiFolder <path>", "Specify custom FactoryAI folder path (default: ~/.factory)").option("-s, --skip", "Skip interactive prompts and install all features");
33635
34004
  claudeCodeCmd.command("setup").description("Setup Claude Code configuration with AIBlueprint defaults").action((options, command) => {
33636
34005
  const parentOptions = command.parent.opts();
33637
- setupCommand(parentOptions.folder, parentOptions.skip);
34006
+ setupCommand({
34007
+ claudeCodeFolder: parentOptions.claudeCodeFolder || parentOptions.folder,
34008
+ codexFolder: parentOptions.codexFolder,
34009
+ openCodeFolder: parentOptions.openCodeFolder,
34010
+ skipInteractive: parentOptions.skip
34011
+ });
33638
34012
  });
33639
34013
  var addCmd = claudeCodeCmd.command("add").description(`Add components to your Claude Code configuration
33640
34014
  ` + `Examples:
@@ -33643,11 +34017,22 @@ var addCmd = claudeCodeCmd.command("add").description(`Add components to your Cl
33643
34017
  ` + " aiblueprint claude-code add commands commit");
33644
34018
  addCmd.command("hook <type>").description("Add a hook to your Claude Code configuration. Available types: post-edit-typescript").action((type, options, command) => {
33645
34019
  const parentOptions = command.parent.parent.opts();
33646
- addHookCommand(type, { folder: parentOptions.folder });
34020
+ const claudeCodeFolder = parentOptions.claudeCodeFolder || parentOptions.folder;
34021
+ addHookCommand(type, { folder: claudeCodeFolder });
33647
34022
  });
33648
34023
  addCmd.command("commands [command-name]").description("Install a Claude Code command or list all available commands (use without argument to list)").action((commandName, options, command) => {
33649
34024
  const parentOptions = command.parent.parent.opts();
33650
- addCommandCommand(commandName, { folder: parentOptions.folder });
34025
+ const claudeCodeFolder = parentOptions.claudeCodeFolder || parentOptions.folder;
34026
+ addCommandCommand(commandName, { folder: claudeCodeFolder });
34027
+ });
34028
+ claudeCodeCmd.command("symlink").description("Create symlinks between different CLI tools (Claude Code, Codex, OpenCode, FactoryAI)").action((options, command) => {
34029
+ const parentOptions = command.parent.opts();
34030
+ symlinkCommand({
34031
+ claudeCodeFolder: parentOptions.claudeCodeFolder || parentOptions.folder,
34032
+ codexFolder: parentOptions.codexFolder,
34033
+ openCodeFolder: parentOptions.openCodeFolder,
34034
+ factoryAiFolder: parentOptions.factoryAiFolder
34035
+ });
33651
34036
  });
33652
34037
  program2.parse(process.argv);
33653
34038
  if (!process.argv.slice(2).length) {