thought-cabinet 0.1.11 → 0.1.13

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 CHANGED
@@ -24,7 +24,7 @@ Thought Cabinet solves these by providing:
24
24
  cd your-project
25
25
 
26
26
  # 1. Install
27
- npm install -g thought-cabinet
27
+ pnpm install -g thought-cabinet
28
28
 
29
29
  # 2. Initialize thoughts in your project
30
30
  thc init
@@ -33,7 +33,7 @@ thc init
33
33
  thc agent init
34
34
 
35
35
  # 4. Use skills in your agent session (e.g. Claude Code)
36
- > /researching-codebase How does the authentication system work?
36
+ > /research-codebase How does the authentication system work?
37
37
  > /creating-plan Add OAuth2 support based on the research
38
38
  > /implementing-plan thoughts/shared/plans/add-oauth.md
39
39
  > /validating-plan thoughts/shared/plans/add-oauth.md
@@ -43,14 +43,14 @@ thc agent init
43
43
 
44
44
  Skills are installed by `thc agent init` and invoked as slash commands in your agent session:
45
45
 
46
- | Skill | Description |
47
- | ----------------------- | --------------------------------------------------------------------- |
48
- | `/researching-codebase` | Deep-dive into codebase, save findings to `thoughts/shared/research/` |
49
- | `/creating-plan` | Create implementation plan with phases and success criteria |
50
- | `/iterating-plan` | Refine existing plans based on feedback |
51
- | `/implementing-plan` | Execute plan phase-by-phase with verification |
52
- | `/validating-plan` | Verify implementation against plan's success criteria |
53
- | `/commit` | Create git commits with clear, descriptive messages |
46
+ | Skill | Description |
47
+ | -------------------- | --------------------------------------------------------------------- |
48
+ | `/research-codebase` | Deep-dive into codebase, save findings to `thoughts/shared/research/` |
49
+ | `/creating-plan` | Create implementation plan with phases and success criteria |
50
+ | `/iterating-plan` | Refine existing plans based on feedback |
51
+ | `/implementing-plan` | Execute plan phase-by-phase with verification |
52
+ | `/validating-plan` | Verify implementation against plan's success criteria |
53
+ | `/commit` | Create git commits with clear, descriptive messages |
54
54
 
55
55
  **Typical workflow**: research the codebase to build understanding, create a plan, iterate until the plan is solid, implement it, then validate the result.
56
56
 
package/dist/index.js CHANGED
@@ -2260,7 +2260,6 @@ import { join } from "path";
2260
2260
  import { existsSync } from "fs";
2261
2261
  var home = homedir();
2262
2262
  var claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
2263
- var codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
2264
2263
  var agents = {
2265
2264
  "claude-code": {
2266
2265
  name: "claude-code",
@@ -2275,34 +2274,6 @@ var agents = {
2275
2274
  configDir: ".codebuddy",
2276
2275
  globalConfigDir: join(home, ".codebuddy"),
2277
2276
  detectInstalled: async () => existsSync(join(process.cwd(), ".codebuddy")) || existsSync(join(home, ".codebuddy"))
2278
- },
2279
- cursor: {
2280
- name: "cursor",
2281
- displayName: "Cursor",
2282
- configDir: ".cursor",
2283
- globalConfigDir: join(home, ".cursor"),
2284
- detectInstalled: async () => existsSync(join(home, ".cursor"))
2285
- },
2286
- codex: {
2287
- name: "codex",
2288
- displayName: "Codex",
2289
- configDir: ".codex",
2290
- globalConfigDir: codexHome,
2291
- detectInstalled: async () => existsSync(codexHome)
2292
- },
2293
- "gemini-cli": {
2294
- name: "gemini-cli",
2295
- displayName: "Gemini CLI",
2296
- configDir: ".gemini",
2297
- globalConfigDir: join(home, ".gemini"),
2298
- detectInstalled: async () => existsSync(join(home, ".gemini"))
2299
- },
2300
- cline: {
2301
- name: "cline",
2302
- displayName: "Cline",
2303
- configDir: ".cline",
2304
- globalConfigDir: join(home, ".cline"),
2305
- detectInstalled: async () => existsSync(join(home, ".cline"))
2306
2277
  }
2307
2278
  };
2308
2279
  function getAllAgents() {
@@ -2323,7 +2294,7 @@ function isValidAgentType(value) {
2323
2294
 
2324
2295
  // src/commands/agent/discovery.ts
2325
2296
  import { readdir, readFile } from "fs/promises";
2326
- import { join as join2, basename, extname } from "path";
2297
+ import { join as join2 } from "path";
2327
2298
  function parseSkillFrontmatter(content) {
2328
2299
  const match = content.match(/^---\n([\s\S]*?)\n---/);
2329
2300
  if (!match) return null;
@@ -2368,13 +2339,12 @@ async function discoverMarkdownAssets(basePath, category) {
2368
2339
  try {
2369
2340
  const entries = await readdir(basePath, { withFileTypes: true });
2370
2341
  for (const entry of entries) {
2371
- if (entry.isDirectory()) continue;
2372
- if (extname(entry.name) !== ".md") continue;
2373
- const filePath = join2(basePath, entry.name);
2374
- const name = basename(entry.name, ".md");
2342
+ if (!entry.isDirectory()) continue;
2343
+ const dirPath = join2(basePath, entry.name);
2344
+ const mdFile = join2(dirPath, `${entry.name}.md`);
2375
2345
  let description = "";
2376
2346
  try {
2377
- const content = await readFile(filePath, "utf-8");
2347
+ const content = await readFile(mdFile, "utf-8");
2378
2348
  const match = content.match(/^---\n([\s\S]*?)\n---/);
2379
2349
  if (match) {
2380
2350
  const descMatch = match[1].match(/description:\s*(.+)/);
@@ -2383,13 +2353,14 @@ async function discoverMarkdownAssets(basePath, category) {
2383
2353
  }
2384
2354
  }
2385
2355
  } catch {
2356
+ continue;
2386
2357
  }
2387
2358
  assets.push({
2388
- name,
2359
+ name: entry.name,
2389
2360
  description,
2390
- sourcePath: filePath,
2361
+ sourcePath: dirPath,
2391
2362
  category,
2392
- isDirectory: false
2363
+ isDirectory: true
2393
2364
  });
2394
2365
  }
2395
2366
  } catch {
@@ -2406,7 +2377,7 @@ async function discoverAllAssets(sourcePath) {
2406
2377
 
2407
2378
  // src/commands/agent/installer.ts
2408
2379
  import { mkdir, cp, readdir as readdir2, symlink as fsSymlink, lstat, rm, readlink } from "fs/promises";
2409
- import { join as join3, basename as basename2, normalize, resolve, sep, relative, dirname } from "path";
2380
+ import { join as join3, basename, normalize, resolve, sep, relative, dirname } from "path";
2410
2381
  import { platform } from "os";
2411
2382
 
2412
2383
  // src/commands/agent/constants.ts
@@ -2426,10 +2397,6 @@ function isPathSafe(basePath, targetPath) {
2426
2397
  const normalizedTarget = normalize(resolve(targetPath));
2427
2398
  return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
2428
2399
  }
2429
- function getCanonicalDir(category, scope, cwd) {
2430
- const baseDir = scope === "global" ? getDefaultConfigDir() : join3(cwd || process.cwd(), ".thought-cabinet");
2431
- return join3(baseDir, CATEGORY_SUBDIRS[category]);
2432
- }
2433
2400
  function getAgentDir(agentType, category, scope, cwd) {
2434
2401
  const agent = agents[agentType];
2435
2402
  const agentBase = scope === "global" && agent.globalConfigDir ? agent.globalConfigDir : join3(cwd || process.cwd(), agent.configDir);
@@ -2515,11 +2482,9 @@ async function installAssetForAgent(asset, agentType, options = {}) {
2515
2482
  };
2516
2483
  }
2517
2484
  const assetName = sanitizeName(asset.name);
2518
- const canonicalBase = getCanonicalDir(asset.category, scope, cwd);
2519
- const canonicalDir = join3(canonicalBase, assetName);
2520
2485
  const agentBase = getAgentDir(agentType, asset.category, scope, cwd);
2521
2486
  const agentDir = join3(agentBase, assetName);
2522
- if (!isPathSafe(canonicalBase, canonicalDir) || !isPathSafe(agentBase, agentDir)) {
2487
+ if (!isPathSafe(agentBase, agentDir)) {
2523
2488
  return {
2524
2489
  success: false,
2525
2490
  path: agentDir,
@@ -2533,7 +2498,7 @@ async function installAssetForAgent(asset, agentType, options = {}) {
2533
2498
  await copyDirectoryContents(asset.sourcePath, targetDir);
2534
2499
  } else {
2535
2500
  await mkdir(targetDir, { recursive: true });
2536
- const fileName = basename2(asset.sourcePath);
2501
+ const fileName = basename(asset.sourcePath);
2537
2502
  await cp(asset.sourcePath, join3(targetDir, fileName), { dereference: true });
2538
2503
  }
2539
2504
  };
@@ -2542,26 +2507,15 @@ async function installAssetForAgent(asset, agentType, options = {}) {
2542
2507
  await copyAsset(agentDir);
2543
2508
  return { success: true, path: agentDir, mode: "copy" };
2544
2509
  }
2545
- await cleanAndCreateDirectory(canonicalDir);
2546
- await copyAsset(canonicalDir);
2547
- const symlinkCreated = await createSymlink(canonicalDir, agentDir);
2510
+ await rm(agentDir, { recursive: true, force: true });
2511
+ await mkdir(dirname(agentDir), { recursive: true });
2512
+ const symlinkCreated = await createSymlink(asset.sourcePath, agentDir);
2548
2513
  if (!symlinkCreated) {
2549
2514
  await cleanAndCreateDirectory(agentDir);
2550
2515
  await copyAsset(agentDir);
2551
- return {
2552
- success: true,
2553
- path: agentDir,
2554
- canonicalPath: canonicalDir,
2555
- mode: "symlink",
2556
- symlinkFailed: true
2557
- };
2516
+ return { success: true, path: agentDir, mode: "symlink", symlinkFailed: true };
2558
2517
  }
2559
- return {
2560
- success: true,
2561
- path: agentDir,
2562
- canonicalPath: canonicalDir,
2563
- mode: "symlink"
2564
- };
2518
+ return { success: true, path: agentDir, mode: "symlink" };
2565
2519
  } catch (error) {
2566
2520
  return {
2567
2521
  success: false,
@@ -2664,30 +2618,7 @@ async function agentInitCommand(options) {
2664
2618
  scope = scopeChoice;
2665
2619
  }
2666
2620
  }
2667
- let mode = options.mode ?? "symlink";
2668
- if (!options.mode && !options.all) {
2669
- const modeChoice = await p4.select({
2670
- message: "Installation mode:",
2671
- options: [
2672
- {
2673
- value: "symlink",
2674
- label: "Symlink (recommended)",
2675
- hint: "Canonical storage + symlinks; update once, all agents see changes"
2676
- },
2677
- {
2678
- value: "copy",
2679
- label: "Copy",
2680
- hint: "Independent copies for each agent"
2681
- }
2682
- ],
2683
- initialValue: "symlink"
2684
- });
2685
- if (p4.isCancel(modeChoice)) {
2686
- p4.cancel("Operation cancelled.");
2687
- process.exit(0);
2688
- }
2689
- mode = modeChoice;
2690
- }
2621
+ const mode = options.mode ?? "symlink";
2691
2622
  if (!options.force) {
2692
2623
  const cwd2 = process.cwd();
2693
2624
  for (const agentType of selectedAgents) {
@@ -2769,7 +2700,6 @@ async function agentInitCommand(options) {
2769
2700
  const cwd = process.cwd();
2770
2701
  let totalInstalled = 0;
2771
2702
  let totalFailed = 0;
2772
- const symlinkWarnings = [];
2773
2703
  const s = p4.spinner();
2774
2704
  s.start("Installing assets...");
2775
2705
  for (const asset of assetsToInstall) {
@@ -2778,7 +2708,7 @@ async function agentInitCommand(options) {
2778
2708
  if (result.success) {
2779
2709
  totalInstalled++;
2780
2710
  if (result.symlinkFailed) {
2781
- symlinkWarnings.push(
2711
+ p4.log.warn(
2782
2712
  `${asset.name} \u2192 ${agents[agentType].displayName}: symlink failed, copied instead`
2783
2713
  );
2784
2714
  }
@@ -2789,12 +2719,6 @@ async function agentInitCommand(options) {
2789
2719
  }
2790
2720
  }
2791
2721
  s.stop("Installation complete.");
2792
- if (symlinkWarnings.length > 0) {
2793
- p4.log.warn("Symlink warnings:");
2794
- for (const warning of symlinkWarnings) {
2795
- p4.log.warn(` ${warning}`);
2796
- }
2797
- }
2798
2722
  const agentNames = selectedAgents.map((a) => agents[a].displayName).join(", ");
2799
2723
  let message = `Installed ${totalInstalled} asset(s) to ${agentNames}`;
2800
2724
  if (totalFailed > 0) {
@@ -2812,7 +2736,7 @@ async function agentInitCommand(options) {
2812
2736
  // src/commands/agent.ts
2813
2737
  function agentCommand(program) {
2814
2738
  const agent = program.command("agent").description("Manage coding agent configuration");
2815
- agent.command("init").description("Initialize coding agent configuration in current directory").option("--target <agents...>", "Target agents (e.g., claude-code codebuddy cursor)").option("-g, --global", "Install to global scope").option("--mode <mode>", "Installation mode: symlink or copy (default: symlink)").option("--source <path>", "Source directory for assets").option("--force", "Force overwrite of existing installations").option("--all", "Install all assets without prompting").action(async (options) => {
2739
+ agent.command("init").description("Initialize coding agent configuration in current directory").option("--target <agents...>", "Target agents (e.g., claude-code codebuddy)").option("-g, --global", "Install to global scope").option("--mode <mode>", "Installation mode: symlink or copy (default: symlink)").option("--source <path>", "Source directory for assets").option("--force", "Force overwrite of existing installations").option("--all", "Install all assets without prompting").action(async (options) => {
2816
2740
  const agentTypes = options.target?.map((a) => {
2817
2741
  if (!isValidAgentType(a)) {
2818
2742
  console.error(`Unknown agent: ${a}`);
@@ -2963,23 +2887,10 @@ function tmuxKillSession(sessionName) {
2963
2887
  // src/agent-config.ts
2964
2888
  import fs13 from "fs";
2965
2889
  import path16 from "path";
2966
- var CANONICAL_DIR = ".thought-cabinet";
2967
2890
  function detectAgentConfigDirs(sourceDir) {
2968
2891
  const uniqueDirs = [...new Set(Object.values(agents).map((a) => a.configDir))];
2969
2892
  return uniqueDirs.filter((dir) => fs13.existsSync(path16.join(sourceDir, dir)));
2970
2893
  }
2971
- function getCanonicalSymlinkTarget(entryPath, canonicalDir) {
2972
- try {
2973
- if (!fs13.lstatSync(entryPath).isSymbolicLink()) return null;
2974
- const linkTarget = fs13.readlinkSync(entryPath);
2975
- const resolvedTarget = path16.resolve(path16.dirname(entryPath), linkTarget);
2976
- const resolvedCanonical = path16.resolve(canonicalDir);
2977
- const isInsideCanonical = resolvedTarget === resolvedCanonical || resolvedTarget.startsWith(resolvedCanonical + path16.sep);
2978
- return isInsideCanonical ? path16.relative(resolvedCanonical, resolvedTarget) : null;
2979
- } catch {
2980
- return null;
2981
- }
2982
- }
2983
2894
  function symlinkOrCopy(srcPath, destPath, linkTarget) {
2984
2895
  try {
2985
2896
  fs13.symlinkSync(linkTarget, destPath);
@@ -2990,19 +2901,15 @@ function symlinkOrCopy(srcPath, destPath, linkTarget) {
2990
2901
  }
2991
2902
  }
2992
2903
  }
2993
- function copyDirWithSymlinkHandling(srcDir, destDir, sourceCanonicalDir, targetCanonicalDir) {
2904
+ function copyDirWithSymlinkHandling(srcDir, destDir) {
2994
2905
  fs13.mkdirSync(destDir, { recursive: true });
2995
2906
  for (const entry of fs13.readdirSync(srcDir, { withFileTypes: true })) {
2996
2907
  const srcPath = path16.join(srcDir, entry.name);
2997
2908
  const destPath = path16.join(destDir, entry.name);
2998
- const canonicalRelPath = getCanonicalSymlinkTarget(srcPath, sourceCanonicalDir);
2999
- if (canonicalRelPath !== null) {
3000
- const newTarget = path16.join(targetCanonicalDir, canonicalRelPath);
3001
- symlinkOrCopy(srcPath, destPath, path16.relative(path16.dirname(destPath), newTarget));
3002
- } else if (entry.isSymbolicLink()) {
2909
+ if (entry.isSymbolicLink()) {
3003
2910
  symlinkOrCopy(srcPath, destPath, fs13.readlinkSync(srcPath));
3004
2911
  } else if (entry.isDirectory()) {
3005
- copyDirWithSymlinkHandling(srcPath, destPath, sourceCanonicalDir, targetCanonicalDir);
2912
+ copyDirWithSymlinkHandling(srcPath, destPath);
3006
2913
  } else {
3007
2914
  fs13.cpSync(srcPath, destPath);
3008
2915
  }
@@ -3012,26 +2919,15 @@ function copyAgentConfigDirs(options) {
3012
2919
  const { sourceDir, targetDir } = options;
3013
2920
  const copied = [];
3014
2921
  const skipped = [];
3015
- const sourceCanonicalDir = path16.join(sourceDir, CANONICAL_DIR);
3016
- const targetCanonicalDir = path16.join(targetDir, CANONICAL_DIR);
3017
- const canonicalCopied = fs13.existsSync(sourceCanonicalDir);
3018
- if (canonicalCopied) {
3019
- fs13.cpSync(sourceCanonicalDir, targetCanonicalDir, { recursive: true });
3020
- }
3021
2922
  for (const dirName of detectAgentConfigDirs(sourceDir)) {
3022
2923
  try {
3023
- copyDirWithSymlinkHandling(
3024
- path16.join(sourceDir, dirName),
3025
- path16.join(targetDir, dirName),
3026
- sourceCanonicalDir,
3027
- targetCanonicalDir
3028
- );
2924
+ copyDirWithSymlinkHandling(path16.join(sourceDir, dirName), path16.join(targetDir, dirName));
3029
2925
  copied.push(dirName);
3030
2926
  } catch {
3031
2927
  skipped.push(dirName);
3032
2928
  }
3033
2929
  }
3034
- return { copied, skipped, canonicalCopied };
2930
+ return { copied, skipped };
3035
2931
  }
3036
2932
 
3037
2933
  // src/commands/worktree/add.ts
@@ -3103,11 +2999,8 @@ async function worktreeAddCommand(name, options) {
3103
2999
  sourceDir: mainRoot,
3104
3000
  targetDir: worktreePath
3105
3001
  });
3106
- if (configResult.copied.length > 0 || configResult.canonicalCopied) {
3107
- const parts = [];
3108
- if (configResult.canonicalCopied) parts.push(".thought-cabinet");
3109
- parts.push(...configResult.copied);
3110
- console.log(chalk16.gray(`Copied config: ${parts.join(", ")}`));
3002
+ if (configResult.copied.length > 0) {
3003
+ console.log(chalk16.gray(`Copied config: ${configResult.copied.join(", ")}`));
3111
3004
  }
3112
3005
  if (options.thoughts !== false) {
3113
3006
  initializeWorktreeThoughts(mainRoot, worktreePath);
@@ -3631,7 +3524,7 @@ function getBranchNames() {
3631
3524
  }
3632
3525
  }
3633
3526
  function getAgentNames() {
3634
- return ["claude-code", "codebuddy", "cursor", "codex", "gemini-cli", "cline"];
3527
+ return ["claude-code", "codebuddy"];
3635
3528
  }
3636
3529
 
3637
3530
  // src/completion/handler.ts