@datalabrotterdam/nova-ai-cli 1.2.0-alpha.4 → 1.2.0-alpha.5
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 +13 -0
- package/README.md +5 -3
- package/dist/index.js +1 -1
- package/dist/{tui-OWU4YGTT.js → tui-4XTC6VJP.js} +167 -63
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# [@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)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **cli:** keep the full-screen frame one row short of the terminal ([37355f4](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/37355f4efb8a237f204c061ad7008faf54a0dd9e))
|
|
7
|
+
* **cli:** pick the account's default model when none is set ([fb0f34c](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/fb0f34c7bac26f1b59c4b6b2245e6fb8365f99f1))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **cli:** full-screen TUI with a pinned prompt and mouse-wheel scrolling ([a68d693](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/a68d6930945bd6440de22c709fc14ae32a9c9f65))
|
|
13
|
+
|
|
1
14
|
# [@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
15
|
|
|
3
16
|
|
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.
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,12 @@ import {
|
|
|
16
16
|
|
|
17
17
|
// src/tui/index.ts
|
|
18
18
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
readCredentials,
|
|
21
|
+
saveDefaultModel as saveDefaultModel2
|
|
22
|
+
} from "@datalabrotterdam/nova-ai-agent/core/credentials.js";
|
|
23
|
+
import { resolveDefaultModel as resolveDefaultModel2 } from "@datalabrotterdam/nova-ai-agent/core/model-capabilities.js";
|
|
24
|
+
import { createNovaClient as createNovaClient2 } from "@datalabrotterdam/nova-ai-agent/core/nova-client.js";
|
|
20
25
|
import { listStoredSessions as listStoredSessions2, loadStoredSession as loadStoredSession2 } from "@datalabrotterdam/nova-ai-agent/core/sessions.js";
|
|
21
26
|
import { setWorkspaceTrusted as setWorkspaceTrusted2 } from "@datalabrotterdam/nova-ai-agent/core/nova-home.js";
|
|
22
27
|
|
|
@@ -115,8 +120,8 @@ The update did not install. Run it yourself: ${update.command}
|
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
// src/tui/ink/app.tsx
|
|
118
|
-
import { Box as Box6, render,
|
|
119
|
-
import { useEffect, useRef, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
123
|
+
import { Box as Box6, render, Text as Text6, useBoxMetrics, useInput as useInput3, useWindowSize } from "ink";
|
|
124
|
+
import { memo, useEffect, useRef, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
120
125
|
import { savedPermissionMode } from "@datalabrotterdam/nova-ai-agent/core/policy/settings.js";
|
|
121
126
|
|
|
122
127
|
// src/tui/session/session-runner.ts
|
|
@@ -1153,6 +1158,17 @@ function kittyKeyboardOverride(env = process.env) {
|
|
|
1153
1158
|
function isLateTerminalReply(input) {
|
|
1154
1159
|
return /^\u001b?\[\?[\d;]*[uc]$/.test(input);
|
|
1155
1160
|
}
|
|
1161
|
+
var MOUSE_REPORTING_ON = "\x1B[?1000h\x1B[?1006h";
|
|
1162
|
+
var MOUSE_REPORTING_OFF = "\x1B[?1000l\x1B[?1006l";
|
|
1163
|
+
var MOUSE_INPUT = /^\u001b?\[<(\d+);\d+;\d+[Mm]$/;
|
|
1164
|
+
function isMouseInput(input) {
|
|
1165
|
+
return MOUSE_INPUT.test(input);
|
|
1166
|
+
}
|
|
1167
|
+
function wheelRows(input) {
|
|
1168
|
+
const button = Number(MOUSE_INPUT.exec(input)?.[1] ?? -1);
|
|
1169
|
+
if (button < 0 || (button & 64) === 0) return 0;
|
|
1170
|
+
return button & 1 ? -3 : 3;
|
|
1171
|
+
}
|
|
1156
1172
|
|
|
1157
1173
|
// src/tui/ink/components/prompt-input.tsx
|
|
1158
1174
|
import { Box, Text, useInput, usePaste } from "ink";
|
|
@@ -1170,7 +1186,8 @@ var palette = {
|
|
|
1170
1186
|
faint: "#64748b",
|
|
1171
1187
|
code: "#fcd34d",
|
|
1172
1188
|
diffAddBg: "#1b3a24",
|
|
1173
|
-
diffRemoveBg: "#3d1f1f"
|
|
1189
|
+
diffRemoveBg: "#3d1f1f",
|
|
1190
|
+
stickyBackground: "#1e293b"
|
|
1174
1191
|
};
|
|
1175
1192
|
var colors = {
|
|
1176
1193
|
primary: chalk.hex(palette.primary),
|
|
@@ -1197,7 +1214,7 @@ function PromptInput({
|
|
|
1197
1214
|
}) {
|
|
1198
1215
|
useSyncExternalStore(editor.subscribe, editor.getVersion);
|
|
1199
1216
|
useInput((input, key) => {
|
|
1200
|
-
if (onKey(input, key)) return;
|
|
1217
|
+
if (isMouseInput(input) || onKey(input, key)) return;
|
|
1201
1218
|
editor.handleKey(input, key);
|
|
1202
1219
|
});
|
|
1203
1220
|
usePaste((text) => editor.paste(text));
|
|
@@ -1483,7 +1500,8 @@ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-run
|
|
|
1483
1500
|
function StatusLine({
|
|
1484
1501
|
ui,
|
|
1485
1502
|
model,
|
|
1486
|
-
mcp
|
|
1503
|
+
mcp,
|
|
1504
|
+
scroll = 0
|
|
1487
1505
|
}) {
|
|
1488
1506
|
const separator = /* @__PURE__ */ jsx2(Text2, { color: palette.faint, children: " | " });
|
|
1489
1507
|
const usage = ui.contextUsage ? `ctx:${formatTokenCount(ui.contextUsage.totalTokens)}${ui.contextUsage.contextWindow ? `/${formatTokenCount(ui.contextUsage.contextWindow)}` : ""}` : null;
|
|
@@ -1511,6 +1529,10 @@ function StatusLine({
|
|
|
1511
1529
|
`mcp:${mcp.connected}/${mcp.configured}`,
|
|
1512
1530
|
mcp.failed ? /* @__PURE__ */ jsx2(Text2, { color: palette.danger, children: ` !${mcp.failed}` }) : null
|
|
1513
1531
|
] }) : null,
|
|
1532
|
+
scroll ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1533
|
+
separator,
|
|
1534
|
+
/* @__PURE__ */ jsx2(Text2, { color: palette.warning, children: `\u2191${scroll} lines \xB7 PgDn/Esc` })
|
|
1535
|
+
] }) : null,
|
|
1514
1536
|
mcp.skills ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1515
1537
|
separator,
|
|
1516
1538
|
`skills:${mcp.skills}`
|
|
@@ -3601,17 +3623,11 @@ var TranscriptCommitter = class {
|
|
|
3601
3623
|
planSignature = null;
|
|
3602
3624
|
snapshot = null;
|
|
3603
3625
|
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
3626
|
sync(state, expanded) {
|
|
3611
3627
|
if (this.snapshot && state === this.lastState && expanded === this.expanded)
|
|
3612
3628
|
return this.snapshot;
|
|
3613
3629
|
this.lastState = state;
|
|
3614
|
-
if (
|
|
3630
|
+
if (state.sessionId !== this.sessionId || expanded !== this.expanded || !this.prefixIntact(state.messages)) {
|
|
3615
3631
|
this.startEpoch(state, expanded);
|
|
3616
3632
|
}
|
|
3617
3633
|
const messages = state.messages;
|
|
@@ -3764,7 +3780,6 @@ var TranscriptCommitter = class {
|
|
|
3764
3780
|
return true;
|
|
3765
3781
|
}
|
|
3766
3782
|
startEpoch(state, expanded) {
|
|
3767
|
-
this.redrawRequested = false;
|
|
3768
3783
|
this.epoch++;
|
|
3769
3784
|
this.sessionId = state.sessionId;
|
|
3770
3785
|
this.expanded = expanded;
|
|
@@ -3810,7 +3825,6 @@ function safeSplitOffset(text, from) {
|
|
|
3810
3825
|
}
|
|
3811
3826
|
|
|
3812
3827
|
// src/tui/ink/controller.ts
|
|
3813
|
-
var CLEAR_SCREEN_AND_SCROLLBACK = "\x1B[2J\x1B[3J\x1B[H";
|
|
3814
3828
|
var EXIT_CONFIRM_MS = 2e3;
|
|
3815
3829
|
var TuiController = class {
|
|
3816
3830
|
constructor(store, runner, io) {
|
|
@@ -3835,6 +3849,8 @@ var TuiController = class {
|
|
|
3835
3849
|
exiting = false;
|
|
3836
3850
|
view;
|
|
3837
3851
|
lastEpoch = 0;
|
|
3852
|
+
scroll = 0;
|
|
3853
|
+
viewport = { rows: 10, contentRows: 0 };
|
|
3838
3854
|
exitArmed = false;
|
|
3839
3855
|
exitTimer;
|
|
3840
3856
|
animationTimer;
|
|
@@ -3893,8 +3909,17 @@ var TuiController = class {
|
|
|
3893
3909
|
this.runner.cancel();
|
|
3894
3910
|
return true;
|
|
3895
3911
|
}
|
|
3912
|
+
if (this.scroll > 0) {
|
|
3913
|
+
this.scrollTo(0);
|
|
3914
|
+
return true;
|
|
3915
|
+
}
|
|
3896
3916
|
return false;
|
|
3897
3917
|
}
|
|
3918
|
+
if (key.pageUp || key.pageDown) {
|
|
3919
|
+
const page = Math.max(1, this.viewport.rows - 2);
|
|
3920
|
+
this.scrollBy(key.pageUp ? page : -page);
|
|
3921
|
+
return true;
|
|
3922
|
+
}
|
|
3898
3923
|
if (key.tab && key.shift) {
|
|
3899
3924
|
this.cycleInteractionMode();
|
|
3900
3925
|
return true;
|
|
@@ -4116,14 +4141,23 @@ ${skill.path}`
|
|
|
4116
4141
|
}
|
|
4117
4142
|
};
|
|
4118
4143
|
/**
|
|
4119
|
-
*
|
|
4120
|
-
*
|
|
4121
|
-
* erased exactly (Ink leaves stale prompt borders), and old scrollback keeps
|
|
4122
|
-
* the old wrapping.
|
|
4144
|
+
* The view reports its transcript size after each layout. While scrolled
|
|
4145
|
+
* up, new output below grows the offset so the rows being read stay put.
|
|
4123
4146
|
*/
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
this.
|
|
4147
|
+
setViewport(rows, contentRows) {
|
|
4148
|
+
const grown = contentRows - this.viewport.contentRows;
|
|
4149
|
+
this.viewport = { rows, contentRows };
|
|
4150
|
+
this.scrollTo(this.scroll > 0 && grown > 0 ? this.scroll + grown : this.scroll);
|
|
4151
|
+
}
|
|
4152
|
+
/** Positive scrolls toward older output. */
|
|
4153
|
+
scrollBy(rows) {
|
|
4154
|
+
this.scrollTo(this.scroll + rows);
|
|
4155
|
+
}
|
|
4156
|
+
scrollTo(offset) {
|
|
4157
|
+
const max = Math.max(0, this.viewport.contentRows - this.viewport.rows);
|
|
4158
|
+
const next = Math.max(0, Math.min(offset, max));
|
|
4159
|
+
if (next === this.scroll) return;
|
|
4160
|
+
this.scroll = next;
|
|
4127
4161
|
this.refresh();
|
|
4128
4162
|
}
|
|
4129
4163
|
toggleToolDetails() {
|
|
@@ -4224,6 +4258,7 @@ ${skill.path}`
|
|
|
4224
4258
|
submit = async (text, images = [], pastes = []) => {
|
|
4225
4259
|
const value = text.trim();
|
|
4226
4260
|
if (!value) return;
|
|
4261
|
+
this.scroll = 0;
|
|
4227
4262
|
this.editor.addToHistory(value);
|
|
4228
4263
|
this.store.setState((state) => ({
|
|
4229
4264
|
inputHistory: [...state.inputHistory, value],
|
|
@@ -4325,7 +4360,10 @@ ${skill.path}`
|
|
|
4325
4360
|
this.view = this.buildView();
|
|
4326
4361
|
if (this.view.transcript.epoch !== this.lastEpoch) {
|
|
4327
4362
|
this.lastEpoch = this.view.transcript.epoch;
|
|
4328
|
-
this.
|
|
4363
|
+
if (this.scroll) {
|
|
4364
|
+
this.scroll = 0;
|
|
4365
|
+
this.view = this.buildView();
|
|
4366
|
+
}
|
|
4329
4367
|
}
|
|
4330
4368
|
for (const listener of this.listeners) listener();
|
|
4331
4369
|
}
|
|
@@ -4339,7 +4377,8 @@ ${skill.path}`
|
|
|
4339
4377
|
model: this.runner.model,
|
|
4340
4378
|
mcp: mcpCounts(this.runner),
|
|
4341
4379
|
expandedTools: this.expandedTools,
|
|
4342
|
-
exiting: this.exiting
|
|
4380
|
+
exiting: this.exiting,
|
|
4381
|
+
scroll: this.scroll
|
|
4343
4382
|
};
|
|
4344
4383
|
}
|
|
4345
4384
|
questionState(pending) {
|
|
@@ -4409,7 +4448,7 @@ function useDialogInput(handle, isActive) {
|
|
|
4409
4448
|
const [, setTick] = useState(0);
|
|
4410
4449
|
useInput2(
|
|
4411
4450
|
(input, key) => {
|
|
4412
|
-
if (handle(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4451
|
+
if (!isMouseInput(input) && handle(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4413
4452
|
},
|
|
4414
4453
|
{ isActive }
|
|
4415
4454
|
);
|
|
@@ -4551,7 +4590,7 @@ function QuestionDialog({
|
|
|
4551
4590
|
const [, setTick] = useState(0);
|
|
4552
4591
|
useInput2(
|
|
4553
4592
|
(input, key) => {
|
|
4554
|
-
if (state.handleKey(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4593
|
+
if (!isMouseInput(input) && state.handleKey(input, key) === "changed") setTick((tick) => tick + 1);
|
|
4555
4594
|
},
|
|
4556
4595
|
{ isActive }
|
|
4557
4596
|
);
|
|
@@ -4633,7 +4672,7 @@ function PermissionDialog({
|
|
|
4633
4672
|
}) {
|
|
4634
4673
|
useInput2(
|
|
4635
4674
|
(input, key) => {
|
|
4636
|
-
if (permissionKey(request, input, key) === "closed") onClose();
|
|
4675
|
+
if (!isMouseInput(input) && permissionKey(request, input, key) === "closed") onClose();
|
|
4637
4676
|
},
|
|
4638
4677
|
{ isActive }
|
|
4639
4678
|
);
|
|
@@ -4684,9 +4723,8 @@ function wrapPlain(text, width) {
|
|
|
4684
4723
|
}
|
|
4685
4724
|
|
|
4686
4725
|
// src/tui/ink/app.tsx
|
|
4687
|
-
import {
|
|
4726
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4688
4727
|
var FRAME_MS = 120;
|
|
4689
|
-
var RESIZE_SETTLE_MS = 100;
|
|
4690
4728
|
async function runInkTui(credentials, cwd, args, options = {}) {
|
|
4691
4729
|
const store = createStore({
|
|
4692
4730
|
messages: [],
|
|
@@ -4728,22 +4766,15 @@ async function runInkTui(credentials, cwd, args, options = {}) {
|
|
|
4728
4766
|
// Ink otherwise turns non-interactive whenever CI is set in the
|
|
4729
4767
|
// environment; the TUI only runs on a terminal anyway.
|
|
4730
4768
|
interactive: Boolean(stdout.isTTY),
|
|
4769
|
+
// Full screen: the transcript scrolls inside the app (PgUp/PgDn) and the
|
|
4770
|
+
// terminal gets its own screen back on exit.
|
|
4771
|
+
alternateScreen: Boolean(stdout.isTTY),
|
|
4731
4772
|
kittyKeyboard: { mode: kittyKeyboard ? "enabled" : "disabled" }
|
|
4732
4773
|
});
|
|
4733
4774
|
controller.start();
|
|
4775
|
+
if (stdout.isTTY) stdout.write(MOUSE_REPORTING_ON);
|
|
4734
4776
|
const onSignal = () => controller.requestExit();
|
|
4735
4777
|
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
4778
|
void (options.checkForUpdate ?? checkForUpdate)().then((updateAvailable) => {
|
|
4748
4779
|
if (updateAvailable) store.setState({ updateAvailable });
|
|
4749
4780
|
});
|
|
@@ -4753,8 +4784,7 @@ async function runInkTui(credentials, cwd, args, options = {}) {
|
|
|
4753
4784
|
await controller.stop();
|
|
4754
4785
|
} finally {
|
|
4755
4786
|
process.off("SIGTERM", onSignal);
|
|
4756
|
-
stdout.
|
|
4757
|
-
if (resizeTimer) clearTimeout(resizeTimer);
|
|
4787
|
+
if (stdout.isTTY) stdout.write(MOUSE_REPORTING_OFF);
|
|
4758
4788
|
instance.unmount();
|
|
4759
4789
|
await instance.waitUntilExit().catch(() => {
|
|
4760
4790
|
});
|
|
@@ -4768,31 +4798,85 @@ function App({ controller }) {
|
|
|
4768
4798
|
const { rows, columns } = useWindowSize();
|
|
4769
4799
|
const bottomRef = useRef(null);
|
|
4770
4800
|
const bottom = useBoxMetrics(bottomRef);
|
|
4801
|
+
const contentRef = useRef(null);
|
|
4802
|
+
const content = useBoxMetrics(contentRef);
|
|
4771
4803
|
const working = useWorking(view);
|
|
4804
|
+
const [userTops, setUserTops] = useState2(/* @__PURE__ */ new Map());
|
|
4805
|
+
useInput3((input) => {
|
|
4806
|
+
const rows2 = wheelRows(input);
|
|
4807
|
+
if (rows2) controller.scrollBy(rows2);
|
|
4808
|
+
});
|
|
4809
|
+
const height = Math.max(4, rows - 1);
|
|
4810
|
+
const viewportRows = Math.max(3, height - (bottom.hasMeasured ? bottom.height : 6));
|
|
4811
|
+
useEffect(() => {
|
|
4812
|
+
if (content.hasMeasured) controller.setViewport(viewportRows, content.height);
|
|
4813
|
+
}, [controller, viewportRows, content.hasMeasured, content.height]);
|
|
4814
|
+
if (view.exiting) return null;
|
|
4772
4815
|
const { transcript } = view;
|
|
4773
|
-
const
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4816
|
+
const blocks = [...transcript.committed, ...transcript.live];
|
|
4817
|
+
const liveKeys = new Set(transcript.live.map((block) => block.key));
|
|
4818
|
+
const onUserTop = (key, top) => setUserTops((tops) => tops.get(key) === top ? tops : new Map(tops).set(key, top));
|
|
4819
|
+
const firstVisibleRow = content.height - viewportRows - view.scroll;
|
|
4820
|
+
let sticky = null;
|
|
4821
|
+
for (const block of blocks) {
|
|
4822
|
+
if (block.kind !== "user") continue;
|
|
4823
|
+
const top = userTops.get(block.key);
|
|
4824
|
+
if (top !== void 0 && top < firstVisibleRow) sticky = block.text;
|
|
4825
|
+
}
|
|
4826
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height, children: [
|
|
4827
|
+
/* @__PURE__ */ jsxs6(
|
|
4828
|
+
Box6,
|
|
4829
|
+
{
|
|
4830
|
+
flexDirection: "column",
|
|
4831
|
+
height: viewportRows,
|
|
4832
|
+
flexShrink: 0,
|
|
4833
|
+
overflowY: "hidden",
|
|
4834
|
+
justifyContent: "flex-end",
|
|
4835
|
+
children: [
|
|
4836
|
+
/* @__PURE__ */ jsxs6(Box6, { ref: contentRef, flexDirection: "column", flexShrink: 0, marginBottom: -view.scroll, children: [
|
|
4837
|
+
blocks.map(
|
|
4838
|
+
(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)
|
|
4839
|
+
),
|
|
4840
|
+
working.line ? /* @__PURE__ */ jsx6(Box6, { marginTop: 1, paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
4841
|
+
/* @__PURE__ */ jsx6(Text6, { color: palette.accent, children: WORKING_FRAMES[working.frame] }),
|
|
4842
|
+
/* @__PURE__ */ jsx6(Text6, { color: palette.muted, children: ` ${working.line}` })
|
|
4843
|
+
] }) }) : null
|
|
4844
|
+
] }),
|
|
4845
|
+
sticky !== null ? /* @__PURE__ */ jsx6(StickyPrompt, { text: sticky, columns }) : null
|
|
4846
|
+
]
|
|
4847
|
+
}
|
|
4848
|
+
),
|
|
4849
|
+
/* @__PURE__ */ jsxs6(Box6, { ref: bottomRef, flexDirection: "column", flexShrink: 0, children: [
|
|
4850
|
+
/* @__PURE__ */ jsx6(Surface, { controller, view, rows: Math.max(6, rows - 4), columns }),
|
|
4851
|
+
/* @__PURE__ */ jsx6(StatusLine, { ui: view.ui, model: view.model, mcp: view.mcp, scroll: view.scroll })
|
|
4793
4852
|
] })
|
|
4794
4853
|
] });
|
|
4795
4854
|
}
|
|
4855
|
+
var CommittedBlock = memo(function CommittedBlock2({
|
|
4856
|
+
block,
|
|
4857
|
+
onUserTop
|
|
4858
|
+
}) {
|
|
4859
|
+
if (block.kind !== "user") {
|
|
4860
|
+
return /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(TranscriptBlockView, { block }) });
|
|
4861
|
+
}
|
|
4862
|
+
return /* @__PURE__ */ jsx6(UserAnchor, { block, onUserTop, children: /* @__PURE__ */ jsx6(TranscriptBlockView, { block }) });
|
|
4863
|
+
}, (before, after) => before.block === after.block);
|
|
4864
|
+
function UserAnchor({
|
|
4865
|
+
block,
|
|
4866
|
+
onUserTop,
|
|
4867
|
+
children
|
|
4868
|
+
}) {
|
|
4869
|
+
const ref = useRef(null);
|
|
4870
|
+
const { top, hasMeasured } = useBoxMetrics(ref);
|
|
4871
|
+
useEffect(() => {
|
|
4872
|
+
if (hasMeasured) onUserTop(block.key, top);
|
|
4873
|
+
}, [block.key, top, hasMeasured]);
|
|
4874
|
+
return /* @__PURE__ */ jsx6(Box6, { ref, marginTop: 1, children });
|
|
4875
|
+
}
|
|
4876
|
+
function StickyPrompt({ text, columns }) {
|
|
4877
|
+
const line = `> ${text.replace(/\s+/g, " ").trim()}`;
|
|
4878
|
+
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) }) });
|
|
4879
|
+
}
|
|
4796
4880
|
function Surface({
|
|
4797
4881
|
controller,
|
|
4798
4882
|
view,
|
|
@@ -4859,7 +4943,14 @@ async function runChat(options) {
|
|
|
4859
4943
|
console.error("nova-ai isn't connected to a Nova AI account yet. Run `nova-ai login` first.");
|
|
4860
4944
|
return 1;
|
|
4861
4945
|
}
|
|
4862
|
-
const
|
|
4946
|
+
const model = options.model ?? stored.defaultModel ?? process.env.NOVA_MODEL ?? await pickDefaultModel(stored);
|
|
4947
|
+
if (!model) {
|
|
4948
|
+
console.error("nova-ai has no model to use yet. Start it with one, for example:");
|
|
4949
|
+
console.error(" nova-ai --model <model-id> (or set NOVA_MODEL)");
|
|
4950
|
+
console.error("Then Ctrl+P in nova-ai switches and remembers the model.");
|
|
4951
|
+
return 1;
|
|
4952
|
+
}
|
|
4953
|
+
const credentials = { ...stored, defaultModel: model };
|
|
4863
4954
|
const updateCheck = checkForUpdate().then((update) => withInstallability(update));
|
|
4864
4955
|
const updatedFrom = process.env[UPDATED_FROM_ENV];
|
|
4865
4956
|
if (updatedFrom) {
|
|
@@ -4894,6 +4985,19 @@ async function runChat(options) {
|
|
|
4894
4985
|
}
|
|
4895
4986
|
return 0;
|
|
4896
4987
|
}
|
|
4988
|
+
async function pickDefaultModel(credentials) {
|
|
4989
|
+
try {
|
|
4990
|
+
const { data } = await createNovaClient2(credentials.apiKey).models.list({ limit: 100 });
|
|
4991
|
+
const model = resolveDefaultModel2(data, void 0);
|
|
4992
|
+
if (model) {
|
|
4993
|
+
saveDefaultModel2(model);
|
|
4994
|
+
console.log(`Using model ${model} (Ctrl+P switches).`);
|
|
4995
|
+
}
|
|
4996
|
+
return model;
|
|
4997
|
+
} catch {
|
|
4998
|
+
return void 0;
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
4897
5001
|
async function askToTrust(cwd) {
|
|
4898
5002
|
const prompt = createInterface2({ input: process.stdin, output: process.stdout });
|
|
4899
5003
|
try {
|
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.5",
|
|
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",
|