@aiwg/cli 2026.7.19 → 2026.7.21

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 (88) hide show
  1. package/README.md +397 -385
  2. package/dist/src/api/index.d.ts +1 -0
  3. package/dist/src/api/index.js +1 -0
  4. package/dist/src/artifacts/browser-export.js +7 -0
  5. package/dist/src/artifacts/citation-parser.js +96 -35
  6. package/dist/src/artifacts/cli.js +59 -5
  7. package/dist/src/artifacts/discover-facets.js +15 -0
  8. package/dist/src/artifacts/discovery-eval.js +290 -0
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +1 -1
  10. package/dist/src/artifacts/fortemi-shard-export.js +1 -1
  11. package/dist/src/artifacts/index-builder.js +54 -17
  12. package/dist/src/artifacts/query-engine.js +10 -6
  13. package/dist/src/artifacts/state-transfer.js +27 -0
  14. package/dist/src/artifacts/stats.js +8 -0
  15. package/dist/src/cli/cli-extension-loader.js +73 -0
  16. package/dist/src/cli/handlers/help.js +2 -1
  17. package/dist/src/cli/handlers/index.js +6 -2
  18. package/dist/src/cli/handlers/resource-versions.js +247 -0
  19. package/dist/src/cli/handlers/sessions.js +966 -0
  20. package/dist/src/cli/handlers/skill-lint.js +49 -45
  21. package/dist/src/cli/handlers/subcommands.js +55 -3
  22. package/dist/src/cli/handlers/use.js +154 -59
  23. package/dist/src/cli/handlers/utilities.js +49 -34
  24. package/dist/src/cli/skill-usage.js +146 -24
  25. package/dist/src/config/cli.js +13 -9
  26. package/dist/src/config/project-artifacts-runtime.mjs +68 -0
  27. package/dist/src/config/project-artifacts.js +1 -68
  28. package/dist/src/extensions/commands/definitions.js +65 -2
  29. package/dist/src/extensions/manifest.js +29 -0
  30. package/dist/src/extensions/project-local-discovery.js +86 -2
  31. package/dist/src/extensions/project-local-remove.js +52 -56
  32. package/dist/src/extensions/shadow-resolver.js +3 -1
  33. package/dist/src/plugins/standalone-packager.js +143 -0
  34. package/dist/src/resources/cache-cleanup.js +67 -0
  35. package/dist/src/resources/doctor.js +107 -0
  36. package/dist/src/resources/lockfile.js +125 -0
  37. package/dist/src/resources/resolver.js +133 -0
  38. package/dist/src/resources/web-release.d.ts +8 -0
  39. package/dist/src/resources/web-release.js +159 -1
  40. package/dist/src/sessions/adapters/claude.js +357 -0
  41. package/dist/src/sessions/adapters/codex.js +521 -0
  42. package/dist/src/sessions/adapters/copilot.js +226 -0
  43. package/dist/src/sessions/adapters/cursor.js +372 -0
  44. package/dist/src/sessions/adapters/factory.js +345 -0
  45. package/dist/src/sessions/adapters/generic.js +225 -0
  46. package/dist/src/sessions/adapters/hermes.js +341 -0
  47. package/dist/src/sessions/adapters/openclaw.js +381 -0
  48. package/dist/src/sessions/adapters/opencode.js +454 -0
  49. package/dist/src/sessions/adapters/openhuman.js +315 -0
  50. package/dist/src/sessions/adapters/warp.js +160 -0
  51. package/dist/src/sessions/adapters/windsurf.js +212 -0
  52. package/dist/src/sessions/candidates.js +210 -0
  53. package/dist/src/sessions/contracts.js +310 -0
  54. package/dist/src/sessions/discovery.js +51 -0
  55. package/dist/src/sessions/fixtures.js +12 -0
  56. package/dist/src/sessions/importer.js +315 -0
  57. package/dist/src/sessions/index.js +25 -0
  58. package/dist/src/sessions/knowledge-shard.js +61 -0
  59. package/dist/src/sessions/optional-backends.js +238 -0
  60. package/dist/src/sessions/policy.js +192 -0
  61. package/dist/src/sessions/ports.js +2 -0
  62. package/dist/src/sessions/promotion.js +367 -0
  63. package/dist/src/sessions/readers.js +176 -0
  64. package/dist/src/sessions/repository.js +1551 -0
  65. package/dist/src/skills/adapters/agent-skills.js +59 -0
  66. package/dist/src/skills/adapters/local.js +19 -1
  67. package/dist/src/skills/agent-skills.js +249 -0
  68. package/dist/src/skills/cli.js +463 -7
  69. package/dist/src/skills/deployer.js +554 -0
  70. package/dist/src/skills/doctor.js +105 -0
  71. package/dist/src/skills/exporter.js +382 -0
  72. package/dist/src/skills/importer.js +921 -0
  73. package/dist/src/skills/registry.js +19 -0
  74. package/dist/src/skills/validator.js +323 -0
  75. package/dist/src/smiths/context-pipeline/aiwg-md.js +5 -1
  76. package/dist/src/smiths/context-pipeline/claude-hook.js +21 -1
  77. package/dist/src/smiths/context-pipeline/finalization.js +5 -3
  78. package/dist/src/smiths/context-pipeline/generator.js +4 -1
  79. package/dist/src/smiths/context-pipeline/parallelism-section.js +34 -1
  80. package/dist/src/smiths/context-pipeline/workspace-context.js +15 -3
  81. package/dist/src/smiths/mcpsmith/example.js +3 -1
  82. package/dist/src/smiths/mcpsmith/generator.js +3 -1
  83. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +2 -1
  84. package/dist/src/storage/cli.js +3 -2
  85. package/dist/src/storage/subsystem-cli.js +7 -2
  86. package/dist/src/update/notifier.mjs +1 -1
  87. package/dist/src/update/service.mjs +123 -0
  88. package/package.json +3 -2
@@ -20,6 +20,7 @@ import { loadManifest, writeManifest, statMatches, makeEntry } from './checksum-
20
20
  import { workspaceLinkedFiles } from '../smiths/context-pipeline/workspace-context.js';
21
21
  import { normalizeOperationalState } from './operational-state.js';
22
22
  import { DEFAULT_PROJECT_AIWG_DIR, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
23
+ import { normalizeStateTransferProjection } from './state-transfer.js';
23
24
  function pathContains(parent, child) {
24
25
  const relative = path.relative(parent, child);
25
26
  return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
@@ -322,10 +323,10 @@ export function parseRunbookDoc(data, body, filePath) {
322
323
  /**
323
324
  * Extract trigger phrases from a SKILL.md / agent body.
324
325
  *
325
- * Skills declare alternate activation phrases under a `## Triggers`
326
- * heading; the body typically lists them as bullet points. This
327
- * function pulls each bullet's leading phrase (the part before any
328
- * `→` arrow or em-dash explanation), lowercased and trimmed.
326
+ * Skills declare alternate activation phrases in `triggers`, `aliases`,
327
+ * `deprecated_names`, or under a `## Triggers` heading. This function pulls
328
+ * those names plus each bullet's leading phrase (the part before any `→`
329
+ * arrow or em-dash explanation), lowercased and trimmed.
329
330
  *
330
331
  * Returns an empty array when no `## Triggers` section is found —
331
332
  * non-skill artifacts get `triggers: undefined` after this is wired.
@@ -334,18 +335,20 @@ export function parseRunbookDoc(data, body, filePath) {
334
335
  */
335
336
  export function extractTriggers(body, frontmatter) {
336
337
  const phrases = [];
337
- const declaredTriggers = Array.isArray(frontmatter?.triggers)
338
- ? frontmatter.triggers
339
- : [];
340
- for (const trigger of declaredTriggers) {
341
- if (typeof trigger !== 'string')
342
- continue;
343
- const phrase = trigger.trim().toLowerCase();
344
- if (phrase.length === 0)
345
- continue;
346
- if (phrase.length > 200)
347
- continue;
348
- phrases.push(phrase);
338
+ for (const field of ['triggers', 'aliases', 'deprecated_names']) {
339
+ const declaredValues = Array.isArray(frontmatter?.[field])
340
+ ? frontmatter[field]
341
+ : [];
342
+ for (const value of declaredValues) {
343
+ if (typeof value !== 'string')
344
+ continue;
345
+ const phrase = value.trim().toLowerCase();
346
+ if (phrase.length === 0)
347
+ continue;
348
+ if (phrase.length > 200)
349
+ continue;
350
+ phrases.push(phrase);
351
+ }
349
352
  }
350
353
  // Find a triggers heading (case-insensitive). Accepted variants:
351
354
  // ## Triggers
@@ -882,6 +885,7 @@ export async function buildIndex(cwd, options = {}) {
882
885
  // Script entrypoint metadata is meaningful for skills only (#1227).
883
886
  const script = type === 'skill' ? extractSkillScript(data) : undefined;
884
887
  const operationalState = normalizeOperationalState(data.operational_state);
888
+ const stateTransfer = normalizeStateTransferProjection(data.state_transfer);
885
889
  // Canonical short name (#1233) — used by the scorer to floor exact-name
886
890
  // queries to 1.0 so hyphenated kernel-skill names like `aiwg-doctor`
887
891
  // remain searchable even when the rendered title strips the hyphen.
@@ -907,6 +911,7 @@ export async function buildIndex(cwd, options = {}) {
907
911
  ...(kernel ? { kernel } : {}),
908
912
  ...(script ? { script } : {}),
909
913
  ...(operationalState ? { operationalState } : {}),
914
+ ...(stateTransfer ? { stateTransfer } : {}),
910
915
  };
911
916
  }
912
917
  entries[relativePath] = entry;
@@ -957,6 +962,7 @@ export async function buildIndex(cwd, options = {}) {
957
962
  }
958
963
  }
959
964
  // Run citation sidecar edge extraction if configured
965
+ let citationMetrics = null;
960
966
  if (graphConfig?.edgeExtraction?.parser === 'citation-sidecar') {
961
967
  // Build REF-XXX → path map from all entries with ref frontmatter
962
968
  const entryFrontmatter = new Map();
@@ -971,6 +977,10 @@ export async function buildIndex(cwd, options = {}) {
971
977
  const refToPath = buildRefToPathMap(entryFrontmatter);
972
978
  // Parse each entry as a citation sidecar and extract edges
973
979
  let citationEdgeCount = 0;
980
+ let outgoingDeclarations = 0;
981
+ let incomingDeclarations = 0;
982
+ const canonicalOutgoing = new Set();
983
+ const declaredIncoming = new Set();
974
984
  for (const entryPath of Object.keys(entries)) {
975
985
  const fullPath = absoluteEntryPath(cwd, entryPath, graph);
976
986
  if (!fs.existsSync(fullPath))
@@ -979,6 +989,14 @@ export async function buildIndex(cwd, options = {}) {
979
989
  const result = parseCitationSidecar(content);
980
990
  if (!result)
981
991
  continue;
992
+ outgoingDeclarations += result.cites.length;
993
+ incomingDeclarations += result.citedBy.length;
994
+ for (const targetRef of result.cites) {
995
+ canonicalOutgoing.add(`${result.ref}\0${targetRef}`);
996
+ }
997
+ for (const sourceRef of result.citedBy) {
998
+ declaredIncoming.add(`${sourceRef}\0${result.ref}`);
999
+ }
982
1000
  const edges = citationResultToEdges(result, refToPath);
983
1001
  if (!depGraph[entryPath]) {
984
1002
  depGraph[entryPath] = { upstream: [], downstream: [] };
@@ -1004,6 +1022,15 @@ export async function buildIndex(cwd, options = {}) {
1004
1022
  citationEdgeCount++;
1005
1023
  }
1006
1024
  }
1025
+ citationMetrics = {
1026
+ canonicalEdges: canonicalOutgoing.size,
1027
+ outgoingDeclarations,
1028
+ incomingDeclarations,
1029
+ unmirroredOutgoing: [...canonicalOutgoing]
1030
+ .filter(edge => !declaredIncoming.has(edge)).length,
1031
+ unmirroredIncoming: [...declaredIncoming]
1032
+ .filter(edge => !canonicalOutgoing.has(edge)).length,
1033
+ };
1007
1034
  if (verbose && citationEdgeCount > 0) {
1008
1035
  console.log(` citation edges: ${citationEdgeCount}`);
1009
1036
  }
@@ -1038,7 +1065,9 @@ export async function buildIndex(cwd, options = {}) {
1038
1065
  manifestStats.pruned = 0;
1039
1066
  writeManifest(indexOutputDir, nextManifest);
1040
1067
  // Write stats
1041
- const totalEdges = Object.values(depGraph).reduce((sum, node) => sum + node.downstream.length, 0);
1068
+ const downstreamEdges = Object.values(depGraph).reduce((sum, node) => sum + node.downstream.length, 0);
1069
+ const adjacencyEntries = Object.values(depGraph).reduce((sum, node) => sum + node.upstream.length + node.downstream.length, 0);
1070
+ const totalEdges = citationMetrics?.canonicalEdges ?? downstreamEdges;
1042
1071
  const orphaned = Object.entries(depGraph).filter(([, node]) => node.upstream.length === 0 && node.downstream.length === 0).length;
1043
1072
  const mostReferenced = Object.entries(depGraph)
1044
1073
  .map(([p, node]) => ({ path: p, count: node.downstream.length }))
@@ -1064,6 +1093,14 @@ export async function buildIndex(cwd, options = {}) {
1064
1093
  tagDistribution: tagDist,
1065
1094
  graphMetrics: {
1066
1095
  totalEdges,
1096
+ ...(citationMetrics ? {
1097
+ canonicalEdges: citationMetrics.canonicalEdges,
1098
+ outgoingDeclarations: citationMetrics.outgoingDeclarations,
1099
+ incomingDeclarations: citationMetrics.incomingDeclarations,
1100
+ adjacencyEntries,
1101
+ unmirroredOutgoing: citationMetrics.unmirroredOutgoing,
1102
+ unmirroredIncoming: citationMetrics.unmirroredIncoming,
1103
+ } : {}),
1067
1104
  orphanedArtifacts: orphaned,
1068
1105
  mostReferenced,
1069
1106
  },
@@ -1740,14 +1740,18 @@ export async function showArtifact(cwd, params) {
1740
1740
  try {
1741
1741
  if (!webRelease)
1742
1742
  throw new Error('verified web release context is unavailable');
1743
- const { fetchVerifiedRawResource } = await import('../resources/web-release.js');
1744
- const bytes = await fetchVerifiedRawResource(webRelease, filePath, {
1745
- baseUrl: params.webReleaseOptions?.baseUrl,
1746
- fetcher: params.webReleaseOptions?.fetcher,
1747
- allowInsecureLoopbackHttp: params.webReleaseOptions?.allowInsecureLoopbackHttp,
1743
+ const { logicalIdFromFirstPartyPath, resolveAiwgResourceBytes } = await import('../resources/resolver.js');
1744
+ const logicalId = logicalIdFromFirstPartyPath(entry.path);
1745
+ if (!logicalId)
1746
+ throw new Error(`indexed path is not a first-party AIWG resource: ${entry.path}`);
1747
+ const resolved = await resolveAiwgResourceBytes(logicalId, {
1748
+ source: 'web',
1749
+ frameworkRoot: aiwgRoot ?? cwd,
1750
+ webRelease,
1751
+ webReleaseOptions: params.webReleaseOptions,
1748
1752
  offline: params.offline,
1749
1753
  });
1750
- content = bytes.toString('utf8');
1754
+ content = resolved.bytes.toString('utf8');
1751
1755
  }
1752
1756
  catch (error) {
1753
1757
  console.error(`Error: AIWG web resource show failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -0,0 +1,27 @@
1
+ export function normalizeStateTransferProjection(value) {
2
+ if (value === undefined)
3
+ return undefined;
4
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
5
+ throw new Error("state_transfer must be an object");
6
+ }
7
+ const record = value;
8
+ const unknown = Object.keys(record).filter((key) => key !== "deleted_at");
9
+ if (unknown.length > 0) {
10
+ throw new Error(`state_transfer has unsupported fields: ${unknown.join(", ")}`);
11
+ }
12
+ if (!Object.hasOwn(record, "deleted_at")) {
13
+ throw new Error("state_transfer.deleted_at is required");
14
+ }
15
+ const deletedAt = record.deleted_at;
16
+ if (deletedAt === null)
17
+ return { deletedAt: null };
18
+ if (deletedAt instanceof Date && !Number.isNaN(deletedAt.getTime())) {
19
+ return { deletedAt: deletedAt.toISOString() };
20
+ }
21
+ if (typeof deletedAt !== "string"
22
+ || Number.isNaN(Date.parse(deletedAt))) {
23
+ throw new Error("state_transfer.deleted_at must be null or an ISO date-time");
24
+ }
25
+ return { deletedAt: new Date(deletedAt).toISOString() };
26
+ }
27
+ //# sourceMappingURL=state-transfer.js.map
@@ -153,6 +153,14 @@ async function renderStats(cwd, stats, options, graphType) {
153
153
  // Dependency graph
154
154
  console.log('Dependency Graph:');
155
155
  console.log(` Total edges: ${stats.graphMetrics.totalEdges}`);
156
+ if (stats.graphMetrics.canonicalEdges !== undefined) {
157
+ console.log(` Canonical edges: ${stats.graphMetrics.canonicalEdges}`);
158
+ console.log(` Outgoing declares: ${stats.graphMetrics.outgoingDeclarations}`);
159
+ console.log(` Incoming declares: ${stats.graphMetrics.incomingDeclarations}`);
160
+ console.log(` Adjacency entries: ${stats.graphMetrics.adjacencyEntries}`);
161
+ console.log(` Unmirrored outgoing:${String(stats.graphMetrics.unmirroredOutgoing).padStart(3)}`);
162
+ console.log(` Unmirrored incoming:${String(stats.graphMetrics.unmirroredIncoming).padStart(3)}`);
163
+ }
156
164
  console.log(` Orphaned artifacts: ${stats.graphMetrics.orphanedArtifacts}`);
157
165
  if (stats.graphMetrics.mostReferenced) {
158
166
  console.log(` Most referenced: ${stats.graphMetrics.mostReferenced.path} (${stats.graphMetrics.mostReferenced.count} dependents)`);
@@ -41,10 +41,83 @@ export async function writeRegistry(cwd, registry) {
41
41
  * Called by `aiwg use <addon>` after reading the addon manifest.
42
42
  */
43
43
  export async function registerCliCommands(cwd, namespace, description, source, subcommands) {
44
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(namespace)) {
45
+ throw new Error(`Invalid CLI namespace: ${namespace}`);
46
+ }
47
+ if (!source || !path.isAbsolute(source)) {
48
+ throw new Error('CLI extension source must be an absolute path.');
49
+ }
50
+ if (Object.keys(subcommands).length === 0) {
51
+ throw new Error(`CLI namespace '${namespace}' declares no subcommands.`);
52
+ }
53
+ for (const [name, subcommand] of Object.entries(subcommands)) {
54
+ if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(name)) {
55
+ throw new Error(`Invalid CLI subcommand: ${namespace} ${name}`);
56
+ }
57
+ if (!subcommand
58
+ || typeof subcommand.file !== 'string'
59
+ || !/^[a-zA-Z0-9_-]+\.mjs$/.test(subcommand.file)) {
60
+ throw new Error(`CLI subcommand '${namespace} ${name}' must name a local .mjs file.`);
61
+ }
62
+ if (typeof subcommand.description !== 'string' || !subcommand.description.trim()) {
63
+ throw new Error(`CLI subcommand '${namespace} ${name}' requires a description.`);
64
+ }
65
+ }
44
66
  const existing = await readRegistry(cwd) ?? {};
45
67
  existing[namespace] = { source, description, subcommands };
46
68
  await writeRegistry(cwd, existing);
47
69
  }
70
+ /**
71
+ * Read and validate an addon-shaped manifest's expandable CLI declaration.
72
+ *
73
+ * `sourceRoot` may be a bundled addon, a project-local addon, or the validated
74
+ * payload of a project-local plugin wrapper. Module files are constrained to
75
+ * the declared commands directory; registration never follows `..` paths.
76
+ */
77
+ export async function loadCliCommandsContribution(sourceRoot) {
78
+ const manifestPath = path.join(sourceRoot, 'manifest.json');
79
+ let raw;
80
+ try {
81
+ raw = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
82
+ }
83
+ catch (error) {
84
+ if (error.code === 'ENOENT')
85
+ return null;
86
+ throw new Error(`Unable to read CLI extension manifest at ${manifestPath}: ${error.message}`);
87
+ }
88
+ const candidate = raw.cli_commands;
89
+ if (candidate === undefined)
90
+ return null;
91
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
92
+ throw new Error(`Invalid cli_commands block in ${manifestPath}`);
93
+ }
94
+ const commands = candidate;
95
+ if (typeof commands.namespace !== 'string'
96
+ || typeof commands.description !== 'string'
97
+ || !commands.subcommands
98
+ || typeof commands.subcommands !== 'object'
99
+ || Array.isArray(commands.subcommands)) {
100
+ throw new Error(`Incomplete cli_commands block in ${manifestPath}`);
101
+ }
102
+ const entry = commands.entry ?? 'commands/';
103
+ if (typeof entry !== 'string'
104
+ || path.isAbsolute(entry)
105
+ || entry.split(/[\\/]+/).includes('..')) {
106
+ throw new Error(`Unsafe cli_commands.entry in ${manifestPath}`);
107
+ }
108
+ const commandsSource = path.resolve(sourceRoot, entry);
109
+ const relative = path.relative(path.resolve(sourceRoot), commandsSource);
110
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
111
+ throw new Error(`cli_commands.entry escapes its addon root in ${manifestPath}`);
112
+ }
113
+ const manifest = {
114
+ namespace: commands.namespace,
115
+ description: commands.description,
116
+ entry,
117
+ subcommands: commands.subcommands,
118
+ };
119
+ return { manifest, commandsSource };
120
+ }
48
121
  /**
49
122
  * Try to execute an addon-contributed CLI command
50
123
  *
@@ -80,6 +80,7 @@ function displayHelp() {
80
80
  helpGroup('DISCOVERY', [
81
81
  ['discover "<phrase>"', 'Find skills/agents/commands/rules by capability'],
82
82
  ['show <type> <name>', 'Stream the body of an indexed artifact'],
83
+ ['versions <list|resolve|show>', 'Browse and resolve signed AIWG web resource releases'],
83
84
  ['index <subcommand>', 'Manage the artifact index (build/query/discover/deps/stats)'],
84
85
  ['artifacts move --to <path>', 'Move/rename the project AIWG artifact root and reindex'],
85
86
  ]);
@@ -120,7 +121,7 @@ function displayHelp() {
120
121
  ['doctor', 'Check installation health'],
121
122
  ['version', 'Show version and channel info'],
122
123
  ['refresh', 'Update AIWG and redeploy frameworks (formerly: sync)'],
123
- ['update', 'Check for updates'],
124
+ ['update', 'Update the active installation and re-deploy installed frameworks (alias: upgrade)'],
124
125
  ['help', 'Show this help message'],
125
126
  ]);
126
127
  helpGroup('CHANNEL', [
@@ -44,6 +44,7 @@ import { serveHandler } from './serve.js';
44
44
  import { lintHandler } from './lint.js';
45
45
  import { feedbackHandler } from './feedback.js';
46
46
  import { sessionHandler } from './session.js';
47
+ import { sessionsHandler } from './sessions.js';
47
48
  import { sandboxHandler, sandboxHandlers } from './sandbox.js';
48
49
  import { diagnoseHandler } from './diagnose.js';
49
50
  import { localExecutorHandler, localExecutorServeHandler } from './local-executor.js';
@@ -54,6 +55,7 @@ import { cockpitHandler } from './cockpit.js';
54
55
  import { commandLogHandler } from './command-log.js';
55
56
  import { skillUsageHandler } from './skill-usage.js';
56
57
  import { modelsHandler } from './models.js';
58
+ import { versionsHandler } from './resource-versions.js';
57
59
  // Re-export individual handlers
58
60
  export {
59
61
  // Maintenance
@@ -61,11 +63,11 @@ helpHandler, versionHandler, doctorHandler, updateHandler, refreshHandler, regen
61
63
  // Framework management
62
64
  useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, issueHandler, issueAuditHandler, runHandler,
63
65
  // Project
64
- newBundleHandler, quickrefHandler, newProjectHandler,
66
+ newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
65
67
  // Workspace
66
68
  statusHandler, wizardHandler, migrateWorkspaceHandler, rollbackWorkspaceHandler,
67
69
  // Subcommands
68
- mcpHandler, catalogHandler, modelsHandler, indexHandler, artifactsHandler, corpusHandler, discoverHandler, showHandler, featuresHandler, skillsHandler, configHandler, opsHandler, storageHandler, activityLogHandler, commandLogHandler, skillUsageHandler, kbHandler, memoryHandler, reflectionsHandler, provenanceHandler, researchStoreHandler, researchQueryHandler, runtimeInfoHandler, agentcardHandler,
70
+ mcpHandler, catalogHandler, modelsHandler, versionsHandler, indexHandler, artifactsHandler, corpusHandler, discoverHandler, showHandler, featuresHandler, skillsHandler, configHandler, opsHandler, storageHandler, activityLogHandler, commandLogHandler, skillUsageHandler, kbHandler, memoryHandler, reflectionsHandler, provenanceHandler, researchStoreHandler, researchQueryHandler, runtimeInfoHandler, agentcardHandler,
69
71
  // Agentic Tools (RLM)
70
72
  chunkHandler, fanoutHandler, rlmPrepHandler, rlmSearchHandler, rlmStatusCliHandler, rlmCacheHandler,
71
73
  // Utilities
@@ -139,6 +141,7 @@ export const allHandlers = [
139
141
  mcpHandler,
140
142
  catalogHandler,
141
143
  modelsHandler,
144
+ versionsHandler,
142
145
  indexHandler,
143
146
  artifactsHandler,
144
147
  corpusHandler,
@@ -222,6 +225,7 @@ export const allHandlers = [
222
225
  feedbackHandler,
223
226
  // Session (#884)
224
227
  sessionHandler,
228
+ sessionsHandler,
225
229
  // Repo access policy (#1376)
226
230
  ...repoAccessHandlers,
227
231
  ];
@@ -0,0 +1,247 @@
1
+ import path from "node:path";
2
+ import { loadResourceTrustRootFile, readVerifiedRegularFile, resolveWebRelease, } from "../../resources/web-release.js";
3
+ import { getProjectDir } from "../../config/aiwg-config.js";
4
+ import { cleanWebResourceCache } from "../../resources/cache-cleanup.js";
5
+ import { writeWebResourceLock } from "../../resources/lockfile.js";
6
+ const MAX_RESOURCE_MANIFEST_BYTES = 4 * 1024 * 1024;
7
+ const DEFAULT_CHANNELS = ["stable", "latest", "canary", "main"];
8
+ function usage() {
9
+ return [
10
+ "Usage: aiwg versions <list|resolve|show|clean-cache> [selector] [--json] [--pretty] [--offline]",
11
+ "",
12
+ "Examples:",
13
+ " aiwg versions list --json",
14
+ " aiwg versions resolve stable --json",
15
+ " aiwg versions resolve stable --write-lock",
16
+ " aiwg versions show 2026.7.18",
17
+ " aiwg versions clean-cache --dry-run",
18
+ ].join("\n");
19
+ }
20
+ function flagValue(args, flag) {
21
+ const index = args.indexOf(flag);
22
+ if (index === -1)
23
+ return undefined;
24
+ const value = args[index + 1];
25
+ if (!value || value.startsWith("--"))
26
+ throw new Error(`${flag} requires a value`);
27
+ return value;
28
+ }
29
+ function parseArgs(args) {
30
+ const [rawSubcommand = "list", ...rest] = args;
31
+ if (rawSubcommand !== "list" &&
32
+ rawSubcommand !== "resolve" &&
33
+ rawSubcommand !== "show" &&
34
+ rawSubcommand !== "clean-cache") {
35
+ throw new Error(`Unknown versions subcommand: ${rawSubcommand}\n\n${usage()}`);
36
+ }
37
+ const valueFlagIndexes = new Set();
38
+ for (const flag of ["--channels", "--target", "--prefix"]) {
39
+ const index = rest.indexOf(flag);
40
+ if (index !== -1) {
41
+ valueFlagIndexes.add(index);
42
+ valueFlagIndexes.add(index + 1);
43
+ }
44
+ }
45
+ const positionals = rest.filter((arg, index) => !arg.startsWith("--") && !valueFlagIndexes.has(index));
46
+ if ((rawSubcommand === "resolve" || rawSubcommand === "show") && positionals.length !== 1) {
47
+ throw new Error(`aiwg versions ${rawSubcommand} requires exactly one version, range, digest, or channel selector\n\n${usage()}`);
48
+ }
49
+ if (rawSubcommand === "list" && positionals.length > 0) {
50
+ throw new Error(`aiwg versions list does not accept positional selectors\n\n${usage()}`);
51
+ }
52
+ if (rawSubcommand === "clean-cache" && positionals.length > 0) {
53
+ throw new Error(`aiwg versions clean-cache does not accept positional selectors\n\n${usage()}`);
54
+ }
55
+ const writeLock = rest.includes("--write-lock");
56
+ if ((rawSubcommand === "list" || rawSubcommand === "clean-cache") && writeLock) {
57
+ throw new Error(`aiwg versions ${rawSubcommand} cannot write resources.lock.json; use resolve or show with --write-lock`);
58
+ }
59
+ const channelsValue = flagValue(rest, "--channels");
60
+ const channels = channelsValue
61
+ ? channelsValue.split(",").map((channel) => channel.trim()).filter(Boolean)
62
+ : [...DEFAULT_CHANNELS];
63
+ if (channels.length === 0)
64
+ throw new Error("--channels must include at least one channel");
65
+ return {
66
+ subcommand: rawSubcommand,
67
+ selector: positionals[0],
68
+ json: rest.includes("--json") || rest.includes("--format=json"),
69
+ pretty: rest.includes("--pretty"),
70
+ offline: rest.includes("--offline"),
71
+ writeLock,
72
+ dryRun: rest.includes("--dry-run"),
73
+ force: rest.includes("--force"),
74
+ channels,
75
+ };
76
+ }
77
+ function webReleaseOptionsFromEnvironment() {
78
+ const baseUrl = process.env.AIWG_RESOURCE_BASE_URL;
79
+ const cacheRoot = process.env.AIWG_RESOURCE_CACHE_ROOT;
80
+ const trustRootFile = process.env.AIWG_RESOURCE_TRUST_ROOT_FILE;
81
+ const publicKeyPem = trustRootFile === undefined
82
+ ? undefined
83
+ : loadResourceTrustRootFile(path.resolve(trustRootFile));
84
+ return {
85
+ ...(baseUrl === undefined ? {} : { baseUrl }),
86
+ ...(cacheRoot === undefined ? {} : { cacheRoot }),
87
+ ...(publicKeyPem === undefined ? {} : { publicKeyPem }),
88
+ ...(process.env.AIWG_RESOURCE_ALLOW_INSECURE_LOOPBACK_HTTP === "1"
89
+ ? { allowInsecureLoopbackHttp: true }
90
+ : {}),
91
+ };
92
+ }
93
+ function readManifestSummary(release) {
94
+ const bytes = readVerifiedRegularFile(release.releaseManifestPath, {
95
+ label: "verified AIWG resource release manifest",
96
+ maxBytes: MAX_RESOURCE_MANIFEST_BYTES,
97
+ expectedSha256: release.manifestDigest,
98
+ });
99
+ const value = JSON.parse(bytes.toString("utf8"));
100
+ const bundles = Array.isArray(value.bundles) ? value.bundles : [];
101
+ const files = Array.isArray(value.files) ? value.files : [];
102
+ return {
103
+ schemaVersion: value.schemaVersion,
104
+ version: value.version,
105
+ compatibility: value.compatibility,
106
+ source: value.source,
107
+ bundles,
108
+ fileCount: files.length,
109
+ };
110
+ }
111
+ function releaseJson(release, manifest, lockfilePath) {
112
+ return {
113
+ selector: release.selector,
114
+ selectorKind: release.selectorKind,
115
+ version: release.version,
116
+ manifestSha256: release.manifestDigest,
117
+ manifestUrl: release.manifestUrl,
118
+ baseUrl: release.baseUrl,
119
+ cacheDir: release.cacheDir,
120
+ channelSequence: release.channelSequence,
121
+ fortemiCore: {
122
+ manifestSha256: release.fortemiManifestSha256,
123
+ manifestSize: release.fortemiManifestSize,
124
+ exportSha256: release.fortemiExportSha256,
125
+ exportSize: release.fortemiExportSize,
126
+ },
127
+ descriptorCount: release.descriptors.size,
128
+ ...(lockfilePath === undefined ? {} : { lockfile: lockfilePath }),
129
+ ...(manifest === undefined ? {} : { manifest }),
130
+ };
131
+ }
132
+ function printJson(value, pretty) {
133
+ console.log(JSON.stringify(value, null, pretty ? 2 : 0));
134
+ }
135
+ function printReleaseText(release, manifest, lockfilePath) {
136
+ console.log(`selector: ${release.selector} (${release.selectorKind})`);
137
+ console.log(`version: ${release.version}`);
138
+ if (release.channelSequence !== undefined)
139
+ console.log(`channel_sequence: ${release.channelSequence}`);
140
+ console.log(`manifest_sha256: ${release.manifestDigest}`);
141
+ console.log(`manifest_url: ${release.manifestUrl}`);
142
+ console.log(`cache_dir: ${release.cacheDir}`);
143
+ console.log(`fortemi_manifest_sha256: ${release.fortemiManifestSha256}`);
144
+ console.log(`fortemi_export_sha256: ${release.fortemiExportSha256}`);
145
+ console.log(`descriptor_count: ${release.descriptors.size}`);
146
+ if (lockfilePath !== undefined)
147
+ console.log(`lockfile: ${lockfilePath}`);
148
+ if (manifest) {
149
+ console.log(`schema_version: ${String(manifest.schemaVersion)}`);
150
+ console.log(`file_count: ${manifest.fileCount}`);
151
+ console.log(`bundle_count: ${manifest.bundles.length}`);
152
+ }
153
+ }
154
+ export const versionsHandler = {
155
+ id: "versions",
156
+ name: "Resource Versions",
157
+ description: "Browse and resolve signed AIWG web resource releases",
158
+ category: "index",
159
+ aliases: [],
160
+ async execute(ctx) {
161
+ if (ctx.args[0] === "help" || ctx.args[0] === "--help" || ctx.args[0] === "-h") {
162
+ console.log(usage());
163
+ return { exitCode: 0 };
164
+ }
165
+ let parsed;
166
+ try {
167
+ parsed = parseArgs(ctx.args);
168
+ }
169
+ catch (error) {
170
+ return { exitCode: 2, message: error instanceof Error ? error.message : String(error) };
171
+ }
172
+ try {
173
+ const baseOptions = webReleaseOptionsFromEnvironment();
174
+ if (parsed.subcommand === "clean-cache") {
175
+ const result = cleanWebResourceCache(getProjectDir(ctx, ctx.args), {
176
+ cacheRoot: process.env.AIWG_RESOURCE_CACHE_ROOT,
177
+ dryRun: parsed.dryRun,
178
+ force: parsed.force,
179
+ });
180
+ if (parsed.json) {
181
+ printJson(result, parsed.pretty);
182
+ }
183
+ else {
184
+ console.log(`cache_root: ${result.cacheRoot}`);
185
+ console.log(`dry_run: ${result.dryRun}`);
186
+ console.log(`force: ${result.force}`);
187
+ console.log(`locked: ${result.locked.length}`);
188
+ console.log(`preserved: ${result.preserved.length}`);
189
+ console.log(`removed: ${result.removed.length}`);
190
+ console.log(`skipped: ${result.skipped.length}`);
191
+ }
192
+ return { exitCode: 0 };
193
+ }
194
+ if (parsed.subcommand === "list") {
195
+ const resolved = [];
196
+ const unavailable = [];
197
+ for (const channel of parsed.channels) {
198
+ try {
199
+ const release = await resolveWebRelease({
200
+ ...baseOptions,
201
+ selector: channel,
202
+ offline: parsed.offline,
203
+ });
204
+ resolved.push(releaseJson(release));
205
+ }
206
+ catch (error) {
207
+ unavailable.push({
208
+ channel,
209
+ error: error instanceof Error ? error.message : String(error),
210
+ });
211
+ }
212
+ }
213
+ if (parsed.json) {
214
+ printJson({ channels: resolved, unavailable }, parsed.pretty);
215
+ }
216
+ else {
217
+ for (const release of resolved) {
218
+ console.log(`${release.selector}: ${release.version} ${release.manifestSha256}`);
219
+ }
220
+ if (resolved.length === 0)
221
+ console.log("No configured channels resolved.");
222
+ }
223
+ return { exitCode: 0 };
224
+ }
225
+ const release = await resolveWebRelease({
226
+ ...baseOptions,
227
+ selector: parsed.selector,
228
+ offline: parsed.offline,
229
+ });
230
+ const manifest = parsed.subcommand === "show" ? readManifestSummary(release) : undefined;
231
+ const lockfilePath = parsed.writeLock
232
+ ? writeWebResourceLock(getProjectDir(ctx, ctx.args), release).path
233
+ : undefined;
234
+ if (parsed.json) {
235
+ printJson(releaseJson(release, manifest, lockfilePath), parsed.pretty);
236
+ }
237
+ else {
238
+ printReleaseText(release, manifest, lockfilePath);
239
+ }
240
+ return { exitCode: 0 };
241
+ }
242
+ catch (error) {
243
+ return { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
244
+ }
245
+ },
246
+ };
247
+ //# sourceMappingURL=resource-versions.js.map