thought-cabinet 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2113 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/thoughts/init.ts
7
+ import fs4 from "fs";
8
+ import path6 from "path";
9
+ import { execSync as execSync2 } from "child_process";
10
+ import chalk2 from "chalk";
11
+ import * as p from "@clack/prompts";
12
+
13
+ // src/config.ts
14
+ import dotenv from "dotenv";
15
+ import fs from "fs";
16
+ import path from "path";
17
+ import chalk from "chalk";
18
+ dotenv.config();
19
+ var _ConfigResolver = class _ConfigResolver {
20
+ constructor(options = {}) {
21
+ this.configFile = this.loadConfigFile(options.configFile);
22
+ this.configFilePath = this.getConfigFilePath(options.configFile);
23
+ }
24
+ loadConfigFile(configFile) {
25
+ if (configFile) {
26
+ const configContent = fs.readFileSync(configFile, "utf8");
27
+ return JSON.parse(configContent);
28
+ }
29
+ const configPaths = [_ConfigResolver.DEFAULT_CONFIG_FILE, getDefaultConfigPath()];
30
+ for (const configPath of configPaths) {
31
+ try {
32
+ if (fs.existsSync(configPath)) {
33
+ const configContent = fs.readFileSync(configPath, "utf8");
34
+ return JSON.parse(configContent);
35
+ }
36
+ } catch (error) {
37
+ console.error(chalk.yellow(`Warning: Could not parse config file ${configPath}: ${error}`));
38
+ }
39
+ }
40
+ return {};
41
+ }
42
+ getConfigFilePath(configFile) {
43
+ if (configFile) return configFile;
44
+ const configPaths = [_ConfigResolver.DEFAULT_CONFIG_FILE, getDefaultConfigPath()];
45
+ for (const configPath of configPaths) {
46
+ try {
47
+ if (fs.existsSync(configPath)) {
48
+ return configPath;
49
+ }
50
+ } catch {
51
+ }
52
+ }
53
+ return getDefaultConfigPath();
54
+ }
55
+ };
56
+ _ConfigResolver.DEFAULT_CONFIG_FILE = "config.json";
57
+ var ConfigResolver = _ConfigResolver;
58
+ function saveConfigFile(config, configFile) {
59
+ const configPath = configFile || getDefaultConfigPath();
60
+ console.log(chalk.yellow(`Writing config to ${configPath}`));
61
+ const configDir = path.dirname(configPath);
62
+ fs.mkdirSync(configDir, { recursive: true });
63
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
64
+ console.log(chalk.green("Config saved successfully"));
65
+ }
66
+ function getDefaultConfigPath() {
67
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || "", ".config");
68
+ return path.join(xdgConfigHome, "thought-cabinet", ConfigResolver.DEFAULT_CONFIG_FILE);
69
+ }
70
+
71
+ // src/commands/thoughts/utils/config.ts
72
+ function loadThoughtsConfig(options = {}) {
73
+ const resolver = new ConfigResolver(options);
74
+ return resolver.configFile.thoughts || null;
75
+ }
76
+ function saveThoughtsConfig(thoughtsConfig, options = {}) {
77
+ const resolver = new ConfigResolver(options);
78
+ resolver.configFile.thoughts = thoughtsConfig;
79
+ saveConfigFile(resolver.configFile, options.configFile);
80
+ }
81
+
82
+ // src/commands/thoughts/utils/paths.ts
83
+ import path2 from "path";
84
+ import os from "os";
85
+ function getDefaultThoughtsRepo() {
86
+ return path2.join(os.homedir(), "thoughts");
87
+ }
88
+ function expandPath(filePath) {
89
+ if (filePath.startsWith("~/")) {
90
+ return path2.join(os.homedir(), filePath.slice(2));
91
+ }
92
+ return path2.resolve(filePath);
93
+ }
94
+ function getCurrentRepoPath() {
95
+ return process.cwd();
96
+ }
97
+ function getRepoNameFromPath(repoPath) {
98
+ const parts = repoPath.split(path2.sep);
99
+ return parts[parts.length - 1] || "unnamed_repo";
100
+ }
101
+ function getRepoThoughtsPath(thoughtsRepoOrConfig, reposDirOrRepoName, repoName) {
102
+ if (typeof thoughtsRepoOrConfig === "string") {
103
+ return path2.join(expandPath(thoughtsRepoOrConfig), reposDirOrRepoName, repoName);
104
+ }
105
+ const config = thoughtsRepoOrConfig;
106
+ return path2.join(expandPath(config.thoughtsRepo), config.reposDir, reposDirOrRepoName);
107
+ }
108
+ function getGlobalThoughtsPath(thoughtsRepoOrConfig, globalDir) {
109
+ if (typeof thoughtsRepoOrConfig === "string") {
110
+ return path2.join(expandPath(thoughtsRepoOrConfig), globalDir);
111
+ }
112
+ const config = thoughtsRepoOrConfig;
113
+ return path2.join(expandPath(config.thoughtsRepo), config.globalDir);
114
+ }
115
+
116
+ // src/commands/thoughts/utils/repository.ts
117
+ import fs2 from "fs";
118
+ import path4 from "path";
119
+ import { execSync } from "child_process";
120
+
121
+ // src/templates/gitignore.ts
122
+ function generateGitignore() {
123
+ return `# OS files
124
+ .DS_Store
125
+ Thumbs.db
126
+
127
+ # Editor files
128
+ .vscode/
129
+ .idea/
130
+ *.swp
131
+ *.swo
132
+ *~
133
+
134
+ # Temporary files
135
+ *.tmp
136
+ *.bak
137
+ `;
138
+ }
139
+
140
+ // src/templates/readme.ts
141
+ function generateRepoReadme({ repoName, user }) {
142
+ return `# ${repoName} Thoughts
143
+
144
+ This directory contains thoughts and notes specific to the ${repoName} repository.
145
+
146
+ - \`${user}/\` - Your personal notes for this repository
147
+ - \`shared/\` - Team-shared notes for this repository
148
+ `;
149
+ }
150
+ function generateGlobalReadme({ user }) {
151
+ return `# Global Thoughts
152
+
153
+ This directory contains thoughts and notes that apply across all repositories.
154
+
155
+ - \`${user}/\` - Your personal cross-repository notes
156
+ - \`shared/\` - Team-shared cross-repository notes
157
+ `;
158
+ }
159
+
160
+ // src/templates/agentMd.ts
161
+ import path3 from "path";
162
+ import os2 from "os";
163
+ function generateAgentMd({
164
+ thoughtsRepo,
165
+ reposDir,
166
+ repoName,
167
+ user,
168
+ productName
169
+ }) {
170
+ const reposPath = path3.join(thoughtsRepo, reposDir, repoName).replace(os2.homedir(), "~");
171
+ const globalPath = path3.join(thoughtsRepo, "global").replace(os2.homedir(), "~");
172
+ return `# Thoughts Directory Structure
173
+
174
+ This directory contains developer thoughts and notes for the ${repoName} repository.
175
+ It is managed by the ThoughtCabinet thoughts system and should not be committed to the code repository.
176
+
177
+ ## Structure
178
+
179
+ - \`${user}/\` \u2192 Your personal notes for this repository (symlink to ${reposPath}/${user})
180
+ - \`shared/\` \u2192 Team-shared notes for this repository (symlink to ${reposPath}/shared)
181
+ - \`global/\` \u2192 Cross-repository thoughts (symlink to ${globalPath})
182
+ - \`${user}/\` - Your personal notes that apply across all repositories
183
+ - \`shared/\` - Team-shared notes that apply across all repositories
184
+ - \`searchable/\` \u2192 Hard links for search tools (auto-generated)
185
+
186
+ ## Searching in Thoughts
187
+
188
+ The \`searchable/\` directory contains hard links to all thoughts files accessible in this repository. This allows search tools to find content without following symlinks.
189
+
190
+ **IMPORTANT**:
191
+ - Files in \`thoughts/searchable/\` are hard links to the original files (editing either updates both)
192
+ - For clarity and consistency, always reference files by their canonical path (e.g., \`thoughts/${user}/todo.md\`, not \`thoughts/searchable/${user}/todo.md\`)
193
+ - The \`searchable/\` directory is automatically updated when you run \`thoughtcabinet sync\`
194
+
195
+ This design ensures that:
196
+ 1. Search tools can find all your thoughts content easily
197
+ 2. The symlink structure remains intact for git operations
198
+ 3. Files remain editable while maintaining consistent path references
199
+
200
+ ## Usage
201
+
202
+ Create markdown files in these directories to document:
203
+
204
+ - Architecture decisions
205
+ - Design notes
206
+ - TODO items
207
+ - Investigation results
208
+ - Any other development thoughts
209
+
210
+ Quick access:
211
+
212
+ - \`thoughts/${user}/\` for your repo-specific notes (most common)
213
+ - \`thoughts/global/${user}/\` for your cross-repo notes
214
+
215
+ These files will be automatically synchronized with your thoughts repository when you commit code changes (when using ${productName}).
216
+
217
+ ## Important
218
+
219
+ - Never commit the thoughts/ directory to your code repository
220
+ - The git pre-commit hook will prevent accidental commits
221
+ - Use \`thoughtcabinet sync\` to manually sync changes
222
+ - Use \`thoughtcabinet status\` to see sync status
223
+ `;
224
+ }
225
+ function generateClaudeMd(params) {
226
+ return generateAgentMd({ ...params, productName: "Claude Code" });
227
+ }
228
+
229
+ // src/templates/gitHooks.ts
230
+ var HOOK_VERSION = "1";
231
+ function generatePreCommitHook({ hookPath }) {
232
+ return `#!/bin/bash
233
+ # ThoughtCabinet thoughts protection - prevent committing thoughts directory
234
+ # Version: ${HOOK_VERSION}
235
+
236
+ if git diff --cached --name-only | grep -q "^thoughts/"; then
237
+ echo "\u274C Cannot commit thoughts/ to code repository"
238
+ echo "The thoughts directory should only exist in your separate thoughts repository."
239
+ git reset HEAD -- thoughts/
240
+ exit 1
241
+ fi
242
+
243
+ # Call any existing pre-commit hook
244
+ if [ -f "${hookPath}.old" ]; then
245
+ "${hookPath}.old" "$@"
246
+ fi
247
+ `;
248
+ }
249
+ function generatePostCommitHook({ hookPath }) {
250
+ return `#!/bin/bash
251
+ # ThoughtCabinet thoughts auto-sync
252
+ # Version: ${HOOK_VERSION}
253
+
254
+ # Check if we're in a worktree
255
+ if [ -f .git ]; then
256
+ # Skip auto-sync in worktrees to avoid repository boundary confusion
257
+ exit 0
258
+ fi
259
+
260
+ # Get the commit message
261
+ COMMIT_MSG=$(git log -1 --pretty=%B)
262
+
263
+ # Auto-sync thoughts after each commit (only in non-worktree repos)
264
+ thoughtcabinet sync --message "Auto-sync with commit: $COMMIT_MSG" >/dev/null 2>&1 &
265
+
266
+ # Call any existing post-commit hook
267
+ if [ -f "${hookPath}.old" ]; then
268
+ "${hookPath}.old" "$@"
269
+ fi
270
+ `;
271
+ }
272
+
273
+ // src/commands/thoughts/utils/repository.ts
274
+ function ensureThoughtsRepoExists(configOrThoughtsRepo, reposDir, globalDir) {
275
+ let thoughtsRepo;
276
+ let effectiveReposDir;
277
+ let effectiveGlobalDir;
278
+ if (typeof configOrThoughtsRepo === "string") {
279
+ thoughtsRepo = configOrThoughtsRepo;
280
+ effectiveReposDir = reposDir;
281
+ effectiveGlobalDir = globalDir;
282
+ } else {
283
+ thoughtsRepo = configOrThoughtsRepo.thoughtsRepo;
284
+ effectiveReposDir = configOrThoughtsRepo.reposDir;
285
+ effectiveGlobalDir = configOrThoughtsRepo.globalDir;
286
+ }
287
+ const expandedRepo = expandPath(thoughtsRepo);
288
+ if (!fs2.existsSync(expandedRepo)) {
289
+ fs2.mkdirSync(expandedRepo, { recursive: true });
290
+ }
291
+ const expandedRepos = path4.join(expandedRepo, effectiveReposDir);
292
+ const expandedGlobal = path4.join(expandedRepo, effectiveGlobalDir);
293
+ if (!fs2.existsSync(expandedRepos)) {
294
+ fs2.mkdirSync(expandedRepos, { recursive: true });
295
+ }
296
+ if (!fs2.existsSync(expandedGlobal)) {
297
+ fs2.mkdirSync(expandedGlobal, { recursive: true });
298
+ }
299
+ const gitPath = path4.join(expandedRepo, ".git");
300
+ const isGitRepo = fs2.existsSync(gitPath) && (fs2.statSync(gitPath).isDirectory() || fs2.statSync(gitPath).isFile());
301
+ if (!isGitRepo) {
302
+ execSync("git init", { cwd: expandedRepo });
303
+ const gitignore = generateGitignore();
304
+ fs2.writeFileSync(path4.join(expandedRepo, ".gitignore"), gitignore);
305
+ execSync("git add .gitignore", { cwd: expandedRepo });
306
+ execSync('git commit -m "Initial thoughts repository setup"', { cwd: expandedRepo });
307
+ }
308
+ }
309
+ function createThoughtsDirectoryStructure(configOrThoughtsRepo, reposDirOrRepoName, globalDirOrUser, repoName, user) {
310
+ let resolvedConfig;
311
+ let effectiveRepoName;
312
+ let effectiveUser;
313
+ if (typeof configOrThoughtsRepo === "string") {
314
+ resolvedConfig = {
315
+ thoughtsRepo: configOrThoughtsRepo,
316
+ reposDir: reposDirOrRepoName,
317
+ globalDir: globalDirOrUser
318
+ };
319
+ effectiveRepoName = repoName;
320
+ effectiveUser = user;
321
+ } else {
322
+ resolvedConfig = configOrThoughtsRepo;
323
+ effectiveRepoName = reposDirOrRepoName;
324
+ effectiveUser = globalDirOrUser;
325
+ }
326
+ const repoThoughtsPath = getRepoThoughtsPath(
327
+ resolvedConfig.thoughtsRepo,
328
+ resolvedConfig.reposDir,
329
+ effectiveRepoName
330
+ );
331
+ const repoUserPath = path4.join(repoThoughtsPath, effectiveUser);
332
+ const repoSharedPath = path4.join(repoThoughtsPath, "shared");
333
+ const globalPath = getGlobalThoughtsPath(resolvedConfig.thoughtsRepo, resolvedConfig.globalDir);
334
+ const globalUserPath = path4.join(globalPath, effectiveUser);
335
+ const globalSharedPath = path4.join(globalPath, "shared");
336
+ for (const dir of [repoUserPath, repoSharedPath, globalUserPath, globalSharedPath]) {
337
+ if (!fs2.existsSync(dir)) {
338
+ fs2.mkdirSync(dir, { recursive: true });
339
+ }
340
+ }
341
+ const repoReadme = generateRepoReadme({
342
+ repoName: effectiveRepoName,
343
+ user: effectiveUser
344
+ });
345
+ const globalReadme = generateGlobalReadme({
346
+ user: effectiveUser
347
+ });
348
+ if (!fs2.existsSync(path4.join(repoThoughtsPath, "README.md"))) {
349
+ fs2.writeFileSync(path4.join(repoThoughtsPath, "README.md"), repoReadme);
350
+ }
351
+ if (!fs2.existsSync(path4.join(globalPath, "README.md"))) {
352
+ fs2.writeFileSync(path4.join(globalPath, "README.md"), globalReadme);
353
+ }
354
+ }
355
+
356
+ // src/commands/thoughts/utils/symlinks.ts
357
+ import fs3 from "fs";
358
+ import path5 from "path";
359
+ function updateSymlinksForNewUsers(currentRepoPath, configOrThoughtsRepo, reposDirOrRepoName, repoNameOrCurrentUser, currentUser) {
360
+ let resolvedConfig;
361
+ let effectiveRepoName;
362
+ let effectiveUser;
363
+ if (typeof configOrThoughtsRepo === "string") {
364
+ resolvedConfig = {
365
+ thoughtsRepo: configOrThoughtsRepo,
366
+ reposDir: reposDirOrRepoName
367
+ };
368
+ effectiveRepoName = repoNameOrCurrentUser;
369
+ effectiveUser = currentUser;
370
+ } else {
371
+ resolvedConfig = configOrThoughtsRepo;
372
+ effectiveRepoName = reposDirOrRepoName;
373
+ effectiveUser = repoNameOrCurrentUser;
374
+ }
375
+ const thoughtsDir = path5.join(currentRepoPath, "thoughts");
376
+ const repoThoughtsPath = getRepoThoughtsPath(
377
+ resolvedConfig.thoughtsRepo,
378
+ resolvedConfig.reposDir,
379
+ effectiveRepoName
380
+ );
381
+ const addedSymlinks = [];
382
+ if (!fs3.existsSync(thoughtsDir) || !fs3.existsSync(repoThoughtsPath)) {
383
+ return addedSymlinks;
384
+ }
385
+ const entries = fs3.readdirSync(repoThoughtsPath, { withFileTypes: true });
386
+ const userDirs = entries.filter((entry) => entry.isDirectory() && entry.name !== "shared" && !entry.name.startsWith(".")).map((entry) => entry.name);
387
+ for (const userName of userDirs) {
388
+ const symlinkPath = path5.join(thoughtsDir, userName);
389
+ const targetPath = path5.join(repoThoughtsPath, userName);
390
+ if (!fs3.existsSync(symlinkPath) && userName !== effectiveUser) {
391
+ try {
392
+ fs3.symlinkSync(targetPath, symlinkPath, "dir");
393
+ addedSymlinks.push(userName);
394
+ } catch {
395
+ }
396
+ }
397
+ }
398
+ return addedSymlinks;
399
+ }
400
+
401
+ // src/commands/thoughts/profile/utils.ts
402
+ function resolveProfileForRepo(config, repoPath) {
403
+ const mapping = config.repoMappings[repoPath];
404
+ if (typeof mapping === "string") {
405
+ return {
406
+ thoughtsRepo: config.thoughtsRepo,
407
+ reposDir: config.reposDir,
408
+ globalDir: config.globalDir,
409
+ profileName: void 0
410
+ };
411
+ }
412
+ if (mapping && typeof mapping === "object") {
413
+ const profileName = mapping.profile;
414
+ if (profileName && config.profiles && config.profiles[profileName]) {
415
+ const profile = config.profiles[profileName];
416
+ return {
417
+ thoughtsRepo: profile.thoughtsRepo,
418
+ reposDir: profile.reposDir,
419
+ globalDir: profile.globalDir,
420
+ profileName
421
+ };
422
+ }
423
+ return {
424
+ thoughtsRepo: config.thoughtsRepo,
425
+ reposDir: config.reposDir,
426
+ globalDir: config.globalDir,
427
+ profileName: void 0
428
+ };
429
+ }
430
+ return {
431
+ thoughtsRepo: config.thoughtsRepo,
432
+ reposDir: config.reposDir,
433
+ globalDir: config.globalDir,
434
+ profileName: void 0
435
+ };
436
+ }
437
+ function getRepoNameFromMapping(mapping) {
438
+ if (!mapping) return void 0;
439
+ if (typeof mapping === "string") return mapping;
440
+ return mapping.repo;
441
+ }
442
+ function getProfileNameFromMapping(mapping) {
443
+ if (!mapping) return void 0;
444
+ if (typeof mapping === "string") return void 0;
445
+ return mapping.profile;
446
+ }
447
+ function validateProfile(config, profileName) {
448
+ return !!(config.profiles && config.profiles[profileName]);
449
+ }
450
+ function sanitizeProfileName(name) {
451
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
452
+ }
453
+
454
+ // src/commands/thoughts/init.ts
455
+ function sanitizeDirectoryName(name) {
456
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
457
+ }
458
+ function checkExistingSetup(config) {
459
+ const thoughtsDir = path6.join(process.cwd(), "thoughts");
460
+ if (!fs4.existsSync(thoughtsDir)) {
461
+ return { exists: false, isValid: false };
462
+ }
463
+ if (!fs4.lstatSync(thoughtsDir).isDirectory()) {
464
+ return { exists: true, isValid: false, message: "thoughts exists but is not a directory" };
465
+ }
466
+ if (!config) {
467
+ return {
468
+ exists: true,
469
+ isValid: false,
470
+ message: "thoughts directory exists but configuration is missing"
471
+ };
472
+ }
473
+ const userPath = path6.join(thoughtsDir, config.user);
474
+ const sharedPath = path6.join(thoughtsDir, "shared");
475
+ const globalPath = path6.join(thoughtsDir, "global");
476
+ const hasUser = fs4.existsSync(userPath) && fs4.lstatSync(userPath).isSymbolicLink();
477
+ const hasShared = fs4.existsSync(sharedPath) && fs4.lstatSync(sharedPath).isSymbolicLink();
478
+ const hasGlobal = fs4.existsSync(globalPath) && fs4.lstatSync(globalPath).isSymbolicLink();
479
+ if (!hasUser || !hasShared || !hasGlobal) {
480
+ return {
481
+ exists: true,
482
+ isValid: false,
483
+ message: "thoughts directory exists but symlinks are missing or broken"
484
+ };
485
+ }
486
+ return { exists: true, isValid: true };
487
+ }
488
+ function setupGitHooks(repoPath) {
489
+ const updated = [];
490
+ let gitCommonDir;
491
+ try {
492
+ gitCommonDir = execSync2("git rev-parse --git-common-dir", {
493
+ cwd: repoPath,
494
+ encoding: "utf8",
495
+ stdio: "pipe"
496
+ }).trim();
497
+ if (!path6.isAbsolute(gitCommonDir)) {
498
+ gitCommonDir = path6.join(repoPath, gitCommonDir);
499
+ }
500
+ } catch (error) {
501
+ throw new Error(`Failed to find git common directory: ${error}`);
502
+ }
503
+ const hooksDir = path6.join(gitCommonDir, "hooks");
504
+ if (!fs4.existsSync(hooksDir)) {
505
+ fs4.mkdirSync(hooksDir, { recursive: true });
506
+ }
507
+ const preCommitPath = path6.join(hooksDir, "pre-commit");
508
+ const preCommitContent = generatePreCommitHook({ hookPath: preCommitPath });
509
+ const postCommitPath = path6.join(hooksDir, "post-commit");
510
+ const postCommitContent = generatePostCommitHook({ hookPath: postCommitPath });
511
+ const hookNeedsUpdate = (hookPath) => {
512
+ if (!fs4.existsSync(hookPath)) return true;
513
+ const content = fs4.readFileSync(hookPath, "utf8");
514
+ if (!content.includes("ThoughtCabinet thoughts")) return false;
515
+ const versionMatch = content.match(/# Version: (\d+)/);
516
+ if (!versionMatch) return true;
517
+ const currentVersion = parseInt(versionMatch[1]);
518
+ return currentVersion < parseInt(HOOK_VERSION);
519
+ };
520
+ if (fs4.existsSync(preCommitPath)) {
521
+ const content = fs4.readFileSync(preCommitPath, "utf8");
522
+ if (!content.includes("ThoughtCabinet thoughts") || hookNeedsUpdate(preCommitPath)) {
523
+ if (!content.includes("ThoughtCabinet thoughts")) {
524
+ fs4.renameSync(preCommitPath, `${preCommitPath}.old`);
525
+ } else {
526
+ fs4.unlinkSync(preCommitPath);
527
+ }
528
+ }
529
+ }
530
+ if (fs4.existsSync(postCommitPath)) {
531
+ const content = fs4.readFileSync(postCommitPath, "utf8");
532
+ if (!content.includes("ThoughtCabinet thoughts") || hookNeedsUpdate(postCommitPath)) {
533
+ if (!content.includes("ThoughtCabinet thoughts")) {
534
+ fs4.renameSync(postCommitPath, `${postCommitPath}.old`);
535
+ } else {
536
+ fs4.unlinkSync(postCommitPath);
537
+ }
538
+ }
539
+ }
540
+ if (!fs4.existsSync(preCommitPath) || hookNeedsUpdate(preCommitPath)) {
541
+ fs4.writeFileSync(preCommitPath, preCommitContent);
542
+ fs4.chmodSync(preCommitPath, "755");
543
+ updated.push("pre-commit");
544
+ }
545
+ if (!fs4.existsSync(postCommitPath) || hookNeedsUpdate(postCommitPath)) {
546
+ fs4.writeFileSync(postCommitPath, postCommitContent);
547
+ fs4.chmodSync(postCommitPath, "755");
548
+ updated.push("post-commit");
549
+ }
550
+ return { updated };
551
+ }
552
+ async function thoughtsInitCommand(options) {
553
+ try {
554
+ if (!options.directory && !process.stdin.isTTY) {
555
+ p.log.error("Not running in interactive terminal.");
556
+ p.log.info("Use --directory flag to specify the repository directory name.");
557
+ process.exit(1);
558
+ }
559
+ const currentRepo = getCurrentRepoPath();
560
+ try {
561
+ execSync2("git rev-parse --git-dir", { stdio: "pipe" });
562
+ } catch {
563
+ p.log.error("Not in a git repository");
564
+ process.exit(1);
565
+ }
566
+ let config = loadThoughtsConfig(options);
567
+ if (!config) {
568
+ p.intro(chalk2.blue("Initial Thoughts Setup"));
569
+ p.log.info("First, let's configure your global thoughts system.");
570
+ const defaultRepo = getDefaultThoughtsRepo();
571
+ p.log.message(
572
+ chalk2.gray("This is where all your thoughts across all projects will be stored.")
573
+ );
574
+ const thoughtsRepoInput = await p.text({
575
+ message: "Thoughts repository location:",
576
+ initialValue: defaultRepo,
577
+ placeholder: defaultRepo
578
+ });
579
+ if (p.isCancel(thoughtsRepoInput)) {
580
+ p.cancel("Operation cancelled.");
581
+ process.exit(0);
582
+ }
583
+ const thoughtsRepo = thoughtsRepoInput || defaultRepo;
584
+ p.log.message(chalk2.gray("Your thoughts will be organized into two main directories:"));
585
+ p.log.message(chalk2.gray("- Repository-specific thoughts (one subdirectory per project)"));
586
+ p.log.message(chalk2.gray("- Global thoughts (shared across all projects)"));
587
+ const reposDirInput = await p.text({
588
+ message: "Directory name for repository-specific thoughts:",
589
+ initialValue: "repos",
590
+ placeholder: "repos"
591
+ });
592
+ if (p.isCancel(reposDirInput)) {
593
+ p.cancel("Operation cancelled.");
594
+ process.exit(0);
595
+ }
596
+ const reposDir2 = reposDirInput || "repos";
597
+ const globalDirInput = await p.text({
598
+ message: "Directory name for global thoughts:",
599
+ initialValue: "global",
600
+ placeholder: "global"
601
+ });
602
+ if (p.isCancel(globalDirInput)) {
603
+ p.cancel("Operation cancelled.");
604
+ process.exit(0);
605
+ }
606
+ const globalDir = globalDirInput || "global";
607
+ const defaultUser = process.env.USER || "user";
608
+ let user = "";
609
+ while (!user || user.toLowerCase() === "global") {
610
+ const userInput = await p.text({
611
+ message: "Your username:",
612
+ initialValue: defaultUser,
613
+ placeholder: defaultUser,
614
+ validate: (value) => {
615
+ if (value.toLowerCase() === "global") {
616
+ return `Username cannot be "global" as it's reserved for cross-project thoughts.`;
617
+ }
618
+ return void 0;
619
+ }
620
+ });
621
+ if (p.isCancel(userInput)) {
622
+ p.cancel("Operation cancelled.");
623
+ process.exit(0);
624
+ }
625
+ user = userInput || defaultUser;
626
+ }
627
+ config = {
628
+ thoughtsRepo,
629
+ reposDir: reposDir2,
630
+ globalDir,
631
+ user,
632
+ repoMappings: {}
633
+ };
634
+ p.note(
635
+ `${chalk2.cyan(thoughtsRepo)}/
636
+ \u251C\u2500\u2500 ${chalk2.cyan(reposDir2)}/ ${chalk2.gray("(project-specific thoughts)")}
637
+ \u2514\u2500\u2500 ${chalk2.cyan(globalDir)}/ ${chalk2.gray("(cross-project thoughts)")}`,
638
+ "Creating thoughts structure"
639
+ );
640
+ ensureThoughtsRepoExists(thoughtsRepo, reposDir2, globalDir);
641
+ saveThoughtsConfig(config, options);
642
+ p.log.success("Global thoughts configuration created");
643
+ }
644
+ if (options.profile) {
645
+ if (!validateProfile(config, options.profile)) {
646
+ p.log.error(`Profile "${options.profile}" does not exist.`);
647
+ p.log.message(chalk2.gray("Available profiles:"));
648
+ if (config.profiles) {
649
+ Object.keys(config.profiles).forEach((name) => {
650
+ p.log.message(chalk2.gray(` - ${name}`));
651
+ });
652
+ } else {
653
+ p.log.message(chalk2.gray(" (none)"));
654
+ }
655
+ p.log.warn("Create a profile first:");
656
+ p.log.message(chalk2.gray(` thoughtcabinet profile create ${options.profile}`));
657
+ process.exit(1);
658
+ }
659
+ }
660
+ const tempProfileConfig = options.profile && config.profiles && config.profiles[options.profile] ? {
661
+ thoughtsRepo: config.profiles[options.profile].thoughtsRepo,
662
+ reposDir: config.profiles[options.profile].reposDir,
663
+ globalDir: config.profiles[options.profile].globalDir,
664
+ profileName: options.profile
665
+ } : {
666
+ thoughtsRepo: config.thoughtsRepo,
667
+ reposDir: config.reposDir,
668
+ globalDir: config.globalDir,
669
+ profileName: void 0
670
+ };
671
+ const setupStatus = checkExistingSetup(config);
672
+ if (setupStatus.exists && !options.force) {
673
+ if (setupStatus.isValid) {
674
+ p.log.warn("Thoughts directory already configured for this repository.");
675
+ const reconfigure = await p.confirm({
676
+ message: "Do you want to reconfigure?",
677
+ initialValue: false
678
+ });
679
+ if (p.isCancel(reconfigure) || !reconfigure) {
680
+ p.cancel("Setup cancelled.");
681
+ return;
682
+ }
683
+ } else {
684
+ p.log.warn(setupStatus.message || "Thoughts setup is incomplete");
685
+ const fix = await p.confirm({
686
+ message: "Do you want to fix the setup?",
687
+ initialValue: true
688
+ });
689
+ if (p.isCancel(fix) || !fix) {
690
+ p.cancel("Setup cancelled.");
691
+ return;
692
+ }
693
+ }
694
+ }
695
+ const expandedRepo = expandPath(tempProfileConfig.thoughtsRepo);
696
+ if (!fs4.existsSync(expandedRepo)) {
697
+ p.log.error(`Thoughts repository not found at ${tempProfileConfig.thoughtsRepo}`);
698
+ p.log.warn("The thoughts repository may have been moved or deleted.");
699
+ const recreate = await p.confirm({
700
+ message: "Do you want to recreate it?",
701
+ initialValue: true
702
+ });
703
+ if (p.isCancel(recreate) || !recreate) {
704
+ p.log.info("Please update your configuration or restore the thoughts repository.");
705
+ process.exit(1);
706
+ }
707
+ ensureThoughtsRepoExists(
708
+ tempProfileConfig.thoughtsRepo,
709
+ tempProfileConfig.reposDir,
710
+ tempProfileConfig.globalDir
711
+ );
712
+ }
713
+ const reposDir = path6.join(expandedRepo, tempProfileConfig.reposDir);
714
+ if (!fs4.existsSync(reposDir)) {
715
+ fs4.mkdirSync(reposDir, { recursive: true });
716
+ }
717
+ const existingRepos = fs4.readdirSync(reposDir).filter((name) => {
718
+ const fullPath = path6.join(reposDir, name);
719
+ return fs4.statSync(fullPath).isDirectory() && !name.startsWith(".");
720
+ });
721
+ const existingMapping = config.repoMappings[currentRepo];
722
+ let mappedName = getRepoNameFromMapping(existingMapping);
723
+ if (!mappedName) {
724
+ if (options.directory) {
725
+ const sanitizedDir = sanitizeDirectoryName(options.directory);
726
+ if (!existingRepos.includes(sanitizedDir)) {
727
+ p.log.error(`Directory "${sanitizedDir}" not found in thoughts repository.`);
728
+ p.log.error("In non-interactive mode (--directory), you must specify a directory");
729
+ p.log.error("name that already exists in the thoughts repository.");
730
+ p.log.warn("Available directories:");
731
+ existingRepos.forEach((repo) => p.log.message(chalk2.gray(` - ${repo}`)));
732
+ process.exit(1);
733
+ }
734
+ mappedName = sanitizedDir;
735
+ p.log.success(
736
+ `Using existing: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/${mappedName}`
737
+ );
738
+ } else {
739
+ p.intro(chalk2.blue("Repository Setup"));
740
+ p.log.info(`Setting up thoughts for: ${chalk2.cyan(currentRepo)}`);
741
+ p.log.message(
742
+ chalk2.gray(
743
+ `This will create a subdirectory in ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/`
744
+ )
745
+ );
746
+ p.log.message(chalk2.gray("to store thoughts specific to this repository."));
747
+ if (existingRepos.length > 0) {
748
+ const selectOptions = [
749
+ ...existingRepos.map((repo) => ({ value: repo, label: `Use existing: ${repo}` })),
750
+ { value: "__create_new__", label: "Create new directory" }
751
+ ];
752
+ const selection = await p.select({
753
+ message: "Select or create a thoughts directory for this repository:",
754
+ options: selectOptions
755
+ });
756
+ if (p.isCancel(selection)) {
757
+ p.cancel("Operation cancelled.");
758
+ process.exit(0);
759
+ }
760
+ if (selection === "__create_new__") {
761
+ const defaultName = getRepoNameFromPath(currentRepo);
762
+ p.log.message(
763
+ chalk2.gray(
764
+ `This name will be used for the directory: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/[name]`
765
+ )
766
+ );
767
+ const nameInput = await p.text({
768
+ message: "Directory name for this project's thoughts:",
769
+ initialValue: defaultName,
770
+ placeholder: defaultName
771
+ });
772
+ if (p.isCancel(nameInput)) {
773
+ p.cancel("Operation cancelled.");
774
+ process.exit(0);
775
+ }
776
+ mappedName = nameInput || defaultName;
777
+ mappedName = sanitizeDirectoryName(mappedName);
778
+ p.log.success(
779
+ `Will create: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/${mappedName}`
780
+ );
781
+ } else {
782
+ mappedName = selection;
783
+ p.log.success(
784
+ `Will use existing: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/${mappedName}`
785
+ );
786
+ }
787
+ } else {
788
+ const defaultName = getRepoNameFromPath(currentRepo);
789
+ p.log.message(
790
+ chalk2.gray(
791
+ `This name will be used for the directory: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/[name]`
792
+ )
793
+ );
794
+ const nameInput = await p.text({
795
+ message: "Directory name for this project's thoughts:",
796
+ initialValue: defaultName,
797
+ placeholder: defaultName
798
+ });
799
+ if (p.isCancel(nameInput)) {
800
+ p.cancel("Operation cancelled.");
801
+ process.exit(0);
802
+ }
803
+ mappedName = nameInput || defaultName;
804
+ mappedName = sanitizeDirectoryName(mappedName);
805
+ p.log.success(
806
+ `Will create: ${tempProfileConfig.thoughtsRepo}/${tempProfileConfig.reposDir}/${mappedName}`
807
+ );
808
+ }
809
+ }
810
+ if (options.profile) {
811
+ config.repoMappings[currentRepo] = {
812
+ repo: mappedName,
813
+ profile: options.profile
814
+ };
815
+ } else {
816
+ config.repoMappings[currentRepo] = mappedName;
817
+ }
818
+ saveThoughtsConfig(config, options);
819
+ }
820
+ if (!mappedName) {
821
+ mappedName = getRepoNameFromMapping(config.repoMappings[currentRepo]);
822
+ }
823
+ const profileConfig = resolveProfileForRepo(config, currentRepo);
824
+ createThoughtsDirectoryStructure(profileConfig, mappedName, config.user);
825
+ const thoughtsDir = path6.join(currentRepo, "thoughts");
826
+ if (fs4.existsSync(thoughtsDir)) {
827
+ const searchableDir = path6.join(thoughtsDir, "searchable");
828
+ if (fs4.existsSync(searchableDir)) {
829
+ try {
830
+ execSync2(`chmod -R 755 "${searchableDir}"`, { stdio: "pipe" });
831
+ } catch {
832
+ }
833
+ }
834
+ fs4.rmSync(thoughtsDir, { recursive: true, force: true });
835
+ }
836
+ fs4.mkdirSync(thoughtsDir);
837
+ const repoTarget = getRepoThoughtsPath(profileConfig, mappedName);
838
+ const globalTarget = getGlobalThoughtsPath(profileConfig);
839
+ fs4.symlinkSync(path6.join(repoTarget, config.user), path6.join(thoughtsDir, config.user), "dir");
840
+ fs4.symlinkSync(path6.join(repoTarget, "shared"), path6.join(thoughtsDir, "shared"), "dir");
841
+ fs4.symlinkSync(globalTarget, path6.join(thoughtsDir, "global"), "dir");
842
+ const otherUsers = updateSymlinksForNewUsers(
843
+ currentRepo,
844
+ profileConfig,
845
+ mappedName,
846
+ config.user
847
+ );
848
+ if (otherUsers.length > 0) {
849
+ p.log.success(`Added symlinks for other users: ${otherUsers.join(", ")}`);
850
+ }
851
+ try {
852
+ execSync2("git remote get-url origin", { cwd: expandedRepo, stdio: "pipe" });
853
+ try {
854
+ execSync2("git pull --rebase", {
855
+ stdio: "pipe",
856
+ cwd: expandedRepo
857
+ });
858
+ p.log.success("Pulled latest thoughts from remote");
859
+ } catch (error) {
860
+ p.log.warn(`Could not pull latest thoughts: ${error.message}`);
861
+ }
862
+ } catch {
863
+ }
864
+ const claudeMd = generateClaudeMd({
865
+ thoughtsRepo: profileConfig.thoughtsRepo,
866
+ reposDir: profileConfig.reposDir,
867
+ repoName: mappedName,
868
+ user: config.user
869
+ });
870
+ fs4.writeFileSync(path6.join(thoughtsDir, "CLAUDE.md"), claudeMd);
871
+ const hookResult = setupGitHooks(currentRepo);
872
+ if (hookResult.updated.length > 0) {
873
+ p.log.step(`Updated git hooks: ${hookResult.updated.join(", ")}`);
874
+ }
875
+ p.log.success("Thoughts setup complete!");
876
+ const structureText = `${chalk2.cyan(currentRepo)}/
877
+ \u2514\u2500\u2500 thoughts/
878
+ \u251C\u2500\u2500 ${config.user}/ ${chalk2.gray(`\u2192 ${profileConfig.thoughtsRepo}/${profileConfig.reposDir}/${mappedName}/${config.user}/`)}
879
+ \u251C\u2500\u2500 shared/ ${chalk2.gray(`\u2192 ${profileConfig.thoughtsRepo}/${profileConfig.reposDir}/${mappedName}/shared/`)}
880
+ \u2514\u2500\u2500 global/ ${chalk2.gray(`\u2192 ${profileConfig.thoughtsRepo}/${profileConfig.globalDir}/`)}
881
+ \u251C\u2500\u2500 ${config.user}/ ${chalk2.gray("(your cross-repo notes)")}
882
+ \u2514\u2500\u2500 shared/ ${chalk2.gray("(team cross-repo notes)")}`;
883
+ p.note(structureText, "Repository structure created");
884
+ p.note(
885
+ `${chalk2.green("\u2713")} Pre-commit hook: Prevents committing thoughts/
886
+ ${chalk2.green("\u2713")} Post-commit hook: Auto-syncs thoughts after commits`,
887
+ "Protection enabled"
888
+ );
889
+ p.outro(
890
+ chalk2.gray("Next steps:\n") + chalk2.gray(
891
+ ` 1. Run ${chalk2.cyan("thoughtcabinet sync")} to create the searchable index
892
+ `
893
+ ) + chalk2.gray(
894
+ ` 2. Create markdown files in ${chalk2.cyan(`thoughts/${config.user}/`)} for your notes
895
+ `
896
+ ) + chalk2.gray(` 3. Your thoughts will sync automatically when you commit code
897
+ `) + chalk2.gray(` 4. Run ${chalk2.cyan("thoughtcabinet status")} to check sync status`)
898
+ );
899
+ } catch (error) {
900
+ p.log.error(`Error during thoughts init: ${error}`);
901
+ process.exit(1);
902
+ }
903
+ }
904
+
905
+ // src/commands/thoughts/destroy.ts
906
+ import fs5 from "fs";
907
+ import path7 from "path";
908
+ import { execSync as execSync3 } from "child_process";
909
+ import chalk3 from "chalk";
910
+ async function thoughtsDestoryCommand(options) {
911
+ try {
912
+ const currentRepo = getCurrentRepoPath();
913
+ const thoughtsDir = path7.join(currentRepo, "thoughts");
914
+ if (!fs5.existsSync(thoughtsDir)) {
915
+ console.error(chalk3.red("Error: Thoughts not initialized for this repository."));
916
+ process.exit(1);
917
+ }
918
+ const config = loadThoughtsConfig(options);
919
+ if (!config) {
920
+ console.error(chalk3.red("Error: Thoughts configuration not found."));
921
+ process.exit(1);
922
+ }
923
+ const mapping = config.repoMappings[currentRepo];
924
+ const mappedName = getRepoNameFromMapping(mapping);
925
+ const profileName = getProfileNameFromMapping(mapping);
926
+ if (!mappedName && !options.force) {
927
+ console.error(chalk3.red("Error: This repository is not in the thoughts configuration."));
928
+ console.error(chalk3.yellow("Use --force to remove the thoughts directory anyway."));
929
+ process.exit(1);
930
+ }
931
+ console.log(chalk3.blue("Removing thoughts setup from current repository..."));
932
+ const searchableDir = path7.join(thoughtsDir, "searchable");
933
+ if (fs5.existsSync(searchableDir)) {
934
+ console.log(chalk3.gray("Removing searchable directory..."));
935
+ try {
936
+ execSync3(`chmod -R 755 "${searchableDir}"`, { stdio: "pipe" });
937
+ } catch {
938
+ }
939
+ fs5.rmSync(searchableDir, { recursive: true, force: true });
940
+ }
941
+ console.log(chalk3.gray("Removing thoughts directory (symlinks only)..."));
942
+ try {
943
+ fs5.rmSync(thoughtsDir, { recursive: true, force: true });
944
+ } catch (error) {
945
+ console.error(chalk3.red(`Error removing thoughts directory: ${error}`));
946
+ console.error(chalk3.yellow("You may need to manually remove: " + thoughtsDir));
947
+ process.exit(1);
948
+ }
949
+ if (mappedName) {
950
+ console.log(chalk3.gray("Removing repository from thoughts configuration..."));
951
+ delete config.repoMappings[currentRepo];
952
+ saveThoughtsConfig(config, options);
953
+ }
954
+ console.log(chalk3.green("\u2705 Thoughts removed from repository"));
955
+ if (mappedName) {
956
+ console.log("");
957
+ console.log(chalk3.gray("Note: Your thoughts content remains safe in:"));
958
+ if (profileName && config.profiles && config.profiles[profileName]) {
959
+ const profile = config.profiles[profileName];
960
+ console.log(chalk3.gray(` ${profile.thoughtsRepo}/${profile.reposDir}/${mappedName}`));
961
+ console.log(chalk3.gray(` (profile: ${profileName})`));
962
+ } else {
963
+ console.log(chalk3.gray(` ${config.thoughtsRepo}/${config.reposDir}/${mappedName}`));
964
+ }
965
+ console.log(chalk3.gray("Only the local symlinks and configuration were removed."));
966
+ }
967
+ } catch (error) {
968
+ console.error(chalk3.red(`Error during thoughts destroy: ${error}`));
969
+ process.exit(1);
970
+ }
971
+ }
972
+
973
+ // src/commands/thoughts/sync.ts
974
+ import fs6 from "fs";
975
+ import path8 from "path";
976
+ import { execSync as execSync4, execFileSync } from "child_process";
977
+ import chalk4 from "chalk";
978
+ function checkGitStatus(repoPath) {
979
+ try {
980
+ const status = execSync4("git status --porcelain", {
981
+ cwd: repoPath,
982
+ encoding: "utf8",
983
+ stdio: "pipe"
984
+ });
985
+ return status.trim().length > 0;
986
+ } catch {
987
+ return false;
988
+ }
989
+ }
990
+ function syncThoughts(thoughtsRepo, message) {
991
+ const expandedRepo = expandPath(thoughtsRepo);
992
+ try {
993
+ execSync4("git add -A", { cwd: expandedRepo, stdio: "pipe" });
994
+ const hasChanges = checkGitStatus(expandedRepo);
995
+ if (hasChanges) {
996
+ const commitMessage = message || `Sync thoughts - ${(/* @__PURE__ */ new Date()).toISOString()}`;
997
+ execFileSync("git", ["commit", "-m", commitMessage], { cwd: expandedRepo, stdio: "pipe" });
998
+ console.log(chalk4.green("\u2705 Thoughts synchronized"));
999
+ } else {
1000
+ console.log(chalk4.gray("No changes to commit"));
1001
+ }
1002
+ try {
1003
+ execSync4("git pull --rebase", {
1004
+ stdio: "pipe",
1005
+ cwd: expandedRepo
1006
+ });
1007
+ } catch (error) {
1008
+ const errorStr = error.toString();
1009
+ if (errorStr.includes("CONFLICT (") || errorStr.includes("Automatic merge failed") || errorStr.includes("Patch failed at") || errorStr.includes('When you have resolved this problem, run "git rebase --continue"')) {
1010
+ console.error(chalk4.red("Error: Merge conflict detected in thoughts repository"));
1011
+ console.error(chalk4.red("Please resolve conflicts manually in:"), expandedRepo);
1012
+ console.error(chalk4.red('Then run "git rebase --continue" and "thoughtcabinet sync" again'));
1013
+ process.exit(1);
1014
+ } else {
1015
+ console.warn(chalk4.yellow("Warning: Could not pull latest changes:"), error.message);
1016
+ }
1017
+ }
1018
+ try {
1019
+ execSync4("git remote get-url origin", { cwd: expandedRepo, stdio: "pipe" });
1020
+ console.log(chalk4.gray("Pushing to remote..."));
1021
+ try {
1022
+ execSync4("git push", { cwd: expandedRepo, stdio: "pipe" });
1023
+ console.log(chalk4.green("\u2705 Pushed to remote"));
1024
+ } catch {
1025
+ console.log(chalk4.yellow("\u26A0\uFE0F Could not push to remote. You may need to push manually."));
1026
+ }
1027
+ } catch {
1028
+ console.log(chalk4.yellow("\u2139\uFE0F No remote configured for thoughts repository"));
1029
+ }
1030
+ } catch (error) {
1031
+ console.error(chalk4.red(`Error syncing thoughts: ${error}`));
1032
+ process.exit(1);
1033
+ }
1034
+ }
1035
+ function createSearchDirectory(thoughtsDir) {
1036
+ const searchDir = path8.join(thoughtsDir, "searchable");
1037
+ if (fs6.existsSync(searchDir)) {
1038
+ try {
1039
+ execSync4(`chmod -R 755 "${searchDir}"`, { stdio: "pipe" });
1040
+ } catch {
1041
+ }
1042
+ fs6.rmSync(searchDir, { recursive: true, force: true });
1043
+ }
1044
+ fs6.mkdirSync(searchDir, { recursive: true });
1045
+ function findFilesFollowingSymlinks(dir, baseDir = dir, visited = /* @__PURE__ */ new Set()) {
1046
+ const files = [];
1047
+ const realPath = fs6.realpathSync(dir);
1048
+ if (visited.has(realPath)) {
1049
+ return files;
1050
+ }
1051
+ visited.add(realPath);
1052
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
1053
+ for (const entry of entries) {
1054
+ const fullPath = path8.join(dir, entry.name);
1055
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
1056
+ files.push(...findFilesFollowingSymlinks(fullPath, baseDir, visited));
1057
+ } else if (entry.isSymbolicLink() && !entry.name.startsWith(".")) {
1058
+ try {
1059
+ const stat = fs6.statSync(fullPath);
1060
+ if (stat.isDirectory()) {
1061
+ files.push(...findFilesFollowingSymlinks(fullPath, baseDir, visited));
1062
+ } else if (stat.isFile() && path8.basename(fullPath) !== "CLAUDE.md") {
1063
+ files.push(path8.relative(baseDir, fullPath));
1064
+ }
1065
+ } catch {
1066
+ }
1067
+ } else if (entry.isFile() && !entry.name.startsWith(".") && entry.name !== "CLAUDE.md") {
1068
+ files.push(path8.relative(baseDir, fullPath));
1069
+ }
1070
+ }
1071
+ return files;
1072
+ }
1073
+ const allFiles = findFilesFollowingSymlinks(thoughtsDir);
1074
+ let linkedCount = 0;
1075
+ for (const relPath of allFiles) {
1076
+ const sourcePath = path8.join(thoughtsDir, relPath);
1077
+ const targetPath = path8.join(searchDir, relPath);
1078
+ const targetDir = path8.dirname(targetPath);
1079
+ if (!fs6.existsSync(targetDir)) {
1080
+ fs6.mkdirSync(targetDir, { recursive: true });
1081
+ }
1082
+ try {
1083
+ const realSourcePath = fs6.realpathSync(sourcePath);
1084
+ fs6.linkSync(realSourcePath, targetPath);
1085
+ linkedCount++;
1086
+ } catch {
1087
+ }
1088
+ }
1089
+ console.log(chalk4.gray(`Created ${linkedCount} hard links in searchable directory`));
1090
+ }
1091
+ async function thoughtsSyncCommand(options) {
1092
+ try {
1093
+ const config = loadThoughtsConfig(options);
1094
+ if (!config) {
1095
+ console.error(chalk4.red('Error: Thoughts not configured. Run "thoughtcabinet init" first.'));
1096
+ process.exit(1);
1097
+ }
1098
+ const currentRepo = getCurrentRepoPath();
1099
+ const thoughtsDir = path8.join(currentRepo, "thoughts");
1100
+ if (!fs6.existsSync(thoughtsDir)) {
1101
+ console.error(chalk4.red("Error: Thoughts not initialized for this repository."));
1102
+ console.error('Run "thoughtcabinet init" to set up thoughts.');
1103
+ process.exit(1);
1104
+ }
1105
+ const mapping = config.repoMappings[currentRepo];
1106
+ const mappedName = getRepoNameFromMapping(mapping);
1107
+ const profileConfig = resolveProfileForRepo(config, currentRepo);
1108
+ if (mappedName) {
1109
+ const newUsers = updateSymlinksForNewUsers(
1110
+ currentRepo,
1111
+ profileConfig,
1112
+ mappedName,
1113
+ config.user
1114
+ );
1115
+ if (newUsers.length > 0) {
1116
+ console.log(chalk4.green(`\u2713 Added symlinks for new users: ${newUsers.join(", ")}`));
1117
+ }
1118
+ }
1119
+ console.log(chalk4.blue("Creating searchable index..."));
1120
+ createSearchDirectory(thoughtsDir);
1121
+ console.log(chalk4.blue("Syncing thoughts..."));
1122
+ syncThoughts(profileConfig.thoughtsRepo, options.message || "");
1123
+ } catch (error) {
1124
+ console.error(chalk4.red(`Error during thoughts sync: ${error}`));
1125
+ process.exit(1);
1126
+ }
1127
+ }
1128
+
1129
+ // src/commands/thoughts/status.ts
1130
+ import fs7 from "fs";
1131
+ import path9 from "path";
1132
+ import { execSync as execSync5 } from "child_process";
1133
+ import chalk5 from "chalk";
1134
+ function getGitStatus(repoPath) {
1135
+ try {
1136
+ return execSync5("git status -sb", {
1137
+ cwd: repoPath,
1138
+ encoding: "utf8",
1139
+ stdio: "pipe"
1140
+ }).trim();
1141
+ } catch {
1142
+ return "Not a git repository";
1143
+ }
1144
+ }
1145
+ function getUncommittedChanges(repoPath) {
1146
+ try {
1147
+ const output = execSync5("git status --porcelain", {
1148
+ cwd: repoPath,
1149
+ encoding: "utf8",
1150
+ stdio: "pipe"
1151
+ });
1152
+ return output.split("\n").filter((line) => line.trim()).map((line) => {
1153
+ const status = line.substring(0, 2);
1154
+ const file = line.substring(3);
1155
+ let statusText = "";
1156
+ if (status[0] === "M" || status[1] === "M") statusText = "modified";
1157
+ else if (status[0] === "A") statusText = "added";
1158
+ else if (status[0] === "D") statusText = "deleted";
1159
+ else if (status[0] === "?") statusText = "untracked";
1160
+ else if (status[0] === "R") statusText = "renamed";
1161
+ return ` ${chalk5.yellow(statusText.padEnd(10))} ${file}`;
1162
+ });
1163
+ } catch {
1164
+ return [];
1165
+ }
1166
+ }
1167
+ function getLastCommit(repoPath) {
1168
+ try {
1169
+ return execSync5('git log -1 --pretty=format:"%h %s (%cr)"', {
1170
+ cwd: repoPath,
1171
+ encoding: "utf8",
1172
+ stdio: "pipe"
1173
+ }).trim();
1174
+ } catch {
1175
+ return "No commits yet";
1176
+ }
1177
+ }
1178
+ function getRemoteStatus(repoPath) {
1179
+ try {
1180
+ execSync5("git remote get-url origin", { cwd: repoPath, stdio: "pipe" });
1181
+ try {
1182
+ execSync5("git fetch", { cwd: repoPath, stdio: "pipe" });
1183
+ } catch {
1184
+ }
1185
+ const status = execSync5("git status -sb", {
1186
+ cwd: repoPath,
1187
+ encoding: "utf8",
1188
+ stdio: "pipe"
1189
+ });
1190
+ if (status.includes("ahead")) {
1191
+ const ahead = status.match(/ahead (\d+)/)?.[1] || "?";
1192
+ return chalk5.yellow(`${ahead} commits ahead of remote`);
1193
+ } else if (status.includes("behind")) {
1194
+ const behind = status.match(/behind (\d+)/)?.[1] || "?";
1195
+ try {
1196
+ execSync5("git pull --rebase", {
1197
+ stdio: "pipe",
1198
+ cwd: repoPath
1199
+ });
1200
+ console.log(chalk5.green("\u2713 Automatically pulled latest changes"));
1201
+ const newStatus = execSync5("git status -sb", {
1202
+ encoding: "utf8",
1203
+ cwd: repoPath,
1204
+ stdio: "pipe"
1205
+ });
1206
+ if (newStatus.includes("behind")) {
1207
+ const newBehind = newStatus.match(/behind (\d+)/)?.[1] || "?";
1208
+ return chalk5.yellow(`${newBehind} commits behind remote (after pull)`);
1209
+ } else {
1210
+ return chalk5.green("Up to date with remote (after pull)");
1211
+ }
1212
+ } catch {
1213
+ return chalk5.yellow(`${behind} commits behind remote`);
1214
+ }
1215
+ } else {
1216
+ return chalk5.green("Up to date with remote");
1217
+ }
1218
+ } catch {
1219
+ return chalk5.gray("No remote configured");
1220
+ }
1221
+ }
1222
+ async function thoughtsStatusCommand(options) {
1223
+ try {
1224
+ const config = loadThoughtsConfig(options);
1225
+ if (!config) {
1226
+ console.error(chalk5.red('Error: Thoughts not configured. Run "thoughtcabinet init" first.'));
1227
+ process.exit(1);
1228
+ }
1229
+ console.log(chalk5.blue("Thoughts Repository Status"));
1230
+ console.log(chalk5.gray("=".repeat(50)));
1231
+ console.log("");
1232
+ console.log(chalk5.yellow("Configuration:"));
1233
+ console.log(` Repository: ${chalk5.cyan(config.thoughtsRepo)}`);
1234
+ console.log(` Repos directory: ${chalk5.cyan(config.reposDir)}`);
1235
+ console.log(` Global directory: ${chalk5.cyan(config.globalDir)}`);
1236
+ console.log(` User: ${chalk5.cyan(config.user)}`);
1237
+ console.log(` Mapped repos: ${chalk5.cyan(Object.keys(config.repoMappings).length)}`);
1238
+ console.log("");
1239
+ const currentRepo = getCurrentRepoPath();
1240
+ const currentMapping = config.repoMappings[currentRepo];
1241
+ const mappedName = getRepoNameFromMapping(currentMapping);
1242
+ const profileName = getProfileNameFromMapping(currentMapping);
1243
+ const profileConfig = resolveProfileForRepo(config, currentRepo);
1244
+ if (mappedName) {
1245
+ console.log(chalk5.yellow("Current Repository:"));
1246
+ console.log(` Path: ${chalk5.cyan(currentRepo)}`);
1247
+ console.log(` Thoughts directory: ${chalk5.cyan(`${profileConfig.reposDir}/${mappedName}`)}`);
1248
+ if (profileName) {
1249
+ console.log(` Profile: ${chalk5.cyan(profileName)}`);
1250
+ } else {
1251
+ console.log(` Profile: ${chalk5.gray("(default)")}`);
1252
+ }
1253
+ const thoughtsDir = path9.join(currentRepo, "thoughts");
1254
+ if (fs7.existsSync(thoughtsDir)) {
1255
+ console.log(` Status: ${chalk5.green("\u2713 Initialized")}`);
1256
+ } else {
1257
+ console.log(` Status: ${chalk5.red("\u2717 Not initialized")}`);
1258
+ }
1259
+ } else {
1260
+ console.log(chalk5.yellow("Current repository not mapped to thoughts"));
1261
+ }
1262
+ console.log("");
1263
+ const expandedRepo = expandPath(profileConfig.thoughtsRepo);
1264
+ console.log(chalk5.yellow("Thoughts Repository Git Status:"));
1265
+ if (profileName) {
1266
+ console.log(chalk5.gray(` (using profile: ${profileName})`));
1267
+ }
1268
+ console.log(` ${getGitStatus(expandedRepo)}`);
1269
+ console.log(` Remote: ${getRemoteStatus(expandedRepo)}`);
1270
+ console.log(` Last commit: ${getLastCommit(expandedRepo)}`);
1271
+ console.log("");
1272
+ const changes = getUncommittedChanges(expandedRepo);
1273
+ if (changes.length > 0) {
1274
+ console.log(chalk5.yellow("Uncommitted changes:"));
1275
+ changes.forEach((change) => console.log(change));
1276
+ console.log("");
1277
+ console.log(chalk5.gray('Run "thoughtcabinet sync" to commit these changes'));
1278
+ } else {
1279
+ console.log(chalk5.green("\u2713 No uncommitted changes"));
1280
+ }
1281
+ } catch (error) {
1282
+ console.error(chalk5.red(`Error checking thoughts status: ${error}`));
1283
+ process.exit(1);
1284
+ }
1285
+ }
1286
+
1287
+ // src/commands/thoughts/config.ts
1288
+ import { spawn } from "child_process";
1289
+ import chalk6 from "chalk";
1290
+ async function thoughtsConfigCommand(options) {
1291
+ try {
1292
+ const configPath = options.configFile || getDefaultConfigPath();
1293
+ if (options.edit) {
1294
+ const editor = process.env.EDITOR || "vi";
1295
+ spawn(editor, [configPath], { stdio: "inherit" });
1296
+ return;
1297
+ }
1298
+ const config = loadThoughtsConfig(options);
1299
+ if (!config) {
1300
+ console.error(chalk6.red("No thoughts configuration found."));
1301
+ console.error('Run "thoughtcabinet init" to create one.');
1302
+ process.exit(1);
1303
+ }
1304
+ if (options.json) {
1305
+ console.log(JSON.stringify(config, null, 2));
1306
+ return;
1307
+ }
1308
+ console.log(chalk6.blue("Thoughts Configuration"));
1309
+ console.log(chalk6.gray("=".repeat(50)));
1310
+ console.log("");
1311
+ console.log(chalk6.yellow("Settings:"));
1312
+ console.log(` Config file: ${chalk6.cyan(configPath)}`);
1313
+ console.log(` Thoughts repository: ${chalk6.cyan(config.thoughtsRepo)}`);
1314
+ console.log(` Repos directory: ${chalk6.cyan(config.reposDir)}`);
1315
+ console.log(` Global directory: ${chalk6.cyan(config.globalDir)}`);
1316
+ console.log(` User: ${chalk6.cyan(config.user)}`);
1317
+ console.log("");
1318
+ console.log(chalk6.yellow("Repository Mappings:"));
1319
+ const mappings = Object.entries(config.repoMappings);
1320
+ if (mappings.length === 0) {
1321
+ console.log(chalk6.gray(" No repositories mapped yet"));
1322
+ } else {
1323
+ mappings.forEach(([repo, mapping]) => {
1324
+ const repoName = getRepoNameFromMapping(mapping);
1325
+ const profileName = getProfileNameFromMapping(mapping);
1326
+ console.log(` ${chalk6.cyan(repo)}`);
1327
+ console.log(` \u2192 ${chalk6.green(`${config.reposDir}/${repoName}`)}`);
1328
+ if (profileName) {
1329
+ console.log(` Profile: ${chalk6.yellow(profileName)}`);
1330
+ } else {
1331
+ console.log(` Profile: ${chalk6.gray("(default)")}`);
1332
+ }
1333
+ });
1334
+ }
1335
+ console.log("");
1336
+ console.log(chalk6.yellow("Profiles:"));
1337
+ if (!config.profiles || Object.keys(config.profiles).length === 0) {
1338
+ console.log(chalk6.gray(" No profiles configured"));
1339
+ } else {
1340
+ Object.keys(config.profiles).forEach((name) => {
1341
+ console.log(` ${chalk6.cyan(name)}`);
1342
+ });
1343
+ }
1344
+ console.log("");
1345
+ console.log(chalk6.gray("To edit configuration, run: thoughtcabinet config --edit"));
1346
+ } catch (error) {
1347
+ console.error(chalk6.red(`Error showing thoughts config: ${error}`));
1348
+ process.exit(1);
1349
+ }
1350
+ }
1351
+
1352
+ // src/commands/thoughts/profile/create.ts
1353
+ import chalk7 from "chalk";
1354
+ import * as p2 from "@clack/prompts";
1355
+ async function profileCreateCommand(profileName, options) {
1356
+ try {
1357
+ if (!options.repo || !options.reposDir || !options.globalDir) {
1358
+ if (!process.stdin.isTTY) {
1359
+ p2.log.error("Not running in interactive terminal.");
1360
+ p2.log.info("Provide all options: --repo, --repos-dir, --global-dir");
1361
+ process.exit(1);
1362
+ }
1363
+ }
1364
+ const config = loadThoughtsConfig(options);
1365
+ if (!config) {
1366
+ p2.log.error("Thoughts not configured.");
1367
+ p2.log.info('Run "thoughtcabinet init" first to set up the base configuration.');
1368
+ process.exit(1);
1369
+ }
1370
+ const sanitizedName = sanitizeProfileName(profileName);
1371
+ if (sanitizedName !== profileName) {
1372
+ p2.log.warn(`Profile name sanitized: "${profileName}" \u2192 "${sanitizedName}"`);
1373
+ }
1374
+ p2.intro(chalk7.blue(`Creating Profile: ${sanitizedName}`));
1375
+ if (validateProfile(config, sanitizedName)) {
1376
+ p2.log.error(`Profile "${sanitizedName}" already exists.`);
1377
+ p2.log.info("Use a different name or delete the existing profile first.");
1378
+ process.exit(1);
1379
+ }
1380
+ let thoughtsRepo;
1381
+ let reposDir;
1382
+ let globalDir;
1383
+ if (options.repo && options.reposDir && options.globalDir) {
1384
+ thoughtsRepo = options.repo;
1385
+ reposDir = options.reposDir;
1386
+ globalDir = options.globalDir;
1387
+ } else {
1388
+ const defaultRepo = getDefaultThoughtsRepo() + `-${sanitizedName}`;
1389
+ p2.log.info("Specify the thoughts repository location for this profile.");
1390
+ const repoInput = await p2.text({
1391
+ message: "Thoughts repository:",
1392
+ initialValue: defaultRepo,
1393
+ placeholder: defaultRepo
1394
+ });
1395
+ if (p2.isCancel(repoInput)) {
1396
+ p2.cancel("Operation cancelled.");
1397
+ process.exit(0);
1398
+ }
1399
+ thoughtsRepo = repoInput || defaultRepo;
1400
+ const reposDirInput = await p2.text({
1401
+ message: "Repository-specific thoughts directory:",
1402
+ initialValue: "repos",
1403
+ placeholder: "repos"
1404
+ });
1405
+ if (p2.isCancel(reposDirInput)) {
1406
+ p2.cancel("Operation cancelled.");
1407
+ process.exit(0);
1408
+ }
1409
+ reposDir = reposDirInput || "repos";
1410
+ const globalDirInput = await p2.text({
1411
+ message: "Global thoughts directory:",
1412
+ initialValue: "global",
1413
+ placeholder: "global"
1414
+ });
1415
+ if (p2.isCancel(globalDirInput)) {
1416
+ p2.cancel("Operation cancelled.");
1417
+ process.exit(0);
1418
+ }
1419
+ globalDir = globalDirInput || "global";
1420
+ }
1421
+ const profileConfig = {
1422
+ thoughtsRepo,
1423
+ reposDir,
1424
+ globalDir
1425
+ };
1426
+ if (!config.profiles) {
1427
+ config.profiles = {};
1428
+ }
1429
+ config.profiles[sanitizedName] = profileConfig;
1430
+ saveThoughtsConfig(config, options);
1431
+ p2.log.step("Initializing profile thoughts repository...");
1432
+ ensureThoughtsRepoExists(profileConfig);
1433
+ p2.log.success(`Profile "${sanitizedName}" created successfully!`);
1434
+ p2.note(
1435
+ `Name: ${chalk7.cyan(sanitizedName)}
1436
+ Thoughts repository: ${chalk7.cyan(thoughtsRepo)}
1437
+ Repos directory: ${chalk7.cyan(reposDir)}
1438
+ Global directory: ${chalk7.cyan(globalDir)}`,
1439
+ "Profile Configuration"
1440
+ );
1441
+ p2.outro(
1442
+ chalk7.gray("Next steps:\n") + chalk7.gray(` 1. Run "thoughtcabinet init --profile ${sanitizedName}" in a repository
1443
+ `) + chalk7.gray(` 2. Your thoughts will sync to the profile's repository`)
1444
+ );
1445
+ } catch (error) {
1446
+ p2.log.error(`Error creating profile: ${error}`);
1447
+ process.exit(1);
1448
+ }
1449
+ }
1450
+
1451
+ // src/commands/thoughts/profile/list.ts
1452
+ import chalk8 from "chalk";
1453
+ async function profileListCommand(options) {
1454
+ try {
1455
+ const config = loadThoughtsConfig(options);
1456
+ if (!config) {
1457
+ console.error(chalk8.red("Error: Thoughts not configured."));
1458
+ process.exit(1);
1459
+ }
1460
+ if (options.json) {
1461
+ console.log(JSON.stringify(config.profiles || {}, null, 2));
1462
+ return;
1463
+ }
1464
+ console.log(chalk8.blue("Thoughts Profiles"));
1465
+ console.log(chalk8.gray("=".repeat(50)));
1466
+ console.log("");
1467
+ console.log(chalk8.yellow("Default Configuration:"));
1468
+ console.log(` Thoughts repository: ${chalk8.cyan(config.thoughtsRepo)}`);
1469
+ console.log(` Repos directory: ${chalk8.cyan(config.reposDir)}`);
1470
+ console.log(` Global directory: ${chalk8.cyan(config.globalDir)}`);
1471
+ console.log("");
1472
+ if (!config.profiles || Object.keys(config.profiles).length === 0) {
1473
+ console.log(chalk8.gray("No profiles configured."));
1474
+ console.log("");
1475
+ console.log(chalk8.gray("Create a profile with: thoughtcabinet profile create <name>"));
1476
+ } else {
1477
+ console.log(chalk8.yellow(`Profiles (${Object.keys(config.profiles).length}):`));
1478
+ console.log("");
1479
+ Object.entries(config.profiles).forEach(([name, profile]) => {
1480
+ console.log(chalk8.cyan(` ${name}:`));
1481
+ console.log(` Thoughts repository: ${profile.thoughtsRepo}`);
1482
+ console.log(` Repos directory: ${profile.reposDir}`);
1483
+ console.log(` Global directory: ${profile.globalDir}`);
1484
+ console.log("");
1485
+ });
1486
+ }
1487
+ } catch (error) {
1488
+ console.error(chalk8.red(`Error listing profiles: ${error}`));
1489
+ process.exit(1);
1490
+ }
1491
+ }
1492
+
1493
+ // src/commands/thoughts/profile/show.ts
1494
+ import chalk9 from "chalk";
1495
+ async function profileShowCommand(profileName, options) {
1496
+ try {
1497
+ const config = loadThoughtsConfig(options);
1498
+ if (!config) {
1499
+ console.error(chalk9.red("Error: Thoughts not configured."));
1500
+ process.exit(1);
1501
+ }
1502
+ if (!validateProfile(config, profileName)) {
1503
+ console.error(chalk9.red(`Error: Profile "${profileName}" not found.`));
1504
+ console.error("");
1505
+ console.error(chalk9.gray("Available profiles:"));
1506
+ if (config.profiles) {
1507
+ Object.keys(config.profiles).forEach((name) => {
1508
+ console.error(chalk9.gray(` - ${name}`));
1509
+ });
1510
+ } else {
1511
+ console.error(chalk9.gray(" (none)"));
1512
+ }
1513
+ process.exit(1);
1514
+ }
1515
+ const profile = config.profiles[profileName];
1516
+ if (options.json) {
1517
+ console.log(JSON.stringify(profile, null, 2));
1518
+ return;
1519
+ }
1520
+ console.log(chalk9.blue(`Profile: ${profileName}`));
1521
+ console.log(chalk9.gray("=".repeat(50)));
1522
+ console.log("");
1523
+ console.log(chalk9.yellow("Configuration:"));
1524
+ console.log(` Thoughts repository: ${chalk9.cyan(profile.thoughtsRepo)}`);
1525
+ console.log(` Repos directory: ${chalk9.cyan(profile.reposDir)}`);
1526
+ console.log(` Global directory: ${chalk9.cyan(profile.globalDir)}`);
1527
+ console.log("");
1528
+ let repoCount = 0;
1529
+ Object.values(config.repoMappings).forEach((mapping) => {
1530
+ if (typeof mapping === "object" && mapping.profile === profileName) {
1531
+ repoCount++;
1532
+ }
1533
+ });
1534
+ console.log(chalk9.yellow("Usage:"));
1535
+ console.log(` Repositories using this profile: ${chalk9.cyan(repoCount)}`);
1536
+ } catch (error) {
1537
+ console.error(chalk9.red(`Error showing profile: ${error}`));
1538
+ process.exit(1);
1539
+ }
1540
+ }
1541
+
1542
+ // src/commands/thoughts/profile/delete.ts
1543
+ import chalk10 from "chalk";
1544
+ import * as p3 from "@clack/prompts";
1545
+ async function profileDeleteCommand(profileName, options) {
1546
+ try {
1547
+ if (!options.force && !process.stdin.isTTY) {
1548
+ p3.log.error("Not running in interactive terminal.");
1549
+ p3.log.info("Use --force flag to delete without confirmation.");
1550
+ process.exit(1);
1551
+ }
1552
+ p3.intro(chalk10.blue(`Delete Profile: ${profileName}`));
1553
+ const config = loadThoughtsConfig(options);
1554
+ if (!config) {
1555
+ p3.log.error("Thoughts not configured.");
1556
+ process.exit(1);
1557
+ }
1558
+ if (!validateProfile(config, profileName)) {
1559
+ p3.log.error(`Profile "${profileName}" not found.`);
1560
+ process.exit(1);
1561
+ }
1562
+ const usingRepos = [];
1563
+ Object.entries(config.repoMappings).forEach(([repoPath, mapping]) => {
1564
+ if (typeof mapping === "object" && mapping.profile === profileName) {
1565
+ usingRepos.push(repoPath);
1566
+ }
1567
+ });
1568
+ if (usingRepos.length > 0 && !options.force) {
1569
+ p3.log.error(`Profile "${profileName}" is in use by ${usingRepos.length} repository(ies):`);
1570
+ usingRepos.forEach((repo) => {
1571
+ p3.log.message(chalk10.gray(` - ${repo}`));
1572
+ });
1573
+ p3.log.warn("Options:");
1574
+ p3.log.message(chalk10.gray(' 1. Run "thoughtcabinet destroy" in each repository'));
1575
+ p3.log.message(
1576
+ chalk10.gray(" 2. Use --force to delete anyway (repos will fall back to default config)")
1577
+ );
1578
+ process.exit(1);
1579
+ }
1580
+ if (!options.force) {
1581
+ p3.log.warn(`You are about to delete profile: ${chalk10.cyan(profileName)}`);
1582
+ p3.log.message(chalk10.gray("This will remove the profile configuration."));
1583
+ p3.log.message(chalk10.gray("The thoughts repository files will NOT be deleted."));
1584
+ const confirmDelete = await p3.confirm({
1585
+ message: `Delete profile "${profileName}"?`,
1586
+ initialValue: false
1587
+ });
1588
+ if (p3.isCancel(confirmDelete) || !confirmDelete) {
1589
+ p3.cancel("Deletion cancelled.");
1590
+ return;
1591
+ }
1592
+ }
1593
+ delete config.profiles[profileName];
1594
+ if (Object.keys(config.profiles).length === 0) {
1595
+ delete config.profiles;
1596
+ }
1597
+ saveThoughtsConfig(config, options);
1598
+ p3.log.success(`Profile "${profileName}" deleted`);
1599
+ if (usingRepos.length > 0) {
1600
+ p3.log.warn("Repositories using this profile will fall back to default config");
1601
+ }
1602
+ p3.outro(chalk10.green("Done"));
1603
+ } catch (error) {
1604
+ p3.log.error(`Error deleting profile: ${error}`);
1605
+ process.exit(1);
1606
+ }
1607
+ }
1608
+
1609
+ // src/commands/thoughts.ts
1610
+ function thoughtsCommand(program2) {
1611
+ const cmd = program2;
1612
+ cmd.command("init").description("Initialize thoughts for current repository").option("--force", "Force reconfiguration even if already set up").option("--config-file <path>", "Path to config file").option(
1613
+ "--directory <name>",
1614
+ "Specify the repository directory name (skips interactive prompt)"
1615
+ ).option("--profile <name>", "Use a specific thoughts profile").action(thoughtsInitCommand);
1616
+ cmd.command("destroy").description("Remove thoughts setup from current repository").option("--force", "Force removal even if not in configuration").option("--config-file <path>", "Path to config file").action(thoughtsDestoryCommand);
1617
+ cmd.command("sync").description("Manually sync thoughts to thoughts repository").option("-m, --message <message>", "Commit message for sync").option("--config-file <path>", "Path to config file").action(thoughtsSyncCommand);
1618
+ cmd.command("status").description("Show status of thoughts repository").option("--config-file <path>", "Path to config file").action(thoughtsStatusCommand);
1619
+ 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);
1620
+ const profile = cmd.command("profile").description("Manage thoughts profiles");
1621
+ 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);
1622
+ profile.command("list").description("List all thoughts profiles").option("--json", "Output as JSON").option("--config-file <path>", "Path to config file").action(profileListCommand);
1623
+ profile.command("show <name>").description("Show details of a specific profile").option("--json", "Output as JSON").option("--config-file <path>", "Path to config file").action(profileShowCommand);
1624
+ profile.command("delete <name>").description("Delete a thoughts profile").option("--force", "Force deletion even if in use").option("--config-file <path>", "Path to config file").action(profileDeleteCommand);
1625
+ }
1626
+
1627
+ // src/commands/agent/init.ts
1628
+ import fs8 from "fs";
1629
+ import path10 from "path";
1630
+ import chalk11 from "chalk";
1631
+ import * as p4 from "@clack/prompts";
1632
+ import { fileURLToPath } from "url";
1633
+ import { dirname } from "path";
1634
+ var __filename2 = fileURLToPath(import.meta.url);
1635
+ var __dirname2 = dirname(__filename2);
1636
+ function ensureGitignoreEntry(targetDir, entry, productName) {
1637
+ const gitignorePath = path10.join(targetDir, ".gitignore");
1638
+ let gitignoreContent = "";
1639
+ if (fs8.existsSync(gitignorePath)) {
1640
+ gitignoreContent = fs8.readFileSync(gitignorePath, "utf8");
1641
+ }
1642
+ const lines = gitignoreContent.split("\n");
1643
+ if (lines.some((line) => line.trim() === entry)) {
1644
+ return;
1645
+ }
1646
+ const newContent = gitignoreContent + (gitignoreContent && !gitignoreContent.endsWith("\n") ? "\n" : "") + "\n# " + productName + " local settings\n" + entry + "\n";
1647
+ fs8.writeFileSync(gitignorePath, newContent);
1648
+ }
1649
+ function copyDirectoryRecursive(sourceDir, targetDir) {
1650
+ let filesCopied = 0;
1651
+ fs8.mkdirSync(targetDir, { recursive: true });
1652
+ const entries = fs8.readdirSync(sourceDir, { withFileTypes: true });
1653
+ for (const entry of entries) {
1654
+ const sourcePath = path10.join(sourceDir, entry.name);
1655
+ const targetPath = path10.join(targetDir, entry.name);
1656
+ if (entry.isDirectory()) {
1657
+ filesCopied += copyDirectoryRecursive(sourcePath, targetPath);
1658
+ } else {
1659
+ fs8.copyFileSync(sourcePath, targetPath);
1660
+ filesCopied++;
1661
+ }
1662
+ }
1663
+ return filesCopied;
1664
+ }
1665
+ async function agentInitCommand(options) {
1666
+ const { product } = options;
1667
+ try {
1668
+ p4.intro(chalk11.blue(`Initialize ${product.name} Configuration`));
1669
+ if (!process.stdin.isTTY && !options.all) {
1670
+ p4.log.error("Not running in interactive terminal.");
1671
+ p4.log.info("Use --all flag to copy all files without prompting.");
1672
+ process.exit(1);
1673
+ }
1674
+ const targetDir = process.cwd();
1675
+ const agentTargetDir = path10.join(targetDir, product.dirName);
1676
+ const possiblePaths = [
1677
+ // When installed via npm: package root is one level up from dist
1678
+ path10.resolve(__dirname2, "..", product.sourceDirName),
1679
+ // When running from repo: repo root is two levels up from dist
1680
+ path10.resolve(__dirname2, "../..", product.sourceDirName)
1681
+ ];
1682
+ let sourceAgentDir = null;
1683
+ for (const candidatePath of possiblePaths) {
1684
+ if (fs8.existsSync(candidatePath)) {
1685
+ sourceAgentDir = candidatePath;
1686
+ break;
1687
+ }
1688
+ }
1689
+ if (!sourceAgentDir) {
1690
+ p4.log.error(`Source ${product.dirName} directory not found in expected locations`);
1691
+ p4.log.info("Searched paths:");
1692
+ possiblePaths.forEach((candidatePath) => {
1693
+ p4.log.info(` - ${candidatePath}`);
1694
+ });
1695
+ p4.log.info("Are you running from the thoughtcabinet repository or npm package?");
1696
+ process.exit(1);
1697
+ }
1698
+ if (fs8.existsSync(agentTargetDir) && !options.force) {
1699
+ const overwrite = await p4.confirm({
1700
+ message: `${product.dirName} directory already exists. Overwrite?`,
1701
+ initialValue: false
1702
+ });
1703
+ if (p4.isCancel(overwrite) || !overwrite) {
1704
+ p4.cancel("Operation cancelled.");
1705
+ process.exit(0);
1706
+ }
1707
+ }
1708
+ let selectedCategories;
1709
+ if (options.all) {
1710
+ selectedCategories = ["commands", "agents", "skills", "settings"];
1711
+ } else {
1712
+ let commandsCount = 0;
1713
+ let agentsCount = 0;
1714
+ const commandsDir = path10.join(sourceAgentDir, "commands");
1715
+ const agentsDir = path10.join(sourceAgentDir, "agents");
1716
+ if (fs8.existsSync(commandsDir)) {
1717
+ commandsCount = fs8.readdirSync(commandsDir).length;
1718
+ }
1719
+ if (fs8.existsSync(agentsDir)) {
1720
+ agentsCount = fs8.readdirSync(agentsDir).length;
1721
+ }
1722
+ let skillsCount = 0;
1723
+ const skillsDir = path10.join(sourceAgentDir, "skills");
1724
+ if (fs8.existsSync(skillsDir)) {
1725
+ skillsCount = fs8.readdirSync(skillsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).length;
1726
+ }
1727
+ p4.note(
1728
+ "Use \u2191/\u2193 to move, press Space to select/deselect, press A to select/deselect all, press Enter to confirm. (Subsequent multi-selects apply; Ctrl+C to exit)",
1729
+ "Multi-select instructions"
1730
+ );
1731
+ const selection = await p4.multiselect({
1732
+ message: "What would you like to copy?",
1733
+ options: [
1734
+ {
1735
+ value: "commands",
1736
+ label: "Commands",
1737
+ hint: `${commandsCount} workflow commands (planning, CI, research, etc.)`
1738
+ },
1739
+ {
1740
+ value: "agents",
1741
+ label: "Agents",
1742
+ hint: `${agentsCount} specialized sub-agents for code analysis`
1743
+ },
1744
+ {
1745
+ value: "skills",
1746
+ label: "Skills",
1747
+ hint: `${skillsCount} specialized skill packages for extended capabilities`
1748
+ },
1749
+ {
1750
+ value: "settings",
1751
+ label: "Settings",
1752
+ hint: "Project permissions configuration"
1753
+ }
1754
+ ],
1755
+ initialValues: ["commands", "agents", "skills", "settings"],
1756
+ required: false
1757
+ });
1758
+ if (p4.isCancel(selection)) {
1759
+ p4.cancel("Operation cancelled.");
1760
+ process.exit(0);
1761
+ }
1762
+ selectedCategories = selection;
1763
+ if (selectedCategories.length === 0) {
1764
+ p4.cancel("No items selected.");
1765
+ process.exit(0);
1766
+ }
1767
+ }
1768
+ fs8.mkdirSync(agentTargetDir, { recursive: true });
1769
+ let filesCopied = 0;
1770
+ let filesSkipped = 0;
1771
+ const filesToCopyByCategory = {};
1772
+ if (!options.all) {
1773
+ if (selectedCategories.includes("commands")) {
1774
+ const sourceDir = path10.join(sourceAgentDir, "commands");
1775
+ if (fs8.existsSync(sourceDir)) {
1776
+ const allFiles = fs8.readdirSync(sourceDir);
1777
+ const fileSelection = await p4.multiselect({
1778
+ message: "Select command files to copy:",
1779
+ options: allFiles.map((file) => ({
1780
+ value: file,
1781
+ label: file
1782
+ })),
1783
+ initialValues: allFiles,
1784
+ required: false
1785
+ });
1786
+ if (p4.isCancel(fileSelection)) {
1787
+ p4.cancel("Operation cancelled.");
1788
+ process.exit(0);
1789
+ }
1790
+ filesToCopyByCategory["commands"] = fileSelection;
1791
+ if (filesToCopyByCategory["commands"].length === 0) {
1792
+ filesSkipped += allFiles.length;
1793
+ }
1794
+ }
1795
+ }
1796
+ if (selectedCategories.includes("agents")) {
1797
+ const sourceDir = path10.join(sourceAgentDir, "agents");
1798
+ if (fs8.existsSync(sourceDir)) {
1799
+ const allFiles = fs8.readdirSync(sourceDir);
1800
+ const fileSelection = await p4.multiselect({
1801
+ message: "Select agent files to copy:",
1802
+ options: allFiles.map((file) => ({
1803
+ value: file,
1804
+ label: file
1805
+ })),
1806
+ initialValues: allFiles,
1807
+ required: false
1808
+ });
1809
+ if (p4.isCancel(fileSelection)) {
1810
+ p4.cancel("Operation cancelled.");
1811
+ process.exit(0);
1812
+ }
1813
+ filesToCopyByCategory["agents"] = fileSelection;
1814
+ if (filesToCopyByCategory["agents"].length === 0) {
1815
+ filesSkipped += allFiles.length;
1816
+ }
1817
+ }
1818
+ }
1819
+ if (selectedCategories.includes("skills")) {
1820
+ const sourceDir = path10.join(sourceAgentDir, "skills");
1821
+ if (fs8.existsSync(sourceDir)) {
1822
+ const allSkills = fs8.readdirSync(sourceDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1823
+ if (allSkills.length > 0) {
1824
+ const skillSelection = await p4.multiselect({
1825
+ message: "Select skills to copy:",
1826
+ options: allSkills.map((skill) => ({
1827
+ value: skill,
1828
+ label: skill
1829
+ })),
1830
+ initialValues: allSkills,
1831
+ required: false
1832
+ });
1833
+ if (p4.isCancel(skillSelection)) {
1834
+ p4.cancel("Operation cancelled.");
1835
+ process.exit(0);
1836
+ }
1837
+ filesToCopyByCategory["skills"] = skillSelection;
1838
+ }
1839
+ }
1840
+ }
1841
+ }
1842
+ let maxThinkingTokens = options.maxThinkingTokens;
1843
+ if (!options.all && selectedCategories.includes("settings")) {
1844
+ if (maxThinkingTokens === void 0) {
1845
+ const tokensPrompt = await p4.text({
1846
+ message: "Maximum thinking tokens:",
1847
+ initialValue: "32000",
1848
+ validate: (value) => {
1849
+ const num = parseInt(value, 10);
1850
+ if (isNaN(num) || num < 1e3) {
1851
+ return "Please enter a valid number (minimum 1000)";
1852
+ }
1853
+ return void 0;
1854
+ }
1855
+ });
1856
+ if (p4.isCancel(tokensPrompt)) {
1857
+ p4.cancel("Operation cancelled.");
1858
+ process.exit(0);
1859
+ }
1860
+ maxThinkingTokens = parseInt(tokensPrompt, 10);
1861
+ }
1862
+ } else if (selectedCategories.includes("settings")) {
1863
+ if (maxThinkingTokens === void 0) {
1864
+ maxThinkingTokens = 32e3;
1865
+ }
1866
+ }
1867
+ for (const category of selectedCategories) {
1868
+ if (category === "commands" || category === "agents") {
1869
+ const sourceDir = path10.join(sourceAgentDir, category);
1870
+ const targetCategoryDir = path10.join(agentTargetDir, category);
1871
+ if (!fs8.existsSync(sourceDir)) {
1872
+ p4.log.warn(`${category} directory not found in source, skipping`);
1873
+ continue;
1874
+ }
1875
+ const allFiles = fs8.readdirSync(sourceDir);
1876
+ let filesToCopy = allFiles;
1877
+ if (!options.all && filesToCopyByCategory[category]) {
1878
+ filesToCopy = filesToCopyByCategory[category];
1879
+ }
1880
+ if (filesToCopy.length === 0) {
1881
+ continue;
1882
+ }
1883
+ fs8.mkdirSync(targetCategoryDir, { recursive: true });
1884
+ for (const file of filesToCopy) {
1885
+ const sourcePath = path10.join(sourceDir, file);
1886
+ const targetPath = path10.join(targetCategoryDir, file);
1887
+ fs8.copyFileSync(sourcePath, targetPath);
1888
+ filesCopied++;
1889
+ }
1890
+ filesSkipped += allFiles.length - filesToCopy.length;
1891
+ p4.log.success(`Copied ${filesToCopy.length} ${category} file(s)`);
1892
+ } else if (category === "settings") {
1893
+ const settingsPath = path10.join(sourceAgentDir, "settings.template.json");
1894
+ const targetSettingsPath = path10.join(agentTargetDir, "settings.json");
1895
+ if (fs8.existsSync(settingsPath)) {
1896
+ const settingsContent = fs8.readFileSync(settingsPath, "utf8");
1897
+ const settings = JSON.parse(settingsContent);
1898
+ if (maxThinkingTokens !== void 0) {
1899
+ if (!settings.env) {
1900
+ settings.env = {};
1901
+ }
1902
+ settings.env.MAX_THINKING_TOKENS = maxThinkingTokens.toString();
1903
+ }
1904
+ if (!settings.env) {
1905
+ settings.env = {};
1906
+ }
1907
+ for (const [key, value] of Object.entries(product.defaultEnvVars)) {
1908
+ settings.env[key] = value;
1909
+ }
1910
+ fs8.writeFileSync(targetSettingsPath, JSON.stringify(settings, null, 2) + "\n");
1911
+ filesCopied++;
1912
+ p4.log.success(`Copied settings.json (maxTokens: ${maxThinkingTokens})`);
1913
+ } else {
1914
+ p4.log.warn("settings.json not found in source, skipping");
1915
+ }
1916
+ } else if (category === "skills") {
1917
+ const sourceDir = path10.join(sourceAgentDir, "skills");
1918
+ const targetCategoryDir = path10.join(agentTargetDir, "skills");
1919
+ if (!fs8.existsSync(sourceDir)) {
1920
+ p4.log.warn("skills directory not found in source, skipping");
1921
+ continue;
1922
+ }
1923
+ const allSkills = fs8.readdirSync(sourceDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1924
+ let skillsToCopy = allSkills;
1925
+ if (!options.all && filesToCopyByCategory["skills"]) {
1926
+ skillsToCopy = filesToCopyByCategory["skills"];
1927
+ }
1928
+ if (skillsToCopy.length === 0) {
1929
+ continue;
1930
+ }
1931
+ fs8.mkdirSync(targetCategoryDir, { recursive: true });
1932
+ let skillFilesCopied = 0;
1933
+ for (const skill of skillsToCopy) {
1934
+ const sourceSkillPath = path10.join(sourceDir, skill);
1935
+ const targetSkillPath = path10.join(targetCategoryDir, skill);
1936
+ skillFilesCopied += copyDirectoryRecursive(sourceSkillPath, targetSkillPath);
1937
+ }
1938
+ filesCopied += skillFilesCopied;
1939
+ filesSkipped += allSkills.length - skillsToCopy.length;
1940
+ p4.log.success(`Copied ${skillsToCopy.length} skill(s) (${skillFilesCopied} files)`);
1941
+ }
1942
+ }
1943
+ if (selectedCategories.includes("settings")) {
1944
+ ensureGitignoreEntry(targetDir, product.gitignoreEntry, product.name);
1945
+ p4.log.info("Updated .gitignore to exclude settings.local.json");
1946
+ }
1947
+ let message = `Successfully copied ${filesCopied} file(s) to ${agentTargetDir}`;
1948
+ if (filesSkipped > 0) {
1949
+ message += chalk11.gray(`
1950
+ Skipped ${filesSkipped} file(s)`);
1951
+ }
1952
+ message += chalk11.gray(`
1953
+ You can now use these commands in ${product.name}.`);
1954
+ p4.outro(message);
1955
+ } catch (error) {
1956
+ p4.log.error(`Error during ${product.name} init: ${error}`);
1957
+ process.exit(1);
1958
+ }
1959
+ }
1960
+
1961
+ // src/commands/agent/registry.ts
1962
+ var AGENT_PRODUCTS = {
1963
+ claude: {
1964
+ id: "claude",
1965
+ name: "Claude Code",
1966
+ dirName: ".claude",
1967
+ sourceDirName: "src/agent-assets",
1968
+ envVarPrefix: "CLAUDE",
1969
+ defaultEnvVars: {
1970
+ MAX_THINKING_TOKENS: "32000",
1971
+ CLAUDE_BASH_MAINTAIN_WORKING_DIR: "1"
1972
+ },
1973
+ gitignoreEntry: ".claude/settings.local.json"
1974
+ },
1975
+ codebuddy: {
1976
+ id: "codebuddy",
1977
+ name: "CodeBuddy Code",
1978
+ dirName: ".codebuddy",
1979
+ sourceDirName: "src/agent-assets",
1980
+ envVarPrefix: "CODEBUDDY",
1981
+ defaultEnvVars: {
1982
+ MAX_THINKING_TOKENS: "32000",
1983
+ // Note: Current not implemented in CodeBuddy
1984
+ CODEBUDDY_BASH_MAINTAIN_PROJECT_WORKING_DIR: "1"
1985
+ },
1986
+ gitignoreEntry: ".codebuddy/settings.local.json"
1987
+ }
1988
+ };
1989
+ function getAgentProduct(id) {
1990
+ const product = AGENT_PRODUCTS[id];
1991
+ if (!product) {
1992
+ const validIds = Object.keys(AGENT_PRODUCTS).join(", ");
1993
+ throw new Error(`Unknown agent product: ${id}. Valid options: ${validIds}`);
1994
+ }
1995
+ return product;
1996
+ }
1997
+
1998
+ // src/commands/agent.ts
1999
+ function agentCommand(program2) {
2000
+ const agent = program2.command("agent").description("Manage coding agent configuration");
2001
+ agent.command("init").description("Initialize coding agent configuration in current directory").option("--force", "Force overwrite of existing agent directory").option("--all", "Copy all files without prompting").option(
2002
+ "--max-thinking-tokens <number>",
2003
+ "Maximum thinking tokens (default: 32000)",
2004
+ (value) => parseInt(value, 10)
2005
+ ).option("--name <name>", "Agent name to configure (claude|codebuddy)", "claude").action(async (options) => {
2006
+ const product = getAgentProduct(options.name);
2007
+ await agentInitCommand({ ...options, product });
2008
+ });
2009
+ }
2010
+
2011
+ // src/commands/metadata/metadata.ts
2012
+ import { execSync as execSync6 } from "child_process";
2013
+ function getGitInfo() {
2014
+ try {
2015
+ execSync6("git rev-parse --is-inside-work-tree", {
2016
+ encoding: "utf8",
2017
+ stdio: "pipe"
2018
+ });
2019
+ const repoRoot = execSync6("git rev-parse --show-toplevel", {
2020
+ encoding: "utf8",
2021
+ stdio: "pipe"
2022
+ }).trim();
2023
+ const repoName = repoRoot.split("/").pop() || "";
2024
+ let branch = "";
2025
+ try {
2026
+ branch = execSync6("git branch --show-current", {
2027
+ encoding: "utf8",
2028
+ stdio: "pipe"
2029
+ }).trim();
2030
+ } catch {
2031
+ try {
2032
+ branch = execSync6("git rev-parse --abbrev-ref HEAD", {
2033
+ encoding: "utf8",
2034
+ stdio: "pipe"
2035
+ }).trim();
2036
+ } catch {
2037
+ branch = "";
2038
+ }
2039
+ }
2040
+ let commit = "";
2041
+ try {
2042
+ commit = execSync6("git rev-parse HEAD", {
2043
+ encoding: "utf8",
2044
+ stdio: "pipe"
2045
+ }).trim();
2046
+ } catch {
2047
+ commit = "";
2048
+ }
2049
+ return { repoRoot, repoName, branch, commit };
2050
+ } catch {
2051
+ return null;
2052
+ }
2053
+ }
2054
+ function formatDate(date) {
2055
+ const pad = (n) => n.toString().padStart(2, "0");
2056
+ const year = date.getFullYear();
2057
+ const month = pad(date.getMonth() + 1);
2058
+ const day = pad(date.getDate());
2059
+ const hours = pad(date.getHours());
2060
+ const minutes = pad(date.getMinutes());
2061
+ const seconds = pad(date.getSeconds());
2062
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
2063
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} ${tz}`;
2064
+ }
2065
+ function formatFilenameTimestamp(date) {
2066
+ const pad = (n) => n.toString().padStart(2, "0");
2067
+ const year = date.getFullYear();
2068
+ const month = pad(date.getMonth() + 1);
2069
+ const day = pad(date.getDate());
2070
+ const hours = pad(date.getHours());
2071
+ const minutes = pad(date.getMinutes());
2072
+ const seconds = pad(date.getSeconds());
2073
+ return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
2074
+ }
2075
+ async function specMetadataCommand() {
2076
+ const now = /* @__PURE__ */ new Date();
2077
+ console.log(`Current Date/Time (TZ): ${formatDate(now)}`);
2078
+ const gitInfo = getGitInfo();
2079
+ if (gitInfo) {
2080
+ if (gitInfo.commit) {
2081
+ console.log(`Current Git Commit Hash: ${gitInfo.commit}`);
2082
+ }
2083
+ if (gitInfo.branch) {
2084
+ console.log(`Current Branch Name: ${gitInfo.branch}`);
2085
+ }
2086
+ if (gitInfo.repoName) {
2087
+ console.log(`Repository Name: ${gitInfo.repoName}`);
2088
+ }
2089
+ }
2090
+ console.log(`Timestamp For Filename: ${formatFilenameTimestamp(now)}`);
2091
+ }
2092
+
2093
+ // src/commands/metadata.ts
2094
+ function metadataCommand(program2) {
2095
+ const metadata = program2.description("Metadata utilities for current repository");
2096
+ metadata.command("metadata").description("Output metadata for current repository (branch, commit, timestamp, etc.)").action(specMetadataCommand);
2097
+ }
2098
+
2099
+ // src/index.ts
2100
+ import dotenv2 from "dotenv";
2101
+ import { createRequire } from "module";
2102
+ dotenv2.config();
2103
+ var require2 = createRequire(import.meta.url);
2104
+ var { version } = require2("../package.json");
2105
+ var program = new Command();
2106
+ program.name("thoughtcabinet").description(
2107
+ "Thought Cabinet (thc) - thoughts management CLI for developer notes and documentation"
2108
+ ).version(version);
2109
+ thoughtsCommand(program);
2110
+ agentCommand(program);
2111
+ metadataCommand(program);
2112
+ program.parse(process.argv);
2113
+ //# sourceMappingURL=index.js.map