@fifthrevision/axle-cli 0.30.1 → 0.31.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 +295 -0
- package/dist/InkBatchRenderer-DPV54ovW.js +1 -0
- package/dist/InkRenderer-C6KY_Kfj.js +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +16 -5
- package/dist/format-CVXITc-j.js +3 -0
- package/dist/store-yk8NwV-f.js +5 -0
- package/dist/tools/index.d.ts +6 -14
- package/dist/tools/index.js +1 -1
- package/dist/tools-gv3b9sAQ.js +5 -0
- package/package.json +13 -12
- package/dist/store/index.d.ts +0 -11
- package/dist/store/index.js +0 -1
- package/dist/tools-DTZhd_3g.js +0 -5
package/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Axle CLI
|
|
2
|
+
|
|
3
|
+
An AI task runner built on [Axle](https://www.npmjs.com/package/@fifthrevision/axle).
|
|
4
|
+
Chat from the terminal, save recurring jobs as checked-in YAML recipes, fan a
|
|
5
|
+
recipe out over a folder of inputs — and resume any run, because every run is
|
|
6
|
+
a session.
|
|
7
|
+
|
|
8
|
+
A recipe is a saved partial application of an invocation: anything the
|
|
9
|
+
command line could say has a home in the YAML, and the command line
|
|
10
|
+
overrides selectively. The normative design lives in
|
|
11
|
+
[docs/architecture/cli.md](../../docs/architecture/cli.md).
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @fifthrevision/axle-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Bare `axle` starts an interactive chat using the default provider and model
|
|
22
|
+
from `~/.axle/cli.yaml` (`defaults.provider`, `defaults.models`). Running a
|
|
23
|
+
YAML job file with `-j` is the non-interactive path.
|
|
24
|
+
|
|
25
|
+
On first run with no configuration anywhere — no credentials, no `cli.yaml`
|
|
26
|
+
providers or defaults, no inline provider in the recipe — `axle` launches a
|
|
27
|
+
setup wizard:
|
|
28
|
+
pick a provider, paste a key (written to `~/.axle/credentials`, chmod 600),
|
|
29
|
+
and pick a default model. Re-run it anytime with `axle setup`. A run that
|
|
30
|
+
can't resolve a model drops into the same model picker.
|
|
31
|
+
|
|
32
|
+
Sessions accumulate under `~/.axle/sessions/cli/` with no automatic
|
|
33
|
+
retention; `axle cleanup` deletes them by age window (24h/7d/30d/all).
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
axle # interactive chat from configured defaults
|
|
37
|
+
axle -m "one question" # one-shot message, prints and exits
|
|
38
|
+
axle -j path/to/job.yaml # run a job file and exit
|
|
39
|
+
axle -j path/to/job.yaml -i # run the task, then continue interactively
|
|
40
|
+
axle -j path/to/job.yaml --args key=value other=thing
|
|
41
|
+
axle batch -j recipe.yaml 'data/*.md' # fan a recipe out over inputs
|
|
42
|
+
axle resume <id> # re-enter any saved session
|
|
43
|
+
axle resume <id> -m "follow up" # one-shot continuation
|
|
44
|
+
axle setup # (re)configure providers and defaults
|
|
45
|
+
axle cleanup # delete old sessions by age window
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Verbs select the machine; flags parameterize it. A session id prefix works
|
|
49
|
+
anywhere a full id does (`axle resume 3a2f` finds the unique match).
|
|
50
|
+
|
|
51
|
+
In the chat, `/quit` (or Ctrl-C / Ctrl-D at the prompt) exits. Ctrl-C during
|
|
52
|
+
a turn asks the agent to stop at the next tool boundary; a second Ctrl-C
|
|
53
|
+
cancels immediately. The session is saved on every exit path.
|
|
54
|
+
|
|
55
|
+
`--renderer` picks the screen renderer for the run: `ink` (default — terminal
|
|
56
|
+
UI with a live streaming region and input line) or `plain` (line-oriented).
|
|
57
|
+
Piped input or output always gets plain. `--no-log` disables the run log
|
|
58
|
+
(otherwise written to `~/.axle/logs/cli/<timestamp>.log`), `-d`/`--debug`
|
|
59
|
+
prints debug detail, and `--args key=value` supplies `{{variables}}` to the
|
|
60
|
+
recipe's task template.
|
|
61
|
+
|
|
62
|
+
Every run persists a resumable session to `~/.axle/sessions/cli/<id>.json`
|
|
63
|
+
(the id is printed at run start and exit). Resuming restores the saved
|
|
64
|
+
provider, model, tools, and conversation — no job file needed.
|
|
65
|
+
|
|
66
|
+
A job file specifies the provider, task prompt, and optional tools/files:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
# job.yaml
|
|
70
|
+
provider: anthropic
|
|
71
|
+
model: anthropic/claude-sonnet-5
|
|
72
|
+
|
|
73
|
+
task: |
|
|
74
|
+
Summarize the attached document.
|
|
75
|
+
|
|
76
|
+
tools:
|
|
77
|
+
- calculator
|
|
78
|
+
|
|
79
|
+
providerTools:
|
|
80
|
+
- web_search
|
|
81
|
+
|
|
82
|
+
files:
|
|
83
|
+
- ./data/report.txt
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`provider` says where requests go. A string names a provider — a built-in
|
|
87
|
+
type (`anthropic`, `openai`, `gemini`, `chatcompletions`) or a provider
|
|
88
|
+
profile from `cli.yaml` — and an object is inline endpoint configuration.
|
|
89
|
+
Both `provider` and `model` are optional; anything the job leaves out
|
|
90
|
+
resolves through the config chain:
|
|
91
|
+
|
|
92
|
+
- provider: job → `defaults.provider` in `cli.yaml`
|
|
93
|
+
- model: job → `defaults.models.<provider name>` → `<TYPE>_MODEL` env or
|
|
94
|
+
credentials → interactive model picker
|
|
95
|
+
|
|
96
|
+
So a model-only job runs on the configured default provider, and a job with
|
|
97
|
+
neither runs entirely on defaults. `model` is a publisher-qualified registry
|
|
98
|
+
id (e.g. `anthropic/claude-sonnet-5`, `openai/gpt-5.5`) or a bare
|
|
99
|
+
provider-native id:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
# Ollama, or any OpenAI-compatible endpoint
|
|
103
|
+
provider:
|
|
104
|
+
type: chatcompletions
|
|
105
|
+
baseUrl: http://localhost:11434/v1
|
|
106
|
+
model: gemma3
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Optional `system` sets the system prompt, and an optional `request` block
|
|
110
|
+
sets provider-portable request options:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
system: You are a terse analyst.
|
|
114
|
+
|
|
115
|
+
request:
|
|
116
|
+
reasoning: on
|
|
117
|
+
temperature: 0.2
|
|
118
|
+
maxOutputTokens: 16000
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`reasoning` takes `default`, `off`, `on`, or `{ effort: low | medium | high }`.
|
|
122
|
+
Leave it unset (or `default`) and the model runs at its provider's own
|
|
123
|
+
default, which is always safe, including for models that cannot disable
|
|
124
|
+
thinking. `on` is medium effort; `off` sends the provider's explicit disable
|
|
125
|
+
and is rejected by models that cannot turn thinking off. On models that only
|
|
126
|
+
take a thinking budget (Claude Haiku, Opus, and Sonnet 4.5; Gemini 2.5), a
|
|
127
|
+
`maxOutputTokens` you set must exceed the budget: 8,192 for `on`, 16,384 for
|
|
128
|
+
`high`. Leave it unset and Axle's default already does.
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
request:
|
|
132
|
+
reasoning:
|
|
133
|
+
effort: high
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Long sessions compact automatically: when the conversation approaches the
|
|
137
|
+
model's context window (~80%), the next send first replaces the history with
|
|
138
|
+
a ~1000-word summary plus a slice of recent user messages kept verbatim (up
|
|
139
|
+
to a tenth of the threshold), summarized by the session's own provider, model, and
|
|
140
|
+
`reasoning` setting; the transcript records a `✔ Compacted context` line.
|
|
141
|
+
Compacted sessions snapshot and resume like any other. Opt out per recipe
|
|
142
|
+
with:
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
compaction: false
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`AXLE_CONTEXT_WINDOW=<tokens>` overrides the resolved window when the
|
|
149
|
+
registry gets a model wrong — the usage bar, compaction threshold, and
|
|
150
|
+
summary target all scale with it. A small value (e.g. `3000`) forces a
|
|
151
|
+
compaction within a few exchanges, which is also the way to see one without
|
|
152
|
+
filling a real context window.
|
|
153
|
+
|
|
154
|
+
CLI job files can use these local tool names:
|
|
155
|
+
|
|
156
|
+
- `calculator`
|
|
157
|
+
- `exec`
|
|
158
|
+
- `patch-file`
|
|
159
|
+
- `read-file`
|
|
160
|
+
- `write-file`
|
|
161
|
+
|
|
162
|
+
## Batch
|
|
163
|
+
|
|
164
|
+
Batch is map(recipe, inputs): one isolated session per input. Inputs resolve
|
|
165
|
+
as positional arguments to the `batch` verb, then the recipe's `batch:`
|
|
166
|
+
block, then an interactive prompt:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
axle batch -j summarize.yml 'data/*.txt' # inputs from the command line
|
|
170
|
+
axle batch -j summarize.yml # inputs from the recipe, or prompted
|
|
171
|
+
axle -j summarize.yml # batch: block present → batch run
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For a recurring job, put the inputs in the recipe — it stays
|
|
175
|
+
self-documenting and runs with plain `-j`:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
# job.yaml
|
|
179
|
+
provider: anthropic
|
|
180
|
+
|
|
181
|
+
task: |
|
|
182
|
+
Summarize this file ({{file}}).
|
|
183
|
+
|
|
184
|
+
batch:
|
|
185
|
+
files: "./data/*.txt"
|
|
186
|
+
concurrency: 3
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Each matched file is attached to the instruct and available as `{{file}}`.
|
|
190
|
+
Every input runs as its own session, so a failed item is inspected or
|
|
191
|
+
continued like any other run: `axle resume <id>` (every settled item line
|
|
192
|
+
prints the id). A project-local ledger (`.axle/batch.jsonl`) indexes
|
|
193
|
+
input → session. Skipping is opt-in: `--incremental` (or
|
|
194
|
+
`incremental: true` in the block; `--no-incremental` overrides) skips
|
|
195
|
+
completed inputs whose content is unchanged — useful when a folder of
|
|
196
|
+
inputs grows over time. Recipe edits never auto-invalidate; a plain run is
|
|
197
|
+
the force-fresh gesture and re-runs everything.
|
|
198
|
+
|
|
199
|
+
On a terminal, batch shows test-runner-style progress: one spinner row per
|
|
200
|
+
in-flight item (current phase, elapsed) and a running totals line, with
|
|
201
|
+
settled items committed to scrollback. `--verbose` (or `concurrency: 1`)
|
|
202
|
+
streams each item's full transcript instead. Piped output prints one line
|
|
203
|
+
per settled item.
|
|
204
|
+
|
|
205
|
+
Batch runs are non-interactive; a batch job cannot be combined with
|
|
206
|
+
`--interactive`.
|
|
207
|
+
|
|
208
|
+
## MCP Servers
|
|
209
|
+
|
|
210
|
+
Add an `mcps` key to connect to MCP servers. Both stdio and HTTP transports
|
|
211
|
+
are supported.
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
# job.yaml
|
|
215
|
+
provider:
|
|
216
|
+
type: anthropic
|
|
217
|
+
|
|
218
|
+
mcps:
|
|
219
|
+
- name: wc
|
|
220
|
+
transport: stdio
|
|
221
|
+
command: npx
|
|
222
|
+
args: ["tsx", "examples/mcps/wordcount-server.ts"]
|
|
223
|
+
- transport: http
|
|
224
|
+
url: http://localhost:3100/mcp
|
|
225
|
+
|
|
226
|
+
task: |
|
|
227
|
+
Count the words in "hello world"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Each entry supports:
|
|
231
|
+
|
|
232
|
+
- `transport` — `"stdio"` or `"http"` (required)
|
|
233
|
+
- `name` — prefix for tool names from this server (optional)
|
|
234
|
+
- `command` / `args` / `env` — for stdio transport
|
|
235
|
+
- `url` / `headers` — for HTTP transport
|
|
236
|
+
|
|
237
|
+
## Configuration
|
|
238
|
+
|
|
239
|
+
For CLI use, put provider secrets in your environment, a local `.env` file, or
|
|
240
|
+
a credentials file. Credentials files use the same key names as the
|
|
241
|
+
environment variables, one `KEY=value` per line, and are read in order —
|
|
242
|
+
environment first, then the project's `.axle/credentials`, then the
|
|
243
|
+
user-level `~/.axle/credentials`:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
OPENAI_API_KEY=...
|
|
247
|
+
ANTHROPIC_API_KEY=...
|
|
248
|
+
GEMINI_API_KEY=...
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Optional model overrides use provider-specific variables:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
OPENAI_MODEL=openai/gpt-5.5
|
|
255
|
+
ANTHROPIC_MODEL=anthropic/claude-sonnet-5
|
|
256
|
+
GEMINI_MODEL=google/gemini-3.5-pro
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For OpenAI-compatible endpoints:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
CHATCOMPLETIONS_BASE_URL=http://localhost:11434/v1
|
|
263
|
+
CHATCOMPLETIONS_MODEL=llama3
|
|
264
|
+
CHATCOMPLETIONS_API_KEY=...
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Provider-level keys in the job file override environment variables. To
|
|
268
|
+
reference a non-standard environment variable from a job file, use `apiKeyEnv`:
|
|
269
|
+
|
|
270
|
+
```yaml
|
|
271
|
+
provider:
|
|
272
|
+
type: openai
|
|
273
|
+
apiKeyEnv: CUSTOM_OPENAI_KEY
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`cli.yaml` (user-level `~/.axle/cli.yaml`, overridden per-project by
|
|
277
|
+
`.axle/cli.yaml`) holds named provider profiles and defaults:
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
providers:
|
|
281
|
+
openrouter: # a profile: pure endpoint config, no model
|
|
282
|
+
type: chatcompletions
|
|
283
|
+
baseUrl: https://openrouter.ai/api/v1
|
|
284
|
+
apiKeyEnv: OPENROUTER_API_KEY
|
|
285
|
+
|
|
286
|
+
defaults:
|
|
287
|
+
provider: openrouter # used when a job names no provider
|
|
288
|
+
models: # per-provider default models
|
|
289
|
+
openrouter: z-ai/glm-4.6
|
|
290
|
+
anthropic: anthropic/claude-sonnet-5
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Profile names share a namespace with the built-in types and may shadow
|
|
294
|
+
them. Across the user and project layers, `defaults` merge per key while
|
|
295
|
+
profiles replace wholesale.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,i as t}from"./format-CVXITc-j.js";import{a as n,i as r,t as i}from"./store-yk8NwV-f.js";import{Static as a,Text as o,render as s,useInput as c}from"ink";import{useSyncExternalStore as l}from"react";import{Fragment as u,jsx as d,jsxs as f}from"react/jsx-runtime";function p({store:e}){let i=l(e.subscribe,e.getSnapshot),s=n();c((e,t)=>{t.ctrl&&e===`c`&&i.onInterrupt?.()});let p=Date.now();return f(u,{children:[d(a,{items:i.staticItems,children:(e,t)=>d(r,{level:e.level,text:e.text},t)}),!i.closed&&i.rows.map(e=>f(o,{children:[d(o,{color:`cyan`,children:s}),` `,h(e.input),` `,f(o,{dimColor:!0,children:[`· `,e.phase,` · `,t(p-e.startedAt)]})]},e.input)),!i.closed&&i.totals&&d(m,{totals:i.totals})]})}function m({totals:t}){let n=t.completed+t.skipped+t.failed;return f(o,{dimColor:!0,children:[` `,n,`/`,t.total,` settled`,t.failed>0?` · ${t.failed} failed`:``,` · ↑ `,e(t.tokensIn),` ↓`,` `,e(t.tokensOut)]})}function h(e){let t=e.split(`/`);return t.length>2?`…/${t.slice(-2).join(`/`)}`:e}var g=class{store=new i({staticItems:[],rows:[]});instance;constructor(){this.instance=s(d(p,{store:this.store}),{exitOnCtrlC:!1,patchConsole:!1})}batchStarted(e){this.store.update(t=>({...t,totals:e}))}itemStarted(e){this.store.update(t=>({...t,rows:[...t.rows,{input:e,phase:`starting`,startedAt:Date.now()}]}))}itemPhase(e,t){this.store.update(n=>({...n,rows:n.rows.map(n=>n.input===e?{...n,phase:t}:n)}))}itemFinished(e,t){this.store.update(n=>({...n,rows:n.rows.filter(t=>t.input!==e),totals:t}))}renderPriorTurns(e){}onEvent(e,t){}info(e){this.host(`info`,e)}success(e){this.host(`success`,e)}warn(e){this.host(`warn`,e)}error(e){this.host(`error`,e)}promptInput(){return Promise.resolve(null)}updateUsage(e){}setInterruptHandler(e){this.store.update(t=>({...t,onInterrupt:e}))}async close(){this.store.update(e=>({...e,closed:!0})),await new Promise(e=>setTimeout(e,20)),this.instance.unmount()}host(e,t){this.store.update(n=>({...n,staticItems:[...n.staticItems,{level:e,text:t}]}))}};export{g as InkBatchRenderer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,r as t,t as n}from"./store-yk8NwV-f.js";import{render as r}from"ink";import{jsx as i}from"react/jsx-runtime";var a=class{store=new n({staticItems:[],awaitingInput:!1,queuedInputs:[]});committed=new Set;instance;waiter;constructor(e){this.instance=r(i(t,{store:this.store,onSubmit:this.handleSubmit,statusBar:e?.statusBar??!0}),{exitOnCtrlC:!1,patchConsole:!1})}handleSubmit=e=>{let t=this.waiter;t?(this.waiter=void 0,this.store.update(e=>({...e,awaitingInput:!1})),t(e)):e!==null&&this.store.update(t=>({...t,queuedInputs:[...t.queuedInputs,e]}))};renderPriorTurns(e){if(e.length===0)return;let t=e.map(e=>({kind:`turn`,turn:e}));for(let t of e)this.committed.add(t.id);this.store.update(e=>({...e,staticItems:[...e.staticItems,...t]}))}onEvent(t,n){let{newlyFinished:r,liveTurn:i}=e(n.turns,this.committed);this.store.update(e=>({...e,staticItems:r.length?[...e.staticItems,...r]:e.staticItems,liveTurn:i}))}info(e){this.host(`info`,e)}success(e){this.host(`success`,e)}warn(e){this.host(`warn`,e)}error(e){this.host(`error`,e)}updateUsage(e){this.store.update(t=>({...t,usage:e}))}setInterruptHandler(e){this.store.update(t=>({...t,onInterrupt:e}))}promptInput(){let e=this.store.getSnapshot().queuedInputs;if(e.length>0){let[t,...n]=e;return this.store.update(e=>({...e,queuedInputs:n})),Promise.resolve(t)}return new Promise(e=>{this.waiter=e,this.store.update(e=>({...e,awaitingInput:!0}))})}async close(){this.handleSubmit(null),this.store.update(e=>({...e,closed:!0})),this.store.update(e=>e.liveTurn?{...e,staticItems:[...e.staticItems,{kind:`turn`,turn:e.liveTurn}],liveTurn:void 0}:e),await new Promise(e=>setTimeout(e,20)),this.instance.unmount()}host(e,t){this.store.update(n=>({...n,staticItems:[...n.staticItems,{kind:`host`,level:e,text:t}]}))}};export{a as InkRenderer};
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,i as t,n,r,t as i}from"./tools-
|
|
3
|
-
`)}
|
|
4
|
-
`))
|
|
5
|
-
|
|
6
|
-
|
|
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.31.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`])})]).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
|
+
`)}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
|
+
`):[];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
|
+
`)+`
|
|
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 O.isCancel(e)&&(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
|
+
`)){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()=>{!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
|
+
> `,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
|
+
`))}endLine(){this.atLineStart||this.emit(`
|
|
11
|
+
`)}line(e){this.endLine(),this.emit(e+`
|
|
12
|
+
`)}};function Lt(e,t,n){return e.getTurn(t)?.parts.find(e=>e.id===n)}function Rt(e){return typeof e==`object`&&!!e&&`itemStarted`in e}async function zt(e,t){if(e===`ink`&&process.stdout.isTTY&&process.stdin.isTTY){if(t?.batchProgress){let{InkBatchRenderer:e}=await import(`./InkBatchRenderer-DPV54ovW.js`);return new e}let{InkRenderer:e}=await import(`./InkRenderer-C6KY_Kfj.js`);return new e({statusBar:t?.statusBar})}return new It}const V=new l().name(`axle`).description(`Axle is a CLI tool for running AI workflows`).version(Ce).enablePositionalOptions().helpCommand(!0);function Bt(e){let t=e.renderer??`ink`;return t!==`plain`&&t!==`ink`&&V.error(`error: unknown renderer "${t}" (expected plain or ink)`),{renderer:t,log:e.log,debug:!!e.debug}}let H;V.option(`-j, --job <path>`,`Run a YAML job file instead of starting a chat`).option(`-m, --message <text>`,`Send one message and exit`).option(`-i, --interactive`,`With --job: continue the conversation interactively after the task`).option(`--args <args...>`,`Template variables in the form key=value`).option(`--renderer <mode>`,`Screen renderer: ink or plain (pipes always get plain)`,`ink`).option(`--no-log`,`Do not write the output to a log file`).option(`-d, --debug`,`Print additional debug information`).addHelpText(`after`,`
|
|
13
|
+
Run a session (default):
|
|
14
|
+
axle Interactive chat from configured defaults
|
|
15
|
+
axle -m "..." One-shot message
|
|
16
|
+
axle -j <recipe> Run a job file (batch if the recipe has a batch block)`).action(e=>{e.job&&e.message!==void 0&&V.error(`error: --message cannot be combined with --job`),H={kind:`kernel`,job:e.job,message:e.message,interactive:!!e.interactive,args:e.args??[],common:Bt(e)}}),V.command(`batch`).description(`Run a recipe once per input, one isolated session each`).argument(`[inputs...]`,`Globs or paths; defaults to the recipe's batch block, else prompts`).requiredOption(`-j, --job <path>`,`Recipe to run`).option(`--incremental`,`Skip completed inputs whose content is unchanged`).option(`--no-incremental`,`Run every input even if the recipe sets incremental`).option(`--verbose`,`Stream full item transcripts instead of progress rows (concurrency 1)`).option(`--args <args...>`,`Template variables in the form key=value`).option(`--renderer <mode>`,`Screen renderer: ink or plain (pipes always get plain)`,`ink`).option(`--no-log`,`Do not write the output to a log file`).option(`-d, --debug`,`Print additional debug information`).action((e,t)=>{H={kind:`batch`,job:t.job,inputs:e,incremental:t.incremental,verbose:!!t.verbose,args:t.args??[],common:Bt(t)}}),V.command(`resume`).description(`Re-enter a saved session`).argument(`<id>`,`Session id (unique prefixes accepted)`).option(`-m, --message <text>`,`Send one message and exit`).option(`--renderer <mode>`,`Screen renderer: ink or plain (pipes always get plain)`,`ink`).option(`--no-log`,`Do not write the output to a log file`).option(`-d, --debug`,`Print additional debug information`).action((e,t)=>{H={kind:`resume`,id:e,message:t.message,common:Bt(t)}}),V.command(`setup`).description(`Configure providers, credentials, and defaults`).action(async()=>{await gt(await P({})),process.exit(0)}),V.command(`cleanup`).description(`Delete saved sessions by age window`).action(async()=>{await Nt(),process.exit(0)}),await V.parseAsync(process.argv),H||process.exit(0);const U=H,W=U.common,G={date:new Date().toISOString().split(`T`)[0],datetime:new Date().toISOString(),cwd:process.cwd()};`args`in U&&Object.assign(G,_t(U.args));const K=new h;if(W.debug&&(K.minLevel=`debug`),W.debug){let e=new m({minLevel:`debug`,showInternal:!0,showTimestamp:!0,markdown:!0});K.addWriter(e)}if(W.log){let e=w(j().user,`logs`,`cli`);S(e,{recursive:!0});let t=w(e,`${new Date().toISOString().replace(/:/g,`-`)}.log`),n=C(t,`a`),r=new m({minLevel:`debug`,showInternal:!0,showTimestamp:!0,output:e=>ie(n,e+`
|
|
17
|
+
`)});K.addWriter(r)}const q=K.startSpan(`cli`,{type:`root`});let J;async function Vt(){try{await J?.close()}finally{await K.flush()}}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 Vt(),process.exit(1)}),W.debug&&(q.debug(`Invocation: `+JSON.stringify(U,null,2)),q.debug(`Additional Arguments: `+JSON.stringify(G,null,2)));async function Y(e){let t=e instanceof Error?e:Error(String(e));(J??console).error(t.message),q.error(t.message),q.debug(t.stack??``),q.end(`error`),await Vt(),J||V.outputHelp(),process.exit(1)}let Ht=await Xe({span:q}).catch(Y),X=await P({span:q}).catch(Y);const Z=U.kind!==`resume`&&U.job?await Ye(U.job,{span:q}).catch(Y):void 0,Ut=U.kind!==`resume`&&U.job&&Z?Z.name??se(process.cwd(),ce(U.job)):`job`,Wt=!!(process.stdin.isTTY&&process.stdout.isTTY);U.kind!==`resume`&&Wt&&ft(X,Ht,Z)&&(await gt(X),Ht=await Xe({span:q}),X=await P({span:q}));const Q=await vt({invocation:U,cliConfig:Ht,serviceConfig:X,jobConfig:Z,jobScope:Ut,variables:G,interactiveTerminal:Wt}).catch(Y),$=await zt(W.renderer,{batchProgress:Q.kind===`batch`&&!Q.spec.verbose,statusBar:Q.kind===`session`&&Q.spec.interactive});J=$;const Gt=()=>process.exit(130);$.setInterruptHandler(Gt),Q.definition.mcps?.length&&$.info(`Connecting MCP servers…`);const{mcps:Kt,agentConfig:qt}=await Ne(Q.definition,X,q).catch(Y);try{q.info(`All systems operational. Running job...`);let e=y(),t=performance.now(),n=!1;try{n=Q.kind===`batch`?await At({...Q.spec,definition:Q.definition,agentConfig:qt},G,e,q,$,Rt($)?$:void 0):await kt({...Q.spec,agentConfig:qt},e,q,$,Q.sessionStore)}catch(e){let t=e instanceof Error?e:Error(String(e));$.error(t.message),q.error(t.message),q.debug(t.stack??``)}finally{$.setInterruptHandler(Gt),Kt.length>0&&await Te(Kt,q)}let r=performance.now()-t;q.info(`Total run time: ${Math.round(r)}ms`),q.info(`Input tokens: ${e.in}`),q.info(`Output tokens: ${e.out}`),e.cachedIn!==void 0&&q.info(`Cached input tokens: ${e.cachedIn}`),e.cacheWriteIn!==void 0&&q.info(`Cache write input tokens: ${e.cacheWriteIn}`),e.reasoningOut!==void 0&&q.info(`Reasoning output tokens: ${e.reasoningOut}`);let i=`in ${(r/1e3).toFixed(1)}s · ↑ ${e.in} ↓ ${e.out} tokens`;n?($.success(`Done ${i}`),q.info(`Complete. Goodbye`),q.end()):($.error(`Failed ${i}`),q.error(`Job failed`),q.end(`error`),process.exitCode=1)}finally{await Vt()}export{};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
function e(e){return e.charAt(0).toUpperCase()+e.slice(1)}function t(e){if(!e?.end)return;let t=Date.parse(e.end)-Date.parse(e.start);if(!(!Number.isFinite(t)||t<0))return n(t)}function n(e){return e<1e3?`${Math.round(e)}ms`:`${(e/1e3).toFixed(1)}s`}function r(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}function i(e,t){return e.length>t?e.slice(0,t-1)+`…`:e}function a(e){return e.split(`
|
|
2
|
+
`).join(`
|
|
3
|
+
`)}function o(e){if(e.kind!==`tool`)return;let t=e.detail.parameters??{};if(Object.keys(t).length!==0)return i(JSON.stringify(t),80)}export{r as a,n as i,o as n,a as o,t as r,i as s,e as t};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{a as e,n as t,o as n,r,s as i,t as a}from"./format-CVXITc-j.js";import{Box as o,Static as s,Text as c,useInput as l}from"ink";import{useEffect as u,useState as d,useSyncExternalStore as f}from"react";import{Fragment as p,jsx as m,jsxs as h}from"react/jsx-runtime";const g=[`⠋`,`⠙`,`⠹`,`⠸`,`⠼`,`⠴`,`⠦`,`⠧`,`⠇`,`⠏`];function _({store:e,onSubmit:t,statusBar:n}){let r=f(e.subscribe,e.getSnapshot);return h(p,{children:[m(s,{items:r.staticItems,children:(e,t)=>m(C,{item:e},t)}),r.liveTurn&&m(w,{turn:r.liveTurn}),r.queuedInputs.map((e,t)=>h(c,{dimColor:!0,children:[`❯ `,e,` (queued)`]},t)),!r.closed&&m(b,{onSubmit:t,awaitingInput:r.awaitingInput,onInterrupt:r.onInterrupt}),n&&!r.closed&&r.usage&&m(v,{usage:r.usage})]})}function v({usage:t}){let n=t.contextLimit?`${y(t.contextTokens/t.contextLimit)} ~${e(t.contextTokens)}tok`:`ctx ~${e(t.contextTokens)}`;return h(c,{dimColor:!0,children:[` `,`↑ `,e(t.in),` ↓ `,e(t.out),` · `,n]})}function y(e){let t=Math.min(8,Math.round(e*8));return`█`.repeat(t)+`░`.repeat(8-t)}function b({onSubmit:e,awaitingInput:t,onInterrupt:r}){let[i,a]=d(``),s=t=>{a(``),e(t)};return l((n,o)=>{if(o.ctrl&&n===`c`){t?e(null):r?.();return}if(o.ctrl&&n===`d`){t&&i===``&&e(null);return}if(o.return){if(o.shift||o.meta){a(e=>e+`
|
|
2
|
+
`);return}s(i);return}if(o.backspace||o.delete){a(e=>e.slice(0,-1));return}if(o.ctrl&&n===`u`){a(``);return}n&&!o.ctrl&&!o.meta&&a(e=>e+n.replace(/\r\n?/g,`
|
|
3
|
+
`))}),!t&&i===``?null:m(o,{marginTop:1,children:h(c,{children:[m(c,{color:`cyan`,children:`❯ `}),n(i),m(c,{inverse:!0,children:` `})]})})}const x={info:{glyph:`ℹ`,color:`cyan`},success:{glyph:`✔`,color:`green`},warn:{glyph:`⚠`,color:`yellow`},error:{glyph:`✖`,color:`red`}};function S({level:e,text:t}){let r=x[e];return h(c,{children:[m(c,{color:r.color,children:r.glyph}),` `,n(t)]})}function C({item:e}){return e.kind===`host`?m(S,{level:e.level,text:e.text}):m(E,{turn:e.turn})}function w({turn:e}){let t=T();return e.parts.length===0?m(c,{color:`cyan`,children:t}):m(E,{turn:e,live:!0,spinnerFrame:t})}function T(){let[e,t]=d(0);return u(()=>{let e=setInterval(()=>t(e=>e+1),80);return()=>clearInterval(e)},[]),g[e%g.length]}function E({turn:e,live:t,spinnerFrame:n}){return h(o,{flexDirection:`column`,children:[e.parts.map((r,i)=>m(D,{part:r,owner:e.owner,live:t,spinner:i===e.parts.length-1?n:void 0},r.id)),e.error&&h(c,{color:`red`,children:[`✖ `,e.error.message]})]})}function D({part:e,owner:t,live:i,spinner:a}){switch(e.type){case`text`:{if(!e.text)return a?m(c,{color:`cyan`,children:a}):null;let r=i?A(e.text,6):e.text.trim();return t===`user`?h(c,{children:[`❯ `,n(r)]}):m(c,{children:r})}case`thinking`:{if(a)return h(c,{children:[m(c,{color:`cyan`,children:a}),` `,m(c,{dimColor:!0,children:`Thinking…`})]});let t=r(e.timing),n=e.summary?.trim();return h(c,{dimColor:!0,children:[`✔ Thinking`,t?` (${t})`:``,n?` — ${n}`:``]})}case`action`:return m(O,{part:e,live:i,spinner:a});case`citation`:return m(o,{flexDirection:`column`,children:e.citations.map((e,t)=>{let n=e.source;return h(c,{dimColor:!0,children:[`※ `,n.title??n.url??n.uri??e.source.type]},t)})});case`file`:return h(c,{dimColor:!0,children:[`▣ `,e.file.name,` (`,e.file.mimeType,`)`]});case`compaction`:{if(e.status===`error`)return h(c,{color:`red`,children:[`✖ Compaction failed: `,e.error]});if(e.status===`running`){let t=e.progress===void 0?``:` ${Math.round(e.progress*100)}%`;return h(c,{children:[m(c,{color:`cyan`,children:a??g[0]}),` `,h(c,{dimColor:!0,children:[`Compacting…`,t]})]})}let t=r(e.timing);return h(c,{children:[m(c,{color:`green`,children:`✔`}),` Compacted context`,t&&h(c,{dimColor:!0,children:[` (`,t,`)`]})]})}}}function O({part:e,live:n,spinner:i}){let s=e.status===`pending`||e.status===`running`,l=s?i??`⠋`:e.status===`complete`?`✔`:e.status===`error`?`✖`:`⚠`,u=s?`cyan`:e.status===`complete`?`green`:e.status===`error`?`red`:`yellow`,d=t(e),f=s||e.status===`cancelled`?void 0:r(e.timing);return h(o,{flexDirection:`column`,children:[h(c,{children:[m(c,{color:u,children:l}),` `,a(e.detail.name),d&&h(c,{dimColor:!0,children:[` `,d]}),f&&h(c,{dimColor:!0,children:[` (`,f,`)`]}),e.status===`cancelled`&&m(c,{dimColor:!0,children:` (cancelled)`})]}),m(k,{result:e.detail.result}),e.kind===`agent`&&e.detail.children.length>0&&m(o,{flexDirection:`column`,paddingLeft:2,children:e.detail.children.map(e=>m(E,{turn:e,live:n},e.id))})]})}function k({result:e}){if(!e)return null;if(e.type===`error`&&e.error)return h(c,{color:`red`,children:[` `,i(e.error.message,200)]});let t=typeof e.content==`string`?e.content:void 0;return t?.trim()?h(c,{dimColor:!0,children:[` `,i(j(t),200)]}):null}function A(e,t){let n=e.trimEnd(),r=n.length;for(let e=0;e<t;e++){let e=n.lastIndexOf(`
|
|
4
|
+
`,r-1);if(e===-1)return n;r=e}return n.slice(r+1)}function j(e){return e.trim().split(`
|
|
5
|
+
`,1)[0]}var M=class{state;listeners=new Set;constructor(e){this.state=e}getSnapshot=()=>this.state;subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)});update(e){this.state=e(this.state);for(let e of this.listeners)e()}};function N(e,t){let n=[],r;for(let i of e)t.has(i.id)||(i.status===`streaming`?r=i:(n.push({kind:`turn`,turn:i}),t.add(i.id)));return{newlyFinished:n,liveTurn:r}}export{T as a,S as i,N as n,_ as r,M as t};
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import { ExecutableTool, ProviderTool, ToolContext, ToolDefinition } from "@fifthrevision/axle";
|
|
2
2
|
import * as z$1 from "zod";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
//#region src/tools/types.d.ts
|
|
6
|
-
interface ExecProviderConfig {
|
|
7
|
-
timeout?: number;
|
|
8
|
-
maxBuffer?: number;
|
|
9
|
-
cwd?: string;
|
|
10
|
-
}
|
|
11
|
-
interface ToolProviderConfig {
|
|
12
|
-
exec?: ExecProviderConfig;
|
|
13
|
-
}
|
|
14
|
-
//#endregion
|
|
15
4
|
//#region src/tools/calculator.d.ts
|
|
16
5
|
declare const calculatorSchema: z.ZodObject<{
|
|
17
6
|
operation: z.ZodEnum<{
|
|
@@ -38,8 +27,11 @@ declare class ExecTool implements ExecutableTool<typeof execSchema> {
|
|
|
38
27
|
private timeout;
|
|
39
28
|
private maxBuffer;
|
|
40
29
|
private cwd?;
|
|
41
|
-
constructor(
|
|
42
|
-
|
|
30
|
+
constructor(options?: {
|
|
31
|
+
timeout?: number;
|
|
32
|
+
maxBuffer?: number;
|
|
33
|
+
cwd?: string;
|
|
34
|
+
});
|
|
43
35
|
summarize(params: z$1.infer<typeof execSchema>): string;
|
|
44
36
|
execute(params: z$1.infer<typeof execSchema>, ctx: ToolContext): Promise<string>;
|
|
45
37
|
}
|
|
@@ -68,4 +60,4 @@ declare const writeFileSchema: z.ZodObject<{
|
|
|
68
60
|
}, z.core.$strip>;
|
|
69
61
|
declare const writeFileTool: ExecutableTool<typeof writeFileSchema>;
|
|
70
62
|
//#endregion
|
|
71
|
-
export { type
|
|
63
|
+
export { type ExecutableTool, type ProviderTool, type ToolContext, type ToolDefinition, calculatorTool, execTool, patchFileTool, readFileTool, writeFileTool };
|
package/dist/tools/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,i as t,n,r,t as i}from"../tools-
|
|
1
|
+
import{a as e,i as t,n,r,t as i}from"../tools-gv3b9sAQ.js";export{e as calculatorTool,t as execTool,r as patchFileTool,n as readFileTool,i as writeFileTool};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{dirname as e}from"node:path";import*as t from"zod";import{z as n}from"zod";import{spawn as r}from"node:child_process";import{mkdir as i,readFile as a,writeFile as o}from"node:fs/promises";const s={name:`calculator`,description:`Performs basic arithmetic operations`,schema:n.object({operation:n.enum([`add`,`subtract`,`multiply`,`divide`]).describe(`The operation to perform (add, subtract, multiply, divide)`),a:n.number().describe(`First operand`),b:n.number().describe(`Second operand`)}),execute:async({operation:e,a:t,b:n})=>{switch(e){case`add`:return`${t} + ${n} = ${t+n}`;case`subtract`:return`${t} - ${n} = ${t-n}`;case`multiply`:return`${t} * ${n} = ${t*n}`;case`divide`:if(n===0)throw Error(`Cannot divide by zero`);return`${t} / ${n} = ${t/n}`;default:throw Error(`Unknown operation: ${e}`)}}};async function c(e,t={}){let n=t.timeout??3e4,i=t.maxBuffer??1048576;return new Promise((a,o)=>{let s=r(e,[],{shell:!0,cwd:t.cwd}),c=``,l=``,u=!1,d=!1,f=!1,p=setTimeout(()=>{u=!0,s.kill(`SIGTERM`)},n),m=()=>{d=!0,s.kill(`SIGTERM`)};t.signal?.addEventListener(`abort`,m);let h=()=>{clearTimeout(p),t.signal?.removeEventListener(`abort`,m)};s.stdout?.setEncoding(`utf-8`),s.stderr?.setEncoding(`utf-8`),s.stdout?.on(`data`,e=>{if(c+=e,c.length+l.length>i){f=!0,s.kill(`SIGTERM`);return}t.onChunk?.(e)}),s.stderr?.on(`data`,e=>{if(l+=e,c.length+l.length>i){f=!0,s.kill(`SIGTERM`);return}t.onChunk?.(e)}),s.on(`error`,e=>{h(),o(e)}),s.on(`close`,e=>{if(h(),u){let e=Error(`Command timed out after ${n}ms`);e.stdout=c,e.stderr=l,o(e);return}if(d){let e=Error(`Command aborted`);e.stdout=c,e.stderr=l,o(e);return}if(f){let e=Error(`Command output exceeded maxBuffer (${i} bytes)`);e.stdout=c,e.stderr=l,o(e);return}if(e!==0){let t=Error(`Command failed with exit code ${e}`);t.stdout=c,t.stderr=l,t.code=e??-1,o(t);return}a({stdout:c,stderr:l})})})}function l(e){if(e instanceof Error){let t=e,n=`Error executing command: ${e.message}`;return t.stdout&&(n+=`\n[stdout]: ${t.stdout}`),t.stderr&&(n+=`\n[stderr]: ${t.stderr}`),n}return`Error executing command: ${String(e)}`}function u(e,t){return t&&t.trim()?`${e}\n[stderr]: ${t}`:e}const d=t.object({command:t.string().describe(`The shell command to execute`)}),f=new class{name=`exec`;description=`Execute a shell command and return the output.`;schema=d;timeout;maxBuffer;cwd;constructor(e){this.timeout=e?.timeout??3e4,this.maxBuffer=e?.maxBuffer??1048576,this.cwd=e?.cwd}summarize(e){return e.command}async execute(e,t){let{command:n}=e;try{let e=await c(n,{timeout:this.timeout,maxBuffer:this.maxBuffer,cwd:this.cwd,signal:t.signal,onChunk:e=>t.emit(e)});return u(e.stdout,e.stderr)}catch(e){return l(e)}}},p={name:`patch-file`,description:`Patch a file by replacing an exact string match within a specified line range`,schema:n.object({path:n.string().describe(`The file path to patch`),old_string:n.string().describe(`The exact text to find and replace`),new_string:n.string().describe(`The replacement text`),start_line:n.number().int().positive().describe(`1-indexed start line of the region to match within`),end_line:n.number().int().positive().describe(`1-indexed end line (inclusive) of the region to match within`)}),summarize:({path:e,start_line:t,end_line:n})=>`${e}:${t}:${n}`,execute:async({path:e,old_string:t,new_string:n,start_line:r,end_line:i})=>{if(i<r)throw Error(`end_line (${i}) must be >= start_line (${r})`);let s;try{s=await a(e,`utf-8`)}catch(t){throw t instanceof Error?Error(`Failed to read file "${e}": ${t.message}`):t}let c=s.split(`
|
|
2
|
+
`);if(r>c.length)throw Error(`start_line (${r}) exceeds file length (${c.length} lines)`);if(i>c.length)throw Error(`end_line (${i}) exceeds file length (${c.length} lines)`);let l=c.slice(r-1,i).join(`
|
|
3
|
+
`),u=l.indexOf(t);if(u===-1)throw Error(`old_string not found within lines ${r}-${i} of "${e}"`);if(l.indexOf(t,u+1)!==-1)throw Error(`old_string matches multiple times within lines ${r}-${i} of "${e}"`);let d=l.replace(t,n),f=[...c.slice(0,r-1),...d.split(`
|
|
4
|
+
`),...c.slice(i)].join(`
|
|
5
|
+
`);try{await o(e,f,`utf-8`)}catch(t){throw t instanceof Error?Error(`Failed to write file "${e}": ${t.message}`):t}return`Successfully patched "${e}" (lines ${r}-${i})`}},m={name:`read-file`,description:`Read the contents of a file from disk`,schema:n.object({path:n.string().describe(`The file path to read from`)}),summarize:({path:e})=>e,execute:async({path:e})=>{try{return await a(e,`utf-8`)}catch(t){throw t instanceof Error?Error(`Failed to read file "${e}": ${t.message}`):t}}},h={name:`write-file`,description:`Write content to a file on disk, creating directories if needed`,schema:n.object({path:n.string().describe(`The file path to write to`),content:n.string().describe(`The content to write to the file`)}),summarize:({path:e})=>e,execute:async({path:t,content:n})=>{try{return await i(e(t),{recursive:!0}),await o(t,n,`utf-8`),`Successfully wrote ${n.length} characters to "${t}"`}catch(e){throw e instanceof Error?Error(`Failed to write file "${t}": ${e.message}`):e}}};export{s as a,f as i,m as n,p as r,h as t};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fifthrevision/axle-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/johncch/axle.git"
|
|
@@ -14,10 +14,6 @@
|
|
|
14
14
|
"./tools": {
|
|
15
15
|
"types": "./dist/tools/index.d.ts",
|
|
16
16
|
"import": "./dist/tools/index.js"
|
|
17
|
-
},
|
|
18
|
-
"./store": {
|
|
19
|
-
"types": "./dist/store/index.d.ts",
|
|
20
|
-
"import": "./dist/store/index.js"
|
|
21
17
|
}
|
|
22
18
|
},
|
|
23
19
|
"files": [
|
|
@@ -26,23 +22,28 @@
|
|
|
26
22
|
"author": "Chong Han Chua",
|
|
27
23
|
"license": "ISC",
|
|
28
24
|
"dependencies": {
|
|
29
|
-
"@
|
|
30
|
-
"commander": "^
|
|
25
|
+
"@clack/prompts": "^1.7.0",
|
|
26
|
+
"@commander-js/extra-typings": "^15.0.0",
|
|
27
|
+
"commander": "^15.0.0",
|
|
31
28
|
"dotenv": "^17.4.2",
|
|
32
29
|
"glob": "^13.0.6",
|
|
30
|
+
"ink": "^7.1.1",
|
|
31
|
+
"react": "^19.2.8",
|
|
33
32
|
"yaml": "^2.9.0",
|
|
34
33
|
"zod": "^4.4.3",
|
|
35
|
-
"@fifthrevision/axle": "0.
|
|
34
|
+
"@fifthrevision/axle": "0.31.0"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
|
-
"@arethetypeswrong/core": "^0.18.
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
37
|
+
"@arethetypeswrong/core": "^0.18.5",
|
|
38
|
+
"@types/react": "^19.2.18",
|
|
39
|
+
"publint": "^0.3.24",
|
|
40
|
+
"tsdown": "^0.22.14",
|
|
41
|
+
"tsx": "^4.23.12",
|
|
42
42
|
"typescript": "^6.0.3"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"start": "tsx ./src/cli.ts",
|
|
46
|
+
"generate:job-schema": "tsx ./scripts/generate-job-schema.ts",
|
|
46
47
|
"build": "tsdown --clean --minify",
|
|
47
48
|
"build:watch": "tsdown --watch",
|
|
48
49
|
"build-dev": "tsdown"
|
package/dist/store/index.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { FileStore } from "@fifthrevision/axle";
|
|
2
|
-
|
|
3
|
-
//#region src/store/LocalFileStore.d.ts
|
|
4
|
-
declare class LocalFileStore implements FileStore {
|
|
5
|
-
readonly rootPath: string;
|
|
6
|
-
constructor(rootPath: string);
|
|
7
|
-
read(path: string): Promise<string | null>;
|
|
8
|
-
write(path: string, content: string): Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
//#endregion
|
|
11
|
-
export { LocalFileStore };
|
package/dist/store/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import e from"node:fs/promises";import t from"node:path";var n=class{rootPath;constructor(e){this.rootPath=e}async read(n){let r=t.join(this.rootPath,n);try{return await e.readFile(r,`utf-8`)}catch{return null}}async write(n,r){let i=t.join(this.rootPath,n);await e.mkdir(t.dirname(i),{recursive:!0}),await e.writeFile(i,r,`utf-8`)}};export{n as LocalFileStore};
|
package/dist/tools-DTZhd_3g.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import*as e from"zod";import{z as t}from"zod";import{spawn as n}from"node:child_process";import{mkdir as r,readFile as i,writeFile as a}from"node:fs/promises";import{dirname as o}from"node:path";const s={name:`calculator`,description:`Performs basic arithmetic operations`,schema:t.object({operation:t.enum([`add`,`subtract`,`multiply`,`divide`]).describe(`The operation to perform (add, subtract, multiply, divide)`),a:t.number().describe(`First operand`),b:t.number().describe(`Second operand`)}),execute:async({operation:e,a:t,b:n})=>{switch(e){case`add`:return`${t} + ${n} = ${t+n}`;case`subtract`:return`${t} - ${n} = ${t-n}`;case`multiply`:return`${t} * ${n} = ${t*n}`;case`divide`:if(n===0)throw Error(`Cannot divide by zero`);return`${t} / ${n} = ${t/n}`;default:throw Error(`Unknown operation: ${e}`)}}};async function c(e,t={}){let r=t.timeout??3e4,i=t.maxBuffer??1048576;return new Promise((a,o)=>{let s=n(e,[],{shell:!0,cwd:t.cwd}),c=``,l=``,u=!1,d=!1,f=!1,p=setTimeout(()=>{u=!0,s.kill(`SIGTERM`)},r),m=()=>{d=!0,s.kill(`SIGTERM`)};t.signal?.addEventListener(`abort`,m);let h=()=>{clearTimeout(p),t.signal?.removeEventListener(`abort`,m)};s.stdout?.setEncoding(`utf-8`),s.stderr?.setEncoding(`utf-8`),s.stdout?.on(`data`,e=>{if(c+=e,c.length+l.length>i){f=!0,s.kill(`SIGTERM`);return}t.onChunk?.(e)}),s.stderr?.on(`data`,e=>{if(l+=e,c.length+l.length>i){f=!0,s.kill(`SIGTERM`);return}t.onChunk?.(e)}),s.on(`error`,e=>{h(),o(e)}),s.on(`close`,e=>{if(h(),u){let e=Error(`Command timed out after ${r}ms`);e.stdout=c,e.stderr=l,o(e);return}if(d){let e=Error(`Command aborted`);e.stdout=c,e.stderr=l,o(e);return}if(f){let e=Error(`Command output exceeded maxBuffer (${i} bytes)`);e.stdout=c,e.stderr=l,o(e);return}if(e!==0){let t=Error(`Command failed with exit code ${e}`);t.stdout=c,t.stderr=l,t.code=e??-1,o(t);return}a({stdout:c,stderr:l})})})}function l(e){if(e instanceof Error){let t=e,n=`Error executing command: ${e.message}`;return t.stdout&&(n+=`\n[stdout]: ${t.stdout}`),t.stderr&&(n+=`\n[stderr]: ${t.stderr}`),n}return`Error executing command: ${String(e)}`}function u(e,t){return t&&t.trim()?`${e}\n[stderr]: ${t}`:e}const d=e.object({command:e.string().describe(`The shell command to execute`)}),f=new class{name=`exec`;description=`Execute a shell command and return the output.`;schema=d;timeout=3e4;maxBuffer=1024*1024;cwd;constructor(e){e&&this.configure(e)}configure(e){this.timeout=e.timeout??3e4,this.maxBuffer=e.maxBuffer??1024*1024,this.cwd=e.cwd}summarize(e){return e.command}async execute(e,t){let{command:n}=e;try{let e=await c(n,{timeout:this.timeout,maxBuffer:this.maxBuffer,cwd:this.cwd,signal:t.signal,onChunk:e=>t.emit(e)});return u(e.stdout,e.stderr)}catch(e){return l(e)}}},p={name:`patch-file`,description:`Patch a file by replacing an exact string match within a specified line range`,schema:t.object({path:t.string().describe(`The file path to patch`),old_string:t.string().describe(`The exact text to find and replace`),new_string:t.string().describe(`The replacement text`),start_line:t.number().int().positive().describe(`1-indexed start line of the region to match within`),end_line:t.number().int().positive().describe(`1-indexed end line (inclusive) of the region to match within`)}),summarize:({path:e,start_line:t,end_line:n})=>`${e}:${t}:${n}`,execute:async({path:e,old_string:t,new_string:n,start_line:r,end_line:o})=>{if(o<r)throw Error(`end_line (${o}) must be >= start_line (${r})`);let s;try{s=await i(e,`utf-8`)}catch(t){throw t instanceof Error?Error(`Failed to read file "${e}": ${t.message}`):t}let c=s.split(`
|
|
2
|
-
`);if(r>c.length)throw Error(`start_line (${r}) exceeds file length (${c.length} lines)`);if(o>c.length)throw Error(`end_line (${o}) exceeds file length (${c.length} lines)`);let l=c.slice(r-1,o).join(`
|
|
3
|
-
`),u=l.indexOf(t);if(u===-1)throw Error(`old_string not found within lines ${r}-${o} of "${e}"`);if(l.indexOf(t,u+1)!==-1)throw Error(`old_string matches multiple times within lines ${r}-${o} of "${e}"`);let d=l.replace(t,n),f=[...c.slice(0,r-1),...d.split(`
|
|
4
|
-
`),...c.slice(o)].join(`
|
|
5
|
-
`);try{await a(e,f,`utf-8`)}catch(t){throw t instanceof Error?Error(`Failed to write file "${e}": ${t.message}`):t}return`Successfully patched "${e}" (lines ${r}-${o})`}},m={name:`read-file`,description:`Read the contents of a file from disk`,schema:t.object({path:t.string().describe(`The file path to read from`)}),summarize:({path:e})=>e,execute:async({path:e})=>{try{return await i(e,`utf-8`)}catch(t){throw t instanceof Error?Error(`Failed to read file "${e}": ${t.message}`):t}}},h={name:`write-file`,description:`Write content to a file on disk, creating directories if needed`,schema:t.object({path:t.string().describe(`The file path to write to`),content:t.string().describe(`The content to write to the file`)}),summarize:({path:e})=>e,execute:async({path:e,content:t})=>{try{return await r(o(e),{recursive:!0}),await a(e,t,`utf-8`),`Successfully wrote ${t.length} characters to "${e}"`}catch(t){throw t instanceof Error?Error(`Failed to write file "${e}": ${t.message}`):t}}};export{s as a,f as i,m as n,p as r,h as t};
|