nou-cli 0.5.0 → 0.5.2

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.
@@ -2,8 +2,8 @@ import { existsSync } from "fs";
2
2
  import { createRequire } from "module";
3
3
  import path from "path";
4
4
  import { fileURLToPath } from "url";
5
- import chalk from "chalk";
6
5
  import { Language, Parser } from "web-tree-sitter";
6
+ import { themeColors } from "./themes.js";
7
7
  /** File extension -> grammar name (tree-sitter-<name>.wasm). Only these are shipped with Nou. */
8
8
  const GRAMMARS = {
9
9
  ".ts": "typescript", ".mts": "typescript", ".cts": "typescript", ".tsx": "tsx",
@@ -30,6 +30,11 @@ let init = null;
30
30
  const languages = new Map();
31
31
  let parser = null;
32
32
  const listeners = new Set();
33
+ /** Tells views to re-highlight (a grammar or the fallback highlighter just loaded). */
34
+ export function notifySyntaxLoaded() {
35
+ for (const l of listeners)
36
+ l();
37
+ }
33
38
  /** Called when a grammar finishes loading, so views can re-highlight. */
34
39
  export function onGrammarLoaded(listener) {
35
40
  listeners.add(listener);
@@ -51,8 +56,7 @@ function load(name) {
51
56
  if (!dir)
52
57
  throw new Error("grammars not found");
53
58
  languages.set(name, await Language.load(path.join(dir, `tree-sitter-${name}.wasm`)));
54
- for (const l of listeners)
55
- l();
59
+ notifySyntaxLoaded();
56
60
  })
57
61
  .catch(() => languages.set(name, "failed"));
58
62
  }
@@ -87,20 +91,6 @@ export function preload(file) {
87
91
  if (name)
88
92
  load(name);
89
93
  }
90
- // --- highlighting --------------------------------------------------------------
91
- const COLORS = {
92
- keyword: chalk.hex("#bb9af7"),
93
- string: chalk.hex("#9ece6a"),
94
- number: chalk.hex("#ff9e64"),
95
- comment: chalk.hex("#565f89").italic,
96
- type: chalk.hex("#2ac3de"),
97
- func: chalk.hex("#7aa2f7"),
98
- property: chalk.hex("#73daca"),
99
- constant: chalk.hex("#ff9e64"),
100
- tag: chalk.hex("#f7768e"),
101
- punctuation: chalk.hex("#89ddff"),
102
- plain: chalk.hex("#c0caf5"),
103
- };
104
94
  const STRINGS = /^(string|template_string|string_fragment|string_content|raw_string_literal|interpreted_string_literal|char_literal|character_literal|escape_sequence|string_literal|heredoc_body)$/;
105
95
  const NUMBERS = /^(number|integer|float|integer_literal|float_literal|number_literal|decimal_integer_literal|decimal_floating_point_literal)$/;
106
96
  const TYPES = /^(type_identifier|predefined_type|primitive_type|builtin_type|type)$/;
@@ -178,11 +168,11 @@ export function highlightTree(file, text) {
178
168
  const node = cursor.currentNode;
179
169
  if (node.childCount === 0 || node.type.includes("comment") || (isString(node) && !hasSubstitution(node))) {
180
170
  if (node.startIndex >= pos) {
181
- out += COLORS.plain(text.slice(pos, node.startIndex));
171
+ out += themeColors().plain(text.slice(pos, node.startIndex));
182
172
  const color = colorFor(node);
183
173
  const span = text.slice(node.startIndex, node.endIndex);
184
174
  // Color each line separately so styles don't bleed across line breaks.
185
- out += span.split("\n").map((part) => (color ? COLORS[color](part) : COLORS.plain(part))).join("\n");
175
+ out += span.split("\n").map((part) => (color ? themeColors()[color](part) : themeColors().plain(part))).join("\n");
186
176
  pos = node.endIndex;
187
177
  }
188
178
  }
@@ -194,7 +184,7 @@ export function highlightTree(file, text) {
194
184
  };
195
185
  if (cursor.gotoFirstChild())
196
186
  visit();
197
- out += COLORS.plain(text.slice(pos));
187
+ out += themeColors().plain(text.slice(pos));
198
188
  tree.delete();
199
189
  return out.split("\n");
200
190
  }
@@ -0,0 +1,57 @@
1
+ import chalk, {} from "chalk";
2
+ /** Well-known editor themes; one-dark is the default because it reads well on most dark terminals. */
3
+ const PALETTES = {
4
+ "one-dark": {
5
+ keyword: "#c678dd", string: "#98c379", number: "#d19a66", comment: "#7f848e", type: "#e5c07b", func: "#61afef",
6
+ property: "#e06c75", constant: "#d19a66", tag: "#e06c75", punctuation: "#abb2bf", plain: "#d7dae0",
7
+ },
8
+ "tokyo-night": {
9
+ keyword: "#bb9af7", string: "#9ece6a", number: "#ff9e64", comment: "#737aa2", type: "#2ac3de", func: "#7aa2f7",
10
+ property: "#73daca", constant: "#ff9e64", tag: "#f7768e", punctuation: "#89ddff", plain: "#c0caf5",
11
+ },
12
+ "github-dark": {
13
+ keyword: "#ff7b72", string: "#a5d6ff", number: "#79c0ff", comment: "#8b949e", type: "#ffa657", func: "#d2a8ff",
14
+ property: "#79c0ff", constant: "#79c0ff", tag: "#7ee787", punctuation: "#c9d1d9", plain: "#e6edf3",
15
+ },
16
+ dracula: {
17
+ keyword: "#ff79c6", string: "#f1fa8c", number: "#bd93f9", comment: "#8a94c4", type: "#8be9fd", func: "#50fa7b",
18
+ property: "#66d9ef", constant: "#bd93f9", tag: "#ff79c6", punctuation: "#f8f8f2", plain: "#f8f8f2",
19
+ },
20
+ "high-contrast": {
21
+ keyword: "#ff9cf5", string: "#a6ff8f", number: "#ffd27f", comment: "#b0b0b0", type: "#7fe7ff", func: "#8fc7ff",
22
+ property: "#ffb38a", constant: "#ffd27f", tag: "#ff8a8a", punctuation: "#ffffff", plain: "#ffffff",
23
+ },
24
+ "github-light": {
25
+ keyword: "#cf222e", string: "#0a3069", number: "#0550ae", comment: "#6e7781", type: "#953800", func: "#8250df",
26
+ property: "#0550ae", constant: "#0550ae", tag: "#116329", punctuation: "#24292f", plain: "#24292f",
27
+ },
28
+ };
29
+ const paint = (palette) => Object.fromEntries(Object.entries(palette).map(([role, hex]) => [role, role === "comment" ? chalk.hex(hex).italic : chalk.hex(hex)]));
30
+ let current = "one-dark";
31
+ let colors = paint(PALETTES[current]);
32
+ let version = 0;
33
+ /** The chalk styles for the current theme. */
34
+ export const themeColors = () => colors;
35
+ /** Changes whenever the theme does, so cached highlighting can be redone. */
36
+ export const themeVersion = () => version;
37
+ export function setCodeTheme(theme) {
38
+ if (theme === current || !PALETTES[theme])
39
+ return;
40
+ current = theme;
41
+ colors = paint(PALETTES[theme]);
42
+ version++;
43
+ }
44
+ /** The same colors for highlight.js (cli-highlight), used for languages tree-sitter doesn't cover. */
45
+ export function highlightJsTheme() {
46
+ const c = colors;
47
+ return {
48
+ keyword: c.keyword, built_in: c.type, type: c.type, literal: c.constant, number: c.number, regexp: c.string,
49
+ string: c.string, subst: c.plain, symbol: c.constant, class: c.type, function: c.func, title: c.func, params: c.plain,
50
+ comment: c.comment, doctag: c.comment, meta: c.comment, "meta-keyword": c.keyword, "meta-string": c.string,
51
+ section: c.func, tag: c.tag, name: c.tag, "builtin-name": c.type, attr: c.property, attribute: c.property,
52
+ variable: c.plain, bullet: c.punctuation, code: c.string, emphasis: c.plain.italic, strong: c.plain.bold,
53
+ formula: c.plain, link: c.func, quote: c.comment, "selector-tag": c.tag, "selector-id": c.func, "selector-class": c.func,
54
+ "selector-attr": c.property, "selector-pseudo": c.property, "template-tag": c.keyword, "template-variable": c.plain,
55
+ addition: c.string, deletion: c.tag, default: c.plain,
56
+ };
57
+ }
@@ -3,13 +3,17 @@ export const tools = [
3
3
  type: "function",
4
4
  function: {
5
5
  name: "run_command",
6
- description: "Run a terminal command",
6
+ description: "Run a shell command in the project folder: tests, builds, type checks, git, package scripts. Returns its output (stdout and stderr together) and, when it fails, its exit code. Use grep, glob and read_file rather than grep/find/cat here. Quote paths with spaces. Don't start interactive programs or servers that never exit.",
7
7
  parameters: {
8
8
  type: "object",
9
9
  properties: {
10
10
  command: {
11
11
  type: "string",
12
12
  description: "The shell command to run",
13
+ },
14
+ timeout: {
15
+ type: "number",
16
+ description: "Seconds to wait before stopping it. Defaults to 120, at most 600.",
13
17
  }
14
18
  },
15
19
  required: ["command"]
@@ -20,7 +24,7 @@ export const tools = [
20
24
  type: "function",
21
25
  function: {
22
26
  name: "read_file",
23
- description: "Read a file. Long files come back in pages of up to 1000 lines; the end of each page says how to read on.",
27
+ description: "Read a file. Lines come back numbered (\" 12\\tcode\"); the numbers aren't part of the file. Long files come back in pages of up to 1000 lines; the end of each page says how to read on.",
24
28
  parameters: {
25
29
  type: "object",
26
30
  properties: {
@@ -99,6 +103,31 @@ export const tools = [
99
103
  }
100
104
  }
101
105
  },
106
+ {
107
+ type: "function",
108
+ function: {
109
+ name: "open_file",
110
+ description: "Show a file in the user's code view (the middle panel), scrolled to a line with the given lines highlighted. Use it when the user asks where something is or to show/open/find something: find the exact place first, then open it.",
111
+ parameters: {
112
+ type: "object",
113
+ properties: {
114
+ path: {
115
+ type: "string",
116
+ description: "Path to the file, relative to the project root",
117
+ },
118
+ line: {
119
+ type: "number",
120
+ description: "First line to show and highlight (1-based)",
121
+ },
122
+ end_line: {
123
+ type: "number",
124
+ description: "Last line to highlight, e.g. the end of the function. Defaults to line.",
125
+ }
126
+ },
127
+ required: ["path"]
128
+ }
129
+ }
130
+ },
102
131
  {
103
132
  type: "function",
104
133
  function: {
@@ -145,6 +174,61 @@ export const tools = [
145
174
  }
146
175
  }
147
176
  },
177
+ {
178
+ type: "function",
179
+ function: {
180
+ name: "multi_edit",
181
+ description: "Make several edits to one file at once, shown to the user as one change. Each edit works like edit_file, applied in order to the result of the one before; if any fails, none are made. Use it instead of several edit_file calls on the same file.",
182
+ parameters: {
183
+ type: "object",
184
+ properties: {
185
+ path: {
186
+ type: "string",
187
+ description: "Path to the file to edit"
188
+ },
189
+ edits: {
190
+ type: "array",
191
+ description: "The edits, in order",
192
+ items: {
193
+ type: "object",
194
+ properties: {
195
+ old_string: { type: "string", description: "The exact text to replace" },
196
+ new_string: { type: "string", description: "The text to put in its place" },
197
+ replace_all: { type: "boolean", description: "Replace every occurrence. Defaults to false." }
198
+ },
199
+ required: ["old_string", "new_string"]
200
+ }
201
+ }
202
+ },
203
+ required: ["path", "edits"]
204
+ }
205
+ }
206
+ },
207
+ {
208
+ type: "function",
209
+ function: {
210
+ name: "update_plan",
211
+ description: "Keep a short checklist for a task with several steps: send the whole list each time, with each step's status. The user sees it. Use it for anything with three or more steps; skip it for quick questions and one-line fixes.",
212
+ parameters: {
213
+ type: "object",
214
+ properties: {
215
+ steps: {
216
+ type: "array",
217
+ description: "Every step, in order",
218
+ items: {
219
+ type: "object",
220
+ properties: {
221
+ step: { type: "string", description: "What the step does, in a few words" },
222
+ status: { type: "string", enum: ["pending", "in_progress", "done"], description: "Where it's at" }
223
+ },
224
+ required: ["step", "status"]
225
+ }
226
+ }
227
+ },
228
+ required: ["steps"]
229
+ }
230
+ }
231
+ },
148
232
  {
149
233
  type: "function",
150
234
  function: {