@datalabrotterdam/nova-ai-cli 1.2.0-alpha.11 → 1.2.0-alpha.12
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,17 @@
|
|
|
1
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.12](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.11...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.12) (2026-10-24)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **cli:** show the model's thinking as one block ([4479cac](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/4479cac70bf1f9973401bdfd420c401e00c11099))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **cli:** collapse the model's thinking by default ([28f04c1](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/28f04c1c5141aac2468c0aa8ba86dc38d885bb4e))
|
|
12
|
+
* **cli:** say when a collapsed tool row has output ([51274a5](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/51274a597700cf3208d48d54c2a32262e8947a96))
|
|
13
|
+
* token and cost totals per session ([8e9aef8](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/8e9aef81b6345832f38d8a2639c35d4cb6fb3b47))
|
|
14
|
+
|
|
1
15
|
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.11](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.10...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.11) (2026-10-24)
|
|
2
16
|
|
|
3
17
|
|
package/README.md
CHANGED
|
@@ -101,11 +101,11 @@ row. To select text, hold Shift (Option in some macOS terminals) while dragging.
|
|
|
101
101
|
| Ctrl+C twice | Exit; prints `nova-ai --resume <id>` |
|
|
102
102
|
| Shift+Tab | Cycle agent / ask / plan / autopilot and your own agents |
|
|
103
103
|
| Ctrl+R / Ctrl+P / Ctrl+K | Sessions / models / permission mode |
|
|
104
|
-
| Ctrl+O / Ctrl+T / Ctrl+B |
|
|
104
|
+
| Ctrl+O / Ctrl+T / Ctrl+B | Thinking and verbose tool output / tool inspector / background jobs |
|
|
105
105
|
| Alt+V | Paste a clipboard image |
|
|
106
106
|
|
|
107
107
|
Slash commands: `/help`, `/model`, `/effort` (thinking models), `/mode`, `/permission`, `/session`,
|
|
108
|
-
`/resume`, `/clear`, `/compact`, `/rewind [n]`, `/queue`, `/steer`, `/usage
|
|
108
|
+
`/resume`, `/clear`, `/compact`, `/rewind [n]`, `/queue`, `/steer`, `/usage` (context, and the session's tokens and estimated cost; the status line shows `≈ €0.0123` once a priced model answered),
|
|
109
109
|
`/tools`, `/mcp`, `/skills`, `/shell` and `/agent` (background jobs), `/trust`
|
|
110
110
|
and `/exit`. Your own commands are markdown files in `~/.nova-ai/commands/` or
|
|
111
111
|
the repository's `.nova-ai/commands/` (Claude Code's format, so
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/tui/session/tui-acp-client.ts
|
|
2
|
+
import { parseUsageTotals } from "@datalabrotterdam/nova-ai-agent/core/usage.js";
|
|
2
3
|
import { NOVA_NOTIFICATIONS } from "@datalabrotterdam/nova-ai-agent/acp/extensions.js";
|
|
3
4
|
import { openBrowser } from "@datalabrotterdam/nova-ai-agent/acp/auth-server.js";
|
|
4
5
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -310,16 +311,15 @@ ${err.message}`;
|
|
|
310
311
|
this.appendStreamingText(update.content.text);
|
|
311
312
|
return;
|
|
312
313
|
case "agent_thought_chunk":
|
|
313
|
-
if (update.content.type === "text" && update.content.text
|
|
314
|
-
this.
|
|
315
|
-
this.appendMessage({
|
|
316
|
-
id: uid(),
|
|
317
|
-
role: "assistant",
|
|
318
|
-
text: update.content.text,
|
|
319
|
-
streaming: false
|
|
320
|
-
});
|
|
314
|
+
if (update.content.type === "text" && update.content.text) {
|
|
315
|
+
this.appendStreamingText(update.content.text, true);
|
|
321
316
|
}
|
|
322
317
|
return;
|
|
318
|
+
case "usage_update": {
|
|
319
|
+
const totals = parseUsageTotals(update._meta?.["nova-ai-cli/usageTotals"]);
|
|
320
|
+
if (totals) this.store.setState({ usageTotals: totals });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
323
|
case "available_commands_update":
|
|
324
324
|
this.agentCommands = update.availableCommands.map(({ name, description, input }) => ({
|
|
325
325
|
name,
|
|
@@ -488,11 +488,13 @@ ${err.message}`;
|
|
|
488
488
|
if (record.toolCallId && record.output)
|
|
489
489
|
this.patchToolCall(record.toolCallId, { output: record.output });
|
|
490
490
|
}
|
|
491
|
-
appendStreamingText(text) {
|
|
491
|
+
appendStreamingText(text, thinking = false) {
|
|
492
|
+
const current = this.streamingMessageId ? this.store.getState().messages.find((message) => message.id === this.streamingMessageId) : void 0;
|
|
493
|
+
if (current?.role === "assistant" && Boolean(current.thinking) !== thinking) this.resetStreaming();
|
|
492
494
|
if (!this.streamingMessageId) {
|
|
493
495
|
const id = uid();
|
|
494
496
|
this.streamingMessageId = id;
|
|
495
|
-
this.appendMessage({ id, role: "assistant", text, streaming: true });
|
|
497
|
+
this.appendMessage({ id, role: "assistant", text, streaming: true, ...thinking ? { thinking: true } : {} });
|
|
496
498
|
return;
|
|
497
499
|
}
|
|
498
500
|
this.store.setState((state) => ({
|
package/dist/index.js
CHANGED
|
@@ -144,7 +144,7 @@ ${MAIN_HELP}
|
|
|
144
144
|
}
|
|
145
145
|
case "print": {
|
|
146
146
|
const update = startUpdateCheck();
|
|
147
|
-
const { default: runHeadless } = await import("./headless-
|
|
147
|
+
const { default: runHeadless } = await import("./headless-XRIRZB7Z.js");
|
|
148
148
|
const code = await runHeadless(command.args);
|
|
149
149
|
if (update) await noticeUpdate(update, 500);
|
|
150
150
|
return code;
|
|
@@ -185,7 +185,7 @@ ${MAIN_HELP}
|
|
|
185
185
|
);
|
|
186
186
|
return 2;
|
|
187
187
|
}
|
|
188
|
-
const { default: runChat } = await import("./tui-
|
|
188
|
+
const { default: runChat } = await import("./tui-RR2LCTL7.js");
|
|
189
189
|
return runChat(options);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
TuiAcpClient,
|
|
12
12
|
createStore
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-KRC3RH3Y.js";
|
|
14
14
|
|
|
15
15
|
// src/tui/index.ts
|
|
16
16
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
@@ -591,7 +591,7 @@ var SessionRunner = class {
|
|
|
591
591
|
this.title = null;
|
|
592
592
|
this.promptActive = false;
|
|
593
593
|
this.agentSessionLoaded = false;
|
|
594
|
-
this.store.setState({ contextUsage: null, plan: [] });
|
|
594
|
+
this.store.setState({ contextUsage: null, usageTotals: null, plan: [] });
|
|
595
595
|
if (previousSessionId)
|
|
596
596
|
await this.nova.closeSession({ sessionId: previousSessionId });
|
|
597
597
|
this.sessionReady = this.createSession();
|
|
@@ -1351,6 +1351,17 @@ function formatElapsedTime(elapsedMs) {
|
|
|
1351
1351
|
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
1352
1352
|
return `${seconds}s`;
|
|
1353
1353
|
}
|
|
1354
|
+
function formatCost(amount, currency = "EUR") {
|
|
1355
|
+
try {
|
|
1356
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency, maximumFractionDigits: amount < 1 ? 4 : 2 }).format(amount);
|
|
1357
|
+
} catch {
|
|
1358
|
+
return `${amount.toFixed(4)} ${currency}`;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function formatUsageTotals(totals) {
|
|
1362
|
+
const cost = totals.cost !== void 0 ? ` \xB7 \u2248 ${formatCost(totals.cost, totals.currency)} (from the models' prices per 1M tokens; cached tokens count as input)` : "";
|
|
1363
|
+
return `This session: ${formatTokenCount(totals.promptTokens)} in${totals.cachedTokens ? ` (${formatTokenCount(totals.cachedTokens)} cached)` : ""} \xB7 ${formatTokenCount(totals.completionTokens)} out${cost}`;
|
|
1364
|
+
}
|
|
1354
1365
|
function formatTokenCount(value) {
|
|
1355
1366
|
if (value >= 1e6) {
|
|
1356
1367
|
const millions = value / 1e6;
|
|
@@ -1547,6 +1558,10 @@ function StatusLine({
|
|
|
1547
1558
|
separator,
|
|
1548
1559
|
usage
|
|
1549
1560
|
] }) : null,
|
|
1561
|
+
ui.usageTotals?.cost !== void 0 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1562
|
+
separator,
|
|
1563
|
+
`\u2248 ${formatCost(ui.usageTotals.cost, ui.usageTotals.currency)}`
|
|
1564
|
+
] }) : null,
|
|
1550
1565
|
ui.queuedCount ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1551
1566
|
separator,
|
|
1552
1567
|
`queued:${ui.queuedCount}`
|
|
@@ -1741,6 +1756,10 @@ function TranscriptBlockView({ block, frame = 0 }) {
|
|
|
1741
1756
|
}
|
|
1742
1757
|
case "assistant": {
|
|
1743
1758
|
const text = block.first ? block.text.replace(/^\s+/, "") : block.text;
|
|
1759
|
+
if (block.thinking) {
|
|
1760
|
+
const lines = text.split("\n").filter((line) => line.trim()).length;
|
|
1761
|
+
return /* @__PURE__ */ jsx4(Prefixed, { prefix: " ", color: palette.faint, children: /* @__PURE__ */ jsx4(Text4, { color: palette.muted, italic: true, children: block.expanded ? `thinking: ${text.trim()}` : block.streaming ? "\u25B8 thinking\u2026" : `\u25B8 thinking (${lines} ${lines === 1 ? "line" : "lines"}) \xB7 Ctrl+O to show` }) });
|
|
1762
|
+
}
|
|
1744
1763
|
return /* @__PURE__ */ jsx4(Prefixed, { prefix: block.first ? `${ASSISTANT_MARKER} ` : " ", color: palette.accent, children: /* @__PURE__ */ jsx4(Markdown, { text: text.trimEnd() }) });
|
|
1745
1764
|
}
|
|
1746
1765
|
case "error":
|
|
@@ -1808,7 +1827,10 @@ function ToolCallBlock({
|
|
|
1808
1827
|
path: call.diff.path,
|
|
1809
1828
|
limit: expanded ? void 0 : COLLAPSED_DIFF_LINES
|
|
1810
1829
|
}
|
|
1811
|
-
) : expanded && !isCompactTool(call) ? /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { color: palette.muted, children: formatToolCallDetails(call) }) }) :
|
|
1830
|
+
) : expanded && !isCompactTool(call) ? /* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { color: palette.muted, children: formatToolCallDetails(call) }) }) : call.status === "completed" && outputLines(call.output).length ? (
|
|
1831
|
+
// Collapsed output says it is there, like collapsed thinking.
|
|
1832
|
+
/* @__PURE__ */ jsx4(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { color: palette.faint, children: `\u25B8 ${outputLines(call.output).length} ${outputLines(call.output).length === 1 ? "line" : "lines"} of output \xB7 ${isCompactTool(call) ? "Ctrl+T to inspect" : "Ctrl+O to show"}` }) })
|
|
1833
|
+
) : null
|
|
1812
1834
|
] });
|
|
1813
1835
|
}
|
|
1814
1836
|
function ActivityBlock({
|
|
@@ -3770,7 +3792,7 @@ var TranscriptCommitter = class {
|
|
|
3770
3792
|
);
|
|
3771
3793
|
break;
|
|
3772
3794
|
case "assistant":
|
|
3773
|
-
this.emitAssistant(message, committing, emit);
|
|
3795
|
+
this.emitAssistant(message, committing, emit, expanded);
|
|
3774
3796
|
if (message.streaming) committing = false;
|
|
3775
3797
|
break;
|
|
3776
3798
|
}
|
|
@@ -3782,14 +3804,22 @@ var TranscriptCommitter = class {
|
|
|
3782
3804
|
this.snapshot = { epoch: this.epoch, committed: this.committedView, live };
|
|
3783
3805
|
return this.snapshot;
|
|
3784
3806
|
}
|
|
3785
|
-
emitAssistant(message, committing, emit) {
|
|
3807
|
+
emitAssistant(message, committing, emit, expanded) {
|
|
3808
|
+
if (message.thinking) {
|
|
3809
|
+
const block2 = { key: message.id, kind: "assistant", text: message.text, first: true, streaming: message.streaming, thinking: true, expanded };
|
|
3810
|
+
if (message.streaming) emit(block2, false, []);
|
|
3811
|
+
else if (message.text.trim()) emit(block2, true, [message.id]);
|
|
3812
|
+
else if (committing) this.committedIds.push(message.id);
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3786
3815
|
let offset = this.partial?.id === message.id ? this.partial.offset : 0;
|
|
3787
3816
|
const block = (text, streaming) => ({
|
|
3788
3817
|
key: `${message.id}:${offset}`,
|
|
3789
3818
|
kind: "assistant",
|
|
3790
3819
|
text,
|
|
3791
3820
|
first: offset === 0,
|
|
3792
|
-
streaming
|
|
3821
|
+
streaming,
|
|
3822
|
+
...message.thinking ? { thinking: true } : {}
|
|
3793
3823
|
});
|
|
3794
3824
|
if (!message.streaming) {
|
|
3795
3825
|
const rest2 = message.text.slice(offset);
|
|
@@ -4269,7 +4299,8 @@ ${skill.path}`
|
|
|
4269
4299
|
this.showText("Context usage", "Calculating context usage...");
|
|
4270
4300
|
try {
|
|
4271
4301
|
const usage = await this.runner.refreshContextUsage();
|
|
4272
|
-
this.
|
|
4302
|
+
const totals = this.store.getState().usageTotals;
|
|
4303
|
+
this.showText("Context usage", [formatContextUsage(usage), ...totals ? [formatUsageTotals(totals)] : []].join("\n\n"));
|
|
4273
4304
|
} catch (error) {
|
|
4274
4305
|
this.showText("Context usage", errorMessage(error, "Failed to calculate context usage."));
|
|
4275
4306
|
}
|
|
@@ -4297,7 +4328,7 @@ ${skill.path}`
|
|
|
4297
4328
|
toggleToolDetails() {
|
|
4298
4329
|
this.expandedTools = !this.expandedTools;
|
|
4299
4330
|
this.store.setState({
|
|
4300
|
-
statusLine: this.expandedTools ? "
|
|
4331
|
+
statusLine: this.expandedTools ? "Thinking and verbose tool details expanded; read/search stay compact." : "Thinking and verbose tool details collapsed."
|
|
4301
4332
|
});
|
|
4302
4333
|
}
|
|
4303
4334
|
toolCalls() {
|
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.12",
|
|
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.9",
|
|
43
43
|
"@datalabrotterdam/nova-ai-browser-host": "^1.0.0",
|
|
44
44
|
"chalk": "^5.3.0",
|
|
45
45
|
"cli-highlight": "^2.1.11",
|