@cloudcli-ai/cloudcli 1.36.3 → 1.37.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/dist/assets/index-D3jfzuKN.css +32 -0
- package/dist/assets/index-DTMWjJj8.js +1550 -0
- package/dist/index.html +2 -2
- package/dist-server/server/constants/config.js.map +1 -1
- package/dist-server/server/index.js +73 -1224
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +17 -4
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/agent/agent.module.js +37 -0
- package/dist-server/server/modules/agent/agent.module.js.map +1 -0
- package/dist-server/server/modules/agent/agent.routes.js +1161 -0
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -0
- package/dist-server/server/modules/agent/index.js +3 -0
- package/dist-server/server/modules/agent/index.js.map +1 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +176 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -0
- package/dist-server/server/modules/assets/assets.routes.js +56 -15
- package/dist-server/server/modules/assets/assets.routes.js.map +1 -1
- package/dist-server/server/modules/assets/index.js +2 -2
- package/dist-server/server/modules/assets/index.js.map +1 -1
- package/dist-server/server/modules/assets/services/image-assets.service.js +39 -1
- package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -1
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js +21 -1
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -1
- package/dist-server/server/{middleware/auth.js → modules/auth/auth.middleware.js} +30 -8
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -0
- package/dist-server/server/modules/auth/auth.module.js +29 -0
- package/dist-server/server/modules/auth/auth.module.js.map +1 -0
- package/dist-server/server/modules/auth/auth.routes.js +45 -0
- package/dist-server/server/modules/auth/auth.routes.js.map +1 -0
- package/dist-server/server/modules/auth/auth.service.js +113 -0
- package/dist-server/server/modules/auth/auth.service.js.map +1 -0
- package/dist-server/server/modules/auth/index.js +9 -0
- package/dist-server/server/modules/auth/index.js.map +1 -0
- package/dist-server/server/modules/auth/tests/auth.service.test.js +80 -0
- package/dist-server/server/modules/auth/tests/auth.service.test.js.map +1 -0
- package/dist-server/server/{browser-use-mcp.js → modules/browser-use/browser-use-mcp.js} +3 -1
- package/dist-server/server/modules/browser-use/browser-use-mcp.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +3 -4
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
- package/dist-server/server/modules/browser-use/index.js +9 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -0
- package/dist-server/server/modules/cli/cli.js +11 -0
- package/dist-server/server/modules/cli/cli.js.map +1 -0
- package/dist-server/server/modules/cli/cli.module.js +86 -0
- package/dist-server/server/modules/cli/cli.module.js.map +1 -0
- package/dist-server/server/modules/cli/cli.service.js +244 -0
- package/dist-server/server/modules/cli/cli.service.js.map +1 -0
- package/dist-server/server/modules/cli/index.js +3 -0
- package/dist-server/server/modules/cli/index.js.map +1 -0
- package/dist-server/server/modules/cli/sandbox.service.js +332 -0
- package/dist-server/server/modules/cli/sandbox.service.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +78 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/sandbox.service.test.js +55 -0
- package/dist-server/server/modules/cli/tests/sandbox.service.test.js.map +1 -0
- package/dist-server/server/modules/commands/commands.module.js +20 -0
- package/dist-server/server/modules/commands/commands.module.js.map +1 -0
- package/dist-server/server/modules/commands/commands.routes.js +497 -0
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -0
- package/dist-server/server/modules/commands/index.js +3 -0
- package/dist-server/server/modules/commands/index.js.map +1 -0
- package/dist-server/server/modules/commands/tests/commands.test.js +91 -0
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -0
- package/dist-server/server/modules/database/index.js +3 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +31 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +76 -7
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +29 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -1
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +22 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js +95 -0
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.routes.js +208 -0
- package/dist-server/server/modules/file-tree/file-tree.routes.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js +576 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -0
- package/dist-server/server/modules/file-tree/index.js +3 -0
- package/dist-server/server/modules/file-tree/index.js.map +1 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js +123 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +260 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -0
- package/dist-server/server/modules/git/git-parsing.service.js +61 -0
- package/dist-server/server/modules/git/git-parsing.service.js.map +1 -0
- package/dist-server/server/modules/git/git.module.js +14 -0
- package/dist-server/server/modules/git/git.module.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +1339 -0
- package/dist-server/server/modules/git/git.routes.js.map +1 -0
- package/dist-server/server/modules/git/index.js +3 -0
- package/dist-server/server/modules/git/index.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +48 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -0
- package/dist-server/server/{routes → modules/git/tests}/git.test.js +1 -1
- package/dist-server/server/modules/git/tests/git.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +15 -1
- package/dist-server/server/modules/notifications/index.js.map +1 -1
- package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js +44 -0
- package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js.map +1 -0
- package/dist-server/server/{services/vapid-keys.js → modules/notifications/vapid-keys.service.js} +3 -2
- package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -0
- package/dist-server/server/modules/plugins/index.js +9 -0
- package/dist-server/server/modules/plugins/index.js.map +1 -0
- package/dist-server/server/{utils/plugin-process-manager.js → modules/plugins/plugin-process.service.js} +3 -2
- package/dist-server/server/modules/plugins/plugin-process.service.js.map +1 -0
- package/dist-server/server/{utils/plugin-loader.js → modules/plugins/plugin-registry.service.js} +2 -1
- package/dist-server/server/modules/plugins/plugin-registry.service.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.module.js +30 -0
- package/dist-server/server/modules/plugins/plugins.module.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.routes.js +70 -0
- package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.service.js +123 -0
- package/dist-server/server/modules/plugins/plugins.service.js.map +1 -0
- package/dist-server/server/modules/plugins/tests/plugins.service.test.js +28 -0
- package/dist-server/server/modules/plugins/tests/plugins.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/index.js +5 -0
- package/dist-server/server/modules/projects/index.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +3 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +6 -0
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +47 -18
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/{claude-sdk.js → modules/providers/list/claude/claude-runtime.provider.js} +69 -55
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +6 -4
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +23 -4
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +59 -75
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/{openai-codex.js → modules/providers/list/codex/codex-runtime.provider.js} +48 -34
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +1 -54
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +290 -6
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +5 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +33 -673
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
- package/dist-server/server/{cursor-cli.js → modules/providers/list/cursor/cursor-runtime.provider.js} +40 -28
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +21 -14
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +112 -333
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/{opencode-cli.js → modules/providers/list/opencode/opencode-runtime.provider.js} +38 -25
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -0
- package/dist-server/server/{opencode-cli.test.js → modules/providers/list/opencode/opencode-runtime.provider.test.js} +27 -4
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +0 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +11 -5
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +2 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +169 -37
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +5 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +247 -184
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +66 -0
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +268 -0
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +12 -4
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +2 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +116 -3
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js +2 -3
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +122 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js +44 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +76 -4
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +15 -126
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +5 -5
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/{provider-image-history.test.js → provider-attachment-history.test.js} +60 -2
- package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +241 -271
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +126 -0
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +166 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +126 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions-details.test.js +62 -0
- package/dist-server/server/modules/providers/tests/sessions-details.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +87 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/skills.test.js +4 -2
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/settings/index.js +3 -0
- package/dist-server/server/modules/settings/index.js.map +1 -0
- package/dist-server/server/modules/settings/settings.module.js +35 -0
- package/dist-server/server/modules/settings/settings.module.js.map +1 -0
- package/dist-server/server/modules/settings/settings.routes.js +34 -0
- package/dist-server/server/modules/settings/settings.routes.js.map +1 -0
- package/dist-server/server/modules/settings/settings.service.js +116 -0
- package/dist-server/server/modules/settings/settings.service.js.map +1 -0
- package/dist-server/server/modules/settings/tests/settings.service.test.js +48 -0
- package/dist-server/server/modules/settings/tests/settings.service.test.js.map +1 -0
- package/dist-server/server/modules/system/index.js +3 -0
- package/dist-server/server/modules/system/index.js.map +1 -0
- package/dist-server/server/modules/system/system.module.js +44 -0
- package/dist-server/server/modules/system/system.module.js.map +1 -0
- package/dist-server/server/modules/system/system.routes.js +16 -0
- package/dist-server/server/modules/system/system.routes.js.map +1 -0
- package/dist-server/server/modules/system/system.service.js +45 -0
- package/dist-server/server/modules/system/system.service.js.map +1 -0
- package/dist-server/server/modules/system/tests/system.service.test.js +100 -0
- package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/index.js +3 -0
- package/dist-server/server/modules/taskmaster/index.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.module.js +20 -0
- package/dist-server/server/modules/taskmaster/taskmaster.module.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +1372 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.service.js +107 -0
- package/dist-server/server/modules/taskmaster/taskmaster.service.js.map +1 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +132 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js +85 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js.map +1 -0
- package/dist-server/server/modules/user/index.js +3 -0
- package/dist-server/server/modules/user/index.js.map +1 -0
- package/dist-server/server/modules/user/tests/user.service.test.js +53 -0
- package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -0
- package/dist-server/server/modules/user/user.module.js +47 -0
- package/dist-server/server/modules/user/user.module.js.map +1 -0
- package/dist-server/server/modules/user/user.routes.js +44 -0
- package/dist-server/server/modules/user/user.routes.js.map +1 -0
- package/dist-server/server/modules/user/user.service.js +59 -0
- package/dist-server/server/modules/user/user.service.js.map +1 -0
- package/dist-server/server/modules/voice/index.js +3 -0
- package/dist-server/server/modules/voice/index.js.map +1 -0
- package/dist-server/server/modules/voice/tests/voice.service.test.js +86 -0
- package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -0
- package/dist-server/server/modules/voice/voice.module.js +44 -0
- package/dist-server/server/modules/voice/voice.module.js.map +1 -0
- package/dist-server/server/modules/voice/voice.routes.js +87 -0
- package/dist-server/server/modules/voice/voice.routes.js.map +1 -0
- package/dist-server/server/modules/voice/voice.service.js +148 -0
- package/dist-server/server/modules/voice/voice.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +2 -2
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +44 -29
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +76 -8
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +51 -20
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/{chat-image-filter.test.js → chat-attachment-filter.test.js} +10 -3
- package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +97 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js +61 -0
- package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/index.js +3 -0
- package/dist-server/server/modules/worktrees/index.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js +37 -0
- package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-create.service.js +67 -0
- package/dist-server/server/modules/worktrees/services/worktree-create.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-git.service.js +172 -0
- package/dist-server/server/modules/worktrees/services/worktree-git.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js +91 -0
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-merge.service.js +118 -0
- package/dist-server/server/modules/worktrees/services/worktree-merge.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js +58 -0
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +65 -0
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js +58 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js +85 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js +58 -0
- package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js +34 -0
- package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js +137 -0
- package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js +99 -0
- package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js +157 -0
- package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js +167 -0
- package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/worktrees.module.js +97 -0
- package/dist-server/server/modules/worktrees/worktrees.module.js.map +1 -0
- package/dist-server/server/modules/worktrees/worktrees.routes.js +84 -0
- package/dist-server/server/modules/worktrees/worktrees.routes.js.map +1 -0
- package/dist-server/server/shared/image-attachments.js +90 -17
- package/dist-server/server/shared/image-attachments.js.map +1 -1
- package/dist-server/server/shared/tests/image-attachments.test.js +43 -1
- package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -1
- package/dist-server/server/shared/utils.js +66 -142
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +9 -5
- package/server/index.ts +401 -0
- package/server/{load-env.js → load-env.ts} +18 -5
- package/server/modules/agent/agent.module.ts +51 -0
- package/server/modules/agent/agent.routes.ts +1287 -0
- package/server/modules/agent/index.ts +2 -0
- package/server/modules/agent/tests/agent.routes.test.ts +205 -0
- package/server/modules/assets/assets.routes.ts +63 -16
- package/server/modules/assets/index.ts +2 -2
- package/server/modules/assets/services/image-assets.service.ts +46 -1
- package/server/modules/assets/tests/image-assets.service.test.ts +28 -0
- package/server/{middleware/auth.js → modules/auth/auth.middleware.ts} +38 -7
- package/server/modules/auth/auth.module.ts +38 -0
- package/server/modules/auth/auth.routes.ts +57 -0
- package/server/modules/auth/auth.service.ts +155 -0
- package/server/modules/auth/index.ts +9 -0
- package/server/modules/auth/tests/auth.service.test.ts +95 -0
- package/server/{browser-use-mcp.ts → modules/browser-use/browser-use-mcp.ts} +3 -1
- package/server/modules/browser-use/browser-use.service.ts +3 -4
- package/server/modules/browser-use/index.ts +8 -0
- package/server/modules/cli/cli.module.ts +96 -0
- package/server/modules/cli/cli.service.ts +281 -0
- package/server/modules/cli/cli.ts +13 -0
- package/server/modules/cli/index.ts +2 -0
- package/server/modules/cli/sandbox.service.ts +413 -0
- package/server/modules/cli/tests/cli.service.test.ts +91 -0
- package/server/modules/cli/tests/sandbox.service.test.ts +61 -0
- package/server/modules/commands/commands.module.ts +22 -0
- package/server/{routes/commands.js → modules/commands/commands.routes.ts} +46 -38
- package/server/modules/commands/index.ts +2 -0
- package/server/modules/commands/tests/commands.test.ts +114 -0
- package/server/modules/database/index.ts +3 -0
- package/server/modules/database/migrations.ts +35 -0
- package/server/modules/database/repositories/provider-models.ts +168 -0
- package/server/modules/database/repositories/sessions.db.ts +103 -8
- package/server/modules/database/schema.ts +30 -0
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -1
- package/server/modules/database/tests/sessions.db.integration.test.ts +32 -0
- package/server/modules/file-tree/file-tree.module.ts +116 -0
- package/server/modules/file-tree/file-tree.routes.ts +258 -0
- package/server/modules/file-tree/file-tree.service.ts +664 -0
- package/server/modules/file-tree/index.ts +2 -0
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -0
- package/server/modules/file-tree/tests/file-tree.service.test.ts +317 -0
- package/server/modules/git/git-parsing.service.ts +66 -0
- package/server/modules/git/git.module.ts +22 -0
- package/server/{routes/git.js → modules/git/git.routes.ts} +91 -132
- package/server/modules/git/index.ts +2 -0
- package/server/modules/git/tests/git-init.routes.test.ts +52 -0
- package/server/{routes/git.test.js → modules/git/tests/git.test.ts} +3 -3
- package/server/modules/notifications/index.ts +9 -0
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -0
- package/server/{services/vapid-keys.js → modules/notifications/vapid-keys.service.ts} +3 -1
- package/server/modules/plugins/index.ts +9 -0
- package/server/{utils/plugin-process-manager.js → modules/plugins/plugin-process.service.ts} +3 -1
- package/server/{utils/plugin-loader.js → modules/plugins/plugin-registry.service.ts} +1 -0
- package/server/modules/plugins/plugins.module.ts +33 -0
- package/server/modules/plugins/plugins.routes.ts +66 -0
- package/server/modules/plugins/plugins.service.ts +147 -0
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -0
- package/server/modules/projects/index.ts +5 -0
- package/server/modules/providers/README.md +41 -9
- package/server/modules/providers/index.ts +4 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +8 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +47 -26
- package/server/{claude-sdk.js → modules/providers/list/claude/claude-runtime.provider.js} +73 -59
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +6 -4
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +27 -4
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +58 -108
- package/server/{openai-codex.js → modules/providers/list/codex/codex-runtime.provider.js} +54 -36
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +1 -60
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +331 -7
- package/server/modules/providers/list/codex/codex-skills.provider.ts +5 -0
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +32 -719
- package/server/{cursor-cli.js → modules/providers/list/cursor/cursor-runtime.provider.js} +58 -27
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +26 -15
- package/server/modules/providers/list/cursor/cursor.provider.ts +3 -0
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +112 -423
- package/server/{opencode-cli.js → modules/providers/list/opencode/opencode-runtime.provider.js} +56 -28
- package/server/{opencode-cli.test.js → modules/providers/list/opencode/opencode-runtime.provider.test.js} +41 -3
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +0 -13
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +13 -5
- package/server/modules/providers/list/opencode/opencode.provider.ts +3 -0
- package/server/modules/providers/provider.routes.ts +228 -39
- package/server/modules/providers/services/provider-capabilities.service.ts +7 -1
- package/server/modules/providers/services/provider-models.service.ts +312 -269
- package/server/modules/providers/services/provider-runtime.service.ts +108 -0
- package/server/modules/providers/services/provider-token-usage.service.ts +357 -0
- package/server/modules/providers/services/session-conversations-search.service.ts +12 -4
- package/server/modules/providers/services/sessions-watcher.service.ts +2 -0
- package/server/modules/providers/services/sessions.service.ts +166 -3
- package/server/modules/providers/shared/base/abstract.provider.ts +4 -3
- package/server/modules/providers/tests/claude-auth.test.ts +150 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +61 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +83 -4
- package/server/modules/providers/tests/opencode-models.test.ts +34 -135
- package/server/modules/providers/tests/opencode-sessions.test.ts +5 -5
- package/server/modules/providers/tests/{provider-image-history.test.ts → provider-attachment-history.test.ts} +68 -1
- package/server/modules/providers/tests/provider-models.service.test.ts +300 -296
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -0
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -0
- package/server/modules/providers/tests/provider.routes.test.ts +183 -0
- package/server/modules/providers/tests/sessions-details.test.ts +73 -0
- package/server/modules/providers/tests/sessions.service.test.ts +127 -0
- package/server/modules/providers/tests/skills.test.ts +9 -2
- package/server/modules/settings/index.ts +2 -0
- package/server/modules/settings/settings.module.ts +50 -0
- package/server/modules/settings/settings.routes.ts +51 -0
- package/server/modules/settings/settings.service.ts +186 -0
- package/server/modules/settings/tests/settings.service.test.ts +54 -0
- package/server/modules/system/index.ts +2 -0
- package/server/modules/system/system.module.ts +62 -0
- package/server/modules/system/system.routes.ts +21 -0
- package/server/modules/system/system.service.ts +76 -0
- package/server/modules/system/tests/system.service.test.ts +118 -0
- package/server/modules/taskmaster/index.ts +2 -0
- package/server/modules/taskmaster/taskmaster.module.ts +24 -0
- package/server/modules/taskmaster/taskmaster.routes.ts +1482 -0
- package/server/modules/taskmaster/taskmaster.service.ts +132 -0
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -0
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -0
- package/server/modules/user/index.ts +2 -0
- package/server/modules/user/tests/user.service.test.ts +61 -0
- package/server/modules/user/user.module.ts +58 -0
- package/server/modules/user/user.routes.ts +50 -0
- package/server/modules/user/user.service.ts +85 -0
- package/server/modules/voice/index.ts +2 -0
- package/server/modules/voice/tests/voice.service.test.ts +98 -0
- package/server/modules/voice/voice.module.ts +47 -0
- package/server/modules/voice/voice.routes.ts +113 -0
- package/server/modules/voice/voice.service.ts +192 -0
- package/server/modules/websocket/README.md +7 -7
- package/server/modules/websocket/services/chat-session-writer.service.ts +2 -2
- package/server/modules/websocket/services/chat-websocket.service.ts +77 -61
- package/server/modules/websocket/services/shell-websocket.service.ts +88 -12
- package/server/modules/websocket/services/websocket-server.service.ts +63 -19
- package/server/modules/websocket/tests/{chat-image-filter.test.ts → chat-attachment-filter.test.ts} +17 -2
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +119 -0
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -0
- package/server/modules/worktrees/index.ts +2 -0
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -0
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -0
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -0
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -0
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -0
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -0
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -0
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -0
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -0
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -0
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -0
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -0
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -0
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -0
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -0
- package/server/modules/worktrees/worktrees.module.ts +113 -0
- package/server/modules/worktrees/worktrees.routes.ts +116 -0
- package/server/shared/image-attachments.ts +112 -18
- package/server/shared/interfaces.ts +33 -25
- package/server/shared/tests/image-attachments.test.ts +55 -0
- package/server/shared/types.ts +685 -36
- package/server/shared/utils.ts +77 -212
- package/dist/assets/index-CcCsKitq.js +0 -1423
- package/dist/assets/index-DLmhQ15L.css +0 -32
- package/dist-server/server/browser-use-mcp.js.map +0 -1
- package/dist-server/server/claude-sdk.js.map +0 -1
- package/dist-server/server/cli.js +0 -647
- package/dist-server/server/cli.js.map +0 -1
- package/dist-server/server/cursor-cli.js.map +0 -1
- package/dist-server/server/middleware/auth.js.map +0 -1
- package/dist-server/server/modules/providers/tests/provider-image-history.test.js.map +0 -1
- package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js.map +0 -1
- package/dist-server/server/openai-codex.js.map +0 -1
- package/dist-server/server/opencode-cli.js.map +0 -1
- package/dist-server/server/opencode-cli.test.js.map +0 -1
- package/dist-server/server/routes/agent.js +0 -1155
- package/dist-server/server/routes/agent.js.map +0 -1
- package/dist-server/server/routes/auth.js +0 -119
- package/dist-server/server/routes/auth.js.map +0 -1
- package/dist-server/server/routes/commands.js +0 -492
- package/dist-server/server/routes/commands.js.map +0 -1
- package/dist-server/server/routes/cursor.js +0 -49
- package/dist-server/server/routes/cursor.js.map +0 -1
- package/dist-server/server/routes/git.js +0 -1389
- package/dist-server/server/routes/git.js.map +0 -1
- package/dist-server/server/routes/git.test.js.map +0 -1
- package/dist-server/server/routes/mcp-utils.js +0 -29
- package/dist-server/server/routes/mcp-utils.js.map +0 -1
- package/dist-server/server/routes/plugins.js +0 -266
- package/dist-server/server/routes/plugins.js.map +0 -1
- package/dist-server/server/routes/settings.js +0 -249
- package/dist-server/server/routes/settings.js.map +0 -1
- package/dist-server/server/routes/taskmaster.js +0 -1362
- package/dist-server/server/routes/taskmaster.js.map +0 -1
- package/dist-server/server/routes/tests/commands.test.js +0 -76
- package/dist-server/server/routes/tests/commands.test.js.map +0 -1
- package/dist-server/server/routes/user.js +0 -116
- package/dist-server/server/routes/user.js.map +0 -1
- package/dist-server/server/services/notification-orchestrator.js +0 -2
- package/dist-server/server/services/notification-orchestrator.js.map +0 -1
- package/dist-server/server/services/tests/notification-orchestrator.test.js +0 -64
- package/dist-server/server/services/tests/notification-orchestrator.test.js.map +0 -1
- package/dist-server/server/services/vapid-keys.js.map +0 -1
- package/dist-server/server/utils/colors.js +0 -20
- package/dist-server/server/utils/colors.js.map +0 -1
- package/dist-server/server/utils/commandParser.js +0 -255
- package/dist-server/server/utils/commandParser.js.map +0 -1
- package/dist-server/server/utils/gitConfig.js +0 -37
- package/dist-server/server/utils/gitConfig.js.map +0 -1
- package/dist-server/server/utils/mcp-detector.js +0 -134
- package/dist-server/server/utils/mcp-detector.js.map +0 -1
- package/dist-server/server/utils/plugin-loader.js.map +0 -1
- package/dist-server/server/utils/plugin-process-manager.js.map +0 -1
- package/dist-server/server/utils/runtime-paths.js +0 -30
- package/dist-server/server/utils/runtime-paths.js.map +0 -1
- package/dist-server/server/utils/taskmaster-websocket.js +0 -124
- package/dist-server/server/utils/taskmaster-websocket.js.map +0 -1
- package/dist-server/server/utils/url-detection.js +0 -58
- package/dist-server/server/utils/url-detection.js.map +0 -1
- package/dist-server/server/voice-proxy.js +0 -226
- package/dist-server/server/voice-proxy.js.map +0 -1
- package/server/cli.js +0 -695
- package/server/index.js +0 -1651
- package/server/routes/agent.js +0 -1257
- package/server/routes/auth.js +0 -137
- package/server/routes/cursor.js +0 -52
- package/server/routes/mcp-utils.js +0 -31
- package/server/routes/plugins.js +0 -307
- package/server/routes/settings.js +0 -282
- package/server/routes/taskmaster.js +0 -1470
- package/server/routes/tests/commands.test.js +0 -82
- package/server/routes/user.js +0 -124
- package/server/services/notification-orchestrator.js +0 -7
- package/server/services/tests/notification-orchestrator.test.js +0 -80
- package/server/utils/colors.js +0 -21
- package/server/utils/commandParser.js +0 -305
- package/server/utils/gitConfig.js +0 -35
- package/server/utils/mcp-detector.js +0 -147
- package/server/utils/runtime-paths.js +0 -37
- package/server/utils/taskmaster-websocket.js +0 -135
- package/server/utils/url-detection.js +0 -71
- package/server/voice-proxy.js +0 -224
- /package/server/constants/{config.js → config.ts} +0 -0
|
@@ -1,55 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Load environment variables before other imports execute
|
|
2
|
+
// Load environment variables before other imports execute.
|
|
3
3
|
import './load-env.js';
|
|
4
4
|
import fs, { promises as fsPromises } from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import http from 'http';
|
|
8
|
-
// cross-spawn is a drop-in for child_process.spawn that resolves .cmd
|
|
9
|
-
// shims/PATHEXT on Windows and delegates to the native spawn elsewhere.
|
|
10
|
-
import spawn from 'cross-spawn';
|
|
11
8
|
import express from 'express';
|
|
12
9
|
import cors from 'cors';
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import { AppError, WORKSPACES_ROOT, getOpenCodeDatabasePath, validateWorkspacePath } from './shared/utils.js';
|
|
16
|
-
import { closeSessionsWatcher, initializeSessionsWatcher } from './modules/providers/index.js';
|
|
10
|
+
import { AppError, findApplicationRoot, getModuleDirectory, terminalTextStyles } from './shared/utils.js';
|
|
11
|
+
import { closeSessionsWatcher, initializeSessionsWatcher, providerRuntimeService, } from './modules/providers/index.js';
|
|
17
12
|
import { createWebSocketServer } from './modules/websocket/index.js';
|
|
18
13
|
import { getConnectableHost } from '../shared/networkHosts.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
26
|
-
import authRoutes from './routes/auth.js';
|
|
27
|
-
import cursorRoutes from './routes/cursor.js';
|
|
28
|
-
import taskmasterRoutes from './routes/taskmaster.js';
|
|
29
|
-
import mcpUtilsRoutes from './routes/mcp-utils.js';
|
|
30
|
-
import commandsRoutes from './routes/commands.js';
|
|
31
|
-
import settingsRoutes from './routes/settings.js';
|
|
32
|
-
import agentRoutes from './routes/agent.js';
|
|
14
|
+
import { createGitModule } from './modules/git/index.js';
|
|
15
|
+
import { authenticateToken, authenticateWebSocket, authRoutes, validateApiKey, } from './modules/auth/index.js';
|
|
16
|
+
import { taskmasterRoutes } from './modules/taskmaster/index.js';
|
|
17
|
+
import { commandsRoutes } from './modules/commands/index.js';
|
|
18
|
+
import { settingsRoutes } from './modules/settings/index.js';
|
|
19
|
+
import { createSystemModule } from './modules/system/index.js';
|
|
20
|
+
import { createAgentModule } from './modules/agent/index.js';
|
|
33
21
|
import projectModuleRoutes from './modules/projects/projects.routes.js';
|
|
34
22
|
import notificationRoutes from './modules/notifications/notifications.routes.js';
|
|
35
|
-
import userRoutes from './
|
|
36
|
-
import pluginsRoutes from './
|
|
23
|
+
import { userRoutes } from './modules/user/index.js';
|
|
24
|
+
import { getPluginPort, pluginsRoutes, startEnabledPluginServers, stopAllPlugins, } from './modules/plugins/index.js';
|
|
37
25
|
import providerRoutes from './modules/providers/provider.routes.js';
|
|
38
|
-
import voiceRoutes from './voice
|
|
26
|
+
import { voiceRoutes } from './modules/voice/index.js';
|
|
39
27
|
import browserUseRoutes from './modules/browser-use/browser-use.routes.js';
|
|
40
28
|
import { assetsRoutes } from './modules/assets/index.js';
|
|
29
|
+
import { fileTreeRoutes } from './modules/file-tree/index.js';
|
|
30
|
+
import { worktreesRoutes } from './modules/worktrees/index.js';
|
|
41
31
|
import browserUseMcpRoutes from './modules/browser-use/browser-use-mcp.routes.js';
|
|
42
32
|
import { browserUseService } from './modules/browser-use/browser-use.service.js';
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import { configureWebPush } from './services/vapid-keys.js';
|
|
46
|
-
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
|
|
33
|
+
import { initializeDatabase, sessionsDb } from './modules/database/index.js';
|
|
34
|
+
import { configureWebPush } from './modules/notifications/index.js';
|
|
47
35
|
import { IS_PLATFORM } from './constants/config.js';
|
|
48
|
-
|
|
49
|
-
const __dirname = getModuleDir(import.meta.url);
|
|
36
|
+
const __dirname = getModuleDirectory(import.meta.url);
|
|
50
37
|
// The server source runs from /server, while the compiled output runs from /dist-server/server.
|
|
51
38
|
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
|
|
52
|
-
const APP_ROOT =
|
|
39
|
+
const APP_ROOT = findApplicationRoot(__dirname);
|
|
53
40
|
const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
|
|
54
41
|
// Version of the code that is actually running, captured once at process
|
|
55
42
|
// startup. This intentionally does NOT re-read package.json per request: after
|
|
@@ -65,16 +52,28 @@ const RUNNING_VERSION = (() => {
|
|
|
65
52
|
return null;
|
|
66
53
|
}
|
|
67
54
|
})();
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
const systemRoutes = createSystemModule({
|
|
56
|
+
appRoot: APP_ROOT,
|
|
57
|
+
installMode,
|
|
58
|
+
isPlatform: IS_PLATFORM,
|
|
59
|
+
});
|
|
71
60
|
console.log('SERVER_PORT from env:', process.env.SERVER_PORT);
|
|
72
|
-
function readUsageNumber(value) {
|
|
73
|
-
const parsed = Number(value);
|
|
74
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
75
|
-
}
|
|
76
61
|
const app = express();
|
|
77
62
|
const server = http.createServer(app);
|
|
63
|
+
const queryClaude = providerRuntimeService.getRunner('claude');
|
|
64
|
+
const queryCursor = providerRuntimeService.getRunner('cursor');
|
|
65
|
+
const queryCodex = providerRuntimeService.getRunner('codex');
|
|
66
|
+
const queryOpenCode = providerRuntimeService.getRunner('opencode');
|
|
67
|
+
const gitRoutes = createGitModule({
|
|
68
|
+
queryClaude,
|
|
69
|
+
queryCursor,
|
|
70
|
+
});
|
|
71
|
+
const agentRoutes = createAgentModule({
|
|
72
|
+
queryClaude,
|
|
73
|
+
queryCursor,
|
|
74
|
+
queryCodex,
|
|
75
|
+
queryOpenCode,
|
|
76
|
+
});
|
|
78
77
|
// Single WebSocket server that handles chat, shell, and plugin proxy paths.
|
|
79
78
|
const wss = createWebSocketServer(server, {
|
|
80
79
|
verifyClient: {
|
|
@@ -82,20 +81,7 @@ const wss = createWebSocketServer(server, {
|
|
|
82
81
|
authenticateWebSocket,
|
|
83
82
|
},
|
|
84
83
|
chat: {
|
|
85
|
-
|
|
86
|
-
claude: queryClaudeSDK,
|
|
87
|
-
cursor: spawnCursor,
|
|
88
|
-
codex: queryCodex,
|
|
89
|
-
opencode: spawnOpenCode,
|
|
90
|
-
},
|
|
91
|
-
abortFns: {
|
|
92
|
-
claude: abortClaudeSDKSession,
|
|
93
|
-
cursor: abortCursorSession,
|
|
94
|
-
codex: abortCodexSession,
|
|
95
|
-
opencode: abortOpenCodeSession,
|
|
96
|
-
},
|
|
97
|
-
resolveToolApproval,
|
|
98
|
-
getPendingApprovalsForSession,
|
|
84
|
+
runtime: providerRuntimeService,
|
|
99
85
|
},
|
|
100
86
|
shell: {
|
|
101
87
|
resolveProviderSessionId: (sessionId, provider) => {
|
|
@@ -105,16 +91,12 @@ const wss = createWebSocketServer(server, {
|
|
|
105
91
|
}
|
|
106
92
|
return null;
|
|
107
93
|
},
|
|
108
|
-
stripAnsiSequences,
|
|
109
|
-
normalizeDetectedUrl,
|
|
110
|
-
extractUrlsFromText,
|
|
111
|
-
shouldAutoOpenUrlFromOutput,
|
|
112
94
|
},
|
|
113
95
|
getPluginPort,
|
|
114
96
|
});
|
|
115
97
|
// Make WebSocket server available to routes
|
|
116
98
|
app.locals.wss = wss;
|
|
117
|
-
app.use(cors({ exposedHeaders: ['X-Refreshed-Token'] }));
|
|
99
|
+
app.use(cors({ exposedHeaders: ['X-Refreshed-Token', 'X-Auth-Error'] }));
|
|
118
100
|
app.use(express.json({
|
|
119
101
|
limit: '50mb',
|
|
120
102
|
type: (req) => {
|
|
@@ -140,22 +122,23 @@ app.get('/health', (req, res) => {
|
|
|
140
122
|
app.use('/api', validateApiKey);
|
|
141
123
|
// Authentication routes (public)
|
|
142
124
|
app.use('/api/auth', authRoutes);
|
|
125
|
+
// File Tree API Routes (protected)
|
|
126
|
+
app.use('/api/file-tree', authenticateToken, fileTreeRoutes);
|
|
143
127
|
// Projects API Routes (protected)
|
|
144
128
|
app.use('/api/projects', authenticateToken, projectModuleRoutes);
|
|
145
|
-
// Chat
|
|
129
|
+
// Chat attachment upload/serving (global ~/.cloudcli/assets store, protected)
|
|
146
130
|
app.use('/api/assets', authenticateToken, assetsRoutes);
|
|
147
131
|
// Git API Routes (protected)
|
|
148
132
|
app.use('/api/git', authenticateToken, gitRoutes);
|
|
149
|
-
//
|
|
150
|
-
app.use('/api/
|
|
133
|
+
// Git worktree management (protected)
|
|
134
|
+
app.use('/api/worktrees', authenticateToken, worktreesRoutes);
|
|
151
135
|
// TaskMaster API Routes (protected)
|
|
152
136
|
app.use('/api/taskmaster', authenticateToken, taskmasterRoutes);
|
|
153
|
-
// MCP utilities
|
|
154
|
-
app.use('/api/mcp-utils', authenticateToken, mcpUtilsRoutes);
|
|
155
137
|
// Commands API Routes (protected)
|
|
156
138
|
app.use('/api/commands', authenticateToken, commandsRoutes);
|
|
157
139
|
// Settings API Routes (protected)
|
|
158
140
|
app.use('/api/settings', authenticateToken, settingsRoutes);
|
|
141
|
+
app.use('/api/system', authenticateToken, systemRoutes);
|
|
159
142
|
app.use('/api/notifications', authenticateToken, notificationRoutes);
|
|
160
143
|
// User API Routes (protected)
|
|
161
144
|
app.use('/api/user', authenticateToken, userRoutes);
|
|
@@ -191,1020 +174,8 @@ app.use(express.static(path.join(APP_ROOT, 'dist'), {
|
|
|
191
174
|
// API Routes (protected)
|
|
192
175
|
// /api/config endpoint removed - no longer needed
|
|
193
176
|
// Frontend now uses window.location for WebSocket URLs
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
// Get the project root directory (parent of server directory)
|
|
198
|
-
const projectRoot = APP_ROOT;
|
|
199
|
-
console.log('Starting system update from directory:', projectRoot);
|
|
200
|
-
// Platform deployments use their own update workflow from the project root.
|
|
201
|
-
const updateCommand = IS_PLATFORM
|
|
202
|
-
// In platform, husky and dev dependencies are not needed
|
|
203
|
-
? 'npm run update:platform'
|
|
204
|
-
: installMode === 'git'
|
|
205
|
-
? 'git checkout main && git pull && npm install'
|
|
206
|
-
: 'npm install -g @cloudcli-ai/cloudcli@latest';
|
|
207
|
-
const updateCwd = IS_PLATFORM || installMode === 'git'
|
|
208
|
-
? projectRoot
|
|
209
|
-
: os.homedir();
|
|
210
|
-
const child = spawn('sh', ['-c', updateCommand], {
|
|
211
|
-
cwd: updateCwd,
|
|
212
|
-
env: process.env
|
|
213
|
-
});
|
|
214
|
-
let output = '';
|
|
215
|
-
let errorOutput = '';
|
|
216
|
-
child.stdout.on('data', (data) => {
|
|
217
|
-
const text = data.toString();
|
|
218
|
-
output += text;
|
|
219
|
-
console.log('Update output:', text);
|
|
220
|
-
});
|
|
221
|
-
child.stderr.on('data', (data) => {
|
|
222
|
-
const text = data.toString();
|
|
223
|
-
errorOutput += text;
|
|
224
|
-
console.error('Update error:', text);
|
|
225
|
-
});
|
|
226
|
-
child.on('close', (code) => {
|
|
227
|
-
if (code === 0) {
|
|
228
|
-
res.json({
|
|
229
|
-
success: true,
|
|
230
|
-
output: output || 'Update completed successfully',
|
|
231
|
-
message: 'Update completed. Please restart the server to apply changes.'
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
res.status(500).json({
|
|
236
|
-
success: false,
|
|
237
|
-
error: 'Update command failed',
|
|
238
|
-
output: output,
|
|
239
|
-
errorOutput: errorOutput
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
child.on('error', (error) => {
|
|
244
|
-
console.error('Update process error:', error);
|
|
245
|
-
res.status(500).json({
|
|
246
|
-
success: false,
|
|
247
|
-
error: error.message
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
console.error('System update error:', error);
|
|
253
|
-
res.status(500).json({
|
|
254
|
-
success: false,
|
|
255
|
-
error: error.message
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
const expandWorkspacePath = (inputPath) => {
|
|
260
|
-
if (!inputPath)
|
|
261
|
-
return inputPath;
|
|
262
|
-
if (inputPath === '~') {
|
|
263
|
-
return WORKSPACES_ROOT;
|
|
264
|
-
}
|
|
265
|
-
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
|
266
|
-
return path.join(WORKSPACES_ROOT, inputPath.slice(2));
|
|
267
|
-
}
|
|
268
|
-
return inputPath;
|
|
269
|
-
};
|
|
270
|
-
// Browse filesystem endpoint for project suggestions - uses existing getFileTree
|
|
271
|
-
app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
|
|
272
|
-
try {
|
|
273
|
-
const { path: dirPath } = req.query;
|
|
274
|
-
console.log('[API] Browse filesystem request for path:', dirPath);
|
|
275
|
-
console.log('[API] WORKSPACES_ROOT is:', WORKSPACES_ROOT);
|
|
276
|
-
// Default to home directory if no path provided
|
|
277
|
-
const defaultRoot = WORKSPACES_ROOT;
|
|
278
|
-
let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
|
|
279
|
-
// Resolve and normalize the path
|
|
280
|
-
targetPath = path.resolve(targetPath);
|
|
281
|
-
// Security check - ensure path is within allowed workspace root
|
|
282
|
-
const validation = await validateWorkspacePath(targetPath);
|
|
283
|
-
if (!validation.valid) {
|
|
284
|
-
return res.status(403).json({ error: validation.error });
|
|
285
|
-
}
|
|
286
|
-
const resolvedPath = validation.resolvedPath || targetPath;
|
|
287
|
-
// Security check - ensure path is accessible
|
|
288
|
-
try {
|
|
289
|
-
await fs.promises.access(resolvedPath);
|
|
290
|
-
const stats = await fs.promises.stat(resolvedPath);
|
|
291
|
-
if (!stats.isDirectory()) {
|
|
292
|
-
return res.status(400).json({ error: 'Path is not a directory' });
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
catch (err) {
|
|
296
|
-
return res.status(404).json({ error: 'Directory not accessible' });
|
|
297
|
-
}
|
|
298
|
-
// Use existing getFileTree function with shallow depth (only direct children)
|
|
299
|
-
const fileTree = await getFileTree(resolvedPath, 1, 0, false); // maxDepth=1, showHidden=false
|
|
300
|
-
// Filter only directories and format for suggestions
|
|
301
|
-
const directories = fileTree
|
|
302
|
-
.filter(item => item.type === 'directory')
|
|
303
|
-
.map(item => ({
|
|
304
|
-
path: item.path,
|
|
305
|
-
name: item.name,
|
|
306
|
-
type: 'directory'
|
|
307
|
-
}))
|
|
308
|
-
.sort((a, b) => {
|
|
309
|
-
const aHidden = a.name.startsWith('.');
|
|
310
|
-
const bHidden = b.name.startsWith('.');
|
|
311
|
-
if (aHidden && !bHidden)
|
|
312
|
-
return 1;
|
|
313
|
-
if (!aHidden && bHidden)
|
|
314
|
-
return -1;
|
|
315
|
-
return a.name.localeCompare(b.name);
|
|
316
|
-
});
|
|
317
|
-
// Add common directories if browsing home directory
|
|
318
|
-
const suggestions = [];
|
|
319
|
-
let resolvedWorkspaceRoot = defaultRoot;
|
|
320
|
-
try {
|
|
321
|
-
resolvedWorkspaceRoot = await fsPromises.realpath(defaultRoot);
|
|
322
|
-
}
|
|
323
|
-
catch (error) {
|
|
324
|
-
// Use default root as-is if realpath fails
|
|
325
|
-
}
|
|
326
|
-
if (resolvedPath === resolvedWorkspaceRoot) {
|
|
327
|
-
const commonDirs = ['Desktop', 'Documents', 'Projects', 'Development', 'Dev', 'Code', 'workspace'];
|
|
328
|
-
const existingCommon = directories.filter(dir => commonDirs.includes(dir.name));
|
|
329
|
-
const otherDirs = directories.filter(dir => !commonDirs.includes(dir.name));
|
|
330
|
-
suggestions.push(...existingCommon, ...otherDirs);
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
suggestions.push(...directories);
|
|
334
|
-
}
|
|
335
|
-
res.json({
|
|
336
|
-
path: resolvedPath,
|
|
337
|
-
suggestions: suggestions
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
catch (error) {
|
|
341
|
-
console.error('Error browsing filesystem:', error);
|
|
342
|
-
res.status(500).json({ error: 'Failed to browse filesystem' });
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
app.post('/api/create-folder', authenticateToken, async (req, res) => {
|
|
346
|
-
try {
|
|
347
|
-
const { path: folderPath } = req.body;
|
|
348
|
-
if (!folderPath) {
|
|
349
|
-
return res.status(400).json({ error: 'Path is required' });
|
|
350
|
-
}
|
|
351
|
-
const expandedPath = expandWorkspacePath(folderPath);
|
|
352
|
-
const resolvedInput = path.resolve(expandedPath);
|
|
353
|
-
const validation = await validateWorkspacePath(resolvedInput);
|
|
354
|
-
if (!validation.valid) {
|
|
355
|
-
return res.status(403).json({ error: validation.error });
|
|
356
|
-
}
|
|
357
|
-
const targetPath = validation.resolvedPath || resolvedInput;
|
|
358
|
-
const parentDir = path.dirname(targetPath);
|
|
359
|
-
try {
|
|
360
|
-
await fs.promises.access(parentDir);
|
|
361
|
-
}
|
|
362
|
-
catch (err) {
|
|
363
|
-
return res.status(404).json({ error: 'Parent directory does not exist' });
|
|
364
|
-
}
|
|
365
|
-
try {
|
|
366
|
-
await fs.promises.access(targetPath);
|
|
367
|
-
return res.status(409).json({ error: 'Folder already exists' });
|
|
368
|
-
}
|
|
369
|
-
catch (err) {
|
|
370
|
-
// Folder doesn't exist, which is what we want
|
|
371
|
-
}
|
|
372
|
-
try {
|
|
373
|
-
await fs.promises.mkdir(targetPath, { recursive: false });
|
|
374
|
-
res.json({ success: true, path: targetPath });
|
|
375
|
-
}
|
|
376
|
-
catch (mkdirError) {
|
|
377
|
-
if (mkdirError.code === 'EEXIST') {
|
|
378
|
-
return res.status(409).json({ error: 'Folder already exists' });
|
|
379
|
-
}
|
|
380
|
-
throw mkdirError;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
catch (error) {
|
|
384
|
-
console.error('Error creating folder:', error);
|
|
385
|
-
res.status(500).json({ error: 'Failed to create folder' });
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
// Read file content endpoint
|
|
389
|
-
app.get('/api/projects/:projectId/file', authenticateToken, async (req, res) => {
|
|
390
|
-
try {
|
|
391
|
-
const { projectId } = req.params;
|
|
392
|
-
const { filePath } = req.query;
|
|
393
|
-
// Security: ensure the requested path is inside the project root
|
|
394
|
-
if (!filePath) {
|
|
395
|
-
return res.status(400).json({ error: 'Invalid file path' });
|
|
396
|
-
}
|
|
397
|
-
// Resolve the absolute project root via the DB-backed helper; the
|
|
398
|
-
// caller passes the DB-assigned `projectId`, not a folder name.
|
|
399
|
-
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
|
400
|
-
if (!projectRoot) {
|
|
401
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
402
|
-
}
|
|
403
|
-
// Handle both absolute and relative paths
|
|
404
|
-
const resolved = path.isAbsolute(filePath)
|
|
405
|
-
? path.resolve(filePath)
|
|
406
|
-
: path.resolve(projectRoot, filePath);
|
|
407
|
-
const normalizedRoot = path.resolve(projectRoot) + path.sep;
|
|
408
|
-
if (!resolved.startsWith(normalizedRoot)) {
|
|
409
|
-
return res.status(403).json({ error: 'Path must be under project root' });
|
|
410
|
-
}
|
|
411
|
-
const content = await fsPromises.readFile(resolved, 'utf8');
|
|
412
|
-
res.json({ content, path: resolved });
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
console.error('Error reading file:', error);
|
|
416
|
-
if (error.code === 'ENOENT') {
|
|
417
|
-
res.status(404).json({ error: 'File not found' });
|
|
418
|
-
}
|
|
419
|
-
else if (error.code === 'EACCES') {
|
|
420
|
-
res.status(403).json({ error: 'Permission denied' });
|
|
421
|
-
}
|
|
422
|
-
else {
|
|
423
|
-
res.status(500).json({ error: error.message });
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
// Serve raw file bytes for previews and downloads.
|
|
428
|
-
app.get('/api/projects/:projectId/files/content', authenticateToken, async (req, res) => {
|
|
429
|
-
try {
|
|
430
|
-
const { projectId } = req.params;
|
|
431
|
-
const { path: filePath } = req.query;
|
|
432
|
-
// Security: ensure the requested path is inside the project root
|
|
433
|
-
if (!filePath) {
|
|
434
|
-
return res.status(400).json({ error: 'Invalid file path' });
|
|
435
|
-
}
|
|
436
|
-
// Projects are now addressed by DB `projectId`, resolved to their path here.
|
|
437
|
-
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
|
438
|
-
if (!projectRoot) {
|
|
439
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
440
|
-
}
|
|
441
|
-
// Match the text reader endpoint so callers can pass either project-relative
|
|
442
|
-
// or absolute paths without changing how the bytes are served.
|
|
443
|
-
const resolved = path.isAbsolute(filePath)
|
|
444
|
-
? path.resolve(filePath)
|
|
445
|
-
: path.resolve(projectRoot, filePath);
|
|
446
|
-
const normalizedRoot = path.resolve(projectRoot) + path.sep;
|
|
447
|
-
if (!resolved.startsWith(normalizedRoot)) {
|
|
448
|
-
return res.status(403).json({ error: 'Path must be under project root' });
|
|
449
|
-
}
|
|
450
|
-
// Check if file exists
|
|
451
|
-
try {
|
|
452
|
-
await fsPromises.access(resolved);
|
|
453
|
-
}
|
|
454
|
-
catch (error) {
|
|
455
|
-
return res.status(404).json({ error: 'File not found' });
|
|
456
|
-
}
|
|
457
|
-
// Get file extension and set appropriate content type
|
|
458
|
-
const mimeType = mime.lookup(resolved) || 'application/octet-stream';
|
|
459
|
-
res.setHeader('Content-Type', mimeType);
|
|
460
|
-
// Stream the file
|
|
461
|
-
const fileStream = fs.createReadStream(resolved);
|
|
462
|
-
fileStream.pipe(res);
|
|
463
|
-
fileStream.on('error', (error) => {
|
|
464
|
-
console.error('Error streaming file:', error);
|
|
465
|
-
if (!res.headersSent) {
|
|
466
|
-
res.status(500).json({ error: 'Error reading file' });
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
console.error('Error serving binary file:', error);
|
|
472
|
-
if (!res.headersSent) {
|
|
473
|
-
res.status(500).json({ error: error.message });
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
// Save file content endpoint
|
|
478
|
-
app.put('/api/projects/:projectId/file', authenticateToken, async (req, res) => {
|
|
479
|
-
try {
|
|
480
|
-
const { projectId } = req.params;
|
|
481
|
-
const { filePath, content } = req.body;
|
|
482
|
-
// Security: ensure the requested path is inside the project root
|
|
483
|
-
if (!filePath) {
|
|
484
|
-
return res.status(400).json({ error: 'Invalid file path' });
|
|
485
|
-
}
|
|
486
|
-
if (content === undefined) {
|
|
487
|
-
return res.status(400).json({ error: 'Content is required' });
|
|
488
|
-
}
|
|
489
|
-
// Projects are now addressed by DB `projectId`, resolved to their path here.
|
|
490
|
-
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
|
491
|
-
if (!projectRoot) {
|
|
492
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
493
|
-
}
|
|
494
|
-
// Handle both absolute and relative paths
|
|
495
|
-
const resolved = path.isAbsolute(filePath)
|
|
496
|
-
? path.resolve(filePath)
|
|
497
|
-
: path.resolve(projectRoot, filePath);
|
|
498
|
-
const normalizedRoot = path.resolve(projectRoot) + path.sep;
|
|
499
|
-
if (!resolved.startsWith(normalizedRoot)) {
|
|
500
|
-
return res.status(403).json({ error: 'Path must be under project root' });
|
|
501
|
-
}
|
|
502
|
-
// Write the new content
|
|
503
|
-
await fsPromises.writeFile(resolved, content, 'utf8');
|
|
504
|
-
res.json({
|
|
505
|
-
success: true,
|
|
506
|
-
path: resolved,
|
|
507
|
-
message: 'File saved successfully'
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
catch (error) {
|
|
511
|
-
console.error('Error saving file:', error);
|
|
512
|
-
if (error.code === 'ENOENT') {
|
|
513
|
-
res.status(404).json({ error: 'File or directory not found' });
|
|
514
|
-
}
|
|
515
|
-
else if (error.code === 'EACCES') {
|
|
516
|
-
res.status(403).json({ error: 'Permission denied' });
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
res.status(500).json({ error: error.message });
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
app.get('/api/projects/:projectId/files', authenticateToken, async (req, res) => {
|
|
524
|
-
try {
|
|
525
|
-
// Using fsPromises from import
|
|
526
|
-
// Resolve the project's absolute path through the DB (projectId is the
|
|
527
|
-
// primary key of the `projects` table after the identifier migration).
|
|
528
|
-
const actualPath = await projectsDb.getProjectPathById(req.params.projectId);
|
|
529
|
-
if (!actualPath) {
|
|
530
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
531
|
-
}
|
|
532
|
-
// Check if path exists
|
|
533
|
-
try {
|
|
534
|
-
await fsPromises.access(actualPath);
|
|
535
|
-
}
|
|
536
|
-
catch (e) {
|
|
537
|
-
return res.status(404).json({ error: `Project path not found: ${actualPath}` });
|
|
538
|
-
}
|
|
539
|
-
const files = await getFileTree(actualPath, 10, 0, true);
|
|
540
|
-
res.json(files);
|
|
541
|
-
}
|
|
542
|
-
catch (error) {
|
|
543
|
-
console.error('[ERROR] File tree error:', error.message);
|
|
544
|
-
res.status(500).json({ error: error.message });
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
// ============================================================================
|
|
548
|
-
// FILE OPERATIONS API ENDPOINTS
|
|
549
|
-
// ============================================================================
|
|
550
|
-
/**
|
|
551
|
-
* Validate that a path is within the project root
|
|
552
|
-
* @param {string} projectRoot - The project root path
|
|
553
|
-
* @param {string} targetPath - The path to validate
|
|
554
|
-
* @returns {{ valid: boolean, resolved?: string, error?: string }}
|
|
555
|
-
*/
|
|
556
|
-
function validatePathInProject(projectRoot, targetPath) {
|
|
557
|
-
const resolved = path.isAbsolute(targetPath)
|
|
558
|
-
? path.resolve(targetPath)
|
|
559
|
-
: path.resolve(projectRoot, targetPath);
|
|
560
|
-
const normalizedRoot = path.resolve(projectRoot) + path.sep;
|
|
561
|
-
if (!resolved.startsWith(normalizedRoot)) {
|
|
562
|
-
return { valid: false, error: 'Path must be under project root' };
|
|
563
|
-
}
|
|
564
|
-
return { valid: true, resolved };
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Validate filename - check for invalid characters
|
|
568
|
-
* @param {string} name - The filename to validate
|
|
569
|
-
* @returns {{ valid: boolean, error?: string }}
|
|
570
|
-
*/
|
|
571
|
-
function validateFilename(name) {
|
|
572
|
-
if (!name || !name.trim()) {
|
|
573
|
-
return { valid: false, error: 'Filename cannot be empty' };
|
|
574
|
-
}
|
|
575
|
-
// Check for invalid characters (Windows + Unix)
|
|
576
|
-
const invalidChars = /[<>:"/\\|?*\x00-\x1f]/;
|
|
577
|
-
if (invalidChars.test(name)) {
|
|
578
|
-
return { valid: false, error: 'Filename contains invalid characters' };
|
|
579
|
-
}
|
|
580
|
-
// Check for reserved names (Windows)
|
|
581
|
-
const reserved = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
|
|
582
|
-
if (reserved.test(name)) {
|
|
583
|
-
return { valid: false, error: 'Filename is a reserved name' };
|
|
584
|
-
}
|
|
585
|
-
// Check for dots only
|
|
586
|
-
if (/^\.+$/.test(name)) {
|
|
587
|
-
return { valid: false, error: 'Filename cannot be only dots' };
|
|
588
|
-
}
|
|
589
|
-
return { valid: true };
|
|
590
|
-
}
|
|
591
|
-
// POST /api/projects/:projectId/files/create - Create new file or directory
|
|
592
|
-
app.post('/api/projects/:projectId/files/create', authenticateToken, async (req, res) => {
|
|
593
|
-
try {
|
|
594
|
-
const { projectId } = req.params;
|
|
595
|
-
const { path: parentPath, type, name } = req.body;
|
|
596
|
-
// Validate input
|
|
597
|
-
if (!name || !type) {
|
|
598
|
-
return res.status(400).json({ error: 'Name and type are required' });
|
|
599
|
-
}
|
|
600
|
-
if (!['file', 'directory'].includes(type)) {
|
|
601
|
-
return res.status(400).json({ error: 'Type must be "file" or "directory"' });
|
|
602
|
-
}
|
|
603
|
-
const nameValidation = validateFilename(name);
|
|
604
|
-
if (!nameValidation.valid) {
|
|
605
|
-
return res.status(400).json({ error: nameValidation.error });
|
|
606
|
-
}
|
|
607
|
-
// Resolve the project directory through the DB using the new projectId.
|
|
608
|
-
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
|
609
|
-
if (!projectRoot) {
|
|
610
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
611
|
-
}
|
|
612
|
-
// Build and validate target path
|
|
613
|
-
const targetDir = parentPath || '';
|
|
614
|
-
const targetPath = targetDir ? path.join(targetDir, name) : name;
|
|
615
|
-
const validation = validatePathInProject(projectRoot, targetPath);
|
|
616
|
-
if (!validation.valid) {
|
|
617
|
-
return res.status(403).json({ error: validation.error });
|
|
618
|
-
}
|
|
619
|
-
const resolvedPath = validation.resolved;
|
|
620
|
-
// Check if already exists
|
|
621
|
-
try {
|
|
622
|
-
await fsPromises.access(resolvedPath);
|
|
623
|
-
return res.status(409).json({ error: `${type === 'file' ? 'File' : 'Directory'} already exists` });
|
|
624
|
-
}
|
|
625
|
-
catch {
|
|
626
|
-
// Doesn't exist, which is what we want
|
|
627
|
-
}
|
|
628
|
-
// Create file or directory
|
|
629
|
-
if (type === 'directory') {
|
|
630
|
-
await fsPromises.mkdir(resolvedPath, { recursive: false });
|
|
631
|
-
}
|
|
632
|
-
else {
|
|
633
|
-
// Ensure parent directory exists
|
|
634
|
-
const parentDir = path.dirname(resolvedPath);
|
|
635
|
-
try {
|
|
636
|
-
await fsPromises.access(parentDir);
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
await fsPromises.mkdir(parentDir, { recursive: true });
|
|
640
|
-
}
|
|
641
|
-
await fsPromises.writeFile(resolvedPath, '', 'utf8');
|
|
642
|
-
}
|
|
643
|
-
res.json({
|
|
644
|
-
success: true,
|
|
645
|
-
path: resolvedPath,
|
|
646
|
-
name,
|
|
647
|
-
type,
|
|
648
|
-
message: `${type === 'file' ? 'File' : 'Directory'} created successfully`
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
catch (error) {
|
|
652
|
-
console.error('Error creating file/directory:', error);
|
|
653
|
-
if (error.code === 'EACCES') {
|
|
654
|
-
res.status(403).json({ error: 'Permission denied' });
|
|
655
|
-
}
|
|
656
|
-
else if (error.code === 'ENOENT') {
|
|
657
|
-
res.status(404).json({ error: 'Parent directory not found' });
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
res.status(500).json({ error: error.message });
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
// PUT /api/projects/:projectId/files/rename - Rename file or directory
|
|
665
|
-
app.put('/api/projects/:projectId/files/rename', authenticateToken, async (req, res) => {
|
|
666
|
-
try {
|
|
667
|
-
const { projectId } = req.params;
|
|
668
|
-
const { oldPath, newName } = req.body;
|
|
669
|
-
// Validate input
|
|
670
|
-
if (!oldPath || !newName) {
|
|
671
|
-
return res.status(400).json({ error: 'oldPath and newName are required' });
|
|
672
|
-
}
|
|
673
|
-
const nameValidation = validateFilename(newName);
|
|
674
|
-
if (!nameValidation.valid) {
|
|
675
|
-
return res.status(400).json({ error: nameValidation.error });
|
|
676
|
-
}
|
|
677
|
-
// Resolve the project directory through the DB using the new projectId.
|
|
678
|
-
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
|
679
|
-
if (!projectRoot) {
|
|
680
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
681
|
-
}
|
|
682
|
-
// Validate old path
|
|
683
|
-
const oldValidation = validatePathInProject(projectRoot, oldPath);
|
|
684
|
-
if (!oldValidation.valid) {
|
|
685
|
-
return res.status(403).json({ error: oldValidation.error });
|
|
686
|
-
}
|
|
687
|
-
const resolvedOldPath = oldValidation.resolved;
|
|
688
|
-
// Check if old path exists
|
|
689
|
-
try {
|
|
690
|
-
await fsPromises.access(resolvedOldPath);
|
|
691
|
-
}
|
|
692
|
-
catch {
|
|
693
|
-
return res.status(404).json({ error: 'File or directory not found' });
|
|
694
|
-
}
|
|
695
|
-
// Build and validate new path
|
|
696
|
-
const parentDir = path.dirname(resolvedOldPath);
|
|
697
|
-
const resolvedNewPath = path.join(parentDir, newName);
|
|
698
|
-
const newValidation = validatePathInProject(projectRoot, resolvedNewPath);
|
|
699
|
-
if (!newValidation.valid) {
|
|
700
|
-
return res.status(403).json({ error: newValidation.error });
|
|
701
|
-
}
|
|
702
|
-
// Check if new path already exists
|
|
703
|
-
try {
|
|
704
|
-
await fsPromises.access(resolvedNewPath);
|
|
705
|
-
return res.status(409).json({ error: 'A file or directory with this name already exists' });
|
|
706
|
-
}
|
|
707
|
-
catch {
|
|
708
|
-
// Doesn't exist, which is what we want
|
|
709
|
-
}
|
|
710
|
-
// Rename
|
|
711
|
-
await fsPromises.rename(resolvedOldPath, resolvedNewPath);
|
|
712
|
-
res.json({
|
|
713
|
-
success: true,
|
|
714
|
-
oldPath: resolvedOldPath,
|
|
715
|
-
newPath: resolvedNewPath,
|
|
716
|
-
newName,
|
|
717
|
-
message: 'Renamed successfully'
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
catch (error) {
|
|
721
|
-
console.error('Error renaming file/directory:', error);
|
|
722
|
-
if (error.code === 'EACCES') {
|
|
723
|
-
res.status(403).json({ error: 'Permission denied' });
|
|
724
|
-
}
|
|
725
|
-
else if (error.code === 'ENOENT') {
|
|
726
|
-
res.status(404).json({ error: 'File or directory not found' });
|
|
727
|
-
}
|
|
728
|
-
else if (error.code === 'EXDEV') {
|
|
729
|
-
res.status(400).json({ error: 'Cannot move across different filesystems' });
|
|
730
|
-
}
|
|
731
|
-
else {
|
|
732
|
-
res.status(500).json({ error: error.message });
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
// DELETE /api/projects/:projectId/files - Delete file or directory
|
|
737
|
-
app.delete('/api/projects/:projectId/files', authenticateToken, async (req, res) => {
|
|
738
|
-
try {
|
|
739
|
-
const { projectId } = req.params;
|
|
740
|
-
const { path: targetPath, type } = req.body;
|
|
741
|
-
// Validate input
|
|
742
|
-
if (!targetPath) {
|
|
743
|
-
return res.status(400).json({ error: 'Path is required' });
|
|
744
|
-
}
|
|
745
|
-
// Resolve the project directory through the DB using the new projectId.
|
|
746
|
-
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
|
747
|
-
if (!projectRoot) {
|
|
748
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
749
|
-
}
|
|
750
|
-
// Validate path
|
|
751
|
-
const validation = validatePathInProject(projectRoot, targetPath);
|
|
752
|
-
if (!validation.valid) {
|
|
753
|
-
return res.status(403).json({ error: validation.error });
|
|
754
|
-
}
|
|
755
|
-
const resolvedPath = validation.resolved;
|
|
756
|
-
// Check if path exists and get stats
|
|
757
|
-
let stats;
|
|
758
|
-
try {
|
|
759
|
-
stats = await fsPromises.stat(resolvedPath);
|
|
760
|
-
}
|
|
761
|
-
catch {
|
|
762
|
-
return res.status(404).json({ error: 'File or directory not found' });
|
|
763
|
-
}
|
|
764
|
-
// Prevent deleting the project root itself
|
|
765
|
-
if (resolvedPath === path.resolve(projectRoot)) {
|
|
766
|
-
return res.status(403).json({ error: 'Cannot delete project root directory' });
|
|
767
|
-
}
|
|
768
|
-
// Delete based on type
|
|
769
|
-
if (stats.isDirectory()) {
|
|
770
|
-
await fsPromises.rm(resolvedPath, { recursive: true, force: true });
|
|
771
|
-
}
|
|
772
|
-
else {
|
|
773
|
-
await fsPromises.unlink(resolvedPath);
|
|
774
|
-
}
|
|
775
|
-
res.json({
|
|
776
|
-
success: true,
|
|
777
|
-
path: resolvedPath,
|
|
778
|
-
type: stats.isDirectory() ? 'directory' : 'file',
|
|
779
|
-
message: 'Deleted successfully'
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
catch (error) {
|
|
783
|
-
console.error('Error deleting file/directory:', error);
|
|
784
|
-
if (error.code === 'EACCES') {
|
|
785
|
-
res.status(403).json({ error: 'Permission denied' });
|
|
786
|
-
}
|
|
787
|
-
else if (error.code === 'ENOENT') {
|
|
788
|
-
res.status(404).json({ error: 'File or directory not found' });
|
|
789
|
-
}
|
|
790
|
-
else if (error.code === 'ENOTEMPTY') {
|
|
791
|
-
res.status(400).json({ error: 'Directory is not empty' });
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
res.status(500).json({ error: error.message });
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
});
|
|
798
|
-
// POST /api/projects/:projectId/files/upload - Upload files
|
|
799
|
-
// Dynamic import of multer for file uploads
|
|
800
|
-
const uploadFilesHandler = async (req, res) => {
|
|
801
|
-
// Dynamic import of multer
|
|
802
|
-
const multer = (await import('multer')).default;
|
|
803
|
-
const uploadMiddleware = multer({
|
|
804
|
-
storage: multer.diskStorage({
|
|
805
|
-
destination: (req, file, cb) => {
|
|
806
|
-
cb(null, os.tmpdir());
|
|
807
|
-
},
|
|
808
|
-
filename: (req, file, cb) => {
|
|
809
|
-
// Use a unique temp name, but preserve original name in file.originalname
|
|
810
|
-
// Note: file.originalname may contain path separators for folder uploads
|
|
811
|
-
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
812
|
-
// For temp file, just use a safe unique name without the path
|
|
813
|
-
cb(null, `upload-${uniqueSuffix}`);
|
|
814
|
-
}
|
|
815
|
-
}),
|
|
816
|
-
limits: {
|
|
817
|
-
fileSize: MAX_FILE_UPLOAD_SIZE_BYTES,
|
|
818
|
-
files: MAX_FILE_UPLOAD_COUNT
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
// Use multer middleware
|
|
822
|
-
uploadMiddleware.array('files', MAX_FILE_UPLOAD_COUNT)(req, res, async (err) => {
|
|
823
|
-
if (err) {
|
|
824
|
-
console.error('Multer error:', err);
|
|
825
|
-
if (err.code === 'LIMIT_FILE_SIZE') {
|
|
826
|
-
return res.status(400).json({ error: `File too large. Maximum size is ${MAX_FILE_UPLOAD_SIZE_MB}MB.` });
|
|
827
|
-
}
|
|
828
|
-
if (err.code === 'LIMIT_FILE_COUNT') {
|
|
829
|
-
return res.status(400).json({ error: `Too many files. Maximum is ${MAX_FILE_UPLOAD_COUNT} files.` });
|
|
830
|
-
}
|
|
831
|
-
return res.status(500).json({ error: err.message });
|
|
832
|
-
}
|
|
833
|
-
try {
|
|
834
|
-
const { projectId } = req.params;
|
|
835
|
-
const { targetPath, relativePaths, requestedFileCount: requestedFileCountRaw } = req.body;
|
|
836
|
-
// Parse relative paths if provided (for folder uploads)
|
|
837
|
-
let filePaths = [];
|
|
838
|
-
if (relativePaths) {
|
|
839
|
-
try {
|
|
840
|
-
filePaths = JSON.parse(relativePaths);
|
|
841
|
-
}
|
|
842
|
-
catch (e) {
|
|
843
|
-
console.log('[DEBUG] Failed to parse relativePaths:', relativePaths);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
console.log('[DEBUG] File upload request:', {
|
|
847
|
-
projectId,
|
|
848
|
-
targetPath: JSON.stringify(targetPath),
|
|
849
|
-
targetPathType: typeof targetPath,
|
|
850
|
-
filesCount: req.files?.length,
|
|
851
|
-
relativePaths: filePaths
|
|
852
|
-
});
|
|
853
|
-
if (!req.files || req.files.length === 0) {
|
|
854
|
-
return res.status(400).json({ error: 'No files provided' });
|
|
855
|
-
}
|
|
856
|
-
const parsedRequestedFileCount = Number.parseInt(requestedFileCountRaw, 10);
|
|
857
|
-
const requestedFileCount = Number.isFinite(parsedRequestedFileCount) && parsedRequestedFileCount > 0
|
|
858
|
-
? parsedRequestedFileCount
|
|
859
|
-
: req.files.length;
|
|
860
|
-
// Resolve the project directory through the DB using the new projectId.
|
|
861
|
-
const projectRoot = await projectsDb.getProjectPathById(projectId);
|
|
862
|
-
if (!projectRoot) {
|
|
863
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
864
|
-
}
|
|
865
|
-
console.log('[DEBUG] Project root:', projectRoot);
|
|
866
|
-
// Validate and resolve target path
|
|
867
|
-
// If targetPath is empty or '.', use project root directly
|
|
868
|
-
const targetDir = targetPath || '';
|
|
869
|
-
let resolvedTargetDir;
|
|
870
|
-
console.log('[DEBUG] Target dir:', JSON.stringify(targetDir));
|
|
871
|
-
if (!targetDir || targetDir === '.' || targetDir === './') {
|
|
872
|
-
// Empty path means upload to project root
|
|
873
|
-
resolvedTargetDir = path.resolve(projectRoot);
|
|
874
|
-
console.log('[DEBUG] Using project root as target:', resolvedTargetDir);
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
const validation = validatePathInProject(projectRoot, targetDir);
|
|
878
|
-
if (!validation.valid) {
|
|
879
|
-
console.log('[DEBUG] Path validation failed:', validation.error);
|
|
880
|
-
return res.status(403).json({ error: validation.error });
|
|
881
|
-
}
|
|
882
|
-
resolvedTargetDir = validation.resolved;
|
|
883
|
-
console.log('[DEBUG] Resolved target dir:', resolvedTargetDir);
|
|
884
|
-
}
|
|
885
|
-
// Ensure target directory exists
|
|
886
|
-
try {
|
|
887
|
-
await fsPromises.access(resolvedTargetDir);
|
|
888
|
-
}
|
|
889
|
-
catch {
|
|
890
|
-
await fsPromises.mkdir(resolvedTargetDir, { recursive: true });
|
|
891
|
-
}
|
|
892
|
-
// Move uploaded files from temp to target directory
|
|
893
|
-
const uploadedFiles = [];
|
|
894
|
-
console.log('[DEBUG] Processing files:', req.files.map(f => ({ originalname: f.originalname, path: f.path })));
|
|
895
|
-
for (let i = 0; i < req.files.length; i++) {
|
|
896
|
-
const file = req.files[i];
|
|
897
|
-
// Use relative path if provided (for folder uploads), otherwise use originalname
|
|
898
|
-
const fileName = (filePaths && filePaths[i]) ? filePaths[i] : file.originalname;
|
|
899
|
-
console.log('[DEBUG] Processing file:', fileName, '(originalname:', file.originalname + ')');
|
|
900
|
-
const destPath = path.join(resolvedTargetDir, fileName);
|
|
901
|
-
// Validate destination path
|
|
902
|
-
const destValidation = validatePathInProject(projectRoot, destPath);
|
|
903
|
-
if (!destValidation.valid) {
|
|
904
|
-
console.log('[DEBUG] Destination validation failed for:', destPath);
|
|
905
|
-
// Clean up temp file
|
|
906
|
-
await fsPromises.unlink(file.path).catch(() => { });
|
|
907
|
-
continue;
|
|
908
|
-
}
|
|
909
|
-
// Ensure parent directory exists (for nested files from folder upload)
|
|
910
|
-
const parentDir = path.dirname(destPath);
|
|
911
|
-
try {
|
|
912
|
-
await fsPromises.access(parentDir);
|
|
913
|
-
}
|
|
914
|
-
catch {
|
|
915
|
-
await fsPromises.mkdir(parentDir, { recursive: true });
|
|
916
|
-
}
|
|
917
|
-
// Move file (copy + unlink to handle cross-device scenarios)
|
|
918
|
-
await fsPromises.copyFile(file.path, destPath);
|
|
919
|
-
await fsPromises.unlink(file.path);
|
|
920
|
-
uploadedFiles.push({
|
|
921
|
-
name: fileName,
|
|
922
|
-
path: destPath,
|
|
923
|
-
size: file.size,
|
|
924
|
-
mimeType: file.mimetype
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
res.json({
|
|
928
|
-
success: true,
|
|
929
|
-
files: uploadedFiles,
|
|
930
|
-
uploadedCount: uploadedFiles.length,
|
|
931
|
-
requestedFileCount,
|
|
932
|
-
targetPath: resolvedTargetDir,
|
|
933
|
-
message: `Uploaded ${uploadedFiles.length} ${uploadedFiles.length === 1 ? 'file' : 'files'} successfully`
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
catch (error) {
|
|
937
|
-
console.error('Error uploading files:', error);
|
|
938
|
-
// Clean up any remaining temp files
|
|
939
|
-
if (req.files) {
|
|
940
|
-
for (const file of req.files) {
|
|
941
|
-
await fsPromises.unlink(file.path).catch(() => { });
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
if (error.code === 'EACCES') {
|
|
945
|
-
res.status(403).json({ error: 'Permission denied' });
|
|
946
|
-
}
|
|
947
|
-
else {
|
|
948
|
-
res.status(500).json({ error: error.message });
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
};
|
|
953
|
-
app.post('/api/projects/:projectId/files/upload', authenticateToken, uploadFilesHandler);
|
|
954
|
-
// Chat image uploads moved to POST /api/assets/images (server/modules/assets),
|
|
955
|
-
// which stores them in the global ~/.cloudcli/assets folder.
|
|
956
|
-
// Get token usage for a specific session. `projectId` is the DB primary key;
|
|
957
|
-
// the Claude branch below resolves it to an absolute path via the DB.
|
|
958
|
-
app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticateToken, async (req, res) => {
|
|
959
|
-
try {
|
|
960
|
-
const { projectId, sessionId } = req.params;
|
|
961
|
-
const homeDir = os.homedir();
|
|
962
|
-
// Allow only safe characters in sessionId
|
|
963
|
-
const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9._-]/g, '');
|
|
964
|
-
if (!safeSessionId || safeSessionId !== String(sessionId)) {
|
|
965
|
-
return res.status(400).json({ error: 'Invalid sessionId' });
|
|
966
|
-
}
|
|
967
|
-
// Provider artifacts on disk (JSONL file names, OpenCode sqlite rows)
|
|
968
|
-
// are keyed by the provider-native session id, while the caller sends
|
|
969
|
-
// the app-facing id. Resolve provider and id mapping from the indexed
|
|
970
|
-
// session row so the frontend does not choose provider-specific paths.
|
|
971
|
-
const sessionRow = sessionsDb.getSessionById(safeSessionId);
|
|
972
|
-
if (!sessionRow) {
|
|
973
|
-
return res.status(404).json({ error: 'Session not found', sessionId: safeSessionId });
|
|
974
|
-
}
|
|
975
|
-
const provider = sessionRow.provider || 'claude';
|
|
976
|
-
const providerNativeSessionId = sessionRow?.provider_session_id || safeSessionId;
|
|
977
|
-
// Handle Cursor sessions - they use SQLite and don't have token usage info
|
|
978
|
-
if (provider === 'cursor') {
|
|
979
|
-
return res.json({
|
|
980
|
-
used: 0,
|
|
981
|
-
total: 0,
|
|
982
|
-
inputTokens: 0,
|
|
983
|
-
outputTokens: 0,
|
|
984
|
-
breakdown: { input: 0, output: 0 },
|
|
985
|
-
unsupported: true,
|
|
986
|
-
message: 'Token usage tracking not available for Cursor sessions'
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
if (provider === 'opencode') {
|
|
990
|
-
const dbPath = getOpenCodeDatabasePath();
|
|
991
|
-
if (!fs.existsSync(dbPath)) {
|
|
992
|
-
return res.status(404).json({ error: 'OpenCode database not found' });
|
|
993
|
-
}
|
|
994
|
-
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
995
|
-
try {
|
|
996
|
-
const columns = db.prepare('PRAGMA table_info(session)').all();
|
|
997
|
-
const columnNames = new Set(columns.map((column) => column.name));
|
|
998
|
-
const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write'];
|
|
999
|
-
if (!requiredColumns.every((column) => columnNames.has(column))) {
|
|
1000
|
-
return res.json({
|
|
1001
|
-
used: 0,
|
|
1002
|
-
inputTokens: 0,
|
|
1003
|
-
outputTokens: 0,
|
|
1004
|
-
breakdown: { input: 0, output: 0 },
|
|
1005
|
-
unsupported: true,
|
|
1006
|
-
message: 'Token usage tracking is not available in this OpenCode database schema'
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
const row = db.prepare(`
|
|
1010
|
-
SELECT
|
|
1011
|
-
tokens_input AS inputTokens,
|
|
1012
|
-
tokens_output AS outputTokens,
|
|
1013
|
-
tokens_reasoning AS reasoningTokens,
|
|
1014
|
-
tokens_cache_read AS cacheReadTokens,
|
|
1015
|
-
tokens_cache_write AS cacheWriteTokens
|
|
1016
|
-
FROM session
|
|
1017
|
-
WHERE id = ?
|
|
1018
|
-
`).get(providerNativeSessionId);
|
|
1019
|
-
if (!row) {
|
|
1020
|
-
return res.status(404).json({ error: 'OpenCode session not found', sessionId: safeSessionId });
|
|
1021
|
-
}
|
|
1022
|
-
const inputTokens = Number(row.inputTokens || 0) + Number(row.cacheReadTokens || 0);
|
|
1023
|
-
const outputTokens = Number(row.outputTokens || 0);
|
|
1024
|
-
const totalUsed = Number(row.inputTokens || 0)
|
|
1025
|
-
+ outputTokens
|
|
1026
|
-
+ Number(row.reasoningTokens || 0)
|
|
1027
|
-
+ Number(row.cacheReadTokens || 0)
|
|
1028
|
-
+ Number(row.cacheWriteTokens || 0);
|
|
1029
|
-
return res.json({
|
|
1030
|
-
used: totalUsed,
|
|
1031
|
-
inputTokens,
|
|
1032
|
-
outputTokens,
|
|
1033
|
-
breakdown: {
|
|
1034
|
-
input: inputTokens,
|
|
1035
|
-
output: outputTokens
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1038
|
-
}
|
|
1039
|
-
finally {
|
|
1040
|
-
db.close();
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
// Handle Codex sessions
|
|
1044
|
-
if (provider === 'codex') {
|
|
1045
|
-
const codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
|
|
1046
|
-
// Find the session file by searching for the session ID
|
|
1047
|
-
const findSessionFile = async (dir) => {
|
|
1048
|
-
try {
|
|
1049
|
-
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
1050
|
-
for (const entry of entries) {
|
|
1051
|
-
const fullPath = path.join(dir, entry.name);
|
|
1052
|
-
if (entry.isDirectory()) {
|
|
1053
|
-
const found = await findSessionFile(fullPath);
|
|
1054
|
-
if (found)
|
|
1055
|
-
return found;
|
|
1056
|
-
}
|
|
1057
|
-
else if (entry.name.includes(providerNativeSessionId) && entry.name.endsWith('.jsonl')) {
|
|
1058
|
-
return fullPath;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
catch (error) {
|
|
1063
|
-
// Skip directories we can't read
|
|
1064
|
-
}
|
|
1065
|
-
return null;
|
|
1066
|
-
};
|
|
1067
|
-
const sessionFilePath = await findSessionFile(codexSessionsDir);
|
|
1068
|
-
if (!sessionFilePath) {
|
|
1069
|
-
return res.status(404).json({ error: 'Codex session file not found', sessionId: safeSessionId });
|
|
1070
|
-
}
|
|
1071
|
-
// Read and parse the Codex JSONL file
|
|
1072
|
-
let fileContent;
|
|
1073
|
-
try {
|
|
1074
|
-
fileContent = await fsPromises.readFile(sessionFilePath, 'utf8');
|
|
1075
|
-
}
|
|
1076
|
-
catch (error) {
|
|
1077
|
-
if (error.code === 'ENOENT') {
|
|
1078
|
-
return res.status(404).json({ error: 'Session file not found', path: sessionFilePath });
|
|
1079
|
-
}
|
|
1080
|
-
throw error;
|
|
1081
|
-
}
|
|
1082
|
-
const lines = fileContent.trim().split('\n');
|
|
1083
|
-
let inputTokens = 0;
|
|
1084
|
-
let outputTokens = 0;
|
|
1085
|
-
let totalTokens = 0;
|
|
1086
|
-
let contextWindow = 200000; // Default for Codex/OpenAI
|
|
1087
|
-
// Find the latest token_count event with info (scan from end)
|
|
1088
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1089
|
-
try {
|
|
1090
|
-
const entry = JSON.parse(lines[i]);
|
|
1091
|
-
// Codex stores token info in event_msg with type: "token_count"
|
|
1092
|
-
if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) {
|
|
1093
|
-
const tokenInfo = entry.payload.info;
|
|
1094
|
-
if (tokenInfo.total_token_usage) {
|
|
1095
|
-
inputTokens = tokenInfo.total_token_usage.input_tokens || 0;
|
|
1096
|
-
outputTokens = tokenInfo.total_token_usage.output_tokens || 0;
|
|
1097
|
-
totalTokens = tokenInfo.total_token_usage.total_tokens || inputTokens + outputTokens;
|
|
1098
|
-
}
|
|
1099
|
-
if (tokenInfo.model_context_window) {
|
|
1100
|
-
contextWindow = tokenInfo.model_context_window;
|
|
1101
|
-
}
|
|
1102
|
-
break; // Stop after finding the latest token count
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
catch (parseError) {
|
|
1106
|
-
// Skip lines that can't be parsed
|
|
1107
|
-
continue;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
return res.json({
|
|
1111
|
-
used: totalTokens,
|
|
1112
|
-
total: contextWindow,
|
|
1113
|
-
inputTokens,
|
|
1114
|
-
outputTokens,
|
|
1115
|
-
breakdown: {
|
|
1116
|
-
input: inputTokens,
|
|
1117
|
-
output: outputTokens
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
}
|
|
1121
|
-
// Handle Claude sessions (default)
|
|
1122
|
-
// Resolve the project path through the DB using the caller-supplied
|
|
1123
|
-
// `projectId`. Legacy code here called extractProjectDirectory with a
|
|
1124
|
-
// folder-encoded project name; the migration centralizes that lookup
|
|
1125
|
-
// in the projects table.
|
|
1126
|
-
const projectPath = await projectsDb.getProjectPathById(projectId);
|
|
1127
|
-
if (!projectPath) {
|
|
1128
|
-
return res.status(404).json({ error: 'Project not found' });
|
|
1129
|
-
}
|
|
1130
|
-
// Construct the JSONL file path
|
|
1131
|
-
// Claude stores session files in ~/.claude/projects/[encoded-project-path]/[session-id].jsonl
|
|
1132
|
-
// The encoding replaces any non-alphanumeric character (except -) with -
|
|
1133
|
-
const encodedPath = projectPath.replace(/[^a-zA-Z0-9-]/g, '-');
|
|
1134
|
-
const projectDir = path.join(homeDir, '.claude', 'projects', encodedPath);
|
|
1135
|
-
// Prefer the indexed transcript path (already produced by the trusted
|
|
1136
|
-
// session synchronizer); fall back to the conventional location
|
|
1137
|
-
// derived from the provider-native session id.
|
|
1138
|
-
let jsonlPath = sessionRow?.jsonl_path;
|
|
1139
|
-
if (!jsonlPath) {
|
|
1140
|
-
jsonlPath = path.join(projectDir, `${providerNativeSessionId}.jsonl`);
|
|
1141
|
-
// Constrain the constructed path to projectDir (the id is
|
|
1142
|
-
// caller-influenced in this fallback branch).
|
|
1143
|
-
const rel = path.relative(path.resolve(projectDir), path.resolve(jsonlPath));
|
|
1144
|
-
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
1145
|
-
return res.status(400).json({ error: 'Invalid path' });
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
// Read and parse the JSONL file
|
|
1149
|
-
let fileContent;
|
|
1150
|
-
try {
|
|
1151
|
-
fileContent = await fsPromises.readFile(jsonlPath, 'utf8');
|
|
1152
|
-
}
|
|
1153
|
-
catch (error) {
|
|
1154
|
-
if (error.code === 'ENOENT') {
|
|
1155
|
-
return res.status(404).json({ error: 'Session file not found', path: jsonlPath });
|
|
1156
|
-
}
|
|
1157
|
-
throw error; // Re-throw other errors to be caught by outer try-catch
|
|
1158
|
-
}
|
|
1159
|
-
const lines = fileContent.trim().split('\n');
|
|
1160
|
-
const parsedContextWindow = parseInt(process.env.CONTEXT_WINDOW, 10);
|
|
1161
|
-
const contextWindow = Number.isFinite(parsedContextWindow) ? parsedContextWindow : 160000;
|
|
1162
|
-
let inputTokens = 0;
|
|
1163
|
-
let outputTokens = 0;
|
|
1164
|
-
let cacheReadTokens = 0;
|
|
1165
|
-
let cacheCreationTokens = 0;
|
|
1166
|
-
// Find the latest assistant message with usage data (scan from end)
|
|
1167
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1168
|
-
try {
|
|
1169
|
-
const entry = JSON.parse(lines[i]);
|
|
1170
|
-
// Only count assistant messages which have usage data
|
|
1171
|
-
if (entry.type === 'assistant' && entry.message?.usage) {
|
|
1172
|
-
const usage = entry.message.usage;
|
|
1173
|
-
// Use token counts from latest assistant message only
|
|
1174
|
-
const directInputTokens = readUsageNumber(usage.input_tokens ?? usage.inputTokens);
|
|
1175
|
-
cacheReadTokens = readUsageNumber(usage.cache_read_input_tokens ?? usage.cacheReadInputTokens ?? usage.cacheReadTokens);
|
|
1176
|
-
cacheCreationTokens = readUsageNumber(usage.cache_creation_input_tokens ?? usage.cacheCreationInputTokens ?? usage.cacheCreationTokens);
|
|
1177
|
-
inputTokens = directInputTokens + cacheReadTokens + cacheCreationTokens;
|
|
1178
|
-
outputTokens = readUsageNumber(usage.output_tokens ?? usage.outputTokens);
|
|
1179
|
-
break; // Stop after finding the latest assistant message
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
catch (parseError) {
|
|
1183
|
-
// Skip lines that can't be parsed
|
|
1184
|
-
continue;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
const totalUsed = inputTokens + outputTokens;
|
|
1188
|
-
const cacheTokens = cacheReadTokens + cacheCreationTokens;
|
|
1189
|
-
res.json({
|
|
1190
|
-
used: totalUsed,
|
|
1191
|
-
total: contextWindow,
|
|
1192
|
-
inputTokens,
|
|
1193
|
-
outputTokens,
|
|
1194
|
-
cacheReadTokens,
|
|
1195
|
-
cacheCreationTokens,
|
|
1196
|
-
cacheTokens,
|
|
1197
|
-
breakdown: {
|
|
1198
|
-
input: inputTokens,
|
|
1199
|
-
output: outputTokens
|
|
1200
|
-
}
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
catch (error) {
|
|
1204
|
-
console.error('Error reading session token usage:', error);
|
|
1205
|
-
res.status(500).json({ error: 'Failed to read session token usage' });
|
|
1206
|
-
}
|
|
1207
|
-
});
|
|
177
|
+
// Chat uploads live under /api/assets (server/modules/assets), which stores
|
|
178
|
+
// images and general files in the global ~/.cloudcli/assets folder.
|
|
1208
179
|
// Serve React app for all other routes (excluding static files)
|
|
1209
180
|
app.get('*', (req, res) => {
|
|
1210
181
|
// Skip requests for static assets (files with extensions)
|
|
@@ -1249,142 +220,20 @@ app.use((err, req, res, next) => {
|
|
|
1249
220
|
},
|
|
1250
221
|
});
|
|
1251
222
|
});
|
|
1252
|
-
|
|
1253
|
-
function permToRwx(perm) {
|
|
1254
|
-
const r = perm & 4 ? 'r' : '-';
|
|
1255
|
-
const w = perm & 2 ? 'w' : '-';
|
|
1256
|
-
const x = perm & 1 ? 'x' : '-';
|
|
1257
|
-
return r + w + x;
|
|
1258
|
-
}
|
|
1259
|
-
// Directories that are almost never interesting for a project tree but can
|
|
1260
|
-
// contain tens of thousands of files. Skipping them before recursion keeps
|
|
1261
|
-
// traversal time bounded on large monorepos and high-latency filesystems
|
|
1262
|
-
// (NFS / SMB).
|
|
1263
|
-
const IGNORED_DIRS = new Set([
|
|
1264
|
-
// JS / TS toolchains
|
|
1265
|
-
'node_modules', 'dist', 'build', '.next', '.nuxt', '.cache', '.parcel-cache',
|
|
1266
|
-
// VCS
|
|
1267
|
-
'.git', '.svn', '.hg',
|
|
1268
|
-
// Python
|
|
1269
|
-
'__pycache__', '.pytest_cache', '.mypy_cache', '.tox', 'venv', '.venv',
|
|
1270
|
-
// Rust / Go / Java / Ruby
|
|
1271
|
-
'target', 'vendor',
|
|
1272
|
-
// Build output / IDE
|
|
1273
|
-
'.gradle', '.idea', 'coverage', '.nyc_output'
|
|
1274
|
-
]);
|
|
1275
|
-
const DEFAULT_FS_CONCURRENCY = 64;
|
|
1276
|
-
const parsedFsConcurrency = Number.parseInt(process.env.FS_CONCURRENCY || '', 10);
|
|
1277
|
-
const FS_CONCURRENCY = Number.isFinite(parsedFsConcurrency) && parsedFsConcurrency > 0
|
|
1278
|
-
? parsedFsConcurrency
|
|
1279
|
-
: DEFAULT_FS_CONCURRENCY;
|
|
1280
|
-
let activeFsOperations = 0;
|
|
1281
|
-
const pendingFsOperations = [];
|
|
1282
|
-
async function acquire() {
|
|
1283
|
-
if (activeFsOperations < FS_CONCURRENCY) {
|
|
1284
|
-
activeFsOperations += 1;
|
|
1285
|
-
return;
|
|
1286
|
-
}
|
|
1287
|
-
await new Promise((resolve) => {
|
|
1288
|
-
pendingFsOperations.push(resolve);
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
function release() {
|
|
1292
|
-
const next = pendingFsOperations.shift();
|
|
1293
|
-
if (next) {
|
|
1294
|
-
next();
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
activeFsOperations = Math.max(0, activeFsOperations - 1);
|
|
1298
|
-
}
|
|
1299
|
-
async function getFileTree(dirPath, maxDepth = 3, currentDepth = 0, showHidden = true) {
|
|
1300
|
-
// Using fsPromises from import
|
|
1301
|
-
let entries;
|
|
1302
|
-
try {
|
|
1303
|
-
await acquire();
|
|
1304
|
-
try {
|
|
1305
|
-
entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
|
|
1306
|
-
}
|
|
1307
|
-
finally {
|
|
1308
|
-
release();
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
catch (error) {
|
|
1312
|
-
// Only log non-permission errors to avoid spam
|
|
1313
|
-
if (error.code !== 'EACCES' && error.code !== 'EPERM') {
|
|
1314
|
-
console.error('Error reading directory:', error);
|
|
1315
|
-
}
|
|
1316
|
-
return [];
|
|
1317
|
-
}
|
|
1318
|
-
const filteredEntries = entries.filter((entry) => !(entry.isDirectory() && IGNORED_DIRS.has(entry.name)));
|
|
1319
|
-
// Process every entry in parallel. On high-latency filesystems (NFS/SMB)
|
|
1320
|
-
// serial stat() was the real bottleneck — issuing them concurrently lets
|
|
1321
|
-
// the kernel pipeline the round-trips and the recursive calls overlap too.
|
|
1322
|
-
const items = await Promise.all(filteredEntries.map(async (entry) => {
|
|
1323
|
-
const itemPath = path.join(dirPath, entry.name);
|
|
1324
|
-
const item = {
|
|
1325
|
-
name: entry.name,
|
|
1326
|
-
path: itemPath,
|
|
1327
|
-
type: entry.isDirectory() ? 'directory' : 'file'
|
|
1328
|
-
};
|
|
1329
|
-
// Get file stats for additional metadata
|
|
1330
|
-
try {
|
|
1331
|
-
await acquire();
|
|
1332
|
-
try {
|
|
1333
|
-
const stats = await fsPromises.lstat(itemPath);
|
|
1334
|
-
item.size = stats.size;
|
|
1335
|
-
item.modified = stats.mtime.toISOString();
|
|
1336
|
-
// Mark symlinks so UI can distinguish them
|
|
1337
|
-
if (stats.isSymbolicLink()) {
|
|
1338
|
-
item.isSymlink = true;
|
|
1339
|
-
}
|
|
1340
|
-
// Convert permissions to rwx format
|
|
1341
|
-
const mode = stats.mode;
|
|
1342
|
-
const ownerPerm = (mode >> 6) & 7;
|
|
1343
|
-
const groupPerm = (mode >> 3) & 7;
|
|
1344
|
-
const otherPerm = mode & 7;
|
|
1345
|
-
item.permissions =
|
|
1346
|
-
((mode >> 6) & 7).toString() +
|
|
1347
|
-
((mode >> 3) & 7).toString() +
|
|
1348
|
-
(mode & 7).toString();
|
|
1349
|
-
item.permissionsRwx =
|
|
1350
|
-
permToRwx(ownerPerm) +
|
|
1351
|
-
permToRwx(groupPerm) +
|
|
1352
|
-
permToRwx(otherPerm);
|
|
1353
|
-
}
|
|
1354
|
-
finally {
|
|
1355
|
-
release();
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
catch (statError) {
|
|
1359
|
-
// If stat fails, provide default values
|
|
1360
|
-
item.size = 0;
|
|
1361
|
-
item.modified = null;
|
|
1362
|
-
item.permissions = '000';
|
|
1363
|
-
item.permissionsRwx = '---------';
|
|
1364
|
-
}
|
|
1365
|
-
if (entry.isDirectory() && currentDepth < maxDepth) {
|
|
1366
|
-
// Recurse. Let readdir's own EACCES bubble up through the catch in
|
|
1367
|
-
// the recursive call rather than doing a separate access() probe
|
|
1368
|
-
// (which doubled the round-trip count on SMB without adding info).
|
|
1369
|
-
// The recursive call starts with a bounded readdir; holding a permit
|
|
1370
|
-
// for the whole subtree can deadlock when sibling directories are
|
|
1371
|
-
// waiting on their own children.
|
|
1372
|
-
item.children = await getFileTree(itemPath, maxDepth, currentDepth + 1, showHidden);
|
|
1373
|
-
}
|
|
1374
|
-
return item;
|
|
1375
|
-
}));
|
|
1376
|
-
return items.sort((a, b) => {
|
|
1377
|
-
if (a.type !== b.type) {
|
|
1378
|
-
return a.type === 'directory' ? -1 : 1;
|
|
1379
|
-
}
|
|
1380
|
-
return a.name.localeCompare(b.name);
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1383
|
-
const SERVER_PORT = process.env.SERVER_PORT || 3001;
|
|
223
|
+
const SERVER_PORT = Number.parseInt(process.env.SERVER_PORT || '3001', 10);
|
|
1384
224
|
const HOST = process.env.HOST || '0.0.0.0';
|
|
1385
225
|
const DISPLAY_HOST = getConnectableHost(HOST);
|
|
1386
226
|
const VITE_PORT = process.env.VITE_PORT || 5173;
|
|
1387
227
|
const LOCAL_SERVER_MARKER_PATH = path.join(os.homedir(), '.cloudcli', 'local-server.json');
|
|
228
|
+
function getErrorCode(error) {
|
|
229
|
+
if (typeof error !== 'object' || error === null || !('code' in error)) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
return String(error.code);
|
|
233
|
+
}
|
|
234
|
+
function getErrorMessage(error) {
|
|
235
|
+
return error instanceof Error ? error.message : String(error);
|
|
236
|
+
}
|
|
1388
237
|
async function writeLocalServerMarker() {
|
|
1389
238
|
const marker = {
|
|
1390
239
|
pid: process.pid,
|
|
@@ -1406,15 +255,15 @@ async function removeLocalServerMarker() {
|
|
|
1406
255
|
return;
|
|
1407
256
|
}
|
|
1408
257
|
catch (error) {
|
|
1409
|
-
if (error
|
|
258
|
+
if (getErrorCode(error) === 'ENOENT')
|
|
1410
259
|
return;
|
|
1411
260
|
}
|
|
1412
261
|
try {
|
|
1413
262
|
await fsPromises.unlink(LOCAL_SERVER_MARKER_PATH);
|
|
1414
263
|
}
|
|
1415
264
|
catch (error) {
|
|
1416
|
-
if (error
|
|
1417
|
-
console.warn('[WARN] Could not remove local server marker:', error
|
|
265
|
+
if (getErrorCode(error) !== 'ENOENT') {
|
|
266
|
+
console.warn('[WARN] Could not remove local server marker:', getErrorMessage(error));
|
|
1418
267
|
}
|
|
1419
268
|
}
|
|
1420
269
|
}
|
|
@@ -1429,25 +278,25 @@ async function startServer() {
|
|
|
1429
278
|
const distIndexPath = path.join(APP_ROOT, 'dist', 'index.html');
|
|
1430
279
|
const isProduction = fs.existsSync(distIndexPath);
|
|
1431
280
|
// Log Claude implementation mode
|
|
1432
|
-
console.log(`${
|
|
281
|
+
console.log(`${terminalTextStyles.info('[INFO]')} Using Claude Agents SDK for Claude integration`);
|
|
1433
282
|
console.log('');
|
|
1434
283
|
if (isProduction) {
|
|
1435
|
-
console.log(`${
|
|
284
|
+
console.log(`${terminalTextStyles.info('[INFO]')} To run in production mode, go to http://${DISPLAY_HOST}:${SERVER_PORT}`);
|
|
1436
285
|
}
|
|
1437
|
-
console.log(`${
|
|
286
|
+
console.log(`${terminalTextStyles.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`);
|
|
1438
287
|
server.listen(SERVER_PORT, HOST, async () => {
|
|
1439
288
|
const appInstallPath = APP_ROOT;
|
|
1440
289
|
await writeLocalServerMarker().catch((error) => {
|
|
1441
290
|
console.warn('[WARN] Could not write local server marker:', error.message);
|
|
1442
291
|
});
|
|
1443
292
|
console.log('');
|
|
1444
|
-
console.log(
|
|
1445
|
-
console.log(` ${
|
|
1446
|
-
console.log(
|
|
293
|
+
console.log(terminalTextStyles.dim('═'.repeat(63)));
|
|
294
|
+
console.log(` ${terminalTextStyles.bright('CloudCLI Server - Ready')}`);
|
|
295
|
+
console.log(terminalTextStyles.dim('═'.repeat(63)));
|
|
1447
296
|
console.log('');
|
|
1448
|
-
console.log(`${
|
|
1449
|
-
console.log(`${
|
|
1450
|
-
console.log(`${
|
|
297
|
+
console.log(`${terminalTextStyles.info('[INFO]')} Server URL: ${terminalTextStyles.bright('http://' + DISPLAY_HOST + ':' + SERVER_PORT)}`);
|
|
298
|
+
console.log(`${terminalTextStyles.info('[INFO]')} Installed at: ${terminalTextStyles.dim(appInstallPath)}`);
|
|
299
|
+
console.log(`${terminalTextStyles.tip('[TIP]')} Run "cloudcli status" for full configuration details`);
|
|
1451
300
|
console.log('');
|
|
1452
301
|
// Start watching the projects folder for changes
|
|
1453
302
|
await initializeSessionsWatcher();
|
|
@@ -1463,19 +312,19 @@ async function startServer() {
|
|
|
1463
312
|
await browserUseService.stopAllSessions();
|
|
1464
313
|
}
|
|
1465
314
|
catch (err) {
|
|
1466
|
-
console.error('[Browser] Error stopping sessions during shutdown:', err
|
|
315
|
+
console.error('[Browser] Error stopping sessions during shutdown:', getErrorMessage(err));
|
|
1467
316
|
}
|
|
1468
317
|
try {
|
|
1469
318
|
await stopAllPlugins();
|
|
1470
319
|
}
|
|
1471
320
|
catch (err) {
|
|
1472
|
-
console.error('[Plugins] Error stopping plugins during shutdown:', err
|
|
321
|
+
console.error('[Plugins] Error stopping plugins during shutdown:', getErrorMessage(err));
|
|
1473
322
|
}
|
|
1474
323
|
try {
|
|
1475
324
|
await removeLocalServerMarker();
|
|
1476
325
|
}
|
|
1477
326
|
catch (err) {
|
|
1478
|
-
console.error('[Local Server] Error removing server marker during shutdown:', err
|
|
327
|
+
console.error('[Local Server] Error removing server marker during shutdown:', getErrorMessage(err));
|
|
1479
328
|
}
|
|
1480
329
|
process.exit(0);
|
|
1481
330
|
};
|