@fifthrevision/axle-cli 0.22.1 → 0.23.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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import{t as e}from"./memory-BmDsikXS.js";import{t}from"./store-BFxiiKeG.js";import{a as n,i as r,n as i,r as a,t as o}from"./tools-Cp5kiYmz.js";import{Command as s}from"@commander-js/extra-typings";import{Agent as c,Anthropic as l,Gemini as u,Instruct as d,MCP as f,OpenAI as p,SimpleWriter as m,Tracer as h,addStats as g,anthropic as _,chatCompletions as v,createAgentConfig as ee,createStats as y,gemini as te,loadFileContent as b,openai as x}from"@fifthrevision/axle";import{createHash as S}from"node:crypto";import{z as C}from"zod";import{appendFile as w,mkdir as ne,readFile as T}from"node:fs/promises";import{basename as re,dirname as ie,extname as E}from"node:path";import{config as ae}from"dotenv";import oe from"yaml";import{glob as se}from"glob";import{createInterface as ce}from"node:readline";var le=`0.22.1`;async function ue(e,t){let n=[];for(let r of e){let e=new f(r);await e.connect({tracer:t}),n.push(e)}return n}async function de(e,t){for(let n of e)try{await n.close({tracer:t})}catch{}}function fe(e,t){switch(e){case`calculator`:return n;case`exec`:{let e=t?.exec;return e&&r.configure(e),r}case`patch-file`:return a;case`read-file`:return i;case`write-file`:return o;default:throw Error(`Unknown tool: ${e}`)}}function pe(e,t){return e.map(e=>fe(e,t))}function D(e,t){let n=e.config??{};switch(e.type){case`openai`:{let e={...t.openai,...n},r=O(e);if(!r)throw Error(`The provider openai is not configured. Please check your configuration.`);return{provider:x(r,{maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:e.model||p.DefaultModel}}case`anthropic`:{let e={...t.anthropic,...n},r=O(e);if(!r)throw Error(`The provider anthropic is not configured. Please check your configuration.`);return{provider:_(r,{maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:e.model||l.DefaultModel}}case`gemini`:{let e={...t.gemini,...n},r=O(e);if(!r)throw Error(`The provider gemini is not configured. Please check your configuration.`);return{provider:te(r,{maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:e.model||u.DefaultModel}}case`chatcompletions`:{let e={...t.chatcompletions,...n},r=e[`base-url`],i=e.model;if(!r||!i)throw Error(`The provider chatcompletions is not configured. Please check your configuration.`);return{provider:v(r,{apiKey:O(e),maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:i}}default:throw Error(`Unknown provider type: ${e.type}`)}}function O(e){let t=e.apiKeyEnv??e[`api-key-env`];return typeof t==`string`&&t.length>0?process.env[t]:e[`api-key`]}function k(e){let{type:t,...n}=e.provider,r=Object.keys(n).length>0?{type:t,config:n}:{type:t};return{version:1,name:e.name,provider:r,tools:e.tools?.map(e=>({name:e})),providerTools:e.provider_tools?.map(e=>({name:e})),mcps:e.mcps}}async function A(n,r,i){let a=k(n),o=a.mcps?.length?await ue(a.mcps,i):[],s=await ee(a,e=>{let t=D(e.provider,r);return{provider:t.provider,model:t.model,tools:e.tools?.length?pe(e.tools.map(e=>e.name)):void 0,mcps:o.length>0?o:void 0}});return{agentConfig:{...s,memory:new e({provider:s.provider,model:s.model,store:new t(`.axle`)})},mcps:o}}C.object({value:C.string()}),C.object({timeout:C.number().optional(),maxBuffer:C.number().optional(),cwd:C.string().optional()});const j={"api-key":C.string().optional(),apiKeyEnv:C.string().optional(),"api-key-env":C.string().optional()},M={maxRetries:C.number().int().nonnegative().optional(),timeoutMs:C.number().int().positive().optional()},N=C.strictObject({type:C.literal(`chatcompletions`),"base-url":C.string().optional(),model:C.string().optional(),...j,...M}),P=C.strictObject({type:C.literal(`anthropic`),model:C.string().optional(),...j,...M}),F=C.strictObject({type:C.literal(`openai`),model:C.string().optional(),...j,...M}),I=C.strictObject({type:C.literal(`gemini`),model:C.string().optional(),...j,...M}),L=C.discriminatedUnion(`type`,[N,P,F,I]),R=C.object({transport:C.literal(`stdio`),name:C.string().optional(),command:C.string(),args:C.array(C.string()).optional(),env:C.record(C.string(),C.string()).optional()}),z=C.object({transport:C.literal(`http`),name:C.string().optional(),url:C.string(),headers:C.record(C.string(),C.string()).optional()}),B=C.discriminatedUnion(`transport`,[R,z]),me=C.object({files:C.string(),resume:C.boolean().default(!1),concurrency:C.number().int().positive().default(3)}),he=C.object({name:C.string().optional(),provider:L,task:C.string(),tools:C.array(C.string()).optional(),provider_tools:C.array(C.string()).optional(),files:C.array(C.string()).optional(),mcps:C.array(B).optional(),batch:me.optional()});async function ge(e,t){let{tracer:n}=t,r=E(e).slice(1);if(r!==`yaml`&&r!==`yml`)throw Error(`Invalid job file format. Expected .yaml or .yml`);let i;try{i=await T(e,{encoding:`utf-8`})}catch{throw Error(`Job File not found, see --help for details`)}let a=oe.parse(i);n?.debug(`Job config: `+JSON.stringify(a,null,2));let o=he.safeParse(a);if(!o.success)throw Error(`The job file is not valid:\n${xe(o.error)}`);return o.data.name||(o.data.name=re(e,E(e))),o.data}async function _e(e){let{tracer:t}=e;ae({quiet:!0});let n=ve();return t?.debug(`Service config: `+JSON.stringify(V(n),null,2)),n}function ve(){return ye({openai:process.env.OPENAI_API_KEY?{"api-key":process.env.OPENAI_API_KEY,model:process.env.OPENAI_MODEL}:void 0,anthropic:process.env.ANTHROPIC_API_KEY?{"api-key":process.env.ANTHROPIC_API_KEY,model:process.env.ANTHROPIC_MODEL}:void 0,gemini:process.env.GEMINI_API_KEY?{"api-key":process.env.GEMINI_API_KEY,model:process.env.GEMINI_MODEL}:void 0,chatcompletions:process.env.CHATCOMPLETIONS_BASE_URL&&process.env.CHATCOMPLETIONS_MODEL?{"base-url":process.env.CHATCOMPLETIONS_BASE_URL,model:process.env.CHATCOMPLETIONS_MODEL,"api-key":process.env.CHATCOMPLETIONS_API_KEY}:void 0})}function ye(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e&&Object.keys(e).length>0))}function V(e){return Array.isArray(e)?e.map(e=>V(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,be(e)&&t?`[redacted]`:V(t)]))}function be(e){return e===`api-key`||e.toLowerCase().includes(`secret`)}function xe(e){return e.issues.map(e=>` - ${e.path.join(`.`)||`root`}: ${e.message}`).join(`
2
+ import{t as e}from"./memory-BS-U-K_q.js";import{t}from"./store-BFxiiKeG.js";import{a as n,i as r,n as i,r as a,t as o}from"./tools-Cp5kiYmz.js";import{Command as s}from"@commander-js/extra-typings";import{Agent as c,Anthropic as l,Gemini as u,Instruct as d,MCP as f,OpenAI as p,SimpleWriter as m,Tracer as h,addStats as g,anthropic as _,chatCompletions as v,createAgentConfig as ee,createStats as y,gemini as te,loadFileContent as b,openai as x}from"@fifthrevision/axle";import{createHash as S}from"node:crypto";import{z as C}from"zod";import{appendFile as w,mkdir as ne,readFile as T}from"node:fs/promises";import{basename as re,dirname as ie,extname as E}from"node:path";import{config as ae}from"dotenv";import oe from"yaml";import{glob as se}from"glob";import{createInterface as ce}from"node:readline";var le=`0.23.0`;async function ue(e,t){let n=[];for(let r of e){let e=new f(r);await e.connect({span:t}),n.push(e)}return n}async function de(e,t){for(let n of e)try{await n.close({span:t})}catch{}}function fe(e,t){switch(e){case`calculator`:return n;case`exec`:{let e=t?.exec;return e&&r.configure(e),r}case`patch-file`:return a;case`read-file`:return i;case`write-file`:return o;default:throw Error(`Unknown tool: ${e}`)}}function pe(e,t){return e.map(e=>fe(e,t))}function D(e,t){let n=e.config??{};switch(e.type){case`openai`:{let e={...t.openai,...n},r=O(e);if(!r)throw Error(`The provider openai is not configured. Please check your configuration.`);return{provider:x(r,{maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:e.model||p.DefaultModel}}case`anthropic`:{let e={...t.anthropic,...n},r=O(e);if(!r)throw Error(`The provider anthropic is not configured. Please check your configuration.`);return{provider:_(r,{maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:e.model||l.DefaultModel}}case`gemini`:{let e={...t.gemini,...n},r=O(e);if(!r)throw Error(`The provider gemini is not configured. Please check your configuration.`);return{provider:te(r,{maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:e.model||u.DefaultModel}}case`chatcompletions`:{let e={...t.chatcompletions,...n},r=e[`base-url`],i=e.model;if(!r||!i)throw Error(`The provider chatcompletions is not configured. Please check your configuration.`);return{provider:v(r,{apiKey:O(e),maxRetries:e.maxRetries,timeoutMs:e.timeoutMs}),model:i}}default:throw Error(`Unknown provider type: ${e.type}`)}}function O(e){let t=e.apiKeyEnv??e[`api-key-env`];return typeof t==`string`&&t.length>0?process.env[t]:e[`api-key`]}function k(e){let{type:t,...n}=e.provider,r=Object.keys(n).length>0?{type:t,config:n}:{type:t};return{version:1,name:e.name,provider:r,tools:e.tools?.map(e=>({name:e})),providerTools:e.provider_tools?.map(e=>({name:e})),mcps:e.mcps}}async function A(n,r,i){let a=k(n),o=a.mcps?.length?await ue(a.mcps,i):[],s=await ee(a,e=>{let t=D(e.provider,r);return{provider:t.provider,model:t.model,tools:e.tools?.length?pe(e.tools.map(e=>e.name)):void 0,mcps:o.length>0?o:void 0}});return{agentConfig:{...s,memory:new e({provider:s.provider,model:s.model,store:new t(`.axle`)})},mcps:o}}C.object({value:C.string()}),C.object({timeout:C.number().optional(),maxBuffer:C.number().optional(),cwd:C.string().optional()});const j={"api-key":C.string().optional(),apiKeyEnv:C.string().optional(),"api-key-env":C.string().optional()},M={maxRetries:C.number().int().nonnegative().optional(),timeoutMs:C.number().int().positive().optional()},N=C.strictObject({type:C.literal(`chatcompletions`),"base-url":C.string().optional(),model:C.string().optional(),...j,...M}),P=C.strictObject({type:C.literal(`anthropic`),model:C.string().optional(),...j,...M}),F=C.strictObject({type:C.literal(`openai`),model:C.string().optional(),...j,...M}),I=C.strictObject({type:C.literal(`gemini`),model:C.string().optional(),...j,...M}),L=C.discriminatedUnion(`type`,[N,P,F,I]),R=C.object({transport:C.literal(`stdio`),name:C.string().optional(),command:C.string(),args:C.array(C.string()).optional(),env:C.record(C.string(),C.string()).optional()}),z=C.object({transport:C.literal(`http`),name:C.string().optional(),url:C.string(),headers:C.record(C.string(),C.string()).optional()}),B=C.discriminatedUnion(`transport`,[R,z]),me=C.object({files:C.string(),resume:C.boolean().default(!1),concurrency:C.number().int().positive().default(3)}),he=C.object({name:C.string().optional(),provider:L,task:C.string(),tools:C.array(C.string()).optional(),provider_tools:C.array(C.string()).optional(),files:C.array(C.string()).optional(),mcps:C.array(B).optional(),batch:me.optional()});async function ge(e,t){let{span:n}=t,r=E(e).slice(1);if(r!==`yaml`&&r!==`yml`)throw Error(`Invalid job file format. Expected .yaml or .yml`);let i;try{i=await T(e,{encoding:`utf-8`})}catch{throw Error(`Job File not found, see --help for details`)}let a=oe.parse(i);n?.debug(`Job config: `+JSON.stringify(a,null,2));let o=he.safeParse(a);if(!o.success)throw Error(`The job file is not valid:\n${xe(o.error)}`);return o.data.name||(o.data.name=re(e,E(e))),o.data}async function _e(e){let{span:t}=e;ae({quiet:!0});let n=ve();return t?.debug(`Service config: `+JSON.stringify(V(n),null,2)),n}function ve(){return ye({openai:process.env.OPENAI_API_KEY?{"api-key":process.env.OPENAI_API_KEY,model:process.env.OPENAI_MODEL}:void 0,anthropic:process.env.ANTHROPIC_API_KEY?{"api-key":process.env.ANTHROPIC_API_KEY,model:process.env.ANTHROPIC_MODEL}:void 0,gemini:process.env.GEMINI_API_KEY?{"api-key":process.env.GEMINI_API_KEY,model:process.env.GEMINI_MODEL}:void 0,chatcompletions:process.env.CHATCOMPLETIONS_BASE_URL&&process.env.CHATCOMPLETIONS_MODEL?{"base-url":process.env.CHATCOMPLETIONS_BASE_URL,model:process.env.CHATCOMPLETIONS_MODEL,"api-key":process.env.CHATCOMPLETIONS_API_KEY}:void 0})}function ye(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e&&Object.keys(e).length>0))}function V(e){return Array.isArray(e)?e.map(e=>V(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,be(e)&&t?`[redacted]`:V(t)]))}function be(e){return e===`api-key`||e.toLowerCase().includes(`secret`)}function xe(e){return e.issues.map(e=>` - ${e.path.join(`.`)||`root`}: ${e.message}`).join(`
3
3
  `)}const H=`.axle/batch.jsonl`;function Se(e,t){let n=S(`sha256`);return n.update(e),n.update(`\0`),n.update(t),n.digest(`hex`)}async function Ce(e=H){let t=new Map,n;try{n=await T(e,`utf-8`)}catch{return t}for(let e of n.split(`
4
4
  `)){let n=e.trim();if(n)try{let e=JSON.parse(n);e.file&&e.hash&&t.set(e.file,e)}catch{}}return t}async function we(e,t=H){await ne(ie(t),{recursive:!0}),await w(t,JSON.stringify(e)+`
5
- `,`utf-8`)}async function Te(e,t,n,r,i,a){let o=new d({prompt:e.task});if(e.files)for(let t of e.files)o.addFile(await b(t));let s=a.startSpan(`job`,{type:`workflow`}),l=new c({...t,tracer:s});try{let e=await l.send(o.withInputs(n)).final;if(g(i,e.usage),e.response){let t=e.response;a.info(t,{markdown:!0})}r.interactive&&await Ee(l,i,a),s.end()}catch(e){let t=e instanceof Error?e.message:String(e);throw s.error(t),s.end(`error`),e}}async function Ee(e,t,n){let r=ce({input:process.stdin,output:process.stdout});r.on(`SIGINT`,()=>{r.close()});let i=e=>new Promise(t=>{r.question(e,t),r.once(`close`,()=>t(null))});try{for(;;){let r=await i(`
6
- > `);if(r===null||r.trim()===``)break;try{let i=await e.send(r.trim()).final;if(g(t,i.usage),i.response){let e=i.response;n.info(e,{markdown:!0})}}catch(e){let t=e instanceof Error?e.message:String(e);n.error(t)}}}finally{r.close()}}async function De(e,t,n,r,i,a,o){let s=await se(t.files);if(s.length===0){o.warn(`No files matched pattern: ${t.files}`);return}o.info(`Batch: ${s.length} file(s) matched "${t.files}"`);let l=t.resume?await Ce():new Map,u=e.files?await Promise.all(e.files.map(e=>b(e))):[],f=0,p=0,m=0;await Oe(t.concurrency??3,s,async i=>{let s=o.startSpan(`batch:${i}`,{type:`workflow`});try{let o=await T(i),m=Se(e.task,o),h=l.get(i);if(t.resume&&h&&h.hash===m){s.info(`Skipped (already completed)`),s.end(),p++;return}let _=new d({prompt:e.task});for(let e of u)_.addFile(e);_.addFile(await b(i));let v={...r,file:i};g(a,(await new c({...n,tracer:s}).send(_.withInputs(v)).final).usage),await we({file:i,hash:m,timestamp:Date.now()}),s.end(),f++}catch(e){let t=e instanceof Error?e.message:String(e);s.error(`Failed: ${t}`),s.end(`error`),m++}}),o.info(`Batch complete: ${f} completed, ${p} skipped, ${m} failed`)}async function Oe(e,t,n){let r=0;async function i(){for(;r<t.length;)await n(t[r++])}let a=Array.from({length:Math.min(e,t.length)},()=>i());await Promise.all(a)}const U=new s().name(`axle`).description(`Axle is a CLI tool for running AI workflows`).version(le).requiredOption(`-j, --job <path>`,`Path to the YAML job file`).option(`--no-log`,`Do not write the output to a log file`).option(`-d, --debug`,`Print additional debug information`).option(`-i, --interactive`,`Continue the conversation interactively after the initial task`).option(`--args <args...>`,`Additional arguments in the form key=value`);U.parse(process.argv);const W=U.opts(),G={date:new Date().toISOString().split(`T`)[0],datetime:new Date().toISOString(),cwd:process.cwd()};W.args&&W.args.forEach(e=>{let[t,n]=e.split(`=`);t&&n&&(G[t.trim()]=n.trim())});const K=new h;W.debug&&(K.minLevel=`debug`);const ke=new m({minLevel:W.debug?`debug`:`info`,showInternal:W.debug,showTimestamp:!0,markdown:!0});if(K.addWriter(ke),W.log){let e=new m({minLevel:`debug`,showInternal:!0,showTimestamp:!0,output:e=>{}});K.addWriter(e)}const q=K.startSpan(`cli`,{type:`root`});process.on(`uncaughtException`,async e=>{console.error(`Uncaught exception:`),console.error(e),q.error(`Uncaught exception:`),q.error(e.message),q.error(e.stack||``),q.end(`error`),await K.flush(),process.exit(1)}),W.debug&&(q.debug(`Options: `+JSON.stringify(W,null,2)),q.debug(`Additional Arguments: `+JSON.stringify(G,null,2)));let J,Y;try{J=await _e({tracer:q}),Y=await ge(W.job,{tracer:q})}catch(e){let t=e instanceof Error?e:Error(String(e));q.error(t.message),q.debug(t.stack??``),q.end(`error`),await K.flush(),U.outputHelp(),process.exit(1)}let X=[],Z;try{let e=await A(Y,J,q);Z=e.agentConfig,X=e.mcps}catch(e){let t=e instanceof Error?e:Error(String(e));q.error(t.message),q.error(t.stack??``),q.end(`error`),await K.flush(),U.outputHelp(),process.exit(1)}if(!Z)throw Error(`Failed to create agent config.`);q.info(`All systems operational. Running job...`);const Q=y(),Ae=performance.now(),$={task:Y.task,files:Y.files};try{Y.batch?await De($,Y.batch,Z,G,W,Q,q):await Te($,Z,G,W,Q,q)}catch(e){let t=e instanceof Error?e:Error(String(e));q.error(t.message),q.debug(t.stack??``)}finally{X.length>0&&await de(X,q)}const je=performance.now()-Ae;q.info(`Total run time: ${Math.round(je)}ms`),q.info(`Input tokens: ${Q.in}`),q.info(`Output tokens: ${Q.out}`),Q.cachedIn!==void 0&&q.info(`Cached input tokens: ${Q.cachedIn}`),Q.cacheWriteIn!==void 0&&q.info(`Cache write input tokens: ${Q.cacheWriteIn}`),Q.reasoningOut!==void 0&&q.info(`Reasoning output tokens: ${Q.reasoningOut}`),q.info(`Complete. Goodbye`),q.end(),await K.flush();export{};
5
+ `,`utf-8`)}async function Te(e,t,n,r,i,a){let o=new d({prompt:e.task});if(e.files)for(let t of e.files)o.addFile(await b(t));let s=a.startSpan(`job`,{type:`workflow`}),l=new c({...t,observability:{trace:s}});try{let e=await l.send(o.withInputs(n)).final;if(g(i,e.usage),e.response){let t=e.response;a.info(t,{markdown:!0})}r.interactive&&await Ee(l,i,a),s.end()}catch(e){let t=e instanceof Error?e.message:String(e);throw s.error(t),s.end(`error`),e}}async function Ee(e,t,n){let r=ce({input:process.stdin,output:process.stdout});r.on(`SIGINT`,()=>{r.close()});let i=e=>new Promise(t=>{r.question(e,t),r.once(`close`,()=>t(null))});try{for(;;){let r=await i(`
6
+ > `);if(r===null||r.trim()===``)break;try{let i=await e.send(r.trim()).final;if(g(t,i.usage),i.response){let e=i.response;n.info(e,{markdown:!0})}}catch(e){let t=e instanceof Error?e.message:String(e);n.error(t)}}}finally{r.close()}}async function De(e,t,n,r,i,a,o){let s=await se(t.files);if(s.length===0){o.warn(`No files matched pattern: ${t.files}`);return}o.info(`Batch: ${s.length} file(s) matched "${t.files}"`);let l=t.resume?await Ce():new Map,u=e.files?await Promise.all(e.files.map(e=>b(e))):[],f=0,p=0,m=0;await Oe(t.concurrency??3,s,async i=>{let s=o.startSpan(`batch:${i}`,{type:`workflow`});try{let o=await T(i),m=Se(e.task,o),h=l.get(i);if(t.resume&&h&&h.hash===m){s.info(`Skipped (already completed)`),s.end(),p++;return}let _=new d({prompt:e.task});for(let e of u)_.addFile(e);_.addFile(await b(i));let v={...r,file:i};g(a,(await new c({...n,observability:{trace:s}}).send(_.withInputs(v)).final).usage),await we({file:i,hash:m,timestamp:Date.now()}),s.end(),f++}catch(e){let t=e instanceof Error?e.message:String(e);s.error(`Failed: ${t}`),s.end(`error`),m++}}),o.info(`Batch complete: ${f} completed, ${p} skipped, ${m} failed`)}async function Oe(e,t,n){let r=0;async function i(){for(;r<t.length;)await n(t[r++])}let a=Array.from({length:Math.min(e,t.length)},()=>i());await Promise.all(a)}const U=new s().name(`axle`).description(`Axle is a CLI tool for running AI workflows`).version(le).requiredOption(`-j, --job <path>`,`Path to the YAML job file`).option(`--no-log`,`Do not write the output to a log file`).option(`-d, --debug`,`Print additional debug information`).option(`-i, --interactive`,`Continue the conversation interactively after the initial task`).option(`--args <args...>`,`Additional arguments in the form key=value`);U.parse(process.argv);const W=U.opts(),G={date:new Date().toISOString().split(`T`)[0],datetime:new Date().toISOString(),cwd:process.cwd()};W.args&&W.args.forEach(e=>{let[t,n]=e.split(`=`);t&&n&&(G[t.trim()]=n.trim())});const K=new h;W.debug&&(K.minLevel=`debug`);const ke=new m({minLevel:W.debug?`debug`:`info`,showInternal:W.debug,showTimestamp:!0,markdown:!0});if(K.addWriter(ke),W.log){let e=new m({minLevel:`debug`,showInternal:!0,showTimestamp:!0,output:e=>{}});K.addWriter(e)}const q=K.startSpan(`cli`,{type:`root`});process.on(`uncaughtException`,async e=>{console.error(`Uncaught exception:`),console.error(e),q.error(`Uncaught exception:`),q.error(e.message),q.error(e.stack||``),q.end(`error`),await K.flush(),process.exit(1)}),W.debug&&(q.debug(`Options: `+JSON.stringify(W,null,2)),q.debug(`Additional Arguments: `+JSON.stringify(G,null,2)));let J,Y;try{J=await _e({span:q}),Y=await ge(W.job,{span:q})}catch(e){let t=e instanceof Error?e:Error(String(e));q.error(t.message),q.debug(t.stack??``),q.end(`error`),await K.flush(),U.outputHelp(),process.exit(1)}let X=[],Z;try{let e=await A(Y,J,q);Z=e.agentConfig,X=e.mcps}catch(e){let t=e instanceof Error?e:Error(String(e));q.error(t.message),q.error(t.stack??``),q.end(`error`),await K.flush(),U.outputHelp(),process.exit(1)}if(!Z)throw Error(`Failed to create agent config.`);q.info(`All systems operational. Running job...`);const Q=y(),Ae=performance.now(),$={task:Y.task,files:Y.files};try{Y.batch?await De($,Y.batch,Z,G,W,Q,q):await Te($,Z,G,W,Q,q)}catch(e){let t=e instanceof Error?e:Error(String(e));q.error(t.message),q.debug(t.stack??``)}finally{X.length>0&&await de(X,q)}const je=performance.now()-Ae;q.info(`Total run time: ${Math.round(je)}ms`),q.info(`Input tokens: ${Q.in}`),q.info(`Output tokens: ${Q.out}`),Q.cachedIn!==void 0&&q.info(`Cached input tokens: ${Q.cachedIn}`),Q.cacheWriteIn!==void 0&&q.info(`Cache write input tokens: ${Q.cacheWriteIn}`),Q.reasoningOut!==void 0&&q.info(`Reasoning output tokens: ${Q.reasoningOut}`),q.info(`Complete. Goodbye`),q.end(),await K.flush();export{};
@@ -1 +1 @@
1
- import{t as e}from"../memory-BmDsikXS.js";export{e as ProceduralMemory};
1
+ import{t as e}from"../memory-BS-U-K_q.js";export{e as ProceduralMemory};
@@ -0,0 +1,20 @@
1
+ import{generate as e}from"@fifthrevision/axle";import t from"node:crypto";import{z as n}from"zod";var r=class{provider;model;enableTools;store;scope;lastAgentName;constructor(e){this.provider=e.provider,this.model=e.model,this.enableTools=e.enableTools??!1,this.store=e.store,this.scope=e.scope}async recall(e){let t=e.span?.startSpan(`memory.recall`,{type:`internal`});this.lastAgentName=e.agentName;let n=await this.loadStore(e.agentName);if(n.instructions.length===0)return t?.info(`no stored instructions`),t?.end(),{};t?.info(`loaded instructions`,{count:n.instructions.length});let r=n.instructions.map((e,t)=>`${t+1}. ${e}`).join(`
2
+ `);return t?.end(),{systemSuffix:`## Learned Instructions\n\n${r}`}}async record(t){if(!t.newMessages||t.newMessages.length===0)return;let n=t.span?.startSpan(`memory.record`,{type:`internal`}),r=this.formatMessages(t.newMessages);if(!r.trim()){n?.info(`no text content to extract from`),n?.end();return}let a=n?.startSpan(`memory.extract`,{type:`llm`}),o=await e({provider:this.provider,model:this.model,messages:[{role:`user`,content:r}],system:`You are a memory extraction system. Your job is to extract learnings from a conversation that should be remembered for future runs.
3
+
4
+ Only extract:
5
+ - Explicit user corrections (e.g., "No, always use bullet points")
6
+ - Stated preferences (e.g., "I prefer concise summaries")
7
+ - Patterns that clearly emerged from user feedback
8
+
9
+ Do NOT extract:
10
+ - General knowledge or facts from the conversation content
11
+ - Inferences or speculation about what the user might want
12
+ - Task-specific details that won't apply to future runs
13
+
14
+ Respond with a JSON array of instruction strings. Each instruction should be a clear, actionable directive.
15
+ If there are no learnings to extract, respond with an empty array: []
16
+
17
+ Example response:
18
+ ["Always use bullet points for lists", "Keep summaries under 3 sentences"]`,span:a});if(!o.ok){n?.warn(`extraction failed`,{error:o.error}),n?.end();return}let s=i(o.final.content);if(!s){n?.end();return}let c=this.parseInstructions(s);if(c.length===0){n?.info(`no instructions extracted`),n?.end();return}let l=await this.loadStore(t.agentName);l.instructions.push(...c),await this.saveStore(t.agentName,l),n?.info(`saved instructions`,{count:c.length}),n?.end()}tools(){if(!this.enableTools)return[];let e=this;return[{name:`add_instruction`,description:`Save a learned instruction for future runs. Use this when the user explicitly asks you to remember something.`,schema:n.object({instruction:n.string().describe(`The instruction to remember`)}),async execute(t){if(!e.lastAgentName)return`Error: memory not initialized (no recall has been called yet)`;let n=await e.loadStore(e.lastAgentName);return n.instructions.push(t.instruction),await e.saveStore(e.lastAgentName,n),`Instruction saved: "${t.instruction}"`}}]}formatMessages(e){let t=[];for(let n of e)if(n.role===`user`){let e=typeof n.content==`string`?n.content:i(n.content);e&&t.push(`User: ${e}`)}else if(n.role===`assistant`){let e=i(n.content);e&&t.push(`Assistant: ${e}`)}return t.join(`
19
+
20
+ `)}parseInstructions(e){let t=e.trim(),n=t.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/);n&&(t=n[1].trim());try{let e=JSON.parse(t);if(Array.isArray(e)&&e.every(e=>typeof e==`string`))return e}catch{}return[]}getStorePath(e,n){let r=e??`default`,i=r;if(n&&Object.keys(n).length>0){let e=Object.entries(n).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${e}=${t}`).join(`&`);i=`${r}-${t.createHash(`sha256`).update(e).digest(`hex`).slice(0,8)}`}return`memory/procedural/${i}.json`}async loadStore(e){let t=this.getStorePath(e,this.scope),n=await this.store.read(t);if(n)try{let e=JSON.parse(n);if(e&&Array.isArray(e.instructions))return{instructions:e.instructions}}catch{}return{instructions:[]}}async saveStore(e,t){if(!t)return;let n=this.getStorePath(e,this.scope);await this.store.write(n,JSON.stringify(t,null,2))}};function i(e){return typeof e==`string`?e:e.filter(e=>e.type===`text`||e.type===`thinking`).map(e=>e.text).join(``)}export{r as t};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fifthrevision/axle-cli",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/johncch/axle.git"
@@ -36,7 +36,7 @@
36
36
  "glob": "^13.0.6",
37
37
  "yaml": "^2.9.0",
38
38
  "zod": "^4.4.3",
39
- "@fifthrevision/axle": "0.22.1"
39
+ "@fifthrevision/axle": "0.23.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@arethetypeswrong/core": "^0.18.2",
@@ -1,20 +0,0 @@
1
- import{generate as e}from"@fifthrevision/axle";import t from"node:crypto";import{z as n}from"zod";var r=class{provider;model;enableTools;store;scope;lastAgentName;constructor(e){this.provider=e.provider,this.model=e.model,this.enableTools=e.enableTools??!1,this.store=e.store,this.scope=e.scope}async recall(e){let t=e.tracer?.startSpan(`memory.recall`,{type:`internal`});this.lastAgentName=e.agentName;let n=await this.loadStore(e.agentName);if(n.instructions.length===0)return t?.info(`no stored instructions`),t?.end(),{};t?.info(`loaded instructions`,{count:n.instructions.length});let r=n.instructions.map((e,t)=>`${t+1}. ${e}`).join(`
2
- `);return t?.end(),{systemSuffix:`## Learned Instructions\n\n${r}`}}async record(t){if(!t.newMessages||t.newMessages.length===0)return;let n=t.tracer?.startSpan(`memory.record`,{type:`internal`}),r=this.formatMessages(t.newMessages);if(!r.trim()){n?.info(`no text content to extract from`),n?.end();return}let a=n?.startSpan(`memory.extract`,{type:`llm`}),o=await e({provider:this.provider,model:this.model,messages:[{role:`user`,content:r}],system:`You are a memory extraction system. Your job is to extract learnings from a conversation that should be remembered for future runs.
3
-
4
- Only extract:
5
- - Explicit user corrections (e.g., "No, always use bullet points")
6
- - Stated preferences (e.g., "I prefer concise summaries")
7
- - Patterns that clearly emerged from user feedback
8
-
9
- Do NOT extract:
10
- - General knowledge or facts from the conversation content
11
- - Inferences or speculation about what the user might want
12
- - Task-specific details that won't apply to future runs
13
-
14
- Respond with a JSON array of instruction strings. Each instruction should be a clear, actionable directive.
15
- If there are no learnings to extract, respond with an empty array: []
16
-
17
- Example response:
18
- ["Always use bullet points for lists", "Keep summaries under 3 sentences"]`,tracer:a});if(!o.ok){n?.warn(`extraction failed`,{error:o.error}),n?.end();return}let s=i(o.final.content);if(!s){n?.end();return}let c=this.parseInstructions(s);if(c.length===0){n?.info(`no instructions extracted`),n?.end();return}let l=await this.loadStore(t.agentName);l.instructions.push(...c),await this.saveStore(t.agentName,l),n?.info(`saved instructions`,{count:c.length}),n?.end()}tools(){if(!this.enableTools)return[];let e=this;return[{name:`add_instruction`,description:`Save a learned instruction for future runs. Use this when the user explicitly asks you to remember something.`,schema:n.object({instruction:n.string().describe(`The instruction to remember`)}),async execute(t){if(!e.lastAgentName)return`Error: memory not initialized (no recall has been called yet)`;let n=await e.loadStore(e.lastAgentName);return n.instructions.push(t.instruction),await e.saveStore(e.lastAgentName,n),`Instruction saved: "${t.instruction}"`}}]}formatMessages(e){let t=[];for(let n of e)if(n.role===`user`){let e=typeof n.content==`string`?n.content:i(n.content);e&&t.push(`User: ${e}`)}else if(n.role===`assistant`){let e=i(n.content);e&&t.push(`Assistant: ${e}`)}return t.join(`
19
-
20
- `)}parseInstructions(e){let t=e.trim(),n=t.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/);n&&(t=n[1].trim());try{let e=JSON.parse(t);if(Array.isArray(e)&&e.every(e=>typeof e==`string`))return e}catch{}return[]}getStorePath(e,n){let r=e??`default`,i=r;if(n&&Object.keys(n).length>0){let e=Object.entries(n).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${e}=${t}`).join(`&`);i=`${r}-${t.createHash(`sha256`).update(e).digest(`hex`).slice(0,8)}`}return`memory/procedural/${i}.json`}async loadStore(e){let t=this.getStorePath(e,this.scope),n=await this.store.read(t);if(n)try{let e=JSON.parse(n);if(e&&Array.isArray(e.instructions))return{instructions:e.instructions}}catch{}return{instructions:[]}}async saveStore(e,t){if(!t)return;let n=this.getStorePath(e,this.scope);await this.store.write(n,JSON.stringify(t,null,2))}};function i(e){return typeof e==`string`?e:e.filter(e=>e.type===`text`||e.type===`thinking`).map(e=>e.text).join(``)}export{r as t};