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/README.md +38 -15
- package/dist/index.js +933 -485
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +24 -13
- package/package.json +1 -1
- package/src/agent-assets/skills/creating-plan/SKILL.md +6 -0
- package/src/agent-assets/skills/creating-plan/plan-template.md +37 -12
- package/src/agent-assets/skills/implementing-plan/SKILL.md +43 -7
- package/src/agent-assets/skills/init-agent-memory/SKILL.md +161 -0
- package/src/agent-assets/skills/init-agent-memory/architectural-patterns-template.md +36 -0
- package/src/agent-assets/skills/init-agent-memory/claude-memory-template.md +40 -0
- package/src/agent-assets/skills/navigate-thoughts/SKILL.md +45 -0
- package/src/agent-assets/skills/onboard/SKILL.md +74 -0
- package/src/agent-assets/skills/onboard/onboard.sh +118 -0
- package/src/agent-assets/skills/test-driven-development/SKILL.md +6 -1
- package/src/agent-assets/skills/test-skill-e2e/SKILL.md +205 -0
- package/src/agent-assets/skills/writing-skill/SKILL.md +207 -0
- package/src/agent-assets/skills/writing-skill/best-practices.md +1211 -0
- package/src/agent-assets/skills/writing-skill/skill-template.md +63 -0
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
|
-
|
|
72
|
-
|
|
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(
|
|
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(
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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(
|
|
1119
|
+
if (p.isCancel(thoughtsRepoInput)) {
|
|
1134
1120
|
p.cancel("Operation cancelled.");
|
|
1135
1121
|
process.exit(0);
|
|
1136
1122
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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 (
|
|
1247
|
-
p.log.
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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/
|
|
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
|
-
|
|
1982
|
-
|
|
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
|
-
|
|
1989
|
-
|
|
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
|
-
|
|
2354
|
+
p3.log.warn(`Profile name sanitized: "${profileName}" \u2192 "${sanitizedName}"`);
|
|
1995
2355
|
}
|
|
1996
|
-
|
|
2356
|
+
p3.intro(chalk12.blue(`Creating Profile: ${sanitizedName}`));
|
|
1997
2357
|
if (validateProfile(config, sanitizedName)) {
|
|
1998
|
-
|
|
1999
|
-
|
|
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 =
|
|
2011
|
-
|
|
2012
|
-
const repoInput = await
|
|
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 (
|
|
2018
|
-
|
|
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
|
|
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 (
|
|
2028
|
-
|
|
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
|
|
2392
|
+
const globalDirInput = await p3.text({
|
|
2033
2393
|
message: "Global thoughts directory:",
|
|
2034
2394
|
initialValue: "global",
|
|
2035
2395
|
placeholder: "global"
|
|
2036
2396
|
});
|
|
2037
|
-
if (
|
|
2038
|
-
|
|
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
|
-
|
|
2413
|
+
p3.log.step("Initializing profile thoughts repository...");
|
|
2054
2414
|
ensureThoughtsRepoExists(profileConfig);
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
`Name: ${
|
|
2058
|
-
Thoughts repository: ${
|
|
2059
|
-
Repos directory: ${
|
|
2060
|
-
Global directory: ${
|
|
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
|
-
|
|
2064
|
-
|
|
2065
|
-
`) +
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
2087
|
-
console.log(
|
|
2446
|
+
console.log(chalk13.blue("Thoughts Profiles"));
|
|
2447
|
+
console.log(chalk13.gray("=".repeat(50)));
|
|
2088
2448
|
console.log("");
|
|
2089
|
-
console.log(
|
|
2090
|
-
console.log(` Thoughts repository: ${
|
|
2091
|
-
console.log(` Repos directory: ${
|
|
2092
|
-
console.log(` Global directory: ${
|
|
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(
|
|
2455
|
+
console.log(chalk13.gray("No profiles configured."));
|
|
2096
2456
|
console.log("");
|
|
2097
|
-
console.log(
|
|
2457
|
+
console.log(chalk13.gray("Create a profile with: thoughtcabinet profile create <name>"));
|
|
2098
2458
|
} else {
|
|
2099
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
2485
|
+
console.error(chalk14.red(`Error: Profile "${profileName}" not found.`));
|
|
2126
2486
|
console.error("");
|
|
2127
|
-
console.error(
|
|
2487
|
+
console.error(chalk14.gray("Available profiles:"));
|
|
2128
2488
|
if (config.profiles) {
|
|
2129
2489
|
Object.keys(config.profiles).forEach((name) => {
|
|
2130
|
-
console.error(
|
|
2490
|
+
console.error(chalk14.gray(` - ${name}`));
|
|
2131
2491
|
});
|
|
2132
2492
|
} else {
|
|
2133
|
-
console.error(
|
|
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(
|
|
2143
|
-
console.log(
|
|
2502
|
+
console.log(chalk14.blue(`Profile: ${profileName}`));
|
|
2503
|
+
console.log(chalk14.gray("=".repeat(50)));
|
|
2144
2504
|
console.log("");
|
|
2145
|
-
console.log(
|
|
2146
|
-
console.log(` Thoughts repository: ${
|
|
2147
|
-
console.log(` Repos directory: ${
|
|
2148
|
-
console.log(` Global directory: ${
|
|
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(
|
|
2157
|
-
console.log(` Repositories using this profile: ${
|
|
2516
|
+
console.log(chalk14.yellow("Usage:"));
|
|
2517
|
+
console.log(` Repositories using this profile: ${chalk14.cyan(repoCount)}`);
|
|
2158
2518
|
} catch (error) {
|
|
2159
|
-
console.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
|
|
2166
|
-
import * as
|
|
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
|
-
|
|
2171
|
-
|
|
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
|
-
|
|
2534
|
+
p4.intro(chalk15.blue(`Delete Profile: ${profileName}`));
|
|
2175
2535
|
const config = loadThoughtsConfig(options);
|
|
2176
2536
|
if (!config) {
|
|
2177
|
-
|
|
2537
|
+
p4.log.error("Thoughts not configured.");
|
|
2178
2538
|
process.exit(1);
|
|
2179
2539
|
}
|
|
2180
2540
|
if (!validateProfile(config, profileName)) {
|
|
2181
|
-
|
|
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
|
-
|
|
2551
|
+
p4.log.error(`Profile "${profileName}" is in use by ${usingRepos.length} repository(ies):`);
|
|
2192
2552
|
usingRepos.forEach((repo) => {
|
|
2193
|
-
|
|
2553
|
+
p4.log.message(chalk15.gray(` - ${repo}`));
|
|
2194
2554
|
});
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
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
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
const confirmDelete = await
|
|
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 (
|
|
2211
|
-
|
|
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
|
-
|
|
2580
|
+
p4.log.success(`Profile "${profileName}" deleted`);
|
|
2221
2581
|
if (usingRepos.length > 0) {
|
|
2222
|
-
|
|
2582
|
+
p4.log.warn("Repositories using this profile will fall back to default config");
|
|
2223
2583
|
}
|
|
2224
|
-
|
|
2584
|
+
p4.outro(chalk15.green("Done"));
|
|
2225
2585
|
} catch (error) {
|
|
2226
|
-
|
|
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
|
|
2252
|
-
import
|
|
2253
|
-
import
|
|
2254
|
-
import * as
|
|
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
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
await
|
|
2515
|
-
await
|
|
2516
|
-
|
|
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 =
|
|
2531
|
-
|
|
2532
|
-
|
|
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
|
-
|
|
2538
|
-
|
|
2875
|
+
resolve2(__dirname2, "..", "src", "agent-assets"),
|
|
2876
|
+
resolve2(__dirname2, "..", "..", "src", "agent-assets"),
|
|
2877
|
+
resolve2(__dirname2, "..", "..", "..", "src", "agent-assets")
|
|
2539
2878
|
];
|
|
2540
|
-
return candidates.find((
|
|
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 :
|
|
2913
|
+
return scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : join4(cwd, agent.configDir);
|
|
2545
2914
|
}
|
|
2546
2915
|
async function agentInitCommand(options) {
|
|
2547
2916
|
try {
|
|
2548
|
-
|
|
2549
|
-
|
|
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
|
-
|
|
2557
|
-
|
|
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
|
-
|
|
2927
|
+
p5.log.warn(`No assets found in ${sourceDir}`);
|
|
2568
2928
|
process.exit(0);
|
|
2569
2929
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
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,
|
|
2626
|
-
if (
|
|
2627
|
-
const overwrite = await
|
|
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 (
|
|
2632
|
-
|
|
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
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
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
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
|
2674
|
-
const
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
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
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
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
|
-
|
|
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
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
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
|
-
|
|
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("
|
|
2722
|
-
const
|
|
2723
|
-
|
|
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 +=
|
|
3170
|
+
message += chalk17.red(` (${totalFailed} failed)`);
|
|
2726
3171
|
}
|
|
2727
|
-
|
|
2728
|
-
Scope: ${scope} | Mode: ${mode}`);
|
|
2729
|
-
p4.outro(message);
|
|
3172
|
+
p6.outro(message);
|
|
2730
3173
|
} catch (error) {
|
|
2731
|
-
|
|
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
|
|
2738
|
-
const
|
|
2739
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
2829
|
-
import
|
|
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
|
|
3333
|
+
import path17 from "path";
|
|
2890
3334
|
function detectAgentConfigDirs(sourceDir) {
|
|
2891
3335
|
const uniqueDirs = [...new Set(Object.values(agents).map((a) => a.configDir))];
|
|
2892
|
-
|
|
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 =
|
|
2908
|
-
const destPath =
|
|
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(
|
|
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(
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3033
|
-
console.log(
|
|
3477
|
+
console.log(chalk18.green("\n\u2713 Worktree created"));
|
|
3478
|
+
console.log(chalk18.gray(`Path: ${worktreePath}`));
|
|
3034
3479
|
if (tmuxAvailable) {
|
|
3035
|
-
console.log(
|
|
3036
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3512
|
+
console.log(chalk18.gray("Thoughts initialized"));
|
|
3068
3513
|
if (result.hooksUpdated.length > 0) {
|
|
3069
|
-
console.log(
|
|
3514
|
+
console.log(chalk18.gray(`Updated git hooks: ${result.hooksUpdated.join(", ")}`));
|
|
3070
3515
|
}
|
|
3071
3516
|
if (pullThoughtsFromRemote(profileConfig.thoughtsRepo)) {
|
|
3072
|
-
console.log(
|
|
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
|
|
3078
|
-
import
|
|
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(
|
|
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 =
|
|
3087
|
-
const 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
|
|
3094
|
-
return
|
|
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(
|
|
3542
|
+
console.log(chalk19.gray("No worktrees found."));
|
|
3098
3543
|
return;
|
|
3099
3544
|
}
|
|
3100
3545
|
const rows = filtered.map((e) => {
|
|
3101
|
-
const name =
|
|
3102
|
-
const isCurrent =
|
|
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(
|
|
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 ?
|
|
3565
|
+
console.log(row.isCurrent ? chalk19.green(line) : line);
|
|
3121
3566
|
}
|
|
3122
3567
|
} catch (error) {
|
|
3123
|
-
console.error(
|
|
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
|
|
3130
|
-
import
|
|
3574
|
+
import path21 from "path";
|
|
3575
|
+
import chalk21 from "chalk";
|
|
3131
3576
|
|
|
3132
3577
|
// src/commands/worktree/utils.ts
|
|
3133
|
-
import
|
|
3134
|
-
import
|
|
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(
|
|
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(
|
|
3599
|
+
console.log(chalk20.gray("\u2713 Thoughts directory cleaned up"));
|
|
3155
3600
|
}
|
|
3156
3601
|
} catch (error) {
|
|
3157
3602
|
if (options.verbose) {
|
|
3158
|
-
console.log(
|
|
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 =
|
|
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(
|
|
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 =
|
|
3203
|
-
if (wtPath ===
|
|
3204
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
3697
|
+
console.log(chalk21.blue(`Rebasing ${wtEntry.branch} onto ${targetBranch}...`));
|
|
3253
3698
|
runGitCommandOrThrow(["rebase", targetBranch], { cwd: wtEntry.worktreePath });
|
|
3254
|
-
console.log(
|
|
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(
|
|
3741
|
+
console.log(chalk21.green("\u2713 Merged and cleaned up"));
|
|
3297
3742
|
} catch (error) {
|
|
3298
|
-
console.error(
|
|
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
|
|
3305
|
-
import
|
|
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(
|
|
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 =
|
|
3759
|
+
const wtPath = path22.resolve(wtEntry.worktreePath);
|
|
3315
3760
|
const hasBranch = !wtEntry.detached && wtEntry.branch !== "(detached)";
|
|
3316
|
-
if (wtPath ===
|
|
3317
|
-
console.error(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
3807
|
+
console.log(chalk22.gray("Removing git worktree..."));
|
|
3363
3808
|
removeGitWorktree(wtPath, mainRoot, { force: options.force });
|
|
3364
3809
|
if (hasBranch) {
|
|
3365
|
-
console.log(
|
|
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(
|
|
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(
|
|
3839
|
+
console.log(chalk22.green("\u2713 Worktree removed"));
|
|
3395
3840
|
} catch (error) {
|
|
3396
|
-
console.error(
|
|
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
|
|
3420
|
-
import
|
|
3864
|
+
import path23 from "path";
|
|
3865
|
+
import chalk23 from "chalk";
|
|
3421
3866
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3422
|
-
import { dirname as
|
|
3867
|
+
import { dirname as dirname3 } from "path";
|
|
3423
3868
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3424
|
-
var __dirname3 =
|
|
3869
|
+
var __dirname3 = dirname3(__filename2);
|
|
3425
3870
|
async function hooksInitCommand() {
|
|
3426
3871
|
try {
|
|
3427
3872
|
const repoPath = process.cwd();
|
|
3428
|
-
const configDir =
|
|
3429
|
-
const configPath =
|
|
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(
|
|
3432
|
-
console.log(
|
|
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
|
-
|
|
3882
|
+
path23.resolve(__dirname3, "..", ".thought-cabinet/hooks.example.json"),
|
|
3438
3883
|
// When installed via npm: one level up from dist/
|
|
3439
|
-
|
|
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(
|
|
3450
|
-
console.log(
|
|
3451
|
-
possiblePaths.forEach((
|
|
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(
|
|
3901
|
+
console.log(chalk23.green(`Created ${HOOKS_CONFIG_FILE}`));
|
|
3457
3902
|
} catch (error) {
|
|
3458
|
-
console.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
|
|
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
|
|
3509
|
-
return
|
|
3510
|
-
}).map((e) =>
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
4100
|
+
skillCommand(program);
|
|
3653
4101
|
metadataCommand(program);
|
|
3654
4102
|
worktreeCommand(program);
|
|
3655
4103
|
hooksCommand(program);
|