@wular/coder 0.2.12 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sdk.js ADDED
@@ -0,0 +1 @@
1
+ import{C as e,F as t,v as n,x as r}from"./app-server-Dwd7IcQH.js";import{E as i,I as a,M as o,R as s,xt as c,yt as l,z as u}from"./runtime-DRtQOCSz.js";import{C as d,T as f,_ as p,b as m,c as h,d as g,f as _,g as v,i as y,l as b,m as x,n as S,o as C,s as w,u as T,y as E}from"./upgrade-DFAtgbtD.js";import D from"./flow/index.js";import O from"node:process";import k from"node:path";function A(e){return e?k.resolve(e):O.cwd()}function j(t,n){let r=e(t,n);if(!r)throw Error(n?`No task found for "${n}".`:`No tasks found for this workspace.`);return r}const M={async run(e,t={}){let{taskId:n}=await a({prompt:e,cwd:A(t.cwd),agent:t.agent,model:t.model,effort:t.effort,permissions:t.permissions,name:t.name,system:t.system,resume:t.resume});return{taskId:n}},async result(e,t={}){let n=A(t.cwd),r=e??j(n).id;return t.wait?u(n,r,{tail:t.tail}):s(n,r,{tail:t.tail})},list(e={}){return m(A(e.cwd),e).jobs},async steer(e,t,n={}){let r=A(n.cwd);return d(r,j(r,e),t)},async stop(e,t={}){let n=A(t.cwd);return i(n,j(n,e))},archive(e,t={}){let r=A(t.cwd);return n(r,j(r,e)),{taskId:e,archived:!0}},delete(e,t={}){let n=A(t.cwd),i=j(n,e);if(i.status===`running`)throw Error(`Task ${i.id} is still running.`);E(i);let a=r(n,i.id);return{taskId:i.id,deleted:a}},approve(e,n,r={}){let i=A(r.cwd),a=j(i,e),o=r.deny?`decline`:`accept`;return l(t(i,a.id),n,o),{taskId:a.id,approvalId:n,decision:o}},approvals(e,n={}){let r=A(n.cwd);return c(t(r,(e?j(r,e):j(r)).id))},stream(e,t={}){let n=A(t.cwd);return f(n,e??j(n).id,{tail:t.tail})}},N={add:(e,t)=>C(A(t.cwd),e,t),update:(e,t)=>_(A(t.cwd),e,t),remove:(e,t={})=>b(A(t.cwd),e,t),list:(e={})=>h(A(e.cwd)),alias:(e,t,n={})=>w(A(n.cwd),e,t,n),unalias:(e,t={})=>g(A(t.cwd),e,t),disable:(e,t={})=>T(A(t.cwd),e,!0,t),enable:(e,t={})=>T(A(t.cwd),e,!1,t)},P={get:(e,t={})=>v(A(t.cwd),e),set:(e,t,n={})=>p(A(n.cwd),e,t,n)};function F(e=[],t={}){return x(A(t.cwd),{claude:e.includes(`claude`),codex:e.includes(`codex`),agents:e.includes(`agents`)})}function I(e={}){return S(e)}function L(e){return y(e)}var R={task:M,flow:D,model:N,config:P,setupHost:F,upgrade:I,docs:L};export{o as CoderError,P as config,R as default,L as docs,D as flow,N as model,F as setupHost,M as task,I as upgrade};
@@ -0,0 +1,27 @@
1
+ import{B as e,D as t,E as n,F as r,H as i,I as a,L as o,N as s,O as c,R as l,S as u,V as d,_ as f,b as p,c as m,d as h,f as g,h as _,j as v,k as y,l as b,m as x,o as S,p as ee,s as C,u as w,x as te,z as T}from"./app-server-Dwd7IcQH.js";import{$ as ne,$t as E,B as re,Bt as D,Ct as ie,Dt as ae,Et as oe,F as se,Ft as ce,G as le,Gt as ue,H as de,I as fe,It as O,J as pe,Jt as me,K as he,L as ge,Lt as _e,M as ve,N as ye,Nt as be,Pt as k,Q as A,Qt as j,Rt as xe,St as Se,Tt as Ce,U as we,Ut as Te,V as Ee,W as De,Wt as M,X as N,Xt as Oe,Y as ke,Yt as P,Z as Ae,Zt as F,_t as je,at as I,bt as Me,ct as Ne,dt as L,en as Pe,et as Fe,ft as Ie,g as Le,gt as Re,ht as R,it as z,k as ze,lt as Be,mt as Ve,nt as B,ot as V,pt as He,q as Ue,qt as H,rn as We,rt as U,st as Ge,tt as W,ut as Ke,wt as qe,z as Je,zt as Ye}from"./runtime-DRtQOCSz.js";import G from"node:process";import K from"node:fs";import q from"node:path";import{spawnSync as Xe}from"node:child_process";import Ze from"node:os";import{fileURLToPath as Qe}from"node:url";import*as J from"zod/mini";import $e from"node:crypto";import et from"node:http";let tt=0;function nt(e){let t=[];typeof e.instructions==`string`&&e.instructions&&t.push({role:`system`,content:e.instructions});let n=Array.isArray(e.input)?e.input:[];for(let e of n)switch(e?.type){case`message`:{let n=Array.isArray(e.content)?e.content.map(e=>e?.text??``).join(``):String(e.content??``);t.push({role:e.role===`developer`?`system`:e.role,content:n});break}case`function_call`:{let n={id:e.call_id,type:`function`,function:{name:e.name,arguments:e.arguments??`{}`}},r=t.at(-1);r?.role===`assistant`&&r.tool_calls?r.tool_calls.push(n):t.push({role:`assistant`,content:null,tool_calls:[n]});break}case`function_call_output`:{let n=e.output;t.push({role:`tool`,tool_call_id:e.call_id,content:typeof n==`string`?n:JSON.stringify(n??``)});break}default:break}return t}function rt(e){let t=(Array.isArray(e.tools)?e.tools:[]).filter(e=>e?.type===`function`).map(e=>({type:`function`,function:{name:e.name,description:e.description??``,parameters:e.parameters??{type:`object`,properties:{}}}})),n=e.text?.format;return{model:e.model,messages:nt(e),...t.length?{tools:t,tool_choice:e.tool_choice??`auto`}:{},...typeof e.parallel_tool_calls==`boolean`?{parallel_tool_calls:e.parallel_tool_calls}:{},...n?.type===`json_schema`?{response_format:{type:`json_schema`,json_schema:{name:n.name,schema:n.schema,strict:n.strict??!1}}}:{},stream:!0,stream_options:{include_usage:!0}}}var it=class{send;model;responseId=`resp_bridge_${++tt}`;text=``;textOpen=!1;toolCalls=new Map;usage=null;buffer=``;constructor(e,t){this.model=e,this.send=t,this.send({type:`response.created`,response:{id:this.responseId,object:`response`,status:`in_progress`,model:e,output:[]}})}push(e){this.buffer+=e;let t=this.buffer.split(`
2
+ `);this.buffer=t.pop()??``;for(let e of t){let t=e.trim();if(!t.startsWith(`data:`))continue;let n=t.slice(5).trim();if(!n||n===`[DONE]`)continue;let r;try{r=JSON.parse(n)}catch{continue}r.usage&&(this.usage=r.usage);let i=r.choices?.[0]?.delta;if(i){typeof i.content==`string`&&i.content&&(this.textOpen||(this.textOpen=!0,this.send({type:`response.output_item.added`,output_index:0,item:{type:`message`,id:`msg_bridge_0`,status:`in_progress`,role:`assistant`,content:[]}})),this.text+=i.content,this.send({type:`response.output_text.delta`,item_id:`msg_bridge_0`,output_index:0,content_index:0,delta:i.content}));for(let e of i.tool_calls??[]){let t=this.toolCalls.get(e.index)??{id:``,name:``,args:``};e.id&&(t.id=e.id),e.function?.name&&(t.name+=e.function.name),e.function?.arguments&&(t.args+=e.function.arguments),this.toolCalls.set(e.index,t)}}}}finish(){let e=[];if(this.text){let t={type:`message`,id:`msg_bridge_0`,status:`completed`,role:`assistant`,content:[{type:`output_text`,text:this.text,annotations:[]}]};e.push(t),this.send({type:`response.output_item.done`,output_index:0,item:t})}let t=e.length;for(let n of this.toolCalls.values()){let r={type:`function_call`,id:`fc_bridge_${t}`,call_id:n.id||`call_bridge_${t}`,name:n.name,arguments:n.args||`{}`,status:`completed`};e.push(r),this.send({type:`response.output_item.done`,output_index:t,item:r}),t+=1}this.send({type:`response.completed`,response:{id:this.responseId,object:`response`,status:`completed`,model:this.model,output:e,usage:{input_tokens:this.usage?.prompt_tokens??0,input_tokens_details:{cached_tokens:this.usage?.prompt_tokens_details?.cached_tokens??0},output_tokens:this.usage?.completion_tokens??0,output_tokens_details:{reasoning_tokens:this.usage?.completion_tokens_details?.reasoning_tokens??0},total_tokens:this.usage?.total_tokens??0}}})}};async function at(e,t=`chat`){let n=t===`responses`,r=W(e.baseUrl,n?`responses`:`chat/completions`),i=null,a=e.envKey?G.env[e.envKey]:void 0;async function o(e){let t=i?[i]:r,n=null;for(let r of t)if(n=await fetch(r,{method:`POST`,headers:{"content-type":`application/json`,...a?{authorization:`Bearer ${a}`}:{}},body:e}),n.status!==404)return i=r,n;return n}let s=$e.randomBytes(16).toString(`hex`),c=et.createServer((e,t)=>{if(e.method!==`POST`||!e.url?.startsWith(`/${s}/`)||!e.url.replace(/\/+$/,``).endsWith(`/responses`)){t.writeHead(404,{"content-type":`application/json`}),t.end(JSON.stringify({error:{message:`coder chat bridge: only POST /responses is supported`}}));return}let r=``;e.on(`data`,e=>r+=e),e.on(`end`,async()=>{let e;try{e=JSON.parse(r)}catch{t.writeHead(400,{"content-type":`application/json`}),t.end(JSON.stringify({error:{message:`invalid JSON`}}));return}try{if(n){let e=await o(r);if(t.writeHead(e.status,{"content-type":e.headers.get(`content-type`)??`application/json`,"cache-control":`no-store`}),e.body)for await(let n of e.body)t.write(n);t.end();return}let i=await o(JSON.stringify(rt(e)));if(!i.ok||!i.body){let e=await i.text().catch(()=>``);t.writeHead(i.status,{"content-type":`application/json`}),t.end(e||JSON.stringify({error:{message:`upstream HTTP ${i.status}`}}));return}t.writeHead(200,{"content-type":`text/event-stream`,"cache-control":`no-store`});let a=new it(e.model,e=>t.write(`data: ${JSON.stringify(e)}\n\n`)),s=new TextDecoder;for await(let e of i.body)a.push(s.decode(e,{stream:!0}));a.finish(),t.end()}catch(e){let n=e instanceof Error?e.message:String(e);t.writeHead(502,{"content-type":`application/json`}),t.end(JSON.stringify({error:{message:`coder chat bridge: ${n}`}}))}})});await new Promise(e=>c.listen(0,`127.0.0.1`,e));let l=c.address();return{url:`http://127.0.0.1:${typeof l==`object`&&l?l.port:0}/${s}/v1`,close:()=>new Promise(e=>{c.close(()=>e()),c.closeAllConnections?.()})}}async function ot(e,t,n){let r=e.envKey?G.env[e.envKey]:void 0,i=!1;for(let a of W(e.baseUrl,t))try{let e=await fetch(a,{method:`POST`,headers:{"content-type":`application/json`,...r?{Authorization:`Bearer ${r}`}:{}},body:JSON.stringify(n),signal:AbortSignal.timeout(5e3)});if(i=!0,e.status!==404&&e.status!==405&&e.status!==501)return{baseUrl:a.slice(0,-(t.length+1))}}catch{}return{reachable:i}}async function st(e){let t=await ot(e,`responses`,{model:e.model,input:[],stream:!1});if(`baseUrl`in t)return{wireApi:`responses`,baseUrl:t.baseUrl};let n=await ot(e,`chat/completions`,{model:e.model,messages:[],stream:!1});return`baseUrl`in n?{wireApi:`chat`,baseUrl:n.baseUrl}:t.reachable||n.reachable?{wireApi:`chat`}:null}function ct(e){return e.resumeThreadId?e.prompt??``:`<system>
3
+ You are running inside a coder task: do the work yourself, directly.
4
+ Do not load the coder skill (recursive), and never dispatch through the \`coder\` CLI - nested dispatch is disabled.
5
+ When finished, provide a concise implementation result summary.
6
+ Your turn is one-shot: background processes will not re-invoke you after it ends, so verify synchronously before finishing.${e.system?`\n-------\n${e.system}`:``}
7
+ </system>
8
+
9
+ ${e.prompt??``}`}function lt(e,t){let n=0;return()=>{Date.now()-n>=1e4&&(n=Date.now(),o(e,t))}}function ut(e,t,{echo:n}){return r=>{let i=typeof r==`string`?{message:r}:r;f(e,t,i),n&&i.message&&G.stderr.write(`[coder] ${i.message}\n`)}}async function dt(e,t,{echo:n}){let i=a(e),o=r(e,t.id),s=I(e),c=ut(e,t.id,{echo:n}),l=Fe[t.permissions??`auto`]??Fe.auto,u=l.approvalPolicy===`never`?void 0:Me({workspaceRoot:i,jobDir:o,mode:l.approvalMode,escalationTimeoutMs:s.approvals.escalationTimeoutMs,allowedNetworkHosts:s.approvals.allowedNetworkHosts,onEvent:r=>{f(e,t.id,r),n&&r.message&&G.stderr.write(`[coder] ${r.message}\n`)}}),d=t.model?s.models?.[t.model]:void 0,p=d&&z(d)?d:void 0;if(p&&!p.wireApi&&t.model){let n=await st(p);n&&(p={...p,...n},s.models[t.model]=p,Ne(e,t.model,n),f(e,t.id,{kind:`info`,message:`detected wire api for ${t.model}: ${n.wireApi} @ ${p.baseUrl}`}))}if(p?.envKey&&!G.env[p.envKey])throw Error(`Missing environment variable: \`${p.envKey}\`.`);let m=p?await at(p,p.wireApi??`chat`):null,h=Ke(s,t.model,m??void 0);try{let n=await Re(e,{prompt:ct(t),model:h?.model??Be(t.model),modelProvider:h?.modelProvider??null,configOverrides:h?.configOverrides??null,effort:t.effort,sandbox:l.sandbox,approvalPolicy:l.approvalPolicy,onApprovalRequest:u,onHeartbeat:lt(e,t.id),resumeThreadId:t.resumeThreadId??null,onProgress:n=>{c(n);let r=typeof n==`object`?n.threadId:null,i=typeof n==`object`?n.turnId:null;(r||i)&&T(e,t.id,{status:`running`,...r?{threadId:r}:{},...i?{turnId:i}:{}})}}),r=n.status===0?`completed`:`failed`;return T(e,t.id,{status:r,threadId:n.threadId,turnId:n.turnId,completedAt:new Date().toISOString()}),Ce(q.join(o,`result.json`),n),n}finally{await m?.close()}}async function ft(e,t,{echo:n}){let i=r(e,t.id),a=ut(e,t.id,{echo:n}),o=await pe(e,{prompt:ct(t),model:t.model,effort:t.effort,permissions:t.permissions,resumeSessionId:t.resumeThreadId??null,onHeartbeat:lt(e,t.id),onProgress:n=>{a(n),n.threadId&&T(e,t.id,{status:`running`,threadId:n.threadId})}});return T(e,t.id,{status:o.status===0?`completed`:`failed`,threadId:o.threadId,completedAt:new Date().toISOString()}),Ce(q.join(i,`result.json`),o),o}function pt(e){return e.engine===`claude`?ft:dt}async function mt(e,t){for(let n=0;n<2;){let r=v(e,t);if(!r||r.status===`failed`||r.status===`cancelled`)return;let i=p(e,t);if(i.length===0){n+=1,await new Promise(e=>setTimeout(e,300));continue}n=0;for(let n of i){let i=T(e,t,{status:`running`,prompt:n,resumeThreadId:r.threadId??null});f(e,t,{message:`Running steered follow-up.`});try{await pt(i)(e,i,{echo:!1})}catch(n){let r=n instanceof Error?n.message:String(n);T(e,t,{status:`failed`,error:r}),f(e,t,{kind:`error`,message:r});return}}}}function ht(e,t){if(e instanceof ve)switch(e.code){case`chain-exhausted`:P(e.payload),G.exit(3);case`read-only-unavailable`:gt(e.message,t);break;case`startup-failed`:t.json&&(P({taskId:e.taskId,status:`failed`,detail:e.message}),G.exit(1)),O(`Task ${e.taskId} failed to start.${e.message?`\n${e.message}`:``}`,{hint:`See: coder task result ${e.taskId}`});break;default:O(e.message,e.hint?{hint:e.hint}:{})}throw e}function gt(e,t){let n=`read-only unavailable on this host: the OS sandbox failed to start (Linux/WSL2 needs bubblewrap + socat). Re-dispatch with --permissions auto (or workspace-write) if writes are acceptable, or install the sandbox deps.`;t.json?P({error:n,code:`read-only-unavailable`,detail:e}):G.stderr.write(`[coder] ${n}\n`),G.exit(1)}async function _t(e){G.env.CODER_WORKER&&O(`Nested dispatch refused: this shell is already inside a coder task. Do the work yourself, directly in this session - ignore any skill or instruction telling you to orchestrate via the coder CLI.`);let{options:t,positionals:n}=b(e,J.object({...S,agent:w,model:w,effort:w,permissions:w,resume:w,host:w,name:w,system:w,background:C,wait:C,"simulate-approval":C})),r=E(t),i=n.join(` `).trim();i||O(`Missing task text.`,{hint:[`Usage: coder run "<task text>"`,`Help: coder task run --help`]});let a={prompt:i,cwd:r,agent:t.agent,model:t.model,effort:t.effort,permissions:t.permissions,name:t.name,system:t.system,resume:t.resume,wait:t.wait,simulateApproval:t[`simulate-approval`],onFallback:({agent:e,detail:t,next:n})=>G.stderr.write(`[coder] ${e} failed to start (${t}); falling back to ${n}.\n`),onInstallNote:e=>G.stderr.write(`[coder] ${e}\n`)},o;try{o=await fe(a)}catch(e){ht(e,t)}if(t.wait)for(;;){let e=o.taskId,n=o.job,s=[` result: coder task result ${e}`,` steer: coder task steer ${e} "<follow-up>"`].join(`
10
+ `);t.json||(G.stderr.write(`${H.dim(`[coder]`)} task ${H.cyan(e)} started (${n.status}); waiting for it to finish — Ctrl-C to detach (it keeps running).\n`),G.stderr.write(`${H.dim(s)}\n`));let c=()=>{G.stderr.write(`\n${H.dim(`[coder] detached — task still running: coder task result ${e}`)}\n`),G.exit(130)};G.on(`SIGINT`,c);let l;try{l=await Je(r,e)}catch(n){throw G.off(`SIGINT`,c),n instanceof ve&&n.code===`approval-pending`&&Pe(e,n.approval,t.json),n}G.off(`SIGINT`,c);let u=l.job,d=l.result,f=d?.error?.message??``;if(u.status===`failed`&&ge(u.permissions??`auto`,f)&&gt(f,t),u.status===`failed`&&ye.test(f)){let e=I(r).chain,n=e[e.indexOf(o.agent)+1];n||(P(se(o.agent,f,u.permissions??`auto`,t.system,i)),G.exit(3)),G.stderr.write(`[coder] ${o.agent} failed to start (${f}); falling back to ${n}.\n`);try{o=await fe({...a,agent:n,model:void 0,effort:void 0,name:void 0,resume:void 0,simulateApproval:void 0})}catch(e){ht(e,t)}continue}if(t.json)P({taskId:e,status:u.status,finalMessage:d?.finalMessage,...f?{error:f}:{},tokens:d?.tokens??null,model:d?.model??u.model??null});else{G.stdout.write(`\n${d?.finalMessage||(f?`${H.red(`error:`)} ${f}`:`(no final message)`)}\n\n`);let t=d?.tokens?` tokens=${Te(d.tokens,d.model??u.model)}`:``;G.stderr.write(`${H.dim(`[coder] task=${e} status=${u.status}${t}`)}\n`)}G.exit(u.status===`completed`?0:1)}let s=o.taskId,c=o.job,l=[` result: coder task result ${s}`,` steer: coder task steer ${s} "<follow-up>"`].join(`
11
+ `);if(t.json){P({taskId:s,status:c.status,startupCheck:o.startupCheck,commands:{result:`coder task result ${s}`,steer:`coder task steer ${s} "<follow-up>"`,stop:`coder task stop ${s}`,watch:`coder task stream ${s}`}});return}G.stdout.write(`${H.dim(`[coder]`)} task ${H.cyan(s)} started in the background (${c.status}).\n`),G.stdout.write(`${H.dim(l)}\n`)}async function vt(e){let{options:t,positionals:n}=b(e,J.object({cwd:w})),i=E(t),a=n[0],o=v(i,a);o||O(`Worker: task ${a} not found.`),T(i,a,{status:`running`,pid:G.pid});try{if(o.simulateApproval){let e=await Se(r(i,a),{onEvent:e=>f(i,a,e)});if(f(i,a,{message:`simulated approval: ${e}`}),e===`decline`){T(i,a,{status:`cancelled`,completedAt:new Date().toISOString(),error:`simulated approval denied`});return}}await pt(o)(i,o,{echo:!1}),await mt(i,a)}catch(e){let t=e instanceof Error?e.message:String(e);T(i,a,{status:`failed`,error:t}),f(i,a,{kind:`error`,message:t}),G.exit(1)}}async function*yt(e,t,n={}){let i=new Set([`completed`,`failed`,`cancelled`]),a=v(e,t);if(!a)throw Error(`No task found for "${t}".`);let o=n.tail??1,c=ie(q.join(r(e,t),`log.jsonl`)),l=!0,u=()=>{let e=c();return l&&(l=!1,o!==`all`&&(e=o===0?[]:e.slice(-o))),e.map(e=>{try{return JSON.parse(e)}catch{return{message:e}}})};for(yield*u();!i.has(a.status);)await new Promise(e=>setTimeout(e,400)),a=s(e,v(e,t)??a),yield*u();yield*u()}async function bt(e){let{options:t,positionals:n}=b(e,J.object({...S,tail:h,trim:J.optional(J.union([J.literal(`none`),J.coerce.number().check(J.int(),J.positive())],{error:`expected a positive integer or "none"`}))}));F(n,1,`task stream`);let i=E(t),a=j(i,n[0]),o=t.tail,c=t.trim,l=c===void 0?300:c===`none`?2**53-1:c;if(!t.json){G.stdout.write(`${H.dim(`[coder] task ${a.id} ${a.status} — streaming (Ctrl-C to stop)`)}\n`);let e=[`${H.dim(`agent`.padEnd(8))} ${Ye(a)}`,...ue(a,H)];G.stdout.write(`${e.join(`
12
+ `)}\n`),a.prompt&&G.stdout.write(`${Oe(a.prompt,H).join(`
13
+ `)}\n`)}for await(let e of yt(i,a.id,{tail:o}))if(t.json){let t=e.message?{...e,message:We(e.message,l,{plain:!0})}:e;G.stdout.write(`${JSON.stringify(t)}\n`)}else{let t=e.message??e.kind;t&&G.stdout.write(`${H.dim(`[coder]`)} ${We(t,l)}\n`)}let u=s(i,v(i,a.id)??a),d=qe(q.join(r(i,a.id),`result.json`));if(t.json)P({taskId:a.id,status:u.status,result:d});else{let e=u.status===`completed`?`(no final message)`:`(task ${u.status})`;G.stdout.write(`\n${_e(d,u.error,e)}\n`);let t=d?.tokens?` tokens=${Te(d.tokens,d.model??u.model)}`:``;G.stderr.write(`${H.dim(`[coder] task=${a.id} status=${u.status}${t}`)}\n`)}G.exit(u.status===`completed`?0:1)}async function xt(t,n,r){if(n.status!==`running`)return null;if(n.engine!==`claude`&&(await je(t,{threadId:n.threadId,text:r})).steered)return{taskId:n.id,steered:`live`};let i=v(t,n.id)??n;return e.includes(i.status)?(u(t,i.id,r),{taskId:i.id,steered:`queued`}):null}async function St(e,t,n,r={}){if(!t.threadId)throw Error(`Task ${t.id} has no thread to steer yet (status: ${t.status}).`);return await xt(e,t,n)||{taskId:(await fe({prompt:n,cwd:t.cwd??e,resume:t.id,agent:t.agent,model:r.model??t.model??void 0,effort:r.effort??t.effort??void 0,permissions:r.permissions??t.permissions??void 0})).taskId,steered:`resumed`}}async function Ct(e){let{options:t,positionals:n}=b(e,J.object({...S,model:w,effort:w,permissions:w,background:C,wait:C})),r=E(t),[i,...a]=n,o=a.join(` `).trim();(!i||!o)&&O(`Missing task id or follow-up text.`,{hint:[`Usage: coder task steer <task-id> "<follow-up>" [--wait]`,`Help: coder task steer --help`]});let s=j(r,i);s.threadId||O(`Task ${s.id} has no thread to steer yet (status: ${s.status}).`,{hint:`Wait for it to start: coder task stream ${s.id}`});let c=await xt(r,s,o);if(c)return wt(r,c.taskId,{queued:c.steered===`queued`,options:t});await _t([o,`--resume`,s.id,`--cwd`,s.cwd??r,...s.agent?[`--agent`,s.agent]:[],...t.model?[`--model`,t.model]:s.model?[`--model`,s.model]:[],...t.effort?[`--effort`,t.effort]:s.effort?[`--effort`,s.effort]:[],...t.permissions?[`--permissions`,t.permissions]:s.permissions?[`--permissions`,s.permissions]:[],...t.wait?[`--wait`]:[]])}async function wt(e,t,{queued:n,options:i}){if(n){if(i.json){P({taskId:t,steered:`queued`});return}G.stdout.write(`${H.dim(`[coder]`)} follow-up queued for task ${H.cyan(t)}; it runs when the current turn finishes.\n`),i.wait&&G.stdout.write(`\n${H.dim(`[coder] --wait is not available for a queued follow-up; check: coder task result ${t}`)}\n`);return}if(!i.wait){if(i.json){P({taskId:t,steered:`live`});return}G.stdout.write(`${H.dim(`[coder]`)} steered follow-up into running task ${H.cyan(t)}.\n`),G.stdout.write(`\n${H.dim(` result: coder task result ${t}`)}\n`);return}let a=v(e,t),o=a?await l(e,a):null,s=qe(q.join(r(e,t),`result.json`));i.json?P({taskId:t,status:o?.status??null,finalMessage:s?.finalMessage??null}):(G.stdout.write(`\n${s?.finalMessage||`(no final message)`}\n\n`),G.stderr.write(`${H.dim(`[coder] task=${t} status=${o?.status??`unknown`}`)}\n`)),G.exitCode=o?.status===`completed`?0:1}function Tt(n,r={}){let i=r.limit===`all`?void 0:r.limit,o=e=>d.includes(e.status),s=[],l=c(n).filter(e=>!o(e)||be(e.completedAt??e.updatedAt??e.createdAt)<=6e5?!0:(y(n,e),s.push(e.id),!1));if(ze(n,s),r.archived?l=t(n):r.running?l=l.filter(t=>e.includes(t.status)):r.stopped&&(l=l.filter(o)),r.dir){let e=a(q.resolve(String(r.dir)));l=l.filter(t=>t.cwd&&a(t.cwd)===e)}if(!r.archived&&!r.running&&!r.stopped){let t=t=>t.status===`failed`?0:e.includes(t.status)?1:t.status===`completed`?3:2;l=[...l].sort((e,n)=>t(e)-t(n))}let u=i===void 0?0:Math.max(0,l.length-i);return i!==void 0&&(l=l.slice(0,i)),{jobs:l,clipped:u}}async function Et(r){let{options:i,positionals:a}=b(r,J.object({cwd:w,dir:w,limit:m,json:C,running:C,stopped:C,archived:C}));F(a,0,`task list`);let o=E(i),s=i.limit===`all`?void 0:i.limit,{jobs:c,clipped:l}=Tt(o,{running:i.running,stopped:i.stopped,archived:i.archived,dir:i.dir??i.cwd,limit:i.limit}),u=c.map(t=>({taskId:t.id,status:t.status,agent:t.agent??null,model:t.model??null,name:t.name??null,cwd:t.cwd??null,prompt:String(t.prompt??``).replace(/\s+/g,` `).trim().slice(0,80),updatedAt:t.updatedAt,archived:t.archived??!1,flowRunId:t.flowRunId??null,idleMs:e.includes(t.status)?be(n(o,t)):null}));if(i.json){P(u);return}if(!u.length){let e=i.archived?`archived tasks`:i.running?`running tasks`:i.stopped?`recently stopped tasks`:`recent tasks`;G.stdout.write(`No ${e}.\n`);let n=[];if(!i.archived){let e=t(o,{migrate:!1}).length;e&&n.push(`${e} archived: coder task list --archived [--limit N]`)}n.length&&G.stdout.write(`\n${D(n,H)}\n`);return}let d=H,f=e=>{let t=e.archived?` ${d.dim(`(archived)`)}`:``,n=e.name?e.name:d.light(e.prompt),r=e.idleMs!==null&&e.idleMs>=12e4?` ${(e.idleMs>6e5?d.red:d.dim)(`· idle ${xe(e.idleMs)}`)}`:``,i=e.agent===`custom`&&e.model?e.model:(e.agent??`-`)+(e.model?`/${e.model}`:``);G.stdout.write(`${d.cyan(e.taskId.padEnd(24))} ${me(e.status,10)} ${d.light(ce(i,14))} ${d.light(ce(e.cwd?q.basename(e.cwd):`-`,12))} ${n}${r}${t}\n`)};G.stdout.write(d.bold(d.light(`${`task-id`.padEnd(24)} ${`status`.padEnd(10)} ${`agent`.padEnd(14)} ${`cwd`.padEnd(12)} prompt\n`)));let p=new Map;for(let e of u)e.flowRunId&&(p.get(e.flowRunId)??p.set(e.flowRunId,[]).get(e.flowRunId)).push(e);let h=new Set,g=!1,_=!1;for(let e of u){if(!e.flowRunId){_&&G.stdout.write(`
14
+ `),f(e),g=!0,_=!1;continue}if(h.has(e.flowRunId))continue;h.add(e.flowRunId);let t=Le(e.flowRunId),n=t?`(${d.bold(e.flowRunId)} ${t.status} — flow ${t.name})`:`(${d.bold(e.flowRunId)} flow)`;G.stdout.write(`${g?`
15
+ `:``}${n}\n`);for(let t of p.get(e.flowRunId))f(t);g=!0,_=!0}l&&G.stdout.write(d.dim(`\n... ${l} more not shown (--limit ${s})\n`));let v=[`Result: coder task result <task-id>`];i.archived||v.push(`Older tasks: coder task list --archived [--limit N]`),G.stdout.write(`\n${D(v,d)}\n`)}function Dt(){let e=process.env.CLAUDE_CONFIG_DIR||q.join(Ze.homedir(),`.claude`);return q.join(e,`projects`)}function Ot(e){if(!e)return!1;let t=Dt(),n=!1;try{for(let r of K.readdirSync(t)){let i=q.join(t,r,`${e}.jsonl`);K.existsSync(i)&&(K.rmSync(i,{force:!0}),n=!0)}}catch{}return n}function Y(e){e.threadId&&(e.engine===`codex`?i(e.threadId):e.engine===`claude`&&Ot(e.threadId))}async function kt(e){let{options:n,positionals:r}=b(e,J.object({...S,"all-archived":C}));F(r,1,`task delete`);let i=E(n);if(n[`all-archived`]){let e=t(i).filter(e=>(Y(e),te(i,e.id))).map(e=>e.id);if(n.json){P({deleted:e,count:e.length});return}G.stdout.write(e.length?`Deleted ${e.length} archived task${e.length===1?``:`s`}.\n`:`${H.dim(`No archived tasks to delete.`)}\n`);return}let a=r[0];a||O(`Missing task id.`,{hint:[`Delete one: coder task delete <task-id>`,`Delete all archived: coder task delete --all-archived`]});let o=j(i,a);if(o.status===`running`&&O(`Task ${o.id} is still running.`,{hint:`Stop it first: coder task stop ${o.id}`}),Y(o),te(i,o.id),n.json){P({taskId:o.id,deleted:!0});return}G.stdout.write(`Deleted task ${H.cyan(o.id)}.\n`)}function X(e,t){return t.split(`.`).reduce((e,t)=>e?.[t],e)}function At(e,t){return t?q.join(a(e),`coder.config.json`):L()}function jt(e,t){let n=I(e);return t?X(n,t):n}function Mt(e,t,n,r={}){let i=At(e,r.workspace),a=K.existsSync(i)?JSON.parse(K.readFileSync(i,`utf8`)):{},o=t.split(`.`),s=o.at(-1),c=a;for(let e of o.slice(0,-1))(typeof c[e]!=`object`||c[e]===null)&&(c[e]={}),c=c[e];r.unset?delete c[s]:c[s]=n;let l=Ie(a);if(l.length)throw Error(`Refusing to write invalid config:\n ${l.join(`
16
+ `)}`);return K.mkdirSync(q.dirname(i),{recursive:!0}),K.writeFileSync(i,`${JSON.stringify(a,null,2)}\n`,`utf8`),{file:i,key:t,effective:X(I(e),t)??null,...r.unset?{unset:!0}:{value:n}}}async function Nt(e){let{options:t,positionals:n}=b(e,J.object({...S,workspace:C})),r=E(t),[i=`list`,a,...o]=n,s=At(r,t.workspace);if(i===`list`){let e=I(r);if(t.json){P(e);return}let n=H,i=e=>[e?.model,e?.effort,e?.permissions].filter(Boolean).join(`/`)||`-`;G.stdout.write([`${n.dim(`chain`)} ${e.chain.join(` -> `)}`,`${n.dim(`codex`)} ${i(e.agents.codex)}`,`${n.dim(`claude`)} ${i(e.agents.claude)}`,`${n.dim(`approvals`)} timeout=${e.approvals.escalationTimeoutMs}ms hosts=[${e.approvals.allowedNetworkHosts.join(`, `)}]`,`${n.dim(`file`)} ${L()}`].join(`
17
+ `)+`
18
+ `);return}if(i===`get`){a||O(`Usage: coder config get <key> (e.g. chain, agents.codex.model)`);let e=X(I(r),a);if(t.json||e===null||typeof e==`object`&&e!==void 0){P(e===void 0?null:e);return}G.stdout.write(`${e===void 0?`(unset)`:String(e)}\n`);return}i!==`set`&&i!==`unset`&&O(`Usage: coder config [get|set|unset] <key> [value] [--workspace]`),(!a||i===`set`&&o.length===0)&&O(`Usage: coder config ${i} <key>${i===`set`?` <value>`:``}`);let c=o.join(` `),l;try{l=JSON.parse(c)}catch{l=c.includes(`,`)?c.split(`,`).map(e=>e.trim()).filter(Boolean):c}let u;try{({effective:u}=Mt(r,a,l,{workspace:t.workspace,unset:i===`unset`}))}catch(e){O(e instanceof Error?e.message:String(e))}if(t.json){P({file:s,key:a,...i===`set`?{value:l}:{unset:!0},effective:u});return}let d=H,f=i===`set`?`${d.cyan(a)} = ${JSON.stringify(l)}`:`unset ${d.cyan(a)}`;G.stdout.write(`${f} ${d.dim(`(${s})`)}\n`)}async function Pt(e,t={}){let n=R(e),r=de(n);r?.updated&&(n=R(e));let i=n.available?await Ve(e):{loggedIn:!1,detail:n.detail},a=Ue(),o=a.available?he():{loggedIn:!1,detail:a.detail},s=L();if(!K.existsSync(s)){let e=n.available?[`codex`,`claude`]:a.available?[`claude`,`codex`]:[`codex`,`claude`];He({...ne,chain:e})}let c=_(),l=t.claude?De(c):null,u=t.agents||t.codex?we(c):null,d=I(e),f=n.available&&i.loggedIn||a.available&&o.loggedIn;return{codex:{available:n.available,detail:n.detail,auth:i.detail,loggedIn:i.loggedIn},...r?{codexUpdate:r}:{},claude:{available:a.available,detail:a.detail,auth:o.detail,loggedIn:o.loggedIn},configFile:s,runtime:Qe(new URL(`../bin/coder.mjs`,import.meta.url)),...l?{claudePlugin:l}:{},...u?{agentsSkill:u}:{},config:d,ready:f}}async function Ft(e){let{options:t,positionals:n}=b(e,J.object({...S,codex:C,claude:C,agents:C})),r=E(t),i=[`claude`,`codex`,`agents`];for(let e of n)i.includes(e)||O(`Unknown host "${e}". Use claude, codex, or agents.`,{hint:`Codex, Pi, OpenCode, and other Agent Skills hosts: coder setup-host agents`}),t[e]=!0;let a=H.bold,o=H.dim;t.json||G.stdout.write(`${a(`Coder host setup`)}\n\n`);let s=await Pt(r,{claude:t.claude,codex:t.codex,agents:t.agents});if(t.json){P(s);return}let{codex:c,claude:l,codexUpdate:u,config:d,configFile:f,claudePlugin:p,agentsSkill:m,ready:h}=s,g=[],_=c.available?c.loggedIn?M(`codex ${o(`${c.detail}; ${c.auth}`)}`):k(`codex not logged in ${o(`(${c.auth})`)} - run: codex login`):k(`codex CLI not installed - run: npm install -g @openai/codex`),v=l.available?l.loggedIn?M(`claude ${o(`${l.detail}; ${l.auth}`)}`):k(`claude not logged in - run: claude auth login`):k(`claude CLI not installed - run: npm install -g @anthropic-ai/claude-code`);g.push(a(`Available Engines`),_,v,` ${o(`custom models (local/provider endpoints): coder model --help`)}`),u?.updated?g.push(M(`codex ${o(`updated ${u.from} -> ${c.detail} (GPT-5.6 support)`)}`)):u&&g.push(k(`codex ${u.note}`)),g.push(``);let y=e=>{let t=d.agents?.[e]??{};return[t.model,t.effort,t.permissions].filter(Boolean).join(`/`)};g.push(a(`Config`),` chain: ${(d.chain??[]).join(` -> `)} codex: ${y(`codex`)} claude: ${y(`claude`)}`,` ${o(f)} ${o(`(coder config set <key> <value> to change)`)}`,``);let x=[[`claude plugin`,p??null],[`agents skill `,m??null]];for(let[e,t]of x)t&&g.push(t.installed?M(`${e} ${o(t.note)}`):k(`${e} ${t.note}`),``);g.push(h?M(`ready - try: coder run --wait "explain this repo's layout"`):k(`not ready - install an engine CLI to run tasks: codex (npm install -g @openai/codex, then codex login) or claude (npm install -g @anthropic-ai/claude-code, then claude auth login)`)),G.stdout.write(`${g.join(`
19
+ `)}\n`)}const It=new Set([`codex`,`claude`,`custom`]);async function Lt(e){let t=e.envKey?G.env[e.envKey]:void 0,n=W(e.baseUrl,`models`),r=null;for(let i of n)try{let n=await fetch(i,{headers:t?{Authorization:`Bearer ${t}`}:{},signal:AbortSignal.timeout(5e3)});if(!n.ok){r=n.status===404?{reachable:!0,modelListed:null,detail:`endpoint reachable (no model list to verify against)`}:{reachable:!1,modelListed:null,detail:`${i} -> HTTP ${n.status}`};continue}let a=await n.json().catch(()=>null),o=Array.isArray(a?.data)?a.data.map(e=>e.id):null;if(!o)return{reachable:!0,modelListed:null,detail:`endpoint reachable`};let s=o.includes(e.model);return{reachable:!0,modelListed:s,detail:s?`endpoint reachable; model "${e.model}" listed`:`endpoint reachable, but "${e.model}" is not in its model list`}}catch(e){r={reachable:!1,modelListed:null,detail:`${i} unreachable (${e instanceof Error?e.message:String(e)})`}}return r}function Rt(e,t){let n=K.existsSync(e)?JSON.parse(K.readFileSync(e,`utf8`)):{};t(n),n.models&&Object.keys(n.models).length===0&&delete n.models;let r=Ie(n);r.length&&O(`Refusing to write invalid config:\n ${r.join(`
20
+ `)}`),K.mkdirSync(q.dirname(e),{recursive:!0}),K.writeFileSync(e,`${JSON.stringify(n,null,2)}\n`,`utf8`)}function Z(e,t){Rt(e,e=>{e.models=e.models??{},t(e.models)})}const Q=J.object({...S,"base-url":w,model:w,"env-key":w,workspace:C});function $(e,t){return e.workspace?q.join(a(t),`coder.config.json`):L()}function zt(e){let t=e.models??{},n=Object.entries(t).filter(e=>z(e[1])),r=e=>`${e.provider}:${e.model}${e.effort?`:${e.effort}`:``}`,i=Object.entries(t).filter(e=>B(e[1])).map(([e,t])=>{let n=t.provider===`claude`?N:A,i=n[t.model]??t.model,a=U(e)?e:Object.entries(n).find(([,e])=>e===i)?.[0];return{name:e,spec:r(t),replaces:a,disabled:!!t.disabled}}),a=new Map;for(let e of i)e.replaces&&(e.name===e.replaces||!a.has(e.replaces))&&a.get(e.replaces)?.name!==e.replaces&&a.set(e.replaces,e);let o=e=>!!t[e]?.disabled,s=i.filter(e=>![...a.values()].some(t=>t.name===e.name));return{models:t,endpoints:n,providerRows:(e,t)=>[...Object.entries(t).map(([e,t])=>{let n=a.get(e);return n?{alias:n.name,spec:n.spec,overrides:e,disabled:n.disabled}:{alias:e,model:t,builtin:!0,disabled:o(e)}}),...s.filter(t=>t.spec.startsWith(`${e}:`)).map(({name:e,spec:t,disabled:n})=>({alias:e,spec:t,disabled:n}))],toggledSlugs:Object.entries(t).filter(([e,t])=>!z(t)&&!B(t)&&!U(e)).map(([e,t])=>({name:e,disabled:!!t.disabled})),disabled:o}}async function Bt(e){let{endpoints:t,providerRows:n,toggledSlugs:r}=zt(I(e)),i=await Promise.all(t.map(async([e,t])=>({name:e,...t,probe:await Lt(t)})));return{codex:n(`codex`,A),claude:n(`claude`,N),custom:i,...r.length?{toggles:r}:{}}}async function Vt(e){let{options:t,positionals:n}=b(e,Q);n.length&&O(`model list takes no arguments.`,{hint:`Usage: coder model list [--json]`});let r=E(t),i=H,{endpoints:a,providerRows:o,toggledSlugs:s,disabled:c}=zt(I(r));if(t.json){P(await Bt(r));return}let l=e=>c(e)?i.dim(`∅`):i.green(`✔`),u=e=>c(e)?` ${i.dim(`(disabled)`)}`:``,d=[],f=(e,t,n)=>{d.push(i.bold(e));for(let e of o(t,n))`overrides`in e?d.push(` ${e.disabled?i.dim(`∅`):i.cyan(`↳`)} ${e.alias.padEnd(14)} ${i.dim(`-> ${e.spec} (overrides ${e.overrides})`)}${e.disabled?` ${i.dim(`(disabled)`)}`:``}`):`builtin`in e?d.push(` ${l(e.alias)} ${e.alias.padEnd(14)} ${i.dim(`-> ${e.model}`)}${u(e.alias)}`):d.push(` ${e.disabled?i.dim(`∅`):i.cyan(`↳`)} ${e.alias.padEnd(14)} ${i.dim(`-> ${e.spec}`)}${e.disabled?` ${i.dim(`(disabled)`)}`:``}`);d.push(``)};if(f(`Codex models`,`codex`,A),f(`Claude models`,`claude`,N),d.push(i.bold(`Custom models`),``),!a.length)d.push(i.dim(` none — coder model add <name> --base-url <url> --model <id> [--env-key VAR]`));else for(let[e,t]of a){let n=await Lt(t),r=t.disabled?i.dim(`∅`):n.reachable?i.green(`✔`):i.red(`✘`);d.push(` ${r} ${e.padEnd(14)} ${t.model} ${i.dim(`@ ${t.baseUrl} — ${n.detail}`)}${u(e)}`)}if(d.push(``),s.length){d.push(i.bold(`Other`),``);for(let e of s)d.push(` ${e.disabled?i.dim(`∅`):i.green(`✔`)} ${e.name.padEnd(14)} ${i.dim(`raw model id`)}${e.disabled?` ${i.dim(`(disabled)`)}`:``}`);d.push(``)}d.push(i.dim(`Alias one: coder model alias <name> <spec> (e.g. fast codex:mini)`),i.dim(`Use one: coder run --model <name> "<task>" (or coder run --agent custom)`)),G.stdout.write(`${d.join(`
21
+ `)}\n`)}async function Ht(e,t,n,r){let i=await st(t),a=i?.wireApi===`responses`;i?(t.wireApi=i.wireApi,t.baseUrl=i.baseUrl??t.baseUrl):delete t.wireApi,Z(n,n=>{n[e]=t});let o=await Lt(t),s=Ee(R(r)),c=R(r);return{name:e,entry:t,file:n,probe:o,codex:c,install:s,nativeResponses:a,keyMissing:!!(t.envKey&&!G.env[t.envKey]),ready:o.reachable&&c.available}}async function Ut(e,t,n,r,i){let a=$(n,r),o=H,{probe:s,codex:c,install:l,nativeResponses:u,keyMissing:d}=await Ht(e,t,a,r);if(n.json){P({name:e,...t,file:a,probe:s,codex:c,...l?{codexInstall:l}:{},ready:s.reachable&&c.available});return}let f=e=>` ${o.green(`✔`)} ${e}`,p=e=>` ${o.red(`✘`)} ${e}`,m=[`${i} ${o.cyan(e)} -> ${t.model} @ ${t.baseUrl} ${o.dim(`(${a})`)}`,``,s.reachable?f(`${s.detail} ${o.dim(`(${u?`responses api, direct`:`chat api, auto-translated`})`)}`):p(s.detail),c.available?f(l?.installed?`${l.note} ${o.dim(`(${c.detail})`)}`:`codex engine ${o.dim(`(${c.detail})`)}`):p(l?.note??`codex CLI not installed - run: npm install -g @openai/codex`)];d&&m.push(p(`env var ${t.envKey} is not set in this shell`)),m.push(``,o.dim([`Use it: coder run --model ${e} "<task>"`,`Make default: coder config set agents.custom.model ${e}`].join(`
22
+ `))),G.stdout.write(`${m.join(`
23
+ `)}\n`)}function Wt(e,t,n){return e||O(`Missing model name.`,{hint:t}),/^[a-z][a-z0-9-]*$/.test(e)||O(`Invalid model name "${e}". Use lowercase kebab-case (e.g. qwen-local).`),(It.has(e)||n===`model`&&U(e))&&O(`"${e}" is reserved; pick another name.`),e}async function Gt(e){let t=`Usage: coder model add <name> --base-url <url> --model <id> [--env-key VAR]`,{options:n,positionals:r}=b(e,Q),i=E(n),a=Wt(r[0],t,`model`),o=I(i).models?.[a];o&&O(z(o)?`Custom model "${a}" already exists.`:`"${a}" is already configured as an ${B(o)?`alias`:`entry`}; remove it first.`,{hint:`Change it: coder model update ${a} [--base-url|--model|--env-key], or remove it first.`}),(!n[`base-url`]||!n.model)&&O(`Missing --base-url or --model.`,{hint:t}),await Ut(a,{baseUrl:V(n[`base-url`]),model:n.model,...n[`env-key`]?{envKey:n[`env-key`]}:{}},n,i,`saved`)}async function Kt(e){let t=`Usage: coder model update <name> [--base-url <url>] [--model <id>] [--env-key VAR]`,{options:n,positionals:r}=b(e,Q),i=E(n),a=r[0],o=a?I(i).models?.[a]:void 0,s=o&&z(o)?o:void 0;(!a||!s)&&O(a?`No custom model named "${a}".`:`Missing model name.`,{hint:a?`List them: coder model list`:t}),!n[`base-url`]&&!n.model&&!n[`env-key`]&&O(`Nothing to update: pass --base-url, --model, or --env-key.`,{hint:t}),await Ut(a,{...s,...n[`base-url`]?{baseUrl:V(n[`base-url`])}:{},...n.model?{model:n.model}:{},...n[`env-key`]?{envKey:n[`env-key`]}:{}},n,i,`updated`)}async function qt(e){let{options:t,positionals:n}=b(e,Q),r=E(t),i=n[0],a;try{a=nn(r,i??``,{workspace:t.workspace})}catch(e){O(e instanceof Error?e.message:String(e),{hint:i?`List them: coder model list`:`Usage: coder model remove <name>`})}if(t.json){P(a);return}G.stdout.write(`removed ${H.cyan(a.removed)} ${H.dim(`(${a.file})`)}\n`)}async function Jt(e){await Xt(e,!0)}async function Yt(e){await Xt(e,!1)}async function Xt(e,t){let n=t?`disable`:`enable`,r=`Usage: coder model ${n} <name>`,{options:i,positionals:a}=b(e,Q),o=E(i),s=a[0];s||O(`Missing model name.`,{hint:r});let{file:c}=on(o,s,t,{workspace:i.workspace});if(i.json){P({[t?`disabled`:`enabled`]:s,file:c});return}G.stdout.write(`${n}d ${H.cyan(s)} ${H.dim(`(${c})`)}\n`)}async function Zt(e){let t=`Usage: coder model alias <name> <spec> (e.g. coder model alias fast codex:mini)`,{options:n,positionals:r}=b(e,Q),i=E(n),a=Wt(r[0],t,`alias`),o=I(i).models?.[a];o&&z(o)&&O(`"${a}" is a custom model name; pick another alias name.`);let s=r[1];s||O(`Missing alias spec.`,{hint:t});let c;try{c=rn(i,a,s,{workspace:n.workspace})}catch(e){let n=e instanceof Error?e.message:String(e);O(n,n.includes(`already dispatchable by name`)?{}:{hint:t})}if(n.json){P(c);return}let l=H,u=`${c.provider} ${c.model}${c.effort?` (${c.effort})`:``}`;G.stdout.write(`aliased ${l.cyan(a)} -> ${c.provider}:${c.model}${c.effort?`:${c.effort}`:``} ${l.dim(`= ${u}`)} ${l.dim(`(${c.file})`)}\n`)}async function Qt(e){let{options:t,positionals:n}=b(e,Q),r=E(t),i=n[0],a=i?I(r).models?.[i]:void 0;(!i||!a||!B(a))&&O(i?`No alias named "${i}".`:`Missing alias name.`,{hint:i?`List them: coder model list`:`Usage: coder model unalias <name>`});let{file:o}=an(r,i,{workspace:t.workspace});if(t.json){P({unaliased:i,file:o});return}G.stdout.write(`unaliased ${H.cyan(i)} ${H.dim(`(${o})`)}\n`)}function $t(e,t){if(!e)throw Error(`Missing model name.`);if(!/^[a-z][a-z0-9-]*$/.test(e))throw Error(`Invalid model name "${e}". Use lowercase kebab-case (e.g. qwen-local).`);if(It.has(e)||t===`model`&&U(e))throw Error(`"${e}" is reserved; pick another name.`)}async function en(e,t,n){if($t(t,`model`),I(e).models?.[t])throw Error(`Custom model "${t}" already exists.`);if(!n.baseUrl||!n.model)throw Error(`Missing baseUrl or model.`);return Ht(t,{baseUrl:V(n.baseUrl),model:n.model,...n.envKey?{envKey:n.envKey}:{}},$({workspace:n.workspace},e),e)}async function tn(e,t,n){let r=I(e).models?.[t],i=r&&z(r)?r:void 0;if(!i)throw Error(`No custom model named "${t}".`);if(!n.baseUrl&&!n.model&&!n.envKey)throw Error(`Nothing to update: pass baseUrl, model, or envKey.`);return Ht(t,{...i,...n.baseUrl?{baseUrl:V(n.baseUrl)}:{},...n.model?{model:n.model}:{},...n.envKey?{envKey:n.envKey}:{}},$({workspace:n.workspace},e),e)}function nn(e,t,n={}){if(!t||!I(e).models?.[t])throw Error(t?`No custom model named "${t}".`:`Missing model name.`);let r=$({workspace:n.workspace},e);return Z(r,e=>{delete e[t]}),{removed:t,file:r}}function rn(e,t,n,r={}){$t(t,`alias`);let i=I(e),a=i.models?.[t];if(a&&z(a))throw Error(`"${t}" is a custom model name; pick another alias name.`);if(!n)throw Error(`Missing alias spec.`);let o=Ge(n,i);if(!o||!o.model)throw Error(`Alias spec "${n}" does not resolve to a model.`);if(o.agent===`custom`)throw Error(`"${n}" names a custom model, which is already dispatchable by name; aliases target codex/claude models.`);let s=n.split(`:`).some(e=>Ae.has(e.trim())||ke.has(e.trim())),c=o.agent===`claude`?N:A,l={provider:o.agent,model:c[o.model]??o.model,...s&&o.effort?{effort:o.effort}:{}},u=$({workspace:r.workspace},e);return Z(u,e=>{e[t]=l}),{alias:t,...l,file:u}}function an(e,t,n={}){let r=t?I(e).models?.[t]:void 0;if(!t||!r||!B(r))throw Error(t?`No alias named "${t}".`:`Missing alias name.`);let i=$({workspace:n.workspace},e);return Z(i,e=>{delete e[t]}),{unaliased:t,file:i}}function on(e,t,n,r={}){if(!t)throw Error(`Missing model name.`);let i=!!I(e).models?.[t],a=$({workspace:r.workspace},e);return Z(a,e=>{let r=e[t];if(!r){n?e[t]={disabled:!0}:i&&(e[t]={disabled:!1});return}z(r)||B(r)?n?r.disabled=!0:delete r.disabled:n?r.disabled=!0:delete e[t]}),{name:t,disabled:n,file:a}}const sn={list:Vt,add:Gt,setup:Gt,update:Kt,remove:qt,disable:Jt,enable:Yt,alias:Zt,unalias:Qt};async function cn(e){let[t,...n]=e;if(t===void 0||t.startsWith(`-`)){await Vt(e);return}let r=sn[t];r||O(`Unknown model subcommand "${t}". Use list, add, update, remove, disable, enable, alias, or unalias.`,{hint:`Help: coder model --help`}),await r(n)}const ln=[{name:`readme`,file:`README.md`,description:`What Coder is and how to get started.`},{name:`skill`,file:`plugins/agents/skills/coder/SKILL.md`,description:`How a host agent should drive Coder.`}];function un(e){let t=q.join(e,`docs`),n=[];try{n=K.readdirSync(t).filter(e=>e.endsWith(`.md`)).sort()}catch{n=[]}let r=n.map(t=>{let n=q.join(`docs`,t);return{name:q.basename(t,`.md`),file:n,description:dn(q.join(e,n))}}),i=ln.map(e=>({name:e.name,file:e.file,description:e.description}));return[...r,...i]}function dn(e){let t=``;try{t=K.readFileSync(e,`utf8`)}catch{return``}let n=t.split(`
24
+ `),r=``,i=``,a=!1;for(let e of n){let t=e.trim();if(t.startsWith("```")){a=!a;continue}if(!(a||t===``)){if(t.startsWith(`#`)){r||=t.replace(/^#+\s*/,``);continue}i=t;break}}return fn(i)||r}function fn(e){if(!e)return``;let t=e.match(/^.*?[.!?](?=\s|$)/);return(t?t[0]:e).trim()}function pn(e){let t=_(),n=un(t);if(!e)return{topics:n.map(e=>({name:e.name,description:e.description}))};let r=n.find(t=>t.name===e.toLowerCase());if(!r)throw Error(`Unknown docs topic "${e}". Available: ${n.map(e=>e.name).join(`, `)}`);let i=q.join(t,r.file),a;try{a=K.readFileSync(i,`utf8`)}catch{throw Error(`Could not read docs for "${r.name}" (${r.file}).`)}return{name:r.name,content:a}}async function mn(e){let{options:t,positionals:n}=b(e,J.object({json:C})),[r]=n,i;try{i=pn(r)}catch(e){let t=e instanceof Error?e.message:String(e);return O(t,t.startsWith(`Unknown docs topic`)?{hint:`List topics: coder docs`}:{})}if(`content`in i){G.stdout.write(i.content.endsWith(`
25
+ `)?i.content:`${i.content}\n`);return}if(t.json){P(i);return}let a=H,o=Math.max(0,...i.topics.map(e=>e.name.length)),s=i.topics.map(e=>` ${a.cyan(e.name.padEnd(o))} ${a.dim(e.description)}`);G.stdout.write([a.bold(`Docs:`),...s,``,D([`Read a topic: coder docs <topic>`],a)].join(`
26
+ `)+`
27
+ `)}async function hn(e={},t){let n=!e.pluginsOnly,r=!e.cliOnly,i=_(),a=q.join(i,`plugins/claude/.claude-plugin/plugin.json`),o={cli:x(),claude:ee(a)},s={};if(n){let n=ae(g),[r,...i]=e.pm===`npm`?[`npm`,`install`,`-g`,`@wular/coder@latest`]:e.pm===`pnpm`?[`pnpm`,`add`,`-g`,`@wular/coder@latest`]:e.pm===`yarn`?[`yarn`,`global`,`add`,`@wular/coder@latest`]:e.pm===`bun`?[`bun`,`add`,`-g`,`@wular/coder@latest`]:n.command,a=e.pm??n.pm;t?.(`via ${a}`);let c=Xe(r,i,{encoding:`utf8`});if(c.error?.code===`ENOENT`)throw Error(`${a} not found on PATH. Re-run with --pm <npm|pnpm|yarn|bun>, or update manually: ${r} ${i.join(` `)}`);if(c.status!==0){let e=(c.stderr||c.stdout||``).trim();throw Error(`CLI update failed (${a}).${e?`\n${e}`:``}\nRun manually: ${r} ${i.join(` `)}`)}oe();let l=x();s.cli={pm:a,from:o.cli,to:l,changed:l!==o.cli}}if(r){if(e.claude||!e.codex&&Ue().available){let e=De(i);s.claudePlugin={installed:e.installed,note:e.note,from:o.claude,to:ee(a)}}if(K.existsSync(re())){let e=le(),t=we(i);s.agentsSkill={installed:t.installed,note:t.note,from:e,to:le()}}}return s}async function gn(e){let{options:t}=b(e,J.object({pm:w,"cli-only":C,"plugins-only":C,codex:C,claude:C})),n=H.bold,r=H.dim,i=(e,t)=>t&&e&&t!==e?`${e} ${r(`->`)} ${n(t)}`:`${t??e??`?`} ${r(`(unchanged)`)}`,a;try{a=await hn({cliOnly:t[`cli-only`],pluginsOnly:t[`plugins-only`],pm:t.pm,codex:t.codex,claude:t.claude},e=>G.stdout.write(`${n(`Updating coder CLI`)} ${r(`${e}...`)}\n`))}catch(e){O(e instanceof Error?e.message:String(e))}if(a.cli&&G.stdout.write(a.cli.changed?`${M(`coder CLI ${i(a.cli.from,a.cli.to)}`)}\n`:`${M(`coder CLI ${a.cli.from} ${r(`(already latest)`)}`)}\n`),a.claudePlugin){let e=a.claudePlugin;G.stdout.write(`${e.installed?M(`claude plugin ${i(e.from,e.to)} ${r(`- ${e.note}`)}`):k(`claude plugin ${e.note}`)}\n`)}if(a.agentsSkill){let e=a.agentsSkill;G.stdout.write(`${e.installed?M(`agents skill ${i(e.from,e.to)} ${r(`- ${e.note}`)}`):k(`agents skill ${e.note}`)}\n`)}}export{St as C,vt as D,_t as E,Ct as S,yt as T,Mt as _,cn as a,Tt as b,Bt as c,an as d,tn as f,jt as g,Nt as h,pn as i,nn as l,Pt as m,hn as n,en as o,Ft as p,mn as r,rn as s,gn as t,on as u,kt as v,bt as w,Et as x,Y as y};
package/docs/config.md ADDED
@@ -0,0 +1,67 @@
1
+ # Configuration
2
+
3
+ The Coder configuration reference. Machine defaults live in `~/.coder/config.json`; a `coder.config.json` at a repo root overrides per project. Merge order, later wins:
4
+
5
+ 1. Built-in defaults
6
+ 2. `~/.coder/config.json`
7
+ 3. `<repo>/coder.config.json`
8
+ 4. CLI flags on the dispatch itself (`--agent`, `--model`, `--effort`, `--permissions`)
9
+
10
+ Read and write values from the CLI (`--workspace` targets the repo file instead of the user file):
11
+
12
+ ```bash
13
+ coder config get [key]
14
+ coder config set chain '["codex", "claude", "custom"]'
15
+ coder config set agents.codex.model terra
16
+ ```
17
+
18
+ ## Full shape
19
+
20
+ ```json
21
+ {
22
+ "chain": ["codex", "claude", "custom"],
23
+ "agents": {
24
+ "codex": { "model": "terra", "effort": "high", "permissions": "auto" },
25
+ "claude": { "model": "opus", "effort": "medium", "permissions": "auto" },
26
+ "custom": { "model": "qwen" }
27
+ },
28
+ "models": {
29
+ "qwen": { "baseUrl": "http://localhost:11434/v1", "model": "qwen2.5-coder:32b" },
30
+ "fast": { "provider": "codex", "model": "gpt-5.4-mini" },
31
+ "big": { "provider": "claude", "model": "opus", "effort": "high" },
32
+ "luna": { "disabled": true }
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## `chain`
38
+
39
+ Fallback order. When an agent can't start (missing binary, auth, quota), the task automatically retries on the next entry. Default `["codex", "claude"]`.
40
+
41
+ ## `agents`
42
+
43
+ Per-agent defaults, used when a dispatch doesn't override them:
44
+
45
+ | Key | Values |
46
+ | --- | --- |
47
+ | `model` | codex: `mini`, `luna`, `terra`, `sol` · claude: `haiku`, `sonnet`, `opus`, `fable` · custom: any name from `models` |
48
+ | `effort` | `low`, `medium`, `high` |
49
+ | `permissions` | `read-only`, `workspace-write`, `auto` |
50
+
51
+ Permission modes are one surface across all engines: read-only can't modify anything, workspace-write stays inside the repo, auto adds policy-answered escalations.
52
+
53
+ ## `models`
54
+
55
+ One namespace for every model you can name as `--model`, keyed by that name. An entry's shape decides what it is:
56
+
57
+ | Shape | Meaning | Managed by |
58
+ | --- | --- | --- |
59
+ | `{ "baseUrl", "model", "envKey"?, "wireApi"? }` | custom OpenAI-compatible endpoint (the `custom` agent) | `coder model add/update/remove` |
60
+ | `{ "provider": "codex"\|"claude", "model", "effort"? }` | alias onto a built-in engine | `coder model alias/unalias` |
61
+ | `{ "disabled": true }` | bare toggle turning off any model name (built-in, entry from another config layer, or raw engine slug) | `coder model disable/enable` |
62
+
63
+ Every shape also accepts `"disabled": true` to park the entry without deleting it. An entry named after a built-in alias (e.g. `mini`) shadows it. See [Models](models.md).
64
+
65
+ ---
66
+
67
+ Config files are validated strictly: an unknown key or bad value fails with a pointed error instead of being silently ignored.
package/docs/flows.md ADDED
@@ -0,0 +1,239 @@
1
+ # Flows
2
+
3
+ Orchestrate many coder tasks with a plain TypeScript file. A flow is code, not config: loops, conditionals, and nesting are just JavaScript, while dispatch, concurrency, journaling, and resume come from the runtime. Every task a flow spawns is a regular coder task, so `coder list`, `coder task steer`, and `coder task stop` work on them like any other.
4
+
5
+ Like tasks, flows are normally authored by your host agent: you describe the wave, the harness writes the flow file into `.coder/flows/`, and you review or edit it before it runs. The file is the contract; everything below is what goes in it.
6
+
7
+ Watch a run live - progress and overview of its tasks as the wave goes:
8
+
9
+ ```sh
10
+ coder flow stream [run-id]
11
+ ```
12
+
13
+ ## A first flow
14
+
15
+ ```ts
16
+ // .coder/flows/audit-routes.ts
17
+ import { z } from 'zod';
18
+ import { task, pipeline } from '@wular/coder/flow';
19
+
20
+ const routes = await task('List every .ts file under src/routes/.', {
21
+ returns: z.object({ files: z.array(z.string()) }),
22
+ });
23
+
24
+ export default await pipeline(routes.data.files, file =>
25
+ task(`Audit ${file} for missing authentication checks.`, { name: file }),
26
+ );
27
+ ```
28
+
29
+ Run it:
30
+
31
+ ```bash
32
+ coder flow run audit-routes --wait
33
+ ```
34
+
35
+ `--wait` streams the run's progress right there; without it the run goes to the background and `coder flow stream` shows the same lines any time. If the run dies mid-wave, `coder flow resume` continues it without re-running finished steps.
36
+
37
+ ## Flows are plain modules
38
+
39
+ A flow is an ordinary TypeScript module: import the primitives, use top-level `await`, and `export default` the result. Input comes in through the `args` import (see [`args`](#args)):
40
+
41
+ ```ts
42
+ // .coder/flows/verify-wave.ts
43
+ import { task, pipeline, args } from '@wular/coder/flow';
44
+
45
+ export default await pipeline(args.clusters as string[], c =>
46
+ task(`Verify cluster ${c}.`),
47
+ );
48
+ ```
49
+
50
+ ## The primitives
51
+
52
+ ### `task(prompt, opts?)`
53
+
54
+ Dispatch one coder task and await its result.
55
+
56
+ ```ts
57
+ const r = await task('Explain the auth module', {
58
+ model: 'terra',
59
+ permissions: 'read-only',
60
+ });
61
+ r.output; // final message text
62
+ ```
63
+
64
+ Options are the same as the `coder run` flags (`agent`, `model`, `effort`, `permissions`, `name`, `system`, `resume`, `cwd` - see `coder run --help`), plus one of its own:
65
+
66
+ - `returns` - a zod schema for structured output instead of prose. The runtime injects the format instructions, validates the reply (one corrective retry on mismatch), and the parsed value lands on `r.data`:
67
+
68
+ ```ts
69
+ const r = await task(
70
+ 'List every file that imports the legacy cache API.',
71
+ {
72
+ returns: z.object({ files: z.array(z.string()) }),
73
+ },
74
+ );
75
+
76
+ r.data.files; // string[], validated
77
+ ```
78
+
79
+ Returns `{ taskId, status, output, data?, tokens, model }`. If the task fails, `task()` throws; catch the error to inspect the failed result or retry.
80
+
81
+ > Note: unlike the SDK's `task.run()`, which returns a task id right away and leaves waiting to you, the flow `task()` waits for the result in one call, and inside a run it is journaled and concurrency-limited.
82
+
83
+ ### `gate(cmd, opts?)`
84
+
85
+ A deterministic shell checkpoint. Gates are never agents: they run the command and return `{ ok, code, output }`. A failing gate does not throw, because a failed gate is a decision point, not an error:
86
+
87
+ ```ts
88
+ const g = await gate('bun tsc');
89
+ if (!g.ok) {
90
+ // retry with g.output in the prompt, park the item, or bail - your call
91
+ }
92
+ ```
93
+
94
+ The command runs through the shell, so treat it like any shell string: never interpolate task output (or other model-derived text) into it - `gate(`test -n "${r.output}"`)` hands the model shell access. Write fixed commands, or pass model output through a file/env var the command reads.
95
+
96
+ ### `pipeline(items, ...stages)`
97
+
98
+ The workhorse. Give it a list of items and one or more stage functions; every item moves through the stages independently, as fast as its own work finishes.
99
+
100
+ ```ts
101
+ const results = await pipeline(
102
+ files, // items
103
+ file => task(`Fix the tests in ${file}.`), // stage 1
104
+ (r, file) => gate(`bun test ${file}`), // stage 2
105
+ );
106
+ ```
107
+
108
+ How to read it:
109
+
110
+ - Stage 1 runs for each file (up to the concurrency limit). The moment `a.ts` finishes stage 1, it enters stage 2, even if `b.ts` is still in stage 1. There is no waiting for the whole batch between stages.
111
+ - Each stage receives `(previousStageResult, originalItem, index)`, so later stages always know which item they are working on.
112
+ - `results` is one entry per item: whatever the last stage returned for it.
113
+ - If a stage throws for an item, that item becomes `null` in the results and skips its remaining stages; the other items are unaffected.
114
+
115
+ Prefer `pipeline` for anything multi-stage: fast items never wait for slow ones.
116
+
117
+ Use `Promise.all` for plain parallel batches:
118
+
119
+ ```ts
120
+ const findings = await Promise.all(
121
+ areas.map(area => task(`Audit ${area}.`)),
122
+ );
123
+ ```
124
+
125
+ ### `args`
126
+
127
+ The flow's input, from `--args '<json>'` or bare `key=value` pairs:
128
+
129
+ ```ts
130
+ import { args } from '@wular/coder/flow';
131
+
132
+ const items = (args.items ?? []) as string[];
133
+ ```
134
+
135
+ Properties are typed `unknown` - assert or default them as you go, or use the function form below for real types.
136
+
137
+ To validate and type the input with zod, shape the flow as two exports:
138
+
139
+ - `export const args` - a zod schema for the input
140
+ - `export default` - an async function holding the flow body
141
+
142
+ The runtime checks the input against the schema first, and only if it passes runs the default function, passing the parsed input as its parameter. (Top-level flows can't get this fail-fast check: importing one already executes it.)
143
+
144
+ ```ts
145
+ export const args = z.object({ clusters: z.array(z.string()) });
146
+
147
+ export default async ({ clusters }: z.infer<typeof args>) => {
148
+ // clusters is string[], validated before any task dispatched
149
+ };
150
+ ```
151
+
152
+ ### `flow(name, args?)`
153
+
154
+ Run another flow inline and get its result:
155
+
156
+ ```ts
157
+ const verified = await flow('verify-wave', { clusters });
158
+ ```
159
+
160
+ The name resolves like `coder flow run <name>` does (see [Where flows live](#where-flows-live)). Sub-flows share the parent's journal, concurrency, and ledger, and nesting is one level deep: a sub-flow calling `flow()` throws.
161
+
162
+ ### `log(msg)`
163
+
164
+ Emit a progress line, shown in the flow log and `coder flow stream`:
165
+
166
+ ```ts
167
+ log(`${parked.length} clusters parked`);
168
+ ```
169
+
170
+ ## Concurrency and ceilings
171
+
172
+ Two knobs, sensible defaults, mentioned together because they scale off the same number:
173
+
174
+ - **`--concurrency`** - tasks running at once. Defaults to the machine's CPU count. Tasks are engine subprocesses doing mostly network waiting, so this is about engine quota and machine load, not about a flow "using up" your cores.
175
+ - **`--max-tasks`** - total tasks one run may dispatch, a backstop against runaway loops. Defaults to CPU count x 10.
176
+
177
+ Token usage is recorded per model in the run's ledger and shown by `coder flow result`. Bookkeeping, not enforcement.
178
+
179
+ ## Resume
180
+
181
+ Every `task`, `gate`, and `flow` call is recorded in the run's journal along with a fingerprint of its inputs (the prompt or command, plus options).
182
+
183
+ `coder flow resume <run-id>` simply imports the module again, fresh, and lets it execute top to bottom. Each call is checked against the journal in order:
184
+
185
+ 1. Inputs identical to last time? Return the recorded result instantly; nothing is dispatched.
186
+ 2. First call whose inputs differ (or that is new)? It runs for real, and so does everything after it.
187
+
188
+ What that means in practice:
189
+
190
+ - **Crash mid-wave** (quota, network, closed laptop): resume replays the finished steps from the journal in milliseconds and continues from where it stopped.
191
+ - **Edit step 3, resume**: steps 1-2 replay from the journal, step 3 onward runs fresh. Iterating on a half-broken flow costs only the part you changed.
192
+ - **Nothing changed**: the whole run replays from the journal and just returns its result again.
193
+
194
+ The one rule this buys: **a flow must produce the same prompts on every run**. Do not build prompts from `Date.now()`, `Math.random()`, or unordered directory listings; pass timestamps and seeds in via `--args`. A prompt that differs between runs looks like an edit, and everything after it re-runs.
195
+
196
+ ## Where flows live
197
+
198
+ `coder flow run <name>` (and `flow(name)` inside a flow) looks for `<name>.ts` or `<name>.mjs` in:
199
+
200
+ 1. `.coder/flows/` walking up from the current directory to the repo root, nearest first. In a monorepo, a flow in `packages/app/.coder/flows/` wins over one at the root when you run from inside `packages/app`.
201
+ 2. `~/.coder/flows/` - global flows, available in every workspace.
202
+
203
+ An explicit path (`coder flow run ./scratch/one-off.ts`) bypasses discovery entirely. `coder flow discover` shows every flow discoverable from where you stand.
204
+
205
+ ## CLI
206
+
207
+ ```bash
208
+ coder flow run <name|path> [--wait] [--args '<json>' | key=value...]
209
+ [--concurrency N] [--max-tasks N] [--json] [--dry-run]
210
+ coder flow list # recent runs: id, name, status, tasks, tokens, age
211
+ coder flow discover # flows runnable here: workspace + global
212
+ coder flow result [run-id] # progress and result: tasks, gates, token ledger
213
+ # --tail <n|all>: last n step rows (default all; 0 = final result only)
214
+ coder flow stream [run-id] # replay the progress lines and keep following live
215
+ # --tail <n|all>: replay the last n events (default all)
216
+ coder flow stop [run-id] # stop a run and its tasks (--keep-tasks to let them finish)
217
+ coder flow resume [run-id] # continue a stopped or edited run; same flags as run
218
+ coder flow archive <run-id> # hide a run from the recent list (or --all-stopped)
219
+ coder flow delete <run-id> # delete a run's record (or --all-archived)
220
+ ```
221
+
222
+ Runs show up in `coder list` alongside tasks, with the run's tasks grouped under it. Any `[run-id]` defaults to the most recent run.
223
+
224
+ `coder flow run` prints the run id and orchestrates in the background. `--wait` follows the run in the foreground, rendering progress live as tasks start and finish; Ctrl-C detaches and the run keeps going, just like a task. `coder flow stop` is the only way to end a run: it stops the orchestrator and the tasks it still has running (pass `--keep-tasks` to let those finish), leaving the journal ready for `resume`. A finished run's result (the flow's default export) is shown by `coder flow result`.
225
+
226
+ On a TTY, `--wait` and `flow stream` render a live view (spinner, in-place task rows); when piped, output degrades to plain append-only lines. Styling honors the standard `NO_COLOR` and `FORCE_COLOR` env vars.
227
+
228
+ `--dry-run` executes the script with dispatch stubbed out: it prints every resolved prompt and gate command with placeholder results, so you can review exactly what would be sent before spending tokens.
229
+
230
+ ## SDK
231
+
232
+ Flows sit on top of the coder SDK, and the SDK is public: everything the CLI does, your own code can do, including running flows programmatically. See [SDK](sdk.md).
233
+
234
+ ## Design notes
235
+
236
+ - **Gates are shell, not agents.** Verification must be deterministic and cheap. An agent judging "did tsc pass" is slower, costlier, and occasionally wrong.
237
+ - **Park, don't die.** A failed item should not kill the wave. Return it as data, keep the rest flowing, fix it manually or in the next run.
238
+ - **Saturation beats phases.** Prefer `pipeline` so fast items never wait for slow ones; reach for `Promise.all` barriers only when a step truly needs all prior results.
239
+ - **The flow executes; you decide.** Keep judgment (what to fix next, whether a spec is ready) in your session, and let flows run the wave you already designed. A flow that invents its own plan mid-run is a flow you cannot review with `--dry-run`.
@@ -1,6 +1,6 @@
1
- # Bring your own model
1
+ # Models
2
2
 
3
- Point Coder at any OpenAI-compatible URL, like Ollama or llama.cpp on your laptop, a vLLM GPU box, OpenRouter, etc., and dispatch subagents to it like any built-in model. No login, no extra setup: permissions, steering, and fallback just work.
3
+ Manage every model Coder can dispatch to. That means the built-in Codex and Claude aliases, and your own - any OpenAI-compatible URL, like Ollama or llama.cpp on your laptop, a vLLM GPU box, OpenRouter, etc. Custom models work like any built-in: no login, and permissions, steering, and fallback just work.
4
4
 
5
5
  ## Connect a model
6
6
 
@@ -38,14 +38,38 @@ Add `custom` to the fallback chain to make your models a fallback tier, e.g. a l
38
38
  coder config set chain '["codex", "claude", "custom"]'
39
39
  ```
40
40
 
41
+ ## Built-ins, aliases, disabling
42
+
43
+ Built-ins are just pre-seeded aliases tied to an engine, so `coder model list`
44
+ shows them and your own aliases together, grouped per engine. Disable
45
+ any model name - a built-in, a custom model, an alias, or a raw engine slug
46
+ like `gpt-5.6-terra` - without removing its configuration, then re-enable it
47
+ when needed; requests for a disabled model fail at dispatch:
48
+
49
+ ```bash
50
+ coder model disable terra
51
+ coder model enable terra
52
+ ```
53
+
54
+ Create an alias for an agent spec with `coder model alias fast codex:mini`
55
+ (effort can ride along: `coder model alias big claude:opus:high`); remove it
56
+ with `coder model unalias fast`. Aliases may reuse a built-in name, so
57
+ `coder model alias mini codex:gpt-x` intentionally overrides the built-in
58
+ `mini`, and `model list` shows the override in its place.
59
+
41
60
  ## Config shape
42
61
 
43
- `coder model add` writes the `models` section of the [config](../README.md#configuration):
62
+ Every model command writes the one `models` section of the
63
+ [config](config.md); the entry's shape says what it is (endpoint,
64
+ engine alias, or a bare disable toggle for any model name):
44
65
 
45
66
  ```json
46
67
  {
47
68
  "models": {
48
- "qwen": { "baseUrl": "http://localhost:11434/v1", "model": "qwen2.5-coder:32b", "envKey": "MY_KEY" }
69
+ "qwen": { "baseUrl": "http://localhost:11434/v1", "model": "qwen2.5-coder:32b", "envKey": "MY_KEY" },
70
+ "fast": { "provider": "codex", "model": "gpt-5.4-mini" },
71
+ "big": { "provider": "claude", "model": "opus", "effort": "high" },
72
+ "terra": { "disabled": true }
49
73
  }
50
74
  }
51
75
  ```
package/docs/sdk.md ADDED
@@ -0,0 +1,82 @@
1
+ # SDK
2
+
3
+ Everything the CLI does, as a library. The SDK mirrors the CLI exactly: every command group is a namespace, every subcommand a function, with the same names and the same options (`coder --help` is the reference for both).
4
+
5
+ ```ts
6
+ import coder from '@wular/coder';
7
+ // or pick namespaces: import { task, flow, model, config } from '@wular/coder';
8
+
9
+ const { taskId } = await coder.task.run('Explain this repo', {
10
+ model: 'terra',
11
+ });
12
+
13
+ const res = await coder.task.result(taskId, { wait: true });
14
+
15
+ console.log(res.result?.finalMessage);
16
+ ```
17
+
18
+ SDK functions never print and never call `process.exit`; results come back as typed values and failures as a typed `CoderError` whose `code` says what went wrong. The case the CLI maps to exit code 3 (every engine in the chain failed to start) is `code: 'chain-exhausted'`, carrying the same fallback payload; a task blocked on a permission is `'approval-pending'`, carrying the approval to answer.
19
+
20
+ ## CLI to SDK mapping
21
+
22
+ | CLI | SDK |
23
+ | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
24
+ | `coder run "<text>"` | `task.run(prompt, opts?)` |
25
+ | `coder list` | `task.list(opts?)` |
26
+ | `coder result [id] [--wait] [--tail N]` | `task.result(id?, { wait?, tail? })` |
27
+ | `coder task steer <id> "<text>"` | `task.steer(id, text)` |
28
+ | `coder task stop <id>` | `task.stop(id)` |
29
+ | `coder task stream [id] [--tail N]` | `task.stream(id?, { tail? })` - async iterable of progress events |
30
+ | `coder task approvals [id]` | `task.approvals(id?)` |
31
+ | `coder task approve <id> <appr>` | `task.approve(id, approvalId, { deny? })` |
32
+ | `coder task archive / delete <id>` | `task.archive(id)` / `task.delete(id)` |
33
+ | `coder flow run <name>` | `flow.run(nameOrPath, { args?, concurrency?, maxTasks? })` |
34
+ | `coder flow list` | `flow.list()` |
35
+ | `coder flow discover` | `flow.discover()` |
36
+ | `coder flow result [id] [--tail N]` | `flow.result(id?, { tail? })` |
37
+ | `coder flow stream [id] [--tail N]` | `flow.stream(id?, { tail? })` |
38
+ | `coder flow stop [id]` | `flow.stop(id?, { keepTasks? })` |
39
+ | `coder flow resume [id]` | `flow.resume(id?)` |
40
+ | `coder flow archive / delete <id>` | `flow.archive(id)` / `flow.delete(id)` |
41
+ | `coder model add / update / remove / list / alias / unalias / disable / enable` | `model.add(name, opts)` / `model.update(...)` / `model.remove(name)` / `model.list()` / `model.alias(name, spec)` / `model.unalias(name)` / `model.disable(name)` / `model.enable(name)` |
42
+ | `coder config get / set` | `config.get(key?)` / `config.set(key, value)` |
43
+ | `coder setup-host [hosts...]` | `setupHost(hosts?)` |
44
+ | `coder upgrade` | `upgrade({ cliOnly?, pluginsOnly? })` |
45
+ | `coder docs [topic]` | `docs(topic?)` |
46
+
47
+ Options objects take the same names as the CLI flags, camelCased (`--max-tasks` becomes `maxTasks`).
48
+
49
+ ## Examples
50
+
51
+ Dispatch a wave and wait for all of it:
52
+
53
+ ```js
54
+ import { task } from '@wular/coder';
55
+
56
+ const ids = await Promise.all(
57
+ areas.map(a => task.run(`Audit ${a}`, { permissions: 'read-only' })),
58
+ );
59
+
60
+ const results = await Promise.all(
61
+ ids.map(({ taskId }) => task.result(taskId, { wait: true })),
62
+ );
63
+ ```
64
+
65
+ Follow a task live:
66
+
67
+ ```ts
68
+ for await (const event of task.stream(taskId)) {
69
+ console.log(event.kind, event.text);
70
+ }
71
+ ```
72
+
73
+ Run a flow from your own tooling:
74
+
75
+ ```ts
76
+ import { flow } from '@wular/coder';
77
+
78
+ const run = await flow.run('fix-tests', { args: { files } });
79
+ console.log(run.result, run.tokens);
80
+ ```
81
+
82
+ If you need a wave with gates, journaling, and resume, write a [flow](flows.md) instead of hand-rolling it on `task.*`; the flow runtime is exactly this SDK plus those services.
package/package.json CHANGED
@@ -1,21 +1,31 @@
1
1
  {
2
2
  "name": "@wular/coder",
3
- "version": "0.2.12",
3
+ "version": "0.3.1",
4
4
  "description": "Delegating coding-agent runtime",
5
5
  "type": "module",
6
+ "main": "./dist/sdk.js",
7
+ "types": "./dist/sdk.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/sdk.d.ts",
11
+ "import": "./dist/sdk.js"
12
+ },
13
+ "./flow": {
14
+ "types": "./dist/flow/index.d.ts",
15
+ "import": "./dist/flow/index.js"
16
+ }
17
+ },
6
18
  "bin": {
7
19
  "coder": "bin/coder.mjs"
8
20
  },
9
21
  "scripts": {
10
- "build": "bun build src/cli.ts src/lib/broker.ts --outdir dist --root src --target node --format esm --minify",
22
+ "build": "tsdown && bunx prettier --write 'dist/**/*.d.ts' --log-level warn",
11
23
  "dev": "bun run build --watch",
12
24
  "typecheck": "tsc -p tsconfig.json --noEmit",
13
25
  "validate-plugin:cursor": "node scripts/validate-template.mjs",
14
26
  "version-bump:patch": "node scripts/bump.mjs patch",
15
27
  "version-bump:minor": "node scripts/bump.mjs minor",
16
- "version-bump:major": "node scripts/bump.mjs major",
17
- "prepublishOnly": "bun run build && bun run typecheck",
18
- "npm-publish": "npm publish --access public --tag latest --registry=https://registry.npmjs.org --//registry.npmjs.org/:_authToken=\"$NPM_TOKEN\""
28
+ "version-bump:major": "node scripts/bump.mjs major"
19
29
  },
20
30
  "files": [
21
31
  "bin",
@@ -33,5 +43,8 @@
33
43
  },
34
44
  "dependencies": {
35
45
  "zod": "^4.4.3"
46
+ },
47
+ "devDependencies": {
48
+ "tsdown": "^0.22.13"
36
49
  }
37
50
  }