profclaw 0.0.1-beta.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/.env.example +215 -0
- package/CHANGELOG.md +49 -0
- package/CONTRIBUTING.md +119 -0
- package/Dockerfile +107 -0
- package/LICENSE +661 -0
- package/README.md +196 -0
- package/config/agents.yml +55 -0
- package/config/budget.yml +15 -0
- package/config/pricing.yml +46 -0
- package/config/settings.yml +168 -0
- package/dist/adapters/claude-code.d.ts +46 -0
- package/dist/adapters/claude-code.js +215 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/ollama.d.ts +45 -0
- package/dist/adapters/ollama.js +277 -0
- package/dist/adapters/openclaw.d.ts +60 -0
- package/dist/adapters/openclaw.js +289 -0
- package/dist/adapters/registry.d.ts +56 -0
- package/dist/adapters/registry.js +163 -0
- package/dist/agents/executor.d.ts +78 -0
- package/dist/agents/executor.js +657 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.js +27 -0
- package/dist/agents/stop-conditions.d.ts +108 -0
- package/dist/agents/stop-conditions.js +299 -0
- package/dist/agents/types.d.ts +185 -0
- package/dist/agents/types.js +14 -0
- package/dist/ai/embedding-service.d.ts +96 -0
- package/dist/ai/embedding-service.js +333 -0
- package/dist/auth/api-tokens.d.ts +71 -0
- package/dist/auth/api-tokens.js +235 -0
- package/dist/auth/auth-service.d.ts +75 -0
- package/dist/auth/auth-service.js +557 -0
- package/dist/auth/device-identity.d.ts +96 -0
- package/dist/auth/device-identity.js +250 -0
- package/dist/auth/github-oauth.d.ts +20 -0
- package/dist/auth/github-oauth.js +59 -0
- package/dist/auth/jira-oauth.d.ts +25 -0
- package/dist/auth/jira-oauth.js +75 -0
- package/dist/auth/linear-oauth.d.ts +21 -0
- package/dist/auth/linear-oauth.js +59 -0
- package/dist/auth/middleware.d.ts +44 -0
- package/dist/auth/middleware.js +190 -0
- package/dist/auth/pairing-codes.d.ts +109 -0
- package/dist/auth/pairing-codes.js +274 -0
- package/dist/auth/password.d.ts +53 -0
- package/dist/auth/password.js +126 -0
- package/dist/auth/qr-pairing.d.ts +36 -0
- package/dist/auth/qr-pairing.js +138 -0
- package/dist/auth/slack-oauth.d.ts +31 -0
- package/dist/auth/slack-oauth.js +80 -0
- package/dist/backup/index.d.ts +50 -0
- package/dist/backup/index.js +184 -0
- package/dist/browser/adapters.d.ts +25 -0
- package/dist/browser/adapters.js +32 -0
- package/dist/browser/cli.d.ts +19 -0
- package/dist/browser/cli.js +277 -0
- package/dist/browser/compression.d.ts +32 -0
- package/dist/browser/compression.js +269 -0
- package/dist/browser/extensions.d.ts +144 -0
- package/dist/browser/extensions.js +251 -0
- package/dist/browser/index.d.ts +37 -0
- package/dist/browser/index.js +43 -0
- package/dist/browser/refs.d.ts +46 -0
- package/dist/browser/refs.js +120 -0
- package/dist/browser/search.d.ts +36 -0
- package/dist/browser/search.js +193 -0
- package/dist/browser/service.d.ts +94 -0
- package/dist/browser/service.js +358 -0
- package/dist/browser/snapshot.d.ts +19 -0
- package/dist/browser/snapshot.js +250 -0
- package/dist/browser/tools.d.ts +259 -0
- package/dist/browser/tools.js +463 -0
- package/dist/browser/types.d.ts +134 -0
- package/dist/browser/types.js +17 -0
- package/dist/chat/agentic-executor.d.ts +108 -0
- package/dist/chat/agentic-executor.js +739 -0
- package/dist/chat/api-key-detector.d.ts +41 -0
- package/dist/chat/api-key-detector.js +290 -0
- package/dist/chat/auto-reply.d.ts +90 -0
- package/dist/chat/auto-reply.js +272 -0
- package/dist/chat/conversations.d.ts +91 -0
- package/dist/chat/conversations.js +297 -0
- package/dist/chat/execution/audit.d.ts +192 -0
- package/dist/chat/execution/audit.js +323 -0
- package/dist/chat/execution/executor.d.ts +56 -0
- package/dist/chat/execution/executor.js +654 -0
- package/dist/chat/execution/guardrails.d.ts +98 -0
- package/dist/chat/execution/guardrails.js +571 -0
- package/dist/chat/execution/index.d.ts +61 -0
- package/dist/chat/execution/index.js +129 -0
- package/dist/chat/execution/model-capability.d.ts +34 -0
- package/dist/chat/execution/model-capability.js +96 -0
- package/dist/chat/execution/process-pool.d.ts +110 -0
- package/dist/chat/execution/process-pool.js +290 -0
- package/dist/chat/execution/pty.d.ts +92 -0
- package/dist/chat/execution/pty.js +285 -0
- package/dist/chat/execution/rate-limiter.d.ts +87 -0
- package/dist/chat/execution/rate-limiter.js +305 -0
- package/dist/chat/execution/registry.d.ts +54 -0
- package/dist/chat/execution/registry.js +220 -0
- package/dist/chat/execution/sandbox.d.ts +109 -0
- package/dist/chat/execution/sandbox.js +684 -0
- package/dist/chat/execution/secrets.d.ts +45 -0
- package/dist/chat/execution/secrets.js +338 -0
- package/dist/chat/execution/security.d.ts +179 -0
- package/dist/chat/execution/security.js +739 -0
- package/dist/chat/execution/self-correction.d.ts +162 -0
- package/dist/chat/execution/self-correction.js +557 -0
- package/dist/chat/execution/session-manager.d.ts +79 -0
- package/dist/chat/execution/session-manager.js +356 -0
- package/dist/chat/execution/session-spawn/config.d.ts +37 -0
- package/dist/chat/execution/session-spawn/config.js +83 -0
- package/dist/chat/execution/session-spawn/index.d.ts +10 -0
- package/dist/chat/execution/session-spawn/index.js +10 -0
- package/dist/chat/execution/session-spawn/manager.d.ts +35 -0
- package/dist/chat/execution/session-spawn/manager.js +418 -0
- package/dist/chat/execution/session-spawn/types.d.ts +117 -0
- package/dist/chat/execution/session-spawn/types.js +16 -0
- package/dist/chat/execution/smart-prompts.d.ts +34 -0
- package/dist/chat/execution/smart-prompts.js +151 -0
- package/dist/chat/execution/stream-results.d.ts +65 -0
- package/dist/chat/execution/stream-results.js +156 -0
- package/dist/chat/execution/tool-router.d.ts +76 -0
- package/dist/chat/execution/tool-router.js +296 -0
- package/dist/chat/execution/tools/agents-list.d.ts +49 -0
- package/dist/chat/execution/tools/agents-list.js +165 -0
- package/dist/chat/execution/tools/browser.d.ts +12 -0
- package/dist/chat/execution/tools/browser.js +14 -0
- package/dist/chat/execution/tools/canvas.d.ts +61 -0
- package/dist/chat/execution/tools/canvas.js +150 -0
- package/dist/chat/execution/tools/complete-task.d.ts +72 -0
- package/dist/chat/execution/tools/complete-task.js +104 -0
- package/dist/chat/execution/tools/cron-tool.d.ts +204 -0
- package/dist/chat/execution/tools/cron-tool.js +680 -0
- package/dist/chat/execution/tools/discord-actions.d.ts +120 -0
- package/dist/chat/execution/tools/discord-actions.js +361 -0
- package/dist/chat/execution/tools/exec.d.ts +43 -0
- package/dist/chat/execution/tools/exec.js +223 -0
- package/dist/chat/execution/tools/file-ops.d.ts +175 -0
- package/dist/chat/execution/tools/file-ops.js +723 -0
- package/dist/chat/execution/tools/git.d.ts +143 -0
- package/dist/chat/execution/tools/git.js +395 -0
- package/dist/chat/execution/tools/github.d.ts +42 -0
- package/dist/chat/execution/tools/github.js +184 -0
- package/dist/chat/execution/tools/glinr-ops.d.ts +202 -0
- package/dist/chat/execution/tools/glinr-ops.js +694 -0
- package/dist/chat/execution/tools/image-analyze.d.ts +29 -0
- package/dist/chat/execution/tools/image-analyze.js +127 -0
- package/dist/chat/execution/tools/index.d.ts +64 -0
- package/dist/chat/execution/tools/index.js +245 -0
- package/dist/chat/execution/tools/integrations.d.ts +109 -0
- package/dist/chat/execution/tools/integrations.js +237 -0
- package/dist/chat/execution/tools/link-understand.d.ts +34 -0
- package/dist/chat/execution/tools/link-understand.js +307 -0
- package/dist/chat/execution/tools/maintenance.d.ts +29 -0
- package/dist/chat/execution/tools/maintenance.js +105 -0
- package/dist/chat/execution/tools/memory-tools.d.ts +76 -0
- package/dist/chat/execution/tools/memory-tools.js +278 -0
- package/dist/chat/execution/tools/openai-image-gen.d.ts +51 -0
- package/dist/chat/execution/tools/openai-image-gen.js +212 -0
- package/dist/chat/execution/tools/profclaw-ops.d.ts +204 -0
- package/dist/chat/execution/tools/profclaw-ops.js +665 -0
- package/dist/chat/execution/tools/session-spawn.d.ts +146 -0
- package/dist/chat/execution/tools/session-spawn.js +544 -0
- package/dist/chat/execution/tools/session-status.d.ts +38 -0
- package/dist/chat/execution/tools/session-status.js +185 -0
- package/dist/chat/execution/tools/sessions-list.d.ts +49 -0
- package/dist/chat/execution/tools/sessions-list.js +153 -0
- package/dist/chat/execution/tools/sessions-send.d.ts +48 -0
- package/dist/chat/execution/tools/sessions-send.js +207 -0
- package/dist/chat/execution/tools/sessions-spawn.d.ts +51 -0
- package/dist/chat/execution/tools/sessions-spawn.js +137 -0
- package/dist/chat/execution/tools/slack-actions.d.ts +90 -0
- package/dist/chat/execution/tools/slack-actions.js +272 -0
- package/dist/chat/execution/tools/subagent-orchestrate.d.ts +66 -0
- package/dist/chat/execution/tools/subagent-orchestrate.js +313 -0
- package/dist/chat/execution/tools/system.d.ts +121 -0
- package/dist/chat/execution/tools/system.js +415 -0
- package/dist/chat/execution/tools/telegram-actions.d.ts +126 -0
- package/dist/chat/execution/tools/telegram-actions.js +339 -0
- package/dist/chat/execution/tools/test-run.d.ts +43 -0
- package/dist/chat/execution/tools/test-run.js +391 -0
- package/dist/chat/execution/tools/tts-speak.d.ts +39 -0
- package/dist/chat/execution/tools/tts-speak.js +199 -0
- package/dist/chat/execution/tools/web-fetch.d.ts +42 -0
- package/dist/chat/execution/tools/web-fetch.js +247 -0
- package/dist/chat/execution/tools/web-search.d.ts +53 -0
- package/dist/chat/execution/tools/web-search.js +130 -0
- package/dist/chat/execution/types.d.ts +337 -0
- package/dist/chat/execution/types.js +8 -0
- package/dist/chat/execution/workflows/built-in.d.ts +10 -0
- package/dist/chat/execution/workflows/built-in.js +249 -0
- package/dist/chat/execution/workflows/executor.d.ts +13 -0
- package/dist/chat/execution/workflows/executor.js +259 -0
- package/dist/chat/execution/workflows/index.d.ts +5 -0
- package/dist/chat/execution/workflows/index.js +5 -0
- package/dist/chat/execution/workflows/trigger.d.ts +15 -0
- package/dist/chat/execution/workflows/trigger.js +111 -0
- package/dist/chat/execution/workflows/types.d.ts +80 -0
- package/dist/chat/execution/workflows/types.js +2 -0
- package/dist/chat/failover/error.d.ts +73 -0
- package/dist/chat/failover/error.js +412 -0
- package/dist/chat/failover/index.d.ts +10 -0
- package/dist/chat/failover/index.js +11 -0
- package/dist/chat/failover/model-fallback.d.ts +97 -0
- package/dist/chat/failover/model-fallback.js +328 -0
- package/dist/chat/failover/types.d.ts +47 -0
- package/dist/chat/failover/types.js +8 -0
- package/dist/chat/format/chunk.d.ts +59 -0
- package/dist/chat/format/chunk.js +253 -0
- package/dist/chat/format/index.d.ts +7 -0
- package/dist/chat/format/index.js +7 -0
- package/dist/chat/format/tool-result-formatter.d.ts +30 -0
- package/dist/chat/format/tool-result-formatter.js +177 -0
- package/dist/chat/group.d.ts +232 -0
- package/dist/chat/group.js +311 -0
- package/dist/chat/index.d.ts +21 -0
- package/dist/chat/index.js +30 -0
- package/dist/chat/memory.d.ts +108 -0
- package/dist/chat/memory.js +343 -0
- package/dist/chat/message-handler.d.ts +38 -0
- package/dist/chat/message-handler.js +274 -0
- package/dist/chat/proactive/index.d.ts +167 -0
- package/dist/chat/proactive/index.js +721 -0
- package/dist/chat/prompt-adapter.d.ts +52 -0
- package/dist/chat/prompt-adapter.js +245 -0
- package/dist/chat/providers/dingtalk/index.d.ts +69 -0
- package/dist/chat/providers/dingtalk/index.js +225 -0
- package/dist/chat/providers/discord/index.d.ts +264 -0
- package/dist/chat/providers/discord/index.js +657 -0
- package/dist/chat/providers/feishu/index.d.ts +97 -0
- package/dist/chat/providers/feishu/index.js +362 -0
- package/dist/chat/providers/googlechat/index.d.ts +269 -0
- package/dist/chat/providers/googlechat/index.js +634 -0
- package/dist/chat/providers/imessage/index.d.ts +61 -0
- package/dist/chat/providers/imessage/index.js +166 -0
- package/dist/chat/providers/index.d.ts +76 -0
- package/dist/chat/providers/index.js +430 -0
- package/dist/chat/providers/irc/index.d.ts +101 -0
- package/dist/chat/providers/irc/index.js +557 -0
- package/dist/chat/providers/line/index.d.ts +113 -0
- package/dist/chat/providers/line/index.js +434 -0
- package/dist/chat/providers/matrix/index.d.ts +161 -0
- package/dist/chat/providers/matrix/index.js +559 -0
- package/dist/chat/providers/mattermost/index.d.ts +106 -0
- package/dist/chat/providers/mattermost/index.js +425 -0
- package/dist/chat/providers/msteams/index.d.ts +198 -0
- package/dist/chat/providers/msteams/index.js +694 -0
- package/dist/chat/providers/nextcloud/index.d.ts +52 -0
- package/dist/chat/providers/nextcloud/index.js +156 -0
- package/dist/chat/providers/nostr/index.d.ts +60 -0
- package/dist/chat/providers/nostr/index.js +200 -0
- package/dist/chat/providers/qq/index.d.ts +57 -0
- package/dist/chat/providers/qq/index.js +260 -0
- package/dist/chat/providers/registry.d.ts +89 -0
- package/dist/chat/providers/registry.js +213 -0
- package/dist/chat/providers/signal/index.d.ts +88 -0
- package/dist/chat/providers/signal/index.js +357 -0
- package/dist/chat/providers/slack/index.d.ts +52 -0
- package/dist/chat/providers/slack/index.js +477 -0
- package/dist/chat/providers/synology/index.d.ts +50 -0
- package/dist/chat/providers/synology/index.js +138 -0
- package/dist/chat/providers/telegram/index.d.ts +148 -0
- package/dist/chat/providers/telegram/index.js +492 -0
- package/dist/chat/providers/tlon/index.d.ts +44 -0
- package/dist/chat/providers/tlon/index.js +243 -0
- package/dist/chat/providers/twitch/index.d.ts +79 -0
- package/dist/chat/providers/twitch/index.js +349 -0
- package/dist/chat/providers/types.d.ts +437 -0
- package/dist/chat/providers/types.js +14 -0
- package/dist/chat/providers/webchat/index.d.ts +38 -0
- package/dist/chat/providers/webchat/index.js +214 -0
- package/dist/chat/providers/wecom/index.d.ts +62 -0
- package/dist/chat/providers/wecom/index.js +228 -0
- package/dist/chat/providers/whatsapp/index.d.ts +147 -0
- package/dist/chat/providers/whatsapp/index.js +396 -0
- package/dist/chat/providers/zalo/index.d.ts +54 -0
- package/dist/chat/providers/zalo/index.js +158 -0
- package/dist/chat/providers/zalo-personal/index.d.ts +53 -0
- package/dist/chat/providers/zalo-personal/index.js +212 -0
- package/dist/chat/skills.d.ts +82 -0
- package/dist/chat/skills.js +410 -0
- package/dist/chat/system-prompts.d.ts +76 -0
- package/dist/chat/system-prompts.js +407 -0
- package/dist/chat/tool-handler.d.ts +57 -0
- package/dist/chat/tool-handler.js +336 -0
- package/dist/chat/tools.d.ts +373 -0
- package/dist/chat/tools.js +512 -0
- package/dist/cli/commands/agent.d.ts +3 -0
- package/dist/cli/commands/agent.js +108 -0
- package/dist/cli/commands/auth.d.ts +12 -0
- package/dist/cli/commands/auth.js +301 -0
- package/dist/cli/commands/browser.d.ts +3 -0
- package/dist/cli/commands/browser.js +138 -0
- package/dist/cli/commands/canvas.d.ts +3 -0
- package/dist/cli/commands/canvas.js +155 -0
- package/dist/cli/commands/channels.d.ts +3 -0
- package/dist/cli/commands/channels.js +167 -0
- package/dist/cli/commands/chat.d.ts +9 -0
- package/dist/cli/commands/chat.js +360 -0
- package/dist/cli/commands/completion.d.ts +3 -0
- package/dist/cli/commands/completion.js +170 -0
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.js +193 -0
- package/dist/cli/commands/cost.d.ts +3 -0
- package/dist/cli/commands/cost.js +134 -0
- package/dist/cli/commands/daemon.d.ts +3 -0
- package/dist/cli/commands/daemon.js +381 -0
- package/dist/cli/commands/devices.d.ts +3 -0
- package/dist/cli/commands/devices.js +150 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +295 -0
- package/dist/cli/commands/logs.d.ts +3 -0
- package/dist/cli/commands/logs.js +139 -0
- package/dist/cli/commands/mcp.d.ts +15 -0
- package/dist/cli/commands/mcp.js +157 -0
- package/dist/cli/commands/memory.d.ts +3 -0
- package/dist/cli/commands/memory.js +170 -0
- package/dist/cli/commands/models.d.ts +3 -0
- package/dist/cli/commands/models.js +197 -0
- package/dist/cli/commands/nodes.d.ts +3 -0
- package/dist/cli/commands/nodes.js +193 -0
- package/dist/cli/commands/onboard.d.ts +13 -0
- package/dist/cli/commands/onboard.js +360 -0
- package/dist/cli/commands/plugin.d.ts +15 -0
- package/dist/cli/commands/plugin.js +226 -0
- package/dist/cli/commands/provider.d.ts +16 -0
- package/dist/cli/commands/provider.js +320 -0
- package/dist/cli/commands/security.d.ts +3 -0
- package/dist/cli/commands/security.js +180 -0
- package/dist/cli/commands/serve.d.ts +3 -0
- package/dist/cli/commands/serve.js +179 -0
- package/dist/cli/commands/session.d.ts +3 -0
- package/dist/cli/commands/session.js +172 -0
- package/dist/cli/commands/setup.d.ts +13 -0
- package/dist/cli/commands/setup.js +636 -0
- package/dist/cli/commands/skill.d.ts +15 -0
- package/dist/cli/commands/skill.js +191 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.js +71 -0
- package/dist/cli/commands/summary.d.ts +3 -0
- package/dist/cli/commands/summary.js +160 -0
- package/dist/cli/commands/task.d.ts +3 -0
- package/dist/cli/commands/task.js +170 -0
- package/dist/cli/commands/ticket.d.ts +3 -0
- package/dist/cli/commands/ticket.js +421 -0
- package/dist/cli/commands/tools.d.ts +9 -0
- package/dist/cli/commands/tools.js +508 -0
- package/dist/cli/commands/tui.d.ts +3 -0
- package/dist/cli/commands/tui.js +158 -0
- package/dist/cli/commands/tunnel.d.ts +3 -0
- package/dist/cli/commands/tunnel.js +135 -0
- package/dist/cli/commands/webhooks.d.ts +3 -0
- package/dist/cli/commands/webhooks.js +191 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +127 -0
- package/dist/cli/utils/api.d.ts +17 -0
- package/dist/cli/utils/api.js +63 -0
- package/dist/cli/utils/config.d.ts +31 -0
- package/dist/cli/utils/config.js +81 -0
- package/dist/cli/utils/output.d.ts +58 -0
- package/dist/cli/utils/output.js +148 -0
- package/dist/core/deployment.d.ts +30 -0
- package/dist/core/deployment.js +114 -0
- package/dist/core/sandbox-config.d.ts +63 -0
- package/dist/core/sandbox-config.js +148 -0
- package/dist/costs/budget.d.ts +28 -0
- package/dist/costs/budget.js +57 -0
- package/dist/costs/persistence.d.ts +77 -0
- package/dist/costs/persistence.js +210 -0
- package/dist/costs/pricing.d.ts +16 -0
- package/dist/costs/pricing.js +39 -0
- package/dist/costs/token-tracker.d.ts +34 -0
- package/dist/costs/token-tracker.js +141 -0
- package/dist/cron/heartbeat.d.ts +23 -0
- package/dist/cron/heartbeat.js +109 -0
- package/dist/cron/index.d.ts +22 -0
- package/dist/cron/index.js +37 -0
- package/dist/cron/issue-poller.d.ts +15 -0
- package/dist/cron/issue-poller.js +174 -0
- package/dist/cron/scheduler.d.ts +315 -0
- package/dist/cron/scheduler.js +1008 -0
- package/dist/cron/stale-checker.d.ts +16 -0
- package/dist/cron/stale-checker.js +61 -0
- package/dist/cron/templates.d.ts +107 -0
- package/dist/cron/templates.js +395 -0
- package/dist/diagnostics/exporters.d.ts +21 -0
- package/dist/diagnostics/exporters.js +138 -0
- package/dist/diagnostics/index.d.ts +10 -0
- package/dist/diagnostics/index.js +9 -0
- package/dist/diagnostics/middleware.d.ts +12 -0
- package/dist/diagnostics/middleware.js +58 -0
- package/dist/diagnostics/tracer.d.ts +133 -0
- package/dist/diagnostics/tracer.js +257 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.js +7 -0
- package/dist/discovery/mdns.d.ts +40 -0
- package/dist/discovery/mdns.js +249 -0
- package/dist/gateway/index.d.ts +35 -0
- package/dist/gateway/index.js +36 -0
- package/dist/gateway/router.d.ts +110 -0
- package/dist/gateway/router.js +649 -0
- package/dist/gateway/types.d.ts +275 -0
- package/dist/gateway/types.js +2 -0
- package/dist/gateway/workflows.d.ts +37 -0
- package/dist/gateway/workflows.js +748 -0
- package/dist/hooks/agent-webhook.d.ts +277 -0
- package/dist/hooks/agent-webhook.js +197 -0
- package/dist/hooks/index.d.ts +13 -0
- package/dist/hooks/index.js +15 -0
- package/dist/hooks/prompt-submit.d.ts +45 -0
- package/dist/hooks/prompt-submit.js +111 -0
- package/dist/hooks/schemas.d.ts +563 -0
- package/dist/hooks/schemas.js +108 -0
- package/dist/hooks/session-end.d.ts +25 -0
- package/dist/hooks/session-end.js +153 -0
- package/dist/hooks/tool-use.d.ts +41 -0
- package/dist/hooks/tool-use.js +201 -0
- package/dist/hooks/types.d.ts +76 -0
- package/dist/hooks/types.js +7 -0
- package/dist/integrations/cloudflare-tunnel.d.ts +78 -0
- package/dist/integrations/cloudflare-tunnel.js +268 -0
- package/dist/integrations/github-client.d.ts +27 -0
- package/dist/integrations/github-client.js +79 -0
- package/dist/integrations/github-projects.d.ts +97 -0
- package/dist/integrations/github-projects.js +433 -0
- package/dist/integrations/github-ticket-sync.d.ts +82 -0
- package/dist/integrations/github-ticket-sync.js +352 -0
- package/dist/integrations/github.d.ts +11 -0
- package/dist/integrations/github.js +241 -0
- package/dist/integrations/jira-client.d.ts +30 -0
- package/dist/integrations/jira-client.js +67 -0
- package/dist/integrations/jira.d.ts +13 -0
- package/dist/integrations/jira.js +108 -0
- package/dist/integrations/linear-client.d.ts +23 -0
- package/dist/integrations/linear-client.js +52 -0
- package/dist/integrations/linear.d.ts +15 -0
- package/dist/integrations/linear.js +312 -0
- package/dist/integrations/tailscale.d.ts +68 -0
- package/dist/integrations/tailscale.js +173 -0
- package/dist/integrations/web-search.d.ts +112 -0
- package/dist/integrations/web-search.js +287 -0
- package/dist/intelligence/index.d.ts +8 -0
- package/dist/intelligence/index.js +8 -0
- package/dist/intelligence/ollama.d.ts +50 -0
- package/dist/intelligence/ollama.js +213 -0
- package/dist/intelligence/rules.d.ts +39 -0
- package/dist/intelligence/rules.js +308 -0
- package/dist/labels/index.d.ts +93 -0
- package/dist/labels/index.js +248 -0
- package/dist/mcp/browser-tools.d.ts +10 -0
- package/dist/mcp/browser-tools.js +16 -0
- package/dist/mcp/client.d.ts +54 -0
- package/dist/mcp/client.js +153 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.js +13 -0
- package/dist/mcp/server.d.ts +40 -0
- package/dist/mcp/server.js +826 -0
- package/dist/mcp/tool-adapter.d.ts +49 -0
- package/dist/mcp/tool-adapter.js +193 -0
- package/dist/memory/experience-store.d.ts +121 -0
- package/dist/memory/experience-store.js +439 -0
- package/dist/memory/index.d.ts +8 -0
- package/dist/memory/index.js +25 -0
- package/dist/memory/memory-service.d.ts +312 -0
- package/dist/memory/memory-service.js +1172 -0
- package/dist/memory/memory-watcher.d.ts +116 -0
- package/dist/memory/memory-watcher.js +239 -0
- package/dist/middleware/logging.d.ts +34 -0
- package/dist/middleware/logging.js +184 -0
- package/dist/middleware/rate-limit.d.ts +29 -0
- package/dist/middleware/rate-limit.js +91 -0
- package/dist/notifications/in-app.d.ts +28 -0
- package/dist/notifications/in-app.js +53 -0
- package/dist/notifications/index.d.ts +3 -0
- package/dist/notifications/index.js +3 -0
- package/dist/notifications/push.d.ts +74 -0
- package/dist/notifications/push.js +175 -0
- package/dist/notifications/slack.d.ts +32 -0
- package/dist/notifications/slack.js +157 -0
- package/dist/plugins/clawhub.d.ts +70 -0
- package/dist/plugins/clawhub.js +260 -0
- package/dist/plugins/loader.d.ts +23 -0
- package/dist/plugins/loader.js +140 -0
- package/dist/plugins/marketplace.d.ts +77 -0
- package/dist/plugins/marketplace.js +275 -0
- package/dist/plugins/registry.d.ts +122 -0
- package/dist/plugins/registry.js +352 -0
- package/dist/plugins/sandbox.d.ts +77 -0
- package/dist/plugins/sandbox.js +287 -0
- package/dist/plugins/scaffolder.d.ts +28 -0
- package/dist/plugins/scaffolder.js +409 -0
- package/dist/plugins/sdk.d.ts +35 -0
- package/dist/plugins/sdk.js +8 -0
- package/dist/plugins/search/brave.d.ts +9 -0
- package/dist/plugins/search/brave.js +157 -0
- package/dist/plugins/search/duckduckgo.d.ts +9 -0
- package/dist/plugins/search/duckduckgo.js +171 -0
- package/dist/plugins/search/index.d.ts +13 -0
- package/dist/plugins/search/index.js +23 -0
- package/dist/plugins/search/searxng.d.ts +9 -0
- package/dist/plugins/search/searxng.js +170 -0
- package/dist/plugins/search/serper.d.ts +9 -0
- package/dist/plugins/search/serper.js +135 -0
- package/dist/plugins/search/tavily.d.ts +9 -0
- package/dist/plugins/search/tavily.js +146 -0
- package/dist/plugins/types.d.ts +174 -0
- package/dist/plugins/types.js +8 -0
- package/dist/projects/index.d.ts +133 -0
- package/dist/projects/index.js +897 -0
- package/dist/projects/types.d.ts +444 -0
- package/dist/projects/types.js +207 -0
- package/dist/providers/adapters/anthropic.d.ts +26 -0
- package/dist/providers/adapters/anthropic.js +306 -0
- package/dist/providers/adapters/base.d.ts +53 -0
- package/dist/providers/adapters/base.js +99 -0
- package/dist/providers/adapters/ollama.d.ts +30 -0
- package/dist/providers/adapters/ollama.js +292 -0
- package/dist/providers/ai-sdk.d.ts +178 -0
- package/dist/providers/ai-sdk.js +1552 -0
- package/dist/providers/core/index.d.ts +8 -0
- package/dist/providers/core/index.js +10 -0
- package/dist/providers/core/models.d.ts +31 -0
- package/dist/providers/core/models.js +949 -0
- package/dist/providers/core/types.d.ts +117 -0
- package/dist/providers/core/types.js +83 -0
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/registry.d.ts +93 -0
- package/dist/providers/registry.js +228 -0
- package/dist/providers/schema-utils.d.ts +23 -0
- package/dist/providers/schema-utils.js +545 -0
- package/dist/providers/types.d.ts +609 -0
- package/dist/providers/types.js +279 -0
- package/dist/queue/failure-handler.d.ts +93 -0
- package/dist/queue/failure-handler.js +379 -0
- package/dist/queue/index.d.ts +23 -0
- package/dist/queue/index.js +86 -0
- package/dist/queue/memory-queue.d.ts +30 -0
- package/dist/queue/memory-queue.js +429 -0
- package/dist/queue/notifications.d.ts +6 -0
- package/dist/queue/notifications.js +128 -0
- package/dist/queue/task-queue.d.ts +55 -0
- package/dist/queue/task-queue.js +518 -0
- package/dist/queue/webhook-queue.d.ts +47 -0
- package/dist/queue/webhook-queue.js +158 -0
- package/dist/routes/agents.d.ts +4 -0
- package/dist/routes/agents.js +45 -0
- package/dist/routes/auth.d.ts +19 -0
- package/dist/routes/auth.js +492 -0
- package/dist/routes/backup.d.ts +6 -0
- package/dist/routes/backup.js +71 -0
- package/dist/routes/chat.d.ts +10 -0
- package/dist/routes/chat.js +1678 -0
- package/dist/routes/clawhub.d.ts +8 -0
- package/dist/routes/clawhub.js +89 -0
- package/dist/routes/costs.d.ts +4 -0
- package/dist/routes/costs.js +78 -0
- package/dist/routes/cron.d.ts +11 -0
- package/dist/routes/cron.js +708 -0
- package/dist/routes/devices.d.ts +9 -0
- package/dist/routes/devices.js +490 -0
- package/dist/routes/discord.d.ts +15 -0
- package/dist/routes/discord.js +524 -0
- package/dist/routes/dlq.d.ts +4 -0
- package/dist/routes/dlq.js +64 -0
- package/dist/routes/gateway.d.ts +4 -0
- package/dist/routes/gateway.js +253 -0
- package/dist/routes/health.d.ts +29 -0
- package/dist/routes/health.js +296 -0
- package/dist/routes/hooks.d.ts +4 -0
- package/dist/routes/hooks.js +170 -0
- package/dist/routes/import.d.ts +9 -0
- package/dist/routes/import.js +540 -0
- package/dist/routes/index.d.ts +38 -0
- package/dist/routes/index.js +39 -0
- package/dist/routes/integrations.d.ts +10 -0
- package/dist/routes/integrations.js +200 -0
- package/dist/routes/labels.d.ts +8 -0
- package/dist/routes/labels.js +240 -0
- package/dist/routes/mcp.d.ts +14 -0
- package/dist/routes/mcp.js +125 -0
- package/dist/routes/memory.d.ts +10 -0
- package/dist/routes/memory.js +622 -0
- package/dist/routes/notifications.d.ts +9 -0
- package/dist/routes/notifications.js +154 -0
- package/dist/routes/oobe.d.ts +10 -0
- package/dist/routes/oobe.js +405 -0
- package/dist/routes/openapi.d.ts +9 -0
- package/dist/routes/openapi.js +537 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/plugins.js +108 -0
- package/dist/routes/projects.d.ts +8 -0
- package/dist/routes/projects.js +592 -0
- package/dist/routes/push.d.ts +8 -0
- package/dist/routes/push.js +75 -0
- package/dist/routes/search.d.ts +4 -0
- package/dist/routes/search.js +189 -0
- package/dist/routes/security.d.ts +10 -0
- package/dist/routes/security.js +189 -0
- package/dist/routes/settings.d.ts +4 -0
- package/dist/routes/settings.js +119 -0
- package/dist/routes/setup.d.ts +11 -0
- package/dist/routes/setup.js +525 -0
- package/dist/routes/skills.d.ts +13 -0
- package/dist/routes/skills.js +152 -0
- package/dist/routes/slack.d.ts +12 -0
- package/dist/routes/slack.js +999 -0
- package/dist/routes/states.d.ts +8 -0
- package/dist/routes/states.js +123 -0
- package/dist/routes/stats.d.ts +6 -0
- package/dist/routes/stats.js +369 -0
- package/dist/routes/summaries.d.ts +4 -0
- package/dist/routes/summaries.js +344 -0
- package/dist/routes/sync.d.ts +13 -0
- package/dist/routes/sync.js +268 -0
- package/dist/routes/tasks.d.ts +4 -0
- package/dist/routes/tasks.js +473 -0
- package/dist/routes/telegram.d.ts +14 -0
- package/dist/routes/telegram.js +420 -0
- package/dist/routes/tickets.d.ts +4 -0
- package/dist/routes/tickets.js +1090 -0
- package/dist/routes/tokens.d.ts +4 -0
- package/dist/routes/tokens.js +83 -0
- package/dist/routes/tools.d.ts +14 -0
- package/dist/routes/tools.js +790 -0
- package/dist/routes/tunnels.d.ts +8 -0
- package/dist/routes/tunnels.js +145 -0
- package/dist/routes/users.d.ts +15 -0
- package/dist/routes/users.js +998 -0
- package/dist/routes/voice.d.ts +8 -0
- package/dist/routes/voice.js +127 -0
- package/dist/routes/webchat.d.ts +11 -0
- package/dist/routes/webchat.js +173 -0
- package/dist/routes/webhook-dedup.d.ts +11 -0
- package/dist/routes/webhook-dedup.js +49 -0
- package/dist/routes/webhooks.d.ts +4 -0
- package/dist/routes/webhooks.js +71 -0
- package/dist/routes/whatsapp.d.ts +14 -0
- package/dist/routes/whatsapp.js +391 -0
- package/dist/security/audit-scanner.d.ts +32 -0
- package/dist/security/audit-scanner.js +217 -0
- package/dist/security/fs-guard.d.ts +41 -0
- package/dist/security/fs-guard.js +206 -0
- package/dist/security/prompt-guard.d.ts +47 -0
- package/dist/security/prompt-guard.js +198 -0
- package/dist/security/ssrf-guard.d.ts +38 -0
- package/dist/security/ssrf-guard.js +266 -0
- package/dist/security/types.d.ts +92 -0
- package/dist/security/types.js +8 -0
- package/dist/server/route-loader.d.ts +15 -0
- package/dist/server/route-loader.js +135 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +835 -0
- package/dist/settings/index.d.ts +841 -0
- package/dist/settings/index.js +451 -0
- package/dist/skills/compiler.d.ts +83 -0
- package/dist/skills/compiler.js +358 -0
- package/dist/skills/frontmatter.d.ts +33 -0
- package/dist/skills/frontmatter.js +175 -0
- package/dist/skills/index.d.ts +19 -0
- package/dist/skills/index.js +24 -0
- package/dist/skills/installer.d.ts +22 -0
- package/dist/skills/installer.js +220 -0
- package/dist/skills/loader.d.ts +72 -0
- package/dist/skills/loader.js +484 -0
- package/dist/skills/prompt-builder.d.ts +48 -0
- package/dist/skills/prompt-builder.js +135 -0
- package/dist/skills/registry.d.ts +115 -0
- package/dist/skills/registry.js +225 -0
- package/dist/skills/types.d.ts +202 -0
- package/dist/skills/types.js +24 -0
- package/dist/states/index.d.ts +64 -0
- package/dist/states/index.js +142 -0
- package/dist/storage/adapter.d.ts +146 -0
- package/dist/storage/adapter.js +2 -0
- package/dist/storage/index.d.ts +48 -0
- package/dist/storage/index.js +135 -0
- package/dist/storage/libsql.d.ts +99 -0
- package/dist/storage/libsql.js +1873 -0
- package/dist/storage/migrations.d.ts +65 -0
- package/dist/storage/migrations.js +289 -0
- package/dist/storage/schema.d.ts +10185 -0
- package/dist/storage/schema.js +1113 -0
- package/dist/summaries/extractor.d.ts +56 -0
- package/dist/summaries/extractor.js +377 -0
- package/dist/summaries/index.d.ts +13 -0
- package/dist/summaries/index.js +14 -0
- package/dist/summaries/storage.d.ts +51 -0
- package/dist/summaries/storage.js +101 -0
- package/dist/summaries/templates.d.ts +68 -0
- package/dist/summaries/templates.js +245 -0
- package/dist/sync/adapters/base.d.ts +42 -0
- package/dist/sync/adapters/base.js +142 -0
- package/dist/sync/adapters/github.d.ts +45 -0
- package/dist/sync/adapters/github.js +445 -0
- package/dist/sync/adapters/linear.d.ts +40 -0
- package/dist/sync/adapters/linear.js +434 -0
- package/dist/sync/config.d.ts +388 -0
- package/dist/sync/config.js +281 -0
- package/dist/sync/engine.d.ts +112 -0
- package/dist/sync/engine.js +623 -0
- package/dist/sync/index.d.ts +31 -0
- package/dist/sync/index.js +34 -0
- package/dist/sync/integration.d.ts +53 -0
- package/dist/sync/integration.js +231 -0
- package/dist/sync/types.d.ts +249 -0
- package/dist/sync/types.js +30 -0
- package/dist/tickets/index.d.ts +139 -0
- package/dist/tickets/index.js +1406 -0
- package/dist/tickets/types.d.ts +817 -0
- package/dist/tickets/types.js +310 -0
- package/dist/tools/adapters.d.ts +56 -0
- package/dist/tools/adapters.js +183 -0
- package/dist/tools/index.d.ts +66 -0
- package/dist/tools/index.js +105 -0
- package/dist/tools/types.d.ts +117 -0
- package/dist/tools/types.js +20 -0
- package/dist/types/agent.d.ts +101 -0
- package/dist/types/agent.js +2 -0
- package/dist/types/errors.d.ts +150 -0
- package/dist/types/errors.js +338 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/summary.d.ts +638 -0
- package/dist/types/summary.js +137 -0
- package/dist/types/task.d.ts +123 -0
- package/dist/types/task.js +46 -0
- package/dist/utils/ai-cache.d.ts +72 -0
- package/dist/utils/ai-cache.js +204 -0
- package/dist/utils/ai-inference.d.ts +57 -0
- package/dist/utils/ai-inference.js +190 -0
- package/dist/utils/ai-responder.d.ts +44 -0
- package/dist/utils/ai-responder.js +272 -0
- package/dist/utils/circuit-breaker.d.ts +139 -0
- package/dist/utils/circuit-breaker.js +281 -0
- package/dist/utils/config-loader.d.ts +12 -0
- package/dist/utils/config-loader.js +44 -0
- package/dist/utils/crypto.d.ts +10 -0
- package/dist/utils/crypto.js +59 -0
- package/dist/utils/env-validator.d.ts +44 -0
- package/dist/utils/env-validator.js +236 -0
- package/dist/utils/file-lock.d.ts +85 -0
- package/dist/utils/file-lock.js +204 -0
- package/dist/utils/logger.d.ts +153 -0
- package/dist/utils/logger.js +282 -0
- package/dist/utils/metrics.d.ts +183 -0
- package/dist/utils/metrics.js +345 -0
- package/dist/utils/security.d.ts +90 -0
- package/dist/utils/security.js +347 -0
- package/dist/voice/index.d.ts +79 -0
- package/dist/voice/index.js +144 -0
- package/dist/voice/stt/whisper.d.ts +13 -0
- package/dist/voice/stt/whisper.js +116 -0
- package/dist/voice/tts/elevenlabs.d.ts +18 -0
- package/dist/voice/tts/elevenlabs.js +178 -0
- package/dist/voice/tts/openai-tts.d.ts +19 -0
- package/dist/voice/tts/openai-tts.js +127 -0
- package/dist/voice/tts/system.d.ts +26 -0
- package/dist/voice/tts/system.js +146 -0
- package/dist/voice/wake-word.d.ts +44 -0
- package/dist/voice/wake-word.js +145 -0
- package/docker-compose.yml +222 -0
- package/package.json +154 -0
- package/profclaw.mjs +24 -0
- package/skills/1password/SKILL.md +155 -0
- package/skills/api-tester/SKILL.md +177 -0
- package/skills/apple-notes/SKILL.md +167 -0
- package/skills/apple-reminders/SKILL.md +178 -0
- package/skills/bear-notes/SKILL.md +166 -0
- package/skills/blogwatcher/SKILL.md +177 -0
- package/skills/camsnap/SKILL.md +130 -0
- package/skills/code-generation/SKILL.md +139 -0
- package/skills/code-review/SKILL.md +116 -0
- package/skills/coding-agent/SKILL.md +184 -0
- package/skills/cron-manager/SKILL.md +141 -0
- package/skills/debug-helper/SKILL.md +134 -0
- package/skills/docker-ops/SKILL.md +246 -0
- package/skills/file-manager/SKILL.md +160 -0
- package/skills/git-workflow/SKILL.md +171 -0
- package/skills/github-issues/SKILL.md +244 -0
- package/skills/gog/SKILL.md +191 -0
- package/skills/goplaces/SKILL.md +156 -0
- package/skills/healthcheck/SKILL.md +270 -0
- package/skills/himalaya/SKILL.md +165 -0
- package/skills/mcp-discovery/SKILL.md +109 -0
- package/skills/memory-manager/SKILL.md +151 -0
- package/skills/model-usage/SKILL.md +177 -0
- package/skills/nano-banana-pro/SKILL.md +214 -0
- package/skills/nano-pdf/SKILL.md +165 -0
- package/skills/notion/SKILL.md +147 -0
- package/skills/obsidian/SKILL.md +163 -0
- package/skills/openai-image-gen/SKILL.md +194 -0
- package/skills/openai-whisper/SKILL.md +98 -0
- package/skills/openai-whisper-api/SKILL.md +130 -0
- package/skills/openhue/SKILL.md +203 -0
- package/skills/oracle/SKILL.md +236 -0
- package/skills/phone-control/SKILL.md +201 -0
- package/skills/profclaw-assistant/SKILL.md +81 -0
- package/skills/profclaw-projects/SKILL.md +68 -0
- package/skills/profclaw-tickets/SKILL.md +91 -0
- package/skills/session-logs/SKILL.md +216 -0
- package/skills/sherpa-onnx-tts/SKILL.md +134 -0
- package/skills/skill-creator/SKILL.md +183 -0
- package/skills/songsee/SKILL.md +170 -0
- package/skills/spotify-player/SKILL.md +254 -0
- package/skills/summarize/SKILL.md +155 -0
- package/skills/system-admin/SKILL.md +179 -0
- package/skills/things-mac/SKILL.md +179 -0
- package/skills/tmux/SKILL.md +170 -0
- package/skills/trello/SKILL.md +154 -0
- package/skills/video-frames/SKILL.md +127 -0
- package/skills/weather/SKILL.md +186 -0
- package/skills/web-research/SKILL.md +128 -0
- package/skills/xurl/SKILL.md +198 -0
- package/ui/README.md +73 -0
- package/ui/dist/assets/ActivityView-CkPgKhwm.js +1 -0
- package/ui/dist/assets/AgentList-C3SVdeEz.js +1 -0
- package/ui/dist/assets/AnalyticsDashboard-DVU--vlP.js +1 -0
- package/ui/dist/assets/AreaChart-B5qMEGWj.js +1 -0
- package/ui/dist/assets/CartesianChart-B50bcUJi.js +36 -0
- package/ui/dist/assets/ChatView-BZFJs7w_.js +10 -0
- package/ui/dist/assets/CostsDashboard-ChfH8BlG.js +1 -0
- package/ui/dist/assets/EstimateSelect-CLpB4NQO.js +1 -0
- package/ui/dist/assets/FloatingChatbot-CBzBY9a0.js +2 -0
- package/ui/dist/assets/InviteCodeManagement-DsbdPa6W.js +2 -0
- package/ui/dist/assets/ProjectDetail-BEM3tFXg.js +2 -0
- package/ui/dist/assets/ProjectDetail-D1-0B2xV.css +1 -0
- package/ui/dist/assets/ProjectIcon-CN1FmFAk.js +1 -0
- package/ui/dist/assets/ProjectList-DSDaNhxQ.js +4 -0
- package/ui/dist/assets/Settings-dPVcqC_d.js +6 -0
- package/ui/dist/assets/StatusIndicator-DA3NAo_b.js +1 -0
- package/ui/dist/assets/SummaryDetail-Bjnf9lT8.js +27 -0
- package/ui/dist/assets/SummaryList-ENwKDBKW.js +1 -0
- package/ui/dist/assets/TaskDetail-DnwG6MzB.js +1 -0
- package/ui/dist/assets/TaskList-D9Ug_HiV.js +1 -0
- package/ui/dist/assets/TicketBoard-CviH1571.js +1 -0
- package/ui/dist/assets/TicketDetail-8Jk8O-33.js +1 -0
- package/ui/dist/assets/TicketList-B-iV4rRA.js +1 -0
- package/ui/dist/assets/UserManagement-CPZ8s7s2.js +2 -0
- package/ui/dist/assets/ViewSwitcher-BUyOOWDI.js +1 -0
- package/ui/dist/assets/alert-dialog-BOmnTkN-.js +7 -0
- package/ui/dist/assets/archive-D5my9zOG.js +1 -0
- package/ui/dist/assets/badge-B4ov81_m.js +1 -0
- package/ui/dist/assets/brain-C53ElFLD.js +1 -0
- package/ui/dist/assets/bug-D0TJoCYn.js +1 -0
- package/ui/dist/assets/calendar-BkWIhKa1.js +1 -0
- package/ui/dist/assets/chevron-up-BRTtndUZ.js +1 -0
- package/ui/dist/assets/circle-x-BN89YhbS.js +1 -0
- package/ui/dist/assets/constants-fc7TFI7u.js +1 -0
- package/ui/dist/assets/cpu-B41x1-vV.js +1 -0
- package/ui/dist/assets/database-D9Cnfmzj.js +1 -0
- package/ui/dist/assets/defineProperty-CoXUr7_6.js +2 -0
- package/ui/dist/assets/dialog-DrWB4EYD.js +1 -0
- package/ui/dist/assets/element-adapter-DL0DQilL.js +4 -0
- package/ui/dist/assets/ellipsis-NuUWekvu.js +1 -0
- package/ui/dist/assets/ellipsis-vertical-BueMfuCw.js +1 -0
- package/ui/dist/assets/file-code-HR9829Jc.js +1 -0
- package/ui/dist/assets/folder-open-C_7oYEqe.js +1 -0
- package/ui/dist/assets/funnel-B5gmNXGf.js +1 -0
- package/ui/dist/assets/git-branch-BRMiJmV3.js +1 -0
- package/ui/dist/assets/git-commit-horizontal-CXOUQeJI.js +1 -0
- package/ui/dist/assets/globe-CCspjvus.js +1 -0
- package/ui/dist/assets/hash-g3102kPC.js +1 -0
- package/ui/dist/assets/history-CNqcBBrV.js +1 -0
- package/ui/dist/assets/index-27mTPKuh.js +1 -0
- package/ui/dist/assets/index-BMx3fH-e.js +1 -0
- package/ui/dist/assets/index-C9jNQXzL.js +1 -0
- package/ui/dist/assets/index-CEZ6R2Uw.js +7 -0
- package/ui/dist/assets/index-DURTouJI.js +1 -0
- package/ui/dist/assets/index-WclMPci1.css +1 -0
- package/ui/dist/assets/index-kb4AJXUB.js +67 -0
- package/ui/dist/assets/layers-CLXiHN6o.js +1 -0
- package/ui/dist/assets/layout-dashboard-B07MFhBs.js +1 -0
- package/ui/dist/assets/lightbulb-B2dgG5ks.js +1 -0
- package/ui/dist/assets/link-2-BxNmr9hL.js +1 -0
- package/ui/dist/assets/markdown-renderer-CjU6hBcW.js +36 -0
- package/ui/dist/assets/message-square-Bf2-CVbt.js +1 -0
- package/ui/dist/assets/pencil-D096Ey62.js +1 -0
- package/ui/dist/assets/play-DfdcSI3x.js +1 -0
- package/ui/dist/assets/popover-BV0QJDQq.js +1 -0
- package/ui/dist/assets/progress-DjPEDJfI.js +1 -0
- package/ui/dist/assets/rocket-CPsTc-5G.js +1 -0
- package/ui/dist/assets/select-LsO16slr.js +1 -0
- package/ui/dist/assets/send-BDYBZDUZ.js +1 -0
- package/ui/dist/assets/server-B3k6noce.js +1 -0
- package/ui/dist/assets/settings-2-Cu4xTjhG.js +1 -0
- package/ui/dist/assets/sheet-BScLnPKX.js +1 -0
- package/ui/dist/assets/shield-check-CQqkwufI.js +1 -0
- package/ui/dist/assets/shield-off-ZFruH9KO.js +1 -0
- package/ui/dist/assets/skeleton-CtkIA_MU.js +1 -0
- package/ui/dist/assets/smartphone-DnoksVkc.js +1 -0
- package/ui/dist/assets/square-uD9ZHMfW.js +1 -0
- package/ui/dist/assets/switch-MQPnwGJK.js +1 -0
- package/ui/dist/assets/table-B8cqlywA.js +1 -0
- package/ui/dist/assets/tag-wEJQRWlV.js +1 -0
- package/ui/dist/assets/textarea-D5LJCOGl.js +1 -0
- package/ui/dist/assets/timer-mbGHegmr.js +1 -0
- package/ui/dist/assets/tooltip-Df1CunSp.js +1 -0
- package/ui/dist/assets/trash-2-B4oAXsX-.js +1 -0
- package/ui/dist/assets/trending-up-BGWxvlmZ.js +1 -0
- package/ui/dist/assets/wand-sparkles-CZy4xvL3.js +1 -0
- package/ui/dist/assets/wrench-ByZkjO6m.js +1 -0
- package/ui/dist/audio-worklets/vad-processor.js +132 -0
- package/ui/dist/brand/profclaw-appicon-192.png +0 -0
- package/ui/dist/brand/profclaw-appicon-512.png +0 -0
- package/ui/dist/brand/profclaw-appicon-shadow.svg +51 -0
- package/ui/dist/brand/profclaw-appicon.svg +50 -0
- package/ui/dist/brand/profclaw-favicon-coral-bg-128.png +0 -0
- package/ui/dist/brand/profclaw-favicon-coral-bg-64.png +0 -0
- package/ui/dist/brand/profclaw-favicon-coral-bg.svg +52 -0
- package/ui/dist/brand/profclaw-favicon-coral-body-128.png +0 -0
- package/ui/dist/brand/profclaw-favicon-coral-body.svg +41 -0
- package/ui/dist/brand/profclaw-favicon-shadow-128.png +0 -0
- package/ui/dist/brand/profclaw-favicon-shadow-64.png +0 -0
- package/ui/dist/brand/profclaw-favicon-shadow.svg +51 -0
- package/ui/dist/brand/profclaw-mark-coral-256.png +0 -0
- package/ui/dist/brand/profclaw-mark-coral-64.png +0 -0
- package/ui/dist/brand/profclaw-mark-coral.svg +31 -0
- package/ui/dist/brand/profclaw-mono-white.svg +18 -0
- package/ui/dist/brand/profclaw-text-dark.svg +18 -0
- package/ui/dist/brand/profclaw-text-light.svg +18 -0
- package/ui/dist/brand/profclaw-wordmark-dark.svg +48 -0
- package/ui/dist/brand/profclaw-wordmark-light.svg +48 -0
- package/ui/dist/brand/safari-pinned-tab.svg +15 -0
- package/ui/dist/favicon/android-chrome-192x192.png +0 -0
- package/ui/dist/favicon/android-chrome-384x384.png +0 -0
- package/ui/dist/favicon/android-chrome-512x512.png +0 -0
- package/ui/dist/favicon/apple-touch-icon.png +0 -0
- package/ui/dist/favicon/browserconfig.xml +9 -0
- package/ui/dist/favicon/favicon-16x16.png +0 -0
- package/ui/dist/favicon/favicon-32x32.png +0 -0
- package/ui/dist/favicon/favicon.ico +0 -0
- package/ui/dist/favicon/mstile-150x150.png +0 -0
- package/ui/dist/favicon/safari-pinned-tab.svg +46 -0
- package/ui/dist/favicon/site.webmanifest +22 -0
- package/ui/dist/index.html +32 -0
- package/ui/dist/manifest.json +49 -0
- package/ui/dist/sw.js +124 -0
- package/ui/dist/vite.svg +1 -0
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Routes
|
|
3
|
+
*
|
|
4
|
+
* Handles user profile and preferences management.
|
|
5
|
+
*/
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import { getCookie } from 'hono/cookie';
|
|
8
|
+
import { eq } from 'drizzle-orm';
|
|
9
|
+
import { randomUUID, randomBytes, createHash } from 'crypto';
|
|
10
|
+
import { getDb } from '../storage/index.js';
|
|
11
|
+
import { users, userPreferences, userApiKeys, sessions, oauthAccounts, inviteCodes } from '../storage/schema.js';
|
|
12
|
+
import { validateSession, getUserById, getUserConnectedAccounts } from '../auth/auth-service.js';
|
|
13
|
+
import { hashPassword, verifyPassword, validatePasswordStrength, generateRecoveryCodes, hashRecoveryCodes, generateInviteCode, hashInviteCode, } from '../auth/password.js';
|
|
14
|
+
import { getSettingsRaw, updateSettings } from '../settings/index.js';
|
|
15
|
+
export const userRoutes = new Hono();
|
|
16
|
+
// Middleware to require authentication
|
|
17
|
+
async function requireAuth(c, next) {
|
|
18
|
+
const token = getCookie(c, 'profclaw_session');
|
|
19
|
+
if (!token) {
|
|
20
|
+
return c.json({ error: 'Not authenticated' }, 401);
|
|
21
|
+
}
|
|
22
|
+
const user = await validateSession(token);
|
|
23
|
+
if (!user) {
|
|
24
|
+
return c.json({ error: 'Invalid session' }, 401);
|
|
25
|
+
}
|
|
26
|
+
c.set('user', user);
|
|
27
|
+
await next();
|
|
28
|
+
}
|
|
29
|
+
// Apply auth middleware to all routes
|
|
30
|
+
userRoutes.use('/*', requireAuth);
|
|
31
|
+
// PROFILE
|
|
32
|
+
/**
|
|
33
|
+
* GET /api/users/me/profile
|
|
34
|
+
* Get full user profile with preferences
|
|
35
|
+
*/
|
|
36
|
+
userRoutes.get('/me/profile', async (c) => {
|
|
37
|
+
const user = c.get('user');
|
|
38
|
+
const db = getDb();
|
|
39
|
+
if (!db)
|
|
40
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
41
|
+
// Get preferences
|
|
42
|
+
const prefs = await db
|
|
43
|
+
.select()
|
|
44
|
+
.from(userPreferences)
|
|
45
|
+
.where(eq(userPreferences.userId, user.id))
|
|
46
|
+
.limit(1);
|
|
47
|
+
// Get connected accounts
|
|
48
|
+
const connectedAccounts = await getUserConnectedAccounts(user.id);
|
|
49
|
+
// Get active sessions count
|
|
50
|
+
const sessionsResult = await db
|
|
51
|
+
.select()
|
|
52
|
+
.from(sessions)
|
|
53
|
+
.where(eq(sessions.userId, user.id));
|
|
54
|
+
return c.json({
|
|
55
|
+
user,
|
|
56
|
+
preferences: prefs[0] || null,
|
|
57
|
+
connectedAccounts,
|
|
58
|
+
activeSessions: sessionsResult.length,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* PATCH /api/users/me/profile
|
|
63
|
+
* Update user profile
|
|
64
|
+
*/
|
|
65
|
+
userRoutes.patch('/me/profile', async (c) => {
|
|
66
|
+
const user = c.get('user');
|
|
67
|
+
const db = getDb();
|
|
68
|
+
if (!db)
|
|
69
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
70
|
+
const body = await c.req.json();
|
|
71
|
+
const { name, bio, timezone, locale } = body;
|
|
72
|
+
// Validate inputs
|
|
73
|
+
if (name !== undefined && (typeof name !== 'string' || name.length < 1)) {
|
|
74
|
+
return c.json({ error: 'Name must be at least 1 character' }, 400);
|
|
75
|
+
}
|
|
76
|
+
await db
|
|
77
|
+
.update(users)
|
|
78
|
+
.set({
|
|
79
|
+
...(name !== undefined && { name }),
|
|
80
|
+
...(bio !== undefined && { bio }),
|
|
81
|
+
...(timezone !== undefined && { timezone }),
|
|
82
|
+
...(locale !== undefined && { locale }),
|
|
83
|
+
updatedAt: new Date(),
|
|
84
|
+
})
|
|
85
|
+
.where(eq(users.id, user.id));
|
|
86
|
+
const updated = await getUserById(user.id);
|
|
87
|
+
return c.json({ user: updated });
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* PUT /api/users/me/email
|
|
91
|
+
* Change user email address
|
|
92
|
+
* Requires password verification for security
|
|
93
|
+
*/
|
|
94
|
+
userRoutes.put('/me/email', async (c) => {
|
|
95
|
+
const user = c.get('user');
|
|
96
|
+
const db = getDb();
|
|
97
|
+
if (!db)
|
|
98
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
99
|
+
const body = await c.req.json();
|
|
100
|
+
const { newEmail, currentPassword } = body;
|
|
101
|
+
// Validate inputs
|
|
102
|
+
if (!newEmail || typeof newEmail !== 'string') {
|
|
103
|
+
return c.json({ error: 'New email is required' }, 400);
|
|
104
|
+
}
|
|
105
|
+
// Basic email validation
|
|
106
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
107
|
+
if (!emailRegex.test(newEmail)) {
|
|
108
|
+
return c.json({ error: 'Invalid email format' }, 400);
|
|
109
|
+
}
|
|
110
|
+
// Get current user with password hash
|
|
111
|
+
const currentUser = await db
|
|
112
|
+
.select()
|
|
113
|
+
.from(users)
|
|
114
|
+
.where(eq(users.id, user.id))
|
|
115
|
+
.limit(1);
|
|
116
|
+
if (!currentUser.length) {
|
|
117
|
+
return c.json({ error: 'User not found' }, 404);
|
|
118
|
+
}
|
|
119
|
+
// If user has a password, verify it
|
|
120
|
+
if (currentUser[0].passwordHash) {
|
|
121
|
+
if (!currentPassword) {
|
|
122
|
+
return c.json({ error: 'Current password is required' }, 400);
|
|
123
|
+
}
|
|
124
|
+
const { valid } = verifyPassword(currentPassword, currentUser[0].passwordHash);
|
|
125
|
+
if (!valid) {
|
|
126
|
+
return c.json({ error: 'Current password is incorrect' }, 401);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Check if email already exists
|
|
130
|
+
const existingEmail = await db
|
|
131
|
+
.select()
|
|
132
|
+
.from(users)
|
|
133
|
+
.where(eq(users.email, newEmail.toLowerCase()))
|
|
134
|
+
.limit(1);
|
|
135
|
+
if (existingEmail.length > 0 && existingEmail[0].id !== user.id) {
|
|
136
|
+
return c.json({ error: 'Email already in use' }, 409);
|
|
137
|
+
}
|
|
138
|
+
// Update email
|
|
139
|
+
await db
|
|
140
|
+
.update(users)
|
|
141
|
+
.set({
|
|
142
|
+
email: newEmail.toLowerCase(),
|
|
143
|
+
updatedAt: new Date(),
|
|
144
|
+
})
|
|
145
|
+
.where(eq(users.id, user.id));
|
|
146
|
+
const updated = await getUserById(user.id);
|
|
147
|
+
return c.json({ user: updated, message: 'Email updated successfully' });
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* PUT /api/users/me/password
|
|
151
|
+
* Change user password
|
|
152
|
+
* OAuth-only users can set a password to enable email+password login
|
|
153
|
+
*/
|
|
154
|
+
userRoutes.put('/me/password', async (c) => {
|
|
155
|
+
const user = c.get('user');
|
|
156
|
+
const db = getDb();
|
|
157
|
+
if (!db)
|
|
158
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
159
|
+
const body = await c.req.json();
|
|
160
|
+
const { currentPassword, newPassword } = body;
|
|
161
|
+
// Validate new password strength
|
|
162
|
+
if (!newPassword || typeof newPassword !== 'string') {
|
|
163
|
+
return c.json({ error: 'New password is required' }, 400);
|
|
164
|
+
}
|
|
165
|
+
const passwordError = validatePasswordStrength(newPassword);
|
|
166
|
+
if (passwordError) {
|
|
167
|
+
return c.json({ error: passwordError }, 400);
|
|
168
|
+
}
|
|
169
|
+
// Get current user with password hash
|
|
170
|
+
const currentUser = await db
|
|
171
|
+
.select()
|
|
172
|
+
.from(users)
|
|
173
|
+
.where(eq(users.id, user.id))
|
|
174
|
+
.limit(1);
|
|
175
|
+
if (!currentUser.length) {
|
|
176
|
+
return c.json({ error: 'User not found' }, 404);
|
|
177
|
+
}
|
|
178
|
+
// If user already has a password, verify current password
|
|
179
|
+
if (currentUser[0].passwordHash) {
|
|
180
|
+
if (!currentPassword) {
|
|
181
|
+
return c.json({ error: 'Current password is required' }, 400);
|
|
182
|
+
}
|
|
183
|
+
const { valid } = verifyPassword(currentPassword, currentUser[0].passwordHash);
|
|
184
|
+
if (!valid) {
|
|
185
|
+
return c.json({ error: 'Current password is incorrect' }, 401);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Note: OAuth-only users (no passwordHash) can set a password without verification
|
|
189
|
+
// Hash new password with scrypt
|
|
190
|
+
const newPasswordHash = hashPassword(newPassword);
|
|
191
|
+
// Update password
|
|
192
|
+
await db
|
|
193
|
+
.update(users)
|
|
194
|
+
.set({
|
|
195
|
+
passwordHash: newPasswordHash,
|
|
196
|
+
updatedAt: new Date(),
|
|
197
|
+
})
|
|
198
|
+
.where(eq(users.id, user.id));
|
|
199
|
+
return c.json({ message: 'Password updated successfully' });
|
|
200
|
+
});
|
|
201
|
+
// CONNECTED ACCOUNTS (OAuth)
|
|
202
|
+
/**
|
|
203
|
+
* GET /api/users/me/connected-accounts
|
|
204
|
+
* List connected OAuth accounts
|
|
205
|
+
*/
|
|
206
|
+
userRoutes.get('/me/connected-accounts', async (c) => {
|
|
207
|
+
const user = c.get('user');
|
|
208
|
+
const accounts = await getUserConnectedAccounts(user.id);
|
|
209
|
+
// Get user to check if they have a password set
|
|
210
|
+
const db = getDb();
|
|
211
|
+
if (!db)
|
|
212
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
213
|
+
const currentUser = await db
|
|
214
|
+
.select({ passwordHash: users.passwordHash })
|
|
215
|
+
.from(users)
|
|
216
|
+
.where(eq(users.id, user.id))
|
|
217
|
+
.limit(1);
|
|
218
|
+
return c.json({
|
|
219
|
+
accounts,
|
|
220
|
+
hasPassword: Boolean(currentUser[0]?.passwordHash),
|
|
221
|
+
canDisconnect: Boolean(currentUser[0]?.passwordHash) || accounts.length > 1,
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
/**
|
|
225
|
+
* DELETE /api/users/me/connected-accounts/:provider
|
|
226
|
+
* Disconnect an OAuth account
|
|
227
|
+
* Requires either a password to be set, or another OAuth account to exist
|
|
228
|
+
*/
|
|
229
|
+
userRoutes.delete('/me/connected-accounts/:provider', async (c) => {
|
|
230
|
+
const user = c.get('user');
|
|
231
|
+
const provider = c.req.param('provider').toLowerCase();
|
|
232
|
+
const db = getDb();
|
|
233
|
+
if (!db)
|
|
234
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
235
|
+
// Get all connected accounts
|
|
236
|
+
const accounts = await db
|
|
237
|
+
.select()
|
|
238
|
+
.from(oauthAccounts)
|
|
239
|
+
.where(eq(oauthAccounts.userId, user.id));
|
|
240
|
+
const accountToRemove = accounts.find((a) => a.provider === provider);
|
|
241
|
+
if (!accountToRemove) {
|
|
242
|
+
return c.json({ error: 'Account not found' }, 404);
|
|
243
|
+
}
|
|
244
|
+
// Check if user has password or other OAuth accounts
|
|
245
|
+
const currentUser = await db
|
|
246
|
+
.select({ passwordHash: users.passwordHash })
|
|
247
|
+
.from(users)
|
|
248
|
+
.where(eq(users.id, user.id))
|
|
249
|
+
.limit(1);
|
|
250
|
+
const hasPassword = Boolean(currentUser[0]?.passwordHash);
|
|
251
|
+
const otherAccounts = accounts.filter((a) => a.provider !== provider);
|
|
252
|
+
if (!hasPassword && otherAccounts.length === 0) {
|
|
253
|
+
return c.json({
|
|
254
|
+
error: 'Cannot disconnect last authentication method. Please set a password first.',
|
|
255
|
+
requiresPassword: true,
|
|
256
|
+
}, 400);
|
|
257
|
+
}
|
|
258
|
+
// Delete the OAuth account
|
|
259
|
+
await db.delete(oauthAccounts).where(eq(oauthAccounts.id, accountToRemove.id));
|
|
260
|
+
return c.json({
|
|
261
|
+
message: `${provider} account disconnected successfully`,
|
|
262
|
+
remainingAccounts: otherAccounts.length,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
/**
|
|
266
|
+
* PUT /api/users/me/primary-email
|
|
267
|
+
* Set which email is the primary (login) email
|
|
268
|
+
* Can choose from connected OAuth account emails or manually set email
|
|
269
|
+
*/
|
|
270
|
+
userRoutes.put('/me/primary-email', async (c) => {
|
|
271
|
+
const user = c.get('user');
|
|
272
|
+
const db = getDb();
|
|
273
|
+
if (!db)
|
|
274
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
275
|
+
const body = await c.req.json();
|
|
276
|
+
const { email, source } = body; // source: 'manual' | 'github' | 'google' etc
|
|
277
|
+
if (!email || typeof email !== 'string') {
|
|
278
|
+
return c.json({ error: 'Email is required' }, 400);
|
|
279
|
+
}
|
|
280
|
+
// Basic email validation
|
|
281
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
282
|
+
if (!emailRegex.test(email)) {
|
|
283
|
+
return c.json({ error: 'Invalid email format' }, 400);
|
|
284
|
+
}
|
|
285
|
+
// If source is an OAuth provider, verify the email belongs to that account
|
|
286
|
+
if (source && source !== 'manual') {
|
|
287
|
+
const accounts = await db
|
|
288
|
+
.select()
|
|
289
|
+
.from(oauthAccounts)
|
|
290
|
+
.where(eq(oauthAccounts.userId, user.id));
|
|
291
|
+
const providerAccount = accounts.find((a) => a.provider === source);
|
|
292
|
+
if (!providerAccount) {
|
|
293
|
+
return c.json({ error: `No ${source} account connected` }, 400);
|
|
294
|
+
}
|
|
295
|
+
// Verify email matches provider data (if available)
|
|
296
|
+
const providerData = providerAccount.providerData;
|
|
297
|
+
const providerEmail = typeof providerData?.email === 'string' ? providerData.email : undefined;
|
|
298
|
+
if (providerEmail && providerEmail.toLowerCase() !== email.toLowerCase()) {
|
|
299
|
+
return c.json({ error: 'Email does not match connected account' }, 400);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Check if email already in use by another user
|
|
303
|
+
const existingUser = await db
|
|
304
|
+
.select()
|
|
305
|
+
.from(users)
|
|
306
|
+
.where(eq(users.email, email.toLowerCase()))
|
|
307
|
+
.limit(1);
|
|
308
|
+
if (existingUser.length > 0 && existingUser[0].id !== user.id) {
|
|
309
|
+
return c.json({ error: 'Email already in use by another account' }, 409);
|
|
310
|
+
}
|
|
311
|
+
// Update primary email
|
|
312
|
+
await db
|
|
313
|
+
.update(users)
|
|
314
|
+
.set({
|
|
315
|
+
email: email.toLowerCase(),
|
|
316
|
+
updatedAt: new Date(),
|
|
317
|
+
})
|
|
318
|
+
.where(eq(users.id, user.id));
|
|
319
|
+
const updated = await getUserById(user.id);
|
|
320
|
+
return c.json({
|
|
321
|
+
user: updated,
|
|
322
|
+
message: 'Primary email updated successfully',
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
// PREFERENCES
|
|
326
|
+
/**
|
|
327
|
+
* GET /api/users/me/preferences
|
|
328
|
+
* Get user preferences
|
|
329
|
+
*/
|
|
330
|
+
userRoutes.get('/me/preferences', async (c) => {
|
|
331
|
+
const user = c.get('user');
|
|
332
|
+
const db = getDb();
|
|
333
|
+
if (!db)
|
|
334
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
335
|
+
const prefs = await db
|
|
336
|
+
.select()
|
|
337
|
+
.from(userPreferences)
|
|
338
|
+
.where(eq(userPreferences.userId, user.id))
|
|
339
|
+
.limit(1);
|
|
340
|
+
if (!prefs.length) {
|
|
341
|
+
// Create default preferences
|
|
342
|
+
const id = randomUUID();
|
|
343
|
+
const now = new Date();
|
|
344
|
+
await db.insert(userPreferences).values({
|
|
345
|
+
id,
|
|
346
|
+
userId: user.id,
|
|
347
|
+
updatedAt: now,
|
|
348
|
+
});
|
|
349
|
+
const created = await db
|
|
350
|
+
.select()
|
|
351
|
+
.from(userPreferences)
|
|
352
|
+
.where(eq(userPreferences.id, id))
|
|
353
|
+
.limit(1);
|
|
354
|
+
return c.json({ preferences: created[0] });
|
|
355
|
+
}
|
|
356
|
+
return c.json({ preferences: prefs[0] });
|
|
357
|
+
});
|
|
358
|
+
/**
|
|
359
|
+
* PATCH /api/users/me/preferences
|
|
360
|
+
* Update user preferences
|
|
361
|
+
*/
|
|
362
|
+
userRoutes.patch('/me/preferences', async (c) => {
|
|
363
|
+
const user = c.get('user');
|
|
364
|
+
const db = getDb();
|
|
365
|
+
if (!db)
|
|
366
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
367
|
+
const body = await c.req.json();
|
|
368
|
+
const allowedFields = [
|
|
369
|
+
'theme',
|
|
370
|
+
'accentColor',
|
|
371
|
+
'fontSize',
|
|
372
|
+
'emailNotifications',
|
|
373
|
+
'pushNotifications',
|
|
374
|
+
'notifyOnMention',
|
|
375
|
+
'notifyOnAssign',
|
|
376
|
+
'notifyOnComment',
|
|
377
|
+
'defaultAgent',
|
|
378
|
+
'aiAutoSuggest',
|
|
379
|
+
'aiResponseLength',
|
|
380
|
+
'editorMode',
|
|
381
|
+
'tabSize',
|
|
382
|
+
'extraSettings',
|
|
383
|
+
];
|
|
384
|
+
// Filter to allowed fields only
|
|
385
|
+
const updates = {};
|
|
386
|
+
for (const field of allowedFields) {
|
|
387
|
+
if (body[field] !== undefined) {
|
|
388
|
+
updates[field] = body[field];
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
updates.updatedAt = new Date();
|
|
392
|
+
// Ensure preferences exist
|
|
393
|
+
const existing = await db
|
|
394
|
+
.select()
|
|
395
|
+
.from(userPreferences)
|
|
396
|
+
.where(eq(userPreferences.userId, user.id))
|
|
397
|
+
.limit(1);
|
|
398
|
+
if (!existing.length) {
|
|
399
|
+
await db.insert(userPreferences).values({
|
|
400
|
+
id: randomUUID(),
|
|
401
|
+
userId: user.id,
|
|
402
|
+
...updates,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
await db
|
|
407
|
+
.update(userPreferences)
|
|
408
|
+
.set(updates)
|
|
409
|
+
.where(eq(userPreferences.userId, user.id));
|
|
410
|
+
}
|
|
411
|
+
const updated = await db
|
|
412
|
+
.select()
|
|
413
|
+
.from(userPreferences)
|
|
414
|
+
.where(eq(userPreferences.userId, user.id))
|
|
415
|
+
.limit(1);
|
|
416
|
+
return c.json({ preferences: updated[0] });
|
|
417
|
+
});
|
|
418
|
+
// API KEYS
|
|
419
|
+
/**
|
|
420
|
+
* GET /api/users/me/api-keys
|
|
421
|
+
* List user's API keys (without the actual key values)
|
|
422
|
+
*/
|
|
423
|
+
userRoutes.get('/me/api-keys', async (c) => {
|
|
424
|
+
const user = c.get('user');
|
|
425
|
+
const db = getDb();
|
|
426
|
+
if (!db)
|
|
427
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
428
|
+
const keys = await db
|
|
429
|
+
.select({
|
|
430
|
+
id: userApiKeys.id,
|
|
431
|
+
name: userApiKeys.name,
|
|
432
|
+
keyPrefix: userApiKeys.keyPrefix,
|
|
433
|
+
scopes: userApiKeys.scopes,
|
|
434
|
+
lastUsedAt: userApiKeys.lastUsedAt,
|
|
435
|
+
usageCount: userApiKeys.usageCount,
|
|
436
|
+
expiresAt: userApiKeys.expiresAt,
|
|
437
|
+
createdAt: userApiKeys.createdAt,
|
|
438
|
+
revokedAt: userApiKeys.revokedAt,
|
|
439
|
+
})
|
|
440
|
+
.from(userApiKeys)
|
|
441
|
+
.where(eq(userApiKeys.userId, user.id));
|
|
442
|
+
return c.json({ apiKeys: keys });
|
|
443
|
+
});
|
|
444
|
+
/**
|
|
445
|
+
* POST /api/users/me/api-keys
|
|
446
|
+
* Create a new API key
|
|
447
|
+
*/
|
|
448
|
+
userRoutes.post('/me/api-keys', async (c) => {
|
|
449
|
+
const user = c.get('user');
|
|
450
|
+
const db = getDb();
|
|
451
|
+
if (!db)
|
|
452
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
453
|
+
const body = await c.req.json();
|
|
454
|
+
const { name, scopes = ['read'], expiresInDays } = body;
|
|
455
|
+
if (!name || typeof name !== 'string') {
|
|
456
|
+
return c.json({ error: 'Name is required' }, 400);
|
|
457
|
+
}
|
|
458
|
+
// Generate API key
|
|
459
|
+
const rawKey = `profclaw_${randomBytes(24).toString('hex')}`;
|
|
460
|
+
const keyPrefix = rawKey.slice(0, 14) + '...';
|
|
461
|
+
const keyHash = createHash('sha256').update(rawKey).digest('hex');
|
|
462
|
+
const id = randomUUID();
|
|
463
|
+
const now = new Date();
|
|
464
|
+
const expiresAt = expiresInDays
|
|
465
|
+
? new Date(now.getTime() + expiresInDays * 24 * 60 * 60 * 1000)
|
|
466
|
+
: null;
|
|
467
|
+
await db.insert(userApiKeys).values({
|
|
468
|
+
id,
|
|
469
|
+
userId: user.id,
|
|
470
|
+
name,
|
|
471
|
+
keyHash,
|
|
472
|
+
keyPrefix,
|
|
473
|
+
scopes,
|
|
474
|
+
expiresAt,
|
|
475
|
+
createdAt: now,
|
|
476
|
+
});
|
|
477
|
+
// Return the full key ONLY on creation (it won't be retrievable later)
|
|
478
|
+
return c.json({
|
|
479
|
+
apiKey: {
|
|
480
|
+
id,
|
|
481
|
+
name,
|
|
482
|
+
key: rawKey, // Only returned once!
|
|
483
|
+
keyPrefix,
|
|
484
|
+
scopes,
|
|
485
|
+
expiresAt,
|
|
486
|
+
createdAt: now,
|
|
487
|
+
},
|
|
488
|
+
message: 'Save this key securely - it will not be shown again',
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
/**
|
|
492
|
+
* DELETE /api/users/me/api-keys/:keyId
|
|
493
|
+
* Revoke an API key
|
|
494
|
+
*/
|
|
495
|
+
userRoutes.delete('/me/api-keys/:keyId', async (c) => {
|
|
496
|
+
const user = c.get('user');
|
|
497
|
+
const keyId = c.req.param('keyId');
|
|
498
|
+
const db = getDb();
|
|
499
|
+
if (!db)
|
|
500
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
501
|
+
// Verify ownership
|
|
502
|
+
const key = await db
|
|
503
|
+
.select()
|
|
504
|
+
.from(userApiKeys)
|
|
505
|
+
.where(eq(userApiKeys.id, keyId))
|
|
506
|
+
.limit(1);
|
|
507
|
+
if (!key.length || key[0].userId !== user.id) {
|
|
508
|
+
return c.json({ error: 'API key not found' }, 404);
|
|
509
|
+
}
|
|
510
|
+
// Mark as revoked
|
|
511
|
+
await db
|
|
512
|
+
.update(userApiKeys)
|
|
513
|
+
.set({ revokedAt: new Date() })
|
|
514
|
+
.where(eq(userApiKeys.id, keyId));
|
|
515
|
+
return c.json({ message: 'API key revoked' });
|
|
516
|
+
});
|
|
517
|
+
// SESSIONS
|
|
518
|
+
/**
|
|
519
|
+
* GET /api/users/me/sessions
|
|
520
|
+
* List active sessions
|
|
521
|
+
*/
|
|
522
|
+
userRoutes.get('/me/sessions', async (c) => {
|
|
523
|
+
const user = c.get('user');
|
|
524
|
+
const db = getDb();
|
|
525
|
+
if (!db)
|
|
526
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
527
|
+
const currentToken = getCookie(c, 'profclaw_session');
|
|
528
|
+
const userSessions = await db
|
|
529
|
+
.select({
|
|
530
|
+
id: sessions.id,
|
|
531
|
+
userAgent: sessions.userAgent,
|
|
532
|
+
ipAddress: sessions.ipAddress,
|
|
533
|
+
deviceName: sessions.deviceName,
|
|
534
|
+
createdAt: sessions.createdAt,
|
|
535
|
+
lastActiveAt: sessions.lastActiveAt,
|
|
536
|
+
expiresAt: sessions.expiresAt,
|
|
537
|
+
token: sessions.token,
|
|
538
|
+
})
|
|
539
|
+
.from(sessions)
|
|
540
|
+
.where(eq(sessions.userId, user.id));
|
|
541
|
+
const sessionsWithCurrent = userSessions.map((s) => ({
|
|
542
|
+
id: s.id,
|
|
543
|
+
userAgent: s.userAgent,
|
|
544
|
+
ipAddress: s.ipAddress,
|
|
545
|
+
deviceName: s.deviceName,
|
|
546
|
+
createdAt: s.createdAt,
|
|
547
|
+
lastActiveAt: s.lastActiveAt,
|
|
548
|
+
expiresAt: s.expiresAt,
|
|
549
|
+
isCurrent: s.token === currentToken,
|
|
550
|
+
}));
|
|
551
|
+
return c.json({ sessions: sessionsWithCurrent });
|
|
552
|
+
});
|
|
553
|
+
/**
|
|
554
|
+
* DELETE /api/users/me/sessions/:sessionId
|
|
555
|
+
* Revoke a specific session
|
|
556
|
+
*/
|
|
557
|
+
userRoutes.delete('/me/sessions/:sessionId', async (c) => {
|
|
558
|
+
const user = c.get('user');
|
|
559
|
+
const sessionId = c.req.param('sessionId');
|
|
560
|
+
const db = getDb();
|
|
561
|
+
if (!db)
|
|
562
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
563
|
+
// Verify ownership
|
|
564
|
+
const session = await db
|
|
565
|
+
.select()
|
|
566
|
+
.from(sessions)
|
|
567
|
+
.where(eq(sessions.id, sessionId))
|
|
568
|
+
.limit(1);
|
|
569
|
+
if (!session.length || session[0].userId !== user.id) {
|
|
570
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
571
|
+
}
|
|
572
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
573
|
+
return c.json({ message: 'Session revoked' });
|
|
574
|
+
});
|
|
575
|
+
/**
|
|
576
|
+
* DELETE /api/users/me/sessions
|
|
577
|
+
* Revoke all sessions except current
|
|
578
|
+
*/
|
|
579
|
+
userRoutes.delete('/me/sessions', async (c) => {
|
|
580
|
+
const user = c.get('user');
|
|
581
|
+
const currentToken = getCookie(c, 'profclaw_session');
|
|
582
|
+
const db = getDb();
|
|
583
|
+
if (!db)
|
|
584
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
585
|
+
// Get all sessions except current
|
|
586
|
+
const userSessions = await db
|
|
587
|
+
.select()
|
|
588
|
+
.from(sessions)
|
|
589
|
+
.where(eq(sessions.userId, user.id));
|
|
590
|
+
let revokedCount = 0;
|
|
591
|
+
for (const session of userSessions) {
|
|
592
|
+
if (session.token !== currentToken) {
|
|
593
|
+
await db.delete(sessions).where(eq(sessions.id, session.id));
|
|
594
|
+
revokedCount++;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return c.json({ message: `Revoked ${revokedCount} sessions` });
|
|
598
|
+
});
|
|
599
|
+
// ONBOARDING
|
|
600
|
+
/**
|
|
601
|
+
* POST /api/users/me/complete-onboarding
|
|
602
|
+
* Mark onboarding as complete
|
|
603
|
+
*/
|
|
604
|
+
userRoutes.post('/me/complete-onboarding', async (c) => {
|
|
605
|
+
const user = c.get('user');
|
|
606
|
+
const db = getDb();
|
|
607
|
+
if (!db)
|
|
608
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
609
|
+
await db
|
|
610
|
+
.update(users)
|
|
611
|
+
.set({ onboardingCompleted: true, updatedAt: new Date() })
|
|
612
|
+
.where(eq(users.id, user.id));
|
|
613
|
+
return c.json({ message: 'Onboarding completed' });
|
|
614
|
+
});
|
|
615
|
+
// RECOVERY CODES
|
|
616
|
+
/**
|
|
617
|
+
* POST /api/users/me/recovery-codes/regenerate
|
|
618
|
+
* Regenerate recovery codes (invalidates all existing codes)
|
|
619
|
+
* Returns new codes ONLY ONCE - they cannot be retrieved again
|
|
620
|
+
*/
|
|
621
|
+
userRoutes.post('/me/recovery-codes/regenerate', async (c) => {
|
|
622
|
+
const user = c.get('user');
|
|
623
|
+
const db = getDb();
|
|
624
|
+
if (!db)
|
|
625
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
626
|
+
try {
|
|
627
|
+
// Generate new recovery codes
|
|
628
|
+
const recoveryCodes = generateRecoveryCodes(8);
|
|
629
|
+
const hashedRecoveryCodes = hashRecoveryCodes(recoveryCodes);
|
|
630
|
+
// Update user with new hashed codes (invalidates old ones)
|
|
631
|
+
await db
|
|
632
|
+
.update(users)
|
|
633
|
+
.set({
|
|
634
|
+
recoveryCodes: JSON.stringify(hashedRecoveryCodes),
|
|
635
|
+
updatedAt: new Date(),
|
|
636
|
+
})
|
|
637
|
+
.where(eq(users.id, user.id));
|
|
638
|
+
return c.json({
|
|
639
|
+
success: true,
|
|
640
|
+
recoveryCodes,
|
|
641
|
+
message: 'New recovery codes generated. Save these codes securely - they will not be shown again!',
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
console.error('[Users] Recovery code regeneration error:', error);
|
|
646
|
+
return c.json({ error: 'Failed to regenerate recovery codes' }, 500);
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
/**
|
|
650
|
+
* GET /api/users/me/recovery-codes/count
|
|
651
|
+
* Get the count of remaining recovery codes (without exposing the codes)
|
|
652
|
+
*/
|
|
653
|
+
userRoutes.get('/me/recovery-codes/count', async (c) => {
|
|
654
|
+
const user = c.get('user');
|
|
655
|
+
const db = getDb();
|
|
656
|
+
if (!db)
|
|
657
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
658
|
+
try {
|
|
659
|
+
const result = await db
|
|
660
|
+
.select({ recoveryCodes: users.recoveryCodes })
|
|
661
|
+
.from(users)
|
|
662
|
+
.where(eq(users.id, user.id))
|
|
663
|
+
.limit(1);
|
|
664
|
+
if (!result.length) {
|
|
665
|
+
return c.json({ error: 'User not found' }, 404);
|
|
666
|
+
}
|
|
667
|
+
const storedCodes = result[0].recoveryCodes;
|
|
668
|
+
const count = storedCodes ? JSON.parse(storedCodes).length : 0;
|
|
669
|
+
return c.json({
|
|
670
|
+
remainingCodes: count,
|
|
671
|
+
hasRecoveryCodes: count > 0,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
console.error('[Users] Recovery code count error:', error);
|
|
676
|
+
return c.json({ error: 'Failed to get recovery code count' }, 500);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
// ADMIN: USER MANAGEMENT
|
|
680
|
+
/**
|
|
681
|
+
* Middleware to require admin role
|
|
682
|
+
*/
|
|
683
|
+
async function requireAdmin(c, next) {
|
|
684
|
+
const user = c.get('user');
|
|
685
|
+
if (user.role !== 'admin') {
|
|
686
|
+
return c.json({ error: 'Admin access required' }, 403);
|
|
687
|
+
}
|
|
688
|
+
await next();
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* GET /api/users/admin/list
|
|
692
|
+
* List all users (admin only)
|
|
693
|
+
*/
|
|
694
|
+
userRoutes.get('/admin/list', requireAdmin, async (c) => {
|
|
695
|
+
const db = getDb();
|
|
696
|
+
if (!db)
|
|
697
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
698
|
+
try {
|
|
699
|
+
const allUsers = await db
|
|
700
|
+
.select({
|
|
701
|
+
id: users.id,
|
|
702
|
+
email: users.email,
|
|
703
|
+
name: users.name,
|
|
704
|
+
role: users.role,
|
|
705
|
+
status: users.status,
|
|
706
|
+
avatarUrl: users.avatarUrl,
|
|
707
|
+
createdAt: users.createdAt,
|
|
708
|
+
updatedAt: users.updatedAt,
|
|
709
|
+
onboardingCompleted: users.onboardingCompleted,
|
|
710
|
+
})
|
|
711
|
+
.from(users)
|
|
712
|
+
.orderBy(users.createdAt);
|
|
713
|
+
return c.json({ users: allUsers, total: allUsers.length });
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
console.error('[Admin] List users error:', error);
|
|
717
|
+
return c.json({ error: 'Failed to list users' }, 500);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
// ADMIN: INVITE CODES (must be before /:userId to avoid route shadowing)
|
|
721
|
+
/**
|
|
722
|
+
* POST /api/users/admin/invites
|
|
723
|
+
* Generate invite code(s) (admin only)
|
|
724
|
+
*/
|
|
725
|
+
userRoutes.post('/admin/invites', requireAdmin, async (c) => {
|
|
726
|
+
const user = c.get('user');
|
|
727
|
+
const db = getDb();
|
|
728
|
+
if (!db)
|
|
729
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
730
|
+
try {
|
|
731
|
+
const body = await c.req.json();
|
|
732
|
+
const count = Math.min(Math.max(body.count || 1, 1), 50);
|
|
733
|
+
const label = body.label || null;
|
|
734
|
+
let expiresAt = null;
|
|
735
|
+
if (body.expiresInDays) {
|
|
736
|
+
expiresAt = new Date(Date.now() + body.expiresInDays * 24 * 60 * 60 * 1000);
|
|
737
|
+
}
|
|
738
|
+
const codes = [];
|
|
739
|
+
for (let i = 0; i < count; i++) {
|
|
740
|
+
const code = generateInviteCode();
|
|
741
|
+
const codeHash = hashInviteCode(code);
|
|
742
|
+
const id = randomUUID();
|
|
743
|
+
await db.insert(inviteCodes).values({
|
|
744
|
+
id,
|
|
745
|
+
codeHash,
|
|
746
|
+
createdBy: user.id,
|
|
747
|
+
expiresAt,
|
|
748
|
+
createdAt: new Date(),
|
|
749
|
+
label,
|
|
750
|
+
});
|
|
751
|
+
codes.push({ id, code, expiresAt });
|
|
752
|
+
}
|
|
753
|
+
return c.json({
|
|
754
|
+
codes,
|
|
755
|
+
message: `Generated ${count} invite code(s)`,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
catch (err) {
|
|
759
|
+
console.error('[Admin] Generate invites error:', err);
|
|
760
|
+
return c.json({ error: 'Failed to generate invite codes' }, 500);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
/**
|
|
764
|
+
* GET /api/users/admin/invites
|
|
765
|
+
* List invite codes (admin only)
|
|
766
|
+
*/
|
|
767
|
+
userRoutes.get('/admin/invites', requireAdmin, async (c) => {
|
|
768
|
+
const db = getDb();
|
|
769
|
+
if (!db)
|
|
770
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
771
|
+
try {
|
|
772
|
+
const allInvites = await db
|
|
773
|
+
.select({
|
|
774
|
+
id: inviteCodes.id,
|
|
775
|
+
createdBy: inviteCodes.createdBy,
|
|
776
|
+
usedBy: inviteCodes.usedBy,
|
|
777
|
+
usedAt: inviteCodes.usedAt,
|
|
778
|
+
expiresAt: inviteCodes.expiresAt,
|
|
779
|
+
createdAt: inviteCodes.createdAt,
|
|
780
|
+
label: inviteCodes.label,
|
|
781
|
+
})
|
|
782
|
+
.from(inviteCodes)
|
|
783
|
+
.orderBy(inviteCodes.createdAt);
|
|
784
|
+
return c.json({ invites: allInvites, total: allInvites.length });
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
console.error('[Admin] List invites error:', err);
|
|
788
|
+
return c.json({ error: 'Failed to list invite codes' }, 500);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
/**
|
|
792
|
+
* DELETE /api/users/admin/invites/:id
|
|
793
|
+
* Revoke/delete an invite code (admin only)
|
|
794
|
+
*/
|
|
795
|
+
userRoutes.delete('/admin/invites/:id', requireAdmin, async (c) => {
|
|
796
|
+
const inviteId = c.req.param('id');
|
|
797
|
+
const db = getDb();
|
|
798
|
+
if (!db)
|
|
799
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
800
|
+
try {
|
|
801
|
+
const existing = await db
|
|
802
|
+
.select()
|
|
803
|
+
.from(inviteCodes)
|
|
804
|
+
.where(eq(inviteCodes.id, inviteId))
|
|
805
|
+
.limit(1);
|
|
806
|
+
if (!existing.length) {
|
|
807
|
+
return c.json({ error: 'Invite code not found' }, 404);
|
|
808
|
+
}
|
|
809
|
+
await db.delete(inviteCodes).where(eq(inviteCodes.id, inviteId));
|
|
810
|
+
return c.json({ message: 'Invite code deleted' });
|
|
811
|
+
}
|
|
812
|
+
catch (err) {
|
|
813
|
+
console.error('[Admin] Delete invite error:', err);
|
|
814
|
+
return c.json({ error: 'Failed to delete invite code' }, 500);
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
// ADMIN: REGISTRATION MODE (must be before /:userId to avoid route shadowing)
|
|
818
|
+
/**
|
|
819
|
+
* GET /api/users/admin/registration-mode
|
|
820
|
+
* Get current registration mode (admin only)
|
|
821
|
+
*/
|
|
822
|
+
userRoutes.get('/admin/registration-mode', requireAdmin, async (c) => {
|
|
823
|
+
try {
|
|
824
|
+
const settings = await getSettingsRaw();
|
|
825
|
+
return c.json({ mode: settings.system.registrationMode });
|
|
826
|
+
}
|
|
827
|
+
catch (err) {
|
|
828
|
+
console.error('[Admin] Get registration mode error:', err);
|
|
829
|
+
return c.json({ error: 'Failed to get registration mode' }, 500);
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
/**
|
|
833
|
+
* PATCH /api/users/admin/registration-mode
|
|
834
|
+
* Set registration mode (admin only)
|
|
835
|
+
*/
|
|
836
|
+
userRoutes.patch('/admin/registration-mode', requireAdmin, async (c) => {
|
|
837
|
+
try {
|
|
838
|
+
const body = await c.req.json();
|
|
839
|
+
const { mode } = body;
|
|
840
|
+
if (mode !== 'open' && mode !== 'invite') {
|
|
841
|
+
return c.json({ error: 'Mode must be "open" or "invite"' }, 400);
|
|
842
|
+
}
|
|
843
|
+
await updateSettings({
|
|
844
|
+
system: { registrationMode: mode },
|
|
845
|
+
});
|
|
846
|
+
return c.json({ mode, message: `Registration mode set to ${mode}` });
|
|
847
|
+
}
|
|
848
|
+
catch (err) {
|
|
849
|
+
console.error('[Admin] Set registration mode error:', err);
|
|
850
|
+
return c.json({ error: 'Failed to set registration mode' }, 500);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
// ADMIN: USER CRUD (/:userId wildcard routes - must be AFTER specific routes)
|
|
854
|
+
/**
|
|
855
|
+
* GET /api/users/admin/:userId
|
|
856
|
+
* Get user details (admin only)
|
|
857
|
+
*/
|
|
858
|
+
userRoutes.get('/admin/:userId', requireAdmin, async (c) => {
|
|
859
|
+
const userId = c.req.param('userId');
|
|
860
|
+
const db = getDb();
|
|
861
|
+
if (!db)
|
|
862
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
863
|
+
try {
|
|
864
|
+
const result = await db
|
|
865
|
+
.select({
|
|
866
|
+
id: users.id,
|
|
867
|
+
email: users.email,
|
|
868
|
+
name: users.name,
|
|
869
|
+
role: users.role,
|
|
870
|
+
status: users.status,
|
|
871
|
+
avatarUrl: users.avatarUrl,
|
|
872
|
+
createdAt: users.createdAt,
|
|
873
|
+
updatedAt: users.updatedAt,
|
|
874
|
+
onboardingCompleted: users.onboardingCompleted,
|
|
875
|
+
})
|
|
876
|
+
.from(users)
|
|
877
|
+
.where(eq(users.id, userId))
|
|
878
|
+
.limit(1);
|
|
879
|
+
if (!result.length) {
|
|
880
|
+
return c.json({ error: 'User not found' }, 404);
|
|
881
|
+
}
|
|
882
|
+
// Get session count
|
|
883
|
+
const userSessions = await db
|
|
884
|
+
.select()
|
|
885
|
+
.from(sessions)
|
|
886
|
+
.where(eq(sessions.userId, userId));
|
|
887
|
+
return c.json({ user: result[0], activeSessions: userSessions.length });
|
|
888
|
+
}
|
|
889
|
+
catch (error) {
|
|
890
|
+
console.error('[Admin] Get user error:', error);
|
|
891
|
+
return c.json({ error: 'Failed to get user' }, 500);
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
/**
|
|
895
|
+
* PATCH /api/users/admin/:userId
|
|
896
|
+
* Update user (admin only)
|
|
897
|
+
*/
|
|
898
|
+
userRoutes.patch('/admin/:userId', requireAdmin, async (c) => {
|
|
899
|
+
const userId = c.req.param('userId');
|
|
900
|
+
const db = getDb();
|
|
901
|
+
if (!db)
|
|
902
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
903
|
+
try {
|
|
904
|
+
const body = await c.req.json();
|
|
905
|
+
const { name, role, status } = body;
|
|
906
|
+
// Validate role if provided
|
|
907
|
+
if (role && !['admin', 'user'].includes(role)) {
|
|
908
|
+
return c.json({ error: 'Invalid role' }, 400);
|
|
909
|
+
}
|
|
910
|
+
// Validate status if provided
|
|
911
|
+
if (status && !['active', 'suspended', 'pending'].includes(status)) {
|
|
912
|
+
return c.json({ error: 'Invalid status' }, 400);
|
|
913
|
+
}
|
|
914
|
+
await db
|
|
915
|
+
.update(users)
|
|
916
|
+
.set({
|
|
917
|
+
...(name !== undefined && { name }),
|
|
918
|
+
...(role !== undefined && { role }),
|
|
919
|
+
...(status !== undefined && { status }),
|
|
920
|
+
updatedAt: new Date(),
|
|
921
|
+
})
|
|
922
|
+
.where(eq(users.id, userId));
|
|
923
|
+
return c.json({ message: 'User updated' });
|
|
924
|
+
}
|
|
925
|
+
catch (error) {
|
|
926
|
+
console.error('[Admin] Update user error:', error);
|
|
927
|
+
return c.json({ error: 'Failed to update user' }, 500);
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
/**
|
|
931
|
+
* POST /api/users/admin/:userId/reset-password
|
|
932
|
+
* Reset user password (admin only) - generates temporary password
|
|
933
|
+
*/
|
|
934
|
+
userRoutes.post('/admin/:userId/reset-password', requireAdmin, async (c) => {
|
|
935
|
+
const userId = c.req.param('userId');
|
|
936
|
+
const db = getDb();
|
|
937
|
+
if (!db)
|
|
938
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
939
|
+
try {
|
|
940
|
+
// Generate a temporary password
|
|
941
|
+
const tempPassword = randomBytes(8).toString('hex');
|
|
942
|
+
const passwordHash = hashPassword(tempPassword);
|
|
943
|
+
// Generate new recovery codes
|
|
944
|
+
const recoveryCodes = generateRecoveryCodes(8);
|
|
945
|
+
const hashedRecoveryCodes = hashRecoveryCodes(recoveryCodes);
|
|
946
|
+
await db
|
|
947
|
+
.update(users)
|
|
948
|
+
.set({
|
|
949
|
+
passwordHash,
|
|
950
|
+
recoveryCodes: JSON.stringify(hashedRecoveryCodes),
|
|
951
|
+
updatedAt: new Date(),
|
|
952
|
+
})
|
|
953
|
+
.where(eq(users.id, userId));
|
|
954
|
+
// Invalidate all sessions for this user
|
|
955
|
+
await db.delete(sessions).where(eq(sessions.userId, userId));
|
|
956
|
+
return c.json({
|
|
957
|
+
message: 'Password reset successfully',
|
|
958
|
+
temporaryPassword: tempPassword,
|
|
959
|
+
recoveryCodes,
|
|
960
|
+
note: 'User must change password on next login. All sessions have been revoked.',
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
console.error('[Admin] Reset password error:', error);
|
|
965
|
+
return c.json({ error: 'Failed to reset password' }, 500);
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
/**
|
|
969
|
+
* DELETE /api/users/admin/:userId
|
|
970
|
+
* Delete user (admin only)
|
|
971
|
+
*/
|
|
972
|
+
userRoutes.delete('/admin/:userId', requireAdmin, async (c) => {
|
|
973
|
+
const adminUser = c.get('user');
|
|
974
|
+
const userId = c.req.param('userId');
|
|
975
|
+
const db = getDb();
|
|
976
|
+
if (!db)
|
|
977
|
+
return c.json({ error: 'Database not initialized' }, 500);
|
|
978
|
+
// Prevent self-deletion
|
|
979
|
+
if (userId === adminUser.id) {
|
|
980
|
+
return c.json({ error: 'Cannot delete your own account' }, 400);
|
|
981
|
+
}
|
|
982
|
+
try {
|
|
983
|
+
// Delete user sessions first
|
|
984
|
+
await db.delete(sessions).where(eq(sessions.userId, userId));
|
|
985
|
+
// Delete user preferences
|
|
986
|
+
await db.delete(userPreferences).where(eq(userPreferences.userId, userId));
|
|
987
|
+
// Delete user API keys
|
|
988
|
+
await db.delete(userApiKeys).where(eq(userApiKeys.userId, userId));
|
|
989
|
+
// Delete user
|
|
990
|
+
await db.delete(users).where(eq(users.id, userId));
|
|
991
|
+
return c.json({ message: 'User deleted' });
|
|
992
|
+
}
|
|
993
|
+
catch (error) {
|
|
994
|
+
console.error('[Admin] Delete user error:', error);
|
|
995
|
+
return c.json({ error: 'Failed to delete user' }, 500);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
//# sourceMappingURL=users.js.map
|