@aiwg/cli 2026.10.5 → 2026.10.7
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/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +7 -3
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +57 -6
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +143 -14
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +114 -55
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/decision.js +22 -3
- package/dist/src/cli/handlers/effect.js +4 -84
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/marketplace.js +15 -4
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +1 -0
- package/dist/src/cli/handlers/setup.js +432 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +335 -107
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +81 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/gitignore.js +4 -0
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +482 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +23 -18
- package/dist/src/decision/qualification/load-staged.js +576 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +12 -5
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +238 -0
- package/dist/src/discovery/plan-apply.js +567 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/effects/host.js +91 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-local-gitignore.js +2 -0
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +22 -96
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/grok-build-config.mjs +2 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +30 -6
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/project-registry.js +2 -1
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/mission-protocol/worker-status.js +91 -1
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +8 -1
- package/dist/src/models/worker-installation.mjs +231 -38
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +124 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +10 -9
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +63 -53
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/workspace-discovery.js +3 -4
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/update/notifier.mjs +2 -4
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +24 -14
- package/tools/agents/providers/claude.mjs +6 -3
- package/tools/agents/providers/codex.mjs +54 -57
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +13 -4
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +12 -15
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +31 -5
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +289 -80
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +13 -3
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +49 -8
- package/tools/ralph-external/session-launcher.mjs +405 -35
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
package/dist/src/mcp/cli.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { startServer, createServer } from './server.mjs';
|
|
10
10
|
import { assertConfigDestination, assertConfigObject, assertProjectCredentials, isPathInsideDir, classifyMcpDestination, projectCredentialRemedy, writeConfigAtomic, writeConfigTransaction } from './config-file.mjs';
|
|
11
|
+
import { replaceServer } from './toml-editor.mjs';
|
|
11
12
|
import fs from 'fs/promises';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
import os from 'os';
|
|
@@ -335,16 +336,14 @@ enabled_tools = [
|
|
|
335
336
|
if (error.code !== 'ENOENT') throw error;
|
|
336
337
|
}
|
|
337
338
|
|
|
338
|
-
//
|
|
339
|
-
|
|
339
|
+
// Parse before deciding whether to write, including already configured files.
|
|
340
|
+
const edited = replaceServer(existing, 'aiwg', tomlContent.trim());
|
|
341
|
+
if (edited.alreadyPresent) {
|
|
340
342
|
console.log('AIWG MCP already configured in ~/.codex/config.toml');
|
|
341
343
|
return true;
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
|
|
345
|
-
const updated = existing.trimEnd() + '\n' + tomlContent.trim() + '\n';
|
|
346
|
-
|
|
347
|
-
await writeConfigAtomic(configPath, updated, { userScope: true });
|
|
346
|
+
await writeConfigAtomic(configPath, edited.text, { userScope: true });
|
|
348
347
|
console.log(`MCP configuration appended to: ${configPath}`);
|
|
349
348
|
console.log(`\nTo use AIWG MCP server with Codex:`);
|
|
350
349
|
console.log(` 1. Restart Codex CLI`);
|
|
@@ -1125,7 +1124,7 @@ Usage:
|
|
|
1125
1124
|
aiwg mcp profile import <file>
|
|
1126
1125
|
aiwg mcp profile export <name> [--out <file>]
|
|
1127
1126
|
aiwg mcp profile init-presets
|
|
1128
|
-
aiwg mcp profile login <name> [--api-key-stdin | --api-key-file <path> | --api-key-env <ENV_VAR_NAME>]
|
|
1127
|
+
aiwg mcp profile login <name> [--api-key-interactive | --api-key-stdin | --api-key-file <path> | --api-key-store <name> | --api-key-env <ENV_VAR_NAME>]
|
|
1129
1128
|
|
|
1130
1129
|
Profiles let you define named subsets of your registered MCP servers:
|
|
1131
1130
|
aiwg mcp profile add dev --servers git-gitea,memory-fortemi --description "Dev work"
|
|
@@ -1163,10 +1162,10 @@ Logging in to a Codex profile's isolated runtime home (#3062):
|
|
|
1163
1162
|
$OPENAI_KEY; visible to every other
|
|
1164
1163
|
child process this shell starts
|
|
1165
1164
|
In every case the key reaches codex over stdin only; it never appears on
|
|
1166
|
-
argv, in logs, or in error messages.
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
docs/
|
|
1165
|
+
argv, in logs, or in error messages. Use --api-key-store <name> for a named
|
|
1166
|
+
OS-store entry, or --api-key-interactive for hidden terminal entry. Give
|
|
1167
|
+
sources in preference order; a missing source without a terminal errors naming the options.
|
|
1168
|
+
See docs/cli/sensitive-values.md for exposure details and manager examples.
|
|
1170
1169
|
`);
|
|
1171
1170
|
}
|
|
1172
1171
|
|
|
@@ -1420,59 +1419,18 @@ async function handleProfileInitPresets() {
|
|
|
1420
1419
|
}
|
|
1421
1420
|
}
|
|
1422
1421
|
|
|
1423
|
-
/**
|
|
1424
|
-
* Read all of stdin and return it as one trimmed string (go/no-go review,
|
|
1425
|
-
* blocker 1C's `--api-key-stdin`). Mirrors `src/cli/handlers/decision.ts`'s
|
|
1426
|
-
* own private `readStdin()` + `.trim()` for `--token-stdin` — duplicated
|
|
1427
|
-
* rather than imported for the same source/dist reason documented on
|
|
1428
|
-
* `requireFlagValue` above (this file runs both compiled and directly from
|
|
1429
|
-
* source). Input is expected to be a single line; a value containing
|
|
1430
|
-
* additional lines is not specially rejected, matching that precedent.
|
|
1431
|
-
*/
|
|
1432
|
-
async function readStdinAll() {
|
|
1433
|
-
const chunks = [];
|
|
1434
|
-
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
1435
|
-
return Buffer.concat(chunks).toString('utf8');
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
/**
|
|
1439
|
-
* Handle `aiwg mcp profile login <name> [--api-key-env <ENV_VAR_NAME> |
|
|
1440
|
-
* --api-key-stdin | --api-key-file <path>]` (#3062, #3069, go/no-go review
|
|
1441
|
-
* blocker 1C).
|
|
1442
|
-
*
|
|
1443
|
-
* Wires `loginInProfile()` (src/mcp/adapters/codex-runtime.ts), which
|
|
1444
|
-
* already implements the full headless-safe behavior this command needs:
|
|
1445
|
-
* with a terminal attached and none of the three given, it runs the
|
|
1446
|
-
* interactive `codex login` OAuth flow. Exactly one credential source may
|
|
1447
|
-
* be given; more than one is a usage error naming the flags given. In
|
|
1448
|
-
* every case the key reaches `loginInProfile()` only as an in-memory value
|
|
1449
|
-
* (or, for `--api-key-env`, a variable *name*) — never on argv, in logs, or
|
|
1450
|
-
* in an error message — and travels to `codex` only on its stdin (see
|
|
1451
|
-
* `spawnCodexLoginWithApiKey()`'s own doc comment). Without a terminal and
|
|
1452
|
-
* without any of the three, `loginInProfile()` itself refuses, naming all
|
|
1453
|
-
* three flags.
|
|
1454
|
-
*
|
|
1455
|
-
* `--api-key-env` reads the key from that named environment variable, as
|
|
1456
|
-
* before. `--api-key-stdin` reads one line from this process's own stdin
|
|
1457
|
-
* (refusing if stdin is a terminal, or if it is empty) — the least
|
|
1458
|
-
* observable source, since the key then never exists in any environment or
|
|
1459
|
-
* on any command line. `--api-key-file <path>` reads the key from a file
|
|
1460
|
-
* that `readApiKeyFromFile()` requires to be a non-symlink, single-hard-
|
|
1461
|
-
* link, regular file owned by the current user with no group/other
|
|
1462
|
-
* permission bits on POSIX — refused otherwise, without ever printing the
|
|
1463
|
-
* path's contents. `--api-key-env` remains the least preferred of the
|
|
1464
|
-
* three (see "Supplying credentials safely" in the docs): the named
|
|
1465
|
-
* variable is visible to every other child process this shell starts.
|
|
1466
|
-
*/
|
|
1422
|
+
/** Standard sensitive input; the Codex adapter still receives only an in-memory key. */
|
|
1467
1423
|
async function handleProfileLogin(args) {
|
|
1468
1424
|
let apiKeyEnv;
|
|
1469
1425
|
let apiKeyFilePath;
|
|
1426
|
+
let apiKeyStore;
|
|
1470
1427
|
try {
|
|
1471
1428
|
// rejectEmpty: an explicitly-given-but-empty variable name/path can
|
|
1472
1429
|
// never name a real environment variable or file (#3062 second review,
|
|
1473
1430
|
// finding 4; go/no-go review blocker 1C).
|
|
1474
1431
|
apiKeyEnv = requireFlagValue(args, { rejectEmpty: true }, '--api-key-env');
|
|
1475
1432
|
apiKeyFilePath = requireFlagValue(args, { rejectEmpty: true }, '--api-key-file');
|
|
1433
|
+
apiKeyStore = requireFlagValue(args, { rejectEmpty: true }, '--api-key-store');
|
|
1476
1434
|
} catch (err) {
|
|
1477
1435
|
if (err instanceof MissingFlagValueError) {
|
|
1478
1436
|
console.error(`${err.message}.`);
|
|
@@ -1482,16 +1440,6 @@ async function handleProfileLogin(args) {
|
|
|
1482
1440
|
}
|
|
1483
1441
|
const useStdin = args.includes('--api-key-stdin');
|
|
1484
1442
|
|
|
1485
|
-
const sourcesGiven = [
|
|
1486
|
-
apiKeyEnv !== undefined ? '--api-key-env' : null,
|
|
1487
|
-
useStdin ? '--api-key-stdin' : null,
|
|
1488
|
-
apiKeyFilePath !== undefined ? '--api-key-file' : null,
|
|
1489
|
-
].filter(Boolean);
|
|
1490
|
-
if (sourcesGiven.length > 1) {
|
|
1491
|
-
console.error(`Only one credential source may be given; got ${sourcesGiven.join(', ')}.`);
|
|
1492
|
-
process.exit(2);
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
1443
|
// Skip --api-key-env's/--api-key-file's own value when hunting for the
|
|
1496
1444
|
// positional profile name, so e.g. `login --api-key-env FOO` (missing
|
|
1497
1445
|
// name) never misreads FOO as the name (mirrors this file's own
|
|
@@ -1501,46 +1449,24 @@ async function handleProfileLogin(args) {
|
|
|
1501
1449
|
for (let i = 0; i < args.length; i += 1) {
|
|
1502
1450
|
const token = args[i];
|
|
1503
1451
|
if (token === '--') break;
|
|
1504
|
-
if (token === '--api-key-env' || token === '--api-key-file') { i += 1; continue; }
|
|
1452
|
+
if (token === '--api-key-env' || token === '--api-key-file' || token === '--api-key-store') { i += 1; continue; }
|
|
1505
1453
|
if (!token.startsWith('--')) positional.push(token);
|
|
1506
1454
|
}
|
|
1507
1455
|
const name = positional[0];
|
|
1508
1456
|
if (!name) {
|
|
1509
|
-
console.error('Usage: aiwg mcp profile login <name> [--api-key-
|
|
1457
|
+
console.error('Usage: aiwg mcp profile login <name> [--api-key-interactive | --api-key-stdin | --api-key-file <path> | --api-key-store <name> | --api-key-env <ENV_VAR_NAME>]');
|
|
1510
1458
|
process.exit(1);
|
|
1511
1459
|
}
|
|
1512
1460
|
|
|
1513
|
-
let apiKey;
|
|
1514
|
-
if (useStdin) {
|
|
1515
|
-
if (process.stdin.isTTY) {
|
|
1516
|
-
console.error(
|
|
1517
|
-
'--api-key-stdin requires piped input, not a terminal; run ' +
|
|
1518
|
-
`\`aiwg mcp profile login ${name}\` with no credential flag for the interactive login instead.`,
|
|
1519
|
-
);
|
|
1520
|
-
process.exit(2);
|
|
1521
|
-
}
|
|
1522
|
-
apiKey = (await readStdinAll()).trim();
|
|
1523
|
-
if (!apiKey) {
|
|
1524
|
-
console.error('--api-key-stdin received no key (stdin was empty).');
|
|
1525
|
-
process.exit(2);
|
|
1526
|
-
}
|
|
1527
|
-
} else if (apiKeyFilePath !== undefined) {
|
|
1528
|
-
const { readApiKeyFromFile } = await loadCodexRuntimeAdapter();
|
|
1529
|
-
try {
|
|
1530
|
-
apiKey = await readApiKeyFromFile(apiKeyFilePath);
|
|
1531
|
-
} catch (err) {
|
|
1532
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1533
|
-
process.exit(2);
|
|
1534
|
-
}
|
|
1535
|
-
if (!apiKey) {
|
|
1536
|
-
console.error(`--api-key-file "${apiKeyFilePath}" is empty.`);
|
|
1537
|
-
process.exit(2);
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
1461
|
try {
|
|
1462
|
+
await loadCodexRuntimeAdapter();
|
|
1463
|
+
const { resolveSensitiveInput } = await import(pathToFileURL(path.join(findOwnPackageRoot(), 'dist', 'src', 'auth', 'sensitive-input.js')).href);
|
|
1464
|
+
const apiKey = await resolveSensitiveInput({
|
|
1465
|
+
prefix: 'api-key', stdin: useStdin, file: apiKeyFilePath, store: apiKeyStore,
|
|
1466
|
+
env: apiKeyEnv, interactive: args.includes('--api-key-interactive'),
|
|
1467
|
+
});
|
|
1542
1468
|
const { loginInProfile } = await loadCodexRuntimeAdapter();
|
|
1543
|
-
const loginOptions =
|
|
1469
|
+
const loginOptions = { apiKey };
|
|
1544
1470
|
await loginInProfile(name, loginOptions);
|
|
1545
1471
|
console.log(`\nLogged in to profile "${name}".`);
|
|
1546
1472
|
} catch (err) {
|
|
@@ -187,11 +187,15 @@ export async function writeConfigAtomic(file, content, { userScope = false, proj
|
|
|
187
187
|
if (rejectSymlinkParents) await assertNoSymlinkParents(file, symlinkRoot);
|
|
188
188
|
const info = await assertConfigDestination(file, projectRoot);
|
|
189
189
|
const mode = restoreMode ?? (userScope ? 0o600 : info ? info.mode & 0o7777 : newFileMode ?? 0o666);
|
|
190
|
-
|
|
190
|
+
// Restrict newly created parents for private settings, ownership and output files.
|
|
191
|
+
// Recursive mkdir leaves every existing directory's permissions unchanged.
|
|
192
|
+
await mkdir(dirname(file), { recursive: true, mode: (mode & 0o077) === 0 ? 0o700 : undefined });
|
|
191
193
|
const temporary = `${file}.${randomUUID()}.tmp`;
|
|
192
194
|
let handle;
|
|
195
|
+
let created = false;
|
|
193
196
|
try {
|
|
194
197
|
handle = await open(temporary, 'wx', mode);
|
|
198
|
+
created = true;
|
|
195
199
|
await handle.writeFile(content, 'utf-8');
|
|
196
200
|
if (userScope || info || restoreMode !== undefined || newFileMode !== undefined) await handle.chmod(mode);
|
|
197
201
|
await handle.sync();
|
|
@@ -204,7 +208,7 @@ export async function writeConfigAtomic(file, content, { userScope = false, proj
|
|
|
204
208
|
try {
|
|
205
209
|
if (handle) await handle.close();
|
|
206
210
|
} finally {
|
|
207
|
-
await unlink(temporary).catch(() => {});
|
|
211
|
+
if (created) await unlink(temporary).catch(() => {});
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
}
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import { lstat, mkdtemp, readFile, readlink, realpath, rmdir } from 'fs/promises';
|
|
19
19
|
import { basename, dirname, isAbsolute, posix, relative, resolve, sep, win32 } from 'path';
|
|
20
|
-
import { assertConfigDestination, assertNoSymlinkParents, writeConfigAtomic } from './config-file.mjs';
|
|
21
|
-
import { resolveCredentialPolicy, stricterCredentialPolicy, warnCredentialPolicyRelaxation, warnDroppedOverlayPolicy } from './credentials.mjs';
|
|
20
|
+
import { assertConfigDestination, assertConfigObject, assertNoSymlinkParents, writeConfigAtomic } from './config-file.mjs';
|
|
21
|
+
import { credentialPolicyViolations, resolveCredentialPolicy, stricterCredentialPolicy, warnCredentialPolicyRelaxation, warnDroppedOverlayPolicy } from './credentials.mjs';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* `platform` lets tests validate Windows path semantics (`C:\org`, UNC
|
|
@@ -62,9 +62,15 @@ export function resolveConfigLayers(configDirOverride, env = process.env, { plat
|
|
|
62
62
|
return layers.length > 0 ? layers : null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
async function readLayer(path) {
|
|
65
|
+
async function readLayer(path, collection) {
|
|
66
66
|
try {
|
|
67
|
-
|
|
67
|
+
const parsed = JSON.parse(await readFile(path, 'utf-8'));
|
|
68
|
+
try {
|
|
69
|
+
assertConfigObject(parsed, collection);
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error(`Invalid MCP configuration layer ${path}: expected an object root and an object ${collection} map`);
|
|
72
|
+
}
|
|
73
|
+
return parsed;
|
|
68
74
|
} catch (error) {
|
|
69
75
|
if (error?.code === 'ENOENT') return null;
|
|
70
76
|
if (error instanceof SyntaxError) throw new Error(`Invalid JSON in MCP configuration layer ${path}`);
|
|
@@ -115,7 +121,7 @@ export async function loadLayered(layers, filename, collection, defaults) {
|
|
|
115
121
|
let lowerPolicySource;
|
|
116
122
|
const writeLayer = layers.length - 1;
|
|
117
123
|
for (const [index, dir] of layers.entries()) {
|
|
118
|
-
const parsed = await readLayer(resolve(dir, filename));
|
|
124
|
+
const parsed = await readLayer(resolve(dir, filename), collection);
|
|
119
125
|
if (!parsed) continue;
|
|
120
126
|
const { [collection]: entries, ...rest } = parsed;
|
|
121
127
|
if (index === writeLayer) {
|
|
@@ -298,16 +304,26 @@ export async function assertLayerWriteDestination(layers, filename) {
|
|
|
298
304
|
/** Persist atomically, then refresh ownership only after a successful write. */
|
|
299
305
|
export async function saveLayerData(file, data, collection, layers, layering, explicitPolicy = false) {
|
|
300
306
|
if (layers) await assertLayerWriteDestination(layers, basename(file));
|
|
307
|
+
// Validate the current bytes even if the caller loaded and cached an older
|
|
308
|
+
// version, or called save(data) on a fresh standalone registry.
|
|
309
|
+
await readLayer(file, collection);
|
|
301
310
|
const persisted = layering ? writeLayerData(data, collection, layering, explicitPolicy) : data;
|
|
302
311
|
// The active identity layer file is always 0600: it is the personal write
|
|
303
312
|
// layer of an active AIWG_CONFIG_LAYERS configuration and may carry secrets.
|
|
304
313
|
// Without layering, `file` is whatever single directory the caller
|
|
305
314
|
// configured (which may be a shared, group-readable organisation checkout
|
|
306
315
|
// maintained directly, not a private identity directory): an existing file
|
|
307
|
-
// keeps its mode
|
|
316
|
+
// keeps its mode unless the persisted servers carry literal credentials,
|
|
317
|
+
// while a newly created file starts private at 0600.
|
|
318
|
+
const info = layers ? null : await assertConfigDestination(file);
|
|
319
|
+
const tighten = info && (info.mode & 0o077) !== 0 && collection === 'servers'
|
|
320
|
+
&& Object.values(persisted[collection] || {}).some(server => credentialPolicyViolations(server, 'references').length > 0);
|
|
308
321
|
await writeConfigAtomic(file, JSON.stringify(persisted, null, 2) + '\n', layers
|
|
309
322
|
? { userScope: true, rejectSymlinkParents: true }
|
|
310
|
-
: { newFileMode: 0o600, rejectSymlinkParents: true });
|
|
323
|
+
: { newFileMode: 0o600, rejectSymlinkParents: true, ...(tighten ? { mode: 0o600 } : {}) });
|
|
324
|
+
if (tighten) {
|
|
325
|
+
console.warn(`Warning: tightened MCP registry ${file} from ${(info.mode & 0o7777).toString(8).padStart(4, '0')} to 0600 because it contains literal credentials.`);
|
|
326
|
+
}
|
|
311
327
|
if (layering) {
|
|
312
328
|
layering.own = new Set(Object.keys(persisted[collection] || {}));
|
|
313
329
|
layering.ownFields = new Set(Object.keys(persisted).filter(field => field !== collection));
|
|
@@ -3,6 +3,7 @@ import { constants } from 'node:fs';
|
|
|
3
3
|
import { access, lstat, readFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, relative, resolve } from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { helperEnvironment } from '../process/cli-command.mjs';
|
|
6
7
|
import { replaceServer } from './toml-editor.mjs';
|
|
7
8
|
import { tomlString as string } from './toml-string.mjs';
|
|
8
9
|
|
|
@@ -144,7 +145,7 @@ export async function unmanageGrokBuildMcp(configPath, names, options = {}) {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
function runJson(binary, args, cwd, env) {
|
|
147
|
-
const run = spawnSync(binary, args, { cwd, env, encoding: 'utf8', timeout: 30_000, killSignal: 'SIGKILL' });
|
|
148
|
+
const run = spawnSync(binary, args, { cwd, env: helperEnvironment(env), encoding: 'utf8', timeout: 30_000, killSignal: 'SIGKILL' });
|
|
148
149
|
if (run.error || run.status !== 0) return { ok: false, detail: run.error?.message || run.stderr || `exit ${run.status}` };
|
|
149
150
|
try { return { ok: true, value: JSON.parse(run.stdout || '{}') }; }
|
|
150
151
|
catch { return { ok: false, detail: 'Grok returned malformed JSON' }; }
|
|
@@ -4,6 +4,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import { resolveMuseXdgSkillsDir } from '../providers/muse-paths.js';
|
|
7
|
+
import { helperEnvironment } from '../process/cli-command.mjs';
|
|
7
8
|
|
|
8
9
|
// Mirrors tools/agents/providers/muse-hooks.mjs. src/ cannot import tools/:
|
|
9
10
|
// tools/ is not part of the compiled dist/ tree.
|
|
@@ -47,7 +48,7 @@ function deepEqual(a, b) {
|
|
|
47
48
|
|
|
48
49
|
function inspectMuseCli(env) {
|
|
49
50
|
const result = spawnSync('muse', ['--version'], {
|
|
50
|
-
env,
|
|
51
|
+
env: helperEnvironment(env),
|
|
51
52
|
encoding: 'utf8',
|
|
52
53
|
timeout: MUSE_VERSION_TIMEOUT_MS,
|
|
53
54
|
killSignal: 'SIGKILL',
|
package/dist/src/mcp/profiles.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @implements #889
|
|
11
11
|
*/
|
|
12
|
-
import { resolveCredentialPolicy } from './credentials.mjs';
|
|
13
12
|
import { isLowerLayerEntry, loadLayered, resolveProfileExtends, resolveConfigLayers, saveLayerData } from './config-layers.mjs';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
13
|
+
import { writeConfigAtomic } from './config-file.mjs';
|
|
14
|
+
import { readFile } from 'fs/promises';
|
|
15
|
+
import { parse, resolve } from 'path';
|
|
16
16
|
import { resolveConfigDir } from '../config/user-config.js';
|
|
17
17
|
// ─────────────────────────────────────────────
|
|
18
18
|
// Constants
|
|
@@ -81,38 +81,21 @@ export class McpProfileRegistry {
|
|
|
81
81
|
async load() {
|
|
82
82
|
if (this.cache)
|
|
83
83
|
return this.cache;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.layering = layered.layering;
|
|
88
|
-
return this.cache;
|
|
89
|
-
}
|
|
90
|
-
const filePath = this.getPath();
|
|
91
|
-
try {
|
|
92
|
-
const content = await readFile(filePath, 'utf-8');
|
|
93
|
-
const parsed = JSON.parse(content);
|
|
94
|
-
this.cache = {
|
|
95
|
-
...DEFAULT_DATA,
|
|
96
|
-
...parsed,
|
|
97
|
-
profiles: parsed.profiles || {},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
this.cache = { ...DEFAULT_DATA, profiles: {} };
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
resolveCredentialPolicy({ registryPolicy: this.cache.credentialPolicy, env: {} });
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
this.cache = null;
|
|
108
|
-
throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
109
|
-
}
|
|
84
|
+
const loaded = await loadLayered(this.layers ?? [this.configDir], PROFILES_FILENAME, 'profiles', DEFAULT_DATA);
|
|
85
|
+
this.cache = loaded.data;
|
|
86
|
+
this.layering = this.layers ? loaded.layering : null;
|
|
110
87
|
return this.cache;
|
|
111
88
|
}
|
|
112
89
|
async save(data = this.cache) {
|
|
113
90
|
if (!data)
|
|
114
91
|
return;
|
|
115
|
-
|
|
92
|
+
// Load ownership before a fresh save so inherited fields stay in their layer.
|
|
93
|
+
let layering = this.layering;
|
|
94
|
+
if (this.layers && !layering) {
|
|
95
|
+
await this.load();
|
|
96
|
+
layering = this.layering;
|
|
97
|
+
}
|
|
98
|
+
await saveLayerData(this.getPath(), data, 'profiles', this.layers, layering);
|
|
116
99
|
this.cache = data;
|
|
117
100
|
if (this.layers) {
|
|
118
101
|
this.clearCache();
|
|
@@ -311,7 +294,11 @@ export class McpProfileRegistry {
|
|
|
311
294
|
const toExport = profileName
|
|
312
295
|
? { [profileName]: data.profiles[profileName] }
|
|
313
296
|
: data.profiles;
|
|
314
|
-
await
|
|
297
|
+
await writeConfigAtomic(filePath, JSON.stringify({ profiles: toExport }, null, 2) + '\n', {
|
|
298
|
+
userScope: true,
|
|
299
|
+
rejectSymlinkParents: true,
|
|
300
|
+
symlinkRoot: parse(resolve(filePath)).root,
|
|
301
|
+
});
|
|
315
302
|
}
|
|
316
303
|
async initPresets() {
|
|
317
304
|
const data = structuredClone(await this.load());
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
* @implements #889
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { resolveCredentialPolicy } from './credentials.mjs';
|
|
14
13
|
import { isLowerLayerEntry, loadLayered, resolveProfileExtends, resolveConfigLayers, saveLayerData } from './config-layers.mjs';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { writeConfigAtomic } from './config-file.mjs';
|
|
15
|
+
import { readFile } from 'fs/promises';
|
|
16
|
+
import { parse, resolve } from 'path';
|
|
17
17
|
import { homedir } from 'os';
|
|
18
18
|
import { existsSync } from 'fs';
|
|
19
19
|
|
|
@@ -112,39 +112,21 @@ export class McpProfileRegistry {
|
|
|
112
112
|
async load() {
|
|
113
113
|
if (this.#cache) return this.#cache;
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.#layering = layered.layering;
|
|
119
|
-
return this.#cache;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const filePath = this.getPath();
|
|
123
|
-
try {
|
|
124
|
-
const content = await readFile(filePath, 'utf-8');
|
|
125
|
-
const parsed = JSON.parse(content);
|
|
126
|
-
this.#cache = {
|
|
127
|
-
...DEFAULT_DATA,
|
|
128
|
-
...parsed,
|
|
129
|
-
profiles: parsed.profiles || {},
|
|
130
|
-
};
|
|
131
|
-
} catch {
|
|
132
|
-
this.#cache = { ...DEFAULT_DATA, profiles: {} };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
resolveCredentialPolicy({ registryPolicy: this.#cache.credentialPolicy, env: {} });
|
|
137
|
-
} catch (error) {
|
|
138
|
-
this.#cache = null;
|
|
139
|
-
throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
115
|
+
const loaded = await loadLayered(this.#layers ?? [this.#configDir], PROFILES_FILENAME, 'profiles', DEFAULT_DATA);
|
|
116
|
+
this.#cache = loaded.data;
|
|
117
|
+
this.#layering = this.#layers ? loaded.layering : null;
|
|
142
118
|
return this.#cache;
|
|
143
119
|
}
|
|
144
120
|
|
|
145
121
|
async save(data = this.#cache) {
|
|
146
122
|
if (!data) return;
|
|
147
|
-
|
|
123
|
+
// Load ownership before a fresh save so inherited fields stay in their layer.
|
|
124
|
+
let layering = this.#layering;
|
|
125
|
+
if (this.#layers && !layering) {
|
|
126
|
+
await this.load();
|
|
127
|
+
layering = this.#layering;
|
|
128
|
+
}
|
|
129
|
+
await saveLayerData(this.getPath(), data, 'profiles', this.#layers, layering);
|
|
148
130
|
this.#cache = data;
|
|
149
131
|
if (this.#layers) {
|
|
150
132
|
this.clearCache();
|
|
@@ -402,7 +384,11 @@ export class McpProfileRegistry {
|
|
|
402
384
|
throw new Error(`Profile "${profileName}" not found.`);
|
|
403
385
|
}
|
|
404
386
|
|
|
405
|
-
await
|
|
387
|
+
await writeConfigAtomic(filePath, JSON.stringify({ profiles: toExport }, null, 2) + '\n', {
|
|
388
|
+
userScope: true,
|
|
389
|
+
rejectSymlinkParents: true,
|
|
390
|
+
symlinkRoot: parse(resolve(filePath)).root,
|
|
391
|
+
});
|
|
406
392
|
}
|
|
407
393
|
|
|
408
394
|
/** Install preset profiles (does not overwrite existing) */
|
package/dist/src/mcp/registry.js
CHANGED
|
@@ -120,32 +120,9 @@ export class McpServerRegistry {
|
|
|
120
120
|
async load() {
|
|
121
121
|
if (this.cache)
|
|
122
122
|
return this.cache;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.layering = layered.layering;
|
|
127
|
-
return this.cache;
|
|
128
|
-
}
|
|
129
|
-
const filePath = this.getPath();
|
|
130
|
-
try {
|
|
131
|
-
const content = await readFile(filePath, 'utf-8');
|
|
132
|
-
const parsed = JSON.parse(content);
|
|
133
|
-
this.cache = {
|
|
134
|
-
...DEFAULT_REGISTRY,
|
|
135
|
-
...parsed,
|
|
136
|
-
servers: parsed.servers || {},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
this.cache = { ...DEFAULT_REGISTRY, servers: {} };
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
resolveCredentialPolicy({ registryPolicy: this.cache.credentialPolicy, env: {} });
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
this.cache = null;
|
|
147
|
-
throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
148
|
-
}
|
|
123
|
+
const loaded = await loadLayered(this.layers ?? [this.configDir], REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
|
|
124
|
+
this.cache = loaded.data;
|
|
125
|
+
this.layering = this.layers ? loaded.layering : null;
|
|
149
126
|
return this.cache;
|
|
150
127
|
}
|
|
151
128
|
/** Save the registry to disk */
|
|
@@ -536,6 +513,9 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
|
|
|
536
513
|
return result;
|
|
537
514
|
}
|
|
538
515
|
async function injectToml(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null) {
|
|
516
|
+
// A deliberately aliased Codex home is trusted; descendants below it are not.
|
|
517
|
+
const destinationRoot = projectRoot ?? (process.env.CODEX_HOME || resolve(process.env.HOME || process.env.USERPROFILE || homedir(), '.codex'));
|
|
518
|
+
await assertConfigDestination(configPath, destinationRoot);
|
|
539
519
|
let existing = '';
|
|
540
520
|
try {
|
|
541
521
|
existing = await readFile(configPath, 'utf-8');
|
|
@@ -553,7 +533,7 @@ async function injectToml(registry, servers, configPath, provider, dryRun, resul
|
|
|
553
533
|
result.serversInjected.push(server.name);
|
|
554
534
|
}
|
|
555
535
|
if (!dryRun) {
|
|
556
|
-
await writeConfigAtomic(configPath, existing, { userScope, projectRoot });
|
|
536
|
+
await writeConfigAtomic(configPath, existing, { userScope, projectRoot: destinationRoot });
|
|
557
537
|
for (const server of servers) {
|
|
558
538
|
await registry.recordInjection(server.name, provider);
|
|
559
539
|
}
|
|
@@ -115,33 +115,9 @@ export class McpServerRegistry {
|
|
|
115
115
|
async load() {
|
|
116
116
|
if (this.#cache) return this.#cache;
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.#layering = layered.layering;
|
|
122
|
-
return this.#cache;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const filePath = this.getPath();
|
|
126
|
-
try {
|
|
127
|
-
const content = await readFile(filePath, 'utf-8');
|
|
128
|
-
const parsed = JSON.parse(content);
|
|
129
|
-
this.#cache = {
|
|
130
|
-
...DEFAULT_REGISTRY,
|
|
131
|
-
...parsed,
|
|
132
|
-
servers: parsed.servers || {},
|
|
133
|
-
};
|
|
134
|
-
} catch {
|
|
135
|
-
this.#cache = { ...DEFAULT_REGISTRY, servers: {} };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
resolveCredentialPolicy({ registryPolicy: this.#cache.credentialPolicy, env: {} });
|
|
140
|
-
} catch (error) {
|
|
141
|
-
this.#cache = null;
|
|
142
|
-
throw new Error(`Invalid MCP credential policy in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
118
|
+
const loaded = await loadLayered(this.#layers ?? [this.#configDir], REGISTRY_FILENAME, 'servers', DEFAULT_REGISTRY);
|
|
119
|
+
this.#cache = loaded.data;
|
|
120
|
+
this.#layering = this.#layers ? loaded.layering : null;
|
|
145
121
|
return this.#cache;
|
|
146
122
|
}
|
|
147
123
|
|
|
@@ -488,6 +464,9 @@ async function injectJson(registry, servers, configPath, provider, dryRun, resul
|
|
|
488
464
|
}
|
|
489
465
|
|
|
490
466
|
async function injectToml(registry, servers, configPath, provider, dryRun, result, userScope, projectRoot, toolPlan = null) {
|
|
467
|
+
// A deliberately aliased Codex home is trusted; descendants below it are not.
|
|
468
|
+
const destinationRoot = projectRoot ?? (process.env.CODEX_HOME || resolve(process.env.HOME || process.env.USERPROFILE || homedir(), '.codex'));
|
|
469
|
+
await assertConfigDestination(configPath, destinationRoot);
|
|
491
470
|
let existing = '';
|
|
492
471
|
try {
|
|
493
472
|
existing = await readFile(configPath, 'utf-8');
|
|
@@ -504,7 +483,7 @@ async function injectToml(registry, servers, configPath, provider, dryRun, resul
|
|
|
504
483
|
}
|
|
505
484
|
|
|
506
485
|
if (!dryRun) {
|
|
507
|
-
await writeConfigAtomic(configPath, existing, { userScope, projectRoot });
|
|
486
|
+
await writeConfigAtomic(configPath, existing, { userScope, projectRoot: destinationRoot });
|
|
508
487
|
|
|
509
488
|
for (const server of servers) {
|
|
510
489
|
await registry.recordInjection(server.name, provider);
|
package/dist/src/mcp/server.mjs
CHANGED
|
@@ -34,6 +34,27 @@ const FRAMEWORKS = {
|
|
|
34
34
|
'media-marketing-kit': path.join(AIWG_ROOT, 'agentic/code/frameworks/media-marketing-kit')
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
/** Keep the published object contracts stable across native Zod converters. */
|
|
38
|
+
function retainToolJsonSchema(schema, seen = new Set()) {
|
|
39
|
+
if (!(schema instanceof z.ZodType) || seen.has(schema)) return;
|
|
40
|
+
seen.add(schema);
|
|
41
|
+
const definition = schema._zod.def;
|
|
42
|
+
if (schema instanceof z.ZodObject) {
|
|
43
|
+
const catchall = definition.catchall;
|
|
44
|
+
if ((!catchall && Object.keys(schema.shape).length > 0) || catchall instanceof z.ZodNever || catchall instanceof z.ZodUnknown) {
|
|
45
|
+
z.globalRegistry.add(schema, { ...z.globalRegistry.get(schema), additionalProperties: catchall instanceof z.ZodUnknown });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (schema instanceof z.ZodRecord && definition.keyType instanceof z.ZodString && !definition.keyType._zod.def.checks?.length) {
|
|
49
|
+
z.globalRegistry.add(schema, { ...z.globalRegistry.get(schema), propertyNames: undefined });
|
|
50
|
+
}
|
|
51
|
+
for (const value of Object.values(definition)) {
|
|
52
|
+
if (value instanceof z.ZodType) retainToolJsonSchema(value, seen);
|
|
53
|
+
else if (Array.isArray(value)) for (const child of value) retainToolJsonSchema(child, seen);
|
|
54
|
+
else if (value && typeof value === 'object') for (const child of Object.values(value)) retainToolJsonSchema(child, seen);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
37
58
|
/**
|
|
38
59
|
* Create and configure the AIWG MCP Server
|
|
39
60
|
*/
|
|
@@ -43,6 +64,18 @@ export async function createServer() {
|
|
|
43
64
|
version: '1.0.0'
|
|
44
65
|
});
|
|
45
66
|
|
|
67
|
+
const registerTool = server.registerTool.bind(server);
|
|
68
|
+
server.registerTool = (name, config, handler) => {
|
|
69
|
+
const schemas = {};
|
|
70
|
+
for (const key of ['inputSchema', 'outputSchema']) {
|
|
71
|
+
if (!config[key]) continue;
|
|
72
|
+
const schema = config[key] instanceof z.ZodType ? config[key] : z.object(config[key]);
|
|
73
|
+
retainToolJsonSchema(schema);
|
|
74
|
+
schemas[key] = schema;
|
|
75
|
+
}
|
|
76
|
+
return registerTool(name, { ...config, ...schemas }, handler);
|
|
77
|
+
};
|
|
78
|
+
|
|
46
79
|
// ============================================
|
|
47
80
|
// TOOLS
|
|
48
81
|
// ============================================
|
|
@@ -248,7 +281,7 @@ export async function createServer() {
|
|
|
248
281
|
description: 'Render AIWG template with provided variables',
|
|
249
282
|
inputSchema: {
|
|
250
283
|
template: z.string().describe('Template path (e.g., "sdlc/intake/project-intake-template.md")'),
|
|
251
|
-
variables: z.record(z.string()).optional().describe('Template variable substitutions')
|
|
284
|
+
variables: z.record(z.string(), z.string()).optional().describe('Template variable substitutions')
|
|
252
285
|
}
|
|
253
286
|
}, async ({ template, variables = {} }) => {
|
|
254
287
|
try {
|