@aiwg/cli 2026.8.20 → 2026.8.25

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.
@@ -102,6 +102,9 @@ export const artifactVerifyHandler = {
102
102
  description: 'Verify cross-asset DSSE provenance and manage trust roots',
103
103
  category: 'utility',
104
104
  aliases: [],
105
+ async help() {
106
+ return { exitCode: 0, message: usage() };
107
+ },
105
108
  async execute(ctx) {
106
109
  if (ctx.args.includes('--help') || ctx.args.includes('-h') || ctx.args.length === 0)
107
110
  return { exitCode: 0, message: usage() };
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @implements @agentic/code/frameworks/sdlc-complete/rules/self-maintenance.md
12
12
  * @source @src/cli/router.ts
13
- * @issue #482, #557, #694
13
+ * @issue #173, #174, #482, #557, #694
14
14
  */
15
15
  import { promises as fs } from 'fs';
16
16
  import path from 'path';
@@ -151,6 +151,30 @@ export function collectModelDeployArgs(args) {
151
151
  }
152
152
  return forwarded;
153
153
  }
154
+ const REFRESH_HELP = `Usage: aiwg refresh [options]
155
+
156
+ Update AIWG, re-deploy installed frameworks, and run health verification.
157
+
158
+ Options:
159
+ --dry-run Preview changes without updating or deploying
160
+ --quiet Suppress progress output
161
+ --skip-update Skip the installation update
162
+ --packages-only Refresh remote packages only
163
+ --provider <name> Override provider auto-detection
164
+ --channel <name> Select the update channel (stable or main)
165
+ --frameworks <list> Re-deploy a comma-separated installed subset
166
+ --model <name> Override all deployed agent model tiers
167
+ --reasoning-model <name> Override the reasoning model tier
168
+ --coding-model <name> Override the coding model tier
169
+ --efficiency-model <name> Override the efficiency model tier
170
+ --filter <pattern> Limit model deployment by agent name
171
+ --filter-role <role> Limit model deployment by role
172
+ --model-tier <tier> Limit model deployment by tier
173
+ --save Save model overrides to the project
174
+ --save-user Save model overrides to user configuration
175
+ -h, --help Show this help without running refresh
176
+
177
+ Alias: aiwg sync (deprecated)`;
154
178
  /**
155
179
  * Refresh command handler (formerly sync)
156
180
  */
@@ -160,6 +184,9 @@ export const refreshHandler = {
160
184
  description: 'Refresh AIWG to latest version and re-deploy installed frameworks',
161
185
  category: 'maintenance',
162
186
  aliases: ['--refresh', 'sync', '--sync'],
187
+ async help() {
188
+ return { exitCode: 0, message: REFRESH_HELP, rawOutput: true };
189
+ },
163
190
  async execute(ctx) {
164
191
  const dryRun = hasFlag(ctx.args, '--dry-run');
165
192
  const quiet = hasFlag(ctx.args, '--quiet');
@@ -451,6 +478,10 @@ export const refreshHandler = {
451
478
  if (dryRun) {
452
479
  ui.info('Dry run complete — no changes made');
453
480
  }
481
+ else if (updateFailure) {
482
+ ui.warn(`Refresh completed with installation update failure (exit ${updateFailure.exitCode}); ` +
483
+ 're-deployment continued, but AIWG may still be on the previous version.');
484
+ }
454
485
  else {
455
486
  ui.success('Refresh complete');
456
487
  }
@@ -459,7 +490,11 @@ export const refreshHandler = {
459
490
  // Quiet mode: JSON output
460
491
  if (quiet) {
461
492
  const output = JSON.stringify({
462
- status: dryRun ? 'dry-run' : 'refreshed',
493
+ status: dryRun
494
+ ? 'dry-run'
495
+ : updateFailure
496
+ ? 'refreshed-with-update-failure'
497
+ : 'refreshed',
463
498
  provider: detectedProvider,
464
499
  frameworks,
465
500
  skipUpdate,
@@ -1139,34 +1139,40 @@ export const pluginStatusHandler = {
1139
1139
  *
1140
1140
  * Delegates to tools/plugin/package-plugins.mjs
1141
1141
  */
1142
+ function packagePluginHelp() {
1143
+ return {
1144
+ exitCode: 0,
1145
+ message: [
1146
+ "aiwg package-plugin — package a project-local or built-in marketplace wrapper",
1147
+ "",
1148
+ "Usage:",
1149
+ " aiwg package-plugin <name> [--source <path>] [--output <path>] [--provider <name>] [--clean] [--dry-run]",
1150
+ " aiwg package-plugin --plugin <name> [options] # compatibility form",
1151
+ "",
1152
+ "Options:",
1153
+ " --source <path> explicit project-local wrapper source (must stay inside the project)",
1154
+ " --output <path> standalone archive output (default: dist/plugins)",
1155
+ " --provider <name> claude, codex, or all for standalone wrappers; built-ins retain all formats",
1156
+ " --clean clean generated plugin output before packaging",
1157
+ " --dry-run, -n preview without writing",
1158
+ " --help, -h show this help",
1159
+ "",
1160
+ "Project-local wrappers are discovered under .aiwg/plugins and packaged as deterministic archives.",
1161
+ ].join("\n"),
1162
+ };
1163
+ }
1142
1164
  export const packagePluginHandler = {
1143
1165
  id: "package-plugin",
1144
1166
  name: "Package Plugin",
1145
1167
  description: "Package a plugin for distribution",
1146
1168
  category: "plugin",
1147
1169
  aliases: ["-package-plugin", "--package-plugin"],
1170
+ async help() {
1171
+ return packagePluginHelp();
1172
+ },
1148
1173
  async execute(ctx) {
1149
1174
  if (ctx.args.includes("--help") || ctx.args.includes("-h")) {
1150
- return {
1151
- exitCode: 0,
1152
- message: [
1153
- "aiwg package-plugin — package a project-local or built-in marketplace wrapper",
1154
- "",
1155
- "Usage:",
1156
- " aiwg package-plugin <name> [--source <path>] [--output <path>] [--provider <name>] [--clean] [--dry-run]",
1157
- " aiwg package-plugin --plugin <name> [options] # compatibility form",
1158
- "",
1159
- "Options:",
1160
- " --source <path> explicit project-local wrapper source (must stay inside the project)",
1161
- " --output <path> standalone archive output (default: dist/plugins)",
1162
- " --provider <name> claude, codex, or all for standalone wrappers; built-ins retain all formats",
1163
- " --clean clean generated plugin output before packaging",
1164
- " --dry-run, -n preview without writing",
1165
- " --help, -h show this help",
1166
- "",
1167
- "Project-local wrappers are discovered under .aiwg/plugins and packaged as deterministic archives.",
1168
- ].join("\n"),
1169
- };
1175
+ return packagePluginHelp();
1170
1176
  }
1171
1177
  const hasExplicitPlugin = ctx.args.includes("--plugin") || ctx.args.includes("-p");
1172
1178
  const positional = ctx.args[0] && !ctx.args[0].startsWith("-")
@@ -466,6 +466,9 @@ export const doctorHandler = {
466
466
  description: 'Run health diagnostics',
467
467
  category: 'maintenance',
468
468
  aliases: ['-doctor', '--doctor'],
469
+ async help() {
470
+ return { exitCode: 0, message: DOCTOR_HELP, rawOutput: true };
471
+ },
469
472
  async execute(ctx) {
470
473
  if (ctx.args.includes('--help') || ctx.args.includes('-h')) {
471
474
  return { exitCode: 0, message: DOCTOR_HELP, rawOutput: true };
@@ -10,6 +10,7 @@
10
10
  * @tests @test/unit/cli/router.test.ts
11
11
  * @issue #33
12
12
  * @issue #58
13
+ * @issue #174
13
14
  */
14
15
  import { loadRegistry } from '../extensions/loader.js';
15
16
  import { getFrameworkRoot } from '../channel/manager.mjs';
@@ -134,6 +135,32 @@ export async function run(args, options = {}) {
134
135
  ui.error(`No handler found for command: ${commandId}`);
135
136
  process.exit(1);
136
137
  }
138
+ // Help must be intercepted before hooks and the normal handler path. Some
139
+ // commands mutate project or installation state, so passing an unrecognised
140
+ // help flag through to execute() is unsafe (#174).
141
+ if (commandArgs.includes('--help') || commandArgs.includes('-h')) {
142
+ const ctx = await buildContext(commandArgs, args, options);
143
+ if (handler.help) {
144
+ const result = await handler.help(ctx);
145
+ if (result.message) {
146
+ if (result.rawOutput) {
147
+ process.stdout.write(result.message.endsWith('\n') ? result.message : `${result.message}\n`);
148
+ }
149
+ else if (result.exitCode !== 0) {
150
+ ui.error(result.message);
151
+ }
152
+ else {
153
+ ui.info(result.message);
154
+ }
155
+ }
156
+ if (result.exitCode !== 0)
157
+ process.exit(result.exitCode);
158
+ }
159
+ else {
160
+ ui.info(`No detailed help for \`aiwg ${commandId}\`. Run \`aiwg help\` for the command overview.`);
161
+ }
162
+ return;
163
+ }
137
164
  // Build context for handler and hooks
138
165
  const ctx = await buildContext(commandArgs, args, options);
139
166
  // Build hook context
@@ -21,11 +21,20 @@ export function resolveManagerCommand(file, args, options = {}) {
21
21
  return {
22
22
  file: commandInterpreter,
23
23
  args: ['/d', '/s', '/c', command],
24
+ // The payload above already contains cmd.exe-native quoting. Instruct
25
+ // Node not to apply MSVCRT escaping to those embedded quotes (#173).
26
+ windowsVerbatimArguments: true,
24
27
  };
25
28
  }
26
29
 
27
30
  export function executeManagerCommand(file, args, options = {}) {
28
31
  const invocation = resolveManagerCommand(file, args, options);
29
32
  const execute = options.execute ?? execFileSync;
30
- return execute(invocation.file, invocation.args, options.execOptions ?? { stdio: 'inherit' });
33
+ const execOptions = options.execOptions
34
+ ? { ...options.execOptions }
35
+ : { stdio: 'inherit' };
36
+ if (invocation.windowsVerbatimArguments === true) {
37
+ execOptions.windowsVerbatimArguments = true;
38
+ }
39
+ return execute(invocation.file, invocation.args, execOptions);
31
40
  }
@@ -6,7 +6,6 @@
6
6
  * the calling handlers.
7
7
  */
8
8
 
9
- import { execFileSync } from 'node:child_process';
10
9
  import { existsSync, readFileSync } from 'node:fs';
11
10
  import path from 'node:path';
12
11
  import { getPackageRoot, loadConfig } from '../channel/manager.mjs';
@@ -16,7 +15,7 @@ import {
16
15
  loadInstallationIdentity,
17
16
  saveInstallationIdentity,
18
17
  } from '../installation/manager.mjs';
19
- import { resolveManagerCommand } from '../installation/manager-command.mjs';
18
+ import { executeManagerCommand } from '../installation/manager-command.mjs';
20
19
 
21
20
  const VALID_MODES = new Set(['npm', 'web', 'source']);
22
21
 
@@ -149,9 +148,7 @@ export async function updateInstallation(options = {}) {
149
148
  throw new Error('Canonical npm installation has no package-manager executable. Run `aiwg installation adopt --manager <absolute-path-to-npm>`.');
150
149
  }
151
150
  if (!dryRun) {
152
- const invocation = resolveManagerCommand(managerExecutable, command, options);
153
- const execute = options.execute ?? ((file, args) => execFileSync(file, args, { stdio: 'inherit' }));
154
- execute(invocation.file, invocation.args);
151
+ executeManagerCommand(managerExecutable, command, options);
155
152
  if (detected.identity && detected.identityPersistent && options.persistIdentity !== false) {
156
153
  saveInstallationIdentity({ ...detected.identity, channel }, options);
157
154
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.8.20",
3
+ "version": "2026.8.25",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",