@visual-z/xeno 0.13.1 → 0.13.2

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,10 @@
1
1
  # xeno changelog
2
2
 
3
+ ## 0.13.2 — 2026-11-05
4
+
5
+ - Skills with descriptive `version` and `argument-hint` fields now load without startup warnings. Unsupported execution fields still report errors instead of being silently ignored.
6
+ - 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.
7
+
3
8
  ## 0.13.1 — 2026-11-02
4
9
 
5
10
  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.13.2",
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.13.2",
14
+ "@visual-z/xeno-darwin-x64": "0.13.2",
15
+ "@visual-z/xeno-linux-arm64": "0.13.2",
16
+ "@visual-z/xeno-linux-x64": "0.13.2",
17
+ "@visual-z/xeno-linux-arm64-musl": "0.13.2",
18
+ "@visual-z/xeno-linux-x64-musl": "0.13.2",
19
+ "@visual-z/xeno-win32-arm64": "0.13.2",
20
+ "@visual-z/xeno-win32-x64": "0.13.2"
21
21
  },
22
22
  "files": [
23
23
  "bin/xeno.exe",