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 CHANGED
File without changes
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "synara",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "AI-powered coding assistant. Cloud brain, local hands.",
5
5
  "bin": {
6
- "synara": "./dist/index.js"
6
+ "synara": "dist/index.js"
7
7
  },
8
8
  "files": [
9
9
  "dist"
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
- }
@@ -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
@@ -1,4 +0,0 @@
1
- // WebSocket protocol between CLI ↔ Cloud Container
2
- // CLI is thin client: display, execute, confirm
3
- // Cloud is thick server: LLM, Harness, context management
4
- export {};
@@ -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
- }
@@ -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
- }