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/README.md +10 -9
- package/dist/index.js +845 -406
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +24 -13
- package/package.json +1 -1
- package/src/agent-assets/skills/implementing-plan/SKILL.md +16 -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/test-driven-development/SKILL.md +6 -1
- 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(),
|
|
@@ -1971,32 +1989,365 @@ async function thoughtsPruneCommand(options) {
|
|
|
1971
1989
|
}
|
|
1972
1990
|
}
|
|
1973
1991
|
|
|
1974
|
-
// src/commands/thoughts/
|
|
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
|
-
|
|
1982
|
-
|
|
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
|
-
|
|
1989
|
-
|
|
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
|
-
|
|
2345
|
+
p3.log.warn(`Profile name sanitized: "${profileName}" \u2192 "${sanitizedName}"`);
|
|
1995
2346
|
}
|
|
1996
|
-
|
|
2347
|
+
p3.intro(chalk12.blue(`Creating Profile: ${sanitizedName}`));
|
|
1997
2348
|
if (validateProfile(config, sanitizedName)) {
|
|
1998
|
-
|
|
1999
|
-
|
|
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 =
|
|
2011
|
-
|
|
2012
|
-
const repoInput = await
|
|
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 (
|
|
2018
|
-
|
|
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
|
|
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 (
|
|
2028
|
-
|
|
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
|
|
2383
|
+
const globalDirInput = await p3.text({
|
|
2033
2384
|
message: "Global thoughts directory:",
|
|
2034
2385
|
initialValue: "global",
|
|
2035
2386
|
placeholder: "global"
|
|
2036
2387
|
});
|
|
2037
|
-
if (
|
|
2038
|
-
|
|
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
|
-
|
|
2404
|
+
p3.log.step("Initializing profile thoughts repository...");
|
|
2054
2405
|
ensureThoughtsRepoExists(profileConfig);
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
`Name: ${
|
|
2058
|
-
Thoughts repository: ${
|
|
2059
|
-
Repos directory: ${
|
|
2060
|
-
Global directory: ${
|
|
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
|
-
|
|
2064
|
-
|
|
2065
|
-
`) +
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
2087
|
-
console.log(
|
|
2437
|
+
console.log(chalk13.blue("Thoughts Profiles"));
|
|
2438
|
+
console.log(chalk13.gray("=".repeat(50)));
|
|
2088
2439
|
console.log("");
|
|
2089
|
-
console.log(
|
|
2090
|
-
console.log(` Thoughts repository: ${
|
|
2091
|
-
console.log(` Repos directory: ${
|
|
2092
|
-
console.log(` Global directory: ${
|
|
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(
|
|
2446
|
+
console.log(chalk13.gray("No profiles configured."));
|
|
2096
2447
|
console.log("");
|
|
2097
|
-
console.log(
|
|
2448
|
+
console.log(chalk13.gray("Create a profile with: thoughtcabinet profile create <name>"));
|
|
2098
2449
|
} else {
|
|
2099
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
2476
|
+
console.error(chalk14.red(`Error: Profile "${profileName}" not found.`));
|
|
2126
2477
|
console.error("");
|
|
2127
|
-
console.error(
|
|
2478
|
+
console.error(chalk14.gray("Available profiles:"));
|
|
2128
2479
|
if (config.profiles) {
|
|
2129
2480
|
Object.keys(config.profiles).forEach((name) => {
|
|
2130
|
-
console.error(
|
|
2481
|
+
console.error(chalk14.gray(` - ${name}`));
|
|
2131
2482
|
});
|
|
2132
2483
|
} else {
|
|
2133
|
-
console.error(
|
|
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(
|
|
2143
|
-
console.log(
|
|
2493
|
+
console.log(chalk14.blue(`Profile: ${profileName}`));
|
|
2494
|
+
console.log(chalk14.gray("=".repeat(50)));
|
|
2144
2495
|
console.log("");
|
|
2145
|
-
console.log(
|
|
2146
|
-
console.log(` Thoughts repository: ${
|
|
2147
|
-
console.log(` Repos directory: ${
|
|
2148
|
-
console.log(` Global directory: ${
|
|
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(
|
|
2157
|
-
console.log(` Repositories using this profile: ${
|
|
2507
|
+
console.log(chalk14.yellow("Usage:"));
|
|
2508
|
+
console.log(` Repositories using this profile: ${chalk14.cyan(repoCount)}`);
|
|
2158
2509
|
} catch (error) {
|
|
2159
|
-
console.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
|
|
2166
|
-
import * as
|
|
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
|
-
|
|
2171
|
-
|
|
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
|
-
|
|
2525
|
+
p4.intro(chalk15.blue(`Delete Profile: ${profileName}`));
|
|
2175
2526
|
const config = loadThoughtsConfig(options);
|
|
2176
2527
|
if (!config) {
|
|
2177
|
-
|
|
2528
|
+
p4.log.error("Thoughts not configured.");
|
|
2178
2529
|
process.exit(1);
|
|
2179
2530
|
}
|
|
2180
2531
|
if (!validateProfile(config, profileName)) {
|
|
2181
|
-
|
|
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
|
-
|
|
2542
|
+
p4.log.error(`Profile "${profileName}" is in use by ${usingRepos.length} repository(ies):`);
|
|
2192
2543
|
usingRepos.forEach((repo) => {
|
|
2193
|
-
|
|
2544
|
+
p4.log.message(chalk15.gray(` - ${repo}`));
|
|
2194
2545
|
});
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
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
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
const confirmDelete = await
|
|
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 (
|
|
2211
|
-
|
|
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
|
-
|
|
2571
|
+
p4.log.success(`Profile "${profileName}" deleted`);
|
|
2221
2572
|
if (usingRepos.length > 0) {
|
|
2222
|
-
|
|
2573
|
+
p4.log.warn("Repositories using this profile will fall back to default config");
|
|
2223
2574
|
}
|
|
2224
|
-
|
|
2575
|
+
p4.outro(chalk15.green("Done"));
|
|
2225
2576
|
} catch (error) {
|
|
2226
|
-
|
|
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
|
|
2252
|
-
import
|
|
2253
|
-
import
|
|
2254
|
-
import * as
|
|
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
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
await
|
|
2515
|
-
await
|
|
2516
|
-
|
|
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 =
|
|
2531
|
-
|
|
2532
|
-
|
|
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
|
-
|
|
2538
|
-
|
|
2866
|
+
resolve2(__dirname2, "..", "src", "agent-assets"),
|
|
2867
|
+
resolve2(__dirname2, "..", "..", "src", "agent-assets"),
|
|
2868
|
+
resolve2(__dirname2, "..", "..", "..", "src", "agent-assets")
|
|
2539
2869
|
];
|
|
2540
|
-
return candidates.find((
|
|
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 :
|
|
2904
|
+
return scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : join4(cwd, agent.configDir);
|
|
2545
2905
|
}
|
|
2546
2906
|
async function agentInitCommand(options) {
|
|
2547
2907
|
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);
|
|
2908
|
+
p5.intro(chalk16.blue("Install Skills & Agents"));
|
|
2909
|
+
const sourceDir = await resolveSourceDir();
|
|
2555
2910
|
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
|
-
}
|
|
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
|
-
|
|
2918
|
+
p5.log.warn(`No assets found in ${sourceDir}`);
|
|
2568
2919
|
process.exit(0);
|
|
2569
2920
|
}
|
|
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
|
-
}
|
|
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,
|
|
2626
|
-
if (
|
|
2627
|
-
const overwrite = await
|
|
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 (
|
|
2632
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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
|
|
2674
|
-
const
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
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
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
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
|
-
|
|
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
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
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
|
-
|
|
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("
|
|
2722
|
-
const
|
|
2723
|
-
|
|
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 +=
|
|
3161
|
+
message += chalk17.red(` (${totalFailed} failed)`);
|
|
2726
3162
|
}
|
|
2727
|
-
|
|
2728
|
-
Scope: ${scope} | Mode: ${mode}`);
|
|
2729
|
-
p4.outro(message);
|
|
3163
|
+
p6.outro(message);
|
|
2730
3164
|
} catch (error) {
|
|
2731
|
-
|
|
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
|
|
2738
|
-
const
|
|
2739
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
2829
|
-
import
|
|
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
|
|
3324
|
+
import path17 from "path";
|
|
2890
3325
|
function detectAgentConfigDirs(sourceDir) {
|
|
2891
3326
|
const uniqueDirs = [...new Set(Object.values(agents).map((a) => a.configDir))];
|
|
2892
|
-
|
|
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 =
|
|
2908
|
-
const destPath =
|
|
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(
|
|
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(
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3033
|
-
console.log(
|
|
3468
|
+
console.log(chalk18.green("\n\u2713 Worktree created"));
|
|
3469
|
+
console.log(chalk18.gray(`Path: ${worktreePath}`));
|
|
3034
3470
|
if (tmuxAvailable) {
|
|
3035
|
-
console.log(
|
|
3036
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3503
|
+
console.log(chalk18.gray("Thoughts initialized"));
|
|
3068
3504
|
if (result.hooksUpdated.length > 0) {
|
|
3069
|
-
console.log(
|
|
3505
|
+
console.log(chalk18.gray(`Updated git hooks: ${result.hooksUpdated.join(", ")}`));
|
|
3070
3506
|
}
|
|
3071
3507
|
if (pullThoughtsFromRemote(profileConfig.thoughtsRepo)) {
|
|
3072
|
-
console.log(
|
|
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
|
|
3078
|
-
import
|
|
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(
|
|
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 =
|
|
3087
|
-
const 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
|
|
3094
|
-
return
|
|
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(
|
|
3533
|
+
console.log(chalk19.gray("No worktrees found."));
|
|
3098
3534
|
return;
|
|
3099
3535
|
}
|
|
3100
3536
|
const rows = filtered.map((e) => {
|
|
3101
|
-
const name =
|
|
3102
|
-
const isCurrent =
|
|
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(
|
|
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 ?
|
|
3556
|
+
console.log(row.isCurrent ? chalk19.green(line) : line);
|
|
3121
3557
|
}
|
|
3122
3558
|
} catch (error) {
|
|
3123
|
-
console.error(
|
|
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
|
|
3130
|
-
import
|
|
3565
|
+
import path21 from "path";
|
|
3566
|
+
import chalk21 from "chalk";
|
|
3131
3567
|
|
|
3132
3568
|
// src/commands/worktree/utils.ts
|
|
3133
|
-
import
|
|
3134
|
-
import
|
|
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(
|
|
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(
|
|
3590
|
+
console.log(chalk20.gray("\u2713 Thoughts directory cleaned up"));
|
|
3155
3591
|
}
|
|
3156
3592
|
} catch (error) {
|
|
3157
3593
|
if (options.verbose) {
|
|
3158
|
-
console.log(
|
|
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 =
|
|
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(
|
|
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 =
|
|
3203
|
-
if (wtPath ===
|
|
3204
|
-
console.error(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
3688
|
+
console.log(chalk21.blue(`Rebasing ${wtEntry.branch} onto ${targetBranch}...`));
|
|
3253
3689
|
runGitCommandOrThrow(["rebase", targetBranch], { cwd: wtEntry.worktreePath });
|
|
3254
|
-
console.log(
|
|
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(
|
|
3732
|
+
console.log(chalk21.green("\u2713 Merged and cleaned up"));
|
|
3297
3733
|
} catch (error) {
|
|
3298
|
-
console.error(
|
|
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
|
|
3305
|
-
import
|
|
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(
|
|
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 =
|
|
3750
|
+
const wtPath = path22.resolve(wtEntry.worktreePath);
|
|
3315
3751
|
const hasBranch = !wtEntry.detached && wtEntry.branch !== "(detached)";
|
|
3316
|
-
if (wtPath ===
|
|
3317
|
-
console.error(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
3798
|
+
console.log(chalk22.gray("Removing git worktree..."));
|
|
3363
3799
|
removeGitWorktree(wtPath, mainRoot, { force: options.force });
|
|
3364
3800
|
if (hasBranch) {
|
|
3365
|
-
console.log(
|
|
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(
|
|
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(
|
|
3830
|
+
console.log(chalk22.green("\u2713 Worktree removed"));
|
|
3395
3831
|
} catch (error) {
|
|
3396
|
-
console.error(
|
|
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
|
|
3420
|
-
import
|
|
3855
|
+
import path23 from "path";
|
|
3856
|
+
import chalk23 from "chalk";
|
|
3421
3857
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3422
|
-
import { dirname as
|
|
3858
|
+
import { dirname as dirname3 } from "path";
|
|
3423
3859
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3424
|
-
var __dirname3 =
|
|
3860
|
+
var __dirname3 = dirname3(__filename2);
|
|
3425
3861
|
async function hooksInitCommand() {
|
|
3426
3862
|
try {
|
|
3427
3863
|
const repoPath = process.cwd();
|
|
3428
|
-
const configDir =
|
|
3429
|
-
const configPath =
|
|
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(
|
|
3432
|
-
console.log(
|
|
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
|
-
|
|
3873
|
+
path23.resolve(__dirname3, "..", ".thought-cabinet/hooks.example.json"),
|
|
3438
3874
|
// When installed via npm: one level up from dist/
|
|
3439
|
-
|
|
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(
|
|
3450
|
-
console.log(
|
|
3451
|
-
possiblePaths.forEach((
|
|
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(
|
|
3892
|
+
console.log(chalk23.green(`Created ${HOOKS_CONFIG_FILE}`));
|
|
3457
3893
|
} catch (error) {
|
|
3458
|
-
console.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
|
|
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
|
|
3509
|
-
return
|
|
3510
|
-
}).map((e) =>
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
4091
|
+
skillCommand(program);
|
|
3653
4092
|
metadataCommand(program);
|
|
3654
4093
|
worktreeCommand(program);
|
|
3655
4094
|
hooksCommand(program);
|