Reaperxxxx commited on
Commit
9750fce
·
verified ·
1 Parent(s): 72af730

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +300 -372
server.js CHANGED
@@ -9,7 +9,10 @@ const axios = require("axios");
9
  const app = express();
10
  const PORT = 7860;
11
  const HOME_DIR = path.join(__dirname, "home");
12
- const TASK_FILE = path.join(HOME_DIR, ".cryo_tasks.txt");
 
 
 
13
 
14
  if (!fs.existsSync(HOME_DIR)) fs.mkdirSync(HOME_DIR, { recursive: true });
15
 
@@ -17,176 +20,67 @@ app.use(cors());
17
  app.use(express.json({ limit: "10mb" }));
18
  app.use(express.static(path.join(__dirname, "public")));
19
 
20
- // ── Multer ─────────────────────────────────────────────────────────────────
21
- const storage = multer.diskStorage({
22
- destination: HOME_DIR,
23
- filename: (req, file, cb) => cb(null, "index.js"),
24
- });
25
- const upload = multer({ storage });
26
-
27
- // ── ALLOWED COMMANDS ────────────────────────────────────────────────────────
28
- const ALLOWED_CMDS = [
29
- "grep", "sed", "awk", "head", "tail", "cat", "wc",
30
- "sort", "uniq", "cut", "tr", "find", "ls", "echo",
31
- "diff", "patch", "node", "npm", "file", "stat",
32
- ];
33
-
34
- const COMMANDS_REFERENCE = `
35
- === SHELL COMMANDS AVAILABLE (cwd = /home) ===
36
-
37
- FILE EXPLORATION:
38
- ls -l List files & directories
39
- find . -name "*.js" Find JS files recursively
40
- find . -name "*.json" Find config files
41
- file filename Show file type
42
- stat filename File metadata (size, dates)
43
-
44
- READING CONTENT:
45
- cat file Read entire file
46
- head -n N file First N lines
47
- tail -n N file Last N lines
48
- sed -n 'X,Yp' file Lines X to Y (surgical)
49
- head -c N file First N bytes
50
- grep -n "pattern" file Lines matching pattern (with line numbers)
51
- grep -A5 "pattern" file Pattern + 5 lines after
52
- grep -B3 "pattern" file Pattern + 3 lines before
53
- grep -C3 "pattern" file Pattern + 3 lines context (before+after)
54
- grep -rn "pattern" . Recursive search all files
55
-
56
- COUNTING / STATS:
57
- wc -l file Count lines
58
- grep -c "pattern" file Count occurrences
59
- awk 'END{print NR}' file Alternative line count
60
-
61
- EDITING (surgical, safe):
62
- sed -i 's/old/new/g' file Replace ALL occurrences
63
- sed -i '12s/old/new/' file Replace on line 12 ONLY
64
- sed -i 'X,Ys/old/new/g' file Replace in lines X–Y
65
- awk '/pattern/' file Extract matching lines
66
- cut -d',' -f2 file Extract 2nd CSV column
67
- tr 'a-z' 'A-Z' Transform chars
68
-
69
- COMPARING:
70
- diff old.js new.js Show differences between files
71
- patch file < patchfile Apply a patch file
72
-
73
- SYNTAX / ERROR CHECKING:
74
- node --check file.js JavaScript syntax validation
75
- npm test Run project tests
76
-
77
- PROJECT INFO:
78
- cat package.json Show project dependencies
79
- npm run scriptname Run npm scripts
80
-
81
- UTILITIES:
82
- echo "text" Print text
83
- sort file Sort lines alphabetically
84
- uniq file Remove duplicate lines
85
-
86
- === AGENT RULES ===
87
- 1. ALWAYS run "node --check file.js" BEFORE any edit
88
- 2. ALWAYS run "node --check file.js" AFTER any edit
89
- 3. Use grep -C3 to understand context BEFORE inserting new code
90
- 4. Match existing code style — grep for similar patterns first
91
- 5. Use sed -n 'X,Yp' to read specific sections before modifying them
92
- 6. Back up important sections mentally before sed -i changes
93
- 7. For adding new features: grep for existing similar features first
94
- ===============================================
95
- `;
96
-
97
- // ── AI Models Config ─────────────────────────────────────────────────────────
98
- const AI_MODELS = {
99
- "gpt5": {
100
- name: "GPT-5",
101
- label: "GPT-5 (Prexzy)",
102
- call: async (prompt, system) => {
103
- const query = system ? `${system}\n\n${prompt}` : prompt;
104
- const { data } = await axios.get(
105
- `https://apis.prexzyvilla.site/ai/gpt-5?text=${encodeURIComponent(query)}`,
106
- { timeout: 40000 }
107
- );
108
- return data.text || data.response || "";
109
  }
110
  },
111
- "gemini": {
112
- name: "Gemini",
113
- label: "Gemini (David)",
114
- call: async (prompt, system) => {
115
- const query = system ? `${system}\n\n${prompt}` : prompt;
116
- const { data } = await axios.get(
117
- `https://apis.davidcyril.name.ng/ai/gemini?text=${encodeURIComponent(query)}`,
118
- { timeout: 40000 }
119
- );
120
- return data.message || data.response || "";
121
  }
122
  },
123
- "claude": {
124
- name: "Claude Haiku",
125
- label: "Claude Haiku (Prexzy)",
126
- call: async (prompt, system) => {
127
- const params = new URLSearchParams({
128
- text: prompt,
129
- "system?": system || "You are Cryo, a precise developer AI assistant. Respond helpfully and clearly."
130
- });
131
- const { data } = await axios.get(
132
- `https://apis.prexzyvilla.site/ai/claude?${params}`,
133
- { timeout: 40000 }
134
- );
135
- return data.response || data.text || "";
136
  }
137
  }
138
  };
139
 
140
- // ── Web Search ───────────────────────────────────────────────────────────────
141
- async function webSearch(query) {
142
- try {
143
- const { data } = await axios.get(
144
- `https://apis.prexzyvilla.site/ai/gpt-5?text=${encodeURIComponent(query)}`,
145
- { timeout: 30000 }
146
- );
147
- return {
148
- answer: data.text || "",
149
- citations: data.citations || [],
150
- model: data.model || "gpt-5"
151
- };
152
- } catch (e) {
153
- return { answer: "", citations: [], model: "gpt-5" };
154
- }
155
- }
156
-
157
- // ── Detect if web search needed ──────────────────────────────────────────────
158
- function needsWebSearch(message) {
159
- const triggers = [
160
- /\b(latest|newest|recent|current|today|now|2024|2025|2026)\b/i,
161
- /\b(news|trending|just released|came out|update|version)\b/i,
162
- /\b(price|cost|how much|buy|purchase|available)\b/i,
163
- /\b(who is|what is .+ doing|where is .+ now)\b/i,
164
- /\b(weather|forecast|stock|score|standings)\b/i,
165
- /\bsearch (for|the web|online)\b/i,
166
- /\blook up\b/i,
167
- /\b(best .+ 2025|top .+ 2026)\b/i,
168
- ];
169
- return triggers.some(r => r.test(message));
170
- }
171
 
172
- function buildSearchQuery(message) {
173
- // Strip filler words and extract intent
174
- return message
175
- .replace(/\b(can you|please|tell me|what is|who is|search for|look up|find me)\b/gi, "")
176
- .replace(/[?!.]/g, "")
177
- .trim()
178
- .substring(0, 120);
179
- }
 
 
 
180
 
181
- // ── Core AI caller ────────────────────────────────────────────────────────────
182
- async function callAI(system, query, modelId = "gpt5") {
183
- const model = AI_MODELS[modelId] || AI_MODELS["gpt5"];
184
- // Combine system + query in a structured way
185
- const fullPrompt = `${system}\n\n---\nUser request:\n${query}\n\nRespond ONLY with valid JSON, no markdown fences.`;
186
- const raw = await model.call(fullPrompt, system);
187
- return raw;
188
- }
189
 
 
190
  function parseJSON(raw) {
191
  if (!raw) return null;
192
  let s = raw.replace(/```json\s*/gi, "").replace(/```\s*/g, "").trim();
@@ -207,13 +101,6 @@ function runCmd(command) {
207
  });
208
  }
209
 
210
- function backupFile(filePath) {
211
- const src = path.join(HOME_DIR, filePath);
212
- if (!fs.existsSync(src)) return;
213
- const dst = path.join(HOME_DIR, filePath.replace(/(\.\w+)?$/, "_backup$1"));
214
- fs.copyFileSync(src, dst);
215
- }
216
-
217
  function getSmartContext(content, query, maxLines = 120) {
218
  const lines = content.split("\n");
219
  if (lines.length <= maxLines) return content;
@@ -229,76 +116,149 @@ function getSmartContext(content, query, maxLines = 120) {
229
  }
230
  }
231
  const middle = rs !== -1
232
- ? `\n// ... [lines 41–${rs} omitted] ...\n` + lines.slice(rs, re + 1).join("\n") + `\n// ... [lines ${re + 1} onward omitted] ...\n`
233
- : `\n// ... [${lines.length - 55} lines omitted] ...\n`;
234
  return head + middle + tail;
235
  }
236
 
237
- function updateTaskLog(tasks) {
238
- const lines = ["=== CRYO TASK LOG ===", `Updated: ${new Date().toISOString()}`, ""];
239
- for (const t of tasks) {
240
- lines.push(`[${t.done ? "" : "⬜"}] ${t.task}`);
241
- if (t.note) lines.push(` → ${t.note}`);
242
- }
243
- fs.writeFileSync(TASK_FILE, lines.join("\n"), "utf8");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  }
245
 
246
- function readTaskLog() {
 
247
  if (!fs.existsSync(TASK_FILE)) return [];
248
- return fs.readFileSync(TASK_FILE, "utf8").split("\n")
249
- .map(l => l.match(/^\[(✅|⬜)\] (.+)/))
250
- .filter(Boolean)
251
- .map(m => ({ done: m[1] === "✅", task: m[2] }));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  }
253
 
254
- async function syntaxFixLoop(filePath, send, maxAttempts = 3, modelId = "gpt5") {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
256
  const check = await runCmd(`node --check ${filePath}`);
257
- const cmdIdx = 800 + attempt;
258
- send("command", { cmd: `node --check ${filePath} (attempt ${attempt})`, index: cmdIdx });
259
  send("command_result", {
260
- cmd: `node --check ${filePath}`,
261
- stdout: check.code === 0 ? "✓ Syntax OK" : "",
262
- stderr: check.stderr,
263
- index: cmdIdx,
264
- isError: check.code !== 0
265
  });
266
  if (check.code === 0) {
267
- if (attempt === 1) send("status", { text: "✅ Syntax OK" });
268
- else send("status", { text: `✅ Syntax fixed on attempt ${attempt}` });
269
  return true;
270
  }
271
- send("status", { text: `⚠️ Syntax error (attempt ${attempt}/${maxAttempts}) — fixing...` });
272
  const absPath = path.join(HOME_DIR, filePath);
273
  const broken = fs.readFileSync(absPath, "utf8");
274
  const fixedRaw = await callAI(
275
- `You are Cryo - a precise JS developer. Fix the syntax error. Return ONLY valid JSON: {"content":"fixed file here","fix":"what was wrong"}`,
276
- `File: ${filePath}\nError:\n${check.stderr}\n\nFile content:\n${broken}`,
277
- modelId
278
  );
279
  const fixed = parseJSON(fixedRaw);
280
  if (fixed?.content) {
281
  fs.writeFileSync(absPath, fixed.content, "utf8");
282
- send("status", { text: `🔨 Applied fix: ${fixed.fix || "syntax correction"}` });
283
- } else {
284
- send("status", { text: `⚠️ Could not auto-fix on attempt ${attempt}` });
285
- break;
286
- }
287
  }
288
  return false;
289
  }
290
 
291
- // ── File Tree ────────────────────────────────────────────────────────────────
292
  app.get("/api/tree", (req, res) => {
293
  const buildTree = (dir, base = HOME_DIR) => {
294
  try {
295
  return fs.readdirSync(dir, { withFileTypes: true })
296
- .filter(e => !e.name.startsWith(".cryo_") && !e.name.startsWith(".zoro_"))
297
  .map(e => {
298
  const relPath = path.relative(base, path.join(dir, e.name));
299
- if (e.isDirectory()) {
300
- return { type: "dir", name: e.name, path: relPath, children: buildTree(path.join(dir, e.name), base) };
301
- }
302
  const stats = fs.statSync(path.join(dir, e.name));
303
  return { type: "file", name: e.name, path: relPath, size: stats.size };
304
  });
@@ -309,7 +269,7 @@ app.get("/api/tree", (req, res) => {
309
  });
310
 
311
  app.post("/api/upload", upload.single("file"), (req, res) => {
312
- res.json({ success: true, path: "index.js" });
313
  });
314
 
315
  app.get("/api/file", (req, res) => {
@@ -323,6 +283,7 @@ app.post("/api/file", (req, res) => {
323
  const { filePath, content } = req.body;
324
  const abs = path.join(HOME_DIR, filePath);
325
  if (!abs.startsWith(HOME_DIR)) return res.status(403).json({ error: "Forbidden" });
 
326
  fs.mkdirSync(path.dirname(abs), { recursive: true });
327
  fs.writeFileSync(abs, content, "utf8");
328
  res.json({ success: true });
@@ -344,14 +305,34 @@ app.get("/api/download", (req, res) => {
344
  });
345
 
346
  app.get("/api/tasks", (req, res) => {
347
- if (!fs.existsSync(TASK_FILE)) return res.json({ tasks: [] });
348
- res.json({ tasks: readTaskLog() });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  });
350
 
351
  app.get("/api/models", (req, res) => {
352
- res.json({
353
- models: Object.entries(AI_MODELS).map(([id, m]) => ({ id, label: m.label, name: m.name }))
354
- });
355
  });
356
 
357
  app.post("/api/exec", (req, res) => {
@@ -365,7 +346,7 @@ app.post("/api/exec", (req, res) => {
365
  });
366
  });
367
 
368
- // ── MAIN CHAT ENDPOINT ────────────────────────────────────────────────────────
369
  app.post("/api/chat", async (req, res) => {
370
  res.setHeader("Content-Type", "text/event-stream");
371
  res.setHeader("Cache-Control", "no-cache");
@@ -375,87 +356,66 @@ app.post("/api/chat", async (req, res) => {
375
  try { res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`); } catch {}
376
  };
377
 
378
- const { message, currentFile, modelId = "gpt5" } = req.body;
379
 
380
  try {
381
- // ── Web Search detection ───────────────────────────────────────────────
382
- if (needsWebSearch(message)) {
383
- send("status", { text: "🌐 Detected web query — searching..." });
384
- const searchQuery = buildSearchQuery(message);
385
- send("web_search", { query: searchQuery });
386
-
387
- const result = await webSearch(searchQuery);
388
-
389
- send("web_result", {
390
- query: searchQuery,
391
- answer: result.answer,
392
- citations: result.citations,
393
- model: result.model
394
- });
395
-
396
- if (result.answer) {
397
  send("message", { text: result.answer });
398
- } else {
399
- send("message", { text: "I searched the web but couldn't find a clear answer. Please try rephrasing your query." });
400
  }
401
-
402
  res.write("data: [DONE]\n\n");
403
  res.end();
404
  return;
405
  }
406
 
407
- // ── File / code operations ──────────────────────────────────────────────
408
  let filePath = currentFile || "index.js";
409
  const absPath = path.join(HOME_DIR, filePath);
410
  let fileContent = fs.existsSync(absPath) ? fs.readFileSync(absPath, "utf8") : "";
411
  const hasFile = fileContent.length > 0;
412
  const fileLines = fileContent.split("\n").length;
 
 
 
413
 
414
- // ════════════════════════════════════════════════════════════════════════
415
- // CASE 1: No file → create from scratch
416
- // ════════════════════════════════════════════════════════════════════════
417
  if (!hasFile) {
418
- send("status", { text: "🧠 Planning what to build..." });
419
 
420
  const aiRaw = await callAI(
421
- `You are Cryo - a sharp, direct developer AI.
422
- Create the code the user asks for. First plan tasks, then generate the complete file.
423
- Return ONLY valid JSON (no markdown fences, no backticks, no extra text):
424
- {
425
- "filename": "index.js",
426
- "description": "what this file is",
427
- "tasks": [{"task": "what you did", "done": true}],
428
- "content": "complete file content",
429
- "status": "Done - what was built"
430
- }`,
431
  message,
432
- modelId
433
  );
434
 
435
  let parsed = parseJSON(aiRaw);
436
  if (!parsed?.content) {
437
  parsed = {
438
- filename: "index.js",
439
- content: aiRaw,
440
- description: "Generated",
441
- tasks: [{ task: "Generate file from request", done: true }],
442
- status: "Created file",
443
  };
444
  }
445
 
446
- send("status", { text: `📋 Plan ready — ${parsed.tasks?.length || 1} tasks` });
447
  send("task_update", { tasks: parsed.tasks || [] });
 
448
 
449
- send("status", { text: "🔍 Checking generated code..." });
450
- const tmpName = "__cryo_new_check.js";
451
  const tmpPath = path.join(HOME_DIR, tmpName);
452
  fs.writeFileSync(tmpPath, parsed.content, "utf8");
453
-
454
- await syntaxFixLoop(tmpName, send, 3, modelId);
455
  parsed.content = fs.readFileSync(tmpPath, "utf8");
456
  fs.unlinkSync(tmpPath);
457
 
458
- send("status", { text: `💾 Saving ${parsed.filename}...` });
459
  const savePath = path.join(HOME_DIR, parsed.filename);
460
  fs.mkdirSync(path.dirname(savePath), { recursive: true });
461
  fs.writeFileSync(savePath, parsed.content, "utf8");
@@ -464,169 +424,135 @@ Return ONLY valid JSON (no markdown fences, no backticks, no extra text):
464
  send("command", { cmd: `node --check ${parsed.filename}`, index: 0 });
465
  send("command_result", {
466
  cmd: `node --check ${parsed.filename}`,
467
- stdout: finalCheck.code === 0 ? "Syntax OK — file saved!" : "",
468
- stderr: finalCheck.stderr,
469
- index: 0,
470
- isError: finalCheck.code !== 0
471
  });
472
 
473
- updateTaskLog(parsed.tasks || []);
 
474
  send("file_created", { filename: parsed.filename, content: parsed.content, description: parsed.description });
475
- send("message", { text: `Created \`${parsed.filename}\` — ${parsed.description}` });
476
  send("tree_update", {});
477
  res.write("data: [DONE]\n\n");
478
  res.end();
479
  return;
480
  }
481
 
482
- // ════════════════════════════════════════════════════════════════════════
483
- // CASE 2: File exists — analyze + command loop + edit/answer
484
- // ════════════════════════════════════════════════════════════════════════
485
- send("status", { text: "🗺️ Analyzing request..." });
486
-
487
  const smartCtx = getSmartContext(fileContent, message);
488
- const existingTasks = readTaskLog();
489
- const pendingTasks = existingTasks.filter(t => !t.done);
490
 
491
  const masterPlanRaw = await callAI(
492
- `You are Cryo - a razor-sharp developer AI agent operating on code files.
493
- You use shell commands to explore, understand, then modify code.
494
-
495
  ${COMMANDS_REFERENCE}
496
-
497
- WORKFLOW FOR EDITS:
498
- 1. EXPLORE: grep for existing patterns similar to what user wants
499
- 2. LOCATE: find exact line numbers (grep -n, wc -l, sed -n to read sections)
500
- 3. UNDERSTAND: read context around insertion points (grep -C5)
501
- 4. CHECK: node --check before any edit
502
- 5. EDIT: surgical sed -i on specific lines
503
- 6. VERIFY: node --check after edit
504
-
505
- WORKFLOW FOR QUERIES:
506
- 1. grep for relevant functions/patterns
507
- 2. read surrounding context
508
- 3. answer precisely
509
-
510
  File: ${filePath} (${fileLines} lines)
511
- Current content preview:
512
- ${smartCtx.substring(0, 2500)}
513
-
514
- ${pendingTasks.length > 0 ? `UNFINISHED TASKS FROM LAST SESSION:\n${pendingTasks.map(t => ` ⬜ ${t.task}`).join("\n")}\n` : ""}
515
-
516
- Respond ONLY with valid JSON (no markdown):
517
- {
518
- "task_type": "query" | "edit" | "create",
519
- "status": "brief description shown to user",
520
- "tasks": [{"task": "subtask name", "done": false}],
521
- "commands": ["cmd1", "cmd2"],
522
- "reasoning": "why these commands in this order"
523
- }`,
524
  message,
525
- modelId
526
  );
527
 
528
  let plan = parseJSON(masterPlanRaw);
529
  if (!plan) plan = { task_type: "query", status: "Analyzing...", tasks: [], commands: [], reasoning: "" };
530
 
531
- send("status", { text: `⚡ ${plan.status || "Processing..."}` });
532
  if (plan.tasks?.length > 0) send("task_update", { tasks: plan.tasks });
533
 
 
534
  const commandResults = [];
 
 
535
  for (let i = 0; i < (plan.commands || []).length; i++) {
536
  const cmd = plan.commands[i];
537
- send("status", { text: `🔧 [${i + 1}/${plan.commands.length}] \`${cmd}\`` });
538
  send("command", { cmd, index: i });
539
 
540
  const result = await runCmd(cmd);
541
  commandResults.push({ cmd, ...result });
 
542
 
543
  send("command_result", {
544
- cmd,
545
- stdout: result.stdout.substring(0, 1000),
546
  stderr: result.stderr.substring(0, 500),
547
- index: i,
548
- isError: result.code !== 0 && !!result.stderr
549
  });
550
 
551
  if (cmd.includes("node --check") && result.code !== 0 && result.stderr) {
552
  const targetFile = cmd.split(/\s+/).pop();
553
- send("status", { text: `⚠️ Syntax error found in ${targetFile} — entering fix loop...` });
554
- await syntaxFixLoop(targetFile, send, 3, modelId);
555
  fileContent = fs.existsSync(absPath) ? fs.readFileSync(absPath, "utf8") : fileContent;
556
  }
557
  }
558
 
 
559
  fileContent = fs.existsSync(absPath) ? fs.readFileSync(absPath, "utf8") : fileContent;
560
 
561
  const resultsText = commandResults
562
- .map(r => `$ ${r.cmd}\n${r.stdout.substring(0, 600)}${r.stderr ? `\nSTDERR: ${r.stderr.substring(0, 300)}` : ""}`)
563
  .join("\n\n");
564
 
565
- send("status", { text: "🧠 Synthesizing..." });
566
 
567
  if (plan.task_type === "query") {
568
- const ansRaw = await callAI(
569
- `You are Cryo - direct, precise developer AI.
570
- Answer the user's question based on file content and command results.
571
- Return ONLY valid JSON: {"answer": "clear precise answer", "status": "Done"}`,
572
- `Question: "${message}"\nFile: ${filePath}\nResults:\n${resultsText}\nContext:\n${smartCtx.substring(0, 2000)}`,
573
- modelId
574
- );
575
- const ans = parseJSON(ansRaw);
576
- send("message", { text: ans?.answer || ansRaw || "No answer generated." });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
  } else {
579
  const hadSedEdits = commandResults.some(r => r.cmd.startsWith("sed -i") && r.code === 0);
580
 
581
  if (hadSedEdits) {
582
- send("status", { text: "🔍 Verifying sed edits..." });
583
- await syntaxFixLoop(filePath, send, 3, modelId);
584
  fileContent = fs.readFileSync(absPath, "utf8");
585
 
586
  const sumRaw = await callAI(
587
- `You are Cryo. Summarize what changed in the file. Return ONLY JSON: {"summary": "what changed", "tasks_completed": ["t1", "t2"]}`,
588
- `User asked: ${message}\nCommands:\n${resultsText}`,
589
- modelId
590
  );
591
  const sum = parseJSON(sumRaw);
592
-
593
- const allTasks = [
594
- ...existingTasks.map(t => ({ ...t, done: true })),
595
- ...(plan.tasks || []).map(t => ({ ...t, done: true }))
596
- ];
597
- updateTaskLog(allTasks);
598
  send("task_update", { tasks: allTasks });
599
-
 
600
  send("file_updated", { filename: filePath, content: fileContent, description: sum?.summary || "Updated" });
601
- send("message", { text: `✅ ${sum?.summary || "Changes applied via sed"}` });
602
 
603
  } else {
604
  const editRaw = await callAI(
605
- `You are Cryo - a precise developer AI.
606
- You have explored the file with commands. Now produce the complete updated file.
607
-
608
- KEY RULES:
609
- - Study command results carefully they show existing code patterns
610
- - Match the EXACT style, indentation, naming conventions of existing code
611
- - For new features/commands: model them exactly after existing similar ones
612
- - Make ONLY the necessary changes, preserve everything else
613
- - The output must be a complete, working file
614
-
615
- Return ONLY valid JSON (no markdown):
616
- {
617
- "content": "COMPLETE updated file content here",
618
- "description": "what changed",
619
- "tasks_completed": ["task1", "task2"],
620
- "status": "Done — what was accomplished"
621
- }`,
622
- `User request: "${message}"
623
- File: ${filePath} (${fileLines} lines)
624
- Current file:
625
- ${fileContent.substring(0, 5000)}
626
- Command exploration results:
627
- ${resultsText}
628
- Produce the complete updated file.`,
629
- modelId
630
  );
631
 
632
  let editParsed = parseJSON(editRaw);
@@ -634,40 +560,42 @@ Produce the complete updated file.`,
634
  editParsed = { content: fileContent, description: "No changes", status: "Done", tasks_completed: [] };
635
  }
636
 
637
- backupFile(filePath);
 
638
 
639
- send("status", { text: "🔍 Checking new content before saving..." });
 
 
640
  const tmpName = `__cryo_presave_${Date.now()}.js`;
641
  const tmpPath = path.join(HOME_DIR, tmpName);
642
  fs.writeFileSync(tmpPath, editParsed.content, "utf8");
643
-
644
- await syntaxFixLoop(tmpName, send, 3, modelId);
645
  editParsed.content = fs.readFileSync(tmpPath, "utf8");
646
  fs.unlinkSync(tmpPath);
647
 
648
  fs.writeFileSync(absPath, editParsed.content, "utf8");
649
- send("status", { text: `💾 Saved ${filePath}` });
650
 
651
- await syntaxFixLoop(filePath, send, 3, modelId);
652
  fileContent = fs.readFileSync(absPath, "utf8");
653
 
654
  const allTasks = [
655
  ...existingTasks.map(t => ({ ...t, done: true })),
656
  ...(plan.tasks || []).map(t => ({ ...t, done: true, note: "done this session" }))
657
  ];
658
- updateTaskLog(allTasks);
 
659
  send("task_update", { tasks: allTasks });
660
-
661
  send("file_updated", { filename: filePath, content: fileContent, description: editParsed.description });
662
- send("message", { text: `✅ ${editParsed.status || editParsed.description}` });
663
  send("tree_update", {});
664
  }
665
  }
666
 
667
- const latestTasks = readTaskLog();
668
  const stillPending = latestTasks.filter(t => !t.done);
669
  if (stillPending.length > 0) {
670
- send("status", { text: `📋 ${stillPending.length} tasks still pending` });
671
  send("pending_tasks", { tasks: stillPending, count: stillPending.length });
672
  }
673
 
@@ -683,6 +611,6 @@ Produce the complete updated file.`,
683
  });
684
 
685
  app.listen(PORT, () => {
686
- console.log(`\n❄️ Cryo Server → http://localhost:${PORT}`);
687
  console.log(`📁 Home dir: ${HOME_DIR}\n`);
688
  });
 
9
  const app = express();
10
  const PORT = 7860;
11
  const HOME_DIR = path.join(__dirname, "home");
12
+ const META_FILE = path.join(HOME_DIR, ".cryo_meta.json");
13
+ const TASK_FILE = path.join(HOME_DIR, ".cryo_tasks.json");
14
+ const CMD_HISTORY_FILE = path.join(HOME_DIR, ".cryo_cmd_history.json");
15
+ const VERSIONS_FILE = path.join(HOME_DIR, ".cryo_versions.json");
16
 
17
  if (!fs.existsSync(HOME_DIR)) fs.mkdirSync(HOME_DIR, { recursive: true });
18
 
 
20
  app.use(express.json({ limit: "10mb" }));
21
  app.use(express.static(path.join(__dirname, "public")));
22
 
23
+ // ── MODELS ─────────────────────────────────────────────────────────────────
24
+ const MODELS = {
25
+ "cryo1": {
26
+ name: "Cryo 1",
27
+ label: "cryo1",
28
+ description: "Fast & light",
29
+ call: async (prompt) => {
30
+ const url = `https://apis.davidcyril.name.ng/ai/gemini?text=${encodeURIComponent(prompt.slice(0, 800))}`;
31
+ const { data } = await axios.get(url, { timeout: 20000 });
32
+ return data.message || data.text || "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
  },
35
+ "cryo3": {
36
+ name: "Cryo 3",
37
+ label: "cryo3",
38
+ description: "Balanced & smart",
39
+ call: async (prompt) => {
40
+ const url = `https://apis.prexzyvilla.site/ai/claude?text=${encodeURIComponent(prompt.slice(0, 900))}&system=You are Cryo, a precise developer AI. Respond concisely in JSON only.`;
41
+ const { data } = await axios.get(url, { timeout: 25000 });
42
+ return data.response || data.text || "";
 
 
43
  }
44
  },
45
+ "cryo4": {
46
+ name: "Cryo 4",
47
+ label: "cryo4",
48
+ description: "Most powerful",
49
+ call: async (prompt) => {
50
+ const url = `https://apis.prexzyvilla.site/ai/gpt-5?text=${encodeURIComponent(prompt.slice(0, 900))}`;
51
+ const { data } = await axios.get(url, { timeout: 30000 });
52
+ return data.text || data.message || "";
 
 
 
 
 
53
  }
54
  }
55
  };
56
 
57
+ // ── ALLOWED COMMANDS ───────────────────────────────────────────────────────
58
+ const ALLOWED_CMDS = [
59
+ "grep", "sed", "awk", "head", "tail", "cat", "wc",
60
+ "sort", "uniq", "cut", "tr", "find", "ls", "echo",
61
+ "diff", "patch", "node", "npm", "file", "stat",
62
+ ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ const COMMANDS_REFERENCE = `
65
+ SHELL COMMANDS (cwd=/home):
66
+ ls, find, cat, head -n N, tail -n N, sed -n 'X,Yp', grep -n, grep -C3, grep -rn
67
+ sed -i 's/old/new/g', node --check file.js, wc -l, diff, stat
68
+
69
+ RULES:
70
+ 1. grep context before any edit
71
+ 2. node --check before+after edits
72
+ 3. match existing code style
73
+ 4. surgical sed-i on specific lines
74
+ `;
75
 
76
+ // ── Multer ─────────────────────────────────────────────────────────────────
77
+ const storage = multer.diskStorage({
78
+ destination: HOME_DIR,
79
+ filename: (req, file, cb) => cb(null, file.originalname || "index.js"),
80
+ });
81
+ const upload = multer({ storage });
 
 
82
 
83
+ // ── Helpers ────────────────────────────────────────────────────────────────
84
  function parseJSON(raw) {
85
  if (!raw) return null;
86
  let s = raw.replace(/```json\s*/gi, "").replace(/```\s*/g, "").trim();
 
101
  });
102
  }
103
 
 
 
 
 
 
 
 
104
  function getSmartContext(content, query, maxLines = 120) {
105
  const lines = content.split("\n");
106
  if (lines.length <= maxLines) return content;
 
116
  }
117
  }
118
  const middle = rs !== -1
119
+ ? `\n// ...[${rs}-${re} relevant]...\n` + lines.slice(rs, re + 1).join("\n")
120
+ : `\n// ...[${lines.length - 55} lines omitted]...\n`;
121
  return head + middle + tail;
122
  }
123
 
124
+ // ── Versioning ─────────────────────────────────────────────────────────────
125
+ function readVersions() {
126
+ if (!fs.existsSync(VERSIONS_FILE)) return {};
127
+ try { return JSON.parse(fs.readFileSync(VERSIONS_FILE, "utf8")); } catch { return {}; }
128
+ }
129
+
130
+ function saveVersions(v) {
131
+ fs.writeFileSync(VERSIONS_FILE, JSON.stringify(v, null, 2), "utf8");
132
+ }
133
+
134
+ function versionFile(filePath) {
135
+ const src = path.join(HOME_DIR, filePath);
136
+ if (!fs.existsSync(src)) return;
137
+ const ts = Date.now();
138
+ const ext = path.extname(filePath);
139
+ const base = filePath.replace(ext, "");
140
+ const vName = `.versions/${base}_v${ts}${ext}`;
141
+ const vDir = path.join(HOME_DIR, ".versions");
142
+ if (!fs.existsSync(vDir)) fs.mkdirSync(vDir, { recursive: true });
143
+ fs.copyFileSync(src, path.join(HOME_DIR, vName));
144
+ const versions = readVersions();
145
+ if (!versions[filePath]) versions[filePath] = [];
146
+ versions[filePath].push({ path: vName, ts, label: new Date(ts).toISOString() });
147
+ if (versions[filePath].length > 10) versions[filePath] = versions[filePath].slice(-10);
148
+ saveVersions(versions);
149
+ return vName;
150
  }
151
 
152
+ // ── Task Management ────────────────────────────────────────────────────────
153
+ function readTasks() {
154
  if (!fs.existsSync(TASK_FILE)) return [];
155
+ try { return JSON.parse(fs.readFileSync(TASK_FILE, "utf8")); } catch { return []; }
156
+ }
157
+
158
+ function saveTasks(tasks) {
159
+ fs.writeFileSync(TASK_FILE, JSON.stringify(tasks, null, 2), "utf8");
160
+ }
161
+
162
+ function updateTasks(newTasks) {
163
+ const existing = readTasks();
164
+ const merged = [...existing, ...newTasks.filter(t => !existing.find(e => e.task === t.task))];
165
+ saveTasks(merged);
166
+ return merged;
167
+ }
168
+
169
+ // ── Command History ────────────────────────────────────────────────────────
170
+ function appendCmdHistory(entries) {
171
+ let hist = [];
172
+ if (fs.existsSync(CMD_HISTORY_FILE)) {
173
+ try { hist = JSON.parse(fs.readFileSync(CMD_HISTORY_FILE, "utf8")); } catch {}
174
+ }
175
+ hist.push(...entries);
176
+ if (hist.length > 200) hist = hist.slice(-200);
177
+ fs.writeFileSync(CMD_HISTORY_FILE, JSON.stringify(hist, null, 2), "utf8");
178
+ }
179
+
180
+ // ── Project Metadata ───────────────────────────────────────────────────────
181
+ function readMeta() {
182
+ if (!fs.existsSync(META_FILE)) return { created_at: new Date().toISOString(), languages: [], last_ai_edit: null };
183
+ try { return JSON.parse(fs.readFileSync(META_FILE, "utf8")); } catch { return {}; }
184
  }
185
 
186
+ function updateMeta(patch) {
187
+ const meta = { ...readMeta(), ...patch, updated_at: new Date().toISOString() };
188
+ fs.writeFileSync(META_FILE, JSON.stringify(meta, null, 2), "utf8");
189
+ return meta;
190
+ }
191
+
192
+ // ── Web Search Detection ───────────────────────────────────────────────────
193
+ const SEARCH_TRIGGERS = [
194
+ /\b(latest|recent|current|today|now|2024|2025|2026|news|trending|new release|just released)\b/i,
195
+ /\b(who is|what is the price|when did|how much does|is .* still|does .* exist)\b/i,
196
+ /\b(search|look up|find out|google|web|online|internet)\b/i,
197
+ /\b(weather|stock|score|result|update|announce|launch)\b/i,
198
+ ];
199
+
200
+ function needsWebSearch(msg) {
201
+ return SEARCH_TRIGGERS.some(r => r.test(msg));
202
+ }
203
+
204
+ async function webSearch(query) {
205
+ const short = query.slice(0, 200);
206
+ const url = `https://apis.prexzyvilla.site/ai/gpt-5?text=${encodeURIComponent(short)}`;
207
+ const { data } = await axios.get(url, { timeout: 20000 });
208
+ return {
209
+ answer: data.text || "",
210
+ citations: data.citations || [],
211
+ model: data.model || "gpt-5"
212
+ };
213
+ }
214
+
215
+ // ── callAI wrapper ─────────────────────────────────────────────────────────
216
+ async function callAI(system, query, modelKey = "cryo4") {
217
+ const model = MODELS[modelKey] || MODELS["cryo4"];
218
+ const prompt = system ? `${system}\n\n${query}` : query;
219
+ return model.call(prompt);
220
+ }
221
+
222
+ // ── Syntax fix loop ────────────────────────────────────────────────────────
223
+ async function syntaxFixLoop(filePath, send, modelKey, maxAttempts = 3) {
224
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
225
  const check = await runCmd(`node --check ${filePath}`);
226
+ const idx = 800 + attempt;
227
+ send("command", { cmd: `node --check ${filePath} (attempt ${attempt})`, index: idx });
228
  send("command_result", {
229
+ cmd: `node --check ${filePath}`, stdout: check.code === 0 ? "Syntax OK" : "",
230
+ stderr: check.stderr, index: idx, isError: check.code !== 0
 
 
 
231
  });
232
  if (check.code === 0) {
233
+ send("status", { text: attempt === 1 ? "Syntax OK" : `Syntax fixed on attempt ${attempt}` });
 
234
  return true;
235
  }
236
+ send("status", { text: `Syntax error (attempt ${attempt}/${maxAttempts}) — fixing...` });
237
  const absPath = path.join(HOME_DIR, filePath);
238
  const broken = fs.readFileSync(absPath, "utf8");
239
  const fixedRaw = await callAI(
240
+ `Fix JS syntax. Return ONLY JSON: {"content":"fixed file","fix":"what changed"}`,
241
+ `Error:\n${check.stderr}\n\nFile:\n${broken.slice(0, 3000)}`,
242
+ modelKey
243
  );
244
  const fixed = parseJSON(fixedRaw);
245
  if (fixed?.content) {
246
  fs.writeFileSync(absPath, fixed.content, "utf8");
247
+ send("status", { text: `Fix applied: ${fixed.fix || "syntax correction"}` });
248
+ } else break;
 
 
 
249
  }
250
  return false;
251
  }
252
 
253
+ // ── FILE TREE ──────────────────────────────────────────────────────────────
254
  app.get("/api/tree", (req, res) => {
255
  const buildTree = (dir, base = HOME_DIR) => {
256
  try {
257
  return fs.readdirSync(dir, { withFileTypes: true })
258
+ .filter(e => !e.name.startsWith(".cryo_") && !e.name.startsWith(".versions") && !e.name.startsWith("__cryo"))
259
  .map(e => {
260
  const relPath = path.relative(base, path.join(dir, e.name));
261
+ if (e.isDirectory()) return { type: "dir", name: e.name, path: relPath, children: buildTree(path.join(dir, e.name), base) };
 
 
262
  const stats = fs.statSync(path.join(dir, e.name));
263
  return { type: "file", name: e.name, path: relPath, size: stats.size };
264
  });
 
269
  });
270
 
271
  app.post("/api/upload", upload.single("file"), (req, res) => {
272
+ res.json({ success: true, path: req.file?.filename || "index.js" });
273
  });
274
 
275
  app.get("/api/file", (req, res) => {
 
283
  const { filePath, content } = req.body;
284
  const abs = path.join(HOME_DIR, filePath);
285
  if (!abs.startsWith(HOME_DIR)) return res.status(403).json({ error: "Forbidden" });
286
+ versionFile(filePath);
287
  fs.mkdirSync(path.dirname(abs), { recursive: true });
288
  fs.writeFileSync(abs, content, "utf8");
289
  res.json({ success: true });
 
305
  });
306
 
307
  app.get("/api/tasks", (req, res) => {
308
+ res.json({ tasks: readTasks() });
309
+ });
310
+
311
+ app.get("/api/history", (req, res) => {
312
+ if (!fs.existsSync(CMD_HISTORY_FILE)) return res.json({ history: [] });
313
+ try { res.json({ history: JSON.parse(fs.readFileSync(CMD_HISTORY_FILE, "utf8")) }); }
314
+ catch { res.json({ history: [] }); }
315
+ });
316
+
317
+ app.get("/api/versions", (req, res) => {
318
+ const { file } = req.query;
319
+ const versions = readVersions();
320
+ res.json({ versions: file ? (versions[file] || []) : versions });
321
+ });
322
+
323
+ app.post("/api/rollback", (req, res) => {
324
+ const { file, versionPath } = req.body;
325
+ const src = path.join(HOME_DIR, versionPath);
326
+ const dst = path.join(HOME_DIR, file);
327
+ if (!src.startsWith(HOME_DIR) || !dst.startsWith(HOME_DIR)) return res.status(403).json({ error: "Forbidden" });
328
+ if (!fs.existsSync(src)) return res.status(404).json({ error: "Version not found" });
329
+ versionFile(file);
330
+ fs.copyFileSync(src, dst);
331
+ res.json({ success: true, content: fs.readFileSync(dst, "utf8") });
332
  });
333
 
334
  app.get("/api/models", (req, res) => {
335
+ res.json({ models: Object.entries(MODELS).map(([k, v]) => ({ key: k, name: v.name, description: v.description })) });
 
 
336
  });
337
 
338
  app.post("/api/exec", (req, res) => {
 
346
  });
347
  });
348
 
349
+ // ── MAIN CHAT ENDPOINT ─────────────────────────────────────────────────────
350
  app.post("/api/chat", async (req, res) => {
351
  res.setHeader("Content-Type", "text/event-stream");
352
  res.setHeader("Cache-Control", "no-cache");
 
356
  try { res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`); } catch {}
357
  };
358
 
359
+ const { message, currentFile, model: modelKey = "cryo4" } = req.body;
360
 
361
  try {
362
+ // ── Web search detection ───────────────────────────────────────────────
363
+ if (needsWebSearch(message) && !currentFile) {
364
+ send("status", { text: "Searching the web..." });
365
+ send("web_search", { query: message });
366
+ try {
367
+ const result = await webSearch(message);
368
+ send("web_search_result", { query: message, citations: result.citations });
 
 
 
 
 
 
 
 
 
369
  send("message", { text: result.answer });
370
+ } catch (e) {
371
+ send("message", { text: "Web search failed: " + e.message });
372
  }
 
373
  res.write("data: [DONE]\n\n");
374
  res.end();
375
  return;
376
  }
377
 
 
378
  let filePath = currentFile || "index.js";
379
  const absPath = path.join(HOME_DIR, filePath);
380
  let fileContent = fs.existsSync(absPath) ? fs.readFileSync(absPath, "utf8") : "";
381
  const hasFile = fileContent.length > 0;
382
  const fileLines = fileContent.split("\n").length;
383
+ const meta = readMeta();
384
+ const existingTasks = readTasks();
385
+ const pendingTasks = existingTasks.filter(t => !t.done);
386
 
387
+ // ── No file: create from scratch ───────────────────────────────────────
 
 
388
  if (!hasFile) {
389
+ send("status", { text: "Planning what to build..." });
390
 
391
  const aiRaw = await callAI(
392
+ `You are Cryo - a precise developer AI. Create the code requested.
393
+ Return ONLY valid JSON (no markdown):
394
+ {"filename":"index.js","description":"what this is","tasks":[{"task":"what you did","done":true}],"content":"complete file content","status":"Done - what was built"}`,
 
 
 
 
 
 
 
395
  message,
396
+ modelKey
397
  );
398
 
399
  let parsed = parseJSON(aiRaw);
400
  if (!parsed?.content) {
401
  parsed = {
402
+ filename: "index.js", content: aiRaw,
403
+ description: "Generated", tasks: [{ task: "Generate file", done: true }], status: "Created"
 
 
 
404
  };
405
  }
406
 
407
+ send("status", { text: `Plan ready — ${parsed.tasks?.length || 1} tasks` });
408
  send("task_update", { tasks: parsed.tasks || [] });
409
+ send("status", { text: "Checking generated code..." });
410
 
411
+ const tmpName = `__cryo_new_${Date.now()}.js`;
 
412
  const tmpPath = path.join(HOME_DIR, tmpName);
413
  fs.writeFileSync(tmpPath, parsed.content, "utf8");
414
+ await syntaxFixLoop(tmpName, send, modelKey, 3);
 
415
  parsed.content = fs.readFileSync(tmpPath, "utf8");
416
  fs.unlinkSync(tmpPath);
417
 
418
+ send("status", { text: `Saving ${parsed.filename}...` });
419
  const savePath = path.join(HOME_DIR, parsed.filename);
420
  fs.mkdirSync(path.dirname(savePath), { recursive: true });
421
  fs.writeFileSync(savePath, parsed.content, "utf8");
 
424
  send("command", { cmd: `node --check ${parsed.filename}`, index: 0 });
425
  send("command_result", {
426
  cmd: `node --check ${parsed.filename}`,
427
+ stdout: finalCheck.code === 0 ? "Syntax OK — file saved!" : "",
428
+ stderr: finalCheck.stderr, index: 0, isError: finalCheck.code !== 0
 
 
429
  });
430
 
431
+ updateTasks(parsed.tasks || []);
432
+ updateMeta({ last_ai_edit: new Date().toISOString(), languages: [parsed.filename.split(".").pop()] });
433
  send("file_created", { filename: parsed.filename, content: parsed.content, description: parsed.description });
434
+ send("message", { text: `Created \`${parsed.filename}\` — ${parsed.description}` });
435
  send("tree_update", {});
436
  res.write("data: [DONE]\n\n");
437
  res.end();
438
  return;
439
  }
440
 
441
+ // ── File exists: analyze + command loop + edit/answer ──────────────────
442
+ send("status", { text: "Analyzing request..." });
 
 
 
443
  const smartCtx = getSmartContext(fileContent, message);
 
 
444
 
445
  const masterPlanRaw = await callAI(
446
+ `You are Cryo - a sharp developer AI agent on code files.
 
 
447
  ${COMMANDS_REFERENCE}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  File: ${filePath} (${fileLines} lines)
449
+ Preview:\n${smartCtx.substring(0, 1800)}
450
+ ${pendingTasks.length > 0 ? `PENDING TASKS:\n${pendingTasks.map(t => ` - ${t.task}`).join("\n")}\n` : ""}
451
+ Respond ONLY with valid JSON:
452
+ {"task_type":"query"|"edit"|"create","status":"brief description","tasks":[{"task":"name","done":false}],"commands":["cmd1"],"reasoning":"why"}`,
 
 
 
 
 
 
 
 
 
453
  message,
454
+ modelKey
455
  );
456
 
457
  let plan = parseJSON(masterPlanRaw);
458
  if (!plan) plan = { task_type: "query", status: "Analyzing...", tasks: [], commands: [], reasoning: "" };
459
 
460
+ send("status", { text: plan.status || "Processing..." });
461
  if (plan.tasks?.length > 0) send("task_update", { tasks: plan.tasks });
462
 
463
+ // Run all planned commands
464
  const commandResults = [];
465
+ const historyEntries = [];
466
+
467
  for (let i = 0; i < (plan.commands || []).length; i++) {
468
  const cmd = plan.commands[i];
469
+ send("status", { text: `[${i + 1}/${plan.commands.length}] ${cmd}` });
470
  send("command", { cmd, index: i });
471
 
472
  const result = await runCmd(cmd);
473
  commandResults.push({ cmd, ...result });
474
+ historyEntries.push({ cmd, stdout: result.stdout.slice(0, 500), stderr: result.stderr.slice(0, 200), code: result.code, ts: Date.now() });
475
 
476
  send("command_result", {
477
+ cmd, stdout: result.stdout.substring(0, 1000),
 
478
  stderr: result.stderr.substring(0, 500),
479
+ index: i, isError: result.code !== 0 && !!result.stderr
 
480
  });
481
 
482
  if (cmd.includes("node --check") && result.code !== 0 && result.stderr) {
483
  const targetFile = cmd.split(/\s+/).pop();
484
+ send("status", { text: `Syntax error in ${targetFile} — entering fix loop...` });
485
+ await syntaxFixLoop(targetFile, send, modelKey, 3);
486
  fileContent = fs.existsSync(absPath) ? fs.readFileSync(absPath, "utf8") : fileContent;
487
  }
488
  }
489
 
490
+ appendCmdHistory(historyEntries);
491
  fileContent = fs.existsSync(absPath) ? fs.readFileSync(absPath, "utf8") : fileContent;
492
 
493
  const resultsText = commandResults
494
+ .map(r => `$ ${r.cmd}\n${r.stdout.substring(0, 500)}${r.stderr ? `\nSTDERR: ${r.stderr.substring(0, 200)}` : ""}`)
495
  .join("\n\n");
496
 
497
+ send("status", { text: "Synthesizing..." });
498
 
499
  if (plan.task_type === "query") {
500
+ // Check if answer might need web info
501
+ if (needsWebSearch(message)) {
502
+ send("web_search", { query: message });
503
+ try {
504
+ const wsResult = await webSearch(message);
505
+ send("web_search_result", { query: message, citations: wsResult.citations });
506
+ send("message", { text: wsResult.answer });
507
+ } catch {
508
+ const ansRaw = await callAI(
509
+ `You are Cryo. Answer based on file content and command results. Return ONLY JSON: {"answer":"clear answer","status":"Done"}`,
510
+ `Question: "${message}"\nResults:\n${resultsText}\nContext:\n${smartCtx.substring(0, 1500)}`,
511
+ modelKey
512
+ );
513
+ const ans = parseJSON(ansRaw);
514
+ send("message", { text: ans?.answer || "No answer generated." });
515
+ }
516
+ } else {
517
+ const ansRaw = await callAI(
518
+ `You are Cryo. Answer based on file content and command results. Return ONLY JSON: {"answer":"clear answer","status":"Done"}`,
519
+ `Question: "${message}"\nResults:\n${resultsText}\nContext:\n${smartCtx.substring(0, 1500)}`,
520
+ modelKey
521
+ );
522
+ const ans = parseJSON(ansRaw);
523
+ send("message", { text: ans?.answer || "No answer generated." });
524
+ }
525
 
526
  } else {
527
  const hadSedEdits = commandResults.some(r => r.cmd.startsWith("sed -i") && r.code === 0);
528
 
529
  if (hadSedEdits) {
530
+ send("status", { text: "Verifying sed edits..." });
531
+ await syntaxFixLoop(filePath, send, modelKey, 3);
532
  fileContent = fs.readFileSync(absPath, "utf8");
533
 
534
  const sumRaw = await callAI(
535
+ `Summarize file changes. Return ONLY JSON: {"summary":"what changed","tasks_completed":["t1"]}`,
536
+ `User: ${message}\nCommands:\n${resultsText}`,
537
+ modelKey
538
  );
539
  const sum = parseJSON(sumRaw);
540
+ const allTasks = [...existingTasks.map(t => ({ ...t, done: true })), ...(plan.tasks || []).map(t => ({ ...t, done: true }))];
541
+ saveTasks(allTasks);
 
 
 
 
542
  send("task_update", { tasks: allTasks });
543
+ versionFile(filePath);
544
+ updateMeta({ last_ai_edit: new Date().toISOString() });
545
  send("file_updated", { filename: filePath, content: fileContent, description: sum?.summary || "Updated" });
546
+ send("message", { text: sum?.summary || "Changes applied via sed" });
547
 
548
  } else {
549
  const editRaw = await callAI(
550
+ `You are Cryo - a precise developer AI. Produce the complete updated file.
551
+ RULES: match existing style, preserve everything not changed, output must be complete working file.
552
+ Return ONLY valid JSON:
553
+ {"content":"COMPLETE file content","description":"what changed","tasks_completed":["t1"],"status":"Done - what was accomplished"}`,
554
+ `User request: "${message}"\nFile: ${filePath} (${fileLines} lines)\nCurrent:\n${fileContent.substring(0, 4000)}\nCommand results:\n${resultsText}\nProduce complete updated file.`,
555
+ modelKey
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
  );
557
 
558
  let editParsed = parseJSON(editRaw);
 
560
  editParsed = { content: fileContent, description: "No changes", status: "Done", tasks_completed: [] };
561
  }
562
 
563
+ // Add Cryo inline comments
564
+ editParsed.content = editParsed.content;
565
 
566
+ versionFile(filePath);
567
+
568
+ send("status", { text: "Checking new content before saving..." });
569
  const tmpName = `__cryo_presave_${Date.now()}.js`;
570
  const tmpPath = path.join(HOME_DIR, tmpName);
571
  fs.writeFileSync(tmpPath, editParsed.content, "utf8");
572
+ await syntaxFixLoop(tmpName, send, modelKey, 3);
 
573
  editParsed.content = fs.readFileSync(tmpPath, "utf8");
574
  fs.unlinkSync(tmpPath);
575
 
576
  fs.writeFileSync(absPath, editParsed.content, "utf8");
577
+ send("status", { text: `Saved ${filePath}` });
578
 
579
+ await syntaxFixLoop(filePath, send, modelKey, 3);
580
  fileContent = fs.readFileSync(absPath, "utf8");
581
 
582
  const allTasks = [
583
  ...existingTasks.map(t => ({ ...t, done: true })),
584
  ...(plan.tasks || []).map(t => ({ ...t, done: true, note: "done this session" }))
585
  ];
586
+ saveTasks(allTasks);
587
+ updateMeta({ last_ai_edit: new Date().toISOString() });
588
  send("task_update", { tasks: allTasks });
 
589
  send("file_updated", { filename: filePath, content: fileContent, description: editParsed.description });
590
+ send("message", { text: editParsed.status || editParsed.description });
591
  send("tree_update", {});
592
  }
593
  }
594
 
595
+ const latestTasks = readTasks();
596
  const stillPending = latestTasks.filter(t => !t.done);
597
  if (stillPending.length > 0) {
598
+ send("status", { text: `${stillPending.length} tasks still pending` });
599
  send("pending_tasks", { tasks: stillPending, count: stillPending.length });
600
  }
601
 
 
611
  });
612
 
613
  app.listen(PORT, () => {
614
+ console.log(`\n❄️ Cryo Dev Server → http://localhost:${PORT}`);
615
  console.log(`📁 Home dir: ${HOME_DIR}\n`);
616
  });