@delt/claude-alarm 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +161 -38
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +66 -17
- package/dist/hub/server.js +127 -17
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +127 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dashboard/index.html +66 -17
|
@@ -617,12 +617,35 @@
|
|
|
617
617
|
border-radius: 5px;
|
|
618
618
|
padding: 6px 10px;
|
|
619
619
|
margin-top: 5px;
|
|
620
|
-
max-height:
|
|
621
|
-
overflow
|
|
620
|
+
max-height: 220px;
|
|
621
|
+
overflow: auto;
|
|
622
622
|
white-space: pre-wrap;
|
|
623
623
|
word-break: break-all;
|
|
624
624
|
line-height: 1.4;
|
|
625
625
|
}
|
|
626
|
+
.perm-preview pre {
|
|
627
|
+
margin: 0;
|
|
628
|
+
white-space: pre;
|
|
629
|
+
font-family: inherit;
|
|
630
|
+
font-size: inherit;
|
|
631
|
+
}
|
|
632
|
+
.perm-preview code { font-family: inherit; }
|
|
633
|
+
.perm-preview.prose {
|
|
634
|
+
font-family: inherit;
|
|
635
|
+
white-space: normal;
|
|
636
|
+
word-break: normal;
|
|
637
|
+
}
|
|
638
|
+
.perm-preview.prose pre,
|
|
639
|
+
.perm-preview.prose code { font-family: monospace; }
|
|
640
|
+
.perm-preview.prose ul { margin: 4px 0; padding-left: 20px; }
|
|
641
|
+
.perm-preview.prose li { margin: 2px 0; }
|
|
642
|
+
.perm-preview.prose strong { font-weight: 600; }
|
|
643
|
+
.perm-preview.prose h2,
|
|
644
|
+
.perm-preview.prose h3,
|
|
645
|
+
.perm-preview.prose h4 { font-size: 12px; font-weight: 600; margin: 4px 0; }
|
|
646
|
+
.perm-preview.prose table { border-collapse: collapse; margin: 4px 0; font-size: 11px; }
|
|
647
|
+
.perm-preview.prose th,
|
|
648
|
+
.perm-preview.prose td { border: 1px solid var(--border); padding: 3px 6px; text-align: left; }
|
|
626
649
|
.perm-actions {
|
|
627
650
|
display: flex;
|
|
628
651
|
gap: 10px;
|
|
@@ -1670,9 +1693,26 @@
|
|
|
1670
1693
|
return toolName;
|
|
1671
1694
|
}
|
|
1672
1695
|
|
|
1673
|
-
|
|
1696
|
+
const LANG_BY_EXT = {
|
|
1697
|
+
js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'javascript',
|
|
1698
|
+
ts: 'typescript', tsx: 'typescript', py: 'python', rb: 'ruby', go: 'go',
|
|
1699
|
+
java: 'java', kt: 'kotlin', rs: 'rust', php: 'php', cs: 'csharp',
|
|
1700
|
+
sh: 'bash', bash: 'bash', zsh: 'bash', ps1: 'powershell',
|
|
1701
|
+
json: 'json', yml: 'yaml', yaml: 'yaml', xml: 'xml', sql: 'sql',
|
|
1702
|
+
md: 'markdown', html: 'html', vue: 'html', css: 'css', scss: 'scss',
|
|
1703
|
+
};
|
|
1704
|
+
function langOf(path) {
|
|
1705
|
+
const m = /\.([a-z0-9]+)$/i.exec(path || '');
|
|
1706
|
+
return m ? (LANG_BY_EXT[m[1].toLowerCase()] || '') : '';
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Returns { text, kind, lang } — dashboard shows full content, no truncation.
|
|
1710
|
+
// kind 'code' must stay byte-exact: markdown rendering would swallow the ** and `
|
|
1711
|
+
// characters that the approver needs to see to judge what actually gets executed.
|
|
1712
|
+
// Only notify-style messages (prose Claude wrote) are safe to render as markdown.
|
|
1674
1713
|
function formatPermPreview(toolName, raw) {
|
|
1675
|
-
if (!raw) return { text: '' };
|
|
1714
|
+
if (!raw) return { text: '', kind: 'code', lang: '' };
|
|
1715
|
+
const isNotify = toolName === 'notify' || toolName.endsWith('__notify');
|
|
1676
1716
|
let p = null;
|
|
1677
1717
|
try { p = JSON.parse(raw); } catch {
|
|
1678
1718
|
// Truncated JSON - extract known fields with regex (closing quote optional for truncated strings)
|
|
@@ -1682,43 +1722,52 @@
|
|
|
1682
1722
|
const contentMatch = raw.match(/"content"\s*:\s*"((?:[^"\\]|\\.)*)"?/);
|
|
1683
1723
|
const titleMatch = raw.match(/"title"\s*:\s*"((?:[^"\\]|\\.)*)"?/);
|
|
1684
1724
|
const msgMatch = raw.match(/"message"\s*:\s*"((?:[^"\\]|\\.)*)"?/);
|
|
1685
|
-
if (titleMatch && msgMatch) return { text: msgMatch[1] };
|
|
1686
|
-
if (cmdMatch) return { text: '$ ' + cmdMatch[1] };
|
|
1687
|
-
if (fileMatch) return { text: fileMatch[1] };
|
|
1688
|
-
if (patternMatch) return { text: patternMatch[1] };
|
|
1689
|
-
if (contentMatch) return { text: contentMatch[1] };
|
|
1690
|
-
return { text: raw };
|
|
1725
|
+
if (titleMatch && msgMatch) return { text: msgMatch[1], kind: 'prose', lang: '' };
|
|
1726
|
+
if (cmdMatch) return { text: '$ ' + cmdMatch[1], kind: 'code', lang: 'bash' };
|
|
1727
|
+
if (fileMatch) return { text: fileMatch[1], kind: 'code', lang: langOf(fileMatch[1]) };
|
|
1728
|
+
if (patternMatch) return { text: patternMatch[1], kind: 'code', lang: '' };
|
|
1729
|
+
if (contentMatch) return { text: contentMatch[1], kind: 'code', lang: '' };
|
|
1730
|
+
return { text: raw, kind: 'code', lang: '' };
|
|
1691
1731
|
}
|
|
1692
1732
|
let text;
|
|
1733
|
+
let kind = 'code';
|
|
1734
|
+
let lang = '';
|
|
1693
1735
|
switch (toolName) {
|
|
1694
1736
|
case 'Bash':
|
|
1695
|
-
text = p.command ? '$ ' + p.command : raw; break;
|
|
1737
|
+
text = p.command ? '$ ' + p.command : raw; lang = 'bash'; break;
|
|
1696
1738
|
case 'Write':
|
|
1697
1739
|
case 'Edit':
|
|
1698
1740
|
if (p.file_path) {
|
|
1699
1741
|
text = p.file_path;
|
|
1742
|
+
lang = langOf(p.file_path);
|
|
1700
1743
|
if (p.new_string) text += '\n+ ' + p.new_string;
|
|
1701
1744
|
else if (p.content) text += '\n' + p.content;
|
|
1702
1745
|
} else { text = raw; }
|
|
1703
1746
|
break;
|
|
1704
1747
|
case 'Read':
|
|
1705
|
-
text = p.file_path || raw; break;
|
|
1748
|
+
text = p.file_path || raw; lang = langOf(p.file_path); break;
|
|
1706
1749
|
case 'Glob':
|
|
1707
1750
|
text = p.pattern || raw; break;
|
|
1708
1751
|
case 'Grep':
|
|
1709
1752
|
text = (p.pattern || '') + (p.path ? ' in ' + p.path : ''); break;
|
|
1710
1753
|
default:
|
|
1711
1754
|
if (p.title && p.message) {
|
|
1712
|
-
text = p.message;
|
|
1755
|
+
text = p.message; kind = 'prose';
|
|
1713
1756
|
} else if (p.content && typeof p.content === 'string') {
|
|
1714
|
-
text = p.content;
|
|
1757
|
+
text = p.content; kind = isNotify ? 'prose' : 'code';
|
|
1715
1758
|
} else {
|
|
1716
1759
|
const entries = Object.entries(p);
|
|
1717
1760
|
if (entries.length <= 3) text = entries.map(([k, v]) => k + ': ' + String(v)).join('\n');
|
|
1718
1761
|
else text = JSON.stringify(p, null, 1);
|
|
1719
1762
|
}
|
|
1720
1763
|
}
|
|
1721
|
-
return { text: text || '' };
|
|
1764
|
+
return { text: text || '', kind, lang };
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
function renderPermPreview(text, kind, lang) {
|
|
1768
|
+
if (kind === 'prose') return `<div class="perm-preview prose">${renderMarkdown(text)}</div>`;
|
|
1769
|
+
const cls = lang ? ` class="lang-${esc(lang)}"` : '';
|
|
1770
|
+
return `<div class="perm-preview"><pre><code${cls}>${esc(text)}</code></pre></div>`;
|
|
1722
1771
|
}
|
|
1723
1772
|
|
|
1724
1773
|
function sendPermissionVerdict(sessionId, requestId, behavior) {
|
|
@@ -1742,7 +1791,7 @@
|
|
|
1742
1791
|
if (!reqs.length) { bar.classList.remove('active'); bar.innerHTML = ''; return; }
|
|
1743
1792
|
bar.classList.add('active');
|
|
1744
1793
|
bar.innerHTML = reqs.map(r => {
|
|
1745
|
-
const { text: previewText } = formatPermPreview(r.toolName, r.inputPreview);
|
|
1794
|
+
const { text: previewText, kind: previewKind, lang: previewLang } = formatPermPreview(r.toolName, r.inputPreview);
|
|
1746
1795
|
const displayName = formatToolName(r.toolName, r.inputPreview);
|
|
1747
1796
|
return `<div class="perm-item">
|
|
1748
1797
|
<div class="perm-icon">⚠</div>
|
|
@@ -1750,7 +1799,7 @@
|
|
|
1750
1799
|
<div class="perm-header">
|
|
1751
1800
|
<span class="perm-tool">${esc(displayName)}</span>
|
|
1752
1801
|
</div>
|
|
1753
|
-
${previewText ?
|
|
1802
|
+
${previewText ? renderPermPreview(previewText, previewKind, previewLang) : ''}
|
|
1754
1803
|
</div>
|
|
1755
1804
|
<div class="perm-actions">
|
|
1756
1805
|
<button class="perm-allow" data-req-id="${esc(r.requestId)}" data-session-id="${esc(sid)}">Allow <span class="perm-kbd">(Enter)</span></button>
|
package/dist/hub/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/hub/server.ts
|
|
4
4
|
import http from "http";
|
|
5
|
-
import
|
|
5
|
+
import fs4 from "fs";
|
|
6
6
|
import path4 from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
import { WebSocketServer, WebSocket } from "ws";
|
|
@@ -210,7 +210,7 @@ import fs from "fs";
|
|
|
210
210
|
import path2 from "path";
|
|
211
211
|
import { randomUUID } from "crypto";
|
|
212
212
|
var TELEGRAM_API = "https://api.telegram.org/bot";
|
|
213
|
-
var TelegramBot = class {
|
|
213
|
+
var TelegramBot = class _TelegramBot {
|
|
214
214
|
config;
|
|
215
215
|
offset = 0;
|
|
216
216
|
polling = false;
|
|
@@ -442,33 +442,45 @@ ${this.mdToHtml(message)}`;
|
|
|
442
442
|
}
|
|
443
443
|
/** Send a permission request with inline buttons */
|
|
444
444
|
async sendPermissionRequest(sessionId, sessionLabel, requestId, toolName, description, inputPreview) {
|
|
445
|
+
const isNotify = toolName.endsWith("__notify") || toolName === "notify";
|
|
445
446
|
let preview = inputPreview;
|
|
447
|
+
let kind = "code";
|
|
448
|
+
let lang = "";
|
|
446
449
|
let truncated = false;
|
|
447
450
|
try {
|
|
448
451
|
const p = JSON.parse(inputPreview);
|
|
449
|
-
if (p.command)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
+
if (p.command) {
|
|
453
|
+
preview = `$ ${p.command}`;
|
|
454
|
+
lang = "bash";
|
|
455
|
+
} else if (p.title && p.message) {
|
|
456
|
+
preview = p.message;
|
|
457
|
+
kind = "prose";
|
|
458
|
+
} else if (p.file_path) {
|
|
452
459
|
preview = p.file_path;
|
|
460
|
+
lang = this.langOf(p.file_path);
|
|
453
461
|
if (p.content) {
|
|
454
462
|
preview += "\n" + p.content.slice(0, 3e3);
|
|
455
463
|
if (p.content.length > 500) truncated = true;
|
|
456
464
|
}
|
|
457
465
|
} else if (p.content && typeof p.content === "string") {
|
|
458
466
|
preview = p.content.slice(0, 3e3);
|
|
467
|
+
if (isNotify) kind = "prose";
|
|
459
468
|
if (p.content.length > 500) truncated = true;
|
|
460
469
|
}
|
|
461
470
|
} catch {
|
|
462
471
|
truncated = true;
|
|
463
472
|
const cmdMatch = inputPreview.match(/"command"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
464
473
|
const contentMatch = inputPreview.match(/"content"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
465
|
-
if (cmdMatch)
|
|
466
|
-
|
|
474
|
+
if (cmdMatch) {
|
|
475
|
+
preview = `$ ${cmdMatch[1]}`;
|
|
476
|
+
lang = "bash";
|
|
477
|
+
} else if (contentMatch) preview = contentMatch[1].slice(0, 3e3);
|
|
467
478
|
}
|
|
468
479
|
const truncNote = truncated ? "\n\n<i>...truncated</i>" : "";
|
|
469
480
|
const previewSlice = preview.slice(0, 3e3);
|
|
470
481
|
const isShort = !previewSlice.includes("\n") && previewSlice.length < 100;
|
|
471
|
-
const
|
|
482
|
+
const langAttr = lang ? ` class="language-${lang}"` : "";
|
|
483
|
+
const previewHtml = kind === "prose" ? this.mdToHtml(previewSlice) : isShort ? `<code>${this.escHtml(previewSlice)}</code>` : `<pre><code${langAttr}>${this.escHtml(previewSlice)}</code></pre>`;
|
|
472
484
|
let displayTool = toolName;
|
|
473
485
|
if ((toolName.endsWith("__notify") || toolName === "notify") && inputPreview) {
|
|
474
486
|
try {
|
|
@@ -568,6 +580,40 @@ ${label}`);
|
|
|
568
580
|
escHtml(s) {
|
|
569
581
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
570
582
|
}
|
|
583
|
+
static LANG_BY_EXT = {
|
|
584
|
+
js: "javascript",
|
|
585
|
+
mjs: "javascript",
|
|
586
|
+
cjs: "javascript",
|
|
587
|
+
jsx: "javascript",
|
|
588
|
+
ts: "typescript",
|
|
589
|
+
tsx: "typescript",
|
|
590
|
+
py: "python",
|
|
591
|
+
rb: "ruby",
|
|
592
|
+
go: "go",
|
|
593
|
+
java: "java",
|
|
594
|
+
kt: "kotlin",
|
|
595
|
+
rs: "rust",
|
|
596
|
+
php: "php",
|
|
597
|
+
cs: "csharp",
|
|
598
|
+
sh: "bash",
|
|
599
|
+
bash: "bash",
|
|
600
|
+
zsh: "bash",
|
|
601
|
+
ps1: "powershell",
|
|
602
|
+
json: "json",
|
|
603
|
+
yml: "yaml",
|
|
604
|
+
yaml: "yaml",
|
|
605
|
+
xml: "xml",
|
|
606
|
+
sql: "sql",
|
|
607
|
+
md: "markdown",
|
|
608
|
+
html: "html",
|
|
609
|
+
vue: "html",
|
|
610
|
+
css: "css",
|
|
611
|
+
scss: "scss"
|
|
612
|
+
};
|
|
613
|
+
langOf(path5) {
|
|
614
|
+
const m = /\.([a-z0-9]+)$/i.exec(path5 || "");
|
|
615
|
+
return m ? _TelegramBot.LANG_BY_EXT[m[1].toLowerCase()] ?? "" : "";
|
|
616
|
+
}
|
|
571
617
|
/** Convert markdown to Telegram HTML (escape first, then apply formatting) */
|
|
572
618
|
mdToHtml(s) {
|
|
573
619
|
let text = s.replace(
|
|
@@ -653,6 +699,54 @@ function saveConfig(config) {
|
|
|
653
699
|
fs2.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
654
700
|
}
|
|
655
701
|
|
|
702
|
+
// src/shared/crash-guard.ts
|
|
703
|
+
import fs3 from "fs";
|
|
704
|
+
var BURST_WINDOW_MS = 6e4;
|
|
705
|
+
var BURST_LIMIT = 10;
|
|
706
|
+
function appendToLog(text) {
|
|
707
|
+
try {
|
|
708
|
+
fs3.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
709
|
+
fs3.appendFileSync(LOG_FILE, text, "utf-8");
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function describe(err) {
|
|
714
|
+
if (err instanceof Error) return err.stack ?? `${err.name}: ${err.message}`;
|
|
715
|
+
try {
|
|
716
|
+
return JSON.stringify(err);
|
|
717
|
+
} catch {
|
|
718
|
+
return String(err);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function installCrashGuard(context) {
|
|
722
|
+
let recent = [];
|
|
723
|
+
const handle = (kind, err) => {
|
|
724
|
+
const stack = describe(err);
|
|
725
|
+
appendToLog(`
|
|
726
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} (${context})
|
|
727
|
+
${stack}
|
|
728
|
+
`);
|
|
729
|
+
logger.error(`${kind} caught by crash guard: ${stack}`);
|
|
730
|
+
const now = Date.now();
|
|
731
|
+
recent = recent.filter((t) => now - t < BURST_WINDOW_MS);
|
|
732
|
+
recent.push(now);
|
|
733
|
+
if (recent.length > BURST_LIMIT) {
|
|
734
|
+
const msg = `${recent.length} unhandled errors within ${BURST_WINDOW_MS / 1e3}s \u2014 exiting`;
|
|
735
|
+
appendToLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
736
|
+
`);
|
|
737
|
+
logger.error(msg);
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
process.on("uncaughtException", (err) => handle("uncaughtException", err));
|
|
742
|
+
process.on("unhandledRejection", (reason) => handle("unhandledRejection", reason));
|
|
743
|
+
}
|
|
744
|
+
function logStartup(context) {
|
|
745
|
+
appendToLog(`
|
|
746
|
+
[${(/* @__PURE__ */ new Date()).toISOString()}] ${context} started (pid ${process.pid})
|
|
747
|
+
`);
|
|
748
|
+
}
|
|
749
|
+
|
|
656
750
|
// src/hub/server.ts
|
|
657
751
|
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
658
752
|
var HubServer = class {
|
|
@@ -696,9 +790,12 @@ var HubServer = class {
|
|
|
696
790
|
this.httpServer = http.createServer((req, res) => this.handleHttp(req, res));
|
|
697
791
|
this.wssChannel = new WebSocketServer({ noServer: true });
|
|
698
792
|
this.wssChannel.on("connection", (ws, req) => this.handleChannelConnection(ws, req));
|
|
793
|
+
this.wssChannel.on("error", (err) => logger.warn(`Channel WebSocket server error: ${err.message}`));
|
|
699
794
|
this.wssDashboard = new WebSocketServer({ noServer: true });
|
|
700
795
|
this.wssDashboard.on("connection", (ws) => this.handleDashboardConnection(ws));
|
|
796
|
+
this.wssDashboard.on("error", (err) => logger.warn(`Dashboard WebSocket server error: ${err.message}`));
|
|
701
797
|
this.httpServer.on("upgrade", (req, socket, head) => {
|
|
798
|
+
socket.on("error", (err) => logger.debug(`Upgrade socket error: ${err.message}`));
|
|
702
799
|
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
703
800
|
const pathname = url.pathname;
|
|
704
801
|
if (this.token && !this.isLocalRequest(req)) {
|
|
@@ -725,8 +822,11 @@ var HubServer = class {
|
|
|
725
822
|
this.cleanupUploads();
|
|
726
823
|
this.startHeartbeat();
|
|
727
824
|
return new Promise((resolve, reject) => {
|
|
728
|
-
|
|
825
|
+
const onStartupError = (err) => reject(err);
|
|
826
|
+
this.httpServer.once("error", onStartupError);
|
|
729
827
|
this.httpServer.listen(this.port, this.host, () => {
|
|
828
|
+
this.httpServer.removeListener("error", onStartupError);
|
|
829
|
+
this.httpServer.on("error", (err) => logger.error(`HTTP server error: ${err.message}`));
|
|
730
830
|
const displayHost = this.host === "0.0.0.0" ? "127.0.0.1" : this.host;
|
|
731
831
|
logger.info(`Hub server listening on http://${displayHost}:${this.port}`);
|
|
732
832
|
resolve();
|
|
@@ -847,8 +947,8 @@ var HubServer = class {
|
|
|
847
947
|
];
|
|
848
948
|
logger.debug(`Dashboard candidates: ${JSON.stringify(candidates)}`);
|
|
849
949
|
for (const candidate of candidates) {
|
|
850
|
-
if (
|
|
851
|
-
const html =
|
|
950
|
+
if (fs4.existsSync(candidate)) {
|
|
951
|
+
const html = fs4.readFileSync(candidate, "utf-8");
|
|
852
952
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
853
953
|
res.end(html);
|
|
854
954
|
return;
|
|
@@ -895,6 +995,10 @@ var HubServer = class {
|
|
|
895
995
|
handleChannelConnection(ws, req) {
|
|
896
996
|
const isLocal = this.isLocalRequest(req);
|
|
897
997
|
logger.info(`Channel server connected (local: ${isLocal})`);
|
|
998
|
+
ws.on("error", (err) => {
|
|
999
|
+
logger.warn(`Channel WebSocket error: ${err.message}`);
|
|
1000
|
+
ws.terminate();
|
|
1001
|
+
});
|
|
898
1002
|
ws.on("pong", () => {
|
|
899
1003
|
for (const [sessionId, sock] of this.channelSockets) {
|
|
900
1004
|
if (sock === ws) {
|
|
@@ -1004,6 +1108,10 @@ var HubServer = class {
|
|
|
1004
1108
|
handleDashboardConnection(ws) {
|
|
1005
1109
|
this.dashboardSockets.add(ws);
|
|
1006
1110
|
logger.info(`Dashboard connected (total: ${this.dashboardSockets.size})`);
|
|
1111
|
+
ws.on("error", (err) => {
|
|
1112
|
+
logger.warn(`Dashboard WebSocket error: ${err.message}`);
|
|
1113
|
+
ws.terminate();
|
|
1114
|
+
});
|
|
1007
1115
|
const sessionsMsg = {
|
|
1008
1116
|
type: "sessions_list",
|
|
1009
1117
|
sessions: this.sessions.getAll()
|
|
@@ -1060,11 +1168,11 @@ var HubServer = class {
|
|
|
1060
1168
|
logger.warn("Image upload rejected: exceeds 10MB");
|
|
1061
1169
|
return;
|
|
1062
1170
|
}
|
|
1063
|
-
|
|
1171
|
+
fs4.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
1064
1172
|
const ext = mimeType.split("/")[1] === "jpeg" ? "jpg" : mimeType.split("/")[1];
|
|
1065
1173
|
const filename = `${randomUUID3()}.${ext}`;
|
|
1066
1174
|
const filePath = path4.join(UPLOADS_DIR, filename);
|
|
1067
|
-
|
|
1175
|
+
fs4.writeFileSync(filePath, buffer);
|
|
1068
1176
|
const forwardMsg = {
|
|
1069
1177
|
type: "image_to_session",
|
|
1070
1178
|
sessionId,
|
|
@@ -1077,7 +1185,7 @@ var HubServer = class {
|
|
|
1077
1185
|
logger.info(`Image saved and forwarded: ${filename} (${buffer.length} bytes)`);
|
|
1078
1186
|
setTimeout(() => {
|
|
1079
1187
|
try {
|
|
1080
|
-
|
|
1188
|
+
fs4.unlinkSync(filePath);
|
|
1081
1189
|
} catch {
|
|
1082
1190
|
}
|
|
1083
1191
|
}, 5 * 60 * 1e3);
|
|
@@ -1247,11 +1355,11 @@ var HubServer = class {
|
|
|
1247
1355
|
}
|
|
1248
1356
|
cleanupUploads() {
|
|
1249
1357
|
try {
|
|
1250
|
-
if (!
|
|
1251
|
-
const files =
|
|
1358
|
+
if (!fs4.existsSync(UPLOADS_DIR)) return;
|
|
1359
|
+
const files = fs4.readdirSync(UPLOADS_DIR);
|
|
1252
1360
|
for (const file of files) {
|
|
1253
1361
|
try {
|
|
1254
|
-
|
|
1362
|
+
fs4.unlinkSync(path4.join(UPLOADS_DIR, file));
|
|
1255
1363
|
} catch {
|
|
1256
1364
|
}
|
|
1257
1365
|
}
|
|
@@ -1295,6 +1403,8 @@ var HubServer = class {
|
|
|
1295
1403
|
}
|
|
1296
1404
|
};
|
|
1297
1405
|
if (process.argv[1] && (process.argv[1].endsWith("hub/server.js") || process.argv[1].endsWith("hub/server.ts"))) {
|
|
1406
|
+
installCrashGuard("hub daemon");
|
|
1407
|
+
logStartup("Hub daemon");
|
|
1298
1408
|
const config = loadConfig();
|
|
1299
1409
|
const hub = new HubServer(config);
|
|
1300
1410
|
hub.start().catch((err) => {
|