thought-cabinet 0.1.13 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -68,11 +68,29 @@ function saveConfigFile(config, configFile) {
68
68
  console.log(chalk.green("Config saved successfully"));
69
69
  }
70
70
  function getDefaultConfigDir() {
71
- const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || "", ".config");
72
- return path.join(xdgConfigHome, "thought-cabinet");
71
+ if (process.env.XDG_CONFIG_HOME) {
72
+ return path.join(process.env.XDG_CONFIG_HOME, "thought-cabinet");
73
+ }
74
+ return path.join(process.env.HOME || "", ".thought-cabinet");
75
+ }
76
+ function getLegacyConfigDir() {
77
+ return path.join(process.env.HOME || "", ".config", "thought-cabinet");
78
+ }
79
+ function resolveConfigDir() {
80
+ const newDir = getDefaultConfigDir();
81
+ const newConfigPath = path.join(newDir, ConfigResolver.DEFAULT_CONFIG_FILE);
82
+ if (fs.existsSync(newConfigPath)) {
83
+ return newDir;
84
+ }
85
+ const legacyDir = getLegacyConfigDir();
86
+ const legacyConfigPath = path.join(legacyDir, ConfigResolver.DEFAULT_CONFIG_FILE);
87
+ if (fs.existsSync(legacyConfigPath)) {
88
+ return legacyDir;
89
+ }
90
+ return newDir;
73
91
  }
74
92
  function getDefaultConfigPath() {
75
- return path.join(getDefaultConfigDir(), ConfigResolver.DEFAULT_CONFIG_FILE);
93
+ return path.join(resolveConfigDir(), ConfigResolver.DEFAULT_CONFIG_FILE);
76
94
  }
77
95
 
78
96
  // src/commands/thoughts/utils/config.ts
@@ -249,7 +267,7 @@ function getRepoRoot(cwd) {
249
267
 
250
268
  // src/commands/thoughts/utils/paths.ts
251
269
  function getDefaultThoughtsRepo() {
252
- return path3.join(os.homedir(), "thoughts");
270
+ return path3.join(getDefaultConfigDir(), "thoughts");
253
271
  }
254
272
  function expandPath(filePath) {
255
273
  if (filePath.startsWith("~/")) {
@@ -929,7 +947,7 @@ function resolveExitCode(exitCode, timedOut) {
929
947
  async function executeHook(hook, input, env = {}) {
930
948
  const startTime = Date.now();
931
949
  const timeoutMs = (hook.timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
932
- return new Promise((resolve2) => {
950
+ return new Promise((resolve3) => {
933
951
  let stdout = "";
934
952
  let stderr = "";
935
953
  let timedOut = false;
@@ -958,7 +976,7 @@ async function executeHook(hook, input, env = {}) {
958
976
  child.on("close", (exitCode) => {
959
977
  clearTimeout(timer);
960
978
  const duration = Date.now() - startTime;
961
- resolve2({
979
+ resolve3({
962
980
  success: exitCode === 0 && !timedOut,
963
981
  exitCode: resolveExitCode(exitCode, timedOut),
964
982
  stdout: stdout.trim(),
@@ -970,7 +988,7 @@ async function executeHook(hook, input, env = {}) {
970
988
  child.on("error", (error) => {
971
989
  clearTimeout(timer);
972
990
  const duration = Date.now() - startTime;
973
- resolve2({
991
+ resolve3({
974
992
  success: false,
975
993
  exitCode: 1,
976
994
  stdout: stdout.trim(),
@@ -1077,81 +1095,92 @@ async function thoughtsInitCommand(options) {
1077
1095
  }
1078
1096
  let config = loadThoughtsConfig(options);
1079
1097
  if (!config) {
1080
- p.intro(chalk5.blue("Initial Thoughts Setup"));
1081
- p.log.info("First, let's configure your global thoughts system.");
1082
- const defaultRepo = getDefaultThoughtsRepo();
1083
- p.log.message(
1084
- chalk5.gray("This is where all your thoughts across all projects will be stored.")
1085
- );
1086
- const thoughtsRepoInput = await p.text({
1087
- message: "Thoughts repository location:",
1088
- initialValue: defaultRepo,
1089
- placeholder: defaultRepo
1090
- });
1091
- if (p.isCancel(thoughtsRepoInput)) {
1092
- p.cancel("Operation cancelled.");
1093
- process.exit(0);
1094
- }
1095
- const thoughtsRepo = thoughtsRepoInput || defaultRepo;
1096
- p.log.message(chalk5.gray("Your thoughts will be organized into two main directories:"));
1097
- p.log.message(chalk5.gray("- Repository-specific thoughts (one subdirectory per project)"));
1098
- p.log.message(chalk5.gray("- Global thoughts (shared across all projects)"));
1099
- const reposDirInput = await p.text({
1100
- message: "Directory name for repository-specific thoughts:",
1101
- initialValue: "repos",
1102
- placeholder: "repos"
1103
- });
1104
- if (p.isCancel(reposDirInput)) {
1105
- p.cancel("Operation cancelled.");
1106
- process.exit(0);
1107
- }
1108
- const reposDir2 = reposDirInput || "repos";
1109
- const globalDirInput = await p.text({
1110
- message: "Directory name for global thoughts:",
1111
- initialValue: "global",
1112
- placeholder: "global"
1113
- });
1114
- if (p.isCancel(globalDirInput)) {
1115
- p.cancel("Operation cancelled.");
1116
- process.exit(0);
1117
- }
1118
- const globalDir = globalDirInput || "global";
1119
- const defaultUser = process.env.USER || "user";
1120
- let user = "";
1121
- while (!user || user.toLowerCase() === "global") {
1122
- const userInput = await p.text({
1123
- message: "Your username:",
1124
- initialValue: defaultUser,
1125
- placeholder: defaultUser,
1126
- validate: (value) => {
1127
- if (value.toLowerCase() === "global") {
1128
- return `Username cannot be "global" as it's reserved for cross-project thoughts.`;
1129
- }
1130
- return void 0;
1131
- }
1098
+ if (options.directory) {
1099
+ const thoughtsRepo = getDefaultThoughtsRepo();
1100
+ const reposDir2 = "repos";
1101
+ const globalDir = "global";
1102
+ const user = process.env.USER || "user";
1103
+ config = { thoughtsRepo, reposDir: reposDir2, globalDir, user, repoMappings: {} };
1104
+ ensureThoughtsRepoExists(thoughtsRepo, reposDir2, globalDir);
1105
+ saveThoughtsConfig(config, options);
1106
+ p.log.success("Global thoughts configuration created with defaults");
1107
+ } else {
1108
+ p.intro(chalk5.blue("Initial Thoughts Setup"));
1109
+ p.log.info("First, let's configure your global thoughts system.");
1110
+ const defaultRepo = getDefaultThoughtsRepo();
1111
+ p.log.message(
1112
+ chalk5.gray("This is where all your thoughts across all projects will be stored.")
1113
+ );
1114
+ const thoughtsRepoInput = await p.text({
1115
+ message: "Thoughts repository location:",
1116
+ initialValue: defaultRepo,
1117
+ placeholder: defaultRepo
1132
1118
  });
1133
- if (p.isCancel(userInput)) {
1119
+ if (p.isCancel(thoughtsRepoInput)) {
1134
1120
  p.cancel("Operation cancelled.");
1135
1121
  process.exit(0);
1136
1122
  }
1137
- user = userInput || defaultUser;
1138
- }
1139
- config = {
1140
- thoughtsRepo,
1141
- reposDir: reposDir2,
1142
- globalDir,
1143
- user,
1144
- repoMappings: {}
1145
- };
1146
- p.note(
1147
- `${chalk5.cyan(thoughtsRepo)}/
1123
+ const thoughtsRepo = thoughtsRepoInput || defaultRepo;
1124
+ p.log.message(chalk5.gray("Your thoughts will be organized into two main directories:"));
1125
+ p.log.message(chalk5.gray("- Repository-specific thoughts (one subdirectory per project)"));
1126
+ p.log.message(chalk5.gray("- Global thoughts (shared across all projects)"));
1127
+ const reposDirInput = await p.text({
1128
+ message: "Directory name for repository-specific thoughts:",
1129
+ initialValue: "repos",
1130
+ placeholder: "repos"
1131
+ });
1132
+ if (p.isCancel(reposDirInput)) {
1133
+ p.cancel("Operation cancelled.");
1134
+ process.exit(0);
1135
+ }
1136
+ const reposDir2 = reposDirInput || "repos";
1137
+ const globalDirInput = await p.text({
1138
+ message: "Directory name for global thoughts:",
1139
+ initialValue: "global",
1140
+ placeholder: "global"
1141
+ });
1142
+ if (p.isCancel(globalDirInput)) {
1143
+ p.cancel("Operation cancelled.");
1144
+ process.exit(0);
1145
+ }
1146
+ const globalDir = globalDirInput || "global";
1147
+ const defaultUser = process.env.USER || "user";
1148
+ let user = "";
1149
+ while (!user || user.toLowerCase() === "global") {
1150
+ const userInput = await p.text({
1151
+ message: "Your username:",
1152
+ initialValue: defaultUser,
1153
+ placeholder: defaultUser,
1154
+ validate: (value) => {
1155
+ if (value.toLowerCase() === "global") {
1156
+ return `Username cannot be "global" as it's reserved for cross-project thoughts.`;
1157
+ }
1158
+ return void 0;
1159
+ }
1160
+ });
1161
+ if (p.isCancel(userInput)) {
1162
+ p.cancel("Operation cancelled.");
1163
+ process.exit(0);
1164
+ }
1165
+ user = userInput || defaultUser;
1166
+ }
1167
+ config = {
1168
+ thoughtsRepo,
1169
+ reposDir: reposDir2,
1170
+ globalDir,
1171
+ user,
1172
+ repoMappings: {}
1173
+ };
1174
+ p.note(
1175
+ `${chalk5.cyan(thoughtsRepo)}/
1148
1176
  \u251C\u2500\u2500 ${chalk5.cyan(reposDir2)}/ ${chalk5.gray("(project-specific thoughts)")}
1149
1177
  \u2514\u2500\u2500 ${chalk5.cyan(globalDir)}/ ${chalk5.gray("(cross-project thoughts)")}`,
1150
- "Creating thoughts structure"
1151
- );
1152
- ensureThoughtsRepoExists(thoughtsRepo, reposDir2, globalDir);
1153
- saveThoughtsConfig(config, options);
1154
- p.log.success("Global thoughts configuration created");
1178
+ "Creating thoughts structure"
1179
+ );
1180
+ ensureThoughtsRepoExists(thoughtsRepo, reposDir2, globalDir);
1181
+ saveThoughtsConfig(config, options);
1182
+ p.log.success("Global thoughts configuration created");
1183
+ }
1155
1184
  }
1156
1185
  if (options.profile) {
1157
1186
  if (!validateProfile(config, options.profile)) {
@@ -1243,18 +1272,16 @@ async function thoughtsInitCommand(options) {
1243
1272
  if (!mappedName) {
1244
1273
  if (options.directory) {
1245
1274
  const sanitizedDir = sanitizeDirectoryName(options.directory);
1246
- if (!existingRepos.includes(sanitizedDir)) {
1247
- p.log.error(`Directory "${sanitizedDir}" not found in thoughts repository.`);
1248
- p.log.error("In non-interactive mode (--directory), you must specify a directory");
1249
- p.log.error("name that already exists in the thoughts repository.");
1250
- p.log.warn("Available directories:");
1251
- existingRepos.forEach((repo) => p.log.message(chalk5.gray(` - ${repo}`)));
1252
- process.exit(1);
1275
+ if (existingRepos.includes(sanitizedDir)) {
1276
+ p.log.success(
1277
+ `Using existing: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/${sanitizedDir}`
1278
+ );
1279
+ } else {
1280
+ p.log.success(
1281
+ `Will create: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/${sanitizedDir}`
1282
+ );
1253
1283
  }
1254
1284
  mappedName = sanitizedDir;
1255
- p.log.success(
1256
- `Using existing: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/${mappedName}`
1257
- );
1258
1285
  } else {
1259
1286
  p.intro(chalk5.blue("Repository Setup"));
1260
1287
  p.log.info(`Setting up thoughts for: ${chalk5.cyan(currentRepo)}`);
@@ -1971,32 +1998,365 @@ async function thoughtsPruneCommand(options) {
1971
1998
  }
1972
1999
  }
1973
2000
 
1974
- // src/commands/thoughts/profile/create.ts
2001
+ // src/commands/thoughts/migrate.ts
2002
+ import fs12 from "fs";
2003
+ import path14 from "path";
1975
2004
  import chalk11 from "chalk";
1976
2005
  import * as p2 from "@clack/prompts";
2006
+
2007
+ // src/commands/agent/registry.ts
2008
+ import { homedir } from "os";
2009
+ import { join } from "path";
2010
+ import { existsSync } from "fs";
2011
+ var home = homedir();
2012
+ var claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
2013
+ var agents = {
2014
+ "claude-code": {
2015
+ name: "claude-code",
2016
+ displayName: "Claude Code",
2017
+ configDir: ".claude",
2018
+ globalConfigDir: claudeHome,
2019
+ detectInstalled: async () => existsSync(claudeHome)
2020
+ },
2021
+ codebuddy: {
2022
+ name: "codebuddy",
2023
+ displayName: "CodeBuddy Code",
2024
+ configDir: ".codebuddy",
2025
+ globalConfigDir: join(home, ".codebuddy"),
2026
+ detectInstalled: async () => existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"))
2027
+ }
2028
+ };
2029
+ function getAllAgents() {
2030
+ return Object.values(agents);
2031
+ }
2032
+ function isValidAgentType(value) {
2033
+ return value in agents;
2034
+ }
2035
+
2036
+ // src/commands/agent/constants.ts
2037
+ var CANONICAL_DIR = ".thought-cabinet";
2038
+ var CATEGORY_SUBDIRS = {
2039
+ skills: "skills",
2040
+ commands: "commands",
2041
+ agents: "agents"
2042
+ };
2043
+
2044
+ // src/commands/thoughts/migrate.ts
2045
+ function planMigration(homeDir) {
2046
+ const home2 = homeDir || process.env.HOME || "";
2047
+ const newConfigDir = path14.join(home2, ".thought-cabinet");
2048
+ const legacyConfigDir = path14.join(home2, ".config", "thought-cabinet");
2049
+ const legacyConfigPath = path14.join(legacyConfigDir, "config.json");
2050
+ const newConfigPath = path14.join(newConfigDir, "config.json");
2051
+ if (fs12.existsSync(newConfigPath)) {
2052
+ return null;
2053
+ }
2054
+ if (!fs12.existsSync(legacyConfigPath)) {
2055
+ return null;
2056
+ }
2057
+ let config;
2058
+ try {
2059
+ config = JSON.parse(fs12.readFileSync(legacyConfigPath, "utf8"));
2060
+ } catch {
2061
+ return null;
2062
+ }
2063
+ const thoughts = config.thoughts;
2064
+ if (!thoughts) {
2065
+ return null;
2066
+ }
2067
+ const moves = [];
2068
+ moves.push({
2069
+ from: legacyConfigPath,
2070
+ to: path14.join(newConfigDir, "config.json"),
2071
+ label: "Config file"
2072
+ });
2073
+ for (const dir of ["agents", "skills"]) {
2074
+ const src = path14.join(legacyConfigDir, dir);
2075
+ if (fs12.existsSync(src)) {
2076
+ moves.push({
2077
+ from: src,
2078
+ to: path14.join(newConfigDir, dir),
2079
+ label: `${dir}/ directory`
2080
+ });
2081
+ }
2082
+ }
2083
+ if (thoughts.thoughtsRepo) {
2084
+ const expandedThoughtsRepo = expandPath(thoughts.thoughtsRepo);
2085
+ const newDefaultThoughtsRepo = path14.join(newConfigDir, "thoughts");
2086
+ if (fs12.existsSync(expandedThoughtsRepo) && expandedThoughtsRepo !== newDefaultThoughtsRepo && !expandedThoughtsRepo.startsWith(newConfigDir + path14.sep)) {
2087
+ moves.push({
2088
+ from: expandedThoughtsRepo,
2089
+ to: newDefaultThoughtsRepo,
2090
+ label: `Default thoughts repo (${thoughts.thoughtsRepo})`
2091
+ });
2092
+ }
2093
+ }
2094
+ if (thoughts.profiles) {
2095
+ for (const [name, profile] of Object.entries(thoughts.profiles)) {
2096
+ const expandedProfileRepo = expandPath(profile.thoughtsRepo);
2097
+ const newProfileRepo = path14.join(newConfigDir, `thoughts-${name}`);
2098
+ if (fs12.existsSync(expandedProfileRepo) && expandedProfileRepo !== newProfileRepo && !expandedProfileRepo.startsWith(newConfigDir + path14.sep)) {
2099
+ moves.push({
2100
+ from: expandedProfileRepo,
2101
+ to: newProfileRepo,
2102
+ label: `Profile "${name}" thoughts repo (${profile.thoughtsRepo})`
2103
+ });
2104
+ }
2105
+ }
2106
+ }
2107
+ const affectedRepos = thoughts.repoMappings ? Object.keys(thoughts.repoMappings) : [];
2108
+ return {
2109
+ moves,
2110
+ newConfigDir,
2111
+ legacyConfigDir,
2112
+ config,
2113
+ affectedRepos
2114
+ };
2115
+ }
2116
+ function executeMigration(plan) {
2117
+ fs12.mkdirSync(plan.newConfigDir, { recursive: true });
2118
+ for (const move of plan.moves) {
2119
+ const destDir = path14.dirname(move.to);
2120
+ fs12.mkdirSync(destDir, { recursive: true });
2121
+ fs12.renameSync(move.from, move.to);
2122
+ }
2123
+ const thoughts = plan.config.thoughts;
2124
+ if (thoughts) {
2125
+ thoughts.thoughtsRepo = path14.join(plan.newConfigDir, "thoughts");
2126
+ if (thoughts.profiles) {
2127
+ for (const [name, profile] of Object.entries(thoughts.profiles)) {
2128
+ profile.thoughtsRepo = path14.join(plan.newConfigDir, `thoughts-${name}`);
2129
+ }
2130
+ }
2131
+ }
2132
+ const newConfigPath = path14.join(plan.newConfigDir, "config.json");
2133
+ fs12.writeFileSync(newConfigPath, JSON.stringify(plan.config, null, 2));
2134
+ }
2135
+ function refreshRepoSymlinks(config, affectedRepos) {
2136
+ const refreshed = [];
2137
+ const skipped = [];
2138
+ for (const repoPath of affectedRepos) {
2139
+ if (!fs12.existsSync(repoPath)) {
2140
+ skipped.push(repoPath);
2141
+ continue;
2142
+ }
2143
+ const mapping = config.repoMappings[repoPath];
2144
+ const mappedName = getRepoNameFromMapping(mapping);
2145
+ if (!mappedName) {
2146
+ skipped.push(repoPath);
2147
+ continue;
2148
+ }
2149
+ const profileConfig = resolveProfileForRepo(
2150
+ {
2151
+ thoughtsRepo: config.thoughtsRepo,
2152
+ reposDir: config.reposDir,
2153
+ globalDir: config.globalDir,
2154
+ user: config.user,
2155
+ repoMappings: config.repoMappings,
2156
+ profiles: config.profiles
2157
+ },
2158
+ repoPath
2159
+ );
2160
+ createThoughtsDirectoryStructure(profileConfig, mappedName, config.user);
2161
+ setupThoughtsDirectory({
2162
+ repoPath,
2163
+ profileConfig,
2164
+ mappedName,
2165
+ user: config.user
2166
+ });
2167
+ refreshed.push(repoPath);
2168
+ }
2169
+ return { refreshed, skipped };
2170
+ }
2171
+ function refreshGlobalAgentSymlinks(legacyConfigDir, newConfigDir, agentList) {
2172
+ let refreshed = 0;
2173
+ const agents2 = [];
2174
+ const agentsToScan = agentList || getAllAgents();
2175
+ for (const agent of agentsToScan) {
2176
+ if (!agent.globalConfigDir) continue;
2177
+ let agentTouched = false;
2178
+ for (const category of Object.values(CATEGORY_SUBDIRS)) {
2179
+ const categoryDir = path14.join(agent.globalConfigDir, category);
2180
+ if (!fs12.existsSync(categoryDir)) continue;
2181
+ const entries = fs12.readdirSync(categoryDir, { withFileTypes: true });
2182
+ for (const entry of entries) {
2183
+ const entryPath = path14.join(categoryDir, entry.name);
2184
+ let stats;
2185
+ try {
2186
+ stats = fs12.lstatSync(entryPath);
2187
+ } catch {
2188
+ continue;
2189
+ }
2190
+ if (!stats.isSymbolicLink()) continue;
2191
+ const linkTarget = fs12.readlinkSync(entryPath);
2192
+ const resolvedTarget = path14.resolve(path14.dirname(entryPath), linkTarget);
2193
+ if (!resolvedTarget.startsWith(legacyConfigDir + path14.sep) && resolvedTarget !== legacyConfigDir) {
2194
+ continue;
2195
+ }
2196
+ const relativeToCfg = path14.relative(legacyConfigDir, resolvedTarget);
2197
+ const newTarget = path14.join(newConfigDir, relativeToCfg);
2198
+ if (!fs12.existsSync(newTarget)) continue;
2199
+ fs12.unlinkSync(entryPath);
2200
+ const newRelative = path14.relative(path14.dirname(entryPath), newTarget);
2201
+ fs12.symlinkSync(newRelative, entryPath);
2202
+ refreshed++;
2203
+ agentTouched = true;
2204
+ }
2205
+ }
2206
+ if (agentTouched) {
2207
+ agents2.push(agent.displayName);
2208
+ }
2209
+ }
2210
+ return { refreshed, agents: agents2 };
2211
+ }
2212
+ function previewGlobalAgentSymlinks(legacyConfigDir, agentList) {
2213
+ const entries = [];
2214
+ const agentsToScan = agentList || getAllAgents();
2215
+ for (const agent of agentsToScan) {
2216
+ if (!agent.globalConfigDir) continue;
2217
+ for (const category of Object.values(CATEGORY_SUBDIRS)) {
2218
+ const categoryDir = path14.join(agent.globalConfigDir, category);
2219
+ if (!fs12.existsSync(categoryDir)) continue;
2220
+ const dirEntries = fs12.readdirSync(categoryDir, { withFileTypes: true });
2221
+ for (const entry of dirEntries) {
2222
+ const entryPath = path14.join(categoryDir, entry.name);
2223
+ try {
2224
+ const stats = fs12.lstatSync(entryPath);
2225
+ if (!stats.isSymbolicLink()) continue;
2226
+ const linkTarget = fs12.readlinkSync(entryPath);
2227
+ const resolvedTarget = path14.resolve(path14.dirname(entryPath), linkTarget);
2228
+ if (resolvedTarget.startsWith(legacyConfigDir + path14.sep)) {
2229
+ entries.push(entryPath);
2230
+ }
2231
+ } catch {
2232
+ continue;
2233
+ }
2234
+ }
2235
+ }
2236
+ }
2237
+ return entries;
2238
+ }
2239
+ async function thoughtsMigrateCommand(options) {
2240
+ const newConfigDir = getDefaultConfigDir();
2241
+ const legacyConfigDir = getLegacyConfigDir();
2242
+ const newConfigPath = path14.join(newConfigDir, ConfigResolver.DEFAULT_CONFIG_FILE);
2243
+ const legacyConfigPath = path14.join(legacyConfigDir, ConfigResolver.DEFAULT_CONFIG_FILE);
2244
+ if (!fs12.existsSync(legacyConfigPath)) {
2245
+ if (fs12.existsSync(newConfigPath)) {
2246
+ p2.log.info("Configuration is already at the new location.");
2247
+ return;
2248
+ }
2249
+ p2.log.info("No configuration found to migrate.");
2250
+ return;
2251
+ }
2252
+ if (legacyConfigDir === newConfigDir) {
2253
+ p2.log.info("Configuration is already at the expected location.");
2254
+ return;
2255
+ }
2256
+ const plan = planMigration();
2257
+ if (!plan) {
2258
+ p2.log.info("Nothing to migrate.");
2259
+ return;
2260
+ }
2261
+ p2.intro(chalk11.blue("Migrate Thought Cabinet Configuration"));
2262
+ p2.log.step("Migration plan:");
2263
+ for (const move of plan.moves) {
2264
+ p2.log.message(` ${move.label}`);
2265
+ p2.log.message(chalk11.gray(` ${move.from} \u2192 ${move.to}`));
2266
+ }
2267
+ if (options.dryRun) {
2268
+ if (plan.affectedRepos.length > 0) {
2269
+ p2.log.step("Repo symlinks that would be refreshed:");
2270
+ for (const repo of plan.affectedRepos) {
2271
+ if (fs12.existsSync(repo)) {
2272
+ p2.log.message(chalk11.gray(` ${repo}/thoughts/`));
2273
+ } else {
2274
+ p2.log.message(chalk11.gray(` ${repo}/thoughts/ (skipped \u2014 repo not found)`));
2275
+ }
2276
+ }
2277
+ }
2278
+ const agentPreview = previewGlobalAgentSymlinks(plan.legacyConfigDir);
2279
+ if (agentPreview.length > 0) {
2280
+ p2.log.step("Global agent symlinks that would be refreshed:");
2281
+ for (const entry of agentPreview) {
2282
+ p2.log.message(chalk11.gray(` ${entry}`));
2283
+ }
2284
+ }
2285
+ p2.log.info("Dry run \u2014 no changes made.");
2286
+ return;
2287
+ }
2288
+ const confirm5 = await p2.confirm({
2289
+ message: "Proceed with migration?",
2290
+ initialValue: true
2291
+ });
2292
+ if (p2.isCancel(confirm5) || !confirm5) {
2293
+ p2.cancel("Migration cancelled.");
2294
+ return;
2295
+ }
2296
+ executeMigration(plan);
2297
+ for (const move of plan.moves) {
2298
+ p2.log.success(`Moved: ${move.label}`);
2299
+ }
2300
+ try {
2301
+ const remaining = fs12.readdirSync(plan.legacyConfigDir);
2302
+ if (remaining.length === 0) {
2303
+ fs12.rmdirSync(plan.legacyConfigDir);
2304
+ p2.log.info(`Removed empty directory: ${plan.legacyConfigDir}`);
2305
+ }
2306
+ } catch {
2307
+ }
2308
+ if (plan.affectedRepos.length > 0) {
2309
+ const config = loadThoughtsConfig({ configFile: path14.join(plan.newConfigDir, "config.json") });
2310
+ if (config) {
2311
+ p2.log.step("Refreshing thoughts symlinks in registered repos...");
2312
+ const repoResult = refreshRepoSymlinks(config, plan.affectedRepos);
2313
+ for (const repo of repoResult.refreshed) {
2314
+ p2.log.success(`Refreshed: ${repo}`);
2315
+ }
2316
+ for (const repo of repoResult.skipped) {
2317
+ p2.log.warn(`Skipped (not found): ${repo}`);
2318
+ }
2319
+ }
2320
+ }
2321
+ p2.log.step("Refreshing global agent symlinks...");
2322
+ const agentResult = refreshGlobalAgentSymlinks(plan.legacyConfigDir, plan.newConfigDir);
2323
+ if (agentResult.refreshed > 0) {
2324
+ p2.log.success(
2325
+ `Refreshed ${agentResult.refreshed} symlink(s) for: ${agentResult.agents.join(", ")}`
2326
+ );
2327
+ } else {
2328
+ p2.log.info("No global agent symlinks needed updating.");
2329
+ }
2330
+ p2.outro(chalk11.green("Migration complete!"));
2331
+ }
2332
+
2333
+ // src/commands/thoughts/profile/create.ts
2334
+ import chalk12 from "chalk";
2335
+ import path15 from "path";
2336
+ import * as p3 from "@clack/prompts";
1977
2337
  async function profileCreateCommand(profileName, options) {
1978
2338
  try {
1979
2339
  if (!options.repo || !options.reposDir || !options.globalDir) {
1980
2340
  if (!process.stdin.isTTY) {
1981
- p2.log.error("Not running in interactive terminal.");
1982
- p2.log.info("Provide all options: --repo, --repos-dir, --global-dir");
2341
+ p3.log.error("Not running in interactive terminal.");
2342
+ p3.log.info("Provide all options: --repo, --repos-dir, --global-dir");
1983
2343
  process.exit(1);
1984
2344
  }
1985
2345
  }
1986
2346
  const config = loadThoughtsConfig(options);
1987
2347
  if (!config) {
1988
- p2.log.error("Thoughts not configured.");
1989
- p2.log.info('Run "thoughtcabinet init" first to set up the base configuration.');
2348
+ p3.log.error("Thoughts not configured.");
2349
+ p3.log.info('Run "thoughtcabinet init" first to set up the base configuration.');
1990
2350
  process.exit(1);
1991
2351
  }
1992
2352
  const sanitizedName = sanitizeProfileName(profileName);
1993
2353
  if (sanitizedName !== profileName) {
1994
- p2.log.warn(`Profile name sanitized: "${profileName}" \u2192 "${sanitizedName}"`);
2354
+ p3.log.warn(`Profile name sanitized: "${profileName}" \u2192 "${sanitizedName}"`);
1995
2355
  }
1996
- p2.intro(chalk11.blue(`Creating Profile: ${sanitizedName}`));
2356
+ p3.intro(chalk12.blue(`Creating Profile: ${sanitizedName}`));
1997
2357
  if (validateProfile(config, sanitizedName)) {
1998
- p2.log.error(`Profile "${sanitizedName}" already exists.`);
1999
- p2.log.info("Use a different name or delete the existing profile first.");
2358
+ p3.log.error(`Profile "${sanitizedName}" already exists.`);
2359
+ p3.log.info("Use a different name or delete the existing profile first.");
2000
2360
  process.exit(1);
2001
2361
  }
2002
2362
  let thoughtsRepo;
@@ -2007,35 +2367,35 @@ async function profileCreateCommand(profileName, options) {
2007
2367
  reposDir = options.reposDir;
2008
2368
  globalDir = options.globalDir;
2009
2369
  } else {
2010
- const defaultRepo = getDefaultThoughtsRepo() + `-${sanitizedName}`;
2011
- p2.log.info("Specify the thoughts repository location for this profile.");
2012
- const repoInput = await p2.text({
2370
+ const defaultRepo = path15.join(getDefaultConfigDir(), `thoughts-${sanitizedName}`);
2371
+ p3.log.info("Specify the thoughts repository location for this profile.");
2372
+ const repoInput = await p3.text({
2013
2373
  message: "Thoughts repository:",
2014
2374
  initialValue: defaultRepo,
2015
2375
  placeholder: defaultRepo
2016
2376
  });
2017
- if (p2.isCancel(repoInput)) {
2018
- p2.cancel("Operation cancelled.");
2377
+ if (p3.isCancel(repoInput)) {
2378
+ p3.cancel("Operation cancelled.");
2019
2379
  process.exit(0);
2020
2380
  }
2021
2381
  thoughtsRepo = repoInput || defaultRepo;
2022
- const reposDirInput = await p2.text({
2382
+ const reposDirInput = await p3.text({
2023
2383
  message: "Repository-specific thoughts directory:",
2024
2384
  initialValue: "repos",
2025
2385
  placeholder: "repos"
2026
2386
  });
2027
- if (p2.isCancel(reposDirInput)) {
2028
- p2.cancel("Operation cancelled.");
2387
+ if (p3.isCancel(reposDirInput)) {
2388
+ p3.cancel("Operation cancelled.");
2029
2389
  process.exit(0);
2030
2390
  }
2031
2391
  reposDir = reposDirInput || "repos";
2032
- const globalDirInput = await p2.text({
2392
+ const globalDirInput = await p3.text({
2033
2393
  message: "Global thoughts directory:",
2034
2394
  initialValue: "global",
2035
2395
  placeholder: "global"
2036
2396
  });
2037
- if (p2.isCancel(globalDirInput)) {
2038
- p2.cancel("Operation cancelled.");
2397
+ if (p3.isCancel(globalDirInput)) {
2398
+ p3.cancel("Operation cancelled.");
2039
2399
  process.exit(0);
2040
2400
  }
2041
2401
  globalDir = globalDirInput || "global";
@@ -2050,56 +2410,56 @@ async function profileCreateCommand(profileName, options) {
2050
2410
  }
2051
2411
  config.profiles[sanitizedName] = profileConfig;
2052
2412
  saveThoughtsConfig(config, options);
2053
- p2.log.step("Initializing profile thoughts repository...");
2413
+ p3.log.step("Initializing profile thoughts repository...");
2054
2414
  ensureThoughtsRepoExists(profileConfig);
2055
- p2.log.success(`Profile "${sanitizedName}" created successfully!`);
2056
- p2.note(
2057
- `Name: ${chalk11.cyan(sanitizedName)}
2058
- Thoughts repository: ${chalk11.cyan(thoughtsRepo)}
2059
- Repos directory: ${chalk11.cyan(reposDir)}
2060
- Global directory: ${chalk11.cyan(globalDir)}`,
2415
+ p3.log.success(`Profile "${sanitizedName}" created successfully!`);
2416
+ p3.note(
2417
+ `Name: ${chalk12.cyan(sanitizedName)}
2418
+ Thoughts repository: ${chalk12.cyan(thoughtsRepo)}
2419
+ Repos directory: ${chalk12.cyan(reposDir)}
2420
+ Global directory: ${chalk12.cyan(globalDir)}`,
2061
2421
  "Profile Configuration"
2062
2422
  );
2063
- p2.outro(
2064
- chalk11.gray("Next steps:\n") + chalk11.gray(` 1. Run "thoughtcabinet init --profile ${sanitizedName}" in a repository
2065
- `) + chalk11.gray(` 2. Your thoughts will sync to the profile's repository`)
2423
+ p3.outro(
2424
+ chalk12.gray("Next steps:\n") + chalk12.gray(` 1. Run "thoughtcabinet init --profile ${sanitizedName}" in a repository
2425
+ `) + chalk12.gray(` 2. Your thoughts will sync to the profile's repository`)
2066
2426
  );
2067
2427
  } catch (error) {
2068
- p2.log.error(`Error creating profile: ${error}`);
2428
+ p3.log.error(`Error creating profile: ${error}`);
2069
2429
  process.exit(1);
2070
2430
  }
2071
2431
  }
2072
2432
 
2073
2433
  // src/commands/thoughts/profile/list.ts
2074
- import chalk12 from "chalk";
2434
+ import chalk13 from "chalk";
2075
2435
  async function profileListCommand(options) {
2076
2436
  try {
2077
2437
  const config = loadThoughtsConfig(options);
2078
2438
  if (!config) {
2079
- console.error(chalk12.red("Error: Thoughts not configured."));
2439
+ console.error(chalk13.red("Error: Thoughts not configured."));
2080
2440
  process.exit(1);
2081
2441
  }
2082
2442
  if (options.json) {
2083
2443
  console.log(JSON.stringify(config.profiles || {}, null, 2));
2084
2444
  return;
2085
2445
  }
2086
- console.log(chalk12.blue("Thoughts Profiles"));
2087
- console.log(chalk12.gray("=".repeat(50)));
2446
+ console.log(chalk13.blue("Thoughts Profiles"));
2447
+ console.log(chalk13.gray("=".repeat(50)));
2088
2448
  console.log("");
2089
- console.log(chalk12.yellow("Default Configuration:"));
2090
- console.log(` Thoughts repository: ${chalk12.cyan(config.thoughtsRepo)}`);
2091
- console.log(` Repos directory: ${chalk12.cyan(config.reposDir)}`);
2092
- console.log(` Global directory: ${chalk12.cyan(config.globalDir)}`);
2449
+ console.log(chalk13.yellow("Default Configuration:"));
2450
+ console.log(` Thoughts repository: ${chalk13.cyan(config.thoughtsRepo)}`);
2451
+ console.log(` Repos directory: ${chalk13.cyan(config.reposDir)}`);
2452
+ console.log(` Global directory: ${chalk13.cyan(config.globalDir)}`);
2093
2453
  console.log("");
2094
2454
  if (!config.profiles || Object.keys(config.profiles).length === 0) {
2095
- console.log(chalk12.gray("No profiles configured."));
2455
+ console.log(chalk13.gray("No profiles configured."));
2096
2456
  console.log("");
2097
- console.log(chalk12.gray("Create a profile with: thoughtcabinet profile create <name>"));
2457
+ console.log(chalk13.gray("Create a profile with: thoughtcabinet profile create <name>"));
2098
2458
  } else {
2099
- console.log(chalk12.yellow(`Profiles (${Object.keys(config.profiles).length}):`));
2459
+ console.log(chalk13.yellow(`Profiles (${Object.keys(config.profiles).length}):`));
2100
2460
  console.log("");
2101
2461
  Object.entries(config.profiles).forEach(([name, profile]) => {
2102
- console.log(chalk12.cyan(` ${name}:`));
2462
+ console.log(chalk13.cyan(` ${name}:`));
2103
2463
  console.log(` Thoughts repository: ${profile.thoughtsRepo}`);
2104
2464
  console.log(` Repos directory: ${profile.reposDir}`);
2105
2465
  console.log(` Global directory: ${profile.globalDir}`);
@@ -2107,30 +2467,30 @@ async function profileListCommand(options) {
2107
2467
  });
2108
2468
  }
2109
2469
  } catch (error) {
2110
- console.error(chalk12.red(`Error listing profiles: ${error}`));
2470
+ console.error(chalk13.red(`Error listing profiles: ${error}`));
2111
2471
  process.exit(1);
2112
2472
  }
2113
2473
  }
2114
2474
 
2115
2475
  // src/commands/thoughts/profile/show.ts
2116
- import chalk13 from "chalk";
2476
+ import chalk14 from "chalk";
2117
2477
  async function profileShowCommand(profileName, options) {
2118
2478
  try {
2119
2479
  const config = loadThoughtsConfig(options);
2120
2480
  if (!config) {
2121
- console.error(chalk13.red("Error: Thoughts not configured."));
2481
+ console.error(chalk14.red("Error: Thoughts not configured."));
2122
2482
  process.exit(1);
2123
2483
  }
2124
2484
  if (!validateProfile(config, profileName)) {
2125
- console.error(chalk13.red(`Error: Profile "${profileName}" not found.`));
2485
+ console.error(chalk14.red(`Error: Profile "${profileName}" not found.`));
2126
2486
  console.error("");
2127
- console.error(chalk13.gray("Available profiles:"));
2487
+ console.error(chalk14.gray("Available profiles:"));
2128
2488
  if (config.profiles) {
2129
2489
  Object.keys(config.profiles).forEach((name) => {
2130
- console.error(chalk13.gray(` - ${name}`));
2490
+ console.error(chalk14.gray(` - ${name}`));
2131
2491
  });
2132
2492
  } else {
2133
- console.error(chalk13.gray(" (none)"));
2493
+ console.error(chalk14.gray(" (none)"));
2134
2494
  }
2135
2495
  process.exit(1);
2136
2496
  }
@@ -2139,13 +2499,13 @@ async function profileShowCommand(profileName, options) {
2139
2499
  console.log(JSON.stringify(profile, null, 2));
2140
2500
  return;
2141
2501
  }
2142
- console.log(chalk13.blue(`Profile: ${profileName}`));
2143
- console.log(chalk13.gray("=".repeat(50)));
2502
+ console.log(chalk14.blue(`Profile: ${profileName}`));
2503
+ console.log(chalk14.gray("=".repeat(50)));
2144
2504
  console.log("");
2145
- console.log(chalk13.yellow("Configuration:"));
2146
- console.log(` Thoughts repository: ${chalk13.cyan(profile.thoughtsRepo)}`);
2147
- console.log(` Repos directory: ${chalk13.cyan(profile.reposDir)}`);
2148
- console.log(` Global directory: ${chalk13.cyan(profile.globalDir)}`);
2505
+ console.log(chalk14.yellow("Configuration:"));
2506
+ console.log(` Thoughts repository: ${chalk14.cyan(profile.thoughtsRepo)}`);
2507
+ console.log(` Repos directory: ${chalk14.cyan(profile.reposDir)}`);
2508
+ console.log(` Global directory: ${chalk14.cyan(profile.globalDir)}`);
2149
2509
  console.log("");
2150
2510
  let repoCount = 0;
2151
2511
  Object.values(config.repoMappings).forEach((mapping) => {
@@ -2153,32 +2513,32 @@ async function profileShowCommand(profileName, options) {
2153
2513
  repoCount++;
2154
2514
  }
2155
2515
  });
2156
- console.log(chalk13.yellow("Usage:"));
2157
- console.log(` Repositories using this profile: ${chalk13.cyan(repoCount)}`);
2516
+ console.log(chalk14.yellow("Usage:"));
2517
+ console.log(` Repositories using this profile: ${chalk14.cyan(repoCount)}`);
2158
2518
  } catch (error) {
2159
- console.error(chalk13.red(`Error showing profile: ${error}`));
2519
+ console.error(chalk14.red(`Error showing profile: ${error}`));
2160
2520
  process.exit(1);
2161
2521
  }
2162
2522
  }
2163
2523
 
2164
2524
  // src/commands/thoughts/profile/delete.ts
2165
- import chalk14 from "chalk";
2166
- import * as p3 from "@clack/prompts";
2525
+ import chalk15 from "chalk";
2526
+ import * as p4 from "@clack/prompts";
2167
2527
  async function profileDeleteCommand(profileName, options) {
2168
2528
  try {
2169
2529
  if (!options.force && !process.stdin.isTTY) {
2170
- p3.log.error("Not running in interactive terminal.");
2171
- p3.log.info("Use --force flag to delete without confirmation.");
2530
+ p4.log.error("Not running in interactive terminal.");
2531
+ p4.log.info("Use --force flag to delete without confirmation.");
2172
2532
  process.exit(1);
2173
2533
  }
2174
- p3.intro(chalk14.blue(`Delete Profile: ${profileName}`));
2534
+ p4.intro(chalk15.blue(`Delete Profile: ${profileName}`));
2175
2535
  const config = loadThoughtsConfig(options);
2176
2536
  if (!config) {
2177
- p3.log.error("Thoughts not configured.");
2537
+ p4.log.error("Thoughts not configured.");
2178
2538
  process.exit(1);
2179
2539
  }
2180
2540
  if (!validateProfile(config, profileName)) {
2181
- p3.log.error(`Profile "${profileName}" not found.`);
2541
+ p4.log.error(`Profile "${profileName}" not found.`);
2182
2542
  process.exit(1);
2183
2543
  }
2184
2544
  const usingRepos = [];
@@ -2188,27 +2548,27 @@ async function profileDeleteCommand(profileName, options) {
2188
2548
  }
2189
2549
  });
2190
2550
  if (usingRepos.length > 0 && !options.force) {
2191
- p3.log.error(`Profile "${profileName}" is in use by ${usingRepos.length} repository(ies):`);
2551
+ p4.log.error(`Profile "${profileName}" is in use by ${usingRepos.length} repository(ies):`);
2192
2552
  usingRepos.forEach((repo) => {
2193
- p3.log.message(chalk14.gray(` - ${repo}`));
2553
+ p4.log.message(chalk15.gray(` - ${repo}`));
2194
2554
  });
2195
- p3.log.warn("Options:");
2196
- p3.log.message(chalk14.gray(' 1. Run "thoughtcabinet destroy" in each repository'));
2197
- p3.log.message(
2198
- chalk14.gray(" 2. Use --force to delete anyway (repos will fall back to default config)")
2555
+ p4.log.warn("Options:");
2556
+ p4.log.message(chalk15.gray(' 1. Run "thoughtcabinet destroy" in each repository'));
2557
+ p4.log.message(
2558
+ chalk15.gray(" 2. Use --force to delete anyway (repos will fall back to default config)")
2199
2559
  );
2200
2560
  process.exit(1);
2201
2561
  }
2202
2562
  if (!options.force) {
2203
- p3.log.warn(`You are about to delete profile: ${chalk14.cyan(profileName)}`);
2204
- p3.log.message(chalk14.gray("This will remove the profile configuration."));
2205
- p3.log.message(chalk14.gray("The thoughts repository files will NOT be deleted."));
2206
- const confirmDelete = await p3.confirm({
2563
+ p4.log.warn(`You are about to delete profile: ${chalk15.cyan(profileName)}`);
2564
+ p4.log.message(chalk15.gray("This will remove the profile configuration."));
2565
+ p4.log.message(chalk15.gray("The thoughts repository files will NOT be deleted."));
2566
+ const confirmDelete = await p4.confirm({
2207
2567
  message: `Delete profile "${profileName}"?`,
2208
2568
  initialValue: false
2209
2569
  });
2210
- if (p3.isCancel(confirmDelete) || !confirmDelete) {
2211
- p3.cancel("Deletion cancelled.");
2570
+ if (p4.isCancel(confirmDelete) || !confirmDelete) {
2571
+ p4.cancel("Deletion cancelled.");
2212
2572
  return;
2213
2573
  }
2214
2574
  }
@@ -2217,13 +2577,13 @@ async function profileDeleteCommand(profileName, options) {
2217
2577
  delete config.profiles;
2218
2578
  }
2219
2579
  saveThoughtsConfig(config, options);
2220
- p3.log.success(`Profile "${profileName}" deleted`);
2580
+ p4.log.success(`Profile "${profileName}" deleted`);
2221
2581
  if (usingRepos.length > 0) {
2222
- p3.log.warn("Repositories using this profile will fall back to default config");
2582
+ p4.log.warn("Repositories using this profile will fall back to default config");
2223
2583
  }
2224
- p3.outro(chalk14.green("Done"));
2584
+ p4.outro(chalk15.green("Done"));
2225
2585
  } catch (error) {
2226
- p3.log.error(`Error deleting profile: ${error}`);
2586
+ p4.log.error(`Error deleting profile: ${error}`);
2227
2587
  process.exit(1);
2228
2588
  }
2229
2589
  }
@@ -2240,6 +2600,7 @@ function thoughtsCommand(program) {
2240
2600
  cmd.command("status").description("Show status of thoughts repository").option("--config-file <path>", "Path to config file").option("--fetch", "Fetch from remote before showing status").action(thoughtsStatusCommand);
2241
2601
  cmd.command("config").description("View or edit thoughts configuration").option("--edit", "Open configuration in editor").option("--json", "Output configuration as JSON").option("--config-file <path>", "Path to config file").action(thoughtsConfigCommand);
2242
2602
  cmd.command("prune").description("Remove stale repository mappings (directories that no longer exist)").option("--apply", "Apply changes (default is dry-run)").option("--config-file <path>", "Path to config file").action(thoughtsPruneCommand);
2603
+ cmd.command("migrate").description("Migrate configuration from ~/.config/thought-cabinet/ to ~/.thought-cabinet/").option("--dry-run", "Show what would be migrated without making changes").option("--config-file <path>", "Path to legacy config file").action(thoughtsMigrateCommand);
2243
2604
  const profile = cmd.command("profile").description("Manage thoughts profiles");
2244
2605
  profile.command("create <name>").description("Create a new thoughts profile").option("--repo <path>", "Thoughts repository path").option("--repos-dir <name>", "Repos directory name").option("--global-dir <name>", "Global directory name").option("--config-file <path>", "Path to config file").action(profileCreateCommand);
2245
2606
  profile.command("list").description("List all thoughts profiles").option("--json", "Output as JSON").option("--config-file <path>", "Path to config file").action(profileListCommand);
@@ -2248,49 +2609,12 @@ function thoughtsCommand(program) {
2248
2609
  }
2249
2610
 
2250
2611
  // src/commands/agent/init.ts
2251
- import fs12 from "fs";
2252
- import path14 from "path";
2253
- import chalk15 from "chalk";
2254
- import * as p4 from "@clack/prompts";
2612
+ import { existsSync as existsSync2 } from "fs";
2613
+ import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
2614
+ import chalk16 from "chalk";
2615
+ import * as p5 from "@clack/prompts";
2255
2616
  import { fileURLToPath } from "url";
2256
-
2257
- // src/commands/agent/registry.ts
2258
- import { homedir } from "os";
2259
- import { join } from "path";
2260
- import { existsSync } from "fs";
2261
- var home = homedir();
2262
- var claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
2263
- var agents = {
2264
- "claude-code": {
2265
- name: "claude-code",
2266
- displayName: "Claude Code",
2267
- configDir: ".claude",
2268
- globalConfigDir: claudeHome,
2269
- detectInstalled: async () => existsSync(claudeHome)
2270
- },
2271
- codebuddy: {
2272
- name: "codebuddy",
2273
- displayName: "CodeBuddy Code",
2274
- configDir: ".codebuddy",
2275
- globalConfigDir: join(home, ".codebuddy"),
2276
- detectInstalled: async () => existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"))
2277
- }
2278
- };
2279
- function getAllAgents() {
2280
- return Object.values(agents);
2281
- }
2282
- async function detectInstalledAgents() {
2283
- const results = await Promise.all(
2284
- Object.entries(agents).map(async ([type, config]) => ({
2285
- type,
2286
- installed: await config.detectInstalled()
2287
- }))
2288
- );
2289
- return results.filter((r) => r.installed).map((r) => r.type);
2290
- }
2291
- function isValidAgentType(value) {
2292
- return value in agents;
2293
- }
2617
+ import { cp as cp2, mkdir as mkdir2, readdir as readdir3 } from "fs/promises";
2294
2618
 
2295
2619
  // src/commands/agent/discovery.ts
2296
2620
  import { readdir, readFile } from "fs/promises";
@@ -2378,16 +2702,7 @@ async function discoverAllAssets(sourcePath) {
2378
2702
  // src/commands/agent/installer.ts
2379
2703
  import { mkdir, cp, readdir as readdir2, symlink as fsSymlink, lstat, rm, readlink } from "fs/promises";
2380
2704
  import { join as join3, basename, normalize, resolve, sep, relative, dirname } from "path";
2381
- import { platform } from "os";
2382
-
2383
- // src/commands/agent/constants.ts
2384
- var CATEGORY_SUBDIRS = {
2385
- skills: "skills",
2386
- commands: "commands",
2387
- agents: "agents"
2388
- };
2389
-
2390
- // src/commands/agent/installer.ts
2705
+ import { homedir as homedir2, platform } from "os";
2391
2706
  function sanitizeName(name) {
2392
2707
  const sanitized = name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.-]+|[.-]+$/g, "");
2393
2708
  return sanitized.substring(0, 255) || "unnamed-asset";
@@ -2402,6 +2717,10 @@ function getAgentDir(agentType, category, scope, cwd) {
2402
2717
  const agentBase = scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : join3(cwd || process.cwd(), agent.configDir);
2403
2718
  return join3(agentBase, CATEGORY_SUBDIRS[category]);
2404
2719
  }
2720
+ function getCanonicalDir(category, scope, cwd) {
2721
+ const baseDir = scope === "global" ? homedir2() : cwd || process.cwd();
2722
+ return join3(baseDir, CANONICAL_DIR, CATEGORY_SUBDIRS[category]);
2723
+ }
2405
2724
  async function cleanAndCreateDirectory(dirPath) {
2406
2725
  try {
2407
2726
  await rm(dirPath, { recursive: true, force: true });
@@ -2507,15 +2826,37 @@ async function installAssetForAgent(asset, agentType, options = {}) {
2507
2826
  await copyAsset(agentDir);
2508
2827
  return { success: true, path: agentDir, mode: "copy" };
2509
2828
  }
2510
- await rm(agentDir, { recursive: true, force: true });
2511
- await mkdir(dirname(agentDir), { recursive: true });
2512
- const symlinkCreated = await createSymlink(asset.sourcePath, agentDir);
2513
- if (!symlinkCreated) {
2514
- await cleanAndCreateDirectory(agentDir);
2515
- await copyAsset(agentDir);
2516
- return { success: true, path: agentDir, mode: "symlink", symlinkFailed: true };
2829
+ if (scope === "project") {
2830
+ const canonicalBase = getCanonicalDir(asset.category, scope, cwd);
2831
+ const canonicalDir = join3(canonicalBase, assetName);
2832
+ await cleanAndCreateDirectory(canonicalDir);
2833
+ await copyAsset(canonicalDir);
2834
+ await rm(agentDir, { recursive: true, force: true });
2835
+ await mkdir(dirname(agentDir), { recursive: true });
2836
+ const symlinkCreated = await createSymlink(canonicalDir, agentDir);
2837
+ if (!symlinkCreated) {
2838
+ await cleanAndCreateDirectory(agentDir);
2839
+ await copyAsset(agentDir);
2840
+ return {
2841
+ success: true,
2842
+ path: agentDir,
2843
+ canonicalPath: canonicalDir,
2844
+ mode: "symlink",
2845
+ symlinkFailed: true
2846
+ };
2847
+ }
2848
+ return { success: true, path: agentDir, canonicalPath: canonicalDir, mode: "symlink" };
2849
+ } else {
2850
+ await rm(agentDir, { recursive: true, force: true });
2851
+ await mkdir(dirname(agentDir), { recursive: true });
2852
+ const symlinkCreated = await createSymlink(asset.sourcePath, agentDir);
2853
+ if (!symlinkCreated) {
2854
+ await cleanAndCreateDirectory(agentDir);
2855
+ await copyAsset(agentDir);
2856
+ return { success: true, path: agentDir, mode: "symlink", symlinkFailed: true };
2857
+ }
2858
+ return { success: true, path: agentDir, mode: "symlink" };
2517
2859
  }
2518
- return { success: true, path: agentDir, mode: "symlink" };
2519
2860
  } catch (error) {
2520
2861
  return {
2521
2862
  success: false,
@@ -2527,216 +2868,318 @@ async function installAssetForAgent(asset, agentType, options = {}) {
2527
2868
  }
2528
2869
 
2529
2870
  // src/commands/agent/init.ts
2530
- var __dirname2 = path14.dirname(fileURLToPath(import.meta.url));
2531
- function resolveSourceDir(customSource) {
2532
- if (customSource) {
2533
- const resolved = path14.resolve(customSource);
2534
- return fs12.existsSync(resolved) ? resolved : null;
2535
- }
2871
+ var __dirname2 = dirname2(fileURLToPath(import.meta.url));
2872
+ var ASSET_CATEGORIES = ["agents", "skills"];
2873
+ function getBundledAssetsDir() {
2536
2874
  const candidates = [
2537
- path14.resolve(__dirname2, "..", "src/agent-assets"),
2538
- path14.resolve(__dirname2, "../..", "src/agent-assets")
2875
+ resolve2(__dirname2, "..", "src", "agent-assets"),
2876
+ resolve2(__dirname2, "..", "..", "src", "agent-assets"),
2877
+ resolve2(__dirname2, "..", "..", "..", "src", "agent-assets")
2539
2878
  ];
2540
- return candidates.find((p5) => fs12.existsSync(p5)) ?? null;
2879
+ return candidates.find((dir) => existsSync2(dir)) ?? null;
2880
+ }
2881
+ async function bootstrapAssetsIfNeeded(configDir, bundledDir) {
2882
+ if (!bundledDir) return null;
2883
+ const hasAssets = ASSET_CATEGORIES.some((cat) => existsSync2(join4(configDir, cat)));
2884
+ if (hasAssets) return configDir;
2885
+ for (const category of ASSET_CATEGORIES) {
2886
+ const src = join4(bundledDir, category);
2887
+ const dest = join4(configDir, category);
2888
+ if (!existsSync2(src)) continue;
2889
+ await mkdir2(dest, { recursive: true });
2890
+ const entries = await readdir3(src, { withFileTypes: true });
2891
+ for (const entry of entries) {
2892
+ if (!entry.isDirectory()) continue;
2893
+ await cp2(join4(src, entry.name), join4(dest, entry.name), {
2894
+ recursive: true,
2895
+ force: false,
2896
+ errorOnExist: false
2897
+ });
2898
+ }
2899
+ }
2900
+ return configDir;
2901
+ }
2902
+ async function resolveSourceDir(configDir, bundledDir) {
2903
+ const config = configDir ?? getDefaultConfigDir();
2904
+ const bundled = bundledDir === void 0 ? getBundledAssetsDir() : bundledDir;
2905
+ const hasAssets = ASSET_CATEGORIES.some((cat) => existsSync2(join4(config, cat)));
2906
+ if (hasAssets) return config;
2907
+ const bootstrapped = await bootstrapAssetsIfNeeded(config, bundled);
2908
+ if (bootstrapped) return bootstrapped;
2909
+ return bundled;
2541
2910
  }
2542
2911
  function resolveAgentBaseDir(agentType, scope, cwd) {
2543
2912
  const agent = agents[agentType];
2544
- return scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : path14.join(cwd, agent.configDir);
2913
+ return scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : join4(cwd, agent.configDir);
2545
2914
  }
2546
2915
  async function agentInitCommand(options) {
2547
2916
  try {
2548
- p4.intro(chalk15.blue("Initialize Agent Configuration"));
2549
- if (!process.stdin.isTTY && !options.all) {
2550
- p4.log.error("Not running in interactive terminal.");
2551
- p4.log.info("Use --all flag to install all assets without prompting.");
2552
- process.exit(1);
2553
- }
2554
- const sourceDir = resolveSourceDir(options.source);
2917
+ p5.intro(chalk16.blue("Install Skills & Agents"));
2918
+ const sourceDir = await resolveSourceDir();
2555
2919
  if (!sourceDir) {
2556
- p4.log.error("Source directory not found.");
2557
- if (options.source) {
2558
- p4.log.info(`Specified path: ${options.source}`);
2559
- } else {
2560
- p4.log.info("Bundled agent assets not found. Are you running from the package?");
2561
- }
2920
+ p5.log.error("Agent assets not found.");
2921
+ p5.log.info("Bundled agent assets not found. Are you running from the package?");
2562
2922
  process.exit(1);
2563
2923
  }
2564
2924
  const discovered = await discoverAllAssets(sourceDir);
2565
2925
  const totalAssets = discovered.agents.length + discovered.skills.length;
2566
2926
  if (totalAssets === 0) {
2567
- p4.log.warn(`No assets found in ${sourceDir}`);
2927
+ p5.log.warn(`No assets found in ${sourceDir}`);
2568
2928
  process.exit(0);
2569
2929
  }
2570
- let selectedAgents;
2571
- if (options.agents) {
2572
- selectedAgents = options.agents;
2573
- } else if (options.all) {
2574
- selectedAgents = ["claude-code"];
2575
- } else {
2576
- const detected = await detectInstalledAgents();
2577
- const allAgents = getAllAgents();
2578
- const agentSelection = await p4.multiselect({
2579
- message: "Select target agents:",
2580
- options: allAgents.map((agent) => ({
2581
- value: agent.name,
2582
- label: agent.displayName,
2583
- hint: detected.includes(agent.name) ? "detected" : void 0
2584
- })),
2585
- initialValues: detected.length > 0 ? detected : ["claude-code"],
2586
- required: true
2587
- });
2588
- if (p4.isCancel(agentSelection)) {
2589
- p4.cancel("Operation cancelled.");
2590
- process.exit(0);
2591
- }
2592
- selectedAgents = agentSelection;
2593
- }
2594
- let scope = options.scope ?? "project";
2595
- if (!options.scope && !options.all) {
2596
- const supportsGlobal = selectedAgents.some((a) => agents[a].globalConfigDir !== void 0);
2597
- if (supportsGlobal) {
2598
- const scopeChoice = await p4.select({
2599
- message: "Installation scope:",
2600
- options: [
2601
- {
2602
- value: "project",
2603
- label: "Project",
2604
- hint: "Install to current directory"
2605
- },
2606
- {
2607
- value: "global",
2608
- label: "Global",
2609
- hint: "Install to home directory (available across projects)"
2610
- }
2611
- ],
2612
- initialValue: "project"
2613
- });
2614
- if (p4.isCancel(scopeChoice)) {
2615
- p4.cancel("Operation cancelled.");
2616
- process.exit(0);
2617
- }
2618
- scope = scopeChoice;
2619
- }
2620
- }
2930
+ const selectedAgents = options.agents ?? ["claude-code"];
2931
+ const scope = options.scope ?? "project";
2621
2932
  const mode = options.mode ?? "symlink";
2933
+ const cwd = process.cwd();
2622
2934
  if (!options.force) {
2623
- const cwd2 = process.cwd();
2624
2935
  for (const agentType of selectedAgents) {
2625
- const agentDir = resolveAgentBaseDir(agentType, scope, cwd2);
2626
- if (fs12.existsSync(agentDir) && !options.all) {
2627
- const overwrite = await p4.confirm({
2936
+ const agentDir = resolveAgentBaseDir(agentType, scope, cwd);
2937
+ if (existsSync2(agentDir) && process.stdin.isTTY) {
2938
+ const overwrite = await p5.confirm({
2628
2939
  message: `${agents[agentType].displayName} directory already exists at ${agentDir}. Overwrite?`,
2629
2940
  initialValue: false
2630
2941
  });
2631
- if (p4.isCancel(overwrite) || !overwrite) {
2632
- p4.cancel("Operation cancelled.");
2942
+ if (p5.isCancel(overwrite) || !overwrite) {
2943
+ p5.cancel("Operation cancelled.");
2633
2944
  process.exit(0);
2634
2945
  }
2635
2946
  }
2636
2947
  }
2637
2948
  }
2638
- let selectedCategories;
2639
- if (options.all) {
2640
- selectedCategories = ["agents", "skills"];
2641
- } else {
2642
- p4.note(
2643
- "Use \u2191/\u2193 to move, Space to select/deselect, A to toggle all, Enter to confirm.",
2644
- "Multi-select"
2645
- );
2646
- const selection = await p4.multiselect({
2647
- message: "What would you like to install?",
2648
- options: [
2649
- {
2650
- value: "agents",
2651
- label: "Agents",
2652
- hint: `${discovered.agents.length} specialized sub-agents`
2653
- },
2654
- {
2655
- value: "skills",
2656
- label: "Skills",
2657
- hint: `${discovered.skills.length} skill packages`
2658
- }
2659
- ],
2660
- initialValues: ["agents", "skills"],
2661
- required: false
2662
- });
2663
- if (p4.isCancel(selection)) {
2664
- p4.cancel("Operation cancelled.");
2665
- process.exit(0);
2949
+ const allAssets = [...discovered.skills, ...discovered.agents];
2950
+ const results = [];
2951
+ for (const asset of allAssets) {
2952
+ for (const agentType of selectedAgents) {
2953
+ const result = await installAssetForAgent(asset, agentType, { scope, cwd, mode });
2954
+ results.push({
2955
+ asset,
2956
+ agentType,
2957
+ success: result.success,
2958
+ symlinkFailed: result.symlinkFailed,
2959
+ error: result.error
2960
+ });
2666
2961
  }
2667
- selectedCategories = selection;
2668
- if (selectedCategories.length === 0) {
2669
- p4.cancel("No categories selected.");
2670
- process.exit(0);
2962
+ }
2963
+ for (const category of ASSET_CATEGORIES) {
2964
+ const categoryResults = results.filter((r) => r.asset.category === category);
2965
+ if (categoryResults.length === 0) continue;
2966
+ const label = category.charAt(0).toUpperCase() + category.slice(1);
2967
+ p5.log.step(chalk16.bold(label));
2968
+ for (const entry of categoryResults) {
2969
+ if (entry.success && entry.symlinkFailed) {
2970
+ p5.log.warn(` \u26A0 ${entry.asset.name} (symlink failed, copied instead)`);
2971
+ } else if (entry.success) {
2972
+ p5.log.success(` \u2713 ${entry.asset.name}`);
2973
+ } else {
2974
+ p5.log.error(` \u2717 ${entry.asset.name}: ${entry.error}`);
2975
+ }
2671
2976
  }
2672
2977
  }
2673
- const assetsToInstall = [];
2674
- const assetCategories = ["agents", "skills"];
2675
- for (const category of assetCategories) {
2676
- if (!selectedCategories.includes(category)) continue;
2677
- const categoryAssets = discovered[category];
2678
- if (categoryAssets.length === 0) continue;
2679
- if (options.all) {
2680
- assetsToInstall.push(...categoryAssets);
2681
- continue;
2978
+ const totalInstalled = results.filter((r) => r.success).length;
2979
+ const totalFailed = results.filter((r) => !r.success).length;
2980
+ const agentNames = selectedAgents.map((a) => agents[a].displayName).join(", ");
2981
+ let message = `Installed ${totalInstalled} asset(s) to ${agentNames}`;
2982
+ if (totalFailed > 0) {
2983
+ message += chalk16.red(` (${totalFailed} failed)`);
2984
+ }
2985
+ message += chalk16.gray(`
2986
+ Scope: ${scope} | Mode: ${mode}`);
2987
+ p5.outro(message);
2988
+ } catch (error) {
2989
+ p5.log.error(`Error during agent init: ${error}`);
2990
+ process.exit(1);
2991
+ }
2992
+ }
2993
+
2994
+ // src/commands/agent/update.ts
2995
+ import { existsSync as existsSync3 } from "fs";
2996
+ import { join as join5 } from "path";
2997
+ import { cp as cp3, readdir as readdir4, mkdir as mkdir3, rm as rm2, lstat as lstat2 } from "fs/promises";
2998
+ import chalk17 from "chalk";
2999
+ import * as p6 from "@clack/prompts";
3000
+ var ASSET_CATEGORIES2 = ["agents", "skills"];
3001
+ async function updateBundledAssets(configDir, bundledDir) {
3002
+ const seeded = [];
3003
+ const skipped = [];
3004
+ for (const category of ASSET_CATEGORIES2) {
3005
+ const bundledCategoryDir = join5(bundledDir, category);
3006
+ const configCategoryDir = join5(configDir, category);
3007
+ if (!existsSync3(bundledCategoryDir)) continue;
3008
+ await mkdir3(configCategoryDir, { recursive: true });
3009
+ const bundledEntries = await readdir4(bundledCategoryDir, { withFileTypes: true });
3010
+ for (const entry of bundledEntries) {
3011
+ if (!entry.isDirectory()) continue;
3012
+ const src = join5(bundledCategoryDir, entry.name);
3013
+ const dest = join5(configCategoryDir, entry.name);
3014
+ await rm2(dest, { recursive: true, force: true });
3015
+ await cp3(src, dest, { recursive: true });
3016
+ seeded.push(`${category}/${entry.name}`);
3017
+ }
3018
+ if (existsSync3(configCategoryDir)) {
3019
+ const configEntries = await readdir4(configCategoryDir, { withFileTypes: true });
3020
+ const bundledNames = new Set(bundledEntries.filter((e) => e.isDirectory()).map((e) => e.name));
3021
+ for (const entry of configEntries) {
3022
+ if (entry.isDirectory() && !bundledNames.has(entry.name)) {
3023
+ skipped.push(`${category}/${entry.name}`);
3024
+ }
2682
3025
  }
2683
- const assetSelection = await p4.multiselect({
2684
- message: `Select ${category} to install:`,
2685
- options: categoryAssets.map((asset) => ({
2686
- value: asset.name,
2687
- label: asset.name,
2688
- hint: asset.description || void 0
2689
- })),
2690
- initialValues: categoryAssets.map((a) => a.name),
2691
- required: false
2692
- });
2693
- if (p4.isCancel(assetSelection)) {
2694
- p4.cancel("Operation cancelled.");
2695
- process.exit(0);
3026
+ }
3027
+ }
3028
+ return { seeded, skipped };
3029
+ }
3030
+ async function detectInstallTargets(repoPaths) {
3031
+ const targets = [];
3032
+ for (const agent of getAllAgents()) {
3033
+ for (const repoPath of repoPaths) {
3034
+ const mode = await detectModeInDir(repoPath, agent.configDir);
3035
+ if (mode) {
3036
+ targets.push({ agentType: agent.name, scope: "project", mode, cwd: repoPath });
2696
3037
  }
2697
- const selectedNames = new Set(assetSelection);
2698
- assetsToInstall.push(...categoryAssets.filter((a) => selectedNames.has(a.name)));
2699
3038
  }
2700
- const cwd = process.cwd();
3039
+ if (agent.globalConfigDir) {
3040
+ const mode = await detectModeInBaseDir(agent.globalConfigDir);
3041
+ if (mode) {
3042
+ targets.push({ agentType: agent.name, scope: "global", mode, cwd: process.cwd() });
3043
+ }
3044
+ }
3045
+ }
3046
+ return targets;
3047
+ }
3048
+ async function detectModeInDir(repoPath, agentConfigDir) {
3049
+ const baseDir = join5(repoPath, agentConfigDir);
3050
+ return detectModeInBaseDir(baseDir);
3051
+ }
3052
+ async function detectModeInBaseDir(baseDir) {
3053
+ let symlinkCount = 0;
3054
+ let copyCount = 0;
3055
+ for (const category of Object.values(CATEGORY_SUBDIRS)) {
3056
+ const categoryDir = join5(baseDir, category);
3057
+ if (!existsSync3(categoryDir)) continue;
3058
+ try {
3059
+ const entries = await readdir4(categoryDir);
3060
+ for (const name of entries) {
3061
+ const entryPath = join5(categoryDir, name);
3062
+ try {
3063
+ const stats = await lstat2(entryPath);
3064
+ if (stats.isSymbolicLink()) {
3065
+ symlinkCount++;
3066
+ } else if (stats.isDirectory()) {
3067
+ copyCount++;
3068
+ }
3069
+ } catch {
3070
+ }
3071
+ }
3072
+ } catch {
3073
+ }
3074
+ }
3075
+ if (symlinkCount === 0 && copyCount === 0) return null;
3076
+ return symlinkCount >= copyCount ? "symlink" : "copy";
3077
+ }
3078
+ async function skillUpdateCommand(options) {
3079
+ try {
3080
+ p6.intro(chalk17.blue("Update Skills"));
3081
+ const bundledDir = getBundledAssetsDir();
3082
+ if (!bundledDir) {
3083
+ p6.log.error("Bundled assets not found. Are you running from the package?");
3084
+ process.exit(1);
3085
+ }
3086
+ const configDir = getDefaultConfigDir();
3087
+ p6.log.step("Updating config directory from bundle...");
3088
+ const seedResult = await updateBundledAssets(configDir, bundledDir);
3089
+ for (const name of seedResult.seeded) {
3090
+ p6.log.info(` Updated: ${name}`);
3091
+ }
3092
+ if (seedResult.skipped.length > 0) {
3093
+ p6.log.info(chalk17.gray(` Preserved ${seedResult.skipped.length} custom asset(s)`));
3094
+ }
3095
+ const discovered = await discoverAllAssets(configDir);
3096
+ const bundledAssetNames = new Set(seedResult.seeded.map((s2) => s2.split("/")[1]));
3097
+ const bundledAssets = [
3098
+ ...discovered.agents.filter((a) => bundledAssetNames.has(a.name)),
3099
+ ...discovered.skills.filter((a) => bundledAssetNames.has(a.name))
3100
+ ];
3101
+ if (bundledAssets.length === 0) {
3102
+ p6.log.warn("No assets to update.");
3103
+ p6.outro("Done.");
3104
+ return;
3105
+ }
3106
+ const repoPaths = [];
3107
+ if (options.all) {
3108
+ const config = loadConfigFile();
3109
+ if (config.thoughts?.repoMappings) {
3110
+ for (const repoPath of Object.keys(config.thoughts.repoMappings)) {
3111
+ if (existsSync3(repoPath)) {
3112
+ repoPaths.push(repoPath);
3113
+ } else {
3114
+ p6.log.warn(`Skipping (not found): ${repoPath}`);
3115
+ }
3116
+ }
3117
+ }
3118
+ const cwd = process.cwd();
3119
+ if (!repoPaths.includes(cwd) && existsSync3(join5(cwd, ".git"))) {
3120
+ repoPaths.push(cwd);
3121
+ }
3122
+ } else {
3123
+ repoPaths.push(process.cwd());
3124
+ }
3125
+ p6.log.step("Detecting existing installations...");
3126
+ const targets = await detectInstallTargets(repoPaths);
3127
+ if (targets.length === 0) {
3128
+ p6.log.warn("No existing installations detected. Run `thc skill install` first.");
3129
+ p6.outro("Done.");
3130
+ return;
3131
+ }
3132
+ for (const t of targets) {
3133
+ const scopeLabel = t.scope === "global" ? "global" : t.cwd;
3134
+ p6.log.info(chalk17.gray(` ${agents[t.agentType].displayName}: ${scopeLabel} (${t.mode})`));
3135
+ }
3136
+ const s = p6.spinner();
3137
+ s.start("Installing updated assets...");
2701
3138
  let totalInstalled = 0;
2702
3139
  let totalFailed = 0;
2703
- const s = p4.spinner();
2704
- s.start("Installing assets...");
2705
- for (const asset of assetsToInstall) {
2706
- for (const agentType of selectedAgents) {
2707
- const result = await installAssetForAgent(asset, agentType, { scope, cwd, mode });
3140
+ for (const target of targets) {
3141
+ for (const asset of bundledAssets) {
3142
+ const result = await installAssetForAgent(asset, target.agentType, {
3143
+ scope: target.scope,
3144
+ cwd: target.cwd,
3145
+ mode: target.mode
3146
+ });
2708
3147
  if (result.success) {
2709
3148
  totalInstalled++;
2710
- if (result.symlinkFailed) {
2711
- p4.log.warn(
2712
- `${asset.name} \u2192 ${agents[agentType].displayName}: symlink failed, copied instead`
2713
- );
2714
- }
2715
3149
  } else {
2716
3150
  totalFailed++;
2717
- p4.log.warn(`Failed: ${asset.name} \u2192 ${agents[agentType].displayName}: ${result.error}`);
3151
+ if (result.error) {
3152
+ p6.log.warn(
3153
+ `Failed: ${asset.name} \u2192 ${agents[target.agentType].displayName}: ${result.error}`
3154
+ );
3155
+ }
2718
3156
  }
2719
3157
  }
2720
3158
  }
2721
- s.stop("Installation complete.");
2722
- const agentNames = selectedAgents.map((a) => agents[a].displayName).join(", ");
2723
- let message = `Installed ${totalInstalled} asset(s) to ${agentNames}`;
3159
+ s.stop("Update complete.");
3160
+ const uniqueRepos = new Set(targets.filter((t) => t.scope === "project").map((t) => t.cwd));
3161
+ const hasGlobal = targets.some((t) => t.scope === "global");
3162
+ let message = `Updated ${totalInstalled} asset(s)`;
3163
+ if (uniqueRepos.size > 0) {
3164
+ message += ` in ${uniqueRepos.size} repo(s)`;
3165
+ }
3166
+ if (hasGlobal) {
3167
+ message += ` + global`;
3168
+ }
2724
3169
  if (totalFailed > 0) {
2725
- message += chalk15.red(` (${totalFailed} failed)`);
3170
+ message += chalk17.red(` (${totalFailed} failed)`);
2726
3171
  }
2727
- message += chalk15.gray(`
2728
- Scope: ${scope} | Mode: ${mode}`);
2729
- p4.outro(message);
3172
+ p6.outro(message);
2730
3173
  } catch (error) {
2731
- p4.log.error(`Error during agent init: ${error}`);
3174
+ p6.log.error(`Error during skill update: ${error}`);
2732
3175
  process.exit(1);
2733
3176
  }
2734
3177
  }
2735
3178
 
2736
3179
  // src/commands/agent.ts
2737
- function agentCommand(program) {
2738
- const agent = program.command("agent").description("Manage coding agent configuration");
2739
- agent.command("init").description("Initialize coding agent configuration in current directory").option("--target <agents...>", "Target agents (e.g., claude-code codebuddy)").option("-g, --global", "Install to global scope").option("--mode <mode>", "Installation mode: symlink or copy (default: symlink)").option("--source <path>", "Source directory for assets").option("--force", "Force overwrite of existing installations").option("--all", "Install all assets without prompting").action(async (options) => {
3180
+ function skillCommand(program) {
3181
+ const skill = program.command("skill").description("Manage skill and agent asset installation");
3182
+ skill.command("install").description("Install skills and agent configs to target agent directories").option("--target <agents...>", "Target agents (e.g., claude-code codebuddy)").option("-g, --global", "Install to global scope").option("--mode <mode>", "Installation mode: symlink or copy (default: symlink)").option("--force", "Force overwrite of existing installations").action(async (options) => {
2740
3183
  const agentTypes = options.target?.map((a) => {
2741
3184
  if (!isValidAgentType(a)) {
2742
3185
  console.error(`Unknown agent: ${a}`);
@@ -2756,22 +3199,23 @@ function agentCommand(program) {
2756
3199
  agents: agentTypes,
2757
3200
  scope: options.global ? "global" : void 0,
2758
3201
  mode,
2759
- source: options.source,
2760
- force: options.force,
2761
- all: options.all
3202
+ force: options.force
2762
3203
  });
2763
3204
  });
3205
+ skill.command("update").description("Update skills from package bundle and refresh installations").option("--all", "Update all registered repos (from config repoMappings)").action(async (options) => {
3206
+ await skillUpdateCommand({ all: options.all });
3207
+ });
2764
3208
  }
2765
3209
 
2766
3210
  // src/commands/metadata/metadata.ts
2767
- import path15 from "path";
3211
+ import path16 from "path";
2768
3212
  function getGitInfo() {
2769
3213
  if (!isGitRepo()) {
2770
3214
  return null;
2771
3215
  }
2772
3216
  try {
2773
3217
  const repoRoot = getRepoRoot();
2774
- const repoName = path15.basename(repoRoot);
3218
+ const repoName = path16.basename(repoRoot);
2775
3219
  const branch = getCurrentBranch();
2776
3220
  const commit = getCurrentCommit();
2777
3221
  return { repoRoot, repoName, branch, commit };
@@ -2825,8 +3269,8 @@ function metadataCommand(program) {
2825
3269
 
2826
3270
  // src/commands/worktree/add.ts
2827
3271
  import fs14 from "fs";
2828
- import path17 from "path";
2829
- import chalk16 from "chalk";
3272
+ import path18 from "path";
3273
+ import chalk18 from "chalk";
2830
3274
 
2831
3275
  // src/tmux.ts
2832
3276
  import { execFileSync as execFileSync3 } from "child_process";
@@ -2886,10 +3330,11 @@ function tmuxKillSession(sessionName) {
2886
3330
 
2887
3331
  // src/agent-config.ts
2888
3332
  import fs13 from "fs";
2889
- import path16 from "path";
3333
+ import path17 from "path";
2890
3334
  function detectAgentConfigDirs(sourceDir) {
2891
3335
  const uniqueDirs = [...new Set(Object.values(agents).map((a) => a.configDir))];
2892
- return uniqueDirs.filter((dir) => fs13.existsSync(path16.join(sourceDir, dir)));
3336
+ uniqueDirs.push(CANONICAL_DIR);
3337
+ return uniqueDirs.filter((dir) => fs13.existsSync(path17.join(sourceDir, dir)));
2893
3338
  }
2894
3339
  function symlinkOrCopy(srcPath, destPath, linkTarget) {
2895
3340
  try {
@@ -2904,8 +3349,8 @@ function symlinkOrCopy(srcPath, destPath, linkTarget) {
2904
3349
  function copyDirWithSymlinkHandling(srcDir, destDir) {
2905
3350
  fs13.mkdirSync(destDir, { recursive: true });
2906
3351
  for (const entry of fs13.readdirSync(srcDir, { withFileTypes: true })) {
2907
- const srcPath = path16.join(srcDir, entry.name);
2908
- const destPath = path16.join(destDir, entry.name);
3352
+ const srcPath = path17.join(srcDir, entry.name);
3353
+ const destPath = path17.join(destDir, entry.name);
2909
3354
  if (entry.isSymbolicLink()) {
2910
3355
  symlinkOrCopy(srcPath, destPath, fs13.readlinkSync(srcPath));
2911
3356
  } else if (entry.isDirectory()) {
@@ -2921,7 +3366,7 @@ function copyAgentConfigDirs(options) {
2921
3366
  const skipped = [];
2922
3367
  for (const dirName of detectAgentConfigDirs(sourceDir)) {
2923
3368
  try {
2924
- copyDirWithSymlinkHandling(path16.join(sourceDir, dirName), path16.join(targetDir, dirName));
3369
+ copyDirWithSymlinkHandling(path17.join(sourceDir, dirName), path17.join(targetDir, dirName));
2925
3370
  copied.push(dirName);
2926
3371
  } catch {
2927
3372
  skipped.push(dirName);
@@ -2935,21 +3380,21 @@ async function worktreeAddCommand(name, options) {
2935
3380
  try {
2936
3381
  validateWorktreeHandle(name);
2937
3382
  if (!isGitRepo()) {
2938
- console.error(chalk16.red("Error: not in a git repository"));
3383
+ console.error(chalk18.red("Error: not in a git repository"));
2939
3384
  process.exit(1);
2940
3385
  }
2941
3386
  const mainRoot = getMainWorktreeRoot();
2942
3387
  const baseDir = getWorktreesBaseDir(mainRoot);
2943
- const worktreePath = options.path ? path17.resolve(options.path) : path17.join(baseDir, name);
3388
+ const worktreePath = options.path ? path18.resolve(options.path) : path18.join(baseDir, name);
2944
3389
  const branch = options.detached ? "" : options.branch ?? name;
2945
3390
  const sessionName = sessionNameForHandle(name);
2946
- fs14.mkdirSync(path17.dirname(worktreePath), { recursive: true });
3391
+ fs14.mkdirSync(path18.dirname(worktreePath), { recursive: true });
2947
3392
  const tmuxAvailable = isTmuxAvailable();
2948
3393
  if (tmuxAvailable) {
2949
3394
  const sessionCandidates = allSessionNamesForHandle(name);
2950
3395
  const existingSession = sessionCandidates.find((s) => tmuxHasSession(s));
2951
3396
  if (existingSession) {
2952
- console.error(chalk16.red(`Error: tmux session already exists: ${existingSession}`));
3397
+ console.error(chalk18.red(`Error: tmux session already exists: ${existingSession}`));
2953
3398
  process.exit(1);
2954
3399
  }
2955
3400
  }
@@ -2993,14 +3438,14 @@ async function worktreeAddCommand(name, options) {
2993
3438
  if (tmuxAvailable) {
2994
3439
  tmuxNewSession(sessionName, worktreePath);
2995
3440
  } else {
2996
- console.log(chalk16.yellow("Warning: tmux not found, skipping session creation"));
3441
+ console.log(chalk18.yellow("Warning: tmux not found, skipping session creation"));
2997
3442
  }
2998
3443
  const configResult = copyAgentConfigDirs({
2999
3444
  sourceDir: mainRoot,
3000
3445
  targetDir: worktreePath
3001
3446
  });
3002
3447
  if (configResult.copied.length > 0) {
3003
- console.log(chalk16.gray(`Copied config: ${configResult.copied.join(", ")}`));
3448
+ console.log(chalk18.gray(`Copied config: ${configResult.copied.join(", ")}`));
3004
3449
  }
3005
3450
  if (options.thoughts !== false) {
3006
3451
  initializeWorktreeThoughts(mainRoot, worktreePath);
@@ -3029,27 +3474,27 @@ async function worktreeAddCommand(name, options) {
3029
3474
  true
3030
3475
  );
3031
3476
  }
3032
- console.log(chalk16.green("\n\u2713 Worktree created"));
3033
- console.log(chalk16.gray(`Path: ${worktreePath}`));
3477
+ console.log(chalk18.green("\n\u2713 Worktree created"));
3478
+ console.log(chalk18.gray(`Path: ${worktreePath}`));
3034
3479
  if (tmuxAvailable) {
3035
- console.log(chalk16.gray(`Tmux session: ${sessionName}`));
3036
- console.log(chalk16.gray(`Attach: tmux attach -t ${sessionName}`));
3480
+ console.log(chalk18.gray(`Tmux session: ${sessionName}`));
3481
+ console.log(chalk18.gray(`Attach: tmux attach -t ${sessionName}`));
3037
3482
  }
3038
3483
  } catch (error) {
3039
- console.error(chalk16.red(`Error: ${error.message}`));
3484
+ console.error(chalk18.red(`Error: ${error.message}`));
3040
3485
  process.exit(1);
3041
3486
  }
3042
3487
  }
3043
3488
  function initializeWorktreeThoughts(mainRoot, worktreePath) {
3044
3489
  const config = loadThoughtsConfig({});
3045
3490
  if (!config) {
3046
- console.log(chalk16.yellow("Thoughts not configured globally, skipping"));
3491
+ console.log(chalk18.yellow("Thoughts not configured globally, skipping"));
3047
3492
  return;
3048
3493
  }
3049
3494
  const mainRepoMapping = config.repoMappings[mainRoot];
3050
3495
  const mappedName = getRepoNameFromMapping(mainRepoMapping);
3051
3496
  if (!mappedName) {
3052
- console.log(chalk16.yellow("Main repo not configured for thoughts, skipping"));
3497
+ console.log(chalk18.yellow("Main repo not configured for thoughts, skipping"));
3053
3498
  return;
3054
3499
  }
3055
3500
  config.repoMappings[worktreePath] = mainRepoMapping;
@@ -3064,42 +3509,42 @@ function initializeWorktreeThoughts(mainRoot, worktreePath) {
3064
3509
  createSearchable: true,
3065
3510
  setupHooks: true
3066
3511
  });
3067
- console.log(chalk16.gray("Thoughts initialized"));
3512
+ console.log(chalk18.gray("Thoughts initialized"));
3068
3513
  if (result.hooksUpdated.length > 0) {
3069
- console.log(chalk16.gray(`Updated git hooks: ${result.hooksUpdated.join(", ")}`));
3514
+ console.log(chalk18.gray(`Updated git hooks: ${result.hooksUpdated.join(", ")}`));
3070
3515
  }
3071
3516
  if (pullThoughtsFromRemote(profileConfig.thoughtsRepo)) {
3072
- console.log(chalk16.gray("Pulled latest thoughts from remote"));
3517
+ console.log(chalk18.gray("Pulled latest thoughts from remote"));
3073
3518
  }
3074
3519
  }
3075
3520
 
3076
3521
  // src/commands/worktree/list.ts
3077
- import path18 from "path";
3078
- import chalk17 from "chalk";
3522
+ import path19 from "path";
3523
+ import chalk19 from "chalk";
3079
3524
  async function worktreeListCommand(options) {
3080
3525
  try {
3081
3526
  if (!isGitRepo()) {
3082
- console.error(chalk17.red("Error: not in a git repository"));
3527
+ console.error(chalk19.red("Error: not in a git repository"));
3083
3528
  process.exit(1);
3084
3529
  }
3085
3530
  const mainRoot = getMainWorktreeRoot();
3086
- const baseDir = path18.resolve(getWorktreesBaseDir(mainRoot));
3087
- const cwd = path18.resolve(process.cwd());
3531
+ const baseDir = path19.resolve(getWorktreesBaseDir(mainRoot));
3532
+ const cwd = path19.resolve(process.cwd());
3088
3533
  const entries = parseWorktreeListPorcelain(
3089
3534
  runGitCommand(["worktree", "list", "--porcelain"], { cwd: mainRoot })
3090
3535
  );
3091
3536
  const sessions = new Set(listTmuxSessions());
3092
3537
  const filtered = options.all ? entries : entries.filter((e) => {
3093
- const p5 = path18.resolve(e.worktreePath);
3094
- return p5 === path18.resolve(mainRoot) || p5.startsWith(baseDir + path18.sep);
3538
+ const p7 = path19.resolve(e.worktreePath);
3539
+ return p7 === path19.resolve(mainRoot) || p7.startsWith(baseDir + path19.sep);
3095
3540
  });
3096
3541
  if (filtered.length === 0) {
3097
- console.log(chalk17.gray("No worktrees found."));
3542
+ console.log(chalk19.gray("No worktrees found."));
3098
3543
  return;
3099
3544
  }
3100
3545
  const rows = filtered.map((e) => {
3101
- const name = path18.basename(e.worktreePath);
3102
- const isCurrent = path18.resolve(e.worktreePath) === cwd;
3546
+ const name = path19.basename(e.worktreePath);
3547
+ const isCurrent = path19.resolve(e.worktreePath) === cwd;
3103
3548
  return {
3104
3549
  name: isCurrent ? `* ${name}` : ` ${name}`,
3105
3550
  branch: e.branch,
@@ -3114,24 +3559,24 @@ async function worktreeListCommand(options) {
3114
3559
  tmux: Math.max("TMUX".length, ...rows.map((r) => r.tmux.length))
3115
3560
  };
3116
3561
  const header = `${" NAME".padEnd(colWidths.name)} ${"BRANCH".padEnd(colWidths.branch)} ${"TMUX".padEnd(colWidths.tmux)} PATH`;
3117
- console.log(chalk17.blue(header));
3562
+ console.log(chalk19.blue(header));
3118
3563
  for (const row of rows) {
3119
3564
  const line = `${row.name.padEnd(colWidths.name)} ${row.branch.padEnd(colWidths.branch)} ${row.tmux.padEnd(colWidths.tmux)} ${row.path}`;
3120
- console.log(row.isCurrent ? chalk17.green(line) : line);
3565
+ console.log(row.isCurrent ? chalk19.green(line) : line);
3121
3566
  }
3122
3567
  } catch (error) {
3123
- console.error(chalk17.red(`Error: ${error.message}`));
3568
+ console.error(chalk19.red(`Error: ${error.message}`));
3124
3569
  process.exit(1);
3125
3570
  }
3126
3571
  }
3127
3572
 
3128
3573
  // src/commands/worktree/merge.ts
3129
- import path20 from "path";
3130
- import chalk19 from "chalk";
3574
+ import path21 from "path";
3575
+ import chalk21 from "chalk";
3131
3576
 
3132
3577
  // src/commands/worktree/utils.ts
3133
- import path19 from "path";
3134
- import chalk18 from "chalk";
3578
+ import path20 from "path";
3579
+ import chalk20 from "chalk";
3135
3580
  function cleanupWorktreeThoughts(wtPath, options = {}) {
3136
3581
  const config = loadThoughtsConfig({});
3137
3582
  if (!config || !config.repoMappings[wtPath]) {
@@ -3139,7 +3584,7 @@ function cleanupWorktreeThoughts(wtPath, options = {}) {
3139
3584
  }
3140
3585
  try {
3141
3586
  if (options.verbose) {
3142
- console.log(chalk18.gray("Cleaning up thoughts directory..."));
3587
+ console.log(chalk20.gray("Cleaning up thoughts directory..."));
3143
3588
  }
3144
3589
  const result = cleanupThoughtsDirectory({
3145
3590
  repoPath: wtPath,
@@ -3151,16 +3596,16 @@ function cleanupWorktreeThoughts(wtPath, options = {}) {
3151
3596
  saveThoughtsConfig(config, {});
3152
3597
  }
3153
3598
  if (result.thoughtsRemoved && options.verbose) {
3154
- console.log(chalk18.gray("\u2713 Thoughts directory cleaned up"));
3599
+ console.log(chalk20.gray("\u2713 Thoughts directory cleaned up"));
3155
3600
  }
3156
3601
  } catch (error) {
3157
3602
  if (options.verbose) {
3158
- console.log(chalk18.yellow(`Warning: Could not clean up thoughts: ${error.message}`));
3603
+ console.log(chalk20.yellow(`Warning: Could not clean up thoughts: ${error.message}`));
3159
3604
  }
3160
3605
  }
3161
3606
  }
3162
3607
  function cleanupWorktreeTmuxSession(wtPath) {
3163
- const handle = path19.basename(wtPath);
3608
+ const handle = path20.basename(wtPath);
3164
3609
  const sessionNames = allSessionNamesForHandle(handle);
3165
3610
  for (const s of sessionNames) {
3166
3611
  tmuxKillSession(s);
@@ -3194,27 +3639,27 @@ function deleteWorktreeBranch(branch, mainRoot, options = {}) {
3194
3639
  async function worktreeMergeCommand(name, options) {
3195
3640
  try {
3196
3641
  if (!isGitRepo()) {
3197
- console.error(chalk19.red("Error: not in a git repository"));
3642
+ console.error(chalk21.red("Error: not in a git repository"));
3198
3643
  process.exit(1);
3199
3644
  }
3200
3645
  const mainRoot = getMainWorktreeRoot();
3201
3646
  const wtEntry = findWorktree(name, mainRoot);
3202
- const wtPath = path20.resolve(wtEntry.worktreePath);
3203
- if (wtPath === path20.resolve(mainRoot)) {
3204
- console.error(chalk19.red("Error: refusing to merge/remove the main worktree"));
3647
+ const wtPath = path21.resolve(wtEntry.worktreePath);
3648
+ if (wtPath === path21.resolve(mainRoot)) {
3649
+ console.error(chalk21.red("Error: refusing to merge/remove the main worktree"));
3205
3650
  process.exit(1);
3206
3651
  }
3207
3652
  if (wtEntry.detached || wtEntry.branch === "(detached)") {
3208
- console.error(chalk19.red("Error: cannot merge a detached worktree"));
3653
+ console.error(chalk21.red("Error: cannot merge a detached worktree"));
3209
3654
  process.exit(1);
3210
3655
  }
3211
3656
  const targetBranch = options.into ?? runGitCommand(["branch", "--show-current"], { cwd: mainRoot });
3212
3657
  if (!targetBranch) {
3213
- console.error(chalk19.red("Error: could not determine target branch. Use --into <branch>."));
3658
+ console.error(chalk21.red("Error: could not determine target branch. Use --into <branch>."));
3214
3659
  process.exit(1);
3215
3660
  }
3216
3661
  if (targetBranch === wtEntry.branch) {
3217
- console.error(chalk19.red("Error: source and target branch are the same"));
3662
+ console.error(chalk21.red("Error: source and target branch are the same"));
3218
3663
  process.exit(1);
3219
3664
  }
3220
3665
  const hooksConfig = loadHooksConfig(mainRoot);
@@ -3245,13 +3690,13 @@ async function worktreeMergeCommand(name, options) {
3245
3690
  cleanupWorktreeThoughts(wtPath, { force: options.force, verbose: true });
3246
3691
  if (!options.force && hasUncommittedChanges(wtEntry.worktreePath)) {
3247
3692
  console.error(
3248
- chalk19.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3693
+ chalk21.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3249
3694
  );
3250
3695
  process.exit(1);
3251
3696
  }
3252
- console.log(chalk19.blue(`Rebasing ${wtEntry.branch} onto ${targetBranch}...`));
3697
+ console.log(chalk21.blue(`Rebasing ${wtEntry.branch} onto ${targetBranch}...`));
3253
3698
  runGitCommandOrThrow(["rebase", targetBranch], { cwd: wtEntry.worktreePath });
3254
- console.log(chalk19.blue(`Fast-forward merging into ${targetBranch}...`));
3699
+ console.log(chalk21.blue(`Fast-forward merging into ${targetBranch}...`));
3255
3700
  runGitCommandOrThrow(["switch", targetBranch], { cwd: mainRoot });
3256
3701
  runGitCommandOrThrow(["merge", "--ff-only", wtEntry.branch], { cwd: mainRoot });
3257
3702
  if (!options.keepSession) {
@@ -3293,35 +3738,35 @@ async function worktreeMergeCommand(name, options) {
3293
3738
  true
3294
3739
  );
3295
3740
  }
3296
- console.log(chalk19.green("\u2713 Merged and cleaned up"));
3741
+ console.log(chalk21.green("\u2713 Merged and cleaned up"));
3297
3742
  } catch (error) {
3298
- console.error(chalk19.red(`Error: ${error.message}`));
3743
+ console.error(chalk21.red(`Error: ${error.message}`));
3299
3744
  process.exit(1);
3300
3745
  }
3301
3746
  }
3302
3747
 
3303
3748
  // src/commands/worktree/remove.ts
3304
- import path21 from "path";
3305
- import chalk20 from "chalk";
3749
+ import path22 from "path";
3750
+ import chalk22 from "chalk";
3306
3751
  async function worktreeRemoveCommand(name, options) {
3307
3752
  try {
3308
3753
  if (!isGitRepo()) {
3309
- console.error(chalk20.red("Error: not in a git repository"));
3754
+ console.error(chalk22.red("Error: not in a git repository"));
3310
3755
  process.exit(1);
3311
3756
  }
3312
3757
  const mainRoot = getMainWorktreeRoot();
3313
3758
  const wtEntry = findWorktree(name, mainRoot);
3314
- const wtPath = path21.resolve(wtEntry.worktreePath);
3759
+ const wtPath = path22.resolve(wtEntry.worktreePath);
3315
3760
  const hasBranch = !wtEntry.detached && wtEntry.branch !== "(detached)";
3316
- if (wtPath === path21.resolve(mainRoot)) {
3317
- console.error(chalk20.red("Error: refusing to remove the main worktree"));
3761
+ if (wtPath === path22.resolve(mainRoot)) {
3762
+ console.error(chalk22.red("Error: refusing to remove the main worktree"));
3318
3763
  process.exit(1);
3319
3764
  }
3320
3765
  if (!options.force && hasBranch) {
3321
3766
  const defaultBranch = getDefaultBranch(mainRoot);
3322
3767
  if (hasUnmergedCommits(wtEntry.branch, defaultBranch, mainRoot)) {
3323
3768
  console.error(
3324
- chalk20.red(
3769
+ chalk22.red(
3325
3770
  `Error: branch '${wtEntry.branch}' has commits not merged into '${defaultBranch}'. Merge first or use --force to discard.`
3326
3771
  )
3327
3772
  );
@@ -3354,19 +3799,19 @@ async function worktreeRemoveCommand(name, options) {
3354
3799
  cleanupWorktreeThoughts(wtPath, { force: options.force, verbose: true });
3355
3800
  if (!options.force && hasUncommittedChanges(wtEntry.worktreePath)) {
3356
3801
  console.error(
3357
- chalk20.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3802
+ chalk22.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3358
3803
  );
3359
3804
  process.exit(1);
3360
3805
  }
3361
3806
  cleanupWorktreeTmuxSession(wtPath);
3362
- console.log(chalk20.gray("Removing git worktree..."));
3807
+ console.log(chalk22.gray("Removing git worktree..."));
3363
3808
  removeGitWorktree(wtPath, mainRoot, { force: options.force });
3364
3809
  if (hasBranch) {
3365
- console.log(chalk20.gray(`Deleting branch '${wtEntry.branch}'...`));
3810
+ console.log(chalk22.gray(`Deleting branch '${wtEntry.branch}'...`));
3366
3811
  try {
3367
3812
  deleteWorktreeBranch(wtEntry.branch, mainRoot, { force: options.force });
3368
3813
  } catch (error) {
3369
- console.log(chalk20.yellow(`Warning: ${error.message}`));
3814
+ console.log(chalk22.yellow(`Warning: ${error.message}`));
3370
3815
  }
3371
3816
  }
3372
3817
  const postHooks = getHooksForEvent(hooksConfig, "PostWorktreeRemove");
@@ -3391,9 +3836,9 @@ async function worktreeRemoveCommand(name, options) {
3391
3836
  true
3392
3837
  );
3393
3838
  }
3394
- console.log(chalk20.green("\u2713 Worktree removed"));
3839
+ console.log(chalk22.green("\u2713 Worktree removed"));
3395
3840
  } catch (error) {
3396
- console.error(chalk20.red(`Error: ${error.message}`));
3841
+ console.error(chalk22.red(`Error: ${error.message}`));
3397
3842
  process.exit(1);
3398
3843
  }
3399
3844
  }
@@ -3416,27 +3861,27 @@ function worktreeCommand(program) {
3416
3861
 
3417
3862
  // src/commands/hooks/init.ts
3418
3863
  import fs15 from "fs";
3419
- import path22 from "path";
3420
- import chalk21 from "chalk";
3864
+ import path23 from "path";
3865
+ import chalk23 from "chalk";
3421
3866
  import { fileURLToPath as fileURLToPath2 } from "url";
3422
- import { dirname as dirname2 } from "path";
3867
+ import { dirname as dirname3 } from "path";
3423
3868
  var __filename2 = fileURLToPath2(import.meta.url);
3424
- var __dirname3 = dirname2(__filename2);
3869
+ var __dirname3 = dirname3(__filename2);
3425
3870
  async function hooksInitCommand() {
3426
3871
  try {
3427
3872
  const repoPath = process.cwd();
3428
- const configDir = path22.join(repoPath, HOOKS_CONFIG_DIR);
3429
- const configPath = path22.join(repoPath, HOOKS_CONFIG_FILE);
3873
+ const configDir = path23.join(repoPath, HOOKS_CONFIG_DIR);
3874
+ const configPath = path23.join(repoPath, HOOKS_CONFIG_FILE);
3430
3875
  if (fs15.existsSync(configPath)) {
3431
- console.log(chalk21.yellow(`${HOOKS_CONFIG_FILE} already exists.`));
3432
- console.log(chalk21.gray(`Edit the file directly: ${configPath}`));
3876
+ console.log(chalk23.yellow(`${HOOKS_CONFIG_FILE} already exists.`));
3877
+ console.log(chalk23.gray(`Edit the file directly: ${configPath}`));
3433
3878
  return;
3434
3879
  }
3435
3880
  const possiblePaths = [
3436
3881
  // When running from built dist: one level up from dist/
3437
- path22.resolve(__dirname3, "..", ".thought-cabinet/hooks.example.json"),
3882
+ path23.resolve(__dirname3, "..", ".thought-cabinet/hooks.example.json"),
3438
3883
  // When installed via npm: one level up from dist/
3439
- path22.resolve(__dirname3, "../..", ".thought-cabinet/hooks.example.json")
3884
+ path23.resolve(__dirname3, "../..", ".thought-cabinet/hooks.example.json")
3440
3885
  ];
3441
3886
  let examplePath = null;
3442
3887
  for (const candidatePath of possiblePaths) {
@@ -3446,16 +3891,16 @@ async function hooksInitCommand() {
3446
3891
  }
3447
3892
  }
3448
3893
  if (!examplePath) {
3449
- console.error(chalk21.red("Error: hooks.example.json not found in expected locations"));
3450
- console.log(chalk21.gray("Searched paths:"));
3451
- possiblePaths.forEach((p5) => console.log(chalk21.gray(` - ${p5}`)));
3894
+ console.error(chalk23.red("Error: hooks.example.json not found in expected locations"));
3895
+ console.log(chalk23.gray("Searched paths:"));
3896
+ possiblePaths.forEach((p7) => console.log(chalk23.gray(` - ${p7}`)));
3452
3897
  process.exit(1);
3453
3898
  }
3454
3899
  fs15.mkdirSync(configDir, { recursive: true });
3455
3900
  fs15.copyFileSync(examplePath, configPath);
3456
- console.log(chalk21.green(`Created ${HOOKS_CONFIG_FILE}`));
3901
+ console.log(chalk23.green(`Created ${HOOKS_CONFIG_FILE}`));
3457
3902
  } catch (error) {
3458
- console.error(chalk21.red(`Error during hooks init: ${error}`));
3903
+ console.error(chalk23.red(`Error during hooks init: ${error}`));
3459
3904
  process.exit(1);
3460
3905
  }
3461
3906
  }
@@ -3483,7 +3928,7 @@ function completionCommand(program) {
3483
3928
  import tabtab from "tabtab";
3484
3929
 
3485
3930
  // src/completion/providers.ts
3486
- import path23 from "path";
3931
+ import path24 from "path";
3487
3932
  function getProfileNames() {
3488
3933
  try {
3489
3934
  const config = loadConfigFile();
@@ -3505,9 +3950,9 @@ function getWorktreeNames() {
3505
3950
  const output = runGitCommand(["worktree", "list", "--porcelain"]);
3506
3951
  const entries = parseWorktreeListPorcelain(output);
3507
3952
  return entries.filter((e) => {
3508
- const p5 = path23.resolve(e.worktreePath);
3509
- return p5.startsWith(baseDir + path23.sep);
3510
- }).map((e) => path23.basename(e.worktreePath));
3953
+ const p7 = path24.resolve(e.worktreePath);
3954
+ return p7.startsWith(baseDir + path24.sep);
3955
+ }).map((e) => path24.basename(e.worktreePath));
3511
3956
  } catch {
3512
3957
  return [];
3513
3958
  }
@@ -3535,9 +3980,10 @@ var TOP_LEVEL_COMMANDS = [
3535
3980
  "status",
3536
3981
  "config",
3537
3982
  "prune",
3983
+ "migrate",
3538
3984
  "profile",
3539
3985
  "worktree",
3540
- "agent",
3986
+ "skill",
3541
3987
  "metadata",
3542
3988
  "hooks",
3543
3989
  "completion"
@@ -3545,7 +3991,7 @@ var TOP_LEVEL_COMMANDS = [
3545
3991
  var SUBCOMMANDS = {
3546
3992
  profile: ["create", "list", "show", "delete"],
3547
3993
  worktree: ["add", "list", "merge", "remove"],
3548
- agent: ["init"],
3994
+ skill: ["install", "update"],
3549
3995
  hooks: ["init"],
3550
3996
  completion: ["install", "uninstall"]
3551
3997
  };
@@ -3556,6 +4002,7 @@ var OPTIONS = {
3556
4002
  status: ["--config-file", "--fetch"],
3557
4003
  config: ["--edit", "--json", "--config-file"],
3558
4004
  prune: ["--apply", "--config-file"],
4005
+ migrate: ["--dry-run", "--config-file"],
3559
4006
  "profile create": ["--repo", "--repos-dir", "--global-dir", "--config-file"],
3560
4007
  "profile list": ["--json", "--config-file"],
3561
4008
  "profile show": ["--json", "--config-file"],
@@ -3564,7 +4011,8 @@ var OPTIONS = {
3564
4011
  "worktree list": ["--all"],
3565
4012
  "worktree merge": ["--into", "--force", "--keep-session", "--keep-worktree", "--keep-branch"],
3566
4013
  "worktree remove": ["--force"],
3567
- "agent init": ["--force", "--all", "--target", "--global", "--mode", "--source"]
4014
+ "skill install": ["--force", "--target", "--global", "--mode"],
4015
+ "skill update": ["--all"]
3568
4016
  };
3569
4017
  var DYNAMIC_ARGS = {
3570
4018
  "profile show": getProfileNames,
@@ -3649,7 +4097,7 @@ async function main() {
3649
4097
  "Thought Cabinet (thc) \u2014 CLI for structured AI coding workflows with filesystem-based memory and context management."
3650
4098
  ).version(version);
3651
4099
  thoughtsCommand(program);
3652
- agentCommand(program);
4100
+ skillCommand(program);
3653
4101
  metadataCommand(program);
3654
4102
  worktreeCommand(program);
3655
4103
  hooksCommand(program);