blun-king-cli 5.4.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/bin/blun.js +100 -0
  2. package/lib/auth.js +162 -0
  3. package/lib/chat.js +82 -0
  4. package/lib/client.js +65 -0
  5. package/lib/ui.js +215 -0
  6. package/package.json +14 -18
  7. package/api.js +0 -965
  8. package/blun-cli.js +0 -3726
  9. package/blun-cli.js.claude +0 -3578
  10. package/blunking-api.js +0 -7
  11. package/bot.js +0 -188
  12. package/browser-controller.js +0 -116
  13. package/chat-memory.js +0 -103
  14. package/file-helper.js +0 -63
  15. package/fuzzy-match.js +0 -78
  16. package/identities.js +0 -106
  17. package/installer.js +0 -160
  18. package/job-manager.js +0 -146
  19. package/local-data.js +0 -71
  20. package/message-builder.js +0 -28
  21. package/noisy-evals.js +0 -38
  22. package/palace-memory.js +0 -246
  23. package/reference-inspector.js +0 -256
  24. package/runtime.js +0 -570
  25. package/task-executor.js +0 -104
  26. package/tests/browser-controller.test.js +0 -47
  27. package/tests/cli.test.js +0 -93
  28. package/tests/file-helper.test.js +0 -18
  29. package/tests/installer.test.js +0 -39
  30. package/tests/job-manager.test.js +0 -99
  31. package/tests/merge-compat.test.js +0 -77
  32. package/tests/messages.test.js +0 -23
  33. package/tests/noisy-evals.test.js +0 -12
  34. package/tests/noisy-intent-corpus.test.js +0 -45
  35. package/tests/reference-inspector.test.js +0 -42
  36. package/tests/runtime.test.js +0 -119
  37. package/tests/task-executor.test.js +0 -40
  38. package/tests/tools.test.js +0 -23
  39. package/tests/user-profile.test.js +0 -66
  40. package/tests/website-builder.test.js +0 -66
  41. package/tmp-build-smoke/nicrazy-landing/index.html +0 -53
  42. package/tmp-build-smoke/nicrazy-landing/style.css +0 -110
  43. package/tmp-shot-smoke/website-shot-1776006760424.png +0 -0
  44. package/tmp-shot-smoke/website-shot-1776007850007.png +0 -0
  45. package/tmp-shot-smoke/website-shot-1776007886209.png +0 -0
  46. package/tmp-shot-smoke/website-shot-1776007903766.png +0 -0
  47. package/tmp-shot-smoke/website-shot-1776008737117.png +0 -0
  48. package/tmp-shot-smoke/website-shot-1776008988859.png +0 -0
  49. package/tmp-smoke/nicrazy-landing/index.html +0 -66
  50. package/tmp-smoke/nicrazy-landing/style.css +0 -104
  51. package/tools.js +0 -177
  52. package/user-profile.js +0 -395
  53. package/website-builder.js +0 -394
  54. package/website-shot-1776010648230.png +0 -0
  55. package/website_builder.txt +0 -38
package/installer.js DELETED
@@ -1,160 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const os = require("os");
4
- const { execFile } = require("child_process");
5
- const { hasApproxWord, hasApproxPhrase } = require("./fuzzy-match");
6
-
7
- const BLUN_HOME = process.env.BLUN_HOME || path.join(os.homedir(), ".blun");
8
- const EXTENSIONS_DIR = path.join(BLUN_HOME, "extensions");
9
- if (!fs.existsSync(EXTENSIONS_DIR)) fs.mkdirSync(EXTENSIONS_DIR, { recursive: true });
10
-
11
- function execFileAsync(command, args, options = {}) {
12
- return new Promise((resolve, reject) => {
13
- execFile(command, args, { ...options, maxBuffer: 20 * 1024 * 1024 }, (error, stdout, stderr) => {
14
- if (error) reject(new Error(stderr || error.message));
15
- else resolve({ stdout, stderr });
16
- });
17
- });
18
- }
19
-
20
- function classifySource(source) {
21
- if (/^https?:\/\/.+\.git$/i.test(source) || /github\.com[:/].+\/.+/i.test(source)) {
22
- return "git";
23
- }
24
- if (/^https?:\/\//i.test(source)) {
25
- return "url";
26
- }
27
- return "npm";
28
- }
29
-
30
- function resolveInstallRequest(text) {
31
- const raw = String(text || "").trim();
32
- const urlMatch = raw.match(/https?:\/\/[^\s]+/i);
33
- if (urlMatch) {
34
- return {
35
- source: urlMatch[0],
36
- kind: classifySource(urlMatch[0]),
37
- label: urlMatch[0]
38
- };
39
- }
40
-
41
- const normalized = raw.toLowerCase();
42
- const aliases = [
43
- {
44
- match: (value) => /\btelegramm?\s+bot\b|\btelegramm?-bot\b/.test(value) || hasApproxPhrase(value, "telegram bot"),
45
- source: "node-telegram-bot-api",
46
- label: "Telegram Bot API"
47
- },
48
- {
49
- match: (value) => /\bplaywright\b/.test(value) || hasApproxWord(value, ["playwright"]) || hasApproxPhrase(value, "playwright"),
50
- source: "playwright",
51
- label: "Playwright"
52
- },
53
- {
54
- match: (value) => /\bdiscord bot\b|\bdiscord\.js\b/.test(value) || hasApproxPhrase(value, "discord bot"),
55
- source: "discord.js",
56
- label: "Discord.js"
57
- }
58
- ];
59
-
60
- for (const alias of aliases) {
61
- if (alias.match(normalized)) {
62
- return {
63
- source: alias.source,
64
- kind: classifySource(alias.source),
65
- label: alias.label
66
- };
67
- }
68
- }
69
-
70
- const packageMatch = normalized.match(/\b(?:installier(?:e)?|install|installiere|hol dir|lade|besorg dir)\b[\s:,-]*([@a-z0-9._/-]+)\b/i);
71
- const fuzzyInstallVerb = hasApproxWord(normalized, ["installieren", "installier", "install", "insallieren", "instaliier"]);
72
- if (packageMatch) {
73
- return {
74
- source: packageMatch[1],
75
- kind: classifySource(packageMatch[1]),
76
- label: packageMatch[1]
77
- };
78
- }
79
-
80
- if (fuzzyInstallVerb) {
81
- const pkgCandidate = normalized
82
- .split(/\s+/)
83
- .find((token) => /^[@a-z0-9._/-]+$/.test(token) && !hasApproxWord(token, ["installieren", "installier", "install", "den", "die", "das", "dir", "bot"]));
84
- if (pkgCandidate) {
85
- return {
86
- source: pkgCandidate,
87
- kind: classifySource(pkgCandidate),
88
- label: pkgCandidate
89
- };
90
- }
91
- }
92
-
93
- return null;
94
- }
95
-
96
- function npmTargetPath(source) {
97
- return path.join(EXTENSIONS_DIR, "node_modules", source);
98
- }
99
-
100
- function isInstalled(source) {
101
- const kind = classifySource(source);
102
- if (kind === "npm") {
103
- return fs.existsSync(npmTargetPath(source));
104
- }
105
- if (kind === "git") {
106
- const repoName = source.split("/").pop().replace(/\.git$/i, "") || "";
107
- return repoName ? fs.existsSync(path.join(EXTENSIONS_DIR, repoName)) : false;
108
- }
109
- if (kind === "url") {
110
- const safeName = source.replace(/[^\w.-]+/g, "_").slice(0, 80);
111
- return fs.existsSync(path.join(EXTENSIONS_DIR, safeName));
112
- }
113
- return false;
114
- }
115
-
116
- async function installSource(source) {
117
- const kind = classifySource(source);
118
- const gitBinary = process.platform === "win32" ? "git.exe" : "git";
119
- const npmBinary = process.platform === "win32" ? "npm.cmd" : "npm";
120
-
121
- if (kind === "git") {
122
- const repoName = source.split("/").pop().replace(/\.git$/i, "") || `repo-${Date.now()}`;
123
- const target = path.join(EXTENSIONS_DIR, repoName);
124
- if (!fs.existsSync(target)) {
125
- await execFileAsync(gitBinary, ["clone", source, target], { cwd: EXTENSIONS_DIR });
126
- }
127
- return { kind, source, target };
128
- }
129
-
130
- if (kind === "url") {
131
- const safeName = source.replace(/[^\w.-]+/g, "_").slice(0, 80);
132
- const target = path.join(EXTENSIONS_DIR, safeName);
133
- const script = [
134
- `$ProgressPreference='SilentlyContinue'`,
135
- `Invoke-WebRequest -UseBasicParsing -Uri '${source}' -OutFile '${target}'`
136
- ].join("; ");
137
- await execFileAsync("powershell.exe", ["-NoProfile", "-Command", script], { cwd: EXTENSIONS_DIR });
138
- return { kind, source, target };
139
- }
140
-
141
- if (kind === "npm" && isInstalled(source)) {
142
- return { kind, source, target: npmTargetPath(source), alreadyInstalled: true };
143
- }
144
-
145
- if (process.platform === "win32") {
146
- const command = `${npmBinary} install ${source}`;
147
- await execFileAsync("cmd.exe", ["/d", "/s", "/c", command], { cwd: EXTENSIONS_DIR });
148
- } else {
149
- await execFileAsync(npmBinary, ["install", source], { cwd: EXTENSIONS_DIR });
150
- }
151
- return { kind, source, target: path.join(EXTENSIONS_DIR, "node_modules", source) };
152
- }
153
-
154
- module.exports = {
155
- EXTENSIONS_DIR,
156
- classifySource,
157
- resolveInstallRequest,
158
- isInstalled,
159
- installSource
160
- };
package/job-manager.js DELETED
@@ -1,146 +0,0 @@
1
- const fs = require("fs");
2
- const os = require("os");
3
- const path = require("path");
4
- const crypto = require("crypto");
5
-
6
- const BLUN_HOME = process.env.BLUN_HOME || path.join(os.homedir(), ".blun");
7
- const DATA_DIR = path.join(BLUN_HOME, "data");
8
- const JOBS_FILE = path.join(DATA_DIR, "jobs.json");
9
-
10
- function ensureDataDir() {
11
- if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
12
- }
13
-
14
- function readJobs() {
15
- ensureDataDir();
16
- if (!fs.existsSync(JOBS_FILE)) return [];
17
- try {
18
- return JSON.parse(fs.readFileSync(JOBS_FILE, "utf8"));
19
- } catch {
20
- return [];
21
- }
22
- }
23
-
24
- function writeJobs(jobs) {
25
- ensureDataDir();
26
- fs.writeFileSync(JOBS_FILE, JSON.stringify(jobs, null, 2), "utf8");
27
- }
28
-
29
- function makeId() {
30
- return `job_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
31
- }
32
-
33
- function createJob({ endpoint, sessionId, payload, mode = "agent", resumedFrom = null }) {
34
- const jobs = readJobs();
35
- const job = {
36
- id: makeId(),
37
- endpoint,
38
- mode,
39
- sessionId: String(sessionId || "default"),
40
- payload: payload || {},
41
- status: "queued",
42
- resumedFrom,
43
- createdAt: Date.now(),
44
- updatedAt: Date.now(),
45
- startedAt: null,
46
- finishedAt: null,
47
- error: null,
48
- result: null,
49
- logs: []
50
- };
51
- jobs.unshift(job);
52
- writeJobs(jobs.slice(0, 200));
53
- return job;
54
- }
55
-
56
- function summarizeJob(job) {
57
- const result = job?.result || {};
58
- return {
59
- id: job?.id || "",
60
- status: job?.status || "unknown",
61
- endpoint: job?.endpoint || "",
62
- taskType: result.task_type || "",
63
- durationMs: job?.startedAt && job?.finishedAt ? Math.max(0, job.finishedAt - job.startedAt) : null,
64
- fileCount: Array.isArray(result.files) ? result.files.length : 0,
65
- referenceCount: Array.isArray(result.references) ? result.references.length : 0,
66
- artifact: result.artifact?.filename || null,
67
- error: job?.error || null
68
- };
69
- }
70
-
71
- function updateJob(jobId, patch = {}) {
72
- const jobs = readJobs();
73
- const index = jobs.findIndex((job) => job.id === jobId);
74
- if (index === -1) return null;
75
- jobs[index] = {
76
- ...jobs[index],
77
- ...patch,
78
- updatedAt: Date.now()
79
- };
80
- writeJobs(jobs);
81
- return jobs[index];
82
- }
83
-
84
- function getJob(jobId) {
85
- return readJobs().find((job) => job.id === jobId) || null;
86
- }
87
-
88
- function listJobs({ sessionId, limit = 20 } = {}) {
89
- return readJobs()
90
- .filter((job) => !sessionId || job.sessionId === sessionId)
91
- .slice(0, limit);
92
- }
93
-
94
- function cloneJobForResume(jobId) {
95
- const original = getJob(jobId);
96
- if (!original) return null;
97
- return createJob({
98
- endpoint: original.endpoint,
99
- sessionId: original.sessionId,
100
- payload: {
101
- ...original.payload,
102
- resume_context: {
103
- resumedFrom: original.id,
104
- previousStatus: original.status,
105
- previousError: original.error || "",
106
- previousAnswer: String(original.result?.answer || "").slice(0, 500),
107
- previousLogs: getJobLogs(original.id).slice(-10)
108
- }
109
- },
110
- mode: original.mode,
111
- resumedFrom: original.id
112
- });
113
- }
114
-
115
- function appendJobLog(jobId, event, payload = {}) {
116
- const jobs = readJobs();
117
- const index = jobs.findIndex((job) => job.id === jobId);
118
- if (index === -1) return null;
119
-
120
- jobs[index].logs = Array.isArray(jobs[index].logs) ? jobs[index].logs : [];
121
- jobs[index].logs.push({
122
- ts: Date.now(),
123
- event,
124
- payload
125
- });
126
- jobs[index].logs = jobs[index].logs.slice(-100);
127
- jobs[index].updatedAt = Date.now();
128
- writeJobs(jobs);
129
- return jobs[index].logs;
130
- }
131
-
132
- function getJobLogs(jobId) {
133
- return getJob(jobId)?.logs || [];
134
- }
135
-
136
- module.exports = {
137
- JOBS_FILE,
138
- createJob,
139
- summarizeJob,
140
- updateJob,
141
- getJob,
142
- listJobs,
143
- cloneJobForResume,
144
- appendJobLog,
145
- getJobLogs
146
- };
package/local-data.js DELETED
@@ -1,71 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const os = require("os");
4
- const crypto = require("crypto");
5
-
6
- const BLUN_HOME = process.env.BLUN_HOME || path.join(os.homedir(), ".blun");
7
- const DATA_DIR = path.join(BLUN_HOME, "data");
8
- const FACTS_FILE = path.join(DATA_DIR, "facts.json");
9
- const REFERENCES_DIR = path.join(DATA_DIR, "references");
10
-
11
- if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
12
- if (!fs.existsSync(REFERENCES_DIR)) fs.mkdirSync(REFERENCES_DIR, { recursive: true });
13
-
14
- function readFacts() {
15
- if (!fs.existsSync(FACTS_FILE)) return [];
16
- try {
17
- return JSON.parse(fs.readFileSync(FACTS_FILE, "utf8"));
18
- } catch {
19
- return [];
20
- }
21
- }
22
-
23
- function writeFacts(facts) {
24
- fs.writeFileSync(FACTS_FILE, JSON.stringify(facts, null, 2), "utf8");
25
- }
26
-
27
- function storeFact({ scope = "global", key, value, tags = [] }) {
28
- const facts = readFacts();
29
- const record = {
30
- id: crypto.randomBytes(8).toString("hex"),
31
- scope,
32
- key,
33
- value,
34
- tags,
35
- updatedAt: Date.now()
36
- };
37
- facts.push(record);
38
- writeFacts(facts);
39
- return record;
40
- }
41
-
42
- function findFacts(query = "") {
43
- const needle = String(query || "").toLowerCase();
44
- return readFacts().filter((fact) => {
45
- const haystack = JSON.stringify(fact).toLowerCase();
46
- return !needle || haystack.includes(needle);
47
- });
48
- }
49
-
50
- function storeReference(url, payload) {
51
- const id = crypto.randomBytes(8).toString("hex");
52
- const safeHost = String(url).replace(/[^a-z0-9]+/gi, "_").slice(0, 80);
53
- const filePath = path.join(REFERENCES_DIR, `${Date.now()}_${safeHost}_${id}.json`);
54
- const record = {
55
- id,
56
- url,
57
- storedAt: Date.now(),
58
- ...payload
59
- };
60
- fs.writeFileSync(filePath, JSON.stringify(record, null, 2), "utf8");
61
- return { ...record, filePath };
62
- }
63
-
64
- module.exports = {
65
- DATA_DIR,
66
- REFERENCES_DIR,
67
- readFacts,
68
- storeFact,
69
- findFacts,
70
- storeReference
71
- };
@@ -1,28 +0,0 @@
1
- function sanitizeHistory(history, limit = 12) {
2
- if (!Array.isArray(history)) return [];
3
-
4
- return history
5
- .filter((item) => item && item.role && typeof item.content === "string")
6
- .slice(-limit)
7
- .map((item) => ({
8
- role: item.role,
9
- content: String(item.content)
10
- }));
11
- }
12
-
13
- function buildConversationMessages({ prompt, session, history, message }) {
14
- const messages = [{ role: "system", content: String(prompt || "") }];
15
- const sourceHistory = Array.isArray(history) && history.length ? history : session?.history;
16
-
17
- for (const item of sanitizeHistory(sourceHistory)) {
18
- messages.push(item);
19
- }
20
-
21
- messages.push({ role: "user", content: String(message || "") });
22
- return messages;
23
- }
24
-
25
- module.exports = {
26
- sanitizeHistory,
27
- buildConversationMessages
28
- };
package/noisy-evals.js DELETED
@@ -1,38 +0,0 @@
1
- const runtime = require("./runtime");
2
-
3
- const CORPUS = [
4
- { input: "instaliier dir den telegarm bot", expected: "installation" },
5
- { input: "insalieren telegramm bot", expected: "installation" },
6
- { input: "kanst du playwrigth instalieren", expected: "installation" },
7
- { input: "mach ein screenshoot von der websiete https://example.com", expected: "browser_capture" },
8
- { input: "brauch ein screnshot von der website https://example.com", expected: "browser_capture" },
9
- { input: "baue mir eine websiete fuer mein studio", expected: "website_builder" },
10
- { input: "mach landingpgae fuer nicrazy style", expected: "website_builder" }
11
- ];
12
-
13
- function evaluateNoisyIntentCorpus() {
14
- const results = CORPUS.map((entry) => {
15
- const actual = runtime.classifyTask(entry.input, null).type;
16
- return {
17
- input: entry.input,
18
- expected: entry.expected,
19
- actual,
20
- passed: actual === entry.expected
21
- };
22
- });
23
-
24
- const passed = results.filter((entry) => entry.passed).length;
25
- const total = results.length;
26
- return {
27
- total,
28
- passed,
29
- failed: total - passed,
30
- score: total ? Number((passed / total).toFixed(4)) : 0,
31
- results
32
- };
33
- }
34
-
35
- module.exports = {
36
- CORPUS,
37
- evaluateNoisyIntentCorpus
38
- };
package/palace-memory.js DELETED
@@ -1,246 +0,0 @@
1
- const fs = require("fs");
2
- const os = require("os");
3
- const path = require("path");
4
- const { execFileSync } = require("child_process");
5
-
6
- const BLUN_HOME = process.env.BLUN_HOME || path.join(os.homedir(), ".blun");
7
- const PALACE_BASE = path.join(BLUN_HOME, "palaces");
8
- const GLOBAL_PALACE = path.join(PALACE_BASE, "_global");
9
-
10
- let mempalaceSupport;
11
-
12
- ensureDir(PALACE_BASE);
13
- ensurePalaceSkeleton(GLOBAL_PALACE);
14
-
15
- function ensureDir(dir) {
16
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
17
- }
18
-
19
- function ensurePalaceSkeleton(dir) {
20
- ensureDir(dir);
21
- ensureDir(path.join(dir, "memory"));
22
- }
23
-
24
- function safeId(value) {
25
- return String(value || "global").replace(/[^a-zA-Z0-9._-]/g, "_");
26
- }
27
-
28
- function getUserPalace(userId) {
29
- const dir = userId ? path.join(PALACE_BASE, safeId(userId)) : GLOBAL_PALACE;
30
- ensurePalaceSkeleton(dir);
31
- maybeInitMempalace(dir);
32
- return dir;
33
- }
34
-
35
- function getPythonBinary() {
36
- if (process.env.BLUN_PYTHON) return process.env.BLUN_PYTHON;
37
- return process.platform === "win32" ? "python" : "python3";
38
- }
39
-
40
- function detectMempalace() {
41
- if (mempalaceSupport !== undefined) return mempalaceSupport;
42
-
43
- try {
44
- execFileSync(getPythonBinary(), ["-m", "mempalace", "--help"], {
45
- encoding: "utf8",
46
- stdio: ["ignore", "pipe", "pipe"],
47
- timeout: 4000
48
- });
49
- mempalaceSupport = true;
50
- } catch {
51
- mempalaceSupport = false;
52
- }
53
-
54
- return mempalaceSupport;
55
- }
56
-
57
- function maybeInitMempalace(dir) {
58
- if (!detectMempalace()) return false;
59
-
60
- const marker = path.join(dir, ".mempalace-init");
61
- if (fs.existsSync(marker)) return true;
62
-
63
- try {
64
- execFileSync(getPythonBinary(), ["-m", "mempalace", "init", "--yes", "."], {
65
- cwd: dir,
66
- encoding: "utf8",
67
- stdio: ["ignore", "pipe", "pipe"],
68
- timeout: 15000
69
- });
70
- fs.writeFileSync(marker, "ok", "utf8");
71
- return true;
72
- } catch {
73
- return false;
74
- }
75
- }
76
-
77
- function runMempalace(dir, args, timeout = 10000) {
78
- return execFileSync(getPythonBinary(), ["-m", "mempalace", ...args], {
79
- cwd: dir,
80
- encoding: "utf8",
81
- stdio: ["ignore", "pipe", "pipe"],
82
- timeout
83
- });
84
- }
85
-
86
- function memoryDir(dir) {
87
- const target = path.join(dir, "memory");
88
- ensureDir(target);
89
- return target;
90
- }
91
-
92
- function listMemoryFiles(dir) {
93
- const target = memoryDir(dir);
94
- return fs.readdirSync(target)
95
- .filter((entry) => entry.endsWith(".md"))
96
- .map((entry) => path.join(target, entry));
97
- }
98
-
99
- function scoreText(text, query) {
100
- const haystack = String(text || "").toLowerCase();
101
- const needles = String(query || "").toLowerCase().split(/\s+/).filter(Boolean);
102
- if (needles.length === 0) return haystack.length ? 1 : 0;
103
- return needles.reduce((score, needle) => score + (haystack.includes(needle) ? 1 : 0), 0);
104
- }
105
-
106
- function fallbackSearch(query, limit, userId) {
107
- const dir = getUserPalace(userId);
108
- const scored = listMemoryFiles(dir)
109
- .map((file) => {
110
- const content = fs.readFileSync(file, "utf8");
111
- return {
112
- file,
113
- content,
114
- score: scoreText(content, query)
115
- };
116
- })
117
- .filter((entry) => entry.score > 0)
118
- .sort((a, b) => b.score - a.score || b.file.localeCompare(a.file))
119
- .slice(0, limit);
120
-
121
- if (scored.length === 0) return "Keine Ergebnisse.";
122
-
123
- return scored.map((entry) => {
124
- const snippet = entry.content.replace(/\s+/g, " ").trim().slice(0, 300);
125
- return `${path.basename(entry.file)}\n${snippet}`;
126
- }).join("\n\n");
127
- }
128
-
129
- function fallbackWakeUp(userId) {
130
- const dir = getUserPalace(userId);
131
- const entries = listMemoryFiles(dir)
132
- .map((file) => ({ file, stat: fs.statSync(file) }))
133
- .sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs)
134
- .slice(0, 5)
135
- .map(({ file }) => {
136
- const content = fs.readFileSync(file, "utf8").replace(/\s+/g, " ").trim();
137
- return `- ${path.basename(file)}: ${content.slice(0, 220)}`;
138
- });
139
-
140
- return entries.length ? entries.join("\n") : "";
141
- }
142
-
143
- function fallbackStats(userId) {
144
- const dir = getUserPalace(userId);
145
- const files = listMemoryFiles(dir);
146
- return `Palace local fallback active. Files: ${files.length}`;
147
- }
148
-
149
- function appendEntry(dir, filename, text) {
150
- const file = path.join(memoryDir(dir), filename);
151
- fs.appendFileSync(file, text, "utf8");
152
- return file;
153
- }
154
-
155
- function palaceSearch(query, limit = 5, userId) {
156
- const dir = getUserPalace(userId);
157
-
158
- if (detectMempalace()) {
159
- try {
160
- const out = runMempalace(dir, ["search", String(query || ""), "--results", String(limit)], 10000);
161
- return out.trim() || "Keine Ergebnisse.";
162
- } catch {
163
- return fallbackSearch(query, limit, userId);
164
- }
165
- }
166
-
167
- return fallbackSearch(query, limit, userId);
168
- }
169
-
170
- function palaceStore(text, source = "misc", userId) {
171
- const dir = getUserPalace(userId);
172
- const stamp = new Date();
173
- const filename = `${stamp.toISOString().slice(0, 10)}_${safeId(source).slice(0, 30) || "misc"}.md`;
174
- const entry = `\n## ${stamp.toISOString()}\n${String(text || "").trim()}\n`;
175
-
176
- try {
177
- appendEntry(dir, filename, entry);
178
-
179
- if (detectMempalace()) {
180
- try {
181
- runMempalace(dir, ["mine", "."], 30000);
182
- } catch {
183
- // Local markdown store already succeeded.
184
- }
185
- }
186
-
187
- return true;
188
- } catch {
189
- return false;
190
- }
191
- }
192
-
193
- function palaceWakeUp(userId) {
194
- const dir = getUserPalace(userId);
195
-
196
- if (detectMempalace()) {
197
- try {
198
- const out = runMempalace(dir, ["wake-up"], 10000);
199
- return out.trim();
200
- } catch {
201
- return fallbackWakeUp(userId);
202
- }
203
- }
204
-
205
- return fallbackWakeUp(userId);
206
- }
207
-
208
- function palaceLearn(knowledge, source = "learn", userId) {
209
- return palaceStore(knowledge, `learn_${source}`, userId);
210
- }
211
-
212
- function palaceLogChat(userId, role, content) {
213
- const dir = getUserPalace(userId);
214
- const stamp = new Date();
215
- const filename = `chat_${stamp.toISOString().slice(0, 10)}.md`;
216
- const snippet = String(content || "").replace(/\s+/g, " ").trim().slice(0, 1200);
217
- appendEntry(dir, filename, `\n**${stamp.toISOString().slice(11, 16)} ${role}:** ${snippet}\n`);
218
- return true;
219
- }
220
-
221
- function palaceStats(userId) {
222
- const dir = getUserPalace(userId);
223
-
224
- if (detectMempalace()) {
225
- try {
226
- const out = runMempalace(dir, ["status"], 10000);
227
- return out.trim();
228
- } catch {
229
- return fallbackStats(userId);
230
- }
231
- }
232
-
233
- return fallbackStats(userId);
234
- }
235
-
236
- module.exports = {
237
- PALACE_BASE,
238
- GLOBAL_PALACE,
239
- getUserPalace,
240
- palaceSearch,
241
- palaceStore,
242
- palaceWakeUp,
243
- palaceLearn,
244
- palaceLogChat,
245
- palaceStats
246
- };