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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "namnam-skills",
3
- "version": "1.0.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();