@sunilaleti/hx 1.0.0 → 1.1.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 +22 -1
- package/README.md +9 -2
- package/dist/hx.js +168 -8
- package/dist/hx.js.map +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,26 @@ the project follows [semantic versioning](https://semver.org/).
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
+
## [1.1.0] - 2026-10-31
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- HX updates itself, as Claude Code does. About once a day a session checks npm for a newer
|
|
36
|
+
release, installs it in the background with `npm install --global`, and says to restart.
|
|
37
|
+
`hx update` (or `hx upgrade`) updates now. Turn the background update off with
|
|
38
|
+
`"autoUpdates": false` in settings or `HX_DISABLE_AUTOUPDATER=1`. Only an npm install updates
|
|
39
|
+
itself; `hx -p` never does.
|
|
40
|
+
1.0.0 has no updater: install 1.1.0 once with `npm install --global @sunilaleti/hx@latest`, and
|
|
41
|
+
it keeps itself current from there.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- CI runs the suite twice instead of four times - Linux and macOS, both on Node 24 - and
|
|
46
|
+
publishes with the npm that ships with Node 24. Node 22, the oldest HX supports, is checked
|
|
47
|
+
by installing the package on it and running `hx --version`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
31
51
|
## [1.0.0] - 2026-10-31
|
|
32
52
|
|
|
33
53
|
### Changed
|
|
@@ -1103,7 +1123,8 @@ First release, published to PyPI as [`hx-cli`](https://pypi.org/project/hx-cli/)
|
|
|
1103
1123
|
- `/configure` and `hx auth` for the OpenRouter key, `hx upgrade` for
|
|
1104
1124
|
self-update, and `install.sh` bootstrapping uv with a pinned Python.
|
|
1105
1125
|
|
|
1106
|
-
[Unreleased]: https://github.com/aletisunil/hx/compare/v1.
|
|
1126
|
+
[Unreleased]: https://github.com/aletisunil/hx/compare/v1.1.0...HEAD
|
|
1127
|
+
[1.1.0]: https://github.com/aletisunil/hx/compare/v1.0.0...v1.1.0
|
|
1107
1128
|
[1.0.0]: https://github.com/aletisunil/hx/compare/v0.3.0-beta.1...v1.0.0
|
|
1108
1129
|
[0.3.0-beta.1]: https://github.com/aletisunil/hx/compare/v0.2.20...v0.3.0-beta.1
|
|
1109
1130
|
[0.2.20]: https://github.com/aletisunil/hx/compare/v0.2.19...v0.2.20
|
package/README.md
CHANGED
|
@@ -16,8 +16,13 @@ npm install --global @sunilaleti/hx
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
HX runs on [Node.js](https://nodejs.org/) 22.15 or newer, on macOS and Linux.
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
Then run `hx`: the first time, it opens with the HX logo and says to type
|
|
20
|
+
`/login`.
|
|
21
|
+
|
|
22
|
+
HX keeps itself up to date, as Claude Code does. About once a day, a session
|
|
23
|
+
checks npm for a newer release, installs it in the background, and says to
|
|
24
|
+
restart. Run `hx update` to update now. To turn the background update off, set
|
|
25
|
+
`"autoUpdates": false` in `~/.hx/settings.json`, or `HX_DISABLE_AUTOUPDATER=1`.
|
|
21
26
|
|
|
22
27
|
Or let the install script check your Node.js version and run that command:
|
|
23
28
|
|
|
@@ -349,6 +354,7 @@ hx auth login [provider] # sign in (openrouter, openai-codex, devin, claude-c
|
|
|
349
354
|
hx auth logout <provider> # forget a stored credential
|
|
350
355
|
hx docs [section] # the manual; no argument lists its sections
|
|
351
356
|
hx changelog [version] # what shipped in each version
|
|
357
|
+
hx update # install the newest release now (also: hx upgrade)
|
|
352
358
|
```
|
|
353
359
|
|
|
354
360
|
Print mode is the scriptable one: stdout carries only the assistant's text, so
|
|
@@ -663,6 +669,7 @@ along with the `.hx/.gitignore` it added to hide it.
|
|
|
663
669
|
{
|
|
664
670
|
"theme": "dark", // dark | light | ansi, or a file in ~/.hx/themes
|
|
665
671
|
"quiet_startup": false, // skip the startup header
|
|
672
|
+
"autoUpdates": true, // install new releases in the background, once a day
|
|
666
673
|
|
|
667
674
|
"models": {
|
|
668
675
|
"model": "anthropic/claude-sonnet-4.5",
|
package/dist/hx.js
CHANGED
|
@@ -6351,7 +6351,8 @@ function buildSettings(data, cwd) {
|
|
|
6351
6351
|
tui: { enterWhileBusy: busyRaw, fullscreen: truthy(tui.fullscreen ?? false) },
|
|
6352
6352
|
tools: { hashline: truthy(pick(tools, "hashline", true)) },
|
|
6353
6353
|
theme: pick(data, "theme", "dark"),
|
|
6354
|
-
quietStartup: truthy(pick(data, "quietStartup", pick(data, "quiet_startup", false)))
|
|
6354
|
+
quietStartup: truthy(pick(data, "quietStartup", pick(data, "quiet_startup", false))),
|
|
6355
|
+
autoUpdates: truthy(pick(data, "autoUpdates", pick(data, "auto_updates", true)))
|
|
6355
6356
|
};
|
|
6356
6357
|
}
|
|
6357
6358
|
function iterate(value) {
|
|
@@ -67732,7 +67733,8 @@ init_net();
|
|
|
67732
67733
|
init_py();
|
|
67733
67734
|
|
|
67734
67735
|
// src/version.ts
|
|
67735
|
-
var VERSION = "1.
|
|
67736
|
+
var VERSION = "1.1.0";
|
|
67737
|
+
var PACKAGE = "@sunilaleti/hx";
|
|
67736
67738
|
|
|
67737
67739
|
// src/providers/codex.ts
|
|
67738
67740
|
init_base();
|
|
@@ -75807,11 +75809,11 @@ var LANGUAGES = {
|
|
|
75807
75809
|
".go": GO,
|
|
75808
75810
|
".rs": RUST
|
|
75809
75811
|
};
|
|
75810
|
-
var
|
|
75812
|
+
var PACKAGE2 = "@vscode/tree-sitter-wasm";
|
|
75811
75813
|
var load = createRequire(import.meta.url);
|
|
75812
75814
|
function packageDir() {
|
|
75813
75815
|
try {
|
|
75814
|
-
return dirname(load.resolve(
|
|
75816
|
+
return dirname(load.resolve(PACKAGE2));
|
|
75815
75817
|
} catch {
|
|
75816
75818
|
return null;
|
|
75817
75819
|
}
|
|
@@ -75825,9 +75827,9 @@ function parserFor(grammar) {
|
|
|
75825
75827
|
parser = (async () => {
|
|
75826
75828
|
const directory = packageDir();
|
|
75827
75829
|
if (directory === null)
|
|
75828
|
-
throw new ToolError(`tree-sitter is not installed: npm install -g ${
|
|
75830
|
+
throw new ToolError(`tree-sitter is not installed: npm install -g ${PACKAGE2}`);
|
|
75829
75831
|
runtime ??= (async () => {
|
|
75830
|
-
const module = load(
|
|
75832
|
+
const module = load(PACKAGE2);
|
|
75831
75833
|
await module.Parser.init({ locateFile: (file) => join(directory, file) });
|
|
75832
75834
|
return module;
|
|
75833
75835
|
})();
|
|
@@ -89678,6 +89680,17 @@ var HXSession = class {
|
|
|
89678
89680
|
this.refreshStatus();
|
|
89679
89681
|
if (this.loop.session.activeMessages().length) this.replayTranscript();
|
|
89680
89682
|
if (this.extra.signIn) this.showSignIn(this.extra.signIn);
|
|
89683
|
+
void this.extra.update?.then((outcome2) => {
|
|
89684
|
+
if (outcome2 === null) return;
|
|
89685
|
+
const level = outcome2.ok ? "success" : "warning";
|
|
89686
|
+
if (this.view.transcript.blocks.length) {
|
|
89687
|
+
this.notice(outcome2.message, level);
|
|
89688
|
+
return;
|
|
89689
|
+
}
|
|
89690
|
+
this.view.transcript.add(new Spacer(1));
|
|
89691
|
+
this.view.transcript.add(new Notice(outcome2.message, level));
|
|
89692
|
+
this.runner.requestRender();
|
|
89693
|
+
});
|
|
89681
89694
|
if (this.loop.permissions !== null)
|
|
89682
89695
|
this.loop.permissions.asker = (request2) => this.askPermission(request2);
|
|
89683
89696
|
const events = this.consumeEvents();
|
|
@@ -90680,6 +90693,145 @@ async function runSession(loop, bus, settings, extra = {}) {
|
|
|
90680
90693
|
return session.relaunch;
|
|
90681
90694
|
}
|
|
90682
90695
|
|
|
90696
|
+
// src/update.ts
|
|
90697
|
+
init_json();
|
|
90698
|
+
init_net();
|
|
90699
|
+
init_pathlib();
|
|
90700
|
+
init_paths();
|
|
90701
|
+
init_py();
|
|
90702
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
90703
|
+
import { mkdirSync as mkdirSync15, openSync as openSync10, readFileSync as readFileSync10, realpathSync as realpathSync3, writeFileSync as writeFileSync12 } from "node:fs";
|
|
90704
|
+
init_which();
|
|
90705
|
+
var RELEASES_URL2 = `https://registry.npmjs.org/-/package/${PACKAGE}/dist-tags`;
|
|
90706
|
+
var RELEASES_ENV2 = "HX_RELEASES_URL";
|
|
90707
|
+
var DISABLE_ENV = "HX_DISABLE_AUTOUPDATER";
|
|
90708
|
+
var RELEASES_TIMEOUT2 = 5;
|
|
90709
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
90710
|
+
var stateFile = () => join(userHome(), "update.json");
|
|
90711
|
+
async function latestRelease(signal) {
|
|
90712
|
+
const url = strip(process.env[RELEASES_ENV2] ?? "") || RELEASES_URL2;
|
|
90713
|
+
try {
|
|
90714
|
+
const response = await request(url, {
|
|
90715
|
+
timeout: RELEASES_TIMEOUT2,
|
|
90716
|
+
...signal ? { signal } : {}
|
|
90717
|
+
});
|
|
90718
|
+
try {
|
|
90719
|
+
if (response.status < 200 || response.status >= 300) return null;
|
|
90720
|
+
const tags = await response.json();
|
|
90721
|
+
const latest = typeof tags === "object" && tags !== null ? tags.latest : null;
|
|
90722
|
+
return typeof latest === "string" && RELEASE2.test(latest) ? latest : null;
|
|
90723
|
+
} finally {
|
|
90724
|
+
response.close();
|
|
90725
|
+
}
|
|
90726
|
+
} catch {
|
|
90727
|
+
return null;
|
|
90728
|
+
}
|
|
90729
|
+
}
|
|
90730
|
+
var RELEASE2 = /^\d+\.\d+\.\d+$/;
|
|
90731
|
+
function compareReleases(a, b) {
|
|
90732
|
+
const left = a.split(".").map(Number);
|
|
90733
|
+
const right = b.split(".").map(Number);
|
|
90734
|
+
for (let index = 0; index < 3; index++) {
|
|
90735
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
90736
|
+
if (difference) return difference;
|
|
90737
|
+
}
|
|
90738
|
+
return 0;
|
|
90739
|
+
}
|
|
90740
|
+
function notAnNpmInstall() {
|
|
90741
|
+
const script = process.argv[1] ?? "";
|
|
90742
|
+
let real2;
|
|
90743
|
+
try {
|
|
90744
|
+
real2 = realpathSync3(script);
|
|
90745
|
+
} catch {
|
|
90746
|
+
return script;
|
|
90747
|
+
}
|
|
90748
|
+
return real2.includes(`/node_modules/${PACKAGE}/`) ? null : parent(parent(real2));
|
|
90749
|
+
}
|
|
90750
|
+
function npm() {
|
|
90751
|
+
return which("npm") ?? join(parent(process.execPath), "npm");
|
|
90752
|
+
}
|
|
90753
|
+
var INSTALL_COMMAND = `npm install --global ${PACKAGE}`;
|
|
90754
|
+
function runNpm(version, background) {
|
|
90755
|
+
const args = ["install", "--global", `${PACKAGE}@${version}`];
|
|
90756
|
+
let stdio = "inherit";
|
|
90757
|
+
if (background) {
|
|
90758
|
+
mkdirSync15(logsDir(), { recursive: true });
|
|
90759
|
+
const log = openSync10(join(logsDir(), "update.log"), "a");
|
|
90760
|
+
stdio = ["ignore", log, log];
|
|
90761
|
+
}
|
|
90762
|
+
return new Promise((resolve2) => {
|
|
90763
|
+
const child = spawn10(npm(), args, { stdio, detached: background });
|
|
90764
|
+
child.once("error", () => resolve2(null));
|
|
90765
|
+
child.once("exit", (code2, signal) => resolve2(code2 ?? (signal ? 1 : 0)));
|
|
90766
|
+
if (background) child.unref();
|
|
90767
|
+
});
|
|
90768
|
+
}
|
|
90769
|
+
async function runUpdate(print2) {
|
|
90770
|
+
const elsewhere2 = notAnNpmInstall();
|
|
90771
|
+
if (elsewhere2 !== null) {
|
|
90772
|
+
print2(
|
|
90773
|
+
`hx is running from ${elsewhere2}, not from an npm install, so it cannot update itself. Update a checkout with git pull and npm run build, or install the release: ${INSTALL_COMMAND}`,
|
|
90774
|
+
process.stderr
|
|
90775
|
+
);
|
|
90776
|
+
return 1;
|
|
90777
|
+
}
|
|
90778
|
+
const latest = await latestRelease();
|
|
90779
|
+
if (latest === null) {
|
|
90780
|
+
print2("Could not reach the npm registry to check for a newer hx.", process.stderr);
|
|
90781
|
+
return 1;
|
|
90782
|
+
}
|
|
90783
|
+
if (compareReleases(latest, VERSION) <= 0) {
|
|
90784
|
+
print2(`hx ${VERSION} is up to date.`);
|
|
90785
|
+
return 0;
|
|
90786
|
+
}
|
|
90787
|
+
print2(`Updating hx ${VERSION} to ${latest}...`);
|
|
90788
|
+
const status3 = await runNpm(latest, false);
|
|
90789
|
+
if (status3 === null) {
|
|
90790
|
+
print2(
|
|
90791
|
+
`npm is not installed, so hx cannot update itself. See https://nodejs.org/`,
|
|
90792
|
+
process.stderr
|
|
90793
|
+
);
|
|
90794
|
+
return 1;
|
|
90795
|
+
}
|
|
90796
|
+
if (status3 !== 0) {
|
|
90797
|
+
print2(`npm could not install ${PACKAGE}@${latest} (exit ${status3}).`, process.stderr);
|
|
90798
|
+
return 1;
|
|
90799
|
+
}
|
|
90800
|
+
print2(`Updated hx to ${latest}.`);
|
|
90801
|
+
return 0;
|
|
90802
|
+
}
|
|
90803
|
+
function lastChecked() {
|
|
90804
|
+
try {
|
|
90805
|
+
const state = JSON.parse(readFileSync10(stateFile(), "utf8"));
|
|
90806
|
+
const at = typeof state === "object" && state !== null ? state.checkedAt : 0;
|
|
90807
|
+
return typeof at === "number" ? at : 0;
|
|
90808
|
+
} catch {
|
|
90809
|
+
return 0;
|
|
90810
|
+
}
|
|
90811
|
+
}
|
|
90812
|
+
async function autoUpdate(enabled, signal) {
|
|
90813
|
+
const disabled = strip(process.env[DISABLE_ENV] ?? "").toLowerCase();
|
|
90814
|
+
if (!enabled || ["1", "true", "yes"].includes(disabled)) return null;
|
|
90815
|
+
if (notAnNpmInstall() !== null) return null;
|
|
90816
|
+
const now3 = Date.now();
|
|
90817
|
+
if (now3 - lastChecked() < CHECK_INTERVAL_MS) return null;
|
|
90818
|
+
try {
|
|
90819
|
+
writeFileSync12(stateFile(), `${dumps({ checkedAt: now3 })}
|
|
90820
|
+
`);
|
|
90821
|
+
} catch {
|
|
90822
|
+
return null;
|
|
90823
|
+
}
|
|
90824
|
+
const latest = await latestRelease(signal);
|
|
90825
|
+
if (signal.aborted || latest === null || compareReleases(latest, VERSION) <= 0) return null;
|
|
90826
|
+
const status3 = await runNpm(latest, true);
|
|
90827
|
+
if (status3 === 0) return { message: `Updated hx to ${latest}. Restart hx to use it.`, ok: true };
|
|
90828
|
+
const why = status3 === null ? "npm could not be started" : `npm exited ${status3}`;
|
|
90829
|
+
return {
|
|
90830
|
+
message: `Could not update hx to ${latest}: ${why}. Run hx update to see why.`,
|
|
90831
|
+
ok: false
|
|
90832
|
+
};
|
|
90833
|
+
}
|
|
90834
|
+
|
|
90683
90835
|
// src/cli.ts
|
|
90684
90836
|
var USAGE = `hx - an agent harness for the terminal
|
|
90685
90837
|
|
|
@@ -90695,6 +90847,7 @@ Usage:
|
|
|
90695
90847
|
hx auth [set|clear] Show or change the OpenRouter API key
|
|
90696
90848
|
hx docs [SECTION|--all] Print the manual, or one section of it
|
|
90697
90849
|
hx changelog [VERSION] Print what shipped in each version
|
|
90850
|
+
hx update Update hx to the newest release
|
|
90698
90851
|
hx --version Show version
|
|
90699
90852
|
hx --help Show this message
|
|
90700
90853
|
|
|
@@ -90758,6 +90911,9 @@ ${USAGE}`, process.stderr);
|
|
|
90758
90911
|
return runMcpCommand(parsed.rest);
|
|
90759
90912
|
case "trace":
|
|
90760
90913
|
return runTraceCommand(parsed);
|
|
90914
|
+
case "update":
|
|
90915
|
+
case "upgrade":
|
|
90916
|
+
return runUpdate(print);
|
|
90761
90917
|
default:
|
|
90762
90918
|
return runTuiCommand(parsed);
|
|
90763
90919
|
}
|
|
@@ -90816,7 +90972,7 @@ function parseArgs(args) {
|
|
|
90816
90972
|
}
|
|
90817
90973
|
if (positional.length) {
|
|
90818
90974
|
const [head4, ...tail3] = positional;
|
|
90819
|
-
if (["resume", "prompt", "trace", "mcp", "auth"].includes(head4)) {
|
|
90975
|
+
if (["resume", "prompt", "trace", "mcp", "auth", "update", "upgrade"].includes(head4)) {
|
|
90820
90976
|
parsed.command = head4;
|
|
90821
90977
|
parsed.rest = tail3;
|
|
90822
90978
|
if (head4 === "resume" && tail3.length) parsed.sessionId = tail3[0];
|
|
@@ -91505,7 +91661,9 @@ async function runTuiCommand(parsed) {
|
|
|
91505
91661
|
if (moved) runtime2.notices.push(moved);
|
|
91506
91662
|
const signIn2 = runtime2.provider instanceof DeferredProvider ? signInHint(runtime2.loop.model, runtime2.auth) : null;
|
|
91507
91663
|
let relaunch = null;
|
|
91664
|
+
const sessionOver = new AbortController();
|
|
91508
91665
|
try {
|
|
91666
|
+
const update = autoUpdate(runtime2.settings.autoUpdates, sessionOver.signal);
|
|
91509
91667
|
await runtime2.connectMcp();
|
|
91510
91668
|
await runtime2.refreshModelsIfStale();
|
|
91511
91669
|
relaunch = await runSession(runtime2.loop, runtime2.bus, runtime2.settings, {
|
|
@@ -91519,9 +91677,11 @@ async function runTuiCommand(parsed) {
|
|
|
91519
91677
|
notices: runtime2.notices,
|
|
91520
91678
|
checkpoints: runtime2.checkpoints,
|
|
91521
91679
|
renameLeft,
|
|
91522
|
-
signIn: signIn2
|
|
91680
|
+
signIn: signIn2,
|
|
91681
|
+
update
|
|
91523
91682
|
});
|
|
91524
91683
|
} finally {
|
|
91684
|
+
sessionOver.abort();
|
|
91525
91685
|
runtime2.bus.close();
|
|
91526
91686
|
await runtime2.aclose();
|
|
91527
91687
|
}
|