@pixelbyte-software/pixcode 1.51.7 → 1.51.9
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/CODE_OF_CONDUCT.md +41 -41
- package/CONTRIBUTING.md +155 -155
- package/LICENSE +718 -718
- package/README.de.md +169 -169
- package/README.ja.md +167 -167
- package/README.ko.md +167 -167
- package/README.md +419 -419
- package/README.ru.md +169 -169
- package/README.tr.md +298 -298
- package/README.zh-CN.md +167 -167
- package/SECURITY.md +46 -46
- package/dist/api-automation.html +110 -110
- package/dist/api-docs.html +548 -548
- package/dist/assets/{index-BvQgSoyC.js → index-Gs7C4hZT.js} +70 -70
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/docs.html +308 -308
- package/dist/favicon.svg +8 -8
- package/dist/features.html +133 -133
- package/dist/generate-icons.js +48 -48
- package/dist/humans.txt +15 -15
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +9 -9
- package/dist/icons/icon-144x144.svg +9 -9
- package/dist/icons/icon-152x152.svg +9 -9
- package/dist/icons/icon-192x192.svg +9 -9
- package/dist/icons/icon-384x384.svg +9 -9
- package/dist/icons/icon-512x512.svg +9 -9
- package/dist/icons/icon-72x72.svg +9 -9
- package/dist/icons/icon-96x96.svg +9 -9
- package/dist/icons/icon-template.svg +9 -9
- package/dist/icons/qwen-logo.svg +14 -14
- package/dist/index.html +58 -58
- package/dist/landing.html +268 -268
- package/dist/llms-full.txt +119 -119
- package/dist/llms.txt +53 -53
- package/dist/logo.svg +12 -12
- package/dist/manifest.json +60 -60
- package/dist/openapi.yaml +1696 -1696
- package/dist/orchestration.html +125 -125
- package/dist/robots.txt +4 -4
- package/dist/site.css +692 -692
- package/dist/sitemap.xml +51 -51
- package/dist/sw.js +132 -132
- package/dist-server/server/cli.js +96 -96
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +64 -64
- package/dist-server/server/index.js +91 -65
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +17 -17
- package/dist-server/server/routes/live-view.js +46 -46
- package/dist-server/server/services/hermes-gateway.js +5 -5
- package/dist-server/server/services/public-api-manifest.js +51 -51
- package/package.json +222 -222
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/github/create-v1.38-issues.mjs +351 -351
- package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
- package/scripts/hermes/configure-pixcode-mcp.mjs +165 -165
- package/scripts/hermes/pixcode-mcp-server.mjs +1009 -1009
- package/scripts/smoke/changes-panel-layout.mjs +48 -48
- package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
- package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
- package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
- package/scripts/smoke/chat-session-state.mjs +19 -19
- package/scripts/smoke/code-editor-theme.mjs +55 -55
- package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
- package/scripts/smoke/command-center-agent-writes.mjs +79 -79
- package/scripts/smoke/command-center-non-git.mjs +46 -46
- package/scripts/smoke/context-packet.mjs +43 -43
- package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
- package/scripts/smoke/daemon-entrypoint.mjs +20 -20
- package/scripts/smoke/default-landing-routing.mjs +33 -33
- package/scripts/smoke/desktop-native-notifications.mjs +30 -30
- package/scripts/smoke/desktop-tray-icon.mjs +33 -33
- package/scripts/smoke/discord-release-workflow.mjs +24 -24
- package/scripts/smoke/git-install-update.mjs +255 -255
- package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
- package/scripts/smoke/hermes-api-install.mjs +56 -56
- package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -426
- package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
- package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
- package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
- package/scripts/smoke/hermes-rest-gateway.mjs +79 -79
- package/scripts/smoke/hermes-rest-live.mjs +42 -42
- package/scripts/smoke/hermes-roundtrip.mjs +167 -167
- package/scripts/smoke/hermes-settings-commands.mjs +349 -349
- package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
- package/scripts/smoke/live-view-diagnostics.mjs +53 -53
- package/scripts/smoke/live-view-environment.mjs +92 -92
- package/scripts/smoke/live-view-integration.mjs +450 -450
- package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
- package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
- package/scripts/smoke/model-registry.mjs +36 -36
- package/scripts/smoke/multi-project-ui.mjs +45 -45
- package/scripts/smoke/multi-worker-slots.mjs +42 -42
- package/scripts/smoke/notification-center.mjs +87 -87
- package/scripts/smoke/notification-inapp-preference.mjs +23 -23
- package/scripts/smoke/notification-taxonomy.mjs +58 -58
- package/scripts/smoke/orchestration-api.mjs +172 -172
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
- package/scripts/smoke/orchestration-live-run.mjs +176 -176
- package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
- package/scripts/smoke/orchestration-model-sync.mjs +30 -30
- package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
- package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
- package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
- package/scripts/smoke/permission-policy.mjs +50 -50
- package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -167
- package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
- package/scripts/smoke/provider-rest-api.mjs +124 -124
- package/scripts/smoke/provider-selection-status.mjs +52 -52
- package/scripts/smoke/run-state-refresh.mjs +52 -52
- package/scripts/smoke/runtime-manager.mjs +99 -99
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
- package/scripts/smoke/static-root-routing.mjs +21 -21
- package/scripts/smoke/strict-handoff-compact.mjs +60 -60
- package/scripts/smoke/taskmaster-config.mjs +24 -24
- package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
- package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
- package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
- package/scripts/smoke/telegram-control.mjs +242 -242
- package/scripts/smoke/tunnel-persistence.mjs +56 -56
- package/scripts/smoke/update-issue-progress.mjs +69 -69
- package/scripts/smoke/update-ux.mjs +55 -55
- package/scripts/smoke/v138-completion.mjs +132 -132
- package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
- package/scripts/smoke/v138-diagnostics.mjs +63 -63
- package/scripts/smoke/v138-issue-planner.mjs +33 -33
- package/scripts/smoke/v143-remote-control.mjs +76 -76
- package/scripts/smoke/v144-production-loop.mjs +47 -47
- package/scripts/smoke/v145-platformization.mjs +46 -46
- package/scripts/smoke/v146-control-room-ui.mjs +150 -150
- package/scripts/smoke/version-modal-autoshow.mjs +29 -29
- package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
- package/scripts/smoke/vscode-workbench-polish.mjs +461 -461
- package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
- package/scripts/smoke/workflow-templates.mjs +43 -43
- package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
- package/scripts/update-git-install.mjs +283 -283
- package/server/claude-sdk.js +920 -920
- package/server/cli.js +1035 -1035
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +344 -344
- package/server/daemon/manager.js +563 -563
- package/server/daemon-manager.js +964 -964
- package/server/database/db.js +895 -895
- package/server/database/json-store.js +197 -197
- package/server/gemini-cli.js +550 -550
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +111 -81
- package/server/load-env.js +35 -35
- package/server/middleware/auth.js +155 -155
- package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
- package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +100 -100
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/agent-card.ts +55 -55
- package/server/modules/orchestration/a2a/routes.ts +590 -590
- package/server/modules/orchestration/a2a/task-store.ts +178 -178
- package/server/modules/orchestration/a2a/types.ts +126 -126
- package/server/modules/orchestration/a2a/validator.ts +113 -113
- package/server/modules/orchestration/hermes/hermes.routes.ts +642 -642
- package/server/modules/orchestration/index.ts +101 -101
- package/server/modules/orchestration/preview/port-watcher.ts +112 -112
- package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
- package/server/modules/orchestration/preview/types.ts +19 -19
- package/server/modules/orchestration/security/permission-policy.ts +401 -401
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
- package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
- package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
- package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
- package/server/modules/orchestration/workflows/context-packet.ts +186 -186
- package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
- package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
- package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
- package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
- package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
- package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
- package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
- package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
- package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
- package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
- package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
- package/server/modules/orchestration/workspace/path-safety.ts +55 -55
- package/server/modules/orchestration/workspace/types.ts +52 -52
- package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
- package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
- package/server/modules/providers/index.ts +2 -2
- package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
- package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +891 -891
- package/server/modules/providers/services/mcp.service.ts +86 -86
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/provider-configs.ts +142 -142
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +462 -462
- package/server/opencode-cli.js +491 -491
- package/server/opencode-response-handler.js +111 -111
- package/server/projects.js +3008 -3008
- package/server/qwen-code-cli.js +410 -410
- package/server/qwen-response-handler.js +73 -73
- package/server/routes/agent.js +1435 -1435
- package/server/routes/auth.js +142 -142
- package/server/routes/codex.js +20 -20
- package/server/routes/commands.js +538 -538
- package/server/routes/cursor.js +61 -61
- package/server/routes/diagnostics.js +41 -41
- package/server/routes/gemini.js +25 -25
- package/server/routes/git.js +1641 -1641
- package/server/routes/live-view.js +387 -387
- package/server/routes/mcp-utils.js +13 -13
- package/server/routes/messages.js +50 -50
- package/server/routes/network.js +117 -117
- package/server/routes/platformization.js +182 -182
- package/server/routes/plugins.js +320 -320
- package/server/routes/production-agent-loop.js +90 -90
- package/server/routes/projects.js +911 -911
- package/server/routes/public-api.js +34 -34
- package/server/routes/qwen.js +27 -27
- package/server/routes/remote.js +50 -50
- package/server/routes/settings.js +321 -321
- package/server/routes/telegram.js +140 -140
- package/server/routes/user.js +125 -125
- package/server/routes/webhooks.js +63 -63
- package/server/services/control-room.js +102 -102
- package/server/services/diagnostics.js +165 -165
- package/server/services/external-access.js +375 -375
- package/server/services/hermes-gateway.js +1562 -1562
- package/server/services/hermes-install-jobs.js +729 -729
- package/server/services/install-jobs.js +715 -715
- package/server/services/live-view.js +956 -956
- package/server/services/managed-runtimes.js +493 -493
- package/server/services/model-registry.js +144 -144
- package/server/services/notification-orchestrator.js +365 -365
- package/server/services/notification-taxonomy.js +204 -204
- package/server/services/platformization.js +773 -773
- package/server/services/production-agent-loop.js +248 -248
- package/server/services/provider-cli-versions.js +149 -149
- package/server/services/provider-credentials.js +189 -189
- package/server/services/provider-models.js +396 -396
- package/server/services/public-api-manifest.js +190 -190
- package/server/services/remote-connection.js +127 -127
- package/server/services/runtime-manager.js +323 -323
- package/server/services/startup-update.js +234 -234
- package/server/services/telegram/bot.js +331 -331
- package/server/services/telegram/control-center.js +979 -979
- package/server/services/telegram/telegram-http-client.js +151 -151
- package/server/services/telegram/translations.js +340 -340
- package/server/services/vapid-keys.js +36 -36
- package/server/services/webhooks.js +216 -216
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +305 -305
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +185 -185
- package/server/utils/port-access.js +209 -209
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +79 -79
- package/shared/modelConstants.js +161 -161
- package/shared/networkHosts.js +22 -22
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
const API_GROUPS = [
|
|
2
|
-
{ id: 'auth', title: 'Authentication', basePath: '/api/auth', scopes: ['auth:read', 'auth:write'] },
|
|
3
|
-
{ id: 'projects', title: 'Projects', basePath: '/api/projects', scopes: ['projects:read', 'projects:write'] },
|
|
4
|
-
{ id: 'sessions', title: 'Sessions and messages', basePath: '/api/sessions', scopes: ['sessions:read', 'sessions:write'] },
|
|
5
|
-
{ id: 'providers', title: 'CLI providers', basePath: '/api/providers', scopes: ['providers:read', 'providers:write'] },
|
|
6
|
-
{ id: 'terminal', title: 'Visible terminal sessions', basePath: '/api/shell/sessions', scopes: ['terminal:launch'] },
|
|
7
|
-
{ id: 'hermes', title: 'Hermes Agent control', basePath: '/api/orchestration/hermes', scopes: ['hermes:mcp', 'hermes:gateway', 'terminal:launch'] },
|
|
8
|
-
{ id: 'orchestration', title: 'Orchestration runs', basePath: '/api/orchestration', scopes: ['orchestration:read', 'orchestration:write'] },
|
|
9
|
-
{ id: 'notifications', title: 'Notifications', basePath: '/api/settings/notifications', scopes: ['notifications:read', 'notifications:write'] },
|
|
10
|
-
{ id: 'files', title: 'Files', basePath: '/api/projects/:projectName/files', scopes: ['files:read', 'files:write'] },
|
|
11
|
-
{ id: 'git', title: 'Source control', basePath: '/api/git', scopes: ['git:read', 'git:write'] },
|
|
12
|
-
{ id: 'settings', title: 'Settings and API keys', basePath: '/api/settings', scopes: ['settings:read', 'settings:write'] },
|
|
13
|
-
{ id: 'updates', title: 'Update status', basePath: '/api/update', scopes: ['updates:read', 'updates:write'] },
|
|
14
|
-
{ id: 'diagnostics', title: 'Diagnostics', basePath: '/api/diagnostics', scopes: ['diagnostics:read'] },
|
|
15
|
-
{ id: 'remote', title: 'Remote connection', basePath: '/api/remote', scopes: ['remote:read', 'remote:write'] },
|
|
16
|
-
{ id: 'telegram', title: 'Telegram control', basePath: '/api/telegram', scopes: ['telegram:read', 'telegram:write'] },
|
|
17
|
-
{ id: 'webhooks', title: 'Outbound webhooks', basePath: '/api/webhooks', scopes: ['webhooks:read', 'webhooks:write'] },
|
|
18
|
-
{ id: 'plugins', title: 'Plugins and MCP tools', basePath: '/api/plugins', scopes: ['plugins:read', 'plugins:write'] },
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const API_SCOPES = Array.from(new Set(API_GROUPS.flatMap((group) => group.scopes))).sort();
|
|
22
|
-
|
|
23
|
-
export function buildPublicApiManifest({ baseUrl = '' } = {}) {
|
|
24
|
-
const origin = String(baseUrl || '').replace(/\/+$/, '');
|
|
25
|
-
return {
|
|
26
|
-
name: 'Pixcode Public API',
|
|
27
|
-
version: '1.38',
|
|
28
|
-
baseUrl: origin || null,
|
|
29
|
-
auth: {
|
|
30
|
-
transports: ['Authorization: Bearer <px_api_key>', 'X-API-Key: <px_api_key>', '?apiKey=<px_api_key>'],
|
|
31
|
-
websocket: 'Pass the same px_ API key as the token query parameter.',
|
|
32
|
-
},
|
|
33
|
-
apiKey: {
|
|
34
|
-
prefix: 'px_',
|
|
35
|
-
scopes: API_SCOPES,
|
|
36
|
-
revocable: true,
|
|
37
|
-
manageableAt: '/api/settings/api-keys',
|
|
38
|
-
},
|
|
39
|
-
groups: API_GROUPS,
|
|
40
|
-
examples: [
|
|
41
|
-
{
|
|
42
|
-
title: 'List projects',
|
|
43
|
-
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/projects`,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
title: 'Fetch diagnostics bundle',
|
|
47
|
-
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/diagnostics/bundle`,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
title: 'Read Hermes control plane',
|
|
51
|
-
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/orchestration/hermes/control-plane`,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
title: 'Read the mobile remote control room',
|
|
55
|
-
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/remote/control-room`,
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
title: 'Register an outbound webhook',
|
|
59
|
-
curl: `curl -X POST -H "Content-Type: application/json" -H "X-API-Key: px_your_key" -d '{"name":"CI listener","url":"https://example.com/pixcode","events":["run.completed","approval.needed"]}' ${origin || 'http://127.0.0.1:3001'}/api/webhooks`,
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function buildTypeScriptSdkStarter({ baseUrl = '' } = {}) {
|
|
66
|
-
const origin = String(baseUrl || 'http://127.0.0.1:3001').replace(/\/+$/, '');
|
|
67
|
-
return `export type PixcodeRun = {
|
|
68
|
-
id: string;
|
|
69
|
-
workflowId: string;
|
|
70
|
-
status: 'queued' | 'running' | 'completed' | 'failed' | 'canceled';
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export class PixcodeClient {
|
|
74
|
-
constructor(
|
|
75
|
-
private readonly apiKey: string,
|
|
76
|
-
private readonly baseUrl = '${origin}',
|
|
77
|
-
) {}
|
|
78
|
-
|
|
79
|
-
private async request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
80
|
-
const response = await fetch(new URL(path, this.baseUrl), {
|
|
81
|
-
...init,
|
|
82
|
-
headers: {
|
|
83
|
-
'Content-Type': 'application/json',
|
|
84
|
-
'X-API-Key': this.apiKey,
|
|
85
|
-
...(init.headers || {}),
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
if (!response.ok) throw new Error(\`Pixcode API \${response.status}: \${await response.text()}\`);
|
|
89
|
-
return response.json() as Promise<T>;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
projects() {
|
|
93
|
-
return this.request<{ projects: unknown[] }>('/api/projects');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
controlRoom() {
|
|
97
|
-
return this.request<{ success: true; controlRoom: unknown }>('/api/remote/control-room');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
approvals() {
|
|
101
|
-
return this.request<{ pendingApprovals: unknown[] }>('/api/orchestration/workflows/approvals');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
decideApproval(approvalId: string, allow: boolean) {
|
|
105
|
-
return this.request(\`/api/orchestration/workflows/approvals/\${encodeURIComponent(approvalId)}\`, {
|
|
106
|
-
method: 'POST',
|
|
107
|
-
body: JSON.stringify({ allow, source: 'api' }),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
startWorkflow(workflowId: string, input: string, metadata: Record<string, unknown> = {}) {
|
|
112
|
-
return this.request<PixcodeRun>(\`/api/orchestration/workflows/\${encodeURIComponent(workflowId)}/runs\`, {
|
|
113
|
-
method: 'POST',
|
|
114
|
-
body: JSON.stringify({ input, metadata }),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
`;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function buildCurlCookbook({ baseUrl = '' } = {}) {
|
|
122
|
-
const origin = String(baseUrl || 'http://127.0.0.1:3001').replace(/\/+$/, '');
|
|
123
|
-
return {
|
|
124
|
-
title: 'Pixcode Public API Cookbook',
|
|
125
|
-
variables: {
|
|
126
|
-
PIXCODE_URL: origin,
|
|
127
|
-
PIXCODE_API_KEY: 'px_your_key',
|
|
128
|
-
},
|
|
129
|
-
examples: [
|
|
130
|
-
{
|
|
131
|
-
title: 'List projects',
|
|
132
|
-
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/projects"`,
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
title: 'Read the mobile control room',
|
|
136
|
-
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/remote/control-room"`,
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
title: 'List pending approvals',
|
|
140
|
-
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/orchestration/workflows/approvals"`,
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
title: 'Read Hermes control plane',
|
|
144
|
-
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/orchestration/hermes/control-plane"`,
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
title: 'Approve a pending action',
|
|
148
|
-
command: `curl -X POST -H "Content-Type: application/json" -H "X-API-Key: $PIXCODE_API_KEY" -d '{"allow":true,"source":"api"}' "$PIXCODE_URL/api/orchestration/workflows/approvals/approval_id"`,
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
title: 'Create a webhook',
|
|
152
|
-
command: `curl -X POST -H "Content-Type: application/json" -H "X-API-Key: $PIXCODE_API_KEY" -d '{"name":"CI listener","url":"https://example.com/pixcode","events":["run.completed","run.failed","approval.needed"]}' "$PIXCODE_URL/api/webhooks"`,
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function buildOpenApiFragment(options = {}) {
|
|
159
|
-
const manifest = buildPublicApiManifest(options);
|
|
160
|
-
return {
|
|
161
|
-
openapi: '3.1.0',
|
|
162
|
-
info: {
|
|
163
|
-
title: manifest.name,
|
|
164
|
-
version: manifest.version,
|
|
165
|
-
},
|
|
166
|
-
security: [{ PixcodeApiKey: [] }],
|
|
167
|
-
components: {
|
|
168
|
-
securitySchemes: {
|
|
169
|
-
PixcodeApiKey: {
|
|
170
|
-
type: 'apiKey',
|
|
171
|
-
in: 'header',
|
|
172
|
-
name: 'X-API-Key',
|
|
173
|
-
description: 'Pixcode px_ API key. Keys are revocable and can carry scopes.',
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
paths: Object.fromEntries(
|
|
178
|
-
manifest.groups.map((group) => [
|
|
179
|
-
group.basePath,
|
|
180
|
-
{
|
|
181
|
-
get: {
|
|
182
|
-
summary: group.title,
|
|
183
|
-
'x-pixcode-group': group.id,
|
|
184
|
-
'x-pixcode-scopes': group.scopes,
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
]),
|
|
188
|
-
),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
1
|
+
const API_GROUPS = [
|
|
2
|
+
{ id: 'auth', title: 'Authentication', basePath: '/api/auth', scopes: ['auth:read', 'auth:write'] },
|
|
3
|
+
{ id: 'projects', title: 'Projects', basePath: '/api/projects', scopes: ['projects:read', 'projects:write'] },
|
|
4
|
+
{ id: 'sessions', title: 'Sessions and messages', basePath: '/api/sessions', scopes: ['sessions:read', 'sessions:write'] },
|
|
5
|
+
{ id: 'providers', title: 'CLI providers', basePath: '/api/providers', scopes: ['providers:read', 'providers:write'] },
|
|
6
|
+
{ id: 'terminal', title: 'Visible terminal sessions', basePath: '/api/shell/sessions', scopes: ['terminal:launch'] },
|
|
7
|
+
{ id: 'hermes', title: 'Hermes Agent control', basePath: '/api/orchestration/hermes', scopes: ['hermes:mcp', 'hermes:gateway', 'terminal:launch'] },
|
|
8
|
+
{ id: 'orchestration', title: 'Orchestration runs', basePath: '/api/orchestration', scopes: ['orchestration:read', 'orchestration:write'] },
|
|
9
|
+
{ id: 'notifications', title: 'Notifications', basePath: '/api/settings/notifications', scopes: ['notifications:read', 'notifications:write'] },
|
|
10
|
+
{ id: 'files', title: 'Files', basePath: '/api/projects/:projectName/files', scopes: ['files:read', 'files:write'] },
|
|
11
|
+
{ id: 'git', title: 'Source control', basePath: '/api/git', scopes: ['git:read', 'git:write'] },
|
|
12
|
+
{ id: 'settings', title: 'Settings and API keys', basePath: '/api/settings', scopes: ['settings:read', 'settings:write'] },
|
|
13
|
+
{ id: 'updates', title: 'Update status', basePath: '/api/update', scopes: ['updates:read', 'updates:write'] },
|
|
14
|
+
{ id: 'diagnostics', title: 'Diagnostics', basePath: '/api/diagnostics', scopes: ['diagnostics:read'] },
|
|
15
|
+
{ id: 'remote', title: 'Remote connection', basePath: '/api/remote', scopes: ['remote:read', 'remote:write'] },
|
|
16
|
+
{ id: 'telegram', title: 'Telegram control', basePath: '/api/telegram', scopes: ['telegram:read', 'telegram:write'] },
|
|
17
|
+
{ id: 'webhooks', title: 'Outbound webhooks', basePath: '/api/webhooks', scopes: ['webhooks:read', 'webhooks:write'] },
|
|
18
|
+
{ id: 'plugins', title: 'Plugins and MCP tools', basePath: '/api/plugins', scopes: ['plugins:read', 'plugins:write'] },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const API_SCOPES = Array.from(new Set(API_GROUPS.flatMap((group) => group.scopes))).sort();
|
|
22
|
+
|
|
23
|
+
export function buildPublicApiManifest({ baseUrl = '' } = {}) {
|
|
24
|
+
const origin = String(baseUrl || '').replace(/\/+$/, '');
|
|
25
|
+
return {
|
|
26
|
+
name: 'Pixcode Public API',
|
|
27
|
+
version: '1.38',
|
|
28
|
+
baseUrl: origin || null,
|
|
29
|
+
auth: {
|
|
30
|
+
transports: ['Authorization: Bearer <px_api_key>', 'X-API-Key: <px_api_key>', '?apiKey=<px_api_key>'],
|
|
31
|
+
websocket: 'Pass the same px_ API key as the token query parameter.',
|
|
32
|
+
},
|
|
33
|
+
apiKey: {
|
|
34
|
+
prefix: 'px_',
|
|
35
|
+
scopes: API_SCOPES,
|
|
36
|
+
revocable: true,
|
|
37
|
+
manageableAt: '/api/settings/api-keys',
|
|
38
|
+
},
|
|
39
|
+
groups: API_GROUPS,
|
|
40
|
+
examples: [
|
|
41
|
+
{
|
|
42
|
+
title: 'List projects',
|
|
43
|
+
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/projects`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: 'Fetch diagnostics bundle',
|
|
47
|
+
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/diagnostics/bundle`,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
title: 'Read Hermes control plane',
|
|
51
|
+
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/orchestration/hermes/control-plane`,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
title: 'Read the mobile remote control room',
|
|
55
|
+
curl: `curl -H "X-API-Key: px_your_key" ${origin || 'http://127.0.0.1:3001'}/api/remote/control-room`,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
title: 'Register an outbound webhook',
|
|
59
|
+
curl: `curl -X POST -H "Content-Type: application/json" -H "X-API-Key: px_your_key" -d '{"name":"CI listener","url":"https://example.com/pixcode","events":["run.completed","approval.needed"]}' ${origin || 'http://127.0.0.1:3001'}/api/webhooks`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function buildTypeScriptSdkStarter({ baseUrl = '' } = {}) {
|
|
66
|
+
const origin = String(baseUrl || 'http://127.0.0.1:3001').replace(/\/+$/, '');
|
|
67
|
+
return `export type PixcodeRun = {
|
|
68
|
+
id: string;
|
|
69
|
+
workflowId: string;
|
|
70
|
+
status: 'queued' | 'running' | 'completed' | 'failed' | 'canceled';
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export class PixcodeClient {
|
|
74
|
+
constructor(
|
|
75
|
+
private readonly apiKey: string,
|
|
76
|
+
private readonly baseUrl = '${origin}',
|
|
77
|
+
) {}
|
|
78
|
+
|
|
79
|
+
private async request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
80
|
+
const response = await fetch(new URL(path, this.baseUrl), {
|
|
81
|
+
...init,
|
|
82
|
+
headers: {
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'X-API-Key': this.apiKey,
|
|
85
|
+
...(init.headers || {}),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) throw new Error(\`Pixcode API \${response.status}: \${await response.text()}\`);
|
|
89
|
+
return response.json() as Promise<T>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
projects() {
|
|
93
|
+
return this.request<{ projects: unknown[] }>('/api/projects');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
controlRoom() {
|
|
97
|
+
return this.request<{ success: true; controlRoom: unknown }>('/api/remote/control-room');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
approvals() {
|
|
101
|
+
return this.request<{ pendingApprovals: unknown[] }>('/api/orchestration/workflows/approvals');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
decideApproval(approvalId: string, allow: boolean) {
|
|
105
|
+
return this.request(\`/api/orchestration/workflows/approvals/\${encodeURIComponent(approvalId)}\`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
body: JSON.stringify({ allow, source: 'api' }),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
startWorkflow(workflowId: string, input: string, metadata: Record<string, unknown> = {}) {
|
|
112
|
+
return this.request<PixcodeRun>(\`/api/orchestration/workflows/\${encodeURIComponent(workflowId)}/runs\`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
body: JSON.stringify({ input, metadata }),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function buildCurlCookbook({ baseUrl = '' } = {}) {
|
|
122
|
+
const origin = String(baseUrl || 'http://127.0.0.1:3001').replace(/\/+$/, '');
|
|
123
|
+
return {
|
|
124
|
+
title: 'Pixcode Public API Cookbook',
|
|
125
|
+
variables: {
|
|
126
|
+
PIXCODE_URL: origin,
|
|
127
|
+
PIXCODE_API_KEY: 'px_your_key',
|
|
128
|
+
},
|
|
129
|
+
examples: [
|
|
130
|
+
{
|
|
131
|
+
title: 'List projects',
|
|
132
|
+
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/projects"`,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
title: 'Read the mobile control room',
|
|
136
|
+
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/remote/control-room"`,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
title: 'List pending approvals',
|
|
140
|
+
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/orchestration/workflows/approvals"`,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
title: 'Read Hermes control plane',
|
|
144
|
+
command: `curl -H "X-API-Key: $PIXCODE_API_KEY" "$PIXCODE_URL/api/orchestration/hermes/control-plane"`,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
title: 'Approve a pending action',
|
|
148
|
+
command: `curl -X POST -H "Content-Type: application/json" -H "X-API-Key: $PIXCODE_API_KEY" -d '{"allow":true,"source":"api"}' "$PIXCODE_URL/api/orchestration/workflows/approvals/approval_id"`,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
title: 'Create a webhook',
|
|
152
|
+
command: `curl -X POST -H "Content-Type: application/json" -H "X-API-Key: $PIXCODE_API_KEY" -d '{"name":"CI listener","url":"https://example.com/pixcode","events":["run.completed","run.failed","approval.needed"]}' "$PIXCODE_URL/api/webhooks"`,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function buildOpenApiFragment(options = {}) {
|
|
159
|
+
const manifest = buildPublicApiManifest(options);
|
|
160
|
+
return {
|
|
161
|
+
openapi: '3.1.0',
|
|
162
|
+
info: {
|
|
163
|
+
title: manifest.name,
|
|
164
|
+
version: manifest.version,
|
|
165
|
+
},
|
|
166
|
+
security: [{ PixcodeApiKey: [] }],
|
|
167
|
+
components: {
|
|
168
|
+
securitySchemes: {
|
|
169
|
+
PixcodeApiKey: {
|
|
170
|
+
type: 'apiKey',
|
|
171
|
+
in: 'header',
|
|
172
|
+
name: 'X-API-Key',
|
|
173
|
+
description: 'Pixcode px_ API key. Keys are revocable and can carry scopes.',
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
paths: Object.fromEntries(
|
|
178
|
+
manifest.groups.map((group) => [
|
|
179
|
+
group.basePath,
|
|
180
|
+
{
|
|
181
|
+
get: {
|
|
182
|
+
summary: group.title,
|
|
183
|
+
'x-pixcode-group': group.id,
|
|
184
|
+
'x-pixcode-scopes': group.scopes,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
]),
|
|
188
|
+
),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
import { appConfigDb } from '../database/db.js';
|
|
2
|
-
|
|
3
|
-
const CONFIG_KEY = 'remote_connection';
|
|
4
|
-
|
|
5
|
-
const DEFAULT_CONFIG = {
|
|
6
|
-
mode: 'local',
|
|
7
|
-
remoteUrl: null,
|
|
8
|
-
apiKey: null,
|
|
9
|
-
updatedAt: null,
|
|
10
|
-
lastHealth: null,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function parseStoredConfig() {
|
|
14
|
-
const raw = appConfigDb.get(CONFIG_KEY);
|
|
15
|
-
if (!raw) return DEFAULT_CONFIG;
|
|
16
|
-
try {
|
|
17
|
-
return normalizeRemoteConnectionConfig(JSON.parse(raw));
|
|
18
|
-
} catch {
|
|
19
|
-
return DEFAULT_CONFIG;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function normalizeRemoteUrl(value) {
|
|
24
|
-
const raw = typeof value === 'string' ? value.trim() : '';
|
|
25
|
-
if (!raw) return null;
|
|
26
|
-
|
|
27
|
-
const parsed = new URL(raw);
|
|
28
|
-
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
29
|
-
throw new Error('Remote Pixcode URL must use http or https.');
|
|
30
|
-
}
|
|
31
|
-
parsed.pathname = parsed.pathname.replace(/\/+$/, '');
|
|
32
|
-
parsed.search = '';
|
|
33
|
-
parsed.hash = '';
|
|
34
|
-
return parsed.toString().replace(/\/$/, '');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function normalizeRemoteConnectionConfig(input = {}) {
|
|
38
|
-
const mode = input.mode === 'remote' ? 'remote' : 'local';
|
|
39
|
-
const remoteUrl = mode === 'remote' ? normalizeRemoteUrl(input.remoteUrl) : null;
|
|
40
|
-
const apiKey = mode === 'remote' && typeof input.apiKey === 'string' && input.apiKey.trim()
|
|
41
|
-
? input.apiKey.trim()
|
|
42
|
-
: null;
|
|
43
|
-
|
|
44
|
-
if (mode === 'remote' && !remoteUrl) {
|
|
45
|
-
throw new Error('Remote Pixcode URL is required in remote mode.');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
...DEFAULT_CONFIG,
|
|
50
|
-
...input,
|
|
51
|
-
mode,
|
|
52
|
-
remoteUrl,
|
|
53
|
-
apiKey,
|
|
54
|
-
updatedAt: typeof input.updatedAt === 'string' ? input.updatedAt : new Date().toISOString(),
|
|
55
|
-
lastHealth: input.lastHealth && typeof input.lastHealth === 'object' ? input.lastHealth : null,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function saveRemoteConnectionConfig(input = {}) {
|
|
60
|
-
const current = parseStoredConfig();
|
|
61
|
-
const normalized = normalizeRemoteConnectionConfig({
|
|
62
|
-
...current,
|
|
63
|
-
...input,
|
|
64
|
-
apiKey: input.apiKey === undefined ? current.apiKey : input.apiKey,
|
|
65
|
-
updatedAt: new Date().toISOString(),
|
|
66
|
-
});
|
|
67
|
-
appConfigDb.set(CONFIG_KEY, JSON.stringify(normalized));
|
|
68
|
-
return getPublicRemoteConnectionConfig(normalized);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function getRemoteConnectionConfig() {
|
|
72
|
-
return parseStoredConfig();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function getPublicRemoteConnectionConfig(config = parseStoredConfig()) {
|
|
76
|
-
return {
|
|
77
|
-
mode: config.mode,
|
|
78
|
-
remoteUrl: config.remoteUrl,
|
|
79
|
-
apiKeyPresent: Boolean(config.apiKey),
|
|
80
|
-
updatedAt: config.updatedAt,
|
|
81
|
-
lastHealth: config.lastHealth,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function checkRemoteConnection(config = parseStoredConfig(), { timeoutMs = 5000 } = {}) {
|
|
86
|
-
const normalized = normalizeRemoteConnectionConfig(config);
|
|
87
|
-
if (normalized.mode !== 'remote') {
|
|
88
|
-
const localHealth = {
|
|
89
|
-
reachable: true,
|
|
90
|
-
checkedAt: new Date().toISOString(),
|
|
91
|
-
status: 'local',
|
|
92
|
-
message: 'Pixcode is running in local mode.',
|
|
93
|
-
};
|
|
94
|
-
saveRemoteConnectionConfig({ ...normalized, lastHealth: localHealth });
|
|
95
|
-
return localHealth;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const controller = new AbortController();
|
|
99
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
100
|
-
const checkedAt = new Date().toISOString();
|
|
101
|
-
try {
|
|
102
|
-
const response = await fetch(`${normalized.remoteUrl}/api/auth/status`, {
|
|
103
|
-
signal: controller.signal,
|
|
104
|
-
headers: normalized.apiKey ? { 'X-API-Key': normalized.apiKey } : {},
|
|
105
|
-
});
|
|
106
|
-
const health = {
|
|
107
|
-
reachable: response.ok,
|
|
108
|
-
checkedAt,
|
|
109
|
-
status: response.ok ? 'ok' : 'http_error',
|
|
110
|
-
statusCode: response.status,
|
|
111
|
-
message: response.ok ? 'Remote Pixcode server is reachable.' : `Remote server returned HTTP ${response.status}.`,
|
|
112
|
-
};
|
|
113
|
-
saveRemoteConnectionConfig({ ...normalized, lastHealth: health });
|
|
114
|
-
return health;
|
|
115
|
-
} catch (error) {
|
|
116
|
-
const health = {
|
|
117
|
-
reachable: false,
|
|
118
|
-
checkedAt,
|
|
119
|
-
status: error?.name === 'AbortError' ? 'timeout' : 'network_error',
|
|
120
|
-
message: error?.message || 'Remote Pixcode server is unreachable.',
|
|
121
|
-
};
|
|
122
|
-
saveRemoteConnectionConfig({ ...normalized, lastHealth: health });
|
|
123
|
-
return health;
|
|
124
|
-
} finally {
|
|
125
|
-
clearTimeout(timeout);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
1
|
+
import { appConfigDb } from '../database/db.js';
|
|
2
|
+
|
|
3
|
+
const CONFIG_KEY = 'remote_connection';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
mode: 'local',
|
|
7
|
+
remoteUrl: null,
|
|
8
|
+
apiKey: null,
|
|
9
|
+
updatedAt: null,
|
|
10
|
+
lastHealth: null,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function parseStoredConfig() {
|
|
14
|
+
const raw = appConfigDb.get(CONFIG_KEY);
|
|
15
|
+
if (!raw) return DEFAULT_CONFIG;
|
|
16
|
+
try {
|
|
17
|
+
return normalizeRemoteConnectionConfig(JSON.parse(raw));
|
|
18
|
+
} catch {
|
|
19
|
+
return DEFAULT_CONFIG;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeRemoteUrl(value) {
|
|
24
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
25
|
+
if (!raw) return null;
|
|
26
|
+
|
|
27
|
+
const parsed = new URL(raw);
|
|
28
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
29
|
+
throw new Error('Remote Pixcode URL must use http or https.');
|
|
30
|
+
}
|
|
31
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, '');
|
|
32
|
+
parsed.search = '';
|
|
33
|
+
parsed.hash = '';
|
|
34
|
+
return parsed.toString().replace(/\/$/, '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeRemoteConnectionConfig(input = {}) {
|
|
38
|
+
const mode = input.mode === 'remote' ? 'remote' : 'local';
|
|
39
|
+
const remoteUrl = mode === 'remote' ? normalizeRemoteUrl(input.remoteUrl) : null;
|
|
40
|
+
const apiKey = mode === 'remote' && typeof input.apiKey === 'string' && input.apiKey.trim()
|
|
41
|
+
? input.apiKey.trim()
|
|
42
|
+
: null;
|
|
43
|
+
|
|
44
|
+
if (mode === 'remote' && !remoteUrl) {
|
|
45
|
+
throw new Error('Remote Pixcode URL is required in remote mode.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...DEFAULT_CONFIG,
|
|
50
|
+
...input,
|
|
51
|
+
mode,
|
|
52
|
+
remoteUrl,
|
|
53
|
+
apiKey,
|
|
54
|
+
updatedAt: typeof input.updatedAt === 'string' ? input.updatedAt : new Date().toISOString(),
|
|
55
|
+
lastHealth: input.lastHealth && typeof input.lastHealth === 'object' ? input.lastHealth : null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function saveRemoteConnectionConfig(input = {}) {
|
|
60
|
+
const current = parseStoredConfig();
|
|
61
|
+
const normalized = normalizeRemoteConnectionConfig({
|
|
62
|
+
...current,
|
|
63
|
+
...input,
|
|
64
|
+
apiKey: input.apiKey === undefined ? current.apiKey : input.apiKey,
|
|
65
|
+
updatedAt: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
appConfigDb.set(CONFIG_KEY, JSON.stringify(normalized));
|
|
68
|
+
return getPublicRemoteConnectionConfig(normalized);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getRemoteConnectionConfig() {
|
|
72
|
+
return parseStoredConfig();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getPublicRemoteConnectionConfig(config = parseStoredConfig()) {
|
|
76
|
+
return {
|
|
77
|
+
mode: config.mode,
|
|
78
|
+
remoteUrl: config.remoteUrl,
|
|
79
|
+
apiKeyPresent: Boolean(config.apiKey),
|
|
80
|
+
updatedAt: config.updatedAt,
|
|
81
|
+
lastHealth: config.lastHealth,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function checkRemoteConnection(config = parseStoredConfig(), { timeoutMs = 5000 } = {}) {
|
|
86
|
+
const normalized = normalizeRemoteConnectionConfig(config);
|
|
87
|
+
if (normalized.mode !== 'remote') {
|
|
88
|
+
const localHealth = {
|
|
89
|
+
reachable: true,
|
|
90
|
+
checkedAt: new Date().toISOString(),
|
|
91
|
+
status: 'local',
|
|
92
|
+
message: 'Pixcode is running in local mode.',
|
|
93
|
+
};
|
|
94
|
+
saveRemoteConnectionConfig({ ...normalized, lastHealth: localHealth });
|
|
95
|
+
return localHealth;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
100
|
+
const checkedAt = new Date().toISOString();
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(`${normalized.remoteUrl}/api/auth/status`, {
|
|
103
|
+
signal: controller.signal,
|
|
104
|
+
headers: normalized.apiKey ? { 'X-API-Key': normalized.apiKey } : {},
|
|
105
|
+
});
|
|
106
|
+
const health = {
|
|
107
|
+
reachable: response.ok,
|
|
108
|
+
checkedAt,
|
|
109
|
+
status: response.ok ? 'ok' : 'http_error',
|
|
110
|
+
statusCode: response.status,
|
|
111
|
+
message: response.ok ? 'Remote Pixcode server is reachable.' : `Remote server returned HTTP ${response.status}.`,
|
|
112
|
+
};
|
|
113
|
+
saveRemoteConnectionConfig({ ...normalized, lastHealth: health });
|
|
114
|
+
return health;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const health = {
|
|
117
|
+
reachable: false,
|
|
118
|
+
checkedAt,
|
|
119
|
+
status: error?.name === 'AbortError' ? 'timeout' : 'network_error',
|
|
120
|
+
message: error?.message || 'Remote Pixcode server is unreachable.',
|
|
121
|
+
};
|
|
122
|
+
saveRemoteConnectionConfig({ ...normalized, lastHealth: health });
|
|
123
|
+
return health;
|
|
124
|
+
} finally {
|
|
125
|
+
clearTimeout(timeout);
|
|
126
|
+
}
|
|
127
|
+
}
|