@datalabrotterdam/nova-ai-cli 1.2.0-alpha.1 → 1.2.0-alpha.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/CHANGELOG.md ADDED
@@ -0,0 +1,84 @@
1
+ # [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.3](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.2...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.3) (2026-10-04)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **agent:** never overwrite an unreadable settings file when switching a skill ([ca989ef](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/ca989ef1810fc903cea069cdadf3041fe164e364))
7
+ * **cli:** install the command as nova-ai only ([eda8064](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/eda806411af1738ff6f6b1404d23d4e84a74ced8))
8
+
9
+
10
+ ### Features
11
+
12
+ * **agent,cli:** Nova skill folders, switching skills off, a budgeted skill list ([0891c79](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/0891c79248d3a645d638f12f46928a54652b0941))
13
+ * **cli:** update notice from npm for every channel, cached daily ([c3a0014](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/c3a001473336aac14d216cdeec0bedde4df34427))
14
+
15
+ # [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.2](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.1...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.2) (2026-10-03)
16
+
17
+
18
+ ### Features
19
+
20
+ * release the agent separately as @datalabrotterdam/nova-ai-agent ([32ecfc0](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/32ecfc0faa99bd5bb80cb2eaf2363abc050978f0))
21
+
22
+ # [1.2.0-alpha.1](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/v1.1.0...v1.2.0-alpha.1) (2026-10-03)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **acp:** bounded, cancellable client terminals in the workspace ([81f7099](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/81f70996d9dfc8d78e12282def1582ea859f2784))
28
+ * **acp:** isolate background prompt jobs from live session history ([8328a8b](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/8328a8bfa1f22871ef36ec38247987e6effcd044))
29
+ * **acp:** replay loaded sessions faithfully; page session/list ([d576171](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/d5761710890f7b2f5d1b340cdce7faafcd2e3561))
30
+ * **env:** no shells or Docker probes when a session starts ([dc3a449](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/dc3a4495f394c1a16e48e282efd76b765af09999))
31
+ * **mcp:** connect servers in parallel with timeouts and diagnostics ([db4c62c](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/db4c62cd9c8ec0f197ea1ae1ce76adcf9d397ed5))
32
+ * **models:** offer and default to chat models only ([5d3ade2](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/5d3ade2727110ca7555bfdfa5b174c9e75b151f9))
33
+ * **security:** harden sessions, setup auth, credentials, processes and file access ([7f02a1d](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/7f02a1d203128555c4d00e5ab4dc4786329b6225))
34
+ * **tui:** keep printed lines inside the terminal width ([dd5cd0a](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/dd5cd0a40592af392eca797399ce56c0e672110e))
35
+ * **tui:** redraw cleanly after the terminal width changes ([f0b2ec6](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/f0b2ec6d01b0bd8ebcf087330c8bfd82072aa08d))
36
+
37
+
38
+ ### Features
39
+
40
+ * **acp:** agentInfo and env_var authentication in initialize ([b736e11](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/b736e115542b8fef083f9fc75514586327d3ac49))
41
+ * **acp:** namespace Nova extensions under _nova/ ([bf56cb9](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/bf56cb9e093dc90fc43b44008f1ddd86446a8cd0))
42
+ * **acp:** serialized turns, cancel, shutdown and proper error codes ([6688470](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/66884701af129ff2c1e9ad78b9e572d7ec64cc4b))
43
+ * **acp:** the agent owns permissions; clients only answer its questions ([7db651d](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/7db651d8d4883da2c170a831aeaa2952dc4444ac))
44
+ * **acp:** usage, session info, commands and model options as updates ([21d0816](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/21d08160c558ca9431deff8f8ead25e89a93d21c))
45
+ * added alpha stage on TUI ([3415411](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/3415411986ef4aefc2aa94dbb66fc73710fb6a5d))
46
+ * **cli:** proper entry point, -p/--print, login/logout, help and version ([73c8f94](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/73c8f9472099fa7c3cedccdd3c6a315d809e6a00))
47
+ * **core:** allow multiple tool calls per round ([f6a2171](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/f6a217145efac0722fddc49995808a7ad7bb2508))
48
+ * **core:** compact proactively at 80% of the model context window ([96afcbb](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/96afcbb7b68f4686a9b769bb80a73b00eebfe4a4))
49
+ * **core:** native tool calling with learned per-model text fallback ([6d71193](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/6d7119395b39a45026d676813ad3596382e2611a))
50
+ * **core:** share the ~/.nova-ai project layout with the VS Code extension ([aa75dde](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/aa75dde65218fdbd16b9f329bd849cc9ec608a28))
51
+ * **core:** validate tool args centrally before dispatch ([0a88e3e](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/0a88e3efb0d7c32611979e408b95e659873ec1e5))
52
+ * expand ACP runtime and terminal workflows ([bfbae15](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/bfbae157c301fbc1b7e88f5e267d25c8da126008))
53
+ * **mcp:** surface tool schemas, respect readOnlyHint, hint cwd args ([2b22be3](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/2b22be327b052788936e4badb010844f7ca3d057))
54
+ * **memory:** shared MEMORY.md index + typed notes, compatible with VS Code ([474c916](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/474c91694f4da58400426f39c290ac6888f4aa19))
55
+ * **modes:** plan mode can read, ask and keep the task list ([e253eaa](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/e253eaa66fd02478d61f34e734a27883068bf357))
56
+ * **policy:** agent-side permission policy with trust-gated rule sources ([234cd3a](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/234cd3adf06423c973c52497e5e31f6868d6b19a))
57
+ * **policy:** rule subjects for the extension's tool names ([5c188b6](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/5c188b610ff6e4ba8a715cc2ee10960736493b86))
58
+ * **tools:** add JSON schema validator for tool args ([24764ef](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/24764ef5100c14404f1923f89cc87c3d64de510d))
59
+ * **tools:** declare args schemas on every built-in tool ([79f6283](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/79f62833144f1a6aba9aaa65a2a19f18eb8793ca))
60
+ * **tools:** whitespace-tolerant edit_file with self-correction hints ([a350e14](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/a350e140e92c8469249d4c6297aad8f8161a217f))
61
+ * **tui:** Ink TUI with inline scrollback replaces pi-tui ([0725bae](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/0725bae27790829fd756afbdb5dd20574a1e0f00))
62
+ * **tui:** Shift+Enter inserts a new line where the terminal can report it ([b9c0b70](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/b9c0b7041057e2bdeb1675bbfa33a12fc3a664b9))
63
+
64
+ # [1.1.0](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/v1.0.1...v1.1.0) (2026-06-23)
65
+
66
+
67
+ ### Features
68
+
69
+ * **acp-web:** theme support, favicon, font, and dep bumps ([0576402](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/0576402b6888a2cf6bc93123d733c4163358c63f))
70
+ * **acp:** add MCP server support (stdio/http/sse) ([ba98b0c](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/ba98b0c0b2e2461e8553331cafa1985e7673ee43))
71
+
72
+ ## [1.0.1](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/v1.0.0...v1.0.1) (2026-06-22)
73
+
74
+
75
+ ### Bug Fixes
76
+
77
+ * correct npm scope to [@datalabrotterdam](https://github.com/datalabrotterdam) ([6ab6bf8](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/6ab6bf88506cc1d4b778c1a30b123cd532a0a8de))
78
+
79
+ # 1.0.0 (2026-06-22)
80
+
81
+
82
+ ### Features
83
+
84
+ * add ACP agent, tool calling, and alpha release channel ([15d19a7](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/15d19a7998b4651ba9ad279fc2607006ea14ed49))
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="./assets/logo.png" width="96" height="96" alt="Nova logo" />
3
+ <img src="https://raw.githubusercontent.com/Datalab-Rotterdam/nova-ai-cli/main/assets/logo.png" width="96" height="96" alt="Nova logo" />
4
4
 
5
5
  # @datalabrotterdam/nova-ai-cli
6
6
 
@@ -12,7 +12,7 @@
12
12
  [![ACP](https://img.shields.io/badge/ACP-agent-blue?style=flat-square)](https://agentclientprotocol.com)
13
13
  [![issues](https://img.shields.io/github/issues/Datalab-Rotterdam/nova-ai-cli?style=flat-square)](https://github.com/Datalab-Rotterdam/nova-ai-cli/issues)
14
14
 
15
- [npm](https://www.npmjs.com/package/@datalabrotterdam/nova-ai-cli) · [Issues](https://github.com/Datalab-Rotterdam/nova-ai-cli/issues) · [ACP reference](./docs/ACP.md) · [Settings and memory](./docs/NOVA_HOME.md)
15
+ [npm](https://www.npmjs.com/package/@datalabrotterdam/nova-ai-cli) · [Issues](https://github.com/Datalab-Rotterdam/nova-ai-cli/issues) · [ACP reference](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/docs/ACP.md) · [Settings and memory](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/docs/NOVA_HOME.md)
16
16
 
17
17
  </div>
18
18
 
@@ -44,6 +44,11 @@ nova-ai # start the terminal UI in your project
44
44
  Or without installing: `npx @datalabrotterdam/nova-ai-cli login`, then
45
45
  `npx @datalabrotterdam/nova-ai-cli`.
46
46
 
47
+ When a newer version is on npm, the terminal UI says so, and so do `-p` and
48
+ `--version` (on stderr, only in a terminal). npm is asked at most once a day;
49
+ an alpha install also follows the `alpha` channel. `NOVA_NO_UPDATE_CHECK=1`
50
+ turns it off (it is off in CI).
51
+
47
52
  ```
48
53
  nova-ai [options] Interactive terminal UI
49
54
  nova-ai -p [options] [prompt] Run one request headless (see nova-ai -p --help)
@@ -90,7 +95,7 @@ Slash commands: `/help`, `/model`, `/mode`, `/permission`, `/session`,
90
95
  `/resume`, `/clear`, `/compact`, `/rewind [n]`, `/queue`, `/steer`, `/usage`,
91
96
  `/tools`, `/mcp`, `/skills`, `/shell` and `/agent` (background jobs), `/trust`
92
97
  and `/exit`. Details:
93
- [`src/tui/README.md`](./src/tui/README.md).
98
+ [`src/tui/README.md`](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/packages/cli/src/tui/README.md).
94
99
 
95
100
  ## Headless
96
101
 
@@ -131,8 +136,12 @@ always options, client file system and terminals, form questions, plans, usage
131
136
  and title updates, MCP servers (stdio, HTTP, SSE), image prompts, and three
132
137
  login methods (browser, `NOVA_API_KEY`, or `nova-ai login` in a terminal).
133
138
  Nova-specific features (prompt queue, rewind, compaction, context usage,
134
- background jobs) are `_nova/…` extension methods. Everything is documented in
135
- [docs/ACP.md](./docs/ACP.md).
139
+ per-session settings, background jobs) are `_nova/…` extension methods. The agent is also published
140
+ on its own as
141
+ [`@datalabrotterdam/nova-ai-agent`](https://www.npmjs.com/package/@datalabrotterdam/nova-ai-agent)
142
+ (command `nova-ai-agent`), without the terminal UI, for integrations that only
143
+ need ACP. Everything is documented in
144
+ [docs/ACP.md](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/docs/ACP.md).
136
145
 
137
146
  ## Security model
138
147
 
@@ -149,6 +158,9 @@ background jobs) are `_nova/…` extension methods. Everything is documented in
149
158
  private `~/.nova-ai/projects/<key>/settings.json`, outside the repository.
150
159
  Compound commands (`a && b`, pipes) are checked part by part; substitutions
151
160
  and nested shells need an exact rule.
161
+ - **Web pages are fetched only with approval.** `fetch_url` asks every time
162
+ unless a rule allows that URL (a URL can carry data out), and a redirect to
163
+ another site needs its own approval.
152
164
  - **File access stays in the workspace** (symlinks resolved); commands run in
153
165
  their own process group with a timeout, and the whole tree is stopped on
154
166
  cancel.
@@ -168,11 +180,14 @@ VS Code extension: settings, per-project trust, sessions and memory.
168
180
  - **Instructions**: `NOVA.md` and `AGENTS.md` in the workspace.
169
181
  - **Memory**: a `MEMORY.md` index plus notes, globally and per project,
170
182
  through the `memory_read` and `memory_write` tools (writes need approval).
171
- - **Skills**: `SKILL.md` files under `.agents/skills`, `.claude/skills` or
172
- `.codex/skills`, in the workspace or your home folder.
183
+ - **Skills**: `SKILL.md` files under `.nova-ai/skills` in the workspace or
184
+ `~/.nova-ai/skills` for every project (`.agents`, `.claude` and `.codex`
185
+ skills folders are read too). `/skills` lists them; `/skills off <name>`
186
+ switches one off in this project (`--global`: everywhere). Only names and
187
+ short descriptions go into the prompt; Nova loads a skill when it fits.
173
188
  - **MCP servers**: `.mcp.json` or `mcpServers` in `.nova-ai/settings.json`.
174
189
 
175
- The complete layout and rule syntax: [docs/NOVA_HOME.md](./docs/NOVA_HOME.md).
190
+ The complete layout and rule syntax: [docs/NOVA_HOME.md](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/docs/NOVA_HOME.md).
176
191
 
177
192
  ## Models
178
193
 
@@ -191,26 +206,13 @@ hand).
191
206
  | `NOVA_TOOL_PROTOCOL` | Force `native` or `text` tool calling |
192
207
  | `NOVA_NES_MODEL` | Separate model for next-edit suggestions (ACP) |
193
208
  | `NOVA_KITTY_KEYBOARD` | `1`/`0`: force the kitty keyboard protocol on or off |
209
+ | `NOVA_NO_UPDATE_CHECK` | Don't ask npm for a newer version (also `NO_UPDATE_NOTIFIER`, or `CI`) |
194
210
 
195
211
  ## Development
196
212
 
197
- ```sh
198
- npm ci
199
- npm test # type check, unit tests (node:test) and the real-terminal (PTY) scenario
200
- npm run build # the login page and the CLI bundle in dist/
201
- npm run dev # run from source with tsx
202
- ```
203
-
204
- Tests never touch your `~/.nova-ai` or the real Nova API: they run with a
205
- throwaway home folder and point the client at a local port. The ACP
206
- conformance test starts the real `--acp` process against a fake gateway. CI
207
- runs the suite on Linux, macOS and Windows with Node 22 and 24 (the PTY
208
- scenario on Linux and macOS). Releases are cut by `semantic-release` from
209
- `main` and `alpha`.
210
-
211
- Planned: OIDC login against the Nova platform instead of a pasted API key,
212
- and Remote Control (driving a running session from the Nova web UI or mobile
213
- app). The standalone browser UI (`--web`) is experimental.
213
+ This package lives in the [nova-ai-cli repository](https://github.com/Datalab-Rotterdam/nova-ai-cli)
214
+ next to the agent package it depends on; see the repository README for
215
+ building and testing.
214
216
 
215
217
  ## License
216
218
 
@@ -0,0 +1,147 @@
1
+ import {
2
+ CLI_PACKAGE_NAME,
3
+ cliVersion
4
+ } from "./chunk-FMHWW3E6.js";
5
+
6
+ // src/update-check.ts
7
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
8
+ import { dirname, join } from "path";
9
+ import { novaHomeRoot } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
10
+ var DIST_TAGS_URL = `https://registry.npmjs.org/-/package/${CLI_PACKAGE_NAME.replace("/", "%2F")}/dist-tags`;
11
+ var DEFAULT_TIMEOUT_MS = 3e3;
12
+ var CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
13
+ function updateCachePath() {
14
+ return join(novaHomeRoot(), "update-check.json");
15
+ }
16
+ function updateChecksDisabled(env = process.env) {
17
+ return Boolean(env.NOVA_NO_UPDATE_CHECK || env.NO_UPDATE_NOTIFIER || env.CI);
18
+ }
19
+ async function checkForUpdate(options = {}) {
20
+ try {
21
+ if (updateChecksDisabled(options.env)) return null;
22
+ const currentVersion = options.currentVersion ?? cliVersion();
23
+ if (!currentVersion) return null;
24
+ const distTags = await readDistTags(options);
25
+ if (!distTags) return null;
26
+ return chooseUpdate(currentVersion, distTags);
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function chooseUpdate(currentVersion, distTags) {
32
+ const channel = prereleaseChannel(currentVersion);
33
+ const tags = channel && distTags[channel] ? [channel, "latest"] : ["latest"];
34
+ let best = null;
35
+ for (const tag of tags) {
36
+ const version = distTags[tag];
37
+ if (!version || !isNewerVersion(version, currentVersion)) continue;
38
+ if (!best || isNewerVersion(version, best.version)) best = { tag, version };
39
+ }
40
+ if (!best) return null;
41
+ return {
42
+ currentVersion,
43
+ latestVersion: best.version,
44
+ command: `npm install -g ${CLI_PACKAGE_NAME}@${best.tag}`
45
+ };
46
+ }
47
+ async function readDistTags(options) {
48
+ const now = (options.now ?? Date.now)();
49
+ const cachePath = options.cachePath === void 0 ? updateCachePath() : options.cachePath;
50
+ const cached = cachePath ? readCache(cachePath) : null;
51
+ if (cached && now - cached.checkedAt >= 0 && now - cached.checkedAt < CACHE_MAX_AGE_MS) {
52
+ return cached.distTags;
53
+ }
54
+ const response = await (options.fetchImpl ?? fetch)(DIST_TAGS_URL, {
55
+ headers: { accept: "application/json" },
56
+ signal: AbortSignal.any([
57
+ AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
58
+ ...options.signal ? [options.signal] : []
59
+ ])
60
+ });
61
+ if (!response.ok) return null;
62
+ const payload = await response.json();
63
+ if (!isDistTags(payload)) return null;
64
+ if (cachePath) writeCache(cachePath, { checkedAt: now, distTags: payload });
65
+ return payload;
66
+ }
67
+ function readCache(path) {
68
+ try {
69
+ const value = JSON.parse(readFileSync(path, "utf8"));
70
+ if (value && typeof value === "object" && typeof value.checkedAt === "number" && isDistTags(value.distTags)) {
71
+ return value;
72
+ }
73
+ } catch {
74
+ }
75
+ return null;
76
+ }
77
+ function writeCache(path, value) {
78
+ try {
79
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
80
+ writeFileSync(path, `${JSON.stringify(value)}
81
+ `, { mode: 384 });
82
+ } catch {
83
+ }
84
+ }
85
+ function isDistTags(value) {
86
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((version) => typeof version === "string");
87
+ }
88
+ function prereleaseChannel(version) {
89
+ const parsed = parseVersion(version);
90
+ const first = parsed?.prerelease[0];
91
+ return first && !/^\d+$/.test(first) ? first : null;
92
+ }
93
+ function readInstalledVersion() {
94
+ return cliVersion();
95
+ }
96
+ function isNewerVersion(candidate, current) {
97
+ const next = parseVersion(candidate);
98
+ const installed = parseVersion(current);
99
+ if (!next || !installed) return false;
100
+ for (let index = 0; index < 3; index++) {
101
+ const difference = next.core[index] - installed.core[index];
102
+ if (difference !== 0) return difference > 0;
103
+ }
104
+ return comparePrerelease(next.prerelease, installed.prerelease) > 0;
105
+ }
106
+ function parseVersion(value) {
107
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
108
+ if (!match) return null;
109
+ return {
110
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
111
+ prerelease: match[4]?.split(".") ?? []
112
+ };
113
+ }
114
+ function comparePrerelease(left, right) {
115
+ if (left.length === 0 || right.length === 0) {
116
+ if (left.length === right.length) return 0;
117
+ return left.length === 0 ? 1 : -1;
118
+ }
119
+ const length = Math.max(left.length, right.length);
120
+ for (let index = 0; index < length; index++) {
121
+ const leftPart = left[index];
122
+ const rightPart = right[index];
123
+ if (leftPart === void 0 || rightPart === void 0) {
124
+ return leftPart === rightPart ? 0 : leftPart === void 0 ? -1 : 1;
125
+ }
126
+ if (leftPart === rightPart) continue;
127
+ const leftNumber = /^\d+$/.test(leftPart) ? Number(leftPart) : null;
128
+ const rightNumber = /^\d+$/.test(rightPart) ? Number(rightPart) : null;
129
+ if (leftNumber !== null && rightNumber !== null) return leftNumber - rightNumber;
130
+ if (leftNumber !== null || rightNumber !== null) return leftNumber !== null ? -1 : 1;
131
+ return leftPart < rightPart ? -1 : 1;
132
+ }
133
+ return 0;
134
+ }
135
+ function formatUpdateNotice(update) {
136
+ return `Update available: nova-ai ${update.currentVersion} \u2192 ${update.latestVersion}. Run: ${update.command}`;
137
+ }
138
+
139
+ export {
140
+ updateCachePath,
141
+ updateChecksDisabled,
142
+ checkForUpdate,
143
+ chooseUpdate,
144
+ readInstalledVersion,
145
+ isNewerVersion,
146
+ formatUpdateNotice
147
+ };
@@ -0,0 +1,11 @@
1
+ // src/version.ts
2
+ import { readPackageVersion } from "@datalabrotterdam/nova-ai-agent/core/version.js";
3
+ var CLI_PACKAGE_NAME = "@datalabrotterdam/nova-ai-cli";
4
+ function cliVersion() {
5
+ return readPackageVersion(CLI_PACKAGE_NAME, import.meta.url);
6
+ }
7
+
8
+ export {
9
+ CLI_PACKAGE_NAME,
10
+ cliVersion
11
+ };
@@ -1,135 +1,8 @@
1
- import {
2
- NOVA_METHODS,
3
- NOVA_NOTIFICATIONS,
4
- NovaAgent,
5
- createAgentApp,
6
- fromElicitationRequest,
7
- isInteractionMode,
8
- readSettings,
9
- resolveWorkspaceFile,
10
- toElicitationResponse
11
- } from "./chunk-GZZ7PY3S.js";
12
- import {
13
- isWorkspaceTrusted
14
- } from "./chunk-JAEXUFAS.js";
15
-
16
- // src/client/in-process.ts
17
- import * as acp from "@agentclientprotocol/sdk";
18
-
19
- // src/client/nova-agent-client.ts
20
- var NovaAgentClient = class {
21
- constructor(connection) {
22
- this.connection = connection;
23
- }
24
- connection;
25
- initialize(params) {
26
- return this.connection.initialize(params);
27
- }
28
- newSession(params) {
29
- return this.connection.newSession(params);
30
- }
31
- loadSession(params) {
32
- return this.connection.loadSession(params);
33
- }
34
- resumeSession(params) {
35
- return this.connection.resumeSession(params);
36
- }
37
- closeSession(params) {
38
- return this.connection.closeSession(params);
39
- }
40
- prompt(params) {
41
- return this.connection.prompt(params);
42
- }
43
- cancel(params) {
44
- return this.connection.cancel(params);
45
- }
46
- setSessionMode(params) {
47
- return this.connection.setSessionMode(params);
48
- }
49
- setSessionConfigOption(params) {
50
- return this.connection.setSessionConfigOption(params);
51
- }
52
- queueEnqueue(params) {
53
- return this.ext(NOVA_METHODS.queueEnqueue, params);
54
- }
55
- queueList(params) {
56
- return this.ext(NOVA_METHODS.queueList, params);
57
- }
58
- queueEditBegin(params) {
59
- return this.ext(NOVA_METHODS.queueEditBegin, params);
60
- }
61
- queueUpdate(params) {
62
- return this.ext(NOVA_METHODS.queueUpdate, params);
63
- }
64
- queueRemove(params) {
65
- return this.ext(NOVA_METHODS.queueRemove, params);
66
- }
67
- queueClear(params) {
68
- return this.ext(NOVA_METHODS.queueClear, params);
69
- }
70
- queueTakeNext(params) {
71
- return this.ext(NOVA_METHODS.queueTakeNext, params);
72
- }
73
- sessionCompact(params) {
74
- return this.ext(
75
- NOVA_METHODS.sessionCompact,
76
- params
77
- );
78
- }
79
- sessionContextUsage(params) {
80
- return this.ext(NOVA_METHODS.sessionContextUsage, params);
81
- }
82
- sessionRewind(params) {
83
- return this.ext(NOVA_METHODS.sessionRewind, params);
84
- }
85
- backgroundStartTerminal(params) {
86
- return this.ext(NOVA_METHODS.backgroundStartTerminal, params);
87
- }
88
- backgroundStartPrompt(params) {
89
- return this.ext(NOVA_METHODS.backgroundStartPrompt, params);
90
- }
91
- backgroundList(params) {
92
- return this.ext(NOVA_METHODS.backgroundList, params);
93
- }
94
- backgroundOutput(params) {
95
- return this.ext(NOVA_METHODS.backgroundOutput, params);
96
- }
97
- backgroundKill(params) {
98
- return this.ext(NOVA_METHODS.backgroundKill, params);
99
- }
100
- backgroundRelease(params) {
101
- return this.ext(NOVA_METHODS.backgroundRelease, params);
102
- }
103
- async ext(method, params) {
104
- return await this.connection.extMethod(method, params);
105
- }
106
- };
107
-
108
- // src/client/in-process.ts
109
- function startInProcessAgent(toClient, agent = new NovaAgent()) {
110
- const toAgent = new TransformStream();
111
- const toClientStream = new TransformStream();
112
- const agentConnection = createAgentApp(agent).connect(
113
- acp.ndJsonStream(toClientStream.writable, toAgent.readable)
114
- );
115
- const connection = new acp.ClientSideConnection(
116
- toClient,
117
- acp.ndJsonStream(toAgent.writable, toClientStream.readable)
118
- );
119
- return {
120
- agent,
121
- client: new NovaAgentClient(connection),
122
- async close() {
123
- await agent.shutdown().catch(() => {
124
- });
125
- agentConnection.close();
126
- }
127
- };
128
- }
129
-
130
1
  // src/tui/settings/workspace-mcp.ts
131
2
  import { readFileSync as readFileSync2 } from "fs";
132
3
  import { join as join2 } from "path";
4
+ import { isWorkspaceTrusted } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
5
+ import { readSettings } from "@datalabrotterdam/nova-ai-agent/core/policy/settings.js";
133
6
 
134
7
  // src/tui/settings/workspace-settings.ts
135
8
  import { readFileSync } from "fs";
@@ -270,93 +143,21 @@ function errorMessage(error, fallback) {
270
143
  }
271
144
 
272
145
  // src/tui/session/tui-acp-client.ts
146
+ import { NOVA_NOTIFICATIONS } from "@datalabrotterdam/nova-ai-agent/acp/extensions.js";
273
147
  import { readFile, writeFile, mkdir } from "fs/promises";
274
148
  import { dirname } from "path";
275
-
276
- // src/core/process-tree.ts
277
- import { spawn, spawnSync } from "child_process";
278
- var KILL_GRACE_MS = 1e3;
279
- var liveChildren = /* @__PURE__ */ new Set();
280
- var exitHookInstalled = false;
281
- function spawnCommand(command, options) {
282
- installExitHook();
283
- const common = {
284
- cwd: options.cwd,
285
- env: options.env ?? process.env,
286
- detached: process.platform !== "win32",
287
- windowsHide: true,
288
- stdio: ["ignore", "pipe", "pipe"]
289
- };
290
- const child = options.args?.length ? spawn(command, options.args, common) : spawn(command, { ...common, shell: true });
291
- liveChildren.add(child);
292
- const forget = () => liveChildren.delete(child);
293
- child.once("exit", forget);
294
- child.once("error", forget);
295
- return child;
296
- }
297
- function killProcessTree(child, signal = "SIGTERM") {
298
- if (child.pid === void 0) return;
299
- try {
300
- if (process.platform === "win32") {
301
- spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
302
- stdio: "ignore",
303
- windowsHide: true
304
- }).on("error", () => {
305
- });
306
- } else {
307
- process.kill(-child.pid, signal);
308
- }
309
- } catch {
310
- try {
311
- child.kill(signal);
312
- } catch {
313
- }
314
- }
315
- }
316
- function terminateProcessTree(child, graceMs = KILL_GRACE_MS) {
317
- if (hasExited(child)) return Promise.resolve();
318
- return new Promise((resolve) => {
319
- let forceTimer;
320
- let giveUpTimer;
321
- const done = () => {
322
- clearTimeout(forceTimer);
323
- clearTimeout(giveUpTimer);
324
- resolve();
325
- };
326
- child.once("exit", done);
327
- killProcessTree(child, "SIGTERM");
328
- forceTimer = setTimeout(() => {
329
- killProcessTree(child, "SIGKILL");
330
- giveUpTimer = setTimeout(done, graceMs);
331
- }, graceMs);
332
- });
333
- }
334
- function hasExited(child) {
335
- return child.exitCode !== null || child.signalCode !== null;
336
- }
337
- function installExitHook() {
338
- if (exitHookInstalled) return;
339
- exitHookInstalled = true;
340
- process.once("exit", () => {
341
- for (const child of liveChildren) {
342
- if (process.platform === "win32" && child.pid !== void 0) {
343
- try {
344
- spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
345
- stdio: "ignore",
346
- windowsHide: true,
347
- timeout: 2e3
348
- });
349
- } catch {
350
- }
351
- } else {
352
- killProcessTree(child, "SIGKILL");
353
- }
354
- }
355
- liveChildren.clear();
356
- });
357
- }
358
-
359
- // src/tui/session/tui-acp-client.ts
149
+ import {
150
+ spawnCommand,
151
+ terminateProcessTree
152
+ } from "@datalabrotterdam/nova-ai-agent/core/process-tree.js";
153
+ import { resolveWorkspaceFile } from "@datalabrotterdam/nova-ai-agent/core/tools/workspace-paths.js";
154
+ import {
155
+ isInteractionMode
156
+ } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
157
+ import {
158
+ fromElicitationRequest,
159
+ toElicitationResponse
160
+ } from "@datalabrotterdam/nova-ai-agent/acp/user-questions.js";
360
161
  var DEFAULT_OUTPUT_LIMIT = 1e5;
361
162
  var nextMessageId = 0;
362
163
  function uid() {
@@ -945,7 +746,6 @@ function createStore(initial) {
945
746
  }
946
747
 
947
748
  export {
948
- startInProcessAgent,
949
749
  readWorkspaceMcpConfiguration,
950
750
  workspaceNeedsTrust,
951
751
  TuiAcpClient,
@@ -2,42 +2,18 @@ import {
2
2
  TuiAcpClient,
3
3
  choosePermissionOption,
4
4
  createStore,
5
- readWorkspaceMcpConfiguration,
6
- startInProcessAgent
7
- } from "./chunk-ZKRZIF3S.js";
8
- import {
9
- NOVA_NOTIFICATIONS,
10
- NovaAgent,
11
- listStoredSessions,
12
- loadStoredSession
13
- } from "./chunk-GZZ7PY3S.js";
14
- import "./chunk-Q7WL3DXP.js";
15
- import {
16
- setWorkspaceTrusted
17
- } from "./chunk-JAEXUFAS.js";
5
+ readWorkspaceMcpConfiguration
6
+ } from "./chunk-NHGIUEIR.js";
18
7
 
19
8
  // src/headless/index.ts
20
9
  import { resolve } from "path";
21
- import * as acp2 from "@agentclientprotocol/sdk";
22
-
23
- // src/client/context-client.ts
10
+ import { NOVA_NOTIFICATIONS } from "@datalabrotterdam/nova-ai-agent/acp/extensions.js";
24
11
  import * as acp from "@agentclientprotocol/sdk";
25
- function clientFromContext(context) {
26
- const request = (method, params) => context.request(method, params);
27
- return {
28
- sessionUpdate: (params) => context.notify(acp.methods.client.session.update, params),
29
- requestPermission: (params) => request(acp.methods.client.session.requestPermission, params),
30
- readTextFile: (params) => request(acp.methods.client.fs.readTextFile, params),
31
- writeTextFile: async (params) => await request(acp.methods.client.fs.writeTextFile, params) ?? {},
32
- createTerminal: (params) => request(acp.methods.client.terminal.create, params),
33
- terminalOutput: (params) => request(acp.methods.client.terminal.output, params),
34
- waitForTerminalExit: (params) => request(acp.methods.client.terminal.waitForExit, params),
35
- killTerminal: async (params) => await request(acp.methods.client.terminal.kill, params) ?? {},
36
- releaseTerminal: async (params) => await request(acp.methods.client.terminal.release, params) ?? {},
37
- unstable_createElicitation: (params) => request(acp.methods.client.elicitation.create, params),
38
- extNotification: (method, params) => context.notify(method, params)
39
- };
40
- }
12
+ import { NovaAgent } from "@datalabrotterdam/nova-ai-agent/acp/agent.js";
13
+ import { clientFromContext } from "@datalabrotterdam/nova-ai-agent/client/context-client.js";
14
+ import { startInProcessAgent } from "@datalabrotterdam/nova-ai-agent/client/in-process.js";
15
+ import { setWorkspaceTrusted } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
16
+ import { listStoredSessions, loadStoredSession } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
41
17
 
42
18
  // src/headless/client.ts
43
19
  var HeadlessAcpClient = class {
@@ -235,7 +211,7 @@ ${HEADLESS_HELP}
235
211
  process.once("SIGINT", onInterrupt);
236
212
  try {
237
213
  await agent.initialize({
238
- protocolVersion: acp2.PROTOCOL_VERSION,
214
+ protocolVersion: acp.PROTOCOL_VERSION,
239
215
  clientCapabilities: client.capabilities,
240
216
  clientInfo: { name: "nova-ai-cli-headless", version: "1" }
241
217
  });