@fifthrevision/axle-cli 0.31.0 → 0.32.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/README.md +4 -1
- package/dist/cli.js +3 -3
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -118,7 +118,10 @@ request:
|
|
|
118
118
|
maxOutputTokens: 16000
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
`reasoning` takes `default`, `off`, `on`, or
|
|
121
|
+
`reasoning` takes `default`, `off`, `on`, or
|
|
122
|
+
`{ effort: low | medium | high, display?: visible | hidden }`. `display`
|
|
123
|
+
defaults to `visible`; `hidden` asks the provider to keep its thinking off
|
|
124
|
+
the wire.
|
|
122
125
|
Leave it unset (or `default`) and the model runs at its provider's own
|
|
123
126
|
default, which is always safe, including for models that cannot disable
|
|
124
127
|
thinking. `on` is medium effort; `off` sends the provider's explicit disable
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,i as t,n,r,t as i}from"./tools-gv3b9sAQ.js";import{n as a,o,r as s,t as c}from"./format-CVXITc-j.js";import{Command as l}from"@commander-js/extra-typings";import{Agent as u,AxleAgentAbortError as ee,Instruct as d,MCP as f,PromptCompactor as p,SimpleWriter as m,Tracer as h,Transcript as g,addStats as te,anthropic as ne,chatCompletions as _,createAgentConfig as v,createStats as y,gemini as b,loadFileContent as re,openai as x}from"@fifthrevision/axle";import{mkdirSync as S,openSync as C,writeSync as ie}from"node:fs";import{dirname as ae,extname as oe,join as w,relative as se,resolve as ce}from"node:path";import{z as T}from"zod";import{appendFile as le,chmod as ue,mkdir as E,readFile as D,readdir as de,rename as fe,rm as pe,stat as me,writeFile as he}from"node:fs/promises";import{config as ge,parse as _e}from"dotenv";import ve from"yaml";import{homedir as ye}from"node:os";import*as O from"@clack/prompts";import{ModelInfo as k}from"@fifthrevision/axle/models";import{glob as be}from"glob";import{createHash as xe}from"node:crypto";import{createInterface as Se}from"node:readline";var Ce=`0.
|
|
2
|
+
import{a as e,i as t,n,r,t as i}from"./tools-gv3b9sAQ.js";import{n as a,o,r as s,t as c}from"./format-CVXITc-j.js";import{Command as l}from"@commander-js/extra-typings";import{Agent as u,AxleAgentAbortError as ee,Instruct as d,MCP as f,PromptCompactor as p,SimpleWriter as m,Tracer as h,Transcript as g,addStats as te,anthropic as ne,chatCompletions as _,createAgentConfig as v,createStats as y,gemini as b,loadFileContent as re,openai as x}from"@fifthrevision/axle";import{mkdirSync as S,openSync as C,writeSync as ie}from"node:fs";import{dirname as ae,extname as oe,join as w,relative as se,resolve as ce}from"node:path";import{z as T}from"zod";import{appendFile as le,chmod as ue,mkdir as E,readFile as D,readdir as de,rename as fe,rm as pe,stat as me,writeFile as he}from"node:fs/promises";import{config as ge,parse as _e}from"dotenv";import ve from"yaml";import{homedir as ye}from"node:os";import*as O from"@clack/prompts";import{ModelInfo as k}from"@fifthrevision/axle/models";import{glob as be}from"glob";import{createHash as xe}from"node:crypto";import{createInterface as Se}from"node:readline";var Ce=`0.32.0`;async function we(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 Te(e,t){for(let n of e)try{await n.close({span:t})}catch{}}function Ee(a){switch(a){case`calculator`:return e;case`exec`:return t;case`patch-file`:return r;case`read-file`:return n;case`write-file`:return i;default:throw Error(`Unknown tool: ${a}`)}}function De(e){return e.map(e=>Ee(e))}const Oe=[`anthropic`,`openai`,`gemini`,`chatcompletions`];function ke(e,t,n){let r=e?.provider,i,a;if(r&&!(`name`in r))i=r,a=r.type;else{let e=r&&`name`in r?r.name:t.defaults?.provider;if(!e)throw Error(`No provider specified and no default provider configured. Add provider: to the job, or set defaults.provider in ~/.axle/cli.yaml.`);a=e;let n=t.providers?.[e];if(n)i=n;else if(Oe.includes(e))i={type:e};else throw Error(`Provider "${e}" is not a provider profile in cli.yaml or a built-in provider type.`)}let o=e?.model??t.defaults?.models?.[a]??n[i.type]?.model,{type:s,...c}=i;return{provider:Object.keys(c).length>0?{type:s,config:c}:{type:s},model:o,providerName:a}}function Ae(e,t,n){let r=e.config??{},i=e.type,a={...t[i],...r},o=n??a.model;if(!o)throw Error(`No model resolved for provider ${i}. Add model: to the job, set defaults.models in ~/.axle/cli.yaml, or set ${i.toUpperCase()}_MODEL.`);switch(i){case`openai`:{let e=A(a);if(!e)throw Error(`The provider openai is not configured. Please check your configuration.`);return{provider:x(e,{maxRetries:a.maxRetries,timeoutMs:a.timeoutMs}),model:o}}case`anthropic`:{let e=A(a);if(!e)throw Error(`The provider anthropic is not configured. Please check your configuration.`);return{provider:ne(e,{maxRetries:a.maxRetries,timeoutMs:a.timeoutMs}),model:o}}case`gemini`:{let e=A(a);if(!e)throw Error(`The provider gemini is not configured. Please check your configuration.`);return{provider:b(e,{maxRetries:a.maxRetries,timeoutMs:a.timeoutMs}),model:o}}case`chatcompletions`:{let e=a.baseUrl;if(!e)throw Error(`The provider chatcompletions is not configured. Please check your configuration.`);return{provider:_(e,{apiKey:A(a),maxRetries:a.maxRetries,timeoutMs:a.timeoutMs,vendor:a.vendor}),model:o}}default:throw Error(`Unknown provider type: ${i}`)}}function A(e){let t=e.apiKeyEnv;return typeof t==`string`&&t.length>0?process.env[t]:e.apiKey}function je(e,t){let n=ke(void 0,e,t);return{version:1,provider:n.provider,model:n.model}}function Me(e,t,n){let r=ke(e,t,n);return{version:1,name:e.name,provider:r.provider,model:r.model,system:e.system,request:e.request,tools:e.tools?.map(e=>({name:e})),providerTools:e.providerTools?.map(e=>({name:e})),mcps:e.mcps}}async function Ne(e,t,n){let r=e.mcps?.length?await we(e.mcps,n):[];return{agentConfig:await v(e,e=>{let n=Ae(e.provider,t,e.model);return{provider:n.provider,model:n.model,tools:e.tools?.length?De(e.tools.map(e=>e.name)):void 0,mcps:r.length>0?r:void 0}}),definition:e,mcps:r}}const Pe=`credentials`,Fe=`cli.yaml`;function j(e){return{project:w(e?.cwd??process.cwd(),`.axle`),user:w(e?.home??ye(),`.axle`)}}const M={apiKey:T.string().optional(),apiKeyEnv:T.string().optional()},N={maxRetries:T.number().int().nonnegative().optional(),timeoutMs:T.number().int().positive().optional()},Ie=T.strictObject({type:T.literal(`chatcompletions`),baseUrl:T.string().optional(),vendor:T.enum([`openrouter`,`together`]).optional(),...M,...N}),Le=T.strictObject({type:T.literal(`anthropic`),...M,...N}),Re=T.strictObject({type:T.literal(`openai`),...M,...N}),ze=T.strictObject({type:T.literal(`gemini`),...M,...N}),Be=T.discriminatedUnion(`type`,[Ie,Le,Re,ze]),Ve=T.union([T.string().min(1).transform(e=>({name:e})),Be]),He=T.object({providers:T.record(T.string(),Be).optional(),defaults:T.object({provider:T.string().optional(),models:T.record(T.string(),T.string()).optional()}).optional()}),Ue=T.object({transport:T.literal(`stdio`),name:T.string().optional(),command:T.string(),args:T.array(T.string()).optional(),env:T.record(T.string(),T.string()).optional()}),We=T.object({transport:T.literal(`http`),name:T.string().optional(),url:T.string(),headers:T.record(T.string(),T.string()).optional()}),Ge=T.discriminatedUnion(`transport`,[Ue,We]),Ke=T.strictObject({files:T.string(),concurrency:T.number().int().positive().default(3),incremental:T.boolean().default(!1)}),qe=T.strictObject({reasoning:T.union([T.enum([`default`,`off`,`on`]),T.strictObject({effort:T.enum([`low`,`medium`,`high`]),display:T.enum([`visible`,`hidden`]).optional()})]).optional(),maxOutputTokens:T.number().int().positive().optional(),temperature:T.number().optional(),topP:T.number().optional(),stop:T.union([T.string(),T.array(T.string())]).optional(),toolChoice:T.union([T.enum([`auto`,`none`,`required`]),T.strictObject({type:T.literal(`tool`),name:T.string()})]).optional(),parallelToolCalls:T.boolean().optional(),providerOptions:T.record(T.string(),T.any()).optional()}),Je=T.strictObject({name:T.string().optional(),provider:Ve.optional(),model:T.string().optional(),system:T.string().optional(),request:qe.optional(),task:T.string(),tools:T.array(T.string()).optional(),providerTools:T.array(T.string()).optional(),files:T.array(T.string()).optional(),mcps:T.array(Ge).optional(),batch:Ke.optional(),compaction:T.boolean().optional()});async function Ye(e,t){let{span:n}=t,r=oe(e).slice(1);if(r!==`yaml`&&r!==`yml`)throw Error(`Invalid job file format. Expected .yaml or .yml`);let i;try{i=await D(e,{encoding:`utf-8`})}catch{throw Error(`Job File not found, see --help for details`)}let a=ve.parse(i);n?.debug(`Job config: `+JSON.stringify(a,null,2));let o=Je.safeParse(a);if(!o.success)throw Error(`The job file is not valid:\n${rt(o.error)}`);return o.data}async function P(e){let{span:t}=e;ge({quiet:!0});let n=j(e),r=[process.env];for(let e of[n.project,n.user]){let t=await $e(w(e,Pe));t&&r.push(t)}let i=et(e=>{for(let t of r)if(t[e])return t[e]});return t?.debug(`Service config: `+JSON.stringify(F(i),null,2)),i}async function Xe(e){let{span:t}=e,n=j(e),r={};for(let e of[n.user,n.project]){let t=w(e,Fe),n=await Qe(t);if(n===null)continue;let i;try{i=ve.parse(n)??{}}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`Invalid config file at ${t}:\n ${n}`)}let a=He.safeParse(i);if(!a.success)throw Error(`Invalid config file at ${t}:\n${rt(a.error)}`);r=Ze(r,a.data)}return t?.debug(`CLI config: `+JSON.stringify(r,null,2)),r}function Ze(e,t){let n={},r={...e.providers,...t.providers};if(Object.keys(r).length>0&&(n.providers=r),e.defaults||t.defaults){n.defaults={...e.defaults,...t.defaults};let r={...e.defaults?.models,...t.defaults?.models};Object.keys(r).length>0&&(n.defaults.models=r)}return n}async function Qe(e){try{return await D(e,{encoding:`utf-8`})}catch(e){if(e.code===`ENOENT`)return null;throw e}}async function $e(e){let t=await Qe(e);return t===null?null:_e(t)}function et(e){return tt({openai:e(`OPENAI_API_KEY`)?{apiKey:e(`OPENAI_API_KEY`),model:e(`OPENAI_MODEL`)}:void 0,anthropic:e(`ANTHROPIC_API_KEY`)?{apiKey:e(`ANTHROPIC_API_KEY`),model:e(`ANTHROPIC_MODEL`)}:void 0,gemini:e(`GEMINI_API_KEY`)?{apiKey:e(`GEMINI_API_KEY`),model:e(`GEMINI_MODEL`)}:void 0,chatcompletions:e(`CHATCOMPLETIONS_BASE_URL`)?{baseUrl:e(`CHATCOMPLETIONS_BASE_URL`),model:e(`CHATCOMPLETIONS_MODEL`),apiKey:e(`CHATCOMPLETIONS_API_KEY`)}:void 0})}function tt(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e&&Object.keys(e).length>0))}function F(e){return Array.isArray(e)?e.map(e=>F(e)):!e||typeof e!=`object`?e:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,nt(e)&&t?`[redacted]`:F(t)]))}function nt(e){return e===`apiKey`||e.toLowerCase().includes(`secret`)}function rt(e){return e.issues.flatMap(e=>it(e,e.path)).map(({path:e,message:t})=>` - ${e.join(`.`)||`root`}: ${t}`).join(`
|
|
3
3
|
`)}function it(e,t){if(e.code===`invalid_union`){let n=e.errors.flatMap(e=>e.flatMap(e=>it(e,[...t,...e.path])));if(!n.length)return[{path:t,message:e.message}];let r=n.filter(e=>e.path.length>t.length);return r.length?r:n}return[{path:t,message:e.message}]}async function I(e,t,n){let r=`${e}.tmp`;try{await he(r,t,n?.mode===void 0?{}:{mode:n.mode}),n?.mode!==void 0&&await ue(r,n.mode),await fe(r,e)}catch(e){throw await pe(r,{force:!0}).catch(()=>{}),e}}function L(e){return w(j({home:e}).user,`sessions`,`cli`)}function at(e,t){return w(L(t),`${e}.json`)}async function ot(e,t){let n;try{n=await de(L(t))}catch{return e}let r=n.filter(e=>e.endsWith(`.json`)).map(e=>e.slice(0,-5));if(r.includes(e))return e;let i=r.filter(t=>t.startsWith(e));if(i.length===1)return i[0];if(i.length>1)throw Error(`Session id prefix "${e}" is ambiguous (${i.length} matches). Use more characters.`);return e}async function st(e){let t=L(e),n;try{n=await de(t)}catch(e){if(e.code===`ENOENT`)return[];throw e}let r=await Promise.all(n.filter(e=>e.endsWith(`.json`)).map(async e=>{let n=w(t,e),r=e.slice(0,-5),i=await me(n),a={sessionId:r,path:n,sizeBytes:i.size,updatedAt:i.mtime.toISOString()};try{let e=JSON.parse(await D(n,`utf-8`));return{...a,updatedAt:e.updatedAt??a.updatedAt,corrupt:!1}}catch{return{...a,corrupt:!0}}}));return r.sort((e,t)=>t.updatedAt.localeCompare(e.updatedAt)),r}async function ct(e,t){let n=at(await ot(e,t),t),r;try{r=await D(n,`utf-8`)}catch(t){throw t.code===`ENOENT`?Error(`No session found with id ${e}`):t}let i;try{i=JSON.parse(r)}catch{throw Error(`Invalid session file at ${n}`)}let a=i;if(a?.version!==1||!a.definition||!a.session)throw Error(`Unsupported or corrupt session file at ${n}`);return a}var R=class{definition;cwd;home;compaction;createdAt;constructor(e,t){this.definition=e,this.cwd=t?.cwd??process.cwd(),this.home=t?.home,this.compaction=t?.compaction,this.createdAt=t?.createdAt}async save(e,t){this.createdAt??=new Date().toISOString();let n={version:1,createdAt:this.createdAt,updatedAt:new Date().toISOString(),cwd:this.cwd,definition:this.definition,compaction:this.compaction,session:e,turns:[...t]};await E(L(this.home),{recursive:!0});let r=at(e.sessionId,this.home);return await I(r,JSON.stringify(n,null,2)),r}};async function lt(e,t){let n=j({home:t}).user,r=w(n,Pe),i=``;try{i=await D(r,`utf-8`)}catch(e){if(e.code!==`ENOENT`)throw e}let a=i.length>0?i.split(`
|
|
4
4
|
`):[];a.at(-1)===``&&a.pop();let o=new Map(Object.entries(e)),s=new Set,c=[];for(let e of a){let t=e.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1];t&&o.has(t)?(c.push(`${t}=${o.get(t)}`),o.delete(t),s.add(t)):t&&s.has(t)||c.push(e)}for(let[e,t]of o)c.push(`${e}=${t}`);return await E(n,{recursive:!0}),await I(r,c.join(`
|
|
5
5
|
`)+`
|
|
6
|
-
`,{mode:384}),r}async function ut(e,t){let n=j({home:t}).user,r=w(n,Fe),i=``;try{i=await D(r,`utf-8`)}catch(e){if(e.code!==`ENOENT`)throw e}let a=ve.parseDocument(i);e.provider!==void 0&&a.setIn([`defaults`,`provider`],e.provider);for(let[t,n]of Object.entries(e.models??{}))a.setIn([`defaults`,`models`,t],n);return await E(n,{recursive:!0}),await I(r,a.toString()),r}const z=[{value:`anthropic`,label:`Anthropic`,keyName:`ANTHROPIC_API_KEY`,publisher:`anthropic/`},{value:`openai`,label:`OpenAI`,keyName:`OPENAI_API_KEY`,publisher:`openai/`},{value:`gemini`,label:`Google Gemini`,keyName:`GEMINI_API_KEY`,publisher:`google/`},{value:`chatcompletions`,label:`OpenAI-compatible endpoint (Ollama, OpenRouter, …)`,keyName:`CHATCOMPLETIONS_API_KEY`}];function dt(e){return!!(e.anthropic?.apiKey??e.openai?.apiKey??e.gemini?.apiKey??e.chatcompletions?.baseUrl)}function ft(e,t,n){return!(dt(e)||Object.keys(t.providers??{}).length>0||t.defaults?.provider||n?.provider&&!(`name`in n.provider))}function B(e){return
|
|
6
|
+
`,{mode:384}),r}async function ut(e,t){let n=j({home:t}).user,r=w(n,Fe),i=``;try{i=await D(r,`utf-8`)}catch(e){if(e.code!==`ENOENT`)throw e}let a=ve.parseDocument(i);e.provider!==void 0&&a.setIn([`defaults`,`provider`],e.provider);for(let[t,n]of Object.entries(e.models??{}))a.setIn([`defaults`,`models`,t],n);return await E(n,{recursive:!0}),await I(r,a.toString()),r}const z=[{value:`anthropic`,label:`Anthropic`,keyName:`ANTHROPIC_API_KEY`,publisher:`anthropic/`},{value:`openai`,label:`OpenAI`,keyName:`OPENAI_API_KEY`,publisher:`openai/`},{value:`gemini`,label:`Google Gemini`,keyName:`GEMINI_API_KEY`,publisher:`google/`},{value:`chatcompletions`,label:`OpenAI-compatible endpoint (Ollama, OpenRouter, …)`,keyName:`CHATCOMPLETIONS_API_KEY`}];function dt(e){return!!(e.anthropic?.apiKey??e.openai?.apiKey??e.gemini?.apiKey??e.chatcompletions?.baseUrl)}function ft(e,t,n){return!(dt(e)||Object.keys(t.providers??{}).length>0||t.defaults?.provider||n?.provider&&!(`name`in n.provider))}function B(e){return typeof e==`symbol`&&(O.cancel(`Setup cancelled.`),process.exit(1)),e}async function pt(e){let t=z.find(t=>t.value===e)?.publisher;if(t){let e=Object.keys(k).filter(e=>e.startsWith(t)).sort();if(e.length>0){let t=`__other__`,n=B(await O.select({message:`Pick a model`,options:[...e.map(e=>{let t=k[e];return{value:e,label:e,hint:t.contextWindow?`${Math.round(t.contextWindow/1e3)}k context`:void 0}}),{value:t,label:`Other (type a model id)`}]}));if(n!==t)return n}}return B(await O.text({message:`Model id`,placeholder:t?`${t}model-name`:`e.g. qwen/qwen-3-coder`,validate:e=>(e??``).trim().length===0?`A model id is required`:void 0})).trim()}async function mt(){return B(await O.text({message:`Input files (glob or path)`,placeholder:`data/*.md`,validate:e=>(e??``).trim().length===0?`Inputs are required`:void 0})).trim()}async function ht(e,t){let n=t?.saveAs??e;O.log.warn(`No model configured for provider ${n}.`);let r=await pt(e);if(t?.offerSave!==!1&&B(await O.confirm({message:`Save ${r} as the default model for ${n}?`,initialValue:!0}))){let e=await ut({models:{[n]:r}});O.log.success(`Saved to ${e}`)}return r}async function gt(e){O.intro(`axle setup`);let t=B(await O.select({message:`Which provider should axle use by default?`,options:z.map(e=>({value:e.value,label:e.label}))})),n=z.find(e=>e.value===t),r={};t===`chatcompletions`&&(r.CHATCOMPLETIONS_BASE_URL=B(await O.text({message:`Base URL of the endpoint`,placeholder:`http://localhost:11434/v1`,initialValue:e.chatcompletions?.baseUrl??``,validate:e=>(e??``).trim().length===0?`A base URL is required`:void 0})).trim());let i=e[t]?.apiKey!==void 0,a=!0;if(i&&(a=B(await O.confirm({message:`A key for ${n.label} is already configured — replace it?`,initialValue:!1}))),a){let e=B(await O.password({message:t===`chatcompletions`?`API key (leave empty if the endpoint needs none)`:`API key for ${n.label}`,validate:e=>t!==`chatcompletions`&&(e??``).trim().length===0?`An API key is required`:void 0})).trim();e.length>0&&(r[n.keyName]=e)}if(Object.keys(r).length>0){let e=await lt(r);O.log.success(`Credentials written to ${e}`)}let o=await pt(t),s=await ut({provider:t,models:{[t]:o}});O.log.success(`Defaults saved to ${s}`),O.outro(`Ready — run axle to chat with ${o}. Re-run anytime with: axle setup`)}function _t(e){let t={};for(let n of e){let e=n.indexOf(`=`);if(e<1)continue;let r=n.slice(0,e).trim(),i=n.slice(e+1).trim();r&&i&&(t[r]=i)}return t}async function vt(e){let{invocation:t,cliConfig:n,serviceConfig:r,jobConfig:i,jobScope:a,variables:o,interactiveTerminal:s}=e,c;if(t.kind===`resume`){let e=await ct(t.id);c={kind:`session`,definition:e.definition,spec:{spanName:`resume`,session:e.session,priorTurns:e.turns,resumedFromCwd:e.cwd,initial:t.message,interactive:t.message===void 0,compaction:e.compaction},sessionStore:new R(e.definition,{cwd:e.cwd,createdAt:e.createdAt,compaction:e.compaction})}}else if(i){let e=Me(i,n,r);if(t.kind===`batch`||i.batch){if(t.kind===`kernel`&&t.interactive)throw Error(`A batch run cannot be combined with --interactive.`);let n;if(t.kind===`batch`&&t.inputs.length>0)n=t.inputs;else if(i.batch)n=[i.batch.files];else if(s)n=[await mt()];else throw Error(`batch needs inputs: pass globs/paths after the recipe, or add a batch: block to it.`);let r=t.kind===`batch`&&t.verbose||(i.batch?.concurrency??3)===1;c={kind:`batch`,definition:e,spec:{task:i.task,files:i.files,inputs:n,concurrency:r?1:i.batch?.concurrency??3,jobName:a,incremental:(t.kind===`batch`?t.incremental:void 0)??i.batch?.incremental??!1,verbose:r,compaction:i.compaction}}}else{let n=new d({prompt:i.task});for(let e of i.files??[])n.addFile(await re(e));c={kind:`session`,definition:e,spec:{spanName:`job`,initial:n.withInputs(o),interactive:t.kind===`kernel`&&t.interactive,compaction:i.compaction},sessionStore:new R(e,{compaction:i.compaction})}}}else{let e=je(n,r);c={kind:`session`,definition:e,spec:{spanName:`chat`,initial:t.kind===`kernel`?t.message:void 0,interactive:t.kind!==`kernel`||t.message===void 0},sessionStore:new R(e)}}if(!c.definition.model&&s){let e=c.kind!==`session`||c.spec.spanName!==`resume`;c.definition.model=await ht(c.definition.provider.type,{offerSave:e,saveAs:e?ke(i,n,r).providerName:void 0})}return c}const yt=`.axle/batch.jsonl`;function bt(e){return xe(`sha256`).update(e).digest(`hex`)}function xt(e,t){return`${e}\u0000${t}`}async function St(e=yt){let t=new Map,n;try{n=await D(e,`utf-8`)}catch{return t}for(let e of n.split(`
|
|
7
7
|
`)){let n=e.trim();if(n)try{let e=JSON.parse(n);e.job&&e.file&&e.hash&&e.sessionId&&e.status&&t.set(xt(e.job,e.file),e)}catch{}}return t}async function Ct(e,t=yt){await E(ae(t),{recursive:!0}),await le(t,JSON.stringify(e)+`
|
|
8
|
-
`,`utf-8`)}function wt(e){switch(e.kind){case`model`:return`Model error: ${e.message}`;case`tool`:return`Tool error (${e.error.name}): ${e.message}`;case`parse`:return`Parse error: ${e.message}`}}function Tt(e,t){let n=Number(process.env.AXLE_CONTEXT_WINDOW);if(Number.isInteger(n)&&n>0)return n;if(t)return t;let r=e.provider.name.toLowerCase()===`gemini`?`google`:e.provider.name.toLowerCase();return k[e.model]?.contextWindow??k[`${r}/${e.model}`]?.contextWindow??2e5}const Et=[`You summarize an agent conversation so it can continue in a smaller context.`,`Preserve durable facts, decisions, constraints, file paths, tool outcomes,`,`completed work, and open tasks. Prefer concrete identifiers over prose.`].join(` `);function Dt(e){let t=Tt(e),n=new p({provider:e.provider,model:e.model,prompt:Et,thresholdTokens:Math.floor(t*.8),summaryWords:1e3,reasoning:e.requestOptions.reasoning});return{compact:n.compact,shouldCompactOnTrigger:n.shouldCompactOnTrigger,triggers:{beforeTurn:!0}}}var Ot=class{agent;transcript;sessionStore;span;persisted;constructor(e){this.agent=new u({...e.agentConfig,observability:{trace:e.span}},e.session),e.compaction!==!1&&this.agent.setCompaction(Dt(this.agent)),this.transcript=new g(e.priorTurns??[]),this.sessionStore=e.sessionStore,this.span=e.span,this.persisted=!!e.session,this.agent.on(t=>{this.transcript.apply(t),e.onEvent(t,this.transcript)})}async save(){if(!this.sessionStore)return!1;try{return await this.sessionStore.save(await this.agent.snapshot(),this.transcript.turns),this.persisted=!0,!0}catch(e){let t=e instanceof Error?e.message:String(e);return this.span.warn(`Failed to save session: ${t}`),!1}}resumeCommand(e=!1){if(this.persisted)return`axle resume ${e?this.agent.sessionId.slice(0,8):this.agent.sessionId}`}};async function kt(e,t,n,r,i){let a=n.startSpan(e.spanName,{type:`workflow`}),o=new Ot({agentConfig:e.agentConfig,span:a,compaction:e.compaction,session:e.session,priorTurns:e.priorTurns,sessionStore:i,onEvent:(e,t)=>r.onEvent(e,t)}),{agent:s}=o,c=new AbortController,l=0,u=()=>{l+=1,l===1&&s.stop()?r.warn(`Finishing the current step — Ctrl-C again to cancel now`):c.abort()};process.on(`SIGINT`,u),r.setInterruptHandler(u);let d=!e.session,f=async()=>{
|
|
8
|
+
`,`utf-8`)}function wt(e){switch(e.kind){case`model`:return`Model error: ${e.message}`;case`tool`:return`Tool error (${e.error.name}): ${e.message}`;case`parse`:return`Parse error: ${e.message}`}}function Tt(e,t){let n=Number(process.env.AXLE_CONTEXT_WINDOW);if(Number.isInteger(n)&&n>0)return n;if(t)return t;let r=e.provider.name.toLowerCase()===`gemini`?`google`:e.provider.name.toLowerCase();return k[e.model]?.contextWindow??k[`${r}/${e.model}`]?.contextWindow??2e5}const Et=[`You summarize an agent conversation so it can continue in a smaller context.`,`Preserve durable facts, decisions, constraints, file paths, tool outcomes,`,`completed work, and open tasks. Prefer concrete identifiers over prose.`].join(` `);function Dt(e){let t=Tt(e),n=new p({provider:e.provider,model:e.model,prompt:Et,thresholdTokens:Math.floor(t*.8),summaryWords:1e3,reasoning:e.requestOptions.reasoning});return{compact:n.compact,shouldCompactOnTrigger:n.shouldCompactOnTrigger,triggers:{beforeTurn:!0}}}var Ot=class{agent;transcript;sessionStore;span;persisted;constructor(e){this.agent=new u({...e.agentConfig,observability:{trace:e.span}},e.session),e.compaction!==!1&&this.agent.setCompaction(Dt(this.agent)),this.transcript=new g(e.priorTurns??[]),this.sessionStore=e.sessionStore,this.span=e.span,this.persisted=!!e.session,this.agent.on(t=>{this.transcript.apply(t),e.onEvent(t,this.transcript)})}async save(){if(!this.sessionStore)return!1;try{return await this.sessionStore.save(await this.agent.snapshot(),this.transcript.turns),this.persisted=!0,!0}catch(e){let t=e instanceof Error?e.message:String(e);return this.span.warn(`Failed to save session: ${t}`),!1}}resumeCommand(e=!1){if(this.persisted)return`axle resume ${e?this.agent.sessionId.slice(0,8):this.agent.sessionId}`}};async function kt(e,t,n,r,i){let a=n.startSpan(e.spanName,{type:`workflow`}),o=new Ot({agentConfig:e.agentConfig,span:a,compaction:e.compaction,session:e.session,priorTurns:e.priorTurns,sessionStore:i,onEvent:(e,t)=>r.onEvent(e,t)}),{agent:s}=o,c=new AbortController,l=0,u=()=>{l+=1,l===1&&s.stop()?r.warn(`Finishing the current step — Ctrl-C again to cancel now`):c.abort()};process.on(`SIGINT`,u),r.setInterruptHandler(u);let d=!e.session,f=async()=>{i&&d&&await o.save()&&(d=!1)};if(e.session){let t=`Resuming session ${s.sessionId}`;if(r.info(t),n.info(t),e.resumedFromCwd&&e.resumedFromCwd!==process.cwd()){let t=`Session was started in ${e.resumedFromCwd}; resuming from ${process.cwd()}`;r.warn(t),n.warn(t)}}else if(i){let e=`Session ${s.sessionId}`;r.info(e),n.info(e)}e.priorTurns?.length&&r.renderPriorTurns(e.priorTurns);let p=()=>{let e=s.context(),n=Tt(s,e.limit);r.updateUsage({in:t.in,out:t.out,contextTokens:e.total,contextLimit:n})};p(),s.on(e=>{e.type===`compaction:complete`&&p()});let m=async e=>{d=!0;try{let i=await s.send(e,{signal:c.signal}).final;if(te(t,i.usage),p(),!i.ok){let e=wt(i.error);return r.error(e),n.error(e),a.error(e),!1}return n.info(i.response,{markdown:!0}),!0}finally{l=0}};try{if(e.initial!==void 0){let t=await m(e.initial);if(await f(),!t)return a.end(`error`),!1}if(e.interactive)for(;;){let e=await r.promptInput();if(e===null)break;let t=e.trim();if(t!==``){if(t===`/quit`)break;try{await m(t)}catch(e){if(e instanceof ee){r.warn(`Interrupted`),n.warn(`Interrupted`);break}let t=e instanceof Error?e.message:String(e);r.error(t),n.error(t)}await f()}}return a.end(),!0}catch(e){if(e instanceof ee)return r.warn(`Interrupted`),n.warn(`Interrupted`),a.end(`cancelled`),!1;let t=e instanceof Error?e.message:String(e);throw a.error(t),a.end(`error`),e}finally{process.removeListener(`SIGINT`,u),r.setInterruptHandler(void 0),await f();let e=o.resumeCommand();if(i&&e){let t=`Resume this session:\n${e}`;r.info(t),n.info(t)}}}async function At(e,t,n,r,i,a){let o=await Promise.all(e.inputs.map(e=>be(e))),s=[...new Set(o.flat())].sort();if(s.length===0){let t=`No files matched: ${e.inputs.join(` `)}`;return i.warn(t),r.warn(t),!0}let l=`Batch: ${s.length} input(s) matched "${e.inputs.join(` `)}"`;i.info(l),r.info(l);let u=e.incremental?await St():new Map,f=e.files?await Promise.all(e.files.map(e=>re(e))):[],p=0,m=0,h=0,g=0,ne=0,_=()=>({total:s.length,completed:p,skipped:m,failed:h,tokensIn:g,tokensOut:ne});a?.batchStarted(_());let v=new AbortController,y=()=>{i.warn(`Cancelling batch…`),v.abort()};process.on(`SIGINT`,y),i.setInterruptHandler(y);try{await jt(e.concurrency,s,async o=>{if(v.signal.aborted)return;let s=r.startSpan(`batch:${o}`,{type:`workflow`}),l;try{l=bt(await D(o))}catch(e){let t=e instanceof Error?e.message:String(e);i.error(`${o}: failed — ${t}`),s.error(`Failed: ${t}`),s.end(`error`),h++,a?.itemFinished(o,_());return}let y=u.get(xt(e.jobName,o));if(e.incremental&&y?.status===`completed`&&y.hash===l){i.info(`${o}: unchanged — skipped`),s.info(`Skipped (already completed)`),s.end(),m++,a?.itemFinished(o,_());return}let b=new R(e.definition,{home:e.home,compaction:e.compaction});a?.itemStarted(o);let x=new Ot({agentConfig:e.agentConfig,span:s,compaction:e.compaction,sessionStore:b,onEvent:(t,n)=>{if(e.verbose)i.onEvent(t,n);else if(a&&t.type===`part:start`){let e=t.part,n=e.type===`action`?c(e.detail.name):e.type===`thinking`?`Thinking`:e.type===`text`?`Writing`:void 0;n&&a.itemPhase(o,n)}}}),{agent:S}=x,C=async(t,n)=>{await Ct({job:e.jobName,file:o,hash:t,sessionId:S.sessionId,status:`failed`,timestamp:Date.now()});let r=x.resumeCommand(!0);i.error(`${o}: failed — ${n}${r?` (${r})`:``}`),s.error(`Failed: ${n}`),s.end(`error`),h++,a?.itemFinished(o,_())};try{let r=new d({prompt:e.task});for(let e of f)r.addFile(e);r.addFile(await re(o));let c=await S.send(r.withInputs({...t,file:o}),{signal:v.signal}).final;if(te(n,c.usage),g+=c.usage.in,ne+=c.usage.out,await x.save(),!c.ok){await C(l,wt(c.error));return}await Ct({job:e.jobName,file:o,hash:l,sessionId:S.sessionId,status:`completed`,timestamp:Date.now()});let u=x.resumeCommand(!0);i.success(`${o}: done${u?` (${u})`:``}`),s.end(),p++,a?.itemFinished(o,_())}catch(e){if(await x.save(),e instanceof ee){s.end(`cancelled`),h++,a?.itemFinished(o,_());return}await C(l,e instanceof Error?e.message:String(e))}})}finally{process.removeListener(`SIGINT`,y),i.setInterruptHandler(void 0)}let b=v.signal.aborted,x=`Batch complete: ${p} completed, ${m} skipped, ${h} failed${b?` (cancelled)`:``}`;return i.info(x),r.info(x),h===0&&!b}async function jt(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 Mt=[{label:`Older than 24 hours`,ms:864e5},{label:`Older than 7 days`,ms:6048e5},{label:`Older than 30 days`,ms:2592e6},{label:`Everything`,ms:0}];async function Nt(e){O.intro(`axle cleanup`);let t=await st(e);if(t.length===0){O.outro(`No saved sessions.`);return}let n=Date.now(),r=(e,t)=>{if(t===0||e.corrupt)return!0;let r=Date.parse(e.updatedAt);return!Number.isFinite(r)||n-r>t},i=Mt.map(e=>{let n=t.filter(t=>r(t,e.ms)),i=n.reduce((e,t)=>e+t.sizeBytes,0);return{window:e,matched:n,label:`${e.label} (${n.length} session${n.length===1?``:`s`} · ${Pt(i)})`}}).filter(e=>e.matched.length>0);if(i.length===0){O.outro(`Nothing old enough to clean up (${t.length} recent sessions).`);return}let a=`__cancel__`,o=await O.select({message:`What should be cleaned up?`,options:[...i.map((e,t)=>({value:String(t),label:e.label})),{value:a,label:`Nothing, cancel`}]});if(O.isCancel(o)||o===a){O.outro(`Nothing deleted.`);return}let s=i[Number(o)],c=await O.confirm({message:`Delete ${s.matched.length} session(s)? This cannot be undone.`,initialValue:!1});if(O.isCancel(c)||!c){O.outro(`Nothing deleted.`);return}for(let e of s.matched)await pe(e.path,{force:!0});O.outro(`Deleted ${s.matched.length} session(s).`)}function Pt(e){return e>=1048576?`${(e/1048576).toFixed(1)} MB`:e>=1024?`${(e/1024).toFixed(1)} kB`:`${e} B`}var Ft=class{active;ended=!1;prompt(){if(this.ended||process.stdin.readableEnded)return Promise.resolve(null);let e=Se({input:process.stdin,output:process.stdout});return this.active=e,e.on(`SIGINT`,()=>e.close()),new Promise(t=>{let n=()=>{this.ended=!0,this.active=void 0,t(null)};e.once(`close`,n),e.question(`
|
|
9
9
|
> `,r=>{e.removeListener(`close`,n),e.close(),this.active=void 0,t(r)})})}close(){this.active?.close(),this.active=void 0}},It=class{write;atLineStart=!0;readline=new Ft;constructor(e){this.write=e?.write??(e=>process.stdout.write(e))}promptInput(){return this.endLine(),this.atLineStart=!0,this.readline.prompt()}renderPriorTurns(e){for(let t of e)for(let e of t.parts)this.renderStaticPart(t.owner,e)}onEvent(e,t){switch(e.type){case`text:delta`:this.emit(e.delta);break;case`part:end`:{let n=Lt(t,e.turnId,e.partId);n?.type===`thinking`?this.renderStaticPart(`agent`,n):this.endLine();break}case`action:complete`:case`action:error`:{let n=Lt(t,e.turnId,e.partId);n?.type===`action`&&this.renderStaticPart(`agent`,n);break}case`compaction:complete`:case`compaction:error`:{let n=Lt(t,e.turnId,e.partId);n?.type===`compaction`&&this.renderStaticPart(`agent`,n);break}case`error`:this.line(`✖ ${e.error.message}`)}}info(e){this.line(`ℹ ${o(e)}`)}success(e){this.line(`✔ ${o(e)}`)}warn(e){this.line(`⚠ ${o(e)}`)}error(e){this.line(`✖ ${o(e)}`)}updateUsage(e){}setInterruptHandler(e){}close(){this.readline.close(),this.endLine()}renderStaticPart(e,t){switch(t.type){case`text`:{if(!t.text.trim())return;let n=t.text.trim();this.line(e===`user`?`❯ ${o(n)}`:n);return}case`thinking`:{let e=s(t.timing);this.line(`✔ Thinking${e?` (${e})`:``}`);return}case`action`:{let e=t.status===`error`?`✖`:t.status===`cancelled`?`⚠`:`✔`,n=a(t),r=t.status===`complete`?s(t.timing):void 0;this.line(`${e} ${c(t.detail.name)}${n?` ${n}`:``}${r?` (${r})`:``}${t.status===`cancelled`?` (cancelled)`:``}`);return}case`compaction`:{if(t.status===`error`){this.line(`✖ Compaction failed: ${t.error}`);return}let e=s(t.timing);this.line(`✔ Compacted context${e?` (${e})`:``}`);return}default:return}}emit(e){e.length!==0&&(this.write(e),this.atLineStart=e.endsWith(`
|
|
10
10
|
`))}endLine(){this.atLineStart||this.emit(`
|
|
11
11
|
`)}line(e){this.endLine(),this.emit(e+`
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fifthrevision/axle-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/johncch/axle.git"
|
|
7
7
|
},
|
|
8
8
|
"description": "Axle command line workflow runner.",
|
|
9
9
|
"type": "module",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22"
|
|
12
|
+
},
|
|
10
13
|
"bin": {
|
|
11
14
|
"axle": "dist/cli.js"
|
|
12
15
|
},
|
|
@@ -22,23 +25,23 @@
|
|
|
22
25
|
"author": "Chong Han Chua",
|
|
23
26
|
"license": "ISC",
|
|
24
27
|
"dependencies": {
|
|
25
|
-
"@clack/prompts": "^1.
|
|
28
|
+
"@clack/prompts": "^1.8.0",
|
|
26
29
|
"@commander-js/extra-typings": "^15.0.0",
|
|
30
|
+
"@fifthrevision/axle": "0.32.0",
|
|
27
31
|
"commander": "^15.0.0",
|
|
28
32
|
"dotenv": "^17.4.2",
|
|
29
33
|
"glob": "^13.0.6",
|
|
30
34
|
"ink": "^7.1.1",
|
|
31
|
-
"react": "^19.
|
|
35
|
+
"react": "^19.3.0",
|
|
32
36
|
"yaml": "^2.9.0",
|
|
33
|
-
"zod": "^4.
|
|
34
|
-
"@fifthrevision/axle": "0.31.0"
|
|
37
|
+
"zod": "^4.6.2"
|
|
35
38
|
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@arethetypeswrong/core": "^0.18.5",
|
|
38
|
-
"@types/react": "^19.
|
|
41
|
+
"@types/react": "^19.3.0",
|
|
39
42
|
"publint": "^0.3.24",
|
|
40
|
-
"tsdown": "^0.
|
|
41
|
-
"tsx": "^4.23.
|
|
43
|
+
"tsdown": "^0.23.0",
|
|
44
|
+
"tsx": "^4.23.13",
|
|
42
45
|
"typescript": "^6.0.3"
|
|
43
46
|
},
|
|
44
47
|
"scripts": {
|