@datalabrotterdam/nova-ai-cli 1.2.0-alpha.4 → 1.2.0-alpha.6
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,28 @@
|
|
|
1
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.6](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.5...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.6) (2026-10-23)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **cli:** start the session before the first prompt ([0c26456](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/0c26456b8126e3b42a6de2e38212213ae5380e62))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **agent:** set workspace trust over ACP ([cec4a89](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/cec4a8949a4232f46448219e4d5363289651215d))
|
|
12
|
+
|
|
13
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.5](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.4...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.5) (2026-10-23)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **cli:** keep the full-screen frame one row short of the terminal ([37355f4](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/37355f4efb8a237f204c061ad7008faf54a0dd9e))
|
|
19
|
+
* **cli:** pick the account's default model when none is set ([fb0f34c](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/fb0f34c7bac26f1b59c4b6b2245e6fb8365f99f1))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* **cli:** full-screen TUI with a pinned prompt and mouse-wheel scrolling ([a68d693](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/a68d6930945bd6440de22c709fc14ae32a9c9f65))
|
|
25
|
+
|
|
1
26
|
# [@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
27
|
|
|
3
28
|
|
package/README.md
CHANGED
|
@@ -71,9 +71,11 @@ Interactive options:
|
|
|
71
71
|
|
|
72
72
|
## Terminal UI
|
|
73
73
|
|
|
74
|
-
The UI
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
The UI runs full screen (the terminal's alternate screen, like `less` or
|
|
75
|
+
`vim`) and gives your shell its screen back on exit. The mouse wheel or
|
|
76
|
+
PgUp/PgDn scroll the transcript, Esc jumps back to the newest output, and
|
|
77
|
+
while you read an answer the prompt that asked for it stays pinned on the top
|
|
78
|
+
row. To select text, hold Shift (Option in some macOS terminals) while dragging.
|
|
77
79
|
|
|
78
80
|
- **Modes** (Shift+Tab): `agent` does the work; `plan` reads the code, asks
|
|
79
81
|
questions and builds a task list but changes nothing; `ask` only answers.
|
|
@@ -1,147 +1,3 @@
|
|
|
1
|
-
// src/tui/settings/workspace-mcp.ts
|
|
2
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
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";
|
|
6
|
-
|
|
7
|
-
// src/tui/settings/workspace-settings.ts
|
|
8
|
-
import { readFileSync } from "fs";
|
|
9
|
-
import { join } from "path";
|
|
10
|
-
function settingsPath(cwd) {
|
|
11
|
-
return join(cwd, ".nova-ai", "settings.json");
|
|
12
|
-
}
|
|
13
|
-
function readWorkspaceSettings(cwd) {
|
|
14
|
-
try {
|
|
15
|
-
const raw = readFileSync(settingsPath(cwd), "utf8");
|
|
16
|
-
return JSON.parse(raw);
|
|
17
|
-
} catch {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// src/tui/settings/workspace-mcp.ts
|
|
23
|
-
var UNTRUSTED_MCP_MESSAGE = "Not started: MCP servers declared by this workspace only run once you trust it (/trust in the TUI, --trust-workspace headless).";
|
|
24
|
-
function readWorkspaceMcpConfiguration(cwd, options = {}) {
|
|
25
|
-
const configured = readWorkspaceSettings(cwd).mcpServers ?? [];
|
|
26
|
-
const project = readProjectMcpConfiguration(cwd);
|
|
27
|
-
const servers = new Map(configured.map((server) => [server.name, server]));
|
|
28
|
-
for (const server of project.servers) servers.set(server.name, server);
|
|
29
|
-
const declared = [...servers.values()];
|
|
30
|
-
if (declared.length && !(options.trusted ?? isWorkspaceTrusted(cwd))) {
|
|
31
|
-
return {
|
|
32
|
-
servers: [],
|
|
33
|
-
failures: [
|
|
34
|
-
...project.failures,
|
|
35
|
-
...declared.map((server) => ({ serverName: server.name, message: UNTRUSTED_MCP_MESSAGE }))
|
|
36
|
-
],
|
|
37
|
-
untrusted: declared.map((server) => server.name)
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return { servers: declared, failures: project.failures };
|
|
41
|
-
}
|
|
42
|
-
function workspaceNeedsTrust(cwd) {
|
|
43
|
-
if (isWorkspaceTrusted(cwd)) return false;
|
|
44
|
-
if (readWorkspaceMcpConfiguration(cwd, { trusted: true }).servers.length) return true;
|
|
45
|
-
return loadWorkspaceAllowRules(cwd).length > 0;
|
|
46
|
-
}
|
|
47
|
-
function loadWorkspaceAllowRules(cwd) {
|
|
48
|
-
return ["settings.json", "settings.local.json"].flatMap((name) => {
|
|
49
|
-
const allow = readSettings(join2(cwd, ".nova-ai", name))?.permissions?.allow;
|
|
50
|
-
return Array.isArray(allow) ? allow : [];
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
function readProjectMcpConfiguration(cwd) {
|
|
54
|
-
const path = join2(cwd, ".mcp.json");
|
|
55
|
-
let raw;
|
|
56
|
-
try {
|
|
57
|
-
raw = readFileSync2(path, "utf8");
|
|
58
|
-
} catch (error) {
|
|
59
|
-
if (error.code === "ENOENT") return { servers: [], failures: [] };
|
|
60
|
-
return { servers: [], failures: [{ serverName: ".mcp.json", message: errorMessage(error, "Could not read file") }] };
|
|
61
|
-
}
|
|
62
|
-
let document;
|
|
63
|
-
try {
|
|
64
|
-
document = JSON.parse(raw);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
return { servers: [], failures: [{ serverName: ".mcp.json", message: errorMessage(error, "Invalid JSON") }] };
|
|
67
|
-
}
|
|
68
|
-
const root = asRecord(document);
|
|
69
|
-
const definitions = root ? asRecord(root.mcpServers) : null;
|
|
70
|
-
if (!definitions) {
|
|
71
|
-
return {
|
|
72
|
-
servers: [],
|
|
73
|
-
failures: [{ serverName: ".mcp.json", message: 'Expected an "mcpServers" object.' }]
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
const servers = [];
|
|
77
|
-
const failures = [];
|
|
78
|
-
for (const [name, value] of Object.entries(definitions)) {
|
|
79
|
-
const config = asRecord(value);
|
|
80
|
-
if (config?.disabled === true || config?.enabled === false) continue;
|
|
81
|
-
try {
|
|
82
|
-
servers.push(normalizeProjectServer(name, config));
|
|
83
|
-
} catch (error) {
|
|
84
|
-
failures.push({ serverName: `.mcp.json:${name}`, message: errorMessage(error, "Invalid server configuration") });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return { servers, failures };
|
|
88
|
-
}
|
|
89
|
-
function normalizeProjectServer(name, config) {
|
|
90
|
-
if (!config) throw new Error("Server definition must be an object.");
|
|
91
|
-
const type = typeof config.type === "string" ? config.type : config.url ? "http" : "stdio";
|
|
92
|
-
if (type === "http" || type === "sse") {
|
|
93
|
-
if (typeof config.url !== "string" || !config.url) throw new Error(`${type} server requires a URL.`);
|
|
94
|
-
try {
|
|
95
|
-
new URL(config.url);
|
|
96
|
-
} catch {
|
|
97
|
-
throw new Error(`${type} server requires a valid URL.`);
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
name,
|
|
101
|
-
type,
|
|
102
|
-
url: config.url,
|
|
103
|
-
headers: normalizePairs(config.headers, "headers")
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
if (type !== "stdio") throw new Error(`Unsupported transport type: ${type}.`);
|
|
107
|
-
if (typeof config.command !== "string" || !config.command) throw new Error("stdio server requires a command.");
|
|
108
|
-
if (config.args !== void 0 && (!Array.isArray(config.args) || !config.args.every((arg) => typeof arg === "string"))) {
|
|
109
|
-
throw new Error("args must be an array of strings.");
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
name,
|
|
113
|
-
command: config.command,
|
|
114
|
-
args: config.args ?? [],
|
|
115
|
-
env: normalizePairs(config.env, "env")
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function normalizePairs(value, label) {
|
|
119
|
-
if (value === void 0) return [];
|
|
120
|
-
if (Array.isArray(value)) {
|
|
121
|
-
if (!value.every((entry) => {
|
|
122
|
-
const pair = asRecord(entry);
|
|
123
|
-
return typeof pair?.name === "string" && typeof pair.value === "string";
|
|
124
|
-
})) {
|
|
125
|
-
throw new Error(`${label} must contain name/value strings.`);
|
|
126
|
-
}
|
|
127
|
-
return value.map((entry) => {
|
|
128
|
-
const pair = entry;
|
|
129
|
-
return { name: pair.name, value: pair.value };
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
const record = asRecord(value);
|
|
133
|
-
if (!record || !Object.values(record).every((entry) => typeof entry === "string")) {
|
|
134
|
-
throw new Error(`${label} must be an object of string values.`);
|
|
135
|
-
}
|
|
136
|
-
return Object.entries(record).map(([name, entry]) => ({ name, value: entry }));
|
|
137
|
-
}
|
|
138
|
-
function asRecord(value) {
|
|
139
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
140
|
-
}
|
|
141
|
-
function errorMessage(error, fallback) {
|
|
142
|
-
return error instanceof Error && error.message ? `${fallback}: ${error.message}` : fallback;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
1
|
// src/tui/session/tui-acp-client.ts
|
|
146
2
|
import { NOVA_NOTIFICATIONS } from "@datalabrotterdam/nova-ai-agent/acp/extensions.js";
|
|
147
3
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -746,8 +602,6 @@ function createStore(initial) {
|
|
|
746
602
|
}
|
|
747
603
|
|
|
748
604
|
export {
|
|
749
|
-
readWorkspaceMcpConfiguration,
|
|
750
|
-
workspaceNeedsTrust,
|
|
751
605
|
TuiAcpClient,
|
|
752
606
|
choosePermissionOption,
|
|
753
607
|
createStore
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TuiAcpClient,
|
|
3
3
|
choosePermissionOption,
|
|
4
|
-
createStore
|
|
5
|
-
|
|
6
|
-
} from "./chunk-NHGIUEIR.js";
|
|
4
|
+
createStore
|
|
5
|
+
} from "./chunk-QSNU4H55.js";
|
|
7
6
|
|
|
8
7
|
// src/headless/index.ts
|
|
9
8
|
import { resolve } from "path";
|
|
@@ -12,7 +11,6 @@ import * as acp from "@agentclientprotocol/sdk";
|
|
|
12
11
|
import { NovaAgent } from "@datalabrotterdam/nova-ai-agent/acp/agent.js";
|
|
13
12
|
import { clientFromContext } from "@datalabrotterdam/nova-ai-agent/client/context-client.js";
|
|
14
13
|
import { startInProcessAgent } from "@datalabrotterdam/nova-ai-agent/client/in-process.js";
|
|
15
|
-
import { setWorkspaceTrusted } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
|
|
16
14
|
import { listStoredSessions, loadStoredSession } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
|
|
17
15
|
|
|
18
16
|
// src/headless/client.ts
|
|
@@ -191,15 +189,19 @@ ${HEADLESS_HELP}
|
|
|
191
189
|
dependencies.agent ?? new NovaAgent()
|
|
192
190
|
);
|
|
193
191
|
const agent = connection.client;
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
serverName
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
192
|
+
const sessionMeta = { "nova-ai-cli/workspaceMcp": options.mcp };
|
|
193
|
+
const reportMcpFailures = (meta) => {
|
|
194
|
+
const failures = toRecord(toRecord(meta)?.["nova-ai-cli/mcp"])?.failures;
|
|
195
|
+
for (const failure of Array.isArray(failures) ? failures : []) {
|
|
196
|
+
const item = toRecord(failure);
|
|
197
|
+
if (typeof item?.serverName !== "string" || typeof item.message !== "string") continue;
|
|
198
|
+
output.write({
|
|
199
|
+
type: "mcp.configuration_error",
|
|
200
|
+
serverName: item.serverName,
|
|
201
|
+
message: item.message
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
203
205
|
let sessionId = null;
|
|
204
206
|
let interrupted = false;
|
|
205
207
|
const onInterrupt = () => {
|
|
@@ -215,18 +217,23 @@ ${HEADLESS_HELP}
|
|
|
215
217
|
clientCapabilities: client.capabilities,
|
|
216
218
|
clientInfo: { name: "nova-ai-cli-headless", version: "1" }
|
|
217
219
|
});
|
|
220
|
+
if (options.trustWorkspace) await agent.workspaceTrust({ cwd: options.cwd, trusted: true });
|
|
218
221
|
if (options.resume) {
|
|
219
|
-
await agent.resumeSession({
|
|
222
|
+
const resumed = await agent.resumeSession({
|
|
220
223
|
sessionId: options.resume,
|
|
221
224
|
cwd: options.cwd,
|
|
222
|
-
mcpServers:
|
|
225
|
+
mcpServers: [],
|
|
226
|
+
_meta: sessionMeta
|
|
223
227
|
});
|
|
228
|
+
reportMcpFailures(resumed._meta);
|
|
224
229
|
sessionId = options.resume;
|
|
225
230
|
} else {
|
|
226
231
|
const created = await agent.newSession({
|
|
227
232
|
cwd: options.cwd,
|
|
228
|
-
mcpServers:
|
|
233
|
+
mcpServers: [],
|
|
234
|
+
_meta: sessionMeta
|
|
229
235
|
});
|
|
236
|
+
reportMcpFailures(created._meta);
|
|
230
237
|
sessionId = created.sessionId;
|
|
231
238
|
}
|
|
232
239
|
await agent.setSessionConfigOption({
|
package/dist/index.js
CHANGED
|
@@ -140,7 +140,7 @@ ${MAIN_HELP}
|
|
|
140
140
|
}
|
|
141
141
|
case "print": {
|
|
142
142
|
const update = startUpdateCheck();
|
|
143
|
-
const { default: runHeadless } = await import("./headless-
|
|
143
|
+
const { default: runHeadless } = await import("./headless-C36E4XSQ.js");
|
|
144
144
|
const code = await runHeadless(command.args);
|
|
145
145
|
if (update) await noticeUpdate(update, 500);
|
|
146
146
|
return code;
|
|
@@ -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-TRADKND7.js");
|
|
179
179
|
return runChat(options);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
@@ -9,16 +9,14 @@ import {
|
|
|
9
9
|
} from "./chunk-FMHWW3E6.js";
|
|
10
10
|
import {
|
|
11
11
|
TuiAcpClient,
|
|
12
|
-
createStore
|
|
13
|
-
|
|
14
|
-
workspaceNeedsTrust
|
|
15
|
-
} from "./chunk-NHGIUEIR.js";
|
|
12
|
+
createStore
|
|
13
|
+
} from "./chunk-QSNU4H55.js";
|
|
16
14
|
|
|
17
15
|
// src/tui/index.ts
|
|
18
16
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
19
17
|
import { readCredentials } from "@datalabrotterdam/nova-ai-agent/core/credentials.js";
|
|
20
18
|
import { listStoredSessions as listStoredSessions2, loadStoredSession as loadStoredSession2 } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
|
|
21
|
-
import { setWorkspaceTrusted
|
|
19
|
+
import { setWorkspaceTrusted } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
|
|
22
20
|
|
|
23
21
|
// src/update-install.ts
|
|
24
22
|
import { spawnSync } from "child_process";
|
|
@@ -115,16 +113,12 @@ The update did not install. Run it yourself: ${update.command}
|
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
// src/tui/ink/app.tsx
|
|
118
|
-
import { Box as Box6, render,
|
|
119
|
-
import { useEffect, useRef, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
116
|
+
import { Box as Box6, render, Text as Text6, useBoxMetrics, useInput as useInput3, useWindowSize } from "ink";
|
|
117
|
+
import { memo, useEffect, useRef, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
120
118
|
import { savedPermissionMode } from "@datalabrotterdam/nova-ai-agent/core/policy/settings.js";
|
|
121
119
|
|
|
122
120
|
// src/tui/session/session-runner.ts
|
|
123
121
|
import * as acp from "@agentclientprotocol/sdk";
|
|
124
|
-
import {
|
|
125
|
-
NovaAIError
|
|
126
|
-
} from "@datalabrotterdam/nova-sdk";
|
|
127
|
-
import { createNovaClient } from "@datalabrotterdam/nova-ai-agent/core/nova-client.js";
|
|
128
122
|
import { NovaAgent } from "@datalabrotterdam/nova-ai-agent/acp/agent.js";
|
|
129
123
|
import { startInProcessAgent } from "@datalabrotterdam/nova-ai-agent/client/in-process.js";
|
|
130
124
|
import { replayHistory } from "@datalabrotterdam/nova-ai-agent/core/history.js";
|
|
@@ -133,15 +127,6 @@ import {
|
|
|
133
127
|
savePermissionMode
|
|
134
128
|
} from "@datalabrotterdam/nova-ai-agent/core/policy/settings.js";
|
|
135
129
|
import { loadStoredSession } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
|
|
136
|
-
import { discoverSkills } from "@datalabrotterdam/nova-ai-agent/core/skills.js";
|
|
137
|
-
import {
|
|
138
|
-
resolveModelContextWindow
|
|
139
|
-
} from "@datalabrotterdam/nova-ai-agent/core/context-compaction.js";
|
|
140
|
-
import {
|
|
141
|
-
chatModels,
|
|
142
|
-
resolveDefaultModel,
|
|
143
|
-
resolveModelSupportsImageInput
|
|
144
|
-
} from "@datalabrotterdam/nova-ai-agent/core/model-capabilities.js";
|
|
145
130
|
|
|
146
131
|
// src/tui/files/file-mentions.ts
|
|
147
132
|
import { statSync } from "fs";
|
|
@@ -286,15 +271,6 @@ var SessionRunner = class {
|
|
|
286
271
|
constructor(store, credentials, cwd) {
|
|
287
272
|
this.store = store;
|
|
288
273
|
this.credentials = credentials;
|
|
289
|
-
this.loadMcpConfiguration(cwd);
|
|
290
|
-
this.skills = discoverSkills(cwd).map(
|
|
291
|
-
({ name, description, source, path }) => ({
|
|
292
|
-
name,
|
|
293
|
-
description,
|
|
294
|
-
source,
|
|
295
|
-
path
|
|
296
|
-
})
|
|
297
|
-
);
|
|
298
274
|
this.cwd = cwd;
|
|
299
275
|
this.acpClient = new TuiAcpClient(
|
|
300
276
|
store,
|
|
@@ -309,13 +285,7 @@ var SessionRunner = class {
|
|
|
309
285
|
clientCapabilities: this.acpClient.capabilities,
|
|
310
286
|
clientInfo: { name: "nova-ai-cli-tui", version: "1" }
|
|
311
287
|
});
|
|
312
|
-
this.
|
|
313
|
-
const model = credentials.defaultModel ?? process.env.NOVA_MODEL;
|
|
314
|
-
if (!model)
|
|
315
|
-
throw new Error(
|
|
316
|
-
"No Nova model configured. Re-run authentication or set NOVA_MODEL."
|
|
317
|
-
);
|
|
318
|
-
this.model = model;
|
|
288
|
+
this.model = credentials.defaultModel ?? process.env.NOVA_MODEL ?? "";
|
|
319
289
|
this.sessionId = crypto.randomUUID();
|
|
320
290
|
this.sessionReady = Promise.resolve();
|
|
321
291
|
}
|
|
@@ -332,15 +302,12 @@ var SessionRunner = class {
|
|
|
332
302
|
connection;
|
|
333
303
|
nova;
|
|
334
304
|
initialized;
|
|
335
|
-
novaClient;
|
|
336
|
-
mcpServers = [];
|
|
337
|
-
mcpConfigurationFailures = [];
|
|
338
305
|
mcpStatus = {
|
|
339
306
|
configured: [],
|
|
340
307
|
connected: [],
|
|
341
308
|
failures: []
|
|
342
309
|
};
|
|
343
|
-
skills;
|
|
310
|
+
skills = [];
|
|
344
311
|
sessionReady;
|
|
345
312
|
agentSessionLoaded = false;
|
|
346
313
|
promptActive = false;
|
|
@@ -349,9 +316,6 @@ var SessionRunner = class {
|
|
|
349
316
|
queuedAttachments = /* @__PURE__ */ new Map();
|
|
350
317
|
editingQueuedMessageId = null;
|
|
351
318
|
imageSupportByModel = /* @__PURE__ */ new Map();
|
|
352
|
-
contextWindow = null;
|
|
353
|
-
contextWindowModel = null;
|
|
354
|
-
contextWindowRequest = 0;
|
|
355
319
|
contextUsageRequest = 0;
|
|
356
320
|
modelCheck = null;
|
|
357
321
|
resumeFrom(storedSessionId) {
|
|
@@ -364,15 +328,7 @@ var SessionRunner = class {
|
|
|
364
328
|
this.sessionId = stored.sessionId;
|
|
365
329
|
this.cwd = stored.cwd;
|
|
366
330
|
this.title = stored.title;
|
|
367
|
-
this.
|
|
368
|
-
this.skills = discoverSkills(stored.cwd).map(
|
|
369
|
-
({ name, description, source, path }) => ({
|
|
370
|
-
name,
|
|
371
|
-
description,
|
|
372
|
-
source,
|
|
373
|
-
path
|
|
374
|
-
})
|
|
375
|
-
);
|
|
331
|
+
this.skills = [];
|
|
376
332
|
const messages = restoreSessionMessages(stored.messages);
|
|
377
333
|
this.store.setState({
|
|
378
334
|
messages,
|
|
@@ -391,7 +347,7 @@ var SessionRunner = class {
|
|
|
391
347
|
return await this.nova.loadSession({
|
|
392
348
|
sessionId: stored.sessionId,
|
|
393
349
|
cwd: stored.cwd,
|
|
394
|
-
mcpServers:
|
|
350
|
+
mcpServers: []
|
|
395
351
|
});
|
|
396
352
|
} finally {
|
|
397
353
|
this.acpClient.muteSessionUpdates = false;
|
|
@@ -516,9 +472,6 @@ var SessionRunner = class {
|
|
|
516
472
|
void this.enqueueQueuedPrompt(value, [], [], "steer", true);
|
|
517
473
|
return true;
|
|
518
474
|
}
|
|
519
|
-
configuredMcpServers() {
|
|
520
|
-
return [...this.mcpServers];
|
|
521
|
-
}
|
|
522
475
|
mcpSessionStatus() {
|
|
523
476
|
return {
|
|
524
477
|
configured: this.mcpStatus.configured.map((server) => ({ ...server })),
|
|
@@ -529,27 +482,29 @@ var SessionRunner = class {
|
|
|
529
482
|
skillSessionStatus() {
|
|
530
483
|
return this.skills.map((skill) => ({ ...skill }));
|
|
531
484
|
}
|
|
485
|
+
async trustWorkspace() {
|
|
486
|
+
await this.initialized;
|
|
487
|
+
await this.nova.workspaceTrust({ cwd: this.cwd, trusted: true });
|
|
488
|
+
}
|
|
489
|
+
/** Every skill of the workspace with its on/off state, from the agent. */
|
|
490
|
+
async listSkills() {
|
|
491
|
+
await this.initialized;
|
|
492
|
+
return (await this.nova.skillsList({ cwd: this.cwd })).skills;
|
|
493
|
+
}
|
|
494
|
+
async setSkillEnabled(name, enabled, global) {
|
|
495
|
+
await this.initialized;
|
|
496
|
+
return (await this.nova.skillsSetEnabled({
|
|
497
|
+
cwd: this.cwd,
|
|
498
|
+
name,
|
|
499
|
+
enabled,
|
|
500
|
+
scope: global ? "global" : "project"
|
|
501
|
+
})).skills;
|
|
502
|
+
}
|
|
532
503
|
contextUsageSnapshot() {
|
|
533
504
|
return this.store.getState().contextUsage;
|
|
534
505
|
}
|
|
535
506
|
async refreshContextUsage() {
|
|
536
507
|
await this.ensureSession();
|
|
537
|
-
const sessionId = this.sessionId;
|
|
538
|
-
const model = this.model;
|
|
539
|
-
if (this.contextWindowModel !== model) {
|
|
540
|
-
const request = ++this.contextWindowRequest;
|
|
541
|
-
const contextWindow = await resolveModelContextWindow(
|
|
542
|
-
this.novaClient,
|
|
543
|
-
model
|
|
544
|
-
);
|
|
545
|
-
if (request === this.contextWindowRequest && model === this.model) {
|
|
546
|
-
this.contextWindow = contextWindow;
|
|
547
|
-
this.contextWindowModel = model;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
if (sessionId !== this.sessionId || !this.agentSessionLoaded) {
|
|
551
|
-
return this.refreshContextUsage();
|
|
552
|
-
}
|
|
553
508
|
const usage = await this.updateContextUsage();
|
|
554
509
|
if (!usage)
|
|
555
510
|
throw new Error(
|
|
@@ -565,13 +520,8 @@ var SessionRunner = class {
|
|
|
565
520
|
}
|
|
566
521
|
setModel(model) {
|
|
567
522
|
this.model = model;
|
|
568
|
-
this.contextWindow = null;
|
|
569
|
-
this.contextWindowModel = null;
|
|
570
|
-
this.contextWindowRequest++;
|
|
571
523
|
saveDefaultModel(model);
|
|
572
524
|
if (this.agentSessionLoaded) void this.updateContextUsage();
|
|
573
|
-
void this.refreshContextUsage().catch(() => {
|
|
574
|
-
});
|
|
575
525
|
}
|
|
576
526
|
/**
|
|
577
527
|
* Hands the mode to the agent's permission policy (which decides) and
|
|
@@ -629,27 +579,30 @@ var SessionRunner = class {
|
|
|
629
579
|
}
|
|
630
580
|
/** Chat models only: embedding, speech and transcription models can't run a session. */
|
|
631
581
|
async listModels() {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}));
|
|
582
|
+
await this.initialized;
|
|
583
|
+
const { models } = await this.nova.modelsList();
|
|
584
|
+
for (const model of models) this.imageSupportByModel.set(model.id, model.imageInput);
|
|
585
|
+
return models.map((model) => ({ id: model.id, name: model.name }));
|
|
637
586
|
}
|
|
638
587
|
/**
|
|
639
|
-
*
|
|
640
|
-
*
|
|
588
|
+
* Takes the account's default model when none is set, and replaces a saved
|
|
589
|
+
* one that cannot chat (older logins saved the first model of the list,
|
|
590
|
+
* e.g. an embedding model); the agent decides, the choice is saved.
|
|
641
591
|
* Runs once; a failed lookup keeps the model and the prompt reports errors.
|
|
642
592
|
*/
|
|
643
593
|
ensureUsableModel() {
|
|
644
594
|
this.modelCheck ??= (async () => {
|
|
645
595
|
try {
|
|
646
|
-
|
|
647
|
-
const
|
|
648
|
-
|
|
596
|
+
await this.initialized;
|
|
597
|
+
const { models, defaultModel } = await this.nova.modelsList(
|
|
598
|
+
this.model ? { preferred: this.model } : {}
|
|
599
|
+
);
|
|
600
|
+
for (const model of models) this.imageSupportByModel.set(model.id, model.imageInput);
|
|
601
|
+
if (!defaultModel || defaultModel === this.model) return;
|
|
649
602
|
const previous = this.model;
|
|
650
|
-
this.setModel(
|
|
603
|
+
this.setModel(defaultModel);
|
|
651
604
|
this.store.setState({
|
|
652
|
-
statusLine: `${previous} is not a chat model; switched to ${
|
|
605
|
+
statusLine: previous ? `${previous} is not a chat model; switched to ${defaultModel} (Ctrl+P to pick another).` : `Using model ${defaultModel} (Ctrl+P switches).`
|
|
653
606
|
});
|
|
654
607
|
} catch {
|
|
655
608
|
}
|
|
@@ -657,14 +610,8 @@ var SessionRunner = class {
|
|
|
657
610
|
return this.modelCheck;
|
|
658
611
|
}
|
|
659
612
|
async supportsImageInput(model = this.model) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const supported = await resolveModelSupportsImageInput(
|
|
663
|
-
this.novaClient,
|
|
664
|
-
model
|
|
665
|
-
);
|
|
666
|
-
this.imageSupportByModel.set(model, supported);
|
|
667
|
-
return supported;
|
|
613
|
+
if (!this.imageSupportByModel.has(model)) await this.listModels();
|
|
614
|
+
return this.imageSupportByModel.get(model) ?? false;
|
|
668
615
|
}
|
|
669
616
|
async compactContext() {
|
|
670
617
|
if (this.store.getState().busy || this.promptActive) {
|
|
@@ -788,7 +735,7 @@ var SessionRunner = class {
|
|
|
788
735
|
});
|
|
789
736
|
}
|
|
790
737
|
} catch (err) {
|
|
791
|
-
const message = err instanceof
|
|
738
|
+
const message = err instanceof Error ? err.message : "Unknown error.";
|
|
792
739
|
pendingToolFailure = `Request failed: ${message}`;
|
|
793
740
|
this.acpClient.appendError(message);
|
|
794
741
|
this.store.setState({ statusLine: "Request failed." });
|
|
@@ -942,11 +889,10 @@ var SessionRunner = class {
|
|
|
942
889
|
];
|
|
943
890
|
}
|
|
944
891
|
async createSession() {
|
|
945
|
-
this.loadMcpConfiguration(this.cwd);
|
|
946
892
|
await this.initialized;
|
|
947
893
|
const { sessionId, _meta } = await this.nova.newSession({
|
|
948
894
|
cwd: this.cwd,
|
|
949
|
-
mcpServers:
|
|
895
|
+
mcpServers: []
|
|
950
896
|
});
|
|
951
897
|
this.sessionId = sessionId;
|
|
952
898
|
this.applyMcpStatus(_meta);
|
|
@@ -970,10 +916,20 @@ var SessionRunner = class {
|
|
|
970
916
|
const value = meta?.["nova-ai-cli/mcp"];
|
|
971
917
|
if (!value || typeof value !== "object") return;
|
|
972
918
|
const record = value;
|
|
919
|
+
const strings = (value2) => Array.isArray(value2) ? value2.filter((entry) => typeof entry === "string") : void 0;
|
|
973
920
|
const configured = Array.isArray(record.configured) ? record.configured.flatMap((entry) => {
|
|
974
921
|
if (!entry || typeof entry !== "object") return [];
|
|
975
922
|
const item = entry;
|
|
976
|
-
return typeof item.name === "string" && typeof item.transport === "string" ? [
|
|
923
|
+
return typeof item.name === "string" && typeof item.transport === "string" ? [
|
|
924
|
+
{
|
|
925
|
+
name: item.name,
|
|
926
|
+
transport: item.transport,
|
|
927
|
+
...typeof item.url === "string" ? { url: item.url } : {},
|
|
928
|
+
...typeof item.command === "string" ? { command: item.command } : {},
|
|
929
|
+
...strings(item.args) ? { args: strings(item.args) } : {},
|
|
930
|
+
...strings(item.env) ? { env: strings(item.env) } : {}
|
|
931
|
+
}
|
|
932
|
+
] : [];
|
|
977
933
|
}) : [];
|
|
978
934
|
const connected = Array.isArray(record.connected) ? record.connected.filter(
|
|
979
935
|
(entry) => typeof entry === "string"
|
|
@@ -983,24 +939,7 @@ var SessionRunner = class {
|
|
|
983
939
|
const item = entry;
|
|
984
940
|
return typeof item.serverName === "string" && typeof item.message === "string" ? [{ serverName: item.serverName, message: item.message }] : [];
|
|
985
941
|
}) : [];
|
|
986
|
-
this.mcpStatus = {
|
|
987
|
-
configured,
|
|
988
|
-
connected,
|
|
989
|
-
failures: [...this.mcpConfigurationFailures, ...failures]
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
loadMcpConfiguration(cwd) {
|
|
993
|
-
const configuration = readWorkspaceMcpConfiguration(cwd);
|
|
994
|
-
this.mcpServers = configuration.servers;
|
|
995
|
-
this.mcpConfigurationFailures = configuration.failures;
|
|
996
|
-
this.mcpStatus = {
|
|
997
|
-
configured: this.mcpServers.map((server) => ({
|
|
998
|
-
name: server.name,
|
|
999
|
-
transport: "type" in server ? server.type : "stdio"
|
|
1000
|
-
})),
|
|
1001
|
-
connected: [],
|
|
1002
|
-
failures: [...this.mcpConfigurationFailures]
|
|
1003
|
-
};
|
|
942
|
+
this.mcpStatus = { configured, connected, failures };
|
|
1004
943
|
}
|
|
1005
944
|
applySkillStatus(meta) {
|
|
1006
945
|
const value = meta?.["nova-ai-cli/skills"];
|
|
@@ -1018,12 +957,28 @@ var SessionRunner = class {
|
|
|
1018
957
|
] : [];
|
|
1019
958
|
});
|
|
1020
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Starts the session ahead of the first prompt, so the agent's MCP and
|
|
962
|
+
* skill status is there for /mcp and the status line. A failure is left to
|
|
963
|
+
* the first prompt, which tries again and reports it.
|
|
964
|
+
*/
|
|
965
|
+
async prepareSession() {
|
|
966
|
+
try {
|
|
967
|
+
await this.ensureSession();
|
|
968
|
+
} catch {
|
|
969
|
+
this.sessionReady = Promise.resolve();
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
get sessionLoaded() {
|
|
973
|
+
return this.agentSessionLoaded;
|
|
974
|
+
}
|
|
1021
975
|
async ensureSession() {
|
|
1022
976
|
await this.ensureUsableModel();
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
977
|
+
for (; ; ) {
|
|
978
|
+
const ready = this.sessionReady;
|
|
979
|
+
await ready;
|
|
980
|
+
if (this.agentSessionLoaded) return;
|
|
981
|
+
if (this.sessionReady === ready) this.sessionReady = this.createSession();
|
|
1027
982
|
}
|
|
1028
983
|
}
|
|
1029
984
|
applyInteractionMode(mode, statusLine) {
|
|
@@ -1039,7 +994,7 @@ var SessionRunner = class {
|
|
|
1039
994
|
try {
|
|
1040
995
|
const usage = await this.nova.sessionContextUsage({
|
|
1041
996
|
sessionId,
|
|
1042
|
-
|
|
997
|
+
...this.model ? { model: this.model } : {},
|
|
1043
998
|
mode: this.interactionMode
|
|
1044
999
|
});
|
|
1045
1000
|
if (request === this.contextUsageRequest && sessionId === this.sessionId) {
|
|
@@ -1153,6 +1108,17 @@ function kittyKeyboardOverride(env = process.env) {
|
|
|
1153
1108
|
function isLateTerminalReply(input) {
|
|
1154
1109
|
return /^\u001b?\[\?[\d;]*[uc]$/.test(input);
|
|
1155
1110
|
}
|
|
1111
|
+
var MOUSE_REPORTING_ON = "\x1B[?1000h\x1B[?1006h";
|
|
1112
|
+
var MOUSE_REPORTING_OFF = "\x1B[?1000l\x1B[?1006l";
|
|
1113
|
+
var MOUSE_INPUT = /^\u001b?\[<(\d+);\d+;\d+[Mm]$/;
|
|
1114
|
+
function isMouseInput(input) {
|
|
1115
|
+
return MOUSE_INPUT.test(input);
|
|
1116
|
+
}
|
|
1117
|
+
function wheelRows(input) {
|
|
1118
|
+
const button = Number(MOUSE_INPUT.exec(input)?.[1] ?? -1);
|
|
1119
|
+
if (button < 0 || (button & 64) === 0) return 0;
|
|
1120
|
+
return button & 1 ? -3 : 3;
|
|
1121
|
+
}
|
|
1156
1122
|
|
|
1157
1123
|
// src/tui/ink/components/prompt-input.tsx
|
|
1158
1124
|
import { Box, Text, useInput, usePaste } from "ink";
|
|
@@ -1170,7 +1136,8 @@ var palette = {
|
|
|
1170
1136
|
faint: "#64748b",
|
|
1171
1137
|
code: "#fcd34d",
|
|
1172
1138
|
diffAddBg: "#1b3a24",
|
|
1173
|
-
diffRemoveBg: "#3d1f1f"
|
|
1139
|
+
diffRemoveBg: "#3d1f1f",
|
|
1140
|
+
stickyBackground: "#1e293b"
|
|
1174
1141
|
};
|
|
1175
1142
|
var colors = {
|
|
1176
1143
|
primary: chalk.hex(palette.primary),
|
|
@@ -1197,7 +1164,7 @@ function PromptInput({
|
|
|
1197
1164
|
}) {
|
|
1198
1165
|
useSyncExternalStore(editor.subscribe, editor.getVersion);
|
|
1199
1166
|
useInput((input, key) => {
|
|
1200
|
-
if (onKey(input, key)) return;
|
|
1167
|
+
if (isMouseInput(input) || onKey(input, key)) return;
|
|
1201
1168
|
editor.handleKey(input, key);
|
|
1202
1169
|
});
|
|
1203
1170
|
usePaste((text) => editor.paste(text));
|
|
@@ -1483,7 +1450,8 @@ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-run
|
|
|
1483
1450
|
function StatusLine({
|
|
1484
1451
|
ui,
|
|
1485
1452
|
model,
|
|
1486
|
-
mcp
|
|
1453
|
+
mcp,
|
|
1454
|
+
scroll = 0
|
|
1487
1455
|
}) {
|
|
1488
1456
|
const separator = /* @__PURE__ */ jsx2(Text2, { color: palette.faint, children: " | " });
|
|
1489
1457
|
const usage = ui.contextUsage ? `ctx:${formatTokenCount(ui.contextUsage.totalTokens)}${ui.contextUsage.contextWindow ? `/${formatTokenCount(ui.contextUsage.contextWindow)}` : ""}` : null;
|
|
@@ -1511,6 +1479,10 @@ function StatusLine({
|
|
|
1511
1479
|
`mcp:${mcp.connected}/${mcp.configured}`,
|
|
1512
1480
|
mcp.failed ? /* @__PURE__ */ jsx2(Text2, { color: palette.danger, children: ` !${mcp.failed}` }) : null
|
|
1513
1481
|
] }) : null,
|
|
1482
|
+
scroll ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1483
|
+
separator,
|
|
1484
|
+
/* @__PURE__ */ jsx2(Text2, { color: palette.warning, children: `\u2191${scroll} lines \xB7 PgDn/Esc` })
|
|
1485
|
+
] }) : null,
|
|
1514
1486
|
mcp.skills ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1515
1487
|
separator,
|
|
1516
1488
|
`skills:${mcp.skills}`
|
|
@@ -1831,12 +1803,6 @@ function PlanView({
|
|
|
1831
1803
|
}
|
|
1832
1804
|
|
|
1833
1805
|
// src/tui/ink/controller.ts
|
|
1834
|
-
import { setWorkspaceTrusted } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
|
|
1835
|
-
import {
|
|
1836
|
-
disabledSkillNames,
|
|
1837
|
-
discoverSkills as discoverSkills2,
|
|
1838
|
-
setSkillEnabled
|
|
1839
|
-
} from "@datalabrotterdam/nova-ai-agent/core/skills.js";
|
|
1840
1806
|
import { INTERACTION_MODES } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
1841
1807
|
|
|
1842
1808
|
// src/tui/commands/builtins.ts
|
|
@@ -1961,8 +1927,8 @@ var builtinCommands = [
|
|
|
1961
1927
|
{
|
|
1962
1928
|
name: "trust",
|
|
1963
1929
|
description: "Trust this workspace: start the MCP servers it declares and apply its .nova-ai allow rules",
|
|
1964
|
-
run(ctx) {
|
|
1965
|
-
ctx.print(ctx.trustWorkspace());
|
|
1930
|
+
async run(ctx) {
|
|
1931
|
+
ctx.print(await ctx.trustWorkspace());
|
|
1966
1932
|
}
|
|
1967
1933
|
},
|
|
1968
1934
|
{
|
|
@@ -1989,16 +1955,16 @@ var builtinCommands = [
|
|
|
1989
1955
|
name: "mcp",
|
|
1990
1956
|
description: "Inspect configured MCP servers",
|
|
1991
1957
|
run(ctx) {
|
|
1992
|
-
ctx.openMcpInspector();
|
|
1958
|
+
return ctx.openMcpInspector();
|
|
1993
1959
|
}
|
|
1994
1960
|
},
|
|
1995
1961
|
{
|
|
1996
1962
|
name: "skills",
|
|
1997
1963
|
description: "List skills, or switch one: /skills on|off <name> [--global]",
|
|
1998
|
-
run(ctx, args) {
|
|
1964
|
+
async run(ctx, args) {
|
|
1999
1965
|
const words = args.trim().split(/\s+/).filter(Boolean);
|
|
2000
1966
|
if (!words.length) {
|
|
2001
|
-
ctx.openSkillInspector();
|
|
1967
|
+
await ctx.openSkillInspector();
|
|
2002
1968
|
return;
|
|
2003
1969
|
}
|
|
2004
1970
|
const global = words.includes("--global");
|
|
@@ -2007,7 +1973,7 @@ var builtinCommands = [
|
|
|
2007
1973
|
ctx.print("Usage: /skills, or /skills on|off <name> [--global]");
|
|
2008
1974
|
return;
|
|
2009
1975
|
}
|
|
2010
|
-
ctx.print(ctx.switchSkill(name, action === "on", global));
|
|
1976
|
+
ctx.print(await ctx.switchSkill(name, action === "on", global));
|
|
2011
1977
|
}
|
|
2012
1978
|
},
|
|
2013
1979
|
{
|
|
@@ -3601,17 +3567,11 @@ var TranscriptCommitter = class {
|
|
|
3601
3567
|
planSignature = null;
|
|
3602
3568
|
snapshot = null;
|
|
3603
3569
|
lastState = null;
|
|
3604
|
-
redrawRequested = false;
|
|
3605
|
-
/** The next sync starts a new epoch, e.g. after the terminal width changed. */
|
|
3606
|
-
requestRedraw() {
|
|
3607
|
-
this.redrawRequested = true;
|
|
3608
|
-
this.lastState = null;
|
|
3609
|
-
}
|
|
3610
3570
|
sync(state, expanded) {
|
|
3611
3571
|
if (this.snapshot && state === this.lastState && expanded === this.expanded)
|
|
3612
3572
|
return this.snapshot;
|
|
3613
3573
|
this.lastState = state;
|
|
3614
|
-
if (
|
|
3574
|
+
if (state.sessionId !== this.sessionId || expanded !== this.expanded || !this.prefixIntact(state.messages)) {
|
|
3615
3575
|
this.startEpoch(state, expanded);
|
|
3616
3576
|
}
|
|
3617
3577
|
const messages = state.messages;
|
|
@@ -3764,7 +3724,6 @@ var TranscriptCommitter = class {
|
|
|
3764
3724
|
return true;
|
|
3765
3725
|
}
|
|
3766
3726
|
startEpoch(state, expanded) {
|
|
3767
|
-
this.redrawRequested = false;
|
|
3768
3727
|
this.epoch++;
|
|
3769
3728
|
this.sessionId = state.sessionId;
|
|
3770
3729
|
this.expanded = expanded;
|
|
@@ -3810,7 +3769,6 @@ function safeSplitOffset(text, from) {
|
|
|
3810
3769
|
}
|
|
3811
3770
|
|
|
3812
3771
|
// src/tui/ink/controller.ts
|
|
3813
|
-
var CLEAR_SCREEN_AND_SCROLLBACK = "\x1B[2J\x1B[3J\x1B[H";
|
|
3814
3772
|
var EXIT_CONFIRM_MS = 2e3;
|
|
3815
3773
|
var TuiController = class {
|
|
3816
3774
|
constructor(store, runner, io) {
|
|
@@ -3835,6 +3793,8 @@ var TuiController = class {
|
|
|
3835
3793
|
exiting = false;
|
|
3836
3794
|
view;
|
|
3837
3795
|
lastEpoch = 0;
|
|
3796
|
+
scroll = 0;
|
|
3797
|
+
viewport = { rows: 10, contentRows: 0 };
|
|
3838
3798
|
exitArmed = false;
|
|
3839
3799
|
exitTimer;
|
|
3840
3800
|
animationTimer;
|
|
@@ -3858,7 +3818,7 @@ var TuiController = class {
|
|
|
3858
3818
|
};
|
|
3859
3819
|
getView = () => this.view;
|
|
3860
3820
|
start() {
|
|
3861
|
-
void this.runner.
|
|
3821
|
+
void this.runner.prepareSession().then(() => this.refresh());
|
|
3862
3822
|
this.syncWindowTitle();
|
|
3863
3823
|
this.animationTimer = setInterval(() => this.syncWindowTitle(true), 120);
|
|
3864
3824
|
this.animationTimer.unref?.();
|
|
@@ -3893,8 +3853,17 @@ var TuiController = class {
|
|
|
3893
3853
|
this.runner.cancel();
|
|
3894
3854
|
return true;
|
|
3895
3855
|
}
|
|
3856
|
+
if (this.scroll > 0) {
|
|
3857
|
+
this.scrollTo(0);
|
|
3858
|
+
return true;
|
|
3859
|
+
}
|
|
3896
3860
|
return false;
|
|
3897
3861
|
}
|
|
3862
|
+
if (key.pageUp || key.pageDown) {
|
|
3863
|
+
const page = Math.max(1, this.viewport.rows - 2);
|
|
3864
|
+
this.scrollBy(key.pageUp ? page : -page);
|
|
3865
|
+
return true;
|
|
3866
|
+
}
|
|
3898
3867
|
if (key.tab && key.shift) {
|
|
3899
3868
|
this.cycleInteractionMode();
|
|
3900
3869
|
return true;
|
|
@@ -4059,9 +4028,13 @@ ${result.output}${result.truncated ? "\n\n[output truncated]" : ""}`
|
|
|
4059
4028
|
state: new ToolInspectorState(() => this.toolCalls(), this.closeOverlay)
|
|
4060
4029
|
});
|
|
4061
4030
|
};
|
|
4062
|
-
openMcpInspector = () => {
|
|
4063
|
-
|
|
4031
|
+
openMcpInspector = async () => {
|
|
4032
|
+
if (!this.runner.sessionLoaded) {
|
|
4033
|
+
this.showText("MCP servers", "Connecting MCP servers...");
|
|
4034
|
+
await this.runner.prepareSession();
|
|
4035
|
+
}
|
|
4064
4036
|
const connection = this.runner.mcpSessionStatus();
|
|
4037
|
+
const servers = connection.configured;
|
|
4065
4038
|
const configuredNames = new Set(servers.map((server) => server.name));
|
|
4066
4039
|
const configurationFailures = connection.failures.filter((failure) => !configuredNames.has(failure.serverName)).map((failure) => `${failure.serverName}
|
|
4067
4040
|
status: failed - ${failure.message}`);
|
|
@@ -4075,16 +4048,20 @@ status: failed - ${failure.message}`);
|
|
|
4075
4048
|
);
|
|
4076
4049
|
};
|
|
4077
4050
|
/** Every discovered skill with its state; switched-off skills stay listed. */
|
|
4078
|
-
openSkillInspector = () => {
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4051
|
+
openSkillInspector = async () => {
|
|
4052
|
+
let skills;
|
|
4053
|
+
try {
|
|
4054
|
+
skills = await this.runner.listSkills();
|
|
4055
|
+
} catch (error) {
|
|
4056
|
+
this.showText("Skills", errorMessage(error, "Could not list the skills."));
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
const on = skills.filter((skill) => skill.enabled).length;
|
|
4083
4060
|
this.showText(
|
|
4084
4061
|
`Skills (${on} on, ${skills.length - on} off)`,
|
|
4085
4062
|
skills.length ? [
|
|
4086
4063
|
...skills.map(
|
|
4087
|
-
(skill) => `${skill.name} [${skill.source}]${
|
|
4064
|
+
(skill) => `${skill.name} [${skill.source}]${skill.enabled ? "" : " (off)"}
|
|
4088
4065
|
${skill.description}
|
|
4089
4066
|
${skill.path}`
|
|
4090
4067
|
),
|
|
@@ -4093,17 +4070,15 @@ ${skill.path}`
|
|
|
4093
4070
|
);
|
|
4094
4071
|
};
|
|
4095
4072
|
/** /skills on|off <name> [--global]: applies from the next message. */
|
|
4096
|
-
switchSkill = (name, enabled, global) => {
|
|
4097
|
-
|
|
4098
|
-
if (!discoverSkills2(cwd).some((skill) => skill.name === name)) {
|
|
4099
|
-
return `No skill named "${name}". /skills lists them.`;
|
|
4100
|
-
}
|
|
4073
|
+
switchSkill = async (name, enabled, global) => {
|
|
4074
|
+
let skills;
|
|
4101
4075
|
try {
|
|
4102
|
-
|
|
4076
|
+
skills = await this.runner.setSkillEnabled(name, enabled, global);
|
|
4103
4077
|
} catch (error) {
|
|
4104
|
-
|
|
4078
|
+
const message = errorMessage(error, "Could not change the skill.");
|
|
4079
|
+
return message.startsWith("No skill named") ? `${message} /skills lists them.` : message;
|
|
4105
4080
|
}
|
|
4106
|
-
const stillOff = enabled &&
|
|
4081
|
+
const stillOff = enabled && skills.some((skill) => skill.name === name && !skill.enabled);
|
|
4107
4082
|
return stillOff ? `Switched "${name}" on ${global ? "globally" : "in this project"}, but it is still off ${global ? "in this project" : "globally"} (use ${global ? "/skills on " + name : "/skills on " + name + " --global"}).` : `Skill "${name}" is ${enabled ? "on" : "off"} ${global ? "everywhere" : "in this project"} from the next message.`;
|
|
4108
4083
|
};
|
|
4109
4084
|
openUsageInspector = async () => {
|
|
@@ -4116,14 +4091,23 @@ ${skill.path}`
|
|
|
4116
4091
|
}
|
|
4117
4092
|
};
|
|
4118
4093
|
/**
|
|
4119
|
-
*
|
|
4120
|
-
*
|
|
4121
|
-
* erased exactly (Ink leaves stale prompt borders), and old scrollback keeps
|
|
4122
|
-
* the old wrapping.
|
|
4094
|
+
* The view reports its transcript size after each layout. While scrolled
|
|
4095
|
+
* up, new output below grows the offset so the rows being read stay put.
|
|
4123
4096
|
*/
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
this.
|
|
4097
|
+
setViewport(rows, contentRows) {
|
|
4098
|
+
const grown = contentRows - this.viewport.contentRows;
|
|
4099
|
+
this.viewport = { rows, contentRows };
|
|
4100
|
+
this.scrollTo(this.scroll > 0 && grown > 0 ? this.scroll + grown : this.scroll);
|
|
4101
|
+
}
|
|
4102
|
+
/** Positive scrolls toward older output. */
|
|
4103
|
+
scrollBy(rows) {
|
|
4104
|
+
this.scrollTo(this.scroll + rows);
|
|
4105
|
+
}
|
|
4106
|
+
scrollTo(offset) {
|
|
4107
|
+
const max = Math.max(0, this.viewport.contentRows - this.viewport.rows);
|
|
4108
|
+
const next = Math.max(0, Math.min(offset, max));
|
|
4109
|
+
if (next === this.scroll) return;
|
|
4110
|
+
this.scroll = next;
|
|
4127
4111
|
this.refresh();
|
|
4128
4112
|
}
|
|
4129
4113
|
toggleToolDetails() {
|
|
@@ -4198,8 +4182,12 @@ ${skill.path}`
|
|
|
4198
4182
|
getPermissionMode: () => this.store.getState().permissionMode,
|
|
4199
4183
|
setPermissionMode: (mode) => this.setPermissionMode(mode),
|
|
4200
4184
|
openPermissionPicker: this.openPermissionModes,
|
|
4201
|
-
trustWorkspace: () => {
|
|
4202
|
-
|
|
4185
|
+
trustWorkspace: async () => {
|
|
4186
|
+
try {
|
|
4187
|
+
await this.runner.trustWorkspace();
|
|
4188
|
+
} catch (error) {
|
|
4189
|
+
return errorMessage(error, "Could not trust the workspace.");
|
|
4190
|
+
}
|
|
4203
4191
|
return "Workspace trusted: its allow rules apply now; MCP servers it declares start with the next session (/clear).";
|
|
4204
4192
|
},
|
|
4205
4193
|
openToolInspector: this.openToolInspector,
|
|
@@ -4224,6 +4212,7 @@ ${skill.path}`
|
|
|
4224
4212
|
submit = async (text, images = [], pastes = []) => {
|
|
4225
4213
|
const value = text.trim();
|
|
4226
4214
|
if (!value) return;
|
|
4215
|
+
this.scroll = 0;
|
|
4227
4216
|
this.editor.addToHistory(value);
|
|
4228
4217
|
this.store.setState((state) => ({
|
|
4229
4218
|
inputHistory: [...state.inputHistory, value],
|
|
@@ -4325,7 +4314,10 @@ ${skill.path}`
|
|
|
4325
4314
|
this.view = this.buildView();
|
|
4326
4315
|
if (this.view.transcript.epoch !== this.lastEpoch) {
|
|
4327
4316
|
this.lastEpoch = this.view.transcript.epoch;
|
|
4328
|
-
this.
|
|
4317
|
+
if (this.scroll) {
|
|
4318
|
+
this.scroll = 0;
|
|
4319
|
+
this.view = this.buildView();
|
|
4320
|
+
}
|
|
4329
4321
|
}
|
|
4330
4322
|
for (const listener of this.listeners) listener();
|
|
4331
4323
|
}
|
|
@@ -4339,7 +4331,8 @@ ${skill.path}`
|
|
|
4339
4331
|
model: this.runner.model,
|
|
4340
4332
|
mcp: mcpCounts(this.runner),
|
|
4341
4333
|
expandedTools: this.expandedTools,
|
|
4342
|
-
exiting: this.exiting
|
|
4334
|
+
exiting: this.exiting,
|
|
4335
|
+
scroll: this.scroll
|
|
4343
4336
|
};
|
|
4344
4337
|
}
|
|
4345
4338
|
questionState(pending) {
|
|
@@ -4371,14 +4364,12 @@ ${skill.path}`
|
|
|
4371
4364
|
}
|
|
4372
4365
|
};
|
|
4373
4366
|
function formatMcpServer(server, status) {
|
|
4374
|
-
const
|
|
4375
|
-
|
|
4376
|
-
if (
|
|
4377
|
-
if ("command" in server) {
|
|
4367
|
+
const lines = [`${server.name} [${server.transport}]`];
|
|
4368
|
+
if (server.url) lines.push(`url: ${server.url}`);
|
|
4369
|
+
if (server.command) {
|
|
4378
4370
|
lines.push(`command: ${server.command}`);
|
|
4379
|
-
if (server.args
|
|
4380
|
-
if (server.env
|
|
4381
|
-
lines.push(`env: ${server.env.map((entry) => entry.name).join(", ")} (values hidden)`);
|
|
4371
|
+
if (server.args?.length) lines.push(`args: ${server.args.join(" ")}`);
|
|
4372
|
+
if (server.env?.length) lines.push(`env: ${server.env.join(", ")} (values hidden)`);
|
|
4382
4373
|
}
|
|
4383
4374
|
const failure = status.failures.find((item) => item.serverName === server.name);
|
|
4384
4375
|
lines.push(
|
|
@@ -4409,7 +4400,7 @@ function useDialogInput(handle, isActive) {
|
|
|
4409
4400
|
const [, setTick] = useState(0);
|
|
4410
4401
|
useInput2(
|
|
4411
4402
|
(input, key) => {
|
|
4412
|
-
if (handle(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4403
|
+
if (!isMouseInput(input) && handle(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4413
4404
|
},
|
|
4414
4405
|
{ isActive }
|
|
4415
4406
|
);
|
|
@@ -4551,7 +4542,7 @@ function QuestionDialog({
|
|
|
4551
4542
|
const [, setTick] = useState(0);
|
|
4552
4543
|
useInput2(
|
|
4553
4544
|
(input, key) => {
|
|
4554
|
-
if (state.handleKey(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4545
|
+
if (!isMouseInput(input) && state.handleKey(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4555
4546
|
},
|
|
4556
4547
|
{ isActive }
|
|
4557
4548
|
);
|
|
@@ -4633,7 +4624,7 @@ function PermissionDialog({
|
|
|
4633
4624
|
}) {
|
|
4634
4625
|
useInput2(
|
|
4635
4626
|
(input, key) => {
|
|
4636
|
-
if (permissionKey(request, input, key) === "closed") onClose();
|
|
4627
|
+
if (!isMouseInput(input) && permissionKey(request, input, key) === "closed") onClose();
|
|
4637
4628
|
},
|
|
4638
4629
|
{ isActive }
|
|
4639
4630
|
);
|
|
@@ -4684,9 +4675,8 @@ function wrapPlain(text, width) {
|
|
|
4684
4675
|
}
|
|
4685
4676
|
|
|
4686
4677
|
// src/tui/ink/app.tsx
|
|
4687
|
-
import {
|
|
4678
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4688
4679
|
var FRAME_MS = 120;
|
|
4689
|
-
var RESIZE_SETTLE_MS = 100;
|
|
4690
4680
|
async function runInkTui(credentials, cwd, args, options = {}) {
|
|
4691
4681
|
const store = createStore({
|
|
4692
4682
|
messages: [],
|
|
@@ -4728,22 +4718,15 @@ async function runInkTui(credentials, cwd, args, options = {}) {
|
|
|
4728
4718
|
// Ink otherwise turns non-interactive whenever CI is set in the
|
|
4729
4719
|
// environment; the TUI only runs on a terminal anyway.
|
|
4730
4720
|
interactive: Boolean(stdout.isTTY),
|
|
4721
|
+
// Full screen: the transcript scrolls inside the app (PgUp/PgDn) and the
|
|
4722
|
+
// terminal gets its own screen back on exit.
|
|
4723
|
+
alternateScreen: Boolean(stdout.isTTY),
|
|
4731
4724
|
kittyKeyboard: { mode: kittyKeyboard ? "enabled" : "disabled" }
|
|
4732
4725
|
});
|
|
4733
4726
|
controller.start();
|
|
4727
|
+
if (stdout.isTTY) stdout.write(MOUSE_REPORTING_ON);
|
|
4734
4728
|
const onSignal = () => controller.requestExit();
|
|
4735
4729
|
process.once("SIGTERM", onSignal);
|
|
4736
|
-
let printedColumns = stdout.columns;
|
|
4737
|
-
let resizeTimer;
|
|
4738
|
-
const onResize = () => {
|
|
4739
|
-
if (resizeTimer) clearTimeout(resizeTimer);
|
|
4740
|
-
resizeTimer = setTimeout(() => {
|
|
4741
|
-
if (stdout.columns === printedColumns) return;
|
|
4742
|
-
printedColumns = stdout.columns;
|
|
4743
|
-
controller.redrawAll();
|
|
4744
|
-
}, RESIZE_SETTLE_MS);
|
|
4745
|
-
};
|
|
4746
|
-
stdout.on("resize", onResize);
|
|
4747
4730
|
void (options.checkForUpdate ?? checkForUpdate)().then((updateAvailable) => {
|
|
4748
4731
|
if (updateAvailable) store.setState({ updateAvailable });
|
|
4749
4732
|
});
|
|
@@ -4753,8 +4736,7 @@ async function runInkTui(credentials, cwd, args, options = {}) {
|
|
|
4753
4736
|
await controller.stop();
|
|
4754
4737
|
} finally {
|
|
4755
4738
|
process.off("SIGTERM", onSignal);
|
|
4756
|
-
stdout.
|
|
4757
|
-
if (resizeTimer) clearTimeout(resizeTimer);
|
|
4739
|
+
if (stdout.isTTY) stdout.write(MOUSE_REPORTING_OFF);
|
|
4758
4740
|
instance.unmount();
|
|
4759
4741
|
await instance.waitUntilExit().catch(() => {
|
|
4760
4742
|
});
|
|
@@ -4768,31 +4750,85 @@ function App({ controller }) {
|
|
|
4768
4750
|
const { rows, columns } = useWindowSize();
|
|
4769
4751
|
const bottomRef = useRef(null);
|
|
4770
4752
|
const bottom = useBoxMetrics(bottomRef);
|
|
4753
|
+
const contentRef = useRef(null);
|
|
4754
|
+
const content = useBoxMetrics(contentRef);
|
|
4771
4755
|
const working = useWorking(view);
|
|
4756
|
+
const [userTops, setUserTops] = useState2(/* @__PURE__ */ new Map());
|
|
4757
|
+
useInput3((input) => {
|
|
4758
|
+
const rows2 = wheelRows(input);
|
|
4759
|
+
if (rows2) controller.scrollBy(rows2);
|
|
4760
|
+
});
|
|
4761
|
+
const height = Math.max(4, rows - 1);
|
|
4762
|
+
const viewportRows = Math.max(3, height - (bottom.hasMeasured ? bottom.height : 6));
|
|
4763
|
+
useEffect(() => {
|
|
4764
|
+
if (content.hasMeasured) controller.setViewport(viewportRows, content.height);
|
|
4765
|
+
}, [controller, viewportRows, content.hasMeasured, content.height]);
|
|
4766
|
+
if (view.exiting) return null;
|
|
4772
4767
|
const { transcript } = view;
|
|
4773
|
-
const
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4768
|
+
const blocks = [...transcript.committed, ...transcript.live];
|
|
4769
|
+
const liveKeys = new Set(transcript.live.map((block) => block.key));
|
|
4770
|
+
const onUserTop = (key, top) => setUserTops((tops) => tops.get(key) === top ? tops : new Map(tops).set(key, top));
|
|
4771
|
+
const firstVisibleRow = content.height - viewportRows - view.scroll;
|
|
4772
|
+
let sticky = null;
|
|
4773
|
+
for (const block of blocks) {
|
|
4774
|
+
if (block.kind !== "user") continue;
|
|
4775
|
+
const top = userTops.get(block.key);
|
|
4776
|
+
if (top !== void 0 && top < firstVisibleRow) sticky = block.text;
|
|
4777
|
+
}
|
|
4778
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height, children: [
|
|
4779
|
+
/* @__PURE__ */ jsxs6(
|
|
4780
|
+
Box6,
|
|
4781
|
+
{
|
|
4782
|
+
flexDirection: "column",
|
|
4783
|
+
height: viewportRows,
|
|
4784
|
+
flexShrink: 0,
|
|
4785
|
+
overflowY: "hidden",
|
|
4786
|
+
justifyContent: "flex-end",
|
|
4787
|
+
children: [
|
|
4788
|
+
/* @__PURE__ */ jsxs6(Box6, { ref: contentRef, flexDirection: "column", flexShrink: 0, marginBottom: -view.scroll, children: [
|
|
4789
|
+
blocks.map(
|
|
4790
|
+
(block) => liveKeys.has(block.key) ? /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(TranscriptBlockView, { block, frame: working.frame }) }, block.key) : /* @__PURE__ */ jsx6(CommittedBlock, { block, onUserTop }, block.key)
|
|
4791
|
+
),
|
|
4792
|
+
working.line ? /* @__PURE__ */ jsx6(Box6, { marginTop: 1, paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
4793
|
+
/* @__PURE__ */ jsx6(Text6, { color: palette.accent, children: WORKING_FRAMES[working.frame] }),
|
|
4794
|
+
/* @__PURE__ */ jsx6(Text6, { color: palette.muted, children: ` ${working.line}` })
|
|
4795
|
+
] }) }) : null
|
|
4796
|
+
] }),
|
|
4797
|
+
sticky !== null ? /* @__PURE__ */ jsx6(StickyPrompt, { text: sticky, columns }) : null
|
|
4798
|
+
]
|
|
4799
|
+
}
|
|
4800
|
+
),
|
|
4801
|
+
/* @__PURE__ */ jsxs6(Box6, { ref: bottomRef, flexDirection: "column", flexShrink: 0, children: [
|
|
4802
|
+
/* @__PURE__ */ jsx6(Surface, { controller, view, rows: Math.max(6, rows - 4), columns }),
|
|
4803
|
+
/* @__PURE__ */ jsx6(StatusLine, { ui: view.ui, model: view.model, mcp: view.mcp, scroll: view.scroll })
|
|
4793
4804
|
] })
|
|
4794
4805
|
] });
|
|
4795
4806
|
}
|
|
4807
|
+
var CommittedBlock = memo(function CommittedBlock2({
|
|
4808
|
+
block,
|
|
4809
|
+
onUserTop
|
|
4810
|
+
}) {
|
|
4811
|
+
if (block.kind !== "user") {
|
|
4812
|
+
return /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(TranscriptBlockView, { block }) });
|
|
4813
|
+
}
|
|
4814
|
+
return /* @__PURE__ */ jsx6(UserAnchor, { block, onUserTop, children: /* @__PURE__ */ jsx6(TranscriptBlockView, { block }) });
|
|
4815
|
+
}, (before, after) => before.block === after.block);
|
|
4816
|
+
function UserAnchor({
|
|
4817
|
+
block,
|
|
4818
|
+
onUserTop,
|
|
4819
|
+
children
|
|
4820
|
+
}) {
|
|
4821
|
+
const ref = useRef(null);
|
|
4822
|
+
const { top, hasMeasured } = useBoxMetrics(ref);
|
|
4823
|
+
useEffect(() => {
|
|
4824
|
+
if (hasMeasured) onUserTop(block.key, top);
|
|
4825
|
+
}, [block.key, top, hasMeasured]);
|
|
4826
|
+
return /* @__PURE__ */ jsx6(Box6, { ref, marginTop: 1, children });
|
|
4827
|
+
}
|
|
4828
|
+
function StickyPrompt({ text, columns }) {
|
|
4829
|
+
const line = `> ${text.replace(/\s+/g, " ").trim()}`;
|
|
4830
|
+
return /* @__PURE__ */ jsx6(Box6, { position: "absolute", top: 0, left: 0, width: columns, children: /* @__PURE__ */ jsx6(Text6, { color: palette.primary, backgroundColor: palette.stickyBackground, wrap: "truncate-end", children: line.padEnd(columns) }) });
|
|
4831
|
+
}
|
|
4796
4832
|
function Surface({
|
|
4797
4833
|
controller,
|
|
4798
4834
|
view,
|
|
@@ -4853,13 +4889,15 @@ function useWorking(view) {
|
|
|
4853
4889
|
}
|
|
4854
4890
|
|
|
4855
4891
|
// src/tui/index.ts
|
|
4892
|
+
import { workspaceNeedsTrust } from "@datalabrotterdam/nova-ai-agent/core/workspace-mcp.js";
|
|
4856
4893
|
async function runChat(options) {
|
|
4857
4894
|
const stored = readCredentials();
|
|
4858
4895
|
if (!stored) {
|
|
4859
4896
|
console.error("nova-ai isn't connected to a Nova AI account yet. Run `nova-ai login` first.");
|
|
4860
4897
|
return 1;
|
|
4861
4898
|
}
|
|
4862
|
-
const
|
|
4899
|
+
const model = options.model ?? stored.defaultModel ?? process.env.NOVA_MODEL;
|
|
4900
|
+
const credentials = { ...stored, defaultModel: model };
|
|
4863
4901
|
const updateCheck = checkForUpdate().then((update) => withInstallability(update));
|
|
4864
4902
|
const updatedFrom = process.env[UPDATED_FROM_ENV];
|
|
4865
4903
|
if (updatedFrom) {
|
|
@@ -4903,7 +4941,7 @@ ${cwd}`);
|
|
|
4903
4941
|
console.log("Trusting it starts those servers and applies those rules. Only trust code you know.");
|
|
4904
4942
|
const answer = (await prompt.question("Trust this workspace? [y/N] ")).trim().toLowerCase();
|
|
4905
4943
|
if (answer === "y" || answer === "yes") {
|
|
4906
|
-
|
|
4944
|
+
setWorkspaceTrusted(cwd, true);
|
|
4907
4945
|
} else {
|
|
4908
4946
|
console.log("Not trusted: its MCP servers stay off and its allow rules are ignored. Use /trust later to change this.");
|
|
4909
4947
|
}
|
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.6",
|
|
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,8 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@agentclientprotocol/sdk": "^0.29.0",
|
|
42
|
-
"@datalabrotterdam/nova-ai-agent": "^1.0.0-alpha.
|
|
43
|
-
"@datalabrotterdam/nova-sdk": "^1.3.2",
|
|
42
|
+
"@datalabrotterdam/nova-ai-agent": "^1.0.0-alpha.3",
|
|
44
43
|
"chalk": "^5.3.0",
|
|
45
44
|
"cli-highlight": "^2.1.11",
|
|
46
45
|
"diff": "^9.0.0",
|
|
@@ -75,5 +74,8 @@
|
|
|
75
74
|
}
|
|
76
75
|
]
|
|
77
76
|
]
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@datalabrotterdam/nova-sdk": "^1.3.2"
|
|
78
80
|
}
|
|
79
81
|
}
|