@north-light/cli 0.3.430 → 0.3.431
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/builtin-memory/crouter-sdk/README.md +2 -2
- package/dist/builtin-memory/crouter-sdk/client.md +4 -4
- package/dist/builtin-memory/crouter-sdk/error-envelope.md +3 -3
- package/dist/builtin-memory/crouter-sdk/getting-started.md +2 -2
- package/dist/builtin-memory/crouter-sdk/guides/README.md +1 -1
- package/dist/builtin-memory/crouter-sdk/guides/{apps-on-crouter-cloud.md → apps-on-northlight.md} +1 -1
- package/dist/builtin-memory/crouter-sdk/guides/apps-with-many-users.md +3 -3
- package/dist/builtin-memory/crouter-sdk/guides/typed-extraction.md +1 -1
- package/dist/builtin-memory/crouter-sdk/release-notes.md +2 -2
- package/dist/builtin-memory/crouter-sdk/resources.md +1 -1
- package/dist/builtin-memory/crouter-sdk/testing.md +3 -3
- package/dist/builtin-memory/internal/plugins.md +1 -1
- package/dist/clients/attach/viewer.js +4 -4
- package/dist/clients/inbox/tui/input.js +1 -1
- package/dist/clients/inbox/tui/page-body.js +1 -1
- package/dist/clients/inbox/tui/render.js +2 -2
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/sys/setup-core.d.ts +1 -1
- package/dist/commands/sys/setup-core.js +3 -3
- package/dist/core/command.d.ts +1 -1
- package/dist/core/command.js +1 -1
- package/dist/core/model-auth/flows.d.ts +1 -1
- package/dist/core/providers/call.js +1 -1
- package/dist/core/runtime/broker-protocol.d.ts +2 -2
- package/dist/daemon/api/bridge.d.ts +1 -1
- package/dist/daemon/reconcilers/live-obligation.d.ts +1 -1
- package/dist/launcher/users.js +4 -4
- package/dist/pi-extensions/canvas-review-boundary.d.ts +1 -1
- package/docs/sdk/README.md +2 -2
- package/docs/sdk/client.md +4 -4
- package/docs/sdk/error-envelope.md +2 -2
- package/docs/sdk/getting-started.md +2 -2
- package/docs/sdk/guides/README.md +1 -1
- package/docs/sdk/guides/{apps-on-crouter-cloud.md → apps-on-northlight.md} +2 -2
- package/docs/sdk/guides/apps-with-many-users.md +3 -3
- package/docs/sdk/guides/typed-extraction.md +1 -1
- package/docs/sdk/release-notes.md +2 -2
- package/docs/sdk/resources.md +1 -1
- package/docs/sdk/testing.md +3 -3
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +2 -2
- package/runtime.lock.json +12 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var B=Object.defineProperty;var f=(e,n)=>B(e,"name",{value:n,configurable:!0});import{isResponseBearingSlot as x}from"../../../core/human/page-schema.js";import{panItemReview as W,verticalCursor as S,wordLeftIndex as C,wordRightIndex as K}from"./render.js";import{commentAnchors as b,commentIdFor as p,pageComplete as I,responseAnswers as L,slotActions as
|
|
1
|
+
var B=Object.defineProperty;var f=(e,n)=>B(e,"name",{value:n,configurable:!0});import{isResponseBearingSlot as x}from"../../../core/human/page-schema.js";import{panItemReview as W,verticalCursor as S,wordLeftIndex as C,wordRightIndex as K}from"./render.js";import{commentAnchors as b,commentIdFor as p,pageComplete as I,responseAnswers as L,slotActions as N,slotAnswered as R,terminalAnswerable as j}from"./slots.js";const g=f((e,n)=>n==="single"&&(e.kind==="options"||e.kind==="cards"),"autoAdvances");function ee(e,n,r,o,i){if(n.ctrl&&e==="c"){i(!1);return}if(r.hint=void 0,r.inputMode!==null){H(e,n,r,o);return}r.phase==="overview"?z(e,n,r,o,i):r.phase==="item-review"?D(e,n,r,o,i):E(e,n,r,o,i)}f(ee,"handleKeypress");function A(e,n,r){return e==="d"||n.pageDown||n.ctrl&&(e==="d"||e==="e")?(r.scrollOffset+=e==="e"?1:10,r.followSelection=!1,!0):e==="u"||n.pageUp||n.ctrl&&(e==="u"||e==="y")?(r.scrollOffset=Math.max(0,r.scrollOffset-(e==="y"?1:10)),r.followSelection=!1,!0):!1}f(A,"scrollKey");function z(e,n,r,o,i){if(A(e,n,r)){o();return}if(e==="j"||n.downArrow)r.currentIndex=Math.min(r.currentIndex+1,Math.max(0,r.slots.length-1)),r.followSelection=!0;else if(e==="k"||n.upArrow)r.currentIndex=Math.max(r.currentIndex-1,0),r.followSelection=!0;else if((n.return||e===" ")&&r.slots.length>0)r.phase="item-review",M(r);else if(e==="q"||n.escape){i(!r.manifest.slots.some(x));return}else return;o()}f(z,"handleOverview");function D(e,n,r,o,i){const c=r.slots[r.currentIndex];if(c===void 0){r.phase="overview",o();return}const s=N(c);if(e==="n"){v(r,1),o();return}if(e==="p"){v(r,-1),o();return}if(e==="q"||n.escape){i(!1);return}if(A(e,n,r)){o();return}if((e==="h"||e==="l"||n.leftArrow||n.rightArrow)&&r.hscrollMax>0&&!s.some(l=>l.kind==="select"&&l.shortcut===e)){W(r,e==="h"||n.leftArrow?-1:1),o();return}if(e==="j"||n.downArrow){r.selectedAction=Math.min(r.selectedAction+1,Math.max(0,s.length-1)),o();return}if(e==="k"||n.upArrow){r.selectedAction=Math.max(0,r.selectedAction-1),o();return}if(e==="c"&&b(c).length>0){Z(r,c,s),o();return}const u=s.find(l=>l.kind==="select"&&l.shortcut===e);if(u?.kind==="select"){F(r,c,u,g(c,u.mode)),o(),g(c,u.mode)&&r.phase==="final"&&I(r)&&i(!0);return}if(e===" "&&s[r.selectedAction]?.kind==="select"){const l=s[r.selectedAction];F(r,c,l,g(c,l.mode)),o(),g(c,l.mode)&&r.phase==="final"&&I(r)&&i(!0);return}if(e==="r"&&s.some(l=>l.kind==="freetext")){const l=m(c,r.responses.get(c.id));r.inputMode={kind:"freetext",buffer:l.freetext??"",cursor:[...l.freetext??""].length},o();return}if(e==="e"&&s.some(l=>l.kind==="edit")){const l=m(c,r.responses.get(c.id));r.inputMode={kind:"text",buffer:l.text,cursor:[...l.text].length},o();return}if(e==="a"&&s.some(l=>l.kind==="accept")){d(r,c,{text:c.config.initialText,edited:!1}),h(r),o();return}if(n.return){const l=s[r.selectedAction];if(l?.kind==="freetext"){const t=m(c,r.responses.get(c.id));r.inputMode={kind:"freetext",buffer:t.freetext??"",cursor:[...t.freetext??""].length},o();return}if(l?.kind==="edit"){const t=m(c,r.responses.get(c.id));r.inputMode={kind:"text",buffer:t.text,cursor:[...t.text].length},o();return}if(l?.kind==="accept"){d(r,c,{text:c.config.initialText,edited:!1}),h(r),o();return}if(R(r,c)){h(r),o();return}if(j(c)){const t=m(c,r.responses.get(c.id));if(L(r,c,t)){d(r,c,t),h(r),o();return}}if(x(c)){r.hint=j(c)?"Select at least one option (space to toggle), or q to go back":"This must be answered in Northlight",o();return}v(r,1),o()}}f(D,"handleItemReview");function E(e,n,r,o,i){if(e==="q"||n.escape){r.phase=r.slots.length===1?"item-review":"overview",r.scrollOffset=0,o();return}if(A(e,n,r)){o();return}n.return&&(I(r)||!r.manifest.slots.some(x))&&i(!0)}f(E,"handleFinal");function M(e){e.selectedAction=0,e.scrollOffset=0,e.followSelection=!0,e.hscrollOffset=0,e.hscrollMax=0}f(M,"resetItem");function v(e,n){const r=e.currentIndex+n;if(r<0){e.phase=e.slots.length>1?"overview":"item-review",e.scrollOffset=0,e.followSelection=!0;return}if(r>=e.slots.length){e.phase="final",e.scrollOffset=0,e.followSelection=!0;return}e.currentIndex=r,M(e)}f(v,"advanceItem");function h(e){for(let n=e.currentIndex+1;n<e.slots.length;n++){const r=e.slots[n];if(x(r)&&!R(e,r)){e.currentIndex=n,M(e);return}}e.phase="final",e.scrollOffset=0}f(h,"advanceToNextIncomplete");function m(e,n){return n!==void 0?n:e.kind==="options"?{selectedOptionIds:[],comments:[]}:e.kind==="cards"?{selectedCardIds:[]}:e.kind==="table"?{selectedRowIds:[],selectedColumnIds:[],comments:[]}:e.kind==="text"?{text:e.config.initialText,edited:!1}:{}}f(m,"responseFor");function O(e){return[...e?.comments??[]]}f(O,"commentsFor");function d(e,n,r){n.id!==void 0&&(e.responses.set(n.id,r),e.persist?.())}f(d,"setResponse");function F(e,n,r,o){const i=m(n,e.responses.get(n.id));if(n.kind==="options"){const c=i,s=w(c.selectedOptionIds,r.id,r.mode);d(e,n,{...c,selectedOptionIds:s})}else if(n.kind==="cards"){const c=i;d(e,n,{...c,selectedCardIds:w(c.selectedCardIds,r.id,r.mode)})}else if(n.kind==="table"){const c=i;r.group==="row"?d(e,n,{...c,selectedRowIds:w(c.selectedRowIds,r.id,r.mode)}):d(e,n,{...c,selectedColumnIds:w(c.selectedColumnIds,r.id,r.mode)})}o&&h(e)}f(F,"commitSelection");function w(e,n,r){return r==="single"?[n]:e.includes(n)?e.filter(o=>o!==n):[...e,n]}f(w,"toggleIds");function U(e){if(e?.kind==="select"){if(e.group==="option")return{kind:"option",optionId:e.id};if(e.group==="row")return{kind:"row",rowId:e.id}}}f(U,"anchorFor");function Z(e,n,r){const o=U(r[e.selectedAction])??b(n)[0],c=O(n.id===void 0?void 0:e.responses.get(n.id)).find(s=>s.id===p(o))?.text??"";e.inputMode={kind:"comment",buffer:c,cursor:[...c].length,anchor:o}}f(Z,"startComment");function G(e){if(e.inputMode?.kind!=="comment")return;const n=e.slots[e.currentIndex],r=b(n);if(r.length===0)return;const o=p(e.inputMode.anchor),i=r.findIndex(u=>p(u)===o),c=r[(i+1)%r.length],s=O(n.id===void 0?void 0:e.responses.get(n.id)).find(u=>u.id===p(c))?.text??"";e.inputMode={kind:"comment",anchor:c,buffer:s,cursor:[...s].length}}f(G,"cycleCommentAnchor");function H(e,n,r,o){const i=r.inputMode;if(n.escape){r.inputMode=null,o();return}if(n.tab&&i.kind==="comment"){G(r),o();return}if(n.return){J(r),o();return}if(n.newline){T(i,`
|
|
2
2
|
`),o();return}n.wordLeft?i.cursor=C(i.buffer,i.cursor):n.wordRight?i.cursor=K(i.buffer,i.cursor):n.leftArrow?i.cursor=Math.max(0,i.cursor-1):n.rightArrow?i.cursor=Math.min([...i.buffer].length,i.cursor+1):n.upArrow?i.cursor=S(i.buffer,i.cursor,-1,q(r)):n.downArrow?i.cursor=S(i.buffer,i.cursor,1,q(r)):n.home||n.ctrl&&e==="a"?i.cursor=Y(i.buffer,i.cursor):n.end||n.ctrl&&e==="e"?i.cursor=_(i.buffer,i.cursor):n.meta&&n.backspace?Q(i):n.backspace||e==="\x7F"?P(i):n.del||n.ctrl&&e==="d"?X(i):!n.ctrl&&!n.meta&&V(i,e),o()}f(H,"handleInputMode");function J(e){const n=e.inputMode,r=e.slots[e.currentIndex],o=m(r,r.id===void 0?void 0:e.responses.get(r.id));if(n.kind==="comment"){const i=n.anchor,c=p(i),s=O(o).filter(u=>u.id!==c);n.buffer.trim()!==""&&s.push({id:c,anchor:i,text:n.buffer}),d(e,r,{...o,comments:s})}else n.kind==="freetext"?(d(e,r,{...o,freetext:n.buffer}),h(e)):(d(e,r,{text:n.buffer,edited:n.buffer!==r.config.initialText}),h(e));e.inputMode=null}f(J,"commitInput");function T(e,n){const r=[...e.buffer];r.splice(e.cursor,0,...n),e.buffer=r.join(""),e.cursor+=[...n].length}f(T,"insert");function P(e){if(e.cursor===0)return;const n=[...e.buffer];n.splice(e.cursor-1,1),e.buffer=n.join(""),e.cursor--}f(P,"backspace");function Q(e){const n=C(e.buffer,e.cursor);if(n===e.cursor)return;const r=[...e.buffer];r.splice(n,e.cursor-n),e.buffer=r.join(""),e.cursor=n}f(Q,"wordBackspace");function V(e,n){const r=n.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g,"").replace(/\r\n?/g,`
|
|
3
3
|
`).replace(/[\x00-\x09\x0B-\x1F\x7F]/g,"");r.length>0&&T(e,r)}f(V,"insertTyped");function X(e){const n=[...e.buffer];e.cursor>=n.length||(n.splice(e.cursor,1),e.buffer=n.join(""))}f(X,"del");function Y(e,n){const r=[...e].slice(0,n).join(""),o=r.lastIndexOf(`
|
|
4
4
|
`);return o<0?0:[...r.slice(0,o+1)].length}f(Y,"lineStart");function _(e,n){const r=[...e],o=r.slice(n).indexOf(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var L=Object.defineProperty;var
|
|
1
|
+
var L=Object.defineProperty;var $=(s,r)=>L(s,"name",{value:r,configurable:!0});import{renderMarkdownBlockAwareLines as M}from"../../../core/termrender/termrender.js";import{describeSlot as S,tableMarkdown as w,terminalAnswerable as x}from"./slots.js";import{BOLD as b,DIM as c,RESET as n,sanitize as o,singleLine as d,truncate as u,wrap as k}from"./ansi.js";function D(s,r,f,m){const p=$(t=>M(t,f,m),"md");if(s.dialect==="html")return r===""?[`${c}HTML page \u2014 open in Northlight to read this${n}`]:p(r);const i=r===""?[]:p(r);let a=-1;for(const t of s.slots){s.steps>1&&t.step!==a&&(i.length>0&&i.push(""),i.push(`${b}Step ${t.step+1}/${s.steps}${n}`),a=t.step),i.length>0&&i.push(""),i.push(`${b}${u(o(d(S(t))),f)}${n}`);const h=t.config.body;if(typeof h=="string"&&h!==""&&i.push(...p(h)),!x(t)){i.push(`${c}${o(t.kind)} \u2014 open in Northlight to answer this${n}`);continue}if(t.kind==="text")i.push(...p(t.config.initialText));else if(t.kind==="table")i.push(...p(w(t.config)));else if(t.kind==="cards"){for(const e of t.config.cards??[])if(i.push(` \u2022 ${u(o(d(e.subtitle===void 0?e.title:`${e.title} \u2014 ${e.subtitle}`)),Math.max(10,f-4))}`),e.body!==void 0)for(const l of k(o(e.body),Math.max(10,f-6)))i.push(` ${c}${l}${n}`)}else if(t.kind==="options"){for(const e of t.config.options)if(i.push(` \u2022 ${u(o(d(e.label)),Math.max(10,f-4))}`),e.description!==void 0)for(const l of k(o(e.description),Math.max(10,f-6)))i.push(` ${c}${l}${n}`)}else if(t.kind==="chart"){const e=t.config,l=e.title??`${e.x??""} vs ${(e.y??[]).map(g=>g.label??g.field).join(", ")}`;i.push(`${c}chart \u2014 ${o(e.kind??"chart")}: ${o(l)}${n}`)}}return i}$(D,"pageBodyLines");export{D as pageBodyLines};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var J=Object.defineProperty;var h=(n,s)=>J(n,"name",{value:s,configurable:!0});import R from"string-width";import{isResponseBearingSlot as O}from"../../../core/human/page-schema.js";import{answerCommentAnchorLabel as K,describePageAnswer as Q}from"../../../core/human/answer.js";import{renderMarkdownBlockAwareLines as U}from"../../../core/termrender/termrender.js";import{commentAnchors as X,describeSlot as S,pageComplete as Z,slotActions as nn,slotAnswered as v,tableMarkdown as en,terminalAnswerable as A}from"./slots.js";import{RESET as
|
|
2
|
-
`){r.push({start:
|
|
1
|
+
var J=Object.defineProperty;var h=(n,s)=>J(n,"name",{value:s,configurable:!0});import R from"string-width";import{isResponseBearingSlot as O}from"../../../core/human/page-schema.js";import{answerCommentAnchorLabel as K,describePageAnswer as Q}from"../../../core/human/answer.js";import{renderMarkdownBlockAwareLines as U}from"../../../core/termrender/termrender.js";import{commentAnchors as X,describeSlot as S,pageComplete as Z,slotActions as nn,slotAnswered as v,tableMarkdown as en,terminalAnswerable as A}from"./slots.js";import{RESET as o,BOLD as M,DIM as $,GREEN as E,YELLOW as w,CYAN as k,REVERSE as W,sanitize as a,singleLine as C,truncate as B,hline as g,wrap as b,centerHorizontal as N,clipLine as q,panLine as tn,visibleWidth as L}from"./ansi.js";const sn=8,y=h((n,s,e)=>U(n,s,e),"mdLines");function T(n){return Q(n.manifest,Object.fromEntries(n.responses),{completedAt:""})}h(T,"pageAnswer");function on(n,s){return s.id===void 0?void 0:n.slots.find(e=>e.id===s.id)}h(on,"answerSlot");function H(n){if(n===void 0||n.state==="unanswered")return"unanswered";if(n.raw!==void 0)return"custom response";const s=n.text!==void 0?[n.text.edited?"edited":"accepted"]:n.groups.map(e=>e.picked.length===0?"none":e.picked.map(t=>t.label).join(", "));return n.freetext!==void 0&&s.push(n.freetext),n.comments.length>0&&s.push(`${n.comments.length} comment${n.comments.length===1?"":"s"}`),s.join("; ")||"answered"}h(H,"answerSummary");function P(n,s){return K(n,s)}h(P,"commentLabel");function pn(n,s,e){const t=Math.min(Math.max(20,s-4),120),r=Math.max(20,s-4),i=["",` ${M}${k}${a(n.manifest.title)}${o}`];if(n.manifest.subtitle)for(const m of b(a(n.manifest.subtitle),t))i.push(` ${$}${m}${o}`);if(n.displayMarkdown!==""){i.push("");for(const m of y(n.displayMarkdown,t,r))i.push(` ${m}`)}else n.manifest.dialect==="html"&&i.push("",` ${$}HTML page \u2014 open in Northlight to read this${o}`);const c=n.slots.length>0;c&&i.push(` ${$}${g(t)}${o}`,"");const f=T(n);let u=-1,l;for(let m=0;m<n.slots.length;m++){const p=n.slots[m];n.manifest.steps>1&&p.step!==u&&(u!==-1&&i.push(""),i.push(` ${M}Step ${p.step+1}/${n.manifest.steps}${o}`),u=p.step);const I=p.id===void 0?void 0:n.responses.get(p.id),V=O(p)?v(n,p)?`${E}\u2713${o}`:`${$}\u25CB${o}`:`${$}\xB7${o}`,_=m===n.currentIndex?`${k}\u25B8${o} `:" ";m===n.currentIndex&&(l=i.length),i.push(` ${_}${V} ${B(C(S(p)),Math.max(10,s-16))}`),I!==void 0&&i.push(` ${$}${B(C(H(on(f,p))),Math.max(10,s-10))}${o}`)}if(n.manifest.slots.some(m=>O(m)&&!A(m))){i.push("");for(const m of b("This page must be answered in Northlight. Leaving it here keeps it in your inbox; x on the list cancels it.",t-2))i.push(` ${w}${m}${o}`)}c&&i.push(""),i.push(` ${$}${g(t)}${o}`);const x=!n.manifest.slots.some(O)?` ${$}u/d${o} scroll ${$}q${o} dismiss`:` ${$}enter${o} review ${$}\u2191\u2193/jk${o} navigate ${$}u/d${o} scroll ${$}q${o} close`;return G(i,x,n,s,e,t+2,n.followSelection?l:void 0)}h(pn,"renderOverview");function rn(n,s,e){const t=[...n],r=Math.max(0,Math.min(s,t.length)),i=z(n,e);let c=0;for(let l=0;l<i.length;l++)i[l].start<=r&&(c=l);const f=i.map((l,d)=>{if(d!==c)return t.slice(l.start,l.end).join("");const x=t.slice(l.start,r).join(""),m=r<l.end?t[r]:" ",p=r<l.end?t.slice(r+1,l.end).join(""):"";return`${x}${W}${m}${o}${p}`}),u=i[c];return r===u.end&&R(t.slice(u.start,u.end).join(""))>=e&&(f[c]=t.slice(u.start,u.end).join(""),f.splice(c+1,0,`${W} ${o}`)),f}h(rn,"renderInputBuffer");function xn(n,s){const e=[...n];let t=Math.max(0,Math.min(s,e.length));for(;t>0&&/\s/.test(e[t-1]);)t--;for(;t>0&&!/\s/.test(e[t-1]);)t--;return t}h(xn,"wordLeftIndex");function Mn(n,s){const e=[...n];let t=Math.max(0,Math.min(s,e.length));for(;t<e.length&&/\s/.test(e[t]);)t++;for(;t<e.length&&!/\s/.test(e[t]);)t++;return t}h(Mn,"wordRightIndex");function z(n,s){const e=[...n],t=Math.max(1,s),r=[];let i=0,c=0;for(let f=0;f<e.length;f++){const u=e[f];if(u===`
|
|
2
|
+
`){r.push({start:i,end:f}),i=f+1,c=0;continue}const l=R(u);c+l>t?(r.push({start:i,end:f}),i=f,c=l):c+=l}return r.push({start:i,end:e.length}),r}h(z,"wrappedRowBounds");function gn(n,s,e,t){const r=z(n,t),i=Math.max(0,Math.min(s,[...n].length));let c=0;for(let u=0;u<r.length;u++)r[u].start<=i&&(c=u);const f=r[c+e];return f===void 0?i:Math.min(f.start+(i-r[c].start),f.end)}h(gn,"verticalCursor");function ln(n,s){return s.id===void 0?[]:n.responses.get(s.id)?.comments??[]}h(ln,"responseComments");function cn(n,s,e,t){if(s.id===void 0)return!1;const r=n.responses.get(s.id);return e==="option"?r?.selectedOptionIds?.includes(t)===!0:e==="card"?r?.selectedCardIds?.includes(t)===!0:e==="row"?r?.selectedRowIds?.includes(t)===!0:r?.selectedColumnIds?.includes(t)===!0}h(cn,"selected");function D(n,s){const e=n.slots[n.currentIndex],t=Math.min(Math.max(20,s-4),120),r=Math.max(20,s-4),i=["",` ${M}${k}[${n.currentIndex+1}/${n.slots.length}]${o} ${$}${a(n.manifest.title)}${o}`,` ${$}${g(t)}${o}`,"",` ${M}${a(S(e))}${o}`],c=e.config.body;if(typeof c=="string"&&c.length>0){i.push("");for(const l of y(c,t,r))i.push(` ${l}`)}if(A(e)){if(e.kind==="chart"){const l=e.config,d=l.title??`${l.x} vs ${(l.y??[]).map(x=>x.label??x.field).join(", ")}`;i.push("",` ${$}chart \u2014 ${a(l.kind)}: ${a(d)}${o}`)}else if(e.kind==="table"){i.push("");for(const d of y(en(e.config),t,r))i.push(` ${d}`);const l=e.config.label;l&&i.push(` ${$}${a(l)}${o}`)}else if(e.kind==="text"){const d=(e.id===void 0?void 0:n.responses.get(e.id))?.text??e.config.initialText;i.push("");for(const x of y(d,t,r))i.push(` ${x}`)}}else{i.push("");for(const l of b(`${a(e.kind)} \u2014 must be answered in Northlight; the terminal cannot submit this page`,Math.max(10,t-2)))i.push(` ${w}${l}${o}`);i.push(` ${$}q leaves it in your inbox; x on the list cancels it${o}`)}i.push("",` ${$}${g(t)}${o}`);let f;if(n.inputMode!==null){const l=n.inputMode.kind==="comment"?`Comment on ${P(e,n.inputMode.anchor)}`:n.inputMode.kind==="text"?"Edit text":"Other response";i.push(` ${M}${l}${o}`);for(const d of rn(n.inputMode.buffer,n.inputMode.cursor,Math.max(10,t-4)))i.push(` ${d}`);f=` ${$}enter save esc cancel${n.editorAvailable?" ^o editor":""}${o}`}else{nn(e).forEach((d,x)=>{const m=x===n.selectedAction?`${k}\u25B8${o}`:" ";if(d.kind==="select"){const p=cn(n,e,d.group,d.id)?`${E}\u2713${o}`:" ";if(i.push(` ${m} [${p}] ${d.shortcut?`[${d.shortcut}] `:""}${a(d.label)}`),d.description)for(const I of b(a(d.description),Math.max(10,t-6)))i.push(` ${$}${I}${o}`)}else{const p=d.kind==="freetext"?"r":d.kind==="edit"?"e":"a";i.push(` ${m} [${p}] ${a(d.label)}`)}});for(const d of ln(n,e))for(const x of b(`attached: ${P(e,d.anchor)} \u2014 ${a(d.text)}`,Math.max(10,t-2)))i.push(` ${$}${x}${o}`);n.hint&&i.push(` ${w}${a(n.hint)}${o}`),f=` ${$}n/p next/previous \u2191\u2193/jk actions${X(e).length>0?" c comment":""} u/d scroll \u2190\u2192/hl pan q close${o}`}const u=Math.max(0,...i.map(L));return{lines:i,footer:f,maxW:t,maxH:Math.max(0,u-Math.max(1,s))}}h(D,"itemLines");function wn(n,s,e){if(n.slots[n.currentIndex]===void 0){n.scrollOffset=0,n.hscrollOffset=0,n.hscrollMax=0;return}const t=D(n,s);n.hscrollMax=t.maxH,n.hscrollOffset=Math.max(0,Math.min(n.hscrollOffset,n.hscrollMax)),n.scrollOffset=Math.max(0,Math.min(n.scrollOffset,Math.max(0,t.lines.length-j(e))))}h(wn,"clampItemReviewScroll");function kn(n,s){n.hscrollOffset=Math.max(0,Math.min(n.hscrollMax,n.hscrollOffset+s*sn))}h(kn,"panItemReview");function bn(n,s,e){const t=D(n,s),r=j(e),i=n.scrollOffset,c=t.lines.slice(i,i+r);for(;c.length<r;)c.push("");const f=n.hscrollOffset>0?c.map(l=>tn(l,n.hscrollOffset,s)):c,u=Math.min(s,t.maxW+2);return r<e&&f.push(Y(t.footer,F(i,r,t.lines.length),t.maxW+2)),N(f.map(l=>q(l,s)),s,u)}h(bn,"renderItemReview");function On(n,s,e){const t=Math.min(Math.max(20,s-4),120),r=["",` ${M}${k}${a(n.manifest.title)} \u2014 Summary${o}`,` ${$}${g(t)}${o}`,""],i=T(n);for(const u of i.slots){const l=n.manifest.slots.find(m=>m.id===u.id),x=l!==void 0&&v(n,l)?`${E}\u2713${o}`:`${w}\u25CB${o}`;r.push(` ${x} ${M}${a(l===void 0?u.kind:S(l))}${o} ${$}${a(H(u))}${o}`)}const c=Z(n);if(!c){r.push("",` ${w}Cannot submit yet.${o}`);for(const u of n.slots.filter(O))v(n,u)||(r.push(` ${$}${u.id??u.kind} is unanswered${o}`),A(u)||r.push(` ${$}${u.id} must be answered in Northlight${o}`))}r.push("",` ${$}${g(t)}${o}`);const f=c?` ${$}enter${o} submit ${$}u/d${o} scroll ${$}q${o} back`:` ${$}u/d${o} scroll ${$}q${o} back`;return G(r,f,n,s,e,t+2)}h(On,"renderFinal");function j(n){return n>=2?n-1:n}h(j,"bodyRows");function F(n,s,e){if(s<=0||e<=s)return{marks:"",full:""};const t=Math.min(e,n+s),r=`${n>0?"\u25B4":" "}${t<e?"\u25BE":" "}`;return{marks:`${$}${r}${o}`,full:`${$}${r} ${n+1}\u2013${t}/${e}${o}`}}h(F,"scrollBadge");function Y(n,s,e){if(s.full==="")return n;const t=h(r=>e-L(n)-L(r)-2,"room");for(const r of[s.full,s.marks])if(t(r)>=2)return`${n}${" ".repeat(t(r))}${r}`;return` ${s.marks} ${n.trimStart()}`}h(Y,"pinnedFooter");function G(n,s,e,t,r,i,c){const f=j(r),u=Math.max(0,n.length-f);e.scrollOffset=Math.max(0,Math.min(e.scrollOffset,u)),c!==void 0&&f>0&&(c<e.scrollOffset?e.scrollOffset=c:c>=e.scrollOffset+f&&(e.scrollOffset=Math.min(u,c-f+1)));const l=n.slice(e.scrollOffset,e.scrollOffset+f);for(;l.length<f;)l.push("");return f<r&&l.push(Y(s,F(e.scrollOffset,f,n.length),i)),N(l.map(d=>q(d,t)),t,Math.min(t,i))}h(G,"centeredWindow");export{wn as clampItemReviewScroll,kn as panItemReview,On as renderFinal,rn as renderInputBuffer,bn as renderItemReview,pn as renderOverview,gn as verticalCursor,xn as wordLeftIndex,Mn as wordRightIndex,z as wrappedRowBounds};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
var B=Object.defineProperty;var u=(e,t)=>B(e,"name",{value:t,configurable:!0});import{readFileSync as W}from"node:fs";import{envNodeId as D}from"../../shared/env.js";import{defineBranch as H,defineLeaf as E}from"../../core/command.js";import{InputError as r}from"../../core/io.js";import{cliClient as w,getNodeOrNull as K,rethrowAsCliError as y}from"../api-client.js";import{DEFAULT_BASH_NICENESS as F,MAX_BASH_NICENESS as N,writeBashNiceness as X}from"../../core/bash-jobs.js";import{nodeReviveLeaf as Y}from"../node-lifecycle-revive.js";import{kindsStateBlock as C}from"../profile/daemon-profiles.js";import{MODEL_SPEC_FORMS as J}from"../../core/runtime/model-selection.js";import{closePane as z,paneExists as G,respawnPaneSync as V,viewerEnv as Q,viewerPaneOf as Z,nodeInPane as I}from"../surface/node/placement.js";const ee=E({name:"recycle",description:"finish the agent in your pane + recycle it into a fresh root",whenToUse:"you are at an agent's pane and done with it: finish it cleanly and recycle the pane in one move \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The hands-on way to end an agent and immediately start over in place. Use `node lifecycle demote` instead to put it on a finishing track IN PLACE (flip terminal, keep it running) without ending it now, `node lifecycle close` to tear a node and its subtree down WITHOUT finishing (no report, revivable), and `push final` when the agent should finish ITSELF from inside its own turn",help:{name:"node lifecycle recycle",summary:"finish the agent in your current pane and recycle the pane \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to finish. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to recycle. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"recycled",type:"boolean",required:!0,constraint:"True when the pane was recycled into a fresh root."},{name:"node_id",type:"string",required:!1,constraint:"The finished node."},{name:"new_root",type:"string",required:!1,constraint:"The fresh root node booted into the pane."}],outputKind:"object",effects:["Pushes a final report from the node (fans out to all subscribers) and marks it done.","Kills the agent's pi and respawns a fresh resident root in the same tmux pane."]},run:u(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=I(t)),n===void 0||n==="")throw new r({error:"no_node",message:"no node found in this pane to finish",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await w().getNode(n).catch(f=>y(f,"List nodes with `crtr node inspect list`.")),o=(await w().recycleNode(n).catch(y)).node_id;let h=!1;return t!==void 0&&t!==""&&(h=V({pane:t,cwd:i.cwd,env:Q(),command:"crtr"})),{recycled:h,node_id:n,new_root:o}},"run"),render:u(e=>{if(e.recycled!==!0)return e.new_root?`Finished ${e.node_id} and spawned a fresh broker root (${e.new_root}), but its viewer did not attach \u2014 the new broker did not come up. Re-focus the root to view it.`:`Recycle failed for ${e.node_id??"?"} \u2014 not in tmux, or no agent in this pane.`;const t=[`Recycled the pane \u2014 finished ${e.node_id}.`];return e.new_root&&t.push(`- new root: ${e.new_root}`),t.join(`
|
|
2
2
|
`)},"render")}),ne=E({name:"close",description:"close a node + cascade-cancel its exclusive subtree",whenToUse:"you want to tear a node down WITHOUT finishing it, cascade-cancelling every descendant it exclusively owns: abandoning a line of work, killing a stuck or wrong-turn subtree, clearing a branch you no longer need. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`node lifecycle revive`). Use `node lifecycle recycle` instead to FINISH the agent in your pane with a final report, and `push final` when a worker should end its own work normally (Alt+C \u2192 x)",help:{name:"node lifecycle close",summary:"close a node and cascade-cancel its subtree \u2014 kill its tmux window plus those of every descendant it EXCLUSIVELY owns (down the subscription spine \u2014 the watches each manager holds on its children), mark retained nodes canceled, and leave them a notice they read on resume. A descendant still subscribed to by a manager outside the subtree is left running. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`crtr node lifecycle revive`)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to close. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to close. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The node that was closed \u2014 the cascade root."},{name:"count",type:"number",required:!1,constraint:"How many nodes were closed (root + cascaded descendants)."},{name:"closed_ids",type:"string[]",required:!1,constraint:"All closed node ids, kill order (leaves first, root last)."},{name:"spared",type:"string[]",required:!1,constraint:"Descendants left alive because a manager outside the subtree still subscribes to them."}],outputKind:"object",effects:["Marks retained nodes and their exclusive descendants `canceled` and clears intent (the daemon never revives a canceled node).","Deletes a non-streaming node with no substantive assistant output unless it has an open managed worktree; a deleted node cannot be revived.","Kills each closed node's tmux window; retained nodes keep their pi sessions and canvas edges for a later revive.","Appends a cancellation notice to each retained node's inbox, surfaced on its next resume."]},run:u(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=I(t)),n===void 0||n==="")throw new r({error:"no_node",message:"no node found in this pane to close",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await w().closeNode(n,process.ppid>0?{caller_pid:process.ppid}:{}).catch(d=>y(d,"List nodes with `crtr node inspect list`."));return{node_id:i.root,count:i.closed.length,closed_ids:i.closed,spared:i.spared}},"run"),render:u(e=>{const t=e.spared?.length??0;return`Closed ${e.node_id} and its exclusive subtree \u2014 ${e.count} node(s) closed${t>0?`, ${t} spared (still managed from outside the subtree)`:""}.`},"render")}),j="Model change first: a running broker, including one still starting right after `node new`, switches over its socket; a dormant node persists the durable model override and launch recipe for its next revive.",pe=E({name:"config",description:"reconfigure a node's settings headlessly \u2014 model, lifecycle (terminal/resident), kind, mode, name; repair a missing cwd",whenToUse:"you want to reconfigure a node's settings headlessly after spawn \u2014 swap its model/provider, flip it terminal\u2194resident (interactable), respecialize its kind, switch base\u2194orchestrator mode, or rename it \u2014 without opening its viewer. It also repairs a dormant node whose recorded cwd is gone, starting its next revive in a fresh conversation. The durable attribute setter; reuses the same recipes a revive reads. Use `node promote` for the opinionated self-promotion path, and `node lifecycle` for teardown actions (recycle/close/demote).",help:{name:"node config",summary:"reconfigure a node's settings headlessly \u2014 model, lifecycle, kind, mode, name; repair a missing cwd",params:[{kind:"flag",name:"node",type:"string",required:!1,repeatable:!0,constraint:"Target node id. Defaults to the node in --pane, else CRTR_NODE_ID. Repeat it, or use --nodes-from, to apply a --profile or --cwd change to many nodes; a bulk run reports each node and keeps going past a failure."},{kind:"flag",name:"nodes-from",type:"string",required:!1,constraint:"File of node ids, one per line (blank lines and #-comments ignored), or `-` for stdin. Bulk form of --node; only with --profile or --cwd."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to resolve the node from. Defaults to $TMUX_PANE."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Durable model change. Omit unless the task or governing guidance requires a different model.",focusedHelp:{whenToUse:"Use only when intentionally changing this node\u2019s durable model because the user, task, or governing agent guidance requires a specific provider, model, capability tier, or family. Omit it to keep the current model; do not use it merely to inspect available models or experiment with a live node.",value:`Accepted durable forms: ${J}. A live broker also accepts a free-text substring matched against its loaded model registry; a dormant node cannot resolve a substring and requires a durable form.`,effects:[j,"A concrete provider/id runs exactly that model on every launch and revive. A portable provider/tier, bare tier, or family alias keeps its tier: when that model's provider is rate-limited or rejects its credential, the node moves to the next model in its route: another provider's model at the same tier first, then the configured fallback tier (strong by default)."]}},{kind:"flag",name:"lifecycle",type:"enum",choices:["terminal","resident"],required:!1,constraint:"Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable while attached or awaiting something live."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here."},{kind:"flag",name:"mode",type:"enum",choices:["base","orchestrator"],required:!1,constraint:"Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator creates your document `roadmap` if absent."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Rename the node; if the node has a live window, also rename that viewer window."},{kind:"flag",name:"nice",type:"int",required:!1,constraint:`Scheduler niceness every command this node's bash tool starts at, 0\u2013${N} (default ${F}). ${F} keeps the node's builds and test suites below the daemon, the brokers, the viewers, and the person's own shell; 0 lets them compete on equal terms with everything else on the host; ${N} makes them yield to it. Applies to the node's very next command, foreground and background alike.`},{kind:"flag",name:"listen",type:"string",required:!1,constraint:`The node's default listening configuration, as JSON mapping an event level ("1".."10") to how events at or above it reach the node: interrupt, steer, after-turn, or next-turn (e.g. '{"3":"next-turn"}' \u2014 events of level 3+ wait for its next turn and never wake it). Applies to every watch with no configuration of its own. Pass \`null\` to reset to the global default.`},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Replacement launch directory (absolute, existing). Alone, or with --keep-session / --restart. Without --keep-session it is a repair: only a dormant, non-forked node with no open managed worktree whose recorded cwd is gone, and it clears the saved Pi session (next revive starts fresh). When the new path is the same directory as the recorded one (a symlink alias) it relocates and keeps the conversation automatically \u2014 see --keep-session."},{kind:"flag",name:"keep-session",type:"bool",required:!1,constraint:"With --cwd: assert the new path is the SAME directory as the recorded cwd and relocate WITHOUT clearing the Pi session. Needed only when the recorded path is already gone (e.g. the symlink was deleted); while it still exists the daemon verifies by realpath and refuses a different directory. Allowed for live, idle, dormant and finished nodes, forks included. Rewrites the recorded cwd and the cwd in the node's transcript header so a later resume finds it. Refused while the node owns an open managed worktree."},{kind:"flag",name:"rewrite-prefix",type:"string",required:!1,constraint:'Bulk cwd form, `OLD=NEW` (absolute paths): with several nodes, each node\'s recorded cwd that equals OLD or lies beneath it is relocated to the same place under NEW, keeping its session (as --keep-session); nodes whose cwd is elsewhere are reported as failed "not under OLD". Use instead of --cwd when the nodes sit in different subdirectories.'},{kind:"flag",name:"profile",type:"string",required:!1,constraint:"Reassign the node to this profile (id or exact name; must be an active profile of the node's app that defines the node's kind). Works for live, idle, dormant, finished and profile-less (detached) nodes. Used alone, or with --restart."},{kind:"flag",name:"restart",type:"bool",required:!1,constraint:"With --profile, or --cwd on a live node: after the change, SIGTERM the node's live broker so crtrd resumes it from the same transcript (continuing an interrupted turn) with the new profile/cwd. No effect on a node with no live broker."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The reconfigured node, present for ordinary config changes."},{name:"model",type:"string",required:!1,constraint:"The resolved model, present only when --model changed it."},{name:"lifecycle",type:"string",required:!1,constraint:"The new lifecycle, present only when --lifecycle changed it."},{name:"kind",type:"string",required:!1,constraint:"The new kind, present only when --kind changed it."},{name:"mode",type:"string",required:!1,constraint:"The new mode, present only when --mode changed it."},{name:"name",type:"string",required:!1,constraint:"The new name, present only when --name changed it."},{name:"nice",type:"number",required:!1,constraint:"The new bash niceness, present only when --nice changed it."},{name:"listen",type:"object",required:!1,constraint:"The new default listening configuration (null = global default), present only when --listen changed it."},{name:"cwd",type:"string",required:!1,constraint:"The resolved replacement directory, present only after a cwd change."},{name:"session_kept",type:"boolean",required:!1,constraint:"With a cwd change: true when the node still has its Pi session bound (conversation preserved)."},{name:"profile",type:"string",required:!1,constraint:"The node's profile id after --profile."},{name:"bulk",type:"boolean",required:!1,constraint:"True for a multi-node run."},{name:"applied",type:"number",required:!1,constraint:"Bulk: nodes changed (or already in the requested state)."},{name:"failed",type:"number",required:!1,constraint:"Bulk: nodes that errored; the exit code is 1 when any did."},{name:"results",type:"array",required:!1,constraint:"Bulk: one {node_id, ok, error?} per node, in input order."}],outputKind:"object",effects:[j,"Kind, mode, and lifecycle changes rebuild the launch spec from the fresh node meta; setting mode to orchestrator creates your document `roadmap` if absent.","Name changes update the row and, when the node already has a live window, rename that viewer window to the new full name.","A cwd repair (gone recorded cwd, no --keep-session) writes only the lexically resolved cwd and clears saved Pi session pointers; the old transcript and durable node work remain untouched.","A session-keeping cwd relocation (--keep-session, or --cwd naming a symlink alias of the recorded directory) rewrites the recorded cwd and the cwd line in the header of each of the node's transcripts; the Pi session stays bound and the next revive resumes the same conversation. A LIVE broker keeps the old cwd string in its environment and tools until it restarts: it keeps working while the old path exists, so pass --restart (or revive it) BEFORE deleting an old symlink.","A profile reassignment rewrites the node's profile and rebuilds its launch recipe. Profile env, documents delivered at boot, purview and bearings are read at launch, so a LIVE broker keeps the old profile until it next restarts; --restart bounces it now. Dormant or finished nodes simply carry the new profile into their next revive. The node's deadlines and profile pause follow the new profile immediately.","A niceness change is stored with the node's own bash-job state, so it takes effect on the next command without a revive and survives one.","A listen change sets the node's default listening configuration; it never changes which watches it holds.","Every successful change publishes a `configured` event on the node (level 1) naming the changed settings, so its watchers can see it."]},helpState:{leaf:C},run:u(async e=>{const t=e.pane??process.env.TMUX_PANE,n=e.node,i=Array.isArray(n)?n:n===void 0?[]:[n],d=e.nodesFrom;if(d!==void 0){const a=d==="-"?W(0,"utf8"):W(d,"utf8");for(const A of a.split(`
|
|
3
|
-
`)){const b=A.replace(/#.*/,"").trim();b!==""&&i.push(b)}}const o=e.profile,h=e.cwd,f=e.rewritePrefix;if(i.length>1||d!==void 0||f!==void 0){if(i.length===0)throw new r({error:"no_nodes",message:"bulk config needs at least one node",next:"Pass --node ID (repeatable) or --nodes-from FILE|-."});if(o===void 0&&h===void 0&&f===void 0)throw new r({error:"bulk_unsupported",message:"several nodes are accepted only with --profile, --cwd or --rewrite-prefix",next:"Pass --profile <profile>, --cwd <dir> or --rewrite-prefix OLD=NEW with the node list."});let a;if(f!==void 0){const p=f.indexOf("="),c=p<0?"":f.slice(0,p).replace(/\/+$/,""),m=p<0?"":f.slice(p+1).replace(/\/+$/,"");if(!c.startsWith("/")||!m.startsWith("/"))throw new r({error:"bad_rewrite_prefix",message:`--rewrite-prefix must be OLD=NEW with absolute paths: ${f}`,field:"rewrite-prefix",next:"Pass e.g. --rewrite-prefix /Users/me/Code/northlight=/Users/me/Code/
|
|
3
|
+
`)){const b=A.replace(/#.*/,"").trim();b!==""&&i.push(b)}}const o=e.profile,h=e.cwd,f=e.rewritePrefix;if(i.length>1||d!==void 0||f!==void 0){if(i.length===0)throw new r({error:"no_nodes",message:"bulk config needs at least one node",next:"Pass --node ID (repeatable) or --nodes-from FILE|-."});if(o===void 0&&h===void 0&&f===void 0)throw new r({error:"bulk_unsupported",message:"several nodes are accepted only with --profile, --cwd or --rewrite-prefix",next:"Pass --profile <profile>, --cwd <dir> or --rewrite-prefix OLD=NEW with the node list."});let a;if(f!==void 0){const p=f.indexOf("="),c=p<0?"":f.slice(0,p).replace(/\/+$/,""),m=p<0?"":f.slice(p+1).replace(/\/+$/,"");if(!c.startsWith("/")||!m.startsWith("/"))throw new r({error:"bad_rewrite_prefix",message:`--rewrite-prefix must be OLD=NEW with absolute paths: ${f}`,field:"rewrite-prefix",next:"Pass e.g. --rewrite-prefix /Users/me/Code/northlight=/Users/me/Code/northlight-systems."});a=[c,m]}const A=w(),b=[];for(const p of[...new Set(i)])try{const c=o!==void 0?{profile:o}:{cwd:h??""};if(a!==void 0){const m=(await A.getNode(p)).cwd;if(m!==a[0]&&!m.startsWith(`${a[0]}/`))throw new Error(`cwd ${m} is not under ${a[0]}`);c.cwd=a[1]+m.slice(a[0].length),c.keep_session=!0}e.keepSession===!0&&(c.keep_session=!0),e.restart===!0&&(c.restart=!0),await A.patchConfig(p,c),b.push({node_id:p,ok:!0})}catch(c){let m=c instanceof Error?c.message:String(c);try{y(c)}catch(U){U instanceof Error&&(m=U.message)}b.push({node_id:p,ok:!1,error:m})}const P=b.filter(p=>!p.ok).length;return P>0&&(process.exitCode=1),{bulk:!0,applied:b.length-P,failed:P,results:b}}const _=await M({...e,node:i[0]},t),x=h;if(x!==void 0&&["model","kind","lifecycle","mode","name","nice","listen","profile"].some(a=>e[a]!==void 0))throw new r({error:"cwd_not_exclusive",message:"cwd cannot be combined with another config flag except --keep-session and --restart",field:"cwd",next:"Pass --cwd alone (plus --keep-session / --restart)."});if(o!==void 0&&["model","kind","lifecycle","mode","name","nice","listen"].some(a=>e[a]!==void 0))throw new r({error:"profile_not_exclusive",message:"profile cannot be combined with another config flag except --restart",field:"profile",next:"Pass --profile alone (plus --restart)."});if(e.keepSession===!0&&x===void 0)throw new r({error:"keep_session_needs_cwd",message:"--keep-session only applies with --cwd",field:"keep-session",next:"Pass --cwd <dir> --keep-session."});if(e.restart===!0&&x===void 0&&o===void 0)throw new r({error:"restart_needs_change",message:"--restart only applies with --profile or --cwd",field:"restart",next:"Pass --profile <profile> or --cwd <dir> with --restart."});const S=e.model?.trim();if(S==="")throw new r({error:"empty_spec",message:"a model spec is required",field:"model",next:"Run `crtr node config --model -h` and read the focused contract before retrying."});const O=e.kind?.trim(),T=e.lifecycle?.trim(),q=e.mode?.trim(),L=e.name,v=e.nice,$=e.listen?.trim();let R;if($!==void 0)try{R=JSON.parse($)}catch{throw new r({error:"invalid_listen",message:`--listen is not JSON: ${$}`,field:"listen",next:`Pass a JSON object such as '{"3":"next-turn"}', or null.`})}if(v!==void 0&&(v<0||v>N))throw new r({error:"bad_nice",message:`niceness must be between 0 and ${N}: ${v}`,field:"nice",next:`A negative niceness needs root, so pass 0\u2013${N}.`});if(T!==void 0&&T.toLowerCase()!=="terminal"&&T.toLowerCase()!=="resident")throw new r({error:"bad_lifecycle",message:`invalid lifecycle: ${T}`,field:"lifecycle",next:"Pass `terminal` or `resident`."});if(q!==void 0&&q.toLowerCase()!=="base"&&q.toLowerCase()!=="orchestrator")throw new r({error:"bad_mode",message:`invalid mode: ${q}`,field:"mode",next:"Pass `base` or `orchestrator`."});const s=[],l={};if(S!==void 0&&(l.model=S,s.push("model")),O!==void 0&&(l.kind=O,s.push("kind")),T!==void 0&&(l.lifecycle=T.toLowerCase(),s.push("lifecycle")),q!==void 0&&(l.mode=q.toLowerCase(),s.push("mode")),L!==void 0&&(l.name=L,s.push("name")),x!==void 0&&(l.cwd=x,s.push("cwd"),e.keepSession===!0&&(l.keep_session=!0),e.restart===!0&&(l.restart=!0)),o!==void 0&&(l.profile=o,s.push("profile"),e.restart===!0&&(l.restart=!0)),$!==void 0&&(l.listen=R??null,s.push("listen")),v!==void 0){const a=await w().getNode(_).catch(A=>y(A));if(X(a.paths.context_dir,v),s.push("nice"),s.length===1)return{node_id:_,nice:v}}if(s.length===0)throw new r({error:"no_change",message:"no config changes requested",next:"Pass at least one of --model, --lifecycle, --kind, --mode, --name, --nice, --listen, --cwd."});const g=await w().patchConfig(_,l).catch(a=>y(a,"List nodes with `crtr node inspect list`."));if(x!==void 0)return{cwd:g.cwd,node_id:_,...l.keep_session===!0||g.pi_session_id!=null?{session_kept:g.pi_session_id!=null}:{}};if(o!==void 0)return{node_id:_,profile:g.profile_id??null};const k={node_id:_};return s.includes("model")&&(k.model=g.model_override??S),s.includes("lifecycle")&&(k.lifecycle=g.lifecycle),s.includes("kind")&&(k.kind=g.kind),s.includes("mode")&&(k.mode=g.mode),s.includes("name")&&(k.name=g.name),s.includes("nice")&&(k.nice=v),s.includes("listen")&&(k.listen=l.listen),k},"run"),render:u(e=>{if(e.bulk===!0){const n=e.results;return[`Applied to ${e.applied} node(s), ${e.failed} failed.`,...n.filter(i=>!i.ok).map(i=>` FAILED ${i.node_id}: ${i.error}`)].join(`
|
|
4
4
|
`)}if(e.cwd!==void 0)return`Set cwd of ${e.node_id}: ${e.cwd}${e.session_kept===!0?" (conversation kept)":""}.`;if(e.profile!==void 0)return`Reassigned ${e.node_id} to profile ${e.profile}.`;const t=[];return e.model!==void 0&&t.push(`model=${e.model}`),e.lifecycle!==void 0&&t.push(`lifecycle=${e.lifecycle}`),e.kind!==void 0&&t.push(`kind=${e.kind}`),e.mode!==void 0&&t.push(`mode=${e.mode}`),e.name!==void 0&&t.push(`name=${e.name}`),e.nice!==void 0&&t.push(`nice=${e.nice}`),e.listen!==void 0&&t.push(`listen=${JSON.stringify(e.listen)}`),`Configured ${e.node_id} \u2014 ${t.join(", ")}`},"render")}),me=E({name:"promote",description:"become an orchestrator of a chosen kind",whenToUse:"the remaining work contains independent units that can run in parallel, and the task is large enough that coordinating and integrating them will materially improve intelligence, productivity, or elapsed throughput. Become a long-lived, roadmap-holding orchestrator whose primary job is steering those children across refreshes (`node yield`). Keep sequential or tightly coupled work in base mode even when it spans phases or context windows; spawning one helper does not itself require promotion",tier:"important",help:{name:"node promote",summary:"promote yourself to an orchestrator when worthwhile parallel delegation should make coordination your primary job; task duration, sequential phases, or one helper do not qualify. Mode only \u2014 lifecycle stays as-is unless you pass --resident",params:[{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys context prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one \u2014 it sets the orchestrator persona you revive into."},{kind:"flag",name:"resident",type:"bool",required:!1,constraint:"ALSO flip lifecycle\u2192resident: make the node interactable \u2014 it is never forced to submit a final, and unattended grace parks it after a summary turn instead, leaving it revivable fresh against its goal and roadmap. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done)."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model you run on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to promote. Defaults to the caller (CRTR_NODE_ID)."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The promoted node."},{name:"kind",type:"string",required:!0,constraint:"The kind it now orchestrates as."},{name:"model",type:"string",required:!1,constraint:"The model tier it now runs on, present only when you changed it with --model."},{name:"lifecycle",type:"string",required:!0,constraint:'"resident" if you passed --resident, else unchanged (typically "terminal").'},{name:"roadmap_written",type:"boolean",required:!0,constraint:"True if the `roadmap` document was created by this call."},{name:"roadmap_ref",type:"string",required:!0,constraint:"Your roadmap document (`<node>/roadmap`) \u2014 author your plan with `crtr doc edit roadmap`."},{name:"goal_path",type:"string",required:!0,constraint:"Absolute path to your goal doc (context/initial-prompt.md) \u2014 the mandate you were spawned with."}],outputKind:"object",effects:["Flips mode\u2192orchestrator + kind\u2192chosen (lifecycle unchanged unless --resident, which also flips lifecycle\u2192resident); --model durably sets the model; rewrites the launch spec to that kind's orchestrator persona; creates your document `roadmap` (rule `on: boot`, `deliver: content`) if absent.","The next model request is gated on appending and persisting your new-role guidance. If a fresh context starts first, its rebuilt baseline carries the new role."]},helpState:{leaf:C},run:u(async e=>{const t=e.node??D();if(t===void 0||t==="")throw new r({error:"no_node",message:"no node to promote (set CRTR_NODE_ID or pass --node)",next:"Run from inside a node, or pass --node <id>."});const n=e.kind,i=e.model,d=e.resident===!0,o=await w().promoteNode(t,{...n!==void 0?{kind:n}:{},...d?{resident:!0}:{},...i!==void 0?{model:i}:{}}).catch(h=>y(h));return{node_id:o.node_id,kind:o.kind,lifecycle:o.lifecycle,...i!==void 0&&o.model_override!=null?{model:o.model_override}:{},roadmap_written:o.roadmap_written??!1,roadmap_ref:o.roadmap_ref??"",goal_path:o.goal_path??""}},"run")});async function M(e,t){let n=e.node;if((n===void 0||n==="")&&(n=I(t)),(n===void 0||n==="")&&(n=D()),n===void 0||n==="")throw new r({error:"no_node",message:"no node (set CRTR_NODE_ID, pass --node, or run from the agent's pane)",next:"Run from inside a node, pass --node <id>, or --pane <pane>."});if(await K(n)===null)throw new r({error:"not_found",message:`no node: ${n}`,next:"List nodes with `crtr node inspect list`."});return n}u(M,"resolveLifecycleNode");const te=E({name:"demote",description:"demote a node to terminal in place; it stays focused and running but now owes a final; --detach also closes its viewer (broker keeps running off-screen)",whenToUse:"you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE \u2014 it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node lifecycle recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node config --lifecycle` for the orthogonal low-level flip (incl. terminal\u2192resident)",help:{name:"node lifecycle demote",summary:"demote a node to terminal IN PLACE \u2014 it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode\u2191); `--detach` also closes its viewer (the broker engine keeps running off-screen)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to demote. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to demote, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you."},{kind:"flag",name:"detach",type:"bool",required:!1,constraint:"After flipping terminal, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and \u2014 now terminal \u2014 pushes a final up the spine when done; a later `focus` reattaches a viewer. The Alt+C \u2192 D move."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The demoted node."},{name:"detached",type:"boolean",required:!1,constraint:"True when --detach closed the agent's viewer pane (broker keeps running)."}],outputKind:"object",effects:["Flips the node's lifecycle\u2192terminal and rebuilds its launch spec so a future revive boots terminal \u2014 it stays focused and running, now owing a final up the spine.","The next model request is gated on appending and persisting the role transition. If a fresh context starts first, its rebuilt baseline carries the new lifecycle.","With --detach: closes the agent's viewer pane WITHOUT touching the broker engine \u2014 it keeps generating off-screen, reconnectable by a later `focus`."]},run:u(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await M(e,t),i=await w().patchConfig(n,{lifecycle:"terminal"}).catch(o=>y(o));let d=!1;if(e.detach===!0){const o=Z(n)??t;o!==void 0&&o!==""&&G(o)&&(d=z(o))}return{node_id:i.node_id,detached:d}},"run"),render:u(e=>`Demoted ${e.node_id} \u2014 lifecycle now terminal (in place)${e.detached===!0?", viewer closed (broker still running off-screen)":""}.`,"render")}),ge=H({name:"lifecycle",description:"lifecycle actions \u2014 recycle, close, demote, or revive a node",whenToUse:"you are ending, transitioning, or bringing back one node you can name: finish the agent in your pane and reboot a fresh root (`recycle`), tear a node + its exclusive subtree down without finishing (`close`), flip a node onto a finishing track in place (`demote`), or bring a dormant node back (`revive`). To merely SET the terminal/resident attribute headlessly use `node config --lifecycle`; to FINISH from inside a node's own turn use `crtr push final`; to mass-reconnect EVERY disconnected node at once use `canvas revive --all`.",help:{name:"node lifecycle",summary:"end, transition, or revive a node \u2014 recycle the pane, close + cascade-cancel a subtree, demote to terminal in place, or revive a dormant node",model:"Lifecycle actions preserve or replace an existing node identity. Finishing is permanent, closing is revivable, demotion changes the completion obligation in place, and revival relaunches a dormant broker. Teardown cascades only through descendants exclusively managed by the affected subtree."},children:[ee,ne,te,Y]}),we=E({name:"yield",description:"refresh your context, same mode, against your roadmap or goal",whenToUse:"your context is filling but the mandate remains; refresh in the same mode, or combine the refresh with promotion when meaningful independent work can run in parallel and coordinating it should become your primary job",help:{name:"node yield",summary:"request a fresh context window against your roadmap or original goal; mode stays unchanged unless --promote is passed",guide:"The yield aborts your turn the instant this command returns: no text or tool call you planned after it will run. So anything the user is owed \u2014 an answer, a status \u2014 must already be a visible message in the conversation before you call it; never plan to reply after yielding. Before invoking: (1) If you hold a document `roadmap`, bring it current (`crtr doc edit roadmap`). It, not the yield note, carries your task into the fresh window. It has four sections: Goal (its first line is also the roadmap\u2019s preview, so set it with `--preview` whenever the goal changes), Exit criteria, Where things stand, and Working set. Keep it at your own level of abstraction. Detail below your level belongs to the node or document that holds it: link to it, don\u2019t copy it. Where things stand is present tense; delete what is done instead of marking it. Name units by what they are and how they fit, never by child node id or status: your bearings already list your children with their status. Working set holds `[[links]]`. Their previews load with the roadmap, so write no note beside a link. When a unit lands, delete its links: its evidence belongs in its report. (2) Give content its own document when it lives on a different timescale from the roadmap (exploration that outlives the current work), goes to a different reader (a brief for a child), or matters only when something specific happens. Link each such document from the Working set. Anything you had to re-derive in this window that a future window will need again belongs in a document: one you own for this task, `--owner repo` for lessons that would help others in the repo. A document that only points at other documents is not worth the extra hop. (3) Actions waiting on one specific event (\u201Cwhen X reports, send Y first\u201D) go in the yield note, not the roadmap. They are spent once they fire. (4) Send any pending human question; asks already in flight survive the refresh.",params:[{kind:"flag",name:"promote",type:"bool",required:!1,constraint:"Also become an orchestrator as you refresh. Pass only when (1) the remaining work contains independent units that can run in parallel and (2) the task is large enough that parallel execution will materially improve intelligence, productivity, or elapsed throughput after coordination and synthesis. Task length, context exhaustion, sequential phases, or one helper are not enough; keep hands-on work in base mode and yield again as needed. Promote when coordinating and integrating children becomes your primary job. Already an orchestrator? This is not an error: your mode stays orchestrator and a missing roadmap is seeded."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a registered sub-kind is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model your fresh revive runs on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"stdin",name:"message",required:!0,constraint:"A note to your future self \u2014 what you just tried, where things stand, the first move on waking, and any action waiting on a specific event: enough to continue efficiently, no more. Never describe a reply as sent unless it is visible in the conversation above this call. Your task and its state belong in the roadmap. Surfaced as <yield-message> in the next revive. Pass as a positional or pipe via heredoc."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The yielding node."},{name:"model",type:"string",required:!1,constraint:"The model tier your fresh revive will run on, present only when you changed it with --model."}],outputKind:"object",effects:["Aborts your current turn as soon as this command returns \u2014 nothing after it runs \u2014 then the daemon revives the node fresh.","--kind and/or --model durably reshape the node (kind respecialization, model change) before the refresh-revive; --promote additionally flips mode\u2192orchestrator and creates your document `roadmap` \u2014 mode is otherwise left exactly as it was."]},helpState:{leaf:C},run:u(async e=>{const t=D();if(t===void 0||t==="")throw new r({error:"no_node",message:"no node to yield (CRTR_NODE_ID unset)",next:"Run from inside a node."});const n=e.kind,i=e.model,d=e.promote===!0,o=typeof e.message=="string"?e.message.trim():"";if(o==="")throw new r({error:"empty_message",message:"a yield message is required (stdin or positional)",next:"Pass a note to your future self as an argument or pipe it on stdin."});const h=await w().yieldNode(t,{note:o,...n!==void 0?{kind:n}:{},...i!==void 0?{model:i}:{},...d?{promote:!0}:{}}).catch(f=>y(f));return{node_id:h.node_id,...i!==void 0&&h.model_override!=null?{model:h.model_override}:{}}},"run")});export{pe as nodeConfig,ge as nodeLifecycle,me as nodePromote,we as nodeYield};
|
|
@@ -14,7 +14,7 @@ export type SetupTool = 'tmux' | 'nvim' | 'nerd-font';
|
|
|
14
14
|
* (`installSource` is its git URL); `marketplace-plugin` installs one plugin
|
|
15
15
|
* from a registered marketplace (`installSource` is a `<marketplace>/<plugin>`
|
|
16
16
|
* ref). Neither runs the bundled pi CLI, and neither happens anywhere but
|
|
17
|
-
* setup: an install that never runs setup — a
|
|
17
|
+
* setup: an install that never runs setup — a Northlight guest image, CI —
|
|
18
18
|
* fetches no catalog and carries no command whose external binary it lacks. */
|
|
19
19
|
export type SetupPackageSourceKind = 'local-path' | 'npm' | 'marketplace-source' | 'marketplace-plugin';
|
|
20
20
|
export type SetupPackageIdentity = string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
var re=Object.defineProperty;var a=(e,t)=>re(e,"name",{value:t,configurable:!0});import{spawn as ie,spawnSync as se}from"node:child_process";import{homedir as w,tmpdir as ae}from"node:os";import{mkdtempSync as oe,rmSync as ce}from"node:fs";import{piAgentDir as P}from"../../core/runtime/package-health.js";import{basename as le,dirname as _,join as m,relative as ue,resolve as de}from"node:path";import{isDeepStrictEqual as x}from"node:util";import{BINDING_CATALOG as pe,normalizeGesture as fe,splitGestureExpression as he,inboxShortcut as me,resolveKeybindings as S}from"../../core/keybindings/index.js";import{runtimeManifest as ge}from"./shared.js";import{atomicWriteJson as ke,readJsonIfExists as ve,readJsonOrNull as L}from"../../core/fs-utils.js";import{resolveBundledPiCliPath as xe}from"../../core/runtime/pi-cli.js";import{crtrVersion as we}from"../../shared/crtr-version.js";import{installTmuxBindings as be}from"../../core/runtime/tmux-chrome.js";import{normalizeTmuxPassthrough as ye}from"../../core/config.js";import{detectNerdFont as Ie,nerdFontInstallCommand as Pe,createCurlPercentReader as Se,NERD_FONT_MANUAL_LINUX_COMMAND as Ce,NERD_FONT_SELECT_HINT as Ee}from"../../core/runtime/nerd-font.js";import{startProgressBar as Ae}from"../../core/runtime/progress-bar.js";import{ensureRenderer as $e,isRendererReady as K}from"../../core/termrender/termrender.js";import{installFromMarketplace as Re,isMarketplaceRelativeSource as De}from"../pkg/plugin-manage.js";import{requiredPluginEnv as Me}from"../../core/plugin-env.js";import{readPluginManifest as Te}from"../../core/manifest.js";import{addMarketplace as j}from"../pkg/market-manage.js";import{clone as Ne}from"../../core/git.js";import{readMarketplaceManifest as Oe}from"../../core/manifest.js";import{findPluginByName as F,findMarketplaceByName as C}from"../../core/resolver.js";import{expandTilde as _e}from"../../core/fs-utils.js";import{applyTerminalEdit as Le}from"./terminal-option.js";const E="crouter-official-marketplace",A="https://github.com/crouton-labs/crouter-official-marketplace.git",$="main";function It(e){const t=[],n=new Set,r=new Map;try{for(const o of e){if(o.sourceKind!=="marketplace-plugin")continue;const[l,c]=o.installSource.split("/");if(!l||!c)continue;const u=F(c,"user")?.manifest;let d=C(l,"user");if(!d&&!u&&l===E)try{let i=r.get(l);i||(i=oe(m(ae(),"crtr-setup-market-")),r.set(l,i),Ne(A,m(i,"checkout"),{ref:$,depth:1}));const p=m(i,"checkout"),v=Oe(p);v&&(d={root:p,manifest:v})}catch{}const h=d?.manifest.plugins.find(i=>i.name===c)?.source,f=h&&De(h)?Te(m(d.root,h))??u:u;if(f)for(const i of Me(f.env))n.has(i)||(n.add(i),t.push({name:i,plugin:c,declaration:f.env[i]}))}}finally{for(const o of r.values())ce(o,{recursive:!0,force:!0})}return t}a(It,"setupPluginEnvFields");function G(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}a(G,"isKeybindingsObject");function g(e){return G(e)?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,Array.isArray(n)?[...n]:n])):{}}a(g,"cloneKeybindings");class Pt{static{a(this,"SetupKeybindingsDraft")}expected;baseline;current;baselineInvalidRoot;invalidRoot;resolutionCache;expectedPassthrough;baselinePassthrough;currentPassthrough;constructor(t,n=void 0){this.expected=t,this.baseline=g(t),this.current=g(t),this.baselineInvalidRoot=t!==void 0&&!G(t)?t:void 0,this.invalidRoot=this.baselineInvalidRoot,this.expectedPassthrough=n,this.baselinePassthrough=ye(n),this.currentPassthrough=[...this.baselinePassthrough]}get dirty(){return!x(this.current,this.baseline)||!x(this.invalidRoot,this.baselineInvalidRoot)||!x(this.currentPassthrough,this.baselinePassthrough)}overrides(){return g(this.current)}expectedKeybindings(){return this.expected}tmuxPassthrough(){return[...this.currentPassthrough]}expectedTmuxPassthrough(){return this.expectedPassthrough}usesTmuxPassthrough(t){return this.currentPassthrough.includes(t)}toggleTmuxPassthrough(t){this.currentPassthrough=this.currentPassthrough.includes(t)?this.currentPassthrough.filter(n=>n!==t):[...this.currentPassthrough,t]}resolution(){return this.resolutionCache===void 0&&(this.resolutionCache=S(this.invalidRoot===void 0?this.current:this.invalidRoot)),this.resolutionCache}hasInvalidRoot(){return this.invalidRoot!==void 0}expression(t){return this.resolution().gestures(t).join(", ")}edit(t,n){if(this.invalidRoot!==void 0)return{ok:!1,diagnostics:[{severity:"error",code:"invalid-keybindings",offendingValue:this.invalidRoot,message:"Reset all before editing: the saved keybindings root is invalid."}]};const r=pe.find(f=>f.id===t);let o;try{o=n.trim()===""?[]:he(n).map(f=>fe(f.trim(),r))}catch(f){return{ok:!1,diagnostics:[{severity:"error",code:"invalid-override",bindingId:t,offendingValue:n,message:f instanceof Error?f.message:String(f)}]}}const l=[...new Set(o)],c={...this.current};delete c[t];const u=[...S(c).gestures(t)],s=x(l,u)?c:{...this.current,[t]:l},d=S(s),h=d.diagnostics.filter(f=>f.bindingId===t||f.relatedBindingIds?.includes(t));return h.length>0?{ok:!1,diagnostics:h}:(this.current=s,this.resolutionCache=void 0,{ok:!0,diagnostics:d.diagnostics})}unbind(t){return this.edit(t,"")}resetRow(t){delete this.current[t],this.resolutionCache=void 0}resetAll(){this.current={},this.currentPassthrough=[],this.invalidRoot=void 0,this.resolutionCache=void 0}discard(){this.current=g(this.baseline),this.currentPassthrough=[...this.baselinePassthrough],this.invalidRoot=this.baselineInvalidRoot,this.resolutionCache=void 0}markSaved(t,n=[]){this.expected=Object.keys(t).length===0?void 0:g(t),this.baseline=g(t),this.current=g(t),this.expectedPassthrough=n.length===0?void 0:[...n],this.baselinePassthrough=[...n],this.currentPassthrough=[...n],this.baselineInvalidRoot=void 0,this.invalidRoot=void 0,this.resolutionCache=void 0}}const Ke={"official-marketplace":{name:E,shortDescription:"the official crtr plugin catalog \u2014 registered as a source so its plugins are installable by ref.",longDescription:"Shallow-clones github.com/crouton-labs/crouter-official-marketplace into the user scope and registers it, which is what makes `crtr pkg plugin install crouter-official-marketplace/<name>` resolve and what fills `crtr pkg market browse`. Registering the source installs no plugin by itself. Uncheck it on a machine that should reach no external catalog \u2014 a plugin selected below still registers the marketplace it comes from, because its ref cannot resolve otherwise."},"crtr-extensions":{name:"pi-crtr-extensions",shortDescription:"crtr companion extensions \u2014 command palette, help gating, subscription/OAuth rotation, nested context.",longDescription:"Bundles the extensions built for crtr: a crtr slash-command palette, crtr/crouter help gating, surfacing of Claude Code marketplace plugin commands, nested AGENTS.md/CLAUDE.md loading, OAuth login + rotation across multiple Claude subscriptions (provider-rotation), and system-prompt trimming. The OAuth/rotation piece is what lets crtr authenticate your Claude account."},"oauth-adapter":{name:"pi-claude-oauth-adapter",shortDescription:"Anthropic OAuth / Claude Code credential compatibility for pi.",longDescription:"Third-party adapter (minzique/dotfiles-agents) letting pi authenticate with Claude using Claude Code OAuth credentials. Installed from npm, pinned to 0.2.2."},"capture-plugin":{name:"capture",shortDescription:"crtr plugin mounting `crtr capture` \u2014 browser automation and UI measurement over CDP.",longDescription:"Installs the `capture` plugin from the official crtr marketplace. It mounts `crtr capture` as a transparent passthrough to the external Capture CLI (`npm install -g @crouton-kit/capture`) for CDP screenshots, rendered-geometry measurement, HAR, accessibility trees, and JS execution against a live page. Uncheck it on a machine where agents never drive a browser \u2014 the command then costs nothing in their root prompt."},"exa-search-plugin":{name:"exa-search",shortDescription:"crtr plugin mounting `crtr exa-search` \u2014 public-web discovery, grounded answers, and content extraction.",longDescription:"Installs the `exa-search` plugin from the official crtr marketplace. It mounts `crtr exa-search` and calls Exa directly with EXA_API_KEY, collected during install or set through `crtr sys env set --name EXA_API_KEY` in the app env store. Uncheck it on a machine whose agents get web search from somewhere else, such as a crouter cloud home where the platform serves `crtr search` from its own plugin."},"dev-plugin":{name:"dev",shortDescription:"crtr plugin mounting the development workflow \u2014 repository setup, worktrees, pull requests, and a dev track in the tutorial.",longDescription:"Installs the dev plugin from the official crtr marketplace. It adds the /dev: workflow commands (init, spec, design, plan, pr-loop), mounts crtr dev for a repository's own lifecycle commands and Grove instances, and contributes the crtr sys tutorial dev track. Uncheck it on a machine whose agents do not work in source repositories."}},R={"official-marketplace":{kind:"marketplace-source",source:A},"crtr-extensions":{kind:"local-path",source:U("pi-crtr-extensions")},"oauth-adapter":{kind:"npm",source:"npm:pi-claude-oauth-adapter@0.2.2"},"capture-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/capture"},"exa-search-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/exa-search"},"dev-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/dev"}},B=["extensions/*.ts","extensions/*/index.ts","!extensions/**/*.d.ts"],je={"official-marketplace":[],"crtr-extensions":["pi-crtr-extensions"],"oauth-adapter":[],"capture-plugin":[],"exa-search-plugin":[],"dev-plugin":[]};function U(e){return m(ge.runtimeHome(),"selected","dist","builtin-pi-packages",e)}a(U,"selectedPiPackageDir");function D(e=U){return["official-marketplace","crtr-extensions","oauth-adapter","capture-plugin","exa-search-plugin","dev-plugin"].map(t=>{const n=Ke[t],r=R[t].kind==="local-path"?e(n.name):R[t].source;return{id:t,name:n.name,sourceKind:R[t].kind,installSource:r,shortDescription:n.shortDescription,longDescription:n.longDescription}})}a(D,"buildSetupManifest");function St(e){switch(e.sourceKind){case"marketplace-source":return`clone + register ${e.installSource}`;case"marketplace-plugin":return`crtr pkg plugin install ${e.installSource}`;default:return`pi install ${e.installSource}`}}a(St,"packagePlanAction");function Fe(e){if(!e.startsWith("npm:"))return null;const t=e.slice(4),n=t.startsWith("@")?t.indexOf("@",1):t.lastIndexOf("@");return n>0?t.slice(0,n):t}a(Fe,"npmPackageName");function b(e,t,n){const r=Fe(e);if(r!==null)return`npm:${r}`;const o=_e(e,n);return de(t,o)}a(b,"normalizePackageIdentity");function y(e){return`package:${e}`}a(y,"packageNameIdentity");function Y(e){return e.startsWith("npm:")?[]:[y(le(e))]}a(Y,"localPackageNameIdentities");function Ge(e,t,n){const r=[b(e.installSource,t,n)];if(e.sourceKind==="local-path"){r.push(y(e.name));for(const o of je[e.id])r.push(y(o))}return r}a(Ge,"manifestIdentities");function z(e,t=D(),n=m(P(),"settings.json"),r=w()){const o=_(n),l=new Set,c=new Map;for(const s of t)for(const d of Ge(s,o,r))c.set(d,s.id);const u=e&&typeof e=="object"?e.packages:void 0;if(!Array.isArray(u))return l;for(const s of u){const d=typeof s=="string"?s:s&&typeof s=="object"&&typeof s.source=="string"?s.source:null;if(d===null)continue;const h=[b(d,o,r),...Y(d)];for(const f of h){const i=c.get(f);if(i==="crtr-extensions"){const p=t.find(v=>v.id===i);if(b(d,o,r)!==b(p.installSource,o,r)||!s||typeof s!="object"||!x(s.extensions,B))continue}i!==void 0&&l.add(i)}}return l}a(z,"detectInstalledSetupPackageIds");function k(e){return se("sh",["-lc",`command -v ${e} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}a(k,"detectCommandOnPath");function Be(e=process.platform,t=k){switch(e){case"darwin":return t("brew")?"brew":"manual";case"linux":return t("apt-get")?"apt-get":t("dnf")?"dnf":t("pacman")?"pacman":"manual";default:return"manual"}}a(Be,"detectPackageManager");function Ue(e,t,n=k){if(e==="nerd-font"?Ie().installed:n(e))return{tool:e,installed:!0,command:null,hint:"already installed",autoInstall:!1};if(e==="nerd-font"){const l=Pe(t);return{tool:e,installed:!1,command:l.command,hint:`${l.hint} \u2014 ${Ee}`,autoInstall:l.autoInstall}}const o=e==="nvim"?"neovim":"tmux";switch(t){case"brew":return{tool:e,installed:!1,command:`brew install ${o}`,hint:"Homebrew install",autoInstall:!0};case"apt-get":return{tool:e,installed:!1,command:`sudo apt-get update && sudo apt-get install -y ${o}`,hint:"apt-get install",autoInstall:!0};case"dnf":return{tool:e,installed:!1,command:`sudo dnf install -y ${o}`,hint:"dnf install",autoInstall:!0};case"pacman":return{tool:e,installed:!1,command:`sudo pacman -S --noconfirm ${o}`,hint:"pacman install",autoInstall:!0};default:return{tool:e,installed:!1,command:null,hint:`manual install required for ${e}`,autoInstall:!1}}}a(Ue,"buildSystemInstallPlan");function M(e=w()){return m(e===w()?P():m(e,".pi","agent"),"settings.json")}a(M,"resolveSettingsPath");function Ct(e={}){const t=e.homeDir??w(),n=e.settingsPath??M(t),r=e.manifest??D(),o=ve(n),l=z(o,r,n,t);for(const d of V(r))l.add(d);const c=e.commandExists??k,u=e.packageManager??Be(process.platform,c),s=["tmux","nvim","nerd-font"].map(d=>Ue(d,u,c));return{manifest:r,installedPackageIds:l,systemPlans:s}}a(Ct,"buildSetupInitialState");function Et(e){const t=e.manifest.map(r=>{const o=e.installedPackageIds.has(r.id)?"installed":"not installed";return`- ${r.name} \u2014 ${r.shortDescription} (${o})`}),n=e.systemPlans.map(r=>r.installed?`- ${r.tool}: \u2713 installed`:r.command?`- ${r.tool}: ${r.command}`:`- ${r.tool}: ${r.hint}`);return["crtr sys setup (non-interactive)","","Provider login: open the wizard in a TTY and use the Providers tab (same flow as /login) to authenticate a model provider.","","Packages:",...t,"","System deps:",...n,"",`Inbox: the inbox-toggle key (${me()??"disabled"}) is a crouter keybinding \u2014 view or rebind it in \`crtr sys settings\`; it is installed on the same tmux server as every other crouter binding.`,"Run `crtr sys setup` in a TTY to open the wizard."].join(`
|
|
1
|
+
var re=Object.defineProperty;var a=(e,t)=>re(e,"name",{value:t,configurable:!0});import{spawn as ie,spawnSync as se}from"node:child_process";import{homedir as w,tmpdir as ae}from"node:os";import{mkdtempSync as oe,rmSync as ce}from"node:fs";import{piAgentDir as P}from"../../core/runtime/package-health.js";import{basename as le,dirname as _,join as m,relative as ue,resolve as de}from"node:path";import{isDeepStrictEqual as x}from"node:util";import{BINDING_CATALOG as pe,normalizeGesture as fe,splitGestureExpression as he,inboxShortcut as me,resolveKeybindings as S}from"../../core/keybindings/index.js";import{runtimeManifest as ge}from"./shared.js";import{atomicWriteJson as ke,readJsonIfExists as ve,readJsonOrNull as L}from"../../core/fs-utils.js";import{resolveBundledPiCliPath as xe}from"../../core/runtime/pi-cli.js";import{crtrVersion as we}from"../../shared/crtr-version.js";import{installTmuxBindings as be}from"../../core/runtime/tmux-chrome.js";import{normalizeTmuxPassthrough as ye}from"../../core/config.js";import{detectNerdFont as Ie,nerdFontInstallCommand as Pe,createCurlPercentReader as Se,NERD_FONT_MANUAL_LINUX_COMMAND as Ce,NERD_FONT_SELECT_HINT as Ee}from"../../core/runtime/nerd-font.js";import{startProgressBar as Ae}from"../../core/runtime/progress-bar.js";import{ensureRenderer as $e,isRendererReady as K}from"../../core/termrender/termrender.js";import{installFromMarketplace as Re,isMarketplaceRelativeSource as De}from"../pkg/plugin-manage.js";import{requiredPluginEnv as Me}from"../../core/plugin-env.js";import{readPluginManifest as Ne}from"../../core/manifest.js";import{addMarketplace as j}from"../pkg/market-manage.js";import{clone as Te}from"../../core/git.js";import{readMarketplaceManifest as Oe}from"../../core/manifest.js";import{findPluginByName as F,findMarketplaceByName as C}from"../../core/resolver.js";import{expandTilde as _e}from"../../core/fs-utils.js";import{applyTerminalEdit as Le}from"./terminal-option.js";const E="crouter-official-marketplace",A="https://github.com/crouton-labs/crouter-official-marketplace.git",$="main";function It(e){const t=[],n=new Set,r=new Map;try{for(const o of e){if(o.sourceKind!=="marketplace-plugin")continue;const[l,c]=o.installSource.split("/");if(!l||!c)continue;const u=F(c,"user")?.manifest;let d=C(l,"user");if(!d&&!u&&l===E)try{let i=r.get(l);i||(i=oe(m(ae(),"crtr-setup-market-")),r.set(l,i),Te(A,m(i,"checkout"),{ref:$,depth:1}));const p=m(i,"checkout"),v=Oe(p);v&&(d={root:p,manifest:v})}catch{}const h=d?.manifest.plugins.find(i=>i.name===c)?.source,f=h&&De(h)?Ne(m(d.root,h))??u:u;if(f)for(const i of Me(f.env))n.has(i)||(n.add(i),t.push({name:i,plugin:c,declaration:f.env[i]}))}}finally{for(const o of r.values())ce(o,{recursive:!0,force:!0})}return t}a(It,"setupPluginEnvFields");function G(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}a(G,"isKeybindingsObject");function g(e){return G(e)?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,Array.isArray(n)?[...n]:n])):{}}a(g,"cloneKeybindings");class Pt{static{a(this,"SetupKeybindingsDraft")}expected;baseline;current;baselineInvalidRoot;invalidRoot;resolutionCache;expectedPassthrough;baselinePassthrough;currentPassthrough;constructor(t,n=void 0){this.expected=t,this.baseline=g(t),this.current=g(t),this.baselineInvalidRoot=t!==void 0&&!G(t)?t:void 0,this.invalidRoot=this.baselineInvalidRoot,this.expectedPassthrough=n,this.baselinePassthrough=ye(n),this.currentPassthrough=[...this.baselinePassthrough]}get dirty(){return!x(this.current,this.baseline)||!x(this.invalidRoot,this.baselineInvalidRoot)||!x(this.currentPassthrough,this.baselinePassthrough)}overrides(){return g(this.current)}expectedKeybindings(){return this.expected}tmuxPassthrough(){return[...this.currentPassthrough]}expectedTmuxPassthrough(){return this.expectedPassthrough}usesTmuxPassthrough(t){return this.currentPassthrough.includes(t)}toggleTmuxPassthrough(t){this.currentPassthrough=this.currentPassthrough.includes(t)?this.currentPassthrough.filter(n=>n!==t):[...this.currentPassthrough,t]}resolution(){return this.resolutionCache===void 0&&(this.resolutionCache=S(this.invalidRoot===void 0?this.current:this.invalidRoot)),this.resolutionCache}hasInvalidRoot(){return this.invalidRoot!==void 0}expression(t){return this.resolution().gestures(t).join(", ")}edit(t,n){if(this.invalidRoot!==void 0)return{ok:!1,diagnostics:[{severity:"error",code:"invalid-keybindings",offendingValue:this.invalidRoot,message:"Reset all before editing: the saved keybindings root is invalid."}]};const r=pe.find(f=>f.id===t);let o;try{o=n.trim()===""?[]:he(n).map(f=>fe(f.trim(),r))}catch(f){return{ok:!1,diagnostics:[{severity:"error",code:"invalid-override",bindingId:t,offendingValue:n,message:f instanceof Error?f.message:String(f)}]}}const l=[...new Set(o)],c={...this.current};delete c[t];const u=[...S(c).gestures(t)],s=x(l,u)?c:{...this.current,[t]:l},d=S(s),h=d.diagnostics.filter(f=>f.bindingId===t||f.relatedBindingIds?.includes(t));return h.length>0?{ok:!1,diagnostics:h}:(this.current=s,this.resolutionCache=void 0,{ok:!0,diagnostics:d.diagnostics})}unbind(t){return this.edit(t,"")}resetRow(t){delete this.current[t],this.resolutionCache=void 0}resetAll(){this.current={},this.currentPassthrough=[],this.invalidRoot=void 0,this.resolutionCache=void 0}discard(){this.current=g(this.baseline),this.currentPassthrough=[...this.baselinePassthrough],this.invalidRoot=this.baselineInvalidRoot,this.resolutionCache=void 0}markSaved(t,n=[]){this.expected=Object.keys(t).length===0?void 0:g(t),this.baseline=g(t),this.current=g(t),this.expectedPassthrough=n.length===0?void 0:[...n],this.baselinePassthrough=[...n],this.currentPassthrough=[...n],this.baselineInvalidRoot=void 0,this.invalidRoot=void 0,this.resolutionCache=void 0}}const Ke={"official-marketplace":{name:E,shortDescription:"the official crtr plugin catalog \u2014 registered as a source so its plugins are installable by ref.",longDescription:"Shallow-clones github.com/crouton-labs/crouter-official-marketplace into the user scope and registers it, which is what makes `crtr pkg plugin install crouter-official-marketplace/<name>` resolve and what fills `crtr pkg market browse`. Registering the source installs no plugin by itself. Uncheck it on a machine that should reach no external catalog \u2014 a plugin selected below still registers the marketplace it comes from, because its ref cannot resolve otherwise."},"crtr-extensions":{name:"pi-crtr-extensions",shortDescription:"crtr companion extensions \u2014 command palette, help gating, subscription/OAuth rotation, nested context.",longDescription:"Bundles the extensions built for crtr: a crtr slash-command palette, crtr/crouter help gating, surfacing of Claude Code marketplace plugin commands, nested AGENTS.md/CLAUDE.md loading, OAuth login + rotation across multiple Claude subscriptions (provider-rotation), and system-prompt trimming. The OAuth/rotation piece is what lets crtr authenticate your Claude account."},"oauth-adapter":{name:"pi-claude-oauth-adapter",shortDescription:"Anthropic OAuth / Claude Code credential compatibility for pi.",longDescription:"Third-party adapter (minzique/dotfiles-agents) letting pi authenticate with Claude using Claude Code OAuth credentials. Installed from npm, pinned to 0.2.2."},"capture-plugin":{name:"capture",shortDescription:"crtr plugin mounting `crtr capture` \u2014 browser automation and UI measurement over CDP.",longDescription:"Installs the `capture` plugin from the official crtr marketplace. It mounts `crtr capture` as a transparent passthrough to the external Capture CLI (`npm install -g @crouton-kit/capture`) for CDP screenshots, rendered-geometry measurement, HAR, accessibility trees, and JS execution against a live page. Uncheck it on a machine where agents never drive a browser \u2014 the command then costs nothing in their root prompt."},"exa-search-plugin":{name:"exa-search",shortDescription:"crtr plugin mounting `crtr exa-search` \u2014 public-web discovery, grounded answers, and content extraction.",longDescription:"Installs the `exa-search` plugin from the official crtr marketplace. It mounts `crtr exa-search` and calls Exa directly with EXA_API_KEY, collected during install or set through `crtr sys env set --name EXA_API_KEY` in the app env store. Uncheck it on a machine whose agents get web search from somewhere else, such as a Northlight home where the platform serves `crtr search` from its own plugin."},"dev-plugin":{name:"dev",shortDescription:"crtr plugin mounting the development workflow \u2014 repository setup, worktrees, pull requests, and a dev track in the tutorial.",longDescription:"Installs the dev plugin from the official crtr marketplace. It adds the /dev: workflow commands (init, spec, design, plan, pr-loop), mounts crtr dev for a repository's own lifecycle commands and Grove instances, and contributes the crtr sys tutorial dev track. Uncheck it on a machine whose agents do not work in source repositories."}},R={"official-marketplace":{kind:"marketplace-source",source:A},"crtr-extensions":{kind:"local-path",source:U("pi-crtr-extensions")},"oauth-adapter":{kind:"npm",source:"npm:pi-claude-oauth-adapter@0.2.2"},"capture-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/capture"},"exa-search-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/exa-search"},"dev-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/dev"}},B=["extensions/*.ts","extensions/*/index.ts","!extensions/**/*.d.ts"],je={"official-marketplace":[],"crtr-extensions":["pi-crtr-extensions"],"oauth-adapter":[],"capture-plugin":[],"exa-search-plugin":[],"dev-plugin":[]};function U(e){return m(ge.runtimeHome(),"selected","dist","builtin-pi-packages",e)}a(U,"selectedPiPackageDir");function D(e=U){return["official-marketplace","crtr-extensions","oauth-adapter","capture-plugin","exa-search-plugin","dev-plugin"].map(t=>{const n=Ke[t],r=R[t].kind==="local-path"?e(n.name):R[t].source;return{id:t,name:n.name,sourceKind:R[t].kind,installSource:r,shortDescription:n.shortDescription,longDescription:n.longDescription}})}a(D,"buildSetupManifest");function St(e){switch(e.sourceKind){case"marketplace-source":return`clone + register ${e.installSource}`;case"marketplace-plugin":return`crtr pkg plugin install ${e.installSource}`;default:return`pi install ${e.installSource}`}}a(St,"packagePlanAction");function Fe(e){if(!e.startsWith("npm:"))return null;const t=e.slice(4),n=t.startsWith("@")?t.indexOf("@",1):t.lastIndexOf("@");return n>0?t.slice(0,n):t}a(Fe,"npmPackageName");function b(e,t,n){const r=Fe(e);if(r!==null)return`npm:${r}`;const o=_e(e,n);return de(t,o)}a(b,"normalizePackageIdentity");function y(e){return`package:${e}`}a(y,"packageNameIdentity");function Y(e){return e.startsWith("npm:")?[]:[y(le(e))]}a(Y,"localPackageNameIdentities");function Ge(e,t,n){const r=[b(e.installSource,t,n)];if(e.sourceKind==="local-path"){r.push(y(e.name));for(const o of je[e.id])r.push(y(o))}return r}a(Ge,"manifestIdentities");function z(e,t=D(),n=m(P(),"settings.json"),r=w()){const o=_(n),l=new Set,c=new Map;for(const s of t)for(const d of Ge(s,o,r))c.set(d,s.id);const u=e&&typeof e=="object"?e.packages:void 0;if(!Array.isArray(u))return l;for(const s of u){const d=typeof s=="string"?s:s&&typeof s=="object"&&typeof s.source=="string"?s.source:null;if(d===null)continue;const h=[b(d,o,r),...Y(d)];for(const f of h){const i=c.get(f);if(i==="crtr-extensions"){const p=t.find(v=>v.id===i);if(b(d,o,r)!==b(p.installSource,o,r)||!s||typeof s!="object"||!x(s.extensions,B))continue}i!==void 0&&l.add(i)}}return l}a(z,"detectInstalledSetupPackageIds");function k(e){return se("sh",["-lc",`command -v ${e} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}a(k,"detectCommandOnPath");function Be(e=process.platform,t=k){switch(e){case"darwin":return t("brew")?"brew":"manual";case"linux":return t("apt-get")?"apt-get":t("dnf")?"dnf":t("pacman")?"pacman":"manual";default:return"manual"}}a(Be,"detectPackageManager");function Ue(e,t,n=k){if(e==="nerd-font"?Ie().installed:n(e))return{tool:e,installed:!0,command:null,hint:"already installed",autoInstall:!1};if(e==="nerd-font"){const l=Pe(t);return{tool:e,installed:!1,command:l.command,hint:`${l.hint} \u2014 ${Ee}`,autoInstall:l.autoInstall}}const o=e==="nvim"?"neovim":"tmux";switch(t){case"brew":return{tool:e,installed:!1,command:`brew install ${o}`,hint:"Homebrew install",autoInstall:!0};case"apt-get":return{tool:e,installed:!1,command:`sudo apt-get update && sudo apt-get install -y ${o}`,hint:"apt-get install",autoInstall:!0};case"dnf":return{tool:e,installed:!1,command:`sudo dnf install -y ${o}`,hint:"dnf install",autoInstall:!0};case"pacman":return{tool:e,installed:!1,command:`sudo pacman -S --noconfirm ${o}`,hint:"pacman install",autoInstall:!0};default:return{tool:e,installed:!1,command:null,hint:`manual install required for ${e}`,autoInstall:!1}}}a(Ue,"buildSystemInstallPlan");function M(e=w()){return m(e===w()?P():m(e,".pi","agent"),"settings.json")}a(M,"resolveSettingsPath");function Ct(e={}){const t=e.homeDir??w(),n=e.settingsPath??M(t),r=e.manifest??D(),o=ve(n),l=z(o,r,n,t);for(const d of V(r))l.add(d);const c=e.commandExists??k,u=e.packageManager??Be(process.platform,c),s=["tmux","nvim","nerd-font"].map(d=>Ue(d,u,c));return{manifest:r,installedPackageIds:l,systemPlans:s}}a(Ct,"buildSetupInitialState");function Et(e){const t=e.manifest.map(r=>{const o=e.installedPackageIds.has(r.id)?"installed":"not installed";return`- ${r.name} \u2014 ${r.shortDescription} (${o})`}),n=e.systemPlans.map(r=>r.installed?`- ${r.tool}: \u2713 installed`:r.command?`- ${r.tool}: ${r.command}`:`- ${r.tool}: ${r.hint}`);return["crtr sys setup (non-interactive)","","Provider login: open the wizard in a TTY and use the Providers tab (same flow as /login) to authenticate a model provider.","","Packages:",...t,"","System deps:",...n,"",`Inbox: the inbox-toggle key (${me()??"disabled"}) is a crouter keybinding \u2014 view or rebind it in \`crtr sys settings\`; it is installed on the same tmux server as every other crouter binding.`,"Run `crtr sys setup` in a TTY to open the wizard."].join(`
|
|
2
2
|
`)+`
|
|
3
|
-
`}a(Et,"renderSetupStaticInstructions");class I extends Error{static{a(this,"SetupCommandError")}output;constructor(t,n){super(t),this.output=n}}function At(e){return`${e.stdout?.toString()??""}${e.stderr?.toString()??""}`}a(At,"commandOutput");function q(e,t,n,r=process.env){return new Promise((o,l)=>{const c=ie(e,t,{stdio:["ignore","pipe","pipe"],env:r});let u="";c.stdout.setEncoding("utf8"),c.stderr.setEncoding("utf8"),c.stdout.on("data",s=>{u+=s}),c.stderr.on("data",s=>{u+=s,n?.(s)}),c.on("error",s=>l(new I(`${e} failed: ${s.message}`,u))),c.on("close",s=>{s===0?o():l(new I(`${e} failed with status ${s??"unknown"}`,u))})})}a(q,"runCapturedCommand");function
|
|
3
|
+
`}a(Et,"renderSetupStaticInstructions");class I extends Error{static{a(this,"SetupCommandError")}output;constructor(t,n){super(t),this.output=n}}function At(e){return`${e.stdout?.toString()??""}${e.stderr?.toString()??""}`}a(At,"commandOutput");function q(e,t,n,r=process.env){return new Promise((o,l)=>{const c=ie(e,t,{stdio:["ignore","pipe","pipe"],env:r});let u="";c.stdout.setEncoding("utf8"),c.stderr.setEncoding("utf8"),c.stdout.on("data",s=>{u+=s}),c.stderr.on("data",s=>{u+=s,n?.(s)}),c.on("error",s=>l(new I(`${e} failed: ${s.message}`,u))),c.on("close",s=>{s===0?o():l(new I(`${e} failed with status ${s??"unknown"}`,u))})})}a(q,"runCapturedCommand");function N(e,t){return q("sh",["-lc",e],t)}a(N,"runShellCommand");function Ye(e,t=xe()){return q(process.execPath,[t,"install",e],void 0,{...process.env,PI_CODING_AGENT_DIR:P()})}a(Ye,"installPiPackage");async function W(){k("capture")||await N("npm install -g @crouton-kit/capture")}a(W,"installCaptureCliIfMissing");function H(e){return e instanceof I?e.output:void 0}a(H,"capturedFailureOutput");function ze(e){const n=H(e)?.split(/\r?\n/).find(r=>r.trim()!=="");return n!==void 0?n.trim():e instanceof Error?e.message:String(e)}a(ze,"firstFailureLine");function qe(e){const t=H(e);t===void 0||t.length===0||process.stderr.write(t.endsWith(`
|
|
4
4
|
`)?t:`${t}
|
|
5
5
|
`)}a(qe,"printCapturedFailure");class We{static{a(this,"SetupProgress")}interactive;bar=null;constructor(t=!!process.stdout.isTTY){this.interactive=t}start(t){return this.interactive?(this.bar=Ae(`Installing ${t}\u2026`),this.bar):null}async clear(){await this.bar?.stop(),this.bar=null}async succeed(t){await this.clear(),this.interactive&&process.stdout.write(`\r\x1B[2K\u2713 ${t}
|
|
6
6
|
`)}async fail(t,n){await this.clear(),this.interactive&&process.stdout.write(`\r\x1B[2K\u2717 ${t} \u2014 ${ze(n)}
|
|
7
7
|
`),qe(n)}async run(t,n){const r=this.start(t);try{const o=await n(r);return await this.succeed(t),o}catch(o){throw await this.fail(t,o),o}}}function X(e,t){return K()?"current":(e?.(),$e(t),K()?"provisioned":"unavailable")}a(X,"ensureManagedRenderer");async function He(){try{be()}catch{}}a(He,"reconcileInboxFrontDoor");function J(e,t){return t.length===0?[`${e}: none`]:[`${e}:`,...t.map(n=>` - ${n}`)]}a(J,"summarySection");function $t(e){return["crtr sys setup complete",...J("installed",e.installed),...J("skipped",e.skipped),"New to crouter? Ask for the tutorial in your first conversation, or run /tutorial any time."].join(`
|
|
8
8
|
`)+`
|
|
9
|
-
`}a($t,"formatSetupExecutionSummary");function V(e,t){const n=[];for(const r of e)try{r.sourceKind==="marketplace-source"?C(r.name,t)!==null&&n.push(r.id):r.sourceKind==="marketplace-plugin"&&F(r.name,t)!==null&&n.push(r.id)}catch{}return n}a(V,"detectInstalledMarketplaceEntries");function Xe(e,t="user"){if(C(e,t)!==null)return null;if(e!==E)throw new Error(`marketplace not registered: ${e}`);return j({url:A,ref:$,scope:t}),e}a(Xe,"ensureMarketplaceRegistered");function Je(e){const t=M(),n=L(t);if(!n||typeof n!="object"||Array.isArray(n))throw new Error(`invalid pi settings: ${t}`);const r=n.packages;if(!Array.isArray(r))throw new Error(`invalid pi packages: ${t}`);const o=a(u=>{const s=typeof u=="string"?u:u&&typeof u=="object"?u.source:void 0;return typeof s=="string"&&Y(s).includes(y("pi-crtr-extensions"))},"isCrtrExtensions"),l=r.findIndex(o),c=r.filter(u=>!o(u));c.splice(l<0?c.length:l,0,{source:ue(_(t),e),extensions:B}),ke(t,{...n,packages:c})}a(Je,"configureCrtrExtensions");const Q="git is not installed \u2014 install it (Debian/Ubuntu: apt-get install git), then run `crtr sys setup`";function Z(e){return e.sourceKind==="marketplace-source"||e.sourceKind==="marketplace-plugin"}a(Z,"isMarketplaceEntry");async function ee(e){if(e.sourceKind==="marketplace-source")return j({url:e.installSource,ref:$,scope:"user"}),[e.name];if(e.sourceKind==="marketplace-plugin"){const[t,n]=e.installSource.split("/"),r=Xe(t);return await Re({marketplace:t,plugin:n},"user"),e.id==="capture-plugin"&&await W(),r===null?[e.name]:[r,e.name]}return await Ye(e.installSource),e.id==="crtr-extensions"&&Je(e.installSource),[e.name]}a(ee,"installSetupPackage");async function Rt(e,t=k){const n=D(),r=z(L(M()),n);for(const s of V(n,"user"))r.add(s);const o=new Map(n.map(s=>[s.id,s])),l=process.stdin.isTTY&&process.stdout.isTTY?["crtr-extensions","official-marketplace","oauth-adapter","capture-plugin","exa-search-plugin"]:["crtr-extensions","official-marketplace","capture-plugin","exa-search-plugin"];let c,u=!1;for(const s of l){const d=o.get(s);if(!r.has(s)){if(Z(d)&&(c??=!t("git"),c)){u||e("marketplace packages",new Error(Q)),u=!0;continue}try{await ee(d)}catch(h){e(d.name,h)}}}try{X(void 0,()=>{})==="unavailable"&&e("termrender renderer",new Error("unavailable \u2014 install uv"))}catch(s){e("termrender renderer",s)}return we()}a(Rt,"reconcileFirstRun");async function Dt(e,t=k){const n=[],r=[],o=[],l=[],c=new We;let u,s=!1;for(const i of e.packages){if(e.installedPackageIds.has(i.id)){if(r.push(`${i.name} (already installed)`),i.id==="capture-plugin"&&!k("capture"))try{await c.run("Capture CLI",()=>W()),n.push("Capture CLI")}catch(p){r.push(`Capture CLI (${p instanceof Error?p.message:String(p)})`)}continue}if(Z(i)&&(u??=!t("git"),u)){s||r.push(`marketplace packages (${Q})`),s=!0;continue}try{n.push(...await c.run(i.name,()=>ee(i)))}catch(p){if(i.sourceKind!=="marketplace-source"&&i.sourceKind!=="marketplace-plugin")throw p;r.push(`${i.name} (${p instanceof Error?p.message:String(p)})`)}}for(const i of e.systemDeps){if(i.installed){l.push(`${i.tool} (already installed)`);continue}if(!i.autoInstall||!i.command){l.push(`${i.tool} (${i.hint})`);continue}const p=i.command,v=p===Ce;await c.run(i.tool,
|
|
9
|
+
`}a($t,"formatSetupExecutionSummary");function V(e,t){const n=[];for(const r of e)try{r.sourceKind==="marketplace-source"?C(r.name,t)!==null&&n.push(r.id):r.sourceKind==="marketplace-plugin"&&F(r.name,t)!==null&&n.push(r.id)}catch{}return n}a(V,"detectInstalledMarketplaceEntries");function Xe(e,t="user"){if(C(e,t)!==null)return null;if(e!==E)throw new Error(`marketplace not registered: ${e}`);return j({url:A,ref:$,scope:t}),e}a(Xe,"ensureMarketplaceRegistered");function Je(e){const t=M(),n=L(t);if(!n||typeof n!="object"||Array.isArray(n))throw new Error(`invalid pi settings: ${t}`);const r=n.packages;if(!Array.isArray(r))throw new Error(`invalid pi packages: ${t}`);const o=a(u=>{const s=typeof u=="string"?u:u&&typeof u=="object"?u.source:void 0;return typeof s=="string"&&Y(s).includes(y("pi-crtr-extensions"))},"isCrtrExtensions"),l=r.findIndex(o),c=r.filter(u=>!o(u));c.splice(l<0?c.length:l,0,{source:ue(_(t),e),extensions:B}),ke(t,{...n,packages:c})}a(Je,"configureCrtrExtensions");const Q="git is not installed \u2014 install it (Debian/Ubuntu: apt-get install git), then run `crtr sys setup`";function Z(e){return e.sourceKind==="marketplace-source"||e.sourceKind==="marketplace-plugin"}a(Z,"isMarketplaceEntry");async function ee(e){if(e.sourceKind==="marketplace-source")return j({url:e.installSource,ref:$,scope:"user"}),[e.name];if(e.sourceKind==="marketplace-plugin"){const[t,n]=e.installSource.split("/"),r=Xe(t);return await Re({marketplace:t,plugin:n},"user"),e.id==="capture-plugin"&&await W(),r===null?[e.name]:[r,e.name]}return await Ye(e.installSource),e.id==="crtr-extensions"&&Je(e.installSource),[e.name]}a(ee,"installSetupPackage");async function Rt(e,t=k){const n=D(),r=z(L(M()),n);for(const s of V(n,"user"))r.add(s);const o=new Map(n.map(s=>[s.id,s])),l=process.stdin.isTTY&&process.stdout.isTTY?["crtr-extensions","official-marketplace","oauth-adapter","capture-plugin","exa-search-plugin"]:["crtr-extensions","official-marketplace","capture-plugin","exa-search-plugin"];let c,u=!1;for(const s of l){const d=o.get(s);if(!r.has(s)){if(Z(d)&&(c??=!t("git"),c)){u||e("marketplace packages",new Error(Q)),u=!0;continue}try{await ee(d)}catch(h){e(d.name,h)}}}try{X(void 0,()=>{})==="unavailable"&&e("termrender renderer",new Error("unavailable \u2014 install uv"))}catch(s){e("termrender renderer",s)}return we()}a(Rt,"reconcileFirstRun");async function Dt(e,t=k){const n=[],r=[],o=[],l=[],c=new We;let u,s=!1;for(const i of e.packages){if(e.installedPackageIds.has(i.id)){if(r.push(`${i.name} (already installed)`),i.id==="capture-plugin"&&!k("capture"))try{await c.run("Capture CLI",()=>W()),n.push("Capture CLI")}catch(p){r.push(`Capture CLI (${p instanceof Error?p.message:String(p)})`)}continue}if(Z(i)&&(u??=!t("git"),u)){s||r.push(`marketplace packages (${Q})`),s=!0;continue}try{n.push(...await c.run(i.name,()=>ee(i)))}catch(p){if(i.sourceKind!=="marketplace-source"&&i.sourceKind!=="marketplace-plugin")throw p;r.push(`${i.name} (${p instanceof Error?p.message:String(p)})`)}}for(const i of e.systemDeps){if(i.installed){l.push(`${i.tool} (already installed)`);continue}if(!i.autoInstall||!i.command){l.push(`${i.tool} (${i.hint})`);continue}const p=i.command,v=p===Ce;await c.run(i.tool,T=>{if(!v||T===null)return N(p);const te=Se();return N(p,ne=>{const O=te(ne);O!==null&&T.setFraction(O/100)})}),o.push(i.tool)}const d=[];let h=!1,f;try{f=X(()=>{h=!0,c.start("termrender renderer")},i=>d.push(i))}catch(i){throw h&&await c.fail("termrender renderer",i),i}switch(f){case"provisioned":h&&await c.succeed("termrender renderer"),n.push("termrender renderer");break;case"current":r.push("termrender renderer (current)");break;case"unavailable":{const i=new I("unavailable \u2014 install uv",d.join(""));h&&await c.fail("termrender renderer",i),r.push("termrender renderer (unavailable \u2014 install uv)");break}}if(e.terminalEdit!==void 0){const i=`Option as Alt (${e.terminalEdit.path})`,p=Le(e.terminalEdit);p.ok?n.push(`${i} \u2014 ${e.terminalEdit.reload}`):r.push(`${i} (${p.message})`)}return await He(),{installed:[...n,...o],skipped:[...r,...l]}}a(Dt,"executeSetupSubmission");export{E as OFFICIAL_MARKETPLACE_NAME,$ as OFFICIAL_MARKETPLACE_REF,A as OFFICIAL_MARKETPLACE_URL,Pt as SetupKeybindingsDraft,Ct as buildSetupInitialState,D as buildSetupManifest,Ue as buildSystemInstallPlan,k as detectCommandOnPath,z as detectInstalledSetupPackageIds,Be as detectPackageManager,Dt as executeSetupSubmission,$t as formatSetupExecutionSummary,b as normalizePackageIdentity,St as packagePlanAction,Rt as reconcileFirstRun,He as reconcileInboxFrontDoor,Et as renderSetupStaticInstructions,M as resolveSettingsPath,It as setupPluginEnvFields};
|
package/dist/core/command.d.ts
CHANGED
|
@@ -180,7 +180,7 @@ export declare function renderNode(node: AnyNode): string;
|
|
|
180
180
|
* the full path to the matched node (not just its local name), so the recovery
|
|
181
181
|
* hint is a command that actually exists. No fuzzy matching. */
|
|
182
182
|
export declare function unknownPathError(node: AnyNode, path: string[], bad: string): CrtrError;
|
|
183
|
-
/**
|
|
183
|
+
/** Northlight's account site: where a person creates a join token. */
|
|
184
184
|
export declare const DEFAULT_DIRECTORY_ISSUER = "https://account.northlight.ai";
|
|
185
185
|
/** An unknown top-level command on a runtime never joined to a directory
|
|
186
186
|
* (no `runtime.issuer`). Every `cloud` leaf and every other provider command
|
package/dist/core/command.js
CHANGED
|
@@ -2,7 +2,7 @@ var M=Object.defineProperty;var d=(e,i)=>M(e,"name",{value:i,configurable:!0});i
|
|
|
2
2
|
|
|
3
3
|
<plugin-help plugin="${S.plugin}">
|
|
4
4
|
${S.text}
|
|
5
|
-
</plugin-help>`).join("")}d(J,"renderNodeWithAddenda");function he(e){return e.some(i=>i==="-h"||i==="--help")}d(he,"helpRequested");function pe(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 o=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===o)?.focusedHelp!==void 0?o:void 0}d(pe,"focusedHelpFlag");function me(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[]}d(me,"rawArgsAfterPath");function ge(e,i){const r=oe(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})`,$.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=ae.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}d(ge,"runPassthrough");function we(e,i,r){const n=fe(e),o=n.length>0?n.join(", "):"(none)",f=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(a=>a.startsWith("(and "));return new H("unknown_path",`unknown subcommand: ${r}`,$.USAGE,{received:r,next:s?`Valid children include: ${o}. Run \`${f}\` for how to find the rest.`:`Valid children: ${o}. Run \`${f}\` for the full list.`})}d(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 H("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from
|
|
5
|
+
</plugin-help>`).join("")}d(J,"renderNodeWithAddenda");function he(e){return e.some(i=>i==="-h"||i==="--help")}d(he,"helpRequested");function pe(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 o=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===o)?.focusedHelp!==void 0?o:void 0}d(pe,"focusedHelpFlag");function me(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[]}d(me,"rawArgsAfterPath");function ge(e,i){const r=oe(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})`,$.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=ae.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}d(ge,"runPassthrough");function we(e,i,r){const n=fe(e),o=n.length>0?n.join(", "):"(none)",f=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(a=>a.startsWith("(and "));return new H("unknown_path",`unknown subcommand: ${r}`,$.USAGE,{received:r,next:s?`Valid children include: ${o}. Run \`${f}\` for how to find the rest.`:`Valid children: ${o}. Run \`${f}\` for the full list.`})}d(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 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}.`,$.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}d(ve,"notJoinedError");async function xe(){try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}d(xe,"runtimeUnjoined");function m(e,i,r,n,o){return new H(e,i,$.USAGE,{received:r,field:n,next:o!==void 0?o:"Run the command with -h to see the parameter schema."})}d(m,"parseArgvError");function v(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}d(v,"flagNameToKey");async function ke(e,i,r){const n={},o=new Set,f=new Map,s=e.filter(t=>t.kind==="positional"),a=e.find(t=>t.kind==="stdin"),x=e.find(t=>t.kind==="context-file"),w=e.filter(t=>t.kind==="flag"),S=new Map(w.map(t=>[t.name,t])),y=w.some(t=>t.sensitive===!0);for(const t of w)t.type==="bool"?n[v(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[v(t.name)]=t.default);const g=[];let c=0,k=!1,A=!1,b=0;for(;b<i.length;){const t=i[b];if(t==="--"&&!A){A=!0,b++;continue}if(!A&&t.startsWith("--")){const h=t.indexOf("=");let l,C;if(h!==-1?(l=t.slice(2,h),C=t.slice(h+1)):l=t.slice(2),l==="context-file"){if(x===void 0)throw m("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const p=C!==void 0?C:i[++b];if(p===void 0)throw m("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let R;try{R=re(p,"utf8")}catch{throw m("invalid_type",`--context-file: cannot read file: ${p}`,p,"context-file","Provide a readable file path.")}let F;try{F=JSON.parse(R)}catch{throw m("invalid_type",`--context-file: file is not valid JSON: ${p}`,p,"context-file","Ensure the file contains a valid JSON object.")}const T=v(x.name);n[T]=F,f.set(T,le(p)),o.add(T),b++;continue}const E=S.get(l);if(E===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 U=v(l);if(o.add(U),E.type==="bool"){if(C!==void 0)throw m("invalid_type",`boolean flag --${l} takes no value`,`--${l}=${C}`,l,`Use --${l} (presence = true) with no value.`);n[U]=!0,b++;continue}const P=C!==void 0?C:i[++b];if(P===void 0||C===void 0&&P.startsWith("--")){const p=E.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,p)}let _;if(E.type==="int"){const p=Number(P);if(!Number.isInteger(p))throw m("invalid_type",`--${l} must be an integer`,P,l,`Provide an integer value for --${l}.`);_=p}else if(E.type==="enum"){const p=E.choices;if(p!==void 0&&!p.includes(P))throw m("invalid_type",`--${l} must be one of: ${p.join(", ")}`,P,l,`Retry with one of: ${p.join(", ")}.`);_=P}else _=P;if(E.repeatable===!0){const p=n[U],R=Array.isArray(p)?p:[];R.push(_),n[U]=R}else n[U]=_;b++;continue}const u=s[c];if(u!==void 0){const h=v(u.name);if(u.repeatable===!0){const l=n[h]??[];l.push(t),n[h]=l}else n[h]=t,c++;g.push(t),k=!0,o.add(h),b++;continue}if(g.length>0){if(a!==void 0&&s.length===0){g.push(t),k=!0,b++;continue}throw a!==void 0?m("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${a.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):m("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&&(a===void 0||a.allowPositional===!1))throw m("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),k=!0,b++}let N=!1;if(a!==void 0){if(g.length>1&&s.length===0){const t=a.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 "${a.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&&a.allowPositional!==!1){const t=g[0];if(t==="-"){const u=await B();(u.trim()!==""||!a.required)&&(n[v(a.name)]=u),N=!0}else{if((await D()).trim()!==""){const h=a.positionalStdinConflict?.(n);throw m("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${a.name}`,t,h?.field??a.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[v(a.name)]=t,N=!0}}else{const t=se(0),u=await B();(u.trim()!==""||!a.required)&&(n[v(a.name)]=u),N=!t||k}}N&&a!==void 0&&o.add(v(a.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const u=v(t.name);if(o.has(u))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[u]=t.kind==="positional"&&t.repeatable===!0?[h]:h,o.add(u))}const j=[];for(const t of e){const u=v(t.name),h=t.kind==="flag"&&t.type==="bool"&&!o.has(u);if(t.required&&(h||n[u]===void 0||n[u]===null)){const l=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";j.push({display:l,name:t.name})}}if(j.length>0){const t=j.map(l=>l.display).join(", "),u=r?.leafPath,h=u!==void 0&&u.length>0?`\`crtr ${u.join(" ")} -h\``:"this command with -h";throw m("missing_parameter",j.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,j.map(l=>l.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(o),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(f),n}d(ke,"parseArgv");const ye=new Set(["--json","--no-autostart"]);async function Be(e,i){const r=i.slice(2);r.includes("--json")&&X(!0);const n=r.filter(a=>!ye.has(a));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const a=ie()?{...e.help,commands:await Promise.all(e.help.commands.map(async x=>{const w=e.subtrees.find(c=>c.name===x.name);if(w?.effectiveHelp===void 0)return x;const y=((await w.effectiveHelp()).listing??[]).filter(c=>c.tier!=="hidden"),g=I(y).map(c=>({path:`${w.name} ${c.name}`,desc:c.tier==="important"?c.description:void 0}));return{...x,subcommands:g.length?g:void 0,otherSubcommandCount:y.length-g.length}}))}:e.help;process.stdout.write(V(a)+`
|
|
6
6
|
`),process.exitCode=$.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:a}=await import("../commands/sys/shared.js");process.stdout.write(a()+`
|
|
7
7
|
`),process.exitCode=$.SUCCESS;return}let{node:o,path:f,remaining:s}=ce(e,n);try{if(o.kind==="branch"&&o.passthrough!==void 0&&ge(o.passthrough,me(r,f)),o.kind==="branch"&&o.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new H("provider_unavailable",o.unavailable,$.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((o.kind==="root"||o.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw o.kind==="root"&&!s[0].startsWith("-")&&await xe()?ve(s[0]):we(o,f,s[0]);if(he(s)){process.stdout.write(await J(o,f,pe(o,s))+`
|
|
8
8
|
`),process.exitCode=$.SUCCESS;return}if(o.kind==="root"||o.kind==="branch"){process.stdout.write(await J(o,f)+`
|
|
@@ -10,7 +10,7 @@ export interface StartedFlow {
|
|
|
10
10
|
expires_at: string;
|
|
11
11
|
}
|
|
12
12
|
/** API name for the ChatGPT (`openai`) provider. The bare API name `openai`
|
|
13
|
-
* stays Codex:
|
|
13
|
+
* stays Codex: Northlight's vendor sign-in page (`vendor=openai`) means Codex. */
|
|
14
14
|
export declare const OPENAI_CHATGPT_API_PROVIDER = "openai-chatgpt";
|
|
15
15
|
/** The API name a client sends to start a sign-in for a runtime provider id. */
|
|
16
16
|
export declare function modelAuthApiProvider(provider: ManagedProviderId): 'anthropic' | 'openai-codex' | 'openai-chatgpt';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var V=Object.defineProperty;var c=(e,r)=>V(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as W}from"../../api/command-manifest/result.js";import{callerAllows as S,requireGroup as X}from"../../daemon/api/handlers/providers.js";import{grantFor as Y,terminalGrant as Z}from"../grants/mirror.js";import{assertUnderCap as B,UNCAPPED_GRANTEE as ee}from"../usage/ledger.js";import{listInstalledPlugins as re}from"../resolver.js";import{ProviderError as u}from"./errors.js";import{ensureProviderBundle as oe,refreshProviderBundle as te,resolveProviderBundle as M,unavailableProviderError as ne}from"./bundles.js";import{prepareFileCall as ie,resignForRetry as D,resolveFileResult as se}from"./files.js";import{consumeApproval as ae,requireProviderApproval as de}from"./approval.js";import{outboundToken as pe}from"./mint.js";import{discardFile as ue}from"./token-cache.js";import{registerProviderCall as le}from"./inflight.js";import{buildRequestPlan as ce}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as fe,ProviderTransportError as me}from"./request.js";import{acceptProviderReceipt as ge}from"./receipts.js";import{writeProviderAudit as ve}from"./audit.js";function O(e,r){const n=[],o=c((i,a)=>{const d=[...a,i.name];if(i.kind==="branch"){for(const v of i.children)o(v,d);return}const l=d[0]===r?d.slice(1).join(" "):d.join(" ");n.push({tool:l,leaf:i})},"walk");for(const i of e.roots)o(i,[]);return n}c(O,"providerLeaves");function J(e,r,n){if(e.kind!=="branch")return null;const o=[];for(const h of e.children)if(h.kind==="branch"){const w=J(h,r,n);w&&o.push(w)}else if("group"in h&&h.group&&S(n,`${r}:${h.group}`)){const{kind:w,name:A,description:t,whenToUse:k,tier:m,summary:q,params:_,output:j,effects:$,group:f}=h;o.push({kind:w,name:A,description:t,whenToUse:k,...m===void 0?{}:{tier:m},summary:q,params:_,output:j,effects:$,group:f})}if(o.length===0)return null;const{kind:i,name:a,description:d,whenToUse:l,tier:v,summary:g,model:b,rootEntry:y}=e;return{kind:i,name:a,description:d,whenToUse:l,...v===void 0?{}:{tier:v},summary:g,...b===void 0?{}:{model:b},...y===void 0?{}:{rootEntry:y},children:o}}c(J,"providerHelpBranch");async function Be(e,r){const n=await M(r),o=O(n.commands,r),i=re("user").find(a=>a.name===r)?.manifest.description??r;return{provider:r,status:"available",major:n.major,display_name:i,groups:n.provider.groups.filter(a=>S(e,`${r}:${a.group}`)).map(a=>({group:a.group,leaves:o.filter(({leaf:d})=>d.group===a.group).map(({tool:d,leaf:l})=>({tool:d,description:l.description,summary:l.summary,params:l.params,output:l.output}))})),commands:n.commands.roots.flatMap(a=>{const d=J(a,r,e);return d?[d]:[]})}}c(Be,"providerTools");function he(e){return ge(e.headers["crtr-receipt"],e)}c(he,"onProviderResponse");function G(e){if(!(e.class==="person"||!e.sub||!e.grantee))return{grantee:e.grantee,spend_cap:Y(e.grantee,e.sub)?.spend_cap??null}}c(G,"capTarget");function we(e){if(e.class==="person"){const r=Z()?.sub;if(!r)throw new u("grant_revoked","This machine has no active
|
|
1
|
+
var V=Object.defineProperty;var c=(e,r)=>V(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as W}from"../../api/command-manifest/result.js";import{callerAllows as S,requireGroup as X}from"../../daemon/api/handlers/providers.js";import{grantFor as Y,terminalGrant as Z}from"../grants/mirror.js";import{assertUnderCap as B,UNCAPPED_GRANTEE as ee}from"../usage/ledger.js";import{listInstalledPlugins as re}from"../resolver.js";import{ProviderError as u}from"./errors.js";import{ensureProviderBundle as oe,refreshProviderBundle as te,resolveProviderBundle as M,unavailableProviderError as ne}from"./bundles.js";import{prepareFileCall as ie,resignForRetry as D,resolveFileResult as se}from"./files.js";import{consumeApproval as ae,requireProviderApproval as de}from"./approval.js";import{outboundToken as pe}from"./mint.js";import{discardFile as ue}from"./token-cache.js";import{registerProviderCall as le}from"./inflight.js";import{buildRequestPlan as ce}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as fe,ProviderTransportError as me}from"./request.js";import{acceptProviderReceipt as ge}from"./receipts.js";import{writeProviderAudit as ve}from"./audit.js";function O(e,r){const n=[],o=c((i,a)=>{const d=[...a,i.name];if(i.kind==="branch"){for(const v of i.children)o(v,d);return}const l=d[0]===r?d.slice(1).join(" "):d.join(" ");n.push({tool:l,leaf:i})},"walk");for(const i of e.roots)o(i,[]);return n}c(O,"providerLeaves");function J(e,r,n){if(e.kind!=="branch")return null;const o=[];for(const h of e.children)if(h.kind==="branch"){const w=J(h,r,n);w&&o.push(w)}else if("group"in h&&h.group&&S(n,`${r}:${h.group}`)){const{kind:w,name:A,description:t,whenToUse:k,tier:m,summary:q,params:_,output:j,effects:$,group:f}=h;o.push({kind:w,name:A,description:t,whenToUse:k,...m===void 0?{}:{tier:m},summary:q,params:_,output:j,effects:$,group:f})}if(o.length===0)return null;const{kind:i,name:a,description:d,whenToUse:l,tier:v,summary:g,model:b,rootEntry:y}=e;return{kind:i,name:a,description:d,whenToUse:l,...v===void 0?{}:{tier:v},summary:g,...b===void 0?{}:{model:b},...y===void 0?{}:{rootEntry:y},children:o}}c(J,"providerHelpBranch");async function Be(e,r){const n=await M(r),o=O(n.commands,r),i=re("user").find(a=>a.name===r)?.manifest.description??r;return{provider:r,status:"available",major:n.major,display_name:i,groups:n.provider.groups.filter(a=>S(e,`${r}:${a.group}`)).map(a=>({group:a.group,leaves:o.filter(({leaf:d})=>d.group===a.group).map(({tool:d,leaf:l})=>({tool:d,description:l.description,summary:l.summary,params:l.params,output:l.output}))})),commands:n.commands.roots.flatMap(a=>{const d=J(a,r,e);return d?[d]:[]})}}c(Be,"providerTools");function he(e){return ge(e.headers["crtr-receipt"],e)}c(he,"onProviderResponse");function G(e){if(!(e.class==="person"||!e.sub||!e.grantee))return{grantee:e.grantee,spend_cap:Y(e.grantee,e.sub)?.spend_cap??null}}c(G,"capTarget");function we(e){if(e.class==="person"){const r=Z()?.sub;if(!r)throw new u("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 u("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}c(we,"identity");function _e(e,r){return[`${e.id}:${r.group}`]}c(_e,"callScope");function be(e,r){const n=O(e.commands,e.id).find(o=>o.tool===r);if(!n)throw new u("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new u("not_found",`tool "${r}" has no group`,{param:"tool"});return n.leaf}c(be,"leafFor");function H(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}c(H,"errorBody");function ye(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const n=Number(r);if(Number.isFinite(n)&&n>=0)return n;const o=Date.parse(r);return Number.isFinite(o)?Math.max(0,Math.ceil((o-Date.now())/1e3)):void 0}c(ye,"retryAfter");function L(e,r,n,o=!1){const i=H(r),a=typeof i.code=="string"?i.code:void 0,d=typeof i.message=="string"?i.message:`provider answered HTTP ${e}`,l=typeof i.field=="string"?i.field:void 0,g={origin:"provider",details:{code:a??null,message:d,...l?{field:l}:{},...typeof i.next=="string"?{next:i.next}:{},...i.received!==void 0?{received:i.received}:{},status:e},...l?{param:l}:{}};return e===429?new u("provider_rate_limited",d,{...g,retry_after_s:ye(n["retry-after"])}):e===401||e>=500?new u("provider_unavailable",d,g):e===403&&a==="provider_refused"?new u("provider_refused",d,g):e===403&&a==="approval_required"?new u("provider_error",o?"provider asked for approval again":"provider approval question is invalid",{...g,status:e}):new u("provider_error",d,{...g,status:e})}c(L,"mapProviderResponse");function z(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.endpoint,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}c(z,"approvalShape");async function De(e,r,n){const o=new AbortController,i=c(()=>o.abort(n.signal.reason),"abort");n.signal.aborted?i():n.signal.addEventListener("abort",i,{once:!0});let a=n.requestId;const d=le({request_id:a,run_id:e.run_id,node_id:e.node_id,controller:o});let l,v,g,b,y,h,w,A="allowed";try{try{await oe(r.provider)}catch{throw ne(r.provider)}let t=await M(r.provider,r.version),k=!1,m,q,_,j=!1,$=!1;for(;;){const f=be(t,r.tool);g=`${t.id}:${f.group}`,X(e,t.id,f.group),B(G(e));let F=null;if(r.approval&&!j){const s=ae(e,r,z(t,f));_=s.option,F=s.prepared,w=r.approval,a=s.requestId,j=!0}const R={params:f.params.filter(s=>"type"in s&&s.type==="file").map(s=>s.name),output:f.output.find(s=>s.type==="file")?.name??null},U=JSON.stringify(R);if(q!==void 0&&q!==U)throw new u("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});q=U,F&&(m=await D(e,R,r.arguments,F,{signal:o.signal})),m??=await ie(e,R,r.arguments,{signal:o.signal});const{sub:I,grantee:x}=we(e);B(G(e));const E=await pe({audience:t.transport.endpoint,sub:I,grantee:x,initiating_app:x,scope:_e(t,f)},{signal:o.signal});b=E.jti;const K={registration:{name:t.id,endpoint:t.transport.endpoint},...t.commands.baseUrl?{baseUrl:t.commands.baseUrl}:{},...t.commands.timeouts?{timeouts:t.commands.timeouts}:{},params:f.params,rest:f.rest,output:f.output,commandPath:[t.id,...r.tool.split(" ")]},Q=ce(K,m.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:a,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},..._?{"Crtr-Approval":_}:{},...m.headers}});v??=Date.now()+(t.commands.timeouts?.requestMs??3e4);let p;try{if(p=await fe(Q,{connectMs:t.commands.timeouts?.connectMs,requestMs:t.commands.timeouts?.requestMs,deadlineAt:v,signal:o.signal}),o.signal.aborted)throw o.signal.reason??new Error("provider call aborted")}catch(s){throw o.signal.aborted?o.signal.reason??s:s instanceof me?new u("provider_unavailable",s.message,{origin:"provider",details:{status:null,code:s.kind}}):s}if(y??=he({provider:t.id,tool:r.tool,request_id:a,run_id:e.run_id,sub:I,grantee:x,ledger_grantee:e.class==="person"?ee:x,jti:E.jti,status:p.status,headers:p.headers}),h=p.headers["crtr-responder"],p.status===401&&ue(E.key),p.status>=200&&p.status<300){const s=p.body;if(!s||typeof s!="object"||Array.isArray(s))throw new u("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:p.status,message:"provider result is not a JSON object"}});let T;try{T=await se(e,m,s,{signal:o.signal})}catch(P){throw P instanceof u&&P.code==="provider_error"?new u("provider_bad_response",P.message,{origin:"provider",details:{status:p.status,message:P.message}}):P}l=T.cleanup;const C=W(f.output,T.result);if(C)throw new u("provider_bad_response",`provider result has an invalid output field "${C.field}"`,{origin:"provider",param:C.field,details:{status:p.status,...C}});if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");return{status:200,body:T.result}}const N=H(p.body);if(p.status===403&&N.code==="approval_required"&&!_&&!$){$=!0;const s=await de(e,r,a,N.question,o.signal,z(t,f),m);if(w=s.questionId,_=s.option,m=await D(e,R,r.arguments,m,{signal:o.signal}),v=void 0,o.signal.aborted)throw new u("user_denied","the approval wait was canceled");continue}if(p.status===403&&N.code==="approval_required"&&_)throw L(p.status,p.body,p.headers,!0);if(p.status!==401&&N.manifest_stale===!0&&!k){k=!0,await te(t.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}}),t=await M(t.id,r.version);continue}throw L(p.status,p.body,p.headers,!!_)}}catch(t){throw t instanceof u&&t.question_id&&(w=t.question_id),A=`refused:${t instanceof u?t.code:"server_error"}`,l?.(),t}finally{try{ve({request_id:a,caller:e,provider:r.provider,tool:r.tool,scope_checked:g,outcome:A,jti:b,receipt_id:y,responder:h,question_id:w})}finally{d()}n.signal.removeEventListener("abort",i)}}c(De,"callProvider");export{De as callProvider,L as mapProviderResponse,he as onProviderResponse,O as providerLeaves,Be as providerTools};
|
|
@@ -430,7 +430,7 @@ export interface ResourcesReloadedFrame {
|
|
|
430
430
|
}
|
|
431
431
|
/** The `error` control frame. SOURCED from the shared `@north-light/api`
|
|
432
432
|
* contract (`src/api/dto/broker.ts`, the same zero-runtime-dep `/v1` module
|
|
433
|
-
*
|
|
433
|
+
* Northlight imports) so the wire shape here and the shape Core consumes
|
|
434
434
|
* are one compiler-checked type — no hand-mirror to drift. `{ code, message,
|
|
435
435
|
* id? }`; `id` is the correlation token echoed ONLY on the failure of a
|
|
436
436
|
* correlated request (a read-op or `dequeue`, which carry an `id` and otherwise
|
|
@@ -439,7 +439,7 @@ export interface ResourcesReloadedFrame {
|
|
|
439
439
|
* errors, command-op failures, frame_overflow). There is NO `retryable` field. */
|
|
440
440
|
export type ErrorFrame = BrokerErrorFrame;
|
|
441
441
|
/** The `node_named` control frame. SOURCED from the shared
|
|
442
|
-
* `@north-light/api` contract for the same reason `error` is:
|
|
442
|
+
* `@north-light/api` contract for the same reason `error` is: Northlight
|
|
443
443
|
* Core reads this frame off its own attach, so the wire shape here and the shape
|
|
444
444
|
* it consumes are one compiler-checked type.
|
|
445
445
|
*
|
|
@@ -22,7 +22,7 @@ export declare const REMOTE_ATTACH_HEARTBEAT_MS = 25000;
|
|
|
22
22
|
*
|
|
23
23
|
* ROLLOUT ORDER — this half is only half the answer, and shipping it FIRST is
|
|
24
24
|
* strictly worse than shipping nothing. The host that owns the attach must also
|
|
25
|
-
* let go of a quiet one;
|
|
25
|
+
* let go of a quiet one; Northlight does that at its own 60s quiesce
|
|
26
26
|
* (`CROUTER_FOLLOW_QUIESCE_MS`). Against a host that holds the attach forever,
|
|
27
27
|
* going quiet here only moves the drop to the network path, the host reports a
|
|
28
28
|
* detach, its client reconnects, and the box wakes again — the same pin, now
|
|
@@ -21,7 +21,7 @@ export declare function retentionHold(fleet: Pick<FleetRegistry, 'isClaimed'>):
|
|
|
21
21
|
/** Whether a person is looking at this node right now: a focused viewer pane,
|
|
22
22
|
* or a broker client that declared itself attending. A client that hellos with
|
|
23
23
|
* `attends: false` is a system observer and deliberately does not count, which
|
|
24
|
-
* is how
|
|
24
|
+
* is how Northlight identifies itself — so an open product page is
|
|
25
25
|
* invisible here, while crouter's own local and remote viewers are not. */
|
|
26
26
|
export declare function hasHumanViewer(nodeId: string): boolean;
|
|
27
27
|
/** What parking an unattended node actually amounts to — the one rule both the
|
package/dist/launcher/users.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var h=Object.defineProperty;var i=(n,t)=>h(n,"name",{value:t,configurable:!0});import{readFileSync as a,writeFileSync as l,renameSync as f,openSync as w,closeSync as m,fchmodSync as
|
|
1
|
+
var h=Object.defineProperty;var i=(n,t)=>h(n,"name",{value:t,configurable:!0});import{readFileSync as a,writeFileSync as l,renameSync as f,openSync as w,closeSync as m,fchmodSync as g,constants as u}from"node:fs";import{APP_UID_MAX as y,APP_UID_MIN as _,LaunchRefused as p}from"./spec.js";function I(){const n=a("/etc/passwd","utf8").split(`
|
|
2
2
|
`).map(s=>s.split(":")),t=a("/etc/group","utf8").split(`
|
|
3
|
-
`).map(s=>s.split(":")),e=n.find(s=>s[0]==="crtrd"),r=t.find(s=>s[0]==="crtr-data");if(!e||!r)throw new Error("crtrd user and crtr-data group must exist");return{daemonUid:Number(e[2]),daemonGid:Number(e[3]),dataGid:Number(r[2])}}i(
|
|
3
|
+
`).map(s=>s.split(":")),e=n.find(s=>s[0]==="crtrd"),r=t.find(s=>s[0]==="crtr-data");if(!e||!r)throw new Error("crtrd user and crtr-data group must exist");return{daemonUid:Number(e[2]),daemonGid:Number(e[3]),dataGid:Number(r[2])}}i(I,"systemIdentity");const $=/^[a-z0-9][a-z0-9-]{0,62}$/;function d(n,t){if(!$.test(n)||!Number.isInteger(t)||t<_||t>y)throw new p("invalid_uid","invalid app or uid")}i(d,"check");class b{static{i(this,"AppUsers")}dataGid;users=new Map;constructor(t){this.dataGid=t}sync(t){const e=new Map,r=new Set;for(const s of t){if(d(s.app,s.uid),e.has(s.uid)||r.has(s.app))throw new p("invalid_uid","duplicate app or uid");e.set(s.uid,s.app),r.add(s.app)}this.users=e,this.write()}create(t,e){if(d(t,e),this.users.has(e)&&this.users.get(e)!==t||[...this.users.values()].includes(t)&&this.users.get(e)!==t)throw new p("invalid_uid","app uid conflict");return this.users.set(e,t),this.write(),e}delete(t){if(!this.users.has(t))throw new p("invalid_uid","unknown app uid");this.users.delete(t),this.write()}has(t){return this.users.has(t)}appFor(t){return this.users.get(t)}entries(){return[...this.users].map(([t,e])=>({app:e,uid:t}))}write(){const t=a("/etc/passwd","utf8").split(`
|
|
4
4
|
`).filter(r=>r&&!/^nl-app-[a-z0-9-]+:/.test(r)),e=a("/etc/group","utf8").split(`
|
|
5
|
-
`).filter(r=>r&&!/^nl-app-[a-z0-9-]+:/.test(r));for(const[r,s]of this.users)t.push(`nl-app-${s}:x:${r}:${this.dataGid}:
|
|
5
|
+
`).filter(r=>r&&!/^nl-app-[a-z0-9-]+:/.test(r));for(const[r,s]of this.users)t.push(`nl-app-${s}:x:${r}:${this.dataGid}:Northlight app:/apps/${s}/home:/bin/sh`),e.push(`nl-app-${s}:x:${r}:`);for(const[r,s]of[["/etc/passwd",t],["/etc/group",e]]){const c=`${r}.launcher-${process.pid}`,o=w(c,u.O_WRONLY|u.O_CREAT|u.O_EXCL,420);try{l(o,`${s.join(`
|
|
6
6
|
`)}
|
|
7
|
-
`),
|
|
7
|
+
`),g(o,420)}finally{m(o)}f(c,r)}}}export{b as AppUsers,I as systemIdentity};
|
|
@@ -21,7 +21,7 @@ interface CustomMessageLike {
|
|
|
21
21
|
originNodeId: string;
|
|
22
22
|
targetFile: string;
|
|
23
23
|
/** Which binding flavor authored this marker — lets a display surface
|
|
24
|
-
* (e.g.
|
|
24
|
+
* (e.g. Northlight's native boundary) pick its copy without parsing the
|
|
25
25
|
* agent-facing prose. Absent on markers written before this field. */
|
|
26
26
|
kind: 'review' | 'page_feedback';
|
|
27
27
|
};
|