thought-cabinet 0.1.13 → 0.2.0

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(),
@@ -1971,32 +1989,365 @@ async function thoughtsPruneCommand(options) {
1971
1989
  }
1972
1990
  }
1973
1991
 
1974
- // src/commands/thoughts/profile/create.ts
1992
+ // src/commands/thoughts/migrate.ts
1993
+ import fs12 from "fs";
1994
+ import path14 from "path";
1975
1995
  import chalk11 from "chalk";
1976
1996
  import * as p2 from "@clack/prompts";
1997
+
1998
+ // src/commands/agent/registry.ts
1999
+ import { homedir } from "os";
2000
+ import { join } from "path";
2001
+ import { existsSync } from "fs";
2002
+ var home = homedir();
2003
+ var claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
2004
+ var agents = {
2005
+ "claude-code": {
2006
+ name: "claude-code",
2007
+ displayName: "Claude Code",
2008
+ configDir: ".claude",
2009
+ globalConfigDir: claudeHome,
2010
+ detectInstalled: async () => existsSync(claudeHome)
2011
+ },
2012
+ codebuddy: {
2013
+ name: "codebuddy",
2014
+ displayName: "CodeBuddy Code",
2015
+ configDir: ".codebuddy",
2016
+ globalConfigDir: join(home, ".codebuddy"),
2017
+ detectInstalled: async () => existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"))
2018
+ }
2019
+ };
2020
+ function getAllAgents() {
2021
+ return Object.values(agents);
2022
+ }
2023
+ function isValidAgentType(value) {
2024
+ return value in agents;
2025
+ }
2026
+
2027
+ // src/commands/agent/constants.ts
2028
+ var CANONICAL_DIR = ".thought-cabinet";
2029
+ var CATEGORY_SUBDIRS = {
2030
+ skills: "skills",
2031
+ commands: "commands",
2032
+ agents: "agents"
2033
+ };
2034
+
2035
+ // src/commands/thoughts/migrate.ts
2036
+ function planMigration(homeDir) {
2037
+ const home2 = homeDir || process.env.HOME || "";
2038
+ const newConfigDir = path14.join(home2, ".thought-cabinet");
2039
+ const legacyConfigDir = path14.join(home2, ".config", "thought-cabinet");
2040
+ const legacyConfigPath = path14.join(legacyConfigDir, "config.json");
2041
+ const newConfigPath = path14.join(newConfigDir, "config.json");
2042
+ if (fs12.existsSync(newConfigPath)) {
2043
+ return null;
2044
+ }
2045
+ if (!fs12.existsSync(legacyConfigPath)) {
2046
+ return null;
2047
+ }
2048
+ let config;
2049
+ try {
2050
+ config = JSON.parse(fs12.readFileSync(legacyConfigPath, "utf8"));
2051
+ } catch {
2052
+ return null;
2053
+ }
2054
+ const thoughts = config.thoughts;
2055
+ if (!thoughts) {
2056
+ return null;
2057
+ }
2058
+ const moves = [];
2059
+ moves.push({
2060
+ from: legacyConfigPath,
2061
+ to: path14.join(newConfigDir, "config.json"),
2062
+ label: "Config file"
2063
+ });
2064
+ for (const dir of ["agents", "skills"]) {
2065
+ const src = path14.join(legacyConfigDir, dir);
2066
+ if (fs12.existsSync(src)) {
2067
+ moves.push({
2068
+ from: src,
2069
+ to: path14.join(newConfigDir, dir),
2070
+ label: `${dir}/ directory`
2071
+ });
2072
+ }
2073
+ }
2074
+ if (thoughts.thoughtsRepo) {
2075
+ const expandedThoughtsRepo = expandPath(thoughts.thoughtsRepo);
2076
+ const newDefaultThoughtsRepo = path14.join(newConfigDir, "thoughts");
2077
+ if (fs12.existsSync(expandedThoughtsRepo) && expandedThoughtsRepo !== newDefaultThoughtsRepo && !expandedThoughtsRepo.startsWith(newConfigDir + path14.sep)) {
2078
+ moves.push({
2079
+ from: expandedThoughtsRepo,
2080
+ to: newDefaultThoughtsRepo,
2081
+ label: `Default thoughts repo (${thoughts.thoughtsRepo})`
2082
+ });
2083
+ }
2084
+ }
2085
+ if (thoughts.profiles) {
2086
+ for (const [name, profile] of Object.entries(thoughts.profiles)) {
2087
+ const expandedProfileRepo = expandPath(profile.thoughtsRepo);
2088
+ const newProfileRepo = path14.join(newConfigDir, `thoughts-${name}`);
2089
+ if (fs12.existsSync(expandedProfileRepo) && expandedProfileRepo !== newProfileRepo && !expandedProfileRepo.startsWith(newConfigDir + path14.sep)) {
2090
+ moves.push({
2091
+ from: expandedProfileRepo,
2092
+ to: newProfileRepo,
2093
+ label: `Profile "${name}" thoughts repo (${profile.thoughtsRepo})`
2094
+ });
2095
+ }
2096
+ }
2097
+ }
2098
+ const affectedRepos = thoughts.repoMappings ? Object.keys(thoughts.repoMappings) : [];
2099
+ return {
2100
+ moves,
2101
+ newConfigDir,
2102
+ legacyConfigDir,
2103
+ config,
2104
+ affectedRepos
2105
+ };
2106
+ }
2107
+ function executeMigration(plan) {
2108
+ fs12.mkdirSync(plan.newConfigDir, { recursive: true });
2109
+ for (const move of plan.moves) {
2110
+ const destDir = path14.dirname(move.to);
2111
+ fs12.mkdirSync(destDir, { recursive: true });
2112
+ fs12.renameSync(move.from, move.to);
2113
+ }
2114
+ const thoughts = plan.config.thoughts;
2115
+ if (thoughts) {
2116
+ thoughts.thoughtsRepo = path14.join(plan.newConfigDir, "thoughts");
2117
+ if (thoughts.profiles) {
2118
+ for (const [name, profile] of Object.entries(thoughts.profiles)) {
2119
+ profile.thoughtsRepo = path14.join(plan.newConfigDir, `thoughts-${name}`);
2120
+ }
2121
+ }
2122
+ }
2123
+ const newConfigPath = path14.join(plan.newConfigDir, "config.json");
2124
+ fs12.writeFileSync(newConfigPath, JSON.stringify(plan.config, null, 2));
2125
+ }
2126
+ function refreshRepoSymlinks(config, affectedRepos) {
2127
+ const refreshed = [];
2128
+ const skipped = [];
2129
+ for (const repoPath of affectedRepos) {
2130
+ if (!fs12.existsSync(repoPath)) {
2131
+ skipped.push(repoPath);
2132
+ continue;
2133
+ }
2134
+ const mapping = config.repoMappings[repoPath];
2135
+ const mappedName = getRepoNameFromMapping(mapping);
2136
+ if (!mappedName) {
2137
+ skipped.push(repoPath);
2138
+ continue;
2139
+ }
2140
+ const profileConfig = resolveProfileForRepo(
2141
+ {
2142
+ thoughtsRepo: config.thoughtsRepo,
2143
+ reposDir: config.reposDir,
2144
+ globalDir: config.globalDir,
2145
+ user: config.user,
2146
+ repoMappings: config.repoMappings,
2147
+ profiles: config.profiles
2148
+ },
2149
+ repoPath
2150
+ );
2151
+ createThoughtsDirectoryStructure(profileConfig, mappedName, config.user);
2152
+ setupThoughtsDirectory({
2153
+ repoPath,
2154
+ profileConfig,
2155
+ mappedName,
2156
+ user: config.user
2157
+ });
2158
+ refreshed.push(repoPath);
2159
+ }
2160
+ return { refreshed, skipped };
2161
+ }
2162
+ function refreshGlobalAgentSymlinks(legacyConfigDir, newConfigDir, agentList) {
2163
+ let refreshed = 0;
2164
+ const agents2 = [];
2165
+ const agentsToScan = agentList || getAllAgents();
2166
+ for (const agent of agentsToScan) {
2167
+ if (!agent.globalConfigDir) continue;
2168
+ let agentTouched = false;
2169
+ for (const category of Object.values(CATEGORY_SUBDIRS)) {
2170
+ const categoryDir = path14.join(agent.globalConfigDir, category);
2171
+ if (!fs12.existsSync(categoryDir)) continue;
2172
+ const entries = fs12.readdirSync(categoryDir, { withFileTypes: true });
2173
+ for (const entry of entries) {
2174
+ const entryPath = path14.join(categoryDir, entry.name);
2175
+ let stats;
2176
+ try {
2177
+ stats = fs12.lstatSync(entryPath);
2178
+ } catch {
2179
+ continue;
2180
+ }
2181
+ if (!stats.isSymbolicLink()) continue;
2182
+ const linkTarget = fs12.readlinkSync(entryPath);
2183
+ const resolvedTarget = path14.resolve(path14.dirname(entryPath), linkTarget);
2184
+ if (!resolvedTarget.startsWith(legacyConfigDir + path14.sep) && resolvedTarget !== legacyConfigDir) {
2185
+ continue;
2186
+ }
2187
+ const relativeToCfg = path14.relative(legacyConfigDir, resolvedTarget);
2188
+ const newTarget = path14.join(newConfigDir, relativeToCfg);
2189
+ if (!fs12.existsSync(newTarget)) continue;
2190
+ fs12.unlinkSync(entryPath);
2191
+ const newRelative = path14.relative(path14.dirname(entryPath), newTarget);
2192
+ fs12.symlinkSync(newRelative, entryPath);
2193
+ refreshed++;
2194
+ agentTouched = true;
2195
+ }
2196
+ }
2197
+ if (agentTouched) {
2198
+ agents2.push(agent.displayName);
2199
+ }
2200
+ }
2201
+ return { refreshed, agents: agents2 };
2202
+ }
2203
+ function previewGlobalAgentSymlinks(legacyConfigDir, agentList) {
2204
+ const entries = [];
2205
+ const agentsToScan = agentList || getAllAgents();
2206
+ for (const agent of agentsToScan) {
2207
+ if (!agent.globalConfigDir) continue;
2208
+ for (const category of Object.values(CATEGORY_SUBDIRS)) {
2209
+ const categoryDir = path14.join(agent.globalConfigDir, category);
2210
+ if (!fs12.existsSync(categoryDir)) continue;
2211
+ const dirEntries = fs12.readdirSync(categoryDir, { withFileTypes: true });
2212
+ for (const entry of dirEntries) {
2213
+ const entryPath = path14.join(categoryDir, entry.name);
2214
+ try {
2215
+ const stats = fs12.lstatSync(entryPath);
2216
+ if (!stats.isSymbolicLink()) continue;
2217
+ const linkTarget = fs12.readlinkSync(entryPath);
2218
+ const resolvedTarget = path14.resolve(path14.dirname(entryPath), linkTarget);
2219
+ if (resolvedTarget.startsWith(legacyConfigDir + path14.sep)) {
2220
+ entries.push(entryPath);
2221
+ }
2222
+ } catch {
2223
+ continue;
2224
+ }
2225
+ }
2226
+ }
2227
+ }
2228
+ return entries;
2229
+ }
2230
+ async function thoughtsMigrateCommand(options) {
2231
+ const newConfigDir = getDefaultConfigDir();
2232
+ const legacyConfigDir = getLegacyConfigDir();
2233
+ const newConfigPath = path14.join(newConfigDir, ConfigResolver.DEFAULT_CONFIG_FILE);
2234
+ const legacyConfigPath = path14.join(legacyConfigDir, ConfigResolver.DEFAULT_CONFIG_FILE);
2235
+ if (!fs12.existsSync(legacyConfigPath)) {
2236
+ if (fs12.existsSync(newConfigPath)) {
2237
+ p2.log.info("Configuration is already at the new location.");
2238
+ return;
2239
+ }
2240
+ p2.log.info("No configuration found to migrate.");
2241
+ return;
2242
+ }
2243
+ if (legacyConfigDir === newConfigDir) {
2244
+ p2.log.info("Configuration is already at the expected location.");
2245
+ return;
2246
+ }
2247
+ const plan = planMigration();
2248
+ if (!plan) {
2249
+ p2.log.info("Nothing to migrate.");
2250
+ return;
2251
+ }
2252
+ p2.intro(chalk11.blue("Migrate Thought Cabinet Configuration"));
2253
+ p2.log.step("Migration plan:");
2254
+ for (const move of plan.moves) {
2255
+ p2.log.message(` ${move.label}`);
2256
+ p2.log.message(chalk11.gray(` ${move.from} \u2192 ${move.to}`));
2257
+ }
2258
+ if (options.dryRun) {
2259
+ if (plan.affectedRepos.length > 0) {
2260
+ p2.log.step("Repo symlinks that would be refreshed:");
2261
+ for (const repo of plan.affectedRepos) {
2262
+ if (fs12.existsSync(repo)) {
2263
+ p2.log.message(chalk11.gray(` ${repo}/thoughts/`));
2264
+ } else {
2265
+ p2.log.message(chalk11.gray(` ${repo}/thoughts/ (skipped \u2014 repo not found)`));
2266
+ }
2267
+ }
2268
+ }
2269
+ const agentPreview = previewGlobalAgentSymlinks(plan.legacyConfigDir);
2270
+ if (agentPreview.length > 0) {
2271
+ p2.log.step("Global agent symlinks that would be refreshed:");
2272
+ for (const entry of agentPreview) {
2273
+ p2.log.message(chalk11.gray(` ${entry}`));
2274
+ }
2275
+ }
2276
+ p2.log.info("Dry run \u2014 no changes made.");
2277
+ return;
2278
+ }
2279
+ const confirm5 = await p2.confirm({
2280
+ message: "Proceed with migration?",
2281
+ initialValue: true
2282
+ });
2283
+ if (p2.isCancel(confirm5) || !confirm5) {
2284
+ p2.cancel("Migration cancelled.");
2285
+ return;
2286
+ }
2287
+ executeMigration(plan);
2288
+ for (const move of plan.moves) {
2289
+ p2.log.success(`Moved: ${move.label}`);
2290
+ }
2291
+ try {
2292
+ const remaining = fs12.readdirSync(plan.legacyConfigDir);
2293
+ if (remaining.length === 0) {
2294
+ fs12.rmdirSync(plan.legacyConfigDir);
2295
+ p2.log.info(`Removed empty directory: ${plan.legacyConfigDir}`);
2296
+ }
2297
+ } catch {
2298
+ }
2299
+ if (plan.affectedRepos.length > 0) {
2300
+ const config = loadThoughtsConfig({ configFile: path14.join(plan.newConfigDir, "config.json") });
2301
+ if (config) {
2302
+ p2.log.step("Refreshing thoughts symlinks in registered repos...");
2303
+ const repoResult = refreshRepoSymlinks(config, plan.affectedRepos);
2304
+ for (const repo of repoResult.refreshed) {
2305
+ p2.log.success(`Refreshed: ${repo}`);
2306
+ }
2307
+ for (const repo of repoResult.skipped) {
2308
+ p2.log.warn(`Skipped (not found): ${repo}`);
2309
+ }
2310
+ }
2311
+ }
2312
+ p2.log.step("Refreshing global agent symlinks...");
2313
+ const agentResult = refreshGlobalAgentSymlinks(plan.legacyConfigDir, plan.newConfigDir);
2314
+ if (agentResult.refreshed > 0) {
2315
+ p2.log.success(
2316
+ `Refreshed ${agentResult.refreshed} symlink(s) for: ${agentResult.agents.join(", ")}`
2317
+ );
2318
+ } else {
2319
+ p2.log.info("No global agent symlinks needed updating.");
2320
+ }
2321
+ p2.outro(chalk11.green("Migration complete!"));
2322
+ }
2323
+
2324
+ // src/commands/thoughts/profile/create.ts
2325
+ import chalk12 from "chalk";
2326
+ import path15 from "path";
2327
+ import * as p3 from "@clack/prompts";
1977
2328
  async function profileCreateCommand(profileName, options) {
1978
2329
  try {
1979
2330
  if (!options.repo || !options.reposDir || !options.globalDir) {
1980
2331
  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");
2332
+ p3.log.error("Not running in interactive terminal.");
2333
+ p3.log.info("Provide all options: --repo, --repos-dir, --global-dir");
1983
2334
  process.exit(1);
1984
2335
  }
1985
2336
  }
1986
2337
  const config = loadThoughtsConfig(options);
1987
2338
  if (!config) {
1988
- p2.log.error("Thoughts not configured.");
1989
- p2.log.info('Run "thoughtcabinet init" first to set up the base configuration.');
2339
+ p3.log.error("Thoughts not configured.");
2340
+ p3.log.info('Run "thoughtcabinet init" first to set up the base configuration.');
1990
2341
  process.exit(1);
1991
2342
  }
1992
2343
  const sanitizedName = sanitizeProfileName(profileName);
1993
2344
  if (sanitizedName !== profileName) {
1994
- p2.log.warn(`Profile name sanitized: "${profileName}" \u2192 "${sanitizedName}"`);
2345
+ p3.log.warn(`Profile name sanitized: "${profileName}" \u2192 "${sanitizedName}"`);
1995
2346
  }
1996
- p2.intro(chalk11.blue(`Creating Profile: ${sanitizedName}`));
2347
+ p3.intro(chalk12.blue(`Creating Profile: ${sanitizedName}`));
1997
2348
  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.");
2349
+ p3.log.error(`Profile "${sanitizedName}" already exists.`);
2350
+ p3.log.info("Use a different name or delete the existing profile first.");
2000
2351
  process.exit(1);
2001
2352
  }
2002
2353
  let thoughtsRepo;
@@ -2007,35 +2358,35 @@ async function profileCreateCommand(profileName, options) {
2007
2358
  reposDir = options.reposDir;
2008
2359
  globalDir = options.globalDir;
2009
2360
  } else {
2010
- const defaultRepo = getDefaultThoughtsRepo() + `-${sanitizedName}`;
2011
- p2.log.info("Specify the thoughts repository location for this profile.");
2012
- const repoInput = await p2.text({
2361
+ const defaultRepo = path15.join(getDefaultConfigDir(), `thoughts-${sanitizedName}`);
2362
+ p3.log.info("Specify the thoughts repository location for this profile.");
2363
+ const repoInput = await p3.text({
2013
2364
  message: "Thoughts repository:",
2014
2365
  initialValue: defaultRepo,
2015
2366
  placeholder: defaultRepo
2016
2367
  });
2017
- if (p2.isCancel(repoInput)) {
2018
- p2.cancel("Operation cancelled.");
2368
+ if (p3.isCancel(repoInput)) {
2369
+ p3.cancel("Operation cancelled.");
2019
2370
  process.exit(0);
2020
2371
  }
2021
2372
  thoughtsRepo = repoInput || defaultRepo;
2022
- const reposDirInput = await p2.text({
2373
+ const reposDirInput = await p3.text({
2023
2374
  message: "Repository-specific thoughts directory:",
2024
2375
  initialValue: "repos",
2025
2376
  placeholder: "repos"
2026
2377
  });
2027
- if (p2.isCancel(reposDirInput)) {
2028
- p2.cancel("Operation cancelled.");
2378
+ if (p3.isCancel(reposDirInput)) {
2379
+ p3.cancel("Operation cancelled.");
2029
2380
  process.exit(0);
2030
2381
  }
2031
2382
  reposDir = reposDirInput || "repos";
2032
- const globalDirInput = await p2.text({
2383
+ const globalDirInput = await p3.text({
2033
2384
  message: "Global thoughts directory:",
2034
2385
  initialValue: "global",
2035
2386
  placeholder: "global"
2036
2387
  });
2037
- if (p2.isCancel(globalDirInput)) {
2038
- p2.cancel("Operation cancelled.");
2388
+ if (p3.isCancel(globalDirInput)) {
2389
+ p3.cancel("Operation cancelled.");
2039
2390
  process.exit(0);
2040
2391
  }
2041
2392
  globalDir = globalDirInput || "global";
@@ -2050,56 +2401,56 @@ async function profileCreateCommand(profileName, options) {
2050
2401
  }
2051
2402
  config.profiles[sanitizedName] = profileConfig;
2052
2403
  saveThoughtsConfig(config, options);
2053
- p2.log.step("Initializing profile thoughts repository...");
2404
+ p3.log.step("Initializing profile thoughts repository...");
2054
2405
  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)}`,
2406
+ p3.log.success(`Profile "${sanitizedName}" created successfully!`);
2407
+ p3.note(
2408
+ `Name: ${chalk12.cyan(sanitizedName)}
2409
+ Thoughts repository: ${chalk12.cyan(thoughtsRepo)}
2410
+ Repos directory: ${chalk12.cyan(reposDir)}
2411
+ Global directory: ${chalk12.cyan(globalDir)}`,
2061
2412
  "Profile Configuration"
2062
2413
  );
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`)
2414
+ p3.outro(
2415
+ chalk12.gray("Next steps:\n") + chalk12.gray(` 1. Run "thoughtcabinet init --profile ${sanitizedName}" in a repository
2416
+ `) + chalk12.gray(` 2. Your thoughts will sync to the profile's repository`)
2066
2417
  );
2067
2418
  } catch (error) {
2068
- p2.log.error(`Error creating profile: ${error}`);
2419
+ p3.log.error(`Error creating profile: ${error}`);
2069
2420
  process.exit(1);
2070
2421
  }
2071
2422
  }
2072
2423
 
2073
2424
  // src/commands/thoughts/profile/list.ts
2074
- import chalk12 from "chalk";
2425
+ import chalk13 from "chalk";
2075
2426
  async function profileListCommand(options) {
2076
2427
  try {
2077
2428
  const config = loadThoughtsConfig(options);
2078
2429
  if (!config) {
2079
- console.error(chalk12.red("Error: Thoughts not configured."));
2430
+ console.error(chalk13.red("Error: Thoughts not configured."));
2080
2431
  process.exit(1);
2081
2432
  }
2082
2433
  if (options.json) {
2083
2434
  console.log(JSON.stringify(config.profiles || {}, null, 2));
2084
2435
  return;
2085
2436
  }
2086
- console.log(chalk12.blue("Thoughts Profiles"));
2087
- console.log(chalk12.gray("=".repeat(50)));
2437
+ console.log(chalk13.blue("Thoughts Profiles"));
2438
+ console.log(chalk13.gray("=".repeat(50)));
2088
2439
  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)}`);
2440
+ console.log(chalk13.yellow("Default Configuration:"));
2441
+ console.log(` Thoughts repository: ${chalk13.cyan(config.thoughtsRepo)}`);
2442
+ console.log(` Repos directory: ${chalk13.cyan(config.reposDir)}`);
2443
+ console.log(` Global directory: ${chalk13.cyan(config.globalDir)}`);
2093
2444
  console.log("");
2094
2445
  if (!config.profiles || Object.keys(config.profiles).length === 0) {
2095
- console.log(chalk12.gray("No profiles configured."));
2446
+ console.log(chalk13.gray("No profiles configured."));
2096
2447
  console.log("");
2097
- console.log(chalk12.gray("Create a profile with: thoughtcabinet profile create <name>"));
2448
+ console.log(chalk13.gray("Create a profile with: thoughtcabinet profile create <name>"));
2098
2449
  } else {
2099
- console.log(chalk12.yellow(`Profiles (${Object.keys(config.profiles).length}):`));
2450
+ console.log(chalk13.yellow(`Profiles (${Object.keys(config.profiles).length}):`));
2100
2451
  console.log("");
2101
2452
  Object.entries(config.profiles).forEach(([name, profile]) => {
2102
- console.log(chalk12.cyan(` ${name}:`));
2453
+ console.log(chalk13.cyan(` ${name}:`));
2103
2454
  console.log(` Thoughts repository: ${profile.thoughtsRepo}`);
2104
2455
  console.log(` Repos directory: ${profile.reposDir}`);
2105
2456
  console.log(` Global directory: ${profile.globalDir}`);
@@ -2107,30 +2458,30 @@ async function profileListCommand(options) {
2107
2458
  });
2108
2459
  }
2109
2460
  } catch (error) {
2110
- console.error(chalk12.red(`Error listing profiles: ${error}`));
2461
+ console.error(chalk13.red(`Error listing profiles: ${error}`));
2111
2462
  process.exit(1);
2112
2463
  }
2113
2464
  }
2114
2465
 
2115
2466
  // src/commands/thoughts/profile/show.ts
2116
- import chalk13 from "chalk";
2467
+ import chalk14 from "chalk";
2117
2468
  async function profileShowCommand(profileName, options) {
2118
2469
  try {
2119
2470
  const config = loadThoughtsConfig(options);
2120
2471
  if (!config) {
2121
- console.error(chalk13.red("Error: Thoughts not configured."));
2472
+ console.error(chalk14.red("Error: Thoughts not configured."));
2122
2473
  process.exit(1);
2123
2474
  }
2124
2475
  if (!validateProfile(config, profileName)) {
2125
- console.error(chalk13.red(`Error: Profile "${profileName}" not found.`));
2476
+ console.error(chalk14.red(`Error: Profile "${profileName}" not found.`));
2126
2477
  console.error("");
2127
- console.error(chalk13.gray("Available profiles:"));
2478
+ console.error(chalk14.gray("Available profiles:"));
2128
2479
  if (config.profiles) {
2129
2480
  Object.keys(config.profiles).forEach((name) => {
2130
- console.error(chalk13.gray(` - ${name}`));
2481
+ console.error(chalk14.gray(` - ${name}`));
2131
2482
  });
2132
2483
  } else {
2133
- console.error(chalk13.gray(" (none)"));
2484
+ console.error(chalk14.gray(" (none)"));
2134
2485
  }
2135
2486
  process.exit(1);
2136
2487
  }
@@ -2139,13 +2490,13 @@ async function profileShowCommand(profileName, options) {
2139
2490
  console.log(JSON.stringify(profile, null, 2));
2140
2491
  return;
2141
2492
  }
2142
- console.log(chalk13.blue(`Profile: ${profileName}`));
2143
- console.log(chalk13.gray("=".repeat(50)));
2493
+ console.log(chalk14.blue(`Profile: ${profileName}`));
2494
+ console.log(chalk14.gray("=".repeat(50)));
2144
2495
  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)}`);
2496
+ console.log(chalk14.yellow("Configuration:"));
2497
+ console.log(` Thoughts repository: ${chalk14.cyan(profile.thoughtsRepo)}`);
2498
+ console.log(` Repos directory: ${chalk14.cyan(profile.reposDir)}`);
2499
+ console.log(` Global directory: ${chalk14.cyan(profile.globalDir)}`);
2149
2500
  console.log("");
2150
2501
  let repoCount = 0;
2151
2502
  Object.values(config.repoMappings).forEach((mapping) => {
@@ -2153,32 +2504,32 @@ async function profileShowCommand(profileName, options) {
2153
2504
  repoCount++;
2154
2505
  }
2155
2506
  });
2156
- console.log(chalk13.yellow("Usage:"));
2157
- console.log(` Repositories using this profile: ${chalk13.cyan(repoCount)}`);
2507
+ console.log(chalk14.yellow("Usage:"));
2508
+ console.log(` Repositories using this profile: ${chalk14.cyan(repoCount)}`);
2158
2509
  } catch (error) {
2159
- console.error(chalk13.red(`Error showing profile: ${error}`));
2510
+ console.error(chalk14.red(`Error showing profile: ${error}`));
2160
2511
  process.exit(1);
2161
2512
  }
2162
2513
  }
2163
2514
 
2164
2515
  // src/commands/thoughts/profile/delete.ts
2165
- import chalk14 from "chalk";
2166
- import * as p3 from "@clack/prompts";
2516
+ import chalk15 from "chalk";
2517
+ import * as p4 from "@clack/prompts";
2167
2518
  async function profileDeleteCommand(profileName, options) {
2168
2519
  try {
2169
2520
  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.");
2521
+ p4.log.error("Not running in interactive terminal.");
2522
+ p4.log.info("Use --force flag to delete without confirmation.");
2172
2523
  process.exit(1);
2173
2524
  }
2174
- p3.intro(chalk14.blue(`Delete Profile: ${profileName}`));
2525
+ p4.intro(chalk15.blue(`Delete Profile: ${profileName}`));
2175
2526
  const config = loadThoughtsConfig(options);
2176
2527
  if (!config) {
2177
- p3.log.error("Thoughts not configured.");
2528
+ p4.log.error("Thoughts not configured.");
2178
2529
  process.exit(1);
2179
2530
  }
2180
2531
  if (!validateProfile(config, profileName)) {
2181
- p3.log.error(`Profile "${profileName}" not found.`);
2532
+ p4.log.error(`Profile "${profileName}" not found.`);
2182
2533
  process.exit(1);
2183
2534
  }
2184
2535
  const usingRepos = [];
@@ -2188,27 +2539,27 @@ async function profileDeleteCommand(profileName, options) {
2188
2539
  }
2189
2540
  });
2190
2541
  if (usingRepos.length > 0 && !options.force) {
2191
- p3.log.error(`Profile "${profileName}" is in use by ${usingRepos.length} repository(ies):`);
2542
+ p4.log.error(`Profile "${profileName}" is in use by ${usingRepos.length} repository(ies):`);
2192
2543
  usingRepos.forEach((repo) => {
2193
- p3.log.message(chalk14.gray(` - ${repo}`));
2544
+ p4.log.message(chalk15.gray(` - ${repo}`));
2194
2545
  });
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)")
2546
+ p4.log.warn("Options:");
2547
+ p4.log.message(chalk15.gray(' 1. Run "thoughtcabinet destroy" in each repository'));
2548
+ p4.log.message(
2549
+ chalk15.gray(" 2. Use --force to delete anyway (repos will fall back to default config)")
2199
2550
  );
2200
2551
  process.exit(1);
2201
2552
  }
2202
2553
  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({
2554
+ p4.log.warn(`You are about to delete profile: ${chalk15.cyan(profileName)}`);
2555
+ p4.log.message(chalk15.gray("This will remove the profile configuration."));
2556
+ p4.log.message(chalk15.gray("The thoughts repository files will NOT be deleted."));
2557
+ const confirmDelete = await p4.confirm({
2207
2558
  message: `Delete profile "${profileName}"?`,
2208
2559
  initialValue: false
2209
2560
  });
2210
- if (p3.isCancel(confirmDelete) || !confirmDelete) {
2211
- p3.cancel("Deletion cancelled.");
2561
+ if (p4.isCancel(confirmDelete) || !confirmDelete) {
2562
+ p4.cancel("Deletion cancelled.");
2212
2563
  return;
2213
2564
  }
2214
2565
  }
@@ -2217,13 +2568,13 @@ async function profileDeleteCommand(profileName, options) {
2217
2568
  delete config.profiles;
2218
2569
  }
2219
2570
  saveThoughtsConfig(config, options);
2220
- p3.log.success(`Profile "${profileName}" deleted`);
2571
+ p4.log.success(`Profile "${profileName}" deleted`);
2221
2572
  if (usingRepos.length > 0) {
2222
- p3.log.warn("Repositories using this profile will fall back to default config");
2573
+ p4.log.warn("Repositories using this profile will fall back to default config");
2223
2574
  }
2224
- p3.outro(chalk14.green("Done"));
2575
+ p4.outro(chalk15.green("Done"));
2225
2576
  } catch (error) {
2226
- p3.log.error(`Error deleting profile: ${error}`);
2577
+ p4.log.error(`Error deleting profile: ${error}`);
2227
2578
  process.exit(1);
2228
2579
  }
2229
2580
  }
@@ -2240,6 +2591,7 @@ function thoughtsCommand(program) {
2240
2591
  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
2592
  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
2593
  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);
2594
+ 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
2595
  const profile = cmd.command("profile").description("Manage thoughts profiles");
2244
2596
  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
2597
  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 +2600,12 @@ function thoughtsCommand(program) {
2248
2600
  }
2249
2601
 
2250
2602
  // 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";
2603
+ import { existsSync as existsSync2 } from "fs";
2604
+ import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
2605
+ import chalk16 from "chalk";
2606
+ import * as p5 from "@clack/prompts";
2255
2607
  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
- }
2608
+ import { cp as cp2, mkdir as mkdir2, readdir as readdir3 } from "fs/promises";
2294
2609
 
2295
2610
  // src/commands/agent/discovery.ts
2296
2611
  import { readdir, readFile } from "fs/promises";
@@ -2378,16 +2693,7 @@ async function discoverAllAssets(sourcePath) {
2378
2693
  // src/commands/agent/installer.ts
2379
2694
  import { mkdir, cp, readdir as readdir2, symlink as fsSymlink, lstat, rm, readlink } from "fs/promises";
2380
2695
  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
2696
+ import { homedir as homedir2, platform } from "os";
2391
2697
  function sanitizeName(name) {
2392
2698
  const sanitized = name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.-]+|[.-]+$/g, "");
2393
2699
  return sanitized.substring(0, 255) || "unnamed-asset";
@@ -2402,6 +2708,10 @@ function getAgentDir(agentType, category, scope, cwd) {
2402
2708
  const agentBase = scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : join3(cwd || process.cwd(), agent.configDir);
2403
2709
  return join3(agentBase, CATEGORY_SUBDIRS[category]);
2404
2710
  }
2711
+ function getCanonicalDir(category, scope, cwd) {
2712
+ const baseDir = scope === "global" ? homedir2() : cwd || process.cwd();
2713
+ return join3(baseDir, CANONICAL_DIR, CATEGORY_SUBDIRS[category]);
2714
+ }
2405
2715
  async function cleanAndCreateDirectory(dirPath) {
2406
2716
  try {
2407
2717
  await rm(dirPath, { recursive: true, force: true });
@@ -2507,15 +2817,37 @@ async function installAssetForAgent(asset, agentType, options = {}) {
2507
2817
  await copyAsset(agentDir);
2508
2818
  return { success: true, path: agentDir, mode: "copy" };
2509
2819
  }
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 };
2820
+ if (scope === "project") {
2821
+ const canonicalBase = getCanonicalDir(asset.category, scope, cwd);
2822
+ const canonicalDir = join3(canonicalBase, assetName);
2823
+ await cleanAndCreateDirectory(canonicalDir);
2824
+ await copyAsset(canonicalDir);
2825
+ await rm(agentDir, { recursive: true, force: true });
2826
+ await mkdir(dirname(agentDir), { recursive: true });
2827
+ const symlinkCreated = await createSymlink(canonicalDir, agentDir);
2828
+ if (!symlinkCreated) {
2829
+ await cleanAndCreateDirectory(agentDir);
2830
+ await copyAsset(agentDir);
2831
+ return {
2832
+ success: true,
2833
+ path: agentDir,
2834
+ canonicalPath: canonicalDir,
2835
+ mode: "symlink",
2836
+ symlinkFailed: true
2837
+ };
2838
+ }
2839
+ return { success: true, path: agentDir, canonicalPath: canonicalDir, mode: "symlink" };
2840
+ } else {
2841
+ await rm(agentDir, { recursive: true, force: true });
2842
+ await mkdir(dirname(agentDir), { recursive: true });
2843
+ const symlinkCreated = await createSymlink(asset.sourcePath, agentDir);
2844
+ if (!symlinkCreated) {
2845
+ await cleanAndCreateDirectory(agentDir);
2846
+ await copyAsset(agentDir);
2847
+ return { success: true, path: agentDir, mode: "symlink", symlinkFailed: true };
2848
+ }
2849
+ return { success: true, path: agentDir, mode: "symlink" };
2517
2850
  }
2518
- return { success: true, path: agentDir, mode: "symlink" };
2519
2851
  } catch (error) {
2520
2852
  return {
2521
2853
  success: false,
@@ -2527,216 +2859,318 @@ async function installAssetForAgent(asset, agentType, options = {}) {
2527
2859
  }
2528
2860
 
2529
2861
  // 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
- }
2862
+ var __dirname2 = dirname2(fileURLToPath(import.meta.url));
2863
+ var ASSET_CATEGORIES = ["agents", "skills"];
2864
+ function getBundledAssetsDir() {
2536
2865
  const candidates = [
2537
- path14.resolve(__dirname2, "..", "src/agent-assets"),
2538
- path14.resolve(__dirname2, "../..", "src/agent-assets")
2866
+ resolve2(__dirname2, "..", "src", "agent-assets"),
2867
+ resolve2(__dirname2, "..", "..", "src", "agent-assets"),
2868
+ resolve2(__dirname2, "..", "..", "..", "src", "agent-assets")
2539
2869
  ];
2540
- return candidates.find((p5) => fs12.existsSync(p5)) ?? null;
2870
+ return candidates.find((dir) => existsSync2(dir)) ?? null;
2871
+ }
2872
+ async function bootstrapAssetsIfNeeded(configDir, bundledDir) {
2873
+ if (!bundledDir) return null;
2874
+ const hasAssets = ASSET_CATEGORIES.some((cat) => existsSync2(join4(configDir, cat)));
2875
+ if (hasAssets) return configDir;
2876
+ for (const category of ASSET_CATEGORIES) {
2877
+ const src = join4(bundledDir, category);
2878
+ const dest = join4(configDir, category);
2879
+ if (!existsSync2(src)) continue;
2880
+ await mkdir2(dest, { recursive: true });
2881
+ const entries = await readdir3(src, { withFileTypes: true });
2882
+ for (const entry of entries) {
2883
+ if (!entry.isDirectory()) continue;
2884
+ await cp2(join4(src, entry.name), join4(dest, entry.name), {
2885
+ recursive: true,
2886
+ force: false,
2887
+ errorOnExist: false
2888
+ });
2889
+ }
2890
+ }
2891
+ return configDir;
2892
+ }
2893
+ async function resolveSourceDir(configDir, bundledDir) {
2894
+ const config = configDir ?? getDefaultConfigDir();
2895
+ const bundled = bundledDir === void 0 ? getBundledAssetsDir() : bundledDir;
2896
+ const hasAssets = ASSET_CATEGORIES.some((cat) => existsSync2(join4(config, cat)));
2897
+ if (hasAssets) return config;
2898
+ const bootstrapped = await bootstrapAssetsIfNeeded(config, bundled);
2899
+ if (bootstrapped) return bootstrapped;
2900
+ return bundled;
2541
2901
  }
2542
2902
  function resolveAgentBaseDir(agentType, scope, cwd) {
2543
2903
  const agent = agents[agentType];
2544
- return scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : path14.join(cwd, agent.configDir);
2904
+ return scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : join4(cwd, agent.configDir);
2545
2905
  }
2546
2906
  async function agentInitCommand(options) {
2547
2907
  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);
2908
+ p5.intro(chalk16.blue("Install Skills & Agents"));
2909
+ const sourceDir = await resolveSourceDir();
2555
2910
  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
- }
2911
+ p5.log.error("Agent assets not found.");
2912
+ p5.log.info("Bundled agent assets not found. Are you running from the package?");
2562
2913
  process.exit(1);
2563
2914
  }
2564
2915
  const discovered = await discoverAllAssets(sourceDir);
2565
2916
  const totalAssets = discovered.agents.length + discovered.skills.length;
2566
2917
  if (totalAssets === 0) {
2567
- p4.log.warn(`No assets found in ${sourceDir}`);
2918
+ p5.log.warn(`No assets found in ${sourceDir}`);
2568
2919
  process.exit(0);
2569
2920
  }
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
- }
2921
+ const selectedAgents = options.agents ?? ["claude-code"];
2922
+ const scope = options.scope ?? "project";
2621
2923
  const mode = options.mode ?? "symlink";
2924
+ const cwd = process.cwd();
2622
2925
  if (!options.force) {
2623
- const cwd2 = process.cwd();
2624
2926
  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({
2927
+ const agentDir = resolveAgentBaseDir(agentType, scope, cwd);
2928
+ if (existsSync2(agentDir) && process.stdin.isTTY) {
2929
+ const overwrite = await p5.confirm({
2628
2930
  message: `${agents[agentType].displayName} directory already exists at ${agentDir}. Overwrite?`,
2629
2931
  initialValue: false
2630
2932
  });
2631
- if (p4.isCancel(overwrite) || !overwrite) {
2632
- p4.cancel("Operation cancelled.");
2933
+ if (p5.isCancel(overwrite) || !overwrite) {
2934
+ p5.cancel("Operation cancelled.");
2633
2935
  process.exit(0);
2634
2936
  }
2635
2937
  }
2636
2938
  }
2637
2939
  }
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);
2940
+ const allAssets = [...discovered.skills, ...discovered.agents];
2941
+ const results = [];
2942
+ for (const asset of allAssets) {
2943
+ for (const agentType of selectedAgents) {
2944
+ const result = await installAssetForAgent(asset, agentType, { scope, cwd, mode });
2945
+ results.push({
2946
+ asset,
2947
+ agentType,
2948
+ success: result.success,
2949
+ symlinkFailed: result.symlinkFailed,
2950
+ error: result.error
2951
+ });
2666
2952
  }
2667
- selectedCategories = selection;
2668
- if (selectedCategories.length === 0) {
2669
- p4.cancel("No categories selected.");
2670
- process.exit(0);
2953
+ }
2954
+ for (const category of ASSET_CATEGORIES) {
2955
+ const categoryResults = results.filter((r) => r.asset.category === category);
2956
+ if (categoryResults.length === 0) continue;
2957
+ const label = category.charAt(0).toUpperCase() + category.slice(1);
2958
+ p5.log.step(chalk16.bold(label));
2959
+ for (const entry of categoryResults) {
2960
+ if (entry.success && entry.symlinkFailed) {
2961
+ p5.log.warn(` \u26A0 ${entry.asset.name} (symlink failed, copied instead)`);
2962
+ } else if (entry.success) {
2963
+ p5.log.success(` \u2713 ${entry.asset.name}`);
2964
+ } else {
2965
+ p5.log.error(` \u2717 ${entry.asset.name}: ${entry.error}`);
2966
+ }
2671
2967
  }
2672
2968
  }
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;
2969
+ const totalInstalled = results.filter((r) => r.success).length;
2970
+ const totalFailed = results.filter((r) => !r.success).length;
2971
+ const agentNames = selectedAgents.map((a) => agents[a].displayName).join(", ");
2972
+ let message = `Installed ${totalInstalled} asset(s) to ${agentNames}`;
2973
+ if (totalFailed > 0) {
2974
+ message += chalk16.red(` (${totalFailed} failed)`);
2975
+ }
2976
+ message += chalk16.gray(`
2977
+ Scope: ${scope} | Mode: ${mode}`);
2978
+ p5.outro(message);
2979
+ } catch (error) {
2980
+ p5.log.error(`Error during agent init: ${error}`);
2981
+ process.exit(1);
2982
+ }
2983
+ }
2984
+
2985
+ // src/commands/agent/update.ts
2986
+ import { existsSync as existsSync3 } from "fs";
2987
+ import { join as join5 } from "path";
2988
+ import { cp as cp3, readdir as readdir4, mkdir as mkdir3, rm as rm2, lstat as lstat2 } from "fs/promises";
2989
+ import chalk17 from "chalk";
2990
+ import * as p6 from "@clack/prompts";
2991
+ var ASSET_CATEGORIES2 = ["agents", "skills"];
2992
+ async function updateBundledAssets(configDir, bundledDir) {
2993
+ const seeded = [];
2994
+ const skipped = [];
2995
+ for (const category of ASSET_CATEGORIES2) {
2996
+ const bundledCategoryDir = join5(bundledDir, category);
2997
+ const configCategoryDir = join5(configDir, category);
2998
+ if (!existsSync3(bundledCategoryDir)) continue;
2999
+ await mkdir3(configCategoryDir, { recursive: true });
3000
+ const bundledEntries = await readdir4(bundledCategoryDir, { withFileTypes: true });
3001
+ for (const entry of bundledEntries) {
3002
+ if (!entry.isDirectory()) continue;
3003
+ const src = join5(bundledCategoryDir, entry.name);
3004
+ const dest = join5(configCategoryDir, entry.name);
3005
+ await rm2(dest, { recursive: true, force: true });
3006
+ await cp3(src, dest, { recursive: true });
3007
+ seeded.push(`${category}/${entry.name}`);
3008
+ }
3009
+ if (existsSync3(configCategoryDir)) {
3010
+ const configEntries = await readdir4(configCategoryDir, { withFileTypes: true });
3011
+ const bundledNames = new Set(bundledEntries.filter((e) => e.isDirectory()).map((e) => e.name));
3012
+ for (const entry of configEntries) {
3013
+ if (entry.isDirectory() && !bundledNames.has(entry.name)) {
3014
+ skipped.push(`${category}/${entry.name}`);
3015
+ }
2682
3016
  }
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);
3017
+ }
3018
+ }
3019
+ return { seeded, skipped };
3020
+ }
3021
+ async function detectInstallTargets(repoPaths) {
3022
+ const targets = [];
3023
+ for (const agent of getAllAgents()) {
3024
+ for (const repoPath of repoPaths) {
3025
+ const mode = await detectModeInDir(repoPath, agent.configDir);
3026
+ if (mode) {
3027
+ targets.push({ agentType: agent.name, scope: "project", mode, cwd: repoPath });
2696
3028
  }
2697
- const selectedNames = new Set(assetSelection);
2698
- assetsToInstall.push(...categoryAssets.filter((a) => selectedNames.has(a.name)));
2699
3029
  }
2700
- const cwd = process.cwd();
3030
+ if (agent.globalConfigDir) {
3031
+ const mode = await detectModeInBaseDir(agent.globalConfigDir);
3032
+ if (mode) {
3033
+ targets.push({ agentType: agent.name, scope: "global", mode, cwd: process.cwd() });
3034
+ }
3035
+ }
3036
+ }
3037
+ return targets;
3038
+ }
3039
+ async function detectModeInDir(repoPath, agentConfigDir) {
3040
+ const baseDir = join5(repoPath, agentConfigDir);
3041
+ return detectModeInBaseDir(baseDir);
3042
+ }
3043
+ async function detectModeInBaseDir(baseDir) {
3044
+ let symlinkCount = 0;
3045
+ let copyCount = 0;
3046
+ for (const category of Object.values(CATEGORY_SUBDIRS)) {
3047
+ const categoryDir = join5(baseDir, category);
3048
+ if (!existsSync3(categoryDir)) continue;
3049
+ try {
3050
+ const entries = await readdir4(categoryDir);
3051
+ for (const name of entries) {
3052
+ const entryPath = join5(categoryDir, name);
3053
+ try {
3054
+ const stats = await lstat2(entryPath);
3055
+ if (stats.isSymbolicLink()) {
3056
+ symlinkCount++;
3057
+ } else if (stats.isDirectory()) {
3058
+ copyCount++;
3059
+ }
3060
+ } catch {
3061
+ }
3062
+ }
3063
+ } catch {
3064
+ }
3065
+ }
3066
+ if (symlinkCount === 0 && copyCount === 0) return null;
3067
+ return symlinkCount >= copyCount ? "symlink" : "copy";
3068
+ }
3069
+ async function skillUpdateCommand(options) {
3070
+ try {
3071
+ p6.intro(chalk17.blue("Update Skills"));
3072
+ const bundledDir = getBundledAssetsDir();
3073
+ if (!bundledDir) {
3074
+ p6.log.error("Bundled assets not found. Are you running from the package?");
3075
+ process.exit(1);
3076
+ }
3077
+ const configDir = getDefaultConfigDir();
3078
+ p6.log.step("Updating config directory from bundle...");
3079
+ const seedResult = await updateBundledAssets(configDir, bundledDir);
3080
+ for (const name of seedResult.seeded) {
3081
+ p6.log.info(` Updated: ${name}`);
3082
+ }
3083
+ if (seedResult.skipped.length > 0) {
3084
+ p6.log.info(chalk17.gray(` Preserved ${seedResult.skipped.length} custom asset(s)`));
3085
+ }
3086
+ const discovered = await discoverAllAssets(configDir);
3087
+ const bundledAssetNames = new Set(seedResult.seeded.map((s2) => s2.split("/")[1]));
3088
+ const bundledAssets = [
3089
+ ...discovered.agents.filter((a) => bundledAssetNames.has(a.name)),
3090
+ ...discovered.skills.filter((a) => bundledAssetNames.has(a.name))
3091
+ ];
3092
+ if (bundledAssets.length === 0) {
3093
+ p6.log.warn("No assets to update.");
3094
+ p6.outro("Done.");
3095
+ return;
3096
+ }
3097
+ const repoPaths = [];
3098
+ if (options.all) {
3099
+ const config = loadConfigFile();
3100
+ if (config.thoughts?.repoMappings) {
3101
+ for (const repoPath of Object.keys(config.thoughts.repoMappings)) {
3102
+ if (existsSync3(repoPath)) {
3103
+ repoPaths.push(repoPath);
3104
+ } else {
3105
+ p6.log.warn(`Skipping (not found): ${repoPath}`);
3106
+ }
3107
+ }
3108
+ }
3109
+ const cwd = process.cwd();
3110
+ if (!repoPaths.includes(cwd) && existsSync3(join5(cwd, ".git"))) {
3111
+ repoPaths.push(cwd);
3112
+ }
3113
+ } else {
3114
+ repoPaths.push(process.cwd());
3115
+ }
3116
+ p6.log.step("Detecting existing installations...");
3117
+ const targets = await detectInstallTargets(repoPaths);
3118
+ if (targets.length === 0) {
3119
+ p6.log.warn("No existing installations detected. Run `thc skill install` first.");
3120
+ p6.outro("Done.");
3121
+ return;
3122
+ }
3123
+ for (const t of targets) {
3124
+ const scopeLabel = t.scope === "global" ? "global" : t.cwd;
3125
+ p6.log.info(chalk17.gray(` ${agents[t.agentType].displayName}: ${scopeLabel} (${t.mode})`));
3126
+ }
3127
+ const s = p6.spinner();
3128
+ s.start("Installing updated assets...");
2701
3129
  let totalInstalled = 0;
2702
3130
  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 });
3131
+ for (const target of targets) {
3132
+ for (const asset of bundledAssets) {
3133
+ const result = await installAssetForAgent(asset, target.agentType, {
3134
+ scope: target.scope,
3135
+ cwd: target.cwd,
3136
+ mode: target.mode
3137
+ });
2708
3138
  if (result.success) {
2709
3139
  totalInstalled++;
2710
- if (result.symlinkFailed) {
2711
- p4.log.warn(
2712
- `${asset.name} \u2192 ${agents[agentType].displayName}: symlink failed, copied instead`
2713
- );
2714
- }
2715
3140
  } else {
2716
3141
  totalFailed++;
2717
- p4.log.warn(`Failed: ${asset.name} \u2192 ${agents[agentType].displayName}: ${result.error}`);
3142
+ if (result.error) {
3143
+ p6.log.warn(
3144
+ `Failed: ${asset.name} \u2192 ${agents[target.agentType].displayName}: ${result.error}`
3145
+ );
3146
+ }
2718
3147
  }
2719
3148
  }
2720
3149
  }
2721
- s.stop("Installation complete.");
2722
- const agentNames = selectedAgents.map((a) => agents[a].displayName).join(", ");
2723
- let message = `Installed ${totalInstalled} asset(s) to ${agentNames}`;
3150
+ s.stop("Update complete.");
3151
+ const uniqueRepos = new Set(targets.filter((t) => t.scope === "project").map((t) => t.cwd));
3152
+ const hasGlobal = targets.some((t) => t.scope === "global");
3153
+ let message = `Updated ${totalInstalled} asset(s)`;
3154
+ if (uniqueRepos.size > 0) {
3155
+ message += ` in ${uniqueRepos.size} repo(s)`;
3156
+ }
3157
+ if (hasGlobal) {
3158
+ message += ` + global`;
3159
+ }
2724
3160
  if (totalFailed > 0) {
2725
- message += chalk15.red(` (${totalFailed} failed)`);
3161
+ message += chalk17.red(` (${totalFailed} failed)`);
2726
3162
  }
2727
- message += chalk15.gray(`
2728
- Scope: ${scope} | Mode: ${mode}`);
2729
- p4.outro(message);
3163
+ p6.outro(message);
2730
3164
  } catch (error) {
2731
- p4.log.error(`Error during agent init: ${error}`);
3165
+ p6.log.error(`Error during skill update: ${error}`);
2732
3166
  process.exit(1);
2733
3167
  }
2734
3168
  }
2735
3169
 
2736
3170
  // 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) => {
3171
+ function skillCommand(program) {
3172
+ const skill = program.command("skill").description("Manage skill and agent asset installation");
3173
+ 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
3174
  const agentTypes = options.target?.map((a) => {
2741
3175
  if (!isValidAgentType(a)) {
2742
3176
  console.error(`Unknown agent: ${a}`);
@@ -2756,22 +3190,23 @@ function agentCommand(program) {
2756
3190
  agents: agentTypes,
2757
3191
  scope: options.global ? "global" : void 0,
2758
3192
  mode,
2759
- source: options.source,
2760
- force: options.force,
2761
- all: options.all
3193
+ force: options.force
2762
3194
  });
2763
3195
  });
3196
+ skill.command("update").description("Update skills from package bundle and refresh installations").option("--all", "Update all registered repos (from config repoMappings)").action(async (options) => {
3197
+ await skillUpdateCommand({ all: options.all });
3198
+ });
2764
3199
  }
2765
3200
 
2766
3201
  // src/commands/metadata/metadata.ts
2767
- import path15 from "path";
3202
+ import path16 from "path";
2768
3203
  function getGitInfo() {
2769
3204
  if (!isGitRepo()) {
2770
3205
  return null;
2771
3206
  }
2772
3207
  try {
2773
3208
  const repoRoot = getRepoRoot();
2774
- const repoName = path15.basename(repoRoot);
3209
+ const repoName = path16.basename(repoRoot);
2775
3210
  const branch = getCurrentBranch();
2776
3211
  const commit = getCurrentCommit();
2777
3212
  return { repoRoot, repoName, branch, commit };
@@ -2825,8 +3260,8 @@ function metadataCommand(program) {
2825
3260
 
2826
3261
  // src/commands/worktree/add.ts
2827
3262
  import fs14 from "fs";
2828
- import path17 from "path";
2829
- import chalk16 from "chalk";
3263
+ import path18 from "path";
3264
+ import chalk18 from "chalk";
2830
3265
 
2831
3266
  // src/tmux.ts
2832
3267
  import { execFileSync as execFileSync3 } from "child_process";
@@ -2886,10 +3321,11 @@ function tmuxKillSession(sessionName) {
2886
3321
 
2887
3322
  // src/agent-config.ts
2888
3323
  import fs13 from "fs";
2889
- import path16 from "path";
3324
+ import path17 from "path";
2890
3325
  function detectAgentConfigDirs(sourceDir) {
2891
3326
  const uniqueDirs = [...new Set(Object.values(agents).map((a) => a.configDir))];
2892
- return uniqueDirs.filter((dir) => fs13.existsSync(path16.join(sourceDir, dir)));
3327
+ uniqueDirs.push(CANONICAL_DIR);
3328
+ return uniqueDirs.filter((dir) => fs13.existsSync(path17.join(sourceDir, dir)));
2893
3329
  }
2894
3330
  function symlinkOrCopy(srcPath, destPath, linkTarget) {
2895
3331
  try {
@@ -2904,8 +3340,8 @@ function symlinkOrCopy(srcPath, destPath, linkTarget) {
2904
3340
  function copyDirWithSymlinkHandling(srcDir, destDir) {
2905
3341
  fs13.mkdirSync(destDir, { recursive: true });
2906
3342
  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);
3343
+ const srcPath = path17.join(srcDir, entry.name);
3344
+ const destPath = path17.join(destDir, entry.name);
2909
3345
  if (entry.isSymbolicLink()) {
2910
3346
  symlinkOrCopy(srcPath, destPath, fs13.readlinkSync(srcPath));
2911
3347
  } else if (entry.isDirectory()) {
@@ -2921,7 +3357,7 @@ function copyAgentConfigDirs(options) {
2921
3357
  const skipped = [];
2922
3358
  for (const dirName of detectAgentConfigDirs(sourceDir)) {
2923
3359
  try {
2924
- copyDirWithSymlinkHandling(path16.join(sourceDir, dirName), path16.join(targetDir, dirName));
3360
+ copyDirWithSymlinkHandling(path17.join(sourceDir, dirName), path17.join(targetDir, dirName));
2925
3361
  copied.push(dirName);
2926
3362
  } catch {
2927
3363
  skipped.push(dirName);
@@ -2935,21 +3371,21 @@ async function worktreeAddCommand(name, options) {
2935
3371
  try {
2936
3372
  validateWorktreeHandle(name);
2937
3373
  if (!isGitRepo()) {
2938
- console.error(chalk16.red("Error: not in a git repository"));
3374
+ console.error(chalk18.red("Error: not in a git repository"));
2939
3375
  process.exit(1);
2940
3376
  }
2941
3377
  const mainRoot = getMainWorktreeRoot();
2942
3378
  const baseDir = getWorktreesBaseDir(mainRoot);
2943
- const worktreePath = options.path ? path17.resolve(options.path) : path17.join(baseDir, name);
3379
+ const worktreePath = options.path ? path18.resolve(options.path) : path18.join(baseDir, name);
2944
3380
  const branch = options.detached ? "" : options.branch ?? name;
2945
3381
  const sessionName = sessionNameForHandle(name);
2946
- fs14.mkdirSync(path17.dirname(worktreePath), { recursive: true });
3382
+ fs14.mkdirSync(path18.dirname(worktreePath), { recursive: true });
2947
3383
  const tmuxAvailable = isTmuxAvailable();
2948
3384
  if (tmuxAvailable) {
2949
3385
  const sessionCandidates = allSessionNamesForHandle(name);
2950
3386
  const existingSession = sessionCandidates.find((s) => tmuxHasSession(s));
2951
3387
  if (existingSession) {
2952
- console.error(chalk16.red(`Error: tmux session already exists: ${existingSession}`));
3388
+ console.error(chalk18.red(`Error: tmux session already exists: ${existingSession}`));
2953
3389
  process.exit(1);
2954
3390
  }
2955
3391
  }
@@ -2993,14 +3429,14 @@ async function worktreeAddCommand(name, options) {
2993
3429
  if (tmuxAvailable) {
2994
3430
  tmuxNewSession(sessionName, worktreePath);
2995
3431
  } else {
2996
- console.log(chalk16.yellow("Warning: tmux not found, skipping session creation"));
3432
+ console.log(chalk18.yellow("Warning: tmux not found, skipping session creation"));
2997
3433
  }
2998
3434
  const configResult = copyAgentConfigDirs({
2999
3435
  sourceDir: mainRoot,
3000
3436
  targetDir: worktreePath
3001
3437
  });
3002
3438
  if (configResult.copied.length > 0) {
3003
- console.log(chalk16.gray(`Copied config: ${configResult.copied.join(", ")}`));
3439
+ console.log(chalk18.gray(`Copied config: ${configResult.copied.join(", ")}`));
3004
3440
  }
3005
3441
  if (options.thoughts !== false) {
3006
3442
  initializeWorktreeThoughts(mainRoot, worktreePath);
@@ -3029,27 +3465,27 @@ async function worktreeAddCommand(name, options) {
3029
3465
  true
3030
3466
  );
3031
3467
  }
3032
- console.log(chalk16.green("\n\u2713 Worktree created"));
3033
- console.log(chalk16.gray(`Path: ${worktreePath}`));
3468
+ console.log(chalk18.green("\n\u2713 Worktree created"));
3469
+ console.log(chalk18.gray(`Path: ${worktreePath}`));
3034
3470
  if (tmuxAvailable) {
3035
- console.log(chalk16.gray(`Tmux session: ${sessionName}`));
3036
- console.log(chalk16.gray(`Attach: tmux attach -t ${sessionName}`));
3471
+ console.log(chalk18.gray(`Tmux session: ${sessionName}`));
3472
+ console.log(chalk18.gray(`Attach: tmux attach -t ${sessionName}`));
3037
3473
  }
3038
3474
  } catch (error) {
3039
- console.error(chalk16.red(`Error: ${error.message}`));
3475
+ console.error(chalk18.red(`Error: ${error.message}`));
3040
3476
  process.exit(1);
3041
3477
  }
3042
3478
  }
3043
3479
  function initializeWorktreeThoughts(mainRoot, worktreePath) {
3044
3480
  const config = loadThoughtsConfig({});
3045
3481
  if (!config) {
3046
- console.log(chalk16.yellow("Thoughts not configured globally, skipping"));
3482
+ console.log(chalk18.yellow("Thoughts not configured globally, skipping"));
3047
3483
  return;
3048
3484
  }
3049
3485
  const mainRepoMapping = config.repoMappings[mainRoot];
3050
3486
  const mappedName = getRepoNameFromMapping(mainRepoMapping);
3051
3487
  if (!mappedName) {
3052
- console.log(chalk16.yellow("Main repo not configured for thoughts, skipping"));
3488
+ console.log(chalk18.yellow("Main repo not configured for thoughts, skipping"));
3053
3489
  return;
3054
3490
  }
3055
3491
  config.repoMappings[worktreePath] = mainRepoMapping;
@@ -3064,42 +3500,42 @@ function initializeWorktreeThoughts(mainRoot, worktreePath) {
3064
3500
  createSearchable: true,
3065
3501
  setupHooks: true
3066
3502
  });
3067
- console.log(chalk16.gray("Thoughts initialized"));
3503
+ console.log(chalk18.gray("Thoughts initialized"));
3068
3504
  if (result.hooksUpdated.length > 0) {
3069
- console.log(chalk16.gray(`Updated git hooks: ${result.hooksUpdated.join(", ")}`));
3505
+ console.log(chalk18.gray(`Updated git hooks: ${result.hooksUpdated.join(", ")}`));
3070
3506
  }
3071
3507
  if (pullThoughtsFromRemote(profileConfig.thoughtsRepo)) {
3072
- console.log(chalk16.gray("Pulled latest thoughts from remote"));
3508
+ console.log(chalk18.gray("Pulled latest thoughts from remote"));
3073
3509
  }
3074
3510
  }
3075
3511
 
3076
3512
  // src/commands/worktree/list.ts
3077
- import path18 from "path";
3078
- import chalk17 from "chalk";
3513
+ import path19 from "path";
3514
+ import chalk19 from "chalk";
3079
3515
  async function worktreeListCommand(options) {
3080
3516
  try {
3081
3517
  if (!isGitRepo()) {
3082
- console.error(chalk17.red("Error: not in a git repository"));
3518
+ console.error(chalk19.red("Error: not in a git repository"));
3083
3519
  process.exit(1);
3084
3520
  }
3085
3521
  const mainRoot = getMainWorktreeRoot();
3086
- const baseDir = path18.resolve(getWorktreesBaseDir(mainRoot));
3087
- const cwd = path18.resolve(process.cwd());
3522
+ const baseDir = path19.resolve(getWorktreesBaseDir(mainRoot));
3523
+ const cwd = path19.resolve(process.cwd());
3088
3524
  const entries = parseWorktreeListPorcelain(
3089
3525
  runGitCommand(["worktree", "list", "--porcelain"], { cwd: mainRoot })
3090
3526
  );
3091
3527
  const sessions = new Set(listTmuxSessions());
3092
3528
  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);
3529
+ const p7 = path19.resolve(e.worktreePath);
3530
+ return p7 === path19.resolve(mainRoot) || p7.startsWith(baseDir + path19.sep);
3095
3531
  });
3096
3532
  if (filtered.length === 0) {
3097
- console.log(chalk17.gray("No worktrees found."));
3533
+ console.log(chalk19.gray("No worktrees found."));
3098
3534
  return;
3099
3535
  }
3100
3536
  const rows = filtered.map((e) => {
3101
- const name = path18.basename(e.worktreePath);
3102
- const isCurrent = path18.resolve(e.worktreePath) === cwd;
3537
+ const name = path19.basename(e.worktreePath);
3538
+ const isCurrent = path19.resolve(e.worktreePath) === cwd;
3103
3539
  return {
3104
3540
  name: isCurrent ? `* ${name}` : ` ${name}`,
3105
3541
  branch: e.branch,
@@ -3114,24 +3550,24 @@ async function worktreeListCommand(options) {
3114
3550
  tmux: Math.max("TMUX".length, ...rows.map((r) => r.tmux.length))
3115
3551
  };
3116
3552
  const header = `${" NAME".padEnd(colWidths.name)} ${"BRANCH".padEnd(colWidths.branch)} ${"TMUX".padEnd(colWidths.tmux)} PATH`;
3117
- console.log(chalk17.blue(header));
3553
+ console.log(chalk19.blue(header));
3118
3554
  for (const row of rows) {
3119
3555
  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);
3556
+ console.log(row.isCurrent ? chalk19.green(line) : line);
3121
3557
  }
3122
3558
  } catch (error) {
3123
- console.error(chalk17.red(`Error: ${error.message}`));
3559
+ console.error(chalk19.red(`Error: ${error.message}`));
3124
3560
  process.exit(1);
3125
3561
  }
3126
3562
  }
3127
3563
 
3128
3564
  // src/commands/worktree/merge.ts
3129
- import path20 from "path";
3130
- import chalk19 from "chalk";
3565
+ import path21 from "path";
3566
+ import chalk21 from "chalk";
3131
3567
 
3132
3568
  // src/commands/worktree/utils.ts
3133
- import path19 from "path";
3134
- import chalk18 from "chalk";
3569
+ import path20 from "path";
3570
+ import chalk20 from "chalk";
3135
3571
  function cleanupWorktreeThoughts(wtPath, options = {}) {
3136
3572
  const config = loadThoughtsConfig({});
3137
3573
  if (!config || !config.repoMappings[wtPath]) {
@@ -3139,7 +3575,7 @@ function cleanupWorktreeThoughts(wtPath, options = {}) {
3139
3575
  }
3140
3576
  try {
3141
3577
  if (options.verbose) {
3142
- console.log(chalk18.gray("Cleaning up thoughts directory..."));
3578
+ console.log(chalk20.gray("Cleaning up thoughts directory..."));
3143
3579
  }
3144
3580
  const result = cleanupThoughtsDirectory({
3145
3581
  repoPath: wtPath,
@@ -3151,16 +3587,16 @@ function cleanupWorktreeThoughts(wtPath, options = {}) {
3151
3587
  saveThoughtsConfig(config, {});
3152
3588
  }
3153
3589
  if (result.thoughtsRemoved && options.verbose) {
3154
- console.log(chalk18.gray("\u2713 Thoughts directory cleaned up"));
3590
+ console.log(chalk20.gray("\u2713 Thoughts directory cleaned up"));
3155
3591
  }
3156
3592
  } catch (error) {
3157
3593
  if (options.verbose) {
3158
- console.log(chalk18.yellow(`Warning: Could not clean up thoughts: ${error.message}`));
3594
+ console.log(chalk20.yellow(`Warning: Could not clean up thoughts: ${error.message}`));
3159
3595
  }
3160
3596
  }
3161
3597
  }
3162
3598
  function cleanupWorktreeTmuxSession(wtPath) {
3163
- const handle = path19.basename(wtPath);
3599
+ const handle = path20.basename(wtPath);
3164
3600
  const sessionNames = allSessionNamesForHandle(handle);
3165
3601
  for (const s of sessionNames) {
3166
3602
  tmuxKillSession(s);
@@ -3194,27 +3630,27 @@ function deleteWorktreeBranch(branch, mainRoot, options = {}) {
3194
3630
  async function worktreeMergeCommand(name, options) {
3195
3631
  try {
3196
3632
  if (!isGitRepo()) {
3197
- console.error(chalk19.red("Error: not in a git repository"));
3633
+ console.error(chalk21.red("Error: not in a git repository"));
3198
3634
  process.exit(1);
3199
3635
  }
3200
3636
  const mainRoot = getMainWorktreeRoot();
3201
3637
  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"));
3638
+ const wtPath = path21.resolve(wtEntry.worktreePath);
3639
+ if (wtPath === path21.resolve(mainRoot)) {
3640
+ console.error(chalk21.red("Error: refusing to merge/remove the main worktree"));
3205
3641
  process.exit(1);
3206
3642
  }
3207
3643
  if (wtEntry.detached || wtEntry.branch === "(detached)") {
3208
- console.error(chalk19.red("Error: cannot merge a detached worktree"));
3644
+ console.error(chalk21.red("Error: cannot merge a detached worktree"));
3209
3645
  process.exit(1);
3210
3646
  }
3211
3647
  const targetBranch = options.into ?? runGitCommand(["branch", "--show-current"], { cwd: mainRoot });
3212
3648
  if (!targetBranch) {
3213
- console.error(chalk19.red("Error: could not determine target branch. Use --into <branch>."));
3649
+ console.error(chalk21.red("Error: could not determine target branch. Use --into <branch>."));
3214
3650
  process.exit(1);
3215
3651
  }
3216
3652
  if (targetBranch === wtEntry.branch) {
3217
- console.error(chalk19.red("Error: source and target branch are the same"));
3653
+ console.error(chalk21.red("Error: source and target branch are the same"));
3218
3654
  process.exit(1);
3219
3655
  }
3220
3656
  const hooksConfig = loadHooksConfig(mainRoot);
@@ -3245,13 +3681,13 @@ async function worktreeMergeCommand(name, options) {
3245
3681
  cleanupWorktreeThoughts(wtPath, { force: options.force, verbose: true });
3246
3682
  if (!options.force && hasUncommittedChanges(wtEntry.worktreePath)) {
3247
3683
  console.error(
3248
- chalk19.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3684
+ chalk21.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3249
3685
  );
3250
3686
  process.exit(1);
3251
3687
  }
3252
- console.log(chalk19.blue(`Rebasing ${wtEntry.branch} onto ${targetBranch}...`));
3688
+ console.log(chalk21.blue(`Rebasing ${wtEntry.branch} onto ${targetBranch}...`));
3253
3689
  runGitCommandOrThrow(["rebase", targetBranch], { cwd: wtEntry.worktreePath });
3254
- console.log(chalk19.blue(`Fast-forward merging into ${targetBranch}...`));
3690
+ console.log(chalk21.blue(`Fast-forward merging into ${targetBranch}...`));
3255
3691
  runGitCommandOrThrow(["switch", targetBranch], { cwd: mainRoot });
3256
3692
  runGitCommandOrThrow(["merge", "--ff-only", wtEntry.branch], { cwd: mainRoot });
3257
3693
  if (!options.keepSession) {
@@ -3293,35 +3729,35 @@ async function worktreeMergeCommand(name, options) {
3293
3729
  true
3294
3730
  );
3295
3731
  }
3296
- console.log(chalk19.green("\u2713 Merged and cleaned up"));
3732
+ console.log(chalk21.green("\u2713 Merged and cleaned up"));
3297
3733
  } catch (error) {
3298
- console.error(chalk19.red(`Error: ${error.message}`));
3734
+ console.error(chalk21.red(`Error: ${error.message}`));
3299
3735
  process.exit(1);
3300
3736
  }
3301
3737
  }
3302
3738
 
3303
3739
  // src/commands/worktree/remove.ts
3304
- import path21 from "path";
3305
- import chalk20 from "chalk";
3740
+ import path22 from "path";
3741
+ import chalk22 from "chalk";
3306
3742
  async function worktreeRemoveCommand(name, options) {
3307
3743
  try {
3308
3744
  if (!isGitRepo()) {
3309
- console.error(chalk20.red("Error: not in a git repository"));
3745
+ console.error(chalk22.red("Error: not in a git repository"));
3310
3746
  process.exit(1);
3311
3747
  }
3312
3748
  const mainRoot = getMainWorktreeRoot();
3313
3749
  const wtEntry = findWorktree(name, mainRoot);
3314
- const wtPath = path21.resolve(wtEntry.worktreePath);
3750
+ const wtPath = path22.resolve(wtEntry.worktreePath);
3315
3751
  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"));
3752
+ if (wtPath === path22.resolve(mainRoot)) {
3753
+ console.error(chalk22.red("Error: refusing to remove the main worktree"));
3318
3754
  process.exit(1);
3319
3755
  }
3320
3756
  if (!options.force && hasBranch) {
3321
3757
  const defaultBranch = getDefaultBranch(mainRoot);
3322
3758
  if (hasUnmergedCommits(wtEntry.branch, defaultBranch, mainRoot)) {
3323
3759
  console.error(
3324
- chalk20.red(
3760
+ chalk22.red(
3325
3761
  `Error: branch '${wtEntry.branch}' has commits not merged into '${defaultBranch}'. Merge first or use --force to discard.`
3326
3762
  )
3327
3763
  );
@@ -3354,19 +3790,19 @@ async function worktreeRemoveCommand(name, options) {
3354
3790
  cleanupWorktreeThoughts(wtPath, { force: options.force, verbose: true });
3355
3791
  if (!options.force && hasUncommittedChanges(wtEntry.worktreePath)) {
3356
3792
  console.error(
3357
- chalk20.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3793
+ chalk22.red("Error: worktree has uncommitted changes. Commit/stash first or use --force.")
3358
3794
  );
3359
3795
  process.exit(1);
3360
3796
  }
3361
3797
  cleanupWorktreeTmuxSession(wtPath);
3362
- console.log(chalk20.gray("Removing git worktree..."));
3798
+ console.log(chalk22.gray("Removing git worktree..."));
3363
3799
  removeGitWorktree(wtPath, mainRoot, { force: options.force });
3364
3800
  if (hasBranch) {
3365
- console.log(chalk20.gray(`Deleting branch '${wtEntry.branch}'...`));
3801
+ console.log(chalk22.gray(`Deleting branch '${wtEntry.branch}'...`));
3366
3802
  try {
3367
3803
  deleteWorktreeBranch(wtEntry.branch, mainRoot, { force: options.force });
3368
3804
  } catch (error) {
3369
- console.log(chalk20.yellow(`Warning: ${error.message}`));
3805
+ console.log(chalk22.yellow(`Warning: ${error.message}`));
3370
3806
  }
3371
3807
  }
3372
3808
  const postHooks = getHooksForEvent(hooksConfig, "PostWorktreeRemove");
@@ -3391,9 +3827,9 @@ async function worktreeRemoveCommand(name, options) {
3391
3827
  true
3392
3828
  );
3393
3829
  }
3394
- console.log(chalk20.green("\u2713 Worktree removed"));
3830
+ console.log(chalk22.green("\u2713 Worktree removed"));
3395
3831
  } catch (error) {
3396
- console.error(chalk20.red(`Error: ${error.message}`));
3832
+ console.error(chalk22.red(`Error: ${error.message}`));
3397
3833
  process.exit(1);
3398
3834
  }
3399
3835
  }
@@ -3416,27 +3852,27 @@ function worktreeCommand(program) {
3416
3852
 
3417
3853
  // src/commands/hooks/init.ts
3418
3854
  import fs15 from "fs";
3419
- import path22 from "path";
3420
- import chalk21 from "chalk";
3855
+ import path23 from "path";
3856
+ import chalk23 from "chalk";
3421
3857
  import { fileURLToPath as fileURLToPath2 } from "url";
3422
- import { dirname as dirname2 } from "path";
3858
+ import { dirname as dirname3 } from "path";
3423
3859
  var __filename2 = fileURLToPath2(import.meta.url);
3424
- var __dirname3 = dirname2(__filename2);
3860
+ var __dirname3 = dirname3(__filename2);
3425
3861
  async function hooksInitCommand() {
3426
3862
  try {
3427
3863
  const repoPath = process.cwd();
3428
- const configDir = path22.join(repoPath, HOOKS_CONFIG_DIR);
3429
- const configPath = path22.join(repoPath, HOOKS_CONFIG_FILE);
3864
+ const configDir = path23.join(repoPath, HOOKS_CONFIG_DIR);
3865
+ const configPath = path23.join(repoPath, HOOKS_CONFIG_FILE);
3430
3866
  if (fs15.existsSync(configPath)) {
3431
- console.log(chalk21.yellow(`${HOOKS_CONFIG_FILE} already exists.`));
3432
- console.log(chalk21.gray(`Edit the file directly: ${configPath}`));
3867
+ console.log(chalk23.yellow(`${HOOKS_CONFIG_FILE} already exists.`));
3868
+ console.log(chalk23.gray(`Edit the file directly: ${configPath}`));
3433
3869
  return;
3434
3870
  }
3435
3871
  const possiblePaths = [
3436
3872
  // When running from built dist: one level up from dist/
3437
- path22.resolve(__dirname3, "..", ".thought-cabinet/hooks.example.json"),
3873
+ path23.resolve(__dirname3, "..", ".thought-cabinet/hooks.example.json"),
3438
3874
  // When installed via npm: one level up from dist/
3439
- path22.resolve(__dirname3, "../..", ".thought-cabinet/hooks.example.json")
3875
+ path23.resolve(__dirname3, "../..", ".thought-cabinet/hooks.example.json")
3440
3876
  ];
3441
3877
  let examplePath = null;
3442
3878
  for (const candidatePath of possiblePaths) {
@@ -3446,16 +3882,16 @@ async function hooksInitCommand() {
3446
3882
  }
3447
3883
  }
3448
3884
  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}`)));
3885
+ console.error(chalk23.red("Error: hooks.example.json not found in expected locations"));
3886
+ console.log(chalk23.gray("Searched paths:"));
3887
+ possiblePaths.forEach((p7) => console.log(chalk23.gray(` - ${p7}`)));
3452
3888
  process.exit(1);
3453
3889
  }
3454
3890
  fs15.mkdirSync(configDir, { recursive: true });
3455
3891
  fs15.copyFileSync(examplePath, configPath);
3456
- console.log(chalk21.green(`Created ${HOOKS_CONFIG_FILE}`));
3892
+ console.log(chalk23.green(`Created ${HOOKS_CONFIG_FILE}`));
3457
3893
  } catch (error) {
3458
- console.error(chalk21.red(`Error during hooks init: ${error}`));
3894
+ console.error(chalk23.red(`Error during hooks init: ${error}`));
3459
3895
  process.exit(1);
3460
3896
  }
3461
3897
  }
@@ -3483,7 +3919,7 @@ function completionCommand(program) {
3483
3919
  import tabtab from "tabtab";
3484
3920
 
3485
3921
  // src/completion/providers.ts
3486
- import path23 from "path";
3922
+ import path24 from "path";
3487
3923
  function getProfileNames() {
3488
3924
  try {
3489
3925
  const config = loadConfigFile();
@@ -3505,9 +3941,9 @@ function getWorktreeNames() {
3505
3941
  const output = runGitCommand(["worktree", "list", "--porcelain"]);
3506
3942
  const entries = parseWorktreeListPorcelain(output);
3507
3943
  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));
3944
+ const p7 = path24.resolve(e.worktreePath);
3945
+ return p7.startsWith(baseDir + path24.sep);
3946
+ }).map((e) => path24.basename(e.worktreePath));
3511
3947
  } catch {
3512
3948
  return [];
3513
3949
  }
@@ -3535,9 +3971,10 @@ var TOP_LEVEL_COMMANDS = [
3535
3971
  "status",
3536
3972
  "config",
3537
3973
  "prune",
3974
+ "migrate",
3538
3975
  "profile",
3539
3976
  "worktree",
3540
- "agent",
3977
+ "skill",
3541
3978
  "metadata",
3542
3979
  "hooks",
3543
3980
  "completion"
@@ -3545,7 +3982,7 @@ var TOP_LEVEL_COMMANDS = [
3545
3982
  var SUBCOMMANDS = {
3546
3983
  profile: ["create", "list", "show", "delete"],
3547
3984
  worktree: ["add", "list", "merge", "remove"],
3548
- agent: ["init"],
3985
+ skill: ["install", "update"],
3549
3986
  hooks: ["init"],
3550
3987
  completion: ["install", "uninstall"]
3551
3988
  };
@@ -3556,6 +3993,7 @@ var OPTIONS = {
3556
3993
  status: ["--config-file", "--fetch"],
3557
3994
  config: ["--edit", "--json", "--config-file"],
3558
3995
  prune: ["--apply", "--config-file"],
3996
+ migrate: ["--dry-run", "--config-file"],
3559
3997
  "profile create": ["--repo", "--repos-dir", "--global-dir", "--config-file"],
3560
3998
  "profile list": ["--json", "--config-file"],
3561
3999
  "profile show": ["--json", "--config-file"],
@@ -3564,7 +4002,8 @@ var OPTIONS = {
3564
4002
  "worktree list": ["--all"],
3565
4003
  "worktree merge": ["--into", "--force", "--keep-session", "--keep-worktree", "--keep-branch"],
3566
4004
  "worktree remove": ["--force"],
3567
- "agent init": ["--force", "--all", "--target", "--global", "--mode", "--source"]
4005
+ "skill install": ["--force", "--target", "--global", "--mode"],
4006
+ "skill update": ["--all"]
3568
4007
  };
3569
4008
  var DYNAMIC_ARGS = {
3570
4009
  "profile show": getProfileNames,
@@ -3649,7 +4088,7 @@ async function main() {
3649
4088
  "Thought Cabinet (thc) \u2014 CLI for structured AI coding workflows with filesystem-based memory and context management."
3650
4089
  ).version(version);
3651
4090
  thoughtsCommand(program);
3652
- agentCommand(program);
4091
+ skillCommand(program);
3653
4092
  metadataCommand(program);
3654
4093
  worktreeCommand(program);
3655
4094
  hooksCommand(program);