@north-light/crouter 0.3.321 → 0.3.323
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/bin/progress-painter.mjs +89 -0
- package/bin/runtime-selector.mjs +123 -4
- package/dist/api/client.d.ts +35 -18
- package/dist/api/client.js +2 -2
- package/dist/api/dto/broker-ops.d.ts +9 -0
- package/dist/api/dto/canvas.d.ts +23 -1
- package/dist/api/dto/modelauth.d.ts +15 -0
- package/dist/api/dto/nodes.d.ts +8 -0
- package/dist/api/errors.d.ts +6 -3
- package/dist/api/errors.js +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +1 -1
- package/dist/api/node-transport.d.ts +18 -0
- package/dist/api/node-transport.js +1 -0
- package/dist/api/routes.d.ts +3 -0
- package/dist/api/routes.js +1 -1
- package/dist/builtin-memory/crouter-sdk.md +86 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +1 -1
- package/dist/cli.js +8 -2
- package/dist/clients/attach/chrome/canvas-panels.js +1 -1
- package/dist/clients/attach/overlays/graph.d.ts +6 -5
- package/dist/clients/attach/overlays/graph.js +1 -1
- package/dist/clients/attach/render/chat-view.d.ts +5 -1
- package/dist/clients/attach/render/chat-view.js +1 -1
- package/dist/clients/attach/session/frames.d.ts +1 -0
- package/dist/clients/attach/session/frames.js +1 -1
- package/dist/clients/attach/viewer.js +576 -576
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/node/create.js +3 -3
- package/dist/commands/sys/branch.js +1 -1
- package/dist/commands/sys/connect.d.ts +1 -0
- package/dist/commands/sys/connect.js +3 -0
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/panels/plugins-panel.js +1 -1
- package/dist/commands/sys/panels/provider-panel.d.ts +4 -0
- package/dist/commands/sys/panels/provider-panel.js +1 -1
- package/dist/commands/sys/provider-login.d.ts +8 -0
- package/dist/commands/sys/provider-login.js +9 -0
- package/dist/commands/sys/setup-core.d.ts +4 -0
- package/dist/commands/sys/setup-core.js +7 -4
- package/dist/commands/sys/setup-wizard.d.ts +1 -1
- package/dist/commands/sys/setup-wizard.js +1 -1
- package/dist/commands/sys.js +1 -1
- package/dist/core/canvas/canvas.d.ts +24 -0
- package/dist/core/canvas/canvas.js +29 -15
- package/dist/core/canvas/migrations.js +35 -32
- package/dist/core/canvas/nav-model.d.ts +1 -1
- package/dist/core/canvas/nav-model.js +1 -1
- package/dist/core/canvas/nav-render.d.ts +31 -25
- package/dist/core/canvas/nav-render.js +1 -1
- package/dist/core/canvas/paths.d.ts +4 -4
- package/dist/core/canvas/render-source.d.ts +6 -16
- package/dist/core/canvas/render-source.js +9 -9
- package/dist/core/canvas/types.d.ts +16 -0
- package/dist/core/config.js +1 -1
- package/dist/core/provider-credential.d.ts +6 -0
- package/dist/core/provider-credential.js +1 -0
- package/dist/core/runtime/boot-root.d.ts +2 -0
- package/dist/core/runtime/boot-root.js +1 -1
- package/dist/core/runtime/broker/daemon-ops.d.ts +2 -1
- package/dist/core/runtime/broker/daemon-ops.js +1 -1
- package/dist/core/runtime/front-door-login.d.ts +2 -0
- package/dist/core/runtime/front-door-login.js +1 -0
- package/dist/core/runtime/front-door.js +7 -5
- package/dist/core/runtime/nerd-font.d.ts +19 -1
- package/dist/core/runtime/nerd-font.js +2 -2
- package/dist/core/runtime/progress-bar.d.ts +13 -0
- package/dist/core/runtime/progress-bar.js +2 -0
- package/dist/core/runtime/provider-credential-gate.d.ts +4 -0
- package/dist/core/runtime/provider-credential-gate.js +2 -0
- package/dist/core/runtime/spawn-env.d.ts +7 -0
- package/dist/core/runtime/spawn-env.js +1 -1
- package/dist/core/secrets.d.ts +3 -0
- package/dist/core/secrets.js +2 -2
- package/dist/core/subscription-state.d.ts +1 -1
- package/dist/core/subscription-state.js +3 -3
- package/dist/core/termrender/termrender.d.ts +3 -1
- package/dist/core/termrender/termrender.js +13 -13
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +1 -1
- package/dist/daemon/api/handlers/canvas.js +4 -4
- package/dist/daemon/api/handlers/health.d.ts +1 -1
- package/dist/daemon/api/handlers/health.js +3 -3
- package/dist/daemon/api/handlers/modelauth.js +1 -1
- package/dist/daemon/api/map.d.ts +15 -1
- package/dist/daemon/api/map.js +2 -2
- package/dist/daemon/api/server.d.ts +5 -1
- package/dist/daemon/api/server.js +4 -4
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/fleet.js +1 -1
- package/dist/daemon/manage.d.ts +1 -0
- package/dist/daemon/manage.js +2 -2
- package/dist/pi-extensions/canvas-stophook.js +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.js +1 -1
- package/docs/sdk/README.md +54 -0
- package/docs/sdk/client.md +92 -0
- package/docs/sdk/docker.md +63 -0
- package/docs/sdk/errors.md +85 -0
- package/docs/sdk/getting-started.md +160 -0
- package/docs/sdk/memory.md +107 -0
- package/docs/sdk/migration.md +108 -0
- package/docs/sdk/nodes.md +193 -0
- package/docs/sdk/resources.md +71 -0
- package/docs/sdk/streaming.md +124 -0
- package/package.json +1 -1
- package/runtime.lock.json +6 -6
- package/scripts/install-runtime.mjs +7 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
`).filter(
|
|
1
|
+
var m=Object.defineProperty;var a=(n,t)=>m(n,"name",{value:t,configurable:!0});import{closeSync as p,fstatSync as w,openSync as b,readSync as g}from"node:fs";import{join as _}from"node:path";import{ApiError as u}from"../api/index.js";import{localClient as d}from"../api/node-transport.js";import{apiErrorToCliError as y}from"../core/io.js";import{autostartDisabled as C,ensureDaemon as S}from"../daemon/manage.js";import{DAEMON_STARTUP_WINDOW_MS as N}from"../daemon/startup-policy.js";import{readState as v}from"../core/config.js";import{resolveRemoteCanvasTarget as T}from"../core/remote-canvas-target.js";import{RemoteCanvasSource as A}from"../core/canvas/remote-canvas-source.js";import{crtrHome as I}from"../core/canvas/paths.js";function l(n){return{...n,fault:n.fault??null}}a(l,"withRowFault");function M(n,t){throw n instanceof u?y(n,t):n}a(M,"rethrowAsCliError");async function V(n){try{return await h().getNode(n)}catch(t){if(t instanceof u&&t.status===404)return null;M(t)}}a(V,"getNodeOrNull");const O=4096,D=20;function R(){let n;try{n=b(_(I(),"crtrd.log"),"r");const{size:t}=w(n),e=Math.max(0,t-O),r=t-e;if(r<=0)return;const s=Buffer.alloc(r);g(n,s,0,r,e);const o=s.toString("utf8").split(`
|
|
2
|
+
`).filter(c=>c.trim()!=="").slice(-D).join(`
|
|
3
3
|
`).trim();return o===""?void 0:`crtrd.log (tail):
|
|
4
|
-
${o}`}catch{return}finally{if(
|
|
4
|
+
${o}`}catch{return}finally{if(n!==void 0)try{p(n)}catch{}}}a(R,"readColdStartDiagnostic");function h(n){return C()?d({autostart:!1,...n?.timeoutMs!==void 0?{timeoutMs:n.timeoutMs}:{}}):d({...n?.timeoutMs!==void 0?{timeoutMs:n.timeoutMs}:{},onColdSocket:a(async()=>{S()},"onColdSocket"),coldStartDiagnostic:R,coldStartPollWindowMs:N})}a(h,"cliClient");const x=2e3;class E{static{a(this,"ApiCanvasSource")}client;rosterCache=null;rosterInFlight=null;constructor(t=h()){this.client=t}async roster(){const t=Date.now();return this.rosterCache!==null&&t-this.rosterCache.at<x?this.rosterCache.index:this.rosterInFlight!==null?this.rosterInFlight:(this.rosterInFlight=this.client.canvasRoster().then(e=>{const r={subscriptions:new Map,subscribers:new Map};for(const s of e.edges){const i={node_id:s.to_id,active:s.active,created:s.created},o={node_id:s.from_id,active:s.active,created:s.created},c=r.subscriptions.get(s.from_id);c===void 0?r.subscriptions.set(s.from_id,[i]):c.push(i);const f=r.subscribers.get(s.to_id);f===void 0?r.subscribers.set(s.to_id,[o]):f.push(o)}return this.rosterCache={at:Date.now(),index:r},r}).finally(()=>{this.rosterInFlight=null}),this.rosterInFlight)}async orNull(t){try{return await t()}catch(e){if(e instanceof u&&e.status===404)return null;throw e}}async getNode(t){const e=await this.orNull(()=>this.client.getNode(t));return e===null?null:l(e)}async getRow(t){const e=await this.orNull(()=>this.client.getNode(t));return e===null?null:l(e)}async listNodes(t){const e=t?.status;let r;if(Array.isArray(e)){const s=new Set(e);r=(await this.client.listNodes()).filter(o=>s.has(o.status))}else e!==void 0?r=await this.client.listNodes({status:e}):r=await this.client.listNodes();return r.map(s=>l(s))}async subscriptionsOf(t){return this.roster().then(e=>e.subscriptions.get(t)??[])}async subscribersOf(t){return this.roster().then(e=>e.subscribers.get(t)??[])}async view(t){const e=new Set([t]),r=[],s=(await this.subscriptionsOf(t)).map(i=>i.node_id);for(;s.length>0;){const i=s.shift();if(!e.has(i)){e.add(i),r.push(i);for(const o of await this.subscriptionsOf(i))e.has(o.node_id)||s.push(o.node_id)}}return r}async ticketCountsForNodes(t){return(await this.client.attentionCounts([...t])).counts}async ticketCountsForView(t){return this.ticketCountsForNodes([t,...await this.view(t)])}async hasActiveLiveSubscription(t){const e=(await this.subscriptionsOf(t)).filter(r=>r.active);for(const r of e){const s=await this.getRow(r.node_id);if(s&&(s.status==="active"||s.status==="idle"))return!0}return!1}}function Y(n){const t=n?.scope??"user",e=n?.canvasName??v(t).activeCanvas??null;if(!e)return new E;const{previewEndpoint:r,relayToken:s}=T(e,t);return new A(r,s)}a(Y,"cliCanvasSource");export{E as ApiCanvasSource,O as COLD_START_DIAGNOSTIC_TAIL_BYTES,Y as cliCanvasSource,h as cliClient,V as getNodeOrNull,R as readColdStartDiagnostic,M as rethrowAsCliError};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var
|
|
1
|
+
var L=Object.defineProperty;var i=(e,r)=>L(e,"name",{value:r,configurable:!0});import{envNodeId as F}from"../../shared/env.js";import{localClient as I}from"../../api/node-transport.js";import{defineLeaf as O}from"../../core/command.js";import{memoryExtensionEffectiveCatalog as K,projectEffectiveMemoryExtensions as Y}from"../../core/memory/extensions.js";import{docLinkNames as U}from"../../core/memory/doc-link-grammar.js";import{ambientMemoryTarget as E,createMemoryDocSnapshot as V,listAllMemoryDocs as W,loadMemoryStoreView as X,loadMemoryTargetView as $,readMemoryDocContent as R,resolveMemoryDocs as z}from"../../core/memory-resolver.js";import{expandShellBlocks as G,hasShellBlocks as H,makeNodeShellRunner as J}from"../../core/runtime/shell-expansion.js";import{emptyContextExposureState as Q,exposureTarget as _,loadContextExposureState as S,registerDocumentExposure as Z,registerExposure as ee,saveContextExposureState as D}from"../../core/substrate/injected-store.js";import{dirDedupKey as te,docsByName as g,isDirName as j,renderDirListing as ne}from"../../core/substrate/listings.js";import{contentReadAttachments as re}from"../../core/substrate/on-read.js";import{renderResult as oe}from"../../core/render.js";import{effectiveDocKind as ae}from"../../core/substrate/schema.js";import{canonicalSegments as ie}from"../../core/memory/identity.js";import{MEMORY_KINDS as se,requireMountedStore as de,resolveReadSelector as ce,selectorParam as M}from"./shared.js";async function le(e){if(e===void 0)return null;try{return await I({autostart:!1,coldStartPollWindowMs:0}).nodeSubject(e)}catch{return null}}i(le,"fetchSubject");function me(){try{return W(void 0,!0,!0)}catch{return[]}}i(me,"corpusDocs");function he(e,r){return{winner:e===r,scope:e.scope,store_root:e.root,physical_relative_path:e.physicalRelativePath,path:e.path}}i(he,"candidateRow");const ue=O({name:"read",description:"load a memory document body by name",whenToUse:"a task in front of you matches a stored document and you already know its canonical name \u2014 read it before improvising. Resolution is exact and takes the full canonical name (a project document is `<namespace>/<local name>`); among documents sharing one name the winner is the nearest scope (node > project stack > profile > user > builtin). A directory name is a valid target too: it answers with that directory\u2019s own document, when it has one, together with its immediate listing, and walking directories is the sanctioned browse move. You name the document by its crtr identifier, never a file path \u2014 do not cat or find the markdown off disk. Reach for `crtr memory find` first when you do not yet know which document applies.",help:{name:"memory read",summary:"resolve an exact canonical name to its document body (frontmatter stripped unless --frontmatter), plus the directory listing when that name has members",params:[{kind:"positional",name:"name",required:!0,constraint:"Full canonical memory name \u2014 a project document is `<namespace>/<local name>`, every other store\u2019s local names are already canonical, where a nested store's namespace includes its path under the repository root. Resolution is exact: a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail. A name that carries members returns its listing alongside its body; a name that is only a directory returns the listing alone."},{kind:"flag",name:"kind",type:"enum",choices:[...se],required:!1,constraint:"Narrows resolution when the name is ambiguous across kinds."},{kind:"flag",name:"frontmatter",type:"bool",required:!1,constraint:"When present, includes the YAML frontmatter in the returned body. Off by default \u2014 only the body is returned."},M("scope-read"),M("dir",{},"Reads that store alone: the name resolves against its documents only, and the listing shows its members only.")],output:[{name:"name",type:"string",required:!0,constraint:"Resolved canonical document name, or the directory name on a directory read."},{name:"local_name",type:"string",required:!1,constraint:"Store-local identity, before the store namespace composed the canonical name. Absent on a directory read."},{name:"kind",type:"string",required:!1,constraint:"Resolved kind: knowledge or preference. Absent on a directory read."},{name:"scope",type:"string",required:!1,constraint:"Scope the document was resolved from: node, project, profile, user, or builtin. Absent on a directory read."},{name:"path",type:"string",required:!1,constraint:"Absolute path to the document on disk. Revise it with `crtr memory edit`, never by editing this file \u2014 an edit records why the change happened and lands in the doc\u2019s revision history. Absent on a directory read."},{name:"store_root",type:"string",required:!1,constraint:"Absolute memory store root the document loaded from. Absent on a directory read."},{name:"physical_relative_path",type:"string",required:!1,constraint:"Store-relative physical markdown path, ordering prefixes and any `INDEX.md` kept. Absent on a directory read."},{name:"representation",type:"string",required:!1,constraint:"Physical representation of this identity: leaf-file or directory-index. Absent on a directory read."},{name:"candidates",type:"object[]",required:!1,constraint:"Present only when several physical documents share this canonical name: one row per candidate in source order, {winner, scope, store_root, physical_relative_path, path}. The winner is what this read returned; reach a non-winning one with `--dir <its project>`."},{name:"content",type:"string",required:!1,constraint:"Document body. Frontmatter stripped unless --frontmatter is set. A document may embed shell as `!`cmd`` or a ```! fenced block; each runs once per read, in the current working directory, and is replaced by its output. Read `path` off disk when you need the literal unexecuted text. May be preceded by one `<auto-loaded-context>` block carrying the doc\u2019s neighborhood listings and any docs routed to this read. Absent on a directory read."},{name:"listing",type:"string[]",required:!1,constraint:"Present when the name has members \u2014 a directory document returns its body AND this listing, a bare directory returns the listing alone: one `[[name]]: <when-and-why>` line per member doc, one bare `[[name]]` per subdirectory. Follow any line with `crtr memory read <name>`."},{name:"extensions",type:"object",required:!1,constraint:"Effective valid plugin-owned metadata, keyed by plugin namespace then field. Includes enabled declaration defaults without writing them to frontmatter; disabled and unresolved namespaces are omitted. Present only on a document read."},{name:"links",type:"string[]",required:!1,constraint:"Canonical names this document links to via `[[name]]` that resolve in the current corpus \u2014 further reading, loaded only on demand with `crtr memory read <name>`. A directory link returns that directory\u2019s listing. Omitted when the body carries no resolvable links."},{name:"follow_up",type:"string",required:!1,constraint:"Present whenever the read returned a listing or resolvable body links."}],outputKind:"object",effects:["Reads the document. A document that embeds shell (`!`cmd`` or a ```! block) runs those commands once, in the current working directory.","Records a content delivery for this node: a document whose profile project caps automatic boot and workspace-open disclosure below content is still returned whole here, and unsolicited channels stay quiet about it afterwards."]},run:i(async e=>{const r=e.name,l=e.kind,N=e.frontmatter,m=ce({scope:e.scope,dir:e.dir}),d=l===void 0?{}:{kind:l};let s=null,p,c,h;if(m.store!==null){const t=X(de(m.store),!0);c=g(t.docs),h=g([...t.docs,...me()]),s=t.candidates(r,d),p=i(()=>t.resolve(r,d),"fail")}else{const t={...d,...m.scope===void 0?{}:{scope:m.scope},includeDescendants:!0},a=$(E(),{...t,quiet:!0,reportDiscovery:!0});c=h=g(a.docs),s=a.candidates(r,d),p=i(()=>a.resolve(r,d),"fail")}const o=F()||void 0;if(s===null){const t=ie(r.trim()).join("/");if(t!==""&&j(c,t)){const a=o!==void 0?S(o):null,b=a===null?null:_(a,"transcript"),B=ne(c,t,b,!1);return o!==void 0&&a!==null&&b!==null&&(ee(b,te(t),"preview"),D(o,a)),{name:t,listing:B,follow_up:"Each line is readable with `crtr memory read <name>` \u2014 a `[[name]]: <line>` entry is a doc, a bare `[[name]]` is a subdirectory whose read browses deeper. Browse the whole inventory with `crtr memory list`."}}p()}const n=s.winner,P=ae(n),y=R(n.path,N),k=H(y)?await G(y,J({cwd:process.cwd()})):y,u=U(n.body).filter(t=>h.has(t)||j(h,t)),v=o!==void 0?S(o):Q(),x=_(v,"transcript"),C=await le(o);Z(x,n.path,n.body,"content");const f=re(n,{subject:C,target:x,byName:c,memoryTarget:E(),listings:"blocks",explicit:!0});o!==void 0&&D(o,v);const T=f.blocks.length===0?k:`<auto-loaded-context>
|
|
2
2
|
${f.blocks.join(`
|
|
3
3
|
`)}
|
|
4
4
|
</auto-loaded-context>
|
|
5
5
|
|
|
6
|
-
${k}`,w=f.listing,q=w!==void 0&&w.length>0,A=q?u.length>0?"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, and the `[[name]]` links in the body are further reading \u2014 follow either with `crtr memory read <name>` only when the task needs that depth.":"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, readable with `crtr memory read <name>` \u2014 follow one only when the task needs that depth.":u.length>0?"The `[[name]]` links in the body are further reading \u2014 follow one with `crtr memory read <name>` only when the task needs that depth.":null;return{name:n.name,local_name:n.localName,kind:
|
|
6
|
+
${k}`,w=f.listing,q=w!==void 0&&w.length>0,A=q?u.length>0?"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, and the `[[name]]` links in the body are further reading \u2014 follow either with `crtr memory read <name>` only when the task needs that depth.":"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, readable with `crtr memory read <name>` \u2014 follow one only when the task needs that depth.":u.length>0?"The `[[name]]` links in the body are further reading \u2014 follow one with `crtr memory read <name>` only when the task needs that depth.":null;return{name:n.name,local_name:n.localName,kind:P,scope:n.scope,path:n.path,store_root:n.root,physical_relative_path:n.physicalRelativePath,representation:n.representation,...s.candidates.length>1?{candidates:s.candidates.map(t=>he(t,n))}:{},content:T,...q?{listing:w}:{},extensions:Y(n.frontmatter?.extensions,K(n)),...u.length>0?{links:u}:{},...A===null?{}:{follow_up:A}}},"run"),render:i(e=>{const{extensions:r,...l}=e;return oe(l,ue.help)},"render")});export{he as candidateRow,V as createMemoryDocSnapshot,ue as readLeaf,R as readMemoryDocContent,z as resolveMemoryDocs};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
`);return Q("kinds",{count:t.length},n)}o(ae,"kindsStateBlock");function se(){return[{kind:"stdin",name:"prompt",required:!1,constraint:"First user message."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Persona kind. Defaults to the profile's default kind; `general` when the profile has none. The <kinds> list below names every top-level installable kind."},{kind:"flag",name:"mode",type:"enum",choices:["base","orchestrator"],required:!1,default:"base",constraint:"Persona mode. base for one hands-on unit; orchestrator when the unit itself needs decomposition across children. When you hold several parallel units of one kind, spawn ONE orchestrator child of that kind to own the fan-out rather than spawning the units yourself \u2014 unless you are already an orchestrator of that kind, whose job is exactly that fan-out."},{kind:"flag",name:"cwd",type:"path",required:!1,constraint:"Pin the node to this directory. Defaults to the spawner\u2019s directory for a managed child, and to the directory this command runs in for a root."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Display name."},{kind:"flag",name:"parent",type:"string",required:!1,constraint:"Parent node id. Defaults to the calling node."},{kind:"flag",name:"node-id",type:"string",required:!1,constraint:"Exact id for the new node; lowercase letters, digits, and hyphens only, at most 128 characters. Rejected if already present."},{kind:"flag",name:"root",type:"bool",required:!1,constraint:"Spawn an independent root with no parent or subscription. Mutually exclusive with --worktree."},{kind:"flag",name:"resident",type:"bool",required:!1,constraint:"Birth the node as resident: it parks and stays wakeable without being forced to submit a final. Pass it ONLY when the user will open this node\u2019s viewer pane and work with it back and forth; a child that only other nodes message stays terminal. Applies to managed children and roots; roots default to resident."},{kind:"flag",name:"terminal",type:"bool",required:!1,constraint:"Birth the node as terminal: it finalizes when its work is done and leaves nothing wakeable behind. Only meaningful with --root, whose default is resident; managed children are already terminal. Pass it for bounded programmatic work no one will open a viewer on. Mutually exclusive with --resident."},{kind:"flag",name:"worktree",type:"string",required:!1,constraint:"Local base branch for a crouter-managed worktree for this child. Mutually exclusive with --root."},{kind:"flag",name:"fork-from",type:"string",required:!1,constraint:"Fork the new node from an existing node id, absolute session path, or partial pi session uuid."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Durable model override. Omit unless the task or governing guidance requires a specific model.",focusedHelp:{whenToUse:"Use only when the user, task, or governing agent guidance already requires a specific provider, model, capability tier, or family. Omit it for routine delegation: the selected persona kind and profile own normal model selection, and review personas may enforce a quality floor.",value:`Accepted forms: ${K}. Concrete provider/id requests must be registered. Portable provider/tier, bare-tier, and family-alias requests resolve through the configured model ladders.`,effects:["The resolved concrete override is stored on the node and in its launch recipe, so every launch and revive uses that exact provider/model.","Portable tiers and aliases resolve through the configured ladders; a persona quality floor may raise a weaker request before its resolved model is stored."]}},{kind:"flag",name:"profile",type:"string",required:!1,constraint:"Profile override. Use only when the task needs different project purview, memory, or environment; otherwise omit for normal inheritance or selection.",focusedHelp:{whenToUse:"Use only when the work must run under a different profile because it needs that profile\u2019s project purview, memory store, stored environment, or defaults. Do not use it merely to change the working directory, persona kind, model, or graph ownership; those have separate parameters.",value:"Pass a profile id or name from the <profiles> list below; a close-enough name resolves to the nearest profile and the choice is reported on stderr. Omit it inside a node to inherit the caller\u2019s profile; outside a node, omission runs normal cwd-based profile selection.",effects:["The selected profile id is stored on the node and supplies its profile-scoped config, memory, environment, and injected bearings.","It does not change the parent or subscription; --cwd separately pins the working directory."],dynamicState:o(()=>V(),"dynamicState")}},{kind:"flag",name:"situational-context",type:"string",required:!1,constraint:"Hidden ambient context appended to injected bearings, never visible chat."},{kind:"flag",name:"no-kickoff",type:"bool",required:!1,hidden:!0,constraint:"With --root, start the broker and session idle without a first message or model turn. Mutually exclusive with a prompt and --mode orchestrator."},{kind:"flag",name:"deadline",type:"string",required:!1,constraint:"Positive, representable wall-clock duration from spawn. Grammar: one or more positive integer-unit parts; units: s, m, h, d; adjacent parts are summed. Omit for no deadline."},{kind:"flag",name:"outcome-action",type:"string",required:!1,constraint:"Declared human-action name that receives the node outcome. Grammar: 1\u2013128 ASCII letters, digits, dots, underscores, or hyphens; starts with a letter or digit. Omit when no external delivery target is needed."},{kind:"flag",name:"outcome-payload",type:"string",required:!1,constraint:"JSON value, serialized at most 64 KiB, frozen and echoed to --outcome-action. Requires --outcome-action."},{kind:"flag",name:"output-schema",type:"string",required:!1,constraint:"Replace prose completion with a schema-constrained final result. Use only when its consumer requires fixed fields.",focusedHelp:{whenToUse:"Use only when the node\u2019s consumer needs a machine-checkable final object with fixed fields. Omit it for ordinary delegation or when a prose report is enough; structured output replaces the completion protocol, and an over-constrained schema can force retries.",value:`Pass the JSON Schema the result must satisfy. ${J} ${B}`,effects:["The node must answer with `crtr push result` and cannot finish until the result is accepted; a plain `crtr push final` is blocked while the request is pending.",$,"The managed parent wakes through its existing subscription when the final report arrives; an independent root has no subscriber to wake.","An accepted result, or a decline, removes the request and terminates the node."]}}]}o(se,"nodeNewParams");function de(){return[{name:"node_id",type:"string",required:!0,constraint:"New node id."},{name:"name",type:"string",required:!0,constraint:"Display name."},{name:"frozen_at",type:"string",required:!1,constraint:"Present when a managed child exists but capacity deferred its broker start."},...R()?[{name:"window",type:"string",required:!1,constraint:"tmux window id when a viewer opens."},{name:"session",type:"string",required:!1,constraint:"tmux session when a viewer opens."}]:[],{name:"follow_up",type:"string",required:!0,constraint:"Managed child: continue unrelated work or stop; its final report wakes you. Root: hand it off; no report wakes you."}]}o(de,"nodeNewOutput");function le(){return["Creates nodes/<id>/ in the canvas home, indexes the node in canvas.db, and starts its detached broker engine.","A managed child creates an active parent subscription; its pushes wake the parent. --root creates an independent node with spawned_by provenance and no parent subscription.","--worktree creates a managed worktree at ~/.crouter/canvas/worktrees/<node-id>/ on a new crtr/<node-id> branch.",$,"--deadline persists a wall-clock deadline from spawn; expiry cancels the node and records a failure outcome with reason deadline_exceeded.","--outcome-action registers one resolved local human-action target. When the node settles, crouter retries delivery of its outcome document until the target accepts it or permanently rejects it; --outcome-payload is frozen and echoed in that document.",...R()?["--root opens a viewer for the new node in the current tmux session."]:[]]}o(le,"nodeNewEffects");async function ce(e){const t=e.prompt??"",n=e.noKickoff===!0;if(!n&&t.trim()==="")throw new r({error:"empty_prompt",message:"a prompt is required (stdin or positional)",next:"Pipe a task on stdin or pass it as an argument, or pass --no-kickoff to spawn an idle node with none."});if(n&&t.trim()!=="")throw new r({error:"no_kickoff_with_prompt",message:"--no-kickoff cannot be combined with a prompt",field:"no-kickoff",next:"Drop --no-kickoff, or drop the prompt."});const i=e.kind,s=e.mode??"base",c=e.cwd?.trim(),h=c!==void 0&&c!==""?oe(c):void 0,O=e.name,I=e.parent,a=e.root===!0,N=e.resident===!0,P=e.terminal===!0;if(N&&P)throw new r({error:"lifecycle_conflict",message:"--resident and --terminal cannot both be passed",field:"terminal",next:"Pass the one birth lifecycle you want, or neither to take the default (resident for --root, terminal for a managed child)."});const f=e.worktree;if(f!==void 0&&f.trim()==="")throw new r({error:"worktree_base_required",message:"--worktree requires a base branch",field:"worktree",next:"Pass an existing local branch that landing can fast-forward."});const m=f?.trim(),S=e.forkFrom,p=e.model?.trim();if(p==="")throw new r({error:"empty_spec",message:"a model spec is required",field:"model",next:`Run \`crtr node new --model -h\` and read the focused contract. ${Y}`});const T=e.profile,D=T===void 0?void 0:X(T).profileId,w=e.situationalContext?.trim(),C=w!==void 0&&w!==""?w:void 0,E=W(e.outputSchema),U=E!==null?JSON.stringify(E):null,u=e.deadline;if(u!==void 0){const l=G(u),F=l===null?Number.NaN:new Date(Date.now()+l).getTime();if(l===null||!Number.isFinite(l)||l<=0||!Number.isFinite(F))throw new r({error:"invalid_deadline",message:"--deadline must be a positive representable duration",field:"deadline",next:"Pass positive integer-unit parts using s, m, h, or d."})}const k=e.outcomeAction,y=e.outcomePayload;if(y!==void 0&&k===void 0)throw new r({error:"outcome_payload_requires_action",message:"--outcome-payload requires --outcome-action",field:"outcome-payload",next:"Pass the declared delivery action with --outcome-action."});let g;if(y!==void 0)try{g=JSON.parse(y)}catch{throw new r({error:"invalid_outcome_payload",message:"--outcome-payload must be valid JSON",field:"outcome-payload",next:"Pass one valid JSON value."})}const b=e.nodeId?.trim();if(a&&m!==void 0)throw new r({error:"worktree_root_conflict",message:"--worktree is mutually exclusive with --root",field:"worktree",next:"Drop either --root or --worktree."});if(n&&!a)throw new r({error:"no_kickoff_requires_root",message:"--no-kickoff is only valid with --root",field:"no-kickoff",next:"Add --root, or drop --no-kickoff and provide a prompt."});if(n&&s==="orchestrator")throw new r({error:"no_kickoff_orchestrator_unsupported",message:"--no-kickoff cannot be combined with --mode orchestrator",field:"no-kickoff",next:"Drop --no-kickoff and provide a prompt, or spawn in base mode."});const v=z(),j=I??v;x("node_new.create_start",{root:a});const d=await Z().createNode({...i!==void 0?{kind:i}:{},mode:s,cwd:process.cwd(),...h!==void 0&&h!==""?{pin_cwd:h}:{},...O!==void 0?{name:O}:{},prompt:t,...a?{root:!0}:{parent:j??null},...v!==void 0?{creator:v}:{},...N?{root_lifecycle:"resident"}:{},...P?{root_lifecycle:"terminal"}:{},...m!==void 0?{worktree:m}:{},...S!==void 0?{fork_from:S}:{},...p!==void 0?{model:p}:{},...D!==void 0?{profile:D}:{},...C!==void 0?{situational_context:C}:{},...n?{no_kickoff:!0}:{},...U!==null?{output_schema:U}:{},...u!==void 0?{deadline:u}:{},...k!==void 0?{outcome_delivery:{action:k,...g===void 0?{}:{payload:g}}}:{},...b!==void 0&&b!==""?{node_id:b}:{},...a?{prefer_warm:!0}:{}}).catch(te);x("node_new.node_created",{node:d.node_id});let _={};a&&(_=ne(d.node_id,d.cwd,d.name)),a&&x("node_new.viewer_window_opened");const A=!a&&d.frozen_at!==null;return{node_id:d.node_id,name:d.name,...A?{frozen_at:d.frozen_at}:{},window:_.window,session:_.session,follow_up:a?n?"Independent root spawned idle. No kickoff turn ran; hand it off or attach and prompt it. No report wakes you.":"Independent root spawned. You are not subscribed, so its finish does not wake you; hand it off and move on.":A?"Child row exists, but no broker started because capacity is full. It is frozen and will launch when a broker slot frees; its final report wakes you then.":await ie(j)}}o(ce,"runNodeCreation");const Te=H({name:"new",description:"create and start a node immediately",whenToUse:"a unit of work is genuinely separable from what you are doing: a bounded subtask you would otherwise grind out inline, several independent units worth running in parallel (usually via one orchestrator child of the matching kind rather than a flat fan-out from you), a scouting pass over unfamiliar code, a change that wants its own git worktree, or an answer you need in a fixed shape rather than as prose",tier:"important",help:{name:"node new",summary:"create a node immediately and start its detached broker",guide:"The prompt is the child\u2019s entire brief: it wakes with this prompt, its persona, and ambient bearings \u2014 none of your conversation carries over. State the outcome and constraints. Pass an implementation plan only when it is already settled or necessary to preserve an approved decision; otherwise, let the child investigate and choose how. Give the reason along with the request: the larger goal this serves and what the child's output enables, so the child resolves ambiguity toward your intent instead of guessing. Name by absolute path every artifact the task depends on (a spec, an explore report, an earlier child's findings); the child inherits only the files the prompt names. When the report must carry something specific\u2014findings inline, or paths to artifacts it wrote\u2014say so in the prompt.",params:se(),output:de(),dynamicState:o(()=>ae(),"dynamicState"),outputKind:"object",effects:le()},run:o(e=>ce(e),"run"),render:o(e=>{const t=e.window?` in window ${e.window}`:"",n=[`Spawned "${e.name}" (${e.node_id})${t}.`];return e.session&&n.push(`- session: ${e.session}`),n.push("",String(e.follow_up)),n.join(`
|
|
3
|
-
`)},"render")});export{
|
|
1
|
+
var J=Object.defineProperty;var o=(e,t)=>J(e,"name",{value:t,configurable:!0});import{envNodeId as W,envProfileId as K}from"../../shared/env.js";import{defineLeaf as Y}from"../../core/command.js";import{InputError as r}from"../../core/io.js";import{mark as O}from"../../core/timing.js";import{OUTPUT_SCHEMA_CONSTRAINTS as G,OUTPUT_SCHEMA_TRANSPORT as V,parseOutputSchemaValue as X}from"../../core/runtime/structured-output.js";import{MODEL_SPEC_FORMS as Q,MODEL_SPEC_NEXT as Z}from"../../core/runtime/model-selection.js";import{readMergedLaunchConfig as R}from"../../core/config.js";import{parseDurationMs as ee}from"../../core/wake.js";import{profilesStateBlock as te}from"../../core/profiles/state-block.js";import{resolveProfileOperand as ne}from"../../core/profiles/manifest.js";import{stateBlock as oe}from"../../core/help.js";import{cliClient as re,getNodeOrNull as I,rethrowAsCliError as ie}from"../api-client.js";import{inTmux as $}from"../../core/runtime/placement-tmux.js";import{openSpawnViewer as ae}from"../surface/node/placement.js";import{resolve as se}from"node:path";const de=1e5,F='--output-schema writes the accepted result to the node\u2019s context/result.json and pushes it as the node\u2019s final report; a node that cannot honestly satisfy the schema declines with `crtr push result --decline "<reason>" --code <token>`, which finishes it with a failure outcome (reason `declined`) whose `declined` field carries { reason, code, retryable } and no result. Name the code tokens you accept in the prompt; crouter carries the code verbatim and you validate it.',N="Do not wait, poll, or duplicate this child's assignment in your own context \u2014 there is no result to await, stopping will not strand you, and duplicated work wastes the delegated context. You're auto-subscribed, so its finish wakes you on its own. Two moves only: do other independent work right now, or stop and end your turn \u2014 the wake brings you back.";async function le(e){if(e===void 0||e==="")return N;const t=await I(e);if(t===null||t.mode!=="orchestrator")return N;const n=t.context_tokens??null;return n===null||n<de?N:`Child spawned \u2014 you're auto-subscribed, so its finish wakes you on its own; never wait, poll, or duplicate the child's assignment in your own context. But you're an orchestrator already carrying ~${Math.round(n/1e3)}k of context: rather than hold this window open for the result, checkpoint context/roadmap.md and \`crtr node yield\` now. Yielding ends this turn, and the child's completion then revives you fresh against your roadmap \u2014 let that clean revive absorb the result instead of this bloated context.`}o(le,"childFollowUp");function Ee(e){const t=Object.keys(R().kinds).sort();if(!t.includes(e))throw new r({error:"unknown_kind",message:`unknown kind: ${e}`,field:"kind",next:`Valid kinds: ${t.join(", ")}.`})}o(Ee,"assertKind");function ce(){const e=R().kinds,t=Object.keys(e).filter(i=>{const s=i.indexOf("/");return s===-1?!0:(e[i]?.availableTo??[i.slice(0,s)]).includes("*")}).sort(),n=t.map(i=>{const s=e[i]?.whenToUse??"";return s?`${i} \u2014 ${s}`:i}).join(`
|
|
2
|
+
`);return oe("kinds",{count:t.length},n)}o(ce,"kindsStateBlock");function ue(){return[{kind:"stdin",name:"prompt",required:!1,constraint:"First user message."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Persona kind. Defaults to the profile's default kind; `general` when the profile has none. The <kinds> list below names every top-level installable kind."},{kind:"flag",name:"mode",type:"enum",choices:["base","orchestrator"],required:!1,default:"base",constraint:"Persona mode. base for one hands-on unit; orchestrator when the unit itself needs decomposition across children. When you hold several parallel units of one kind, spawn ONE orchestrator child of that kind to own the fan-out rather than spawning the units yourself \u2014 unless you are already an orchestrator of that kind, whose job is exactly that fan-out."},{kind:"flag",name:"cwd",type:"path",required:!1,constraint:"Pin the node to this directory. Defaults to the spawner\u2019s directory for a managed child, and to the directory this command runs in for a root."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Display name."},{kind:"flag",name:"parent",type:"string",required:!1,constraint:"Parent node id. Defaults to the calling node."},{kind:"flag",name:"node-id",type:"string",required:!1,constraint:"Exact id for the new node; lowercase letters, digits, and hyphens only, at most 128 characters. Rejected if already present."},{kind:"flag",name:"root",type:"bool",required:!1,constraint:"Spawn an independent root with no parent or subscription. Mutually exclusive with --worktree."},{kind:"flag",name:"resident",type:"bool",required:!1,constraint:"Birth the node as resident: it parks and stays wakeable without being forced to submit a final. Pass it ONLY when the user will open this node\u2019s viewer pane and work with it back and forth; a child that only other nodes message stays terminal. Applies to managed children and roots; roots default to resident."},{kind:"flag",name:"terminal",type:"bool",required:!1,constraint:"Birth the node as terminal: it finalizes when its work is done and leaves nothing wakeable behind. Only meaningful with --root, whose default is resident; managed children are already terminal. Pass it for bounded programmatic work no one will open a viewer on. Mutually exclusive with --resident."},{kind:"flag",name:"worktree",type:"string",required:!1,constraint:"Local base branch for a crouter-managed worktree for this child. Mutually exclusive with --root."},{kind:"flag",name:"fork-from",type:"string",required:!1,constraint:"Fork the new node from an existing node id, absolute session path, or partial pi session uuid."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Durable model override. Omit unless the task or governing guidance requires a specific model.",focusedHelp:{whenToUse:"Use only when the user, task, or governing agent guidance already requires a specific provider, model, capability tier, or family. Omit it for routine delegation: the selected persona kind and profile own normal model selection, and review personas may enforce a quality floor.",value:`Accepted forms: ${Q}. Concrete provider/id requests must be registered. Portable provider/tier, bare-tier, and family-alias requests resolve through the configured model ladders.`,effects:["The resolved concrete override is stored on the node and in its launch recipe, so every launch and revive uses that exact provider/model.","Portable tiers and aliases resolve through the configured ladders; a persona quality floor may raise a weaker request before its resolved model is stored."]}},{kind:"flag",name:"profile",type:"string",required:!1,constraint:"Profile override. Use only when the task needs different project purview, memory, or environment; otherwise omit for normal inheritance or selection.",focusedHelp:{whenToUse:"Use only when the work must run under a different profile because it needs that profile\u2019s project purview, memory store, stored environment, or defaults. Do not use it merely to change the working directory, persona kind, model, or graph ownership; those have separate parameters.",value:"Pass a profile id or name from the <profiles> list below; a close-enough name resolves to the nearest profile and the choice is reported on stderr. Omit it inside a node to inherit the caller\u2019s profile; outside a node, omission runs normal cwd-based profile selection.",effects:["The selected profile id is stored on the node and supplies its profile-scoped config, memory, environment, and injected bearings.","It does not change the parent or subscription; --cwd separately pins the working directory."],dynamicState:o(()=>te(),"dynamicState")}},{kind:"flag",name:"situational-context",type:"string",required:!1,constraint:"Hidden ambient context appended to injected bearings, never visible chat."},{kind:"flag",name:"no-kickoff",type:"bool",required:!1,hidden:!0,constraint:"With --root, start the broker and session idle without a first message or model turn. Mutually exclusive with a prompt and --mode orchestrator."},{kind:"flag",name:"deadline",type:"string",required:!1,constraint:"Positive, representable wall-clock duration from spawn. Grammar: one or more positive integer-unit parts; units: s, m, h, d; adjacent parts are summed. Omit for no deadline."},{kind:"flag",name:"outcome-action",type:"string",required:!1,constraint:"Declared human-action name that receives the node outcome. Grammar: 1\u2013128 ASCII letters, digits, dots, underscores, or hyphens; starts with a letter or digit. Omit when no external delivery target is needed."},{kind:"flag",name:"outcome-payload",type:"string",required:!1,constraint:"JSON value, serialized at most 64 KiB, frozen and echoed to --outcome-action. Requires --outcome-action."},{kind:"flag",name:"output-schema",type:"string",required:!1,constraint:"Replace prose completion with a schema-constrained final result. Use only when its consumer requires fixed fields.",focusedHelp:{whenToUse:"Use only when the node\u2019s consumer needs a machine-checkable final object with fixed fields. Omit it for ordinary delegation or when a prose report is enough; structured output replaces the completion protocol, and an over-constrained schema can force retries.",value:`Pass the JSON Schema the result must satisfy. ${V} ${G}`,effects:["The node must answer with `crtr push result` and cannot finish until the result is accepted; a plain `crtr push final` is blocked while the request is pending.",F,"The managed parent wakes through its existing subscription when the final report arrives; an independent root has no subscriber to wake.","An accepted result, or a decline, removes the request and terminates the node."]}}]}o(ue,"nodeNewParams");function fe(){return[{name:"node_id",type:"string",required:!0,constraint:"New node id."},{name:"name",type:"string",required:!0,constraint:"Display name."},{name:"frozen_at",type:"string",required:!1,constraint:"Present when a managed child exists but capacity deferred its broker start."},...$()?[{name:"window",type:"string",required:!1,constraint:"tmux window id when a viewer opens."},{name:"session",type:"string",required:!1,constraint:"tmux session when a viewer opens."}]:[],{name:"follow_up",type:"string",required:!0,constraint:"Managed child: continue unrelated work or stop; its final report wakes you. Root: hand it off; no report wakes you."}]}o(fe,"nodeNewOutput");function he(){return["Creates nodes/<id>/ in the canvas home, indexes the node in canvas.db, and starts its detached broker engine.","A managed child creates an active parent subscription; its pushes wake the parent. --root creates an independent node with spawned_by provenance and no parent subscription.","--worktree creates a managed worktree at ~/.crouter/canvas/worktrees/<node-id>/ on a new crtr/<node-id> branch.",F,"--deadline persists a wall-clock deadline from spawn; expiry cancels the node and records a failure outcome with reason deadline_exceeded.","--outcome-action registers one resolved local human-action target. When the node settles, crouter retries delivery of its outcome document until the target accepts it or permanently rejects it; --outcome-payload is frozen and echoed in that document.",...$()?["--root opens a viewer for the new node in the current tmux session."]:[]]}o(he,"nodeNewEffects");async function me(e){const t=e.prompt??"",n=e.noKickoff===!0;if(!n&&t.trim()==="")throw new r({error:"empty_prompt",message:"a prompt is required (stdin or positional)",next:"Pipe a task on stdin or pass it as an argument, or pass --no-kickoff to spawn an idle node with none."});if(n&&t.trim()!=="")throw new r({error:"no_kickoff_with_prompt",message:"--no-kickoff cannot be combined with a prompt",field:"no-kickoff",next:"Drop --no-kickoff, or drop the prompt."});const i=e.kind,s=e.mode??"base",f=e.cwd?.trim(),m=f!==void 0&&f!==""?se(f):void 0,S=e.name,L=e.parent,a=e.root===!0,T=e.resident===!0,C=e.terminal===!0;if(T&&C)throw new r({error:"lifecycle_conflict",message:"--resident and --terminal cannot both be passed",field:"terminal",next:"Pass the one birth lifecycle you want, or neither to take the default (resident for --root, terminal for a managed child)."});const p=e.worktree;if(p!==void 0&&p.trim()==="")throw new r({error:"worktree_base_required",message:"--worktree requires a base branch",field:"worktree",next:"Pass an existing local branch that landing can fast-forward."});const w=p?.trim(),D=e.forkFrom,y=e.model?.trim();if(y==="")throw new r({error:"empty_spec",message:"a model spec is required",field:"model",next:`Run \`crtr node new --model -h\` and read the focused contract. ${Z}`});const E=e.profile,k=E===void 0?void 0:ne(E).profileId,g=e.situationalContext?.trim(),U=g!==void 0&&g!==""?g:void 0,j=X(e.outputSchema),M=j!==null?JSON.stringify(j):null,h=e.deadline;if(h!==void 0){const l=ee(h),B=l===null?Number.NaN:new Date(Date.now()+l).getTime();if(l===null||!Number.isFinite(l)||l<=0||!Number.isFinite(B))throw new r({error:"invalid_deadline",message:"--deadline must be a positive representable duration",field:"deadline",next:"Pass positive integer-unit parts using s, m, h, or d."})}const b=e.outcomeAction,v=e.outcomePayload;if(v!==void 0&&b===void 0)throw new r({error:"outcome_payload_requires_action",message:"--outcome-payload requires --outcome-action",field:"outcome-payload",next:"Pass the declared delivery action with --outcome-action."});let _;if(v!==void 0)try{_=JSON.parse(v)}catch{throw new r({error:"invalid_outcome_payload",message:"--outcome-payload must be valid JSON",field:"outcome-payload",next:"Pass one valid JSON value."})}const x=e.nodeId?.trim();if(a&&w!==void 0)throw new r({error:"worktree_root_conflict",message:"--worktree is mutually exclusive with --root",field:"worktree",next:"Drop either --root or --worktree."});if(n&&!a)throw new r({error:"no_kickoff_requires_root",message:"--no-kickoff is only valid with --root",field:"no-kickoff",next:"Add --root, or drop --no-kickoff and provide a prompt."});if(n&&s==="orchestrator")throw new r({error:"no_kickoff_orchestrator_unsupported",message:"--no-kickoff cannot be combined with --mode orchestrator",field:"no-kickoff",next:"Drop --no-kickoff and provide a prompt, or spawn in base mode."});const q=W(),c=L??q;let u=(k??K())||null;if(u===null&&c!==void 0&&(u=(await I(c))?.profile_id??null),u===null&&c===void 0){const{selectProfileForCwdReadOnly:l}=await import("../../core/profiles/select.js");u=l(process.cwd())}const{exitForMissingProviderCredential:z,hasConfiguredProviderCredential:H}=await import("../../core/runtime/provider-credential-gate.js");await H(u)||z(),O("node_new.create_start",{root:a});const d=await re().createNode({...i!==void 0?{kind:i}:{},mode:s,cwd:process.cwd(),...m!==void 0&&m!==""?{pin_cwd:m}:{},...S!==void 0?{name:S}:{},prompt:t,...a?{root:!0}:{parent:c??null},...q!==void 0?{creator:q}:{},...T?{root_lifecycle:"resident"}:{},...C?{root_lifecycle:"terminal"}:{},...w!==void 0?{worktree:w}:{},...D!==void 0?{fork_from:D}:{},...y!==void 0?{model:y}:{},...k!==void 0?{profile:k}:{},...U!==void 0?{situational_context:U}:{},...n?{no_kickoff:!0}:{},...M!==null?{output_schema:M}:{},...h!==void 0?{deadline:h}:{},...b!==void 0?{outcome_delivery:{action:b,..._===void 0?{}:{payload:_}}}:{},...x!==void 0&&x!==""?{node_id:x}:{},...a?{prefer_warm:!0}:{}}).catch(ie);O("node_new.node_created",{node:d.node_id});let P={};a&&(P=ae(d.node_id,d.cwd,d.name)),a&&O("node_new.viewer_window_opened");const A=!a&&d.frozen_at!==null;return{node_id:d.node_id,name:d.name,...A?{frozen_at:d.frozen_at}:{},window:P.window,session:P.session,follow_up:a?n?"Independent root spawned idle. No kickoff turn ran; hand it off or attach and prompt it. No report wakes you.":"Independent root spawned. You are not subscribed, so its finish does not wake you; hand it off and move on.":A?"Child row exists, but no broker started because capacity is full. It is frozen and will launch when a broker slot frees; its final report wakes you then.":await le(c)}}o(me,"runNodeCreation");const Ue=Y({name:"new",description:"create and start a node immediately",whenToUse:"a unit of work is genuinely separable from what you are doing: a bounded subtask you would otherwise grind out inline, several independent units worth running in parallel (usually via one orchestrator child of the matching kind rather than a flat fan-out from you), a scouting pass over unfamiliar code, a change that wants its own git worktree, or an answer you need in a fixed shape rather than as prose",tier:"important",help:{name:"node new",summary:"create a node immediately and start its detached broker",guide:"The prompt is the child\u2019s entire brief: it wakes with this prompt, its persona, and ambient bearings \u2014 none of your conversation carries over. State the outcome and constraints. Pass an implementation plan only when it is already settled or necessary to preserve an approved decision; otherwise, let the child investigate and choose how. Give the reason along with the request: the larger goal this serves and what the child's output enables, so the child resolves ambiguity toward your intent instead of guessing. Name by absolute path every artifact the task depends on (a spec, an explore report, an earlier child's findings); the child inherits only the files the prompt names. When the report must carry something specific\u2014findings inline, or paths to artifacts it wrote\u2014say so in the prompt.",params:ue(),output:fe(),dynamicState:o(()=>ce(),"dynamicState"),outputKind:"object",effects:he()},run:o(e=>me(e),"run"),render:o(e=>{const t=e.window?` in window ${e.window}`:"",n=[`Spawned "${e.name}" (${e.node_id})${t}.`];return e.session&&n.push(`- session: ${e.session}`),n.push("",String(e.follow_up)),n.join(`
|
|
3
|
+
`)},"render")});export{Ee as assertKind,le as childFollowUp,ce as kindsStateBlock,Ue as nodeNew};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var e=(n,t)=>s(n,"name",{value:t,configurable:!0});import{defineBranch as o}from"../../core/command.js";const r={concept:"crtr configuration, diagnostics, and self-management",desc:"settings, config, setup, doctor, context, sync, migrate, update, version, feedback, logs, support, daemon",useWhen:"managing the crtr installation, inspecting diagnostics, or operating the crtrd supervisor process"},a={name:"sys",summary:"crtr system configuration, settings, diagnostics, and self-management",model:"Configuration and settings own durable behavior; diagnostics and support observe it. Setup, migration, sync, and update change installed or generated state, while daemon control owns the supervisor lifecycle."};function d(n){return o({name:"sys",rootEntry:r,help:{...a},children:n})}e(d,"defineSysBranch");export{d as defineSysBranch};
|
|
1
|
+
var s=Object.defineProperty;var e=(n,t)=>s(n,"name",{value:t,configurable:!0});import{defineBranch as o}from"../../core/command.js";const r={concept:"crtr configuration, diagnostics, and self-management",desc:"settings, config, connect, setup, doctor, context, sync, migrate, update, version, feedback, logs, support, daemon",useWhen:"managing the crtr installation, inspecting diagnostics, or operating the crtrd supervisor process"},a={name:"sys",summary:"crtr system configuration, settings, diagnostics, and self-management",model:"Configuration and settings own durable behavior; diagnostics and support observe it. Setup, migration, sync, and update change installed or generated state, while daemon control owns the supervisor lifecycle."};function d(n){return o({name:"sys",rootEntry:r,help:{...a},children:n})}e(d,"defineSysBranch");export{d as defineSysBranch};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sysConnectLeaf: import("../../core/command.js").LeafDef;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var v=Object.defineProperty;var s=(e,t)=>v(e,"name",{value:t,configurable:!0});import{randomBytes as k}from"node:crypto";import C from"node:path";import{defineLeaf as _}from"../../core/command.js";import{readConfig as h,updateConfig as m}from"../../core/config.js";import{emit as P,InputError as u,isJsonOutput as $}from"../../core/io.js";import{renderResult as R}from"../../core/render.js";import{getApiToken as g,setApiToken as x}from"../../core/secrets.js";import{envCrtrdTcp as L,envCrtrdToken as w,envNodeId as I}from"../../shared/env.js";import{shellQuote as d}from"../../shared/shell-quote.js";import{cliClient as y}from"../api-client.js";const A="127.0.0.1:8787",b=3e4,E=100;function D(){return k(32).toString("hex")}s(D,"token");function N(e){$()?P(e):process.stdout.write(R(e,O.help)+`
|
|
2
|
+
`)}s(N,"printCredentials");function S(e){const t=e.lastIndexOf(":");return t===-1||Number(e.slice(t+1))!==0?e:`${e.slice(0,t+1)}8787`}s(S,"fixedTcpPort");async function q(){const e=y(),t=Date.now()+b;let n;for(;Date.now()<t;){try{const r=await e.status();if(r.listeners.tcp!==null)return r}catch(r){n=r}await new Promise(r=>setTimeout(r,E))}throw new u({error:"listener_unavailable",message:`the successor did not expose a TCP listener within ${b}ms${n instanceof Error?`: ${n.message}`:""}`,next:"Run `crtr sys daemon status` to inspect the daemon, then retry `crtr sys connect`."})}s(q,"awaitTcpListener");async function T(e,t){const n=await e.getModelAuthReadiness(t);if(n.credential==="ready")return;process.stderr.write(`Logging in to ${n.provider} for ${n.model}.
|
|
3
|
+
`);const{loginProvider:r,openProviderLoginRuntime:o,terminalAuthInteraction:a}=await import("./provider-login.js");await r(await o(),n.provider,a());const i=await e.getModelAuthReadiness(t);if(i.credential!=="ready")throw new u({error:"provider_login_incomplete",message:`provider ${i.provider} credential is ${i.credential} after login`,next:`Run \`${M(t)}\` again and complete the provider login.`})}s(T,"ensureProviderCredential");function M(e){return["crtr sys connect",e.profile===void 0?void 0:`--profile ${d(e.profile)}`,e.cwd===void 0?void 0:`--cwd ${d(e.cwd)}`,e.kind===void 0?void 0:`--kind ${d(e.kind)}`,e.model===void 0?void 0:`--model ${d(e.model)}`].filter(t=>t!==void 0).join(" ")}s(M,"connectCommand");const O=_({name:"connect",description:"enable browser access to this daemon",whenToUse:"an application needs a browser-reachable daemon URL and bearer token; this enables the local TCP listener and reports the credentials to paste into that application.",help:{name:"sys connect",summary:"enable authenticated browser access and log in to the selected model provider when needed; a handover reports browser credentials before it begins",params:[{kind:"flag",name:"profile",type:"string",required:!1,constraint:"Profile setting selecting the provider a root launch with these settings would use."},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Directory setting selecting the provider a root launch with these settings would use. Default: this directory."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Node kind setting selecting the provider a root launch with these settings would use."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Model setting selecting the provider a root launch with these settings would use."}],output:[{name:"base_url",type:"string",required:!0,constraint:"The HTTP URL configured for the TCP listener."},{name:"token",type:"string",required:!0,constraint:"The bearer token accepted by that listener; paste it only into the application that needs daemon access."}],outputKind:"object",effects:["Stores a missing listener address in user settings and a missing bearer token in the 0600 user secrets store.","When the listener is not already up with both settings present, asks the daemon to hand over to a successor so it starts the listener.","After the listener is available, resolves the provider selected by these params (default: this directory) and runs its existing OAuth login flow when that provider has no usable credential."]},run:s(async e=>{const t={profile:e.profile,cwd:C.resolve(e.cwd??process.cwd()),kind:e.kind,model:e.model},n=I(),o=h("user").api.tcp,a=g("user"),i=y(),p=await i.status();o===""?m("user",l=>{l.api.tcp=A}):Number(o.slice(o.lastIndexOf(":")+1))===0&&m("user",l=>{l.api.tcp=S(o)});const c=L()??h("user").api.tcp;if(Number(c.slice(c.lastIndexOf(":")+1))===0)throw new u({error:"dynamic_tcp_port",message:"CRTRD_TCP must use a fixed port for `crtr sys connect`",next:"Set CRTRD_TCP to a host and fixed port, then run `crtr sys connect` again."});if(o!==""&&a!==void 0&&p.listeners.tcp!==null)return await T(i,t),{base_url:`http://${p.listeners.tcp}`,token:w()??a};a===void 0&&x(D(),"user");const f=w()??g("user");if(f===void 0)throw new Error("TCP listener cannot start without a bearer token");N({base_url:`http://${c}`,token:f}),await i.restartDaemon(),await q(),await T(i,t),n!==void 0&&await i.sendMessage(n,{body:"Browser access is ready.",tier:"urgent",from:n})},"run")});export{O as sysConnectLeaf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var s=(r,e)=>c(r,"name",{value:e,configurable:!0});import{defineLeaf as d,defineBranch as w}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{parseDurationMs as f}from"../../core/wake.js";import{DaemonNotPreparedError as g,probeDaemonHealth as u,spawnDaemon as h,stopDaemonProcess as y,sweepStrayDaemons as b}from"../../daemon/manage.js";import{isDaemonRunning as v,readPidfile as m,isPidAlive as l}from"../../daemon/pidfile.js";import{cliClient as k}from"../api-client.js";import{CrtrClient as _}from"../../api/index.js";import{defineSysBranch as T}from"./branch.js";const q=d({name:"start",description:"start the daemon in the background",whenToUse:"bringing the crtrd supervisor up for the first time in a session so node window-exits get handled (no-op only when its Unix API socket already serves)",help:{name:"sys daemon start",summary:"start the crtrd supervisor daemon in the background (no-op only if its Unix API socket serves)",params:[{kind:"flag",name:"prepare",type:"bool",required:!1,constraint:"Hold normal daemon work after storage preparation until `sys daemon admit`; omit for ordinary startup."}],output:[{name:"started",type:"boolean",required:!0,constraint:"True when a new daemon process was spawned; false when one was already running."},{name:"pid",type:"number",required:!1,constraint:"PID of the newly spawned daemon."},{name:"existing_pid",type:"number",required:!1,constraint:"PID of the already-running daemon (when started=false)."},{name:"running",type:"boolean",required:!1,constraint:"True only when a live pidfile owner does not serve its Unix API socket."},{name:"serving",type:"boolean",required:!1,constraint:"False only when a live pidfile owner does not answer /healthz."},{name:"probe_error",type:"string",required:!1,constraint:"The final /healthz probe failure when a live owner missed readiness."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon phase when it was reachable."}],outputKind:"object",effects:["Spawns dist/daemon/crtrd-cli.js detached + unref'd; it outlives this process.","Writes a pidfile at CRTR_PIDFILE if set, otherwise crtrHome()/crtrd.pid."]},run:s(async r=>{try{return await h({targetPhase:r.prepare===!0?"prepared":"ready"})}catch(e){throw e instanceof g?new o({error:"daemon_not_prepared",message:e.message,next:"Stop the live daemon, then start a new one with `crtr sys daemon start --prepare` before setup."}):e}},"run")}),x=100;async function D(r,e){const n=Date.now(),a=n+e;let i="initializing",p="the daemon socket was not reachable";for(;Date.now()<a;){try{const t=await u(Math.min(1e3,Math.max(1,a-Date.now())));if(i=t.startup_phase,p=t.startup_blocked??`daemon is ${t.startup_phase}`,t.startup_phase==="blocked")throw new o({error:"startup_blocked",message:t.startup_blocked??"daemon startup is blocked",received:t.startup_phase,field:"phase",next:"Repair the reported blocker paths with `crtr sys migrate`, then retry the await."});if(t.startup_phase===r)return{startup_phase:r,waited_ms:Date.now()-n};if(r==="prepared"&&t.startup_phase==="ready")throw new o({error:"await_missed_phase",message:"daemon is already ready and cannot return to prepared in this process",received:t.startup_phase,field:"phase",next:"Start a new daemon with `crtr sys daemon start --prepare` before awaiting prepared."})}catch(t){if(t instanceof o)throw t;p=t instanceof Error?t.message:String(t)}await new Promise(t=>setTimeout(t,Math.min(x,Math.max(0,a-Date.now()))))}throw new o({error:"await_timeout",message:`daemon did not reach ${r} within ${e}ms (last phase: ${i}; last observation: ${p})`,received:i,field:"phase",next:"Inspect `crtr sys daemon status` and resolve any reported startup blocker before retrying."})}s(D,"awaitDaemonPhase");const I=d({name:"await",description:"wait for a daemon startup phase",whenToUse:"a caller started or was given a daemon that must reach prepared or ready before its next step.",help:{name:"sys daemon await",summary:"wait for the running daemon to report one startup phase",params:[{kind:"flag",name:"phase",type:"enum",choices:["prepared","ready"],required:!0,constraint:"The phase that permits the caller\u2019s next step."},{kind:"flag",name:"timeout",type:"string",required:!1,default:"5m",constraint:"Maximum wait as a positive duration such as 90s, 5m, or 1h30m."}],output:[{name:"startup_phase",type:"string",required:!0,constraint:"The requested phase."},{name:"waited_ms",type:"number",required:!0,constraint:"Elapsed wall-clock time before the phase was observed."}],outputKind:"object",effects:["Read-only: polls /healthz until the requested phase is observed or the caller-bounded timeout expires."]},run:s(async r=>{const e=r.timeout,n=f(e);if(n===null||n<=0)throw new o({error:"invalid_duration",message:`not a positive duration: ${e}`,received:e,field:"timeout",next:"Use a positive duration such as 90s, 5m, or 1h30m."});return D(r.phase,n)},"run")}),S=d({name:"admit",description:"admit normal work after daemon preparation",whenToUse:"the caller has completed its setup against a prepared daemon and is ready for normal daemon work to start.",help:{name:"sys daemon admit",summary:"ask a prepared daemon to start normal services",params:[],output:[{name:"admitted",type:"boolean",required:!0,constraint:"True only when this request began admission."},{name:"already_admitted",type:"boolean",required:!0,constraint:"True when admission had already begun or completed."},{name:"startup_phase",type:"string",required:!0,constraint:"The phase after admission settles."}],outputKind:"object",effects:["Starts normal daemon services when the daemon is prepared."]},run:s(async r=>await _.forLocalSocket({autostart:!1}).admitDaemon(),"run")}),E=d({name:"status",description:"check whether the daemon is running",whenToUse:"operator/dev diagnostic \u2014 probing whether the crtrd supervisor process is alive while troubleshooting revive/supervision behavior",help:{name:"sys daemon status",summary:"check whether the crtrd supervisor daemon is currently running",params:[],output:[{name:"running",type:"boolean",required:!0,constraint:"True when the daemon pidfile owner is alive."},{name:"serving",type:"boolean",required:!1,constraint:"True when the live daemon answers /healthz; false when one probe does not."},{name:"pid",type:"number",required:!1,constraint:"PID of the running daemon."},{name:"probe_error",type:"string",required:!1,constraint:"The failed /healthz observation when the live owner did not answer."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon startup phase when the socket answered."}],outputKind:"object",effects:["Read-only: reads the pidfile, probes the pid, and requests /healthz without autostart."]},run:s(async r=>{const e=m();if(!(e!==null&&l(e)))return{running:!1};try{const a=await u();return{running:!0,serving:a.ok,pid:e,...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}}catch(a){return{running:!0,serving:!1,pid:e,probe_error:a.message}}},"run")}),P=d({name:"restart",description:"put the selected runtime generation in service via a daemon-owned handover",whenToUse:"adopting a newly installed runtime generation \u2014 the routine restart. Use this instead of stop+start: the daemon answers before it hands over, so a caller running inside a node (whose broker the handover tears down) still gets a settled result and is resumed automatically.",help:{name:"sys daemon restart",summary:"ask the running daemon to replace itself with a successor on the selected runtime generation",params:[],output:[{name:"restarted",type:"boolean",required:!0,constraint:"True when a running daemon accepted the handover. False when none was running \u2014 one was started instead."},{name:"started",type:"boolean",required:!1,constraint:"True when no daemon was running and this call started one (restarted=false)."},{name:"pid",type:"number",required:!1,constraint:"The daemon that accepted the handover, or the one just started."},{name:"grace_ms",type:"number",required:!1,constraint:"How long the daemon waits after answering before teardown begins."},{name:"note",type:"string",required:!1,constraint:"What happens next to the calling node. Read it before acting on an interrupted turn."}],outputKind:"object",effects:["The daemon acknowledges immediately, then after a short grace tears its whole broker fleet down, releases its claim, spawns a successor on the runtime generation currently selected, and exits.","Each torn-down broker hands its foreground bash commands to the file-backed background job system; they keep running and report completion by urgent inbox message.","Every node it tore down \u2014 including the caller \u2014 is resolved by the successor through the ordinary row-driven supervision tick.","The calling process is killed as part of that teardown, AFTER this result is returned."]},run:s(async r=>{if(!v()){const e=await h();return{restarted:!1,started:e.started,...e.pid!==void 0?{pid:e.pid}:{},...e.existing_pid!==void 0?{pid:e.existing_pid}:{},note:"No daemon was running, so there was nothing to hand over; one was started on the selected runtime generation."}}try{const e=await k().restartDaemon();return{restarted:e.accepted,pid:e.predecessor_pid,grace_ms:e.grace_ms,note:"Handover accepted. If you are running inside a node, your broker is about to be torn down and resumed on the new generation \u2014 THIS RESTART ALREADY SUCCEEDED. When your turn resumes with this result in the transcript, do not re-run it."}}catch(e){throw new o({error:"daemon_restart_failed",message:`the daemon did not accept a handover: ${e.message}`,next:"Run `crtr sys daemon status` to inspect the daemon."})}},"run")}),R=d({name:"stop",description:"stop the running daemon",whenToUse:"shutting the crtrd supervisor down for good, ending auto-revival of nodes on window exit. Not the way to pick up a new runtime generation \u2014 `restart` is, because stopping kills every broker of this epoch, including the caller's own, so this command cannot report its result to an agent that runs it.",help:{name:"sys daemon stop",summary:"stop the crtrd supervisor daemon and confirm it exited",params:[],output:[{name:"stopped",type:"boolean",required:!0,constraint:"True when a daemon (recorded or stray) was found and killed. False when none was running."},{name:"pid",type:"number",required:!1,constraint:"The recorded pidfile pid that exited (when one was recorded and alive)."},{name:"strays_killed",type:"array",required:!1,constraint:"PIDs of stray crtrd daemons (alive but not in the pidfile) swept alongside the recorded one \u2014 the backstop against a daemon that survived a prior stop."},{name:"force_killed",type:"boolean",required:!1,constraint:"True when the recorded daemon ignored SIGTERM and had to be SIGKILLed."}],outputKind:"object",effects:["Sends SIGTERM to the recorded pid and waits for it to exit and release its pidfile, escalating to SIGKILL if it does not.","Then sweeps any OTHER live crtrd daemon process (SIGTERM, escalating to SIGKILL after 2s) so a stray daemon orphaned from the pidfile cannot keep supervising."]},run:s(async r=>{const e=m(),n=e!==null&&l(e);try{let a=!1;n&&(a=await y(e)==="killed");const i=b(null);return!n&&i.length===0?{stopped:!1}:{stopped:!0,...n?{pid:e}:{},...i.length>0?{strays_killed:i}:{},...a?{force_killed:!0}:{}}}catch(a){throw new o({error:"daemon_stop_failed",message:`failed to stop daemon${n?` pid ${e}`:""}: ${a.message}`,next:"Run `crtr sys daemon status` to inspect the daemon before retrying."})}},"run")}),A=w({name:"daemon",description:"manage the crtrd supervisor process",whenToUse:"managing the crtrd supervisor that auto-revives nodes on window exit \u2014 start it, restart it onto a new runtime generation, check its status, or stop it",help:{name:"sys daemon",summary:"manage the crtrd canvas supervisor daemon",model:"crtrd is a thin background daemon that polls active+idle nodes and acts on window exit: crashed windows become dead; refresh-yield windows get a fresh respawn. It holds no orchestration logic \u2014 just process supervision. These are operator/dev self-management, not something a normal agent turn needs to check. Picking up a newly installed runtime generation is `restart` (a handover the daemon performs on itself); `stop` is the terminal one, and it kills the caller along with the rest of the fleet."},children:[q,I,S,P,E,R]});function G(){return T([A])}s(G,"registerDaemonControlSys");export{A as daemonBranch,G as registerDaemonControlSys};
|
|
1
|
+
var c=Object.defineProperty;var s=(r,e)=>c(r,"name",{value:e,configurable:!0});import{defineLeaf as d,defineBranch as w}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{parseDurationMs as f}from"../../core/wake.js";import{DaemonNotPreparedError as g,probeDaemonHealth as u,probeErrorMessage as y,spawnDaemon as h,stopDaemonProcess as b,sweepStrayDaemons as v}from"../../daemon/manage.js";import{isDaemonRunning as _,readPidfile as m,isPidAlive as l}from"../../daemon/pidfile.js";import{cliClient as k}from"../api-client.js";import{localClient as T}from"../../api/node-transport.js";import{defineSysBranch as q}from"./branch.js";const x=d({name:"start",description:"start the daemon in the background",whenToUse:"bringing the crtrd supervisor up for the first time in a session so node window-exits get handled (no-op only when its Unix API socket already serves)",help:{name:"sys daemon start",summary:"start the crtrd supervisor daemon in the background (no-op only if its Unix API socket serves)",params:[{kind:"flag",name:"prepare",type:"bool",required:!1,constraint:"Hold normal daemon work after storage preparation until `sys daemon admit`; omit for ordinary startup."}],output:[{name:"started",type:"boolean",required:!0,constraint:"True when a new daemon process was spawned; false when one was already running."},{name:"pid",type:"number",required:!1,constraint:"PID of the newly spawned daemon."},{name:"existing_pid",type:"number",required:!1,constraint:"PID of the already-running daemon (when started=false)."},{name:"running",type:"boolean",required:!1,constraint:"True only when a live pidfile owner does not serve its Unix API socket."},{name:"serving",type:"boolean",required:!1,constraint:"False only when a live pidfile owner does not answer /healthz."},{name:"probe_error",type:"string",required:!1,constraint:"The final /healthz probe failure when a live owner missed readiness."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon phase when it was reachable."}],outputKind:"object",effects:["Spawns dist/daemon/crtrd-cli.js detached + unref'd; it outlives this process.","Writes a pidfile at CRTR_PIDFILE if set, otherwise crtrHome()/crtrd.pid."]},run:s(async r=>{try{return await h({targetPhase:r.prepare===!0?"prepared":"ready"})}catch(e){throw e instanceof g?new o({error:"daemon_not_prepared",message:e.message,next:"Stop the live daemon, then start a new one with `crtr sys daemon start --prepare` before setup."}):e}},"run")}),D=100;async function I(r,e){const n=Date.now(),a=n+e;let i="initializing",p="the daemon socket was not reachable";for(;Date.now()<a;){try{const t=await u(Math.min(1e3,Math.max(1,a-Date.now())));if(i=t.startup_phase,p=t.startup_blocked??`daemon is ${t.startup_phase}`,t.startup_phase==="blocked")throw new o({error:"startup_blocked",message:t.startup_blocked??"daemon startup is blocked",received:t.startup_phase,field:"phase",next:"Repair the reported blocker paths with `crtr sys migrate`, then retry the await."});if(t.startup_phase===r)return{startup_phase:r,waited_ms:Date.now()-n};if(r==="prepared"&&t.startup_phase==="ready")throw new o({error:"await_missed_phase",message:"daemon is already ready and cannot return to prepared in this process",received:t.startup_phase,field:"phase",next:"Start a new daemon with `crtr sys daemon start --prepare` before awaiting prepared."})}catch(t){if(t instanceof o)throw t;p=t instanceof Error?t.message:String(t)}await new Promise(t=>setTimeout(t,Math.min(D,Math.max(0,a-Date.now()))))}throw new o({error:"await_timeout",message:`daemon did not reach ${r} within ${e}ms (last phase: ${i}; last observation: ${p})`,received:i,field:"phase",next:"Inspect `crtr sys daemon status` and resolve any reported startup blocker before retrying."})}s(I,"awaitDaemonPhase");const S=d({name:"await",description:"wait for a daemon startup phase",whenToUse:"a caller started or was given a daemon that must reach prepared or ready before its next step.",help:{name:"sys daemon await",summary:"wait for the running daemon to report one startup phase",params:[{kind:"flag",name:"phase",type:"enum",choices:["prepared","ready"],required:!0,constraint:"The phase that permits the caller\u2019s next step."},{kind:"flag",name:"timeout",type:"string",required:!1,default:"5m",constraint:"Maximum wait as a positive duration such as 90s, 5m, or 1h30m."}],output:[{name:"startup_phase",type:"string",required:!0,constraint:"The requested phase."},{name:"waited_ms",type:"number",required:!0,constraint:"Elapsed wall-clock time before the phase was observed."}],outputKind:"object",effects:["Read-only: polls /healthz until the requested phase is observed or the caller-bounded timeout expires."]},run:s(async r=>{const e=r.timeout,n=f(e);if(n===null||n<=0)throw new o({error:"invalid_duration",message:`not a positive duration: ${e}`,received:e,field:"timeout",next:"Use a positive duration such as 90s, 5m, or 1h30m."});return I(r.phase,n)},"run")}),E=d({name:"admit",description:"admit normal work after daemon preparation",whenToUse:"the caller has completed its setup against a prepared daemon and is ready for normal daemon work to start.",help:{name:"sys daemon admit",summary:"ask a prepared daemon to start normal services",params:[],output:[{name:"admitted",type:"boolean",required:!0,constraint:"True only when this request began admission."},{name:"already_admitted",type:"boolean",required:!0,constraint:"True when admission had already begun or completed."},{name:"startup_phase",type:"string",required:!0,constraint:"The phase after admission settles."}],outputKind:"object",effects:["Starts normal daemon services when the daemon is prepared."]},run:s(async r=>await T({autostart:!1}).admitDaemon(),"run")}),P=d({name:"status",description:"check whether the daemon is running",whenToUse:"operator/dev diagnostic \u2014 probing whether the crtrd supervisor process is alive while troubleshooting revive/supervision behavior",help:{name:"sys daemon status",summary:"check whether the crtrd supervisor daemon is currently running",params:[],output:[{name:"running",type:"boolean",required:!0,constraint:"True when the daemon pidfile owner is alive."},{name:"serving",type:"boolean",required:!1,constraint:"True when the live daemon answers /healthz; false when one probe does not."},{name:"pid",type:"number",required:!1,constraint:"PID of the running daemon."},{name:"probe_error",type:"string",required:!1,constraint:"The failed /healthz observation when the live owner did not answer."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon startup phase when the socket answered."}],outputKind:"object",effects:["Read-only: reads the pidfile, probes the pid, and requests /healthz without autostart."]},run:s(async r=>{const e=m();if(!(e!==null&&l(e)))return{running:!1};try{const a=await u();return{running:!0,serving:a.ok,pid:e,...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}}catch(a){return{running:!0,serving:!1,pid:e,probe_error:y(a)}}},"run")}),R=d({name:"restart",description:"put the selected runtime generation in service via a daemon-owned handover",whenToUse:"adopting a newly installed runtime generation \u2014 the routine restart. Use this instead of stop+start: the daemon answers before it hands over, so a caller running inside a node (whose broker the handover tears down) still gets a settled result and is resumed automatically.",help:{name:"sys daemon restart",summary:"ask the running daemon to replace itself with a successor on the selected runtime generation",params:[],output:[{name:"restarted",type:"boolean",required:!0,constraint:"True when a running daemon accepted the handover. False when none was running \u2014 one was started instead."},{name:"started",type:"boolean",required:!1,constraint:"True when no daemon was running and this call started one (restarted=false)."},{name:"pid",type:"number",required:!1,constraint:"The daemon that accepted the handover, or the one just started."},{name:"grace_ms",type:"number",required:!1,constraint:"How long the daemon waits after answering before teardown begins."},{name:"note",type:"string",required:!1,constraint:"What happens next to the calling node. Read it before acting on an interrupted turn."}],outputKind:"object",effects:["The daemon acknowledges immediately, then after a short grace tears its whole broker fleet down, releases its claim, spawns a successor on the runtime generation currently selected, and exits.","Each torn-down broker hands its foreground bash commands to the file-backed background job system; they keep running and report completion by urgent inbox message.","Every node it tore down \u2014 including the caller \u2014 is resolved by the successor through the ordinary row-driven supervision tick.","The calling process is killed as part of that teardown, AFTER this result is returned."]},run:s(async r=>{if(!_()){const e=await h();return{restarted:!1,started:e.started,...e.pid!==void 0?{pid:e.pid}:{},...e.existing_pid!==void 0?{pid:e.existing_pid}:{},note:"No daemon was running, so there was nothing to hand over; one was started on the selected runtime generation."}}try{const e=await k().restartDaemon();return{restarted:e.accepted,pid:e.predecessor_pid,grace_ms:e.grace_ms,note:"Handover accepted. If you are running inside a node, your broker is about to be torn down and resumed on the new generation \u2014 THIS RESTART ALREADY SUCCEEDED. When your turn resumes with this result in the transcript, do not re-run it."}}catch(e){throw new o({error:"daemon_restart_failed",message:`the daemon did not accept a handover: ${e.message}`,next:"Run `crtr sys daemon status` to inspect the daemon."})}},"run")}),A=d({name:"stop",description:"stop the running daemon",whenToUse:"shutting the crtrd supervisor down for good, ending auto-revival of nodes on window exit. Not the way to pick up a new runtime generation \u2014 `restart` is, because stopping kills every broker of this epoch, including the caller's own, so this command cannot report its result to an agent that runs it.",help:{name:"sys daemon stop",summary:"stop the crtrd supervisor daemon and confirm it exited",params:[],output:[{name:"stopped",type:"boolean",required:!0,constraint:"True when a daemon (recorded or stray) was found and killed. False when none was running."},{name:"pid",type:"number",required:!1,constraint:"The recorded pidfile pid that exited (when one was recorded and alive)."},{name:"strays_killed",type:"array",required:!1,constraint:"PIDs of stray crtrd daemons (alive but not in the pidfile) swept alongside the recorded one \u2014 the backstop against a daemon that survived a prior stop."},{name:"force_killed",type:"boolean",required:!1,constraint:"True when the recorded daemon ignored SIGTERM and had to be SIGKILLed."}],outputKind:"object",effects:["Sends SIGTERM to the recorded pid and waits for it to exit and release its pidfile, escalating to SIGKILL if it does not.","Then sweeps any OTHER live crtrd daemon process (SIGTERM, escalating to SIGKILL after 2s) so a stray daemon orphaned from the pidfile cannot keep supervising."]},run:s(async r=>{const e=m(),n=e!==null&&l(e);try{let a=!1;n&&(a=await b(e)==="killed");const i=v(null);return!n&&i.length===0?{stopped:!1}:{stopped:!0,...n?{pid:e}:{},...i.length>0?{strays_killed:i}:{},...a?{force_killed:!0}:{}}}catch(a){throw new o({error:"daemon_stop_failed",message:`failed to stop daemon${n?` pid ${e}`:""}: ${a.message}`,next:"Run `crtr sys daemon status` to inspect the daemon before retrying."})}},"run")}),U=w({name:"daemon",description:"manage the crtrd supervisor process",whenToUse:"managing the crtrd supervisor that auto-revives nodes on window exit \u2014 start it, restart it onto a new runtime generation, check its status, or stop it",help:{name:"sys daemon",summary:"manage the crtrd canvas supervisor daemon",model:"crtrd is a thin background daemon that polls active+idle nodes and acts on window exit: crashed windows become dead; refresh-yield windows get a fresh respawn. It holds no orchestration logic \u2014 just process supervision. These are operator/dev self-management, not something a normal agent turn needs to check. Picking up a newly installed runtime generation is `restart` (a handover the daemon performs on itself); `stop` is the terminal one, and it kills the caller along with the rest of the fleet."},children:[x,S,E,R,P,A]});function F(){return q([U])}s(F,"registerDaemonControlSys");export{U as daemonBranch,F as registerDaemonControlSys};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var p=Object.defineProperty;var c=(a,e)=>p(a,"name",{value:e,configurable:!0});import{truncateToWidth as d}from"@earendil-works/pi-tui";import{theme as t,wrapText as I}from"../../../core/tui/panel.js";const l="crtr-extensions";class ${static{c(this,"PluginsPanel")}manifest;installedPackageIds;onChange;selectedPackageIds=new Set;index=0;expandedId=null;constructor(e,s,n){this.manifest=e,this.installedPackageIds=s,this.onChange=n;for(const i of e)this.selectedPackageIds.add(i.id)}badge(){const e=this.manifest.filter(s=>!this.installedPackageIds.has(s.id)).length;return e===0?`${this.manifest.length} \u2713`:`${e} pending`}get selected(){return this.manifest.filter(e=>this.selectedPackageIds.has(e.id))}current(){return this.manifest[this.index]}handleInput(e,s){if(s===void 0)return!1;if(s("crtr.setup.list-down",e))this.index=this.manifest.length===0?0:(this.index+1)%this.manifest.length;else if(s("crtr.setup.list-up",e))this.index=this.manifest.length===0?0:(this.index-1+this.manifest.length)%this.manifest.length;else if(s("crtr.setup.toggle",e)){const n=this.current();n&&n.id!==l&&(this.selectedPackageIds.has(n.id)?this.selectedPackageIds.delete(n.id):this.selectedPackageIds.add(n.id))}else if(s("crtr.setup.details",e)){const n=this.current();n&&(this.expandedId=this.expandedId===n.id?null:n.id)}else return!1;return this.onChange(),!0}render(e){const s=[d(t.fg("accent",t.bold("Plugins")),e),d(t.fg("muted","Everything is checked by default. Leave it all on unless you have a specific reason not to."),e)];for(const[n,i]of this.manifest.entries()){const r=n===this.index,h=this.selectedPackageIds.has(i.id)?t.fg("success","[x]"):t.fg("dim","[ ]"),f=r?t.fg("accent","\u25B6"):t.fg("dim"," "),o=r?t.fg("accent",i.name):i.name,g=i.id===l?` ${t.fg("dim","required")}`:"",u=this.installedPackageIds.has(i.id)?t.fg("success","\u2713 installed"):t.fg("warning","not installed");if(s.push(d(`${f} ${h} ${o}${g} ${t.fg("dim","\u2014")} ${t.fg("muted",i.shortDescription)} ${u}`,e)),this.expandedId===i.id)for(const m of I(i.longDescription,Math.max(1,e-4)))s.push(d(t.fg("dim",` ${m}`),e))}return s}}export{$ as PluginsPanel};
|
|
@@ -52,6 +52,10 @@ export declare class ProviderPanel implements SetupPanel {
|
|
|
52
52
|
isNestedFlowActive(): boolean;
|
|
53
53
|
dispose(): void;
|
|
54
54
|
private runMutation;
|
|
55
|
+
/** Prefilled label for a new managed account: `default` for the first one, then
|
|
56
|
+
* `account-2`, `account-3`… so Enter alone never collides with an existing row
|
|
57
|
+
* (pool labels compare case-insensitively). */
|
|
58
|
+
private suggestedAccountLabel;
|
|
55
59
|
private rename;
|
|
56
60
|
private remove;
|
|
57
61
|
private promote;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var R=Object.defineProperty;var m=(u,e)=>R(u,"name",{value:e,configurable:!0});import{LoginDialogComponent as D}from"@earendil-works/pi-coding-agent";import{Input as E,matchesKey as h,truncateToWidth as B}from"@earendil-works/pi-tui";import{execFile as N}from"node:child_process";import{dialogAuthInteraction as T}from"../../../core/auth-interaction.js";import{isProviderCredentialConfigured as F}from"../../../core/provider-credential.js";import{isManagedProvider as C,promoteSubscription as U,readSubscriptionPool as k,renameSubscription as _}from"../../../core/subscription-state.js";import{removeProviderRow as V}from"../../../core/provider-management.js";import{cliClient as j}from"../../api-client.js";import{loginProvider as q}from"../provider-login.js";import{fitTail as P,theme as s,visibleRange as M}from"../../../core/tui/panel.js";function S(u,e){const t=u.getProviders().filter(i=>i.auth.oauth!==void 0),n=new Set(t.map(i=>i.id)),o=new Set;return[...t.map(i=>({providerId:i.id,name:i.name,managed:C(i.id),authType:"oauth"})),...e.getAll().map(i=>({providerId:i.provider,name:e.getProviderDisplayName(i.provider),managed:C(i.provider),authType:n.has(i.provider)?"oauth":"api_key"}))].filter(i=>!o.has(i.providerId)&&!!o.add(i.providerId))}m(S,"buildProviderCatalog");function K(u,e){return e||u instanceof Error&&u.name==="AbortError"?"Login cancelled":`Login error: ${u instanceof Error?u.message:String(u)}`}m(K,"classifyLoginFailure");class te{static{m(this,"ProviderPanel")}tui;agentDir;runtime;registry;renameInput=new E;rows=[];selected=0;mode="list";notice="";catalog=[];catalogIndex=0;loginProvider;loginFlow;mutationInFlight=!1;onChange;constructor(e,t,n,o){this.tui=e,this.agentDir=t,this.runtime=n,this.registry=o,this.renameInput.onSubmit=()=>this.rename(),this.renameInput.onEscape=()=>{this.mode="list"},this.reload()}notifyChange(){this.onChange?.(),this.tui.requestRender()}reloadAfterAuthChange(){this.reload(),this.registry.refresh().then(()=>{this.reload(),this.notifyChange()})}reload(){const e=this.registry,t=[];for(const n of S(this.runtime,e)){const o=e.getProviderAuthStatus(n.providerId);if(n.managed){const i=k(n.providerId);if(i.length===0&&!o.source)continue;t.push({...n,configured:F(n.providerId,e),source:o.source,sourceLabel:o.label}),i.forEach((a,c)=>t.push({...n,label:a.label,preferred:c===0,cooldownUntil:a.rateLimitedUntil,accountId:a.accountId}));continue}o.source&&t.push({...n,configured:F(n.providerId,e),source:o.source,sourceLabel:o.label})}this.rows=t,this.selected=Math.min(this.selected,Math.max(0,t.length))}selectedRow(){return this.rows[this.selected]}openBrowser(e){const t=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",n=process.platform==="win32"?["/c","start","",e]:[e];N(t,n,{timeout:5e3},()=>{})}openCatalog(){this.catalog=S(this.runtime,this.registry),this.catalogIndex=0,this.mode="catalog"}startLogin(e,t){this.loginProvider=e;let n;const o=m(r=>{!n.active||this.loginFlow!==n||(n.active=!1,this.loginFlow=void 0,this.mode="list",this.notice=r,this.reloadAfterAuthChange(),this.notifyChange())},"complete"),i=new D(this.tui,e.providerId,(r,p)=>{r||o(p?`Login failed: ${p}`:"Login cancelled")},e.name);n={dialog:i,active:!0,committing:!1},this.loginFlow=n,this.mode="login";const a=i.handleInput.bind(i);i.handleInput=r=>{n.committing||a(r)};const c=m(()=>n.active&&this.loginFlow===n&&!i.signal.aborted,"isActive"),d=m(()=>c()?(n.committing=!0,i.showDetails(["Saving credentials\u2026"]),!0):!1,"beginCommit"),g=m(r=>{o(K(r,i.signal.aborted))},"fail");if(e.authType==="api_key"){i.showPrompt(`Enter API key for ${e.name}:`,"sk-...").then(async r=>{if(c()){if(!r.trim()){o("No key entered \u2014 login cancelled");return}d()&&(await j().installCredential(e.providerId,{kind:"api_key",api_key:r.trim()}),o(`API key saved for ${e.name}`))}}).catch(g);return}const y=T(i,{openBrowser:m(r=>this.openBrowser(r),"openBrowser"),onChange:m(()=>this.notifyChange(),"onChange"),isActive:c});q(this.runtime,e.providerId,y,{label:t,beforeInstall:d}).then(r=>{r&&c()&&o(`Logged in to ${e.name}`)}).catch(g)}isNestedFlowActive(){return this.mode==="login"}dispose(){const e=this.loginFlow;e&&(e.active=!1,this.loginFlow=void 0,e.dialog.handleInput("\x1B"))}runMutation(e){this.mutationInFlight||(this.mutationInFlight=!0,e().catch(t=>{this.notice=t instanceof Error?t.message:String(t)}).finally(()=>{this.mutationInFlight=!1,this.notifyChange()}))}suggestedAccountLabel(e){const t=new Set((C(e)?k(e):[]).map(o=>o.label.toLowerCase()));if(!t.has("default"))return"default";let n=2;for(;t.has(`account-${n}`);)n+=1;return`account-${n}`}rename(){const e=this.selectedRow();!e?.managed||!e.label||(this.mode="list",this.runMutation(async()=>{await _(e.providerId,e.label,this.renameInput.getValue()),this.notice=`Renamed ${e.name} account`,this.reload()}))}remove(e){this.runMutation(async()=>{this.notice=await V(this.agentDir,e),this.reloadAfterAuthChange()})}promote(e){this.runMutation(async()=>{await U(e.providerId,e.label),this.notice=`Preferred ${e.name} account`,this.reload()})}handleInput(e){if(this.mode==="login")return this.loginFlow?.dialog.handleInput(e),!0;if(this.mutationInFlight)return!0;if(this.mode==="rename"||this.mode==="label")return this.renameInput.handleInput(e),!0;if(this.mode==="catalog"){if(h(e,"escape"))return this.mode="list",!0;if(h(e,"j")||h(e,"down"))return this.catalogIndex=Math.min(this.catalog.length-1,this.catalogIndex+1),!0;if(h(e,"k")||h(e,"up"))return this.catalogIndex=Math.max(0,this.catalogIndex-1),!0;if(h(e,"enter")){const o=this.catalog[this.catalogIndex];if(o.managed){this.loginProvider=o;const i=this.suggestedAccountLabel(o.providerId);this.renameInput.setValue(i),this.renameInput.onSubmit=()=>{this.startLogin(o,this.renameInput.getValue().trim()||i)},this.mode="label"}else this.startLogin(o);return!0}return!0}if(this.mode==="remove"){if(h(e,"y")||h(e,"enter")){const o=this.selectedRow();o&&(o.managed||o.configured)&&this.remove(o)}return this.mode="list",!0}if(h(e,"j")||h(e,"down"))return this.selected=Math.min(this.rows.length,this.selected+1),!0;if(h(e,"k")||h(e,"up"))return this.selected=Math.max(0,this.selected-1),!0;const t=this.selectedRow();if(h(e,"p")&&t?.managed&&t.label)return this.promote(t),!0;if(h(e,"r")&&t?.managed&&t.label)return this.renameInput.setValue(t.label),this.renameInput.onSubmit=()=>this.rename(),this.mode="rename",!0;if(h(e,"x")&&(t?.managed||t?.configured))return this.mode="remove",!0;if(h(e,"a"))return this.openCatalog(),!0;const n=t?.source==="environment"||t?.source==="runtime";return h(e,"l")&&t&&!n?(t.managed&&t.label?this.startLogin(t,t.label):this.startLogin(t),!0):h(e,"enter")?(t&&!n?this.startLogin(t,t.managed&&t.label?t.label:void 0):t||this.openCatalog(),!0):!1}badge(){return String(this.rows.filter(e=>e.label===void 0).length)}render(e,t){const n=s.fg("accent",s.bold("Providers")),o=s.fg("dim",`${this.rows.filter(c=>c.label===void 0).length} configured`),i=Math.max(1,t);let a;if(this.mode==="login")a=P(this.loginFlow?.dialog.render(e)??[],i);else if(this.mode==="rename"||this.mode==="label")a=P([n,o,"",s.fg("accent",this.mode==="rename"?"Rename account":`Name the ${this.loginProvider?.name??"provider"} account`),...this.mode==="label"?[s.fg("dim","Names this credential \u2014 keep work and personal accounts on one provider apart")]:[],...this.renameInput.render(Math.max(1,e-8))],i);else if(this.mode==="remove"){const c=this.selectedRow(),d=c?.label??(c?.managed?`${c.name} \xB7 all accounts`:c?.name??"account");a=P([n,o,"",s.fg("warning",`Remove ${d}? y/enter confirm \xB7 any other key cancel`)],i)}else{const c=[n,o].slice(0,Math.max(0,i-1)),d=i-c.length;if(a=[...c],this.mode==="catalog"){const g=this.catalog.length,r=g>d&&d>=2,p=r?d-1:d,{start:I,end:v}=M(g,this.catalogIndex,p);let w=d-(v-I)-(r?1:0);w>0&&(a.push(s.fg("dim","Select a provider to add \xB7 esc back")),w-=1);for(let f=I;f<v;f++){const x=this.catalog[f];a.push(`${f===this.catalogIndex?s.fg("accent","\u25B6"):" "} ${x.name}`)}r&&a.push(s.fg("dim",`(${this.catalogIndex+1}/${g})`))}else{const g=this.rows.length+1,r=g>d&&d>=2,p=r?d-1:d,{start:I,end:v}=M(g,this.selected,p);let w=d-(v-I)-(r?1:0);const f=this.rows.length===0&&w>0;f&&(w-=1);const x=!!this.notice&&w>0;f&&a.push(s.fg("warning","No provider configured yet."));for(let b=I;b<v;b++){if(b===this.rows.length){a.push(`${this.selected===this.rows.length?s.fg("accent","\u25B6"):" "} ${s.fg("accent","\uFF0B Add provider")}`);continue}const l=this.rows[b],A=b===this.selected?s.fg("accent","\u25B6"):" ";if(l.label){const L=l.cooldownUntil&&l.cooldownUntil>Date.now()?`cooldown ${Math.ceil((l.cooldownUntil-Date.now())/6e4)}m`:"ready",$=l.accountId?` \xB7 ${l.accountId.length>12?`${l.accountId.slice(0,8)}\u2026${l.accountId.slice(-4)}`:l.accountId}`:"";a.push(`${A} ${l.preferred?s.fg("success","\u25CF"):" "} ${l.label}${s.fg("dim",$)} ${s.fg("dim",L)}${l.preferred?s.fg("accent"," preferred"):""}`)}else{const L=l.managed?`${this.rows.filter($=>$.providerId===l.providerId&&$.label).length} accounts`:l.source==="environment"||l.source==="runtime"?`${l.source}${l.sourceLabel?` \xB7 ${l.sourceLabel}`:""} (view only)`:"configured";a.push(`${A} ${s.fg("accent",l.name)}${s.fg("dim",` ${L}`)}`)}}r&&a.push(s.fg("dim",`(${this.selected+1}/${g})`)),x&&a.push(s.fg("dim",this.notice))}}return a.map(c=>B(c,e))}}export{te as ProviderPanel,S as buildProviderCatalog,K as classifyLoginFailure};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModelRuntime } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { ProviderAuthInteraction } from '@earendil-works/pi-ai';
|
|
3
|
+
export declare function openProviderLoginRuntime(): Promise<ModelRuntime>;
|
|
4
|
+
export declare function loginProvider(runtime: ModelRuntime, providerId: string, interaction: ProviderAuthInteraction, options?: {
|
|
5
|
+
label?: string;
|
|
6
|
+
beforeInstall?: () => boolean;
|
|
7
|
+
}): Promise<boolean>;
|
|
8
|
+
export declare function terminalAuthInteraction(): ProviderAuthInteraction;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
var y=Object.defineProperty;var o=(e,r)=>y(e,"name",{value:r,configurable:!0});import{execFile as b}from"node:child_process";import{createInterface as x}from"node:readline/promises";import{join as u}from"node:path";import{ModelRuntime as $}from"@earendil-works/pi-coding-agent";import{cliClient as d}from"../api-client.js";import{InputError as f}from"../../core/io.js";import{PoolCredentialStore as k}from"../../core/pool-credential-store.js";import{isManagedProvider as m,readSubscriptionPool as C}from"../../core/subscription-state.js";import{piAgentDir as P}from"../../core/runtime/package-health.js";async function U(){const e=P();return $.create({credentials:new k(u(e,"auth.json")),modelsPath:u(e,"models.json")})}o(U,"openProviderLoginRuntime");async function Y(e,r,i,t={}){const c=e.getProvider(r)?.auth.oauth;if(c===void 0)throw new f({error:"provider_login_unavailable",message:`provider ${r} has no OAuth login flow`,next:"Configure an API key through `crtr sys setup`, then run `crtr sys connect` again."});const g=m(r)?Math.max(0,...C(r).map(a=>a.lastRateLimitedAt)):void 0,n=await c.login(i);if(t.beforeInstall?.()===!1)return!1;if(m(r)){const a=typeof n.accountId=="string"&&n.accountId.trim()?n.accountId.trim():void 0;return await d().installCredential(r,{kind:"managed_account",...t.label?{label:t.label}:{auto_label:!0},...a?{account_id:a}:{},access_token:n.access,refresh_token:n.refresh,expires_at:new Date(n.expires).toISOString(),last_rate_limited_at:g}),!0}const{type:I,access:h,refresh:w,expires:_,...l}=n;return await d().installCredential(r,{kind:"oauth",access_token:h,refresh_token:w,expires_at:new Date(_).toISOString(),...Object.keys(l).length>0?{extra:l}:{}}),!0}o(Y,"loginProvider");function p(e){const r=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",i=process.platform==="win32"?["/c","start","",e]:[e];b(r,i,{timeout:5e3},()=>{})}o(p,"openBrowser");function A(e){switch(e.type){case"auth_url":process.stderr.write(`${e.instructions}
|
|
2
|
+
${e.url}
|
|
3
|
+
`),p(e.url);break;case"device_code":process.stderr.write(`${e.verificationUri}
|
|
4
|
+
Code: ${e.userCode}
|
|
5
|
+
`),p(e.verificationUri);break;case"info":case"progress":process.stderr.write(`${e.message}
|
|
6
|
+
`);break}}o(A,"notify");async function S(e){if(!process.stdin.isTTY||!process.stderr.isTTY)throw new f({error:"interactive_login_required",message:"provider login requires an interactive terminal",next:"Run `crtr sys connect` from an interactive terminal, or configure the provider credential through `crtr sys setup` first."});const r=e.type==="select"?`${e.message}
|
|
7
|
+
${e.options.map((t,s)=>`${s+1}. ${t.label}`).join(`
|
|
8
|
+
`)}
|
|
9
|
+
Select: `:`${e.message}${e.placeholder?` (${e.placeholder})`:""}: `,i=x({input:process.stdin,output:process.stderr});try{const t=await i.question(r,{signal:e.signal});if(e.type!=="select")return t;const s=e.options[Number.parseInt(t,10)-1]?.id;if(s===void 0)throw new Error("Login cancelled");return s}finally{i.close()}}o(S,"promptTerminal");function B(){return{signal:new AbortController().signal,notify:A,prompt:S}}o(B,"terminalAuthInteraction");export{Y as loginProvider,U as openProviderLoginRuntime,B as terminalAuthInteraction};
|
|
@@ -102,4 +102,8 @@ export declare function renderSetupStaticInstructions(state: SetupInitialState):
|
|
|
102
102
|
/** Refresh crouter-owned tmux bindings. Setup must never fail because tmux is unavailable. */
|
|
103
103
|
export declare function reconcileInboxFrontDoor(): Promise<void>;
|
|
104
104
|
export declare function formatSetupExecutionSummary(summary: SetupExecutionSummary): string;
|
|
105
|
+
/** The non-interactive first command reconciles only crouter-owned packages.
|
|
106
|
+
* Individual failures are reported by the CLI but never prevent later work or
|
|
107
|
+
* writing the once-per-machine marker. */
|
|
108
|
+
export declare function reconcileFirstRun(onFailure: (item: string, error: unknown) => void): Promise<string>;
|
|
105
109
|
export declare function executeSetupSubmission(submission: SetupSubmission): Promise<SetupExecutionSummary>;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
var T=Object.defineProperty;var s=(e,t)=>T(e,"name",{value:t,configurable:!0});import{spawnSync as g}from"node:child_process";import{homedir as m}from"node:os";import{basename as N,dirname as _,join as v,resolve as L}from"node:path";import{isDeepStrictEqual as f}from"node:util";import{BINDING_CATALOG as j,normalizeGesture as M,splitGestureExpression as K,inboxShortcut as F,resolveKeybindings as k}from"../../core/keybindings/index.js";import{builtinPiPackageDir as b}from"../../core/scope.js";import{readJsonIfExists as G}from"../../core/fs-utils.js";import{resolveBundledPiCliPath as B}from"../../core/runtime/pi-cli.js";import{installTmuxBindings as U}from"../../core/runtime/tmux-chrome.js";import{normalizeTmuxPassthrough as z}from"../../core/config.js";import{detectNerdFont as Y,nerdFontInstallCommand as q,NERD_FONT_SELECT_HINT as H}from"../../core/runtime/nerd-font.js";import{ensureRenderer as W,isRendererReady as w}from"../../core/termrender/termrender.js";import{installFromMarketplace as V}from"../pkg/plugin-manage.js";import{addMarketplace as S}from"../pkg/market-manage.js";import{findPluginByName as J,findMarketplaceByName as A}from"../../core/resolver.js";import{expandTilde as X}from"../../core/fs-utils.js";const C="crouter-official-marketplace",R="https://github.com/crouton-labs/crouter-official-marketplace.git",$="main";function E(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}s(E,"isKeybindingsObject");function h(e){return E(e)?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,Array.isArray(n)?[...n]:n])):{}}s(h,"cloneKeybindings");class Ee{static{s(this,"SetupKeybindingsDraft")}expected;baseline;current;baselineInvalidRoot;invalidRoot;resolutionCache;expectedPassthrough;baselinePassthrough;currentPassthrough;constructor(t,n=void 0){this.expected=t,this.baseline=h(t),this.current=h(t),this.baselineInvalidRoot=t!==void 0&&!E(t)?t:void 0,this.invalidRoot=this.baselineInvalidRoot,this.expectedPassthrough=n,this.baselinePassthrough=z(n),this.currentPassthrough=[...this.baselinePassthrough]}get dirty(){return!f(this.current,this.baseline)||!f(this.invalidRoot,this.baselineInvalidRoot)||!f(this.currentPassthrough,this.baselinePassthrough)}overrides(){return h(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=k(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 i=j.find(u=>u.id===t);let a;try{a=n.trim()===""?[]:K(n).map(u=>M(u.trim(),i))}catch(u){return{ok:!1,diagnostics:[{severity:"error",code:"invalid-override",bindingId:t,offendingValue:n,message:u instanceof Error?u.message:String(u)}]}}const r=[...new Set(a)],c={...this.current};delete c[t];const d=[...k(c).gestures(t)],o=f(r,d)?c:{...this.current,[t]:r},l=k(o),p=l.diagnostics.filter(u=>u.bindingId===t||u.relatedBindingIds?.includes(t));return p.length>0?{ok:!1,diagnostics:p}:(this.current=o,this.resolutionCache=void 0,{ok:!0,diagnostics:l.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=h(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:h(t),this.baseline=h(t),this.current=h(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 Q={"official-marketplace":{name:C,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.1.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."},"search-plugin":{name:"search",shortDescription:"crtr plugin mounting `crtr search` \u2014 public-web discovery, grounded answers, and content extraction.",longDescription:"Installs the `search` plugin from the official crtr marketplace. It mounts `crtr search` and calls Exa directly, resolving its own API key from EXA_API_KEY or ~/.crouter/exa.key \u2014 crouter never handles that credential. 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."}},P={"official-marketplace":{kind:"marketplace-source",source:R},"crtr-extensions":{kind:"local-path",source:b("pi-crtr-extensions")},"oauth-adapter":{kind:"npm",source:"npm:pi-claude-oauth-adapter@0.1.2"},"capture-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/capture"},"search-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/search"}},Z={"official-marketplace":[],"crtr-extensions":["pi-crtr-extensions"],"oauth-adapter":[],"capture-plugin":[],"search-plugin":[]};function D(e=b){return["official-marketplace","crtr-extensions","oauth-adapter","capture-plugin","search-plugin"].map(t=>{const n=Q[t],i=P[t].kind==="local-path"?e(n.name):P[t].source;return{id:t,name:n.name,sourceKind:P[t].kind,installSource:i,shortDescription:n.shortDescription,longDescription:n.longDescription}})}s(D,"buildSetupManifest");function De(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}`}}s(De,"packagePlanAction");function ee(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}s(ee,"npmPackageName");function O(e,t,n){const i=ee(e);if(i!==null)return`npm:${i}`;const a=X(e,n);return L(t,a)}s(O,"normalizePackageIdentity");function x(e){return`package:${e}`}s(x,"packageNameIdentity");function te(e){return e.startsWith("npm:")?[]:[x(N(e))]}s(te,"localPackageNameIdentities");function ne(e,t,n){const i=[O(e.installSource,t,n)];if(e.sourceKind==="local-path"){i.push(x(e.name));for(const a of Z[e.id])i.push(x(a))}return i}s(ne,"manifestIdentities");function re(e,t=D(),n=v(m(),".pi","agent","settings.json"),i=m()){const a=_(n),r=new Set,c=new Map;for(const o of t)for(const l of ne(o,a,i))c.set(l,o.id);const d=e&&typeof e=="object"?e.packages:void 0;if(!Array.isArray(d))return r;for(const o of d){const l=typeof o=="string"?o:o&&typeof o=="object"&&typeof o.source=="string"?o.source:null;if(l===null)continue;const p=[O(l,a,i),...te(l)];for(const u of p){const I=c.get(u);I!==void 0&&r.add(I)}}return r}s(re,"detectInstalledSetupPackageIds");function y(e){return g("sh",["-lc",`command -v ${e} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}s(y,"detectCommandOnPath");function ie(e=process.platform,t=y){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"}}s(ie,"detectPackageManager");function se(e,t,n=y){if(e==="nerd-font"?Y().installed:n(e))return{tool:e,installed:!0,command:null,hint:"already installed",autoInstall:!1};if(e==="nerd-font"){const r=q(t);return{tool:e,installed:!1,command:r.command,hint:`${r.hint} \u2014 ${H}`,autoInstall:r.autoInstall}}const a=e==="nvim"?"neovim":"tmux";switch(t){case"brew":return{tool:e,installed:!1,command:`brew install ${a}`,hint:"Homebrew install",autoInstall:!0};case"apt-get":return{tool:e,installed:!1,command:`sudo apt-get update && sudo apt-get install -y ${a}`,hint:"apt-get install",autoInstall:!0};case"dnf":return{tool:e,installed:!1,command:`sudo dnf install -y ${a}`,hint:"dnf install",autoInstall:!0};case"pacman":return{tool:e,installed:!1,command:`sudo pacman -S --noconfirm ${a}`,hint:"pacman install",autoInstall:!0};default:return{tool:e,installed:!1,command:null,hint:`manual install required for ${e}`,autoInstall:!1}}}s(se,"buildSystemInstallPlan");function ae(e=m()){return v(e,".pi","agent","settings.json")}s(ae,"resolveSettingsPath");function Oe(e={}){const t=e.homeDir??m(),n=e.settingsPath??ae(t),i=e.manifest??D(b),a=G(n),r=re(a,i,n,t);for(const l of de(i))r.add(l);const c=e.commandExists??y,d=e.packageManager??ie(process.platform,c),o=["tmux","nvim","nerd-font"].map(l=>se(l,d,c));return{manifest:i,installedPackageIds:r,systemPlans:o}}s(Oe,"buildSetupInitialState");function Te(e){const t=e.manifest.map(i=>{const a=e.installedPackageIds.has(i.id)?"installed":"not installed";return`- ${i.name} \u2014 ${i.shortDescription} (${a})`}),n=e.systemPlans.map(i=>i.installed?`- ${i.tool}: \u2713 installed`:i.command?`- ${i.tool}: ${i.command}`:`- ${i.tool}: ${i.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 (${F()??"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 W=Object.defineProperty;var a=(e,t)=>W(e,"name",{value:t,configurable:!0});import{spawn as q,spawnSync as H}from"node:child_process";import{homedir as g}from"node:os";import{basename as V,dirname as J,join as S,resolve as X}from"node:path";import{isDeepStrictEqual as k}from"node:util";import{BINDING_CATALOG as Q,normalizeGesture as Z,splitGestureExpression as ee,inboxShortcut as te,resolveKeybindings as P}from"../../core/keybindings/index.js";import{builtinPiPackageDir as b}from"../../core/scope.js";import{readJsonIfExists as ne,readJsonOrNull as re}from"../../core/fs-utils.js";import{resolveBundledPiCliPath as ie}from"../../core/runtime/pi-cli.js";import{crtrVersion as se}from"../../shared/crtr-version.js";import{installTmuxBindings as ae}from"../../core/runtime/tmux-chrome.js";import{normalizeTmuxPassthrough as oe}from"../../core/config.js";import{detectNerdFont as ce,nerdFontInstallCommand as ue,createCurlPercentReader as le,NERD_FONT_MANUAL_LINUX_COMMAND as de,NERD_FONT_SELECT_HINT as pe}from"../../core/runtime/nerd-font.js";import{startProgressBar as he}from"../../core/runtime/progress-bar.js";import{ensureRenderer as fe,isRendererReady as A}from"../../core/termrender/termrender.js";import{installFromMarketplace as me}from"../pkg/plugin-manage.js";import{addMarketplace as $}from"../pkg/market-manage.js";import{findPluginByName as ge,findMarketplaceByName as E}from"../../core/resolver.js";import{expandTilde as ke}from"../../core/fs-utils.js";const R="crouter-official-marketplace",D="https://github.com/crouton-labs/crouter-official-marketplace.git",O="main";function T(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}a(T,"isKeybindingsObject");function h(e){return T(e)?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,Array.isArray(n)?[...n]:n])):{}}a(h,"cloneKeybindings");class Je{static{a(this,"SetupKeybindingsDraft")}expected;baseline;current;baselineInvalidRoot;invalidRoot;resolutionCache;expectedPassthrough;baselinePassthrough;currentPassthrough;constructor(t,n=void 0){this.expected=t,this.baseline=h(t),this.current=h(t),this.baselineInvalidRoot=t!==void 0&&!T(t)?t:void 0,this.invalidRoot=this.baselineInvalidRoot,this.expectedPassthrough=n,this.baselinePassthrough=oe(n),this.currentPassthrough=[...this.baselinePassthrough]}get dirty(){return!k(this.current,this.baseline)||!k(this.invalidRoot,this.baselineInvalidRoot)||!k(this.currentPassthrough,this.baselinePassthrough)}overrides(){return h(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=P(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=Q.find(p=>p.id===t);let o;try{o=n.trim()===""?[]:ee(n).map(p=>Z(p.trim(),r))}catch(p){return{ok:!1,diagnostics:[{severity:"error",code:"invalid-override",bindingId:t,offendingValue:n,message:p instanceof Error?p.message:String(p)}]}}const i=[...new Set(o)],u={...this.current};delete u[t];const c=[...P(u).gestures(t)],l=k(i,c)?u:{...this.current,[t]:i},s=P(l),d=s.diagnostics.filter(p=>p.bindingId===t||p.relatedBindingIds?.includes(t));return d.length>0?{ok:!1,diagnostics:d}:(this.current=l,this.resolutionCache=void 0,{ok:!0,diagnostics:s.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=h(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:h(t),this.baseline=h(t),this.current=h(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 be={"official-marketplace":{name:R,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.1.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."},"search-plugin":{name:"search",shortDescription:"crtr plugin mounting `crtr search` \u2014 public-web discovery, grounded answers, and content extraction.",longDescription:"Installs the `search` plugin from the official crtr marketplace. It mounts `crtr search` and calls Exa directly, resolving its own API key from EXA_API_KEY or ~/.crouter/exa.key \u2014 crouter never handles that credential. 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."}},v={"official-marketplace":{kind:"marketplace-source",source:D},"crtr-extensions":{kind:"local-path",source:b("pi-crtr-extensions")},"oauth-adapter":{kind:"npm",source:"npm:pi-claude-oauth-adapter@0.1.2"},"capture-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/capture"},"search-plugin":{kind:"marketplace-plugin",source:"crouter-official-marketplace/search"}},we={"official-marketplace":[],"crtr-extensions":["pi-crtr-extensions"],"oauth-adapter":[],"capture-plugin":[],"search-plugin":[]};function I(e=b){return["official-marketplace","crtr-extensions","oauth-adapter","capture-plugin","search-plugin"].map(t=>{const n=be[t],r=v[t].kind==="local-path"?e(n.name):v[t].source;return{id:t,name:n.name,sourceKind:v[t].kind,installSource:r,shortDescription:n.shortDescription,longDescription:n.longDescription}})}a(I,"buildSetupManifest");function Xe(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(Xe,"packagePlanAction");function Pe(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(Pe,"npmPackageName");function N(e,t,n){const r=Pe(e);if(r!==null)return`npm:${r}`;const o=ke(e,n);return X(t,o)}a(N,"normalizePackageIdentity");function x(e){return`package:${e}`}a(x,"packageNameIdentity");function ve(e){return e.startsWith("npm:")?[]:[x(V(e))]}a(ve,"localPackageNameIdentities");function Ie(e,t,n){const r=[N(e.installSource,t,n)];if(e.sourceKind==="local-path"){r.push(x(e.name));for(const o of we[e.id])r.push(x(o))}return r}a(Ie,"manifestIdentities");function L(e,t=I(),n=S(g(),".pi","agent","settings.json"),r=g()){const o=J(n),i=new Set,u=new Map;for(const l of t)for(const s of Ie(l,o,r))u.set(s,l.id);const c=e&&typeof e=="object"?e.packages:void 0;if(!Array.isArray(c))return i;for(const l of c){const s=typeof l=="string"?l:l&&typeof l=="object"&&typeof l.source=="string"?l.source:null;if(s===null)continue;const d=[N(s,o,r),...ve(s)];for(const p of d){const f=u.get(p);f!==void 0&&i.add(f)}}return i}a(L,"detectInstalledSetupPackageIds");function m(e){return H("sh",["-lc",`command -v ${e} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}a(m,"detectCommandOnPath");function xe(e=process.platform,t=m){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(xe,"detectPackageManager");function ye(e,t,n=m){if(e==="nerd-font"?ce().installed:n(e))return{tool:e,installed:!0,command:null,hint:"already installed",autoInstall:!1};if(e==="nerd-font"){const i=ue(t);return{tool:e,installed:!1,command:i.command,hint:`${i.hint} \u2014 ${pe}`,autoInstall:i.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(ye,"buildSystemInstallPlan");function _(e=g()){return S(e,".pi","agent","settings.json")}a(_,"resolveSettingsPath");function Qe(e={}){const t=e.homeDir??g(),n=e.settingsPath??_(t),r=e.manifest??I(b),o=ne(n),i=L(o,r,n,t);for(const s of G(r))i.add(s);const u=e.commandExists??m,c=e.packageManager??xe(process.platform,u),l=["tmux","nvim","nerd-font"].map(s=>ye(s,c,u));return{manifest:r,installedPackageIds:i,systemPlans:l}}a(Qe,"buildSetupInitialState");function Ze(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 (${te()??"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
|
-
`}
|
|
4
|
-
`)
|
|
3
|
+
`}a(Ze,"renderSetupStaticInstructions");class w extends Error{static{a(this,"SetupCommandError")}output;constructor(t,n){super(t),this.output=n}}function et(e){return`${e.stdout?.toString()??""}${e.stderr?.toString()??""}`}a(et,"commandOutput");function M(e,t,n){return new Promise((r,o)=>{const i=q(e,t,{stdio:["ignore","pipe","pipe"]});let u="";i.stdout.setEncoding("utf8"),i.stderr.setEncoding("utf8"),i.stdout.on("data",c=>{u+=c}),i.stderr.on("data",c=>{u+=c,n?.(c)}),i.on("error",c=>o(new w(`${e} failed: ${c.message}`,u))),i.on("close",c=>{c===0?r():o(new w(`${e} failed with status ${c??"unknown"}`,u))})})}a(M,"runCapturedCommand");function y(e,t){return M("sh",["-lc",e],t)}a(y,"runShellCommand");function Ce(e,t=ie()){return M(process.execPath,[t,"install",e])}a(Ce,"installPiPackage");async function K(){m("capture")||await y("npm install -g @crouton-kit/capture")}a(K,"installCaptureCliIfMissing");function F(e){return e instanceof w?e.output:void 0}a(F,"capturedFailureOutput");function Se(e){const n=F(e)?.split(/\r?\n/).find(r=>r.trim()!=="");return n!==void 0?n.trim():e instanceof Error?e.message:String(e)}a(Se,"firstFailureLine");function Ae(e){const t=F(e);t===void 0||t.length===0||process.stderr.write(t.endsWith(`
|
|
4
|
+
`)?t:`${t}
|
|
5
|
+
`)}a(Ae,"printCapturedFailure");class $e{static{a(this,"SetupProgress")}interactive;bar=null;constructor(t=!!process.stdout.isTTY){this.interactive=t}start(t){return this.interactive?(this.bar=he(`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
|
+
`)}async fail(t,n){await this.clear(),this.interactive&&process.stdout.write(`\r\x1B[2K\u2717 ${t} \u2014 ${Se(n)}
|
|
7
|
+
`),Ae(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 j(e,t){return A()?"current":(e?.(),fe(t),A()?"provisioned":"unavailable")}a(j,"ensureManagedRenderer");async function Ee(){try{ae()}catch{}}a(Ee,"reconcileInboxFrontDoor");function B(e,t){return t.length===0?[`${e}: none`]:[`${e}:`,...t.map(n=>` - ${n}`)]}a(B,"summarySection");function tt(e){return["crtr sys setup complete",...B("installed",e.installed),...B("skipped",e.skipped)].join(`
|
|
5
8
|
`)+`
|
|
6
|
-
`}
|
|
9
|
+
`}a(tt,"formatSetupExecutionSummary");function G(e,t){const n=[];for(const r of e)try{r.sourceKind==="marketplace-source"?E(r.name,t)!==null&&n.push(r.id):r.sourceKind==="marketplace-plugin"&&ge(r.name,t)!==null&&n.push(r.id)}catch{}return n}a(G,"detectInstalledMarketplaceEntries");function Re(e,t="user"){if(E(e,t)!==null)return null;if(e!==R)throw new Error(`marketplace not registered: ${e}`);return $({url:D,ref:O,scope:t}),e}a(Re,"ensureMarketplaceRegistered");async function U(e){if(e.sourceKind==="marketplace-source")return $({url:e.installSource,ref:O,scope:"user"}),[e.name];if(e.sourceKind==="marketplace-plugin"){const[t,n]=e.installSource.split("/"),r=Re(t);return await me({marketplace:t,plugin:n},"user"),e.id==="capture-plugin"&&await K(),r===null?[e.name]:[r,e.name]}return await Ce(e.installSource),[e.name]}a(U,"installSetupPackage");async function nt(e){const t=I(b),n=L(re(_()),t);for(const i of G(t,"user"))n.add(i);const r=new Map(t.map(i=>[i.id,i])),o=process.stdin.isTTY&&process.stdout.isTTY?["crtr-extensions","official-marketplace","oauth-adapter","capture-plugin","search-plugin"]:["crtr-extensions","official-marketplace","capture-plugin","search-plugin"];for(const i of o){const u=r.get(i);if(!n.has(i))try{await U(u)}catch(c){e(u.name,c)}}try{j(void 0,()=>{})==="unavailable"&&e("termrender renderer",new Error("unavailable \u2014 install uv"))}catch(i){e("termrender renderer",i)}return se()}a(nt,"reconcileFirstRun");async function rt(e){const t=[],n=[],r=[],o=[],i=new $e;for(const s of e.packages){if(e.installedPackageIds.has(s.id)){if(n.push(`${s.name} (already installed)`),s.id==="capture-plugin"&&!m("capture"))try{await i.run("Capture CLI",()=>K()),t.push("Capture CLI")}catch(d){n.push(`Capture CLI (${d instanceof Error?d.message:String(d)})`)}continue}try{t.push(...await i.run(s.name,()=>U(s)))}catch(d){if(s.sourceKind!=="marketplace-source"&&s.sourceKind!=="marketplace-plugin")throw d;n.push(`${s.name} (${d instanceof Error?d.message:String(d)})`)}}for(const s of e.systemDeps){if(s.installed){o.push(`${s.tool} (already installed)`);continue}if(!s.autoInstall||!s.command){o.push(`${s.tool} (${s.hint})`);continue}const d=s.command,p=d===de;await i.run(s.tool,f=>{if(!p||f===null)return y(d);const Y=le();return y(d,z=>{const C=Y(z);C!==null&&f.setFraction(C/100)})}),r.push(s.tool)}const u=[];let c=!1,l;try{l=j(()=>{c=!0,i.start("termrender renderer")},s=>u.push(s))}catch(s){throw c&&await i.fail("termrender renderer",s),s}switch(l){case"provisioned":c&&await i.succeed("termrender renderer"),t.push("termrender renderer");break;case"current":n.push("termrender renderer (current)");break;case"unavailable":{const s=new w("unavailable \u2014 install uv",u.join(""));c&&await i.fail("termrender renderer",s),n.push("termrender renderer (unavailable \u2014 install uv)");break}}return await Ee(),{installed:[...t,...r],skipped:[...n,...o]}}a(rt,"executeSetupSubmission");export{R as OFFICIAL_MARKETPLACE_NAME,O as OFFICIAL_MARKETPLACE_REF,D as OFFICIAL_MARKETPLACE_URL,Je as SetupKeybindingsDraft,Qe as buildSetupInitialState,I as buildSetupManifest,ye as buildSystemInstallPlan,m as detectCommandOnPath,L as detectInstalledSetupPackageIds,xe as detectPackageManager,rt as executeSetupSubmission,tt as formatSetupExecutionSummary,N as normalizePackageIdentity,Xe as packagePlanAction,nt as reconcileFirstRun,Ee as reconcileInboxFrontDoor,Ze as renderSetupStaticInstructions,_ as resolveSettingsPath};
|