aiblueprint-cli 1.1.4 → 1.1.8

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.
Files changed (46) hide show
  1. package/README.md +496 -41
  2. package/claude-code-config/agents/action.md +36 -0
  3. package/claude-code-config/agents/explore-codebase.md +6 -0
  4. package/claude-code-config/agents/explore-docs.md +88 -0
  5. package/claude-code-config/agents/fix-grammar.md +49 -0
  6. package/claude-code-config/agents/snipper.md +2 -0
  7. package/claude-code-config/agents/websearch.md +1 -0
  8. package/claude-code-config/commands/commit.md +1 -1
  9. package/claude-code-config/commands/debug.md +91 -0
  10. package/claude-code-config/commands/epct/code.md +171 -0
  11. package/claude-code-config/commands/epct/deploy.md +116 -0
  12. package/claude-code-config/commands/epct/explore.md +97 -0
  13. package/claude-code-config/commands/epct/plan.md +132 -0
  14. package/claude-code-config/commands/epct/tasks.md +206 -0
  15. package/claude-code-config/commands/explore.md +45 -0
  16. package/claude-code-config/commands/melvynx-plugin.md +1 -0
  17. package/claude-code-config/commands/oneshot.md +57 -0
  18. package/claude-code-config/hooks/hooks.json +15 -0
  19. package/claude-code-config/scripts/statusline/CLAUDE.md +178 -0
  20. package/claude-code-config/scripts/statusline/README.md +105 -0
  21. package/claude-code-config/scripts/statusline/biome.json +34 -0
  22. package/claude-code-config/scripts/statusline/bun.lockb +0 -0
  23. package/claude-code-config/scripts/statusline/data/.gitignore +5 -0
  24. package/claude-code-config/scripts/statusline/fixtures/test-input.json +25 -0
  25. package/claude-code-config/scripts/statusline/package.json +21 -0
  26. package/claude-code-config/scripts/statusline/src/commands/CLAUDE.md +3 -0
  27. package/claude-code-config/scripts/statusline/src/commands/spend-month.ts +60 -0
  28. package/claude-code-config/scripts/statusline/src/commands/spend-today.ts +42 -0
  29. package/claude-code-config/scripts/statusline/src/index.ts +141 -0
  30. package/claude-code-config/scripts/statusline/src/lib/context.ts +103 -0
  31. package/claude-code-config/scripts/statusline/src/lib/formatters.ts +218 -0
  32. package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
  33. package/claude-code-config/scripts/statusline/src/lib/spend.ts +119 -0
  34. package/claude-code-config/scripts/statusline/src/lib/types.ts +25 -0
  35. package/claude-code-config/scripts/statusline/src/lib/usage-limits.ts +147 -0
  36. package/claude-code-config/scripts/statusline/statusline.config.ts +122 -0
  37. package/claude-code-config/scripts/statusline/test.ts +20 -0
  38. package/claude-code-config/scripts/statusline/tsconfig.json +27 -0
  39. package/dist/cli.js +722 -256
  40. package/package.json +1 -2
  41. package/claude-code-config/output-styles/assistant.md +0 -15
  42. package/claude-code-config/output-styles/honnest.md +0 -9
  43. package/claude-code-config/output-styles/senior-dev.md +0 -14
  44. package/claude-code-config/scripts/statusline-ccusage.sh +0 -188
  45. package/claude-code-config/scripts/statusline.readme.md +0 -194
  46. /package/claude-code-config/{hooks → scripts}/hook-post-file.ts +0 -0
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 path7 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,311 @@ 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
+ import path4 from "path";
33006
+ async function checkAndInstallDependencies() {
33007
+ const checkCommand = (cmd) => {
33008
+ try {
33009
+ execSync(`which ${cmd}`, { stdio: "ignore" });
33010
+ return true;
33011
+ } catch {
33012
+ return false;
33013
+ }
33014
+ };
33015
+ if (!checkCommand("bun")) {
33016
+ console.log(source_default.yellow(`
33017
+ Installing bun...`));
33018
+ try {
33019
+ execSync("npm install -g bun", { stdio: "inherit" });
33020
+ } catch (error) {
33021
+ console.log(source_default.red(" Failed to install bun. Please install it manually: npm install -g bun"));
33022
+ }
33023
+ }
33024
+ if (!checkCommand("ccusage")) {
33025
+ console.log(source_default.yellow(`
33026
+ Installing ccusage...`));
33027
+ try {
33028
+ execSync("npm install -g ccusage", { stdio: "inherit" });
33029
+ } catch (error) {
33030
+ console.log(source_default.red(" Failed to install ccusage. Please install it manually: npm install -g ccusage"));
33031
+ }
33032
+ }
33033
+ }
33034
+ async function installStatuslineDependencies(claudeDir) {
33035
+ const statuslineDir = path4.join(claudeDir, "scripts/statusline");
33036
+ console.log(source_default.yellow(`
33037
+ Installing statusline dependencies...`));
33038
+ try {
33039
+ execSync("bun install", {
33040
+ cwd: statuslineDir,
33041
+ stdio: "inherit"
33042
+ });
33043
+ console.log(source_default.green(" ✓ Statusline dependencies installed"));
33044
+ } catch (error) {
33045
+ console.log(source_default.red(" Failed to install statusline dependencies. Please run 'bun install' manually in ~/.claude/scripts/statusline"));
33046
+ }
33047
+ }
33048
+
33049
+ // src/commands/setup/settings.ts
33050
+ var import_fs_extra3 = __toESM(require_lib4(), 1);
33051
+ import path5 from "path";
33052
+ async function updateSettings(options, claudeDir) {
33053
+ const settingsPath = path5.join(claudeDir, "settings.json");
33054
+ let settings = {};
33055
+ try {
33056
+ const existingSettings = await import_fs_extra3.default.readFile(settingsPath, "utf-8");
33057
+ settings = JSON.parse(existingSettings);
33058
+ } catch {
33059
+ }
33060
+ if (options.customStatusline) {
33061
+ if (settings.statusLine) {
33062
+ const confirmAnswer = await lib_default.prompt([
33063
+ {
33064
+ type: "confirm",
33065
+ name: "replace",
33066
+ message: "You already have a statusLine configuration. Replace it?"
33067
+ }
33068
+ ]);
33069
+ if (!confirmAnswer.replace) {
33070
+ console.log(source_default.yellow(" Keeping existing statusLine configuration"));
33071
+ } else {
33072
+ settings.statusLine = {
33073
+ type: "command",
33074
+ command: `bun ${path5.join(claudeDir, "scripts/statusline/src/index.ts")}`,
33075
+ padding: 0
33076
+ };
33077
+ }
33078
+ } else {
33079
+ settings.statusLine = {
33080
+ type: "command",
33081
+ command: `bun ${path5.join(claudeDir, "scripts/statusline/src/index.ts")}`,
33082
+ padding: 0
33083
+ };
33084
+ }
33085
+ }
33086
+ if (!settings.hooks) {
33087
+ settings.hooks = {};
33088
+ }
33089
+ if (options.commandValidation) {
33090
+ if (!settings.hooks.PreToolUse) {
33091
+ settings.hooks.PreToolUse = [];
33092
+ }
33093
+ const bashHook = {
33094
+ matcher: "Bash",
33095
+ hooks: [
33096
+ {
33097
+ type: "command",
33098
+ command: `bun ${path5.join(claudeDir, "scripts/validate-command.js")}`
33099
+ }
33100
+ ]
33101
+ };
33102
+ const existingBashHook = settings.hooks.PreToolUse.find((h) => h.matcher === "Bash");
33103
+ if (!existingBashHook) {
33104
+ settings.hooks.PreToolUse.push(bashHook);
33105
+ }
33106
+ }
33107
+ if (options.notificationSounds) {
33108
+ if (!settings.hooks.Stop) {
33109
+ settings.hooks.Stop = [];
33110
+ }
33111
+ const stopHook = {
33112
+ matcher: "",
33113
+ hooks: [
33114
+ {
33115
+ type: "command",
33116
+ command: `afplay -v 0.1 ${path5.join(claudeDir, "song/finish.mp3")}`
33117
+ }
33118
+ ]
33119
+ };
33120
+ const existingStopHook = settings.hooks.Stop.find((h) => h.hooks?.some((hook) => hook.command?.includes("finish.mp3")));
33121
+ if (!existingStopHook) {
33122
+ settings.hooks.Stop.push(stopHook);
33123
+ }
33124
+ if (!settings.hooks.Notification) {
33125
+ settings.hooks.Notification = [];
33126
+ }
33127
+ const notificationHook = {
33128
+ matcher: "",
33129
+ hooks: [
33130
+ {
33131
+ type: "command",
33132
+ command: `afplay -v 0.1 ${path5.join(claudeDir, "song/need-human.mp3")}`
33133
+ }
33134
+ ]
33135
+ };
33136
+ const existingNotificationHook = settings.hooks.Notification.find((h) => h.hooks?.some((hook) => hook.command?.includes("need-human.mp3")));
33137
+ if (!existingNotificationHook) {
33138
+ settings.hooks.Notification.push(notificationHook);
33139
+ }
33140
+ }
33141
+ if (options.postEditTypeScript) {
33142
+ if (!settings.hooks.PostToolUse) {
33143
+ settings.hooks.PostToolUse = [];
33144
+ }
33145
+ const postEditHook = {
33146
+ matcher: "Edit|Write|MultiEdit",
33147
+ hooks: [
33148
+ {
33149
+ type: "command",
33150
+ command: `bun ${path5.join(claudeDir, "scripts/hook-post-file.ts")}`
33151
+ }
33152
+ ]
33153
+ };
33154
+ const existingPostEditHook = settings.hooks.PostToolUse.find((h) => h.matcher === "Edit|Write|MultiEdit" && h.hooks?.some((hook) => hook.command?.includes("hook-post-file.ts")));
33155
+ if (!existingPostEditHook) {
33156
+ settings.hooks.PostToolUse.push(postEditHook);
33157
+ }
33158
+ }
33159
+ await import_fs_extra3.default.writeJson(settingsPath, settings, { spaces: 2 });
33160
+ }
33161
+
33162
+ // src/commands/setup/utils.ts
33163
+ var import_fs_extra4 = __toESM(require_lib4(), 1);
33164
+ import path6 from "path";
32863
33165
 
32864
33166
  class SimpleSpinner {
32865
33167
  message = "";
@@ -32880,8 +33182,8 @@ async function downloadFromGitHub(relativePath, targetPath) {
32880
33182
  return false;
32881
33183
  }
32882
33184
  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));
33185
+ await import_fs_extra4.default.ensureDir(path6.dirname(targetPath));
33186
+ await import_fs_extra4.default.writeFile(targetPath, Buffer.from(content));
32885
33187
  return true;
32886
33188
  } catch (error) {
32887
33189
  return false;
@@ -32898,10 +33200,10 @@ async function downloadDirectoryFromGitHub(dirPath, targetDir) {
32898
33200
  if (!Array.isArray(files)) {
32899
33201
  return false;
32900
33202
  }
32901
- await import_fs_extra.default.ensureDir(targetDir);
33203
+ await import_fs_extra4.default.ensureDir(targetDir);
32902
33204
  for (const file of files) {
32903
33205
  const relativePath = `${dirPath}/${file.name}`;
32904
- const targetPath = path2.join(targetDir, file.name);
33206
+ const targetPath = path6.join(targetDir, file.name);
32905
33207
  if (file.type === "file") {
32906
33208
  await downloadFromGitHub(relativePath, targetPath);
32907
33209
  } else if (file.type === "dir") {
@@ -32913,7 +33215,18 @@ async function downloadDirectoryFromGitHub(dirPath, targetDir) {
32913
33215
  return false;
32914
33216
  }
32915
33217
  }
32916
- async function setupCommand(customFolder, skipInteractive) {
33218
+
33219
+ // src/commands/setup.ts
33220
+ var __filename2 = fileURLToPath(import.meta.url);
33221
+ var __dirname2 = dirname(__filename2);
33222
+ var GITHUB_RAW_BASE2 = "https://raw.githubusercontent.com/Melvynx/aiblueprint-cli/main/claude-code-config";
33223
+ async function setupCommand(params = {}) {
33224
+ const {
33225
+ claudeCodeFolder: customClaudeCodeFolder,
33226
+ codexFolder: customCodexFolder,
33227
+ openCodeFolder: customOpenCodeFolder,
33228
+ skipInteractive
33229
+ } = params;
32917
33230
  try {
32918
33231
  console.log(source_default.blue.bold(`
32919
33232
  \uD83D\uDE80 AIBlueprint Claude Code Setup
@@ -32921,23 +33234,72 @@ async function setupCommand(customFolder, skipInteractive) {
32921
33234
  console.log(source_default.bgBlue(" Setting up your Claude Code environment "));
32922
33235
  let features;
32923
33236
  if (skipInteractive) {
32924
- features = ["shellShortcuts", "commandValidation", "customStatusline", "aiblueprintCommands", "aiblueprintAgents", "outputStyles", "notificationSounds"];
33237
+ features = [
33238
+ "shellShortcuts",
33239
+ "commandValidation",
33240
+ "customStatusline",
33241
+ "aiblueprintCommands",
33242
+ "aiblueprintAgents",
33243
+ "notificationSounds",
33244
+ "codexSymlink",
33245
+ "openCodeSymlink"
33246
+ ];
32925
33247
  console.log(source_default.green("✓ Installing all features (--skip mode)"));
32926
33248
  } 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
- }]);
33249
+ const answers = await lib_default.prompt([
33250
+ {
33251
+ type: "checkbox",
33252
+ name: "features",
33253
+ message: "Which features would you like to install?",
33254
+ choices: [
33255
+ {
33256
+ value: "shellShortcuts",
33257
+ name: "Shell shortcuts (cc, ccc aliases) - Quick access to Claude Code",
33258
+ checked: true
33259
+ },
33260
+ {
33261
+ value: "commandValidation",
33262
+ name: "Command validation - Security hook for bash commands",
33263
+ checked: true
33264
+ },
33265
+ {
33266
+ value: "customStatusline",
33267
+ name: "Custom statusline - Shows git, costs, tokens info",
33268
+ checked: true
33269
+ },
33270
+ {
33271
+ value: "aiblueprintCommands",
33272
+ name: "AIBlueprint commands - Pre-configured command templates",
33273
+ checked: true
33274
+ },
33275
+ {
33276
+ value: "aiblueprintAgents",
33277
+ name: "AIBlueprint agents - Specialized AI agents",
33278
+ checked: true
33279
+ },
33280
+ {
33281
+ value: "notificationSounds",
33282
+ name: "Notification sounds - Audio alerts for events",
33283
+ checked: true
33284
+ },
33285
+ {
33286
+ value: "postEditTypeScript",
33287
+ name: "Post-edit TypeScript hook - Auto-format and lint TypeScript files",
33288
+ checked: false
33289
+ },
33290
+ {
33291
+ value: "codexSymlink",
33292
+ name: "Codex symlink - Link commands to ~/.codex/prompts",
33293
+ checked: false
33294
+ },
33295
+ {
33296
+ value: "openCodeSymlink",
33297
+ name: "OpenCode symlink - Link commands to ~/.config/opencode/command",
33298
+ checked: false
33299
+ }
33300
+ ]
33301
+ }
33302
+ ]);
32941
33303
  features = answers.features;
32942
33304
  if (!features || features.length === 0) {
32943
33305
  console.log(source_default.yellow("Setup cancelled - no features selected"));
@@ -32950,16 +33312,18 @@ async function setupCommand(customFolder, skipInteractive) {
32950
33312
  customStatusline: features.includes("customStatusline"),
32951
33313
  aiblueprintCommands: features.includes("aiblueprintCommands"),
32952
33314
  aiblueprintAgents: features.includes("aiblueprintAgents"),
32953
- outputStyles: features.includes("outputStyles"),
32954
- notificationSounds: features.includes("notificationSounds")
33315
+ notificationSounds: features.includes("notificationSounds"),
33316
+ postEditTypeScript: features.includes("postEditTypeScript"),
33317
+ codexSymlink: features.includes("codexSymlink"),
33318
+ openCodeSymlink: features.includes("openCodeSymlink")
32955
33319
  };
32956
33320
  const s = new SimpleSpinner;
32957
- const claudeDir = customFolder ? path2.resolve(customFolder) : path2.join(os3.homedir(), ".claude");
33321
+ const claudeDir = customClaudeCodeFolder ? path7.resolve(customClaudeCodeFolder) : path7.join(os5.homedir(), ".claude");
32958
33322
  console.log(source_default.gray(`Installing to: ${claudeDir}`));
32959
- await import_fs_extra.default.ensureDir(claudeDir);
33323
+ await import_fs_extra5.default.ensureDir(claudeDir);
32960
33324
  let useGitHub = true;
32961
33325
  let sourceDir;
32962
- const testUrl = `${GITHUB_RAW_BASE}/scripts/validate-command.js`;
33326
+ const testUrl = `${GITHUB_RAW_BASE2}/scripts/validate-command.js`;
32963
33327
  try {
32964
33328
  const testResponse = await fetch(testUrl);
32965
33329
  useGitHub = testResponse.ok;
@@ -32969,14 +33333,14 @@ async function setupCommand(customFolder, skipInteractive) {
32969
33333
  if (!useGitHub) {
32970
33334
  const currentDir = process.cwd();
32971
33335
  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")
33336
+ path7.join(currentDir, "claude-code-config"),
33337
+ path7.join(__dirname2, "../../claude-code-config"),
33338
+ path7.join(__dirname2, "../claude-code-config"),
33339
+ path7.join(path7.dirname(process.argv[1]), "../claude-code-config")
32976
33340
  ];
32977
33341
  sourceDir = possiblePaths.find((p) => {
32978
33342
  try {
32979
- return import_fs_extra.default.existsSync(p);
33343
+ return import_fs_extra5.default.existsSync(p);
32980
33344
  } catch {
32981
33345
  return false;
32982
33346
  }
@@ -32993,56 +33357,66 @@ async function setupCommand(customFolder, skipInteractive) {
32993
33357
  await setupShellShortcuts();
32994
33358
  s.stop("Shell shortcuts configured");
32995
33359
  }
32996
- if (options.commandValidation || options.customStatusline || options.notificationSounds) {
33360
+ if (options.commandValidation || options.customStatusline || options.notificationSounds || options.postEditTypeScript) {
32997
33361
  s.start("Setting up scripts");
32998
33362
  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"];
33363
+ const scriptsDir = path7.join(claudeDir, "scripts");
33364
+ await import_fs_extra5.default.ensureDir(scriptsDir);
33365
+ const scriptFiles = [
33366
+ "validate-command.js",
33367
+ "validate-command.readme.md"
33368
+ ];
33369
+ if (options.postEditTypeScript) {
33370
+ scriptFiles.push("hook-post-file.ts");
33371
+ }
33002
33372
  for (const file of scriptFiles) {
33003
- await downloadFromGitHub(`scripts/${file}`, path2.join(scriptsDir, file));
33373
+ await downloadFromGitHub(`scripts/${file}`, path7.join(scriptsDir, file));
33374
+ }
33375
+ if (options.customStatusline) {
33376
+ await downloadDirectoryFromGitHub("scripts/statusline", path7.join(scriptsDir, "statusline"));
33004
33377
  }
33005
33378
  } else {
33006
- await import_fs_extra.default.copy(path2.join(sourceDir, "scripts"), path2.join(claudeDir, "scripts"), { overwrite: true });
33379
+ await import_fs_extra5.default.copy(path7.join(sourceDir, "scripts"), path7.join(claudeDir, "scripts"), { overwrite: true });
33007
33380
  }
33008
33381
  s.stop("Scripts installed");
33009
33382
  }
33010
33383
  if (options.aiblueprintCommands) {
33011
33384
  s.start("Setting up AIBlueprint commands");
33012
33385
  if (useGitHub) {
33013
- await downloadDirectoryFromGitHub("commands", path2.join(claudeDir, "commands"));
33386
+ await downloadDirectoryFromGitHub("commands", path7.join(claudeDir, "commands"));
33014
33387
  } else {
33015
- await import_fs_extra.default.copy(path2.join(sourceDir, "commands"), path2.join(claudeDir, "commands"), { overwrite: true });
33388
+ await import_fs_extra5.default.copy(path7.join(sourceDir, "commands"), path7.join(claudeDir, "commands"), { overwrite: true });
33016
33389
  }
33017
33390
  s.stop("Commands installed");
33018
33391
  }
33392
+ if (options.codexSymlink && options.aiblueprintCommands) {
33393
+ s.start("Setting up Codex symlink");
33394
+ await setupCodexSymlink(claudeDir, customCodexFolder, customClaudeCodeFolder);
33395
+ s.stop("Codex symlink configured");
33396
+ }
33397
+ if (options.openCodeSymlink && options.aiblueprintCommands) {
33398
+ s.start("Setting up OpenCode symlink");
33399
+ await setupOpenCodeSymlink(claudeDir, customOpenCodeFolder, customClaudeCodeFolder);
33400
+ s.stop("OpenCode symlink configured");
33401
+ }
33019
33402
  if (options.aiblueprintAgents) {
33020
33403
  s.start("Setting up AIBlueprint agents");
33021
33404
  if (useGitHub) {
33022
- await downloadDirectoryFromGitHub("agents", path2.join(claudeDir, "agents"));
33405
+ await downloadDirectoryFromGitHub("agents", path7.join(claudeDir, "agents"));
33023
33406
  } else {
33024
- await import_fs_extra.default.copy(path2.join(sourceDir, "agents"), path2.join(claudeDir, "agents"), { overwrite: true });
33407
+ await import_fs_extra5.default.copy(path7.join(sourceDir, "agents"), path7.join(claudeDir, "agents"), { overwrite: true });
33025
33408
  }
33026
33409
  s.stop("Agents installed");
33027
33410
  }
33028
- if (options.outputStyles) {
33029
- s.start("Setting up output styles");
33030
- if (useGitHub) {
33031
- await downloadDirectoryFromGitHub("output-styles", path2.join(claudeDir, "output-styles"));
33032
- } else {
33033
- await import_fs_extra.default.copy(path2.join(sourceDir, "output-styles"), path2.join(claudeDir, "output-styles"), { overwrite: true });
33034
- }
33035
- s.stop("Output styles installed");
33036
- }
33037
33411
  if (options.notificationSounds) {
33038
33412
  s.start("Setting up notification sounds");
33039
33413
  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"));
33414
+ const songDir = path7.join(claudeDir, "song");
33415
+ await import_fs_extra5.default.ensureDir(songDir);
33416
+ await downloadFromGitHub("song/finish.mp3", path7.join(songDir, "finish.mp3"));
33417
+ await downloadFromGitHub("song/need-human.mp3", path7.join(songDir, "need-human.mp3"));
33044
33418
  } else {
33045
- await import_fs_extra.default.copy(path2.join(sourceDir, "song"), path2.join(claudeDir, "song"), { overwrite: true });
33419
+ await import_fs_extra5.default.copy(path7.join(sourceDir, "song"), path7.join(claudeDir, "song"), { overwrite: true });
33046
33420
  }
33047
33421
  s.stop("Notification sounds installed");
33048
33422
  }
@@ -33050,6 +33424,9 @@ async function setupCommand(customFolder, skipInteractive) {
33050
33424
  s.start("Checking dependencies");
33051
33425
  await checkAndInstallDependencies();
33052
33426
  s.stop("Dependencies checked");
33427
+ s.start("Installing statusline dependencies");
33428
+ await installStatuslineDependencies(claudeDir);
33429
+ s.stop("Statusline dependencies installed");
33053
33430
  }
33054
33431
  s.start("Updating settings.json");
33055
33432
  await updateSettings(options, claudeDir);
@@ -33070,164 +33447,16 @@ Next steps:`));
33070
33447
  process.exit(1);
33071
33448
  }
33072
33449
  }
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
33450
 
33222
33451
  // src/commands/addHook.ts
33223
- var import_fs_extra5 = __toESM(require_lib4(), 1);
33224
- import path6 from "path";
33452
+ var import_fs_extra9 = __toESM(require_lib4(), 1);
33453
+ import path11 from "path";
33225
33454
  import { fileURLToPath as fileURLToPath3 } from "url";
33226
33455
  import { dirname as dirname3 } from "path";
33227
33456
 
33228
33457
  // src/utils/claude-config.ts
33229
- var import_fs_extra2 = __toESM(require_lib4(), 1);
33230
- import path3 from "path";
33458
+ var import_fs_extra6 = __toESM(require_lib4(), 1);
33459
+ import path8 from "path";
33231
33460
  import { fileURLToPath as fileURLToPath2 } from "url";
33232
33461
  import { dirname as dirname2 } from "path";
33233
33462
  var __filename3 = fileURLToPath2(import.meta.url);
@@ -33258,14 +33487,14 @@ function parseYamlFrontmatter(content) {
33258
33487
  }
33259
33488
  function getLocalConfigPaths(subDir) {
33260
33489
  return [
33261
- path3.join(__dirname3, `../claude-code-config/${subDir}`),
33262
- path3.join(__dirname3, `../../claude-code-config/${subDir}`)
33490
+ path8.join(__dirname3, `../claude-code-config/${subDir}`),
33491
+ path8.join(__dirname3, `../../claude-code-config/${subDir}`)
33263
33492
  ];
33264
33493
  }
33265
33494
  async function findLocalConfigDir(subDir) {
33266
33495
  const possiblePaths = getLocalConfigPaths(subDir);
33267
33496
  for (const testPath of possiblePaths) {
33268
- if (await import_fs_extra2.default.pathExists(testPath)) {
33497
+ if (await import_fs_extra6.default.pathExists(testPath)) {
33269
33498
  return testPath;
33270
33499
  }
33271
33500
  }
@@ -33276,26 +33505,26 @@ async function getTargetDirectory(options) {
33276
33505
  return options.folder;
33277
33506
  }
33278
33507
  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);
33508
+ const localClaudeDir = path8.join(cwd, ".claude");
33509
+ const isGitRepo = await import_fs_extra6.default.pathExists(path8.join(cwd, ".git"));
33510
+ const hasClaudeConfig = await import_fs_extra6.default.pathExists(localClaudeDir);
33282
33511
  if (isGitRepo || hasClaudeConfig) {
33283
33512
  return localClaudeDir;
33284
33513
  }
33285
- return path3.join(process.env.HOME || process.env.USERPROFILE || "~", ".claude");
33514
+ return path8.join(process.env.HOME || process.env.USERPROFILE || "~", ".claude");
33286
33515
  }
33287
33516
 
33288
33517
  // src/utils/file-installer.ts
33289
- var import_fs_extra4 = __toESM(require_lib4(), 1);
33290
- import path5 from "path";
33518
+ var import_fs_extra8 = __toESM(require_lib4(), 1);
33519
+ import path10 from "path";
33291
33520
 
33292
33521
  // src/utils/github.ts
33293
- var import_fs_extra3 = __toESM(require_lib4(), 1);
33294
- import path4 from "path";
33295
- var GITHUB_RAW_BASE2 = "https://raw.githubusercontent.com/Melvynx/aiblueprint-cli/main/claude-code-config";
33522
+ var import_fs_extra7 = __toESM(require_lib4(), 1);
33523
+ import path9 from "path";
33524
+ var GITHUB_RAW_BASE3 = "https://raw.githubusercontent.com/Melvynx/aiblueprint-cli/main/claude-code-config";
33296
33525
  async function downloadFromGitHub2(relativePath) {
33297
33526
  try {
33298
- const url = `${GITHUB_RAW_BASE2}/${relativePath}`;
33527
+ const url = `${GITHUB_RAW_BASE3}/${relativePath}`;
33299
33528
  const response = await fetch(url);
33300
33529
  if (!response.ok) {
33301
33530
  return null;
@@ -33320,7 +33549,7 @@ async function listFilesFromGitHub(dirPath) {
33320
33549
  }
33321
33550
  async function isGitHubAvailable() {
33322
33551
  try {
33323
- const testUrl = `${GITHUB_RAW_BASE2}/commands/commit.md`;
33552
+ const testUrl = `${GITHUB_RAW_BASE3}/commands/commit.md`;
33324
33553
  const testResponse = await fetch(testUrl);
33325
33554
  return testResponse.ok;
33326
33555
  } catch {
@@ -33330,8 +33559,8 @@ async function isGitHubAvailable() {
33330
33559
  async function downloadAndWriteFile(relativePath, targetPath) {
33331
33560
  const content = await downloadFromGitHub2(relativePath);
33332
33561
  if (content) {
33333
- await import_fs_extra3.default.ensureDir(path4.dirname(targetPath));
33334
- await import_fs_extra3.default.writeFile(targetPath, content);
33562
+ await import_fs_extra7.default.ensureDir(path9.dirname(targetPath));
33563
+ await import_fs_extra7.default.writeFile(targetPath, content);
33335
33564
  return true;
33336
33565
  }
33337
33566
  return false;
@@ -33340,7 +33569,7 @@ async function downloadAndWriteFile(relativePath, targetPath) {
33340
33569
  // src/utils/file-installer.ts
33341
33570
  async function installFileWithGitHubFallback(options) {
33342
33571
  const { sourceDir, targetPath, fileName } = options;
33343
- await import_fs_extra4.default.ensureDir(path5.dirname(targetPath));
33572
+ await import_fs_extra8.default.ensureDir(path10.dirname(targetPath));
33344
33573
  const useGitHub = options.useGitHub ?? await isGitHubAvailable();
33345
33574
  if (useGitHub) {
33346
33575
  const relativePath = `${sourceDir}/${fileName}`;
@@ -33354,11 +33583,11 @@ async function installFileWithGitHubFallback(options) {
33354
33583
  if (!localConfigDir) {
33355
33584
  throw new Error(`Neither GitHub nor local ${sourceDir} directory found`);
33356
33585
  }
33357
- const localFilePath = path5.join(localConfigDir, fileName);
33358
- if (!await import_fs_extra4.default.pathExists(localFilePath)) {
33586
+ const localFilePath = path10.join(localConfigDir, fileName);
33587
+ if (!await import_fs_extra8.default.pathExists(localFilePath)) {
33359
33588
  throw new Error(`File not found: ${fileName}`);
33360
33589
  }
33361
- await import_fs_extra4.default.copy(localFilePath, targetPath);
33590
+ await import_fs_extra8.default.copy(localFilePath, targetPath);
33362
33591
  }
33363
33592
  async function getFileContentWithGitHubFallback(sourceDir, fileName) {
33364
33593
  const useGitHub = await isGitHubAvailable();
@@ -33373,11 +33602,11 @@ async function getFileContentWithGitHubFallback(sourceDir, fileName) {
33373
33602
  if (!localConfigDir) {
33374
33603
  throw new Error(`Neither GitHub nor local ${sourceDir} directory found`);
33375
33604
  }
33376
- const localFilePath = path5.join(localConfigDir, fileName);
33377
- if (!await import_fs_extra4.default.pathExists(localFilePath)) {
33605
+ const localFilePath = path10.join(localConfigDir, fileName);
33606
+ if (!await import_fs_extra8.default.pathExists(localFilePath)) {
33378
33607
  throw new Error(`File not found: ${fileName}`);
33379
33608
  }
33380
- return await import_fs_extra4.default.readFile(localFilePath, "utf-8");
33609
+ return await import_fs_extra8.default.readFile(localFilePath, "utf-8");
33381
33610
  }
33382
33611
 
33383
33612
  // src/commands/addHook.ts
@@ -33399,6 +33628,8 @@ var supportedHooks = {
33399
33628
  name: "Post Edit TypeScript Hook",
33400
33629
  description: "Runs Prettier, ESLint, and TypeScript checks after editing TypeScript files",
33401
33630
  hookFile: "hook-post-file.ts",
33631
+ sourceDir: "scripts",
33632
+ targetDir: "scripts",
33402
33633
  event: "PostToolUse",
33403
33634
  matcher: "Edit|Write|MultiEdit"
33404
33635
  }
@@ -33417,10 +33648,10 @@ async function addHookCommand(hookType, options) {
33417
33648
  const s = new SimpleSpinner2;
33418
33649
  const targetDir = await getTargetDirectory(options);
33419
33650
  const claudeDir = targetDir;
33420
- const hooksDir = path6.join(claudeDir, "hooks");
33421
- const hookFilePath = path6.join(hooksDir, hook.hookFile);
33422
- const settingsPath = path6.join(claudeDir, "settings.json");
33423
- if (await import_fs_extra5.default.pathExists(hookFilePath)) {
33651
+ const targetHookDir = path11.join(claudeDir, hook.targetDir || "hooks");
33652
+ const hookFilePath = path11.join(targetHookDir, hook.hookFile);
33653
+ const settingsPath = path11.join(claudeDir, "settings.json");
33654
+ if (await import_fs_extra9.default.pathExists(hookFilePath)) {
33424
33655
  const overwriteAnswer = await lib_default.prompt([{
33425
33656
  type: "confirm",
33426
33657
  name: "overwrite",
@@ -33433,18 +33664,18 @@ async function addHookCommand(hookType, options) {
33433
33664
  }
33434
33665
  try {
33435
33666
  s.start("Installing hook...");
33436
- await import_fs_extra5.default.ensureDir(hooksDir);
33667
+ await import_fs_extra9.default.ensureDir(targetHookDir);
33437
33668
  await installFileWithGitHubFallback({
33438
- sourceDir: "hooks",
33669
+ sourceDir: hook.sourceDir || "hooks",
33439
33670
  targetPath: hookFilePath,
33440
33671
  fileName: hook.hookFile
33441
33672
  });
33442
- await import_fs_extra5.default.chmod(hookFilePath, 493);
33673
+ await import_fs_extra9.default.chmod(hookFilePath, 493);
33443
33674
  s.stop("Hook file installed");
33444
33675
  s.start("Updating settings.json...");
33445
33676
  let settings = {};
33446
33677
  try {
33447
- const existingSettings = await import_fs_extra5.default.readFile(settingsPath, "utf-8");
33678
+ const existingSettings = await import_fs_extra9.default.readFile(settingsPath, "utf-8");
33448
33679
  settings = JSON.parse(existingSettings);
33449
33680
  } catch {
33450
33681
  settings = {};
@@ -33460,7 +33691,7 @@ async function addHookCommand(hookType, options) {
33460
33691
  hooks: [
33461
33692
  {
33462
33693
  type: "command",
33463
- command: `bun $CLAUDE_PROJECT_DIR/.claude/hooks/${hook.hookFile}`
33694
+ command: `bun $CLAUDE_PROJECT_DIR/.claude/${hook.targetDir || "hooks"}/${hook.hookFile}`
33464
33695
  }
33465
33696
  ]
33466
33697
  };
@@ -33481,7 +33712,7 @@ async function addHookCommand(hookType, options) {
33481
33712
  } else {
33482
33713
  settings.hooks[hook.event].push(newHook);
33483
33714
  }
33484
- await import_fs_extra5.default.writeFile(settingsPath, JSON.stringify(settings, null, 2));
33715
+ await import_fs_extra9.default.writeFile(settingsPath, JSON.stringify(settings, null, 2));
33485
33716
  s.stop("Settings updated");
33486
33717
  console.log(source_default.green("✨ Hook installed successfully!"));
33487
33718
  console.log(source_default.gray(`
@@ -33500,8 +33731,8 @@ The hook will run automatically when you edit TypeScript files with Claude Code.
33500
33731
  }
33501
33732
 
33502
33733
  // src/commands/addCommand.ts
33503
- var import_fs_extra6 = __toESM(require_lib4(), 1);
33504
- import path7 from "path";
33734
+ var import_fs_extra10 = __toESM(require_lib4(), 1);
33735
+ import path12 from "path";
33505
33736
  class SimpleSpinner3 {
33506
33737
  message = "";
33507
33738
  start(message) {
@@ -33518,12 +33749,13 @@ async function discoverAvailableCommands() {
33518
33749
  let mdFiles = [];
33519
33750
  if (useGitHub) {
33520
33751
  mdFiles = (await listFilesFromGitHub("commands")).filter((file) => file.endsWith(".md"));
33521
- } else {
33752
+ }
33753
+ if (mdFiles.length === 0) {
33522
33754
  const commandsDir = await findLocalConfigDir("commands");
33523
33755
  if (!commandsDir) {
33524
33756
  throw new Error("Commands directory not found");
33525
33757
  }
33526
- const files = await import_fs_extra6.default.readdir(commandsDir);
33758
+ const files = await import_fs_extra10.default.readdir(commandsDir);
33527
33759
  mdFiles = files.filter((file) => file.endsWith(".md"));
33528
33760
  }
33529
33761
  for (const file of mdFiles) {
@@ -33584,9 +33816,9 @@ async function addCommandCommand(commandName, options = {}) {
33584
33816
  if (options.folder) {
33585
33817
  console.log(source_default.gray(`Using custom folder: ${targetDir}`));
33586
33818
  }
33587
- const commandsDir = path7.join(targetDir, "commands");
33588
- const commandFilePath = path7.join(commandsDir, command.commandFile);
33589
- if (await import_fs_extra6.default.pathExists(commandFilePath)) {
33819
+ const commandsDir = path12.join(targetDir, "commands");
33820
+ const commandFilePath = path12.join(commandsDir, command.commandFile);
33821
+ if (await import_fs_extra10.default.pathExists(commandFilePath)) {
33590
33822
  const overwriteAnswer = await lib_default.prompt([{
33591
33823
  type: "confirm",
33592
33824
  name: "overwrite",
@@ -33626,6 +33858,219 @@ The command will be available immediately in Claude Code.`));
33626
33858
  }
33627
33859
  }
33628
33860
 
33861
+ // src/commands/symlink.ts
33862
+ var TOOLS = [
33863
+ {
33864
+ name: "Claude Code",
33865
+ value: "claude-code",
33866
+ supportsCommands: true,
33867
+ supportsAgents: true
33868
+ },
33869
+ {
33870
+ name: "Codex",
33871
+ value: "codex",
33872
+ supportsCommands: true,
33873
+ supportsAgents: false
33874
+ },
33875
+ {
33876
+ name: "OpenCode",
33877
+ value: "opencode",
33878
+ supportsCommands: true,
33879
+ supportsAgents: false
33880
+ },
33881
+ {
33882
+ name: "FactoryAI",
33883
+ value: "factoryai",
33884
+ supportsCommands: true,
33885
+ supportsAgents: true
33886
+ }
33887
+ ];
33888
+ async function symlinkCommand(params = {}) {
33889
+ try {
33890
+ console.log(source_default.blue.bold(`
33891
+ \uD83D\uDD17 Symlink Manager
33892
+ `));
33893
+ console.log(source_default.gray("Create symlinks between different CLI tool configurations"));
33894
+ const sourceAnswer = await lib_default.prompt([
33895
+ {
33896
+ type: "list",
33897
+ name: "source",
33898
+ message: "Select source tool:",
33899
+ choices: TOOLS.map((tool) => ({
33900
+ name: tool.name,
33901
+ value: tool.value
33902
+ }))
33903
+ }
33904
+ ]);
33905
+ const sourceTool = sourceAnswer.source;
33906
+ const sourceConfig = TOOLS.find((t) => t.value === sourceTool);
33907
+ const contentTypeChoices = [];
33908
+ if (sourceConfig.supportsCommands) {
33909
+ contentTypeChoices.push({ name: "Commands only", value: "commands" });
33910
+ }
33911
+ if (sourceConfig.supportsAgents) {
33912
+ contentTypeChoices.push({ name: "Agents only", value: "agents" });
33913
+ }
33914
+ if (sourceConfig.supportsCommands && sourceConfig.supportsAgents) {
33915
+ contentTypeChoices.push({ name: "Both", value: "both" });
33916
+ }
33917
+ if (contentTypeChoices.length === 0) {
33918
+ console.log(source_default.red(`
33919
+ ❌ Error: ${sourceConfig.name} doesn't support any syncable content`));
33920
+ process.exit(1);
33921
+ }
33922
+ const contentAnswer = await lib_default.prompt([
33923
+ {
33924
+ type: "list",
33925
+ name: "contentType",
33926
+ message: "What would you like to sync?",
33927
+ choices: contentTypeChoices
33928
+ }
33929
+ ]);
33930
+ const syncType = contentAnswer.contentType;
33931
+ const syncCommands = syncType === "commands" || syncType === "both";
33932
+ const syncAgents = syncType === "agents" || syncType === "both";
33933
+ const destinationChoices = [];
33934
+ for (const tool of TOOLS) {
33935
+ if (tool.value === sourceTool)
33936
+ continue;
33937
+ if (syncCommands && tool.supportsCommands) {
33938
+ destinationChoices.push({
33939
+ name: syncAgents ? `${tool.name} (commands)` : tool.name,
33940
+ value: `${tool.value}-commands`,
33941
+ tool: tool.value,
33942
+ contentType: "commands"
33943
+ });
33944
+ }
33945
+ if (syncAgents && tool.supportsAgents) {
33946
+ destinationChoices.push({
33947
+ name: syncCommands ? `${tool.name} (agents)` : tool.name,
33948
+ value: `${tool.value}-agents`,
33949
+ tool: tool.value,
33950
+ contentType: "agents"
33951
+ });
33952
+ }
33953
+ }
33954
+ if (destinationChoices.length === 0) {
33955
+ console.log(source_default.yellow(`
33956
+ ⚠️ No compatible destination tools found for the selected sync type`));
33957
+ process.exit(0);
33958
+ }
33959
+ const destinationAnswer = await lib_default.prompt([
33960
+ {
33961
+ type: "checkbox",
33962
+ name: "destinations",
33963
+ message: "Select destination tools (multi-select):",
33964
+ choices: destinationChoices.map((choice) => ({
33965
+ name: choice.name,
33966
+ value: choice.value,
33967
+ checked: false
33968
+ })),
33969
+ validate: (answer) => {
33970
+ if (answer.length === 0) {
33971
+ return "Please select at least one destination";
33972
+ }
33973
+ return true;
33974
+ }
33975
+ }
33976
+ ]);
33977
+ const selectedDestinations = destinationAnswer.destinations;
33978
+ const customFolders = {
33979
+ "claude-code": params.claudeCodeFolder,
33980
+ codex: params.codexFolder,
33981
+ opencode: params.openCodeFolder,
33982
+ factoryai: params.factoryAiFolder
33983
+ };
33984
+ const sourcePaths = await getToolPaths(sourceTool, customFolders[sourceTool]);
33985
+ console.log(source_default.blue(`
33986
+ \uD83D\uDCE6 Creating symlinks...
33987
+ `));
33988
+ let successCount = 0;
33989
+ let skipCount = 0;
33990
+ for (const destValue of selectedDestinations) {
33991
+ const destChoice = destinationChoices.find((c) => c.value === destValue);
33992
+ const destPaths = await getToolPaths(destChoice.tool, customFolders[destChoice.tool]);
33993
+ let sourcePath;
33994
+ let targetPath;
33995
+ if (destChoice.contentType === "commands") {
33996
+ sourcePath = sourcePaths.commandsPath;
33997
+ targetPath = destPaths.commandsPath;
33998
+ } else {
33999
+ sourcePath = sourcePaths.agentsPath;
34000
+ targetPath = destPaths.agentsPath;
34001
+ }
34002
+ const toolName = TOOLS.find((t) => t.value === destChoice.tool)?.name || destChoice.tool;
34003
+ const contentLabel = destChoice.contentType === "commands" ? "commands" : "agents";
34004
+ try {
34005
+ const success = await createSymlink(sourcePath, targetPath, {
34006
+ skipMessage: source_default.yellow(` ⚠️ ${toolName} ${contentLabel} path already exists and is not a symlink. Skipping...`)
34007
+ });
34008
+ if (success) {
34009
+ console.log(source_default.green(` ✓ ${toolName} (${contentLabel}) symlink created`));
34010
+ successCount++;
34011
+ } else {
34012
+ skipCount++;
34013
+ }
34014
+ } catch (error) {
34015
+ console.error(source_default.red(` ✗ Failed to create ${toolName} (${contentLabel}) symlink:`), error);
34016
+ }
34017
+ }
34018
+ console.log(source_default.green(`
34019
+ ✨ Symlink setup complete! ${successCount} created, ${skipCount} skipped`));
34020
+ } catch (error) {
34021
+ console.error(source_default.red(`
34022
+ ❌ Symlink setup failed:`), error);
34023
+ process.exit(1);
34024
+ }
34025
+ }
34026
+
34027
+ // src/commands/statusline.ts
34028
+ var import_fs_extra11 = __toESM(require_lib4(), 1);
34029
+ import path13 from "path";
34030
+ import { homedir } from "os";
34031
+ async function statuslineCommand(options) {
34032
+ const claudeDir = options.folder ? path13.resolve(options.folder) : path13.join(homedir(), ".claude");
34033
+ console.log(source_default.blue("\uD83D\uDE80 Setting up AIBlueprint Statusline..."));
34034
+ console.log(source_default.gray(` Target: ${claudeDir}
34035
+ `));
34036
+ await import_fs_extra11.default.ensureDir(claudeDir);
34037
+ console.log(source_default.cyan("\uD83D\uDCE6 Checking dependencies..."));
34038
+ await checkAndInstallDependencies();
34039
+ console.log(source_default.cyan(`
34040
+ \uD83D\uDCE5 Downloading statusline files...`));
34041
+ const scriptsDir = path13.join(claudeDir, "scripts");
34042
+ await import_fs_extra11.default.ensureDir(scriptsDir);
34043
+ const success = await downloadDirectoryFromGitHub("scripts/statusline", path13.join(scriptsDir, "statusline"));
34044
+ if (!success) {
34045
+ console.log(source_default.red(" Failed to download statusline files from GitHub"));
34046
+ return;
34047
+ }
34048
+ console.log(source_default.cyan(`
34049
+ \uD83D\uDCE6 Installing statusline dependencies...`));
34050
+ await installStatuslineDependencies(claudeDir);
34051
+ console.log(source_default.cyan(`
34052
+ ⚙️ Configuring settings.json...`));
34053
+ const settingsPath = path13.join(claudeDir, "settings.json");
34054
+ let settings = {};
34055
+ try {
34056
+ const existingSettings = await import_fs_extra11.default.readFile(settingsPath, "utf-8");
34057
+ settings = JSON.parse(existingSettings);
34058
+ } catch {
34059
+ }
34060
+ settings.statusLine = {
34061
+ type: "command",
34062
+ command: `bun ${path13.join(claudeDir, "scripts/statusline/src/index.ts")}`,
34063
+ padding: 0
34064
+ };
34065
+ await import_fs_extra11.default.writeJson(settingsPath, settings, { spaces: 2 });
34066
+ console.log(source_default.green(`
34067
+ ✅ Statusline setup complete!`));
34068
+ console.log(source_default.gray(`
34069
+ Your Claude Code statusline is now configured.`));
34070
+ console.log(source_default.gray(`Restart Claude Code to see the changes.
34071
+ `));
34072
+ }
34073
+
33629
34074
  // src/cli.ts
33630
34075
  import { readFileSync as readFileSync2 } from "fs";
33631
34076
  import { dirname as dirname4, join } from "path";
@@ -33634,10 +34079,15 @@ var __dirname5 = dirname4(fileURLToPath4(import.meta.url));
33634
34079
  var packageJson = JSON.parse(readFileSync2(join(__dirname5, "../package.json"), "utf8"));
33635
34080
  var program2 = new Command;
33636
34081
  program2.name("aiblueprint").description("AIBlueprint CLI for setting up Claude Code configurations").version(packageJson.version);
33637
- 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");
34082
+ 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");
33638
34083
  claudeCodeCmd.command("setup").description("Setup Claude Code configuration with AIBlueprint defaults").action((options, command) => {
33639
34084
  const parentOptions = command.parent.opts();
33640
- setupCommand(parentOptions.folder, parentOptions.skip);
34085
+ setupCommand({
34086
+ claudeCodeFolder: parentOptions.claudeCodeFolder || parentOptions.folder,
34087
+ codexFolder: parentOptions.codexFolder,
34088
+ openCodeFolder: parentOptions.openCodeFolder,
34089
+ skipInteractive: parentOptions.skip
34090
+ });
33641
34091
  });
33642
34092
  var addCmd = claudeCodeCmd.command("add").description(`Add components to your Claude Code configuration
33643
34093
  ` + `Examples:
@@ -33646,11 +34096,27 @@ var addCmd = claudeCodeCmd.command("add").description(`Add components to your Cl
33646
34096
  ` + " aiblueprint claude-code add commands commit");
33647
34097
  addCmd.command("hook <type>").description("Add a hook to your Claude Code configuration. Available types: post-edit-typescript").action((type, options, command) => {
33648
34098
  const parentOptions = command.parent.parent.opts();
33649
- addHookCommand(type, { folder: parentOptions.folder });
34099
+ const claudeCodeFolder = parentOptions.claudeCodeFolder || parentOptions.folder;
34100
+ addHookCommand(type, { folder: claudeCodeFolder });
33650
34101
  });
33651
34102
  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) => {
33652
34103
  const parentOptions = command.parent.parent.opts();
33653
- addCommandCommand(commandName, { folder: parentOptions.folder });
34104
+ const claudeCodeFolder = parentOptions.claudeCodeFolder || parentOptions.folder;
34105
+ addCommandCommand(commandName, { folder: claudeCodeFolder });
34106
+ });
34107
+ claudeCodeCmd.command("symlink").description("Create symlinks between different CLI tools (Claude Code, Codex, OpenCode, FactoryAI)").action((options, command) => {
34108
+ const parentOptions = command.parent.opts();
34109
+ symlinkCommand({
34110
+ claudeCodeFolder: parentOptions.claudeCodeFolder || parentOptions.folder,
34111
+ codexFolder: parentOptions.codexFolder,
34112
+ openCodeFolder: parentOptions.openCodeFolder,
34113
+ factoryAiFolder: parentOptions.factoryAiFolder
34114
+ });
34115
+ });
34116
+ claudeCodeCmd.command("statusline").description("Setup custom statusline with git status, costs, and token usage").action((options, command) => {
34117
+ const parentOptions = command.parent.opts();
34118
+ const claudeCodeFolder = parentOptions.claudeCodeFolder || parentOptions.folder;
34119
+ statuslineCommand({ folder: claudeCodeFolder });
33654
34120
  });
33655
34121
  program2.parse(process.argv);
33656
34122
  if (!process.argv.slice(2).length) {