@anyforge/control 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var Ws=Object.defineProperty;var Gs=(t,e,n)=>()=>{if(n)throw n[0];try{return t&&(e=t(t=0)),e}catch(r){throw n=[r],r}};var Hs=(t,e)=>{for(var n in e)Ws(t,n,{get:e[n],enumerable:!0})};var Nn={};Hs(Nn,{default:()=>zl});var zl,qn=Gs(()=>{zl={name:"@anyforge/control",version:"0.8.
|
|
2
|
+
var Ws=Object.defineProperty;var Gs=(t,e,n)=>()=>{if(n)throw n[0];try{return t&&(e=t(t=0)),e}catch(r){throw n=[r],r}};var Hs=(t,e)=>{for(var n in e)Ws(t,n,{get:e[n],enumerable:!0})};var Nn={};Hs(Nn,{default:()=>zl});var zl,qn=Gs(()=>{zl={name:"@anyforge/control",version:"0.8.1",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:{"@agentclientprotocol/sdk":"^1.9.0","@earendil-works/pi-tui":"^1.0.2",kleur:"^4.1.5",open:"^11.0.4",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.28.2","node-pty":"^1.1.0",tsx:"^4.7.0",typescript:"^7.0.2"}}});import{red as Rs,yellow as Pd,dim as Xe,bold as W}from"kleur/colors";import{execFileSync as Vn}from"child_process";import ta from"open";import Yn from"prompts";import{green as Et,dim as M,bold as Z,yellow as Ce,cyan as an}from"kleur/colors";import{promises as rn}from"fs";import{homedir as Ks}from"os";import{join as Jr,dirname as Hd}from"path";var Qr=Jr(Ks(),".anyforge"),Hn=Jr(Qr,"credentials");async function Xr(t){await rn.mkdir(Qr,{recursive:!0,mode:448}),await rn.writeFile(Hn,JSON.stringify(t,null,2),{mode:384})}async function it(){try{let t=await rn.readFile(Hn,"utf8");return JSON.parse(t)}catch(t){if(t.code==="ENOENT")return null;throw t}}async function Zr(){try{return await rn.unlink(Hn),!0}catch(t){if(t.code==="ENOENT")return!1;throw t}}import{promises as U}from"fs";import{execFileSync as zs}from"child_process";import{homedir as we}from"os";import{join as Y}from"path";import on from"prompts";import{dim as ce,green as zn,yellow as sn}from"kleur/colors";var Te="# === AnyForge Control \u2014 added by `anyforge init` ===",St="# === end AnyForge Control ===",je=".anyforge-bak",Vs=/claude_(max|pro|team|enterprise)/i,Ys=["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 oo(t){let e=[],n=await Js(t);if(n&&e.push(n),t.scope==="repo"){let r=await Qs(t);return r&&e.push(r),e}for(let r of Zs){let o=await r.detect();if(!(!o||!(await on({type:"confirm",name:"go",message:`Patch ${r.label} via shell rc (${ce(o.path)}) ?`,initial:!0})).go))try{await r.patch(o,t),e.push({label:r.label,path:o.path}),console.log(zn(" \u2714")+" "+r.label)}catch(s){console.warn(" \u26A0 Skipped "+r.label+": "+s.message)}}return e}async function Js(t){let e=t.scope==="repo"&&t.repoRoot?{dir:Y(t.repoRoot,".claude"),file:"settings.local.json"}:{dir:Y(we(),".claude"),file:"settings.json"},n=Y(e.dir,e.file);if(!(await on({type:"confirm",name:"go",message:`Patch Claude Code config at ${ce(n)} ?`,initial:!0})).go)return null;await U.mkdir(e.dir,{recursive:!0});let o=await U.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(sn(" \u26A0 Local LLM proxy detected: ")+ce(a)),console.log(ce(" AnyForge only routes Anthropic Claude models. Local models (Ollama,")),console.log(ce(" LM Studio, etc.) will not work in Claude Code after this patch.")),console.log(ce(" To restore: remove ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN")),console.log(ce(` from ${n}`)),console.log(),!(await on({type:"confirm",name:"go",message:"Overwrite existing local LLM proxy config?",initial:!1})).go)?null:(await pe(n)&&!await pe(n+je)&&await U.copyFile(n,n+je),s.ANTHROPIC_BASE_URL=`${t.endpoint}/v1/anthropic`,s.ANTHROPIC_AUTH_TOKEN=t.apiKey,delete s.ANTHROPIC_API_KEY,i.env=s,await U.writeFile(n,JSON.stringify(i,null,2)+`
|
|
3
3
|
`),console.log(zn(" \u2714")+" Claude Code"),{label:t.scope==="repo"?"Claude Code (repo)":"Claude Code (global)",path:n})}async function io(t){let e=Y(we(),".claude"),n=Y(e,"settings.json");await U.mkdir(e,{recursive:!0});let r=await U.readFile(n,"utf8").catch(()=>"{}"),o;try{o=JSON.parse(r)}catch{o={}}let i=o.env??{},s=i.OTEL_EXPORTER_OTLP_ENDPOINT;return s&&!Kn(s,co(t.endpoint))?(console.log(sn(" \u26A0 An OpenTelemetry endpoint is already configured: ")+ce(s)),console.log(ce(" Leaving it untouched. To send Claude Code usage to AnyForge, point")),console.log(ce(" OTEL_EXPORTER_OTLP_ENDPOINT at "+t.endpoint+"/api/otel")),null):(await pe(n)&&!await pe(n+je)&&await U.copyFile(n,n+je),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 U.writeFile(n,JSON.stringify(o,null,2)+`
|
|
4
4
|
`),{label:"Claude Code usage telemetry (global)",path:n})}async function Qs(t){if(!t.repoRoot)return null;let e=Y(t.repoRoot,".env");if(!(await on({type:"confirm",name:"go",message:`Write AnyForge env block to ${ce(e)} ?`,initial:!0})).go)return null;let r=[Te,`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}`,St,""].join(`
|
|
5
5
|
`),o="";try{o=await U.readFile(e,"utf8")}catch{}let i;return o.includes(Te)?i=o.replace(new RegExp(`${Te}[\\s\\S]*?${St}\\n?`),r):o.length===0?i=r:i=o.endsWith(`
|
|
@@ -312,7 +312,7 @@ Reply with explicit option ids:
|
|
|
312
312
|
/answer ${JSON.stringify(n)}${t.optional?`
|
|
313
313
|
Or skip: /skip ${t.id}`:""}`}function bs(t){return{type:"object",properties:Object.fromEntries(t.questions.map((e,n)=>[`answer_${n}`,e.multiSelect?{type:"array",title:e.header,description:e.question,items:{type:"string",enum:e.options.map(r=>r.id)},minItems:1}:{type:"string",title:e.header,description:`${e.question}
|
|
314
314
|
${e.options.map(r=>`${r.id}: ${r.label} \u2014 ${r.description}${r.confirms?" (confirms an action)":""}`).join(`
|
|
315
|
-
`)}`,enum:e.options.map(r=>r.id),enumNames:e.options.map(r=>r.label)}])),required:t.questions.map((e,n)=>`answer_${n}`)}}function Os(t,e){if(!e||typeof e!="object"||Array.isArray(e))return te();let n=e;return Object.keys(n).length!==t.questions.length?te():Dr(t,{questionId:t.id,answers:t.questions.map((r,o)=>({questionId:r.id,optionIds:r.multiSelect?
|
|
315
|
+
`)}`,enum:e.options.map(r=>r.id),enumNames:e.options.map(r=>r.label)}])),required:t.questions.map((e,n)=>`answer_${n}`)}}function Os(t,e){if(!e||typeof e!="object"||Array.isArray(e))return te();let n=e;return Object.keys(n).length!==t.questions.length?te():Dr(t,{questionId:t.id,answers:t.questions.map((r,o)=>{let i=n[`answer_${o}`];return typeof i=="string"&&(r.multiSelect||!r.options.some(s=>s.id===i))?{questionId:r.id,optionIds:[],customText:i}:{questionId:r.id,optionIds:r.multiSelect?i:[i]}})})}function Rd(t){return{client:async()=>new ae(await Pn(t.endpointOverride,t.workspace)),login:async e=>{let n=await xt(t.endpointOverride),r=await Je({endpoint:n.endpoint??$n,workspace:t.workspace,write:t.write,approve:t.approve,signal:e,onAuthorizationUrl:o=>process.stderr.write(`Authorize AnyForge Oracle in your browser:
|
|
316
316
|
${o}
|
|
317
317
|
`)});return new ae(_e(r))}}}var Mn=class{constructor(e,n=Rd(e)){this.options=e;this.dependencies=n}options;dependencies;initialized=!1;formSupported=!1;sessions=new Map;loading=new Set;pendingSetups=0;clientPromise;loginPromise;stopping=!1;lifetime=new AbortController;app(){let e=async n=>{try{return await n()}catch(r){throw Id(r)}};return Ad({name:"anyforge"}).onRequest("initialize",({params:n})=>this.initialize(n)).onRequest("authenticate",({params:n})=>e(async()=>{if(this.ready(),n.methodId!=="oracle-browser")throw N.invalidParams(void 0,"Unknown authentication method. Terminal authentication runs as a separate command.");return await this.authenticate(),{}})).onRequest("session/new",({params:n,client:r})=>e(()=>this.newSession(n,r))).onRequest("session/load",({params:n,client:r})=>e(()=>this.loadSession(n,r))).onRequest("session/prompt",({params:n,client:r})=>e(()=>this.prompt(n,r))).onNotification("session/cancel",({params:n})=>this.cancel(n.sessionId))}initialize(e){if(this.initialized)throw N.invalidRequest(void 0,"This connection is already initialized.");if(!Number.isSafeInteger(e.protocolVersion)||e.protocolVersion<1)throw N.unsupportedProtocolVersion(e.protocolVersion,[1]);return this.initialized=!0,this.formSupported=e.clientCapabilities?.elicitation?.form!=null,{protocolVersion:1,agentInfo:{name:"anyforge",title:"AnyForge Hosted Oracle",version:this.options.version},agentCapabilities:{loadSession:!0,promptCapabilities:{image:!1,audio:!1,embeddedContext:!1},mcpCapabilities:{http:!1,sse:!1}},authMethods:[{id:"oracle-browser",name:"Sign in to AnyForge",description:"Authorize a workspace-scoped Oracle grant in your browser."},...e.clientCapabilities?.auth?.terminal===!0?[{id:"oracle-terminal",name:"Sign in from a terminal",type:"terminal",args:["--login"]}]:[]]}}ready(){if(!this.initialized)throw N.invalidRequest(void 0,"Initialize the ACP connection first.");if(this.stopping)throw N.requestCancelled()}client(){return this.clientPromise??=this.dependencies.client().catch(e=>{throw this.clientPromise=void 0,e}),this.clientPromise}async authenticate(){if(this.sessions.size||this.pendingSetups)throw N.invalidRequest(void 0,"Restart this ACP process to change authentication after opening a session.");return this.loginPromise||(this.loginPromise=this.dependencies.login(this.lifetime.signal).then(async e=>{await e.config(this.lifetime.signal),this.clientPromise=Promise.resolve(e)}).finally(()=>{this.loginPromise=void 0})),this.loginPromise}validateSetup(e){if(this.ready(),!Od(e.cwd))throw N.invalidParams(void 0,"ACP cwd must be an absolute path. Hosted mode does not read this directory.");if(this.loginPromise)throw N.invalidRequest(void 0,"Wait for authentication to finish.");if(this.sessions.size+this.pendingSetups>=128&&(!("sessionId"in e)||!this.sessions.has(e.sessionId)))throw N.invalidRequest(void 0,"This ACP process has reached its 128-session limit. Restart it to open more sessions.")}async newSession(e,n){this.validateSetup(e),this.pendingSetups+=1;try{let r=await this.client();await r.config(this.lifetime.signal);let o=await r.createSession({visibility:"private",...this.options.context?{context:this.options.context}:{}},this.lifetime.signal);if(this.stopping)throw N.requestCancelled();return this.sessions.set(o.sessionId,{client:r,mcpNoticePending:e.mcpServers.length>0}),e.mcpServers.length&&await this.mcpNotice(n,o.sessionId),{sessionId:o.sessionId}}finally{this.pendingSetups-=1}}async loadSession(e,n){if(this.validateSetup(e),this.sessions.get(e.sessionId)?.active||this.loading.has(e.sessionId))throw N.invalidRequest(void 0,"Cannot load a session during an active prompt or load.");this.loading.add(e.sessionId),this.pendingSetups+=1;try{let r=await this.client(),o=await r.getSession(e.sessionId,this.lifetime.signal);if(o.activeStreamId)throw N.invalidRequest(void 0,"The hosted session is still running. Wait for it to settle before loading it.");if(this.stopping)throw N.requestCancelled();this.sessions.set(e.sessionId,{client:r,question:xd(o),mcpNoticePending:e.mcpServers.length>0}),e.mcpServers.length&&await this.mcpNotice(n,e.sessionId);for(let i of o.messages)i.content&&await this.update(n,e.sessionId,{sessionUpdate:i.role==="user"?"user_message_chunk":"agent_message_chunk",content:{type:"text",text:u(i.content)}}),i.question&&await this.text(n,e.sessionId,It(i.question));return{}}finally{this.loading.delete(e.sessionId),this.pendingSetups-=1}}prompt(e,n){this.ready();let r=this.sessions.get(e.sessionId);if(!r)throw N.invalidParams(void 0,"Load or create this session before prompting it.");if(r.active||this.loading.has(e.sessionId))throw N.invalidRequest(void 0,"A prompt or load is already running in this session. Wait for it to settle.");let o=ws(e.prompt),i=r.question?vs(r.question,o):void 0;if(!r.question&&(o.startsWith("/answer ")||o.startsWith("/skip ")))throw N.invalidParams(void 0,"There is no pending question in this session.");let s={cancelled:!1,abort:new AbortController,interaction:new AbortController,done:Promise.resolve({stopReason:"end_turn"})};return r.active=s,s.done=this.runPrompt(r,s,e.sessionId,i?"":o,i,n).finally(()=>{s.requestId=void 0,s.interaction.abort(),s.abort.abort(),s.deadline&&clearTimeout(s.deadline),r.active=void 0}),s.done}async runPrompt(e,n,r,o,i,s){e.mcpNoticePending&&(await this.mcpNotice(s,r),e.mcpNoticePending=!1);let a=await e.client.config(this.lifetime.signal);if(n.cancelled)return{stopReason:"cancelled"};let c=this.options.write&&e.client.scopes.has("write")&&a.capabilities?.write===!0,d=c&&this.options.approve&&e.client.scopes.has("approve")&&a.capabilities?.approve===!0;for(let p=0;p<20;p+=1){if(n.cancelled)return{stopReason:"cancelled"};n.requestId=bd();let l={requestId:n.requestId,text:o,readOnly:!c,...d?{allowApprove:!0}:{},...this.options.context?{context:this.options.context}:{},...this.options.contextText?{contextText:this.options.contextText}:{},...i?{questionResponse:i}:{}},A=new xe,k=new Set,x=await wt(e.client,r,l,{onEvent:async g=>{if(g.type==="delta"&&typeof g.content=="string")await this.text(s,r,u(g.content));else if((g.type==="tool_call"||g.type==="tool_call_start")&&typeof g.id=="string"&&!k.has(g.id))k.add(g.id),await this.update(s,r,{sessionUpdate:"tool_call",toolCallId:g.id,title:typeof g.label=="string"?u(g.label).slice(0,200):ct(typeof g.name=="string"?g.name:""),status:"in_progress"});else if(g.type==="tool_result"&&typeof g.toolUseId=="string"&&k.has(g.toolUseId))await this.update(s,r,{sessionUpdate:"tool_call_update",toolCallId:g.toolUseId,status:g.ok===!1?"failed":"completed"});else if(g.type==="question")e.question=Wt(g);else if(g.type==="navigator_file_created"){let R=qe(g.file);await this.text(s,r,`Generated file: ${R.filename} (${R.mediaType}, ${R.sizeBytes} bytes). Session id: ${r}; file id: ${R.fileId}. Download using anyforge oracle download with an explicit output directory.`)}}},n.abort.signal,A);if(n.requestId=void 0,x.error)throw N.internalError(void 0,x.error);if(i&&(e.question=x.question),n.cancelled||x.done?.exitReason==="cancelled")return{stopReason:"cancelled"};if(!x.question){let g=x.done?.exitReason;if(g==="completed")return{stopReason:"end_turn"};if(g==="max_tokens"||g==="token_budget")return{stopReason:"max_tokens"};if(g==="max_rounds")return{stopReason:"max_turn_requests"};throw N.internalError(void 0,`Oracle stopped before completion (${typeof g=="string"?u(g):"unknown reason"}). Resume the hosted session to inspect it.`)}if(e.question=x.question,!this.formSupported)return await this.text(s,r,It(x.question)),{stopReason:"end_turn"};let b;try{b=await _d(s.request("elicitation/create",{mode:"form",sessionId:r,message:It(x.question),requestedSchema:bs(x.question)},{cancellationSignal:n.interaction.signal}),n.interaction.signal)}catch(g){if(n.cancelled)return{stopReason:"cancelled"};if(!(g instanceof N)||g.code!==-32601)throw g;return await this.text(s,r,It(x.question)),{stopReason:"end_turn"}}if(n.cancelled)return{stopReason:"cancelled"};if(b.action!=="accept")return await this.text(s,r,`No answer was submitted. The question remains pending.
|
|
318
318
|
`+It(x.question)),{stopReason:"end_turn"};i=Os(x.question,b.content),o=""}return{stopReason:"max_turn_requests"}}async cancel(e){let n=this.sessions.get(e),r=n?.active;if(!n||!r||(r.cancelled=!0,r.interaction.abort(),!r.requestId))return;r.deadline??=setTimeout(()=>r.abort.abort(new Error("Oracle cancellation did not settle within 15 seconds. Inspect the hosted session before retrying.")),15e3);let o=r.requestId;r.cancel??=this.cancelUntilClaimed(n.client,e,o,r).catch(i=>{r.requestId&&(process.stderr.write(`Oracle cancellation request failed: ${z(i)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anyforge/control",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
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",
|