@datalabrotterdam/nova-ai-cli 1.2.0-alpha.15 → 1.2.0-alpha.17
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 +14 -0
- package/README.md +7 -8
- package/dist/index.js +1 -1
- package/dist/{tui-W6ZQ2SWR.js → tui-VP2V5DOF.js} +95 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.17](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.16...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.17) (2026-10-25)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **cli:** /tasks to monitor and stop background tasks; drop Ctrl+B ([aaa3cb9](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/aaa3cb9dc058ab9a726964414a959084c9237669))
|
|
7
|
+
|
|
8
|
+
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.16](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.15...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.16) (2026-10-25)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **cli:** Ctrl+C clears the prompt; Esc asks about running background tasks ([a7cb854](https://github.com/Datalab-Rotterdam/nova-ai-cli/commit/a7cb854f4494d88851b672af913d7a1dee666684))
|
|
14
|
+
|
|
1
15
|
# [@datalabrotterdam/nova-ai-cli-v1.2.0-alpha.15](https://github.com/Datalab-Rotterdam/nova-ai-cli/compare/@datalabrotterdam/nova-ai-cli@1.2.0-alpha.14...@datalabrotterdam/nova-ai-cli@1.2.0-alpha.15) (2026-10-25)
|
|
2
16
|
|
|
3
17
|
|
package/README.md
CHANGED
|
@@ -75,9 +75,9 @@ The UI runs full screen (the terminal's alternate screen, like `less` or
|
|
|
75
75
|
`vim`) and gives your shell its screen back on exit. The mouse wheel or
|
|
76
76
|
PgUp/PgDn scroll the transcript, Esc jumps back to the newest output, and
|
|
77
77
|
while you read an answer the prompt that asked for it stays pinned on the top
|
|
78
|
-
row.
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
row. Drag over text to select it, then click (or press Enter) to copy it;
|
|
79
|
+
Shift+arrows extend the selection and Esc cancels. `/copy` copies the last
|
|
80
|
+
answer.
|
|
81
81
|
|
|
82
82
|
- **Modes** (Shift+Tab): `agent` does the work; `plan` reads the code, asks
|
|
83
83
|
questions and builds a task list but changes nothing; `ask` only answers;
|
|
@@ -99,17 +99,16 @@ again gives the wheel back). `/copy` copies the last answer.
|
|
|
99
99
|
| Key | Action |
|
|
100
100
|
|---|---|
|
|
101
101
|
| 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) |
|
|
102
|
-
| Esc |
|
|
103
|
-
| Ctrl+C
|
|
102
|
+
| Esc | Interrupt the running turn (asks whether running background tasks keep going) |
|
|
103
|
+
| Ctrl+C | Clear the prompt; on an empty prompt, twice to exit (prints `nova-ai --resume <id>`) |
|
|
104
104
|
| Shift+Tab | Cycle agent / ask / plan / autopilot and your own agents |
|
|
105
105
|
| Ctrl+R / Ctrl+P / Ctrl+K | Sessions / models / permission mode |
|
|
106
|
-
| Ctrl+O / Ctrl+T
|
|
107
|
-
| Ctrl+S | Select mode: the mouse selects text instead of scrolling (again to switch back) |
|
|
106
|
+
| Ctrl+O / Ctrl+T | Thinking and verbose tool output / tool inspector |
|
|
108
107
|
| Alt+V | Paste a clipboard image |
|
|
109
108
|
|
|
110
109
|
Slash commands: `/help`, `/model`, `/effort` (thinking models), `/mode`, `/permission`, `/session`,
|
|
111
110
|
`/resume`, `/clear`, `/compact`, `/copy`, `/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),
|
|
112
|
-
`/tools`, `/mcp`, `/skills`, `/shell` and `/agent` (background jobs), `/trust`
|
|
111
|
+
`/tools`, `/tasks` (watch or stop background shells and agents), `/mcp`, `/skills`, `/shell` and `/agent` (background jobs), `/trust`
|
|
113
112
|
and `/exit`. Your own commands are markdown files in `~/.nova-ai/commands/` or
|
|
114
113
|
the repository's `.nova-ai/commands/` (Claude Code's format, so
|
|
115
114
|
`.claude/commands` works too): the file is the prompt, `$ARGUMENTS` what you
|
package/dist/index.js
CHANGED
|
@@ -2175,6 +2175,13 @@ var builtinCommands = [
|
|
|
2175
2175
|
ctx.openToolInspector();
|
|
2176
2176
|
}
|
|
2177
2177
|
},
|
|
2178
|
+
{
|
|
2179
|
+
name: "tasks",
|
|
2180
|
+
description: "Monitor background shells and agents, and stop one",
|
|
2181
|
+
run(ctx) {
|
|
2182
|
+
return ctx.openTasks();
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2178
2185
|
{
|
|
2179
2186
|
name: "mcp",
|
|
2180
2187
|
description: "Inspect MCP servers, or sign in to one: /mcp login|logout <server>",
|
|
@@ -4179,7 +4186,7 @@ var TuiController = class {
|
|
|
4179
4186
|
if (key.escape) {
|
|
4180
4187
|
if (this.editor.autocomplete) return false;
|
|
4181
4188
|
if (this.store.getState().busy) {
|
|
4182
|
-
this.
|
|
4189
|
+
void this.interrupt();
|
|
4183
4190
|
return true;
|
|
4184
4191
|
}
|
|
4185
4192
|
if (this.scroll > 0) {
|
|
@@ -4212,9 +4219,6 @@ var TuiController = class {
|
|
|
4212
4219
|
case "k":
|
|
4213
4220
|
this.openPermissionModes();
|
|
4214
4221
|
return true;
|
|
4215
|
-
case "b":
|
|
4216
|
-
void this.openBackgroundTasks();
|
|
4217
|
-
return true;
|
|
4218
4222
|
case "o":
|
|
4219
4223
|
this.toggleToolDetails();
|
|
4220
4224
|
return true;
|
|
@@ -4225,7 +4229,12 @@ var TuiController = class {
|
|
|
4225
4229
|
return false;
|
|
4226
4230
|
}
|
|
4227
4231
|
}
|
|
4232
|
+
/** Ctrl+C clears a typed prompt first; on an empty one it arms exit. */
|
|
4228
4233
|
pressCtrlC() {
|
|
4234
|
+
if (this.editor.getText()) {
|
|
4235
|
+
this.editor.setText("");
|
|
4236
|
+
return;
|
|
4237
|
+
}
|
|
4229
4238
|
if (this.exitArmed) {
|
|
4230
4239
|
this.requestExit();
|
|
4231
4240
|
return;
|
|
@@ -4369,36 +4378,91 @@ var TuiController = class {
|
|
|
4369
4378
|
(value) => this.setPermissionMode(value)
|
|
4370
4379
|
);
|
|
4371
4380
|
};
|
|
4372
|
-
|
|
4381
|
+
/** Esc during a turn: stop it, then ask whether background tasks it started keep running. */
|
|
4382
|
+
async interrupt() {
|
|
4383
|
+
this.runner.cancel();
|
|
4384
|
+
const running = (await this.runner.listBackgroundJobs().catch(() => [])).filter(
|
|
4385
|
+
(job) => job.status === "running"
|
|
4386
|
+
);
|
|
4387
|
+
if (running.length === 0) return;
|
|
4388
|
+
const titles = running.map((job) => job.title).join(", ");
|
|
4389
|
+
this.showSelection(
|
|
4390
|
+
`Interrupted. ${running.length === 1 ? "1 background task is" : `${running.length} background tasks are`} still running.`,
|
|
4391
|
+
[
|
|
4392
|
+
{ value: "keep", label: "Keep running", description: titles },
|
|
4393
|
+
{ value: "stop", label: running.length === 1 ? "Stop it" : "Stop them", description: titles }
|
|
4394
|
+
],
|
|
4395
|
+
"background-tasks",
|
|
4396
|
+
(value) => {
|
|
4397
|
+
if (value !== "stop") return;
|
|
4398
|
+
void Promise.allSettled(running.map((job) => this.runner.killBackgroundJob(job.jobId))).then(
|
|
4399
|
+
(results) => {
|
|
4400
|
+
const stopped = results.filter((result) => result.status === "fulfilled").length;
|
|
4401
|
+
this.store.setState({ statusLine: `Stopped ${stopped} of ${running.length} background tasks.` });
|
|
4402
|
+
}
|
|
4403
|
+
);
|
|
4404
|
+
}
|
|
4405
|
+
);
|
|
4406
|
+
}
|
|
4407
|
+
/** /tasks: background shells and agents; pick one to read its output or stop it. */
|
|
4408
|
+
openTasks = async () => {
|
|
4409
|
+
let jobs;
|
|
4373
4410
|
try {
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4411
|
+
jobs = await this.runner.listBackgroundJobs();
|
|
4412
|
+
} catch (error) {
|
|
4413
|
+
this.showText("Background tasks", errorMessage(error, "Failed to list background tasks."));
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
if (jobs.length === 0) {
|
|
4417
|
+
this.showText("Background tasks", "No background shells or agents in this session.");
|
|
4418
|
+
return;
|
|
4419
|
+
}
|
|
4420
|
+
this.showSelection(
|
|
4421
|
+
"Background tasks",
|
|
4422
|
+
jobs.map((job) => ({ value: job.jobId, label: taskLabel(job), description: job.status })),
|
|
4423
|
+
"background-tasks",
|
|
4424
|
+
(jobId) => {
|
|
4425
|
+
const job = jobs.find((candidate) => candidate.jobId === jobId);
|
|
4426
|
+
if (job.status !== "running") {
|
|
4427
|
+
void this.showTaskOutput(jobId);
|
|
4428
|
+
return;
|
|
4429
|
+
}
|
|
4430
|
+
this.showSelection(
|
|
4431
|
+
taskLabel(job),
|
|
4432
|
+
[
|
|
4433
|
+
{ value: "output", label: "View output", description: "What it printed so far" },
|
|
4434
|
+
{ value: "stop", label: "Stop", description: "Kill this task" }
|
|
4435
|
+
],
|
|
4436
|
+
"background-tasks",
|
|
4437
|
+
(action) => void (action === "stop" ? this.stopTask(job) : this.showTaskOutput(jobId))
|
|
4438
|
+
);
|
|
4439
|
+
}
|
|
4440
|
+
);
|
|
4441
|
+
};
|
|
4442
|
+
async showTaskOutput(jobId) {
|
|
4443
|
+
try {
|
|
4444
|
+
const result = await this.runner.backgroundOutput(jobId);
|
|
4445
|
+
this.showText(
|
|
4446
|
+
taskLabel(result.job),
|
|
4447
|
+
`status: ${result.job.status}
|
|
4389
4448
|
outputPath: ${result.outputPath ?? result.job.outputPath ?? ""}
|
|
4390
4449
|
|
|
4391
4450
|
${result.output}${result.truncated ? "\n\n[output truncated]" : ""}`
|
|
4392
|
-
)
|
|
4393
|
-
).catch(
|
|
4394
|
-
(error) => this.showText("Background job", errorMessage(error, "Failed to read output."))
|
|
4395
|
-
);
|
|
4396
|
-
}
|
|
4397
4451
|
);
|
|
4398
4452
|
} catch (error) {
|
|
4399
|
-
this.showText("Background
|
|
4453
|
+
this.showText("Background task", errorMessage(error, "Failed to read output."));
|
|
4400
4454
|
}
|
|
4401
|
-
}
|
|
4455
|
+
}
|
|
4456
|
+
/** Stops one task, then shows the list again. */
|
|
4457
|
+
async stopTask(job) {
|
|
4458
|
+
try {
|
|
4459
|
+
await this.runner.killBackgroundJob(job.jobId);
|
|
4460
|
+
this.store.setState({ statusLine: `Stopped ${taskLabel(job)}.` });
|
|
4461
|
+
} catch (error) {
|
|
4462
|
+
this.store.setState({ statusLine: errorMessage(error, `Failed to stop ${taskLabel(job)}.`) });
|
|
4463
|
+
}
|
|
4464
|
+
await this.openTasks();
|
|
4465
|
+
}
|
|
4402
4466
|
openToolInspector = () => {
|
|
4403
4467
|
if (this.toolCalls().length === 0) {
|
|
4404
4468
|
this.showText("Tool calls", "No tool calls yet.");
|
|
@@ -4654,6 +4718,7 @@ ${skill.path}`
|
|
|
4654
4718
|
return "Workspace trusted: its allow rules apply now; MCP servers it declares start with the next session (/clear).";
|
|
4655
4719
|
},
|
|
4656
4720
|
openToolInspector: this.openToolInspector,
|
|
4721
|
+
openTasks: this.openTasks,
|
|
4657
4722
|
openMcpInspector: this.openMcpInspector,
|
|
4658
4723
|
openSkillInspector: this.openSkillInspector,
|
|
4659
4724
|
switchSkill: this.switchSkill,
|
|
@@ -4861,6 +4926,9 @@ function mcpCounts(runner) {
|
|
|
4861
4926
|
function errorMessage(error, fallback) {
|
|
4862
4927
|
return error instanceof Error ? error.message : fallback;
|
|
4863
4928
|
}
|
|
4929
|
+
function taskLabel(job) {
|
|
4930
|
+
return `${job.kind === "terminal" ? "shell" : "agent"}: ${job.title}`;
|
|
4931
|
+
}
|
|
4864
4932
|
|
|
4865
4933
|
// src/tui/ink/dialogs/views.tsx
|
|
4866
4934
|
import { Box as Box5, Text as Text5, useInput as useInput2, usePaste as usePaste2 } from "ink";
|
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.17",
|
|
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",
|