@datalabrotterdam/nova-ai-cli 1.2.0-alpha.3 → 1.2.0-alpha.4

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,3 +1,15 @@
1
+ # [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.4](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.3...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.4) (2026-10-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **cli:** depend on the agent version the CLI was released with ([4931439](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/4931439d106063b81e24367d6aa9c3f285f4ad15))
7
+
8
+
9
+ ### Features
10
+
11
+ * **cli:** offer npm updates at start-up, remind until installed, /update ([18defca](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/18defcaea988698f9d0909419bb2aa1fdedfa5a9))
12
+
1
13
  # [@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
14
 
3
15
 
package/README.md CHANGED
@@ -44,10 +44,17 @@ 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).
47
+ When a newer version is on npm, `nova-ai` asks before the terminal UI opens:
48
+ Enter installs it (`npm install -g`) and restarts, `n` asks again tomorrow, `s`
49
+ skips that version. Until it is installed, the status line keeps reminding you,
50
+ and `/update` installs it and restarts in the same session. `-p` and
51
+ `--version` print a notice (on stderr, only in a terminal). npm is asked at
52
+ most once a day; an alpha install also follows the `alpha` channel.
53
+ `NOVA_NO_UPDATE_CHECK=1` turns it off (it is off in CI).
54
+
55
+ nova-ai installs updates itself only when it was installed with
56
+ `npm install -g`; with npx, another package manager or a project dependency it
57
+ shows the command to run instead.
51
58
 
52
59
  ```
53
60
  nova-ai [options] Interactive terminal UI
@@ -41,6 +41,7 @@ function chooseUpdate(currentVersion, distTags) {
41
41
  return {
42
42
  currentVersion,
43
43
  latestVersion: best.version,
44
+ tag: best.tag,
44
45
  command: `npm install -g ${CLI_PACKAGE_NAME}@${best.tag}`
45
46
  };
46
47
  }
@@ -61,7 +62,7 @@ async function readDistTags(options) {
61
62
  if (!response.ok) return null;
62
63
  const payload = await response.json();
63
64
  if (!isDistTags(payload)) return null;
64
- if (cachePath) writeCache(cachePath, { checkedAt: now, distTags: payload });
65
+ if (cachePath) writeCache(cachePath, { ...cached, checkedAt: now, distTags: payload });
65
66
  return payload;
66
67
  }
67
68
  function readCache(path) {
@@ -74,6 +75,26 @@ function readCache(path) {
74
75
  }
75
76
  return null;
76
77
  }
78
+ var SNOOZE_MS = 24 * 60 * 60 * 1e3;
79
+ function shouldAskAboutUpdate(update, options = {}) {
80
+ const cachePath = options.cachePath === void 0 ? updateCachePath() : options.cachePath;
81
+ const cached = cachePath ? readCache(cachePath) : null;
82
+ if (!cached) return true;
83
+ if (cached.skippedVersion === update.latestVersion) return false;
84
+ const now = (options.now ?? Date.now)();
85
+ return !(typeof cached.snoozedUntil === "number" && now < cached.snoozedUntil);
86
+ }
87
+ function rememberUpdateAnswer(answer, update, options = {}) {
88
+ const cachePath = options.cachePath === void 0 ? updateCachePath() : options.cachePath;
89
+ if (!cachePath) return;
90
+ const cached = readCache(cachePath);
91
+ const base = cached ?? { checkedAt: 0, distTags: {} };
92
+ const now = (options.now ?? Date.now)();
93
+ writeCache(
94
+ cachePath,
95
+ answer === "skip" ? { ...base, skippedVersion: update.latestVersion, snoozedUntil: void 0 } : { ...base, snoozedUntil: now + SNOOZE_MS }
96
+ );
97
+ }
77
98
  function writeCache(path, value) {
78
99
  try {
79
100
  mkdirSync(dirname(path), { recursive: true, mode: 448 });
@@ -141,6 +162,8 @@ export {
141
162
  updateChecksDisabled,
142
163
  checkForUpdate,
143
164
  chooseUpdate,
165
+ shouldAskAboutUpdate,
166
+ rememberUpdateAnswer,
144
167
  readInstalledVersion,
145
168
  isNewerVersion,
146
169
  formatUpdateNotice
package/dist/index.js CHANGED
@@ -89,7 +89,7 @@ function parseTuiArgs(args) {
89
89
  function startUpdateCheck() {
90
90
  if (!process.stderr.isTTY) return null;
91
91
  const abort = new AbortController();
92
- const result = import("./update-check-OX6WAPNN.js").then(({ checkForUpdate }) => checkForUpdate({ signal: abort.signal })).catch(() => null);
92
+ const result = import("./update-check-G3PYCRWM.js").then(({ checkForUpdate }) => checkForUpdate({ signal: abort.signal })).catch(() => null);
93
93
  return { result, cancel: () => abort.abort() };
94
94
  }
95
95
  async function noticeUpdate(check, waitMs) {
@@ -103,7 +103,7 @@ async function noticeUpdate(check, waitMs) {
103
103
  clearTimeout(timer);
104
104
  check.cancel();
105
105
  if (!update) return;
106
- const { formatUpdateNotice } = await import("./update-check-OX6WAPNN.js");
106
+ const { formatUpdateNotice } = await import("./update-check-G3PYCRWM.js");
107
107
  process.stderr.write(`
108
108
  ${formatUpdateNotice(update)}
109
109
  `);
@@ -175,7 +175,7 @@ ${MAIN_HELP}
175
175
  );
176
176
  return 2;
177
177
  }
178
- const { default: runChat } = await import("./tui-LNFSHL4Y.js");
178
+ const { default: runChat } = await import("./tui-OWU4YGTT.js");
179
179
  return runChat(options);
180
180
  }
181
181
  }
@@ -1,7 +1,12 @@
1
1
  import {
2
- checkForUpdate
3
- } from "./chunk-37PMUZ34.js";
4
- import "./chunk-FMHWW3E6.js";
2
+ checkForUpdate,
3
+ rememberUpdateAnswer,
4
+ shouldAskAboutUpdate
5
+ } from "./chunk-L77KQRGZ.js";
6
+ import {
7
+ CLI_PACKAGE_NAME,
8
+ cliVersion
9
+ } from "./chunk-FMHWW3E6.js";
5
10
  import {
6
11
  TuiAcpClient,
7
12
  createStore,
@@ -10,11 +15,105 @@ import {
10
15
  } from "./chunk-NHGIUEIR.js";
11
16
 
12
17
  // src/tui/index.ts
13
- import { createInterface } from "readline/promises";
18
+ import { createInterface as createInterface2 } from "readline/promises";
14
19
  import { readCredentials } from "@datalabrotterdam/nova-ai-agent/core/credentials.js";
15
- import { listStoredSessions as listStoredSessions2 } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
20
+ import { listStoredSessions as listStoredSessions2, loadStoredSession as loadStoredSession2 } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
16
21
  import { setWorkspaceTrusted as setWorkspaceTrusted2 } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
17
22
 
23
+ // src/update-install.ts
24
+ import { spawnSync } from "child_process";
25
+ import { existsSync, realpathSync } from "fs";
26
+ import { dirname, join, sep } from "path";
27
+ import { createInterface } from "readline/promises";
28
+ var START_WAIT_MS = 1500;
29
+ var UPDATED_FROM_ENV = "NOVA_UPDATED_FROM";
30
+ function globalNpmPrefix(options = {}) {
31
+ const script = options.scriptPath ?? process.argv[1];
32
+ if (!script) return null;
33
+ const windows = (options.platform ?? process.platform) === "win32";
34
+ const marker = `${sep}node_modules${sep}${CLI_PACKAGE_NAME.split("/").join(sep)}${sep}`;
35
+ const resolved = realPath(script);
36
+ const at = resolved.lastIndexOf(marker);
37
+ if (at < 0) return null;
38
+ const nodeModulesParent = resolved.slice(0, at);
39
+ const prefix = windows ? nodeModulesParent : dirname(nodeModulesParent);
40
+ if (!windows && nodeModulesParent !== join(prefix, "lib")) return null;
41
+ return existsSync(join(prefix, windows ? "nova-ai.cmd" : join("bin", "nova-ai"))) ? prefix : null;
42
+ }
43
+ async function withInstallability(update, options = {}) {
44
+ if (!update) return null;
45
+ return { ...update, installable: globalNpmPrefix(options) !== null };
46
+ }
47
+ function npmInvocation(args) {
48
+ return process.platform === "win32" ? [`npm ${args.map((arg) => /\s/.test(arg) ? `"${arg}"` : arg).join(" ")}`, [], true] : ["npm", args, false];
49
+ }
50
+ function realPath(path) {
51
+ try {
52
+ return realpathSync(path);
53
+ } catch {
54
+ return path;
55
+ }
56
+ }
57
+ function parseUpdateAnswer(answer) {
58
+ const value = answer.trim().toLowerCase();
59
+ if (value === "" || value === "y" || value === "yes") return "update";
60
+ if (value === "s" || value === "skip") return "skip";
61
+ return "later";
62
+ }
63
+ async function askToUpdate(update) {
64
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
65
+ try {
66
+ console.log(`
67
+ Update available: nova-ai ${update.currentVersion} \u2192 ${update.latestVersion}`);
68
+ return parseUpdateAnswer(
69
+ await prompt.question("Update now? [Y/n/s] (n: not today, s: skip this version) ")
70
+ );
71
+ } finally {
72
+ prompt.close();
73
+ }
74
+ }
75
+ async function offerUpdateAtStart(check, restartArgs) {
76
+ let timer;
77
+ const update = await Promise.race([
78
+ check,
79
+ new Promise((resolve2) => {
80
+ timer = setTimeout(() => resolve2(null), START_WAIT_MS);
81
+ })
82
+ ]);
83
+ clearTimeout(timer);
84
+ if (!update?.installable || !shouldAskAboutUpdate(update)) return null;
85
+ const answer = await askToUpdate(update);
86
+ if (answer !== "update") {
87
+ rememberUpdateAnswer(answer, update);
88
+ console.log(
89
+ answer === "skip" ? `Skipping ${update.latestVersion}; you'll be asked again for a newer version.` : "Not now. /update installs it any time."
90
+ );
91
+ return null;
92
+ }
93
+ return installAndRestart(update, restartArgs);
94
+ }
95
+ function installAndRestart(update, restartArgs) {
96
+ const prefix = globalNpmPrefix();
97
+ if (!prefix) return null;
98
+ console.log(`
99
+ Installing nova-ai ${update.latestVersion}\u2026`);
100
+ const [file, args, shell] = npmInvocation(["install", "-g", "--prefix", prefix, `${CLI_PACKAGE_NAME}@${update.tag}`]);
101
+ const install = spawnSync(file, args, { stdio: "inherit", shell });
102
+ if (install.status !== 0) {
103
+ console.error(`
104
+ The update did not install. Run it yourself: ${update.command}
105
+ `);
106
+ return null;
107
+ }
108
+ const script = process.argv[1];
109
+ if (!script) return null;
110
+ const restarted = spawnSync(process.execPath, [...process.execArgv, script, ...restartArgs], {
111
+ stdio: "inherit",
112
+ env: { ...process.env, [UPDATED_FROM_ENV]: update.currentVersion }
113
+ });
114
+ return restarted.status ?? 1;
115
+ }
116
+
18
117
  // src/tui/ink/app.tsx
19
118
  import { Box as Box6, render, Static, Text as Text6, useBoxMetrics, useWindowSize } from "ink";
20
119
  import { useEffect, useRef, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
@@ -46,7 +145,7 @@ import {
46
145
 
47
146
  // src/tui/files/file-mentions.ts
48
147
  import { statSync } from "fs";
49
- import { isAbsolute, relative, resolve, sep } from "path";
148
+ import { isAbsolute, relative, resolve, sep as sep2 } from "path";
50
149
  var MENTION_RE = /@(?:"([^"]+)"|'([^']+)'|([^\s]+))/g;
51
150
  var TRAILING_PUNCTUATION = /[.,;:!?\)\]\}]+$/;
52
151
  function findWorkspaceFileMentions(text, cwd) {
@@ -56,10 +155,10 @@ function findWorkspaceFileMentions(text, cwd) {
56
155
  const raw = match[1] ?? match[2] ?? match[3] ?? "";
57
156
  const candidates = [raw, raw.replace(TRAILING_PUNCTUATION, "")].filter((value, index, all) => value && all.indexOf(value) === index);
58
157
  for (const candidate of candidates) {
59
- const normalized = candidate.replace(/[\\/]/g, sep);
158
+ const normalized = candidate.replace(/[\\/]/g, sep2);
60
159
  const absolutePath = resolve(workspaceRoot, normalized);
61
160
  const relativePath = relative(workspaceRoot, absolutePath);
62
- if (relativePath.startsWith(`..${sep}`) || relativePath === ".." || isAbsolute(relativePath)) continue;
161
+ if (relativePath.startsWith(`..${sep2}`) || relativePath === ".." || isAbsolute(relativePath)) continue;
63
162
  try {
64
163
  if (!statSync(absolutePath).isFile()) continue;
65
164
  } catch {
@@ -67,7 +166,7 @@ function findWorkspaceFileMentions(text, cwd) {
67
166
  }
68
167
  mentions.set(absolutePath.toLowerCase(), {
69
168
  absolutePath,
70
- relativePath: relativePath.split(sep).join("/")
169
+ relativePath: relativePath.split(sep2).join("/")
71
170
  });
72
171
  break;
73
172
  }
@@ -1389,7 +1488,7 @@ function StatusLine({
1389
1488
  const separator = /* @__PURE__ */ jsx2(Text2, { color: palette.faint, children: " | " });
1390
1489
  const usage = ui.contextUsage ? `ctx:${formatTokenCount(ui.contextUsage.totalTokens)}${ui.contextUsage.contextWindow ? `/${formatTokenCount(ui.contextUsage.contextWindow)}` : ""}` : null;
1391
1490
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
1392
- ui.updateAvailable ? /* @__PURE__ */ jsx2(Text2, { color: palette.warning, children: `Update ${ui.updateAvailable.latestVersion} available (current ${ui.updateAvailable.currentVersion}). Run: ${ui.updateAvailable.command}` }) : null,
1491
+ ui.updateAvailable ? /* @__PURE__ */ jsx2(Text2, { color: palette.warning, children: `Update ${ui.updateAvailable.latestVersion} available (current ${ui.updateAvailable.currentVersion}). ${ui.updateAvailable.installable ? "Type /update to install it." : `Run: ${ui.updateAvailable.command}`}` }) : null,
1393
1492
  ui.statusLine ? /* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: ui.statusLine }) : null,
1394
1493
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
1395
1494
  /* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: model }),
@@ -1819,6 +1918,14 @@ var builtinCommands = [
1819
1918
  ctx.exit();
1820
1919
  }
1821
1920
  },
1921
+ {
1922
+ name: "update",
1923
+ description: "Install the newer nova-ai from npm and restart in this session",
1924
+ run(ctx) {
1925
+ const message = ctx.requestUpdate();
1926
+ if (message) ctx.print(message);
1927
+ }
1928
+ },
1822
1929
  {
1823
1930
  name: "mode",
1824
1931
  description: "Switch interaction mode (/mode agent|ask|plan)",
@@ -2198,10 +2305,10 @@ console.log(ObjC.unwrap(png.base64EncodedStringWithOptions(0)));
2198
2305
  `;
2199
2306
 
2200
2307
  // src/tui/files/find-fd.ts
2201
- import { spawnSync } from "child_process";
2308
+ import { spawnSync as spawnSync2 } from "child_process";
2202
2309
  var FD_CANDIDATES = ["fd", "fdfind"];
2203
2310
  var cachedPath;
2204
- function resolveFdPath(run = (command, args) => spawnSync(command, args, { stdio: "ignore" })) {
2311
+ function resolveFdPath(run = (command, args) => spawnSync2(command, args, { stdio: "ignore" })) {
2205
2312
  if (cachedPath !== void 0) return cachedPath;
2206
2313
  for (const candidate of FD_CANDIDATES) {
2207
2314
  const result = run(candidate, ["--version"]);
@@ -2216,7 +2323,7 @@ function resolveFdPath(run = (command, args) => spawnSync(command, args, { stdio
2216
2323
 
2217
2324
  // src/tui/files/list-workspace-files.ts
2218
2325
  import { readdirSync } from "fs";
2219
- import { join, relative as relative2, sep as sep2 } from "path";
2326
+ import { join as join2, relative as relative2, sep as sep3 } from "path";
2220
2327
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".nova-ai", "out", "coverage", ".next", ".turbo"]);
2221
2328
  function listWorkspaceFiles(cwd, max = 2e3) {
2222
2329
  const results = [];
@@ -2232,12 +2339,12 @@ function listWorkspaceFiles(cwd, max = 2e3) {
2232
2339
  for (const entry of entries) {
2233
2340
  if (results.length >= max) break;
2234
2341
  if (entry.isSymbolicLink()) continue;
2235
- const full = join(dir, entry.name);
2342
+ const full = join2(dir, entry.name);
2236
2343
  if (entry.isDirectory()) {
2237
2344
  if (SKIP_DIRS.has(entry.name)) continue;
2238
2345
  stack.push(full);
2239
2346
  } else if (entry.isFile()) {
2240
- results.push(relative2(cwd, full).split(sep2).join("/"));
2347
+ results.push(relative2(cwd, full).split(sep3).join("/"));
2241
2348
  }
2242
2349
  }
2243
2350
  }
@@ -2268,7 +2375,7 @@ var DraftImageAttachments = class {
2268
2375
  import { spawn } from "child_process";
2269
2376
  import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2270
2377
  import { homedir } from "os";
2271
- import { basename, dirname, join as join2 } from "path";
2378
+ import { basename, dirname as dirname2, join as join3 } from "path";
2272
2379
  var PATH_DELIMITERS = /* @__PURE__ */ new Set([" ", " ", '"', "'", "="]);
2273
2380
  var MAX_SUGGESTIONS2 = 20;
2274
2381
  var WorkspaceAutocompleteProvider = class {
@@ -2349,14 +2456,14 @@ var WorkspaceAutocompleteProvider = class {
2349
2456
  pathSuggestions(prefix) {
2350
2457
  const quoted = prefix.startsWith('"');
2351
2458
  const raw = quoted ? prefix.slice(1) : prefix;
2352
- const expanded = raw === "~" || raw.startsWith("~/") ? join2(homedir(), raw.slice(1)) : raw;
2459
+ const expanded = raw === "~" || raw.startsWith("~/") ? join3(homedir(), raw.slice(1)) : raw;
2353
2460
  const absolute = expanded.startsWith("/") || /^[A-Za-z]:[\\/]/.test(expanded);
2354
2461
  const separator = Math.max(raw.lastIndexOf("/"), raw.lastIndexOf("\\"));
2355
2462
  const listsDirectory = raw === "" || raw === "~" || separator === raw.length - 1;
2356
- const directoryPart = listsDirectory ? expanded : dirname(expanded);
2463
+ const directoryPart = listsDirectory ? expanded : dirname2(expanded);
2357
2464
  const namePrefix = listsDirectory ? "" : basename(expanded);
2358
2465
  const displayDirectory = raw === "~" ? "~/" : separator >= 0 ? raw.slice(0, separator + 1) : "";
2359
- const searchDirectory = absolute ? directoryPart : join2(this.cwd, directoryPart);
2466
+ const searchDirectory = absolute ? directoryPart : join3(this.cwd, directoryPart);
2360
2467
  let entries;
2361
2468
  try {
2362
2469
  entries = readdirSync2(searchDirectory, { withFileTypes: true });
@@ -2370,7 +2477,7 @@ var WorkspaceAutocompleteProvider = class {
2370
2477
  if (!isDirectory && entry.isSymbolicLink()) {
2371
2478
  try {
2372
2479
  isDirectory = statSync2(
2373
- join2(searchDirectory, entry.name)
2480
+ join3(searchDirectory, entry.name)
2374
2481
  ).isDirectory();
2375
2482
  } catch {
2376
2483
  }
@@ -3739,6 +3846,8 @@ var TuiController = class {
3739
3846
  unsubscribeStore;
3740
3847
  resolveExit = () => {
3741
3848
  };
3849
+ /** Set by /update: the caller installs and restarts after the TUI exits. */
3850
+ updateRequested = false;
3742
3851
  /** Settles once the user asked to quit; `run` then tears the view down. */
3743
3852
  exitRequested = new Promise((resolve2) => {
3744
3853
  this.resolveExit = resolve2;
@@ -4063,6 +4172,19 @@ ${skill.path}`
4063
4172
  clear: () => void this.runner.startNewSession(),
4064
4173
  newSession: () => this.runner.startNewSession(),
4065
4174
  exit: () => this.requestExit(),
4175
+ requestUpdate: () => {
4176
+ const update = this.store.getState().updateAvailable;
4177
+ if (!update) return "No newer nova-ai found on npm.";
4178
+ if (!update.installable) {
4179
+ return `nova-ai ${update.latestVersion} is available, but this copy wasn't installed with npm -g, so it can't update itself. Run: ${update.command}`;
4180
+ }
4181
+ if (this.store.getState().busy) {
4182
+ return "Wait for the current request to finish (or press Esc), then run /update again.";
4183
+ }
4184
+ this.updateRequested = true;
4185
+ this.requestExit();
4186
+ return null;
4187
+ },
4066
4188
  resumeSession: (sessionId) => {
4067
4189
  const id = sessionId ?? listSessionsForCwd(this.runner.cwd)[0]?.sessionId;
4068
4190
  return id ? this.runner.resumeFrom(id) : false;
@@ -4639,6 +4761,7 @@ async function runInkTui(credentials, cwd, args, options = {}) {
4639
4761
  }
4640
4762
  stdout.write(`Resume this session with: nova-ai --resume ${runner.sessionId}
4641
4763
  `);
4764
+ return { sessionId: runner.sessionId, updateRequested: controller.updateRequested };
4642
4765
  }
4643
4766
  function App({ controller }) {
4644
4767
  const view = useSyncExternalStore2(controller.subscribe, controller.getView);
@@ -4737,6 +4860,15 @@ async function runChat(options) {
4737
4860
  return 1;
4738
4861
  }
4739
4862
  const credentials = options.model ? { ...stored, defaultModel: options.model } : stored;
4863
+ const updateCheck = checkForUpdate().then((update) => withInstallability(update));
4864
+ const updatedFrom = process.env[UPDATED_FROM_ENV];
4865
+ if (updatedFrom) {
4866
+ delete process.env[UPDATED_FROM_ENV];
4867
+ console.log(`Updated nova-ai ${updatedFrom} \u2192 ${cliVersion() ?? "?"}.`);
4868
+ } else {
4869
+ const restartedCode = await offerUpdateAtStart(updateCheck, process.argv.slice(2));
4870
+ if (restartedCode !== null) return restartedCode;
4871
+ }
4740
4872
  const cwd = process.cwd();
4741
4873
  let resume = options.resume;
4742
4874
  if (options.continueLast) {
@@ -4748,11 +4880,22 @@ async function runChat(options) {
4748
4880
  }
4749
4881
  if (workspaceNeedsTrust(cwd)) await askToTrust(cwd);
4750
4882
  const args = resume ? ["--resume", resume] : [];
4751
- await runInkTui(credentials, cwd, args);
4883
+ const result = await runInkTui(credentials, cwd, args, { checkForUpdate: () => updateCheck });
4884
+ if (result.updateRequested) {
4885
+ const update = await updateCheck;
4886
+ if (update) {
4887
+ const restartArgs = [
4888
+ ...loadStoredSession2(result.sessionId) ? ["--resume", result.sessionId] : [],
4889
+ ...options.model ? ["--model", options.model] : []
4890
+ ];
4891
+ const restartedCode = installAndRestart(update, restartArgs);
4892
+ if (restartedCode !== null) return restartedCode;
4893
+ }
4894
+ }
4752
4895
  return 0;
4753
4896
  }
4754
4897
  async function askToTrust(cwd) {
4755
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
4898
+ const prompt = createInterface2({ input: process.stdin, output: process.stdout });
4756
4899
  try {
4757
4900
  console.log(`
4758
4901
  ${cwd}`);
@@ -4,9 +4,11 @@ import {
4
4
  formatUpdateNotice,
5
5
  isNewerVersion,
6
6
  readInstalledVersion,
7
+ rememberUpdateAnswer,
8
+ shouldAskAboutUpdate,
7
9
  updateCachePath,
8
10
  updateChecksDisabled
9
- } from "./chunk-37PMUZ34.js";
11
+ } from "./chunk-L77KQRGZ.js";
10
12
  import "./chunk-FMHWW3E6.js";
11
13
  export {
12
14
  checkForUpdate,
@@ -14,6 +16,8 @@ export {
14
16
  formatUpdateNotice,
15
17
  isNewerVersion,
16
18
  readInstalledVersion,
19
+ rememberUpdateAnswer,
20
+ shouldAskAboutUpdate,
17
21
  updateCachePath,
18
22
  updateChecksDisabled
19
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalabrotterdam/nova-ai-cli",
3
- "version": "1.2.0-alpha.3",
3
+ "version": "1.2.0-alpha.4",
4
4
  "description": "Agentic coding with DataLab Rotterdam's Nova AI in your terminal: interactive TUI, headless mode, and an ACP agent for editors",
5
5
  "license": "Apache-2.0",
6
6
  "author": "DataLab Rotterdam",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@agentclientprotocol/sdk": "^0.29.0",
42
- "@datalabrotterdam/nova-ai-agent": "^1.0.0-alpha.1",
42
+ "@datalabrotterdam/nova-ai-agent": "^1.0.0-alpha.2",
43
43
  "@datalabrotterdam/nova-sdk": "^1.3.2",
44
44
  "chalk": "^5.3.0",
45
45
  "cli-highlight": "^2.1.11",