tokrepo 3.5.0 → 3.6.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 +219 -7
- 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.6.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');
|
|
@@ -1278,8 +1278,161 @@ function buildCodexInstallPlan(workflow, contents, opts = {}) {
|
|
|
1278
1278
|
return plan;
|
|
1279
1279
|
}
|
|
1280
1280
|
|
|
1281
|
+
function metadataValue(metadata, snakeName, camelName, fallback) {
|
|
1282
|
+
if (!metadata) return fallback;
|
|
1283
|
+
if (metadata[snakeName] !== undefined) return metadata[snakeName];
|
|
1284
|
+
if (metadata[camelName] !== undefined) return metadata[camelName];
|
|
1285
|
+
return fallback;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function normalizeToolName(value) {
|
|
1289
|
+
return String(value || '').trim().toLowerCase().replace(/-/g, '_');
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function riskProfileFromFlags(flags = []) {
|
|
1293
|
+
const set = new Set(flags || []);
|
|
1294
|
+
return {
|
|
1295
|
+
executes_code: set.has('executable'),
|
|
1296
|
+
modifies_global_config: set.has('mcp'),
|
|
1297
|
+
requires_secrets: set.has('env') ? ['ENV'] : [],
|
|
1298
|
+
uses_absolute_paths: set.has('absolute-path'),
|
|
1299
|
+
network_access: set.has('network'),
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function mergedPlanRiskProfile(plan) {
|
|
1304
|
+
const metadata = plan.agentMetadata || {};
|
|
1305
|
+
const rp = metadataValue(metadata, 'risk_profile', 'riskProfile', {}) || {};
|
|
1306
|
+
const flags = new Set(plan.risks || []);
|
|
1307
|
+
return {
|
|
1308
|
+
executes_code: Boolean(rp.executes_code || rp.executesCode || flags.has('executable')),
|
|
1309
|
+
modifies_global_config: Boolean(rp.modifies_global_config || rp.modifiesGlobalConfig || flags.has('mcp')),
|
|
1310
|
+
requires_secrets: rp.requires_secrets || rp.requiresSecrets || (flags.has('env') ? ['ENV'] : []),
|
|
1311
|
+
uses_absolute_paths: Boolean(rp.uses_absolute_paths || rp.usesAbsolutePaths || flags.has('absolute-path')),
|
|
1312
|
+
network_access: Boolean(rp.network_access || rp.networkAccess || flags.has('network')),
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function decideCodexPolicy(plan) {
|
|
1317
|
+
const metadata = plan.agentMetadata || {};
|
|
1318
|
+
const targetTools = metadataValue(metadata, 'target_tools', 'targetTools', []) || [];
|
|
1319
|
+
const assetKind = normalizeToolName(metadataValue(metadata, 'asset_kind', 'assetKind', ''));
|
|
1320
|
+
const risk = mergedPlanRiskProfile(plan);
|
|
1321
|
+
let decision = 'allow';
|
|
1322
|
+
const reasons = [];
|
|
1323
|
+
const raise = (next) => {
|
|
1324
|
+
const rank = { allow: 0, confirm: 1, stage_only: 2, deny: 3 };
|
|
1325
|
+
if ((rank[next] || 0) > (rank[decision] || 0)) decision = next;
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
if (targetTools.length && !targetTools.map(normalizeToolName).includes('codex')) {
|
|
1329
|
+
raise('confirm');
|
|
1330
|
+
reasons.push('metadata target_tools does not include codex');
|
|
1331
|
+
}
|
|
1332
|
+
if (['script', 'cli_tool', 'mcp_config'].includes(assetKind)) {
|
|
1333
|
+
raise('stage_only');
|
|
1334
|
+
reasons.push(`asset_kind ${assetKind} is not activated directly for Codex`);
|
|
1335
|
+
}
|
|
1336
|
+
if (plan.installMode === 'stage_only') {
|
|
1337
|
+
raise('stage_only');
|
|
1338
|
+
reasons.push('install_mode is stage_only');
|
|
1339
|
+
}
|
|
1340
|
+
if (risk.executes_code) {
|
|
1341
|
+
raise('stage_only');
|
|
1342
|
+
reasons.push('risk_profile.executes_code is true');
|
|
1343
|
+
}
|
|
1344
|
+
if (risk.modifies_global_config) {
|
|
1345
|
+
raise('stage_only');
|
|
1346
|
+
reasons.push('risk_profile.modifies_global_config is true');
|
|
1347
|
+
}
|
|
1348
|
+
if ((risk.requires_secrets || []).length) {
|
|
1349
|
+
raise('stage_only');
|
|
1350
|
+
reasons.push('risk_profile.requires_secrets is not empty');
|
|
1351
|
+
}
|
|
1352
|
+
if (risk.uses_absolute_paths) {
|
|
1353
|
+
raise('confirm');
|
|
1354
|
+
reasons.push('risk_profile.uses_absolute_paths is true');
|
|
1355
|
+
}
|
|
1356
|
+
if (risk.network_access) {
|
|
1357
|
+
raise('confirm');
|
|
1358
|
+
reasons.push('risk_profile.network_access is true');
|
|
1359
|
+
}
|
|
1360
|
+
if (reasons.length === 0) reasons.push('safe markdown-only Codex install');
|
|
1361
|
+
|
|
1362
|
+
return {
|
|
1363
|
+
decision,
|
|
1364
|
+
requiresConfirmation: decision === 'confirm',
|
|
1365
|
+
reasons: Array.from(new Set(reasons)),
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function buildPublicPlanActions(plan) {
|
|
1370
|
+
const stage = plan.installMode === 'stage_only';
|
|
1371
|
+
return plan.files.map(file => ({
|
|
1372
|
+
type: stage ? 'stage_file' : 'write_file',
|
|
1373
|
+
path: file.path,
|
|
1374
|
+
sourceName: file.sourceName,
|
|
1375
|
+
sha256: file.sha256,
|
|
1376
|
+
bytes: file.bytes,
|
|
1377
|
+
ifExists: 'overwrite',
|
|
1378
|
+
entrypoint: path.basename(file.path).toLowerCase() === 'skill.md',
|
|
1379
|
+
risk: riskProfileFromFlags(file.riskFlags || []),
|
|
1380
|
+
}));
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function buildPublicPlanPreconditions(plan, policyDecision) {
|
|
1384
|
+
const metadata = plan.agentMetadata || {};
|
|
1385
|
+
const targetTools = metadataValue(metadata, 'target_tools', 'targetTools', []) || [];
|
|
1386
|
+
const out = [
|
|
1387
|
+
{ type: 'target_supported', status: 'pass', message: 'codex install target is supported' },
|
|
1388
|
+
{ type: 'install_root', status: 'pass', message: '~/.codex/skills for activated skills; ~/.codex/tokrepo/staged for staged assets' },
|
|
1389
|
+
];
|
|
1390
|
+
if (!targetTools.length || targetTools.map(normalizeToolName).includes('codex')) {
|
|
1391
|
+
out.push({ type: 'target_tool_metadata', status: 'pass', message: 'metadata allows codex' });
|
|
1392
|
+
} else {
|
|
1393
|
+
out.push({ type: 'target_tool_metadata', status: 'warn', message: 'metadata target_tools does not include codex' });
|
|
1394
|
+
}
|
|
1395
|
+
out.push({
|
|
1396
|
+
type: 'content_hash',
|
|
1397
|
+
status: plan.contentHash ? 'pass' : 'warn',
|
|
1398
|
+
message: plan.contentHash ? 'asset metadata includes content_hash' : 'asset metadata does not include content_hash',
|
|
1399
|
+
});
|
|
1400
|
+
const policyStatus = policyDecision.decision === 'deny' ? 'block'
|
|
1401
|
+
: policyDecision.decision === 'allow' ? 'pass'
|
|
1402
|
+
: 'warn';
|
|
1403
|
+
out.push({ type: 'policy_decision', status: policyStatus, message: `${policyDecision.decision} for ${plan.uuid}` });
|
|
1404
|
+
return out;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
function buildPublicPlanRollback(plan) {
|
|
1408
|
+
const seen = new Set();
|
|
1409
|
+
const rollback = [];
|
|
1410
|
+
for (const file of plan.files) {
|
|
1411
|
+
if (!file.path || seen.has(file.path)) continue;
|
|
1412
|
+
seen.add(file.path);
|
|
1413
|
+
rollback.push({ type: 'remove_file', path: file.path });
|
|
1414
|
+
}
|
|
1415
|
+
return rollback;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
function buildPublicPlanPostVerify(plan) {
|
|
1419
|
+
const metadata = plan.agentMetadata || {};
|
|
1420
|
+
const verification = metadataValue(metadata, 'verification', 'verification', {}) || {};
|
|
1421
|
+
const out = plan.files.map(file => ({ type: 'file_sha256', path: file.path, sha256: file.sha256 }));
|
|
1422
|
+
for (const expected of (verification.expected_files || verification.expectedFiles || [])) {
|
|
1423
|
+
out.push({ type: 'expected_file', path: expected });
|
|
1424
|
+
}
|
|
1425
|
+
for (const command of (verification.commands || [])) {
|
|
1426
|
+
out.push({ type: 'command', command });
|
|
1427
|
+
}
|
|
1428
|
+
return out;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1281
1431
|
function publicInstallPlan(plan) {
|
|
1432
|
+
const policyDecision = decideCodexPolicy(plan);
|
|
1433
|
+
const actions = buildPublicPlanActions(plan);
|
|
1282
1434
|
return {
|
|
1435
|
+
schemaVersion: 2,
|
|
1283
1436
|
uuid: plan.uuid,
|
|
1284
1437
|
title: plan.title,
|
|
1285
1438
|
sourceUrl: plan.sourceUrl,
|
|
@@ -1288,7 +1441,12 @@ function publicInstallPlan(plan) {
|
|
|
1288
1441
|
manifestPath: plan.manifestPath,
|
|
1289
1442
|
baseDir: plan.baseDir,
|
|
1290
1443
|
risks: plan.risks,
|
|
1291
|
-
|
|
1444
|
+
preconditions: buildPublicPlanPreconditions(plan, policyDecision),
|
|
1445
|
+
actions,
|
|
1446
|
+
policyDecision,
|
|
1447
|
+
requiresConfirmation: policyDecision.requiresConfirmation,
|
|
1448
|
+
rollback: buildPublicPlanRollback(plan),
|
|
1449
|
+
postVerify: buildPublicPlanPostVerify(plan),
|
|
1292
1450
|
contentHash: plan.contentHash || '',
|
|
1293
1451
|
agentMetadata: plan.agentMetadata || {},
|
|
1294
1452
|
files: plan.files.map(file => ({
|
|
@@ -1303,7 +1461,8 @@ function publicInstallPlan(plan) {
|
|
|
1303
1461
|
}
|
|
1304
1462
|
|
|
1305
1463
|
function hasCodexInstallRisks(plan) {
|
|
1306
|
-
|
|
1464
|
+
const decision = decideCodexPolicy(plan).decision;
|
|
1465
|
+
return decision === 'confirm' || decision === 'stage_only' || decision === 'deny';
|
|
1307
1466
|
}
|
|
1308
1467
|
|
|
1309
1468
|
function formatRiskLine(file) {
|
|
@@ -1312,15 +1471,19 @@ function formatRiskLine(file) {
|
|
|
1312
1471
|
}
|
|
1313
1472
|
|
|
1314
1473
|
async function confirmCodexInstallRisks(plan, opts = {}) {
|
|
1474
|
+
const policy = decideCodexPolicy(plan);
|
|
1475
|
+
if (policy.decision === 'deny') {
|
|
1476
|
+
throw new Error(`Install policy denied this asset: ${policy.reasons.join('; ')}`);
|
|
1477
|
+
}
|
|
1315
1478
|
if (plan.installMode === 'stage_only') return;
|
|
1316
|
-
if (opts.dryRun || opts.stage ||
|
|
1479
|
+
if (opts.dryRun || opts.stage || policy.decision === 'allow') return;
|
|
1317
1480
|
if (opts.approveMcp || opts.approve_mcp || opts.yes) return;
|
|
1318
1481
|
|
|
1319
1482
|
if (opts.json || opts.throwOnError || process.env.TOKREPO_NONINTERACTIVE === '1') {
|
|
1320
|
-
throw new Error(`Install
|
|
1483
|
+
throw new Error(`Install policy is ${policy.decision}: ${policy.reasons.join('; ')}. Re-run with --dry-run to inspect, --stage to stage the plan, or --approve-mcp to approve writing the Codex skill bundle.`);
|
|
1321
1484
|
}
|
|
1322
1485
|
|
|
1323
|
-
warn(`
|
|
1486
|
+
warn(`Install policy is ${policy.decision}: ${policy.reasons.join('; ')}`);
|
|
1324
1487
|
log(` ${C.dim}TokRepo will only write files under ${CODEX_SKILLS_DIR}; it will not merge MCP configs, modify PATH, or execute scripts.${C.reset}`);
|
|
1325
1488
|
const riskyFiles = plan.files
|
|
1326
1489
|
.map(formatRiskLine)
|
|
@@ -1488,6 +1651,33 @@ async function cmdInstall() {
|
|
|
1488
1651
|
}
|
|
1489
1652
|
}
|
|
1490
1653
|
|
|
1654
|
+
async function cmdPlan() {
|
|
1655
|
+
const args = parseArgs(process.argv);
|
|
1656
|
+
const target = args.positional[0];
|
|
1657
|
+
if (!target) {
|
|
1658
|
+
showPlanHelp();
|
|
1659
|
+
process.exit(1);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
const config = readConfig();
|
|
1663
|
+
const apiBase = config?.api || DEFAULT_API;
|
|
1664
|
+
const targetTool = validateInstallTarget(args.flags.target || 'codex');
|
|
1665
|
+
if (targetTool !== 'codex') {
|
|
1666
|
+
error(`plan currently supports --target codex only`);
|
|
1667
|
+
}
|
|
1668
|
+
const result = await installOneAsset(target, config, apiBase, {
|
|
1669
|
+
targetTool,
|
|
1670
|
+
dryRun: true,
|
|
1671
|
+
stage: Boolean(args.flags.stage),
|
|
1672
|
+
installMode: args.flags.installMode,
|
|
1673
|
+
json: true,
|
|
1674
|
+
manifest: true,
|
|
1675
|
+
throwOnError: true,
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
outputJson(result.plan);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1491
1681
|
// Install all assets in a theme pack — sequentially, continue past per-item errors
|
|
1492
1682
|
async function installPack(slug, config, apiBase, opts) {
|
|
1493
1683
|
info(`Fetching pack ${C.bold}${slug}${C.reset}...`);
|
|
@@ -2546,6 +2736,7 @@ ${C.bold}USAGE${C.reset}
|
|
|
2546
2736
|
${C.bold}DISCOVER & INSTALL${C.reset}
|
|
2547
2737
|
${C.cyan}search${C.reset} <query> Search assets by keyword
|
|
2548
2738
|
${C.cyan}detail${C.reset} <name|uuid> Show full asset metadata
|
|
2739
|
+
${C.cyan}plan${C.reset} <name|uuid> Print agent-native Codex install plan
|
|
2549
2740
|
${C.cyan}install${C.reset} <name|uuid> Smart install (auto-detects type & placement)
|
|
2550
2741
|
${C.cyan}pull${C.reset} <url|uuid|@u/n> Download raw asset files
|
|
2551
2742
|
${C.cyan}clone${C.reset} @username Clone all assets from a user
|
|
@@ -2591,6 +2782,7 @@ ${C.bold}EXAMPLES${C.reset}
|
|
|
2591
2782
|
tokrepo search "mcp server" # Find MCP configs
|
|
2592
2783
|
tokrepo search video --json # Machine-readable search
|
|
2593
2784
|
tokrepo detail ca000374-f5d8-... --json # Machine-readable detail
|
|
2785
|
+
tokrepo plan 91aeb22d-eff0-4310-... # Install plan v2 for agents
|
|
2594
2786
|
tokrepo install ca000374-f5d8-... # Install by UUID
|
|
2595
2787
|
tokrepo install ca000374-f5d8-... --target codex
|
|
2596
2788
|
tokrepo install c4b18aeb --target gemini # Install for Gemini CLI
|
|
@@ -2670,6 +2862,23 @@ EXAMPLES
|
|
|
2670
2862
|
`);
|
|
2671
2863
|
}
|
|
2672
2864
|
|
|
2865
|
+
function showPlanHelp() {
|
|
2866
|
+
log(`
|
|
2867
|
+
${C.bold}tokrepo plan${C.reset}
|
|
2868
|
+
|
|
2869
|
+
USAGE
|
|
2870
|
+
tokrepo plan <uuid|url|name> [--target codex] [--stage]
|
|
2871
|
+
|
|
2872
|
+
OUTPUT
|
|
2873
|
+
Machine-readable install plan v2 with preconditions, actions, policyDecision,
|
|
2874
|
+
rollback, postVerify, risk metadata, and destination file hashes.
|
|
2875
|
+
|
|
2876
|
+
EXAMPLES
|
|
2877
|
+
tokrepo plan 91aeb22d-eff0-4310-abc6-811d2394b420
|
|
2878
|
+
tokrepo plan https://tokrepo.com/en/workflows/91aeb22d-eff0-4310-abc6-811d2394b420
|
|
2879
|
+
`);
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2673
2882
|
function showListHelp() {
|
|
2674
2883
|
log(`
|
|
2675
2884
|
${C.bold}tokrepo list${C.reset}
|
|
@@ -2729,6 +2938,8 @@ function showCommandHelp(command) {
|
|
|
2729
2938
|
showSearchHelp(); break;
|
|
2730
2939
|
case 'detail':
|
|
2731
2940
|
showDetailHelp(); break;
|
|
2941
|
+
case 'plan':
|
|
2942
|
+
showPlanHelp(); break;
|
|
2732
2943
|
case 'install':
|
|
2733
2944
|
case 'i':
|
|
2734
2945
|
showInstallHelp(); break;
|
|
@@ -2764,6 +2975,7 @@ async function main() {
|
|
|
2764
2975
|
case 'pull': await cmdPull(); break;
|
|
2765
2976
|
case 'search': case 'find': await cmdSearch(); break;
|
|
2766
2977
|
case 'detail': await cmdDetail(); break;
|
|
2978
|
+
case 'plan': await cmdPlan(); break;
|
|
2767
2979
|
case 'install': case 'i': await cmdInstall(); break;
|
|
2768
2980
|
case 'list': await cmdList(); break;
|
|
2769
2981
|
case 'update': await cmdUpdate(); break;
|
|
@@ -2784,7 +2996,7 @@ async function main() {
|
|
|
2784
2996
|
}
|
|
2785
2997
|
|
|
2786
2998
|
// Non-blocking update check after command completes
|
|
2787
|
-
if (!wantsJson(process.argv) && !args.flags.help) {
|
|
2999
|
+
if (command !== 'plan' && !wantsJson(process.argv) && !args.flags.help) {
|
|
2788
3000
|
checkForUpdate();
|
|
2789
3001
|
}
|
|
2790
3002
|
}
|