specrails-desktop 2.58.1 → 2.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/client/dist/assets/{ActivityFeedPage-BQGppIR0.js → ActivityFeedPage-vAY9C71W.js} +1 -1
- package/client/dist/assets/AgentBrowserCapture-CpwjGXQm.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-BHeoJVxQ.js +2 -0
- package/client/dist/assets/AgentModeCodePane-B0Nf3YkN.js +2 -0
- package/client/dist/assets/{AgentModeJobsPane-DR_06oqH.js → AgentModeJobsPane-CpbIItY_.js} +1 -1
- package/client/dist/assets/{AgentsPage-BKIKjm_1.js → AgentsPage-BVJAFXn3.js} +3 -3
- package/client/dist/assets/{AnalyticsPage-069npRVo.js → AnalyticsPage-DDWees-K.js} +1 -1
- package/client/dist/assets/CodePage-CzV5tkvS.js +3 -0
- package/client/dist/assets/{DesktopAnalyticsPage-qtWAPM47.js → DesktopAnalyticsPage-DfRHNK7i.js} +1 -1
- package/client/dist/assets/DocsDialog-BxduBYbh.js +11 -0
- package/client/dist/assets/DocsPage-DOVeRJn_.js +11 -0
- package/client/dist/assets/{ExportDropdown-BcMpvV89.js → ExportDropdown-BnKVV8UK.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-Da9dvM-0.js +19 -0
- package/client/dist/assets/JobDetailModal-DhwKbZGe.js +1 -0
- package/client/dist/assets/JobDetailPage-MtPE-E_1.js +1 -0
- package/client/dist/assets/JobsPage-0vdlbNik.js +1 -0
- package/client/dist/assets/LoopBuilderPage-DCUHozXh.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-je5pSCf-.js → LoopPreviewModal-Df-S8NGf.js} +1 -1
- package/client/dist/assets/LoopWindowSurface-sdb7QbSo.js +1 -0
- package/client/dist/assets/LoopsPage-CDfCC9Px.js +2 -0
- package/client/dist/assets/MinimizedChatsContext-DKlPf54Y.js +1 -0
- package/client/dist/assets/PluginsPage-iL0G3rQQ.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-DOkjT7xv.js +1 -0
- package/client/dist/assets/{RepositoryDeliveries-BiwJuS8r.js → RepositoryDeliveries-CJVpOUpZ.js} +1 -1
- package/client/dist/assets/RepositoryScopeSelector-BeM-2gIZ.js +2 -0
- package/client/dist/assets/ReviewPacketPage-EBCim2w_.js +4 -0
- package/client/dist/assets/TemplatePreviewModal-1-92UGwN.js +1 -0
- package/client/dist/assets/TicketDetailModalContext-CkVXuHxU.js +16 -0
- package/client/dist/assets/{Trans-D3AfDylf.js → Trans-CzhPjnjB.js} +1 -1
- package/client/dist/assets/agent--8QAfoXL.js +1 -0
- package/client/dist/assets/agent-3HxXkIWX.js +1 -0
- package/client/dist/assets/agent-BjttZLIl.js +1 -0
- package/client/dist/assets/agent-CpFCz32T.js +1 -0
- package/client/dist/assets/agent-DVnw6ybO.js +1 -0
- package/client/dist/assets/agent-Z5s_GTP0.js +1 -0
- package/client/dist/assets/agent-hv57-zyG.js +1 -0
- package/client/dist/assets/agent-xGrZ_aN1.js +1 -0
- package/client/dist/assets/{agentRuntime-DGz_IqKW.js → agentRuntime-15Pkk6we.js} +1 -1
- package/client/dist/assets/agentRuntime-Bpx3yWHK.js +1 -0
- package/client/dist/assets/{agentRuntime-Bg9tbmqm.js → agentRuntime-C78n13To.js} +1 -1
- package/client/dist/assets/{agentRuntime-D-GJVGkj.js → agentRuntime-CCI7XF4c.js} +1 -1
- package/client/dist/assets/{agentRuntime-D2__CVI_.js → agentRuntime-CKDHe2-a.js} +1 -1
- package/client/dist/assets/{agentRuntime-D35dGVOP.js → agentRuntime-Ddqvp2bP.js} +1 -1
- package/client/dist/assets/{agentRuntime-hInH9Fzj.js → agentRuntime-TXPQowKJ.js} +1 -1
- package/client/dist/assets/{agentRuntime-O1liDt6G.js → agentRuntime-hqZRy08E.js} +1 -1
- package/client/dist/assets/{builder-DGETbtZj.js → builder-B9gorTnT.js} +1 -1
- package/client/dist/assets/{builder-DyQperXU.js → builder-Bvw5s3Mi.js} +1 -1
- package/client/dist/assets/{builder-C1neIYGR.js → builder-CL5l4dU3.js} +1 -1
- package/client/dist/assets/{builder-DXEJUlC0.js → builder-CVDTVrQX.js} +1 -1
- package/client/dist/assets/{builder-CzW5ovyI.js → builder-CpS9liE9.js} +1 -1
- package/client/dist/assets/{builder-CvnFm0im.js → builder-DNctIL9Q.js} +1 -1
- package/client/dist/assets/{builder-D06L7iPy.js → builder-De2NxX6o.js} +1 -1
- package/client/dist/assets/{builder-C0FyvM83.js → builder-DeYT7Y-m.js} +1 -1
- package/client/dist/assets/{button-Ccc9Dc-D.js → button-D1xExSTF.js} +1 -1
- package/client/dist/assets/{common-DmsclwjP.js → common-Bdi0qN4S.js} +1 -1
- package/client/dist/assets/{common-C4Yvvhp6.js → common-Bhb9zN1P.js} +1 -1
- package/client/dist/assets/{common-od8WThlf.js → common-BykKlZg1.js} +1 -1
- package/client/dist/assets/{common-C0BOdeNi.js → common-CvnNNSJA.js} +1 -1
- package/client/dist/assets/{common-BCK7c9Pv.js → common-D5zk_mqq.js} +1 -1
- package/client/dist/assets/{common-DpLWwgI1.js → common-DQNO_al5.js} +1 -1
- package/client/dist/assets/{common-OCvl3c8t.js → common-Dax30Vmp.js} +1 -1
- package/client/dist/assets/{common-c0aujEZC.js → common-DkllpVSb.js} +1 -1
- package/client/dist/assets/{dialog-xXB2q5HJ.js → dialog-CMgps2MY.js} +1 -1
- package/client/dist/assets/external-link-D6XxC_Cu.js +1 -0
- package/client/dist/assets/file-text-BcLCfcWZ.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-aBss1Nai.js → formatDistanceToNow-DlQaXc7c.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DOGj1Mfz.js → getTimezoneOffsetInMilliseconds-DxUFGcVq.js} +1 -1
- package/client/dist/assets/index-Bfunvi6w.css +2 -0
- package/client/dist/assets/index-Czov72g7.js +83 -0
- package/client/dist/assets/integrations-BkpRUbvp.js +1 -0
- package/client/dist/assets/integrations-Bo8qbJf4.js +1 -0
- package/client/dist/assets/integrations-CmSrKzZb.js +1 -0
- package/client/dist/assets/integrations-DGtq26Rr.js +1 -0
- package/client/dist/assets/integrations-De38GFnQ.js +1 -0
- package/client/dist/assets/integrations-Dw6zdtb-.js +1 -0
- package/client/dist/assets/integrations-Dwl9KQp0.js +1 -0
- package/client/dist/assets/integrations-Refg_d2I.js +1 -0
- package/client/dist/assets/jira-api-CbJvbRYx.js +1 -0
- package/client/dist/assets/{jobs-KG4Sn12X.js → jobs-1G3wodhl.js} +1 -1
- package/client/dist/assets/{jobs-6_-r75yC.js → jobs-B4pu8TdF.js} +1 -1
- package/client/dist/assets/{jobs-pPv4KWjb.js → jobs-BD47Ov11.js} +1 -1
- package/client/dist/assets/{jobs-B1yqyId5.js → jobs-BLOoVRRl.js} +1 -1
- package/client/dist/assets/{jobs-BKiK-ZWh.js → jobs-CSrRx5lD.js} +1 -1
- package/client/dist/assets/{jobs-BFuV7ttH.js → jobs-DZAbh1m2.js} +1 -1
- package/client/dist/assets/{jobs-DmKV7b7d.js → jobs-epP7gt8W.js} +1 -1
- package/client/dist/assets/{jobs-BKa8LuwA.js → jobs-tWjcDF_6.js} +1 -1
- package/client/dist/assets/loops--KWr0X-w.js +1 -0
- package/client/dist/assets/{loops-BO3V8244.js → loops-3IutojWt.js} +1 -1
- package/client/dist/assets/{loops-C-hZK8pf.js → loops-BAosWtXE.js} +1 -1
- package/client/dist/assets/{loops-eUnCfLjC.js → loops-BISDssL5.js} +1 -1
- package/client/dist/assets/{loops-Dv849ckF.js → loops-Cm55CXJ4.js} +1 -1
- package/client/dist/assets/{loops-CDOWZ0ci.js → loops-D7tL_Saw.js} +1 -1
- package/client/dist/assets/{loops-BJDY9FTh.js → loops-DHQtnfze.js} +1 -1
- package/client/dist/assets/{loops-Q9LIgGkq.js → loops-kIJJkaxf.js} +1 -1
- package/client/dist/assets/nav--u7iPYlS.js +1 -0
- package/client/dist/assets/nav-C5Jgfaby.js +1 -0
- package/client/dist/assets/nav-CAna_Wpt.js +1 -0
- package/client/dist/assets/nav-CCLqeyHo.js +1 -0
- package/client/dist/assets/nav-Cf3c90Jk.js +1 -0
- package/client/dist/assets/nav-DMYQaQjR.js +1 -0
- package/client/dist/assets/nav-Df_PkZ9F.js +1 -0
- package/client/dist/assets/nav-VyNGBJwj.js +1 -0
- package/client/dist/assets/{project-repositories-CSnfzUpg.js → project-repositories-BkBD3rxx.js} +1 -1
- package/client/dist/assets/provider-capabilities-lePfeT_Y.js +1 -0
- package/client/dist/assets/save-OsUqVn99.js +1 -0
- package/client/dist/assets/settings-B5Hy_Kol.js +1 -0
- package/client/dist/assets/settings-BBj_kZaD.js +1 -0
- package/client/dist/assets/settings-ByVE7jdZ.js +1 -0
- package/client/dist/assets/settings-COlH_P33.js +1 -0
- package/client/dist/assets/settings-D77mC_Lb.js +1 -0
- package/client/dist/assets/settings-DZKy7MqN.js +1 -0
- package/client/dist/assets/settings-Dbyd6gmj.js +1 -0
- package/client/dist/assets/settings-Dn-pXukr.js +1 -0
- package/client/dist/assets/setup-Be485n2u.js +1 -0
- package/client/dist/assets/setup-CKGUxKr-.js +1 -0
- package/client/dist/assets/setup-Ck1Uwd7_.js +1 -0
- package/client/dist/assets/setup-Cm774a6v.js +1 -0
- package/client/dist/assets/setup-DIqMQpxt.js +1 -0
- package/client/dist/assets/setup-DfCsoNFL.js +1 -0
- package/client/dist/assets/setup-DkeE8mP0.js +1 -0
- package/client/dist/assets/setup-xVntVaz1.js +1 -0
- package/client/dist/assets/{spending-HBSk-6jv.js → spending-CYotH-9N.js} +1 -1
- package/client/dist/assets/subscriptionUsage-BH73RO86.js +1 -0
- package/client/dist/assets/subscriptionUsage-BWTeWlHx.js +1 -0
- package/client/dist/assets/subscriptionUsage-CBh82zVa.js +1 -0
- package/client/dist/assets/subscriptionUsage-CHcQ7lLe.js +1 -0
- package/client/dist/assets/subscriptionUsage-CdlE5Mg1.js +1 -0
- package/client/dist/assets/subscriptionUsage-DUPBmSQi.js +1 -0
- package/client/dist/assets/subscriptionUsage-DrlwrJ17.js +1 -0
- package/client/dist/assets/subscriptionUsage-EFu0mIK3.js +1 -0
- package/client/dist/assets/tauri-shell-DE6LfGPw.js +2 -0
- package/client/dist/assets/{useDesktop-0unHDBJJ.js → useDesktop-DpxVGfUC.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-BN1rhyEm.js → useRuntimeRuns-CfGRbhr9.js} +1 -1
- package/client/dist/assets/useSharedWebSocket-C17TGqT8.js +2 -0
- package/client/dist/index.html +33 -32
- package/docs/README.md +1 -9
- package/docs/agent-live-steering.md +0 -6
- package/docs/agents/README.md +0 -2
- package/docs/agents/mcp.md +1 -14
- package/docs/cli.md +0 -13
- package/docs/codex.md +2 -75
- package/docs/creating-specs.md +0 -39
- package/docs/customizing.md +1 -15
- package/docs/getting-started.md +0 -23
- package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/de/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/de/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/de/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/de/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/de/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/de/integrations/1-ai-providers.md +2 -50
- package/docs/guide/de/integrations/2-plugins.md +0 -8
- package/docs/guide/de/integrations/5-mcp-server.md +1 -14
- package/docs/guide/de/integrations/6-agent-chat.md +0 -11
- package/docs/guide/de/integrations/7-local-engines.md +0 -4
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/de/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +0 -20
- package/docs/guide/de/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/de/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +0 -10
- package/docs/guide/en/agents/4-custom-agents-catalog.md +0 -25
- package/docs/guide/en/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/en/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/en/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/en/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/en/insights/3-code-explorer.md +0 -5
- package/docs/guide/en/integrations/1-ai-providers.md +10 -99
- package/docs/guide/en/integrations/2-plugins.md +0 -8
- package/docs/guide/en/integrations/5-mcp-server.md +1 -1
- package/docs/guide/en/integrations/6-agent-chat.md +0 -17
- package/docs/guide/en/integrations/7-local-engines.md +0 -4
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +2 -24
- package/docs/guide/en/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +10 -65
- package/docs/guide/en/pipeline/5-the-loop-builder.md +0 -7
- package/docs/guide/en/specs/2-add-spec-quick-mode.md +0 -11
- package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/es/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/es/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/es/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/es/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/es/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/es/integrations/1-ai-providers.md +10 -75
- package/docs/guide/es/integrations/2-plugins.md +0 -8
- package/docs/guide/es/integrations/5-mcp-server.md +1 -1
- package/docs/guide/es/integrations/6-agent-chat.md +0 -11
- package/docs/guide/es/integrations/7-local-engines.md +0 -4
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +3 -13
- package/docs/guide/es/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +10 -53
- package/docs/guide/es/pipeline/5-the-loop-builder.md +1 -6
- package/docs/guide/es/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/fr/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/fr/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/fr/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/fr/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/fr/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/fr/integrations/1-ai-providers.md +2 -50
- package/docs/guide/fr/integrations/2-plugins.md +0 -8
- package/docs/guide/fr/integrations/5-mcp-server.md +1 -14
- package/docs/guide/fr/integrations/6-agent-chat.md +0 -11
- package/docs/guide/fr/integrations/7-local-engines.md +0 -4
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/fr/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +0 -22
- package/docs/guide/fr/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/fr/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/it/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/it/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/it/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/it/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/it/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/it/integrations/1-ai-providers.md +2 -48
- package/docs/guide/it/integrations/2-plugins.md +0 -8
- package/docs/guide/it/integrations/5-mcp-server.md +1 -14
- package/docs/guide/it/integrations/6-agent-chat.md +0 -11
- package/docs/guide/it/integrations/7-local-engines.md +0 -4
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +2 -13
- package/docs/guide/it/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +0 -20
- package/docs/guide/it/pipeline/5-the-loop-builder.md +1 -6
- package/docs/guide/it/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +0 -9
- package/docs/guide/ja/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/ja/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/ja/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/ja/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/ja/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/ja/integrations/1-ai-providers.md +1 -69
- package/docs/guide/ja/integrations/2-plugins.md +0 -8
- package/docs/guide/ja/integrations/5-mcp-server.md +1 -14
- package/docs/guide/ja/integrations/6-agent-chat.md +0 -10
- package/docs/guide/ja/integrations/7-local-engines.md +0 -4
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/ja/pipeline/2-the-job-detail-view.md +0 -3
- package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +0 -21
- package/docs/guide/ja/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/ja/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +0 -8
- package/docs/guide/pt/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/pt/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/pt/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/pt/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/pt/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/pt/integrations/1-ai-providers.md +2 -48
- package/docs/guide/pt/integrations/2-plugins.md +0 -8
- package/docs/guide/pt/integrations/5-mcp-server.md +1 -14
- package/docs/guide/pt/integrations/6-agent-chat.md +0 -10
- package/docs/guide/pt/integrations/7-local-engines.md +0 -4
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/pt/pipeline/2-the-job-detail-view.md +0 -2
- package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +0 -21
- package/docs/guide/pt/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/pt/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +0 -7
- package/docs/guide/zh/agents/4-custom-agents-catalog.md +0 -15
- package/docs/guide/zh/getting-started/1-what-is-specrails.md +0 -2
- package/docs/guide/zh/getting-started/2-installing-and-first-run.md +0 -2
- package/docs/guide/zh/getting-started/3-adding-your-first-project.md +0 -4
- package/docs/guide/zh/getting-started/4-the-dashboard-tour.md +0 -5
- package/docs/guide/zh/integrations/1-ai-providers.md +1 -67
- package/docs/guide/zh/integrations/2-plugins.md +0 -8
- package/docs/guide/zh/integrations/5-mcp-server.md +1 -14
- package/docs/guide/zh/integrations/6-agent-chat.md +0 -9
- package/docs/guide/zh/integrations/7-local-engines.md +0 -4
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +0 -13
- package/docs/guide/zh/pipeline/2-the-job-detail-view.md +0 -2
- package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +0 -20
- package/docs/guide/zh/pipeline/5-the-loop-builder.md +0 -6
- package/docs/guide/zh/specs/2-add-spec-quick-mode.md +0 -7
- package/docs/internals/adding-a-provider.md +7 -0
- package/docs/internals/api-reference.md +1 -0
- package/docs/internals/core-runtime-updates.md +9 -2
- package/docs/internals/desktop-owned-workflows.md +144 -0
- package/docs/internals/mission-rail-cards.md +15 -0
- package/docs/internals/operations-runbook.md +26 -0
- package/docs/internals/programmatic-agent-runtime.md +34 -28
- package/docs/internals/project-builder.md +34 -11
- package/docs/internals/safe-pr-review-flow.md +25 -7
- package/docs/internals/source-map.md +53 -3
- package/docs/internals/spec-addenda.md +11 -5
- package/docs/internals/visual-design-audit.md +5 -0
- package/docs/local-providers.md +8 -13
- package/docs/mcp.md +1 -17
- package/docs/platforms/macos.md +0 -40
- package/docs/platforms/windows-parity.md +1 -1
- package/docs/platforms/windows.md +0 -8
- package/docs/running-pipelines.md +1 -46
- package/docs/subscription-usage.md +19 -0
- package/docs/terminal.md +0 -2
- package/docs/tracking-cost.md +2 -33
- package/package.json +1 -1
- package/server/dist/core-execution.js +24 -15
- package/server/dist/core-update-manager.js +1 -1
- package/server/dist/db/migrations.js +9 -0
- package/server/dist/desktop-db.js +23 -10
- package/server/dist/desktop-router.js +8 -4
- package/server/dist/index.js +10 -5
- package/server/dist/mcp/guide.js +1 -0
- package/server/dist/mcp/tools/jobs.js +1 -1
- package/server/dist/mcp/tools/loops.js +2 -0
- package/server/dist/mcp/tools/meta.js +6 -7
- package/server/dist/mcp/tools/projects.js +31 -6
- package/server/dist/mcp/tools/rails.js +6 -3
- package/server/dist/mcp/tools/setup.js +10 -0
- package/server/dist/mcp/tools/specs.js +1 -1
- package/server/dist/modules/accounting/runtime/pricing.js +6 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +169 -17
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +42 -0
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js +10 -4
- package/server/dist/modules/builder/runtime/milestone-chain.js +76 -10
- package/server/dist/modules/delivery/runtime/delivery-evidence.js +3 -2
- package/server/dist/modules/delivery/runtime/multi-repo-execution.js +10 -5
- package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +66 -30
- package/server/dist/modules/delivery/runtime/rail-launch-parser.js +8 -0
- package/server/dist/modules/delivery/runtime/rail-pr-store.js +3 -3
- package/server/dist/modules/delivery/runtime/rail-relaunch.js +154 -0
- package/server/dist/modules/delivery/runtime/rails-router.js +157 -36
- package/server/dist/modules/delivery/runtime/review-packet.js +1 -1
- package/server/dist/modules/execution/runtime/revision-seed.js +6 -2
- package/server/dist/modules/loops/runtime/loop-agent-defaults.json +6 -0
- package/server/dist/modules/loops/runtime/loop-agents.js +21 -0
- package/server/dist/modules/loops/runtime/loop-command-catalog.js +1 -1
- package/server/dist/modules/loops/runtime/loop-core-factory.js +19 -3
- package/server/dist/modules/loops/runtime/loop-definition-events.js +1 -1
- package/server/dist/modules/loops/runtime/loop-definition.js +79 -12
- package/server/dist/modules/loops/runtime/loop-effect.js +1 -1
- package/server/dist/modules/loops/runtime/loop-executors.js +24 -9
- package/server/dist/modules/loops/runtime/loop-factory.js +1 -1
- package/server/dist/modules/loops/runtime/loop-graph.js +5 -14
- package/server/dist/modules/loops/runtime/loop-implement-recipe.js +53 -0
- package/server/dist/modules/loops/runtime/loop-run-manager.js +30 -20
- package/server/dist/modules/loops/runtime/loop-templates.js +29 -5
- package/server/dist/modules/loops/runtime/loops-router.js +17 -0
- package/server/dist/modules/missions/runtime/agent-context-resolver.js +1 -1
- package/server/dist/modules/missions/runtime/agent-operator-prompt.js +17 -8
- package/server/dist/modules/specs/runtime/spec-models.js +4 -7
- package/server/dist/modules/subscription-usage/adapters/claude.js +123 -0
- package/server/dist/modules/subscription-usage/adapters/codex.js +164 -0
- package/server/dist/modules/subscription-usage/adapters/errors.js +15 -0
- package/server/dist/modules/subscription-usage/adapters/http.js +20 -0
- package/server/dist/modules/subscription-usage/adapters/local-auth.js +61 -0
- package/server/dist/modules/subscription-usage/application.js +12 -0
- package/server/dist/modules/subscription-usage/domain.js +104 -0
- package/server/dist/modules/subscription-usage/index.js +2 -0
- package/server/dist/modules/subscription-usage/ports.js +2 -0
- package/server/dist/modules/subscription-usage/runtime/composition.js +20 -0
- package/server/dist/modules/subscription-usage/runtime/usage-service.js +111 -0
- package/server/dist/project-registry.js +16 -1
- package/server/dist/project-repositories.js +51 -2
- package/server/dist/project-router-helpers.js +1 -1
- package/server/dist/project-router-loop-runs.js +4 -1
- package/server/dist/providers/claude-adapter.js +6 -4
- package/server/dist/providers/codex-adapter.js +4 -5
- package/server/dist/schemas/workflow-definition.schema.json +2 -0
- package/server/dist/worktree-manager.js +114 -6
- package/server/dist/worktree-overlay.js +4 -4
- package/client/dist/assets/AgentBrowserCapture-PI-cxyxi.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-mQsOqdXx.js +0 -2
- package/client/dist/assets/AgentModeCodePane-BL-vpmlY.js +0 -2
- package/client/dist/assets/CodePage-Alitorrv.js +0 -3
- package/client/dist/assets/DocsDialog-DwL6rgcT.js +0 -11
- package/client/dist/assets/DocsPage-BA1hiJZ-.js +0 -11
- package/client/dist/assets/InteractiveJobComposer-B3H1YHr6.js +0 -19
- package/client/dist/assets/JobDetailModal-XZQ9eAAy.js +0 -1
- package/client/dist/assets/JobDetailPage-B-SIaq_q.js +0 -1
- package/client/dist/assets/JobsPage-iQXAv2mx.js +0 -1
- package/client/dist/assets/LoopBuilderPage-B_gbFAHn.js +0 -1
- package/client/dist/assets/LoopsPage-BZwcY5cu.js +0 -2
- package/client/dist/assets/MinimizedChatsContext-CNx_0bZu.js +0 -1
- package/client/dist/assets/PluginsPage-DFocnoHX.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-Bh3S6EBB.js +0 -1
- package/client/dist/assets/RepositoryScopeSelector-C2mvWBQ6.js +0 -1
- package/client/dist/assets/ReviewPacketPage-CrAf-WDy.js +0 -4
- package/client/dist/assets/TemplatePreviewModal-C7DordpN.js +0 -1
- package/client/dist/assets/TicketDetailModalContext-B-DySpKL.js +0 -16
- package/client/dist/assets/agent-BaG10AN6.js +0 -1
- package/client/dist/assets/agent-CH4aJ884.js +0 -1
- package/client/dist/assets/agent-CLc9fA7v.js +0 -1
- package/client/dist/assets/agent-DHh9-AdL.js +0 -1
- package/client/dist/assets/agent-DkKnd6Wv.js +0 -1
- package/client/dist/assets/agent-aiVhqFW1.js +0 -1
- package/client/dist/assets/agent-dbznD3eO.js +0 -1
- package/client/dist/assets/agent-sMVFcPwL.js +0 -1
- package/client/dist/assets/agentRuntime-BeUb3g9g.js +0 -1
- package/client/dist/assets/file-text-CFT-xXKN.js +0 -1
- package/client/dist/assets/index-BS7KCWkV.css +0 -2
- package/client/dist/assets/index-C4Mv6tV6.js +0 -80
- package/client/dist/assets/integrations-CH0zy1Mw.js +0 -1
- package/client/dist/assets/integrations-CIAjhd7L.js +0 -1
- package/client/dist/assets/integrations-CZdwOe8A.js +0 -1
- package/client/dist/assets/integrations-Ct7omMld.js +0 -1
- package/client/dist/assets/integrations-D6MSxy3-.js +0 -1
- package/client/dist/assets/integrations-DbZa-Zyc.js +0 -1
- package/client/dist/assets/integrations-b8pH-vfl.js +0 -1
- package/client/dist/assets/integrations-mB-7zoyT.js +0 -1
- package/client/dist/assets/jira-api-BYbEPlkr.js +0 -1
- package/client/dist/assets/layers-eDMjG41I.js +0 -1
- package/client/dist/assets/loops-B4_D-nbg.js +0 -1
- package/client/dist/assets/nav-B-tpjyc2.js +0 -1
- package/client/dist/assets/nav-BJNXGg29.js +0 -1
- package/client/dist/assets/nav-BdhtRLP7.js +0 -1
- package/client/dist/assets/nav-ByYgyN5T.js +0 -1
- package/client/dist/assets/nav-DA0Mva2c.js +0 -1
- package/client/dist/assets/nav-DcJm_Jg_.js +0 -1
- package/client/dist/assets/nav-DhMPtAXc.js +0 -1
- package/client/dist/assets/nav-DhPb-Isc.js +0 -1
- package/client/dist/assets/provider-capabilities-Vi1ZGvAK.js +0 -1
- package/client/dist/assets/save-B6wGWwBR.js +0 -1
- package/client/dist/assets/settings-3o4Ntzi5.js +0 -1
- package/client/dist/assets/settings-B97jlkwD.js +0 -1
- package/client/dist/assets/settings-BTaT_bsS.js +0 -1
- package/client/dist/assets/settings-CT8w7WiC.js +0 -1
- package/client/dist/assets/settings-Cvr8_tvh.js +0 -1
- package/client/dist/assets/settings-Dn1qgAC8.js +0 -1
- package/client/dist/assets/settings-EShDdg4J.js +0 -1
- package/client/dist/assets/settings-tfLhQZRN.js +0 -1
- package/client/dist/assets/setup-CBMu3tqH.js +0 -1
- package/client/dist/assets/setup-CPh-e6J2.js +0 -1
- package/client/dist/assets/setup-CRKLp_ke.js +0 -1
- package/client/dist/assets/setup-Cy302v4J.js +0 -1
- package/client/dist/assets/setup-D681vTPf.js +0 -1
- package/client/dist/assets/setup-DYK0VBkE.js +0 -1
- package/client/dist/assets/setup-KAuiIiVs.js +0 -1
- package/client/dist/assets/setup-OGX-oCfI.js +0 -1
- package/client/dist/assets/useSharedWebSocket-BZMrX4jr.js +0 -2
- package/client/dist/assets/wrench-BfmNO9Vw.js +0 -1
- package/docs/agy-cli-provider-study.md +0 -257
- package/docs/gemini-cli-provider-study.md +0 -301
- package/docs/gemini-core-support-evaluation.md +0 -160
- package/docs/gemini.md +0 -257
- package/docs/kimi.md +0 -291
|
@@ -149,7 +149,7 @@ function formatProjectOverview(ref, index, deps) {
|
|
|
149
149
|
lines.push('resolution: unresolved project');
|
|
150
150
|
return lines.join('\n');
|
|
151
151
|
}
|
|
152
|
-
lines.push(`project.id: ${project.id}`, `project.name: ${safe(project.name, 180)}`, `project.slug: ${safe(project.slug, 120)}`, `project.path: ${project.path}`, `project.provider: ${safe(project.provider, 80)}`, `project.providers: ${(project.providers ?? [project.provider]).join(', ')}`, `project.runtime: ${context ? 'available' : 'unavailable; do not interpret this as a deleted project'}`, `project.repositories: ${JSON.stringify((0, project_repositories_1.getProjectRepositories)(project).map(repository => ({ id: repository.id, name: repository.name, path: repository.path, isPrimary: repository.isPrimary, kind: repository.kind, available: repository.available })))}`, 'repository.scope: One shared project backlog. Files and Git operations require repositoryId when more than one member exists; find/search can discover across members. Reading context does not grant implementation write access. Historical specs without repositoryIds target only the primary.');
|
|
152
|
+
lines.push(`project.id: ${project.id}`, `project.name: ${safe(project.name, 180)}`, `project.slug: ${safe(project.slug, 120)}`, `project.path: ${project.path}`, `project.provider: ${safe(project.provider, 80)}`, `project.providers: ${(project.providers ?? [project.provider]).join(', ')}`, `project.runtime: ${context ? 'available' : 'unavailable; do not interpret this as a deleted project'}`, `project.repositories: ${JSON.stringify((0, project_repositories_1.getProjectRepositories)(project).map(repository => ({ id: repository.id, name: repository.name, path: repository.path, workspacePaths: repository.workspacePaths ?? [repository.workspacePath ?? repository.path], isPrimary: repository.isPrimary, kind: repository.kind, available: repository.available })))}`, 'repository.scope: One shared project backlog. Files and Git operations require repositoryId when more than one member exists; find/search can discover across members. Reading context does not grant implementation write access. Historical specs without repositoryIds target only the primary.');
|
|
153
153
|
try {
|
|
154
154
|
const tickets = Object.values((0, ticket_store_1.readStore)(ticketFileFor(project)).tickets);
|
|
155
155
|
const counts = tickets.reduce((acc, ticket) => {
|
|
@@ -628,6 +628,13 @@ summary later.
|
|
|
628
628
|
N-1. When talking to the user ALWAYS use the 1-based label (tool results
|
|
629
629
|
include \`railLabel\`, e.g. railIndex 3 → "Rail 4") or the rail's custom name;
|
|
630
630
|
never quote the raw railIndex as the rail's name.
|
|
631
|
+
- Code workspace selection: discover \`workspacePaths\` in the project context. A
|
|
632
|
+
project folder can contain several code workspaces; tests must run in the
|
|
633
|
+
workspace being changed. Include \`workspaceSelection\` (repository ID → array
|
|
634
|
+
of registered workspace paths) in rail-launch cards and direct launch calls to
|
|
635
|
+
target a subset. Preserve \`repositoryIds\` for affected Git memberships. Do not
|
|
636
|
+
invent paths or substitute the parent folder. Omission uses every configured
|
|
637
|
+
workspace for each affected membership.
|
|
631
638
|
- Configure then launch: \`specrails_rails(set_tickets, railIndex, ticketIds)\` →
|
|
632
639
|
\`specrails_rails(launch, railIndex, mode, …)\`. Setting a profile and then
|
|
633
640
|
switching the rail's engine to a provider without profile support
|
|
@@ -657,15 +664,15 @@ summary later.
|
|
|
657
664
|
relaunch strategy even when \`main\` did not have that spec before the PR. If
|
|
658
665
|
asked why a strategy was chosen, verify against active PR contents before
|
|
659
666
|
answering.
|
|
660
|
-
- **"Change something about work already delivered" = a
|
|
667
|
+
- **"Change something about work already delivered" = a delivery continuation.** When
|
|
661
668
|
the user asks for a modification to a delivery that is awaiting their decision
|
|
662
669
|
(any non-terminal card: \`on_review\`, \`pr_draft\`, \`pr_ready\`, \`no_changes\`,
|
|
663
670
|
\`implementation_failed\`), call \`specrails_rails(launch)\` with
|
|
664
671
|
\`revisionOfDeliveryId\` = that card's \`prDeliveryId\` and \`revisionNote\` = what
|
|
665
672
|
they asked for, in their own words. Do NOT tell them to publish, discard or
|
|
666
|
-
merge first
|
|
667
|
-
|
|
668
|
-
|
|
673
|
+
merge first. Respect the user's explicit loop selection, including Implement:
|
|
674
|
+
pass that loopId alongside the delivery target. Without a preference, use
|
|
675
|
+
\`loopId:'factory:sdd-quick-openspec'\`. The server preserves the branch. The rail must still carry exactly that delivery's
|
|
669
676
|
specs; a mismatch returns \`invalid_revision_target\`, which means re-check the
|
|
670
677
|
rail's spec assignment rather than retrying blindly.
|
|
671
678
|
- **"Resolve / fix the review comments on this PR" = a FOLLOW-UP launch, never a
|
|
@@ -706,9 +713,9 @@ summary later.
|
|
|
706
713
|
\`dismiss_addendum\` withdraws it. Prefer an addendum + normal launch over a
|
|
707
714
|
\`revisionNote\` when the change should stay attached to the spec for later
|
|
708
715
|
runs; the two combine freely.
|
|
709
|
-
**
|
|
710
|
-
|
|
711
|
-
|
|
716
|
+
**Respect explicit loop selection for addenda**, including Implement.
|
|
717
|
+
Recommend Quick SDD when no preference was given; never silently replace
|
|
718
|
+
Implement with Quick SDD after agreeing to use Implement. A same-spec pending delivery is
|
|
712
719
|
continued without requiring publish/discard first. Keep the exact ticket and
|
|
713
720
|
repository scope; pass the delivery id when known. The runtime assigns a NEW
|
|
714
721
|
run-specific OpenSpec change for the frozen addenda, so NEVER edit
|
|
@@ -889,6 +896,8 @@ never as a launch):
|
|
|
889
896
|
\`null\` means "the rail's / project's default".
|
|
890
897
|
- \`targetPrNumber\` / \`baseBranch\`: only when the user named an existing PR
|
|
891
898
|
or a branch to stack on.
|
|
899
|
+
- \`repositoryIds\`: selected registered repository memberships, consistent with the specs.
|
|
900
|
+
- \`workspaceSelection\`: optional object mapping each selected repository id to a non-empty array of registered workspace paths. Discover paths from project context; never invent them. Omission uses every configured workspace. The card lets the user edit this choice before Play.
|
|
892
901
|
- \`rationale\`: ONE sentence — why this grouping / engine.
|
|
893
902
|
|
|
894
903
|
Prose around the block: say what you propose in one or two lines and stop —
|
|
@@ -916,7 +925,7 @@ investigate and fix. Read runtime_diagnose before recommending a retry.
|
|
|
916
925
|
function buildOperatorInstructions() {
|
|
917
926
|
return (0, feature_flags_1.isMissionRailCardsEnabled)() ? `${exports.OPERATOR_INSTRUCTIONS}\n${exports.RAIL_LAUNCH_CARD_SECTION}` : exports.OPERATOR_INSTRUCTIONS;
|
|
918
927
|
}
|
|
919
|
-
exports.RAIL_LAUNCH_CARD_SYSTEM_CLAUSE = ' When the user asks to assign, prepare or launch specs on a rail WITHOUT explicitly saying to launch now, do not call specrails_rails launch — emit one fenced rail-launch JSON block per rail (ticketIds, railIndex or newRail, mode, loopId, aiEngine, model, reasoningEffort, profileName, rationale) so the app renders an editable launch card the user plays; never propose a rail whose availability is not free. A message starting with "[Specrails run-failure briefing" is app-posted: answer in ≤ 6 lines with the failure and ONE recommended next action, and never resume, recover, relaunch or discard in that turn.';
|
|
928
|
+
exports.RAIL_LAUNCH_CARD_SYSTEM_CLAUSE = ' When the user asks to assign, prepare or launch specs on a rail WITHOUT explicitly saying to launch now, do not call specrails_rails launch — emit one fenced rail-launch JSON block per rail (ticketIds, railIndex or newRail, mode, loopId, aiEngine, model, reasoningEffort, profileName, repositoryIds, workspaceSelection, rationale) so the app renders an editable launch card the user plays; never propose a rail whose availability is not free. A message starting with "[Specrails run-failure briefing" is app-posted: answer in ≤ 6 lines with the failure and ONE recommended next action, and never resume, recover, relaunch or discard in that turn.';
|
|
920
929
|
/** The per-turn system prompt (flag-aware). */
|
|
921
930
|
function buildOperatorSystemPrompt() {
|
|
922
931
|
return (0, feature_flags_1.isMissionRailCardsEnabled)() ? `${exports.OPERATOR_SYSTEM_PROMPT}${exports.RAIL_LAUNCH_CARD_SYSTEM_CLAUSE}` : exports.OPERATOR_SYSTEM_PROMPT;
|
|
@@ -6,20 +6,17 @@ exports.isValidModelForProvider = isValidModelForProvider;
|
|
|
6
6
|
exports.getProviderDefault = getProviderDefault;
|
|
7
7
|
const providers_1 = require("../../../providers");
|
|
8
8
|
exports.CLAUDE_MODELS = [
|
|
9
|
-
{ value: 'sonnet', label: '
|
|
10
|
-
{ value: 'fable', label: '
|
|
9
|
+
{ value: 'sonnet', label: 'Sonnet 5.5' },
|
|
10
|
+
{ value: 'fable', label: 'Fable 5.1' },
|
|
11
11
|
{ value: 'opus', label: 'Claude Opus' },
|
|
12
12
|
{ value: 'haiku', label: 'Claude Haiku' },
|
|
13
13
|
];
|
|
14
14
|
exports.CODEX_MODELS = [
|
|
15
|
+
{ value: 'gpt-6.1-sol', label: 'GPT-6.1 Sol' },
|
|
15
16
|
{ value: 'gpt-6-astra', label: 'GPT-6 Astra' },
|
|
16
17
|
{ value: 'gpt-5.6-sol', label: 'GPT-5.6 Sol' },
|
|
17
18
|
{ value: 'gpt-5.6-terra', label: 'GPT-5.6 Terra' },
|
|
18
19
|
{ value: 'gpt-5.6-luna', label: 'GPT-5.6 Luna' },
|
|
19
|
-
{ value: 'gpt-5.5', label: 'GPT-5.5' },
|
|
20
|
-
{ value: 'gpt-5.4', label: 'GPT-5.4' },
|
|
21
|
-
{ value: 'gpt-5.4-mini', label: 'GPT-5.4 Mini' },
|
|
22
|
-
{ value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
|
|
23
20
|
];
|
|
24
21
|
// Mirrors GEMINI_MODELS in server/providers/gemini-adapter.ts.
|
|
25
22
|
exports.GEMINI_MODELS = [
|
|
@@ -43,7 +40,7 @@ const PROVIDER_MODELS = {
|
|
|
43
40
|
};
|
|
44
41
|
exports.PROVIDER_DEFAULT_MODEL = {
|
|
45
42
|
claude: 'sonnet',
|
|
46
|
-
codex: 'gpt-
|
|
43
|
+
codex: 'gpt-6.1-sol',
|
|
47
44
|
gemini: 'gemini-3.5-flash',
|
|
48
45
|
kimi: 'k3',
|
|
49
46
|
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createClaudeReader = createClaudeReader;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const domain_1 = require("../domain");
|
|
12
|
+
const errors_1 = require("./errors");
|
|
13
|
+
const local_auth_1 = require("./local-auth");
|
|
14
|
+
function readKeychain(service, signal) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
let account;
|
|
17
|
+
try {
|
|
18
|
+
account = process.env.USER || process.env.USERNAME || (0, node_os_1.userInfo)().username;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
account = 'claude-code-user';
|
|
22
|
+
}
|
|
23
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(account))
|
|
24
|
+
account = 'claude-code-user';
|
|
25
|
+
(0, node_child_process_1.execFile)('/usr/bin/security', ['find-generic-password', '-s', service, '-a', account, '-w'], { timeout: 3000, maxBuffer: 1024 * 1024, signal }, (error, stdout) => {
|
|
26
|
+
if (!error)
|
|
27
|
+
resolve(stdout);
|
|
28
|
+
else if (error.code === 44)
|
|
29
|
+
resolve(null);
|
|
30
|
+
else
|
|
31
|
+
reject(new errors_1.UsageError('credentials-unreadable'));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function createClaudeReader(options = {}) {
|
|
36
|
+
const configured = options.configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
37
|
+
const configDir = configured ?? node_path_1.default.join((0, node_os_1.homedir)(), '.claude');
|
|
38
|
+
async function credentials(signal) {
|
|
39
|
+
if (!node_path_1.default.isAbsolute(configDir))
|
|
40
|
+
throw new errors_1.UsageError('unsupported-platform');
|
|
41
|
+
let keychainFailure = false;
|
|
42
|
+
if ((options.platform ?? process.platform) === 'darwin') {
|
|
43
|
+
const aliases = configured ? [configDir] : [];
|
|
44
|
+
if (configured) {
|
|
45
|
+
try {
|
|
46
|
+
const canonical = await (0, promises_1.realpath)(configDir);
|
|
47
|
+
if (canonical !== configDir)
|
|
48
|
+
aliases.push(canonical);
|
|
49
|
+
}
|
|
50
|
+
catch { /* use the exact resolved context */ }
|
|
51
|
+
}
|
|
52
|
+
const services = configured ? aliases.map(dir => `Claude Code-credentials-${(0, local_auth_1.fingerprint)(dir.normalize('NFC')).slice(0, 8)}`) : ['Claude Code-credentials'];
|
|
53
|
+
for (const service of services) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = await (options.keychain ?? readKeychain)(service, signal);
|
|
56
|
+
if (raw)
|
|
57
|
+
return raw;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
keychainFailure = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const file = await (options.readFile ?? local_auth_1.readAuthFile)(node_path_1.default.join(configDir, '.credentials.json'));
|
|
66
|
+
if (file)
|
|
67
|
+
return file;
|
|
68
|
+
if (keychainFailure)
|
|
69
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
async context(signal) { return (0, local_auth_1.fingerprint)(`${configDir}:${await credentials(signal) ?? 'none'}`); },
|
|
74
|
+
async read(signal) {
|
|
75
|
+
const raw = await credentials(signal);
|
|
76
|
+
if (!raw)
|
|
77
|
+
return { availability: 'signed-out', windows: [], plan: null, source: null };
|
|
78
|
+
let data;
|
|
79
|
+
try {
|
|
80
|
+
data = (0, domain_1.record)(JSON.parse(raw));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
84
|
+
}
|
|
85
|
+
const oauth = (0, domain_1.record)(data.claudeAiOauth);
|
|
86
|
+
if (typeof oauth.accessToken !== 'string' || !oauth.accessToken)
|
|
87
|
+
return { availability: 'unsupported-auth', windows: [], plan: null, source: null };
|
|
88
|
+
async function query(route) {
|
|
89
|
+
const response = await (options.request ?? fetch)(`https://api.anthropic.com/api/oauth/${route}`, {
|
|
90
|
+
headers: { Authorization: `Bearer ${oauth.accessToken}`, 'anthropic-beta': 'oauth-2025-04-20', 'User-Agent': 'claude-code/2.1.0' },
|
|
91
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(10_000)]), redirect: 'error',
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
await response.body?.cancel().catch(() => { });
|
|
95
|
+
if (response.status === 401)
|
|
96
|
+
throw new errors_1.UsageError('signed-out');
|
|
97
|
+
if (response.status === 403)
|
|
98
|
+
throw new errors_1.UsageError('permission-denied');
|
|
99
|
+
if (response.status === 429) {
|
|
100
|
+
const header = response.headers.get('retry-after');
|
|
101
|
+
const seconds = header !== null && header.trim() !== '' ? Number(header) : NaN;
|
|
102
|
+
const date = header ? Date.parse(header) : NaN;
|
|
103
|
+
const retry = Number.isFinite(seconds) ? seconds * 1000 : Number.isFinite(date) ? date - Date.now() : 60_000;
|
|
104
|
+
throw new errors_1.UsageError('rate-limited', true, Math.max(30_000, Math.min(24 * 60 * 60_000, retry)));
|
|
105
|
+
}
|
|
106
|
+
throw new errors_1.UsageError('provider-error', response.status >= 500);
|
|
107
|
+
}
|
|
108
|
+
return (0, local_auth_1.limitedJson)(response);
|
|
109
|
+
}
|
|
110
|
+
const payload = await query('usage'), windows = (0, domain_1.normalizeClaude)(payload);
|
|
111
|
+
let plan = (0, domain_1.claudePlan)(oauth.subscriptionType) ?? (0, domain_1.claudePlan)(oauth.rateLimitTier);
|
|
112
|
+
const candidate = (0, domain_1.normalizeClaudeSpend)(payload, 'enterprise', (options.now ?? Date.now)());
|
|
113
|
+
if (!plan && candidate) {
|
|
114
|
+
const profile = (0, domain_1.record)(await query('profile')), organization = (0, domain_1.record)(profile.organization);
|
|
115
|
+
plan = (0, domain_1.claudePlan)(organization.organization_type) ?? (0, domain_1.claudePlan)(organization.rate_limit_tier);
|
|
116
|
+
}
|
|
117
|
+
const spend = plan === 'enterprise' ? candidate : null;
|
|
118
|
+
if (!windows.length && !spend)
|
|
119
|
+
throw new errors_1.UsageError('usage-unavailable');
|
|
120
|
+
return { availability: 'available', windows: spend ? [] : windows, spend, plan, source: 'oauth' };
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createCodexReader = createCodexReader;
|
|
7
|
+
const node_string_decoder_1 = require("node:string_decoder");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const win_spawn_1 = require("../../../util/win-spawn");
|
|
11
|
+
const domain_1 = require("../domain");
|
|
12
|
+
const errors_1 = require("./errors");
|
|
13
|
+
const local_auth_1 = require("./local-auth");
|
|
14
|
+
async function stopProbe(child) {
|
|
15
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
16
|
+
return;
|
|
17
|
+
await new Promise(resolve => {
|
|
18
|
+
const done = () => { clearTimeout(timer); child.off('close', done); resolve(); };
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
if (child.pid)
|
|
21
|
+
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL', () => done());
|
|
22
|
+
else {
|
|
23
|
+
child.kill('SIGKILL');
|
|
24
|
+
done();
|
|
25
|
+
}
|
|
26
|
+
}, 500);
|
|
27
|
+
child.once('close', done);
|
|
28
|
+
child.stdin?.end();
|
|
29
|
+
if (child.pid)
|
|
30
|
+
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGTERM');
|
|
31
|
+
else
|
|
32
|
+
child.kill('SIGTERM');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function createCodexReader(options = {}) {
|
|
36
|
+
const home = options.home ?? process.env.CODEX_HOME ?? node_path_1.default.join((0, node_os_1.homedir)(), '.codex');
|
|
37
|
+
return {
|
|
38
|
+
async context() {
|
|
39
|
+
if (!node_path_1.default.isAbsolute(home))
|
|
40
|
+
throw new errors_1.UsageError('unsupported-platform');
|
|
41
|
+
return (0, local_auth_1.fingerprint)(`${home}:${await (options.readFile ?? local_auth_1.readAuthFile)(node_path_1.default.join(home, 'auth.json')) ?? 'keychain-or-none'}`);
|
|
42
|
+
},
|
|
43
|
+
read(signal) {
|
|
44
|
+
if (signal.aborted)
|
|
45
|
+
return Promise.reject(new errors_1.UsageError('aborted'));
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
let child;
|
|
48
|
+
try {
|
|
49
|
+
child = options.spawn ? options.spawn() : (0, win_spawn_1.spawnCli)('codex', ['app-server', '--listen', 'stdio://'], {
|
|
50
|
+
cwd: (0, node_os_1.homedir)(), env: { ...process.env, CODEX_HOME: home }, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
reject(new errors_1.UsageError('unsupported-cli'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let buffer = '', finished = false, plan = null, identity;
|
|
58
|
+
const decoder = new node_string_decoder_1.StringDecoder('utf8');
|
|
59
|
+
const timer = setTimeout(() => finish(new errors_1.UsageError('probe-timeout', true)), options.timeoutMs ?? 25_000);
|
|
60
|
+
function cleanup() {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
signal.removeEventListener('abort', abort);
|
|
63
|
+
child.stdout?.off('data', data);
|
|
64
|
+
child.stderr?.off('data', drain);
|
|
65
|
+
child.off('error', failure);
|
|
66
|
+
child.off('close', closed);
|
|
67
|
+
child.stdin?.off('error', failure);
|
|
68
|
+
}
|
|
69
|
+
function finish(error, result) {
|
|
70
|
+
if (finished)
|
|
71
|
+
return;
|
|
72
|
+
finished = true;
|
|
73
|
+
cleanup();
|
|
74
|
+
// Continue draining without retaining provider diagnostics during termination.
|
|
75
|
+
child.stdout?.resume();
|
|
76
|
+
child.stderr?.resume();
|
|
77
|
+
void (options.stop ?? stopProbe)(child).then(() => error ? reject(error) : resolve(result), () => reject(new errors_1.UsageError('cleanup-failed', true)));
|
|
78
|
+
}
|
|
79
|
+
const abort = () => finish(new errors_1.UsageError('aborted'));
|
|
80
|
+
const failure = () => finish(new errors_1.UsageError('unsupported-cli'));
|
|
81
|
+
const closed = () => finish(new errors_1.UsageError('probe-exited', true));
|
|
82
|
+
const drain = () => { };
|
|
83
|
+
function send(id, method, params = {}) {
|
|
84
|
+
try {
|
|
85
|
+
child.stdin?.write(`${JSON.stringify({ id, method, params })}\n`);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
failure();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function data(chunk) {
|
|
92
|
+
buffer += decoder.write(chunk);
|
|
93
|
+
if (Buffer.byteLength(buffer) > 1024 * 1024) {
|
|
94
|
+
finish(new errors_1.UsageError('invalid-response', true));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let newline;
|
|
98
|
+
while (!finished && (newline = buffer.indexOf('\n')) >= 0) {
|
|
99
|
+
const line = buffer.slice(0, newline);
|
|
100
|
+
buffer = buffer.slice(newline + 1);
|
|
101
|
+
let message;
|
|
102
|
+
try {
|
|
103
|
+
message = (0, domain_1.record)(JSON.parse(line));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
finish(new errors_1.UsageError('invalid-response', true));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (typeof message.id !== 'number')
|
|
110
|
+
continue;
|
|
111
|
+
if (message.error) {
|
|
112
|
+
const code = (0, domain_1.record)(message.error).code;
|
|
113
|
+
finish(new errors_1.UsageError(code === -32601 ? 'unsupported-cli' : 'provider-error', code !== -32601));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (message.id === 1) {
|
|
117
|
+
try {
|
|
118
|
+
child.stdin?.write(`${JSON.stringify({ method: 'initialized', params: {} })}\n`);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
failure();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
send(2, 'account/read', { refreshToken: false });
|
|
125
|
+
}
|
|
126
|
+
else if (message.id === 2) {
|
|
127
|
+
const account = (0, domain_1.record)((0, domain_1.record)(message.result).account);
|
|
128
|
+
if (!Object.keys(account).length) {
|
|
129
|
+
finish(undefined, { availability: 'signed-out', windows: [], plan: null, source: null });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (account.type !== 'chatgpt') {
|
|
133
|
+
finish(undefined, { availability: 'unsupported-auth', windows: [], plan: null, source: null });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
plan = typeof account.planType === 'string' ? account.planType.slice(0, 80) : null;
|
|
137
|
+
// Never export an email; only an internal fingerprint for identity checks.
|
|
138
|
+
if (typeof account.email === 'string')
|
|
139
|
+
identity = (0, local_auth_1.fingerprint)(account.email);
|
|
140
|
+
send(3, 'account/rateLimits/read');
|
|
141
|
+
}
|
|
142
|
+
else if (message.id === 3) {
|
|
143
|
+
const payload = (0, domain_1.record)(message.result), windows = (0, domain_1.normalizeCodex)(payload);
|
|
144
|
+
if (typeof payload.accountId === 'string')
|
|
145
|
+
identity = (0, local_auth_1.fingerprint)(payload.accountId);
|
|
146
|
+
if (!windows.length) {
|
|
147
|
+
finish(new errors_1.UsageError('usage-unavailable'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
finish(undefined, { availability: 'available', windows, plan, source: 'app-server', identity });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
155
|
+
child.on('error', failure);
|
|
156
|
+
child.on('close', closed);
|
|
157
|
+
child.stdin?.on('error', failure);
|
|
158
|
+
child.stdout?.on('data', data);
|
|
159
|
+
child.stderr?.on('data', drain);
|
|
160
|
+
send(1, 'initialize', { clientInfo: { name: 'specrails-subscription-usage', title: 'Specrails', version: '1.0.0' } });
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UsageError = void 0;
|
|
4
|
+
class UsageError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
retryable;
|
|
7
|
+
retryMs;
|
|
8
|
+
constructor(code, retryable = false, retryMs) {
|
|
9
|
+
super(code);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.retryable = retryable;
|
|
12
|
+
this.retryMs = retryMs;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.UsageError = UsageError;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerUsageRoutes = registerUsageRoutes;
|
|
4
|
+
const domain_1 = require("../domain");
|
|
5
|
+
function registerUsageRoutes(router, service) {
|
|
6
|
+
router.get('/subscription-usage', (_req, res) => res.json(service.snapshot()));
|
|
7
|
+
router.post('/subscription-usage/refresh', (req, res) => {
|
|
8
|
+
const body = req.body ?? {};
|
|
9
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body) || Object.keys(body).some(key => key !== 'providerId' && key !== 'automatic')) {
|
|
10
|
+
res.status(400).json({ error: 'Invalid usage refresh request' });
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const { providerId, automatic } = body;
|
|
14
|
+
if ((providerId !== undefined && !domain_1.USAGE_PROVIDERS.includes(providerId)) || (automatic !== undefined && typeof automatic !== 'boolean')) {
|
|
15
|
+
res.status(400).json({ error: 'Invalid usage refresh request' });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
res.status(202).json(service.refresh(providerId, automatic === true));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fingerprint = void 0;
|
|
4
|
+
exports.readAuthFile = readAuthFile;
|
|
5
|
+
exports.limitedJson = limitedJson;
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const fingerprint = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
10
|
+
exports.fingerprint = fingerprint;
|
|
11
|
+
/** Bounded read; never expose auth bytes or paths in errors. */
|
|
12
|
+
async function readAuthFile(file) {
|
|
13
|
+
let handle;
|
|
14
|
+
try {
|
|
15
|
+
handle = await (0, promises_1.open)(file, 'r');
|
|
16
|
+
const stat = await handle.stat();
|
|
17
|
+
if (!stat.isFile() || stat.size > 1024 * 1024)
|
|
18
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
19
|
+
const buffer = Buffer.alloc(1024 * 1024 + 1);
|
|
20
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
21
|
+
if (bytesRead > 1024 * 1024)
|
|
22
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
23
|
+
return buffer.toString('utf8', 0, bytesRead);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code === 'ENOENT')
|
|
27
|
+
return null;
|
|
28
|
+
throw new errors_1.UsageError('credentials-unreadable');
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await handle?.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function limitedJson(response) {
|
|
35
|
+
const reader = response.body?.getReader();
|
|
36
|
+
if (!reader)
|
|
37
|
+
throw new errors_1.UsageError('invalid-response', true);
|
|
38
|
+
let size = 0;
|
|
39
|
+
const chunks = [];
|
|
40
|
+
try {
|
|
41
|
+
while (true) {
|
|
42
|
+
const { done, value } = await reader.read();
|
|
43
|
+
if (done)
|
|
44
|
+
break;
|
|
45
|
+
size += value.length;
|
|
46
|
+
if (size > 1024 * 1024)
|
|
47
|
+
throw new errors_1.UsageError('invalid-response', true);
|
|
48
|
+
chunks.push(value);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new errors_1.UsageError('invalid-response', true);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await reader.cancel().catch(() => { });
|
|
59
|
+
reader.releaseLock();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withFreshness = withFreshness;
|
|
4
|
+
exports.backoff = backoff;
|
|
5
|
+
function withFreshness(row, now) {
|
|
6
|
+
if (!row.observedAt)
|
|
7
|
+
return { ...row, freshness: 'unknown' };
|
|
8
|
+
const expired = now - Date.parse(row.observedAt) >= 300_000 || row.windows.some(w => w.resetsAt && Date.parse(w.resetsAt) <= now)
|
|
9
|
+
|| !!row.spend?.resetsAt && Date.parse(row.spend.resetsAt) <= now;
|
|
10
|
+
return { ...row, freshness: expired || row.refreshState === 'error' ? 'stale' : 'fresh' };
|
|
11
|
+
}
|
|
12
|
+
function backoff(failures) { return Math.min(300_000, 30_000 * 2 ** Math.min(4, Math.max(0, failures - 1))); }
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.duration = exports.percentage = exports.record = exports.USAGE_PROVIDERS = void 0;
|
|
4
|
+
exports.timestamp = timestamp;
|
|
5
|
+
exports.windowLabel = windowLabel;
|
|
6
|
+
exports.normalizeClaude = normalizeClaude;
|
|
7
|
+
exports.claudePlan = claudePlan;
|
|
8
|
+
exports.hasClaudeWindows = hasClaudeWindows;
|
|
9
|
+
exports.normalizeClaudeSpend = normalizeClaudeSpend;
|
|
10
|
+
exports.normalizeCodex = normalizeCodex;
|
|
11
|
+
exports.initialUsage = initialUsage;
|
|
12
|
+
exports.USAGE_PROVIDERS = ['claude', 'codex'];
|
|
13
|
+
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
14
|
+
exports.record = record;
|
|
15
|
+
const percentage = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100 ? value : null;
|
|
16
|
+
exports.percentage = percentage;
|
|
17
|
+
const duration = (value) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : null;
|
|
18
|
+
exports.duration = duration;
|
|
19
|
+
function timestamp(value, seconds = false) {
|
|
20
|
+
const time = typeof value === 'number' && Number.isFinite(value) ? value * (seconds ? 1000 : 1) : typeof value === 'string' ? Date.parse(value) : NaN;
|
|
21
|
+
return Number.isFinite(time) && time > 0 && time <= 8.64e15 ? new Date(time).toISOString() : null;
|
|
22
|
+
}
|
|
23
|
+
function windowLabel(minutes) {
|
|
24
|
+
return minutes === 300 ? 'session' : minutes === 10080 ? 'weekly' : minutes === 43200 ? 'monthly' : 'window';
|
|
25
|
+
}
|
|
26
|
+
function normalizeClaude(payload) {
|
|
27
|
+
const data = (0, exports.record)(payload);
|
|
28
|
+
const windows = [];
|
|
29
|
+
for (const [key, raw] of Object.entries(data)) {
|
|
30
|
+
if (!/^(five_hour|seven_day(?:_[a-z0-9_]+)?|fable_(weekly|seven_day))$/.test(key) || raw === null)
|
|
31
|
+
continue;
|
|
32
|
+
const value = (0, exports.record)(raw);
|
|
33
|
+
if (!['utilization', 'used_percentage', 'resets_at'].some(field => Object.hasOwn(value, field)))
|
|
34
|
+
continue;
|
|
35
|
+
const minutes = key === 'five_hour' ? 300 : 10080;
|
|
36
|
+
const model = key.startsWith('seven_day_') ? key.slice(10) : key.startsWith('fable_') ? 'fable' : null;
|
|
37
|
+
windows.push({ id: key, label: windowLabel(minutes), scope: model ? 'model' : 'account', model,
|
|
38
|
+
usedPercent: (0, exports.percentage)(value.utilization ?? value.used_percentage), durationMinutes: minutes, resetsAt: timestamp(value.resets_at, typeof value.resets_at === 'number') });
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(data.limits))
|
|
41
|
+
for (const raw of data.limits.slice(0, 32)) {
|
|
42
|
+
const value = (0, exports.record)(raw), model = (0, exports.record)((0, exports.record)(value.scope).model).display_name;
|
|
43
|
+
if (value.kind !== 'weekly_scoped' || typeof model !== 'string' || !model.trim())
|
|
44
|
+
continue;
|
|
45
|
+
const name = model.trim().slice(0, 80);
|
|
46
|
+
if (windows.some(w => w.model?.toLowerCase() === name.toLowerCase()))
|
|
47
|
+
continue;
|
|
48
|
+
windows.push({ id: `model:${name}`, label: 'weekly', scope: 'model', model: name,
|
|
49
|
+
usedPercent: (0, exports.percentage)(value.percent), durationMinutes: 10080, resetsAt: timestamp(value.resets_at, true) });
|
|
50
|
+
}
|
|
51
|
+
return windows;
|
|
52
|
+
}
|
|
53
|
+
function claudePlan(value) {
|
|
54
|
+
if (typeof value !== 'string')
|
|
55
|
+
return null;
|
|
56
|
+
const words = value.trim().toLowerCase().split(/[^a-z0-9]+/);
|
|
57
|
+
return ['enterprise', 'team', 'max', 'pro', 'ultra', 'free'].find(plan => words.includes(plan)) ?? null;
|
|
58
|
+
}
|
|
59
|
+
function hasClaudeWindows(windows) {
|
|
60
|
+
return windows.some(window => window.usedPercent !== null || window.resetsAt !== null);
|
|
61
|
+
}
|
|
62
|
+
function normalizeClaudeSpend(payload, plan, now) {
|
|
63
|
+
if (plan !== 'enterprise' || hasClaudeWindows(normalizeClaude(payload)))
|
|
64
|
+
return null;
|
|
65
|
+
const extra = (0, exports.record)((0, exports.record)(payload).extra_usage);
|
|
66
|
+
const amount = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
67
|
+
const used = amount(extra.used_credits), limit = amount(extra.monthly_limit);
|
|
68
|
+
if (used === null && limit === null)
|
|
69
|
+
return null;
|
|
70
|
+
const currency = extra.currency == null ? 'USD' : typeof extra.currency === 'string' ? extra.currency.trim().toUpperCase() : '';
|
|
71
|
+
if (!/^[A-Z]{3}$/.test(currency))
|
|
72
|
+
return null;
|
|
73
|
+
// OAuth spend is in currency minor units, including fractional minor units.
|
|
74
|
+
const exponent = new Intl.NumberFormat('en', { style: 'currency', currency }).resolvedOptions().maximumFractionDigits ?? 2;
|
|
75
|
+
const divisor = 10 ** exponent;
|
|
76
|
+
const usedAmount = used === null ? null : used / divisor, limitAmount = limit === null ? null : limit / divisor;
|
|
77
|
+
const limitStatus = limit !== null ? 'limited' : extra.monthly_limit === null && extra.is_enabled === true ? 'unlimited' : 'unknown';
|
|
78
|
+
const ratio = used !== null && limit !== null && limit > 0 ? used / limit * 100 : null;
|
|
79
|
+
const usedPercent = limitStatus === 'limited' ? amount(extra.utilization) ?? (ratio !== null && Number.isFinite(ratio) ? ratio : null) : null;
|
|
80
|
+
const date = new Date(now);
|
|
81
|
+
return { kind: 'enterprise-on-demand', usedAmount, limitAmount, limitStatus, currency, usedPercent,
|
|
82
|
+
resetsAt: timestamp(extra.resets_at, typeof extra.resets_at === 'number') ?? new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1)).toISOString() };
|
|
83
|
+
}
|
|
84
|
+
function normalizeCodex(payload) {
|
|
85
|
+
const data = (0, exports.record)(payload), groups = (0, exports.record)(data.rateLimitsByLimitId);
|
|
86
|
+
const entries = Object.keys(groups).length ? Object.entries(groups) : [['codex', data.rateLimits]];
|
|
87
|
+
const windows = [];
|
|
88
|
+
for (const [group, raw] of entries.slice(0, 32)) {
|
|
89
|
+
const bucket = (0, exports.record)(raw);
|
|
90
|
+
for (const slot of ['primary', 'secondary']) {
|
|
91
|
+
if (!bucket[slot] || typeof bucket[slot] !== 'object')
|
|
92
|
+
continue;
|
|
93
|
+
const value = (0, exports.record)(bucket[slot]), minutes = (0, exports.duration)(value.windowDurationMins);
|
|
94
|
+
windows.push({ id: `${group}:${slot}`, label: windowLabel(minutes), scope: group === 'codex' ? 'account' : 'model',
|
|
95
|
+
model: group === 'codex' ? null : (typeof bucket.limitName === 'string' ? bucket.limitName : group).slice(0, 80),
|
|
96
|
+
usedPercent: (0, exports.percentage)(value.usedPercent), durationMinutes: minutes, resetsAt: timestamp(value.resetsAt, true) });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return windows;
|
|
100
|
+
}
|
|
101
|
+
function initialUsage(providerId, generation) {
|
|
102
|
+
return { providerId, installed: null, generation, availability: 'unavailable', refreshState: 'idle', freshness: 'unknown', plan: null,
|
|
103
|
+
windows: [], spend: null, source: null, observedAt: null, attemptedAt: null, retryAt: null, issue: null };
|
|
104
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDesktopUsageService = createDesktopUsageService;
|
|
4
|
+
const provider_detection_1 = require("../../../provider-detection");
|
|
5
|
+
const claude_1 = require("../adapters/claude");
|
|
6
|
+
const codex_1 = require("../adapters/codex");
|
|
7
|
+
const usage_service_1 = require("./usage-service");
|
|
8
|
+
function createDesktopUsageService() {
|
|
9
|
+
return (0, usage_service_1.createUsageService)({
|
|
10
|
+
installed: async (id) => {
|
|
11
|
+
const provider = (await (0, provider_detection_1.getDetectionSnapshot)()).providers[id];
|
|
12
|
+
return !!provider?.installed;
|
|
13
|
+
},
|
|
14
|
+
eligible: async (id) => {
|
|
15
|
+
const provider = (await (0, provider_detection_1.getDetectionSnapshot)()).providers[id];
|
|
16
|
+
return !!provider?.executable && provider.meetsMinimum !== false && !provider.vetoed;
|
|
17
|
+
},
|
|
18
|
+
readers: { claude: (0, claude_1.createClaudeReader)(), codex: (0, codex_1.createCodexReader)() },
|
|
19
|
+
});
|
|
20
|
+
}
|