@aiwg/cli 2026.10.6 → 2026.10.8

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 (100) hide show
  1. package/bin/aiwg.mjs +4 -2
  2. package/dist/src/a2a/agent-card.js +11 -0
  3. package/dist/src/api/index.d.ts +1 -0
  4. package/dist/src/api/index.js +1 -0
  5. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
  6. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
  7. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
  8. package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
  9. package/dist/src/artifacts/fortemi-shard-export.js +284 -0
  10. package/dist/src/artifacts/index-preparation.js +2 -0
  11. package/dist/src/auth/client.js +5 -1
  12. package/dist/src/auth/credential-store.js +5 -1
  13. package/dist/src/auth/sensitive-input.js +151 -0
  14. package/dist/src/channel/manager.mjs +3 -0
  15. package/dist/src/cli/handlers/agentcard.js +47 -25
  16. package/dist/src/cli/handlers/auth.js +22 -2
  17. package/dist/src/cli/handlers/cost-report.js +7 -1
  18. package/dist/src/cli/handlers/decision.js +36 -10
  19. package/dist/src/cli/handlers/effect.js +4 -84
  20. package/dist/src/cli/handlers/guided-setup.js +1 -1
  21. package/dist/src/cli/handlers/init.js +1 -1
  22. package/dist/src/cli/handlers/install.js +6 -0
  23. package/dist/src/cli/handlers/local-executor.js +33 -6
  24. package/dist/src/cli/handlers/marketplace.js +15 -4
  25. package/dist/src/cli/handlers/setup.js +24 -20
  26. package/dist/src/cli/handlers/use.js +57 -15
  27. package/dist/src/cli/prompt-utils.js +73 -0
  28. package/dist/src/config/gitignore.js +4 -0
  29. package/dist/src/config/workspace.js +2 -1
  30. package/dist/src/cost/fleet-report.js +20 -22
  31. package/dist/src/decision/adapters/jev.js +20 -31
  32. package/dist/src/decision/ask.js +6 -1
  33. package/dist/src/decision/benchmark.js +19 -6
  34. package/dist/src/decision/driver.js +2 -2
  35. package/dist/src/decision/jev-credentials.js +18 -5
  36. package/dist/src/decision/ledger.js +230 -34
  37. package/dist/src/decision/qualification/gate-evidence.js +6 -4
  38. package/dist/src/decision/qualification/load-staged.js +47 -10
  39. package/dist/src/decision/qualification/runner.js +7 -3
  40. package/dist/src/discovery/interview.js +10 -5
  41. package/dist/src/discovery/plan-apply.js +26 -30
  42. package/dist/src/discovery/plan-validation.js +2 -2
  43. package/dist/src/effects/host.js +91 -0
  44. package/dist/src/extensions/commands/definitions.js +2 -2
  45. package/dist/src/extensions/project-local-gitignore.js +2 -0
  46. package/dist/src/issues/cli.js +16 -5
  47. package/dist/src/issues/live.js +44 -12
  48. package/dist/src/marketplace/exchange.js +18 -6
  49. package/dist/src/marketplace/provenance.js +3 -2
  50. package/dist/src/mcp/adapters/codex-runtime.js +46 -76
  51. package/dist/src/mcp/cli.mjs +122 -109
  52. package/dist/src/mcp/credentials.d.mts +4 -0
  53. package/dist/src/mcp/credentials.mjs +44 -3
  54. package/dist/src/mcp/grok-build-config.mjs +4 -1
  55. package/dist/src/mcp/muse-native-config.mjs +2 -1
  56. package/dist/src/mcp/omp-config.mjs +2 -0
  57. package/dist/src/mcp/registry.js +19 -15
  58. package/dist/src/mcp/registry.mjs +2 -18
  59. package/dist/src/mcp/tools/agentic-sandbox.mjs +46 -28
  60. package/dist/src/mcp/tools/worker-invoke.mjs +13 -2
  61. package/dist/src/memory/project-registry.js +2 -1
  62. package/dist/src/mission-protocol/worker-status.js +91 -1
  63. package/dist/src/models/grok-build-models.js +20 -0
  64. package/dist/src/models/worker-installation.d.mts +1 -1
  65. package/dist/src/models/worker-installation.mjs +65 -4
  66. package/dist/src/packages/adapters/git.js +99 -23
  67. package/dist/src/packages/adapters/gitea.js +8 -27
  68. package/dist/src/packages/adapters/github.js +7 -25
  69. package/dist/src/packages/registry.js +4 -4
  70. package/dist/src/process/cli-command.d.mts +5 -0
  71. package/dist/src/process/cli-command.mjs +42 -0
  72. package/dist/src/providers/provider-definitions.js +2 -2
  73. package/dist/src/serve/shared-host-scheduler.js +4 -1
  74. package/dist/src/serve/worker-invocation.js +10 -0
  75. package/dist/src/smiths/context-pipeline/claude-hook.js +9 -9
  76. package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
  77. package/dist/src/smiths/context-pipeline/finalization.js +3 -2
  78. package/dist/src/smiths/context-pipeline/generator.js +7 -19
  79. package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
  80. package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
  81. package/dist/src/smiths/context-pipeline/workspace-context.js +4 -12
  82. package/dist/src/storage/backends/fortemi.js +107 -49
  83. package/dist/src/tracker/project-inputs.js +2 -0
  84. package/dist/src/update/notifier.mjs +2 -4
  85. package/package.json +1 -1
  86. package/tools/agents/providers/base.mjs +5 -1
  87. package/tools/agents/providers/claude.mjs +5 -2
  88. package/tools/agents/providers/codex.mjs +4 -1
  89. package/tools/agents/providers/context-write.mjs +28 -0
  90. package/tools/agents/providers/copilot.mjs +5 -2
  91. package/tools/agents/providers/hermes.mjs +5 -2
  92. package/tools/agents/providers/omp.mjs +3 -1
  93. package/tools/agents/providers/rule-delivery.mjs +29 -12
  94. package/tools/agents/providers/warp.mjs +4 -1
  95. package/tools/agents/providers/windsurf.mjs +6 -3
  96. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
  97. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
  98. package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
  99. package/tools/ralph-external/process-monitor.mjs +42 -4
  100. package/tools/ralph-external/session-launcher.mjs +125 -3
@@ -10,6 +10,7 @@
10
10
  * @issue #1253
11
11
  */
12
12
  import { fetchAgentCard } from '../../a2a/agent-card.js';
13
+ import { resolveSensitiveInput, sensitiveInputOptions } from '../../auth/sensitive-input.js';
13
14
  import { loadJwkSet } from '../../a2a/jws.js';
14
15
  import { AiwgError, EXIT_CODES, handlerResultFromError } from '../errors.js';
15
16
  function parseVerifyArgs(args) {
@@ -39,6 +40,9 @@ function parseVerifyArgs(args) {
39
40
  }
40
41
  const jwks = get('--jwks');
41
42
  const bearer = get('--bearer');
43
+ if (args.includes('--bearer') && (!bearer || bearer.startsWith('--'))) {
44
+ throw new AiwgError({ code: 'ERR_AGENTCARD_BEARER', message: '--bearer requires a value; prefer --bearer-stdin, --bearer-file, --bearer-store or --bearer-env.', exitCode: EXIT_CODES.USAGE });
45
+ }
42
46
  const skipVerify = args.includes('--skip-verify');
43
47
  const json = args.includes('--json');
44
48
  if (!jwks && !skipVerify) {
@@ -73,36 +77,49 @@ async function loadJwksFromSource(source) {
73
77
  }
74
78
  async function handleVerify(args) {
75
79
  const parsed = parseVerifyArgs(args);
80
+ const sources = sensitiveInputOptions(args, 'bearer');
81
+ const hasSource = sources.interactive || sources.stdin || sources.file !== undefined || sources.store !== undefined || sources.env !== undefined;
82
+ if (parsed.bearer !== undefined) {
83
+ console.error('Warning: --bearer is deprecated and exposes the value on argv; use --bearer-interactive, --bearer-stdin, --bearer-file, --bearer-store or --bearer-env.');
84
+ }
85
+ // No source intentionally means public/unauthenticated access, even at a TTY.
86
+ const bearer = hasSource
87
+ ? await resolveSensitiveInput(sources)
88
+ : parsed.bearer === undefined ? undefined : await resolveSensitiveInput({ prefix: 'bearer', env: 'LEGACY_BEARER' }, { readEnv: () => parsed.bearer });
89
+ const redact = (text) => bearer ? text.split(bearer).join('[REDACTED]') : text;
90
+ const log = (text) => console.log(redact(text));
91
+ const error = (text) => console.error(redact(text));
92
+ const jsonReplacer = (_key, value) => typeof value === 'string' ? redact(value) : value;
76
93
  const opts = {
77
94
  skipVerify: parsed.skipVerify,
78
95
  };
79
- if (parsed.bearer !== undefined)
80
- opts.bearer = parsed.bearer;
81
- if (parsed.jwks !== undefined && !parsed.skipVerify) {
82
- opts.jwks = await loadJwksFromSource(parsed.jwks);
83
- }
96
+ if (bearer !== undefined)
97
+ opts.bearer = bearer;
84
98
  let verified;
85
99
  try {
100
+ if (parsed.jwks !== undefined && !parsed.skipVerify) {
101
+ opts.jwks = await loadJwksFromSource(parsed.jwks);
102
+ }
86
103
  verified = await fetchAgentCard(parsed.host, parsed.instance, opts);
87
104
  }
88
105
  catch (err) {
89
106
  const message = err.message;
90
107
  if (parsed.json) {
91
- console.log(JSON.stringify({
108
+ log(JSON.stringify({
92
109
  ok: false,
93
110
  host: parsed.host,
94
111
  instance: parsed.instance,
95
112
  error: message,
96
- }, null, 2));
113
+ }, jsonReplacer, 2));
97
114
  }
98
115
  else {
99
- console.error(`AgentCard verification FAILED for ${parsed.host}/${parsed.instance}`);
100
- console.error(` ${message}`);
116
+ error(`AgentCard verification FAILED for ${parsed.host}/${parsed.instance}`);
117
+ error(` ${message}`);
101
118
  }
102
119
  return { exitCode: EXIT_CODES.GENERAL };
103
120
  }
104
121
  if (parsed.json) {
105
- console.log(JSON.stringify({
122
+ log(JSON.stringify({
106
123
  ok: true,
107
124
  host: parsed.host,
108
125
  instance: parsed.instance,
@@ -118,38 +135,38 @@ async function handleVerify(args) {
118
135
  capabilities: verified.card.capabilities,
119
136
  skills: verified.card.skills,
120
137
  },
121
- }, null, 2));
138
+ }, jsonReplacer, 2));
122
139
  }
123
140
  else {
124
141
  const banner = parsed.skipVerify ? '(verification SKIPPED)' : 'VERIFIED';
125
- console.log(`AgentCard ${banner} for ${parsed.host}/${parsed.instance}`);
126
- console.log(` name: ${verified.card.name}`);
127
- console.log(` protocolVersion: ${verified.card.protocolVersion}`);
128
- console.log(` url: ${verified.card.url}`);
129
- console.log(` version: ${verified.card.version}`);
142
+ log(`AgentCard ${banner} for ${parsed.host}/${parsed.instance}`);
143
+ log(` name: ${verified.card.name}`);
144
+ log(` protocolVersion: ${verified.card.protocolVersion}`);
145
+ log(` url: ${verified.card.url}`);
146
+ log(` version: ${verified.card.version}`);
130
147
  if (verified.card.preferredTransport) {
131
- console.log(` preferredTransport: ${verified.card.preferredTransport}`);
148
+ log(` preferredTransport: ${verified.card.preferredTransport}`);
132
149
  }
133
150
  if (verified.kid) {
134
- console.log(` signing kid: ${verified.kid}`);
151
+ log(` signing kid: ${verified.kid}`);
135
152
  }
136
- console.log(` verifiedAt: ${verified.verifiedAt}`);
153
+ log(` verifiedAt: ${verified.verifiedAt}`);
137
154
  const exts = verified.card.capabilities?.extensions ?? [];
138
155
  if (exts.length > 0) {
139
- console.log(` extensions:`);
156
+ log(` extensions:`);
140
157
  for (const e of exts) {
141
158
  const tag = e.required ? '[required]' : '[optional]';
142
- console.log(` ${tag} ${e.uri}`);
159
+ log(` ${tag} ${e.uri}`);
143
160
  }
144
161
  }
145
162
  const skills = verified.card.skills ?? [];
146
163
  if (skills.length > 0) {
147
- console.log(` skills (${skills.length}):`);
164
+ log(` skills (${skills.length}):`);
148
165
  for (const s of skills.slice(0, 5)) {
149
- console.log(` - ${s.id}${s.name ? ' (' + s.name + ')' : ''}`);
166
+ log(` - ${s.id}${s.name ? ' (' + s.name + ')' : ''}`);
150
167
  }
151
168
  if (skills.length > 5)
152
- console.log(` ... and ${skills.length - 5} more`);
169
+ log(` ... and ${skills.length - 5} more`);
153
170
  }
154
171
  }
155
172
  return { exitCode: 0 };
@@ -164,7 +181,12 @@ function printUsage() {
164
181
  console.log(' --host <url> Executor host (e.g. https://exec.test)');
165
182
  console.log(' --instance <id> Instance id (UUIDv7)');
166
183
  console.log(' --jwks <path|url> JWKS file path or URL (required unless --skip-verify)');
167
- console.log(' --bearer <token> Optional bearer token for the card endpoint');
184
+ console.log(' --bearer-interactive Read optional bearer in a terminal without echo');
185
+ console.log(' --bearer-stdin Read optional bearer from piped stdin');
186
+ console.log(' --bearer-file <path> Read optional bearer from an owner-only private file');
187
+ console.log(' --bearer-store <name> Read optional bearer from a named OS store entry');
188
+ console.log(' --bearer-env <NAME> Read optional bearer from a named shell variable');
189
+ console.log(' --bearer <token> Deprecated: exposes the value on argv');
168
190
  console.log(' --skip-verify Skip JWS verification (NOT recommended)');
169
191
  console.log(' --json Emit JSON output');
170
192
  }
@@ -1,3 +1,4 @@
1
+ import { namedSensitiveStore, resolveSensitiveInput, sensitiveInputOptions } from '../../auth/sensitive-input.js';
1
2
  import { AuthClient, defaultBrowserOpener } from "../../auth/client.js";
2
3
  import { authConfigFromEnvironment } from "../../auth/config.js";
3
4
  import { createCredentialStore } from "../../auth/credential-store.js";
@@ -5,6 +6,8 @@ function usage() {
5
6
  return [
6
7
  "Usage:",
7
8
  " aiwg auth login [--device] [--device-label <label>] [--store native|file] [--allow-file-store]",
9
+ " aiwg auth secret set <name> [--value-interactive | --value-stdin | --value-file <path> | --value-store <name> | --value-env <name>]",
10
+ " aiwg auth secret delete <name>",
8
11
  " aiwg auth status [--json] [--store native|file] [--allow-file-store]",
9
12
  " aiwg auth logout [--all] [--store native|file] [--allow-file-store]",
10
13
  "",
@@ -46,9 +49,25 @@ export function createAuthHandler(dependencies = {}) {
46
49
  return { exitCode: 0 };
47
50
  }
48
51
  const [subcommand, ...args] = ctx.args;
49
- if (!["login", "status", "logout"].includes(subcommand))
52
+ if (!["login", "status", "logout", "secret"].includes(subcommand))
50
53
  return { exitCode: 2, message: usage() };
51
54
  try {
55
+ if (subcommand === "secret") {
56
+ const [operation, name, ...inputArgs] = args;
57
+ if (!["set", "delete"].includes(operation) || !name || name.startsWith('--'))
58
+ return { exitCode: 2, message: usage() };
59
+ const store = (dependencies.secretStore ?? namedSensitiveStore)(name);
60
+ if (operation === "delete")
61
+ await store.deleteSecret();
62
+ else {
63
+ const secret = await resolveSensitiveInput({ ...sensitiveInputOptions(inputArgs), signal: ctx.signal }, {
64
+ environment: dependencies.env, ...dependencies.sensitiveInput,
65
+ });
66
+ await store.saveSecret(secret);
67
+ }
68
+ console.log(operation === "delete" ? "Named sensitive value removed." : "Named sensitive value stored.");
69
+ return { exitCode: 0 };
70
+ }
52
71
  const storeMode = value(args, "--store") || "native";
53
72
  if (!["native", "file"].includes(storeMode))
54
73
  return { exitCode: 2, message: "--store must be native or file" };
@@ -119,7 +138,8 @@ export function createAuthHandler(dependencies = {}) {
119
138
  return { exitCode: 0 };
120
139
  }
121
140
  catch (error) {
122
- return { exitCode: classify(error), message: error instanceof Error ? error.message : String(error) };
141
+ return { exitCode: classify(error), message: subcommand === 'secret' && !/^(--value-|A sensitive value|An interactive terminal|Sensitive value store name)/.test(error instanceof Error ? error.message : '')
142
+ ? 'Native sensitive value store operation failed.' : error instanceof Error ? error.message : String(error) };
123
143
  }
124
144
  },
125
145
  };
@@ -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,11 +1,12 @@
1
+ import { resolveSensitiveInput, sensitiveInputOptions } from '../../auth/sensitive-input.js';
1
2
  import { decisionPricing } from '../../decision/pricing.js';
2
- 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';
3
4
  function jsonResult(value, exitCode = 0) {
4
5
  return { exitCode, message: JSON.stringify(value, null, 2), rawOutput: true };
5
6
  }
6
7
  function usage() {
7
8
  return [
8
- '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]',
9
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]',
10
11
  '',
11
12
  'Commands:',
@@ -17,11 +18,12 @@ function usage() {
17
18
  ' validate <request|definition|ruleset|binding> <path>',
18
19
  ' evaluate --request <path> [--host-policy-module <path>]',
19
20
  ' setup synthetic-classification [--output-dir <dir>]',
20
- ' setup jev [--token-stdin] [--region <r>] [--endpoint <url>] [--verify] [--remove]',
21
+ ' setup jev [--token-interactive | --token-stdin | --token-file <path> | --token-store <name> | --token-env <name>] [--region <r>] [--endpoint <url>] [--verify] [--remove]',
21
22
  ' ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--provider <id>] [--session <id>] [--caller <tag>] [--record-text]',
22
- ' 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]',
23
24
  ' pricing [--refresh --model jev-X.Y.Z] [--dry-run] [--offline | --allow-snapshot] [--json]',
24
- ' 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]',
25
27
  '',
26
28
  'Notes:',
27
29
  ' Every COMPLETED `ask` call (CLI and MCP) appends one record to the decision',
@@ -41,9 +43,13 @@ function usage() {
41
43
  ` --session and --caller must match ${ATTRIBUTION_FIELD_PATTERN.source} (<= ${MAX_ATTRIBUTION_FIELD_LENGTH} characters).`,
42
44
  ' `history`/`stats` accept `--caller <tag>` (exact), `--caller \'<prefix>*\'` (prefix; the only wildcard), or `--no-caller` (records with no tag; not combinable with `--caller`).',
43
45
  ' `history`/`stats` reject unknown flags and positional arguments with exit 2.',
44
- ' The ledger has no built-in retention: it grows without bound and the',
45
- ' operator may truncate or delete it at any time; readers tolerate a missing',
46
- ' 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',
47
53
  ' artifact root, owned by the same user running aiwg — it is not a defense',
48
54
  ' against a hostile process already running as that user.',
49
55
  ].join('\n');
@@ -101,7 +107,7 @@ function takeAttributionOption(args, name, reason) {
101
107
  return value;
102
108
  }
103
109
  async function decisionSetupJev(rest) {
104
- const token = rest.includes('--token-stdin') ? (await readStdin()).trim() : undefined;
110
+ const token = rest.includes('--remove') ? undefined : await resolveSensitiveInput(sensitiveInputOptions(rest, 'token'));
105
111
  const result = await setupJev({
106
112
  token,
107
113
  region: takeOption(rest, '--region'),
@@ -206,7 +212,7 @@ function parseGroupBy(value) {
206
212
  return value;
207
213
  }
208
214
  const HISTORY_VALUE_FLAGS = ['--since', '--provider', '--session', '--kind', '--status', '--caller'];
209
- const HISTORY_BOOL_FLAGS = ['--json', '--no-caller'];
215
+ const HISTORY_BOOL_FLAGS = ['--json', '--no-caller', '--include-archives'];
210
216
  const STATS_VALUE_FLAGS = [...HISTORY_VALUE_FLAGS, '--group-by'];
211
217
  const STATS_BOOL_FLAGS = HISTORY_BOOL_FLAGS;
212
218
  /**
@@ -268,6 +274,7 @@ function parseLedgerFilters(args) {
268
274
  kind: parseKindFilter(takeOption(args, '--kind')),
269
275
  status: parseStatusFilter(takeOption(args, '--status')),
270
276
  ...parseCallerFilter(args),
277
+ ...(args.includes('--include-archives') ? { includeArchives: true } : {}),
271
278
  };
272
279
  }
273
280
  /** One header line stating the caller filter, shown only when one is set. */
@@ -322,6 +329,23 @@ async function decisionStats(ctx) {
322
329
  ]);
323
330
  return { exitCode: 0, message: [...header, ...lines].join('\n'), rawOutput: true };
324
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
+ }
325
349
  async function executeDecision(ctx) {
326
350
  const [command, subcommand, ...rest] = ctx.args;
327
351
  const options = { cwd: ctx.cwd, frameworkRoot: ctx.frameworkRoot, env: process.env };
@@ -334,6 +358,8 @@ async function executeDecision(ctx) {
334
358
  return await decisionAsk(ctx);
335
359
  if (command === 'history')
336
360
  return await decisionHistory(ctx);
361
+ if (command === 'rotate')
362
+ return await decisionRotate(ctx);
337
363
  if (command === 'pricing') {
338
364
  const args = ctx.args.slice(1);
339
365
  assertLedgerArgs(args, ['--model'], ['--refresh', '--dry-run', '--offline', '--allow-snapshot', '--json']);
@@ -19,10 +19,8 @@
19
19
  * @see docs/contracts/effect-ledger.v1.md
20
20
  */
21
21
  import { readFile } from 'node:fs/promises';
22
- import { basename, resolve } from 'node:path';
23
- import { createSecretStore } from '../../auth/credential-store.js';
24
- import { readAiwgConfig } from '../../config/aiwg-config.js';
25
- import { CORE_EFFECT_KINDS, DEFAULT_LEDGER_KEY_SERVICE, EFFECT_EXIT_CODES, EFFECT_SUBSYSTEMS, EffectLedgerError, LEDGER_TEST_KEY_ENV, LedgerSigningKey, activeKey, containsRestrictedMaterial, createBuiltinVerifierRegistry, createTrackerVerifiers, credentialStoreKeyProvider, effectId as deriveEffectId, effectOutputJson, environmentTestKeyProvider, generateLedgerKeySecret, initLedgerKeyring, inspectLedgerLocks, isValidEffectId, ledgerLockRecoveryVerifier, lookupEffect, openEffectLedger, payloadDigest, readLedgerKeyring, reconcileEffect, recordIntent, recordOutcome, recordReconciled, recoverStaleLedgerLock, rotateKey, runVerifier, staticKeyProvider, verifyDecisionLinks, verifyLedger, writeCheckpoint, } from '../../effects/index.js';
22
+ import { resolve } from 'node:path';
23
+ import { CORE_EFFECT_KINDS, EFFECT_EXIT_CODES, EFFECT_SUBSYSTEMS, EffectLedgerError, LedgerSigningKey, activeKey, containsRestrictedMaterial, createBuiltinVerifierRegistry, createTrackerVerifiers, effectId as deriveEffectId, effectOutputJson, generateLedgerKeySecret, initLedgerKeyring, inspectLedgerLocks, isValidEffectId, ledgerLockRecoveryVerifier, lookupEffect, payloadDigest, readLedgerKeyring, reconcileEffect, recordIntent, recordOutcome, recordReconciled, recoverStaleLedgerLock, rotateKey, runVerifier, staticKeyProvider, verifyDecisionLinks, verifyLedger, writeCheckpoint, } from '../../effects/index.js';
26
24
  import { parseLedgerPrivateKey } from '../../effects/keys.js';
27
25
  import { JsonlOperatorDecisionStore, verifyDecisionChain } from '../../audit/operator-decision.js';
28
26
  import { readConfig, readGitRemoteUrls } from '../../tracker/project-inputs.js';
@@ -166,77 +164,7 @@ function keyValue(entry, flag) {
166
164
  throw usage(`--${flag} expects key=value`, 'invalid-key-value');
167
165
  return [entry.slice(0, eq), entry.slice(eq + 1)];
168
166
  }
169
- const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,255}$/;
170
- function defaultProject(projectDir) {
171
- const cleaned = basename(resolve(projectDir)).replace(/[^A-Za-z0-9._:/@-]/g, '-').replace(/^[^A-Za-z0-9]+/, '');
172
- return cleaned && IDENTIFIER.test(cleaned) ? cleaned : 'project';
173
- }
174
- async function hostConfig(projectDir) {
175
- let config;
176
- try {
177
- config = await readAiwgConfig(projectDir);
178
- }
179
- catch {
180
- throw new EffectLedgerError('usage', 'aiwg.config could not be read', 'config-unreadable');
181
- }
182
- const effects = config?.effects;
183
- if (effects === undefined || effects === null)
184
- return {};
185
- if (typeof effects !== 'object' || Array.isArray(effects))
186
- throw usage('aiwg.config effects must be an object', 'config-invalid');
187
- return effects;
188
- }
189
- function testContext(env) {
190
- return env.VITEST === 'true' || env.NODE_ENV === 'test' || env.CI === 'true' || env.CI === '1';
191
- }
192
- function keyStore(session, role) {
193
- const { deps, config, account } = session;
194
- if (deps.keyStore)
195
- return deps.keyStore(role, account);
196
- if (session.env[LEDGER_TEST_KEY_ENV] && testContext(session.env))
197
- return null;
198
- const provider = config.keyProvider ?? {};
199
- try {
200
- if (provider.type === 'file') {
201
- if (!provider.path)
202
- throw new Error('missing path');
203
- const pathname = resolve(session.projectDir, provider.path);
204
- return createSecretStore({ useFile: true, allowFile: true, pathname: role === 'next' ? `${pathname}.next` : pathname, service: DEFAULT_LEDGER_KEY_SERVICE, account });
205
- }
206
- if (provider.type !== undefined && provider.type !== 'credential-store')
207
- throw new Error('unknown provider');
208
- return createSecretStore({ service: provider.service ?? DEFAULT_LEDGER_KEY_SERVICE, account: role === 'next' ? `${account}.next` : account });
209
- }
210
- catch {
211
- throw new EffectLedgerError('key-unavailable', 'Effect ledger key provider is not configured correctly on this host', 'key-provider-invalid');
212
- }
213
- }
214
- function keyProvider(session) {
215
- const store = keyStore(session, 'current');
216
- if (!store)
217
- return environmentTestKeyProvider(session.env);
218
- return credentialStoreKeyProvider({ account: session.account, store });
219
- }
220
- function describeKeyProvider(session) {
221
- if (session.deps.keyStore)
222
- return { type: 'injected', account: session.account };
223
- if (session.env[LEDGER_TEST_KEY_ENV] && testContext(session.env))
224
- return { type: 'environment-test-key' };
225
- const provider = session.config.keyProvider ?? {};
226
- if (provider.type === 'file')
227
- return { type: 'file', account: session.account };
228
- return { type: 'credential-store', service: provider.service ?? DEFAULT_LEDGER_KEY_SERVICE, account: session.account };
229
- }
230
- function openLedger(session, registry) {
231
- const writer = session.config.writer ?? 'cli';
232
- return openEffectLedger({
233
- projectDir: session.projectDir, scope: session.scope, writer, keyProvider: keyProvider(session),
234
- ...(session.deps.clock ? { clock: session.deps.clock } : {}),
235
- ...(session.deps.sink ? { sink: session.deps.sink } : {}),
236
- ...(registry ? { verifiers: registry } : {}),
237
- ...(session.deps.lockTimeoutMs ? { lockTimeoutMs: session.deps.lockTimeoutMs } : {}),
238
- });
239
- }
167
+ import { effectSession, keyStore, describeKeyProvider, openLedger } from '../../effects/host.js';
240
168
  /**
241
169
  * The one place the CLI builds its verifier registry: the built-ins, lock
242
170
  * recovery, the tracker verifiers (#2719) and any injected extensions.
@@ -804,15 +732,7 @@ async function execute(ctx, deps) {
804
732
  throw usage('--format must be json or text', 'invalid-format');
805
733
  const projectDir = resolve(one(parsed, 'project-dir') ?? ctx.cwd ?? process.cwd());
806
734
  const subsystem = resolveSubsystem(parsed);
807
- const config = await hostConfig(projectDir);
808
- const tenant = config.tenant ?? 'local';
809
- const project = config.project ?? defaultProject(projectDir);
810
- if (!IDENTIFIER.test(tenant) || !IDENTIFIER.test(project))
811
- throw usage('aiwg.config effects tenant and project must be identifiers', 'config-invalid');
812
- const scope = { tenant, project, subsystem };
813
- const env = deps.env ?? process.env;
814
- const account = config.keyProvider?.account ?? `ledger/${tenant}/${project}/${subsystem}`;
815
- const session = { projectDir, scope, config, account, deps, env };
735
+ const session = await effectSession(projectDir, subsystem, deps);
816
736
  let rendered;
817
737
  switch (sub) {
818
738
  case 'id':
@@ -56,7 +56,7 @@ export function parseGuidedSetupFlags(args, command) {
56
56
  const separator = raw.indexOf('=');
57
57
  const provider = raw.slice(0, separator) === 'jev' ? 'jev' : normalizeProviderDefinitionId(raw.slice(0, separator));
58
58
  if (separator < 1 || !provider || provider === 'generic')
59
- throw new Error('--credential requires a known <provider>=env:NAME|file:PATH|stdin.');
59
+ throw new Error('--credential requires a known <provider>=stdin|file:PATH|store:NAME|env:NAME.');
60
60
  credentials[provider] = credentialReference(raw.slice(separator + 1));
61
61
  }
62
62
  if (tokenStdin) {
@@ -191,7 +191,7 @@ export const initHandler = {
191
191
  --intent code|docs|ops|mixed Workspace usage intent
192
192
  --jev enable|decline|defer Optional external decision service (default off)
193
193
  --region <r> --endpoint <url> --verify --remove --token-stdin Existing Jev options
194
- --credential <provider>=env:NAME|file:PATH|stdin Credential reference only
194
+ --credential <provider>=stdin|file:PATH|store:NAME|env:NAME Credential reference only
195
195
  --providers <list> Provider IDs
196
196
  --default-scripts true|false Include default scripts
197
197
  --custom-scripts true|false Include custom scripts
@@ -18,6 +18,7 @@
18
18
  * @implements #804
19
19
  */
20
20
  import path from 'path';
21
+ import { sensitiveInputOptions } from '../../auth/sensitive-input.js';
21
22
  import { installPackage } from '../../packages/registry.js';
22
23
  import { recordDeployment } from '../../packages/package-registry.js';
23
24
  import { marketplaceConfigDir, resolveVerificationPolicy } from '../../marketplace/exchange.js';
@@ -59,6 +60,7 @@ export const installHandler = {
59
60
  ' --deploy Deploy immediately after install',
60
61
  ' --provider <name> Target provider (claude, copilot, cursor...)',
61
62
  ' --target <dir> Project directory to deploy into',
63
+ ' --token-interactive | --token-stdin | --token-file <path> | --token-store <name> | --token-env <name>',
62
64
  ' --refresh Force re-pull even if cached',
63
65
  ' --ref <tag-or-sha> Resolve this ref and lock its immutable commit',
64
66
  ' --package <id> Select one wrapper when a repository contains several',
@@ -69,6 +71,9 @@ export const installHandler = {
69
71
  ].join('\n'),
70
72
  };
71
73
  }
74
+ if (/^https?:\/\/[^/]*@/.test(rawRef)) {
75
+ return { exitCode: 1, message: 'Credentials in Git URLs are refused; use --token-stdin, --token-file, --token-store or --token-env.' };
76
+ }
72
77
  const deploy = hasFlag(ctx.args, '--deploy');
73
78
  const refresh = hasFlag(ctx.args, '--refresh');
74
79
  const provider = parseFlag(ctx.args, '--provider') ?? 'claude';
@@ -97,6 +102,7 @@ export const installHandler = {
97
102
  const resolvedPolicy = await resolveVerificationPolicy(policyName, scope);
98
103
  const installed = await installPackage(rawRef, {
99
104
  refresh,
105
+ tokenInput: sensitiveInputOptions(ctx.args, 'token'),
100
106
  ref: parseFlag(ctx.args, '--ref'),
101
107
  packageSelector: parseFlag(ctx.args, '--package'),
102
108
  verify,
@@ -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 {