tokrepo 1.1.0 → 2.0.0

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.
Files changed (2) hide show
  1. package/bin/tokrepo.js +254 -25
  2. package/package.json +2 -2
package/bin/tokrepo.js CHANGED
@@ -79,7 +79,7 @@ function apiRequest(method, urlPath, body, token, apiBase) {
79
79
 
80
80
  const headers = {
81
81
  'Content-Type': 'application/json',
82
- 'User-Agent': 'tokrepo-cli/1.1.0',
82
+ 'User-Agent': 'tokrepo-cli/2.0.0',
83
83
  };
84
84
  if (token) {
85
85
  headers['Authorization'] = `Bearer ${token}`;
@@ -513,6 +513,221 @@ async function cmdPull() {
513
513
  }
514
514
  }
515
515
 
516
+ // ─── Search ───
517
+
518
+ async function cmdSearch() {
519
+ const query = process.argv.slice(3).join(' ');
520
+ if (!query) error('Usage: tokrepo search <keyword>');
521
+
522
+ log(`\n${C.bold}tokrepo search${C.reset} "${query}"\n`);
523
+
524
+ const config = readConfig();
525
+ const apiBase = config?.api || DEFAULT_API;
526
+
527
+ try {
528
+ const encoded = encodeURIComponent(query);
529
+ const data = await apiRequest('GET', `/api/v1/tokenboard/workflows/list?keyword=${encoded}&page=1&page_size=20&sort_by=views`, null, config?.token, apiBase);
530
+
531
+ if (!data.list || data.list.length === 0) {
532
+ info('No assets found.');
533
+ log(`\n ${C.dim}Try different keywords or browse: https://tokrepo.com/en/featured${C.reset}\n`);
534
+ return;
535
+ }
536
+
537
+ log(` ${C.bold}${data.total}${C.reset} results:\n`);
538
+
539
+ for (let i = 0; i < data.list.length; i++) {
540
+ const wf = data.list[i];
541
+ const tags = (wf.tags || []).map(t => t.name).join(', ');
542
+ const views = wf.view_count || 0;
543
+ const votes = wf.vote_count || 0;
544
+
545
+ log(` ${C.dim}${String(i + 1).padStart(2)}.${C.reset} ${C.bold}${wf.title}${C.reset}`);
546
+ if (tags) log(` ${C.cyan}${tags}${C.reset} ${C.dim}★${votes} 👁${views}${C.reset}`);
547
+ log(` ${C.dim}tokrepo install ${wf.uuid}${C.reset}`);
548
+ log('');
549
+ }
550
+ } catch (e) {
551
+ error(`Search failed: ${e.message}`);
552
+ }
553
+ }
554
+
555
+ // ─── Install (smart pull with correct placement) ───
556
+
557
+ async function cmdInstall() {
558
+ const target = process.argv[3];
559
+ if (!target) {
560
+ error(`Usage: tokrepo install <name-or-uuid>
561
+
562
+ Examples:
563
+ tokrepo install awesome-cursor-rules
564
+ tokrepo install ca000374-f5d8-4d75-a30c-460fda0b6b0e
565
+ tokrepo install https://tokrepo.com/en/workflows/ca000374-...`);
566
+ }
567
+
568
+ log(`\n${C.bold}tokrepo install${C.reset}\n`);
569
+
570
+ const config = readConfig();
571
+ const apiBase = config?.api || DEFAULT_API;
572
+
573
+ // Resolve target to UUID
574
+ let uuid = target;
575
+
576
+ // URL format
577
+ const urlMatch = target.match(/workflows\/([a-f0-9-]+)/);
578
+ if (urlMatch) {
579
+ uuid = urlMatch[1];
580
+ }
581
+ // UUID format check
582
+ else if (!/^[a-f0-9-]{36}$/.test(target)) {
583
+ // Search by name
584
+ info(`Searching for "${target}"...`);
585
+ try {
586
+ const encoded = encodeURIComponent(target);
587
+ const searchData = await apiRequest('GET', `/api/v1/tokenboard/workflows/list?keyword=${encoded}&page=1&page_size=5&sort_by=views`, null, config?.token, apiBase);
588
+
589
+ if (!searchData.list || searchData.list.length === 0) {
590
+ error(`No asset found matching "${target}". Try: tokrepo search ${target}`);
591
+ }
592
+
593
+ // If exact title match, use it directly
594
+ const exact = searchData.list.find(w => w.title.toLowerCase().includes(target.toLowerCase()));
595
+ const chosen = exact || searchData.list[0];
596
+
597
+ uuid = chosen.uuid;
598
+ info(`Found: ${C.bold}${chosen.title}${C.reset}`);
599
+ } catch (e) {
600
+ error(`Search failed: ${e.message}`);
601
+ }
602
+ }
603
+
604
+ // Fetch the asset
605
+ info(`Fetching ${uuid.substring(0, 8)}...`);
606
+
607
+ let workflow, files;
608
+ try {
609
+ const data = await apiRequest('GET', `/api/v1/tokenboard/workflows/detail?uuid=${uuid}`, null, config?.token, apiBase);
610
+ workflow = data.workflow;
611
+ files = data.workflow.files || [];
612
+ } catch (e) {
613
+ error(`Fetch failed: ${e.message}`);
614
+ }
615
+
616
+ log(`\n ${C.bold}${workflow.title}${C.reset}`);
617
+ if (workflow.description) log(` ${C.dim}${workflow.description.substring(0, 100)}${C.reset}`);
618
+
619
+ // Determine asset type from tags
620
+ let assetType = 'other';
621
+ if (workflow.tags && workflow.tags.length > 0) {
622
+ assetType = (workflow.tags[0].slug || workflow.tags[0].name || '').toLowerCase();
623
+ }
624
+
625
+ // Get content — prefer files, fallback to steps
626
+ const contents = [];
627
+
628
+ if (files.length > 0) {
629
+ for (const f of files) {
630
+ if (f.content && !f.content.startsWith('PK')) {
631
+ contents.push({ name: f.name, content: f.content, type: f.file_type || f.fileType || 'other' });
632
+ }
633
+ }
634
+ }
635
+
636
+ if (contents.length === 0 && workflow.steps) {
637
+ for (const step of workflow.steps) {
638
+ const content = step.prompt_template || step.promptTemplate;
639
+ if (content && !content.startsWith('PK')) {
640
+ const name = (step.title || `step-${step.step_order}`).replace(/[/\\?%*:|"<>]/g, '-');
641
+ contents.push({ name, content, type: assetType });
642
+ }
643
+ }
644
+ }
645
+
646
+ if (contents.length === 0) {
647
+ error('No installable content found in this asset.');
648
+ }
649
+
650
+ log('');
651
+
652
+ // Smart install based on asset type
653
+ let installed = 0;
654
+
655
+ for (const item of contents) {
656
+ let destDir = process.cwd();
657
+ let fileName = item.name;
658
+
659
+ // Ensure file has extension
660
+ if (!path.extname(fileName)) fileName += '.md';
661
+
662
+ switch (assetType) {
663
+ case 'skills':
664
+ case 'skill': {
665
+ // Install to .claude/skills/ if it exists, otherwise current dir
666
+ const claudeSkillsDir = path.join(process.cwd(), '.claude', 'skills');
667
+ if (fs.existsSync(path.join(process.cwd(), '.claude'))) {
668
+ if (!fs.existsSync(claudeSkillsDir)) {
669
+ fs.mkdirSync(claudeSkillsDir, { recursive: true });
670
+ }
671
+ destDir = claudeSkillsDir;
672
+ }
673
+ break;
674
+ }
675
+ case 'mcp':
676
+ case 'mcp configs': {
677
+ // Save as mcp config, hint about manual merge
678
+ if (!fileName.endsWith('.json')) fileName = fileName.replace(/\.md$/, '.json');
679
+ break;
680
+ }
681
+ case 'configs':
682
+ case 'config': {
683
+ // Save to project root
684
+ break;
685
+ }
686
+ case 'scripts':
687
+ case 'script': {
688
+ // Save and make executable
689
+ if (!path.extname(fileName) || fileName.endsWith('.md')) {
690
+ // Detect language from content
691
+ if (item.content.startsWith('#!/usr/bin/env python') || item.content.includes('import ')) {
692
+ fileName = fileName.replace(/\.md$/, '.py');
693
+ } else if (item.content.startsWith('#!/bin/bash') || item.content.startsWith('#!/bin/sh')) {
694
+ fileName = fileName.replace(/\.md$/, '.sh');
695
+ }
696
+ }
697
+ break;
698
+ }
699
+ case 'prompts':
700
+ case 'prompt': {
701
+ // Save as markdown
702
+ if (!fileName.endsWith('.md') && !fileName.endsWith('.prompt')) fileName += '.md';
703
+ break;
704
+ }
705
+ }
706
+
707
+ const destPath = path.join(destDir, fileName);
708
+
709
+ // Don't overwrite without warning
710
+ if (fs.existsSync(destPath)) {
711
+ warn(`File exists: ${path.relative(process.cwd(), destPath)} (overwriting)`);
712
+ }
713
+
714
+ fs.writeFileSync(destPath, item.content);
715
+
716
+ // Make scripts executable
717
+ if (assetType === 'script' || assetType === 'scripts') {
718
+ try { fs.chmodSync(destPath, 0o755); } catch {}
719
+ }
720
+
721
+ const relPath = path.relative(process.cwd(), destPath);
722
+ success(`Installed: ${relPath}`);
723
+ installed++;
724
+ }
725
+
726
+ log('');
727
+ success(`${installed} file(s) installed from ${C.bold}${workflow.title}${C.reset}`);
728
+ log(` ${C.dim}Source: https://tokrepo.com/en/workflows/${uuid}${C.reset}\n`);
729
+ }
730
+
516
731
  async function cmdWhoami() {
517
732
  const config = readConfig();
518
733
  if (!config || !config.token) error('Not logged in. Run: tokrepo login');
@@ -635,41 +850,53 @@ async function cmdTags() {
635
850
 
636
851
  function showHelp() {
637
852
  log(`
638
- ${C.bold}tokrepo${C.reset} — Push AI assets to tokrepo.com
853
+ ${C.bold}tokrepo${C.reset} — AI assets for humans and agents. Like GitHub, for AI experience.
639
854
 
640
855
  ${C.bold}QUICK START${C.reset}
641
- ${C.cyan}tokrepo login${C.reset} # one-time: paste your token
642
- ${C.cyan}tokrepo push --public .${C.reset} # push current directory
643
- ${C.cyan}tokrepo push --public README.md script.py${C.reset} # push specific files
856
+ ${C.cyan}tokrepo search cursor rules${C.reset} # find assets
857
+ ${C.cyan}tokrepo install awesome-cursor-rules${C.reset} # install to your project
858
+ ${C.cyan}tokrepo push --public .${C.reset} # share your own assets
644
859
 
645
860
  ${C.bold}USAGE${C.reset}
646
- tokrepo push [files/dirs...] [options]
647
-
648
- ${C.bold}OPTIONS${C.reset}
861
+ tokrepo <command> [args] [options]
862
+
863
+ ${C.bold}DISCOVER & INSTALL${C.reset}
864
+ ${C.cyan}search${C.reset} <query> Search assets by keyword
865
+ ${C.cyan}install${C.reset} <name|uuid> Smart install (auto-detects type & placement)
866
+ ${C.cyan}pull${C.reset} <url|uuid> Download raw asset files
867
+
868
+ ${C.bold}PUBLISH${C.reset}
869
+ ${C.cyan}push${C.reset} [files...] Push files/directory to TokRepo
870
+ ${C.cyan}init${C.reset} Create .tokrepo.json project config
871
+ ${C.cyan}update${C.reset} <uuid> [f] Update existing asset
872
+ ${C.cyan}delete${C.reset} <uuid> Delete an asset
873
+
874
+ ${C.bold}ACCOUNT${C.reset}
875
+ ${C.cyan}login${C.reset} Save API token
876
+ ${C.cyan}list${C.reset} List your published assets
877
+ ${C.cyan}tags${C.reset} List available tags
878
+ ${C.cyan}whoami${C.reset} Show current user
879
+ ${C.cyan}help${C.reset} Show this help
880
+
881
+ ${C.bold}PUSH OPTIONS${C.reset}
649
882
  ${C.cyan}--public${C.reset} Make asset publicly visible (default)
650
883
  ${C.cyan}--private${C.reset} Make asset private
651
884
  ${C.cyan}--title${C.reset} "..." Set title (auto-detected from README or dir name)
652
885
  ${C.cyan}--desc${C.reset} "..." Set description
653
- ${C.cyan}--tag${C.reset} Skills Add tag (repeatable: --tag Skills --tag MCP)
654
- ${C.cyan}-y, --yes${C.reset} Skip confirmation prompts
655
-
656
- ${C.bold}COMMANDS${C.reset}
657
- ${C.cyan}login${C.reset} Save API token
658
- ${C.cyan}push${C.reset} [files...] Push files/directory (default: current dir)
659
- ${C.cyan}init${C.reset} Create .tokrepo.json project config
660
- ${C.cyan}pull${C.reset} <url> Download asset to local files
661
- ${C.cyan}list${C.reset} List your assets
662
- ${C.cyan}update${C.reset} <uuid> [f] Update existing asset
663
- ${C.cyan}delete${C.reset} <uuid> Delete an asset
664
- ${C.cyan}tags${C.reset} List available tags
665
- ${C.cyan}whoami${C.reset} Show current user
666
- ${C.cyan}help${C.reset} Show this help
886
+ ${C.cyan}--tag${C.reset} Skills Add tag (repeatable)
887
+
888
+ ${C.bold}INSTALL BEHAVIOR${C.reset}
889
+ Skills → .claude/skills/ (if .claude/ exists)
890
+ Scripts → current dir (chmod +x)
891
+ Configs → project root
892
+ MCP → current dir (.json)
893
+ Prompts → current dir (.md)
667
894
 
668
895
  ${C.bold}EXAMPLES${C.reset}
669
- tokrepo push --public . # Push all files in current dir
896
+ tokrepo search "mcp server" # Find MCP configs
897
+ tokrepo install ca000374-f5d8-... # Install by UUID
898
+ tokrepo push --public . # Push current directory
670
899
  tokrepo push --public --title "My MCP" . # Push with custom title
671
- tokrepo push --public src/ README.md # Push specific paths
672
- tokrepo push # Uses .tokrepo.json if exists
673
900
 
674
901
  ${C.bold}FILE TYPE AUTO-DETECTION${C.reset}
675
902
  .sh .py .js .ts .mjs .go .rs → script
@@ -693,6 +920,8 @@ async function main() {
693
920
  case 'init': await cmdInit(); break;
694
921
  case 'push': await cmdPush(); break;
695
922
  case 'pull': await cmdPull(); break;
923
+ case 'search': case 'find': await cmdSearch(); break;
924
+ case 'install': case 'i': await cmdInstall(); break;
696
925
  case 'list': await cmdList(); break;
697
926
  case 'update': await cmdUpdate(); break;
698
927
  case 'delete': await cmdDelete(); break;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tokrepo",
3
- "version": "1.1.0",
4
- "description": "Push AI assets to tokrepo.comSkills, Prompts, MCP Configs, Scripts",
3
+ "version": "2.0.0",
4
+ "description": "AI assets for humans and agents search, install, push. Like GitHub, for AI experience.",
5
5
  "bin": {
6
6
  "tokrepo": "bin/tokrepo.js"
7
7
  },