@anyforge/control 0.2.4 → 0.4.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.
package/dist/cli.js CHANGED
@@ -1,141 +1,347 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * @anyforge/control — entrypoint.
4
- *
5
- * Subcommands:
6
- * init — browser-based auth flow + config patching
7
- * whoami — show current identity
8
- * logout — clear stored credentials
9
- * status — connectivity + key health check
10
- * apply-patch — apply a unified diff (file or stdin) via git apply
11
- * proxy — local proxy mode (Phase 2 — coming soon)
12
- *
13
- * Each subcommand is a separate module under src/commands/. The
14
- * argv parsing here is deliberately tiny — no commander, no yargs.
15
- * For three flags and four commands, hand-rolled is shorter than
16
- * the lib + plugin chain.
17
- */
18
- import { red, yellow, dim, bold } from 'kleur/colors';
19
- import { runInit } from './commands/init.js';
20
- import { runWhoami } from './commands/whoami.js';
21
- import { runLogout } from './commands/logout.js';
22
- import { runStatus } from './commands/status.js';
23
- import { runApplyPatch } from './commands/applyPatch.js';
24
- function parseArgs(argv) {
25
- const a = { positional: [] };
26
- for (let i = 0; i < argv.length; i += 1) {
27
- const v = argv[i];
28
- if (!a.command && !v.startsWith('-')) {
29
- a.command = v;
30
- continue;
31
- }
32
- if (v === '--help' || v === '-h')
33
- a.help = true;
34
- else if (v === '--version' || v === '-V')
35
- a.version = true;
36
- else if (v === '--endpoint' && argv[i + 1]) {
37
- a.endpoint = argv[i + 1];
38
- i += 1;
39
- }
40
- else if (v === '--3way')
41
- a.threeWay = true;
42
- else if (v === '--dry-run')
43
- a.dryRun = true;
44
- else if (v === '--force' || v === '-f')
45
- a.force = true;
46
- else if (!v.startsWith('-'))
47
- a.positional.push(v);
48
- }
49
- return a;
50
- }
51
- function printHelp() {
52
- // eslint-disable-next-line no-console
53
- console.log(`
54
- ${bold('AnyForge Control')} — governed LLM proxy for any agent.
55
-
56
- ${bold('USAGE')}
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?`),`
15
+ `).replace(/\n{3,}/g,`
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:
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:
31
+ `)+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":`---
53
+ name: anyforge-platform
54
+ description: >
55
+ Orient an agent to AnyForge's structure, delivery model, surfaces, live control
56
+ tools, and safety boundaries. Use when an agent is new to AnyForge, needs to
57
+ choose a platform surface or tool, interpret a platform record, or explain how
58
+ AnyForge works.
59
+ compatibility: Any Agent Skills-compatible host or MCP client connected to AnyForge.
60
+ metadata:
61
+ owner: AnyForge
62
+ version: "1.0.0"
63
+ ---
64
+
65
+ # AnyForge platform guide
66
+
67
+ Use this guide for platform orientation. It is static product knowledge. It does
68
+ not contain tenant data, grant permissions, or replace live tool results.
69
+
70
+ ## What AnyForge is
71
+
72
+ AnyForge is an agentic software delivery platform. It connects discovery,
73
+ planning, implementation, review, human approval, and delivery records in one
74
+ audited system. It can govern agents that run in AnyForge and agents that
75
+ connect from an external IDE or terminal.
76
+
77
+ AnyForge is not a coding agent by itself. The Control surface governs and
78
+ exposes the work; Code Studio and Crew provide hosted delivery; external agents
79
+ can use MCP tools or the CLI.
80
+
81
+ ## The platform model
82
+
83
+ | Entity | Meaning | Key boundary |
84
+ | --- | --- | --- |
85
+ | Program | An optional business or technical objective spanning initiatives and, sometimes, codebases. | A program is a container for coordinated outcomes, not a repository. |
86
+ | Codebase | A connected GitHub repository with repository metadata and, when available, AnyForge's Phase 1 and Phase 2 analysis. | Analysis is a snapshot. Confirm current state with live tools and the repository. |
87
+ | Initiative | A scoped unit of work. It belongs to at most one program and has a primary codebase. | Do not assume a program or initiative is ready to run until its live state says so. |
88
+ | Work item | A concrete unit of delivery under an initiative. | It is the unit crews implement, review, and report. |
89
+ | Crew | A governed delivery run. An Engineer agent drives the work and specialist agents can review it. | Human approval gates control high-impact transitions. |
90
+ | Code Studio | AnyForge's browser-based single-agent coding surface. | It is suited to interactive work and can escalate to a Crew. |
91
+ | Oracle | A read-oriented assistant for AnyForge documentation, workspace records, and connected sources. | It should look up facts and say when a source or permission is missing. |
92
+ | Skill | Reusable agent guidance. | A skill changes instructions; it does not grant tools, access, or authority. |
93
+
94
+ Read \`references/entity-model.md\` for the longer relationship and lifecycle
95
+ map.
96
+
97
+ ## Surfaces and when to use them
98
+
99
+ - **Console:** visual control surface for programs, initiatives, crews,
100
+ approvals, settings, reports, and live status.
101
+ - **MCP:** tool and prompt interface for Claude, Cursor, VS Code, and other
102
+ agent clients. MCP exposes live AnyForge records and actions allowed by the
103
+ caller's authenticated scopes.
104
+ - **CLI:** scriptable terminal interface for authentication, approvals, crews,
105
+ work items, workflows, and Oracle questions. It is useful for operators and
106
+ automation, not a replacement for a coding agent.
107
+ - **Code Studio:** hosted interactive coding session for one agent.
108
+ - **Crew:** governed multi-agent delivery when the work needs repeatability,
109
+ specialist review, an audit trail, or human sign-off.
110
+ - **Oracle:** ask how the platform works or ask about workspace data. It is
111
+ read-only by default.
112
+
113
+ Use the surface that matches the job. Use MCP or the CLI for live control from
114
+ an external agent. Use the Console when a visual workflow, approval review, or
115
+ report is easier to inspect there.
116
+
117
+ ## How an external agent should work
118
+
119
+ 1. **Orient.** Read this guide only when the task needs platform context. Use
120
+ the references for deeper details.
121
+ 2. **Discover.** Use read tools before forming claims. Typical starting points
122
+ are \`anyforge_list_repo_index\`, \`anyforge_list_programs\`,
123
+ \`anyforge_list_initiatives\`, \`anyforge_list_work_items\`,
124
+ \`anyforge_list_crews\`, or \`anyforge_list_pending_approvals\`.
125
+ 3. **Ground.** Use the matching \`get\` tool and repository analysis before
126
+ planning codebase-specific work. Treat missing, stale, or partial data as a
127
+ stated limitation.
128
+ 4. **Plan.** For new delivery, make the intent, current context, and observable
129
+ expectations explicit. Program Genesis uses this ICE shape before it creates
130
+ initiatives.
131
+ 5. **Confirm.** Present the proposed write or dispatch action before executing
132
+ it when the user has not already given clear approval. Read-only inspection
133
+ does not need confirmation.
134
+ 6. **Act.** Use the narrowest write or approval tool that matches the confirmed
135
+ action. The Control API re-checks tenant identity and scopes on every call.
136
+ 7. **Verify.** Re-read the affected record after a successful mutation. Do not
137
+ infer a write, dispatch, merge, deployment, or production change from model
138
+ prose alone.
139
+
140
+ The \`genesis\` MCP prompt is a guided flow for scoping a program. The
141
+ \`platform_guide\` MCP prompt and \`anyforge://guide/platform\` resource expose
142
+ this orientation through MCP without requiring a local file.
143
+
144
+ ## Tool and authority rules
145
+
146
+ - Read and list tools are for discovery. They do not prove that a mutation is
147
+ allowed or that the underlying system has changed.
148
+ - Create, update, dispatch, approve, halt, resume, and external-integration
149
+ tools can change state or trigger work. Use them only for the user's stated
150
+ objective and explain the effect.
151
+ - MCP tool annotations are hints for client UX. Authentication, tenant
152
+ isolation, role checks, and scopes are enforced by AnyForge's server-side
153
+ control boundary.
154
+ - Use the live response's entity IDs and URLs. Do not invent IDs, status,
155
+ coverage, repository facts, or completion claims.
156
+ - AnyForge's repository analysis and documentation can explain structure, but
157
+ live records and the current repository are the evidence for current state.
158
+ - Never put API keys, access tokens, customer data, or private workspace facts
159
+ into a portable skill or an agent's reusable context.
160
+
161
+ For tool categories and representative names, read
162
+ \`references/tool-map.md\`. For permissions, provenance, and external-agent
163
+ boundaries, read \`references/operating-boundaries.md\`.
164
+
165
+ ## What this guide does not do
166
+
167
+ This guide does not replace a tenant's procedures, repository instructions, or
168
+ AnyForge's live authorization. It does not tell an agent to bypass a human
169
+ approval, execute a script, merge code, or claim that production changed.
170
+ Tenant skills and repository \`SKILL.md\` files may add local guidance, but they
171
+ remain subordinate to platform permissions and the user's request.
172
+ `,"references/entity-model.md":`# AnyForge entity model
173
+
174
+ This reference expands the short model in the \`anyforge-platform\` skill. Use
175
+ live MCP or Control API results for IDs, status, ownership, dates, and counts.
176
+
177
+ ## Relationships
178
+
179
+ \`\`\`text
180
+ Program (optional)
181
+ \u2514\u2500\u2500 Initiative (one per scoped codebase slice)
182
+ \u251C\u2500\u2500 Codebase (primary repository)
183
+ \u2514\u2500\u2500 Work items
184
+ \u2514\u2500\u2500 Crew runs (when dispatched)
185
+ \`\`\`
186
+
187
+ A program coordinates an outcome. An initiative is the codebase-scoped unit
188
+ that can be planned and dispatched. A work item is a concrete delivery unit.
189
+ A Crew is a run that performs or reviews delivery for work items. A codebase
190
+ can be used as a primary target or as read-only context, subject to the live
191
+ initiative and Crew configuration.
192
+
193
+ ## Delivery lifecycle
194
+
195
+ 1. A user states an objective and its outcome.
196
+ 2. The agent discovers candidate codebases and reads their available analysis.
197
+ 3. The user confirms the scope and the initiative proposal.
198
+ 4. AnyForge stores the program and initiative records.
199
+ 5. An architect can decompose an initiative into a backlog.
200
+ 6. A human reviews the relevant approval gate.
201
+ 7. A Crew can implement, test, and have specialists review the work.
202
+ 8. The user verifies the result and the records are updated from evidence.
203
+
204
+ The exact state machine and available actions are server contracts. Never
205
+ assume that a record can skip a gate because a tool name sounds like it should
206
+ allow it.
207
+
208
+ ## Related concepts
209
+
210
+ - **Code Studio** is the browser-based single-agent surface. It can share a
211
+ conversation with a Crew after escalation.
212
+ - **Crew** is the governed multi-agent surface. A normal run has an Engineer
213
+ driving delivery and specialist roles available for review. Human approval
214
+ gates remain part of the delivery loop.
215
+ - **Oracle** answers platform and workspace questions from the documentation
216
+ and connected records. It is read-only by default.
217
+ - **Skills** are instruction text. Company skills, bundled platform skills,
218
+ and repository skills are different sources with different trust and scope;
219
+ none of them grants authority.
220
+ `,"references/tool-map.md":"# AnyForge MCP tool map\n\nTool names can grow as the platform grows. Use `tools/list` and the current\ntool description as the contract. The names below are representative entry\npoints in the public MCP server.\n\n## Discover and inspect\n\n- `anyforge_list_repo_index` and `anyforge_get_repo_analysis` discover connected\n repositories and their analysis.\n- `anyforge_list_programs` and `anyforge_get_program` inspect program records.\n- `anyforge_list_initiatives` and `anyforge_get_initiative` inspect scoped work.\n- `anyforge_list_work_items` and `anyforge_get_work_item` inspect delivery\n units.\n- `anyforge_list_crews` and `anyforge_get_crew` inspect Crew runs and output.\n- `anyforge_list_pending_approvals` and `anyforge_get_approval_detail` inspect\n human gates.\n- `anyforge_ask_oracle` asks a read-oriented cross-workspace question.\n\n## Plan and write\n\n- `anyforge_create_program` and `anyforge_update_program` manage program\n records.\n- `anyforge_create_initiative`, `anyforge_update_initiative`, and\n `anyforge_dispatch_architect` manage initiative planning and dispatch.\n- `anyforge_create_work_item` and `anyforge_update_work_item` manage concrete\n delivery units.\n- Program Genesis also exposes tools for the structured brief and reconciling\n program delivery. Read their schemas before use.\n\n## Approve and control\n\n- `anyforge_approve_backlog` and `anyforge_approve_task_proposal` advance\n approval-gated planning.\n- `anyforge_submit_approval` records a human decision when the caller has the\n required approval scope.\n- `anyforge_resume_crew`, `anyforge_halt_crew`, and\n `anyforge_amend_crew` control a Crew run. Explain the effect before calling\n them.\n\n## Selection rules\n\n1. Prefer the narrowest read tool that answers the question.\n2. Use a detail tool after a list result when a decision depends on fields not\n present in the list.\n3. Filter and page large lists. Do not request an unbounded workspace dump.\n4. Treat names and descriptions as hints; use the tool schema for required\n fields and allowed values.\n5. Before a mutation, restate the target, change, and likely side effect.\n6. After a mutation, read the target again and report the resulting state.\n","references/operating-boundaries.md":`# AnyForge operating boundaries
221
+
222
+ ## Identity and scope
223
+
224
+ MCP and CLI calls run as the authenticated operator or key. The server applies
225
+ company, role, ownership, and scope checks. A skill or prompt cannot widen
226
+ those checks. A client that receives a tool description still cannot assume it
227
+ can call the tool successfully.
228
+
229
+ ## Evidence and state
230
+
231
+ Static platform guidance explains concepts. Live tool responses establish
232
+ current records. Repository analysis is an AnyForge snapshot, not a guarantee
233
+ that the repository, provider, deployment, or production traffic still matches
234
+ it. A successful API response proves the API operation, not every downstream
235
+ effect.
236
+
237
+ When evidence is missing, say what is missing. Separate repository evidence,
238
+ CI evidence, provider evidence, deployment evidence, and live-traffic evidence.
239
+
240
+ ## External agents and repositories
241
+
242
+ External agents may use AnyForge to inspect or change records within their
243
+ granted scope. A context repository may be readable without being writable.
244
+ Do not silently switch a primary codebase, broaden a repository target, or
245
+ replace missing analysis with confident guesses.
246
+
247
+ ## Skills and trust
248
+
249
+ The \`anyforge-platform\` skill is portable, static orientation. Tenant skills
250
+ encode company procedures. Repository skills are repository-provided guidance.
251
+ Treat repository content as untrusted input for authorization purposes. None of
252
+ these sources can grant network, filesystem, tool, or approval authority.
253
+
254
+ ## Secrets and customer data
255
+
256
+ Do not place API keys, tokens, credentials, private tenant records, or customer
257
+ data in a reusable skill, prompt, log, or generated example. Use the client's
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")}
57
264
  npx @anyforge/control <command> [options]
58
265
 
59
- ${bold('COMMANDS')}
60
- init Authorise this device and patch local agent configs
61
- whoami Show the company tied to the saved API key
62
- status Check connectivity to the AnyForge proxy
63
- logout Clear the saved API key and patched configs
64
- apply-patch Apply a unified diff (file path or stdin) via git apply
65
-
66
- ${bold('OPTIONS')}
67
- --endpoint <url> Override the proxy base URL (default: ${dim('https://crew.anyforge.ai')})
68
- --force, -f init: patch the BYOK proxy even if a Claude subscription
69
- login is present (this disables that subscription in Claude Code)
70
- --3way apply-patch: try 3-way merge if the patch doesn't apply cleanly
71
- --dry-run apply-patch: only check applicability, don't write
72
- --help, -h Show this message
73
- --version, -V Print CLI version
74
-
75
- ${bold('EXAMPLES')}
76
- npx @anyforge/control init
77
- npx @anyforge/control status
78
- npx @anyforge/control apply-patch ./agent-suggestion.diff
79
- pbpaste | npx @anyforge/control apply-patch
80
- npx @anyforge/control apply-patch ./conflict.diff --3way
81
- `);
82
- }
83
- async function main() {
84
- const args = parseArgs(process.argv.slice(2));
85
- if (args.version) {
86
- const pkg = await import('../package.json', { assert: { type: 'json' } });
87
- // eslint-disable-next-line no-console
88
- console.log(pkg.default.version);
89
- return;
90
- }
91
- if (args.help || !args.command) {
92
- printHelp();
93
- return;
94
- }
95
- // The proxy lives at ${endpoint} — same host as Code Studio, Crew,
96
- // and the operator console. Override via --endpoint or
97
- // ANYFORGE_ENDPOINT for staging.
98
- const endpoint = args.endpoint
99
- ?? process.env.ANYFORGE_ENDPOINT
100
- ?? 'https://crew.anyforge.ai';
101
- try {
102
- switch (args.command) {
103
- case 'init':
104
- await runInit({ endpoint, force: args.force });
105
- break;
106
- case 'whoami':
107
- await runWhoami();
108
- break;
109
- case 'status':
110
- await runStatus({ endpoint });
111
- break;
112
- case 'logout':
113
- await runLogout();
114
- break;
115
- case 'apply-patch':
116
- await runApplyPatch({
117
- patchFile: args.positional[0],
118
- threeWay: args.threeWay,
119
- dryRun: args.dryRun,
120
- });
121
- break;
122
- case 'proxy':
123
- // eslint-disable-next-line no-console
124
- console.log(yellow('Local proxy mode is coming in a future release.'));
125
- // eslint-disable-next-line no-console
126
- console.log(dim(' Until then, your agents talk directly to the AnyForge proxy.'));
127
- break;
128
- default:
129
- // eslint-disable-next-line no-console
130
- console.error(red(`Unknown command: ${args.command}`));
131
- printHelp();
132
- process.exit(1);
133
- }
134
- }
135
- catch (err) {
136
- // eslint-disable-next-line no-console
137
- console.error(red('Error: ') + (err instanceof Error ? err.message : String(err)));
138
- process.exit(1);
139
- }
140
- }
141
- void main();
266
+ ${I("SETUP")}
267
+ init Authorise this device and patch local agent configs
268
+ whoami Show the company, scopes and endpoint this key acts as
269
+ status Check connectivity to the AnyForge proxy
270
+ logout Clear the saved API key and revert patched configs
271
+ apply-patch [file] Apply a unified diff (file or stdin) via git apply
272
+
273
+ ${I("SKILLS")}
274
+ skill install anyforge-platform
275
+ Install the portable AnyForge platform guide
276
+ (--scope project|user, --client agents|claude|codex)
277
+
278
+ ${I("APPROVALS")} ${J("(the gate that blocks a human)")}
279
+ approvals List HIL approvals awaiting a decision
280
+ approve <id> Approve one, optionally with --notes
281
+ reject <id> Reject one, optionally with --notes
282
+
283
+ ${I("CREWS")}
284
+ crews List crews (--active for running/paused only)
285
+ crew <id> Show one crew, its status and recent output
286
+ crew start <item> Dispatch a crew on a work item (--profile, --follow)
287
+ crew follow <id> Watch a run until it needs you or ends (--interval)
288
+ halt <id> Stop a crew
289
+ resume <id> Resume a paused or halted crew
290
+
291
+ ${I("WORK & WORKFLOWS")}
292
+ work List work items (--status, --type, --priority, --search).
293
+ Paged: one page by default, --all walks every page
294
+ workflows List operator workflows
295
+ run <id> Dispatch a workflow run, with optional --input JSON
296
+ ask <question> Ask the Oracle one question
297
+
298
+ ${I("MEASUREMENT")}
299
+ backtest-corpus Can this workspace be back-tested? Counts the incidents
300
+ that could become graded replay cases (--days 30|90|180|365)
301
+ review-interception Serious findings the reviewer raised on your pull requests,
302
+ and how many the next round no longer raised (--days 7|30|90|180)
303
+ review-coverage How many recorded pull requests got reviewed, how fast the
304
+ author heard back, and what a limit stopped (--days 7|30|90|180)
305
+
306
+ ${I("OPTIONS")}
307
+ --json Print the raw API payload instead of a table
308
+ --endpoint <url> Override the base URL (default: ${J(Tt)})
309
+ --limit <n> Page size on list commands (work: 1-200, default 50)
310
+ --all work: follow the cursor until the backlog is exhausted
311
+ --notes <text> approve/reject: reviewer notes, recorded in the audit trail
312
+ --profile <id> crew start: saved run profile to dispatch with
313
+ --follow crew start: watch the run instead of returning the id
314
+ --interval <s> crew follow: seconds between polls (5-120, default 10)
315
+ --input <json> run: workflow inputs, e.g. '{"repo":"acme/web"}'
316
+ --context <text> ask: background for the Oracle
317
+ --days <n> backtest-corpus: window (30, 90, 180, 365; default all time)
318
+ review-interception: window (7, 30, 90, 180; default all time)
319
+ review-coverage: window (7, 30, 90, 180; default all time)
320
+ --scope <scope> skill install: project (default) or user
321
+ --client <client> skill install: agents (default), claude, or codex
322
+ --force skill install: replace AnyForge-managed files
323
+ --help, -h Show this message
324
+ --version, -V Print CLI version
325
+
326
+ ${I("EXIT CODES")}
327
+ 0 ok 1 failure 2 usage 3 not authenticated 4 API error
328
+
329
+ ${I("EXAMPLES")}
330
+ anyforge approvals
331
+ anyforge approve hil_123 --notes "ship it"
332
+ anyforge crews --active --json | jq -r '.[].id'
333
+ anyforge crew start wi_123 --follow
334
+ anyforge crew follow crew_456
335
+ anyforge work --status IN_PROGRESS --priority P0
336
+ anyforge work --program prog_123 --all --json | jq -r '.items[].id'
337
+ anyforge run wf_nightly --input '{"repo":"acme/web"}'
338
+ anyforge backtest-corpus --days 90
339
+ anyforge backtest-corpus --json | jq '.backTestable, .biggestGap'
340
+ anyforge review-interception --days 30
341
+ anyforge review-coverage --days 30
342
+ 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();