@wanghuimvp/axon 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/cli.js +421 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Axon
|
|
2
|
+
|
|
3
|
+
An agentic coding CLI. Axon streams from Anthropic and runs a multi-step tool loop over your codebase — it reads, searches, and reasons across your files to answer a prompt.
|
|
4
|
+
|
|
5
|
+
> Foundation release (`0.0.x`). Non-interactive `axon -p` mode with read-only tools. Interactive TUI, write/edit/shell tools, and multi-provider support are on the roadmap.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @wanghuimvp/axon
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Axon reads your Anthropic key from the environment:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# macOS / Linux
|
|
19
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
20
|
+
|
|
21
|
+
# Windows (PowerShell)
|
|
22
|
+
$env:ANTHROPIC_API_KEY="sk-ant-..."
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
axon -p "list the TypeScript files in src and explain what engine.ts does"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Axon will stream the model's reasoning, run read-only tools as needed (`read_file`, `list_dir`, `glob`, `grep`), and print the result.
|
|
32
|
+
|
|
33
|
+
### Options
|
|
34
|
+
|
|
35
|
+
- `-p, --print <prompt>` — run one prompt non-interactively and stream the result.
|
|
36
|
+
- `--version` — print the version.
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
Optional config file at `~/.axon/config.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"provider": "anthropic",
|
|
45
|
+
"model": "claude-opus-4-8",
|
|
46
|
+
"providers": {
|
|
47
|
+
"anthropic": { "apiKey": "env:ANTHROPIC_API_KEY" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
API keys are read from environment variables and never written to disk.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/version.ts
|
|
7
|
+
var VERSION = "0.0.1";
|
|
8
|
+
|
|
9
|
+
// src/config/config.ts
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
var DEFAULTS = {
|
|
14
|
+
provider: "anthropic",
|
|
15
|
+
model: "claude-opus-4-8",
|
|
16
|
+
providers: {
|
|
17
|
+
anthropic: { apiKey: "env:ANTHROPIC_API_KEY" }
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function resolveEnvRefs(cfg) {
|
|
21
|
+
const providers = {};
|
|
22
|
+
for (const [name, p] of Object.entries(cfg.providers)) {
|
|
23
|
+
const apiKey = p.apiKey?.startsWith("env:") ? process.env[p.apiKey.slice(4)] : p.apiKey;
|
|
24
|
+
providers[name] = { ...p, apiKey };
|
|
25
|
+
}
|
|
26
|
+
return { ...cfg, providers };
|
|
27
|
+
}
|
|
28
|
+
function loadConfig() {
|
|
29
|
+
const path = join(homedir(), ".axon", "config.json");
|
|
30
|
+
let fileCfg = {};
|
|
31
|
+
try {
|
|
32
|
+
fileCfg = JSON.parse(readFileSync(path, "utf8"));
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
const merged = {
|
|
36
|
+
provider: fileCfg.provider ?? DEFAULTS.provider,
|
|
37
|
+
model: fileCfg.model ?? DEFAULTS.model,
|
|
38
|
+
providers: { ...DEFAULTS.providers, ...fileCfg.providers ?? {} }
|
|
39
|
+
};
|
|
40
|
+
return resolveEnvRefs(merged);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/providers/registry.ts
|
|
44
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
45
|
+
|
|
46
|
+
// src/providers/anthropic.ts
|
|
47
|
+
function mapStop(reason) {
|
|
48
|
+
if (reason === "tool_use") return "tool_use";
|
|
49
|
+
if (reason === "max_tokens") return "max_tokens";
|
|
50
|
+
return "end";
|
|
51
|
+
}
|
|
52
|
+
function blockToAnthropic(b) {
|
|
53
|
+
if (b.type === "text") return { type: "text", text: b.text };
|
|
54
|
+
return { type: "tool_use", id: b.id, name: b.name, input: b.args };
|
|
55
|
+
}
|
|
56
|
+
function toAnthropicMessages(messages) {
|
|
57
|
+
const out = [];
|
|
58
|
+
let i = 0;
|
|
59
|
+
while (i < messages.length) {
|
|
60
|
+
const m = messages[i];
|
|
61
|
+
if (m.role === "tool") {
|
|
62
|
+
const results = [];
|
|
63
|
+
while (i < messages.length && messages[i].role === "tool") {
|
|
64
|
+
const t = messages[i];
|
|
65
|
+
results.push({ type: "tool_result", tool_use_id: t.toolCallId, content: t.content });
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
out.push({ role: "user", content: results });
|
|
69
|
+
} else {
|
|
70
|
+
out.push({ role: m.role, content: m.content.map(blockToAnthropic) });
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function toAnthropicTool(t) {
|
|
77
|
+
return { name: t.name, description: t.description, input_schema: t.parameters };
|
|
78
|
+
}
|
|
79
|
+
var AnthropicProvider = class {
|
|
80
|
+
constructor(deps) {
|
|
81
|
+
this.deps = deps;
|
|
82
|
+
}
|
|
83
|
+
async *stream(req) {
|
|
84
|
+
const stream = await this.deps.client.messages.create({
|
|
85
|
+
model: this.deps.model,
|
|
86
|
+
max_tokens: 4096,
|
|
87
|
+
stream: true,
|
|
88
|
+
system: req.system,
|
|
89
|
+
messages: toAnthropicMessages(req.messages),
|
|
90
|
+
tools: req.tools.map(toAnthropicTool)
|
|
91
|
+
});
|
|
92
|
+
const toolBlocks = /* @__PURE__ */ new Map();
|
|
93
|
+
let stopReason = "end";
|
|
94
|
+
for await (const ev of stream) {
|
|
95
|
+
switch (ev.type) {
|
|
96
|
+
case "content_block_start":
|
|
97
|
+
if (ev.content_block?.type === "tool_use") {
|
|
98
|
+
toolBlocks.set(ev.index, { id: ev.content_block.id, name: ev.content_block.name, json: "" });
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case "content_block_delta":
|
|
102
|
+
if (ev.delta?.type === "text_delta") {
|
|
103
|
+
yield { type: "text_delta", text: ev.delta.text };
|
|
104
|
+
} else if (ev.delta?.type === "input_json_delta") {
|
|
105
|
+
const t = toolBlocks.get(ev.index);
|
|
106
|
+
if (t) t.json += ev.delta.partial_json;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case "content_block_stop": {
|
|
110
|
+
const t = toolBlocks.get(ev.index);
|
|
111
|
+
if (t) {
|
|
112
|
+
let args;
|
|
113
|
+
try {
|
|
114
|
+
args = t.json.trim() ? JSON.parse(t.json) : {};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
throw new Error(`Tool "${t.name}" (id=${t.id}): invalid tool-call JSON: ${JSON.stringify(t.json)}`, { cause: err });
|
|
117
|
+
}
|
|
118
|
+
yield { type: "tool_call", id: t.id, name: t.name, args };
|
|
119
|
+
toolBlocks.delete(ev.index);
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "message_delta":
|
|
124
|
+
stopReason = mapStop(ev.delta?.stop_reason);
|
|
125
|
+
break;
|
|
126
|
+
case "message_stop":
|
|
127
|
+
yield { type: "done", stopReason };
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/providers/registry.ts
|
|
135
|
+
function createProvider(cfg) {
|
|
136
|
+
if (cfg.provider === "anthropic") {
|
|
137
|
+
const apiKey = cfg.providers.anthropic?.apiKey;
|
|
138
|
+
if (!apiKey || !apiKey.trim()) throw new Error("Missing Anthropic API key. Set ANTHROPIC_API_KEY.");
|
|
139
|
+
const client = new Anthropic({ apiKey });
|
|
140
|
+
return new AnthropicProvider({ client, model: cfg.model });
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`Unsupported provider: ${cfg.provider}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/tools/fs.ts
|
|
146
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
147
|
+
import { join as join2, resolve as resolve2 } from "node:path";
|
|
148
|
+
import fg from "fast-glob";
|
|
149
|
+
|
|
150
|
+
// src/tools/paths.ts
|
|
151
|
+
import { resolve, relative, isAbsolute, sep } from "node:path";
|
|
152
|
+
function resolveInside(cwd, p) {
|
|
153
|
+
const root = resolve(cwd);
|
|
154
|
+
const full = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
155
|
+
const rel = relative(root, full);
|
|
156
|
+
if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) {
|
|
157
|
+
throw new Error(`path escapes project root: ${p}`);
|
|
158
|
+
}
|
|
159
|
+
return full;
|
|
160
|
+
}
|
|
161
|
+
function assertSafeGlob(pattern) {
|
|
162
|
+
const norm = pattern.replace(/\\/g, "/");
|
|
163
|
+
if (isAbsolute(pattern) || norm === ".." || norm.startsWith("../") || norm.includes("/../")) {
|
|
164
|
+
throw new Error(`glob pattern escapes project root: ${pattern}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/tools/fs.ts
|
|
169
|
+
function fail(err) {
|
|
170
|
+
return { ok: false, output: err instanceof Error ? err.message : String(err) };
|
|
171
|
+
}
|
|
172
|
+
var readFileTool = {
|
|
173
|
+
name: "read_file",
|
|
174
|
+
dangerous: false,
|
|
175
|
+
schema: {
|
|
176
|
+
name: "read_file",
|
|
177
|
+
description: "Read a file, returns content with 1-based line numbers.",
|
|
178
|
+
parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
|
|
179
|
+
},
|
|
180
|
+
async run(args, ctx) {
|
|
181
|
+
try {
|
|
182
|
+
const { path } = args;
|
|
183
|
+
const full = resolveInside(ctx.cwd, path);
|
|
184
|
+
const text = await readFile(full, "utf8");
|
|
185
|
+
const numbered = text.replace(/\n$/, "").split("\n").map((line, i) => `${i + 1} ${line}`).join("\n");
|
|
186
|
+
return { ok: true, output: numbered };
|
|
187
|
+
} catch (err) {
|
|
188
|
+
return fail(err);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
var listDirTool = {
|
|
193
|
+
name: "list_dir",
|
|
194
|
+
dangerous: false,
|
|
195
|
+
schema: {
|
|
196
|
+
name: "list_dir",
|
|
197
|
+
description: "List the entries of a directory. Directories get a trailing slash.",
|
|
198
|
+
parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
|
|
199
|
+
},
|
|
200
|
+
async run(args, ctx) {
|
|
201
|
+
try {
|
|
202
|
+
const { path } = args;
|
|
203
|
+
const full = resolveInside(ctx.cwd, path);
|
|
204
|
+
const entries = await readdir(full, { withFileTypes: true });
|
|
205
|
+
const out = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
|
|
206
|
+
return { ok: true, output: out };
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return fail(err);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var globTool = {
|
|
213
|
+
name: "glob",
|
|
214
|
+
dangerous: false,
|
|
215
|
+
schema: {
|
|
216
|
+
name: "glob",
|
|
217
|
+
description: "Find files matching a glob pattern (relative paths from project root).",
|
|
218
|
+
parameters: { type: "object", properties: { pattern: { type: "string" } }, required: ["pattern"] }
|
|
219
|
+
},
|
|
220
|
+
async run(args, ctx) {
|
|
221
|
+
try {
|
|
222
|
+
const { pattern } = args;
|
|
223
|
+
assertSafeGlob(pattern);
|
|
224
|
+
const matches = await fg(pattern, { cwd: resolve2(ctx.cwd), onlyFiles: true, dot: false });
|
|
225
|
+
return { ok: true, output: matches.join("\n") };
|
|
226
|
+
} catch (err) {
|
|
227
|
+
return fail(err);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var grepTool = {
|
|
232
|
+
name: "grep",
|
|
233
|
+
dangerous: false,
|
|
234
|
+
schema: {
|
|
235
|
+
name: "grep",
|
|
236
|
+
description: "Search file contents by regex. Returns file:line:text for each match.",
|
|
237
|
+
parameters: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: { pattern: { type: "string" }, glob: { type: "string" } },
|
|
240
|
+
required: ["pattern"]
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
async run(args, ctx) {
|
|
244
|
+
try {
|
|
245
|
+
const { pattern, glob = "**/*" } = args;
|
|
246
|
+
assertSafeGlob(glob);
|
|
247
|
+
const root = resolve2(ctx.cwd);
|
|
248
|
+
const re = new RegExp(pattern);
|
|
249
|
+
const files = await fg(glob, { cwd: root, onlyFiles: true, dot: false });
|
|
250
|
+
const hits = [];
|
|
251
|
+
for (const rel of files) {
|
|
252
|
+
const text = await readFile(join2(root, rel), "utf8").catch(() => "");
|
|
253
|
+
text.split("\n").forEach((line, i) => {
|
|
254
|
+
if (re.test(line)) hits.push(`${rel}:${i + 1}:${line}`);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return { ok: true, output: hits.join("\n") };
|
|
258
|
+
} catch (err) {
|
|
259
|
+
return fail(err);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// src/tools/registry.ts
|
|
265
|
+
function buildReadOnlyTools() {
|
|
266
|
+
const tools = [readFileTool, listDirTool, globTool, grepTool];
|
|
267
|
+
return new Map(tools.map((t) => [t.name, t]));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/core/conversation.ts
|
|
271
|
+
var Conversation = class {
|
|
272
|
+
msgs = [];
|
|
273
|
+
get messages() {
|
|
274
|
+
return [...this.msgs];
|
|
275
|
+
}
|
|
276
|
+
pushUser(text) {
|
|
277
|
+
this.msgs.push({ role: "user", content: [{ type: "text", text }] });
|
|
278
|
+
}
|
|
279
|
+
pushAssistant(content) {
|
|
280
|
+
this.msgs.push({ role: "assistant", content });
|
|
281
|
+
}
|
|
282
|
+
pushToolResult(toolCallId, content) {
|
|
283
|
+
this.msgs.push({ role: "tool", toolCallId, content });
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// src/core/engine.ts
|
|
288
|
+
var Engine = class {
|
|
289
|
+
constructor(deps) {
|
|
290
|
+
this.deps = deps;
|
|
291
|
+
}
|
|
292
|
+
convo = new Conversation();
|
|
293
|
+
listeners = [];
|
|
294
|
+
on(fn) {
|
|
295
|
+
this.listeners.push(fn);
|
|
296
|
+
}
|
|
297
|
+
/** Read-only snapshot of the conversation transcript. */
|
|
298
|
+
get history() {
|
|
299
|
+
return this.convo.messages;
|
|
300
|
+
}
|
|
301
|
+
emit(e) {
|
|
302
|
+
for (const l of this.listeners) l(e);
|
|
303
|
+
}
|
|
304
|
+
// NOTE: the `error` EngineEvent is reserved for the Plan 3 interactive/TUI path;
|
|
305
|
+
// stream failures currently propagate to the CLI's top-level handler in the non-interactive slice.
|
|
306
|
+
async submit(text) {
|
|
307
|
+
this.convo.pushUser(text);
|
|
308
|
+
const maxSteps = this.deps.maxSteps ?? 50;
|
|
309
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
310
|
+
const blocks = [];
|
|
311
|
+
const calls = [];
|
|
312
|
+
let textBlock = null;
|
|
313
|
+
let stopReason = "end";
|
|
314
|
+
for await (const ev of this.deps.provider.stream({
|
|
315
|
+
system: this.deps.system,
|
|
316
|
+
messages: this.convo.messages,
|
|
317
|
+
tools: [...this.deps.tools.values()].map((t) => t.schema)
|
|
318
|
+
})) {
|
|
319
|
+
if (ev.type === "text_delta") {
|
|
320
|
+
if (!textBlock) {
|
|
321
|
+
textBlock = { type: "text", text: "" };
|
|
322
|
+
blocks.push(textBlock);
|
|
323
|
+
}
|
|
324
|
+
textBlock.text += ev.text;
|
|
325
|
+
this.emit({ type: "text_delta", text: ev.text });
|
|
326
|
+
} else if (ev.type === "tool_call") {
|
|
327
|
+
textBlock = null;
|
|
328
|
+
blocks.push({ type: "tool_call", id: ev.id, name: ev.name, args: ev.args });
|
|
329
|
+
calls.push({ id: ev.id, name: ev.name, args: ev.args });
|
|
330
|
+
} else if (ev.type === "done") {
|
|
331
|
+
stopReason = ev.stopReason;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
this.convo.pushAssistant(blocks);
|
|
335
|
+
if (calls.length === 0) {
|
|
336
|
+
this.emit({ type: "turn_done", stopReason });
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
for (const call of calls) {
|
|
340
|
+
this.emit({ type: "tool_start", id: call.id, name: call.name, args: call.args });
|
|
341
|
+
const tool = this.deps.tools.get(call.name);
|
|
342
|
+
if (!tool) {
|
|
343
|
+
this.emit({ type: "tool_end", id: call.id, ok: false, output: `unknown tool: ${call.name}` });
|
|
344
|
+
this.convo.pushToolResult(call.id, `unknown tool: ${call.name}`);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
let result;
|
|
348
|
+
try {
|
|
349
|
+
result = await tool.run(call.args, { cwd: this.deps.cwd });
|
|
350
|
+
} catch (err) {
|
|
351
|
+
result = { ok: false, output: String(err) };
|
|
352
|
+
}
|
|
353
|
+
this.emit({ type: "tool_end", id: call.id, ok: result.ok, output: result.output });
|
|
354
|
+
this.convo.pushToolResult(call.id, result.output);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
this.emit({ type: "turn_done", stopReason: "max_steps" });
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/ui/printRunner.ts
|
|
362
|
+
function printRunner(engine, write) {
|
|
363
|
+
engine.on((e) => {
|
|
364
|
+
switch (e.type) {
|
|
365
|
+
case "text_delta":
|
|
366
|
+
write(e.text);
|
|
367
|
+
break;
|
|
368
|
+
case "tool_start":
|
|
369
|
+
write(`
|
|
370
|
+
\u23F3 ${e.name}(${JSON.stringify(e.args)})
|
|
371
|
+
`);
|
|
372
|
+
break;
|
|
373
|
+
case "tool_end":
|
|
374
|
+
write(`${e.ok ? "\u2705" : "\u274C"} ${truncate(e.output)}
|
|
375
|
+
`);
|
|
376
|
+
break;
|
|
377
|
+
case "permission_request":
|
|
378
|
+
write(`
|
|
379
|
+
\u{1F512} ${e.action}: ${e.detail}
|
|
380
|
+
`);
|
|
381
|
+
break;
|
|
382
|
+
case "turn_done":
|
|
383
|
+
write(`
|
|
384
|
+
[done: ${e.stopReason}]
|
|
385
|
+
`);
|
|
386
|
+
break;
|
|
387
|
+
case "error":
|
|
388
|
+
write(`
|
|
389
|
+
\u{1F4A5} ${e.message}
|
|
390
|
+
`);
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function truncate(s, max = 500) {
|
|
396
|
+
return s.length > max ? `${s.slice(0, max)}\u2026` : s;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/cli.ts
|
|
400
|
+
var SYSTEM = `You are Axon, an agentic coding assistant. Use the provided tools to inspect the project and answer precisely. When done, stop calling tools.`;
|
|
401
|
+
var program = new Command();
|
|
402
|
+
program.name("axon").version(VERSION).option("-p, --print <prompt>", "run one prompt non-interactively and stream the result");
|
|
403
|
+
program.parse();
|
|
404
|
+
var opts = program.opts();
|
|
405
|
+
async function main() {
|
|
406
|
+
if (!opts.print) {
|
|
407
|
+
process.stdout.write('Interactive TUI not built yet \u2014 use: axon -p "your prompt"\n');
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const cfg = loadConfig();
|
|
411
|
+
const provider = createProvider(cfg);
|
|
412
|
+
const tools = buildReadOnlyTools();
|
|
413
|
+
const engine = new Engine({ provider, tools, system: SYSTEM, cwd: process.cwd() });
|
|
414
|
+
printRunner(engine, (s) => process.stdout.write(s));
|
|
415
|
+
await engine.submit(opts.print);
|
|
416
|
+
}
|
|
417
|
+
main().catch((err) => {
|
|
418
|
+
process.stderr.write(`\u{1F4A5} ${err instanceof Error ? err.message : String(err)}
|
|
419
|
+
`);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wanghuimvp/axon",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Axon — an agentic coding CLI. Streams from Anthropic, runs a multi-step tool loop over your codebase.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": { "axon": "dist/cli.js" },
|
|
7
|
+
"files": ["dist"],
|
|
8
|
+
"engines": { "node": ">=20" },
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["cli", "agent", "agentic", "coding", "anthropic", "claude", "ai", "assistant", "axon"],
|
|
11
|
+
"repository": { "type": "git", "url": "git+https://github.com/Wade-DevCode/axon.git" },
|
|
12
|
+
"homepage": "https://github.com/Wade-DevCode/axon#readme",
|
|
13
|
+
"bugs": { "url": "https://github.com/Wade-DevCode/axon/issues" },
|
|
14
|
+
"publishConfig": { "access": "public" },
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "tsx src/cli.ts",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --packages=external",
|
|
19
|
+
"prepublishOnly": "npm test && npm run build"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@anthropic-ai/sdk": "^0.40.0",
|
|
23
|
+
"commander": "^12.0.0",
|
|
24
|
+
"fast-glob": "^3.3.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"esbuild": "^0.23.0",
|
|
29
|
+
"tsx": "^4.0.0",
|
|
30
|
+
"typescript": "^5.5.0",
|
|
31
|
+
"vitest": "^2.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|