namnam-skills 1.0.0 ā 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/cli.js +544 -0
- package/src/conversation.js +447 -0
- package/src/indexer.js +793 -0
- package/src/templates/core/namnam.md +493 -237
- package/src/templates/platforms/AGENTS.md +47 -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/validate-and-fix.md +0 -69
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "namnam-skills",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Ultimate AI Skills Installer - Universal support for Claude, Codex, Cursor, Windsurf, Cline, Aider, and more. 150+ agents, workflows, and skills.",
|
|
5
5
|
"author": "NamNam",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"commander": "^12.1.0",
|
|
48
48
|
"fs-extra": "^11.2.0",
|
|
49
49
|
"inquirer": "^9.2.15",
|
|
50
|
+
"namnam-skills": "^1.0.0",
|
|
50
51
|
"ora": "^8.0.1"
|
|
51
52
|
},
|
|
52
53
|
"engines": {
|
package/src/cli.js
CHANGED
|
@@ -7,6 +7,25 @@ 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
|
+
} from './conversation.js';
|
|
20
|
+
import {
|
|
21
|
+
buildIndex,
|
|
22
|
+
hasIndex,
|
|
23
|
+
getIndexMeta,
|
|
24
|
+
getIndexStats,
|
|
25
|
+
checkIndexChanges,
|
|
26
|
+
searchSymbols,
|
|
27
|
+
generateAIContext
|
|
28
|
+
} from './indexer.js';
|
|
10
29
|
|
|
11
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
31
|
const __dirname = path.dirname(__filename);
|
|
@@ -457,4 +476,529 @@ async function generatePlatformFiles(cwd, templatesDir, force = false) {
|
|
|
457
476
|
}
|
|
458
477
|
}
|
|
459
478
|
|
|
479
|
+
// ========================================
|
|
480
|
+
// CONVERSATION COMMANDS
|
|
481
|
+
// ========================================
|
|
482
|
+
|
|
483
|
+
const conversation = program
|
|
484
|
+
.command('conversation')
|
|
485
|
+
.alias('conv')
|
|
486
|
+
.description('Manage conversation context (@conversation feature)');
|
|
487
|
+
|
|
488
|
+
conversation
|
|
489
|
+
.command('init')
|
|
490
|
+
.description('Initialize conversation storage in current project')
|
|
491
|
+
.action(async () => {
|
|
492
|
+
console.log(banner);
|
|
493
|
+
const spinner = ora('Initializing conversation storage...').start();
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const cwd = process.cwd();
|
|
497
|
+
await initConversations(cwd);
|
|
498
|
+
spinner.succeed(chalk.green('Conversation storage initialized!'));
|
|
499
|
+
console.log(chalk.gray('\nLocation: .claude/conversations/'));
|
|
500
|
+
console.log(chalk.cyan('\nUsage:'));
|
|
501
|
+
console.log(chalk.white(' namnam conv save - Save current conversation'));
|
|
502
|
+
console.log(chalk.white(' namnam conv list - List saved conversations'));
|
|
503
|
+
console.log(chalk.white(' namnam conv show <id> - View a conversation'));
|
|
504
|
+
console.log(chalk.white(' @conversation:<id> - Reference in prompts'));
|
|
505
|
+
} catch (error) {
|
|
506
|
+
spinner.fail(chalk.red('Failed to initialize'));
|
|
507
|
+
console.error(error.message);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
conversation
|
|
512
|
+
.command('save')
|
|
513
|
+
.description('Save a conversation with context')
|
|
514
|
+
.option('-t, --title <title>', 'Conversation title')
|
|
515
|
+
.option('-s, --summary <summary>', 'Short summary')
|
|
516
|
+
.option('-g, --tags <tags>', 'Comma-separated tags')
|
|
517
|
+
.action(async (options) => {
|
|
518
|
+
console.log(banner);
|
|
519
|
+
|
|
520
|
+
const answers = await inquirer.prompt([
|
|
521
|
+
{
|
|
522
|
+
type: 'input',
|
|
523
|
+
name: 'title',
|
|
524
|
+
message: 'Conversation title:',
|
|
525
|
+
default: options.title || `Conversation ${new Date().toLocaleDateString()}`,
|
|
526
|
+
when: !options.title
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
type: 'input',
|
|
530
|
+
name: 'summary',
|
|
531
|
+
message: 'Short summary (1-2 sentences):',
|
|
532
|
+
when: !options.summary
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
type: 'editor',
|
|
536
|
+
name: 'context',
|
|
537
|
+
message: 'Enter the conversation context (opens editor):'
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
type: 'input',
|
|
541
|
+
name: 'tags',
|
|
542
|
+
message: 'Tags (comma-separated):',
|
|
543
|
+
when: !options.tags
|
|
544
|
+
}
|
|
545
|
+
]);
|
|
546
|
+
|
|
547
|
+
const spinner = ora('Saving conversation...').start();
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
const result = await saveConversation({
|
|
551
|
+
title: options.title || answers.title,
|
|
552
|
+
summary: options.summary || answers.summary || '',
|
|
553
|
+
context: answers.context || '',
|
|
554
|
+
tags: (options.tags || answers.tags || '').split(',').map(t => t.trim()).filter(Boolean)
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
spinner.succeed(chalk.green('Conversation saved!'));
|
|
558
|
+
console.log(chalk.cyan(`\nID: ${chalk.white(result.shortId)}`));
|
|
559
|
+
console.log(chalk.cyan(`Full ID: ${chalk.gray(result.id)}`));
|
|
560
|
+
console.log(chalk.yellow(`\nReference with: @conversation:${result.shortId}`));
|
|
561
|
+
} catch (error) {
|
|
562
|
+
spinner.fail(chalk.red('Failed to save conversation'));
|
|
563
|
+
console.error(error.message);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
conversation
|
|
568
|
+
.command('list')
|
|
569
|
+
.description('List all saved conversations')
|
|
570
|
+
.option('-l, --limit <number>', 'Number of conversations to show', '20')
|
|
571
|
+
.option('-t, --tag <tag>', 'Filter by tag')
|
|
572
|
+
.option('-s, --search <term>', 'Search in title/summary')
|
|
573
|
+
.action(async (options) => {
|
|
574
|
+
console.log(banner);
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
const conversations = await listConversations({
|
|
578
|
+
limit: parseInt(options.limit),
|
|
579
|
+
tag: options.tag,
|
|
580
|
+
search: options.search
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (conversations.length === 0) {
|
|
584
|
+
console.log(chalk.yellow('\nš No conversations found.'));
|
|
585
|
+
console.log(chalk.gray('Use `namnam conv save` to save your first conversation.'));
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
console.log(chalk.cyan(`\nš Saved Conversations (${conversations.length}):\n`));
|
|
590
|
+
|
|
591
|
+
for (const conv of conversations) {
|
|
592
|
+
const date = new Date(conv.createdAt).toLocaleDateString();
|
|
593
|
+
const tags = conv.tags?.length ? chalk.gray(` [${conv.tags.join(', ')}]`) : '';
|
|
594
|
+
|
|
595
|
+
console.log(chalk.yellow(` ${conv.shortId}`) + chalk.white(` - ${conv.title}`) + tags);
|
|
596
|
+
if (conv.summary) {
|
|
597
|
+
console.log(chalk.gray(` ${conv.summary.substring(0, 60)}${conv.summary.length > 60 ? '...' : ''}`));
|
|
598
|
+
}
|
|
599
|
+
console.log(chalk.gray(` ${date}`));
|
|
600
|
+
console.log();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
console.log(chalk.cyan('Usage:'));
|
|
604
|
+
console.log(chalk.white(' namnam conv show <id> - View full context'));
|
|
605
|
+
console.log(chalk.white(' @conversation:<id> - Reference in AI prompts'));
|
|
606
|
+
} catch (error) {
|
|
607
|
+
console.error(chalk.red('Failed to list conversations:'), error.message);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
conversation
|
|
612
|
+
.command('show <id>')
|
|
613
|
+
.description('Show conversation details and context')
|
|
614
|
+
.option('-f, --full', 'Show full log if available')
|
|
615
|
+
.action(async (id, options) => {
|
|
616
|
+
console.log(banner);
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
const conv = await getConversation(id);
|
|
620
|
+
|
|
621
|
+
if (!conv) {
|
|
622
|
+
console.log(chalk.red(`\nā Conversation not found: ${id}`));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
console.log(chalk.cyan(`\nš ${conv.title}\n`));
|
|
627
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
628
|
+
console.log(chalk.white('ID: ') + chalk.yellow(conv.shortId));
|
|
629
|
+
console.log(chalk.white('Full ID: ') + chalk.gray(conv.id));
|
|
630
|
+
console.log(chalk.white('Created: ') + chalk.gray(new Date(conv.createdAt).toLocaleString()));
|
|
631
|
+
console.log(chalk.white('Updated: ') + chalk.gray(new Date(conv.updatedAt).toLocaleString()));
|
|
632
|
+
if (conv.tags?.length) {
|
|
633
|
+
console.log(chalk.white('Tags: ') + chalk.cyan(conv.tags.join(', ')));
|
|
634
|
+
}
|
|
635
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
636
|
+
|
|
637
|
+
if (conv.summary) {
|
|
638
|
+
console.log(chalk.cyan('\nš Summary:\n'));
|
|
639
|
+
console.log(conv.summary);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (conv.context) {
|
|
643
|
+
console.log(chalk.cyan('\nš Context:\n'));
|
|
644
|
+
console.log(conv.context);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (options.full && conv.fullLog) {
|
|
648
|
+
console.log(chalk.cyan('\nš Full Log:\n'));
|
|
649
|
+
console.log(conv.fullLog);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
console.log(chalk.gray('\nā'.repeat(50)));
|
|
653
|
+
console.log(chalk.yellow(`\nReference with: @conversation:${conv.shortId}`));
|
|
654
|
+
} catch (error) {
|
|
655
|
+
console.error(chalk.red('Failed to show conversation:'), error.message);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
conversation
|
|
660
|
+
.command('context <id>')
|
|
661
|
+
.description('Output conversation context for AI consumption')
|
|
662
|
+
.action(async (id) => {
|
|
663
|
+
try {
|
|
664
|
+
const context = await getConversationContext(id);
|
|
665
|
+
|
|
666
|
+
if (!context) {
|
|
667
|
+
console.error(`Conversation not found: ${id}`);
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Output raw context (for piping to other tools)
|
|
672
|
+
console.log(context);
|
|
673
|
+
} catch (error) {
|
|
674
|
+
console.error('Error:', error.message);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
conversation
|
|
680
|
+
.command('delete <id>')
|
|
681
|
+
.description('Delete a saved conversation')
|
|
682
|
+
.option('-f, --force', 'Skip confirmation')
|
|
683
|
+
.action(async (id, options) => {
|
|
684
|
+
console.log(banner);
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
const conv = await getConversation(id);
|
|
688
|
+
|
|
689
|
+
if (!conv) {
|
|
690
|
+
console.log(chalk.red(`\nā Conversation not found: ${id}`));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (!options.force) {
|
|
695
|
+
const answers = await inquirer.prompt([
|
|
696
|
+
{
|
|
697
|
+
type: 'confirm',
|
|
698
|
+
name: 'confirm',
|
|
699
|
+
message: `Delete "${conv.title}" (${conv.shortId})?`,
|
|
700
|
+
default: false
|
|
701
|
+
}
|
|
702
|
+
]);
|
|
703
|
+
|
|
704
|
+
if (!answers.confirm) {
|
|
705
|
+
console.log(chalk.gray('Cancelled.'));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const spinner = ora('Deleting conversation...').start();
|
|
711
|
+
await deleteConversation(id);
|
|
712
|
+
spinner.succeed(chalk.green(`Deleted: ${conv.title}`));
|
|
713
|
+
} catch (error) {
|
|
714
|
+
console.error(chalk.red('Failed to delete conversation:'), error.message);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
conversation
|
|
719
|
+
.command('update <id>')
|
|
720
|
+
.description('Update conversation metadata or context')
|
|
721
|
+
.option('-t, --title <title>', 'Update title')
|
|
722
|
+
.option('-s, --summary <summary>', 'Update summary')
|
|
723
|
+
.option('-g, --tags <tags>', 'Update tags (comma-separated)')
|
|
724
|
+
.option('-c, --context', 'Update context via editor')
|
|
725
|
+
.action(async (id, options) => {
|
|
726
|
+
console.log(banner);
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
const conv = await getConversation(id);
|
|
730
|
+
|
|
731
|
+
if (!conv) {
|
|
732
|
+
console.log(chalk.red(`\nā Conversation not found: ${id}`));
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const updates = {};
|
|
737
|
+
|
|
738
|
+
if (options.title) updates.title = options.title;
|
|
739
|
+
if (options.summary) updates.summary = options.summary;
|
|
740
|
+
if (options.tags) {
|
|
741
|
+
updates.tags = options.tags.split(',').map(t => t.trim()).filter(Boolean);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// If context update requested, open editor
|
|
745
|
+
if (options.context) {
|
|
746
|
+
const answers = await inquirer.prompt([
|
|
747
|
+
{
|
|
748
|
+
type: 'editor',
|
|
749
|
+
name: 'context',
|
|
750
|
+
message: 'Update context:',
|
|
751
|
+
default: conv.context || ''
|
|
752
|
+
}
|
|
753
|
+
]);
|
|
754
|
+
if (answers.context !== undefined) {
|
|
755
|
+
updates.context = answers.context;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (Object.keys(updates).length === 0) {
|
|
760
|
+
console.log(chalk.yellow('\nā ļø No updates provided.'));
|
|
761
|
+
console.log(chalk.gray('Use -t, -s, -g, or -c flags to specify what to update.'));
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const spinner = ora('Updating conversation...').start();
|
|
766
|
+
await updateConversation(id, updates);
|
|
767
|
+
spinner.succeed(chalk.green('Conversation updated!'));
|
|
768
|
+
|
|
769
|
+
console.log(chalk.cyan(`\nUpdated: ${options.title || conv.title}`));
|
|
770
|
+
} catch (error) {
|
|
771
|
+
console.error(chalk.red('Failed to update conversation:'), error.message);
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
conversation
|
|
776
|
+
.command('export <id> [output]')
|
|
777
|
+
.description('Export conversation to markdown file')
|
|
778
|
+
.action(async (id, output) => {
|
|
779
|
+
console.log(banner);
|
|
780
|
+
|
|
781
|
+
try {
|
|
782
|
+
const conv = await getConversation(id);
|
|
783
|
+
|
|
784
|
+
if (!conv) {
|
|
785
|
+
console.log(chalk.red(`\nā Conversation not found: ${id}`));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const outputPath = output || `conversation-${conv.shortId}.md`;
|
|
790
|
+
const spinner = ora('Exporting conversation...').start();
|
|
791
|
+
|
|
792
|
+
await exportConversation(id, outputPath);
|
|
793
|
+
spinner.succeed(chalk.green(`Exported to: ${outputPath}`));
|
|
794
|
+
} catch (error) {
|
|
795
|
+
console.error(chalk.red('Failed to export conversation:'), error.message);
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// ========================================
|
|
800
|
+
// INDEX COMMANDS
|
|
801
|
+
// ========================================
|
|
802
|
+
|
|
803
|
+
const index = program
|
|
804
|
+
.command('index')
|
|
805
|
+
.alias('idx')
|
|
806
|
+
.description('Codebase indexing for deep code understanding');
|
|
807
|
+
|
|
808
|
+
index
|
|
809
|
+
.command('build')
|
|
810
|
+
.description('Build or rebuild the codebase index')
|
|
811
|
+
.option('-f, --force', 'Force rebuild even if index exists')
|
|
812
|
+
.action(async (options) => {
|
|
813
|
+
console.log(banner);
|
|
814
|
+
|
|
815
|
+
const cwd = process.cwd();
|
|
816
|
+
|
|
817
|
+
// Check if index exists
|
|
818
|
+
if (!options.force && await hasIndex(cwd)) {
|
|
819
|
+
const changes = await checkIndexChanges(cwd);
|
|
820
|
+
if (!changes.hasChanges) {
|
|
821
|
+
console.log(chalk.green('\nā
Index is up to date!'));
|
|
822
|
+
const stats = await getIndexStats(cwd);
|
|
823
|
+
console.log(chalk.gray(` Last updated: ${new Date(stats.lastUpdated).toLocaleString()}`));
|
|
824
|
+
console.log(chalk.gray(` Files: ${stats.totalFiles} | Functions: ${stats.totalFunctions} | Classes: ${stats.totalClasses}`));
|
|
825
|
+
console.log(chalk.cyan('\nUse --force to rebuild anyway.'));
|
|
826
|
+
return;
|
|
827
|
+
} else {
|
|
828
|
+
console.log(chalk.yellow('\nš Changes detected since last index:'));
|
|
829
|
+
if (changes.newFiles?.length) console.log(chalk.green(` + ${changes.newFiles.length} new files`));
|
|
830
|
+
if (changes.modifiedFiles?.length) console.log(chalk.yellow(` ~ ${changes.modifiedFiles.length} modified files`));
|
|
831
|
+
if (changes.deletedFiles?.length) console.log(chalk.red(` - ${changes.deletedFiles.length} deleted files`));
|
|
832
|
+
console.log();
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const spinner = ora('Building codebase index...').start();
|
|
837
|
+
|
|
838
|
+
try {
|
|
839
|
+
const meta = await buildIndex(cwd, {
|
|
840
|
+
onProgress: (progress) => {
|
|
841
|
+
if (progress.phase === 'scanning') {
|
|
842
|
+
spinner.text = progress.message;
|
|
843
|
+
} else if (progress.phase === 'indexing') {
|
|
844
|
+
spinner.text = `Indexing: ${progress.current}/${progress.total} files`;
|
|
845
|
+
} else if (progress.phase === 'analyzing') {
|
|
846
|
+
spinner.text = 'Analyzing patterns...';
|
|
847
|
+
} else if (progress.phase === 'saving') {
|
|
848
|
+
spinner.text = 'Saving index...';
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
spinner.succeed(chalk.green('Index built successfully!'));
|
|
854
|
+
|
|
855
|
+
console.log(chalk.cyan('\nš Index Statistics:'));
|
|
856
|
+
console.log(chalk.white(` Files indexed: ${meta.stats.totalFiles}`));
|
|
857
|
+
console.log(chalk.white(` Total lines: ${meta.stats.totalLines.toLocaleString()}`));
|
|
858
|
+
console.log(chalk.white(` Functions: ${meta.stats.totalFunctions}`));
|
|
859
|
+
console.log(chalk.white(` Classes: ${meta.stats.totalClasses}`));
|
|
860
|
+
|
|
861
|
+
if (meta.patterns) {
|
|
862
|
+
console.log(chalk.cyan('\nš Detected Patterns:'));
|
|
863
|
+
if (meta.patterns.framework) console.log(chalk.white(` Framework: ${meta.patterns.framework}`));
|
|
864
|
+
console.log(chalk.white(` Language: ${meta.patterns.language}`));
|
|
865
|
+
if (meta.patterns.styling) console.log(chalk.white(` Styling: ${meta.patterns.styling}`));
|
|
866
|
+
if (meta.patterns.testing) console.log(chalk.white(` Testing: ${meta.patterns.testing}`));
|
|
867
|
+
if (meta.patterns.packageManager) console.log(chalk.white(` Package Manager: ${meta.patterns.packageManager}`));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
console.log(chalk.gray('\nš Index location: .claude/index/'));
|
|
871
|
+
console.log(chalk.yellow('\nš” The index will be auto-loaded when using /namnam'));
|
|
872
|
+
|
|
873
|
+
} catch (error) {
|
|
874
|
+
spinner.fail(chalk.red('Failed to build index'));
|
|
875
|
+
console.error(error.message);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
index
|
|
880
|
+
.command('status')
|
|
881
|
+
.description('Show index status and statistics')
|
|
882
|
+
.action(async () => {
|
|
883
|
+
console.log(banner);
|
|
884
|
+
|
|
885
|
+
const cwd = process.cwd();
|
|
886
|
+
|
|
887
|
+
if (!(await hasIndex(cwd))) {
|
|
888
|
+
console.log(chalk.yellow('\nā ļø No index found.'));
|
|
889
|
+
console.log(chalk.gray('Run `namnam index build` to create one.'));
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const stats = await getIndexStats(cwd);
|
|
894
|
+
const changes = await checkIndexChanges(cwd);
|
|
895
|
+
|
|
896
|
+
console.log(chalk.cyan('\nš Codebase Index Status\n'));
|
|
897
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
898
|
+
|
|
899
|
+
// Stats
|
|
900
|
+
console.log(chalk.white('Files indexed: ') + chalk.yellow(stats.totalFiles));
|
|
901
|
+
console.log(chalk.white('Total lines: ') + chalk.yellow(stats.totalLines.toLocaleString()));
|
|
902
|
+
console.log(chalk.white('Functions: ') + chalk.yellow(stats.totalFunctions));
|
|
903
|
+
console.log(chalk.white('Classes: ') + chalk.yellow(stats.totalClasses));
|
|
904
|
+
console.log(chalk.white('Last updated: ') + chalk.gray(new Date(stats.lastUpdated).toLocaleString()));
|
|
905
|
+
|
|
906
|
+
// Patterns
|
|
907
|
+
if (stats.patterns) {
|
|
908
|
+
console.log(chalk.gray('\nā'.repeat(50)));
|
|
909
|
+
console.log(chalk.cyan('\nDetected Patterns:'));
|
|
910
|
+
if (stats.patterns.framework) console.log(chalk.white(` Framework: ${stats.patterns.framework}`));
|
|
911
|
+
console.log(chalk.white(` Language: ${stats.patterns.language}`));
|
|
912
|
+
if (stats.patterns.styling) console.log(chalk.white(` Styling: ${stats.patterns.styling}`));
|
|
913
|
+
if (stats.patterns.testing) console.log(chalk.white(` Testing: ${stats.patterns.testing}`));
|
|
914
|
+
if (stats.patterns.packageManager) console.log(chalk.white(` Package Manager: ${stats.patterns.packageManager}`));
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Changes
|
|
918
|
+
console.log(chalk.gray('\nā'.repeat(50)));
|
|
919
|
+
if (changes.hasChanges) {
|
|
920
|
+
console.log(chalk.yellow('\nā ļø Changes detected since last index:'));
|
|
921
|
+
if (changes.newFiles?.length) console.log(chalk.green(` + ${changes.newFiles.length} new files`));
|
|
922
|
+
if (changes.modifiedFiles?.length) console.log(chalk.yellow(` ~ ${changes.modifiedFiles.length} modified files`));
|
|
923
|
+
if (changes.deletedFiles?.length) console.log(chalk.red(` - ${changes.deletedFiles.length} deleted files`));
|
|
924
|
+
console.log(chalk.cyan('\nRun `namnam index build` to update.'));
|
|
925
|
+
} else {
|
|
926
|
+
console.log(chalk.green('\nā
Index is up to date!'));
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
index
|
|
931
|
+
.command('search <query>')
|
|
932
|
+
.description('Search symbols (functions, classes, types) in the codebase')
|
|
933
|
+
.option('-l, --limit <n>', 'Limit results', '20')
|
|
934
|
+
.action(async (query, options) => {
|
|
935
|
+
console.log(banner);
|
|
936
|
+
|
|
937
|
+
const cwd = process.cwd();
|
|
938
|
+
|
|
939
|
+
if (!(await hasIndex(cwd))) {
|
|
940
|
+
console.log(chalk.yellow('\nā ļø No index found. Building...'));
|
|
941
|
+
await buildIndex(cwd);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const results = await searchSymbols(query, cwd);
|
|
945
|
+
const limit = parseInt(options.limit);
|
|
946
|
+
|
|
947
|
+
if (results.length === 0) {
|
|
948
|
+
console.log(chalk.yellow(`\nš No results found for "${query}"`));
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
console.log(chalk.cyan(`\nš Search results for "${query}" (${results.length} found):\n`));
|
|
953
|
+
|
|
954
|
+
const limited = results.slice(0, limit);
|
|
955
|
+
for (const result of limited) {
|
|
956
|
+
const icon = result.type === 'function' ? 'š' : result.type === 'class' ? 'ā' : 'š';
|
|
957
|
+
const exported = result.exported ? chalk.green(' [exported]') : '';
|
|
958
|
+
console.log(
|
|
959
|
+
chalk.yellow(` ${icon} `) +
|
|
960
|
+
chalk.white(result.name) +
|
|
961
|
+
chalk.gray(` - ${result.file}:${result.line}`) +
|
|
962
|
+
exported
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (results.length > limit) {
|
|
967
|
+
console.log(chalk.gray(`\n ... and ${results.length - limit} more results`));
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
index
|
|
972
|
+
.command('context')
|
|
973
|
+
.description('Generate AI context from the index')
|
|
974
|
+
.option('-q, --query <query>', 'Search query to find relevant files')
|
|
975
|
+
.option('-f, --files <files>', 'Comma-separated list of files to include')
|
|
976
|
+
.option('-o, --output <file>', 'Output to file instead of stdout')
|
|
977
|
+
.action(async (options) => {
|
|
978
|
+
const cwd = process.cwd();
|
|
979
|
+
|
|
980
|
+
if (!(await hasIndex(cwd))) {
|
|
981
|
+
console.error(chalk.red('No index found. Run `namnam index build` first.'));
|
|
982
|
+
process.exit(1);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const result = await generateAIContext({
|
|
986
|
+
query: options.query,
|
|
987
|
+
files: options.files ? options.files.split(',').map(f => f.trim()) : []
|
|
988
|
+
}, cwd);
|
|
989
|
+
|
|
990
|
+
if (result.error) {
|
|
991
|
+
console.error(chalk.red(result.error));
|
|
992
|
+
process.exit(1);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (options.output) {
|
|
996
|
+
await fs.writeFile(options.output, result.context);
|
|
997
|
+
console.log(chalk.green(`Context written to: ${options.output}`));
|
|
998
|
+
console.log(chalk.gray(`Estimated tokens: ${result.tokenEstimate}`));
|
|
999
|
+
} else {
|
|
1000
|
+
console.log(result.context);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
460
1004
|
program.parse();
|