tokrepo 3.4.0 → 3.5.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 +239 -10
- 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.5.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');
|
|
@@ -236,6 +236,12 @@ function guessTag(fileType) {
|
|
|
236
236
|
return map[fileType] || null;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
function parseCsvList(value) {
|
|
240
|
+
if (!value) return [];
|
|
241
|
+
if (Array.isArray(value)) return value.flatMap(parseCsvList);
|
|
242
|
+
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
243
|
+
}
|
|
244
|
+
|
|
239
245
|
// ─── Glob matching ───
|
|
240
246
|
|
|
241
247
|
function matchGlob(pattern, filename) {
|
|
@@ -295,7 +301,9 @@ function parseArgs(argv) {
|
|
|
295
301
|
}
|
|
296
302
|
|
|
297
303
|
const valueFlags = new Set([
|
|
298
|
-
'title', 'desc', 'tag', 'target', 'keyword', 'types',
|
|
304
|
+
'title', 'desc', 'tag', 'target', 'targets', 'keyword', 'types',
|
|
305
|
+
'kind', 'install-mode', 'install_mode', 'entrypoint', 'asset-kind', 'asset_kind',
|
|
306
|
+
'version',
|
|
299
307
|
'page', 'page-size', 'page_size', 'sort-by', 'sort_by',
|
|
300
308
|
'time-window', 'time_window',
|
|
301
309
|
]);
|
|
@@ -318,6 +326,10 @@ function parseArgs(argv) {
|
|
|
318
326
|
args.flags.dryRun = value;
|
|
319
327
|
} else if (normalized === 'approve_mcp') {
|
|
320
328
|
args.flags.approveMcp = value;
|
|
329
|
+
} else if (normalized === 'install_mode') {
|
|
330
|
+
args.flags.installMode = value;
|
|
331
|
+
} else if (normalized === 'asset_kind') {
|
|
332
|
+
args.flags.assetKind = value;
|
|
321
333
|
}
|
|
322
334
|
args.flags[normalized] = value;
|
|
323
335
|
};
|
|
@@ -574,6 +586,10 @@ async function cmdPush() {
|
|
|
574
586
|
description = args.flags.desc || description || '';
|
|
575
587
|
visibility = args.flags.public ? 1 : (args.flags.private ? 0 : (projectConfig?.visibility ?? 0));
|
|
576
588
|
tags = args.flags.tags || tags || [];
|
|
589
|
+
const kind = args.flags.kind || args.flags.assetKind || projectConfig?.kind || projectConfig?.asset_kind || '';
|
|
590
|
+
const targetTools = parseCsvList(args.flags.targets || args.flags.target || projectConfig?.target_tools || projectConfig?.targetTools);
|
|
591
|
+
const installMode = args.flags.installMode || projectConfig?.install_mode || projectConfig?.installMode || '';
|
|
592
|
+
const entrypoint = args.flags.entrypoint || projectConfig?.entrypoint || '';
|
|
577
593
|
|
|
578
594
|
// Read files and detect types
|
|
579
595
|
const pushFiles = [];
|
|
@@ -613,6 +629,15 @@ async function cmdPush() {
|
|
|
613
629
|
if (detectedTags.size > 0) {
|
|
614
630
|
log(` ${C.bold}Tags:${C.reset} ${Array.from(detectedTags).join(', ')}`);
|
|
615
631
|
}
|
|
632
|
+
const metadataSummary = [
|
|
633
|
+
kind ? `kind=${kind}` : '',
|
|
634
|
+
targetTools.length ? `target_tools=${targetTools.join(',')}` : '',
|
|
635
|
+
installMode ? `install_mode=${installMode}` : '',
|
|
636
|
+
entrypoint ? `entrypoint=${entrypoint}` : '',
|
|
637
|
+
].filter(Boolean);
|
|
638
|
+
if (metadataSummary.length > 0) {
|
|
639
|
+
log(` ${C.bold}Agent meta:${C.reset} ${metadataSummary.join(' · ')}`);
|
|
640
|
+
}
|
|
616
641
|
log('');
|
|
617
642
|
|
|
618
643
|
for (const f of pushFiles) {
|
|
@@ -634,6 +659,10 @@ async function cmdPush() {
|
|
|
634
659
|
tags: Array.from(detectedTags),
|
|
635
660
|
token_cost: String(Math.round(totalChars / 4)),
|
|
636
661
|
visibility: visibility,
|
|
662
|
+
kind,
|
|
663
|
+
target_tools: targetTools,
|
|
664
|
+
install_mode: installMode,
|
|
665
|
+
entrypoint,
|
|
637
666
|
}, config.token, config.api);
|
|
638
667
|
|
|
639
668
|
log('');
|
|
@@ -1095,11 +1124,13 @@ function explicitInstallMode(workflow) {
|
|
|
1095
1124
|
const candidates = [
|
|
1096
1125
|
workflow?.installMode,
|
|
1097
1126
|
workflow?.install_mode,
|
|
1127
|
+
workflow?.agent_metadata?.install_mode,
|
|
1128
|
+
workflow?.agentMetadata?.installMode,
|
|
1098
1129
|
workflow?.metadata?.installMode,
|
|
1099
1130
|
workflow?.metadata?.install_mode,
|
|
1100
1131
|
].filter(Boolean);
|
|
1101
1132
|
const mode = String(candidates[0] || '').toLowerCase();
|
|
1102
|
-
return ['single', 'bundle', 'split'].includes(mode) ? mode : '';
|
|
1133
|
+
return ['single', 'bundle', 'split', 'stage_only'].includes(mode) ? mode : '';
|
|
1103
1134
|
}
|
|
1104
1135
|
|
|
1105
1136
|
function inferCodexInstallMode(workflow, contents) {
|
|
@@ -1157,6 +1188,7 @@ function addPlanFile(plan, destPath, content, sourceName, type) {
|
|
|
1157
1188
|
|
|
1158
1189
|
function buildCodexInstallPlan(workflow, contents, opts = {}) {
|
|
1159
1190
|
const installMode = opts.installMode || inferCodexInstallMode(workflow, contents);
|
|
1191
|
+
const agentMetadata = workflow?.agent_metadata || workflow?.agentMetadata || {};
|
|
1160
1192
|
const plan = {
|
|
1161
1193
|
uuid: workflow.uuid,
|
|
1162
1194
|
title: workflow.title,
|
|
@@ -1166,8 +1198,20 @@ function buildCodexInstallPlan(workflow, contents, opts = {}) {
|
|
|
1166
1198
|
manifestPath: CODEX_MANIFEST_FILE,
|
|
1167
1199
|
files: [],
|
|
1168
1200
|
risks: [],
|
|
1201
|
+
agentMetadata,
|
|
1202
|
+
contentHash: workflow.content_hash || workflow.contentHash || agentMetadata.content_hash || '',
|
|
1169
1203
|
};
|
|
1170
1204
|
|
|
1205
|
+
if (installMode === 'stage_only') {
|
|
1206
|
+
const stageDir = path.join(CODEX_TOKREPO_DIR, 'staged', workflow.uuid);
|
|
1207
|
+
plan.baseDir = stageDir;
|
|
1208
|
+
contents.forEach((item, index) => {
|
|
1209
|
+
const relName = sanitizeRelativePath(item.name || `file-${index + 1}.md`);
|
|
1210
|
+
addPlanFile(plan, path.join(stageDir, relName), `${String(item.content || '').trim()}\n`, item.name, item.type);
|
|
1211
|
+
});
|
|
1212
|
+
return plan;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1171
1215
|
if (installMode === 'split') {
|
|
1172
1216
|
const usedDirs = new Set();
|
|
1173
1217
|
contents.forEach((item, index) => {
|
|
@@ -1244,6 +1288,9 @@ function publicInstallPlan(plan) {
|
|
|
1244
1288
|
manifestPath: plan.manifestPath,
|
|
1245
1289
|
baseDir: plan.baseDir,
|
|
1246
1290
|
risks: plan.risks,
|
|
1291
|
+
requiresConfirmation: hasCodexInstallRisks(plan),
|
|
1292
|
+
contentHash: plan.contentHash || '',
|
|
1293
|
+
agentMetadata: plan.agentMetadata || {},
|
|
1247
1294
|
files: plan.files.map(file => ({
|
|
1248
1295
|
path: file.path,
|
|
1249
1296
|
sourceName: file.sourceName,
|
|
@@ -1265,6 +1312,7 @@ function formatRiskLine(file) {
|
|
|
1265
1312
|
}
|
|
1266
1313
|
|
|
1267
1314
|
async function confirmCodexInstallRisks(plan, opts = {}) {
|
|
1315
|
+
if (plan.installMode === 'stage_only') return;
|
|
1268
1316
|
if (opts.dryRun || opts.stage || !hasCodexInstallRisks(plan)) return;
|
|
1269
1317
|
if (opts.approveMcp || opts.approve_mcp || opts.yes) return;
|
|
1270
1318
|
|
|
@@ -1301,6 +1349,32 @@ function stageCodexInstallPlan(plan) {
|
|
|
1301
1349
|
return stagePath;
|
|
1302
1350
|
}
|
|
1303
1351
|
|
|
1352
|
+
function executeStageOnlyCodexPlan(plan) {
|
|
1353
|
+
const installedFiles = [];
|
|
1354
|
+
const stageRoot = path.join(CODEX_TOKREPO_DIR, 'staged', plan.uuid);
|
|
1355
|
+
if (!fs.existsSync(stageRoot)) fs.mkdirSync(stageRoot, { recursive: true, mode: 0o700 });
|
|
1356
|
+
|
|
1357
|
+
for (const file of plan.files) {
|
|
1358
|
+
if (!ensureInside(stageRoot, file.path)) {
|
|
1359
|
+
throw new Error(`Stage path escaped TokRepo staging directory: ${file.path}`);
|
|
1360
|
+
}
|
|
1361
|
+
const destDir = path.dirname(file.path);
|
|
1362
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true, mode: 0o700 });
|
|
1363
|
+
fs.writeFileSync(file.path, file.content, { mode: 0o600 });
|
|
1364
|
+
installedFiles.push({
|
|
1365
|
+
path: file.path,
|
|
1366
|
+
sourceName: file.sourceName,
|
|
1367
|
+
sha256: sha256(file.content),
|
|
1368
|
+
bytes: Buffer.byteLength(String(file.content || '')),
|
|
1369
|
+
riskFlags: file.riskFlags,
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const stagePath = path.join(stageRoot, 'install-plan.json');
|
|
1374
|
+
fs.writeFileSync(stagePath, `${JSON.stringify(publicInstallPlan(plan), null, 2)}\n`, { mode: 0o600 });
|
|
1375
|
+
return { dryRun: true, staged: true, stageOnly: true, stagePath, plan: publicInstallPlan(plan), installedFiles };
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1304
1378
|
function readCodexManifest() {
|
|
1305
1379
|
try {
|
|
1306
1380
|
const parsed = JSON.parse(fs.readFileSync(CODEX_MANIFEST_FILE, 'utf8'));
|
|
@@ -1322,6 +1396,8 @@ function writeCodexManifestRecord(plan, installedFiles) {
|
|
|
1322
1396
|
targetTool: 'codex',
|
|
1323
1397
|
installMode: plan.installMode,
|
|
1324
1398
|
installedAt,
|
|
1399
|
+
contentHash: plan.contentHash || '',
|
|
1400
|
+
agentMetadata: plan.agentMetadata || {},
|
|
1325
1401
|
installedFiles: installedFiles.map(file => ({
|
|
1326
1402
|
path: file.path,
|
|
1327
1403
|
sourceName: file.sourceName,
|
|
@@ -1340,6 +1416,7 @@ function writeCodexManifestRecord(plan, installedFiles) {
|
|
|
1340
1416
|
|
|
1341
1417
|
function executeCodexInstallPlan(plan, opts = {}) {
|
|
1342
1418
|
if (opts.dryRun) return { dryRun: true, plan: publicInstallPlan(plan), installedFiles: [] };
|
|
1419
|
+
if (plan.installMode === 'stage_only') return executeStageOnlyCodexPlan(plan);
|
|
1343
1420
|
if (opts.stage) {
|
|
1344
1421
|
const stagePath = stageCodexInstallPlan(plan);
|
|
1345
1422
|
return { dryRun: true, staged: true, stagePath, plan: publicInstallPlan(plan), installedFiles: [] };
|
|
@@ -1547,9 +1624,13 @@ async function installOneAsset(target, config, apiBase, opts) {
|
|
|
1547
1624
|
|
|
1548
1625
|
if (!opts.json) {
|
|
1549
1626
|
const plan = result.plan;
|
|
1550
|
-
if (opts.stage) {
|
|
1627
|
+
if (result.staged || opts.stage) {
|
|
1551
1628
|
info(`Staged install plan: ${result.stagePath}`);
|
|
1552
|
-
|
|
1629
|
+
if (result.stageOnly) {
|
|
1630
|
+
info(`stage_only asset: files were written only under ${path.dirname(result.stagePath)}; no Codex skill was activated.`);
|
|
1631
|
+
} else {
|
|
1632
|
+
info(`No Codex skill files were written. Re-run with --approve-mcp or --yes to install.`);
|
|
1633
|
+
}
|
|
1553
1634
|
} else if (opts.dryRun) {
|
|
1554
1635
|
info(`Dry run: ${plan.files.length} file(s) would be installed to ${CODEX_SKILLS_DIR}`);
|
|
1555
1636
|
for (const file of plan.files) {
|
|
@@ -1764,6 +1845,12 @@ async function cmdList() {
|
|
|
1764
1845
|
}
|
|
1765
1846
|
|
|
1766
1847
|
async function cmdUpdate() {
|
|
1848
|
+
const args = parseArgs(process.argv);
|
|
1849
|
+
if (args.flags.target || args.flags.all || args.flags.force) {
|
|
1850
|
+
await cmdSyncInstalled();
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1767
1854
|
const uuid = process.argv[3];
|
|
1768
1855
|
if (!uuid) error('Usage: tokrepo update <uuid> [file]');
|
|
1769
1856
|
|
|
@@ -1833,9 +1920,11 @@ function tagMatchesTypes(workflow, requestedTypes) {
|
|
|
1833
1920
|
if (!requestedTypes || requestedTypes.length === 0) return true;
|
|
1834
1921
|
const tags = (workflow.tags || []).flatMap(t => [t.slug, t.name]).filter(Boolean).map(t => String(t).toLowerCase());
|
|
1835
1922
|
const assetType = getWorkflowAssetType(workflow);
|
|
1923
|
+
const metadataKind = String(workflow.asset_kind || workflow.agent_metadata?.asset_kind || workflow.agentMetadata?.assetKind || '').toLowerCase();
|
|
1836
1924
|
return requestedTypes.some(type => {
|
|
1837
1925
|
const needle = String(type).trim().toLowerCase();
|
|
1838
1926
|
if (!needle) return false;
|
|
1927
|
+
if (metadataKind === needle || metadataKind === `${needle}s`) return true;
|
|
1839
1928
|
if (assetType === needle || assetType === `${needle}s`) return true;
|
|
1840
1929
|
return tags.some(tag => tag === needle || tag === `${needle}s` || tag.includes(needle));
|
|
1841
1930
|
});
|
|
@@ -2116,17 +2205,17 @@ async function cmdSyncInstalled() {
|
|
|
2116
2205
|
|
|
2117
2206
|
const manifest = readCodexManifest();
|
|
2118
2207
|
const installed = (manifest.installs || []).filter(item => (item.targetTool || item.target_tool) === 'codex');
|
|
2208
|
+
const dryRun = Boolean(args.flags.dryRun || args.flags.dry_run);
|
|
2209
|
+
const stage = Boolean(args.flags.stage);
|
|
2119
2210
|
if (installed.length === 0) {
|
|
2120
|
-
if (json) outputJson({ targetTool: 'codex', count: 0, results: [] });
|
|
2211
|
+
if (json) outputJson({ targetTool: 'codex', manifestPath: CODEX_MANIFEST_FILE, dryRun, stage, count: 0, results: [] });
|
|
2121
2212
|
else info(`No Codex installs found in ${CODEX_MANIFEST_FILE}`);
|
|
2122
2213
|
return;
|
|
2123
2214
|
}
|
|
2124
2215
|
|
|
2125
2216
|
const config = readConfig();
|
|
2126
2217
|
const apiBase = config?.api || DEFAULT_API;
|
|
2127
|
-
const
|
|
2128
|
-
const stage = Boolean(args.flags.stage);
|
|
2129
|
-
const force = Boolean(args.flags.update || args.flags.force);
|
|
2218
|
+
const force = Boolean(args.flags.update || args.flags.force || args.flags.all);
|
|
2130
2219
|
const results = [];
|
|
2131
2220
|
|
|
2132
2221
|
for (let i = 0; i < installed.length; i++) {
|
|
@@ -2218,6 +2307,126 @@ async function cmdSyncInstalled() {
|
|
|
2218
2307
|
}
|
|
2219
2308
|
}
|
|
2220
2309
|
|
|
2310
|
+
async function cmdInstalled() {
|
|
2311
|
+
const args = parseArgs(process.argv);
|
|
2312
|
+
const targetTool = validateInstallTarget(args.flags.target || 'codex');
|
|
2313
|
+
if (targetTool !== 'codex') error(`installed currently supports --target codex only`);
|
|
2314
|
+
|
|
2315
|
+
const json = Boolean(args.flags.json);
|
|
2316
|
+
if (!json) log(`\n${C.bold}tokrepo installed${C.reset}\n`);
|
|
2317
|
+
|
|
2318
|
+
const manifest = readCodexManifest();
|
|
2319
|
+
const records = (manifest.installs || []).filter(item => (item.targetTool || item.target_tool) === 'codex');
|
|
2320
|
+
const list = records.map(record => {
|
|
2321
|
+
const files = (record.installedFiles || record.installed_files || []).map(file => {
|
|
2322
|
+
const actualSha = file.path && fs.existsSync(file.path) ? currentFileSha(file.path) : '';
|
|
2323
|
+
return {
|
|
2324
|
+
path: file.path,
|
|
2325
|
+
sourceName: file.sourceName || file.source_name,
|
|
2326
|
+
sha256: file.sha256,
|
|
2327
|
+
exists: Boolean(file.path && fs.existsSync(file.path)),
|
|
2328
|
+
changed: Boolean(actualSha && file.sha256 && actualSha !== file.sha256),
|
|
2329
|
+
};
|
|
2330
|
+
});
|
|
2331
|
+
return {
|
|
2332
|
+
uuid: record.uuid,
|
|
2333
|
+
title: record.title,
|
|
2334
|
+
sourceUrl: record.sourceUrl || record.source_url,
|
|
2335
|
+
targetTool: 'codex',
|
|
2336
|
+
installMode: record.installMode || record.install_mode,
|
|
2337
|
+
installedAt: record.installedAt || record.installed_at,
|
|
2338
|
+
contentHash: record.contentHash || record.content_hash || '',
|
|
2339
|
+
risks: record.risks || [],
|
|
2340
|
+
files,
|
|
2341
|
+
status: files.some(file => !file.exists) ? 'missing-files' : files.some(file => file.changed) ? 'local-changes' : 'installed',
|
|
2342
|
+
};
|
|
2343
|
+
});
|
|
2344
|
+
|
|
2345
|
+
if (json) {
|
|
2346
|
+
outputJson({ targetTool: 'codex', manifestPath: CODEX_MANIFEST_FILE, count: list.length, list });
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
if (list.length === 0) {
|
|
2351
|
+
info(`No Codex installs found in ${CODEX_MANIFEST_FILE}`);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
for (const item of list) {
|
|
2356
|
+
const color = item.status === 'installed' ? C.green : C.yellow;
|
|
2357
|
+
log(` ${color}${item.status}${C.reset} ${C.bold}${item.title || item.uuid}${C.reset}`);
|
|
2358
|
+
log(` ${C.dim}${item.uuid} · ${item.installMode || 'unknown'} · ${item.files.length} file(s)${C.reset}\n`);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
async function cmdOutdated() {
|
|
2363
|
+
const args = parseArgs(process.argv);
|
|
2364
|
+
const targetTool = validateInstallTarget(args.flags.target || 'codex');
|
|
2365
|
+
if (targetTool !== 'codex') error(`outdated currently supports --target codex only`);
|
|
2366
|
+
|
|
2367
|
+
const json = Boolean(args.flags.json);
|
|
2368
|
+
if (!json) log(`\n${C.bold}tokrepo outdated${C.reset}\n`);
|
|
2369
|
+
|
|
2370
|
+
const manifest = readCodexManifest();
|
|
2371
|
+
const installed = (manifest.installs || []).filter(item => (item.targetTool || item.target_tool) === 'codex');
|
|
2372
|
+
if (installed.length === 0) {
|
|
2373
|
+
if (json) outputJson({ targetTool: 'codex', count: 0, outdated: 0, list: [] });
|
|
2374
|
+
else info(`No Codex installs found in ${CODEX_MANIFEST_FILE}`);
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
const config = readConfig();
|
|
2379
|
+
const apiBase = config?.api || DEFAULT_API;
|
|
2380
|
+
const list = [];
|
|
2381
|
+
let unchanged = 0;
|
|
2382
|
+
let failed = 0;
|
|
2383
|
+
|
|
2384
|
+
for (const record of installed) {
|
|
2385
|
+
try {
|
|
2386
|
+
const { workflow, contents } = await fetchWorkflowForInstall(record.uuid, config, apiBase);
|
|
2387
|
+
const plan = buildCodexInstallPlan(workflow, contents, { installMode: record.installMode || record.install_mode });
|
|
2388
|
+
const diff = diffCodexPlanWithLocal(plan, record);
|
|
2389
|
+
if (diff.needsUpdate) {
|
|
2390
|
+
list.push({
|
|
2391
|
+
uuid: record.uuid,
|
|
2392
|
+
title: workflow.title,
|
|
2393
|
+
status: 'outdated',
|
|
2394
|
+
reasons: diff.reasons,
|
|
2395
|
+
plan: publicInstallPlan(plan),
|
|
2396
|
+
});
|
|
2397
|
+
} else {
|
|
2398
|
+
unchanged++;
|
|
2399
|
+
}
|
|
2400
|
+
} catch (e) {
|
|
2401
|
+
failed++;
|
|
2402
|
+
list.push({ uuid: record.uuid, title: record.title || record.uuid, status: 'failed', error: e.message });
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
if (json) {
|
|
2407
|
+
outputJson({ targetTool: 'codex', manifestPath: CODEX_MANIFEST_FILE, count: installed.length, outdated: list.filter(i => i.status === 'outdated').length, unchanged, failed, list });
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
const outdated = list.filter(item => item.status === 'outdated');
|
|
2412
|
+
if (outdated.length === 0 && failed === 0) {
|
|
2413
|
+
success(`All ${unchanged} Codex install(s) are up to date.`);
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
for (const item of list) {
|
|
2417
|
+
if (item.status === 'failed') {
|
|
2418
|
+
warn(`${item.title}: ${item.error}`);
|
|
2419
|
+
} else {
|
|
2420
|
+
log(` ${C.yellow}outdated${C.reset} ${C.bold}${item.title}${C.reset}`);
|
|
2421
|
+
for (const reason of item.reasons.slice(0, 3)) {
|
|
2422
|
+
log(` ${C.dim}${reason.type}: ${reason.path || ''}${C.reset}`);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
log('');
|
|
2427
|
+
info(`Run ${C.cyan}tokrepo update --target codex --all${C.reset} to update installed Codex assets.`);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2221
2430
|
async function cmdTags() {
|
|
2222
2431
|
log(`\n${C.bold}tokrepo tags${C.reset}\n`);
|
|
2223
2432
|
|
|
@@ -2340,13 +2549,16 @@ ${C.bold}DISCOVER & INSTALL${C.reset}
|
|
|
2340
2549
|
${C.cyan}install${C.reset} <name|uuid> Smart install (auto-detects type & placement)
|
|
2341
2550
|
${C.cyan}pull${C.reset} <url|uuid|@u/n> Download raw asset files
|
|
2342
2551
|
${C.cyan}clone${C.reset} @username Clone all assets from a user
|
|
2552
|
+
${C.cyan}installed${C.reset} List installed Codex assets from manifest
|
|
2553
|
+
${C.cyan}outdated${C.reset} Check installed Codex assets for updates
|
|
2343
2554
|
${C.cyan}sync-installed${C.reset} Update installed Codex assets from manifest
|
|
2344
2555
|
|
|
2345
2556
|
${C.bold}PUBLISH${C.reset}
|
|
2346
2557
|
${C.cyan}push${C.reset} [files...] Push files/directory (idempotent upsert)
|
|
2347
2558
|
${C.cyan}status${C.reset} Compare local vs remote (like git status)
|
|
2348
2559
|
${C.cyan}init${C.reset} Create .tokrepo.json project config
|
|
2349
|
-
${C.cyan}update${C.reset} <uuid> [f] Update existing asset
|
|
2560
|
+
${C.cyan}update${C.reset} <uuid> [f] Update existing remote asset
|
|
2561
|
+
${C.cyan}update${C.reset} --target codex --all Update installed Codex assets
|
|
2350
2562
|
${C.cyan}delete${C.reset} <uuid> Delete an asset
|
|
2351
2563
|
|
|
2352
2564
|
${C.bold}ACCOUNT${C.reset}
|
|
@@ -2362,6 +2574,9 @@ ${C.bold}PUSH OPTIONS${C.reset}
|
|
|
2362
2574
|
${C.cyan}--title${C.reset} "..." Set title (auto-detected from README or dir name)
|
|
2363
2575
|
${C.cyan}--desc${C.reset} "..." Set description
|
|
2364
2576
|
${C.cyan}--tag${C.reset} Skills Add tag (repeatable)
|
|
2577
|
+
${C.cyan}--kind${C.reset} skill Set agent asset_kind
|
|
2578
|
+
${C.cyan}--target${C.reset} codex Add target tool metadata on push
|
|
2579
|
+
${C.cyan}--install-mode${C.reset} bundle Set install_mode metadata
|
|
2365
2580
|
|
|
2366
2581
|
${C.bold}INSTALL BEHAVIOR${C.reset}
|
|
2367
2582
|
Skills → .claude/skills/ (if .claude/ exists)
|
|
@@ -2380,8 +2595,12 @@ ${C.bold}EXAMPLES${C.reset}
|
|
|
2380
2595
|
tokrepo install ca000374-f5d8-... --target codex
|
|
2381
2596
|
tokrepo install c4b18aeb --target gemini # Install for Gemini CLI
|
|
2382
2597
|
tokrepo clone @henuwangkai --target codex --keyword video
|
|
2598
|
+
tokrepo installed --target codex --json
|
|
2599
|
+
tokrepo outdated --target codex --json
|
|
2600
|
+
tokrepo update --target codex --all
|
|
2383
2601
|
tokrepo sync-installed --target codex --dry-run
|
|
2384
2602
|
tokrepo push --private my-rules.md # Save one file privately
|
|
2603
|
+
tokrepo push . --kind skill --target codex --install-mode bundle
|
|
2385
2604
|
tokrepo push --public skill.md # Share one file publicly
|
|
2386
2605
|
tokrepo push --private . # Push current dir as private
|
|
2387
2606
|
tokrepo push --public --title "My MCP" . # Push dir publicly with title
|
|
@@ -2484,6 +2703,9 @@ ${C.bold}tokrepo sync-installed${C.reset}
|
|
|
2484
2703
|
|
|
2485
2704
|
USAGE
|
|
2486
2705
|
tokrepo sync-installed --target codex [--dry-run] [--stage] [--update] [--approve-mcp] [--json]
|
|
2706
|
+
tokrepo installed --target codex [--json]
|
|
2707
|
+
tokrepo outdated --target codex [--json]
|
|
2708
|
+
tokrepo update --target codex --all [--stage] [--approve-mcp] [--json]
|
|
2487
2709
|
|
|
2488
2710
|
BEHAVIOR
|
|
2489
2711
|
Reads ~/.codex/tokrepo/install-manifest.json, fetches each TokRepo asset again,
|
|
@@ -2491,6 +2713,9 @@ BEHAVIOR
|
|
|
2491
2713
|
changed or missing files. Use --update to force reinstall unchanged assets.
|
|
2492
2714
|
|
|
2493
2715
|
EXAMPLES
|
|
2716
|
+
tokrepo installed --target codex --json
|
|
2717
|
+
tokrepo outdated --target codex --json
|
|
2718
|
+
tokrepo update --target codex --all
|
|
2494
2719
|
tokrepo sync-installed --target codex --dry-run --json
|
|
2495
2720
|
tokrepo sync-installed --target codex --stage
|
|
2496
2721
|
tokrepo sync-installed --target codex --update --approve-mcp
|
|
@@ -2513,6 +2738,8 @@ function showCommandHelp(command) {
|
|
|
2513
2738
|
showCloneHelp(); break;
|
|
2514
2739
|
case 'sync-installed':
|
|
2515
2740
|
case 'sync':
|
|
2741
|
+
case 'installed':
|
|
2742
|
+
case 'outdated':
|
|
2516
2743
|
showSyncInstalledHelp(); break;
|
|
2517
2744
|
default:
|
|
2518
2745
|
showHelp(); break;
|
|
@@ -2542,6 +2769,8 @@ async function main() {
|
|
|
2542
2769
|
case 'update': await cmdUpdate(); break;
|
|
2543
2770
|
case 'delete': await cmdDelete(); break;
|
|
2544
2771
|
case 'clone': await cmdClone(); break;
|
|
2772
|
+
case 'installed': await cmdInstalled(); break;
|
|
2773
|
+
case 'outdated': await cmdOutdated(); break;
|
|
2545
2774
|
case 'sync-installed': case 'sync': await cmdSyncInstalled(); break;
|
|
2546
2775
|
case 'tags': await cmdTags(); break;
|
|
2547
2776
|
case 'status': case 'diff': await cmdStatus(); break;
|