@north-light/cli 0.3.454 → 0.3.455

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/error-codes.d.ts +25 -0
  4. package/dist/api/error-codes.js +1 -1
  5. package/dist/builtin-memory/crouter-sdk/release-notes.md +6 -0
  6. package/dist/commands/pkg/browse/catalog.js +1 -1
  7. package/dist/commands/pkg/plugin-manage.js +1 -1
  8. package/dist/core/command-manifests/registry.d.ts +2 -1
  9. package/dist/core/command-plugins/bundle.d.ts +3 -10
  10. package/dist/core/command-plugins/bundle.js +1 -1
  11. package/dist/core/command-plugins/compose.js +1 -1
  12. package/dist/core/command-plugins/discovery.js +1 -1
  13. package/dist/core/command-plugins/endpoint.d.ts +12 -0
  14. package/dist/core/command-plugins/endpoint.js +1 -1
  15. package/dist/core/command.d.ts +13 -0
  16. package/dist/core/command.js +9 -9
  17. package/dist/core/peer/client.d.ts +4 -2
  18. package/dist/core/peer/client.js +1 -1
  19. package/dist/core/peer/directory.d.ts +7 -0
  20. package/dist/core/peer/directory.js +1 -1
  21. package/dist/core/peer/envelope.d.ts +4 -0
  22. package/dist/core/peer/envelope.js +1 -1
  23. package/dist/core/peer/home.js +1 -1
  24. package/dist/core/providers/bundles.d.ts +2 -0
  25. package/dist/core/providers/bundles.js +2 -2
  26. package/dist/core/providers/call.d.ts +2 -0
  27. package/dist/core/providers/call.js +1 -1
  28. package/dist/core/providers/device.d.ts +39 -0
  29. package/dist/core/providers/device.js +1 -0
  30. package/dist/core/providers/errors.js +1 -1
  31. package/dist/daemon/api/handlers/objects.js +2 -2
  32. package/dist/daemon/api/handlers/providers.d.ts +2 -0
  33. package/dist/daemon/api/handlers/providers.js +1 -1
  34. package/dist/daemon/peer/outbox.js +1 -1
  35. package/dist/daemon/peer/relay-client.js +1 -1
  36. package/dist/daemon/peer/relay-protocol.d.ts +1 -1
  37. package/dist/daemon/peer/relay-protocol.js +1 -1
  38. package/dist/types.d.ts +4 -0
  39. package/docs/ci-runner.md +50 -4
  40. package/docs/sdk/release-notes.md +6 -0
  41. package/package.json +3 -3
  42. package/packages/crouter-identity/package.json +1 -1
  43. package/runtime.lock.json +12 -12
@@ -1,5 +1,5 @@
1
1
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
- export declare const CONTRACT_LEVEL = "1.3";
2
+ export declare const CONTRACT_LEVEL = "1.4";
3
3
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
4
  export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
5
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -1 +1 @@
1
- var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.3",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
1
+ var c=Object.defineProperty;var e=(r,t)=>c(r,"name",{value:t,configurable:!0});const f="1.4",i="Crouter-Required-Contract",p="Crouter-Contract";function l(r){const t=/^(\d+)\.(\d+)$/.exec(r.trim());return t===null?null:{major:Number(t[1]),minor:Number(t[2])}}e(l,"parseContractLevel");function a(r){return r==null?[]:r.split(",").map(t=>l(t)).filter(t=>t!==null)}e(a,"parseServedContracts");function s(r,t){const n=l(r);return n===null?!1:(typeof t=="string"||t===null||t===void 0?a(t):t).some(u=>u.major===n.major&&u.minor>=n.minor)}e(s,"contractSatisfies");function C(r,t,n={}){if(r===void 0)return null;if(l(r)===null)return{status:400,code:"invalid_request",param:i,message:`${i} must be a contract level like 1.0.`};if(s(r,t))return null;const o=r.trim();return{status:426,code:"runtime_version_unsupported",message:t===null||a(t).length===0?`The runtime did not name its contract level; the caller requires contract ${o} or later. Roll the runtime forward.`:`The runtime serves contract ${t}; the caller requires contract ${o} or later within the same major. Roll the runtime forward, or pin an SDK whose crouterContract the runtime serves.`,details:{sdk_version:(n.legacyMinimum??"").trim(),runtime_version:n.runtimeVersion??null,required_contract:o,runtime_contract:t}}}e(C,"contractRefusal");export{p as CONTRACT_HEADER,f as CONTRACT_LEVEL,i as REQUIRED_CONTRACT_HEADER,C as contractRefusal,s as contractSatisfies,l as parseContractLevel,a as parseServedContracts};
@@ -134,6 +134,31 @@ export declare const errorCodes: {
134
134
  readonly status: 409;
135
135
  readonly retryable: false;
136
136
  };
137
+ readonly invalid_argument: {
138
+ readonly type: "invalid_request_error";
139
+ readonly status: 400;
140
+ readonly retryable: false;
141
+ };
142
+ readonly machine_required: {
143
+ readonly type: "invalid_request_error";
144
+ readonly status: 409;
145
+ readonly retryable: false;
146
+ };
147
+ readonly device_offline: {
148
+ readonly type: "server_error";
149
+ readonly status: 503;
150
+ readonly retryable: true;
151
+ };
152
+ readonly device_timeout: {
153
+ readonly type: "server_error";
154
+ readonly status: 504;
155
+ readonly retryable: true;
156
+ };
157
+ readonly device_disconnected: {
158
+ readonly type: "server_error";
159
+ readonly status: 503;
160
+ readonly retryable: false;
161
+ };
137
162
  readonly not_found: {
138
163
  readonly type: "invalid_request_error";
139
164
  readonly status: 404;
@@ -1 +1 @@
1
- const e={unauthorized:{type:"authentication_error",status:401,retryable:!1},machine_revoked:{type:"authentication_error",status:401,retryable:!1},grant_revoked:{type:"authentication_error",status:401,retryable:!1},grant_removed:{type:"authentication_error",status:401,retryable:!1},grant_suspended:{type:"authentication_error",status:401,retryable:!1},refresh_token_expired:{type:"authentication_error",status:401,retryable:!1},refresh_token_reused:{type:"authentication_error",status:401,retryable:!1},scope_missing:{type:"permission_error",status:403,retryable:!1},owner_only:{type:"permission_error",status:403,retryable:!1},other_app_run:{type:"permission_error",status:403,retryable:!1},payer_not_app:{type:"permission_error",status:403,retryable:!1},approval_pending:{type:"permission_error",status:403,retryable:!1},user_denied:{type:"permission_error",status:403,retryable:!1},provider_refused:{type:"permission_error",status:403,retryable:!1},provider_not_connected:{type:"permission_error",status:403,retryable:!1},provider_setup_pending:{type:"permission_error",status:403,retryable:!1},question_pages_off:{type:"permission_error",status:403,retryable:!1},unsandboxed_machine:{type:"permission_error",status:403,retryable:!1},app_not_granted:{type:"permission_error",status:403,retryable:!1},isolated_run:{type:"permission_error",status:403,retryable:!1},provider_version_retired:{type:"permission_error",status:403,retryable:!1},invalid_scope:{type:"invalid_request_error",status:400,retryable:!1},invalid_request:{type:"invalid_request_error",status:400,retryable:!1},range_not_satisfiable:{type:"invalid_request_error",status:416,retryable:!1},not_here:{type:"invalid_request_error",status:404,retryable:!1},peer_version_too_old:{type:"invalid_request_error",status:426,retryable:!1},conflict:{type:"invalid_request_error",status:409,retryable:!1},not_found:{type:"invalid_request_error",status:404,retryable:!1},model_unavailable:{type:"invalid_request_error",status:404,retryable:!1},upstream_model_unavailable:{type:"server_error",status:503,retryable:!0},substitution_refused:{type:"invalid_request_error",status:400,retryable:!1},idempotency_conflict:{type:"invalid_request_error",status:409,retryable:!1},run_settled:{type:"invalid_request_error",status:409,retryable:!1},cap_exhausted:{type:"rate_limit_error",status:402,retryable:!1},concurrency_limit:{type:"rate_limit_error",status:429,retryable:!0},all_routes_exhausted:{type:"rate_limit_error",status:429,retryable:!0},provider_rate_limited:{type:"rate_limit_error",status:429,retryable:!0},rate_limited:{type:"rate_limit_error",status:429,retryable:!0},runtime_unreachable:{type:"server_error",status:503,retryable:!0},isolated_unavailable:{type:"server_error",status:503,retryable:!0},runtime_starting:{type:"server_error",status:503,retryable:!0},runtime_provisioning:{type:"server_error",status:503,retryable:!0},provider_unavailable:{type:"server_error",status:503,retryable:!0},upstream_refused:{type:"server_error",status:502,retryable:!1},provider_bad_response:{type:"server_error",status:502,retryable:!1},service_unavailable:{type:"server_error",status:503,retryable:!0},not_implemented:{type:"server_error",status:501,retryable:!1},permission_error:{type:"permission_error",status:403,retryable:!1},storage_unavailable:{type:"server_error",status:503,retryable:!0},server_error:{type:"server_error",status:500,retryable:!0},server_failure:{type:"server_error",status:500,retryable:!1},wake_timeout:{type:"server_error",status:504,retryable:!0}};export{e as errorCodes};
1
+ const e={unauthorized:{type:"authentication_error",status:401,retryable:!1},machine_revoked:{type:"authentication_error",status:401,retryable:!1},grant_revoked:{type:"authentication_error",status:401,retryable:!1},grant_removed:{type:"authentication_error",status:401,retryable:!1},grant_suspended:{type:"authentication_error",status:401,retryable:!1},refresh_token_expired:{type:"authentication_error",status:401,retryable:!1},refresh_token_reused:{type:"authentication_error",status:401,retryable:!1},scope_missing:{type:"permission_error",status:403,retryable:!1},owner_only:{type:"permission_error",status:403,retryable:!1},other_app_run:{type:"permission_error",status:403,retryable:!1},payer_not_app:{type:"permission_error",status:403,retryable:!1},approval_pending:{type:"permission_error",status:403,retryable:!1},user_denied:{type:"permission_error",status:403,retryable:!1},provider_refused:{type:"permission_error",status:403,retryable:!1},provider_not_connected:{type:"permission_error",status:403,retryable:!1},provider_setup_pending:{type:"permission_error",status:403,retryable:!1},question_pages_off:{type:"permission_error",status:403,retryable:!1},unsandboxed_machine:{type:"permission_error",status:403,retryable:!1},app_not_granted:{type:"permission_error",status:403,retryable:!1},isolated_run:{type:"permission_error",status:403,retryable:!1},provider_version_retired:{type:"permission_error",status:403,retryable:!1},invalid_scope:{type:"invalid_request_error",status:400,retryable:!1},invalid_request:{type:"invalid_request_error",status:400,retryable:!1},range_not_satisfiable:{type:"invalid_request_error",status:416,retryable:!1},not_here:{type:"invalid_request_error",status:404,retryable:!1},peer_version_too_old:{type:"invalid_request_error",status:426,retryable:!1},conflict:{type:"invalid_request_error",status:409,retryable:!1},invalid_argument:{type:"invalid_request_error",status:400,retryable:!1},machine_required:{type:"invalid_request_error",status:409,retryable:!1},device_offline:{type:"server_error",status:503,retryable:!0},device_timeout:{type:"server_error",status:504,retryable:!0},device_disconnected:{type:"server_error",status:503,retryable:!1},not_found:{type:"invalid_request_error",status:404,retryable:!1},model_unavailable:{type:"invalid_request_error",status:404,retryable:!1},upstream_model_unavailable:{type:"server_error",status:503,retryable:!0},substitution_refused:{type:"invalid_request_error",status:400,retryable:!1},idempotency_conflict:{type:"invalid_request_error",status:409,retryable:!1},run_settled:{type:"invalid_request_error",status:409,retryable:!1},cap_exhausted:{type:"rate_limit_error",status:402,retryable:!1},concurrency_limit:{type:"rate_limit_error",status:429,retryable:!0},all_routes_exhausted:{type:"rate_limit_error",status:429,retryable:!0},provider_rate_limited:{type:"rate_limit_error",status:429,retryable:!0},rate_limited:{type:"rate_limit_error",status:429,retryable:!0},runtime_unreachable:{type:"server_error",status:503,retryable:!0},isolated_unavailable:{type:"server_error",status:503,retryable:!0},runtime_starting:{type:"server_error",status:503,retryable:!0},runtime_provisioning:{type:"server_error",status:503,retryable:!0},provider_unavailable:{type:"server_error",status:503,retryable:!0},upstream_refused:{type:"server_error",status:502,retryable:!1},provider_bad_response:{type:"server_error",status:502,retryable:!1},service_unavailable:{type:"server_error",status:503,retryable:!0},not_implemented:{type:"server_error",status:501,retryable:!1},permission_error:{type:"permission_error",status:403,retryable:!1},storage_unavailable:{type:"server_error",status:503,retryable:!0},server_error:{type:"server_error",status:500,retryable:!0},server_failure:{type:"server_error",status:500,retryable:!1},wake_timeout:{type:"server_error",status:504,retryable:!0}};export{e as errorCodes};
@@ -9,6 +9,12 @@ when-and-why-to-read: When you need Release notes, this knowledge should be read
9
9
 
10
10
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
11
11
 
12
+ ## 0.3.455
13
+
14
+ ### `provider.call` can target a device (additive, no app change)
15
+
16
+ `provider.call` takes an optional `machine` (a machine id; the `runtime:` prefix is optional) for a provider served by one of the account's devices, such as `crtr device`. Without it, one device is used; with several the call is refused `machine_required` and `details.candidates` lists them. Five error codes are new and are raised only on that path: `machine_required`, `device_offline`, `device_timeout`, `device_disconnected` and `invalid_argument` (`machine` sent to a provider that is not served by a device). A call that sends no `machine` to an ordinary provider sees no change.
17
+
12
18
  ## 0.3.452
13
19
 
14
20
  ### `oauth.client(userId)` no longer offers methods the runtime does not serve (type change)
@@ -1 +1 @@
1
- var z=Object.defineProperty;var l=(e,n)=>z(e,"name",{value:n,configurable:!0});import{statSync as w}from"node:fs";import{isAbsolute as D,join as k}from"node:path";import{SUBTREE_NAMES as U,coreCommandPaths as B,extensibleCoreBranches as _}from"../../../build-root.js";import{discoverPluginCommandCandidates as F,validateEffectiveCommandPlugins as L,validatePluginCommands as q}from"../../../core/command-plugins/discovery.js";import{qualifiedTopLevelCommandName as C}from"../../../core/command-manifests/registry.js";import{listDirs as x,pathExists as A,walkFiles as Z}from"../../../core/fs-utils.js";import{currentSha as W,gitSync as G}from"../../../core/git.js";import{packageFiles as H}from"../../../core/graph/package-files.js";import{readMarketplaceManifest as K,readPluginManifest as E}from"../../../core/manifest.js";import{readConfig as O}from"../../../core/config.js";import{documentFields as J}from"../../../migrations/004-canvas-documents/fields.js";import{pluginMemoryDir as Q,projectScopeRoot as y}from"../../../core/scope.js";import{runtimeRoot as S}from"../../../core/layout.js";import{isGitUrl as V}from"../shared.js";import{INSTALLED_SOURCE_ID as $,marketplaceSourceId as M}from"./model.js";function I(e){const n=/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/.exec(e);return n===null?null:{core:[Number(n[1]),Number(n[2]),Number(n[3])],prerelease:n[4]?.split(".")??[]}}l(I,"parseSemver");function X(e,n){for(let r=0;r<e.core.length;r+=1){const o=e.core[r]-n.core[r];if(o!==0)return o}if(e.prerelease.length===0||n.prerelease.length===0)return e.prerelease.length===n.prerelease.length?0:e.prerelease.length===0?1:-1;for(let r=0;r<Math.max(e.prerelease.length,n.prerelease.length);r+=1){const o=e.prerelease[r],a=n.prerelease[r];if(o===void 0||a===void 0)return o===void 0?-1:1;const i=/^\d+$/.test(o),t=/^\d+$/.test(a);if(i&&t){const s=Number(o)-Number(a);if(s!==0)return s}else{if(i!==t)return i?-1:1;if(o!==a)return o<a?-1:1}}return 0}l(X,"compareSemver");function P(e,n){if(e===void 0||n===void 0)return!1;const r=I(e),o=I(n);return r!==null&&o!==null&&X(o,r)>0}l(P,"offeredVersionIsNewer");function Y(e){return e.startsWith("./")||e.startsWith("../")||!D(e)&&!V(e)}l(Y,"isMarketplaceRelativeSource");function j(e,n){return{kind:"installed",scope:e.scope,enabled:e.enabled,version:e.version,path:e.root,updateAvailable:P(e.version,n)}}l(j,"pluginInstallState");function ee(e,n){return{key:`${$}:${e.scope}:${e.name}`,name:e.name,ref:e.name,sourceId:$,...e.sourceMarketplace!==void 0?{marketplace:e.sourceMarketplace}:{},...n===void 0?{}:{version:n},description:e.manifest.description,keywords:[],owner:e.manifest.owner,source:e.manifest.source,install:j(e,n),root:e.root}}l(ee,"installedEntry");function v(e,n){return`${e}:${n}`}l(v,"marketplacePluginKey");function ne(e,n){const r=W(e.root)??void 0,o=G(["show","-s","--format=%cI","HEAD"],e.root),a=o.status===0&&o.stdout.trim()||void 0,i=e.manifest.plugins;return{name:e.name,scope:e.scope,url:e.url,ref:e.ref,path:e.root,owner:e.manifest.owner,version:e.manifest.version,pluginCount:i.length,installedCount:i.filter(t=>n.has(v(e.name,t.name))).length,updatableCount:i.filter(t=>{const s=n.get(v(e.name,t.name));return s!==void 0&&P(s.version,t.version)}).length,revision:r,revisionDate:a}}l(ne,"marketplaceDetail");function te(e,n,r){const o=r.get(v(e.name,n.name)),a=Y(n.source)?k(e.root,n.source):void 0;return{key:`${M(e.name,e.scope)}:${n.name}`,name:n.name,ref:`${e.name}/${n.name}`,sourceId:M(e.name,e.scope),marketplace:e.name,version:n.version,description:n.description,keywords:n.keywords??[],owner:e.manifest.owner,source:n.source,install:o===void 0?{kind:"absent"}:j(o,n.version),...o!==void 0?{root:o.root}:a!==void 0&&A(a)?{root:a}:{}}}l(te,"marketplaceEntry");function N(e,n){try{return O(e)}catch(r){return n.push(`Could not read ${e} config: ${b(r)}`),null}}l(N,"readableConfig");function re(e){const n=[...y()===null?[]:[{scope:"project",root:y()}],{scope:"user",root:S()}],r=[];for(const{scope:o,root:a}of n){const i=N(o,e);for(const t of x(k(a,"plugins"))){const s=k(a,"plugins",t);try{const c=E(s);if(c===null){e.push(`Plugin "${t}" has no readable manifest: ${s}`);continue}const u=i?.plugins[t];r.push({name:t,scope:o,root:s,manifest:c,enabled:typeof u?.enabled=="boolean"?u.enabled:!0,sourceMarketplace:typeof u?.source_marketplace=="string"?u.source_marketplace:void 0,version:typeof u?.version=="string"?u.version:c.version})}catch(c){e.push(`Could not read plugin "${t}": ${b(c)}`)}}}return r}l(re,"readInstalledPlugins");function oe(e,n,r){if(!Array.isArray(e))return r.push(`Marketplace "${n}" has an invalid plugins list.`),[];const o=[];for(const[a,i]of e.entries()){if(typeof i!="object"||i===null||Array.isArray(i)){r.push(`Marketplace "${n}" has an invalid plugin entry at index ${a}.`);continue}const t=i;if(typeof t.name!="string"||t.name===""||typeof t.source!="string"||t.source===""){r.push(`Marketplace "${n}" has a plugin entry without a name and source at index ${a}.`);continue}o.push({name:t.name,source:t.source,...typeof t.version=="string"?{version:t.version}:{},...typeof t.description=="string"?{description:t.description}:{},...Array.isArray(t.keywords)&&t.keywords.every(s=>typeof s=="string")?{keywords:t.keywords}:{}})}return o}l(oe,"marketplaceEntries");function se(e,n,r){if(typeof e!="object"||e===null||Array.isArray(e))return r.push(`Marketplace "${n}" has an invalid manifest.`),null;const o=e;if(typeof o.name!="string"||o.name==="")return r.push(`Marketplace "${n}" has a manifest without a name.`),null;const a=o.owner,i=typeof a=="object"&&a!==null&&!Array.isArray(a)?{...typeof a.name=="string"?{name:a.name}:{},...typeof a.email=="string"?{email:a.email}:{}}:void 0;return{name:o.name,plugins:oe(o.plugins,o.name,r),...typeof o.version=="string"?{version:o.version}:{},...i!==void 0?{owner:i}:{}}}l(se,"readableMarketplaceManifest");function ae(e){const n=[...y()===null?[]:[{scope:"project",root:y()}],{scope:"user",root:S()}],r=[];for(const{scope:o,root:a}of n){const i=N(o,e);for(const t of x(k(a,"marketplaces"))){const s=k(a,"marketplaces",t);try{const c=K(s);if(c===null){e.push(`Marketplace "${t}" has no readable manifest: ${s}`);continue}const u=se(c,t,e);if(u===null)continue;const d=i?.marketplaces[t];r.push({name:t,scope:o,root:s,manifest:u,url:d?.url??"",ref:d?.ref??"main"})}catch(c){e.push(`Could not read marketplace "${t}": ${b(c)}`)}}}return r}l(ae,"readMarketplaces");function xe(){const e=[],n=re(e),r=ae(e),o=new Map(n.filter(t=>t.sourceMarketplace!==void 0).map(t=>[v(t.sourceMarketplace,t.name),t])),a=new Map(r.flatMap(t=>t.manifest.plugins.map(s=>[v(t.name,s.name),s.version]))),i=[{id:$,kind:"installed",label:"Installed",entries:n.map(t=>ee(t,t.sourceMarketplace===void 0?void 0:a.get(v(t.sourceMarketplace,t.name)))).sort((t,s)=>t.name.localeCompare(s.name))}];for(const t of r.sort((s,c)=>s.name.localeCompare(c.name)))try{i.push({id:M(t.name,t.scope),kind:"marketplace",label:t.name,marketplace:ne(t,o),entries:t.manifest.plugins.map(s=>te(t,s,o)).sort((s,c)=>s.name.localeCompare(c.name))})}catch(s){e.push(`Could not load marketplace "${t.name}": ${b(s)}`)}return{sources:i,installScopes:["user",...y()===null?[]:["project"]],warnings:e}}l(xe,"loadCatalog");function R(e,n=[]){const r=[...n,e.name];return e.kind==="branch"?{path:r,kind:"branch",description:e.description,whenToUse:e.whenToUse,summary:e.summary,...e.tier!==void 0?{tier:e.tier}:{},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},...e.rootEntry!==void 0?{rootEntry:e.rootEntry}:{},children:e.children.map(o=>R(o,r)),params:[],output:[],effects:[]}:{path:r,kind:"leaf",description:e.description,whenToUse:e.whenToUse,summary:e.summary,...e.tier!==void 0?{tier:e.tier}:{},children:[],params:e.params,output:e.output,effects:[...e.effects],..."rest"in e?{rest:{method:e.rest.method,path:e.rest.path,streaming:e.rest.streaming===!0}}:{}}}l(R,"commandNode");function ie(e){return H(Q(e)).files.map(n=>{const r=J(n.frontmatter,n.body,{missingPrivacy:"public"});let o;try{o=w(n.path).size}catch{}return{name:`${e.name}/${n.name}`,kind:r.kind==="preference"?"preference":"knowledge",...r.summary!==null?{shortForm:r.summary}:{},...r.preview!==""?{routingLine:r.preview}:{},path:n.path,...o!==void 0?{sizeBytes:o}:{},rules:r.rules,unlisted:r.unlisted,...r.whyItExists!==null?{rationale:r.whyItExists}:{},body:n.body}})}l(ie,"docDetails");function le(e){try{return Z(e).reduce((n,r)=>n+w(r).size,0)}catch{return}}l(le,"directorySize");async function Ae(e){if(e.root===void 0||!A(e.root))return null;try{const n=E(e.root);if(n===null)return null;const r=e.install.kind==="installed"?e.install.scope:"user",o={name:e.name,scope:r,root:e.root,manifest:n,enabled:e.install.kind!=="installed"||e.install.enabled},a=new Set(U),[i,t]=await Promise.all([B(),_()]);let s=q(o,a,i,t);const c=[];if(e.install.kind==="installed"&&e.install.enabled)try{s=L(a,void 0,void 0,i,t).find(p=>p.plugin.root===e.root)??s}catch(d){c.push({message:`Could not resolve the effective command registry: ${b(d)}`,next:"Repair the unreadable installed plugin manifest, then reopen the dossier."})}if(e.install.kind!=="installed"||!e.install.enabled){const d=new Map;for(const m of F())if(m.plugin.root!==e.root)for(const f of m.contributions){const h=d.get(f.node.name)??[];h.push(m.plugin),d.set(f.node.name,h)}const p=e.install.kind==="installed"?"enabling":"installing";s.contributions=s.contributions.map(m=>{const f=d.get(m.node.name);if(f===void 0||f.length===0)return m;const h=C({name:e.name,sourceMarketplace:e.marketplace},m.node.name),T=f.map(g=>`moves ${g.name}'s to "${C(g,m.node.name)}"`);return s.issues.push({code:"command_collision",plugin:o.name,path:m.node.name,message:`top-level command "${m.node.name}" is already claimed by ${f.map(g=>g.name).join(", ")} \u2014 ${p} mounts this one as "${h}" and ${T.join(" and ")}`,received:[...f.map(g=>g.name),o.name].join(", "),expected:"an origin-qualified top-level command for each claimant",next:`After ${p}, invoke "${h}".`}),{...m,node:{...m.node,name:h}}})}const u=n.transport;return{root:e.root,transport:u?.kind??null,...u?.kind==="exec"&&u.executable!==void 0?{transportTarget:u.executable}:{},...u?.kind==="http"?{transportTarget:u.endpoint}:{},commands:s.contributions.map(d=>R(d.node)),commandIssues:[...s.issues.map(({message:d,next:p})=>({message:d,...p!==void 0?{next:p}:{}})),...c],docs:ie(o),sizeBytes:le(e.root)}}catch{return null}}l(Ae,"loadCapability");function b(e){return e instanceof Error?e.message:String(e)}l(b,"errorMessage");export{Ae as loadCapability,xe as loadCatalog};
1
+ var z=Object.defineProperty;var l=(e,n)=>z(e,"name",{value:n,configurable:!0});import{statSync as w}from"node:fs";import{isAbsolute as D,join as k}from"node:path";import{SUBTREE_NAMES as U,coreCommandPaths as B,extensibleCoreBranches as _}from"../../../build-root.js";import{discoverPluginCommandCandidates as F,validateEffectiveCommandPlugins as L,validatePluginCommands as q}from"../../../core/command-plugins/discovery.js";import{qualifiedTopLevelCommandName as C}from"../../../core/command-manifests/registry.js";import{listDirs as x,pathExists as A,walkFiles as Z}from"../../../core/fs-utils.js";import{currentSha as W,gitSync as G}from"../../../core/git.js";import{packageFiles as H}from"../../../core/graph/package-files.js";import{readMarketplaceManifest as K,readPluginManifest as E}from"../../../core/manifest.js";import{readConfig as O}from"../../../core/config.js";import{documentFields as J}from"../../../migrations/004-canvas-documents/fields.js";import{pluginMemoryDir as Q,projectScopeRoot as y}from"../../../core/scope.js";import{runtimeRoot as S}from"../../../core/layout.js";import{isGitUrl as V}from"../shared.js";import{INSTALLED_SOURCE_ID as $,marketplaceSourceId as M}from"./model.js";function I(e){const n=/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/.exec(e);return n===null?null:{core:[Number(n[1]),Number(n[2]),Number(n[3])],prerelease:n[4]?.split(".")??[]}}l(I,"parseSemver");function X(e,n){for(let r=0;r<e.core.length;r+=1){const o=e.core[r]-n.core[r];if(o!==0)return o}if(e.prerelease.length===0||n.prerelease.length===0)return e.prerelease.length===n.prerelease.length?0:e.prerelease.length===0?1:-1;for(let r=0;r<Math.max(e.prerelease.length,n.prerelease.length);r+=1){const o=e.prerelease[r],a=n.prerelease[r];if(o===void 0||a===void 0)return o===void 0?-1:1;const i=/^\d+$/.test(o),t=/^\d+$/.test(a);if(i&&t){const s=Number(o)-Number(a);if(s!==0)return s}else{if(i!==t)return i?-1:1;if(o!==a)return o<a?-1:1}}return 0}l(X,"compareSemver");function P(e,n){if(e===void 0||n===void 0)return!1;const r=I(e),o=I(n);return r!==null&&o!==null&&X(o,r)>0}l(P,"offeredVersionIsNewer");function Y(e){return e.startsWith("./")||e.startsWith("../")||!D(e)&&!V(e)}l(Y,"isMarketplaceRelativeSource");function j(e,n){return{kind:"installed",scope:e.scope,enabled:e.enabled,version:e.version,path:e.root,updateAvailable:P(e.version,n)}}l(j,"pluginInstallState");function ee(e,n){return{key:`${$}:${e.scope}:${e.name}`,name:e.name,ref:e.name,sourceId:$,...e.sourceMarketplace!==void 0?{marketplace:e.sourceMarketplace}:{},...n===void 0?{}:{version:n},description:e.manifest.description,keywords:[],owner:e.manifest.owner,source:e.manifest.source,install:j(e,n),root:e.root}}l(ee,"installedEntry");function v(e,n){return`${e}:${n}`}l(v,"marketplacePluginKey");function ne(e,n){const r=W(e.root)??void 0,o=G(["show","-s","--format=%cI","HEAD"],e.root),a=o.status===0&&o.stdout.trim()||void 0,i=e.manifest.plugins;return{name:e.name,scope:e.scope,url:e.url,ref:e.ref,path:e.root,owner:e.manifest.owner,version:e.manifest.version,pluginCount:i.length,installedCount:i.filter(t=>n.has(v(e.name,t.name))).length,updatableCount:i.filter(t=>{const s=n.get(v(e.name,t.name));return s!==void 0&&P(s.version,t.version)}).length,revision:r,revisionDate:a}}l(ne,"marketplaceDetail");function te(e,n,r){const o=r.get(v(e.name,n.name)),a=Y(n.source)?k(e.root,n.source):void 0;return{key:`${M(e.name,e.scope)}:${n.name}`,name:n.name,ref:`${e.name}/${n.name}`,sourceId:M(e.name,e.scope),marketplace:e.name,version:n.version,description:n.description,keywords:n.keywords??[],owner:e.manifest.owner,source:n.source,install:o===void 0?{kind:"absent"}:j(o,n.version),...o!==void 0?{root:o.root}:a!==void 0&&A(a)?{root:a}:{}}}l(te,"marketplaceEntry");function N(e,n){try{return O(e)}catch(r){return n.push(`Could not read ${e} config: ${b(r)}`),null}}l(N,"readableConfig");function re(e){const n=[...y()===null?[]:[{scope:"project",root:y()}],{scope:"user",root:S()}],r=[];for(const{scope:o,root:a}of n){const i=N(o,e);for(const t of x(k(a,"plugins"))){const s=k(a,"plugins",t);try{const c=E(s);if(c===null){e.push(`Plugin "${t}" has no readable manifest: ${s}`);continue}const u=i?.plugins[t];r.push({name:t,scope:o,root:s,manifest:c,enabled:typeof u?.enabled=="boolean"?u.enabled:!0,sourceMarketplace:typeof u?.source_marketplace=="string"?u.source_marketplace:void 0,version:typeof u?.version=="string"?u.version:c.version})}catch(c){e.push(`Could not read plugin "${t}": ${b(c)}`)}}}return r}l(re,"readInstalledPlugins");function oe(e,n,r){if(!Array.isArray(e))return r.push(`Marketplace "${n}" has an invalid plugins list.`),[];const o=[];for(const[a,i]of e.entries()){if(typeof i!="object"||i===null||Array.isArray(i)){r.push(`Marketplace "${n}" has an invalid plugin entry at index ${a}.`);continue}const t=i;if(typeof t.name!="string"||t.name===""||typeof t.source!="string"||t.source===""){r.push(`Marketplace "${n}" has a plugin entry without a name and source at index ${a}.`);continue}o.push({name:t.name,source:t.source,...typeof t.version=="string"?{version:t.version}:{},...typeof t.description=="string"?{description:t.description}:{},...Array.isArray(t.keywords)&&t.keywords.every(s=>typeof s=="string")?{keywords:t.keywords}:{}})}return o}l(oe,"marketplaceEntries");function se(e,n,r){if(typeof e!="object"||e===null||Array.isArray(e))return r.push(`Marketplace "${n}" has an invalid manifest.`),null;const o=e;if(typeof o.name!="string"||o.name==="")return r.push(`Marketplace "${n}" has a manifest without a name.`),null;const a=o.owner,i=typeof a=="object"&&a!==null&&!Array.isArray(a)?{...typeof a.name=="string"?{name:a.name}:{},...typeof a.email=="string"?{email:a.email}:{}}:void 0;return{name:o.name,plugins:oe(o.plugins,o.name,r),...typeof o.version=="string"?{version:o.version}:{},...i!==void 0?{owner:i}:{}}}l(se,"readableMarketplaceManifest");function ae(e){const n=[...y()===null?[]:[{scope:"project",root:y()}],{scope:"user",root:S()}],r=[];for(const{scope:o,root:a}of n){const i=N(o,e);for(const t of x(k(a,"marketplaces"))){const s=k(a,"marketplaces",t);try{const c=K(s);if(c===null){e.push(`Marketplace "${t}" has no readable manifest: ${s}`);continue}const u=se(c,t,e);if(u===null)continue;const d=i?.marketplaces[t];r.push({name:t,scope:o,root:s,manifest:u,url:d?.url??"",ref:d?.ref??"main"})}catch(c){e.push(`Could not read marketplace "${t}": ${b(c)}`)}}}return r}l(ae,"readMarketplaces");function xe(){const e=[],n=re(e),r=ae(e),o=new Map(n.filter(t=>t.sourceMarketplace!==void 0).map(t=>[v(t.sourceMarketplace,t.name),t])),a=new Map(r.flatMap(t=>t.manifest.plugins.map(s=>[v(t.name,s.name),s.version]))),i=[{id:$,kind:"installed",label:"Installed",entries:n.map(t=>ee(t,t.sourceMarketplace===void 0?void 0:a.get(v(t.sourceMarketplace,t.name)))).sort((t,s)=>t.name.localeCompare(s.name))}];for(const t of r.sort((s,c)=>s.name.localeCompare(c.name)))try{i.push({id:M(t.name,t.scope),kind:"marketplace",label:t.name,marketplace:ne(t,o),entries:t.manifest.plugins.map(s=>te(t,s,o)).sort((s,c)=>s.name.localeCompare(c.name))})}catch(s){e.push(`Could not load marketplace "${t.name}": ${b(s)}`)}return{sources:i,installScopes:["user",...y()===null?[]:["project"]],warnings:e}}l(xe,"loadCatalog");function R(e,n=[]){const r=[...n,e.name];return e.kind==="branch"?{path:r,kind:"branch",description:e.description,whenToUse:e.whenToUse,summary:e.summary,...e.tier!==void 0?{tier:e.tier}:{},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},...e.rootEntry!==void 0?{rootEntry:e.rootEntry}:{},children:e.children.map(o=>R(o,r)),params:[],output:[],effects:[]}:{path:r,kind:"leaf",description:e.description,whenToUse:e.whenToUse,summary:e.summary,...e.tier!==void 0?{tier:e.tier}:{},children:[],params:e.params,output:e.output,effects:[...e.effects],..."rest"in e?{rest:{method:e.rest.method,path:e.rest.path,streaming:e.rest.streaming===!0}}:{}}}l(R,"commandNode");function ie(e){return H(Q(e)).files.map(n=>{const r=J(n.frontmatter,n.body,{missingPrivacy:"public"});let o;try{o=w(n.path).size}catch{}return{name:`${e.name}/${n.name}`,kind:r.kind==="preference"?"preference":"knowledge",...r.summary!==null?{shortForm:r.summary}:{},...r.preview!==""?{routingLine:r.preview}:{},path:n.path,...o!==void 0?{sizeBytes:o}:{},rules:r.rules,unlisted:r.unlisted,...r.whyItExists!==null?{rationale:r.whyItExists}:{},body:n.body}})}l(ie,"docDetails");function le(e){try{return Z(e).reduce((n,r)=>n+w(r).size,0)}catch{return}}l(le,"directorySize");async function Ae(e){if(e.root===void 0||!A(e.root))return null;try{const n=E(e.root);if(n===null)return null;const r=e.install.kind==="installed"?e.install.scope:"user",o={name:e.name,scope:r,root:e.root,manifest:n,enabled:e.install.kind!=="installed"||e.install.enabled},a=new Set(U),[i,t]=await Promise.all([B(),_()]);let s=q(o,a,i,t);const c=[];if(e.install.kind==="installed"&&e.install.enabled)try{s=L(a,void 0,void 0,i,t).find(p=>p.plugin.root===e.root)??s}catch(d){c.push({message:`Could not resolve the effective command registry: ${b(d)}`,next:"Repair the unreadable installed plugin manifest, then reopen the dossier."})}if(e.install.kind!=="installed"||!e.install.enabled){const d=new Map;for(const m of F())if(m.plugin.root!==e.root)for(const f of m.contributions){const h=d.get(f.node.name)??[];h.push(m.plugin),d.set(f.node.name,h)}const p=e.install.kind==="installed"?"enabling":"installing";s.contributions=s.contributions.map(m=>{const f=d.get(m.node.name);if(f===void 0||f.length===0)return m;const h=C({name:e.name,sourceMarketplace:e.marketplace},m.node.name),T=f.map(g=>`moves ${g.name}'s to "${C(g,m.node.name)}"`);return s.issues.push({code:"command_collision",plugin:o.name,path:m.node.name,message:`top-level command "${m.node.name}" is already claimed by ${f.map(g=>g.name).join(", ")} \u2014 ${p} mounts this one as "${h}" and ${T.join(" and ")}`,received:[...f.map(g=>g.name),o.name].join(", "),expected:"an origin-qualified top-level command for each claimant",next:`After ${p}, invoke "${h}".`}),{...m,node:{...m.node,name:h}}})}const u=n.transport;return{root:e.root,transport:u?.kind==="machine"?"http":u?.kind??null,...u?.kind==="exec"&&u.executable!==void 0?{transportTarget:u.executable}:{},...u?.kind==="http"?{transportTarget:u.endpoint}:{},commands:s.contributions.map(d=>R(d.node)),commandIssues:[...s.issues.map(({message:d,next:p})=>({message:d,...p!==void 0?{next:p}:{}})),...c],docs:ie(o),sizeBytes:le(e.root)}}catch{return null}}l(Ae,"loadCapability");function b(e){return e instanceof Error?e.message:String(e)}l(b,"errorMessage");export{Ae as loadCapability,xe as loadCatalog};
@@ -14,4 +14,4 @@ ${c.join(`
14
14
  ${d.map(f=>`- ${f}`).join(`
15
15
  `)}`,{issues:d})}const[r,c]=await Promise.all([D(),I()]),l=J({name:e.name,scope:n,root:t,manifest:e,enabled:!0},new Set(_),r,c);if(l.issues.length>0)throw W(`plugin command behavior at ${o} is invalid`,l.issues);await Rn(e,t,n,i,o,s);const p=g(t,"memory"),v=Qe(n,i,{candidate:e});for(const d of Je(p,f=>f.endsWith(".md"),f=>f.startsWith("."))){let f;try{f=tn(Ge(d)).data}catch(k){throw m(`plugin memory document ${L(t,d)} has invalid YAML frontmatter: ${k instanceof Error?k.message.split(`
16
16
  `)[0]:String(k)}`)}const y=en(f);if(y!==null)throw m(`plugin memory document ${L(t,d)} is invalid: ${y}`);for(const k of Xe(f?.extensions,v))throw m(`plugin memory document ${L(t,d)} is invalid: ${k.path}: ${k.message}`)}return Sn(e,t,n,o,a)}u(j,"assertPluginCandidateValid");function Tn(e){return{kind:"http",endpoint:new URL(e.endpoint).origin,...e.authEnv!==void 0?{authEnv:e.authEnv}:{}}}u(Tn,"invocationTransport");async function qn(e,t,n,i,o){const s=await j(o,n,t,i,`${n}/.crouter-plugin/plugin.json`),a={name:e,scope:t,root:n,manifest:o,enabled:!0},[r,c]=await Promise.all([D(),I()]),l=J(a,new Set(_),r,c);if(l.issues.length>0)throw W(`bundle staged package for "${e}" is invalid`,l.issues);return s}u(qn,"validateStagedBundle");function ve(e){return e.sourceMarketplace!==void 0||K(e.root)||F(e.root)}u(ve,"isSourceInstalled");function ye(e){throw m(`HTTP plugin "${e}" uses the removed commands-only endpoint format`,{next:`Run \`crtr pkg plugin remove ${e}\`, then reinstall it with \`crtr pkg plugin install --name ${e} --endpoint <archive-url>\`.`})}u(ye,"rejectLegacyHttpPlugin");function An(e,t,n){if(!b(n))return;const i=q(e,t);if(i?.manifest.bundle!==void 0)return i;throw i?.manifest.transport?.kind==="http"&&i!==void 0&&!ve(i)&&ye(e),m(`plugin "${e}" already exists at ${n}; run \`crtr pkg plugin remove ${e}\` before installing the archive plugin.`)}u(An,"existingBundleRoot");function ke(e,t,n,i){const o=he(e,t.endpoint,t.authEnv),s={endpoint:o.endpoint,...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},a=g(A(n),e);return An(e,n,a),{bundle:s,transport:Tn(s),scopeRootPath:i,root:a}}u(ke,"resolveBundleTarget");function we(e,t){return{name:e,endpoint:t.endpoint,...t.authEnv!==void 0?{authEnv:t.authEnv}:{}}}u(we,"fetchTarget");const Cn=6e4,Mn=1e4;async function be(e,t,n,i){const o=t==="project"?O():R();return M(g(o,"tmp")),We(oe(o,e),()=>i(o),{timeoutMs:n,timeoutError:u(()=>m(`timed out waiting for another crtr process to finish replacing plugin "${e}"`,{next:`Re-run once that process has finished, or check for a stalled crtr holding ${oe(o,e)}.`}),"timeoutError")})}u(be,"withBundleSwapLock");async function $e(e,t,n,i){return be(e,n,Cn,async o=>{const s=ke(e,t,n,o),a=await ue(we(e,s.bundle));if(a.status!==200){const r=`HTTP plugin bundle fetch failed for "${e}": ${a.message}`;throw a.status==="auth_env_missing"?w(r):a.status==="cli_unreachable"?Be(r):m(r)}return Pe(e,n,s,a.raw,a.etag,i)})}u($e,"replaceBundlePlugin");async function kt(e,t,n,i){return be(e,n,Mn,async o=>{try{const s=ke(e,t,n,o),a=i.conditional?Ie(n).plugins[e]?.etag:void 0,r={...a!==void 0?{ifNoneMatch:a}:{},...i.timeoutMs!==void 0?{timeoutMs:i.timeoutMs}:{}},c=await ue(we(e,s.bundle),r);if(c.status===304)return;if(c.status!==200)throw m(`HTTP plugin bundle revalidation failed for "${e}": ${c.message}`);const l=dn(c.raw);if(pn(e,n,s.root,l)){mn(n,e,c.etag,{swapped:!1});return}return await Pe(e,n,s,c.raw,c.etag,i,l)}finally{Ln(n,e)}})}u(kt,"revalidateBundlePlugin");function Ln(e,t){U(e,n=>{n.plugins[t]===void 0&&(n.plugins[t]={}),n.plugins[t].checked_at=G()})}u(Ln,"recordPluginCheck");async function Pe(e,t,n,i,o,s,a){const{bundle:r,transport:c,scopeRootPath:l,root:p}=n,v=await Hn(e,i,{scope:t,scopeRootPath:l,root:p,bundle:r,transport:c,etag:o,enable:s.enable,...a!==void 0?{version:a}:{}}),{validated:d,version:f,staged:y}=v,k=await Y(e,t);if(k===void 0)throw m(`bundle plugin "${e}" has no staged command report`);const $=await Q(e,t);return{name:e,scope:t,path:p,transport:"http",version:f,docs:d.memory.length,kinds:Object.keys(d.kinds??{}).length,pageComponents:(d.page_components??[]).length,commands:k,...$!==void 0?{hooks:$}:{},...y!==void 0?{memory:y}:{}}}u(Pe,"applyBundleArchive");async function wt(e){const[t,n]=await Promise.all([D(),I()]);return fn(e,{reservedCoreNames:new Set(_),coreCommandPaths:t,extensibleCoreBranches:n})}u(wt,"validatePluginArchive");async function Hn(e,t,n){const[i,o]=await Promise.all([D(),I()]);return un(e,t,{...n,validation:{reservedCoreNames:new Set(_),coreCommandPaths:i,extensibleCoreBranches:o},validateStaged:u((s,a)=>qn(e,n.scope,s,n.scopeRootPath,a),"validateStaged"),invalid:u((s,a)=>W(s,a),"invalid")})}u(Hn,"installPluginArchive");async function Bn(e,t,n,i){const o=he(e,t,n);return $e(e,{endpoint:o.endpoint,...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},i,{enable:!0})}u(Bn,"installHttpPlugin");function _n(e){if(e.startsWith("./")||e.startsWith("../")||e.startsWith("/"))return{kind:"local",value:e};if(me(e))return{kind:"git",value:e};const t=e.split("/");if(t.length===2&&t[0]!==""&&t[1]!=="")return{kind:"marketplace",value:{marketplace:t[0],plugin:t[1]}};throw w(`invalid plugin install ref: ${e}
17
- Accepted forms: marketplace/plugin, git URL (https://, ssh://, git@, file://, or *.git), or local path starting ./, ../, or /. Bare plugin names are not install refs.`)}u(_n,"classifyInstallRef");function je(e){return e.startsWith("./")||e.startsWith("../")||!Me(e)&&!me(e)}u(je,"isMarketplaceRelativeSource");async function xe(e,t,n){const i=t.get(e);if(i!==void 0)return i;const o=De(e);if(o===null){if(n.strict)throw h(`marketplace not found: ${e}`);return null}const s=se(o.root);if(s===null)throw h(`marketplace manifest not found: ${o.root}/.crouter-marketplace/marketplace.json`);if(!K(o.root)){const l={marketplace:o,manifest:s,pull:{updated:!1,sha:S(o.root)??""}};return t.set(e,l),l}const a=S(o.root),r=g(ee(o.root),`.${e}.${process.pid}.${Date.now()}.candidate`),c=`${r}.previous`;try{V(o.url,r,{ref:o.ref,depth:1});const l=se(r);if(l===null)throw h(`marketplace manifest not found in candidate checkout: ${r}/.crouter-marketplace/marketplace.json`);const p=ne().filter(d=>d.sourceMarketplace===e&&F(d.root));for(const d of p){const f=l.plugins.find(Te=>Te.name===d.name);if(f===void 0||!je(f.source))throw m(`marketplace candidate removes relative-source plugin "${d.name}" while it is installed`);const y=ze(d.root);if(y===null)throw m(`installed marketplace plugin "${d.name}" has no readable symlink target`);const k=L(o.root,N(ee(d.root),y)),$=N(r,f.source);if($!==N(r,k))throw m(`marketplace candidate moves installed relative-source plugin "${d.name}" from ${k} to ${f.source}`);if(!b($))throw h(`marketplace candidate source path does not exist for plugin "${d.name}": ${$}`);const X=x($);if(X===null)throw h(`plugin manifest not found in marketplace candidate: ${$}/.crouter-plugin/plugin.json`);const Se=d.scope==="project"?C("project"):R();await j(X,$,d.scope,Se,`${$}/.crouter-plugin/plugin.json`)}P(o.root,c);try{P(r,o.root)}catch(d){throw P(c,o.root),d}E(c);for(const d of p){const f=l.plugins.find(y=>y.name===d.name);Re(d.scope,d.name,f.version??d.manifest.version)}U(o.scope,d=>{d.marketplaces[e]===void 0&&(d.marketplaces[e]={}),d.marketplaces[e].last_updated=G()});const v={marketplace:o,manifest:l,pull:{updated:a!==S(o.root),sha:S(o.root)??""}};return t.set(e,v),v}finally{E(r),b(c)&&!b(o.root)&&P(c,o.root)}}u(xe,"refreshMarketplace");function Re(e,t,n){B(e,i=>{const o=i.plugins[t];o!==void 0?n!==void 0&&(o.version=n):i.plugins[t]={enabled:!0,version:n}}),U(e,i=>{i.plugins[t]===void 0&&(i.plugins[t]={}),i.plugins[t].last_updated=G()})}u(Re,"updatePluginVersion");async function Dn(e,t,n){const i=n==="project"?O():R();z(n,i);const o=A(n),s=Ye(e),a=g(i,"tmp",`${s}.${process.pid}.${Date.now()}`);M(g(i,"tmp"));try{V(e,a,{ref:t,depth:1});const r=x(a);if(r===null)throw m(`cloned repo does not contain a valid .crouter-plugin/plugin.json: ${e}`);const c=await j(r,a,n,i,`${e}/.crouter-plugin/plugin.json`),l=g(o,r.name);if(b(l))throw m(`plugin "${r.name}" is already installed at ${l}`);return M(o),P(a,l),B(n,p=>{p.plugins[r.name]={enabled:!0,version:r.version}}),{name:r.name,scope:n,path:l,...c!==void 0?{memory:c}:{}}}finally{E(a)}}u(Dn,"installFromGit");async function In(e,t){const n=N(e),i=x(n);if(i===null)throw h(`plugin manifest not found at ${n}/.crouter-plugin/plugin.json`);const o=t==="project"?O():R(),s=await j(i,n,t,o,`${n}/.crouter-plugin/plugin.json`,void 0,"linked");z(t,o);const a=g(A(t),i.name);if(b(a))throw m(`plugin "${i.name}" is already installed at ${a}`);return ae(n,a),B(t,r=>{r.plugins[i.name]={enabled:!0,version:i.version}}),{name:i.name,scope:t,path:a,...s!==void 0?{memory:s}:{}}}u(In,"installFromLocal");async function Nn(e,t){const n=await xe(e.marketplace,new Map,{strict:!0});if(n===null)throw h(`marketplace not found: ${e.marketplace}`);const i=n.manifest.plugins.find(l=>l.name===e.plugin);if(i===void 0)throw h(`plugin "${e.plugin}" not found in marketplace "${e.marketplace}"`);const o=C(t);z(t,o);const s=g(A(t),e.plugin);if(b(s))throw m(`plugin "${e.plugin}" is already installed at ${s}`);let a,r;if(je(i.source)){const l=g(n.marketplace.root,i.source);if(!b(l))throw h(`plugin source path does not exist: ${l}`);const p=x(l);if(p===null)throw h(`plugin manifest not found at ${l}/.crouter-plugin/plugin.json`);r=await j(p,l,t,o,`${l}/.crouter-plugin/plugin.json`,void 0,"linked"),ae(l,s),a=p}else{const l=g(o,"tmp",`${e.plugin}.${process.pid}.${Date.now()}`);M(g(o,"tmp"));try{V(i.source,l,{depth:1});const p=x(l);if(p===null)throw h(`plugin manifest not found at ${l}/.crouter-plugin/plugin.json`);r=await j(p,l,t,o,`${l}/.crouter-plugin/plugin.json`),M(A(t)),P(l,s),a=p}finally{E(l)}}const c=i.version??a.version;return B(t,l=>{const p={enabled:!0,source_marketplace:e.marketplace};c!==void 0&&(p.version=c),l.plugins[e.plugin]=p}),{name:e.plugin,scope:t,path:s,...r!==void 0?{memory:r}:{}}}u(Nn,"installFromMarketplace");async function Un(e){if(F(e.root)){const r=x(e.root);if(r===null)throw h(`plugin manifest not found at ${e.root}/.crouter-plugin/plugin.json`);const c=e.scope==="project"?C("project"):R(),l=await j(r,e.root,e.scope,c,`${e.root}/.crouter-plugin/plugin.json`,void 0,"linked");return{updated:!1,sha:S(e.root)??"",manifest:r,...l!==void 0?{memory:l}:{}}}if(!K(e.root)){const r=x(e.root);if(r===null)throw h(`plugin manifest not found at ${e.root}/.crouter-plugin/plugin.json`);const c=e.scope==="project"?C("project"):R(),l=await j(r,e.root,e.scope,c,`${e.root}/.crouter-plugin/plugin.json`);return{updated:!1,sha:"",manifest:r,...l!==void 0?{memory:l}:{}}}const t=re(["config","--get","remote.origin.url"],e.root).stdout.trim();if(t==="")throw m(`plugin "${e.name}" has no origin remote to stage an update from`);const n=re(["symbolic-ref","--short","HEAD"],e.root).stdout.trim();if(n==="")throw m(`plugin "${e.name}" is detached at its installed revision and has no tracked branch to update`);const i=e.scope==="project"?C("project"):R(),o=g(i,"tmp",`${e.name}.${process.pid}.${Date.now()}.candidate`),s=`${o}.previous`,a=S(e.root);M(g(i,"tmp"));try{V(t,o,{ref:n,depth:1});const r=x(o);if(r===null)throw h(`plugin manifest not found in update candidate: ${o}/.crouter-plugin/plugin.json`);const c=await j(r,o,e.scope,i,`${o}/.crouter-plugin/plugin.json`);P(e.root,s);try{P(o,e.root)}catch(p){throw P(s,e.root),p}E(s);const l=S(e.root)??"";return{updated:a!==l,sha:l,manifest:r,...c!==void 0?{memory:c}:{}}}finally{E(o),b(s)&&!b(e.root)&&P(s,e.root)}}u(Un,"updateGitPluginCandidate");async function Ee(e,t,n){if(e.manifest.provider!==void 0)return{name:e.name,transport:"http",updated:!1,warnings:[`"${e.name}" is a capability provider bundle; the runtime refreshes it on grant sync`]};const i=ve(e);if(!i&&e.manifest.bundle!==void 0){const p=await $e(e.name,e.manifest.bundle,e.scope,{enable:e.enabled});return{name:e.name,transport:"http",updated:!0,version:p.version,docs:p.docs,kinds:p.kinds,pageComponents:p.pageComponents,commands:p.commands,...p.hooks!==void 0?{hooks:p.hooks}:{},...p.memory!==void 0?{memory:p.memory}:{}}}e.manifest.transport?.kind==="http"&&!i&&ye(e.name);let o,s=!1;if(e.sourceMarketplace!==void 0){const p=await xe(e.sourceMarketplace,t,n);p!==null&&(o=p.manifest.plugins.find(d=>d.name===e.name)?.version,s=p.pull.updated)}const a=await Un(e);Re(e.scope,e.name,o??a.manifest.version);const r=await Y(e.name,e.scope),c=await Q(e.name,e.scope),l=[...ge(a.manifest),...await Z(a.manifest)];return{name:e.name,...e.manifest.transport!==void 0?{transport:e.manifest.transport.kind}:{},updated:s||a.updated,sha:a.sha,...r!==void 0?{commands:r}:{},...c!==void 0?{hooks:c}:{},...a.memory!==void 0?{memory:a.memory}:{},...l.length>0?{warnings:l}:{}}}u(Ee,"updateOnePlugin");async function T(e){const n=(await Le().documents.reconcilePackages().catch(i=>He(i))).conflicts.find(i=>i.plugin===e);if(n)throw new _e("conflict",`plugin "${e}" is installed but its documents are not on the canvas: ${n.message}`,yn.GENERAL,{plugin:e,next:"Uninstall the other package providing that plugin name, then run `crtr pkg plugin update --name "+e+"`."})}u(T,"reconcilePluginDocuments");const bt=H({name:"install",description:"install from a ref or HTTP endpoint",whenToUse:"installing one plugin from a marketplace ref, git URL, local path, or an HTTP directory-archive endpoint",help:{name:"pkg plugin install",summary:"install a plugin from a ref or HTTP endpoint",params:[{kind:"positional",name:"install-ref",type:"string",required:!1,constraint:"Plugin install ref. Provide exactly one of INSTALL-REF or --endpoint. Accepted ref forms: marketplace/plugin; git URL starting https://, ssh://, git@, file://, or ending .git; local path starting ./, ../, or /."},{kind:"flag",name:"endpoint",type:"string",required:!1,constraint:"HTTP plugin directory-archive endpoint. Provide exactly one of --endpoint or INSTALL-REF. Must be an absolute https: URL; http: is permitted only for localhost, any *.localhost host, 127.0.0.1, [::1], ::1, or host.docker.internal. Credentials and fragments are rejected."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Required with --endpoint and rejected without it. Lowercase kebab-case plugin handle."},{kind:"flag",name:"auth-env",type:"string",required:!1,constraint:"Optional environment variable holding the HTTP bearer token. Accepted only with --endpoint; its value is never stored."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: project if available, else user."},{kind:"flag",name:"ref",type:"string",required:!1,constraint:"Git ref (branch/tag) for git URL installs. Rejected with --endpoint; ignored for marketplace and local path refs."}],output:[{name:"name",type:"string",required:!0,constraint:"Plugin name as installed."},{name:"scope",type:"string",required:!0,constraint:"Scope the plugin was installed into."},{name:"path",type:"string",required:!0,constraint:"Absolute path to the installed plugin directory."},{name:"transport",type:"string",required:!1,constraint:"exec or http when the plugin declares commands. Always http for an --endpoint install."},{name:"version",type:"string",required:!1,constraint:"First 12 hex characters of the fetched archive SHA-256 for an --endpoint install."},{name:"docs",type:"integer",required:!1,constraint:"Memory document count written from an --endpoint archive."},{name:"kinds",type:"integer",required:!1,constraint:"Kind-registry entry count declared by an --endpoint archive\u2019s bundle.json."},{name:"pageComponents",type:"integer",required:!1,constraint:"Page-component registration count declared by an --endpoint archive\u2019s bundle.json."},{name:"commands",type:"object",required:!1,constraint:"Present only when the plugin declares a command manifest. {mounts: string[] (accepted command paths now live for the next invocation), issues: object[] (typed validation issues that rejected a contribution \u2014 {code, path?, message, received, expected, next})}. Validated statically; commands are never executed."},{name:"hooks",type:"object",required:!1,constraint:"Present only when the plugin declares hooks. {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], lifecycle?: {event, phase, op, description, effects}[], issues: object[], trust: string}. Command targets, lifecycle events, and replacement collisions are validated statically; hook executables are never run."},{name:"warnings",type:"string[]",required:!1,constraint:"Advisory missing PATH executable requirements declared by the installed plugin. The install still succeeds and leaves the plugin enabled."},{name:"memory",type:"object",required:!1,constraint:"Present only when the plugin ships documents (its `memory/` folder). {docs: integer (documents checked), linkedSource?: string (the caller-owned directory, present only when the install keeps that directory as a live link)}."}],outputKind:"object",effects:["A ref install clones, links, or copies the plugin into the scope plugins directory and registers it with enabled=true. Marketplace installs refresh the source marketplace before resolving the plugin entry.","An --endpoint install fetches and validates one authenticated uncompressed tar archive before writing. It replaces the complete plugin directory with synthesized provenance and invocation metadata, commands.json, memory docs, and, when declared, hooks.json plus the hooks/ tree; it also copies any kind-registry entries the bundle.json declares (they join the launch registry below this scope\u2019s own config.json kinds), and any page components it declares (they join the active page-component catalog alongside the user config entries); a fetch or validation failure preserves the prior package.","If the plugin declares a command manifest, its commands go live on the next crtr invocation. Exec commands run trusted local code only when explicitly invoked; HTTP commands call their declared endpoint only when explicitly invoked.","Plugin command hooks go live on the next crtr invocation. They run implicitly with caller authority, may receive normalized inputs and after results, may block or replace declared targets, and may transmit received data. Lifecycle hooks run unprompted at every declared event before node launch and may transmit node identity and resolved paths. Hook executables are never run during install or update validation.","A plugin manifest `requires` declaration is checked against the PATH a node bash receives after install. Missing executables emit a warning with the declared install hint but never fail the install, disable the plugin, or remove its bare-binary contributions.","Before activation the candidate\u2019s `memory/` documents are checked for identity findings and refused if it cannot mount; a local path or marketplace-relative source stays a live link, named as `memory.linkedSource`. A refused package installs nothing."]},run:u(async e=>{const t=e.installRef,n=e.endpoint,i=e.name,o=e.authEnv,s=e.scope,a=e.ref,r=wn(s);if(t===void 0==(n===void 0))throw w("provide exactly one of INSTALL-REF or --endpoint");if(n!==void 0){if(i===void 0)throw w("--name is required with --endpoint");if(a!==void 0)throw w("--ref is only valid with a git INSTALL-REF, not --endpoint");const y=await Bn(i,n,o,r);return await T(i),y}if(i!==void 0)throw w("--name is only valid with --endpoint");if(o!==void 0)throw w("--auth-env is only valid with --endpoint");const c=_n(t),l=c.kind==="marketplace"?await Nn(c.value,r):c.kind==="local"?await In(c.value,r):await Dn(c.value,a,r),p=q(l.name,l.scope),v=await Y(l.name,l.scope),d=await Q(l.name,l.scope),f=p===null?[]:[...ge(p.manifest),...await Z(p.manifest)];return await T(l.name),{...l,...p?.manifest.transport!==void 0?{transport:p.manifest.transport.kind}:{},...v!==void 0?{commands:v}:{},...d!==void 0?{hooks:d}:{},...f.length>0?{warnings:f}:{}}},"run")}),$t=H({name:"remove",description:"remove plugin and directory",whenToUse:"uninstalling a plugin: deletes its directory and removes it from config",help:{name:"pkg plugin remove",summary:"remove a plugin and its directory from the given scope",params:[{kind:"positional",name:"name",type:"string",required:!0,constraint:"Plugin name to remove."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: searches every scope."}],output:[{name:"scopes",type:"string[]",required:!0,constraint:"Scopes the plugin was removed from."}],outputKind:"object",effects:["Deletes the plugin directory. Removes the plugin entry from config.json."]},run:u(async e=>{const t=e.name,n=e.scope;if(t==="crtr")throw w('cannot remove builtin plugin "crtr" \u2014 it ships with the binary');let i;if(n!==void 0){const s=te(n);if(s==="builtin")throw w("cannot remove plugins from builtin scope");i=s==="all"?["project","user"].filter(a=>a!=="project"||ie()!==null):[s]}else i=["project","user"].filter(s=>s!=="project"||ie()!==null);const o=[];for(const s of i){const a=A(s);if(a===null)continue;const r=g(a,t);b(r)&&(E(r),B(s,c=>{delete c.plugins[t]}),U(s,c=>{delete c.plugins[t]}),o.push(s))}if(o.length===0)throw h(`plugin not found: ${t}`);return await T(),{scopes:o}},"run")}),Pt=H({name:"enable",description:"enable a plugin",whenToUse:"reactivating a plugin that was previously disabled so its memory docs resolve again",help:{name:"pkg plugin enable",summary:"enable a plugin in the given scope",params:[{kind:"positional",name:"name",type:"string",required:!0,constraint:"Plugin name to enable."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: scope where the plugin is installed."}],output:[{name:"scope",type:"string",required:!0,constraint:"Scope the change was applied in."},{name:"memory",type:"object",required:!1,constraint:"Present only when the plugin ships documents (its `memory/` folder). {docs: integer (documents checked), linkedSource?: string (the caller-owned directory, present only for a symlinked install)}."}],outputKind:"object",effects:["Validates the installed package before any config change: its `memory/` documents are checked for identity findings and refused if it cannot mount. A refusal leaves the plugin disabled.","Sets plugin enabled=true in config.json."]},run:u(async e=>{const t=e.name,n=kn(t,e.scope);if(n===null)throw h(`plugin not found: ${t}`);const i=x(n.root);if(i===null)throw h(`plugin manifest not found at ${n.root}/.crouter-plugin/plugin.json`);const o=n.scope==="project"?C("project"):R(),s=await j(i,n.root,n.scope,o,`${n.root}/.crouter-plugin/plugin.json`,void 0,F(n.root)?"linked":"package"),a=await fe(e,!0);return await T(t),{scope:a.scope,...s!==void 0?{memory:s}:{}}},"run")}),jt=H({name:"disable",description:"disable without removing",whenToUse:"hiding a plugin from resolution without deleting it, so you can re-enable it later",help:{name:"pkg plugin disable",summary:"disable a plugin (keeps files, hides from resolution)",params:[{kind:"positional",name:"name",type:"string",required:!0,constraint:"Plugin name to disable."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: scope where the plugin is installed."}],output:[{name:"scope",type:"string",required:!0,constraint:"Scope the change was applied in."}],outputKind:"object",effects:["Sets plugin enabled=false in config.json."]},run:u(async e=>{const t=await fe(e,!1);return await T(),{scope:t.scope}},"run")}),xt=H({name:"update",description:"update installed plugin content",whenToUse:"refreshing one named plugin or all installed plugins: archive plugins replace their complete package, while source-installed plugins retain their source update behavior",help:{name:"pkg plugin update",summary:"refresh one or all installed plugins",params:[{kind:"flag",name:"name",type:"string",required:!1,constraint:"Plugin name to update. Omit to update all installed plugins."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Narrows resolution."}],output:[{name:"updated",type:"object[]",required:!0,constraint:"One entry per plugin processed: {name, transport?, updated, sha?, version?, docs?, kinds?, pageComponents?, commands?, hooks?, memory?, warnings?, error?}. memory is present for a plugin shipping documents and reports the same memory check install performs: {docs, linkedSource?}. Archive plugins report transport=http and replace their complete directory after an authenticated refetch; version is the archive content identifier, docs is the written memory-document count, kinds is the declared kind-registry entry count, and pageComponents is the declared page-component registration count. sha is present for git updates. A bulk update reports and skips a failed archive plugin. commands is present for validated command plugins: {mounts: string[] (accepted command paths), issues: object[]}. hooks is present for plugins declaring hooks: {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], lifecycle?: {event, phase, op, description, effects}[], issues: object[], trust: string}; it is static and never executes hooks. warnings lists advisory PATH requirements that remain absent after update."}],outputKind:"object",effects:["Archive plugins unconditionally re-fetch, validate, and replace their complete package; a named failure exits nonzero and preserves the prior package. Bulk update reports a failed archive plugin and continues. Source-installed plugins retain their source update, version, and last_updated behavior.","A capability provider bundle (installed by the runtime from the directory) is skipped with a warning: the runtime refreshes it on grant sync.","Each candidate passes the same pre-activation memory check install performs."]},run:u(async e=>{const t=e.name,n=e.scope,i=n===void 0?void 0:te(n),o=new Map;if(t!==void 0){const r=i===void 0||i==="all"||i==="builtin"?q(t):q(t,i);if(r===null)throw h(`plugin not found: ${t}${i===void 0||i==="all"||i==="builtin"?"":` (scope: ${i})`}`);const c=await Ee(r,o,{strict:!0});return await T(t),{updated:[c]}}const s=ne().filter(r=>r.scope!=="builtin"&&(i===void 0||i==="all"||r.scope===i)),a=[];for(const r of s)try{a.push(await Ee(r,o,{strict:!1}))}catch(c){a.push({name:r.name,...r.manifest.transport!==void 0?{transport:r.manifest.transport.kind}:{},updated:!1,error:c instanceof Error?c.message:String(c)})}return await T(),{updated:a}},"run")});export{j as assertPluginCandidateValid,Nn as installFromMarketplace,Hn as installPluginArchive,je as isMarketplaceRelativeSource,jt as pluginDisable,Pt as pluginEnable,bt as pluginInstall,$t as pluginRemove,xt as pluginUpdate,kt as revalidateBundlePlugin,wt as validatePluginArchive};
17
+ Accepted forms: marketplace/plugin, git URL (https://, ssh://, git@, file://, or *.git), or local path starting ./, ../, or /. Bare plugin names are not install refs.`)}u(_n,"classifyInstallRef");function je(e){return e.startsWith("./")||e.startsWith("../")||!Me(e)&&!me(e)}u(je,"isMarketplaceRelativeSource");async function xe(e,t,n){const i=t.get(e);if(i!==void 0)return i;const o=De(e);if(o===null){if(n.strict)throw h(`marketplace not found: ${e}`);return null}const s=se(o.root);if(s===null)throw h(`marketplace manifest not found: ${o.root}/.crouter-marketplace/marketplace.json`);if(!K(o.root)){const l={marketplace:o,manifest:s,pull:{updated:!1,sha:S(o.root)??""}};return t.set(e,l),l}const a=S(o.root),r=g(ee(o.root),`.${e}.${process.pid}.${Date.now()}.candidate`),c=`${r}.previous`;try{V(o.url,r,{ref:o.ref,depth:1});const l=se(r);if(l===null)throw h(`marketplace manifest not found in candidate checkout: ${r}/.crouter-marketplace/marketplace.json`);const p=ne().filter(d=>d.sourceMarketplace===e&&F(d.root));for(const d of p){const f=l.plugins.find(Te=>Te.name===d.name);if(f===void 0||!je(f.source))throw m(`marketplace candidate removes relative-source plugin "${d.name}" while it is installed`);const y=ze(d.root);if(y===null)throw m(`installed marketplace plugin "${d.name}" has no readable symlink target`);const k=L(o.root,N(ee(d.root),y)),$=N(r,f.source);if($!==N(r,k))throw m(`marketplace candidate moves installed relative-source plugin "${d.name}" from ${k} to ${f.source}`);if(!b($))throw h(`marketplace candidate source path does not exist for plugin "${d.name}": ${$}`);const X=x($);if(X===null)throw h(`plugin manifest not found in marketplace candidate: ${$}/.crouter-plugin/plugin.json`);const Se=d.scope==="project"?C("project"):R();await j(X,$,d.scope,Se,`${$}/.crouter-plugin/plugin.json`)}P(o.root,c);try{P(r,o.root)}catch(d){throw P(c,o.root),d}E(c);for(const d of p){const f=l.plugins.find(y=>y.name===d.name);Re(d.scope,d.name,f.version??d.manifest.version)}U(o.scope,d=>{d.marketplaces[e]===void 0&&(d.marketplaces[e]={}),d.marketplaces[e].last_updated=G()});const v={marketplace:o,manifest:l,pull:{updated:a!==S(o.root),sha:S(o.root)??""}};return t.set(e,v),v}finally{E(r),b(c)&&!b(o.root)&&P(c,o.root)}}u(xe,"refreshMarketplace");function Re(e,t,n){B(e,i=>{const o=i.plugins[t];o!==void 0?n!==void 0&&(o.version=n):i.plugins[t]={enabled:!0,version:n}}),U(e,i=>{i.plugins[t]===void 0&&(i.plugins[t]={}),i.plugins[t].last_updated=G()})}u(Re,"updatePluginVersion");async function Dn(e,t,n){const i=n==="project"?O():R();z(n,i);const o=A(n),s=Ye(e),a=g(i,"tmp",`${s}.${process.pid}.${Date.now()}`);M(g(i,"tmp"));try{V(e,a,{ref:t,depth:1});const r=x(a);if(r===null)throw m(`cloned repo does not contain a valid .crouter-plugin/plugin.json: ${e}`);const c=await j(r,a,n,i,`${e}/.crouter-plugin/plugin.json`),l=g(o,r.name);if(b(l))throw m(`plugin "${r.name}" is already installed at ${l}`);return M(o),P(a,l),B(n,p=>{p.plugins[r.name]={enabled:!0,version:r.version}}),{name:r.name,scope:n,path:l,...c!==void 0?{memory:c}:{}}}finally{E(a)}}u(Dn,"installFromGit");async function In(e,t){const n=N(e),i=x(n);if(i===null)throw h(`plugin manifest not found at ${n}/.crouter-plugin/plugin.json`);const o=t==="project"?O():R(),s=await j(i,n,t,o,`${n}/.crouter-plugin/plugin.json`,void 0,"linked");z(t,o);const a=g(A(t),i.name);if(b(a))throw m(`plugin "${i.name}" is already installed at ${a}`);return ae(n,a),B(t,r=>{r.plugins[i.name]={enabled:!0,version:i.version}}),{name:i.name,scope:t,path:a,...s!==void 0?{memory:s}:{}}}u(In,"installFromLocal");async function Nn(e,t){const n=await xe(e.marketplace,new Map,{strict:!0});if(n===null)throw h(`marketplace not found: ${e.marketplace}`);const i=n.manifest.plugins.find(l=>l.name===e.plugin);if(i===void 0)throw h(`plugin "${e.plugin}" not found in marketplace "${e.marketplace}"`);const o=C(t);z(t,o);const s=g(A(t),e.plugin);if(b(s))throw m(`plugin "${e.plugin}" is already installed at ${s}`);let a,r;if(je(i.source)){const l=g(n.marketplace.root,i.source);if(!b(l))throw h(`plugin source path does not exist: ${l}`);const p=x(l);if(p===null)throw h(`plugin manifest not found at ${l}/.crouter-plugin/plugin.json`);r=await j(p,l,t,o,`${l}/.crouter-plugin/plugin.json`,void 0,"linked"),ae(l,s),a=p}else{const l=g(o,"tmp",`${e.plugin}.${process.pid}.${Date.now()}`);M(g(o,"tmp"));try{V(i.source,l,{depth:1});const p=x(l);if(p===null)throw h(`plugin manifest not found at ${l}/.crouter-plugin/plugin.json`);r=await j(p,l,t,o,`${l}/.crouter-plugin/plugin.json`),M(A(t)),P(l,s),a=p}finally{E(l)}}const c=i.version??a.version;return B(t,l=>{const p={enabled:!0,source_marketplace:e.marketplace};c!==void 0&&(p.version=c),l.plugins[e.plugin]=p}),{name:e.plugin,scope:t,path:s,...r!==void 0?{memory:r}:{}}}u(Nn,"installFromMarketplace");async function Un(e){if(F(e.root)){const r=x(e.root);if(r===null)throw h(`plugin manifest not found at ${e.root}/.crouter-plugin/plugin.json`);const c=e.scope==="project"?C("project"):R(),l=await j(r,e.root,e.scope,c,`${e.root}/.crouter-plugin/plugin.json`,void 0,"linked");return{updated:!1,sha:S(e.root)??"",manifest:r,...l!==void 0?{memory:l}:{}}}if(!K(e.root)){const r=x(e.root);if(r===null)throw h(`plugin manifest not found at ${e.root}/.crouter-plugin/plugin.json`);const c=e.scope==="project"?C("project"):R(),l=await j(r,e.root,e.scope,c,`${e.root}/.crouter-plugin/plugin.json`);return{updated:!1,sha:"",manifest:r,...l!==void 0?{memory:l}:{}}}const t=re(["config","--get","remote.origin.url"],e.root).stdout.trim();if(t==="")throw m(`plugin "${e.name}" has no origin remote to stage an update from`);const n=re(["symbolic-ref","--short","HEAD"],e.root).stdout.trim();if(n==="")throw m(`plugin "${e.name}" is detached at its installed revision and has no tracked branch to update`);const i=e.scope==="project"?C("project"):R(),o=g(i,"tmp",`${e.name}.${process.pid}.${Date.now()}.candidate`),s=`${o}.previous`,a=S(e.root);M(g(i,"tmp"));try{V(t,o,{ref:n,depth:1});const r=x(o);if(r===null)throw h(`plugin manifest not found in update candidate: ${o}/.crouter-plugin/plugin.json`);const c=await j(r,o,e.scope,i,`${o}/.crouter-plugin/plugin.json`);P(e.root,s);try{P(o,e.root)}catch(p){throw P(s,e.root),p}E(s);const l=S(e.root)??"";return{updated:a!==l,sha:l,manifest:r,...c!==void 0?{memory:c}:{}}}finally{E(o),b(s)&&!b(e.root)&&P(s,e.root)}}u(Un,"updateGitPluginCandidate");async function Ee(e,t,n){if(e.manifest.provider!==void 0)return{name:e.name,transport:"http",updated:!1,warnings:[`"${e.name}" is a capability provider bundle; the runtime refreshes it on grant sync`]};const i=ve(e);if(!i&&e.manifest.bundle!==void 0){const p=await $e(e.name,e.manifest.bundle,e.scope,{enable:e.enabled});return{name:e.name,transport:"http",updated:!0,version:p.version,docs:p.docs,kinds:p.kinds,pageComponents:p.pageComponents,commands:p.commands,...p.hooks!==void 0?{hooks:p.hooks}:{},...p.memory!==void 0?{memory:p.memory}:{}}}e.manifest.transport?.kind==="http"&&!i&&ye(e.name);let o,s=!1;if(e.sourceMarketplace!==void 0){const p=await xe(e.sourceMarketplace,t,n);p!==null&&(o=p.manifest.plugins.find(d=>d.name===e.name)?.version,s=p.pull.updated)}const a=await Un(e);Re(e.scope,e.name,o??a.manifest.version);const r=await Y(e.name,e.scope),c=await Q(e.name,e.scope),l=[...ge(a.manifest),...await Z(a.manifest)];return{name:e.name,...e.manifest.transport!==void 0?{transport:e.manifest.transport.kind==="machine"?"http":e.manifest.transport.kind}:{},updated:s||a.updated,sha:a.sha,...r!==void 0?{commands:r}:{},...c!==void 0?{hooks:c}:{},...a.memory!==void 0?{memory:a.memory}:{},...l.length>0?{warnings:l}:{}}}u(Ee,"updateOnePlugin");async function T(e){const n=(await Le().documents.reconcilePackages().catch(i=>He(i))).conflicts.find(i=>i.plugin===e);if(n)throw new _e("conflict",`plugin "${e}" is installed but its documents are not on the canvas: ${n.message}`,yn.GENERAL,{plugin:e,next:"Uninstall the other package providing that plugin name, then run `crtr pkg plugin update --name "+e+"`."})}u(T,"reconcilePluginDocuments");const bt=H({name:"install",description:"install from a ref or HTTP endpoint",whenToUse:"installing one plugin from a marketplace ref, git URL, local path, or an HTTP directory-archive endpoint",help:{name:"pkg plugin install",summary:"install a plugin from a ref or HTTP endpoint",params:[{kind:"positional",name:"install-ref",type:"string",required:!1,constraint:"Plugin install ref. Provide exactly one of INSTALL-REF or --endpoint. Accepted ref forms: marketplace/plugin; git URL starting https://, ssh://, git@, file://, or ending .git; local path starting ./, ../, or /."},{kind:"flag",name:"endpoint",type:"string",required:!1,constraint:"HTTP plugin directory-archive endpoint. Provide exactly one of --endpoint or INSTALL-REF. Must be an absolute https: URL; http: is permitted only for localhost, any *.localhost host, 127.0.0.1, [::1], ::1, or host.docker.internal. Credentials and fragments are rejected."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Required with --endpoint and rejected without it. Lowercase kebab-case plugin handle."},{kind:"flag",name:"auth-env",type:"string",required:!1,constraint:"Optional environment variable holding the HTTP bearer token. Accepted only with --endpoint; its value is never stored."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: project if available, else user."},{kind:"flag",name:"ref",type:"string",required:!1,constraint:"Git ref (branch/tag) for git URL installs. Rejected with --endpoint; ignored for marketplace and local path refs."}],output:[{name:"name",type:"string",required:!0,constraint:"Plugin name as installed."},{name:"scope",type:"string",required:!0,constraint:"Scope the plugin was installed into."},{name:"path",type:"string",required:!0,constraint:"Absolute path to the installed plugin directory."},{name:"transport",type:"string",required:!1,constraint:"exec or http when the plugin declares commands. Always http for an --endpoint install."},{name:"version",type:"string",required:!1,constraint:"First 12 hex characters of the fetched archive SHA-256 for an --endpoint install."},{name:"docs",type:"integer",required:!1,constraint:"Memory document count written from an --endpoint archive."},{name:"kinds",type:"integer",required:!1,constraint:"Kind-registry entry count declared by an --endpoint archive\u2019s bundle.json."},{name:"pageComponents",type:"integer",required:!1,constraint:"Page-component registration count declared by an --endpoint archive\u2019s bundle.json."},{name:"commands",type:"object",required:!1,constraint:"Present only when the plugin declares a command manifest. {mounts: string[] (accepted command paths now live for the next invocation), issues: object[] (typed validation issues that rejected a contribution \u2014 {code, path?, message, received, expected, next})}. Validated statically; commands are never executed."},{name:"hooks",type:"object",required:!1,constraint:"Present only when the plugin declares hooks. {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], lifecycle?: {event, phase, op, description, effects}[], issues: object[], trust: string}. Command targets, lifecycle events, and replacement collisions are validated statically; hook executables are never run."},{name:"warnings",type:"string[]",required:!1,constraint:"Advisory missing PATH executable requirements declared by the installed plugin. The install still succeeds and leaves the plugin enabled."},{name:"memory",type:"object",required:!1,constraint:"Present only when the plugin ships documents (its `memory/` folder). {docs: integer (documents checked), linkedSource?: string (the caller-owned directory, present only when the install keeps that directory as a live link)}."}],outputKind:"object",effects:["A ref install clones, links, or copies the plugin into the scope plugins directory and registers it with enabled=true. Marketplace installs refresh the source marketplace before resolving the plugin entry.","An --endpoint install fetches and validates one authenticated uncompressed tar archive before writing. It replaces the complete plugin directory with synthesized provenance and invocation metadata, commands.json, memory docs, and, when declared, hooks.json plus the hooks/ tree; it also copies any kind-registry entries the bundle.json declares (they join the launch registry below this scope\u2019s own config.json kinds), and any page components it declares (they join the active page-component catalog alongside the user config entries); a fetch or validation failure preserves the prior package.","If the plugin declares a command manifest, its commands go live on the next crtr invocation. Exec commands run trusted local code only when explicitly invoked; HTTP commands call their declared endpoint only when explicitly invoked.","Plugin command hooks go live on the next crtr invocation. They run implicitly with caller authority, may receive normalized inputs and after results, may block or replace declared targets, and may transmit received data. Lifecycle hooks run unprompted at every declared event before node launch and may transmit node identity and resolved paths. Hook executables are never run during install or update validation.","A plugin manifest `requires` declaration is checked against the PATH a node bash receives after install. Missing executables emit a warning with the declared install hint but never fail the install, disable the plugin, or remove its bare-binary contributions.","Before activation the candidate\u2019s `memory/` documents are checked for identity findings and refused if it cannot mount; a local path or marketplace-relative source stays a live link, named as `memory.linkedSource`. A refused package installs nothing."]},run:u(async e=>{const t=e.installRef,n=e.endpoint,i=e.name,o=e.authEnv,s=e.scope,a=e.ref,r=wn(s);if(t===void 0==(n===void 0))throw w("provide exactly one of INSTALL-REF or --endpoint");if(n!==void 0){if(i===void 0)throw w("--name is required with --endpoint");if(a!==void 0)throw w("--ref is only valid with a git INSTALL-REF, not --endpoint");const y=await Bn(i,n,o,r);return await T(i),y}if(i!==void 0)throw w("--name is only valid with --endpoint");if(o!==void 0)throw w("--auth-env is only valid with --endpoint");const c=_n(t),l=c.kind==="marketplace"?await Nn(c.value,r):c.kind==="local"?await In(c.value,r):await Dn(c.value,a,r),p=q(l.name,l.scope),v=await Y(l.name,l.scope),d=await Q(l.name,l.scope),f=p===null?[]:[...ge(p.manifest),...await Z(p.manifest)];return await T(l.name),{...l,...p?.manifest.transport!==void 0?{transport:p.manifest.transport.kind}:{},...v!==void 0?{commands:v}:{},...d!==void 0?{hooks:d}:{},...f.length>0?{warnings:f}:{}}},"run")}),$t=H({name:"remove",description:"remove plugin and directory",whenToUse:"uninstalling a plugin: deletes its directory and removes it from config",help:{name:"pkg plugin remove",summary:"remove a plugin and its directory from the given scope",params:[{kind:"positional",name:"name",type:"string",required:!0,constraint:"Plugin name to remove."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: searches every scope."}],output:[{name:"scopes",type:"string[]",required:!0,constraint:"Scopes the plugin was removed from."}],outputKind:"object",effects:["Deletes the plugin directory. Removes the plugin entry from config.json."]},run:u(async e=>{const t=e.name,n=e.scope;if(t==="crtr")throw w('cannot remove builtin plugin "crtr" \u2014 it ships with the binary');let i;if(n!==void 0){const s=te(n);if(s==="builtin")throw w("cannot remove plugins from builtin scope");i=s==="all"?["project","user"].filter(a=>a!=="project"||ie()!==null):[s]}else i=["project","user"].filter(s=>s!=="project"||ie()!==null);const o=[];for(const s of i){const a=A(s);if(a===null)continue;const r=g(a,t);b(r)&&(E(r),B(s,c=>{delete c.plugins[t]}),U(s,c=>{delete c.plugins[t]}),o.push(s))}if(o.length===0)throw h(`plugin not found: ${t}`);return await T(),{scopes:o}},"run")}),Pt=H({name:"enable",description:"enable a plugin",whenToUse:"reactivating a plugin that was previously disabled so its memory docs resolve again",help:{name:"pkg plugin enable",summary:"enable a plugin in the given scope",params:[{kind:"positional",name:"name",type:"string",required:!0,constraint:"Plugin name to enable."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: scope where the plugin is installed."}],output:[{name:"scope",type:"string",required:!0,constraint:"Scope the change was applied in."},{name:"memory",type:"object",required:!1,constraint:"Present only when the plugin ships documents (its `memory/` folder). {docs: integer (documents checked), linkedSource?: string (the caller-owned directory, present only for a symlinked install)}."}],outputKind:"object",effects:["Validates the installed package before any config change: its `memory/` documents are checked for identity findings and refused if it cannot mount. A refusal leaves the plugin disabled.","Sets plugin enabled=true in config.json."]},run:u(async e=>{const t=e.name,n=kn(t,e.scope);if(n===null)throw h(`plugin not found: ${t}`);const i=x(n.root);if(i===null)throw h(`plugin manifest not found at ${n.root}/.crouter-plugin/plugin.json`);const o=n.scope==="project"?C("project"):R(),s=await j(i,n.root,n.scope,o,`${n.root}/.crouter-plugin/plugin.json`,void 0,F(n.root)?"linked":"package"),a=await fe(e,!0);return await T(t),{scope:a.scope,...s!==void 0?{memory:s}:{}}},"run")}),jt=H({name:"disable",description:"disable without removing",whenToUse:"hiding a plugin from resolution without deleting it, so you can re-enable it later",help:{name:"pkg plugin disable",summary:"disable a plugin (keeps files, hides from resolution)",params:[{kind:"positional",name:"name",type:"string",required:!0,constraint:"Plugin name to disable."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Default: scope where the plugin is installed."}],output:[{name:"scope",type:"string",required:!0,constraint:"Scope the change was applied in."}],outputKind:"object",effects:["Sets plugin enabled=false in config.json."]},run:u(async e=>{const t=await fe(e,!1);return await T(),{scope:t.scope}},"run")}),xt=H({name:"update",description:"update installed plugin content",whenToUse:"refreshing one named plugin or all installed plugins: archive plugins replace their complete package, while source-installed plugins retain their source update behavior",help:{name:"pkg plugin update",summary:"refresh one or all installed plugins",params:[{kind:"flag",name:"name",type:"string",required:!1,constraint:"Plugin name to update. Omit to update all installed plugins."},{kind:"flag",name:"scope",type:"enum",choices:["user","project"],required:!1,constraint:"Narrows resolution."}],output:[{name:"updated",type:"object[]",required:!0,constraint:"One entry per plugin processed: {name, transport?, updated, sha?, version?, docs?, kinds?, pageComponents?, commands?, hooks?, memory?, warnings?, error?}. memory is present for a plugin shipping documents and reports the same memory check install performs: {docs, linkedSource?}. Archive plugins report transport=http and replace their complete directory after an authenticated refetch; version is the archive content identifier, docs is the written memory-document count, kinds is the declared kind-registry entry count, and pageComponents is the declared page-component registration count. sha is present for git updates. A bulk update reports and skips a failed archive plugin. commands is present for validated command plugins: {mounts: string[] (accepted command paths), issues: object[]}. hooks is present for plugins declaring hooks: {manifestPath?, executablePath?, declarations: {target, phase, op, description, effects}[], lifecycle?: {event, phase, op, description, effects}[], issues: object[], trust: string}; it is static and never executes hooks. warnings lists advisory PATH requirements that remain absent after update."}],outputKind:"object",effects:["Archive plugins unconditionally re-fetch, validate, and replace their complete package; a named failure exits nonzero and preserves the prior package. Bulk update reports a failed archive plugin and continues. Source-installed plugins retain their source update, version, and last_updated behavior.","A capability provider bundle (installed by the runtime from the directory) is skipped with a warning: the runtime refreshes it on grant sync.","Each candidate passes the same pre-activation memory check install performs."]},run:u(async e=>{const t=e.name,n=e.scope,i=n===void 0?void 0:te(n),o=new Map;if(t!==void 0){const r=i===void 0||i==="all"||i==="builtin"?q(t):q(t,i);if(r===null)throw h(`plugin not found: ${t}${i===void 0||i==="all"||i==="builtin"?"":` (scope: ${i})`}`);const c=await Ee(r,o,{strict:!0});return await T(t),{updated:[c]}}const s=ne().filter(r=>r.scope!=="builtin"&&(i===void 0||i==="all"||r.scope===i)),a=[];for(const r of s)try{a.push(await Ee(r,o,{strict:!1}))}catch(c){a.push({name:r.name,...r.manifest.transport!==void 0?{transport:r.manifest.transport.kind}:{},updated:!1,error:c instanceof Error?c.message:String(c)})}return await T(),{updated:a}},"run")});export{j as assertPluginCandidateValid,Nn as installFromMarketplace,Hn as installPluginArchive,je as isMarketplaceRelativeSource,jt as pluginDisable,Pt as pluginEnable,bt as pluginInstall,$t as pluginRemove,xt as pluginUpdate,kt as revalidateBundlePlugin,wt as validatePluginArchive};
@@ -17,7 +17,8 @@ export declare function qualifiedTopLevelCommandName(contributor: Pick<CommandCo
17
17
  * run function to be used by the tree composer. */
18
18
  export type LeafAdapter = (leaf: DeclLeafBase, commandPath: readonly string[]) => {
19
19
  outputKind: 'object' | 'jsonl';
20
- run: LeafDef['run'];
20
+ run: LeafDef['run']; /** The leaf calls a device machine: it takes `--machine <id>` (see `LeafDef.machineSelector`). */
21
+ machineSelector?: true;
21
22
  };
22
23
  /** One accepted external contribution after collision resolution: a validated
23
24
  * top-level branch node, contributor identity, and the adapter callback that
@@ -1,5 +1,6 @@
1
1
  import { type ValidatedCommandManifest } from '../../api/command-manifest/manifest.js';
2
2
  import type { KindConfig, MemoryExtensionDeclarations, PageComponentRegistration, PluginBundle, PluginManifest, Scope } from '../../types.js';
3
+ import { type BundleTransport } from './endpoint.js';
3
4
  /** Who may call a capability provider's tool group (contract 3). */
4
5
  export type ProviderGroupWho = 'own' | 'user' | 'allowlist' | 'any';
5
6
  /** One tool group a capability provider declares in `bundle.json`. */
@@ -34,11 +35,7 @@ export interface ValidatedBundle {
34
35
  version?: number;
35
36
  /** The transport `bundle.json` declares, when it declares one (a bundle the
36
37
  * directory emits for a capability provider always does). */
37
- transport?: {
38
- kind: 'http';
39
- endpoint: string;
40
- authEnv?: string;
41
- };
38
+ transport?: BundleTransport;
42
39
  /** The capability-provider block: present only on a provider bundle. */
43
40
  provider?: ProviderBlock;
44
41
  /** Kind-registry contributions declared in `bundle.json` (optional). The
@@ -82,11 +79,7 @@ export declare function validatePluginBundle(archive: Uint8Array, options: {
82
79
  extensibleCoreBranches?: ReadonlySet<string>;
83
80
  }): Promise<PluginBundleValidation>;
84
81
  /** An archive install's invocation transport. */
85
- export type BundleInvocationTransport = {
86
- kind: 'http';
87
- endpoint: string;
88
- authEnv?: string;
89
- };
82
+ export type BundleInvocationTransport = BundleTransport;
90
83
  /** The hash recorded as a bundle package's `version`: the exact archive bytes
91
84
  * unpacked at its root. Same bytes, same version — which is what lets a
92
85
  * re-sent archive be recognized as the generation already installed. */
@@ -1 +1 @@
1
- var A=Object.defineProperty;var p=(e,r)=>A(e,"name",{value:r,configurable:!0});import{Parser as J}from"tar";import{createHash as I}from"node:crypto";import{chmodSync as W,renameSync as k}from"node:fs";import{dirname as $,join as w,resolve as V,sep as z}from"node:path";import{validateCommandManifest as B}from"../../api/command-manifest/manifest.js";import{ensureScopeInitialized as F,invalidPluginKindsReasons as M,readConfig as R,updateConfig as D,updateState as C}from"../config.js";import{general as x}from"../errors.js";import{atomicWriteJson as T,atomicWriteText as q,ensureDir as j,nowIso as E,pathExists as _,removePath as S,syncDirectory as H,syncTree as U}from"../fs-utils.js";import{invalidPageComponentsReasons as K,normalizePageComponents as G}from"../human/page-catalog.js";import{validateMemoryExtensionDeclarations as X}from"../plugin-extensions.js";import{validateHttpPluginTransport as Z}from"./endpoint.js";const O=/^[A-Za-z0-9_-]{1,64}$/,L=new Set(["id","major","groups"]),Y=new Set(["group","consent_line","rate","who","allowlist"]),Q=new Set(["own","user","allowlist","any"]),ee=new Set(["bundleVersion","kinds","page_components","memory_extensions","version","transport","provider"]);function ne(e,r,n,o,s,a){return{code:e,message:r,received:n,expected:o,next:s,...a!==void 0?{path:a}:{}}}p(ne,"issue");function d(e,r,n,o,s){return ne("plugin_bundle_invalid",e,r,n,o,s)}p(d,"bundleInvalid");function re(e){if(e[0]===31&&e[1]===139)return d("bundle archive must be uncompressed tar data","gzip-compressed archive","an uncompressed tar archive","Serve the archive without compression.");if(e.length===0||e.length%512!==0)return d("bundle archive must be a non-empty whole number of 512-byte tar blocks",`${e.length} bytes`,"a non-empty tar stream with a 512-byte block length","Serve one complete uncompressed tar archive.");if(e.length<1024||!e.subarray(-1024).every(r=>r===0))return d("bundle archive is missing the required two zero-block terminator","archive tail is not 1024 zero bytes","a complete tar stream ending in two 512-byte zero blocks","Serve the complete uncompressed tar archive.")}p(re,"archiveEnvelopeIssue");function oe(e,r){if(e.length===0||e.includes("\0")||e.includes("\\")||e.startsWith("/")||/^[A-Za-z]:($|\/)/.test(e))return d("bundle member path must be a safe relative POSIX path",e,"a relative path without absolute, drive-letter, backslash, or NUL forms","Use a safe relative POSIX member path.",e);if(r==="file"&&e.endsWith("/"))return d("only directory members may have a trailing slash",e,"a regular-file path without a trailing slash","Emit this member as a regular file path.",e);if((r==="directory"&&e.endsWith("/")?e.slice(0,-1):e).split("/").some(o=>o===""||o==="."||o===".."))return d("bundle member path contains an unsafe segment",e,"non-empty segments other than . or ..","Use a safe relative POSIX member path.",e)}p(oe,"validateMemberName");function se(e,r){if(r==="file")return e==="bundle.json"||e==="commands.json"||e==="hooks.json"||e.startsWith("memory/")&&e.endsWith(".md")||e.startsWith("hooks/")?void 0:d("bundle contains an unsupported file member",e,"bundle.json, commands.json, hooks.json, memory/**/*.md, or hooks/**","Remove unsupported members from the archive.",e);if(!(e==="memory"||e.startsWith("memory/")||e==="hooks"||e.startsWith("hooks/")))return d("bundle contains an unsupported directory member",e,"memory or hooks, or a directory below either subtree","Remove unsupported directory members from the archive.",e)}p(se,"validateMemberShape");function te(e){return e.type==="directory"&&e.path.endsWith("/")?e.path.slice(0,-1):e.path}p(te,"materializedPath");function ie(e){const r=new Map;for(const n of e){const o=te(n),s=r.get(o);if(s!==void 0)return d(s===n.type?"bundle contains a duplicate member":"bundle claims one path as both a file and a directory",`${o} (${s} and ${n.type})`,"each materialized path exactly once, with one type","Emit each archive path once, as a single member type.",o);r.set(o,n.type)}for(const[n,o]of r)if(o==="file"){for(const s of r.keys())if(s.startsWith(`${n}/`))return d("bundle nests a member beneath a regular-file path",`${s} beneath file ${n}`,"members nested only beneath directory paths","Emit the parent path as a directory or remove the nested member.",s)}}p(ie,"validateMemberTree");async function ae(e){const r=[],n=new Set;let o;return await new Promise((s,a)=>{const l=new J({strict:!0,maxMetaEntrySize:Number.MAX_SAFE_INTEGER,gzip:!1,brotli:!1,zstd:!1,onReadEntry:p(t=>{const m=t.path,f=t.type==="File"||t.type==="OldFile"?"file":t.type==="Directory"?"directory":void 0;if(f===void 0||t.unsupported||t.ignore||t.linkpath!==void 0){o??=d("bundle member must be a regular file or directory without a link target",`${m} (${t.type})`,"a regular file or directory member without link metadata","Remove links and unsupported member types from the archive.",m),t.resume();return}o??=oe(m,f),o??=se(m,f),n.has(m)&&(o??=d("bundle contains a duplicate member",m,"each member path exactly once","Emit each archive member only once.",m)),n.add(m);const i=[];t.on("data",b=>i.push(b)),t.on("end",()=>{o===void 0&&r.push({path:m,type:f,bytes:new Uint8Array(Buffer.concat(i))})})},"onReadEntry")});l.on("ignoredEntry",t=>{const m=t.path;o??=d("bundle contains an ignored or unsupported archive member",`${m} (${t.type})`,"only supported regular file or directory members","Remove ignored or unsupported members from the archive.",m),t.resume()}),l.once("error",a),l.once("end",s);try{l.end(Buffer.from(e))}catch(t){a(t)}}).catch(s=>{o??=d("bundle archive could not be parsed",s instanceof Error?s.message:String(s),"a valid complete uncompressed tar archive","Fix the archive format and serve it again.")}),o===void 0?{members:r}:{issue:o}}p(ae,"parseArchive");function N(e){return typeof e=="number"&&Number.isInteger(e)&&e>=1}p(N,"isPositiveInt");function de(e){const r=p((t,m,f,i)=>({issue:d(t,m,f,"Fix the provider block in bundle.json.",`bundle.json.${i}`)}),"bad");if(typeof e!="object"||e===null||Array.isArray(e))return r("bundle.json provider must be an object",JSON.stringify(e),"{ id, major, groups }","provider");const n=e,o=Object.keys(n).filter(t=>!L.has(t));if(o.length>0)return r("bundle.json provider has unknown keys",o.join(", "),"only: id, major, groups","provider");if(typeof n.id!="string"||!O.test(n.id))return r("provider id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(n.id),'a provider id of 1-64 letters, digits, "_" or "-"',"provider.id");if(!N(n.major))return r("provider major must be an integer of at least 1",JSON.stringify(n.major),"the served major as an integer >= 1","provider.major");const s=n.groups;if(!Array.isArray(s)||s.length===0)return r("provider groups must be a non-empty array",JSON.stringify(s),"at least one { group, consent_line, rate?, who, allowlist }","provider.groups");const a=[],l=new Set;for(let t=0;t<s.length;t++){const m=`provider.groups[${t}]`,f=s[t];if(typeof f!="object"||f===null||Array.isArray(f))return r("provider group must be an object",JSON.stringify(f),"{ group, consent_line, rate?, who, allowlist }",m);const i=f,b=Object.keys(i).filter(v=>!Y.has(v));if(b.length>0)return r("provider group has unknown keys",b.join(", "),"only: group, consent_line, rate, who, allowlist",m);if(typeof i.group!="string"||!O.test(i.group))return r("provider group id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(i.group),'a group id of 1-64 letters, digits, "_" or "-"',`${m}.group`);if(l.has(i.group))return r("provider group ids must be unique",i.group,"each group declared once",`${m}.group`);if(l.add(i.group),typeof i.consent_line!="string"||i.consent_line.trim().length===0)return r("provider group consent_line must be a non-empty string",JSON.stringify(i.consent_line),"the consent line shown for this group",`${m}.consent_line`);const h=i.rate;if(h!=null&&(typeof h!="number"||!Number.isFinite(h)||h<0))return r("provider group rate must be a non-negative number or null",JSON.stringify(h),"a finite number >= 0, null, or omitted",`${m}.rate`);if(typeof i.who!="string"||!Q.has(i.who))return r("provider group who must be own|user|allowlist|any",JSON.stringify(i.who),"own | user | allowlist | any",`${m}.who`);const c=i.allowlist;if(!Array.isArray(c)||!c.every(v=>typeof v=="string"&&v.length>0))return r("provider group allowlist must be an array of non-empty strings",JSON.stringify(c),"an array of app principals ([] unless who is allowlist)",`${m}.allowlist`);if(c.length>0&&i.who!=="allowlist")return r("provider group allowlist must be empty unless who is allowlist",`who ${i.who}`,'who: "allowlist", or allowlist: []',`${m}.allowlist`);a.push({group:i.group,consent_line:i.consent_line,...h!==void 0?{rate:h}:{},who:i.who,allowlist:c})}return{provider:{id:n.id,major:n.major,groups:a}}}p(de,"parseProviderBlock");function ue(e){const r=[],n=p((o,s)=>{const a=[...s,o.name];if(o.kind==="leaf")r.push({path:a.join(" "),leaf:o});else for(const l of o.children)n(l,a)},"walk");for(const o of e.roots)n(o,[]);for(const o of e.coreMounts)n(o.node,o.parent);return r}p(ue,"collectLeaves");function le(e,r){const n=e.provider;if(n===void 0)return;if(e.transport===void 0)return d("a provider bundle must declare its transport","(missing)",'transport { kind: "http", endpoint } in bundle.json',"Add the provider endpoint as bundle.json transport.","bundle.json.transport");if(e.transport.authEnv!==void 0)return d("a provider bundle must not declare authEnv",e.transport.authEnv,"no authEnv: the runtime presents its own outbound token","Remove transport.authEnv from bundle.json.","bundle.json.transport.authEnv");const o=new Set(n.groups.map(s=>s.group));for(const{path:s,leaf:a}of ue(r)){const l="group"in a?a.group:void 0;if(l===void 0)return d(`provider leaf "${s}" has no group`,s,"a group on every leaf of a provider bundle",`Add a group to leaf "${s}".`,`commands.json:${s}.group`);if(!o.has(l))return d(`provider leaf "${s}" names undeclared group "${l}"`,l,`one of: ${[...o].join(", ")}`,`Declare group "${l}" in the provider block, or move leaf "${s}" to a declared group.`,`commands.json:${s}.group`);if("rest"in a&&a.rest.streaming===!0)return d(`provider leaf "${s}" streams`,"rest.streaming: true","no streaming leaves in a provider bundle",`Make leaf "${s}" return one response.`,`commands.json:${s}.rest.streaming`)}}p(le,"providerBundleIssue");function me(e){let r;try{r=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issue:d("bundle.json is not valid JSON","invalid JSON",'{"bundleVersion":1}',"Write valid bundle metadata.","bundle.json")}}if(typeof r!="object"||r===null||Array.isArray(r))return{issue:d("bundle.json must be a JSON object",JSON.stringify(r),"an object with bundleVersion set to 1 and optionally kinds",'Set bundle.json to {"bundleVersion":1}.',"bundle.json")};const n=r,o=Object.keys(n).filter(t=>!ee.has(t));if(n.bundleVersion!==1||o.length>0)return{issue:d("bundle.json must contain bundleVersion 1 and at most version, transport, provider, kinds, page_components, and memory_extensions",JSON.stringify(r),"an object with bundleVersion set to 1 and optional version, transport, provider, kinds, page_components, and memory_extensions",'Set bundle.json to {"bundleVersion":1} plus optional contribution blocks.',"bundle.json")};if(n.kinds!==void 0){const t=M(n.kinds);if(t.length>0)return{issue:d("bundle.json kinds block is invalid",t.join("; "),"each entry a full KindConfig (whenToUse required) or a sparse patch of launch fields","Fix the kinds entries in bundle.json.","bundle.json")}}if(n.page_components!==void 0){const t=K(n.page_components);if(t.length>0)return{issue:d("bundle.json page_components block is invalid",t.join("; "),"an array of page component registrations, each a kind string or {kind, description?, useWhen?, doc?, display?}","Fix the page_components entries in bundle.json.","bundle.json")}}let s;if(n.memory_extensions!==void 0)try{s=X(n.memory_extensions)}catch(t){return{issue:d("bundle.json memory_extensions block is invalid",t instanceof Error?t.message:String(t),"a valid memory_extensions declaration object","Fix the memory_extensions declarations in bundle.json.","bundle.json")}}if(n.version!==void 0&&!N(n.version))return{issue:d("bundle.json version must be an integer of at least 1",JSON.stringify(n.version),"the plugin major as an integer >= 1, or omitted","Fix version in bundle.json.","bundle.json.version")};let a;if(n.transport!==void 0){const t=Z(n.transport);if(t.transport===void 0)return{issue:d("bundle.json transport is invalid",t.errors.join("; "),'{ kind: "http", endpoint, authEnv? }',"Fix transport in bundle.json.","bundle.json.transport")};a=t.transport}let l;if(n.provider!==void 0){const t=de(n.provider);if(t.issue!==void 0)return{issue:t.issue};if(l=t.provider,n.version!==void 0&&n.version!==l.major)return{issue:d("bundle.json provider major must equal version",`version ${String(n.version)}, major ${l.major}`,"one major","Set provider.major and version to the same integer.","bundle.json.provider.major")}}return{...n.version!==void 0?{version:n.version}:{},...a!==void 0?{transport:a}:{},...l!==void 0?{provider:l}:{},...n.kinds!==void 0?{kinds:n.kinds}:{},...n.page_components!==void 0?{page_components:G(n.page_components)}:{},...s!==void 0?{memory_extensions:s}:{}}}p(me,"parseBundleMetadata");function pe(e,r,n,o=new Set){let s;try{s=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issues:[d("commands.json is not valid JSON","invalid JSON","a valid command-manifest JSON object","Regenerate commands.json.","commands.json")]}}const a=B(s,{transport:"http",reservedCoreNames:r,extensibleCoreBranches:o,...n!==void 0?{coreCommandPaths:n}:{}});return{...a.manifest!==void 0?{commands:a.manifest}:{},issues:a.issues}}p(pe,"parseCommands");async function ce(e,r){const n=re(e);if(n!==void 0)return{issues:[n]};const o=await ae(e);if(o.issue!==void 0||o.members===void 0)return{issues:[o.issue]};const s=ie(o.members);if(s!==void 0)return{issues:[s]};const a=o.members.find(u=>u.path==="bundle.json"&&u.type==="file"),l=o.members.find(u=>u.path==="commands.json"&&u.type==="file");if(a===void 0||l===void 0)return{issues:[d("bundle must contain exactly one bundle.json and one commands.json regular file",`bundle.json=${a!==void 0}, commands.json=${l!==void 0}`,"one regular bundle.json and one regular commands.json member","Add the required metadata members to the archive.")]};const t=o.members.find(u=>u.path==="hooks.json"&&u.type==="file"),m=o.members.filter(u=>u.type==="file"&&u.path.startsWith("hooks/"));if((t!==void 0||o.members.some(u=>u.path==="hooks"||u.path.startsWith("hooks/")))&&(t===void 0||!m.some(u=>u.path==="hooks/dispatch")))return{issues:[d("bundle hook payload must contain both hooks.json and hooks/dispatch regular files",`hooks.json=${t!==void 0}, hooks/dispatch=${m.some(u=>u.path==="hooks/dispatch")}`,"both hooks.json and hooks/dispatch regular files","Add both required hook members or remove the hook payload.")]};const i=me(a.bytes);if(i.issue!==void 0)return{issues:[i.issue]};const b=pe(l.bytes,r.reservedCoreNames,r.coreCommandPaths,r.extensibleCoreBranches);if(b.commands===void 0)return{issues:b.issues};const h=b.commands.version;if(i.version!==void 0&&h!==void 0&&i.version!==h)return{issues:[d("bundle.json version and commands.json version differ",`bundle.json ${i.version}, commands.json ${h}`,"one plugin major","Emit the same version in bundle.json and commands.json.","bundle.json.version")]};const c=i.version??h;if(i.provider!==void 0&&c===void 0)return{issues:[d("a provider bundle requires a plugin version","no version in bundle.json or commands.json",`version ${i.provider.major}, the provider major`,"Emit the provider major as the plugin version.","bundle.json.version")]};if(i.provider!==void 0&&c!==i.provider.major)return{issues:[d("bundle provider major must equal the plugin version",`version ${c}, major ${i.provider.major}`,"one major","Emit the provider major as the plugin version.","bundle.json.provider.major")]};const v=le(i,b.commands);return v!==void 0?{issues:[v]}:{bundle:{bundleVersion:1,...c!==void 0?{version:c}:{},...i.transport!==void 0?{transport:i.transport}:{},...i.provider!==void 0?{provider:i.provider}:{},...i.kinds!==void 0?{kinds:i.kinds}:{},...i.page_components!==void 0?{page_components:i.page_components}:{},...i.memory_extensions!==void 0?{memory_extensions:i.memory_extensions}:{},commands:b.commands,commandsBytes:l.bytes,directories:o.members.filter(u=>u.type==="directory").map(u=>({path:u.path})),memory:o.members.filter(u=>u.type==="file"&&u.path.startsWith("memory/")).map(u=>({path:u.path,bytes:u.bytes})),...t!==void 0?{hooks:{manifest:t.bytes,files:m.map(u=>({path:u.path,bytes:u.bytes}))}}:{}},issues:[]}}p(ce,"validatePluginBundle");function fe(e){return I("sha256").update(e).digest("hex").slice(0,12)}p(fe,"bundleVersion");function Ne(e,r,n,o){return R(r).plugins[e]?.version!==o?!1:_(w(n,"commands.json"))}p(Ne,"isInstalledGeneration");function he(e,r,n,o){C(e,s=>{s.plugins[r]===void 0&&(s.plugins[r]={}),o.swapped&&(s.plugins[r].last_updated=E()),s.plugins[r].checked_at=E(),n===void 0?delete s.plugins[r].etag:s.plugins[r].etag=n})}p(he,"recordCheckedGeneration");function g(e,r){const n=V(e,r);if(n===e||!n.startsWith(e+z))throw x(`bundle member path escapes the staging directory: ${r}`);return n}p(g,"stagedPath");function be(e,r,n){const o=p((s,a,l=420)=>q(s,a,{mode:l,durable:!0}),"write");T(g(e,".crouter-plugin/plugin.json"),r,{durable:!0});for(const s of n.directories)j(g(e,s.path));o(g(e,"commands.json"),n.commandsBytes);for(const s of n.memory)o(g(e,s.path),s.bytes);if(n.hooks!==void 0){o(g(e,"hooks.json"),n.hooks.manifest);for(const s of n.hooks.files)o(g(e,s.path),s.bytes);W(g(e,"hooks/dispatch"),493)}U(e)}p(be,"writeStagedBundle");async function Pe(e,r,n){const{scope:o,scopeRootPath:s,root:a}=n,l=await ce(r,n.validation);if(l.bundle===void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,l.issues);const t=l.bundle,m=n.accept?.(t);if(m!==void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,[d(m,e,"the bundle this install asked for","Serve the requested bundle.")]);const f=n.version??fe(r),i=t.provider!==void 0&&t.transport!==void 0?t.transport:n.transport,b={name:e,version:f,commands:"commands.json",transport:i,bundle:n.bundle,...t.kinds!==void 0?{kinds:t.kinds}:{},...t.page_components!==void 0?{page_components:t.page_components}:{},...t.memory_extensions!==void 0?{memory_extensions:t.memory_extensions}:{},...t.hooks!==void 0?{hooks:"hooks.json",hookExecutable:"hooks/dispatch"}:{},...t.provider!==void 0?{provider:t.provider}:{}},h=w(s,"tmp"),c=w(h,`${e}.${process.pid}`),v=`${c}.prev`;if(_(v))throw x(`cannot replace plugin "${e}": prior interrupted replacement remains at ${v}`);j(h),j($(a)),S(c);let u;try{j(c),be(c,b,t),u=await n.validateStaged(c,b);let y=!1;_(a)&&(k(a,v),y=!0);try{k(c,a)}catch(P){throw y&&k(v,a),P}H($(a)),y&&S(v)}finally{S(c)}return F(o,s),D(o,y=>{y.plugins[e]={enabled:n.enable?!0:y.plugins[e]?.enabled??!0,version:f}}),he(o,e,n.etag,{swapped:!0}),{manifest:b,validated:t,version:f,root:a,staged:u}}p(Pe,"installBundleArchive");export{fe as bundleVersion,Pe as installBundleArchive,Ne as isInstalledGeneration,he as recordCheckedGeneration,ce as validatePluginBundle,be as writeStagedBundle};
1
+ var P=Object.defineProperty;var p=(e,r)=>P(e,"name",{value:r,configurable:!0});import{Parser as J}from"tar";import{createHash as I}from"node:crypto";import{chmodSync as W,renameSync as k}from"node:fs";import{dirname as $,join as w,resolve as V,sep as z}from"node:path";import{validateCommandManifest as B}from"../../api/command-manifest/manifest.js";import{ensureScopeInitialized as F,invalidPluginKindsReasons as M,readConfig as R,updateConfig as D,updateState as C}from"../config.js";import{general as x}from"../errors.js";import{atomicWriteJson as T,atomicWriteText as q,ensureDir as j,nowIso as E,pathExists as _,removePath as S,syncDirectory as U,syncTree as H}from"../fs-utils.js";import{invalidPageComponentsReasons as K,normalizePageComponents as G}from"../human/page-catalog.js";import{validateMemoryExtensionDeclarations as X}from"../plugin-extensions.js";import{validateBundleTransport as Z}from"./endpoint.js";const O=/^[A-Za-z0-9_-]{1,64}$/,L=new Set(["id","major","groups"]),Y=new Set(["group","consent_line","rate","who","allowlist"]),Q=new Set(["own","user","allowlist","any"]),ee=new Set(["bundleVersion","kinds","page_components","memory_extensions","version","transport","provider"]);function ne(e,r,n,o,s,a){return{code:e,message:r,received:n,expected:o,next:s,...a!==void 0?{path:a}:{}}}p(ne,"issue");function d(e,r,n,o,s){return ne("plugin_bundle_invalid",e,r,n,o,s)}p(d,"bundleInvalid");function re(e){if(e[0]===31&&e[1]===139)return d("bundle archive must be uncompressed tar data","gzip-compressed archive","an uncompressed tar archive","Serve the archive without compression.");if(e.length===0||e.length%512!==0)return d("bundle archive must be a non-empty whole number of 512-byte tar blocks",`${e.length} bytes`,"a non-empty tar stream with a 512-byte block length","Serve one complete uncompressed tar archive.");if(e.length<1024||!e.subarray(-1024).every(r=>r===0))return d("bundle archive is missing the required two zero-block terminator","archive tail is not 1024 zero bytes","a complete tar stream ending in two 512-byte zero blocks","Serve the complete uncompressed tar archive.")}p(re,"archiveEnvelopeIssue");function oe(e,r){if(e.length===0||e.includes("\0")||e.includes("\\")||e.startsWith("/")||/^[A-Za-z]:($|\/)/.test(e))return d("bundle member path must be a safe relative POSIX path",e,"a relative path without absolute, drive-letter, backslash, or NUL forms","Use a safe relative POSIX member path.",e);if(r==="file"&&e.endsWith("/"))return d("only directory members may have a trailing slash",e,"a regular-file path without a trailing slash","Emit this member as a regular file path.",e);if((r==="directory"&&e.endsWith("/")?e.slice(0,-1):e).split("/").some(o=>o===""||o==="."||o===".."))return d("bundle member path contains an unsafe segment",e,"non-empty segments other than . or ..","Use a safe relative POSIX member path.",e)}p(oe,"validateMemberName");function se(e,r){if(r==="file")return e==="bundle.json"||e==="commands.json"||e==="hooks.json"||e.startsWith("memory/")&&e.endsWith(".md")||e.startsWith("hooks/")?void 0:d("bundle contains an unsupported file member",e,"bundle.json, commands.json, hooks.json, memory/**/*.md, or hooks/**","Remove unsupported members from the archive.",e);if(!(e==="memory"||e.startsWith("memory/")||e==="hooks"||e.startsWith("hooks/")))return d("bundle contains an unsupported directory member",e,"memory or hooks, or a directory below either subtree","Remove unsupported directory members from the archive.",e)}p(se,"validateMemberShape");function te(e){return e.type==="directory"&&e.path.endsWith("/")?e.path.slice(0,-1):e.path}p(te,"materializedPath");function ie(e){const r=new Map;for(const n of e){const o=te(n),s=r.get(o);if(s!==void 0)return d(s===n.type?"bundle contains a duplicate member":"bundle claims one path as both a file and a directory",`${o} (${s} and ${n.type})`,"each materialized path exactly once, with one type","Emit each archive path once, as a single member type.",o);r.set(o,n.type)}for(const[n,o]of r)if(o==="file"){for(const s of r.keys())if(s.startsWith(`${n}/`))return d("bundle nests a member beneath a regular-file path",`${s} beneath file ${n}`,"members nested only beneath directory paths","Emit the parent path as a directory or remove the nested member.",s)}}p(ie,"validateMemberTree");async function ae(e){const r=[],n=new Set;let o;return await new Promise((s,a)=>{const l=new J({strict:!0,maxMetaEntrySize:Number.MAX_SAFE_INTEGER,gzip:!1,brotli:!1,zstd:!1,onReadEntry:p(t=>{const m=t.path,f=t.type==="File"||t.type==="OldFile"?"file":t.type==="Directory"?"directory":void 0;if(f===void 0||t.unsupported||t.ignore||t.linkpath!==void 0){o??=d("bundle member must be a regular file or directory without a link target",`${m} (${t.type})`,"a regular file or directory member without link metadata","Remove links and unsupported member types from the archive.",m),t.resume();return}o??=oe(m,f),o??=se(m,f),n.has(m)&&(o??=d("bundle contains a duplicate member",m,"each member path exactly once","Emit each archive member only once.",m)),n.add(m);const i=[];t.on("data",b=>i.push(b)),t.on("end",()=>{o===void 0&&r.push({path:m,type:f,bytes:new Uint8Array(Buffer.concat(i))})})},"onReadEntry")});l.on("ignoredEntry",t=>{const m=t.path;o??=d("bundle contains an ignored or unsupported archive member",`${m} (${t.type})`,"only supported regular file or directory members","Remove ignored or unsupported members from the archive.",m),t.resume()}),l.once("error",a),l.once("end",s);try{l.end(Buffer.from(e))}catch(t){a(t)}}).catch(s=>{o??=d("bundle archive could not be parsed",s instanceof Error?s.message:String(s),"a valid complete uncompressed tar archive","Fix the archive format and serve it again.")}),o===void 0?{members:r}:{issue:o}}p(ae,"parseArchive");function N(e){return typeof e=="number"&&Number.isInteger(e)&&e>=1}p(N,"isPositiveInt");function de(e){const r=p((t,m,f,i)=>({issue:d(t,m,f,"Fix the provider block in bundle.json.",`bundle.json.${i}`)}),"bad");if(typeof e!="object"||e===null||Array.isArray(e))return r("bundle.json provider must be an object",JSON.stringify(e),"{ id, major, groups }","provider");const n=e,o=Object.keys(n).filter(t=>!L.has(t));if(o.length>0)return r("bundle.json provider has unknown keys",o.join(", "),"only: id, major, groups","provider");if(typeof n.id!="string"||!O.test(n.id))return r("provider id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(n.id),'a provider id of 1-64 letters, digits, "_" or "-"',"provider.id");if(!N(n.major))return r("provider major must be an integer of at least 1",JSON.stringify(n.major),"the served major as an integer >= 1","provider.major");const s=n.groups;if(!Array.isArray(s)||s.length===0)return r("provider groups must be a non-empty array",JSON.stringify(s),"at least one { group, consent_line, rate?, who, allowlist }","provider.groups");const a=[],l=new Set;for(let t=0;t<s.length;t++){const m=`provider.groups[${t}]`,f=s[t];if(typeof f!="object"||f===null||Array.isArray(f))return r("provider group must be an object",JSON.stringify(f),"{ group, consent_line, rate?, who, allowlist }",m);const i=f,b=Object.keys(i).filter(v=>!Y.has(v));if(b.length>0)return r("provider group has unknown keys",b.join(", "),"only: group, consent_line, rate, who, allowlist",m);if(typeof i.group!="string"||!O.test(i.group))return r("provider group id must match [A-Za-z0-9_-]{1,64}",JSON.stringify(i.group),'a group id of 1-64 letters, digits, "_" or "-"',`${m}.group`);if(l.has(i.group))return r("provider group ids must be unique",i.group,"each group declared once",`${m}.group`);if(l.add(i.group),typeof i.consent_line!="string"||i.consent_line.trim().length===0)return r("provider group consent_line must be a non-empty string",JSON.stringify(i.consent_line),"the consent line shown for this group",`${m}.consent_line`);const h=i.rate;if(h!=null&&(typeof h!="number"||!Number.isFinite(h)||h<0))return r("provider group rate must be a non-negative number or null",JSON.stringify(h),"a finite number >= 0, null, or omitted",`${m}.rate`);if(typeof i.who!="string"||!Q.has(i.who))return r("provider group who must be own|user|allowlist|any",JSON.stringify(i.who),"own | user | allowlist | any",`${m}.who`);const c=i.allowlist;if(!Array.isArray(c)||!c.every(v=>typeof v=="string"&&v.length>0))return r("provider group allowlist must be an array of non-empty strings",JSON.stringify(c),"an array of app principals ([] unless who is allowlist)",`${m}.allowlist`);if(c.length>0&&i.who!=="allowlist")return r("provider group allowlist must be empty unless who is allowlist",`who ${i.who}`,'who: "allowlist", or allowlist: []',`${m}.allowlist`);a.push({group:i.group,consent_line:i.consent_line,...h!==void 0?{rate:h}:{},who:i.who,allowlist:c})}return{provider:{id:n.id,major:n.major,groups:a}}}p(de,"parseProviderBlock");function ue(e){const r=[],n=p((o,s)=>{const a=[...s,o.name];if(o.kind==="leaf")r.push({path:a.join(" "),leaf:o});else for(const l of o.children)n(l,a)},"walk");for(const o of e.roots)n(o,[]);for(const o of e.coreMounts)n(o.node,o.parent);return r}p(ue,"collectLeaves");function le(e,r){const n=e.provider;if(n===void 0)return;if(e.transport===void 0)return d("a provider bundle must declare its transport","(missing)",'transport { kind: "http", endpoint } in bundle.json',"Add the provider endpoint as bundle.json transport.","bundle.json.transport");if(e.transport.kind==="http"&&e.transport.authEnv!==void 0)return d("a provider bundle must not declare authEnv",e.transport.authEnv,"no authEnv: the runtime presents its own outbound token","Remove transport.authEnv from bundle.json.","bundle.json.transport.authEnv");const o=new Set(n.groups.map(s=>s.group));for(const{path:s,leaf:a}of ue(r)){const l="group"in a?a.group:void 0;if(l===void 0)return d(`provider leaf "${s}" has no group`,s,"a group on every leaf of a provider bundle",`Add a group to leaf "${s}".`,`commands.json:${s}.group`);if(!o.has(l))return d(`provider leaf "${s}" names undeclared group "${l}"`,l,`one of: ${[...o].join(", ")}`,`Declare group "${l}" in the provider block, or move leaf "${s}" to a declared group.`,`commands.json:${s}.group`);if("rest"in a&&a.rest.streaming===!0)return d(`provider leaf "${s}" streams`,"rest.streaming: true","no streaming leaves in a provider bundle",`Make leaf "${s}" return one response.`,`commands.json:${s}.rest.streaming`)}}p(le,"providerBundleIssue");function me(e){let r;try{r=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issue:d("bundle.json is not valid JSON","invalid JSON",'{"bundleVersion":1}',"Write valid bundle metadata.","bundle.json")}}if(typeof r!="object"||r===null||Array.isArray(r))return{issue:d("bundle.json must be a JSON object",JSON.stringify(r),"an object with bundleVersion set to 1 and optionally kinds",'Set bundle.json to {"bundleVersion":1}.',"bundle.json")};const n=r,o=Object.keys(n).filter(t=>!ee.has(t));if(n.bundleVersion!==1||o.length>0)return{issue:d("bundle.json must contain bundleVersion 1 and at most version, transport, provider, kinds, page_components, and memory_extensions",JSON.stringify(r),"an object with bundleVersion set to 1 and optional version, transport, provider, kinds, page_components, and memory_extensions",'Set bundle.json to {"bundleVersion":1} plus optional contribution blocks.',"bundle.json")};if(n.kinds!==void 0){const t=M(n.kinds);if(t.length>0)return{issue:d("bundle.json kinds block is invalid",t.join("; "),"each entry a full KindConfig (whenToUse required) or a sparse patch of launch fields","Fix the kinds entries in bundle.json.","bundle.json")}}if(n.page_components!==void 0){const t=K(n.page_components);if(t.length>0)return{issue:d("bundle.json page_components block is invalid",t.join("; "),"an array of page component registrations, each a kind string or {kind, description?, useWhen?, doc?, display?}","Fix the page_components entries in bundle.json.","bundle.json")}}let s;if(n.memory_extensions!==void 0)try{s=X(n.memory_extensions)}catch(t){return{issue:d("bundle.json memory_extensions block is invalid",t instanceof Error?t.message:String(t),"a valid memory_extensions declaration object","Fix the memory_extensions declarations in bundle.json.","bundle.json")}}if(n.version!==void 0&&!N(n.version))return{issue:d("bundle.json version must be an integer of at least 1",JSON.stringify(n.version),"the plugin major as an integer >= 1, or omitted","Fix version in bundle.json.","bundle.json.version")};let a;if(n.transport!==void 0){const t=Z(n.transport);if(t.transport===void 0)return{issue:d("bundle.json transport is invalid",t.errors.join("; "),'{ kind: "http", endpoint, authEnv? } or { kind: "machine" }',"Fix transport in bundle.json.","bundle.json.transport")};a=t.transport}let l;if(n.provider!==void 0){const t=de(n.provider);if(t.issue!==void 0)return{issue:t.issue};if(l=t.provider,n.version!==void 0&&n.version!==l.major)return{issue:d("bundle.json provider major must equal version",`version ${String(n.version)}, major ${l.major}`,"one major","Set provider.major and version to the same integer.","bundle.json.provider.major")}}return{...n.version!==void 0?{version:n.version}:{},...a!==void 0?{transport:a}:{},...l!==void 0?{provider:l}:{},...n.kinds!==void 0?{kinds:n.kinds}:{},...n.page_components!==void 0?{page_components:G(n.page_components)}:{},...s!==void 0?{memory_extensions:s}:{}}}p(me,"parseBundleMetadata");function pe(e,r,n,o=new Set){let s;try{s=JSON.parse(Buffer.from(e).toString("utf8"))}catch{return{issues:[d("commands.json is not valid JSON","invalid JSON","a valid command-manifest JSON object","Regenerate commands.json.","commands.json")]}}const a=B(s,{transport:"http",reservedCoreNames:r,extensibleCoreBranches:o,...n!==void 0?{coreCommandPaths:n}:{}});return{...a.manifest!==void 0?{commands:a.manifest}:{},issues:a.issues}}p(pe,"parseCommands");async function ce(e,r){const n=re(e);if(n!==void 0)return{issues:[n]};const o=await ae(e);if(o.issue!==void 0||o.members===void 0)return{issues:[o.issue]};const s=ie(o.members);if(s!==void 0)return{issues:[s]};const a=o.members.find(u=>u.path==="bundle.json"&&u.type==="file"),l=o.members.find(u=>u.path==="commands.json"&&u.type==="file");if(a===void 0||l===void 0)return{issues:[d("bundle must contain exactly one bundle.json and one commands.json regular file",`bundle.json=${a!==void 0}, commands.json=${l!==void 0}`,"one regular bundle.json and one regular commands.json member","Add the required metadata members to the archive.")]};const t=o.members.find(u=>u.path==="hooks.json"&&u.type==="file"),m=o.members.filter(u=>u.type==="file"&&u.path.startsWith("hooks/"));if((t!==void 0||o.members.some(u=>u.path==="hooks"||u.path.startsWith("hooks/")))&&(t===void 0||!m.some(u=>u.path==="hooks/dispatch")))return{issues:[d("bundle hook payload must contain both hooks.json and hooks/dispatch regular files",`hooks.json=${t!==void 0}, hooks/dispatch=${m.some(u=>u.path==="hooks/dispatch")}`,"both hooks.json and hooks/dispatch regular files","Add both required hook members or remove the hook payload.")]};const i=me(a.bytes);if(i.issue!==void 0)return{issues:[i.issue]};const b=pe(l.bytes,r.reservedCoreNames,r.coreCommandPaths,r.extensibleCoreBranches);if(b.commands===void 0)return{issues:b.issues};const h=b.commands.version;if(i.version!==void 0&&h!==void 0&&i.version!==h)return{issues:[d("bundle.json version and commands.json version differ",`bundle.json ${i.version}, commands.json ${h}`,"one plugin major","Emit the same version in bundle.json and commands.json.","bundle.json.version")]};const c=i.version??h;if(i.provider!==void 0&&c===void 0)return{issues:[d("a provider bundle requires a plugin version","no version in bundle.json or commands.json",`version ${i.provider.major}, the provider major`,"Emit the provider major as the plugin version.","bundle.json.version")]};if(i.provider!==void 0&&c!==i.provider.major)return{issues:[d("bundle provider major must equal the plugin version",`version ${c}, major ${i.provider.major}`,"one major","Emit the provider major as the plugin version.","bundle.json.provider.major")]};const v=le(i,b.commands);return v!==void 0?{issues:[v]}:{bundle:{bundleVersion:1,...c!==void 0?{version:c}:{},...i.transport!==void 0?{transport:i.transport}:{},...i.provider!==void 0?{provider:i.provider}:{},...i.kinds!==void 0?{kinds:i.kinds}:{},...i.page_components!==void 0?{page_components:i.page_components}:{},...i.memory_extensions!==void 0?{memory_extensions:i.memory_extensions}:{},commands:b.commands,commandsBytes:l.bytes,directories:o.members.filter(u=>u.type==="directory").map(u=>({path:u.path})),memory:o.members.filter(u=>u.type==="file"&&u.path.startsWith("memory/")).map(u=>({path:u.path,bytes:u.bytes})),...t!==void 0?{hooks:{manifest:t.bytes,files:m.map(u=>({path:u.path,bytes:u.bytes}))}}:{}},issues:[]}}p(ce,"validatePluginBundle");function fe(e){return I("sha256").update(e).digest("hex").slice(0,12)}p(fe,"bundleVersion");function Ne(e,r,n,o){return R(r).plugins[e]?.version!==o?!1:_(w(n,"commands.json"))}p(Ne,"isInstalledGeneration");function he(e,r,n,o){C(e,s=>{s.plugins[r]===void 0&&(s.plugins[r]={}),o.swapped&&(s.plugins[r].last_updated=E()),s.plugins[r].checked_at=E(),n===void 0?delete s.plugins[r].etag:s.plugins[r].etag=n})}p(he,"recordCheckedGeneration");function g(e,r){const n=V(e,r);if(n===e||!n.startsWith(e+z))throw x(`bundle member path escapes the staging directory: ${r}`);return n}p(g,"stagedPath");function be(e,r,n){const o=p((s,a,l=420)=>q(s,a,{mode:l,durable:!0}),"write");T(g(e,".crouter-plugin/plugin.json"),r,{durable:!0});for(const s of n.directories)j(g(e,s.path));o(g(e,"commands.json"),n.commandsBytes);for(const s of n.memory)o(g(e,s.path),s.bytes);if(n.hooks!==void 0){o(g(e,"hooks.json"),n.hooks.manifest);for(const s of n.hooks.files)o(g(e,s.path),s.bytes);W(g(e,"hooks/dispatch"),493)}H(e)}p(be,"writeStagedBundle");async function Ae(e,r,n){const{scope:o,scopeRootPath:s,root:a}=n,l=await ce(r,n.validation);if(l.bundle===void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,l.issues);const t=l.bundle,m=n.accept?.(t);if(m!==void 0)throw n.invalid(`HTTP plugin bundle for "${e}" is invalid`,[d(m,e,"the bundle this install asked for","Serve the requested bundle.")]);const f=n.version??fe(r),i=t.provider!==void 0&&t.transport!==void 0?t.transport:n.transport,b={name:e,version:f,commands:"commands.json",transport:i,bundle:n.bundle,...t.kinds!==void 0?{kinds:t.kinds}:{},...t.page_components!==void 0?{page_components:t.page_components}:{},...t.memory_extensions!==void 0?{memory_extensions:t.memory_extensions}:{},...t.hooks!==void 0?{hooks:"hooks.json",hookExecutable:"hooks/dispatch"}:{},...t.provider!==void 0?{provider:t.provider}:{}},h=w(s,"tmp"),c=w(h,`${e}.${process.pid}`),v=`${c}.prev`;if(_(v))throw x(`cannot replace plugin "${e}": prior interrupted replacement remains at ${v}`);j(h),j($(a)),S(c);let u;try{j(c),be(c,b,t),u=await n.validateStaged(c,b);let y=!1;_(a)&&(k(a,v),y=!0);try{k(c,a)}catch(A){throw y&&k(v,a),A}U($(a)),y&&S(v)}finally{S(c)}return F(o,s),D(o,y=>{y.plugins[e]={enabled:n.enable?!0:y.plugins[e]?.enabled??!0,version:f}}),he(o,e,n.etag,{swapped:!0}),{manifest:b,validated:t,version:f,root:a,staged:u}}p(Ae,"installBundleArchive");export{fe as bundleVersion,Ae as installBundleArchive,Ne as isInstalledGeneration,he as recordCheckedGeneration,ce as validatePluginBundle,be as writeStagedBundle};
@@ -1 +1 @@
1
- var x=Object.defineProperty;var p=(n,e)=>x(n,"name",{value:e,configurable:!0});import{resolve as k}from"node:path";import{envNodeId as E}from"../../shared/env.js";import{cliClient as c}from"../../commands/api-client.js";import{defineBranch as h,defineLeaf as j}from"../command.js";import{resolveCommandRegistry as w}from"../command-manifests/registry.js";import{executeExternalLeaf as C}from"./transport/exec-invoke.js";import{invokeHttpLeaf as L}from"./transport/http-invoke.js";async function U(n){const e=await c().request("GET",`/v1/providers/${encodeURIComponent(n)}/tools`);return new Set(e.groups.flatMap(r=>r.leaves.map(a=>a.tool)))}p(U,"allowedProviderTools");function m(n,e,r){return n.children.some(a=>a.kind==="leaf"?r.has([...e,a.name].join(" ")):m(a,[...e,a.name],r))}p(m,"hasAllowedLeaf");function M(n,e,r){return n.children.filter(a=>a.tier!=="hidden"&&(a.kind==="leaf"?r.has([...e,a.name].join(" ")):m(a,[...e,a.name],r))).length}p(M,"visibleChildCount");function T(n,e,r){return async()=>{if(!E())return n.help;const a=await U(e.id);return{...n.help,listing:n.help.listing?.flatMap(s=>{const t=n.children.find(o=>o.name===s.name);if(t?.kind==="leaf")return a.has([...r,t.name].join(" "))?[s]:[];if(t?.kind!=="branch")return[];const i=[...r,t.name];if(!m(t,i,a))return[];const u=M(t,i,a);return[{...s,...s.subCount!==void 0?{subCount:u}:{}}]})}}}p(T,"providerHelp");function g(n,e,r){return{outputKind:"object",run:p(async a=>{const s={...a};for(const t of r.params){if(!("type"in t)||t.type!=="file")continue;const i=t.name.replace(/-([a-z])/g,(o,d)=>d.toUpperCase()),u=s[i];typeof u=="string"&&(s[i]=k(u))}return c({timeoutMs:0}).request("POST","/v1/provider/call",{provider:n,tool:e,arguments:s},{timeout:0})},"run")}}p(g,"providerLeafAdapter");function H(n,e){const r=[],a=[];for(const s of n){const{provider:t,commands:i}=s;if(!/^[A-Za-z0-9_-]{1,64}$/.test(t)||t==="crtr"||e.has(t)||!Array.isArray(i))continue;if(s.status!=="available"){const o=s.error??"bundle installation pending",d=`Unavailable (${s.status}): ${o}`,f=h({name:t,rootEntry:{concept:s.display_name,desc:d,useWhen:d},help:{name:t,summary:d},children:[]});f.unavailable=d,a.push(f);continue}const u=p(o=>o.kind==="branch"?{...o,children:o.children.map(u)}:{...o,rest:{method:"GET",path:"/",params:{}}},"helpNode");for(const o of i)o.name!==t||o.kind!=="branch"||!Array.isArray(o.children)||r.push({contributor:{name:t,scope:"user"},manifestName:o.name,node:u(o),adaptLeaf:p((d,f)=>g(t,f.slice(1).join(" "),d),"adaptLeaf")})}return[...A(w(r,e)),...a]}p(H,"composeSandboxProviderSubtrees");function v(n,e){return(r,a)=>{const s=n.transport,t=e(a);if(n.provider!==void 0)return g(n.provider.id,t.slice(1).join(" "),r);if(s.kind==="exec"){const u=s.executable;if(u===void 0)throw new Error(`exec plugin "${n.plugin.name}" has an executable leaf without transport.executable`);return{outputKind:"object",run:p(async o=>C({plugin:n.plugin,executable:u,commandPath:t,output:r.output},o),"run")}}const i=r;return{outputKind:i.rest.streaming===!0?"jsonl":"object",run:p((u,o)=>L({registration:{name:n.plugin.name,endpoint:s.endpoint,...s.authEnv!==void 0?{authEnv:s.authEnv}:{}},...n.manifest?.baseUrl!==void 0?{baseUrl:n.manifest.baseUrl}:{},...n.manifest?.timeouts!==void 0?{timeouts:n.manifest.timeouts}:{},params:i.params,rest:i.rest,output:i.output,commandPath:t},u,o),"run")}}}p(v,"adaptLeaf");function _(n){return{contributions:n.map(e=>({contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,manifestName:e.manifestName,adaptLeaf:v(e,r=>[e.manifestName,...r.slice(1)]),...e.provider!==void 0?{provider:e.provider}:{}})),coreMounts:[],issues:[]}}p(_,"adaptPluginContributions");function q(n){return n.map(e=>({parent:e.parent,plugin:e.plugin.name,contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,...e.provider!==void 0?{provider:e.provider}:{},adaptLeaf:v({...e,manifestName:e.node.name},r=>e.provider===void 0?[...r]:[e.provider.id,...r.slice(e.parent.length)])}))}p(q,"adaptPluginCoreMounts");function A(n){return n.contributions.map(e=>l(e,e.node,[e.node.name]))}p(A,"composeExternalSubtrees");function z(n,e){return n.filter(r=>r.parent.join("\0")===e.join("\0")).map(r=>r.node.kind==="branch"?l(r,r.node,[...e,r.node.name],r.provider===void 0?1:e.length):y(r,r.node,[...e,r.node.name]))}p(z,"composeCoreBranchMounts");function l(n,e,r,a=1){const s=e.rootEntry===void 0?void 0:{concept:e.rootEntry.concept,desc:e.rootEntry.description,useWhen:e.rootEntry.whenToUse},t=h({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...s!==void 0?{rootEntry:s}:{},help:{name:r.join(" "),summary:e.summary,...e.model!==void 0?{model:e.model}:{},...r.length===1&&n.extensionIssue!==void 0?{extensionIssue:n.extensionIssue}:{}},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},children:e.children.map(i=>i.kind==="branch"?l(n,i,[...r,i.name],a):y(n,i,[...r,i.name]))});return n.provider!==void 0&&(t.effectiveHelp=T(t,n.provider,r.slice(a))),t}p(l,"buildBranch");function y(n,e,r){const a=n.adaptLeaf(e,r);return j({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},help:{name:r.join(" "),summary:e.summary,params:e.params,output:e.output,outputKind:a.outputKind,effects:[...e.effects]},run:a.run})}p(y,"buildLeaf");export{_ as adaptPluginContributions,q as adaptPluginCoreMounts,z as composeCoreBranchMounts,A as composeExternalSubtrees,H as composeSandboxProviderSubtrees};
1
+ var k=Object.defineProperty;var u=(n,e)=>k(n,"name",{value:e,configurable:!0});import{resolve as w}from"node:path";import{envNodeId as x}from"../../shared/env.js";import{cliClient as f}from"../../commands/api-client.js";import{defineBranch as h,defineLeaf as C}from"../command.js";import{resolveCommandRegistry as j}from"../command-manifests/registry.js";import{executeExternalLeaf as L}from"./transport/exec-invoke.js";import{invokeHttpLeaf as U}from"./transport/http-invoke.js";async function M(n){const e=await f().request("GET",`/v1/providers/${encodeURIComponent(n)}/tools`);return new Set(e.groups.flatMap(r=>r.leaves.map(a=>a.tool)))}u(M,"allowedProviderTools");function m(n,e,r){return n.children.some(a=>a.kind==="leaf"?r.has([...e,a.name].join(" ")):m(a,[...e,a.name],r))}u(m,"hasAllowedLeaf");function T(n,e,r){return n.children.filter(a=>a.tier!=="hidden"&&(a.kind==="leaf"?r.has([...e,a.name].join(" ")):m(a,[...e,a.name],r))).length}u(T,"visibleChildCount");function S(n,e,r){return async()=>{if(!x())return n.help;const a=await M(e.id);return{...n.help,listing:n.help.listing?.flatMap(i=>{const t=n.children.find(s=>s.name===i.name);if(t?.kind==="leaf")return a.has([...r,t.name].join(" "))?[i]:[];if(t?.kind!=="branch")return[];const o=[...r,t.name];if(!m(t,o,a))return[];const p=T(t,o,a);return[{...i,...i.subCount!==void 0?{subCount:p}:{}}]})}}}u(S,"providerHelp");const A="Runs on one of your devices. Add `--machine <machine_id>` to pick which; without it the call goes to your only device, and with several the answer is `machine_required` listing each one's machine_id. The `runtime:` prefix on the id is optional.";function g(n,e,r,a=!1){return{outputKind:"object",...a?{machineSelector:!0}:{},run:u(async(i,t)=>{const o={...i};for(const p of r.params){if(!("type"in p)||p.type!=="file")continue;const s=p.name.replace(/-([a-z])/g,(c,E)=>E.toUpperCase()),d=o[s];typeof d=="string"&&(o[s]=w(d))}return f({timeoutMs:0}).request("POST","/v1/provider/call",{provider:n,tool:e,arguments:o,...t?.machine===void 0?{}:{machine:t.machine}},{timeout:0})},"run")}}u(g,"providerLeafAdapter");function q(n,e){const r=[],a=[];for(const i of n){const{provider:t,commands:o}=i;if(!/^[A-Za-z0-9_-]{1,64}$/.test(t)||t==="crtr"||e.has(t)||!Array.isArray(o))continue;if(i.status!=="available"){const s=i.error??"bundle installation pending",d=`Unavailable (${i.status}): ${s}`,c=h({name:t,rootEntry:{concept:i.display_name,desc:d,useWhen:d},help:{name:t,summary:d},children:[]});c.unavailable=d,a.push(c);continue}const p=u(s=>s.kind==="branch"?{...s,children:s.children.map(p)}:{...s,rest:{method:"GET",path:"/",params:{}}},"helpNode");for(const s of o)s.name!==t||s.kind!=="branch"||!Array.isArray(s.children)||r.push({contributor:{name:t,scope:"user"},manifestName:s.name,node:p(s),adaptLeaf:u((d,c)=>g(t,c.slice(1).join(" "),d,i.transport==="machine"),"adaptLeaf")})}return[...N(j(r,e)),...a]}u(q,"composeSandboxProviderSubtrees");function v(n,e){return(r,a)=>{const i=n.transport,t=e(a);if(n.provider!==void 0)return g(n.provider.id,t.slice(1).join(" "),r,i.kind==="machine");if(i.kind==="exec"){const p=i.executable;if(p===void 0)throw new Error(`exec plugin "${n.plugin.name}" has an executable leaf without transport.executable`);return{outputKind:"object",run:u(async s=>L({plugin:n.plugin,executable:p,commandPath:t,output:r.output},s),"run")}}if(i.kind==="machine")throw new Error(`machine plugin "${n.plugin.name}" is not a capability provider`);const o=r;return{outputKind:o.rest.streaming===!0?"jsonl":"object",run:u((p,s)=>U({registration:{name:n.plugin.name,endpoint:i.endpoint,...i.authEnv!==void 0?{authEnv:i.authEnv}:{}},...n.manifest?.baseUrl!==void 0?{baseUrl:n.manifest.baseUrl}:{},...n.manifest?.timeouts!==void 0?{timeouts:n.manifest.timeouts}:{},params:o.params,rest:o.rest,output:o.output,commandPath:t},p,s),"run")}}}u(v,"adaptLeaf");function B(n){return{contributions:n.map(e=>({contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,manifestName:e.manifestName,adaptLeaf:v(e,r=>[e.manifestName,...r.slice(1)]),...e.provider!==void 0?{provider:e.provider}:{}})),coreMounts:[],issues:[]}}u(B,"adaptPluginContributions");function G(n){return n.map(e=>({parent:e.parent,plugin:e.plugin.name,contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,...e.provider!==void 0?{provider:e.provider}:{},adaptLeaf:v({...e,manifestName:e.node.name},r=>e.provider===void 0?[...r]:[e.provider.id,...r.slice(e.parent.length)])}))}u(G,"adaptPluginCoreMounts");function N(n){return n.contributions.map(e=>l(e,e.node,[e.node.name]))}u(N,"composeExternalSubtrees");function z(n,e){return n.filter(r=>r.parent.join("\0")===e.join("\0")).map(r=>r.node.kind==="branch"?l(r,r.node,[...e,r.node.name],r.provider===void 0?1:e.length):y(r,r.node,[...e,r.node.name]))}u(z,"composeCoreBranchMounts");function l(n,e,r,a=1){const i=e.rootEntry===void 0?void 0:{concept:e.rootEntry.concept,desc:e.rootEntry.description,useWhen:e.rootEntry.whenToUse},t=h({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...i!==void 0?{rootEntry:i}:{},help:{name:r.join(" "),summary:e.summary,...e.model!==void 0?{model:e.model}:{},...r.length===1&&n.extensionIssue!==void 0?{extensionIssue:n.extensionIssue}:{}},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},children:e.children.map(o=>o.kind==="branch"?l(n,o,[...r,o.name],a):y(n,o,[...r,o.name]))});return n.provider!==void 0&&(t.effectiveHelp=S(t,n.provider,r.slice(a))),t}u(l,"buildBranch");function y(n,e,r){const a=n.adaptLeaf(e,r);return C({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...a.machineSelector===!0?{machineSelector:!0}:{},help:{name:r.join(" "),summary:e.summary,...a.machineSelector===!0?{guide:A}:{},params:e.params,output:e.output,outputKind:a.outputKind,effects:[...e.effects]},run:a.run})}u(y,"buildLeaf");export{B as adaptPluginContributions,G as adaptPluginCoreMounts,z as composeCoreBranchMounts,N as composeExternalSubtrees,q as composeSandboxProviderSubtrees};
@@ -1 +1 @@
1
- var C=Object.defineProperty;var d=(e,t)=>C(e,"name",{value:t,configurable:!0});import{realpathSync as y,readFileSync as N,statSync as b}from"node:fs";import{isSandboxedNode as F}from"../../shared/env.js";import{isAbsolute as O,resolve as _,sep as q}from"node:path";import{listInstalledPlugins as A,listInstalledPluginsInRoot as E}from"../resolver.js";import{projectScopeRoots as R}from"../scope.js";import{validateCommandManifest as I}from"../../api/command-manifest/manifest.js";import{qualifiedTopLevelCommandName as $,resolveCommandRegistry as J}from"../command-manifests/registry.js";import{adaptPluginContributions as T,adaptPluginCoreMounts as B}from"./compose.js";import{validateHttpPluginTransport as H}from"./endpoint.js";import{applyCommandExtensions as L}from"./extensions.js";function w(e=process.cwd(),t){const a=new Set,s=[],i=d(n=>{a.has(n.name)||(a.add(n.name),s.push(n))},"consider");for(const n of R(e,t))for(const r of E("project",n))i(r);if(!F())for(const n of A("user"))i(n);return s.filter(n=>n.enabled&&(n.manifest.commands!==void 0||n.manifest.transport!==void 0))}d(w,"effectiveCommandPlugins");function W(e,t){return a=>t.push({...a,plugin:e.name})}d(W,"issueFor");function v(e,t){if(typeof t!="string"||t.length===0)return null;try{const a=y(e),s=y(_(e,t));return(s===a||s.startsWith(a+q))&&b(s).isFile()?s:null}catch{return null}}d(v,"safePath");function X(e,t){const a=e.manifest.transport;if(a===void 0){t.push({code:"command_manifest_invalid",plugin:e.name,message:"commands requires transport in plugin.json",received:"(missing)",expected:"a transport object",next:"Add plugin.json.transport.",path:"transport"});return}if(typeof a!="object"||a===null||Array.isArray(a)){t.push({code:"command_manifest_invalid",plugin:e.name,message:"transport must be an object",received:Array.isArray(a)?"array":typeof a,expected:"an exec or http transport object",next:"Fix plugin.json.transport.",path:"transport"});return}const s=a;if(s.kind==="http"){const n=H(s);if(n.transport!==void 0)return n.transport;t.push({code:"command_manifest_invalid",plugin:e.name,message:n.errors.join("; "),received:JSON.stringify(a),expected:"a valid HTTP transport",next:"Fix plugin.json.transport.",path:"transport"});return}const i=Object.keys(s).filter(n=>!["kind","executable"].includes(n));if(s.kind!=="exec"||i.length||s.executable!==void 0&&(typeof s.executable!="string"||s.executable.length===0)){t.push({code:"command_manifest_invalid",plugin:e.name,message:i.length?`transport has unknown keys: ${i.join(", ")}`:"exec transport requires a non-empty executable when declared",received:JSON.stringify(a),expected:'{ kind: "exec", executable?: "plugin-relative path" }',next:"Fix plugin.json.transport.",path:"transport"});return}return{kind:"exec",...s.executable!==void 0?{executable:s.executable}:{}}}d(X,"transportFor");function k(e){return e.kind==="leaf"||e.children.some(k)}d(k,"hasExecutableLeaf");function M(e){return[...e.roots,...e.coreMounts.map(t=>t.node)]}d(M,"manifestNodes");function z(e,t,a,s){if(t.executable===void 0){if(!M(a).some(k))return t;s.push({code:"command_manifest_invalid",plugin:e.name,message:"exec transport requires executable when commands.json declares executable leaves",received:"(missing)",expected:'{ kind: "exec", executable: "plugin-relative path" }',next:"Add plugin.json.transport.executable.",path:"transport.executable"});return}const i=v(e.root,t.executable);if(i===null){s.push({code:"command_path_unsafe",plugin:e.name,message:"executable escapes the plugin root or is not a regular file",received:t.executable,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.transport.executable.",path:"transport.executable"});return}if((b(i).mode&73)===0){s.push({code:"command_not_executable",plugin:e.name,message:"executable lacks the POSIX exec bit",received:t.executable,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update the plugin.",path:"transport.executable"});return}return{kind:"exec",executable:i}}d(z,"validateExecExecutable");function G(e){return O(e)||e.startsWith(".")||e.includes("/")||e.includes("\\")}d(G,"namesPath");function K(e,t,a){let s=!0;const i=d(n=>{if(n.kind!=="branch")return;const r=n.passthrough;if(r!==void 0&&G(r.bin)){const l=v(e.root,r.bin);l===null?(a.push({code:"command_path_unsafe",plugin:e.name,message:"passthrough binary escapes the plugin root or is not a regular file",received:r.bin,expected:"a relative path to a regular file inside the plugin root",next:"Fix passthrough.bin in commands.json.",path:"passthrough.bin"}),s=!1):(b(l).mode&73)===0?(a.push({code:"command_not_executable",plugin:e.name,message:"passthrough binary lacks the POSIX exec bit",received:r.bin,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update commands.json.",path:"passthrough.bin"}),s=!1):n.passthrough={...r,bin:l}}for(const l of n.children)i(l)},"visit");for(const n of M(t))i(n);return s}d(K,"resolvePassthroughBinaries");function S(e,t=new Set,a,s=new Set){const i=[],n=e.manifest.commands,r=e.manifest.transport;let l=_(e.root,typeof n=="string"?n:"commands.json");const c=d(x=>({plugin:e,manifestPath:l,...x===void 0?{}:{transport:x},contributions:[],coreMounts:[],issues:i}),"empty");if(n===void 0&&r===void 0)return c();if(typeof n!="string"||n.length===0)return i.push({code:"command_manifest_invalid",plugin:e.name,message:"transport requires commands in plugin.json",received:"(missing)",expected:"a plugin-root-relative commands path",next:"Add plugin.json.commands or remove transport.",path:"commands"}),c();const o=X(e,i);if(o===void 0)return c();const u=v(e.root,n);if(u===null)return i.push({code:"command_path_unsafe",plugin:e.name,message:"commands pointer escapes the plugin root or is not a regular file",received:n,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.commands.",path:"commands"}),c(o);l=u;let p;try{p=N(u)}catch{p=null}if(p===null)return i.push({code:"command_manifest_unreadable",plugin:e.name,message:"commands.json is missing or unreadable",received:u,expected:"a readable JSON file",next:"Reinstall or update the plugin."}),c(o);let h;try{h=JSON.parse(Buffer.from(p).toString("utf8"))}catch{return i.push({code:"command_manifest_invalid",plugin:e.name,message:"commands.json is not valid JSON",received:u,expected:"a JSON object",next:"Regenerate commands.json."}),c(o)}const f=I(h,{transport:o.kind,reservedCoreNames:t,extensibleCoreBranches:s,...a!==void 0?{coreCommandPaths:a}:{}});if(f.manifest===void 0)return f.issues.forEach(W(e,i)),c(o);const m=o.kind==="exec"?z(e,o,f.manifest,i):o;if(m===void 0)return c(o);if(m.kind==="exec"&&!K(e,f.manifest,i))return c(m);const g=e.manifest.provider,j=f.manifest.roots;if(!j.every(x=>x.kind==="branch"))throw new Error("plugin manifest validation returned a top-level leaf");return{plugin:e,manifestPath:l,transport:m,manifest:f.manifest,contributions:j.map(x=>({plugin:e,transport:m,node:x,manifest:f.manifest,manifestName:x.name,...g!==void 0?{provider:g}:{}})),coreMounts:f.manifest.coreMounts.map(x=>({plugin:e,transport:m,parent:x.parent,node:x.node,manifest:f.manifest,...g!==void 0?{provider:g}:{}})),issues:i}}d(S,"validatePluginCommands");function ie(e=process.cwd(),t){return w(e,t).map(a=>S(a))}d(ie,"discoverPluginCommandCandidates");function P(e,t=process.cwd(),a,s,i=new Set){const n=w(t,a).map(c=>S(c,e,s,i)),r=new Map;for(const c of n)for(const o of c.contributions){const u=r.get(o.node.name);u===void 0?r.set(o.node.name,[o]):u.push(o)}for(const[c,o]of r)if(o.length>1)for(const u of o){const{plugin:p}=u,h=$({name:p.name,sourceMarketplace:p.sourceMarketplace},c),f=n.find(m=>m.plugin.root===p.root);f.contributions=f.contributions.map(m=>m===u?{...m,node:{...m.node,name:h}}:m),f.issues.push({code:"command_collision",plugin:p.name,path:c,message:`top-level command "${c}" is claimed by ${o.map(m=>m.plugin.name).join(", ")} \u2014 mounted as "${h}"`,received:o.map(m=>m.plugin.name).join(", "),expected:"an origin-qualified top-level command for each claimant",next:`Invoke "${h}" to use this plugin command.`})}const l=new Map;for(const c of n)for(const o of c.coreMounts){const u=[...o.parent,o.node.name].join("/"),p=l.get(u);p===void 0?l.set(u,[o]):p.push(o)}for(const[c,o]of l)if(o.length>1)for(const u of o){const p=n.find(h=>h.plugin.root===u.plugin.root);p.coreMounts=p.coreMounts.filter(h=>h!==u),p.issues.push({code:"command_collision",plugin:u.plugin.name,path:c.replace("/","."),message:`contributed command "${u.node.name}" conflicts with another plugin child below the extensible core branch`,received:o.map(h=>h.plugin.name).join(", "),expected:`a unique child name below ${u.parent.join(".")}`,next:"Rename the contributed command or remove this mount."})}return n}d(P,"validateEffectiveCommandPlugins");function Q(e,t){return{...t,contributor:{name:e.name,scope:e.scope,sourceMarketplace:e.sourceMarketplace}}}d(Q,"contributorIssue");function re(e,t=process.cwd(),a,s,i=new Set){const n=P(e,t,a,s,i);return{contributions:n.flatMap(r=>r.contributions),coreMounts:n.flatMap(r=>r.coreMounts),issues:n.flatMap(r=>r.issues)}}d(re,"discoverCommandContributions");function ce(e,t=process.cwd(),a,s,i=new Set){const n=P(e,t,a,s,i),r=T(n.flatMap(o=>o.contributions)).contributions,l=J(r,e);return{contributions:L(l.contributions,t).contributions,coreMounts:B(n.flatMap(o=>o.coreMounts)),issues:[...n.flatMap(o=>o.issues.map(u=>Q(o.plugin,u))),...l.issues]}}d(ce,"buildExternalCommandSnapshot");export{ce as buildExternalCommandSnapshot,re as discoverCommandContributions,ie as discoverPluginCommandCandidates,w as effectiveCommandPlugins,P as validateEffectiveCommandPlugins,S as validatePluginCommands};
1
+ var C=Object.defineProperty;var u=(e,t)=>C(e,"name",{value:t,configurable:!0});import{realpathSync as j,readFileSync as N,statSync as g}from"node:fs";import{isSandboxedNode as F}from"../../shared/env.js";import{isAbsolute as O,resolve as k,sep as q}from"node:path";import{listInstalledPlugins as A,listInstalledPluginsInRoot as E}from"../resolver.js";import{projectScopeRoots as R}from"../scope.js";import{validateCommandManifest as I}from"../../api/command-manifest/manifest.js";import{qualifiedTopLevelCommandName as J,resolveCommandRegistry as $}from"../command-manifests/registry.js";import{adaptPluginContributions as T,adaptPluginCoreMounts as B}from"./compose.js";import{validateHttpPluginTransport as H}from"./endpoint.js";import{applyCommandExtensions as L}from"./extensions.js";function _(e=process.cwd(),t){const i=new Set,a=[],s=u(n=>{i.has(n.name)||(i.add(n.name),a.push(n))},"consider");for(const n of R(e,t))for(const r of E("project",n))s(r);if(!F())for(const n of A("user"))s(n);return a.filter(n=>n.enabled&&(n.manifest.commands!==void 0||n.manifest.transport!==void 0))}u(_,"effectiveCommandPlugins");function W(e,t){return i=>t.push({...i,plugin:e.name})}u(W,"issueFor");function v(e,t){if(typeof t!="string"||t.length===0)return null;try{const i=j(e),a=j(k(e,t));return(a===i||a.startsWith(i+q))&&g(a).isFile()?a:null}catch{return null}}u(v,"safePath");function X(e,t){const i=e.manifest.transport;if(i===void 0){t.push({code:"command_manifest_invalid",plugin:e.name,message:"commands requires transport in plugin.json",received:"(missing)",expected:"a transport object",next:"Add plugin.json.transport.",path:"transport"});return}if(typeof i!="object"||i===null||Array.isArray(i)){t.push({code:"command_manifest_invalid",plugin:e.name,message:"transport must be an object",received:Array.isArray(i)?"array":typeof i,expected:"an exec or http transport object",next:"Fix plugin.json.transport.",path:"transport"});return}const a=i;if(a.kind==="http"){const n=H(a);if(n.transport!==void 0)return n.transport;t.push({code:"command_manifest_invalid",plugin:e.name,message:n.errors.join("; "),received:JSON.stringify(i),expected:"a valid HTTP transport",next:"Fix plugin.json.transport.",path:"transport"});return}if(a.kind==="machine"){if(e.manifest.provider===void 0||Object.keys(a).length!==1){t.push({code:"command_manifest_invalid",plugin:e.name,message:e.manifest.provider===void 0?"a machine transport belongs to a capability provider bundle":"transport has unknown keys",received:JSON.stringify(i),expected:'{ kind: "machine" } on a provider bundle',next:"Fix plugin.json.transport.",path:"transport"});return}return{kind:"machine"}}const s=Object.keys(a).filter(n=>!["kind","executable"].includes(n));if(a.kind!=="exec"||s.length||a.executable!==void 0&&(typeof a.executable!="string"||a.executable.length===0)){t.push({code:"command_manifest_invalid",plugin:e.name,message:s.length?`transport has unknown keys: ${s.join(", ")}`:"exec transport requires a non-empty executable when declared",received:JSON.stringify(i),expected:'{ kind: "exec", executable?: "plugin-relative path" }',next:"Fix plugin.json.transport.",path:"transport"});return}return{kind:"exec",...a.executable!==void 0?{executable:a.executable}:{}}}u(X,"transportFor");function w(e){return e.kind==="leaf"||e.children.some(w)}u(w,"hasExecutableLeaf");function M(e){return[...e.roots,...e.coreMounts.map(t=>t.node)]}u(M,"manifestNodes");function z(e,t,i,a){if(t.executable===void 0){if(!M(i).some(w))return t;a.push({code:"command_manifest_invalid",plugin:e.name,message:"exec transport requires executable when commands.json declares executable leaves",received:"(missing)",expected:'{ kind: "exec", executable: "plugin-relative path" }',next:"Add plugin.json.transport.executable.",path:"transport.executable"});return}const s=v(e.root,t.executable);if(s===null){a.push({code:"command_path_unsafe",plugin:e.name,message:"executable escapes the plugin root or is not a regular file",received:t.executable,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.transport.executable.",path:"transport.executable"});return}if((g(s).mode&73)===0){a.push({code:"command_not_executable",plugin:e.name,message:"executable lacks the POSIX exec bit",received:t.executable,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update the plugin.",path:"transport.executable"});return}return{kind:"exec",executable:s}}u(z,"validateExecExecutable");function G(e){return O(e)||e.startsWith(".")||e.includes("/")||e.includes("\\")}u(G,"namesPath");function K(e,t,i){let a=!0;const s=u(n=>{if(n.kind!=="branch")return;const r=n.passthrough;if(r!==void 0&&G(r.bin)){const l=v(e.root,r.bin);l===null?(i.push({code:"command_path_unsafe",plugin:e.name,message:"passthrough binary escapes the plugin root or is not a regular file",received:r.bin,expected:"a relative path to a regular file inside the plugin root",next:"Fix passthrough.bin in commands.json.",path:"passthrough.bin"}),a=!1):(g(l).mode&73)===0?(i.push({code:"command_not_executable",plugin:e.name,message:"passthrough binary lacks the POSIX exec bit",received:r.bin,expected:"a file with an executable permission bit",next:"chmod +x the plugin executable, then update commands.json.",path:"passthrough.bin"}),a=!1):n.passthrough={...r,bin:l}}for(const l of n.children)s(l)},"visit");for(const n of M(t))s(n);return a}u(K,"resolvePassthroughBinaries");function S(e,t=new Set,i,a=new Set){const s=[],n=e.manifest.commands,r=e.manifest.transport;let l=k(e.root,typeof n=="string"?n:"commands.json");const c=u(x=>({plugin:e,manifestPath:l,...x===void 0?{}:{transport:x},contributions:[],coreMounts:[],issues:s}),"empty");if(n===void 0&&r===void 0)return c();if(typeof n!="string"||n.length===0)return s.push({code:"command_manifest_invalid",plugin:e.name,message:"transport requires commands in plugin.json",received:"(missing)",expected:"a plugin-root-relative commands path",next:"Add plugin.json.commands or remove transport.",path:"commands"}),c();const o=X(e,s);if(o===void 0)return c();const d=v(e.root,n);if(d===null)return s.push({code:"command_path_unsafe",plugin:e.name,message:"commands pointer escapes the plugin root or is not a regular file",received:n,expected:"a relative path to a regular file inside the plugin root",next:"Fix plugin.json.commands.",path:"commands"}),c(o);l=d;let p;try{p=N(d)}catch{p=null}if(p===null)return s.push({code:"command_manifest_unreadable",plugin:e.name,message:"commands.json is missing or unreadable",received:d,expected:"a readable JSON file",next:"Reinstall or update the plugin."}),c(o);let h;try{h=JSON.parse(Buffer.from(p).toString("utf8"))}catch{return s.push({code:"command_manifest_invalid",plugin:e.name,message:"commands.json is not valid JSON",received:d,expected:"a JSON object",next:"Regenerate commands.json."}),c(o)}const f=I(h,{transport:o.kind==="machine"?"http":o.kind,reservedCoreNames:t,extensibleCoreBranches:a,...i!==void 0?{coreCommandPaths:i}:{}});if(f.manifest===void 0)return f.issues.forEach(W(e,s)),c(o);const m=o.kind==="exec"?z(e,o,f.manifest,s):o;if(m===void 0)return c(o);if(m.kind==="exec"&&!K(e,f.manifest,s))return c(m);const b=e.manifest.provider,y=f.manifest.roots;if(!y.every(x=>x.kind==="branch"))throw new Error("plugin manifest validation returned a top-level leaf");return{plugin:e,manifestPath:l,transport:m,manifest:f.manifest,contributions:y.map(x=>({plugin:e,transport:m,node:x,manifest:f.manifest,manifestName:x.name,...b!==void 0?{provider:b}:{}})),coreMounts:f.manifest.coreMounts.map(x=>({plugin:e,transport:m,parent:x.parent,node:x.node,manifest:f.manifest,...b!==void 0?{provider:b}:{}})),issues:s}}u(S,"validatePluginCommands");function se(e=process.cwd(),t){return _(e,t).map(i=>S(i))}u(se,"discoverPluginCommandCandidates");function P(e,t=process.cwd(),i,a,s=new Set){const n=_(t,i).map(c=>S(c,e,a,s)),r=new Map;for(const c of n)for(const o of c.contributions){const d=r.get(o.node.name);d===void 0?r.set(o.node.name,[o]):d.push(o)}for(const[c,o]of r)if(o.length>1)for(const d of o){const{plugin:p}=d,h=J({name:p.name,sourceMarketplace:p.sourceMarketplace},c),f=n.find(m=>m.plugin.root===p.root);f.contributions=f.contributions.map(m=>m===d?{...m,node:{...m.node,name:h}}:m),f.issues.push({code:"command_collision",plugin:p.name,path:c,message:`top-level command "${c}" is claimed by ${o.map(m=>m.plugin.name).join(", ")} \u2014 mounted as "${h}"`,received:o.map(m=>m.plugin.name).join(", "),expected:"an origin-qualified top-level command for each claimant",next:`Invoke "${h}" to use this plugin command.`})}const l=new Map;for(const c of n)for(const o of c.coreMounts){const d=[...o.parent,o.node.name].join("/"),p=l.get(d);p===void 0?l.set(d,[o]):p.push(o)}for(const[c,o]of l)if(o.length>1)for(const d of o){const p=n.find(h=>h.plugin.root===d.plugin.root);p.coreMounts=p.coreMounts.filter(h=>h!==d),p.issues.push({code:"command_collision",plugin:d.plugin.name,path:c.replace("/","."),message:`contributed command "${d.node.name}" conflicts with another plugin child below the extensible core branch`,received:o.map(h=>h.plugin.name).join(", "),expected:`a unique child name below ${d.parent.join(".")}`,next:"Rename the contributed command or remove this mount."})}return n}u(P,"validateEffectiveCommandPlugins");function Q(e,t){return{...t,contributor:{name:e.name,scope:e.scope,sourceMarketplace:e.sourceMarketplace}}}u(Q,"contributorIssue");function re(e,t=process.cwd(),i,a,s=new Set){const n=P(e,t,i,a,s);return{contributions:n.flatMap(r=>r.contributions),coreMounts:n.flatMap(r=>r.coreMounts),issues:n.flatMap(r=>r.issues)}}u(re,"discoverCommandContributions");function ce(e,t=process.cwd(),i,a,s=new Set){const n=P(e,t,i,a,s),r=T(n.flatMap(o=>o.contributions)).contributions,l=$(r,e);return{contributions:L(l.contributions,t).contributions,coreMounts:B(n.flatMap(o=>o.coreMounts)),issues:[...n.flatMap(o=>o.issues.map(d=>Q(o.plugin,d))),...l.issues]}}u(ce,"buildExternalCommandSnapshot");export{ce as buildExternalCommandSnapshot,re as discoverCommandContributions,se as discoverPluginCommandCandidates,_ as effectiveCommandPlugins,P as validateEffectiveCommandPlugins,S as validatePluginCommands};
@@ -22,3 +22,15 @@ export declare function validateHttpPluginTransport(raw: unknown): {
22
22
  };
23
23
  errors: string[];
24
24
  };
25
+ /** The transport a served provider bundle declares: HTTP (the provider's endpoint) or `{ kind: "machine" }` (calls go to a device machine). */
26
+ export type BundleTransport = {
27
+ kind: 'http';
28
+ endpoint: string;
29
+ authEnv?: string;
30
+ } | {
31
+ kind: 'machine';
32
+ };
33
+ export declare function validateBundleTransport(raw: unknown): {
34
+ transport?: BundleTransport;
35
+ errors: string[];
36
+ };
@@ -1 +1 @@
1
- var p=Object.defineProperty;var e=(n,t)=>p(n,"name",{value:t,configurable:!0});import{validateEnvVarName as i}from"../env-name.js";function h(n){return n==="localhost"||n.endsWith(".localhost")||n==="127.0.0.1"||n==="[::1]"||n==="::1"||n==="host.docker.internal"}e(h,"isLoopbackHostname");function l(n){const t=[];if(typeof n!="string")t.push("endpoint must be a string");else try{const o=new URL(n);(o.protocol!=="https:"&&!(o.protocol==="http:"&&h(o.hostname))||o.username||o.password||o.hash)&&t.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}catch{t.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}return{isValid:t.length===0,errors:t}}e(l,"validateEndpoint");function u(n){try{const t=new URL(n),o=t.pathname.length>1&&t.pathname.endsWith("/")?t.pathname.slice(0,-1):t.pathname;return`${t.protocol}//${t.host}${o}${t.search}`}catch{return n}}e(u,"normalizeEndpoint");function f(n){const t=[];if(typeof n!="object"||n===null||Array.isArray(n))return{errors:["transport must be an object"]};const o=n,r=Object.keys(o).filter(a=>!["kind","endpoint","authEnv"].includes(a));r.length&&t.push(`transport has unknown keys: ${r.join(", ")}`),o.kind!=="http"&&t.push('transport.kind must be "http"');const s=l(o.endpoint);return t.push(...s.errors),o.authEnv!==void 0&&t.push(...i(o.authEnv).errors),t.length?{errors:t}:{transport:{kind:"http",endpoint:u(o.endpoint),...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},errors:t}}e(f,"validateHttpPluginTransport");export{u as normalizeEndpoint,l as validateEndpoint,f as validateHttpPluginTransport};
1
+ var i=Object.defineProperty;var e=(n,t)=>i(n,"name",{value:t,configurable:!0});import{validateEnvVarName as p}from"../env-name.js";function h(n){return n==="localhost"||n.endsWith(".localhost")||n==="127.0.0.1"||n==="[::1]"||n==="::1"||n==="host.docker.internal"}e(h,"isLoopbackHostname");function l(n){const t=[];if(typeof n!="string")t.push("endpoint must be a string");else try{const o=new URL(n);(o.protocol!=="https:"&&!(o.protocol==="http:"&&h(o.hostname))||o.username||o.password||o.hash)&&t.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}catch{t.push("endpoint must be an absolute https:// URL (http:// permitted for loopback and *.localhost hosts only; no credentials or fragment)")}return{isValid:t.length===0,errors:t}}e(l,"validateEndpoint");function u(n){try{const t=new URL(n),o=t.pathname.length>1&&t.pathname.endsWith("/")?t.pathname.slice(0,-1):t.pathname;return`${t.protocol}//${t.host}${o}${t.search}`}catch{return n}}e(u,"normalizeEndpoint");function c(n){const t=[];if(typeof n!="object"||n===null||Array.isArray(n))return{errors:["transport must be an object"]};const o=n,r=Object.keys(o).filter(a=>!["kind","endpoint","authEnv"].includes(a));r.length&&t.push(`transport has unknown keys: ${r.join(", ")}`),o.kind!=="http"&&t.push('transport.kind must be "http"');const s=l(o.endpoint);return t.push(...s.errors),o.authEnv!==void 0&&t.push(...p(o.authEnv).errors),t.length?{errors:t}:{transport:{kind:"http",endpoint:u(o.endpoint),...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},errors:t}}e(c,"validateHttpPluginTransport");function m(n){if(typeof n=="object"&&n!==null&&!Array.isArray(n)&&n.kind==="machine"){const t=Object.keys(n).filter(o=>o!=="kind");return t.length?{errors:[`transport has unknown keys: ${t.join(", ")}`]}:{transport:{kind:"machine"},errors:[]}}return c(n)}e(m,"validateBundleTransport");export{u as normalizeEndpoint,m as validateBundleTransport,l as validateEndpoint,c as validateHttpPluginTransport};