@aiwg/cli 2026.7.19 → 2026.7.20

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 (41) hide show
  1. package/README.md +397 -385
  2. package/dist/src/artifacts/cli.js +59 -5
  3. package/dist/src/artifacts/discover-facets.js +15 -0
  4. package/dist/src/artifacts/discovery-eval.js +290 -0
  5. package/dist/src/artifacts/fortemi-core-query-adapter.js +1 -1
  6. package/dist/src/artifacts/fortemi-shard-export.js +1 -1
  7. package/dist/src/artifacts/query-engine.js +10 -6
  8. package/dist/src/cli/handlers/help.js +2 -1
  9. package/dist/src/cli/handlers/index.js +3 -1
  10. package/dist/src/cli/handlers/resource-versions.js +247 -0
  11. package/dist/src/cli/handlers/subcommands.js +55 -3
  12. package/dist/src/cli/handlers/use.js +15 -3
  13. package/dist/src/cli/handlers/utilities.js +27 -26
  14. package/dist/src/config/cli.js +13 -9
  15. package/dist/src/config/project-artifacts-runtime.mjs +68 -0
  16. package/dist/src/config/project-artifacts.js +1 -68
  17. package/dist/src/extensions/commands/definitions.js +36 -2
  18. package/dist/src/extensions/project-local-discovery.js +86 -2
  19. package/dist/src/extensions/project-local-remove.js +52 -56
  20. package/dist/src/extensions/shadow-resolver.js +3 -1
  21. package/dist/src/plugins/standalone-packager.js +143 -0
  22. package/dist/src/resources/cache-cleanup.js +67 -0
  23. package/dist/src/resources/doctor.js +107 -0
  24. package/dist/src/resources/lockfile.js +125 -0
  25. package/dist/src/resources/resolver.js +133 -0
  26. package/dist/src/resources/web-release.d.ts +8 -0
  27. package/dist/src/resources/web-release.js +159 -1
  28. package/dist/src/smiths/context-pipeline/aiwg-md.js +5 -1
  29. package/dist/src/smiths/context-pipeline/claude-hook.js +21 -1
  30. package/dist/src/smiths/context-pipeline/finalization.js +5 -3
  31. package/dist/src/smiths/context-pipeline/generator.js +4 -1
  32. package/dist/src/smiths/context-pipeline/parallelism-section.js +34 -1
  33. package/dist/src/smiths/context-pipeline/workspace-context.js +15 -3
  34. package/dist/src/smiths/mcpsmith/example.js +3 -1
  35. package/dist/src/smiths/mcpsmith/generator.js +3 -1
  36. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +2 -1
  37. package/dist/src/storage/cli.js +3 -2
  38. package/dist/src/storage/subsystem-cli.js +7 -2
  39. package/dist/src/update/notifier.mjs +1 -1
  40. package/dist/src/update/service.mjs +123 -0
  41. package/package.json +3 -2
@@ -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
@@ -1115,9 +1115,44 @@ export const packagePluginHandler = {
1115
1115
  category: "plugin",
1116
1116
  aliases: ["-package-plugin", "--package-plugin"],
1117
1117
  async execute(ctx) {
1118
+ if (ctx.args.includes("--help") || ctx.args.includes("-h")) {
1119
+ return {
1120
+ exitCode: 0,
1121
+ message: [
1122
+ "aiwg package-plugin — package a project-local or built-in marketplace wrapper",
1123
+ "",
1124
+ "Usage:",
1125
+ " aiwg package-plugin <name> [--source <path>] [--output <path>] [--provider <name>] [--clean] [--dry-run]",
1126
+ " aiwg package-plugin --plugin <name> [options] # compatibility form",
1127
+ "",
1128
+ "Options:",
1129
+ " --source <path> explicit project-local wrapper source (must stay inside the project)",
1130
+ " --output <path> standalone archive output (default: dist/plugins)",
1131
+ " --provider <name> claude, codex, or all for standalone wrappers; built-ins retain all formats",
1132
+ " --clean clean generated plugin output before packaging",
1133
+ " --dry-run, -n preview without writing",
1134
+ " --help, -h show this help",
1135
+ "",
1136
+ "Project-local wrappers are discovered under .aiwg/plugins and packaged as deterministic archives.",
1137
+ ].join("\n"),
1138
+ };
1139
+ }
1140
+ const hasExplicitPlugin = ctx.args.includes("--plugin") || ctx.args.includes("-p");
1141
+ const positional = ctx.args[0] && !ctx.args[0].startsWith("-")
1142
+ ? ctx.args[0]
1143
+ : undefined;
1144
+ if (!hasExplicitPlugin && !positional) {
1145
+ return {
1146
+ exitCode: 1,
1147
+ message: "Error: plugin name is required.\n\nRun `aiwg package-plugin --help` for usage.",
1148
+ };
1149
+ }
1150
+ const normalizedArgs = hasExplicitPlugin
1151
+ ? ctx.args
1152
+ : ["--plugin", positional, ...ctx.args.slice(1)];
1118
1153
  const frameworkRoot = await getFrameworkRoot();
1119
1154
  const runner = createScriptRunner(frameworkRoot);
1120
- return runner.run("tools/plugin/package-plugins.mjs", ctx.args, {
1155
+ return runner.run("tools/plugin/package-plugins.mjs", normalizedArgs, {
1121
1156
  cwd: ctx.cwd,
1122
1157
  });
1123
1158
  },
@@ -1134,6 +1169,23 @@ export const packageAllPluginsHandler = {
1134
1169
  category: "plugin",
1135
1170
  aliases: ["-package-all-plugins", "--package-all-plugins"],
1136
1171
  async execute(ctx) {
1172
+ if (ctx.args.includes("--help") || ctx.args.includes("-h")) {
1173
+ return {
1174
+ exitCode: 0,
1175
+ message: [
1176
+ "aiwg package-all-plugins — package every built-in marketplace wrapper",
1177
+ "",
1178
+ "Usage:",
1179
+ " aiwg package-all-plugins [--provider <name>] [--clean] [--dry-run]",
1180
+ "",
1181
+ "Options:",
1182
+ " --provider <name> claude, codex, cursor, factory, openclaw, or all",
1183
+ " --clean clean generated plugin output before packaging",
1184
+ " --dry-run, -n preview without writing",
1185
+ " --help, -h show this help",
1186
+ ].join("\n"),
1187
+ };
1188
+ }
1137
1189
  const frameworkRoot = await getFrameworkRoot();
1138
1190
  const runner = createScriptRunner(frameworkRoot);
1139
1191
  return runner.run("tools/plugin/package-plugins.mjs", ["--all", ...ctx.args], {
@@ -1199,7 +1251,7 @@ export const corpusHandler = {
1199
1251
  * with the project's general-purpose artifact graph indices.
1200
1252
  *
1201
1253
  * aiwg discover "<phrase>" [--limit N] [--type skill,agent,...] [--json]
1202
- * [--resource-source local|web|auto] [--aiwg-version <exact-or-channel>] [--offline]
1254
+ * [--resource-source local|web|auto] [--aiwg-version <version|range|digest|channel>] [--offline]
1203
1255
  */
1204
1256
  export const discoverHandler = {
1205
1257
  id: "discover",
@@ -1254,7 +1306,7 @@ export const featuresHandler = {
1254
1306
  * so consumers don't need to navigate AIWG's storage paths themselves.
1255
1307
  *
1256
1308
  * aiwg show <name> [--type skill,agent,...] [--json] [--first]
1257
- * [--resource-source local|web|auto] [--aiwg-version <exact-or-channel>] [--offline]
1309
+ * [--resource-source local|web|auto] [--aiwg-version <version|range|digest|channel>] [--offline]
1258
1310
  */
1259
1311
  export const showHandler = {
1260
1312
  id: "show",
@@ -775,9 +775,18 @@ async function countBundleSourceArtifacts(bundlePath) {
775
775
  */
776
776
  async function deployOneProjectLocalBundle(opts) {
777
777
  const { bundle, ctx, frameworkRoot, provider, target, dryRun, verbose, quiet, modelArgs } = opts;
778
+ const counts = await countBundleSourceArtifacts(bundle.artifactPath);
779
+ const artifactTotal = counts.agents + counts.commands + counts.skills + counts.rules;
780
+ if (verbose || dryRun) {
781
+ ui.dim(` Artifacts: agents=${counts.agents} commands=${counts.commands} skills=${counts.skills} rules=${counts.rules}`);
782
+ }
783
+ if (artifactTotal === 0) {
784
+ ui.warn(`Project-local ${bundle.type} '${bundle.id}' has no deployable agents, commands, skills, or rules at ${bundle.artifactPath}`);
785
+ return { exitCode: 1, counts };
786
+ }
778
787
  const runner = createScriptRunner(frameworkRoot);
779
788
  const args = [
780
- '--source', bundle.bundlePath,
789
+ '--source', bundle.artifactPath,
781
790
  '--deploy-commands', '--deploy-skills', '--deploy-rules',
782
791
  '--provider', provider,
783
792
  '--target', target,
@@ -814,7 +823,6 @@ async function deployOneProjectLocalBundle(opts) {
814
823
  });
815
824
  // Approximate counts from the bundle's source dirs (deploy-agents.mjs is
816
825
  // idempotent and copies file-for-file from these dirs)
817
- const counts = await countBundleSourceArtifacts(bundle.bundlePath);
818
826
  void ctx;
819
827
  return { exitCode: result.exitCode, counts };
820
828
  }
@@ -903,6 +911,10 @@ async function deployProjectLocalBundles(opts) {
903
911
  if (verbose || dryRun) {
904
912
  const action = dryRun ? '[dry-run] Would deploy' : 'Deploying';
905
913
  console.log(`${action} project-local ${bundle.type} '${bundle.id}' from ${bundle.localPath} → ${provider}`);
914
+ if (bundle.artifactPath !== bundle.bundlePath) {
915
+ const payloadDisplay = path.relative(projectDir, bundle.artifactPath) || '.';
916
+ ui.dim(` Resolved plugin payload: ${payloadDisplay}`);
917
+ }
906
918
  }
907
919
  const result = await deployOneProjectLocalBundle({
908
920
  bundle, ctx, frameworkRoot, provider, target, dryRun, verbose, quiet, modelArgs,
@@ -941,7 +953,7 @@ async function deployProjectLocalBundles(opts) {
941
953
  const mHash = await hashManifest(manifestAbsPath);
942
954
  // #1037 — record per-artifact source hashes so `aiwg remove` can
943
955
  // detect pristine vs mutated vs replaced deployed files.
944
- const artifactHashes = await hashBundleArtifacts(bundle.bundlePath);
956
+ const artifactHashes = await hashBundleArtifacts(bundle.artifactPath);
945
957
  const updated = updateInstalled(config, bundle.id, provider, result.counts, {
946
958
  version: bundle.manifest.version,
947
959
  source: 'project-local',
@@ -14,7 +14,7 @@ import fsp from 'fs/promises';
14
14
  import path from 'path';
15
15
  import { createScriptRunner } from './script-runner.js';
16
16
  import { getFrameworkRoot } from '../../channel/manager.mjs';
17
- import { forceUpdateCheck } from '../../update/checker.mjs';
17
+ import { updateInstallation } from '../../update/service.mjs';
18
18
  import { useHandler as useFrameworkHandler } from './use.js';
19
19
  import { projectAiwgPath } from '../../config/project-artifacts.js';
20
20
  import { checkCollisions, } from '../../smiths/skillsmith/collision-detector.js';
@@ -536,6 +536,21 @@ export const doctorHandler = {
536
536
  catch {
537
537
  // Project-local section is non-fatal for doctor
538
538
  }
539
+ // Web-backed resource lock/cache diagnostics (#1850). Report lock source
540
+ // mode, cold cache, and digest drift without requiring web mode to be in use.
541
+ try {
542
+ const { buildWebResourceDoctorSection } = await import('../../resources/doctor.js');
543
+ const section = buildWebResourceDoctorSection(ctx.cwd || process.cwd(), {
544
+ cacheRoot: process.env.AIWG_RESOURCE_CACHE_ROOT,
545
+ });
546
+ if (section.output)
547
+ console.log(section.output);
548
+ if (section.hasFailures)
549
+ return { exitCode: 1, message: '' };
550
+ }
551
+ catch (error) {
552
+ console.log(`\n── Web resource cache ──\n ⚠ unable to audit: ${error instanceof Error ? error.message : String(error)}`);
553
+ }
539
554
  // Canonical workspace-context graph diagnostics (#1811). Legacy projects
540
555
  // remain valid; drift, loops, conflicts, and possible credentials fail.
541
556
  try {
@@ -578,7 +593,7 @@ export const updateHandler = {
578
593
  name: 'Update',
579
594
  description: 'Update AIWG and re-deploy installed frameworks',
580
595
  category: 'maintenance',
581
- aliases: ['-update', '--update'],
596
+ aliases: ['-update', '--update', 'upgrade'],
582
597
  async execute(ctx) {
583
598
  const args = ctx.args;
584
599
  const deployAll = args.includes('--all');
@@ -592,8 +607,12 @@ export const updateHandler = {
592
607
  // Step 1: Check for package updates (unless --skip-check)
593
608
  if (!skipCheck) {
594
609
  try {
595
- console.log('Checking for AIWG updates...\n');
596
- await forceUpdateCheck();
610
+ console.log('Updating AIWG installation...\n');
611
+ const update = await updateInstallation({
612
+ dryRun,
613
+ offline: args.includes('--offline'),
614
+ });
615
+ console.log(`${update.message}\n`);
597
616
  }
598
617
  catch (error) {
599
618
  console.error(`Warning: Update check failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -626,30 +645,12 @@ export const updateHandler = {
626
645
  console.log(' aiwg use all');
627
646
  return { exitCode: 0 };
628
647
  }
629
- // Map registry IDs to framework use-names
630
- const installedFrameworks = [];
631
- const unmapped = [];
632
- for (const fw of registry.frameworks) {
633
- const useName = REGISTRY_ID_TO_USE_NAME[fw.id];
634
- if (useName) {
635
- installedFrameworks.push(useName);
636
- }
637
- else {
638
- unmapped.push(fw.id);
639
- }
640
- }
641
- if (installedFrameworks.length === 0) {
642
- console.log('No recognized frameworks in registry');
643
- if (unmapped.length > 0) {
644
- console.log(`Unrecognized entries: ${unmapped.join(', ')}`);
645
- }
646
- return { exitCode: 0 };
647
- }
648
+ // Canonical framework IDs need their historical public aliases. Add-ons,
649
+ // extensions, and project-local bundles are already accepted by `aiwg use`,
650
+ // so preserve their registry IDs instead of silently skipping them.
651
+ const installedFrameworks = registry.frameworks.map(item => REGISTRY_ID_TO_USE_NAME[item.id] ?? item.id);
648
652
  // Report what will be updated
649
653
  console.log(`Installed frameworks: ${installedFrameworks.join(', ')}`);
650
- if (unmapped.length > 0) {
651
- console.log(`Skipping unrecognized: ${unmapped.join(', ')}`);
652
- }
653
654
  console.log('');
654
655
  if (dryRun) {
655
656
  console.log('Dry run: Would re-deploy the following frameworks:');
@@ -21,6 +21,7 @@ import { fileURLToPath } from 'url';
21
21
  import path from 'path';
22
22
  import { UserConfig } from './user-config.js';
23
23
  import { AiwgError, EXIT_CODES } from '../cli/errors.js';
24
+ import { projectAiwgPath, resolveProjectAiwgDir } from './project-artifacts.js';
24
25
  const _scriptDir = path.dirname(fileURLToPath(import.meta.url));
25
26
  /**
26
27
  * Main CLI entry point for `aiwg config <subcommand> [args]`
@@ -182,7 +183,7 @@ async function projectConfigGet(key, args) {
182
183
  if (!cfg) {
183
184
  throw new AiwgError({
184
185
  code: 'ERR_NO_PROJECT_CONFIG',
185
- message: 'No .aiwg/aiwg.config in this project.',
186
+ message: 'No project AIWG config found at the resolved artifact root.',
186
187
  hint: 'Run `aiwg init`, then ask your AIWG agent to set up repo/tracker/delivery policy.',
187
188
  exitCode: EXIT_CODES.CONFIG,
188
189
  });
@@ -362,7 +363,7 @@ async function handleProjectValidate(args) {
362
363
  if (!cfg) {
363
364
  throw new AiwgError({
364
365
  code: 'ERR_NO_PROJECT_CONFIG',
365
- message: 'No .aiwg/aiwg.config in this project.',
366
+ message: 'No project AIWG config found at the resolved artifact root.',
366
367
  hint: 'Run `aiwg init`, then configure project policy.',
367
368
  exitCode: EXIT_CODES.CONFIG,
368
369
  });
@@ -403,7 +404,8 @@ async function handleProjectValidate(args) {
403
404
  ? validateIssueLabels(cfg.issues)
404
405
  : resolveIssueLabels(undefined, 'local').diagnostics;
405
406
  const diagnostics = [...indexErrors, ...externalLinkErrors, ...labelDiagnostics];
406
- console.log(`Project config: ${projectDir}/.aiwg/aiwg.config\n`);
407
+ console.log(`Project config: ${projectAiwgPath(projectDir, 'aiwg.config')}`);
408
+ console.log(`Artifact root: ${resolveProjectAiwgDir(projectDir)}\n`);
407
409
  if (diagnostics.length === 0) {
408
410
  console.log('✓ Project config valid');
409
411
  return;
@@ -464,7 +466,7 @@ async function projectConfigReset(key, args) {
464
466
  if (!cfg) {
465
467
  throw new AiwgError({
466
468
  code: 'ERR_NO_PROJECT_CONFIG',
467
- message: 'No .aiwg/aiwg.config in this project.',
469
+ message: 'No project AIWG config found at the resolved artifact root.',
468
470
  hint: 'Run `aiwg init`, then ask your AIWG agent to establish project policy.',
469
471
  exitCode: EXIT_CODES.CONFIG,
470
472
  });
@@ -553,7 +555,7 @@ For project-level config: aiwg config show --project [--json]
553
555
  if (!cfg) {
554
556
  throw new AiwgError({
555
557
  code: 'ERR_NO_PROJECT_CONFIG',
556
- message: 'No .aiwg/aiwg.config in this project.',
558
+ message: 'No project AIWG config found at the resolved artifact root.',
557
559
  hint: 'Run `aiwg init`, then ask your AIWG agent to set up repo/tracker/delivery policy.',
558
560
  exitCode: EXIT_CODES.CONFIG,
559
561
  });
@@ -593,7 +595,8 @@ For project-level config: aiwg config show --project [--json]
593
595
  return;
594
596
  }
595
597
  // Human-readable view
596
- console.log(`Project config: ${projectDir}/.aiwg/aiwg.config\n`);
598
+ console.log(`Project config: ${projectAiwgPath(projectDir, 'aiwg.config')}`);
599
+ console.log(`Artifact root: ${resolveProjectAiwgDir(projectDir)}\n`);
597
600
  console.log(`Schema version: ${cfg.version}`);
598
601
  console.log(`Providers: ${cfg.providers.join(', ') || '(none)'}`);
599
602
  console.log('');
@@ -663,13 +666,14 @@ function printUsage() {
663
666
 
664
667
  Subcommands:
665
668
  get <key> Read a user config value
666
- get --project <key> Read a project config value (.aiwg/aiwg.config)
669
+ get --project <key> Read a project config value (default .aiwg/aiwg.config;
670
+ AIWG_ARTIFACTS_PATH may override the artifact root)
667
671
  set <key> <value> Write a user config value
668
672
  set --project <key> <value> Write a project config value (validates enums)
669
673
  list Show all user config
670
- show --project Show resolved project config (.aiwg/aiwg.config)
674
+ show --project Show resolved project config and artifact root
671
675
  validate Validate user config files
672
- validate --project Validate .aiwg/aiwg.config taxonomy/index semantics
676
+ validate --project Validate resolved project config taxonomy/index semantics
673
677
  [--provider gitea|github|local] [--available-label NAME ...]
674
678
  reset [<key>] Reset key or all config to defaults
675
679
  path Print config directory path
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Runtime implementation of project AIWG artifact directory resolution.
3
+ *
4
+ * Kept as ESM JavaScript so direct `.mjs` tools and compiled TypeScript
5
+ * consumers share exactly one resolver implementation.
6
+ */
7
+
8
+ import { homedir } from 'os';
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { isAbsolute, join, resolve } from 'path';
11
+
12
+ export const DEFAULT_PROJECT_AIWG_DIR = '.aiwg';
13
+ export const AIWG_ARTIFACTS_PATH_ENV = 'AIWG_ARTIFACTS_PATH';
14
+ export const PROJECT_AIWG_LOCATION_FILE = '.aiwg-location';
15
+
16
+ const ARTIFACT_PATH_ENV_ALIASES = [
17
+ AIWG_ARTIFACTS_PATH_ENV,
18
+ 'AIWG_PROJECT_ARTIFACTS_PATH',
19
+ 'AIWG_PROJECT_AIWG_DIR',
20
+ ];
21
+
22
+ export function expandProjectArtifactPath(pathValue, projectDir) {
23
+ const trimmed = pathValue.trim();
24
+ if (trimmed === '~') return homedir();
25
+ if (trimmed.startsWith('~/')) return resolve(homedir(), trimmed.slice(2));
26
+ if (isAbsolute(trimmed)) return trimmed;
27
+ return resolve(projectDir, trimmed);
28
+ }
29
+
30
+ export function parseProjectArtifactLocation(contents) {
31
+ for (const rawLine of contents.split(/\r?\n/)) {
32
+ let line = rawLine.trim();
33
+ if (line.length === 0 || line.startsWith('#')) continue;
34
+ if (line.startsWith('export ')) line = line.slice('export '.length).trim();
35
+ const assignment = line.match(/^AIWG_ARTIFACTS_PATH\s*=\s*(.+)$/);
36
+ if (assignment) line = assignment[1].trim();
37
+ if (
38
+ (line.startsWith('"') && line.endsWith('"')) ||
39
+ (line.startsWith("'") && line.endsWith("'"))
40
+ ) {
41
+ line = line.slice(1, -1);
42
+ }
43
+ return line.length > 0 ? line : null;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ export function readProjectArtifactLocation(projectDir) {
49
+ const pointerPath = resolve(projectDir, PROJECT_AIWG_LOCATION_FILE);
50
+ if (!existsSync(pointerPath)) return null;
51
+ return parseProjectArtifactLocation(readFileSync(pointerPath, 'utf-8'));
52
+ }
53
+
54
+ export function resolveProjectAiwgDir(projectDir, env = process.env) {
55
+ for (const key of ARTIFACT_PATH_ENV_ALIASES) {
56
+ const value = env[key];
57
+ if (typeof value === 'string' && value.trim().length > 0) {
58
+ return expandProjectArtifactPath(value, projectDir);
59
+ }
60
+ }
61
+ const configuredLocation = readProjectArtifactLocation(projectDir);
62
+ if (configuredLocation) return expandProjectArtifactPath(configuredLocation, projectDir);
63
+ return resolve(projectDir, DEFAULT_PROJECT_AIWG_DIR);
64
+ }
65
+
66
+ export function projectAiwgPath(projectDir, ...segments) {
67
+ return join(resolveProjectAiwgDir(projectDir), ...segments);
68
+ }