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.
- package/bin/tokrepo.js +348 -8
- 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.
|
|
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
|
-
|
|
550
|
-
const
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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;
|