skimpyclaw 0.3.14 → 0.4.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.
- package/README.md +47 -37
- package/dist/__tests__/adapter-types.test.d.ts +4 -0
- package/dist/__tests__/adapter-types.test.js +63 -0
- package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
- package/dist/__tests__/anthropic-adapter.test.js +264 -0
- package/dist/__tests__/api.test.js +0 -1
- package/dist/__tests__/cli.integration.test.js +2 -4
- package/dist/__tests__/cli.test.js +0 -1
- package/dist/__tests__/code-agents-notifications.test.js +137 -0
- package/dist/__tests__/code-agents-parser.test.js +19 -1
- package/dist/__tests__/code-agents-preflight.test.js +3 -28
- package/dist/__tests__/code-agents-utils.test.js +34 -9
- package/dist/__tests__/code-agents-worktrees.test.js +116 -0
- package/dist/__tests__/codex-adapter.test.js +184 -0
- package/dist/__tests__/codex-auth.test.js +66 -0
- package/dist/__tests__/codex-provider-gating.test.js +35 -0
- package/dist/__tests__/codex-unified-loop.test.js +111 -0
- package/dist/__tests__/config-security.test.js +127 -0
- package/dist/__tests__/config.test.js +23 -0
- package/dist/__tests__/context-manager.test.js +243 -164
- package/dist/__tests__/cron-run.test.js +250 -0
- package/dist/__tests__/cron.test.js +12 -38
- package/dist/__tests__/digests.test.js +67 -0
- package/dist/__tests__/discord-attachments.test.js +211 -0
- package/dist/__tests__/discord-docs.test.d.ts +1 -0
- package/dist/__tests__/discord-docs.test.js +27 -0
- package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-agents.test.js +115 -0
- package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-context.test.js +42 -0
- package/dist/__tests__/doctor.formatters.test.js +4 -4
- package/dist/__tests__/doctor.index.test.js +1 -1
- package/dist/__tests__/doctor.runner.test.js +3 -15
- package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
- package/dist/__tests__/env-sanitizer.test.js +45 -0
- package/dist/__tests__/exec-approval.test.js +61 -0
- package/dist/__tests__/fetch-tool.test.d.ts +1 -0
- package/dist/__tests__/fetch-tool.test.js +85 -0
- package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
- package/dist/__tests__/gateway-status-auth.test.js +72 -0
- package/dist/__tests__/heartbeat.test.js +3 -3
- package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
- package/dist/__tests__/interactive-sessions.test.js +96 -0
- package/dist/__tests__/langfuse.test.js +6 -18
- package/dist/__tests__/model-selection.test.js +3 -4
- package/dist/__tests__/providers-init.test.js +2 -8
- package/dist/__tests__/providers-routing.test.js +1 -1
- package/dist/__tests__/providers-utils.test.js +13 -3
- package/dist/__tests__/sessions.test.js +14 -10
- package/dist/__tests__/setup.test.js +12 -29
- package/dist/__tests__/skills.test.js +10 -7
- package/dist/__tests__/stream-formatter.test.d.ts +1 -0
- package/dist/__tests__/stream-formatter.test.js +114 -0
- package/dist/__tests__/token-efficiency.test.js +131 -15
- package/dist/__tests__/tool-loop.test.d.ts +4 -0
- package/dist/__tests__/tool-loop.test.js +505 -0
- package/dist/__tests__/tools.test.js +101 -276
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +14 -0
- package/dist/__tests__/voice.test.js +21 -0
- package/dist/agent.js +35 -4
- package/dist/api.js +113 -37
- package/dist/channels/discord/attachments.d.ts +50 -0
- package/dist/channels/discord/attachments.js +137 -0
- package/dist/channels/discord/delegation.d.ts +5 -0
- package/dist/channels/discord/delegation.js +136 -0
- package/dist/channels/discord/handlers.js +694 -7
- package/dist/channels/discord/index.d.ts +16 -1
- package/dist/channels/discord/index.js +64 -1
- package/dist/channels/discord/thread-agents.d.ts +54 -0
- package/dist/channels/discord/thread-agents.js +323 -0
- package/dist/channels/discord/threads.d.ts +58 -0
- package/dist/channels/discord/threads.js +192 -0
- package/dist/channels/discord/types.js +4 -2
- package/dist/channels/discord/utils.d.ts +16 -0
- package/dist/channels/discord/utils.js +86 -6
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/types.js +1 -1
- package/dist/channels/telegram/utils.js +9 -3
- package/dist/channels.d.ts +1 -1
- package/dist/cli.js +20 -400
- package/dist/code-agents/executor.d.ts +1 -1
- package/dist/code-agents/executor.js +101 -45
- package/dist/code-agents/index.d.ts +2 -7
- package/dist/code-agents/index.js +111 -80
- package/dist/code-agents/interactive-resume.d.ts +6 -0
- package/dist/code-agents/interactive-resume.js +98 -0
- package/dist/code-agents/interactive-sessions.d.ts +20 -0
- package/dist/code-agents/interactive-sessions.js +132 -0
- package/dist/code-agents/parser.js +5 -1
- package/dist/code-agents/registry.d.ts +7 -1
- package/dist/code-agents/registry.js +11 -23
- package/dist/code-agents/stream-formatter.d.ts +8 -0
- package/dist/code-agents/stream-formatter.js +92 -0
- package/dist/code-agents/types.d.ts +16 -24
- package/dist/code-agents/utils.d.ts +35 -11
- package/dist/code-agents/utils.js +349 -95
- package/dist/code-agents/worktrees.d.ts +37 -0
- package/dist/code-agents/worktrees.js +116 -0
- package/dist/config.d.ts +2 -4
- package/dist/config.js +123 -23
- package/dist/cron.d.ts +1 -6
- package/dist/cron.js +175 -82
- package/dist/dashboard/assets/index-B345aOO-.js +65 -0
- package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/digests.d.ts +1 -0
- package/dist/digests.js +132 -42
- package/dist/doctor/checks.d.ts +0 -3
- package/dist/doctor/checks.js +1 -108
- package/dist/doctor/runner.js +1 -4
- package/dist/env-sanitizer.d.ts +2 -0
- package/dist/env-sanitizer.js +61 -0
- package/dist/exec-approval.d.ts +11 -1
- package/dist/exec-approval.js +17 -4
- package/dist/gateway.d.ts +3 -1
- package/dist/gateway.js +17 -7
- package/dist/heartbeat.js +1 -6
- package/dist/langfuse.js +3 -29
- package/dist/model-selection.js +3 -1
- package/dist/providers/adapter.d.ts +118 -0
- package/dist/providers/adapter.js +6 -0
- package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
- package/dist/providers/adapters/anthropic-adapter.js +204 -0
- package/dist/providers/adapters/codex-adapter.d.ts +26 -0
- package/dist/providers/adapters/codex-adapter.js +203 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -272
- package/dist/providers/codex.d.ts +21 -0
- package/dist/providers/codex.js +149 -330
- package/dist/providers/content.d.ts +1 -1
- package/dist/providers/content.js +2 -2
- package/dist/providers/context-manager.d.ts +18 -6
- package/dist/providers/context-manager.js +199 -223
- package/dist/providers/index.d.ts +9 -1
- package/dist/providers/index.js +73 -64
- package/dist/providers/loop-utils.d.ts +20 -0
- package/dist/providers/loop-utils.js +30 -0
- package/dist/providers/tool-loop.d.ts +12 -0
- package/dist/providers/tool-loop.js +251 -0
- package/dist/providers/utils.d.ts +19 -3
- package/dist/providers/utils.js +100 -29
- package/dist/secure-store.d.ts +8 -0
- package/dist/secure-store.js +80 -0
- package/dist/service.js +3 -28
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.js +147 -18
- package/dist/setup-templates.js +13 -25
- package/dist/setup.d.ts +10 -6
- package/dist/setup.js +84 -292
- package/dist/skills.js +3 -11
- package/dist/tools/agent-delegation.d.ts +19 -0
- package/dist/tools/agent-delegation.js +49 -0
- package/dist/tools/bash-tool.js +89 -34
- package/dist/tools/definitions.d.ts +199 -302
- package/dist/tools/definitions.js +70 -123
- package/dist/tools/execute-context.d.ts +13 -4
- package/dist/tools/fetch-tool.js +109 -13
- package/dist/tools/file-tools.js +7 -1
- package/dist/tools.d.ts +7 -7
- package/dist/tools.js +133 -151
- package/dist/types.d.ts +37 -30
- package/dist/utils.js +4 -6
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +17 -4
- package/package.json +33 -23
- package/templates/TOOLS.md +0 -27
- package/dist/__tests__/audit.test.js +0 -122
- package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
- package/dist/__tests__/code-agents-sandbox.test.js +0 -163
- package/dist/__tests__/orchestrator.test.js +0 -425
- package/dist/__tests__/sandbox-bridge.test.js +0 -116
- package/dist/__tests__/sandbox-manager.test.js +0 -144
- package/dist/__tests__/sandbox-mount-security.test.js +0 -139
- package/dist/__tests__/sandbox-runtime.test.js +0 -176
- package/dist/__tests__/subagent.test.js +0 -240
- package/dist/__tests__/telegram.test.js +0 -42
- package/dist/code-agents/orchestrator.d.ts +0 -29
- package/dist/code-agents/orchestrator.js +0 -694
- package/dist/code-agents/worktree.d.ts +0 -40
- package/dist/code-agents/worktree.js +0 -215
- package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
- package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
- package/dist/dashboard.d.ts +0 -8
- package/dist/dashboard.js +0 -4071
- package/dist/discord.d.ts +0 -8
- package/dist/discord.js +0 -792
- package/dist/mcp-context-a8c.d.ts +0 -13
- package/dist/mcp-context-a8c.js +0 -34
- package/dist/orchestrator.d.ts +0 -15
- package/dist/orchestrator.js +0 -676
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +0 -355
- package/dist/sandbox/bridge.d.ts +0 -5
- package/dist/sandbox/bridge.js +0 -63
- package/dist/sandbox/index.d.ts +0 -5
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/manager.d.ts +0 -7
- package/dist/sandbox/manager.js +0 -100
- package/dist/sandbox/mount-security.d.ts +0 -12
- package/dist/sandbox/mount-security.js +0 -122
- package/dist/sandbox/runtime.d.ts +0 -39
- package/dist/sandbox/runtime.js +0 -192
- package/dist/sandbox-utils.d.ts +0 -6
- package/dist/sandbox-utils.js +0 -36
- package/dist/subagent.d.ts +0 -19
- package/dist/subagent.js +0 -407
- package/dist/telegram.d.ts +0 -2
- package/dist/telegram.js +0 -11
- package/dist/tools/browser-tool.d.ts +0 -3
- package/dist/tools/browser-tool.js +0 -266
- package/sandbox/Dockerfile +0 -40
- /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
- /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
- /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
- /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;600;700;800&family=Noto+Sans:wght@400;500;600&display=swap";.sidebar{background:var(--bg-sidebar);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;z-index:10;transition:background .3s ease;overflow-y:auto}.sidebar-header{padding:28px 24px 20px;display:flex;align-items:center;gap:12px}.sidebar-logo{width:40px;height:40px;background:transparent;border-radius:0;display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:none;flex-shrink:0}.sidebar-brand{display:flex;flex-direction:column}.sidebar-close-btn{display:none;margin-left:auto;width:30px;height:30px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface);color:var(--text-dim);align-items:center;justify-content:center;cursor:pointer}.sidebar-close-btn:hover{background:var(--surface-hover);color:var(--text)}.sidebar-brand-name{font-family:var(--serif);font-size:17px;font-weight:800;color:var(--text);letter-spacing:-.01em}.sidebar-brand-status{font-size:11px;color:var(--success);display:flex;align-items:center;gap:5px;font-weight:500}.sidebar-brand-status:before{content:"";width:6px;height:6px;border-radius:50%;background:var(--success);display:inline-block}.sidebar-section{padding:0 12px;margin-bottom:8px}.sidebar-section-label{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;padding:16px 12px 8px}.sidebar-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:var(--radius-sm);cursor:pointer;color:var(--text-dim);font-size:14px;font-weight:500;transition:all .15s ease;border:none;background:transparent;width:100%;text-align:left;font-family:var(--sans);position:relative}.sidebar-item:hover{background:var(--sidebar-hover);color:var(--text)}.sidebar-item.active{background:var(--sidebar-active);color:var(--text-dim);font-weight:600;box-shadow:var(--shadow-sm)}.sidebar-item.active:before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:0 3px 3px 0;background:var(--accent)}.sidebar-item svg{width:20px;height:20px;flex-shrink:0;opacity:.6}.sidebar-item.active svg{opacity:1}.sidebar-item:hover svg{opacity:.85}.sidebar-item-icon{display:inline-flex;align-items:center;justify-content:center;line-height:0}.sidebar-item.active .sidebar-item-icon{color:var(--accent)}.sidebar-item-label{display:inline-flex;align-items:center;line-height:1.2}.sidebar-item .item-badge{margin-left:auto;font-size:11px;font-weight:600;padding:2px 8px;border-radius:999px;background:var(--warning-soft);color:var(--warning)}.sidebar-spacer{flex:1}.sidebar-footer{padding:16px 12px;border-top:1px solid var(--border-light)}.sidebar-footer-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:var(--radius-sm);cursor:pointer;color:var(--text-muted);font-size:13px;font-weight:500;transition:all .15s ease;border:none;background:transparent;width:100%;text-align:left;font-family:var(--sans)}.sidebar-footer-item:hover{background:var(--sidebar-hover);color:var(--text-dim)}.sidebar-footer-item.active{background:var(--sidebar-active);color:var(--text-dim);font-weight:600}.sidebar-footer-item svg{width:18px;height:18px;flex-shrink:0;opacity:.5}.sidebar-footer-item.active .sidebar-item-icon{color:var(--accent)}@media (max-width: 980px){.sidebar-close-btn{display:inline-flex}}:root,:root[data-theme=light],[data-theme=light]{--text: #0c0303;--background: #fffaf5;--text-dim: #5a4a40;--text-muted: #9a8a80;--bg: var(--background);--bg-sidebar: var(--background);--surface: #ffffff;--surface-alt: #f5ebe5;--surface-hover: #ebe0d8;--primary: #d33b36;--primary-hover: #c65a24;--primary-soft: rgba(219, 111, 57, .16);--primary-soft-2: rgba(219, 111, 57, .08);--secondary: #f2ded2;--secondary-soft: rgba(242, 222, 210, .22);--accent: #dfaa6d;--accent-hover: #e07030;--accent-soft: rgba(243, 134, 70, .16);--accent-soft-2: rgba(243, 134, 70, .08);--success: #5a9b5a;--success-soft: rgba(90, 155, 90, .16);--warning: #c9a43c;--warning-soft: rgba(201, 164, 60, .12);--error: #d94a4a;--error-soft: rgba(217, 74, 74, .12);--border: rgba(23, 16, 11, .08);--border-light: rgba(23, 16, 11, .05);--shadow-sm: 0 1px 2px rgba(23, 16, 11, .04);--shadow: 0 2px 8px rgba(23, 16, 11, .06);--shadow-md: 0 4px 16px rgba(23, 16, 11, .08);--sidebar-active: #ffffff;--sidebar-hover: rgba(23, 16, 11, .04);--welcome-from: #db6f39;--welcome-to: #f38646;--welcome-shadow: rgba(219, 111, 57, .3);--welcome-text: #ffffff;--log-info: #5a4a40;--log-warn: #b08a1a;--log-error: #d94a4a;--highlight: #f38646;--info: #7bb8f9;--info-soft: rgba(123, 184, 249, .12);--chip-telegram: #4a7a99;--chip-telegram-bg: rgba(109, 166, 214, .13);--chip-discord: #5a5a99;--chip-discord-bg: rgba(130, 146, 235, .14);--chip-cron: #9a7f3e;--chip-cron-bg: rgba(203, 169, 93, .14);--pending: #7a8a91;--pending-soft: rgba(142, 154, 166, .1);--mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;--sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--serif: "Playfair Display", Georgia, "Times New Roman", serif;--radius: 16px;--radius-md: 12px;--radius-sm: 8px;--sidebar-width: 260px}:root[data-theme=dark],[data-theme=dark]{--text: #fcf3f3;--background: #121212;--text-dim: #d4ccc6;--text-muted: #948c86;--bg: var(--background);--bg-sidebar: var(--background);--surface: #1a120c;--surface-alt: #251c14;--surface-hover: #30261c;--primary: #c9312c;--primary-hover: #db6f39;--primary-soft: rgba(198, 90, 36, .2);--primary-soft-2: rgba(198, 90, 36, .1);--secondary: #f2ded2;--secondary-soft: rgba(242, 222, 210, .22);--accent: #dfaa6d;--accent-hover: #d65a1a;--accent-soft: rgba(187, 76, 12, .2);--accent-soft-2: rgba(187, 76, 12, .1);--success: #5ab85a;--success-soft: rgba(90, 184, 90, .18);--warning: #d4b45a;--warning-soft: rgba(212, 180, 90, .14);--error: #e07a6a;--error-soft: rgba(224, 122, 106, .14);--border: rgba(243, 236, 231, .08);--border-light: rgba(243, 236, 231, .05);--shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);--shadow: 0 2px 8px rgba(0, 0, 0, .35);--shadow-md: 0 4px 16px rgba(0, 0, 0, .4);--sidebar-active: rgba(198, 90, 36, .2);--sidebar-hover: rgba(243, 236, 231, .04);--welcome-from: #c65a24;--welcome-to: #e07a40;--welcome-shadow: rgba(198, 90, 36, .4);--welcome-text: #f3ece7;--log-info: #d4ccc6;--log-warn: #d4b45a;--log-error: #e07a6a;--highlight: #bb4c0c;--info: #7ab8f9;--info-soft: rgba(122, 184, 249, .14);--chip-telegram: #8eb8e0;--chip-telegram-bg: rgba(126, 184, 224, .16);--chip-discord: #9e9eed;--chip-discord-bg: rgba(158, 158, 237, .16);--chip-cron: #d4b45a;--chip-cron-bg: rgba(212, 180, 90, .16);--pending: #9ba5b0;--pending-soft: rgba(155, 165, 176, .14)}*{margin:0;padding:0;box-sizing:border-box}html{font-size:100%}body{font-family:"Noto Sans",var(--sans);font-weight:400;background:var(--bg);color:var(--text);min-height:100vh;font-size:14px;line-height:1.55;transition:background .3s ease,color .3s ease;-webkit-font-smoothing:antialiased}h1,h2,h3,h4,h5{font-family:var(--serif);font-weight:700}h1{font-size:4.21rem}h2{font-size:3.158rem}h3{font-size:2.369rem}h4{font-size:1.777rem}h5{font-size:1.333rem}small{font-size:.75rem}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}.shell{display:grid;grid-template-columns:var(--sidebar-width) 1fr;min-height:100vh}.main{position:relative;overflow-y:auto;padding:32px 36px}.btn{padding:8px 16px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);font-family:var(--sans);font-weight:500;font-size:13px;cursor:pointer;transition:all .12s ease}.btn:hover{background:var(--surface-hover)}.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn-primary:hover{background:var(--accent-hover)}.btn-success{background:var(--success);border-color:var(--success);color:#fff}.btn-success:hover{opacity:.9}.btn-danger{background:var(--error-soft);border-color:#ef444433;color:var(--error)}.btn-danger:hover{background:#ef44441f}.btn-sm,.btn-small{padding:5px 12px;font-size:12px}.btn-refresh{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border:1px solid var(--border-light);border-radius:999px;background:var(--surface-alt);color:var(--text-dim);font-family:var(--sans);font-weight:500;font-size:13px;cursor:pointer;transition:all .15s ease;box-shadow:var(--shadow-sm)}.btn-refresh:hover{background:var(--surface-hover);border-color:var(--border);color:var(--text);box-shadow:var(--shadow)}.btn-refresh:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.btn-refresh:active{transform:scale(.98)}input:not([type=checkbox]):not([type=radio]),select,textarea{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-alt);color:var(--text);font-family:var(--sans);font-size:13px;line-height:1.5;outline:none;transition:border-color .12s ease,box-shadow .12s ease,background .12s ease}textarea{resize:vertical;min-height:90px}input:not([type=checkbox]):not([type=radio]):focus,select:focus,textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);background:var(--surface)}input::placeholder,textarea::placeholder{color:var(--text-muted)}.form-grid{display:grid;gap:10px}.form-grid.two{grid-template-columns:1fr 1fr}.form-grid.three{grid-template-columns:repeat(3,minmax(0,1fr))}.form-field{display:flex;flex-direction:column;gap:6px}.form-label{font-size:12px;color:var(--text-dim);font-weight:600;letter-spacing:.01em}.form-help{font-size:11px;color:var(--text-muted)}.form-actions{display:flex;gap:8px;align-items:center;margin-top:12px}.form-checkbox{display:flex;align-items:center;gap:10px;font-size:13px;line-height:1.35;color:var(--text-dim);cursor:pointer;-webkit-user-select:none;user-select:none}.form-checkbox input[type=checkbox]{-moz-appearance:none;appearance:none;-webkit-appearance:none;width:18px;height:18px;flex:0 0 18px;border:1px solid var(--border);border-radius:5px;background:var(--surface);display:inline-grid;place-content:center;margin:0;cursor:pointer;transition:border-color .12s ease,box-shadow .12s ease,background .12s ease}.form-checkbox input[type=checkbox]:before{content:"";width:10px;height:10px;transform:scale(0);transition:transform .1s ease-in-out;background:#fff;clip-path:polygon(14% 44%,0 63%,44% 100%,100% 18%,82% 0,40% 64%)}.form-checkbox input[type=checkbox]:checked{border-color:var(--accent);background:var(--accent)}.form-checkbox input[type=checkbox]:checked:before{transform:scale(1)}.form-checkbox input[type=checkbox]:focus-visible{outline:none;box-shadow:0 0 0 3px var(--accent-soft)}.form-check-row{display:flex;gap:20px;align-items:center;flex-wrap:wrap}.form-check-row.tight{gap:12px}.form-check-col{display:flex;flex-direction:column;gap:6px}.form-divider{height:1px;background:var(--border-light);margin:12px 0}.toast-container{position:fixed;bottom:24px;right:24px;z-index:1000;display:flex;flex-direction:column;gap:8px}.toast{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px 18px;font-size:13px;box-shadow:var(--shadow-md);animation:toast-in .2s ease}.toast.success{border-left:3px solid var(--success)}.toast.error{border-left:3px solid var(--error)}.toast.warning{border-left:3px solid var(--warning)}@keyframes toast-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.section{margin-bottom:28px}.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.section-title{font-family:var(--serif);font-size:18px;font-weight:800;color:var(--text);letter-spacing:-.01em}.section-link{font-size:13px;color:var(--accent);cursor:pointer;text-decoration:none;font-weight:500}.section-link:hover{text-decoration:underline}.section-link-content{display:inline-flex;align-items:center;gap:6px}.page-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:28px}.page-title{font-family:var(--serif);font-size:26px;font-weight:800;letter-spacing:-.02em;color:var(--text)}.header-actions{display:flex;gap:10px;align-items:center;flex-wrap:nowrap}.page-header .header-actions{margin-right:72px}.header-meta{font-size:12px;color:var(--text-muted);font-family:var(--mono)}.theme-toggle{width:36px;height:36px;border:1px solid var(--border);background:var(--surface);border-radius:var(--radius-sm);cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;transition:all .15s ease;color:var(--text)}.theme-toggle:hover{background:var(--surface-hover)}.global-theme-toggle{position:absolute;top:32px;right:36px;width:37px;height:37px;border:1px solid var(--border);background:var(--surface);border-radius:var(--radius-sm);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s ease;box-shadow:var(--shadow-sm);color:var(--text);z-index:100}.global-theme-toggle:hover{background:var(--surface-hover);box-shadow:var(--shadow)}.global-sidebar-toggle,.sidebar-backdrop{display:none}.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-bottom:10px}.cards-link-row{display:flex;justify-content:flex-end;margin-bottom:22px}.stat-card{background:var(--surface);border-radius:var(--radius);padding:22px 24px;box-shadow:var(--shadow-sm);transition:all .2s ease;border:1px solid var(--border-light);display:flex;align-items:center;gap:16px;min-height:138px}.stat-card:hover{box-shadow:var(--shadow);transform:translateY(-1px)}.stat-icon{width:48px;height:48px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:18px;flex:0 0 auto}.stat-icon svg{width:20px;height:20px}.stat-icon.sage{background:var(--accent-soft);color:var(--accent)}.stat-icon.green{background:var(--success-soft);color:var(--success)}.stat-icon.amber{background:var(--warning-soft);color:var(--warning)}.stat-icon.blue{background:var(--info-soft);color:var(--info)}.stat-icon.rose{background:#f43f5e14;color:#f43f5e}.stat-icon.muted{background:var(--surface-alt);color:var(--text-muted)}.stats-grid-5{grid-template-columns:repeat(5,1fr)}.stats-grid-5 .stat-card{min-height:120px;padding:18px 20px;gap:12px}.stats-grid-5 .stat-icon{width:42px;height:42px}.stats-grid-5 .stat-icon svg{width:18px;height:18px}.stats-grid-5 .stat-value{font-size:26px}.stats-grid-5 .stat-value-md{font-size:18px}.stats-grid-6{grid-template-columns:repeat(6,1fr)}.stats-grid-6 .stat-card{min-height:120px;padding:16px 18px;gap:10px}.stats-grid-6 .stat-icon{width:38px;height:38px}.stats-grid-6 .stat-icon svg{width:16px;height:16px}.stats-grid-6 .stat-value{font-size:24px}.stats-grid-6 .stat-value-md{font-size:16px}.stat-body{display:flex;flex-direction:column;justify-content:center;min-width:0}.stat-value{font-family:var(--serif);font-size:32px;font-weight:700;line-height:1.02;letter-spacing:-.02em}.stat-value-md{font-size:22px}.stat-subtitle{font-size:11px;color:var(--text-dim);font-weight:500;line-height:1.2}.feed-card{background:var(--surface);border-radius:var(--radius);box-shadow:var(--shadow-sm);border:1px solid var(--border-light);overflow:hidden}.feed{display:flex;flex-direction:column}.feed-item{display:grid;grid-template-columns:40px 1fr auto;gap:14px;align-items:start;padding:16px 20px;transition:background .12s ease;border-bottom:1px solid var(--border-light)}.feed-item:last-child{border-bottom:none}.feed-item:hover{background:var(--surface-alt)}.feed-icon{width:40px;height:40px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}.feed-icon.telegram{background:var(--accent-soft);color:var(--accent)}.feed-icon.cron{background:var(--warning-soft);color:var(--warning)}.feed-icon.system{background:var(--surface-alt);color:var(--text-muted)}.feed-icon.code{background:var(--accent-soft);color:var(--accent)}.feed-title{font-weight:600;font-size:14px;margin-bottom:3px}.feed-detail{font-size:13px;color:var(--text-dim);line-height:1.45}.feed-time{font-size:11px;color:var(--text-muted);font-family:var(--mono);white-space:nowrap;padding-top:2px}.cron-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.cron-card{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:20px 22px;display:flex;justify-content:space-between;align-items:center;box-shadow:var(--shadow-sm);transition:all .15s ease}.cron-card:hover{box-shadow:var(--shadow);transform:translateY(-1px)}.cron-info{flex:1}.cron-name{font-weight:600;font-size:14px;margin-bottom:4px;color:var(--text)}.cron-schedule{font-size:12px;color:var(--text-dim);font-family:var(--mono)}.cron-next{font-size:12px;color:var(--success);font-family:var(--mono);text-align:right;font-weight:500}.welcome-banner{background:linear-gradient(135deg,var(--welcome-from),var(--welcome-to));border-radius:var(--radius);padding:28px 32px;margin-bottom:28px;color:var(--welcome-text);position:relative;overflow:hidden}.welcome-banner:after{content:"";position:absolute;right:-20px;top:-20px;width:200px;height:200px;border-radius:50%;background:#ffffff14}.welcome-banner:before{content:"";position:absolute;right:80px;bottom:-40px;width:120px;height:120px;border-radius:50%;background:#ffffff0d}.welcome-greeting{font-family:var(--serif);font-size:24px;font-weight:800;margin-bottom:6px;letter-spacing:-.01em;position:relative;z-index:1}.welcome-sub{font-size:14px;opacity:.85;font-weight:400;position:relative;z-index:1}.approval-banner{background:var(--warning-soft);border:1px solid rgba(245,158,11,.15);border-radius:var(--radius);padding:18px 22px;margin-bottom:24px;display:flex;align-items:center;gap:16px}.approval-banner-icon{font-size:22px;flex-shrink:0}.approval-banner-content{flex:1}.approval-banner-title{font-weight:600;font-size:14px;margin-bottom:3px}.approval-banner-detail{font-size:13px;color:var(--text-dim);font-family:var(--mono)}.approval-banner-actions{display:flex;gap:8px}.approval-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px 24px;margin-bottom:10px;display:flex;align-items:center;gap:16px;box-shadow:var(--shadow-sm)}.approval-card-icon{width:44px;height:44px;border-radius:var(--radius-sm);background:var(--warning-soft);color:var(--warning);display:flex;align-items:center;justify-content:center;flex-shrink:0}.approval-card-content{flex:1;min-width:0}.approval-card-title{font-weight:700;font-size:15px;color:var(--text);margin-bottom:4px}.approval-card-detail{font-size:13px;color:var(--text-dim);display:flex;align-items:center;gap:6px;flex-wrap:wrap}.approval-cmd{font-family:var(--mono);font-size:12px;background:var(--surface-alt);border:1px solid var(--border);border-radius:4px;padding:2px 8px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:480px}.approval-card-cwd{font-size:11px;color:var(--text-muted);font-family:var(--mono);margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.approval-card-time{font-family:var(--mono);font-size:14px;color:var(--warning);font-weight:600;flex-shrink:0;min-width:40px;text-align:right}.approval-card-actions{display:flex;gap:8px;flex-shrink:0}.approval-btn-deny{padding:8px 18px;font-size:13px;font-weight:600;border-radius:var(--radius-sm);border:1.5px solid var(--border);background:var(--surface);color:var(--text);cursor:pointer;transition:all .12s ease}.approval-btn-deny:hover{background:var(--surface-alt);border-color:var(--text-muted)}.approval-btn-deny:disabled{opacity:.5;cursor:not-allowed}.approval-btn-approve{padding:8px 20px;font-size:13px;font-weight:600;border-radius:var(--radius-sm);border:none;background:var(--accent);color:#fff;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:all .12s ease}.approval-btn-approve:hover{background:var(--accent-hover)}.approval-btn-approve:disabled{opacity:.5;cursor:not-allowed}.approvals-page{display:flex;flex-direction:column;gap:0}.appr-auto-refresh{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:12px;color:var(--text-muted);border:1px solid var(--border);border-radius:999px;padding:5px 14px}.appr-auto-dot{width:7px;height:7px;border-radius:50%;background:var(--success);display:inline-block}.appr-section-label{font-size:11px;font-weight:700;letter-spacing:.06em;color:var(--text-muted);text-transform:uppercase;margin-bottom:12px;margin-top:8px}.appr-list{display:flex;flex-direction:column;gap:14px}.appr-item{display:flex;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm)}.appr-item-border{width:4px;flex-shrink:0}.appr-item-border.pending{background:var(--warning)}.appr-item-border.approved{background:var(--success)}.appr-item-border.denied{background:var(--error)}.appr-item-border.expired{background:var(--text-muted)}.appr-item-body{flex:1;padding:16px 20px;display:flex;flex-direction:column;gap:10px;min-width:0}.appr-item-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.appr-item-icon{width:36px;height:36px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;flex-shrink:0}.appr-item-icon.pending{background:var(--warning-soft);color:var(--warning)}.appr-item-icon.approved{background:var(--success-soft);color:var(--success)}.appr-item-icon.denied{background:var(--error-soft);color:var(--error)}.appr-item-icon.expired{background:var(--surface-alt);color:var(--text-muted)}.appr-item-id{font-family:var(--mono);font-weight:600;font-size:14px;color:var(--text)}.appr-chip{display:inline-block;font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.04em;padding:2px 8px;border-radius:4px;text-transform:uppercase}.appr-chip.tier{background:var(--surface-alt);color:var(--text-dim)}.appr-chip.tier.tier-1{background:var(--info-soft);color:var(--info)}.appr-chip.tier.tier-2{background:var(--warning-soft);color:var(--warning)}.appr-chip.tier.tier-3{background:var(--error-soft);color:var(--error)}.appr-chip.pending{background:var(--warning-soft);color:var(--warning)}.appr-chip.approved{background:var(--success-soft);color:var(--success)}.appr-chip.denied{background:var(--error-soft);color:var(--error)}.appr-chip.expired{background:var(--surface-alt);color:var(--text-muted)}.appr-tier-legend{margin-top:10px;display:grid;grid-template-columns:auto 1fr;gap:6px 10px;align-items:center}.appr-tier-label{font-size:12px;color:var(--text-dim)}.appr-elapsed{display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:13px;color:var(--warning);font-weight:600}.appr-actions{display:flex;gap:8px}.appr-reason{font-size:14px;color:var(--text-dim)}.appr-cmd-block{font-family:var(--mono);font-size:13px;background:var(--surface-alt);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 14px;color:var(--text);overflow-x:auto;white-space:pre}.appr-footer{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--text-muted)}.appr-footer-item{display:inline-flex;align-items:center;gap:4px;font-family:var(--mono)}.appr-footer-sep{color:var(--border)}.appr-footer-resolution{font-style:italic;color:var(--text-muted)}.spinner{width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:48px 24px;color:var(--text-muted)}.empty-state-icon{font-size:36px;margin-bottom:12px}.empty-state-text{font-size:14px}.markdown-view{font-size:14px;line-height:1.55;color:var(--text-dim)}.markdown-view h1,.markdown-view h2,.markdown-view h3,.markdown-view h4,.markdown-view h5,.markdown-view h6{font-family:var(--serif);color:var(--text);font-weight:800;margin:0 0 8px}.markdown-view p{margin:0 0 7px}.markdown-view ul,.markdown-view ol{padding-left:20px;margin:0 0 7px}.markdown-view code{font-family:var(--mono);background:var(--surface-alt);padding:1px 4px;border-radius:4px;font-size:12px}.markdown-view pre{margin:0 0 9px;background:var(--surface-alt);border:1px solid var(--border-light);border-radius:var(--radius-sm);padding:12px;overflow:auto}.markdown-view pre code{background:transparent;padding:0}.markdown-view blockquote{border-left:3px solid var(--accent);padding-left:10px;margin:0 0 9px;color:var(--text-muted)}.chat-list{display:flex;flex-direction:column;gap:10px}.chat-row{display:flex;flex-direction:column}.chat-row.user{align-items:flex-end}.chat-row.assistant{align-items:flex-start}.chat-meta{font-size:11px;color:var(--text-muted);font-family:var(--mono);margin-bottom:4px}.chat-bubble{max-width:82%;padding:10px 12px;border-radius:12px;font-size:14px;line-height:1.5;border:1px solid var(--border-light)}.chat-bubble.user{background:var(--accent-soft);color:var(--text)}.chat-bubble.assistant{background:var(--surface-alt);color:var(--text-dim)}.chat-bubble .markdown-view p:last-child,.chat-bubble .markdown-view ul:last-child,.chat-bubble .markdown-view ol:last-child,.chat-bubble .markdown-view pre:last-child,.chat-bubble .markdown-view blockquote:last-child{margin-bottom:0}.messages-page{height:calc(100vh - 118px);display:flex;flex-direction:column}.messages-chat-card{flex:1;height:100%;min-height:0;display:flex;flex-direction:column;margin-bottom:0}.messages-chat-list{flex:1;min-height:0;overflow-y:auto;padding-right:4px;padding-bottom:8px;-ms-overflow-style:none;scrollbar-width:none}.messages-chat-list::-webkit-scrollbar{width:0;height:0;display:none}.messages-composer-inline{margin-top:auto;padding-top:12px;border-top:1px solid var(--border-light)}.messages-filler{flex:1;display:flex;align-items:center;justify-content:center}.card{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:20px 22px;margin-bottom:14px;box-shadow:var(--shadow-sm)}.card-title{font-size:14px;color:var(--text);margin-bottom:8px;font-weight:500}.toolbar{display:flex;gap:8px;align-items:center;margin-bottom:12px;flex-wrap:wrap}.agents-layout{display:grid;grid-template-columns:minmax(260px,340px) minmax(0,1fr);gap:16px;align-items:start}.agents-list{display:grid;gap:8px}.agent-profile-item{width:100%;display:grid;grid-template-columns:34px minmax(0,1fr) auto;align-items:center;gap:10px;padding:10px;border:1px solid var(--border-light);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);cursor:pointer;text-align:left}.agent-profile-item:hover,.agent-profile-item.active{border-color:var(--accent);background:var(--surface-hover)}.agent-profile-icon{width:34px;height:34px;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;background:var(--accent-soft);color:var(--accent);font-size:14px;font-weight:700}.agent-profile-main{min-width:0;display:grid;gap:2px}.agent-profile-alias{font-weight:700;color:var(--text)}.agent-profile-meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-muted);font-family:var(--mono);font-size:11px}.agent-profile-count{min-width:22px;height:22px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;background:var(--surface-alt);color:var(--text-muted);font-family:var(--mono);font-size:11px}.agents-empty{border:1px solid var(--border-light);border-radius:var(--radius);background:var(--surface)}.agents-editor{margin-bottom:0}.agents-editor-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:14px}.agent-editor-subtitle{color:var(--text-muted);font-size:12px}.agent-inherit-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:10px 0 14px;color:var(--text-dim);font-size:12px}.agent-inherit-row code{padding:3px 7px;border-radius:999px;background:var(--surface-alt);color:var(--text-muted);font-family:var(--mono);font-size:11px}.agent-model-aliases{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0 14px}.agents-prompt{min-height:220px;font-family:var(--mono)}.split{display:grid;grid-template-columns:320px 1fr;gap:18px;height:calc(100vh - 180px)}.split-list{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);overflow-y:auto;box-shadow:var(--shadow-sm)}.split-detail{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);overflow-y:auto;padding:24px;box-shadow:var(--shadow-sm)}.list-item{padding:16px 20px;border-bottom:1px solid var(--border-light);cursor:pointer;transition:background .12s ease}.list-item:last-child{border-bottom:none}.list-item:hover{background:var(--surface-alt)}.list-item.active{background:var(--accent-soft-2);border-left:3px solid var(--accent)}.list-item-title{font-weight:600;font-size:14px;margin-bottom:3px;color:var(--text)}.list-item-sub,.list-item-meta{font-size:12px;color:var(--text-dim);font-family:var(--mono)}.audit-entry{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:16px 18px;margin-bottom:10px}.audit-header{display:flex;justify-content:space-between;align-items:start;gap:10px;margin-bottom:8px}.audit-id{font-family:var(--mono);font-size:12px;color:var(--accent);font-weight:600}.audit-meta{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text-muted);font-family:var(--mono)}.audit-summary{font-size:13px;color:var(--text-dim);line-height:1.5}.audit-page{display:flex;flex-direction:column;gap:12px}.audit-header-meta{font-family:var(--mono);font-size:11px;color:var(--text-muted)}.audit-top-stats{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:10px 14px;display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}.audit-stat-left{display:flex;align-items:baseline;gap:8px}.audit-big{font-family:var(--serif);font-size:20px;line-height:1}.audit-big.ok{color:var(--success)}.audit-big.error{color:var(--error)}.audit-big.running{color:var(--warning)}.audit-stat-label{font-size:11px;color:var(--text-muted);font-weight:600}.audit-stat-triggers{display:flex;gap:8px;flex-wrap:wrap}.audit-filters-row{display:flex;flex-direction:column;gap:8px;align-items:stretch}.audit-search{width:100%;min-width:0;max-width:none;display:inline-flex;align-items:center;gap:8px;border:1px solid var(--border-light);background:var(--surface);color:var(--text-muted);border-radius:14px;padding:10px 14px;min-height:56px}.audit-search input{border:none;background:transparent;color:var(--text);width:100%;min-width:120px;font-size:11px;font-weight:500}.audit-search input:focus{outline:none}.audit-search svg{width:20px;height:20px}.audit-filter-controls{display:flex;gap:8px;align-items:center}.audit-filter-controls>.btn.btn-sm{min-height:32px;height:32px;min-width:160px;width:auto;border-radius:10px;font-size:12px;font-weight:500;padding:5px 12px}.audit-timeline{display:flex;flex-direction:column;gap:8px}.audit-row{display:grid;grid-template-columns:56px 1fr;gap:12px}.audit-time-col{display:flex;flex-direction:column;align-items:center;color:var(--text-muted)}.audit-time{font-family:var(--mono);font-size:10px;margin-top:12px}.audit-time-line{width:1px;flex:1;min-height:40px;background:var(--border);margin-top:8px}.audit-card{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:12px 14px}.audit-card-head{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:6px}.audit-chip-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}.audit-chip{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--border-light);border-radius:999px;padding:2px 8px;font-size:9px;line-height:1.4;font-family:var(--mono);color:var(--text-muted);text-transform:uppercase;letter-spacing:.01em}.audit-chip strong{color:var(--text-dim);font-weight:600}.audit-chip.muted{background:var(--surface-alt)}.audit-chip.id{text-transform:none;letter-spacing:0}.audit-chip.trigger.telegram{background:var(--chip-telegram-bg);color:var(--chip-telegram);border-color:transparent}.audit-chip.trigger.discord{background:var(--chip-discord-bg);color:var(--chip-discord);border-color:transparent}.audit-chip.trigger.cron{background:var(--chip-cron-bg);color:var(--chip-cron);border-color:transparent}.audit-chip.trigger.system,.audit-chip.trigger.gateway,.audit-chip.trigger.dashboard{background:var(--surface-alt);border-color:transparent;color:var(--text-dim)}.audit-chip.status.success{background:var(--success-soft);color:var(--success);border-color:transparent}.audit-chip.status.error{background:var(--error-soft);color:var(--error);border-color:transparent}.audit-chip.status.running{background:var(--warning-soft);color:var(--warning);border-color:transparent}.audit-chip.soft{background:var(--surface-alt);text-transform:none}.audit-duration{font-family:var(--mono);font-size:10px;color:var(--text-muted);white-space:nowrap}.audit-title-line{font-size:14px;line-height:1.35;color:var(--text-dim);margin-bottom:8px}.audit-event-pills{display:flex;gap:6px;flex-wrap:wrap}.audit-load-more-wrap{text-align:center;margin-top:8px}.audit-card-expandable:hover{border-color:var(--border)}.audit-card-head-right{display:flex;align-items:center;gap:6px}.audit-expand-icon{color:var(--text-muted);display:flex;align-items:center}.audit-detail-section{margin-top:10px;border-top:1px solid var(--border-light);padding-top:8px;display:flex;flex-direction:column;gap:0}.audit-event-row{display:grid;grid-template-columns:90px 1fr auto;gap:8px;align-items:baseline;padding:5px 2px;border-bottom:1px solid var(--border-light)}.audit-event-row:last-child{border-bottom:none}.audit-event-type-label{font-family:var(--mono);font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.audit-event-type-label.tool{color:var(--accent, #7c6af0)}.audit-event-content{font-size:12px;color:var(--text-dim);line-height:1.4;word-break:break-word;overflow:hidden}.audit-event-content.mono{font-family:var(--mono);font-size:11px}.audit-event-dur{font-family:var(--mono);font-size:10px;color:var(--text-muted);white-space:nowrap;text-align:right}.audit-event-detail{display:flex;flex-direction:column;gap:4px}.audit-event-detail-label{font-family:var(--mono);font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em}.audit-json-block{background:var(--surface-alt);border:1px solid var(--border-light);border-radius:var(--radius-sm);padding:10px 12px;font-family:var(--mono);font-size:11px;line-height:1.6;color:var(--text-dim);white-space:pre;overflow-x:auto;-moz-tab-size:2;tab-size:2}.audit-kv-list{display:flex;flex-direction:column;gap:2px;margin:0;padding:8px 10px;background:var(--surface-alt);border:1px solid var(--border-light);border-radius:var(--radius-sm)}.audit-kv-row{display:grid;grid-template-columns:minmax(80px,28%) 1fr;gap:8px;align-items:baseline;min-height:20px}.audit-kv-row dt,.audit-kv-key{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.audit-kv-row dd,.audit-kv-val{font-family:var(--mono);font-size:11px;color:var(--text-dim);margin:0;word-break:break-word}.audit-kv-inline{font-family:var(--mono);font-size:11px;color:var(--text-muted);background:transparent;padding:0;white-space:pre-wrap;word-break:break-all}.audit-detail-text{font-family:var(--mono);font-size:11px;line-height:1.6;color:var(--text-dim);padding:8px 12px;background:var(--surface-alt);border:1px solid var(--border-light);border-radius:var(--radius-sm);white-space:pre-wrap;word-break:break-word;display:block}@media (max-width: 900px){.audit-row{grid-template-columns:1fr}.audit-time-col{align-items:flex-start;flex-direction:row;gap:8px}.audit-time-line{display:none}.audit-filter-controls{flex-direction:column;align-items:stretch}.audit-filter-controls>.btn.btn-sm{width:100%}}.coding-page{--coding-accent: #ad5038;--coding-accent-soft: rgba(173, 80, 56, .08)}.coding-page .page-header{margin-bottom:18px}.coding-refresh-pill{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border:1px solid var(--border-light);border-radius:999px;background:var(--surface-alt);color:var(--text-dim);font-size:13px;font-family:var(--sans);font-weight:500;box-shadow:var(--shadow-sm);line-height:1}.coding-refresh-pill .dot{width:6px;height:6px;border-radius:50%;background:var(--success)}.coding-page .btn{display:inline-flex;align-items:center;gap:6px}.coding-body{display:flex;flex-direction:column;gap:14px}.coding-stats-grid{display:flex;gap:12px;margin-bottom:6px;flex-wrap:wrap}.coding-stat-card{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:14px 20px;display:flex;align-items:center;gap:10px;min-width:140px;flex:1}.coding-stat-icon{width:38px;height:38px;border-radius:var(--radius-sm);display:inline-flex;align-items:center;justify-content:center}.coding-stat-icon svg{width:18px;height:18px}.coding-stat-content{display:flex;flex-direction:column;justify-content:center;gap:0;min-width:0}.coding-stat-icon.running{background:var(--coding-accent-soft);color:var(--coding-accent)}.coding-stat-icon.completed{background:var(--success-soft);color:var(--success)}.coding-stat-icon.failed{background:var(--error-soft);color:var(--error)}.coding-stat-icon.cost{background:var(--warning-soft);color:var(--warning)}.coding-stat-value{font-family:var(--serif);font-size:18px;font-weight:500;line-height:1;color:var(--text)}.coding-stat-label{font-size:10px;color:var(--text-muted);font-weight:400;margin-top:2px;line-height:1.1}.coding-task-card{background:var(--surface);border:1px solid var(--border-light);border-top-width:3px;border-top-color:var(--card-accent, var(--coding-accent));border-radius:var(--radius);padding:0;overflow:hidden;box-shadow:var(--shadow-sm)}.coding-task-main{padding:18px 22px 0;display:grid;grid-template-columns:36px 1fr auto;gap:14px;align-items:start}.coding-task-icon{width:36px;height:36px;border-radius:var(--radius-sm);display:inline-flex;align-items:center;justify-content:center;margin-top:2px}.coding-task-icon.running{background:var(--coding-accent-soft);color:var(--coding-accent)}.coding-task-card.running .coding-task-icon.running{background:var(--card-accent-soft, var(--coding-accent-soft));color:var(--card-accent, var(--coding-accent))}.coding-task-icon.completed{background:var(--success-soft);color:var(--success)}.coding-task-icon.failed{background:var(--error-soft);color:var(--error)}.coding-task-icon.pending{background:var(--pending-soft);color:var(--pending)}.coding-task-content{min-width:0}.coding-task-pills{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px}.coding-pill{display:inline-flex;align-items:center;border:1px solid var(--border-light);background:var(--surface-alt);color:var(--text-muted);border-radius:999px;padding:2px 8px;font-size:9px;text-transform:uppercase;letter-spacing:.04em;font-family:var(--sans);font-weight:500}.coding-pill.id{border:none;background:transparent;color:var(--card-accent, var(--coding-accent));padding:0;font-size:12px;letter-spacing:0;text-transform:none;font-family:var(--mono);font-weight:500}.coding-pill.model,.coding-pill.effort{text-transform:none;letter-spacing:0;font-family:var(--mono);max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.coding-pill.effort,.coding-pill.status.running{color:var(--card-accent, var(--coding-accent));background:var(--card-accent-soft, var(--coding-accent-soft));border-color:transparent}.coding-pill.status.completed{color:var(--success);background:var(--success-soft);border-color:transparent}.coding-pill.status.failed{color:var(--error);background:var(--error-soft);border-color:transparent}.coding-pill.status.pending{color:var(--pending)}.coding-task-title{margin-top:0;font-size:13px;color:var(--text);font-weight:400;line-height:1.5}.coding-task-submeta{padding:10px 0 16px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;font-size:11px;color:var(--text-muted);font-family:var(--mono)}.coding-task-submeta span{display:inline-flex;align-items:center;gap:6px}.coding-task-action{align-self:flex-start;border:1px solid transparent;background:transparent;color:var(--text-dim);border-radius:999px;font-size:11px;font-weight:500;padding:6px 14px;display:inline-flex;align-items:center;gap:6px}.coding-task-action.running{color:var(--card-accent, var(--coding-accent));border-color:color-mix(in srgb,var(--card-accent, var(--coding-accent)) 25%,transparent);background:color-mix(in srgb,var(--card-accent, var(--coding-accent)) 8%,transparent)}.coding-task-action.failed{color:var(--error);border-color:#c254504d;background:#c254500a}.coding-task-progress{height:1px;background:var(--border-light)}.coding-task-progress.running{background:linear-gradient(to right,var(--card-accent, var(--coding-accent)) 56%,var(--border-light) 56%)}.coding-task-progress.failed{background:linear-gradient(to right,var(--error) 32%,var(--border-light) 32%)}.coding-task-progress.completed{background:linear-gradient(to right,var(--success) 100%,var(--success) 100%)}.coding-task-progress.pending{background:linear-gradient(to right,var(--pending) 20%,var(--border-light) 20%)}.coding-subagents,.coding-output{border-top:1px solid var(--border-light);padding:8px 22px}.coding-subagents>summary,.coding-output>summary{list-style:none;cursor:pointer;font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.04em;font-weight:500;display:inline-flex;align-items:center;gap:6px}.coding-subagents>summary::-webkit-details-marker,.coding-output>summary::-webkit-details-marker{display:none}.coding-subagents-list{margin-top:10px;border-left:2px solid var(--border);padding-left:16px;margin-left:8px}.coding-subagent-row{margin-top:8px;border:1px solid var(--border-light);background:var(--surface-alt);border-radius:var(--radius-sm);padding:12px 14px;display:grid;grid-template-columns:40px auto auto auto 1fr auto;gap:8px;align-items:center}.coding-subagent-icon{width:24px;height:24px;border-radius:6px;display:inline-flex;align-items:center;justify-content:center}.coding-subagent-icon.running{background:var(--card-accent-soft, var(--coding-accent-soft));color:var(--card-accent, var(--coding-accent))}.coding-subagent-icon.completed{background:var(--success-soft);color:var(--success)}.coding-subagent-icon.failed{background:var(--error-soft);color:var(--error)}.coding-subagent-icon.pending{background:var(--pending-soft);color:var(--pending)}.coding-subagent-task{font-size:12px;color:var(--text-dim);line-height:1.45;font-weight:400}.coding-subagent-time{font-size:11px;color:var(--text-muted);font-family:var(--mono);display:inline-flex;align-items:center;gap:3px}.coding-subagent-card{margin-top:8px;border:1px solid var(--border-light);background:var(--surface-alt);border-radius:var(--radius-sm);overflow:hidden;transition:border-color .15s}.coding-subagent-card.running{border-left:2px solid var(--card-accent, var(--coding-accent))}.coding-subagent-card.completed{border-left:2px solid var(--success)}.coding-subagent-card.failed{border-left:2px solid var(--error)}.coding-subagent-card.pending{border-left:2px solid var(--border);opacity:.7}.coding-subagent-header{padding:10px 14px;display:flex;align-items:center;gap:8px;cursor:pointer;list-style:none;font-size:12px}.coding-subagent-header::-webkit-details-marker{display:none}.coding-subagent-chevron{flex-shrink:0;transition:transform .15s;color:var(--text-muted)}.coding-subagent-card[open]>.coding-subagent-header .coding-subagent-chevron{transform:rotate(0)}.coding-subagent-card:not([open])>.coding-subagent-header .coding-subagent-chevron{transform:rotate(-90deg)}.coding-subagent-body{padding:0 14px 12px;border-top:1px solid var(--border-light)}.coding-subagent-body .coding-subagent-task{padding:8px 0 4px}.ca-subagent-output{max-height:300px;overflow-y:auto;font-size:12px;padding:8px;background:#0000001a;border-radius:var(--radius-sm);margin-top:6px}.ca-status-badge{display:inline-flex;align-items:center;padding:3px 10px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.ca-status-badge.running{background:var(--accent-soft);color:var(--accent)}.ca-status-badge.validating{background:var(--warning-soft);color:var(--warning)}.ca-status-badge.completed{background:var(--success-soft);color:var(--success)}.ca-status-badge.failed,.ca-status-badge.timeout,.ca-status-badge.cancelled{background:var(--error-soft);color:var(--error)}.tree-container{display:flex;flex-direction:column;gap:8px}.ca-tree{margin-bottom:12px}.ca-tree-children{margin:8px 0 0 18px;padding-left:14px;border-left:2px solid var(--border-light)}.ca-tree-child{background:var(--surface-alt);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;margin-bottom:8px}.ca-output{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius-sm);padding:10px 12px;font-size:12px;line-height:1.6;max-height:260px;overflow-y:auto;margin-top:8px}.ca-output.markdown-content{font-family:var(--sans)}.ca-output.markdown-content code,.ca-output.markdown-content pre{font-family:var(--mono)}.ca-error{background:var(--error-soft);border-color:#c2545033;color:var(--error)}.usage-stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-bottom:28px}.usage-table{width:100%;border-collapse:collapse;font-size:13px}.usage-table th{background:var(--surface-alt);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted);padding:10px 14px;text-align:left;border-bottom:1px solid var(--border-light)}.usage-table td{padding:10px 14px;border-bottom:1px solid var(--border-light);color:var(--text-dim);font-family:var(--mono);font-size:12px}.usage-table tr:last-child td{border-bottom:none}.usage-table tr:hover td{background:var(--surface-alt)}.usage-cost-cell{font-weight:600;color:var(--text)}.usage-model-pill{font-family:var(--mono);font-size:11px;background:var(--surface-alt);border:1px solid var(--border-light);border-radius:4px;padding:2px 6px;color:var(--text)}.digest-header{margin-bottom:14px}.digest-title{font-family:var(--serif);font-size:24px;font-weight:700;line-height:1.2}.digest-meta{font-size:12px;color:var(--text-muted);font-family:var(--mono);margin-top:4px}.digest-reader{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:16px 18px;word-break:break-word;line-height:1.65;font-size:14px;color:var(--text-dim)}.digest-articles{padding:6px 0}.digest-article-card{padding:12px 0;border-bottom:1px solid var(--border-light)}.digest-article-card:last-child{border-bottom:none}.digest-article-card .source-badge{font-size:11px;color:var(--accent);font-weight:600;text-transform:uppercase;letter-spacing:.04em}.digest-article-card .article-title{display:inline-block;font-size:15px;font-weight:600;color:var(--text);margin-top:4px;text-decoration:none}.digest-article-card .article-title:hover{color:var(--accent);text-decoration:underline}.digest-article-card .article-stats{font-size:12px;color:var(--text-muted);margin-top:5px}.skills-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}.skill-card{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);padding:18px 20px;box-shadow:var(--shadow-sm)}.skill-header{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:8px}.skill-name{font-weight:600;font-size:15px}.skill-desc{font-size:13px;color:var(--text-dim);line-height:1.5;margin-bottom:10px}.skill-tags{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px}.skill-tag{font-size:11px;font-family:var(--mono);padding:2px 8px;border-radius:999px;background:var(--surface-alt);color:var(--text-muted);border:1px solid var(--border-light)}.markdown-content{line-height:1.65;color:var(--text-dim)}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{font-family:var(--serif);color:var(--text);font-weight:800;margin:16px 0 10px;line-height:1.3}.markdown-content h1:first-child,.markdown-content h2:first-child,.markdown-content h3:first-child,.markdown-content h4:first-child,.markdown-content h5:first-child,.markdown-content h6:first-child{margin-top:0}.markdown-content h1{font-size:22px}.markdown-content h2{font-size:19px}.markdown-content h3{font-size:17px}.markdown-content h4{font-size:15px}.markdown-content h5{font-size:14px}.markdown-content h6{font-size:13px}.markdown-content p{margin:0 0 10px}.markdown-content p:last-child{margin-bottom:0}.markdown-content ul,.markdown-content ol{padding-left:20px;margin:0 0 10px}.markdown-content li{margin-bottom:4px}.markdown-content a{color:var(--accent);text-decoration:none}.markdown-content a:hover{text-decoration:underline}.markdown-content code{font-family:var(--mono);background:var(--surface-alt);padding:2px 6px;border-radius:4px;font-size:12px;color:var(--text)}.markdown-content pre{margin:0 0 12px;background:var(--surface-alt);border:1px solid var(--border-light);border-radius:var(--radius-sm);padding:12px;overflow:auto}.markdown-content pre code{background:transparent;padding:0;font-size:12px}.markdown-content blockquote{border-left:3px solid var(--accent);padding-left:12px;margin:0 0 12px;color:var(--text-muted);font-style:italic}.markdown-content table{width:100%;border-collapse:collapse;margin:0 0 12px;font-size:13px}.markdown-content th,.markdown-content td{border:1px solid var(--border-light);padding:8px 10px;text-align:left}.markdown-content th{background:var(--surface-alt);font-weight:600;color:var(--text)}.markdown-content td{color:var(--text-dim)}.markdown-content hr{border:none;border-top:1px solid var(--border-light);margin:16px 0}.markdown-content strong,.markdown-content b{font-weight:600;color:var(--text)}.markdown-content em,.markdown-content i{font-style:italic}.templates-page{display:flex;flex-direction:column;gap:0}.templates-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:28px;position:relative;z-index:1;padding-right:72px}.templates-loading{display:flex;justify-content:center;align-items:center;padding:64px}.templates-layout{display:grid;grid-template-columns:280px 1fr;gap:24px;align-items:stretch;min-height:600px}.templates-sidebar{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);box-shadow:var(--shadow-sm);display:flex;flex-direction:column;height:100%}.templates-sidebar-header{padding:20px 20px 16px;border-bottom:1px solid var(--border-light)}.templates-sidebar-label{font-size:11px;font-weight:700;letter-spacing:.06em;color:var(--text-muted);text-transform:uppercase;margin-bottom:10px}.templates-agent-select{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-alt);color:var(--text-dim);font-family:var(--sans);font-size:13px;font-weight:500;cursor:pointer;outline:none}.templates-agent-select:disabled{opacity:.6;cursor:not-allowed}.templates-list{display:flex;flex-direction:column;flex:1;overflow-y:auto}.templates-list-item{display:flex;align-items:center;gap:12px;padding:14px 20px;border:none;border-bottom:1px solid var(--border-light);background:transparent;cursor:pointer;transition:background .15s ease;text-align:left;width:100%;position:relative}.templates-list-item:last-child{border-bottom:none}.templates-list-item:hover{background:var(--surface-alt)}.templates-list-item.active{background:var(--accent-soft-2)}.templates-list-item.active:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent)}.templates-list-icon{width:32px;height:32px;border-radius:var(--radius-sm);background:var(--surface-alt);display:flex;align-items:center;justify-content:center;color:var(--text-dim);flex-shrink:0;font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:.02em}.templates-list-item.active .templates-list-icon{background:var(--accent-soft);color:var(--accent)}.templates-list-content{flex:1;min-width:0}.templates-list-title{font-size:13px;font-weight:500;color:var(--text);margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.templates-list-item.active .templates-list-title{color:var(--accent);font-weight:600}.templates-list-meta{font-size:11px;font-family:var(--mono);color:var(--text-muted)}.templates-main{background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius);box-shadow:var(--shadow-sm);display:flex;flex-direction:column;height:100%}.templates-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 32px;color:var(--text-muted);gap:12px}.templates-empty p{font-size:14px;margin:0}.templates-editor-header{display:flex;justify-content:space-between;align-items:center;padding:20px 28px;border-bottom:1px solid var(--border-light);gap:16px}.templates-editor-title-row{display:flex;align-items:center;gap:12px;flex:1;min-width:0}.templates-editor-title{font-family:var(--serif);font-size:20px;font-weight:700;color:var(--text);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.templates-unsaved-pill{display:inline-flex;align-items:center;padding:4px 12px;border-radius:999px;background:var(--warning-soft);color:var(--warning);font-size:11px;font-weight:600;letter-spacing:.02em;white-space:nowrap}.templates-editor-actions{display:flex;gap:10px;align-items:center}.templates-discard-btn{padding:8px 16px;border:none;border-radius:var(--radius-sm);background:transparent;color:var(--text-dim);font-family:var(--sans);font-weight:500;font-size:13px;cursor:pointer;transition:all .15s ease}.templates-discard-btn:hover{color:var(--text);background:var(--surface-alt)}.templates-save-btn{padding:8px 20px;border:none;border-radius:var(--radius-sm);background:var(--accent);color:#fff;font-family:var(--sans);font-weight:600;font-size:13px;cursor:pointer;transition:all .15s ease}.templates-save-btn:hover{background:var(--accent-hover)}.templates-save-btn:disabled{opacity:.5;cursor:not-allowed}.templates-tabs{display:flex;gap:0;padding:0 28px;border-bottom:1px solid var(--border-light)}.templates-tab{padding:12px 20px;border:none;background:transparent;color:var(--text-muted);font-family:var(--sans);font-weight:500;font-size:13px;cursor:pointer;position:relative;transition:color .15s ease}.templates-tab:hover{color:var(--text-dim)}.templates-tab.active{color:var(--accent)}.templates-tab.active:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--accent)}.templates-metadata{display:grid;grid-template-columns:auto auto auto 1fr;gap:24px;padding:16px 28px;border-bottom:1px solid var(--border-light);background:var(--surface-alt)}.templates-metadata-item{display:flex;flex-direction:column;gap:4px}.templates-metadata-label{font-size:10px;font-weight:700;letter-spacing:.06em;color:var(--text-muted);text-transform:uppercase}.templates-metadata-value{font-size:12px;font-family:var(--mono);color:var(--text-dim);font-weight:500}.templates-metadata-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.templates-content{flex:1;display:flex;flex-direction:column;overflow:hidden}.templates-textarea{flex:1;width:100%;min-height:500px;padding:24px 28px;border:none;background:var(--surface);color:var(--text);font-family:var(--mono);font-size:13px;line-height:1.65;resize:none;outline:none}.templates-markdown-preview{flex:1;padding:28px 32px;overflow-y:auto}.templates-markdown-preview .markdown-view{max-width:800px;margin:0 auto}.templates-markdown-preview .markdown-view h1,.templates-markdown-preview .markdown-view h2,.templates-markdown-preview .markdown-view h3,.templates-markdown-preview .markdown-view h4,.templates-markdown-preview .markdown-view h5,.templates-markdown-preview .markdown-view h6{font-family:var(--serif);color:var(--text);font-weight:800;margin:16px 0 8px;line-height:1.3}.templates-markdown-preview .markdown-view h1:first-child,.templates-markdown-preview .markdown-view h2:first-child,.templates-markdown-preview .markdown-view h3:first-child{margin-top:0}.templates-markdown-preview .markdown-view h1{font-size:28px;padding-bottom:8px;border-bottom:1px solid var(--border-light)}.templates-markdown-preview .markdown-view h2{font-size:22px}.templates-markdown-preview .markdown-view h3{font-size:18px}.templates-markdown-preview .markdown-view p{margin:0 0 8px;line-height:1.58}.templates-markdown-preview .markdown-view blockquote{border-left:4px solid var(--accent);padding-left:16px;margin:10px 0;color:var(--text-dim);font-style:italic}.templates-markdown-preview .markdown-view hr{border:none;border-top:1px solid var(--border-light);margin:18px 0}.templates-markdown-preview .markdown-view code{font-family:var(--mono);background:var(--surface-alt);padding:2px 6px;border-radius:4px;font-size:12px;color:var(--text)}.templates-markdown-preview .markdown-view pre{background:var(--surface-alt);border:1px solid var(--border-light);border-radius:var(--radius-sm);padding:12px;margin:10px 0;overflow-x:auto}.templates-markdown-preview .markdown-view pre code{background:transparent;padding:0}[data-theme=dark] .split-list,[data-theme=dark] .split-detail,[data-theme=dark] .templates-list,[data-theme=dark] .templates-markdown-preview,[data-theme=dark] .ca-output{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.12) transparent}[data-theme=dark] .split-list::-webkit-scrollbar,[data-theme=dark] .split-detail::-webkit-scrollbar,[data-theme=dark] .templates-list::-webkit-scrollbar,[data-theme=dark] .templates-markdown-preview::-webkit-scrollbar,[data-theme=dark] .ca-output::-webkit-scrollbar{width:6px}[data-theme=dark] .split-list::-webkit-scrollbar-thumb,[data-theme=dark] .split-detail::-webkit-scrollbar-thumb,[data-theme=dark] .templates-list::-webkit-scrollbar-thumb,[data-theme=dark] .templates-markdown-preview::-webkit-scrollbar-thumb,[data-theme=dark] .ca-output::-webkit-scrollbar-thumb{background:#ffffff1f;border-radius:3px}[data-theme=dark] .split-list::-webkit-scrollbar-track,[data-theme=dark] .split-detail::-webkit-scrollbar-track,[data-theme=dark] .templates-list::-webkit-scrollbar-track,[data-theme=dark] .templates-markdown-preview::-webkit-scrollbar-track,[data-theme=dark] .ca-output::-webkit-scrollbar-track{background:transparent}@media (max-width: 980px){.shell{grid-template-columns:1fr}.shell.shell-narrow .sidebar{position:fixed;top:0;left:0;height:100vh;width:min(86vw,320px);border-right:1px solid var(--border-light);box-shadow:var(--shadow-md);transform:translate(-104%);transition:transform .2s ease;z-index:210}.shell.shell-narrow.sidebar-open .sidebar{transform:translate(0)}.global-sidebar-toggle{position:fixed;top:20px;left:16px;width:37px;height:37px;border:1px solid var(--border);background:var(--surface);border-radius:var(--radius-sm);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:all .15s ease;box-shadow:var(--shadow-sm);color:var(--text);z-index:220}.global-sidebar-toggle:hover{background:var(--surface-hover);box-shadow:var(--shadow)}.sidebar-backdrop{display:block;position:fixed;top:0;right:0;bottom:0;left:0;border:none;padding:0;background:#00000042;z-index:205}.main{padding:20px 16px}.page-header{padding-left:56px;min-height:40px}.global-theme-toggle{top:20px;right:16px;z-index:220}.page-header .header-actions{margin-right:62px;flex-wrap:wrap}.split{grid-template-columns:1fr;height:auto}.split-list{max-height:280px}.stats-grid,.stats-grid-5,.stats-grid-6,.usage-stats-grid{grid-template-columns:1fr 1fr}.stat-card{min-height:116px;padding:18px;gap:12px}.stat-icon{width:44px;height:44px}.stat-icon svg{width:18px;height:18px}.stat-value{font-size:24px}.stat-value-md{font-size:18px}.stat-subtitle{font-size:10px}.coding-stats-grid{grid-template-columns:1fr 1fr}.agents-layout{grid-template-columns:1fr}.coding-tl-row{grid-template-columns:56px 1fr 46px}.coding-subagent-row,.cron-grid{grid-template-columns:1fr}.templates-layout{grid-template-columns:1fr;gap:16px}.templates-sidebar{max-height:280px;overflow-y:auto}.templates-metadata{grid-template-columns:1fr 1fr;gap:16px}.templates-editor-header{flex-direction:column;align-items:stretch}.templates-editor-title-row{flex-direction:column;align-items:flex-start;gap:8px}.templates-editor-actions{width:100%;justify-content:flex-end}}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<title>SkimpyClaw Dashboard</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
8
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@600;700;800&display=swap" rel="stylesheet">
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-B345aOO-.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-ZWK4dalJ.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="app"></div>
|
package/dist/digests.d.ts
CHANGED
package/dist/digests.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// Digest storage and management for cron job outputs
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
3
|
+
import { join, resolve, sep } from 'path';
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
5
|
import { getLogsDir } from './config.js';
|
|
6
6
|
const DIGESTS_DIR_NAME = 'digests';
|
|
7
|
+
const DIGEST_INDEX_FILE = 'index.json';
|
|
8
|
+
const DIGEST_ID_PATTERN = /^[A-Za-z0-9_-]+-[a-f0-9]{8}$/;
|
|
9
|
+
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
7
10
|
export function getDigestsDir() {
|
|
8
11
|
const dir = join(getLogsDir(), DIGESTS_DIR_NAME);
|
|
9
12
|
if (!existsSync(dir)) {
|
|
@@ -25,15 +28,85 @@ function generateDigestId(jobId, timestamp) {
|
|
|
25
28
|
function generateArticleId(url) {
|
|
26
29
|
return createHash('md5').update(url).digest('hex').slice(0, 12);
|
|
27
30
|
}
|
|
31
|
+
function getDigestIndexPath() {
|
|
32
|
+
return join(getDigestsDir(), DIGEST_INDEX_FILE);
|
|
33
|
+
}
|
|
34
|
+
function isValidPathToken(value) {
|
|
35
|
+
return value.length > 0 && !value.includes('/') && !value.includes('\\') && !value.includes('\0') && !value.includes('..');
|
|
36
|
+
}
|
|
37
|
+
function isValidDigestId(id) {
|
|
38
|
+
return DIGEST_ID_PATTERN.test(id) && isValidPathToken(id);
|
|
39
|
+
}
|
|
40
|
+
function isValidDate(value) {
|
|
41
|
+
return DATE_PATTERN.test(value);
|
|
42
|
+
}
|
|
43
|
+
function getDigestDate(digest) {
|
|
44
|
+
return new Date(digest.createdAt).toISOString().split('T')[0];
|
|
45
|
+
}
|
|
46
|
+
function loadDigestIndex() {
|
|
47
|
+
const indexPath = getDigestIndexPath();
|
|
48
|
+
if (!existsSync(indexPath))
|
|
49
|
+
return {};
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(readFileSync(indexPath, 'utf-8'));
|
|
52
|
+
const valid = {};
|
|
53
|
+
for (const [id, entry] of Object.entries(parsed)) {
|
|
54
|
+
if (!isValidDigestId(id) || !entry || typeof entry !== 'object')
|
|
55
|
+
continue;
|
|
56
|
+
const jobId = entry.jobId;
|
|
57
|
+
const date = entry.date;
|
|
58
|
+
if (typeof jobId !== 'string' || typeof date !== 'string')
|
|
59
|
+
continue;
|
|
60
|
+
if (!isValidPathToken(jobId) || !isValidDate(date))
|
|
61
|
+
continue;
|
|
62
|
+
valid[id] = { jobId, date };
|
|
63
|
+
}
|
|
64
|
+
return valid;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function saveDigestIndex(index) {
|
|
71
|
+
writeFileSync(getDigestIndexPath(), JSON.stringify(index, null, 2), 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
function setDigestIndexEntry(index, digest) {
|
|
74
|
+
const date = getDigestDate(digest);
|
|
75
|
+
if (!isValidDigestId(digest.id) || !isValidPathToken(digest.jobId) || !isValidDate(date))
|
|
76
|
+
return false;
|
|
77
|
+
const current = index[digest.id];
|
|
78
|
+
if (current && current.jobId === digest.jobId && current.date === date)
|
|
79
|
+
return false;
|
|
80
|
+
index[digest.id] = { jobId: digest.jobId, date };
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
function resolveDigestFilePath(id, entry) {
|
|
84
|
+
if (!isValidDigestId(id) || !isValidPathToken(entry.jobId) || !isValidDate(entry.date)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const baseDir = getDigestsDir();
|
|
88
|
+
const resolvedBase = resolve(baseDir);
|
|
89
|
+
const resolvedPath = resolve(baseDir, entry.jobId, `${entry.date}-${id}.json`);
|
|
90
|
+
if (resolvedPath !== resolvedBase && !resolvedPath.startsWith(`${resolvedBase}${sep}`)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return resolvedPath;
|
|
94
|
+
}
|
|
28
95
|
export function saveDigest(digest) {
|
|
29
96
|
const jobDir = getJobDir(digest.jobId);
|
|
30
|
-
const date =
|
|
97
|
+
const date = getDigestDate(digest);
|
|
31
98
|
const filePath = join(jobDir, `${date}-${digest.id}.json`);
|
|
32
99
|
writeFileSync(filePath, JSON.stringify(digest, null, 2), 'utf-8');
|
|
100
|
+
const index = loadDigestIndex();
|
|
101
|
+
if (setDigestIndexEntry(index, digest)) {
|
|
102
|
+
saveDigestIndex(index);
|
|
103
|
+
}
|
|
33
104
|
}
|
|
34
105
|
export function getDigests(jobId, limit) {
|
|
35
106
|
const digestsDir = getDigestsDir();
|
|
36
107
|
const items = [];
|
|
108
|
+
const index = loadDigestIndex();
|
|
109
|
+
let indexChanged = false;
|
|
37
110
|
const jobDirs = jobId
|
|
38
111
|
? [join(digestsDir, jobId)].filter(existsSync)
|
|
39
112
|
: readdirSync(digestsDir, { withFileTypes: true })
|
|
@@ -55,6 +128,9 @@ export function getDigests(jobId, limit) {
|
|
|
55
128
|
articleCount: digest.articles.length,
|
|
56
129
|
preview: digest.articles.slice(0, 2).map(a => a.title),
|
|
57
130
|
});
|
|
131
|
+
if (setDigestIndexEntry(index, digest)) {
|
|
132
|
+
indexChanged = true;
|
|
133
|
+
}
|
|
58
134
|
}
|
|
59
135
|
catch {
|
|
60
136
|
// Skip invalid files
|
|
@@ -64,54 +140,55 @@ export function getDigests(jobId, limit) {
|
|
|
64
140
|
// Sort by createdAt desc
|
|
65
141
|
items.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
66
142
|
if (limit && limit > 0) {
|
|
143
|
+
if (indexChanged) {
|
|
144
|
+
saveDigestIndex(index);
|
|
145
|
+
}
|
|
67
146
|
return items.slice(0, limit);
|
|
68
147
|
}
|
|
148
|
+
if (indexChanged) {
|
|
149
|
+
saveDigestIndex(index);
|
|
150
|
+
}
|
|
69
151
|
return items;
|
|
70
152
|
}
|
|
71
153
|
export function getDigest(id) {
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
}
|
|
154
|
+
const index = loadDigestIndex();
|
|
155
|
+
const entry = index[id];
|
|
156
|
+
if (!entry)
|
|
157
|
+
return null;
|
|
158
|
+
const filePath = resolveDigestFilePath(id, entry);
|
|
159
|
+
if (!filePath || !existsSync(filePath)) {
|
|
160
|
+
delete index[id];
|
|
161
|
+
saveDigestIndex(index);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
166
|
+
return JSON.parse(content);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
90
170
|
}
|
|
91
|
-
return null;
|
|
92
171
|
}
|
|
93
172
|
export function deleteDigest(id) {
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
173
|
+
const index = loadDigestIndex();
|
|
174
|
+
const entry = index[id];
|
|
175
|
+
if (!entry)
|
|
176
|
+
return false;
|
|
177
|
+
const filePath = resolveDigestFilePath(id, entry);
|
|
178
|
+
if (!filePath || !existsSync(filePath)) {
|
|
179
|
+
delete index[id];
|
|
180
|
+
saveDigestIndex(index);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
unlinkSync(filePath);
|
|
185
|
+
delete index[id];
|
|
186
|
+
saveDigestIndex(index);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return false;
|
|
113
191
|
}
|
|
114
|
-
return false;
|
|
115
192
|
}
|
|
116
193
|
export function updateArticleReadStatus(digestId, articleId, read) {
|
|
117
194
|
const digest = getDigest(digestId);
|
|
@@ -157,9 +234,19 @@ function extractUrls(text) {
|
|
|
157
234
|
}
|
|
158
235
|
function extractTitleForUrl(text, url) {
|
|
159
236
|
const lines = text.split('\n');
|
|
237
|
+
const isMarkdownHeading = (value) => /^#{1,6}\s+\S/.test(value);
|
|
160
238
|
for (let i = 0; i < lines.length; i++) {
|
|
161
239
|
const line = lines[i];
|
|
162
240
|
if (line.includes(url)) {
|
|
241
|
+
const markdownLinkMatches = [...line.matchAll(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g)];
|
|
242
|
+
for (const match of markdownLinkMatches) {
|
|
243
|
+
const markdownUrl = match[2]?.replace(/[.,;:!?)\]>'"`]+$/, '');
|
|
244
|
+
if (markdownUrl === url) {
|
|
245
|
+
const title = match[1]?.trim();
|
|
246
|
+
if (title)
|
|
247
|
+
return title.slice(0, 150);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
163
250
|
// If the URL is on its own line (🔗 link line), look backwards for the title
|
|
164
251
|
const isLinkOnlyLine = /^\s*🔗?\s*https?:\/\//i.test(line.trim());
|
|
165
252
|
if (isLinkOnlyLine) {
|
|
@@ -173,6 +260,9 @@ function extractTitleForUrl(text, url) {
|
|
|
173
260
|
// eslint-disable-next-line no-misleading-character-class
|
|
174
261
|
if (/^[\u2B06\uFE0F\u{1F4AC}\u{1F525}\u{1F504}\u2B50\u{1F517}]+/u.test(prev))
|
|
175
262
|
continue;
|
|
263
|
+
// Skip markdown section headers (e.g. "## World Headlines")
|
|
264
|
+
if (isMarkdownHeading(prev))
|
|
265
|
+
continue;
|
|
176
266
|
// Skip lines that are just URLs
|
|
177
267
|
if (/^https?:\/\//.test(prev))
|
|
178
268
|
continue;
|
|
@@ -196,7 +286,7 @@ function extractTitleForUrl(text, url) {
|
|
|
196
286
|
if (i > 0) {
|
|
197
287
|
const prevLine = lines[i - 1].trim();
|
|
198
288
|
// eslint-disable-next-line no-misleading-character-class
|
|
199
|
-
if (prevLine && !prevLine.startsWith('http') && !/^[\u2B06\uFE0F\u{1F4AC}\u{1F525}\u{1F504}\u2B50\u{1F517}]+/u.test(prevLine)) {
|
|
289
|
+
if (prevLine && !prevLine.startsWith('http') && !isMarkdownHeading(prevLine) && !/^[\u2B06\uFE0F\u{1F4AC}\u{1F525}\u{1F504}\u2B50\u{1F517}]+/u.test(prevLine)) {
|
|
200
290
|
const cleaned = prevLine.replace(/^\d+\.\s*/, '').replace(/^[-*•]\s*/, '').trim();
|
|
201
291
|
if (cleaned.length > 0)
|
|
202
292
|
return cleaned.slice(0, 150);
|
package/dist/doctor/checks.d.ts
CHANGED
|
@@ -10,11 +10,8 @@ export declare function checkAllowedPathsWritable(config: Config): Promise<Docto
|
|
|
10
10
|
export declare function checkProviderAuth(providerName: string, providerConfig: NonNullable<Config['models']['providers'][string]>): Promise<DoctorCheckResult>;
|
|
11
11
|
export declare function checkTelegramToken(token: string): Promise<DoctorCheckResult>;
|
|
12
12
|
export declare function checkDiscordToken(token: string): Promise<DoctorCheckResult>;
|
|
13
|
-
export declare function checkBrowserBinaryIfEnabled(config: Config): Promise<DoctorCheckResult>;
|
|
14
13
|
export declare function checkVoiceDependencies(config: Config): Promise<DoctorCheckResult>;
|
|
15
|
-
export declare function checkPlaywrightIfBrowserEnabled(config: Config): Promise<DoctorCheckResult>;
|
|
16
14
|
export declare function checkMcpConfig(config: Config): Promise<DoctorCheckResult>;
|
|
17
15
|
export declare function checkGatewayHostBindable(host: string): Promise<DoctorCheckResult>;
|
|
18
16
|
export declare function checkSkimpyclawDirWritable(): Promise<DoctorCheckResult>;
|
|
19
|
-
export declare function checkSandboxAvailable(config: Config): Promise<DoctorCheckResult>;
|
|
20
17
|
export declare function checkPortAvailability(port: number): Promise<DoctorCheckResult>;
|
package/dist/doctor/checks.js
CHANGED
|
@@ -101,7 +101,7 @@ export async function checkEnvVarPatterns(config) {
|
|
|
101
101
|
const key = providerCfg?.apiKey || providerCfg?.authToken || '';
|
|
102
102
|
if (!key)
|
|
103
103
|
continue;
|
|
104
|
-
if (/
|
|
104
|
+
if (/anthropic/i.test(providerName) && !key.startsWith('sk-')) {
|
|
105
105
|
issues.push(`${providerName} key should usually start with "sk-"`);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -147,16 +147,6 @@ export async function checkProviderAuth(providerName, providerConfig) {
|
|
|
147
147
|
}
|
|
148
148
|
const normalized = providerName.toLowerCase();
|
|
149
149
|
try {
|
|
150
|
-
if (normalized === 'openai') {
|
|
151
|
-
const res = await fetch(`${providerConfig.baseURL || 'https://api.openai.com/v1'}/models`, {
|
|
152
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
153
|
-
signal: AbortSignal.timeout(7000),
|
|
154
|
-
});
|
|
155
|
-
if (!res.ok) {
|
|
156
|
-
return fail(name, category, `${res.status} ${res.statusText}`, `Check ${providerName.toUpperCase()}_API_KEY and provider base URL.`);
|
|
157
|
-
}
|
|
158
|
-
return ok(name, category, `${res.status} ${res.statusText}`);
|
|
159
|
-
}
|
|
160
150
|
if (normalized === 'anthropic') {
|
|
161
151
|
// OAuth tokens can't be validated via API (they use Claude Code's internal refresh flow)
|
|
162
152
|
if (providerConfig.authToken) {
|
|
@@ -225,44 +215,6 @@ export async function checkDiscordToken(token) {
|
|
|
225
215
|
return fail(name, category, detail, 'Check network and DISCORD_BOT_TOKEN.');
|
|
226
216
|
}
|
|
227
217
|
}
|
|
228
|
-
function isAnyBrowserEnabled(config) {
|
|
229
|
-
return Boolean(config.channels.telegram?.tools?.browser?.enabled
|
|
230
|
-
|| config.channels.discord?.tools?.browser?.enabled
|
|
231
|
-
|| config.heartbeat?.tools?.browser?.enabled);
|
|
232
|
-
}
|
|
233
|
-
export async function checkBrowserBinaryIfEnabled(config) {
|
|
234
|
-
const name = 'browser_binary_available';
|
|
235
|
-
const category = 'runtime';
|
|
236
|
-
if (!isAnyBrowserEnabled(config)) {
|
|
237
|
-
return ok(name, category, 'Browser tools disabled');
|
|
238
|
-
}
|
|
239
|
-
const explicitPath = config.channels.telegram?.tools?.browser?.executablePath
|
|
240
|
-
|| config.channels.discord?.tools?.browser?.executablePath
|
|
241
|
-
|| config.heartbeat?.tools?.browser?.executablePath;
|
|
242
|
-
if (explicitPath) {
|
|
243
|
-
if (existsSync(explicitPath)) {
|
|
244
|
-
return ok(name, category, explicitPath);
|
|
245
|
-
}
|
|
246
|
-
return fail(name, category, `Browser executable not found at ${explicitPath}`, 'Fix tools.browser.executablePath or install a supported browser.');
|
|
247
|
-
}
|
|
248
|
-
const probes = [
|
|
249
|
-
['which', ['google-chrome']],
|
|
250
|
-
['which', ['chromium']],
|
|
251
|
-
['which', ['chromium-browser']],
|
|
252
|
-
['which', ['firefox']],
|
|
253
|
-
];
|
|
254
|
-
for (const [cmd, args] of probes) {
|
|
255
|
-
const probe = spawnSync(cmd, args, { encoding: 'utf-8' });
|
|
256
|
-
if (probe.status === 0 && probe.stdout.trim()) {
|
|
257
|
-
return ok(name, category, probe.stdout.trim());
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
const macChrome = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
261
|
-
if (existsSync(macChrome)) {
|
|
262
|
-
return ok(name, category, macChrome);
|
|
263
|
-
}
|
|
264
|
-
return fail(name, category, 'No supported browser binary found', 'Install Chrome/Chromium/Firefox or set tools.browser.executablePath.');
|
|
265
|
-
}
|
|
266
218
|
export async function checkVoiceDependencies(config) {
|
|
267
219
|
const name = 'voice_dependencies';
|
|
268
220
|
const category = 'runtime';
|
|
@@ -289,18 +241,6 @@ export async function checkVoiceDependencies(config) {
|
|
|
289
241
|
const sttMethod = hasLocalWhisper ? (whisperCli.status === 0 ? 'whisper-cli' : 'whisper') : 'API STT';
|
|
290
242
|
return ok(name, category, `ffmpeg and ${sttMethod} available`);
|
|
291
243
|
}
|
|
292
|
-
export async function checkPlaywrightIfBrowserEnabled(config) {
|
|
293
|
-
const name = 'playwright_installed';
|
|
294
|
-
const category = 'runtime';
|
|
295
|
-
if (!isAnyBrowserEnabled(config)) {
|
|
296
|
-
return ok(name, category, 'Browser tools disabled');
|
|
297
|
-
}
|
|
298
|
-
const pw = spawnSync('npx', ['playwright', '--version'], { encoding: 'utf-8', timeout: 10000 });
|
|
299
|
-
if (pw.status === 0) {
|
|
300
|
-
return ok(name, category, `Playwright ${(pw.stdout || '').trim()}`);
|
|
301
|
-
}
|
|
302
|
-
return fail(name, category, 'Playwright not installed', 'Run: npx playwright install chromium');
|
|
303
|
-
}
|
|
304
244
|
export async function checkMcpConfig(config) {
|
|
305
245
|
const name = 'mcp_config';
|
|
306
246
|
const category = 'runtime';
|
|
@@ -357,53 +297,6 @@ export async function checkSkimpyclawDirWritable() {
|
|
|
357
297
|
return fail(name, category, `Directory not writable: ${base}`, 'Fix permissions for ~/.skimpyclaw.');
|
|
358
298
|
}
|
|
359
299
|
}
|
|
360
|
-
export async function checkSandboxAvailable(config) {
|
|
361
|
-
const name = 'sandbox_available';
|
|
362
|
-
const category = 'runtime';
|
|
363
|
-
if (!config.sandbox?.enabled) {
|
|
364
|
-
return ok(name, category, 'Sandbox disabled');
|
|
365
|
-
}
|
|
366
|
-
// Determine which runtime to check
|
|
367
|
-
const explicit = config.sandbox.runtime;
|
|
368
|
-
let rt = null;
|
|
369
|
-
if (explicit) {
|
|
370
|
-
const check = spawnSync(explicit, ['--version'], { encoding: 'utf-8' });
|
|
371
|
-
if (check.status !== 0) {
|
|
372
|
-
return fail(name, category, `${explicit} CLI not found`, `Install ${explicit} or change sandbox.runtime in config.`);
|
|
373
|
-
}
|
|
374
|
-
rt = explicit;
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
// Auto-detect: prefer container, fall back to docker
|
|
378
|
-
const containerCheck = spawnSync('container', ['--version'], { encoding: 'utf-8' });
|
|
379
|
-
if (containerCheck.status === 0) {
|
|
380
|
-
rt = 'container';
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
const dockerCheck = spawnSync('docker', ['--version'], { encoding: 'utf-8' });
|
|
384
|
-
if (dockerCheck.status === 0) {
|
|
385
|
-
rt = 'docker';
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (!rt) {
|
|
389
|
-
return fail(name, category, 'No container runtime found', 'Install Apple Containers or Docker, or disable sandbox in config.');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// For Apple Containers, check system is running
|
|
393
|
-
if (rt === 'container') {
|
|
394
|
-
const systemCheck = spawnSync('container', ['system', 'status'], { encoding: 'utf-8' });
|
|
395
|
-
if (systemCheck.status !== 0) {
|
|
396
|
-
return fail(name, category, 'Container system not running', 'Run "container system start" to start the container runtime.');
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
// Check if sandbox image exists
|
|
400
|
-
const image = config.sandbox.image || 'skimpyclaw-sandbox';
|
|
401
|
-
const imageCheck = spawnSync(rt, ['image', 'inspect', image], { encoding: 'utf-8' });
|
|
402
|
-
if (imageCheck.status !== 0) {
|
|
403
|
-
return fail(name, category, `Sandbox image "${image}" not found`, `Build it: ${rt} build -t ${image} sandbox/`);
|
|
404
|
-
}
|
|
405
|
-
return ok(name, category, `Runtime: ${rt}, image "${image}" available`);
|
|
406
|
-
}
|
|
407
300
|
export async function checkPortAvailability(port) {
|
|
408
301
|
const name = 'gateway_port_available';
|
|
409
302
|
const category = 'runtime';
|
package/dist/doctor/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from '../config.js';
|
|
2
|
-
import { checkNodeVersion, checkPackageManagerAvailable, checkTypeScriptCompile, checkConfigExistsAndValidJson, checkRequiredEnvVars, checkEnvVarPatterns, checkAllowedPathsWritable, checkProviderAuth, checkTelegramToken, checkDiscordToken,
|
|
2
|
+
import { checkNodeVersion, checkPackageManagerAvailable, checkTypeScriptCompile, checkConfigExistsAndValidJson, checkRequiredEnvVars, checkEnvVarPatterns, checkAllowedPathsWritable, checkProviderAuth, checkTelegramToken, checkDiscordToken, checkVoiceDependencies, checkMcpConfig, checkGatewayHostBindable, checkSkimpyclawDirWritable, checkPortAvailability, } from './checks.js';
|
|
3
3
|
export function computeExitCode(report) {
|
|
4
4
|
if (report.checks.some((check) => !check.ok && check.fatal)) {
|
|
5
5
|
return 2;
|
|
@@ -98,14 +98,11 @@ export async function runDoctor() {
|
|
|
98
98
|
checks.push(await runSafe('discord_token_valid', 'channels', () => checkDiscordToken(discord.token)));
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
checks.push(await runSafe('browser_binary_available', 'runtime', () => checkBrowserBinaryIfEnabled(config)));
|
|
102
|
-
checks.push(await runSafe('playwright_installed', 'runtime', () => checkPlaywrightIfBrowserEnabled(config)));
|
|
103
101
|
checks.push(await runSafe('voice_dependencies', 'runtime', () => checkVoiceDependencies(config)));
|
|
104
102
|
checks.push(await runSafe('mcp_config', 'runtime', () => checkMcpConfig(config)));
|
|
105
103
|
checks.push(await runSafe('gateway_host_bindable', 'runtime', () => checkGatewayHostBindable(config.gateway.host ?? '127.0.0.1')));
|
|
106
104
|
checks.push(await runSafe('skimpyclaw_dirs_writable', 'runtime', () => checkSkimpyclawDirWritable()));
|
|
107
105
|
checks.push(await runSafe('gateway_port_available', 'runtime', () => checkPortAvailability(config.gateway.port)));
|
|
108
|
-
checks.push(await runSafe('sandbox_available', 'runtime', () => checkSandboxAvailable(config)));
|
|
109
106
|
const report = buildReport(startedAt, checks);
|
|
110
107
|
return { report, exitCode: report.exitCode };
|
|
111
108
|
}
|