@datalabrotterdam/nova-ai-cli 1.2.0-alpha.6 → 1.2.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +33 -4
- package/dist/{chunk-QSNU4H55.js → chunk-KDAUUQBE.js} +39 -4
- package/dist/{headless-C36E4XSQ.js → headless-BQXK7TNC.js} +1 -1
- package/dist/index.js +4 -10
- package/dist/{tui-TRADKND7.js → tui-TJFV6NC6.js} +262 -33
- package/package.json +3 -3
- package/dist/webui-L4GGMXJ3.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.8](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.7...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.8) (2026-10-24)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* slash commands from markdown files ([305f111](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/305f1112c6f8312ab8a0683954c4e5bc88d7b952))
|
|
7
|
+
|
|
8
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.7](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.6...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.7) (2026-10-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **agent:** Claude Code compatible hooks ([227fe8b](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/227fe8b273ec2e211e9d57674e76af5c42a473bd))
|
|
14
|
+
* **agent:** install the shared Nova skills ([64139dd](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/64139dd612933343f04f165de1a9ab78832d6864))
|
|
15
|
+
* **agent:** mark and protect bundled skills ([6ef52eb](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/6ef52eb6d048f8a3b4e75686244aabcfd0f62d6e))
|
|
16
|
+
* **agent:** thinking effort for reasoning models ([8551fea](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/8551feacff3e1675f369030e20fb2b5071f0db7f))
|
|
17
|
+
* **agent:** web_search tool ([12985eb](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/12985eb9ca06d6c814d4e0162465c651889d9370))
|
|
18
|
+
* **cli:** /effort slider for thinking models ([8ca67f2](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/8ca67f2f5dcc8f6bff8c36409a2baba5d43c4a02))
|
|
19
|
+
* **cli:** remember the thinking level ([883196c](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/883196c9fdb089f4f26ac82b557cc87dfc0b1c0b))
|
|
20
|
+
* OAuth sign-in for MCP servers ([ba57c79](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/ba57c79642f902ab2bc20f59e4627fb2b774bb4a))
|
|
21
|
+
* the user's agent files as session modes ([3386de8](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/3386de88d07b0351bd1e44960fcacfef26e3d45b))
|
|
22
|
+
|
|
1
23
|
# [@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
24
|
|
|
3
25
|
|
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ row. To select text, hold Shift (Option in some macOS terminals) while dragging.
|
|
|
79
79
|
|
|
80
80
|
- **Modes** (Shift+Tab): `agent` does the work; `plan` reads the code, asks
|
|
81
81
|
questions and builds a task list but changes nothing; `ask` only answers.
|
|
82
|
+
Your own agents (`~/.nova-ai/agents/<name>.agent.md`, the format the VS Code
|
|
83
|
+
extension uses) are extra modes after these; `/mode` lists them.
|
|
82
84
|
- **Permissions**: before an edit or command you choose once, for this
|
|
83
85
|
session, always (saved as an exact rule) or deny. Ctrl+K switches between
|
|
84
86
|
asking, accepting edits, and bypass (never remembered).
|
|
@@ -95,15 +97,18 @@ row. To select text, hold Shift (Option in some macOS terminals) while dragging.
|
|
|
95
97
|
| Enter / Shift+Enter | Send / new line (Shift+Enter where the terminal supports the kitty keyboard protocol: VS Code, kitty, WezTerm, Ghostty, foot, Alacritty, recent iTerm2; Alt+Enter, Ctrl+J or a trailing `\` work everywhere) |
|
|
96
98
|
| Esc | Cancel the running turn |
|
|
97
99
|
| Ctrl+C twice | Exit; prints `nova-ai --resume <id>` |
|
|
98
|
-
| Shift+Tab | Cycle agent / ask / plan |
|
|
100
|
+
| Shift+Tab | Cycle agent / ask / plan and your own agents |
|
|
99
101
|
| Ctrl+R / Ctrl+P / Ctrl+K | Sessions / models / permission mode |
|
|
100
102
|
| Ctrl+O / Ctrl+T / Ctrl+B | Verbose tool output / tool inspector / background jobs |
|
|
101
103
|
| Alt+V | Paste a clipboard image |
|
|
102
104
|
|
|
103
|
-
Slash commands: `/help`, `/model`, `/mode`, `/permission`, `/session`,
|
|
105
|
+
Slash commands: `/help`, `/model`, `/effort` (thinking models), `/mode`, `/permission`, `/session`,
|
|
104
106
|
`/resume`, `/clear`, `/compact`, `/rewind [n]`, `/queue`, `/steer`, `/usage`,
|
|
105
107
|
`/tools`, `/mcp`, `/skills`, `/shell` and `/agent` (background jobs), `/trust`
|
|
106
|
-
and `/exit`.
|
|
108
|
+
and `/exit`. Your own commands are markdown files in `~/.nova-ai/commands/` or
|
|
109
|
+
the repository's `.nova-ai/commands/` (Claude Code's format, so
|
|
110
|
+
`.claude/commands` works too): the file is the prompt, `$ARGUMENTS` what you
|
|
111
|
+
type after the command, and they show up in the `/` menu. Details:
|
|
107
112
|
[`src/tui/README.md`](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/packages/cli/src/tui/README.md).
|
|
108
113
|
|
|
109
114
|
## Headless
|
|
@@ -169,7 +174,10 @@ need ACP. Everything is documented in
|
|
|
169
174
|
and nested shells need an exact rule.
|
|
170
175
|
- **Web pages are fetched only with approval.** `fetch_url` asks every time
|
|
171
176
|
unless a rule allows that URL (a URL can carry data out), and a redirect to
|
|
172
|
-
another site needs its own approval.
|
|
177
|
+
another site needs its own approval. `web_search` asks the same way, since
|
|
178
|
+
the query goes to the search providers (You.com, DuckDuckGo; your own
|
|
179
|
+
SearXNG server first when `~/.nova-ai/settings.json` has
|
|
180
|
+
`"webSearch": { "searxngUrl": "https://…" }`).
|
|
173
181
|
- **File access stays in the workspace** (symlinks resolved); commands run in
|
|
174
182
|
their own process group with a timeout, and the whole tree is stopped on
|
|
175
183
|
cancel.
|
|
@@ -194,7 +202,22 @@ VS Code extension: settings, per-project trust, sessions and memory.
|
|
|
194
202
|
skills folders are read too). `/skills` lists them; `/skills off <name>`
|
|
195
203
|
switches one off in this project (`--global`: everywhere). Only names and
|
|
196
204
|
short descriptions go into the prompt; Nova loads a skill when it fits.
|
|
205
|
+
Nova installs its own skills for Word, Excel, PowerPoint and PDF files
|
|
206
|
+
(`nova-docx`, `nova-xlsx`, `nova-pptx`, `nova-pdf`, from
|
|
207
|
+
[nova-ai-skills](https://github.com/Datalab-Rotterdam/nova-ai-skills)) in
|
|
208
|
+
`~/.nova-ai/skills` and keeps them up to date; they need Python 3.
|
|
209
|
+
- **Hooks**: Claude Code's `hooks` format in the same settings files (a
|
|
210
|
+
repository's own only once you trust the workspace): `PreToolUse`,
|
|
211
|
+
`PostToolUse`, `UserPromptSubmit`, `SessionStart` and `Stop`, with Claude
|
|
212
|
+
Code's tool names as matchers, so hooks from `.claude/settings.json` work
|
|
213
|
+
when copied over.
|
|
197
214
|
- **MCP servers**: `.mcp.json` or `mcpServers` in `.nova-ai/settings.json`.
|
|
215
|
+
HTTP servers that use OAuth show "needs sign-in" in `/mcp`; `/mcp login
|
|
216
|
+
<server>` opens the sign-in page in your browser (tokens are kept in
|
|
217
|
+
`~/.nova-ai/mcp-auth`, refreshed automatically), `/mcp logout <server>`
|
|
218
|
+
forgets them. A server that wants a client registered by hand takes
|
|
219
|
+
`"oauth": { "clientId", "clientSecret", "scope", "callbackPort" }` in
|
|
220
|
+
`.mcp.json`.
|
|
198
221
|
|
|
199
222
|
The complete layout and rule syntax: [docs/NOVA_HOME.md](https://github.com/Datalab-Rotterdam/nova-ai-cli/blob/main/docs/NOVA_HOME.md).
|
|
200
223
|
|
|
@@ -207,6 +230,12 @@ to a text protocol for models that don't support it. When the context window
|
|
|
207
230
|
fills up, older messages are summarized automatically (`/compact` does it by
|
|
208
231
|
hand).
|
|
209
232
|
|
|
233
|
+
Thinking models are marked ◆ in the model list. For those with levels the
|
|
234
|
+
status line shows the current one (`· thinking medium`) and `/effort` opens a
|
|
235
|
+
slider (←/→, Enter), or sets one directly (`/effort high`, `/effort off`). The
|
|
236
|
+
choice is remembered like the model; a model without that level uses its own
|
|
237
|
+
default.
|
|
238
|
+
|
|
210
239
|
| Environment | |
|
|
211
240
|
|---|---|
|
|
212
241
|
| `NOVA_API_KEY`, `NOVA_MODEL` | Use this key / model instead of the stored ones |
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/tui/session/tui-acp-client.ts
|
|
2
2
|
import { NOVA_NOTIFICATIONS } from "@datalabrotterdam/nova-ai-agent/acp/extensions.js";
|
|
3
|
+
import { openBrowser } from "@datalabrotterdam/nova-ai-agent/acp/auth-server.js";
|
|
3
4
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
4
5
|
import { dirname } from "path";
|
|
5
6
|
import {
|
|
@@ -7,13 +8,11 @@ import {
|
|
|
7
8
|
terminateProcessTree
|
|
8
9
|
} from "@datalabrotterdam/nova-ai-agent/core/process-tree.js";
|
|
9
10
|
import { resolveWorkspaceFile } from "@datalabrotterdam/nova-ai-agent/core/tools/workspace-paths.js";
|
|
10
|
-
import {
|
|
11
|
-
isInteractionMode
|
|
12
|
-
} from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
13
11
|
import {
|
|
14
12
|
fromElicitationRequest,
|
|
15
13
|
toElicitationResponse
|
|
16
14
|
} from "@datalabrotterdam/nova-ai-agent/acp/user-questions.js";
|
|
15
|
+
import { INTERACTION_MODES } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
17
16
|
var DEFAULT_OUTPUT_LIMIT = 1e5;
|
|
18
17
|
var nextMessageId = 0;
|
|
19
18
|
function uid() {
|
|
@@ -31,6 +30,10 @@ var TuiAcpClient = class {
|
|
|
31
30
|
cwd;
|
|
32
31
|
onBackgroundLifecycle;
|
|
33
32
|
onInteractionModeChange;
|
|
33
|
+
/** The modes the agent offers (built-in, then the user's agent files); set by the runner. */
|
|
34
|
+
modeIds = [...INTERACTION_MODES];
|
|
35
|
+
/** Slash commands the agent understands, such as the user's command files. */
|
|
36
|
+
agentCommands = [];
|
|
34
37
|
capabilities = {
|
|
35
38
|
fs: { readTextFile: true, writeTextFile: true },
|
|
36
39
|
terminal: true,
|
|
@@ -91,6 +94,29 @@ var TuiAcpClient = class {
|
|
|
91
94
|
appendUserMessage(text) {
|
|
92
95
|
this.appendMessage({ id: uid(), role: "user", text });
|
|
93
96
|
}
|
|
97
|
+
/** An MCP server's sign-in page: opened in the browser, and printed in case that fails. */
|
|
98
|
+
mcpAuthUrl(params) {
|
|
99
|
+
const { serverName, url } = params ?? {};
|
|
100
|
+
if (typeof serverName !== "string" || typeof url !== "string") return;
|
|
101
|
+
let page;
|
|
102
|
+
try {
|
|
103
|
+
page = new URL(url);
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (page.protocol !== "https:" && page.protocol !== "http:") {
|
|
108
|
+
this.appendError(`MCP server "${serverName}" sent a sign-in address that is not a web page; not opened.`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
openBrowser(page.toString());
|
|
112
|
+
this.appendMessage({
|
|
113
|
+
id: uid(),
|
|
114
|
+
role: "assistant",
|
|
115
|
+
streaming: false,
|
|
116
|
+
text: `Sign in to MCP server "${serverName}" in your browser. If it did not open, visit:
|
|
117
|
+
${page}`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
94
120
|
appendError(text) {
|
|
95
121
|
this.appendMessage({ id: uid(), role: "error", text });
|
|
96
122
|
}
|
|
@@ -294,8 +320,15 @@ ${err.message}`;
|
|
|
294
320
|
});
|
|
295
321
|
}
|
|
296
322
|
return;
|
|
323
|
+
case "available_commands_update":
|
|
324
|
+
this.agentCommands = update.availableCommands.map(({ name, description, input }) => ({
|
|
325
|
+
name,
|
|
326
|
+
description,
|
|
327
|
+
...input && "hint" in input && input.hint ? { hint: input.hint } : {}
|
|
328
|
+
}));
|
|
329
|
+
return;
|
|
297
330
|
case "current_mode_update":
|
|
298
|
-
if (
|
|
331
|
+
if (this.modeIds.includes(update.currentModeId)) {
|
|
299
332
|
this.onInteractionModeChange?.(update.currentModeId);
|
|
300
333
|
}
|
|
301
334
|
return;
|
|
@@ -415,6 +448,8 @@ ${err.message}`;
|
|
|
415
448
|
return this.backgroundUpdate(params);
|
|
416
449
|
case NOVA_NOTIFICATIONS.queueChanged:
|
|
417
450
|
return this.queueChanged(params);
|
|
451
|
+
case NOVA_NOTIFICATIONS.mcpAuthUrl:
|
|
452
|
+
return this.mcpAuthUrl(params);
|
|
418
453
|
default:
|
|
419
454
|
return;
|
|
420
455
|
}
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,7 @@ var MODE_FLAGS = {
|
|
|
17
17
|
"--headless": "print",
|
|
18
18
|
"--setup": "login",
|
|
19
19
|
"--version": "version",
|
|
20
|
-
"-v": "version"
|
|
21
|
-
"--web": "web"
|
|
20
|
+
"-v": "version"
|
|
22
21
|
};
|
|
23
22
|
function parseCli(argv) {
|
|
24
23
|
const first = argv[0];
|
|
@@ -34,7 +33,7 @@ function parseCli(argv) {
|
|
|
34
33
|
const mode = [...modes][0];
|
|
35
34
|
const wantsHelp = options.includes("--help") || options.includes("-h");
|
|
36
35
|
if (!mode) return wantsHelp ? { mode: "help", args: [] } : { mode: "tui", args: argv };
|
|
37
|
-
const drop = /* @__PURE__ */ new Set(["--acp", "--headless", "--setup", "--version", "-v"
|
|
36
|
+
const drop = /* @__PURE__ */ new Set(["--acp", "--headless", "--setup", "--version", "-v"]);
|
|
38
37
|
return { mode, args: argv.filter((arg, index) => !(drop.has(arg) && (end < 0 || index < end))) };
|
|
39
38
|
}
|
|
40
39
|
function versionText() {
|
|
@@ -140,7 +139,7 @@ ${MAIN_HELP}
|
|
|
140
139
|
}
|
|
141
140
|
case "print": {
|
|
142
141
|
const update = startUpdateCheck();
|
|
143
|
-
const { default: runHeadless } = await import("./headless-
|
|
142
|
+
const { default: runHeadless } = await import("./headless-BQXK7TNC.js");
|
|
144
143
|
const code = await runHeadless(command.args);
|
|
145
144
|
if (update) await noticeUpdate(update, 500);
|
|
146
145
|
return code;
|
|
@@ -153,11 +152,6 @@ ${MAIN_HELP}
|
|
|
153
152
|
const { runLogout } = await import("@datalabrotterdam/nova-ai-agent/commands/login.js");
|
|
154
153
|
return runLogout();
|
|
155
154
|
}
|
|
156
|
-
case "web": {
|
|
157
|
-
const { default: runWebUi } = await import("./webui-L4GGMXJ3.js");
|
|
158
|
-
await runWebUi();
|
|
159
|
-
return 0;
|
|
160
|
-
}
|
|
161
155
|
case "tui": {
|
|
162
156
|
let options;
|
|
163
157
|
try {
|
|
@@ -175,7 +169,7 @@ ${MAIN_HELP}
|
|
|
175
169
|
);
|
|
176
170
|
return 2;
|
|
177
171
|
}
|
|
178
|
-
const { default: runChat } = await import("./tui-
|
|
172
|
+
const { default: runChat } = await import("./tui-TJFV6NC6.js");
|
|
179
173
|
return runChat(options);
|
|
180
174
|
}
|
|
181
175
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
TuiAcpClient,
|
|
12
12
|
createStore
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-KDAUUQBE.js";
|
|
14
14
|
|
|
15
15
|
// src/tui/index.ts
|
|
16
16
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
@@ -122,11 +122,15 @@ import * as acp from "@agentclientprotocol/sdk";
|
|
|
122
122
|
import { NovaAgent } from "@datalabrotterdam/nova-ai-agent/acp/agent.js";
|
|
123
123
|
import { startInProcessAgent } from "@datalabrotterdam/nova-ai-agent/client/in-process.js";
|
|
124
124
|
import { replayHistory } from "@datalabrotterdam/nova-ai-agent/core/history.js";
|
|
125
|
-
import {
|
|
125
|
+
import {
|
|
126
|
+
saveDefaultEffort,
|
|
127
|
+
saveDefaultModel
|
|
128
|
+
} from "@datalabrotterdam/nova-ai-agent/core/credentials.js";
|
|
126
129
|
import {
|
|
127
130
|
savePermissionMode
|
|
128
131
|
} from "@datalabrotterdam/nova-ai-agent/core/policy/settings.js";
|
|
129
132
|
import { loadStoredSession } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
|
|
133
|
+
import { resolveEffort } from "@datalabrotterdam/nova-ai-agent/core/model-capabilities.js";
|
|
130
134
|
|
|
131
135
|
// src/tui/files/file-mentions.ts
|
|
132
136
|
import { statSync } from "fs";
|
|
@@ -286,6 +290,7 @@ var SessionRunner = class {
|
|
|
286
290
|
clientInfo: { name: "nova-ai-cli-tui", version: "1" }
|
|
287
291
|
});
|
|
288
292
|
this.model = credentials.defaultModel ?? process.env.NOVA_MODEL ?? "";
|
|
293
|
+
this.effort = credentials.defaultEffort ?? null;
|
|
289
294
|
this.sessionId = crypto.randomUUID();
|
|
290
295
|
this.sessionReady = Promise.resolve();
|
|
291
296
|
}
|
|
@@ -296,6 +301,14 @@ var SessionRunner = class {
|
|
|
296
301
|
title = null;
|
|
297
302
|
model;
|
|
298
303
|
interactionMode = "agent";
|
|
304
|
+
/** The modes the agent offers: the built-in ones, then the user's agent files. */
|
|
305
|
+
get modeIds() {
|
|
306
|
+
return this.acpClient.modeIds;
|
|
307
|
+
}
|
|
308
|
+
/** The agent's slash commands (the user's command files); sent as a prompt. */
|
|
309
|
+
get agentCommands() {
|
|
310
|
+
return this.acpClient.agentCommands;
|
|
311
|
+
}
|
|
299
312
|
/** The agent, served in-process over a real ACP connection (tests may stub it). */
|
|
300
313
|
agent = new NovaAgent();
|
|
301
314
|
acpClient;
|
|
@@ -315,7 +328,10 @@ var SessionRunner = class {
|
|
|
315
328
|
handoffActive = false;
|
|
316
329
|
queuedAttachments = /* @__PURE__ */ new Map();
|
|
317
330
|
editingQueuedMessageId = null;
|
|
318
|
-
|
|
331
|
+
/** From the agent's model list: image input, thinking levels. */
|
|
332
|
+
modelInfo = /* @__PURE__ */ new Map();
|
|
333
|
+
/** The thinking level the user chose (saved); null: each model's default. */
|
|
334
|
+
effort;
|
|
319
335
|
contextUsageRequest = 0;
|
|
320
336
|
modelCheck = null;
|
|
321
337
|
resumeFrom(storedSessionId) {
|
|
@@ -353,6 +369,7 @@ var SessionRunner = class {
|
|
|
353
369
|
this.acpClient.muteSessionUpdates = false;
|
|
354
370
|
}
|
|
355
371
|
}).then((response) => {
|
|
372
|
+
this.applyModes(response.modes);
|
|
356
373
|
this.applyMcpStatus(response._meta);
|
|
357
374
|
this.applySkillStatus(response._meta);
|
|
358
375
|
this.agentSessionLoaded = true;
|
|
@@ -360,6 +377,7 @@ var SessionRunner = class {
|
|
|
360
377
|
void this.nova.setSessionMode({ sessionId: this.sessionId, modeId: this.interactionMode }).catch(() => {
|
|
361
378
|
});
|
|
362
379
|
this.applyPermissionMode(this.sessionId);
|
|
380
|
+
this.applyEffort(this.sessionId);
|
|
363
381
|
void this.updateContextUsage();
|
|
364
382
|
void this.refreshContextUsage().catch(() => {
|
|
365
383
|
});
|
|
@@ -482,6 +500,14 @@ var SessionRunner = class {
|
|
|
482
500
|
skillSessionStatus() {
|
|
483
501
|
return this.skills.map((skill) => ({ ...skill }));
|
|
484
502
|
}
|
|
503
|
+
/** Signs in to (or out of) an HTTP MCP server; the agent reconnects it and reports the new status. */
|
|
504
|
+
async mcpSignIn(serverName, signIn) {
|
|
505
|
+
await this.ensureSession();
|
|
506
|
+
const params = { sessionId: this.sessionId, serverName };
|
|
507
|
+
const meta = signIn ? await this.nova.mcpLogin(params) : await this.nova.mcpLogout(params);
|
|
508
|
+
this.applyMcpStatus(meta);
|
|
509
|
+
return this.mcpSessionStatus();
|
|
510
|
+
}
|
|
485
511
|
async trustWorkspace() {
|
|
486
512
|
await this.initialized;
|
|
487
513
|
await this.nova.workspaceTrust({ cwd: this.cwd, trusted: true });
|
|
@@ -581,8 +607,12 @@ var SessionRunner = class {
|
|
|
581
607
|
async listModels() {
|
|
582
608
|
await this.initialized;
|
|
583
609
|
const { models } = await this.nova.modelsList();
|
|
584
|
-
for (const model of models) this.
|
|
585
|
-
return models.map((model) => ({ id: model.id, name: model.name }));
|
|
610
|
+
for (const model of models) this.modelInfo.set(model.id, model);
|
|
611
|
+
return models.map((model) => ({ id: model.id, name: model.name, thinking: model.thinking }));
|
|
612
|
+
}
|
|
613
|
+
/** Sync, from the loaded model list: false until it has loaded. */
|
|
614
|
+
hasThinkingLevels(model = this.model) {
|
|
615
|
+
return (this.modelInfo.get(model)?.efforts.length ?? 0) > 0;
|
|
586
616
|
}
|
|
587
617
|
/**
|
|
588
618
|
* Takes the account's default model when none is set, and replaces a saved
|
|
@@ -597,7 +627,7 @@ var SessionRunner = class {
|
|
|
597
627
|
const { models, defaultModel } = await this.nova.modelsList(
|
|
598
628
|
this.model ? { preferred: this.model } : {}
|
|
599
629
|
);
|
|
600
|
-
for (const model of models) this.
|
|
630
|
+
for (const model of models) this.modelInfo.set(model.id, model);
|
|
601
631
|
if (!defaultModel || defaultModel === this.model) return;
|
|
602
632
|
const previous = this.model;
|
|
603
633
|
this.setModel(defaultModel);
|
|
@@ -610,8 +640,41 @@ var SessionRunner = class {
|
|
|
610
640
|
return this.modelCheck;
|
|
611
641
|
}
|
|
612
642
|
async supportsImageInput(model = this.model) {
|
|
613
|
-
|
|
614
|
-
|
|
643
|
+
return (await this.modelDetails(model))?.imageInput ?? false;
|
|
644
|
+
}
|
|
645
|
+
/** What the agent's model list says about `model`; undefined when it isn't listed. */
|
|
646
|
+
async modelDetails(model = this.model) {
|
|
647
|
+
if (!this.modelInfo.has(model)) await this.listModels();
|
|
648
|
+
return this.modelInfo.get(model);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* The level the current model thinks at, as the agent picks it (the
|
|
652
|
+
* chosen one when the model has it); null for a model without levels
|
|
653
|
+
* or before the model list has loaded.
|
|
654
|
+
*/
|
|
655
|
+
currentEffort() {
|
|
656
|
+
const info = this.modelInfo.get(this.model);
|
|
657
|
+
if (!info?.efforts.length) return null;
|
|
658
|
+
return resolveEffort(
|
|
659
|
+
{ reasoning_efforts: info.efforts, reasoning_default: info.reasoningDefault },
|
|
660
|
+
this.effort
|
|
661
|
+
) ?? null;
|
|
662
|
+
}
|
|
663
|
+
/** The current model's levels from the loaded list; empty when it has none. */
|
|
664
|
+
modelEffortLevels() {
|
|
665
|
+
return this.modelInfo.get(this.model)?.efforts ?? [];
|
|
666
|
+
}
|
|
667
|
+
/** Saved and kept across models and sessions; a model without the level uses its default. */
|
|
668
|
+
setEffort(level) {
|
|
669
|
+
this.effort = level;
|
|
670
|
+
saveDefaultEffort(level);
|
|
671
|
+
if (this.agentSessionLoaded) this.applyEffort(this.sessionId);
|
|
672
|
+
}
|
|
673
|
+
applyEffort(sessionId) {
|
|
674
|
+
if (!this.effort) return;
|
|
675
|
+
void this.nova.setSessionConfigOption({ sessionId, configId: "thought_level", value: this.effort }).catch(() => {
|
|
676
|
+
this.acpClient.appendError("Could not change the thinking level.");
|
|
677
|
+
});
|
|
615
678
|
}
|
|
616
679
|
async compactContext() {
|
|
617
680
|
if (this.store.getState().busy || this.promptActive) {
|
|
@@ -890,11 +953,12 @@ var SessionRunner = class {
|
|
|
890
953
|
}
|
|
891
954
|
async createSession() {
|
|
892
955
|
await this.initialized;
|
|
893
|
-
const { sessionId, _meta } = await this.nova.newSession({
|
|
956
|
+
const { sessionId, _meta, modes } = await this.nova.newSession({
|
|
894
957
|
cwd: this.cwd,
|
|
895
958
|
mcpServers: []
|
|
896
959
|
});
|
|
897
960
|
this.sessionId = sessionId;
|
|
961
|
+
this.applyModes(modes);
|
|
898
962
|
this.applyMcpStatus(_meta);
|
|
899
963
|
this.applySkillStatus(_meta);
|
|
900
964
|
this.agentSessionLoaded = true;
|
|
@@ -902,6 +966,7 @@ var SessionRunner = class {
|
|
|
902
966
|
void this.nova.setSessionMode({ sessionId, modeId: this.interactionMode }).catch(() => {
|
|
903
967
|
});
|
|
904
968
|
this.applyPermissionMode(sessionId);
|
|
969
|
+
this.applyEffort(sessionId);
|
|
905
970
|
void this.updateContextUsage();
|
|
906
971
|
void this.refreshContextUsage().catch(() => {
|
|
907
972
|
});
|
|
@@ -937,7 +1002,13 @@ var SessionRunner = class {
|
|
|
937
1002
|
const failures = Array.isArray(record.failures) ? record.failures.flatMap((entry) => {
|
|
938
1003
|
if (!entry || typeof entry !== "object") return [];
|
|
939
1004
|
const item = entry;
|
|
940
|
-
return typeof item.serverName === "string" && typeof item.message === "string" ? [
|
|
1005
|
+
return typeof item.serverName === "string" && typeof item.message === "string" ? [
|
|
1006
|
+
{
|
|
1007
|
+
serverName: item.serverName,
|
|
1008
|
+
message: item.message,
|
|
1009
|
+
...item.needsAuth === true ? { needsAuth: true } : {}
|
|
1010
|
+
}
|
|
1011
|
+
] : [];
|
|
941
1012
|
}) : [];
|
|
942
1013
|
this.mcpStatus = { configured, connected, failures };
|
|
943
1014
|
}
|
|
@@ -981,6 +1052,10 @@ var SessionRunner = class {
|
|
|
981
1052
|
if (this.sessionReady === ready) this.sessionReady = this.createSession();
|
|
982
1053
|
}
|
|
983
1054
|
}
|
|
1055
|
+
applyModes(modes) {
|
|
1056
|
+
if (modes?.availableModes.length) this.acpClient.modeIds = modes.availableModes.map((mode) => mode.id);
|
|
1057
|
+
if (!this.modeIds.includes(this.interactionMode)) this.applyInteractionMode("agent", "Mode: agent");
|
|
1058
|
+
}
|
|
984
1059
|
applyInteractionMode(mode, statusLine) {
|
|
985
1060
|
this.interactionMode = mode;
|
|
986
1061
|
this.store.setState({ interactionMode: mode, statusLine });
|
|
@@ -1450,6 +1525,7 @@ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-run
|
|
|
1450
1525
|
function StatusLine({
|
|
1451
1526
|
ui,
|
|
1452
1527
|
model,
|
|
1528
|
+
effort = null,
|
|
1453
1529
|
mcp,
|
|
1454
1530
|
scroll = 0
|
|
1455
1531
|
}) {
|
|
@@ -1460,6 +1536,7 @@ function StatusLine({
|
|
|
1460
1536
|
ui.statusLine ? /* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: ui.statusLine }) : null,
|
|
1461
1537
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
|
|
1462
1538
|
/* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: model }),
|
|
1539
|
+
effort ? /* @__PURE__ */ jsx2(Text2, { color: palette.muted, children: ` \xB7 thinking ${effort.toLowerCase()}` }) : null,
|
|
1463
1540
|
separator,
|
|
1464
1541
|
ui.interactionMode,
|
|
1465
1542
|
separator,
|
|
@@ -1802,18 +1879,14 @@ function PlanView({
|
|
|
1802
1879
|
] });
|
|
1803
1880
|
}
|
|
1804
1881
|
|
|
1805
|
-
// src/tui/ink/controller.ts
|
|
1806
|
-
import { INTERACTION_MODES } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
1807
|
-
|
|
1808
1882
|
// src/tui/commands/builtins.ts
|
|
1809
|
-
import { isInteractionMode } from "@datalabrotterdam/nova-ai-agent/core/interaction-modes.js";
|
|
1810
1883
|
var permissionModes = ["ask", "acceptEdits", "bypassAll"];
|
|
1811
1884
|
var builtinCommands = [
|
|
1812
1885
|
{
|
|
1813
1886
|
name: "help",
|
|
1814
1887
|
description: "Show available commands",
|
|
1815
1888
|
run(ctx) {
|
|
1816
|
-
const lines = builtinCommands.map((c) => `/${c.name} \u2014 ${c.description}`);
|
|
1889
|
+
const lines = [...builtinCommands.filter((c) => c.available?.(ctx) ?? true), ...ctx.agentCommands()].map((c) => `/${c.name} \u2014 ${c.description}`);
|
|
1817
1890
|
ctx.print(lines.join("\n"));
|
|
1818
1891
|
}
|
|
1819
1892
|
},
|
|
@@ -1894,15 +1967,15 @@ var builtinCommands = [
|
|
|
1894
1967
|
},
|
|
1895
1968
|
{
|
|
1896
1969
|
name: "mode",
|
|
1897
|
-
description: "Switch interaction mode (/mode agent|ask|plan)",
|
|
1970
|
+
description: "Switch interaction mode (/mode agent|ask|plan|<agent file>)",
|
|
1898
1971
|
run(ctx, args) {
|
|
1899
1972
|
const mode = args.trim();
|
|
1900
1973
|
if (!mode) {
|
|
1901
|
-
ctx.print(`Current mode: ${ctx.getInteractionMode()}`);
|
|
1974
|
+
ctx.print(`Current mode: ${ctx.getInteractionMode()} (available: ${ctx.getInteractionModes().join(", ")})`);
|
|
1902
1975
|
return;
|
|
1903
1976
|
}
|
|
1904
|
-
if (!
|
|
1905
|
-
ctx.print(
|
|
1977
|
+
if (!ctx.getInteractionModes().includes(mode)) {
|
|
1978
|
+
ctx.print(`Usage: /mode ${ctx.getInteractionModes().join("|")}`);
|
|
1906
1979
|
return;
|
|
1907
1980
|
}
|
|
1908
1981
|
ctx.setInteractionMode(mode);
|
|
@@ -1944,6 +2017,16 @@ var builtinCommands = [
|
|
|
1944
2017
|
ctx.openModelPicker();
|
|
1945
2018
|
}
|
|
1946
2019
|
},
|
|
2020
|
+
{
|
|
2021
|
+
name: "effort",
|
|
2022
|
+
description: "Set how hard the model thinks (/effort <level>, or no args for a slider)",
|
|
2023
|
+
available: (ctx) => ctx.hasThinkingLevels(),
|
|
2024
|
+
async run(ctx, args) {
|
|
2025
|
+
const level = args.trim();
|
|
2026
|
+
if (level) ctx.print(await ctx.setEffort(level));
|
|
2027
|
+
else await ctx.openEffortPicker();
|
|
2028
|
+
}
|
|
2029
|
+
},
|
|
1947
2030
|
{
|
|
1948
2031
|
name: "tools",
|
|
1949
2032
|
description: "Inspect tool calls from this session",
|
|
@@ -1953,9 +2036,20 @@ var builtinCommands = [
|
|
|
1953
2036
|
},
|
|
1954
2037
|
{
|
|
1955
2038
|
name: "mcp",
|
|
1956
|
-
description: "Inspect
|
|
1957
|
-
run(ctx) {
|
|
1958
|
-
|
|
2039
|
+
description: "Inspect MCP servers, or sign in to one: /mcp login|logout <server>",
|
|
2040
|
+
async run(ctx, args) {
|
|
2041
|
+
const [action, ...rest] = args.trim().split(/\s+/).filter(Boolean);
|
|
2042
|
+
const name = rest.join(" ");
|
|
2043
|
+
if (!action) {
|
|
2044
|
+
await ctx.openMcpInspector();
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
if (action !== "login" && action !== "logout" || !name) {
|
|
2048
|
+
ctx.print("Usage: /mcp, or /mcp login|logout <server>");
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
if (action === "login") ctx.print(`Signing in to "${name}"\u2026 (waits up to 5 minutes for the browser)`);
|
|
2052
|
+
ctx.print(await ctx.mcpSignIn(name, action === "login"));
|
|
1959
2053
|
}
|
|
1960
2054
|
},
|
|
1961
2055
|
{
|
|
@@ -2142,8 +2236,8 @@ function formatJob(job) {
|
|
|
2142
2236
|
|
|
2143
2237
|
// src/tui/commands/index.ts
|
|
2144
2238
|
var COMMANDS = [...builtinCommands];
|
|
2145
|
-
function allCommands() {
|
|
2146
|
-
return COMMANDS;
|
|
2239
|
+
function allCommands(ctx) {
|
|
2240
|
+
return ctx ? COMMANDS.filter((c) => c.available?.(ctx) ?? true) : COMMANDS;
|
|
2147
2241
|
}
|
|
2148
2242
|
function findCommand(name) {
|
|
2149
2243
|
return COMMANDS.find((c) => c.name === name);
|
|
@@ -2361,7 +2455,8 @@ var WorkspaceAutocompleteProvider = class {
|
|
|
2361
2455
|
const mention = extractMentionPrefix(before);
|
|
2362
2456
|
if (mention) return this.mentionSuggestions(mention, options.signal);
|
|
2363
2457
|
if (cursorLine === 0 && before.startsWith("/") && !/\s/.test(before)) {
|
|
2364
|
-
const
|
|
2458
|
+
const commands = typeof this.commands === "function" ? this.commands() : this.commands;
|
|
2459
|
+
const items2 = filterCommands(commands, before.slice(1));
|
|
2365
2460
|
return items2.length ? { items: items2, prefix: before } : null;
|
|
2366
2461
|
}
|
|
2367
2462
|
if (!options.force) return null;
|
|
@@ -2884,6 +2979,40 @@ var SelectionState = class {
|
|
|
2884
2979
|
return "changed";
|
|
2885
2980
|
}
|
|
2886
2981
|
};
|
|
2982
|
+
var SliderState = class {
|
|
2983
|
+
constructor(levels, selected, onSelect, onCancel) {
|
|
2984
|
+
this.levels = levels;
|
|
2985
|
+
this.selected = selected;
|
|
2986
|
+
this.onSelect = onSelect;
|
|
2987
|
+
this.onCancel = onCancel;
|
|
2988
|
+
}
|
|
2989
|
+
levels;
|
|
2990
|
+
selected;
|
|
2991
|
+
onSelect;
|
|
2992
|
+
onCancel;
|
|
2993
|
+
handleKey(input, key) {
|
|
2994
|
+
if (isCancel(input, key)) {
|
|
2995
|
+
this.onCancel();
|
|
2996
|
+
return "closed";
|
|
2997
|
+
}
|
|
2998
|
+
if (key.return) {
|
|
2999
|
+
const level = this.levels[this.selected];
|
|
3000
|
+
if (!level) return "ignored";
|
|
3001
|
+
this.onSelect(level);
|
|
3002
|
+
return "closed";
|
|
3003
|
+
}
|
|
3004
|
+
const last = this.levels.length - 1;
|
|
3005
|
+
let next = this.selected;
|
|
3006
|
+
if (key.leftArrow || key.downArrow) next = Math.max(0, this.selected - 1);
|
|
3007
|
+
else if (key.rightArrow || key.upArrow) next = Math.min(last, this.selected + 1);
|
|
3008
|
+
else if (key.home) next = 0;
|
|
3009
|
+
else if (key.end) next = last;
|
|
3010
|
+
else return "ignored";
|
|
3011
|
+
if (next === this.selected) return "ignored";
|
|
3012
|
+
this.selected = next;
|
|
3013
|
+
return "changed";
|
|
3014
|
+
}
|
|
3015
|
+
};
|
|
2887
3016
|
var ScrollState = class {
|
|
2888
3017
|
constructor(onClose) {
|
|
2889
3018
|
this.onClose = onClose;
|
|
@@ -3132,6 +3261,9 @@ function permissionKey(request, input, key) {
|
|
|
3132
3261
|
return "closed";
|
|
3133
3262
|
}
|
|
3134
3263
|
|
|
3264
|
+
// src/tui/ink/controller.ts
|
|
3265
|
+
import { effortLabel } from "@datalabrotterdam/nova-ai-agent/core/model-capabilities.js";
|
|
3266
|
+
|
|
3135
3267
|
// src/tui/ink/editor/prompt-editor.ts
|
|
3136
3268
|
var MAX_HISTORY = 200;
|
|
3137
3269
|
var PromptEditor = class {
|
|
@@ -3965,8 +4097,8 @@ var TuiController = class {
|
|
|
3965
4097
|
"Models",
|
|
3966
4098
|
models.map((model) => ({
|
|
3967
4099
|
value: model.id,
|
|
3968
|
-
label: model.id,
|
|
3969
|
-
description: model.name
|
|
4100
|
+
label: model.thinking ? `${model.id} \u25C6` : model.id,
|
|
4101
|
+
description: [model.name, model.thinking ? "thinking" : null].filter(Boolean).join(" \xB7 ") || void 0
|
|
3970
4102
|
})),
|
|
3971
4103
|
"model-picker",
|
|
3972
4104
|
(model) => this.setModel(model)
|
|
@@ -3975,6 +4107,58 @@ var TuiController = class {
|
|
|
3975
4107
|
this.showText("Models", errorMessage(error, "Failed to list models."));
|
|
3976
4108
|
}
|
|
3977
4109
|
};
|
|
4110
|
+
effortName() {
|
|
4111
|
+
const level = this.runner.currentEffort();
|
|
4112
|
+
const efforts = level ? this.runner.modelEffortLevels() : [];
|
|
4113
|
+
return level ? effortLabel(level, efforts) : null;
|
|
4114
|
+
}
|
|
4115
|
+
/** The selected model's levels and current one, or why it has none. */
|
|
4116
|
+
async effortLevels() {
|
|
4117
|
+
let info;
|
|
4118
|
+
try {
|
|
4119
|
+
info = await this.runner.modelDetails();
|
|
4120
|
+
} catch (error) {
|
|
4121
|
+
return errorMessage(error, "Could not load the model list.");
|
|
4122
|
+
}
|
|
4123
|
+
const model = this.runner.model || "This model";
|
|
4124
|
+
if (!info?.efforts.length) {
|
|
4125
|
+
return info?.thinking ? `${model} thinks at a fixed depth; there is no level to choose.` : `${model} is not a thinking model; /model lists the ones marked \u25C6.`;
|
|
4126
|
+
}
|
|
4127
|
+
return { efforts: info.efforts, current: this.runner.currentEffort() ?? info.efforts[0] };
|
|
4128
|
+
}
|
|
4129
|
+
/** /effort: a slider over the selected model's levels. */
|
|
4130
|
+
openEffort = async () => {
|
|
4131
|
+
const levels = await this.effortLevels();
|
|
4132
|
+
if (typeof levels === "string") {
|
|
4133
|
+
this.showText("Thinking effort", levels);
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
const state = new SliderState(
|
|
4137
|
+
levels.efforts.map((value) => ({ value, label: effortLabel(value, levels.efforts) })),
|
|
4138
|
+
Math.max(0, levels.efforts.indexOf(levels.current)),
|
|
4139
|
+
(item) => {
|
|
4140
|
+
this.closeOverlay();
|
|
4141
|
+
this.runner.setEffort(item.value);
|
|
4142
|
+
this.store.setState({ statusLine: `Thinking effort: ${item.label}.` });
|
|
4143
|
+
},
|
|
4144
|
+
this.closeOverlay
|
|
4145
|
+
);
|
|
4146
|
+
this.showOverlay({ kind: "slider", title: `Thinking effort \xB7 ${this.runner.model}`, state });
|
|
4147
|
+
};
|
|
4148
|
+
/** /effort <level>: by id ("high") or name ("Extra high", "off"). */
|
|
4149
|
+
setEffort = async (level) => {
|
|
4150
|
+
const levels = await this.effortLevels();
|
|
4151
|
+
if (typeof levels === "string") return levels;
|
|
4152
|
+
const wanted = level.trim().toLowerCase();
|
|
4153
|
+
const value = levels.efforts.find(
|
|
4154
|
+
(candidate) => candidate === wanted || effortLabel(candidate, levels.efforts).toLowerCase() === wanted
|
|
4155
|
+
);
|
|
4156
|
+
const names = levels.efforts.map((candidate) => effortLabel(candidate, levels.efforts).toLowerCase());
|
|
4157
|
+
if (!value) return `${this.runner.model} has these levels: ${names.join(", ")}.`;
|
|
4158
|
+
this.runner.setEffort(value);
|
|
4159
|
+
this.refresh();
|
|
4160
|
+
return `Thinking effort: ${effortLabel(value, levels.efforts)}.`;
|
|
4161
|
+
};
|
|
3978
4162
|
openPermissionModes = () => {
|
|
3979
4163
|
const modes = [
|
|
3980
4164
|
{ value: "ask", label: "ask", description: "Prompt before tools make changes" },
|
|
@@ -4061,7 +4245,7 @@ status: failed - ${failure.message}`);
|
|
|
4061
4245
|
`Skills (${on} on, ${skills.length - on} off)`,
|
|
4062
4246
|
skills.length ? [
|
|
4063
4247
|
...skills.map(
|
|
4064
|
-
(skill) => `${skill.name} [${skill.source}]${skill.enabled ? "" : " (off)"}
|
|
4248
|
+
(skill) => `${skill.name} [${skill.bundledBy ? `bundled with ${skill.bundledBy}` : skill.source}]${skill.enabled ? "" : " (off)"}
|
|
4065
4249
|
${skill.description}
|
|
4066
4250
|
${skill.path}`
|
|
4067
4251
|
),
|
|
@@ -4132,8 +4316,8 @@ ${skill.path}`
|
|
|
4132
4316
|
this.runner.setInteractionMode(mode);
|
|
4133
4317
|
}
|
|
4134
4318
|
cycleInteractionMode() {
|
|
4135
|
-
const
|
|
4136
|
-
this.setInteractionMode(
|
|
4319
|
+
const modes = this.runner.modeIds;
|
|
4320
|
+
this.setInteractionMode(modes[(modes.indexOf(this.runner.interactionMode) + 1) % modes.length]);
|
|
4137
4321
|
}
|
|
4138
4322
|
appendAssistant(text) {
|
|
4139
4323
|
this.store.setState((state) => ({
|
|
@@ -4175,13 +4359,29 @@ ${skill.path}`
|
|
|
4175
4359
|
},
|
|
4176
4360
|
openSessionSwitcher: this.openSessions,
|
|
4177
4361
|
openModelPicker: () => void this.openModels(),
|
|
4362
|
+
hasThinkingLevels: () => this.runner.hasThinkingLevels(),
|
|
4363
|
+
openEffortPicker: () => this.openEffort(),
|
|
4364
|
+
setEffort: (level) => this.setEffort(level),
|
|
4178
4365
|
getModel: () => this.runner.model,
|
|
4179
4366
|
setModel: (model) => this.setModel(model),
|
|
4180
4367
|
getInteractionMode: () => this.runner.interactionMode,
|
|
4368
|
+
getInteractionModes: () => this.runner.modeIds,
|
|
4369
|
+
agentCommands: () => this.agentCommands(),
|
|
4181
4370
|
setInteractionMode: (mode) => this.setInteractionMode(mode),
|
|
4182
4371
|
getPermissionMode: () => this.store.getState().permissionMode,
|
|
4183
4372
|
setPermissionMode: (mode) => this.setPermissionMode(mode),
|
|
4184
4373
|
openPermissionPicker: this.openPermissionModes,
|
|
4374
|
+
mcpSignIn: async (serverName, signIn) => {
|
|
4375
|
+
try {
|
|
4376
|
+
const status = await this.runner.mcpSignIn(serverName, signIn);
|
|
4377
|
+
this.refresh();
|
|
4378
|
+
const failure = status.failures.find((item) => item.serverName === serverName);
|
|
4379
|
+
if (!signIn) return `Signed out of "${serverName}".`;
|
|
4380
|
+
return failure ? `Signed in, but "${serverName}" did not connect: ${failure.message}` : `Signed in to "${serverName}"; its tools are available from the next message.`;
|
|
4381
|
+
} catch (error) {
|
|
4382
|
+
return errorMessage(error, signIn ? "Sign-in failed." : "Sign-out failed.");
|
|
4383
|
+
}
|
|
4384
|
+
},
|
|
4185
4385
|
trustWorkspace: async () => {
|
|
4186
4386
|
try {
|
|
4187
4387
|
await this.runner.trustWorkspace();
|
|
@@ -4208,6 +4408,10 @@ ${skill.path}`
|
|
|
4208
4408
|
killBackgroundJob: (jobId) => this.runner.killBackgroundJob(jobId),
|
|
4209
4409
|
releaseBackgroundJob: (jobId) => this.runner.releaseBackgroundJob(jobId)
|
|
4210
4410
|
};
|
|
4411
|
+
/** The agent's commands the CLI doesn't have itself; a hint is shown after the description. */
|
|
4412
|
+
agentCommands() {
|
|
4413
|
+
return this.runner.agentCommands.filter((command) => !findCommand(command.name)).map(({ name, description, hint }) => ({ name, description: hint ? `${description} ${hint}` : description }));
|
|
4414
|
+
}
|
|
4211
4415
|
// ------------------------------------------------------------ prompt
|
|
4212
4416
|
submit = async (text, images = [], pastes = []) => {
|
|
4213
4417
|
const value = text.trim();
|
|
@@ -4227,6 +4431,10 @@ ${skill.path}`
|
|
|
4227
4431
|
const name = match?.[1] ?? "";
|
|
4228
4432
|
const command = findCommand(name);
|
|
4229
4433
|
if (!command) {
|
|
4434
|
+
if (this.agentCommands().some((agentCommand) => agentCommand.name === name)) {
|
|
4435
|
+
await this.runner.submit(value, images, pastes);
|
|
4436
|
+
return;
|
|
4437
|
+
}
|
|
4230
4438
|
this.appendError(`Unknown command: /${name}`);
|
|
4231
4439
|
return;
|
|
4232
4440
|
}
|
|
@@ -4288,7 +4496,7 @@ ${skill.path}`
|
|
|
4288
4496
|
const fdPath = resolveFdPath();
|
|
4289
4497
|
editor.setAutocompleteProvider(
|
|
4290
4498
|
new WorkspaceAutocompleteProvider(
|
|
4291
|
-
allCommands().map((command) => ({
|
|
4499
|
+
() => [...allCommands(this.commandContext), ...this.agentCommands()].map((command) => ({
|
|
4292
4500
|
name: command.name,
|
|
4293
4501
|
description: command.description
|
|
4294
4502
|
})),
|
|
@@ -4329,6 +4537,7 @@ ${skill.path}`
|
|
|
4329
4537
|
overlay: this.overlay,
|
|
4330
4538
|
question: this.questionState(ui.pendingQuestion),
|
|
4331
4539
|
model: this.runner.model,
|
|
4540
|
+
effort: this.effortName(),
|
|
4332
4541
|
mcp: mcpCounts(this.runner),
|
|
4333
4542
|
expandedTools: this.expandedTools,
|
|
4334
4543
|
exiting: this.exiting,
|
|
@@ -4373,7 +4582,7 @@ function formatMcpServer(server, status) {
|
|
|
4373
4582
|
}
|
|
4374
4583
|
const failure = status.failures.find((item) => item.serverName === server.name);
|
|
4375
4584
|
lines.push(
|
|
4376
|
-
failure ? `status: failed - ${failure.message}` : status.connected.includes(server.name) ? "status: connected" : "status: configured; connection starts with the ACP session"
|
|
4585
|
+
failure?.needsAuth ? `status: needs sign-in - /mcp login ${server.name}` : failure ? `status: failed - ${failure.message}` : status.connected.includes(server.name) ? "status: connected" : "status: configured; connection starts with the ACP session"
|
|
4377
4586
|
);
|
|
4378
4587
|
return lines.join("\n");
|
|
4379
4588
|
}
|
|
@@ -4412,6 +4621,25 @@ function Frame({ title, children, footer }) {
|
|
|
4412
4621
|
footer ? /* @__PURE__ */ jsx5(Text5, { color: palette.faint, wrap: "truncate-end", children: footer }) : null
|
|
4413
4622
|
] });
|
|
4414
4623
|
}
|
|
4624
|
+
function SliderDialog({
|
|
4625
|
+
title,
|
|
4626
|
+
state,
|
|
4627
|
+
isActive = true
|
|
4628
|
+
}) {
|
|
4629
|
+
useDialogInput((input, key) => state.handleKey(input, key), isActive);
|
|
4630
|
+
const current = state.levels[state.selected];
|
|
4631
|
+
return /* @__PURE__ */ jsxs5(Frame, { title, footer: "\u2190\u2192 adjust \xB7 Enter apply \xB7 Esc cancel", children: [
|
|
4632
|
+
/* @__PURE__ */ jsx5(Text5, { wrap: "truncate-end", children: state.levels.map((level, index) => {
|
|
4633
|
+
const selected = index === state.selected;
|
|
4634
|
+
return /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
4635
|
+
index > 0 ? /* @__PURE__ */ jsx5(Text5, { color: palette.faint, children: " \u2500\u2500 " }) : null,
|
|
4636
|
+
/* @__PURE__ */ jsx5(Text5, { color: selected ? palette.primary : palette.faint, children: selected ? "\u25CF " : "\u25CB " }),
|
|
4637
|
+
/* @__PURE__ */ jsx5(Text5, { bold: selected, color: selected ? void 0 : palette.muted, children: level.label })
|
|
4638
|
+
] }, level.value);
|
|
4639
|
+
}) }),
|
|
4640
|
+
current?.description ? /* @__PURE__ */ jsx5(Text5, { color: palette.muted, children: current.description }) : null
|
|
4641
|
+
] });
|
|
4642
|
+
}
|
|
4415
4643
|
function SelectionDialog({
|
|
4416
4644
|
title,
|
|
4417
4645
|
state,
|
|
@@ -4800,7 +5028,7 @@ function App({ controller }) {
|
|
|
4800
5028
|
),
|
|
4801
5029
|
/* @__PURE__ */ jsxs6(Box6, { ref: bottomRef, flexDirection: "column", flexShrink: 0, children: [
|
|
4802
5030
|
/* @__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 })
|
|
5031
|
+
/* @__PURE__ */ jsx6(StatusLine, { ui: view.ui, model: view.model, effort: view.effort, mcp: view.mcp, scroll: view.scroll })
|
|
4804
5032
|
] })
|
|
4805
5033
|
] });
|
|
4806
5034
|
}
|
|
@@ -4843,6 +5071,7 @@ function Surface({
|
|
|
4843
5071
|
const overlay = view.overlay;
|
|
4844
5072
|
if (overlay?.kind === "selection")
|
|
4845
5073
|
return /* @__PURE__ */ jsx6(SelectionDialog, { title: overlay.title, state: overlay.state, height: rows });
|
|
5074
|
+
if (overlay?.kind === "slider") return /* @__PURE__ */ jsx6(SliderDialog, { title: overlay.title, state: overlay.state });
|
|
4846
5075
|
if (overlay?.kind === "text")
|
|
4847
5076
|
return /* @__PURE__ */ jsx6(
|
|
4848
5077
|
ScrollPanel,
|
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.8",
|
|
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.5",
|
|
43
43
|
"chalk": "^5.3.0",
|
|
44
44
|
"cli-highlight": "^2.1.11",
|
|
45
45
|
"diff": "^9.0.0",
|
|
@@ -76,6 +76,6 @@
|
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@datalabrotterdam/nova-sdk": "
|
|
79
|
+
"@datalabrotterdam/nova-sdk": "1.4.0-k8s.3"
|
|
80
80
|
}
|
|
81
81
|
}
|
package/dist/webui-L4GGMXJ3.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// src/webui/index.ts
|
|
2
|
-
async function runWebUi() {
|
|
3
|
-
console.log("nova-ai --web is a work in progress \u2014 the browser UI isn't built yet.");
|
|
4
|
-
console.log("Use `nova-ai` for the terminal UI, `nova-ai -p` headless, or `nova-ai --acp` for editors.");
|
|
5
|
-
}
|
|
6
|
-
var webui_default = runWebUi;
|
|
7
|
-
export {
|
|
8
|
-
webui_default as default
|
|
9
|
-
};
|