klaus-ai 0.1.32 → 0.1.34
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/{chunk-CUHPAETD.js → chunk-2BPE3KS3.js} +2 -2
- package/dist/{chunk-PQ7LQQV5.js → chunk-4FM5XPLQ.js} +13 -1
- package/dist/chunk-4FM5XPLQ.js.map +1 -0
- package/dist/{chunk-4MHB4Z26.js → chunk-72GNH56Q.js} +3 -2
- package/dist/chunk-72GNH56Q.js.map +1 -0
- package/dist/{chunk-EM5GHEMW.js → chunk-PQJGV3GK.js} +121 -33
- package/dist/chunk-PQJGV3GK.js.map +1 -0
- package/dist/{chunk-WW6GPUVK.js → chunk-ZMURWSKG.js} +2 -2
- package/dist/{config-DXLYRR6L.js → config-SXG7ZHAX.js} +2 -2
- package/dist/{cron-CT4VRRGB.js → cron-EIFBWAAN.js} +2 -2
- package/dist/{daemon-AUBT756J.js → daemon-B2ASRUGC.js} +2 -2
- package/dist/{doctor-RTJQXP6S.js → doctor-Q5H572VG.js} +3 -3
- package/dist/index.js +32 -23
- package/dist/index.js.map +1 -1
- package/dist/{invite-store-P7P3MW26.js → invite-store-HEM7UG7W.js} +2 -2
- package/dist/{memory-store-5LSNCLAP.js → memory-store-TAUFN2OU.js} +3 -3
- package/dist/{message-store-47FYEXRH.js → message-store-OIQCBBSU.js} +2 -2
- package/dist/{session-store-67OTZ7SQ.js → session-store-TWYPZ6L4.js} +2 -2
- package/dist/{setup-wizard-Y47UAVK3.js → setup-wizard-U3ZEYWA6.js} +103 -24
- package/dist/setup-wizard-U3ZEYWA6.js.map +1 -0
- package/dist/{user-store-BSSSG2P5.js → user-store-WSM4ZC7J.js} +2 -2
- package/dist/{web-FNQGLMCG.js → web-OPQ6K6MP.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-4MHB4Z26.js.map +0 -1
- package/dist/chunk-EM5GHEMW.js.map +0 -1
- package/dist/chunk-PQ7LQQV5.js.map +0 -1
- package/dist/setup-wizard-Y47UAVK3.js.map +0 -1
- /package/dist/{chunk-CUHPAETD.js.map → chunk-2BPE3KS3.js.map} +0 -0
- /package/dist/{chunk-WW6GPUVK.js.map → chunk-ZMURWSKG.js.map} +0 -0
- /package/dist/{config-DXLYRR6L.js.map → config-SXG7ZHAX.js.map} +0 -0
- /package/dist/{cron-CT4VRRGB.js.map → cron-EIFBWAAN.js.map} +0 -0
- /package/dist/{daemon-AUBT756J.js.map → daemon-B2ASRUGC.js.map} +0 -0
- /package/dist/{doctor-RTJQXP6S.js.map → doctor-Q5H572VG.js.map} +0 -0
- /package/dist/{invite-store-P7P3MW26.js.map → invite-store-HEM7UG7W.js.map} +0 -0
- /package/dist/{memory-store-5LSNCLAP.js.map → memory-store-TAUFN2OU.js.map} +0 -0
- /package/dist/{message-store-47FYEXRH.js.map → message-store-OIQCBBSU.js.map} +0 -0
- /package/dist/{session-store-67OTZ7SQ.js.map → session-store-TWYPZ6L4.js.map} +0 -0
- /package/dist/{user-store-BSSSG2P5.js.map → user-store-WSM4ZC7J.js.map} +0 -0
- /package/dist/{web-FNQGLMCG.js.map → web-OPQ6K6MP.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
CONFIG_DIR
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-72GNH56Q.js";
|
|
5
5
|
|
|
6
6
|
// src/memory-store.ts
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
@@ -178,4 +178,4 @@ export {
|
|
|
178
178
|
buildMemoryFlushPrompt,
|
|
179
179
|
MemoryStore
|
|
180
180
|
};
|
|
181
|
-
//# sourceMappingURL=chunk-
|
|
181
|
+
//# sourceMappingURL=chunk-2BPE3KS3.js.map
|
|
@@ -211,6 +211,18 @@ var TEXTS = {
|
|
|
211
211
|
en: " macOS: brew install frpc\n Other: https://github.com/fatedier/frp/releases",
|
|
212
212
|
zh: " macOS: brew install frpc\n \u5176\u4ED6\u7CFB\u7EDF: https://github.com/fatedier/frp/releases"
|
|
213
213
|
},
|
|
214
|
+
web_tunnel_frp_cf_relay: {
|
|
215
|
+
en: "Enable Cloudflare CDN relay? (reduces latency ~10x for cross-region)",
|
|
216
|
+
zh: "\u542F\u7528 Cloudflare CDN \u52A0\u901F? (\u8DE8\u5730\u533A\u5EF6\u8FDF\u964D\u4F4E\u7EA6 10 \u500D)"
|
|
217
|
+
},
|
|
218
|
+
web_tunnel_frp_cf_relay_domain: {
|
|
219
|
+
en: "CF relay domain (CF-proxied A record \u2192 VPS, e.g. frp.example.com)",
|
|
220
|
+
zh: "CF \u4E2D\u7EE7\u57DF\u540D (CF \u4EE3\u7406\u7684 A \u8BB0\u5F55 \u2192 VPS, \u5982 frp.example.com)"
|
|
221
|
+
},
|
|
222
|
+
web_tunnel_frp_cf_relay_guide: {
|
|
223
|
+
en: "CF CDN relay routes frpc traffic through Cloudflare's backbone network\ninstead of direct TCP, dramatically reducing cross-region latency.\n\nSetup required on Cloudflare:\n1. DNS: Add A record for relay domain \u2192 VPS IP (orange cloud ON)\n2. Origin Rules: Rewrite destination port to frps bind port (e.g. 7000)\n3. SSL/TLS mode: Flexible\n4. Network \u2192 WebSocket: ON",
|
|
224
|
+
zh: "CF CDN \u4E2D\u7EE7\u8BA9 frpc \u6D41\u91CF\u8D70 Cloudflare \u9AA8\u5E72\u7F51\u7EDC,\n\u800C\u975E\u76F4\u8FDE TCP, \u5927\u5E45\u964D\u4F4E\u8DE8\u5730\u533A\u5EF6\u8FDF (\u5982\u4E2D\u56FD\u2192\u7F8E\u56FD: 2000ms\u2192200ms)\u3002\n\n\u9700\u8981\u5728 Cloudflare \u914D\u7F6E:\n1. DNS: \u6DFB\u52A0 A \u8BB0\u5F55, \u4E2D\u7EE7\u57DF\u540D \u2192 VPS IP (\u5F00\u542F\u6A59\u8272\u4E91\u6735\u4EE3\u7406)\n2. Origin Rules: \u76EE\u6807\u7AEF\u53E3\u6539\u5199\u4E3A frps \u7ED1\u5B9A\u7AEF\u53E3 (\u5982 7000)\n3. SSL/TLS \u6A21\u5F0F: \u7075\u6D3B (Flexible)\n4. \u7F51\u7EDC \u2192 WebSocket: \u5F00\u542F"
|
|
225
|
+
},
|
|
214
226
|
// ── Cloudflare Named Tunnel ──
|
|
215
227
|
web_tunnel_named_guide: {
|
|
216
228
|
en: "Cloudflare Named Tunnel setup:\n\n1. Open https://one.dash.cloudflare.com \u2192 Networks \u2192 Tunnels\n2. Create a tunnel (e.g. 'klaus'), copy the connector token\n3. In the tunnel's Routes tab \u2192 Add route \u2192 Published application:\n - Domain: your domain (e.g. chat.example.com)\n - Service Type: HTTP (not HTTPS!)\n - URL: localhost:PORT (e.g. localhost:3000)\n4. Check your Cloudflare DNS panel \u2014 delete any old A records for this domain\n The tunnel will auto-create a CNAME record\n\nThe token looks like: eyJhIjoiNz...",
|
|
@@ -502,4 +514,4 @@ export {
|
|
|
502
514
|
setLang,
|
|
503
515
|
t
|
|
504
516
|
};
|
|
505
|
-
//# sourceMappingURL=chunk-
|
|
517
|
+
//# sourceMappingURL=chunk-4FM5XPLQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/i18n.ts"],"sourcesContent":["type Lang = \"en\" | \"zh\";\n\nconst TEXTS: Record<string, Record<Lang, string>> = {\n // ── Setup ──\n setup_title: {\n en: \" Klaus Setup \",\n zh: \" Klaus 安装引导 \",\n },\n config_exists: {\n en: \"Config already exists at {path}\\nCurrent channel(s): {channel}\",\n zh: \"配置文件已存在: {path}\\n当前通道: {channel}\",\n },\n overwrite: {\n en: \"Overwrite existing config?\",\n zh: \"是否覆盖现有配置?\",\n },\n config_action: {\n en: \"What would you like to do?\",\n zh: \"请选择操作:\",\n },\n config_action_reconfigure: {\n en: \"Reconfigure (edit current config, keep unchanged values)\",\n zh: \"重新配置(编辑当前配置,未修改项保持不变)\",\n },\n config_action_overwrite: {\n en: \"Overwrite (start fresh)\",\n zh: \"覆盖(从头开始配置)\",\n },\n config_action_cancel: {\n en: \"Cancel (keep current config)\",\n zh: \"取消(保留当前配置)\",\n },\n setup_cancelled: {\n en: \"Setup cancelled. Existing config preserved.\",\n zh: \"已取消。保留现有配置。\",\n },\n checking: {\n en: \"Checking prerequisites...\",\n zh: \"检查环境...\",\n },\n node_ok: {\n en: \"Node.js {version}\",\n zh: \"Node.js {version}\",\n },\n node_need: {\n en: \"Node.js >= 18 required\",\n zh: \"需要 Node.js >= 18\",\n },\n cli_ok: {\n en: \"Claude Code CLI found\",\n zh: \"Claude Code CLI 已安装\",\n },\n cli_not_found: {\n en: \"Claude Code CLI not found. Install: npm i -g @anthropic-ai/claude-code\",\n zh: \"未找到 Claude Code CLI。安装: npm i -g @anthropic-ai/claude-code\",\n },\n checks_passed: {\n en: \"All checks passed\",\n zh: \"所有检查通过\",\n },\n checks_failed: {\n en: \"Some checks failed. Please fix them before continuing.\",\n zh: \"部分检查未通过。请先修复以上问题。\",\n },\n choose_channel: {\n en: \"Choose channel(s) (space to select, enter to confirm)\",\n zh: \"选择通道(空格选择,回车确认)\",\n },\n choose_channel_hint: {\n en: \"You can select multiple channels to run simultaneously\",\n zh: \"可多选,同时启动多个通道\",\n },\n // ── Add / Remove Channel ──\n add_channel_title: {\n en: \" Add Channel \",\n zh: \" 添加通道 \",\n },\n add_channel_select: {\n en: \"Select a channel to add:\",\n zh: \"选择要添加的通道:\",\n },\n add_channel_none: {\n en: \"All channels are already configured.\",\n zh: \"所有通道均已配置。\",\n },\n add_channel_success: {\n en: \"Channel {channel} added successfully.\",\n zh: \"通道 {channel} 添加成功。\",\n },\n add_channel_no_config: {\n en: \"No config found. Run `klaus setup` first.\",\n zh: \"未找到配置文件。请先运行 `klaus setup`。\",\n },\n remove_channel_title: {\n en: \" Remove Channel \",\n zh: \" 移除通道 \",\n },\n remove_channel_select: {\n en: \"Select a channel to remove:\",\n zh: \"选择要移除的通道:\",\n },\n remove_channel_confirm: {\n en: \"Remove channel {channel}? Its config will be deleted.\",\n zh: \"移除通道 {channel}?其配置将被删除。\",\n },\n remove_channel_success: {\n en: \"Channel {channel} removed.\",\n zh: \"通道 {channel} 已移除。\",\n },\n remove_channel_last: {\n en: \"Cannot remove the last channel. At least one must remain.\",\n zh: \"无法移除最后一个通道,至少保留一个。\",\n },\n remove_channel_none: {\n en: \"No channels configured.\",\n zh: \"没有已配置的通道。\",\n },\n // ── Web Guide ──\n channel_web: {\n en: \"Web Chat (Browser UI, localhost + optional Cloudflare Tunnel)\",\n zh: \"网页聊天 (浏览器 UI, 本地 + 可选 Cloudflare Tunnel 公网访问)\",\n },\n web_title: {\n en: \"Web Chat Setup\",\n zh: \"网页聊天配置\",\n },\n web_guide: {\n en:\n \"Web Chat provides a browser-based chat interface.\\n\\n\" +\n \"Features:\\n\" +\n \"- Works on localhost by default\\n\" +\n \"- Optional Cloudflare Tunnel for public access (no account needed)\\n\" +\n \"- Token-based authentication\\n\" +\n \"- Share the URL with your token to give others access\\n\\n\" +\n \"After setup, open the URL shown in terminal to start chatting.\",\n zh:\n \"网页聊天提供基于浏览器的聊天界面。\\n\\n\" +\n \"功能特点:\\n\" +\n \"- 默认在本地运行\\n\" +\n \"- 可选 Cloudflare Tunnel 公网访问 (无需账号)\\n\" +\n \"- 基于 Token 认证\\n\" +\n \"- 分享含 Token 的 URL 即可让他人访问\\n\\n\" +\n \"配置完成后,打开终端显示的 URL 即可开始聊天。\",\n },\n web_token: {\n en: \"Access Token (leave empty to auto-generate)\",\n zh: \"访问令牌 (留空自动生成)\",\n },\n web_token_generated: {\n en: \"Token auto-generated: {token}\",\n zh: \"已自动生成令牌: {token}\",\n },\n web_port: {\n en: \"Port (default 3000)\",\n zh: \"端口 (默认 3000)\",\n },\n // ── Tunnel provider selection ──\n web_tunnel_mode: {\n en: \"Choose tunnel mode for public access\",\n zh: \"选择公网访问隧道模式\",\n },\n web_tunnel_none: {\n en: \"None — localhost only\",\n zh: \"不启用 — 仅本地访问\",\n },\n web_tunnel_quick: {\n en: \"Cloudflare Quick Tunnel — random URL, no account needed\",\n zh: \"Cloudflare 快速隧道 — 随机 URL, 无需账号\",\n },\n web_tunnel_named: {\n en: \"Cloudflare Named Tunnel — fixed hostname via dashboard token\",\n zh: \"Cloudflare 命名隧道 — 通过 Dashboard Token 使用固定域名\",\n },\n web_tunnel_ngrok: {\n en: \"ngrok — free static domain available\",\n zh: \"ngrok — 支持免费静态域名\",\n },\n web_tunnel_custom: {\n en: \"Custom — your own URL + optional startup command\",\n zh: \"自定义 — 自行提供公网 URL + 可选启动命令\",\n },\n web_tunnel_frp: {\n en: \"frp — self-hosted reverse proxy (frps on VPS + frpc locally)\",\n zh: \"frp — 自建内网穿透 (VPS 上运行 frps, 本地运行 frpc)\",\n },\n // ── frp tunnel ──\n web_tunnel_frp_guide: {\n en:\n \"frp (Fast Reverse Proxy) setup:\\n\\n\" +\n \"Prerequisites:\\n\" +\n \"- A VPS with public IP running frps (server)\\n\" +\n \"- frpc (client) installed locally\\n\\n\" +\n \"If you have a domain pointed to the VPS, use HTTP mode.\\n\" +\n \"Otherwise, use TCP mode with a remote port.\",\n zh:\n \"frp (Fast Reverse Proxy) 配置:\\n\\n\" +\n \"前置要求:\\n\" +\n \"- 一台有公网 IP 的 VPS, 上面运行 frps (服务端)\\n\" +\n \"- 本地安装 frpc (客户端)\\n\\n\" +\n \"如果域名已指向 VPS, 使用 HTTP 模式。\\n\" +\n \"否则使用 TCP 模式 + 远程端口。\",\n },\n web_tunnel_frp_server_addr: {\n en: \"frps server address (IP or domain)\",\n zh: \"frps 服务器地址 (IP 或域名)\",\n },\n web_tunnel_frp_server_port: {\n en: \"frps server port (default 7000)\",\n zh: \"frps 服务端口 (默认 7000)\",\n },\n web_tunnel_frp_token: {\n en: \"frp authentication token\",\n zh: \"frp 认证 token\",\n },\n web_tunnel_frp_proxy_type: {\n en: \"Proxy type\",\n zh: \"代理类型\",\n },\n web_tunnel_frp_proxy_http: {\n en: \"HTTP — use with a domain pointed to VPS (recommended)\",\n zh: \"HTTP — 配合域名指向 VPS 使用 (推荐)\",\n },\n web_tunnel_frp_proxy_tcp: {\n en: \"TCP — direct port forwarding, no domain needed\",\n zh: \"TCP — 直接端口转发, 无需域名\",\n },\n web_tunnel_frp_custom_domain: {\n en: \"Custom domain (pointed to VPS, e.g. chat.example.com)\",\n zh: \"自定义域名 (已指向 VPS, 如 chat.example.com)\",\n },\n web_tunnel_frp_remote_port: {\n en: \"Remote port on VPS (e.g. 8080)\",\n zh: \"VPS 上的远程端口 (如 8080)\",\n },\n web_frp_install_hint: {\n en:\n \" macOS: brew install frpc\\n\" +\n \" Other: https://github.com/fatedier/frp/releases\",\n zh:\n \" macOS: brew install frpc\\n\" +\n \" 其他系统: https://github.com/fatedier/frp/releases\",\n },\n web_tunnel_frp_cf_relay: {\n en: \"Enable Cloudflare CDN relay? (reduces latency ~10x for cross-region)\",\n zh: \"启用 Cloudflare CDN 加速? (跨地区延迟降低约 10 倍)\",\n },\n web_tunnel_frp_cf_relay_domain: {\n en: \"CF relay domain (CF-proxied A record → VPS, e.g. frp.example.com)\",\n zh: \"CF 中继域名 (CF 代理的 A 记录 → VPS, 如 frp.example.com)\",\n },\n web_tunnel_frp_cf_relay_guide: {\n en:\n \"CF CDN relay routes frpc traffic through Cloudflare's backbone network\\n\" +\n \"instead of direct TCP, dramatically reducing cross-region latency.\\n\\n\" +\n \"Setup required on Cloudflare:\\n\" +\n \"1. DNS: Add A record for relay domain → VPS IP (orange cloud ON)\\n\" +\n \"2. Origin Rules: Rewrite destination port to frps bind port (e.g. 7000)\\n\" +\n \"3. SSL/TLS mode: Flexible\\n\" +\n \"4. Network → WebSocket: ON\",\n zh:\n \"CF CDN 中继让 frpc 流量走 Cloudflare 骨干网络,\\n\" +\n \"而非直连 TCP, 大幅降低跨地区延迟 (如中国→美国: 2000ms→200ms)。\\n\\n\" +\n \"需要在 Cloudflare 配置:\\n\" +\n \"1. DNS: 添加 A 记录, 中继域名 → VPS IP (开启橙色云朵代理)\\n\" +\n \"2. Origin Rules: 目标端口改写为 frps 绑定端口 (如 7000)\\n\" +\n \"3. SSL/TLS 模式: 灵活 (Flexible)\\n\" +\n \"4. 网络 → WebSocket: 开启\",\n },\n // ── Cloudflare Named Tunnel ──\n web_tunnel_named_guide: {\n en:\n \"Cloudflare Named Tunnel setup:\\n\\n\" +\n \"1. Open https://one.dash.cloudflare.com → Networks → Tunnels\\n\" +\n \"2. Create a tunnel (e.g. 'klaus'), copy the connector token\\n\" +\n \"3. In the tunnel's Routes tab → Add route → Published application:\\n\" +\n \" - Domain: your domain (e.g. chat.example.com)\\n\" +\n \" - Service Type: HTTP (not HTTPS!)\\n\" +\n \" - URL: localhost:PORT (e.g. localhost:3000)\\n\" +\n \"4. Check your Cloudflare DNS panel — delete any old A records for this domain\\n\" +\n \" The tunnel will auto-create a CNAME record\\n\\n\" +\n \"The token looks like: eyJhIjoiNz...\",\n zh:\n \"Cloudflare 命名隧道配置:\\n\\n\" +\n \"1. 打开 https://one.dash.cloudflare.com → Networks → Tunnels\\n\" +\n \"2. 创建隧道 (如 'klaus'), 复制 connector token\\n\" +\n \"3. 在隧道的 Routes 标签页 → Add route → Published application:\\n\" +\n \" - Domain: 你的域名 (如 chat.example.com)\\n\" +\n \" - Service Type: 选 HTTP (不是 HTTPS!)\\n\" +\n \" - URL: localhost:端口 (如 localhost:3000)\\n\" +\n \"4. 检查 Cloudflare DNS 面板 — 删除该域名的旧 A 记录\\n\" +\n \" 隧道会自动创建 CNAME 记录\\n\\n\" +\n \"Token 格式如: eyJhIjoiNz...\",\n },\n web_tunnel_cf_token: {\n en: \"Cloudflare Tunnel connector token\",\n zh: \"Cloudflare Tunnel connector token\",\n },\n web_tunnel_cf_hostname: {\n en: \"Public hostname (optional, for display)\",\n zh: \"公网域名 (可选, 用于显示)\",\n },\n web_cf_install_hint: {\n en:\n \" macOS: brew install cloudflared\\n\" +\n \" Other: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/\",\n zh:\n \" macOS: brew install cloudflared\\n\" +\n \" 其他系统: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/\",\n },\n // ── ngrok ──\n web_tunnel_ngrok_guide: {\n en:\n \"ngrok setup:\\n\\n\" +\n \"1. Sign up at https://ngrok.com (free)\\n\" +\n \"2. Get your auth token from https://dashboard.ngrok.com/get-started/your-authtoken\\n\" +\n \"3. Optionally claim a free static domain at Endpoints → Domains\",\n zh:\n \"ngrok 配置:\\n\\n\" +\n \"1. 在 https://ngrok.com 注册 (免费)\\n\" +\n \"2. 从 https://dashboard.ngrok.com/get-started/your-authtoken 获取 auth token\\n\" +\n \"3. 可选: 在 Endpoints → Domains 领取免费静态域名\",\n },\n web_tunnel_ngrok_authtoken: {\n en: \"ngrok auth token\",\n zh: \"ngrok auth token\",\n },\n web_tunnel_ngrok_domain: {\n en: \"Static domain (optional, e.g. my-app.ngrok-free.app)\",\n zh: \"静态域名 (可选, 如 my-app.ngrok-free.app)\",\n },\n web_ngrok_install_hint: {\n en: \" macOS: brew install ngrok\\n Other: https://ngrok.com/download\",\n zh: \" macOS: brew install ngrok\\n 其他系统: https://ngrok.com/download\",\n },\n // ── Custom tunnel ──\n web_tunnel_custom_guide: {\n en:\n \"Custom tunnel setup:\\n\\n\" +\n \"Provide your own public URL (e.g. https://chat.example.com).\\n\" +\n \"Optionally, specify a command to start the tunnel (e.g. frpc, bore, etc.).\\n\" +\n \"Klaus will run this command on startup and kill it on shutdown.\",\n zh:\n \"自定义隧道配置:\\n\\n\" +\n \"提供公网 URL (如 https://chat.example.com).\\n\" +\n \"可选: 提供隧道启动命令 (如 frpc, bore 等).\\n\" +\n \"Klaus 会在启动时运行此命令, 退出时自动关闭.\",\n },\n web_tunnel_custom_url: {\n en: \"Public URL\",\n zh: \"公网 URL\",\n },\n web_tunnel_custom_command: {\n en: \"Startup command (optional)\",\n zh: \"启动命令 (可选)\",\n },\n // ── Binary detection (generic) ──\n web_binary_found: {\n en: \"{cmd} found\",\n zh: \"已找到 {cmd}\",\n },\n web_binary_not_found: {\n en: \"{cmd} not found\",\n zh: \"未找到 {cmd}\",\n },\n web_binary_auto_install: {\n en: \"Run `{cmd}` to install automatically?\",\n zh: \"运行 `{cmd}` 自动安装?\",\n },\n web_binary_installing: {\n en: \"Installing {cmd}...\",\n zh: \"正在安装 {cmd}...\",\n },\n web_binary_install_ok: {\n en: \"{cmd} installed successfully\",\n zh: \"{cmd} 安装成功\",\n },\n web_binary_install_fail: {\n en: \"{cmd} installation failed. Please install manually.\",\n zh: \"{cmd} 安装失败, 请手动安装.\",\n },\n validate_required: {\n en: \"Required\",\n zh: \"必填\",\n },\n validate_invalid_url: {\n en: \"Must be a valid URL\",\n zh: \"必须是有效的 URL\",\n },\n web_setup_done: {\n en: \"Web Chat configured. URL will be shown when you run: klaus start\",\n zh: \"网页聊天已配置。运行 klaus start 后将显示访问 URL。\",\n },\n // ── Persona ──\n persona_title: {\n en: \"Bot Persona\",\n zh: \"机器人人设\",\n },\n persona_method: {\n en: \"How do you want to set the persona?\",\n zh: \"如何设置人设?\",\n },\n persona_from_clipboard: {\n en: \"Paste from clipboard (recommended — copy text first, then select this)\",\n zh: \"从剪贴板粘贴 (推荐 — 先复制内容,再选此项)\",\n },\n persona_clipboard_preview: {\n en: \"Clipboard content preview:\",\n zh: \"剪贴板内容预览:\",\n },\n persona_clipboard_confirm: {\n en: \"Use this as persona?\",\n zh: \"使用这段内容作为人设?\",\n },\n persona_clipboard_empty: {\n en: \"Clipboard is empty. Skipping persona.\",\n zh: \"剪贴板为空,跳过人设设置。\",\n },\n persona_lines: {\n en: \"lines\",\n zh: \"行\",\n },\n persona_from_file: {\n en: \"From file\",\n zh: \"从文件读取\",\n },\n persona_direct: {\n en: \"Type directly (single line only)\",\n zh: \"直接输入 (仅支持单行)\",\n },\n persona_skip_option: {\n en: \"Skip (use default Claude behavior)\",\n zh: \"跳过 (使用默认 Claude 行为)\",\n },\n persona_keep: {\n en: \"Keep current persona (no change)\",\n zh: \"保留当前人设(不修改)\",\n },\n persona_file_prompt: {\n en: \"Path to persona file (text/markdown)\",\n zh: \"人设文件路径 (文本或 Markdown 文件)\",\n },\n persona_file_required: {\n en: \"File path is required\",\n zh: \"请输入文件路径\",\n },\n persona_file_not_found: {\n en: \"File not found\",\n zh: \"文件不存在\",\n },\n persona_prompt: {\n en: \"Enter system prompt (single line)\",\n zh: \"输入 system prompt (单行)\",\n },\n persona_placeholder: {\n en: \"You are a helpful AI assistant...\",\n zh: \"你是一个友好的 AI 助手...\",\n },\n persona_saved: {\n en: \"Persona configured\",\n zh: \"人设已配置\",\n },\n persona_skipped: {\n en: \"Using default Claude behavior\",\n zh: \"使用默认 Claude 行为\",\n },\n // ── Chat Commands ──\n cmd_reset: {\n en: \"Session reset.\",\n zh: \"会话已重置。\",\n },\n cmd_help: {\n en:\n \"Available commands:\\n\" +\n \"/new /reset /clear — Reset conversation\\n\" +\n \"/help — Show this help\\n\" +\n \"/session — Show session info\\n\" +\n \"/model — Show current model\\n\" +\n \"/model <name> — Switch model (sonnet/opus/haiku)\\n\" +\n \"/skills — Show enabled skills\\n\" +\n \"/cron — Scheduled tasks (list/run/add/edit/remove/status)\",\n zh:\n \"可用命令:\\n\" +\n \"/new /reset /clear — 重置对话\\n\" +\n \"/help — 显示帮助\\n\" +\n \"/session — 查看会话信息\\n\" +\n \"/model — 查看当前模型\\n\" +\n \"/model <名称> — 切换模型 (sonnet/opus/haiku)\\n\" +\n \"/skills — 查看已启用的技能\\n\" +\n \"/cron — 定时任务 (列表/触发/添加/编辑/删除/状态)\",\n },\n cmd_session_info: {\n en: \"Session: {key}\\nStatus: {status}\\nModel: {model}\",\n zh: \"会话: {key}\\n状态: {status}\\n模型: {model}\",\n },\n cmd_session_active: {\n en: \"active\",\n zh: \"活跃\",\n },\n cmd_session_idle: {\n en: \"idle\",\n zh: \"空闲\",\n },\n cmd_model_current: {\n en: \"Current model: {model}\",\n zh: \"当前模型: {model}\",\n },\n cmd_model_switched: {\n en: \"Model switched to: {model}\",\n zh: \"模型已切换为: {model}\",\n },\n cmd_model_unknown: {\n en: \"Unknown model: {name}\\nAvailable: sonnet, opus, haiku\",\n zh: \"未知模型: {name}\\n可选: sonnet, opus, haiku\",\n },\n cmd_default_model: {\n en: \"default\",\n zh: \"默认\",\n },\n cmd_skills_list: {\n en: \"Enabled skills ({count}):\\n{list}\\n\\nSkills are auto-gated by binary/env presence.\\nUser overrides: ~/.klaus/skills/<name>/SKILL.md\",\n zh: \"已启用的技能 ({count}):\\n{list}\\n\\n技能会根据二进制/环境变量自动判断可用性。\\n用户自定义: ~/.klaus/skills/<name>/SKILL.md\",\n },\n // ── Cron Commands ──\n cmd_cron_disabled: {\n en: 'Cron is not enabled.\\n\\nTo enable, add to ~/.klaus/config.yaml:\\n\\ncron:\\n enabled: true\\n tasks:\\n - id: daily-summary\\n schedule: \"0 9 * * *\"\\n prompt: \"Summarize recent events\"',\n zh: '定时任务未启用。\\n\\n在 ~/.klaus/config.yaml 中添加:\\n\\ncron:\\n enabled: true\\n tasks:\\n - id: daily-summary\\n schedule: \"0 9 * * *\"\\n prompt: \"总结近期事件\"',\n },\n cmd_cron_empty: {\n en: \"No cron tasks configured.\",\n zh: \"未配置任何定时任务。\",\n },\n cmd_cron_list: {\n en: \"Cron tasks ({count}):\\n{list}\",\n zh: \"定时任务 ({count}):\\n{list}\",\n },\n cmd_cron_help: {\n en:\n \"Cron commands:\\n\" +\n \"/cron — List all tasks\\n\" +\n \"/cron status — Scheduler status\\n\" +\n \"/cron run <id> — Trigger task now\\n\" +\n \"/cron runs <id> — View run history\\n\" +\n \"/cron add <id> <schedule> <prompt> — Add task\\n\" +\n \"/cron edit <id> <field>=<value> — Edit task\\n\" +\n \"/cron remove <id> — Remove task\\n\" +\n \"/cron enable <id> — Enable task\\n\" +\n \"/cron disable <id> — Disable task\",\n zh:\n \"定时任务命令:\\n\" +\n \"/cron — 列出所有任务\\n\" +\n \"/cron status — 调度器状态\\n\" +\n \"/cron run <id> — 立即触发任务\\n\" +\n \"/cron runs <id> — 查看运行历史\\n\" +\n \"/cron add <id> <schedule> <prompt> — 添加任务\\n\" +\n \"/cron edit <id> <字段>=<值> — 编辑任务\\n\" +\n \"/cron remove <id> — 删除任务\\n\" +\n \"/cron enable <id> — 启用任务\\n\" +\n \"/cron disable <id> — 禁用任务\",\n },\n cmd_cron_added: {\n en: 'Task \"{id}\" added.\\nSchedule: {schedule}\\nPrompt: {prompt}',\n zh: '任务 \"{id}\" 已添加。\\n调度: {schedule}\\n提示: {prompt}',\n },\n cmd_cron_edited: {\n en: 'Task \"{id}\" updated.',\n zh: '任务 \"{id}\" 已更新。',\n },\n cmd_cron_removed: {\n en: 'Task \"{id}\" removed.',\n zh: '任务 \"{id}\" 已删除。',\n },\n cmd_cron_not_found: {\n en: 'Task \"{id}\" not found.',\n zh: '未找到任务 \"{id}\"。',\n },\n cmd_cron_triggered: {\n en: 'Task \"{id}\" triggered. Status: {status}',\n zh: '任务 \"{id}\" 已触发。状态: {status}',\n },\n cmd_cron_not_due: {\n en: 'Task \"{id}\" is not due yet.',\n zh: '任务 \"{id}\" 尚未到期。',\n },\n cmd_cron_runs_header: {\n en: \"Run history for {id} (last {count}):\\n{list}\",\n zh: \"{id} 的运行历史 (最近 {count} 条):\\n{list}\",\n },\n cmd_cron_runs_empty: {\n en: 'No runs recorded for \"{id}\".',\n zh: '任务 \"{id}\" 暂无运行记录。',\n },\n cmd_cron_status: {\n en: \"Scheduler: {state}\\nTasks: {total} total, {active} active\\nRunning: {running}\\nNext wake: {next}\",\n zh: \"调度器: {state}\\n任务: 共 {total} 个, {active} 个活跃\\n执行中: {running}\\n下次触发: {next}\",\n },\n cmd_cron_enabled: {\n en: 'Task \"{id}\" enabled.',\n zh: '任务 \"{id}\" 已启用。',\n },\n cmd_cron_disabled_task: {\n en: 'Task \"{id}\" disabled.',\n zh: '任务 \"{id}\" 已禁用。',\n },\n cmd_skills_none: {\n en: \"No skills enabled.\\n\\nAvailable: {available}\\n\\nEnable in ~/.klaus/config.yaml:\\n skills: all\\n # or list specific skills:\\n skills:\\n - video-frames\\n - xurl\\n\\nSkills require their CLI tools installed (auto-gated).\",\n zh: \"未启用任何技能。\\n\\n可用: {available}\\n\\n在 ~/.klaus/config.yaml 中启用:\\n skills: all\\n # 或指定具体技能:\\n skills:\\n - video-frames\\n - xurl\\n\\n技能需要相应 CLI 工具已安装(自动检测)。\",\n },\n // ── Done ──\n config_saved: {\n en: \"Config saved to {path}\",\n zh: \"配置已保存到 {path}\",\n },\n setup_done: {\n en: \"Setup complete! Run: klaus start\",\n zh: \"安装完成! 运行: klaus start\",\n },\n};\n\nlet currentLang: Lang = \"en\";\n\nexport function setLang(lang: Lang): void {\n currentLang = lang;\n}\n\nexport function getLang(): Lang {\n return currentLang;\n}\n\nexport function t(key: string, vars?: Record<string, string>): string {\n let text = TEXTS[key]?.[currentLang] ?? key;\n if (vars) {\n for (const [k, v] of Object.entries(vars)) {\n text = text.replaceAll(`{${k}}`, v);\n }\n }\n return text;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -85,7 +85,8 @@ function parseTunnelConfig(raw, envTunnel) {
|
|
|
85
85
|
...Array.isArray(obj.custom_domains) ? { custom_domains: obj.custom_domains.map(String) } : {},
|
|
86
86
|
...obj.remote_port != null ? { remote_port: Math.floor(Number(obj.remote_port)) } : {},
|
|
87
87
|
...obj.proxy_name ? { proxy_name: String(obj.proxy_name) } : {},
|
|
88
|
-
...obj.tls_enable === true ? { tls_enable: true } : {}
|
|
88
|
+
...obj.tls_enable === true ? { tls_enable: true } : {},
|
|
89
|
+
...obj.transport_protocol === "websocket" ? { transport_protocol: "websocket" } : {}
|
|
89
90
|
};
|
|
90
91
|
default:
|
|
91
92
|
console.warn(
|
|
@@ -296,4 +297,4 @@ export {
|
|
|
296
297
|
parseRelativeTime,
|
|
297
298
|
loadCronConfig
|
|
298
299
|
};
|
|
299
|
-
//# sourceMappingURL=chunk-
|
|
300
|
+
//# sourceMappingURL=chunk-72GNH56Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport yaml from \"js-yaml\";\nimport type {\n WebConfig,\n SessionConfig,\n TranscriptsConfig,\n TunnelConfig,\n GoogleOAuthConfig,\n CronConfig,\n CronTask,\n CronDelivery,\n CronRetryConfig,\n CronFailureAlert,\n CronFailureDestination,\n CronRunLogConfig,\n} from \"./types.js\";\n\nexport const CONFIG_DIR = join(homedir(), \".klaus\");\nexport const CONFIG_FILE = join(CONFIG_DIR, \"config.yaml\");\n\nexport function loadConfig(): Record<string, unknown> {\n if (!existsSync(CONFIG_FILE)) return {};\n const content = readFileSync(CONFIG_FILE, \"utf-8\");\n return (yaml.load(content) as Record<string, unknown>) ?? {};\n}\n\nexport function saveConfig(data: Record<string, unknown>): void {\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(CONFIG_FILE, yaml.dump(data, { flowLevel: -1 }), \"utf-8\");\n}\n\nexport function getChannelNames(): string[] {\n const cfg = loadConfig();\n const raw = cfg.channel;\n if (Array.isArray(raw)) return raw.map(String);\n return [(raw as string) ?? \"web\"];\n}\n\n/**\n * Add a channel to the existing config without overwriting other sections.\n * Handles string→array conversion for the `channel` field.\n */\nexport function addChannelToConfig(\n channelId: string,\n channelCfg: Record<string, unknown>,\n): void {\n const cfg = loadConfig();\n const raw = cfg.channel;\n const existing: string[] = Array.isArray(raw)\n ? raw.map(String)\n : raw\n ? [String(raw)]\n : [];\n\n if (!existing.includes(channelId)) {\n existing.push(channelId);\n }\n cfg.channel = existing.length === 1 ? existing[0] : existing;\n cfg[channelId] = channelCfg;\n saveConfig(cfg);\n}\n\n/**\n * Remove a channel from config. Returns false if it's the last channel.\n */\nexport function removeChannelFromConfig(channelId: string): boolean {\n const cfg = loadConfig();\n const raw = cfg.channel;\n const existing: string[] = Array.isArray(raw)\n ? raw.map(String)\n : raw\n ? [String(raw)]\n : [];\n\n const filtered = existing.filter((c) => c !== channelId);\n if (filtered.length === 0) return false;\n\n cfg.channel = filtered.length === 1 ? filtered[0] : filtered;\n delete cfg[channelId];\n saveConfig(cfg);\n return true;\n}\n\nfunction parseTunnelConfig(\n raw: unknown,\n envTunnel: string | undefined,\n): TunnelConfig | false {\n // boolean true (backward compat) or env \"true\" → quick tunnel\n if (raw === true || (raw == null && envTunnel === \"true\")) {\n return { provider: \"cloudflare-quick\" };\n }\n\n // boolean false or absent → no tunnel\n if (raw === false || raw == null) {\n return false;\n }\n\n // object → parse by provider\n if (typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n const provider = obj.provider as string;\n\n switch (provider) {\n case \"cloudflare-quick\":\n return { provider: \"cloudflare-quick\" };\n case \"cloudflare\":\n return {\n provider: \"cloudflare\",\n token: String(obj.token ?? \"\"),\n ...(obj.hostname ? { hostname: String(obj.hostname) } : {}),\n };\n case \"ngrok\":\n return {\n provider: \"ngrok\",\n authtoken: String(obj.authtoken ?? \"\"),\n ...(obj.domain ? { domain: String(obj.domain) } : {}),\n };\n case \"custom\":\n return {\n provider: \"custom\",\n url: String(obj.url ?? \"\"),\n ...(obj.command ? { command: String(obj.command) } : {}),\n };\n case \"frp\":\n return {\n provider: \"frp\",\n server_addr: String(obj.server_addr ?? \"\"),\n server_port: Math.floor(positiveNumber(obj.server_port, 7000)),\n token: String(obj.token ?? \"\"),\n ...(obj.proxy_type === \"tcp\"\n ? { proxy_type: \"tcp\" as const }\n : { proxy_type: \"http\" as const }),\n ...(Array.isArray(obj.custom_domains)\n ? { custom_domains: obj.custom_domains.map(String) }\n : {}),\n ...(obj.remote_port != null\n ? { remote_port: Math.floor(Number(obj.remote_port)) }\n : {}),\n ...(obj.proxy_name ? { proxy_name: String(obj.proxy_name) } : {}),\n ...(obj.tls_enable === true ? { tls_enable: true } : {}),\n ...(obj.transport_protocol === \"websocket\"\n ? { transport_protocol: \"websocket\" as const }\n : {}),\n };\n default:\n console.warn(\n `[Web] Unknown tunnel provider \"${provider}\", using quick tunnel`,\n );\n return { provider: \"cloudflare-quick\" };\n }\n }\n\n // Truthy non-object (string \"true\" etc) → quick tunnel\n if (raw) {\n return { provider: \"cloudflare-quick\" };\n }\n\n return false;\n}\n\nexport function loadWebConfig(): WebConfig {\n const cfg = (loadConfig().web as Record<string, unknown>) ?? {};\n\n // Google OAuth (optional)\n let google: GoogleOAuthConfig | undefined;\n const googleCfg = cfg.google as Record<string, unknown> | undefined;\n if (googleCfg) {\n const clientId =\n (googleCfg.client_id as string) ??\n process.env.KLAUS_GOOGLE_CLIENT_ID ??\n \"\";\n const clientSecret =\n (googleCfg.client_secret as string) ??\n process.env.KLAUS_GOOGLE_CLIENT_SECRET ??\n \"\";\n if (clientId && clientSecret) {\n google = { clientId, clientSecret };\n }\n }\n\n return {\n port: Number(cfg.port ?? process.env.KLAUS_WEB_PORT ?? 3000),\n tunnel: parseTunnelConfig(cfg.tunnel, process.env.KLAUS_WEB_TUNNEL),\n permissions: Boolean(\n cfg.permissions ?? process.env.KLAUS_WEB_PERMISSIONS === \"true\",\n ),\n sessionMaxAgeDays: positiveNumber(cfg.session_max_age_days, 7),\n ...(google ? { google } : {}),\n };\n}\n\nfunction positiveNumber(raw: unknown, fallback: number): number {\n const n = Number(raw ?? fallback);\n return Number.isFinite(n) && n > 0 ? n : fallback;\n}\n\nexport function loadSessionConfig(): SessionConfig {\n const cfg = (loadConfig().session as Record<string, unknown>) ?? {};\n return {\n idleMs: positiveNumber(cfg.idle_minutes, 240) * 60 * 1000,\n maxEntries: Math.floor(positiveNumber(cfg.max_entries, 100)),\n maxAgeMs: positiveNumber(cfg.max_age_days, 7) * 24 * 60 * 60 * 1000,\n };\n}\n\nexport function loadTranscriptsConfig(): TranscriptsConfig {\n const cfg = (loadConfig().transcripts as Record<string, unknown>) ?? {};\n return {\n transcriptsDir: (cfg.dir as string) ?? join(CONFIG_DIR, \"transcripts\"),\n maxFiles: Math.floor(positiveNumber(cfg.max_files, 200)),\n maxAgeDays: positiveNumber(cfg.max_age_days, 30),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Cron config helpers\n// ---------------------------------------------------------------------------\n\nconst THINKING_LEVELS = new Set([\"off\", \"minimal\", \"low\", \"medium\", \"high\"]);\n\n/**\n * Parse a relative duration string (e.g., \"20m\", \"1h\", \"2h30m\", \"90s\")\n * into an absolute ISO 8601 timestamp from now.\n * Returns undefined if not a valid relative duration.\n */\nexport function parseRelativeTime(input: string): string | undefined {\n const trimmed = input.trim().toLowerCase();\n // Must contain at least one digit followed by a unit letter\n if (!/^\\d/.test(trimmed)) return undefined;\n // Must NOT look like ISO 8601\n if (/^\\d{4}-\\d{2}/.test(trimmed)) return undefined;\n\n let totalMs = 0;\n const re = /(\\d+)\\s*(s|sec|m|min|h|hr|d|day)s?/g;\n let match: RegExpExecArray | null;\n let matched = false;\n\n while ((match = re.exec(trimmed)) !== null) {\n matched = true;\n const value = parseInt(match[1], 10);\n const unit = match[2];\n switch (unit) {\n case \"s\":\n case \"sec\":\n totalMs += value * 1000;\n break;\n case \"m\":\n case \"min\":\n totalMs += value * 60 * 1000;\n break;\n case \"h\":\n case \"hr\":\n totalMs += value * 60 * 60 * 1000;\n break;\n case \"d\":\n case \"day\":\n totalMs += value * 24 * 60 * 60 * 1000;\n break;\n }\n }\n\n if (!matched || totalMs <= 0) return undefined;\n return new Date(Date.now() + totalMs).toISOString();\n}\n\nfunction parseFailureAlert(raw: unknown): CronFailureAlert | undefined {\n if (!raw || typeof raw !== \"object\") return undefined;\n const o = raw as Record<string, unknown>;\n if (o.enabled === false) return undefined;\n const mode = String(o.mode ?? \"\");\n return {\n enabled: o.enabled !== false,\n after: Math.floor(positiveNumber(o.after, 2)),\n cooldownMs: positiveNumber(o.cooldown_minutes, 60) * 60 * 1000,\n ...(o.channel ? { channel: String(o.channel) } : {}),\n ...(o.to ? { to: String(o.to) } : {}),\n ...(mode === \"announce\" || mode === \"webhook\" ? { mode } : {}),\n ...(o.webhook_url ? { webhookUrl: String(o.webhook_url) } : {}),\n ...(o.webhook_token ? { webhookToken: String(o.webhook_token) } : {}),\n };\n}\n\nfunction parseRetryConfig(raw: unknown): CronRetryConfig | undefined {\n if (!raw || typeof raw !== \"object\") return undefined;\n const o = raw as Record<string, unknown>;\n return {\n maxAttempts: Math.floor(positiveNumber(o.max_attempts, 3)),\n backoffMs: (() => {\n const arr = Array.isArray(o.backoff_ms)\n ? o.backoff_ms.map(Number).filter((n) => Number.isFinite(n) && n > 0)\n : [];\n return arr.length > 0\n ? arr\n : [30_000, 60_000, 300_000, 900_000, 3_600_000];\n })(),\n retryOn: Array.isArray(o.retry_on)\n ? o.retry_on.map(String)\n : [\"rate_limit\", \"network\", \"server_error\"],\n };\n}\n\nfunction parseRunLogConfig(raw: unknown): CronRunLogConfig | undefined {\n if (!raw || typeof raw !== \"object\") return undefined;\n const o = raw as Record<string, unknown>;\n return {\n maxBytes: Math.floor(positiveNumber(o.max_bytes, 2_000_000)),\n keepLines: Math.floor(positiveNumber(o.keep_lines, 2000)),\n };\n}\n\nfunction parseFailureDestination(\n raw: unknown,\n): CronFailureDestination | undefined {\n if (!raw || typeof raw !== \"object\") return undefined;\n const o = raw as Record<string, unknown>;\n const mode = String(o.mode ?? \"\");\n return {\n ...(o.channel ? { channel: String(o.channel) } : {}),\n ...(o.to ? { to: String(o.to) } : {}),\n ...(o.account_id ? { accountId: String(o.account_id) } : {}),\n ...(mode === \"announce\" || mode === \"webhook\" ? { mode } : {}),\n };\n}\n\nfunction parseDelivery(raw: unknown): CronDelivery | undefined {\n if (!raw || typeof raw !== \"object\") return undefined;\n const d = raw as Record<string, unknown>;\n if (!d.channel || typeof d.channel !== \"string\") return undefined;\n const mode = d.mode as string | undefined;\n return {\n channel: d.channel,\n ...(d.to ? { to: String(d.to) } : {}),\n ...(mode === \"announce\" || mode === \"webhook\" || mode === \"none\"\n ? { mode }\n : {}),\n ...(d.best_effort === true ? { bestEffort: true } : {}),\n ...(d.account_id ? { accountId: String(d.account_id) } : {}),\n ...(d.failure_destination\n ? { failureDestination: parseFailureDestination(d.failure_destination) }\n : {}),\n };\n}\n\nexport function loadCronConfig(): CronConfig {\n const cfg = (loadConfig().cron as Record<string, unknown>) ?? {};\n const enabled = cfg.enabled === true;\n const rawTasks = Array.isArray(cfg.tasks) ? cfg.tasks : [];\n\n const tasks: CronTask[] = rawTasks\n .filter(\n (t: unknown): t is Record<string, unknown> =>\n typeof t === \"object\" && t !== null,\n )\n .map((t) => {\n const thinking = String(t.thinking ?? \"\");\n // Resolve relative time for at-type schedules\n const rawSchedule = String(t.schedule ?? \"\");\n const resolvedSchedule = parseRelativeTime(rawSchedule) ?? rawSchedule;\n\n return {\n id: String(t.id ?? \"\"),\n name: t.name != null ? String(t.name) : undefined,\n description: t.description != null ? String(t.description) : undefined,\n schedule: resolvedSchedule,\n prompt: String(t.prompt ?? \"\"),\n model: t.model != null ? String(t.model) : undefined,\n enabled: t.enabled !== false,\n fallbacks: Array.isArray(t.fallbacks)\n ? t.fallbacks.map(String)\n : undefined,\n thinking: THINKING_LEVELS.has(thinking)\n ? (thinking as CronTask[\"thinking\"])\n : undefined,\n lightContext: t.light_context === true ? true : undefined,\n timeoutSeconds:\n t.timeout_seconds != null\n ? Math.floor(Number(t.timeout_seconds))\n : undefined,\n deleteAfterRun:\n t.delete_after_run != null ? t.delete_after_run === true : undefined,\n staggerMs:\n t.stagger_ms != null ? Math.floor(Number(t.stagger_ms)) : undefined,\n deliver: parseDelivery(t.deliver),\n webhookUrl: t.webhook_url != null ? String(t.webhook_url) : undefined,\n webhookToken:\n t.webhook_token != null ? String(t.webhook_token) : undefined,\n failureAlert:\n t.failure_alert === false\n ? (false as const)\n : parseFailureAlert(t.failure_alert),\n createdAt: t.created_at != null ? Number(t.created_at) : Date.now(),\n updatedAt: t.updated_at != null ? Number(t.updated_at) : Date.now(),\n };\n })\n .filter((t) => t.id && t.schedule && t.prompt);\n\n return {\n enabled,\n tasks,\n webhookToken:\n cfg.webhook_token != null ? String(cfg.webhook_token) : undefined,\n retry: parseRetryConfig(cfg.retry),\n sessionRetentionMs:\n cfg.session_retention_minutes != null\n ? positiveNumber(cfg.session_retention_minutes, 1440) * 60 * 1000\n : undefined,\n runLog: parseRunLogConfig(cfg.run_log),\n failureAlert: parseFailureAlert(cfg.failure_alert),\n maxConcurrentRuns:\n cfg.max_concurrent_runs != null\n ? Math.floor(positiveNumber(cfg.max_concurrent_runs, 0))\n : undefined,\n failureDestination: parseFailureDestination(cfg.failure_destination),\n storePath: cfg.store != null ? String(cfg.store) : undefined,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,15 +5,45 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
loadWebConfig,
|
|
7
7
|
saveConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-72GNH56Q.js";
|
|
9
9
|
|
|
10
10
|
// src/channels/web.ts
|
|
11
11
|
import {
|
|
12
12
|
createServer
|
|
13
13
|
} from "http";
|
|
14
|
-
import { mkdirSync as
|
|
15
|
-
import { join as
|
|
14
|
+
import { mkdirSync as mkdirSync3, watch } from "fs";
|
|
15
|
+
import { join as join4, resolve } from "path";
|
|
16
16
|
import { tmpdir as tmpdir2 } from "os";
|
|
17
|
+
|
|
18
|
+
// src/workspace.ts
|
|
19
|
+
import { mkdirSync } from "fs";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
var WORKSPACES_DIR = join(CONFIG_DIR, "workspaces");
|
|
22
|
+
var VALID_USER_ID_RE = /^[0-9a-f]{32}$/;
|
|
23
|
+
function validateUserId(userId) {
|
|
24
|
+
if (!VALID_USER_ID_RE.test(userId)) {
|
|
25
|
+
throw new Error(`Invalid userId for workspace: ${userId}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function ensureWorkspace(userId) {
|
|
29
|
+
validateUserId(userId);
|
|
30
|
+
const dir = join(WORKSPACES_DIR, userId);
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
return dir;
|
|
33
|
+
}
|
|
34
|
+
function getWorkspacePath(userId) {
|
|
35
|
+
validateUserId(userId);
|
|
36
|
+
return join(WORKSPACES_DIR, userId);
|
|
37
|
+
}
|
|
38
|
+
function extractUserId(sessionKey) {
|
|
39
|
+
if (sessionKey.startsWith("web:")) {
|
|
40
|
+
const parts = sessionKey.split(":");
|
|
41
|
+
if (parts.length >= 3) return parts[1];
|
|
42
|
+
}
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/channels/web.ts
|
|
17
47
|
import { WebSocketServer, WebSocket } from "ws";
|
|
18
48
|
|
|
19
49
|
// src/channels/web-ui.ts
|
|
@@ -266,7 +296,7 @@ html, body { height: 100dvh; width: 100vw; margin: 0; padding: 0; font-family: v
|
|
|
266
296
|
<button id="attach" title="Attach file">
|
|
267
297
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>
|
|
268
298
|
</button>
|
|
269
|
-
<input type="file" id="file-input" multiple hidden accept="image/*,audio/*,video/*,.pdf,.txt,.md,.json,.csv,.xml,.html,.js,.ts,.py,.go,.rs,.java,.c,.cpp,.h,.yaml,.yml,.toml,.log,.sh,.bat">
|
|
299
|
+
<input type="file" id="file-input" multiple hidden accept="image/*,audio/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.odt,.ods,.odp,.rtf,.txt,.md,.json,.csv,.xml,.html,.js,.ts,.py,.go,.rs,.java,.c,.cpp,.h,.yaml,.yml,.toml,.log,.sh,.bat">
|
|
270
300
|
<textarea id="input" rows="1" placeholder="Send a message to Klaus..." data-i18n-placeholder="placeholder" autocomplete="off"></textarea>
|
|
271
301
|
<button id="send" disabled>
|
|
272
302
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4Z"></path><path d="M22 2 11 13"></path></svg>
|
|
@@ -1995,7 +2025,7 @@ html, body { height: 100%; font-family: var(--font-main); background: var(--bg);
|
|
|
1995
2025
|
import { execFileSync, spawn } from "child_process";
|
|
1996
2026
|
import { writeFileSync, rmSync, mkdtempSync } from "fs";
|
|
1997
2027
|
import { tmpdir } from "os";
|
|
1998
|
-
import { join } from "path";
|
|
2028
|
+
import { join as join2 } from "path";
|
|
1999
2029
|
function hasCommand(cmd) {
|
|
2000
2030
|
try {
|
|
2001
2031
|
execFileSync("which", [cmd], { stdio: "pipe" });
|
|
@@ -2157,16 +2187,23 @@ function generateFrpcToml(cfg, port) {
|
|
|
2157
2187
|
"[auth]",
|
|
2158
2188
|
`token = "${escapeTOML(cfg.token)}"`
|
|
2159
2189
|
];
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2190
|
+
const transportProto = cfg.transport_protocol ?? "tcp";
|
|
2191
|
+
lines.push(
|
|
2192
|
+
"",
|
|
2193
|
+
"[transport]",
|
|
2194
|
+
"poolCount = 5",
|
|
2195
|
+
`protocol = "${transportProto}"`,
|
|
2196
|
+
`tls.enable = ${cfg.tls_enable ? "true" : "false"}`
|
|
2197
|
+
);
|
|
2163
2198
|
lines.push(
|
|
2164
2199
|
"",
|
|
2165
2200
|
"[[proxies]]",
|
|
2166
2201
|
`name = "${escapeTOML(proxyName)}"`,
|
|
2167
2202
|
`type = "${proxyType}"`,
|
|
2168
2203
|
`localIP = "127.0.0.1"`,
|
|
2169
|
-
`localPort = ${port}
|
|
2204
|
+
`localPort = ${port}`,
|
|
2205
|
+
`transport.useEncryption = true`,
|
|
2206
|
+
`transport.useCompression = true`
|
|
2170
2207
|
);
|
|
2171
2208
|
if (proxyType === "http" && cfg.custom_domains?.length) {
|
|
2172
2209
|
const domains = cfg.custom_domains.map((d) => `"${escapeTOML(d)}"`).join(", ");
|
|
@@ -2196,8 +2233,8 @@ function startFrpTunnel(cfg, port) {
|
|
|
2196
2233
|
);
|
|
2197
2234
|
return null;
|
|
2198
2235
|
}
|
|
2199
|
-
const tmpDir = mkdtempSync(
|
|
2200
|
-
const configPath =
|
|
2236
|
+
const tmpDir = mkdtempSync(join2(tmpdir(), "klaus-frp-"));
|
|
2237
|
+
const configPath = join2(tmpDir, "frpc.toml");
|
|
2201
2238
|
writeFileSync(configPath, generateFrpcToml(cfg, port), {
|
|
2202
2239
|
encoding: "utf-8",
|
|
2203
2240
|
mode: 384
|
|
@@ -2360,9 +2397,9 @@ function formatToolEvent(event) {
|
|
|
2360
2397
|
|
|
2361
2398
|
// src/channels/web-auth.ts
|
|
2362
2399
|
import { randomBytes } from "crypto";
|
|
2363
|
-
import { join as
|
|
2400
|
+
import { join as join3 } from "path";
|
|
2364
2401
|
import {
|
|
2365
|
-
mkdirSync,
|
|
2402
|
+
mkdirSync as mkdirSync2,
|
|
2366
2403
|
writeFileSync as writeFileSync2,
|
|
2367
2404
|
unlinkSync,
|
|
2368
2405
|
createReadStream,
|
|
@@ -2624,7 +2661,7 @@ async function handleAuthProfile(req, res, userStore) {
|
|
|
2624
2661
|
}
|
|
2625
2662
|
json(res, 200, { user: userResponse(updated) });
|
|
2626
2663
|
}
|
|
2627
|
-
var AVATARS_DIR =
|
|
2664
|
+
var AVATARS_DIR = join3(CONFIG_DIR, "avatars");
|
|
2628
2665
|
var MAX_AVATAR_SIZE = 2 * 1024 * 1024;
|
|
2629
2666
|
var ALLOWED_AVATAR_TYPES = ["image/jpeg", "image/png", "image/webp"];
|
|
2630
2667
|
async function handleAvatarUpload(req, res, userStore) {
|
|
@@ -2646,16 +2683,16 @@ async function handleAvatarUpload(req, res, userStore) {
|
|
|
2646
2683
|
const body = await readBody(req, MAX_AVATAR_SIZE);
|
|
2647
2684
|
const ext = contentType === "image/png" ? "png" : contentType === "image/webp" ? "webp" : "jpg";
|
|
2648
2685
|
const fileName2 = `${auth.user.id}.${ext}`;
|
|
2649
|
-
|
|
2686
|
+
mkdirSync2(AVATARS_DIR, { recursive: true });
|
|
2650
2687
|
for (const oldExt of ["jpg", "png", "webp"]) {
|
|
2651
2688
|
if (oldExt === ext) continue;
|
|
2652
|
-
const oldPath =
|
|
2689
|
+
const oldPath = join3(AVATARS_DIR, `${auth.user.id}.${oldExt}`);
|
|
2653
2690
|
try {
|
|
2654
2691
|
unlinkSync(oldPath);
|
|
2655
2692
|
} catch {
|
|
2656
2693
|
}
|
|
2657
2694
|
}
|
|
2658
|
-
writeFileSync2(
|
|
2695
|
+
writeFileSync2(join3(AVATARS_DIR, fileName2), body);
|
|
2659
2696
|
const avatarUrl = `/api/avatars/${fileName2}`;
|
|
2660
2697
|
userStore.setAvatarUrl(auth.user.id, avatarUrl);
|
|
2661
2698
|
const updated = userStore.getUserById(auth.user.id);
|
|
@@ -2677,7 +2714,7 @@ function handleAvatarServe(req, res, fileName2) {
|
|
|
2677
2714
|
res.end("bad request");
|
|
2678
2715
|
return;
|
|
2679
2716
|
}
|
|
2680
|
-
const filePath =
|
|
2717
|
+
const filePath = join3(AVATARS_DIR, fileName2);
|
|
2681
2718
|
if (!existsSync(filePath)) {
|
|
2682
2719
|
res.writeHead(404);
|
|
2683
2720
|
res.end("not found");
|
|
@@ -2905,8 +2942,6 @@ You are Klaus. You have always been Klaus. There is nothing behind the curtain.
|
|
|
2905
2942
|
`;
|
|
2906
2943
|
|
|
2907
2944
|
// src/channels/web.ts
|
|
2908
|
-
var UPLOAD_DIR = join3(tmpdir2(), "klaus-web-uploads");
|
|
2909
|
-
mkdirSync2(UPLOAD_DIR, { recursive: true });
|
|
2910
2945
|
var MAX_UPLOAD_SIZE = 10 * 1024 * 1024;
|
|
2911
2946
|
var MAX_DOWNLOAD_SIZE = 50 * 1024 * 1024;
|
|
2912
2947
|
var downloadTokens = /* @__PURE__ */ new Map();
|
|
@@ -2920,12 +2955,13 @@ setInterval(() => {
|
|
|
2920
2955
|
var DOWNLOAD_ALLOWED_DIRS = ["/tmp", tmpdir2()];
|
|
2921
2956
|
function registerDownloadToken(filePath, userId) {
|
|
2922
2957
|
const resolved = resolve(filePath);
|
|
2958
|
+
const userWorkspace = getWorkspacePath(userId);
|
|
2923
2959
|
const allowed = DOWNLOAD_ALLOWED_DIRS.some(
|
|
2924
2960
|
(dir) => resolved === dir || resolved.startsWith(dir + "/")
|
|
2925
|
-
);
|
|
2961
|
+
) || resolved === userWorkspace || resolved.startsWith(userWorkspace + "/");
|
|
2926
2962
|
if (!allowed) {
|
|
2927
2963
|
throw new Error(
|
|
2928
|
-
`Download path not allowed: ${resolved} (must be under /tmp)`
|
|
2964
|
+
`Download path not allowed: ${resolved} (must be under /tmp or user workspace)`
|
|
2929
2965
|
);
|
|
2930
2966
|
}
|
|
2931
2967
|
const token = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
@@ -2942,12 +2978,59 @@ var ALLOWED_MIME_PREFIXES = [
|
|
|
2942
2978
|
"image/",
|
|
2943
2979
|
"audio/",
|
|
2944
2980
|
"video/",
|
|
2945
|
-
"text/
|
|
2981
|
+
"text/",
|
|
2946
2982
|
"application/pdf",
|
|
2947
2983
|
"application/json",
|
|
2948
2984
|
"application/zip",
|
|
2949
|
-
"application/gzip"
|
|
2985
|
+
"application/gzip",
|
|
2986
|
+
"application/xml",
|
|
2987
|
+
// MS Office
|
|
2988
|
+
"application/msword",
|
|
2989
|
+
"application/vnd.ms-",
|
|
2990
|
+
"application/vnd.openxmlformats-officedocument.",
|
|
2991
|
+
// OpenDocument
|
|
2992
|
+
"application/vnd.oasis.opendocument.",
|
|
2993
|
+
// Other common docs
|
|
2994
|
+
"application/rtf",
|
|
2995
|
+
"application/x-yaml"
|
|
2950
2996
|
];
|
|
2997
|
+
var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2998
|
+
"doc",
|
|
2999
|
+
"docx",
|
|
3000
|
+
"xls",
|
|
3001
|
+
"xlsx",
|
|
3002
|
+
"ppt",
|
|
3003
|
+
"pptx",
|
|
3004
|
+
"odt",
|
|
3005
|
+
"ods",
|
|
3006
|
+
"odp",
|
|
3007
|
+
"rtf",
|
|
3008
|
+
"pdf",
|
|
3009
|
+
"txt",
|
|
3010
|
+
"md",
|
|
3011
|
+
"csv",
|
|
3012
|
+
"json",
|
|
3013
|
+
"xml",
|
|
3014
|
+
"yaml",
|
|
3015
|
+
"yml",
|
|
3016
|
+
"toml",
|
|
3017
|
+
"log",
|
|
3018
|
+
"html",
|
|
3019
|
+
"js",
|
|
3020
|
+
"ts",
|
|
3021
|
+
"py",
|
|
3022
|
+
"go",
|
|
3023
|
+
"rs",
|
|
3024
|
+
"java",
|
|
3025
|
+
"c",
|
|
3026
|
+
"cpp",
|
|
3027
|
+
"h",
|
|
3028
|
+
"sh",
|
|
3029
|
+
"bat",
|
|
3030
|
+
"zip",
|
|
3031
|
+
"gz",
|
|
3032
|
+
"tar"
|
|
3033
|
+
]);
|
|
2951
3034
|
var messageStoreRef = null;
|
|
2952
3035
|
var inviteStoreRef = null;
|
|
2953
3036
|
var sessionStoreRef = null;
|
|
@@ -3067,9 +3150,9 @@ async function servePublicFile(res, filename, contentType) {
|
|
|
3067
3150
|
const __filename = fileURLToPath(import.meta.url);
|
|
3068
3151
|
const __dirname = dirname(__filename);
|
|
3069
3152
|
const candidates = [
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3153
|
+
join4(__dirname, "..", "public", filename),
|
|
3154
|
+
join4(__dirname, "..", "..", "public", filename),
|
|
3155
|
+
join4(process.cwd(), "public", filename)
|
|
3073
3156
|
];
|
|
3074
3157
|
for (const p of candidates) {
|
|
3075
3158
|
try {
|
|
@@ -3315,7 +3398,8 @@ async function handleUpload(req, res) {
|
|
|
3315
3398
|
const mimeAllowed = ALLOWED_MIME_PREFIXES.some(
|
|
3316
3399
|
(prefix) => contentType.startsWith(prefix)
|
|
3317
3400
|
);
|
|
3318
|
-
|
|
3401
|
+
const ext = fileName2.split(".").pop()?.toLowerCase() ?? "";
|
|
3402
|
+
if (!mimeAllowed && !ALLOWED_EXTENSIONS.has(ext)) {
|
|
3319
3403
|
jsonResponse(res, 415, { error: "unsupported media type" });
|
|
3320
3404
|
return;
|
|
3321
3405
|
}
|
|
@@ -3323,7 +3407,9 @@ async function handleUpload(req, res) {
|
|
|
3323
3407
|
const baseName = fileName2.split(/[\\/]/).pop() ?? "upload";
|
|
3324
3408
|
const safeBase = baseName.replace(/[^\w.\-]/g, "_");
|
|
3325
3409
|
const diskName = `${Date.now()}-${Math.random().toString(36).slice(2, 6)}-${safeBase}`;
|
|
3326
|
-
const
|
|
3410
|
+
const userUploadDir = join4(ensureWorkspace(auth.user.id), "uploads");
|
|
3411
|
+
mkdirSync3(userUploadDir, { recursive: true });
|
|
3412
|
+
const filePath = join4(userUploadDir, diskName);
|
|
3327
3413
|
const { writeFile } = await import("fs/promises");
|
|
3328
3414
|
await writeFile(filePath, data);
|
|
3329
3415
|
const fileId = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
@@ -3461,7 +3547,7 @@ async function handleAdminUsers(req, res) {
|
|
|
3461
3547
|
}
|
|
3462
3548
|
jsonResponse(res, 405, { error: "method not allowed" });
|
|
3463
3549
|
}
|
|
3464
|
-
var
|
|
3550
|
+
var VALID_USER_ID_RE2 = /^[0-9a-f]{32}$/;
|
|
3465
3551
|
async function handleAdminSessions(req, res) {
|
|
3466
3552
|
if (!adminAuth(req, res)) return;
|
|
3467
3553
|
if (req.method !== "GET") {
|
|
@@ -3474,7 +3560,7 @@ async function handleAdminSessions(req, res) {
|
|
|
3474
3560
|
}
|
|
3475
3561
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3476
3562
|
const userId = url.searchParams.get("userId") ?? "";
|
|
3477
|
-
if (!userId || !
|
|
3563
|
+
if (!userId || !VALID_USER_ID_RE2.test(userId)) {
|
|
3478
3564
|
jsonResponse(res, 400, { error: "missing or invalid userId" });
|
|
3479
3565
|
return;
|
|
3480
3566
|
}
|
|
@@ -3500,7 +3586,7 @@ async function handleAdminHistory(req, res) {
|
|
|
3500
3586
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3501
3587
|
const userId = url.searchParams.get("userId") ?? "";
|
|
3502
3588
|
const sessionId = url.searchParams.get("sessionId") ?? "";
|
|
3503
|
-
if (!userId || !
|
|
3589
|
+
if (!userId || !VALID_USER_ID_RE2.test(userId)) {
|
|
3504
3590
|
jsonResponse(res, 400, { error: "missing or invalid userId" });
|
|
3505
3591
|
return;
|
|
3506
3592
|
}
|
|
@@ -4021,6 +4107,8 @@ var webPlugin = {
|
|
|
4021
4107
|
};
|
|
4022
4108
|
|
|
4023
4109
|
export {
|
|
4110
|
+
ensureWorkspace,
|
|
4111
|
+
extractUserId,
|
|
4024
4112
|
getToolConfig,
|
|
4025
4113
|
DEFAULT_PERSONA,
|
|
4026
4114
|
setMessageStore,
|
|
@@ -4031,4 +4119,4 @@ export {
|
|
|
4031
4119
|
readBody2 as readBody,
|
|
4032
4120
|
webPlugin
|
|
4033
4121
|
};
|
|
4034
|
-
//# sourceMappingURL=chunk-
|
|
4122
|
+
//# sourceMappingURL=chunk-PQJGV3GK.js.map
|