@anyforge/control 0.4.0 → 0.6.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 +55 -1
  2. package/dist/cli.js +160 -67
  3. package/package.json +9 -5
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # @anyforge/control
2
2
 
3
- Two things in one binary:
3
+ Three interfaces in one binary:
4
4
 
5
5
  1. **A governed LLM proxy** for any agent — Claude Code, Cursor, Cline, Continue, or your own SDK. Route their calls through AnyForge for cost analytics, audit log, and policy enforcement.
6
6
  2. **A scriptable AnyForge client** — approvals, crews, work items and workflows from a shell, with no browser and no agent in the loop.
7
+ 3. **The hosted Oracle in your terminal** — persistent conversations, streamed answers, workspace context, questions, explicit attachments and generated-file downloads.
7
8
 
8
9
  The second half is what makes AnyForge usable from CI. Every command speaks the same `/api/v1/control/*` API the MCP server does, but without needing a language model to drive it.
9
10
 
@@ -55,6 +56,58 @@ Claude Code recognises both, but they behave differently. `ANTHROPIC_API_KEY` is
55
56
 
56
57
  ## Commands
57
58
 
59
+ ### Oracle
60
+
61
+ Requires Node.js 22.19 or later. Sign in with a separate Oracle credential, then
62
+ start a conversation or submit a read-only turn from a script:
63
+
64
+ ```bash
65
+ anyforge oracle login
66
+ anyforge oracle
67
+ anyforge oracle --program prog_123
68
+ anyforge oracle exec "Summarize current risks" --json
69
+ cat incident.log | anyforge oracle exec "Explain this log" --stream-json
70
+ anyforge oracle sessions
71
+ anyforge oracle resume <session-id>
72
+ anyforge oracle download <session-id> <file-id> --output ./downloads
73
+ ```
74
+
75
+ Oracle login uses browser authorization with PKCE and stores its credential in
76
+ `~/.anyforge/oracle`. It leaves your inference proxy and MCP setup unchanged.
77
+ `anyforge oracle logout` revokes and clears that Oracle login. For unattended
78
+ use, an attributable API key in `ANYFORGE_API_TOKEN` takes precedence over the
79
+ saved Oracle login.
80
+
81
+ Interactive conversations are read only by default. Use `oracle login --write`
82
+ and start with `oracle --write` to enable workspace edits. Consequential edits
83
+ require a later explicit confirmation of the proposed action. Operator actions
84
+ and external MCP writes additionally require `--approve`. `oracle exec` always
85
+ remains read only.
86
+
87
+ Use Shift+Enter for multiline input. Type `/` for the command palette:
88
+ `/help`, `/status` (session, access, model, last turn, usage and limits),
89
+ `/new`, `/clear`, `/resume`, `/context`, `/attach`, `/copy`, `/files`,
90
+ `/usage`, `/model` and `/exit`. `/design [topic]` and `/catchup` are answered
91
+ by the Oracle itself, as in the web widget; acting on `/catchup` decisions
92
+ needs a session started with `--write --approve`.
93
+
94
+ While Oracle works, a status line shows what it is doing, how long it has
95
+ taken and which step it is on, and each tool appears as it runs. Enter queues
96
+ one follow-up message. Esc or Ctrl+C cancels the current run and keeps the
97
+ conversation open; press Ctrl+C twice, or Ctrl+D on an empty line, to exit.
98
+ Ctrl+O shows or hides finished tool activity; Ctrl+Shift+F searches the
99
+ conversation. A disconnect leaves the hosted run active.
100
+
101
+ Only files explicitly named with `--file` or `/attach` are uploaded: at most
102
+ 10 files, 10 MiB each and 20 MiB total. Piped input is limited to 1 MiB; free-text
103
+ `--context` to 8 KiB. Download destinations must already exist, and downloads
104
+ never overwrite files. Oracle does not discover local files or run local tools.
105
+ The server also checks saved conversation size; use a new session or an
106
+ attachment when a turn exceeds the remaining conversation limit.
107
+
108
+ `anyforge ask` keeps its existing stateless read-only API and JSON output.
109
+ See the [CLI guide](https://anyforge.ai/docs/cli) for the full command contract.
110
+
58
111
  ### Setup
59
112
 
60
113
  | | |
@@ -100,6 +153,7 @@ CI gates on these, so they are a contract:
100
153
  | `2` | Usage error |
101
154
  | `3` | Not authenticated — run `init`, or set `ANYFORGE_API_TOKEN` |
102
155
  | `4` | The API refused; its error code prints to stderr |
156
+ | `130` | An Oracle run was interrupted: `exec` was cancelled with Ctrl+C, or you left an interactive session whose last answer you cancelled |
103
157
 
104
158
  Errors and progress go to stderr, so a `--json` pipe stays parseable under a failure.
105
159
 
package/dist/cli.js CHANGED
@@ -1,55 +1,55 @@
1
1
  #!/usr/bin/env node
2
- var Lt=Object.defineProperty;var Nt=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ft=(e,t)=>{for(var n in t)Lt(e,n,{get:t[n],enumerable:!0})};var St={};Ft(St,{default:()=>Vn});var Vn,Ot=Nt(()=>{Vn={name:"@anyforge/control",version:"0.4.0",description:"AnyForge Control CLI \u2014 governed LLM proxy for any agent, and a scriptable client for AnyForge approvals, crews, work items and workflows.",license:"Apache-2.0",homepage:"https://anyforge.ai",repository:{type:"git",url:"git+https://github.com/pootonline/sovereign-engineering.git",directory:"packages/control/cli"},publishConfig:{access:"public"},type:"module",main:"dist/cli.js",bin:{anyforge:"dist/cli.js",control:"dist/cli.js"},files:["dist","README.md"],scripts:{build:"esbuild src/cli.ts --bundle --minify --platform=node --format=esm --outfile=dist/cli.js --packages=external && chmod +x dist/cli.js","type-check":"tsc --noEmit",test:"tsx --test src/commands/*.test.ts",dev:"tsx src/cli.ts",prepublishOnly:"npm run build","publish:beta":"npm publish --access public --tag beta","publish:stable":"npm publish --access public"},engines:{node:">=22"},dependencies:{kleur:"^4.1.5",open:"^10.0.0",prompts:"^2.4.2"},devDependencies:{"@types/node":"^22.20.0","@types/prompts":"^2.4.9",esbuild:"^0.25.0",tsx:"^4.7.0",typescript:"^7.0.2"}}});import{red as _t,yellow as zn,dim as J,bold as I}from"kleur/colors";import{execFileSync as ge}from"child_process";import Yt from"open";import fe from"prompts";import{green as K,dim as g,bold as A,yellow as F,cyan as Q}from"kleur/colors";import{promises as X}from"fs";import{homedir as Dt}from"os";import{join as Le,dirname as lo}from"path";var Ne=Le(Dt(),".anyforge"),de=Le(Ne,"credentials");async function Fe(e){await X.mkdir(Ne,{recursive:!0,mode:448}),await X.writeFile(de,JSON.stringify(e,null,2),{mode:384})}async function T(){try{let e=await X.readFile(de,"utf8");return JSON.parse(e)}catch(e){if(e.code==="ENOENT")return null;throw e}}async function De(){try{return await X.unlink(de),!0}catch(e){if(e.code==="ENOENT")return!1;throw e}}import{promises as f}from"fs";import{execFileSync as jt}from"child_process";import{homedir as _}from"os";import{join as m}from"path";import V from"prompts";import{dim as C,green as pe,yellow as z}from"kleur/colors";var $="# === AnyForge Control \u2014 added by `anyforge init` ===",G="# === end AnyForge Control ===",N=".anyforge-bak",Mt=/claude_(max|pro|team|enterprise)/i,Ut=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_EXPORTER_OTLP_HEADERS","OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"];async function Ke(e){let t=[],n=await Gt(e);if(n&&t.push(n),e.scope==="repo"){let o=await Kt(e);return o&&t.push(o),t}for(let o of Wt){let r=await o.detect();if(!(!r||!(await V({type:"confirm",name:"go",message:`Patch ${o.label} via shell rc (${C(r.path)}) ?`,initial:!0})).go))try{await o.patch(r,e),t.push({label:o.label,path:r.path}),console.log(pe(" \u2714")+" "+o.label)}catch(s){console.warn(" \u26A0 Skipped "+o.label+": "+s.message)}}return t}async function Gt(e){let t=e.scope==="repo"&&e.repoRoot?{dir:m(e.repoRoot,".claude"),file:"settings.local.json"}:{dir:m(_(),".claude"),file:"settings.json"},n=m(t.dir,t.file);if(!(await V({type:"confirm",name:"go",message:`Patch Claude Code config at ${C(n)} ?`,initial:!0})).go)return null;await f.mkdir(t.dir,{recursive:!0});let r=await f.readFile(n,"utf8").catch(()=>"{}"),i;try{i=JSON.parse(r)}catch{i={}}let s=i.env??{},a=s.ANTHROPIC_BASE_URL;return a&&!a.includes(e.endpoint)&&(a.includes("localhost")||a.includes("127.0.0.1")||a.includes("0.0.0.0"))&&(console.log(),console.log(z(" \u26A0 Local LLM proxy detected: ")+C(a)),console.log(C(" AnyForge only routes Anthropic Claude models. Local models (Ollama,")),console.log(C(" LM Studio, etc.) will not work in Claude Code after this patch.")),console.log(C(" To restore: remove ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN")),console.log(C(` from ${n}`)),console.log(),!(await V({type:"confirm",name:"go",message:"Overwrite existing local LLM proxy config?",initial:!1})).go)?null:(await P(n)&&!await P(n+N)&&await f.copyFile(n,n+N),s.ANTHROPIC_BASE_URL=`${e.endpoint}/v1/anthropic`,s.ANTHROPIC_AUTH_TOKEN=e.apiKey,delete s.ANTHROPIC_API_KEY,i.env=s,await f.writeFile(n,JSON.stringify(i,null,2)+`
3
- `),console.log(pe(" \u2714")+" Claude Code"),{label:e.scope==="repo"?"Claude Code (repo)":"Claude Code (global)",path:n})}async function qe(e){let t=m(_(),".claude"),n=m(t,"settings.json");await f.mkdir(t,{recursive:!0});let o=await f.readFile(n,"utf8").catch(()=>"{}"),r;try{r=JSON.parse(o)}catch{r={}}let i=r.env??{},s=i.OTEL_EXPORTER_OTLP_ENDPOINT;return s&&!ue(s,Ye(e.endpoint))?(console.log(z(" \u26A0 An OpenTelemetry endpoint is already configured: ")+C(s)),console.log(C(" Leaving it untouched. To send Claude Code usage to AnyForge, point")),console.log(C(" OTEL_EXPORTER_OTLP_ENDPOINT at "+e.endpoint+"/api/otel")),null):(await P(n)&&!await P(n+N)&&await f.copyFile(n,n+N),i.CLAUDE_CODE_ENABLE_TELEMETRY="1",i.OTEL_METRICS_EXPORTER="otlp",i.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",i.OTEL_EXPORTER_OTLP_ENDPOINT=`${e.endpoint}/api/otel`,i.OTEL_EXPORTER_OTLP_HEADERS=`Authorization=Bearer ${e.apiKey}`,i.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE="delta",r.env=i,await f.writeFile(n,JSON.stringify(r,null,2)+`
4
- `),{label:"Claude Code usage telemetry (global)",path:n})}async function Kt(e){if(!e.repoRoot)return null;let t=m(e.repoRoot,".env");if(!(await V({type:"confirm",name:"go",message:`Write AnyForge env block to ${C(t)} ?`,initial:!0})).go)return null;let o=[$,`ANYFORGE_API_KEY=${e.apiKey}`,`ANTHROPIC_BASE_URL=${e.endpoint}/v1/anthropic`,`ANTHROPIC_AUTH_TOKEN=${e.apiKey}`,`OPENAI_BASE_URL=${e.endpoint}/v1/openai`,`OPENAI_API_KEY=${e.apiKey}`,G,""].join(`
5
- `),r="";try{r=await f.readFile(t,"utf8")}catch{}let i;return r.includes($)?i=r.replace(new RegExp(`${$}[\\s\\S]*?${G}\\n?`),o):r.length===0?i=o:i=r.endsWith(`
6
- `)?`${r}${o}`:`${r}
7
- ${o}`,i!==r&&await f.writeFile(t,i),await qt(e.repoRoot),console.log(pe(" \u2714")+" .env"),{label:".env (repo)",path:t}}async function qt(e){let t=m(e,".gitignore");try{(await f.readFile(t,"utf8")).split(`
8
- `).map(i=>i.trim()).some(i=>i===".env"||i==="/.env"||i==="*.env")||console.log(z(" \u26A0 .env is not in .gitignore \u2014 add it before committing."))}catch{console.log(z(" \u26A0 No .gitignore found \u2014 make sure .env is excluded before committing."))}}var Wt=[{label:"Cursor (shell hint)",async detect(){let e=m(_(),".cursor");return await P(e)?{path:".cursor (shell hint)"}:null},async patch(e,t){await je(t)}},{label:"Cline / Continue (shell hint)",async detect(){let e=await P(m(_(),".cline")),t=await P(m(_(),".continue"));return e||t?{path:"~/.cline or ~/.continue (shell hint)"}:null},async patch(e,t){await je(t)}}];async function je(e){let t=[$,`export ANYFORGE_API_KEY="${e.apiKey}"`,`export ANTHROPIC_BASE_URL="${e.endpoint}/v1/anthropic"`,`export ANTHROPIC_AUTH_TOKEN="${e.apiKey}"`,`export OPENAI_BASE_URL="${e.endpoint}/v1/openai"`,`export OPENAI_API_KEY="${e.apiKey}"`,"unset ANTHROPIC_API_KEY 2>/dev/null || true",G,""].join(`
9
- `);for(let n of[".zshrc",".bashrc"]){let o=m(_(),n);if(!await P(o))continue;let r=await f.readFile(o,"utf8"),i;r.includes($)?i=r.replace(new RegExp(`${$}[\\s\\S]*?${G}\\n?`),t):i=r.endsWith(`
10
- `)?`${r}
11
- ${t}`:`${r}
12
-
13
- ${t}`,i!==r&&await f.writeFile(o,i)}}async function P(e){try{return await f.access(e),!0}catch{return!1}}async function We(){try{let e=await f.readFile(m(_(),".claude.json"),"utf8"),n=JSON.parse(e).oauthAccount,o=n?.organizationType??"";if(n&&Mt.test(o))return{isSubscription:!0,plan:/max/i.test(o)?"Max":/pro/i.test(o)?"Pro":/enterprise/i.test(o)?"Enterprise":/team/i.test(o)?"Team":o,email:n.emailAddress??null}}catch{}return{isSubscription:!1,plan:null,email:null}}async function He(e={}){let t=e.endpoint?Ye(e.endpoint):null,n=[],o=m(_(),".claude","settings.json");await Me(o,t)&&n.push(o);for(let i of[".zshrc",".bashrc"]){let s=m(_(),i);await Ue(s)&&n.push(s)}let r=e.repoRoot??Ht(process.cwd());if(r){let i=m(r,".claude","settings.local.json");await Me(i,t)&&n.push(i);let s=m(r,".env");await Ue(s)&&n.push(s)}return{reverted:n}}function ue(e,t){return e?e.includes("anyforge")?!0:t?e.includes(t):!1:!1}async function Me(e,t){if(!await P(e))return!1;if(await P(e+N))return await f.copyFile(e+N,e),await f.unlink(e+N).catch(()=>{}),!0;let n;try{n=JSON.parse(await f.readFile(e,"utf8"))}catch{return!1}let o=n.env;if(!o)return!1;let r=!1,i=[["ANTHROPIC_BASE_URL","ANTHROPIC_AUTH_TOKEN"],["OPENAI_BASE_URL","OPENAI_API_KEY"]];for(let[s,a]of i)ue(o[s],t)&&(delete o[s],delete o[a],r=!0);if(o.ANYFORGE_API_KEY&&(delete o.ANYFORGE_API_KEY,r=!0),ue(o.OTEL_EXPORTER_OTLP_ENDPOINT,t))for(let s of Ut)s in o&&(delete o[s],r=!0);return r?(Object.keys(o).length===0&&delete n.env,await f.writeFile(e,JSON.stringify(n,null,2)+`
14
- `),!0):!1}async function Ue(e){let t;try{t=await f.readFile(e,"utf8")}catch{return!1}if(!t.includes($))return!1;let n=t.replace(new RegExp(`\\n?${Ge($)}[\\s\\S]*?${Ge(G)}\\n?`),`
2
+ var Ci=Object.defineProperty;var Ti=(t,e)=>()=>(t&&(e=t(t=0)),e);var _i=(t,e)=>{for(var n in e)Ci(t,n,{get:e[n],enumerable:!0})};var or={};_i(or,{default:()=>dc});var dc,ir=Ti(()=>{dc={name:"@anyforge/control",version:"0.6.0",description:"AnyForge CLI \u2014 hosted Oracle conversations, a governed LLM proxy, and a scriptable client for approvals, crews, work items and workflows.",license:"Apache-2.0",homepage:"https://anyforge.ai",repository:{type:"git",url:"git+https://github.com/pootonline/sovereign-engineering.git",directory:"packages/control/cli"},publishConfig:{access:"public"},type:"module",main:"dist/cli.js",bin:{anyforge:"dist/cli.js",control:"dist/cli.js"},files:["dist","README.md"],scripts:{build:"esbuild src/cli.ts --bundle --minify --platform=node --format=esm --outfile=dist/cli.js --packages=external && chmod +x dist/cli.js","type-check":"tsc --noEmit",test:"npm run build && node scripts/test.mjs",dev:"tsx src/cli.ts",prepublishOnly:"npm run build","publish:beta":"npm publish --access public --tag beta","publish:stable":"npm publish --access public"},engines:{node:">=22.19.0"},dependencies:{"@earendil-works/pi-tui":"^0.87.1",kleur:"^4.1.5",open:"^10.0.0",prompts:"^2.4.2","proper-lockfile":"^4.1.2"},devDependencies:{"@types/node":"^22.20.0","@types/prompts":"^2.4.9","@types/proper-lockfile":"^4.1.4",esbuild:"^0.25.0","node-pty":"^1.1.0",tsx:"^4.7.0",typescript:"^7.0.2"}}});import{red as pi,yellow as Lc,dim as At,bold as B}from"kleur/colors";import{execFileSync as vn}from"child_process";import Di from"open";import yn from"prompts";import{green as Ze,dim as L,bold as Z,yellow as xe,cyan as Pt}from"kleur/colors";import{promises as Tt}from"fs";import{homedir as $i}from"os";import{join as wr,dirname as Kc}from"path";var yr=wr($i(),".anyforge"),gn=wr(yr,"credentials");async function vr(t){await Tt.mkdir(yr,{recursive:!0,mode:448}),await Tt.writeFile(gn,JSON.stringify(t,null,2),{mode:384})}async function ve(){try{let t=await Tt.readFile(gn,"utf8");return JSON.parse(t)}catch(t){if(t.code==="ENOENT")return null;throw t}}async function Or(){try{return await Tt.unlink(gn),!0}catch(t){if(t.code==="ENOENT")return!1;throw t}}import{promises as P}from"fs";import{execFileSync as Pi}from"child_process";import{homedir as le}from"os";import{join as M}from"path";import _t from"prompts";import{dim as X,green as wn,yellow as $t}from"kleur/colors";var Oe="# === AnyForge Control \u2014 added by `anyforge init` ===",Xe="# === end AnyForge Control ===",ke=".anyforge-bak",Li=/claude_(max|pro|team|enterprise)/i,Fi=["CLAUDE_CODE_ENABLE_TELEMETRY","OTEL_METRICS_EXPORTER","OTEL_EXPORTER_OTLP_PROTOCOL","OTEL_EXPORTER_OTLP_ENDPOINT","OTEL_EXPORTER_OTLP_HEADERS","OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"];async function xr(t){let e=[],n=await Ni(t);if(n&&e.push(n),t.scope==="repo"){let r=await qi(t);return r&&e.push(r),e}for(let r of Ui){let o=await r.detect();if(!(!o||!(await _t({type:"confirm",name:"go",message:`Patch ${r.label} via shell rc (${X(o.path)}) ?`,initial:!0})).go))try{await r.patch(o,t),e.push({label:r.label,path:o.path}),console.log(wn(" \u2714")+" "+r.label)}catch(s){console.warn(" \u26A0 Skipped "+r.label+": "+s.message)}}return e}async function Ni(t){let e=t.scope==="repo"&&t.repoRoot?{dir:M(t.repoRoot,".claude"),file:"settings.local.json"}:{dir:M(le(),".claude"),file:"settings.json"},n=M(e.dir,e.file);if(!(await _t({type:"confirm",name:"go",message:`Patch Claude Code config at ${X(n)} ?`,initial:!0})).go)return null;await P.mkdir(e.dir,{recursive:!0});let o=await P.readFile(n,"utf8").catch(()=>"{}"),i;try{i=JSON.parse(o)}catch{i={}}let s=i.env??{},a=s.ANTHROPIC_BASE_URL;return a&&!a.includes(t.endpoint)&&(a.includes("localhost")||a.includes("127.0.0.1")||a.includes("0.0.0.0"))&&(console.log(),console.log($t(" \u26A0 Local LLM proxy detected: ")+X(a)),console.log(X(" AnyForge only routes Anthropic Claude models. Local models (Ollama,")),console.log(X(" LM Studio, etc.) will not work in Claude Code after this patch.")),console.log(X(" To restore: remove ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN")),console.log(X(` from ${n}`)),console.log(),!(await _t({type:"confirm",name:"go",message:"Overwrite existing local LLM proxy config?",initial:!1})).go)?null:(await re(n)&&!await re(n+ke)&&await P.copyFile(n,n+ke),s.ANTHROPIC_BASE_URL=`${t.endpoint}/v1/anthropic`,s.ANTHROPIC_AUTH_TOKEN=t.apiKey,delete s.ANTHROPIC_API_KEY,i.env=s,await P.writeFile(n,JSON.stringify(i,null,2)+`
3
+ `),console.log(wn(" \u2714")+" Claude Code"),{label:t.scope==="repo"?"Claude Code (repo)":"Claude Code (global)",path:n})}async function Ir(t){let e=M(le(),".claude"),n=M(e,"settings.json");await P.mkdir(e,{recursive:!0});let r=await P.readFile(n,"utf8").catch(()=>"{}"),o;try{o=JSON.parse(r)}catch{o={}}let i=o.env??{},s=i.OTEL_EXPORTER_OTLP_ENDPOINT;return s&&!hn(s,Cr(t.endpoint))?(console.log($t(" \u26A0 An OpenTelemetry endpoint is already configured: ")+X(s)),console.log(X(" Leaving it untouched. To send Claude Code usage to AnyForge, point")),console.log(X(" OTEL_EXPORTER_OTLP_ENDPOINT at "+t.endpoint+"/api/otel")),null):(await re(n)&&!await re(n+ke)&&await P.copyFile(n,n+ke),i.CLAUDE_CODE_ENABLE_TELEMETRY="1",i.OTEL_METRICS_EXPORTER="otlp",i.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",i.OTEL_EXPORTER_OTLP_ENDPOINT=`${t.endpoint}/api/otel`,i.OTEL_EXPORTER_OTLP_HEADERS=`Authorization=Bearer ${t.apiKey}`,i.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE="delta",o.env=i,await P.writeFile(n,JSON.stringify(o,null,2)+`
4
+ `),{label:"Claude Code usage telemetry (global)",path:n})}async function qi(t){if(!t.repoRoot)return null;let e=M(t.repoRoot,".env");if(!(await _t({type:"confirm",name:"go",message:`Write AnyForge env block to ${X(e)} ?`,initial:!0})).go)return null;let r=[Oe,`ANYFORGE_API_KEY=${t.apiKey}`,`ANTHROPIC_BASE_URL=${t.endpoint}/v1/anthropic`,`ANTHROPIC_AUTH_TOKEN=${t.apiKey}`,`OPENAI_BASE_URL=${t.endpoint}/v1/openai`,`OPENAI_API_KEY=${t.apiKey}`,Xe,""].join(`
5
+ `),o="";try{o=await P.readFile(e,"utf8")}catch{}let i;return o.includes(Oe)?i=o.replace(new RegExp(`${Oe}[\\s\\S]*?${Xe}\\n?`),r):o.length===0?i=r:i=o.endsWith(`
6
+ `)?`${o}${r}`:`${o}
7
+ ${r}`,i!==o&&await P.writeFile(e,i),await Mi(t.repoRoot),console.log(wn(" \u2714")+" .env"),{label:".env (repo)",path:e}}async function Mi(t){let e=M(t,".gitignore");try{(await P.readFile(e,"utf8")).split(`
8
+ `).map(i=>i.trim()).some(i=>i===".env"||i==="/.env"||i==="*.env")||console.log($t(" \u26A0 .env is not in .gitignore \u2014 add it before committing."))}catch{console.log($t(" \u26A0 No .gitignore found \u2014 make sure .env is excluded before committing."))}}var Ui=[{label:"Cursor (shell hint)",async detect(){let t=M(le(),".cursor");return await re(t)?{path:".cursor (shell hint)"}:null},async patch(t,e){await br(e)}},{label:"Cline / Continue (shell hint)",async detect(){let t=await re(M(le(),".cline")),e=await re(M(le(),".continue"));return t||e?{path:"~/.cline or ~/.continue (shell hint)"}:null},async patch(t,e){await br(e)}}];async function br(t){let e=[Oe,`export ANYFORGE_API_KEY="${t.apiKey}"`,`export ANTHROPIC_BASE_URL="${t.endpoint}/v1/anthropic"`,`export ANTHROPIC_AUTH_TOKEN="${t.apiKey}"`,`export OPENAI_BASE_URL="${t.endpoint}/v1/openai"`,`export OPENAI_API_KEY="${t.apiKey}"`,"unset ANTHROPIC_API_KEY 2>/dev/null || true",Xe,""].join(`
9
+ `);for(let n of[".zshrc",".bashrc"]){let r=M(le(),n);if(!await re(r))continue;let o=await P.readFile(r,"utf8"),i;o.includes(Oe)?i=o.replace(new RegExp(`${Oe}[\\s\\S]*?${Xe}\\n?`),e):i=o.endsWith(`
10
+ `)?`${o}
11
+ ${e}`:`${o}
12
+
13
+ ${e}`,i!==o&&await P.writeFile(r,i)}}async function re(t){try{return await P.access(t),!0}catch{return!1}}async function Sr(){try{let t=await P.readFile(M(le(),".claude.json"),"utf8"),n=JSON.parse(t).oauthAccount,r=n?.organizationType??"";if(n&&Li.test(r))return{isSubscription:!0,plan:/max/i.test(r)?"Max":/pro/i.test(r)?"Pro":/enterprise/i.test(r)?"Enterprise":/team/i.test(r)?"Team":r,email:n.emailAddress??null}}catch{}return{isSubscription:!1,plan:null,email:null}}async function Er(t={}){let e=t.endpoint?Cr(t.endpoint):null,n=[],r=M(le(),".claude","settings.json");await Rr(r,e)&&n.push(r);for(let i of[".zshrc",".bashrc"]){let s=M(le(),i);await Ar(s)&&n.push(s)}let o=t.repoRoot??ji(process.cwd());if(o){let i=M(o,".claude","settings.local.json");await Rr(i,e)&&n.push(i);let s=M(o,".env");await Ar(s)&&n.push(s)}return{reverted:n}}function hn(t,e){return t?t.includes("anyforge")?!0:e?t.includes(e):!1:!1}async function Rr(t,e){if(!await re(t))return!1;if(await re(t+ke))return await P.copyFile(t+ke,t),await P.unlink(t+ke).catch(()=>{}),!0;let n;try{n=JSON.parse(await P.readFile(t,"utf8"))}catch{return!1}let r=n.env;if(!r)return!1;let o=!1,i=[["ANTHROPIC_BASE_URL","ANTHROPIC_AUTH_TOKEN"],["OPENAI_BASE_URL","OPENAI_API_KEY"]];for(let[s,a]of i)hn(r[s],e)&&(delete r[s],delete r[a],o=!0);if(r.ANYFORGE_API_KEY&&(delete r.ANYFORGE_API_KEY,o=!0),hn(r.OTEL_EXPORTER_OTLP_ENDPOINT,e))for(let s of Fi)s in r&&(delete r[s],o=!0);return o?(Object.keys(r).length===0&&delete n.env,await P.writeFile(t,JSON.stringify(n,null,2)+`
14
+ `),!0):!1}async function Ar(t){let e;try{e=await P.readFile(t,"utf8")}catch{return!1}if(!e.includes(Oe))return!1;let n=e.replace(new RegExp(`\\n?${kr(Oe)}[\\s\\S]*?${kr(Xe)}\\n?`),`
15
15
  `).replace(/\n{3,}/g,`
16
16
 
17
- `);return await f.writeFile(e,n),!0}function Ge(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Ye(e){try{return new URL(e).host}catch{return null}}function Ht(e){try{return jt("git",["rev-parse","--show-toplevel"],{cwd:e,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return null}}var Bt=2e3,Jt=900*1e3;async function Je(e){let{endpoint:t}=e;console.log(A("AnyForge Control \u2014 device authorisation")),console.log(g(` endpoint: ${t}`));let n=process.cwd(),o=Be(n),r=o?Zt(o):null,s=await(await fetch(`${t}/api/cli/init`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({repo:r})})).json();if(!s.ok||!s.data)throw new Error(`Could not start the authorisation flow: ${s.error??"unknown error"}`);let{code:a,authUrl:l,pollUrl:k}=s.data;console.log(),console.log("Open this URL in your browser to authorise:"),console.log(` ${Q(l)}`),console.log(g(` Code: ${a}`)),console.log();try{await Yt(l)}catch{}let b=await Xt({pollUrl:k,endpoint:t});await Fe(b),console.log(K("\u2714")+" API key saved to "+g("~/.anyforge/credentials"));let L=await We();if(L.isSubscription&&!e.force){await Vt(L,t,b.apiKey);return}let $t=process.cwd(),xe=Be($t),Te=await zt(xe),$e=await Ke({endpoint:t,apiKey:b.apiKey,scope:Te,repoRoot:xe});$e.length===0?(console.log(F("No agent configs were patched.")),console.log(g(" Set ANTHROPIC_BASE_URL="+t+"/v1/anthropic and")),console.log(g(" ANTHROPIC_AUTH_TOKEN=<your AnyForge key> manually if needed."))):Qt($e),console.log(),L.isSubscription&&e.force&&(console.log(F(` \u26A0 --force: your Claude ${L.plan??""} subscription login is now shadowed`)),console.log(F(" by the AnyForge token \u2014 Claude Code will use the BYOK proxy, which")),console.log(F(" needs an Anthropic API key on your company. Run ")+A("anyforge logout")),console.log(F(" to revert and restore your subscription."))),Te==="repo"&&(console.log(g(" Re-run this command in another repo to patch it too,")),console.log(g(' or run from any directory and choose "global" to install system-wide.'))),console.log(A("Done.")+" Visit "+Q(`${t}/usage`)+" to see your traffic.")}async function Xt(e){let t=Date.now();process.stdout.write("Waiting for browser authorisation\u2026 ");let n=0;for(;Date.now()-t<Jt;){await new Promise(i=>setTimeout(i,Bt)),process.stdout.write("."),n+=1,n>40&&(process.stdout.write(`
18
- `),n=0);let r=await(await fetch(e.pollUrl)).json();if(r.status==="authorized"&&r.data)return process.stdout.write(" "+K("done")+`
19
- `),{apiKey:r.data.apiKey,companyId:r.data.companyId,endpoint:e.endpoint,savedAt:new Date().toISOString()};if(r.status==="expired"||r.status==="not_found")throw process.stdout.write(`
20
- `),new Error(`Authorisation ${r.status}. Re-run \`anyforge init\`.`)}throw new Error("Authorisation timed out (15 min). Re-run `anyforge init`.")}async function Vt(e,t,n){let o=e.email?` (${e.email})`:"";console.log(),console.log(F(` \u26A0 Claude ${e.plan??""} subscription login detected${o}.`)),console.log(),console.log(" AnyForge Control\u2019s inference proxy is "+A("BYOK-only")+" \u2014 it routes"),console.log(" Claude Code through your own Anthropic API key for metering and"),console.log(" governance. Pointing Claude Code at the proxy would "+A("override and")),console.log(" "+A("disable your subscription login")+", so we did NOT patch your config."),console.log(),console.log(" Instead, use your subscription for inference and add the "+A("AnyForge")),console.log(" "+A("MCP server")+" for control (HIL approvals, work items, crews):"),console.log();let r=`claude mcp add anyforge --env ANYFORGE_API_KEY=${n} -- npx -y @anyforge/mcp-server`;if((await fe({type:"confirm",name:"go",message:"Add the AnyForge MCP server to Claude Code now?",initial:!0})).go)try{ge("claude",["mcp","add","anyforge","--env",`ANYFORGE_API_KEY=${n}`,"--","npx","-y","@anyforge/mcp-server"],{stdio:"inherit"}),console.log(K(" \u2714")+" AnyForge MCP server added to Claude Code.")}catch{console.log(F(" Could not run `claude mcp add` automatically (is the `claude` CLI on PATH?).")),console.log(g(" Add it manually:")),console.log(" "+r)}else console.log(g(" Add it later with:")),console.log(" "+r);if(console.log(),(await fe({type:"confirm",name:"go",message:"Send Claude Code usage to your AnyForge dashboard? (token counts only \u2014 no prompt content)",initial:!0})).go){let a=await qe({endpoint:t,apiKey:n});a&&(console.log(K(" \u2714")+" Usage telemetry enabled \u2014 view it at "+Q(`${t}/usage`)),console.log(g(" (written to "+a.path+"; `anyforge logout` removes it)")))}else console.log(g(" Skipped \u2014 your subscription usage stays local to Claude Code."));console.log(),console.log(g(" Want the metered BYOK proxy instead? Re-run with ")+A("--force")+g(" using an")),console.log(g(" Anthropic API key on your company (this disables your subscription in")),console.log(g(" Claude Code; ")+A("anyforge logout")+g(" reverts it).")),console.log(),console.log(A("Done.")+" Visit "+Q(`${t}/usage`)+" to see your traffic.")}function Be(e){try{return ge("git",["rev-parse","--show-toplevel"],{cwd:e,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return null}}async function zt(e){return e?(await fe({type:"select",name:"scope",message:"Where should AnyForge patch Claude Code?",choices:[{title:`This repo only (${e}/.claude/settings.local.json + .env)`,value:"repo"},{title:"Globally (~/.claude/settings.json)",value:"global"}],initial:0})).scope??"repo":(console.log(g(" Not a git repo root \u2014 installing globally.")),console.log(g(" Run this from a repo root if you want a per-repo install instead.")),"global")}function Qt(e){console.log(K("\u2714")+" Patched "+e.length+" agent config"+(e.length===1?"":"s")+":");for(let t of e)console.log(` ${g("\u2022")} ${t.label} ${g("("+t.path+")")}`)}function Zt(e){try{let t=ge("git",["remote","get-url","origin"],{cwd:e,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim(),n=t.match(/^git@[\w.-]+:([\w.-]+\/[\w.-]+?)(?:\.git)?$/);if(n)return n[1];let o=t.match(/^https?:\/\/[\w.-]+\/([\w.-]+\/[\w.-]+?)(?:\.git)?$/);return o?o[1]:null}catch{return null}}import{dim as we,yellow as on,bold as rn}from"kleur/colors";var R={OK:0,FAILURE:1,USAGE:2,NO_CREDENTIALS:3,API_ERROR:4},y=class extends Error{exitCode;hint;constructor(t,n=R.FAILURE,o){super(t),this.name="CliError",this.exitCode=n,this.hint=o}},c=class extends y{constructor(t,n){super(t,R.USAGE,n),this.name="UsageError"}},Z=class extends y{constructor(){super("Not authenticated \u2014 no ANYFORGE_API_TOKEN and no saved credential.",R.NO_CREDENTIALS,"Run `npx @anyforge/control init`, or set ANYFORGE_API_TOKEN for unattended use."),this.name="NotAuthenticatedError"}},ee=class extends y{code;status;constructor(t,n,o){super(o?`${t}: ${o}`:t,R.API_ERROR,en(t,n)),this.name="ControlApiError",this.code=t,this.status=n}};function en(e,t){if(e==="missing_api_key"||e==="invalid_api_key"||e==="invalid_token"||t===401)return"The key was rejected. Re-run `npx @anyforge/control init`.";if(e==="insufficient_scope"||t===403)return"This key lacks the scope for that command. Run `anyforge whoami` to see its scopes.";if(t===404)return"No such id in this company. Check it with the matching list command.";if(t===429)return"Rate limited. Retry after a short pause."}var Xe="https://crew.anyforge.ai";async function te(e){let t=process.env.ANYFORGE_API_TOKEN,n=e||process.env.ANYFORGE_API_URL;if(t)return{apiKey:t,endpoint:n||Xe};let o=await T();if(o?.apiKey)return{apiKey:o.apiKey,endpoint:n||o.endpoint||Xe};throw new Z}async function p(e,t,n,o){let r=`${e.endpoint.replace(/\/$/,"")}/api/v1/control${n}`,i;try{i=await fetch(r,{method:t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},...o!==void 0?{body:JSON.stringify(o)}:{}})}catch(a){throw new y(`Cannot reach ${e.endpoint}: ${a instanceof Error?a.message:String(a)}`,R.FAILURE,"Check your network, or pass --endpoint if this workspace is self-hosted.")}let s;try{s=await i.json()}catch{throw new y(`Non-JSON response from ${r} (HTTP ${i.status}).`,R.FAILURE)}if(!i.ok||s.ok===!1){let a=typeof s.error=="string"?s.error:`http_${i.status}`,l=typeof s.message=="string"?s.message:typeof s.detail=="string"?s.detail:void 0;throw new ee(a,i.status,l)}return s.data}function E(e){let t=Object.entries(e).filter(([,n])=>n!==void 0&&n!=="").map(([n,o])=>`${encodeURIComponent(n)}=${encodeURIComponent(String(o))}`);return t.length?`?${t.join("&")}`:""}async function me(e){return p(e,"GET","/me")}function he(e,t,n,o){let r=e?.entityLabels?.[t]?.[n];return typeof r=="string"&&r.trim()?r:o}async function Ve(e){try{return await me(e)}catch{return null}}function ze(e){return[...e].length}function M(e,t){if(t<=0)return"";let n=[...e];return n.length<=t?e:t===1?"\u2026":`${n.slice(0,t-1).join("")}\u2026`}function v(e,t){if(e.length===0)return"";let n=e.map(i=>t.map(s=>{let a=s.value(i)??"";return s.max?M(a,s.max):a})),o=t.map((i,s)=>Math.max(ze(i.header),...n.map(a=>ze(a[s])))),r=i=>i.map((s,a)=>a===i.length-1?s:s.padEnd(o[a]+2)).join("").replace(/\s+$/,"");return[r(t.map(i=>i.header)),...n.map(r)].join(`
21
- `)}function d(e){return JSON.stringify(e,null,2)}function Qe(e,t=Date.now()){let n=tn(e);if(n===null)return"\u2014";let o=Math.max(0,Math.floor((t-n)/1e3));if(o<60)return`${o}s`;let r=Math.floor(o/60);if(r<60)return`${r}m`;let i=Math.floor(r/60);return i<48?`${i}h`:`${Math.floor(i/24)}d`}function tn(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"){let t=Date.parse(e);return Number.isNaN(t)?null:t}return null}function u(e,t,n="\u2014"){let o=e[t];return typeof o=="string"&&o.trim()?o:typeof o=="number"&&Number.isFinite(o)?String(o):n}function w(e,t,n="\u2014"){for(let o of t){let r=e[o];if(typeof r=="string"&&r.trim())return r}return n}function S(e){let t=Array.isArray(e)?e:e&&typeof e=="object"?nn(e):null;return t?t.filter(n=>!!n&&typeof n=="object"&&!Array.isArray(n)):[]}function nn(e){for(let t of["items","results","rows","data"])if(Array.isArray(e[t]))return e[t];return null}function ne(e,t,n){let o=e?`last ${e}d`:"all time";return!t||!n?o:`last ${Math.max(1,Math.round((Date.now()-n)/864e5))}d of ${e?`${e}d`:"all time"} \u2014 read cap`}async function Ze(e){let t=await te(e.endpoint),n=await me(t);if(e.json){console.log(d(n));return}console.log(`Company: ${rn(n.companyName??n.companyId)}${n.companyName?we(` (${n.companyId})`):""}`),console.log(`Endpoint: ${t.endpoint}`),console.log(`Scopes: ${n.scopes.length?n.scopes.join(", "):on("none")}`),n.userId&&console.log(`User: ${n.userId}`);let o=await T();console.log(o?we(`Key tail: \u2026${o.apiKey.slice(-4)} \xB7 saved ${new Date(o.savedAt).toLocaleString()}`):we("Key from: ANYFORGE_API_TOKEN (no saved credential file)"))}import{green as et,dim as oe,yellow as sn}from"kleur/colors";async function tt(){let e=await T(),t=await De(),{reverted:n}=await He({endpoint:e?.endpoint??null});if(console.log(t?et("\u2714")+" Local credentials cleared.":sn("No credentials were saved on this machine.")),n.length){console.log(et("\u2714")+" Reverted patched configs:");for(let o of n)console.log(` ${oe("\u2022")} ${o}`);console.log(oe(" Your previous Claude Code login (if any) is restored."))}else console.log(oe(" No AnyForge config patches found to revert."));console.log(oe(" Revoke the key server-side at /settings \u2192 API keys."))}import{green as an,red as re,yellow as ln,dim as ie}from"kleur/colors";async function nt(e){let t=await T();if(!t){console.log(ln("Not logged in.")+" Run "+ie("npx @anyforge/control init")+" first.");return}let n=e.endpoint||t.endpoint;console.log(`Endpoint: ${n}`),console.log(`Company: ${t.companyId}`);let o={model:"claude-3-5-haiku-20241022",max_tokens:1,messages:[{role:"user",content:"Hi"}]},r;try{r=await fetch(`${n}/v1/anthropic/v1/messages`,{method:"POST",headers:{"Content-Type":"application/json","anthropic-version":"2023-06-01",Authorization:`Bearer ${t.apiKey}`},body:JSON.stringify(o)})}catch(i){console.log(re("\u2717 Proxy unreachable: ")+i.message),process.exit(1)}r.status===401&&(console.log(re("\u2717 API key rejected.")+" Re-run "+ie("npx @anyforge/control init")+"."),process.exit(1)),r.status===502&&(console.log(re("\u2717 No BYOK Anthropic key configured for your company.")),console.log(ie(" Add one at ")+n+ie("/settings \u2192 API keys.")),process.exit(1)),r.ok||(console.log(re(`\u2717 Proxy returned ${r.status}: `)+(await r.text()).slice(0,200)),process.exit(1)),console.log(an("\u2714 Proxy reachable, key valid, BYOK configured."))}import{spawn as cn}from"node:child_process";import{readFile as dn}from"node:fs/promises";import{red as q,green as ye,yellow as un,dim as W,bold as ot}from"kleur/colors";async function rt(e){let t=await pn(e.patchFile);t.trim()||(process.stderr.write(q(`Empty patch \u2014 nothing to apply.
22
- `)),process.exit(1));let n=fn(t);process.stderr.write(W(`Patch: ${n.files} ${n.files===1?"file":"files"}, `)+ye(`+${n.added}`)+" "+q(`-${n.removed}`)+`
23
- `);let o=await ve(["apply","--check"],t);if(o.code!==0){if(e.threeWay){let i=await ve(["apply","--3way"],t);i.code!==0&&(process.stderr.write(q(`3-way apply failed:
17
+ `);return await P.writeFile(t,n),!0}function kr(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Cr(t){try{return new URL(t).host}catch{return null}}function ji(t){try{return Pi("git",["rev-parse","--show-toplevel"],{cwd:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return null}}var Bi=2e3,Gi=900*1e3;async function _r(t){let{endpoint:e}=t;console.log(Z("AnyForge Control \u2014 device authorisation")),console.log(L(` endpoint: ${e}`));let n=process.cwd(),r=Tr(n),o=r?Ji(r):null,s=await(await fetch(`${e}/api/cli/init`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({repo:o})})).json();if(!s.ok||!s.data)throw new Error(`Could not start the authorisation flow: ${s.error??"unknown error"}`);let{code:a,authUrl:c,pollUrl:p}=s.data;console.log(),console.log("Open this URL in your browser to authorise:"),console.log(` ${Pt(c)}`),console.log(L(` Code: ${a}`)),console.log();try{await Di(c)}catch{}let l=await zi({pollUrl:p,endpoint:e});await vr(l),console.log(Ze("\u2714")+" API key saved to "+L("~/.anyforge/credentials"));let u=await Sr();if(u.isSubscription&&!t.force){await Wi(u,e,l.apiKey);return}let v=process.cwd(),x=Tr(v),k=await Ki(x),b=await xr({endpoint:e,apiKey:l.apiKey,scope:k,repoRoot:x});b.length===0?(console.log(xe("No agent configs were patched.")),console.log(L(" Set ANTHROPIC_BASE_URL="+e+"/v1/anthropic and")),console.log(L(" ANTHROPIC_AUTH_TOKEN=<your AnyForge key> manually if needed."))):Hi(b),console.log(),u.isSubscription&&t.force&&(console.log(xe(` \u26A0 --force: your Claude ${u.plan??""} subscription login is now shadowed`)),console.log(xe(" by the AnyForge token \u2014 Claude Code will use the BYOK proxy, which")),console.log(xe(" needs an Anthropic API key on your company. Run ")+Z("anyforge logout")),console.log(xe(" to revert and restore your subscription."))),k==="repo"&&(console.log(L(" Re-run this command in another repo to patch it too,")),console.log(L(' or run from any directory and choose "global" to install system-wide.'))),console.log(Z("Done.")+" Visit "+Pt(`${e}/usage`)+" to see your traffic.")}async function zi(t){let e=Date.now();process.stdout.write("Waiting for browser authorisation\u2026 ");let n=0;for(;Date.now()-e<Gi;){await new Promise(i=>setTimeout(i,Bi)),process.stdout.write("."),n+=1,n>40&&(process.stdout.write(`
18
+ `),n=0);let o=await(await fetch(t.pollUrl)).json();if(o.status==="authorized"&&o.data)return process.stdout.write(" "+Ze("done")+`
19
+ `),{apiKey:o.data.apiKey,companyId:o.data.companyId,endpoint:t.endpoint,savedAt:new Date().toISOString()};if(o.status==="expired"||o.status==="not_found")throw process.stdout.write(`
20
+ `),new Error(`Authorisation ${o.status}. Re-run \`anyforge init\`.`)}throw new Error("Authorisation timed out (15 min). Re-run `anyforge init`.")}async function Wi(t,e,n){let r=t.email?` (${t.email})`:"";console.log(),console.log(xe(` \u26A0 Claude ${t.plan??""} subscription login detected${r}.`)),console.log(),console.log(" AnyForge Control\u2019s inference proxy is "+Z("BYOK-only")+" \u2014 it routes"),console.log(" Claude Code through your own Anthropic API key for metering and"),console.log(" governance. Pointing Claude Code at the proxy would "+Z("override and")),console.log(" "+Z("disable your subscription login")+", so we did NOT patch your config."),console.log(),console.log(" Instead, use your subscription for inference and add the "+Z("AnyForge")),console.log(" "+Z("MCP server")+" for control (HIL approvals, work items, crews):"),console.log();let o=`claude mcp add anyforge --env ANYFORGE_API_KEY=${n} -- npx -y @anyforge/mcp-server`;if((await yn({type:"confirm",name:"go",message:"Add the AnyForge MCP server to Claude Code now?",initial:!0})).go)try{vn("claude",["mcp","add","anyforge","--env",`ANYFORGE_API_KEY=${n}`,"--","npx","-y","@anyforge/mcp-server"],{stdio:"inherit"}),console.log(Ze(" \u2714")+" AnyForge MCP server added to Claude Code.")}catch{console.log(xe(" Could not run `claude mcp add` automatically (is the `claude` CLI on PATH?).")),console.log(L(" Add it manually:")),console.log(" "+o)}else console.log(L(" Add it later with:")),console.log(" "+o);if(console.log(),(await yn({type:"confirm",name:"go",message:"Send Claude Code usage to your AnyForge dashboard? (token counts only \u2014 no prompt content)",initial:!0})).go){let a=await Ir({endpoint:e,apiKey:n});a&&(console.log(Ze(" \u2714")+" Usage telemetry enabled \u2014 view it at "+Pt(`${e}/usage`)),console.log(L(" (written to "+a.path+"; `anyforge logout` removes it)")))}else console.log(L(" Skipped \u2014 your subscription usage stays local to Claude Code."));console.log(),console.log(L(" Want the metered BYOK proxy instead? Re-run with ")+Z("--force")+L(" using an")),console.log(L(" Anthropic API key on your company (this disables your subscription in")),console.log(L(" Claude Code; ")+Z("anyforge logout")+L(" reverts it).")),console.log(),console.log(Z("Done.")+" Visit "+Pt(`${e}/usage`)+" to see your traffic.")}function Tr(t){try{return vn("git",["rev-parse","--show-toplevel"],{cwd:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return null}}async function Ki(t){return t?(await yn({type:"select",name:"scope",message:"Where should AnyForge patch Claude Code?",choices:[{title:`This repo only (${t}/.claude/settings.local.json + .env)`,value:"repo"},{title:"Globally (~/.claude/settings.json)",value:"global"}],initial:0})).scope??"repo":(console.log(L(" Not a git repo root \u2014 installing globally.")),console.log(L(" Run this from a repo root if you want a per-repo install instead.")),"global")}function Hi(t){console.log(Ze("\u2714")+" Patched "+t.length+" agent config"+(t.length===1?"":"s")+":");for(let e of t)console.log(` ${L("\u2022")} ${e.label} ${L("("+e.path+")")}`)}function Ji(t){try{let e=vn("git",["remote","get-url","origin"],{cwd:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim(),n=e.match(/^git@[\w.-]+:([\w.-]+\/[\w.-]+?)(?:\.git)?$/);if(n)return n[1];let r=e.match(/^https?:\/\/[\w.-]+\/([\w.-]+\/[\w.-]+?)(?:\.git)?$/);return r?r[1]:null}catch{return null}}import{dim as bn,yellow as Xi,bold as Zi}from"kleur/colors";var m={OK:0,FAILURE:1,USAGE:2,NO_CREDENTIALS:3,API_ERROR:4,INTERRUPTED:130},f=class extends Error{exitCode;hint;constructor(e,n=m.FAILURE,r){super(e),this.name="CliError",this.exitCode=n,this.hint=r}},y=class extends f{constructor(e,n){super(e,m.USAGE,n),this.name="UsageError"}},oe=class extends f{constructor(){super("Not authenticated \u2014 no ANYFORGE_API_TOKEN and no saved credential.",m.NO_CREDENTIALS,"Run `npx @anyforge/control init`, or set ANYFORGE_API_TOKEN for unattended use."),this.name="NotAuthenticatedError"}},be=class extends f{code;status;constructor(e,n,r){super(r?`${e}: ${r}`:e,m.API_ERROR,Yi(e,n)),this.name="ControlApiError",this.code=e,this.status=n}};function Yi(t,e){if(t==="missing_api_key"||t==="invalid_api_key"||t==="invalid_token"||e===401)return"The key was rejected. Re-run `npx @anyforge/control init`.";if(t==="insufficient_scope"||e===403)return"This key lacks the scope for that command. Run `anyforge whoami` to see its scopes.";if(e===404)return"No such id in this company. Check it with the matching list command.";if(e===429)return"Rate limited. Retry after a short pause."}var $r="https://crew.anyforge.ai";async function Fe(t){let e=process.env.ANYFORGE_API_TOKEN,n=t||process.env.ANYFORGE_API_URL;if(e)return{apiKey:e,endpoint:n||$r};let r=await ve();if(r?.apiKey)return{apiKey:r.apiKey,endpoint:n||r.endpoint||$r};throw new oe}async function T(t,e,n,r){let o=`${t.endpoint.replace(/\/$/,"")}/api/v1/control${n}`,i;try{i=await fetch(o,{method:e,headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.apiKey}`},...r!==void 0?{body:JSON.stringify(r)}:{}})}catch(a){throw new f(`Cannot reach ${t.endpoint}: ${a instanceof Error?a.message:String(a)}`,m.FAILURE,"Check your network, or pass --endpoint if this workspace is self-hosted.")}let s;try{s=await i.json()}catch{throw new f(`Non-JSON response from ${o} (HTTP ${i.status}).`,m.FAILURE)}if(!i.ok||s.ok===!1){let a=typeof s.error=="string"?s.error:`http_${i.status}`,c=typeof s.message=="string"?s.message:typeof s.detail=="string"?s.detail:void 0;throw new be(a,i.status,c)}return s.data}function H(t){let e=Object.entries(t).filter(([,n])=>n!==void 0&&n!=="").map(([n,r])=>`${encodeURIComponent(n)}=${encodeURIComponent(String(r))}`);return e.length?`?${e.join("&")}`:""}async function et(t){return T(t,"GET","/me")}function On(t,e,n,r){let o=t?.entityLabels?.[e]?.[n];return typeof o=="string"&&o.trim()?o:r}async function Pr(t){try{return await et(t)}catch{return null}}function Lr(t){return[...t].length}function Ne(t,e){if(e<=0)return"";let n=[...t];return n.length<=e?t:e===1?"\u2026":`${n.slice(0,e-1).join("")}\u2026`}function z(t,e){if(t.length===0)return"";let n=t.map(i=>e.map(s=>{let a=s.value(i)??"";return s.max?Ne(a,s.max):a})),r=e.map((i,s)=>Math.max(Lr(i.header),...n.map(a=>Lr(a[s])))),o=i=>i.map((s,a)=>a===i.length-1?s:s.padEnd(r[a]+2)).join("").replace(/\s+$/,"");return[o(e.map(i=>i.header)),...n.map(o)].join(`
21
+ `)}function E(t){return JSON.stringify(t,null,2)}function Fr(t,e=Date.now()){let n=Qi(t);if(n===null)return"\u2014";let r=Math.max(0,Math.floor((e-n)/1e3));if(r<60)return`${r}s`;let o=Math.floor(r/60);if(o<60)return`${o}m`;let i=Math.floor(o/60);return i<48?`${i}h`:`${Math.floor(i/24)}d`}function Qi(t){if(typeof t=="number")return Number.isFinite(t)?t:null;if(typeof t=="string"){let e=Date.parse(t);return Number.isNaN(e)?null:e}return null}function C(t,e,n="\u2014"){let r=t[e];return typeof r=="string"&&r.trim()?r:typeof r=="number"&&Number.isFinite(r)?String(r):n}function G(t,e,n="\u2014"){for(let r of e){let o=t[r];if(typeof o=="string"&&o.trim())return o}return n}function ie(t){let e=Array.isArray(t)?t:t&&typeof t=="object"?Vi(t):null;return e?e.filter(n=>!!n&&typeof n=="object"&&!Array.isArray(n)):[]}function Vi(t){for(let e of["items","results","rows","data"])if(Array.isArray(t[e]))return t[e];return null}function Lt(t,e,n){let r=t?`last ${t}d`:"all time";return!e||!n?r:`last ${Math.max(1,Math.round((Date.now()-n)/864e5))}d of ${t?`${t}d`:"all time"} \u2014 read cap`}async function Nr(t){let e=await Fe(t.endpoint),n=await et(e);if(t.json){console.log(E(n));return}console.log(`Company: ${Zi(n.companyName??n.companyId)}${n.companyName?bn(` (${n.companyId})`):""}`),console.log(`Endpoint: ${e.endpoint}`),console.log(`Scopes: ${n.scopes.length?n.scopes.join(", "):Xi("none")}`),n.userId&&console.log(`User: ${n.userId}`);let r=await ve();console.log(r?bn(`Key tail: \u2026${r.apiKey.slice(-4)} \xB7 saved ${new Date(r.savedAt).toLocaleString()}`):bn("Key from: ANYFORGE_API_TOKEN (no saved credential file)"))}import{green as qr,dim as Ft,yellow as es}from"kleur/colors";async function Mr(){let t=await ve(),e=await Or(),{reverted:n}=await Er({endpoint:t?.endpoint??null});if(console.log(e?qr("\u2714")+" Local credentials cleared.":es("No credentials were saved on this machine.")),n.length){console.log(qr("\u2714")+" Reverted patched configs:");for(let r of n)console.log(` ${Ft("\u2022")} ${r}`);console.log(Ft(" Your previous Claude Code login (if any) is restored."))}else console.log(Ft(" No AnyForge config patches found to revert."));console.log(Ft(" Revoke the key server-side at /settings \u2192 API keys."))}import{green as ts,red as Nt,yellow as ns,dim as qt}from"kleur/colors";async function Ur(t){let e=await ve();if(!e){console.log(ns("Not logged in.")+" Run "+qt("npx @anyforge/control init")+" first.");return}let n=t.endpoint||e.endpoint;console.log(`Endpoint: ${n}`),console.log(`Company: ${e.companyId}`);let r={model:"claude-3-5-haiku-20241022",max_tokens:1,messages:[{role:"user",content:"Hi"}]},o;try{o=await fetch(`${n}/v1/anthropic/v1/messages`,{method:"POST",headers:{"Content-Type":"application/json","anthropic-version":"2023-06-01",Authorization:`Bearer ${e.apiKey}`},body:JSON.stringify(r)})}catch(i){console.log(Nt("\u2717 Proxy unreachable: ")+i.message),process.exit(1)}o.status===401&&(console.log(Nt("\u2717 API key rejected.")+" Re-run "+qt("npx @anyforge/control init")+"."),process.exit(1)),o.status===502&&(console.log(Nt("\u2717 No BYOK Anthropic key configured for your company.")),console.log(qt(" Add one at ")+n+qt("/settings \u2192 API keys.")),process.exit(1)),o.ok||(console.log(Nt(`\u2717 Proxy returned ${o.status}: `)+(await o.text()).slice(0,200)),process.exit(1)),console.log(ts("\u2714 Proxy reachable, key valid, BYOK configured."))}import{spawn as rs}from"node:child_process";import{readFile as os}from"node:fs/promises";import{red as tt,green as Rn,yellow as is,dim as nt,bold as jr}from"kleur/colors";async function Dr(t){let e=await ss(t.patchFile);e.trim()||(process.stderr.write(tt(`Empty patch \u2014 nothing to apply.
22
+ `)),process.exit(1));let n=as(e);process.stderr.write(nt(`Patch: ${n.files} ${n.files===1?"file":"files"}, `)+Rn(`+${n.added}`)+" "+tt(`-${n.removed}`)+`
23
+ `);let r=await An(["apply","--check"],e);if(r.code!==0){if(t.threeWay){let i=await An(["apply","--3way"],e);i.code!==0&&(process.stderr.write(tt(`3-way apply failed:
24
24
  `)+i.stderr+`
25
- `),process.exit(1)),process.stderr.write(un(`\u2713 Applied via 3-way merge \u2014 review conflicts before committing.
26
- `));return}process.stderr.write(q(`Patch does not apply cleanly:
27
- `)+o.stderr+`
28
- `),process.stderr.write(W("Try ")+ot("--3way")+W(` to attempt a 3-way merge against the index.
29
- `)),process.exit(1)}if(e.dryRun){process.stderr.write(ye(`\u2713 Patch would apply cleanly.
30
- `));return}let r=await ve(["apply"],t);r.code!==0&&(process.stderr.write(q(`git apply failed:
25
+ `),process.exit(1)),process.stderr.write(is(`\u2713 Applied via 3-way merge \u2014 review conflicts before committing.
26
+ `));return}process.stderr.write(tt(`Patch does not apply cleanly:
31
27
  `)+r.stderr+`
32
- `),process.exit(1)),process.stderr.write(ye(`\u2713 Applied.
33
- `)),n.files>0&&process.stderr.write(W(" Run ")+ot("git diff --stat")+W(` to review.
34
- `))}async function pn(e){if(e)return await dn(e,"utf8");let t=[];for await(let n of process.stdin)t.push(n);return Buffer.concat(t).toString("utf8")}function ve(e,t){return new Promise((n,o)=>{let r=cn("git",e,{stdio:["pipe","pipe","pipe"]}),i="",s="";r.stdout.on("data",a=>{i+=a.toString()}),r.stderr.on("data",a=>{s+=a.toString()}),r.on("error",o),r.on("close",a=>n({code:a??0,stdout:i,stderr:s})),r.stdin.write(t),r.stdin.end()})}function fn(e){let t=0,n=0,o=0;for(let r of e.split(`
35
- `))if(r.startsWith("diff --git"))t+=1;else{if(r.startsWith("+++")||r.startsWith("---"))continue;r.startsWith("+")?n+=1:r.startsWith("-")&&(o+=1)}return t===0&&(n>0||o>0)&&(t=(e.match(/^\+\+\+ /gm)??[]).length),{files:t,added:n,removed:o}}import{bold as gn,dim as be,green as mn,red as hn}from"kleur/colors";async function it(e){let t=await p(e.creds,"GET",`/hil${E({limit:e.limit})}`);if(e.json){console.log(d(t));return}let n=S(t);if(n.length===0){console.log(be("No approvals pending."));return}console.log(v(n,[{header:"ID",value:o=>u(o,"id")},{header:"CREW",value:o=>u(o,"crewId"),max:24},{header:"AGE",value:o=>Qe(o.requestedAt)},{header:"SUMMARY",value:o=>w(o,["planSummary","summary","title","description"]),max:60}])),console.log(be(`
36
- ${n.length} pending \xB7 approve with `)+gn("anyforge approve <id>"))}async function Re(e){if(!e.approvalId)throw new c(`${e.decision==="APPROVED"?"approve":"reject"} requires an approval id.`,"List them with `anyforge approvals`.");let t=await p(e.creds,"POST",`/hil/${encodeURIComponent(e.approvalId)}`,{decision:e.decision,...e.notes?{notes:e.notes}:{}});if(e.json){console.log(d(t));return}let n=e.decision==="APPROVED"?mn("Approved"):hn("Rejected");console.log(`${n} ${e.approvalId}`);let o=t&&typeof t=="object"?t.signature:void 0;typeof o=="string"&&o&&console.log(be(`Audit signature: ${o}`))}import{bold as wn,dim as Ee}from"kleur/colors";async function st(e){let t=await p(e.creds,"GET",`/crews${E({status:e.status,activeOnly:e.activeOnly,limit:e.limit})}`);if(e.json){console.log(d(t));return}let n=S(t);if(n.length===0){console.log(Ee(e.activeOnly?"No active crews.":"No crews."));return}console.log(v(n,[{header:"ID",value:o=>u(o,"id")},{header:"STATUS",value:o=>u(o,"status")},{header:"TASK",value:o=>w(o,["task"],"\u2014"),max:52},{header:"NOTE",value:o=>w(o,["errorMessage"],""),max:40}]))}async function at(e){if(!e.crewId)throw new c("crew requires a crew id.","List them with `anyforge crews`.");let t=await p(e.creds,"GET",`/crews/${encodeURIComponent(e.crewId)}${E({messages:e.messages})}`);if(e.json){console.log(d(t));return}let n=t?.crew&&typeof t.crew=="object"?t.crew:t;console.log(`${wn(yn(w(n,["taskDescription"],u(n,"id"))))}`),console.log(`Status: ${u(n,"status")}`),console.log(`Repo: ${w(n,["githubRepo"])}`);let o=u(n,"prUrl","");o&&console.log(`PR: ${o}`);let r=u(n,"url","");r&&console.log(`Console: ${r}`);let i=S(t?.chat);if(i.length>0){console.log(Ee(`
37
- Last ${i.length} messages:`));for(let s of i){let a=w(s,["agentRole","role","author","agent","type"],"?"),l=w(s,["content","body","text","summary"],"");l&&console.log(`${Ee(`[${a}]`)} ${M(l.replace(/\s+/g," "),160)}`)}}}async function ke(e){if(!e.crewId)throw new c(`${e.action} requires a crew id.`,"List them with `anyforge crews`.");let t=await p(e.creds,"POST",`/crews/${encodeURIComponent(e.crewId)}/${e.action}`);if(e.json){console.log(d(t));return}console.log(`${e.action==="halt"?"Halted":"Resumed"} ${e.crewId}`)}function yn(e){return M(e.split(/\r?\n/).find(t=>t.trim())?.trim()??e,80)}import{bold as vn,dim as U,red as lt,yellow as dt}from"kleur/colors";var bn=new Set(["COMPLETE","HALTED","REJECTED","CANCELLED"]),Rn=new Set(["HIL_PENDING","HIL_SHIP","PERMISSION_PENDING","PAUSED"]),En={HIL_PENDING:"Decide it with `anyforge approvals`, or from the Slack alert.",HIL_SHIP:"Decide it with `anyforge approvals`, or from the Slack alert.",PERMISSION_PENDING:"The agent asked for a tool outside its profile. Allow or deny it in the Console.",PAUSED:"Restart it with `anyforge resume <id>`."};async function ut(e){if(!e.workItemId)throw new c("crew start requires a work item id.","Find one with `anyforge work --status READY`.");let t=await p(e.creds,"PATCH",`/work/${encodeURIComponent(e.workItemId)}`,{action:"dispatch",...e.profileId?{runProfileId:e.profileId}:{}});if(e.json&&!e.follow){console.log(d(t));return}let n=typeof t?.crewId=="string"?t.crewId:"";if(!n)throw new y("Dispatch was accepted but returned no crew id.",R.FAILURE,"Check `anyforge crews --active` to see whether one started.");for(let o of t?.warnings??[])console.error(dt(`warning: ${o}`));if(!e.follow){console.log(`Started ${vn(n)} on ${e.workItemId}`),console.log(U(`Follow it with \`anyforge crew follow ${n}\``));return}await Ce({creds:e.creds,json:e.json,crewId:n,intervalSeconds:e.intervalSeconds})}var kn=5,Cn=10,An=120,ct=30;async function Ce(e){if(!e.crewId)throw new c("crew follow requires a crew id.","List them with `anyforge crews --active`.");let t=Math.min(Math.max(e.intervalSeconds??Cn,kn),An)*1e3,n=Date.now()+ct*6e4,o=new Set,r="",i=!1;for(;;){let s=await p(e.creds,"GET",`/crews/${encodeURIComponent(e.crewId)}?messages=50`),a=s?.crew??{},l=u(a,"status","");for(let k of s?.chat??[]){let b=u(k,"id","");!b||o.has(b)||(o.add(b),i&&!e.json&&In(k))}if(i=!0,l&&l!==r&&(e.json||console.log(U(`\u2500\u2500 ${l}`)),r=l),Rn.has(l)){if(e.json)console.log(d({crewId:e.crewId,status:l,waiting:!0,url:u(a,"url","")}));else{console.log(dt(`
38
- ${l} \u2014 this crew is waiting on a person.`));let k=En[l];k&&console.log(U(k));let b=u(a,"url","");b&&console.log(U(b))}return}if(bn.has(l)){if(e.json)console.log(d({crewId:e.crewId,status:l,waiting:!1,url:u(a,"url","")}));else{let k=`
39
- ${l}`;console.log(l==="COMPLETE"?k:lt(k));let b=u(a,"prUrl","");b&&console.log(U(b));let L=u(a,"errorMessage","");L&&console.log(lt(L))}if(l!=="COMPLETE")throw new y(`Crew ${e.crewId} ended ${l}.`,R.FAILURE);return}if(Date.now()>=n)throw new y(`Still ${l||"running"} after ${ct} minutes \u2014 giving up watching.`,R.FAILURE,`The crew is unaffected. Check it with \`anyforge crew ${e.crewId}\`.`);await Sn(t)}}function In(e){let t=u(e,"agentRole","")||u(e,"type","?"),n=Pn(e,["content","text","summary"]);n&&console.log(`${U(`[${t}]`)} ${M(n.replace(/\s+/g," "),160)}`)}function Pn(e,t){for(let n of t){let o=e[n];if(typeof o=="string"&&o.trim())return o.trim()}return""}function Sn(e){return new Promise(t=>{setTimeout(t,e)})}import{dim as se}from"kleur/colors";var ft=100;function pt(e){let t=e&&typeof e=="object"?e:{};return{total:typeof t.total=="number"?t.total:null,hasMore:t.hasMore===!0,cursor:typeof t.nextCursor=="string"?t.nextCursor:null}}async function On(e){let t=s=>`/work${E({status:e.status,type:e.type,priority:e.priority,initiative:e.initiative,program:e.program,search:e.search,assignee:e.assignee,includeArchived:e.includeArchived,limit:e.limit,cursor:s})}`,n=await p(e.creds,"GET",t()),o=S(n),r=pt(n),i=1;if(!e.all)return{rows:o,total:r.total,hasMore:r.hasMore,raw:n,pagesFetched:i};for(;r.hasMore&&r.cursor&&i<ft;){let s=await p(e.creds,"GET",t(r.cursor));o.push(...S(s)),r=pt(s),i+=1}return{rows:o,total:r.total,hasMore:r.hasMore,raw:n,pagesFetched:i}}async function gt(e){let t=await On(e);if(e.json){console.log(d(e.all?{items:t.rows,total:t.total,hasMore:t.hasMore,nextCursor:null}:t.raw));return}let n=t.rows,o=await Ve(e.creds),r=he(o,"work_item","plural","Work items");if(n.length===0){console.log(se(`No ${r.toLowerCase()} match that filter.`));return}console.log(v(n,[{header:"ID",value:l=>u(l,"id")},{header:"PRI",value:l=>u(l,"priority","")},{header:"TYPE",value:l=>u(l,"type","")},{header:"STATUS",value:l=>u(l,"status")},{header:"TITLE",value:l=>w(l,["title","name","summary"]),max:58}]));let i=(n.length===1?he(o,"work_item","singular","work item"):r).toLowerCase(),s=t.total!==null&&t.total!==n.length?`${n.length} of ${t.total} ${r.toLowerCase()}`:`${n.length} ${i}`,a=t.hasMore?e.all?se(` \u2014 stopped after ${ft} pages; narrow the filter`):se(" \u2014 pass --all for the rest"):"";console.log(`${se(`
40
- ${s}`)}${a}`)}import{bold as _n,dim as ae}from"kleur/colors";async function mt(e){let t=await p(e.creds,"GET","/workflows");if(e.json){console.log(d(t));return}let n=S(t);if(n.length===0){console.log(ae("No workflows defined."));return}console.log(v(n,[{header:"ID",value:o=>u(o,"id")},{header:"STATUS",value:o=>w(o,["status","state"],"")},{header:"NAME",value:o=>w(o,["name","title"]),max:58}])),console.log(ae(`
41
- Dispatch with `)+_n("anyforge run <id>"))}async function ht(e){if(!e.workflowId)throw new c("run requires a workflow id.","List them with `anyforge workflows`.");let t;if(e.input!==void 0){try{t=JSON.parse(e.input)}catch(r){throw new c(`--input is not valid JSON: ${r instanceof Error?r.message:String(r)}`,`Example: --input '{"repo":"acme/web"}'`)}if(!t||typeof t!="object"||Array.isArray(t))throw new c("--input must be a JSON object.",`Example: --input '{"repo":"acme/web"}'`)}let n=await p(e.creds,"POST",`/workflows/${encodeURIComponent(e.workflowId)}/run`,t===void 0?void 0:{inputs:t});if(e.json){console.log(d(n));return}let o=w(n??{},["id","runId"],"");console.log(`Queued run ${o||ae("(id not returned)")}`),console.log(ae("Runs pause at gate steps for an operator decision."))}import{dim as xn}from"kleur/colors";async function wt(e){let t=e.question?.trim();if(!t)throw new c("ask requires a question.",'Example: anyforge ask "which initiatives slipped this cycle?"');!e.json&&process.stderr.isTTY&&process.stderr.write(xn(`Asking the Oracle (this can take a few minutes)\u2026
42
- `));let n=await p(e.creds,"POST","/oracle",{question:t,...e.context?{context:e.context}:{}});if(e.json){console.log(d(n));return}let o=n&&typeof n=="object"?["answer","text","content","response"].map(r=>n[r]).find(r=>typeof r=="string"&&r.trim()):void 0;console.log(typeof o=="string"?o:d(n))}import{bold as Ae,dim as yt,green as Tn,yellow as $n}from"kleur/colors";function Ln(e){return typeof e=="number"?`${Math.round(e*100)}%`:"\u2014"}async function vt(e){let t=await p(e.creds,"GET",`/backtest-corpus${E({days:e.days})}`);if(e.json){console.log(d(t));return}let n=t.incidentsInWindow??0,o=t.backTestable??0,r=t.windowDays?`last ${t.windowDays}d`:"all time";console.log(`${Ae(String(o))} of ${Ae(String(n))} incidents are back-testable `+yt(`(${Ln(t.coverage)} \xB7 ${r})`)),t.viable?console.log(Tn("Enough to run a back-test.")):console.log($n(`Not yet viable \u2014 ${t.shortfall??0} more needed.`));let i=(t.excluded??[]).filter(s=>s.count>0);i.length>0&&console.log(v(i,[{header:"EXCLUDED",value:s=>String(s.count)},{header:"REASON",value:s=>s.label,max:56}])),t.biggestGap&&console.log(yt(`
43
- Biggest fixable gap: `)+Ae(`${t.biggestGap.label} (${t.biggestGap.count})`))}import{bold as le,dim as H,green as Nn,yellow as bt}from"kleur/colors";var Ie=e=>typeof e=="number"?`${Math.round(e*1e3)/10}%`:"\u2014";async function Rt(e){let t=await p(e.creds,"GET",`/review-interception${E({days:e.days})}`);if(e.json){console.log(d(t));return}let n=ne(t.windowDays,t.truncated,t.oldestReviewMs),o=i=>{console.log(i)};if(o(`${le(String(t.seriousRaised??0))} serious findings raised across ${le(String(t.prsReviewed??0))} pull requests ${H(`(${n})`)}`),t.verdict==="measured")o(`${Nn(String(t.disappearedSerious??0))} no longer raised by the next round \u2014 ${le(Ie(t.interceptionFloor))} to ${le(Ie(t.interceptionCeiling))}`),o(H(`floor counts every unmeasured finding as unresolved \xB7 ceiling is over the ${t.measurableSerious??0} that had a later round`));else{o(bt("Not enough findings had a later round to measure a rate."));for(let i of t.inconclusiveReasons??[])o(H(` ${i}`))}let r=(t.byLens??[]).filter(i=>i.serious>0);if(r.length>0&&o(v(r,[{header:"LENS",value:i=>i.lens,max:16},{header:"RAISED",value:i=>String(i.raised)},{header:"SERIOUS",value:i=>String(i.serious)},{header:"CHECKED",value:i=>String(i.measurable)},{header:"GONE",value:i=>String(i.disappeared)}])),t.truncated){let i=t.oldestReviewMs?new Date(t.oldestReviewMs).toISOString().slice(0,10):null;o(bt(`
44
- Read cap reached \u2014 these are the most recent reviews${i?` (back to ${i})`:""}, not the whole window. Every count is a lower bound.`))}t.reviews&&t.skippedShare!=null&&t.skippedShare>0&&o(H(`
45
- ${t.reviews.skipped} of ${t.reviews.total} reviews were skipped (${Ie(t.skippedShare)}) \u2014 drafts, size caps or path policy.`));for(let i of t.caveats??[])o(H(`
46
- ${i}`))}import{bold as Y,dim as D,green as Fn,red as Dn,yellow as Pe}from"kleur/colors";var jn=e=>typeof e=="number"?`${Math.round(e*1e3)/10}%`:"\u2014";function Se(e){if(typeof e!="number")return"\u2014";if(e<60)return`${e}m`;if(e<1440){let n=Math.floor(e/60);return`${n}h ${Math.round(e-n*60)}m`}let t=Math.floor(e/1440);return`${t}d ${Math.round((e-t*1440)/60)}h`}var Mn={capacity:"a limit fired",policy:"configured off",by_design:"correctly declined",unclassified:"reason not recognised"};async function Et(e){let t=await p(e.creds,"GET",`/review-coverage${E({days:e.days})}`);if(e.json){console.log(d(t));return}let n=s=>{console.log(s)},o=ne(t.windowDays,t.truncated,t.oldestRecordMs);n(`${Y(String(t.pullRequestsReviewed??0))} of ${Y(String(t.pullRequestsSeen??0))} recorded pull requests reviewed ${t.coverageRate!=null?Y(`(${jn(t.coverageRate)})`):""} ${D(`(${o})`)}`),t.repositoriesReviewed&&n(`across ${Y(String(t.repositoriesReviewed))} repositories`),n(D("recorded = pull requests AnyForge was enabled to watch, not every pull request you opened")),t.medianLatencyMinutes!=null?(n(`
47
- Feedback reached the author in ${Fn(Se(t.medianLatencyMinutes))} median, ${Y(Se(t.p90LatencyMinutes))} at p90 ${D(`(${t.latencySamples??0} reviews)`)}`),t.medianOnOpenLatencyMinutes!=null&&n(D(` ${Se(t.medianOnOpenLatencyMinutes)} median on pull-request open (${t.onOpenSamples??0} reviews, no quiet period in the way)`))):n(Pe(`
48
- Not enough timed reviews to report a latency figure.`));let r=t.capacityGapPullRequests??0;r>0&&n(`
49
- ${Dn(String(r))} pull requests went unreviewed because a limit fired `+D("\u2014 a cap or a diff over the size ceiling, not a decision anyone made"));let i=t.bySkipClass??[];i.length>0&&n(v(i,[{header:"WHY UNREVIEWED",value:s=>Mn[s.skipClass]??s.skipClass,max:24},{header:"PRS",value:s=>String(s.pullRequests)},{header:"REASONS",value:s=>s.reasons.join(", "),max:46}])),t.failedOnlyPullRequests&&n(D(`${t.failedOnlyPullRequests} pull requests had every review attempt error out.`));for(let s of t.inconclusiveReasons??[])n(Pe(`
50
- ${s}`));if(t.truncated){let s=t.oldestRecordMs?new Date(t.oldestRecordMs).toISOString().slice(0,10):null;n(Pe(`
51
- Read cap reached \u2014 these are the most recent records${s?` (back to ${s})`:""}, not the whole window. Every count is a lower bound.`))}for(let s of t.caveats??[])n(D(`
52
- ${s}`))}import{promises as B}from"node:fs";import{homedir as qn}from"node:os";import{dirname as Ct,join as j}from"node:path";import{dim as At,green as Wn,yellow as Hn}from"kleur/colors";var O="anyforge-platform",Un="Orient an agent to AnyForge's structure, delivery model, surfaces, live control tools, and safety boundaries.",kt="anyforge://guide/platform",Gn="text/markdown";var ce={"SKILL.md":`---
28
+ `),process.stderr.write(nt("Try ")+jr("--3way")+nt(` to attempt a 3-way merge against the index.
29
+ `)),process.exit(1)}if(t.dryRun){process.stderr.write(Rn(`\u2713 Patch would apply cleanly.
30
+ `));return}let o=await An(["apply"],e);o.code!==0&&(process.stderr.write(tt(`git apply failed:
31
+ `)+o.stderr+`
32
+ `),process.exit(1)),process.stderr.write(Rn(`\u2713 Applied.
33
+ `)),n.files>0&&process.stderr.write(nt(" Run ")+jr("git diff --stat")+nt(` to review.
34
+ `))}async function ss(t){if(t)return await os(t,"utf8");let e=[];for await(let n of process.stdin)e.push(n);return Buffer.concat(e).toString("utf8")}function An(t,e){return new Promise((n,r)=>{let o=rs("git",t,{stdio:["pipe","pipe","pipe"]}),i="",s="";o.stdout.on("data",a=>{i+=a.toString()}),o.stderr.on("data",a=>{s+=a.toString()}),o.on("error",r),o.on("close",a=>n({code:a??0,stdout:i,stderr:s})),o.stdin.write(e),o.stdin.end()})}function as(t){let e=0,n=0,r=0;for(let o of t.split(`
35
+ `))if(o.startsWith("diff --git"))e+=1;else{if(o.startsWith("+++")||o.startsWith("---"))continue;o.startsWith("+")?n+=1:o.startsWith("-")&&(r+=1)}return e===0&&(n>0||r>0)&&(e=(t.match(/^\+\+\+ /gm)??[]).length),{files:e,added:n,removed:r}}import{bold as cs,dim as kn,green as ls,red as ds}from"kleur/colors";async function Br(t){let e=await T(t.creds,"GET",`/hil${H({limit:t.limit})}`);if(t.json){console.log(E(e));return}let n=ie(e);if(n.length===0){console.log(kn("No approvals pending."));return}console.log(z(n,[{header:"ID",value:r=>C(r,"id")},{header:"CREW",value:r=>C(r,"crewId"),max:24},{header:"AGE",value:r=>Fr(r.requestedAt)},{header:"SUMMARY",value:r=>G(r,["planSummary","summary","title","description"]),max:60}])),console.log(kn(`
36
+ ${n.length} pending \xB7 approve with `)+cs("anyforge approve <id>"))}async function xn(t){if(!t.approvalId)throw new y(`${t.decision==="APPROVED"?"approve":"reject"} requires an approval id.`,"List them with `anyforge approvals`.");let e=await T(t.creds,"POST",`/hil/${encodeURIComponent(t.approvalId)}`,{decision:t.decision,...t.notes?{notes:t.notes}:{}});if(t.json){console.log(E(e));return}let n=t.decision==="APPROVED"?ls("Approved"):ds("Rejected");console.log(`${n} ${t.approvalId}`);let r=e&&typeof e=="object"?e.signature:void 0;typeof r=="string"&&r&&console.log(kn(`Audit signature: ${r}`))}import{bold as us,dim as In}from"kleur/colors";async function Gr(t){let e=await T(t.creds,"GET",`/crews${H({status:t.status,activeOnly:t.activeOnly,limit:t.limit})}`);if(t.json){console.log(E(e));return}let n=ie(e);if(n.length===0){console.log(In(t.activeOnly?"No active crews.":"No crews."));return}console.log(z(n,[{header:"ID",value:r=>C(r,"id")},{header:"STATUS",value:r=>C(r,"status")},{header:"TASK",value:r=>G(r,["task"],"\u2014"),max:52},{header:"NOTE",value:r=>G(r,["errorMessage"],""),max:40}]))}async function zr(t){if(!t.crewId)throw new y("crew requires a crew id.","List them with `anyforge crews`.");let e=await T(t.creds,"GET",`/crews/${encodeURIComponent(t.crewId)}${H({messages:t.messages})}`);if(t.json){console.log(E(e));return}let n=e?.crew&&typeof e.crew=="object"?e.crew:e;console.log(`${us(ps(G(n,["taskDescription"],C(n,"id"))))}`),console.log(`Status: ${C(n,"status")}`),console.log(`Repo: ${G(n,["githubRepo"])}`);let r=C(n,"prUrl","");r&&console.log(`PR: ${r}`);let o=C(n,"url","");o&&console.log(`Console: ${o}`);let i=ie(e?.chat);if(i.length>0){console.log(In(`
37
+ Last ${i.length} messages:`));for(let s of i){let a=G(s,["agentRole","role","author","agent","type"],"?"),c=G(s,["content","body","text","summary"],"");c&&console.log(`${In(`[${a}]`)} ${Ne(c.replace(/\s+/g," "),160)}`)}}}async function Sn(t){if(!t.crewId)throw new y(`${t.action} requires a crew id.`,"List them with `anyforge crews`.");let e=await T(t.creds,"POST",`/crews/${encodeURIComponent(t.crewId)}/${t.action}`);if(t.json){console.log(E(e));return}console.log(`${t.action==="halt"?"Halted":"Resumed"} ${t.crewId}`)}function ps(t){return Ne(t.split(/\r?\n/).find(e=>e.trim())?.trim()??t,80)}import{bold as fs,dim as qe,red as Wr,yellow as Hr}from"kleur/colors";var ms=new Set(["COMPLETE","HALTED","REJECTED","CANCELLED"]),gs=new Set(["HIL_PENDING","HIL_SHIP","PERMISSION_PENDING","PAUSED"]),hs={HIL_PENDING:"Decide it with `anyforge approvals`, or from the Slack alert.",HIL_SHIP:"Decide it with `anyforge approvals`, or from the Slack alert.",PERMISSION_PENDING:"The agent asked for a tool outside its profile. Allow or deny it in the Console.",PAUSED:"Restart it with `anyforge resume <id>`."};async function Jr(t){if(!t.workItemId)throw new y("crew start requires a work item id.","Find one with `anyforge work --status READY`.");let e=await T(t.creds,"PATCH",`/work/${encodeURIComponent(t.workItemId)}`,{action:"dispatch",...t.profileId?{runProfileId:t.profileId}:{}});if(t.json&&!t.follow){console.log(E(e));return}let n=typeof e?.crewId=="string"?e.crewId:"";if(!n)throw new f("Dispatch was accepted but returned no crew id.",m.FAILURE,"Check `anyforge crews --active` to see whether one started.");for(let r of e?.warnings??[])console.error(Hr(`warning: ${r}`));if(!t.follow){console.log(`Started ${fs(n)} on ${t.workItemId}`),console.log(qe(`Follow it with \`anyforge crew follow ${n}\``));return}await En({creds:t.creds,json:t.json,crewId:n,intervalSeconds:t.intervalSeconds})}var ws=5,ys=10,vs=120,Kr=30;async function En(t){if(!t.crewId)throw new y("crew follow requires a crew id.","List them with `anyforge crews --active`.");let e=Math.min(Math.max(t.intervalSeconds??ys,ws),vs)*1e3,n=Date.now()+Kr*6e4,r=new Set,o="",i=!1;for(;;){let s=await T(t.creds,"GET",`/crews/${encodeURIComponent(t.crewId)}?messages=50`),a=s?.crew??{},c=C(a,"status","");for(let p of s?.chat??[]){let l=C(p,"id","");!l||r.has(l)||(r.add(l),i&&!t.json&&Os(p))}if(i=!0,c&&c!==o&&(t.json||console.log(qe(`\u2500\u2500 ${c}`)),o=c),gs.has(c)){if(t.json)console.log(E({crewId:t.crewId,status:c,waiting:!0,url:C(a,"url","")}));else{console.log(Hr(`
38
+ ${c} \u2014 this crew is waiting on a person.`));let p=hs[c];p&&console.log(qe(p));let l=C(a,"url","");l&&console.log(qe(l))}return}if(ms.has(c)){if(t.json)console.log(E({crewId:t.crewId,status:c,waiting:!1,url:C(a,"url","")}));else{let p=`
39
+ ${c}`;console.log(c==="COMPLETE"?p:Wr(p));let l=C(a,"prUrl","");l&&console.log(qe(l));let u=C(a,"errorMessage","");u&&console.log(Wr(u))}if(c!=="COMPLETE")throw new f(`Crew ${t.crewId} ended ${c}.`,m.FAILURE);return}if(Date.now()>=n)throw new f(`Still ${c||"running"} after ${Kr} minutes \u2014 giving up watching.`,m.FAILURE,`The crew is unaffected. Check it with \`anyforge crew ${t.crewId}\`.`);await Rs(e)}}function Os(t){let e=C(t,"agentRole","")||C(t,"type","?"),n=bs(t,["content","text","summary"]);n&&console.log(`${qe(`[${e}]`)} ${Ne(n.replace(/\s+/g," "),160)}`)}function bs(t,e){for(let n of e){let r=t[n];if(typeof r=="string"&&r.trim())return r.trim()}return""}function Rs(t){return new Promise(e=>{setTimeout(e,t)})}import{dim as Mt}from"kleur/colors";var Qr=100;function Yr(t){let e=t&&typeof t=="object"?t:{};return{total:typeof e.total=="number"?e.total:null,hasMore:e.hasMore===!0,cursor:typeof e.nextCursor=="string"?e.nextCursor:null}}async function As(t){let e=s=>`/work${H({status:t.status,type:t.type,priority:t.priority,initiative:t.initiative,program:t.program,search:t.search,assignee:t.assignee,includeArchived:t.includeArchived,limit:t.limit,cursor:s})}`,n=await T(t.creds,"GET",e()),r=ie(n),o=Yr(n),i=1;if(!t.all)return{rows:r,total:o.total,hasMore:o.hasMore,raw:n,pagesFetched:i};for(;o.hasMore&&o.cursor&&i<Qr;){let s=await T(t.creds,"GET",e(o.cursor));r.push(...ie(s)),o=Yr(s),i+=1}return{rows:r,total:o.total,hasMore:o.hasMore,raw:n,pagesFetched:i}}async function Vr(t){let e=await As(t);if(t.json){console.log(E(t.all?{items:e.rows,total:e.total,hasMore:e.hasMore,nextCursor:null}:e.raw));return}let n=e.rows,r=await Pr(t.creds),o=On(r,"work_item","plural","Work items");if(n.length===0){console.log(Mt(`No ${o.toLowerCase()} match that filter.`));return}console.log(z(n,[{header:"ID",value:c=>C(c,"id")},{header:"PRI",value:c=>C(c,"priority","")},{header:"TYPE",value:c=>C(c,"type","")},{header:"STATUS",value:c=>C(c,"status")},{header:"TITLE",value:c=>G(c,["title","name","summary"]),max:58}]));let i=(n.length===1?On(r,"work_item","singular","work item"):o).toLowerCase(),s=e.total!==null&&e.total!==n.length?`${n.length} of ${e.total} ${o.toLowerCase()}`:`${n.length} ${i}`,a=e.hasMore?t.all?Mt(` \u2014 stopped after ${Qr} pages; narrow the filter`):Mt(" \u2014 pass --all for the rest"):"";console.log(`${Mt(`
40
+ ${s}`)}${a}`)}import{bold as ks,dim as Ut}from"kleur/colors";async function Xr(t){let e=await T(t.creds,"GET","/workflows");if(t.json){console.log(E(e));return}let n=ie(e);if(n.length===0){console.log(Ut("No workflows defined."));return}console.log(z(n,[{header:"ID",value:r=>C(r,"id")},{header:"STATUS",value:r=>G(r,["status","state"],"")},{header:"NAME",value:r=>G(r,["name","title"]),max:58}])),console.log(Ut(`
41
+ Dispatch with `)+ks("anyforge run <id>"))}async function Zr(t){if(!t.workflowId)throw new y("run requires a workflow id.","List them with `anyforge workflows`.");let e;if(t.input!==void 0){try{e=JSON.parse(t.input)}catch(o){throw new y(`--input is not valid JSON: ${o instanceof Error?o.message:String(o)}`,`Example: --input '{"repo":"acme/web"}'`)}if(!e||typeof e!="object"||Array.isArray(e))throw new y("--input must be a JSON object.",`Example: --input '{"repo":"acme/web"}'`)}let n=await T(t.creds,"POST",`/workflows/${encodeURIComponent(t.workflowId)}/run`,e===void 0?void 0:{inputs:e});if(t.json){console.log(E(n));return}let r=G(n??{},["id","runId"],"");console.log(`Queued run ${r||Ut("(id not returned)")}`),console.log(Ut("Runs pause at gate steps for an operator decision."))}import{dim as xs}from"kleur/colors";async function eo(t){let e=t.question?.trim();if(!e)throw new y("ask requires a question.",'Example: anyforge ask "which initiatives slipped this cycle?"');!t.json&&process.stderr.isTTY&&process.stderr.write(xs(`Asking the Oracle (this can take a few minutes)\u2026
42
+ `));let n=await T(t.creds,"POST","/oracle",{question:e,...t.context?{context:t.context}:{}});if(t.json){console.log(E(n));return}let r=n&&typeof n=="object"?["answer","text","content","response"].map(o=>n[o]).find(o=>typeof o=="string"&&o.trim()):void 0;console.log(typeof r=="string"?r:E(n))}import{bold as Cn,dim as to,green as Is,yellow as Ss}from"kleur/colors";function Es(t){return typeof t=="number"?`${Math.round(t*100)}%`:"\u2014"}async function no(t){let e=await T(t.creds,"GET",`/backtest-corpus${H({days:t.days})}`);if(t.json){console.log(E(e));return}let n=e.incidentsInWindow??0,r=e.backTestable??0,o=e.windowDays?`last ${e.windowDays}d`:"all time";console.log(`${Cn(String(r))} of ${Cn(String(n))} incidents are back-testable `+to(`(${Es(e.coverage)} \xB7 ${o})`)),e.viable?console.log(Is("Enough to run a back-test.")):console.log(Ss(`Not yet viable \u2014 ${e.shortfall??0} more needed.`));let i=(e.excluded??[]).filter(s=>s.count>0);i.length>0&&console.log(z(i,[{header:"EXCLUDED",value:s=>String(s.count)},{header:"REASON",value:s=>s.label,max:56}])),e.biggestGap&&console.log(to(`
43
+ Biggest fixable gap: `)+Cn(`${e.biggestGap.label} (${e.biggestGap.count})`))}import{bold as jt,dim as rt,green as Cs,yellow as ro}from"kleur/colors";var Tn=t=>typeof t=="number"?`${Math.round(t*1e3)/10}%`:"\u2014";async function oo(t){let e=await T(t.creds,"GET",`/review-interception${H({days:t.days})}`);if(t.json){console.log(E(e));return}let n=Lt(e.windowDays,e.truncated,e.oldestReviewMs),r=i=>{console.log(i)};if(r(`${jt(String(e.seriousRaised??0))} serious findings raised across ${jt(String(e.prsReviewed??0))} pull requests ${rt(`(${n})`)}`),e.verdict==="measured")r(`${Cs(String(e.disappearedSerious??0))} no longer raised by the next round \u2014 ${jt(Tn(e.interceptionFloor))} to ${jt(Tn(e.interceptionCeiling))}`),r(rt(`floor counts every unmeasured finding as unresolved \xB7 ceiling is over the ${e.measurableSerious??0} that had a later round`));else{r(ro("Not enough findings had a later round to measure a rate."));for(let i of e.inconclusiveReasons??[])r(rt(` ${i}`))}let o=(e.byLens??[]).filter(i=>i.serious>0);if(o.length>0&&r(z(o,[{header:"LENS",value:i=>i.lens,max:16},{header:"RAISED",value:i=>String(i.raised)},{header:"SERIOUS",value:i=>String(i.serious)},{header:"CHECKED",value:i=>String(i.measurable)},{header:"GONE",value:i=>String(i.disappeared)}])),e.truncated){let i=e.oldestReviewMs?new Date(e.oldestReviewMs).toISOString().slice(0,10):null;r(ro(`
44
+ Read cap reached \u2014 these are the most recent reviews${i?` (back to ${i})`:""}, not the whole window. Every count is a lower bound.`))}e.reviews&&e.skippedShare!=null&&e.skippedShare>0&&r(rt(`
45
+ ${e.reviews.skipped} of ${e.reviews.total} reviews were skipped (${Tn(e.skippedShare)}) \u2014 drafts, size caps or path policy.`));for(let i of e.caveats??[])r(rt(`
46
+ ${i}`))}import{bold as ot,dim as Ie,green as Ts,red as _s,yellow as _n}from"kleur/colors";var $s=t=>typeof t=="number"?`${Math.round(t*1e3)/10}%`:"\u2014";function $n(t){if(typeof t!="number")return"\u2014";if(t<60)return`${t}m`;if(t<1440){let n=Math.floor(t/60);return`${n}h ${Math.round(t-n*60)}m`}let e=Math.floor(t/1440);return`${e}d ${Math.round((t-e*1440)/60)}h`}var Ps={capacity:"a limit fired",policy:"configured off",by_design:"correctly declined",unclassified:"reason not recognised"};async function io(t){let e=await T(t.creds,"GET",`/review-coverage${H({days:t.days})}`);if(t.json){console.log(E(e));return}let n=s=>{console.log(s)},r=Lt(e.windowDays,e.truncated,e.oldestRecordMs);n(`${ot(String(e.pullRequestsReviewed??0))} of ${ot(String(e.pullRequestsSeen??0))} recorded pull requests reviewed ${e.coverageRate!=null?ot(`(${$s(e.coverageRate)})`):""} ${Ie(`(${r})`)}`),e.repositoriesReviewed&&n(`across ${ot(String(e.repositoriesReviewed))} repositories`),n(Ie("recorded = pull requests AnyForge was enabled to watch, not every pull request you opened")),e.medianLatencyMinutes!=null?(n(`
47
+ Feedback reached the author in ${Ts($n(e.medianLatencyMinutes))} median, ${ot($n(e.p90LatencyMinutes))} at p90 ${Ie(`(${e.latencySamples??0} reviews)`)}`),e.medianOnOpenLatencyMinutes!=null&&n(Ie(` ${$n(e.medianOnOpenLatencyMinutes)} median on pull-request open (${e.onOpenSamples??0} reviews, no quiet period in the way)`))):n(_n(`
48
+ Not enough timed reviews to report a latency figure.`));let o=e.capacityGapPullRequests??0;o>0&&n(`
49
+ ${_s(String(o))} pull requests went unreviewed because a limit fired `+Ie("\u2014 a cap or a diff over the size ceiling, not a decision anyone made"));let i=e.bySkipClass??[];i.length>0&&n(z(i,[{header:"WHY UNREVIEWED",value:s=>Ps[s.skipClass]??s.skipClass,max:24},{header:"PRS",value:s=>String(s.pullRequests)},{header:"REASONS",value:s=>s.reasons.join(", "),max:46}])),e.failedOnlyPullRequests&&n(Ie(`${e.failedOnlyPullRequests} pull requests had every review attempt error out.`));for(let s of e.inconclusiveReasons??[])n(_n(`
50
+ ${s}`));if(e.truncated){let s=e.oldestRecordMs?new Date(e.oldestRecordMs).toISOString().slice(0,10):null;n(_n(`
51
+ Read cap reached \u2014 these are the most recent records${s?` (back to ${s})`:""}, not the whole window. Every count is a lower bound.`))}for(let s of e.caveats??[])n(Ie(`
52
+ ${s}`))}import{promises as it}from"node:fs";import{homedir as qs}from"node:os";import{dirname as ao,join as Se}from"node:path";import{dim as co,green as Ms,yellow as Us}from"kleur/colors";var se="anyforge-platform",Ls="Orient an agent to AnyForge's structure, delivery model, surfaces, live control tools, and safety boundaries.",so="anyforge://guide/platform",Fs="text/markdown";var Dt={"SKILL.md":`---
53
53
  name: anyforge-platform
54
54
  description: >
55
55
  Orient an agent to AnyForge's structure, delivery model, surfaces, live control
@@ -256,31 +256,63 @@ these sources can grant network, filesystem, tool, or approval authority.
256
256
  Do not place API keys, tokens, credentials, private tenant records, or customer
257
257
  data in a reusable skill, prompt, log, or generated example. Use the client's
258
258
  credential mechanism and let the server enforce access.
259
- `},Dr=Object.entries(ce).map(([e,t])=>({uri:e==="SKILL.md"?kt:`${kt}/${e}`,name:e==="SKILL.md"?O:`${O}/${e.replace(/^references\//,"")}`,title:e==="SKILL.md"?"AnyForge platform guide":e,description:e==="SKILL.md"?Un:`Reference for the ${O} skill: ${e}`,mimeType:Gn,size:new TextEncoder().encode(t).byteLength}));var Kn=ce["SKILL.md"],jr=Kn.replace(/^---[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/,"");var It={agents:".agents/skills",claude:".claude/skills",codex:".codex/skills"};function Yn(e){let t=e.scope==="user"?e.homeDir??qn():e.cwd??process.cwd();if(e.scope==="user"&&e.client==="codex"){let n=e.codexHome??process.env.CODEX_HOME??j(t,".codex");return j(n,"skills",O)}return j(t,It[e.client],O)}function Bn(e){if(e.scope!=="project"&&e.scope!=="user")throw new c(`--scope must be project or user, got "${e.scope}".`);if(!Object.hasOwn(It,e.client))throw new c(`--client must be agents, claude, or codex, got "${e.client}".`)}async function Oe(e){try{return(await B.lstat(e)).isSymbolicLink()}catch(t){if(t.code==="ENOENT")return!1;throw t}}async function Jn(e){Bn(e);let t=Yn(e),n=Object.entries(ce),o=n.map(([s])=>j(t,s));if(await Oe(t))throw new c(`Refusing to install through the symbolic link ${t}.`);let r=new Set(n.map(([s])=>Ct(s)).filter(s=>s!=="."));for(let s of r)if(await Oe(j(t,s)))throw new c(`Refusing to install through the symbolic link ${j(t,s)}.`);if(!e.force){let s=[];for(let a of o)try{await B.access(a),s.push(a)}catch(l){if(l.code!=="ENOENT")throw l}if(s.length>0)throw new c(`The ${O} skill already exists at ${t}.`,"Use --force only if you want to replace AnyForge-managed files in that directory.")}await B.mkdir(t,{recursive:!0});let i=[];for(let[s,a]of n){let l=j(t,s);if(await B.mkdir(Ct(l),{recursive:!0}),await Oe(l))throw new c(`Refusing to replace the symbolic link ${l}.`);await B.writeFile(l,a,{encoding:"utf8",mode:420}),i.push(s)}return{name:O,scope:e.scope,client:e.client,directory:t,files:i}}async function Pt(e){if(e.subcommand!=="install")throw new c(`Unknown skill command${e.subcommand?`: ${e.subcommand}`:""}.`,"Use `anyforge skill install anyforge-platform`.");if(e.name!==O)throw new c(`Unknown skill${e.name?`: ${e.name}`:""}.`,`The only portable platform skill is ${O}.`);let t=e.scope??"project",n=e.client??"agents";if(!["project","user"].includes(t))throw new c(`--scope must be project or user, got "${t}".`);if(!["agents","claude","codex"].includes(n))throw new c(`--client must be agents, claude, or codex, got "${n}".`);let o=await Jn({scope:t,client:n,force:e.force});if(e.json){process.stdout.write(JSON.stringify(o)+`
260
- `);return}console.log(Wn("Installed ")+`${o.name} ${At(`(${o.scope}, ${o.client})`)}`),console.log(` ${o.directory}`),console.log(At(` ${o.files.length} files installed. Re-run with --force to refresh them.`)),o.scope==="project"&&o.client==="agents"&&console.log(Hn(" This portable location is detected by AnyForge repository analysis."))}var Tt="https://crew.anyforge.ai",Qn=new Set(["endpoint","limit","status","type","priority","initiative","program","search","assignee","notes","input","context","messages","scope","client","days","profile","interval"]),Zn=new Set(["help","version","json","active","archived","all","follow","3way","dry-run","force"]);function eo(e){let t={positional:[],flags:{}};for(let n=0;n<e.length;n+=1){let o=e[n];if(!o.startsWith("-")){t.command?t.positional.push(o):t.command=o;continue}let r=o==="-h"?"help":o==="-V"?"version":o==="-f"?"force":o.replace(/^--?/,""),[i,s]=r.includes("=")?[r.slice(0,r.indexOf("=")),r.slice(r.indexOf("=")+1)]:[r,void 0];if(Qn.has(i)){let a=s??e[++n];if(a===void 0)throw new c(`--${i} needs a value.`);t.flags[i]=a;continue}if(Zn.has(i)){t.flags[i]=s===void 0?!0:s!=="false";continue}throw new c(`Unknown option: ${o}`,"Run `anyforge --help` for the command list.")}return t}function x(e,t){if(e===void 0||typeof e=="boolean")return;let n=Number(e);if(!Number.isFinite(n)||n<=0)throw new c(`--${t} must be a positive number, got "${e}".`);return n}function h(e){return typeof e=="string"?e:void 0}function _e(e){return e===!0?!0:void 0}function to(){console.log(`
261
- ${I("AnyForge Control")} \u2014 governed LLM proxy, and a scriptable AnyForge client.
262
-
263
- ${I("USAGE")}
259
+ `},Od=Object.entries(Dt).map(([t,e])=>({uri:t==="SKILL.md"?so:`${so}/${t}`,name:t==="SKILL.md"?se:`${se}/${t.replace(/^references\//,"")}`,title:t==="SKILL.md"?"AnyForge platform guide":t,description:t==="SKILL.md"?Ls:`Reference for the ${se} skill: ${t}`,mimeType:Fs,size:new TextEncoder().encode(e).byteLength}));var Ns=Dt["SKILL.md"],bd=Ns.replace(/^---[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/,"");var lo={agents:".agents/skills",claude:".claude/skills",codex:".codex/skills"};function js(t){let e=t.scope==="user"?t.homeDir??qs():t.cwd??process.cwd();if(t.scope==="user"&&t.client==="codex"){let n=t.codexHome??process.env.CODEX_HOME??Se(e,".codex");return Se(n,"skills",se)}return Se(e,lo[t.client],se)}function Ds(t){if(t.scope!=="project"&&t.scope!=="user")throw new y(`--scope must be project or user, got "${t.scope}".`);if(!Object.hasOwn(lo,t.client))throw new y(`--client must be agents, claude, or codex, got "${t.client}".`)}async function Pn(t){try{return(await it.lstat(t)).isSymbolicLink()}catch(e){if(e.code==="ENOENT")return!1;throw e}}async function Bs(t){Ds(t);let e=js(t),n=Object.entries(Dt),r=n.map(([s])=>Se(e,s));if(await Pn(e))throw new y(`Refusing to install through the symbolic link ${e}.`);let o=new Set(n.map(([s])=>ao(s)).filter(s=>s!=="."));for(let s of o)if(await Pn(Se(e,s)))throw new y(`Refusing to install through the symbolic link ${Se(e,s)}.`);if(!t.force){let s=[];for(let a of r)try{await it.access(a),s.push(a)}catch(c){if(c.code!=="ENOENT")throw c}if(s.length>0)throw new y(`The ${se} skill already exists at ${e}.`,"Use --force only if you want to replace AnyForge-managed files in that directory.")}await it.mkdir(e,{recursive:!0});let i=[];for(let[s,a]of n){let c=Se(e,s);if(await it.mkdir(ao(c),{recursive:!0}),await Pn(c))throw new y(`Refusing to replace the symbolic link ${c}.`);await it.writeFile(c,a,{encoding:"utf8",mode:420}),i.push(s)}return{name:se,scope:t.scope,client:t.client,directory:e,files:i}}async function uo(t){if(t.subcommand!=="install")throw new y(`Unknown skill command${t.subcommand?`: ${t.subcommand}`:""}.`,"Use `anyforge skill install anyforge-platform`.");if(t.name!==se)throw new y(`Unknown skill${t.name?`: ${t.name}`:""}.`,`The only portable platform skill is ${se}.`);let e=t.scope??"project",n=t.client??"agents";if(!["project","user"].includes(e))throw new y(`--scope must be project or user, got "${e}".`);if(!["agents","claude","codex"].includes(n))throw new y(`--client must be agents, claude, or codex, got "${n}".`);let r=await Bs({scope:e,client:n,force:t.force});if(t.json){process.stdout.write(JSON.stringify(r)+`
260
+ `);return}console.log(Ms("Installed ")+`${r.name} ${co(`(${r.scope}, ${r.client})`)}`),console.log(` ${r.directory}`),console.log(co(` ${r.files.length} files installed. Re-run with --force to refresh them.`)),r.scope==="project"&&r.client==="agents"&&console.log(Us(" This portable location is detected by AnyForge repository analysis."))}import{randomUUID as uc}from"node:crypto";import{open as pc,realpath as fc,stat as mc}from"node:fs/promises";import{basename as gc,join as hc,resolve as wc}from"node:path";import{bold as di,cyan as yc,dim as Rt,yellow as an}from"kleur/colors";import{ProcessTerminal as vc,SelectList as Oc,Text as bc,TuiAltScreen as Rc}from"@earendil-works/pi-tui";var Gs=new Set(["find_callers","find_definition","find_imports","trace_path","get_neighbors","graph_stats"]);function zs(t){return Gs.has(t)?"querying the code graph":t==="search_code_semantic"?"searching the code index":t==="search_codebase_docs"?"searching codebase docs":null}function Bt(t){let e=zs(t);if(e)return e;let n=/^mcp__(.+?)__(.+)$/.exec(t);return n?`${n[1].replace(/[_-]+/g," ")} \xB7 ${n[2].replace(/_/g," ")}`:t.replace(/_/g," ")}import{truncateToWidth as Qt,wrapTextWithAnsi as Po}from"@earendil-works/pi-tui";import{cyan as Lo,dim as He,green as Fo,italic as Aa,red as Yn,yellow as Qn}from"kleur/colors";import{truncateToWidth as st,visibleWidth as Nn,wrapTextWithAnsi as Hs}from"@earendil-works/pi-tui";import{bold as Js,dim as Ys}from"kleur/colors";var Ws=/\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\)|[PX^_][\s\S]*?\x1B\\|[@-_])/g,Ks=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g;function d(t){return t.replace(Ws,"").replace(Ks,"")}function Ln(t){try{let e=new URL(t);return e.protocol!=="https:"&&e.protocol!=="http:"||!e.hostname||e.username||e.password?null:e.toString()}catch{return null}}function po(t){return t.replace(/\x1B\]8;;([^\x1B\x07]*)(?:\x07|\x1B\\)/g,(e,n)=>Ln(n)?e:"\x1B]8;;\x07")}function Fn(t){let n=d(t).replace(/(!?)\[([^\]\n]+)\]\((<[^>\n]*>|[^\s)]+)(?:\s+[^)]*)?\)/g,(i,s,a,c)=>{let p=c.startsWith("<")&&c.endsWith(">")?c.slice(1,-1):c,l=Ln(p);return l?`${s}[${a}](${l})`:a}),r=new Map,o=n.replace(/^(\s{0,3}\[([^\]]+)\]:\s*)(\S+)(.*)$/gm,(i,s,a,c,p)=>{let l=Ln(c);return r.set(a.trim().toLowerCase(),l),l?`${s}${l}${p}`:""});return r.size===0?o:o.replace(/(!?)\[([^\]\n]+)\](?:\[([^\]\n]*)\])?/g,(i,s,a,c)=>{let p=(c||a).trim().toLowerCase();return!r.has(p)||r.get(p)?i:a})}function de(t){return typeof t=="string"?d(t):Array.isArray(t)?t.map(e=>de(e)):t&&typeof t=="object"?Object.fromEntries(Object.entries(t).map(([e,n])=>[d(e),de(n)])):t}function N(t){return t<1024?`${t} B`:t<1024*1024?`${at(t/1024)} KiB`:`${at(t/(1024*1024))} MiB`}function ct(t){let e=Math.max(0,t);if(e<1e4)return`${at(e/1e3)}s`;let n=Math.round(e/1e3);if(n<60)return`${n}s`;let r=Math.floor(n/3600),o=Math.floor(n%3600/60),i=n%60;return r?o?`${r}h ${o}m`:`${r}h`:i?`${o}m ${i}s`:`${o}m`}function qn(t){let e=Math.floor(Math.max(0,t)/1e3);return e<60?`${e}s`:`${Math.floor(e/60)}m ${String(e%60).padStart(2,"0")}s`}function ee(t){return Math.abs(t)<1e3?String(Math.round(t)):Math.abs(t)<1e6?`${at(t/1e3,t<1e5?1:0)}k`:`${at(t/1e6)}M`}function at(t,e=1){return t.toFixed(e).replace(/\.0+$/,"")}function Qs(t){return d(t).replace(/(Ms|Bytes)$/,"").replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/[_-]+/g," ").toLowerCase().trim()}function Vs(t,e){return typeof e=="boolean"?e?"on":"off":typeof e=="string"?d(e):/Ms$/.test(t)?ct(e):/Bytes$/.test(t)?N(e):/tokens?/i.test(t)?ee(e):String(e)}function fo(t){return t?Object.entries(t).filter(e=>e[1]!==null).map(([e,n])=>`${Qs(e)} ${Vs(e,n)}`):[]}function Mn(t,e,n=2){let r=Math.min(16,Math.max(...t.map(([s])=>Nn(s)),0)+2),o=Math.max(10,e-n-r),i=[];for(let[s,a]of t)Hs(a,o).forEach((p,l)=>{let u=l===0?Ys(st(s,r-1).padEnd(r)):" ".repeat(r);i.push(st(`${" ".repeat(n)}${u}${p}`,e))});return i}function mo(t,e,n){return[st(Js(t),n),...Mn(e,n)]}function go(t,e,n){let r=Nn(n),o=e.filter(Boolean).join(" \xB7 ");if(r+4>t)return st(o||n,t);let i=t-r-2,s=st(o,i),a=Math.max(2,t-Nn(s)-r);return`${s}${" ".repeat(a)}${n}`}import{bold as dt,cyan as lt,dim as Re,green as Xs,italic as ho,strikethrough as Zs,underline as wo,yellow as ea}from"kleur/colors";import{Markdown as ta}from"@earendil-works/pi-tui";var na={heading:t=>dt(lt(t)),link:t=>wo(lt(t)),linkUrl:t=>Re(t),code:t=>ea(t),codeBlock:t=>t,codeBlockBorder:t=>Re(t),quote:t=>ho(t),quoteBorder:t=>Re(t),hr:t=>Re(t),listBullet:t=>lt(t),bold:t=>dt(t),italic:t=>ho(t),strikethrough:t=>Zs(t),underline:t=>wo(t)},ut={selectedPrefix:t=>lt(t),selectedText:t=>dt(t),description:t=>Re(t),scrollInfo:t=>Re(t),noMatch:t=>Re(t)},yo={borderColor:t=>Re(t),selectList:ut};var Me=class{content;markdown;cache;constructor(e){this.content=d(e),this.markdown=new ta(Fn(this.content),0,0,na)}getText(){return this.content}setText(e){let n=d(e);n!==this.content&&(this.content=n,this.markdown.setText(Fn(n)),this.cache=void 0)}invalidate(){this.markdown.invalidate(),this.cache=void 0}render(e){let n=Math.max(12,e);if(this.cache?.width===n)return this.cache.lines;let r=this.content?this.markdown.render(n).map(po):[];return this.cache={width:n,lines:r},r}};function pt(t,e){return t==="user"?`${dt(Xs(e?d(e):"You"))}`:`${dt(lt("Oracle"))}`}function ue(t){return t.shared||t.visibility==="company"?`Shared with the company${t.entityKey?` \xB7 ${d(t.entityKey)}`:""}`:"Private to you"}function vo(t){return t.shared||t.visibility==="company"?"shared":"private"}import{createReadStream as ha}from"node:fs";import{stat as wa}from"node:fs/promises";import{basename as ya}from"node:path";import{setTimeout as Co}from"node:timers/promises";var Oo={txt:"text/plain",log:"text/plain",md:"text/markdown",markdown:"text/markdown",csv:"text/csv",json:"application/json",pdf:"application/pdf",ts:"text/plain",tsx:"text/plain",js:"text/plain",jsx:"text/plain",mjs:"text/plain",cjs:"text/plain",py:"text/plain",go:"text/plain",rs:"text/plain",yaml:"text/plain",yml:"text/plain",toml:"text/plain",xml:"text/plain",html:"text/plain",css:"text/plain",sh:"text/plain",sql:"text/plain",java:"text/plain",kt:"text/plain",swift:"text/plain",c:"text/plain",h:"text/plain",cpp:"text/plain",rb:"text/plain",php:"text/plain",tf:"text/plain",diff:"text/plain",patch:"text/plain",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"};function bo(t){let e=t.slice(t.lastIndexOf(".")+1).toLowerCase();return Object.hasOwn(Oo,e)?Oo[e]:null}var Un="/api/v1/oracle";function fe(t){let e;try{e=new URL(t)}catch{throw new f(`Invalid Oracle endpoint: ${t}`,m.USAGE)}if(!["http:","https:"].includes(e.protocol)||e.username||e.password)throw new f("Oracle endpoint must be an http or https URL without embedded credentials.",m.USAGE);if(e.pathname!=="/"||e.search||e.hash)throw new f("Oracle endpoint must be the workspace origin, without a path, query or fragment.",m.USAGE);return e.origin}function pe(t,e){return`${fe(t)}${Un}${e}`}function ra(t,e,n){let r;try{r=new URL(t)}catch{throw new f(`Oracle server returned an invalid ${n} endpoint.`,m.API_ERROR)}let o=new URL(fe(e));if(r.origin!==o.origin||!r.pathname.startsWith("/api/oauth/")||r.search||r.hash||r.username||r.password)throw new f(`Oracle server returned an unsafe ${n} endpoint.`,m.API_ERROR);return r.toString()}function oa(t,e){if(!t||typeof t!="object")throw new f("Oracle server omitted OAuth endpoints.",m.API_ERROR);let n=t,r=["registration","authorization","token","revocation"],o={};for(let i of r){if(typeof n[i]!="string")throw new f(`Oracle server omitted its OAuth ${i} endpoint.`,m.API_ERROR);o[i]=ra(n[i],e,i)}return o}async function Ge(t,e=fetch,n){let r=fe(t),o;try{o=await e(pe(r,"/config"),{headers:{Accept:"application/json",...n?{Authorization:`Bearer ${n}`}:{}},redirect:"error",signal:AbortSignal.timeout(2e4)})}catch(l){throw new f(`Cannot reach ${r}: ${l instanceof Error?l.message:String(l)}`,m.FAILURE,"Check your network, or pass --endpoint for a self-hosted workspace.")}let i=await De(o,pe(r,"/config")),s=$(je(i,o.status)),a=`${r}${Un}`;if(s.protocolVersion!==1)throw new f(`Oracle protocol mismatch: this CLI supports ${1}, and the server reports ${String(s.protocolVersion)}.`,m.API_ERROR,"Update @anyforge/control to a version supported by this workspace.");if(s.resource!==a)throw new f("Oracle server resource does not match this workspace origin.",m.API_ERROR);let c=Array.isArray(s.scopes)?s.scopes:null;if(!c||!c.every(l=>l==="read"||l==="write"||l==="approve"))throw new f("Oracle server returned an invalid scope list.",m.API_ERROR);let p={protocolVersion:1,resource:a,scopes:c,oauth:oa(s.oauth,r)};if(s.identity!==void 0){let l=$(s.identity);if(typeof l.uid!="string"||typeof l.companyId!="string")throw new f("Oracle server returned an invalid identity.",m.API_ERROR);p.identity={uid:d(l.uid),companyId:d(l.companyId)}}if(s.capabilities!==void 0){let l=$(s.capabilities);if(![l.read,l.write,l.approve].every(u=>typeof u=="boolean"))throw new f("Oracle server returned invalid capabilities.",m.API_ERROR);p.capabilities={read:l.read,write:l.write,approve:l.approve}}if(s.model!==void 0){let l=$(s.model);if(typeof l.id!="string"||typeof l.provider!="string")throw new f("Oracle server returned invalid model metadata.",m.API_ERROR);p.model={id:d(l.id),provider:d(l.provider)}}if(s.limits!==void 0){let l=$(s.limits);if(Object.values(l).some(u=>!["string","number","boolean"].includes(typeof u)&&u!==null))throw new f("Oracle server returned invalid limits metadata.",m.API_ERROR);p.limits=Object.fromEntries(Object.entries(l).map(([u,v])=>[d(u),typeof v=="string"?d(v):v]))}return Array.isArray(s.commands)&&(p.commands=ia(s.commands)),p}function ia(t){let e=[];for(let n of t.slice(0,50)){let r=$(n);if(typeof r.name!="string"||!/^[a-z][a-z0-9_-]{0,31}$/.test(r.name)||typeof r.description!="string"||e.some(i=>i.name===r.name))continue;let o=typeof r.args=="string"?d(r.args).slice(0,60).trim():"";e.push({name:r.name,description:d(r.description).slice(0,200),...o?{args:o}:{}})}return e}var zt=class{endpoint;scopes;source;#e;#t;cachedConfig;constructor(e,n=fetch){this.endpoint=fe(e.endpoint),this.#e=e.getAccessToken??(async()=>e.accessToken),this.#t=n,this.scopes=new Set(e.scopes),this.source=e.source}async config(){if(!this.cachedConfig){let e=await this.#e();try{this.cachedConfig=await Ge(this.endpoint,this.#t,e)}catch(n){if(this.source!=="oauth"||!(n instanceof be)||n.status!==401)throw n;let r=await this.#e(e);if(r===e)throw n;this.cachedConfig=await Ge(this.endpoint,this.#t,r)}}return this.cachedConfig}async listSessions(){let e=await this.json("GET","/sessions"),n=Array.isArray(e)?e:$(e).sessions;if(!Array.isArray(n))throw new f("Oracle server returned an invalid sessions list.",m.API_ERROR);return n.map(ko)}async createSession(e={}){let n=await this.json("POST","/sessions",e),r=$(n);return!Array.isArray(r.messages)&&typeof r.sessionId=="string"?this.getSession(r.sessionId):Ro(n)}async getSession(e){return Ro(await this.json("GET",`/sessions/${encodeURIComponent(e)}`))}async submitTurn(e,n,r,o){let i,s=pe(this.endpoint,`/sessions/${encodeURIComponent(e)}/turns`),a=async p=>this.#t(s,{method:"POST",headers:{Authorization:`Bearer ${p}`,Accept:"text/event-stream, application/json","Content-Type":"application/json"},redirect:"error",body:JSON.stringify(n),...r?{signal:r}:{}});try{let p=await this.#e();if(i=await a(p),i.status===401&&this.source==="oauth"){let l=await this.#e(p);l!==p&&(i=await a(l))}}catch(p){throw p instanceof f||r?.aborted?p:new f(`Oracle connection was interrupted: ${p instanceof Error?p.message:String(p)}`,m.FAILURE,"The server may still be working. Resume this session to replay its saved stream.")}if(o?.(i),i.ok||await Ue(i),!(i.headers.get("content-type")?.toLowerCase()??"").includes("text/event-stream")){let p=await De(i,pe(this.endpoint,`/sessions/${e}/turns`)),l=je(p,i.status);throw new f(`Oracle server did not start a stream (${sa(l)}).`,m.API_ERROR)}return i}async replayEvents(e,n,r){let o=[];for await(let i of this.streamReplayEvents(e,n,r))o.push(i);return o}async*streamReplayEvents(e,n,r,o){let i=`/sessions/${encodeURIComponent(e)}/events?streamId=${encodeURIComponent(n)}&from=${Math.max(0,r)}`,s=await this.authenticatedFetch("GET",i,{headers:{Accept:"text/event-stream, application/json"}},0);if(s.ok||await Ue(s),(s.headers.get("content-type")?.toLowerCase()??"").includes("text/event-stream")){yield*ft(s,o);return}let c=je(await De(s,pe(this.endpoint,i)),s.status),p=Array.isArray(c)?c:$(c).events;if(!Array.isArray(p))throw new f("Oracle replay response did not contain events.",m.API_ERROR);for(let l of p)yield jn(l)}async*streamRequestEvents(e,n,r=-1,o){let i=`/sessions/${encodeURIComponent(e)}/events?requestId=${encodeURIComponent(n)}&from=${r}`,s=await this.authenticatedFetch("GET",i,{headers:{Accept:"text/event-stream, application/json"}},0);if(s.ok||await Ue(s),(s.headers.get("content-type")?.toLowerCase()??"").includes("text/event-stream")){yield*ft(s,o);return}let c=je(await De(s,pe(this.endpoint,i)),s.status),p=Array.isArray(c)?c:$(c).events;if(!Array.isArray(p))throw new f("Oracle request replay response did not contain events.",m.API_ERROR);for(let l of p)yield jn(l)}async cancelTurn(e,n){return this.json("POST",`/sessions/${encodeURIComponent(e)}/cancel`,{streamId:n})}async cancelRequest(e,n){return this.json("POST",`/sessions/${encodeURIComponent(e)}/cancel`,{requestId:n})}async upload(e,n){let r=new FormData;r.set("sessionId",e),r.set("file",n,n.name);let o=await this.authenticatedFetch("POST","/uploads",{method:"POST",body:r,headers:{Accept:"application/json"}});return o.ok||await Ue(o),aa(je(await De(o,pe(this.endpoint,"/uploads")),o.status))}async download(e,n){let r=await this.authenticatedFetch("GET",`/sessions/${encodeURIComponent(e)}/files/${encodeURIComponent(n)}`,{headers:{Accept:"application/octet-stream, application/json"}});return r.ok||await Ue(r),r}async json(e,n,r){let o=await this.authenticatedFetch(e,n,{headers:{Accept:"application/json",...r!==void 0?{"Content-Type":"application/json"}:{}},...r!==void 0?{body:JSON.stringify(r)}:{}});return o.ok||await Ue(o),je(await De(o,pe(this.endpoint,n)),o.status)}async authenticatedFetch(e,n,r={},o=2e4){try{let i=pe(this.endpoint,n),s=Object.fromEntries(new Headers(r.headers).entries()),a=r.signal||o<=0?void 0:AbortSignal.timeout(o),c=v=>this.#t(i,{...r,method:e,headers:{...s,Authorization:`Bearer ${v}`},redirect:"error",...a?{signal:a}:{}}),p=await this.#e(),l=await c(p),u=r.body===void 0||typeof r.body=="string"||r.body===null;if(l.status===401&&this.source==="oauth"){let v=await this.#e(p);v!==p&&u&&(l=await c(v))}return l}catch(i){throw i instanceof f?i:new f(`Cannot reach ${this.endpoint}: ${i instanceof Error?i.message:String(i)}`,m.FAILURE,"Check your network, or pass --endpoint for a self-hosted workspace.")}}};async function*ft(t,e){if(!t.body)throw new f("Oracle stream has no response body.",m.FAILURE);let n=t.body.getReader(),r=new TextDecoder,o="",i=0,s=[],a=0,c=-1,p=()=>{if(s.length===0)return null;let l=s.join(`
261
+ `);if(s=[],a=0,l==="[DONE]")return null;let u;try{u=JSON.parse(l)}catch{throw new f("Oracle stream returned invalid JSON.",m.API_ERROR)}let v=jn(u);return v.type==="finalizing"?v:v.idx<=c?null:(c=v.idx,v)};try{for(;;){let{value:u,done:v}=await n.read();u?.byteLength&&e?.();let x=r.decode(u,{stream:!v});if(o+=x,i+=Gt(x),i>2*1024*1024)throw new f("Oracle stream event exceeded the 2 MiB protocol limit.",m.API_ERROR);let k=0,b=o.indexOf(`
262
+ `,k);for(;b>=0;){let O=o.slice(k,b).replace(/\r$/,"");if(i-=Gt(o.slice(k,b+1)),k=b+1,O===""){let R=p();R&&(yield R)}else if(!O.startsWith(":")){let R=O.indexOf(":"),V=R<0?O:O.slice(0,R),K=R<0?"":O.slice(R+1).replace(/^ /,"");if(V==="data"&&(s.push(K),a+=Gt(K)+1,a>2*1024*1024))throw new f("Oracle stream event exceeded the 2 MiB protocol limit.",m.API_ERROR)}b=o.indexOf(`
263
+ `,k)}if(o=o.slice(k),v)break}if(o.trim()){let u=o.replace(/\r$/,"");if(u.startsWith("data:")&&(s.push(u.slice(5).replace(/^ /,"")),a+=Gt(s[s.length-1]??"")+1,a>2*1024*1024))throw new f("Oracle stream event exceeded the 2 MiB protocol limit.",m.API_ERROR)}let l=p();l&&(yield l)}catch(l){throw await n.cancel("Invalid or oversized Oracle event stream.").catch(()=>{}),l}finally{n.releaseLock()}}function Gt(t){return Buffer.byteLength(t,"utf8")}function Dn(t){let e=["program","initiative","codebase"].filter(o=>typeof t[o]=="string");if(e.length>1)throw new f("Choose only one of --program, --initiative or --codebase.",m.USAGE);let n=e[0];if(!n)return;let r=t[n];if(typeof r!="string"||!r.trim())throw new f(`--${n} needs an id.`,m.USAGE);return{kind:n,id:r.trim()}}function $(t){return!t||typeof t!="object"||Array.isArray(t)?{}:t}function sa(t){if(typeof t=="string")return d(t);try{return d(JSON.stringify(t))}catch{return"unexpected response"}}function je(t,e){let n=$(t);if(n.ok===!1){let r=Ao(n.error,e),o=typeof n.message=="string"?d(n.message).slice(0,2e3):void 0;throw new be(r,e,o)}return n.ok===!0&&"data"in n?n.data:t}async function De(t,e){try{return await t.json()}catch{throw new f(`Non-JSON response from ${e} (HTTP ${t.status}).`,m.FAILURE)}}async function Ue(t){let e;try{e=await t.clone().json()}catch{e=null}let n=$(e),r=Ao(n.error??n.error_description,t.status),o=typeof n.message=="string"?n.message:typeof n.error_description=="string"?n.error_description:typeof n.detail=="string"?n.detail:void 0;throw new be(r,t.status,typeof o=="string"?d(o).slice(0,2e3):void 0)}function Ao(t,e){if(typeof t!="string")return`http_${e}`;let n=d(t).slice(0,120);return/^[A-Za-z0-9_.-]+$/.test(n)?n:`http_${e}`}function ko(t){let e=$(t);if(typeof e.sessionId!="string"||!/^[A-Za-z0-9_-]{1,128}$/.test(e.sessionId)||typeof e.title!="string"||e.visibility!=="private"&&e.visibility!=="company"||typeof e.shared!="boolean")throw new f("Oracle server returned an invalid session summary.",m.API_ERROR);return{sessionId:e.sessionId,title:d(e.title),visibility:e.visibility,shared:e.shared,...typeof e.entityKey=="string"?{entityKey:d(e.entityKey)}:{},...typeof e.updatedAt=="number"||e.updatedAt===null?{updatedAt:e.updatedAt}:{}}}function Ro(t){let e=$(t),n=ko(e);if(!Array.isArray(e.messages))throw new f("Oracle server returned an invalid session transcript.",m.API_ERROR);let r=e.messages.map((o,i)=>{let s=$(o);if(s.role!=="user"&&s.role!=="assistant"||typeof s.content!="string")throw new f(`Oracle session message ${i+1} is invalid.`,m.API_ERROR);let a=Array.isArray(s.attachments)?s.attachments.map(ua):void 0,c=Array.isArray(s.files)?s.files.map(pa):void 0;return{role:s.role,content:d(s.content),...typeof s.id=="string"?{id:s.id}:{},...typeof s.authorName=="string"?{authorName:d(s.authorName)}:{},...s.question&&typeof s.question=="object"?{question:ca(s.question)}:{},...s.questionResponse&&typeof s.questionResponse=="object"?{questionResponse:la(s.questionResponse)}:{},...a?{attachments:a}:{},...c?{files:c}:{}}});return{...n,messages:r,...typeof e.activeStreamId=="string"?{activeStreamId:e.activeStreamId}:{}}}function jn(t){let e=$(t);if(e.protocolVersion!==1||typeof e.idx!="number"||!Number.isSafeInteger(e.idx)||e.idx<0||typeof e.type!="string")throw new f("Oracle stream event does not match protocol version 1.",m.API_ERROR);return de(e)}function aa(t){let e=$(t);if(typeof e.fileId!="string"||typeof e.name!="string"||typeof e.mediaType!="string")throw new f("Oracle upload response is missing file metadata.",m.API_ERROR);return{fileId:e.fileId,name:d(e.name),mediaType:d(e.mediaType),...typeof e.sizeBytes=="number"?{sizeBytes:e.sizeBytes}:{}}}function ca(t){let e=$(t);if(typeof e.id!="string"||!Array.isArray(e.questions))throw new f("Oracle returned an invalid question card.",m.API_ERROR);return{id:d(e.id),questions:e.questions.map((n,r)=>{let o=$(n);if(typeof o.id!="string"||typeof o.header!="string"||typeof o.question!="string"||typeof o.multiSelect!="boolean"||!Array.isArray(o.options))throw new f(`Oracle question ${r+1} is invalid.`,m.API_ERROR);return{id:d(o.id),header:d(o.header),question:d(o.question),multiSelect:o.multiSelect,options:o.options.map((i,s)=>{let a=$(i);if(typeof a.id!="string"||typeof a.label!="string"||typeof a.description!="string")throw new f(`Oracle question option ${s+1} is invalid.`,m.API_ERROR);return{id:d(a.id),label:d(a.label),description:d(a.description),...typeof a.recommended=="boolean"?{recommended:a.recommended}:{},...typeof a.confirms=="boolean"?{confirms:a.confirms}:{}}})}}),optional:e.optional===!0}}function la(t){let e=$(t);if(typeof e.questionId!="string"||!Array.isArray(e.answers))throw new f("Oracle returned an invalid question response.",m.API_ERROR);return{questionId:d(e.questionId),answers:e.answers.map(da)}}function da(t){let e=$(t);if(typeof e.questionId!="string"||!Array.isArray(e.optionIds)||!e.optionIds.every(n=>typeof n=="string"))throw new f("Oracle returned an invalid question answer.",m.API_ERROR);return{questionId:d(e.questionId),optionIds:e.optionIds.map(n=>d(n)),...typeof e.customText=="string"?{customText:d(e.customText).slice(0,8e3)}:{}}}function ua(t){let e=$(t);if(typeof e.fileId!="string"||typeof e.name!="string"||typeof e.mediaType!="string")throw new f("Oracle session attachment metadata is invalid.",m.API_ERROR);return{fileId:d(e.fileId),name:d(e.name),mediaType:d(e.mediaType),...typeof e.sizeBytes=="number"&&Number.isFinite(e.sizeBytes)?{sizeBytes:e.sizeBytes}:{}}}function pa(t){let e=$(t);if(typeof e.fileId!="string"||typeof e.filename!="string"||typeof e.mediaType!="string"||typeof e.sizeBytes!="number"||!Number.isFinite(e.sizeBytes))throw new f("Oracle session file metadata is invalid.",m.API_ERROR);return{fileId:d(e.fileId),filename:d(e.filename),mediaType:d(e.mediaType),sizeBytes:e.sizeBytes,...typeof e.format=="string"?{format:d(e.format)}:{}}}import{randomUUID as xo}from"node:crypto";import{promises as W}from"node:fs";import{homedir as fa}from"node:os";import{join as Bn}from"node:path";import*as Io from"proper-lockfile";var mt=Bn(fa(),".anyforge"),me=Bn(mt,"oracle"),So=Bn(mt,"oracle.last-session");function ma(t){if(!t||typeof t!="object"||Array.isArray(t))return null;let e=t;return e.version!==1||typeof e.endpoint!="string"||typeof e.clientId!="string"||typeof e.redirectUri!="string"||typeof e.accessToken!="string"||typeof e.refreshToken!="string"||typeof e.expiresAt!="number"||!Number.isFinite(e.expiresAt)||!Array.isArray(e.scopes)||!e.scopes.every(n=>typeof n=="string")||e.lastSessionId!==void 0&&typeof e.lastSessionId!="string"?null:{version:1,endpoint:e.endpoint,clientId:e.clientId,redirectUri:e.redirectUri,accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt,scopes:e.scopes,...typeof e.lastSessionId=="string"?{lastSessionId:e.lastSessionId}:{}}}async function Gn(t){await W.mkdir(mt,{recursive:!0,mode:448});let e=`${me}.${xo()}.tmp`;try{await W.writeFile(e,`${JSON.stringify(t,null,2)}
264
+ `,{mode:384,flag:"wx"}),await W.rename(e,me),await W.chmod(me,384)}finally{await W.rm(e,{force:!0}).catch(()=>{})}}async function Wt(){let t;try{t=await W.readFile(me,"utf8")}catch(r){if(r.code==="ENOENT")return null;throw r}let e;try{e=JSON.parse(t)}catch{throw new Error(`Oracle credentials at ${me} are invalid JSON; run \`anyforge oracle logout\` and sign in again.`)}let n=ma(e);if(!n)throw new Error(`Oracle credentials at ${me} have an unsupported format; run \`anyforge oracle logout\` and sign in again.`);return n}async function ze(){try{return await W.unlink(me),!0}catch(t){if(t.code==="ENOENT")return!1;throw t}}async function Eo(t,e){await ga(So,{version:1,endpoint:e,sessionId:t})}async function Kt(t){let e;try{e=await W.readFile(So,"utf8")}catch(o){if(o.code==="ENOENT")return;throw o}let n;try{n=JSON.parse(e)}catch{return}if(!n||typeof n!="object"||Array.isArray(n))return;let r=n;return r.version===1&&r.endpoint===t&&typeof r.sessionId=="string"?r.sessionId:void 0}async function ga(t,e){await W.mkdir(mt,{recursive:!0,mode:448});let n=`${t}.${xo()}.tmp`;try{await W.writeFile(n,`${JSON.stringify(e,null,2)}
265
+ `,{mode:384,flag:"wx"}),await W.rename(n,t),await W.chmod(t,384)}finally{await W.rm(n,{force:!0}).catch(()=>{})}}async function Ht(t){await W.mkdir(mt,{recursive:!0,mode:448});let e=await Io.lock(me,{realpath:!1,lockfilePath:`${me}.refresh-lock`,stale:6e4,update:1e4,retries:{retries:300,minTimeout:100,maxTimeout:100,randomize:!1}});try{return await t()}finally{await e()}}var zn=8192,To=10*1048576,va=20*1048576,_o=10,$o=3;function Wn(t,e){if(t.type!=="tool_call"&&t.type!=="tool_call_start"||typeof t.spawnId=="string")return!1;let n=typeof t.id=="string"?t.id:void 0;if(n){if(e.has(n))return!1;e.add(n)}return t.name!=="ask_user"}var Ee=class{lastIdx=-1;answer="";question;error;done;streamId;files=[];toolIds=new Set;accept(e){return e.type==="finalizing"?!0:e.idx<=this.lastIdx?!1:(this.lastIdx=e.idx,e.type==="stream_start"&&typeof e.streamId=="string"?this.streamId=d(e.streamId):e.type==="delta"&&typeof e.content=="string"?this.answer+=e.content:Wn(e,this.toolIds)?this.answer="":e.type==="question"?this.question=Jn(e):e.type==="error"?this.error=Hn(e):e.type==="done"?this.done=e:e.type==="navigator_file_created"&&Oa(this.files,e.file),!0)}get settled(){return!!(this.done||this.error)}outcome(){return{answer:this.answer,...this.question?{question:this.question}:{},...this.error?{error:this.error}:{},...this.done?{done:this.done}:{},...this.streamId?{streamId:this.streamId}:{},...this.files.length?{files:[...this.files]}:{}}}};async function Jt(t,e,n,r,o,i=new Ee){let s=async u=>{let v=i.streamId;i.accept(u)&&(i.streamId&&i.streamId!==v&&r.onStream?.(i.streamId),await r.onEvent(u))},a=()=>Kn(t,e,i,s,o,r.onActivity),c=async()=>{try{for await(let u of t.streamRequestEvents(e,n.requestId,-1,r.onActivity))await s(u)}catch(u){throw o?.aborted?u:new f("Oracle connection failed and the submitted request could not be recovered by its request id. It was not resubmitted with a new id.",m.FAILURE,`Run anyforge oracle resume ${e} to inspect the saved session.`)}if(i.settled)return i.outcome();if(i.streamId)return a();throw new f("Oracle request lookup returned no stream events. It was not resubmitted with a new id.",m.FAILURE,`Run anyforge oracle resume ${e} to inspect the saved session.`)},p=async()=>{try{let u=await t.submitTurn(e,n,o);for await(let v of ft(u,r.onActivity))await s(v)}catch(u){if(o?.aborted||u instanceof f&&u.exitCode===m.API_ERROR)throw u;return i.streamId?a():c()}return i.settled?i.outcome():i.streamId?a():c()},l;try{l=await t.submitTurn(e,n,o)}catch(u){if(o?.aborted||u instanceof f&&u.exitCode!==m.FAILURE)throw u;return p()}if(!l)throw new f("Oracle did not return a stream response.",m.FAILURE);r.onResponse?.();try{for await(let u of ft(l,r.onActivity))await s(u)}catch(u){if(o?.aborted||u instanceof f)throw u;return i.streamId?a():p()}return i.settled?i.outcome():i.streamId?a():p()}async function Kn(t,e,n,r,o,i){let s=n.streamId;if(!s)throw new f("Oracle stream replay needs a stream id.",m.FAILURE);let a=0;for(;!n.settled&&a<$o;){if(o?.aborted)throw o.reason;try{for await(let c of t.streamReplayEvents(e,s,n.lastIdx+1,i))await r(c);if(!n.settled){if((await t.getSession(e)).activeStreamId!==s)break;await Co(300,void 0,o?{signal:o}:void 0)}}catch(c){if(o?.aborted||c instanceof f&&c.exitCode===m.API_ERROR||(a+=1,a>=$o))throw c;await Co(300,void 0,o?{signal:o}:void 0)}}if(!n.settled)throw new f("Oracle stream could not be fully replayed; the turn was not submitted again.",m.FAILURE,`Run anyforge oracle resume ${e} to inspect the session.`);return n.outcome()}function gt(t,e){let n=e.done,r=n?.servedBy,o=Ce(n?.tokens),i=U(n?.rounds),s=typeof n?.exitReason=="string"?d(n.exitReason):void 0,a=n?.exitReason==="completed"&&!e.question,c=e.files??(Array.isArray(n?.files)?n.files.map(We):void 0),p=e.error?"error":e.question?"awaiting_input":a?"complete":"interrupted";return{protocolVersion:1,sessionId:t,status:p,answer:d(e.answer),partial:!a,...typeof n?.messageId=="string"?{messageId:d(n.messageId)}:typeof n?.answerId=="string"?{messageId:d(n.answerId)}:{},...e.question?{question:e.question}:{},...e.error?{error:d(e.error)}:{},...e.streamId?{streamId:d(e.streamId)}:{},...r?{servedBy:de(r)}:{},...o?{usage:{...o,...i!==void 0?{rounds:i}:{}}}:{},...i!==void 0?{rounds:i}:{},...s?{exitReason:s}:{},...c?.length?{files:de(c)}:{}}}function S(t,e){return typeof t=="string"?d(t).slice(0,e):""}function Hn(t){return typeof t.message=="string"?d(t.message).slice(0,2e3):"Oracle turn failed."}function Q(t){return d(t instanceof Error?t.message:String(t)).slice(0,2e3)}function Jn(t){let e=t.question;if(!e||typeof e!="object")throw new f("Oracle question event omitted its question card.",m.API_ERROR);return ba(e)}function We(t){let e=t&&typeof t=="object"&&!Array.isArray(t)?t:{};if(typeof e.fileId!="string"||!e.fileId||typeof e.filename!="string"||!e.filename||typeof e.mediaType!="string"||!e.mediaType||typeof e.sizeBytes!="number"||!Number.isSafeInteger(e.sizeBytes)||e.sizeBytes<0)throw new f("Oracle generated-file event omitted safe file metadata.",m.API_ERROR);return{fileId:d(e.fileId),filename:d(e.filename),mediaType:d(e.mediaType),sizeBytes:e.sizeBytes,...typeof e.format=="string"?{format:d(e.format)}:{}}}function Oa(t,e){let n=We(e);t.some(r=>r.fileId===n.fileId)||t.push(n)}function ba(t){let e=t;if(typeof e.id!="string"||!Array.isArray(e.questions))throw new f("Oracle returned an invalid question card.",m.API_ERROR);return{id:d(e.id),optional:e.optional===!0,questions:e.questions.map((n,r)=>{let o=n&&typeof n=="object"?n:{};if(typeof o.id!="string"||typeof o.header!="string"||typeof o.question!="string"||typeof o.multiSelect!="boolean"||!Array.isArray(o.options))throw new f(`Oracle question ${r+1} is invalid.`,m.API_ERROR);return{id:d(o.id),header:d(o.header),question:d(o.question),multiSelect:o.multiSelect,options:o.options.map(i=>{let s=i&&typeof i=="object"?i:{};if(typeof s.id!="string"||typeof s.label!="string"||typeof s.description!="string")throw new f("Oracle question option is invalid.",m.API_ERROR);return{id:d(s.id),label:d(s.label),description:d(s.description),...typeof s.recommended=="boolean"?{recommended:s.recommended}:{},...typeof s.confirms=="boolean"?{confirms:s.confirms}:{}}})}})}}function U(t){return typeof t=="number"&&Number.isFinite(t)?t:void 0}function Ce(t){if(typeof t=="number"&&Number.isFinite(t))return{totalTokens:t};if(!t||typeof t!="object"||Array.isArray(t))return;let e=t,n=U(e.in)??U(e.input)??U(e.inputTokens),r=U(e.cacheRead),o=U(e.cacheWrite),i=n===void 0?void 0:n+(r??0)+(o??0),s=U(e.out)??U(e.output)??U(e.outputTokens),a=U(e.total)??U(e.totalTokens)??(i!==void 0||s!==void 0?(i??0)+(s??0):void 0);if(!(i===void 0&&s===void 0&&a===void 0))return{...i!==void 0?{inputTokens:i}:{},...r?{cachedInputTokens:r}:{},...s!==void 0?{outputTokens:s}:{},...a!==void 0?{totalTokens:a}:{}}}function Ke(t){if(t&&typeof t=="object"){let e=t;if(typeof e.accepted=="boolean")return e.accepted?"Oracle server accepted the cancellation request.":"Oracle server did not accept the cancellation request.";if(typeof e.cancelled=="boolean")return e.cancelled?"Oracle server cancelled the active turn.":"Oracle server did not cancel the active turn."}return"Oracle server acknowledged the cancellation request."}function ht(t,e){let n=e&&t&&(t.kind==="program"||t.kind==="initiative")?{kind:t.kind,id:t.id}:void 0;return{...!e&&t?{context:t}:{},...n?{sharedEntity:n}:{},visibility:e?"company":"private"}}async function wt(t,e){await Eo(t,e).catch(()=>{})}async function Yt(t,e,n,r){if(!n.length)return[];if(n.length>_o)throw new y(`Attach no more than ${_o} files per turn.`);let o=0,i=[];for(let a of n){let c=await wa(a);if(!c.isFile())throw new y(`Attachment is not a file: ${a}`);if(c.size>To)throw new y(`Attachment exceeds the 10 MiB per-file CLI limit: ${a}`);let p=await Ra(a,To);if(o+=p.byteLength,o>va)throw new y("Attachments exceed the 20 MiB total CLI limit.");let l=ya(a).replace(/[\u0000-\u001f\u007f/\\]/g,"_").slice(0,200)||"attachment",u=bo(l)??"";if(!u)throw new y(`Unsupported attachment type: ${a}`);i.push({content:p,name:l,mediaType:u})}let s=[];for(let{content:a,name:c,mediaType:p}of i){await r(d(c),a.byteLength);let l=new ArrayBuffer(a.byteLength);new Uint8Array(l).set(a);let u=new File([l],c,{type:p});s.push(await t.upload(e,u))}return s}async function Ra(t,e){let n=[],r=0;for await(let o of ha(t)){let i=Buffer.isBuffer(o)?o:Buffer.from(o);if(r+=i.byteLength,r>e)throw new y(`Attachment exceeds the ${N(e)} limit: ${t}`);n.push(i)}return Buffer.concat(n)}var yt=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],ka=2e4,xa=6,Ia={lookup:"Looking things up",quick_edit:"Making a quick edit",final_answer:"Composing the answer",follow_up:"Suggesting follow-ups"},Sa={max_rounds:"reached the step limit",max_tokens:"reached the output limit",token_budget:"reached the token budget",deadline:"ran out of time",interrupted:"interrupted",loop_detected:"stopped a repeating loop",cancelled:"cancelled"},Vt=class{constructor(e,n){this.startedAt=e;this.sessionId=n;this.lastSignalAt=e}phase="sending";answer="";tools=[];notices=[];files=[];lastSignalAt;endedAt;end;done;upload;round;maxRounds;serverPhase;agents=new Map;toolIds=new Set;get toolCount(){return this.tools.length}get failedCount(){return this.tools.filter(e=>e.status==="failed").length}get finished(){return this.end!==void 0}setUploading(e,n,r){this.phase="uploading",this.upload=`${e} (${N(n)})`,this.lastSignalAt=r}setPhase(e,n){this.phase=e,this.lastSignalAt=n}touch(e){this.lastSignalAt=e}apply(e,n){if(this.lastSignalAt=n,this.phase==="cancelling")return this.record(e);switch(e.type){case"stream_start":case"thinking":this.phase!=="finishing"&&(this.phase="thinking");break;case"round_start":this.round=U(e.round),this.maxRounds=U(e.maxRounds)??this.maxRounds;for(let r=this.tools.length-1;r>=0;r--)this.tools[r]?.started&&this.tools.splice(r,1);this.phase!=="finishing"&&(this.phase="thinking");break;case"phase":this.serverPhase=typeof e.phase=="string"?e.phase:void 0;break;case"delta":this.phase!=="finishing"&&(this.phase="writing");break;case"tool_result":this.phase!=="finishing"&&(this.phase="thinking");break;case"finalizing":this.phase="finishing";break}this.record(e)}record(e){switch(e.type){case"delta":typeof e.content=="string"&&(this.answer+=e.content);break;case"tool_call_start":case"tool_call":this.recordToolCall(e);break;case"tool_result":{let n=this.tools.find(r=>r.id!==void 0&&r.id===e.toolUseId);n&&(n.status=e.ok===!1?"failed":"ok");break}case"subagent_start":typeof e.spawnId=="string"&&this.agents.set(e.spawnId,"running");break;case"subagent_done":typeof e.spawnId=="string"&&this.agents.set(e.spawnId,"done");break;case"warning":this.notices.push({tone:"warning",text:`Warning: ${S(e.message,500)}`});break;case"navigator_file_error":this.notices.push({tone:"warning",text:`Could not generate ${S(e.filename,200)}: ${S(e.error,500)}`});break;case"attachment_notice":if(Array.isArray(e.notices))for(let n of e.notices){let r=n&&typeof n=="object"?n:{};this.notices.push({tone:"warning",text:`${S(r.name,160)}: ${S(r.message,500)}`})}break;case"provider_fallback":{let n=S(e.from,120),r=S(e.to,120),o=S(e.reason,40).replaceAll("_"," ");this.notices.push({tone:"info",text:`Switched model${n?` from ${n}`:""}${r?` to ${r}`:""}${o?` (${o})`:""}.`});break}case"navigator_file_created":{let n=We(e.file);if(this.files.some(r=>r.fileId===n.fileId))break;this.files.push(n),this.notices.push({tone:"info",text:`Generated ${n.filename} (${N(n.sizeBytes)}). Download: anyforge oracle download ${this.sessionId} ${n.fileId} --output .`});break}case"error":this.notices.push({tone:"error",text:Hn(e)});break;case"done":this.done=e;break}}recordToolCall(e){let n=typeof e.id=="string"?e.id:void 0,r=Wn(e,this.toolIds),o=typeof e.name=="string"?e.name:"";if(o==="ask_user")return;let i=S(typeof e.label=="string"?e.label:Bt(o),120)||"tool",s=n?this.tools.find(c=>c.id===n):void 0;if(s){s.label=i,e.type==="tool_call"&&delete s.started;return}let a={label:i,status:"running",...n?{id:n}:{},...typeof e.spawnId=="string"?{spawnId:e.spawnId}:{},...e.type==="tool_call_start"?{started:!0}:{}};r&&this.answer.trim()&&(a.note=d(this.answer.trim()),this.answer=""),this.tools.push(a),this.phase!=="finishing"&&(this.phase="thinking")}note(e,n){this.notices.push({tone:e,text:d(n)})}finish(e,n){this.end=e,this.endedAt=n}label(){if(this.phase==="cancelling")return"Cancelling";if(this.phase==="uploading")return`Uploading ${this.upload??"attachment"}`;if(this.phase==="sending")return"Sending";if(this.phase==="preparing")return"Preparing context";if(this.phase==="finishing")return"Finishing up";let e=[...this.agents.values()].filter(r=>r==="running").length;if(e)return`Researching (${e} agent${e===1?"":"s"})`;let n=this.tools.filter(r=>r.status==="running"&&!r.spawnId);return n.length===1?`Running ${n[0]?.label}`:n.length>1?`Running ${n.length} tools`:this.phase==="writing"?"Writing":this.serverPhase&&Ia[this.serverPhase]||"Thinking"}statusLine(e,n){let r=[this.label(),qn(e-this.startedAt)];this.round&&r.push(this.maxRounds?`step ${this.round}/${this.maxRounds}`:`step ${this.round}`);let o=e-this.lastSignalAt;return o>=ka&&this.phase!=="cancelling"&&r.push(`no update for ${qn(o)}`),n&&e-this.startedAt>=n*.9&&this.phase!=="finishing"&&r.push("wrapping up"),r.join(" \xB7 ")}summaryLine(){let e=[],n=ct((this.endedAt??this.startedAt)-this.startedAt),r=typeof this.done?.exitReason=="string"?this.done.exitReason:void 0,o;this.end==="complete"?o=Fo("\u2713"):this.end==="awaiting_input"?o=Lo("?")+" Waiting for your answer":this.end==="cancelled"?o=Qn("\u2717")+" Interrupted":this.end==="error"?o=Yn("\u2717")+" Error":o=Qn("\u2717")+` Stopped: ${r&&Sa[r]||"no final answer"}`,e.push(n),this.tools.length&&e.push(`${this.tools.length} tool${this.tools.length===1?"":"s"}`);let i=Ce(this.done?.tokens);(i?.inputTokens!==void 0||i?.outputTokens!==void 0)&&e.push(`${ee(i.inputTokens??0)} in${i.cachedInputTokens?` (${ee(i.cachedInputTokens)} cached)`:""} / ${ee(i.outputTokens??0)} out`);let s=vt(this.done?.servedBy);return s&&e.push(s),`${o} ${He(e.join(" \xB7 "))}`}};function vt(t){if(!t||typeof t!="object")return"";let e=t,n=S(e.model??e.id,100);if(!n)return"";let r=e.fallback&&typeof e.fallback=="object"?e.fallback:void 0;if(r&&typeof r.from=="string"){let o=typeof r.reason=="string"?`: ${S(r.reason,40).replaceAll("_"," ")}`:"";return`${n} (fallback from ${S(r.from,100)}${o})`}return n}var Xt=class{constructor(e,n){this.activity=e;this.view=n}answer=new Me("");getText(){return this.answer.getText()}invalidate(){this.answer.invalidate()}render(e){let n=this.activity;this.answer.setText(n.answer);let r=[pt("assistant")];r.push(...this.renderTools(e)),r.push(...this.answer.render(e));for(let o of n.notices){let i=o.tone==="error"?Yn:o.tone==="warning"?Qn:He;r.push(...Po(i(o.text),e))}return n.finished&&r.push(Qt(n.summaryLine(),e)),r}renderTools(e){let{tools:n}=this.activity;if(!n.length)return[];if(this.activity.finished&&!this.view.expanded()){let s=this.activity.failedCount,a=`Used ${n.length} tool${n.length===1?"":"s"}${s?` \xB7 ${s} failed`:""} (ctrl+o to expand)`;return[Qt(He(` \u23BF ${a}`),e)]}let r=this.activity.finished?n:n.slice(-xa),o=n.length-r.length,i=[];return o&&i.push(Qt(He(` \u23BF \u2026 ${o} earlier tool${o===1?"":"s"}`),e)),r.forEach((s,a)=>{if(s.note)for(let u of Po(s.note,Math.max(10,e-4)).slice(0,3))i.push(` ${He(Aa(u))}`);let c=s.status==="running"?Lo(yt[this.view.frame()%yt.length]??"*"):s.status==="ok"?Fo("\u2713"):Yn("\u2717"),p=a===0&&!o?" \u23BF ":" ",l=s.spawnId?` ${He("agent \xB7")} `:"";i.push(Qt(`${p}${l}${c} ${s.label}`,e))}),i}};import{randomUUID as Ma}from"node:crypto";import{stat as Ua}from"node:fs/promises";import{basename as zo}from"node:path";import{bold as ja,cyan as Wo,dim as Ko,red as nn}from"kleur/colors";import{Container as Da,Editor as Ba,ProcessTerminal as Ga,ScrollView as za,SelectList as Wa,TuiAltScreen as Ka,VStack as Ha,getNativeClipboard as Ja,matchesKey as rn,truncateToWidth as Ho,wrapTextWithAnsi as Ya}from"@earendil-works/pi-tui";import{truncateToWidth as _a,visibleWidth as $a,wrapTextWithAnsi as Uo}from"@earendil-works/pi-tui";import{bold as Pa,cyan as La,dim as ge}from"kleur/colors";import{CombinedAutocompleteProvider as Ea}from"@earendil-works/pi-tui";var Ot=[{name:"help",description:"Show commands and keyboard shortcuts"},{name:"status",description:"Show session, access, model, usage and limits"},{name:"model",description:"Show the workspace model and the model that answered last"},{name:"usage",description:"Show turns and tokens used in this terminal"},{name:"new",args:"[shared]",description:"Start a new conversation",idleOnly:!0,completions:["shared"]},{name:"clear",description:"Clear the screen and start a new private conversation",idleOnly:!0},{name:"resume",args:"[session-id]",description:"Pick or open a saved conversation",idleOnly:!0},{name:"context",args:"[clear|program|initiative|codebase <id>]",description:"Show or change the context for the next message",completions:["clear","program ","initiative ","codebase "]},{name:"attach",args:"<path>",description:"Attach a file to your next message"},{name:"copy",description:"Copy the last answer as Markdown"},{name:"files",description:"List files Oracle generated in this conversation"},{name:"exit",description:"Leave the Oracle",aliases:["quit"]}],Ca=[{name:"design",args:"[topic]",description:"Talk through a technical design before anything is created"},{name:"catchup",description:"Catch up on your work: what needs you, what landed, what is running"}];function No(t){let e=new Set(Ot.flatMap(n=>[n.name,...n.aliases??[]]));return(t??Ca).filter(n=>!e.has(n.name))}function qo(t,e){let n=/^\/([A-Za-z][\w-]*)(?:\s+([\s\S]*))?$/.exec(t.trim());if(!n)return null;let r=(n[1]??"").toLowerCase(),o=(n[2]??"").trim(),i=Ot.find(a=>a.name===r||a.aliases?.includes(r));if(i)return{kind:"local",command:i,arg:o,args:o?o.split(/\s+/):[]};let s=e.find(a=>a.name===r);return s?{kind:"server",command:s}:{kind:"unknown",name:r}}function Mo(t,e){let n=[...Ot.flatMap(i=>[i,...(i.aliases??[]).map(s=>({...i,name:s}))]).map(i=>({name:i.name,description:i.description,...i.args?{argumentHint:i.args}:{},...i.completions?{getArgumentCompletions:s=>Ta(i.completions??[],s)}:{}})),...t.map(i=>({name:i.name,description:i.description,...i.args?{argumentHint:i.args}:{}}))],r=new Ea(n,e),o=/^\s*\/attach\s+/;return{async getSuggestions(i,s,a,c){let p=(i[s]??"").slice(0,a);return s!==0||!p.trimStart().startsWith("/")?null:o.test(p)?r.getSuggestions(i,s,a,{...c,force:!0}):r.getSuggestions(i,s,a,c)},applyCompletion(i,s,a,c,p){return r.applyCompletion(i,s,a,c,p)},shouldTriggerFileCompletion(i,s,a){return s===0&&o.test((i[s]??"").slice(0,a))}}}function Ta(t,e){let n=t.filter(r=>r.startsWith(e.toLowerCase())&&r!==e).map(r=>({value:r,label:r.trim()}));return n.length?n:null}function Zt(t,e){return t?e?"write + approve":"write":"read only"}function Je(t){return t?`${t.kind} ${d(t.id)}`:"no context"}function jo(t,e){let n=[`${Pa(La("AnyForge Oracle"))}${ge(` \xB7 v${d(t.version)}`)}`,`${ue(t.session)} \xB7 ${t.access} \xB7 ${Je(t.context)}`,...t.session.messages.length?[`Resumed: ${d(t.session.title)}`]:[],ge("Only your messages and the files you attach leave this terminal."),ge("/help commands \xB7 /status details")];if(e<24)return n.flatMap(s=>Uo(s,Math.max(1,e)));let r=Math.min(e,76),o=r-4,i=[ge(`\u256D${"\u2500".repeat(r-2)}\u256E`)];for(let s of n.flatMap(a=>Uo(a,o))){let a=_a(s,o);i.push(`${ge("\u2502")} ${a}${" ".repeat(Math.max(0,o-$a(a)))} ${ge("\u2502")}`)}return i.push(ge(`\u2570${"\u2500".repeat(r-2)}\u256F`)),i}function Do(t){let e=(r,o)=>r?`${ge(r)} ${o}`:o,n=[{title:"Commands",rows:Ot.map(r=>[`/${r.name}`,e(r.args,r.aliases?.length?`${r.description} (also /${r.aliases.join(", /")})`:r.description)])}];if(t.server.length){let r=t.server.map(o=>[`/${o.name}`,e(o.args,d(o.description))]);t.server.some(o=>o.name==="catchup")&&!t.canApprove&&r.push(["",ge("Acting on /catchup decisions needs a session started with `anyforge oracle --write --approve`.")]),n.push({title:"Workspace commands (answered by Oracle)",rows:r})}return n.push({title:"Keys",rows:[["Enter","Send. While Oracle works, queues one message for when it finishes"],["Shift+Enter","New line (Ctrl+J also works)"],["Esc","Interrupt the current answer"],["Ctrl+C","Interrupt the answer, or clear the input; press twice to exit"],["Ctrl+D","Exit when the input is empty"],["Ctrl+O","Show or hide tool activity for finished answers"],["Ctrl+Shift+F","Search the conversation"],["Up / Down","Recall messages sent in this terminal"],["Tab","Complete a command, or a file path after /attach"],["PgUp / PgDn","Scroll; Home and End jump to the top and bottom"]]}),n}function Vn(t){let n=[["Model",`workspace default ${t.model?`${d(t.model.provider)}/${d(t.model.id)}`:"chosen by the server"}`]],r=vt(t.lastServedBy);if(r){let o=t.lastServedBy,i=S(o.routing,40).replaceAll("_"," ");n.push(["Last answer",`${r}${i?` \xB7 ${i}`:""}`])}return n}function Xn(t){return[["This run",t.turns?`${t.turns} turn${t.turns===1?"":"s"} \xB7 ${t.tools} tool${t.tools===1?"":"s"} \xB7 ${ee(t.inputTokens)} in / ${ee(t.outputTokens)} out tokens`:"no turns yet"]]}function Zn(t){if(!t?.finished)return[];let e=[ct((t.endedAt??t.startedAt)-t.startedAt)];e.push(`${t.toolCount} tool${t.toolCount===1?"":"s"}${t.failedCount?` (${t.failedCount} failed)`:""}`);let n=U(t.done?.rounds);n!==void 0&&e.push(`${n} round${n===1?"":"s"}`);let r=Ce(t.done?.tokens);(r?.inputTokens!==void 0||r?.outputTokens!==void 0)&&e.push(`${ee(r.inputTokens??0)} in${r.cachedInputTokens?` (${ee(r.cachedInputTokens)} cached)`:""} / ${ee(r.outputTokens??0)} out tokens`);let o=S(t.done?.exitReason,40);return e.push(o?o.replaceAll("_"," "):t.end??""),[["Last turn",e.filter(Boolean).join(" \xB7 ")]]}function Bo(t){let e=t.capabilities?["read","write","approve"].filter(o=>t.capabilities?.[o]).join(", ")||"none":"not reported",n=t.contextText?` \xB7 note ${N(Buffer.byteLength(t.contextText,"utf8"))}`:"",r=fo(t.limits);return[["Session",`${t.session.sessionId} \xB7 ${d(t.session.title)||"Untitled"}`],["Audience",ue(t.session)],["Access",`this session ${Zt(t.write,t.approve)} \xB7 grant ${t.grantScopes.join(", ")||"none"} \xB7 workspace role ${e}`],["Context",`${Je(t.context)}${n}`],...Vn(t),...Zn(t.lastTurn),...Xn(t.totals),["Limits",r.length?r.join(" \xB7 "):"not reported"],...t.identity?[["Workspace",`company ${t.identity.companyId} \xB7 user ${t.identity.uid}`]]:[],["Endpoint",t.endpoint],["Version",`CLI ${d(t.version)} \xB7 protocol ${1}`],["Attached",t.pendingFiles.length?`${t.pendingFiles.map(d).join(", ")} (uploads with your next message)`:"nothing waiting"]]}function Go(t,e){return e.length?e.map((n,r)=>[`${r+1}.`,`${d(n.filename)} (${N(n.sizeBytes)}) \xB7 anyforge oracle download ${t} ${d(n.fileId)} --output .`]):[["Files","Oracle has not generated files in this conversation."]]}import{bold as Fa,cyan as er,dim as en,yellow as Na}from"kleur/colors";import{Input as qa,Text as Te,matchesKey as Ye}from"@earendil-works/pi-tui";var tn=class{constructor(e,n,r){this.tui=e;this.question=n;this.onComplete=r;(n.questions[0]?.options.length??0)===0&&(this.mode="custom"),this.customInput.onSubmit=o=>{let i=o.trim().slice(0,8e3);if(!i){this.errorMessage="Enter a short answer before continuing.",this.tui.requestRender();return}this.recordCurrent(i)},this.customInput.onEscape=()=>{if(!this.question.questions[this.questionIndex]?.options.length){this.question.optional?this.finish(null):this.requireAnswer();return}this.customInput.setValue(""),this.mode="select",this.tui.setFocus(this),this.tui.requestRender()}}focused=!1;questionIndex=0;optionIndex=0;mode="select";errorMessage="";selected=new Map;answers=[];customInput=new qa({prompt:`${er("Your answer: ")} `,placeholder:"Write a short answer and press Enter",placeholderStyle:en});invalidate(){}render(e){let n=this.question.questions[this.questionIndex];if(!n)return[];this.customInput.focused=this.focused&&this.mode==="custom";let r=Math.max(12,e-2),o=this.question.questions.length>1?` \xB7 ${this.questionIndex+1}/${this.question.questions.length}`:"",i=[...new Te(`${Fa(er(d(n.header)))}${en(o)}`,0,0).render(r),...new Te(d(n.question),0,0).render(r),...this.renderOptions(n,r)];return this.mode==="custom"?(i.push(...this.customInput.render(r)),i.push(...new Te(en(`Enter submits your text${n.options.length?" \xB7 Esc back to the choices":this.question.optional?" \xB7 Esc skip":""}`),0,0).render(r))):i.push(...new Te(en(this.instructions(n)),0,0).render(r)),this.errorMessage&&i.push(...new Te(Na(this.errorMessage),0,0).render(r)),i.map(s=>`${er("\u2502")} ${s}`)}handleInput(e){if(this.mode==="custom"){this.customInput.handleInput(e);return}let n=this.question.questions[this.questionIndex];if(!n)return;let r=this.selectedFor(n);if(n.options.length===0){if(e.toLowerCase()==="s"&&this.question.optional){this.finish(null);return}if(Ye(e,"escape")){this.question.optional?this.finish(null):this.requireAnswer();return}this.customInput.handleInput(e);return}if(Ye(e,"up")){this.optionIndex=Math.max(0,this.optionIndex-1),this.errorMessage="",this.tui.requestRender();return}if(Ye(e,"down")){this.optionIndex=Math.min(n.options.length-1,this.optionIndex+1),this.errorMessage="",this.tui.requestRender();return}if(Ye(e,"escape")){this.question.optional?this.finish(null):this.requireAnswer();return}if(e.toLowerCase()==="s"&&this.question.optional){this.finish(null);return}if(e.toLowerCase()==="o"){this.mode="custom",this.tui.setFocus(this),this.tui.requestRender();return}let o=Number(e);if(Number.isInteger(o)&&o>=1&&o<=n.options.length){let i=n.options[o-1]?.id;i&&(this.optionIndex=o-1,n.multiSelect?r.has(i)?r.delete(i):r.add(i):(r.clear(),r.add(i)),this.tui.requestRender());return}if(Ye(e,"space")&&n.multiSelect){let i=n.options[this.optionIndex];i&&(r.has(i.id)?r.delete(i.id):r.add(i.id),this.tui.requestRender());return}if(Ye(e,"enter")){if(r.size===0){this.errorMessage="Select an option explicitly, type a custom answer, or skip if optional.",this.tui.requestRender();return}this.recordCurrent()}}renderOptions(e,n){let r=this.selectedFor(e),o=[];return e.options.forEach((i,s)=>{let a=r.has(i.id),c=e.multiSelect?a?"[x]":"[ ]":a?"(x)":"( )",p=s===this.optionIndex?"\u203A":" ",l=i.recommended?" \xB7 recommended":"",u=i.confirms?" \xB7 confirms the shown action":"",v=`${p} ${s+1}. ${c} ${d(i.label)}${l}${u}`;o.push(...new Te(v,0,0).render(n)),o.push(...new Te(` ${d(i.description)}`,0,0).render(n))}),o}instructions(e){return e.options.length===0?`Write a short answer${this.question.optional?" \xB7 s or Esc skip card":""}`:`\u2191/\u2193 move \xB7 1\u2013${e.options.length} ${e.multiSelect?"toggle":"select"}${e.multiSelect?" \xB7 Space toggle":""} \xB7 Enter continue \xB7 o write your own${this.question.optional?" \xB7 s or Esc skip card":""}`}requireAnswer(){let e=this.question.questions[this.questionIndex];this.errorMessage=e?.options.length?"Oracle needs an answer to continue: pick an option, or press o to write your own.":"Oracle needs an answer to continue: type it and press Enter.",this.tui.requestRender()}selectedFor(e){let n=this.selected.get(e.id);return n||(n=new Set,this.selected.set(e.id,n)),n}recordCurrent(e){let n=this.question.questions[this.questionIndex];if(!n)return;e&&this.selectedFor(n).clear();let r=[...this.selectedFor(n)],o={questionId:n.id,optionIds:r,...e?{customText:e}:{}},i=this.answers.findIndex(s=>s.questionId===n.id);if(i>=0?this.answers.splice(i,1,o):this.answers.push(o),this.customInput.setValue(""),this.mode="select",this.questionIndex<this.question.questions.length-1){this.questionIndex+=1,this.optionIndex=0,this.mode=this.question.questions[this.questionIndex]?.options.length===0?"custom":"select",this.errorMessage="",this.tui.setFocus(this),this.tui.requestRender();return}this.finish({questionId:this.question.id,answers:this.answers.map(s=>({...s}))})}finish(e){this.onComplete(e)}};var Jo=1500,Qa=100,Va="\x1B]133;A\x07";function Qe(t){return{render:t,invalidate(){}}}function Yo(t,e,n){let r=new Me(e);return{getText:()=>r.getText(),invalidate:()=>r.invalidate(),render:o=>t==="user"?["",`${Va}${pt("user",n)}`,...r.render(o)]:[pt("assistant"),...r.render(o)]}}async function Qo(t){let{client:e,config:n}=t,r=new Ga,o=new Ka(r,void 0,void 0,{scrollToEndIndicator:()=>" more below \xB7 End to jump "}),i=new Da,s=new za(i,{follow:"end",primary:!0,overscroll:"chain"}),a=new Ba(o,yo),c=No(n.commands),p=typeof n.limits?.turnSoftMs=="number"?n.limits.turnSoftMs:void 0,l=t.session,u=t.initialContext,v=t.contextText,x=t.canWrite,k=t.canApprove,b,O,R,V,K=[...t.initialFiles],ae=!1,cn=!1,kt=!1,ln=0,_e,cr=0,Ae="",xt,dn,$e=[],Pe={turns:0,inputTokens:0,outputTokens:0,tools:0},It=new Map,ce,gi=Qe(g=>{if(b){let w=Wo(yt[ln%yt.length]??"*");return[Ho(`${w} ${b.activity.statusLine(Date.now(),p)}`,g)]}return ce?[Ho(`${Wo("?")} Waiting for your answer to the question above`,g)]:[]}),hi=Qe(g=>{let w=[Zt(x,k),Je(u),vo(l),K.length?`${K.length} file${K.length===1?"":"s"} attached`:"",O!==void 0?"1 message queued":""];return[Ko(go(g,w,b?"esc to interrupt":"/help \xB7 /status"))]});o.setLayoutRoot(new Ha([{component:s,basis:0,grow:1,minSize:1},{component:gi,basis:"auto",shrink:0},{component:a,basis:"auto",shrink:0,minSize:2},{component:hi,basis:"auto",shrink:0,minSize:1}]));let Y=()=>o.requestRender(),F=g=>o.flash(d(g),3e3),we=g=>{i.addChild(g),Y()},te=(g,w=Ko)=>{let h=d(g);we(Qe(A=>Ya(w(h),Math.max(1,A))))},St=(g,w)=>{we(Qe(h=>["",...mo(g,w,h)]))},wi=()=>{_e||(_e=setInterval(()=>{ln+=1,Y()},Qa),_e.unref())},un=()=>{_e&&clearInterval(_e),_e=void 0},Ve=()=>{ae||o.hasOverlay()||o.setFocus(ce??a)},lr=g=>{R=g;let w=It.get(g.id);w&&i.removeChild(w);let h=new tn(o,g,A=>{It.delete(g.id),ce===h&&(ce=void 0),i.removeChild(h),Oi(A??{questionId:g.id,skipped:!0,answers:[]}),Ve()});It.set(g.id,h),ce=h,we(h)},pn=g=>{i.clear(),It.clear(),ce=void 0,l=g,R=void 0,V=void 0,$e=[],Ae="";let w={version:t.version,session:g,access:Zt(x,k),context:u};we(Qe(A=>jo(w,A)));let h;for(let A of g.messages){we(Yo(A.role,A.content,A.authorName)),A.role==="assistant"&&(Ae=A.content),A.question&&(h=A.question),A.questionResponse&&(te("A stored response was recorded for this question."),h?.id===A.questionResponse.questionId&&(h=void 0));for(let I of A.files??[])$e.some(q=>q.fileId===I.fileId)||$e.push(I),te(`File: ${I.filename} (${I.mediaType}, ${N(I.sizeBytes)}) \xB7 anyforge oracle download ${g.sessionId} ${I.fileId} --output .`)}h&&lr(h),Ve(),wt(g.sessionId,e.endpoint)},dr,yi=new Promise(g=>{dr=g}),Et=()=>{ae||(ae=!0,un(),b?.controller.abort(new Error("Detached from the active Oracle stream.")),o.stop(),dr())},fn=async()=>{let g=b;if(!g||g.cancelling)return;g.cancelling=!0,g.activity.setPhase("cancelling",Date.now()),Y();let{streamId:w,requestId:h}=g;g.controller.abort(new Error("Turn cancelled by user."));let A;if(w)try{A=Ke(await e.cancelTurn(l.sessionId,w))}catch(I){A=`Could not confirm server cancellation: ${Q(I)}`}else if(h)try{A=Ke(await e.cancelRequest(l.sessionId,h))}catch(I){A=`Could not confirm server cancellation by request id: ${Q(I)}`}else A="The turn had not reached the server; nothing was sent.";g.activity.note("info",A),Y()},Le=async g=>{if(b||ae)return;if(g.kind==="submit"&&g.response){let _=R;if(!_||_.id!==g.response.questionId){F("That question is no longer active. Resume the session to refresh it.");return}te(Xa(_,g.response)),R=void 0}else g.kind==="submit"&&we(Yo("user",g.text));let w=l.sessionId,h=new Vt(Date.now(),w),A=new Xt(h,{expanded:()=>kt,frame:()=>ln}),I={activity:h,view:A,controller:new AbortController,cancelling:!1};b=I,we(A),wi();let q=new Ee,gr=_=>{h.apply(_,Date.now()),_.type==="question"&&(lr(Jn(_)),Ve()),Y()},hr={onEvent:gr,onStream:_=>{I.streamId=_},onResponse:()=>{h.phase==="sending"&&h.setPhase("preparing",Date.now())},onActivity:()=>h.touch(Date.now())},ye="error";try{if(g.kind==="replay"){q.streamId=g.streamId,I.streamId=g.streamId,h.setPhase("thinking",Date.now());let _=await Kn(e,w,q,async ne=>{q.accept(ne)&&gr(ne)},I.controller.signal,hr.onActivity);ye=gt(w,_).status}else{let _=g.response?[]:K.splice(0),ne=await Yt(e,w,_,(mn,Ei)=>{h.setUploading(mn,Ei,Date.now()),Y()});I.controller.signal.throwIfAborted(),h.setPhase("sending",Date.now());let Ct=Ma();I.requestId=Ct;let Ii={requestId:Ct,text:g.response?"":g.text,readOnly:!x,...k?{allowApprove:!0}:{},...u?{context:u}:{},...v?{contextText:v}:{},...g.response?{questionResponse:g.response}:{},...ne.length?{attachments:ne.map(mn=>({fileId:mn.fileId}))}:{}},Si=await Jt(e,w,Ii,hr,I.controller.signal,q);ye=gt(w,Si).status}}catch(_){if(I.controller.signal.aborted)ye="cancelled";else{ye="error";let ne=_ instanceof f&&_.hint?` ${d(_.hint)}`:"";h.note("error",`${Q(_)}${ne}`)}}finally{h.finish(ye,Date.now()),b=void 0,un(),ae||(cn=ye==="cancelled"),xt=h,h.answer.trim()&&(Ae=h.answer),h.done?.servedBy&&(dn=h.done.servedBy);let _=Ce(h.done?.tokens);Pe.turns+=1,Pe.tools+=h.toolCount,Pe.inputTokens+=_?.inputTokens??0,Pe.outputTokens+=_?.outputTokens??0;for(let ne of h.files)$e.some(Ct=>Ct.fileId===ne.fileId)||$e.push(ne);Y()}if(!ae){if(I.streamId&&ye!=="cancelled"){let _=await e.getSession(w).catch(()=>{});_&&_.sessionId===l.sessionId&&(l=_)}vi(ye)}},vi=g=>{if(!(ae||b)){if(V){let w=V;V=void 0,Le({kind:"submit",text:"",response:w});return}if(O!==void 0){let w=O;if(O=void 0,g==="complete"&&!ce){Le({kind:"submit",text:w});return}a.getText()||a.setText(w),F("Your queued message is back in the input; press Enter to send it.")}Ve(),Y()}},Oi=g=>{if(b){V=g,F("Answer saved; Oracle continues once the current turn settles.");return}Le({kind:"submit",text:"",response:g})},ur=async g=>{try{let w=await e.getSession(g);pn(w),w.activeStreamId&&Le({kind:"replay",streamId:w.activeStreamId}),F(`Resumed ${w.title||w.sessionId}.`)}catch(w){te(Q(w),nn)}},bi=async()=>{try{let g=await e.listSessions();if(!g.length){F("No saved conversations to resume.");return}let w=new Wa(g.map(q=>({value:q.sessionId,label:q.title,description:ue(q)})),Math.min(12,g.length),ut),h=await Kt(e.endpoint),A=h?g.findIndex(q=>q.sessionId===h):-1;A>=0&&w.setSelectedIndex(A);let I=o.showOverlay(w,{width:"85%",maxHeight:"80%",anchor:"center"});w.onSelect=q=>{I.hide(),ur(q.value)},w.onCancel=()=>{I.hide(),Ve()},I.focus()}catch(g){te(Q(g),nn)}},pr=async g=>{if(g&&(!u||u.kind!=="program"&&u.kind!=="initiative")){F("Use /context program <id> or /context initiative <id> before /new shared.");return}try{let w=await e.createSession(ht(u,g));pn(w),F(`Started a new conversation \xB7 ${ue(w)}.`)}catch(w){te(Q(w),nn)}},Ri=()=>({session:l,write:x,approve:k,grantScopes:[...e.scopes],...n.capabilities?{capabilities:n.capabilities}:{},...u?{context:u}:{},...v?{contextText:v}:{},...n.model?{model:n.model}:{},lastServedBy:dn,...xt?{lastTurn:xt}:{},totals:{...Pe},...n.limits?{limits:n.limits}:{},...n.identity?{identity:n.identity}:{},endpoint:e.endpoint,version:t.version,pendingFiles:K.map(g=>zo(g))}),Ai=async()=>{if(!Ae.trim()){F("There is no answer to copy yet.");return}try{let g=Ja();g?.setText?await g.setText(Ae):r.write(`\x1B]52;c;${Buffer.from(Ae,"utf8").toString("base64")}\x07`),F(`Copied the last answer (${Ae.length} characters).`)}catch(g){F(`Could not copy: ${Q(g)}`)}},ki=async(g,w,h)=>{if(g.idleOnly&&b){F(`Wait for Oracle to finish, or press Esc, before /${g.name}.`);return}switch(g.name){case"help":we(Qe(A=>["",...Do({server:c,canApprove:k}).flatMap(I=>[ja(I.title),...Mn(I.rows,A)])]));return;case"status":St("Status",Bo(Ri()));return;case"model":St("Model",Vn({...n.model?{model:n.model}:{},lastServedBy:dn}));return;case"usage":St("Usage",[...Zn(xt),...Xn(Pe)]);return;case"files":St("Generated files",Go(l.sessionId,$e));return;case"copy":await Ai();return;case"exit":Et();return;case"new":if(h&&h!=="shared"){F("Usage: /new or /new shared");return}await pr(h==="shared");return;case"clear":await pr(!1);return;case"resume":h?await ur(h):await bi();return;case"context":{if(!h){te(`Context: ${Je(u)}${v?" + free-text note":""}. Use /context <program|initiative|codebase> <id> or /context clear.`);return}if(h==="clear")u=void 0;else{let[A,I,...q]=w;if(!["program","initiative","codebase"].includes(A??"")||!I||q.length){F("Usage: /context program <id>, /context initiative <id>, /context codebase <id>, or /context clear");return}u={kind:A,id:I}}te(`Context for your next message: ${Je(u)}.`);return}case"attach":{if(!h){F("Usage: /attach <file path>");return}let A=await Ua(h).catch(()=>{});if(!A?.isFile()){F(`Not a readable file: ${h}`);return}K.push(h),te(`Attached ${zo(h)} (${N(A.size)}). It uploads with your next message.`);return}}},fr=g=>{let w=g.trim();if(!w)return;a.addToHistory(w);let h=qo(w,c);if(h?.kind==="local"){ki(h.command,h.args,h.arg).catch(A=>te(Q(A),nn));return}if(h?.kind==="unknown"){a.setText(w),F(`Unknown command /${h.name}. Type /help to see the commands.`);return}if(b){let A=O!==void 0;O=w,F(A?"Replaced your queued message. It sends when Oracle finishes.":"Queued. It sends when Oracle finishes; Esc interrupts instead."),Y();return}Le({kind:"submit",text:w})},xi=o.addInputListener(g=>{if(rn(g,"ctrl+c")){if(b)return fn(),{consume:!0};if(o.hasOverlay())return;if(o.getFocusedComponent()===a&&a.getText())return a.setText(""),Y(),{consume:!0};let w=Date.now();return w-cr<=Jo?Et():(cr=w,o.flash("Press Ctrl+C again to exit",Jo)),{consume:!0}}if(rn(g,"escape")){let w=ce!==void 0&&o.getFocusedComponent()===ce;return b&&!a.isShowingAutocomplete()&&!o.hasOverlay()&&!w?(fn(),{consume:!0}):void 0}if(rn(g,"ctrl+d"))return o.getFocusedComponent()===a&&!a.getText()?(Et(),{consume:!0}):void 0;if(rn(g,"ctrl+o"))return kt=!kt,F(kt?"Showing tool activity":"Hiding tool activity"),Y(),{consume:!0}}),mr=()=>{let g=!!b;fn().finally(()=>{g&&(cn=!0),Et()})};process.on("SIGINT",mr),a.onSubmit=fr,a.onChange=()=>Y(),a.setAutocompleteProvider(Mo(c,process.cwd())),pn(t.session),o.start(),l.activeStreamId&&Le({kind:"replay",streamId:l.activeStreamId}),t.initialPrompt&&fr(t.initialPrompt),await yi,xi(),process.off("SIGINT",mr),un(),b?.controller.abort(),await r.drainInput().catch(()=>{}),cn&&(process.exitCode=130)}function Xa(t,e){if(e.skipped)return"Skipped the optional Oracle question.";let n=[];for(let r of e.answers){let o=t.questions.find(i=>i.id===r.questionId);for(let i of r.optionIds){let s=o?.options.find(a=>a.id===i);s&&n.push(s.label)}r.customText&&n.push(r.customText)}return`You answered: ${n.map(d).join("; ")||"no options selected"}`}import{createHash as Za,randomBytes as Vo,timingSafeEqual as ec}from"node:crypto";import{createServer as tc}from"node:http";import nc from"open";var rc=6e4,oc=5*6e4,on=new Map;async function tr(t){let e=fe(t.endpoint),n=await Ge(e),r=["read"];t.write&&r.push("write"),t.approve&&(r.includes("write")||r.push("write"),r.push("approve"));let o=Vo(32).toString("base64url"),i=await ac(o);try{let s={client_name:"AnyForge CLI",redirect_uris:[i.redirectUri],grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none"},a=await cc(n.oauth.registration,s);if(typeof a.client_id!="string"||!a.client_id)throw new f("OAuth registration did not return a client id.",m.API_ERROR);let c=Vo(32).toString("base64url"),p=Za("sha256").update(c).digest("base64url"),l={client_id:a.client_id,redirect_uri:i.redirectUri,response_type:"code",scope:r.join(" "),state:o,code_challenge:p,code_challenge_method:"S256",resource:n.resource},u=new URL(n.oauth.authorization);for(let[R,V]of Object.entries(l))u.searchParams.set(R,String(V));t.onAuthorizationUrl?.(u.toString());try{t.openBrowser?await t.openBrowser(u.toString()):await nc(u.toString())}catch{process.stderr.write(`Open this URL to authorize AnyForge Oracle:
266
+ ${u.toString()}
267
+ `)}let v=await i.wait();if(!ti(v.state,o))throw new f("Oracle sign-in callback state did not match; authorization was rejected.",m.API_ERROR);if(v.error)throw new f(`Oracle authorization failed: ${d(v.error)}${v.errorDescription?` (${d(v.errorDescription)})`:""}`,m.API_ERROR);if(!v.code)throw new f("Oracle authorization callback did not include a code.",m.API_ERROR);let x={client_id:a.client_id,grant_type:"authorization_code",code:v.code,redirect_uri:i.redirectUri,code_verifier:c,resource:n.resource},k=await oi(n.oauth.token,x);ri(k);let b=ni(k.scope);if(!b.includes("read")||b.some(R=>!r.includes(R)))throw await rr(n.oauth.revocation,a.client_id,k.access_token,k.refresh_token).catch(()=>{}),new f("The granted Oracle scopes are invalid for this request.",m.API_ERROR);let O={version:1,endpoint:e,clientId:a.client_id,redirectUri:i.redirectUri,accessToken:k.access_token,refreshToken:k.refresh_token,expiresAt:Date.now()+k.expires_in*1e3,scopes:b};return await Ht(()=>Gn(O)),O}finally{await i.close()}}async function Zo(){let t=await Ht(async()=>{let e=await Wt();if(!e)return{hadCredentials:!1,revoked:!1,revokeFailure:void 0};try{let n=await Ge(e.endpoint);return await rr(n.oauth.revocation,e.clientId,e.accessToken,e.refreshToken),await ze(),{hadCredentials:!0,revoked:!0,revokeFailure:void 0}}catch(n){return await ze(),{hadCredentials:!0,revoked:!1,revokeFailure:n}}});if(t.hadCredentials&&!t.revoked)throw new f(`Local Oracle credentials were cleared, but server revocation failed: ${t.revokeFailure instanceof Error?d(t.revokeFailure.message):"unknown error"}`,m.FAILURE,"Run `anyforge oracle login` again if you still need access. The server will expire this access token automatically.");return{hadCredentials:t.hadCredentials,revoked:t.revoked}}async function ei(t){let e=t||process.env.ANYFORGE_ENDPOINT;if(process.env.ANYFORGE_API_TOKEN)return Xo(e);let n=await Wt();if(n&&(!e||fe(e)===n.endpoint)){let r=n.endpoint;return{endpoint:r,accessToken:n.accessToken,scopes:n.scopes.filter(nr),source:"oauth",getAccessToken:async o=>ic(r,o)}}try{return await Xo(e)}catch(r){throw n&&e&&fe(e)!==n.endpoint?new oe:r instanceof oe?new oe:r}}async function Xo(t){let e=await Fe(t),r=(await et({apiKey:e.apiKey,endpoint:e.endpoint})).scopes.filter(nr);return{endpoint:e.endpoint,accessToken:e.apiKey,scopes:r,source:"control"}}async function ic(t,e){let n=fe(t),r=on.get(n);if(r)return r;let o=Ht(()=>sc(n,e));on.set(n,o);try{return await o}finally{on.get(n)===o&&on.delete(n)}}async function sc(t,e){let n=await Wt();if(!n||n.endpoint!==t)throw new oe;if(n.expiresAt>Date.now()+rc&&(!e||n.accessToken!==e))return n.accessToken;let r=await Ge(n.endpoint),o;try{let s={client_id:n.clientId,grant_type:"refresh_token",refresh_token:n.refreshToken,resource:r.resource};o=await oi(r.oauth.token,s)}catch(s){throw s instanceof sn&&["invalid_grant","invalid_client","invalid_target"].includes(s.code)?(await ze(),new oe):s}ri(o);let i;try{i=ni(o.scope)}catch(s){throw await ze(),s}if(i.some(s=>!n?.scopes.includes(s)))throw await rr(r.oauth.revocation,n.clientId,o.access_token,o.refresh_token),await ze(),new f("Oracle refresh attempted to widen the saved grant; credentials were cleared.",m.API_ERROR);return n={...n,accessToken:o.access_token,refreshToken:o.refresh_token,expiresAt:Date.now()+o.expires_in*1e3,scopes:i},await Gn(n),n.accessToken}var sn=class extends f{constructor(n,r,o){super(`OAuth ${n}: ${r}`,o===401?m.NO_CREDENTIALS:m.API_ERROR);this.code=n;this.name="OAuthResponseError"}};async function ac(t){let e,n,r=!1,o=new Promise((c,p)=>{e=c,n=p});o.catch(()=>{});let i=tc((c,p)=>{if((c.url?.length??0)>8192){p.writeHead(414,{"Content-Type":"text/plain; charset=utf-8"}).end("Authorization response is too large.");return}let l=c.url??"/";if(!l.startsWith("/callback")||l.startsWith("//")){p.writeHead(400,{"Content-Type":"text/plain; charset=utf-8"}).end("Authorization response path is invalid.");return}let u=new URL(l,"http://127.0.0.1");if(c.method!=="GET"||u.pathname!=="/callback"){p.writeHead(404,{"Content-Type":"text/plain; charset=utf-8"}).end("Not found");return}let v=u.searchParams.get("code")?.slice(0,2048)??"",x=u.searchParams.get("state")?.slice(0,256)??"",k=u.searchParams.get("error")?.slice(0,256)??void 0,b=u.searchParams.get("error_description")?.slice(0,1024)??void 0;if(!x){p.writeHead(400,{"Content-Type":"text/plain; charset=utf-8"}).end("Authorization response is missing state.");return}if(!ti(x,t)){p.writeHead(400,{"Content-Type":"text/plain; charset=utf-8"}).end("Authorization response state did not match.");return}if(r){p.writeHead(409,{"Content-Type":"text/plain; charset=utf-8"}).end("Authorization response already received.");return}r=!0,p.writeHead(200,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'"}).end('<!doctype html><meta charset="utf-8"><title>AnyForge Oracle</title><p>Sign-in response received. You may close this tab.</p>'),e({code:v,state:x,...k?{error:k}:{},...b?{errorDescription:b}:{}})});await new Promise((c,p)=>{i.once("error",p),i.listen(0,"127.0.0.1",()=>{i.removeListener("error",p),c()})});let s=i.address();if(!s||typeof s=="string")throw new f("Could not bind an Oracle sign-in callback port.",m.FAILURE);let a=setTimeout(()=>{r||(r=!0,n(new f("Oracle sign-in timed out before the browser returned.",m.FAILURE)))},oc);return a.unref(),{redirectUri:`http://127.0.0.1:${s.port}/callback`,wait:()=>o,close:async()=>{clearTimeout(a),i.listening&&(i.closeAllConnections(),await new Promise(c=>i.close(()=>c())))}}}function ti(t,e){let n=Buffer.from(t),r=Buffer.from(e);return n.length===r.length&&ec(n,r)}function ni(t){let e=t.split(/[\s+]+/).filter(Boolean);if(!e.every(nr))throw new f("OAuth server returned an unknown Oracle scope.",m.API_ERROR);let n=[...new Set(e)];if(!n.includes("read"))throw new f("Oracle OAuth grant is missing the required read scope.",m.API_ERROR);return n}function nr(t){return t==="read"||t==="write"||t==="approve"}function ri(t){if(!t||typeof t.access_token!="string"||!t.access_token||typeof t.refresh_token!="string"||!t.refresh_token||typeof t.token_type!="string"||t.token_type.toLowerCase()!=="bearer"||typeof t.expires_in!="number"||!Number.isFinite(t.expires_in)||t.expires_in<=0||typeof t.scope!="string")throw new f("OAuth token response did not match the supported format.",m.API_ERROR)}async function cc(t,e){return ii(t,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(e)})}async function oi(t,e){return ii(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams(Object.entries(e).map(([n,r])=>[n,String(r)])).toString()})}async function ii(t,e){let n;try{n=await fetch(t,{...e,redirect:"error",signal:AbortSignal.timeout(2e4)})}catch(o){throw new f(`OAuth request failed: ${d(o instanceof Error?o.message:String(o))}`,m.FAILURE)}let r=null;try{r=await n.json()}catch{}if(!n.ok){let o=r&&typeof r=="object"?r:{},i=typeof o.error=="string"?o.error:`http_${n.status}`,s=typeof o.error_description=="string"?d(o.error_description):i;throw new sn(i,s,n.status)}if(!r||typeof r!="object"||Array.isArray(r))throw new f(`OAuth endpoint returned invalid JSON (${n.status}).`,m.API_ERROR);return r}async function rr(t,e,n,r){let o=async i=>{let s=await fetch(t,{method:"POST",redirect:"error",signal:AbortSignal.timeout(2e4),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams({client_id:e,token:i}).toString()});if(!s.ok){let a=`HTTP ${s.status}`;try{let c=await s.json();typeof c.error_description=="string"&&(a=d(c.error_description))}catch{}throw new f(`OAuth token revocation failed: ${a}`,m.API_ERROR)}};await o(r),n!==r&&await o(n)}var Ac=1048576,kc=25*1048576;async function ui(t){let[e,...n]=t.positional,r=["exec","sessions","resume","login","logout","download"].includes(e??""),o=r?void 0:t.positional.join(" ");if(j(t.flags,"read-only")&&(j(t.flags,"write")||j(t.flags,"approve")))throw new y("--read-only cannot be combined with --write or --approve.");if(!r&&(!process.stdin.isTTY||!process.stdout.isTTY))throw new y("Interactive Oracle needs a terminal.",'Use `anyforge oracle exec "your question"` for scripts and pipes.');if(e==="login"){if(n.length)throw new y("oracle login does not accept positional arguments.");let x=await tr({endpoint:t.endpointOverride??t.endpoint,write:j(t.flags,"write"),approve:j(t.flags,"approve")}),k=x.scopes.join(", "),O=[j(t.flags,"write")?"write":void 0,j(t.flags,"approve")?"approve":void 0].filter(R=>!!R).filter(R=>!x.scopes.includes(R));process.stdout.write(`Connected to ${x.endpoint} with scopes: ${k}.
268
+ `),O.length&&process.stderr.write(`This account was granted read only; the server withheld: ${O.join(", ")}.
269
+ `);return}if(e==="logout"){if(n.length)throw new y("oracle logout does not accept positional arguments.");let x=await Zo();process.stdout.write(x.hadCredentials?`Oracle credentials cleared and revoked.
270
+ `:`No saved Oracle credentials.
271
+ `);return}let i;try{i=await ai(t.endpointOverride)}catch(x){if(!r&&x instanceof f&&x.exitCode===m.NO_CREDENTIALS&&process.stdin.isTTY&&process.stdout.isTTY){let k=(await import("prompts")).default;if((await k({type:"confirm",name:"login",message:"Sign in to AnyForge Oracle now?",initial:!0})).login!==!0)throw x;let O=await tr({endpoint:t.endpointOverride??t.endpoint});process.stderr.write(`Connected to ${O.endpoint} with scopes: ${O.scopes.join(", ")}.
272
+ `),i=await ai(t.endpointOverride)}else throw x}if(e==="sessions"){if(n.length)throw new y("oracle sessions does not accept positional arguments.");let x=await i.listSessions();Ic(x,t.flags.json===!0);return}if(e==="download"){await Tc(i,n,t.flags);return}if(e==="exec"){if(j(t.flags,"write")||j(t.flags,"approve"))throw new y("oracle exec is read-only. Use interactive `anyforge oracle --write` for authorized actions.");if(t.flags.json===!0&&j(t.flags,"stream-json"))throw new y("Choose either --json or --stream-json.");let x=await xc(n.join(" ")),k=await i.config();ci(i,k),li(bt(t.flags,"context"));let b=Dn(t.flags),O=await i.createSession(ht(b,!1));await wt(O.sessionId,i.endpoint);let R=await Yt(i,O.sessionId,si(t.flags),(K,ae)=>J(process.stderr,`Uploading explicit attachment ${K} (${N(ae)}).
273
+ `)),V={requestId:uc(),text:x,readOnly:!0,...b?{context:b}:{},...bt(t.flags,"context")?{contextText:bt(t.flags,"context")}:{},...R.length?{attachments:R.map(K=>({fileId:K.fileId}))}:{}};await Ec(i,O.sessionId,V,{json:t.flags.json===!0,streamJson:j(t.flags,"stream-json"),config:k});return}if(e==="resume"&&n.length>1)throw new y("oracle resume accepts at most one session id.");if(e==="resume"&&(!process.stdin.isTTY||!process.stdout.isTTY))throw new y("oracle resume is interactive and needs a terminal.","Use `anyforge oracle sessions` to list sessions, or run `anyforge oracle exec` for a new scripted turn.");let s=await i.config();ci(i,s);let a=Dn(t.flags),c=bt(t.flags,"context");li(c);let p=j(t.flags,"shared"),l=j(t.flags,"write")||j(t.flags,"approve");if(l&&(!i.scopes.has("write")||s.capabilities?.write!==!0))throw new f("This Oracle grant or current workspace role does not allow write actions.",m.API_ERROR);if(j(t.flags,"approve")&&(!i.scopes.has("approve")||s.capabilities?.approve!==!0))throw new f("This Oracle grant or current workspace role does not allow approval actions.",m.API_ERROR);if(p&&(!a||a.kind!=="program"&&a.kind!=="initiative"))throw new y("--shared requires --program or --initiative.");let u;if(e==="resume"){let k=(n[0]||void 0)??await Sc(await i.listSessions(),await Kt(i.endpoint));u=await i.getSession(k)}else u=await i.createSession(ht(a,p));await wt(u.sessionId,i.endpoint);let v=await Promise.resolve().then(()=>(ir(),or));await Qo({version:v.default.version,client:i,config:s,session:u,initialPrompt:o,initialContext:a,contextText:c,shared:p,canWrite:l,canApprove:j(t.flags,"approve"),initialFiles:si(t.flags)})}function j(t,e){return t[e]===!0}function bt(t,e){let n=t[e];return typeof n=="string"?n:void 0}function si(t){let e=t.file;return typeof e=="string"?[e]:Array.isArray(e)?e:[]}async function ai(t){try{return new zt(await ei(t))}catch(e){throw e instanceof f&&e.exitCode===m.NO_CREDENTIALS?new f("Not authenticated for the hosted Oracle.",m.NO_CREDENTIALS,"Run `anyforge oracle login`, or set ANYFORGE_API_TOKEN for an existing Control grant."):e}}function ci(t,e){if(!t.scopes.has("read")||e.capabilities?.read===!1)throw new f("This credential does not have Oracle read access.",m.API_ERROR)}function li(t){if(t&&Buffer.byteLength(t,"utf8")>zn)throw new y(`--context must be ${zn/1024} KiB or less.`)}async function xc(t){let e=t.trim();if(process.stdin.isTTY){if(e)return e;throw new y("oracle exec requires a prompt when stdin is a terminal.",'Example: anyforge oracle exec "summarize this program"')}let n=[],r=0;for await(let i of process.stdin){let s=Buffer.isBuffer(i)?i:Buffer.from(i);if(r+=s.byteLength,r>Ac)throw new y("Piped Oracle input exceeds the 1 MiB limit.");n.push(s)}let o=Buffer.concat(n).toString("utf8").trim();if(!e&&!o)throw new y("oracle exec needs a prompt in argv or piped stdin.");return[e,o].filter(Boolean).join(`
274
+
275
+ `)}function Ic(t,e){if(e){process.stdout.write(`${JSON.stringify(t,null,2)}
276
+ `);return}if(!t.length){process.stdout.write(`No Oracle sessions are available.
277
+ `);return}for(let n of t){let r=ue(n);process.stdout.write(`${di(n.sessionId)} ${d(n.title)} ${Rt(r)}
278
+ `)}}async function Sc(t,e){if(!t.length)throw new f("No accessible Oracle sessions are available.",m.API_ERROR);return new Promise((n,r)=>{let o=new vc,i=new Rc(o),s=new bc(di("Resume an Oracle session")),a=new Oc(t.map(u=>({value:u.sessionId,label:u.title,description:ue(u)})),12,ut),c=e?t.findIndex(u=>u.sessionId===e):-1;c>=0&&a.setSelectedIndex(c),i.addChild(s),i.addChild(a),a.onSelect=u=>{l(),n(u.value)},a.onCancel=()=>{l(),r(new f("Session selection cancelled.",m.FAILURE))};let p=()=>{l(),r(new f("Session selection interrupted.",m.INTERRUPTED))},l=()=>{process.off("SIGINT",p),i.stop()};process.once("SIGINT",p),i.start()})}async function J(t,e){t.write(e)||await new Promise(n=>t.once("drain",n))}async function Ec(t,e,n,r){let o=new Ee,i=!1,s,a=new AbortController,c=()=>s??=p(),p=async()=>{if(i=!0,process.exitCode=130,a.abort(new Error("Turn cancelled by Ctrl+C.")),o.streamId)try{let R=await t.cancelTurn(e,o.streamId);await J(process.stderr,`${Ke(R)}
279
+ `)}catch(R){await J(process.stderr,`Could not confirm server cancellation: ${Q(R)}
280
+ `)}else try{let R=await t.cancelRequest(e,n.requestId);await J(process.stderr,`${Ke(R)}
281
+ `)}catch(R){await J(process.stderr,`Could not confirm server cancellation by request id: ${Q(R)}
282
+ `)}},l=()=>{c().catch(()=>{})};process.on("SIGINT",l);let u=async R=>J(process.stdout,`${JSON.stringify(de(R))}
283
+ `),v=new sr(e),x=-1,k=async R=>{if(r.streamJson){if(R.idx<=x)return;R.type!=="finalizing"&&(x=R.idx),await u({...R,sessionId:e})}else await v.emit(R)},b;try{b=await Jt(t,e,n,{onEvent:k},a.signal,o)}catch(R){if(!i)throw process.off("SIGINT",l),R;b=o.outcome()}finally{process.off("SIGINT",l),s&&await s}let O=gt(e,b);return r.streamJson?await u({protocolVersion:1,type:"result",result:O}):r.json?await J(process.stdout,`${JSON.stringify(O,null,2)}
284
+ `):(O.answer&&await J(process.stdout,`${O.answer}
285
+ `),O.status==="awaiting_input"&&await J(process.stderr,`Oracle needs a response; resume this session interactively to answer the question.
286
+ `),O.status==="interrupted"&&await J(process.stderr,`Oracle turn ended without a complete final answer.
287
+ `),O.status==="error"&&O.error&&await J(process.stderr,`Oracle error: ${d(O.error)}
288
+ `),(O.servedBy||O.usage||O.rounds!==void 0)&&await J(process.stderr,Cc(O.servedBy,O.usage,O.rounds))),i?process.exitCode=130:O.status==="error"?process.exitCode=m.API_ERROR:O.status==="interrupted"&&(process.exitCode=m.FAILURE),O}var sr=class{constructor(e){this.sessionId=e}labels=new Map;announced=new Set;async emit(e){let n=this.describe(e);n&&await J(process.stderr,`${n}
289
+ `)}describe(e){switch(e.type){case"tool_call":{if(e.name==="ask_user")return"";let n=S(typeof e.label=="string"?e.label:Bt(S(e.name,240)),120),r=typeof e.id=="string"?e.id:void 0;if(r){if(this.labels.set(r,n),this.announced.has(r))return"";this.announced.add(r)}return`${Rt(typeof e.spawnId=="string"?"Oracle \xB7 agent \xB7":"Oracle \xB7")} ${yc(n)}`}case"tool_result":{if(e.ok!==!1)return"";let n=typeof e.toolUseId=="string"?this.labels.get(e.toolUseId):void 0;return an(`Oracle \xB7 ${n??"a tool"} failed`)}case"warning":return an(`Warning: ${S(e.message,500)}`);case"navigator_file_error":return an(`Could not generate ${S(e.filename,200)}: ${S(e.error,500)}`);case"navigator_file_created":{let n=We(e.file);return Rt(`Generated ${n.filename} (${N(n.sizeBytes)}). Download: anyforge oracle download ${this.sessionId} ${n.fileId} --output .`)}case"provider_fallback":{let n=S(e.from,120),r=S(e.to,120);return Rt(`Oracle switched model${n?` from ${n}`:""}${r?` to ${r}`:""}.`)}case"attachment_notice":return Array.isArray(e.notices)?e.notices.map(n=>{let r=n&&typeof n=="object"?n:{};return`${an("Attachment")} ${S(r.name,160)}: ${S(r.message,500)}`}).join(`
290
+ `):"";default:return""}}};function Cc(t,e,n){let r=[];if(t&&typeof t=="object"){let o=t,i=vt(o);i&&r.push(`model ${i}`),typeof o.routing=="string"&&r.push(S(o.routing.replaceAll("_"," "),40)),typeof o.configuredModel=="string"&&o.configuredModel!==o.model&&r.push(`configured ${S(o.configuredModel,100)}`),typeof o.provider=="string"&&r.push(S(o.provider,100))}return e?.inputTokens!==void 0||e?.outputTokens!==void 0?r.push(`${e.inputTokens??0} in${e.cachedInputTokens?` (${e.cachedInputTokens} cached)`:""} / ${e.outputTokens??0} out tokens`):e?.totalTokens!==void 0&&r.push(`${e.totalTokens} tokens`),n!==void 0&&r.push(`${n} rounds`),r.length?`${Rt(r.join(" \xB7 "))}
291
+ `:""}async function Tc(t,e,n){if(e.length!==2)throw new y("oracle download needs exactly a session id and file id.");let[r,o]=e;if(!r||!o)throw new y("oracle download needs a session id and file id.");let i=bt(n,"output");if(!i)throw new y("oracle download requires --output <existing-directory>.");let s=wc(i),a=await fc(s);if(!(await mc(a)).isDirectory())throw new y("--output must point to an existing directory.");let p=await t.download(r,o),l=$c(_c(p.headers.get("content-disposition"))),u=hc(a,l),v=await Pc(p,kc),x;try{x=await pc(u,"wx",384),await x.writeFile(v)}catch(k){throw k.code==="EEXIST"?new f(`Refusing to overwrite ${u}.`,m.FAILURE):k}finally{await x?.close()}process.stdout.write(`Downloaded ${d(l)} to ${d(u)} (${N(v.byteLength)}).
292
+ `)}function _c(t){if(!t)return"oracle-file.bin";let e=/filename\*=UTF-8''([^;]+)/i.exec(t)?.[1];if(e)try{return decodeURIComponent(e.replace(/^"|"$/g,""))}catch{return"oracle-file.bin"}let n=/filename="?([^";]+)"?/i.exec(t)?.[1];return n?n.trim():"oracle-file.bin"}function $c(t){let e=gc(t.replace(/\\/g,"/")).replace(/[\u0000-\u001f\u007f/:]/g,"_").replace(/^\.+/,"").trim().slice(0,180);return!e||e==="."||e===".."?"oracle-file.bin":e}async function Pc(t,e){if(!t.body)return Buffer.alloc(0);let n=t.body.getReader(),r=[],o=0;try{for(;;){let{value:i,done:s}=await n.read();if(s)break;let a=Buffer.from(i);if(o+=a.byteLength,o>e)throw new f(`Oracle download exceeds the ${N(e)} limit.`,m.API_ERROR);r.push(a)}}catch(i){throw await n.cancel("Oracle download exceeded its byte limit.").catch(()=>{}),i}finally{n.releaseLock()}return Buffer.concat(r)}var mi="https://crew.anyforge.ai",Fc=new Set(["endpoint","limit","status","type","priority","initiative","program","search","assignee","notes","input","context","messages","scope","client","days","profile","interval","codebase","file","output"]),Nc=new Set(["help","version","json","active","archived","all","follow","3way","dry-run","force","shared","write","approve","read-only","stream-json"]);function qc(t){let e={positional:[],flags:{}};for(let n=0;n<t.length;n+=1){let r=t[n];if(!r.startsWith("-")){e.command?e.positional.push(r):e.command=r;continue}let o=r==="-h"?"help":r==="-V"?"version":r==="-f"?"force":r.replace(/^--?/,""),[i,s]=o.includes("=")?[o.slice(0,o.indexOf("=")),o.slice(o.indexOf("=")+1)]:[o,void 0];if(Fc.has(i)){let a=s??t[++n];if(a===void 0)throw new y(`--${i} needs a value.`);if(i==="file"){let c=e.flags.file;e.flags.file=[...typeof c=="string"?[c]:Array.isArray(c)?c:[],a]}else e.flags[i]=a;continue}if(Nc.has(i)){e.flags[i]=s===void 0?!0:s!=="false";continue}throw new y(`Unknown option: ${r}`,"Run `anyforge --help` for the command list.")}return e}function he(t,e){if(t===void 0||typeof t=="boolean")return;let n=Number(t);if(!Number.isFinite(n)||n<=0)throw new y(`--${e} must be a positive number, got "${t}".`);return n}function D(t){return typeof t=="string"?t:void 0}function ar(t){return t===!0?!0:void 0}function Mc(){console.log(`
293
+ ${B("AnyForge Control")} \u2014 governed LLM proxy, and a scriptable AnyForge client.
294
+
295
+ ${B("USAGE")}
264
296
  npx @anyforge/control <command> [options]
265
297
 
266
- ${I("SETUP")}
298
+ ${B("SETUP")}
267
299
  init Authorise this device and patch local agent configs
268
300
  whoami Show the company, scopes and endpoint this key acts as
269
301
  status Check connectivity to the AnyForge proxy
270
302
  logout Clear the saved API key and revert patched configs
271
303
  apply-patch [file] Apply a unified diff (file or stdin) via git apply
272
304
 
273
- ${I("SKILLS")}
305
+ ${B("SKILLS")}
274
306
  skill install anyforge-platform
275
307
  Install the portable AnyForge platform guide
276
308
  (--scope project|user, --client agents|claude|codex)
277
309
 
278
- ${I("APPROVALS")} ${J("(the gate that blocks a human)")}
310
+ ${B("APPROVALS")} ${At("(the gate that blocks a human)")}
279
311
  approvals List HIL approvals awaiting a decision
280
312
  approve <id> Approve one, optionally with --notes
281
313
  reject <id> Reject one, optionally with --notes
282
314
 
283
- ${I("CREWS")}
315
+ ${B("CREWS")}
284
316
  crews List crews (--active for running/paused only)
285
317
  crew <id> Show one crew, its status and recent output
286
318
  crew start <item> Dispatch a crew on a work item (--profile, --follow)
@@ -288,14 +320,23 @@ ${I("CREWS")}
288
320
  halt <id> Stop a crew
289
321
  resume <id> Resume a paused or halted crew
290
322
 
291
- ${I("WORK & WORKFLOWS")}
323
+ ${B("WORK & WORKFLOWS")}
292
324
  work List work items (--status, --type, --priority, --search).
293
325
  Paged: one page by default, --all walks every page
294
326
  workflows List operator workflows
295
327
  run <id> Dispatch a workflow run, with optional --input JSON
296
328
  ask <question> Ask the Oracle one question
297
329
 
298
- ${I("MEASUREMENT")}
330
+ ${B("HOSTED ORACLE")}
331
+ oracle [prompt] Start an interactive hosted Oracle session
332
+ oracle exec <text> Submit one read-only turn (--json or --stream-json)
333
+ oracle resume Resume an Oracle session
334
+ oracle sessions List Oracle sessions
335
+ oracle login Connect an Oracle grant (separate from Control credentials)
336
+ oracle logout Revoke and clear the saved Oracle grant
337
+ oracle --help Show Oracle options and slash commands
338
+
339
+ ${B("MEASUREMENT")}
299
340
  backtest-corpus Can this workspace be back-tested? Counts the incidents
300
341
  that could become graded replay cases (--days 30|90|180|365)
301
342
  review-interception Serious findings the reviewer raised on your pull requests,
@@ -303,9 +344,9 @@ ${I("MEASUREMENT")}
303
344
  review-coverage How many recorded pull requests got reviewed, how fast the
304
345
  author heard back, and what a limit stopped (--days 7|30|90|180)
305
346
 
306
- ${I("OPTIONS")}
347
+ ${B("OPTIONS")}
307
348
  --json Print the raw API payload instead of a table
308
- --endpoint <url> Override the base URL (default: ${J(Tt)})
349
+ --endpoint <url> Override the base URL (default: ${At(mi)})
309
350
  --limit <n> Page size on list commands (work: 1-200, default 50)
310
351
  --all work: follow the cursor until the backlog is exhausted
311
352
  --notes <text> approve/reject: reviewer notes, recorded in the audit trail
@@ -323,10 +364,10 @@ ${I("OPTIONS")}
323
364
  --help, -h Show this message
324
365
  --version, -V Print CLI version
325
366
 
326
- ${I("EXIT CODES")}
367
+ ${B("EXIT CODES")}
327
368
  0 ok 1 failure 2 usage 3 not authenticated 4 API error
328
369
 
329
- ${I("EXAMPLES")}
370
+ ${B("EXAMPLES")}
330
371
  anyforge approvals
331
372
  anyforge approve hil_123 --notes "ship it"
332
373
  anyforge crews --active --json | jq -r '.[].id'
@@ -340,8 +381,60 @@ ${I("EXAMPLES")}
340
381
  anyforge review-interception --days 30
341
382
  anyforge review-coverage --days 30
342
383
  anyforge ask "which initiatives slipped this cycle?"
343
- `)}async function no(e,t,n,o){let r=t.flags.json===!0,[i]=t.positional;switch(e){case"init":return Je({endpoint:n,force:t.flags.force===!0});case"logout":return tt();case"status":return nt({endpoint:n});case"whoami":return Ze({endpoint:o,json:r});case"apply-patch":return rt({patchFile:i,threeWay:t.flags["3way"]===!0,dryRun:t.flags["dry-run"]===!0});case"proxy":console.log(zn("Local proxy mode is coming in a future release.")),console.log(J(" Until then, your agents talk directly to the AnyForge proxy."));return;case"skill":return Pt({subcommand:i,name:t.positional[1],scope:h(t.flags.scope),client:h(t.flags.client),force:t.flags.force===!0,json:r})}let s=await te(o);switch(e){case"approvals":return it({creds:s,json:r,limit:x(t.flags.limit,"limit")});case"approve":return Re({creds:s,json:r,approvalId:i,decision:"APPROVED",notes:h(t.flags.notes)});case"reject":return Re({creds:s,json:r,approvalId:i,decision:"REJECTED",notes:h(t.flags.notes)});case"crews":return st({creds:s,json:r,status:h(t.flags.status),activeOnly:_e(t.flags.active),limit:x(t.flags.limit,"limit")});case"crew":{let[a,l]=t.positional;return a==="start"?ut({creds:s,json:r,workItemId:l,profileId:h(t.flags.profile),follow:t.flags.follow===!0,intervalSeconds:x(t.flags.interval,"interval")}):a==="follow"?Ce({creds:s,json:r,crewId:l,intervalSeconds:x(t.flags.interval,"interval")}):at({creds:s,json:r,crewId:i,messages:x(t.flags.messages,"messages")})}case"halt":return ke({creds:s,json:r,crewId:i,action:"halt"});case"resume":return ke({creds:s,json:r,crewId:i,action:"resume"});case"work":return gt({creds:s,json:r,status:h(t.flags.status),type:h(t.flags.type),priority:h(t.flags.priority),initiative:h(t.flags.initiative),program:h(t.flags.program),search:h(t.flags.search),assignee:h(t.flags.assignee),includeArchived:_e(t.flags.archived),limit:x(t.flags.limit,"limit"),all:_e(t.flags.all)});case"workflows":return mt({creds:s,json:r});case"run":return ht({creds:s,json:r,workflowId:i,input:h(t.flags.input)});case"ask":return wt({creds:s,json:r,question:t.positional.join(" "),context:h(t.flags.context)});case"backtest-corpus":return vt({creds:s,json:r,days:x(t.flags.days,"days")});case"review-interception":return Rt({creds:s,json:r,days:x(t.flags.days,"days")});case"review-coverage":return Et({creds:s,json:r,days:x(t.flags.days,"days")});default:throw new c(`Unknown command: ${e}`,"Run `anyforge --help` for the command list.")}}async function oo(){let e;try{e=eo(process.argv.slice(2))}catch(o){xt(o);return}if(e.flags.version===!0){let o=await Promise.resolve().then(()=>(Ot(),St));console.log(o.default.version);return}if(e.flags.help===!0||!e.command){to();return}let t=h(e.flags.endpoint)??process.env.ANYFORGE_ENDPOINT,n=t??Tt;try{await no(e.command,e,n,t)}catch(o){xt(o)}}function xt(e){e instanceof y&&(process.stderr.write(_t("Error: ")+e.message+`
344
- `),e.hint?process.stderr.write(J(e.hint)+`
345
- `):e instanceof c&&process.stderr.write(J("Run `anyforge --help`.")+`
346
- `),process.exit(e.exitCode)),process.stderr.write(_t("Error: ")+(e instanceof Error?e.message:String(e))+`
347
- `),process.exit(R.FAILURE)}oo();
384
+ `)}function Uc(){console.log(`
385
+ ${B("AnyForge Oracle")} \u2014 chat with your hosted, workspace-governed Oracle.
386
+
387
+ ${B("USAGE")}
388
+ anyforge oracle [initial prompt] [options] Start an interactive session
389
+ anyforge oracle exec <prompt|stdin> [options] Submit one read-only turn
390
+ anyforge oracle resume [session-id] Resume a session in the terminal
391
+ anyforge oracle sessions [--json] List sessions you can access
392
+ anyforge oracle login [--write] [--approve] Connect this CLI to Oracle
393
+ anyforge oracle logout Revoke and clear Oracle login
394
+ anyforge oracle download <session> <file> --output <directory>
395
+
396
+ ${B("OPTIONS")}
397
+ --json Print one final JSON object (exec)
398
+ --stream-json Print event JSONL followed by a final result (exec)
399
+ --shared Share with the company (requires --program or --initiative)
400
+ --write Allow workspace-changing actions for this session
401
+ --approve Allow confirmation-gated actions; also requests write
402
+ --read-only Explicitly keep the session read only
403
+ --program <id> Select an authorized program as context
404
+ --initiative <id> Select an authorized initiative as context
405
+ --codebase <id> Select an authorized codebase as context
406
+ --context <text> Add up to 8 KiB of free-text context
407
+ --file <path> Attach one explicit file; may be repeated
408
+ --endpoint <url> Use this AnyForge deployment
409
+ --help Show this Oracle help without signing in
410
+
411
+ CLI attachment limits are 10 MiB per file and 20 MiB total per turn. Files are
412
+ uploaded only when named with --file or /attach. Generated files require an
413
+ explicit \`oracle download\` destination; existing files are never overwritten.
414
+
415
+ ${B("IN THE CONVERSATION")}
416
+ /help Commands and keys /status Session, model, limits
417
+ /new [shared] Start a conversation /clear Clear and start afresh
418
+ /resume [id] Open a saved conversation /usage Turns and tokens so far
419
+ /context <kind> <id> Change context /files Generated files
420
+ /attach <path> Attach a file /copy Copy the last answer
421
+ /model Model details /exit Leave (also Ctrl+D)
422
+ /design [topic] Talk through a technical design with Oracle
423
+ /catchup What needs you, what landed, what is running
424
+ (acting on its decisions needs --write --approve)
425
+
426
+ Enter sends; while Oracle works it queues one message. Shift+Enter adds a
427
+ line. Esc or Ctrl+C interrupts an answer and keeps the session open; press
428
+ Ctrl+C twice to exit. Ctrl+O shows tool activity; Ctrl+Shift+F searches.
429
+
430
+ Examples:
431
+ anyforge oracle
432
+ anyforge oracle "Summarize the open risks"
433
+ anyforge oracle --shared --program prog_123
434
+ anyforge oracle exec "Summarize current risks" --json
435
+ cat incident.log | anyforge oracle exec "Explain this log" --stream-json
436
+ `)}async function jc(t,e,n,r){let o=e.flags.json===!0,[i]=e.positional;if(t==="oracle")return ui({positional:e.positional,flags:e.flags,endpointOverride:r,endpoint:n});switch(t){case"init":return _r({endpoint:n,force:e.flags.force===!0});case"logout":return Mr();case"status":return Ur({endpoint:n});case"whoami":return Nr({endpoint:r,json:o});case"apply-patch":return Dr({patchFile:i,threeWay:e.flags["3way"]===!0,dryRun:e.flags["dry-run"]===!0});case"proxy":console.log(Lc("Local proxy mode is coming in a future release.")),console.log(At(" Until then, your agents talk directly to the AnyForge proxy."));return;case"skill":return uo({subcommand:i,name:e.positional[1],scope:D(e.flags.scope),client:D(e.flags.client),force:e.flags.force===!0,json:o})}let s=await Fe(r);switch(t){case"approvals":return Br({creds:s,json:o,limit:he(e.flags.limit,"limit")});case"approve":return xn({creds:s,json:o,approvalId:i,decision:"APPROVED",notes:D(e.flags.notes)});case"reject":return xn({creds:s,json:o,approvalId:i,decision:"REJECTED",notes:D(e.flags.notes)});case"crews":return Gr({creds:s,json:o,status:D(e.flags.status),activeOnly:ar(e.flags.active),limit:he(e.flags.limit,"limit")});case"crew":{let[a,c]=e.positional;return a==="start"?Jr({creds:s,json:o,workItemId:c,profileId:D(e.flags.profile),follow:e.flags.follow===!0,intervalSeconds:he(e.flags.interval,"interval")}):a==="follow"?En({creds:s,json:o,crewId:c,intervalSeconds:he(e.flags.interval,"interval")}):zr({creds:s,json:o,crewId:i,messages:he(e.flags.messages,"messages")})}case"halt":return Sn({creds:s,json:o,crewId:i,action:"halt"});case"resume":return Sn({creds:s,json:o,crewId:i,action:"resume"});case"work":return Vr({creds:s,json:o,status:D(e.flags.status),type:D(e.flags.type),priority:D(e.flags.priority),initiative:D(e.flags.initiative),program:D(e.flags.program),search:D(e.flags.search),assignee:D(e.flags.assignee),includeArchived:ar(e.flags.archived),limit:he(e.flags.limit,"limit"),all:ar(e.flags.all)});case"workflows":return Xr({creds:s,json:o});case"run":return Zr({creds:s,json:o,workflowId:i,input:D(e.flags.input)});case"ask":return eo({creds:s,json:o,question:e.positional.join(" "),context:D(e.flags.context)});case"backtest-corpus":return no({creds:s,json:o,days:he(e.flags.days,"days")});case"review-interception":return oo({creds:s,json:o,days:he(e.flags.days,"days")});case"review-coverage":return io({creds:s,json:o,days:he(e.flags.days,"days")});default:throw new y(`Unknown command: ${t}`,"Run `anyforge --help` for the command list.")}}async function Dc(){let t;try{t=qc(process.argv.slice(2))}catch(r){fi(r);return}if(t.flags.version===!0){let r=await Promise.resolve().then(()=>(ir(),or));console.log(r.default.version);return}if(t.command==="oracle"&&t.flags.help===!0){Uc();return}if(t.flags.help===!0||!t.command){Mc();return}let e=D(t.flags.endpoint)??process.env.ANYFORGE_ENDPOINT,n=e??mi;try{if(t.command!=="oracle"){let o=["approve","codebase","file","output","read-only","shared","stream-json","write"].find(i=>t.flags[i]!==void 0);if(o)throw new y(`--${o} is only supported by the oracle command.`)}await jc(t.command,t,n,e)}catch(r){fi(r)}}function fi(t){t instanceof f&&(process.stderr.write(pi("Error: ")+d(t.message)+`
437
+ `),t.hint?process.stderr.write(At(d(t.hint))+`
438
+ `):t instanceof y&&process.stderr.write(At("Run `anyforge --help`.")+`
439
+ `),process.exit(t.exitCode)),process.stderr.write(pi("Error: ")+d(t instanceof Error?t.message:String(t))+`
440
+ `),process.exit(m.FAILURE)}Dc();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@anyforge/control",
3
- "version": "0.4.0",
4
- "description": "AnyForge Control CLI \u2014 governed LLM proxy for any agent, and a scriptable client for AnyForge approvals, crews, work items and workflows.",
3
+ "version": "0.6.0",
4
+ "description": "AnyForge CLI — hosted Oracle conversations, a governed LLM proxy, and a scriptable client for approvals, crews, work items and workflows.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://anyforge.ai",
7
7
  "repository": {
@@ -25,24 +25,28 @@
25
25
  "scripts": {
26
26
  "build": "esbuild src/cli.ts --bundle --minify --platform=node --format=esm --outfile=dist/cli.js --packages=external && chmod +x dist/cli.js",
27
27
  "type-check": "tsc --noEmit",
28
- "test": "tsx --test src/commands/*.test.ts",
28
+ "test": "npm run build && node scripts/test.mjs",
29
29
  "dev": "tsx src/cli.ts",
30
30
  "prepublishOnly": "npm run build",
31
31
  "publish:beta": "npm publish --access public --tag beta",
32
32
  "publish:stable": "npm publish --access public"
33
33
  },
34
34
  "engines": {
35
- "node": ">=22"
35
+ "node": ">=22.19.0"
36
36
  },
37
37
  "dependencies": {
38
+ "@earendil-works/pi-tui": "^0.87.1",
38
39
  "kleur": "^4.1.5",
39
40
  "open": "^10.0.0",
40
- "prompts": "^2.4.2"
41
+ "prompts": "^2.4.2",
42
+ "proper-lockfile": "^4.1.2"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@types/node": "^22.20.0",
44
46
  "@types/prompts": "^2.4.9",
47
+ "@types/proper-lockfile": "^4.1.4",
45
48
  "esbuild": "^0.25.0",
49
+ "node-pty": "^1.1.0",
46
50
  "tsx": "^4.7.0",
47
51
  "typescript": "^7.0.2"
48
52
  }