@north-light/cli 0.3.523 → 0.3.525
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/command-manifest/result.d.ts +4 -2
- package/dist/api/command-manifest/result.js +1 -1
- package/dist/api/command-manifest/schema.js +1 -1
- package/dist/api/plugin-manifest-schema.d.ts +3 -0
- package/dist/builtin-memory/crouter-plugin/bundles-and-memory.md +2 -2
- package/dist/builtin-memory/crouter-plugin/commands.md +2 -2
- package/dist/builtin-memory/crouter-plugin/deploying.md +2 -2
- package/dist/builtin-memory/crouter-plugin/output.md +3 -1
- package/dist/clients/attach/chrome/roster.d.ts +1 -1
- package/dist/clients/attach/chrome/roster.js +4 -4
- package/dist/clients/attach/render/chat-view.d.ts +9 -0
- package/dist/clients/attach/render/chat-view.js +3 -3
- package/dist/clients/attach/render/page-block.d.ts +2 -0
- package/dist/clients/attach/render/page-block.js +1 -1
- package/dist/clients/attach/session/connection.d.ts +1 -1
- package/dist/clients/attach/session/context.d.ts +1 -1
- package/dist/clients/attach/session/profile-files.d.ts +3 -3
- package/dist/clients/attach/session/profile-files.js +1 -1
- package/dist/clients/attach/viewer.js +633 -756
- package/dist/commands/pkg/browse/command-view.js +1 -1
- package/dist/core/canvas/db.d.ts +28 -2
- package/dist/core/canvas/db.js +2 -2
- package/dist/core/canvas/nav-model.js +1 -1
- package/dist/core/canvas/recovery.js +1 -1
- package/dist/core/conversation-store/client.d.ts +2 -2
- package/dist/core/conversation-store/index.d.ts +30 -2
- package/dist/core/conversation-store/index.js +1 -1
- package/dist/core/conversation-store/patch.js +1 -1
- package/dist/core/graph/watches.d.ts +2 -0
- package/dist/core/graph/watches.js +9 -9
- package/dist/core/help.js +7 -7
- package/dist/core/llm/run.js +2 -2
- package/dist/core/runtime/broker/fault-retry.d.ts +9 -3
- package/dist/core/runtime/broker/fault-retry.js +1 -1
- package/dist/core/runtime/broker/rebind.d.ts +4 -0
- package/dist/core/runtime/broker/rebind.js +1 -1
- package/dist/core/runtime/broker/run-turn-queue.d.ts +2 -0
- package/dist/core/runtime/broker/run-turn-queue.js +1 -1
- package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
- package/dist/core/runtime/broker-persona-guidance.js +3 -3
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/close.d.ts +2 -2
- package/dist/core/runtime/close.js +1 -1
- package/dist/daemon/reconcilers/broker-supervision.js +4 -4
- package/dist/daemon/runs/observer.js +2 -2
- package/docs/plugin/bundles-and-memory.md +2 -2
- package/docs/plugin/commands.md +2 -2
- package/docs/plugin/deploying.md +2 -2
- package/docs/plugin/output.md +3 -1
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -3,6 +3,8 @@ export interface DeclaredOutputShape {
|
|
|
3
3
|
constraint: string;
|
|
4
4
|
/** Named members of an `object` value. Declaring them closes the object. */
|
|
5
5
|
children?: DeclaredOutputField[];
|
|
6
|
+
/** Shape of every member `children` does not declare. Absent means the object is closed. */
|
|
7
|
+
extra?: DeclaredOutputShape;
|
|
6
8
|
/** Shape of each contained value of an `array` or `map` value. */
|
|
7
9
|
items?: DeclaredOutputShape;
|
|
8
10
|
/** Alternatives of a `union` value. */
|
|
@@ -31,7 +33,7 @@ export declare function validateDeclaredResult(output: DeclaredOutputField[], re
|
|
|
31
33
|
} | null;
|
|
32
34
|
/** Checks one JSON value against one shape, recursively: object members, array
|
|
33
35
|
* items, map values, union variants, enum values and nullability. An object that
|
|
34
|
-
* declares `children` is closed
|
|
35
|
-
* depth
|
|
36
|
+
* declares `children` is closed unless it carries `extra`: an undeclared
|
|
37
|
+
* member is a mismatch at any depth, or must fit `extra`. Returns null, or the first mismatch with its full path from `path`.
|
|
36
38
|
* A declared type that is not recognized is accepted. */
|
|
37
39
|
export declare function validateValue(shape: DeclaredOutputShape, value: unknown, path: string): ValueMismatch | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var g=Object.defineProperty;var c=(
|
|
1
|
+
var g=Object.defineProperty;var c=(l,n)=>g(l,"name",{value:n,configurable:!0});import{isRecord as d}from"../../shared/predicates.js";function j(l,n){for(const e of l){const u=n[e.name];if(!(Object.prototype.hasOwnProperty.call(n,e.name)&&u!==void 0)){if(e.required)return{field:e.name};continue}const t=s(e,u,e.name);if(t!==null)return{field:t.path,receivedType:t.receivedType,expected:t.expected}}return null}c(j,"validateDeclaredResult");function s(l,n,e){const u=l.type.split("|").map(t=>t.trim()).filter(Boolean),r=u.filter(t=>t.toLowerCase()!=="null");if(n===null&&r.length<u.length||r.length===1&&!p(r[0])||!u.every(t=>p(t)||/^[a-z][a-z0-9_-]*$/.test(t)))return null;if(r.length===1)return b(r[0],l,n,e);for(const t of r)if(b(t,l,n,e)===null)return null;return m(e,l.type,n)}c(s,"validateValue");function m(l,n,e){const u=e===null?"null":Array.isArray(e)?"array":typeof e;return{path:l,expected:n,receivedType:u}}c(m,"mismatch");function p(l){const n=l.toLowerCase();return n==="null"||n==="string"||n==="path"||n==="markdown"||n==="int"||n==="integer"||n==="number"||n==="float"||n==="bool"||n==="boolean"||n==="array"||n.endsWith("[]")||n==="object"||n==="map"||n==="union"||n==="unknown"||n==="enum"||n==="file"}c(p,"knownType");function b(l,n,e,u){const r=l.toLowerCase(),t=c(f=>m(u,f,e),"fail");if(r==="null")return e===null?null:t("null");if(r==="unknown")return null;if(r==="enum")return n.values===void 0||typeof e=="string"&&n.values.includes(e)?null:t(`one of ${(n.values??[]).join(", ")}`);if(r==="file")return d(e)||typeof e=="string"?null:t("file");if(r==="string"||r==="path"||r==="markdown")return typeof e=="string"?null:t(r);if(r==="int"||r==="integer")return typeof e=="number"&&Number.isInteger(e)?null:t("int");if(r==="number"||r==="float")return typeof e=="number"?null:t("number");if(r==="bool"||r==="boolean")return typeof e=="boolean"?null:t("bool");if(r==="array"){if(!Array.isArray(e))return t("array");if(n.items===void 0)return null;for(let f=0;f<e.length;f++){const i=s(n.items,e[f],`${u}[${f}]`);if(i!==null)return i}return null}if(r.endsWith("[]"))return Array.isArray(e)?null:t("array");if(r==="map"){if(!d(e))return t("map");if(n.items===void 0)return null;for(const[f,i]of Object.entries(e)){const o=s(n.items,i,`${u}[${JSON.stringify(f)}]`);if(o!==null)return o}return null}if(r==="object"){if(!d(e))return t("object");if(n.children===void 0)return null;for(const i of n.children){const o=e[i.name];if(!(Object.prototype.hasOwnProperty.call(e,i.name)&&o!==void 0)){if(i.required)return{path:`${u}.${i.name}`,expected:`${i.type} (required)`,receivedType:"undefined"};continue}const y=s(i,o,`${u}.${i.name}`);if(y!==null)return y}const f=new Set(n.children.map(i=>i.name));for(const i of Object.keys(e)){if(f.has(i))continue;if(n.extra===void 0)return m(`${u}.${i}`,"no such member (the object is closed)",e[i]);if(e[i]===void 0)continue;const o=s(n.extra,e[i],`${u}.${i}`);if(o!==null)return o}return null}if(r==="union"){const f=[];for(const o of n.variants??[]){const a=s(o,e,u);if(a===null)return null;f.push(a)}const i=f[0];return i!==void 0&&i.path!==u&&f.every(o=>o.path===i.path)?{path:i.path,expected:[...new Set(f.map(o=>o.expected))].join(" | "),receivedType:i.receivedType}:t((n.variants??[]).map(o=>o.type).join(" | "))}return typeof e=="string"&&e===l?null:t(l)}c(b,"atomMismatch");export{j as validateDeclaredResult,s as validateValue};
|
|
@@ -1 +1 @@
|
|
|
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
|
+
var V=Object.defineProperty;var S=(e,t)=>V(e,"name",{value:t,configurable:!0});import{isRecord as x}from"../../shared/predicates.js";const Z=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","extra","items","variants","values"]),P=new Set(["type","constraint","children","extra","items","variants","values"]),oe=/^[A-Z][A-Z0-9_]*$/;function s(e){return e===null?"null":Array.isArray(e)?"array":typeof e}S(s,"typeName");function R(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}S(R,"checkKeys");function re(e,t,a){if(!x(e))return a("command_node_invalid","rootEntry must be an object",s(e),"a { concept, description, whenToUse } object","Fix rootEntry.",t),null;if(!R(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`,s(e[n]),"a non-empty string",`Add rootEntry.${n}.`,`${t}.${n}`),null;return{concept:e.concept,description:e.description,whenToUse:e.whenToUse}}S(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",s(e),"an array of params (or omitted)","Fix params.",t),null;const n=[],o=new Set;let p=0;for(let h=0;h<e.length;h++){const r=e[h],i=`${t}[${h}]`;if(!x(r))return a("command_node_invalid","param must be an object",s(r),"a param object","Fix the param.",i),null;const c=r.kind;if(typeof c!="string"||!(c in H))return a("command_node_invalid","param kind must be positional|flag|stdin|context-file",String(c),"positional | flag | stdin | context-file","Fix the param kind.",`${i}.kind`),null;if(!R(r,H[c],i,a))return null;const u=r.name;if(typeof u!="string"||!N.test(u))return a("command_node_invalid","param name must be a kebab-case token",String(u),"a kebab-case token","Rename the param.",`${i}.name`),null;if(o.has(u))return a("command_node_invalid","duplicate param name",u,"unique param names","Rename the duplicate param.",`${i}.name`),null;if(o.add(u),typeof r.required!="boolean")return a("command_node_invalid","param required must be a boolean",s(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",s(r.constraint),"an inline constraint string","Add a constraint.",`${i}.constraint`),null;if(c==="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 f=!1;if(r.repeatable!==void 0){if(typeof r.repeatable!="boolean")return a("command_node_invalid","repeatable must be a boolean",s(r.repeatable),"true | false | omitted","Fix repeatable.",`${i}.repeatable`),null;f=r.repeatable}if(f&&r.type==="file")return a("command_node_invalid","repeatable is not valid on a file positional",`${u}: file`,"one file per file param","Remove repeatable, or declare one file param per file.",`${i}.repeatable`),null;const d=M(r,i,a);if(d===null)return null;const m=G(r,i,a);if(m===null)return null;if(f&&m!==void 0)return a("command_node_invalid","repeatable positional must not declare defaultFromEnv",u,"no defaultFromEnv on a repeatable positional (values accumulate into an array)","Remove defaultFromEnv, or drop repeatable.",`${i}.defaultFromEnv`),null;n.push({kind:"positional",name:u,...r.type!==void 0?{type:r.type}:{},required:r.required,constraint:r.constraint,...f?{repeatable:!0}:{},...d!==void 0?{encoding:d}:{},...m!==void 0?{defaultFromEnv:m}:{}})}else if(c==="stdin")n.push({kind:"stdin",name:u,required:r.required,constraint:r.constraint});else if(c==="context-file")n.push({kind:"context-file",name:u,required:r.required,constraint:r.constraint,...typeof r.shape=="string"?{shape:r.shape}:{}});else if(c==="flag"){const f=r.type;if(typeof f!="string"||!J.has(f))return a("command_node_invalid","flag type must be string|int|bool|path|enum|file|json",String(f),"string | int | bool | path | enum | file | json","Fix the flag type.",`${i}.type`),null;let d;if(f==="enum"){const _=r.choices;if(!Array.isArray(_)||_.length===0||!_.every(j=>typeof j=="string"))return a("command_node_invalid","enum flag requires a non-empty string choices array",s(_),"a non-empty array of strings","Declare choices for the enum flag.",`${i}.choices`),null;d=_}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 m;if(f==="json"){const _=r.shape;if(!x(_))return a("command_node_invalid","json flag requires a shape object",s(_),"a { type, constraint, children?, items?, variants?, values? } object","Declare the shape of the JSON value the flag takes.",`${i}.shape`),null;if(!R(_,P,`${i}.shape`,a))return null;const j=A(_,`${i}.shape`,!1,a);if(j===null)return null;m=j}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 y=!1;if(r.repeatable!==void 0){if(typeof r.repeatable!="boolean")return a("command_node_invalid","repeatable must be a boolean",s(r.repeatable),"true | false | omitted","Fix repeatable.",`${i}.repeatable`),null;y=r.repeatable}if(f==="json"&&(r.default!==void 0||r.defaultFromEnv!==void 0))return a("command_node_invalid","a json flag takes no default",u,"no default or defaultFromEnv on a json flag","Remove the default.",`${i}.${r.default!==void 0?"default":"defaultFromEnv"}`),null;if(y&&(f==="bool"||f==="path"||f==="file"||f==="json"))return a("command_node_invalid",`repeatable is not valid on a ${f} flag`,`${u}: ${f}`,"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",u,"no default on a repeatable flag (values accumulate into an array)","Remove the default.",`${i}.default`),null;const E=M(r,i,a);if(E===null)return null;const v=G(r,i,a);if(v===null)return null;if(y&&v!==void 0)return a("command_node_invalid","repeatable flag must not declare defaultFromEnv",u,"no defaultFromEnv on a repeatable flag (values accumulate into an array)","Remove defaultFromEnv, or drop repeatable.",`${i}.defaultFromEnv`),null;n.push({kind:"flag",name:u,type:f,...m!==void 0?{shape:m}:{},...d!==void 0?{choices:d}:{},...E!==void 0?{encoding:E}:{},required:r.required,constraint:r.constraint,...r.default!==void 0?{default:r.default}:{},...y?{repeatable:!0}:{},...v!==void 0?{defaultFromEnv:v}:{}})}}return n}S(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}S(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}S(G,"validateDefaultFromEnv");function Y(e){return e.split("|").map(t=>t.trim().toLowerCase()).filter(t=>t.length>0&&t!=="null").join(" | ")}S(Y,"outputBaseType");function le(e){return e.split("|").some(t=>/^file(\[\])*$/i.test(t.trim()))}S(le,"mentionsFile");function A(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",s(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",s(e.constraint),"a constraint string","Add a constraint.",`${t}.constraint`),null;const o=e.type,p=Y(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 h=[["children",["object"]],["items",["array","map"]],["variants",["union"]],["values",["enum"]]];for(const[d,m]of h)if(e[d]!==void 0&&!m.includes(p)){const y=m.map(E=>`"${E}"`).join(" or ");return n("command_node_invalid",`output ${d} is only valid on a ${m.join(" or ")} field`,`type ${o}`,`type ${y} (optionally "| null")`,`Remove ${d}, or change the type to ${m[0]}.`,`${t}.${d}`),null}if(p==="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(p==="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.extra!==void 0){const d=e.extra,m=`${t}.extra`;if(p!=="object"||e.children===void 0)return n("command_node_invalid","extra is only valid on an object field that declares children",`type ${o}${e.children===void 0?" without children":""}`,'type "object" with children (optionally "| null")',"Remove extra, or declare children; an object without children already accepts any members, and a map takes items.",m),null;if(!x(d))return n("command_node_invalid","output extra must be an object",s(d),"a { type, constraint, ... } shape","Fix the extra shape.",m),null;if(!R(d,P,m,n))return null;const y=A(d,m,!1,n);if(y===null)return null;i=y}let c;if(e.items!==void 0){const d=e.items,m=`${t}.items`;if(!x(d))return n("command_node_invalid","output items must be an object",s(d),"a { type, constraint, children?, items?, variants?, values? } object","Fix the items shape.",m),null;if(!R(d,P,m,n))return null;const y=A(d,m,!1,n);if(y===null)return null;c=y}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"}`:s(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 m=0;m<d.length;m++){const y=`${t}.variants[${m}]`,E=d[m];if(!x(E))return n("command_node_invalid","union variant must be an object",s(E),"a { type, constraint, ... } object","Fix the variant shape.",y),null;if(!R(E,P,y,n))return null;const v=A(E,y,!1,n);if(v===null)return null;u.push(v)}}let f;if(e.values!==void 0){const d=e.values;if(!Array.isArray(d)||d.length===0||!d.every(m=>typeof m=="string"&&m.length>0))return n("command_node_invalid","enum output values must be a non-empty array of strings",s(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;f=d}return{type:o,constraint:e.constraint,...r!==void 0?{children:r}:{},...i!==void 0?{extra:i}:{},...c!==void 0?{items:c}:{},...u!==void 0?{variants:u}:{},...f!==void 0?{values:f}:{}}}S(A,"validateOutputShape");function de(e,t,a){return z(e,t,!0,a)}S(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",s(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 h;for(let r=0;r<e.length;r++){const i=e[r],c=`${t}[${r}]`;if(!x(i))return n("command_node_invalid","output field must be an object",s(i),"a { name, type, required, constraint } object","Fix the output field.",c),null;if(!R(i,ae,c,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",s(i.name),"a field name","Add a name.",`${c}.name`),null;if(p.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.",`${c}.name`),null;if(p.add(i.name),typeof i.required!="boolean")return n("command_node_invalid","output field required must be a boolean",s(i.required),"true | false","Set required.",`${c}.required`),null;const u=A(i,c,a,n);if(u===null)return null;if(Y(u.type)==="file"){if(h!==void 0)return n("command_node_invalid","a leaf declares at most one file output",`${h}, ${i.name}`,'one top-level output field of type "file"',"Return the extra files through a separate leaf, or drop the extra file field.",`${c}.type`),null;h=i.name}o.push({name:i.name,...u,required:i.required})}return o}S(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",s(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",s(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 hidden|normal|common|important",String(e.tier),"hidden | normal | common | important","Fix or omit tier.",`${t}.tier`),!1):!0}S(B,"checkCommon");function me(e,t,a,n,o,p={}){const h=t.join(".");if(!x(e))return o("command_node_invalid","node must be an object",s(e),"a branch or leaf node object","Fix the node.",h),null;const r=e.kind;return r==="branch"?se(e,h,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.",`${h}.kind`),null):ce(e,h,n,o):(o("command_node_invalid",'node kind must be "branch" or "leaf"',String(r),"branch | leaf","Set kind to branch or leaf.",`${h}.kind`),null)}S(me,"validateCommandNode");function se(e,t,a,n,o,p){const h=p.allowPassthrough!==!1;if(!R(e,n==="exec"&&h?X:C,t,o)||!B(e,t,o))return null;const i=e.rootEntry!==void 0,c=p.topLevelRootEntry??"required";if(a&&c==="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||c==="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 u=e.extensible!==void 0;if(u&&(!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 f;if(i){const v=re(e.rootEntry,`${t}.rootEntry`,o);if(v===null)return null;f=v}if(typeof e.summary!="string"||e.summary.length===0)return o("command_node_invalid","branch summary must be a non-empty string",s(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",s(e.model),"a string or omitted","Fix or remove model.",`${t}.model`),null;let d;if(n==="exec"&&e.passthrough!==void 0){const v=ue(e.passthrough,`${t}.passthrough`,o);if(v===null)return null;d=v}if(u&&d!==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 m=e.children;if(d!==void 0){if(!Array.isArray(m)||m.length!==0)return o("command_node_invalid","a passthrough branch must declare children: []",Array.isArray(m)?`${m.length} children`:s(m),"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(m))return o("command_node_invalid","branch children must be an array",s(m),"an array of branch/leaf nodes (may be empty for populated by mounts)","Fix children.",`${t}.children`),null;const y=[],E=new Set;for(let v=0;v<m.length;v++){const _=me(m[v],[...t.split("."),`children[${v}]`],!1,n,o,p);if(_===null)return null;if(E.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;E.add(_.name),y.push(_)}return{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...f!==void 0?{rootEntry:f}:{},...u?{extensible:!0}:{},summary:e.summary,...e.model!==void 0?{model:e.model}:{},...d!==void 0?{passthrough:d}:{},children:y}}S(se,"validateBranch");function ue(e,t,a){if(!x(e))return a("command_node_invalid","passthrough must be an object",s(e),"an object { bin, installHint }","Fix or remove passthrough.",t),null;if(!R(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`,s(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}}S(ue,"validatePassthrough");function ce(e,t,a,n){if(!R(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",s(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 h=e.effects;if(!Array.isArray(h)||h.length===0||!h.every(u=>typeof u=="string"))return n("command_node_invalid","leaf effects must be a non-empty string array",s(h),'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:h};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:s(i),'a group id of 1-64 letters, digits, "_" or "-" (no ":" or whitespace)',`Rename the group on leaf ${t}.`,`${t}.group`),null;const c=ye(e.rest,t,o,n);return c===null?null:{...r,rest:c,...i!==void 0?{group:i}:{}}}S(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",s(e),"{ method, path, streaming?, params }","Provide a valid rest mapping.",o),null;const p=new Set(["method","path","streaming","body","bodyRoot","params"]),h=Object.keys(e).filter(l=>!p.has(l));if(h.length>0)return n("command_rest_invalid","unknown rest keys",h.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 c=new Set,u=/\{([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\}/g;let f;for(;(f=u.exec(i))!==null;)c.add(f[1]);let d=!1;if(e.streaming!==void 0){if(typeof e.streaming!="boolean")return n("command_rest_invalid","streaming must be a boolean",s(e.streaming),"true | false | omitted","Fix streaming.",`${o}.streaming`),null;d=e.streaming}const m=e.params;if(!x(m))return n("command_rest_invalid","params must be an object",s(m),"a mapping of param names to placement specs","Provide params.",`${o}.params`),null;const y=new Map;for(const l of a)y.set(l.name,l);const E={},v=[],_=new Set;for(const[l,$]of Object.entries(m)){if(!y.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",s($),"{ in, as? }","Fix the param mapping.",`${o}.params.${l}`),null;const k=new Set(["in","as"]),T=Object.keys($).filter(F=>!k.has(F));if(T.length>0)return n("command_rest_invalid","unknown param mapping keys",T.join(", "),"only: in, as","Remove the unknown keys.",`${o}.params.${l}`),null;const b=$.in;if(typeof b!="string"||!he.has(b))return n("command_rest_invalid","param placement must be path|query|body|header",String(b),"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(b==="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(b==="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=y.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 U=y.get(l);if((U.kind==="flag"||U.kind==="positional")&&U.repeatable===!0&&b!=="body")return n("command_rest_invalid","repeatable param must use body placement",`${l}: in "${b}"`,'in: "body" (array values ship as a JSON array)',`Map "${l}" to body placement.`,`${o}.params.${l}.in`),null;if(b==="body"){const w=y.get(l).kind==="stdin",I=g||l;v.push({name:l,isStdin:w,as:I})}E[l]={in:b,...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 j=new Set;for(const[l,$]of Object.entries(E))$.in==="path"&&j.add(l);if(j.size!==c.size||![...c].every(l=>j.has(l)))return n("command_rest_invalid","mismatch between path placeholders and path params",`placeholders: ${[...c].join(", ")} | params: ${[...j].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 K;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",s(l),"a map of constant literal body fields (string | number | boolean)","Fix body.",`${o}.body`),null;const $={};for(const[b,g]of Object.entries(l)){if(typeof g!="string"&&typeof g!="number"&&typeof g!="boolean")return n("command_rest_invalid",`body.${b} must be a string, number, or boolean`,s(g),"string | number | boolean",`Fix body.${b}.`,`${o}.body.${b}`),null;$[b]=g}const k=new Set(v.map(b=>b.as)),T=Object.keys($).filter(b=>k.has(b));if(T.length>0)return n("command_rest_invalid","body constant key collides with a param's body key",T.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;K=$}let O;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",s(l),"a non-empty string key","Fix bodyRoot.",`${o}.bodyRoot`),null;O=l}for(const[l,$]of Object.entries(E)){const k=y.get(l);if((k.kind==="context-file"||(k.kind==="positional"||k.kind==="flag")&&k.type==="path")&&$.in!=="body"){const b=k.kind==="context-file"?"context-file":`path ${k.kind}`;return n("command_rest_invalid",`file param (${b}) must be placed in body`,`${l}: in "${$.in}"`,'in: "body"',"Move the file param to body.",`${o}.params.${l}.in`),null}}const q=new Map;for(const l of v)q.has(l.as)||q.set(l.as,[]),q.get(l.as).push({name:l.name,isStdin:l.isStdin});for(const[l,$]of q.entries())if($.length>1){const k=$.filter(b=>b.isStdin).length,T=$.filter(b=>!b.isStdin).length;if(!(k===1&&T===1))return n("command_rest_invalid","invalid body as grouping",$.map(b=>b.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,...d!==!1?{streaming:d}:{},...K!==void 0?{body:K}:{},...O!==void 0?{bodyRoot:O}:{},params:E}}S(ye,"validateRestMapping");export{le as mentionsFile,Y as outputBaseType,me as validateCommandNode};
|
|
@@ -16,6 +16,9 @@ export interface ManifestFieldShape {
|
|
|
16
16
|
constraint: string;
|
|
17
17
|
/** Named members; only when the base type is `object`. */
|
|
18
18
|
children?: ManifestField[];
|
|
19
|
+
/** The shape every member not named in `children` must have; only when the base type is `object` and `children` is present.
|
|
20
|
+
* An object with `children` and no `extra` is closed. */
|
|
21
|
+
extra?: ManifestFieldShape;
|
|
19
22
|
/** Value shape; only when the base type is `array` or `map`. */
|
|
20
23
|
items?: ManifestFieldShape;
|
|
21
24
|
/** Alternatives; required when the base type is `union`. */
|
|
@@ -21,13 +21,13 @@ await writeFile('acme.tar', bundle.tar);
|
|
|
21
21
|
|
|
22
22
|
`buildBundle` returns `commandsJson`, `bundleJson`, generated `memory/` members, uncompressed tar bytes, and an ETag. It runs the manifest validator and throws `ManifestInvalidError` when the generated command manifest is invalid. `buildCommandManifest(plugin, { mountPath })` returns the manifest object when archive bytes are not needed.
|
|
23
23
|
|
|
24
|
-
The tar has exactly `bundle.json`, `commands.json`, and `memory/<name>.md` members for the documents declared in the `memory` array of `definePlugin`. `bundle.json` carries `bundleVersion`, plus `version` when the plugin declares one, `object_kinds` when it declares object kinds, and `groups` when a provider declares its tool groups. `groups` is keyed by group id, each value `{"consent_line": string, "who"?: "own" | "user" | "allowlist" | "any", "allowlist"?: string[]}`, with `who` and `allowlist` written only when declared:
|
|
24
|
+
The tar has exactly `bundle.json`, `commands.json`, and `memory/<name>.md` members for the documents declared in the `memory` array of `definePlugin`. `bundle.json` carries `bundleVersion`, plus `version` when the plugin declares one, `object_kinds` when it declares object kinds, and `groups` when a provider declares its tool groups (the directory refuses a provider archive without them). `groups` is keyed by group id, each value `{"consent_line": string, "who"?: "own" | "user" | "allowlist" | "any", "allowlist"?: string[]}`, with `who` and `allowlist` written only when declared:
|
|
25
25
|
|
|
26
26
|
```json
|
|
27
27
|
{"bundleVersion":1,"version":1,"groups":{"read":{"consent_line":"Read your Acme apps and deployments."},"deploy":{"consent_line":"Start deployments on your Acme apps.","who":"allowlist","allowlist":["app:k3x9"]}}}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
When the directory serves a provider's bundle it writes the `provider` block from its own rows; `groups` in the archive you build is the declaration the directory reads from your endpoint. A local install ignores `groups`. The archive does not contain `plugin.json`. For an endpoint install, crouter creates `.crouter-plugin/plugin.json` from the endpoint, `--name`, `--auth-env`, and archive hash.
|
|
30
|
+
When the directory serves a provider's bundle it writes the `provider` block from its own rows; `groups` in the archive you build is the declaration the directory reads from your endpoint, and the only source of the provider's group rows. A local install ignores `groups`. The archive does not contain `plugin.json`. For an endpoint install, crouter creates `.crouter-plugin/plugin.json` from the endpoint, `--name`, `--auth-env`, and archive hash.
|
|
31
31
|
|
|
32
32
|
## Documents in the `memory` array
|
|
33
33
|
|
|
@@ -13,7 +13,7 @@ when-and-why-to-read: When you need Commands, this knowledge should be read
|
|
|
13
13
|
|
|
14
14
|
Use `defineBranch` to group commands and give it `children`. Use `defineLeaf` for a request that returns one object. A leaf requires `params`, `output`, `effects`, and `handler`; `params` may be omitted when the command has no input. `effects` is a non-empty list shown to the agent. Say whether a command mutates an application, creates billable resources, or is read-only.
|
|
15
15
|
|
|
16
|
-
A leaf may declare `group`, the capability-provider tool group it belongs to. A group matches `[A-Za-z0-9_-]{1,64}`, and `definePlugin` throws `PluginDefinitionError` naming the leaf for anything else. `buildCommandManifest` writes the group onto the leaf; a local install ignores it. The group's consent line and who may be granted it are declared in `definePlugin({ groups })` (below); its
|
|
16
|
+
A leaf may declare `group`, the capability-provider tool group it belongs to. A group matches `[A-Za-z0-9_-]{1,64}`, and `definePlugin` throws `PluginDefinitionError` naming the leaf for anything else. `buildCommandManifest` writes the group onto the leaf; a local install ignores it. The group's consent line and who may be granted it are declared in `definePlugin({ groups })` (below); its price is set with the directory's pricing (the console's Provider tab or `crtr cloud apps provider pricing set`). `sideEffect: true` marks a leaf whose tool has a side effect, so a repeated request must not run it twice. It is never written into the manifest.
|
|
17
17
|
|
|
18
18
|
`definePlugin` accepts `version`, the plugin's integer major (at least 1), written into both `commands.json` and `bundle.json`; a plugin without one is treated as major 1. `provider: true` marks a capability provider registered in the directory. It requires `version`, and it requires a `group` on every leaf: `definePlugin` throws naming the first leaf without one.
|
|
19
19
|
|
|
@@ -32,7 +32,7 @@ definePlugin({
|
|
|
32
32
|
});
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Each group has `consentLine`, the line the directory's consent page shows, and optionally `who` (`own`, `user`, `allowlist` or `any`; the directory treats an omitted `who` as `user`) and `allowlist` (app principals `app:<id>`, required with `who: 'allowlist'` and refused otherwise). `buildBundle` writes the block into `bundle.json` as `groups`, and the directory applies it when it refreshes the provider's manifest. `groups` is
|
|
35
|
+
Each group has `consentLine`, the line the directory's consent page shows, and optionally `who` (`own`, `user`, `allowlist` or `any`; the directory treats an omitted `who` as `user`) and `allowlist` (app principals `app:<id>`, required with `who: 'allowlist'` and refused otherwise). `buildBundle` writes the block into `bundle.json` as `groups`, and the directory applies it when it refreshes the provider's manifest. The directory refuses a provider archive whose `bundle.json` has no `groups` (`bundle.json groups: is required`), and a provider's groups come only from this declaration: the directory's console and CLI do not edit groups. `definePlugin` itself accepts a provider without `groups`, but once declared every leaf's `group` must name a declared group and every declared group must be used by at least one leaf; `definePlugin` throws `PluginDefinitionError` otherwise. A plugin that is not `provider: true` must not declare `groups`.
|
|
36
36
|
|
|
37
37
|
A branch or leaf may set `tier` to `common` or `important` to be promoted into its parent's `-h`. A command with more than 40 children lists only its promoted children in its own `-h`, followed by the count of the rest; the others still run and answer `-h` by name. Use this when a plugin mounts one command per entry of a large catalog. On such a command only promoted leaves reach the top-level `crtr -h`; a promoted branch shows in the command's own `-h`.
|
|
38
38
|
|
|
@@ -64,8 +64,8 @@ A definition with `version: N`, or a list of definitions with one per supported
|
|
|
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
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`,
|
|
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,
|
|
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`, opens a closed object by dropping its `children` or adding an `extra`, or adds an open object member whose shape its old `extra` did not accept;
|
|
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, closes an open object by adding `children` or removing its `extra`, or adds a member to an open object that its old `extra` did not accept. Removing an output member is always breaking, open or closed.
|
|
69
69
|
|
|
70
70
|
A new optional flag, a new leaf, a new optional output field and changed prose are additive.
|
|
71
71
|
|
|
@@ -27,12 +27,14 @@ 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 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
|
+
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 unless it declares `extra` (see Structured fields), 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, and so does one whose `extra` is `field.unknown(...)`.
|
|
31
31
|
|
|
32
32
|
## Structured fields
|
|
33
33
|
|
|
34
34
|
`field.object({ ... })` types an object's members. Each member is a `field.*` builder, and members can be structured themselves. `field.array(field.string('A tag.'))` types each element; the element's `required` is ignored and the array's constraint defaults to the element's. `field.enum(['open', 'closed'], 'Thread state.')` restricts a string to fixed values, which the handler sees as literal types. The generated manifest carries the whole shape: object members as ordered `children`, an array element as `items`, enum `values`. `crtr` command help renders it. `field.nullable` keeps the members, element, or values of the field it wraps.
|
|
35
35
|
|
|
36
|
+
An object with members is closed: a member it does not declare is rejected. To accept members it does not declare, pass `extra` as the third argument, `field.object({ id: field.string('Id.') }, 'The row.', { extra: field.unknown('Any other column.') })`. Every undeclared member must then have the `extra` field's shape (`field.unknown` accepts any value; `field.string(...)` only strings; the `extra` field's own `required` is ignored). The manifest carries it as `extra`, `crtr` command help lists it as an `other members` row, and the generated client types it as an index signature. The handler's inferred type for an open object is the declared members plus `{ readonly [key: string]: unknown }`, whatever the `extra` shape is, so a handler can return an object literal with extra members; the runtime check enforces the `extra` shape. `extra` is only valid on an object with members (an object without members already accepts anything; a `map` takes a value shape), and a `file` cannot be inside it.
|
|
37
|
+
|
|
36
38
|
```ts
|
|
37
39
|
output: {
|
|
38
40
|
thread: field.object({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AttachSession } from '../session/context.js';
|
|
2
2
|
/** The slice of the session the roster reads. */
|
|
3
|
-
export type RosterSession = Pick<AttachSession, 'nodeId' | 'remote' | 'role' | 'canvasSource' | 'tui' | 'pal' | 'editor' | 'containers'>;
|
|
3
|
+
export type RosterSession = Pick<AttachSession, 'nodeId' | 'remote' | 'role' | 'meta' | 'canvasSource' | 'tui' | 'pal' | 'editor' | 'containers'>;
|
|
4
4
|
export interface RosterHooks {
|
|
5
5
|
/** Live per-node human-ticket counts — read fresh on every rebuild, since the
|
|
6
6
|
* viewer's attention poll reassigns the map behind us. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var q=Object.defineProperty;var
|
|
2
|
-
`)[0]??"").replace(/\s+/g," ").trim();return d.length>t?`${d.slice(0,t-1)}\u2026`:d},"commandHead"),B=
|
|
3
|
-
`)[0]}`),n.requestChromeRefresh()}))},"confirmKill");
|
|
4
|
-
`)[0]}`));return}if(e.kind==="bash"){if(
|
|
1
|
+
var q=Object.defineProperty;var o=(m,n)=>q(m,"name",{value:n,configurable:!0});import{execFile as _}from"node:child_process";import{Text as O,decodeKittyPrintable as k,matchesKey as R}from"@earendil-works/pi-tui";import{cliClient as w}from"../../../commands/api-client.js";import{focusNode as D}from"../../../core/runtime/focus-viewer.js";import{daemonErrorMessage as H}from"../../inbox/resolve.js";import{activeBackgroundBashJobs as j}from"../../../core/bash-jobs.js";import{contextDir as U}from"../../../core/canvas/paths.js";import{fillBar as J,fillWidth as X,stripSgr as F,REVERSE as L}from"../../../core/canvas/nav-render.js";import{buildCanvasPanelLines as V}from"./canvas-panels.js";const W=15e3;function se(m,n){const{editor:x,tui:S,pal:E,nodeId:f,remote:a,role:C,canvasSource:K}=m,N=o(()=>U(f,m.meta.grantee),"nodeContextDir"),I=m.containers.reports;let c=[],r,g=process.env.CRTR_ATTACH_ROSTER;g!==void 0&&delete process.env.CRTR_ATTACH_ROSTER;let l,v;const b=o(()=>{n.syncCursor(),I.clear();for(const e of c){const t=e.key===r;I.addChild(new O(t?J(F(e.line),X(),L):e.line,1,0))}S.requestRender()},"paint"),$=o(async()=>{const e=a?[]:(await w().listCrons()).filter(i=>i.created_by===f),t=a?[]:j(N()),d=a?[]:(await w().listReviews({state:"open",origin_node_id:f})).reviews;if(c=(await V(f,n.asks(),E,K,e,t,d)).items,g!==void 0){const i=g;g=void 0;const p=c.find(h=>h.kind==="node"&&h.nodeId===i);p!==void 0&&(r=p.key)}r!==void 0&&!c.some(i=>i.key===r)&&(r=void 0),l!==void 0&&!t.some(i=>i.jobId===l?.jobId)&&y(!0),b()},"refresh"),y=o(e=>{v!==void 0&&(clearTimeout(v),v=void 0),l!==void 0&&(l=void 0,e&&n.setNotice(""))},"disarmKill"),u=o(()=>{y(!0),r!==void 0&&(r=void 0,b())},"exit"),T=o(e=>k(e)??(e.length===1?e:void 0),"typed"),A=o((e,t=40)=>{const d=(e.split(`
|
|
2
|
+
`)[0]??"").replace(/\s+/g," ").trim();return d.length>t?`${d.slice(0,t-1)}\u2026`:d},"commandHead"),B=o(()=>{if(C!=="controller"){n.setNotice("Read-only observer attach \u2014 cannot stop background jobs");return}const e=j(N());if(e.length===0){n.setNotice("Background job already finished"),n.requestChromeRefresh();return}if(e.length>1){n.setNotice(`${e.length} background jobs \u2014 press \u21B5 to pick one in the Inspector`);return}const t=e[0];l={jobId:t.jobId,label:A(t.command)},n.setNotice(`Stop \u201C${l.label}\u201D? Press y to confirm.`,{sticky:!0}),v=setTimeout(()=>y(!0),W)},"armKill"),M=o(()=>{const e=l;e!==void 0&&(y(!1),n.setNotice(`Stopping \u201C${e.label}\u201D\u2026`),_("crtr",["node","bash","kill",e.jobId,"--node",f],t=>{n.setNotice(t===null?`Stopped \u201C${e.label}\u201D.`:`Could not stop the job: ${t.message.split(`
|
|
3
|
+
`)[0]}`),n.requestChromeRefresh()}))},"confirmKill");x.onCursorDownAtBottom=a?void 0:()=>{r!==void 0||c.length===0||(r=c[0].key,b())};const P=o(()=>{const e=c.find(s=>s.key===r);if(e===void 0||a)return;if(e.kind==="crons"){u(),n.openInspector("schedule");return}if(e.kind==="review"){if(C!=="controller"){n.setNotice("Read-only observer attach \u2014 cannot open the review companion");return}u();const s=process.env.TMUX_PANE;if(s===void 0||s===""){n.setNotice("Could not open the companion: no tmux pane");return}(async()=>{const i=w(),p=await i.getNode(e.companionNodeId);(await D(i,p,{pane:s})).focused||n.setNotice("Could not open the companion")})().catch(i=>n.setNotice(`Could not open the companion: ${H(i).split(`
|
|
4
|
+
`)[0]}`));return}if(e.kind==="bash"){if(j(N()).length===0){n.setNotice("Background job already finished"),n.requestChromeRefresh();return}u(),n.openInspector("jobs");return}if(e.kind==="object-wait"){u(),n.openObject(e.objectId);return}const t=e.nodeId;if(t===f){u();return}if(C!=="controller"){n.setNotice("Read-only observer attach \u2014 cannot focus nodes");return}const d=process.env.TMUX_PANE;if(d===void 0||d===""){n.setNotice("cannot swap \u2014 no tmux pane");return}_("tmux",["run-shell","-b",`crtr surface node focus ${t} --pane ${d} --in-place >/dev/null 2>&1`],()=>{})},"openSelected");return{refresh:$,handleKey:o(e=>{if(a)return!1;const t=R(e,"down"),d=R(e,"up");if(r===void 0)return!1;const s=c.findIndex(p=>p.key===r),i=c[s];if(l!==void 0){if(T(e)==="y")return M(),!0;y(!0)}return i?.kind==="bash"&&T(e)==="x"?(B(),!0):d?(s<=0?u():(r=c[s-1].key,b()),!0):t?(s>=0&&s<c.length-1&&(r=c[s+1].key,b()),!0):R(e,"enter")?(P(),!0):R(e,"escape")?(u(),!0):(u(),!1)},"handleKey"),active:o(()=>r!==void 0,"active"),exit:u}}o(se,"createRoster");export{se as createRoster};
|
|
@@ -88,6 +88,9 @@ export declare class ChatView {
|
|
|
88
88
|
/** Inline page blocks placed in this transcript, by page id — one block per
|
|
89
89
|
* page, so a `--replace` revision refreshes the block already on screen. */
|
|
90
90
|
private readonly pageBlocks;
|
|
91
|
+
/** The person's inbox answers in the live region, each with its leading
|
|
92
|
+
* spacer. Summary mode hides them with the settled pages they answer. */
|
|
93
|
+
private readonly inboxAnswers;
|
|
91
94
|
private readonly inlinePageBlocks;
|
|
92
95
|
private readonly pageOpenHint;
|
|
93
96
|
private readonly backgroundBashHint;
|
|
@@ -249,6 +252,11 @@ export declare class ChatView {
|
|
|
249
252
|
* flight is never hidden or minimized. Only a settled group with a broker-known
|
|
250
253
|
* summary collapses; incomplete/failed generation leaves its folded call lines. */
|
|
251
254
|
private applyToolDisplay;
|
|
255
|
+
/** Summary mode hides the back-and-forth with the person: every settled
|
|
256
|
+
* inline page and every answer they gave from their inbox. A page still
|
|
257
|
+
* awaiting an answer stays on screen. */
|
|
258
|
+
private hidesInboxExchange;
|
|
259
|
+
private applyInboxExchangeDisplay;
|
|
252
260
|
/** `/show-working` (Alt+C → z → w): expand or fold the working behind every
|
|
253
261
|
* consolidated reply. Returns the new state, or null when the
|
|
254
262
|
* `consolidate_replies` experiment is off for this attach. */
|
|
@@ -299,6 +307,7 @@ export declare class ChatView {
|
|
|
299
307
|
* they typed here, and closes the run around it the same way. */
|
|
300
308
|
private appendHumanAnswer;
|
|
301
309
|
private appendHumanAnswerParts;
|
|
310
|
+
private trackInboxAnswer;
|
|
302
311
|
/** Only the full-fidelity trailing region gets summary groups. Condensed
|
|
303
312
|
* history intentionally drops tool traffic, so grouping there would create
|
|
304
313
|
* phantom summary rows and cross the live/condensed boundary. */
|