nou-cli 0.5.13 → 0.5.15
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/README.md +2 -2
- package/dist/src/engines/claude.js +11 -3
- package/dist/src/engines/codex.js +4 -1
- package/dist/src/engines/external.js +48 -1
- package/dist/src/ui/App.js +42 -11
- package/dist/src/ui/commands.js +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ Every change is reviewed like a code review:
|
|
|
104
104
|
| <kbd>u</kbd> | undo Nou's last edit |
|
|
105
105
|
| <kbd>v</kbd> | diff the open file vs git `HEAD`, Nou's last edit, or this session · <kbd>n</kbd>/<kbd>N</kbd> jump between changes · <kbd>[</kbd>/<kbd>]</kbd> previous/next file in a session diff |
|
|
106
106
|
| <kbd>c</kbd> | in the file tree: show only changed files |
|
|
107
|
-
| <kbd>f</kbd> | follow mode (off by default): watch along as Nou works. Each file it reads (the exact lines, highlighted) is shown a step at a time, a little behind it, and its edits as they happen. The status bar shows how many steps behind it is; set the speed in settings → Preferences (slow · normal · fast · instant) |
|
|
107
|
+
| <kbd>f</kbd> or `/follow [on\|off]` | follow mode (off by default, and your choice is remembered): watch along as Nou works, with any engine (Codex's `sed`/`cat` reads count too). Each file it reads (the exact lines, highlighted) is shown a step at a time, a little behind it, and its edits as they happen. The status bar shows how many steps behind it is; set the speed in settings → Preferences (slow · normal · fast · instant) |
|
|
108
108
|
| <kbd><</kbd> / <kbd>></kbd> / <kbd>=</kbd> | make the focused pane narrower / wider / reset the layout |
|
|
109
109
|
| <kbd>`</kbd> | a terminal under the code, like VS Code's (also `/terminal`): Enter runs a command, Ctrl+C stops it, ↑↓ history, Esc back to the code, × or `exit` closes it; drag its top border (or <kbd>Alt</kbd>+↑/↓) to make it taller or shorter. Commands run through your shell one at a time, so full-screen programs like vim don't draw there |
|
|
110
110
|
| <kbd>z</kbd> | in the code view: fold every function and block to its first line, or open them all again (also `/fold`, `/unfold`); click a ▾/▸ beside a line number to fold or open just that one |
|
|
@@ -113,7 +113,7 @@ Every change is reviewed like a code review:
|
|
|
113
113
|
| <kbd>m</kbd> | mouse on/off, so you can select and copy text in the terminal (in the chat, just drag over the text: it's copied when you let go; double-click copies a word, path or link, triple-click a paragraph) |
|
|
114
114
|
| <kbd>s</kbd> | settings: model, preferences, plugins |
|
|
115
115
|
| <kbd>y</kbd> / <kbd>n</kbd> | accept / reject a proposed edit or command, from any pane; the rest of Nou keeps working while it waits (<kbd>o</kbd> shows the edit) |
|
|
116
|
-
| <kbd>Enter</kbd> while Nou works | queue the message: it goes when Nou finishes, together with anything else you queued, as one message; commands that only change the screen, like `/terminal`, `/games` or `/open`, run right away. To send now instead of waiting: <kbd>Alt</kbd>+<kbd>Enter</kbd> (stops Nou and sends it), click the queued message, or `/now
|
|
116
|
+
| <kbd>Enter</kbd> while Nou works | queue the message: it goes when Nou finishes, together with anything else you queued, as one message; commands that only change the screen, like `/terminal`, `/games` or `/open`, run right away. To send now instead of waiting: <kbd>Alt</kbd>+<kbd>Enter</kbd> (stops Nou and sends it), click the queued message, or `/now`. Nou answers it first, then finishes what it was working on, unless your message says to drop it; or turn on **Send while Nou works** in settings so <kbd>Enter</kbd> sends right away (<kbd>Alt</kbd>+<kbd>Enter</kbd> then queues) |
|
|
117
117
|
| <kbd>Esc</kbd> | stop Nou (anything queued goes back in the input) · close a diff · back to chat |
|
|
118
118
|
| <kbd>Ctrl</kbd>+<kbd>C</kbd> | stop Nou while it works · press twice to quit |
|
|
119
119
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { toRelative } from "../commands/index.js";
|
|
4
|
-
import { ExternalEngine, replaceIn } from "./external.js";
|
|
4
|
+
import { ExternalEngine, readsInCommand, replaceIn } from "./external.js";
|
|
5
5
|
import { loadPackage } from "./index.js";
|
|
6
6
|
const EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
|
7
7
|
/** Claude Code tools that change files; Nou previews and approves them. */
|
|
@@ -95,8 +95,16 @@ export class ClaudeCodeEngine extends ExternalEngine {
|
|
|
95
95
|
? { hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: `This edit introduced problems; fix them:\n${problems}` } }
|
|
96
96
|
: {};
|
|
97
97
|
}
|
|
98
|
-
if (tool === "Read" && args.file_path)
|
|
99
|
-
|
|
98
|
+
if (tool === "Read" && args.file_path) {
|
|
99
|
+
// A part of the file (offset and limit, in lines): follow mode shows those lines.
|
|
100
|
+
const from = typeof args.offset === "number" ? Math.max(1, args.offset) : undefined;
|
|
101
|
+
const to = from && typeof args.limit === "number" ? from + args.limit - 1 : undefined;
|
|
102
|
+
this.hooks.onRead(toRelative(args.file_path), from, to);
|
|
103
|
+
}
|
|
104
|
+
if ((tool === "Bash" || tool === "PowerShell") && typeof args.command === "string") {
|
|
105
|
+
for (const read of readsInCommand(args.command))
|
|
106
|
+
this.hooks.onRead(read.path, read.line, read.to);
|
|
107
|
+
}
|
|
100
108
|
this.hooks.onEntry({ kind: "tool", text: tool === "Bash" || tool === "PowerShell" ? `Ran: ${args.command}` : describeTool(tool, args) });
|
|
101
109
|
return {};
|
|
102
110
|
};
|
|
@@ -3,7 +3,7 @@ import { realpathSync, statSync } from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { transientMessage } from "../agent/index.js";
|
|
5
5
|
import { listAllFiles } from "../ui/QuickOpen.js";
|
|
6
|
-
import { ExternalEngine } from "./external.js";
|
|
6
|
+
import { ExternalEngine, readsInCommand } from "./external.js";
|
|
7
7
|
import { loadPackage } from "./index.js";
|
|
8
8
|
const EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
|
9
9
|
const MAX_FILES = 5000;
|
|
@@ -166,6 +166,9 @@ export class CodexEngine extends ExternalEngine {
|
|
|
166
166
|
}
|
|
167
167
|
else if (event.type === "item.started" && event.item.type === "command_execution") {
|
|
168
168
|
this.hooks.onBusy(`Codex: ${event.item.command}`);
|
|
169
|
+
// Codex reads files with shell commands: follow mode shows them as it would a read.
|
|
170
|
+
for (const read of readsInCommand(event.item.command))
|
|
171
|
+
this.hooks.onRead(read.path, read.line, read.to);
|
|
169
172
|
}
|
|
170
173
|
else if (event.type === "item.completed") {
|
|
171
174
|
const item = event.item;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "fs";
|
|
1
|
+
import { readFileSync, statSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { newSessionId } from "../agent/index.js";
|
|
4
4
|
import { isInProject, toRelative } from "../commands/index.js";
|
|
@@ -10,6 +10,53 @@ function readOrNull(filePath) {
|
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* The files a shell command reads, so follow mode can show them: engines like Codex read with `sed -n
|
|
15
|
+
* '10,80p' f`, `cat f`, `nl -ba f`, `head -n 40 f` instead of a read tool. Only files that exist in the
|
|
16
|
+
* project count; anything it can't make sense of is no read.
|
|
17
|
+
*/
|
|
18
|
+
export function readsInCommand(command) {
|
|
19
|
+
// Codex runs `bash -lc '<script>'`: look inside the quotes.
|
|
20
|
+
const wrapped = command.match(/^\S*(?:bash|sh|zsh)\s+-l?c\s+(['"])([\s\S]*)\1\s*$/);
|
|
21
|
+
const script = wrapped ? wrapped[2] : command;
|
|
22
|
+
const reads = [];
|
|
23
|
+
for (const part of script.split(/\s*(?:&&|\|\||;|\||\n)\s*/)) {
|
|
24
|
+
const words = (part.match(/'[^']*'|"[^"]*"|\S+/g) ?? []).map((w) => w.replace(/^(['"])(.*)\1$/, "$2"));
|
|
25
|
+
const [tool, ...args] = words;
|
|
26
|
+
if (!tool)
|
|
27
|
+
continue;
|
|
28
|
+
const files = args.filter((a) => !a.startsWith("-") && /[./]|\w\.\w/.test(a));
|
|
29
|
+
let range = {};
|
|
30
|
+
if (tool === "sed" && args.includes("-n")) {
|
|
31
|
+
const m = args.join(" ").match(/(\d+)\s*,\s*(\d+)\s*p/) ?? args.join(" ").match(/(\d+)p/);
|
|
32
|
+
if (!m)
|
|
33
|
+
continue;
|
|
34
|
+
range = { line: Number(m[1]), to: Number(m[2] ?? m[1]) };
|
|
35
|
+
files.splice(0, files.length, ...files.filter((f) => !/^\d+(,\d+)?p$/.test(f)));
|
|
36
|
+
// `nl -ba f | sed -n '5,9p'`: the lines are of the file read just before it in the pipe.
|
|
37
|
+
const last = reads.at(-1);
|
|
38
|
+
if (!files.length && last && last.line === undefined)
|
|
39
|
+
Object.assign(last, range);
|
|
40
|
+
}
|
|
41
|
+
else if (tool === "head") {
|
|
42
|
+
const n = Number(args.join(" ").match(/-n\s*(\d+)|-(\d+)/)?.slice(1).find(Boolean) ?? 10);
|
|
43
|
+
range = { line: 1, to: n };
|
|
44
|
+
}
|
|
45
|
+
else if (!["cat", "nl", "bat", "less", "more", "tail"].includes(tool))
|
|
46
|
+
continue;
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
try {
|
|
49
|
+
if (!statSync(path.resolve(process.cwd(), file)).isFile() || !isInProject(file))
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
reads.push({ path: toRelative(file), ...range });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return reads;
|
|
59
|
+
}
|
|
13
60
|
/** Replaces `oldText` with `newText`, as edit tools do; null if it isn't there (the tool will fail on its own). */
|
|
14
61
|
export function replaceIn(text, oldText, newText, all = false) {
|
|
15
62
|
if (!oldText || !text.includes(oldText))
|
package/dist/src/ui/App.js
CHANGED
|
@@ -35,7 +35,7 @@ import { FIND_KEY, isFind } from "./keys.js";
|
|
|
35
35
|
import { FileTree } from "./FileTree.js";
|
|
36
36
|
import { CodeView } from "./CodeView.js";
|
|
37
37
|
import { Editor } from "./Editor.js";
|
|
38
|
-
import { SLASH_COMMANDS, closestCommand, onlyChangesScreen, runsWhileBusy } from "./commands.js";
|
|
38
|
+
import { SLASH_COMMANDS, closestCommand, finishAfter, onlyChangesScreen, runsWhileBusy } from "./commands.js";
|
|
39
39
|
import { WHEEL_STEP, sendWheel } from "./scroll.js";
|
|
40
40
|
import { Chat, chatTabZones } from "./Chat.js";
|
|
41
41
|
import { copyToClipboard, openInBrowser } from "./clipboard.js";
|
|
@@ -1457,6 +1457,17 @@ export function App({ initialModel, permContext, resume }) {
|
|
|
1457
1457
|
if (!followTimer.current)
|
|
1458
1458
|
playFollow();
|
|
1459
1459
|
};
|
|
1460
|
+
/** Turns follow mode on or off (`to`), or flips it; remembered for next time, and the chat says what it does. */
|
|
1461
|
+
const toggleFollow = (to) => {
|
|
1462
|
+
const on = to ?? !followRef.current;
|
|
1463
|
+
followRef.current = on;
|
|
1464
|
+
setFollow(on);
|
|
1465
|
+
if (settingsRef.current.followMode !== on)
|
|
1466
|
+
updateSettings({ followMode: on });
|
|
1467
|
+
info(on
|
|
1468
|
+
? "Follow on: the code view now shows each file Nou reads or edits as it works (f, the follow chip or /follow off to stop)."
|
|
1469
|
+
: "Follow off: the code view stays on what you have open. Edits waiting for your OK still show.");
|
|
1470
|
+
};
|
|
1460
1471
|
const followStepRef = useRef(followStep);
|
|
1461
1472
|
followStepRef.current = followStep;
|
|
1462
1473
|
useEffect(() => {
|
|
@@ -1570,7 +1581,7 @@ export function App({ initialModel, permContext, resume }) {
|
|
|
1570
1581
|
setChatPrefill((p) => ({ text: "/", nonce: (p?.nonce ?? 0) + 1 }));
|
|
1571
1582
|
}
|
|
1572
1583
|
else if (input === "f")
|
|
1573
|
-
|
|
1584
|
+
toggleFollow();
|
|
1574
1585
|
else if (input === "m")
|
|
1575
1586
|
toggleMouse();
|
|
1576
1587
|
else if (input === "`")
|
|
@@ -1752,6 +1763,12 @@ ${snippet}
|
|
|
1752
1763
|
return info(`No file matches "${wanted}". To find where something is in the code, ask in the chat.`, "error");
|
|
1753
1764
|
return openRef({ path: file, ...(m[2] ? { line: Number(m[2]) } : {}), ...(m[3] ? { to: Number(m[3]) } : {}) });
|
|
1754
1765
|
}
|
|
1766
|
+
case "follow": {
|
|
1767
|
+
const want = arg.trim().toLowerCase();
|
|
1768
|
+
if (want && !/^(on|off)$/.test(want))
|
|
1769
|
+
return info("Use /follow on or /follow off (or just /follow to switch).", "error");
|
|
1770
|
+
return toggleFollow(want ? want === "on" : undefined);
|
|
1771
|
+
}
|
|
1755
1772
|
case "effort": {
|
|
1756
1773
|
// Speed vs smarts, by name: /effort smarter (or the effort itself: /effort xhigh).
|
|
1757
1774
|
const options = EFFORTS.map((e) => EFFORT_LABELS[e].name.toLowerCase()).join(", ");
|
|
@@ -2017,6 +2034,17 @@ ${snippet}
|
|
|
2017
2034
|
agentRef.current?.cancel();
|
|
2018
2035
|
stopped.current = true;
|
|
2019
2036
|
};
|
|
2037
|
+
/** What Nou was working on when a message cut in (see interruptToSend), so it finishes that too. */
|
|
2038
|
+
const unfinished = useRef(null);
|
|
2039
|
+
const lastAsked = useRef(null);
|
|
2040
|
+
/** Stops Nou so what's queued goes now; the request it was on isn't dropped, it's handed back with it. */
|
|
2041
|
+
const interruptToSend = () => {
|
|
2042
|
+
// What the agent was last asked, unfinished work handed back included: cutting in twice keeps both.
|
|
2043
|
+
if (lastAsked.current)
|
|
2044
|
+
unfinished.current = lastAsked.current;
|
|
2045
|
+
stop();
|
|
2046
|
+
stopped.current = false; // what's queued is meant to go
|
|
2047
|
+
};
|
|
2020
2048
|
useEffect(() => {
|
|
2021
2049
|
if (busy || approval || !queued.length)
|
|
2022
2050
|
return;
|
|
@@ -2031,7 +2059,10 @@ ${snippet}
|
|
|
2031
2059
|
const isCommand = (text) => text.startsWith("/");
|
|
2032
2060
|
const take = isCommand(queued[0]) ? 1 : Math.max(1, queued.findIndex(isCommand) === -1 ? queued.length : queued.findIndex(isCommand));
|
|
2033
2061
|
setQueued(queued.slice(take));
|
|
2034
|
-
|
|
2062
|
+
const text = queued.slice(0, take).join("\n\n");
|
|
2063
|
+
const previous = unfinished.current;
|
|
2064
|
+
unfinished.current = null;
|
|
2065
|
+
send(text, previous && !isCommand(text) ? finishAfter(text, previous) : undefined);
|
|
2035
2066
|
}, [busy, approval, queued]);
|
|
2036
2067
|
// When a reply points at places in the code (Nou's engine with show_places, the others with a ```nou-show
|
|
2037
2068
|
// block, which is taken out of the reply), the first one opens highlighted and the rest are listed.
|
|
@@ -2186,8 +2217,7 @@ ${snippet}
|
|
|
2186
2217
|
const sendNow = () => {
|
|
2187
2218
|
if (!queued.length)
|
|
2188
2219
|
return;
|
|
2189
|
-
|
|
2190
|
-
stopped.current = false; // what's queued is meant to go
|
|
2220
|
+
interruptToSend();
|
|
2191
2221
|
};
|
|
2192
2222
|
/**
|
|
2193
2223
|
* From the chat: while Nou works, a message waits its turn, or (`now`: Alt+Enter) stops Nou and goes right
|
|
@@ -2198,14 +2228,14 @@ ${snippet}
|
|
|
2198
2228
|
return send(text);
|
|
2199
2229
|
const now = settings.sendNowWhileBusy ? !alt : alt;
|
|
2200
2230
|
if (now) {
|
|
2201
|
-
|
|
2202
|
-
stopped.current = false; // this one is meant to go
|
|
2231
|
+
interruptToSend();
|
|
2203
2232
|
setQueued((q) => [text, ...q]);
|
|
2204
2233
|
}
|
|
2205
2234
|
else
|
|
2206
2235
|
setQueued((q) => [...q, text]);
|
|
2207
2236
|
};
|
|
2208
|
-
|
|
2237
|
+
/** `forAgent`: what the agent gets instead of `text` (the chat still shows `text`). */
|
|
2238
|
+
const send = (text, forAgent) => {
|
|
2209
2239
|
// Right after /resume lists them, a number picks a conversation instead of being sent to the agent.
|
|
2210
2240
|
const picking = pickingResume.current;
|
|
2211
2241
|
pickingResume.current = false;
|
|
@@ -2222,7 +2252,8 @@ ${snippet}
|
|
|
2222
2252
|
return;
|
|
2223
2253
|
}
|
|
2224
2254
|
setEntries((prev) => [...prev, { kind: "user", text }]);
|
|
2225
|
-
|
|
2255
|
+
lastAsked.current = forAgent ?? text;
|
|
2256
|
+
void agentRef.current.send(forAgent ?? text, editorContext() ?? undefined);
|
|
2226
2257
|
};
|
|
2227
2258
|
function saveFromEditor(text, file = openFile) {
|
|
2228
2259
|
if (!file)
|
|
@@ -2535,7 +2566,7 @@ ${snippet}
|
|
|
2535
2566
|
{ title: "Ask before every edit and command", keys: "Shift+Tab", run: () => cycleAutoMode("ask") },
|
|
2536
2567
|
{ title: "Auto-edit: apply edits without asking", keys: "Shift+Tab", run: () => cycleAutoMode("auto-edit") },
|
|
2537
2568
|
{ title: "Auto: edits and commands without asking", keys: "Shift+Tab", run: () => cycleAutoMode("auto") },
|
|
2538
|
-
{ title: `Follow mode ${follow ? "off" : "on"}: jump to files Nou works on`, keys: "f", run: () =>
|
|
2569
|
+
{ title: `Follow mode ${follow ? "off" : "on"}: jump to files Nou works on`, keys: "f", run: () => toggleFollow() },
|
|
2539
2570
|
{ title: `Replay speed for follow mode: ${settings.followSpeed} (next: ${FOLLOW_SPEEDS[(FOLLOW_SPEEDS.indexOf(settings.followSpeed) + 1) % FOLLOW_SPEEDS.length]})`, keys: "settings", run: () => updateSettings({ followSpeed: FOLLOW_SPEEDS[(FOLLOW_SPEEDS.indexOf(settings.followSpeed) + 1) % FOLLOW_SPEEDS.length] }) },
|
|
2540
2571
|
{ title: `Mouse ${settings.mouse ? "off (select text in the terminal)" : "on"}`, keys: "m", run: toggleMouse },
|
|
2541
2572
|
{ title: "Copy the last reply", keys: "/copy", run: () => void runCommand("/copy") },
|
|
@@ -2611,7 +2642,7 @@ ${snippet}
|
|
|
2611
2642
|
x += width;
|
|
2612
2643
|
};
|
|
2613
2644
|
add(` ${autoMode} `.length, () => cycleAutoMode());
|
|
2614
|
-
add(followChip.length, () =>
|
|
2645
|
+
add(followChip.length, () => toggleFollow());
|
|
2615
2646
|
add(` ✗ ${totals.errors} ▲ ${totals.warnings} `.length, toggleProblems);
|
|
2616
2647
|
if (checking.length)
|
|
2617
2648
|
add(3 + checking.join(", ").length);
|
package/dist/src/ui/commands.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Commands that only change what's on screen (or handle a busy Nou themselves): they run right away
|
|
3
3
|
* while Nou works instead of waiting in the queue behind it.
|
|
4
4
|
*/
|
|
5
|
-
const INSTANT = new Set(["files", "actions", "palette", "zoom", "now", "fold", "unfold", "redraw", "new", "tab", "close", "terminal", "games", "open", "settings", "auto", "diff", "sessions", "history", "problems", "copy", "help", "version", "cancel"]);
|
|
5
|
+
const INSTANT = new Set(["follow", "files", "actions", "palette", "zoom", "now", "fold", "unfold", "redraw", "new", "tab", "close", "terminal", "games", "open", "settings", "auto", "diff", "sessions", "history", "problems", "copy", "help", "version", "cancel"]);
|
|
6
6
|
/** Whether a typed command runs right away while Nou works (see INSTANT); `/showme` counts only to look or step. */
|
|
7
7
|
/**
|
|
8
8
|
* The command a mistyped name means: the same letters in another order ("/termnial"), or one letter off
|
|
@@ -51,6 +51,7 @@ export const SLASH_COMMANDS = [
|
|
|
51
51
|
{ name: "models", args: "[provider]", description: "list the models a provider offers" },
|
|
52
52
|
{ name: "connect", args: "<provider>", description: "enter an API key (and URL) for a provider" },
|
|
53
53
|
{ name: "open", args: "<file>[:line]", description: "open a file in the code view (part of its name is enough)" },
|
|
54
|
+
{ name: "follow", args: "[on|off]", description: "the code view shows each file the agent reads or edits as it works" },
|
|
54
55
|
{ name: "effort", args: "[fastest|fast|balanced|smarter|smartest]", description: "speed vs smarts: how hard the model thinks (cheaper + faster, or smarter)" },
|
|
55
56
|
{ name: "workspace", args: "[new <folder> | n]", description: "several projects in this window, a tab each in the header: open one, or switch" },
|
|
56
57
|
{ name: "files", description: "find a file by part of its name (also Ctrl+P)" },
|
|
@@ -96,3 +97,11 @@ export function matchCommands(typed, extra = []) {
|
|
|
96
97
|
const contains = query ? all.filter((c) => !c.name.startsWith(query) && (c.name.includes(query) || c.description.includes(query))) : [];
|
|
97
98
|
return [...starts.sort((a, b) => Number(b.name === query) - Number(a.name === query)), ...contains];
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* A message sent while Nou was working stops it so it's answered now, but what Nou was on isn't dropped:
|
|
102
|
+
* the agent is told what that was and to finish it after, unless the new message says otherwise.
|
|
103
|
+
*/
|
|
104
|
+
export function finishAfter(text, previous) {
|
|
105
|
+
const was = previous.length > 6000 ? `${previous.slice(0, 6000)}…` : previous;
|
|
106
|
+
return `${text}\n\n[Sent while you were still working on my previous message, so you were interrupted. Handle this first, then carry on with and finish the previous request, picking up where you left off (don't redo finished steps), unless this message cancels or replaces it. The previous request was:\n${was}]`;
|
|
107
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nou-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
4
4
|
"description": "Nou: an IDE-style AI coding assistant for your terminal. Local (Ollama, LM Studio) and cloud (Claude, OpenAI, Gemini...) models, live diffs, git-aware, built-in editor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|