@ashlr/hub 3.4.0 → 3.5.1
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/CHANGELOG.md +107 -1
- package/README.md +3 -0
- package/dist/build-identity.json +2 -2
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/local-runtime.js +40 -9
- package/dist/cli/local-runtime.js.map +1 -1
- package/dist/core/fleet/external-skill-audit.js +1 -1
- package/dist/core/fleet/external-skill-audit.js.map +1 -1
- package/dist/core/local-eval/classify.d.ts +57 -0
- package/dist/core/local-eval/classify.js +121 -0
- package/dist/core/local-eval/classify.js.map +1 -0
- package/dist/core/local-eval/configuration.d.ts +37 -0
- package/dist/core/local-eval/configuration.js +171 -0
- package/dist/core/local-eval/configuration.js.map +1 -0
- package/dist/core/local-eval/index.d.ts +15 -0
- package/dist/core/local-eval/index.js +16 -0
- package/dist/core/local-eval/index.js.map +1 -0
- package/dist/core/local-eval/main.d.ts +31 -0
- package/dist/core/local-eval/main.js +174 -0
- package/dist/core/local-eval/main.js.map +1 -0
- package/dist/core/local-eval/report.d.ts +25 -0
- package/dist/core/local-eval/report.js +157 -0
- package/dist/core/local-eval/report.js.map +1 -0
- package/dist/core/local-eval/runner.d.ts +55 -0
- package/dist/core/local-eval/runner.js +290 -0
- package/dist/core/local-eval/runner.js.map +1 -0
- package/dist/core/local-eval/tasks.d.ts +22 -0
- package/dist/core/local-eval/tasks.js +303 -0
- package/dist/core/local-eval/tasks.js.map +1 -0
- package/dist/core/local-eval/trace.d.ts +67 -0
- package/dist/core/local-eval/trace.js +309 -0
- package/dist/core/local-eval/trace.js.map +1 -0
- package/dist/core/local-eval/types.d.ts +262 -0
- package/dist/core/local-eval/types.js +23 -0
- package/dist/core/local-eval/types.js.map +1 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-host.d.ts +68 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-host.js +120 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-host.js.map +1 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-process.d.ts +15 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-process.js +21 -0
- package/dist/core/local-runtime/llama/anthropic-proxy-process.js.map +1 -0
- package/dist/core/local-runtime/llama/anthropic-proxy.d.ts +2 -1
- package/dist/core/local-runtime/llama/anthropic-proxy.js +14 -6
- package/dist/core/local-runtime/llama/anthropic-proxy.js.map +1 -1
- package/dist/core/local-runtime/llama/anthropic-shim.d.ts +33 -0
- package/dist/core/local-runtime/llama/anthropic-shim.js +59 -0
- package/dist/core/local-runtime/llama/anthropic-shim.js.map +1 -1
- package/dist/core/local-runtime/llama/config.d.ts +123 -6
- package/dist/core/local-runtime/llama/config.js +196 -6
- package/dist/core/local-runtime/llama/config.js.map +1 -1
- package/dist/core/local-runtime/llama/index.d.ts +15 -0
- package/dist/core/local-runtime/llama/index.js +13 -0
- package/dist/core/local-runtime/llama/index.js.map +1 -1
- package/dist/core/local-runtime/llama/paths.d.ts +17 -0
- package/dist/core/local-runtime/llama/paths.js +25 -0
- package/dist/core/local-runtime/llama/paths.js.map +1 -1
- package/dist/core/local-runtime/llama/proxy-invocation.d.ts +101 -0
- package/dist/core/local-runtime/llama/proxy-invocation.js +134 -0
- package/dist/core/local-runtime/llama/proxy-invocation.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-launchd.d.ts +83 -0
- package/dist/core/local-runtime/llama/proxy-launchd.js +216 -0
- package/dist/core/local-runtime/llama/proxy-launchd.js.map +1 -0
- package/dist/core/local-runtime/llama/shapes.d.ts +2 -1
- package/dist/core/local-runtime/llama/shapes.js +2 -1
- package/dist/core/local-runtime/llama/shapes.js.map +1 -1
- package/dist/core/verse/adapters/claude.js +16 -0
- package/dist/core/verse/adapters/claude.js.map +1 -1
- package/dist/core/verse/adapters/codex.js +60 -2
- package/dist/core/verse/adapters/codex.js.map +1 -1
- package/dist/core/verse/adapters/grok.js +13 -0
- package/dist/core/verse/adapters/grok.js.map +1 -1
- package/dist/core/verse/control-api.d.ts +8 -9
- package/dist/core/verse/control-api.js +14 -114
- package/dist/core/verse/control-api.js.map +1 -1
- package/dist/core/verse/github-api.d.ts +50 -0
- package/dist/core/verse/github-api.js +211 -0
- package/dist/core/verse/github-api.js.map +1 -0
- package/dist/core/verse/github-proposal.d.ts +64 -0
- package/dist/core/verse/github-proposal.js +186 -0
- package/dist/core/verse/github-proposal.js.map +1 -0
- package/dist/core/verse/github-repo.d.ts +104 -0
- package/dist/core/verse/github-repo.js +476 -0
- package/dist/core/verse/github-repo.js.map +1 -0
- package/dist/core/verse/github-types.d.ts +174 -0
- package/dist/core/verse/github-types.js +31 -0
- package/dist/core/verse/github-types.js.map +1 -0
- package/dist/core/verse/mcp-cli-health.d.ts +149 -0
- package/dist/core/verse/mcp-cli-health.js +227 -0
- package/dist/core/verse/mcp-cli-health.js.map +1 -0
- package/dist/core/verse/mcp-control-api.d.ts +68 -0
- package/dist/core/verse/mcp-control-api.js +322 -0
- package/dist/core/verse/mcp-control-api.js.map +1 -0
- package/dist/core/verse/mcp-mutation.d.ts +187 -0
- package/dist/core/verse/mcp-mutation.js +471 -0
- package/dist/core/verse/mcp-mutation.js.map +1 -0
- package/dist/core/verse/mcp-scope.d.ts +114 -0
- package/dist/core/verse/mcp-scope.js +149 -0
- package/dist/core/verse/mcp-scope.js.map +1 -0
- package/dist/core/verse/mcp-seat-view.d.ts +197 -0
- package/dist/core/verse/mcp-seat-view.js +322 -0
- package/dist/core/verse/mcp-seat-view.js.map +1 -0
- package/dist/core/verse/path-guard.d.ts +115 -0
- package/dist/core/verse/path-guard.js +171 -0
- package/dist/core/verse/path-guard.js.map +1 -0
- package/dist/core/verse/seats.js +30 -2
- package/dist/core/verse/seats.js.map +1 -1
- package/dist/core/verse/session-engine.js +60 -1
- package/dist/core/verse/session-engine.js.map +1 -1
- package/dist/core/verse/session-store.js +20 -0
- package/dist/core/verse/session-store.js.map +1 -1
- package/dist/core/verse/types.d.ts +225 -1
- package/dist/core/verse/types.js +26 -0
- package/dist/core/verse/types.js.map +1 -1
- package/dist/core/verse/verse-api.d.ts +16 -1
- package/dist/core/verse/verse-api.js +338 -16
- package/dist/core/verse/verse-api.js.map +1 -1
- package/dist/core/verse/workspaces.d.ts +130 -0
- package/dist/core/verse/workspaces.js +501 -0
- package/dist/core/verse/workspaces.js.map +1 -0
- package/dist/core/web/api.js +23 -0
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/public/next/assets/App-BbVoUCIi.js +7 -0
- package/dist/core/web/public/next/assets/App-BbVoUCIi.js.map +1 -0
- package/dist/core/web/public/next/assets/App-Cgi6j1Gk.css +1 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-BW9iVjUa.js +2 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-BW9iVjUa.js.map +1 -0
- package/dist/core/web/public/next/assets/ApprovalsSection-Su-NUTis.css +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js +2 -0
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js.map +1 -0
- package/dist/core/web/public/next/assets/AutonomySection-Cpk_oEhc.css +1 -0
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js +80 -0
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js.map +1 -0
- package/dist/core/web/public/next/assets/ChatSection-LQiAn6Eu.css +1 -0
- package/dist/core/web/public/next/assets/ConfirmDialog-CXevY3Au.js +2 -0
- package/dist/core/web/public/next/assets/ConfirmDialog-CXevY3Au.js.map +1 -0
- package/dist/core/web/public/next/assets/{ConfirmDialog-cYuivhCX.css → ConfirmDialog-Dx15x0Ho.css} +1 -1
- package/dist/core/web/public/next/assets/Dialog-Bn0CgEGz.js +2 -0
- package/dist/core/web/public/next/assets/Dialog-Bn0CgEGz.js.map +1 -0
- package/dist/core/web/public/next/assets/Dialog-tQ8kFulD.css +1 -0
- package/dist/core/web/public/next/assets/DiffViewer-C8HHTnVE.css +1 -0
- package/dist/core/web/public/next/assets/DiffViewer-QKyisN2L.js +7 -0
- package/dist/core/web/public/next/assets/DiffViewer-QKyisN2L.js.map +1 -0
- package/dist/core/web/public/next/assets/{MutationTokenDialog-jWLLCxWn.css → MutationTokenDialog-CrfJ0PdD.css} +1 -1
- package/dist/core/web/public/next/assets/MutationTokenDialog-DbSHPy3w.js +2 -0
- package/dist/core/web/public/next/assets/MutationTokenDialog-DbSHPy3w.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-C-PCgCzo.js +7 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-C-PCgCzo.js.map +1 -0
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-CM_8pJs6.css +1 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-Dw8ZjmkS.js +2 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-Dw8ZjmkS.js.map +1 -0
- package/dist/core/web/public/next/assets/RouteErrorBoundary-ekyMXAK8.css +1 -0
- package/dist/core/web/public/next/assets/SettingsSection-BII7TTSD.css +1 -0
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js +2 -0
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js.map +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Bk5HipM0.js +2 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Bk5HipM0.js.map +1 -0
- package/dist/core/web/public/next/assets/UniverseConsoleApp-Dh7A-hLD.css +1 -0
- package/dist/core/web/public/next/assets/UniverseView-Bqa8DH57.css +1 -0
- package/dist/core/web/public/next/assets/UniverseView-D8k9q0ce.js +9 -0
- package/dist/core/web/public/next/assets/UniverseView-D8k9q0ce.js.map +1 -0
- package/dist/core/web/public/next/assets/UsageSection-B2E06ZH1.js +2 -0
- package/dist/core/web/public/next/assets/UsageSection-B2E06ZH1.js.map +1 -0
- package/dist/core/web/public/next/assets/UsageSection-DwgNOYvi.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js.map +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-D2E3mU9a.css +1 -0
- package/dist/core/web/public/next/assets/charts-CsrtT4rk.css +1 -0
- package/dist/core/web/public/next/assets/charts-DSUpczxG.js +2 -0
- package/dist/core/web/public/next/assets/charts-DSUpczxG.js.map +1 -0
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js +2 -0
- package/dist/core/web/public/next/assets/fleet-model-ClBbQ0Uj.js.map +1 -0
- package/dist/core/web/public/next/assets/format-BNYWGJat.js +2 -0
- package/dist/core/web/public/next/assets/format-BNYWGJat.js.map +1 -0
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js +2 -0
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js.map +1 -0
- package/dist/core/web/public/next/assets/global-D463gG1X.css +1 -0
- package/dist/core/web/public/next/assets/icons-BxOX8-XB.js +2 -0
- package/dist/core/web/public/next/assets/icons-BxOX8-XB.js.map +1 -0
- package/dist/core/web/public/next/assets/index-BMRIET7F.js +11 -0
- package/dist/core/web/public/next/assets/index-BMRIET7F.js.map +1 -0
- package/dist/core/web/public/next/assets/queries-AxnVa02h.js +2 -0
- package/dist/core/web/public/next/assets/queries-AxnVa02h.js.map +1 -0
- package/dist/core/web/public/next/assets/use-guarded-action-BO6c94Wp.js +2 -0
- package/dist/core/web/public/next/assets/use-guarded-action-BO6c94Wp.js.map +1 -0
- package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js +2 -0
- package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js.map +1 -0
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js +2 -0
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js.map +1 -0
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/docs/README.md +4 -0
- package/node_modules/marked/README.md +8 -0
- package/node_modules/marked/bin/main.js +12 -11
- package/node_modules/marked/lib/marked.d.ts +88 -25
- package/node_modules/marked/lib/marked.esm.js +50 -44
- package/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/node_modules/marked/lib/marked.umd.js +50 -44
- package/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/node_modules/marked/man/marked.1 +1 -1
- package/node_modules/marked/package.json +21 -21
- package/package.json +13 -8
- package/dist/cli/activation.d.ts +0 -41
- package/dist/cli/activation.js +0 -485
- package/dist/cli/activation.js.map +0 -1
- package/dist/core/fleet/generated-repair-resolution.d.ts +0 -36
- package/dist/core/fleet/generated-repair-resolution.js +0 -245
- package/dist/core/fleet/generated-repair-resolution.js.map +0 -1
- package/dist/core/local-runtime/llama/port.d.ts +0 -17
- package/dist/core/local-runtime/llama/port.js +0 -47
- package/dist/core/local-runtime/llama/port.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-host.d.ts +0 -54
- package/dist/core/local-runtime/llama/proxy-host.js +0 -159
- package/dist/core/local-runtime/llama/proxy-host.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-process.d.ts +0 -148
- package/dist/core/local-runtime/llama/proxy-process.js +0 -250
- package/dist/core/local-runtime/llama/proxy-process.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-record.d.ts +0 -40
- package/dist/core/local-runtime/llama/proxy-record.js +0 -133
- package/dist/core/local-runtime/llama/proxy-record.js.map +0 -1
- package/dist/core/local-runtime/llama/proxy-supervisor.d.ts +0 -118
- package/dist/core/local-runtime/llama/proxy-supervisor.js +0 -511
- package/dist/core/local-runtime/llama/proxy-supervisor.js.map +0 -1
- package/dist/core/web/public/next/assets/App-BCKqSppK.css +0 -1
- package/dist/core/web/public/next/assets/App-LuILXjWG.js +0 -16
- package/dist/core/web/public/next/assets/App-LuILXjWG.js.map +0 -1
- package/dist/core/web/public/next/assets/ApprovalsSection-B_Xor8JH.js +0 -2
- package/dist/core/web/public/next/assets/ApprovalsSection-B_Xor8JH.js.map +0 -1
- package/dist/core/web/public/next/assets/ApprovalsSection-CRP59wSb.css +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-BB8RD-tv.css +0 -1
- package/dist/core/web/public/next/assets/AutonomySection-BEwITtPJ.js +0 -2
- package/dist/core/web/public/next/assets/AutonomySection-BEwITtPJ.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-DrxqhBPK.css +0 -1
- package/dist/core/web/public/next/assets/ChatSection-bRAzlMth.js +0 -77
- package/dist/core/web/public/next/assets/ChatSection-bRAzlMth.js.map +0 -1
- package/dist/core/web/public/next/assets/ConfirmDialog-BqkZdPs_.js +0 -2
- package/dist/core/web/public/next/assets/ConfirmDialog-BqkZdPs_.js.map +0 -1
- package/dist/core/web/public/next/assets/Dialog-CPmedg4S.js +0 -2
- package/dist/core/web/public/next/assets/Dialog-CPmedg4S.js.map +0 -1
- package/dist/core/web/public/next/assets/Dialog-DxN7RgB5.css +0 -1
- package/dist/core/web/public/next/assets/DiffViewer-DmPJbeM_.js +0 -7
- package/dist/core/web/public/next/assets/DiffViewer-DmPJbeM_.js.map +0 -1
- package/dist/core/web/public/next/assets/DiffViewer-hN7X6wGT.css +0 -1
- package/dist/core/web/public/next/assets/Epistemic-DiyMyeaN.css +0 -1
- package/dist/core/web/public/next/assets/Epistemic-M3SxLHjq.js +0 -2
- package/dist/core/web/public/next/assets/Epistemic-M3SxLHjq.js.map +0 -1
- package/dist/core/web/public/next/assets/Meter-CGBsbXUX.css +0 -1
- package/dist/core/web/public/next/assets/Meter-D_X7c9U3.js +0 -2
- package/dist/core/web/public/next/assets/Meter-D_X7c9U3.js.map +0 -1
- package/dist/core/web/public/next/assets/MutationTokenDialog-Ba5ZHu-8.js +0 -2
- package/dist/core/web/public/next/assets/MutationTokenDialog-Ba5ZHu-8.js.map +0 -1
- package/dist/core/web/public/next/assets/RefreshIndicator-O7jUBOmX.css +0 -1
- package/dist/core/web/public/next/assets/RefreshIndicator-vHGpmyzb.js +0 -2
- package/dist/core/web/public/next/assets/RefreshIndicator-vHGpmyzb.js.map +0 -1
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-B5g6JEVk.js +0 -7
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-B5g6JEVk.js.map +0 -1
- package/dist/core/web/public/next/assets/ResourcePoolConsoleApp-DLl4DbGG.css +0 -1
- package/dist/core/web/public/next/assets/RouteErrorBoundary-BIpdnxp-.css +0 -1
- package/dist/core/web/public/next/assets/RouteErrorBoundary-CroxuKoi.js +0 -2
- package/dist/core/web/public/next/assets/RouteErrorBoundary-CroxuKoi.js.map +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-DGJMdUh2.css +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-p-7FCGfn.js +0 -2
- package/dist/core/web/public/next/assets/SettingsSection-p-7FCGfn.js.map +0 -1
- package/dist/core/web/public/next/assets/Skeleton-4qq4HuJO.js +0 -2
- package/dist/core/web/public/next/assets/Skeleton-4qq4HuJO.js.map +0 -1
- package/dist/core/web/public/next/assets/Skeleton-D2JaTXyv.css +0 -1
- package/dist/core/web/public/next/assets/StatusBadge-BbEdqEGn.js +0 -2
- package/dist/core/web/public/next/assets/StatusBadge-BbEdqEGn.js.map +0 -1
- package/dist/core/web/public/next/assets/StatusBadge-TpDqZf6K.css +0 -1
- package/dist/core/web/public/next/assets/TableView-R6Yeztp1.css +0 -1
- package/dist/core/web/public/next/assets/TableView-nyYs7Sv1.js +0 -2
- package/dist/core/web/public/next/assets/TableView-nyYs7Sv1.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-CiZZxRPV.css +0 -1
- package/dist/core/web/public/next/assets/UniverseConsoleApp-DjTpQUrA.js +0 -2
- package/dist/core/web/public/next/assets/UniverseConsoleApp-DjTpQUrA.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseView-BtApwayT.js +0 -9
- package/dist/core/web/public/next/assets/UniverseView-BtApwayT.js.map +0 -1
- package/dist/core/web/public/next/assets/UniverseView-gBxl6yum.css +0 -1
- package/dist/core/web/public/next/assets/UsageSection-Db99N6ao.js +0 -2
- package/dist/core/web/public/next/assets/UsageSection-Db99N6ao.js.map +0 -1
- package/dist/core/web/public/next/assets/UsageSection-UUYDidOg.css +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-COa3J2dO.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-COa3J2dO.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-mOAXvWa6.css +0 -1
- package/dist/core/web/public/next/assets/fleet-model-DXCfVtNW.js +0 -2
- package/dist/core/web/public/next/assets/fleet-model-DXCfVtNW.js.map +0 -1
- package/dist/core/web/public/next/assets/format-fggwu_Rs.js +0 -2
- package/dist/core/web/public/next/assets/format-fggwu_Rs.js.map +0 -1
- package/dist/core/web/public/next/assets/global-BeRYd1Bu.css +0 -1
- package/dist/core/web/public/next/assets/global-CNVapoV1.js +0 -2
- package/dist/core/web/public/next/assets/global-CNVapoV1.js.map +0 -1
- package/dist/core/web/public/next/assets/index-BkUlX2Se.js +0 -51
- package/dist/core/web/public/next/assets/index-BkUlX2Se.js.map +0 -1
- package/dist/core/web/public/next/assets/index-YNTgDG2b.js +0 -2
- package/dist/core/web/public/next/assets/index-YNTgDG2b.js.map +0 -1
- package/dist/core/web/public/next/assets/queries-C9XS4SYq.js +0 -2
- package/dist/core/web/public/next/assets/queries-C9XS4SYq.js.map +0 -1
- package/dist/core/web/public/next/assets/use-guarded-action-Bqa2v0a-.css +0 -1
- package/dist/core/web/public/next/assets/use-guarded-action-Bql1nA-z.js +0 -2
- package/dist/core/web/public/next/assets/use-guarded-action-Bql1nA-z.js.map +0 -1
- package/dist/core/web/public/next/assets/verse-model-IrNQKtER.js +0 -2
- package/dist/core/web/public/next/assets/verse-model-IrNQKtER.js.map +0 -1
- package/node_modules/which/CHANGELOG.md +0 -166
- /package/node_modules/marked/{LICENSE.md → LICENSE} +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/verse/mcp-scope.ts — MCP scope, as reported by Locus.
|
|
3
|
+
*
|
|
4
|
+
* docs/VERSE-WORKSPACES.md §3: "a server appropriate for a personal account
|
|
5
|
+
* may be wrong for a client's. Locus already models exactly this — principal,
|
|
6
|
+
* tenant, sealed session — so scope should be asked of Locus rather than
|
|
7
|
+
* reinvented in the hub, and the hub should hold opaque references rather
|
|
8
|
+
* than copies of anything sensitive."
|
|
9
|
+
*
|
|
10
|
+
* So this module OWNS NO SCOPE. It asks `src/core/integrations/locus.ts` and
|
|
11
|
+
* projects the answer onto a transport shape. Two rules follow from that:
|
|
12
|
+
*
|
|
13
|
+
* 1. Every field here is an OPAQUE REFERENCE Locus already publishes in its
|
|
14
|
+
* own `agent report --json` (alias, tenant, binding_id, principal). None
|
|
15
|
+
* of them is a credential, and none of them is derived, inferred or
|
|
16
|
+
* cached by the hub. When Locus says nothing, this says nothing — it
|
|
17
|
+
* never substitutes a hub-local notion of "which account is this".
|
|
18
|
+
* 2. Mutation admission is NOT decided here either. It is decided by the
|
|
19
|
+
* repo's existing pre-mutate gate (`assertLocusPreMutate`), whose mode
|
|
20
|
+
* ladder (off | warn | enforce) is already the contract every other
|
|
21
|
+
* spawn site in this repo obeys — see src/core/run/engines.ts:662. A new
|
|
22
|
+
* surface inventing a stricter or looser rule than the dispatch path is
|
|
23
|
+
* exactly the "reinvented in the hub" failure this file exists to avoid.
|
|
24
|
+
*
|
|
25
|
+
* Pure projection + one shell-out through locus.ts. Never throws.
|
|
26
|
+
*/
|
|
27
|
+
import { assertLocusPreMutate, locusAgentReport, } from '../integrations/locus.js';
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Projection
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
const UNAVAILABLE = {
|
|
32
|
+
available: false,
|
|
33
|
+
pinned: false,
|
|
34
|
+
aliasRef: null,
|
|
35
|
+
tenantRef: null,
|
|
36
|
+
principalRef: null,
|
|
37
|
+
bindingRef: null,
|
|
38
|
+
sealOk: null,
|
|
39
|
+
expired: null,
|
|
40
|
+
frozen: null,
|
|
41
|
+
expiresAt: null,
|
|
42
|
+
status: null,
|
|
43
|
+
statusOneline: null,
|
|
44
|
+
reason: 'mcp-scope-locus-unavailable',
|
|
45
|
+
};
|
|
46
|
+
/** A non-empty string, or null. Locus's optional fields arrive as either. */
|
|
47
|
+
function ref(value) {
|
|
48
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
49
|
+
}
|
|
50
|
+
/** A boolean, or null when Locus did not report the flag at all. */
|
|
51
|
+
function flag(value) {
|
|
52
|
+
return typeof value === 'boolean' ? value : null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Project one `locus agent report --json` probe onto {@link VerseMcpScope}.
|
|
56
|
+
*
|
|
57
|
+
* Pure: pass a canned {@link LocusProbeResult} in tests rather than shelling
|
|
58
|
+
* out. An unavailable probe, a null report and a null pin are three different
|
|
59
|
+
* facts and each gets its own reason.
|
|
60
|
+
*/
|
|
61
|
+
export function projectVerseMcpScope(probe) {
|
|
62
|
+
if (!probe.available || probe.report === null)
|
|
63
|
+
return UNAVAILABLE;
|
|
64
|
+
const report = probe.report;
|
|
65
|
+
const pin = report.pin ?? null;
|
|
66
|
+
const base = {
|
|
67
|
+
available: true,
|
|
68
|
+
status: ref(report.status),
|
|
69
|
+
statusOneline: ref(report.status_oneline),
|
|
70
|
+
};
|
|
71
|
+
if (pin === null || pin.pinned !== true) {
|
|
72
|
+
return {
|
|
73
|
+
...UNAVAILABLE,
|
|
74
|
+
...base,
|
|
75
|
+
pinned: false,
|
|
76
|
+
reason: 'mcp-scope-unpinned',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const sealOk = flag(pin.seal_ok);
|
|
80
|
+
const expired = flag(pin.expired);
|
|
81
|
+
// Order matters: an expired pin whose seal never verified is reported as
|
|
82
|
+
// seal-unverified, because that is the more fundamental failure and the one
|
|
83
|
+
// the operator has to fix first.
|
|
84
|
+
const reason = sealOk === false ? 'mcp-scope-seal-unverified'
|
|
85
|
+
: expired === true ? 'mcp-scope-expired'
|
|
86
|
+
: 'mcp-scope-pinned';
|
|
87
|
+
return {
|
|
88
|
+
...base,
|
|
89
|
+
pinned: true,
|
|
90
|
+
aliasRef: ref(pin.alias),
|
|
91
|
+
tenantRef: ref(pin.tenant),
|
|
92
|
+
principalRef: ref(pin.principal),
|
|
93
|
+
bindingRef: ref(pin.binding_id),
|
|
94
|
+
sealOk,
|
|
95
|
+
expired,
|
|
96
|
+
frozen: flag(pin.frozen),
|
|
97
|
+
expiresAt: ref(pin.expires_at),
|
|
98
|
+
reason,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Ask Locus for the current scope.
|
|
103
|
+
*
|
|
104
|
+
* `read` is the injection seam: production passes nothing and gets the real
|
|
105
|
+
* `locusAgentReport()` shell-out; tests pass a function returning a canned
|
|
106
|
+
* probe and never spawn anything. Never throws — locusAgentReport() already
|
|
107
|
+
* reports failure as `{available: false}` rather than raising.
|
|
108
|
+
*/
|
|
109
|
+
export function readVerseMcpScope(read = locusAgentReport) {
|
|
110
|
+
try {
|
|
111
|
+
return projectVerseMcpScope(read());
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return UNAVAILABLE;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The scope a write would land under, plus the repo's existing admission
|
|
119
|
+
* decision for it.
|
|
120
|
+
*
|
|
121
|
+
* `decide` and `read` are injection seams for tests. In production both shell
|
|
122
|
+
* out to Locus once each.
|
|
123
|
+
*/
|
|
124
|
+
export function readVerseMcpScopeGate(options = {}) {
|
|
125
|
+
const scope = readVerseMcpScope(options.read ?? locusAgentReport);
|
|
126
|
+
let decision;
|
|
127
|
+
try {
|
|
128
|
+
decision = options.decide
|
|
129
|
+
? options.decide()
|
|
130
|
+
// `arguments.length >= 2` inside assertLocusPreMutate distinguishes
|
|
131
|
+
// "config omitted -> read ~/.ashlr" from "config null -> no config", so
|
|
132
|
+
// the two calls must stay separate rather than passing `undefined`.
|
|
133
|
+
: 'config' in options
|
|
134
|
+
? assertLocusPreMutate(options.env, options.config)
|
|
135
|
+
: assertLocusPreMutate(options.env);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Fail closed on an unexpected throw: an undecidable gate is not an open
|
|
139
|
+
// one for a surface that installs arbitrary code.
|
|
140
|
+
decision = { allow: false, mode: 'enforce', blockers: ['locus pre-mutate gate could not be evaluated'] };
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
scope,
|
|
144
|
+
mode: decision.mode,
|
|
145
|
+
allow: decision.allow,
|
|
146
|
+
blockers: [...decision.blockers],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=mcp-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-scope.js","sourceRoot":"","sources":["../../../src/core/verse/mcp-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EACL,oBAAoB,EACpB,gBAAgB,GAIjB,MAAM,0BAA0B,CAAC;AA8DlC,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,MAAM,WAAW,GAAkB;IACjC,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,IAAI;IACf,YAAY,EAAE,IAAI;IAClB,UAAU,EAAE,IAAI;IAChB,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,IAAI;IACZ,aAAa,EAAE,IAAI;IACnB,MAAM,EAAE,6BAA6B;CACtC,CAAC;AAEF,6EAA6E;AAC7E,SAAS,GAAG,CAAC,KAAc;IACzB,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED,oEAAoE;AACpE,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAuB;IAC1D,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IAElE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC;IAC/B,MAAM,IAAI,GAAG;QACX,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;QAC1B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC;KAC1C,CAAC;IAEF,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO;YACL,GAAG,WAAW;YACd,GAAG,IAAI;YACP,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,oBAAoB;SAC7B,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAElC,yEAAyE;IACzE,4EAA4E;IAC5E,iCAAiC;IACjC,MAAM,MAAM,GACV,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,2BAA2B;QAC5C,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,mBAAmB;YACtC,CAAC,CAAC,kBAAkB,CAAC;IAE3B,OAAO;QACL,GAAG,IAAI;QACP,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;QAC1B,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;QAChC,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC;QAC/B,MAAM;QACN,OAAO;QACP,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;QACxB,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC;QAC9B,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA+B,gBAAgB;IAC/E,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAKlC,EAAE;IACJ,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,IAAI,IAAI,gBAAgB,CAAC,CAAC;IAElE,IAAI,QAAwE,CAAC;IAC7E,IAAI,CAAC;QACH,QAAQ,GAAG,OAAO,CAAC,MAAM;YACvB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE;YAClB,oEAAoE;YACpE,wEAAwE;YACxE,oEAAoE;YACpE,CAAC,CAAC,QAAQ,IAAI,OAAO;gBACnB,CAAC,CAAC,oBAAoB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC;gBACnD,CAAC,CAAC,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,kDAAkD;QAClD,QAAQ,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,8CAA8C,CAAC,EAAE,CAAC;IAC3G,CAAC;IAED,OAAO;QACL,KAAK;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/verse/mcp-seat-view.ts — which MCP servers each Verse seat would load.
|
|
3
|
+
*
|
|
4
|
+
* READ-ONLY. Nothing in this file writes, installs, enables or disables
|
|
5
|
+
* anything. docs/VERSE-WORKSPACES.md §3 asks for exactly one thing first:
|
|
6
|
+
* "Surface which servers each seat would load, since a server configured for
|
|
7
|
+
* one account should not silently apply to another."
|
|
8
|
+
*
|
|
9
|
+
* ── The finding this module exists to make visible ──────────────────────────
|
|
10
|
+
*
|
|
11
|
+
* `discoverMcpServers()` scans HOME-level JSON configs (see
|
|
12
|
+
* `knownConfigPaths()`): ~/.claude.json, ~/.claude/settings.json, ~/.mcp.json,
|
|
13
|
+
* ~/.ashlrcode/settings.json, ~/.ashlr-workbench/settings.json,
|
|
14
|
+
* ~/.aw/settings.json, ~/.ashlr/settings.json.
|
|
15
|
+
*
|
|
16
|
+
* NO VERSE SEAT READS ANY OF THEM. Verified against the adapters and the
|
|
17
|
+
* launcher, not recalled:
|
|
18
|
+
*
|
|
19
|
+
* - CLAUDE and LOCAL seats: `buildClaudeLaunch` (src/core/verse/adapters/
|
|
20
|
+
* claude.ts) puts `--strict-mcp-config --mcp-config '{"mcpServers":{}}'`
|
|
21
|
+
* in every turn's argv, for both engines. That is a complete override:
|
|
22
|
+
* the seat loads ZERO MCP servers, whatever any config file says.
|
|
23
|
+
* - CODEX and GROK seats: their adapters add no MCP flag, so each loads its
|
|
24
|
+
* OWN native config — and the account is pinned by a private per-account
|
|
25
|
+
* state directory (`CODEX_HOME` / `GROK_HOME` / `CLAUDE_CONFIG_DIR` =
|
|
26
|
+
* `<profile>/native-state`, src/core/resources/native-profile.ts:72-78,
|
|
27
|
+
* :108). That directory is per ACCOUNT, so two Codex accounts already
|
|
28
|
+
* cannot share a server by accident. Their config is `config.toml` —
|
|
29
|
+
* TOML, which `discoverMcpServers()` (JSON only) cannot read.
|
|
30
|
+
*
|
|
31
|
+
* Reporting a machine-wide server list as "what this seat runs" would
|
|
32
|
+
* therefore be a lie in every direction at once: it would show servers no
|
|
33
|
+
* seat loads, and hide the per-account TOML that Codex and Grok actually do.
|
|
34
|
+
* So this module reports the two things separately and names the reason for
|
|
35
|
+
* each, and reports EMPTY rather than guessing.
|
|
36
|
+
*
|
|
37
|
+
* ── What may leave this module ──────────────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* Never: a launcher argv, an absolute native-profile path, a home-anchored
|
|
40
|
+
* absolute path, or a real env value. Config files are identified by their
|
|
41
|
+
* path RELATIVE TO $HOME (`.claude.json`, `.ashlr/settings.json`), which
|
|
42
|
+
* names the file precisely and carries no username. Every spec crosses the
|
|
43
|
+
* boundary through `redactEnv()`, whose whole reason for existing is that
|
|
44
|
+
* these specs carry secrets.
|
|
45
|
+
*/
|
|
46
|
+
import type { McpServerSpec } from '../types.js';
|
|
47
|
+
import type { VerseEngine } from './types.js';
|
|
48
|
+
import { type VerseAccountIdentity } from './accounts.js';
|
|
49
|
+
import { type VerseMcpScope } from './mcp-scope.js';
|
|
50
|
+
/**
|
|
51
|
+
* Engines whose adapter argv pins an EMPTY `--mcp-config` under
|
|
52
|
+
* `--strict-mcp-config`, so the seat can load nothing at all.
|
|
53
|
+
*
|
|
54
|
+
* Both entries come from one function: `buildClaudeLaunch` serves engine
|
|
55
|
+
* 'claude' and engine 'local' alike (the latter is the same `claude` binary
|
|
56
|
+
* with ANTHROPIC_BASE_URL pointed at Ollama).
|
|
57
|
+
*
|
|
58
|
+
* If that argv ever loses those two flags this constant becomes a lie, which
|
|
59
|
+
* is why `test/verse-mcp-seat-view.test.ts` asserts the flags are still in the
|
|
60
|
+
* adapter's real output rather than trusting this list.
|
|
61
|
+
*/
|
|
62
|
+
export declare const VERSE_MCP_ISOLATED_ENGINES: readonly VerseEngine[];
|
|
63
|
+
/** The two argv flags that make an isolated seat isolated. Asserted in tests. */
|
|
64
|
+
export declare const VERSE_MCP_ISOLATION_FLAGS: readonly string[];
|
|
65
|
+
/** Why a seat loads what it loads. Machine-readable, verbatim, never prose. */
|
|
66
|
+
export type VerseMcpSeatReason =
|
|
67
|
+
/** The adapter pins an empty --mcp-config: this seat loads nothing. */
|
|
68
|
+
'mcp-seat-isolated-by-adapter'
|
|
69
|
+
/** The account's private config file does not exist. */
|
|
70
|
+
| 'mcp-account-config-absent'
|
|
71
|
+
/** The config exists but is TOML, which this JSON-only discovery does not parse. */
|
|
72
|
+
| 'mcp-account-config-not-json'
|
|
73
|
+
/** The config exists, is JSON, and was read. */
|
|
74
|
+
| 'mcp-account-config-read'
|
|
75
|
+
/** The config exists but could not be read or parsed. */
|
|
76
|
+
| 'mcp-account-config-unreadable'
|
|
77
|
+
/** The account's private state directory could not be resolved. */
|
|
78
|
+
| 'mcp-account-profile-unresolved';
|
|
79
|
+
/**
|
|
80
|
+
* One MCP server, safe to serialize.
|
|
81
|
+
*
|
|
82
|
+
* `command` and `args` are shown IN FULL and verbatim: docs/VERSE-WORKSPACES.md
|
|
83
|
+
* §3 requires the operator to read what they are about to run. `env` keys are
|
|
84
|
+
* shown; every env VALUE is the literal `'<set>'` from `redactEnv`.
|
|
85
|
+
*/
|
|
86
|
+
export interface VerseMcpServerView {
|
|
87
|
+
name: string;
|
|
88
|
+
command: string;
|
|
89
|
+
args: string[];
|
|
90
|
+
/** Keys only — every value is `'<set>'`. Null when the spec sets no env. */
|
|
91
|
+
env: Record<string, string> | null;
|
|
92
|
+
/** Config file path RELATIVE TO $HOME. Never absolute, never a username. */
|
|
93
|
+
sourceRef: string;
|
|
94
|
+
}
|
|
95
|
+
/** What one seat would load, and why. */
|
|
96
|
+
export interface VerseMcpSeatView {
|
|
97
|
+
seatId: string;
|
|
98
|
+
label: string;
|
|
99
|
+
engine: VerseEngine;
|
|
100
|
+
accountId: string;
|
|
101
|
+
/** Servers this seat would actually load. Empty is a real answer. */
|
|
102
|
+
servers: VerseMcpServerView[];
|
|
103
|
+
reason: VerseMcpSeatReason;
|
|
104
|
+
/**
|
|
105
|
+
* The account config file this seat reads, named relative to the account's
|
|
106
|
+
* own private state directory (e.g. `config.toml`). The directory itself is
|
|
107
|
+
* private and is never published.
|
|
108
|
+
*/
|
|
109
|
+
configFile: string | null;
|
|
110
|
+
configFormat: 'json' | 'toml' | null;
|
|
111
|
+
/** Plain facts the UI must show rather than implying a fault. */
|
|
112
|
+
notes: string[];
|
|
113
|
+
}
|
|
114
|
+
/** Servers configured on this machine that no Verse seat reads. */
|
|
115
|
+
export interface VerseMcpMachineRegistry {
|
|
116
|
+
servers: VerseMcpServerView[];
|
|
117
|
+
/**
|
|
118
|
+
* TRUE when at least one server is configured machine-wide. Combined with
|
|
119
|
+
* every seat reporting none, that is the fact worth surfacing loudly.
|
|
120
|
+
*/
|
|
121
|
+
configured: boolean;
|
|
122
|
+
note: string;
|
|
123
|
+
}
|
|
124
|
+
export interface VerseMcpSnapshot {
|
|
125
|
+
sampledAt: string;
|
|
126
|
+
seats: VerseMcpSeatView[];
|
|
127
|
+
machine: VerseMcpMachineRegistry;
|
|
128
|
+
scope: VerseMcpScope;
|
|
129
|
+
notes: string[];
|
|
130
|
+
}
|
|
131
|
+
export declare const VERSE_MCP_ISOLATED_NOTE: string;
|
|
132
|
+
export declare const VERSE_MCP_TOML_NOTE: string;
|
|
133
|
+
export declare const VERSE_MCP_PER_ACCOUNT_NOTE: string;
|
|
134
|
+
export declare const VERSE_MCP_MACHINE_UNUSED_NOTE: string;
|
|
135
|
+
export declare const VERSE_MCP_MACHINE_EMPTY_NOTE = "No MCP servers are configured in this machine's home-level configs.";
|
|
136
|
+
/**
|
|
137
|
+
* Name a config file by its path relative to $HOME (`.claude.json`,
|
|
138
|
+
* `.ashlr/settings.json`). Precise enough to act on, and free of the username
|
|
139
|
+
* that an absolute path would carry.
|
|
140
|
+
*
|
|
141
|
+
* A path outside $HOME is reported as `(outside home)` — its basename alone
|
|
142
|
+
* would be ambiguous and its full path is not ours to publish.
|
|
143
|
+
*/
|
|
144
|
+
export declare function homeRelativeSourceRef(path: string, home?: string): string;
|
|
145
|
+
/** Project one discovered spec for transport, env values redacted. */
|
|
146
|
+
export declare function projectMcpServerView(spec: McpServerSpec, home?: string): VerseMcpServerView;
|
|
147
|
+
/**
|
|
148
|
+
* Resolve an account's private native state directory WITHOUT publishing it.
|
|
149
|
+
*
|
|
150
|
+
* connections.json carries each account's launcher argv. That argv is the
|
|
151
|
+
* account's identity and never leaves the server (src/core/verse/seats.ts:9).
|
|
152
|
+
* We read it here only to locate `<profile>/profile.json`, whose
|
|
153
|
+
* `nativeStatePath` is the directory the provider CLI is pinned to
|
|
154
|
+
* (`CODEX_HOME` / `GROK_HOME` / `CLAUDE_CONFIG_DIR`).
|
|
155
|
+
*
|
|
156
|
+
* Returns null rather than guessing when anything is missing.
|
|
157
|
+
*/
|
|
158
|
+
export declare function resolveAccountStateRoots(accountsRoot: string): Map<string, string>;
|
|
159
|
+
/** The minimum a seat must carry for this view. Keeps tests free of fixtures. */
|
|
160
|
+
export interface VerseMcpSeatInput {
|
|
161
|
+
id: string;
|
|
162
|
+
label: string;
|
|
163
|
+
engine: VerseEngine;
|
|
164
|
+
accountId: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Derive what one seat would load.
|
|
168
|
+
*
|
|
169
|
+
* `stateRoots` maps accountId -> private state directory (from
|
|
170
|
+
* {@link resolveAccountStateRoots}). A seat whose account is absent from it
|
|
171
|
+
* reports `mcp-account-profile-unresolved` rather than falling back to a
|
|
172
|
+
* machine-wide list.
|
|
173
|
+
*/
|
|
174
|
+
export declare function deriveVerseMcpSeatView(seat: VerseMcpSeatInput, stateRoots: ReadonlyMap<string, string>, home?: string): VerseMcpSeatView;
|
|
175
|
+
/** The machine-wide registry, and the fact that no seat reads it. */
|
|
176
|
+
export declare function buildVerseMcpMachineRegistry(paths?: string[], home?: string): VerseMcpMachineRegistry;
|
|
177
|
+
export interface VerseMcpSnapshotOptions {
|
|
178
|
+
accountsRoot: string;
|
|
179
|
+
/** Seats to describe. Callers pass `discoverSeats()`'s public seat list. */
|
|
180
|
+
seats: readonly VerseMcpSeatInput[];
|
|
181
|
+
/** Injected machine config paths (tests). Defaults to knownConfigPaths(). */
|
|
182
|
+
machineConfigPaths?: string[];
|
|
183
|
+
/** Injected scope read (tests). Defaults to the real Locus shell-out. */
|
|
184
|
+
scope?: VerseMcpScope;
|
|
185
|
+
/** Injected home (tests). */
|
|
186
|
+
home?: string;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The read-only answer to "which MCP servers would each seat load".
|
|
190
|
+
*
|
|
191
|
+
* Deliberately does no discovery of its own beyond the two registries: it
|
|
192
|
+
* never enables, disables or reorders anything, and it never reports a server
|
|
193
|
+
* against a seat that would not load it.
|
|
194
|
+
*/
|
|
195
|
+
export declare function buildVerseMcpSnapshot(options: VerseMcpSnapshotOptions): VerseMcpSnapshot;
|
|
196
|
+
/** Account identities, re-exported so a caller needs one import for the view. */
|
|
197
|
+
export declare function verseMcpAccountIdentities(accountsRoot: string): VerseAccountIdentity[];
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/verse/mcp-seat-view.ts — which MCP servers each Verse seat would load.
|
|
3
|
+
*
|
|
4
|
+
* READ-ONLY. Nothing in this file writes, installs, enables or disables
|
|
5
|
+
* anything. docs/VERSE-WORKSPACES.md §3 asks for exactly one thing first:
|
|
6
|
+
* "Surface which servers each seat would load, since a server configured for
|
|
7
|
+
* one account should not silently apply to another."
|
|
8
|
+
*
|
|
9
|
+
* ── The finding this module exists to make visible ──────────────────────────
|
|
10
|
+
*
|
|
11
|
+
* `discoverMcpServers()` scans HOME-level JSON configs (see
|
|
12
|
+
* `knownConfigPaths()`): ~/.claude.json, ~/.claude/settings.json, ~/.mcp.json,
|
|
13
|
+
* ~/.ashlrcode/settings.json, ~/.ashlr-workbench/settings.json,
|
|
14
|
+
* ~/.aw/settings.json, ~/.ashlr/settings.json.
|
|
15
|
+
*
|
|
16
|
+
* NO VERSE SEAT READS ANY OF THEM. Verified against the adapters and the
|
|
17
|
+
* launcher, not recalled:
|
|
18
|
+
*
|
|
19
|
+
* - CLAUDE and LOCAL seats: `buildClaudeLaunch` (src/core/verse/adapters/
|
|
20
|
+
* claude.ts) puts `--strict-mcp-config --mcp-config '{"mcpServers":{}}'`
|
|
21
|
+
* in every turn's argv, for both engines. That is a complete override:
|
|
22
|
+
* the seat loads ZERO MCP servers, whatever any config file says.
|
|
23
|
+
* - CODEX and GROK seats: their adapters add no MCP flag, so each loads its
|
|
24
|
+
* OWN native config — and the account is pinned by a private per-account
|
|
25
|
+
* state directory (`CODEX_HOME` / `GROK_HOME` / `CLAUDE_CONFIG_DIR` =
|
|
26
|
+
* `<profile>/native-state`, src/core/resources/native-profile.ts:72-78,
|
|
27
|
+
* :108). That directory is per ACCOUNT, so two Codex accounts already
|
|
28
|
+
* cannot share a server by accident. Their config is `config.toml` —
|
|
29
|
+
* TOML, which `discoverMcpServers()` (JSON only) cannot read.
|
|
30
|
+
*
|
|
31
|
+
* Reporting a machine-wide server list as "what this seat runs" would
|
|
32
|
+
* therefore be a lie in every direction at once: it would show servers no
|
|
33
|
+
* seat loads, and hide the per-account TOML that Codex and Grok actually do.
|
|
34
|
+
* So this module reports the two things separately and names the reason for
|
|
35
|
+
* each, and reports EMPTY rather than guessing.
|
|
36
|
+
*
|
|
37
|
+
* ── What may leave this module ──────────────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* Never: a launcher argv, an absolute native-profile path, a home-anchored
|
|
40
|
+
* absolute path, or a real env value. Config files are identified by their
|
|
41
|
+
* path RELATIVE TO $HOME (`.claude.json`, `.ashlr/settings.json`), which
|
|
42
|
+
* names the file precisely and carries no username. Every spec crosses the
|
|
43
|
+
* boundary through `redactEnv()`, whose whole reason for existing is that
|
|
44
|
+
* these specs carry secrets.
|
|
45
|
+
*/
|
|
46
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
47
|
+
import { homedir } from 'node:os';
|
|
48
|
+
import { dirname, join, relative, isAbsolute, sep } from 'node:path';
|
|
49
|
+
import { discoverMcpServers, redactEnv } from '../mcp-registry.js';
|
|
50
|
+
import { readVerseAccountIdentities } from './accounts.js';
|
|
51
|
+
import { readVerseMcpScope } from './mcp-scope.js';
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Verified facts about the adapters, encoded once
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Engines whose adapter argv pins an EMPTY `--mcp-config` under
|
|
57
|
+
* `--strict-mcp-config`, so the seat can load nothing at all.
|
|
58
|
+
*
|
|
59
|
+
* Both entries come from one function: `buildClaudeLaunch` serves engine
|
|
60
|
+
* 'claude' and engine 'local' alike (the latter is the same `claude` binary
|
|
61
|
+
* with ANTHROPIC_BASE_URL pointed at Ollama).
|
|
62
|
+
*
|
|
63
|
+
* If that argv ever loses those two flags this constant becomes a lie, which
|
|
64
|
+
* is why `test/verse-mcp-seat-view.test.ts` asserts the flags are still in the
|
|
65
|
+
* adapter's real output rather than trusting this list.
|
|
66
|
+
*/
|
|
67
|
+
export const VERSE_MCP_ISOLATED_ENGINES = ['claude', 'local'];
|
|
68
|
+
/** The two argv flags that make an isolated seat isolated. Asserted in tests. */
|
|
69
|
+
export const VERSE_MCP_ISOLATION_FLAGS = ['--strict-mcp-config', '--mcp-config'];
|
|
70
|
+
/**
|
|
71
|
+
* Per-account native config file, relative to that account's private state
|
|
72
|
+
* directory. Claude's is listed for completeness even though a Claude seat is
|
|
73
|
+
* isolated by its adapter before the file is ever consulted.
|
|
74
|
+
*/
|
|
75
|
+
const NATIVE_CONFIG_FILE = {
|
|
76
|
+
codex: { file: 'config.toml', format: 'toml' },
|
|
77
|
+
grok: { file: 'config.toml', format: 'toml' },
|
|
78
|
+
claude: { file: '.claude.json', format: 'json' },
|
|
79
|
+
};
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Notes (plain language, no machine codes)
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
export const VERSE_MCP_ISOLATED_NOTE = 'Every turn on this seat is launched with --strict-mcp-config and an empty --mcp-config, ' +
|
|
84
|
+
'so it loads no MCP servers at all regardless of what any config file holds.';
|
|
85
|
+
export const VERSE_MCP_TOML_NOTE = 'This account keeps its MCP servers in a TOML config, which is read by the provider CLI ' +
|
|
86
|
+
'itself and is not parsed here. Hub cannot list or change it without rewriting the ' +
|
|
87
|
+
"operator's TOML, which it deliberately does not do.";
|
|
88
|
+
export const VERSE_MCP_PER_ACCOUNT_NOTE = 'This account runs against its own private state directory, so a server configured for ' +
|
|
89
|
+
'another account does not reach it.';
|
|
90
|
+
export const VERSE_MCP_MACHINE_UNUSED_NOTE = 'These servers are configured in this machine\'s home-level configs. No Verse seat reads ' +
|
|
91
|
+
'them: Claude and local seats are launched with an empty --mcp-config, and Codex and Grok ' +
|
|
92
|
+
'seats read their own per-account config instead.';
|
|
93
|
+
export const VERSE_MCP_MACHINE_EMPTY_NOTE = 'No MCP servers are configured in this machine\'s home-level configs.';
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Path handling — nothing absolute crosses the boundary
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
/**
|
|
98
|
+
* Name a config file by its path relative to $HOME (`.claude.json`,
|
|
99
|
+
* `.ashlr/settings.json`). Precise enough to act on, and free of the username
|
|
100
|
+
* that an absolute path would carry.
|
|
101
|
+
*
|
|
102
|
+
* A path outside $HOME is reported as `(outside home)` — its basename alone
|
|
103
|
+
* would be ambiguous and its full path is not ours to publish.
|
|
104
|
+
*/
|
|
105
|
+
export function homeRelativeSourceRef(path, home = homedir()) {
|
|
106
|
+
if (!isAbsolute(path))
|
|
107
|
+
return path;
|
|
108
|
+
const rel = relative(home, path);
|
|
109
|
+
if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel))
|
|
110
|
+
return '(outside home)';
|
|
111
|
+
return rel.split(sep).join('/');
|
|
112
|
+
}
|
|
113
|
+
/** Project one discovered spec for transport, env values redacted. */
|
|
114
|
+
export function projectMcpServerView(spec, home = homedir()) {
|
|
115
|
+
const safe = redactEnv(spec);
|
|
116
|
+
return {
|
|
117
|
+
name: safe.name,
|
|
118
|
+
command: safe.command,
|
|
119
|
+
args: [...safe.args],
|
|
120
|
+
env: safe.env && Object.keys(safe.env).length > 0 ? { ...safe.env } : null,
|
|
121
|
+
sourceRef: homeRelativeSourceRef(safe.source, home),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Per-account private state directory
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* Resolve an account's private native state directory WITHOUT publishing it.
|
|
129
|
+
*
|
|
130
|
+
* connections.json carries each account's launcher argv. That argv is the
|
|
131
|
+
* account's identity and never leaves the server (src/core/verse/seats.ts:9).
|
|
132
|
+
* We read it here only to locate `<profile>/profile.json`, whose
|
|
133
|
+
* `nativeStatePath` is the directory the provider CLI is pinned to
|
|
134
|
+
* (`CODEX_HOME` / `GROK_HOME` / `CLAUDE_CONFIG_DIR`).
|
|
135
|
+
*
|
|
136
|
+
* Returns null rather than guessing when anything is missing.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveAccountStateRoots(accountsRoot) {
|
|
139
|
+
const out = new Map();
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
const raw = readFileSync(join(accountsRoot, 'connections.json'), 'utf8');
|
|
143
|
+
if (raw.length > 1024 * 1024)
|
|
144
|
+
return out;
|
|
145
|
+
parsed = JSON.parse(raw);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
151
|
+
return out;
|
|
152
|
+
const accounts = parsed['accounts'];
|
|
153
|
+
if (!Array.isArray(accounts))
|
|
154
|
+
return out;
|
|
155
|
+
for (const entry of accounts) {
|
|
156
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry))
|
|
157
|
+
continue;
|
|
158
|
+
const row = entry;
|
|
159
|
+
const id = row['id'];
|
|
160
|
+
const command = row['command'];
|
|
161
|
+
if (typeof id !== 'string' || id.length === 0 || !Array.isArray(command))
|
|
162
|
+
continue;
|
|
163
|
+
const launcher = command.find((part) => typeof part === 'string' && part.endsWith('launcher.mjs'));
|
|
164
|
+
if (launcher === undefined)
|
|
165
|
+
continue;
|
|
166
|
+
// profile.json is the launcher's own manifest (ResourceNativeProfile). It
|
|
167
|
+
// also carries the argv, so only `nativeStatePath` is lifted out of it.
|
|
168
|
+
let stateRoot = null;
|
|
169
|
+
try {
|
|
170
|
+
const manifest = JSON.parse(readFileSync(join(dirname(launcher), 'profile.json'), 'utf8'));
|
|
171
|
+
if (manifest !== null && typeof manifest === 'object' && !Array.isArray(manifest)) {
|
|
172
|
+
const value = manifest['nativeStatePath'];
|
|
173
|
+
if (typeof value === 'string' && value.length > 0)
|
|
174
|
+
stateRoot = value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
stateRoot = null;
|
|
179
|
+
}
|
|
180
|
+
if (stateRoot !== null)
|
|
181
|
+
out.set(id, stateRoot);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Read one account's private JSON MCP registry.
|
|
187
|
+
*
|
|
188
|
+
* Only ever called for a JSON-format config. TOML is refused upstream rather
|
|
189
|
+
* than half-parsed.
|
|
190
|
+
*/
|
|
191
|
+
function readAccountJsonServers(stateRoot, file, home) {
|
|
192
|
+
const path = join(stateRoot, file);
|
|
193
|
+
if (!existsSync(path))
|
|
194
|
+
return { servers: [], ok: true };
|
|
195
|
+
// discoverMcpServers takes injected paths and never throws; a malformed
|
|
196
|
+
// config comes back as an empty registry, which we cannot distinguish from
|
|
197
|
+
// a config with no servers. That ambiguity is acceptable here because both
|
|
198
|
+
// render identically ("this seat loads nothing from its own config").
|
|
199
|
+
try {
|
|
200
|
+
const registry = discoverMcpServers([path]);
|
|
201
|
+
return { servers: registry.servers.map((spec) => projectMcpServerView(spec, home)), ok: true };
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return { servers: [], ok: false };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Derive what one seat would load.
|
|
209
|
+
*
|
|
210
|
+
* `stateRoots` maps accountId -> private state directory (from
|
|
211
|
+
* {@link resolveAccountStateRoots}). A seat whose account is absent from it
|
|
212
|
+
* reports `mcp-account-profile-unresolved` rather than falling back to a
|
|
213
|
+
* machine-wide list.
|
|
214
|
+
*/
|
|
215
|
+
export function deriveVerseMcpSeatView(seat, stateRoots, home = homedir()) {
|
|
216
|
+
const base = { seatId: seat.id, label: seat.label, engine: seat.engine, accountId: seat.accountId };
|
|
217
|
+
// 1. Adapter isolation wins over every config on disk. Checking it FIRST is
|
|
218
|
+
// the point: a Claude seat with a fully populated ~/.claude.json still
|
|
219
|
+
// loads nothing, and saying otherwise is the exact lie this file avoids.
|
|
220
|
+
if (VERSE_MCP_ISOLATED_ENGINES.includes(seat.engine)) {
|
|
221
|
+
return {
|
|
222
|
+
...base,
|
|
223
|
+
servers: [],
|
|
224
|
+
reason: 'mcp-seat-isolated-by-adapter',
|
|
225
|
+
configFile: null,
|
|
226
|
+
configFormat: null,
|
|
227
|
+
notes: [VERSE_MCP_ISOLATED_NOTE],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// 2. Native seats read their own per-account config.
|
|
231
|
+
const provider = seat.engine;
|
|
232
|
+
const config = NATIVE_CONFIG_FILE[provider];
|
|
233
|
+
if (config === undefined) {
|
|
234
|
+
return {
|
|
235
|
+
...base,
|
|
236
|
+
servers: [],
|
|
237
|
+
reason: 'mcp-account-profile-unresolved',
|
|
238
|
+
configFile: null,
|
|
239
|
+
configFormat: null,
|
|
240
|
+
notes: [],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const stateRoot = stateRoots.get(seat.accountId);
|
|
244
|
+
if (stateRoot === undefined) {
|
|
245
|
+
return {
|
|
246
|
+
...base,
|
|
247
|
+
servers: [],
|
|
248
|
+
reason: 'mcp-account-profile-unresolved',
|
|
249
|
+
configFile: config.file,
|
|
250
|
+
configFormat: config.format,
|
|
251
|
+
notes: [VERSE_MCP_PER_ACCOUNT_NOTE],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const exists = existsSync(join(stateRoot, config.file));
|
|
255
|
+
if (!exists) {
|
|
256
|
+
return {
|
|
257
|
+
...base,
|
|
258
|
+
servers: [],
|
|
259
|
+
reason: 'mcp-account-config-absent',
|
|
260
|
+
configFile: config.file,
|
|
261
|
+
configFormat: config.format,
|
|
262
|
+
notes: [VERSE_MCP_PER_ACCOUNT_NOTE],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (config.format === 'toml') {
|
|
266
|
+
return {
|
|
267
|
+
...base,
|
|
268
|
+
servers: [],
|
|
269
|
+
reason: 'mcp-account-config-not-json',
|
|
270
|
+
configFile: config.file,
|
|
271
|
+
configFormat: 'toml',
|
|
272
|
+
notes: [VERSE_MCP_TOML_NOTE, VERSE_MCP_PER_ACCOUNT_NOTE],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const read = readAccountJsonServers(stateRoot, config.file, home);
|
|
276
|
+
return {
|
|
277
|
+
...base,
|
|
278
|
+
servers: read.servers,
|
|
279
|
+
reason: read.ok ? 'mcp-account-config-read' : 'mcp-account-config-unreadable',
|
|
280
|
+
configFile: config.file,
|
|
281
|
+
configFormat: 'json',
|
|
282
|
+
notes: [VERSE_MCP_PER_ACCOUNT_NOTE],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Snapshot
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
/** The machine-wide registry, and the fact that no seat reads it. */
|
|
289
|
+
export function buildVerseMcpMachineRegistry(paths, home = homedir()) {
|
|
290
|
+
const registry = discoverMcpServers(paths);
|
|
291
|
+
const servers = registry.servers.map((spec) => projectMcpServerView(spec, home));
|
|
292
|
+
return {
|
|
293
|
+
servers,
|
|
294
|
+
configured: servers.length > 0,
|
|
295
|
+
note: servers.length > 0 ? VERSE_MCP_MACHINE_UNUSED_NOTE : VERSE_MCP_MACHINE_EMPTY_NOTE,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The read-only answer to "which MCP servers would each seat load".
|
|
300
|
+
*
|
|
301
|
+
* Deliberately does no discovery of its own beyond the two registries: it
|
|
302
|
+
* never enables, disables or reorders anything, and it never reports a server
|
|
303
|
+
* against a seat that would not load it.
|
|
304
|
+
*/
|
|
305
|
+
export function buildVerseMcpSnapshot(options) {
|
|
306
|
+
const home = options.home ?? homedir();
|
|
307
|
+
const stateRoots = resolveAccountStateRoots(options.accountsRoot);
|
|
308
|
+
const seats = options.seats.map((seat) => deriveVerseMcpSeatView(seat, stateRoots, home));
|
|
309
|
+
const machine = buildVerseMcpMachineRegistry(options.machineConfigPaths, home);
|
|
310
|
+
const scope = options.scope ?? readVerseMcpScope();
|
|
311
|
+
const notes = [];
|
|
312
|
+
if (machine.configured && seats.every((seat) => seat.servers.length === 0)) {
|
|
313
|
+
notes.push(`${machine.servers.length} MCP server${machine.servers.length === 1 ? ' is' : 's are'} configured ` +
|
|
314
|
+
'on this machine and no Verse seat loads any of them.');
|
|
315
|
+
}
|
|
316
|
+
return { sampledAt: new Date().toISOString(), seats, machine, scope, notes };
|
|
317
|
+
}
|
|
318
|
+
/** Account identities, re-exported so a caller needs one import for the view. */
|
|
319
|
+
export function verseMcpAccountIdentities(accountsRoot) {
|
|
320
|
+
return readVerseAccountIdentities(accountsRoot);
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=mcp-seat-view.js.map
|