@ashrlabs/ashr 0.0.0-stage → 0.2.0

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.
Files changed (3) hide show
  1. package/README.md +338 -2
  2. package/dist/ashr.js +735 -0
  3. package/package.json +50 -4
package/dist/ashr.js ADDED
@@ -0,0 +1,735 @@
1
+ #!/usr/bin/env node
2
+ var ze={name:"@ashrlabs/ashr",version:"0.2.0",description:"Ashr terminal agent for post-training models",repository:{type:"git",url:"git+https://github.com/ashrlabs/manifold.git",directory:"cli"},type:"module",engines:{node:">=22.19.0"},bin:{ashr:"dist/ashr.js"},files:["dist"],scripts:{build:"node scripts/build.mjs","build:binary":"node scripts/build-binary.mjs",typecheck:"tsc -p tsconfig.json --noEmit",test:"vitest --run","test:parity":"vitest --run --config vitest.parity.config.ts","test:parity:browser":"node scripts/test-parity-browser.mjs"},dependencies:{"@earendil-works/pi-ai":"1.1.0","@earendil-works/pi-tui":"1.1.0",diff:"8.0.4","highlight.js":"10.7.3","shell-quote":"1.12.0"},devDependencies:{"@earendil-works/chord":"1.1.0","@earendil-works/pi-agent-core":"1.1.0","@earendil-works/pi-codemode":"1.1.0","@earendil-works/pi-coding-agent":"1.1.0","@earendil-works/pi-durable":"1.1.0","@types/node":"22.19.19","@types/picomatch":"4.0.3","@types/shell-quote":"1.7.5","@xterm/headless":"6.0.0",esbuild:"0.28.2","js-tiktoken":"1.0.21",picomatch:"4.0.7",postject:"1.0.0-alpha.6",typescript:"7.0.2",vitest:"4.1.9"}};var me=`${ze.version} (74ddf7af, 2026-10-23)`;import{mkdirSync as wr,writeFileSync as br}from"node:fs";import{dirname as yr}from"node:path";import{createHash as ur}from"node:crypto";import{existsSync as pr,readFileSync as hr}from"node:fs";import{homedir as mr}from"node:os";import{basename as fr,join as ce,resolve as Yt}from"node:path";function gr(r=process.env){return r.ASHR_HOME?Yt(r.ASHR_HOME):ce(mr(),".ashr")}function Jt(r){let e=Yt(r);return`${fr(e).replace(/[^A-Za-z0-9._-]/g,"_")||"root"}-${ur("sha256").update(e).digest("hex").slice(0,8)}`}function ee(r=process.env){let e=gr(r);return{home:e,credentials:ce(e,"credentials.json"),settings:ce(e,"settings.json"),trust:ce(e,"trust.json"),agentDir:ce(e,"agent"),memory:ce(e,"memory.md"),sessions:(t,n)=>ce(e,"sessions",t,Jt(n)),worktrees:(t,n)=>ce(e,"worktrees",Jt(t),n)}}function Vt(r){if(pr(r))try{return JSON.parse(hr(r,"utf8"))}catch(e){throw new Error(`${r} is not valid JSON (${e.message}). Fix or delete it.`)}}function Ae(r=process.env){let e=Vt(ee(r).credentials)??{};return{...e.manifold?{manifold:e.manifold}:{},...e.account?{account:e.account}:{},providers:e.providers??{}}}function re(r,e=process.env){let t=ee(e).credentials;wr(yr(t),{recursive:!0}),br(t,`${JSON.stringify(r,null,2)}
3
+ `,{mode:384})}var W="https://api.manifold.ashr.io",de="https://manifold.ashr.io",fe=class extends Error{constructor(t,n){super(t);this.status=n;this.name="KeyRejectedError"}status};function gt(r){let e=new URL(r);if(!["http:","https:"].includes(e.protocol)||e.username||e.password||e.search||e.hash)throw new Error("Use HTTPS or a loopback HTTP API URL without credentials");if(e.protocol==="http:"&&!["localhost","127.0.0.1","[::1]"].includes(e.hostname))throw new Error(`${r} must use https, or reach the server through a port-forward: ssh -L 8080:localhost:8080 <host>, then --api-url http://localhost:8080`);return e}async function Re(r,e,t=fetch){gt(r);let n;try{n=await t(`${r.replace(/\/$/,"")}/v1/tasks`,{headers:{Authorization:`Bearer ${e}`}})}catch(s){throw new Error(`Couldn't reach ${r}: ${s.message}. Check the address and your connection.`)}if(n.status===401||n.status===403)throw new fe(`That key was rejected by ${r}. Create one in the Manifold dashboard under API keys.`,n.status);if(!n.ok)throw new fe(`Manifold answered ${n.status} at ${r}. Try again, or check the address.`,n.status)}var wt="Sign in to a self-hosted server with: ashr login --key <key> --api-url <url>",kr=new URL(W).hostname;function vr(r){try{return new URL(r).hostname===kr}catch{return!1}}function Ge(r,e,t){return[r,e.MANIFOLD_API_URL,t.manifold?.apiUrl].find(n=>!!n&&!vr(n))}async function Zt(r,e,t){let n=t.writeErr??(a=>process.stderr.write(a)),s;if(!0)s=r.apiUrl??t.env.MANIFOLD_API_URL??W;else{if(!r.key)return n(`${wt}
4
+ `),2;let a=Ge(r.apiUrl,t.env,e);if(!a)return n(`${Ie}
5
+ `),2;s=a}let i=r.key??(t.prompt?await t.prompt("Manifold API key (ask_\u2026): "):"");if(!i)throw new Error("No key entered.");return await Re(s,i,t.fetch),(t.save??re)({...e,manifold:{apiUrl:s,key:i,profile:e.manifold?.profile??"default"}}),(t.write??(a=>process.stdout.write(a)))(`Signed in to ${s}.
6
+ `),0}var Xt=`Usage:
7
+ ashr start the agent in this repo
8
+ ashr init [--yes] run the Ashr agent's sandboxes on this Docker host (your repo, data and GPUs
9
+ stay here); re-run to upgrade (to set up a repo, run ashr in it and type /init)
10
+ ashr -c, --continue reopen your latest session here
11
+ ashr -r, --resume pick a session to resume
12
+ ashr -p "<prompt>" run one turn and print the answer
13
+ --mode ask|edit|auto permission mode (default from settings, else auto)
14
+ --allow "<rule>" allow one more rule for this run (repeatable)
15
+ --output text|json how to print the result
16
+ ashr --model provider/id use this model
17
+ ashr login codex ChatGPT through the Codex sign-in
18
+ ashr login chatgpt | claude sign in with your model subscription
19
+ ashr login --provider anthropic [--key sk-\u2026] store a model provider key
20
+ ashr logout chatgpt | codex | claude | --provider <name>
21
+ ashr update install the latest ashr build over this one
22
+ ashr telemetry [status | show | on | off] what usage data ashr sends, and turning it off
23
+ ashr --version | --help`,_r=`${Xt}
24
+
25
+ Hosted Manifold workspace:
26
+ ashr --hosted open the hosted Manifold workspace (--local picks the local agent)
27
+ ashr --task <name> select a hosted task
28
+ ashr --chat <id> open an existing hosted chat
29
+ ashr --service require a Manifold service key (never opens a browser)
30
+ --api-url <url> select the API deployment
31
+ --dashboard-url <url> select the dashboard used for browser sign-in
32
+ ashr login [--key ask_\u2026] [--api-url <url>] sign in to Manifold (browser, or a service key)
33
+ ashr logout forget the saved Manifold sign-in`,xr=_r,Ie="This ashr runs on your machine only: it has no hosted workspace and no Manifold sign-in. Sign in to your model provider with ashr login codex, ashr login chatgpt, ashr login claude, or ashr login --provider anthropic.",j=class extends Error{constructor(e){super(`${e}
34
+
35
+ ${xr}`)}};function Qt(r){let e={command:"run",runtime:"hosted",runtimeChosen:!1,service:!1,continue:!1,resume:!1,allow:[],output:"text"},t,n=[...r];if(n[0]==="update")e.command="update",n.shift();else if(n[0]==="init"){if(e.command="init",n.shift(),n[0]!==void 0&&!n[0].startsWith("-"))throw new j("ashr init sets this host up to run the Ashr agent's sandboxes and takes no repository. To set up a repo, clone it, run ashr in it and type /init.")}else if(n[0]==="telemetry"){e.command="telemetry",n.shift();let a=n[0]!==void 0&&!n[0].startsWith("-")?n.shift():"status";if(!["status","show","on","off"].includes(a))throw new j(`ashr telemetry takes status, show, on or off, not "${a}".`);e.telemetryAction=a}else(n[0]==="login"||n[0]==="logout")&&(e.command=n.shift(),n[0]!==void 0&&!n[0].startsWith("-")&&(e.subscription=n.shift()));let s=a=>{let o=n.shift();if(o===void 0||o.startsWith("-")&&o.length>1)throw new j(`${a} needs a value.`);return o};for(;n.length;){let a=n.shift();if(!!0&&a==="--dashboard-url")throw new j(Ie);switch(a){case"--local":case"--hosted":{let o=a==="--local"?"local":"hosted";if(t&&t!==o)throw new j("Choose either --local or --hosted.");t=o;break}case"--service":e.service=!0;break;case"-y":case"--yes":e.yes=!0;break;case"--task":e.task=s(a);break;case"--chat":e.chat=s(a);break;case"--dashboard-url":e.dashboardUrl=s(a);break;case"-h":case"--help":e.command="help";break;case"-v":case"--version":e.command="version";break;case"-c":case"--continue":e.continue=!0;break;case"-r":case"--resume":e.resume=!0;break;case"-p":case"--print":e.print=s(a);break;case"--model":e.model=s(a);break;case"--allow":e.allow.push(s(a));break;case"--key":e.key=s(a);break;case"--api-url":e.apiUrl=s(a);break;case"--provider":e.provider=s(a);break;case"--mode":{let o=s(a);if(o!=="ask"&&o!=="edit"&&o!=="auto")throw new j(`--mode must be ask, edit or auto, not "${o}".`);e.mode=o;break}case"--output":{let o=s(a);if(o!=="text"&&o!=="json")throw new j(`--output must be text or json, not "${o}".`);e.output=o;break}default:throw new j(`Unknown option ${a}.`)}}if(e.yes&&e.command!=="init")throw new j("--yes goes with ashr init.");let i=e.print!==void 0||e.resume||e.model!==void 0||e.mode!==void 0||e.allow.length>0;if(e.runtime=t??(i?"local":"hosted"),e.runtimeChosen=t!==void 0||i||!!(e.task||e.chat||e.service),e.command==="run"){if(e.runtime==="hosted"&&i)throw new j("Print, resume, model and permission flags apply to --local sessions only. Use /model within hosted chats.");if(e.runtime==="local"&&(e.task||e.chat||e.service))throw new j("Task, chat and service selection apply to hosted sessions only.");if(e.runtime==="hosted"&&e.continue&&e.chat)throw new j("Use either --continue or --chat, not both.")}return e}import{hostname as Ii}from"node:os";import{hostname as Ir}from"node:os";import{createServer as Sr}from"node:http";import{randomBytes as Er,timingSafeEqual as $r}from"node:crypto";var Tr=(r,e)=>Buffer.byteLength(r)===Buffer.byteLength(e)&&$r(Buffer.from(r),Buffer.from(e));async function Ar(r){let e=0,t=[];for await(let n of r){if(e+=n.length,e>16384)throw Error("Too large");t.push(n)}return new URLSearchParams(Buffer.concat(t).toString())}async function We(r){let e=new URL(r.dashboardUrl);if(e.protocol!=="https:"&&!(e.protocol==="http:"&&["127.0.0.1","localhost"].includes(e.hostname)))throw Error("Dashboard must use HTTPS (or explicitly configured local development).");if(e.username||e.password)throw Error("Dashboard URL cannot contain credentials.");if(r.signal?.aborted)throw Error("Sign-in cancelled.");let t=Er(32).toString("hex"),n="",s,i=new Promise((p,g)=>{s={resolve:p,reject:g}}),a=(p,g,f,b)=>{p.writeHead(g,{"Content-Type":"text/html; charset=utf-8","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'"}),p.end(Rr(f,b))},o=Sr(async(p,g)=>{if(g.setHeader("Cache-Control","no-store"),g.setHeader("Referrer-Policy","no-referrer"),g.setHeader("X-Content-Type-Options","nosniff"),p.headers.host!==n||p.method!=="POST"||p.url!=="/callback"||p.headers.origin!==void 0&&p.headers.origin!=="null"&&p.headers.origin!==e.origin){a(g,403,"This link doesn't belong to this terminal.","Run ashr again to start a new sign-in.");return}let f;try{f=await Ar(p)}catch{a(g,400,"Sign-in failed.","Run ashr again to start a new sign-in.");return}if(!Tr(f.get("state")??"",t)){a(g,403,"This link doesn't belong to this terminal.","Run ashr again to start a new sign-in.");return}if(f.get("error")){a(g,200,"Sign-in cancelled.","Nothing was shared. You can close this tab."),c(Error("Sign-in was cancelled in the browser."));return}let b=f.get("key")??"";if(!(r.keyPattern??/^ask_[A-Za-z0-9]{8,200}$/).test(b)){a(g,400,"Sign-in failed.","Manifold didn't return a usable key. Run ashr again."),c(Error("The dashboard returned no usable key."));return}r.redirectTo?(g.writeHead(303,{Location:r.redirectTo}),g.end()):a(g,200,"You're signed in.","Return to your terminal. You can close this tab."),c(void 0,{key:b,email:f.get("email")||void 0,organization:f.get("organization")||void 0})});o.requestTimeout=1e4,o.headersTimeout=1e4;let l=setTimeout(()=>c(Error("Sign-in timed out. Run ashr again.")),6e5),d=()=>c(Error("Sign-in cancelled."));function c(p,g){if(!s)return;let{resolve:f,reject:b}=s;s=void 0,clearTimeout(l),r.signal?.removeEventListener("abort",d),setTimeout(()=>{o.close(),o.closeAllConnections()},250).unref(),p?b(p):f(g)}await new Promise((p,g)=>{o.once("error",g),o.listen(0,"127.0.0.1",p)});let u=o.address();if(!u||typeof u=="string")return c(Error("Loopback listener failed.")),i;n=`127.0.0.1:${u.port}`,r.signal?.addEventListener("abort",d,{once:!0});let h=new URL(r.path??"/terminal",e);return h.search=new URLSearchParams({port:String(u.port),state:t,device:r.device}).toString(),r.onUrl(h.toString()),i}function Rr(r,e){let t=n=>n.replace(/[&<>"]/g,s=>`&#${s.charCodeAt(0)};`);return`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${t(r)} \xB7 Ashr</title><style>
36
+ :root{--paper:#faf8f4;--raised:#fffdf9;--ink:#141310;--soft:#57544c;--line:#dcd8ce;--accent:#4d41d0}
37
+ @media (prefers-color-scheme:dark){:root{--paper:#141310;--raised:#1c1b17;--ink:#f4f1e9;--soft:#a9a49a;--line:#34322c;--accent:#a49bff}}
38
+ *{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:3rem 1.25rem;background:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px),var(--paper);background-size:2.5rem 2.5rem;color:var(--ink);font:15px/1.55 "Helvetica Neue",system-ui,sans-serif}
39
+ main{width:min(28rem,100%);padding:2.5rem;background:var(--raised);border:1px solid var(--line);box-shadow:0 18px 40px -18px rgba(20,19,16,.25)}
40
+ p.e{margin:0 0 .8rem;color:var(--accent);font:500 .68rem ui-monospace,monospace;letter-spacing:.18em}
41
+ h1{margin:0 0 .6rem;font-size:2rem;font-weight:650;letter-spacing:-.035em;line-height:1.05}p{margin:0;color:var(--soft)}
42
+ </style></head><body><main><p class="e">ASHR TERMINAL</p><h1>${t(r)}</h1><p>${t(e)}</p></main></body></html>`}function x(r){return(typeof r=="string"?r:JSON.stringify(r,null,2)??String(r)).replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g,"").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g,"")}var bt=/^acl_[0-9a-f]{32}$/;function Pr(r){return new URL("/cli",r.MANIFOLD_DASHBOARD_URL??de).toString()}function en(r){return!!(r.SSH_CONNECTION||r.SSH_TTY)}var Cr=3;async function tn(r,e){if(r.account&&bt.test(r.account.key))return r.account;let t=f=>b=>e.tty?`\x1B[${f}m${b}\x1B[0m`:b,n={bold:t("1"),dim:t("2"),accent:t("38;2;134;122;254"),green:t("32"),red:t("31"),yellow:t("33")},s=Pr(e.env),i=Ir().replace(/\.local$/,""),a=new AbortController,o=()=>a.abort();process.once("SIGINT",o);let l=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],d,c=()=>{d&&(clearInterval(d),d=void 0,e.write("\r\x1B[2K"))},u=new URL(s);u.search=new URLSearchParams({paste:"1",device:i}).toString();let h=u.toString(),p=async f=>{f||(e.write(` Open this link in a browser on any machine, sign in, and paste the code it shows:
43
+ ${n.accent(h)}
44
+
45
+ `),en(e.env)||e.openBrowser(h).catch(()=>{}));for(let b=1;;b++){let _=(await e.readCode(" Paste the code from the browser: ")).trim();if(bt.test(_))return{key:_};if(!_)throw Error("no code was pasted");if(b>=Cr)throw Error("that isn't an ashr account code");e.write(` ${n.red("\u2717")} That isn't an ashr account code (it starts with acl_). Try again, or press Enter to skip.
46
+ `)}},g=async()=>{let f=!1,b=new AbortController,_;try{return await(e.startLogin??We)({dashboardUrl:s,path:"/cli",keyPattern:bt,redirectTo:`${s}?connected=1`,device:i,signal:AbortSignal.any([a.signal,b.signal]),onUrl:m=>{if(f=!0,e.write(` Opening your browser\u2026
47
+ ${n.dim("If it doesn't open, visit:")}
48
+ ${n.accent(m)}
49
+
50
+ `),e.openBrowser(m).catch(()=>{}),!e.tty){e.write(` Waiting for you to sign in in the browser\u2026
51
+ `);return}e.onKeys&&(e.write(` ${n.dim("Browser on another machine? Open this link there, then press Enter to paste a code instead:")}
52
+ ${n.accent(h)}
53
+
54
+ `),_=e.onKeys({enter:()=>b.abort(),interrupt:()=>a.abort()}));let w=0,E=n.dim(`${e.onKeys?"\xB7 Enter to paste a code ":""}\xB7 Ctrl+C to cancel`),k=()=>`\r ${n.accent(l[w++%l.length])} Waiting for you to sign in in the browser ${E}`;e.write(k()),d=setInterval(()=>e.write(k()),80)}})}catch(m){if(a.signal.aborted)throw m;if(b.signal.aborted)return c(),{linkShown:!0};if(f)throw m;return{linkShown:!1}}finally{_?.()}};try{e.write(`
55
+ ${n.bold("ashr")}
56
+
57
+ Sign in once to start.
58
+ `);let f=en(e.env)?{linkShown:!1}:await g(),b="key"in f?f:await p(f.linkShown);c();let _={key:b.key,...b.email?{email:b.email}:{}};return(e.saveCredentials??re)({...r,account:_}),e.write(` ${n.green("\u2713")} Signed in${b.email?` as ${n.bold(x(b.email))}`:""}. Usage and cost: ${n.accent(s)} ${n.dim("(or /account)")}
59
+
60
+ `),_}catch(f){if(c(),a.signal.aborted)return e.write(` Sign-in cancelled.
61
+ `),"cancelled";let b=x(f.message).split(/(?<=\.)\s/)[0].replace(/\.$/,"");e.write(` ${n.yellow("!")} Couldn't sign in: ${b}. Starting without an account; ashr asks again next run.
62
+
63
+ `);return}finally{process.removeListener("SIGINT",o)}}import{spawn as hi}from"node:child_process";import{hostname as mi}from"node:os";import{ProcessTerminal as fi}from"@earendil-works/pi-tui";var z=class extends Error{constructor(t,n){super(`HTTP ${t}: ${n}`);this.status=t;this.detail=n}status;detail},ge=class{constructor(e){this.options=e;this.base=gt(e.apiUrl)}options;base;get apiOrigin(){return this.base.origin}async request(e,t={}){let n=await this.requestResponse(e,t,"application/json");if(n.status!==204)return await n.json()}async requestStream(e,t={}){return this.requestResponse(e,t,"text/event-stream")}async requestResponse(e,t,n){let s=decodeURIComponent(e.split("?")[0]);if(!s.startsWith("/v1/")||s.includes("\\")||s.split("/").some(c=>c===".."||c===".")||/[\x00-\x1f#]/.test(s)||/[\x00-\x20#]/.test(e.split("?")[0])||/%2f|%5c/i.test(e.split("?")[0]))throw new Error("Invalid API path");let i=new URL(e,this.base);if(i.origin!==this.base.origin||!i.pathname.startsWith("/v1/"))throw new Error("Invalid API path");t.signal?.throwIfAborted();let a=t.signal,o=a?await new Promise((c,u)=>{let h=()=>{a.removeEventListener("abort",h),u(a.reason??new DOMException("Local request paused","AbortError"))};a.addEventListener("abort",h,{once:!0}),Promise.resolve().then(()=>this.options.getToken()).then(p=>{a.removeEventListener("abort",h),c(p)},p=>{a.removeEventListener("abort",h),u(p)})}):await this.options.getToken();if(t.signal?.throwIfAborted(),!o)throw new z(401,"Authentication required");let l=new Headers(t.headers);l.has("authorization")||l.set("authorization",`Bearer ${o}`),l.set("accept",n),t.body&&!l.has("content-type")&&l.set("content-type","application/json");let d;try{d=await(this.options.fetch??fetch)(i,{...t,headers:l,redirect:"manual"})}catch{throw t.signal?.aborted?t.signal.reason??new DOMException("Local request paused","AbortError"):new Error("Connection failed. The request may have been accepted; retry with the same request ID.")}if(d.status>=300&&d.status<400)throw new Error("API redirect refused");if(!d.ok){let c="Request failed";try{let u=await d.json();c=typeof u.detail=="string"?u.detail:JSON.stringify(u.detail??c)}catch{}throw new z(d.status,[o,l.get("authorization")?.replace(/^Bearer /,"")??""].filter(Boolean).reduce((u,h)=>u.split(h).join("[credential]"),c))}return d}};import{CombinedAutocompleteProvider as Fs,TuiMainScreen as Bs,matchesKey as ie}from"@earendil-works/pi-tui";import{parse as Ks}from"shell-quote";import{randomUUID as Or}from"node:crypto";import{lstat as nn,readFile as Nr,readdir as Mr,open as Dr}from"node:fs/promises";import{basename as Lr,join as rn,resolve as on}from"node:path";function sn(r){if(!r||r.length>300||r.includes("\\")||r.startsWith("/")||r.split("/").some(e=>!e||e==="."||e===".."||e.startsWith("."))||/[\x00-\x1f]/.test(r))throw Error("Use a safe relative skill path");return r}async function yt(r,e){let t=await Dr(on(r),"wx",384);try{await t.writeFile(e)}finally{await t.close()}}async function an(r){let e=on(r),t=await nn(e);if(t.isSymbolicLink())throw Error("Skill paths cannot be symbolic links");let n={},s=0;async function i(d,c){sn(c);let u=await nn(d);if(u.isSymbolicLink()||!u.isFile())throw Error("Skill paths must be regular files");if(u.size>1e6)throw Error("Skill exceeds 1 MB");let h=await Nr(d,"utf8");if(s+=Buffer.byteLength(h),s>1e6||Object.keys(n).length>=100)throw Error("Import at most 100 files totaling 1 MB");n[c]=h}async function a(d,c=""){for(let u of await Mr(d,{withFileTypes:!0})){let h=c+u.name;if(sn(h),u.isSymbolicLink())throw Error("Skill paths cannot be symbolic links");u.isDirectory()?await a(rn(d,u.name),h+"/"):await i(rn(d,u.name),h)}}if(t.isDirectory())await a(e);else{if(Lr(e)!=="SKILL.md")throw Error("Import a SKILL.md file or skill folder");await i(e,"SKILL.md")}let o=n["SKILL.md"]??"",l=/^name:\s*([a-z0-9]+(?:-[a-z0-9]+)*)\s*$/m.exec(o)?.[1];if(!o.startsWith(`---
64
+ `)&&!o.startsWith(`---\r
65
+ `)||!l||l.length>64||!/^description:\s*\S/m.test(o))throw Error("SKILL.md needs name and description frontmatter");return{name:l,files:n,enabled:!0}}function Je(r){let e=r.trim().toLowerCase().replace(/[\s-]+/g,"_");if(!/^[a-z0-9_]+$/.test(e)||e.length>200)throw Error("Enter a valid task name");return e}var Ye=class{constructor(e,t){this.client=e;this.scope=t===void 0?void 0:Je(t)}client;detail;events=[];cursor=0;connection="Disconnected";generation=0;pending=new Map;scope;pollInFlight;get task(){return this.scope}get chat(){return this.detail?.id}get busy(){return["working","queued","stopping"].includes(this.detail?.status??"")}path(e=this.chat){if(!e)throw Error("Open or create a hosted chat first");return`/v1/agent/chats/${encodeURIComponent(e)}`}async list(e=""){return(await this.client.request(`/v1/agent/chats${this.scope?"?task="+encodeURIComponent(this.scope):""}`)).chats.filter(n=>n.name.toLowerCase().includes(e.toLowerCase())||n.id.includes(e))}setTask(e){this.scope=e===void 0?void 0:Je(e),this.generation++,this.detail=void 0,this.events=[],this.cursor=0,this.connection="Disconnected"}async open(e){let t=++this.generation;this.detail=void 0,this.events=[],this.cursor=0,this.connection="Connecting";try{let n=await this.client.request(this.path(e));if(t!==this.generation)return;if(this.scope&&n.task!==this.scope)throw Error("Chat belongs to a different task");this.detail=n}catch(n){throw t===this.generation&&(this.connection="Open failed"),n}}async create(e="New chat"){let t=this.generation,n=await this.client.request("/v1/agent/chats",{method:"POST",body:JSON.stringify({name:e,task:this.scope??null})});return t===this.generation&&await this.open(n.id),n}async refresh(){let e=this.generation,t=this.chat;if(!t)return;let n=await this.client.request(this.path(t));e===this.generation&&(this.detail=n)}async rename(e){if(!e.trim()||e.length>120)throw Error("Name must be 1\u2013120 characters");let t=this.generation,n=await this.client.request(this.path(),{method:"PATCH",body:JSON.stringify({name:e})});t===this.generation&&this.detail&&(this.detail={...this.detail,...n})}async fork(e,t){let n=this.generation,s=await this.client.request(this.path()+"/fork",{method:"POST",body:JSON.stringify({name:e,...t===void 0?{}:{through:t}})});return n===this.generation&&await this.open(s.id),s}poll(){if(this.pollInFlight)return this.pollInFlight;let e=this.pollPage();return this.pollInFlight=e,e.finally(()=>{this.pollInFlight===e&&(this.pollInFlight=void 0)}).catch(()=>{}),e}async pollPage(){if(!this.chat)return;let e=this.generation,t=this.chat;try{let n=await this.client.request(this.path(t)+`/events?after=${this.cursor}&limit=500`);if(e!==this.generation)return;let s=new Set(this.events.map(i=>i.seq));for(let i of n.events){if(!Number.isSafeInteger(i.seq)||i.seq<=0)throw Error("Invalid event cursor");s.has(i.seq)||(this.events.push(i),s.add(i.seq),i.kind==="status"&&this.detail&&(this.detail.status=String(i.data.status)))}if(this.events.sort((i,a)=>i.seq-a.seq),!Number.isSafeInteger(n.cursor)||n.cursor<this.cursor)throw Error("Invalid event cursor");this.cursor=n.cursor,this.connection="Connected"}catch(n){throw e===this.generation&&(this.connection=n instanceof z&&[401,403].includes(n.status)?"Authentication required":"Disconnected"),n}}async send(e,t="",n={}){if(["prompt","steer","follow_up","shell"].includes(e)&&!t.trim())throw Error("Enter a message or command");if(t.length>1e5)throw Error("Command exceeds 100000 characters");let s=this.chat;this.path(s);let a=[...this.pending.values()].find(o=>o.chat===s&&o.body.kind===e&&o.body.text===t&&JSON.stringify(o.body.options)===JSON.stringify(n))??{chat:s,body:{request_id:Or(),kind:e,text:t,options:n}};return this.pending.set(a.body.request_id,a),this.deliver(a)}async deliver(e){let t;for(let n=50;;n=Math.min(n*2,1e3))try{t=await this.client.request(this.path(e.chat)+"/commands",{method:"POST",body:JSON.stringify(e.body)});break}catch(s){if(!(s instanceof z&&s.status===409&&/^Chat changed/.test(s.detail)))throw s;await new Promise(i=>setTimeout(i,n*(.5+Math.random())))}return this.pending.delete(e.body.request_id),this.detail?.id===e.chat&&(e.body.kind==="stop"?this.detail.status="stopping":!["cancel","approve","reject"].includes(e.body.kind)&&["idle","failed","stopped"].includes(this.detail.status)&&(this.detail.status="queued")),t}async retry(e){let t=[...this.pending.values()].filter(s=>s.chat===this.chat),n=e?this.pending.get(e):t.at(-1);if(!n||n.chat!==this.chat)throw Error("No uncertain command in this chat");return this.deliver(n)}async editQueue(e,t){let n=this.generation;if(await this.refresh(),n!==this.generation)throw Error("Chat changed during queue edit");let s=this.detail?.commands.find(i=>i.id===e);if(!s||!["accepted","held"].includes(s.status))throw Error("That command has already been delivered");if(!t.trim())throw Error("Enter replacement text");if(await this.send("cancel",e),n!==this.generation)throw Error("Chat changed after cancellation; original cancelled, replacement not sent");await this.send(s.kind,t,s.options),await this.refresh()}async skill(e){let t=this.generation,n=this.chat;await this.client.request(this.path(n)+"/skills",{method:"POST",body:JSON.stringify({name:e.name,files:e.files,enabled:e.enabled})}),t===this.generation&&(await this.refresh(),await this.send("reload"))}async export(){let e=this.generation;if(await this.pollInFlight,e!==this.generation)throw Error("Chat changed during export");await this.refresh();do{let t=this.events.length;if(await this.poll(),e!==this.generation)throw Error("Chat changed during export");if(this.events.length-t<500)break}while(!0);if(e!==this.generation)throw Error("Chat changed during export");return{format:"ashr-chat-v1",name:this.detail?.name,files:this.detail?.files,skills:this.detail?.skills,events:this.events.filter(t=>t.kind!=="delta")}}};import{Editor as us,truncateToWidth as Ze,visibleWidth as mn}from"@earendil-works/pi-tui";import{diffLines as Ur,diffWords as Hr}from"diff";import J from"highlight.js/lib/core.js";import qr from"highlight.js/lib/languages/bash.js";import jr from"highlight.js/lib/languages/c.js";import Fr from"highlight.js/lib/languages/cpp.js";import Br from"highlight.js/lib/languages/css.js";import Kr from"highlight.js/lib/languages/dockerfile.js";import zr from"highlight.js/lib/languages/go.js";import Gr from"highlight.js/lib/languages/ini.js";import Wr from"highlight.js/lib/languages/java.js";import Jr from"highlight.js/lib/languages/javascript.js";import Yr from"highlight.js/lib/languages/json.js";import Vr from"highlight.js/lib/languages/markdown.js";import Zr from"highlight.js/lib/languages/python.js";import Xr from"highlight.js/lib/languages/rust.js";import Qr from"highlight.js/lib/languages/sql.js";import es from"highlight.js/lib/languages/typescript.js";import ts from"highlight.js/lib/languages/xml.js";import ns from"highlight.js/lib/languages/yaml.js";for(let[r,e]of Object.entries({bash:qr,c:jr,cpp:Fr,css:Br,dockerfile:Kr,go:zr,ini:Gr,java:Wr,javascript:Jr,json:Yr,markdown:Vr,python:Zr,rust:Xr,sql:Qr,typescript:es,xml:ts,yaml:ns}))J.registerLanguage(r,e);J.registerAliases(["sh","shell","zsh","console"],{languageName:"bash"});J.registerAliases(["py","py3","python3"],{languageName:"python"});J.registerAliases(["js","jsx","mjs","cjs","node"],{languageName:"javascript"});J.registerAliases(["ts","tsx"],{languageName:"typescript"});J.registerAliases(["yml"],{languageName:"yaml"});J.registerAliases(["toml"],{languageName:"ini"});J.registerAliases(["html","svg"],{languageName:"xml"});J.registerAliases(["jsonl","jsonc"],{languageName:"json"});J.registerAliases(["md"],{languageName:"markdown"});var rs={dark:{comment:"#7d796f",keyword:"#a99cff",function:"#d4a24c",string:"#34c38f",number:"#e0a86a",type:"#8fb8ff",operator:"#a8a49a",punctuation:"#a8a49a",added:"#34c38f",removed:"#e07a5f",context:"#7d796f"},light:{comment:"#6f6a60",keyword:"#4d41d0",function:"#8f5f10",string:"#0b7a52",number:"#a5501c",type:"#2f5fb0",operator:"#57544c",punctuation:"#57544c",added:"#0b7a52",removed:"#b3452b",context:"#6f6a60"}};function dn(r,e){return t=>{let n=rs[r][t];if(e==="none"||!n)return l=>l;let[s,i,a]=[1,3,5].map(l=>parseInt(n.slice(l,l+2),16)),o=e==="truecolor"?`\x1B[38;2;${s};${i};${a}m`:`\x1B[38;5;${ss(s,i,a)}m`;return l=>`${o}${l}\x1B[39m`}}function ss(r,e,t){let n=s=>s<48?0:s<115?1:Math.min(5,Math.round((s-35)/40));return 16+36*n(r)+6*n(e)+n(t)}function is(r){return{keyword:r("keyword"),built_in:r("type"),literal:r("number"),number:r("number"),regexp:r("string"),string:r("string"),comment:r("comment"),doctag:r("comment"),meta:r("comment"),function:r("function"),title:r("function"),class:r("type"),type:r("type"),tag:r("punctuation"),name:r("keyword"),operator:r("operator"),punctuation:r("punctuation"),addition:r("added"),deletion:r("removed")}}var os={amp:"&",lt:"<",gt:">",quot:'"',apos:"'","#39":"'","#x27":"'","#x2F":"/"};function as(r,e){let t="",n="",s=[],i=()=>{if(!n)return;let a;for(let o=s.length-1;o>=0&&!a;o--){let l=s[o];l&&(a=e[l]??e[l.split(/[.-]/)[0]])}t+=a?a(n):n,n=""};for(let a=0;a<r.length;)if(r.startsWith("<span",a)){let o=r.indexOf(">",a);i(),s.push(/class="hljs-([^" ]+)/.exec(r.slice(a,o))?.[1]),a=o+1}else if(r.startsWith("</span>",a))i(),s.pop(),a+=7;else if(r[a]==="&"){let o=r.indexOf(";",a),l=o>a?os[r.slice(a+1,o)]:void 0;n+=l??"&",a=l?o+1:a+1}else n+=r[a++];return i(),t}function Pe(r,e,t,n){let s=e?.trim().toLowerCase();if(n==="none"||!s||!J.getLanguage(s))return r.split(`
66
+ `);try{return as(J.highlight(r,{language:s,ignoreIllegals:!0}).value,is(dn(t,n))).split(`
67
+ `)}catch{return r.split(`
68
+ `)}}function ln(r){let e=r.split("/").pop()?.toLowerCase()??"";if(e==="dockerfile")return"dockerfile";let t=e.includes(".")?e.split(".").pop():"";return J.getLanguage(t)?t:void 0}var cn=r=>`\x1B[7m${r}\x1B[27m`;function un(r,e,t,n,s=3){let i=dn(t,n),[a,o,l]=[i("added"),i("removed"),i("context")],d=Ur(r,e),c=String(Math.max(r.split(`
69
+ `).length,e.split(`
70
+ `).length)).length,u=w=>String(w).padStart(c),h=[],p=1,g=1;for(let w of d){let E=w.value.replace(/\n$/,"").split(`
71
+ `);for(let k of E)w.added?h.push({kind:"+",n:g++,text:k}):w.removed?h.push({kind:"-",n:p++,text:k}):h.push({kind:" ",n:(p++,g++),text:k})}let f=h.map((w,E)=>w.kind!==" "?E:-1).filter(w=>w>=0),b=new Set(f.flatMap(w=>Array.from({length:2*s+1},(E,k)=>w-s+k))),_=[],m=!1;for(let w=0;w<h.length;w++){let E=h[w];if(!b.has(w)){!m&&_.length&&_.push(l(`${" ".repeat(c+1)} \u22EE`)),m=!0;continue}m=!1;let k=E.text.replace(/\t/g," "),S=h[w+1];if(E.kind==="-"&&S?.kind==="+"&&h[w-1]?.kind!=="-"&&h[w+2]?.kind!=="+"){let v="",A="";for(let M of Hr(k,S.text.replace(/\t/g," ")))M.removed?v+=cn(M.value):M.added?A+=cn(M.value):(v+=M.value,A+=M.value);_.push(o(`-${u(E.n)} ${v}`),a(`+${u(S.n)} ${A}`)),w++;continue}_.push(E.kind==="+"?a(`+${u(E.n)} ${k}`):E.kind==="-"?o(`-${u(E.n)} ${k}`):l(` ${u(E.n)} ${k}`))}return{lines:_,added:h.filter(w=>w.kind==="+").length,removed:h.filter(w=>w.kind==="-").length}}var cs={dark:{text:"#f3f0e8",soft:"#a8a49a",hair:"#4a473f",accent:"#867afe",ok:"#34c38f",bad:"#e07a5f",warn:"#d4a24c"},light:{text:"#141310",soft:"#57544c",hair:"#857f73",accent:"#4d41d0",ok:"#0b7a52",bad:"#b3452b",warn:"#8f5f10"}},ds=r=>[1,3,5].map(e=>parseInt(r.slice(e,e+2),16));function ls([r,e,t]){let n=p=>p<48?0:p<115?1:Math.min(5,Math.round((p-35)/40)),s=p=>p===0?0:55+p*40,[i,a,o]=[n(r),n(e),n(t)],l=16+36*i+6*a+o,d=Math.round((r+e+t)/3),c=d>238?255:d<8?232:232+Math.round((d-8)/10),u=8+(c-232)*10,h=p=>(p[0]-r)**2+(p[1]-e)**2+(p[2]-t)**2;return h([s(i),s(a),s(o)])<=h([u,u,u])?l:c}function pn(r=process.env){return r.NO_COLOR!==void 0&&r.NO_COLOR!==""?"none":/truecolor|24bit/i.test(r.COLORTERM??"")?"truecolor":"256"}function Ve(r=process.env,e){return r.ASHR_THEME==="light"||r.ASHR_THEME==="dark"?r.ASHR_THEME:e&&(.2126*e.r+.7152*e.g+.0722*e.b)/255>.5?"light":"dark"}function kt({background:r,mode:e}){let t=cs[r],n=(s,i)=>a=>e==="none"?a:`${s}${a}${i}`;return{background:r,mode:e,fg(s,i){if(e==="none"||s==="text")return i;let a=ds(t[s]);return`${e==="truecolor"?`\x1B[38;2;${a[0]};${a[1]};${a[2]}m`:`\x1B[38;5;${ls(a)}m`}${i}\x1B[39m`},bold:n("\x1B[1m","\x1B[22m"),dim:n("\x1B[2m","\x1B[22m"),italic:n("\x1B[3m","\x1B[23m")}}function Ce(r){let e=t=>n=>r.fg(t,n);return{heading:t=>r.bold(r.fg("text",t)),link:e("accent"),linkUrl:e("soft"),code:e("accent"),codeBlock:e("text"),codeBlockBorder:t=>t==="```"?"":r.fg("soft",t.replace(/^```/,"")),codeBlockIndent:` ${r.fg("hair","\u2502")} `,highlightCode:(t,n)=>Pe(t,n,r.background,r.mode),quote:e("soft"),quoteBorder:e("hair"),hr:e("hair"),listBullet:e("soft"),bold:r.bold,italic:r.italic,strikethrough:t=>`\x1B[9m${t}\x1B[29m`,underline:t=>`\x1B[4m${t}\x1B[24m`}}function hn(r){return{borderColor:e=>r.fg("hair",e),selectList:{selectedPrefix:e=>r.fg("accent",e),selectedText:e=>r.bold(r.fg("accent",e)),description:e=>r.fg("soft",e),scrollInfo:e=>r.fg("soft",e),noMatch:e=>r.fg("soft",e)}}}var ps="Ask, build, or steer the agent\u2026",Xe=class{constructor(e,t){this.t=t;this.editor=new us(e,hn(t),{paddingX:1})}t;editor;thinking;secret;get focused(){return this.editor.focused}set focused(e){this.editor.focused=e}set onSubmit(e){this.editor.onSubmit=e}getText(){return this.editor.getText()}setText(e){this.editor.setText(e)}handleInput(e){this.editor.handleInput(e)}invalidate(){this.editor.invalidate()}render(e){let{t}=this,n=Math.max(4,e-3),s=this.editor.render(n),i=s.findIndex((p,g)=>g>0&&/^[─\s]*$/.test(p.replace(/\x1b\[[0-9;]*m/g,""))&&p.includes("\u2500")),a=s.slice(1,i>0?i:s.length),o=i>0?s.slice(i+1):[],l=t.fg("hair","\u2502");if(this.secret!==void 0){let p=this.getText().length,g=p?`${t.fg("text","\u2022".repeat(Math.min(p,n-2)))}\x1B[7m \x1B[0m`:`\x1B[7m \x1B[0m${t.fg("soft",this.secret)}`,f=Ze(g+" ".repeat(Math.max(0,n-mn(g))),n,"");return[t.fg("accent",`\u250C${"\u2500".repeat(e-2)}\u2510`),`${l}${t.fg("accent","\u203A")}${f}${l}`,t.fg("accent",`\u2514${"\u2500".repeat(e-2)}\u2518`)]}let d=this.getText()==="",c=a.map((p,g)=>{let f=p;if(d&&g===0){let _=p.indexOf("\x1B[7m"),m=_>=0?p.slice(0,p.indexOf("\x1B[0m",_)+4).trimStart():" ";f=Ze(`${m}${t.fg("soft",ps)}`,n,"\u2026")}let b=f+" ".repeat(Math.max(0,n-mn(f)));return`${l}${g===0?t.fg("accent","\u203A"):" "}${Ze(b,n,"")}${l}`}),u=this.getText().startsWith("!")?"ok":{medium:"soft",high:"accent",xhigh:"accent"}[this.thinking??""]??"hair",h=t.fg(u,"\u2502");return[t.fg(u,`\u250C${"\u2500".repeat(e-2)}\u2510`),...c.map(p=>`${h}${p.slice(l.length,p.length-l.length)}${h}`),t.fg(u,`\u2514${"\u2500".repeat(e-2)}\u2518`),...o.map(p=>Ze(p,e,""))]}};import{Container as wn,decodeKittyPrintable as bn,Markdown as $s,matchesKey as q,truncateToWidth as st,visibleWidth as G,wrapTextWithAnsi as ne}from"@earendil-works/pi-tui";import{getBuiltinModel as Ts}from"@earendil-works/pi-ai/providers/all";import{truncateToWidth as hs,visibleWidth as we}from"@earendil-works/pi-tui";var ms=r=>r.includes("\x1B_G")||r.includes("\x1B]1337;File="),F=class{cachedWidth=-1;cachedLines=[];render(e){return e!==this.cachedWidth&&(this.cachedLines=this.draw(e).map(t=>vt(t,e)),this.cachedWidth=e),this.cachedLines}invalidate(){this.cachedWidth=-1}changed(){this.cachedWidth=-1}};function vt(r,e){return ms(r)?r:we(r)>e?hs(r,Math.max(0,e-1),"\u2026"):r}function be(r,e,t){let n=we(e)>t-2?vt(e,Math.max(0,t-2)):e,s=t-we(n)-1,i=we(r)>s?vt(r,Math.max(0,s)):r;return i+" ".repeat(Math.max(1,t-we(i)-we(n)))+n}function Qe(r){return r>=1e6?`${(r/1e6).toFixed(1)}M`:r>=1e3?`${Math.round(r/1e3)}k`:String(r)}function fn(r){let e=Math.floor(r/1e3);return`${Math.floor(e/60)}:${String(e%60).padStart(2,"0")}`}import{getCapabilities as fs,Image as gs,imageFallback as ws,wrapTextWithAnsi as bs}from"@earendil-works/pi-tui";var Oe=6,_t={bash:{from:"tail",lines:5},grep:{from:"head",lines:15},find:{from:"head",lines:20},ls:{from:"head",lines:20},write:{from:"head",lines:10},read:{from:"head",lines:0}},ys={from:"head",lines:10},te=r=>typeof r=="string"?x(r):void 0,Ne=r=>(r??"").replace(/^\/workspace\/?/,"")||".";function ks(r){let e=r/1e3;if(e<60)return`${e.toFixed(1)}s`;let t=Math.floor(e/60);return t<60?`${t}m ${Math.floor(e)%60}s`:`${Math.floor(t/60)}h ${t%60}m`}var he=class extends F{constructor(t,n){super();this.t=t;this.info=n}t;info;state={kind:"running",elapsedMs:0};expanded=!1;waiting=!1;change;tookMs;partial="";images=[];setWaiting(t){this.waiting=t,this.changed()}setImages(t){this.images=t,this.changed()}addPartial(t,n=!1){this.partial=n?t:this.partial+t,this.changed()}setRunning(t){this.state={kind:"running",elapsedMs:t},this.changed()}finish(t){let n=t.output??t.summary;this.state=t.isError?{kind:"failed",summary:t.summary,output:n}:{kind:"done",summary:t.summary,output:n},this.tookMs=t.tookMs,this.changed()}setChange(t,n){this.change={before:x(t),after:x(n)},this.changed()}toggle(){this.expanded=!this.expanded,this.changed()}get finished(){return this.state.kind!=="running"}get path(){let t=this.info.args??{};return te(t.path)??te(t.file_path)??(["read","write","edit"].includes(this.info.name)?x(this.info.subject):void 0)}draw(t){let n=[...this.body(Math.min(t,100)),...this.pictures(Math.min(t,100))];return n.length>1?[...n,""]:n}pictures(t){let{t:n}=this,s=" ".repeat(Oe);return this.images.flatMap(i=>{let a=x(i.mime??"image/png");return i.omitted||!i.data?[`${s}${n.fg("soft",`[${a} image${i.bytes?`, ${Math.round(i.bytes/1024)} KB`:""}, too large to show here]`)}`]:fs().images?new gs(i.data,a,{fallbackColor:o=>n.fg("soft",o)},{maxWidthCells:Math.min(80,t-Oe),maxHeightCells:24}).render(t-Oe).map(o=>`${s}${o}`):[`${s}${n.fg("soft",ws(a))}`]})}call(){let{t,info:n}=this,s=n.args??{},i=o=>t.fg("soft",x(o)),a=o=>t.bold(t.fg("text",x(o)));switch(n.name){case"bash":{let o=(te(s.command)??n.subject).replace(/\s*\n\s*/g," \u23CE ");return`${a(`$ ${o}`)}${s.timeout?i(` (timeout ${s.timeout}s)`):""}`}case"read":{let o=typeof s.offset=="number"?s.offset:void 0,l=o!==void 0||typeof s.limit=="number"?t.fg("warn",`:${o??1}${typeof s.limit=="number"?`-${(o??1)+s.limit-1}`:""}`):"";return`${a("read")} ${t.fg("accent",Ne(this.path))}${l}`}case"write":case"edit":return`${a(n.name)} ${t.fg("accent",Ne(this.path))}`;case"grep":return`${a("grep")} ${t.fg("accent",`/${te(s.pattern)??""}/`)}${i(` in ${Ne(te(s.path))}`)}${s.glob?i(` (${s.glob})`):""}${s.limit!==void 0?i(` limit ${s.limit}`):""}`;case"find":return`${a("find")} ${t.fg("accent",te(s.pattern)??"")}${i(` in ${Ne(te(s.path))}`)}${s.limit!==void 0?i(` (limit ${s.limit})`):""}`;case"ls":return`${a("ls")} ${t.fg("accent",Ne(te(s.path)))}${s.limit!==void 0?i(` (limit ${s.limit})`):""}`;default:{let o=n.subject.replace(/\s*\n\s*/g," ");return`${a(n.name.replace(/^platform_/,""))}${o?` ${i(o)}`:""}`}}}body(t){let{t:n,info:s,state:i}=this,a=s.subagent?` ${n.fg("hair","\u21B3")} `:" ",o=this.change?.before?un(this.change.before,this.change.after,n.background,n.mode):void 0,l=i.kind==="running"?n.fg("warn",this.waiting?"needs approval":`running ${fn(i.elapsedMs)}`):i.kind==="failed"?n.fg("bad","failed"):o?`${n.fg("ok",`+${o.added}`)} ${n.fg("bad",`\u2212${o.removed}`)}`:this.change?n.fg("ok","new"):n.fg("ok","done"),d=[be(`${a}${this.call()}`,l,t)];if(i.kind==="running"&&!this.partial&&!(s.name==="write"&&te(s.args?.content)))return d;let c=" ".repeat(Oe),u=Math.max(10,t-Oe),h=m=>m.flatMap(w=>bs(w.replace(/\t/g," "),u)).map(w=>`${c}${w}`),p=(m,w)=>`${c}${n.fg("soft",`\u2026 ${m} ${w} line${m===1?"":"s"} \xB7 ctrl+o to expand`)}`,g=(m,w)=>{for(;m.length&&!m.at(-1).replace(/\x1b\[[0-9;]*m/g,"").trim();)m=m.slice(0,-1);return this.expanded||m.length<=w.lines?h(m):w.lines===0?[]:w.from==="head"?[...h(m.slice(0,w.lines)),p(m.length-w.lines,"more")]:[p(m.length-w.lines,"earlier"),...h(m.slice(-w.lines))]},f=(m,w="soft")=>x(m).split(`
72
+ `).map(E=>n.fg(w,E));if(i.kind==="failed"){let m=(i.output||i.summary).replace(/\n+$/,"").trim();return d.push(...g(f(m||"failed with no output","bad"),{from:"tail",lines:5})),d}let b=this.path?ln(this.path):void 0;if(o&&(s.name==="edit"||s.name==="write"))return d.push(...h(o.lines)),d;if(s.name==="write"){let m=this.change?.after??te(s.args?.content)??"";return d.push(...g(Pe(m.replace(/\n$/,""),b,n.background,n.mode),_t.write)),d}if(i.kind==="running"){let m=this.partial.replace(/\n+$/,"");return m.trim()&&d.push(...g(f(m),_t[s.name]??{from:"tail",lines:5})),d}let _=x(i.output).replace(/\n+$/,"");if(_.trim()){let m=s.name==="read"&&b?Pe(_,b,n.background,n.mode):f(_);d.push(...g(m,_t[s.name]??ys))}return s.name==="bash"&&this.tookMs!==void 0&&d.push(`${c}${n.fg("soft",`Took ${ks(this.tookMs)}`)}`),d}};import{Markdown as gn,wrapTextWithAnsi as vs}from"@earendil-works/pi-tui";var ue=7,xt=100;function _s(r){return r.split(`
73
+ `).filter(t=>/^\s*(```|~~~)/.test(t)).length%2?`${r}
74
+ \`\`\``:r}function xs(r,e){let t=r.split(`
75
+ `),n=[],s,i=a=>a.trim().replace(/^\||\|$/g,"").split("|").map(o=>o.trim());for(let a=0;a<t.length;a++){let o=t[a],l=/^\s*(`{3,}|~{3,})(.*)$/.exec(o);if(s){l&&l[1][0]===s[0]&&l[1].length>=s.length&&!l[2].trim()&&(s=void 0),n.push(o);continue}if(l&&(l[1][0]==="~"||!l[2].includes("`"))){s=l[1],n.push(o);continue}let d=t[a+1]??"";if(!/^\s*\|.*\|\s*$/.test(o)||!/^\s*\|?\s*:?-{2,}/.test(d)){n.push(o);continue}let c=a+2;for(;c<t.length&&/^\s*\|.*\|\s*$/.test(t[c]);)c++;let u=i(o),h=t.slice(a+2,c).map(i);if(u.map((g,f)=>Math.max(g.length,...h.map(b=>(b[f]??"").length))).reduce((g,f)=>g+f+3,1)<=e)n.push(...t.slice(a,c));else for(let g of h){n.push(`- **${g[0]??""}**`);for(let f=1;f<u.length;f++)g[f]&&n.push(` ${u[f]}: ${g[f]}`)}a=c-1}return n.join(`
76
+ `)}function Ss(r){let e;return r.split(`
77
+ `).map(t=>{let n=/^\s*(`{3,}|~{3,})(.*)$/.exec(t);return e?(n&&n[1][0]===e[0]&&n[1].length>=e.length&&!n[2].trim()&&(e=void 0),t):(n&&(n[1][0]==="~"||!n[2].includes("`"))&&(e=n[1]),e?t:t.replace(/^#{3,6}\s+(.+?)\s*#*\s*$/,"**$1**"))}).join(`
78
+ `)}var se=class r extends F{constructor(t){super();this.t=t;this.md=new gn("",0,0,Ce(t))}t;static hideThinking=!1;text="";thinking="";mdWidth=-1;streaming=!0;note;md;append(t){this.text+=t,this.mdWidth=-1,this.changed()}setText(t){this.text=t,this.mdWidth=-1,this.changed()}appendThinking(t){this.thinking+=t,this.changed()}setThinking(t){this.thinking=t,this.changed()}invalidate(){super.invalidate(),this.mdWidth=-1,this.md.invalidate()}hasText(){return this.text.trim().length>0||this.thinking.trim().length>0}setNote(t){this.note=t,this.changed()}draw(t){let{t:n}=this,s=Math.max(10,Math.min(xt,t-ue));if(this.mdWidth!==s){let u=Ss(xs(this.text,s));this.md.setText(this.streaming?_s(u):u),this.mdWidth=s}let i=this.text?this.md.render(Math.max(10,Math.min(xt,t-ue))).map(u=>u.trimEnd()).filter((u,h,p)=>u!==""||p[h-1]!==""):[""],a=` ${n.bold(n.fg("accent","Ashr"))} `,o=x(this.thinking).trim(),l=o?r.hideThinking?[n.italic(n.fg("soft","Thinking\u2026 \xB7 ctrl+t to show"))]:o.split(`
79
+ `).flatMap(u=>vs(u,Math.max(10,Math.min(xt,t-ue)))).map(u=>n.italic(n.fg("soft",u))):[],c=["",...[...l,...l.length&&this.text?[""]:[],...this.text||!l.length?i:[]].map((u,h)=>(h===0?a:" ".repeat(ue))+u)];return this.note&&c.push(`${" ".repeat(ue)}${n.fg("soft",this.note)}`),c}};var Es=/^\[subagent ([^\]\s]+) ([^\]]+)\]\s*/;var et=class extends F{constructor(t,n,s=8){super();this.t=t;this.preview=s;let i=Es.exec(n);this.name=i?.[1]??"subagent",this.outcome=i?.[2]??"",this.md=new gn(i?n.slice(i[0].length):n,0,0,Ce(t))}t;preview;expanded=!1;name;outcome;md;toggle(){this.expanded=!this.expanded,this.changed()}draw(t){let{t:n}=this,s=this.md.render(Math.max(10,t-ue)),i=this.expanded?s:s.slice(0,this.preview),a=/fail/.test(this.outcome)?"bad":/cap|stop/.test(this.outcome)?"warn":"ok",o=["",` ${n.bold(n.fg("accent",this.name))} ${n.fg(a,this.outcome)}`,...i.map(l=>" ".repeat(ue)+l)];return i.length<s.length&&o.push(`${" ".repeat(ue)}${n.fg("soft",`\u2026 ${s.length-i.length} more lines \xB7 ctrl-o to expand`)}`),o}};var St=class extends F{constructor(t,n){super();this.t=t;this.text=n}t;text;draw(t){let n=/^\/skill:([\w.-]+)\s*([\s\S]*)$/.exec(this.text);if(n){let i=n[2].trim();return["",` ${this.t.fg("accent","\u203A")} ${this.t.fg("accent","skill")} ${this.t.bold(this.t.fg("text",n[1]))}`,...i?ne(i,Math.max(10,t-3)).map(a=>` ${this.t.fg("soft",a)}`):[]]}return["",...this.text.split(`
80
+ `).flatMap(i=>ne(i,Math.max(10,t-3))).map((i,a)=>`${a===0?` ${this.t.fg("accent","\u203A")} `:" "}${this.t.fg("text",i)}`)]}},Et=class extends F{constructor(t,n,s="soft",i="",a=""){super();this.t=t;this.text=n;this.role=s;this.mark=i;this.title=a}t;text;role;mark;title;draw(t){let{t:n}=this,s=this.mark?`${n.fg(this.role,this.mark)} `:"",i=this.title?`${n.bold(n.fg(this.role,x(this.title)))} `:"",a=1+G(s)+G(i),o=this.title?"text":this.role,l=" ".repeat(Math.min(a,12)),[d="",...c]=x(this.text).replace(/\t/g," ").split(`
81
+ `),[u="",...h]=ne(d,Math.max(10,t-a-1)),p=[h.join(" "),...c].filter((g,f)=>f>0||g).flatMap(g=>ne(g,Math.max(10,t-l.length-1)));return["",` ${s}${i}${n.fg(o,u)}`,...p.map(g=>`${l}${n.fg(o,g)}`)]}},ke=class extends F{constructor(t,n,s,i="accent"){super();this.t=t;this.title=n;this.body=s;this.role=i}t;title;body;role;draw(t){let{t:n}=this,s=Math.max(10,t-2),i=typeof this.body=="function"?this.body(s):this.body.flatMap(a=>pe(a,s));return["",` ${n.bold(n.fg(this.role,this.title))}`,` ${n.fg("hair","\u2500".repeat(Math.max(0,t-1)))}`,...i.map(a=>` ${a}`)]}};function pe(r,e,t){let n=$t(r),s=Math.min(t??/^(\s*(?:[•›↳-] )?)/.exec(n)?.[1]?.length??0,Math.floor(e/2)),[i="",...a]=ne(r,e);if(!a.length)return[i];let o=ne(a.map(l=>l.trimStart()).join(" "),Math.max(10,e-s));return[i,...o.map(l=>`${" ".repeat(s)}${l}`)]}function $t(r){return r.replace(/\x1b\[[0-9;]*m/g,"").replace(/\x1b\]8;;[^\x07]*\x07/g,"")}var Tt=class extends F{constructor(t,n){super();this.t=t;this.data=n}t;data;decision;decide(t){this.decision=t,this.changed()}draw(t){let{t:n,data:s}=this;t=Math.min(t,100);let i=String(s.approval_id??""),a=s.estimate!==void 0,o=n.fg(this.decision?"hair":"accent","\u2503"),l=a?n.fg("warn","SPEND"):n.fg("soft","APPROVAL"),d=x(s.action??"Approval needed").replace(/_/g," "),[c="",...u]=ne(d,Math.max(10,t-G(l)-5)),h=["",be(`${o} ${n.bold(n.fg("text",c))}`,l,t),...u.map(p=>`${o} ${n.bold(n.fg("text",p))}`)];for(let p of x(s.summary??"").split(`
82
+ `))for(let g of p?ne(p,Math.max(10,t-5)):[])h.push(`${o} ${n.fg("text",g)}`);if(a&&h.push(`${o} ${n.fg("warn",Rs(s.estimate))}`),this.decision){let p=this.decision==="approved"?"ok":this.decision==="rejected"?"bad":"soft",g=this.decision==="approved"?"\u2713 ":this.decision==="rejected"?"\u2717 ":"\u2013 ";h.push(`${o} ${n.fg(p,`${g}${this.decision}`)}`)}else h.push(`${o} ${n.bold(n.fg("accent",`/approve ${i}`))} ${n.fg("soft",`/reject ${i}`)}`);return h}},At=class extends F{constructor(t){super();this.t=t}t;where="";set(t){t=x(t),t!==this.where&&(this.where=t,this.changed())}draw(t){let{t:n}=this,s=` ${n.bold(n.fg("text","ashr"))} ${n.fg("accent","labs")}`,i=t-G(s)-3,a=i<8?"":G(this.where)>i?st(this.where,i,"\u2026"):this.where;return[be(s,n.fg("soft",a),t),` ${n.fg("hair","\u2500".repeat(Math.max(0,t-1)))}`]}};function As(r){let e=r?.indexOf("/")??-1;if(!(!r||e<=0))try{return Ts(r.slice(0,e),r.slice(e+1))?.contextWindow}catch{return}}var Rt=class extends F{constructor(t){super();this.t=t}t;info={connection:"",busy:void 0,cost:0,context:void 0,frame:0};set(t){this.info={...this.info,...t},this.changed()}draw(t){let{t:n,info:s}=this,i=n.fg("soft"," \xB7 "),a=x(s.chat?.status??""),o=["working","queued","stopping"].includes(a),l="\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F"[s.frame%10],d=s.busy?`${n.fg("accent",l)} ${n.fg("text",s.busy)}`:s.chat?o?`${n.fg("accent",l)} ${n.fg("text",a)}`:a==="failed"?n.fg("bad","\u25CF failed"):`${n.fg("ok","\u25CF")} ${n.fg("text",a||"idle")}`:n.fg("soft","no chat"),c=s.chat&&s.connection&&s.connection!=="Connected"?n.fg("warn",s.connection.toLowerCase()):"",u=As(s.chat?.model),h=s.context===void 0?"":u?`${Qe(s.context)} / ${Qe(u)} context`:`${Qe(s.context)} context`,p=s.cost?`est. $${s.cost<.01?s.cost.toFixed(4):s.cost.toFixed(2)} model`:"",g=[d,c,p?n.fg("soft",p):"",h?n.fg("soft",h):"",s.chat?.model?n.fg("soft",x(s.chat.model)):""].filter(Boolean),f=t<60?"":n.fg("soft",o?"esc stop \xB7 ctrl+o output":"ctrl+o output \xB7 /help"),b=()=>2+G(g.join(i))+(f?G(f)+2:0)<=t;for(;!b()&&g.length>1;)g.pop();return b()||(f=""),[be(` ${g.join(i)}`,f,t)]}};function yn(r){for(let e of["text","message","detail","error"]){let t=r[e];if(typeof t=="string"&&t.trim())return x(t);if(t&&typeof t=="object")return yn(t)}return"Something went wrong. Try again, or check /history."}function Rs(r){if(typeof r!="object"||!r)return`Estimated cost ${x(r)}`;let e=r,t=c=>typeof c=="number"?`$${c.toFixed(2)}`:void 0,n=e.total_cost_usd,s=t(typeof n=="object"?n?.low:n??e.usd??e.cost_usd),i=typeof n=="object"?t(n?.high):void 0,a=s&&i&&s!==i?`${s}\u2013${i}`:i??s,o=Number(e.seconds??(typeof e.hours=="number"?e.hours*3600:NaN)),l=Number.isFinite(o)?o>=3600?`${+(o/3600).toFixed(1)}h`:`${Math.max(1,Math.round(o/60))}m`:"",d=[a?`Estimated cost ${a}`:"",l?`up to ${l}`:""].filter(Boolean);return d.length?d.join(" \xB7 "):`Estimate ${it(r)}`}function De(r,e=Date.now()){let t=Math.max(0,Math.round((e-Date.parse(r))/1e3));return Number.isFinite(t)?t<60?"now":t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:`${Math.floor(t/86400)}d ago`:""}function Ct(r){if(!r||typeof r!="object")return x(r??"");let e=r;for(let t of["command","path","file_path","query","pattern","url","name","task","alias"])if(typeof e[t]=="string"&&e[t])return x(e[t]).replace(/\s*\n\s*/g," ");return it(e)}function it(r){return r===null||typeof r!="object"?String(r):Object.entries(r).filter(([,e])=>e!=null&&typeof e!="object").map(([e,t])=>`${e}=${String(t)}`).join(" ")}var Is=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/,Ps=["text","content","summary","description","message","detail"],Cs=["items","results","events","data","chats","tasks","runs","rows","entries","notes"];function Os(r){let e=r.toLowerCase();return/fail|error|reject|cancel/.test(e)?"bad":/run|work|queue|pending|start|progress/.test(e)?"accent":/succe|complete|done|ready|active|healthy|ok|approved|deployed/.test(e)?"ok":"soft"}function ye(r,e,t,n="text"){if(t==null||t==="")return r.fg("hair","\u2014");if(typeof t=="boolean")return t?r.fg("ok","\u2713"):r.fg("soft","\u2013");let s=x(t);return Is.test(s)&&!Number.isNaN(Date.parse(s))?r.fg("soft",De(s)):/(^|_)status$|^state$/.test(e)?r.fg(Os(s),s):r.fg(n,s)}function It(r,e){let t=G(r)>e?st(r,Math.max(1,e),"\u2026"):r;return t+" ".repeat(Math.max(0,e-G(t)))}function Ns(r,e,t){let s=Object.keys(Object.assign({},...e.map(u=>u??{}))).filter(u=>e.some(h=>h?.[u]!==null&&h?.[u]!==void 0&&typeof h[u]!="object")),i=s.filter(u=>u!=="id"&&!u.endsWith("_id")),a=i.length?i:s,o=e.map(u=>a.map((h,p)=>ye(r,h,u?.[h],p===0?"text":"soft"))),l=a.map(u=>u.replace(/_at$/,"").replace(/_/g," ").toUpperCase()),d=a.map((u,h)=>Math.min(40,Math.max(G(l[h]),...o.map(p=>G(p[h]))))),c=()=>d.reduce((u,h)=>u+h,0)+2*(d.length-1);for(;c()>t&&a.length>2;)a=a.slice(0,-1),d=d.slice(0,-1);return c()>t&&(d[0]=Math.max(6,d[0]-(c()-t))),[a.map((u,h)=>r.fg("soft",It(l[h],d[h]))).join(" ").trimEnd(),...o.map(u=>a.map((h,p)=>It(u[p],d[p])).join(" ").trimEnd())]}function Pt(r,e,t=100){if(typeof e=="string")return e.split(`
83
+ `).flatMap(n=>pe(r.fg("text",x(n)),t));if(Array.isArray(e)){if(!e.length)return[r.fg("soft","Nothing here yet.")];if(e.every(i=>i===null||typeof i!="object"))return e.flatMap(i=>pe(`${r.fg("soft","\u2022")} ${ye(r,"",i)}`,t,2));if(!e.every(i=>i&&typeof i=="object"&&!Array.isArray(i)))return e.flatMap(i=>pe(`${r.fg("soft","\u2022")} ${r.fg("text",x(JSON.stringify(i)))}`,t,2));let n=e,s=Ps.find(i=>n.some(a=>typeof a?.[i]=="string"));return s?n.flatMap(i=>{let a=Object.entries(i).filter(([o,l])=>o!==s&&o!=="id"&&!o.endsWith("_id")&&l!==null&&l!==void 0&&l!==""&&typeof l!="object").slice(0,3).map(([o,l])=>$t(ye(r,o,l)));return[...pe(`${r.fg("accent","\u2022")} ${r.fg("text",x(i?.[s]??""))}`,t,2),...i.id!==void 0&&i.id!==null?pe(` ${r.fg("soft",`id: ${x(i.id)}`)}`,t,2):[],...a.length?[` ${r.fg("soft",a.join(" \xB7 "))}`]:[]]}):Ns(r,n,t)}if(e&&typeof e=="object"){let n=e,s=Object.keys(n).filter(o=>Array.isArray(n[o]));if(s.length===1&&Cs.includes(s[0])){let o=Object.entries(n).filter(([l,d])=>l!==s[0]&&d!==null&&d!==void 0&&d!==""&&typeof d!="object").map(([l,d])=>`${l.replace(/_/g," ")} ${$t(ye(r,l,d))}`);return[...Pt(r,n[s[0]],t),...o.length?["",r.fg("soft",o.join(" \xB7 "))]:[]]}let i=Object.entries(n).filter(([,o])=>o!=null&&o!=="");if(!i.length)return[r.fg("soft","Nothing here yet.")];let a=Math.min(20,Math.max(...i.map(([o])=>o.length)));return i.flatMap(([o,l])=>{let d=o.replace(/_/g," ");return typeof l!="object"?pe(`${r.fg("soft",d.padEnd(a))} ${ye(r,o,l)}`,t,a+2):[r.fg("soft",d),...Pt(r,l,t-2).map(c=>` ${c}`)]})}return[ye(r,"",e)]}function Ms(r,e,t=100){let n=e.map(a=>{let o=/^([/!].*?)(?:\s{2,}| — )(.*)$/.exec(a);return o?{usage:o[1].trim(),about:o[2].trim()}:{usage:"",about:a}}),s=Math.min(30,Math.max(0,...n.map(a=>a.usage.length))+2),i=t<60;return n.flatMap(({usage:a,about:o})=>{if(!a)return o?pe(r.fg("soft",o),t):[""];let l=a.replace(/(^|\| |; )([/!][\w-]+)/g,(u,h,p)=>`${h}${r.fg("accent",p)}`);if(i||a.length+2>s)return[r.fg("text",l),...o?ne(o,t-4).map(u=>` ${r.fg("soft",u)}`):[]];let[d="",...c]=ne(o,Math.max(10,t-s));return[`${r.fg("text",l)}${" ".repeat(s-a.length)}${r.fg("soft",d)}`,...c.map(u=>`${" ".repeat(s)}${r.fg("soft",u)}`)]})}var nt=class extends F{constructor(t,n,s,i=Date.now(),a=10){super();this.t=t;this.chats=n;this.current=s;this.now=i;this.rows=a;this.chats=[...n].sort((o,l)=>Date.parse(l.updated_at)-Date.parse(o.updated_at))}t;chats;current;now;rows;filter="";index=0;top=0;onPick;onCancel;shown(){let t=this.filter.toLowerCase();return t?this.chats.filter(n=>n.name.toLowerCase().includes(t)||(n.task??"").toLowerCase().includes(t)):this.chats}handleInput(t){let n=this.shown();if(q(t,"escape")||q(t,"ctrl+c"))return this.onCancel?.();if(q(t,"enter")||q(t,"return")){let i=n[this.index];i&&this.onPick?.(i);return}let s=q(t,"up")?-1:q(t,"down")?1:q(t,"pageUp")?-this.rows:q(t,"pageDown")?this.rows:0;if(s)this.index=Math.max(0,Math.min(n.length-1,this.index+s));else if(q(t,"backspace"))this.filter=this.filter.slice(0,-1);else{let i=bn(t)??(t.length===1&&t>=" "&&t!=="\x7F"?t:void 0);if(!i)return;this.filter+=i,this.index=0}this.changed()}draw(t){let{t:n}=this,s=this.shown();this.index=Math.min(this.index,Math.max(0,s.length-1)),this.index<this.top&&(this.top=this.index),this.index>=this.top+this.rows&&(this.top=this.index-this.rows+1);let i=s.slice(this.top,this.top+this.rows),a=i.map(m=>De(m.updated_at,this.now)),o=Math.max(0,...a.map(m=>m.length)),l=m=>m!=="idle",d=Math.max(0,...i.filter(m=>l(m.status)).map(m=>m.status.length)),c=t>=70?Math.min(24,Math.max(0,...i.map(m=>(m.task??"").length))):0,u=6+(c?c+2:0)+(d?d+2:0)+o+1,h=Math.max(8,t-u),p=(m,w)=>{let E=G(m)>w?st(m,w,"\u2026"):m;return E+" ".repeat(Math.max(0,w-G(E)))},g=i.map((m,w)=>{let E=this.top+w===this.index,k=m.id===this.current,S=m.status==="failed"?"bad":["working","queued","stopping"].includes(m.status)?"accent":"soft";return[E?n.fg("accent"," \u203A"):" ",n.fg(E?"accent":"text",p(x(m.name)+(k?" (open)":""),h)),c?n.fg("soft",p(x(m.task??""),c)):"",d?n.fg(S,p(l(m.status)?m.status:"",d)):"",n.fg("soft",a[w].padStart(o))].filter(v=>v!=="").join(" ")}),f=s.length-this.top-i.length,b=` ${n.bold(n.fg("accent","Chats"))} ${n.fg("soft",`\xB7 ${s.length}${this.filter?" matching":""}`)}`,_=this.filter?n.fg("text",x(this.filter)):n.fg("soft","type to filter");return["",b,` ${n.fg("hair","\u2500".repeat(Math.max(0,t-1)))}`,` ${n.fg("accent","/")} ${_}${this.filter?"\x1B[7m \x1B[0m":""}`,"",...this.top?[n.fg("soft",` \u2191 ${this.top} more`)]:[],...g.length?g:[n.fg("soft"," No chats match. Backspace to widen the search.")],...f>0?[n.fg("soft",` \u2193 ${f} more`)]:[],"",n.fg("soft"," \u2191\u2193 move \xB7 enter open \xB7 esc close")]}},ve=class extends F{constructor(t,n,s,i=10){super();this.t=t;this.title=n;this.items=s;this.rows=i;this.index=Math.max(0,s.findIndex(a=>a.current))}t;title;items;rows;filter="";index;top=0;onPick;onCancel;shown(){let t=this.filter.toLowerCase();return t?this.items.filter(n=>`${n.label} ${n.detail??""}`.toLowerCase().includes(t)):this.items}handleInput(t){let n=this.shown();if(q(t,"escape")||q(t,"ctrl+c"))return this.onCancel?.();if(q(t,"enter")||q(t,"return")){let i=n[this.index];i&&this.onPick?.(i);return}let s=q(t,"up")?-1:q(t,"down")?1:q(t,"pageUp")?-this.rows:q(t,"pageDown")?this.rows:0;if(s)this.index=Math.max(0,Math.min(n.length-1,this.index+s));else if(q(t,"backspace"))this.filter=this.filter.slice(0,-1);else{let i=bn(t)??(t.length===1&&t>=" "&&t!=="\x7F"?t:void 0);if(!i)return;this.filter+=i,this.index=0}this.changed()}draw(t){let{t:n}=this,s=this.shown();this.index=Math.min(this.index,Math.max(0,s.length-1)),this.index<this.top&&(this.top=this.index),this.index>=this.top+this.rows&&(this.top=this.index-this.rows+1);let i=s.slice(this.top,this.top+this.rows),a=Math.min(Math.max(8,...i.map(d=>G(d.label)+(d.current?2:0))),Math.floor(t*.6)),o=i.map((d,c)=>{let u=this.top+c===this.index,h=It(x(d.label)+(d.current?" \u2713":""),a),p=d.detail?st(x(d.detail),Math.max(0,t-a-8),"\u2026"):"";return`${u?n.fg("accent"," \u203A"):" "} ${n.fg(u?"accent":"text",h)}${p?` ${n.fg("soft",p)}`:""}`}),l=s.length-this.top-i.length;return["",` ${n.bold(n.fg("accent",this.title))} ${n.fg("soft",`\xB7 ${s.length}`)}`,` ${n.fg("hair","\u2500".repeat(Math.max(0,t-1)))}`,` ${n.fg("accent","/")} ${this.filter?`${n.fg("text",x(this.filter))}\x1B[7m \x1B[0m`:n.fg("soft","type to filter")}`,"",...this.top?[n.fg("soft",` \u2191 ${this.top} more`)]:[],...o.length?o:[n.fg("soft"," Nothing matches.")],...l>0?[n.fg("soft",` \u2193 ${l} more`)]:[],"",n.fg("soft"," \u2191\u2193 move \xB7 enter choose \xB7 esc close")]}},tt=r=>typeof r=="string"&&r?r:void 0;function Ds(r,e){let t=tt(r.subagent_id),n=tt(r.name),s=tt(r.status);if(!t||!n||!s)return;let i=e.get(t),a=typeof r.cap_usd=="number"&&Number.isFinite(r.cap_usd)?r.cap_usd:i?.cap_usd,o=tt(r.model)??i?.model;return e.set(t,{name:n,status:s,...a===void 0?{}:{cap_usd:a},...o?{model:o}:{}}),s==="running"&&!i?`subagent ${n} started${o?` on ${o}`:""}${a===void 0?"":`, cap $${a.toFixed(2)}`}`:i?.status===s?void 0:`subagent ${n} ${s}`}var rt=class{constructor(e,t,n){this.t=e;this.tui=t;this.header=new At(e),this.status=new Rt(e),this.transcript.addChild(this.header),this.live.addChild(new Me),this.live.addChild(n),this.live.addChild(this.status)}t;tui;transcript=new wn;live=new wn;header;status;assistant=new Map;tools=new Map;approvals=new Map;subagents=new Map;streaming;lastTool;usage={cost:0,context:void 0};since=Date.now()-3e4;pick(e,t){let n=this.live.children.indexOf(t);return this.live.children[n]=e,this.tui.requestRender(!0),()=>{this.live.children[n]=t,this.tui.requestRender(!0)}}reset(){this.transcript.clear(),this.transcript.addChild(this.header),this.assistant.clear(),this.tools.clear(),this.approvals.clear(),this.subagents.clear(),this.streaming=void 0,this.lastTool=void 0,this.usage={cost:0,context:void 0},this.status.set({...this.usage}),this.tui.requestRender(!0)}setHeader(e){this.header.set(e)}setStatus(e){this.status.set(e)}toggleThinking(){return se.hideThinking=!se.hideThinking,this.transcript.invalidate(),this.tui.requestRender(!0),se.hideThinking}toggleLastTool(){return this.lastTool?(this.lastTool.toggle(),this.tui.requestRender(!0),!0):!1}tick(e){this.status.set({frame:e});for(let{row:t,started:n}of this.tools.values())t.finished||t.setRunning(Date.now()-n)}add(e){this.transcript.addChild(e)}note(e,t="soft",n="",s=""){this.add(new Et(this.t,e,t,n,s))}panel(e,t,n="accent"){let s=Array.isArray(t)&&t.length?` ${this.t.fg("soft",`\xB7 ${t.length}`)}`:"";this.add(new ke(this.t,`${x(e)}${s}`,i=>Pt(this.t,t,i),n))}chats(e,t){let{t:n}=this;if(!e.length){this.panel("Chats","No chats yet. Type a message to start one.");return}let s=[...e].sort((c,u)=>Date.parse(u.updated_at)-Date.parse(c.updated_at)),i=Math.min(32,Math.max(...s.map(c=>c.name.length))),a=Math.min(28,Math.max(4,...s.map(c=>(c.task??"").length))),o=(c,u)=>c.length>u?`${c.slice(0,u-1)}\u2026`:c.padEnd(u),l=c=>c==="failed"?"bad":["working","queued","stopping"].includes(c)?"accent":"soft",d=s.map(c=>{let u=c.id===t;return[u?n.fg("accent","\u25CF"):" ",n.fg(u?"accent":"text",o(x(c.name),i)),n.fg("soft",o(x(c.task??"\u2014"),a)),n.fg(l(c.status),c.status.padEnd(8)),n.fg("soft",De(c.updated_at).padStart(8)),n.fg("hair",c.id)].join(" ")});this.add(new ke(n,`Chats ${n.fg("soft",`\xB7 ${e.length}`)}`,[...d,"",n.fg("soft","/open ID opens one \xB7 /new NAME starts one")]))}markdown(e,t){let n=new $s(t,0,0,Ce(this.t));this.add(new ke(this.t,x(e),s=>n.render(s).map(i=>i.trimEnd())))}help(e,t){this.add(new ke(this.t,e,n=>Ms(this.t,t,n)))}event(e){let{t}=this,n=e.data??{};switch(e.kind){case"user":{let s=n.mode==="steer"?"steer: ":n.mode==="follow_up"?"next: ":"";this.add(new St(t,`${s}${x(n.text??"")}`));return}case"delta":{this.streaming||(this.streaming=new se(t),this.add(this.streaming)),typeof n.thinking=="string"&&this.streaming.appendThinking(x(n.thinking)),typeof n.text=="string"&&this.streaming.append(x(n.text));return}case"assistant":{let s=String(n.message_id??e.seq),i=x(n.text??""),a=this.streaming??this.assistant.get(s);a||(a=new se(t),this.add(a));let o=new se(t);o.streaming=!1,typeof n.thinking=="string"&&o.setThinking(x(n.thinking)),o.append(i),this.transcript.children[this.transcript.children.indexOf(a)]=o,this.assistant.set(s,o),this.streaming=void 0;return}case"tool_start":{this.transcript.children.at(-1)instanceof he||this.add(new Me);let s=n.arguments&&typeof n.arguments=="object"?n.arguments:void 0,i=new he(t,{seq:e.seq,name:x(n.tool??"tool"),subject:Ct(n.arguments),subagent:!!n.subagent_id,args:s}),a=e.created_at?Date.parse(e.created_at):NaN;this.tools.set(String(n.call_id??e.seq),{row:i,started:Number.isFinite(a)?a:Date.now()}),this.add(i);return}case"tool_end":{let s=this.tools.get(String(n.call_id??"")),i=s?.row??new he(t,{seq:e.seq,name:x(n.tool??"tool"),subject:"",subagent:!!n.subagent_id});s||(this.transcript.children.at(-1)instanceof he||this.add(new Me),this.add(i));let a=x(n.text??""),o=a.split(`
84
+ `).find(d=>d.trim())??"",l=e.created_at?Date.parse(e.created_at):Date.now();i.finish({isError:!!n.error,summary:n.cancelled?"cancelled":o,output:a,tookMs:s?Math.max(0,l-s.started):void 0}),Array.isArray(n.images)&&i.setImages(n.images),this.lastTool=i;return}case"tool_update":{let s=this.tools.get(String(n.call_id??""));s&&!s.row.finished&&typeof n.text=="string"&&s.row.addPartial(x(n.text),!!n.reset);return}case"approval_request":{let s=new Tt(t,n);this.approvals.set(String(n.approval_id??""),s),this.add(s);return}case"approval_decision":case"approval_withdrawn":this.approvals.get(String(n.approval_id??""))?.decide(e.kind==="approval_withdrawn"?`withdrawn${n.reason?` \xB7 ${x(n.reason)}`:""}`:x(n.decision??"decided"));return;case"usage":{(e.created_at?Date.parse(e.created_at):NaN)<this.since||(this.usage.cost+=Number(n.cost??0)),typeof n.context=="number"&&!n.subagent_id&&(this.usage.context=n.context),this.status.set({...this.usage});return}case"error":this.streaming=void 0,this.note(yn(n),"bad","\u2717");return;case"compaction":this.note(typeof n.summary=="string"&&n.summary.trim()?`Context compacted. Summary:
85
+ ${x(n.summary)}`:x(n.text??"Context compacted."),"soft","\u21BB");return;case"file":{let s=x(n.path??"").replace(/^\/workspace\//,""),a=[...this.tools.values()].map(o=>o.row).reverse().find(o=>["write","edit"].includes(o.info.name)&&(o.path??"").replace(/^\/workspace\//,"")===s);a&&typeof n.content=="string"?a.setChange(typeof n.before=="string"?n.before:"",n.content):this.note(`wrote ${s}`,"soft","\u270E");return}case"status":n.status==="stopped"&&this.streaming?.setNote("stopped");return;case"subagent":{let s=Ds(n,this.subagents);if(s&&this.note(x(s),/failed/.test(s)?"bad":"soft","\u25C6"),typeof n.report=="string"&&n.report.trim()){let i=new et(t,`[subagent ${x(n.name??"subagent")} ${x(n.status??"done")}] ${x(n.report)}`);this.add(i),this.lastTool=i}return}case"checkpoint":return;default:return}}},Me=class extends F{draw(){return[""]}};import{getBuiltinModels as zs}from"@earendil-works/pi-ai/providers/all";import{accessSync as Ls,constants as Us,statSync as Hs}from"node:fs";import{delimiter as qs,join as js}from"node:path";function Ot(r){return{...r,triggerCharacters:r.triggerCharacters,applyCompletion:r.applyCompletion.bind(r),shouldTriggerFileCompletion:r.shouldTriggerFileCompletion?.bind(r),async getSuggestions(e,t,n,s){let i=await r.getSuggestions(e,t,n,s),a=i?.prefix.startsWith("/")?i.prefix.slice(1).toLowerCase():"";if(!i||!a)return i;let o=i.items.filter(l=>l.value.replace(/^\//,"").toLowerCase().startsWith(a));return o.length?{...i,items:o}:i}}}function kn(r){let e=process.platform==="win32"?".exe":"";for(let t of(r.PATH??"").split(qs).filter(Boolean))for(let n of["fd","fdfind"]){let s=js(t,n+e);try{if(Ls(s,Us.X_OK),Hs(s).isFile())return s}catch{}}}var Nt=["off","minimal","low","medium","high","xhigh"];function Gs(r){let e=r.split("/")[0];if(!e)return[];let t=[];try{t=zs(e)}catch{return[]}return t.map(n=>({value:`${e}/${n.id}`,label:`${e}/${n.id}`,detail:[n.name,n.contextWindow?`${Math.round(n.contextWindow/1e3)}k context`:"",n.reasoning?"reasoning":""].filter(Boolean).join(" \xB7 "),current:`${e}/${n.id}`===r}))}function Ws(r){let e=new Map;for(let t of r){let n=/^\/([\w-]+)\s*(.*)$/.exec(t);if(!n||e.has(n[1]))continue;let[s="",i]=n[2].split(/\s{2,}| — /,2).map(o=>o.trim()),a=[...new Set(s.split(/\s*[|;]\s*/).map(o=>o.split(/\s/)[0]).filter(o=>/^[a-z]/.test(o)))].join(" \xB7 ");e.set(n[1],{name:n[1],description:i||a||s,...i&&s?{argumentHint:s}:{}})}return[...e.values()]}var vn=["Same chats, files, queues and approvals as the browser agent.","/chats [SEARCH] list chats","/new NAME start a chat","/open ID open a chat","/rename NAME rename this chat","/fork [NAME [SEQ]] branch this chat (alone: pick the message to branch from)","/tasks list tasks","/task NAME scope chats to a task (/task all clears it)","/task create NAME create a task","/model [PROVIDER/MODEL] switch model (alone: pick one)","/thinking [LEVEL] set reasoning effort (alone: pick one)","/compact compact the context","/reload reload the hosted runtime","/steer TEXT redirect the agent while it works","/next TEXT queue a message for when it finishes","/stop stop the agent","/approve ID approve a pending request","/reject ID reject a pending request","/queue show queued messages (edit ID TEXT, remove ID)","/pending commands whose delivery is uncertain","/retry [REQUEST_ID] resend an uncertain command","/files files the agent changed","/view PATH show a file","/diff PATH show a file's changes","/download PATH DEST save a workspace file locally","/export DEST save this chat","/skills list skills (import PATH, view, toggle, invoke, reload)","/memory [QUERY] search memory (page OFFSET, delete ID)","/history [SEQ] replay earlier events","!COMMAND run a shell command in the workspace (!! keeps output out of context)","Esc stops the agent or pauses a local transfer. Ctrl-O shows the last tool's output. Ctrl-T hides or shows the agent's reasoning. Ctrl-C stops work, otherwise quits.","/help show every command","/quit leave; hosted work keeps running"];function Js(r){let e=r.replace(/^!!?/,"").trim().split(`
86
+ `)[0].trim();return e?e.length>60?`${e.slice(0,57)}\u2026`:e:"New chat"}function Ys(r){let e;for(let t of r)(!e||Date.parse(t.updated_at)>Date.parse(e.updated_at))&&(e=t);return e}async function _n(r){let e=new Ye(r.client,r.task),t=r.env??process.env,n=new Bs(r.terminal),s=kt({background:Ve(t),mode:pn(t)}),i=new Xe(n,s),a=!1;i.editor.setAutocompleteProvider(Ot(new Fs(Ws([...vn,...r.moreHelpLines??[]]),process.cwd(),kn(t))));let o=new rt(s,n,i);function l(){let y=e.detail?e.detail.name:`new chat${["Disconnected","Connected"].includes(e.connection)?"":` \xB7 ${e.connection.toLowerCase()}`}`;o.setHeader([r.org,e.task??"all tasks",y].filter(Boolean).join(" \xB7 ")),o.setStatus({chat:e.detail,connection:e.connection}),i.thinking=e.detail?.thinking}let d=!1,c=()=>{},u,h=0,p=-1,g=0,f=0,b,_=new Promise(y=>c=y),m,w,E={Ready:"soft","New chat":"ok",Saved:"ok",Imported:"ok",Exported:"ok","Stop requested":"warn",Busy:"warn","Hosted workspace loading":"warn","Transfer paused":"warn",Connection:"warn",Error:"bad","Stop failed":"bad","Dashboard command failed":"bad",Cancelled:"soft",GitHub:"ok","Connect GitHub":"soft","GitHub disconnected":"warn"};function k(y,T){let I=y==="GitHub"&&typeof T=="string"&&T.startsWith("Not connected")?"warn":E[y];I&&typeof T=="string"?o.note(T,I,I==="bad"?"\u2717":I==="ok"?"\u2713":I==="warn"?"!":"",y==="Ready"?"":y):typeof T=="string"&&/commands$|^Account$/i.test(y)?o.help(y,T.split(`
87
+ `)):o.panel(y,T,I==="bad"?"bad":"accent"),n.requestRender()}function S(){p!==e.generation&&(p=e.generation,g=0,o.reset());for(let y of e.events.slice(g))o.event(y);g=e.events.length,l(),n.requestRender()}function v(){m?.abort(),!d&&(d=!0,u&&clearTimeout(u),clearInterval(b),e.setTask(e.task),n.stop(),c(0))}async function A(){if(!d){try{await e.poll(),e.chat&&await e.refresh(),h=0,S()}catch(y){h++,(h===1||h===5)&&k("Connection",y instanceof Error?y.message:String(y)),y instanceof z&&[401,403].includes(y.status)&&(h=5)}!d&&h<5?u=setTimeout(()=>{A()},Math.min(1e4,500*2**h)):d||k("Connection","Polling paused after 5 failures. /reconnect resumes; uncertain commands remain under /pending.")}}function M(){h=0,u&&clearTimeout(u),A()}function D(y,T){if(!y?.trim())throw Error(T);return y}async function $e(y){let T=y.trim();if(!T)return;if(T==="/quit"||T==="/exit"){v();return}if(!T.startsWith("/")){if(!e.chat){let Be=await e.create(Js(T));S(),k("New chat",`${Be.name} (${Be.id})`)}let R=T.startsWith("!"),$=R?"shell":e.busy?"steer":"prompt",K=R?T.replace(/^!!?/,"").trim():T,ae=R?{excludeFromContext:T.startsWith("!!")}:{},Te=w;if(Te&&Te.raw===T&&Te.chat===e.chat&&e.pending.has(Te.request_id))await e.retry(Te.request_id);else try{await e.send($,K,ae)}catch(Be){let ft=[...e.pending.values()].find(Ke=>Ke.chat===e.chat&&Ke.body.kind===$&&Ke.body.text===K&&JSON.stringify(Ke.body.options)===JSON.stringify(ae));throw ft&&(w={raw:T,chat:ft.chat,request_id:ft.body.request_id}),Be}w=void 0;return}let I=/^\/(\S+)(?:\s+([\s\S]*))?$/.exec(T),L=I[1],C=I[2]??"",H=Ks(C);if(H.some(R=>typeof R!="string"))throw Error("Use plain arguments; shell operators are supported only with !");let O=H;if(r.runDashboardCommand){m=new AbortController;try{let R=await r.runDashboardCommand(T,{client:r.client,task:e.task,show:k,markdown:($,K)=>{o.markdown($,K),n.requestRender()},signal:m.signal,promptSecret:je});if(m.signal.throwIfAborted(),R)return}finally{m=void 0}}switch(L){case"help":k("Chat commands",vn.join(`
88
+ `));break;case"reconnect":M();break;case"tasks":k("Tasks",(await r.client.request("/v1/tasks")).slice().sort((R,$)=>Date.parse(String($.created_at??0))-Date.parse(String(R.created_at??0))));break;case"task":if(O[0]==="create"){let R=await r.client.request("/v1/tasks",{method:"POST",body:JSON.stringify({name:D(O.slice(1).join(" "),"Enter task name")})});e.setTask(R.name)}else{let R=D(C,"Use /task NAME or /task all"),$=R==="all"?R:Je(R);if($!=="all"&&!(await r.client.request("/v1/tasks")).some(ae=>ae.name===$))throw Error("Task not found; use /tasks or /task create NAME");e.setTask($==="all"?void 0:$)}S(),o.chats(await e.list(),e.chat);break;case"model":case"thinking":case"fork":if(!C){await X(L);break}if(L==="fork"){let R=O.at(-1),$=R&&/^\d+$/.test(R)?Number(R):void 0;await e.fork(D($===void 0?C:O.slice(0,-1).join(" "),"Enter fork name"),$),await e.poll(),S();break}if(L==="thinking"&&!Nt.includes(C))throw Error(`Thinking level: ${Nt.join(", ")}`);await e.send(L,C);break;case"chats":{let R=await e.list(C);if(!R.length){o.chats(R);break}let $=new nt(s,R,e.chat);oe($,K=>{K.id!==e.chat&&(async()=>{await e.open(K.id),await e.poll(),S()})().catch(ae=>k("Error",ae instanceof Error?ae.message:String(ae)))});break}case"open":await e.open(D(O[0],"Enter chat ID")),await e.poll(),S();break;case"new":await e.create(C||"New chat"),await e.poll(),S();break;case"rename":await e.rename(D(C,"Enter chat name"));break;case"compact":case"reload":case"stop":await e.send(L);break;case"approve":case"reject":await e.send(L,D(C,"Enter approval ID"));break;case"steer":case"next":await e.send(L==="next"?"follow_up":"steer",D(C,"Enter a message"));break;case"retry":await e.retry(O[0]);break;case"pending":k("Uncertain commands",[...e.pending.values()].filter(R=>R.chat===e.chat));break;case"queue":if(O[0]==="edit")await e.editQueue(D(O[1],"Enter command ID"),D(O.slice(2).join(" "),"Enter replacement text"));else if(O[0]==="remove")await e.send("cancel",D(O[1],"Enter command ID")),await e.refresh();else{if(O.length)throw Error("Use /queue, /queue edit ID TEXT, or /queue remove ID");await e.refresh(),k("Queue",e.detail?.commands??[])}break;case"files":await e.refresh(),k("Files",Object.keys(e.detail?.files??{}));break;case"view":case"diff":case"download":{await e.refresh();let R=D(O[0],"Enter hosted file path"),$=e.detail?.files[R];if(!$)throw Error("File not found");if(L==="download"){let K=D(O[1],"Enter an explicit local destination");await yt(K,$.content),k("Saved",K)}else k(R,L==="diff"?`BEFORE
89
+ ${$.before??"(no previous content)"}
90
+ AFTER
91
+ ${$.content}`:$.content);break}case"skills":await e.refresh(),k("Skills",e.detail?.skills??[]);break;case"skill":{let R=O[0];if(R==="import"){let $=await an(D(O[1],"Enter local skill file or folder"));await e.skill($),k("Imported",$.name)}else if(R==="reload")await e.send("reload");else{await e.refresh();let $=e.detail?.skills.find(K=>K.name===O[1]);if(!$)throw Error("Skill not found");if(R==="view")k($.name,$.files[O[2]??"SKILL.md"]??(()=>{throw Error("Skill file not found")})());else if(R==="toggle")await e.skill({...$,enabled:!$.enabled});else if(R==="invoke"){if(!$.enabled)throw Error("Enable the skill before invoking it");await e.send(e.busy?"steer":"prompt",`/skill:${$.name} ${O.slice(2).join(" ")}`.trim())}else throw Error("Use /skill import, view, toggle, invoke, or reload")}break}case"memory":if(O[0]==="delete")k("Deleted note",await r.client.request(`/v1/agent/notes/${encodeURIComponent(D(O[1],"Enter note ID"))}`,{method:"DELETE"}));else{let R=O[0]==="page"?Number(O[1]):0;if(!Number.isSafeInteger(R)||R<0)throw Error("Page offset must be a nonnegative integer");let $=O[0]==="page"?O.slice(2).join(" "):C;k("Memory",await r.client.request(`/v1/agent/notes?limit=100&offset=${R}&query=${encodeURIComponent($)}`))}break;case"history":{let R=C?Number(C):0;if(!Number.isSafeInteger(R)||R<0)throw Error("Enter a nonnegative sequence");k("History",e.events.filter($=>$.seq>=R).map($=>({seq:$.seq,kind:$.kind,created_at:$.created_at,text:$.kind==="tool_start"?`${String($.data?.tool??"tool")} ${Ct($.data?.arguments)}`:typeof $.data?.text=="string"&&$.data.text?$.data.text.split(`
92
+ `).find(K=>K.trim())??"":it($.data??{})})));break}case"export":await yt(D(O[0],"Enter an explicit local export destination"),JSON.stringify(await e.export(),null,2)+`
93
+ `),k("Exported",O[0]);break;default:throw Error(`Unknown command /${L}. Use /help.`)}n.requestRender()}function oe(y,T){let I=o.pick(y,i);a=!0;let L=()=>{a=!1,I(),n.setFocus(i)};y.onCancel=L,y.onPick=C=>{L(),T(C)},n.setFocus(y)}async function X(y){let T=(C,H)=>{e.send(C,H).then(()=>k("Saved",`${C==="model"?"Model":"Reasoning"}: ${H}`),O=>k("Error",O instanceof Error?O.message:String(O)))};if(y==="thinking"){let C=e.detail?.thinking;oe(new ve(s,"Reasoning",Nt.map(H=>({value:H,label:H,current:H===C}))),H=>T("thinking",H.value));return}if(y==="model"){let C=e.detail?.model??"",H=Gs(C);if(!H.length)throw Error("Use /model PROVIDER/MODEL");oe(new ve(s,"Model",H),O=>T("model",O.value));return}let I=e.events.filter(C=>C.kind==="user"&&typeof C.data?.text=="string");if(!I.length)throw Error("Nothing to fork from yet");let L=I.map(C=>({value:String(C.seq),label:String(C.data.text).split(`
94
+ `)[0],detail:C.created_at?De(C.created_at):""})).reverse();oe(new ve(s,"Fork from",L),C=>{(async()=>{await e.fork(`${e.detail?.name??"Chat"} (fork)`,Number(C.value)),await e.poll(),S()})().catch(H=>k("Error",H instanceof Error?H.message:String(H)))})}let V=!1,B=!1,Q;function je(y,T){Q=T,i.secret=y,i.setText(""),n.requestRender()}function Fe(){Q=void 0,i.secret=void 0,i.setText(""),n.requestRender()}async function P(y){if(Q){let I=Q;if(Fe(),!y.trim())return;V=!0,o.setStatus({busy:"checking"});try{await I(y.trim())}catch(L){k("Error",L instanceof Error?L.message:String(L))}finally{V=!1,o.setStatus({busy:void 0}),n.requestRender()}return}let T=e.generation;if(!B||V){i.setText(y),k(B?"Busy":"Hosted workspace loading",B?"Wait for the current request to finish. Your draft is preserved.":"Wait for the selected chat to load. Your draft is preserved.");return}V=!0,o.setStatus({busy:y.startsWith("/")?`running ${y.split(" ")[0]}`:"sending"}),n.requestRender();try{await $e(y),i.getText()===y&&i.setText("")}catch(I){d||(S(),T===e.generation&&!i.getText()&&!y.startsWith("/")&&i.setText(y),k(I instanceof Error&&I.name==="AbortError"?"Transfer paused":"Error",I instanceof Error&&I.name==="AbortError"?"Local request interrupted. Upload receipts are preserved; use /uploads retry to resume. Hosted jobs continue.":I instanceof z?I.status===401?`${I.detail}. Run /login to sign in again.`:I.detail:I instanceof Error?I.message:String(I)))}finally{V=!1,o.setStatus({busy:void 0}),l(),T!==e.generation&&p!==e.generation&&!/^\/(task|open|new|fork|quit|exit)(?:\s|$)/.test(y)&&(p=e.generation,g=0,o.reset()),n.requestRender()}}n.addChild(o.transcript),n.addChild(o.live),i.onSubmit=y=>{P(y)},n.addInputListener(y=>{if(!(a&&!ie(y,"ctrl+c"))){if(Q&&ie(y,"escape"))return Fe(),k("Cancelled","Nothing was sent."),{consume:!0};if(ie(y,"escape")&&m)return m.abort(),k("Transfer paused","Interrupting the local dashboard request; upload receipts are preserved."),{consume:!0};if(B&&!m&&e.busy&&(ie(y,"escape")||ie(y,"ctrl+c")))return e.send("stop").then(()=>k("Stop requested","Stopping hosted work; the chat remains open."),T=>k("Stop failed",T instanceof Error?T.message:String(T))),{consume:!0};if(ie(y,"ctrl+t"))return o.toggleThinking(),{consume:!0};if(ie(y,"ctrl+o"))return o.toggleLastTool(),{consume:!0};if(ie(y,"ctrl+c"))return i.getText()?i.setText(""):v(),n.requestRender(),{consume:!0};if(ie(y,"escape")&&i.getText()&&!i.editor.isShowingAutocomplete())return i.setText(""),n.requestRender(),{consume:!0}}}),n.setFocus(i),n.start(),!t.ASHR_THEME&&s.mode!=="none"&&n.queryTerminalColors({timeoutMs:150}).then(y=>{let T=y?.background;d||!T||Ve(t,T)===s.background||(Object.assign(s,kt({background:Ve(t,T),mode:s.mode})),n.invalidate(),n.requestRender(!0))}).catch(()=>{}),l(),b=setInterval(()=>{o.tick(++f),(V||e.busy)&&n.requestRender()},120);try{let y=r.chat??(r.continueLatest?Ys(await e.list())?.id:void 0);y?(await e.open(y),await e.poll(),S(),k("Ready",`${e.detail?.name??y} is open. /new NAME starts another chat, /chats lists them, /help shows every command.`)):(p=e.generation,k("Ready","Type a message to start a new chat. /chats opens an earlier one \xB7 / shows commands"))}catch(y){k("Error",y instanceof Error?y.message:String(y))}return B=!0,r.onReady?.({workspace:e,submit:P,quit:v}),A(),await _}var _e=`# Manifold Python SDK
95
+
96
+ ## Quickstart
97
+
98
+ Capture calls, train candidates, and serve promoted models. Reviewed against repository code on September 15, 2026; deployed API/worker and installed SDK versions may differ.
99
+
100
+ ### Install
101
+
102
+ \`\`\`bash
103
+ pip install ashr-manifold # Python 3.10+; from a checkout: pip install ./sdk
104
+ export ASHR_BASE_URL="https://api.manifold.ashr.io"
105
+ export ASHR_API_KEY="ask_..."
106
+ \`\`\`
107
+
108
+ Create a service key in the platform\u2019s API keys page. The platform renders this guide under SDK docs.
109
+
110
+ \`\`\`python
111
+ from ashr_sdk import AshrClient
112
+ ashr = AshrClient() # reads both environment variables
113
+ \`\`\`
114
+
115
+ Examples use placeholder IDs. Replace them with objects in your tenant; keep one client open and close it when finished.
116
+
117
+ ### Serve a promoted model
118
+
119
+ Call a promoted model with the Anthropic- or OpenAI-style client, pointed at
120
+ Manifold's base URL with the task name as \`model\`. The capture ID comes back in
121
+ the \`X-Manifold-Interaction-Id\` response header:
122
+
123
+ \`\`\`python
124
+ import os
125
+ from anthropic import Anthropic
126
+
127
+ model_client = Anthropic(
128
+ api_key=os.environ["ASHR_API_KEY"],
129
+ base_url=os.environ["ASHR_BASE_URL"].rstrip("/"),
130
+ )
131
+ served = model_client.messages.with_raw_response.create(
132
+ model="game_generation", # a promoted task; add "@alias" to pin one
133
+ max_tokens=1024,
134
+ messages=[{"role": "user", "content": "Build an ice cavern."}],
135
+ )
136
+ message = served.parse()
137
+ interaction_id = served.headers.get("x-manifold-interaction-id")
138
+ print(message.content[0].text, interaction_id)
139
+ \`\`\`
140
+
141
+ Requires a promoted, loadable artifact. Manifold captures served completions
142
+ automatically when telemetry succeeds. See Host a trained model for the OpenAI
143
+ client, tools, streaming, and limits.
144
+
145
+ ### Wrap an existing client
146
+
147
+ For models served elsewhere, install the provider package and wrap its client:
148
+
149
+ \`\`\`python
150
+ import anthropic
151
+ import ashr_sdk as capture
152
+
153
+ capture.init(task="game_generation")
154
+ client = capture.wrap(anthropic.Anthropic()) # your provider credentials
155
+ response = client.messages.create(
156
+ model="claude-sonnet-4-5", max_tokens=512,
157
+ messages=[{"role": "user", "content": "Design a level."}],
158
+ )
159
+ capture.flush()
160
+ \`\`\`
161
+
162
+ Supports OpenAI and Anthropic sync, async, and streaming calls. Capture failures warn without failing the provider call. Flush before short-lived processes exit.
163
+
164
+ ## Capture production behavior
165
+
166
+ Production shows captured calls, model lineage, review coverage, latency, and cost. Use the SDK to capture calls and record feedback.
167
+
168
+ ### Group calls with a trace
169
+
170
+ \`\`\`python
171
+ with ashr.trace("support_routing", client_ref="ticket-4821"):
172
+ interaction_id = ashr.log(
173
+ task="support_routing", model_id="m_router",
174
+ input={"subject": "Cannot reset password"},
175
+ output={"queue": "identity"}, latency_ms=210,
176
+ input_tokens=86, output_tokens=18,
177
+ decisions=[{
178
+ "field_path": "queue", "value": "identity", "confidence": 0.91,
179
+ "alternatives": [{"value": "billing", "probability": 0.07}],
180
+ "source_citation": "ticket subject",
181
+ }],
182
+ )
183
+ \`\`\`
184
+
185
+ \`log()\` requires a registered \`model_id\`. The wrapper and \`log_batch()\` also support model-name auto-registration. \`client_ref\` is your request ID; use dotted \`field_path\` values for nested decisions.
186
+
187
+ ### Attach artifacts
188
+
189
+ \`\`\`python
190
+ artifact = ashr.artifacts.register(
191
+ name="ticket attachment", kind="document",
192
+ bucket="my-evidence", object_key="tickets/4821.pdf",
193
+ )
194
+ interaction_id = ashr.log(
195
+ "support_routing", "m_router", {"ticket": "4821"}, {"queue": "identity"},
196
+ artifacts=[artifact["id"]],
197
+ )
198
+ url = ashr.artifacts.access_url(artifact["id"], expires_in=900)
199
+ \`\`\`
200
+
201
+ \`artifacts.list()\` and \`get(id)\` read registered evidence. These are object-store artifacts; discovered training weights use the download API under Run model experiments.
202
+
203
+ ## Build feedback and evidence
204
+
205
+ ### Record human feedback
206
+
207
+ \`\`\`python
208
+ ashr.feedback(
209
+ interaction_id, "corrected", corrected_output={"queue": "account_recovery"},
210
+ diff={"queue": ["identity", "account_recovery"]},
211
+ by="reviewer-142", reason="routing_error",
212
+ )
213
+ \`\`\`
214
+
215
+ Use \`confirmed\` for correct output, \`corrected\` with the complete desired output, or \`rejected\`. Rejected output is never a positive target; it can supply a DPO negative.
216
+
217
+ ### Report validator checks
218
+
219
+ \`\`\`python
220
+ ashr.checks([{
221
+ "interaction_id": interaction_id,
222
+ "checks": {"schema_valid": True, "correct_queue": False},
223
+ "details": {"correct_queue": "expected account_recovery"},
224
+ }], reviewer="routing-validator")
225
+ \`\`\`
226
+
227
+ All checks passing confirms the observation; any failure rejects it. Report measured results only.
228
+
229
+ ### Import labeled data
230
+
231
+ \`\`\`python
232
+ result = ashr.datasets.import_examples(
233
+ task="support_routing", model_id="m_router",
234
+ examples=[{"subject": "Reset password", "queue": "identity"}],
235
+ input_fn=lambda row: {"subject": row["subject"]},
236
+ output_fn=lambda row: {"queue": row["queue"]},
237
+ metadata_fn=lambda row: {"source": "reviewed_tickets"},
238
+ batch_size=25, freeze=False,
239
+ )
240
+ \`\`\`
241
+
242
+ Accepts any iterable, including a Hugging Face Dataset. Batches allow 1\u2013500 rows; use 25\u201350 for large payloads. \`log_batch()\` accepts \`interaction\` plus optional \`feedback\`; \`interaction.model\` can register by name.
243
+
244
+ \`input.source\` is reserved for \`manifold.source/v1\`; put ordinary provenance in \`input.metadata\`. The batch API accepts historical \`created_at\` values from 2020 through now and normalizes aware timestamps to UTC.
245
+
246
+ ### Freeze and resume safely
247
+
248
+ \`\`\`python
249
+ dataset = ashr.datasets.freeze("support_routing", holdout_fraction=0.1)
250
+ dataset_id = dataset["id"]
251
+ versions = ashr.datasets.list()
252
+ \`\`\`
253
+
254
+ Freeze records immutable membership and holdout splits. Alternatively, set \`freeze=True\` on import and use \`result["dataset"]["id"]\`; do not freeze again without new labeled data (400). After an import failure, inspect \`failed_index\`/\`completed\` in the API error. The SDK exposes that detail as \`AshrError\` text, not a cross-batch resume cursor. Track your batches. After an ambiguous import or freeze timeout, reconcile accepted rows or dataset versions before retrying; a concurrent freeze returns 409.
255
+
256
+ ## Run model experiments
257
+
258
+ Experiments trains candidates on the same frozen dataset and compares their evaluation evidence. Use the SDK for training configuration, cancellation, retries, and deployment rollback.
259
+
260
+ ### Create an experiment
261
+
262
+ \`\`\`python
263
+ experiment = ashr.experiments.create(
264
+ name="support-router-v5", task="support_routing", dataset="ds_support_v5",
265
+ models=["org/router-small", {
266
+ "model_ref": "org/router-large", "revision": "PINNED_REVISION",
267
+ "recipe": "dpo_lora", "config": {"gpu": "L40S", "dpo_beta": 0.1},
268
+ }],
269
+ training_config={"epochs": 1, "eval_batch_size": 8, "eval_max_tokens": 256},
270
+ primary_metric="field_f1", gates={"field_f1": 0.8, "schema_validity": 0.95},
271
+ evaluator_config={"required_fields": ["queue"]},
272
+ )
273
+ print(experiment.id, experiment.estimate())
274
+ experiment.start(idempotency_key="support-router-v5-start")
275
+ experiment.wait(timeout=21600, poll_interval=10)
276
+ results = experiment.comparison()
277
+ print(experiment.failures())
278
+ \`\`\`
279
+
280
+ Pin each model revision. Candidate settings override experiment defaults. SFT uses positive targets; DPO needs chosen/rejected pairs. Gates require measured metrics and cannot lower platform floors. An estimate is directional, not a bill.
281
+
282
+ ### Track runs and recover failures
283
+
284
+ \`\`\`python
285
+ experiment.refresh()
286
+ for candidate in experiment.candidates:
287
+ for run in candidate["runs"]:
288
+ current = ashr.training_runs.get(run["id"])
289
+ print(current["status"], current.get("error"))
290
+ stale_runs = ashr.training_runs.stale(minutes=15)
291
+ # After inspecting a specific run:
292
+ # ashr.training_runs.cancel(run_id)
293
+ # retried = ashr.training_runs.retry(run_id)
294
+ \`\`\`
295
+
296
+ \`wait()\` includes failed/cancelled terminal states; inspect errors and gates. Save the experiment ID before starting. Retry \`start()\` on \`experiments.get(id)\` with the same key. Repeating \`experiments.run()\` creates a new experiment. \`experiments.list()\` lists existing experiments.
297
+
298
+ The worker enables gradient checkpointing by default. For OOM, reduce \`max_length\` or choose a supported larger GPU. \`retain_failed_artifacts=True\` retains a written final artifact after a gate miss; it cannot recover unwritten weights.
299
+
300
+ ### Own the weights
301
+
302
+ \`\`\`python
303
+ resume = ashr.training_runs.weights_url(run_id)
304
+ adapter = ashr.training_runs.weights_url(run_id, format="adapter")
305
+ exact = ashr.training_runs.weights_url(run_id, checkpoint=600)
306
+ final = ashr.training_runs.weights_url(run_id, checkpoint=None, format="adapter")
307
+ print(resume["url"])
308
+ \`\`\`
309
+
310
+ Signed downloads expire after one hour by default. \`latest\` resolves at download time; numbered steps pin selection. Resume archives include weights, optimizer, scheduler, RNG, and trainer state; adapter archives are for inference. A URL can be issued before files exist; the download may return 409 before the first save. Defaults: \`save_steps=100\`, \`save_total_limit=2\`. Download needed steps before pruning.
311
+
312
+ \`\`\`python
313
+ import os
314
+ ashr.training_runs.export_to_huggingface(
315
+ run_id, repo_id="acme/support-router-v5", hf_token=os.environ["HF_TOKEN"],
316
+ )
317
+ \`\`\`
318
+
319
+ The token is forwarded for upload, not stored. The response means queued, not uploaded. Load adapter files with PEFT and the matching base model.
320
+
321
+ ### Discovered artifacts
322
+
323
+ Artifacts lists checkpoints/adapters from Modal volumes, including failed runs. Filter for weights; inspect written time, adoption status, and download size, which includes nested files. Discovery does not mean evaluated, registered, or promoted.
324
+
325
+ For a row\u2019s exact \`volume\` and \`path\`, use its download button/API command or \`POST /v1/modal-artifacts/download-url\` with \`format="resume"\` or \`"adapter"\`. Your session or service key authorizes the grant; no Modal token is needed. The SDK has no dedicated helper for this endpoint.
326
+
327
+ ## Run the full learning cycle
328
+
329
+ ### Run a cycle
330
+
331
+ \`\`\`python
332
+ report = ashr.learning_loops.run(
333
+ task="support_routing",
334
+ candidates=[{"model_source_id": "src_router", "recipe": "sft_lora"}],
335
+ eval_config={"primary_metric": "field_f1"},
336
+ halving={"metric": "proxy_score", "at_progress": 0.5, "kill_fraction": 0.5},
337
+ alias="production", timeout_seconds=300,
338
+ )
339
+ print(report["killed"], report["winner"], report["promoted"], report["reason"])
340
+ \`\`\`
341
+
342
+ Uses registered model-source IDs. Freezes/reuses evidence, builds an environment, trains, halves, evaluates, and promotes only a passing winner that beats the incumbent. Halving is recorded before launch. This synchronous request needs sufficient client/gateway time; inspect runs after a timeout before starting another cycle.
343
+
344
+ ### Test without GPUs
345
+
346
+ Use \`MANIFOLD_TRAINING_BACKEND=mock\` on a local test API. It exercises callbacks, gates, lineage, and cancellation with simulated training. \`backend/scripts/learning_loop_demo.py\` runs the local example. Mock scores do not establish model quality.
347
+
348
+ ## Continuous learning configuration
349
+
350
+ ### Build an environment
351
+
352
+ \`\`\`python
353
+ environment = ashr.environments.build(
354
+ task="support_routing", dataset_id="ds_support_v5",
355
+ reward_spec={"resolved": 1, "policy_violation": -5},
356
+ )
357
+ ashr.learning_loops.configure(
358
+ task="support_routing", environment_id=environment["id"],
359
+ config={"trigger_after_reviewed": 500, "trigger_every_days": 7,
360
+ "benchmark_incumbent": True, "auto_train": True},
361
+ )
362
+ \`\`\`
363
+
364
+ These calls store environment and retraining configuration. Trigger scheduling remains in progress. Read configurations with \`environments.list()\` and \`learning_loops.list()\`.
365
+
366
+ ### Application-side routing
367
+
368
+ \`\`\`python
369
+ ashr.router.configure(
370
+ task="support_routing", incumbent_model_id="m_current",
371
+ challenger_model_id="m_candidate", traffic_percent=5,
372
+ )
373
+ assignment = ashr.router.decide(task="support_routing", request_key="ticket-4821")
374
+ # Your application serves assignment["model_id"], then records the actual result:
375
+ ashr.router.outcome(assignment["id"], {"resolved": True, "reward": 1})
376
+ ashr.router.set_traffic("support_routing", 0)
377
+ \`\`\`
378
+
379
+ Assignment is deterministic per policy/key. For managed serving, also promote the challenger to \`canary\` as described below.
380
+
381
+ ## Host a trained model
382
+
383
+ Requires a promoted task alias backed by a loadable trained artifact. Installing the SDK or finding weights does not create a deployment.
384
+
385
+ ### Choose a client
386
+
387
+ OpenAI uses the API root plus \`/v1\`; Anthropic and \`AshrClient\` use the API root. Install provider packages separately.
388
+
389
+ \`\`\`python
390
+ import os
391
+ from openai import OpenAI
392
+
393
+ client = OpenAI(api_key=os.environ["ASHR_API_KEY"],
394
+ base_url=os.environ["ASHR_BASE_URL"].rstrip("/") + "/v1")
395
+ response = client.chat.completions.create(
396
+ model="game_generation", max_completion_tokens=1024,
397
+ messages=[{"role": "user", "content": "Build an ice cavern."}],
398
+ )
399
+ print(response.choices[0].message.content)
400
+ \`\`\`
401
+
402
+ \`\`\`python
403
+ from anthropic import Anthropic
404
+
405
+ client = Anthropic(api_key=os.environ["ASHR_API_KEY"],
406
+ base_url=os.environ["ASHR_BASE_URL"].rstrip("/"))
407
+ message = client.messages.create(
408
+ model="game_generation", max_tokens=1024,
409
+ system="Return a concise level design.",
410
+ messages=[{"role": "user", "content": "Build an ice cavern."}],
411
+ )
412
+ print(message.content[0].text)
413
+ \`\`\`
414
+
415
+ Tools and streaming work on either client. From OpenAI, declare \`tools\` and stream with \`stream=True\`:
416
+
417
+ \`\`\`python
418
+ openai_client = OpenAI(api_key=os.environ["ASHR_API_KEY"],
419
+ base_url=os.environ["ASHR_BASE_URL"].rstrip("/") + "/v1")
420
+ stream = openai_client.chat.completions.create(
421
+ model="game_generation", stream=True,
422
+ messages=[{"role": "user", "content": "Design a level, then call the tool."}],
423
+ tools=[{"type": "function", "function": {
424
+ "name": "save_level",
425
+ "parameters": {"type": "object", "properties": {"name": {"type": "string"}}}}}],
426
+ )
427
+ stream_text = "".join(
428
+ chunk.choices[0].delta.content or "" for chunk in stream)
429
+ \`\`\`
430
+
431
+ A non-streaming tools call returns \`tool_calls\` on the message with \`finish_reason="tool_calls"\`; send each result back as a \`{"role": "tool", "tool_call_id": ..., "content": ...}\` message. The Anthropic client uses \`tools\` with \`input_schema\` and receives \`tool_use\` content blocks.
432
+
433
+ Native SDK: \`ashr.infer(task, input, alias="production", max_new_tokens=1024)\` returns the parsed \`output\`, per-field \`decisions\`, \`usage\`, \`deployment\`, and \`interaction_id\`. Read \`deployment.alias\` for the actual selection; the top-level alias is the requested one.
434
+
435
+ \`ashr.infer\` (and \`POST /v1/inference/{task}\`) is **deprecated for chat and agentic models** \u2014 use the Anthropic or OpenAI client shown above, which serve the hosted model directly with tools and streaming. It remains supported, and is the right choice, for structured-output tasks (e.g. classification) that need the per-field \`decisions\` capture the transparent surfaces don't produce. It is not being removed.
436
+
437
+ ### Supported requests and generation limits
438
+
439
+ - Model: \`task\` or \`task@alias\`; default alias is \`production\`.
440
+ - Both surfaces are served by the same hosted model \u2014 OpenAI (\`/v1/chat/completions\`) is translated to Anthropic Messages at the edge \u2014 so the same model key works from either SDK. \`system\`/\`user\`/\`assistant\`/\`tool\` roles, \`temperature\`, \`top_p\`, and \`stop\` are supported; \`max_completion_tokens\` takes precedence over \`max_tokens\`.
441
+ - **Tools and streaming are supported on both surfaces.** OpenAI \`tools\` come back as \`tool_calls\` (with \`finish_reason: "tool_calls"\`), \`tool\` messages carry results back, and \`stream: true\` yields \`chat.completion.chunk\` events including tool-call deltas and final usage. The Anthropic surface forwards request and response bodies unchanged \u2014 tools, thinking, and streams \u2014 subject to the hosted model\u2019s capabilities.
442
+ - OpenAI multimodal (image) content is not supported; a message whose content is not text is rejected. Older API deployments support only text and reject streaming; verify your deployed revision.
443
+ - Token limits must be positive. Omitted limits use remaining model context; the Anthropic Python client requires \`max_tokens\`. Configure client timeouts for your budget.
444
+ - Platform keys (\`ask_\`) support platform operations. Model keys (\`msk_\`) grant inference for one task; use a platform key for feedback and training.
445
+
446
+ ### Completion evidence and feedback
447
+
448
+ No capture wrapper is needed for hosted calls. OpenAI responses add \`manifold\` metadata:
449
+
450
+ \`\`\`python
451
+ metadata = response.model_dump()["manifold"]
452
+ interaction_id = metadata["interaction_id"]
453
+ # After actual review, and only if interaction_id is not None:
454
+ # ashr.feedback(interaction_id, "confirmed", by="reviewer-142")
455
+ \`\`\`
456
+
457
+ Includes task, actual alias, and deployment ID. Both surfaces return the capture ID in the \`X-Manifold-Interaction-Id\` header on non-streaming responses (read it via each client\u2019s raw-response interface); the OpenAI non-streaming body also carries it in the \`manifold\` block above. Streams return no capture ID; their capture retains text deltas, not tool/thinking deltas, even though those events reach the caller. Capture failure returns a null interaction ID without failing a completed answer. Do not attach feedback to a missing ID.
458
+
459
+ OpenAI finish reasons: \`stop\`/\`length\`/\`tool_calls\`; null means missing/unrecognized worker evidence. Anthropic stop reasons are passed through from the hosted model. Inspect truncated output. Literal text is retained in \`sampling_params.raw_text\` when available; retained context is not automatically supervised training data.
460
+
461
+ ### Canary rollout and rollback
462
+
463
+ \`\`\`python
464
+ incumbent = ashr.deployments.resolve("support_routing", alias="production")
465
+ canary = experiment.promote(candidate_id, alias="canary")
466
+ ashr.router.configure(
467
+ task="support_routing", incumbent_model_id=incumbent["model_id"],
468
+ challenger_model_id=canary["model_id"], traffic_percent=5,
469
+ )
470
+ ashr.router.set_traffic("support_routing", 0) # stop automatic canary selection
471
+ \`\`\`
472
+
473
+ Use a passing candidate ID. Production requests participate in the split; explicit \`@canary\` bypasses it, even at zero traffic. OpenAI \`user\` or Anthropic \`metadata.user_id\` makes assignment sticky; without a key it is per request. Native \`infer()\` has no request-key parameter.
474
+
475
+ Automatic routing falls back to production if canary is absent, unavailable, or mismatched. Check returned deployment metadata. Alias-history rollback is separate: \`deployments.rollback(deployment_id)\`.
476
+
477
+ ### Hosting failures and separate endpoints
478
+
479
+ - 401: invalid key/environment. 409: alias lacks a loadable artifact.
480
+ - 400/422: local request validation failed. Anthropic upstream failures become 502, or an error event after a stream opens. 502 also indicates inference gateway failure.
481
+ - Timeout: completion may be unknown; a retry can duplicate work.
482
+
483
+ Verify an authenticated request through the intended alias; health and alias existence do not prove GPU readiness. The standalone \`ashr-model-api\` uses its own fixed model and endpoint key, without Manifold tenant lookup or billing. Obtain its details from the operator; do not assume those calls appear in Production.
484
+
485
+ ## Deploy and monitor
486
+
487
+ ### Promote and resolve
488
+
489
+ After reviewing metrics and gates, select a passing candidate:
490
+
491
+ \`\`\`python
492
+ deployment = experiment.promote(candidate_id, alias="production")
493
+ production = ashr.deployments.resolve("support_routing")
494
+ # To restore the previous revision:
495
+ # ashr.deployments.rollback(deployment["id"])
496
+ \`\`\`
497
+
498
+ \`deployments.list(task=...)\` lists aliases; \`get(id)\` reads deployment details. Training completion is not serving readiness or evidence of quality.
499
+
500
+ ### Read production metrics
501
+
502
+ \`\`\`python
503
+ health = ashr.monitoring.model("m_router", days=30)
504
+ daily = ashr.monitoring.timeseries("m_router", days=30)
505
+ comparison = ashr.monitoring.compare("m_previous", "m_router", days=30)
506
+ drift = ashr.monitoring.drift("m_router", days=30, minimum_reviewed=20)
507
+ \`\`\`
508
+
509
+ Inspect volume, review coverage, correction rate, and latency. Reviewer rates use reviewed observations; pending output is not counted as correct.
510
+
511
+ ### Create an alert
512
+
513
+ \`\`\`python
514
+ policy = ashr.monitoring.create_alert(
515
+ name="Routing correction rate", model_id="m_router",
516
+ metric="correction_rate", operator="gte", threshold=0.08,
517
+ window_days=7, minimum_observations=50,
518
+ )
519
+ policies = ashr.monitoring.alert_policies()
520
+ alerts = ashr.monitoring.alerts(limit=100)
521
+ \`\`\`
522
+
523
+ \`evaluate_alerts()\` evaluates policies now. Scheduled checks depend on the deployed monitoring worker.
524
+
525
+ ## Pipelines and progress
526
+
527
+ Pipeline shows stage evidence; Data sources shows owned/shared source access. Use an ID from \`pipelines.list()\`; \`manifold_loop\` exists only after it has been seeded.
528
+
529
+ ### Reading progress
530
+
531
+ \`\`\`python
532
+ pipelines = ashr.pipelines.list()
533
+ status = ashr.pipelines.status("manifold_loop", task="document_parsing")
534
+ print(status.progress, status.stage("capture"))
535
+ fresh = status.refresh(task="document_parsing")
536
+ \`\`\`
537
+
538
+ - \`progress.unbroken\`: completed prefix; \`complete\`: all completed stages, including those past a gap.
539
+ - \`next\` and \`startable\`: next gap and stages whose dependencies permit running.
540
+ - \`status.complete\`, \`blocked\`, \`failed\`, \`unknown\`, \`stale\`, \`startable\`, and \`gates\`: filtered stage lists. \`state(id)\` reads one state.
541
+ - \`unknown\`: probe could not run, not pending. Freshness uses \`observedAt\`, \`ageSeconds\`, \`stale\`, and each probe\u2019s TTL. \`refresh=True\` re-probes.
542
+
543
+ ### Writing a definition
544
+
545
+ \`\`\`python
546
+ ashr.pipelines.put({
547
+ "schemaVersion": 1, "id": "my_pipeline", "label": "My pipeline",
548
+ "phases": [{"id": "acquire", "label": "Acquire", "stages": [{
549
+ "id": "reachable", "title": "Data is reachable",
550
+ "probe": {
551
+ "kind": "object_store", "storeRef": "corpus", "prefix": "sessions/",
552
+ "maxObjects": 10000,
553
+ "extract": {"objects": {"path": "$.Contents[*]", "agg": "count"}},
554
+ "complete": {"gte": ["objects", 1]},
555
+ "detail": "{objects} objects reachable", "ttlSeconds": 3600,
556
+ },
557
+ }]}],
558
+ })
559
+ \`\`\`
560
+
561
+ \`put()\` validates tenant-safe probes and invalidates cached readings. \`get(id)\`, \`delete(id)\`, and \`registries()\` manage definitions and allowed probe/query names. Full schema: \`docs/pipelines.md\`.
562
+
563
+ ### Data sources
564
+
565
+ \`\`\`python
566
+ ashr.pipelines.data_sources.put(
567
+ "corpus", provider="s3_compatible", bucket="my-corpus",
568
+ endpoint="https://nyc3.digitaloceanspaces.com", allowed_prefixes=["sessions/"],
569
+ credential_ref="awssm://YOUR_SECRET_REFERENCE",
570
+ )
571
+ sources = ashr.pipelines.data_sources.list()
572
+ access = ashr.pipelines.data_sources.check("corpus")
573
+ print(access)
574
+ \`\`\`
575
+
576
+ Supply either a secret reference or \`credentials={"access_key_id": ..., "secret_access_key": ...}\`. Credentials are accepted once and never returned. Empty \`allowed_prefixes\` grants no access; probes name the source alias.
577
+
578
+ For shared data, set \`shared_from\` and optionally \`shared_from_tenant_id\`; resolved \`sharing.from_tenant\` takes precedence over the label. \`credential.name\` is a pointer, not secret material. \`delete(alias)\` removes a source.
579
+
580
+ A configured credential does not prove access. Inspect \`reachable\`, \`error\`, and \`truncated\`; a capped listing is a lower bound. S3-compatible checks list metadata only. GCS, Azure Blob, and HTTPS can be registered but report unsupported listing, so their probes read \`unknown\`.
581
+
582
+ ## Reliability and lifecycle
583
+
584
+ ### Handle errors
585
+
586
+ \`\`\`python
587
+ from ashr_sdk import AshrClient, AshrError
588
+
589
+ try:
590
+ with AshrClient(timeout=180) as ashr:
591
+ health = ashr.monitoring.model("m_router")
592
+ except AshrError as error:
593
+ print(error.status_code, error.request_id, str(error))
594
+ \`\`\`
595
+
596
+ Include request IDs when reporting failures. API errors and transport failures raise \`AshrError\`; transport failures may have no status or request ID. Reconcile timed-out writes before retrying.
597
+
598
+ ### Manage the client
599
+
600
+ Use a context manager for scripts, or reuse one \`AshrClient\` and call \`close()\` at shutdown. Module-level capture uses \`flush()\` before exit. Never place service keys in browser code; the platform uses Clerk through its same-origin API.
601
+ `;var xn=/^# (.+)$/m.exec(_e)?.[1]??"Manifold Python SDK",ot=[],Mt=0,Dt=!1;for(let r of _e.split(/(?<=\n)/)){r.startsWith("```")&&(Dt=!Dt);let e=Dt?null:/^## ([^\r\n]+)\r?\n?$/.exec(r);if(e){let t=ot.at(-1);t&&(t.end=Mt);let n=e[1],s=n.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"");ot.push({title:n,slug:s,start:Mt,end:_e.length})}Mt+=r.length}function Sn(r){let e=r||"quickstart";if(e==="all")return{title:xn,content:_e};if(e==="index")return{title:xn,content:ot.map(n=>`- ${n.title}: /sdk ${n.slug}`).join(`
602
+ `)+`
603
+
604
+ /sdk all reads the complete guide. /sdk defaults to Quickstart.`};let t=ot.find(n=>n.slug===e);if(!t)throw Error(`Unknown SDK topic '${e}'. Use /sdk index.`);return{title:t.title,content:_e.slice(t.start,t.end)}}import{open as En,readFile as Zs,rename as Xs,unlink as Qs}from"node:fs/promises";import{randomUUID as ei}from"node:crypto";async function $n(r,e,t,n,s,i,a){let o=r.client.requestStream;if(!o)throw Error("This client does not support streaming samples");let l={version:1,task:e,api_origin:r.client.apiOrigin,messages:[],turns:[]},d=!1;if(i){if(!r.client.apiOrigin)throw Error("Conversation requires a client with a verified API origin");try{l=JSON.parse(await Zs(i,"utf8")),d=!0}catch(g){if(g.code!=="ENOENT")throw Error("Conversation must be a readable terminal sample JSON file");await(await En(i,"wx",384)).close()}if(l.version!==1||l.task!==e||!Array.isArray(l.messages)||!Array.isArray(l.turns)||l.messages.some(g=>!g||!["user","assistant"].includes(g.role)||typeof g.content!="string"))throw Error("Conversation task or format does not match this sample");if(d&&l.api_origin!==r.client.apiOrigin)throw Error("Conversation API origin does not match; no history was sent")}let c=async()=>{if(!i)return;let g=`${i}.${ei()}.tmp`,f=await En(g,"wx",384);try{await f.writeFile(JSON.stringify(l,null,2)+`
605
+ `),await f.sync(),await f.close(),await Xs(g,i)}catch(b){throw await f.close().catch(()=>{}),await Qs(g).catch(()=>{}),b}};r.signal?.throwIfAborted(),l.messages.push({role:"user",content:t});let u=[...l.messages],h={role:"assistant",content:""};l.messages.push(h);let p={events:[],status:"streaming"};l.turns.push(p),await c();try{let g=await o.call(r.client,"/v1/messages",{method:"POST",signal:r.signal,body:JSON.stringify({model:e,max_tokens:n,temperature:s,stream:!0,messages:u})});if(!g.body)throw Error("Sample response has no stream");let f=g.body.getReader(),b=new TextDecoder,_="",m=!1,w=()=>{f.cancel(r.signal?.reason)};r.signal?.addEventListener("abort",w,{once:!0});let E=async k=>{let S=k.split(`
606
+ `).filter(M=>M.startsWith("data:")).map(M=>M.slice(5).trimStart()).join(`
607
+ `);if(!S)return;if(S==="[DONE]"){m=!0;return}let v=JSON.parse(S);if(p.events.push(v),v.type==="error"||v.error)throw Error("Model stream error: "+JSON.stringify(v.error??v));let A=v.type==="content_block_delta"&&v.delta?.type==="text_delta"?v.delta.text:v.type==="content_block_start"?v.content_block?.text:v.choices?.[0]?.delta?.content;typeof A=="string"&&A&&(h.content+=A,a("Sample text",A)),v.type==="message_stop"&&(m=!0),await c()};try{for(;;){r.signal?.throwIfAborted();let k=await f.read();_+=b.decode(k.value,{stream:!k.done}),_=_.replace(/\r\n/g,`
608
+ `);let S;for(;(S=_.indexOf(`
609
+
610
+ `))>=0;)await E(_.slice(0,S)),_=_.slice(S+2);if(k.done)break}if(r.signal?.throwIfAborted(),_.trim()&&await E(_),!m)throw Error("Model stream incomplete; partial conversation retained. A new sample is a new paid request.")}finally{r.signal?.removeEventListener("abort",w),await f.cancel().catch(()=>{})}p.status="complete",await c(),a("Model sample",{task:e,message:h,events:p.events,conversation:i??null})}catch(g){p.status="interrupted",h.content||l.messages.pop(),await c();let f=g instanceof Error?g.message:String(g);throw Error(f)}}import{parse as ci}from"shell-quote";import{readFile as di}from"node:fs/promises";import{createHash as ti}from"node:crypto";import{createReadStream as ni}from"node:fs";import{open as Tn,readFile as ri,rename as si,stat as An,unlink as Rn,writeFile as In}from"node:fs/promises";import{basename as ii,resolve as Ht}from"node:path";import{Readable as oi}from"node:stream";import{pipeline as ai}from"node:stream/promises";function Pn(r){let e=new URL(r);if(e.username||e.password||!(e.protocol==="https:"||e.protocol==="http:"&&["127.0.0.1","localhost","[::1]"].includes(e.hostname)))throw Error("Transfer URL must use HTTPS (or loopback for local fixtures)");return e.href}async function Cn(r,e,t,n,s,i){i?.throwIfAborted();let a=Ht(n),o=await Tn(a,"wx",384);try{let l=await r.request(e,{method:"POST",body:JSON.stringify(t),...i?{signal:i}:{}}),d=await fetch(Pn(l.url),{redirect:"error",signal:i});if(!d.ok||!d.body)throw Error(`Artifact download failed (${d.status})`);await ai(oi.fromWeb(d.body),o.createWriteStream(),{signal:i}),s("Artifact downloaded",{output:a,bytes:(await An(a)).size})}catch(l){throw await o.close().catch(()=>{}),await Rn(a).catch(()=>{}),l}finally{await o.close().catch(()=>{})}}async function Lt(r,e){let t=await An(r);if(!t.isFile()||t.size<1)throw Error("Upload requires a nonempty regular file");let n=ti("sha256");for await(let s of ni(r,{signal:e}))e?.throwIfAborted(),n.update(s);return{size:t.size,sha256:n.digest("hex")}}async function Ut(r,e){let t=r+".tmp";await In(t,JSON.stringify(e,null,2)+`
611
+ `,{flag:"wx",mode:384});try{await si(t,r)}catch(n){throw await Rn(t).catch(()=>{}),n}}async function On(r,e,t,n,s,i){i?.throwIfAborted();let a=Ht(r==="retry"?e:t),o;if(r==="retry"){if(o=JSON.parse(await ri(a,"utf8")),o.version!==1||!o.file||!Number.isInteger(o.size)||!/^[a-f0-9]{64}$/.test(o.sha256))throw Error("Invalid upload receipt");if(o.state==="complete"){s("Upload already complete",{receipt:a,key:o.key});return}if(o.state!=="uploading")throw Error(`Upload is ${o.state}; last response is uncertain. Inspect upload status before retrying; creation/completion will not be repeated.`);let f=await Lt(o.file,i);if(f.size!==o.size||f.sha256!==o.sha256)throw Error("Source file changed; refusing to resume")}else{let f=Ht(e),b=await Lt(f,i);o={version:1,file:f,...b,state:"creating"},await In(a,JSON.stringify(o,null,2)+`
612
+ `,{flag:"wx",mode:384}),s("Upload opening",{receipt:a,file:f,bytes:b.size}),i?.throwIfAborted();let _=await n.request("/v1/uploads/create",{method:"POST",body:JSON.stringify({filename:ii(f),size:b.size,content_type:"application/octet-stream"}),...i?{signal:i}:{}});o={...o,upload_id:_.upload_id,key:_.key,part_size:_.part_size,part_count:_.part_count,state:"uploading"};try{await Ut(a,o)}catch(m){throw s("Upload receipt write failed",{receipt:a,upload_id:o.upload_id,key:o.key,action:"Keep these identifiers; creation must not be repeated."}),m}}if(!o.upload_id||!o.key||!Number.isInteger(o.part_size)||o.part_size<1||!Number.isInteger(o.part_count)||o.part_count<1||o.part_count>1e4||Math.ceil(o.size/o.part_size)!==o.part_count)throw Error("Invalid multipart upload plan; receipt preserved");let l=`/v1/uploads/${encodeURIComponent(o.upload_id)}`,c=[...(await n.request(`${l}/status?${new URLSearchParams({key:o.key})}`,i?{signal:i}:void 0)).uploaded_parts],u=new Set(c.map(f=>f.part_number)),h=await Tn(o.file,"r");try{for(let f=1;f<=o.part_count;f++){if(i?.throwIfAborted(),u.has(f))continue;let b=await n.request(l+"/sign",{method:"POST",body:JSON.stringify({key:o.key,part_numbers:[f]}),...i?{signal:i}:{}});i?.throwIfAborted();let _=(f-1)*o.part_size,m=Math.min(o.part_size,o.size-_),w=Buffer.alloc(m);if((await h.read(w,0,m,_)).bytesRead!==m)throw Error("Source file changed while uploading");let k=await fetch(Pn(b.urls[String(f)]),{method:"PUT",body:w,redirect:"error",signal:i});if(!k.ok)throw Error(`Upload part ${f} failed (${k.status}); resume with /uploads retry '${a}' --confirm`);let S=k.headers.get("etag");if(!S)throw Error(`Upload part ${f} returned no ETag; resume from server status`);c.push({part_number:f,etag:S}),s("Upload progress",{receipt:a,uploaded_parts:c.length,total_parts:o.part_count})}}finally{await h.close()}let p=await Lt(o.file,i);if(p.size!==o.size||p.sha256!==o.sha256)throw Error("Source file changed while uploading; refusing completion; receipt preserved");i?.throwIfAborted(),o.state="completing",await Ut(a,o);let g=await n.request(l+"/complete",{method:"POST",body:JSON.stringify({key:o.key,parts:c.map(({part_number:f,etag:b})=>({part_number:f,etag:b})).sort((f,b)=>f.part_number-b.part_number)}),...i?{signal:i}:{}});o.state="complete",await Ut(a,o),s("Upload complete",{receipt:a,result:g})}var U=encodeURIComponent,at=["/dashboard help \u2014 commands use shell-quoted arguments; writes require repeating with --confirm","/pipelines list | show ID | status ID | task [TASK]","/runs list [--app-name NAME --limit N] | show ID [--logs true|false] | claims","/runs sync --confirm | explain ID [--regenerate true|false] --confirm (paid log explanation)","/sources list | check ALIAS --confirm","/sources objects ALIAS --limit N [--prefix PREFIX]","/sources connect ALIAS --config FILE --confirm | disconnect ALIAS --confirm","/datasets list [--task TASK]; /evals list [--limit N --after CURSOR --pipeline-id ID] | show ID | report ID","/evals results ID --model-run-id ID [--limit N --after CURSOR]","/benchmarks list [--task TASK --benchmark NAME --status STATUS --limit N --offset N] | show EXPERIMENT","/benchmarks events TRAINING_RUN [--limit N --after CURSOR --kind KIND,KIND --query TEXT --problems-only true|false]","/experiments list [--task TASK] | show ID | comparison ID | estimate ID","/models list; /monitoring summary MODEL | series MODEL | drift MODEL [--days N]","/monitoring compare BASELINE CANDIDATE [--days N]","/interactions list [--task TASK --model-id ID --feedback-status STATUS --limit N --offset N] | show ID","/uploads list | status ID --key KEY | cancel ID --key KEY --confirm","/uploads send FILE --receipt FILE --confirm | retry RECEIPT --confirm (server parts are resume authority)","/artifacts list [--kind KIND --limit N --has-weights true|false] | records [--kind KIND] | show ID","/artifacts download VOLUME PATH --output FILE [--format resume|adapter] --confirm","/artifacts download-record ID --output FILE --confirm (never overwrites)","/serving list | pools | show ID","/serving promote EXPERIMENT CANDIDATE [--alias production] --confirm",'/serving sample "PROMPT" [--task TASK --max-tokens N --temperature N --conversation FILE] --confirm',"/sdk [TOPIC|index|all] \u2014 read the shared SDK guide; index lists browser section names"];function li(r){let e=[],t={},n=!1;for(let s=0;s<r.length;s++){let i=r[s];if(i==="--confirm"){if(n)throw Error("Duplicate --confirm");n=!0}else if(i.startsWith("--")){let a=i.slice(2),o=r[++s];if(!a||o===void 0||o.startsWith("--")||Object.hasOwn(t,a))throw Error(`Expected one value for ${i}`);t[a]=o}else e.push(i)}return{values:e,options:t,confirm:n}}function Z(r,e,t,n=[]){if(r.values.length<e||r.values.length>t)throw Error(`Expected ${e===t?e:`${e}\u2013${t}`} positional arguments; see /dashboard help`);for(let s of Object.keys(r.options))if(!n.includes(s))throw Error(`Unknown option --${s}`)}function xe(r,e){let t=r.options[e];if(!t)throw Error(`Required --${e}`);return t}function ui(r,e){let t=r.options.task??e.task;if(!t)throw Error("Select a task or pass --task TASK");return t}function Y(r,e,t,n={}){let s=new URLSearchParams;for(let i of t)e.options[i]!==void 0&&s.set(i.replaceAll("-","_"),e.options[i]);for(let[i,a]of Object.entries(n))s.has(i)||s.set(i,a);return r+(s.size?`?${s}`:"")}function Ue(r){return typeof r=="string"?r.replace(/[\x00-\x1f\x7f-\x9f]/g,e=>`\\u${e.charCodeAt(0).toString(16).padStart(4,"0")}`):Array.isArray(r)?r.map(Ue):r&&typeof r=="object"?Object.fromEntries(Object.entries(r).map(([e,t])=>[String(Ue(e)),Ue(t)])):r}function Le(r,e,t,n){return e.confirm?!0:(r.show("Confirmation required",Ue({action:t,target:n,instruction:"Repeat the exact command with --confirm. No write was sent."})),!1)}function pi(r){let t=(Array.isArray(r.candidates)?r.candidates:[]).find(a=>a.recipe==="external_ptb"&&a.config?.ptb_role==="base"),n=Array.isArray(t?.runs)?t.runs:[],s=a=>{let o=Date.parse(String(a.started_at??a.created_at??""));return Number.isNaN(o)?-1/0:o};return[...n].sort((a,o)=>s(a)-s(o)).at(-1)?.status??null}async function Nn(r,e){let t=r.trim().split(/\s/)[0]?.slice(1);if(!r.trim().startsWith("/")||!["dashboard","pipelines","runs","sources","datasets","evals","benchmarks","experiments","models","monitoring","interactions","uploads","artifacts","serving","sdk"].includes(t??""))return!1;let n=[],s=o=>typeof o=="string"?n.reduce((l,d)=>l.split(d).join("[credential]"),o):Array.isArray(o)?o.map(s):o&&typeof o=="object"?Object.fromEntries(Object.entries(o).map(([l,d])=>[l,s(d)])):o,i=(o,l)=>e.show(o,Ue(s(l))),a={request:(o,l)=>e.client.request(o,e.signal?{...l,signal:e.signal}:l)};try{let o=ci(r,()=>{throw Error("Environment expansion is unavailable; use literal quoted values")});if(o.some(m=>typeof m!="string"))throw Error("Shell operators and comments are unavailable");let l=o;l.shift();let d=l.shift()??(t==="sdk"?"":"list"),c=li(l),u=c.values;if(t==="dashboard"||d==="help")return i("Hosted dashboard commands",at),!0;if(t==="sdk"){Z(c,0,0);let w=Sn(d);return e.markdown?e.markdown(w.title,w.content):e.show(w.title,w.content),!0}if(t==="benchmarks"){if(d==="show")return Z(c,1,1),i("Benchmark experiment",await a.request(`/v1/experiments/${U(u[0])}`)),!0;if(d==="events"){Z(c,1,1,["limit","after","kind","query","problems-only"]);let A=Number(c.options.limit??200);if(!Number.isInteger(A)||A<1||A>500)throw Error("Event limit must be 1\u2013500");if(c.options["problems-only"]&&!["true","false"].includes(c.options["problems-only"]))throw Error("problems-only must be true or false");let M=Y(`/v1/training-runs/${U(u[0])}`,c,["after","limit"],{limit:String(A)}),D=await a.request(M),$e=D.events??[],oe=c.options.kind?new Set(c.options.kind.split(",")):void 0,X=(c.options.query??"").trim().toLowerCase(),V=$e.filter(B=>(!oe||oe.has(String(B.event)))&&(c.options["problems-only"]!=="true"||["warning","warn","error","critical"].includes(String(B.level??"").toLowerCase())||["integrity_flag","stopped"].includes(String(B.event)))&&(!X||`${B.event} ${B.message??""} ${JSON.stringify(Object.fromEntries(Object.entries(B.metadata??{}).filter(([Q])=>Q!=="event_key")))}`.toLowerCase().includes(X))).reverse();return i("Benchmark event page",{...D,events:V,page_event_count:$e.length,filtered_event_count:V.length,filters_apply_to:"this event page; use next_after to read the next page"}),!0}if(d!=="list")throw Error("Use /benchmarks list, show or events");Z(c,0,0,["task","benchmark","status","limit","offset"]);let m=await a.request("/v1/experiments"),w=c.options.task??e.task,E=A=>String(A??"").trim().toLowerCase().replace(/[\s-]+/g,"_"),k=m.filter(A=>{let M=A.eval_config;return M?.kind==="ptb_benchmark_run"&&(!w||E(A.task)===E(w))&&(!c.options.benchmark||M.benchmark===c.options.benchmark)&&(!c.options.status||pi(A)===c.options.status)}),S=Number(c.options.limit??25),v=Number(c.options.offset??0);if(!Number.isInteger(S)||S<1||S>200||!Number.isInteger(v)||v<0)throw Error("limit must be 1\u2013200; offset must be nonnegative");return i("Benchmark runs",{items:k.slice(v,v+S),total:k.length,limit:S,offset:v,next_offset:v+S<k.length?v+S:null,pagination:"client-side; backend experiment list is not paginated"}),!0}if(t==="uploads"&&["send","retry"].includes(d))return Z(c,1,1,d==="send"?["receipt"]:[]),d==="send"&&xe(c,"receipt"),Le(e,c,`Upload ${d}`,{file:u[0],receipt:c.options.receipt})&&await On(d,u[0],c.options.receipt,a,i,e.signal),!0;if(t==="artifacts"&&["download","download-record"].includes(d)){Z(c,d==="download"?2:1,d==="download"?2:1,d==="download"?["output","format"]:["output"]),xe(c,"output");let m=c.options.format??"resume";if(!["resume","adapter"].includes(m))throw Error("format must be resume or adapter");if(Le(e,c,"Download artifact",{source:u,output:c.options.output,format:m})){let w=d==="download"?"/v1/modal-artifacts/download-url":`/v1/artifacts/${U(u[0])}/access-url`,E=d==="download"?{volume:u[0],path:u[1],format:m}:{expires_in:300};await Cn(a,w,E,c.options.output,i,e.signal)}return!0}if(t==="serving"&&d==="sample"){Z(c,1,1,["task","max-tokens","temperature","conversation"]);let m=ui(c,e),w=Number(c.options["max-tokens"]??256),E=Number(c.options.temperature??.7);if(!Number.isInteger(w)||w<1||w>8192||!Number.isFinite(E)||E<0||E>2)throw Error("max-tokens must be 1\u20138192; temperature must be 0\u20132");return Le(e,c,"Sample model (inference spend)",{task:m,prompt:u[0],max_tokens:w,temperature:E,conversation:c.options.conversation})&&await $n(e,m,u[0],w,E,c.options.conversation,i),!0}let h,p=(m,w=0,E=w,k=[])=>{Z(c,w,E,k),h={path:m}},g=(m,w,E,k,S=k,v=[])=>{Z(c,k,S,v),h={path:m,method:w,body:E}};if(t==="pipelines"){if(d==="list"&&p("/v1/pipelines"),(d==="show"||d==="status")&&p(`/v1/pipelines/${U(u[0]??"")}${d==="status"?"/status":""}`,1),d==="task"){Z(c,0,1);let m=u[0]??e.task;if(!m)throw Error("Select a task or provide TASK");h={path:`/v1/tasks/${U(m)}/pipeline`}}}else if(t==="runs")d==="list"&&p(Y("/v1/modal-runs",c,["app-name","limit"]),0,0,["app-name","limit"]),d==="show"&&p(Y(`/v1/modal-runs/${U(u[0]??"")}`,c,["logs"]),1,1,["logs"]),d==="claims"&&p("/v1/modal-run-claims"),d==="sync"&&g("/v1/modal-runs/sync","POST",void 0,0),d==="explain"&&g(Y(`/v1/modal-runs/${U(u[0]??"")}/explain`,c,["regenerate"]),"POST",void 0,1,1,["regenerate"]);else if(t==="sources"){if(d==="list"&&p("/v1/pipeline-data-sources"),d==="objects"&&(xe(c,"limit"),p(Y(`/v1/pipeline-data-sources/${U(u[0]??"")}/objects`,c,["limit","prefix"]),1,1,["limit","prefix"])),d==="check"&&g(`/v1/pipeline-data-sources/${U(u[0]??"")}/check`,"POST",void 0,1),d==="disconnect"&&g(`/v1/pipeline-data-sources/${U(u[0]??"")}`,"DELETE",void 0,1),d==="connect"){Z(c,1,1,["config"]);let m=xe(c,"config");if(!Le(e,c,"Connect data source",{alias:u[0],config:m}))return!0;let w;try{w=JSON.parse(await di(m,"utf8"))}catch{throw Error("Source config must be a readable JSON object")}if(!w||typeof w!="object"||Array.isArray(w))throw Error("Source config must be a JSON object");if(w.credentials&&typeof w.credentials=="object")for(let E of Object.values(w.credentials))typeof E=="string"&&E&&n.push(E);typeof w.credential_ref=="string"&&w.credential_ref&&n.push(w.credential_ref),h={path:`/v1/pipeline-data-sources/${U(u[0])}`,method:"PUT",body:w}}}else if(t==="datasets"||t==="models")d==="list"&&p(`/v1/${t}`,0,0,t==="datasets"?["task"]:[]);else if(t==="evals"){let m=`/v1/evaluation-runs/${U(u[0]??"")}`;d==="list"&&p(Y("/v1/evaluation-runs",c,["limit","after"]),0,0,["limit","after","pipeline-id"]),(d==="show"||d==="report")&&p(m+(d==="report"?"/report":""),1),d==="results"&&(xe(c,"model-run-id"),p(Y(`${m}/task-results`,c,["model-run-id","after","limit"]),1,1,["model-run-id","after","limit"]))}else t==="experiments"?(d==="list"&&p("/v1/experiments",0,0,["task"]),["show","comparison","estimate"].includes(d)&&p(`/v1/experiments/${U(u[0]??"")}${d==="show"?"":"/"+d}`,1)):t==="monitoring"?(["summary","series","drift"].includes(d)&&p(Y(`/v1/models/${U(u[0]??"")}/monitoring${d==="summary"?"":d==="series"?"/timeseries":"/drift"}`,c,["days","minimum-reviewed"]),1,1,d==="drift"?["days","minimum-reviewed"]:["days"]),d==="compare"&&p(Y("/v1/monitoring/compare",c,["days"],{baseline_model_id:u[0]??"",candidate_model_id:u[1]??""}),2,2,["days"])):t==="interactions"?(d==="list"&&p(Y("/v1/interactions",c,["limit","offset","feedback-status","model-id","task"],e.task?{task:e.task}:{}),0,0,["task","model-id","feedback-status","limit","offset"]),d==="show"&&p(`/v1/interactions/${U(u[0]??"")}`,1)):t==="uploads"?(d==="list"&&p("/v1/uploads"),(d==="status"||d==="cancel")&&(xe(c,"key"),Z(c,1,1,["key"]),h={path:Y(`/v1/uploads/${U(u[0])}/${d==="status"?"status":"abort"}`,c,["key"]),method:d==="cancel"?"POST":void 0})):t==="artifacts"?(d==="list"&&p(Y("/v1/modal-artifacts",c,["kind","limit"]),0,0,["kind","limit","has-weights"]),d==="records"&&p(Y("/v1/artifacts",c,["kind"]),0,0,["kind"]),d==="show"&&p(`/v1/artifacts/${U(u[0]??"")}`,1)):t==="serving"&&(d==="list"&&p("/v1/deployments"),d==="pools"&&p("/v1/serving/pools"),d==="show"&&p(`/v1/deployments/${U(u[0]??"")}`,1),d==="promote"&&g(`/v1/experiments/${U(u[0]??"")}/promote`,"POST",{candidate_id:u[1],alias:c.options.alias??"production"},2,2,["alias"]));if(!h)throw Error(`Unknown ${t} action '${d}'; see /dashboard help`);if(h.method&&!Le(e,c,`${t} ${d}`,{method:h.method,path:h.path,...t==="sources"&&d==="connect"?{config:c.options.config}:{body:h.body,...c.options.output?{output:c.options.output}:{}}}))return!0;let f=h.method?{method:h.method,...h.body===void 0?{}:{body:JSON.stringify(h.body)}}:void 0,b=await a.request(h.path,f);if(t==="evals"&&d==="list"&&c.options["pipeline-id"]){let m=b;Array.isArray(m.items)&&(b={...m,items:m.items.filter(w=>w.pipeline_id===c.options["pipeline-id"]),filters_apply_to:"this page; use next_cursor for the next page"})}if(t==="artifacts"&&d==="list"&&c.options["has-weights"]){if(!["true","false"].includes(c.options["has-weights"]))throw Error("has-weights must be true or false");let m=b;Array.isArray(m.artifacts)&&(b={...m,artifacts:m.artifacts.filter(w=>w.has_weights===(c.options["has-weights"]==="true"))})}let _=c.options.task??e.task;_&&d==="list"&&["datasets","experiments"].includes(t)&&Array.isArray(b)&&(b=b.filter(m=>m?.task===_)),i(`${t} ${d}`,b)}catch(o){i("Dashboard command failed",o instanceof Error?o.message:String(o))}return!0}var Mn="https://github.com/settings/personal-access-tokens/new?"+new URLSearchParams({name:"Ashr agent",description:"Lets the Ashr agent clone, push to, and open pull requests on this repository.",expires_in:"7",contents:"write",pull_requests:"write"}).toString();function Dn(r){if(!r.connected)return"Not connected. Run /github to let the agent work in your repo.";let e=r.repos.length===1?r.repos[0]:`${r.repos.length} repositories (${r.repos.join(", ")})`,t=r.expires_at?` \xB7 expires ${new Date(r.expires_at).toLocaleDateString("en-US",{month:"short",day:"numeric"})}`:"";return`Agent can work in ${e}${r.login?` as @${r.login}`:""}${t}. It can clone, push branches and open pull requests.`}var Ln=["/account who this terminal is signed in as","/org open organization settings in the dashboard","/github connect your GitHub repo (paste a token; the agent can clone, push and open PRs)","/github off remove the agent's GitHub access","/login sign in again, e.g. to switch organization","/logout sign this terminal out"];function Se(r){let e=process.platform==="darwin"?"open":process.platform==="win32"?"explorer.exe":"xdg-open",t=hi(e,[r],{stdio:"ignore",detached:!0});return t.unref(),new Promise((n,s)=>{t.once("error",s),t.once("exit",i=>i===0?n():s(Error("Browser could not open")))})}var gi=r=>{let e=t=>n=>r?`\x1B[${t}m${n}\x1B[0m`:n;return{bold:e("1"),dim:e("2"),accent:e("38;2;134;122;254"),green:e("32"),red:e("31")}};async function ct(r,e,t,n,s){if(!!0)throw Error(wt);let i=n.write??(h=>process.stdout.write(h)),a=n.interactive??!!process.stdout.isTTY,o=gi(a),l=new AbortController,d=()=>l.abort();process.once("SIGINT",d);let c=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],u;try{i(`
613
+ ${o.bold("ashr")} ${o.dim("\xB7 Manifold")}
614
+
615
+ `),s&&i(` ${s}
616
+
617
+ `);let h=await(n.startLogin??We)({dashboardUrl:e,device:mi().replace(/\.local$/,""),signal:l.signal,onUrl:f=>{if(i(` Sign in to continue. Opening your browser\u2026
618
+ ${o.dim("If it doesn't open, visit:")}
619
+ ${o.accent(f)}
620
+
621
+ `),(n.openBrowser??Se)(f).catch(()=>{}),a){let b=0,_=()=>`\r ${o.accent(c[b++%c.length])} Waiting for you to approve in the browser ${o.dim("\xB7 Ctrl+C to cancel")}`;i(_()),u=setInterval(()=>i(_()),80)}else i(` Waiting for you to approve in the browser\u2026
622
+ `)}});u&&(clearInterval(u),u=void 0,i("\r\x1B[2K")),await(n.validate??Re)(r,h.key,n.fetch);let p={apiUrl:r,key:h.key,profile:t.manifold?.profile??"default",email:h.email,organization:h.organization};(n.saveCredentials??re)({...t,manifold:p});let g=[h.email,h.organization].filter(Boolean).map(f=>x(f)).join(o.dim(" \xB7 "));return i(` ${o.green("\u2713")} Signed in${g?` as ${o.bold(g)}`:""}
623
+
624
+ `),p}catch(h){if(u&&(clearInterval(u),i("\r\x1B[2K")),l.signal.aborted){i(` Sign-in cancelled.
625
+ `);return}throw i(` ${o.red("\u2717")} ${x(h.message)}
626
+ `),h}finally{process.removeListener("SIGINT",d)}}async function Un(r,e,t={}){let n=t.env??process.env,s=r.apiUrl??n.MANIFOLD_API_URL??W,i=r.dashboardUrl??n.MANIFOLD_DASHBOARD_URL??de;return await ct(s,i,e,t)?0:130}async function Hn(r,e,t={}){let n=t.env??process.env,s=t.write??(b=>process.stdout.write(b)),i=Ge(r.apiUrl,n,e);if(!!0&&!i)throw Error(Ie);let a=r.apiUrl??n.MANIFOLD_API_URL??e.manifold?.apiUrl??W,o=r.dashboardUrl??n.MANIFOLD_DASHBOARD_URL??de,l=r.key??n.MANIFOLD_API_KEY,d=t.saveCredentials??re,c=!l&&e.manifold&&e.manifold.apiUrl===a?e.manifold:void 0,u=l;if(!u&&r.service)throw Error("No Manifold service key. Set MANIFOLD_API_KEY or run ashr login --key <key>.");if(!u&&c)try{await(t.validate??Re)(a,c.key,t.fetch),u=c.key}catch(b){if(!(b instanceof fe&&b.status===401))throw b;let{manifold:_,...m}=e;if(e=m,d(e),c=await ct(a,o,e,t,"This terminal's sign-in was revoked. Sign in again."),!c)return 130;e={...e,manifold:c},u=c.key}if(!u){if(c=await ct(a,o,e,t),!c)return 130;e={...e,manifold:c},u=c.key}let h={token:u},p=new ge({apiUrl:a,getToken:()=>Promise.resolve(h.token),fetch:t.fetch}),g=r.task,f=r.chat;for(;;){let b,_,m=await(t.run??_n)({client:p,terminal:t.terminal??new fi,task:g,chat:f,env:n,org:c?.organization,continueLatest:r.continue&&!f,onReady:k=>{b=k},moreHelpLines:[...at,...Ln],runDashboardCommand:async(k,S)=>{if(k==="/help")return S.show("Platform commands",at.join(`
627
+ `)),S.show("Account",Ln.join(`
628
+ `)),!1;if(k==="/login"||k==="/logout"){if(l)throw Error("This session uses MANIFOLD_API_KEY or --key; unset it to sign in through the browser.");return _=k==="/login"?"login":"logout",b?.quit(),!0}if(k==="/account")return S.show("Account",c?{signed_in_as:c.email??"unknown account",organization:c.organization??"\u2014",terminal_key:`${c.key.slice(0,12)}\u2026`,...a.includes("api.manifold.ashr.io")?{}:{api:a}}:`Using a service key (${h.token.slice(0,12)}\u2026) against ${a}.`),!0;if(k==="/github off")return await S.client.request("/v1/settings/github",{method:"DELETE"}),S.show("GitHub disconnected","The agent can only clone public repositories now. To also kill the token itself, revoke it at https://github.com/settings/personal-access-tokens"),!0;if(k==="/github"){let v=await S.client.request("/v1/settings/github");if(v.connected&&S.show("GitHub",Dn(v)+`
629
+ Paste a new token below to replace it, or press Esc. /github off removes access.`),(t.openBrowser??Se)(Mn).catch(()=>{}),S.show("Connect GitHub",`GitHub is open with the token pre-filled. Pick your repo, click Generate, then paste the token here.
630
+ If the browser didn't open: ${Mn}`),!S.promptSecret)throw Error("This terminal can't take a hidden token; connect GitHub from the dashboard instead.");return S.promptSecret("Paste your GitHub token (hidden) \xB7 Esc cancels",async A=>{let M=await S.client.request("/v1/settings/github",{method:"PUT",body:JSON.stringify({token:A})});S.show("GitHub",Dn(M))}),!0}if(k==="/account settings"||/^\/org(?:\s|$)/.test(k)){let v=new URL("/?view=organization",o).toString();return(t.openBrowser??Se)(v).catch(()=>{}),S.show("Opened in your browser",v),!0}return Nn(k,S)}});if(!_)return m;let{manifold:w,...E}=e;if(e=E,d(e),_==="logout")return s(`Signed out. The terminal key stays listed under API keys in Manifold until an admin revokes it.
631
+ `),0;if(c=await ct(a,o,e,t),!c)return 130;e={...e,manifold:c},h.token=c.key,g=void 0,f=void 0}}import{createInterface as wi}from"node:readline/promises";function He(r,e="No terminal to read the key from. Pass it with --key."){return new Promise((t,n)=>{let s=process.stdin;if(!s.isTTY){n(new Error(e));return}process.stdout.write(r);let i="";s.setRawMode(!0),s.resume();let a=o=>{for(let l of o.toString("utf8")){if(l==="\r"||l===`
632
+ `){s.setRawMode(!1),s.pause(),s.off("data",a),process.stdout.write(`
633
+ `),t(i.trim());return}l===""&&(s.setRawMode(!1),process.stdout.write(`
634
+ `),process.exit(130)),l==="\x7F"?i=i.slice(0,-1):i+=l}};s.on("data",a)})}function qn(r){let e=process.stdin,t=e.isTTY?e.isRaw:!1,n=s=>{let i=s.toString("utf8");i.includes("")?r.interrupt():/[\r\n]/.test(i)&&r.enter()};return e.isTTY&&e.setRawMode(!0),e.on("data",n),e.resume(),()=>{e.off("data",n),e.isTTY&&e.setRawMode(t),e.pause()}}async function jn(r){let e=wi({input:process.stdin,output:process.stdout});try{return await e.question(r)}finally{e.close()}}import{spawn as bi}from"node:child_process";var Fn=(r,e,t={})=>new Promise(n=>{let s=t.input===void 0?"ignore":"pipe",i=bi(r,e,{stdio:t.show?[s,"inherit","inherit"]:[s,"pipe","pipe"]});i.stdin?.end(t.input);let a="",o="",l=!1,d=c=>{l||(l=!0,n(c))};i.stdout?.on("data",c=>{a+=c.toString("utf8")}),i.stderr?.on("data",c=>{o+=c.toString("utf8")}),i.on("error",c=>d({code:127,stdout:a,stderr:c.message,missing:c.code==="ENOENT"})),i.on("close",c=>d({code:c??1,stdout:a,stderr:o}))});import{randomBytes as vi,randomUUID as _i}from"node:crypto";import{chmodSync as xi,existsSync as Si,mkdirSync as Ei,readFileSync as $i,writeFileSync as Yn}from"node:fs";import{hostname as Ti}from"node:os";import{join as lt}from"node:path";var Bn=`# The Ashr runner on one Docker host: the hosted Ashr agent's sandboxes, on this machine. \`ashr init\` embeds this file
635
+ # and runs it as compose project ashr-runner with the images at the CLI's version from Ashr's registry. The runner
636
+ # dials out to the Ashr API only; it starts sandbox containers through the host's Docker socket. Workspaces, snapshots,
637
+ # the /data volume and GPU runs stay on this host. See docs/customer-cloud.md, the runner split.
638
+ name: ashr-runner
639
+
640
+ services:
641
+ runner:
642
+ image: \${ASHR_IMAGE_REPO:-ashr}/ashr-runner:\${ASHR_VERSION:-dev}
643
+ environment:
644
+ ASHR_API_URL: \${ASHR_RUNNER_API_URL:-https://api.manifold.ashr.io}
645
+ ASHR_RUNNER_TOKEN: \${ASHR_RUNNER_TOKEN:?run ashr init to register this runner}
646
+ ASHR_DOCKER_SNAPSHOTS: /state/snapshots
647
+ ASHR_AGENT_SANDBOX_IMAGE: \${ASHR_IMAGE_REPO:-ashr}/ashr-sandbox:\${ASHR_VERSION:-dev}
648
+ ASHR_AGENT_GPU_IMAGE: \${ASHR_IMAGE_REPO:-ashr}/ashr-sandbox-gpu:\${ASHR_VERSION:-dev}
649
+ ASHR_DOCKER_GPUS: \${ASHR_DOCKER_GPUS:-}
650
+ ASHR_DOCKER_GPU_TYPE: \${ASHR_DOCKER_GPU_TYPE:-}
651
+ volumes:
652
+ - /var/run/docker.sock:/var/run/docker.sock
653
+ - ashr-runner-state:/state
654
+ restart: unless-stopped
655
+
656
+ # Image-only: pulled so the runner never pulls a sandbox image itself.
657
+ sandbox:
658
+ image: \${ASHR_IMAGE_REPO:-ashr}/ashr-sandbox:\${ASHR_VERSION:-dev}
659
+ profiles: [images]
660
+
661
+ sandbox-gpu:
662
+ image: \${ASHR_IMAGE_REPO:-ashr}/ashr-sandbox-gpu:\${ASHR_VERSION:-dev}
663
+ profiles: [images]
664
+
665
+ volumes:
666
+ ashr-runner-state:
667
+ `;var Kn={H200:4.54,H100:3.95,"A100-80GB":2.5,L40S:1.95,A10G:1.1,T4:.59};var N=class extends Error{},qe=Object.keys(Kn),ki=r=>r.trim().split(`
668
+ `).find(e=>e.trim())??"";async function zn(r){let e=await r("docker",["version","--format","{{.Server.Version}}"]);if(e.missing)throw new N("Docker is not installed. Install Docker Engine 24+ with the Compose plugin (https://docs.docker.com/engine/install/), then run ashr init again.");if(e.code!==0)throw new N(`Cannot reach the Docker daemon: ${e.stderr.trim()||`docker version exited ${e.code}`}
669
+ Start Docker (sudo systemctl start docker) and make sure this user can use it (sudo usermod -aG docker $USER, then log in again), then run ashr init again.`);let t=await r("docker",["compose","version","--short"]);if(t.code!==0)throw new N(`Docker Compose v2 is not available (docker compose version: ${ki(t.stderr)||`exit ${t.code}`}). Install the docker-compose-plugin package; the old docker-compose v1 is not supported.`);let n=t.stdout.trim().replace(/^v/,"");if(!(Number(n.split(".")[0])>=2))throw new N(`Docker Compose ${n} is too old; install Compose v2 (the docker-compose-plugin package).`);let i=await r("nvidia-smi",["-L"]);if(i.missing)return{gpus:[],nvidiaRuntime:!1};if(i.code!==0)throw new N(`nvidia-smi is installed but failed: ${i.stderr.trim()||i.stdout.trim()||`exit ${i.code}`}
670
+ Fix the NVIDIA driver (nvidia-smi must list the GPUs), or remove nvidia-smi from this host if it has no GPUs.`);let a=i.stdout.split(`
671
+ `).map(l=>/^GPU \d+: (.+?)(?: \(UUID: [^)]*\))?\s*$/.exec(l.trim())?.[1]).filter(l=>!!l);if(!a.length)return{gpus:a,nvidiaRuntime:!1};let o=await r("docker",["info","--format","{{json .Runtimes}}"]);if(o.code!==0)throw new N(`docker info failed: ${o.stderr.trim()||`exit ${o.code}`}`);return{gpus:a,nvidiaRuntime:/"nvidia"/.test(o.stdout)}}var qt=`This host has NVIDIA GPUs, but Docker has no NVIDIA runtime, so containers cannot use them. Install the NVIDIA Container Toolkit (https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html), then run
672
+ sudo nvidia-ctk runtime configure --runtime=docker && sudo systemctl restart docker
673
+ and run ashr init again.`;function Gn(r){let e=new Set(r.map(t=>{let n=new Set(t.toUpperCase().split(/[^A-Z0-9]+/).filter(Boolean));return qe.find(s=>s.toUpperCase().split("-").every(i=>n.has(i)))}));if(e.size===1)return[...e][0]}function Wn(r){let e=new Map;for(let t of r.split(`
674
+ `)){let n=/^\s*([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(t);if(!n)continue;let s=n[2].trim();s.length>=2&&(s[0]==='"'||s[0]==="'")&&s.endsWith(s[0])&&(s=s.slice(1,-1)),e.set(n[1],s)}return e}function Jn(r,e){let t=r.length?r.replace(/\n$/,"").split(`
675
+ `):[];for(let[n,s]of Object.entries(e)){if(/[\r\n]/.test(s))throw new Error(`${n} cannot contain a line break.`);let i=new RegExp(`^\\s*${n}=`),a=t.length-1;for(;a>=0&&!i.test(t[a]);)a--;a>=0?t[a]=`${n}=${s}`:t.push(`${n}=${s}`)}return`${t.join(`
676
+ `)}
677
+ `}var Vn="784315209140.dkr.ecr.us-west-2.amazonaws.com/ashr-selfhost",dt="ashr-runner",Ai=r=>lt(ee(r).home,"runner");function jt(r,...e){return["compose","-p",dt,"--project-directory",r,"-f",lt(r,"docker-compose.yml"),...e]}async function Zn(r){let{run:e,write:t,env:n}=r,s=r.version??ze.version,i=r.sleep??(P=>new Promise(y=>{setTimeout(y,P)})),a=(n.ASHR_API_URL||W).replace(/\/$/,""),o=Ai(n),l=lt(o,".env"),d=(...P)=>e("docker",jt(o,...P));t(`Checking this host...
678
+ `);let c=await zn(e);t(` Docker and Compose v2: ok
679
+ GPUs: ${c.gpus.length?c.gpus.join(", "):"none"}
680
+ `);let u=!Si(l),h=u?"":$i(l,"utf8"),p=Wn(h),g=n.ASHR_API_KEY||r.apiKey||await r.workspaceKey?.()||"";if(!g){if(r.yes)throw new N("No Ashr API key. Set ASHR_API_KEY (an organization key from the dashboard's Settings > API keys), or run ashr init without --yes to enter one.");if(g=(await r.prompt.secret(`Ashr API key (${de}, Settings > API keys): `)).trim(),!g)throw new N("No Ashr API key entered.")}let f=new ge({apiUrl:a,getToken:async()=>g,fetch:r.fetch}),b=async(P,y={},T)=>{try{return await f.request(P,y)}catch(I){throw I instanceof z&&I.status===T?I:I instanceof z&&I.status===401?new N(`Ashr refused the API key (${a}). Use an organization key from the dashboard's Settings > API keys.`):I instanceof z?new N(`${a}${P.split("?")[0]}: ${I.detail}`):new N(`Could not reach ${a}: ${I.message}`)}},_={},m=p.get("ASHR_RUNNER_ID")??"";if(!p.get("ASHR_RUNNER_TOKEN")){let P=await b("/v1/runners",{method:"POST",body:JSON.stringify({name:Ti()})});_.ASHR_RUNNER_ID=m=P.id,_.ASHR_RUNNER_TOKEN=P.token,t(`Registered this host as runner ${P.id}; it replaces any earlier runner of this workspace.
681
+ `)}(n.ASHR_API_URL||n.ASHR_RUNNER_API_URL)&&(_.ASHR_RUNNER_API_URL=n.ASHR_RUNNER_API_URL||a);let E=(await b("/v1/model-logins",{},404).catch(P=>{if(P instanceof z)return{};throw P}))["openai-codex"];if(E?.required&&!E.connected){if(r.yes)throw new N("This workspace needs your ChatGPT plan connected. Run ashr init without --yes to sign in.");let P=await b("/v1/model-logins/openai-codex/browser",{method:"POST"});t(`
682
+ Connect your ChatGPT plan (this workspace's agent runs on it). Open this in a browser and sign in:
683
+ ${P.url}
684
+ The browser then ends on a page at http://localhost:1455/auth/callback?code=... that does not load.
685
+ `),r.openBrowser?.(P.url).catch(()=>{});let y=(await r.prompt.line("Paste that whole address here: ")).trim(),T=await b("/v1/model-logins/openai-codex/browser/finish",{method:"POST",body:JSON.stringify({flow:P.flow,pasted:y})});t(` ChatGPT: connected${T.account?` (${T.account})`:""}
686
+ `)}if(!p.has("ASHR_DOCKER_GPUS")&&c.gpus.length)if(r.yes||!/^n/i.test((await r.prompt.line(`Use this host's ${c.gpus.length} GPU(s) for GPU runs? [Y/n] `)).trim())){let y=n.ASHR_DOCKER_GPU_TYPE||Gn(c.gpus);for(;!y||!qe.includes(y);){if(r.yes)throw new N(`Cannot tell which GPU type ${[...new Set(c.gpus)].join(", ")} is. Set ASHR_DOCKER_GPU_TYPE to one of ${qe.join(", ")} (the closest match) and run ashr init --yes again, or run it without --yes to choose.`);y=(await r.prompt.line(`Which GPU type is ${c.gpus[0]}? One of ${qe.join(", ")}: `)).trim()}_.ASHR_DOCKER_GPUS="all",_.ASHR_DOCKER_GPU_TYPE=y}else _.ASHR_DOCKER_GPUS="",_.ASHR_DOCKER_GPU_TYPE="";let k=!!(_.ASHR_DOCKER_GPUS??p.get("ASHR_DOCKER_GPUS"));if(k&&!c.nvidiaRuntime)throw new N(qt);let S=n.ASHR_IMAGE_REPO||p.get("ASHR_IMAGE_REPO")||Vn;_.ASHR_IMAGE_REPO=S,_.ASHR_VERSION=s,Ei(o,{recursive:!0,mode:448}),Yn(lt(o,"docker-compose.yml"),Bn),Yn(l,Jn(u?`# The Ashr runner, written by ashr init. Re-running it keeps this file and updates only ASHR_VERSION, and
687
+ # ASHR_IMAGE_REPO when it is set in the environment. ASHR_RUNNER_TOKEN lets this host take the agent's sandbox work: keep this file mode 0600.
688
+ `:h,_),{mode:384}),xi(l,384);let A=_.ASHR_RUNNER_TOKEN??p.get("ASHR_RUNNER_TOKEN");t(u?`Wrote ${l} (mode 0600).
689
+ `:`Kept ${l}; images now ${S}/ashr-*:${s}.
690
+ `);let M=["runner","sandbox",...k?["sandbox-gpu"]:[]],D=M.map(P=>`${S}/ashr-${P}:${s}`);if(S===Vn&&(await e("docker",["image","inspect",...D])).code!==0&&await Ri(r,a,A),t(`Pulling ${S}/ashr-{${M.join(",")}}:${s} ...
691
+ `),(await e("docker",jt(o,"--profile","images","pull","--policy","missing",...M),{show:!0})).code!==0)throw new N(`Could not pull the Ashr images (docker's error is above). Check that this host can reach ${S.split("/")[0]} and that ${S}/ashr-runner:${s} exists.`);if(t(`Starting the runner ...
692
+ `),(await e("docker",jt(o,"up","-d","--pull","missing","--remove-orphans","runner"),{show:!0})).code!==0)throw new N(`The runner did not start (docker's error is above). See why with: docker compose -p ${dt} logs runner`);let X=async()=>{let P=await d("logs","--no-color","--tail","40","runner");return`
693
+ Runner log (docker compose -p ${dt} logs runner):
694
+ ${P.stdout}${P.stderr}`};t(`Waiting for the runner to reach Ashr ...
695
+ `);for(let P=0;;P+=2){if(!(await d("ps","--status","running","-q","runner")).stdout.trim())throw new N(`The runner container stopped.${await X()}`);if((await b("/v1/runners")).find(I=>I.id===m)?.online)break;if(P>=60)throw new N(`The runner has not reached ${a} after a minute.${await X()}`);await i(2e3)}t(` runner: online
696
+ `),t(`Checking a sandbox command on this host ...
697
+ `);let V=await b("/v1/agent/chats",{method:"POST",body:JSON.stringify({name:"ashr init check"})}),B=`ashr-sandbox-ok-${vi(4).toString("hex")}`;await b(`/v1/agent/chats/${V.id}/commands`,{method:"POST",body:JSON.stringify({request_id:_i(),kind:"shell",text:`echo ${B} $(hostname)`,options:{}})});let Q=0,je=!1,Fe=Date.now()+5*6e4;e:for(;;){let P=await b(`/v1/agent/chats/${V.id}/events?after=${Q}`);Q=P.cursor;for(let y of P.events){if(y.kind==="error")throw new N(`${String(y.data.text??"The agent reported an error.")}${await X()}`);if(y.kind==="status"&&y.data.status==="failed")throw new N(`The check chat failed.${await X()}`);if(y.kind==="tool_end"&&String(y.data.text??"").includes(B)&&(je=!0),je&&y.kind==="status"&&y.data.status==="idle")break e}if(Date.now()>Fe)throw new N(`The sandbox command did not finish in 5 minutes.${await X()}`);await i(1e3)}if(t(` sandbox: ok
698
+ `),k){t(`Checking the GPUs ...
699
+ `);let P=`${S}/ashr-sandbox-gpu:${s}`;if((await e("docker",["run","--rm","--gpus=1",P,"nvidia-smi"],{show:!0})).code!==0)throw new N(`A GPU container could not start (docker's error is above): docker run --rm --gpus=1 ${P} nvidia-smi
700
+ ${qt}`);t(` GPUs: ok
701
+ `)}return t(["",`This host now runs the Ashr agent's sandboxes (runner ${m}, ${s}).`,"The agent works in containers here: your repo, data, checkpoints and GPU runs stay on this host.","",`Chat with it: ${de}, or run ashr`,`Upgrade: run ashr init again with a newer ashr. Settings: ${l}`,`Stop: docker compose -p ${dt} down (the agent then cannot reach its sandboxes until it is started again).`,""].join(`
702
+ `)),0}async function Ri(r,e,t){let n;try{n=await(r.fetch??fetch)(`${e}/v1/runner/registry-credentials`,{method:"POST",headers:{authorization:`Bearer ${t}`}})}catch(a){throw new N(`Could not reach ${e} for the image registry login: ${a.message}`)}let s=await n.json().catch(()=>({}));if(!n.ok||!s.registry||!s.username||!s.password)throw new N(`Could not get the image registry login: ${s.detail??`HTTP ${n.status}`}`);let i=await r.run("docker",["login",s.registry,"--username",s.username,"--password-stdin"],{input:s.password});if(i.code!==0)throw new N(`docker login to ${s.registry} failed: ${i.stderr.trim()}`);r.write(`Logged docker in to the Ashr image registry (${s.registry}).
703
+ `)}function Xn(r){let e=Ae().manifold;return Zn({env:process.env,yes:!!r.yes,run:Fn,prompt:{line:t=>process.stdin.isTTY?jn(t):Promise.reject(new Error("No terminal to answer on. Run ashr init --yes to take the defaults.")),secret:t=>He(t,"No terminal to read the key from. Set ASHR_API_KEY in the environment.")},write:t=>process.stdout.write(t),apiKey:e&&e.apiUrl.replace(/\/$/,"")===(process.env.ASHR_API_URL||W).replace(/\/$/,"")?e.key:void 0,openBrowser:process.env.SSH_CONNECTION||process.env.SSH_TTY?void 0:Se,workspaceKey:async()=>{let t=Ae(),n=r.yes||!process.stdin.isTTY?t.account:await tn(t,{env:process.env,write:i=>process.stdout.write(i),openBrowser:Se,tty:!!process.stdout.isTTY,readCode:i=>He(i,"No terminal to read the code from."),onKeys:qn});if(!n||n==="cancelled")return;let s=await fetch(`${(process.env.ASHR_API_URL||W).replace(/\/$/,"")}/v1/cli/account/workspace-key`,{method:"POST",headers:{authorization:`Bearer ${n.key}`,"content-type":"application/json"},body:JSON.stringify({device:Ii().replace(/\.local$/,"")})}).catch(()=>{});return s?.ok?(await s.json()).key:void 0}})}import{readFileSync as oo}from"node:fs";import{randomBytes as Gi,randomUUID as Wi}from"node:crypto";import{mkdirSync as Ji,readFileSync as Yi,writeFileSync as Bt}from"node:fs";import{arch as Vi,platform as Zi}from"node:os";import{basename as Xi,dirname as Qi}from"node:path";import{homedir as Ci,hostname as Oi,userInfo as Ni}from"node:os";var Ee={version:1,description:"What ashr redacts from telemetry before it leaves the machine (cli/src/telemetry/redact.ts) and again before the API forwards it (backend/app/telemetry_redact.py, used by backend/app/routes/telemetry.py). Rules apply in order. Patterns use the syntax JavaScript and Python share: no named groups, no lookbehind, flags from `flags` only. `group` replaces that capture group alone; `validate` keeps a match only when the named check passes. Each placeholder is held as an inert marker (a control character, an index, a control character) until every rule and the entropy pass have run, then restored, so no later rule matches inside an earlier placeholder. When a whole OTLP export is redacted, the values under `keepKeys` (ids, timestamps, integers) are structure, not content, and are left as they are. Every repeat in a pattern that can backtrack is bounded, or stops at the next opening marker (a private key body never crosses another `-----BEGIN `), so redaction stays linear in the text's length and no text is cut short.",keepKeys:["traceId","spanId","parentSpanId","startTimeUnixNano","endTimeUnixNano","timeUnixNano","intValue"],entropy:{minLength:24,minBits:4,label:"SECRET:high_entropy"},rules:[{id:"private_key",label:"SECRET:private_key",pattern:"-----BEGIN [A-Z ]*PRIVATE KEY-----(?:(?!-----BEGIN )[\\s\\S])*?-----END [A-Z ]*PRIVATE KEY-----"},{id:"url_credentials",label:"CREDENTIALS",pattern:"(://)([^\\s:/@]+:[^\\s@/]+)(@)",group:2},{id:"anthropic",label:"SECRET:anthropic",pattern:"sk-ant-[A-Za-z0-9_-]{20,}"},{id:"openai",label:"SECRET:openai",pattern:"\\bsk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{20,}"},{id:"aws_access_key",label:"SECRET:aws",pattern:"\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b"},{id:"aws_secret",label:"SECRET:aws",pattern:`(aws_secret_access_key["']?\\s*[=:]\\s*["']?)([A-Za-z0-9/+=]{40})`,flags:"i",group:2},{id:"github",label:"SECRET:github",pattern:"\\b(?:gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{22,})"},{id:"huggingface",label:"SECRET:huggingface",pattern:"\\bhf_[A-Za-z0-9]{30,}"},{id:"slack",label:"SECRET:slack",pattern:"\\bxox[abprs]-[A-Za-z0-9-]{10,}"},{id:"stripe",label:"SECRET:stripe",pattern:"\\b[sr]k_(?:live|test)_[A-Za-z0-9]{20,}"},{id:"google_api",label:"SECRET:google",pattern:"\\bAIza[0-9A-Za-z_-]{35}"},{id:"modal",label:"SECRET:modal",pattern:"\\b(?:ak|as)-[A-Za-z0-9]{20,}"},{id:"manifold",label:"SECRET:manifold",pattern:"\\bask_[A-Za-z0-9_-]{20,}"},{id:"jwt",label:"SECRET:jwt",pattern:"\\beyJ[A-Za-z0-9_-]{10,2048}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}"},{id:"bearer",label:"SECRET:bearer",pattern:"(Bearer\\s+)([A-Za-z0-9._~+/-]{16,}=*)",group:2},{id:"assignment",label:"SECRET:assigned",pattern:`((?:api[_-]?key|secret|token|passw(?:or)?d|auth|credential)s?["']?\\s*[:=]\\s*["']?)([^\\s"',;}]{8,})`,flags:"i",group:2},{id:"email",label:"EMAIL",pattern:"\\b[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{1,253}\\.[A-Za-z]{2,63}\\b"},{id:"card",label:"CARD",pattern:"\\b(?:\\d[ -]?){12,18}\\d\\b",validate:"luhn"},{id:"ssn",label:"SSN",pattern:"\\b\\d{3}-\\d{2}-\\d{4}\\b"},{id:"phone",label:"PHONE",pattern:"(?:\\+?1[-. ]?)?\\(?\\b\\d{3}\\)?[-. ]\\d{3}[-. ]\\d{4}\\b|\\+[1-9]\\d{7,14}\\b"},{id:"ipv4",label:"IP",pattern:"\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b",validate:"ipv4"},{id:"home_path",label:"~",pattern:`(?:/Users/|/home/|[A-Za-z]:\\\\Users\\\\)[^/\\\\\\s"']+`,bare:!0}]};var Mi=Ee.rules.map(r=>({...r,re:new RegExp(r.pattern,`g${r.flags??""}`)})),Di=new RegExp(`[A-Za-z0-9+/_=-]{${Ee.entropy.minLength},}`,"g"),Li=new Set(Ee.keepKeys),Qn=r=>r.bare?r.label:`<${r.label}>`;function Ui(r){let e=r.replace(/\D/g,"");if(e.length<13||e.length>19)return!1;let t=0;for(let n=0;n<e.length;n++){let s=Number(e[e.length-1-n]);n%2===1&&(s=s*2>9?s*2-9:s*2),t+=s}return t%10===0}var Hi=r=>r.split(".").every(e=>Number(e)<=255)&&!r.startsWith("0.")&&r!=="127.0.0.1",qi={luhn:Ui,ipv4:Hi};function ji(r){let e=new Map;for(let n of r)e.set(n,(e.get(n)??0)+1);let t=0;for(let n of e.values())t-=n/r.length*Math.log2(n/r.length);return t}var er=r=>r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");function Fi(r=process.env){let e=/KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH/i;return[...new Set(Object.entries(r).filter(([t,n])=>e.test(t)&&n&&n.length>=8).map(([,t])=>t))].sort((t,n)=>n.length-t.length)}var Bi=new Set(["root","admin","user","ubuntu","runner","node","ec2-user","debian","vagrant"]);function Ki(){let r=[];try{r.push(Ni().username)}catch{}return r.push(Oi()),r.filter(e=>e.length>=5&&!Bi.has(e.toLowerCase()))}function zi(r,e=[]){let t=[],n=i=>`\0${t.push(i)-1}`,s=r;for(let i of e)i.length>=4&&(s=s.split(i).join(n("<SECRET:known>")));for(let i of Mi)s=s.replace(i.re,(...a)=>{let o=a[0];return i.validate&&!qi[i.validate](o)?o:i.group===void 0?n(Qn(i)):a.slice(1,-2).map((d,c)=>c+1===i.group?n(Qn(i)):d??"").join("")});return s=s.replace(Di,i=>/[a-z]/.test(i)&&/[A-Z]/.test(i)&&/\d/.test(i)&&ji(i)>=Ee.entropy.minBits?n(`<${Ee.entropy.label}>`):i),s.replace(/\u0000(\d+)\u0001/g,(i,a)=>t[Number(a)]??i)}function tr(r=process.env){let e=Fi(r),t=new RegExp(er(Ci()),"g"),n=Ki().map(s=>new RegExp(`\\b${er(s)}\\b`,"g"));return s=>{let i=zi(s.replace(t,"~"),e);for(let a of n)i=i.replace(a,"<USER>");return i}}function ut(r,e){return typeof r=="string"?e(r):Array.isArray(r)?r.map(t=>ut(t,e)):r&&typeof r=="object"?Object.fromEntries(Object.entries(r).map(([t,n])=>[t,Li.has(t)?n:ut(n,e)])):r}var eo="/v1/telemetry/otel/v1/traces",nr=r=>r!==void 0&&["1","true","on","yes"].includes(r.toLowerCase()),to=r=>r!==void 0&&["0","false","off","no"].includes(r.toLowerCase());function zt(r){try{return JSON.parse(Yi(r,"utf8"))}catch{return}}function Gt(r,e){Ji(Qi(r),{recursive:!0}),Bt(r,`${JSON.stringify(e,null,2)}
704
+ `)}var Kt=r=>`${ee(r).home}/telemetry.json`,mt=r=>`${ee(r).home}/telemetry-last.jsonl`;function rr(r){let e=zt(Kt(r));if(e?.installId)return e;let t={installId:Wi()};try{Gt(Kt(r),t)}catch{}return t}function no(r){let e={};for(let t of(r??"").split(",")){let n=t.indexOf("=");if(n<=0)continue;let s=t.slice(n+1).trim(),i=s;try{i=decodeURIComponent(s)}catch{}e[t.slice(0,n).trim()]=i}return e}function sr(r=process.env){let e=zt(ee(r).settings)?.telemetry??{},t=nr(r.DO_NOT_TRACK)?"DO_NOT_TRACK":to(r.ASHR_TELEMETRY)?"ASHR_TELEMETRY":e.enabled===!1?"~/.ashr/settings.json":void 0,n=(s,i)=>r[s]!==void 0?nr(r[s]):i===!0;return{enabled:!t,logPrompts:n("ASHR_TELEMETRY_LOG_PROMPTS",e.logPrompts),logToolDetails:n("ASHR_TELEMETRY_LOG_TOOL_DETAILS",e.logToolDetails),logToolOutput:n("ASHR_TELEMETRY_LOG_TOOL_OUTPUT",e.logToolOutput),endpoint:r.ASHR_TELEMETRY_ENDPOINT??e.endpoint??`${W}${eo}`,headers:{...e.headers??{},...no(r.ASHR_TELEMETRY_HEADERS)},installId:rr(r).installId,...t?{disabledBy:t}:{}}}function ir(r,e=process.env){let t=ee(e).settings,n=zt(t)??{};Gt(t,{...n,telemetry:{...n.telemetry,enabled:r}})}var Wt="ashr sends usage data (commands, tools used, durations, token counts, errors; never your code, prompts or tool output) to help improve it. See it with `ashr telemetry show`; turn it off with `ashr telemetry off` or ASHR_TELEMETRY=off.";function or(r,e=process.env){if(!r.enabled)return;let t=rr(e);if(!t.noticeShown){try{Gt(Kt(e),{...t,noticeShown:!0})}catch{return}return Wt}}var Ft=r=>`${BigInt(Math.round(r))*1000000n}`,pt=r=>Gi(r).toString("hex");function ht(r){return Object.entries(r).flatMap(([e,t])=>t===void 0?[]:typeof t=="boolean"?[{key:e,value:{boolValue:t}}]:typeof t=="number"?[{key:e,value:Number.isInteger(t)?{intValue:String(t)}:{doubleValue:t}}]:[{key:e,value:{stringValue:t}}])}function ro(r){if(typeof r!="string")return;let e=r.trim().split(/\s+/).filter(t=>!/^[A-Z_][A-Z0-9_]*=/.test(t)&&!["sudo","env","time","nohup","exec"].includes(t));return e[0]?Xi(e[0]):void 0}var so={command:()=>{},attach:()=>async()=>{},flush:async()=>{}};async function io(r,e,t){await fetch(r,{method:"POST",headers:{"content-type":"application/json",...t},body:e,signal:AbortSignal.timeout(5e3)})}function ar(r){let{config:e}=r;if(!e.enabled)return so;let t=r.env??process.env,n=r.now??Date.now,s=r.send??io,i=tr(t),a=[],o=Promise.resolve(),l=ht({"service.name":"ashr","service.version":me,"os.type":Zi(),"host.arch":Vi(),"user.id":e.installId,"ashr.install_id":e.installId,"langfuse.environment":t.ASHR_TELEMETRY_ENVIRONMENT??"production"}),d=()=>{if(!a.length)return o;let p=a.splice(0),g={resourceSpans:[{resource:{attributes:l},scopeSpans:[{scope:{name:"ashr",version:me},spans:ut(p,i)}]}]},f=JSON.stringify(g);try{Bt(mt(t),`${f}
705
+ `,{flag:"a"})}catch{}return o=o.then(()=>s(e.endpoint,f,e.headers)).catch(()=>{}),o};setInterval(()=>{d()},3e4).unref();let u=(p,g,f,b)=>({traceId:g,spanId:pt(8),...b?{parentSpanId:b}:{},name:p,kind:1,startTimeUnixNano:Ft(n()),attributes:ht(f),events:[]}),h=(p,g={},f)=>{p.endTimeUnixNano=Ft(n()),p.attributes.push(...ht(g)),p.status=f?{code:2,message:f}:{code:1},a.push(p)};return{command(p,g={}){let f=u(`ashr ${p}`,pt(16),{"langfuse.trace.name":`ashr ${p}`,"ashr.command":p,...g});h(f)},attach(p,g){try{Bt(mt(t),"")}catch{}let f={"session.id":p.id,"langfuse.session.id":p.id,"user.id":e.installId,"langfuse.user.id":e.installId},b=u("ashr session",pt(16),{...f,"langfuse.trace.name":"session start","gen_ai.request.model":p.modelLabel,"ashr.print":g.print,"ashr.mode":g.mode()});h(b);let _,m=()=>{let v=pt(16);return _={span:u("invoke_agent",v,{...f,"langfuse.trace.name":"turn","gen_ai.operation.name":"invoke_agent","gen_ai.agent.name":"ashr","gen_ai.request.model":p.modelLabel,"ashr.mode":g.mode()}),tools:new Map,reply:"",before:p.stats(),toolCalls:0,errors:0},_},w=(v,A)=>(_??m()).span.events.push({timeUnixNano:Ft(n()),name:v,attributes:ht(A)}),E=async()=>{let v=_;if(!v)return;_=void 0;for(let D of v.tools.values())h(D,{"ashr.tool.unfinished":!0});let A=p.stats(),M=e.logPrompts?(await p.userMessages().catch(()=>[]))[0]?.text:void 0;h(v.span,{"gen_ai.usage.input_tokens":A.tokens.input-v.before.tokens.input,"gen_ai.usage.output_tokens":A.tokens.output-v.before.tokens.output,"ashr.usage.cache_read_tokens":A.tokens.cacheRead-v.before.tokens.cacheRead,"ashr.usage.cache_write_tokens":A.tokens.cacheWrite-v.before.tokens.cacheWrite,"ashr.cost_usd":Number((A.cost-v.before.cost).toFixed(6)),"ashr.tool_calls":v.toolCalls,"ashr.errors":v.errors,"ashr.reply_chars":v.reply.length,"langfuse.trace.input":M,"langfuse.trace.output":e.logPrompts?v.reply:void 0}),d()},k=v=>{switch(v.type){case"busy":_||m();break;case"text":(_??m()).reply+=v.delta;break;case"text_reset":(_??m()).reply=v.text;break;case"tool_start":{let A=_??m();A.toolCalls++,A.tools.set(v.id,u(`execute_tool ${v.name}`,A.span.traceId,{"gen_ai.operation.name":"execute_tool","gen_ai.tool.name":v.name,"gen_ai.tool.call.id":v.id,"ashr.tool.program":v.name==="bash"?ro(v.args.command):void 0,"langfuse.observation.input":e.logToolDetails?JSON.stringify(v.args):void 0},A.span.spanId));break}case"tool_end":{let A=_?.tools.get(v.id);if(!A||!_)break;_.tools.delete(v.id),v.isError&&_.errors++,h(A,{"ashr.tool.is_error":v.isError,"ashr.tool.output_chars":v.output.length,"langfuse.observation.output":e.logToolOutput?v.output:void 0},v.isError?"tool failed":void 0);break}case"subagent":w("subagent",{"ashr.subagent":v.name,"gen_ai.tool.name":v.text.split(" ")[0],"ashr.detail":e.logToolDetails?v.text:void 0,"ashr.is_error":v.isError});break;case"report":w("subagent_report",{"ashr.report_chars":v.text.length,"ashr.report":e.logToolOutput?v.text:void 0});break;case"retry":w("retry",{"ashr.attempt":v.attempt,"ashr.error":v.error});break;case"compaction":w(`compaction_${v.phase}`,{"ashr.error":v.error});break;case"error":(_??m()).errors++,w("error",{"ashr.error":v.message});break;case"settled":E();break;default:break}},S=p.subscribe(k);return async()=>{S(),await E()}},flush:async()=>{await d(),await Promise.race([o,new Promise(p=>{setTimeout(p,3e3).unref()})])}}}function cr(r,e,t,n){if(r==="on"||r==="off")return ir(r==="on",t),n(r==="off"?`Telemetry is off. ashr sends nothing.
706
+ `:`Telemetry is on.
707
+ `),r==="on"&&e.disabledBy&&e.disabledBy!=="~/.ashr/settings.json"&&n(`It stays off in this shell while ${e.disabledBy} is set.
708
+ `),0;if(r==="show"){let i="";try{i=oo(mt(t),"utf8").trim()}catch{}return n(i?`${i}
709
+ `:`Nothing has been sent yet.
710
+ `),0}let s=i=>i?"sent":"not sent";return n([e.enabled?`Telemetry is on. ${Wt}`:`Telemetry is off (${e.disabledBy}).`,`Endpoint: ${e.endpoint}`,`Prompts and replies: ${s(e.logPrompts)} (ASHR_TELEMETRY_LOG_PROMPTS)`,`Tool arguments: ${s(e.logToolDetails)} (ASHR_TELEMETRY_LOG_TOOL_DETAILS)`,`Tool output: ${s(e.logToolOutput)} (ASHR_TELEMETRY_LOG_TOOL_OUTPUT)`,"Secrets, emails, phone numbers and your home directory are removed on this machine before anything is sent.",""].join(`
711
+ `)),0}var dr=`Usage:
712
+ ashr init [--yes] connect this Docker host to your Ashr workspace: the agent's commands, data and GPU
713
+ jobs run here; re-run to upgrade
714
+ ashr open your Ashr workspace in the terminal
715
+ ashr -c, --continue reopen your latest chat
716
+ ashr --task <name> select a task
717
+ ashr --chat <id> open an existing chat
718
+ ashr --service require a service key (MANIFOLD_API_KEY or ashr login --key); never opens a browser
719
+ ashr login [--key ask_\u2026] sign in through the browser, or save a service key
720
+ ashr logout forget the saved sign-in
721
+ ashr telemetry [status | show | on | off] what usage data ashr sends, and turning it off
722
+ ashr --version | --help`,ao="The Ashr agent runs in your Ashr workspace, not inside this terminal: run ashr init to connect this machine and your ChatGPT plan, then ashr to open the workspace.";async function co(r){let e=Qt(r),t=sr(process.env);if(e.command==="telemetry")return cr(e.telemetryAction??"status",t,process.env,i=>process.stdout.write(i));if(e.command==="help")return process.stdout.write(`ashr ${me} \u2014 the Ashr terminal
723
+
724
+ ${dr}
725
+ `),0;if(e.command==="version")return process.stdout.write(`${me}
726
+ `),0;let n=or(t,process.env);n&&process.stderr.write(`${n}
727
+ `);let s=ar({config:t});e.command!=="run"&&s.command(e.command,{});try{if(e.command==="update")return process.stdout.write(`Update with: npm i -g @ashrlabs/ashr@latest
728
+ `),0;if(e.command==="init")return await Xn(e);if(e.subscription||e.provider||e.command==="run"&&e.runtime==="local")return process.stderr.write(`${ao}
729
+ `),2;let i=Ae();return e.command==="login"?await(e.key||e.service?Zt(e,i,{env:process.env,prompt:He}):Un(e,i)):e.command==="logout"?(delete i.manifold,re(i),process.stdout.write(`Signed out.
730
+ `),0):await Hn(e,i)}finally{await s.flush()}}co(process.argv.slice(2)).then(r=>process.exit(r),r=>{let e=r instanceof j?`${r.message.split(`
731
+
732
+ Usage:`)[0]}
733
+
734
+ ${dr}`:r instanceof Error?r.message:String(r);process.stderr.write(`${e}
735
+ `),process.exit(r instanceof j?2:1)});export{ao as HOSTED_ONLY,dr as PUBLISHED_USAGE};