@wanghuimvp/axon 0.2.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +25 -2
  2. package/dist/cli.js +402 -4
  3. package/package.json +8 -3
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Axon
2
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.
3
+ A multi-provider agentic coding CLI. Axon runs a multi-step tool loop over your codebase — it reads, searches, edits, and runs commands to carry out a request — with an interactive chat or a one-shot non-interactive mode.
4
4
 
5
- > Foundation release (`0.0.x`). Non-interactive `axon -p` mode with read-only tools (always available) and write/edit/shell tools (enabled with `--yolo`). Interactive TUI is on the roadmap.
5
+ > `0.x` — interactive TUI (`axon`) and non-interactive (`axon -p`), multi-provider (Anthropic / OpenAI + OpenAI-compatible / Gemini), read + write/edit/shell tools.
6
6
 
7
7
  ## Install
8
8
 
@@ -10,6 +10,29 @@ An agentic coding CLI. Axon streams from Anthropic and runs a multi-step tool lo
10
10
  npm install -g @wanghuimvp/axon
11
11
  ```
12
12
 
13
+ ## Interactive mode
14
+
15
+ Run `axon` with no arguments to open the interactive chat:
16
+
17
+ ```bash
18
+ axon
19
+ ```
20
+
21
+ On first run without an API key, `axon` opens a short setup screen — paste a key for the active provider and it is saved to `~/.axon/config.json` (with restricted file permissions) so later runs start straight in the chat. You can also set the provider's environment variable instead (e.g. `ANTHROPIC_API_KEY`), which always takes precedence.
22
+
23
+ Axon reads your project on startup — `package.json` and the first of `AGENTS.md` / `CLAUDE.md` / `README.md` — and gives the model that context.
24
+
25
+ Type a request and press Enter. Axon streams its reasoning and tool calls. When it wants to write a file or run a command, you get an inline prompt:
26
+
27
+ ```
28
+ 🔒 write_file({"path":"NOTES.md", …})
29
+ [a] allow once / [A] always this session / [d] deny
30
+ ```
31
+
32
+ Press `a` to allow once, `A` to allow that tool for the rest of the session, or `d` to deny (the model is told and adapts). `Ctrl+C` quits. Pass `--yolo` to skip all prompts.
33
+
34
+ For one-shot, non-interactive use, see [Usage](#usage) (`axon -p`).
35
+
13
36
  ## Providers
14
37
 
15
38
  Axon speaks to three backends. The OpenAI-compatible provider also drives any
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ function loadConfig() {
54
54
  }
55
55
 
56
56
  // src/config/configFile.ts
57
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync } from "node:fs";
57
+ import { readFileSync as readFileSync2, writeFileSync, mkdirSync, chmodSync } from "node:fs";
58
58
  import { homedir as homedir2 } from "node:os";
59
59
  import { join as join2, dirname } from "node:path";
60
60
  function configPath() {
@@ -86,6 +86,18 @@ function setConfigValue(key, value) {
86
86
  mkdirSync(dirname(path), { recursive: true });
87
87
  writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n");
88
88
  }
89
+ function setApiKey(provider, key) {
90
+ const cfg = readConfigFile();
91
+ const providers = cfg.providers ??= {};
92
+ (providers[provider] ??= {}).apiKey = key;
93
+ const path = configPath();
94
+ mkdirSync(dirname(path), { recursive: true });
95
+ writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
96
+ try {
97
+ chmodSync(path, 384);
98
+ } catch {
99
+ }
100
+ }
89
101
 
90
102
  // src/providers/registry.ts
91
103
  import Anthropic from "@anthropic-ai/sdk";
@@ -773,14 +785,395 @@ function truncate(s, max = 500) {
773
785
  return s.length > max ? `${s.slice(0, max)}\u2026` : s;
774
786
  }
775
787
 
788
+ // src/ui/runTui.tsx
789
+ import { useState as useState3, useRef } from "react";
790
+ import { Box as Box6, Text as Text6, render } from "ink";
791
+
792
+ // src/core/projectContext.ts
793
+ import { readFileSync as readFileSync3 } from "node:fs";
794
+ import { join as join4 } from "node:path";
795
+ function truncate2(s, max) {
796
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
797
+ }
798
+ function loadProjectContext(cwd) {
799
+ const parts = [];
800
+ try {
801
+ const pkg = JSON.parse(readFileSync3(join4(cwd, "package.json"), "utf8"));
802
+ const bits = [];
803
+ if (pkg.name) bits.push(`name: ${pkg.name}`);
804
+ if (pkg.description) bits.push(`description: ${pkg.description}`);
805
+ if (pkg.scripts && typeof pkg.scripts === "object") bits.push(`scripts: ${Object.keys(pkg.scripts).join(", ")}`);
806
+ if (bits.length) parts.push(`package.json \u2014 ${bits.join("; ")}`);
807
+ } catch {
808
+ }
809
+ for (const name of ["AGENTS.md", "CLAUDE.md", "README.md"]) {
810
+ try {
811
+ const text = readFileSync3(join4(cwd, name), "utf8").trim();
812
+ if (text) {
813
+ parts.push(`${name}:
814
+ ${truncate2(text, 2e3)}`);
815
+ break;
816
+ }
817
+ } catch {
818
+ }
819
+ }
820
+ return parts.join("\n\n");
821
+ }
822
+
823
+ // src/ui/permissionController.ts
824
+ function createPermissionController() {
825
+ const sessionAllow = /* @__PURE__ */ new Set();
826
+ const subscribers = /* @__PURE__ */ new Set();
827
+ const queue = [];
828
+ let active = null;
829
+ let currentPending = null;
830
+ const notify = (p) => {
831
+ currentPending = p;
832
+ for (const fn of subscribers) fn(p);
833
+ };
834
+ const pump = () => {
835
+ if (active || queue.length === 0) return;
836
+ active = queue.shift();
837
+ notify({ req: active.req });
838
+ };
839
+ const gate = (req) => {
840
+ if (sessionAllow.has(req.name)) {
841
+ return Promise.resolve({ allow: true, reason: "allowed (remembered this session)" });
842
+ }
843
+ return new Promise((res) => {
844
+ queue.push({ req, settle: res });
845
+ pump();
846
+ });
847
+ };
848
+ const resolve3 = (decision) => {
849
+ if (!active) return;
850
+ const { req, settle } = active;
851
+ active = null;
852
+ if (decision === "always") sessionAllow.add(req.name);
853
+ settle({
854
+ allow: decision !== "deny",
855
+ reason: decision === "deny" ? `permission denied by user: ${req.name}` : `allowed (${decision})`
856
+ });
857
+ pump();
858
+ if (!active) notify(null);
859
+ };
860
+ const subscribe = (fn) => {
861
+ subscribers.add(fn);
862
+ return () => {
863
+ subscribers.delete(fn);
864
+ };
865
+ };
866
+ const getPending = () => currentPending;
867
+ return { gate, subscribe, getPending, resolve: resolve3 };
868
+ }
869
+
870
+ // src/config/credentials.ts
871
+ function hasUsableKey(cfg) {
872
+ const k = cfg.providers[cfg.provider]?.apiKey;
873
+ return typeof k === "string" && k.trim().length > 0;
874
+ }
875
+ var INFO = {
876
+ anthropic: { envVar: "ANTHROPIC_API_KEY", url: "https://console.anthropic.com/settings/keys" },
877
+ openai: { envVar: "OPENAI_API_KEY", url: "https://platform.openai.com/api-keys" },
878
+ gemini: { envVar: "GEMINI_API_KEY", url: "https://aistudio.google.com/apikey" }
879
+ };
880
+ function keyProviderInfo(provider) {
881
+ return INFO[provider] ?? { envVar: `${provider.toUpperCase()}_API_KEY`, url: "" };
882
+ }
883
+
884
+ // src/ui/app.tsx
885
+ import { useEffect, useState, useSyncExternalStore, useCallback } from "react";
886
+ import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
887
+ import TextInput from "ink-text-input";
888
+
889
+ // src/ui/components/MessageView.tsx
890
+ import { Box, Text } from "ink";
891
+ import { jsx, jsxs } from "react/jsx-runtime";
892
+ function toolIcon(status) {
893
+ if (status === "running") return "\u23F3";
894
+ return status === "ok" ? "\u2705" : "\u274C";
895
+ }
896
+ function truncate3(s, max = 300) {
897
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
898
+ }
899
+ function MessageView({ items }) {
900
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((it, i) => {
901
+ if (it.kind === "user") {
902
+ return /* @__PURE__ */ jsxs(Text, { children: [
903
+ /* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
904
+ "You",
905
+ " "
906
+ ] }),
907
+ it.text
908
+ ] }, i);
909
+ }
910
+ if (it.kind === "assistant") {
911
+ return /* @__PURE__ */ jsxs(Text, { children: [
912
+ /* @__PURE__ */ jsxs(Text, { color: "green", bold: true, children: [
913
+ "Axon",
914
+ " "
915
+ ] }),
916
+ it.text
917
+ ] }, i);
918
+ }
919
+ if (it.kind === "error") {
920
+ return /* @__PURE__ */ jsxs(Text, { color: "red", children: [
921
+ "\u{1F4A5} ",
922
+ it.text
923
+ ] }, i);
924
+ }
925
+ if (it.kind === "tool") {
926
+ return /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
927
+ toolIcon(it.status),
928
+ " ",
929
+ it.name,
930
+ "(",
931
+ truncate3(JSON.stringify(it.args ?? {}), 60),
932
+ ")",
933
+ it.output ? ` \u2014 ${truncate3(it.output)}` : ""
934
+ ] }, i);
935
+ }
936
+ return null;
937
+ }) });
938
+ }
939
+
940
+ // src/ui/components/PermissionPrompt.tsx
941
+ import { Box as Box2, Text as Text2, useInput } from "ink";
942
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
943
+ function summarize(args) {
944
+ const s = JSON.stringify(args ?? {});
945
+ return s.length > 80 ? `${s.slice(0, 80)}\u2026` : s;
946
+ }
947
+ function decisionForKey(input) {
948
+ if (input === "a") return "once";
949
+ if (input === "A") return "always";
950
+ if (input === "d") return "deny";
951
+ return null;
952
+ }
953
+ function PermissionPrompt({ req, onDecide }) {
954
+ useInput((input) => {
955
+ const d = decisionForKey(input);
956
+ if (d) onDecide(d);
957
+ });
958
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
959
+ /* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
960
+ "\u{1F512} ",
961
+ req.name,
962
+ "(",
963
+ summarize(req.args),
964
+ ")"
965
+ ] }),
966
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: "[a] allow once / [A] always this session / [d] deny" })
967
+ ] });
968
+ }
969
+
970
+ // src/ui/components/StatusBar.tsx
971
+ import { Box as Box3, Text as Text3 } from "ink";
972
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
973
+ function StatusBar({ provider, model, running, yolo }) {
974
+ return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
975
+ "[axon: ",
976
+ provider,
977
+ "/",
978
+ model,
979
+ yolo ? " \xB7 yolo" : "",
980
+ " \xB7 ",
981
+ running ? "working\u2026" : "ready",
982
+ " \xB7 ^C quit]"
983
+ ] }) });
984
+ }
985
+
986
+ // src/ui/app.tsx
987
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
988
+ function reduceEvent(items, e) {
989
+ switch (e.type) {
990
+ case "text_delta": {
991
+ const last = items[items.length - 1];
992
+ if (last && last.kind === "assistant") {
993
+ return [...items.slice(0, -1), { kind: "assistant", text: last.text + e.text }];
994
+ }
995
+ return [...items, { kind: "assistant", text: e.text }];
996
+ }
997
+ case "tool_start":
998
+ return [...items, { kind: "tool", id: e.id, name: e.name, args: e.args, status: "running" }];
999
+ case "tool_end":
1000
+ return items.map(
1001
+ (it) => it.kind === "tool" && it.id === e.id ? { ...it, status: e.ok ? "ok" : "fail", output: e.output } : it
1002
+ );
1003
+ default:
1004
+ return items;
1005
+ }
1006
+ }
1007
+ function usePendingPermission(controller) {
1008
+ const subscribe = useCallback((onChange) => controller.subscribe(() => onChange()), [controller]);
1009
+ const getSnapshot = useCallback(() => controller.getPending(), [controller]);
1010
+ const pending = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1011
+ return pending ? pending.req : null;
1012
+ }
1013
+ function App({ engine, controller, provider, model, yolo }) {
1014
+ const [items, setItems] = useState([]);
1015
+ const [input, setInput] = useState("");
1016
+ const [running, setRunning] = useState(false);
1017
+ const pending = usePendingPermission(controller);
1018
+ useEffect(() => {
1019
+ engine.on((e) => setItems((prev) => reduceEvent(prev, e)));
1020
+ }, [engine]);
1021
+ useInput2(
1022
+ (_input, key) => {
1023
+ if (key.escape && !running) setInput("");
1024
+ },
1025
+ { isActive: !pending }
1026
+ );
1027
+ const handleSubmit = (text) => {
1028
+ if (!text.trim() || running) return;
1029
+ setItems((prev) => [...prev, { kind: "user", text }]);
1030
+ setInput("");
1031
+ setRunning(true);
1032
+ engine.submit(text).catch((err) => {
1033
+ const msg = err instanceof Error ? err.message : String(err);
1034
+ setItems((prev) => [...prev, { kind: "error", text: msg }]);
1035
+ }).finally(() => setRunning(false));
1036
+ };
1037
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1038
+ /* @__PURE__ */ jsx4(MessageView, { items }),
1039
+ pending ? /* @__PURE__ */ jsx4(PermissionPrompt, { req: pending, onDecide: (d) => controller.resolve(d) }) : running ? /* @__PURE__ */ jsx4(Text4, { color: "gray", children: "\u2026 working (Ctrl+C to quit)" }) : /* @__PURE__ */ jsxs4(Box4, { children: [
1040
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u203A " }),
1041
+ /* @__PURE__ */ jsx4(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit })
1042
+ ] }),
1043
+ /* @__PURE__ */ jsx4(StatusBar, { provider, model, running, yolo })
1044
+ ] });
1045
+ }
1046
+
1047
+ // src/ui/Setup.tsx
1048
+ import { useState as useState2 } from "react";
1049
+ import { Box as Box5, Text as Text5 } from "ink";
1050
+ import TextInput2 from "ink-text-input";
1051
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1052
+ function trimmedKey(v) {
1053
+ const t = v.trim();
1054
+ return t ? t : null;
1055
+ }
1056
+ function Setup({
1057
+ provider,
1058
+ info,
1059
+ onSubmit
1060
+ }) {
1061
+ const [value, setValue] = useState2("");
1062
+ const submit = (v) => {
1063
+ const k = trimmedKey(v);
1064
+ if (k) onSubmit(k);
1065
+ };
1066
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1067
+ /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
1068
+ 'No API key found for "',
1069
+ provider,
1070
+ '".'
1071
+ ] }),
1072
+ /* @__PURE__ */ jsxs5(Text5, { children: [
1073
+ "Set ",
1074
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: info.envVar }),
1075
+ " in your environment, or paste a key below to save it to ~/.axon/config.json."
1076
+ ] }),
1077
+ info.url ? /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1078
+ "Get a key at: ",
1079
+ info.url
1080
+ ] }) : null,
1081
+ /* @__PURE__ */ jsxs5(Box5, { children: [
1082
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "key \u203A " }),
1083
+ /* @__PURE__ */ jsx5(TextInput2, { value, onChange: setValue, onSubmit: submit, mask: "*" })
1084
+ ] })
1085
+ ] });
1086
+ }
1087
+
1088
+ // src/ui/runTui.tsx
1089
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1090
+ var TUI_SYSTEM = `You are Axon, an interactive agentic coding assistant. Use the tools to inspect and modify the project: read_file, list_dir, glob, grep (read-only) and write_file, edit_file, shell (these change the workspace; the user is prompted to approve each). Prefer edit_file for surgical changes. Explain briefly what you are doing.`;
1091
+ function Root({ deps }) {
1092
+ const { cfg, controller, yolo, welcome, buildEngine, persistKey } = deps;
1093
+ const [ready, setReady] = useState3(hasUsableKey(cfg));
1094
+ const engineRef = useRef(null);
1095
+ if (ready && !engineRef.current) engineRef.current = buildEngine();
1096
+ if (!ready || !engineRef.current) {
1097
+ const info = keyProviderInfo(cfg.provider);
1098
+ return /* @__PURE__ */ jsx6(
1099
+ Setup,
1100
+ {
1101
+ provider: cfg.provider,
1102
+ info,
1103
+ onSubmit: (key) => {
1104
+ persistKey(cfg.provider, key);
1105
+ cfg.providers[cfg.provider] = { ...cfg.providers[cfg.provider] ?? {}, apiKey: key };
1106
+ setReady(true);
1107
+ }
1108
+ }
1109
+ );
1110
+ }
1111
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1112
+ /* @__PURE__ */ jsx6(Text6, { color: "green", children: welcome }),
1113
+ /* @__PURE__ */ jsx6(
1114
+ App,
1115
+ {
1116
+ engine: engineRef.current,
1117
+ controller,
1118
+ provider: cfg.provider,
1119
+ model: resolveModel(cfg),
1120
+ yolo
1121
+ }
1122
+ )
1123
+ ] });
1124
+ }
1125
+ function runTui(opts) {
1126
+ const cfg = loadConfig();
1127
+ if (opts.provider) cfg.provider = opts.provider;
1128
+ if (opts.model) cfg.model = opts.model;
1129
+ const controller = createPermissionController();
1130
+ const deps = {
1131
+ cfg,
1132
+ controller,
1133
+ yolo: Boolean(opts.yolo),
1134
+ welcome: `Axon \xB7 ${cfg.provider}/${resolveModel(cfg)} \xB7 type a request, Ctrl+C to quit`,
1135
+ persistKey: (provider, key) => {
1136
+ try {
1137
+ setApiKey(provider, key);
1138
+ } catch {
1139
+ }
1140
+ },
1141
+ buildEngine: () => {
1142
+ const provider = createProvider(cfg);
1143
+ const tools = buildAllTools();
1144
+ const gate = opts.yolo ? allowAllGate : controller.gate;
1145
+ const context = loadProjectContext(process.cwd());
1146
+ const system = TUI_SYSTEM + (context ? `
1147
+
1148
+ Project context:
1149
+ ${context}` : "");
1150
+ return new Engine({ provider, tools, system, cwd: process.cwd(), gate });
1151
+ }
1152
+ };
1153
+ if (!process.stdin.isTTY) {
1154
+ process.stderr.write('axon: the interactive chat needs a terminal. For non-interactive use, run: axon -p "your prompt"\n');
1155
+ process.exit(1);
1156
+ }
1157
+ render(/* @__PURE__ */ jsx6(Root, { deps }));
1158
+ }
1159
+
776
1160
  // src/cli.ts
777
1161
  var READONLY_SYSTEM = `You are Axon, an agentic coding assistant. Use the read-only tools \u2014 read_file, list_dir, glob, grep \u2014 to inspect the project and answer precisely. When done, stop calling tools.`;
778
1162
  var YOLO_SYSTEM = `You are Axon, an agentic coding assistant. Use the provided tools to inspect AND modify the project: read_file, list_dir, glob, grep (read-only), and write_file, edit_file, shell (these change the workspace). Prefer edit_file for surgical changes. When done, stop calling tools.`;
1163
+ function redactKeys(cfg) {
1164
+ const providers = cfg.providers;
1165
+ if (!providers || typeof providers !== "object") return cfg;
1166
+ const masked = {};
1167
+ for (const [name, p] of Object.entries(providers)) {
1168
+ masked[name] = p && typeof p === "object" && "apiKey" in p && p.apiKey ? { ...p, apiKey: "***redacted***" } : p;
1169
+ }
1170
+ return { ...cfg, providers: masked };
1171
+ }
779
1172
  var program = new Command();
780
1173
  program.name("axon").version(VERSION).option("-p, --print <prompt>", "run one prompt non-interactively and stream the result").option("--provider <name>", "override the provider for this run (anthropic | openai | gemini)").option("--model <name>", "override the model for this run").option("--yolo", "allow write/edit/shell tools without prompting (non-interactive)");
781
1174
  program.command("config").argument("<action>", "get | set").argument("[key]", "config key (provider | model | <provider>.<baseUrl|model>)").argument("[value]", "value to set").action((action, key, value) => {
782
1175
  if (action === "get") {
783
- process.stdout.write(JSON.stringify(readConfigFile(), null, 2) + "\n");
1176
+ process.stdout.write(JSON.stringify(redactKeys(readConfigFile()), null, 2) + "\n");
784
1177
  return;
785
1178
  }
786
1179
  if (action === "set") {
@@ -803,7 +1196,7 @@ program.action(() => {
803
1196
  program.parse();
804
1197
  async function main(opts) {
805
1198
  if (!opts.print) {
806
- process.stdout.write('Interactive TUI not built yet \u2014 use: axon -p "your prompt"\n');
1199
+ runTui({ provider: opts.provider, model: opts.model, yolo: opts.yolo });
807
1200
  return;
808
1201
  }
809
1202
  const cfg = loadConfig();
@@ -812,7 +1205,12 @@ async function main(opts) {
812
1205
  const provider = createProvider(cfg);
813
1206
  const tools = opts.yolo ? buildAllTools() : buildReadOnlyTools();
814
1207
  const gate = opts.yolo ? allowAllGate : denyGate;
815
- const system = opts.yolo ? YOLO_SYSTEM : READONLY_SYSTEM;
1208
+ const baseSystem = opts.yolo ? YOLO_SYSTEM : READONLY_SYSTEM;
1209
+ const context = loadProjectContext(process.cwd());
1210
+ const system = baseSystem + (context ? `
1211
+
1212
+ Project context:
1213
+ ${context}` : "");
816
1214
  const engine = new Engine({ provider, tools, system, cwd: process.cwd(), gate });
817
1215
  printRunner(engine, (s) => process.stdout.write(s));
818
1216
  process.stderr.write(`[axon: ${cfg.provider} / ${resolveModel(cfg)}${opts.yolo ? " / yolo" : ""}]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wanghuimvp/axon",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Axon — a multi-provider agentic coding CLI (Anthropic, OpenAI + OpenAI-compatible endpoints, Gemini). Runs a multi-step tool loop over your codebase.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  "scripts": {
39
39
  "dev": "tsx src/cli.ts",
40
40
  "test": "vitest run",
41
- "build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --packages=external",
41
+ "build": "esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --packages=external --jsx=automatic",
42
42
  "prepublishOnly": "npm test && npm run build"
43
43
  },
44
44
  "dependencies": {
@@ -46,11 +46,16 @@
46
46
  "@google/genai": "^1.52.0",
47
47
  "commander": "^12.0.0",
48
48
  "fast-glob": "^3.3.0",
49
- "openai": "^4.104.0"
49
+ "ink": "^5.2.1",
50
+ "ink-text-input": "^6.0.0",
51
+ "openai": "^4.104.0",
52
+ "react": "^18.3.1"
50
53
  },
51
54
  "devDependencies": {
52
55
  "@types/node": "^20.0.0",
56
+ "@types/react": "^18.3.31",
53
57
  "esbuild": "^0.23.0",
58
+ "ink-testing-library": "^4.0.0",
54
59
  "tsx": "^4.0.0",
55
60
  "typescript": "^5.5.0",
56
61
  "vitest": "^2.0.0"