@bevel-software/platform-core-backend 0.14.0 → 0.15.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/THIRD-PARTY-NOTICES.md +6 -6
- package/dist/core/catalog-cache-invalidation.d.ts +29 -0
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -0
- package/dist/core/catalog-cache-invalidation.js +71 -0
- package/dist/core/catalog-cache-invalidation.js.map +1 -0
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +99 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +18 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +96 -43
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +18 -6
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +26 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +213 -71
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +59 -7
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +51 -1
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +95 -1
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +4 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/access-model/plugin-principals.d.ts +49 -0
- package/dist/modules/access-model/plugin-principals.d.ts.map +1 -0
- package/dist/modules/access-model/plugin-principals.js +125 -0
- package/dist/modules/access-model/plugin-principals.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +326 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +52 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/kb-sync/index.d.ts +6 -0
- package/dist/modules/kb-sync/index.d.ts.map +1 -0
- package/dist/modules/kb-sync/index.js +5 -0
- package/dist/modules/kb-sync/index.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +77 -0
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.interface.js +2 -0
- package/dist/modules/kb-sync/kb-sync.interface.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +70 -0
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.routes.js +135 -0
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts +45 -0
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -0
- package/dist/modules/kb-sync/kb-sync.service.js +183 -0
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -0
- package/dist/modules/kb-sync/sync-auth.d.ts +59 -0
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-auth.js +62 -0
- package/dist/modules/kb-sync/sync-auth.js.map +1 -0
- package/dist/modules/kb-sync/sync-payload.d.ts +32 -0
- package/dist/modules/kb-sync/sync-payload.d.ts.map +1 -0
- package/dist/modules/kb-sync/sync-payload.js +105 -0
- package/dist/modules/kb-sync/sync-payload.js.map +1 -0
- package/dist/modules/marketplace/git-http.routes.d.ts +42 -0
- package/dist/modules/marketplace/git-http.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/git-http.routes.js +190 -0
- package/dist/modules/marketplace/git-http.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts +23 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-admin.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts +59 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js +96 -0
- package/dist/modules/marketplace/github-facade/github-facade-codes.store.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts +99 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js +147 -0
- package/dist/modules/marketplace/github-facade/github-facade-credentials.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts +47 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js +228 -0
- package/dist/modules/marketplace/github-facade/github-facade.routes.js.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts +109 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js +172 -0
- package/dist/modules/marketplace/github-facade/github-facade.service.js.map +1 -0
- package/dist/modules/marketplace/github-facade/index.d.ts +6 -0
- package/dist/modules/marketplace/github-facade/index.d.ts.map +1 -0
- package/dist/modules/marketplace/github-facade/index.js +6 -0
- package/dist/modules/marketplace/github-facade/index.js.map +1 -0
- package/dist/modules/marketplace/index.d.ts +4 -0
- package/dist/modules/marketplace/index.d.ts.map +1 -0
- package/dist/modules/marketplace/index.js +4 -0
- package/dist/modules/marketplace/index.js.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts +122 -0
- package/dist/modules/marketplace/marketplace-repo.service.d.ts.map +1 -0
- package/dist/modules/marketplace/marketplace-repo.service.js +300 -0
- package/dist/modules/marketplace/marketplace-repo.service.js.map +1 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +4 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +14 -8
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +13 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.d.ts +13 -2
- package/dist/modules/mcp/oauth/oauth-state.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-state.js +3 -1
- package/dist/modules/mcp/oauth/oauth-state.js.map +1 -1
- package/dist/modules/plugins/compile/compile-marketplace.d.ts +97 -0
- package/dist/modules/plugins/compile/compile-marketplace.d.ts.map +1 -0
- package/dist/modules/plugins/compile/compile-marketplace.js +240 -0
- package/dist/modules/plugins/compile/compile-marketplace.js.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts +66 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.d.ts.map +1 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js +132 -0
- package/dist/modules/plugins/compile/marketplace-compiler.service.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts +37 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js +135 -0
- package/dist/modules/plugins/discovery/bundle-dialect/bundle.source.js.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts +45 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js +147 -0
- package/dist/modules/plugins/discovery/bundle-dialect/registry.js.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts +37 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js +107 -0
- package/dist/modules/plugins/discovery/kb-plugin-source.js.map +1 -0
- package/dist/modules/plugins/discovery/native.source.d.ts +15 -0
- package/dist/modules/plugins/discovery/native.source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/native.source.js +102 -0
- package/dist/modules/plugins/discovery/native.source.js.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts +96 -0
- package/dist/modules/plugins/discovery/plugin-source.d.ts.map +1 -0
- package/dist/modules/plugins/discovery/plugin-source.js +2 -0
- package/dist/modules/plugins/discovery/plugin-source.js.map +1 -0
- package/dist/modules/plugins/index.d.ts +10 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js +8 -0
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +66 -0
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.js +160 -0
- package/dist/modules/plugins/plugin-links.js.map +1 -0
- package/dist/modules/plugins/plugin-links.service.d.ts +114 -0
- package/dist/modules/plugins/plugin-links.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-links.service.js +304 -0
- package/dist/modules/plugins/plugin-links.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +39 -3
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +152 -52
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.d.ts +69 -0
- package/dist/modules/plugins/plugin-rename.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-rename.service.js +245 -0
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +27 -1
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +7 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +120 -12
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.d.ts +50 -3
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.service.js +108 -47
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +9 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +82 -2
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +26 -0
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +9 -1
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/index.d.ts +1 -0
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +9 -4
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +38 -0
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -0
- package/dist/modules/skills/skill-access-requests.routes.js +168 -0
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +31 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.d.ts +28 -3
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +42 -3
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +4 -0
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +83 -22
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +18 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js +2 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +13 -4
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +26 -9
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +36 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +130 -92
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +18 -25
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +40 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +227 -54
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +6 -0
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +8 -0
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +52 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +242 -9
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts +35 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js +121 -0
- package/dist/modules/workspace/startup/steps/plugin-manifests.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +46 -2
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +112 -25
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +9 -9
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +33 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +110 -16
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +20 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +29 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/fs-errors.d.ts +8 -0
- package/dist/shared/fs-errors.d.ts.map +1 -0
- package/dist/shared/fs-errors.js +11 -0
- package/dist/shared/fs-errors.js.map +1 -0
- package/dist/shared/fs-walk.d.ts +20 -6
- package/dist/shared/fs-walk.d.ts.map +1 -1
- package/dist/shared/fs-walk.js +35 -27
- package/dist/shared/fs-walk.js.map +1 -1
- package/dist/shared/kb-walk.d.ts +52 -0
- package/dist/shared/kb-walk.d.ts.map +1 -0
- package/dist/shared/kb-walk.js +76 -0
- package/dist/shared/kb-walk.js.map +1 -0
- package/dist/shared/path-order.d.ts +18 -0
- package/dist/shared/path-order.d.ts.map +1 -0
- package/dist/shared/path-order.js +35 -0
- package/dist/shared/path-order.js.map +1 -0
- package/dist/shared/ttl-cache.d.ts +28 -1
- package/dist/shared/ttl-cache.d.ts.map +1 -1
- package/dist/shared/ttl-cache.js +33 -1
- package/dist/shared/ttl-cache.js.map +1 -1
- package/kb-template/.bevelignore +1 -1
- package/kb-template/AGENTS.md +95 -30
- package/kb-template/Skills/.gitkeep +0 -0
- package/migrations/0005_github_facade.sql +25 -0
- package/migrations/meta/0005_snapshot.json +1717 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +6 -6
- package/src/__tests__/kb-layout-config.test.ts +110 -0
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +146 -0
- package/src/core/catalog-cache-invalidation.ts +92 -0
- package/src/core/create-core-server.ts +123 -7
- package/src/core/create-core-services.ts +162 -41
- package/src/modules/access/__tests__/access-control.invalidate-race.test.ts +86 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +19 -3
- package/src/modules/access/__tests__/access-plugin-principals.test.ts +368 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +8 -0
- package/src/modules/access/access-control.interface.ts +20 -9
- package/src/modules/access/access-control.service.ts +240 -78
- package/src/modules/access/access.routes.ts +96 -14
- package/src/modules/access-model/__tests__/access-grammar.test.ts +18 -0
- package/src/modules/access-model/__tests__/plugin-token-canonical.test.ts +40 -0
- package/src/modules/access-model/access-grammar.ts +114 -2
- package/src/modules/access-model/group-files.ts +4 -0
- package/src/modules/access-model/plugin-principals.ts +137 -0
- package/src/modules/database/core-schema.ts +54 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +297 -0
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +295 -0
- package/src/modules/kb-sync/__tests__/sync-auth.test.ts +118 -0
- package/src/modules/kb-sync/__tests__/sync-payload.test.ts +148 -0
- package/src/modules/kb-sync/index.ts +19 -0
- package/src/modules/kb-sync/kb-sync.interface.ts +79 -0
- package/src/modules/kb-sync/kb-sync.routes.ts +197 -0
- package/src/modules/kb-sync/kb-sync.service.ts +201 -0
- package/src/modules/kb-sync/sync-auth.ts +118 -0
- package/src/modules/kb-sync/sync-payload.ts +143 -0
- package/src/modules/marketplace/__tests__/github-facade.test.ts +443 -0
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +179 -0
- package/src/modules/marketplace/git-http.routes.ts +193 -0
- package/src/modules/marketplace/github-facade/github-facade-admin.routes.ts +78 -0
- package/src/modules/marketplace/github-facade/github-facade-codes.store.ts +145 -0
- package/src/modules/marketplace/github-facade/github-facade-credentials.service.ts +208 -0
- package/src/modules/marketplace/github-facade/github-facade.routes.ts +268 -0
- package/src/modules/marketplace/github-facade/github-facade.service.ts +221 -0
- package/src/modules/marketplace/github-facade/index.ts +25 -0
- package/src/modules/marketplace/index.ts +3 -0
- package/src/modules/marketplace/marketplace-repo.service.ts +337 -0
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -0
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +24 -2
- package/src/modules/mcp/oauth/oauth-state.ts +15 -3
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +163 -0
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +294 -0
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +56 -12
- package/src/modules/plugins/__tests__/plugin-links.service.test.ts +287 -0
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +205 -5
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +394 -0
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +80 -33
- package/src/modules/plugins/compile/compile-marketplace.ts +403 -0
- package/src/modules/plugins/compile/marketplace-compiler.service.ts +150 -0
- package/src/modules/plugins/discovery/__tests__/kb-plugin-source.test.ts +329 -0
- package/src/modules/plugins/discovery/bundle-dialect/bundle.source.ts +140 -0
- package/src/modules/plugins/discovery/bundle-dialect/registry.ts +175 -0
- package/src/modules/plugins/discovery/kb-plugin-source.ts +114 -0
- package/src/modules/plugins/discovery/native.source.ts +122 -0
- package/src/modules/plugins/discovery/plugin-source.ts +99 -0
- package/src/modules/plugins/index.ts +10 -0
- package/src/modules/plugins/plugin-links.service.ts +363 -0
- package/src/modules/plugins/plugin-links.ts +184 -0
- package/src/modules/plugins/plugin-provision.service.ts +160 -54
- package/src/modules/plugins/plugin-rename.service.ts +298 -0
- package/src/modules/plugins/plugins.contract.ts +28 -1
- package/src/modules/plugins/plugins.routes.ts +128 -11
- package/src/modules/plugins/plugins.service.ts +106 -53
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +57 -0
- package/src/modules/settings/__tests__/setup.routes.sync-status.test.ts +77 -0
- package/src/modules/settings/deployment-settings.service.ts +95 -2
- package/src/modules/settings/setup.routes.ts +24 -0
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +128 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +85 -0
- package/src/modules/skills/index.ts +1 -0
- package/src/modules/skills/pending-skills.service.ts +7 -5
- package/src/modules/skills/skill-access-requests.routes.ts +199 -0
- package/src/modules/skills/skills.contract.ts +32 -0
- package/src/modules/skills/skills.routes.ts +59 -3
- package/src/modules/skills/skills.service.ts +84 -22
- package/src/modules/tool-auth/external-api-key.interface.ts +20 -1
- package/src/modules/tool-auth/external-api-key.service.ts +31 -13
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +24 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.cli.test.ts +2 -0
- package/src/modules/tool-manuals/mcp-json-discovery.ts +154 -88
- package/src/modules/tool-manuals/tool-manuals.service.ts +14 -23
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +2 -2
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +119 -2
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +437 -0
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +47 -4
- package/src/modules/workflow/git/__tests__/git.service.syncFromRemote.test.ts +253 -0
- package/src/modules/workflow/git/git.service.ts +239 -56
- package/src/modules/workflow/git/pull-request.service.ts +9 -0
- package/src/modules/workflow/workflow.service.ts +260 -9
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +6 -7
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +93 -0
- package/src/modules/workspace/__tests__/workspace.service.missing-branch.test.ts +28 -0
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +42 -12
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +219 -4
- package/src/modules/workspace/startup/steps/plugin-manifests.step.ts +129 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +44 -2
- package/src/modules/workspace/startup/steps/template-files.step.ts +131 -27
- package/src/modules/workspace/workspace.routes.ts +9 -17
- package/src/modules/workspace/workspace.service.ts +107 -16
- package/src/shared/__tests__/filename.test.ts +0 -0
- package/src/shared/__tests__/fs-walk.test.ts +22 -1
- package/src/shared/__tests__/kb-walk.test.ts +107 -0
- package/src/shared/__tests__/path-order.test.ts +24 -0
- package/src/shared/__tests__/ttl-cache.test.ts +55 -0
- package/src/shared/domain-errors.ts +29 -0
- package/src/shared/fs-errors.ts +10 -0
- package/src/shared/fs-walk.ts +38 -23
- package/src/shared/kb-walk.ts +95 -0
- package/src/shared/path-order.ts +33 -0
- package/src/shared/ttl-cache.ts +33 -1
|
@@ -53,6 +53,8 @@ interface HarnessOpts {
|
|
|
53
53
|
email?: string | null;
|
|
54
54
|
skills?: SkillSummary[];
|
|
55
55
|
tools?: ToolManualSummary[];
|
|
56
|
+
/** More plugins: folder path below `Plugins/` → manifest name. */
|
|
57
|
+
extraPlugins?: Record<string, string>;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
function cr(over: Partial<ChangeRequest>): ChangeRequest {
|
|
@@ -75,17 +77,21 @@ async function makeHarness(opts: HarnessOpts = {}) {
|
|
|
75
77
|
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-plugins-routes-'));
|
|
76
78
|
tmpDirs.push(workspaceDir);
|
|
77
79
|
const kbRoot = path.join(workspaceDir, KB);
|
|
78
|
-
// Both carry the
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
path.join(kbRoot, 'Plugins',
|
|
87
|
-
'
|
|
88
|
-
|
|
80
|
+
// Both carry the manifest that makes a folder a plugin to discovery, and
|
|
81
|
+
// the access.md that makes it exist to the index.
|
|
82
|
+
const fixtures: [string, string][] = [
|
|
83
|
+
['GTM', 'gtm'],
|
|
84
|
+
['Finance', 'finance'],
|
|
85
|
+
...Object.entries(opts.extraPlugins ?? {}),
|
|
86
|
+
];
|
|
87
|
+
for (const [folder, name] of fixtures) {
|
|
88
|
+
await fs.mkdir(path.join(kbRoot, 'Plugins', folder), { recursive: true });
|
|
89
|
+
await fs.writeFile(path.join(kbRoot, 'Plugins', folder, 'plugin.json'), `{"name":"${name}"}`);
|
|
90
|
+
await fs.writeFile(
|
|
91
|
+
path.join(kbRoot, 'Plugins', folder, 'access.md'),
|
|
92
|
+
'---\nread:\n - everyone\n---\nread: []\n',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
89
95
|
|
|
90
96
|
const workspaceService = {
|
|
91
97
|
getOrCreateForBranch: async (branch: string) => ({ id: workspaceIdForBranch(branch) }),
|
|
@@ -233,11 +239,35 @@ describe('/api/plugins routes', () => {
|
|
|
233
239
|
server = h.server;
|
|
234
240
|
const { status, plugins } = await listPlugins(h.baseUrl);
|
|
235
241
|
expect(status).toBe(200);
|
|
236
|
-
|
|
242
|
+
// Named by identity (the manifest), labelled by folder.
|
|
243
|
+
expect(plugins.map((g) => [g.name, g.displayName])).toEqual([['finance', 'Finance'], ['gtm', 'GTM']]);
|
|
237
244
|
expect(plugins[0]).toMatchObject({ canRead: true, skillCount: 0, toolCount: 1 });
|
|
238
245
|
expect(plugins[1]).toMatchObject({ canRead: true, skillCount: 1, toolCount: 0 });
|
|
239
246
|
});
|
|
240
247
|
|
|
248
|
+
it("carries the index's broken-link count into the summary — the server's, not the caller's slice", async () => {
|
|
249
|
+
const entry = {
|
|
250
|
+
name: 'gtm',
|
|
251
|
+
displayName: 'GTM',
|
|
252
|
+
folders: ['Plugins/GTM'],
|
|
253
|
+
linksAreManaged: true,
|
|
254
|
+
skillCount: 2,
|
|
255
|
+
toolCount: 0,
|
|
256
|
+
brokenLinks: 2,
|
|
257
|
+
owners: { roles: [], users: [] },
|
|
258
|
+
writers: { roles: [], users: [] },
|
|
259
|
+
readers: { restricted: true, roles: [], users: [] },
|
|
260
|
+
};
|
|
261
|
+
const h = await makeHarness({
|
|
262
|
+
readable: MEMBER_OF_BOTH,
|
|
263
|
+
index: { catalog: async () => [entry], invalidate: () => {} } as unknown as IPluginIndexService,
|
|
264
|
+
});
|
|
265
|
+
server = h.server;
|
|
266
|
+
const { plugins } = await listPlugins(h.baseUrl);
|
|
267
|
+
expect(plugins).toHaveLength(1);
|
|
268
|
+
expect(plugins[0]).toMatchObject({ name: 'gtm', brokenLinks: 2 });
|
|
269
|
+
});
|
|
270
|
+
|
|
241
271
|
it('a DISCOVERABLE plugin (access.md readable, folder not) lists locked with hasRequested from the join CR', async () => {
|
|
242
272
|
const h = await makeHarness({
|
|
243
273
|
readable: { [ALI]: ['Plugins/Finance/access.md'] },
|
|
@@ -245,7 +275,7 @@ describe('/api/plugins routes', () => {
|
|
|
245
275
|
});
|
|
246
276
|
server = h.server;
|
|
247
277
|
const { plugins } = await listPlugins(h.baseUrl);
|
|
248
|
-
expect(plugins.map((g) => g.name)).toEqual(['
|
|
278
|
+
expect(plugins.map((g) => g.name)).toEqual(['finance']);
|
|
249
279
|
expect(plugins[0]).toMatchObject({
|
|
250
280
|
canRead: false,
|
|
251
281
|
canWrite: false,
|
|
@@ -258,8 +288,9 @@ describe('/api/plugins routes', () => {
|
|
|
258
288
|
const h = await makeHarness({ readable: { [ALI]: ['Plugins/Finance/access.md'] } });
|
|
259
289
|
server = h.server;
|
|
260
290
|
const { plugins, raw } = await listPlugins(h.baseUrl);
|
|
261
|
-
expect(plugins.map((g) => g.name)).toEqual(['
|
|
262
|
-
expect(raw).not.toContain('"name":"
|
|
291
|
+
expect(plugins.map((g) => g.name)).toEqual(['finance']);
|
|
292
|
+
expect(raw).not.toContain('"name":"gtm"');
|
|
293
|
+
expect(raw).not.toContain('"GTM"');
|
|
263
294
|
expect(raw).not.toContain('Plugins/GTM');
|
|
264
295
|
});
|
|
265
296
|
|
|
@@ -267,7 +298,7 @@ describe('/api/plugins routes', () => {
|
|
|
267
298
|
const h = await makeHarness({ writable: { [ALI]: ['Plugins/GTM/access.md'] } });
|
|
268
299
|
server = h.server;
|
|
269
300
|
const { plugins } = await listPlugins(h.baseUrl);
|
|
270
|
-
expect(plugins.map((g) => g.name)).toEqual(['
|
|
301
|
+
expect(plugins.map((g) => g.name)).toEqual(['gtm']);
|
|
271
302
|
expect(plugins[0]).toMatchObject({ canRead: false, canWrite: true });
|
|
272
303
|
});
|
|
273
304
|
|
|
@@ -278,7 +309,7 @@ describe('/api/plugins routes', () => {
|
|
|
278
309
|
});
|
|
279
310
|
server = h.server;
|
|
280
311
|
const { plugins } = await listPlugins(h.baseUrl);
|
|
281
|
-
expect(plugins[0]).toMatchObject({ name: '
|
|
312
|
+
expect(plugins[0]).toMatchObject({ name: 'gtm', canRead: true, hasRequested: false });
|
|
282
313
|
});
|
|
283
314
|
|
|
284
315
|
it('lists the owner verdict per caller — the folder verdict, not the manager one', async () => {
|
|
@@ -288,8 +319,8 @@ describe('/api/plugins routes', () => {
|
|
|
288
319
|
});
|
|
289
320
|
server = h.server;
|
|
290
321
|
const { plugins } = await listPlugins(h.baseUrl);
|
|
291
|
-
expect(plugins.find((g) => g.name === '
|
|
292
|
-
expect(plugins.find((g) => g.name === '
|
|
322
|
+
expect(plugins.find((g) => g.name === 'gtm')).toMatchObject({ isOwner: true });
|
|
323
|
+
expect(plugins.find((g) => g.name === 'finance')).toMatchObject({ isOwner: false });
|
|
293
324
|
});
|
|
294
325
|
|
|
295
326
|
it('delete: 404 for unknown AND for a non-owner — a manager included (identical, fail-closed)', async () => {
|
|
@@ -301,7 +332,7 @@ describe('/api/plugins routes', () => {
|
|
|
301
332
|
writable: { [ALI]: ['Plugins/GTM/access.md'] },
|
|
302
333
|
});
|
|
303
334
|
server = h.server;
|
|
304
|
-
for (const name of ['Nope', '
|
|
335
|
+
for (const name of ['Nope', 'gtm']) {
|
|
305
336
|
const res = await fetch(`${h.baseUrl}/api/plugins/${name}`, { method: 'DELETE' });
|
|
306
337
|
expect(res.status, name).toBe(404);
|
|
307
338
|
expect(await res.json()).toEqual({ error: 'Unknown plugin', kind: 'unknown-plugin' });
|
|
@@ -309,12 +340,13 @@ describe('/api/plugins routes', () => {
|
|
|
309
340
|
expect(h.provision.deletePlugin).not.toHaveBeenCalled();
|
|
310
341
|
});
|
|
311
342
|
|
|
312
|
-
it('delete: an OWNER deletes through the provision door
|
|
343
|
+
it('delete: an OWNER deletes through the provision door — found by identity, deleted by folder', async () => {
|
|
313
344
|
const h = await makeHarness({ owner: { [ALI]: ['Plugins/GTM'] } });
|
|
314
345
|
server = h.server;
|
|
315
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
346
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/gtm`, { method: 'DELETE' });
|
|
316
347
|
expect(res.status).toBe(200);
|
|
317
348
|
expect(await res.json()).toEqual({ ok: true });
|
|
349
|
+
// Provisioning owns folders, so it is handed the folder, not the name.
|
|
318
350
|
expect(h.provision.deletePlugin).toHaveBeenCalledWith(
|
|
319
351
|
expect.objectContaining({ email: ALI }),
|
|
320
352
|
'GTM',
|
|
@@ -326,7 +358,7 @@ describe('/api/plugins routes', () => {
|
|
|
326
358
|
const { PluginProvisionError } = await import('../plugin-provision.service.js');
|
|
327
359
|
h.provision.deletePlugin.mockRejectedValueOnce(new PluginProvisionError('Unknown plugin', 404));
|
|
328
360
|
server = h.server;
|
|
329
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
361
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/gtm`, { method: 'DELETE' });
|
|
330
362
|
expect(res.status).toBe(404);
|
|
331
363
|
expect(await res.json()).toEqual({ error: 'Unknown plugin' });
|
|
332
364
|
});
|
|
@@ -336,7 +368,7 @@ describe('/api/plugins routes', () => {
|
|
|
336
368
|
const h = await makeHarness({ owner: { [ALI]: ['Plugins/GTM'] } });
|
|
337
369
|
h.provision.deletePlugin.mockRejectedValueOnce(new Error('push refused'));
|
|
338
370
|
server = h.server;
|
|
339
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
371
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/gtm`, { method: 'DELETE' });
|
|
340
372
|
expect(res.status).toBe(500);
|
|
341
373
|
expect(await res.json()).toEqual({ error: 'Failed to delete the plugin' });
|
|
342
374
|
error.mockRestore();
|
|
@@ -345,7 +377,7 @@ describe('/api/plugins routes', () => {
|
|
|
345
377
|
it('join-request: 404 for unknown AND for undiscoverable (identical, fail-closed)', async () => {
|
|
346
378
|
const h = await makeHarness({});
|
|
347
379
|
server = h.server;
|
|
348
|
-
for (const name of ['Nope', '
|
|
380
|
+
for (const name of ['Nope', 'gtm']) {
|
|
349
381
|
const res = await fetch(`${h.baseUrl}/api/plugins/${name}/join-request`, { method: 'POST' });
|
|
350
382
|
expect(res.status, name).toBe(404);
|
|
351
383
|
expect(await res.json()).toEqual({ error: 'Unknown plugin', kind: 'unknown-plugin' });
|
|
@@ -355,7 +387,7 @@ describe('/api/plugins routes', () => {
|
|
|
355
387
|
it('join-request: 409 when the caller can already read the folder', async () => {
|
|
356
388
|
const h = await makeHarness({ readable: MEMBER_OF_BOTH });
|
|
357
389
|
server = h.server;
|
|
358
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
390
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/finance/join-request`, { method: 'POST' });
|
|
359
391
|
expect(res.status).toBe(409);
|
|
360
392
|
expect((await res.json()).kind).toBe('already-readable');
|
|
361
393
|
});
|
|
@@ -363,10 +395,12 @@ describe('/api/plugins routes', () => {
|
|
|
363
395
|
it('join-request: branch + splice + commit + CR, on the deterministic join branch', async () => {
|
|
364
396
|
const h = await makeHarness({ readable: { [ALI]: ['Plugins/Finance/access.md'] } });
|
|
365
397
|
server = h.server;
|
|
366
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
398
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/finance/join-request`, { method: 'POST' });
|
|
367
399
|
expect(res.status).toBe(200);
|
|
368
400
|
expect(await res.json()).toEqual({ ok: true, number: 42 });
|
|
369
401
|
|
|
402
|
+
// The branch is cut from the FOLDER name, as every join branch before the
|
|
403
|
+
// manifest became the identity was — so none of them is orphaned.
|
|
370
404
|
const branch = joinBranchFor(ALI, 'Finance');
|
|
371
405
|
expect(h.workflow.createBranch).toHaveBeenCalledWith(wsId, branch, DEFAULT_BRANCH);
|
|
372
406
|
// The write went to the plugin's access.md and added the caller to the
|
|
@@ -389,13 +423,26 @@ describe('/api/plugins routes', () => {
|
|
|
389
423
|
);
|
|
390
424
|
});
|
|
391
425
|
|
|
426
|
+
it('keys a join request by the folder PATH below the root — two folders sharing a basename never share a branch', async () => {
|
|
427
|
+
const h = await makeHarness({
|
|
428
|
+
extraPlugins: { 'teams/GTM': 'team-gtm' },
|
|
429
|
+
readable: { [ALI]: ['Plugins/teams/GTM/access.md'] },
|
|
430
|
+
});
|
|
431
|
+
server = h.server;
|
|
432
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/team-gtm/join-request`, { method: 'POST' });
|
|
433
|
+
expect(res.status).toBe(200);
|
|
434
|
+
const branch = joinBranchFor(ALI, 'teams/GTM');
|
|
435
|
+
expect(branch).not.toBe(joinBranchFor(ALI, 'GTM'));
|
|
436
|
+
expect(h.workflow.createBranch).toHaveBeenCalledWith(wsId, branch, DEFAULT_BRANCH);
|
|
437
|
+
});
|
|
438
|
+
|
|
392
439
|
it('join-request is idempotent: an existing open join CR is returned, nothing new is created', async () => {
|
|
393
440
|
const h = await makeHarness({
|
|
394
441
|
readable: { [ALI]: ['Plugins/Finance/access.md'] },
|
|
395
442
|
authoredCrs: [cr({ branch: joinBranchFor(ALI, 'Finance'), number: 9 })],
|
|
396
443
|
});
|
|
397
444
|
server = h.server;
|
|
398
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
445
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/finance/join-request`, { method: 'POST' });
|
|
399
446
|
expect(res.status).toBe(200);
|
|
400
447
|
expect(await res.json()).toEqual({ ok: true, number: 9 });
|
|
401
448
|
expect(h.workflow.createBranch).not.toHaveBeenCalled();
|
|
@@ -418,7 +465,7 @@ describe('/api/plugins routes', () => {
|
|
|
418
465
|
it('join-requests: a MANAGER gets the service\'s list for the plugin', async () => {
|
|
419
466
|
const h = await makeHarness({ writable: { [ALI]: ['Plugins/GTM/access.md'] } });
|
|
420
467
|
server = h.server;
|
|
421
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
468
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/gtm/join-requests`);
|
|
422
469
|
expect(res.status).toBe(200);
|
|
423
470
|
expect(await res.json()).toEqual({ requests: [] });
|
|
424
471
|
expect(h.joinRequests.list).toHaveBeenCalledWith(
|
|
@@ -434,7 +481,7 @@ describe('/api/plugins routes', () => {
|
|
|
434
481
|
// question only the server answers.
|
|
435
482
|
const h = await makeHarness({ readable: MEMBER_OF_BOTH });
|
|
436
483
|
server = h.server;
|
|
437
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
484
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/gtm/join-requests`);
|
|
438
485
|
expect(res.status).toBe(200);
|
|
439
486
|
expect(await res.json()).toEqual({ requests: [] });
|
|
440
487
|
expect(h.joinRequests.list).not.toHaveBeenCalled();
|
|
@@ -443,7 +490,7 @@ describe('/api/plugins routes', () => {
|
|
|
443
490
|
it('reconcile: 404 for a non-manager — indistinguishable from a missing request', async () => {
|
|
444
491
|
const h = await makeHarness({ readable: MEMBER_OF_BOTH, authoredCrs: [cr({ number: 7 })] });
|
|
445
492
|
server = h.server;
|
|
446
|
-
const denied = await fetch(`${h.baseUrl}/api/plugins/
|
|
493
|
+
const denied = await fetch(`${h.baseUrl}/api/plugins/gtm/join-requests/7/reconcile`, {
|
|
447
494
|
method: 'POST',
|
|
448
495
|
});
|
|
449
496
|
expect(denied.status).toBe(404);
|
|
@@ -453,7 +500,7 @@ describe('/api/plugins routes', () => {
|
|
|
453
500
|
it('reconcile: 404 for a manager when the change request does not exist', async () => {
|
|
454
501
|
const h = await makeHarness({ writable: { [ALI]: ['Plugins/GTM/access.md'] } });
|
|
455
502
|
server = h.server;
|
|
456
|
-
const missing = await fetch(`${h.baseUrl}/api/plugins/
|
|
503
|
+
const missing = await fetch(`${h.baseUrl}/api/plugins/gtm/join-requests/999/reconcile`, {
|
|
457
504
|
method: 'POST',
|
|
458
505
|
});
|
|
459
506
|
expect(missing.status).toBe(404);
|
|
@@ -467,7 +514,7 @@ describe('/api/plugins routes', () => {
|
|
|
467
514
|
});
|
|
468
515
|
(h.joinRequests.reconcile as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
|
469
516
|
server = h.server;
|
|
470
|
-
const res = await fetch(`${h.baseUrl}/api/plugins/
|
|
517
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/gtm/join-requests/7/reconcile`, {
|
|
471
518
|
method: 'POST',
|
|
472
519
|
});
|
|
473
520
|
expect(res.status).toBe(200);
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
PLUGIN_MANIFEST_FILE,
|
|
5
|
+
PLUGIN_MCP_FILE,
|
|
6
|
+
PLUGIN_MANIFEST_SCHEMA,
|
|
7
|
+
PLUGIN_MCP_SCHEMA,
|
|
8
|
+
pluginManifestName,
|
|
9
|
+
} from '@bevel-software/platform-shared';
|
|
10
|
+
import { walkFiles } from '../../../shared/fs-walk.js';
|
|
11
|
+
import { containsVariableReference } from '../../../shared/variable-refs.js';
|
|
12
|
+
import { judgeMcpServerEntry } from '../../tool-manuals/mcp-json-discovery.js';
|
|
13
|
+
import type { SkillSummary } from '../../skills/skills.contract.js';
|
|
14
|
+
import type { LinkMembership } from '../plugin-links.js';
|
|
15
|
+
import type { DiscoveredPlugin } from '../discovery/plugin-source.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The compiler: SOURCE layout in, DISTRIBUTION layout out.
|
|
19
|
+
*
|
|
20
|
+
* Source is what lives in the knowledge base — shared skills under
|
|
21
|
+
* `Skills/`, plugin folders under `Plugins/` whose manifests LINK to them.
|
|
22
|
+
* Distribution is what an agent installs: a marketplace of self-contained
|
|
23
|
+
* plugins, each physically holding its skills (Claude Code refuses paths
|
|
24
|
+
* that escape a plugin's directory, and symlinks are out), in the vendor
|
|
25
|
+
* layouts the clients read today — `.claude-plugin/plugin.json`,
|
|
26
|
+
* `.codex-plugin/plugin.json`, `.mcp.json` — beside the Agent Plugins
|
|
27
|
+
* `plugin.json` and `mcp.json`.
|
|
28
|
+
*
|
|
29
|
+
* What comes out, for one caller:
|
|
30
|
+
*
|
|
31
|
+
* .claude-plugin/marketplace.json Claude Code's catalogue
|
|
32
|
+
* .agents/plugins/marketplace.json Codex's catalogue, every entry
|
|
33
|
+
* INSTALLED_BY_DEFAULT
|
|
34
|
+
* plugins/<slug>/… one per plugin the caller may read
|
|
35
|
+
* anything of: inline + linked skills,
|
|
36
|
+
* the portable half of mcp.json
|
|
37
|
+
* plugins/skills-and-knowledge/… every readable skill no plugin above
|
|
38
|
+
* ships — how a standalone skill, or one
|
|
39
|
+
* only in plugins the caller cannot see,
|
|
40
|
+
* is still one install away — plus the
|
|
41
|
+
* knowledge base's own MCP endpoint
|
|
42
|
+
* plugins/hexis-all/… a bundle whose only content is a
|
|
43
|
+
* dependency on every plugin above —
|
|
44
|
+
* Claude Code installs them all from
|
|
45
|
+
* one `claude plugin install`
|
|
46
|
+
* skills/<name>/… every readable skill once, flat, for
|
|
47
|
+
* `npx skills add <url> --all`
|
|
48
|
+
* README.md names the source commit
|
|
49
|
+
*
|
|
50
|
+
* FAIL CLOSED, per file: a skill is copied only when `readable` says yes for
|
|
51
|
+
* its SKILL.md — the same per-path verdict the catalog filters on — so the
|
|
52
|
+
* compiled tree for a caller holds exactly what that caller may read, and
|
|
53
|
+
* nothing about a skill they may not. Plugins with nothing readable are
|
|
54
|
+
* left out entirely. `access.md` and `.bevelignore` never ship; neither do
|
|
55
|
+
* the hexis-only `.tool` manuals (no other client can run them), nor an
|
|
56
|
+
* `mcp.json` header carrying a `${VAR}` vault reference (the extension block
|
|
57
|
+
* that resolves it is ours, and a header a client would send verbatim must
|
|
58
|
+
* not name a secret it cannot expand).
|
|
59
|
+
*
|
|
60
|
+
* A pure function of its inputs — it reads the KB tree it is pointed at and
|
|
61
|
+
* returns a virtual tree; writing it somewhere (a git namespace, a folder, a
|
|
62
|
+
* remote) is the sink's business.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
export interface MarketplaceOptions {
|
|
66
|
+
/** The marketplace's name — a slug the clients key it on. */
|
|
67
|
+
name: string;
|
|
68
|
+
/** Shown as the marketplace owner. */
|
|
69
|
+
owner: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
/** The default-branch commit the tree was compiled from; also the bundle's version. */
|
|
72
|
+
sourceCommit: string;
|
|
73
|
+
/** The name of the one-install bundle plugin. */
|
|
74
|
+
bundleName?: string;
|
|
75
|
+
/** The name of the leftovers plugin holding skills no other plugin ships. */
|
|
76
|
+
skillsPluginName?: string;
|
|
77
|
+
/**
|
|
78
|
+
* This deployment's hosted MCP endpoint — the knowledge base as a tool.
|
|
79
|
+
* Shipped in the leftovers plugin's `mcp.json` so an agent that installs
|
|
80
|
+
* from the marketplace can read the knowledge its skills refer to. URL only,
|
|
81
|
+
* never a credential: the endpoint challenges with OAuth metadata, and the
|
|
82
|
+
* client signs the person in on first use.
|
|
83
|
+
*/
|
|
84
|
+
knowledgeBaseMcp?: { name: string; url: string };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CompileInput {
|
|
88
|
+
/** Absolute path of the KB checkout (the folder holding `Plugins/`, `Skills/`, …). */
|
|
89
|
+
kbRoot: string;
|
|
90
|
+
/** The released catalog, UNFILTERED — `readable` does the filtering. */
|
|
91
|
+
skills: SkillSummary[];
|
|
92
|
+
/** The plugins as the configured source discovered them (manifests and servers already parsed). */
|
|
93
|
+
plugins: DiscoveredPlugin[];
|
|
94
|
+
/** Which plugins hold which skills, from the link index. */
|
|
95
|
+
membership: LinkMembership;
|
|
96
|
+
/** The caller's read verdict on a repo-relative path (`<skillFolder>/SKILL.md`). */
|
|
97
|
+
readable: (repoPath: string) => Promise<boolean>;
|
|
98
|
+
options: MarketplaceOptions;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface VirtualTree {
|
|
102
|
+
/** repo-relative POSIX path → file bytes. */
|
|
103
|
+
files: Map<string, Buffer>;
|
|
104
|
+
/** What was left out and why — name clashes, unparsable manifests. */
|
|
105
|
+
warnings: string[];
|
|
106
|
+
/** The plugin slugs the tree carries, in marketplace order. */
|
|
107
|
+
plugins: string[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const BUNDLE_NAME = 'hexis-all';
|
|
111
|
+
const SKILLS_PLUGIN_NAME = 'skills-and-knowledge';
|
|
112
|
+
/** Slugs the compiler emits itself; a real plugin may not take them. */
|
|
113
|
+
const RESERVED_SLUGS = new Set([BUNDLE_NAME, SKILLS_PLUGIN_NAME]);
|
|
114
|
+
const NEVER_SHIPPED = new Set(['access.md', '.bevelignore']);
|
|
115
|
+
|
|
116
|
+
export async function compileMarketplace(input: CompileInput): Promise<VirtualTree> {
|
|
117
|
+
const { kbRoot, skills, plugins, membership, readable, options } = input;
|
|
118
|
+
const files = new Map<string, Buffer>();
|
|
119
|
+
const warnings: string[] = [];
|
|
120
|
+
const put = (rel: string, content: string | Buffer) =>
|
|
121
|
+
files.set(rel, typeof content === 'string' ? Buffer.from(content, 'utf-8') : content);
|
|
122
|
+
|
|
123
|
+
// One verdict per skill, resolved once: every plugin below reads from it. A
|
|
124
|
+
// RETIRED skill (its governance lifecycle) stays in the catalog for its
|
|
125
|
+
// owners but never ships — retiring is how a skill leaves every agent.
|
|
126
|
+
const readableSkills = new Map<string, SkillSummary>();
|
|
127
|
+
for (const s of skills) {
|
|
128
|
+
if (s.lifecycle === 'retired') continue;
|
|
129
|
+
if (await readable(`${s.path}/SKILL.md`)) readableSkills.set(s.path, s);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface PluginOut {
|
|
133
|
+
slug: string;
|
|
134
|
+
displayName: string;
|
|
135
|
+
description?: string;
|
|
136
|
+
version?: string;
|
|
137
|
+
skills: SkillSummary[];
|
|
138
|
+
/** Source folder (real plugins only). */
|
|
139
|
+
folder?: string;
|
|
140
|
+
/** The portable `mcpServers` map to ship, when the plugin has servers. */
|
|
141
|
+
mcp?: Record<string, Record<string, unknown>>;
|
|
142
|
+
}
|
|
143
|
+
const out: PluginOut[] = [];
|
|
144
|
+
|
|
145
|
+
// 1. Real plugins: inline skills (in the folder) + linked skills (from the
|
|
146
|
+
// manifest), each only if readable. Personal folders are places, not
|
|
147
|
+
// plugins; a plugin the index does not count as existing is not shipped.
|
|
148
|
+
const byName = new Map(plugins.map((p) => [p.name, p]));
|
|
149
|
+
for (const [name, links] of [...membership.byPlugin.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
150
|
+
const plugin = byName.get(name);
|
|
151
|
+
if (!plugin || plugin.personal || !plugin.exists) continue;
|
|
152
|
+
const manifest = plugin.manifest;
|
|
153
|
+
const inline = [...readableSkills.values()].filter((s) => s.path.startsWith(`${plugin.folder}/`));
|
|
154
|
+
const linked = links.linkedSkills
|
|
155
|
+
.map((p) => readableSkills.get(p))
|
|
156
|
+
.filter((s): s is SkillSummary => s !== undefined);
|
|
157
|
+
const dedup = dedupeByName([...inline, ...linked], (s, other) =>
|
|
158
|
+
warnings.push(
|
|
159
|
+
`${name}: skill "${s.name}" at ${s.path} shares its name with ${other.path} — the second is left out`,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
const mcp = portableMcp(plugin.mcpServers, (name, reason) =>
|
|
163
|
+
warnings.push(`${plugin.folder}: mcp server "${name}" not shipped — ${reason}`),
|
|
164
|
+
);
|
|
165
|
+
if (dedup.length === 0 && mcp === null) continue; // nothing this caller may see
|
|
166
|
+
// The plugin's identity IS the marketplace slug. Folded once more here
|
|
167
|
+
// all the same: it becomes a path segment in the compiled tree, and a
|
|
168
|
+
// checked-in manifest is repository content, not trusted input — the
|
|
169
|
+
// folding (`[a-z0-9.-]`, no runs, no leading dots) leaves nothing that
|
|
170
|
+
// can be a separator or a parent reference.
|
|
171
|
+
const slug = pluginManifestName(name);
|
|
172
|
+
if (RESERVED_SLUGS.has(slug)) {
|
|
173
|
+
warnings.push(`${plugin.folder}: manifest name "${slug}" is reserved by the marketplace and cannot be a plugin — plugin skipped`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (out.some((p) => p.slug === slug)) {
|
|
177
|
+
warnings.push(`${plugin.folder}: manifest name "${slug}" is already taken in this marketplace — plugin skipped`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
out.push({
|
|
181
|
+
slug,
|
|
182
|
+
displayName: plugin.displayName,
|
|
183
|
+
description: typeof manifest?.description === 'string' ? manifest.description : undefined,
|
|
184
|
+
version: typeof manifest?.version === 'string' ? manifest.version : undefined,
|
|
185
|
+
skills: dedup,
|
|
186
|
+
folder: plugin.folder,
|
|
187
|
+
mcp: mcp ?? undefined,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 2. The leftovers plugin: every readable skill that no plugin above ships.
|
|
192
|
+
// Agents install plugins, not loose skills, so a skill in no plugin — or
|
|
193
|
+
// only in plugins this caller cannot see anything of — still has to be one
|
|
194
|
+
// install away. ONE plugin per caller, not one per scope: there is nothing
|
|
195
|
+
// to choose between (the caller may read all of it) and the bundle installs
|
|
196
|
+
// it regardless, so scope plugins would only add names and duplicate what
|
|
197
|
+
// the real plugins already carry.
|
|
198
|
+
// It also carries the knowledge base's own MCP endpoint (URL only — the
|
|
199
|
+
// client's OAuth flow signs the person in), so an agent installing from
|
|
200
|
+
// the marketplace can read the knowledge its skills point at. With the
|
|
201
|
+
// endpoint configured the plugin exists even for a caller with no
|
|
202
|
+
// leftover skill: the knowledge is the point.
|
|
203
|
+
const covered = new Set(out.flatMap((p) => p.skills.map((s) => s.path)));
|
|
204
|
+
const leftovers = [...readableSkills.values()].filter((s) => !covered.has(s.path));
|
|
205
|
+
const kbMcp = options.knowledgeBaseMcp
|
|
206
|
+
? { [options.knowledgeBaseMcp.name]: { type: 'streamable-http', url: options.knowledgeBaseMcp.url } }
|
|
207
|
+
: undefined;
|
|
208
|
+
if (leftovers.length > 0 || kbMcp) {
|
|
209
|
+
const slug = pluginManifestName(options.skillsPluginName ?? SKILLS_PLUGIN_NAME);
|
|
210
|
+
if (out.some((p) => p.slug === slug)) {
|
|
211
|
+
warnings.push(`the skills plugin "${slug}" collides with a plugin's manifest name — standalone skills not shipped`);
|
|
212
|
+
} else {
|
|
213
|
+
out.push({
|
|
214
|
+
slug,
|
|
215
|
+
displayName: 'Skills and knowledge',
|
|
216
|
+
description: kbMcp
|
|
217
|
+
? 'Every skill you may read that no other plugin here ships, and the knowledge base as an MCP server'
|
|
218
|
+
: 'Every skill you may read that no other plugin here ships',
|
|
219
|
+
skills: dedupeByName(leftovers, (s, other) =>
|
|
220
|
+
warnings.push(`${slug}: "${s.name}" at ${s.path} shares its name with ${other.path} — left out`),
|
|
221
|
+
),
|
|
222
|
+
mcp: kbMcp,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 3. Materialise every plugin: the three manifests + copied skill folders.
|
|
228
|
+
for (const p of out) {
|
|
229
|
+
const base = `plugins/${p.slug}`;
|
|
230
|
+
const spec: Record<string, unknown> = { $schema: PLUGIN_MANIFEST_SCHEMA, name: p.slug };
|
|
231
|
+
if (p.version) spec.version = p.version;
|
|
232
|
+
if (p.description) spec.description = p.description;
|
|
233
|
+
put(`${base}/${PLUGIN_MANIFEST_FILE}`, `${JSON.stringify(spec, null, 2)}\n`);
|
|
234
|
+
const vendor: Record<string, unknown> = { name: p.slug };
|
|
235
|
+
if (p.version) vendor.version = p.version;
|
|
236
|
+
vendor.description = p.description ?? p.displayName;
|
|
237
|
+
put(`${base}/.claude-plugin/plugin.json`, `${JSON.stringify(vendor, null, 2)}\n`);
|
|
238
|
+
put(`${base}/.codex-plugin/plugin.json`, `${JSON.stringify(vendor, null, 2)}\n`);
|
|
239
|
+
if (p.mcp) {
|
|
240
|
+
put(`${base}/${PLUGIN_MCP_FILE}`, `${JSON.stringify({ $schema: PLUGIN_MCP_SCHEMA, mcpServers: p.mcp }, null, 2)}\n`);
|
|
241
|
+
put(`${base}/.mcp.json`, `${JSON.stringify({ mcpServers: p.mcp }, null, 2)}\n`);
|
|
242
|
+
}
|
|
243
|
+
for (const s of p.skills) {
|
|
244
|
+
await copySkill(kbRoot, s, `${base}/skills/${s.name}`, put);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 4. The one-install bundle: a Claude manifest with nothing but dependencies.
|
|
249
|
+
const bundle = pluginManifestName(options.bundleName ?? BUNDLE_NAME);
|
|
250
|
+
const slugs = out.map((p) => p.slug);
|
|
251
|
+
if (slugs.includes(bundle)) {
|
|
252
|
+
// Only reachable with a non-default bundle name that folds to a real
|
|
253
|
+
// plugin's slug: the default is reserved above. Skipping the bundle
|
|
254
|
+
// beats overwriting that plugin's manifests with a dependency list.
|
|
255
|
+
warnings.push(`the bundle name "${bundle}" collides with a plugin's manifest name — no bundle plugin emitted`);
|
|
256
|
+
}
|
|
257
|
+
if (slugs.length > 0 && !slugs.includes(bundle)) {
|
|
258
|
+
const shortSha = options.sourceCommit.slice(0, 12) || '0';
|
|
259
|
+
const description = `Everything in ${options.owner}'s marketplace you may read — one install.`;
|
|
260
|
+
put(
|
|
261
|
+
`plugins/${bundle}/.claude-plugin/plugin.json`,
|
|
262
|
+
`${JSON.stringify({ name: bundle, version: `0.0.0-${shortSha}`, description, dependencies: slugs }, null, 2)}\n`,
|
|
263
|
+
);
|
|
264
|
+
put(
|
|
265
|
+
`plugins/${bundle}/${PLUGIN_MANIFEST_FILE}`,
|
|
266
|
+
`${JSON.stringify({ $schema: PLUGIN_MANIFEST_SCHEMA, name: bundle, description }, null, 2)}\n`,
|
|
267
|
+
);
|
|
268
|
+
put(
|
|
269
|
+
`plugins/${bundle}/.codex-plugin/plugin.json`,
|
|
270
|
+
`${JSON.stringify({ name: bundle, description }, null, 2)}\n`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 5. The flat root skills/ folder, every readable skill once.
|
|
275
|
+
const flat = dedupeByName(
|
|
276
|
+
[...readableSkills.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
277
|
+
(s, other) => warnings.push(`skills/: "${s.name}" at ${s.path} shares its name with ${other.path} — left out`),
|
|
278
|
+
);
|
|
279
|
+
for (const s of flat) await copySkill(kbRoot, s, `skills/${s.name}`, put);
|
|
280
|
+
|
|
281
|
+
// 6. The two catalogues + a README naming the source.
|
|
282
|
+
const bundleEmitted = slugs.length > 0 && !slugs.includes(bundle);
|
|
283
|
+
const entries = [...out.map((p) => ({ slug: p.slug, description: p.description ?? p.displayName })), ...(bundleEmitted ? [{ slug: bundle, description: 'Everything you may read, one install' }] : [])];
|
|
284
|
+
put(
|
|
285
|
+
'.claude-plugin/marketplace.json',
|
|
286
|
+
`${JSON.stringify(
|
|
287
|
+
{
|
|
288
|
+
name: options.name,
|
|
289
|
+
owner: { name: options.owner },
|
|
290
|
+
...(options.description ? { description: options.description } : {}),
|
|
291
|
+
plugins: entries.map((e) => ({ name: e.slug, source: `./plugins/${e.slug}`, description: e.description })),
|
|
292
|
+
},
|
|
293
|
+
null,
|
|
294
|
+
2,
|
|
295
|
+
)}\n`,
|
|
296
|
+
);
|
|
297
|
+
put(
|
|
298
|
+
'.agents/plugins/marketplace.json',
|
|
299
|
+
`${JSON.stringify(
|
|
300
|
+
{
|
|
301
|
+
name: options.name,
|
|
302
|
+
plugins: entries.map((e) => ({
|
|
303
|
+
name: e.slug,
|
|
304
|
+
source: { source: 'local', path: `./plugins/${e.slug}` },
|
|
305
|
+
policy: { installation: 'INSTALLED_BY_DEFAULT' },
|
|
306
|
+
description: e.description,
|
|
307
|
+
})),
|
|
308
|
+
},
|
|
309
|
+
null,
|
|
310
|
+
2,
|
|
311
|
+
)}\n`,
|
|
312
|
+
);
|
|
313
|
+
put(
|
|
314
|
+
'README.md',
|
|
315
|
+
[
|
|
316
|
+
`# ${options.name}`,
|
|
317
|
+
'',
|
|
318
|
+
`Compiled from ${options.owner}'s knowledge base at commit \`${options.sourceCommit}\`.`,
|
|
319
|
+
'This tree is generated: edit the knowledge base, not these files.',
|
|
320
|
+
'',
|
|
321
|
+
`Plugins: ${entries.map((e) => e.slug).join(', ') || 'none'}.`,
|
|
322
|
+
'',
|
|
323
|
+
].join('\n'),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
return { files, warnings, plugins: entries.map((e) => e.slug) };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// --- helpers ------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
/** Copy a skill folder (minus what never ships) under `dest`. */
|
|
332
|
+
async function copySkill(
|
|
333
|
+
kbRoot: string,
|
|
334
|
+
skill: SkillSummary,
|
|
335
|
+
dest: string,
|
|
336
|
+
put: (rel: string, content: Buffer) => void,
|
|
337
|
+
): Promise<void> {
|
|
338
|
+
const abs = path.join(kbRoot, skill.path);
|
|
339
|
+
for (const rel of await walkFiles(abs, (name) => !NEVER_SHIPPED.has(name))) {
|
|
340
|
+
put(`${dest}/${rel}`, await fs.readFile(path.join(abs, rel)));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** First by (name, path) order wins; the loser is reported to `onClash`. */
|
|
345
|
+
function dedupeByName(
|
|
346
|
+
list: SkillSummary[],
|
|
347
|
+
onClash: (loser: SkillSummary, winner: SkillSummary) => void,
|
|
348
|
+
): SkillSummary[] {
|
|
349
|
+
const sorted = [...list].sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
|
|
350
|
+
const seen = new Map<string, SkillSummary>();
|
|
351
|
+
const out: SkillSummary[] = [];
|
|
352
|
+
for (const s of sorted) {
|
|
353
|
+
const winner = seen.get(s.name);
|
|
354
|
+
if (winner) {
|
|
355
|
+
if (winner.path !== s.path) onClash(s, winner);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
seen.set(s.name, s);
|
|
359
|
+
out.push(s);
|
|
360
|
+
}
|
|
361
|
+
return out;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The portable half of a plugin's `mcpServers`: every entry the ONE
|
|
366
|
+
* judgement accepts (a name a client can key on, a transport it speaks, the
|
|
367
|
+
* field that transport needs), normalised, minus what a client cannot use —
|
|
368
|
+
* `${VAR}` vault references in a url or a header. Null when there is no
|
|
369
|
+
* server to ship; each server left out is reported with the judgement's
|
|
370
|
+
* reason, so an omission is diagnosable.
|
|
371
|
+
*/
|
|
372
|
+
function portableMcp(
|
|
373
|
+
servers: Record<string, unknown> | null,
|
|
374
|
+
leftOut: (name: string, reason: string) => void,
|
|
375
|
+
): Record<string, Record<string, unknown>> | null {
|
|
376
|
+
if (!servers) return null;
|
|
377
|
+
const out: Record<string, Record<string, unknown>> = {};
|
|
378
|
+
for (const [name, raw] of Object.entries(servers)) {
|
|
379
|
+
const verdict = judgeMcpServerEntry(name, raw);
|
|
380
|
+
if (!verdict.ok) {
|
|
381
|
+
leftOut(name, verdict.reason);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (verdict.transport === 'stdio') {
|
|
385
|
+
out[name] = { ...verdict.entry };
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const entry: Record<string, unknown> = { type: verdict.entry.type, url: verdict.entry.url };
|
|
389
|
+
if (containsVariableReference(verdict.entry.url)) {
|
|
390
|
+
leftOut(name, 'its url is expanded from the vault, which a client cannot do');
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (verdict.entry.headers) {
|
|
394
|
+
const headers: Record<string, string> = {};
|
|
395
|
+
for (const [h, v] of Object.entries(verdict.entry.headers)) {
|
|
396
|
+
if (!containsVariableReference(v)) headers[h] = v;
|
|
397
|
+
}
|
|
398
|
+
if (Object.keys(headers).length > 0) entry.headers = headers;
|
|
399
|
+
}
|
|
400
|
+
out[name] = entry;
|
|
401
|
+
}
|
|
402
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
403
|
+
}
|