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.
Files changed (2) hide show
  1. package/bin/tokrepo.js +239 -10
  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.4.0';
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
- info(`No Codex skill files were written. Re-run with --approve-mcp or --yes to install.`);
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 dryRun = Boolean(args.flags.dryRun || args.flags.dry_run);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokrepo",
3
- "version": "3.4.0",
3
+ "version": "3.5.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"