@rnbsolucoes/axion-code 0.1.3

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 ADDED
@@ -0,0 +1,207 @@
1
+ # Axion Code
2
+
3
+ Axion Code is the Go-first CLI agent harness for the Axion ecosystem. It brings
4
+ the Axion Agent runtime to the terminal with PREVC + A workflow state, Mythos
5
+ execution discipline, provider/model management, persistent sessions, shared
6
+ Axion Desktop configuration, native dotcontext support, and a Claude Code-style
7
+ TUI focused on agentic coding workflows.
8
+
9
+ This MVP follows the PREVC-P documentation package in `.context/`:
10
+
11
+ - Go core;
12
+ - PREVC + A / Mythos event rail;
13
+ - JSONL protocol for `run --json`;
14
+ - SQLite-first shared user session store;
15
+ - Claude Code-style terminal TUI powered by Bubble Tea;
16
+ - full-width chat layout with initial brand splash;
17
+ - provider catalog/profile foundation with mock and OpenRouter adapters;
18
+ - interactive slash palette and provider/model/permission menus;
19
+ - Axion Desktop fallback provider presets, including OpenAI-compatible and Anthropic-compatible endpoints;
20
+ - shared Axion Desktop/CLI provider, model and permission stores under `%USERPROFILE%\.axion`;
21
+ - Pi Agent assets in inspect/import-only mode;
22
+ - MCP probe/list/read-only posture.
23
+
24
+ ## MVP Commands
25
+
26
+ ```powershell
27
+ axion-code
28
+ axion-code ask "Explique este projeto"
29
+ axion-code ask --provider openrouter --model google/gemini-2.5-flash-lite "Responda em uma linha"
30
+ axion-code run --json
31
+ axion-code doctor --json
32
+ axion-code session list --json
33
+ axion-code provider list --json
34
+ axion-code provider profile init
35
+ axion-code provider profile list --json
36
+ axion-code provider profile add or-free --catalog openrouter --model openrouter/free --credential-ref env:OPENROUTER_API_KEY
37
+ axion-code provider profile set or-free
38
+ axion-code provider profile delete or-free
39
+ axion-code provider profile active
40
+ axion-code provider model list openrouter --json
41
+ axion-code provider test openrouter google/gemini-2.5-flash-lite
42
+ axion-code permission list --json
43
+ axion-code permission set full_permission
44
+ axion-code graphics doctor
45
+ axion-code graphics logo
46
+ axion-code graphics logo --mode sixel --width 180
47
+ axion-code mcp list --json
48
+ axion-code package inspect <path>
49
+ ```
50
+
51
+ ## Install
52
+
53
+ Official install, after the package is published to the public npm registry:
54
+
55
+ ```powershell
56
+ npm install -g @rnbsolucoes/axion-code
57
+ axion
58
+ ```
59
+
60
+ The GitHub source repository can remain private. The npm package is intentionally
61
+ published as a small installer/runtime package: it contains the Node wrapper,
62
+ platform binaries under `npm/releases`, and documentation, but it does not ship
63
+ the Go source tree (`cmd/`, `internal/`, `go.mod`, `go.sum`).
64
+
65
+ Private GitHub install is not the recommended distribution path. Commands like
66
+ `npm install -g rnbsolucoes/Axion-CLI` use Git access and may require SSH keys or
67
+ credentials on every machine. Use the scoped npm package instead.
68
+
69
+ Development install from the current local clone:
70
+
71
+ ```powershell
72
+ npm install -g .
73
+ axion
74
+ ```
75
+
76
+ If an older local `axion.ps1` shim already exists, replace it once:
77
+
78
+ ```powershell
79
+ npm install -g . --force
80
+ ```
81
+
82
+ The npm package exposes both `axion` and `axion-code`. It does not use a
83
+ `postinstall` hook. On first run, the wrapper copies the packaged binary for the
84
+ current platform into the local Axion Code bin directory and then executes it. In
85
+ a source clone only, if no packaged binary exists, the wrapper can build from Go
86
+ as a development fallback.
87
+
88
+ Requirements:
89
+
90
+ - Node.js 18+ for the npm wrapper.
91
+ - End users do not need Go when installing from npm.
92
+ - Development source builds need Go 1.26+ on `PATH`, `AXION_GO`, or the Codex
93
+ bundled Go toolchain at `%USERPROFILE%\.codex\toolchains\go1.26.4\go\bin\go.exe`.
94
+
95
+ For this development machine, if an older PowerShell shim still points directly
96
+ to `%LOCALAPPDATA%\AxionCode\bin\axion-code.exe`, rebuild the local binary:
97
+
98
+ ```powershell
99
+ go build -o "$env:LOCALAPPDATA\AxionCode\bin\axion-code.exe" ./cmd/axion-code
100
+ ```
101
+
102
+ ## Publish
103
+
104
+ Build the npm package binaries before publishing:
105
+
106
+ ```powershell
107
+ npm run build:npm-binaries:release
108
+ npm pack --dry-run
109
+ npm publish --access public
110
+ ```
111
+
112
+ The npm account or organization must own the `@rnbsolucoes` scope. Scoped public
113
+ packages must be published with public access on the first publish.
114
+ The current release build targets Windows x64 and Linux x64. Add more targets by
115
+ running `node npm/build-package-binaries.mjs --targets=all` or a comma-separated
116
+ target list when those platforms are ready.
117
+
118
+ ## Update
119
+
120
+ Check for a newer version:
121
+
122
+ ```powershell
123
+ axion update --check
124
+ axion version --check
125
+ ```
126
+
127
+ Update the installed CLI. This is the official update alias and must remain the
128
+ single command users run whenever a new Axion Code binary/package is released:
129
+
130
+ ```powershell
131
+ axion update
132
+ ```
133
+
134
+ The npm wrapper checks the public npm registry first and falls back to the GitHub
135
+ package metadata only when available. If a newer version exists, the initial
136
+ Axion Code splash shows the update notice and hides it after the first
137
+ interaction.
138
+
139
+ Without npm:
140
+
141
+ ```powershell
142
+ go build -o "$env:LOCALAPPDATA\AxionCode\bin\axion-code.exe" ./cmd/axion-code
143
+ ```
144
+
145
+ Provider and model configuration is shared with Axion Desktop:
146
+
147
+ ```text
148
+ %USERPROFILE%\.axion\harness\providers.json
149
+ %USERPROFILE%\.axion\harness\active_profile
150
+ %USERPROFILE%\.axion\axion-mode.json
151
+ %USERPROFILE%\.axion\sessions\axion.db
152
+ ```
153
+
154
+ OpenRouter uses `OPENROUTER_API_KEY` from the environment or a `secret:<NAME>` reference stored in `C:\Israel\Pesoal\secrets.env` (override for tests: `AXION_SECRETS_ENV`). Provider profile config stores only references, never the key value.
155
+
156
+ For test/dev isolation, set `AXION_HOME` to a temporary folder. In normal use, leave it unset so Desktop and CLI remain two surfaces of the same Axion harness.
157
+
158
+ The interactive TUI supports:
159
+
160
+ ```text
161
+ / inline slash palette
162
+ /provider
163
+ /provider set <profile-id>
164
+ /model
165
+ /permission
166
+ ```
167
+
168
+ Slash palette behavior: `↑/↓` selects, `Tab` completes the selected command, `Enter` executes the selected command, `Esc` closes active menus/wizards.
169
+
170
+ `/model` pulls the active provider's `/models` endpoint when credentials are configured. Reasoning and fast-mode steps appear only when the selected model metadata exposes those parameters.
171
+
172
+ Provider menu actions:
173
+
174
+ ```text
175
+ Register provider
176
+ Edit provider
177
+ Set provider
178
+ Delete provider
179
+ ```
180
+
181
+ For a profile-backed OpenRouter session:
182
+
183
+ ```powershell
184
+ axion provider profile add or-free --catalog openrouter --model openrouter/free --credential-ref env:OPENROUTER_API_KEY
185
+ axion provider profile set or-free
186
+ axion
187
+ ```
188
+
189
+ ## Build
190
+
191
+ ```powershell
192
+ go test -count=1 ./...
193
+ go build ./cmd/axion-code
194
+ ```
195
+
196
+ If Go is not on PATH, use a verified local Go toolchain and keep generated binaries out of git.
197
+
198
+ ## Current Limits
199
+
200
+ This is a functional direction MVP, not the full harness:
201
+
202
+ - no provider streaming yet; current provider profile foundation is non-streaming;
203
+ - terminal logo uses Sixel when available and falls back to width-bounded ANSI/block rendering;
204
+ - initial chat splash shows the Axion logo and system name until the first interaction;
205
+ - no tool execution yet;
206
+ - no executable Pi RPC bridge yet;
207
+ - no MCP transport execution yet.
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import https from "node:https";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const here = dirname(fileURLToPath(import.meta.url));
9
+ const root = resolve(here, "..", "..");
10
+ const isWindows = process.platform === "win32";
11
+ const exeName = isWindows ? "axion-code.exe" : "axion-code";
12
+ const platformKey = `${process.platform}-${process.arch}`;
13
+ const packagedExePath = join(root, "npm", "releases", platformKey, exeName);
14
+ const binDir = isWindows
15
+ ? join(process.env.LOCALAPPDATA || join(process.env.USERPROFILE || root, "AppData", "Local"), "AxionCode", "bin")
16
+ : join(process.env.HOME || root, ".local", "share", "axion-code", "bin");
17
+ const exePath = join(binDir, exeName);
18
+ const markerPath = join(binDir, ".axion-code-package-version");
19
+ const updateCachePath = join(binDir, ".axion-code-update.json");
20
+ const packageJSON = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
21
+ const updateSource = packageJSON.name;
22
+ const githubSource = "github:rnbsolucoes/Axion-CLI";
23
+ const updatePackageURL = "https://raw.githubusercontent.com/rnbsolucoes/Axion-CLI/main/package.json";
24
+ const npmRegistryURL = "https://registry.npmjs.org/@rnbsolucoes%2Faxion-code/latest";
25
+ const updateCommand = "axion update";
26
+ const updateCacheTTL = 12 * 60 * 60 * 1000;
27
+
28
+ function findGo() {
29
+ if (process.env.AXION_GO) {
30
+ return process.env.AXION_GO;
31
+ }
32
+ if (isWindows && process.env.USERPROFILE) {
33
+ const bundled = join(process.env.USERPROFILE, ".codex", "toolchains", "go1.26.4", "go", "bin", "go.exe");
34
+ if (existsSync(bundled)) {
35
+ return bundled;
36
+ }
37
+ }
38
+ return "go";
39
+ }
40
+
41
+ function sourceAvailable() {
42
+ return existsSync(join(root, "go.mod")) && existsSync(join(root, "cmd", "axion-code"));
43
+ }
44
+
45
+ function installPackagedBinary() {
46
+ if (!existsSync(packagedExePath)) {
47
+ return false;
48
+ }
49
+ copyFileSync(packagedExePath, exePath);
50
+ if (!isWindows) {
51
+ chmodSync(exePath, 0o755);
52
+ }
53
+ writeFileSync(markerPath, `${packageJSON.version}\n`, "utf8");
54
+ return true;
55
+ }
56
+
57
+ function buildFromSource() {
58
+ const go = findGo();
59
+ const result = spawnSync(go, ["build", "-o", exePath, "./cmd/axion-code"], {
60
+ cwd: root,
61
+ stdio: "inherit",
62
+ shell: false
63
+ });
64
+ if (result.status !== 0) {
65
+ return false;
66
+ }
67
+ writeFileSync(markerPath, `${packageJSON.version}\n`, "utf8");
68
+ return true;
69
+ }
70
+
71
+ function ensureBinary() {
72
+ const marker = existsSync(markerPath) ? readFileSync(markerPath, "utf8").trim() : "";
73
+ if (existsSync(exePath) && marker === packageJSON.version) {
74
+ return;
75
+ }
76
+ mkdirSync(binDir, { recursive: true });
77
+ if (installPackagedBinary()) {
78
+ return;
79
+ }
80
+ if (sourceAvailable() && buildFromSource()) {
81
+ return;
82
+ }
83
+ console.error("");
84
+ console.error(`Axion Code binary for ${platformKey} was not found in this package.`);
85
+ console.error("If you are using a source clone, install Go 1.26+ on PATH or set AXION_GO and run:");
86
+ console.error(" npm run install:local");
87
+ console.error("If you installed from npm, update to a package that includes your platform binary.");
88
+ process.exit(1);
89
+ }
90
+
91
+ function semverParts(value) {
92
+ return String(value || "")
93
+ .trim()
94
+ .split(/[+-]/)[0]
95
+ .split(".")
96
+ .slice(0, 3)
97
+ .map((part) => Number.parseInt(part, 10) || 0);
98
+ }
99
+
100
+ function compareVersions(a, b) {
101
+ const aa = semverParts(a);
102
+ const bb = semverParts(b);
103
+ for (let i = 0; i < 3; i += 1) {
104
+ if ((aa[i] || 0) > (bb[i] || 0)) return 1;
105
+ if ((aa[i] || 0) < (bb[i] || 0)) return -1;
106
+ }
107
+ return String(a || "").localeCompare(String(b || ""));
108
+ }
109
+
110
+ function readJSON(path) {
111
+ try {
112
+ return JSON.parse(readFileSync(path, "utf8"));
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ function writeJSON(path, value) {
119
+ try {
120
+ mkdirSync(dirname(path), { recursive: true });
121
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
122
+ } catch {
123
+ // Update metadata is advisory; failing to cache must not block startup.
124
+ }
125
+ }
126
+
127
+ function fetchJSON(url, timeoutMs = 1800) {
128
+ return new Promise((resolvePromise, rejectPromise) => {
129
+ const headers = { "User-Agent": `Axion-Code/${packageJSON.version}` };
130
+ if (url.includes("githubusercontent.com")) {
131
+ const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
132
+ if (token) {
133
+ headers.Authorization = `Bearer ${token}`;
134
+ }
135
+ }
136
+ const req = https.get(url, { headers }, (res) => {
137
+ if (res.statusCode !== 200) {
138
+ res.resume();
139
+ rejectPromise(new Error(`status ${res.statusCode}`));
140
+ return;
141
+ }
142
+ let body = "";
143
+ res.setEncoding("utf8");
144
+ res.on("data", (chunk) => {
145
+ body += chunk;
146
+ if (body.length > 128 * 1024) {
147
+ req.destroy(new Error("version payload too large"));
148
+ }
149
+ });
150
+ res.on("end", () => {
151
+ try {
152
+ resolvePromise(JSON.parse(body));
153
+ } catch (error) {
154
+ rejectPromise(error);
155
+ }
156
+ });
157
+ });
158
+ req.setTimeout(timeoutMs, () => req.destroy(new Error("version check timeout")));
159
+ req.on("error", rejectPromise);
160
+ });
161
+ }
162
+
163
+ async function fetchLatestPackage() {
164
+ const errors = [];
165
+ for (const candidate of [
166
+ { url: npmRegistryURL, source: packageJSON.name },
167
+ { url: updatePackageURL, source: githubSource }
168
+ ]) {
169
+ try {
170
+ const remote = await fetchJSON(candidate.url);
171
+ const latestVersion = String(remote.version || "").trim();
172
+ if (latestVersion) {
173
+ return { latestVersion, source: candidate.source };
174
+ }
175
+ errors.push(`${candidate.url}: missing version`);
176
+ } catch (error) {
177
+ errors.push(`${candidate.url}: ${error.message}`);
178
+ }
179
+ }
180
+ throw new Error(errors.join("; "));
181
+ }
182
+
183
+ async function readUpdateInfo({ force = false } = {}) {
184
+ const cached = readJSON(updateCachePath);
185
+ const now = Date.now();
186
+ if (!force && cached?.latestVersion && now - Number(cached.checkedAt || 0) < updateCacheTTL) {
187
+ return {
188
+ currentVersion: packageJSON.version,
189
+ latestVersion: cached.latestVersion,
190
+ updateAvailable: compareVersions(cached.latestVersion, packageJSON.version) > 0,
191
+ source: updateSource,
192
+ checkedAt: cached.checkedAt,
193
+ fromCache: true
194
+ };
195
+ }
196
+ try {
197
+ const remote = await fetchLatestPackage();
198
+ const latestVersion = remote.latestVersion;
199
+ const info = {
200
+ currentVersion: packageJSON.version,
201
+ latestVersion,
202
+ updateAvailable: latestVersion ? compareVersions(latestVersion, packageJSON.version) > 0 : false,
203
+ source: remote.source || updateSource,
204
+ checkedAt: now,
205
+ fromCache: false
206
+ };
207
+ if (latestVersion) {
208
+ writeJSON(updateCachePath, { latestVersion, checkedAt: now, source: info.source });
209
+ }
210
+ return info;
211
+ } catch (error) {
212
+ return {
213
+ currentVersion: packageJSON.version,
214
+ latestVersion: cached?.latestVersion || "",
215
+ updateAvailable: cached?.latestVersion ? compareVersions(cached.latestVersion, packageJSON.version) > 0 : false,
216
+ source: cached?.source || updateSource,
217
+ checkedAt: cached?.checkedAt || 0,
218
+ fromCache: Boolean(cached?.latestVersion),
219
+ error: error.message
220
+ };
221
+ }
222
+ }
223
+
224
+ async function updateCommandHandler(args) {
225
+ const checkOnly = args.includes("--check");
226
+ const asJSON = args.includes("--json");
227
+ const info = await readUpdateInfo({ force: true });
228
+ if (checkOnly) {
229
+ if (asJSON) {
230
+ console.log(JSON.stringify({
231
+ current: info.currentVersion,
232
+ latest: info.latestVersion || undefined,
233
+ update_available: info.updateAvailable,
234
+ source: info.source,
235
+ command: updateCommand,
236
+ checked: Boolean(info.latestVersion),
237
+ error: info.error || undefined
238
+ }));
239
+ return;
240
+ }
241
+ if (info.updateAvailable) {
242
+ console.log(`Update available: ${info.currentVersion} -> ${info.latestVersion}`);
243
+ console.log(`Run: ${updateCommand}`);
244
+ return;
245
+ }
246
+ if (info.latestVersion) {
247
+ console.log(`Axion Code ${info.currentVersion} is up to date.`);
248
+ return;
249
+ }
250
+ console.log(`Axion Code ${info.currentVersion}. Could not check latest version.`);
251
+ if (info.error) {
252
+ console.error(`Version check warning: ${info.error}`);
253
+ }
254
+ return;
255
+ }
256
+ const source = info.source === packageJSON.name ? packageJSON.name : updateSource;
257
+ console.log(`Updating Axion Code from ${source}...`);
258
+ const result = spawnSync("npm", ["install", "-g", source], {
259
+ stdio: "inherit",
260
+ shell: false
261
+ });
262
+ if (result.error) {
263
+ console.error(result.error.message);
264
+ process.exit(1);
265
+ }
266
+ if (result.status !== 0) {
267
+ process.exit(result.status || 1);
268
+ }
269
+ console.log("Axion Code update completed. Run axion again.");
270
+ }
271
+
272
+ const args = process.argv.slice(2);
273
+ if (args[0] === "update") {
274
+ await updateCommandHandler(args.slice(1));
275
+ process.exit(0);
276
+ }
277
+
278
+ const updateInfo = await readUpdateInfo();
279
+ ensureBinary();
280
+ const run = spawnSync(exePath, process.argv.slice(2), {
281
+ stdio: "inherit",
282
+ shell: false,
283
+ env: {
284
+ ...process.env,
285
+ AXION_CODE_VERSION: packageJSON.version,
286
+ AXION_CODE_LATEST_VERSION: updateInfo.latestVersion || "",
287
+ AXION_CODE_UPDATE_AVAILABLE: updateInfo.updateAvailable ? "1" : "0",
288
+ AXION_CODE_UPDATE_COMMAND: updateCommand,
289
+ AXION_CODE_UPDATE_SOURCE: updateSource
290
+ }
291
+ });
292
+
293
+ if (run.error) {
294
+ console.error(run.error.message);
295
+ process.exit(1);
296
+ }
297
+ process.exit(run.status ?? 0);
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@rnbsolucoes/axion-code",
3
+ "version": "0.1.3",
4
+ "description": "Axion Code CLI harness for the Axion ecosystem.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/rnbsolucoes/Axion-CLI.git"
9
+ },
10
+ "homepage": "https://github.com/rnbsolucoes/Axion-CLI#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/rnbsolucoes/Axion-CLI/issues"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "registry": "https://registry.npmjs.org/"
17
+ },
18
+ "bin": {
19
+ "axion": "npm/bin/axion.mjs",
20
+ "axion-code": "npm/bin/axion.mjs"
21
+ },
22
+ "scripts": {
23
+ "build": "go build -o dist/axion-code ./cmd/axion-code",
24
+ "build:npm-binaries": "node npm/build-package-binaries.mjs",
25
+ "build:npm-binaries:release": "node npm/build-package-binaries.mjs --targets=win32-x64,linux-x64",
26
+ "install:local": "node npm/install-local.mjs",
27
+ "test": "go test -count=1 ./..."
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "files": [
33
+ "npm/bin",
34
+ "npm/releases",
35
+ "README.md"
36
+ ],
37
+ "keywords": [
38
+ "axion",
39
+ "cli",
40
+ "agent",
41
+ "prevc",
42
+ "harness"
43
+ ]
44
+ }