namnam-skills 1.0.0 → 1.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/package.json +2 -1
- package/src/cli.js +1012 -0
- package/src/conversation.js +914 -0
- package/src/indexer.js +944 -0
- package/src/templates/namnam.md +693 -0
- package/src/templates/platforms/AGENTS.md +47 -0
- package/src/watcher.js +356 -0
- package/src/templates/core/code-review.md +0 -70
- package/src/templates/core/git-commit.md +0 -57
- package/src/templates/core/git-push.md +0 -53
- package/src/templates/core/git-status.md +0 -48
- package/src/templates/core/namnam.md +0 -324
- package/src/templates/core/validate-and-fix.md +0 -69
package/src/cli.js
CHANGED
|
@@ -7,6 +7,37 @@ import fs from 'fs-extra';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import inquirer from 'inquirer';
|
|
10
|
+
import {
|
|
11
|
+
initConversations,
|
|
12
|
+
saveConversation,
|
|
13
|
+
getConversation,
|
|
14
|
+
listConversations,
|
|
15
|
+
deleteConversation,
|
|
16
|
+
exportConversation,
|
|
17
|
+
getConversationContext,
|
|
18
|
+
updateConversation,
|
|
19
|
+
// Auto-memory functions
|
|
20
|
+
initAutoMemory,
|
|
21
|
+
loadAutoMemoryIndex,
|
|
22
|
+
autoSaveMemory,
|
|
23
|
+
getRelevantMemories,
|
|
24
|
+
generateAutoMemoryContext,
|
|
25
|
+
startSession,
|
|
26
|
+
getCurrentSession,
|
|
27
|
+
updateSession,
|
|
28
|
+
endSession,
|
|
29
|
+
remember,
|
|
30
|
+
recall
|
|
31
|
+
} from './conversation.js';
|
|
32
|
+
import {
|
|
33
|
+
buildIndex,
|
|
34
|
+
hasIndex,
|
|
35
|
+
getIndexMeta,
|
|
36
|
+
getIndexStats,
|
|
37
|
+
checkIndexChanges,
|
|
38
|
+
searchSymbols,
|
|
39
|
+
generateAIContext
|
|
40
|
+
} from './indexer.js';
|
|
10
41
|
|
|
11
42
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
43
|
const __dirname = path.dirname(__filename);
|
|
@@ -457,4 +488,985 @@ async function generatePlatformFiles(cwd, templatesDir, force = false) {
|
|
|
457
488
|
}
|
|
458
489
|
}
|
|
459
490
|
|
|
491
|
+
// ========================================
|
|
492
|
+
// CONVERSATION COMMANDS
|
|
493
|
+
// ========================================
|
|
494
|
+
|
|
495
|
+
const conversation = program
|
|
496
|
+
.command('conversation')
|
|
497
|
+
.alias('conv')
|
|
498
|
+
.description('Manage conversation context (@conversation feature)');
|
|
499
|
+
|
|
500
|
+
conversation
|
|
501
|
+
.command('init')
|
|
502
|
+
.description('Initialize conversation storage in current project')
|
|
503
|
+
.action(async () => {
|
|
504
|
+
console.log(banner);
|
|
505
|
+
const spinner = ora('Initializing conversation storage...').start();
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
const cwd = process.cwd();
|
|
509
|
+
await initConversations(cwd);
|
|
510
|
+
spinner.succeed(chalk.green('Conversation storage initialized!'));
|
|
511
|
+
console.log(chalk.gray('\nLocation: .claude/conversations/'));
|
|
512
|
+
console.log(chalk.cyan('\nUsage:'));
|
|
513
|
+
console.log(chalk.white(' namnam conv save - Save current conversation'));
|
|
514
|
+
console.log(chalk.white(' namnam conv list - List saved conversations'));
|
|
515
|
+
console.log(chalk.white(' namnam conv show <id> - View a conversation'));
|
|
516
|
+
console.log(chalk.white(' @conversation:<id> - Reference in prompts'));
|
|
517
|
+
} catch (error) {
|
|
518
|
+
spinner.fail(chalk.red('Failed to initialize'));
|
|
519
|
+
console.error(error.message);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
conversation
|
|
524
|
+
.command('save')
|
|
525
|
+
.description('Save a conversation with context')
|
|
526
|
+
.option('-t, --title <title>', 'Conversation title')
|
|
527
|
+
.option('-s, --summary <summary>', 'Short summary')
|
|
528
|
+
.option('-g, --tags <tags>', 'Comma-separated tags')
|
|
529
|
+
.action(async (options) => {
|
|
530
|
+
console.log(banner);
|
|
531
|
+
|
|
532
|
+
const answers = await inquirer.prompt([
|
|
533
|
+
{
|
|
534
|
+
type: 'input',
|
|
535
|
+
name: 'title',
|
|
536
|
+
message: 'Conversation title:',
|
|
537
|
+
default: options.title || `Conversation ${new Date().toLocaleDateString()}`,
|
|
538
|
+
when: !options.title
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
type: 'input',
|
|
542
|
+
name: 'summary',
|
|
543
|
+
message: 'Short summary (1-2 sentences):',
|
|
544
|
+
when: !options.summary
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
type: 'editor',
|
|
548
|
+
name: 'context',
|
|
549
|
+
message: 'Enter the conversation context (opens editor):'
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
type: 'input',
|
|
553
|
+
name: 'tags',
|
|
554
|
+
message: 'Tags (comma-separated):',
|
|
555
|
+
when: !options.tags
|
|
556
|
+
}
|
|
557
|
+
]);
|
|
558
|
+
|
|
559
|
+
const spinner = ora('Saving conversation...').start();
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
const result = await saveConversation({
|
|
563
|
+
title: options.title || answers.title,
|
|
564
|
+
summary: options.summary || answers.summary || '',
|
|
565
|
+
context: answers.context || '',
|
|
566
|
+
tags: (options.tags || answers.tags || '').split(',').map(t => t.trim()).filter(Boolean)
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
spinner.succeed(chalk.green('Conversation saved!'));
|
|
570
|
+
console.log(chalk.cyan(`\nID: ${chalk.white(result.shortId)}`));
|
|
571
|
+
console.log(chalk.cyan(`Full ID: ${chalk.gray(result.id)}`));
|
|
572
|
+
console.log(chalk.yellow(`\nReference with: @conversation:${result.shortId}`));
|
|
573
|
+
} catch (error) {
|
|
574
|
+
spinner.fail(chalk.red('Failed to save conversation'));
|
|
575
|
+
console.error(error.message);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
conversation
|
|
580
|
+
.command('list')
|
|
581
|
+
.description('List all saved conversations')
|
|
582
|
+
.option('-l, --limit <number>', 'Number of conversations to show', '20')
|
|
583
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
584
|
+
.option('-s, --search <term>', 'Search in title/summary')
|
|
585
|
+
.action(async (options) => {
|
|
586
|
+
console.log(banner);
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
const conversations = await listConversations({
|
|
590
|
+
limit: parseInt(options.limit),
|
|
591
|
+
tag: options.tag,
|
|
592
|
+
search: options.search
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
if (conversations.length === 0) {
|
|
596
|
+
console.log(chalk.yellow('\n📭 No conversations found.'));
|
|
597
|
+
console.log(chalk.gray('Use `namnam conv save` to save your first conversation.'));
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
console.log(chalk.cyan(`\n📚 Saved Conversations (${conversations.length}):\n`));
|
|
602
|
+
|
|
603
|
+
for (const conv of conversations) {
|
|
604
|
+
const date = new Date(conv.createdAt).toLocaleDateString();
|
|
605
|
+
const tags = conv.tags?.length ? chalk.gray(` [${conv.tags.join(', ')}]`) : '';
|
|
606
|
+
|
|
607
|
+
console.log(chalk.yellow(` ${conv.shortId}`) + chalk.white(` - ${conv.title}`) + tags);
|
|
608
|
+
if (conv.summary) {
|
|
609
|
+
console.log(chalk.gray(` ${conv.summary.substring(0, 60)}${conv.summary.length > 60 ? '...' : ''}`));
|
|
610
|
+
}
|
|
611
|
+
console.log(chalk.gray(` ${date}`));
|
|
612
|
+
console.log();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
console.log(chalk.cyan('Usage:'));
|
|
616
|
+
console.log(chalk.white(' namnam conv show <id> - View full context'));
|
|
617
|
+
console.log(chalk.white(' @conversation:<id> - Reference in AI prompts'));
|
|
618
|
+
} catch (error) {
|
|
619
|
+
console.error(chalk.red('Failed to list conversations:'), error.message);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
conversation
|
|
624
|
+
.command('show <id>')
|
|
625
|
+
.description('Show conversation details and context')
|
|
626
|
+
.option('-f, --full', 'Show full log if available')
|
|
627
|
+
.action(async (id, options) => {
|
|
628
|
+
console.log(banner);
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
const conv = await getConversation(id);
|
|
632
|
+
|
|
633
|
+
if (!conv) {
|
|
634
|
+
console.log(chalk.red(`\n❌ Conversation not found: ${id}`));
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
console.log(chalk.cyan(`\n📝 ${conv.title}\n`));
|
|
639
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
640
|
+
console.log(chalk.white('ID: ') + chalk.yellow(conv.shortId));
|
|
641
|
+
console.log(chalk.white('Full ID: ') + chalk.gray(conv.id));
|
|
642
|
+
console.log(chalk.white('Created: ') + chalk.gray(new Date(conv.createdAt).toLocaleString()));
|
|
643
|
+
console.log(chalk.white('Updated: ') + chalk.gray(new Date(conv.updatedAt).toLocaleString()));
|
|
644
|
+
if (conv.tags?.length) {
|
|
645
|
+
console.log(chalk.white('Tags: ') + chalk.cyan(conv.tags.join(', ')));
|
|
646
|
+
}
|
|
647
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
648
|
+
|
|
649
|
+
if (conv.summary) {
|
|
650
|
+
console.log(chalk.cyan('\n📋 Summary:\n'));
|
|
651
|
+
console.log(conv.summary);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (conv.context) {
|
|
655
|
+
console.log(chalk.cyan('\n📄 Context:\n'));
|
|
656
|
+
console.log(conv.context);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (options.full && conv.fullLog) {
|
|
660
|
+
console.log(chalk.cyan('\n📜 Full Log:\n'));
|
|
661
|
+
console.log(conv.fullLog);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
console.log(chalk.gray('\n─'.repeat(50)));
|
|
665
|
+
console.log(chalk.yellow(`\nReference with: @conversation:${conv.shortId}`));
|
|
666
|
+
} catch (error) {
|
|
667
|
+
console.error(chalk.red('Failed to show conversation:'), error.message);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
conversation
|
|
672
|
+
.command('context <id>')
|
|
673
|
+
.description('Output conversation context for AI consumption')
|
|
674
|
+
.action(async (id) => {
|
|
675
|
+
try {
|
|
676
|
+
const context = await getConversationContext(id);
|
|
677
|
+
|
|
678
|
+
if (!context) {
|
|
679
|
+
console.error(`Conversation not found: ${id}`);
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Output raw context (for piping to other tools)
|
|
684
|
+
console.log(context);
|
|
685
|
+
} catch (error) {
|
|
686
|
+
console.error('Error:', error.message);
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
conversation
|
|
692
|
+
.command('delete <id>')
|
|
693
|
+
.description('Delete a saved conversation')
|
|
694
|
+
.option('-f, --force', 'Skip confirmation')
|
|
695
|
+
.action(async (id, options) => {
|
|
696
|
+
console.log(banner);
|
|
697
|
+
|
|
698
|
+
try {
|
|
699
|
+
const conv = await getConversation(id);
|
|
700
|
+
|
|
701
|
+
if (!conv) {
|
|
702
|
+
console.log(chalk.red(`\n❌ Conversation not found: ${id}`));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (!options.force) {
|
|
707
|
+
const answers = await inquirer.prompt([
|
|
708
|
+
{
|
|
709
|
+
type: 'confirm',
|
|
710
|
+
name: 'confirm',
|
|
711
|
+
message: `Delete "${conv.title}" (${conv.shortId})?`,
|
|
712
|
+
default: false
|
|
713
|
+
}
|
|
714
|
+
]);
|
|
715
|
+
|
|
716
|
+
if (!answers.confirm) {
|
|
717
|
+
console.log(chalk.gray('Cancelled.'));
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const spinner = ora('Deleting conversation...').start();
|
|
723
|
+
await deleteConversation(id);
|
|
724
|
+
spinner.succeed(chalk.green(`Deleted: ${conv.title}`));
|
|
725
|
+
} catch (error) {
|
|
726
|
+
console.error(chalk.red('Failed to delete conversation:'), error.message);
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
conversation
|
|
731
|
+
.command('update <id>')
|
|
732
|
+
.description('Update conversation metadata or context')
|
|
733
|
+
.option('-t, --title <title>', 'Update title')
|
|
734
|
+
.option('-s, --summary <summary>', 'Update summary')
|
|
735
|
+
.option('-g, --tags <tags>', 'Update tags (comma-separated)')
|
|
736
|
+
.option('-c, --context', 'Update context via editor')
|
|
737
|
+
.action(async (id, options) => {
|
|
738
|
+
console.log(banner);
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
const conv = await getConversation(id);
|
|
742
|
+
|
|
743
|
+
if (!conv) {
|
|
744
|
+
console.log(chalk.red(`\n❌ Conversation not found: ${id}`));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const updates = {};
|
|
749
|
+
|
|
750
|
+
if (options.title) updates.title = options.title;
|
|
751
|
+
if (options.summary) updates.summary = options.summary;
|
|
752
|
+
if (options.tags) {
|
|
753
|
+
updates.tags = options.tags.split(',').map(t => t.trim()).filter(Boolean);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// If context update requested, open editor
|
|
757
|
+
if (options.context) {
|
|
758
|
+
const answers = await inquirer.prompt([
|
|
759
|
+
{
|
|
760
|
+
type: 'editor',
|
|
761
|
+
name: 'context',
|
|
762
|
+
message: 'Update context:',
|
|
763
|
+
default: conv.context || ''
|
|
764
|
+
}
|
|
765
|
+
]);
|
|
766
|
+
if (answers.context !== undefined) {
|
|
767
|
+
updates.context = answers.context;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (Object.keys(updates).length === 0) {
|
|
772
|
+
console.log(chalk.yellow('\n⚠️ No updates provided.'));
|
|
773
|
+
console.log(chalk.gray('Use -t, -s, -g, or -c flags to specify what to update.'));
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const spinner = ora('Updating conversation...').start();
|
|
778
|
+
await updateConversation(id, updates);
|
|
779
|
+
spinner.succeed(chalk.green('Conversation updated!'));
|
|
780
|
+
|
|
781
|
+
console.log(chalk.cyan(`\nUpdated: ${options.title || conv.title}`));
|
|
782
|
+
} catch (error) {
|
|
783
|
+
console.error(chalk.red('Failed to update conversation:'), error.message);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
conversation
|
|
788
|
+
.command('export <id> [output]')
|
|
789
|
+
.description('Export conversation to markdown file')
|
|
790
|
+
.action(async (id, output) => {
|
|
791
|
+
console.log(banner);
|
|
792
|
+
|
|
793
|
+
try {
|
|
794
|
+
const conv = await getConversation(id);
|
|
795
|
+
|
|
796
|
+
if (!conv) {
|
|
797
|
+
console.log(chalk.red(`\n❌ Conversation not found: ${id}`));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const outputPath = output || `conversation-${conv.shortId}.md`;
|
|
802
|
+
const spinner = ora('Exporting conversation...').start();
|
|
803
|
+
|
|
804
|
+
await exportConversation(id, outputPath);
|
|
805
|
+
spinner.succeed(chalk.green(`Exported to: ${outputPath}`));
|
|
806
|
+
} catch (error) {
|
|
807
|
+
console.error(chalk.red('Failed to export conversation:'), error.message);
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// ========================================
|
|
812
|
+
// INDEX COMMANDS
|
|
813
|
+
// ========================================
|
|
814
|
+
|
|
815
|
+
const index = program
|
|
816
|
+
.command('index')
|
|
817
|
+
.alias('idx')
|
|
818
|
+
.description('Codebase indexing for deep code understanding');
|
|
819
|
+
|
|
820
|
+
index
|
|
821
|
+
.command('build')
|
|
822
|
+
.description('Build or rebuild the codebase index')
|
|
823
|
+
.option('-f, --force', 'Force rebuild even if index exists')
|
|
824
|
+
.option('--json', 'Output results as JSON (for programmatic use)')
|
|
825
|
+
.action(async (options) => {
|
|
826
|
+
const cwd = process.cwd();
|
|
827
|
+
|
|
828
|
+
// JSON mode - no banner or colors
|
|
829
|
+
if (options.json) {
|
|
830
|
+
try {
|
|
831
|
+
const meta = await buildIndex(cwd);
|
|
832
|
+
console.log(JSON.stringify({
|
|
833
|
+
success: true,
|
|
834
|
+
stats: {
|
|
835
|
+
files: meta.stats.totalFiles,
|
|
836
|
+
lines: meta.stats.totalLines,
|
|
837
|
+
functions: meta.stats.totalFunctions,
|
|
838
|
+
classes: meta.stats.totalClasses
|
|
839
|
+
},
|
|
840
|
+
patterns: meta.patterns
|
|
841
|
+
}));
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.log(JSON.stringify({
|
|
844
|
+
success: false,
|
|
845
|
+
error: error.message
|
|
846
|
+
}));
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
console.log(banner);
|
|
853
|
+
|
|
854
|
+
// Check if index exists
|
|
855
|
+
if (!options.force && await hasIndex(cwd)) {
|
|
856
|
+
const changes = await checkIndexChanges(cwd);
|
|
857
|
+
if (!changes.hasChanges) {
|
|
858
|
+
console.log(chalk.green('\n✅ Index is up to date!'));
|
|
859
|
+
const stats = await getIndexStats(cwd);
|
|
860
|
+
console.log(chalk.gray(` Last updated: ${new Date(stats.lastUpdated).toLocaleString()}`));
|
|
861
|
+
console.log(chalk.gray(` Files: ${stats.totalFiles} | Functions: ${stats.totalFunctions} | Classes: ${stats.totalClasses}`));
|
|
862
|
+
console.log(chalk.cyan('\nUse --force to rebuild anyway.'));
|
|
863
|
+
return;
|
|
864
|
+
} else {
|
|
865
|
+
console.log(chalk.yellow('\n📝 Changes detected since last index:'));
|
|
866
|
+
if (changes.newFiles?.length) console.log(chalk.green(` + ${changes.newFiles.length} new files`));
|
|
867
|
+
if (changes.modifiedFiles?.length) console.log(chalk.yellow(` ~ ${changes.modifiedFiles.length} modified files`));
|
|
868
|
+
if (changes.deletedFiles?.length) console.log(chalk.red(` - ${changes.deletedFiles.length} deleted files`));
|
|
869
|
+
console.log();
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const spinner = ora('Building codebase index...').start();
|
|
874
|
+
|
|
875
|
+
try {
|
|
876
|
+
const meta = await buildIndex(cwd, {
|
|
877
|
+
onProgress: (progress) => {
|
|
878
|
+
if (progress.phase === 'scanning') {
|
|
879
|
+
spinner.text = progress.message;
|
|
880
|
+
} else if (progress.phase === 'indexing') {
|
|
881
|
+
spinner.text = `Indexing: ${progress.current}/${progress.total} files`;
|
|
882
|
+
} else if (progress.phase === 'analyzing') {
|
|
883
|
+
spinner.text = 'Analyzing patterns...';
|
|
884
|
+
} else if (progress.phase === 'saving') {
|
|
885
|
+
spinner.text = 'Saving index...';
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
spinner.succeed(chalk.green('Index built successfully!'));
|
|
891
|
+
|
|
892
|
+
console.log(chalk.cyan('\n📊 Index Statistics:'));
|
|
893
|
+
console.log(chalk.white(` Files indexed: ${meta.stats.totalFiles}`));
|
|
894
|
+
console.log(chalk.white(` Total lines: ${meta.stats.totalLines.toLocaleString()}`));
|
|
895
|
+
console.log(chalk.white(` Functions: ${meta.stats.totalFunctions}`));
|
|
896
|
+
console.log(chalk.white(` Classes: ${meta.stats.totalClasses}`));
|
|
897
|
+
|
|
898
|
+
if (meta.patterns) {
|
|
899
|
+
console.log(chalk.cyan('\n🔍 Detected Patterns:'));
|
|
900
|
+
if (meta.patterns.framework) console.log(chalk.white(` Framework: ${meta.patterns.framework}`));
|
|
901
|
+
console.log(chalk.white(` Language: ${meta.patterns.language}`));
|
|
902
|
+
if (meta.patterns.styling) console.log(chalk.white(` Styling: ${meta.patterns.styling}`));
|
|
903
|
+
if (meta.patterns.testing) console.log(chalk.white(` Testing: ${meta.patterns.testing}`));
|
|
904
|
+
if (meta.patterns.packageManager) console.log(chalk.white(` Package Manager: ${meta.patterns.packageManager}`));
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
console.log(chalk.gray('\n📁 Index location: .claude/index/'));
|
|
908
|
+
console.log(chalk.yellow('\n💡 The index will be auto-loaded when using /namnam'));
|
|
909
|
+
|
|
910
|
+
} catch (error) {
|
|
911
|
+
spinner.fail(chalk.red('Failed to build index'));
|
|
912
|
+
console.error(error.message);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
index
|
|
917
|
+
.command('status')
|
|
918
|
+
.description('Show index status and statistics')
|
|
919
|
+
.option('--json', 'Output results as JSON (for programmatic use)')
|
|
920
|
+
.action(async (options) => {
|
|
921
|
+
const cwd = process.cwd();
|
|
922
|
+
|
|
923
|
+
// JSON mode
|
|
924
|
+
if (options.json) {
|
|
925
|
+
if (!(await hasIndex(cwd))) {
|
|
926
|
+
console.log(JSON.stringify({ exists: false }));
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
const stats = await getIndexStats(cwd);
|
|
930
|
+
const changes = await checkIndexChanges(cwd);
|
|
931
|
+
console.log(JSON.stringify({
|
|
932
|
+
exists: true,
|
|
933
|
+
totalFiles: stats.totalFiles,
|
|
934
|
+
totalLines: stats.totalLines,
|
|
935
|
+
totalFunctions: stats.totalFunctions,
|
|
936
|
+
totalClasses: stats.totalClasses,
|
|
937
|
+
lastUpdated: stats.lastUpdated,
|
|
938
|
+
patterns: stats.patterns,
|
|
939
|
+
hasChanges: changes.hasChanges,
|
|
940
|
+
changes: changes.hasChanges ? {
|
|
941
|
+
newFiles: changes.newFiles?.length || 0,
|
|
942
|
+
modifiedFiles: changes.modifiedFiles?.length || 0,
|
|
943
|
+
deletedFiles: changes.deletedFiles?.length || 0
|
|
944
|
+
} : null
|
|
945
|
+
}));
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
console.log(banner);
|
|
950
|
+
|
|
951
|
+
if (!(await hasIndex(cwd))) {
|
|
952
|
+
console.log(chalk.yellow('\n⚠️ No index found.'));
|
|
953
|
+
console.log(chalk.gray('Run `namnam index build` to create one.'));
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const stats = await getIndexStats(cwd);
|
|
958
|
+
const changes = await checkIndexChanges(cwd);
|
|
959
|
+
|
|
960
|
+
console.log(chalk.cyan('\n📊 Codebase Index Status\n'));
|
|
961
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
962
|
+
|
|
963
|
+
// Stats
|
|
964
|
+
console.log(chalk.white('Files indexed: ') + chalk.yellow(stats.totalFiles));
|
|
965
|
+
console.log(chalk.white('Total lines: ') + chalk.yellow(stats.totalLines.toLocaleString()));
|
|
966
|
+
console.log(chalk.white('Functions: ') + chalk.yellow(stats.totalFunctions));
|
|
967
|
+
console.log(chalk.white('Classes: ') + chalk.yellow(stats.totalClasses));
|
|
968
|
+
console.log(chalk.white('Last updated: ') + chalk.gray(new Date(stats.lastUpdated).toLocaleString()));
|
|
969
|
+
|
|
970
|
+
// Patterns
|
|
971
|
+
if (stats.patterns) {
|
|
972
|
+
console.log(chalk.gray('\n─'.repeat(50)));
|
|
973
|
+
console.log(chalk.cyan('\nDetected Patterns:'));
|
|
974
|
+
if (stats.patterns.framework) console.log(chalk.white(` Framework: ${stats.patterns.framework}`));
|
|
975
|
+
console.log(chalk.white(` Language: ${stats.patterns.language}`));
|
|
976
|
+
if (stats.patterns.styling) console.log(chalk.white(` Styling: ${stats.patterns.styling}`));
|
|
977
|
+
if (stats.patterns.testing) console.log(chalk.white(` Testing: ${stats.patterns.testing}`));
|
|
978
|
+
if (stats.patterns.packageManager) console.log(chalk.white(` Package Manager: ${stats.patterns.packageManager}`));
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Changes
|
|
982
|
+
console.log(chalk.gray('\n─'.repeat(50)));
|
|
983
|
+
if (changes.hasChanges) {
|
|
984
|
+
console.log(chalk.yellow('\n⚠️ Changes detected since last index:'));
|
|
985
|
+
if (changes.newFiles?.length) console.log(chalk.green(` + ${changes.newFiles.length} new files`));
|
|
986
|
+
if (changes.modifiedFiles?.length) console.log(chalk.yellow(` ~ ${changes.modifiedFiles.length} modified files`));
|
|
987
|
+
if (changes.deletedFiles?.length) console.log(chalk.red(` - ${changes.deletedFiles.length} deleted files`));
|
|
988
|
+
console.log(chalk.cyan('\nRun `namnam index build` to update.'));
|
|
989
|
+
} else {
|
|
990
|
+
console.log(chalk.green('\n✅ Index is up to date!'));
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
index
|
|
995
|
+
.command('search <query>')
|
|
996
|
+
.description('Search symbols (functions, classes, types) in the codebase')
|
|
997
|
+
.option('-l, --limit <n>', 'Limit results', '20')
|
|
998
|
+
.action(async (query, options) => {
|
|
999
|
+
console.log(banner);
|
|
1000
|
+
|
|
1001
|
+
const cwd = process.cwd();
|
|
1002
|
+
|
|
1003
|
+
if (!(await hasIndex(cwd))) {
|
|
1004
|
+
console.log(chalk.yellow('\n⚠️ No index found. Building...'));
|
|
1005
|
+
await buildIndex(cwd);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const results = await searchSymbols(query, cwd);
|
|
1009
|
+
const limit = parseInt(options.limit);
|
|
1010
|
+
|
|
1011
|
+
if (results.length === 0) {
|
|
1012
|
+
console.log(chalk.yellow(`\n🔍 No results found for "${query}"`));
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
console.log(chalk.cyan(`\n🔍 Search results for "${query}" (${results.length} found):\n`));
|
|
1017
|
+
|
|
1018
|
+
const limited = results.slice(0, limit);
|
|
1019
|
+
for (const result of limited) {
|
|
1020
|
+
const icon = result.type === 'function' ? '𝑓' : result.type === 'class' ? '◇' : '𝑇';
|
|
1021
|
+
const exported = result.exported ? chalk.green(' [exported]') : '';
|
|
1022
|
+
console.log(
|
|
1023
|
+
chalk.yellow(` ${icon} `) +
|
|
1024
|
+
chalk.white(result.name) +
|
|
1025
|
+
chalk.gray(` - ${result.file}:${result.line}`) +
|
|
1026
|
+
exported
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (results.length > limit) {
|
|
1031
|
+
console.log(chalk.gray(`\n ... and ${results.length - limit} more results`));
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
index
|
|
1036
|
+
.command('context')
|
|
1037
|
+
.description('Generate AI context from the index')
|
|
1038
|
+
.option('-q, --query <query>', 'Search query to find relevant files')
|
|
1039
|
+
.option('-f, --files <files>', 'Comma-separated list of files to include')
|
|
1040
|
+
.option('-o, --output <file>', 'Output to file instead of stdout')
|
|
1041
|
+
.action(async (options) => {
|
|
1042
|
+
const cwd = process.cwd();
|
|
1043
|
+
|
|
1044
|
+
if (!(await hasIndex(cwd))) {
|
|
1045
|
+
console.error(chalk.red('No index found. Run `namnam index build` first.'));
|
|
1046
|
+
process.exit(1);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const result = await generateAIContext({
|
|
1050
|
+
query: options.query,
|
|
1051
|
+
files: options.files ? options.files.split(',').map(f => f.trim()) : []
|
|
1052
|
+
}, cwd);
|
|
1053
|
+
|
|
1054
|
+
if (result.error) {
|
|
1055
|
+
console.error(chalk.red(result.error));
|
|
1056
|
+
process.exit(1);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (options.output) {
|
|
1060
|
+
await fs.writeFile(options.output, result.context);
|
|
1061
|
+
console.log(chalk.green(`Context written to: ${options.output}`));
|
|
1062
|
+
console.log(chalk.gray(`Estimated tokens: ${result.tokenEstimate}`));
|
|
1063
|
+
} else {
|
|
1064
|
+
console.log(result.context);
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
index
|
|
1069
|
+
.command('watch')
|
|
1070
|
+
.description('Watch for file changes and auto-update index (live indexing)')
|
|
1071
|
+
.option('-v, --verbose', 'Show detailed logs')
|
|
1072
|
+
.option('--no-auto-rebuild', 'Only notify on changes, do not auto-rebuild')
|
|
1073
|
+
.action(async (options) => {
|
|
1074
|
+
console.log(banner);
|
|
1075
|
+
console.log(chalk.cyan('\n👁️ Live Index Watcher\n'));
|
|
1076
|
+
|
|
1077
|
+
const cwd = process.cwd();
|
|
1078
|
+
|
|
1079
|
+
// Dynamic import to avoid loading watcher on every CLI call
|
|
1080
|
+
const { IndexWatcher, writeWatcherStatus, clearWatcherStatus } = await import('./watcher.js');
|
|
1081
|
+
|
|
1082
|
+
const watcher = new IndexWatcher(cwd, {
|
|
1083
|
+
verbose: options.verbose,
|
|
1084
|
+
autoRebuild: options.autoRebuild !== false
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// Event handlers
|
|
1088
|
+
watcher.on('started', () => {
|
|
1089
|
+
console.log(chalk.green('✓ Watcher started'));
|
|
1090
|
+
console.log(chalk.gray(` Watching: ${cwd}`));
|
|
1091
|
+
console.log(chalk.gray(` Auto-rebuild: ${options.autoRebuild !== false ? 'enabled' : 'disabled'}`));
|
|
1092
|
+
console.log(chalk.yellow('\n Press Ctrl+C to stop\n'));
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
watcher.on('change', ({ type, path: filePath }) => {
|
|
1096
|
+
const icon = type === 'rename' ? '~' : type === 'change' ? '±' : '+';
|
|
1097
|
+
console.log(chalk.gray(` ${icon} ${filePath}`));
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
watcher.on('indexing', ({ type }) => {
|
|
1101
|
+
if (type === 'initial') {
|
|
1102
|
+
console.log(chalk.yellow(' Building initial index...'));
|
|
1103
|
+
} else {
|
|
1104
|
+
console.log(chalk.yellow(' Updating index...'));
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
watcher.on('indexed', ({ type, changesProcessed }) => {
|
|
1109
|
+
if (type === 'initial') {
|
|
1110
|
+
console.log(chalk.green(' ✓ Initial index complete'));
|
|
1111
|
+
} else {
|
|
1112
|
+
console.log(chalk.green(` ✓ Index updated (${changesProcessed} files)`));
|
|
1113
|
+
}
|
|
1114
|
+
writeWatcherStatus(cwd, watcher.getStatus());
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
watcher.on('error', (err) => {
|
|
1118
|
+
console.log(chalk.red(` ✗ Error: ${err.message}`));
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
// Graceful shutdown
|
|
1122
|
+
const shutdown = async () => {
|
|
1123
|
+
console.log(chalk.yellow('\n\nStopping watcher...'));
|
|
1124
|
+
watcher.stop();
|
|
1125
|
+
await clearWatcherStatus(cwd);
|
|
1126
|
+
console.log(chalk.green('Watcher stopped.'));
|
|
1127
|
+
process.exit(0);
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
process.on('SIGINT', shutdown);
|
|
1131
|
+
process.on('SIGTERM', shutdown);
|
|
1132
|
+
|
|
1133
|
+
// Start watching
|
|
1134
|
+
try {
|
|
1135
|
+
await watcher.start();
|
|
1136
|
+
await writeWatcherStatus(cwd, watcher.getStatus());
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
console.error(chalk.red('Failed to start watcher:'), error.message);
|
|
1139
|
+
process.exit(1);
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// ========================================
|
|
1144
|
+
// MEMORY COMMANDS (Auto-Memory System)
|
|
1145
|
+
// ========================================
|
|
1146
|
+
|
|
1147
|
+
const memory = program
|
|
1148
|
+
.command('memory')
|
|
1149
|
+
.alias('mem')
|
|
1150
|
+
.description('Auto-memory system for persistent context across sessions');
|
|
1151
|
+
|
|
1152
|
+
memory
|
|
1153
|
+
.command('remember <content>')
|
|
1154
|
+
.description('Save a memory (decision, pattern, or context)')
|
|
1155
|
+
.option('-t, --type <type>', 'Memory type: decision, pattern, context, learning', 'context')
|
|
1156
|
+
.option('-i, --importance <level>', 'Importance: low, normal, high, critical', 'normal')
|
|
1157
|
+
.option('-g, --tags <tags>', 'Comma-separated tags')
|
|
1158
|
+
.option('-f, --files <files>', 'Related files (comma-separated)')
|
|
1159
|
+
.option('-s, --summary <summary>', 'Short summary of the memory')
|
|
1160
|
+
.action(async (content, options) => {
|
|
1161
|
+
console.log(banner);
|
|
1162
|
+
|
|
1163
|
+
try {
|
|
1164
|
+
const cwd = process.cwd();
|
|
1165
|
+
const spinner = ora('Saving memory...').start();
|
|
1166
|
+
|
|
1167
|
+
const memory = await remember(content, {
|
|
1168
|
+
type: options.type,
|
|
1169
|
+
importance: options.importance,
|
|
1170
|
+
tags: options.tags ? options.tags.split(',').map(t => t.trim()) : [],
|
|
1171
|
+
files: options.files ? options.files.split(',').map(f => f.trim()) : [],
|
|
1172
|
+
summary: options.summary,
|
|
1173
|
+
source: 'user'
|
|
1174
|
+
}, cwd);
|
|
1175
|
+
|
|
1176
|
+
spinner.succeed(chalk.green('Memory saved!'));
|
|
1177
|
+
|
|
1178
|
+
console.log(chalk.cyan('\n📝 Memory Details:'));
|
|
1179
|
+
console.log(chalk.gray(` ID: ${memory.id}`));
|
|
1180
|
+
console.log(chalk.gray(` Type: ${memory.type}`));
|
|
1181
|
+
console.log(chalk.gray(` Importance: ${memory.importance}`));
|
|
1182
|
+
if (memory.tags.length) {
|
|
1183
|
+
console.log(chalk.gray(` Tags: ${memory.tags.join(', ')}`));
|
|
1184
|
+
}
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
console.error(chalk.red('Failed to save memory:'), error.message);
|
|
1187
|
+
process.exit(1);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
memory
|
|
1192
|
+
.command('recall [query]')
|
|
1193
|
+
.description('Recall relevant memories')
|
|
1194
|
+
.option('-l, --limit <n>', 'Number of memories to return', '10')
|
|
1195
|
+
.option('-t, --types <types>', 'Filter by types (comma-separated)', 'decision,pattern,context,learning')
|
|
1196
|
+
.option('-i, --min-importance <level>', 'Minimum importance level', 'low')
|
|
1197
|
+
.option('--json', 'Output as JSON')
|
|
1198
|
+
.action(async (query, options) => {
|
|
1199
|
+
const cwd = process.cwd();
|
|
1200
|
+
|
|
1201
|
+
try {
|
|
1202
|
+
const memories = await recall(query || '', {
|
|
1203
|
+
limit: parseInt(options.limit),
|
|
1204
|
+
types: options.types.split(',').map(t => t.trim()),
|
|
1205
|
+
minImportance: options.minImportance
|
|
1206
|
+
}, cwd);
|
|
1207
|
+
|
|
1208
|
+
if (options.json) {
|
|
1209
|
+
console.log(JSON.stringify(memories, null, 2));
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
console.log(banner);
|
|
1214
|
+
|
|
1215
|
+
if (memories.length === 0) {
|
|
1216
|
+
console.log(chalk.yellow('\n⚠️ No memories found.'));
|
|
1217
|
+
if (query) {
|
|
1218
|
+
console.log(chalk.gray(` Query: "${query}"`));
|
|
1219
|
+
}
|
|
1220
|
+
console.log(chalk.gray('\n Use `namnam memory remember` to save memories.'));
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
console.log(chalk.cyan(`\n🧠 Memories${query ? ` matching "${query}"` : ''} (${memories.length}):\n`));
|
|
1225
|
+
|
|
1226
|
+
const typeIcons = {
|
|
1227
|
+
decision: '⚖️',
|
|
1228
|
+
pattern: '🔄',
|
|
1229
|
+
context: '📝',
|
|
1230
|
+
learning: '💡'
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
const importanceColors = {
|
|
1234
|
+
low: chalk.gray,
|
|
1235
|
+
normal: chalk.white,
|
|
1236
|
+
high: chalk.yellow,
|
|
1237
|
+
critical: chalk.red
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
for (const mem of memories) {
|
|
1241
|
+
const icon = typeIcons[mem.type] || '📝';
|
|
1242
|
+
const colorFn = importanceColors[mem.importance] || chalk.white;
|
|
1243
|
+
const age = getTimeAgo(new Date(mem.createdAt));
|
|
1244
|
+
|
|
1245
|
+
console.log(colorFn(` ${icon} ${mem.summary || mem.content.substring(0, 60)}...`));
|
|
1246
|
+
console.log(chalk.gray(` ${mem.type} | ${mem.importance} | ${age}`));
|
|
1247
|
+
if (mem.tags.length) {
|
|
1248
|
+
console.log(chalk.gray(` Tags: ${mem.tags.join(', ')}`));
|
|
1249
|
+
}
|
|
1250
|
+
console.log();
|
|
1251
|
+
}
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
console.error(chalk.red('Failed to recall memories:'), error.message);
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
memory
|
|
1259
|
+
.command('list')
|
|
1260
|
+
.description('List all memories')
|
|
1261
|
+
.option('-t, --type <type>', 'Filter by type')
|
|
1262
|
+
.option('-l, --limit <n>', 'Limit results', '20')
|
|
1263
|
+
.action(async (options) => {
|
|
1264
|
+
console.log(banner);
|
|
1265
|
+
|
|
1266
|
+
const cwd = process.cwd();
|
|
1267
|
+
|
|
1268
|
+
try {
|
|
1269
|
+
const index = await loadAutoMemoryIndex(cwd);
|
|
1270
|
+
|
|
1271
|
+
let allMemories = [
|
|
1272
|
+
...index.memories.map(m => ({ ...m, _category: 'context' })),
|
|
1273
|
+
...index.decisions.map(m => ({ ...m, _category: 'decision' })),
|
|
1274
|
+
...index.patterns.map(m => ({ ...m, _category: 'pattern' }))
|
|
1275
|
+
];
|
|
1276
|
+
|
|
1277
|
+
// Filter by type
|
|
1278
|
+
if (options.type) {
|
|
1279
|
+
allMemories = allMemories.filter(m => m.type === options.type);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// Sort by date (newest first)
|
|
1283
|
+
allMemories.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
1284
|
+
|
|
1285
|
+
// Limit
|
|
1286
|
+
const limit = parseInt(options.limit);
|
|
1287
|
+
allMemories = allMemories.slice(0, limit);
|
|
1288
|
+
|
|
1289
|
+
if (allMemories.length === 0) {
|
|
1290
|
+
console.log(chalk.yellow('\n⚠️ No memories found.'));
|
|
1291
|
+
console.log(chalk.gray('\n Use `namnam memory remember` to save memories.'));
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
console.log(chalk.cyan(`\n🧠 All Memories (${allMemories.length}):\n`));
|
|
1296
|
+
|
|
1297
|
+
console.log(chalk.gray(' ID | Type | Importance | Content'));
|
|
1298
|
+
console.log(chalk.gray(' ' + '-'.repeat(70)));
|
|
1299
|
+
|
|
1300
|
+
for (const mem of allMemories) {
|
|
1301
|
+
const content = (mem.summary || mem.content).substring(0, 35);
|
|
1302
|
+
console.log(
|
|
1303
|
+
chalk.gray(` ${mem.id.padEnd(20)} | `) +
|
|
1304
|
+
chalk.cyan(`${mem.type.padEnd(9)} | `) +
|
|
1305
|
+
chalk.yellow(`${mem.importance.padEnd(10)} | `) +
|
|
1306
|
+
chalk.white(content)
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
console.log(chalk.gray('\n Total: ' + allMemories.length + ' memories'));
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
console.error(chalk.red('Failed to list memories:'), error.message);
|
|
1313
|
+
process.exit(1);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
memory
|
|
1318
|
+
.command('session')
|
|
1319
|
+
.description('Manage current session')
|
|
1320
|
+
.option('-s, --start [task]', 'Start a new session')
|
|
1321
|
+
.option('-e, --end', 'End current session')
|
|
1322
|
+
.option('-u, --update', 'Update session with a decision or note')
|
|
1323
|
+
.option('-d, --decision <text>', 'Record a decision')
|
|
1324
|
+
.option('-n, --note <text>', 'Add a note/memory')
|
|
1325
|
+
.option('--status', 'Show current session status')
|
|
1326
|
+
.action(async (options) => {
|
|
1327
|
+
console.log(banner);
|
|
1328
|
+
|
|
1329
|
+
const cwd = process.cwd();
|
|
1330
|
+
|
|
1331
|
+
try {
|
|
1332
|
+
if (options.start !== undefined) {
|
|
1333
|
+
const task = typeof options.start === 'string' ? options.start : null;
|
|
1334
|
+
const session = await startSession({ task }, cwd);
|
|
1335
|
+
console.log(chalk.green('\n✅ Session started!'));
|
|
1336
|
+
console.log(chalk.gray(` ID: ${session.id}`));
|
|
1337
|
+
if (task) {
|
|
1338
|
+
console.log(chalk.gray(` Task: ${task}`));
|
|
1339
|
+
}
|
|
1340
|
+
console.log(chalk.cyan('\n Use `namnam memory session --end` when done.'));
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (options.end) {
|
|
1345
|
+
const session = await endSession({}, cwd);
|
|
1346
|
+
if (!session) {
|
|
1347
|
+
console.log(chalk.yellow('\n⚠️ No active session to end.'));
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
console.log(chalk.green('\n✅ Session ended!'));
|
|
1351
|
+
console.log(chalk.gray(` ID: ${session.id}`));
|
|
1352
|
+
console.log(chalk.gray(` Duration: ${getTimeAgo(new Date(session.startedAt))}`));
|
|
1353
|
+
console.log(chalk.gray(` Decisions saved: ${session.decisions.length}`));
|
|
1354
|
+
console.log(chalk.gray(` Memories saved: ${session.memories.length}`));
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
if (options.decision) {
|
|
1359
|
+
await updateSession({ decision: options.decision }, cwd);
|
|
1360
|
+
console.log(chalk.green('\n✅ Decision recorded!'));
|
|
1361
|
+
console.log(chalk.gray(` "${options.decision}"`));
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (options.note) {
|
|
1366
|
+
await updateSession({ memory: options.note }, cwd);
|
|
1367
|
+
console.log(chalk.green('\n✅ Note added!'));
|
|
1368
|
+
console.log(chalk.gray(` "${options.note}"`));
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Default: show status
|
|
1373
|
+
const session = await getCurrentSession(cwd);
|
|
1374
|
+
if (!session) {
|
|
1375
|
+
console.log(chalk.yellow('\n⚠️ No active session.'));
|
|
1376
|
+
console.log(chalk.gray('\n Use `namnam memory session --start` to begin.'));
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
console.log(chalk.cyan('\n📋 Current Session:\n'));
|
|
1381
|
+
console.log(chalk.gray(` ID: ${session.id}`));
|
|
1382
|
+
console.log(chalk.gray(` Started: ${new Date(session.startedAt).toLocaleString()}`));
|
|
1383
|
+
console.log(chalk.gray(` Task: ${session.task || '(none)'}`));
|
|
1384
|
+
console.log(chalk.gray(` Files modified: ${session.filesModified.length}`));
|
|
1385
|
+
console.log(chalk.gray(` Decisions: ${session.decisions.length}`));
|
|
1386
|
+
console.log(chalk.gray(` Notes: ${session.memories.length}`));
|
|
1387
|
+
|
|
1388
|
+
if (session.decisions.length > 0) {
|
|
1389
|
+
console.log(chalk.cyan('\n Recent Decisions:'));
|
|
1390
|
+
for (const d of session.decisions.slice(-3)) {
|
|
1391
|
+
console.log(chalk.white(` • ${d.content}`));
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
} catch (error) {
|
|
1395
|
+
console.error(chalk.red('Session error:'), error.message);
|
|
1396
|
+
process.exit(1);
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
memory
|
|
1401
|
+
.command('context')
|
|
1402
|
+
.description('Generate auto-memory context for AI consumption')
|
|
1403
|
+
.option('-q, --query <query>', 'Query to find relevant memories')
|
|
1404
|
+
.option('-l, --limit <n>', 'Max memories to include', '10')
|
|
1405
|
+
.option('--json', 'Output as JSON')
|
|
1406
|
+
.action(async (options) => {
|
|
1407
|
+
const cwd = process.cwd();
|
|
1408
|
+
|
|
1409
|
+
try {
|
|
1410
|
+
const context = await generateAutoMemoryContext({
|
|
1411
|
+
query: options.query,
|
|
1412
|
+
limit: parseInt(options.limit)
|
|
1413
|
+
}, cwd);
|
|
1414
|
+
|
|
1415
|
+
if (!context) {
|
|
1416
|
+
if (!options.json) {
|
|
1417
|
+
console.log(chalk.yellow('No relevant memories found.'));
|
|
1418
|
+
} else {
|
|
1419
|
+
console.log(JSON.stringify({ context: null }));
|
|
1420
|
+
}
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
if (options.json) {
|
|
1425
|
+
console.log(JSON.stringify({ context }));
|
|
1426
|
+
} else {
|
|
1427
|
+
console.log(context);
|
|
1428
|
+
}
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
console.error(chalk.red('Failed to generate context:'), error.message);
|
|
1431
|
+
process.exit(1);
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
memory
|
|
1436
|
+
.command('init')
|
|
1437
|
+
.description('Initialize auto-memory system for this project')
|
|
1438
|
+
.action(async () => {
|
|
1439
|
+
console.log(banner);
|
|
1440
|
+
|
|
1441
|
+
const cwd = process.cwd();
|
|
1442
|
+
|
|
1443
|
+
try {
|
|
1444
|
+
const spinner = ora('Initializing auto-memory system...').start();
|
|
1445
|
+
await initAutoMemory(cwd);
|
|
1446
|
+
spinner.succeed(chalk.green('Auto-memory system initialized!'));
|
|
1447
|
+
|
|
1448
|
+
console.log(chalk.cyan('\n📝 Memory directory created at:'));
|
|
1449
|
+
console.log(chalk.gray(` .claude/auto-memories/`));
|
|
1450
|
+
|
|
1451
|
+
console.log(chalk.cyan('\n🚀 Quick Start:'));
|
|
1452
|
+
console.log(chalk.gray(' namnam memory remember "User prefers TypeScript" -t pattern'));
|
|
1453
|
+
console.log(chalk.gray(' namnam memory recall "preferences"'));
|
|
1454
|
+
console.log(chalk.gray(' namnam memory session --start "Implement feature X"'));
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
console.error(chalk.red('Failed to initialize:'), error.message);
|
|
1457
|
+
process.exit(1);
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
// Helper function for time ago
|
|
1462
|
+
function getTimeAgo(date) {
|
|
1463
|
+
const seconds = Math.floor((new Date() - date) / 1000);
|
|
1464
|
+
|
|
1465
|
+
if (seconds < 60) return 'just now';
|
|
1466
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
1467
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
1468
|
+
if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`;
|
|
1469
|
+
return date.toLocaleDateString();
|
|
1470
|
+
}
|
|
1471
|
+
|
|
460
1472
|
program.parse();
|