@stigmer/react 3.3.0 → 3.4.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.
Files changed (344) hide show
  1. package/agent/useAgentCount.d.ts +2 -0
  2. package/agent/useAgentCount.d.ts.map +1 -1
  3. package/agent/useAgentCount.js.map +1 -1
  4. package/api-key/CreateApiKeyForm.d.ts +7 -1
  5. package/api-key/CreateApiKeyForm.d.ts.map +1 -1
  6. package/api-key/CreateApiKeyForm.js +2 -2
  7. package/api-key/CreateApiKeyForm.js.map +1 -1
  8. package/billing/index.d.ts +0 -18
  9. package/billing/index.d.ts.map +1 -1
  10. package/billing/index.js +4 -9
  11. package/billing/index.js.map +1 -1
  12. package/cursor-accounts/CursorAccountEditor.d.ts +7 -1
  13. package/cursor-accounts/CursorAccountEditor.d.ts.map +1 -1
  14. package/cursor-accounts/CursorAccountEditor.js +21 -5
  15. package/cursor-accounts/CursorAccountEditor.js.map +1 -1
  16. package/cursor-accounts/CursorAccountsConsole.d.ts +9 -5
  17. package/cursor-accounts/CursorAccountsConsole.d.ts.map +1 -1
  18. package/cursor-accounts/CursorAccountsConsole.js +34 -140
  19. package/cursor-accounts/CursorAccountsConsole.js.map +1 -1
  20. package/cursor-accounts/MemberCoverageTable.d.ts +29 -0
  21. package/cursor-accounts/MemberCoverageTable.d.ts.map +1 -0
  22. package/cursor-accounts/MemberCoverageTable.js +150 -0
  23. package/cursor-accounts/MemberCoverageTable.js.map +1 -0
  24. package/cursor-accounts/MemberKeysPanel.d.ts +25 -0
  25. package/cursor-accounts/MemberKeysPanel.d.ts.map +1 -0
  26. package/cursor-accounts/MemberKeysPanel.js +115 -0
  27. package/cursor-accounts/MemberKeysPanel.js.map +1 -0
  28. package/cursor-accounts/badges.d.ts +9 -0
  29. package/cursor-accounts/badges.d.ts.map +1 -0
  30. package/cursor-accounts/badges.js +10 -0
  31. package/cursor-accounts/badges.js.map +1 -0
  32. package/cursor-accounts/cursor-account-coverage.d.ts +41 -0
  33. package/cursor-accounts/cursor-account-coverage.d.ts.map +1 -0
  34. package/cursor-accounts/cursor-account-coverage.js +26 -0
  35. package/cursor-accounts/cursor-account-coverage.js.map +1 -0
  36. package/cursor-accounts/cursor-account-format.d.ts +13 -0
  37. package/cursor-accounts/cursor-account-format.d.ts.map +1 -1
  38. package/cursor-accounts/cursor-account-format.js +16 -1
  39. package/cursor-accounts/cursor-account-format.js.map +1 -1
  40. package/datastore/CollectionRecordsBrowser.js +18 -4
  41. package/datastore/CollectionRecordsBrowser.js.map +1 -1
  42. package/datastore/RecordFilterBuilder.d.ts +8 -1
  43. package/datastore/RecordFilterBuilder.d.ts.map +1 -1
  44. package/datastore/RecordFilterBuilder.js +2 -2
  45. package/datastore/RecordFilterBuilder.js.map +1 -1
  46. package/datastore/recordFilter.d.ts +9 -1
  47. package/datastore/recordFilter.d.ts.map +1 -1
  48. package/datastore/recordFilter.js +12 -1
  49. package/datastore/recordFilter.js.map +1 -1
  50. package/datastore/useDatastoreCount.d.ts +2 -0
  51. package/datastore/useDatastoreCount.d.ts.map +1 -1
  52. package/datastore/useDatastoreCount.js.map +1 -1
  53. package/execution/ExecutionProgress.d.ts.map +1 -1
  54. package/execution/ExecutionProgress.js +13 -4
  55. package/execution/ExecutionProgress.js.map +1 -1
  56. package/execution/MessageThread.d.ts.map +1 -1
  57. package/execution/MessageThread.js +10 -2
  58. package/execution/MessageThread.js.map +1 -1
  59. package/execution/useCreateAgentExecution.d.ts +31 -1
  60. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  61. package/execution/useCreateAgentExecution.js +2 -0
  62. package/execution/useCreateAgentExecution.js.map +1 -1
  63. package/index.d.ts +8 -6
  64. package/index.d.ts.map +1 -1
  65. package/index.js +8 -4
  66. package/index.js.map +1 -1
  67. package/internal/form-primitives.d.ts +10 -0
  68. package/internal/form-primitives.d.ts.map +1 -0
  69. package/{billing → internal}/form-primitives.js +5 -9
  70. package/internal/form-primitives.js.map +1 -0
  71. package/manifest/ApplyManifestDialog.d.ts +10 -5
  72. package/manifest/ApplyManifestDialog.d.ts.map +1 -1
  73. package/manifest/ApplyManifestDialog.js +30 -13
  74. package/manifest/ApplyManifestDialog.js.map +1 -1
  75. package/manifest/useApplyManifest.d.ts +6 -2
  76. package/manifest/useApplyManifest.d.ts.map +1 -1
  77. package/manifest/useApplyManifest.js +5 -5
  78. package/manifest/useApplyManifest.js.map +1 -1
  79. package/mcp-server/McpServerDetailView.d.ts +38 -2
  80. package/mcp-server/McpServerDetailView.d.ts.map +1 -1
  81. package/mcp-server/McpServerDetailView.js +21 -5
  82. package/mcp-server/McpServerDetailView.js.map +1 -1
  83. package/mcp-server/index.d.ts +7 -0
  84. package/mcp-server/index.d.ts.map +1 -1
  85. package/mcp-server/index.js +9 -0
  86. package/mcp-server/index.js.map +1 -1
  87. package/mcp-server/steps/EnvironmentAuthStep.d.ts +4 -0
  88. package/mcp-server/steps/EnvironmentAuthStep.d.ts.map +1 -1
  89. package/mcp-server/steps/EnvironmentAuthStep.js +4 -0
  90. package/mcp-server/steps/EnvironmentAuthStep.js.map +1 -1
  91. package/mcp-server/steps/IdentityTransportStep.d.ts +6 -0
  92. package/mcp-server/steps/IdentityTransportStep.d.ts.map +1 -1
  93. package/mcp-server/steps/IdentityTransportStep.js +7 -1
  94. package/mcp-server/steps/IdentityTransportStep.js.map +1 -1
  95. package/mcp-server/steps/ReviewStep.d.ts +5 -0
  96. package/mcp-server/steps/ReviewStep.d.ts.map +1 -1
  97. package/mcp-server/steps/ReviewStep.js +16 -2
  98. package/mcp-server/steps/ReviewStep.js.map +1 -1
  99. package/mcp-server/useMcpServerCount.d.ts +2 -0
  100. package/mcp-server/useMcpServerCount.d.ts.map +1 -1
  101. package/mcp-server/useMcpServerCount.js.map +1 -1
  102. package/package.json +4 -4
  103. package/pricing-governance/BaselineEditor.d.ts.map +1 -0
  104. package/{billing → pricing-governance}/BaselineEditor.js +19 -2
  105. package/pricing-governance/BaselineEditor.js.map +1 -0
  106. package/pricing-governance/ModelCatalogPanel.d.ts.map +1 -0
  107. package/pricing-governance/ModelCatalogPanel.js.map +1 -0
  108. package/pricing-governance/ModelGovernanceDetail.d.ts.map +1 -0
  109. package/pricing-governance/ModelGovernanceDetail.js.map +1 -0
  110. package/pricing-governance/OperatorAccessNotice.d.ts.map +1 -0
  111. package/pricing-governance/OperatorAccessNotice.js.map +1 -0
  112. package/pricing-governance/PricingGovernanceConsole.d.ts.map +1 -0
  113. package/{billing → pricing-governance}/PricingGovernanceConsole.js +1 -1
  114. package/pricing-governance/PricingGovernanceConsole.js.map +1 -0
  115. package/pricing-governance/PricingGovernancePanel.d.ts.map +1 -0
  116. package/pricing-governance/PricingGovernancePanel.js.map +1 -0
  117. package/pricing-governance/RetireConfirm.d.ts.map +1 -0
  118. package/{billing → pricing-governance}/RetireConfirm.js +1 -1
  119. package/pricing-governance/RetireConfirm.js.map +1 -0
  120. package/pricing-governance/governance-primitives.d.ts.map +1 -0
  121. package/pricing-governance/governance-primitives.js.map +1 -0
  122. package/pricing-governance/index.d.ts +19 -0
  123. package/pricing-governance/index.d.ts.map +1 -0
  124. package/pricing-governance/index.js +20 -0
  125. package/pricing-governance/index.js.map +1 -0
  126. package/pricing-governance/pricing-format.d.ts.map +1 -0
  127. package/pricing-governance/pricing-format.js.map +1 -0
  128. package/pricing-governance/useDecidePricingOverride.d.ts.map +1 -0
  129. package/pricing-governance/useDecidePricingOverride.js.map +1 -0
  130. package/pricing-governance/useModelGovernanceView.d.ts.map +1 -0
  131. package/pricing-governance/useModelGovernanceView.js.map +1 -0
  132. package/pricing-governance/useModelPricingBaselines.d.ts.map +1 -0
  133. package/pricing-governance/useModelPricingBaselines.js.map +1 -0
  134. package/pricing-governance/usePricingGovernance.d.ts.map +1 -0
  135. package/pricing-governance/usePricingGovernance.js.map +1 -0
  136. package/pricing-governance/useRetireModelPricingBaseline.d.ts.map +1 -0
  137. package/pricing-governance/useRetireModelPricingBaseline.js.map +1 -0
  138. package/pricing-governance/useUpsertModelPricingBaseline.d.ts.map +1 -0
  139. package/pricing-governance/useUpsertModelPricingBaseline.js.map +1 -0
  140. package/resource-creation/CreationPicker.d.ts.map +1 -1
  141. package/resource-creation/CreationPicker.js +3 -3
  142. package/resource-creation/CreationPicker.js.map +1 -1
  143. package/resource-creation/WizardNav.d.ts.map +1 -1
  144. package/resource-creation/WizardNav.js +1 -1
  145. package/resource-creation/WizardNav.js.map +1 -1
  146. package/resource-detail/Section.d.ts +8 -1
  147. package/resource-detail/Section.d.ts.map +1 -1
  148. package/resource-detail/Section.js +2 -2
  149. package/resource-detail/Section.js.map +1 -1
  150. package/resource-workbench/components/ResourceWorkbench.d.ts +9 -1
  151. package/resource-workbench/components/ResourceWorkbench.d.ts.map +1 -1
  152. package/resource-workbench/components/ResourceWorkbench.js +2 -1
  153. package/resource-workbench/components/ResourceWorkbench.js.map +1 -1
  154. package/resource-workbench/hooks/useResourceCollection.d.ts +9 -0
  155. package/resource-workbench/hooks/useResourceCollection.d.ts.map +1 -1
  156. package/resource-workbench/hooks/useResourceCollection.js +2 -2
  157. package/resource-workbench/hooks/useResourceCollection.js.map +1 -1
  158. package/search/useResourceCount.d.ts +7 -0
  159. package/search/useResourceCount.d.ts.map +1 -1
  160. package/search/useResourceCount.js +2 -1
  161. package/search/useResourceCount.js.map +1 -1
  162. package/session/NewSessionViewer.d.ts.map +1 -1
  163. package/session/NewSessionViewer.js +17 -3
  164. package/session/NewSessionViewer.js.map +1 -1
  165. package/session/SessionViewer.d.ts +2 -1
  166. package/session/SessionViewer.d.ts.map +1 -1
  167. package/session/SessionViewer.js +19 -11
  168. package/session/SessionViewer.js.map +1 -1
  169. package/session/SessionViewerLayout.d.ts +90 -0
  170. package/session/SessionViewerLayout.d.ts.map +1 -0
  171. package/session/SessionViewerLayout.js +32 -0
  172. package/session/SessionViewerLayout.js.map +1 -0
  173. package/session/index.d.ts +4 -0
  174. package/session/index.d.ts.map +1 -1
  175. package/session/index.js +10 -0
  176. package/session/index.js.map +1 -1
  177. package/session/useCreateSession.d.ts +28 -0
  178. package/session/useCreateSession.d.ts.map +1 -1
  179. package/session/useCreateSession.js +2 -1
  180. package/session/useCreateSession.js.map +1 -1
  181. package/session/useNewSessionFlow.d.ts +30 -0
  182. package/session/useNewSessionFlow.d.ts.map +1 -1
  183. package/session/useNewSessionFlow.js +7 -1
  184. package/session/useNewSessionFlow.js.map +1 -1
  185. package/session/useSessionRailViews.d.ts +2 -2
  186. package/session/useUpdateSession.d.ts +29 -10
  187. package/session/useUpdateSession.d.ts.map +1 -1
  188. package/session/useUpdateSession.js +29 -10
  189. package/session/useUpdateSession.js.map +1 -1
  190. package/skill/useSkillCount.d.ts +2 -0
  191. package/skill/useSkillCount.d.ts.map +1 -1
  192. package/skill/useSkillCount.js.map +1 -1
  193. package/src/agent/useAgentCount.ts +2 -0
  194. package/src/api-key/CreateApiKeyForm.tsx +8 -1
  195. package/src/api-key/__tests__/CreateApiKeyForm.test.tsx +58 -0
  196. package/src/billing/index.ts +5 -37
  197. package/src/cursor-accounts/CursorAccountEditor.tsx +61 -13
  198. package/src/cursor-accounts/CursorAccountsConsole.tsx +44 -400
  199. package/src/cursor-accounts/MemberCoverageTable.tsx +439 -0
  200. package/src/cursor-accounts/MemberKeysPanel.tsx +278 -0
  201. package/src/cursor-accounts/__tests__/CursorAccountsConsole.test.tsx +306 -10
  202. package/src/cursor-accounts/__tests__/cursor-account-coverage.test.ts +73 -0
  203. package/src/cursor-accounts/badges.tsx +26 -0
  204. package/src/cursor-accounts/cursor-account-coverage.ts +71 -0
  205. package/src/cursor-accounts/cursor-account-format.ts +17 -1
  206. package/src/datastore/CollectionRecordsBrowser.tsx +29 -3
  207. package/src/datastore/RecordFilterBuilder.tsx +12 -1
  208. package/src/datastore/__tests__/CollectionRecordsBrowser.test.tsx +38 -0
  209. package/src/datastore/__tests__/RecordFilterBuilder.test.tsx +14 -0
  210. package/src/datastore/__tests__/recordFilter.test.ts +13 -0
  211. package/src/datastore/recordFilter.ts +15 -1
  212. package/src/datastore/useDatastoreCount.ts +2 -0
  213. package/src/execution/ExecutionProgress.tsx +14 -4
  214. package/src/execution/MessageThread.tsx +10 -2
  215. package/src/execution/__tests__/ExecutionProgress.test.tsx +67 -0
  216. package/src/execution/__tests__/MessageThread.test.tsx +44 -0
  217. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +86 -0
  218. package/src/execution/useCreateAgentExecution.ts +41 -6
  219. package/src/index.ts +43 -24
  220. package/src/{billing → internal}/form-primitives.tsx +4 -32
  221. package/src/manifest/ApplyManifestDialog.tsx +40 -16
  222. package/src/manifest/__tests__/ApplyManifestDialog.test.tsx +147 -0
  223. package/src/manifest/__tests__/useApplyManifest.test.tsx +6 -6
  224. package/src/manifest/useApplyManifest.ts +12 -8
  225. package/src/mcp-server/McpServerDetailView.tsx +52 -4
  226. package/src/mcp-server/__tests__/McpServerDetailView.test.tsx +345 -0
  227. package/src/mcp-server/index.ts +13 -0
  228. package/src/mcp-server/steps/EnvironmentAuthStep.tsx +4 -0
  229. package/src/mcp-server/steps/IdentityTransportStep.tsx +9 -1
  230. package/src/mcp-server/steps/ReviewStep.tsx +18 -1
  231. package/src/mcp-server/useMcpServerCount.ts +2 -0
  232. package/src/{billing → pricing-governance}/BaselineEditor.tsx +36 -3
  233. package/src/{billing → pricing-governance}/PricingGovernanceConsole.tsx +1 -1
  234. package/src/{billing → pricing-governance}/RetireConfirm.tsx +1 -1
  235. package/src/pricing-governance/index.ts +50 -0
  236. package/src/resource-creation/CreationPicker.tsx +7 -0
  237. package/src/resource-creation/WizardNav.tsx +1 -0
  238. package/src/resource-detail/Section.tsx +9 -1
  239. package/src/resource-workbench/components/ResourceWorkbench.tsx +13 -0
  240. package/src/resource-workbench/hooks/useResourceCollection.ts +11 -1
  241. package/src/search/useResourceCount.ts +9 -1
  242. package/src/session/NewSessionViewer.tsx +48 -61
  243. package/src/session/SessionViewer.tsx +71 -86
  244. package/src/session/SessionViewerLayout.tsx +131 -0
  245. package/src/session/__tests__/SessionViewerLayout.test.tsx +140 -0
  246. package/src/session/__tests__/useCreateSession.test.tsx +94 -0
  247. package/src/session/__tests__/useNewSessionFlow.test.tsx +33 -0
  248. package/src/session/index.ts +14 -0
  249. package/src/session/useCreateSession.ts +30 -0
  250. package/src/session/useNewSessionFlow.ts +45 -1
  251. package/src/session/useSessionRailViews.tsx +2 -2
  252. package/src/session/useUpdateSession.ts +29 -10
  253. package/src/skill/useSkillCount.ts +2 -0
  254. package/src/test/__tests__/samples.test.ts +159 -2
  255. package/src/test/index.ts +1 -1
  256. package/src/test/samples.ts +109 -14
  257. package/src/workflow/useWorkflowCount.ts +2 -0
  258. package/styles.css +1 -1
  259. package/test/index.d.ts +1 -1
  260. package/test/index.d.ts.map +1 -1
  261. package/test/index.js +1 -1
  262. package/test/index.js.map +1 -1
  263. package/test/samples.d.ts +79 -7
  264. package/test/samples.d.ts.map +1 -1
  265. package/test/samples.js +101 -14
  266. package/test/samples.js.map +1 -1
  267. package/workflow/useWorkflowCount.d.ts +2 -0
  268. package/workflow/useWorkflowCount.d.ts.map +1 -1
  269. package/workflow/useWorkflowCount.js.map +1 -1
  270. package/billing/BaselineEditor.d.ts.map +0 -1
  271. package/billing/BaselineEditor.js.map +0 -1
  272. package/billing/ModelCatalogPanel.d.ts.map +0 -1
  273. package/billing/ModelCatalogPanel.js.map +0 -1
  274. package/billing/ModelGovernanceDetail.d.ts.map +0 -1
  275. package/billing/ModelGovernanceDetail.js.map +0 -1
  276. package/billing/OperatorAccessNotice.d.ts.map +0 -1
  277. package/billing/OperatorAccessNotice.js.map +0 -1
  278. package/billing/PricingGovernanceConsole.d.ts.map +0 -1
  279. package/billing/PricingGovernanceConsole.js.map +0 -1
  280. package/billing/PricingGovernancePanel.d.ts.map +0 -1
  281. package/billing/PricingGovernancePanel.js.map +0 -1
  282. package/billing/RetireConfirm.d.ts.map +0 -1
  283. package/billing/RetireConfirm.js.map +0 -1
  284. package/billing/form-primitives.d.ts +0 -18
  285. package/billing/form-primitives.d.ts.map +0 -1
  286. package/billing/form-primitives.js.map +0 -1
  287. package/billing/governance-primitives.d.ts.map +0 -1
  288. package/billing/governance-primitives.js.map +0 -1
  289. package/billing/pricing-format.d.ts.map +0 -1
  290. package/billing/pricing-format.js.map +0 -1
  291. package/billing/useDecidePricingOverride.d.ts.map +0 -1
  292. package/billing/useDecidePricingOverride.js.map +0 -1
  293. package/billing/useModelGovernanceView.d.ts.map +0 -1
  294. package/billing/useModelGovernanceView.js.map +0 -1
  295. package/billing/useModelPricingBaselines.d.ts.map +0 -1
  296. package/billing/useModelPricingBaselines.js.map +0 -1
  297. package/billing/usePricingGovernance.d.ts.map +0 -1
  298. package/billing/usePricingGovernance.js.map +0 -1
  299. package/billing/useRetireModelPricingBaseline.d.ts.map +0 -1
  300. package/billing/useRetireModelPricingBaseline.js.map +0 -1
  301. package/billing/useUpsertModelPricingBaseline.d.ts.map +0 -1
  302. package/billing/useUpsertModelPricingBaseline.js.map +0 -1
  303. /package/{billing → pricing-governance}/BaselineEditor.d.ts +0 -0
  304. /package/{billing → pricing-governance}/ModelCatalogPanel.d.ts +0 -0
  305. /package/{billing → pricing-governance}/ModelCatalogPanel.js +0 -0
  306. /package/{billing → pricing-governance}/ModelGovernanceDetail.d.ts +0 -0
  307. /package/{billing → pricing-governance}/ModelGovernanceDetail.js +0 -0
  308. /package/{billing → pricing-governance}/OperatorAccessNotice.d.ts +0 -0
  309. /package/{billing → pricing-governance}/OperatorAccessNotice.js +0 -0
  310. /package/{billing → pricing-governance}/PricingGovernanceConsole.d.ts +0 -0
  311. /package/{billing → pricing-governance}/PricingGovernancePanel.d.ts +0 -0
  312. /package/{billing → pricing-governance}/PricingGovernancePanel.js +0 -0
  313. /package/{billing → pricing-governance}/RetireConfirm.d.ts +0 -0
  314. /package/{billing → pricing-governance}/governance-primitives.d.ts +0 -0
  315. /package/{billing → pricing-governance}/governance-primitives.js +0 -0
  316. /package/{billing → pricing-governance}/pricing-format.d.ts +0 -0
  317. /package/{billing → pricing-governance}/pricing-format.js +0 -0
  318. /package/{billing → pricing-governance}/useDecidePricingOverride.d.ts +0 -0
  319. /package/{billing → pricing-governance}/useDecidePricingOverride.js +0 -0
  320. /package/{billing → pricing-governance}/useModelGovernanceView.d.ts +0 -0
  321. /package/{billing → pricing-governance}/useModelGovernanceView.js +0 -0
  322. /package/{billing → pricing-governance}/useModelPricingBaselines.d.ts +0 -0
  323. /package/{billing → pricing-governance}/useModelPricingBaselines.js +0 -0
  324. /package/{billing → pricing-governance}/usePricingGovernance.d.ts +0 -0
  325. /package/{billing → pricing-governance}/usePricingGovernance.js +0 -0
  326. /package/{billing → pricing-governance}/useRetireModelPricingBaseline.d.ts +0 -0
  327. /package/{billing → pricing-governance}/useRetireModelPricingBaseline.js +0 -0
  328. /package/{billing → pricing-governance}/useUpsertModelPricingBaseline.d.ts +0 -0
  329. /package/{billing → pricing-governance}/useUpsertModelPricingBaseline.js +0 -0
  330. /package/src/{billing → pricing-governance}/ModelCatalogPanel.tsx +0 -0
  331. /package/src/{billing → pricing-governance}/ModelGovernanceDetail.tsx +0 -0
  332. /package/src/{billing → pricing-governance}/OperatorAccessNotice.tsx +0 -0
  333. /package/src/{billing → pricing-governance}/PricingGovernancePanel.tsx +0 -0
  334. /package/src/{billing → pricing-governance}/__tests__/ModelCatalogPanel.test.tsx +0 -0
  335. /package/src/{billing → pricing-governance}/__tests__/PricingGovernanceConsole.test.tsx +0 -0
  336. /package/src/{billing → pricing-governance}/__tests__/useModelGovernanceView.test.tsx +0 -0
  337. /package/src/{billing → pricing-governance}/governance-primitives.tsx +0 -0
  338. /package/src/{billing → pricing-governance}/pricing-format.ts +0 -0
  339. /package/src/{billing → pricing-governance}/useDecidePricingOverride.ts +0 -0
  340. /package/src/{billing → pricing-governance}/useModelGovernanceView.ts +0 -0
  341. /package/src/{billing → pricing-governance}/useModelPricingBaselines.ts +0 -0
  342. /package/src/{billing → pricing-governance}/usePricingGovernance.ts +0 -0
  343. /package/src/{billing → pricing-governance}/useRetireModelPricingBaseline.ts +0 -0
  344. /package/src/{billing → pricing-governance}/useUpsertModelPricingBaseline.ts +0 -0
@@ -0,0 +1,278 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { cn } from "@stigmer/theme";
5
+ import { getUserMessage } from "@stigmer/sdk";
6
+ import type { CursorAccountView } from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/io_pb";
7
+ import { Button } from "../button/index.js";
8
+ import { INPUT_CLASSES } from "../internal/form-primitives.js";
9
+ import { toError } from "../internal/toError.js";
10
+ import type { CursorAccountCoverage } from "./cursor-account-coverage.js";
11
+ import { MemberCoverageTable } from "./MemberCoverageTable.js";
12
+ import type { useCursorMemberKeyActions } from "./useCursorMemberKeyActions.js";
13
+
14
+ /** One line's outcome from a bulk key import. */
15
+ interface ImportLineResult {
16
+ /** Masked identification of the key (never the full material). */
17
+ readonly keyPreview: string;
18
+ readonly ok: boolean;
19
+ /** Bound email on success; the server/Cursor error on failure. */
20
+ readonly message: string;
21
+ }
22
+
23
+ /** Parse bulk-import text: one key per line, trimmed, deduped. */
24
+ function parseImportKeys(text: string): string[] {
25
+ return [...new Set(text.split(/\r?\n/).map((s) => s.trim()).filter((s) => s !== ""))];
26
+ }
27
+
28
+ function maskKey(key: string, index: number): string {
29
+ return key.length >= 8 ? `key ${index + 1} (…${key.slice(-4)})` : `key ${index + 1}`;
30
+ }
31
+
32
+ /**
33
+ * The team-coverage panel of one Cursor account: the roster-coverage
34
+ * table (every member and key in three explicit categories) plus the
35
+ * add-key and bulk-import forms.
36
+ *
37
+ * Bulk import is sequential on purpose: each key is validated live
38
+ * against Cursor's /v1/me, and per-key errors (wrong key class, revoked,
39
+ * duplicate) must be attributable to their line. Keys whose owner turns
40
+ * out not to be on the team are still added — the post-import sync lands
41
+ * them in the table's "not on the team" category with invite guidance.
42
+ */
43
+ export function MemberKeysPanel({
44
+ accountId,
45
+ view,
46
+ coverage,
47
+ actions,
48
+ onChanged,
49
+ onImported,
50
+ }: {
51
+ readonly accountId: string;
52
+ readonly view: CursorAccountView;
53
+ /** Derived once by the parent (also feeds its header member count). */
54
+ readonly coverage: CursorAccountCoverage;
55
+ readonly actions: ReturnType<typeof useCursorMemberKeyActions>;
56
+ readonly onChanged: () => void;
57
+ /** Called after a bulk import added at least one key (triggers sync). */
58
+ readonly onImported: () => void;
59
+ }) {
60
+ const [newKey, setNewKey] = useState("");
61
+ const [newLabel, setNewLabel] = useState("");
62
+ const [showImport, setShowImport] = useState(false);
63
+ const [importText, setImportText] = useState("");
64
+ const [isImporting, setIsImporting] = useState(false);
65
+ const [importProgress, setImportProgress] = useState<
66
+ { readonly done: number; readonly total: number } | null
67
+ >(null);
68
+ const [importResults, setImportResults] = useState<readonly ImportLineResult[] | null>(
69
+ null,
70
+ );
71
+
72
+ const keyViews = view.keyViews;
73
+ const hasRows =
74
+ coverage.unclassified.length > 0 ||
75
+ coverage.onTeamWithKey.length > 0 ||
76
+ coverage.onTeamWithoutKey.length > 0 ||
77
+ coverage.offTeamWithKey.length > 0;
78
+
79
+ const submitNewKey = (e: React.FormEvent) => {
80
+ e.preventDefault();
81
+ if (newKey.trim() === "" || actions.isSubmitting) return;
82
+ void actions
83
+ .addKey({ accountId, apiKey: newKey.trim(), label: newLabel.trim() || undefined })
84
+ .then(() => {
85
+ setNewKey("");
86
+ setNewLabel("");
87
+ onChanged();
88
+ }, () => {
89
+ // Surfaced via actions.error (incl. Cursor's own key-class 401 text).
90
+ });
91
+ };
92
+
93
+ const importKeyCount = parseImportKeys(importText).length;
94
+
95
+ const submitImport = async (e: React.FormEvent) => {
96
+ e.preventDefault();
97
+ const keys = parseImportKeys(importText);
98
+ if (keys.length === 0 || isImporting) return;
99
+
100
+ setIsImporting(true);
101
+ setImportResults(null);
102
+
103
+ // Sequential on purpose: each key is validated live against Cursor's
104
+ // /v1/me, and per-key errors (wrong key class, revoked, duplicate)
105
+ // must be attributable to their line.
106
+ const knownKeyIds = new Set(
107
+ keyViews.map((kv) => kv.key?.keyId).filter((id): id is string => !!id),
108
+ );
109
+ const results: ImportLineResult[] = [];
110
+ const failedKeys: string[] = [];
111
+ for (const [i, key] of keys.entries()) {
112
+ setImportProgress({ done: i, total: keys.length });
113
+ try {
114
+ const account = await actions.addKey({ accountId, apiKey: key });
115
+ const addedKey = account.memberKeys.find((mk) => !knownKeyIds.has(mk.keyId));
116
+ if (addedKey) knownKeyIds.add(addedKey.keyId);
117
+ results.push({
118
+ keyPreview: maskKey(key, i),
119
+ ok: true,
120
+ message: addedKey?.boundEmail
121
+ ? `added — bound to ${addedKey.boundEmail}`
122
+ : "added",
123
+ });
124
+ } catch (err) {
125
+ actions.clearError();
126
+ failedKeys.push(key);
127
+ results.push({
128
+ keyPreview: maskKey(key, i),
129
+ ok: false,
130
+ message: getUserMessage(toError(err)),
131
+ });
132
+ }
133
+ }
134
+
135
+ setImportProgress(null);
136
+ setImportResults(results);
137
+ // Failed lines stay in the box so the operator can fix and retry.
138
+ setImportText(failedKeys.join("\n"));
139
+ setIsImporting(false);
140
+ if (failedKeys.length < keys.length) onImported();
141
+ };
142
+
143
+ return (
144
+ <section className="space-y-2">
145
+ <h4 className="text-xs font-semibold text-foreground">Team coverage</h4>
146
+
147
+ {keyViews.length === 0 && (
148
+ <p className="text-xs text-muted-foreground" role="status">
149
+ No execution keys — this account is <strong>not routable</strong>.
150
+ Add a member's user-scoped API key below.
151
+ </p>
152
+ )}
153
+
154
+ {hasRows && (
155
+ <MemberCoverageTable
156
+ accountId={accountId}
157
+ coverage={coverage}
158
+ inviteLink={view.account?.teamInviteLink ?? ""}
159
+ actions={actions}
160
+ onChanged={onChanged}
161
+ />
162
+ )}
163
+
164
+ {actions.error && (
165
+ <p className="text-destructive text-xs" role="alert">
166
+ {getUserMessage(actions.error)}
167
+ </p>
168
+ )}
169
+
170
+ <form className="flex flex-wrap items-end gap-2" onSubmit={submitNewKey}>
171
+ <label className="block min-w-56 flex-1 space-y-1">
172
+ <span className="text-[11px] font-medium text-muted-foreground">
173
+ User-scoped API key
174
+ </span>
175
+ <input
176
+ className={INPUT_CLASSES}
177
+ type="password"
178
+ value={newKey}
179
+ onChange={(e) => setNewKey(e.target.value)}
180
+ placeholder="the member's personal Cursor API key"
181
+ disabled={actions.isSubmitting}
182
+ autoComplete="off"
183
+ />
184
+ </label>
185
+ <label className="block min-w-40 space-y-1">
186
+ <span className="text-[11px] font-medium text-muted-foreground">
187
+ Label (optional)
188
+ </span>
189
+ <input
190
+ className={INPUT_CLASSES}
191
+ value={newLabel}
192
+ onChange={(e) => setNewLabel(e.target.value)}
193
+ placeholder="e.g. zane — prod"
194
+ disabled={actions.isSubmitting}
195
+ />
196
+ </label>
197
+ <Button type="submit" size="sm" disabled={newKey.trim() === "" || actions.isSubmitting}>
198
+ {actions.isSubmitting ? "Verifying…" : "Add key"}
199
+ </Button>
200
+ <Button
201
+ type="button"
202
+ size="sm"
203
+ variant="outline"
204
+ disabled={isImporting}
205
+ onClick={() => {
206
+ setShowImport((v) => !v);
207
+ setImportResults(null);
208
+ }}
209
+ >
210
+ {showImport ? "Hide import" : "Import keys"}
211
+ </Button>
212
+ </form>
213
+
214
+ {showImport && (
215
+ <form className="space-y-2 rounded-md border border-border bg-card p-3" onSubmit={submitImport}>
216
+ <label className="block space-y-1">
217
+ <span className="text-[11px] font-medium text-muted-foreground">
218
+ Bulk import — one user-scoped API key per line
219
+ </span>
220
+ <textarea
221
+ className={cn(INPUT_CLASSES, "min-h-24 font-mono")}
222
+ value={importText}
223
+ onChange={(e) => setImportText(e.target.value)}
224
+ placeholder={"key_...\nkey_...\nkey_..."}
225
+ disabled={isImporting}
226
+ autoComplete="off"
227
+ spellCheck={false}
228
+ />
229
+ </label>
230
+ <p className="text-[11px] text-muted-foreground">
231
+ Each key is verified against Cursor and bound to its owning team
232
+ member, one at a time. Lines that fail stay in the box so you can
233
+ fix and retry them. A roster sync runs automatically afterwards,
234
+ so the coverage table reflects current team membership.
235
+ </p>
236
+ <div className="flex items-center gap-2">
237
+ <Button type="submit" size="sm" disabled={importKeyCount === 0 || isImporting}>
238
+ {isImporting && importProgress
239
+ ? `Importing ${importProgress.done + 1} of ${importProgress.total}…`
240
+ : importKeyCount > 0
241
+ ? `Import ${importKeyCount} ${importKeyCount === 1 ? "key" : "keys"}`
242
+ : "Import keys"}
243
+ </Button>
244
+ <Button
245
+ type="button"
246
+ size="sm"
247
+ variant="outline"
248
+ disabled={isImporting}
249
+ onClick={() => {
250
+ setShowImport(false);
251
+ setImportText("");
252
+ setImportResults(null);
253
+ }}
254
+ >
255
+ Close
256
+ </Button>
257
+ </div>
258
+ {importResults && (
259
+ <ul
260
+ role="list"
261
+ aria-label="Import results"
262
+ className="m-0 list-none space-y-0.5 p-0 text-[11px]"
263
+ >
264
+ {importResults.map((result) => (
265
+ <li
266
+ key={result.keyPreview}
267
+ className={result.ok ? "text-muted-foreground" : "text-destructive"}
268
+ >
269
+ {result.keyPreview}: {result.message}
270
+ </li>
271
+ ))}
272
+ </ul>
273
+ )}
274
+ </form>
275
+ )}
276
+ </section>
277
+ );
278
+ }
@@ -17,6 +17,7 @@ import {
17
17
  CursorAccountViewSchema,
18
18
  CursorMemberKeyViewSchema,
19
19
  CursorMemberKeyState,
20
+ CursorTeamMemberViewSchema,
20
21
  } from "@stigmer/protos/ai/stigmer/platform/cursoraccount/v1/io_pb";
21
22
  import { StigmerContext } from "../../context";
22
23
  import { FetchCacheContext } from "../../internal/FetchCacheProvider";
@@ -108,6 +109,14 @@ function scenarView() {
108
109
  name: "Uncovered U",
109
110
  role: "member",
110
111
  }),
112
+ // Cursor marks departed seats role:"removed" in place; they stay
113
+ // in the roster snapshot but never in the coverage categories.
114
+ create(CursorTeamMemberSchema, {
115
+ userId: "u3",
116
+ email: "departed@scenar.ai",
117
+ name: "Departed D",
118
+ role: "removed",
119
+ }),
111
120
  ],
112
121
  spend: [
113
122
  create(CursorMemberSpendSchema, {
@@ -128,6 +137,8 @@ function scenarView() {
128
137
  }),
129
138
  }),
130
139
  ],
140
+ // The server populates both the deprecated flat list and the
141
+ // spend-joined views; the console reads only the views.
131
142
  membersWithoutKeys: [
132
143
  create(CursorTeamMemberSchema, {
133
144
  userId: "u2",
@@ -136,6 +147,21 @@ function scenarView() {
136
147
  role: "member",
137
148
  }),
138
149
  ],
150
+ membersWithoutKeysViews: [
151
+ create(CursorTeamMemberViewSchema, {
152
+ member: create(CursorTeamMemberSchema, {
153
+ userId: "u2",
154
+ email: "uncovered@scenar.ai",
155
+ name: "Uncovered U",
156
+ role: "member",
157
+ }),
158
+ spend: create(CursorMemberSpendSchema, {
159
+ userId: "u2",
160
+ email: "uncovered@scenar.ai",
161
+ includedSpendUsdMicros: 42000n,
162
+ }),
163
+ }),
164
+ ],
139
165
  });
140
166
  }
141
167
 
@@ -153,14 +179,15 @@ describe("CursorAccountsConsole", () => {
153
179
  );
154
180
  });
155
181
 
156
- it("lists accounts with routability at a glance", async () => {
182
+ it("lists accounts with routability and the derived pool class at a glance", async () => {
157
183
  const notRoutable = create(CursorAccountSummarySchema, {
158
184
  account: scenarAccount({
159
185
  accountId: "acc-2",
160
186
  displayName: "empty team",
161
187
  memberKeys: [],
188
+ // No org assignment = shared-pool account (DD-008): the class is
189
+ // derived from org_ids, never from the deprecated default flag.
162
190
  orgIds: [],
163
- isPlatformDefault: true,
164
191
  }),
165
192
  enabledKeyCount: 0,
166
193
  });
@@ -174,7 +201,7 @@ describe("CursorAccountsConsole", () => {
174
201
  await waitFor(() => expect(screen.getByText("scenar team")).toBeTruthy());
175
202
  expect(screen.getByText("empty team")).toBeTruthy();
176
203
  expect(screen.getByText("Not routable")).toBeTruthy();
177
- expect(screen.getByText("platform default")).toBeTruthy();
204
+ expect(screen.getByText("shared pool")).toBeTruthy();
178
205
  });
179
206
 
180
207
  it("shows the designed access notice on PERMISSION_DENIED", async () => {
@@ -193,7 +220,7 @@ describe("CursorAccountsConsole", () => {
193
220
  expect(screen.queryByText(/only platform operators/)).toBeNull();
194
221
  });
195
222
 
196
- it("opens the detail view with key coverage, spend, and gap list", async () => {
223
+ it("opens the detail view with the coverage table: covered and gap rows in their groups", async () => {
197
224
  const client = createMockStigmer({
198
225
  listAccounts: vi.fn().mockResolvedValue({ accounts: [scenarSummary()] }),
199
226
  getAccountView: vi.fn().mockResolvedValue(scenarView()),
@@ -203,12 +230,198 @@ describe("CursorAccountsConsole", () => {
203
230
  await waitFor(() => expect(screen.getByText("scenar team")).toBeTruthy());
204
231
  await userEvent.click(screen.getByRole("button", { name: /scenar team/ }));
205
232
 
206
- await waitFor(() => expect(screen.getByText(/zane@scenar\.ai/)).toBeTruthy());
207
- // Spend joined to the key row: 169342 micro-USD = $0.17.
208
- expect(screen.getByText(/\$0\.17 this cycle/)).toBeTruthy();
209
- // Coverage gap: the active member with no key, by name and email.
210
- expect(screen.getByText(/uncovered@scenar\.ai/)).toBeTruthy();
233
+ await waitFor(() =>
234
+ expect(screen.getByRole("table", { name: "Team coverage" })).toBeTruthy(),
235
+ );
236
+ // Category 1: on the team, key held email, key name, spend, status.
237
+ expect(screen.getByText(/On the team — key held/)).toBeTruthy();
238
+ expect(screen.getByText("zane@scenar.ai")).toBeTruthy();
239
+ expect(screen.getByText("stigmer-prod")).toBeTruthy();
240
+ // The email is the row's identity: full value always hoverable on
241
+ // both row kinds (the cell wraps rather than truncates).
242
+ expect(screen.getByTitle("zane@scenar.ai")).toBeTruthy();
243
+ expect(screen.getByTitle("uncovered@scenar.ai")).toBeTruthy();
244
+ expect(screen.getByText("$0.17")).toBeTruthy(); // included, 169342 micro-USD
211
245
  expect(screen.getByText("Active")).toBeTruthy();
246
+ // Category 2: on the team, no key — server-joined spend on the gap row.
247
+ expect(screen.getByText(/On the team — no execution key/)).toBeTruthy();
248
+ expect(screen.getByText("uncovered@scenar.ai")).toBeTruthy();
249
+ expect(screen.getByText("$0.04")).toBeTruthy(); // included, 42000 micro-USD
250
+ expect(screen.getByText("No key")).toBeTruthy();
251
+ // Both rows carry all four numeric columns: zero on-demand renders as
252
+ // a real dollar amount and zero pool percents as "0%" (matching
253
+ // Cursor's dashboard), never as em-dashes — those mean "no spend row".
254
+ expect(screen.getAllByText("$0.00")).toHaveLength(2); // on-demand × 2 rows
255
+ expect(screen.getAllByText("0%")).toHaveLength(4); // first-party + API × 2 rows
256
+ // Removed roster entries never render as coverage rows.
257
+ expect(screen.queryByText("departed@scenar.ai")).toBeNull();
258
+ // Header sync line: active members from server facts, removed seats
259
+ // by list arithmetic (roster entries minus active members).
260
+ expect(screen.getByText(/2 members · 1 removed seat/)).toBeTruthy();
261
+ });
262
+
263
+ it("affirms full coverage instead of hiding the gap group when every active member holds a key", async () => {
264
+ const coveredView = create(CursorAccountViewSchema, {
265
+ account: scenarAccount(),
266
+ snapshot: create(CursorAccountSyncSnapshotSchema, {
267
+ accountId: "acc-1",
268
+ syncedAt: timestampFromDate(new Date("2026-07-22T12:00:00Z")),
269
+ members: [
270
+ create(CursorTeamMemberSchema, {
271
+ userId: "u1",
272
+ email: "zane@scenar.ai",
273
+ name: "Zane S",
274
+ role: "owner",
275
+ }),
276
+ ],
277
+ }),
278
+ keyViews: [
279
+ create(CursorMemberKeyViewSchema, {
280
+ key: scenarAccount().memberKeys[0],
281
+ state: CursorMemberKeyState.member_key_active,
282
+ }),
283
+ ],
284
+ // A fully covered roster: the server sends no gap views.
285
+ });
286
+ const client = createMockStigmer({
287
+ listAccounts: vi.fn().mockResolvedValue({ accounts: [scenarSummary()] }),
288
+ getAccountView: vi.fn().mockResolvedValue(coveredView),
289
+ });
290
+ render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
291
+
292
+ await waitFor(() => expect(screen.getByText("scenar team")).toBeTruthy());
293
+ await userEvent.click(screen.getByRole("button", { name: /scenar team/ }));
294
+
295
+ // The gap group renders with an affirmative answer, so "fully
296
+ // covered" is distinguishable from "the sync never classified".
297
+ await waitFor(() =>
298
+ expect(screen.getByText(/On the team — no execution key/)).toBeTruthy(),
299
+ );
300
+ expect(screen.getByText(/roster is fully covered/)).toBeTruthy();
301
+ // A member with no spend row at all renders em-dash cells.
302
+ expect(screen.getAllByText("—").length).toBeGreaterThan(0);
303
+ });
304
+
305
+ it("classifies off-team keys with invite guidance; copy button only with a configured link", async () => {
306
+ const writeText = vi.fn().mockResolvedValue(undefined);
307
+ Object.defineProperty(window.navigator, "clipboard", {
308
+ value: { writeText },
309
+ configurable: true,
310
+ });
311
+ const offTeamView = create(CursorAccountViewSchema, {
312
+ account: scenarAccount({
313
+ teamInviteLink: "https://cursor.com/team-invite/abc",
314
+ }),
315
+ snapshot: create(CursorAccountSyncSnapshotSchema, {
316
+ accountId: "acc-1",
317
+ syncedAt: timestampFromDate(new Date("2026-07-22T12:00:00Z")),
318
+ }),
319
+ keyViews: [
320
+ create(CursorMemberKeyViewSchema, {
321
+ key: create(CursorMemberKeySchema, {
322
+ keyId: "k-stranger",
323
+ apiKey: "***REDACTED***",
324
+ boundEmail: "stranger@else.where",
325
+ enabled: true,
326
+ }),
327
+ state: CursorMemberKeyState.member_key_owner_unknown,
328
+ }),
329
+ create(CursorMemberKeyViewSchema, {
330
+ key: create(CursorMemberKeySchema, {
331
+ keyId: "k-gone",
332
+ apiKey: "***REDACTED***",
333
+ boundEmail: "gone@scenar.ai",
334
+ enabled: true,
335
+ }),
336
+ state: CursorMemberKeyState.member_key_owner_removed,
337
+ }),
338
+ ],
339
+ });
340
+ const client = createMockStigmer({
341
+ listAccounts: vi.fn().mockResolvedValue({ accounts: [scenarSummary()] }),
342
+ getAccountView: vi.fn().mockResolvedValue(offTeamView),
343
+ });
344
+ render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
345
+
346
+ await waitFor(() => expect(screen.getByText("scenar team")).toBeTruthy());
347
+ await userEvent.click(screen.getByRole("button", { name: /scenar team/ }));
348
+
349
+ await waitFor(() =>
350
+ expect(screen.getByText(/Key held — not on the team/)).toBeTruthy(),
351
+ );
352
+ // The two off-team causes carry distinct labels.
353
+ expect(screen.getByText("Not on team")).toBeTruthy();
354
+ expect(screen.getByText("Left team")).toBeTruthy();
355
+
356
+ // One "Copy invite" per off-team row — a copy, never a navigation.
357
+ const copyButtons = screen.getAllByRole("button", { name: "Copy invite" });
358
+ expect(copyButtons).toHaveLength(2);
359
+ await userEvent.click(copyButtons[0]);
360
+ await waitFor(() =>
361
+ expect(writeText).toHaveBeenCalledWith("https://cursor.com/team-invite/abc"),
362
+ );
363
+ expect(screen.getByRole("button", { name: "Copied" })).toBeTruthy();
364
+ });
365
+
366
+ it("points at the Cursor dashboard when no invite link is configured", async () => {
367
+ const offTeamView = create(CursorAccountViewSchema, {
368
+ account: scenarAccount(),
369
+ snapshot: create(CursorAccountSyncSnapshotSchema, {
370
+ accountId: "acc-1",
371
+ syncedAt: timestampFromDate(new Date("2026-07-22T12:00:00Z")),
372
+ }),
373
+ keyViews: [
374
+ create(CursorMemberKeyViewSchema, {
375
+ key: create(CursorMemberKeySchema, {
376
+ keyId: "k-stranger",
377
+ apiKey: "***REDACTED***",
378
+ boundEmail: "stranger@else.where",
379
+ enabled: true,
380
+ }),
381
+ state: CursorMemberKeyState.member_key_owner_unknown,
382
+ }),
383
+ ],
384
+ });
385
+ const client = createMockStigmer({
386
+ listAccounts: vi.fn().mockResolvedValue({ accounts: [scenarSummary()] }),
387
+ getAccountView: vi.fn().mockResolvedValue(offTeamView),
388
+ });
389
+ render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
390
+
391
+ await waitFor(() => expect(screen.getByText("scenar team")).toBeTruthy());
392
+ await userEvent.click(screen.getByRole("button", { name: /scenar team/ }));
393
+
394
+ await waitFor(() =>
395
+ expect(screen.getByText(/Key held — not on the team/)).toBeTruthy(),
396
+ );
397
+ expect(screen.queryByRole("button", { name: "Copy invite" })).toBeNull();
398
+ expect(screen.getByText(/paste the team's invite link/)).toBeTruthy();
399
+ });
400
+
401
+ it("withholds classification before the first sync instead of reporting keys off-team", async () => {
402
+ const unsyncedView = create(CursorAccountViewSchema, {
403
+ account: scenarAccount(),
404
+ keyViews: [
405
+ create(CursorMemberKeyViewSchema, {
406
+ key: scenarAccount().memberKeys[0],
407
+ state: CursorMemberKeyState.member_key_owner_unknown,
408
+ }),
409
+ ],
410
+ });
411
+ const client = createMockStigmer({
412
+ listAccounts: vi.fn().mockResolvedValue({ accounts: [scenarSummary()] }),
413
+ getAccountView: vi.fn().mockResolvedValue(unsyncedView),
414
+ });
415
+ render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
416
+
417
+ await waitFor(() => expect(screen.getByText("scenar team")).toBeTruthy());
418
+ await userEvent.click(screen.getByRole("button", { name: /scenar team/ }));
419
+
420
+ await waitFor(() =>
421
+ expect(screen.getByText(/not yet classified/)).toBeTruthy(),
422
+ );
423
+ expect(screen.getByText("Awaiting sync")).toBeTruthy();
424
+ expect(screen.queryByText(/Key held — not on the team/)).toBeNull();
212
425
  });
213
426
 
214
427
  it("adds a member key and refreshes both views", async () => {
@@ -290,9 +503,13 @@ describe("CursorAccountsConsole", () => {
290
503
  "key_admin_plain",
291
504
  );
292
505
  await userEvent.type(
293
- screen.getByLabelText(/Assigned organization ids/),
506
+ screen.getByLabelText(/Dedicated organization ids/),
294
507
  "org-x org-y",
295
508
  );
509
+ await userEvent.type(
510
+ screen.getByLabelText(/Team invite link/),
511
+ "https://cursor.com/team-invite/abc",
512
+ );
296
513
  await userEvent.click(screen.getByRole("button", { name: "Create account" }));
297
514
 
298
515
  await waitFor(() => expect(upsertAccount).toHaveBeenCalledTimes(1));
@@ -300,7 +517,39 @@ describe("CursorAccountsConsole", () => {
300
517
  expect(submitted.displayName).toBe("new team");
301
518
  expect(submitted.adminApiKey).toBe("key_admin_plain");
302
519
  expect(submitted.orgIds).toEqual(["org-x", "org-y"]);
520
+ // Readable round-trip: the invite link submits as typed, no marker.
521
+ expect(submitted.teamInviteLink).toBe("https://cursor.com/team-invite/abc");
303
522
  expect(submitted.enabled).toBe(true);
523
+ // Checkbox default checked ("on-demand enabled", Cursor's team default)
524
+ // negates into the proto field.
525
+ expect(submitted.onDemandUsageDisabled).toBe(false);
526
+ // The deprecated default flag is never written by current clients —
527
+ // the shared pool is derived from empty org_ids (DD-008).
528
+ expect(submitted.isPlatformDefault).toBe(false);
529
+ });
530
+
531
+ it("declares on-demand usage off through the editor checkbox", async () => {
532
+ const upsertAccount = vi.fn().mockResolvedValue(scenarAccount());
533
+ const client = createMockStigmer({
534
+ listAccounts: vi.fn().mockResolvedValue({ accounts: [] }),
535
+ upsertAccount,
536
+ });
537
+ render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
538
+
539
+ await waitFor(() =>
540
+ expect(screen.getByRole("button", { name: "Add account" })).toBeTruthy(),
541
+ );
542
+ await userEvent.click(screen.getByRole("button", { name: "Add account" }));
543
+
544
+ await userEvent.type(screen.getByLabelText(/Display name/), "capped team");
545
+ await userEvent.type(screen.getByLabelText(/Team Admin API key/), "key_admin");
546
+ await userEvent.click(
547
+ screen.getByRole("checkbox", { name: /On-demand usage enabled/ }),
548
+ );
549
+ await userEvent.click(screen.getByRole("button", { name: "Create account" }));
550
+
551
+ await waitFor(() => expect(upsertAccount).toHaveBeenCalledTimes(1));
552
+ expect(upsertAccount.mock.calls[0][0].account.onDemandUsageDisabled).toBe(true);
304
553
  });
305
554
 
306
555
  it("bulk-imports keys one per line, keeping failed lines for retry", async () => {
@@ -362,6 +611,53 @@ describe("CursorAccountsConsole", () => {
362
611
  await waitFor(() => expect(syncAccount).toHaveBeenCalledWith("acc-1"));
363
612
  });
364
613
 
614
+ it("shows pool-utilization columns and the usage-guard badge from server-computed facts", async () => {
615
+ const guardedView = create(CursorAccountViewSchema, {
616
+ account: scenarAccount({ onDemandUsageDisabled: true }),
617
+ snapshot: create(CursorAccountSyncSnapshotSchema, {
618
+ accountId: "acc-1",
619
+ syncedAt: timestampFromDate(new Date("2026-07-22T12:00:00Z")),
620
+ }),
621
+ keyViews: [
622
+ create(CursorMemberKeyViewSchema, {
623
+ key: scenarAccount().memberKeys[0],
624
+ state: CursorMemberKeyState.member_key_active,
625
+ usageGuardTripped: true,
626
+ spend: create(CursorMemberSpendSchema, {
627
+ email: "zane@scenar.ai",
628
+ includedSpendUsdMicros: 169342n,
629
+ apiPercentUsed: 100,
630
+ autoPercentUsed: 5.19,
631
+ totalPercentUsed: 26.58,
632
+ }),
633
+ }),
634
+ ],
635
+ });
636
+ const client = createMockStigmer({
637
+ listAccounts: vi.fn().mockResolvedValue({ accounts: [scenarSummary()] }),
638
+ getAccountView: vi.fn().mockResolvedValue(guardedView),
639
+ });
640
+ render(<CursorAccountsConsole />, { wrapper: wrapper(client) });
641
+
642
+ await waitFor(() => expect(screen.getByText("scenar team")).toBeTruthy());
643
+ await userEvent.click(screen.getByRole("button", { name: /scenar team/ }));
644
+
645
+ // The four numeric columns map 1:1 onto Cursor's Members page:
646
+ // first-party pool %, API pool %, included $, on-demand $.
647
+ await waitFor(() => expect(screen.getByText("100%")).toBeTruthy()); // API
648
+ expect(screen.getByText("5%")).toBeTruthy(); // first-party (5.19)
649
+ expect(screen.getByText("$0.17")).toBeTruthy(); // included, 169342 micro-USD
650
+ expect(screen.getByText("$0.00")).toBeTruthy(); // on-demand, none
651
+ // The blended totalPercentUsed (26.58) is deliberately not rendered —
652
+ // it maps to nothing on Cursor's dashboard and reads a flat 100 for
653
+ // removed members.
654
+ expect(screen.queryByText("27%")).toBeNull();
655
+ // The badge renders the server's flag — no client-side threshold math.
656
+ expect(screen.getByText("Usage guard")).toBeTruthy();
657
+ // The header carries the declaration driving the guard.
658
+ expect(screen.getByText(/on-demand usage off \(usage guard active\)/)).toBeTruthy();
659
+ });
660
+
365
661
  it("keeps the stored admin key when the editor's masked value is untouched", async () => {
366
662
  const upsertAccount = vi.fn().mockResolvedValue(scenarAccount());
367
663
  const client = createMockStigmer({