tokrepo 3.7.0 → 3.8.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 +348 -8
  2. package/package.json +1 -1
package/bin/tokrepo.js CHANGED
@@ -25,7 +25,7 @@ const CONFIG_DIR = path.join(os.homedir(), '.tokrepo');
25
25
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
26
26
  const PROJECT_CONFIG = '.tokrepo.json';
27
27
  const DEFAULT_API = 'https://api.tokrepo.com';
28
- const CLI_VERSION = '3.7.0';
28
+ const CLI_VERSION = '3.8.0';
29
29
  const VERSION_CHECK_FILE = path.join(os.homedir(), '.tokrepo', '.version-check');
30
30
  const CODEX_DIR = path.join(os.homedir(), '.codex');
31
31
  const CODEX_SKILLS_DIR = path.join(CODEX_DIR, 'skills');
@@ -302,9 +302,9 @@ function parseArgs(argv) {
302
302
  }
303
303
 
304
304
  const valueFlags = new Set([
305
- 'title', 'desc', 'tag', 'target', 'targets', 'keyword', 'types',
305
+ 'title', 'desc', 'tag', 'target', 'targets', 'keyword', 'query', 'types',
306
306
  'kind', 'install-mode', 'install_mode', 'entrypoint', 'asset-kind', 'asset_kind',
307
- 'version',
307
+ 'version', 'uuid',
308
308
  'policy', 'session',
309
309
  'page', 'page-size', 'page_size', 'sort-by', 'sort_by',
310
310
  'time-window', 'time_window',
@@ -546,13 +546,152 @@ function browserAuthFlow() {
546
546
  });
547
547
  }
548
548
 
549
- async function cmdPush() {
550
- const args = parseArgs(process.argv);
549
+ function inferAssetKindFromPushFiles(files = []) {
550
+ const normalized = files.map(file => ({
551
+ ...file,
552
+ lowerName: String(file.name || '').toLowerCase(),
553
+ content: String(file.content || ''),
554
+ }));
555
+ if (normalized.some(file => /"mcpServers"\s*:/.test(file.content) || /\bmcpServers\s*:/.test(file.content))) return 'mcp_config';
556
+ if (normalized.some(file => file.type === 'script' || /\.(sh|py|js|mjs|ts|rb|go|rs|lua)$/.test(file.lowerName) || /^#!\//.test(file.content))) return 'script';
557
+ if (normalized.some(file => file.lowerName === 'package.json' && /"bin"\s*:/.test(file.content))) return 'cli_tool';
558
+ if (normalized.some(file => file.type === 'skill' || isCodexSkillDocument(file))) return 'skill';
559
+ if (normalized.some(file => file.type === 'prompt')) return 'prompt';
560
+ if (normalized.some(file => file.type === 'config')) return 'config';
561
+ if (normalized.some(file => file.lowerName.endsWith('.md'))) return 'knowledge';
562
+ return 'other';
563
+ }
551
564
 
552
- const config = readConfig();
553
- if (!config || !config.token) {
554
- error(`Not logged in. Run: ${C.cyan}tokrepo login${C.reset}`);
565
+ function analyzePushMetadataQuality(files = [], metadata = {}) {
566
+ const issues = [];
567
+ const add = (severity, code, message, fix = '') => {
568
+ issues.push({ severity, code, message, fix });
569
+ };
570
+
571
+ const normalizedKind = normalizeToolName(metadata.kind || '');
572
+ const inferredKind = inferAssetKindFromPushFiles(files);
573
+ const targetTools = parseCsvList(metadata.targetTools).map(normalizeToolName);
574
+ const installMode = normalizeCodexInstallMode(metadata.installMode);
575
+ const rawInstallMode = String(metadata.installMode || '').trim();
576
+ const entrypoint = String(metadata.entrypoint || '').trim();
577
+ const fileNames = new Set(files.map(file => String(file.name || '').replace(/\\/g, '/')));
578
+ const lowerFileNames = new Set(Array.from(fileNames).map(name => name.toLowerCase()));
579
+ const multiFile = files.length > 1;
580
+ const riskFlags = Array.from(new Set(files.flatMap(file => analyzeInstallRisks(file.name, file.content, file.type))));
581
+ const containsCodex = targetTools.includes('codex');
582
+
583
+ if (!normalizedKind) {
584
+ add('warning', 'missing_asset_kind', `No asset_kind was provided; inferred "${inferredKind}".`, `Recommended: --kind ${inferredKind}`);
585
+ } else if (!['skill', 'prompt', 'knowledge', 'mcp_config', 'script', 'cli_tool', 'config', 'pack', 'other'].includes(normalizedKind)) {
586
+ add('warning', 'unknown_asset_kind', `Unknown asset_kind "${metadata.kind}".`, 'Recommended: use skill, prompt, knowledge, mcp_config, script, cli_tool, config, or pack.');
587
+ }
588
+
589
+ if (targetTools.length === 0) {
590
+ add('warning', 'missing_target_tools', 'No target_tools metadata was provided.', 'Recommended: --target codex or --targets codex,claude_code,gemini_cli.');
591
+ }
592
+
593
+ const invalidTargets = targetTools.filter(tool => !['codex', 'claude_code', 'gemini_cli', 'cursor', 'windsurf', 'opencode'].includes(tool));
594
+ if (invalidTargets.length > 0) {
595
+ add('info', 'unknown_target_tool', `Unknown target tool(s): ${invalidTargets.join(', ')}.`, 'Use stable ids when possible: codex, claude_code, gemini_cli, cursor, windsurf.');
596
+ }
597
+
598
+ if (rawInstallMode && !installMode) {
599
+ add('warning', 'unknown_install_mode', `Unknown install_mode "${metadata.installMode}".`, 'Recommended: single, bundle, split, or stage_only.');
600
+ } else if (!rawInstallMode && (multiFile || containsCodex)) {
601
+ add('warning', 'missing_install_mode', `No install_mode was provided for ${multiFile ? 'a multi-file asset' : 'a Codex-targeted asset'}.`, `Recommended: --install-mode ${multiFile ? 'bundle' : 'single'}.`);
602
+ }
603
+
604
+ if (entrypoint && !fileNames.has(entrypoint) && !lowerFileNames.has(entrypoint.toLowerCase())) {
605
+ add('warning', 'entrypoint_missing', `Entrypoint "${entrypoint}" is not included in the pushed files.`, 'Recommended: set --entrypoint to an included file, usually SKILL.md.');
606
+ }
607
+
608
+ if (!entrypoint && (installMode === 'single' || installMode === 'bundle' || containsCodex)) {
609
+ add('warning', 'missing_entrypoint', 'No entrypoint metadata was provided.', 'Recommended: --entrypoint SKILL.md or the main markdown file.');
610
+ }
611
+
612
+ const effectiveKind = normalizedKind || inferredKind;
613
+ if (containsCodex && ['script', 'cli_tool', 'mcp_config'].includes(effectiveKind) && installMode !== 'stage_only') {
614
+ add('warning', 'high_risk_codex_activation', `${effectiveKind} assets targeted at Codex should stage by default.`, 'Recommended: --install-mode stage_only or publish a markdown skill wrapper.');
615
+ }
616
+ if (riskFlags.includes('executable')) {
617
+ add('warning', 'executes_code_detected', 'Executable code was detected in the asset files.', 'Recommended: declare script/cli_tool or use stage_only for agent installs.');
618
+ }
619
+ if (riskFlags.includes('mcp')) {
620
+ add('warning', 'mcp_config_detected', 'MCP config content was detected.', 'Recommended: --kind mcp_config --install-mode stage_only.');
621
+ }
622
+ if (riskFlags.includes('env')) {
623
+ add('info', 'secret_or_env_mentions', 'Environment variable or secret-like words were mentioned.', 'Check this is documentation only and no real secret is included.');
624
+ }
625
+ if (riskFlags.includes('absolute-path')) {
626
+ add('warning', 'absolute_path_detected', 'Absolute local paths were detected.', 'Recommended: replace machine-specific paths with placeholders.');
627
+ }
628
+
629
+ if (containsCodex && effectiveKind === 'skill') {
630
+ const skillDocs = files.filter(file => isCodexSkillDocument(file));
631
+ if (installMode === 'split' && skillDocs.length !== files.length) {
632
+ add('warning', 'split_requires_skill_docs', 'install_mode=split works best when every markdown file has skill frontmatter.', 'Recommended: use bundle or add name/description frontmatter to each split skill.');
633
+ }
634
+ if (skillDocs.length === 0) {
635
+ add('warning', 'missing_codex_skill_frontmatter', 'No Codex skill frontmatter was found.', 'Recommended: add YAML frontmatter with name and description.');
636
+ }
637
+ }
638
+
639
+ const severityPenalty = issues.reduce((sum, issue) => {
640
+ if (issue.severity === 'warning') return sum + 10;
641
+ return sum + 2;
642
+ }, 0);
643
+ const score = Math.max(0, 100 - severityPenalty);
644
+ const status = issues.some(issue => issue.severity === 'warning') ? 'warn' : 'pass';
645
+
646
+ return {
647
+ score,
648
+ status,
649
+ inferredAssetKind: inferredKind,
650
+ assetKind: normalizedKind || '',
651
+ targetTools,
652
+ installMode: installMode || '',
653
+ entrypoint,
654
+ riskFlags,
655
+ issueCount: issues.length,
656
+ issues,
657
+ };
658
+ }
659
+
660
+ function formatMetadataQualityLabel(report) {
661
+ const color = report.status === 'pass' ? C.green : C.yellow;
662
+ return `${color}${report.status}${C.reset} ${C.bold}${report.score}/100${C.reset}`;
663
+ }
664
+
665
+ function printMetadataQualityReport(report, opts = {}) {
666
+ const compact = Boolean(opts.compact);
667
+ if (!compact) {
668
+ log(`\n${C.bold}Agent metadata quality${C.reset}`);
669
+ log(` Status: ${formatMetadataQualityLabel(report)}`);
670
+ log(` Inferred kind: ${report.inferredAssetKind}`);
671
+ if (report.targetTools.length) log(` Targets: ${report.targetTools.join(', ')}`);
672
+ if (report.installMode) log(` Install mode: ${report.installMode}`);
673
+ if (report.riskFlags.length) log(` Risk flags: ${report.riskFlags.join(', ')}`);
674
+ } else {
675
+ log(`${C.bold}Agent metadata quality suggestions:${C.reset}`);
676
+ }
677
+
678
+ if (report.issues.length === 0) {
679
+ if (!compact) success('Metadata is agent-ready.');
680
+ return;
555
681
  }
682
+ for (const issue of report.issues.slice(0, compact ? 8 : report.issues.length)) {
683
+ const color = issue.severity === 'warning' ? C.yellow : C.dim;
684
+ log(` ${color}${issue.severity.toUpperCase()}${C.reset} ${issue.code}: ${issue.message}`);
685
+ if (issue.fix) log(` ${C.dim}${issue.fix}${C.reset}`);
686
+ }
687
+ if (compact && report.issues.length > 8) {
688
+ log(` ${C.dim}...and ${report.issues.length - 8} more suggestion(s). Run --metadata-report for full detail.${C.reset}`);
689
+ }
690
+ if (!compact) log('');
691
+ }
692
+
693
+ async function cmdPush() {
694
+ const args = parseArgs(process.argv);
556
695
 
557
696
  const projectConfig = readProjectConfig();
558
697
  const baseDir = process.cwd();
@@ -623,6 +762,31 @@ async function cmdPush() {
623
762
  error('No readable text files found to push.');
624
763
  }
625
764
 
765
+ const metadataQuality = analyzePushMetadataQuality(pushFiles, {
766
+ kind,
767
+ targetTools,
768
+ installMode,
769
+ entrypoint,
770
+ title,
771
+ description,
772
+ });
773
+
774
+ if (args.flags.metadata_report || args.flags.metadataReport) {
775
+ if (args.flags.json) {
776
+ outputJson({ schemaVersion: 1, metadataQuality, files: pushFiles.map(file => ({ name: file.name, type: file.type, bytes: Buffer.byteLength(file.content || '') })) });
777
+ } else {
778
+ printMetadataQualityReport(metadataQuality);
779
+ }
780
+ return;
781
+ }
782
+
783
+ // Public registry policy: quality gates advise by default and never block user uploads.
784
+ // TokRepo-owned CI can opt into strict mode with TOKREPO_METADATA_STRICT=1.
785
+ if (process.env.TOKREPO_METADATA_STRICT === '1' && metadataQuality.issues.length > 0 && !args.flags.force) {
786
+ if (!args.flags.json) printMetadataQualityReport(metadataQuality);
787
+ error(`Internal metadata quality gate failed. Fix the issues or re-run with --force to push anyway.`);
788
+ }
789
+
626
790
  // Show summary
627
791
  log(`\n${C.bold}tokrepo push${C.reset}\n`);
628
792
  log(` ${C.bold}Title:${C.reset} ${title}`);
@@ -640,6 +804,7 @@ async function cmdPush() {
640
804
  if (metadataSummary.length > 0) {
641
805
  log(` ${C.bold}Agent meta:${C.reset} ${metadataSummary.join(' · ')}`);
642
806
  }
807
+ log(` ${C.bold}Agent quality:${C.reset} ${formatMetadataQualityLabel(metadataQuality)}`);
643
808
  log('');
644
809
 
645
810
  for (const f of pushFiles) {
@@ -650,9 +815,18 @@ async function cmdPush() {
650
815
 
651
816
  const totalChars = pushFiles.reduce((sum, f) => sum + f.content.length, 0);
652
817
 
818
+ if (metadataQuality.issues.length > 0) {
819
+ printMetadataQualityReport(metadataQuality, { compact: true });
820
+ }
821
+
653
822
  // Push
654
823
  info('Pushing...');
655
824
 
825
+ const config = readConfig();
826
+ if (!config || !config.token) {
827
+ error(`Not logged in. Run: ${C.cyan}tokrepo login${C.reset}`);
828
+ }
829
+
656
830
  try {
657
831
  const data = await apiRequest('POST', '/api/v1/tokenboard/push/upsert', {
658
832
  title,
@@ -2793,6 +2967,145 @@ async function fetchServerCodexInstallPlan(uuid, config, apiBase) {
2793
2967
  }
2794
2968
  }
2795
2969
 
2970
+ function runSelfCliJson(cliArgs, opts = {}) {
2971
+ const childProcess = require('child_process');
2972
+ const stdout = childProcess.execFileSync(process.execPath, [__filename, ...cliArgs], {
2973
+ env: { ...process.env, ...(opts.env || {}), TOKREPO_NONINTERACTIVE: '1' },
2974
+ cwd: opts.cwd || process.cwd(),
2975
+ encoding: 'utf8',
2976
+ maxBuffer: 50 * 1024 * 1024,
2977
+ });
2978
+ return JSON.parse(stdout);
2979
+ }
2980
+
2981
+ function makeEvalResult(name, ok, details = {}) {
2982
+ return {
2983
+ name,
2984
+ ok: Boolean(ok),
2985
+ status: ok ? 'pass' : 'fail',
2986
+ ...details,
2987
+ };
2988
+ }
2989
+
2990
+ function createTempDir(prefix) {
2991
+ return fs.mkdtempSync(path.join(os.tmpdir(), `${prefix}-`));
2992
+ }
2993
+
2994
+ async function cmdEvalAgent() {
2995
+ const args = parseArgs(process.argv);
2996
+ const json = Boolean(args.flags.json);
2997
+ const sampleUuid = args.flags.uuid || '91aeb22d-eff0-4310-abc6-811d2394b420';
2998
+ const query = args.flags.keyword || args.flags.query || 'video';
2999
+ const keepTemp = Boolean(args.flags.keep_temp || args.flags.keepTemp);
3000
+ const startedAt = new Date().toISOString();
3001
+ const results = [];
3002
+ const tempRoots = [];
3003
+
3004
+ if (!json) log(`\n${C.bold}tokrepo eval-agent${C.reset}\n`);
3005
+
3006
+ const runScenario = async (name, fn) => {
3007
+ const start = Date.now();
3008
+ try {
3009
+ const details = await fn();
3010
+ const result = makeEvalResult(name, true, { durationMs: Date.now() - start, ...details });
3011
+ results.push(result);
3012
+ if (!json) success(`${name} (${result.durationMs}ms)`);
3013
+ } catch (e) {
3014
+ const result = makeEvalResult(name, false, { durationMs: Date.now() - start, error: e.message });
3015
+ results.push(result);
3016
+ if (!json) warn(`${name}: ${e.message}`);
3017
+ }
3018
+ };
3019
+
3020
+ await runScenario('search_filters_codex_allow_skill', async () => {
3021
+ const data = runSelfCliJson(['search', query, '--target', 'codex', '--kind', 'skill', '--policy', 'allow', '--json', '--page-size', '10']);
3022
+ if (!data.count || !Array.isArray(data.list)) throw new Error('filtered search returned no list');
3023
+ const bad = data.list.find(item => item.policyDecision?.decision && item.policyDecision.decision !== 'allow');
3024
+ if (bad) throw new Error(`search returned non-allow asset ${bad.uuid}`);
3025
+ return { count: data.count, firstUuid: data.list[0]?.uuid, firstTitle: data.list[0]?.title };
3026
+ });
3027
+
3028
+ await runScenario('install_plan_contract', async () => {
3029
+ const plan = runSelfCliJson(['plan', sampleUuid, '--target', 'codex']);
3030
+ if (plan.schemaVersion !== 2) throw new Error(`expected schemaVersion 2, got ${plan.schemaVersion}`);
3031
+ if (!plan.policyDecision?.decision) throw new Error('missing policyDecision');
3032
+ if (!Array.isArray(plan.actions) || plan.actions.length === 0) throw new Error('missing actions');
3033
+ if (!Array.isArray(plan.rollback) || plan.rollback.length === 0) throw new Error('missing rollback');
3034
+ if (!Array.isArray(plan.postVerify) || plan.postVerify.length === 0) throw new Error('missing postVerify');
3035
+ return {
3036
+ sourceOfTruth: plan.sourceOfTruth,
3037
+ concretePlanSource: plan.concretePlanSource,
3038
+ policy: plan.policyDecision.decision,
3039
+ actions: plan.actions.length,
3040
+ };
3041
+ });
3042
+
3043
+ await runScenario('metadata_quality_report_non_blocking', async () => {
3044
+ const tmp = createTempDir('tokrepo-eval-quality');
3045
+ tempRoots.push(tmp);
3046
+ const skillPath = path.join(tmp, 'SKILL.md');
3047
+ fs.writeFileSync(skillPath, `---\nname: eval-agent-sample\ndescription: \"Sample skill used by tokrepo eval-agent.\"\n---\n\n# Eval Agent Sample\n\nUse this to test metadata quality reporting.\n`);
3048
+ const report = runSelfCliJson(['push', skillPath, '--metadata-report', '--json', '--kind', 'skill', '--target', 'codex', '--install-mode', 'single', '--entrypoint', 'SKILL.md']);
3049
+ if (!report.metadataQuality) throw new Error('missing metadataQuality');
3050
+ if (report.metadataQuality.status !== 'pass') throw new Error(`expected pass, got ${report.metadataQuality.status}`);
3051
+ return { score: report.metadataQuality.score, status: report.metadataQuality.status };
3052
+ });
3053
+
3054
+ await runScenario('codex_install_verify_and_rollback', async () => {
3055
+ const tmpHome = createTempDir('tokrepo-eval-home');
3056
+ tempRoots.push(tmpHome);
3057
+ const env = { HOME: tmpHome };
3058
+ const install = runSelfCliJson(['install', sampleUuid, '--target', 'codex', '--yes', '--json'], { env });
3059
+ if (!install.sessionId) throw new Error('install did not create sessionId');
3060
+ if (!install.verification?.ok) throw new Error('install verification failed');
3061
+ if (!install.installedFiles?.length) throw new Error('no files installed');
3062
+ const installed = runSelfCliJson(['installed', '--target', 'codex', '--json'], { env });
3063
+ if (installed.count < 1) throw new Error('installed manifest did not record asset');
3064
+ const rollback = runSelfCliJson(['rollback', '--last', '--target', 'codex', '--json'], { env });
3065
+ if (!rollback.removedFiles?.length) throw new Error('rollback removed no files');
3066
+ const after = runSelfCliJson(['installed', '--target', 'codex', '--json'], { env });
3067
+ if (after.count !== 0) throw new Error(`manifest still has ${after.count} install(s) after rollback`);
3068
+ return {
3069
+ installedFiles: install.installedFiles.length,
3070
+ sessionId: install.sessionId,
3071
+ removedFiles: rollback.removedFiles.length,
3072
+ };
3073
+ });
3074
+
3075
+ if (!keepTemp) {
3076
+ for (const dir of tempRoots) {
3077
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
3078
+ }
3079
+ }
3080
+
3081
+ const failed = results.filter(result => !result.ok);
3082
+ const summary = {
3083
+ schemaVersion: 1,
3084
+ startedAt,
3085
+ finishedAt: new Date().toISOString(),
3086
+ cliVersion: CLI_VERSION,
3087
+ targetTool: 'codex',
3088
+ sampleUuid,
3089
+ query,
3090
+ status: failed.length === 0 ? 'pass' : 'fail',
3091
+ passed: results.length - failed.length,
3092
+ failed: failed.length,
3093
+ count: results.length,
3094
+ tempRoots: keepTemp ? tempRoots : [],
3095
+ results,
3096
+ };
3097
+
3098
+ if (json) {
3099
+ outputJson(summary);
3100
+ } else {
3101
+ log('');
3102
+ if (summary.status === 'pass') success(`Agent eval passed: ${summary.passed}/${summary.count}`);
3103
+ else warn(`Agent eval failed: ${summary.failed}/${summary.count}`);
3104
+ }
3105
+
3106
+ if (failed.length > 0) process.exitCode = 1;
3107
+ }
3108
+
2796
3109
  async function cmdSyncInstalled() {
2797
3110
  const args = parseArgs(process.argv);
2798
3111
  const targetTool = validateInstallTarget(args.flags.target || 'codex');
@@ -3439,6 +3752,7 @@ ${C.bold}DISCOVER & INSTALL${C.reset}
3439
3752
  ${C.cyan}sync-installed${C.reset} Update installed Codex assets from manifest
3440
3753
  ${C.cyan}uninstall${C.reset} <uuid> Remove a managed Codex install
3441
3754
  ${C.cyan}rollback${C.reset} --last Roll back the latest Codex install session
3755
+ ${C.cyan}eval-agent${C.reset} Run agent-native contract and lifecycle evals
3442
3756
 
3443
3757
  ${C.bold}PUBLISH${C.reset}
3444
3758
  ${C.cyan}push${C.reset} [files...] Push files/directory (idempotent upsert)
@@ -3464,6 +3778,7 @@ ${C.bold}PUSH OPTIONS${C.reset}
3464
3778
  ${C.cyan}--kind${C.reset} skill Set agent asset_kind
3465
3779
  ${C.cyan}--target${C.reset} codex Add target tool metadata on push
3466
3780
  ${C.cyan}--install-mode${C.reset} bundle Set install_mode metadata
3781
+ ${C.cyan}--metadata-report${C.reset} Print agent metadata quality suggestions without pushing
3467
3782
 
3468
3783
  ${C.bold}INSTALL BEHAVIOR${C.reset}
3469
3784
  Skills → .claude/skills/ (if .claude/ exists)
@@ -3489,7 +3804,9 @@ ${C.bold}EXAMPLES${C.reset}
3489
3804
  tokrepo sync-installed --target codex --dry-run
3490
3805
  tokrepo uninstall 91aeb22d --target codex --dry-run
3491
3806
  tokrepo rollback --last --target codex --dry-run
3807
+ tokrepo eval-agent --json
3492
3808
  tokrepo push --private my-rules.md # Save one file privately
3809
+ tokrepo push . --metadata-report --json # Check agent metadata without uploading
3493
3810
  tokrepo push . --kind skill --target codex --install-mode bundle
3494
3811
  tokrepo push --public skill.md # Share one file publicly
3495
3812
  tokrepo push --private . # Push current dir as private
@@ -3666,6 +3983,25 @@ EXAMPLES
3666
3983
  `);
3667
3984
  }
3668
3985
 
3986
+ function showEvalAgentHelp() {
3987
+ log(`
3988
+ ${C.bold}tokrepo eval-agent${C.reset}
3989
+
3990
+ USAGE
3991
+ tokrepo eval-agent [--json] [--uuid <asset-uuid>] [--keyword video] [--keep-temp]
3992
+
3993
+ BEHAVIOR
3994
+ Runs agent-native smoke evals against search filters, install-plan contracts,
3995
+ metadata quality reporting, Codex install verification, manifest state, and rollback.
3996
+ Lifecycle tests use a temporary HOME and do not touch your real ~/.codex.
3997
+
3998
+ EXAMPLES
3999
+ tokrepo eval-agent
4000
+ tokrepo eval-agent --json
4001
+ tokrepo eval-agent --uuid 91aeb22d-eff0-4310-abc6-811d2394b420 --keyword video --json
4002
+ `);
4003
+ }
4004
+
3669
4005
  function showCommandHelp(command) {
3670
4006
  switch (command) {
3671
4007
  case 'search':
@@ -3693,6 +4029,9 @@ function showCommandHelp(command) {
3693
4029
  showUninstallHelp(); break;
3694
4030
  case 'rollback':
3695
4031
  showRollbackHelp(); break;
4032
+ case 'eval-agent':
4033
+ case 'eval':
4034
+ showEvalAgentHelp(); break;
3696
4035
  default:
3697
4036
  showHelp(); break;
3698
4037
  }
@@ -3725,6 +4064,7 @@ async function main() {
3725
4064
  case 'installed': await cmdInstalled(); break;
3726
4065
  case 'uninstall': case 'remove': case 'rm': await cmdUninstall(); break;
3727
4066
  case 'rollback': await cmdRollback(); break;
4067
+ case 'eval-agent': case 'eval': await cmdEvalAgent(); break;
3728
4068
  case 'outdated': await cmdOutdated(); break;
3729
4069
  case 'sync-installed': case 'sync': await cmdSyncInstalled(); break;
3730
4070
  case 'tags': await cmdTags(); break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokrepo",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
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"