@visual-z/xeno 0.13.1 → 0.14.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/CHANGELOG.md CHANGED
@@ -1,5 +1,60 @@
1
1
  # xeno changelog
2
2
 
3
+ ## 0.14.0 — 2026-11-14
4
+
5
+ Commands (breaking):
6
+ - The slash command list is reorganized: 24 commands are listed, in six groups (Session, Model, Work, Inspect, Knowledge, System), down from 49. The other names are no longer listed but still work exactly as before when typed: `/usage`, `/context`, `/state` and `/control` are under the new `/status`; `/name`, `/export`, `/import`, `/clone` and `/copy` under `/session`; `/thinking`, `/scoped-models` and `/models` under `/model`; `/login` and `/logout` under `/provider`; `/config`, `/trust` and `/reload` under `/settings`; `/hotkeys`, `/changelog` and `/debug` under `/help`; `/branches` and `/processes` under `/tasks`; `/accept` and `/acceptance` under `/goal`; `/exit` under `/quit`. Typing an old name shows where it now lives, and many can also be written as a sub-command, such as `/settings reload`.
7
+ - In the `/` list, Tab completes the command name and shows its arguments; Enter runs it. Tab no longer runs a command.
8
+ - Bare `/session` and `/status` open a menu instead of printing details. The `/model` picker, provider and settings pickers and the `/goal` menu reach the commands folded into them.
9
+ - The `/` menu is grouped by purpose when nothing is typed, shows each command's arguments and whether Enter runs it, opens a picker or opens a menu, scrolls past ten rows, and ends with a key hint. `/help` in the TUI opens the same list as a dialog.
10
+ - Completion acts on the word under the cursor, including after cursor moves, mouse clicks and wide characters, and running a command from the palette keeps the draft.
11
+
12
+ Look (breaking for the default colour):
13
+ - The first frame is drawn in the terminal's colours: xeno remembers the palette each terminal last reported in `~/.xeno/terminal-palette.json` and repaints once only if the terminal now answers differently. Startup no longer waits about 300 ms for colours.
14
+ - The default accent is a new xenon blue-violet, adjusted for contrast against your terminal's background. Set `accent: terminal` to get the terminal's ANSI blue back. Reasoning text uses the muted colour and inline code a tint toward the accent. Backgrounds are never painted; green, red and yellow mark only glyphs.
15
+ - The transcript hangs on one vertical line. Each tool call is a row with a dotted leader to its result (exit code, counts, duration, lines changed); a context segment change is drawn as a cut with what the handoff carried; each finished turn ends with a stamp saying how it ended and how many receipts covered the changes. Tool and turn durations come from the logged times.
16
+ - The input box shows what is happening on its top edge (running, quiet for a while, awaiting approval, accepted, retrying) and no longer says Ready when idle; the model and reasoning level sit at bottom left, mode, approval and segment use at bottom right. The sidebar has no border and four sections: Goal, Context, Files, Usage. At 80–109 columns the top bar is one line.
17
+ - The mascot is redrawn and moves only when something happens: it blinks, watches the input while you type, perks up when the `/` menu opens, startles on errors and sleeps when the window loses focus. It stays still with animation turned off. The greeting adds a "last here" row for the previous session.
18
+
19
+ Context:
20
+ - When a request has finished and the context already holds more than half of the usable window, or 128k tokens, whichever is smaller, the next request starts a new segment, so finished work no longer rides along in every later request. Interruptions, "continue" after a failure, interjections and running goals do not trigger it.
21
+ - For models billed at a higher rate past a context length (gpt-5.5-pro and gpt-5.4-pro at 272k, Gemini 3.1 Pro and grok-4.x at 200k), the usable window stops at that line. A larger window you set yourself is still used, and xeno says the rest is billed at the higher rate.
22
+ - When a handoff is too long, it keeps the most recently touched files, gates, branches and processes instead of the alphabetically last ones.
23
+
24
+ Tools:
25
+ - `grep` uses ripgrep under permission rules too, over the files the rules admit, so it is no longer much slower than `rg` in the shell.
26
+ - Empty optional arguments count as not provided, so models that fill every field no longer fail `write`, `history` and `note`.
27
+ - A plain nonzero exit shows only its header and output, without a generic diagnostic.
28
+ - A test run written as `cd x && set -o pipefail; cargo test | tail` counts as verification. Commands that could exit 0 without the gate running, such as `exit 0 && bun test`, no longer count.
29
+ - The completion check asks for verification only for net changes inside the workspace that a gate could check; reverted, outside, ignored and document edits no longer trigger it. Verification works in large repositories by reading Git's index; receipts recorded by earlier versions are stale until the command is run again.
30
+ - Escaped Ctrl-C (`\u0003`, `^C`) sent to a process interrupts it; background jobs a command leaves behind are named when they are stopped; a managed process that exits while nobody watches is announced before the next model request.
31
+ - A batch of parallel calls shares a total display budget of four single calls, so up to four parallel reads come back whole. A cut-off read says where to continue.
32
+ - Memory results are bounded and say when nothing matched; `read` can start from the end and `when` takes date ranges. A new memory `env` action answers environment questions (hosts, paths, tool versions) from facts that successful commands established, per project. Facts are not synced unless you turn them on with `/memory sync env-facts on`; the first scan after upgrading rereads xeno and Codex history once.
33
+ - Credentials are judged by where text came from: your messages are hidden more readily (camel-case keys, "host user password" written together), while tool output keeps identifiers and code.
34
+ - `/usage` warns when a provider route drops the model's reasoning between requests.
35
+ - An interrupted model request records how long it waited, how many attempts it made and whether a response had started.
36
+
37
+ Web:
38
+ - `websearch` sends one request per query, reports rate limits and missing keys as such, retries once when no result can have arrived, accepts other tools' argument names (and says when it renamed one) and returns shorter highlights.
39
+ - `webfetch` is in the default coding tools, reads long pages in windows continued from the saved page, keeps only the main content and says plainly when a page needs JavaScript or shows a bot challenge.
40
+ - `webfetch` refuses loopback, private, link-local, `.local`, `.internal` and dot-less hosts, checked after DNS resolution and on every redirect, unless a permission rule with the new `host` selector names the host. A `no_proxy` entry written as a host name may no longer match, because the connection now goes to the checked address.
41
+
42
+ Computer on macOS:
43
+ - `fill` sets pop-up menus and checkboxes; clicking a text field puts the caret there; ordinary keys are accepted.
44
+ - A step can name its control, and a sequence can follow the app across windows, so dismissing a dialog, filling a form and submitting is one call.
45
+ - A sequence reads the window once before and once after, element handles stay valid while their window is unchanged, and screenshots nobody will see are skipped.
46
+ - Opening and closing windows, alert sheets and a locked screen give results the agent can continue from. Results show each fact once.
47
+
48
+ Models and usage:
49
+ - DeepSeek's vendor-verified peak prices are kept when a refreshed model directory lists the off-peak price.
50
+
51
+ Compatibility: logs written by 0.14.0 can contain a process exit reason `exited` and a segment cut reason `request_boundary`, which earlier versions reject; earlier versions skip `env_fact` memory records and rebuild a 0.14.0 handoff once in their own order.
52
+
53
+ ## 0.13.2 — 2026-11-05
54
+
55
+ - Skills with descriptive `version` and `argument-hint` fields now load without startup warnings. Unsupported execution fields still report errors instead of being silently ignored.
56
+ - Trusted global installs on Windows register the actual npm/Bun bin directory in the user PATH without replacing existing entries. Reopen your terminal application afterward. Bun update commands now include `--trust` so native installation is not skipped; failed native placement on Windows reports an installation error.
57
+
3
58
  ## 0.13.1 — 2026-11-02
4
59
 
5
60
  Models:
package/README.md CHANGED
@@ -22,7 +22,19 @@ bun add -g --trust @visual-z/xeno
22
22
  ```
23
23
 
24
24
  Without `--trust`, `xeno` still starts on macOS and Linux through a small
25
- launcher script.
25
+ launcher script. Windows requires `--trust` so the native executable is placed.
26
+
27
+ On Windows, the global install step adds the actual global bin directory to
28
+ your user PATH when needed. Close and reopen the terminal application after
29
+ installing; already-running shells keep their old environment. Local project
30
+ installs do not change PATH. If scripts were skipped, reinstall with
31
+ `bun add -g --trust --force @visual-z/xeno`. To use the current PowerShell too:
32
+
33
+ ```powershell
34
+ $xenoBin = (bun pm bin -g).Trim()
35
+ $env:Path = "$xenoBin;$env:Path"
36
+ xeno --version
37
+ ```
26
38
 
27
39
  ## Start
28
40
 
package/install.cjs CHANGED
@@ -9,13 +9,13 @@
9
9
  //
10
10
  // If this never runs (`bun add -g` skips dependency scripts unless trusted),
11
11
  // the placeholder is itself a POSIX sh launcher that finds the same binary.
12
- // A failure here never fails the install: the placeholder still works.
12
+ // The placeholder works only on POSIX. Windows must finish native placement.
13
13
  //
14
14
  // PLATFORMS must match scripts/binary-targets.ts; test/binary-release.test.ts
15
15
  // enforces that.
16
16
 
17
17
  const { spawnSync } = require("child_process");
18
- const { copyFileSync, existsSync, linkSync, renameSync, unlinkSync, chmodSync } = require("fs");
18
+ const { copyFileSync, existsSync, linkSync, renameSync, unlinkSync, chmodSync, readFileSync, realpathSync } = require("fs");
19
19
  const { arch } = require("os");
20
20
  const path = require("path");
21
21
 
@@ -62,10 +62,119 @@ function place(source, dest) {
62
62
  if (process.platform !== "win32") chmodSync(dest, 0o755);
63
63
  }
64
64
 
65
+ function windowsPathKey(value, env) {
66
+ const expanded = value.trim().replace(/^"(.*)"$/, "$1").replace(/%([^%]+)%/g, (token, name) => {
67
+ const key = Object.keys(env).find(key => key.toLowerCase() === name.toLowerCase());
68
+ return key === undefined ? token : env[key];
69
+ });
70
+ return path.win32.isAbsolute(expanded) ? path.win32.normalize(expanded).replace(/[\\/]+$/, "").toLowerCase() : "";
71
+ }
72
+
73
+ // Preserve the stored PATH bytes and order; normalization is for comparison only.
74
+ function updatedWindowsPath(user, machine, bin, env, userKind = "ExpandString", machineKind = "ExpandString") {
75
+ if (!path.win32.isAbsolute(bin) || /[;"\r\n\0]/.test(bin)) throw Error("The global bin is not a valid absolute PATH entry");
76
+ const target = windowsPathKey(bin, env);
77
+ if ([[user, userKind], [machine, machineKind]].some(([value, kind]) => value.split(";").some(entry => windowsPathKey(entry, kind === "ExpandString" ? env : {}) === target))) return null;
78
+ return user + (user && !user.endsWith(";") ? ";" : "") + bin;
79
+ }
80
+
81
+ function installQuery(program, args) {
82
+ const result = spawnSync(program, args, { encoding: "utf8", windowsHide: true, timeout: 10000,
83
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" } });
84
+ if (result.error || result.status !== 0) throw Error(`Could not query ${args.join(" ")} for the global PATH directory`);
85
+ return result.stdout.trim();
86
+ }
87
+
88
+ function windowsGlobalBin() {
89
+ const env = process.env;
90
+ const executable = env.npm_execpath;
91
+ const bun = executable && /^bun(?:\.exe)?$/i.test(path.win32.basename(executable));
92
+ const owns = root => windowsPathKey(__dirname, env) === windowsPathKey(path.win32.join(root, "node_modules", "@visual-z", "xeno"), env);
93
+ if (!bun && env.npm_config_global === "true") {
94
+ const prefix = env.npm_config_prefix;
95
+ if (!prefix || !path.win32.isAbsolute(prefix) || !owns(prefix)) return null;
96
+ return prefix; // npm's Windows shims live directly in the global prefix.
97
+ }
98
+ if (!bun) return null;
99
+ // Bun has no global-install flag in lifecycle env and no lockfile yet on a
100
+ // fresh install. Prove ownership from the already-linked global command.
101
+ const bin = installQuery(executable, ["pm", "bin", "-g"]);
102
+ if (!path.win32.isAbsolute(bin) || /[;"\r\n\0]/.test(bin)) throw Error("Invalid Bun global bin directory for PATH setup");
103
+ const shim = path.win32.join(bin, "xeno.bunx");
104
+ if (!existsSync(shim)) return null;
105
+ // Bun's BinLinkingShim format starts with a UTF-16 path followed by quote/NUL.
106
+ // bin.zig removes the first '..\\'; resolve from the global bin's parent.
107
+ // Sources: Bun v1.2.17 BinLinkingShim.zig and v1.4.3 BinLinkingShim.rs,
108
+ // both in src/install/windows-shim; their v5 path prefix is unchanged.
109
+ const bytes = readFileSync(shim);
110
+ const header = bytes.toString("utf16le"), end = header.indexOf('"\0');
111
+ if (bytes.length % 2 !== 0 || end <= 0) throw Error("Unrecognized Bun command shim; could not verify the global PATH target");
112
+ const target = path.win32.resolve(bin, "..", header.slice(0, end));
113
+ return windowsPathKey(realpathSync(target), env) === windowsPathKey(realpathSync(path.win32.join(__dirname, "bin", "xeno.exe")), env) ? bin : null;
114
+ }
115
+
116
+ const READ_WINDOWS_PATH = `
117
+ $ErrorActionPreference = 'Stop'
118
+ [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
119
+ $userKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment')
120
+ $machineKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment')
121
+ try {
122
+ $user = if ($userKey) { $userKey.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { '' }
123
+ $kind = if ($userKey -and $userKey.GetValueNames() -contains 'Path') { $userKey.GetValueKind('Path').ToString() } else { 'String' }
124
+ $machine = if ($machineKey) { $machineKey.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { '' }
125
+ $machineKind = if ($machineKey -and $machineKey.GetValueNames() -contains 'Path') { $machineKey.GetValueKind('Path').ToString() } else { 'String' }
126
+ @{ user = [string]$user; kind = $kind; machine = [string]$machine; machineKind = $machineKind } | ConvertTo-Json -Compress
127
+ } finally {
128
+ if ($userKey) { $userKey.Dispose() }
129
+ if ($machineKey) { $machineKey.Dispose() }
130
+ }
131
+ `;
132
+
133
+ const WRITE_WINDOWS_PATH = `
134
+ $ErrorActionPreference = 'Stop'
135
+ $update = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String([Console]::In.ReadToEnd().Trim())) | ConvertFrom-Json
136
+ $key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')
137
+ try {
138
+ $before = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
139
+ $kind = if ($key.GetValueNames() -contains 'Path') { $key.GetValueKind('Path').ToString() } else { 'String' }
140
+ if ($before -cne $update.before -or $kind -cne $update.kind) { throw 'User PATH changed during installation; retry the install.' }
141
+ $key.SetValue('Path', [string]$update.after, [Microsoft.Win32.RegistryValueKind]$kind)
142
+ if ([string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) -cne $update.after) { throw 'User PATH readback did not match.' }
143
+ } finally { $key.Dispose() }
144
+ # Notify Explorer so newly launched terminal hosts inherit the changed environment.
145
+ # Existing terminals must restart even if notification cannot be delivered.
146
+ try {
147
+ Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class XenoEnvironment { [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern IntPtr SendMessageTimeout(IntPtr window, uint message, UIntPtr wparam, string lparam, uint flags, uint timeout, out UIntPtr result); }'
148
+ $result = [UIntPtr]::Zero
149
+ [void][XenoEnvironment]::SendMessageTimeout([IntPtr]65535, 26, [UIntPtr]::Zero, 'Environment', 2, 2000, [ref]$result)
150
+ } catch { Write-Warning 'PATH saved. Sign out and back in if restarting your terminal does not pick it up.' }
151
+ `;
152
+
153
+ function configureWindowsPath() {
154
+ const bin = windowsGlobalBin();
155
+ if (bin === null) return;
156
+ const powershell = path.win32.join(process.env.SystemRoot || "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
157
+ const run = (script, input) => {
158
+ const result = spawnSync(powershell, ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")],
159
+ { encoding: "utf8", windowsHide: true, timeout: 15000, ...(input === undefined ? {} : { input }) });
160
+ if (result.error || result.status !== 0) throw Error("Could not read or update the Windows user PATH; check your environment settings and retry");
161
+ return result.stdout;
162
+ };
163
+ const state = JSON.parse(run(READ_WINDOWS_PATH));
164
+ if (typeof state.user !== "string" || typeof state.machine !== "string" || ![state.kind, state.machineKind].every(kind => ["String", "ExpandString"].includes(kind))) throw Error("Unexpected Windows PATH registry value");
165
+ const after = updatedWindowsPath(state.user, state.machine, bin, process.env, state.kind, state.machineKind);
166
+ if (after === null) return;
167
+ // Data travels through stdin, never PowerShell source or the command line:
168
+ // Unicode, quotes and long PATHs cannot become code or exceed argv limits.
169
+ run(WRITE_WINDOWS_PATH, Buffer.from(JSON.stringify({ before: state.user, after, kind: state.kind }), "utf8").toString("base64"));
170
+ console.log(`[xeno postinstall] Added ${bin} to your user PATH. Close and reopen your terminal application to run xeno.`);
171
+ }
172
+
65
173
  function main() {
66
174
  const key = platformKey(), info = PLATFORMS[key];
67
175
  if (!info) {
68
176
  console.error(`[xeno postinstall] Unsupported platform: ${key}. Supported: ${Object.keys(PLATFORMS).join(", ")}`);
177
+ if (process.platform === "win32") process.exitCode = 1;
69
178
  return;
70
179
  }
71
180
  let dir;
@@ -73,6 +182,7 @@ function main() {
73
182
  catch {
74
183
  console.error(`[xeno postinstall] ${info.pkg} is not installed (--omit=optional, or its download failed).`);
75
184
  console.error(" Reinstall @visual-z/xeno to fetch it.");
185
+ if (process.platform === "win32") process.exitCode = 1;
76
186
  return;
77
187
  }
78
188
  try {
@@ -80,9 +190,17 @@ function main() {
80
190
  if (existsSync(path.join(dir, WASM))) place(path.join(dir, WASM), path.join(__dirname, "bin", WASM));
81
191
  } catch (error) {
82
192
  console.error(`[xeno postinstall] Could not place the binary: ${error.message}`);
83
- console.error(" xeno still starts through its launcher, one process slower.");
193
+ if (process.platform === "win32") {
194
+ console.error(" Reinstall with npm i -g @visual-z/xeno or bun add -g --trust @visual-z/xeno. Windows requires the native binary.");
195
+ process.exitCode = 1;
196
+ } else console.error(" xeno still starts through its launcher, one process slower.");
197
+ return;
198
+ }
199
+ if (process.platform === "win32") {
200
+ try { configureWindowsPath(); }
201
+ catch (error) { console.error(`[xeno postinstall] PATH setup failed: ${error.message}`); }
84
202
  }
85
203
  }
86
204
 
87
205
  if (require.main === module) main();
88
- module.exports = { PLATFORMS, platformKey };
206
+ module.exports = { PLATFORMS, platformKey, updatedWindowsPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visual-z/xeno",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Segmented append-only agent log with pure context and handoff projections",
6
6
  "bin": {
@@ -10,14 +10,14 @@
10
10
  "postinstall": "node install.cjs"
11
11
  },
12
12
  "optionalDependencies": {
13
- "@visual-z/xeno-darwin-arm64": "0.13.1",
14
- "@visual-z/xeno-darwin-x64": "0.13.1",
15
- "@visual-z/xeno-linux-arm64": "0.13.1",
16
- "@visual-z/xeno-linux-x64": "0.13.1",
17
- "@visual-z/xeno-linux-arm64-musl": "0.13.1",
18
- "@visual-z/xeno-linux-x64-musl": "0.13.1",
19
- "@visual-z/xeno-win32-arm64": "0.13.1",
20
- "@visual-z/xeno-win32-x64": "0.13.1"
13
+ "@visual-z/xeno-darwin-arm64": "0.14.0",
14
+ "@visual-z/xeno-darwin-x64": "0.14.0",
15
+ "@visual-z/xeno-linux-arm64": "0.14.0",
16
+ "@visual-z/xeno-linux-x64": "0.14.0",
17
+ "@visual-z/xeno-linux-arm64-musl": "0.14.0",
18
+ "@visual-z/xeno-linux-x64-musl": "0.14.0",
19
+ "@visual-z/xeno-win32-arm64": "0.14.0",
20
+ "@visual-z/xeno-win32-x64": "0.14.0"
21
21
  },
22
22
  "files": [
23
23
  "bin/xeno.exe",