@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.
- package/dist/src/artifacts/cli.js +9 -5
- package/dist/src/auth/config.js +3 -8
- package/dist/src/auth/resource-credentials.js +35 -2
- package/dist/src/cli/handlers/cost-report.js +7 -1
- package/dist/src/cli/handlers/decision.js +33 -8
- package/dist/src/cli/handlers/diagnose.js +13 -0
- package/dist/src/cli/handlers/local-executor.js +33 -6
- package/dist/src/cli/handlers/resource-versions.js +8 -5
- package/dist/src/cli/handlers/script-runner.js +13 -1
- package/dist/src/cli/handlers/use.js +18 -3
- package/dist/src/cost/fleet-report.js +20 -22
- package/dist/src/decision/driver.js +2 -2
- package/dist/src/decision/jev-credentials.js +18 -5
- package/dist/src/decision/ledger.js +230 -34
- package/dist/src/mcp/cli.mjs +105 -19
- package/dist/src/mcp/credentials.d.mts +4 -0
- package/dist/src/mcp/credentials.mjs +44 -3
- package/dist/src/mcp/grok-build-config.mjs +2 -0
- package/dist/src/mcp/omp-config.mjs +2 -0
- package/dist/src/mcp/registry.js +19 -15
- package/dist/src/mcp/registry.mjs +2 -18
- package/dist/src/mcp/tools/agentic-sandbox.mjs +46 -28
- package/dist/src/models/grok-build-models.js +20 -0
- package/dist/src/providers/provider-definitions.js +2 -2
- package/dist/src/resources/web-release.js +76 -65
- package/dist/src/serve/shared-host-scheduler.js +4 -1
- package/dist/src/storage/backends/fortemi.js +112 -50
- package/dist/src/storage/backends/logseq.js +16 -3
- package/dist/src/storage/backends/postgres.js +74 -33
- package/dist/src/storage/backends/postgrest.js +37 -13
- package/dist/src/storage/config.js +6 -1
- package/dist/src/uhp/config.js +4 -0
- 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
|
|
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'),
|
package/dist/src/auth/config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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) {
|