synara 0.1.3 → 0.1.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/dist/index.js +0 -0
- package/package.json +2 -2
- package/dist/auth.js +0 -50
- package/dist/permissions.js +0 -56
- package/dist/protocol.js +0 -4
- package/dist/tools/executor.js +0 -84
- package/dist/ui/terminal.js +0 -215
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/dist/auth.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Login flow — username/password → D1 auth → JWT stored locally
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import * as readline from "readline";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
const API_URL = process.env.SYNAPSE_API || "https://api.synapse.cloudc.top";
|
|
7
|
-
const TOKEN_DIR = path.join(process.env.HOME || "~", ".synapse");
|
|
8
|
-
const TOKEN_PATH = path.join(TOKEN_DIR, "token");
|
|
9
|
-
function prompt(question, hidden = false) {
|
|
10
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
11
|
-
return new Promise((resolve) => {
|
|
12
|
-
rl.question(question, (answer) => {
|
|
13
|
-
rl.close();
|
|
14
|
-
resolve(answer.trim());
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
export async function login() {
|
|
19
|
-
console.log(chalk.cyan.bold("\n ⚡ Synapse Login\n"));
|
|
20
|
-
const username = await prompt(" Username: ");
|
|
21
|
-
const password = await prompt(" Password: ");
|
|
22
|
-
try {
|
|
23
|
-
const resp = await fetch(`${API_URL}/api/login`, {
|
|
24
|
-
method: "POST",
|
|
25
|
-
headers: { "Content-Type": "application/json" },
|
|
26
|
-
body: JSON.stringify({ username, password }),
|
|
27
|
-
});
|
|
28
|
-
const data = await resp.json();
|
|
29
|
-
if (!resp.ok || !data.token) {
|
|
30
|
-
console.log(chalk.red(`\n ❌ ${data.error || "Login failed"}`));
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
fs.mkdirSync(TOKEN_DIR, { recursive: true });
|
|
34
|
-
fs.writeFileSync(TOKEN_PATH, data.token, { mode: 0o600 });
|
|
35
|
-
console.log(chalk.green("\n ✓ Logged in. Token saved to ~/.synapse/token"));
|
|
36
|
-
}
|
|
37
|
-
catch (e) {
|
|
38
|
-
console.log(chalk.red(`\n ❌ Connection failed: ${e.message}`));
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export function logout() {
|
|
43
|
-
try {
|
|
44
|
-
fs.unlinkSync(TOKEN_PATH);
|
|
45
|
-
console.log(chalk.green(" ✓ Logged out."));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
console.log(chalk.gray(" Already logged out."));
|
|
49
|
-
}
|
|
50
|
-
}
|
package/dist/permissions.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// Permission manager — CLI-side authority on all permission decisions
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
import * as readline from "readline";
|
|
5
|
-
const PROJECT_ROOT = process.cwd();
|
|
6
|
-
const trusted = new Set();
|
|
7
|
-
function isInsideProject(filePath) {
|
|
8
|
-
const resolved = path.resolve(PROJECT_ROOT, filePath);
|
|
9
|
-
return resolved.startsWith(PROJECT_ROOT + path.sep) || resolved === PROJECT_ROOT;
|
|
10
|
-
}
|
|
11
|
-
const READ_ONLY_TOOLS = new Set(["read_file", "grep", "glob", "list_dir"]);
|
|
12
|
-
/**
|
|
13
|
-
* Auto-check: returns "y" if auto-approved, "t" if trusted, or "ask" if needs interactive prompt.
|
|
14
|
-
*/
|
|
15
|
-
export async function askPermission(tool, args, description) {
|
|
16
|
-
// Read-only tools: auto-approve only within project dir
|
|
17
|
-
if (READ_ONLY_TOOLS.has(tool)) {
|
|
18
|
-
const targetPath = args.path || args.pattern || ".";
|
|
19
|
-
if (isInsideProject(targetPath))
|
|
20
|
-
return "y";
|
|
21
|
-
}
|
|
22
|
-
// Already trusted for this session (bash can never be trusted)
|
|
23
|
-
if (tool !== "bash" && trusted.has(tool))
|
|
24
|
-
return "t";
|
|
25
|
-
return "ask";
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Interactive prompt: asks user y/n/t and waits for Enter.
|
|
29
|
-
*/
|
|
30
|
-
export async function askPermissionInteractive(tool, args, description) {
|
|
31
|
-
const hint = tool === "bash"
|
|
32
|
-
? "Allow this action? [y/n]: "
|
|
33
|
-
: "Allow this action? Use 't' to trust (always allow) this tool for the session. [y/n/t]: ";
|
|
34
|
-
const answer = await promptLine("\n" + hint);
|
|
35
|
-
const key = answer.trim().toLowerCase()[0] || "n";
|
|
36
|
-
if (key === "t" && tool !== "bash") {
|
|
37
|
-
trusted.add(tool);
|
|
38
|
-
return "t";
|
|
39
|
-
}
|
|
40
|
-
return key === "y" ? "y" : "n";
|
|
41
|
-
}
|
|
42
|
-
export function readFileForDiff(filePath) {
|
|
43
|
-
try {
|
|
44
|
-
const resolved = path.resolve(PROJECT_ROOT, filePath);
|
|
45
|
-
return fs.readFileSync(resolved, "utf-8");
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function promptLine(question) {
|
|
52
|
-
return new Promise((resolve) => {
|
|
53
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
54
|
-
rl.question(question, (answer) => { rl.close(); resolve(answer); });
|
|
55
|
-
});
|
|
56
|
-
}
|
package/dist/protocol.js
DELETED
package/dist/tools/executor.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
// Local tool executor — runs tools on user's machine
|
|
2
|
-
// All "intelligence" is on the cloud side; this just executes
|
|
3
|
-
import { execSync } from "child_process";
|
|
4
|
-
import * as fs from "fs";
|
|
5
|
-
import * as path from "path";
|
|
6
|
-
const PROJECT_ROOT = process.cwd();
|
|
7
|
-
export async function executeTool(tool, args) {
|
|
8
|
-
try {
|
|
9
|
-
switch (tool) {
|
|
10
|
-
case "read_file": return readFile(args);
|
|
11
|
-
case "write_file": return writeFile(args);
|
|
12
|
-
case "edit_file": return editFile(args);
|
|
13
|
-
case "grep": return grep(args);
|
|
14
|
-
case "glob": return globFiles(args);
|
|
15
|
-
case "list_dir": return listDir(args);
|
|
16
|
-
case "bash": return bash(args);
|
|
17
|
-
default: return { success: false, output: `Unknown tool: ${tool}` };
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
return { success: false, output: e.message };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function resolve(p) {
|
|
25
|
-
return path.resolve(PROJECT_ROOT, p);
|
|
26
|
-
}
|
|
27
|
-
function readFile(args) {
|
|
28
|
-
const content = fs.readFileSync(resolve(args.path), "utf-8");
|
|
29
|
-
return { success: true, output: content };
|
|
30
|
-
}
|
|
31
|
-
function writeFile(args) {
|
|
32
|
-
const full = resolve(args.path);
|
|
33
|
-
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
34
|
-
fs.writeFileSync(full, args.content, "utf-8");
|
|
35
|
-
return { success: true, output: `Written ${args.path} (${args.content.length} chars)` };
|
|
36
|
-
}
|
|
37
|
-
function editFile(args) {
|
|
38
|
-
const full = resolve(args.path);
|
|
39
|
-
const content = fs.readFileSync(full, "utf-8");
|
|
40
|
-
if (!content.includes(args.old_str)) {
|
|
41
|
-
return { success: false, output: `old_str not found in ${args.path}` };
|
|
42
|
-
}
|
|
43
|
-
const count = content.split(args.old_str).length - 1;
|
|
44
|
-
if (count > 1) {
|
|
45
|
-
return { success: false, output: `old_str found ${count} times in ${args.path}, must be unique` };
|
|
46
|
-
}
|
|
47
|
-
fs.writeFileSync(full, content.replace(args.old_str, args.new_str), "utf-8");
|
|
48
|
-
return { success: true, output: `Edited ${args.path}` };
|
|
49
|
-
}
|
|
50
|
-
function grep(args) {
|
|
51
|
-
const target = args.path || ".";
|
|
52
|
-
let cmd = `grep -rn --include='${args.include || "*"}' '${args.pattern}' ${target} 2>/dev/null | head -50`;
|
|
53
|
-
const output = execSync(cmd, { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 10000 });
|
|
54
|
-
return { success: true, output: output || "(no matches)" };
|
|
55
|
-
}
|
|
56
|
-
function globFiles(args) {
|
|
57
|
-
const cmd = `find . -name '${args.pattern}' -not -path '*/node_modules/*' -not -path '*/.git/*' 2>/dev/null | head -100`;
|
|
58
|
-
const output = execSync(cmd, { cwd: PROJECT_ROOT, encoding: "utf-8", timeout: 10000 });
|
|
59
|
-
return { success: true, output: output || "(no files found)" };
|
|
60
|
-
}
|
|
61
|
-
function listDir(args) {
|
|
62
|
-
const target = resolve(args.path || ".");
|
|
63
|
-
const entries = fs.readdirSync(target, { withFileTypes: true });
|
|
64
|
-
const lines = entries
|
|
65
|
-
.filter((e) => !e.name.startsWith(".") && e.name !== "node_modules")
|
|
66
|
-
.map((e) => `${e.isDirectory() ? "📁" : "📄"} ${e.name}`)
|
|
67
|
-
.join("\n");
|
|
68
|
-
return { success: true, output: lines || "(empty)" };
|
|
69
|
-
}
|
|
70
|
-
function bash(args) {
|
|
71
|
-
// Block obviously destructive commands
|
|
72
|
-
const cmd = args.command.trim();
|
|
73
|
-
const blocked = [/\brm\s+-rf\s+[\/~]/, /\bmkfs\b/, /\bdd\s+if=/, /\b>\s*\/dev\/sd/, /\bchmod\s+-R\s+777\s+\//, /\bsudo\s+rm\b/];
|
|
74
|
-
if (blocked.some(p => p.test(cmd))) {
|
|
75
|
-
return { success: false, output: `Blocked: dangerous command detected` };
|
|
76
|
-
}
|
|
77
|
-
const output = execSync(cmd, {
|
|
78
|
-
cwd: PROJECT_ROOT,
|
|
79
|
-
encoding: "utf-8",
|
|
80
|
-
timeout: args.timeout || 30000,
|
|
81
|
-
maxBuffer: 1024 * 1024,
|
|
82
|
-
});
|
|
83
|
-
return { success: true, output };
|
|
84
|
-
}
|
package/dist/ui/terminal.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// Terminal UI — Kiro-style output
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
function stripAnsi(s) {
|
|
6
|
-
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
7
|
-
}
|
|
8
|
-
export function printWelcome() {
|
|
9
|
-
const w = process.stdout.columns || 60;
|
|
10
|
-
const bw = Math.min(w - 4, 62);
|
|
11
|
-
const dim = chalk.gray;
|
|
12
|
-
const hi = chalk.cyan;
|
|
13
|
-
const hiBold = chalk.cyanBright.bold;
|
|
14
|
-
const white = chalk.white;
|
|
15
|
-
const border = hi("│");
|
|
16
|
-
const pad = (s, len) => s + " ".repeat(Math.max(0, len - stripAnsi(s).length));
|
|
17
|
-
const line = (content) => ` ${border} ${pad(content, bw - 4)} ${border}`;
|
|
18
|
-
console.log("");
|
|
19
|
-
console.log(hi(" ███████╗██╗ ██╗███╗ ██╗ █████╗ ██████╗ ███████╗███████╗"));
|
|
20
|
-
console.log(hi(" ██╔════╝╚██╗ ██╔╝████╗ ██║██╔══██╗██╔══██╗██╔════╝██╔════╝"));
|
|
21
|
-
console.log(hiBold(" ███████╗ ╚████╔╝ ██╔██╗ ██║███████║██████╔╝███████╗█████╗ "));
|
|
22
|
-
console.log(hi(" ╚════██║ ╚██╔╝ ██║╚██╗██║██╔══██║██╔═══╝ ╚════██║██╔══╝ "));
|
|
23
|
-
console.log(hiBold(" ███████║ ██║ ██║ ╚████║██║ ██║██║ ███████║███████╗"));
|
|
24
|
-
console.log(hi(" ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝"));
|
|
25
|
-
console.log("");
|
|
26
|
-
console.log(` ${hi("┌" + "─".repeat(bw - 2) + "┐")}`);
|
|
27
|
-
console.log(line(dim("AI-powered coding assistant") + " ".repeat(Math.max(0, bw - 38)) + dim("v0.1.2")));
|
|
28
|
-
console.log(line(""));
|
|
29
|
-
console.log(line(dim("model ") + white("Kimi K2.5") + dim(" context ") + white("256k")));
|
|
30
|
-
console.log(line(dim("location ") + white(process.cwd())));
|
|
31
|
-
console.log(line(""));
|
|
32
|
-
console.log(line(dim("/save save session /load restore session")));
|
|
33
|
-
console.log(line(dim("/compact compress history /context show usage")));
|
|
34
|
-
console.log(line(dim("/tools list tools /quit exit")));
|
|
35
|
-
console.log(line(""));
|
|
36
|
-
console.log(line(dim("Powered by Cloudflare · Built by Bowen Liu")));
|
|
37
|
-
console.log(` ${hi("└" + "─".repeat(bw - 2) + "┘")}`);
|
|
38
|
-
console.log("");
|
|
39
|
-
}
|
|
40
|
-
export function printAssistant(content) {
|
|
41
|
-
console.log(chalk.white(content));
|
|
42
|
-
}
|
|
43
|
-
export function printThinking(content) {
|
|
44
|
-
console.log(chalk.gray(` 💭 ${content}`));
|
|
45
|
-
}
|
|
46
|
-
export function printToolCall(tool, args, description) {
|
|
47
|
-
if (description) {
|
|
48
|
-
console.log(chalk.yellow(`\n 🔧 `) + description + chalk.gray(` (using tool: ${tool})`));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
// Kiro-style: "Creating file.py", "Updating file.py", "Running: cmd"
|
|
52
|
-
let label = "";
|
|
53
|
-
const filePath = args.path || args.pattern || "";
|
|
54
|
-
const fileDisplay = filePath ? chalk.cyan(filePath) : "";
|
|
55
|
-
switch (tool) {
|
|
56
|
-
case "write_file": {
|
|
57
|
-
const exists = filePath && fs.existsSync(path.resolve(process.cwd(), filePath));
|
|
58
|
-
label = exists ? `Updating ${fileDisplay}` : `Creating ${fileDisplay}`;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
case "edit_file":
|
|
62
|
-
label = `Updating ${fileDisplay}`;
|
|
63
|
-
break;
|
|
64
|
-
case "read_file":
|
|
65
|
-
label = `Reading ${fileDisplay}`;
|
|
66
|
-
break;
|
|
67
|
-
case "bash":
|
|
68
|
-
label = `Running: ${chalk.cyan(args.command || "?")}`;
|
|
69
|
-
break;
|
|
70
|
-
case "web_search":
|
|
71
|
-
label = `Searching: ${chalk.cyan(args.query || "?")}`;
|
|
72
|
-
break;
|
|
73
|
-
case "web_fetch":
|
|
74
|
-
label = `Fetching: ${chalk.cyan(args.url || "?")}`;
|
|
75
|
-
break;
|
|
76
|
-
case "grep":
|
|
77
|
-
label = `Searching '${args.pattern}' in ${fileDisplay || "."}`;
|
|
78
|
-
break;
|
|
79
|
-
case "glob":
|
|
80
|
-
label = `Finding files: ${args.pattern}`;
|
|
81
|
-
break;
|
|
82
|
-
case "list_dir":
|
|
83
|
-
label = `Listing ${fileDisplay || "."}`;
|
|
84
|
-
break;
|
|
85
|
-
default:
|
|
86
|
-
label = `${tool}`;
|
|
87
|
-
}
|
|
88
|
-
console.log(chalk.yellow(`\n 🔧 `) + label);
|
|
89
|
-
}
|
|
90
|
-
export function printToolResult(tool, success, output, elapsedMs) {
|
|
91
|
-
const elapsed = elapsedMs != null ? chalk.gray(` - Completed in ${(elapsedMs / 1000).toFixed(1)}s`) : "";
|
|
92
|
-
const bytes = output.length;
|
|
93
|
-
if (!success) {
|
|
94
|
-
console.log(chalk.red(` ✗ Failed`) + elapsed);
|
|
95
|
-
if (output.trim())
|
|
96
|
-
console.log(chalk.gray(` ${output.slice(0, 200)}`));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (tool === "bash") {
|
|
100
|
-
console.log(chalk.blue(` ✓ Command executed`) + elapsed);
|
|
101
|
-
if (output.trim()) {
|
|
102
|
-
const lines = output.split("\n").map(l => ` ${l}`).join("\n");
|
|
103
|
-
console.log(chalk.white(lines));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
else if (tool === "read_file") {
|
|
107
|
-
console.log(chalk.blue(` ✓ Successfully read ${formatBytes(bytes)}`) + elapsed);
|
|
108
|
-
}
|
|
109
|
-
else if (tool === "write_file") {
|
|
110
|
-
console.log(chalk.blue(` ✓ Successfully wrote ${formatBytes(bytes)}`) + elapsed);
|
|
111
|
-
}
|
|
112
|
-
else if (tool === "edit_file") {
|
|
113
|
-
console.log(chalk.blue(` ✓ Successfully edited`) + elapsed);
|
|
114
|
-
}
|
|
115
|
-
else if (tool === "grep") {
|
|
116
|
-
const matchCount = output.trim() ? output.split("\n").length : 0;
|
|
117
|
-
console.log(chalk.blue(` ✓ ${matchCount} matches found`) + elapsed);
|
|
118
|
-
if (output.trim()) {
|
|
119
|
-
const lines = output.split("\n").slice(0, 10).map(l => ` ${chalk.gray(l)}`).join("\n");
|
|
120
|
-
console.log(lines);
|
|
121
|
-
if (matchCount > 10)
|
|
122
|
-
console.log(chalk.gray(` ... (${matchCount} lines)`));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
else if (tool === "glob") {
|
|
126
|
-
const fileCount = output.trim() ? output.split("\n").length : 0;
|
|
127
|
-
console.log(chalk.blue(` ✓ ${fileCount} files found`) + elapsed);
|
|
128
|
-
}
|
|
129
|
-
else if (tool === "list_dir") {
|
|
130
|
-
const entryCount = output.trim() ? output.split("\n").length : 0;
|
|
131
|
-
console.log(chalk.blue(` ✓ ${entryCount} entries`) + elapsed);
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
console.log(chalk.blue(` ✓ Done`) + elapsed);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function formatBytes(bytes) {
|
|
138
|
-
if (bytes < 1024)
|
|
139
|
-
return `${bytes} bytes`;
|
|
140
|
-
if (bytes < 1024 * 1024)
|
|
141
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
142
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
143
|
-
}
|
|
144
|
-
export function printError(msg) {
|
|
145
|
-
console.log(chalk.red(` ❌ ${msg}`));
|
|
146
|
-
}
|
|
147
|
-
export function printCloudToolResult(tool, success, output) {
|
|
148
|
-
const icon = success ? chalk.green("✓") : chalk.red("✗");
|
|
149
|
-
if (tool === "web_search") {
|
|
150
|
-
// Show first result title/url
|
|
151
|
-
const firstLine = output.split("\n").find(l => l.startsWith("[")) || "";
|
|
152
|
-
console.log(` ${icon} ${chalk.gray(firstLine.slice(0, 120))}`);
|
|
153
|
-
}
|
|
154
|
-
else if (tool === "web_fetch") {
|
|
155
|
-
// Show title or brief status
|
|
156
|
-
const title = output.split("\n")[0]?.slice(0, 80) || "fetched";
|
|
157
|
-
console.log(` ${icon} ${chalk.gray(title)}`);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
console.log(` ${icon}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// Green + with line numbers for new file
|
|
164
|
-
export function printFileContent(filePath, content) {
|
|
165
|
-
console.log(chalk.cyan(`\n ── ${filePath} ──`));
|
|
166
|
-
const lines = content.split("\n");
|
|
167
|
-
for (let i = 0; i < lines.length; i++) {
|
|
168
|
-
const num = chalk.gray(String(i + 1).padStart(4) + " │ ");
|
|
169
|
-
console.log(num + chalk.green("+ " + lines[i]));
|
|
170
|
-
}
|
|
171
|
-
console.log(chalk.cyan(" ── end ──\n"));
|
|
172
|
-
}
|
|
173
|
-
// Red - / green + with context lines and line numbers
|
|
174
|
-
export function printDiff(oldStr, newStr, filePath, fullContent) {
|
|
175
|
-
console.log("");
|
|
176
|
-
const oldLines = oldStr.split("\n");
|
|
177
|
-
const newLines = newStr.split("\n");
|
|
178
|
-
let startLine = 0;
|
|
179
|
-
if (fullContent) {
|
|
180
|
-
const idx = fullContent.indexOf(oldStr);
|
|
181
|
-
if (idx >= 0) {
|
|
182
|
-
startLine = fullContent.substring(0, idx).split("\n").length;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// 3 lines before
|
|
186
|
-
if (fullContent && startLine > 1) {
|
|
187
|
-
const allLines = fullContent.split("\n");
|
|
188
|
-
const ctxStart = Math.max(0, startLine - 1 - 3);
|
|
189
|
-
for (let i = ctxStart; i < startLine - 1; i++) {
|
|
190
|
-
const num = chalk.gray(String(i + 1).padStart(4) + " │ ");
|
|
191
|
-
console.log(num + chalk.gray(" " + allLines[i]));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
for (let i = 0; i < oldLines.length; i++) {
|
|
195
|
-
const lineNo = startLine > 0 ? startLine + i : i + 1;
|
|
196
|
-
const num = chalk.gray(String(lineNo).padStart(4) + " │ ");
|
|
197
|
-
console.log(num + chalk.red("- " + oldLines[i]));
|
|
198
|
-
}
|
|
199
|
-
for (let i = 0; i < newLines.length; i++) {
|
|
200
|
-
const lineNo = startLine > 0 ? startLine + i : i + 1;
|
|
201
|
-
const num = chalk.gray(String(lineNo).padStart(4) + " │ ");
|
|
202
|
-
console.log(num + chalk.green("+ " + newLines[i]));
|
|
203
|
-
}
|
|
204
|
-
// 3 lines after
|
|
205
|
-
if (fullContent && startLine > 0) {
|
|
206
|
-
const allLines = fullContent.split("\n");
|
|
207
|
-
const afterStart = startLine - 1 + oldLines.length;
|
|
208
|
-
const afterEnd = Math.min(allLines.length, afterStart + 3);
|
|
209
|
-
for (let i = afterStart; i < afterEnd; i++) {
|
|
210
|
-
const num = chalk.gray(String(i + 1).padStart(4) + " │ ");
|
|
211
|
-
console.log(num + chalk.gray(" " + allLines[i]));
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
console.log("");
|
|
215
|
-
}
|