nou-cli 0.0.0-stage → 0.1.0
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/.env.example +24 -0
- package/README.md +54 -2
- package/dist/index.js +35 -0
- package/dist/src/agent/index.js +214 -0
- package/dist/src/commands/index.js +115 -0
- package/dist/src/config/index.js +36 -0
- package/dist/src/context/index.js +53 -0
- package/dist/src/git/index.js +94 -0
- package/dist/src/git/tools.js +52 -0
- package/dist/src/plugins/index.js +43 -0
- package/dist/src/providers/anthropic.js +109 -0
- package/dist/src/providers/index.js +46 -0
- package/dist/src/providers/ollama.js +57 -0
- package/dist/src/providers/openai.js +106 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/tools/index.js +100 -0
- package/dist/src/ui/App.js +432 -0
- package/dist/src/ui/Chat.js +90 -0
- package/dist/src/ui/CodeView.js +84 -0
- package/dist/src/ui/Editor.js +165 -0
- package/dist/src/ui/FileTree.js +141 -0
- package/dist/src/ui/Header.js +9 -0
- package/dist/src/ui/Pane.js +8 -0
- package/dist/src/ui/QuickOpen.js +91 -0
- package/dist/src/ui/Settings.js +150 -0
- package/dist/src/ui/text.js +95 -0
- package/dist/src/ui/theme.js +41 -0
- package/dist/src/util/index.js +9 -0
- package/package.json +64 -4
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { highlight, supportsLanguage } from "cli-highlight";
|
|
3
|
+
import { diffLines } from "diff";
|
|
4
|
+
const LANGUAGES = {
|
|
5
|
+
".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
|
|
6
|
+
".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
|
|
7
|
+
".py": "python", ".json": "json", ".md": "markdown", ".css": "css", ".html": "xml",
|
|
8
|
+
".sh": "bash", ".ps1": "powershell", ".yml": "yaml", ".yaml": "yaml", ".go": "go",
|
|
9
|
+
".rs": "rust", ".java": "java", ".c": "c", ".cpp": "cpp", ".cs": "csharp", ".sql": "sql",
|
|
10
|
+
};
|
|
11
|
+
/** Splits file text into display lines: normalizes CRLF and expands tabs. */
|
|
12
|
+
export function splitLines(text) {
|
|
13
|
+
const lines = text.replace(/\r\n/g, "\n").replace(/\t/g, " ").split("\n");
|
|
14
|
+
if (lines.length > 1 && lines[lines.length - 1] === "")
|
|
15
|
+
lines.pop();
|
|
16
|
+
return lines;
|
|
17
|
+
}
|
|
18
|
+
export function highlightLines(filePath, text) {
|
|
19
|
+
const language = LANGUAGES[path.extname(filePath).toLowerCase()];
|
|
20
|
+
const plain = splitLines(text);
|
|
21
|
+
if (!language || !supportsLanguage(language) || text.length > 200_000)
|
|
22
|
+
return plain;
|
|
23
|
+
try {
|
|
24
|
+
return highlight(plain.join("\n"), { language, ignoreIllegals: true }).split("\n");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return plain;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Word-wraps plain text to the given width. */
|
|
31
|
+
export function wrap(text, width) {
|
|
32
|
+
const out = [];
|
|
33
|
+
const max = Math.max(1, width);
|
|
34
|
+
for (const raw of splitLines(text)) {
|
|
35
|
+
if (raw.length <= max) {
|
|
36
|
+
out.push(raw);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
let line = "";
|
|
40
|
+
for (const word of raw.split(/(\s+)/)) {
|
|
41
|
+
if ((line + word).length <= max) {
|
|
42
|
+
line += word;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (line.trim())
|
|
46
|
+
out.push(line.trimEnd());
|
|
47
|
+
line = word.trimStart();
|
|
48
|
+
while (line.length > max) {
|
|
49
|
+
out.push(line.slice(0, max));
|
|
50
|
+
line = line.slice(max);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
out.push(line);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/** Lines up before/after text into rows for a side by side diff. */
|
|
58
|
+
export function sideBySide(before, after) {
|
|
59
|
+
const parts = diffLines(before.replace(/\r\n/g, "\n"), after.replace(/\r\n/g, "\n"));
|
|
60
|
+
const rows = [];
|
|
61
|
+
let ln = 1;
|
|
62
|
+
let rn = 1;
|
|
63
|
+
const cells = (value) => (value ? splitLines(value) : []);
|
|
64
|
+
for (let i = 0; i < parts.length; i++) {
|
|
65
|
+
const part = parts[i];
|
|
66
|
+
if (!part.added && !part.removed) {
|
|
67
|
+
for (const text of cells(part.value))
|
|
68
|
+
rows.push({ left: { n: ln++, text }, right: { n: rn++, text }, changed: false });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const removed = part.removed ? cells(part.value) : [];
|
|
72
|
+
let added = part.added ? cells(part.value) : [];
|
|
73
|
+
if (part.removed && parts[i + 1]?.added) {
|
|
74
|
+
added = cells(parts[++i].value);
|
|
75
|
+
}
|
|
76
|
+
for (let k = 0; k < Math.max(removed.length, added.length); k++) {
|
|
77
|
+
const row = { changed: true };
|
|
78
|
+
if (k < removed.length)
|
|
79
|
+
row.left = { n: ln++, text: removed[k] };
|
|
80
|
+
if (k < added.length)
|
|
81
|
+
row.right = { n: rn++, text: added[k] };
|
|
82
|
+
rows.push(row);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return rows;
|
|
86
|
+
}
|
|
87
|
+
/** 1-based line numbers in `after` that were added or changed. */
|
|
88
|
+
export function changedLines(before, after) {
|
|
89
|
+
const lines = new Set();
|
|
90
|
+
for (const row of sideBySide(before, after)) {
|
|
91
|
+
if (row.changed && row.right)
|
|
92
|
+
lines.add(row.right.n);
|
|
93
|
+
}
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
/** Tokyo Night inspired palette (truecolor; Windows Terminal supports it). */
|
|
3
|
+
export const C = {
|
|
4
|
+
accent: "#7aa2f7",
|
|
5
|
+
accent2: "#bb9af7",
|
|
6
|
+
cyan: "#7dcfff",
|
|
7
|
+
green: "#9ece6a",
|
|
8
|
+
yellow: "#e0af68",
|
|
9
|
+
orange: "#ff9e64",
|
|
10
|
+
red: "#f7768e",
|
|
11
|
+
text: "#c0caf5",
|
|
12
|
+
dim: "#565f89",
|
|
13
|
+
panel: "#1f2335",
|
|
14
|
+
};
|
|
15
|
+
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
16
|
+
const GRADIENT = [C.accent, "#8c9cf7", C.accent2, "#c39af2", "#e08ad6", C.red, C.orange, C.yellow, C.green, C.cyan];
|
|
17
|
+
/** Colors for each character of a label, shifted by `frame` so it shimmers when animated. */
|
|
18
|
+
export function gradient(text, frame = 0) {
|
|
19
|
+
return [...text].map((ch, i) => ({ ch, color: GRADIENT[(i + frame) % GRADIENT.length] }));
|
|
20
|
+
}
|
|
21
|
+
export const GIT_COLORS = {
|
|
22
|
+
M: C.yellow, A: C.green, "?": C.green, D: C.red, R: C.cyan, U: C.orange,
|
|
23
|
+
};
|
|
24
|
+
const ICONS = {
|
|
25
|
+
".ts": ["◆", "#3178c6"], ".tsx": ["◆", C.cyan], ".mts": ["◆", "#3178c6"], ".cts": ["◆", "#3178c6"],
|
|
26
|
+
".js": ["◆", "#f1e05a"], ".jsx": ["◆", C.cyan], ".mjs": ["◆", "#f1e05a"], ".cjs": ["◆", "#f1e05a"],
|
|
27
|
+
".py": ["◆", "#3572a5"], ".json": ["◇", C.yellow], ".md": ["≡", C.text], ".css": ["◆", C.accent2],
|
|
28
|
+
".html": ["◆", C.orange], ".yml": ["◇", C.red], ".yaml": ["◇", C.red], ".go": ["◆", C.cyan],
|
|
29
|
+
".rs": ["◆", C.orange], ".sh": ["$", C.green], ".ps1": ["$", C.accent], ".lock": ["◌", C.dim],
|
|
30
|
+
};
|
|
31
|
+
export function fileIcon(file) {
|
|
32
|
+
const base = path.basename(file).toLowerCase();
|
|
33
|
+
if (base.startsWith(".env"))
|
|
34
|
+
return { icon: "⚿", color: C.yellow };
|
|
35
|
+
if (base.startsWith(".git"))
|
|
36
|
+
return { icon: "◌", color: C.orange };
|
|
37
|
+
if (base === "package.json")
|
|
38
|
+
return { icon: "⬢", color: C.green };
|
|
39
|
+
const [icon, color] = ICONS[path.extname(base)] ?? ["•", C.dim];
|
|
40
|
+
return { icon, color };
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const artWork = `
|
|
2
|
+
.__ __. ______ __ __ ______ __ __
|
|
3
|
+
| \\ | | / __ \\ | | | | / || | | |
|
|
4
|
+
| \\| | | | | | | | | | | ,----'| | | |
|
|
5
|
+
| . \` | | | | | | | | | | | | | | |
|
|
6
|
+
| |\\ | | \`--' | | \`--' | | \`----.| \`----.| |
|
|
7
|
+
|__| \\__| \\______/ \\______/ \\______||_______||__|
|
|
8
|
+
|
|
9
|
+
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nou-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nou": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
".env.example",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20.12"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "npx tsx index.ts",
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"start": "node dist/index.js",
|
|
21
|
+
"typecheck": "tsc --noEmit -p .",
|
|
22
|
+
"lint": "eslint index.ts src",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/JorgeOchoaReyes/nou.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/JorgeOchoaReyes/nou#readme",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"ai",
|
|
32
|
+
"cli",
|
|
33
|
+
"tui",
|
|
34
|
+
"coding-assistant",
|
|
35
|
+
"ollama",
|
|
36
|
+
"claude",
|
|
37
|
+
"llm",
|
|
38
|
+
"agent"
|
|
39
|
+
],
|
|
40
|
+
"author": "Jorge Raul Ochoa-Reyes",
|
|
41
|
+
"license": "ISC",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@anthropic-ai/sdk": "^0.133.0",
|
|
44
|
+
"@clack/prompts": "^1.1.0",
|
|
45
|
+
"chalk": "^5.6.2",
|
|
46
|
+
"cli-highlight": "^2.1.11",
|
|
47
|
+
"diff": "^9.0.0",
|
|
48
|
+
"ink": "^8.0.0",
|
|
49
|
+
"ink-text-input": "^6.0.0",
|
|
50
|
+
"ollama": "^0.6.3",
|
|
51
|
+
"openai": "^7.32.0",
|
|
52
|
+
"react": "^19.3.0",
|
|
53
|
+
"zod": "^4.3.6"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^10.0.1",
|
|
57
|
+
"@types/node": "^25.5.0",
|
|
58
|
+
"@types/react": "^19.3.0",
|
|
59
|
+
"eslint": "^10.2.0",
|
|
60
|
+
"globals": "^17.4.0",
|
|
61
|
+
"jiti": "^2.6.1",
|
|
62
|
+
"tsx": "^4.23.15",
|
|
63
|
+
"typescript": "^6.0.2",
|
|
64
|
+
"typescript-eslint": "^8.58.1"
|
|
65
|
+
}
|
|
66
|
+
}
|