@aiwg/cli 2026.10.7 → 2026.10.9

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 (33) hide show
  1. package/dist/src/artifacts/cli.js +9 -5
  2. package/dist/src/auth/config.js +3 -8
  3. package/dist/src/auth/resource-credentials.js +35 -2
  4. package/dist/src/cli/handlers/cost-report.js +7 -1
  5. package/dist/src/cli/handlers/decision.js +33 -8
  6. package/dist/src/cli/handlers/diagnose.js +13 -0
  7. package/dist/src/cli/handlers/local-executor.js +33 -6
  8. package/dist/src/cli/handlers/resource-versions.js +8 -5
  9. package/dist/src/cli/handlers/script-runner.js +13 -1
  10. package/dist/src/cli/handlers/use.js +18 -3
  11. package/dist/src/cost/fleet-report.js +20 -22
  12. package/dist/src/decision/driver.js +2 -2
  13. package/dist/src/decision/jev-credentials.js +18 -5
  14. package/dist/src/decision/ledger.js +230 -34
  15. package/dist/src/mcp/cli.mjs +105 -19
  16. package/dist/src/mcp/credentials.d.mts +4 -0
  17. package/dist/src/mcp/credentials.mjs +44 -3
  18. package/dist/src/mcp/grok-build-config.mjs +2 -0
  19. package/dist/src/mcp/omp-config.mjs +2 -0
  20. package/dist/src/mcp/registry.js +19 -15
  21. package/dist/src/mcp/registry.mjs +2 -18
  22. package/dist/src/mcp/tools/agentic-sandbox.mjs +46 -28
  23. package/dist/src/models/grok-build-models.js +20 -0
  24. package/dist/src/providers/provider-definitions.js +2 -2
  25. package/dist/src/resources/web-release.js +76 -65
  26. package/dist/src/serve/shared-host-scheduler.js +4 -1
  27. package/dist/src/storage/backends/fortemi.js +112 -50
  28. package/dist/src/storage/backends/logseq.js +16 -3
  29. package/dist/src/storage/backends/postgres.js +74 -33
  30. package/dist/src/storage/backends/postgrest.js +37 -13
  31. package/dist/src/storage/config.js +6 -1
  32. package/dist/src/uhp/config.js +4 -0
  33. package/package.json +1 -1
@@ -24,9 +24,9 @@ import { resolveIndexWorkers } from './index-preparation.js';
24
24
  import { SUPPORTED_VIEWS } from './corpus-views/renderers.js';
25
25
  import { parseResourceSelector, readVerifiedRegularFile, } from '../resources/web-release.js';
26
26
  import { findPackageRoot } from '../cli/find-package-root.js';
27
- import { createResourceCredentialProvider } from '../auth/resource-credentials.js';
27
+ import { createResourceCredentialProvider, prepareResourceCredentials } from '../auth/resource-credentials.js';
28
28
  const MAX_RESOURCE_TRUST_ROOT_BYTES = 64 * 1024;
29
- function webReleaseOptionsFromEnvironment() {
29
+ function webReleaseOptionsFromEnvironment(credentialProvider = createResourceCredentialProvider(process.env)) {
30
30
  const baseUrl = process.env.AIWG_RESOURCE_BASE_URL;
31
31
  const cacheRoot = process.env.AIWG_RESOURCE_CACHE_ROOT;
32
32
  const trustRootFile = process.env.AIWG_RESOURCE_TRUST_ROOT_FILE;
@@ -44,7 +44,7 @@ function webReleaseOptionsFromEnvironment() {
44
44
  }
45
45
  }
46
46
  return {
47
- credentialProvider: createResourceCredentialProvider(process.env),
47
+ credentialProvider,
48
48
  ...(baseUrl === undefined ? {} : { baseUrl }),
49
49
  ...(cacheRoot === undefined ? {} : { cacheRoot }),
50
50
  ...(publicKeyPem === undefined ? {} : { publicKeyPem }),
@@ -1465,6 +1465,8 @@ const DISCOVER_VALUE_FLAGS = new Set([
1465
1465
  * @implements #1214
1466
1466
  */
1467
1467
  async function handleDiscover(args) {
1468
+ const credentials = await prepareResourceCredentials(args);
1469
+ args = credentials.args;
1468
1470
  const { discoverCapability } = await import('./query-engine.js');
1469
1471
  const cwd = process.cwd();
1470
1472
  // Parse the positional phrase. Flags may appear before or after it — the
@@ -1526,7 +1528,7 @@ async function handleDiscover(args) {
1526
1528
  const offline = flags.includes('--offline');
1527
1529
  const webReleaseOptions = resourceSource === 'local'
1528
1530
  ? undefined
1529
- : webReleaseOptionsFromEnvironment();
1531
+ : webReleaseOptionsFromEnvironment(credentials.credentialProvider);
1530
1532
  await discoverCapability(cwd, {
1531
1533
  phrase,
1532
1534
  typeFilter,
@@ -1578,6 +1580,8 @@ function showFlagValues(flags, name) {
1578
1580
  return values;
1579
1581
  }
1580
1582
  async function handleShow(args) {
1583
+ const credentials = await prepareResourceCredentials(args);
1584
+ args = credentials.args;
1581
1585
  const { showArtifact, showMetadata } = await import('./query-engine.js');
1582
1586
  const cwd = process.cwd();
1583
1587
  const positional = [];
@@ -1676,7 +1680,7 @@ async function handleShow(args) {
1676
1680
  const offline = flags.includes('--offline');
1677
1681
  const webReleaseOptions = resourceSource === 'local'
1678
1682
  ? undefined
1679
- : webReleaseOptionsFromEnvironment();
1683
+ : webReleaseOptionsFromEnvironment(credentials.credentialProvider);
1680
1684
  const params = {
1681
1685
  name,
1682
1686
  boundRuleBodies: flags.includes('--bound-rule-bodies'),
@@ -1,5 +1,5 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
2
+ import { readPrivateValueFile } from "./sensitive-input.js";
3
3
  export const DEFAULT_AUTH_BASE_URL = "https://releases.aiwg.io";
4
4
  export const DEFAULT_AUTH_CLIENT_ID = "aiwg-cli";
5
5
  export const DEFAULT_AUTH_SCOPES = ["releases:read", "profile:read"];
@@ -24,14 +24,9 @@ export function authConfigFromEnvironment(env = process.env) {
24
24
  throw new Error("AIWG auth scopes are invalid");
25
25
  return { baseUrl, clientId, scopes, requestTimeoutMs: 30_000 };
26
26
  }
27
- export function explicitResourceToken(env = process.env) {
27
+ export async function explicitResourceToken(env = process.env) {
28
28
  if (env.AIWG_RESOURCE_TOKEN_FILE) {
29
- const pathname = path.resolve(env.AIWG_RESOURCE_TOKEN_FILE);
30
- const stat = fs.lstatSync(pathname);
31
- if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0) {
32
- throw new Error("AIWG_RESOURCE_TOKEN_FILE must be a non-symlink regular file with mode 0600");
33
- }
34
- return fs.readFileSync(pathname, "utf8").trim() || null;
29
+ return await readPrivateValueFile(path.resolve(env.AIWG_RESOURCE_TOKEN_FILE), "AIWG_RESOURCE_TOKEN_FILE") || null;
35
30
  }
36
31
  return env.AIWG_RESOURCE_TOKEN?.trim() || null;
37
32
  }
@@ -1,12 +1,19 @@
1
1
  import { explicitResourceToken } from "./config.js";
2
2
  import { createCredentialStore } from "./credential-store.js";
3
+ import { resolveSensitiveInput, sensitiveInputOptions } from "./sensitive-input.js";
3
4
  /**
4
5
  * Resolve credentials for protected release downloads. Explicit compatibility
5
6
  * inputs take precedence over interactive-login credentials.
6
7
  */
7
- export function createResourceCredentialProvider(env = process.env, store) {
8
+ export function createResourceCredentialProvider(env = process.env, store, source = {}, dependencies = {}) {
9
+ const hasSource = source.interactive || source.stdin || source.file !== undefined || source.store !== undefined || source.env !== undefined;
10
+ // A command may fetch several manifests and bodies. Never consume stdin or
11
+ // ask for the same explicit credential more than once, including on failure.
12
+ let resolvedSource;
8
13
  return async () => {
9
- const explicit = explicitResourceToken(env);
14
+ if (hasSource)
15
+ return resolvedSource ??= resolveSensitiveInput({ ...source, prefix: 'resource-token' }, { ...dependencies, environment: env });
16
+ const explicit = await explicitResourceToken(env);
10
17
  if (explicit)
11
18
  return explicit;
12
19
  const selected = store ?? createCredentialStore();
@@ -22,4 +29,30 @@ export function createResourceCredentialProvider(env = process.env, store) {
22
29
  }
23
30
  };
24
31
  }
32
+ /** Parse once before command-specific positional parsing or helper forwarding. */
33
+ export async function prepareResourceCredentials(args, env = process.env, store, dependencies = {}) {
34
+ const source = sensitiveInputOptions(args, 'resource-token');
35
+ const remaining = [];
36
+ const valueFlags = ['--resource-token-file', '--resource-token-store', '--resource-token-env'];
37
+ for (let index = 0; index < args.length; index++) {
38
+ const token = args[index];
39
+ if (token === '--') {
40
+ remaining.push(...args.slice(index));
41
+ break;
42
+ }
43
+ if (valueFlags.includes(token)) {
44
+ index++;
45
+ continue;
46
+ }
47
+ if (valueFlags.some(flag => token.startsWith(`${flag}=`)) ||
48
+ token === '--resource-token-stdin' || token === '--resource-token-interactive')
49
+ continue;
50
+ remaining.push(token);
51
+ }
52
+ const credentialProvider = createResourceCredentialProvider(env, store, source, dependencies);
53
+ if (source.interactive || source.stdin || source.file !== undefined || source.store !== undefined || source.env !== undefined) {
54
+ await credentialProvider();
55
+ }
56
+ return { args: remaining, credentialProvider, omitEnv: ['AIWG_RESOURCE_TOKEN', ...(source.env ? [source.env] : [])] };
57
+ }
25
58
  //# sourceMappingURL=resource-credentials.js.map
@@ -1,6 +1,7 @@
1
1
  /** OpenRouter fleet cost-report CLI handler. @issue #1187 */
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
+ import { sensitiveInputOptions } from '../../auth/sensitive-input.js';
4
5
  import { FleetConfigMissingError, formatFleetSpendReport, generateFleetSpendReport, } from '../../cost/fleet-report.js';
5
6
  function option(args, name) {
6
7
  const index = args.indexOf(name);
@@ -9,10 +10,11 @@ function option(args, name) {
9
10
  }
10
11
  function usage() {
11
12
  return [
12
- 'Usage: aiwg cost-report (--fleet | --key <key_ref>) [--source openrouter] [--config <fleet.yaml>] [--json]',
13
+ 'Usage: aiwg cost-report (--fleet | --key <key_ref> [--key-store <name>]) [--source openrouter] [--config <fleet.yaml>] [--json]',
13
14
  '',
14
15
  'Reads bot-to-key references from ~/.config/aiwg/fleet.yaml and credentials from',
15
16
  '~/.config/aiwg/keys/<key_ref> or AIWG_OPENROUTER_KEY_<KEY_REF>.',
17
+ 'Use --key-store <name> for one key, or per-entry key_store references in fleet YAML.',
16
18
  '',
17
19
  'AIWG observes and correlates spend; OpenRouter enforces all key limits and caps.',
18
20
  ].join('\n');
@@ -40,6 +42,9 @@ export const costReportHandler = {
40
42
  ? path.resolve(ctx.cwd, configuredPath)
41
43
  : path.join(os.homedir(), '.config', 'aiwg', 'fleet.yaml');
42
44
  try {
45
+ const keyStore = sensitiveInputOptions(ctx.args, 'key').store;
46
+ if (keyStore !== undefined && !keyRef)
47
+ return { exitCode: 2, message: '--key-store requires --key <key_ref>; use per-entry key_store references for --fleet.' };
43
48
  const capValue = option(ctx.args, '--monthly-cap');
44
49
  const cap = capValue === undefined ? 0 : Number(capValue);
45
50
  if (!Number.isFinite(cap) || cap < 0)
@@ -53,6 +58,7 @@ export const costReportHandler = {
53
58
  bot: option(ctx.args, '--bot') ?? keyRef,
54
59
  machine: option(ctx.args, '--machine') ?? os.hostname(),
55
60
  key_ref: keyRef,
61
+ ...(keyStore !== undefined ? { key_store: keyStore } : {}),
56
62
  monthly_cap: cap,
57
63
  }],
58
64
  } : {}),
@@ -1,12 +1,12 @@
1
1
  import { resolveSensitiveInput, sensitiveInputOptions } from '../../auth/sensitive-input.js';
2
2
  import { decisionPricing } from '../../decision/pricing.js';
3
- import { ATTRIBUTION_FIELD_PATTERN, DecisionAskUsageError, decisionCapabilities, DecisionLedgerSinceError, decisionLedgerHistory, decisionLedgerStats, evaluateRequestPath, listPatterns, livePlan, MAX_ATTRIBUTION_FIELD_LENGTH, materializeSyntheticClassificationSetup, parseDecisionLedgerSince, readDecisionContextFile, runDecisionOffloadCheckpoint, runOfflinePattern, setupJev, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
3
+ import { ATTRIBUTION_FIELD_PATTERN, DecisionAskUsageError, decisionCapabilities, DecisionLedgerSinceError, decisionLedgerHistory, decisionLedgerStats, rotateDecisionLedger, parseDecisionLedgerRetention, evaluateRequestPath, listPatterns, livePlan, MAX_ATTRIBUTION_FIELD_LENGTH, materializeSyntheticClassificationSetup, parseDecisionLedgerSince, readDecisionContextFile, runDecisionOffloadCheckpoint, runOfflinePattern, setupJev, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
4
4
  function jsonResult(value, exitCode = 0) {
5
5
  return { exitCode, message: JSON.stringify(value, null, 2), rawOutput: true };
6
6
  }
7
7
  function usage() {
8
8
  return [
9
- 'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup|history|stats|pricing> [options]',
9
+ 'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup|history|stats|rotate|pricing> [options]',
10
10
  ' aiwg decision ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--context <text> | --context-file <path> | --context-stdin] [--threshold 0.8] [--timeout-ms 15000] [--passes N] [--escalation-passes N] [--provider <id>] [--session <id>] [--caller <tag>] [--record-text] [--json]',
11
11
  '',
12
12
  'Commands:',
@@ -20,9 +20,10 @@ function usage() {
20
20
  ' setup synthetic-classification [--output-dir <dir>]',
21
21
  ' setup jev [--token-interactive | --token-stdin | --token-file <path> | --token-store <name> | --token-env <name>] [--region <r>] [--endpoint <url>] [--verify] [--remove]',
22
22
  ' ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--provider <id>] [--session <id>] [--caller <tag>] [--record-text]',
23
- ' history [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--json]',
23
+ ' history [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--include-archives] [--json]',
24
24
  ' pricing [--refresh --model jev-X.Y.Z] [--dry-run] [--offline | --allow-snapshot] [--json]',
25
- ' stats [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--group-by provider|session|kind|caller|passes|escalation] [--json]',
25
+ ' stats [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--group-by provider|session|kind|caller|passes|escalation] [--include-archives] [--json]',
26
+ ' rotate [--keep-last <Nd|N> | --all] [--to <path>] [--json]',
26
27
  '',
27
28
  'Notes:',
28
29
  ' Every COMPLETED `ask` call (CLI and MCP) appends one record to the decision',
@@ -42,9 +43,13 @@ function usage() {
42
43
  ` --session and --caller must match ${ATTRIBUTION_FIELD_PATTERN.source} (<= ${MAX_ATTRIBUTION_FIELD_LENGTH} characters).`,
43
44
  ' `history`/`stats` accept `--caller <tag>` (exact), `--caller \'<prefix>*\'` (prefix; the only wildcard), or `--no-caller` (records with no tag; not combinable with `--caller`).',
44
45
  ' `history`/`stats` reject unknown flags and positional arguments with exit 2.',
45
- ' The ledger has no built-in retention: it grows without bound and the',
46
- ' operator may truncate or delete it at any time; readers tolerate a missing',
47
- ' file. The ledger trusts only the local filesystem under the project',
46
+ ' `rotate` keeps the last 90 days by default; --keep-last accepts days or a',
47
+ ' positive count. Rotating everything requires --all. Malformed lines stay',
48
+ ' inline unless --all is used. Archives are private .jsonl files beneath',
49
+ ' archive/decision/ in the resolved artifact root; --to must stay there.',
50
+ ' `history`/`stats` optionally read archives with --include-archives, offline.',
51
+ ' Appenders, rotation and readers share one cross-process lock.',
52
+ ' The ledger trusts only the local filesystem under the project',
48
53
  ' artifact root, owned by the same user running aiwg — it is not a defense',
49
54
  ' against a hostile process already running as that user.',
50
55
  ].join('\n');
@@ -207,7 +212,7 @@ function parseGroupBy(value) {
207
212
  return value;
208
213
  }
209
214
  const HISTORY_VALUE_FLAGS = ['--since', '--provider', '--session', '--kind', '--status', '--caller'];
210
- const HISTORY_BOOL_FLAGS = ['--json', '--no-caller'];
215
+ const HISTORY_BOOL_FLAGS = ['--json', '--no-caller', '--include-archives'];
211
216
  const STATS_VALUE_FLAGS = [...HISTORY_VALUE_FLAGS, '--group-by'];
212
217
  const STATS_BOOL_FLAGS = HISTORY_BOOL_FLAGS;
213
218
  /**
@@ -269,6 +274,7 @@ function parseLedgerFilters(args) {
269
274
  kind: parseKindFilter(takeOption(args, '--kind')),
270
275
  status: parseStatusFilter(takeOption(args, '--status')),
271
276
  ...parseCallerFilter(args),
277
+ ...(args.includes('--include-archives') ? { includeArchives: true } : {}),
272
278
  };
273
279
  }
274
280
  /** One header line stating the caller filter, shown only when one is set. */
@@ -323,6 +329,23 @@ async function decisionStats(ctx) {
323
329
  ]);
324
330
  return { exitCode: 0, message: [...header, ...lines].join('\n'), rawOutput: true };
325
331
  }
332
+ async function decisionRotate(ctx) {
333
+ const args = ctx.args.slice(1);
334
+ assertLedgerArgs(args, ['--keep-last', '--to'], ['--all', '--json']);
335
+ const keepLast = args.includes('--keep-last') ? takeRequiredOption(args, '--keep-last') : undefined;
336
+ const all = args.includes('--all');
337
+ try {
338
+ parseDecisionLedgerRetention(keepLast, all);
339
+ }
340
+ catch (error) {
341
+ throw new DecisionAskUsageError('invalid-retention', error instanceof Error ? error.message : String(error));
342
+ }
343
+ const result = await rotateDecisionLedger({ projectRoot: ctx.cwd, keepLast, all,
344
+ to: args.includes('--to') ? takeRequiredOption(args, '--to') : undefined });
345
+ if (args.includes('--json'))
346
+ return jsonResult(result);
347
+ return { exitCode: 0, rawOutput: true, message: `Archived ${result.archived} line(s)${result.archivePath ? ` to ${result.archivePath}` : ''}; retained ${result.retained} line(s) inline (${result.malformed} malformed).` };
348
+ }
326
349
  async function executeDecision(ctx) {
327
350
  const [command, subcommand, ...rest] = ctx.args;
328
351
  const options = { cwd: ctx.cwd, frameworkRoot: ctx.frameworkRoot, env: process.env };
@@ -335,6 +358,8 @@ async function executeDecision(ctx) {
335
358
  return await decisionAsk(ctx);
336
359
  if (command === 'history')
337
360
  return await decisionHistory(ctx);
361
+ if (command === 'rotate')
362
+ return await decisionRotate(ctx);
338
363
  if (command === 'pricing') {
339
364
  const args = ctx.args.slice(1);
340
365
  assertLedgerArgs(args, ['--model'], ['--refresh', '--dry-run', '--offline', '--allow-snapshot', '--json']);
@@ -40,9 +40,22 @@ export const diagnoseHandler = {
40
40
  description: 'Produce a shareable support bundle (logs + env + config) for bug reports',
41
41
  category: 'maintenance',
42
42
  aliases: [],
43
+ async help() {
44
+ return { exitCode: 0, message: [
45
+ 'Usage: aiwg diagnose [--stdout] [--include-secrets]',
46
+ '',
47
+ '--stdout Print the bundle as JSON instead of writing a tarball.',
48
+ '--include-secrets Disable sanitization. Output contains secrets; review it before sharing.',
49
+ ].join('\n') };
50
+ },
43
51
  async execute(ctx) {
52
+ if (ctx.args.includes('--help') || ctx.args.includes('-h'))
53
+ return diagnoseHandler.help(ctx);
44
54
  const stdoutMode = ctx.args.includes('--stdout');
45
55
  const includeSecrets = ctx.args.includes('--include-secrets');
56
+ if (includeSecrets) {
57
+ process.stderr.write('Warning: --include-secrets output contains secrets. Review it before sharing.\n');
58
+ }
46
59
  const cwd = getProjectDir(ctx, ctx.args);
47
60
  const log = getLogger('cli:diagnose');
48
61
  const span = log.span('diagnose', { stdoutMode, includeSecrets });
@@ -21,9 +21,22 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
21
21
  import { createHash, randomUUID } from 'crypto';
22
22
  import { pathToFileURL } from 'node:url';
23
23
  import { projectAiwgPath } from '../../config/project-artifacts.js';
24
+ import { resolveSensitiveInput, sensitiveInputOptions } from '../../auth/sensitive-input.js';
24
25
  const DEFAULT_PORT = 8200;
25
26
  const DEFAULT_BIND = '127.0.0.1';
26
27
  const DEFAULT_AIWG_SERVE = 'http://127.0.0.1:7337';
28
+ /** Optional input: no source keeps registration/cache behavior, even at a terminal. */
29
+ export async function resolveLocalExecutorToken(args, dependencies = {}) {
30
+ const source = sensitiveInputOptions(args, 'token');
31
+ const environment = dependencies.environment ?? process.env;
32
+ const explicit = source.interactive || source.stdin || source.file !== undefined || source.store !== undefined || source.env !== undefined;
33
+ if (!explicit) {
34
+ if (!environment.AIWG_EXECUTOR_TOKEN)
35
+ return undefined;
36
+ source.env = 'AIWG_EXECUTOR_TOKEN';
37
+ }
38
+ return resolveSensitiveInput(source, { ...dependencies, environment });
39
+ }
27
40
  /**
28
41
  * Resolve or generate-once the executor identity.
29
42
  * Written to `.aiwg/local-executor/identity.json` so restarts reuse the same
@@ -122,6 +135,14 @@ export const localExecutorServeHandler = {
122
135
  // The subcommand is: `aiwg local-executor serve [flags]`
123
136
  // ctx.args[0] === 'serve' (consumed by router) or flags directly
124
137
  const flagArgs = ctx.args[0] === 'serve' ? ctx.args.slice(1) : ctx.args;
138
+ let preexistingToken;
139
+ try {
140
+ preexistingToken = await resolveLocalExecutorToken(flagArgs);
141
+ }
142
+ catch (error) {
143
+ return { exitCode: 1, message: error.message };
144
+ }
145
+ const redact = (message) => preexistingToken ? message.split(preexistingToken).join('[REDACTED]') : message;
125
146
  const opts = parseLocalExecutorArgs(flagArgs);
126
147
  warnIfNonLoopback(opts.bind);
127
148
  // ── Dynamic imports (same pattern as serve.ts) ──────────────────────────
@@ -138,7 +159,7 @@ export const localExecutorServeHandler = {
138
159
  }
139
160
  catch (err) {
140
161
  return {
141
- message: `Failed to load executor-shim: ${err.message}`,
162
+ message: `Failed to load executor-shim: ${redact(err.message)}`,
142
163
  exitCode: 1,
143
164
  };
144
165
  }
@@ -148,7 +169,7 @@ export const localExecutorServeHandler = {
148
169
  }
149
170
  catch (err) {
150
171
  return {
151
- message: `Failed to load DaemonSupervisor: ${err.message}`,
172
+ message: `Failed to load DaemonSupervisor: ${redact(err.message)}`,
152
173
  exitCode: 1,
153
174
  };
154
175
  }
@@ -208,7 +229,7 @@ export const localExecutorServeHandler = {
208
229
  restBase,
209
230
  wsBase,
210
231
  aiwgServeUrl: opts.aiwgServe,
211
- token: process.env['AIWG_EXECUTOR_TOKEN'],
232
+ token: preexistingToken,
212
233
  tokenPath: projectAiwgPath(process.cwd(), 'local-executor', 'tokens', `${createHash('sha256').update(`${opts.aiwgServe}\0${opts.executorId}`).digest('hex')}.json`),
213
234
  });
214
235
  // Register with aiwg-serve
@@ -218,7 +239,7 @@ export const localExecutorServeHandler = {
218
239
  }
219
240
  catch (err) {
220
241
  return {
221
- message: `Failed to register with aiwg-serve at ${opts.aiwgServe}: ${err.message}\n` +
242
+ message: `Failed to register with aiwg-serve at ${opts.aiwgServe}: ${redact(err.message)}\n` +
222
243
  'Is aiwg serve running? Start it with: aiwg serve --no-open',
223
244
  exitCode: 1,
224
245
  };
@@ -235,7 +256,7 @@ export const localExecutorServeHandler = {
235
256
  catch (err) {
236
257
  await shim.deregister();
237
258
  return {
238
- message: `Failed to start executor server: ${err.message}`,
259
+ message: `Failed to start executor server: ${redact(err.message).split(token).join('[REDACTED]')}`,
239
260
  exitCode: 1,
240
261
  };
241
262
  }
@@ -304,9 +325,15 @@ export const localExecutorHandler = {
304
325
  ' --aiwg-serve <url> Registration target (default: http://127.0.0.1:7337)\n' +
305
326
  ' --max-concurrency <n> Max concurrent missions (default: 4)\n' +
306
327
  ' --executor-id <uuid> Override stable executor ID\n' +
328
+ ' --token-interactive Read optional existing bearer without echo\n' +
329
+ ' --token-stdin Read optional existing bearer from stdin\n' +
330
+ ' --token-file <path> Read optional existing bearer from a private file\n' +
331
+ ' --token-store <name> Read optional existing bearer from the OS store\n' +
332
+ ' --token-env <NAME> Read optional existing bearer from a named variable\n' +
307
333
  '\n' +
308
334
  'Environment:\n' +
309
- ' AIWG_SERVE_ENDPOINT Override --aiwg-serve default\n');
335
+ ' AIWG_SERVE_ENDPOINT Override --aiwg-serve default\n' +
336
+ ' AIWG_EXECUTOR_TOKEN Optional preexisting executor bearer\n');
310
337
  return { exitCode: 0 };
311
338
  }
312
339
  return {
@@ -3,13 +3,14 @@ import { loadResourceTrustRootFile, readVerifiedRegularFile, resolveWebRelease,
3
3
  import { getProjectDir } from "../../config/aiwg-config.js";
4
4
  import { cleanWebResourceCache } from "../../resources/cache-cleanup.js";
5
5
  import { writeWebResourceLock } from "../../resources/lockfile.js";
6
- import { createResourceCredentialProvider } from "../../auth/resource-credentials.js";
6
+ import { createResourceCredentialProvider, prepareResourceCredentials } from "../../auth/resource-credentials.js";
7
7
  const MAX_RESOURCE_MANIFEST_BYTES = 4 * 1024 * 1024;
8
8
  const DEFAULT_CHANNELS = ["stable", "latest", "canary", "main"];
9
9
  function usage() {
10
10
  return [
11
11
  "Usage: aiwg versions <list|resolve|show|clean-cache> [selector] [--json] [--pretty] [--offline]",
12
12
  "",
13
+ "Protected releases: --resource-token-stdin, --resource-token-file <path>, --resource-token-store <name>, --resource-token-env <NAME>, --resource-token-interactive",
13
14
  "Examples:",
14
15
  " aiwg versions list --json",
15
16
  " aiwg versions resolve stable --json",
@@ -75,7 +76,7 @@ function parseArgs(args) {
75
76
  channels,
76
77
  };
77
78
  }
78
- function webReleaseOptionsFromEnvironment() {
79
+ function webReleaseOptionsFromEnvironment(credentialProvider = createResourceCredentialProvider(process.env)) {
79
80
  const baseUrl = process.env.AIWG_RESOURCE_BASE_URL;
80
81
  const cacheRoot = process.env.AIWG_RESOURCE_CACHE_ROOT;
81
82
  const trustRootFile = process.env.AIWG_RESOURCE_TRUST_ROOT_FILE;
@@ -83,7 +84,7 @@ function webReleaseOptionsFromEnvironment() {
83
84
  ? undefined
84
85
  : loadResourceTrustRootFile(path.resolve(trustRootFile));
85
86
  return {
86
- credentialProvider: createResourceCredentialProvider(process.env),
87
+ credentialProvider,
87
88
  ...(baseUrl === undefined ? {} : { baseUrl }),
88
89
  ...(cacheRoot === undefined ? {} : { cacheRoot }),
89
90
  ...(publicKeyPem === undefined ? {} : { publicKeyPem }),
@@ -168,14 +169,16 @@ export const versionsHandler = {
168
169
  return { exitCode: 0 };
169
170
  }
170
171
  let parsed;
172
+ let credentials;
171
173
  try {
172
- parsed = parseArgs(ctx.args);
174
+ credentials = await prepareResourceCredentials(ctx.args);
175
+ parsed = parseArgs(credentials.args);
173
176
  }
174
177
  catch (error) {
175
178
  return { exitCode: 2, message: error instanceof Error ? error.message : String(error) };
176
179
  }
177
180
  try {
178
- const baseOptions = webReleaseOptionsFromEnvironment();
181
+ const baseOptions = webReleaseOptionsFromEnvironment(credentials.credentialProvider);
179
182
  if (parsed.subcommand === "clean-cache") {
180
183
  const result = cleanWebResourceCache(getProjectDir(ctx, ctx.args), {
181
184
  cacheRoot: process.env.AIWG_RESOURCE_CACHE_ROOT,
@@ -57,10 +57,13 @@ export class DefaultScriptRunner {
57
57
  const stdio = options.capture
58
58
  ? ['pipe', options.stdoutFd ?? 'pipe', 'pipe']
59
59
  : 'inherit';
60
+ const environment = { ...process.env, ...options.env };
61
+ for (const name of options.omitEnv ?? [])
62
+ delete environment[name];
60
63
  const child = spawn('node', [fullPath, ...args], {
61
64
  stdio,
62
65
  cwd: options.cwd || process.cwd(),
63
- env: { ...process.env, ...options.env },
66
+ env: environment,
64
67
  });
65
68
  // Always drain captured pipes, even after the diagnostic cap is reached.
66
69
  // stdout is not part of HandlerResult; discard it. Retain only the first
@@ -146,10 +149,13 @@ export class DefaultScriptRunner {
146
149
  async runDeployment(fullPath, args, options) {
147
150
  const previousConsole = globalThis.console;
148
151
  const previousRoot = process.env.AIWG_ROOT;
152
+ const omitted = new Map((options.omitEnv ?? []).map(name => [name, process.env[name]]));
149
153
  const stderrLimit = 1024 * 1024;
150
154
  const diagnostics = [];
151
155
  let diagnosticBytes = 0;
152
156
  try {
157
+ for (const name of omitted.keys())
158
+ delete process.env[name];
153
159
  if (options.env?.AIWG_ROOT !== undefined)
154
160
  process.env.AIWG_ROOT = options.env.AIWG_ROOT;
155
161
  if (options.capture) {
@@ -187,6 +193,12 @@ export class DefaultScriptRunner {
187
193
  }
188
194
  finally {
189
195
  globalThis.console = previousConsole;
196
+ for (const [name, value] of omitted) {
197
+ if (value === undefined)
198
+ delete process.env[name];
199
+ else
200
+ process.env[name] = value;
201
+ }
190
202
  if (previousRoot === undefined)
191
203
  delete process.env.AIWG_ROOT;
192
204
  else
@@ -65,7 +65,7 @@ import { verifyModelWrapperDeployment } from '../../models/wrapper-deployment.js
65
65
  import { aggregateUseDeploymentResult, buildDryRunUseResult, renderUseDeploymentResult, verifyProviderDeployment, } from '../services/deployment-verification.js';
66
66
  import { finalizeProviderTransformationReceipt, providerReceiptHasLocalSources, sourceVerificationsFromSignedWebRelease, } from '../../providers/transformation-receipt-integration.js';
67
67
  import { loadResourceTrustRootFile, resolveWebRelease, } from '../../resources/web-release.js';
68
- import { createResourceCredentialProvider } from '../../auth/resource-credentials.js';
68
+ import { createResourceCredentialProvider, prepareResourceCredentials } from '../../auth/resource-credentials.js';
69
69
  /**
70
70
  * Valid framework identifiers
71
71
  */
@@ -102,7 +102,7 @@ export async function resolveProviderReceiptSource(options) {
102
102
  release = await resolveWebRelease({ ...releaseOptions, selector: versionInfo.version, offline: true });
103
103
  }
104
104
  catch {
105
- const credentialProvider = createResourceCredentialProvider(process.env);
105
+ const credentialProvider = options.credentialProvider ?? createResourceCredentialProvider(process.env);
106
106
  const token = await credentialProvider();
107
107
  // Protected production resources require the authenticated release
108
108
  // credential. A configured alternate endpoint may intentionally be public.
@@ -1697,6 +1697,7 @@ function createUseScriptRunner(frameworkRoot) {
1697
1697
  const runner = createScriptRunner(frameworkRoot);
1698
1698
  return {
1699
1699
  async run(script, args, options = {}) {
1700
+ options = { ...options, omitEnv: useDeployment?.resourceCredentials?.omitEnv ?? ['AIWG_RESOURCE_TOKEN'] };
1700
1701
  if (script === 'tools/agents/deploy-agents.mjs')
1701
1702
  args = useDeploymentArgs(args);
1702
1703
  if (!usePreviewActions)
@@ -2472,6 +2473,9 @@ Bundles:
2472
2473
  <addon> | <extension> Any installed addon or extension name
2473
2474
 
2474
2475
  Options:
2476
+ --resource-token-stdin | --resource-token-file <path> | --resource-token-store <name>
2477
+ --resource-token-env <NAME> | --resource-token-interactive
2478
+ Select the optional protected release bearer
2475
2479
  --provider <name> Target provider (default: .aiwg/aiwg.config providers)
2476
2480
  --target <dir> Deploy into <dir> instead of the current directory
2477
2481
  --scope project|user Deploy to the project (default) or the user scope
@@ -2543,11 +2547,22 @@ export class UseHandler {
2543
2547
  return { exitCode: 0, message: USE_HELP, rawOutput: true };
2544
2548
  }
2545
2549
  async execute(ctx) {
2550
+ let resourceCredentials = useDeployment?.resourceCredentials;
2551
+ try {
2552
+ if (!resourceCredentials) {
2553
+ resourceCredentials = await prepareResourceCredentials(ctx.args);
2554
+ ctx = { ...ctx, args: resourceCredentials.args };
2555
+ }
2556
+ }
2557
+ catch (error) {
2558
+ return { exitCode: 2, message: error instanceof Error ? error.message : String(error) };
2559
+ }
2546
2560
  const invalidOption = validateUseOptions(ctx.args, this.preparedProviderContinuations);
2547
2561
  if (invalidOption)
2548
2562
  return { exitCode: 2, message: invalidOption };
2549
2563
  const previousDeployment = useDeployment;
2550
2564
  useDeployment = {
2565
+ resourceCredentials,
2551
2566
  version: previousDeployment?.version ?? resolveDeployVersion(ctx.frameworkRoot || await getFrameworkRoot()),
2552
2567
  skipContextFiles: previousDeployment?.skipContextFiles || ctx.args.includes('--no-context-files'),
2553
2568
  contextFiles: previousDeployment?.contextFiles ?? [],
@@ -2730,7 +2745,7 @@ export class UseHandler {
2730
2745
  scope: effectiveScope,
2731
2746
  requestedBundles: [requestedBundle],
2732
2747
  };
2733
- const sourceResolution = await resolveProviderReceiptSource(receiptOptions);
2748
+ const sourceResolution = await resolveProviderReceiptSource({ ...receiptOptions, credentialProvider: useDeployment?.resourceCredentials?.credentialProvider });
2734
2749
  await finalizeProviderTransformationReceipt({ ...receiptOptions, ...sourceResolution });
2735
2750
  }
2736
2751
  catch (error) {