@aiworker/web 1.22.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/bin/cli.js +14 -4
- package/bin/cli.test.js +165 -73
- package/bin/lib/cli-api-target.js +2 -2
- package/bin/lib/cli-args.js +119 -119
- package/bin/lib/cli-lifecycle.js +9 -9
- package/bin/lib/cli-network.js +3 -3
- package/bin/lib/cli-paths.js +3 -3
- package/bin/lib/cli-ports.js +2 -2
- package/bin/lib/cli-process.js +4 -4
- package/bin/lib/cli-startup.js +64 -15
- package/bin/lib/cli-tunnel-profiles.js +1 -1
- package/bin/lib/cli-tunnel-utils.js +2 -2
- package/bin/lib/commands-connect-url.js +19 -19
- package/bin/lib/commands-lifecycle.js +27 -27
- package/bin/lib/commands-logs.js +5 -5
- package/bin/lib/commands-models.js +1 -1
- package/bin/lib/commands-projects.js +1 -1
- package/bin/lib/commands-schedule.js +1 -1
- package/bin/lib/commands-serve.js +29 -29
- package/bin/lib/commands-session.js +1 -1
- package/bin/lib/commands-startup.js +45 -10
- package/bin/lib/commands-status.js +1 -1
- package/bin/lib/commands-tunnel.js +40 -40
- package/bin/lib/commands-update.js +1 -1
- package/bin/lib/commands-update.test.js +6 -6
- package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
- package/dist/assets/DiffView-DXjVsEOw.js +4 -0
- package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
- package/dist/assets/FilesView-CJdG0-wk.js +17 -0
- package/dist/assets/GitView-BXsS3jG6.js +6 -0
- package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
- package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
- package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
- package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
- package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
- package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
- package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
- package/dist/assets/PlanView-CJTmOF6I.js +5 -0
- package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
- package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
- package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
- package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
- package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
- package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
- package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
- package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
- package/dist/assets/de-DCrsQ5zS.js +10 -0
- package/dist/assets/es-Dwv4YfH6.js +16 -0
- package/dist/assets/fr-BCWg4Cnx.js +14 -0
- package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
- package/dist/assets/index-B68h5S5B.css +1 -0
- package/dist/assets/index-DVwMarHI.js +39 -0
- package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
- package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
- package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
- package/dist/assets/ja-Bruw3RYC.js +16 -0
- package/dist/assets/ko-idhXGF8j.js +16 -0
- package/dist/assets/main-BkyaQhJX.js +930 -0
- package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
- package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
- package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
- package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
- package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
- package/dist/assets/pl-CGZWUqZJ.js +16 -0
- package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
- package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
- package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
- package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
- package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
- package/dist/assets/surface-oMvhsog3.js +1 -0
- package/dist/assets/tr-BK_KiO2V.js +7 -0
- package/dist/assets/uk-CG74xV8N.js +16 -0
- package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
- package/dist/assets/useGitComparison-DH220WmO.js +1 -0
- package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
- package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
- package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
- package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
- package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
- package/dist/assets/zh-CN-T4AXofZI.js +16 -0
- package/dist/assets/zh-TW-Dte40sxp.js +16 -0
- package/dist/index.html +21 -59
- package/dist/mini-chat.html +7 -7
- package/dist/mobile.html +5 -5
- package/package.json +3 -4
- package/server/index.d.ts +15 -0
- package/server/index.js +158 -89
- package/server/lib/agent-memory/actions.js +2 -2
- package/server/lib/agent-memory/feature-flag.js +1 -1
- package/server/lib/agent-memory/feature-flag.test.js +8 -8
- package/server/lib/agent-memory/routes.js +2 -2
- package/server/lib/agent-tool/runtime.js +37 -54
- package/server/lib/agent-tool/runtime.test.js +59 -59
- package/server/lib/browser-control/broker.js +3 -3
- package/server/lib/browser-control/broker.test.js +1 -1
- package/server/lib/changelog/update-notes.js +82 -0
- package/server/lib/changelog/update-notes.test.js +59 -0
- package/server/lib/client-auth/remote-clients.test.js +5 -5
- package/server/lib/context-obligatory/runtime.js +7 -7
- package/server/lib/context-obligatory/runtime.test.js +5 -5
- package/server/lib/data-dir-migration.js +40 -0
- package/server/lib/data-dir-migration.test.js +59 -0
- package/server/lib/dev-servers/parse.js +2 -2
- package/server/lib/dev-servers/parse.test.js +2 -2
- package/server/lib/dev-servers/routes.js +1 -1
- package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
- package/server/lib/dev-tunnel/client.js +1 -1
- package/server/lib/dev-tunnel/runtime.js +5 -5
- package/server/lib/dev-tunnel/tunnel.test.js +21 -1
- package/server/lib/dictation/local/model-catalog.js +1 -1
- package/server/lib/dictation/local/worker-process.js +2 -2
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-ws-bridge.js +1 -1
- package/server/lib/event-stream/protocol.test.js +4 -4
- package/server/lib/event-stream/runtime.test.js +18 -18
- package/server/lib/fs/DOCUMENTATION.md +5 -0
- package/server/lib/fs/routes.js +68 -26
- package/server/lib/fs/routes.test.js +208 -22
- package/server/lib/git/DOCUMENTATION.md +32 -6
- package/server/lib/git/identity-storage.js +1 -1
- package/server/lib/git/issue-2746-longpaths.test.js +4 -4
- package/server/lib/git/routes.js +41 -5
- package/server/lib/git/service.js +300 -224
- package/server/lib/git/service.test.js +596 -28
- package/server/lib/github/auth.js +8 -8
- package/server/lib/github/pr-status.js +1 -1
- package/server/lib/github/routes.js +2 -2
- package/server/lib/inherited-env.js +17 -10
- package/server/lib/inherited-env.test.js +10 -11
- package/server/lib/linear/auth.js +8 -8
- package/server/lib/linear/auth.test.js +24 -24
- package/server/lib/linear/issues.test.js +6 -6
- package/server/lib/linear/mapping.test.js +4 -4
- package/server/lib/linear/oauth.js +2 -2
- package/server/lib/linear/oauth.test.js +14 -14
- package/server/lib/linear/routes.js +5 -5
- package/server/lib/linear/routes.test.js +16 -16
- package/server/lib/linear/status-runtime.test.js +9 -9
- package/server/lib/linear/status.js +1 -1
- package/server/lib/linear/status.test.js +15 -15
- package/server/lib/linear/teams.test.js +4 -4
- package/server/lib/magic-prompts/routes.js +2 -2
- package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
- package/server/lib/mcp-reconnect/runtime.js +122 -0
- package/server/lib/mcp-reconnect/runtime.test.js +140 -0
- package/server/lib/message-queue/DOCUMENTATION.md +161 -0
- package/server/lib/message-queue/runtime.js +870 -0
- package/server/lib/message-queue/runtime.test.js +482 -0
- package/server/lib/network-defaults.js +23 -0
- package/server/lib/network-defaults.test.js +52 -0
- package/server/lib/notifications/apns-runtime.js +17 -17
- package/server/lib/notifications/apns-runtime.test.js +11 -11
- package/server/lib/notifications/emitter-runtime.js +1 -1
- package/server/lib/notifications/emitter-runtime.test.js +1 -1
- package/server/lib/notifications/push-runtime.js +7 -7
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +1 -1
- package/server/lib/openchamber-control/action-resolution.test.js +10 -10
- package/server/lib/openchamber-control/actions.js +24 -24
- package/server/lib/openchamber-control/error.js +4 -4
- package/server/lib/openchamber-control/routes.js +2 -2
- package/server/lib/openchamber-control/routes.test.js +5 -5
- package/server/lib/openchamber-control/screenshots.js +1 -1
- package/server/lib/openchamber-control/screenshots.test.js +1 -1
- package/server/lib/openchamber-control/service.js +50 -50
- package/server/lib/openchamber-control/service.test.js +5 -5
- package/server/lib/openchamber-sessions/routes.js +124 -22
- package/server/lib/openchamber-sessions/routes.test.js +99 -11
- package/server/lib/opencode/DOCUMENTATION.md +30 -6
- package/server/lib/opencode/auth.js +1 -1
- package/server/lib/opencode/bootstrap-runtime.js +8 -6
- package/server/lib/opencode/cli-options.js +8 -8
- package/server/lib/opencode/config-paths.test.js +116 -0
- package/server/lib/opencode/core-routes.js +8 -7
- package/server/lib/opencode/core-routes.test.js +11 -11
- package/server/lib/opencode/env-config.js +5 -5
- package/server/lib/opencode/env-config.test.js +7 -7
- package/server/lib/opencode/env-runtime.js +10 -10
- package/server/lib/opencode/env-runtime.test.js +18 -18
- package/server/lib/opencode/feature-routes-runtime.js +19 -12
- package/server/lib/opencode/hmr-state-runtime.js +2 -2
- package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
- package/server/lib/opencode/input-history-scope.js +14 -0
- package/server/lib/opencode/lifecycle.js +8 -8
- package/server/lib/opencode/lifecycle.test.js +4 -4
- package/server/lib/opencode/managed-plugin-config.js +35 -0
- package/server/lib/opencode/managed-process-registry.js +3 -3
- package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
- package/server/lib/opencode/openchamber-routes.js +79 -19
- package/server/lib/opencode/openchamber-routes.test.js +139 -12
- package/server/lib/opencode/plugins.js +2 -2
- package/server/lib/opencode/plugins.test.js +1 -1
- package/server/lib/opencode/project-icon-routes.js +2 -2
- package/server/lib/opencode/project-icon-routes.test.js +1 -1
- package/server/lib/opencode/providers.js +44 -2
- package/server/lib/opencode/providers.test.js +131 -4
- package/server/lib/opencode/proxy.js +2 -2
- package/server/lib/opencode/pwa-manifest-routes.js +1 -1
- package/server/lib/opencode/restart-session-recovery.test.js +1 -1
- package/server/lib/opencode/routes-upgrade.test.js +4 -4
- package/server/lib/opencode/routes.js +19 -17
- package/server/lib/opencode/server-startup-runtime.js +6 -6
- package/server/lib/opencode/session-runtime.js +3 -3
- package/server/lib/opencode/session-runtime.test.js +6 -6
- package/server/lib/opencode/settings-files.js +214 -0
- package/server/lib/opencode/settings-helpers.js +116 -44
- package/server/lib/opencode/settings-helpers.test.js +354 -12
- package/server/lib/opencode/settings-registry.json +747 -0
- package/server/lib/opencode/settings-runtime.js +141 -20
- package/server/lib/opencode/settings-runtime.test.js +191 -4
- package/server/lib/opencode/shared.js +5 -2
- package/server/lib/opencode/shared.test.js +7 -7
- package/server/lib/opencode/shutdown-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +2 -1
- package/server/lib/opencode/snippets.js +1 -2
- package/server/lib/opencode/startup-performance.js +1 -1
- package/server/lib/opencode/startup-performance.test.js +6 -6
- package/server/lib/opencode/static-routes-runtime.js +8 -8
- package/server/lib/opencode/static-routes-runtime.test.js +7 -7
- package/server/lib/opencode/upgrade-capability.js +2 -2
- package/server/lib/opencode/upgrade-capability.test.js +3 -3
- package/server/lib/package-manager.js +17 -42
- package/server/lib/package-manager.test.js +19 -19
- package/server/lib/permission-auto-accept/runtime.js +1 -1
- package/server/lib/project-context/DOCUMENTATION.md +25 -1
- package/server/lib/project-context/routes.http.test.js +18 -0
- package/server/lib/project-context/routes.js +16 -1
- package/server/lib/project-context/runtime.js +248 -23
- package/server/lib/project-context/runtime.test.js +106 -3
- package/server/lib/projects/DOCUMENTATION.md +76 -0
- package/server/lib/projects/project-config.js +144 -11
- package/server/lib/projects/project-id.js +17 -0
- package/server/lib/projects/project-setup.js +409 -0
- package/server/lib/projects/project-setup.test.js +557 -0
- package/server/lib/projects/routes.js +59 -0
- package/server/lib/quota/DOCUMENTATION.md +40 -2
- package/server/lib/quota/credentials/providers.js +4 -0
- package/server/lib/quota/credentials/store.js +4 -4
- package/server/lib/quota/credentials/store.test.js +8 -8
- package/server/lib/quota/index.js +1 -0
- package/server/lib/quota/providers/cline-pass.js +125 -0
- package/server/lib/quota/providers/cline-pass.test.js +103 -0
- package/server/lib/quota/providers/exe-dev.js +75 -0
- package/server/lib/quota/providers/exe-dev.test.js +47 -0
- package/server/lib/quota/providers/hyper.js +118 -0
- package/server/lib/quota/providers/hyper.test.js +126 -0
- package/server/lib/quota/providers/index.js +22 -0
- package/server/lib/quota/providers/neuralwatt.js +3 -6
- package/server/lib/quota/providers/neuralwatt.test.js +7 -7
- package/server/lib/quota/providers/ollama-cloud.js +32 -1
- package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
- package/server/lib/quota/providers/opencode-go.js +3 -2
- package/server/lib/quota/providers/opencode-go.test.js +35 -6
- package/server/lib/quota/providers/openrouter.js +119 -24
- package/server/lib/quota/providers/openrouter.test.js +330 -0
- package/server/lib/quota/providers/xai.js +1 -1
- package/server/lib/quota/routes.js +2 -0
- package/server/lib/quota/utils/auth.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +56 -1
- package/server/lib/relay/cross-compat.test.js +26 -0
- package/server/lib/relay/downstream-scheduler.js +154 -0
- package/server/lib/relay/downstream-scheduler.test.js +162 -0
- package/server/lib/relay/e2ee.js +10 -8
- package/server/lib/relay/flow-control.test.js +177 -0
- package/server/lib/relay/host-client.js +38 -9
- package/server/lib/relay/host-client.test.js +9 -1
- package/server/lib/relay/host-lock.js +1 -1
- package/server/lib/relay/service.js +5 -5
- package/server/lib/relay/tunnel-codec.js +9 -0
- package/server/lib/relay/tunnel-host.js +69 -18
- package/server/lib/relay/tunnel-host.test.js +9 -1
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
- package/server/lib/scheduled-tasks/routes.js +5 -5
- package/server/lib/scheduled-tasks/service.js +25 -25
- package/server/lib/scheduled-tasks/service.test.js +1 -1
- package/server/lib/security/bind-host.js +4 -4
- package/server/lib/security/bind-host.test.js +1 -1
- package/server/lib/security/request-security.js +11 -5
- package/server/lib/security/request-security.test.js +14 -14
- package/server/lib/session-assist/runtime.js +15 -18
- package/server/lib/session-folders/routes.js +2 -2
- package/server/lib/session-folders/routes.test.js +8 -8
- package/server/lib/session-goal/DOCUMENTATION.md +20 -7
- package/server/lib/session-goal/create.js +4 -2
- package/server/lib/session-goal/create.test.js +1 -1
- package/server/lib/session-goal/objectives.js +4 -4
- package/server/lib/session-goal/routes.js +1 -1
- package/server/lib/session-goal/runtime.js +91 -30
- package/server/lib/session-goal/runtime.test.js +428 -2
- package/server/lib/session-knowledge/runtime.js +11 -11
- package/server/lib/session-knowledge/runtime.test.js +5 -5
- package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
- package/server/lib/skills-catalog/cache.test.js +2 -2
- package/server/lib/skills-catalog/disk-cache.js +5 -5
- package/server/lib/skills-catalog/github-meta.test.js +2 -2
- package/server/lib/skills-catalog/install.js +3 -2
- package/server/lib/small-model/DOCUMENTATION.md +7 -2
- package/server/lib/small-model/call.js +18 -7
- package/server/lib/small-model/call.test.js +58 -6
- package/server/lib/small-model/index.js +12 -16
- package/server/lib/small-model/index.test.js +1 -1
- package/server/lib/small-model/resolve.js +1 -1
- package/server/lib/small-model/resolve.test.js +1 -1
- package/server/lib/small-model/routes.js +2 -1
- package/server/lib/small-model/runtime-providers.js +15 -7
- package/server/lib/small-model/runtime-providers.test.js +7 -1
- package/server/lib/system-prompt/runtime.js +3 -22
- package/server/lib/system-prompt/runtime.test.js +3 -3
- package/server/lib/terminal/DOCUMENTATION.md +14 -10
- package/server/lib/terminal/runtime.js +190 -31
- package/server/lib/terminal/runtime.test.js +572 -38
- package/server/lib/terminal/shells.js +20 -3
- package/server/lib/terminal/shells.test.js +33 -2
- package/server/lib/tts/base-url.js +3 -3
- package/server/lib/tts/routes.test.js +20 -20
- package/server/lib/ui-auth/ui-auth.js +12 -9
- package/server/lib/ui-auth/ui-auth.test.js +19 -3
- package/server/lib/ui-auth/ui-passkeys.js +7 -7
- package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
- package/server/lib/walkthrough/index.js +1 -0
- package/server/lib/walkthrough/jobs.test.js +1 -1
- package/server/lib/walkthrough/language.test.js +1 -1
- package/server/lib/walkthrough/model-settings.js +11 -15
- package/server/lib/walkthrough/model-settings.test.js +1 -1
- package/server/lib/walkthrough/prompt.js +3 -1
- package/server/lib/walkthrough/pull-request.test.js +5 -5
- package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
- package/server/lib/walkthrough/routes.test.js +26 -5
- package/server/lib/walkthrough/sources.js +23 -2
- package/server/lib/walkthrough/store.js +3 -3
- package/server/lib/walkthrough/store.test.js +1 -1
- package/server/opencode-proxy.test.js +2 -2
- package/server/proxy-headers.js +1 -1
- package/server/sse-routes.test.js +4 -4
- package/README.md.bak +0 -250
- package/bin/lib/DOCUMENTATION.md.bak +0 -163
- package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
- package/dist/assets/DiffView-DluiRSla.js +0 -4
- package/dist/assets/FilesView-Bdq8ppva.js +0 -17
- package/dist/assets/GitView-Znn7VJcP.js +0 -6
- package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
- package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
- package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
- package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
- package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
- package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
- package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
- package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
- package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
- package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
- package/dist/assets/de-C1P6X5Rs.js +0 -9
- package/dist/assets/es-CDgaF1_t.js +0 -15
- package/dist/assets/fr-Bgxqvvz_.js +0 -13
- package/dist/assets/index-C57jT0Sa.css +0 -1
- package/dist/assets/index-DW_E3rnL.js +0 -7
- package/dist/assets/ja-BDRrEcNP.js +0 -15
- package/dist/assets/ko-Ba8sQV79.js +0 -15
- package/dist/assets/main-Bv5Pkngh.js +0 -928
- package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
- package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
- package/dist/assets/pl-Dioz29BI.js +0 -15
- package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
- package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
- package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
- package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
- package/dist/assets/tr-DFnMlPxY.js +0 -6
- package/dist/assets/uk-ZrCNjM7Y.js +0 -15
- package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
- package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
- package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
- package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
- package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
- package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
- package/dist/assets/zh-TW-CzEUk38j.js +0 -15
- package/server/index.d.ts.bak +0 -39
- package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
- package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
- package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
- package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
- package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
- package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
- package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
- package/server/lib/git/DOCUMENTATION.md.bak +0 -193
- package/server/lib/github/DOCUMENTATION.md.bak +0 -183
- package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
- package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
- package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
- package/server/lib/notifications/APNS.md.bak +0 -135
- package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
- package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
- package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
- package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
- package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
- package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
- package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
- package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
- package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
- package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
- package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
- package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
- package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
- package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
- package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
- package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
- package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
- package/server/lib/text/DOCUMENTATION.md.bak +0 -35
- package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
- package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
- package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
- package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
## Purpose
|
|
4
4
|
This module fetches quota and usage signals for supported providers in the web server runtime.
|
|
5
5
|
|
|
6
|
+
Node server entrypoints apply `../network-defaults.js` before serving requests,
|
|
7
|
+
including the daemon launched directly through `server/index.js`. Connection
|
|
8
|
+
attempts get 5 seconds without changing address-family selection. The VS Code
|
|
9
|
+
extension applies the same policy in its own process at activation.
|
|
10
|
+
|
|
6
11
|
## Entrypoints and structure
|
|
7
12
|
- `packages/web/server/lib/quota/index.js`: public entrypoint imported by `packages/web/server/index.js`.
|
|
8
13
|
- `packages/web/server/lib/quota/routes.js`: Express route registration for quota endpoints.
|
|
@@ -18,12 +23,15 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
|
|
|
18
23
|
| Provider ID | Display name | Module | Auth aliases/keys |
|
|
19
24
|
| --- | --- | --- | --- |
|
|
20
25
|
| `claude` | Claude | `providers/claude/` | Claude Code Keychain entry, Claude Code credentials file, OpenCode `auth.json` (`anthropic`, `claude`), `CLAUDE_CODE_OAUTH_TOKEN` |
|
|
26
|
+
| `cline-pass` | ClinePass | `providers/cline-pass.js` | `cline-pass` (API key under `key` or `token`) |
|
|
21
27
|
| `codex` | Codex | `providers/codex.js` | `openai`, `codex`, `chatgpt` |
|
|
22
28
|
| `command-code` | Command Code | `providers/command-code.js` | `command-code` OAuth/API credential in OpenCode `auth.json`, or `COMMAND_CODE_API_KEY` |
|
|
23
29
|
| `cursor` | Cursor | `providers/cursor.js` | Environment/token files, AiWorker-managed credentials, or explicit one-time Cursor import |
|
|
24
30
|
| `crof` | CrofAI | `providers/crof.js` | `crof` (API key under `key` or `token`) |
|
|
25
31
|
| `deepseek` | DeepSeek | `providers/deepseek.js` | `deepseek` (API key under `key` or `token`) |
|
|
32
|
+
| `exe-dev` | exe.dev | `providers/exe-dev.js` | Usage API token stored under `~/.config/openchamber/quota/` |
|
|
26
33
|
| `google` | Google | `providers/google/index.js` | `google`, `google.oauth`, Antigravity accounts file |
|
|
34
|
+
| `hyper` | Charm Hyper | `providers/hyper.js` | `hyper` (API key under `key` or `token`) |
|
|
27
35
|
| `github-copilot` | GitHub Copilot | `providers/copilot.js` | `github-copilot`, `copilot` |
|
|
28
36
|
| `github-copilot-addon` | GitHub Copilot Add-on | `providers/copilot.js` | `github-copilot`, `copilot` |
|
|
29
37
|
| `kimi-for-coding` | Kimi for Coding | `providers/kimi.js` | `kimi-for-coding`, `kimi` |
|
|
@@ -33,7 +41,7 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
|
|
|
33
41
|
| `zhipuai-coding-plan` | Zhipu AI Coding Plan | `providers/zhipuai-coding-plan.js` | `zhipuai-coding-plan`, `zhipuai`, `zhipu` |
|
|
34
42
|
| `minimax-coding-plan` | MiniMax Coding Plan (minimax.io) | `providers/minimax-coding-plan.js` / `providers/minimax-shared.js` | `minimax-coding-plan` |
|
|
35
43
|
| `minimax-cn-coding-plan` | MiniMax Coding Plan (minimaxi.com) | `providers/minimax-cn-coding-plan.js` / `providers/minimax-shared.js` | `minimax-cn-coding-plan` |
|
|
36
|
-
| `ollama-cloud` | Ollama Cloud | `providers/ollama-cloud.js` | Manual cookie stored under `~/.config/openchamber/quota/` |
|
|
44
|
+
| `ollama-cloud` | Ollama Cloud | `providers/ollama-cloud.js` | Manual cookie pasted into Settings (`aid=...; __Secure-session=...` from `ollama.com`), stored under `~/.config/openchamber/quota/` |
|
|
37
45
|
| `wafer` | Wafer.ai | `providers/wafer.js` | `wafer`, `wafer-ai`, `wafer_ai`, `wafer.ai` |
|
|
38
46
|
| `opencode-go` | OpenCode Go | `providers/opencode-go.js` | `opencode-go` API key from OpenCode `auth.json` |
|
|
39
47
|
| `neuralwatt` | NeuralWatt | `providers/neuralwatt.js` | `neuralwatt` (API key under `key` or `token`) |
|
|
@@ -51,7 +59,7 @@ All providers should return results via shared helpers to preserve API shape:
|
|
|
51
59
|
Provider modules must export `providerId`, `providerName`, `aliases`, `isConfigured(auth?)`, and `fetchQuota()`.
|
|
52
60
|
`fetchQuota()` should return a quota result with `usage.windows` keyed by window name (for example `5h`, `7d`, `daily`) and optional provider-specific `usage.models` data.
|
|
53
61
|
|
|
54
|
-
Ollama Cloud and Cursor credentials are explicitly managed through Settings. OpenCode Go usage uses `GET https://opencode.ai/zen/go/v1/usage` with the `opencode-go` API key from OpenCode `auth.json` as a bearer token. The server validates managed credentials before atomic `0600` writes and never returns secrets through its API. AiWorker never scans browser cookie stores or automatically reads Cursor storage; Cursor import is an explicit one-time user action and never modifies Cursor's database.
|
|
62
|
+
exe.dev, Ollama Cloud, and Cursor credentials are explicitly managed through Settings. exe.dev usage uses a separately generated HTTPS API token restricted to `billing credits usage` and aggregates every `exe-*` model provider into one monthly credit window. Generate the token with `ssh exe.dev "ssh-key generate-api-key --label=aiworker --exp=30d --cmds='billing credits usage'"`. OpenCode Go usage uses `GET https://opencode.ai/zen/go/v1/usage` with the `opencode-go` API key from OpenCode `auth.json` as a bearer token and the stable `x-opencode-session: openchamber-usage` workload id. The server validates managed credentials before atomic `0600` writes and never returns secrets through its API. AiWorker never scans browser cookie stores or automatically reads Cursor storage; Cursor import is an explicit one-time user action and never modifies Cursor's database.
|
|
55
63
|
|
|
56
64
|
Command Code usage resolves account scope through `GET /alpha/whoami`, then reads server-backed credit balances and five-hour/weekly limits from `GET /alpha/billing/credits?orgId=...`. Personal accounts return `org: null` and use `/alpha/billing/credits` without an `orgId`; organization accounts include their organization id. Web/Electron and VS Code read the standard `command-code` OpenCode auth entry (including OAuth `access`) or `COMMAND_CODE_API_KEY`; credentials remain in the owning runtime and are never returned to shared UI.
|
|
57
65
|
|
|
@@ -89,6 +97,22 @@ In 2025/2026 MiniMax rebranded "Coding Plan" to "Token Plan" alongside the M3 mo
|
|
|
89
97
|
- **model_remains array**: Now contains entries for multiple model categories (chat, speech, video, image). The provider selects the chat-model entry by matching `MiniMax-M*`, then `general`/`chat`/`text` by name, then any entry with a remaining percent.
|
|
90
98
|
- **Window status**: The `current_interval_status` and `current_weekly_status` fields indicate whether a window is active. Status `3` means the window is not applicable for the current plan tier (e.g. legacy plans without weekly limits). The provider omits inactive windows.
|
|
91
99
|
|
|
100
|
+
## ClinePass quota semantics
|
|
101
|
+
|
|
102
|
+
ClinePass reads `data.limits` from its usage-limits endpoint. Web/Electron and
|
|
103
|
+
VS Code accept only known window types with finite numeric or non-empty numeric
|
|
104
|
+
string percentages. Invalid windows are skipped independently; no usable windows
|
|
105
|
+
is a failed refresh, not zero usage. Both implementations choose a non-empty
|
|
106
|
+
`key`, then `token`, and expose auth/fetch dependencies for focused tests.
|
|
107
|
+
Saved UI provider-visibility lists remain authoritative; installations without a
|
|
108
|
+
saved list include ClinePass through the provider registry.
|
|
109
|
+
|
|
110
|
+
## Charm Hyper balance semantics
|
|
111
|
+
|
|
112
|
+
`GET https://hyper.charm.land/v1/credits` returns a team's current Hypercredit balance, not a percentage or reset timestamp. The [Hyper FAQ](https://hyper.charm.land/faq) defines one Hypercredit as $0.05. Both runtimes expose `credits_balance` in dollars and `credits` as a numeric label under the UI's localized window title. Keep English unit text out of that numeric label.
|
|
113
|
+
|
|
114
|
+
Web and VS Code accept finite numeric balances and non-empty numeric strings. Missing, blank, or malformed balances remain explicit failures; zero is valid. Credential lookup uses a non-empty string `key`, then `token`, so malformed or blank keys cannot mark the provider configured or hide a valid fallback token. Hyper fetchers accept `readAuth` and `fetchImpl` dependencies for tests without replacing filesystem or auth modules.
|
|
115
|
+
|
|
92
116
|
## Kimi for Coding field semantics
|
|
93
117
|
|
|
94
118
|
`GET https://api.kimi.com/coding/v1/usages` is inconsistent about which field carries consumption:
|
|
@@ -97,6 +121,10 @@ In 2025/2026 MiniMax rebranded "Coding Plan" to "Token Plan" alongside the M3 mo
|
|
|
97
121
|
|
|
98
122
|
The provider computes `usedPercent` from whichever of `used`/`remaining` is present (`used` takes precedence when both exist) rather than assuming one field name. Both `packages/web/server/lib/quota/providers/kimi.js` and `packages/vscode/src/quotaProviders.ts` (`fetchKimiQuota`) must stay in sync — the VS Code extension duplicates this parsing logic rather than importing it.
|
|
99
123
|
|
|
124
|
+
## Ollama Cloud settings-page shapes
|
|
125
|
+
|
|
126
|
+
Ollama Cloud authentication uses two cookies (`aid` and `__Secure-session`) pasted together as one single-line Cookie header value. Ollama serves two different `/settings` page shapes and `parseOllamaSettingsHtml` supports both: session/weekly/premium-interaction windows (percent-based plans) and a `monthly` window derived from "Monthly usage: $X of $Y used" (cost-based plans) with a symmetric `$X / $Y` money `valueLabel` that reads correctly in both used/remaining display modes. The "Extra usage" credits block is surfaced as a balance-only `credits_balance` window with a plain money `valueLabel` when present, matching the Codex/DeepSeek credits treatment ("Credits Balance" in the UI); a $0 balance is omitted instead of showing an empty credits row. Keep `packages/web/server/lib/quota/providers/ollama-cloud.js` and `packages/vscode/src/quotaProviders.ts` (`parseOllamaSettingsHtml`) in sync — the VS Code extension duplicates this parsing logic rather than importing it.
|
|
127
|
+
|
|
100
128
|
## GitHub Copilot quota semantics
|
|
101
129
|
|
|
102
130
|
GitHub Copilot usage exposes only the `premium_interactions` snapshot as the
|
|
@@ -116,6 +144,16 @@ snapshot carries `entitlement`, `remaining`, `unlimited`, and
|
|
|
116
144
|
- When entitlement/remaining are unusable, fall back to `100 - percent_remaining`.
|
|
117
145
|
- Snapshots other than `premium_interactions` (legacy annual plans) yield zero windows.
|
|
118
146
|
|
|
147
|
+
## OpenRouter key semantics
|
|
148
|
+
|
|
149
|
+
OpenRouter quota reads `GET https://openrouter.ai/api/v1/key`, which is documented as callable with any valid API key. `GET /api/v1/credits` is documented as "Management key required" and is not used. Calling `/credits` with a normal inference key has been observed to return HTTP 200 with `{total_credits:0, total_usage:0}` rather than an error; this behavior is not documented and is why the old implementation silently rendered "$0.00 left · $0.00 spent". A `/credits` fallback for unlimited keys would render the same zeros, so unlimited keys report `usage_monthly` instead.
|
|
150
|
+
|
|
151
|
+
The documented `limit`, `limit_remaining`, and `limit_reset` fields are present and null on unlimited keys; null means unlimited, never missing data. For a limited key, window usage is `limit - limit_remaining`, not `usage`: `usage` is all-time and measures a different axis from the current reset window. Pairing `usage` with the current limit produces a wrong number. `limit_remaining` is server-computed and already honors `include_byok_in_limit`, so `byok_*` fields are ignored.
|
|
152
|
+
|
|
153
|
+
Unlimited keys report `usage_monthly` in a `monthly` window with no percent. `limit_reset` is a period string (`daily`, `weekly`, `monthly`, or null), not a timestamp; `resetAt` is derived from the documented midnight-UTC boundaries, with weeks starting Monday. A set `limit` with a null `limit_reset` is a lifetime cap and maps to the `credits` window with no reset.
|
|
154
|
+
|
|
155
|
+
Keep `packages/web/server/lib/quota/providers/openrouter.js` and `packages/vscode/src/quotaProviders.ts` in sync, as with the Kimi and Copilot providers; the VS Code extension duplicates this parsing logic rather than importing the web provider.
|
|
156
|
+
|
|
119
157
|
## Notes for contributors
|
|
120
158
|
- Keep provider IDs stable; clients use them directly.
|
|
121
159
|
- Avoid adding alias-based dispatch in `fetchQuotaForProvider`; dispatch currently expects exact provider IDs.
|
|
@@ -3,6 +3,10 @@ import { deleteQuotaCredential, readQuotaCredential, writeQuotaCredential } from
|
|
|
3
3
|
const clean = (value) => typeof value === 'string' && !/[\r\n]/.test(value) ? value.trim() : '';
|
|
4
4
|
|
|
5
5
|
export const normalizers = {
|
|
6
|
+
'exe-dev': (value) => {
|
|
7
|
+
const usageToken = clean(value?.usageToken);
|
|
8
|
+
return usageToken ? { usageToken } : null;
|
|
9
|
+
},
|
|
6
10
|
'ollama-cloud': (value) => {
|
|
7
11
|
const cookie = clean(value?.cookie);
|
|
8
12
|
return cookie ? { cookie } : null;
|
|
@@ -2,12 +2,12 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
const MANAGED_QUOTA_PROVIDERS = new Set(['ollama-cloud', 'cursor']);
|
|
5
|
+
const MANAGED_QUOTA_PROVIDERS = new Set(['exe-dev', 'ollama-cloud', 'cursor']);
|
|
6
6
|
|
|
7
7
|
const credentialsDirectory = () => path.join(
|
|
8
|
-
process.env.
|
|
9
|
-
? path.resolve(process.env.
|
|
10
|
-
: path.join(os.homedir(), '.config', '
|
|
8
|
+
process.env.AIWORKER_DATA_DIR
|
|
9
|
+
? path.resolve(process.env.AIWORKER_DATA_DIR)
|
|
10
|
+
: path.join(os.homedir(), '.config', 'aiworker'),
|
|
11
11
|
'quota',
|
|
12
12
|
);
|
|
13
13
|
|
|
@@ -4,18 +4,18 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { deleteLegacyOpenCodeGoCredential, deleteQuotaCredential, readQuotaCredential, writeQuotaCredential } from './store.js';
|
|
6
6
|
|
|
7
|
-
const previousDataDir = process.env.
|
|
7
|
+
const previousDataDir = process.env.AIWORKER_DATA_DIR;
|
|
8
8
|
const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-quota-store-'));
|
|
9
|
-
process.env.
|
|
9
|
+
process.env.AIWORKER_DATA_DIR = temporaryDirectory;
|
|
10
10
|
|
|
11
11
|
describe('quota credential store', () => {
|
|
12
12
|
it('uses owner-only permissions and rejects arbitrary provider paths', () => {
|
|
13
|
-
writeQuotaCredential('
|
|
13
|
+
writeQuotaCredential('exe-dev', { usageToken: 'secret' });
|
|
14
14
|
expect(fs.statSync(path.join(temporaryDirectory, 'quota')).mode & 0o777).toBe(0o700);
|
|
15
|
-
expect(fs.statSync(path.join(temporaryDirectory, 'quota', '
|
|
16
|
-
expect(readQuotaCredential('
|
|
15
|
+
expect(fs.statSync(path.join(temporaryDirectory, 'quota', 'exe-dev.json')).mode & 0o777).toBe(0o600);
|
|
16
|
+
expect(readQuotaCredential('exe-dev', (value) => value)).toEqual({ usageToken: 'secret' });
|
|
17
17
|
expect(() => writeQuotaCredential('../escape', {})).toThrow('Unsupported credential provider');
|
|
18
|
-
deleteQuotaCredential('
|
|
18
|
+
deleteQuotaCredential('exe-dev');
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it('removes the obsolete OpenCode Go credential without parsing it', () => {
|
|
@@ -28,7 +28,7 @@ describe('quota credential store', () => {
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
afterAll(() => {
|
|
31
|
-
if (previousDataDir === undefined) delete process.env.
|
|
32
|
-
else process.env.
|
|
31
|
+
if (previousDataDir === undefined) delete process.env.AIWORKER_DATA_DIR;
|
|
32
|
+
else process.env.AIWORKER_DATA_DIR = previousDataDir;
|
|
33
33
|
fs.rmSync(temporaryDirectory, { recursive: true, force: true });
|
|
34
34
|
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode/auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
asObject,
|
|
6
|
+
asNonEmptyString,
|
|
7
|
+
buildResult,
|
|
8
|
+
toUsageWindow,
|
|
9
|
+
toNumber,
|
|
10
|
+
toTimestamp
|
|
11
|
+
} from '../utils/index.js';
|
|
12
|
+
|
|
13
|
+
export const providerId = 'cline-pass';
|
|
14
|
+
export const providerName = 'ClinePass';
|
|
15
|
+
export const aliases = ['cline-pass'];
|
|
16
|
+
const CLINE_USAGE_URL = 'https://api.cline.bot/api/v1/users/me/plan/usage-limits';
|
|
17
|
+
|
|
18
|
+
// Cline reports a rolling five-hour window, a rolling weekly window, and a
|
|
19
|
+
// calendar-month limit. Each window carries its duration so consumers can rank
|
|
20
|
+
// limits by how soon they run out; the calendar month has no fixed duration.
|
|
21
|
+
const WINDOW_KINDS = new Map([
|
|
22
|
+
['five_hour', { key: '5h', windowSeconds: 5 * 60 * 60 }],
|
|
23
|
+
['weekly', { key: 'weekly', windowSeconds: 7 * 24 * 60 * 60 }],
|
|
24
|
+
['monthly', { key: 'monthly', windowSeconds: null }]
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const getApiKey = (auth) => {
|
|
28
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
29
|
+
return asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const isConfigured = (auth = readAuthFile()) => Boolean(getApiKey(auth));
|
|
33
|
+
|
|
34
|
+
export const fetchQuota = async ({ readAuth = readAuthFile, fetchImpl = fetch } = {}) => {
|
|
35
|
+
const apiKey = getApiKey(readAuth());
|
|
36
|
+
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
return buildResult({
|
|
39
|
+
providerId,
|
|
40
|
+
providerName,
|
|
41
|
+
ok: false,
|
|
42
|
+
configured: false,
|
|
43
|
+
error: 'Not configured'
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const timeoutSignal = AbortSignal.timeout(15_000);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetchImpl(CLINE_USAGE_URL, {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: `Bearer ${apiKey}`,
|
|
54
|
+
'Accept-Encoding': 'identity'
|
|
55
|
+
},
|
|
56
|
+
signal: timeoutSignal
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
return buildResult({
|
|
61
|
+
providerId,
|
|
62
|
+
providerName,
|
|
63
|
+
ok: false,
|
|
64
|
+
configured: true,
|
|
65
|
+
error: response.status === 401
|
|
66
|
+
? 'Session expired — please re-authenticate with ClinePass'
|
|
67
|
+
: `API error: ${response.status}`
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const payload = asObject(await response.json());
|
|
72
|
+
const data = asObject(payload?.data);
|
|
73
|
+
const limits = Array.isArray(data?.limits) ? data.limits : [];
|
|
74
|
+
|
|
75
|
+
const windows = {};
|
|
76
|
+
for (const item of limits) {
|
|
77
|
+
const limit = asObject(item);
|
|
78
|
+
if (!limit) continue;
|
|
79
|
+
const kind = WINDOW_KINDS.get(asNonEmptyString(limit.type));
|
|
80
|
+
if (!kind) continue;
|
|
81
|
+
const usedPercent = toNumber(asNonEmptyString(limit.percentUsed)
|
|
82
|
+
?? (Number.isFinite(limit.percentUsed) ? limit.percentUsed : null));
|
|
83
|
+
if (usedPercent === null) continue;
|
|
84
|
+
windows[kind.key] = toUsageWindow({
|
|
85
|
+
usedPercent,
|
|
86
|
+
windowSeconds: kind.windowSeconds,
|
|
87
|
+
resetAt: toTimestamp(limit.resetsAt)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (Object.keys(windows).length === 0) {
|
|
92
|
+
return buildResult({
|
|
93
|
+
providerId,
|
|
94
|
+
providerName,
|
|
95
|
+
ok: false,
|
|
96
|
+
configured: true,
|
|
97
|
+
error: 'No quota data in response'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return buildResult({
|
|
102
|
+
providerId,
|
|
103
|
+
providerName,
|
|
104
|
+
ok: true,
|
|
105
|
+
configured: true,
|
|
106
|
+
usage: { windows }
|
|
107
|
+
});
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const isTimeout = error instanceof DOMException && (
|
|
110
|
+
error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted)
|
|
111
|
+
);
|
|
112
|
+
const isParseError = error instanceof SyntaxError;
|
|
113
|
+
return buildResult({
|
|
114
|
+
providerId,
|
|
115
|
+
providerName,
|
|
116
|
+
ok: false,
|
|
117
|
+
configured: true,
|
|
118
|
+
error: isTimeout
|
|
119
|
+
? 'Request timed out'
|
|
120
|
+
: isParseError
|
|
121
|
+
? 'Invalid response from provider'
|
|
122
|
+
: (error instanceof Error ? error.message : 'Request failed')
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { fetchQuota, isConfigured } from './cline-pass.js';
|
|
3
|
+
|
|
4
|
+
const readAuth = () => ({ 'cline-pass': { key: 'test-token' } });
|
|
5
|
+
|
|
6
|
+
// Response shape verified by the contributor against the live ClinePass API.
|
|
7
|
+
const documentedPayload = {
|
|
8
|
+
data: { limits: [
|
|
9
|
+
{ type: 'five_hour', percentUsed: 43, resetsAt: '2026-09-08T17:00:44.598174595Z' },
|
|
10
|
+
{ type: 'weekly', percentUsed: 17 },
|
|
11
|
+
{ type: 'monthly', percentUsed: 8 },
|
|
12
|
+
] },
|
|
13
|
+
success: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('ClinePass quota provider', () => {
|
|
17
|
+
it('maps the documented windows and sends credentials only in headers', async () => {
|
|
18
|
+
const result = await fetchQuota({ readAuth, fetchImpl: async (url, options) => {
|
|
19
|
+
expect(url).toBe('https://api.cline.bot/api/v1/users/me/plan/usage-limits');
|
|
20
|
+
expect(new Headers(options.headers).get('Authorization')).toBe('Bearer test-token');
|
|
21
|
+
expect(options.signal).toBeInstanceOf(AbortSignal);
|
|
22
|
+
return Response.json(documentedPayload);
|
|
23
|
+
} });
|
|
24
|
+
expect(result.ok).toBe(true);
|
|
25
|
+
expect(result.providerId).toBe('cline-pass');
|
|
26
|
+
expect(Object.keys(result.usage.windows)).toEqual(['5h', 'weekly', 'monthly']);
|
|
27
|
+
expect(result.usage.windows['5h'].usedPercent).toBe(43);
|
|
28
|
+
expect(result.usage.windows['5h'].remainingPercent).toBe(57);
|
|
29
|
+
expect(result.usage.windows['5h'].windowSeconds).toBe(18_000);
|
|
30
|
+
expect(result.usage.windows['5h'].resetAt).toBe(Date.parse('2026-09-08T17:00:44.598174595Z'));
|
|
31
|
+
expect(result.usage.windows.weekly.usedPercent).toBe(17);
|
|
32
|
+
expect(result.usage.windows.weekly.windowSeconds).toBe(604_800);
|
|
33
|
+
expect(result.usage.windows.monthly.usedPercent).toBe(8);
|
|
34
|
+
expect(result.usage.windows.monthly.windowSeconds).toBeNull();
|
|
35
|
+
expect(JSON.stringify(result)).not.toContain('test-token');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it.each([0, '0', '51'])('accepts finite percentage %s', async (percentUsed) => {
|
|
39
|
+
const result = await fetchQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [{ type: 'weekly', percentUsed }] } }) });
|
|
40
|
+
expect(result.ok).toBe(true);
|
|
41
|
+
expect(result.usage.windows.weekly.usedPercent).toBe(Number(percentUsed));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it.each([
|
|
45
|
+
{ type: 'constructor', percentUsed: 5 }, { type: 'toString', percentUsed: 5 },
|
|
46
|
+
{ type: '__proto__', percentUsed: 5 }, { type: 'quarterly', percentUsed: 5 },
|
|
47
|
+
{ type: 'weekly', percentUsed: '' }, { type: 'weekly', percentUsed: ' ' },
|
|
48
|
+
{ type: 'weekly', percentUsed: null }, { type: 'weekly', percentUsed: true },
|
|
49
|
+
{ type: 'weekly', percentUsed: 'NaN' }, null,
|
|
50
|
+
])('ignores malformed windows without discarding valid siblings: %j', async (limit) => {
|
|
51
|
+
const invalid = await fetchQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [limit] } }) });
|
|
52
|
+
expect(invalid.ok).toBe(false);
|
|
53
|
+
expect(invalid.configured).toBe(true);
|
|
54
|
+
expect(invalid.usage).toBeNull();
|
|
55
|
+
const mixed = await fetchQuota({ readAuth, fetchImpl: async () => Response.json({ data: { limits: [limit, { type: 'monthly', percentUsed: 8 }] } }) });
|
|
56
|
+
expect(mixed.ok).toBe(true);
|
|
57
|
+
expect(Object.keys(mixed.usage.windows)).toEqual(['monthly']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it.each([null, [], {}, { data: null }, { data: { limits: [] } }])('rejects empty or malformed payload %j', async (payload) => {
|
|
61
|
+
const result = await fetchQuota({ readAuth, fetchImpl: async () => Response.json(payload) });
|
|
62
|
+
expect(result.ok).toBe(false);
|
|
63
|
+
expect(result.usage).toBeNull();
|
|
64
|
+
expect(result.error).toBe('No quota data in response');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it.each([{ key: '' }, { key: ' ' }, { key: 42 }, {}])('uses a usable token when the key is malformed: %j', async (entry) => {
|
|
68
|
+
const auth = { 'cline-pass': { ...entry, token: 'test-token' } };
|
|
69
|
+
expect(isConfigured(auth)).toBe(true);
|
|
70
|
+
const result = await fetchQuota({ readAuth: () => auth, fetchImpl: async (_url, options) => {
|
|
71
|
+
expect(new Headers(options.headers).get('Authorization')).toBe('Bearer test-token');
|
|
72
|
+
return Response.json(documentedPayload);
|
|
73
|
+
} });
|
|
74
|
+
expect(result.ok).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it.each([{}, { 'cline-pass': { key: '' } }, { 'cline-pass': { key: 42 } }])('does not fetch without usable credentials: %j', async (auth) => {
|
|
78
|
+
expect(isConfigured(auth)).toBe(false);
|
|
79
|
+
const result = await fetchQuota({ readAuth: () => auth, fetchImpl: async () => { throw new Error('Unexpected fetch'); } });
|
|
80
|
+
expect(result.configured).toBe(false);
|
|
81
|
+
expect(result.error).toBe('Not configured');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it.each([[401, 'Session expired — please re-authenticate with ClinePass'], [503, 'API error: 503']])('reports HTTP %s', async (status, error) => {
|
|
85
|
+
const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response(null, { status }) });
|
|
86
|
+
expect(result.ok).toBe(false);
|
|
87
|
+
expect(result.usage).toBeNull();
|
|
88
|
+
expect(result.error).toBe(error);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('reports invalid JSON', async () => {
|
|
92
|
+
const result = await fetchQuota({ readAuth, fetchImpl: async () => new Response('{') });
|
|
93
|
+
expect(result.error).toBe('Invalid response from provider');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('recognizes the timeout exception from AbortSignal.timeout', async () => {
|
|
97
|
+
const result = await fetchQuota({ readAuth, fetchImpl: async () => { throw new DOMException('Timed out', 'TimeoutError'); } });
|
|
98
|
+
expect(result.ok).toBe(false);
|
|
99
|
+
expect(result.configured).toBe(true);
|
|
100
|
+
expect(result.usage).toBeNull();
|
|
101
|
+
expect(result.error).toBe('Request timed out');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readManagedCredential } from '../credentials/providers.js';
|
|
2
|
+
import { asObject, buildResult, formatMoney, toNumber, toTimestamp, toUsageWindow } from '../utils/index.js';
|
|
3
|
+
|
|
4
|
+
export const providerId = 'exe-dev';
|
|
5
|
+
export const providerName = 'exe.dev';
|
|
6
|
+
export const aliases = ['exe-dev'];
|
|
7
|
+
const EXEC_URL = 'https://exe.dev/exec';
|
|
8
|
+
const USAGE_COMMAND = 'billing credits usage --group=day --json';
|
|
9
|
+
|
|
10
|
+
export const parseExeDevUsage = (payload) => {
|
|
11
|
+
const data = asObject(payload);
|
|
12
|
+
if (!data) return null;
|
|
13
|
+
|
|
14
|
+
const totalCost = toNumber(data.total_cost_usd);
|
|
15
|
+
const monthlyAllowance = toNumber(data.monthly_allowance_usd);
|
|
16
|
+
const resetAt = toTimestamp(data.period_end);
|
|
17
|
+
if (totalCost === null || monthlyAllowance === null || monthlyAllowance < 0 || resetAt === null) return null;
|
|
18
|
+
|
|
19
|
+
const usedPercent = monthlyAllowance > 0
|
|
20
|
+
? Math.min(100, Math.max(0, (totalCost / monthlyAllowance) * 100))
|
|
21
|
+
: null;
|
|
22
|
+
const spent = formatMoney(totalCost);
|
|
23
|
+
const allowance = formatMoney(monthlyAllowance);
|
|
24
|
+
if (spent === null || allowance === null) return null;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
monthly: toUsageWindow({
|
|
28
|
+
usedPercent,
|
|
29
|
+
windowSeconds: null,
|
|
30
|
+
resetAt,
|
|
31
|
+
valueLabel: `$${spent} / $${allowance}`,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const fetchExeDevUsage = async (credential, fetchImpl = fetch) => {
|
|
37
|
+
const response = await fetchImpl(EXEC_URL, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
Accept: 'application/json',
|
|
41
|
+
Authorization: `Bearer ${credential.usageToken}`,
|
|
42
|
+
'Content-Type': 'text/plain',
|
|
43
|
+
'User-Agent': 'AiWorker quota provider',
|
|
44
|
+
},
|
|
45
|
+
body: USAGE_COMMAND,
|
|
46
|
+
signal: AbortSignal.timeout(15_000),
|
|
47
|
+
});
|
|
48
|
+
if (response.status === 401 || response.status === 403) throw new Error('exe.dev authentication failed');
|
|
49
|
+
if (!response.ok) throw new Error(`exe.dev usage API returned HTTP ${response.status}`);
|
|
50
|
+
const windows = parseExeDevUsage(await response.json().catch(() => null));
|
|
51
|
+
if (!windows) throw new Error('exe.dev usage data could not be parsed');
|
|
52
|
+
return windows;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const isConfigured = () => Boolean(readManagedCredential(providerId));
|
|
56
|
+
|
|
57
|
+
export const fetchQuota = async () => {
|
|
58
|
+
const credential = readManagedCredential(providerId);
|
|
59
|
+
if (!credential) {
|
|
60
|
+
return buildResult({ providerId, providerName, ok: false, configured: false, error: 'Not configured' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const windows = await fetchExeDevUsage(credential);
|
|
65
|
+
return buildResult({ providerId, providerName, ok: true, configured: true, usage: { windows } });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return buildResult({
|
|
68
|
+
providerId,
|
|
69
|
+
providerName,
|
|
70
|
+
ok: false,
|
|
71
|
+
configured: true,
|
|
72
|
+
error: error instanceof Error ? error.message : 'Request failed',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { fetchExeDevUsage, parseExeDevUsage } from './exe-dev.js';
|
|
3
|
+
|
|
4
|
+
const payload = {
|
|
5
|
+
allowance_spend_usd: 0.11,
|
|
6
|
+
extra_spend_usd: 0,
|
|
7
|
+
group: 'day',
|
|
8
|
+
month: '2026-09',
|
|
9
|
+
monthly_allowance_usd: 20,
|
|
10
|
+
period_end: '2026-10-01T00:00:00Z',
|
|
11
|
+
period_start: '2026-09-01T00:00:00Z',
|
|
12
|
+
total_cost_usd: 0.11,
|
|
13
|
+
total_requests: 17,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('exe.dev quota provider', () => {
|
|
17
|
+
it('parses monthly credit usage', () => {
|
|
18
|
+
const windows = parseExeDevUsage(payload);
|
|
19
|
+
expect(windows?.monthly.usedPercent).toBeCloseTo(0.55);
|
|
20
|
+
expect(windows?.monthly.valueLabel).toBe('$0.11 / $20.00');
|
|
21
|
+
expect(windows?.monthly.resetAt).toBe(Date.parse('2026-10-01T00:00:00Z'));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('sends the scoped billing command without exposing the token elsewhere', async () => {
|
|
25
|
+
const requests = [];
|
|
26
|
+
const windows = await fetchExeDevUsage({ usageToken: 'test-token' }, async (url, init) => {
|
|
27
|
+
requests.push({ url, init });
|
|
28
|
+
return Response.json(payload);
|
|
29
|
+
});
|
|
30
|
+
expect(windows.monthly.valueLabel).toBe('$0.11 / $20.00');
|
|
31
|
+
expect(requests).toHaveLength(1);
|
|
32
|
+
expect(requests[0].url).toBe('https://exe.dev/exec');
|
|
33
|
+
expect(requests[0].init.method).toBe('POST');
|
|
34
|
+
expect(requests[0].init.body).toBe('billing credits usage --group=day --json');
|
|
35
|
+
expect(requests[0].init.headers.Authorization).toBe('Bearer test-token');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rejects malformed successful responses', async () => {
|
|
39
|
+
await expect(fetchExeDevUsage({ usageToken: 'test-token' }, async () => Response.json({})))
|
|
40
|
+
.rejects.toThrow('could not be parsed');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('reports authentication failure without including the token', async () => {
|
|
44
|
+
await expect(fetchExeDevUsage({ usageToken: 'test-token' }, async () => new Response('', { status: 401 })))
|
|
45
|
+
.rejects.toThrow('exe.dev authentication failed');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode/auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
formatMoney,
|
|
9
|
+
asObject,
|
|
10
|
+
asNonEmptyString
|
|
11
|
+
} from '../utils/index.js';
|
|
12
|
+
|
|
13
|
+
export const providerId = 'hyper';
|
|
14
|
+
export const providerName = 'Charm Hyper';
|
|
15
|
+
export const aliases = ['hyper'];
|
|
16
|
+
const HYPER_QUOTA_URL = 'https://hyper.charm.land/v1/credits';
|
|
17
|
+
const CREDIT_TO_USD = 0.05;
|
|
18
|
+
|
|
19
|
+
const getApiKey = (auth) => {
|
|
20
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
21
|
+
return asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const isConfigured = (auth = readAuthFile()) => Boolean(getApiKey(auth));
|
|
25
|
+
|
|
26
|
+
export const fetchQuota = async ({ readAuth = readAuthFile, fetchImpl = fetch } = {}) => {
|
|
27
|
+
const apiKey = getApiKey(readAuth());
|
|
28
|
+
|
|
29
|
+
if (!apiKey) {
|
|
30
|
+
return buildResult({
|
|
31
|
+
providerId,
|
|
32
|
+
providerName,
|
|
33
|
+
ok: false,
|
|
34
|
+
configured: false,
|
|
35
|
+
error: 'Not configured'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const timeoutSignal = AbortSignal.timeout(15_000);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetchImpl(HYPER_QUOTA_URL, {
|
|
43
|
+
method: 'GET',
|
|
44
|
+
headers: {
|
|
45
|
+
Authorization: `Bearer ${apiKey}`,
|
|
46
|
+
'Accept-Encoding': 'identity'
|
|
47
|
+
},
|
|
48
|
+
signal: timeoutSignal
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
return buildResult({
|
|
53
|
+
providerId,
|
|
54
|
+
providerName,
|
|
55
|
+
ok: false,
|
|
56
|
+
configured: true,
|
|
57
|
+
error: response.status === 401 || response.status === 403
|
|
58
|
+
? 'Session expired — please re-authenticate with Charm Hyper'
|
|
59
|
+
: `API error: ${response.status}`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const payload = asObject(await response.json());
|
|
64
|
+
const rawBalance = payload?.balance;
|
|
65
|
+
const balance = toNumber(asNonEmptyString(rawBalance)
|
|
66
|
+
?? (Number.isFinite(rawBalance) ? rawBalance : null));
|
|
67
|
+
|
|
68
|
+
if (balance === null) {
|
|
69
|
+
return buildResult({
|
|
70
|
+
providerId,
|
|
71
|
+
providerName,
|
|
72
|
+
ok: false,
|
|
73
|
+
configured: true,
|
|
74
|
+
error: 'No quota data in response'
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const creditsLabel = Number.isInteger(balance) ? String(balance) : formatMoney(balance);
|
|
79
|
+
const windows = {
|
|
80
|
+
credits_balance: toUsageWindow({
|
|
81
|
+
usedPercent: null,
|
|
82
|
+
windowSeconds: null,
|
|
83
|
+
resetAt: null,
|
|
84
|
+
valueLabel: `$${formatMoney(balance * CREDIT_TO_USD)}`
|
|
85
|
+
}),
|
|
86
|
+
credits: toUsageWindow({
|
|
87
|
+
usedPercent: null,
|
|
88
|
+
windowSeconds: null,
|
|
89
|
+
resetAt: null,
|
|
90
|
+
valueLabel: creditsLabel
|
|
91
|
+
})
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return buildResult({
|
|
95
|
+
providerId,
|
|
96
|
+
providerName,
|
|
97
|
+
ok: true,
|
|
98
|
+
configured: true,
|
|
99
|
+
usage: { windows }
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const isTimeout = error instanceof DOMException && (
|
|
103
|
+
error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted)
|
|
104
|
+
);
|
|
105
|
+
const isParseError = error instanceof SyntaxError;
|
|
106
|
+
return buildResult({
|
|
107
|
+
providerId,
|
|
108
|
+
providerName,
|
|
109
|
+
ok: false,
|
|
110
|
+
configured: true,
|
|
111
|
+
error: isTimeout
|
|
112
|
+
? 'Request timed out'
|
|
113
|
+
: isParseError
|
|
114
|
+
? 'Invalid response from provider'
|
|
115
|
+
: (error instanceof Error ? error.message : 'Request failed')
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|