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.
- package/bin/tokrepo.js +254 -25
- 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/
|
|
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} —
|
|
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
|
|
642
|
-
${C.cyan}tokrepo
|
|
643
|
-
${C.cyan}tokrepo push --public
|
|
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
|
|
647
|
-
|
|
648
|
-
${C.bold}
|
|
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
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
|
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": "
|
|
4
|
-
"description": "
|
|
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
|
},
|