@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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
4
|
-
|
|
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,
|
|
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(`..${
|
|
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(
|
|
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) =>
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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("~/") ?
|
|
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 :
|
|
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 :
|
|
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
|
-
|
|
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 =
|
|
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-
|
|
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
|
+
"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.
|
|
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",
|