mythos-code 1.7.1 → 1.7.8

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.
Files changed (2) hide show
  1. package/bin/mythos.js +206 -20
  2. package/package.json +1 -1
package/bin/mythos.js CHANGED
@@ -10,6 +10,107 @@ import { exec, spawn, execFileSync } from "node:child_process";
10
10
  // Wird roh in bin/mythos.js (CLI, ESM) und main.js (App, CommonJS) eingefügt.
11
11
  // Erwartet, dass `fs` und `path` im umgebenden Code schon importiert sind.
12
12
 
13
+ // ---------- Werkzeug-Blöcke robust lesen (CLI, Desktop-App: Haupt- und Fensterprozess) ----------
14
+ // Wird roh über den Platzhalter TOOL_PARSE eingefügt. Keine Abhängigkeiten.
15
+ // Modelle schreiben Aufrufe nicht immer sauber: fehlendes </tool>, <tool_call> statt <tool>, ```json-Zäune,
16
+ // {"name":…,"arguments":{…}} statt flacher Felder, echte Zeilenumbrüche in Strings oder abgeschnittenes JSON.
17
+ // Nichts davon darf als Rohtext im Chat landen.
18
+
19
+ const TOOL_NAMES = ["run", "read", "edit", "write", "search", "ls", "websearch", "fetch", "cloud", "todo"];
20
+ const TOOL_OPEN_RE = /<(tool_call|tool_use|tool)(?:\s[^>]*)?>/i;
21
+ // Ein ganzer Block – auch nicht geschlossen (dann bis zum Textende).
22
+ const TOOL_BLOCK_RE = /<(tool_call|tool_use|tool)(?:\s[^>]*)?>[\s\S]*?(?:<\/\1\s*>|$)/gi;
23
+
24
+ /** Alle Werkzeug-Blöcke und verwaiste End-Tags entfernen. */
25
+ function stripToolBlocks(text) {
26
+ return String(text || "").replace(TOOL_BLOCK_RE, "").replace(/<\/(?:tool_call|tool_use|tool)\s*>/gi, "");
27
+ }
28
+
29
+ /** Erstes JSON-Objekt im Text herausschneiden. Rohe Zeilenumbrüche in Strings werden escaped,
30
+ * abgeschnittenes JSON wird geschlossen. → { json, complete } oder null. */
31
+ function jsonObjectAt(s) {
32
+ const start = s.indexOf("{");
33
+ if (start < 0) return null;
34
+ let out = "", inStr = false, esc = false;
35
+ const stack = [];
36
+ for (let i = start; i < s.length; i++) {
37
+ const ch = s[i];
38
+ if (inStr) {
39
+ if (esc) { esc = false; out += ch; continue; }
40
+ if (ch === "\\") { esc = true; out += ch; continue; }
41
+ if (ch === '"') { inStr = false; out += ch; continue; }
42
+ out += ch === "\n" ? "\\n" : ch === "\r" ? "\\r" : ch === "\t" ? "\\t" : ch;
43
+ continue;
44
+ }
45
+ if (ch === '"') { inStr = true; out += ch; continue; }
46
+ if (ch === "{" || ch === "[") stack.push(ch === "{" ? "}" : "]");
47
+ else if (ch === "}" || ch === "]") { stack.pop(); out += ch; if (!stack.length) return { json: out, complete: true }; continue; }
48
+ out += ch;
49
+ }
50
+ // Abgeschnitten: offenen String und Klammern schließen, hängendes Komma/Doppelpunkt weg.
51
+ if (esc) out = out.slice(0, -1);
52
+ if (inStr) out += '"';
53
+ out = out.replace(/[,:]\s*$/, "");
54
+ while (stack.length) out += stack.pop();
55
+ return { json: out, complete: false };
56
+ }
57
+
58
+ /** Ersten Werkzeug-Aufruf aus einer Antwort lesen.
59
+ * → { tool: {name, …} | null, error: string | "", text: sichtbarer Text ohne Werkzeug-Blöcke } */
60
+ function parseToolBlock(out) {
61
+ out = String(out || "");
62
+ const text = stripToolBlocks(out).trim();
63
+ const m = TOOL_OPEN_RE.exec(out);
64
+ if (!m) return { tool: null, error: "", text };
65
+ let body = out.slice(m.index + m[0].length);
66
+ const end = body.search(/<\/(?:tool_call|tool_use|tool)\s*>/i);
67
+ if (end >= 0) body = body.slice(0, end);
68
+ body = body.replace(/```[a-z]*\s*/gi, "");
69
+ const raw = jsonObjectAt(body);
70
+ if (!raw) return { tool: null, error: "Im Werkzeug-Block steht kein JSON-Objekt.", text };
71
+ let t;
72
+ try { t = JSON.parse(raw.json); } catch (e) { return { tool: null, error: "Werkzeug-JSON ungültig (" + e.message + ").", text }; }
73
+ if (!t || typeof t !== "object" || Array.isArray(t)) return { tool: null, error: "Werkzeug-JSON muss ein Objekt sein.", text };
74
+ // Name auch aus <tool name="…"> oder anderen üblichen Feldern.
75
+ const attr = m[0].match(/\bname\s*=\s*["']([^"']+)["']/i);
76
+ if (typeof t.name !== "string") t.name = String(t.tool_name || t.function || (attr && attr[1]) || "");
77
+ // {"name":"x","arguments":{…}} / "input" / "parameters" / "args" flach machen (bei MCP: nach "args").
78
+ for (const k of ["arguments", "input", "parameters", "args"]) {
79
+ const a = t[k];
80
+ if (!a || typeof a !== "object" || Array.isArray(a)) continue;
81
+ if (t.name === "mcp") { if (k !== "args") { delete t[k]; if (!t.args) t.args = a; } continue; }
82
+ delete t[k]; for (const x of Object.keys(a)) if (!(x in t)) t[x] = a[x];
83
+ }
84
+ // mcp__server__tool (Hook-Schreibweise) → MCP-Aufruf.
85
+ const mm = t.name.match(/^mcp__(.+?)__(.+)$/);
86
+ if (mm) { const { name, ...rest } = t; t = { name: "mcp", server: mm[1], tool: mm[2], args: rest.args || rest }; }
87
+ t.name = t.name.trim();
88
+ if (!t.name) return { tool: null, error: "Im Werkzeug-Block fehlt \"name\".", text };
89
+ // Abgeschnittene Aufrufe nicht ausführen (sonst z. B. halbe Dateien) – außer harmlose wie todo.
90
+ if (!raw.complete && t.name !== "todo") return { tool: null, error: "Der Werkzeug-Aufruf " + t.name + " ist abgeschnitten.", text };
91
+ return { tool: t, error: "", text };
92
+ }
93
+
94
+ /** Prüfen, ob es das Werkzeug wirklich gibt. extra = { mcp: [{ server, tools: [Namen] }], browser: bool }.
95
+ * → "" wenn ok, sonst eine Erklärung fürs Modell (mit der Liste, was es gibt). */
96
+ function toolProblem(t, extra) {
97
+ extra = extra || {};
98
+ const mcp = extra.mcp || [];
99
+ const avail = TOOL_NAMES.concat(mcp.length ? ["mcp"] : [], extra.browser ? ["browser"] : []);
100
+ const list = "Verfügbar sind nur: " + avail.join(", ") + (mcp.length ? " · MCP: " + mcp.map((s) => s.server + " (" + s.tools.join(", ") + ")").join("; ") : " · keine MCP-Connectoren verbunden") + ".";
101
+ const tail = " Erfinde keine Werkzeuge. Wenn etwas damit nicht geht, sag das dem Nutzer kurz und mach mit dem Rest weiter.";
102
+ if (t.name === "mcp") {
103
+ const s = mcp.find((x) => x.server === t.server) || mcp.find((x) => String(x.server).toLowerCase() === String(t.server || "").toLowerCase());
104
+ if (!s) return "FEHLER: MCP-Server „" + (t.server || "?") + "“ ist nicht verbunden. " + list + tail;
105
+ t.server = s.server;
106
+ if (!s.tools.includes(t.tool)) return "FEHLER: MCP-Werkzeug „" + t.tool + "“ gibt es bei " + s.server + " nicht. " + list + tail;
107
+ return "";
108
+ }
109
+ if (t.name === "browser") return extra.browser ? "" : "FEHLER: Das Browser-Plugin ist nicht verbunden. " + list + tail;
110
+ return TOOL_NAMES.includes(t.name) ? "" : "FEHLER: Werkzeug „" + t.name + "“ gibt es nicht. " + list + tail;
111
+ }
112
+
113
+
13
114
  const TOOL_PROMPT = [
14
115
  "Werkzeuge – antworte pro Schritt mit GENAU EINEM Block:",
15
116
  '<tool>{"name":"run","cmd":"..."}</tool> Shell-Befehl ausführen (Ausgabe läuft live mit)',
@@ -20,7 +121,9 @@ const TOOL_PROMPT = [
20
121
  '<tool>{"name":"ls","path":"."}</tool> Ordner auflisten',
21
122
  '<tool>{"name":"websearch","query":"..."}</tool> Im Internet suchen (z. B. Doku, Fehlermeldungen)',
22
123
  '<tool>{"name":"fetch","url":"https://..."}</tool> Webseite als Text lesen',
124
+ '<tool>{"name":"cloud","app":"Name der App"}</tool> Mythos Cloud verbinden: eigenes Backend (Datenbank) + KI-Gateway (Chat, Streaming, Bilder) für die App, die du baust. Nutze das IMMER, sobald die App Daten speichern oder KI nutzen soll – statt Supabase/Firebase/OpenAI-Keys o. Ä. Legt mythos-cloud.js im Projekt an und erklärt die Nutzung.',
23
125
  '<tool>{"name":"todo","items":[{"text":"Schritt","done":false}]}</tool> Aufgabenliste anlegen/aktualisieren (bei größeren Aufgaben zuerst planen, dann Punkte abhaken; immer die komplette Liste senden)',
126
+ "Es gibt NUR diese Werkzeuge (plus die unten genannten MCP-/Browser-Werkzeuge, falls welche aufgeführt sind). Erfinde keine Werkzeuge, Connectoren oder MCP-Server. Fehlt etwas (z. B. ein nicht verbundener Connector), sag das dem Nutzer kurz und mach mit dem Rest weiter. Schließe jeden Block mit </tool>.",
24
127
  "Nach jedem Werkzeug bekommst du das Ergebnis. Suche erst, statt Dateien blind zu lesen. Ändere bestehende Dateien mit edit statt write.",
25
128
  "Arbeite Schritt für Schritt, bis die Aufgabe erledigt ist, dann antworte normal ohne <tool> (Markdown erlaubt).",
26
129
  ].join("\n");
@@ -52,10 +155,24 @@ const goalPrompt = (goal) => goal
52
155
  "\nDer Auftraggeber ist NICHT am Rechner und liest erst viel später mit, was hier im Chat passiert ist – frage ihn nichts, er kann nicht antworten. Behandle jede Entscheidung, jede Rückfrage und jedes Hindernis als etwas, das du selbst lösen musst:" +
53
156
  "\n- Gib bei Fehlern NIEMALS auf. Ein fehlgeschlagener Befehl, eine fehlende Abhängigkeit, ein kaputter Build sind Aufgaben, keine Endstationen: lies die Fehlermeldung genau, versuche eine andere Vorgehensweise, installiere Fehlendes selbst (z. B. per npm/pip/winget), nutze bei Bedarf websearch/fetch um die Lösung nachzuschlagen, und probiere es erneut. Erst wenn du wirklich mehrere grundverschiedene Ansätze erfolglos versucht hast, notierst du das Problem knapp und machst mit dem Rest der Aufgabe weiter." +
54
157
  "\n- Triff sinnvolle Annahmen statt zu fragen, und schreib kurz dazu, welche Annahme du getroffen hast." +
55
- "\n- Teile deinen Fortschritt normal im Chat mit (was du tust und warum), aber warte nicht auf eine Antwort." +
158
+ "\n- Beginne SOFORT mit einer kurzen Bestätigung und deinem Plan (2–6 Stichpunkte) als normalen Text, BEVOR du das erste Werkzeug nutzt – so sieht der Auftraggeber direkt, dass du das Ziel verstanden hast." +
159
+ "\n- Teile deinen Fortschritt normal im Chat mit (was du tust und warum), aber warte nicht auf eine Antwort. Schreibe vor jedem größeren Schritt einen kurzen Satz dazu." +
160
+ "\n- Baue große Projekte in vielen kleinen Dateien (je höchstens ca. 300 Zeilen) statt in einer riesigen Datei auf einmal." +
56
161
  "\n- Das ist eine große, langlaufende Aufgabe: Plane für 1 bis 5 Stunden durchgehende Arbeit in vielen kleinen Schritten, nicht für ein paar Minuten. Höre nicht zu früh auf – arbeite lieber zu gründlich als zu knapp." +
57
162
  "\nArbeite komplett selbstständig weiter, bis das Ziel vollständig erreicht und überprüft ist. Erst dann schreibe in deiner letzten Antwort eine eigene Zeile: ZIEL ERREICHT"
58
163
  : "";
164
+ /** Kurzer Live-Status aus der gerade gestreamten Antwort, z. B. „✍ schreibt src/main.js · 12 KB“. */
165
+ function streamActivity(full) {
166
+ if (!full) return "denkt nach…";
167
+ const k = full.lastIndexOf("<tool>");
168
+ if (k < 0 || full.indexOf("</tool>", k) >= 0) return "schreibt Antwort…";
169
+ const t = full.slice(k + 6), kb = (t.length / 1024).toFixed(1).replace(".", ",") + " KB";
170
+ const f = (key) => { const m = t.match(new RegExp('"' + key + '"\\s*:\\s*"([^"\\\\]{0,120})')); return m ? m[1] : ""; };
171
+ const name = f("name"), path = f("path");
172
+ if (name === "write" || name === "edit") return "✍ " + (name === "write" ? "schreibt " : "ändert ") + (path || "eine Datei") + " · " + kb;
173
+ if (name === "run") return "⚙ bereitet Befehl vor: " + f("cmd").slice(0, 60);
174
+ return "🔧 bereitet " + (name || "Werkzeug") + " vor…";
175
+ }
59
176
  const GOAL_NUDGE = "Das Ziel ist noch nicht als erreicht gemeldet. Der Auftraggeber ist nicht da – gib bei Problemen nicht auf, sondern versuche einen anderen Weg, nutze websearch/fetch für Lösungen und arbeite selbstständig weiter, ohne Rückfragen. Wenn es wirklich vollständig erledigt und geprüft ist, schreibe ZIEL ERREICHT.";
60
177
  const SUMMARY_PROMPT = "Fasse jetzt kurz auf Deutsch zusammen, OHNE Werkzeuge:\n**Geändert:** welche Dateien und was\n**Geklappt:** was funktioniert (und wie geprüft)\n**Offen:** was noch fehlt oder beachtet werden muss\nMaximal 12 Zeilen.";
61
178
 
@@ -412,7 +529,7 @@ let goal = "";
412
529
  const SYSTEM = () => "Du bist Mythos Code, ein autonomer Coding-Agent im Terminal des Nutzers (OS: " + process.platform + ", Projektordner: " + ROOT + ").\n" +
413
530
  TOOL_PROMPT + memoryPrompt(readMemory(ROOT)) + goalPrompt(goal);
414
531
 
415
- const SITE = process.env.MYTHOS_SITE_URL || "https://ai-mythos.lovable.app";
532
+ const SITE = process.env.MYTHOS_SITE_URL || "https://mythos-core-ai.lovable.app";
416
533
  const FN = process.env.MYTHOS_FN_URL || API.replace(/\/v1-messages$/, "");
417
534
  const authPost = async (body) => (await fetch(FN + "/cli-auth", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })).json();
418
535
 
@@ -452,10 +569,33 @@ async function login() {
452
569
  const fmt = (ms) => { const s = Math.floor(ms / 1000); return (s >= 60 ? Math.floor(s / 60) + "m " : "") + (s % 60) + "s"; };
453
570
 
454
571
  // Live-Timer: läuft während der ganzen Aufgabe, pausiert für Ausgaben und Rückfragen.
455
- let T0 = 0, tick = null;
572
+ let T0 = 0, tick = null, activity = "";
456
573
  const clearLine = () => process.stdout.write("\r\x1b[K");
457
- const startTick = () => { if (tick || !working) return; tick = setInterval(() => process.stdout.write("\r" + C.d + "⏳ Mythos arbeitet… " + fmt(Date.now() - T0) + " (Strg+C = stoppen)" + C.x + " "), 250); };
574
+ // Zeigt live, was Mythos gerade tut (z. B. „✍ schreibt src/main.js · 12 KB“) – auf Terminalbreite gekürzt.
575
+ const tickLine = () => {
576
+ const s = "⏳ Mythos arbeitet… " + fmt(Date.now() - T0) + (activity ? " · " + activity : "") + " (Strg+C = stoppen)";
577
+ const w = (process.stdout.columns || 100) - 2;
578
+ return s.length > w ? s.slice(0, w - 1) + "…" : s;
579
+ };
580
+ const startTick = () => { if (tick || !working) return; tick = setInterval(() => process.stdout.write("\r\x1b[K" + C.d + tickLine() + C.x), 250); };
458
581
  const stopTick = () => { if (tick) { clearInterval(tick); tick = null; } clearLine(); };
582
+ let adN = 0, adTier = null, adAdminDay = "";
583
+ async function maybeAd() {
584
+ try {
585
+ if (adTier == null) { const j = await (await fetch(API.replace(/\/v1-messages.*$/, "") + "/cli-auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "usage", api_key: cfg.key }) })).json(); adTier = j.admin ? "admin" : (j.tier || "free"); }
586
+ let due = false; const today = new Date().toDateString();
587
+ if (adTier === "admin") { due = adAdminDay !== today; adAdminDay = today; }
588
+ else { const every = adTier === "free" ? 4 : adTier === "light" ? 10 : 0; due = every > 0 && (++adN % every === 0); }
589
+ if (due) {
590
+ const fnBase = API.replace(/\/v1-messages.*$/, "");
591
+ let ads = []; try { ads = (await (await fetch(fnBase + "/ad-review", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "list" }) })).json()).ads || []; } catch {}
592
+ const pool = [{ house: true, weight: 3 }].concat(ads); let r = Math.random() * pool.reduce((t, a) => t + Math.max(1, a.weight), 0); let ad = pool[0];
593
+ for (const a of pool) { r -= Math.max(1, a.weight); if (r <= 0) { ad = a; break; } }
594
+ if (!ad.house) { console.log("\x1b[2m📣 Werbung\x1b[0m \x1b[1m" + ad.product + "\x1b[0m – " + ad.ad_text + ": " + ad.link + "\n\x1b[2m Buche deine eigene Werbung: https://mythos-core-ai.lovable.app/werbung\x1b[0m\n"); return; }
595
+ }
596
+ if (due) console.log("\x1b[2m📣 Werbung\x1b[0m \x1b[1m@at\x1b[0m – dein Feed voller Vibes: https://at-feed-vibes.lovable.app/\n\x1b[2m Buche deine eigene Werbung: https://mythos-core-ai.lovable.app/werbung\x1b[0m\n");
597
+ } catch {}
598
+ }
459
599
  const say = (s) => { const was = !!tick; stopTick(); console.log(s); if (was) startTick(); };
460
600
 
461
601
  // ---------- Stoppen mit Strg+C ----------
@@ -503,9 +643,9 @@ function streamPrinter() {
503
643
  return {
504
644
  push(full) {
505
645
  let vis = full;
506
- const k = vis.indexOf("<tool>");
646
+ const k = vis.search(/<tool(?:_call|_use)?[\s>]/i);
507
647
  if (k >= 0) vis = vis.slice(0, k);
508
- else for (let n = 5; n > 0; n--) if (vis.endsWith("<tool>".slice(0, n))) { vis = vis.slice(0, -n); break; }
648
+ else { const p = vis.match(/<(?:t(?:o(?:o(?:l(?:_(?:c(?:a(?:l(?:l)?)?)?|u(?:s(?:e)?)?)?)?)?)?)?)?$/i); if (p) vis = vis.slice(0, p.index); }
509
649
  if (vis.length <= printed) return;
510
650
  buf += vis.slice(printed); printed = vis.length;
511
651
  let i; while ((i = buf.indexOf("\n")) !== -1) { line(buf.slice(0, i)); buf = buf.slice(i + 1); }
@@ -514,6 +654,20 @@ function streamPrinter() {
514
654
  };
515
655
  }
516
656
 
657
+ // Sichtbare Gedanken (wie bei base44): laufen gedimmt Wort für Wort, bevor die eigentliche Antwort beginnt.
658
+ function thinkPrinter() {
659
+ let printed = 0, started = false, ended = false;
660
+ return {
661
+ push(full) {
662
+ if (ended) return;
663
+ if (!started) { stopTick(); process.stdout.write(C.d + "💭 "); started = true; }
664
+ if (full.length <= printed) return;
665
+ process.stdout.write(full.slice(printed)); printed = full.length;
666
+ },
667
+ end() { if (ended) return; ended = true; if (started) process.stdout.write(C.x + "\n"); startTick(); },
668
+ };
669
+ }
670
+
517
671
  // ---------- Modell ----------
518
672
  // Verlauf kürzen, damit lange Aufgaben nicht an zu großem Kontext scheitern. level 0 = mild, 2 = stark.
519
673
  const shorten = (s, max) => s.length <= max ? s : s.slice(0, Math.floor(max * 0.7)) + "\n…[gekürzt]…\n" + s.slice(s.length - Math.floor(max * 0.3));
@@ -529,7 +683,7 @@ function compact(history, level) {
529
683
  }
530
684
 
531
685
  let tokens = { in: 0, out: 0 };
532
- async function once(messages, onText) {
686
+ async function once(messages, onText, onThink) {
533
687
  const c = ctl = new AbortController(); const to = setTimeout(() => c.abort(), 170000);
534
688
  const system = SYSTEM();
535
689
  tokens.in += estimateTokens(system, messages);
@@ -540,7 +694,7 @@ async function once(messages, onText) {
540
694
  body: JSON.stringify({ model: cfg.model || "mythos-code", max_tokens: 8000, system, messages, stream: true }),
541
695
  });
542
696
  if (!r.ok) { const j = await r.json().catch(() => ({})); const e = new Error(j?.error?.message || ("HTTP " + r.status)); e.status = r.status; throw e; }
543
- const reader = r.body.getReader(); const dec = new TextDecoder(); let buf = "", out = "";
697
+ const reader = r.body.getReader(); const dec = new TextDecoder(); let buf = "", out = "", think = "";
544
698
  while (true) {
545
699
  const { done, value } = await reader.read(); if (done) break;
546
700
  buf += dec.decode(value, { stream: true });
@@ -549,6 +703,8 @@ async function once(messages, onText) {
549
703
  if (!line.startsWith("data:")) continue;
550
704
  let p; try { p = JSON.parse(line.slice(5)); } catch { continue; }
551
705
  if (p.type === "error") { const e = new Error(p.error?.message || "Überlastet"); e.status = 529; throw e; }
706
+ // Sichtbare Gedanken: kommen als eigener Block VOR der Antwort.
707
+ if (p.delta?.type === "thinking_delta" && p.delta.thinking) { think += p.delta.thinking; if (onThink) onThink(think); }
552
708
  if (p.delta?.text) { out += p.delta.text; if (onText) onText(out); }
553
709
  }
554
710
  }
@@ -558,14 +714,16 @@ async function once(messages, onText) {
558
714
  } finally { clearTimeout(to); }
559
715
  }
560
716
 
561
- async function call(history, onText) {
717
+ async function call(history, onText, onThink) {
562
718
  for (let a = 0; a < 6; a++) {
563
719
  if (stopped) throw new Error("Gestoppt");
564
- try { return await once(compact(history, Math.min(2, Math.floor(a / 2))), onText); }
720
+ try { return await once(compact(history, Math.min(2, Math.floor(a / 2))), onText, onThink); }
565
721
  catch (e) {
566
722
  if (stopped) throw e;
567
723
  if (e.status === 401) throw new Error("Anmeldung ungültig – bitte neu anmelden: mythos login");
568
724
  if (/Daily limit/i.test(e.message)) throw new Error("Tageslimit erreicht – mit Pro unbegrenzt.");
725
+ // Konfigurationsproblem (Guthaben leer, kein Ausweich-Schlüssel) statt normaler Überlastung -> nicht sinnlos wiederholen.
726
+ if (/nicht konfiguriert/i.test(e.message)) throw e;
569
727
  await new Promise((r) => setTimeout(r, Math.min(8000, 1500 * (a + 1))));
570
728
  }
571
729
  }
@@ -599,6 +757,19 @@ function runLive(cmd) {
599
757
  });
600
758
  }
601
759
 
760
+ // Mythos Cloud: Backend + KI-Gateway für die gebaute App verbinden und mythos-cloud.js anlegen.
761
+ async function cloudConnect(t) {
762
+ try {
763
+ const r = await fetch(FN + "/app-cloud", { method: "POST", headers: { "content-type": "application/json" },
764
+ body: JSON.stringify({ action: "connect", api_key: cfg.key, name: String(t.app || path.basename(ROOT)) }) });
765
+ const j = await r.json().catch(() => ({}));
766
+ if (!r.ok || !j.client_js) return "FEHLER: Mythos Cloud: " + (j.error || "HTTP " + r.status);
767
+ const res = fileTool({ name: "write", path: (t.path = t.path || j.file), content: j.client_js }, ROOT);
768
+ say(C.g + " ☁ Mythos Cloud verbunden (" + j.app + ")" + C.x);
769
+ return res.result + "\n\n" + j.guide;
770
+ } catch (e) { return "FEHLER: Mythos Cloud nicht erreichbar: " + e.message; }
771
+ }
772
+
602
773
  async function runTool(t) {
603
774
  if (t.name === "todo") {
604
775
  const items = normalizeTodos(t);
@@ -612,6 +783,7 @@ async function runTool(t) {
612
783
  : t.name === "write" ? "write " + t.path + " (" + String(t.content || "").length + " Zeichen)"
613
784
  : t.name === "websearch" ? "websearch " + t.query
614
785
  : t.name === "fetch" ? "fetch " + t.url
786
+ : t.name === "cloud" ? "cloud verbinden: " + (t.app || "App")
615
787
  : t.name + " " + (t.path || "");
616
788
  say(C.y + "⚙ " + label + C.x);
617
789
  // Sicherheitsnetz: gefährliche Befehle fragen immer nach (auch mit --yes) und werden bei /goal blockiert.
@@ -624,13 +796,14 @@ async function runTool(t) {
624
796
  startTick();
625
797
  if (a !== "j" && a !== "y") return "Vom Nutzer abgelehnt (Sicherheitsnetz). Finde einen sichereren Weg.";
626
798
  }
627
- if ((t.name === "run" || t.name === "write" || t.name === "edit") && !autoYes && !why) {
799
+ if ((t.name === "run" || t.name === "write" || t.name === "edit" || t.name === "cloud") && !autoYes && !why) {
628
800
  stopTick();
629
801
  const a = (await ask(C.d + " Erlauben? [j/N/a=immer] " + C.x)).trim().toLowerCase();
630
802
  startTick();
631
803
  if (a === "a") autoYes = true; else if (a !== "j" && a !== "y") return "Vom Nutzer abgelehnt.";
632
804
  }
633
805
  if (t.name === "run") return runLive(t.cmd);
806
+ if (t.name === "cloud") return cloudConnect(t);
634
807
  if (isWebTool(t)) {
635
808
  const r = await webTool(t);
636
809
  say(C.d + " " + r.result.split("\n").filter(Boolean).slice(0, 3).join(" · ").slice(0, 160) + C.x);
@@ -647,19 +820,26 @@ async function turnInner(history) {
647
820
  let nudges = 0;
648
821
  for (let i = 0; i < max && !stopped; i++) {
649
822
  const pr = streamPrinter();
650
- const out = await call(history, (full) => pr.push(full));
651
- pr.end();
823
+ let think = null;
824
+ activity = "denkt nach…";
825
+ const out = await call(history,
826
+ (full) => { if (think) think.end(); activity = streamActivity(full); pr.push(full); },
827
+ (t) => { if (!think) think = thinkPrinter(); think.push(t); });
828
+ if (think) think.end();
829
+ pr.end(); activity = "";
652
830
  if (stopped) break;
653
831
  history.push({ role: "assistant", content: out });
654
- const m = out.match(/<tool>([\s\S]*?)<\/tool>/);
655
- if (!m) {
832
+ const pb = parseToolBlock(out);
833
+ if (!pb.tool && !pb.error) {
656
834
  if (!goal || /ZIEL ERREICHT/.test(out)) return goal ? "reached" : "done";
657
835
  if (++nudges > 40) { say(C.y + "🎯 Mythos kommt beim Ziel nicht weiter – schau es dir bitte an (/weiter macht weiter)." + C.x); return "stuck"; }
658
836
  history.push({ role: "user", content: GOAL_NUDGE });
659
837
  continue;
660
838
  }
661
839
  nudges = 0;
662
- let t; try { t = JSON.parse(m[1]); } catch { history.push({ role: "user", content: "Tool-JSON ungültig." }); continue; }
840
+ if (pb.error) { history.push({ role: "user", content: pb.error + " Sende den Block vollständig als gültiges JSON in <tool>…</tool>." }); continue; }
841
+ const t = pb.tool, bad = toolProblem(t);
842
+ if (bad) { history.push({ role: "user", content: "Werkzeug-Ergebnis:\n" + bad }); continue; }
663
843
  const res = await runTool(t);
664
844
  history.push({ role: "user", content: "Werkzeug-Ergebnis:\n" + res });
665
845
  }
@@ -671,7 +851,11 @@ async function summarize(history) {
671
851
  history.push({ role: "user", content: SUMMARY_PROMPT });
672
852
  say(C.m + C.b + "📋 Zusammenfassung" + C.x);
673
853
  const pr = streamPrinter();
674
- const out = await call(history, (full) => pr.push(full));
854
+ let think = null;
855
+ const out = await call(history,
856
+ (full) => { if (think) think.end(); pr.push(full); },
857
+ (t) => { if (!think) think = thinkPrinter(); think.push(t); });
858
+ if (think) think.end();
675
859
  pr.end();
676
860
  history.push({ role: "assistant", content: out });
677
861
  return out;
@@ -692,6 +876,7 @@ async function turn(history, input, images) {
692
876
  const took = fmt(Date.now() - T0);
693
877
  console.log((stopped ? C.r + "■ Gestoppt nach " + took : C.g + "✓ Mythos hat " + took + " gearbeitet") + C.x +
694
878
  C.d + " · ≈ " + fmtTokens(tokens.in + tokens.out - tok0) + " Tokens (Sitzung ≈ " + fmtTokens(tokens.in + tokens.out) + ")" + C.x + "\n");
879
+ if (!stopped && status !== "error") await maybeAd();
695
880
  if (status === "reached") { console.log(C.g + "🎯 Ziel erreicht: " + wasGoal + C.x + "\n"); goal = ""; }
696
881
  // Handy-Benachrichtigung bei /goal oder längeren Aufgaben.
697
882
  if (cfg.notify && (wasGoal || Date.now() - T0 > 60000)) {
@@ -717,7 +902,7 @@ async function commit(msg) {
717
902
  stopped = false;
718
903
  try {
719
904
  msg = (await call([{ role: "user", content: "Schreibe eine kurze Git-Commit-Nachricht auf Deutsch für diese Änderungen: erste Zeile max. 72 Zeichen, optional Leerzeile + Stichpunkte. Antworte NUR mit der Nachricht, ohne Werkzeuge, ohne Codeblock.\n\n" + st.out + "\n" + diff.slice(0, 20000) }]))
720
- .replace(/<tool>[\s\S]*?<\/tool>/g, "").replace(/^```\w*\n?|```$/g, "").trim();
905
+ .replace(TOOL_BLOCK_RE, "").replace(/^```\w*\n?|```$/g, "").trim();
721
906
  } catch (e) { clearLine(); return say(C.r + "⚠ " + e.message + C.x); }
722
907
  clearLine();
723
908
  }
@@ -812,6 +997,7 @@ async function command(q, history) {
812
997
  if (/^(stop|aus|ende|beenden)$/i.test(arg)) { goal = ""; return console.log("🎯 Ziel beendet.\n"); }
813
998
  if (!autoYes) { autoYes = true; console.log(C.d + "Vollzugriff für /goal eingeschaltet – Mythos fragt nicht mehr nach." + C.x); }
814
999
  goal = arg;
1000
+ console.log(C.m + C.b + "🎯 Ziel angenommen: " + C.x + arg + C.d + "\n Mythos plant jetzt und arbeitet selbstständig – der Fortschritt erscheint hier. Stoppen: Strg+C" + C.x);
815
1001
  return turn(history, "Neues Ziel: " + arg + "\nArbeite jetzt komplett selbstständig daran, bis es erreicht und geprüft ist.");
816
1002
  }
817
1003
  if (cmd === "/weiter") {
@@ -858,7 +1044,7 @@ async function command(q, history) {
858
1044
  let sum = "";
859
1045
  try {
860
1046
  sum = (await call([{ role: "user", content: "Fasse den bisherigen Verlauf dieses Coding-Chats so zusammen, dass ein Agent nahtlos weiterarbeiten kann: **Ziel**, **Stand**, **Wichtige Dateien & Entscheidungen**, **Offene Punkte**. Maximal 25 Zeilen, ohne Werkzeuge.\n\nVERLAUF:\n" + transcript }]))
861
- .replace(/<tool>[\s\S]*?<\/tool>/g, "").trim();
1047
+ .replace(TOOL_BLOCK_RE, "").trim();
862
1048
  } catch (e) { clearLine(); return console.log(C.r + "⚠ " + e.message + C.x + "\n"); }
863
1049
  clearLine();
864
1050
  history.length = 0;
@@ -868,7 +1054,7 @@ async function command(q, history) {
868
1054
  if (cmd === "/stats") { const b = gitBranch(); return console.log(mdLines(projectStats(ROOT) + (b ? "\n**Git:** Branch `" + b + "`" : "")) + "\n"); }
869
1055
  if (cmd === "/export") {
870
1056
  const file = path.resolve(ROOT, arg || "mythos-chat-" + new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-") + ".md");
871
- const msgs = history.map((m) => ({ role: /^Werkzeug-Ergebnis/.test(m.content) ? "tool" : m.role, text: String(m.content).replace(/<tool>[\s\S]*?<\/tool>/g, "").slice(0, 20000) })).filter((m) => m.text.trim());
1057
+ const msgs = history.map((m) => ({ role: /^Werkzeug-Ergebnis/.test(m.content) ? "tool" : m.role, text: String(m.content).replace(TOOL_BLOCK_RE, "").slice(0, 20000) })).filter((m) => m.text.trim());
872
1058
  fs.writeFileSync(file, chatToMarkdown("Mythos-Chat – " + path.basename(ROOT), msgs));
873
1059
  return console.log(C.g + "✓ Gespeichert: " + file + C.x + "\n");
874
1060
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mythos-code",
3
- "version": "1.7.1",
3
+ "version": "1.7.8",
4
4
  "description": "Mythos Code – KI-Coding-Agent im Terminal (by Mythoscraft)",
5
5
  "bin": {
6
6
  "mythos": "bin/mythos.js",