@north-light/cli 0.3.519 → 0.3.520

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.
@@ -1,10 +1,12 @@
1
1
  export interface DeclaredOutputShape {
2
2
  type: string;
3
3
  constraint: string;
4
- /** Named members of an `object` value. */
4
+ /** Named members of an `object` value. Declaring them closes the object. */
5
5
  children?: DeclaredOutputField[];
6
- /** Element shape of an `array` value. */
6
+ /** Shape of each contained value of an `array` or `map` value. */
7
7
  items?: DeclaredOutputShape;
8
+ /** Alternatives of a `union` value. */
9
+ variants?: DeclaredOutputShape[];
8
10
  /** Allowed values of an `enum` value. */
9
11
  values?: string[];
10
12
  }
@@ -12,14 +14,24 @@ export interface DeclaredOutputField extends DeclaredOutputShape {
12
14
  name: string;
13
15
  required: boolean;
14
16
  }
17
+ /** The first place a value departs from its shape: `path` is the full path from
18
+ * the root the caller passed (`data.messages[3].from`, `--to[1]`). */
19
+ export interface ValueMismatch {
20
+ path: string;
21
+ expected: string;
22
+ receivedType: string;
23
+ }
15
24
  /** Validates a result object against declared output field descriptors.
16
- * Returns null if valid; otherwise returns a { field, receivedType } error. */
25
+ * Returns null if valid; otherwise returns a { field, receivedType } error
26
+ * where `field` is the full path of the first mismatch. */
17
27
  export declare function validateDeclaredResult(output: DeclaredOutputField[], result: Record<string, unknown>): {
18
28
  field: string;
19
29
  receivedType?: string;
30
+ expected?: string;
20
31
  } | null;
21
- /** Core leaf declarations may explicitly require a nullable field. */
22
- export declare function validateCoreDeclaredResult(output: DeclaredOutputField[], result: Record<string, unknown>): {
23
- field: string;
24
- receivedType?: string;
25
- } | null;
32
+ /** Checks one JSON value against one shape, recursively: object members, array
33
+ * items, map values, union variants, enum values and nullability. An object that
34
+ * declares `children` is closed — an undeclared member is a mismatch at any
35
+ * depth. Returns null, or the first mismatch with its full path from `path`.
36
+ * A declared type that is not recognized is accepted. */
37
+ export declare function validateValue(shape: DeclaredOutputShape, value: unknown, path: string): ValueMismatch | null;
@@ -1 +1 @@
1
- var c=Object.defineProperty;var i=(n,e)=>c(n,"name",{value:e,configurable:!0});import{isRecord as u}from"../../shared/predicates.js";function g(n,e){return f(n,e,!0)}i(g,"validateDeclaredResult");function h(n,e){return f(n,e,!0)}i(h,"validateCoreDeclaredResult");function f(n,e,r){for(const t of n){const o=e[t.name],a=Object.prototype.hasOwnProperty.call(e,t.name)&&o!==void 0&&(r||o!==null);if(t.required&&!a)return{field:t.name};const s=r?p(t,o):y(t.type,o);if(a&&!s)return{field:t.name,receivedType:o===null?"null":typeof o}}return null}i(f,"validateFields");function y(n,e){const r=n.toLowerCase();return r==="string"||r==="path"?typeof e=="string":r==="int"||r==="integer"?typeof e=="number"&&Number.isInteger(e):r==="number"||r==="float"?typeof e=="number":r==="bool"||r==="boolean"?typeof e=="boolean":r==="array"||r.endsWith("[]")?Array.isArray(e):r==="object"?u(e):!0}i(y,"typeMatches");function p(n,e){const r=n.type.split("|").map(o=>o.trim()).filter(Boolean);return r.length===1&&!l(r[0])||!r.every(o=>l(o)||/^[a-z][a-z0-9_-]*$/.test(o))?!0:r.some(o=>m(o,e,n))}i(p,"coreTypeMatches");function l(n){const e=n.toLowerCase();return e==="null"||e==="string"||e==="path"||e==="markdown"||e==="int"||e==="integer"||e==="number"||e==="float"||e==="bool"||e==="boolean"||e==="array"||e.endsWith("[]")||e==="object"||e==="enum"||e==="file"}i(l,"knownType");function m(n,e,r){const t=n.toLowerCase();return t==="null"?e===null:t==="enum"?r.values===void 0||typeof e=="string"&&r.values.includes(e):t==="file"?u(e)||typeof e=="string":t==="string"||t==="path"||t==="markdown"?typeof e=="string":t==="int"||t==="integer"?typeof e=="number"&&Number.isInteger(e):t==="number"||t==="float"?typeof e=="number":t==="bool"||t==="boolean"?typeof e=="boolean":t==="array"||t.endsWith("[]")?Array.isArray(e):t==="object"?u(e):typeof e=="string"&&e===n}i(m,"atomMatches");export{h as validateCoreDeclaredResult,g as validateDeclaredResult};
1
+ var g=Object.defineProperty;var c=(u,e)=>g(u,"name",{value:e,configurable:!0});import{isRecord as d}from"../../shared/predicates.js";function j(u,e){for(const n of u){const l=e[n.name];if(!(Object.prototype.hasOwnProperty.call(e,n.name)&&l!==void 0)){if(n.required)return{field:n.name};continue}const t=s(n,l,n.name);if(t!==null)return{field:t.path,receivedType:t.receivedType,expected:t.expected}}return null}c(j,"validateDeclaredResult");function s(u,e,n){const l=u.type.split("|").map(t=>t.trim()).filter(Boolean),r=l.filter(t=>t.toLowerCase()!=="null");if(e===null&&r.length<l.length||r.length===1&&!p(r[0])||!l.every(t=>p(t)||/^[a-z][a-z0-9_-]*$/.test(t)))return null;if(r.length===1)return b(r[0],u,e,n);for(const t of r)if(b(t,u,e,n)===null)return null;return m(n,u.type,e)}c(s,"validateValue");function m(u,e,n){const l=n===null?"null":Array.isArray(n)?"array":typeof n;return{path:u,expected:e,receivedType:l}}c(m,"mismatch");function p(u){const e=u.toLowerCase();return e==="null"||e==="string"||e==="path"||e==="markdown"||e==="int"||e==="integer"||e==="number"||e==="float"||e==="bool"||e==="boolean"||e==="array"||e.endsWith("[]")||e==="object"||e==="map"||e==="union"||e==="unknown"||e==="enum"||e==="file"}c(p,"knownType");function b(u,e,n,l){const r=u.toLowerCase(),t=c(o=>m(l,o,n),"fail");if(r==="null")return n===null?null:t("null");if(r==="unknown")return null;if(r==="enum")return e.values===void 0||typeof n=="string"&&e.values.includes(n)?null:t(`one of ${(e.values??[]).join(", ")}`);if(r==="file")return d(n)||typeof n=="string"?null:t("file");if(r==="string"||r==="path"||r==="markdown")return typeof n=="string"?null:t(r);if(r==="int"||r==="integer")return typeof n=="number"&&Number.isInteger(n)?null:t("int");if(r==="number"||r==="float")return typeof n=="number"?null:t("number");if(r==="bool"||r==="boolean")return typeof n=="boolean"?null:t("bool");if(r==="array"){if(!Array.isArray(n))return t("array");if(e.items===void 0)return null;for(let o=0;o<n.length;o++){const i=s(e.items,n[o],`${l}[${o}]`);if(i!==null)return i}return null}if(r.endsWith("[]"))return Array.isArray(n)?null:t("array");if(r==="map"){if(!d(n))return t("map");if(e.items===void 0)return null;for(const[o,i]of Object.entries(n)){const f=s(e.items,i,`${l}[${JSON.stringify(o)}]`);if(f!==null)return f}return null}if(r==="object"){if(!d(n))return t("object");if(e.children===void 0)return null;for(const i of e.children){const f=n[i.name];if(!(Object.prototype.hasOwnProperty.call(n,i.name)&&f!==void 0)){if(i.required)return{path:`${l}.${i.name}`,expected:`${i.type} (required)`,receivedType:"undefined"};continue}const y=s(i,f,`${l}.${i.name}`);if(y!==null)return y}const o=new Set(e.children.map(i=>i.name));for(const i of Object.keys(n))if(!o.has(i))return m(`${l}.${i}`,"no such member (the object is closed)",n[i]);return null}if(r==="union"){const o=[];for(const f of e.variants??[]){const a=s(f,n,l);if(a===null)return null;o.push(a)}const i=o[0];return i!==void 0&&i.path!==l&&o.every(f=>f.path===i.path)?{path:i.path,expected:[...new Set(o.map(f=>f.expected))].join(" | "),receivedType:i.receivedType}:t((e.variants??[]).map(f=>f.type).join(" | "))}return typeof n=="string"&&n===u?null:t(u)}c(b,"atomMismatch");export{j as validateDeclaredResult,s as validateValue};
@@ -1 +1 @@
1
- var V=Object.defineProperty;var _=(e,t)=>V(e,"name",{value:t,configurable:!0});import{isRecord as x}from"../../shared/predicates.js";const Z=new Set(["normal","common","important"]),W=new Set(["string","int","bool","path","enum","file"]),X=/^[A-Za-z0-9_-]{1,64}$/,O=/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,U=new Set(["kind","name","description","whenToUse","tier","rootEntry","extensible","summary","model","children"]),J=new Set([...U,"passthrough"]),L=new Set(["bin","installHint"]),N=new Set(["kind","name","description","whenToUse","tier","summary","params","output","effects"]),Q=new Set([...N,"outputKind"]),w=new Set([...N,"rest","group"]),ee=new Set(["concept","description","whenToUse"]),ne=new Set(["text","base64"]),C={positional:new Set(["kind","name","type","required","constraint","repeatable","encoding","defaultFromEnv"]),flag:new Set(["kind","name","type","choices","required","constraint","default","repeatable","encoding","defaultFromEnv"]),stdin:new Set(["kind","name","required","constraint"]),"context-file":new Set(["kind","name","required","constraint","shape"])},te=new Set(["name","type","required","constraint","children","items","values"]),ae=new Set(["type","constraint","children","items","values"]),oe=/^[A-Z][A-Z0-9_]*$/;function m(e){return e===null?"null":Array.isArray(e)?"array":typeof e}_(m,"typeName");function T(e,t,a,n){const o=Object.keys(e).filter(p=>!t.has(p));return o.length>0?(n("command_node_invalid","unknown keys",o.join(", "),`only: ${[...t].join(", ")}`,"Remove the unknown keys.",a),!1):!0}_(T,"checkKeys");function re(e,t,a){if(!x(e))return a("command_node_invalid","rootEntry must be an object",m(e),"a { concept, description, whenToUse } object","Fix rootEntry.",t),null;if(!T(e,ee,t,a))return null;for(const n of["concept","description","whenToUse"])if(typeof e[n]!="string"||e[n].length===0)return a("command_node_invalid",`rootEntry.${n} must be a non-empty string`,m(e[n]),"a non-empty string",`Add rootEntry.${n}.`,`${t}.${n}`),null;return{concept:e.concept,description:e.description,whenToUse:e.whenToUse}}_(re,"validateRootEntry");function ie(e,t,a){if(e===void 0)return[];if(!Array.isArray(e))return a("command_node_invalid","params must be an array",m(e),"an array of params (or omitted)","Fix params.",t),null;const n=[],o=new Set;let p=0;for(let c=0;c<e.length;c++){const r=e[c],i=`${t}[${c}]`;if(!x(r))return a("command_node_invalid","param must be an object",m(r),"a param object","Fix the param.",i),null;const u=r.kind;if(typeof u!="string"||!(u in C))return a("command_node_invalid","param kind must be positional|flag|stdin|context-file",String(u),"positional | flag | stdin | context-file","Fix the param kind.",`${i}.kind`),null;if(!T(r,C[u],i,a))return null;const d=r.name;if(typeof d!="string"||!O.test(d))return a("command_node_invalid","param name must be a kebab-case token",String(d),"a kebab-case token","Rename the param.",`${i}.name`),null;if(o.has(d))return a("command_node_invalid","duplicate param name",d,"unique param names","Rename the duplicate param.",`${i}.name`),null;if(o.add(d),typeof r.required!="boolean")return a("command_node_invalid","param required must be a boolean",m(r.required),"true | false","Set required.",`${i}.required`),null;if(typeof r.constraint!="string")return a("command_node_invalid","param constraint must be a string",m(r.constraint),"an inline constraint string","Add a constraint.",`${i}.constraint`),null;if(u==="positional"){if(p++,p>1)return a("command_node_invalid","a leaf may declare at most one positional","multiple positionals","at most one positional param","Convert extra positionals to flags.",i),null;if(r.type!==void 0&&r.type!=="string"&&r.type!=="path"&&r.type!=="file")return a("command_node_invalid","positional type must be string|path|file",String(r.type),"string | path | file | omitted","Fix the positional type.",`${i}.type`),null;let s=!1;if(r.repeatable!==void 0){if(typeof r.repeatable!="boolean")return a("command_node_invalid","repeatable must be a boolean",m(r.repeatable),"true | false | omitted","Fix repeatable.",`${i}.repeatable`),null;s=r.repeatable}if(s&&r.type==="file")return a("command_node_invalid","repeatable is not valid on a file positional",`${d}: file`,"one file per file param","Remove repeatable, or declare one file param per file.",`${i}.repeatable`),null;const g=H(r,i,a);if(g===null)return null;const y=M(r,i,a);if(y===null)return null;if(s&&y!==void 0)return a("command_node_invalid","repeatable positional must not declare defaultFromEnv",d,"no defaultFromEnv on a repeatable positional (values accumulate into an array)","Remove defaultFromEnv, or drop repeatable.",`${i}.defaultFromEnv`),null;n.push({kind:"positional",name:d,...r.type!==void 0?{type:r.type}:{},required:r.required,constraint:r.constraint,...s?{repeatable:!0}:{},...g!==void 0?{encoding:g}:{},...y!==void 0?{defaultFromEnv:y}:{}})}else if(u==="stdin")n.push({kind:"stdin",name:d,required:r.required,constraint:r.constraint});else if(u==="context-file")n.push({kind:"context-file",name:d,required:r.required,constraint:r.constraint,...typeof r.shape=="string"?{shape:r.shape}:{}});else if(u==="flag"){const s=r.type;if(typeof s!="string"||!W.has(s))return a("command_node_invalid","flag type must be string|int|bool|path|enum|file",String(s),"string | int | bool | path | enum | file","Fix the flag type.",`${i}.type`),null;let g;if(s==="enum"){const h=r.choices;if(!Array.isArray(h)||h.length===0||!h.every(k=>typeof k=="string"))return a("command_node_invalid","enum flag requires a non-empty string choices array",m(h),"a non-empty array of strings","Declare choices for the enum flag.",`${i}.choices`),null;g=h}else if(r.choices!==void 0)return a("command_node_invalid","choices is only valid on an enum flag","choices","no choices unless type is enum","Remove choices.",`${i}.choices`),null;let y=!1;if(r.repeatable!==void 0){if(typeof r.repeatable!="boolean")return a("command_node_invalid","repeatable must be a boolean",m(r.repeatable),"true | false | omitted","Fix repeatable.",`${i}.repeatable`),null;y=r.repeatable}if(y&&(s==="bool"||s==="path"||s==="file"))return a("command_node_invalid",`repeatable is not valid on a ${s} flag`,`${d}: ${s}`,"repeatable only on string | int | enum flags","Remove repeatable or change the flag type.",`${i}.repeatable`),null;if(y&&r.default!==void 0)return a("command_node_invalid","repeatable flag must not declare a default",d,"no default on a repeatable flag (values accumulate into an array)","Remove the default.",`${i}.default`),null;const $=H(r,i,a);if($===null)return null;const E=M(r,i,a);if(E===null)return null;if(y&&E!==void 0)return a("command_node_invalid","repeatable flag must not declare defaultFromEnv",d,"no defaultFromEnv on a repeatable flag (values accumulate into an array)","Remove defaultFromEnv, or drop repeatable.",`${i}.defaultFromEnv`),null;n.push({kind:"flag",name:d,type:s,...g!==void 0?{choices:g}:{},...$!==void 0?{encoding:$}:{},required:r.required,constraint:r.constraint,...r.default!==void 0?{default:r.default}:{},...y?{repeatable:!0}:{},...E!==void 0?{defaultFromEnv:E}:{}})}}return n}_(ie,"validateParams");function H(e,t,a){const n=e.encoding;if(n!==void 0)return typeof n!="string"||!ne.has(n)?(a("command_node_invalid","param encoding must be text|base64",String(n),"text | base64 | omitted","Fix the encoding.",`${t}.encoding`),null):e.type!=="path"?(a("command_node_invalid",'encoding is only valid on a type:"path" param',`type ${String(e.type)}`,'type: "path"','Set the param type to "path", or remove encoding.',`${t}.encoding`),null):n}_(H,"validateFileEncoding");function M(e,t,a){const n=e.defaultFromEnv;if(n===void 0)return;if(typeof n!="string"||!oe.test(n))return a("command_node_invalid","defaultFromEnv must name an environment variable in UPPER_SNAKE_CASE",String(n),'e.g. "CRTR_NODE_ID"',"Fix the environment variable name.",`${t}.defaultFromEnv`),null;const o=e.type===void 0&&e.kind==="positional"?"string":e.type;return o!=="string"&&o!=="path"?(a("command_node_invalid","defaultFromEnv is only valid on a string|path param",`type ${String(e.type)}`,'type: "string" | "path"',"Change the param type, or remove defaultFromEnv.",`${t}.defaultFromEnv`),null):e.default!==void 0?(a("command_node_invalid","a param must not declare both default and defaultFromEnv",`${String(e.name)}: default + defaultFromEnv`,"exactly one default source","Remove one \u2014 defaultFromEnv counts as supplied, a static default does not.",`${t}.defaultFromEnv`),null):n}_(M,"validateDefaultFromEnv");function D(e){return e.split("|").map(t=>t.trim().toLowerCase()).filter(t=>t.length>0&&t!=="null").join(" | ")}_(D,"outputBaseType");function le(e){return e.split("|").some(t=>/^file(\[\])*$/i.test(t.trim()))}_(le,"mentionsFile");function G(e,t,a,n){if(typeof e.type!="string"||e.type.length===0)return n("command_node_invalid","output field type must be a non-empty string",m(e.type),"a type name","Add a type.",`${t}.type`),null;if(typeof e.constraint!="string")return n("command_node_invalid","output field constraint must be a string",m(e.constraint),"a constraint string","Add a constraint.",`${t}.constraint`),null;const o=e.type,p=D(o);if(le(o)&&p!=="file")return n("command_node_invalid","a file output is one file, optionally nullable",o,'type "file" or "file | null"','Return one file per leaf; declare it as "file" or "file | null".',`${t}.type`),null;if(p==="file"&&!a)return n("command_node_invalid","a file output must be a top-level output field",o,`type "file" only on a leaf's own output fields, never inside children or items`,"Move the file field to the top level of the leaf output.",`${t}.type`),null;const c=[["children","object"],["items","array"],["values","enum"]];for(const[d,s]of c)if(e[d]!==void 0&&p!==s)return n("command_node_invalid",`output ${d} is only valid on an ${s} field`,`type ${o}`,`type "${s}" (optionally "${s} | null")`,`Remove ${d}, or change the type to ${s}.`,`${t}.${d}`),null;let r;if(e.children!==void 0){const d=Y(e.children,`${t}.children`,!1,n);if(d===null)return null;r=d}let i;if(e.items!==void 0){const d=e.items,s=`${t}.items`;if(!x(d))return n("command_node_invalid","output items must be an object",m(d),"a { type, constraint, children?, items?, values? } object","Fix the items shape.",s),null;if(!T(d,ae,s,n))return null;const g=G(d,s,!1,n);if(g===null)return null;i=g}let u;if(e.values!==void 0){const d=e.values;if(!Array.isArray(d)||d.length===0||!d.every(s=>typeof s=="string"&&s.length>0))return n("command_node_invalid","enum output values must be a non-empty array of strings",m(d),"a non-empty array of non-empty strings","Declare the values the field may take.",`${t}.values`),null;if(new Set(d).size!==d.length)return n("command_node_invalid","enum output values must be unique",d.join(", "),"distinct values","Remove the duplicate value.",`${t}.values`),null;u=d}return{type:o,constraint:e.constraint,...r!==void 0?{children:r}:{},...i!==void 0?{items:i}:{},...u!==void 0?{values:u}:{}}}_(G,"validateOutputShape");function de(e,t,a){return Y(e,t,!0,a)}_(de,"validateOutput");function Y(e,t,a,n){if(!Array.isArray(e))return n("command_node_invalid",a?"output must be an array":"output children must be an array",m(e),"an array of output fields",a?"Declare the leaf output fields.":"Declare the object's member fields.",t),null;const o=[],p=new Set;let c;for(let r=0;r<e.length;r++){const i=e[r],u=`${t}[${r}]`;if(!x(i))return n("command_node_invalid","output field must be an object",m(i),"a { name, type, required, constraint } object","Fix the output field.",u),null;if(!T(i,te,u,n))return null;if(typeof i.name!="string"||i.name.length===0)return n("command_node_invalid","output field name must be a non-empty string",m(i.name),"a field name","Add a name.",`${u}.name`),null;if(!a&&p.has(i.name))return n("command_node_invalid","duplicate output child name",i.name,"unique member names within one object","Rename the duplicate member.",`${u}.name`),null;if(p.add(i.name),typeof i.required!="boolean")return n("command_node_invalid","output field required must be a boolean",m(i.required),"true | false","Set required.",`${u}.required`),null;const d=G(i,u,a,n);if(d===null)return null;if(D(d.type)==="file"){if(c!==void 0)return n("command_node_invalid","a leaf declares at most one file output",`${c}, ${i.name}`,'one top-level output field of type "file"',"Return the extra files through a separate leaf, or drop the extra file field.",`${u}.type`),null;c=i.name}o.push({name:i.name,...d,required:i.required})}return o}_(Y,"validateOutputFields");function z(e,t,a){const n=e.name;return typeof n!="string"||!O.test(n)?(a("command_node_invalid","node name must be a lowercase kebab-case token",String(n),'a kebab-case token like "app" or "list-all"',"Rename the node.",`${t}.name`),!1):typeof e.description!="string"||e.description.length===0?(a("command_node_invalid","node description must be a non-empty string",m(e.description),"a short parent-listing description","Add a description.",`${t}.description`),!1):typeof e.whenToUse!="string"||e.whenToUse.length===0?(a("command_node_invalid","node whenToUse must be a non-empty string",m(e.whenToUse),"a parent selection rubric","Add a whenToUse rubric.",`${t}.whenToUse`),!1):e.tier!==void 0&&!Z.has(String(e.tier))?(a("command_node_invalid","node tier must be normal|common|important",String(e.tier),"normal | common | important","Fix or omit tier (external plugins cannot declare hidden commands).",`${t}.tier`),!1):!0}_(z,"checkCommon");function me(e,t,a,n,o,p={}){const c=t.join(".");if(!x(e))return o("command_node_invalid","node must be an object",m(e),"a branch or leaf node object","Fix the node.",c),null;const r=e.kind;return r==="branch"?se(e,c,a,n,o,p):r==="leaf"?a&&p.topLevelRootEntry!=="forbidden"?(o("command_node_invalid","top-level node must be a branch","leaf","a branch node with a rootEntry","Wrap the command in a top-level branch (noun) with a rootEntry.",`${c}.kind`),null):ce(e,c,n,o):(o("command_node_invalid",'node kind must be "branch" or "leaf"',String(r),"branch | leaf","Set kind to branch or leaf.",`${c}.kind`),null)}_(me,"validateCommandNode");function se(e,t,a,n,o,p){const c=p.allowPassthrough!==!1;if(!T(e,n==="exec"&&c?J:U,t,o)||!z(e,t,o))return null;const i=e.rootEntry!==void 0,u=p.topLevelRootEntry??"required";if(a&&u==="required"&&!i)return o("command_node_invalid","top-level branch requires a rootEntry","(missing)","a rootEntry { concept, description, whenToUse }","Add a rootEntry to the top-level branch.",`${t}.rootEntry`),null;if((!a||u==="forbidden")&&i)return o("command_node_invalid",a?"repository fragment root must not declare a rootEntry":"nested branch must not declare a rootEntry","rootEntry",a?"no rootEntry (the owning plugin branch already has one)":"no rootEntry (only top-level branches carry one)",a?"Remove rootEntry from the repository fragment root.":"Remove rootEntry from the nested branch.",`${t}.rootEntry`),null;const d=e.extensible!==void 0;if(d&&(!a||p.allowExtensible===!1||e.extensible!==!0))return o("command_node_invalid",a?"extensible must be exactly true on a plugin top-level branch":"extensible is valid only on a top-level branch mount",String(e.extensible),a?"extensible: true":"no extensible marker on nested branches",a?"Set extensible to true or remove it.":"Move the marker to the plugin top-level branch or remove it.",`${t}.extensible`),null;let s;if(i){const h=re(e.rootEntry,`${t}.rootEntry`,o);if(h===null)return null;s=h}if(typeof e.summary!="string"||e.summary.length===0)return o("command_node_invalid","branch summary must be a non-empty string",m(e.summary),"a one-line summary string","Add a summary.",`${t}.summary`),null;if(e.model!==void 0&&typeof e.model!="string")return o("command_node_invalid","branch model must be a string",m(e.model),"a string or omitted","Fix or remove model.",`${t}.model`),null;let g;if(n==="exec"&&e.passthrough!==void 0){const h=ue(e.passthrough,`${t}.passthrough`,o);if(h===null)return null;g=h}if(d&&g!==void 0)return o("command_node_invalid","an extensible branch cannot declare passthrough","extensible and passthrough","one branch mode: extensible or passthrough","Remove passthrough or remove extensible.",t),null;const y=e.children;if(g!==void 0){if(!Array.isArray(y)||y.length!==0)return o("command_node_invalid","a passthrough branch must declare children: []",Array.isArray(y)?`${y.length} children`:m(y),"an empty children array","Set children to [] \u2014 a passthrough branch forwards all argv and can own no subcommands.",`${t}.children`),null}else if(!Array.isArray(y))return o("command_node_invalid","branch children must be an array",m(y),"an array of branch/leaf nodes (may be empty for populated by mounts)","Fix children.",`${t}.children`),null;const $=[],E=new Set;for(let h=0;h<y.length;h++){const k=me(y[h],[...t.split("."),`children[${h}]`],!1,n,o,p);if(k===null)return null;if(E.has(k.name))return o("command_node_invalid","duplicate child name",k.name,"unique child names within a branch","Rename the duplicate child.",`${t}.children[${h}].name`),null;E.add(k.name),$.push(k)}return{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...s!==void 0?{rootEntry:s}:{},...d?{extensible:!0}:{},summary:e.summary,...e.model!==void 0?{model:e.model}:{},...g!==void 0?{passthrough:g}:{},children:$}}_(se,"validateBranch");function ue(e,t,a){if(!x(e))return a("command_node_invalid","passthrough must be an object",m(e),"an object { bin, installHint }","Fix or remove passthrough.",t),null;if(!T(e,L,t,a))return null;for(const n of["bin","installHint"])if(typeof e[n]!="string"||e[n].length===0)return a("command_node_invalid",`passthrough ${n} must be a non-empty string`,m(e[n]),n==="bin"?"the external binary name":"a one-line install hint",`Set passthrough.${n}.`,`${t}.${n}`),null;return{bin:e.bin,installHint:e.installHint}}_(ue,"validatePassthrough");function ce(e,t,a,n){if(!T(e,a==="exec"?Q:w,t,n)||!z(e,t,n))return null;if(typeof e.summary!="string"||e.summary.length===0)return n("command_node_invalid","leaf summary must be a non-empty string",m(e.summary),"a one-line summary string","Add a summary.",`${t}.summary`),null;if(a==="exec"&&e.outputKind!=="object")return n("command_node_invalid",'leaf outputKind must be "object" in v1',String(e.outputKind),'exactly "object"','Set outputKind to "object".',`${t}.outputKind`),null;if(a==="http"&&e.rest===void 0)return n("command_node_invalid","HTTP leaf requires a rest mapping","(missing)","a rest object with method, path, and params","Add a rest mapping.",`${t}.rest`),null;const o=ie(e.params,`${t}.params`,n);if(o===null)return null;const p=de(e.output,`${t}.output`,n);if(p===null)return null;const c=e.effects;if(!Array.isArray(c)||c.length===0||!c.every(d=>typeof d=="string"))return n("command_node_invalid","leaf effects must be a non-empty string array",m(c),'a non-empty array of strings (["None. Read-only."] for read-only leaves)',"Declare every persistent effect.",`${t}.effects`),null;const r={kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},summary:e.summary,params:o,output:p,effects:c};if(a==="exec")return{...r,outputKind:"object"};const i=e.group;if(i!==void 0&&(typeof i!="string"||!X.test(i)))return n("command_node_invalid","leaf group must match [A-Za-z0-9_-]{1,64}",typeof i=="string"?i:m(i),'a group id of 1-64 letters, digits, "_" or "-" (no ":" or whitespace)',`Rename the group on leaf ${t}.`,`${t}.group`),null;const u=he(e.rest,t,o,n);return u===null?null:{...r,rest:u,...i!==void 0?{group:i}:{}}}_(ce,"validateLeaf");const fe=new Set(["GET","POST","PUT","PATCH","DELETE"]),pe=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/,ye=new Set(["path","query","body","header"]);function he(e,t,a,n){const o=`${t}.rest`;if(!x(e))return n("command_rest_invalid","rest must be an object",m(e),"{ method, path, streaming?, params }","Provide a valid rest mapping.",o),null;const p=new Set(["method","path","streaming","body","bodyRoot","params"]),c=Object.keys(e).filter(l=>!p.has(l));if(c.length>0)return n("command_rest_invalid","unknown rest keys",c.join(", "),"only: method, path, streaming, body, bodyRoot, params","Remove the unknown keys.",o),null;const r=e.method;if(typeof r!="string"||!fe.has(r))return n("command_rest_invalid","method must be GET|POST|PUT|PATCH|DELETE",String(r),"one of the HTTP methods","Set method to a valid HTTP verb.",`${o}.method`),null;const i=e.path;if(typeof i!="string"||!i.startsWith("/"))return n("command_rest_invalid","path must be an absolute path starting with /",String(i),'an absolute path like "/v1/devices/{id}"',"Fix the path.",`${o}.path`),null;const u=new Set,d=/\{([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\}/g;let s;for(;(s=d.exec(i))!==null;)u.add(s[1]);let g=!1;if(e.streaming!==void 0){if(typeof e.streaming!="boolean")return n("command_rest_invalid","streaming must be a boolean",m(e.streaming),"true | false | omitted","Fix streaming.",`${o}.streaming`),null;g=e.streaming}const y=e.params;if(!x(y))return n("command_rest_invalid","params must be an object",m(y),"a mapping of param names to placement specs","Provide params.",`${o}.params`),null;const $=new Map;for(const l of a)$.set(l.name,l);const E={},h=[],k=new Set;for(const[l,v]of Object.entries(y)){if(!$.has(l))return n("command_rest_invalid","rest params key has no matching declared param",l,"a name from the leaf params array","Remove this rest params entry or declare the param.",`${o}.params.${l}`),null;if(k.add(l),!x(v))return n("command_rest_invalid","param mapping must be an object",m(v),"{ in, as? }","Fix the param mapping.",`${o}.params.${l}`),null;const S=new Set(["in","as"]),R=Object.keys(v).filter(F=>!S.has(F));if(R.length>0)return n("command_rest_invalid","unknown param mapping keys",R.join(", "),"only: in, as","Remove the unknown keys.",`${o}.params.${l}`),null;const f=v.in;if(typeof f!="string"||!ye.has(f))return n("command_rest_invalid","param placement must be path|query|body|header",String(f),"path | query | body | header","Set in to a valid placement.",`${o}.params.${l}.in`),null;const b=v.as;if(b!==void 0&&(typeof b!="string"||b.length===0))return n("command_rest_invalid","as must be a non-empty string",String(b),"a non-empty string","Fix the as value.",`${o}.params.${l}.as`),null;if(f==="header"){if(typeof b!="string"||b.length===0)return n("command_rest_invalid","header placement requires a non-empty as",String(b),"a header name","Provide an as value for the header name.",`${o}.params.${l}.as`),null;if(!pe.test(b))return n("command_rest_invalid",'header "as" must be a legal HTTP header field name',b,"an HTTP token (letters, digits, and !#$%&'*+-.^_`|~)","Fix the header name.",`${o}.params.${l}.as`),null;if(b.toLowerCase()==="crtr-request-id")return n("command_rest_invalid","Crtr-Request-Id is assigned by crtr for each leaf call",b,"a header name other than Crtr-Request-Id","Remove this header mapping.",`${o}.params.${l}.as`),null}else if(f==="path"){if(b!==void 0)return n("command_rest_invalid","path placement must not have as","as","no as (placeholder name governs)","Remove as.",`${o}.params.${l}.as`),null;const F=$.get(l);if(F.required!==!0)return n("command_rest_invalid",'in:"path" param must be declared required',`${l}: required ${String(F.required)}`,"required: true",`Set "required": true on the "${l}" param.`,`${o}.params.${l}`),null}const K=$.get(l);if((K.kind==="flag"||K.kind==="positional")&&K.repeatable===!0&&f!=="body")return n("command_rest_invalid","repeatable param must use body placement",`${l}: in "${f}"`,'in: "body" (array values ship as a JSON array)',`Map "${l}" to body placement.`,`${o}.params.${l}.in`),null;if(f==="body"){const B=$.get(l).kind==="stdin",I=b||l;h.push({name:l,isStdin:B,as:I})}E[l]={in:f,...b!==void 0?{as:b}:{}}}for(const l of a)if(!k.has(l.name))return n("command_rest_invalid","declared param not in rest mapping",l.name,"an entry in rest.params","Add a rest mapping for this param.",`${o}.params`),null;const A=new Set;for(const[l,v]of Object.entries(E))v.in==="path"&&A.add(l);if(A.size!==u.size||![...u].every(l=>A.has(l)))return n("command_rest_invalid","mismatch between path placeholders and path params",`placeholders: ${[...u].join(", ")} | params: ${[...A].join(", ")}`,'exact bijection between {placeholders} and in:"path" params',"Ensure every placeholder has a matching path param, and vice versa.",`${o}.path and ${o}.params`),null;if(r==="GET"&&h.length>0)return n("command_rest_invalid","GET method cannot have body params",h.map(l=>l.name).join(", "),"no body placement for GET","Move body params to query, or change the method.",`${o}.method or ${o}.params`),null;let j;if(e.body!==void 0){if(r==="GET")return n("command_rest_invalid","GET method cannot have body constants","body","no body for GET","Remove body, or change the method.",`${o}.body`),null;const l=e.body;if(!x(l))return n("command_rest_invalid","body must be an object",m(l),"a map of constant literal body fields (string | number | boolean)","Fix body.",`${o}.body`),null;const v={};for(const[f,b]of Object.entries(l)){if(typeof b!="string"&&typeof b!="number"&&typeof b!="boolean")return n("command_rest_invalid",`body.${f} must be a string, number, or boolean`,m(b),"string | number | boolean",`Fix body.${f}.`,`${o}.body.${f}`),null;v[f]=b}const S=new Set(h.map(f=>f.as)),R=Object.keys(v).filter(f=>S.has(f));if(R.length>0)return n("command_rest_invalid","body constant key collides with a param's body key",R.join(", "),"body constant keys distinct from every param's body key (its as or name)","Rename the colliding body constant or param.",`${o}.body`),null;j=v}let P;if(e.bodyRoot!==void 0){if(r==="GET")return n("command_rest_invalid","GET method cannot have bodyRoot","bodyRoot","no bodyRoot for GET","Remove bodyRoot, or change the method.",`${o}.bodyRoot`),null;const l=e.bodyRoot;if(typeof l!="string"||l.length===0)return n("command_rest_invalid","bodyRoot must be a non-empty string",m(l),"a non-empty string key","Fix bodyRoot.",`${o}.bodyRoot`),null;P=l}for(const[l,v]of Object.entries(E)){const S=$.get(l);if((S.kind==="context-file"||(S.kind==="positional"||S.kind==="flag")&&S.type==="path")&&v.in!=="body"){const f=S.kind==="context-file"?"context-file":`path ${S.kind}`;return n("command_rest_invalid",`file param (${f}) must be placed in body`,`${l}: in "${v.in}"`,'in: "body"',"Move the file param to body.",`${o}.params.${l}.in`),null}}const q=new Map;for(const l of h)q.has(l.as)||q.set(l.as,[]),q.get(l.as).push({name:l.name,isStdin:l.isStdin});for(const[l,v]of q.entries())if(v.length>1){const S=v.filter(f=>f.isStdin).length,R=v.filter(f=>!f.isStdin).length;if(!(S===1&&R===1))return n("command_rest_invalid","invalid body as grouping",v.map(f=>f.name).join(", "),"exactly one stdin param and one non-stdin param may share a body as (for coalesced sources)","Fix the body grouping.",`${o}.params (as: "${l}")`),null}return{method:r,path:i,...g!==!1?{streaming:g}:{},...j!==void 0?{body:j}:{},...P!==void 0?{bodyRoot:P}:{},params:E}}_(he,"validateRestMapping");export{le as mentionsFile,D as outputBaseType,me as validateCommandNode};
1
+ var Z=Object.defineProperty;var E=(e,t)=>Z(e,"name",{value:t,configurable:!0});import{isRecord as x}from"../../shared/predicates.js";const w=new Set(["hidden","normal","common","important"]),J=new Set(["string","int","bool","path","enum","file","json"]),W=/^[A-Za-z0-9_-]{1,64}$/,N=/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,C=new Set(["kind","name","description","whenToUse","tier","rootEntry","extensible","summary","model","children"]),X=new Set([...C,"passthrough"]),Q=new Set(["bin","installHint"]),D=new Set(["kind","name","description","whenToUse","tier","summary","params","output","effects"]),L=new Set([...D,"outputKind"]),ee=new Set([...D,"rest","group"]),ne=new Set(["concept","description","whenToUse"]),te=new Set(["text","base64"]),H={positional:new Set(["kind","name","type","required","constraint","repeatable","encoding","defaultFromEnv"]),flag:new Set(["kind","name","type","choices","required","constraint","default","repeatable","encoding","defaultFromEnv","shape"]),stdin:new Set(["kind","name","required","constraint"]),"context-file":new Set(["kind","name","required","constraint","shape"])},ae=new Set(["name","type","required","constraint","children","items","variants","values"]),U=new Set(["type","constraint","children","items","variants","values"]),oe=/^[A-Z][A-Z0-9_]*$/;function m(e){return e===null?"null":Array.isArray(e)?"array":typeof e}E(m,"typeName");function T(e,t,a,n){const o=Object.keys(e).filter(h=>!t.has(h));return o.length>0?(n("command_node_invalid","unknown keys",o.join(", "),`only: ${[...t].join(", ")}`,"Remove the unknown keys.",a),!1):!0}E(T,"checkKeys");function re(e,t,a){if(!x(e))return a("command_node_invalid","rootEntry must be an object",m(e),"a { concept, description, whenToUse } object","Fix rootEntry.",t),null;if(!T(e,ne,t,a))return null;for(const n of["concept","description","whenToUse"])if(typeof e[n]!="string"||e[n].length===0)return a("command_node_invalid",`rootEntry.${n} must be a non-empty string`,m(e[n]),"a non-empty string",`Add rootEntry.${n}.`,`${t}.${n}`),null;return{concept:e.concept,description:e.description,whenToUse:e.whenToUse}}E(re,"validateRootEntry");function ie(e,t,a){if(e===void 0)return[];if(!Array.isArray(e))return a("command_node_invalid","params must be an array",m(e),"an array of params (or omitted)","Fix params.",t),null;const n=[],o=new Set;let h=0;for(let p=0;p<e.length;p++){const r=e[p],i=`${t}[${p}]`;if(!x(r))return a("command_node_invalid","param must be an object",m(r),"a param object","Fix the param.",i),null;const u=r.kind;if(typeof u!="string"||!(u in H))return a("command_node_invalid","param kind must be positional|flag|stdin|context-file",String(u),"positional | flag | stdin | context-file","Fix the param kind.",`${i}.kind`),null;if(!T(r,H[u],i,a))return null;const c=r.name;if(typeof c!="string"||!N.test(c))return a("command_node_invalid","param name must be a kebab-case token",String(c),"a kebab-case token","Rename the param.",`${i}.name`),null;if(o.has(c))return a("command_node_invalid","duplicate param name",c,"unique param names","Rename the duplicate param.",`${i}.name`),null;if(o.add(c),typeof r.required!="boolean")return a("command_node_invalid","param required must be a boolean",m(r.required),"true | false","Set required.",`${i}.required`),null;if(typeof r.constraint!="string")return a("command_node_invalid","param constraint must be a string",m(r.constraint),"an inline constraint string","Add a constraint.",`${i}.constraint`),null;if(u==="positional"){if(h++,h>1)return a("command_node_invalid","a leaf may declare at most one positional","multiple positionals","at most one positional param","Convert extra positionals to flags.",i),null;if(r.type!==void 0&&r.type!=="string"&&r.type!=="path"&&r.type!=="file")return a("command_node_invalid","positional type must be string|path|file",String(r.type),"string | path | file | omitted","Fix the positional type.",`${i}.type`),null;let d=!1;if(r.repeatable!==void 0){if(typeof r.repeatable!="boolean")return a("command_node_invalid","repeatable must be a boolean",m(r.repeatable),"true | false | omitted","Fix repeatable.",`${i}.repeatable`),null;d=r.repeatable}if(d&&r.type==="file")return a("command_node_invalid","repeatable is not valid on a file positional",`${c}: file`,"one file per file param","Remove repeatable, or declare one file param per file.",`${i}.repeatable`),null;const s=M(r,i,a);if(s===null)return null;const f=G(r,i,a);if(f===null)return null;if(d&&f!==void 0)return a("command_node_invalid","repeatable positional must not declare defaultFromEnv",c,"no defaultFromEnv on a repeatable positional (values accumulate into an array)","Remove defaultFromEnv, or drop repeatable.",`${i}.defaultFromEnv`),null;n.push({kind:"positional",name:c,...r.type!==void 0?{type:r.type}:{},required:r.required,constraint:r.constraint,...d?{repeatable:!0}:{},...s!==void 0?{encoding:s}:{},...f!==void 0?{defaultFromEnv:f}:{}})}else if(u==="stdin")n.push({kind:"stdin",name:c,required:r.required,constraint:r.constraint});else if(u==="context-file")n.push({kind:"context-file",name:c,required:r.required,constraint:r.constraint,...typeof r.shape=="string"?{shape:r.shape}:{}});else if(u==="flag"){const d=r.type;if(typeof d!="string"||!J.has(d))return a("command_node_invalid","flag type must be string|int|bool|path|enum|file|json",String(d),"string | int | bool | path | enum | file | json","Fix the flag type.",`${i}.type`),null;let s;if(d==="enum"){const _=r.choices;if(!Array.isArray(_)||_.length===0||!_.every(R=>typeof R=="string"))return a("command_node_invalid","enum flag requires a non-empty string choices array",m(_),"a non-empty array of strings","Declare choices for the enum flag.",`${i}.choices`),null;s=_}else if(r.choices!==void 0)return a("command_node_invalid","choices is only valid on an enum flag","choices","no choices unless type is enum","Remove choices.",`${i}.choices`),null;let f;if(d==="json"){const _=r.shape;if(!x(_))return a("command_node_invalid","json flag requires a shape object",m(_),"a { type, constraint, children?, items?, variants?, values? } object","Declare the shape of the JSON value the flag takes.",`${i}.shape`),null;if(!T(_,U,`${i}.shape`,a))return null;const R=q(_,`${i}.shape`,!1,a);if(R===null)return null;f=R}else if(r.shape!==void 0)return a("command_node_invalid","shape is only valid on a json flag","shape","no shape unless type is json","Remove shape.",`${i}.shape`),null;let b=!1;if(r.repeatable!==void 0){if(typeof r.repeatable!="boolean")return a("command_node_invalid","repeatable must be a boolean",m(r.repeatable),"true | false | omitted","Fix repeatable.",`${i}.repeatable`),null;b=r.repeatable}if(d==="json"&&(r.default!==void 0||r.defaultFromEnv!==void 0))return a("command_node_invalid","a json flag takes no default",c,"no default or defaultFromEnv on a json flag","Remove the default.",`${i}.${r.default!==void 0?"default":"defaultFromEnv"}`),null;if(b&&(d==="bool"||d==="path"||d==="file"||d==="json"))return a("command_node_invalid",`repeatable is not valid on a ${d} flag`,`${c}: ${d}`,"repeatable only on string | int | enum flags","Remove repeatable or change the flag type.",`${i}.repeatable`),null;if(b&&r.default!==void 0)return a("command_node_invalid","repeatable flag must not declare a default",c,"no default on a repeatable flag (values accumulate into an array)","Remove the default.",`${i}.default`),null;const S=M(r,i,a);if(S===null)return null;const v=G(r,i,a);if(v===null)return null;if(b&&v!==void 0)return a("command_node_invalid","repeatable flag must not declare defaultFromEnv",c,"no defaultFromEnv on a repeatable flag (values accumulate into an array)","Remove defaultFromEnv, or drop repeatable.",`${i}.defaultFromEnv`),null;n.push({kind:"flag",name:c,type:d,...f!==void 0?{shape:f}:{},...s!==void 0?{choices:s}:{},...S!==void 0?{encoding:S}:{},required:r.required,constraint:r.constraint,...r.default!==void 0?{default:r.default}:{},...b?{repeatable:!0}:{},...v!==void 0?{defaultFromEnv:v}:{}})}}return n}E(ie,"validateParams");function M(e,t,a){const n=e.encoding;if(n!==void 0)return typeof n!="string"||!te.has(n)?(a("command_node_invalid","param encoding must be text|base64",String(n),"text | base64 | omitted","Fix the encoding.",`${t}.encoding`),null):e.type!=="path"?(a("command_node_invalid",'encoding is only valid on a type:"path" param',`type ${String(e.type)}`,'type: "path"','Set the param type to "path", or remove encoding.',`${t}.encoding`),null):n}E(M,"validateFileEncoding");function G(e,t,a){const n=e.defaultFromEnv;if(n===void 0)return;if(typeof n!="string"||!oe.test(n))return a("command_node_invalid","defaultFromEnv must name an environment variable in UPPER_SNAKE_CASE",String(n),'e.g. "CRTR_NODE_ID"',"Fix the environment variable name.",`${t}.defaultFromEnv`),null;const o=e.type===void 0&&e.kind==="positional"?"string":e.type;return o!=="string"&&o!=="path"?(a("command_node_invalid","defaultFromEnv is only valid on a string|path param",`type ${String(e.type)}`,'type: "string" | "path"',"Change the param type, or remove defaultFromEnv.",`${t}.defaultFromEnv`),null):e.default!==void 0?(a("command_node_invalid","a param must not declare both default and defaultFromEnv",`${String(e.name)}: default + defaultFromEnv`,"exactly one default source","Remove one \u2014 defaultFromEnv counts as supplied, a static default does not.",`${t}.defaultFromEnv`),null):n}E(G,"validateDefaultFromEnv");function Y(e){return e.split("|").map(t=>t.trim().toLowerCase()).filter(t=>t.length>0&&t!=="null").join(" | ")}E(Y,"outputBaseType");function le(e){return e.split("|").some(t=>/^file(\[\])*$/i.test(t.trim()))}E(le,"mentionsFile");function q(e,t,a,n){if(typeof e.type!="string"||e.type.length===0)return n("command_node_invalid","output field type must be a non-empty string",m(e.type),"a type name","Add a type.",`${t}.type`),null;if(typeof e.constraint!="string")return n("command_node_invalid","output field constraint must be a string",m(e.constraint),"a constraint string","Add a constraint.",`${t}.constraint`),null;const o=e.type,h=Y(o);if(le(o)&&h!=="file")return n("command_node_invalid","a file output is one file, optionally nullable",o,'type "file" or "file | null"','Return one file per leaf; declare it as "file" or "file | null".',`${t}.type`),null;if(h==="file"&&!a)return n("command_node_invalid","a file output must be a top-level output field",o,`type "file" only on a leaf's own output fields, never inside children or items`,"Move the file field to the top level of the leaf output.",`${t}.type`),null;const p=[["children",["object"]],["items",["array","map"]],["variants",["union"]],["values",["enum"]]];for(const[d,s]of p)if(e[d]!==void 0&&!s.includes(h)){const f=s.map(b=>`"${b}"`).join(" or ");return n("command_node_invalid",`output ${d} is only valid on a ${s.join(" or ")} field`,`type ${o}`,`type ${f} (optionally "| null")`,`Remove ${d}, or change the type to ${s[0]}.`,`${t}.${d}`),null}if(h==="union"&&e.variants===void 0)return n("command_node_invalid","a union output requires variants",`type ${o}`,"variants: two or more shapes","Declare the alternatives the union may take.",`${t}.variants`),null;if(h==="map"&&e.items===void 0)return n("command_node_invalid","a map output requires items",`type ${o}`,"items: the shape of every value",`Declare the shape of the map's values, or use type "object".`,`${t}.items`),null;let r;if(e.children!==void 0){const d=z(e.children,`${t}.children`,!1,n);if(d===null)return null;r=d}let i;if(e.items!==void 0){const d=e.items,s=`${t}.items`;if(!x(d))return n("command_node_invalid","output items must be an object",m(d),"a { type, constraint, children?, items?, variants?, values? } object","Fix the items shape.",s),null;if(!T(d,U,s,n))return null;const f=q(d,s,!1,n);if(f===null)return null;i=f}let u;if(e.variants!==void 0){const d=e.variants;if(!Array.isArray(d)||d.length<2)return n("command_node_invalid","union variants must be an array of two or more shapes",Array.isArray(d)?`${d.length} variant${d.length===1?"":"s"}`:m(d),"an array of two or more { type, constraint, ... } shapes","Declare at least two variants, or use the variant's own type.",`${t}.variants`),null;u=[];for(let s=0;s<d.length;s++){const f=`${t}.variants[${s}]`,b=d[s];if(!x(b))return n("command_node_invalid","union variant must be an object",m(b),"a { type, constraint, ... } object","Fix the variant shape.",f),null;if(!T(b,U,f,n))return null;const S=q(b,f,!1,n);if(S===null)return null;u.push(S)}}let c;if(e.values!==void 0){const d=e.values;if(!Array.isArray(d)||d.length===0||!d.every(s=>typeof s=="string"&&s.length>0))return n("command_node_invalid","enum output values must be a non-empty array of strings",m(d),"a non-empty array of non-empty strings","Declare the values the field may take.",`${t}.values`),null;if(new Set(d).size!==d.length)return n("command_node_invalid","enum output values must be unique",d.join(", "),"distinct values","Remove the duplicate value.",`${t}.values`),null;c=d}return{type:o,constraint:e.constraint,...r!==void 0?{children:r}:{},...i!==void 0?{items:i}:{},...u!==void 0?{variants:u}:{},...c!==void 0?{values:c}:{}}}E(q,"validateOutputShape");function de(e,t,a){return z(e,t,!0,a)}E(de,"validateOutput");function z(e,t,a,n){if(!Array.isArray(e))return n("command_node_invalid",a?"output must be an array":"output children must be an array",m(e),"an array of output fields",a?"Declare the leaf output fields.":"Declare the object's member fields.",t),null;const o=[],h=new Set;let p;for(let r=0;r<e.length;r++){const i=e[r],u=`${t}[${r}]`;if(!x(i))return n("command_node_invalid","output field must be an object",m(i),"a { name, type, required, constraint } object","Fix the output field.",u),null;if(!T(i,ae,u,n))return null;if(typeof i.name!="string"||i.name.length===0)return n("command_node_invalid","output field name must be a non-empty string",m(i.name),"a field name","Add a name.",`${u}.name`),null;if(h.has(i.name))return n("command_node_invalid",a?"duplicate output field name":"duplicate output child name",i.name,a?"unique output field names within one leaf":"unique member names within one object",a?"Rename the duplicate field.":"Rename the duplicate member.",`${u}.name`),null;if(h.add(i.name),typeof i.required!="boolean")return n("command_node_invalid","output field required must be a boolean",m(i.required),"true | false","Set required.",`${u}.required`),null;const c=q(i,u,a,n);if(c===null)return null;if(Y(c.type)==="file"){if(p!==void 0)return n("command_node_invalid","a leaf declares at most one file output",`${p}, ${i.name}`,'one top-level output field of type "file"',"Return the extra files through a separate leaf, or drop the extra file field.",`${u}.type`),null;p=i.name}o.push({name:i.name,...c,required:i.required})}return o}E(z,"validateOutputFields");function B(e,t,a){const n=e.name;return typeof n!="string"||!N.test(n)?(a("command_node_invalid","node name must be a lowercase kebab-case token",String(n),'a kebab-case token like "app" or "list-all"',"Rename the node.",`${t}.name`),!1):typeof e.description!="string"||e.description.length===0?(a("command_node_invalid","node description must be a non-empty string",m(e.description),"a short parent-listing description","Add a description.",`${t}.description`),!1):typeof e.whenToUse!="string"||e.whenToUse.length===0?(a("command_node_invalid","node whenToUse must be a non-empty string",m(e.whenToUse),"a parent selection rubric","Add a whenToUse rubric.",`${t}.whenToUse`),!1):e.tier!==void 0&&!w.has(String(e.tier))?(a("command_node_invalid","node tier must be hidden|normal|common|important",String(e.tier),"hidden | normal | common | important","Fix or omit tier.",`${t}.tier`),!1):!0}E(B,"checkCommon");function me(e,t,a,n,o,h={}){const p=t.join(".");if(!x(e))return o("command_node_invalid","node must be an object",m(e),"a branch or leaf node object","Fix the node.",p),null;const r=e.kind;return r==="branch"?se(e,p,a,n,o,h):r==="leaf"?a&&h.topLevelRootEntry!=="forbidden"?(o("command_node_invalid","top-level node must be a branch","leaf","a branch node with a rootEntry","Wrap the command in a top-level branch (noun) with a rootEntry.",`${p}.kind`),null):ce(e,p,n,o):(o("command_node_invalid",'node kind must be "branch" or "leaf"',String(r),"branch | leaf","Set kind to branch or leaf.",`${p}.kind`),null)}E(me,"validateCommandNode");function se(e,t,a,n,o,h){const p=h.allowPassthrough!==!1;if(!T(e,n==="exec"&&p?X:C,t,o)||!B(e,t,o))return null;const i=e.rootEntry!==void 0,u=h.topLevelRootEntry??"required";if(a&&u==="required"&&!i)return o("command_node_invalid","top-level branch requires a rootEntry","(missing)","a rootEntry { concept, description, whenToUse }","Add a rootEntry to the top-level branch.",`${t}.rootEntry`),null;if((!a||u==="forbidden")&&i)return o("command_node_invalid",a?"repository fragment root must not declare a rootEntry":"nested branch must not declare a rootEntry","rootEntry",a?"no rootEntry (the owning plugin branch already has one)":"no rootEntry (only top-level branches carry one)",a?"Remove rootEntry from the repository fragment root.":"Remove rootEntry from the nested branch.",`${t}.rootEntry`),null;const c=e.extensible!==void 0;if(c&&(!a||h.allowExtensible===!1||e.extensible!==!0))return o("command_node_invalid",a?"extensible must be exactly true on a plugin top-level branch":"extensible is valid only on a top-level branch mount",String(e.extensible),a?"extensible: true":"no extensible marker on nested branches",a?"Set extensible to true or remove it.":"Move the marker to the plugin top-level branch or remove it.",`${t}.extensible`),null;let d;if(i){const v=re(e.rootEntry,`${t}.rootEntry`,o);if(v===null)return null;d=v}if(typeof e.summary!="string"||e.summary.length===0)return o("command_node_invalid","branch summary must be a non-empty string",m(e.summary),"a one-line summary string","Add a summary.",`${t}.summary`),null;if(e.model!==void 0&&typeof e.model!="string")return o("command_node_invalid","branch model must be a string",m(e.model),"a string or omitted","Fix or remove model.",`${t}.model`),null;let s;if(n==="exec"&&e.passthrough!==void 0){const v=ue(e.passthrough,`${t}.passthrough`,o);if(v===null)return null;s=v}if(c&&s!==void 0)return o("command_node_invalid","an extensible branch cannot declare passthrough","extensible and passthrough","one branch mode: extensible or passthrough","Remove passthrough or remove extensible.",t),null;const f=e.children;if(s!==void 0){if(!Array.isArray(f)||f.length!==0)return o("command_node_invalid","a passthrough branch must declare children: []",Array.isArray(f)?`${f.length} children`:m(f),"an empty children array","Set children to [] \u2014 a passthrough branch forwards all argv and can own no subcommands.",`${t}.children`),null}else if(!Array.isArray(f))return o("command_node_invalid","branch children must be an array",m(f),"an array of branch/leaf nodes (may be empty for populated by mounts)","Fix children.",`${t}.children`),null;const b=[],S=new Set;for(let v=0;v<f.length;v++){const _=me(f[v],[...t.split("."),`children[${v}]`],!1,n,o,h);if(_===null)return null;if(S.has(_.name))return o("command_node_invalid","duplicate child name",_.name,"unique child names within a branch","Rename the duplicate child.",`${t}.children[${v}].name`),null;S.add(_.name),b.push(_)}return{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...d!==void 0?{rootEntry:d}:{},...c?{extensible:!0}:{},summary:e.summary,...e.model!==void 0?{model:e.model}:{},...s!==void 0?{passthrough:s}:{},children:b}}E(se,"validateBranch");function ue(e,t,a){if(!x(e))return a("command_node_invalid","passthrough must be an object",m(e),"an object { bin, installHint }","Fix or remove passthrough.",t),null;if(!T(e,Q,t,a))return null;for(const n of["bin","installHint"])if(typeof e[n]!="string"||e[n].length===0)return a("command_node_invalid",`passthrough ${n} must be a non-empty string`,m(e[n]),n==="bin"?"the external binary name":"a one-line install hint",`Set passthrough.${n}.`,`${t}.${n}`),null;return{bin:e.bin,installHint:e.installHint}}E(ue,"validatePassthrough");function ce(e,t,a,n){if(!T(e,a==="exec"?L:ee,t,n)||!B(e,t,n))return null;if(typeof e.summary!="string"||e.summary.length===0)return n("command_node_invalid","leaf summary must be a non-empty string",m(e.summary),"a one-line summary string","Add a summary.",`${t}.summary`),null;if(a==="exec"&&e.outputKind!=="object")return n("command_node_invalid",'leaf outputKind must be "object" in v1',String(e.outputKind),'exactly "object"','Set outputKind to "object".',`${t}.outputKind`),null;if(a==="http"&&e.rest===void 0)return n("command_node_invalid","HTTP leaf requires a rest mapping","(missing)","a rest object with method, path, and params","Add a rest mapping.",`${t}.rest`),null;const o=ie(e.params,`${t}.params`,n);if(o===null)return null;const h=de(e.output,`${t}.output`,n);if(h===null)return null;const p=e.effects;if(!Array.isArray(p)||p.length===0||!p.every(c=>typeof c=="string"))return n("command_node_invalid","leaf effects must be a non-empty string array",m(p),'a non-empty array of strings (["None. Read-only."] for read-only leaves)',"Declare every persistent effect.",`${t}.effects`),null;const r={kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},summary:e.summary,params:o,output:h,effects:p};if(a==="exec")return{...r,outputKind:"object"};const i=e.group;if(i!==void 0&&(typeof i!="string"||!W.test(i)))return n("command_node_invalid","leaf group must match [A-Za-z0-9_-]{1,64}",typeof i=="string"?i:m(i),'a group id of 1-64 letters, digits, "_" or "-" (no ":" or whitespace)',`Rename the group on leaf ${t}.`,`${t}.group`),null;const u=ye(e.rest,t,o,n);return u===null?null:{...r,rest:u,...i!==void 0?{group:i}:{}}}E(ce,"validateLeaf");const fe=new Set(["GET","POST","PUT","PATCH","DELETE"]),pe=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/,he=new Set(["path","query","body","header"]);function ye(e,t,a,n){const o=`${t}.rest`;if(!x(e))return n("command_rest_invalid","rest must be an object",m(e),"{ method, path, streaming?, params }","Provide a valid rest mapping.",o),null;const h=new Set(["method","path","streaming","body","bodyRoot","params"]),p=Object.keys(e).filter(l=>!h.has(l));if(p.length>0)return n("command_rest_invalid","unknown rest keys",p.join(", "),"only: method, path, streaming, body, bodyRoot, params","Remove the unknown keys.",o),null;const r=e.method;if(typeof r!="string"||!fe.has(r))return n("command_rest_invalid","method must be GET|POST|PUT|PATCH|DELETE",String(r),"one of the HTTP methods","Set method to a valid HTTP verb.",`${o}.method`),null;const i=e.path;if(typeof i!="string"||!i.startsWith("/"))return n("command_rest_invalid","path must be an absolute path starting with /",String(i),'an absolute path like "/v1/devices/{id}"',"Fix the path.",`${o}.path`),null;const u=new Set,c=/\{([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\}/g;let d;for(;(d=c.exec(i))!==null;)u.add(d[1]);let s=!1;if(e.streaming!==void 0){if(typeof e.streaming!="boolean")return n("command_rest_invalid","streaming must be a boolean",m(e.streaming),"true | false | omitted","Fix streaming.",`${o}.streaming`),null;s=e.streaming}const f=e.params;if(!x(f))return n("command_rest_invalid","params must be an object",m(f),"a mapping of param names to placement specs","Provide params.",`${o}.params`),null;const b=new Map;for(const l of a)b.set(l.name,l);const S={},v=[],_=new Set;for(const[l,$]of Object.entries(f)){if(!b.has(l))return n("command_rest_invalid","rest params key has no matching declared param",l,"a name from the leaf params array","Remove this rest params entry or declare the param.",`${o}.params.${l}`),null;if(_.add(l),!x($))return n("command_rest_invalid","param mapping must be an object",m($),"{ in, as? }","Fix the param mapping.",`${o}.params.${l}`),null;const k=new Set(["in","as"]),j=Object.keys($).filter(F=>!k.has(F));if(j.length>0)return n("command_rest_invalid","unknown param mapping keys",j.join(", "),"only: in, as","Remove the unknown keys.",`${o}.params.${l}`),null;const y=$.in;if(typeof y!="string"||!he.has(y))return n("command_rest_invalid","param placement must be path|query|body|header",String(y),"path | query | body | header","Set in to a valid placement.",`${o}.params.${l}.in`),null;const g=$.as;if(g!==void 0&&(typeof g!="string"||g.length===0))return n("command_rest_invalid","as must be a non-empty string",String(g),"a non-empty string","Fix the as value.",`${o}.params.${l}.as`),null;if(y==="header"){if(typeof g!="string"||g.length===0)return n("command_rest_invalid","header placement requires a non-empty as",String(g),"a header name","Provide an as value for the header name.",`${o}.params.${l}.as`),null;if(!pe.test(g))return n("command_rest_invalid",'header "as" must be a legal HTTP header field name',g,"an HTTP token (letters, digits, and !#$%&'*+-.^_`|~)","Fix the header name.",`${o}.params.${l}.as`),null;if(g.toLowerCase()==="crtr-request-id")return n("command_rest_invalid","Crtr-Request-Id is assigned by crtr for each leaf call",g,"a header name other than Crtr-Request-Id","Remove this header mapping.",`${o}.params.${l}.as`),null}else if(y==="path"){if(g!==void 0)return n("command_rest_invalid","path placement must not have as","as","no as (placeholder name governs)","Remove as.",`${o}.params.${l}.as`),null;const F=b.get(l);if(F.required!==!0)return n("command_rest_invalid",'in:"path" param must be declared required',`${l}: required ${String(F.required)}`,"required: true",`Set "required": true on the "${l}" param.`,`${o}.params.${l}`),null}const O=b.get(l);if((O.kind==="flag"||O.kind==="positional")&&O.repeatable===!0&&y!=="body")return n("command_rest_invalid","repeatable param must use body placement",`${l}: in "${y}"`,'in: "body" (array values ship as a JSON array)',`Map "${l}" to body placement.`,`${o}.params.${l}.in`),null;if(y==="body"){const I=b.get(l).kind==="stdin",V=g||l;v.push({name:l,isStdin:I,as:V})}S[l]={in:y,...g!==void 0?{as:g}:{}}}for(const l of a)if(!_.has(l.name))return n("command_rest_invalid","declared param not in rest mapping",l.name,"an entry in rest.params","Add a rest mapping for this param.",`${o}.params`),null;const R=new Set;for(const[l,$]of Object.entries(S))$.in==="path"&&R.add(l);if(R.size!==u.size||![...u].every(l=>R.has(l)))return n("command_rest_invalid","mismatch between path placeholders and path params",`placeholders: ${[...u].join(", ")} | params: ${[...R].join(", ")}`,'exact bijection between {placeholders} and in:"path" params',"Ensure every placeholder has a matching path param, and vice versa.",`${o}.path and ${o}.params`),null;if(r==="GET"&&v.length>0)return n("command_rest_invalid","GET method cannot have body params",v.map(l=>l.name).join(", "),"no body placement for GET","Move body params to query, or change the method.",`${o}.method or ${o}.params`),null;let P;if(e.body!==void 0){if(r==="GET")return n("command_rest_invalid","GET method cannot have body constants","body","no body for GET","Remove body, or change the method.",`${o}.body`),null;const l=e.body;if(!x(l))return n("command_rest_invalid","body must be an object",m(l),"a map of constant literal body fields (string | number | boolean)","Fix body.",`${o}.body`),null;const $={};for(const[y,g]of Object.entries(l)){if(typeof g!="string"&&typeof g!="number"&&typeof g!="boolean")return n("command_rest_invalid",`body.${y} must be a string, number, or boolean`,m(g),"string | number | boolean",`Fix body.${y}.`,`${o}.body.${y}`),null;$[y]=g}const k=new Set(v.map(y=>y.as)),j=Object.keys($).filter(y=>k.has(y));if(j.length>0)return n("command_rest_invalid","body constant key collides with a param's body key",j.join(", "),"body constant keys distinct from every param's body key (its as or name)","Rename the colliding body constant or param.",`${o}.body`),null;P=$}let K;if(e.bodyRoot!==void 0){if(r==="GET")return n("command_rest_invalid","GET method cannot have bodyRoot","bodyRoot","no bodyRoot for GET","Remove bodyRoot, or change the method.",`${o}.bodyRoot`),null;const l=e.bodyRoot;if(typeof l!="string"||l.length===0)return n("command_rest_invalid","bodyRoot must be a non-empty string",m(l),"a non-empty string key","Fix bodyRoot.",`${o}.bodyRoot`),null;K=l}for(const[l,$]of Object.entries(S)){const k=b.get(l);if((k.kind==="context-file"||(k.kind==="positional"||k.kind==="flag")&&k.type==="path")&&$.in!=="body"){const y=k.kind==="context-file"?"context-file":`path ${k.kind}`;return n("command_rest_invalid",`file param (${y}) must be placed in body`,`${l}: in "${$.in}"`,'in: "body"',"Move the file param to body.",`${o}.params.${l}.in`),null}}const A=new Map;for(const l of v)A.has(l.as)||A.set(l.as,[]),A.get(l.as).push({name:l.name,isStdin:l.isStdin});for(const[l,$]of A.entries())if($.length>1){const k=$.filter(y=>y.isStdin).length,j=$.filter(y=>!y.isStdin).length;if(!(k===1&&j===1))return n("command_rest_invalid","invalid body as grouping",$.map(y=>y.name).join(", "),"exactly one stdin param and one non-stdin param may share a body as (for coalesced sources)","Fix the body grouping.",`${o}.params (as: "${l}")`),null}return{method:r,path:i,...s!==!1?{streaming:s}:{},...P!==void 0?{body:P}:{},...K!==void 0?{bodyRoot:K}:{},params:S}}E(ye,"validateRestMapping");export{le as mentionsFile,Y as outputBaseType,me as validateCommandNode};
@@ -1,5 +1,5 @@
1
1
  /** How prominently a node surfaces in ancestor `-h` listings. Default 'normal'. */
2
- export type ManifestTier = 'normal' | 'common' | 'important';
2
+ export type ManifestTier = 'hidden' | 'normal' | 'common' | 'important';
3
3
  /** Root-entry prose for a top-level branch — what every agent reads before it
4
4
  * has engaged the command at all. Required on a plugin's top-level branch. */
5
5
  export interface ManifestRootEntry {
@@ -16,8 +16,10 @@ export interface ManifestFieldShape {
16
16
  constraint: string;
17
17
  /** Named members; only when the base type is `object`. */
18
18
  children?: ManifestField[];
19
- /** Element shape; only when the base type is `array`. */
19
+ /** Value shape; only when the base type is `array` or `map`. */
20
20
  items?: ManifestFieldShape;
21
+ /** Alternatives; required when the base type is `union`. */
22
+ variants?: ManifestFieldShape[];
21
23
  /** The allowed values; required, and only valid, when the base type is `enum`. */
22
24
  values?: string[];
23
25
  }
@@ -53,7 +55,9 @@ export interface ManifestFlagParam {
53
55
  /** 'bool' flags take no value — presence is true. 'file' names a file
54
56
  * argument a capability-provider call carries; locally it is sent as the
55
57
  * string typed, never read from disk (that is `path` with `encoding`). */
56
- type: 'string' | 'int' | 'bool' | 'path' | 'enum' | 'file';
58
+ type: 'string' | 'int' | 'bool' | 'path' | 'enum' | 'file' | 'json';
59
+ /** Required only for a json flag. */
60
+ shape?: ManifestFieldShape;
57
61
  /** Required, and only valid, when type is 'enum'. */
58
62
  choices?: string[];
59
63
  required: boolean;
@@ -63,7 +63,9 @@ A definition with `version: N`, or a list of definitions with one per supported
63
63
  - removes a parameter, changes its type, makes it required, or adds a required one; removes an enum choice from a flag; or changes the environment variable it defaults from; or moves an existing positional;
64
64
  - gains paging where it returned everything: a limit flag whose constraint states a default page size ("default N"), or a cursor input together with a `next_cursor`-style output. The answer keeps its shape, so callers that do not page silently get a partial list. A changed stated page size is breaking too. A new `--cursor`, `--limit` with no stated default, or `next_cursor` output alone is additive, and so is a declared `default`, which crtr applies while parsing and does not send;
65
65
  - changes how the request is built: method, route (apart from the `/v<N>` segment), body root, constant body fields, a parameter's placement or wire name, or streaming;
66
- - removes an output field, changes its type, makes it optional, drops an enum value, or adds a `file` output.
66
+ - removes an output field, changes its type, makes it optional, or adds a `file` output;
67
+ - widens an output so a client typed against the previous archive could read a value outside its type: adds an enum value or a union variant, adds `| null`, turns `int` into `number`, replaces a declared type with `unknown`, or opens a closed object by dropping its `children`;
68
+ - narrows a `json` flag's shape so a value the previous archive accepted is refused: removes an enum value or a union variant, adds a required member, or closes an open object by adding `children`.
67
69
 
68
70
  A new optional flag, a new leaf, a new optional output field and changed prose are additive.
69
71
 
@@ -27,7 +27,7 @@ Each leaf declares an `output` object. Field object keys are returned verbatim i
27
27
 
28
28
  Fields are required by default. Pass `{ required: false }` for a field that can be absent. `field.nullable(field.string('...'))` declares a present field that can be `string | null`; it does not make the field optional.
29
29
 
30
- The handler result type is inferred from `output`, including structured members and elements. TypeScript reports a missing required field or a wrong value type at the `defineLeaf` call. `definePlugin` throws `PluginDefinitionError` for a malformed structured field, a file field that is not top-level, or a second file output. The Fetch handler checks the returned object's top-level fields again before sending it. It rejects missing fields, known type mismatches, and keys that were not declared, returning a non-2xx `handler_output_invalid` error instead of a JSON success response. It does not look inside objects or arrays, so their members are checked only by TypeScript.
30
+ The handler result type is inferred from `output`, including structured members and elements. TypeScript reports a missing required field or a wrong value type at the `defineLeaf` call. `definePlugin` throws `PluginDefinitionError` for a malformed structured field, a file field that is not top-level, or a second file output. The Fetch handler checks the returned object's top-level fields again before sending it. It rejects missing fields, known type mismatches, and keys that were not declared, returning a non-2xx `handler_output_invalid` error instead of a JSON success response. It also checks nested values against the declared shape: an object with declared `children` is closed, so a member it does not declare, a missing required member, an array element or map value of the wrong shape, or an enum value outside `values` is rejected. An `object` declared without `children` accepts any members.
31
31
 
32
32
  ## Structured fields
33
33
 
@@ -38,7 +38,7 @@ export interface CommandNode {
38
38
  /** The selection rubric — when to reach for this command. */
39
39
  whenToUse: string;
40
40
  summary: string;
41
- tier?: "normal" | "common" | "important";
41
+ tier?: "hidden" | "normal" | "common" | "important";
42
42
  /** Empty for a leaf and for a passthrough branch. */
43
43
  children: CommandNode[];
44
44
  /** Branch only: forwards every argv token to an external binary instead of
@@ -12,7 +12,7 @@ ${t.map(i=>`- ${i}`).join(`
12
12
  ${c.join(`
13
13
  `)}`,{issues:l,next:`Correct the package's memory documents, then retry \u2014 "${e.name}" was not activated.`})}return{docs:a.length,...o==="linked"?{linkedSource:Je(r)}:{}}}d(An,"migratePluginMemory");async function P(e,n,t,i,o,r,a="package"){try{en(e)}catch(m){const h=m instanceof Error?m.message:String(m);throw f(`plugin manifest at ${o} declares invalid memory_extensions: ${h}`)}if(xn(e,o),Tn(e,o),Sn(e,o),En(e,o),e.env!==void 0){const m=Me(e.env);if(m.length)throw f(`plugin manifest at ${o} declares an invalid env block:
14
14
  ${m.map(h=>`- ${h}`).join(`
15
- `)}`,{issues:m})}const[l,c]=await Promise.all([I(),N()]),s=Q({name:e.name,scope:t,root:n,manifest:e,enabled:!0},new Set(D),l,c);if(s.issues.length>0)throw W(`plugin command behavior at ${o} is invalid`,s.issues);const u=s.manifest?.roots??[],y=wn(e,{name:e.name,leafPaths:kn(e.name,u),streamingLeafPaths:bn(e.name,u)});if(y.length>0)throw f(`plugin manifest at ${o} declares an invalid object_kinds or scopes block:
15
+ `)}`,{issues:m})}const[l,c]=await Promise.all([I(),N()]),s=Q({name:e.name,scope:t,root:n,manifest:e,enabled:!0},new Set(D),l,c);if(s.issues.length>0)throw W(`plugin command behavior at ${o} is invalid`,s.issues);const u=s.manifest?.roots??[],y=wn(e,{name:e.name,leafPaths:kn(e.name,e,u),streamingLeafPaths:bn(e.name,e,u)});if(y.length>0)throw f(`plugin manifest at ${o} declares an invalid object_kinds or scopes block:
16
16
  ${y.map(m=>`- ${m}`).join(`
17
17
  `)}`,{issues:y});await qn(e,n,t,i,o,r);const p=v(n,"memory"),k=Xe(t,i,{candidate:e});for(const m of Ye(p,h=>h.endsWith(".md"),h=>h.startsWith("."))){let h;try{h=on(Ge(m)).data}catch(R){throw f(`plugin memory document ${L(n,m)} has invalid YAML frontmatter: ${R instanceof Error?R.message.split(`
18
18
  `)[0]:String(R)}`)}const w=nn(h);if(w!==null)throw f(`plugin memory document ${L(n,m)} is invalid: ${w}`);for(const R of Ze(h?.extensions,k))throw f(`plugin memory document ${L(n,m)} is invalid: ${R.path}: ${R.message}`)}return An(e,n,t,o,a)}d(P,"assertPluginCandidateValid");function Mn(e){return{kind:"http",endpoint:new URL(e.endpoint).origin,...e.authEnv!==void 0?{authEnv:e.authEnv}:{}}}d(Mn,"invocationTransport");async function _n(e,n,t,i,o){const r=await P(o,t,n,i,`${t}/.crouter-plugin/plugin.json`),a={name:e,scope:n,root:t,manifest:o,enabled:!0},[l,c]=await Promise.all([I(),N()]),s=Q(a,new Set(D),l,c);if(s.issues.length>0)throw W(`bundle staged package for "${e}" is invalid`,s.issues);return r}d(_n,"validateStagedBundle");function ye(e){return e.sourceMarketplace!==void 0||Y(e.root)||O(e.root)}d(ye,"isSourceInstalled");function ke(e){throw f(`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>\`.`})}d(ke,"rejectLegacyHttpPlugin");function Ln(e,n,t){if(!$(t))return;const i=C(e,n);if(i?.manifest.bundle!==void 0)return i;throw i?.manifest.transport?.kind==="http"&&i!==void 0&&!ye(i)&&ke(e),f(`plugin "${e}" already exists at ${t}; run \`crtr pkg plugin remove ${e}\` before installing the archive plugin.`)}d(Ln,"existingBundleRoot");function we(e,n,t,i){const o=ge(e,n.endpoint,n.authEnv),r={endpoint:o.endpoint,...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},a=v(A(t),e);return Ln(e,t,a),{bundle:r,transport:Mn(r),scopeRootPath:i,root:a}}d(we,"resolveBundleTarget");function be(e,n){return{name:e,endpoint:n.endpoint,...n.authEnv!==void 0?{authEnv:n.authEnv}:{}}}d(be,"fetchTarget");const Bn=6e4,Hn=1e4;async function $e(e,n,t,i){const o=n==="project"?z():S();return _(v(o,"tmp")),Ke(ae(o,e),()=>i(o),{timeoutMs:t,timeoutError:d(()=>f(`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 ${ae(o,e)}.`}),"timeoutError")})}d($e,"withBundleSwapLock");async function je(e,n,t,i){return $e(e,t,Bn,async o=>{const r=we(e,n,t,o),a=await pe(be(e,r.bundle));if(a.status!==200){const l=`HTTP plugin bundle fetch failed for "${e}": ${a.message}`;throw a.status==="auth_env_missing"?b(l):a.status==="cli_unreachable"?He(l):f(l)}return Pe(e,t,r,a.raw,a.etag,i)})}d(je,"replaceBundlePlugin");async function Rt(e,n,t,i){return $e(e,t,Hn,async o=>{try{const r=we(e,n,t,o),a=i.conditional?Ne(t).plugins[e]?.etag:void 0,l={...a!==void 0?{ifNoneMatch:a}:{},...i.timeoutMs!==void 0?{timeoutMs:i.timeoutMs}:{}},c=await pe(be(e,r.bundle),l);if(c.status===304)return;if(c.status!==200)throw f(`HTTP plugin bundle revalidation failed for "${e}": ${c.message}`);const s=un(c.raw);if(fn(e,t,r.root,s)){mn(t,e,c.etag,{swapped:!1});return}return await Pe(e,t,r,c.raw,c.etag,i,s)}finally{Dn(t,e)}})}d(Rt,"revalidateBundlePlugin");function Dn(e,n){V(e,t=>{t.plugins[n]===void 0&&(t.plugins[n]={}),t.plugins[n].checked_at=J()})}d(Dn,"recordPluginCheck");async function Pe(e,n,t,i,o,r,a){const{bundle:l,transport:c,scopeRootPath:s,root:u}=t,y=await In(e,i,{scope:n,scopeRootPath:s,root:u,bundle:l,transport:c,etag:o,enable:r.enable,...a!==void 0?{version:a}:{}}),{validated:p,version:k,staged:m}=y,h=await X(e,n);if(h===void 0)throw f(`bundle plugin "${e}" has no staged command report`);const w=await Z(e,n);return{name:e,scope:n,path:u,transport:"http",version:k,docs:p.memory.length,kinds:Object.keys(p.kinds??{}).length,pageComponents:(p.page_components??[]).length,objectKinds:Object.keys(p.object_kinds??{}).length,scopes:(p.scopes??[]).length,commands:h,...w!==void 0?{hooks:w}:{},...m!==void 0?{memory:m}:{}}}d(Pe,"applyBundleArchive");async function xt(e){const[n,t]=await Promise.all([I(),N()]);return hn(e,{reservedCoreNames:new Set(D),coreCommandPaths:n,extensibleCoreBranches:t})}d(xt,"validatePluginArchive");async function In(e,n,t){const[i,o]=await Promise.all([I(),N()]);return pn(e,n,{...t,validation:{reservedCoreNames:new Set(D),coreCommandPaths:i,extensibleCoreBranches:o},validateStaged:d((r,a)=>_n(e,t.scope,r,t.scopeRootPath,a),"validateStaged"),invalid:d((r,a)=>W(r,a),"invalid")})}d(In,"installPluginArchive");async function Nn(e,n,t,i){const o=ge(e,n,t);return je(e,{endpoint:o.endpoint,...o.authEnv!==void 0?{authEnv:o.authEnv}:{}},i,{enable:!0})}d(Nn,"installHttpPlugin");function Un(e){if(e.startsWith("./")||e.startsWith("../")||e.startsWith("/"))return{kind:"local",value:e};if(me(e))return{kind:"git",value:e};const n=e.split("/");if(n.length===2&&n[0]!==""&&n[1]!=="")return{kind:"marketplace",value:{marketplace:n[0],plugin:n[1]}};throw b(`invalid plugin install ref: ${e}
@@ -1 +1 @@
1
- var R=Object.defineProperty;var f=(e,t)=>R(e,"name",{value:t,configurable:!0});import{spawn as T}from"node:child_process";import{socketFetch as A}from"../../../api/node-transport.js";import{apiSocketPath as C}from"../../canvas/paths.js";import{posixProcessGroupSpawnOptions as j,terminateProcessGroup as D}from"../../../hook-process.js";import{ExitCode as S}from"../../../types.js";import{isRecord as m}from"../../../shared/predicates.js";import{envExecutionId as G,envNodeId as K}from"../../../shared/env.js";import{validateCoreDeclaredResult as M}from"../../../api/command-manifest/result.js";import{CrtrError as b}from"../../errors.js";import{diag as I}from"../../io.js";const q=10*1024*1024,V=15e3,B=new Set(["internal","unknown_path","command_collision","command_hook_collision","plugin_protocol_error","hook_protocol_error"]),H=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,L=/^[0-9a-f]{32}$/,$="Run `crtr pkg plugin show` or `crtr sys doctor` to inspect the plugin.";async function ie(e,t){const u=process.cwd(),o=F(e,t,u),r=await z(e,t,JSON.stringify(o),u);if(r.signal!==null)throw n(e,t,`executable was killed by signal ${r.signal}`,"a clean exit with one JSON envelope on stdout");const s=U(e,t,r.stdout);if(s.ok&&r.status!==0?I(`crtr: ${k(e)} returned ok:true but exited ${r.status} (honoring the envelope)`):!s.ok&&r.status===0&&I(`crtr: ${k(e)} returned ok:false but exited 0 (honoring the envelope)`),s.ok||X(e,t,s),e.phase!=="replace")return;const l=s.result;if(!m(l))throw n(e,t,"replacement success result was not an object","result: an object matching the target output declaration");const h=e.output;if(h===void 0)throw n(e,t,"replacement target output declaration was missing","the target leaf output declaration");const d=M(h,l);if(d!==null){const i=h.find(a=>a.name===d.field);throw d.receivedType!==void 0?n(e,t,`replacement output field "${d.field}" has the wrong type`,`${d.field}: ${i?.type??"unknown"}`):n(e,t,`replacement result is missing required output field "${d.field}"`,`a "${d.field}" field of type ${i?.type??"unknown"}`)}return l}f(ie,"invokeExecHook");async function z(e,t,u,o){if(process.platform==="linux"){const s=K(),l=G(),h=process.env.CRTR_API_SOCKET??C();if(s&&!l)throw n(e,t,"executable requires a current node execution","a current node execution");let d;try{const i=s?`/v1/nodes/${encodeURIComponent(s)}/hooks/exec`:"/v1/hooks/exec";d=await A(h)(`http://localhost${i}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({...s?{expected_execution_id:l}:{},hook_id:e.id,cwd:o,request:u})});const a=await d.json();if(!d.ok){const c=m(a)&&m(a.error)?a.error:null,g=c&&typeof c.message=="string"?c.message:`daemon refused hook execution (${d.status})`;throw c&&(c.code==="hook_timeout"||c.code==="hook_output_limit")?new b(c.code,`${k(e)}: ${g}`,S.GENERAL,N(e,t,void 0,$,g)):new Error(g)}if(!m(a)||typeof a.stdout!="string"||a.status!==null&&typeof a.status!="number"||a.signal!==null&&typeof a.signal!="string")throw new Error("invalid hook execution response");return{stdout:a.stdout,status:a.status,signal:a.signal}}catch(i){throw i instanceof b?i:n(e,t,`executable could not be run (${E(i)})`,"a registered runnable hook executable")}}let r;try{r=T(e.executable,["--crtr-hook-protocol","1"],{cwd:o,env:process.env,stdio:["pipe","pipe","inherit"],...j()})}catch(s){throw n(e,t,`executable could not be run (${E(s)})`,"a runnable hook executable")}return new Promise((s,l)=>{let h=0;const d=[];let i,a=!1,c=!1,g=null,P=null,w;const x=f(()=>{if(!(!a||i!==void 0&&!c)){if(w!==void 0&&clearTimeout(w),i?.kind==="timeout"){l(n(e,t,`executable timed out after ${V}ms`,"one JSON envelope before the execution timeout"));return}if(i?.kind==="output_limit"){l(n(e,t,"stdout exceeded the 10 MiB cap","one JSON envelope under 10 MiB on stdout"));return}if(i?.kind==="spawn"){l(n(e,t,`executable could not be run (${E(i.error)})`,"a runnable hook executable"));return}if(i?.kind==="cleanup"){l(n(e,t,`executable cleanup failed (${E(i.error)})`,"a hook executable whose process group can be terminated"));return}s({stdout:Buffer.concat(d).toString("utf8"),status:g,signal:P})}},"finish"),_=f(p=>{i===void 0&&(i=p,w!==void 0&&clearTimeout(w),D(r).then(()=>{c=!0,x()},O=>{i={kind:"cleanup",error:O},c=!0,x()}))},"terminate");r.stdout.on("data",p=>{if(h+=p.byteLength,h>q){_({kind:"output_limit"});return}d.push(p)}),r.once("error",p=>{if(r.pid===void 0){i={kind:"spawn",error:p},a=!0,c=!0,x();return}_({kind:"spawn",error:p})}),r.once("close",(p,O)=>{g=p,P=O,a=!0,x()}),w=setTimeout(()=>_({kind:"timeout"}),V),r.stdin.end(u)})}f(z,"runHookProcess");function F(e,t,u){if(!L.test(t.operationId))throw n(e,t,"operationId was not a 32-character lowercase hexadecimal string","a valid crouter operation identity");if(!y(t.input))throw n(e,t,"input was not JSON-compatible","a JSON object");if(!t.providedParams.every(r=>typeof r=="string"))throw n(e,t,"providedParams contained a non-string value","an array of parameter names");const o={protocolVersion:1,op:e.op,command:[...e.commandPath],operationId:t.operationId,input:t.input,context:{cwd:u,providedParams:[...t.providedParams]}};if(e.phase==="after"){if(t.result===void 0||!y(t.result))throw n(e,t,"after hook result was missing or not JSON-compatible","the completed primary result");return{...o,phase:"after",result:t.result}}if(t.result!==void 0)throw n(e,t,`${e.phase} hook invocation included a result`,"result only for after hooks");return e.phase==="before"?{...o,phase:"before"}:{...o,phase:"replace"}}f(F,"buildRequest");function U(e,t,u){let o;try{o=JSON.parse(u.trim())}catch{throw n(e,t,"stdout was not a single JSON envelope","exactly one JSON envelope on stdout")}if(!m(o))throw n(e,t,"stdout was not a JSON object","a { protocolVersion: 1, ok: boolean, ... } envelope");if(o.protocolVersion!==1)throw n(e,t,`unsupported protocolVersion ${String(o.protocolVersion)}`,"protocolVersion exactly 1");if(o.ok===!0){const s=e.phase==="replace"?["protocolVersion","ok","result"]:["protocolVersion","ok"];if(!J(o,s))throw n(e,t,"success envelope had missing or extra fields",`{ ${s.join(", ")} } only`);if(e.phase==="replace"){if(!y(o.result))throw n(e,t,"replacement result was not JSON-compatible","a JSON result");return{protocolVersion:1,ok:!0,result:o.result}}return{protocolVersion:1,ok:!0}}if(o.ok!==!1||!J(o,["protocolVersion","ok","error"]))throw n(e,t,"failure envelope had missing or extra fields","{ protocolVersion, ok, error } only");const r=o.error;if(!m(r)||!J(r,["code","message"],["field","next"]))throw n(e,t,"error envelope was missing fields or contained extra fields","error: { code, message, field?, next? }");if(typeof r.code!="string"||r.code===""||typeof r.message!="string"||r.message==="")throw n(e,t,"error envelope had an empty or non-string code/message","non-empty error code and message strings");if(r.field!==void 0&&typeof r.field!="string"||r.next!==void 0&&typeof r.next!="string")throw n(e,t,"error envelope had a non-string field/next","string field and next values when present");return{protocolVersion:1,ok:!1,error:{code:r.code,message:r.message,...r.field===void 0?{}:{field:r.field},...r.next===void 0?{}:{next:r.next}}}}f(U,"parseEnvelope");function X(e,t,u){const{code:o,message:r,field:s,next:l}=u.error;throw!H.test(o)||B.has(o)?n(e,t,`error code "${o}" was invalid or reserved`,"a non-reserved lowercase snake_case code"):new b(o,`${k(e)}: ${r}`,S.GENERAL,N(e,t,s,l))}f(X,"throwHookFailure");function n(e,t,u,o){return new b("hook_protocol_error",`${k(e)}: ${u}. Expected ${o}.`,S.GENERAL,N(e,t,void 0,$,u))}f(n,"protocolError");function N(e,t,u,o,r){const s={plugin:e.plugin,op:e.op,phase:e.phase,command:[...e.commandPath],...r===void 0?{}:{received:r},...u===void 0?{}:{field:u}};if(e.phase!=="after")return{...s,next:o??$};const l="Do not retry the original command; its primary action completed. Inspect and reconcile the failed after hook.";return{...s,primaryCompleted:!0,result:t.result,next:o===void 0||o===$?l:`${o} ${l}`}}f(N,"failureDetails");function k(e){return`plugin "${e.plugin}" ${e.phase} hook "${e.op}" for \`${e.commandPath.join(" ")}\``}f(k,"label");function J(e,t,u=[]){const o=Object.keys(e);return t.every(r=>Object.hasOwn(e,r))&&o.every(r=>t.includes(r)||u.includes(r))}f(J,"hasExactKeys");function y(e){return e===null||typeof e=="boolean"||typeof e=="string"?!0:typeof e=="number"?Number.isFinite(e):Array.isArray(e)?e.every(y):m(e)&&Object.values(e).every(y)}f(y,"isJson");function E(e){return e instanceof Error?e.message:String(e)}f(E,"describeError");export{V as EXEC_HOOK_TIMEOUT_MS,ie as invokeExecHook};
1
+ var R=Object.defineProperty;var f=(e,t)=>R(e,"name",{value:t,configurable:!0});import{spawn as T}from"node:child_process";import{socketFetch as A}from"../../../api/node-transport.js";import{apiSocketPath as j}from"../../canvas/paths.js";import{posixProcessGroupSpawnOptions as C,terminateProcessGroup as D}from"../../../hook-process.js";import{ExitCode as S}from"../../../types.js";import{isRecord as m}from"../../../shared/predicates.js";import{envExecutionId as G,envNodeId as K}from"../../../shared/env.js";import{validateDeclaredResult as M}from"../../../api/command-manifest/result.js";import{CrtrError as b}from"../../errors.js";import{diag as I}from"../../io.js";const q=10*1024*1024,V=15e3,B=new Set(["internal","unknown_path","command_collision","command_hook_collision","plugin_protocol_error","hook_protocol_error"]),H=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,L=/^[0-9a-f]{32}$/,$="Run `crtr pkg plugin show` or `crtr sys doctor` to inspect the plugin.";async function ie(e,t){const u=process.cwd(),o=F(e,t,u),r=await z(e,t,JSON.stringify(o),u);if(r.signal!==null)throw n(e,t,`executable was killed by signal ${r.signal}`,"a clean exit with one JSON envelope on stdout");const s=U(e,t,r.stdout);if(s.ok&&r.status!==0?I(`crtr: ${k(e)} returned ok:true but exited ${r.status} (honoring the envelope)`):!s.ok&&r.status===0&&I(`crtr: ${k(e)} returned ok:false but exited 0 (honoring the envelope)`),s.ok||X(e,t,s),e.phase!=="replace")return;const l=s.result;if(!m(l))throw n(e,t,"replacement success result was not an object","result: an object matching the target output declaration");const h=e.output;if(h===void 0)throw n(e,t,"replacement target output declaration was missing","the target leaf output declaration");const d=M(h,l);if(d!==null){const i=h.find(a=>a.name===d.field);throw d.receivedType!==void 0?n(e,t,`replacement output field "${d.field}" has the wrong type`,`${d.field}: ${d.expected??i?.type??"unknown"}`):n(e,t,`replacement result is missing required output field "${d.field}"`,`a "${d.field}" field of type ${i?.type??"unknown"}`)}return l}f(ie,"invokeExecHook");async function z(e,t,u,o){if(process.platform==="linux"){const s=K(),l=G(),h=process.env.CRTR_API_SOCKET??j();if(s&&!l)throw n(e,t,"executable requires a current node execution","a current node execution");let d;try{const i=s?`/v1/nodes/${encodeURIComponent(s)}/hooks/exec`:"/v1/hooks/exec";d=await A(h)(`http://localhost${i}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({...s?{expected_execution_id:l}:{},hook_id:e.id,cwd:o,request:u})});const a=await d.json();if(!d.ok){const c=m(a)&&m(a.error)?a.error:null,g=c&&typeof c.message=="string"?c.message:`daemon refused hook execution (${d.status})`;throw c&&(c.code==="hook_timeout"||c.code==="hook_output_limit")?new b(c.code,`${k(e)}: ${g}`,S.GENERAL,N(e,t,void 0,$,g)):new Error(g)}if(!m(a)||typeof a.stdout!="string"||a.status!==null&&typeof a.status!="number"||a.signal!==null&&typeof a.signal!="string")throw new Error("invalid hook execution response");return{stdout:a.stdout,status:a.status,signal:a.signal}}catch(i){throw i instanceof b?i:n(e,t,`executable could not be run (${E(i)})`,"a registered runnable hook executable")}}let r;try{r=T(e.executable,["--crtr-hook-protocol","1"],{cwd:o,env:process.env,stdio:["pipe","pipe","inherit"],...C()})}catch(s){throw n(e,t,`executable could not be run (${E(s)})`,"a runnable hook executable")}return new Promise((s,l)=>{let h=0;const d=[];let i,a=!1,c=!1,g=null,P=null,w;const x=f(()=>{if(!(!a||i!==void 0&&!c)){if(w!==void 0&&clearTimeout(w),i?.kind==="timeout"){l(n(e,t,`executable timed out after ${V}ms`,"one JSON envelope before the execution timeout"));return}if(i?.kind==="output_limit"){l(n(e,t,"stdout exceeded the 10 MiB cap","one JSON envelope under 10 MiB on stdout"));return}if(i?.kind==="spawn"){l(n(e,t,`executable could not be run (${E(i.error)})`,"a runnable hook executable"));return}if(i?.kind==="cleanup"){l(n(e,t,`executable cleanup failed (${E(i.error)})`,"a hook executable whose process group can be terminated"));return}s({stdout:Buffer.concat(d).toString("utf8"),status:g,signal:P})}},"finish"),_=f(p=>{i===void 0&&(i=p,w!==void 0&&clearTimeout(w),D(r).then(()=>{c=!0,x()},O=>{i={kind:"cleanup",error:O},c=!0,x()}))},"terminate");r.stdout.on("data",p=>{if(h+=p.byteLength,h>q){_({kind:"output_limit"});return}d.push(p)}),r.once("error",p=>{if(r.pid===void 0){i={kind:"spawn",error:p},a=!0,c=!0,x();return}_({kind:"spawn",error:p})}),r.once("close",(p,O)=>{g=p,P=O,a=!0,x()}),w=setTimeout(()=>_({kind:"timeout"}),V),r.stdin.end(u)})}f(z,"runHookProcess");function F(e,t,u){if(!L.test(t.operationId))throw n(e,t,"operationId was not a 32-character lowercase hexadecimal string","a valid crouter operation identity");if(!y(t.input))throw n(e,t,"input was not JSON-compatible","a JSON object");if(!t.providedParams.every(r=>typeof r=="string"))throw n(e,t,"providedParams contained a non-string value","an array of parameter names");const o={protocolVersion:1,op:e.op,command:[...e.commandPath],operationId:t.operationId,input:t.input,context:{cwd:u,providedParams:[...t.providedParams]}};if(e.phase==="after"){if(t.result===void 0||!y(t.result))throw n(e,t,"after hook result was missing or not JSON-compatible","the completed primary result");return{...o,phase:"after",result:t.result}}if(t.result!==void 0)throw n(e,t,`${e.phase} hook invocation included a result`,"result only for after hooks");return e.phase==="before"?{...o,phase:"before"}:{...o,phase:"replace"}}f(F,"buildRequest");function U(e,t,u){let o;try{o=JSON.parse(u.trim())}catch{throw n(e,t,"stdout was not a single JSON envelope","exactly one JSON envelope on stdout")}if(!m(o))throw n(e,t,"stdout was not a JSON object","a { protocolVersion: 1, ok: boolean, ... } envelope");if(o.protocolVersion!==1)throw n(e,t,`unsupported protocolVersion ${String(o.protocolVersion)}`,"protocolVersion exactly 1");if(o.ok===!0){const s=e.phase==="replace"?["protocolVersion","ok","result"]:["protocolVersion","ok"];if(!J(o,s))throw n(e,t,"success envelope had missing or extra fields",`{ ${s.join(", ")} } only`);if(e.phase==="replace"){if(!y(o.result))throw n(e,t,"replacement result was not JSON-compatible","a JSON result");return{protocolVersion:1,ok:!0,result:o.result}}return{protocolVersion:1,ok:!0}}if(o.ok!==!1||!J(o,["protocolVersion","ok","error"]))throw n(e,t,"failure envelope had missing or extra fields","{ protocolVersion, ok, error } only");const r=o.error;if(!m(r)||!J(r,["code","message"],["field","next"]))throw n(e,t,"error envelope was missing fields or contained extra fields","error: { code, message, field?, next? }");if(typeof r.code!="string"||r.code===""||typeof r.message!="string"||r.message==="")throw n(e,t,"error envelope had an empty or non-string code/message","non-empty error code and message strings");if(r.field!==void 0&&typeof r.field!="string"||r.next!==void 0&&typeof r.next!="string")throw n(e,t,"error envelope had a non-string field/next","string field and next values when present");return{protocolVersion:1,ok:!1,error:{code:r.code,message:r.message,...r.field===void 0?{}:{field:r.field},...r.next===void 0?{}:{next:r.next}}}}f(U,"parseEnvelope");function X(e,t,u){const{code:o,message:r,field:s,next:l}=u.error;throw!H.test(o)||B.has(o)?n(e,t,`error code "${o}" was invalid or reserved`,"a non-reserved lowercase snake_case code"):new b(o,`${k(e)}: ${r}`,S.GENERAL,N(e,t,s,l))}f(X,"throwHookFailure");function n(e,t,u,o){return new b("hook_protocol_error",`${k(e)}: ${u}. Expected ${o}.`,S.GENERAL,N(e,t,void 0,$,u))}f(n,"protocolError");function N(e,t,u,o,r){const s={plugin:e.plugin,op:e.op,phase:e.phase,command:[...e.commandPath],...r===void 0?{}:{received:r},...u===void 0?{}:{field:u}};if(e.phase!=="after")return{...s,next:o??$};const l="Do not retry the original command; its primary action completed. Inspect and reconcile the failed after hook.";return{...s,primaryCompleted:!0,result:t.result,next:o===void 0||o===$?l:`${o} ${l}`}}f(N,"failureDetails");function k(e){return`plugin "${e.plugin}" ${e.phase} hook "${e.op}" for \`${e.commandPath.join(" ")}\``}f(k,"label");function J(e,t,u=[]){const o=Object.keys(e);return t.every(r=>Object.hasOwn(e,r))&&o.every(r=>t.includes(r)||u.includes(r))}f(J,"hasExactKeys");function y(e){return e===null||typeof e=="boolean"||typeof e=="string"?!0:typeof e=="number"?Number.isFinite(e):Array.isArray(e)?e.every(y):m(e)&&Object.values(e).every(y)}f(y,"isJson");function E(e){return e instanceof Error?e.message:String(e)}f(E,"describeError");export{V as EXEC_HOOK_TIMEOUT_MS,ie as invokeExecHook};
@@ -1 +1 @@
1
- var x=Object.defineProperty;var u=(e,r)=>x(e,"name",{value:r,configurable:!0});import{spawnSync as v}from"node:child_process";import{validateDeclaredResult as $}from"../../../api/command-manifest/result.js";import{CrtrError as f}from"../../errors.js";import{diag as m}from"../../io.js";import{ExitCode as g}from"../../../types.js";import{isRecord as c}from"../../../shared/predicates.js";const E=10*1024*1024,h=new Set(["internal","unknown_path","command_collision","plugin_protocol_error"]),b=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,w="Run `crtr pkg plugin show` or `crtr sys doctor` to inspect the plugin.";function S(e,r,t){return{protocolVersion:1,command:e.commandPath,input:r,context:{cwd:t,...e.plugin!==void 0?{plugin:{name:e.plugin.name,version:e.plugin.version??e.plugin.manifest.version??null,scope:e.plugin.scope,root:e.plugin.root}}:{extension:e.extension}}}}u(S,"buildLeafRequest");function D(e,r){const t=S(e,r,process.cwd()),n=v(e.executable,["--crtr-command-protocol","1"],{input:JSON.stringify(t),cwd:process.cwd(),env:{...process.env,...e.appEnv},maxBuffer:E,stdio:["pipe","pipe","inherit"],encoding:"utf8"});if(n.error!==void 0){const o=n.error;throw o.code==="ENOBUFS"||o.code==="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"?i(e,"stdout exceeded the 10 MiB cap","one JSON envelope under 10 MiB on stdout"):i(e,`executable could not be run (${o.code??o.message})`,"a runnable command executable")}return y(e,n.stdout??"",n.status,n.signal)}u(D,"executeExternalLeaf");function y(e,r,t,n){if(n!==null)throw i(e,`executable was killed by signal ${n}`,"a clean exit with one JSON envelope on stdout");let o;try{o=JSON.parse(r.trim())}catch{throw i(e,"stdout was not a single JSON envelope","exactly one JSON envelope on stdout")}if(!c(o))throw i(e,"stdout was not a JSON object","a { protocolVersion: 1, ok: boolean, ... } envelope");if(o.protocolVersion!==1)throw i(e,`unsupported protocolVersion ${String(o.protocolVersion)}`,"protocolVersion exactly 1");if(typeof o.ok!="boolean")throw i(e,'envelope is missing a boolean "ok"',"a boolean ok field");const a=o.ok;if(a&&t!==0?m(`crtr: ${p(e)} returned ok:true but exited ${t} (honoring the envelope)`):!a&&t===0&&m(`crtr: ${p(e)} returned ok:false but exited 0 (honoring the envelope)`),a){const s=o.result;if(!c(s))throw i(e,"success envelope has no result object","result: an object matching the declared output");return k(e,s),s}const l=o.error;if(!c(l)||typeof l.code!="string"||typeof l.message!="string")throw i(e,"error envelope is missing a { code, message }","error: { code, message, ... }");const d=l.code;throw!b.test(d)||h.has(d)?i(e,`error code "${d}" is invalid or reserved`,`a lowercase snake_case code not in {${[...h].join(", ")}}`):new f(d,l.message,g.GENERAL,{received:l.received,field:typeof l.field=="string"?l.field:void 0,next:typeof l.next=="string"?l.next:w})}u(y,"parseLeafEnvelope");function k(e,r){const t=$(e.output,r);if(t===null)return;const n=t.field,o=e.output.find(a=>a.name===n);throw t.receivedType!==void 0?i(e,`output field "${n}" has the wrong type`,`${n}: ${o?.type??"unknown"}`):i(e,`result is missing required output field "${n}"`,`a "${n}" field of type ${o?.type??"unknown"}`)}u(k,"validateResult");function i(e,r,t){return new f("plugin_protocol_error",`${p(e)}: ${r}. Expected ${t}.`,g.GENERAL,{received:r,next:w})}u(i,"protocolError");function p(e){return e.plugin!==void 0?`plugin "${e.plugin.name}" command \`${e.commandPath.join(" ")}\``:`extension branch "${e.extension.branch}" command \`${e.commandPath.join(" ")}\``}u(p,"label");export{E as MAX_STDOUT,S as buildLeafRequest,D as executeExternalLeaf,p as label,y as parseLeafEnvelope,i as protocolError};
1
+ var w=Object.defineProperty;var u=(e,r)=>w(e,"name",{value:r,configurable:!0});import{spawnSync as v}from"node:child_process";import{validateDeclaredResult as $}from"../../../api/command-manifest/result.js";import{CrtrError as f}from"../../errors.js";import{diag as m}from"../../io.js";import{ExitCode as g}from"../../../types.js";import{isRecord as c}from"../../../shared/predicates.js";const E=10*1024*1024,h=new Set(["internal","unknown_path","command_collision","plugin_protocol_error"]),b=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,x="Run `crtr pkg plugin show` or `crtr sys doctor` to inspect the plugin.";function S(e,r,n){return{protocolVersion:1,command:e.commandPath,input:r,context:{cwd:n,...e.plugin!==void 0?{plugin:{name:e.plugin.name,version:e.plugin.version??e.plugin.manifest.version??null,scope:e.plugin.scope,root:e.plugin.root}}:{extension:e.extension}}}}u(S,"buildLeafRequest");function D(e,r){const n=S(e,r,process.cwd()),t=v(e.executable,["--crtr-command-protocol","1"],{input:JSON.stringify(n),cwd:process.cwd(),env:{...process.env,...e.appEnv},maxBuffer:E,stdio:["pipe","pipe","inherit"],encoding:"utf8"});if(t.error!==void 0){const o=t.error;throw o.code==="ENOBUFS"||o.code==="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"?i(e,"stdout exceeded the 10 MiB cap","one JSON envelope under 10 MiB on stdout"):i(e,`executable could not be run (${o.code??o.message})`,"a runnable command executable")}return y(e,t.stdout??"",t.status,t.signal)}u(D,"executeExternalLeaf");function y(e,r,n,t){if(t!==null)throw i(e,`executable was killed by signal ${t}`,"a clean exit with one JSON envelope on stdout");let o;try{o=JSON.parse(r.trim())}catch{throw i(e,"stdout was not a single JSON envelope","exactly one JSON envelope on stdout")}if(!c(o))throw i(e,"stdout was not a JSON object","a { protocolVersion: 1, ok: boolean, ... } envelope");if(o.protocolVersion!==1)throw i(e,`unsupported protocolVersion ${String(o.protocolVersion)}`,"protocolVersion exactly 1");if(typeof o.ok!="boolean")throw i(e,'envelope is missing a boolean "ok"',"a boolean ok field");const a=o.ok;if(a&&n!==0?m(`crtr: ${p(e)} returned ok:true but exited ${n} (honoring the envelope)`):!a&&n===0&&m(`crtr: ${p(e)} returned ok:false but exited 0 (honoring the envelope)`),a){const s=o.result;if(!c(s))throw i(e,"success envelope has no result object","result: an object matching the declared output");return k(e,s),s}const l=o.error;if(!c(l)||typeof l.code!="string"||typeof l.message!="string")throw i(e,"error envelope is missing a { code, message }","error: { code, message, ... }");const d=l.code;throw!b.test(d)||h.has(d)?i(e,`error code "${d}" is invalid or reserved`,`a lowercase snake_case code not in {${[...h].join(", ")}}`):new f(d,l.message,g.GENERAL,{received:l.received,field:typeof l.field=="string"?l.field:void 0,next:typeof l.next=="string"?l.next:x})}u(y,"parseLeafEnvelope");function k(e,r){const n=$(e.output,r);if(n===null)return;const t=n.field,o=e.output.find(a=>a.name===t);throw n.receivedType!==void 0?i(e,`output field "${t}" has the wrong type`,`${t}: ${n.expected??o?.type??"unknown"}`):i(e,`result is missing required output field "${t}"`,`a "${t}" field of type ${o?.type??"unknown"}`)}u(k,"validateResult");function i(e,r,n){return new f("plugin_protocol_error",`${p(e)}: ${r}. Expected ${n}.`,g.GENERAL,{received:r,next:x})}u(i,"protocolError");function p(e){return e.plugin!==void 0?`plugin "${e.plugin.name}" command \`${e.commandPath.join(" ")}\``:`extension branch "${e.extension.branch}" command \`${e.commandPath.join(" ")}\``}u(p,"label");export{E as MAX_STDOUT,S as buildLeafRequest,D as executeExternalLeaf,p as label,y as parseLeafEnvelope,i as protocolError};
@@ -1 +1 @@
1
- var R=Object.defineProperty;var f=(e,t)=>R(e,"name",{value:t,configurable:!0});import{readFileSync as O,statSync as k}from"node:fs";import{resolve as j}from"node:path";import{validateDeclaredResult as C}from"../../../api/command-manifest/result.js";import{CrtrError as w}from"../../errors.js";import{envelopeClaimsStale as P,staleErrorDetails as z}from"../../manifest-stale.js";import{ExitCode as h}from"../../../types.js";import{isRecord as x}from"../../../shared/predicates.js";import{trimHeaderValue as U}from"./sign.js";const q=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,T=new Set(["internal","unknown_path","command_collision","cli_protocol_error"]),D=new Set(["authorization","crtr-request-id","crtr-run-id","crtr-approval","crtr-upload-id","crtr-upload-link","accept","content-type","crtr-caller","crtr-signature"]),M=new Set(["crtr-caller","crtr-signature","crtr-request-id"]);function te(e,t,n,r){const i=new Map;for(const o of e.params)i.set(o.name,o);const c=Object.entries(e.rest.params),u=e.baseUrl??new URL(e.registration.endpoint).origin;let y=e.rest.path;for(const[o,a]of c){if(a.in!=="path")continue;const d=t[S(o)];y=y.split(`{${o}}`).join(I(d))}const b=new URL(y,u);for(const[o,a]of c){if(a.in!=="query")continue;const d=S(o);v(i.get(o),d,t,n)&&b.searchParams.append(a.as??o,String(t[d]))}const m=G(e,c,i,t,n,r.fileMode),p=Object.keys(m).length>0?JSON.stringify(m):void 0,l={},s=f((o,a)=>{const d=Object.keys(l).find(g=>g.toLowerCase()===o.toLowerCase());d!==void 0&&delete l[d],l[o]=U(a)},"setHeader");s("Accept","application/json"),s("Crtr-Request-Id",r.requestId),r.authorization!==null&&s("Authorization",r.authorization);for(const[o,a]of c){if(a.in!=="header")continue;const d=S(o);if(!v(i.get(o),d,t,n))continue;const g=a.as;if(M.has(g.toLowerCase())||r.fileMode==="pass-through"&&D.has(g.toLowerCase()))throw new w("invalid_type",`${E(e)}: header "${g}" is reserved for the runtime`,h.USAGE,{field:o,next:"Remove the runtime-owned header mapping from the manifest."});const A=String(t[d]);if(/[\r\n]/.test(A))throw new w("invalid_type",`${E(e)}: header "${g}" value must not contain a newline`,h.USAGE,{field:o,received:"(newline in header value)",next:"Remove newline characters from the value."});s(g,A)}p!==void 0&&s("Content-Type","application/json");for(const[o,a]of Object.entries(r.extraHeaders)){if(r.fileMode==="pass-through"&&/[\r\n]/.test(a))throw new w("invalid_type",`${E(e)}: header "${o}" value must not contain a newline`,h.USAGE);s(o,a)}if(r.fileMode==="pass-through"){if(s("Accept","application/json"),p!==void 0)s("Content-Type","application/json");else{const o=Object.keys(l).find(a=>a.toLowerCase()==="content-type");o!==void 0&&delete l[o]}if(s("Crtr-Request-Id",r.requestId),r.authorization!==null)s("Authorization",r.authorization);else{const o=Object.keys(l).find(a=>a.toLowerCase()==="authorization");o!==void 0&&delete l[o]}}return{url:b,method:e.rest.method,headers:l,...p!==void 0?{body:p}:{}}}f(te,"buildRequestPlan");function G(e,t,n,r,i,c){const u=new Map;for(const[m,p]of t){if(p.in!=="body")continue;const l=p.as??m,s=u.get(l);s!==void 0?s.push(m):u.set(l,[m])}const y={};for(const[m,p]of u){const l=p.map(d=>({name:d,param:n.get(d)})),s=l.find(d=>d.param.kind!=="stdin"),o=l.find(d=>d.param.kind==="stdin");let a;s!==void 0&&v(s.param,S(s.name),r,i)?a={param:s.param,value:r[S(s.name)]}:o!==void 0&&v(o.param,S(o.name),r,i)&&(a={param:o.param,value:r[S(o.name)]}),a!==void 0&&(y[m]=H(e,a.param,a.value,c))}const b={...e.rest.body};return e.rest.bodyRoot!==void 0?b[e.rest.bodyRoot]=y:Object.assign(b,y),b}f(G,"assembleBody");function H(e,t,n,r){return r==="pass-through"||t.kind==="context-file"?n:(t.kind==="positional"||t.kind==="flag")&&t.type==="path"?N(E(e),t,n):n}f(H,"materializeBodyValue");function N(e,t,n){if(Array.isArray(n))return n.map(u=>N(e,t,u));const r=String(n),i=j(process.cwd(),r);let c;try{c=k(i)}catch{throw _(e,t.name,r,"no file exists at that path","an existing local file path")}if(!c.isFile())throw _(e,t.name,r,"the path is not a regular file","a regular local file (not a directory, socket, or device)");try{return"encoding"in t&&t.encoding==="base64"?O(i).toString("base64"):O(i,"utf8")}catch{throw _(e,t.name,r,"the file could not be read","a readable local file path")}}f(N,"readPathParam");function _(e,t,n,r,i){return new w("invalid_type",`${e}: cannot read file for "${t}" \u2014 ${r}: ${n}`,h.USAGE,{field:t,received:n,next:`Pass ${i}, relative to the current directory or absolute.`})}f(_,"fileError");function v(e,t,n,r){return r!==void 0?r.providedParams.has(t):Object.prototype.hasOwnProperty.call(n,t)&&n[t]!==void 0}f(v,"isPresent");function I(e){const t=String(e);return t==="."||t===".."?t.replace(/\./g,"%2E"):encodeURIComponent(t)}f(I,"encodePathSegmentValue");function S(e){return e.replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}f(S,"paramKey");function ne(e,t){let n;try{n=JSON.parse(t)}catch{throw $(e,"response body was not valid JSON","a JSON object matching the declared output")}if(!x(n))throw $(e,`response body was not a JSON object (got ${V(n)})`,"a JSON object matching the declared output");const r=C(e.output,n);if(r!==null){const c=e.output.find(u=>u.name===r.field)?.type??"unknown";throw r.receivedType!==void 0?$(e,`output field "${r.field}" has the wrong type`,`${r.field}: ${c}`):$(e,`response is missing required output field "${r.field}"`,`a "${r.field}" field of type ${c}`)}return n}f(ne,"mapUnary2xx");function re(e,t,n){const r=L(t),i=J(n);if(i!==null){const c=q.test(i.code)&&!T.has(i.code);throw new w(c?i.code:"cli_protocol_error",i.message,r,{...i.received!==void 0?{received:i.received}:{},...i.field!==void 0?{field:i.field}:{},...i.manifestStale?z(e.registration.name):{},http_status:t,next:i.next??"Inspect the backend response, then retry if appropriate."})}throw new w("cli_http_error",`${E(e)}: request failed with HTTP ${t}`,r,{http_status:t,next:"Inspect the backend response, then retry if appropriate."})}f(re,"throwNon2xx");function J(e){let t;try{t=JSON.parse(e)}catch{return null}if(!x(t)||!x(t.error))return null;const n=t.error;return typeof n.code!="string"||typeof n.message!="string"?null:{code:n.code,message:n.message,...typeof n.field=="string"?{field:n.field}:{},...n.received!==void 0?{received:n.received}:{},...typeof n.next=="string"?{next:n.next}:{},manifestStale:P(n),...n.question!==void 0?{question:n.question}:{}}}f(J,"parseHttpError");function L(e){return e===400||e===401||e===403||e===422?h.USAGE:e===404?h.NOT_FOUND:e===409?h.AMBIGUOUS:h.GENERAL}f(L,"deriveExit");function $(e,t,n){return new w("cli_protocol_error",`${E(e)}: ${t}. Expected ${n}.`,h.GENERAL,{received:t,next:`Run \`crtr pkg plugin update ${e.registration.name}\` or inspect the backend response.`})}f($,"protocolError");function E(e){return`HTTP-transport plugin "${e.registration.name}" command \`${e.commandPath.join(" ")}\``}f(E,"label");function V(e){return e===null?"null":Array.isArray(e)?"array":typeof e}f(V,"typeName");export{te as buildRequestPlan,ne as mapUnary2xx,J as parseHttpError,N as readPathParam,re as throwNon2xx};
1
+ var R=Object.defineProperty;var c=(e,t)=>R(e,"name",{value:t,configurable:!0});import{readFileSync as O,statSync as k}from"node:fs";import{resolve as j}from"node:path";import{validateDeclaredResult as C}from"../../../api/command-manifest/result.js";import{CrtrError as w}from"../../errors.js";import{envelopeClaimsStale as P,staleErrorDetails as z}from"../../manifest-stale.js";import{ExitCode as h}from"../../../types.js";import{isRecord as x}from"../../../shared/predicates.js";import{trimHeaderValue as U}from"./sign.js";const q=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,T=new Set(["internal","unknown_path","command_collision","cli_protocol_error"]),D=new Set(["authorization","crtr-request-id","crtr-run-id","crtr-approval","crtr-upload-id","crtr-upload-link","accept","content-type","crtr-caller","crtr-signature"]),M=new Set(["crtr-caller","crtr-signature","crtr-request-id"]);function te(e,t,n,r){const i=new Map;for(const o of e.params)i.set(o.name,o);const f=Object.entries(e.rest.params),u=e.baseUrl??new URL(e.registration.endpoint).origin;let y=e.rest.path;for(const[o,a]of f){if(a.in!=="path")continue;const d=t[S(o)];y=y.split(`{${o}}`).join(I(d))}const b=new URL(y,u);for(const[o,a]of f){if(a.in!=="query")continue;const d=S(o);v(i.get(o),d,t,n)&&b.searchParams.append(a.as??o,String(t[d]))}const m=G(e,f,i,t,n,r.fileMode),p=Object.keys(m).length>0?JSON.stringify(m):void 0,l={},s=c((o,a)=>{const d=Object.keys(l).find(g=>g.toLowerCase()===o.toLowerCase());d!==void 0&&delete l[d],l[o]=U(a)},"setHeader");s("Accept","application/json"),s("Crtr-Request-Id",r.requestId),r.authorization!==null&&s("Authorization",r.authorization);for(const[o,a]of f){if(a.in!=="header")continue;const d=S(o);if(!v(i.get(o),d,t,n))continue;const g=a.as;if(M.has(g.toLowerCase())||r.fileMode==="pass-through"&&D.has(g.toLowerCase()))throw new w("invalid_type",`${E(e)}: header "${g}" is reserved for the runtime`,h.USAGE,{field:o,next:"Remove the runtime-owned header mapping from the manifest."});const A=String(t[d]);if(/[\r\n]/.test(A))throw new w("invalid_type",`${E(e)}: header "${g}" value must not contain a newline`,h.USAGE,{field:o,received:"(newline in header value)",next:"Remove newline characters from the value."});s(g,A)}p!==void 0&&s("Content-Type","application/json");for(const[o,a]of Object.entries(r.extraHeaders)){if(r.fileMode==="pass-through"&&/[\r\n]/.test(a))throw new w("invalid_type",`${E(e)}: header "${o}" value must not contain a newline`,h.USAGE);s(o,a)}if(r.fileMode==="pass-through"){if(s("Accept","application/json"),p!==void 0)s("Content-Type","application/json");else{const o=Object.keys(l).find(a=>a.toLowerCase()==="content-type");o!==void 0&&delete l[o]}if(s("Crtr-Request-Id",r.requestId),r.authorization!==null)s("Authorization",r.authorization);else{const o=Object.keys(l).find(a=>a.toLowerCase()==="authorization");o!==void 0&&delete l[o]}}return{url:b,method:e.rest.method,headers:l,...p!==void 0?{body:p}:{}}}c(te,"buildRequestPlan");function G(e,t,n,r,i,f){const u=new Map;for(const[m,p]of t){if(p.in!=="body")continue;const l=p.as??m,s=u.get(l);s!==void 0?s.push(m):u.set(l,[m])}const y={};for(const[m,p]of u){const l=p.map(d=>({name:d,param:n.get(d)})),s=l.find(d=>d.param.kind!=="stdin"),o=l.find(d=>d.param.kind==="stdin");let a;s!==void 0&&v(s.param,S(s.name),r,i)?a={param:s.param,value:r[S(s.name)]}:o!==void 0&&v(o.param,S(o.name),r,i)&&(a={param:o.param,value:r[S(o.name)]}),a!==void 0&&(y[m]=H(e,a.param,a.value,f))}const b={...e.rest.body};return e.rest.bodyRoot!==void 0?b[e.rest.bodyRoot]=y:Object.assign(b,y),b}c(G,"assembleBody");function H(e,t,n,r){return r==="pass-through"||t.kind==="context-file"?n:(t.kind==="positional"||t.kind==="flag")&&t.type==="path"?N(E(e),t,n):n}c(H,"materializeBodyValue");function N(e,t,n){if(Array.isArray(n))return n.map(u=>N(e,t,u));const r=String(n),i=j(process.cwd(),r);let f;try{f=k(i)}catch{throw _(e,t.name,r,"no file exists at that path","an existing local file path")}if(!f.isFile())throw _(e,t.name,r,"the path is not a regular file","a regular local file (not a directory, socket, or device)");try{return"encoding"in t&&t.encoding==="base64"?O(i).toString("base64"):O(i,"utf8")}catch{throw _(e,t.name,r,"the file could not be read","a readable local file path")}}c(N,"readPathParam");function _(e,t,n,r,i){return new w("invalid_type",`${e}: cannot read file for "${t}" \u2014 ${r}: ${n}`,h.USAGE,{field:t,received:n,next:`Pass ${i}, relative to the current directory or absolute.`})}c(_,"fileError");function v(e,t,n,r){return r!==void 0?r.providedParams.has(t):Object.prototype.hasOwnProperty.call(n,t)&&n[t]!==void 0}c(v,"isPresent");function I(e){const t=String(e);return t==="."||t===".."?t.replace(/\./g,"%2E"):encodeURIComponent(t)}c(I,"encodePathSegmentValue");function S(e){return e.replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}c(S,"paramKey");function ne(e,t){let n;try{n=JSON.parse(t)}catch{throw $(e,"response body was not valid JSON","a JSON object matching the declared output")}if(!x(n))throw $(e,`response body was not a JSON object (got ${V(n)})`,"a JSON object matching the declared output");const r=C(e.output,n);if(r!==null){const f=e.output.find(u=>u.name===r.field)?.type??"unknown";throw r.receivedType!==void 0?$(e,`output field "${r.field}" has the wrong type`,`${r.field}: ${r.expected??f}`):$(e,`response is missing required output field "${r.field}"`,`a "${r.field}" field of type ${f}`)}return n}c(ne,"mapUnary2xx");function re(e,t,n){const r=L(t),i=J(n);if(i!==null){const f=q.test(i.code)&&!T.has(i.code);throw new w(f?i.code:"cli_protocol_error",i.message,r,{...i.received!==void 0?{received:i.received}:{},...i.field!==void 0?{field:i.field}:{},...i.manifestStale?z(e.registration.name):{},http_status:t,next:i.next??"Inspect the backend response, then retry if appropriate."})}throw new w("cli_http_error",`${E(e)}: request failed with HTTP ${t}`,r,{http_status:t,next:"Inspect the backend response, then retry if appropriate."})}c(re,"throwNon2xx");function J(e){let t;try{t=JSON.parse(e)}catch{return null}if(!x(t)||!x(t.error))return null;const n=t.error;return typeof n.code!="string"||typeof n.message!="string"?null:{code:n.code,message:n.message,...typeof n.field=="string"?{field:n.field}:{},...n.received!==void 0?{received:n.received}:{},...typeof n.next=="string"?{next:n.next}:{},manifestStale:P(n),...n.question!==void 0?{question:n.question}:{}}}c(J,"parseHttpError");function L(e){return e===400||e===401||e===403||e===422?h.USAGE:e===404?h.NOT_FOUND:e===409?h.AMBIGUOUS:h.GENERAL}c(L,"deriveExit");function $(e,t,n){return new w("cli_protocol_error",`${E(e)}: ${t}. Expected ${n}.`,h.GENERAL,{received:t,next:`Run \`crtr pkg plugin update ${e.registration.name}\` or inspect the backend response.`})}c($,"protocolError");function E(e){return`HTTP-transport plugin "${e.registration.name}" command \`${e.commandPath.join(" ")}\``}c(E,"label");function V(e){return e===null?"null":Array.isArray(e)?"array":typeof e}c(V,"typeName");export{te as buildRequestPlan,ne as mapUnary2xx,J as parseHttpError,N as readPathParam,re as throwNon2xx};
@@ -1,10 +1,10 @@
1
- var J=Object.defineProperty;var f=(e,i)=>J(e,"name",{value:i,configurable:!0});import{renderRoot as F,renderBranch as V,renderLeafArgv as O,withAsyncHelpState as z,collapseWideChildren as G,rootPromotedChildren as I}from"./help.js";import{beginPreview as L,publishPreviewResult as K,readStdinRaw as B,peekStdinRaw as D,emit as Y,handle as Q,setJsonOutput as X,isJsonOutput as Z}from"./io.js";import{renderResult as ee}from"./render.js";import{CrtrError as N}from"./errors.js";import{isManifestStaleError as ne}from"./manifest-stale.js";import{operationIdContext as te}from"./events/operation-id.js";import{ExitCode as S}from"../types.js";import{envNodeId as ie}from"../shared/env.js";import{readFileSync as re}from"node:fs";import{spawnSync as ae}from"node:child_process";import{constants as oe}from"node:os";import{isatty as se}from"node:tty";import{resolve as le}from"node:path";function W(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))W(i);return Object.freeze(e)}f(W,"freezeCommandMetadata");function Fe(e){const i=W(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>z(i,r,n)),slash:e.slash===void 0?void 0:W(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(Fe,"defineLeaf");function de(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(de,"visibleSubCount");function Ve(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=de(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:i.tier??"normal",kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:e.children}}f(Ve,"defineBranch");function Ie(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Ie,"collectSlashSpecs");function Be(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0).map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),l=I(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:l.length>0?l:void 0,otherSubcommandCount:a.length-l.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(Be,"defineRoot");function fe(e){if(e.kind==="root")return e.subtrees.filter(i=>i.tier!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=G(e.children.filter(n=>n.tier!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(fe,"childNames");function ce(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const l=i[a];if(l==="-h"||l==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(ce,"walk");function ue(e){return e.kind==="root"?F(e.help):e.kind==="branch"?V(e.help):O(e.help)}f(ue,"renderNode");async function M(e,i,r){if(r!==void 0&&e.kind==="leaf")return O(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?O(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?V(await e.effectiveHelp()):ue(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:l},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:x}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),b=await l(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:x});return n+b.map($=>`
1
+ var G=Object.defineProperty;var f=(e,i)=>G(e,"name",{value:i,configurable:!0});import{renderRoot as V,renderBranch as I,renderLeafArgv as W,withAsyncHelpState as L,collapseWideChildren as K,rootPromotedChildren as J}from"./help.js";import{beginPreview as D,publishPreviewResult as Y,readStdinRaw as B,peekStdinRaw as Q,emit as X,handle as Z,setJsonOutput as ee,isJsonOutput as ne}from"./io.js";import{renderResult as te}from"./render.js";import{CrtrError as H}from"./errors.js";import{validateValue as ie}from"../api/command-manifest/result.js";import{isManifestStaleError as re}from"./manifest-stale.js";import{operationIdContext as ae}from"./events/operation-id.js";import{ExitCode as C}from"../types.js";import{envNodeId as oe}from"../shared/env.js";import{readFileSync as se}from"node:fs";import{spawnSync as le}from"node:child_process";import{constants as de}from"node:os";import{isatty as fe}from"node:tty";import{resolve as ce}from"node:path";function q(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))q(i);return Object.freeze(e)}f(q,"freezeCommandMetadata");function Je(e){const i=q(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>L(i,r,n)),slash:e.slash===void 0?void 0:q(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(Je,"defineLeaf");function A(e){return e.kind==="branch"&&e.children.length>0&&M(e)===0?"hidden":e.tier??"normal"}f(A,"effectiveTier");function M(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(M,"visibleSubCount");function Be(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=M(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:A(i),kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:e.children}}f(Be,"defineBranch");function Me(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Me,"collectSlashSpecs");function ze(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0&&A(n)!=="hidden").map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),d=J(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:d.length>0?d:void 0,otherSubcommandCount:a.length-d.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(ze,"defineRoot");function ue(e){if(e.kind==="root")return e.subtrees.filter(i=>A(i)!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=K(e.children.filter(n=>A(n)!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(ue,"childNames");function he(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const d=i[a];if(d==="-h"||d==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===d);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(he,"walk");function pe(e){return e.kind==="root"?V(e.help):e.kind==="branch"?I(e.help):W(e.help)}f(pe,"renderNode");async function z(e,i,r){if(r!==void 0&&e.kind==="leaf")return W(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?W(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?I(await e.effectiveHelp()):pe(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:d},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:k}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),v=await d(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:k});return n+v.map(E=>`
2
2
 
3
- <plugin-help plugin="${$.plugin}">
4
- ${$.text}
5
- </plugin-help>`).join("")}f(M,"renderNodeWithAddenda");function he(e){return e.some(i=>i==="-h"||i==="--help")}f(he,"helpRequested");function me(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(me,"focusedHelpFlag");function pe(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(pe,"rawArgsAfterPath");function ge(e,i){const r=ae(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new N("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,S.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=oe.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(ge,"runPassthrough");function we(e,i,r){const n=fe(e),a=n.length>0?n.join(", "):"(none)",l=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new N("unknown_path",`unknown subcommand: ${r}`,S.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${l}\` for how to find the rest.`:`Valid children: ${a}. Run \`${l}\` for the full list.`})}f(we,"unknownPathError");const be="https://account.northlight.ai";function ve(e){const i=be,r=`open ${i}/account/machines, choose Create join token, then run \`crtr sys join --directory ${i} --token <token>\``;return new N("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from Northlight, this machine isn't joined to your account yet: ${r}.`,S.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}f(ve,"notJoinedError");async function xe(){try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}f(xe,"runtimeUnjoined");function p(e,i,r,n,a){return new N(e,i,S.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(p,"parseArgvError");function k(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f(k,"flagNameToKey");async function ke(e,i,r){const n={},a=new Set,l=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),x=e.find(t=>t.kind==="context-file"),b=e.filter(t=>t.kind==="flag"),$=new Map(b.map(t=>[t.name,t])),y=b.some(t=>t.sensitive===!0);for(const t of b)t.type==="bool"?n[k(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[k(t.name)]=t.default);const g=[];let v=0,_=!1,w=!1,c=0;for(;c<i.length;){const t=i[c];if(t==="--"&&!w){w=!0,c++;continue}if(!w&&t.startsWith("--")){const h=t.indexOf("=");let d,P;if(h!==-1?(d=t.slice(2,h),P=t.slice(h+1)):d=t.slice(2),d==="context-file"){if(x===void 0)throw p("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const m=P!==void 0?P:i[++c];if(m===void 0)throw p("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let H;try{H=re(m,"utf8")}catch{throw p("invalid_type",`--context-file: cannot read file: ${m}`,m,"context-file","Provide a readable file path.")}let q;try{q=JSON.parse(H)}catch{throw p("invalid_type",`--context-file: file is not valid JSON: ${m}`,m,"context-file","Ensure the file contains a valid JSON object.")}const T=k(x.name);n[T]=q,l.set(T,le(m)),a.add(T),c++;continue}const E=$.get(d);if(E===void 0)throw p("unknown_flag",`unknown flag: --${d}`,`--${d}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const U=k(d);if(a.add(U),E.type==="bool"){if(P!==void 0)throw p("invalid_type",`boolean flag --${d} takes no value`,`--${d}=${P}`,d,`Use --${d} (presence = true) with no value.`);n[U]=!0,c++;continue}const C=P!==void 0?P:i[++c];if(C===void 0||P===void 0&&C.startsWith("--")){const m=E.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${d} -h\` and read the focused value contract before retrying.`:void 0;throw p("missing_parameter",`--${d} requires a value`,`--${d}`,d,m)}let R;if(E.type==="int"){const m=Number(C);if(!Number.isInteger(m))throw p("invalid_type",`--${d} must be an integer`,C,d,`Provide an integer value for --${d}.`);R=m}else if(E.type==="enum"){const m=E.choices;if(m!==void 0&&!m.includes(C))throw p("invalid_type",`--${d} must be one of: ${m.join(", ")}`,C,d,`Retry with one of: ${m.join(", ")}.`);R=C}else R=C;if(E.repeatable===!0){const m=n[U],H=Array.isArray(m)?m:[];H.push(R),n[U]=H}else n[U]=R;c++;continue}const u=s[v];if(u!==void 0){const h=k(u.name);if(u.repeatable===!0){const d=n[h]??[];d.push(t),n[h]=d}else n[h]=t,v++;g.push(t),_=!0,a.add(h),c++;continue}if(g.length>0){if(o!==void 0&&s.length===0){g.push(t),_=!0,c++;continue}throw o!==void 0?p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):p("bad_invocation",y?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,y?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw p("bad_invocation",y?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,y?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");g.push(t),_=!0,c++}let A=!1;if(o!==void 0){if(g.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?p("bad_invocation",t.message,void 0,t.field,t.next):p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(g.length>0&&s.length===0&&o.allowPositional!==!1){const t=g[0];if(t==="-"){const u=await B();(u.trim()!==""||!o.required)&&(n[k(o.name)]=u),A=!0}else{if((await D()).trim()!==""){const h=o.positionalStdinConflict?.(n);throw p("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,h?.field??o.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[k(o.name)]=t,A=!0}}else{const t=se(0),u=await B();(u.trim()!==""||!o.required)&&(n[k(o.name)]=u),A=!t||_}}A&&o!==void 0&&a.add(k(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const u=k(t.name);if(a.has(u))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[u]=t.kind==="positional"&&t.repeatable===!0?[h]:h,a.add(u))}const j=[];for(const t of e){const u=k(t.name),h=t.kind==="flag"&&t.type==="bool"&&!a.has(u);if(t.required&&(h||n[u]===void 0||n[u]===null)){const d=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";j.push({display:d,name:t.name})}}if(j.length>0){const t=j.map(d=>d.display).join(", "),u=r?.leafPath,h=u!==void 0&&u.length>0?`\`crtr ${u.join(" ")} -h\``:"this command with -h";throw p("missing_parameter",j.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,j.map(d=>d.name).join(", "),`Run ${h} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(l),n}f(ke,"parseArgv");function ye(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const l=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&l===void 0){i.push(a);continue}const s=l??e[n+1];if(s===void 0||s===""||l===void 0&&s.startsWith("-"))throw p("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");l===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f(ye,"takeMachineSelector");const Se=new Set(["--json","--no-autostart"]);async function Me(e,i){const r=i.slice(2);r.includes("--json")&&X(!0);const n=r.filter(o=>!Se.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=ie()?{...e.help,commands:await Promise.all(e.help.commands.map(async x=>{const b=e.subtrees.find(v=>v.name===x.name);if(b?.effectiveHelp===void 0)return x;const y=((await b.effectiveHelp()).listing??[]).filter(v=>v.tier!=="hidden"),g=I(y).map(v=>({path:`${b.name} ${v.name}`,desc:v.tier==="important"?v.description:void 0}));return{...x,subcommands:g.length?g:void 0,otherSubcommandCount:y.length-g.length}}))}:e.help;process.stdout.write(F(o)+`
6
- `),process.exitCode=S.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:o}=await import("../commands/sys/shared.js");process.stdout.write(o()+`
7
- `),process.exitCode=S.SUCCESS;return}let{node:a,path:l,remaining:s}=ce(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&ge(a.passthrough,pe(r,l)),a.kind==="branch"&&a.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new N("provider_unavailable",a.unavailable,S.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&await xe()?ve(s[0]):we(a,l,s[0]);if(he(s)){process.stdout.write(await M(a,l,me(a,s))+`
8
- `),process.exitCode=S.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await M(a,l)+`
9
- `),process.exitCode=S.SUCCESS;return}const o=a;L(l.join(" "));const x=o.help.params!==void 0?o.help.params:[];let b=new Set,$=new Map,y=s,g;if(o.machineSelector===!0&&!x.some(c=>c.kind==="flag"&&c.name==="machine")){const c=ye(s);y=c.rest,g=c.machine}const v=await ke(x,y,{leafPath:l,onProvidedParams:f(c=>{b=c},"onProvidedParams"),onContextFilePaths:f(c=>{$=c},"onContextFilePaths")}),_={providedParams:b,contextFilePaths:$,...g===void 0?{}:{machine:g}},w=await te.fresh(()=>o.run(v,_));if(K(w==null?w:o.preview?.(w)??w),w!=null)if(Z())Y(w);else{const c=o.render!==void 0?o.render(w):ee(w,o.help);process.stdout.write(c+(o.stdoutExact?.(w)?"":`
10
- `))}}catch(o){if(ne(o))throw o;Q(o)}}f(Me,"runCli");export{be as DEFAULT_DIRECTORY_ISSUER,Se as GLOBAL_TOKENS,Ie as collectSlashSpecs,Ve as defineBranch,Fe as defineLeaf,Be as defineRoot,ve as notJoinedError,ke as parseArgv,ue as renderNode,Me as runCli,ye as takeMachineSelector,we as unknownPathError,ce as walk};
3
+ <plugin-help plugin="${E.plugin}">
4
+ ${E.text}
5
+ </plugin-help>`).join("")}f(z,"renderNodeWithAddenda");function me(e){return e.some(i=>i==="-h"||i==="--help")}f(me,"helpRequested");function ge(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(ge,"focusedHelpFlag");function we(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(we,"rawArgsAfterPath");function ve(e,i){const r=le(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new H("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,C.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=de.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(ve,"runPassthrough");function be(e,i,r){const n=ue(e),a=n.length>0?n.join(", "):"(none)",d=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new H("unknown_path",`unknown subcommand: ${r}`,C.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${d}\` for how to find the rest.`:`Valid children: ${a}. Run \`${d}\` for the full list.`})}f(be,"unknownPathError");const xe="https://account.northlight.ai";function ke(e){const i=xe,r=`open ${i}/account/machines, choose Create join token, then run \`crtr sys join --directory ${i} --token <token>\``;return new H("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from Northlight, this machine isn't joined to your account yet: ${r}.`,C.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}f(ke,"notJoinedError");async function ye(){try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}f(ye,"runtimeUnjoined");function m(e,i,r,n,a){return new H(e,i,C.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(m,"parseArgvError");function $(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f($,"flagNameToKey");async function $e(e,i,r){const n={},a=new Set,d=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),k=e.find(t=>t.kind==="context-file"),v=e.filter(t=>t.kind==="flag"),E=new Map(v.map(t=>[t.name,t])),S=v.some(t=>t.sensitive===!0);for(const t of v)t.type==="bool"?n[$(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[$(t.name)]=t.default);const g=[];let b=0,j=!1,w=!1,u=0;for(;u<i.length;){const t=i[u];if(t==="--"&&!w){w=!0,u++;continue}if(!w&&t.startsWith("--")){const p=t.indexOf("=");let l,_;if(p!==-1?(l=t.slice(2,p),_=t.slice(p+1)):l=t.slice(2),l==="context-file"){if(k===void 0)throw m("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const c=_!==void 0?_:i[++u];if(c===void 0)throw m("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let x;try{x=se(c,"utf8")}catch{throw m("invalid_type",`--context-file: cannot read file: ${c}`,c,"context-file","Provide a readable file path.")}let F;try{F=JSON.parse(x)}catch{throw m("invalid_type",`--context-file: file is not valid JSON: ${c}`,c,"context-file","Ensure the file contains a valid JSON object.")}const O=$(k.name);n[O]=F,d.set(O,ce(c)),a.add(O),u++;continue}const P=E.get(l);if(P===void 0)throw m("unknown_flag",`unknown flag: --${l}`,`--${l}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const R=$(l);if(a.add(R),P.type==="bool"){if(_!==void 0)throw m("invalid_type",`boolean flag --${l} takes no value`,`--${l}=${_}`,l,`Use --${l} (presence = true) with no value.`);n[R]=!0,u++;continue}const y=_!==void 0?_:i[++u];if(y===void 0||_===void 0&&y.startsWith("--")){const c=P.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${l} -h\` and read the focused value contract before retrying.`:void 0;throw m("missing_parameter",`--${l} requires a value`,`--${l}`,l,c)}let U;if(P.type==="int"){const c=Number(y);if(!Number.isInteger(c))throw m("invalid_type",`--${l} must be an integer`,y,l,`Provide an integer value for --${l}.`);U=c}else if(P.type==="enum"){const c=P.choices;if(c!==void 0&&!c.includes(y))throw m("invalid_type",`--${l} must be one of: ${c.join(", ")}`,y,l,`Retry with one of: ${c.join(", ")}.`);U=y}else if(P.type==="json"){let c;try{c=JSON.parse(y)}catch{throw m("invalid_type",`--${l} is not valid JSON`,y,l,`Pass one JSON value for --${l}; run -h for its shape.`)}const x=P.shape===void 0?null:ie(P.shape,c,`--${l}`);if(x!==null)throw m("invalid_type",`${x.path} must be ${x.expected}, got ${x.receivedType}`,y,x.path,`Fix ${x.path}; run -h for the shape of --${l}.`);U=c}else U=y;if(P.repeatable===!0){const c=n[R],x=Array.isArray(c)?c:[];x.push(U),n[R]=x}else n[R]=U;u++;continue}const h=s[b];if(h!==void 0){const p=$(h.name);if(h.repeatable===!0){const l=n[p]??[];l.push(t),n[p]=l}else n[p]=t,b++;g.push(t),j=!0,a.add(p),u++;continue}if(g.length>0){if(o!==void 0&&s.length===0){g.push(t),j=!0,u++;continue}throw o!==void 0?m("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):m("bad_invocation",S?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,S?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw m("bad_invocation",S?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,S?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");g.push(t),j=!0,u++}let T=!1;if(o!==void 0){if(g.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?m("bad_invocation",t.message,void 0,t.field,t.next):m("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(g.length>0&&s.length===0&&o.allowPositional!==!1){const t=g[0];if(t==="-"){const h=await B();(h.trim()!==""||!o.required)&&(n[$(o.name)]=h),T=!0}else{if((await Q()).trim()!==""){const p=o.positionalStdinConflict?.(n);throw m("bad_invocation",p?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,p?.field??o.name,p?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[$(o.name)]=t,T=!0}}else{const t=fe(0),h=await B();(h.trim()!==""||!o.required)&&(n[$(o.name)]=h),T=!t||j}}T&&o!==void 0&&a.add($(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const h=$(t.name);if(a.has(h))continue;const p=process.env[t.defaultFromEnv];p===void 0||p===""||(n[h]=t.kind==="positional"&&t.repeatable===!0?[p]:p,a.add(h))}const N=[];for(const t of e){const h=$(t.name),p=t.kind==="flag"&&t.type==="bool"&&!a.has(h);if(t.required&&(p||n[h]===void 0||n[h]===null)){const l=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";N.push({display:l,name:t.name})}}if(N.length>0){const t=N.map(l=>l.display).join(", "),h=r?.leafPath,p=h!==void 0&&h.length>0?`\`crtr ${h.join(" ")} -h\``:"this command with -h";throw m("missing_parameter",N.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,N.map(l=>l.name).join(", "),`Run ${p} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(d),n}f($e,"parseArgv");function Se(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const d=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&d===void 0){i.push(a);continue}const s=d??e[n+1];if(s===void 0||s===""||d===void 0&&s.startsWith("-"))throw m("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");d===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f(Se,"takeMachineSelector");const Pe=new Set(["--json","--no-autostart"]);async function Ge(e,i){const r=i.slice(2);r.includes("--json")&&ee(!0);const n=r.filter(o=>!Pe.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=oe()?{...e.help,commands:await Promise.all(e.help.commands.map(async k=>{const v=e.subtrees.find(b=>b.name===k.name);if(v?.effectiveHelp===void 0)return k;const S=((await v.effectiveHelp()).listing??[]).filter(b=>b.tier!=="hidden"),g=J(S).map(b=>({path:`${v.name} ${b.name}`,desc:b.tier==="important"?b.description:void 0}));return{...k,subcommands:g.length?g:void 0,otherSubcommandCount:S.length-g.length}}))}:e.help;process.stdout.write(V(o)+`
6
+ `),process.exitCode=C.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:o}=await import("../commands/sys/shared.js");process.stdout.write(o()+`
7
+ `),process.exitCode=C.SUCCESS;return}let{node:a,path:d,remaining:s}=he(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&ve(a.passthrough,we(r,d)),a.kind==="branch"&&a.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new H("provider_unavailable",a.unavailable,C.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&await ye()?ke(s[0]):be(a,d,s[0]);if(me(s)){process.stdout.write(await z(a,d,ge(a,s))+`
8
+ `),process.exitCode=C.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await z(a,d)+`
9
+ `),process.exitCode=C.SUCCESS;return}const o=a;D(d.join(" "));const k=o.help.params!==void 0?o.help.params:[];let v=new Set,E=new Map,S=s,g;if(o.machineSelector===!0&&!k.some(u=>u.kind==="flag"&&u.name==="machine")){const u=Se(s);S=u.rest,g=u.machine}const b=await $e(k,S,{leafPath:d,onProvidedParams:f(u=>{v=u},"onProvidedParams"),onContextFilePaths:f(u=>{E=u},"onContextFilePaths")}),j={providedParams:v,contextFilePaths:E,...g===void 0?{}:{machine:g}},w=await ae.fresh(()=>o.run(b,j));if(Y(w==null?w:o.preview?.(w)??w),w!=null)if(ne())X(w);else{const u=o.render!==void 0?o.render(w):te(w,o.help);process.stdout.write(u+(o.stdoutExact?.(w)?"":`
10
+ `))}}catch(o){if(re(o))throw o;Z(o)}}f(Ge,"runCli");export{xe as DEFAULT_DIRECTORY_ISSUER,Pe as GLOBAL_TOKENS,Me as collectSlashSpecs,Be as defineBranch,Je as defineLeaf,ze as defineRoot,ke as notJoinedError,$e as parseArgv,pe as renderNode,Ge as runCli,Se as takeMachineSelector,be as unknownPathError,he as walk};
@@ -1,4 +1,4 @@
1
- import type { DeclaredOutputField } from '../api/command-manifest/result.js';
1
+ import type { DeclaredOutputField, DeclaredOutputShape } from '../api/command-manifest/result.js';
2
2
  export type Field = DeclaredOutputField;
3
3
  /** Positional argument. A repeatable positional consumes every remaining positional token. */
4
4
  export interface PositionalParam {
@@ -43,7 +43,9 @@ export interface FlagParam {
43
43
  name: string;
44
44
  /** 'bool' flags take no value — presence = true. 'file' names a
45
45
  * capability-provider file argument, sent locally as the string typed. */
46
- type: 'string' | 'int' | 'bool' | 'path' | 'enum' | 'file';
46
+ type: 'string' | 'int' | 'bool' | 'path' | 'enum' | 'file' | 'json';
47
+ /** Required only when type is 'json': the shape of the one JSON value the flag takes. */
48
+ shape?: DeclaredOutputShape;
47
49
  /** Required only when type is 'enum'. */
48
50
  choices?: string[];
49
51
  required: boolean;
package/dist/core/help.js CHANGED
@@ -1,9 +1,9 @@
1
- var w=Object.defineProperty;var r=(e,n)=>w(e,"name",{value:n,configurable:!0});async function O(e,n,t){const s=r(async a=>{try{const i=await a();return()=>i}catch{return()=>null}},"read"),o=t===void 0?void 0:n.flags?.[t],u=new Map(o===void 0?[]:[[t,await s(o)]]);return{...e,...t===void 0&&n.leaf!==void 0?{dynamicState:await s(n.leaf)}:{},params:e.params?.map(a=>{const i=a.kind==="flag"?u.get(a.name):void 0;return a.kind==="flag"&&a.focusedHelp!==void 0&&i!==void 0?{...a,focusedHelp:{...a.focusedHelp,dynamicState:i}}:a})}}r(O,"withAsyncHelpState");function j(e,n,t){const s=Object.entries(n).map(([o,u])=>` ${o}="${u}"`).join("");return`<${e}${s}>
1
+ var k=Object.defineProperty;var u=(e,n)=>k(e,"name",{value:n,configurable:!0});import{outputBaseType as y}from"../api/command-manifest/schema.js";async function E(e,n,t){const s=u(async o=>{try{const i=await o();return()=>i}catch{return()=>null}},"read"),r=t===void 0?void 0:n.flags?.[t],a=new Map(r===void 0?[]:[[t,await s(r)]]);return{...e,...t===void 0&&n.leaf!==void 0?{dynamicState:await s(n.leaf)}:{},params:e.params?.map(o=>{const i=o.kind==="flag"?a.get(o.name):void 0;return o.kind==="flag"&&o.focusedHelp!==void 0&&i!==void 0?{...o,focusedHelp:{...o.focusedHelp,dynamicState:i}}:o})}}u(E,"withAsyncHelpState");function A(e,n,t){const s=Object.entries(n).map(([r,a])=>` ${r}="${a}"`).join("");return`<${e}${s}>
2
2
  ${t}
3
- </${e}>`}r(j,"stateBlock");function f(e){if(e===void 0)return null;try{const n=e();return n!==null&&n!==""?n:null}catch{return null}}r(f,"evalDynamic");function l(e){let n=0;for(const t of e)t.length>n&&(n=t.length);return n}r(l,"maxLen");function p(e,n){return e+" ".repeat(Math.max(0,n-e.length))}r(p,"pad");const S=`I/O contract: flags and positional args on input; stdout is agent-ready markdown/XML you
3
+ </${e}>`}u(A,"stateBlock");function p(e){if(e===void 0)return null;try{const n=e();return n!==null&&n!==""?n:null}catch{return null}}u(p,"evalDynamic");function m(e){let n=0;for(const t of e)t.length>n&&(n=t.length);return n}u(m,"maxLen");function h(e,n){return e+" ".repeat(Math.max(0,n-e.length))}u(h,"pad");const N=`I/O contract: flags and positional args on input; stdout is agent-ready markdown/XML you
4
4
  act on directly \u2014 read it as a continuation of your prompt, don't parse it as data.
5
- Exit 0 on success, non-zero on failure. Schemas appear at leaf -h; a leaf may route an uncommon flag to focused --flag -h guidance.`,I="Before running a crtr command that changes state \u2014 creates, mutates, or deletes anything \u2014 whose exact contract (args, flags, effects) you haven't verified this session, run `-h` on it and read the schema first \u2014 a reliable read beats a guess that wastes a turn or triggers an unintended effect. When a leaf routes a flag to focused help, read that focused help before using the flag; the compact row is discovery, not its invocation contract. Same when the user names a capability you don't fully recognize: `-h` it before acting.";function N(e){const n=e.subcommands??[],t=e.otherSubcommandCount??0;if(n.length===0&&t===0)return[];const s=[];if(n.length>0){const o=l(n.map(u=>u.path));for(const u of n)s.push(u.desc!==void 0&&u.desc!==""?` ${p(u.path,o)} ${u.desc}`:` ${u.path}`)}if(t>0){const o=n.length>0?"other subcommand":"subcommand";s.push(` [+${t} ${o}${t===1?"":"s"} \u2014 \`crtr ${e.name} -h\`]`)}return s}r(N,"rootSubcommandLines");function E(e){const n=[];n.push(`${e.tagline}`),n.push("");for(const t of e.commands){n.push(`<command name="${t.name}">`),n.push(t.concept),n.push(`use when ${t.useWhen}`);for(const o of N(t))n.push(o);const s=f(t.dynamicState);s!==null&&n.push(s),n.push("</command>"),n.push("")}if(e.globals.length>0){n.push("Globals");const t=l(e.globals.map(s=>s.name));for(const s of e.globals)n.push(` ${p(s.name,t)} ${s.desc}`);n.push("")}return n.push(S),n.push(""),n.push(I),n.join(`
6
- `)}r(E,"renderRoot");function d(e){return e.replace(/"/g,"'")}r(d,"attr");const $=40;function k(e){if(e.length<=$)return{listed:[...e],unlisted:0};const n=e.filter(t=>t.tier==="common"||t.tier==="important");return n.length===0?{listed:[...e],unlisted:0}:{listed:n,unlisted:e.length-n.length}}r(k,"collapseWideChildren");function W(e){const n=e.length>$;return e.filter(t=>(t.tier==="common"||t.tier==="important")&&(!n||t.kind==="leaf"))}r(W,"rootPromotedChildren");function A(e){const n=[];n.push(`<command name="${e.name}" description="${d(e.summary)}">`);const t=f(e.dynamicState);t!==null&&n.push(t),e.model!==void 0&&n.push(e.model);const{listed:s,unlisted:o}=k((e.listing??[]).filter(u=>u.tier!=="hidden"));for(const u of s){const a=u.plugin===void 0?"":` plugin="${d(u.plugin)}"`,i=u.subCount!==void 0&&u.subCount>0?` subcommands="${u.subCount}"`:"";n.length>1&&n.push(""),n.push(`<subcommand name="${u.name}" description="${d(u.description)}" whenToUse="${d(u.whenToUse)}"${a}${i}/>`)}return o>0&&(n.push(""),n.push(`<unlisted-subcommands count="${o}">${o} more subcommands are not listed here. Each one runs by name, and \`crtr ${e.name} <name> -h\` prints its help.</unlisted-subcommands>`)),e.extensionIssue!==void 0&&n.push(`<extension-issue fragment="${d(e.extensionIssue.fragment)}">This repository's contributed commands were rejected: ${e.extensionIssue.message}. Run \`crtr sys doctor\` for the full report.</extension-issue>`),n.push("</command>"),n.join(`
7
- `)}r(A,"renderBranch");function C(e){if(e.kind==="positional")return`${e.name.toUpperCase()}${e.repeatable===!0?"...":""}`;if(e.kind==="stdin")return"stdin";if(e.kind==="context-file")return"--context-file PATH";const n=e;return n.type==="bool"?`--${n.name}`:`--${n.name} ${n.name.toUpperCase().replace(/-/g,"_")}`}r(C,"paramLabel");function g(e){return e===void 0?"":e==="base64"?" Local file path: the file is read and its raw bytes are base64-encoded into the request; the path itself is never sent.":" Local file path: the file is read as UTF-8 text and its content is sent as the value; the path itself is never sent."}r(g,"fileEncodingNote");function y(e){return e===void 0?"":` Defaults to $${e} when that is set and non-empty in the environment, and counts as supplied.`}r(y,"envDefaultNote");function b(e){return e==="file"?" A file: pass its path.":""}r(b,"fileParamNote");function v(e,n){return e.focusedHelp===void 0?"":` Before using this flag, run \`crtr ${n} --${e.name} -h\` and read its focused guidance.`}r(v,"focusedFlagHelpNote");function T(e,n){const t=e.required?"required":"optional";if(e.kind==="positional"){const c=e.repeatable===!0?" Repeatable \u2014 pass multiple values to accumulate in argv order.":"";return`positional, ${t}.${b(e.type)}${g(e.encoding)}${y(e.defaultFromEnv)} ${e.constraint}${c}`}if(e.kind==="stdin")return`${t}. ${e.constraint}`;if(e.kind==="context-file"){const c=e.shape!==void 0?` Shape: ${e.shape}`:"";return`${t}. Path to a JSON file.${c} ${e.constraint}`.trim()}const s=e,o=v(s,n);if(s.type==="bool")return`${t} boolean. Presence means true. ${s.constraint}${o}`.trim();const u=s.default!==void 0?` Default: ${String(s.default)}.`:"",a=s.type==="enum"&&s.choices!==void 0?` One of: ${s.choices.join(", ")}.`:"",i=s.repeatable===!0?" Repeatable \u2014 pass multiple times to accumulate.":"";return`${s.type}, ${t}.${a}${u}${b(s.type)}${g(s.encoding)}${y(s.defaultFromEnv)} ${s.constraint}${i}${o}`.trim()}r(T,"paramDesc");function m(e){if(e.items===void 0)return e.type;const n=/\|\s*null\s*$/i.test(e.type)?", or null":"";return`array of ${m(e.items)}${n}`}r(m,"outputTypePhrase");function x(e){return e.values!==void 0?` One of: ${e.values.join(", ")}.`:""}r(x,"outputValuesNote");function h(e,n,t){const s=l(e.map(o=>o.name));for(const o of e){const u=o.constraint.length>0?` ${o.constraint}`:"";t.push(`${n}${p(o.name,s)} ${m(o)}.${x(o)}${u}`),H(o,`${n} `,t)}}r(h,"renderOutputFields");function H(e,n,t){e.children!==void 0&&e.children.length>0&&h(e.children,n,t);let s=e.items;for(;s!==void 0;){const o=x(s);if((s.constraint.length>0||o.length>0)&&t.push(`${n}each element: ${m(s)}.${o}${s.constraint.length>0?` ${s.constraint}`:""}`),s.children!==void 0&&s.children.length>0){h(s.children,n,t);return}s=s.items}}r(H,"renderOutputShapeBody");function B(e,n){const t=[`${e.name}: ${e.summary}.`],s=(e.params??[]).filter(a=>!(a.kind==="flag"&&a.hidden===!0)),o=n===void 0?void 0:s.find(a=>a.kind==="flag"&&a.name===n&&a.focusedHelp!==void 0);if(o?.focusedHelp!==void 0){const a=o.focusedHelp;if(t.push(""),t.push(`<parameter name="${o.name}">`),t.push("When to use"),t.push(` ${a.whenToUse}`),t.push(""),t.push("Value"),t.push(` ${a.value}`),(a.effects??[]).length>0){t.push(""),t.push("Effects");for(const c of a.effects??[])t.push(` ${c}`)}const i=f(a.dynamicState);return i!==null&&(t.push(""),t.push(i)),t.push("</parameter>"),t.join(`
8
- `)}if(e.guide!==void 0&&(t.push(""),t.push(e.guide)),t.push(""),s.length>0){t.push("Input");const a=s.map(C),i=l(a);for(let c=0;c<s.length;c++)t.push(` ${p(a[c],i)} ${T(s[c],e.name)}`)}else t.push(e.inputNote!==void 0?e.inputNote:"No input parameters.");t.push(""),t.push("Output (fields carried in the rendered result)"),h(e.output," ",t),t.push(""),t.push("Effects");for(const a of e.effects)t.push(` ${a}`);const u=f(e.dynamicState);return u!==null&&(t.push(""),t.push(u)),t.join(`
9
- `)}r(B,"renderLeafArgv");export{$ as WIDE_BRANCH_LISTING_LIMIT,k as collapseWideChildren,A as renderBranch,B as renderLeafArgv,E as renderRoot,W as rootPromotedChildren,j as stateBlock,O as withAsyncHelpState};
5
+ Exit 0 on success, non-zero on failure. Schemas appear at leaf -h; a leaf may route an uncommon flag to focused --flag -h guidance.`,I="Before running a crtr command that changes state \u2014 creates, mutates, or deletes anything \u2014 whose exact contract (args, flags, effects) you haven't verified this session, run `-h` on it and read the schema first \u2014 a reliable read beats a guess that wastes a turn or triggers an unintended effect. When a leaf routes a flag to focused help, read that focused help before using the flag; the compact row is discovery, not its invocation contract. Same when the user names a capability you don't fully recognize: `-h` it before acting.";function j(e){const n=e.subcommands??[],t=e.otherSubcommandCount??0;if(n.length===0&&t===0)return[];const s=[];if(n.length>0){const r=m(n.map(a=>a.path));for(const a of n)s.push(a.desc!==void 0&&a.desc!==""?` ${h(a.path,r)} ${a.desc}`:` ${a.path}`)}if(t>0){const r=n.length>0?"other subcommand":"subcommand";s.push(` [+${t} ${r}${t===1?"":"s"} \u2014 \`crtr ${e.name} -h\`]`)}return s}u(j,"rootSubcommandLines");function B(e){const n=[];n.push(`${e.tagline}`),n.push("");for(const t of e.commands){n.push(`<command name="${t.name}">`),n.push(t.concept),n.push(`use when ${t.useWhen}`);for(const r of j(t))n.push(r);const s=p(t.dynamicState);s!==null&&n.push(s),n.push("</command>"),n.push("")}if(e.globals.length>0){n.push("Globals");const t=m(e.globals.map(s=>s.name));for(const s of e.globals)n.push(` ${h(s.name,t)} ${s.desc}`);n.push("")}return n.push(N),n.push(""),n.push(I),n.join(`
6
+ `)}u(B,"renderRoot");function f(e){return e.replace(/"/g,"'")}u(f,"attr");const v=40;function C(e){if(e.length<=v)return{listed:[...e],unlisted:0};const n=e.filter(t=>t.tier==="common"||t.tier==="important");return n.length===0?{listed:[...e],unlisted:0}:{listed:n,unlisted:e.length-n.length}}u(C,"collapseWideChildren");function D(e){const n=e.length>v;return e.filter(t=>(t.tier==="common"||t.tier==="important")&&(!n||t.kind==="leaf"))}u(D,"rootPromotedChildren");function P(e){const n=[];n.push(`<command name="${e.name}" description="${f(e.summary)}">`);const t=p(e.dynamicState);t!==null&&n.push(t),e.model!==void 0&&n.push(e.model);const{listed:s,unlisted:r}=C((e.listing??[]).filter(a=>a.tier!=="hidden"));for(const a of s){const o=a.plugin===void 0?"":` plugin="${f(a.plugin)}"`,i=a.subCount!==void 0&&a.subCount>0?` subcommands="${a.subCount}"`:"";n.length>1&&n.push(""),n.push(`<subcommand name="${a.name}" description="${f(a.description)}" whenToUse="${f(a.whenToUse)}"${o}${i}/>`)}return r>0&&(n.push(""),n.push(`<unlisted-subcommands count="${r}">${r} more subcommands are not listed here. Each one runs by name, and \`crtr ${e.name} <name> -h\` prints its help.</unlisted-subcommands>`)),e.extensionIssue!==void 0&&n.push(`<extension-issue fragment="${f(e.extensionIssue.fragment)}">This repository's contributed commands were rejected: ${e.extensionIssue.message}. Run \`crtr sys doctor\` for the full report.</extension-issue>`),n.push("</command>"),n.join(`
7
+ `)}u(P,"renderBranch");function O(e){if(e.kind==="positional")return`${e.name.toUpperCase()}${e.repeatable===!0?"...":""}`;if(e.kind==="stdin")return"stdin";if(e.kind==="context-file")return"--context-file PATH";const n=e;return n.type==="bool"?`--${n.name}`:`--${n.name} ${n.name.toUpperCase().replace(/-/g,"_")}`}u(O,"paramLabel");function x(e){return e===void 0?"":e==="base64"?" Local file path: the file is read and its raw bytes are base64-encoded into the request; the path itself is never sent.":" Local file path: the file is read as UTF-8 text and its content is sent as the value; the path itself is never sent."}u(x,"fileEncodingNote");function w(e){return e===void 0?"":` Defaults to $${e} when that is set and non-empty in the environment, and counts as supplied.`}u(w,"envDefaultNote");function S(e){return e==="file"?" A file: pass its path.":""}u(S,"fileParamNote");function T(e,n){return e.focusedHelp===void 0?"":` Before using this flag, run \`crtr ${n} --${e.name} -h\` and read its focused guidance.`}u(T,"focusedFlagHelpNote");function H(e,n){const t=e.required?"required":"optional";if(e.kind==="positional"){const c=e.repeatable===!0?" Repeatable \u2014 pass multiple values to accumulate in argv order.":"";return`positional, ${t}.${S(e.type)}${x(e.encoding)}${w(e.defaultFromEnv)} ${e.constraint}${c}`}if(e.kind==="stdin")return`${t}. ${e.constraint}`;if(e.kind==="context-file"){const c=e.shape!==void 0?` Shape: ${e.shape}`:"";return`${t}. Path to a JSON file.${c} ${e.constraint}`.trim()}const s=e,r=T(s,n);if(s.type==="bool")return`${t} boolean. Presence means true. ${s.constraint}${r}`.trim();const a=s.default!==void 0?` Default: ${String(s.default)}.`:"",o=s.type==="enum"&&s.choices!==void 0?` One of: ${s.choices.join(", ")}.`:"",i=s.repeatable===!0?" Repeatable \u2014 pass multiple times to accumulate.":"";return s.type==="json"?`json, ${t}. One JSON value: ${d(s.shape??{type:"unknown",constraint:""})}. ${s.constraint}${r}`.trim():`${s.type}, ${t}.${o}${a}${S(s.type)}${x(s.encoding)}${w(s.defaultFromEnv)} ${s.constraint}${i}${r}`.trim()}u(H,"paramDesc");function d(e){const n=/\|\s*null\s*$/i.test(e.type),t=y(e.type);return t==="union"&&e.variants!==void 0?`${e.variants.map(d).join(" | ")}${n?" | null":""}`:t==="map"&&e.items!==void 0?`{ [key: string]: ${d(e.items)} }${n?" | null":""}`:e.items===void 0?e.type:`array of ${d(e.items)}${n?", or null":""}`}u(d,"outputTypePhrase");function $(e){return e.values!==void 0?` One of: ${e.values.join(", ")}.`:""}u($,"outputValuesNote");function b(e,n,t){const s=m(e.map(r=>r.name));for(const r of e){const a=r.constraint.length>0?` ${r.constraint}`:"";t.push(`${n}${h(r.name,s)} ${d(r)}.${$(r)}${a}`),g(r,`${n} `,t)}}u(b,"renderOutputFields");function g(e,n,t){e.children!==void 0&&e.children.length>0&&b(e.children,n,t);for(const a of e.variants??[]){const o=$(a);t.push(`${n}variant: ${d(a)}.${o}${a.constraint.length>0?` ${a.constraint}`:""}`),g(a,`${n} `,t)}let s=e.items,r=y(e.type)==="map"?"each value":"each element";for(;s!==void 0;){const a=$(s);(s.constraint.length>0||a.length>0)&&t.push(`${n}${r}: ${d(s)}.${a}${s.constraint.length>0?` ${s.constraint}`:""}`);for(const o of s.variants??[])t.push(`${n}variant: ${d(o)}.${$(o)}${o.constraint.length>0?` ${o.constraint}`:""}`),g(o,`${n} `,t);if(s.children!==void 0&&s.children.length>0){b(s.children,n,t);return}r=y(s.type)==="map"?"each value":"each element",s=s.items}}u(g,"renderOutputShapeBody");function R(e,n){const t=[`${e.name}: ${e.summary}.`],s=(e.params??[]).filter(o=>!(o.kind==="flag"&&o.hidden===!0)),r=n===void 0?void 0:s.find(o=>o.kind==="flag"&&o.name===n&&o.focusedHelp!==void 0);if(r?.focusedHelp!==void 0){const o=r.focusedHelp;if(t.push(""),t.push(`<parameter name="${r.name}">`),t.push("When to use"),t.push(` ${o.whenToUse}`),t.push(""),t.push("Value"),t.push(` ${o.value}`),(o.effects??[]).length>0){t.push(""),t.push("Effects");for(const c of o.effects??[])t.push(` ${c}`)}const i=p(o.dynamicState);return i!==null&&(t.push(""),t.push(i)),t.push("</parameter>"),t.join(`
8
+ `)}if(e.guide!==void 0&&(t.push(""),t.push(e.guide)),t.push(""),s.length>0){t.push("Input");const o=s.map(O),i=m(o);for(let c=0;c<s.length;c++){t.push(` ${h(o[c],i)} ${H(s[c],e.name)}`);const l=s[c];l.kind==="flag"&&l.type==="json"&&l.shape!==void 0&&g(l.shape,` ${" ".repeat(i)} `,t)}}else t.push(e.inputNote!==void 0?e.inputNote:"No input parameters.");t.push(""),t.push("Output (fields carried in the rendered result)"),b(e.output," ",t),t.push(""),t.push("Effects");for(const o of e.effects)t.push(` ${o}`);const a=p(e.dynamicState);return a!==null&&(t.push(""),t.push(a)),t.join(`
9
+ `)}u(R,"renderLeafArgv");export{v as WIDE_BRANCH_LISTING_LIMIT,C as collapseWideChildren,P as renderBranch,R as renderLeafArgv,B as renderRoot,D as rootPromotedChildren,A as stateBlock,E as withAsyncHelpState};
@@ -1 +1 @@
1
- var c=Object.defineProperty;var o=(n,t)=>c(n,"name",{value:t,configurable:!0});import{apiSocketPath as l}from"./canvas/paths.js";import{validatePluginCommands as d}from"./command-plugins/discovery.js";import{declaresObjectBlocks as m,leafPathsBelowRoot as g,needsAppToken as f,objectBlocksReasons as h,streamingLeafPathsBelowRoot as P}from"./plugin-objects-manifest.js";import{listInstalledPlugins as k}from"./resolver.js";import{pluginAppToken as u}from"./secrets.js";function v(n){const t=d(n),e=t.manifest?.roots??[];return{leafPaths:g(n.name,e),streamingLeafPaths:P(n.name,e),issues:t.issues.map(i=>i.message)}}o(v,"pluginLeafPaths");function a(){const n=[],t=[];for(const e of k("user")){if(!e.enabled||!(f(e.manifest)||m(e.manifest)))continue;const{leafPaths:i,streamingLeafPaths:s,issues:p}=v(e),r=[...p,...h(e.manifest,{name:e.name,leafPaths:i,streamingLeafPaths:s})];r.length===0?n.push(e):t.push({plugin:e,error:r.join("; ")})}return{valid:n,invalid:t}}o(a,"classify");function A(){return a().valid.filter(n=>f(n.manifest))}o(A,"appPlugins");function O(){return a().valid.filter(n=>Object.keys(n.manifest.object_kinds??{}).length>0)}o(O,"kindPlugins");function _(){return a().invalid}o(_,"invalidAppPlugins");function B(n){const t=u(n);return t===void 0?null:{CRTR_APP_TOKEN:t.token,CRTR_DAEMON_SOCKET:l()}}o(B,"pluginAppEnv");function E(n){const t=A().find(s=>s.name===n);if(t===void 0)return null;const e=u(n);if(e===void 0)return null;const i=t.manifest.transport;return{plugin:t,token:e.token,socket:l(),endpoint:i?.kind==="http"?i.endpoint:null}}o(E,"pluginAppCredential");export{A as appPlugins,_ as invalidAppPlugins,O as kindPlugins,E as pluginAppCredential,B as pluginAppEnv,v as pluginLeafPaths};
1
+ var m=Object.defineProperty;var s=(n,t)=>m(n,"name",{value:t,configurable:!0});import{apiSocketPath as l}from"./canvas/paths.js";import{validatePluginCommands as c}from"./command-plugins/discovery.js";import{declaresObjectBlocks as d,leafPathsBelowRoot as h,needsAppToken as f,objectBlocksReasons as P,streamingLeafPathsBelowRoot as g}from"./plugin-objects-manifest.js";import{listInstalledPlugins as k}from"./resolver.js";import{pluginAppToken as u}from"./secrets.js";function v(n){const t=c(n),e=t.manifest?.roots??[];return{leafPaths:h(n.name,n.manifest,e),streamingLeafPaths:g(n.name,n.manifest,e),issues:t.issues.map(o=>o.message)}}s(v,"pluginLeafPaths");function a(){const n=[],t=[];for(const e of k("user")){if(!e.enabled||!(f(e.manifest)||d(e.manifest)))continue;const{leafPaths:o,streamingLeafPaths:i,issues:p}=v(e),r=[...p,...P(e.manifest,{name:e.name,leafPaths:o,streamingLeafPaths:i})];r.length===0?n.push(e):t.push({plugin:e,error:r.join("; ")})}return{valid:n,invalid:t}}s(a,"classify");function A(){return a().valid.filter(n=>f(n.manifest))}s(A,"appPlugins");function O(){return a().valid.filter(n=>Object.keys(n.manifest.object_kinds??{}).length>0)}s(O,"kindPlugins");function _(){return a().invalid}s(_,"invalidAppPlugins");function B(n){const t=u(n);return t===void 0?null:{CRTR_APP_TOKEN:t.token,CRTR_DAEMON_SOCKET:l()}}s(B,"pluginAppEnv");function E(n){const t=A().find(i=>i.name===n);if(t===void 0)return null;const e=u(n);if(e===void 0)return null;const o=t.manifest.transport;return{plugin:t,token:e.token,socket:l(),endpoint:o?.kind==="http"?o.endpoint:null}}s(E,"pluginAppCredential");export{A as appPlugins,_ as invalidAppPlugins,O as kindPlugins,E as pluginAppCredential,B as pluginAppEnv,v as pluginLeafPaths};
@@ -15,7 +15,9 @@ export declare function objectBlocksReasons(manifest: Pick<PluginManifest, 'obje
15
15
  export declare function declaresObjectBlocks(manifest: Pick<PluginManifest, 'object_kinds' | 'scopes'>): boolean;
16
16
  /** A user plugin holds a `crtr_app_` token when it is not a provider and declares kinds or scopes, or serves over HTTP. */
17
17
  export declare function needsAppToken(manifest: Pick<PluginManifest, 'object_kinds' | 'scopes' | 'provider' | 'transport'>): boolean;
18
- /** The space-joined path of every leaf below the plugin's root command (`page read` for plugin `search`'s leaf `search page read`). */
19
- export declare function leafPathsBelowRoot(name: string, roots: ReadonlyArray<DeclNode<DeclLeaf>>): Set<string>;
18
+ /** A provider leaf's tool path from its full command path (root first): root-relative when the bundle has one root command, else as declared. The one rule `providerLeaves` dispatches by and the object-kind gate validates against. */
19
+ export declare function providerToolPath(rootCount: number, path: readonly string[]): string;
20
+ /** The space-joined path of every leaf below the plugin's root command (`page read` for plugin `search`'s leaf `search page read`); for a provider bundle, every leaf's tool path (`providerToolPath`). */
21
+ export declare function leafPathsBelowRoot(name: string, manifest: Pick<PluginManifest, 'provider'>, roots: ReadonlyArray<DeclNode<DeclLeaf>>): Set<string>;
20
22
  /** The leaves of `leafPathsBelowRoot` that declare `rest.streaming: true`. */
21
- export declare function streamingLeafPathsBelowRoot(name: string, roots: ReadonlyArray<DeclNode<DeclLeaf>>): Set<string>;
23
+ export declare function streamingLeafPathsBelowRoot(name: string, manifest: Pick<PluginManifest, 'provider'>, roots: ReadonlyArray<DeclNode<DeclLeaf>>): Set<string>;
@@ -1 +1 @@
1
- var l=Object.defineProperty;var r=(e,n)=>l(e,"name",{value:n,configurable:!0});import{parseScope as h}from"@north-light/identity";import{KIND as g,LOCAL as k,RESERVED_KIND_ROOTS as b,MAX_KIND_EVENTS as E,MIN_EVENT_LEVEL as j,MAX_EVENT_LEVEL as _,eventCatalogReasons as S,objectBlocksShapeReasons as a,objectKindReadReasons as f}from"../api/command-manifest/object-kinds.js";const w=/^crtr:objects:(?:read|watch|link|write):/;function L(e,n){const t=a(e);if(t.length>0)return t;const{name:c,leafPaths:p,streamingLeafPaths:s}=n;for(const[o,u]of Object.entries(e.object_kinds??{}))o.split(".")[0]!==c&&t.push(`object_kinds ${JSON.stringify(o)} must start with the plugin name "${c}."`),t.push(...f(o,u,c,p,s));const i=e.scopes??[];e.scopes!==void 0&&e.provider!==void 0&&t.push("scopes is for local plugins; a provider gets its scopes from directory consent");for(const o of i){try{h(o)}catch{t.push(`scope ${JSON.stringify(o)} is not a recognized scope`);continue}w.test(o)?o.endsWith(`:app:${c}`)&&t.push(`scope ${JSON.stringify(o)} names the plugin's own space; an app needs no scope for its own objects`):t.push(`scope ${JSON.stringify(o)} must be crtr:objects:<read|watch|link|write>:<target>`)}return t}r(L,"objectBlocksReasons");function m(e){return e.object_kinds!==void 0||e.scopes!==void 0}r(m,"declaresObjectBlocks");function y(e){return e.provider!==void 0?!1:Object.keys(e.object_kinds??{}).length>0||(e.scopes?.length??0)>0||e.transport?.kind==="http"}r(y,"needsAppToken");function P(e,n){return d(e,n,()=>!0)}r(P,"leafPathsBelowRoot");function V(e,n){return d(e,n,t=>"rest"in t&&t.rest.streaming===!0)}r(V,"streamingLeafPathsBelowRoot");function d(e,n,t){const c=new Set,p=r((s,i)=>{if(s.kind==="leaf")t(s)&&c.add([...i,s.name].join(" "));else for(const o of s.children)p(o,[...i,s.name])},"walk");for(const s of n)if(s.name===e&&s.kind==="branch")for(const i of s.children)p(i,[]);return c}r(d,"collectLeafPaths");export{g as KIND,k as LOCAL,_ as MAX_EVENT_LEVEL,E as MAX_KIND_EVENTS,j as MIN_EVENT_LEVEL,b as RESERVED_KIND_ROOTS,m as declaresObjectBlocks,S as eventCatalogReasons,P as leafPathsBelowRoot,y as needsAppToken,L as objectBlocksReasons,a as objectBlocksShapeReasons,f as objectKindReadReasons,V as streamingLeafPathsBelowRoot};
1
+ var h=Object.defineProperty;var r=(e,n)=>h(e,"name",{value:n,configurable:!0});import{parseScope as g}from"@north-light/identity";import{KIND as k,LOCAL as b,RESERVED_KIND_ROOTS as j,MAX_KIND_EVENTS as E,MIN_EVENT_LEVEL as _,MAX_EVENT_LEVEL as S,eventCatalogReasons as w,objectBlocksShapeReasons as f,objectKindReadReasons as d}from"../api/command-manifest/object-kinds.js";const N=/^crtr:objects:(?:read|watch|link|write):/;function P(e,n){const o=f(e);if(o.length>0)return o;const{name:s,leafPaths:i,streamingLeafPaths:a}=n;for(const[t,c]of Object.entries(e.object_kinds??{}))t.split(".")[0]!==s&&o.push(`object_kinds ${JSON.stringify(t)} must start with the plugin name "${s}."`),o.push(...d(t,c,s,i,a));const p=e.scopes??[];e.scopes!==void 0&&e.provider!==void 0&&o.push("scopes is for local plugins; a provider gets its scopes from directory consent");for(const t of p){try{g(t)}catch{o.push(`scope ${JSON.stringify(t)} is not a recognized scope`);continue}N.test(t)?t.endsWith(`:app:${s}`)&&o.push(`scope ${JSON.stringify(t)} names the plugin's own space; an app needs no scope for its own objects`):o.push(`scope ${JSON.stringify(t)} must be crtr:objects:<read|watch|link|write>:<target>`)}return o}r(P,"objectBlocksReasons");function v(e){return e.object_kinds!==void 0||e.scopes!==void 0}r(v,"declaresObjectBlocks");function y(e){return e.provider!==void 0?!1:Object.keys(e.object_kinds??{}).length>0||(e.scopes?.length??0)>0||e.transport?.kind==="http"}r(y,"needsAppToken");function O(e,n){return(e===1?n.slice(1):n).join(" ")}r(O,"providerToolPath");function V(e,n,o){return u(e,n,o,()=>!0)}r(V,"leafPathsBelowRoot");function B(e,n,o){return u(e,n,o,s=>"rest"in s&&s.rest.streaming===!0)}r(B,"streamingLeafPathsBelowRoot");function u(e,n,o,s){const i=n.provider!==void 0,a=new Set,p=r((t,c)=>{if(t.kind==="leaf")s(t)&&a.add(i?O(o.length,[...c,t.name]):[...c,t.name].join(" "));else for(const l of t.children)p(l,[...c,t.name])},"walk");for(const t of o)if(!(t.kind!=="branch"||!i&&t.name!==e))if(i)p(t,[]);else for(const c of t.children)p(c,[]);return a}r(u,"collectLeafPaths");export{k as KIND,b as LOCAL,S as MAX_EVENT_LEVEL,E as MAX_KIND_EVENTS,_ as MIN_EVENT_LEVEL,j as RESERVED_KIND_ROOTS,v as declaresObjectBlocks,w as eventCatalogReasons,V as leafPathsBelowRoot,y as needsAppToken,P as objectBlocksReasons,f as objectBlocksShapeReasons,d as objectKindReadReasons,O as providerToolPath,B as streamingLeafPathsBelowRoot};
@@ -2,10 +2,11 @@ import type { ManifestHttpLeaf, ManifestBranch, ManifestRootEntry } from '../../
2
2
  import type { ValidatedCommandManifest } from '../../api/command-manifest/manifest.js';
3
3
  import type { ProviderCaller, ProviderCallInput, ProviderRequest } from '../../daemon/api/handlers/providers.js';
4
4
  import { ProviderError } from './errors.js';
5
- /** A canonical command path is relative to the provider, never provider-prefixed. */
5
+ /** `tool` is a canonical command path, relative to the provider and never provider-prefixed. `hidden` is true for a leaf declared hidden or under a hidden branch: it stays callable by its exact path but is never listed. */
6
6
  export interface ProviderLeaf {
7
7
  tool: string;
8
8
  leaf: ManifestHttpLeaf;
9
+ hidden: boolean;
9
10
  }
10
11
  export declare function providerLeaves(commands: ValidatedCommandManifest): ProviderLeaf[];
11
12
  export type ProviderHelpLeaf = Pick<ManifestHttpLeaf, 'kind' | 'name' | 'description' | 'whenToUse' | 'tier' | 'summary' | 'params' | 'output' | 'effects' | 'group'>;
@@ -1 +1 @@
1
- var re=Object.defineProperty;var m=(e,t)=>re(e,"name",{value:t,configurable:!0});import{validateDeclaredResult as I}from"../../api/command-manifest/result.js";import{callerAllows as te,requireGroup as oe}from"../../daemon/api/handlers/providers.js";import{terminalGrant as ne}from"../grants/mirror.js";import{UNCAPPED_GRANTEE as ie}from"../usage/ledger.js";import{listInstalledPlugins as se}from"../resolver.js";import{ProviderError as l}from"./errors.js";import{resolveProviderRef as ae}from"./roots.js";import{ensureProviderBundle as de,refreshProviderBundle as ue,resolveProviderBundle as U,unavailableProviderError as le}from"./bundles.js";import{prepareFileCall as pe,resignForRetry as G,resolveFileResult as z}from"./files.js";import{consumeApproval as ce,requireProviderApproval as fe}from"./approval.js";import{outboundToken as me}from"./mint.js";import{discardFile as ve}from"./token-cache.js";import{registerProviderCall as ge}from"./inflight.js";import{buildRequestPlan as he}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as we,ProviderTransportError as be}from"./request.js";import{acceptProviderReceipt as ye}from"./receipts.js";import{writeProviderAudit as _e}from"./audit.js";import{selectDevice as je,sendDeviceCall as Ae,deviceFailure as ke,deviceUnreachable as Q,DEFAULT_DEVICE_REQUEST_MS as qe}from"./device.js";import{isLibsSearch as V,wantsBundling as Pe,callLibsSearch as Ee,bundleExec as $e,mapLibOutcome as Ce,reportLibCalls as Re}from"./device-libs.js";import{directoryIssuer as Se,ownMachineId as Ne}from"../peer/directory.js";import{formatCrtrCaller as K}from"../command-plugins/caller-header.js";import{getNode as De}from"../canvas/canvas.js";import{envelopeForProviderCaller as Fe}from"../peer/envelope.js";function W(e){const t=[],a=e.roots.length===1?e.roots[0].name:void 0,f=m((r,o)=>{const s=[...o,r.name];if(r.kind==="branch"){for(const j of r.children)f(j,s);return}const p=s[0]===a?s.slice(1).join(" "):s.join(" ");t.push({tool:p,leaf:r})},"walk");for(const r of e.roots)f(r,[]);return t}m(W,"providerLeaves");function X(e){if(e.kind!=="branch")return null;const t=[];for(const h of e.children)if(h.kind==="branch"){const A=X(h);A&&t.push(A)}else if("group"in h&&h.group){const{kind:A,name:d,description:_,whenToUse:v,tier:w,summary:q,params:n,output:k,effects:i,group:b}=h;t.push({kind:A,name:d,description:_,whenToUse:v,...w===void 0?{}:{tier:w},summary:q,params:n,output:k,effects:i,group:b})}if(t.length===0)return null;const{kind:a,name:f,description:r,whenToUse:o,tier:s,summary:p,model:j,rootEntry:g}=e;return{kind:a,name:f,description:r,whenToUse:o,...s===void 0?{}:{tier:s},summary:p,...j===void 0?{}:{model:j},...g===void 0?{}:{rootEntry:g},children:t}}m(X,"providerHelpBranch");async function cr(e,t){const a=await U(t),f=W(a.commands),r=se("user").find(o=>o.name===t)?.manifest.description??t;return{provider:t,status:"available",major:a.major,display_name:r,transport:a.transport.kind,groups:a.provider.groups.filter(o=>te(e,`${t}:${o.group}`)).map(o=>({group:o.group,leaves:f.filter(({leaf:s})=>s.group===o.group).map(({tool:s,leaf:p})=>({tool:s,description:p.description,summary:p.summary,params:p.params,output:p.output}))})),commands:a.commands.roots.flatMap(o=>{const s=X(o);return s?[s]:[]})}}m(cr,"providerTools");function Me(e){return ye(e.headers["crtr-receipt"],e)}m(Me,"onProviderResponse");function Oe(e){if(e.class==="person"){const t=ne()?.sub;if(!t)throw new l("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:t,grantee:t}}if(!e.sub||!e.grantee)throw new l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}m(Oe,"identity");function Te(e,t){return[`${e.id}:${t.group}`]}m(Te,"callScope");function Ie(e,t){const a=W(e.commands).find(f=>f.tool===t);if(!a)throw new l("not_found",`tool "${t}" is not in provider "${e.id}"`,{param:"tool"});if(!a.leaf.group)throw new l("not_found",`tool "${t}" has no group`,{param:"tool"});return a.leaf}m(Ie,"leafFor");function B(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const t=e.error;return!t||typeof t!="object"||Array.isArray(t)?{}:t}m(B,"errorBody");function Ue(e){const t=Array.isArray(e)?e[0]:e;if(!t)return;const a=Number(t);if(Number.isFinite(a)&&a>=0)return a;const f=Date.parse(t);return Number.isFinite(f)?Math.max(0,Math.ceil((f-Date.now())/1e3)):void 0}m(Ue,"retryAfter");function F(e,t,a,f=!1){const r=B(t),o=typeof r.code=="string"?r.code:void 0,s=typeof r.message=="string"?r.message:`provider answered HTTP ${e}`,p=typeof r.field=="string"?r.field:void 0,g={origin:"provider",details:{code:o??null,message:s,...p?{field:p}:{},...typeof r.next=="string"?{next:r.next}:{},...r.received!==void 0?{received:r.received}:{},status:e},...p?{param:p}:{}};return e===429?new l("provider_rate_limited",s,{...g,retry_after_s:Ue(a["retry-after"])}):e===401||e>=500?new l("provider_unavailable",s,g):e===403&&o==="provider_refused"?new l("provider_refused",s,g):e===403&&o==="approval_required"?new l("provider_error",f?"provider asked for approval again":"provider approval question is invalid",{...g,status:e}):new l("provider_error",s,{...g,status:e})}m(F,"mapProviderResponse");function Y(e,t){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:t.rest,params:t.params,output:t.output})}m(Y,"approvalShape");function Be(e){const t=e.origin!==void 0?K({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):K({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?De(e.node_id)?.profile_id??void 0:void 0,machine:Se()!==null?Ne():void 0});return t===""?{}:{"Crtr-Caller":t}}m(Be,"crtrCallerHeader");async function fr(e,t,a){const f=ae(t.provider);if(f===null)throw new l("not_found",`provider "${t.provider}" is not available`,{param:"provider"});const r={...t,provider:f},o=new AbortController,s=m(()=>o.abort(a.signal.reason),"abort");a.signal.aborted?s():a.signal.addEventListener("abort",s,{once:!0});let p=a.requestId;const j=ge({request_id:p,run_id:e.run_id,node_id:e.node_id,controller:o});let g,h,A,d,_,v,w,q="allowed";try{try{await de(r.provider)}catch{throw le(r.provider)}let n=await U(r.provider,r.version),k=!1,i,b,P,L=!1,J=!1,M,H=0;for(;;){const y=Ie(n,r.tool);if(A=`${n.id}:${y.group}`,oe(e,n.id,y.group),n.transport.kind!=="machine"&&r.machine!==void 0)throw new l("invalid_argument",`provider "${n.id}" is not served by a device, so it takes no machine`,{param:"machine"});n.transport.kind==="machine"&&M===void 0&&(M=await je(r.machine,void 0,{newest:V(n.major,r.tool)}),H=Date.now()+(n.commands.timeouts?.requestMs??qe));let O=null;if(r.approval&&!L){const u=ce(e,r,Y(n,y));P=u.option,O=u.prepared,w=r.approval,p=u.requestId,L=!0}const C={params:y.params.filter(u=>"type"in u&&u.type==="file").map(u=>u.name),output:y.output.find(u=>u.type==="file")?.name??null},x=JSON.stringify(C);if(b!==void 0&&b!==x)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});b=x,O&&(i=await G(e,C,r.arguments,O,{signal:o.signal})),i??=await pe(e,C,r.arguments,{signal:o.signal});const{sub:T,grantee:R}=Oe(e);if(n.transport.kind==="machine"){const u=await Le(e,n,r,y,i,{sub:T,requestId:p,deadlineAt:H,signal:o.signal,machine:M});return v=u.responder,g=u.cleanup,{status:200,body:u.result}}const E=await me({audience:n.transport.endpoint,sub:T,grantee:R,initiating_app:R,scope:Te(n,y)},{signal:o.signal});d=E.jti;const Z={registration:{name:n.id,endpoint:n.transport.endpoint},...n.commands.baseUrl?{baseUrl:n.commands.baseUrl}:{},...n.commands.timeouts?{timeouts:n.commands.timeouts}:{},params:y.params,rest:y.rest,output:y.output,commandPath:[n.id,...r.tool.split(" ")]},ee=he(Z,i.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:p,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...Be(e),...P?{"Crtr-Approval":P}:{},...i.headers}});h??=Date.now()+(n.commands.timeouts?.requestMs??3e4);let c;try{if(c=await we(ee,{connectMs:n.commands.timeouts?.connectMs,requestMs:n.commands.timeouts?.requestMs,deadlineAt:h,signal:o.signal}),o.signal.aborted)throw o.signal.reason??new Error("provider call aborted")}catch(u){throw o.signal.aborted?o.signal.reason??u:u instanceof be?new l("provider_unavailable",u.message,{origin:"provider",details:{status:null,code:u.kind}}):u}if(_??=Me({provider:n.id,tool:r.tool,request_id:p,run_id:e.run_id,sub:T,grantee:R,payer:E.payer,ledger_grantee:e.class==="person"?ie:R,jti:E.jti,status:c.status,headers:c.headers}),v=c.headers["crtr-responder"],c.status===401&&ve(E.key),c.status>=200&&c.status<300){const u=c.body;if(!u||typeof u!="object"||Array.isArray(u))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:c.status,message:"provider result is not a JSON object"}});let N;try{N=await z(e,i,u,{signal:o.signal})}catch($){throw $ instanceof l&&$.code==="provider_error"?new l("provider_bad_response",$.message,{origin:"provider",details:{status:c.status,message:$.message}}):$}g=N.cleanup;const D=I(y.output,N.result);if(D)throw new l("provider_bad_response",`provider result has an invalid output field "${D.field}"`,{origin:"provider",param:D.field,details:{status:c.status,...D}});if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");return{status:200,body:N.result}}const S=B(c.body);if(c.status===403&&S.code==="approval_required"&&!P&&!J){J=!0;const u=await fe(e,r,p,S.question,o.signal,Y(n,y),i);if(w=u.questionId,P=u.option,i=await G(e,C,r.arguments,i,{signal:o.signal}),h=void 0,o.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(c.status===403&&S.code==="approval_required"&&P)throw F(c.status,c.body,c.headers,!0);if(c.status!==401&&S.manifest_stale===!0&&!k){k=!0,await ue(n.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}}),n=await U(n.id,r.version);continue}throw F(c.status,c.body,c.headers,!!P)}}catch(n){throw n instanceof l&&n.question_id&&(w=n.question_id),v===void 0&&n instanceof l&&typeof n.responder=="string"&&(v=n.responder),q=`refused:${n instanceof l?n.code:"server_error"}`,g?.(),n}finally{try{_e({request_id:p,caller:e,provider:r.provider,tool:r.tool,scope_checked:A,outcome:q,jti:d,receipt_id:_,responder:v,question_id:w})}finally{j()}a.signal.removeEventListener("abort",s)}}m(fr,"callProvider");async function Le(e,t,a,f,r,o){const{machine:s}=o,p=Fe(e,o.sub),j={machine:s,envelope:p,signal:o.signal,deadlineAt:o.deadlineAt,mapFailure:F};if(V(t.major,a.tool)){const i=await Ee(j,r.arguments,t.major),b=I(f.output,i);if(b)throw new l("provider_bad_response",`the libraries result has an invalid output field "${b.field}"`,{origin:"provider",param:b.field,details:{status:200,...b}});return{result:i,responder:void 0,cleanup:void 0}}let g=r.arguments,h=null,A="";if(Pe(t.major,a.tool,r.arguments)){const i=await $e(j,r.arguments,t.major);if(o.deadlineAt-Date.now()<=0)throw Q(s,"timeout","");i!==null&&(g=i.arguments,h=i.services,A=i.release)}const d=await Ae(s,{tool:a.tool,arguments:g,version:t.major,requestId:o.requestId,runId:e.run_id,deadlineAt:o.deadlineAt,upload:r.upload===null?null:{id:r.upload.upload_id,link:r.upload.upload_link}},p,o.signal);if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");if("unreachable"in d)throw Q(s,d.unreachable.reason,d.unreachable.message);const _=d.headers["crtr-responder"],v=B(d.body);if(d.status<200||d.status>=300){const i=ke(s,d.status,typeof v.code=="string"?v.code:void 0,typeof v.message=="string"?v.message:void 0,d.body),b=i===null&&d.status>=500&&typeof v.code=="string"?new l("provider_error",typeof v.message=="string"?v.message:`the device answered HTTP ${d.status}`,{origin:"runtime",status:d.status,details:{code:v.code,status:d.status}}):null;throw Object.assign(i??b??F(d.status,d.body,d.headers),{responder:_})}const w=d.body&&typeof d.body=="object"&&!Array.isArray(d.body)?d.body.result:void 0;if(!w||typeof w!="object"||Array.isArray(w))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:d.status,message:"the device result is not a JSON object"}}),{responder:_});let q=w;if(h!==null){const i=Ce(q,h);if(Re(s,p,A,i.calls),"error"in i)throw Object.assign(i.error,{responder:_});q=i.result}let n;try{n=await z(e,r,q,{signal:o.signal})}catch(i){throw i instanceof l&&i.code==="provider_error"?Object.assign(new l("provider_bad_response",i.message,{origin:"provider",details:{status:d.status,message:i.message}}),{responder:_}):i}const k=I(f.output,n.result);if(k)throw n.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${k.field}"`,{origin:"provider",param:k.field,details:{status:d.status,...k}}),{responder:_});return{result:n.result,responder:_,cleanup:n.cleanup}}m(Le,"callDevice");export{fr as callProvider,Be as crtrCallerHeader,F as mapProviderResponse,Me as onProviderResponse,W as providerLeaves,cr as providerTools};
1
+ var re=Object.defineProperty;var v=(e,r)=>re(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as I}from"../../api/command-manifest/result.js";import{callerAllows as te,requireGroup as oe}from"../../daemon/api/handlers/providers.js";import{terminalGrant as ne}from"../grants/mirror.js";import{UNCAPPED_GRANTEE as ie}from"../usage/ledger.js";import{listInstalledPlugins as se}from"../resolver.js";import{ProviderError as p}from"./errors.js";import{resolveProviderRef as ae}from"./roots.js";import{providerToolPath as de}from"../plugin-objects-manifest.js";import{ensureProviderBundle as ue,refreshProviderBundle as le,resolveProviderBundle as U,unavailableProviderError as pe}from"./bundles.js";import{prepareFileCall as ce,resignForRetry as G,resolveFileResult as z}from"./files.js";import{consumeApproval as fe,requireProviderApproval as me}from"./approval.js";import{outboundToken as ve}from"./mint.js";import{discardFile as ge}from"./token-cache.js";import{registerProviderCall as he}from"./inflight.js";import{buildRequestPlan as we}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as be,ProviderTransportError as ye}from"./request.js";import{acceptProviderReceipt as _e}from"./receipts.js";import{writeProviderAudit as Ae}from"./audit.js";import{selectDevice as je,sendDeviceCall as ke,deviceFailure as Pe,deviceUnreachable as Q,DEFAULT_DEVICE_REQUEST_MS as qe}from"./device.js";import{isLibsSearch as V,wantsBundling as Ee,callLibsSearch as $e,bundleExec as Ce,mapLibOutcome as Re,reportLibCalls as Se}from"./device-libs.js";import{directoryIssuer as Ne,ownMachineId as Te}from"../peer/directory.js";import{formatCrtrCaller as K}from"../command-plugins/caller-header.js";import{getNode as De}from"../canvas/canvas.js";import{envelopeForProviderCaller as Fe}from"../peer/envelope.js";function W(e){const r=[],s=v((c,t,o)=>{const a=[...t,c.name],d=o||c.tier==="hidden";if(c.kind==="branch"){for(const m of c.children)s(m,a,d);return}const A=de(e.roots.length,a);r.push({tool:A,leaf:c,hidden:d})},"walk");for(const c of e.roots)s(c,[],!1);return r}v(W,"providerLeaves");function X(e){if(e.kind!=="branch")return null;const r=[];for(const h of e.children)if(h.kind==="branch"){const j=X(h);j&&r.push(j)}else if("group"in h&&h.group){const{kind:j,name:u,description:_,whenToUse:g,tier:w,summary:P,params:n,output:k,effects:i,group:b}=h;r.push({kind:j,name:u,description:_,whenToUse:g,...w===void 0?{}:{tier:w},summary:P,params:n,output:k,effects:i,group:b})}if(r.length===0)return null;const{kind:s,name:c,description:t,whenToUse:o,tier:a,summary:d,model:A,rootEntry:m}=e;return{kind:s,name:c,description:t,whenToUse:o,...a===void 0?{}:{tier:a},summary:d,...A===void 0?{}:{model:A},...m===void 0?{}:{rootEntry:m},children:r}}v(X,"providerHelpBranch");async function mr(e,r){const s=await U(r),c=W(s.commands),t=se("user").find(o=>o.name===r)?.manifest.description??r;return{provider:r,status:"available",major:s.major,display_name:t,transport:s.transport.kind,groups:s.provider.groups.filter(o=>te(e,`${r}:${o.group}`)).map(o=>({group:o.group,leaves:c.filter(({leaf:a,hidden:d})=>!d&&a.group===o.group).map(({tool:a,leaf:d})=>({tool:a,description:d.description,summary:d.summary,params:d.params,output:d.output}))})),commands:s.commands.roots.flatMap(o=>{const a=X(o);return a?[a]:[]})}}v(mr,"providerTools");function Me(e){return _e(e.headers["crtr-receipt"],e)}v(Me,"onProviderResponse");function Oe(e){if(e.class==="person"){const r=ne()?.sub;if(!r)throw new p("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new p("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}v(Oe,"identity");function Ie(e,r){return[`${e.id}:${r.group}`]}v(Ie,"callScope");function Ue(e,r){const s=W(e.commands).find(c=>c.tool===r);if(!s)throw new p("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!s.leaf.group)throw new p("not_found",`tool "${r}" has no group`,{param:"tool"});return s.leaf}v(Ue,"leafFor");function B(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}v(B,"errorBody");function Be(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const s=Number(r);if(Number.isFinite(s)&&s>=0)return s;const c=Date.parse(r);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}v(Be,"retryAfter");function D(e,r,s,c=!1){const t=B(r),o=typeof t.code=="string"?t.code:void 0,a=typeof t.message=="string"?t.message:`provider answered HTTP ${e}`,d=typeof t.field=="string"?t.field:void 0,m={origin:"provider",details:{code:o??null,message:a,...d?{field:d}:{},...typeof t.next=="string"?{next:t.next}:{},...t.received!==void 0?{received:t.received}:{},status:e},...d?{param:d}:{}};return e===429?new p("provider_rate_limited",a,{...m,retry_after_s:Be(s["retry-after"])}):e===401||e>=500?new p("provider_unavailable",a,m):e===403&&o==="provider_refused"?new p("provider_refused",a,m):e===403&&o==="approval_required"?new p("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...m,status:e}):new p("provider_error",a,{...m,status:e})}v(D,"mapProviderResponse");function Y(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}v(Y,"approvalShape");function Le(e){const r=e.origin!==void 0?K({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):K({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?De(e.node_id)?.profile_id??void 0:void 0,machine:Ne()!==null?Te():void 0});return r===""?{}:{"Crtr-Caller":r}}v(Le,"crtrCallerHeader");async function vr(e,r,s){const c=ae(r.provider);if(c===null)throw new p("not_found",`provider "${r.provider}" is not available`,{param:"provider"});const t={...r,provider:c},o=new AbortController,a=v(()=>o.abort(s.signal.reason),"abort");s.signal.aborted?a():s.signal.addEventListener("abort",a,{once:!0});let d=s.requestId;const A=he({request_id:d,run_id:e.run_id,node_id:e.node_id,controller:o});let m,h,j,u,_,g,w,P="allowed";try{try{await ue(t.provider)}catch{throw pe(t.provider)}let n=await U(t.provider,t.version),k=!1,i,b,q,L=!1,J=!1,F,H=0;for(;;){const y=Ue(n,t.tool);if(j=`${n.id}:${y.group}`,oe(e,n.id,y.group),n.transport.kind!=="machine"&&t.machine!==void 0)throw new p("invalid_argument",`provider "${n.id}" is not served by a device, so it takes no machine`,{param:"machine"});n.transport.kind==="machine"&&F===void 0&&(F=await je(t.machine,void 0,{newest:V(n.major,t.tool)}),H=Date.now()+(n.commands.timeouts?.requestMs??qe));let M=null;if(t.approval&&!L){const l=fe(e,t,Y(n,y));q=l.option,M=l.prepared,w=t.approval,d=l.requestId,L=!0}const C={params:y.params.filter(l=>"type"in l&&l.type==="file").map(l=>l.name),output:y.output.find(l=>l.type==="file")?.name??null},x=JSON.stringify(C);if(b!==void 0&&b!==x)throw new p("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});b=x,M&&(i=await G(e,C,t.arguments,M,{signal:o.signal})),i??=await ce(e,C,t.arguments,{signal:o.signal});const{sub:O,grantee:R}=Oe(e);if(n.transport.kind==="machine"){const l=await Je(e,n,t,y,i,{sub:O,requestId:d,deadlineAt:H,signal:o.signal,machine:F});return g=l.responder,m=l.cleanup,{status:200,body:l.result}}const E=await ve({audience:n.transport.endpoint,sub:O,grantee:R,initiating_app:R,scope:Ie(n,y)},{signal:o.signal});u=E.jti;const Z={registration:{name:n.id,endpoint:n.transport.endpoint},...n.commands.baseUrl?{baseUrl:n.commands.baseUrl}:{},...n.commands.timeouts?{timeouts:n.commands.timeouts}:{},params:y.params,rest:y.rest,output:y.output,commandPath:[n.id,...t.tool.split(" ")]},ee=we(Z,i.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:d,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...Le(e),...q?{"Crtr-Approval":q}:{},...i.headers}});h??=Date.now()+(n.commands.timeouts?.requestMs??3e4);let f;try{if(f=await be(ee,{connectMs:n.commands.timeouts?.connectMs,requestMs:n.commands.timeouts?.requestMs,deadlineAt:h,signal:o.signal}),o.signal.aborted)throw o.signal.reason??new Error("provider call aborted")}catch(l){throw o.signal.aborted?o.signal.reason??l:l instanceof ye?new p("provider_unavailable",l.message,{origin:"provider",details:{status:null,code:l.kind}}):l}if(_??=Me({provider:n.id,tool:t.tool,request_id:d,run_id:e.run_id,sub:O,grantee:R,payer:E.payer,ledger_grantee:e.class==="person"?ie:R,jti:E.jti,status:f.status,headers:f.headers}),g=f.headers["crtr-responder"],f.status===401&&ge(E.key),f.status>=200&&f.status<300){const l=f.body;if(!l||typeof l!="object"||Array.isArray(l))throw new p("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let N;try{N=await z(e,i,l,{signal:o.signal})}catch($){throw $ instanceof p&&$.code==="provider_error"?new p("provider_bad_response",$.message,{origin:"provider",details:{status:f.status,message:$.message}}):$}m=N.cleanup;const T=I(y.output,N.result);if(T)throw new p("provider_bad_response",`provider result has an invalid output field "${T.field}"`,{origin:"provider",param:T.field,details:{status:f.status,...T}});if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");return{status:200,body:N.result}}const S=B(f.body);if(f.status===403&&S.code==="approval_required"&&!q&&!J){J=!0;const l=await me(e,t,d,S.question,o.signal,Y(n,y),i);if(w=l.questionId,q=l.option,i=await G(e,C,t.arguments,i,{signal:o.signal}),h=void 0,o.signal.aborted)throw new p("user_denied","the approval wait was canceled");continue}if(f.status===403&&S.code==="approval_required"&&q)throw D(f.status,f.body,f.headers,!0);if(f.status!==401&&S.manifest_stale===!0&&!k){k=!0,await le(n.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}}),n=await U(n.id,t.version);continue}throw D(f.status,f.body,f.headers,!!q)}}catch(n){throw n instanceof p&&n.question_id&&(w=n.question_id),g===void 0&&n instanceof p&&typeof n.responder=="string"&&(g=n.responder),P=`refused:${n instanceof p?n.code:"server_error"}`,m?.(),n}finally{try{Ae({request_id:d,caller:e,provider:t.provider,tool:t.tool,scope_checked:j,outcome:P,jti:u,receipt_id:_,responder:g,question_id:w})}finally{A()}s.signal.removeEventListener("abort",a)}}v(vr,"callProvider");async function Je(e,r,s,c,t,o){const{machine:a}=o,d=Fe(e,o.sub),A={machine:a,envelope:d,signal:o.signal,deadlineAt:o.deadlineAt,mapFailure:D};if(V(r.major,s.tool)){const i=await $e(A,t.arguments,r.major),b=I(c.output,i);if(b)throw new p("provider_bad_response",`the libraries result has an invalid output field "${b.field}"`,{origin:"provider",param:b.field,details:{status:200,...b}});return{result:i,responder:void 0,cleanup:void 0}}let m=t.arguments,h=null,j="";if(Ee(r.major,s.tool,t.arguments)){const i=await Ce(A,t.arguments,r.major);if(o.deadlineAt-Date.now()<=0)throw Q(a,"timeout","");i!==null&&(m=i.arguments,h=i.services,j=i.release)}const u=await ke(a,{tool:s.tool,arguments:m,version:r.major,requestId:o.requestId,runId:e.run_id,deadlineAt:o.deadlineAt,upload:t.upload===null?null:{id:t.upload.upload_id,link:t.upload.upload_link}},d,o.signal);if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");if("unreachable"in u)throw Q(a,u.unreachable.reason,u.unreachable.message);const _=u.headers["crtr-responder"],g=B(u.body);if(u.status<200||u.status>=300){const i=Pe(a,u.status,typeof g.code=="string"?g.code:void 0,typeof g.message=="string"?g.message:void 0,u.body),b=i===null&&u.status>=500&&typeof g.code=="string"?new p("provider_error",typeof g.message=="string"?g.message:`the device answered HTTP ${u.status}`,{origin:"runtime",status:u.status,details:{code:g.code,status:u.status}}):null;throw Object.assign(i??b??D(u.status,u.body,u.headers),{responder:_})}const w=u.body&&typeof u.body=="object"&&!Array.isArray(u.body)?u.body.result:void 0;if(!w||typeof w!="object"||Array.isArray(w))throw Object.assign(new p("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:u.status,message:"the device result is not a JSON object"}}),{responder:_});let P=w;if(h!==null){const i=Re(P,h);if(Se(a,d,j,i.calls),"error"in i)throw Object.assign(i.error,{responder:_});P=i.result}let n;try{n=await z(e,t,P,{signal:o.signal})}catch(i){throw i instanceof p&&i.code==="provider_error"?Object.assign(new p("provider_bad_response",i.message,{origin:"provider",details:{status:u.status,message:i.message}}),{responder:_}):i}const k=I(c.output,n.result);if(k)throw n.cleanup?.(),Object.assign(new p("provider_bad_response",`the device result has an invalid output field "${k.field}"`,{origin:"provider",param:k.field,details:{status:u.status,...k}}),{responder:_});return{result:n.result,responder:_,cleanup:n.cleanup}}v(Je,"callDevice");export{vr as callProvider,Le as crtrCallerHeader,D as mapProviderResponse,Me as onProviderResponse,W as providerLeaves,mr as providerTools};
@@ -61,7 +61,9 @@ A definition with `version: N`, or a list of definitions with one per supported
61
61
  - removes a parameter, changes its type, makes it required, or adds a required one; removes an enum choice from a flag; or changes the environment variable it defaults from; or moves an existing positional;
62
62
  - gains paging where it returned everything: a limit flag whose constraint states a default page size ("default N"), or a cursor input together with a `next_cursor`-style output. The answer keeps its shape, so callers that do not page silently get a partial list. A changed stated page size is breaking too. A new `--cursor`, `--limit` with no stated default, or `next_cursor` output alone is additive, and so is a declared `default`, which crtr applies while parsing and does not send;
63
63
  - changes how the request is built: method, route (apart from the `/v<N>` segment), body root, constant body fields, a parameter's placement or wire name, or streaming;
64
- - removes an output field, changes its type, makes it optional, drops an enum value, or adds a `file` output.
64
+ - removes an output field, changes its type, makes it optional, or adds a `file` output;
65
+ - widens an output so a client typed against the previous archive could read a value outside its type: adds an enum value or a union variant, adds `| null`, turns `int` into `number`, replaces a declared type with `unknown`, or opens a closed object by dropping its `children`;
66
+ - narrows a `json` flag's shape so a value the previous archive accepted is refused: removes an enum value or a union variant, adds a required member, or closes an open object by adding `children`.
65
67
 
66
68
  A new optional flag, a new leaf, a new optional output field and changed prose are additive.
67
69
 
@@ -26,7 +26,7 @@ Each leaf declares an `output` object. Field object keys are returned verbatim i
26
26
 
27
27
  Fields are required by default. Pass `{ required: false }` for a field that can be absent. `field.nullable(field.string('...'))` declares a present field that can be `string | null`; it does not make the field optional.
28
28
 
29
- The handler result type is inferred from `output`, including structured members and elements. TypeScript reports a missing required field or a wrong value type at the `defineLeaf` call. `definePlugin` throws `PluginDefinitionError` for a malformed structured field, a file field that is not top-level, or a second file output. The Fetch handler checks the returned object's top-level fields again before sending it. It rejects missing fields, known type mismatches, and keys that were not declared, returning a non-2xx `handler_output_invalid` error instead of a JSON success response. It does not look inside objects or arrays, so their members are checked only by TypeScript.
29
+ The handler result type is inferred from `output`, including structured members and elements. TypeScript reports a missing required field or a wrong value type at the `defineLeaf` call. `definePlugin` throws `PluginDefinitionError` for a malformed structured field, a file field that is not top-level, or a second file output. The Fetch handler checks the returned object's top-level fields again before sending it. It rejects missing fields, known type mismatches, and keys that were not declared, returning a non-2xx `handler_output_invalid` error instead of a JSON success response. It also checks nested values against the declared shape: an object with declared `children` is closed, so a member it does not declare, a missing required member, an array element or map value of the wrong shape, or an enum value outside `values` is rejected. An `object` declared without `children` accepts any members.
30
30
 
31
31
  ## Structured fields
32
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.519",
3
+ "version": "0.3.520",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -139,7 +139,7 @@
139
139
  },
140
140
  "license": "UNLICENSED",
141
141
  "dependencies": {
142
- "@north-light/identity": "0.3.519",
142
+ "@north-light/identity": "0.3.520",
143
143
  "@earendil-works/pi-agent-core": "1.1.0",
144
144
  "@earendil-works/pi-ai": "1.1.0",
145
145
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.519",
3
+ "version": "0.3.520",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.519",
3
+ "version": "0.3.520",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.519",
9
+ "version": "0.3.520",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -24,7 +24,7 @@
24
24
  "@milkdown/preset-gfm": "^7.22.3",
25
25
  "@milkdown/prose": "^7.22.3",
26
26
  "@milkdown/utils": "^7.22.3",
27
- "@north-light/identity": "0.3.519",
27
+ "@north-light/identity": "0.3.520",
28
28
  "cron-parser": "^5.6.0",
29
29
  "esbuild": "^0.27.7",
30
30
  "jose": "^6.2.12",
@@ -12468,17 +12468,17 @@
12468
12468
  },
12469
12469
  "packages/crouter-api": {
12470
12470
  "name": "@north-light/api",
12471
- "version": "0.3.519",
12471
+ "version": "0.3.520",
12472
12472
  "license": "UNLICENSED"
12473
12473
  },
12474
12474
  "packages/crouter-env-docker": {
12475
12475
  "name": "@north-light/env-docker",
12476
- "version": "0.3.519",
12476
+ "version": "0.3.520",
12477
12477
  "license": "UNLICENSED"
12478
12478
  },
12479
12479
  "packages/crouter-identity": {
12480
12480
  "name": "@north-light/identity",
12481
- "version": "0.3.519",
12481
+ "version": "0.3.520",
12482
12482
  "license": "UNLICENSED",
12483
12483
  "dependencies": {
12484
12484
  "jose": "^6.2.1"
@@ -12486,20 +12486,20 @@
12486
12486
  },
12487
12487
  "packages/crouter-plugin": {
12488
12488
  "name": "@north-light/plugin",
12489
- "version": "0.3.519",
12489
+ "version": "0.3.520",
12490
12490
  "license": "UNLICENSED",
12491
12491
  "dependencies": {
12492
- "@north-light/api": "0.3.519",
12493
- "@north-light/identity": "0.3.519"
12492
+ "@north-light/api": "0.3.520",
12493
+ "@north-light/identity": "0.3.520"
12494
12494
  }
12495
12495
  },
12496
12496
  "packages/crouter-sdk": {
12497
12497
  "name": "@north-light/sdk",
12498
- "version": "0.3.519",
12498
+ "version": "0.3.520",
12499
12499
  "license": "UNLICENSED",
12500
12500
  "dependencies": {
12501
- "@north-light/api": "0.3.519",
12502
- "@north-light/identity": "0.3.519",
12501
+ "@north-light/api": "0.3.520",
12502
+ "@north-light/identity": "0.3.520",
12503
12503
  "jose": "^6.2.1"
12504
12504
  },
12505
12505
  "bin": {