cli-claw-kit 0.0.1
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/LICENSE +21 -0
- package/README.md +245 -0
- package/config/default-groups.json +1 -0
- package/config/global-agents-md.template.md +37 -0
- package/config/mount-allowlist.json +11 -0
- package/container/Dockerfile +160 -0
- package/container/agent-runner/dist/.tsbuildinfo +1 -0
- package/container/agent-runner/dist/agent-definitions.js +22 -0
- package/container/agent-runner/dist/channel-prefixes.js +16 -0
- package/container/agent-runner/dist/codex-config.js +29 -0
- package/container/agent-runner/dist/image-detector.js +96 -0
- package/container/agent-runner/dist/index.js +2587 -0
- package/container/agent-runner/dist/mcp-tools.js +1076 -0
- package/container/agent-runner/dist/stream-event.types.js +5 -0
- package/container/agent-runner/dist/stream-processor.js +867 -0
- package/container/agent-runner/dist/types.js +6 -0
- package/container/agent-runner/dist/utils.js +115 -0
- package/container/agent-runner/package.json +36 -0
- package/container/agent-runner/prompts/security-rules.md +31 -0
- package/container/agent-runner/src/agent-definitions.ts +27 -0
- package/container/agent-runner/src/channel-prefixes.ts +16 -0
- package/container/agent-runner/src/codex-config.ts +40 -0
- package/container/agent-runner/src/image-detector.ts +116 -0
- package/container/agent-runner/src/index.ts +3107 -0
- package/container/agent-runner/src/mcp-tools.ts +1295 -0
- package/container/agent-runner/src/stream-event.types.ts +10 -0
- package/container/agent-runner/src/stream-processor.ts +932 -0
- package/container/agent-runner/src/types.ts +75 -0
- package/container/agent-runner/src/utils.ts +114 -0
- package/container/agent-runner/tsconfig.json +17 -0
- package/container/build.sh +28 -0
- package/container/entrypoint.sh +64 -0
- package/container/skills/agent-browser/SKILL.md +159 -0
- package/container/skills/install-skill/SKILL.md +64 -0
- package/container/skills/post-test-cleanup/SKILL.md +121 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/agent-output-parser.js +459 -0
- package/dist/app-root.js +52 -0
- package/dist/assistant-meta-footer.js +1 -0
- package/dist/auth.js +91 -0
- package/dist/billing.js +694 -0
- package/dist/channel-prefixes.js +16 -0
- package/dist/cli.js +86 -0
- package/dist/commands.js +79 -0
- package/dist/config.js +120 -0
- package/dist/container-runner.js +981 -0
- package/dist/daily-summary.js +210 -0
- package/dist/db.js +3683 -0
- package/dist/dingtalk.js +1347 -0
- package/dist/feishu-markdown-style.js +97 -0
- package/dist/feishu-streaming-card.js +1875 -0
- package/dist/feishu.js +1628 -0
- package/dist/file-manager.js +270 -0
- package/dist/group-queue.js +1070 -0
- package/dist/group-runtime.js +35 -0
- package/dist/host-workspace-cwd.js +85 -0
- package/dist/im-channel.js +384 -0
- package/dist/im-command-utils.js +142 -0
- package/dist/im-downloader.js +45 -0
- package/dist/im-manager.js +527 -0
- package/dist/im-utils.js +53 -0
- package/dist/image-detector.js +96 -0
- package/dist/index.js +5828 -0
- package/dist/logger.js +22 -0
- package/dist/mcp-utils.js +66 -0
- package/dist/message-attachments.js +69 -0
- package/dist/message-notifier.js +36 -0
- package/dist/middleware/auth.js +85 -0
- package/dist/mount-security.js +315 -0
- package/dist/permissions.js +67 -0
- package/dist/project-memory.js +6 -0
- package/dist/provider-pool.js +189 -0
- package/dist/qq.js +826 -0
- package/dist/reset-admin.js +42 -0
- package/dist/routes/admin.js +543 -0
- package/dist/routes/agent-definitions.js +241 -0
- package/dist/routes/agents.js +533 -0
- package/dist/routes/auth.js +675 -0
- package/dist/routes/billing.js +490 -0
- package/dist/routes/browse.js +210 -0
- package/dist/routes/bug-report.js +387 -0
- package/dist/routes/config.js +1868 -0
- package/dist/routes/files.js +671 -0
- package/dist/routes/groups.js +1367 -0
- package/dist/routes/mcp-servers.js +320 -0
- package/dist/routes/memory.js +523 -0
- package/dist/routes/monitor.js +307 -0
- package/dist/routes/skills.js +777 -0
- package/dist/routes/tasks.js +509 -0
- package/dist/routes/usage.js +64 -0
- package/dist/routes/workspace-config.js +458 -0
- package/dist/runtime-build.js +112 -0
- package/dist/runtime-command-handler.js +189 -0
- package/dist/runtime-command-registry.js +1 -0
- package/dist/runtime-config.js +1777 -0
- package/dist/runtime-identity.js +52 -0
- package/dist/schemas.js +590 -0
- package/dist/script-runner.js +64 -0
- package/dist/sdk-query.js +82 -0
- package/dist/skill-utils.js +145 -0
- package/dist/sqlite-compat.js +19 -0
- package/dist/stream-event.types.js +5 -0
- package/dist/streaming-runtime-meta.js +29 -0
- package/dist/task-scheduler.js +695 -0
- package/dist/task-utils.js +13 -0
- package/dist/telegram-pairing.js +59 -0
- package/dist/telegram.js +897 -0
- package/dist/terminal-manager.js +307 -0
- package/dist/tool-step-display.js +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +85 -0
- package/dist/web-context.js +161 -0
- package/dist/web.js +1377 -0
- package/dist/wechat-crypto.js +182 -0
- package/dist/wechat.js +589 -0
- package/dist/workspace-runtime-reset.js +35 -0
- package/package.json +107 -0
- package/shared/assistant-meta-footer.ts +127 -0
- package/shared/channel-prefixes.ts +16 -0
- package/shared/dist/assistant-meta-footer.d.ts +29 -0
- package/shared/dist/assistant-meta-footer.js +85 -0
- package/shared/dist/channel-prefixes.d.ts +4 -0
- package/shared/dist/channel-prefixes.js +16 -0
- package/shared/dist/image-detector.d.ts +20 -0
- package/shared/dist/image-detector.js +96 -0
- package/shared/dist/runtime-command-registry.d.ts +38 -0
- package/shared/dist/runtime-command-registry.js +185 -0
- package/shared/dist/stream-event.d.ts +65 -0
- package/shared/dist/stream-event.js +8 -0
- package/shared/dist/tool-step-display.d.ts +4 -0
- package/shared/dist/tool-step-display.js +11 -0
- package/shared/image-detector.ts +116 -0
- package/shared/runtime-command-registry.ts +252 -0
- package/shared/stream-event.ts +67 -0
- package/shared/tool-step-display.ts +21 -0
- package/shared/tsconfig.json +24 -0
- package/web/dist/assets/BillingPage-B1wBR_o-.js +52 -0
- package/web/dist/assets/ChatPage-6GBZ9nXN.css +32 -0
- package/web/dist/assets/ChatPage-BOJcXtaj.js +161 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/SettingsPage-DoY7FoZ_.js +153 -0
- package/web/dist/assets/ShareImageDialog-C1ga8b7l.js +22 -0
- package/web/dist/assets/TasksPage-CRivnNsx.js +14 -0
- package/web/dist/assets/_basePickBy-Bf-bSoS9.js +1 -0
- package/web/dist/assets/_baseUniq-zAOaCuKw.js +1 -0
- package/web/dist/assets/arc-Dm9mVQ9U.js +1 -0
- package/web/dist/assets/architectureDiagram-2XIMDMQ5-BLmzX1wr.js +36 -0
- package/web/dist/assets/band-CquvqAHh.js +1 -0
- package/web/dist/assets/blockDiagram-WCTKOSBZ-B9pcqm3j.js +132 -0
- package/web/dist/assets/c4Diagram-IC4MRINW-Cytx1q3b.js +10 -0
- package/web/dist/assets/channel-BOVj73LR.js +1 -0
- package/web/dist/assets/channel-meta-CQD0Pei-.js +41 -0
- package/web/dist/assets/chunk-4BX2VUAB-0ToDr6RE.js +1 -0
- package/web/dist/assets/chunk-55IACEB6-DQDjnXfS.js +1 -0
- package/web/dist/assets/chunk-FMBD7UC4-Di8ABm6c.js +15 -0
- package/web/dist/assets/chunk-JSJVCQXG-BZQN6rnX.js +1 -0
- package/web/dist/assets/chunk-KX2RTZJC-zBbcpaN_.js +1 -0
- package/web/dist/assets/chunk-NQ4KR5QH-BCrLoU88.js +220 -0
- package/web/dist/assets/chunk-QZHKN3VN-Bqk8juan.js +1 -0
- package/web/dist/assets/chunk-WL4C6EOR-D2YX-MHY.js +189 -0
- package/web/dist/assets/classDiagram-VBA2DB6C-DUUoMyaK.js +1 -0
- package/web/dist/assets/classDiagram-v2-RAHNMMFH-DUUoMyaK.js +1 -0
- package/web/dist/assets/clone-BmaCesfa.js +1 -0
- package/web/dist/assets/cose-bilkent-S5V4N54A-CTsv6qQA.js +1 -0
- package/web/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
- package/web/dist/assets/dagre-KLK3FWXG-Ci4Jh9nu.js +4 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/diagram-E7M64L7V-BFRnfTI2.js +24 -0
- package/web/dist/assets/diagram-IFDJBPK2-B7Zhnp0b.js +43 -0
- package/web/dist/assets/diagram-P4PSJMXO-BVyP7nwq.js +24 -0
- package/web/dist/assets/erDiagram-INFDFZHY-NorKdTOF.js +70 -0
- package/web/dist/assets/error-CGD5mp5f.js +1 -0
- package/web/dist/assets/flowDiagram-PKNHOUZH-Ch97nABF.js +162 -0
- package/web/dist/assets/ganttDiagram-A5KZAMGK-BQ2pLWsy.js +292 -0
- package/web/dist/assets/gitGraphDiagram-K3NZZRJ6-bcvnBsD2.js +65 -0
- package/web/dist/assets/graph-CeAEckur.js +1 -0
- package/web/dist/assets/index-CPnL1_qC.js +768 -0
- package/web/dist/assets/index-DVevCbcO.css +10 -0
- package/web/dist/assets/infoDiagram-LFFYTUFH-CcsrFdj-.js +2 -0
- package/web/dist/assets/init-Dmth1JHB.js +1 -0
- package/web/dist/assets/ishikawaDiagram-PHBUUO56-1upyMfHN.js +70 -0
- package/web/dist/assets/journeyDiagram-4ABVD52K-CKUi-V0c.js +139 -0
- package/web/dist/assets/kanban-definition-K7BYSVSG-DOnQwXfL.js +89 -0
- package/web/dist/assets/layout-BmMMqTnJ.js +1 -0
- package/web/dist/assets/linear-DiaJloY5.js +1 -0
- package/web/dist/assets/mermaid.core-BWLV1B2v.js +254 -0
- package/web/dist/assets/mindmap-definition-YRQLILUH-BeAKHVWP.js +68 -0
- package/web/dist/assets/ordinal-DILIJJjt.js +1 -0
- package/web/dist/assets/pieDiagram-SKSYHLDU-DfiMSfWo.js +30 -0
- package/web/dist/assets/quadrantDiagram-337W2JSQ-wZxZOJxd.js +7 -0
- package/web/dist/assets/requirementDiagram-Z7DCOOCP-BK4HHm17.js +73 -0
- package/web/dist/assets/sankeyDiagram-WA2Y5GQK-BX6t2avX.js +10 -0
- package/web/dist/assets/sequenceDiagram-2WXFIKYE-BPQlkbAa.js +145 -0
- package/web/dist/assets/sheet-rI0FfB1g.js +6 -0
- package/web/dist/assets/sliders-horizontal-CuijWFNK.js +6 -0
- package/web/dist/assets/sparkles-BsMYXJoT.js +11 -0
- package/web/dist/assets/square-0CqMX1Q3.js +11 -0
- package/web/dist/assets/stateDiagram-RAJIS63D-DxkV0Vwd.js +1 -0
- package/web/dist/assets/stateDiagram-v2-FVOUBMTO-qLYoiOPe.js +1 -0
- package/web/dist/assets/step-D51IIHGA.js +1 -0
- package/web/dist/assets/tasks-D8JjBTwx.js +1 -0
- package/web/dist/assets/time-O8zIGux3.js +1 -0
- package/web/dist/assets/timeline-definition-YZTLITO2-kNp1DyFc.js +61 -0
- package/web/dist/assets/treemap-KZPCXAKY-CkrClVhk.js +162 -0
- package/web/dist/assets/utils-KGAn0XTg.js +11 -0
- package/web/dist/assets/vennDiagram-LZ73GAT5-CgdzEZz4.js +34 -0
- package/web/dist/assets/xychartDiagram-JWTSCODW-DfYGPfNB.js +7 -0
- package/web/dist/assets/zap-_hKJYy7J.js +6 -0
- package/web/dist/favicon.svg +332 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-55-Regular.woff2 +0 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-65-Medium.woff2 +0 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-75-SemiBold.woff2 +0 -0
- package/web/dist/fonts/DMSans-latin-ext.woff2 +0 -0
- package/web/dist/fonts/DMSans-latin.woff2 +0 -0
- package/web/dist/icons/README.md +20 -0
- package/web/dist/icons/apple-touch-icon-180.png +0 -0
- package/web/dist/icons/icon-128.png +0 -0
- package/web/dist/icons/icon-144.png +0 -0
- package/web/dist/icons/icon-152.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-192.svg +332 -0
- package/web/dist/icons/icon-384.png +0 -0
- package/web/dist/icons/icon-48.png +0 -0
- package/web/dist/icons/icon-512-maskable.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/icon-512.svg +332 -0
- package/web/dist/icons/icon-72.png +0 -0
- package/web/dist/icons/icon-96.png +0 -0
- package/web/dist/icons/loading-logo.svg +332 -0
- package/web/dist/icons/logo-1024.png +0 -0
- package/web/dist/icons/logo-icon.svg +332 -0
- package/web/dist/icons/logo-text.svg +332 -0
- package/web/dist/index.html +30 -0
- package/web/dist/manifest.webmanifest +1 -0
- package/web/dist/registerSW.js +1 -0
- package/web/dist/sw.js +1 -0
- package/web/dist/workbox-08d6266a.js +1 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-level Skills and MCP Servers management routes.
|
|
3
|
+
*
|
|
4
|
+
* Operates on the workspace's `.claude/` directory (project-level config).
|
|
5
|
+
* SDK reads both 'project' and 'user' settingSources, so these configs
|
|
6
|
+
* take effect alongside global (user-level) configs without any changes
|
|
7
|
+
* to agent-runner or container-runner.
|
|
8
|
+
*/
|
|
9
|
+
import { Hono } from 'hono';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import { execFile } from 'child_process';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
import { authMiddleware } from '../middleware/auth.js';
|
|
16
|
+
import { GROUPS_DIR } from '../config.js';
|
|
17
|
+
import { canAccessGroup } from '../web-context.js';
|
|
18
|
+
import { getJidsByFolder, getRegisteredGroup } from '../db.js';
|
|
19
|
+
import { resolveEffectiveHostWorkspaceCwd } from '../host-workspace-cwd.js';
|
|
20
|
+
import { validateSkillId, validateSkillPath, scanSkillDirectory, } from '../skill-utils.js';
|
|
21
|
+
const execFileAsync = promisify(execFile);
|
|
22
|
+
const workspaceConfigRoutes = new Hono();
|
|
23
|
+
// --- Path Resolution ---
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the workspace root directory for a registered group.
|
|
26
|
+
* Host mode uses the effective host cwd contract; container mode keeps storage under ~/.cli-claw/groups/{folder}/.
|
|
27
|
+
*/
|
|
28
|
+
export function getWorkspaceRoot(group, homeGroup) {
|
|
29
|
+
if (group.executionMode === 'host') {
|
|
30
|
+
const effectiveCwd = resolveEffectiveHostWorkspaceCwd(group, homeGroup ?? undefined);
|
|
31
|
+
if (!effectiveCwd) {
|
|
32
|
+
throw new Error('Host workspace is missing custom_cwd');
|
|
33
|
+
}
|
|
34
|
+
return effectiveCwd;
|
|
35
|
+
}
|
|
36
|
+
return path.join(GROUPS_DIR, group.folder);
|
|
37
|
+
}
|
|
38
|
+
function findHomeSiblingGroup(group) {
|
|
39
|
+
if (group.is_home)
|
|
40
|
+
return undefined;
|
|
41
|
+
for (const jid of getJidsByFolder(group.folder)) {
|
|
42
|
+
const sibling = getRegisteredGroup(jid);
|
|
43
|
+
if (sibling?.is_home) {
|
|
44
|
+
return sibling;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
function getWorkspaceClaudeDir(group) {
|
|
50
|
+
return path.join(getWorkspaceRoot(group, findHomeSiblingGroup(group)), '.claude');
|
|
51
|
+
}
|
|
52
|
+
function getWorkspaceSkillsDir(group) {
|
|
53
|
+
return path.join(getWorkspaceClaudeDir(group), 'skills');
|
|
54
|
+
}
|
|
55
|
+
function getWorkspaceSettingsPath(group) {
|
|
56
|
+
return path.join(getWorkspaceClaudeDir(group), 'settings.json');
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Metadata file for workspace MCP servers.
|
|
60
|
+
* Stores full config + enabled state so we can remove disabled servers
|
|
61
|
+
* from settings.json (SDK won't see them) while preserving the config
|
|
62
|
+
* for re-enabling.
|
|
63
|
+
*/
|
|
64
|
+
function getWorkspaceMcpMetaPath(group) {
|
|
65
|
+
return path.join(getWorkspaceClaudeDir(group), 'cli-claw-workspace.json');
|
|
66
|
+
}
|
|
67
|
+
function readWorkspaceMeta(group) {
|
|
68
|
+
try {
|
|
69
|
+
const data = fs.readFileSync(getWorkspaceMcpMetaPath(group), 'utf-8');
|
|
70
|
+
return JSON.parse(data);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { mcpServers: {} };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function writeWorkspaceMeta(group, meta) {
|
|
77
|
+
const metaPath = getWorkspaceMcpMetaPath(group);
|
|
78
|
+
fs.mkdirSync(path.dirname(metaPath), { recursive: true });
|
|
79
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
80
|
+
}
|
|
81
|
+
function readWorkspaceSettings(group) {
|
|
82
|
+
try {
|
|
83
|
+
const data = fs.readFileSync(getWorkspaceSettingsPath(group), 'utf-8');
|
|
84
|
+
return JSON.parse(data);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function writeWorkspaceSettings(group, settings) {
|
|
91
|
+
const settingsPath = getWorkspaceSettingsPath(group);
|
|
92
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
93
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Sync enabled MCP servers to settings.json so SDK can discover them.
|
|
97
|
+
* Disabled servers are removed from settings.json but kept in metadata.
|
|
98
|
+
*/
|
|
99
|
+
function syncMcpToSettings(group, meta, existingSettings) {
|
|
100
|
+
const settings = existingSettings ?? readWorkspaceSettings(group);
|
|
101
|
+
const mcpServers = {};
|
|
102
|
+
for (const [id, entry] of Object.entries(meta.mcpServers)) {
|
|
103
|
+
if (!entry.enabled)
|
|
104
|
+
continue;
|
|
105
|
+
const isHttpType = entry.type === 'http' || entry.type === 'sse';
|
|
106
|
+
if (isHttpType) {
|
|
107
|
+
if (!entry.url)
|
|
108
|
+
continue;
|
|
109
|
+
const server = {
|
|
110
|
+
type: entry.type,
|
|
111
|
+
url: entry.url,
|
|
112
|
+
};
|
|
113
|
+
if (entry.headers && Object.keys(entry.headers).length > 0) {
|
|
114
|
+
server.headers = entry.headers;
|
|
115
|
+
}
|
|
116
|
+
mcpServers[id] = server;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if (!entry.command)
|
|
120
|
+
continue;
|
|
121
|
+
const server = { command: entry.command };
|
|
122
|
+
if (entry.args && entry.args.length > 0)
|
|
123
|
+
server.args = entry.args;
|
|
124
|
+
if (entry.env && Object.keys(entry.env).length > 0)
|
|
125
|
+
server.env = entry.env;
|
|
126
|
+
mcpServers[id] = server;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (Object.keys(mcpServers).length > 0) {
|
|
130
|
+
settings.mcpServers = mcpServers;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
delete settings.mcpServers;
|
|
134
|
+
}
|
|
135
|
+
writeWorkspaceSettings(group, settings);
|
|
136
|
+
}
|
|
137
|
+
// --- Middleware: resolve group + access check ---
|
|
138
|
+
function resolveGroup(c) {
|
|
139
|
+
const jid = c.req.param('jid');
|
|
140
|
+
const authUser = c.get('user');
|
|
141
|
+
const group = getRegisteredGroup(jid);
|
|
142
|
+
if (!group) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
if (!canAccessGroup(authUser, group)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
return group;
|
|
149
|
+
}
|
|
150
|
+
// ===========================
|
|
151
|
+
// Skills API
|
|
152
|
+
// ===========================
|
|
153
|
+
// GET /workspace-config/skills — list workspace skills
|
|
154
|
+
workspaceConfigRoutes.get('/:jid/workspace-config/skills', authMiddleware, async (c) => {
|
|
155
|
+
const group = resolveGroup(c);
|
|
156
|
+
if (!group)
|
|
157
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
158
|
+
const skillsDir = getWorkspaceSkillsDir(group);
|
|
159
|
+
const skills = scanSkillDirectory(skillsDir, 'workspace');
|
|
160
|
+
return c.json({ skills });
|
|
161
|
+
});
|
|
162
|
+
// POST /workspace-config/skills/install — install skill to workspace
|
|
163
|
+
workspaceConfigRoutes.post('/:jid/workspace-config/skills/install', authMiddleware, async (c) => {
|
|
164
|
+
const group = resolveGroup(c);
|
|
165
|
+
if (!group)
|
|
166
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
167
|
+
const body = await c.req.json().catch(() => ({}));
|
|
168
|
+
const pkg = typeof body.package === 'string' ? body.package.trim() : '';
|
|
169
|
+
if (!/^[\w\-]+\/[\w\-.]+(?:[@#][\w\-.\/]+)?$/.test(pkg) &&
|
|
170
|
+
!/^https?:\/\//.test(pkg)) {
|
|
171
|
+
return c.json({ error: 'Invalid package name format' }, 400);
|
|
172
|
+
}
|
|
173
|
+
// Install to a temp HOME, then copy to workspace skills dir
|
|
174
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-skill-install-'));
|
|
175
|
+
const tempSkillsDir = path.join(tempHome, '.claude', 'skills');
|
|
176
|
+
fs.mkdirSync(tempSkillsDir, { recursive: true });
|
|
177
|
+
try {
|
|
178
|
+
await execFileAsync('npx', ['-y', 'skills', 'add', pkg, '--global', '--yes', '-a', 'claude-code'], {
|
|
179
|
+
timeout: 60_000,
|
|
180
|
+
env: { ...process.env, HOME: tempHome },
|
|
181
|
+
});
|
|
182
|
+
// Discover installed skill directories
|
|
183
|
+
const installedEntries = [];
|
|
184
|
+
if (fs.existsSync(tempSkillsDir)) {
|
|
185
|
+
for (const entry of fs.readdirSync(tempSkillsDir, {
|
|
186
|
+
withFileTypes: true,
|
|
187
|
+
})) {
|
|
188
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
189
|
+
installedEntries.push(entry.name);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (installedEntries.length === 0) {
|
|
194
|
+
return c.json({ error: 'No skills were installed — package may be invalid' }, 500);
|
|
195
|
+
}
|
|
196
|
+
// Copy to workspace skills directory
|
|
197
|
+
const targetDir = getWorkspaceSkillsDir(group);
|
|
198
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
199
|
+
for (const name of installedEntries) {
|
|
200
|
+
const src = path.join(tempSkillsDir, name);
|
|
201
|
+
const dest = path.join(targetDir, name);
|
|
202
|
+
if (fs.existsSync(dest)) {
|
|
203
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
204
|
+
}
|
|
205
|
+
// Resolve symlinks and copy real content
|
|
206
|
+
let realSrc = src;
|
|
207
|
+
try {
|
|
208
|
+
const lstat = fs.lstatSync(src);
|
|
209
|
+
if (lstat.isSymbolicLink()) {
|
|
210
|
+
realSrc = fs.realpathSync(src);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// use src as-is
|
|
215
|
+
}
|
|
216
|
+
fs.cpSync(realSrc, dest, { recursive: true });
|
|
217
|
+
}
|
|
218
|
+
return c.json({ success: true, installed: installedEntries });
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return c.json({
|
|
222
|
+
error: 'Failed to install skill',
|
|
223
|
+
details: error instanceof Error ? error.message : 'Unknown error',
|
|
224
|
+
}, 500);
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
try {
|
|
228
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
/* ignore */
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// PATCH /workspace-config/skills/:id — enable/disable
|
|
236
|
+
workspaceConfigRoutes.patch('/:jid/workspace-config/skills/:id', authMiddleware, async (c) => {
|
|
237
|
+
const group = resolveGroup(c);
|
|
238
|
+
if (!group)
|
|
239
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
240
|
+
const id = c.req.param('id');
|
|
241
|
+
if (!validateSkillId(id)) {
|
|
242
|
+
return c.json({ error: 'Invalid skill ID' }, 400);
|
|
243
|
+
}
|
|
244
|
+
const { enabled } = await c.req.json();
|
|
245
|
+
const skillsDir = getWorkspaceSkillsDir(group);
|
|
246
|
+
const skillDir = path.join(skillsDir, id);
|
|
247
|
+
if (!fs.existsSync(skillDir)) {
|
|
248
|
+
return c.json({ error: 'Skill not found' }, 404);
|
|
249
|
+
}
|
|
250
|
+
if (!validateSkillPath(skillsDir, skillDir)) {
|
|
251
|
+
return c.json({ error: 'Invalid skill path' }, 400);
|
|
252
|
+
}
|
|
253
|
+
const srcPath = path.join(skillDir, enabled ? 'SKILL.md.disabled' : 'SKILL.md');
|
|
254
|
+
const dstPath = path.join(skillDir, enabled ? 'SKILL.md' : 'SKILL.md.disabled');
|
|
255
|
+
if (!fs.existsSync(srcPath)) {
|
|
256
|
+
return c.json({ error: 'Skill not found or already in desired state' }, 404);
|
|
257
|
+
}
|
|
258
|
+
fs.renameSync(srcPath, dstPath);
|
|
259
|
+
return c.json({ success: true });
|
|
260
|
+
});
|
|
261
|
+
// DELETE /workspace-config/skills/:id — delete skill
|
|
262
|
+
workspaceConfigRoutes.delete('/:jid/workspace-config/skills/:id', authMiddleware, async (c) => {
|
|
263
|
+
const group = resolveGroup(c);
|
|
264
|
+
if (!group)
|
|
265
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
266
|
+
const id = c.req.param('id');
|
|
267
|
+
if (!validateSkillId(id)) {
|
|
268
|
+
return c.json({ error: 'Invalid skill ID' }, 400);
|
|
269
|
+
}
|
|
270
|
+
const skillsDir = getWorkspaceSkillsDir(group);
|
|
271
|
+
const skillDir = path.join(skillsDir, id);
|
|
272
|
+
if (!fs.existsSync(skillDir)) {
|
|
273
|
+
return c.json({ error: 'Skill not found' }, 404);
|
|
274
|
+
}
|
|
275
|
+
if (!validateSkillPath(skillsDir, skillDir)) {
|
|
276
|
+
return c.json({ error: 'Invalid skill path' }, 400);
|
|
277
|
+
}
|
|
278
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
279
|
+
return c.json({ success: true });
|
|
280
|
+
});
|
|
281
|
+
// ===========================
|
|
282
|
+
// MCP Servers API
|
|
283
|
+
// ===========================
|
|
284
|
+
// GET /workspace-config/mcp-servers — list workspace MCP servers
|
|
285
|
+
workspaceConfigRoutes.get('/:jid/workspace-config/mcp-servers', authMiddleware, async (c) => {
|
|
286
|
+
const group = resolveGroup(c);
|
|
287
|
+
if (!group)
|
|
288
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
289
|
+
const meta = readWorkspaceMeta(group);
|
|
290
|
+
const settings = readWorkspaceSettings(group);
|
|
291
|
+
const settingsMcp = settings.mcpServers || {};
|
|
292
|
+
// Merge: metadata has full info; also discover servers in settings.json
|
|
293
|
+
// that aren't in metadata (e.g. manually added by user)
|
|
294
|
+
const servers = [];
|
|
295
|
+
// From metadata
|
|
296
|
+
for (const [id, entry] of Object.entries(meta.mcpServers)) {
|
|
297
|
+
servers.push({ id, ...entry });
|
|
298
|
+
}
|
|
299
|
+
// From settings.json (not in metadata = externally added)
|
|
300
|
+
for (const [id, entry] of Object.entries(settingsMcp)) {
|
|
301
|
+
if (meta.mcpServers[id])
|
|
302
|
+
continue; // already covered
|
|
303
|
+
const isHttpType = entry.type === 'http' || entry.type === 'sse';
|
|
304
|
+
servers.push({
|
|
305
|
+
id,
|
|
306
|
+
enabled: true, // in settings.json = enabled
|
|
307
|
+
addedAt: '',
|
|
308
|
+
...(isHttpType
|
|
309
|
+
? {
|
|
310
|
+
type: entry.type,
|
|
311
|
+
url: entry.url,
|
|
312
|
+
...(entry.headers
|
|
313
|
+
? { headers: entry.headers }
|
|
314
|
+
: {}),
|
|
315
|
+
}
|
|
316
|
+
: {
|
|
317
|
+
command: entry.command,
|
|
318
|
+
...(entry.args ? { args: entry.args } : {}),
|
|
319
|
+
...(entry.env
|
|
320
|
+
? { env: entry.env }
|
|
321
|
+
: {}),
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return c.json({ servers });
|
|
326
|
+
});
|
|
327
|
+
// POST /workspace-config/mcp-servers — add MCP server
|
|
328
|
+
workspaceConfigRoutes.post('/:jid/workspace-config/mcp-servers', authMiddleware, async (c) => {
|
|
329
|
+
const group = resolveGroup(c);
|
|
330
|
+
if (!group)
|
|
331
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
332
|
+
const body = await c.req.json().catch(() => ({}));
|
|
333
|
+
const { id, command, args, env, description, type, url, headers } = body;
|
|
334
|
+
if (!id || typeof id !== 'string') {
|
|
335
|
+
return c.json({ error: 'id is required' }, 400);
|
|
336
|
+
}
|
|
337
|
+
if (!/^[\w\-]+$/.test(id)) {
|
|
338
|
+
return c.json({ error: 'Invalid server ID' }, 400);
|
|
339
|
+
}
|
|
340
|
+
const isHttpType = type === 'http' || type === 'sse';
|
|
341
|
+
if (isHttpType) {
|
|
342
|
+
if (!url || typeof url !== 'string') {
|
|
343
|
+
return c.json({ error: 'url is required for http/sse type' }, 400);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
if (!command || typeof command !== 'string') {
|
|
348
|
+
return c.json({ error: 'command is required' }, 400);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const meta = readWorkspaceMeta(group);
|
|
352
|
+
if (meta.mcpServers[id]) {
|
|
353
|
+
return c.json({ error: `Server "${id}" already exists` }, 409);
|
|
354
|
+
}
|
|
355
|
+
const entry = {
|
|
356
|
+
enabled: true,
|
|
357
|
+
addedAt: new Date().toISOString(),
|
|
358
|
+
...(description ? { description } : {}),
|
|
359
|
+
};
|
|
360
|
+
if (isHttpType) {
|
|
361
|
+
entry.type = type;
|
|
362
|
+
entry.url = url;
|
|
363
|
+
if (headers && Object.keys(headers).length > 0)
|
|
364
|
+
entry.headers = headers;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
entry.command = command;
|
|
368
|
+
if (args && args.length > 0)
|
|
369
|
+
entry.args = args;
|
|
370
|
+
if (env && Object.keys(env).length > 0)
|
|
371
|
+
entry.env = env;
|
|
372
|
+
}
|
|
373
|
+
meta.mcpServers[id] = entry;
|
|
374
|
+
writeWorkspaceMeta(group, meta);
|
|
375
|
+
syncMcpToSettings(group, meta);
|
|
376
|
+
return c.json({ success: true, server: { id, ...entry } });
|
|
377
|
+
});
|
|
378
|
+
// PATCH /workspace-config/mcp-servers/:id — update/enable/disable
|
|
379
|
+
workspaceConfigRoutes.patch('/:jid/workspace-config/mcp-servers/:id', authMiddleware, async (c) => {
|
|
380
|
+
const group = resolveGroup(c);
|
|
381
|
+
if (!group)
|
|
382
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
383
|
+
const id = c.req.param('id');
|
|
384
|
+
if (!/^[\w\-]+$/.test(id)) {
|
|
385
|
+
return c.json({ error: 'Invalid server ID' }, 400);
|
|
386
|
+
}
|
|
387
|
+
const body = await c.req.json().catch(() => ({}));
|
|
388
|
+
const { command, args, env, enabled, description, url, headers } = body;
|
|
389
|
+
const meta = readWorkspaceMeta(group);
|
|
390
|
+
let entry = meta.mcpServers[id];
|
|
391
|
+
// If not in metadata, check settings.json for externally added servers
|
|
392
|
+
if (!entry) {
|
|
393
|
+
const settings = readWorkspaceSettings(group);
|
|
394
|
+
const settingsMcp = settings.mcpServers || {};
|
|
395
|
+
const settingsEntry = settingsMcp[id];
|
|
396
|
+
if (!settingsEntry) {
|
|
397
|
+
return c.json({ error: 'Server not found' }, 404);
|
|
398
|
+
}
|
|
399
|
+
// Import from settings into metadata
|
|
400
|
+
const isHttp = settingsEntry.type === 'http' || settingsEntry.type === 'sse';
|
|
401
|
+
entry = {
|
|
402
|
+
enabled: true,
|
|
403
|
+
addedAt: '',
|
|
404
|
+
...(isHttp
|
|
405
|
+
? {
|
|
406
|
+
type: settingsEntry.type,
|
|
407
|
+
url: settingsEntry.url,
|
|
408
|
+
}
|
|
409
|
+
: { command: settingsEntry.command }),
|
|
410
|
+
};
|
|
411
|
+
meta.mcpServers[id] = entry;
|
|
412
|
+
}
|
|
413
|
+
if (command !== undefined)
|
|
414
|
+
entry.command = command;
|
|
415
|
+
if (args !== undefined)
|
|
416
|
+
entry.args = args;
|
|
417
|
+
if (env !== undefined)
|
|
418
|
+
entry.env = env;
|
|
419
|
+
if (url !== undefined)
|
|
420
|
+
entry.url = url;
|
|
421
|
+
if (headers !== undefined)
|
|
422
|
+
entry.headers = headers;
|
|
423
|
+
if (typeof enabled === 'boolean')
|
|
424
|
+
entry.enabled = enabled;
|
|
425
|
+
if (description !== undefined) {
|
|
426
|
+
entry.description =
|
|
427
|
+
typeof description === 'string' ? description : undefined;
|
|
428
|
+
}
|
|
429
|
+
writeWorkspaceMeta(group, meta);
|
|
430
|
+
syncMcpToSettings(group, meta);
|
|
431
|
+
return c.json({ success: true, server: { id, ...entry } });
|
|
432
|
+
});
|
|
433
|
+
// DELETE /workspace-config/mcp-servers/:id — delete MCP server
|
|
434
|
+
workspaceConfigRoutes.delete('/:jid/workspace-config/mcp-servers/:id', authMiddleware, async (c) => {
|
|
435
|
+
const group = resolveGroup(c);
|
|
436
|
+
if (!group)
|
|
437
|
+
return c.json({ error: 'Group not found or access denied' }, 404);
|
|
438
|
+
const id = c.req.param('id');
|
|
439
|
+
if (!/^[\w\-]+$/.test(id)) {
|
|
440
|
+
return c.json({ error: 'Invalid server ID' }, 400);
|
|
441
|
+
}
|
|
442
|
+
const meta = readWorkspaceMeta(group);
|
|
443
|
+
const hadMeta = !!meta.mcpServers[id];
|
|
444
|
+
delete meta.mcpServers[id];
|
|
445
|
+
// Also remove from settings.json directly
|
|
446
|
+
const settings = readWorkspaceSettings(group);
|
|
447
|
+
const settingsMcp = settings.mcpServers || {};
|
|
448
|
+
const hadSettings = id in settingsMcp;
|
|
449
|
+
if (!hadMeta && !hadSettings) {
|
|
450
|
+
return c.json({ error: 'Server not found' }, 404);
|
|
451
|
+
}
|
|
452
|
+
if (hadMeta) {
|
|
453
|
+
writeWorkspaceMeta(group, meta);
|
|
454
|
+
}
|
|
455
|
+
syncMcpToSettings(group, meta, settings);
|
|
456
|
+
return c.json({ success: true });
|
|
457
|
+
});
|
|
458
|
+
export default workspaceConfigRoutes;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { APP_ROOT } from './app-root.js';
|
|
4
|
+
export function getRuntimeBuildArtifactPaths(appRoot = APP_ROOT) {
|
|
5
|
+
return {
|
|
6
|
+
backendBuildPath: path.resolve(appRoot, 'dist', 'index.js'),
|
|
7
|
+
backendPackagePath: path.resolve(appRoot, 'package.json'),
|
|
8
|
+
agentRunnerBuildPath: path.resolve(appRoot, 'container', 'agent-runner', 'dist', 'index.js'),
|
|
9
|
+
agentRunnerPackagePath: path.resolve(appRoot, 'container', 'agent-runner', 'package.json'),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function readPackageVersion(packageJsonPath) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
15
|
+
return typeof parsed.version === 'string' ? parsed.version : null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function readBuildFingerprint(filePath, version = null) {
|
|
22
|
+
const resolvedPath = path.resolve(filePath);
|
|
23
|
+
try {
|
|
24
|
+
const stat = fs.statSync(resolvedPath);
|
|
25
|
+
return {
|
|
26
|
+
path: resolvedPath,
|
|
27
|
+
version,
|
|
28
|
+
exists: true,
|
|
29
|
+
mtimeMs: stat.mtimeMs,
|
|
30
|
+
mtimeIso: stat.mtime.toISOString(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return {
|
|
35
|
+
path: resolvedPath,
|
|
36
|
+
version,
|
|
37
|
+
exists: false,
|
|
38
|
+
mtimeMs: null,
|
|
39
|
+
mtimeIso: null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function isBuildFingerprintStale(loaded, current) {
|
|
44
|
+
return (loaded.path !== current.path ||
|
|
45
|
+
loaded.exists !== current.exists ||
|
|
46
|
+
loaded.mtimeMs !== current.mtimeMs);
|
|
47
|
+
}
|
|
48
|
+
export function formatBuildFingerprintForLog(fingerprint) {
|
|
49
|
+
const version = fingerprint.version || 'unknown';
|
|
50
|
+
const mtime = fingerprint.mtimeIso || 'missing';
|
|
51
|
+
const suffix = fingerprint.exists ? `mtime=${mtime}` : 'missing';
|
|
52
|
+
return `${version} @ ${fingerprint.path} (${suffix})`;
|
|
53
|
+
}
|
|
54
|
+
export function createRuntimeBuildSnapshot() {
|
|
55
|
+
const artifactPaths = getRuntimeBuildArtifactPaths();
|
|
56
|
+
return {
|
|
57
|
+
pid: process.pid,
|
|
58
|
+
startedAt: new Date().toISOString(),
|
|
59
|
+
backend: readBuildFingerprint(artifactPaths.backendBuildPath, readPackageVersion(artifactPaths.backendPackagePath)),
|
|
60
|
+
agentRunner: readBuildFingerprint(artifactPaths.agentRunnerBuildPath, readPackageVersion(artifactPaths.agentRunnerPackagePath)),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function createRuntimeBuildStatus(snapshot, current = {}) {
|
|
64
|
+
const artifactPaths = getRuntimeBuildArtifactPaths();
|
|
65
|
+
const currentBackend = current.backend ||
|
|
66
|
+
readBuildFingerprint(artifactPaths.backendBuildPath, snapshot.backend.version ||
|
|
67
|
+
readPackageVersion(artifactPaths.backendPackagePath));
|
|
68
|
+
const currentAgentRunner = current.agentRunner ||
|
|
69
|
+
readBuildFingerprint(artifactPaths.agentRunnerBuildPath, snapshot.agentRunner.version ||
|
|
70
|
+
readPackageVersion(artifactPaths.agentRunnerPackagePath));
|
|
71
|
+
const backendStale = isBuildFingerprintStale(snapshot.backend, currentBackend);
|
|
72
|
+
const agentRunnerStale = isBuildFingerprintStale(snapshot.agentRunner, currentAgentRunner);
|
|
73
|
+
return {
|
|
74
|
+
pid: snapshot.pid,
|
|
75
|
+
startedAt: snapshot.startedAt,
|
|
76
|
+
backend: {
|
|
77
|
+
loaded: snapshot.backend,
|
|
78
|
+
current: currentBackend,
|
|
79
|
+
stale: backendStale,
|
|
80
|
+
},
|
|
81
|
+
agentRunner: {
|
|
82
|
+
loaded: snapshot.agentRunner,
|
|
83
|
+
current: currentAgentRunner,
|
|
84
|
+
stale: agentRunnerStale,
|
|
85
|
+
},
|
|
86
|
+
stale: backendStale || agentRunnerStale,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const STARTUP_RUNTIME_BUILD_SNAPSHOT = createRuntimeBuildSnapshot();
|
|
90
|
+
export function getRuntimeBuildStartupSnapshot() {
|
|
91
|
+
return STARTUP_RUNTIME_BUILD_SNAPSHOT;
|
|
92
|
+
}
|
|
93
|
+
export function getRuntimeBuildStatus() {
|
|
94
|
+
return createRuntimeBuildStatus(STARTUP_RUNTIME_BUILD_SNAPSHOT);
|
|
95
|
+
}
|
|
96
|
+
export function isRuntimeBuildStale() {
|
|
97
|
+
return getRuntimeBuildStatus().stale;
|
|
98
|
+
}
|
|
99
|
+
export function getRuntimeBuildLogFields() {
|
|
100
|
+
const status = getRuntimeBuildStatus();
|
|
101
|
+
return {
|
|
102
|
+
staleBuild: status.stale,
|
|
103
|
+
backendPid: status.pid,
|
|
104
|
+
backendStartedAt: status.startedAt,
|
|
105
|
+
backendBuildLoaded: formatBuildFingerprintForLog(status.backend.loaded),
|
|
106
|
+
backendBuildCurrent: formatBuildFingerprintForLog(status.backend.current),
|
|
107
|
+
backendBuildStale: status.backend.stale,
|
|
108
|
+
agentRunnerBuildLoaded: formatBuildFingerprintForLog(status.agentRunner.loaded),
|
|
109
|
+
agentRunnerBuildCurrent: formatBuildFingerprintForLog(status.agentRunner.current),
|
|
110
|
+
agentRunnerBuildStale: status.agentRunner.stale,
|
|
111
|
+
};
|
|
112
|
+
}
|