@inneranimalmedia/agentsam-sdk 2.6.4 → 2.6.5

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 (418) hide show
  1. package/AGENTSAM.md +11 -0
  2. package/README.md +15 -9
  3. package/apps/client-cms-editor/AGENTS.md +12 -0
  4. package/apps/client-cms-editor/IMPORT_PROVENANCE.json +8 -0
  5. package/apps/client-cms-editor/README.md +72 -0
  6. package/apps/client-cms-editor/agentsam.app.json +57 -0
  7. package/apps/client-cms-editor/backend/README.md +5 -0
  8. package/apps/client-cms-editor/backend/package.json +22 -0
  9. package/apps/client-cms-editor/backend/src/api/client.ts +200 -0
  10. package/apps/client-cms-editor/backend/src/demo-bootstrap.ts +145 -0
  11. package/apps/client-cms-editor/backend/src/index.ts +6 -0
  12. package/apps/client-cms-editor/backend/src/model.ts +115 -0
  13. package/apps/client-cms-editor/backend/src/preview/bridge.ts +79 -0
  14. package/apps/client-cms-editor/backend/src/preview/urls.ts +55 -0
  15. package/apps/client-cms-editor/backend/src/routing/cms-route.js +293 -0
  16. package/apps/client-cms-editor/backend/src/routing/index.js +10 -0
  17. package/apps/client-cms-editor/backend/src/storefront.ts +52 -0
  18. package/apps/client-cms-editor/backend/tsconfig.json +15 -0
  19. package/apps/client-cms-editor/backend/worker/index.js +34 -0
  20. package/apps/client-cms-editor/backend/wrangler.jsonc +11 -0
  21. package/apps/client-cms-editor/bin/agentsam-cms.mjs +229 -0
  22. package/apps/client-cms-editor/frontend/index.html +12 -0
  23. package/apps/client-cms-editor/frontend/package.json +33 -0
  24. package/apps/client-cms-editor/frontend/src/AgentSamDrawer.tsx +188 -0
  25. package/apps/client-cms-editor/frontend/src/CmsAgentSurface.tsx +191 -0
  26. package/apps/client-cms-editor/frontend/src/CmsEditor.tsx +1345 -0
  27. package/apps/client-cms-editor/frontend/src/env.d.ts +14 -0
  28. package/apps/client-cms-editor/frontend/src/index.ts +8 -0
  29. package/apps/client-cms-editor/frontend/src/lib/content-safety.ts +63 -0
  30. package/apps/client-cms-editor/frontend/src/main.tsx +17 -0
  31. package/apps/client-cms-editor/frontend/src/mount.tsx +51 -0
  32. package/apps/client-cms-editor/frontend/src/styles/agentsam-drawer.css +214 -0
  33. package/apps/client-cms-editor/frontend/src/styles/cmsShell.css +1739 -0
  34. package/apps/client-cms-editor/frontend/src/styles/studio.css +106 -0
  35. package/apps/client-cms-editor/frontend/src/surfaces/CmsDashboard.tsx +313 -0
  36. package/apps/client-cms-editor/frontend/src/surfaces/CmsGuidedChatHero.tsx +121 -0
  37. package/apps/client-cms-editor/frontend/src/surfaces/CmsHubPage.tsx +132 -0
  38. package/apps/client-cms-editor/frontend/src/surfaces/CmsSiteSwitcher.tsx +176 -0
  39. package/apps/client-cms-editor/frontend/src/surfaces/index.ts +5 -0
  40. package/apps/client-cms-editor/frontend/src/surfaces/types.ts +48 -0
  41. package/apps/client-cms-editor/frontend/tsconfig.json +25 -0
  42. package/apps/client-cms-editor/frontend/vite.config.ts +13 -0
  43. package/apps/client-cms-editor/package-lock.json +3832 -0
  44. package/apps/client-cms-editor/package.json +23 -0
  45. package/apps/client-cms-editor/reference/DONOR_README.md +52 -0
  46. package/apps/client-cms-editor/reference/DONOR_package-lock.json +1253 -0
  47. package/apps/client-cms-editor/reference/DONOR_tsconfig.json +15 -0
  48. package/apps/client-cms-editor/shared/cms/package.json +21 -0
  49. package/apps/client-cms-editor/shared/cms/src/agent-context.ts +35 -0
  50. package/apps/client-cms-editor/shared/cms/src/cloudflare-bindings.ts +51 -0
  51. package/apps/client-cms-editor/shared/cms/src/editor-types.ts +58 -0
  52. package/apps/client-cms-editor/shared/cms/src/index.ts +5 -0
  53. package/apps/client-cms-editor/shared/cms/src/publication.ts +24 -0
  54. package/apps/client-cms-editor/shared/cms/src/routes.ts +11 -0
  55. package/apps/client-cms-editor/shared/cms/tsconfig.json +12 -0
  56. package/bin/agentsam-rapid-rust +36 -0
  57. package/docs/AUTH_IDENTITY_CONTRACT.md +2 -0
  58. package/docs/MACHINE_INTELLIGENCE.md +120 -0
  59. package/docs/WINDOWS_RUNTIME_INSTALL.md +30 -0
  60. package/docs/architecture/AGENTSAM_APPLICATION_CONTRACT.md +188 -0
  61. package/docs/architecture/AGENTSAM_RUNTIME_PROTOCOL.md +151 -0
  62. package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +12 -4
  63. package/docs/architecture/R2_SLUG_NAMESPACE.md +20 -0
  64. package/docs/architecture/WORK_PRODUCT_EXTRACTION.md +97 -0
  65. package/docs/brand/AGENT_SAM_ICON_GRAMMAR.md +70 -0
  66. package/docs/content-studio/HOST_MOUNT_CHECKLIST.md +71 -0
  67. package/docs/content-studio/REVISION_GATE_2026-09-27.md +501 -0
  68. package/docs/contracts/capability-install-planner.md +67 -0
  69. package/docs/contracts/environment-vocabulary.md +195 -0
  70. package/docs/contracts/google-cloud-oauth-setup.md +199 -0
  71. package/docs/local-studio/WORKMODE_DONOR_NOTES.md +1 -1
  72. package/docs/plans/LOCAL-STUDIO-GRADUATION-2026-09-26.md +290 -0
  73. package/docs/plans/LOCAL-STUDIO-REAL-APP-CONTRACT-ADDENDUM-2026-09-27.md +394 -0
  74. package/docs/plans/MACHINE-FIRST-APPS-TUI-2026-09-24.md +227 -0
  75. package/docs/plans/TOMORROW-EXTENSIVE-TODO-2026-09-26.md +221 -0
  76. package/docs/plans/TOMORROW-MACHINE-FIRST-BRIEFING.md +145 -0
  77. package/docs/plans/UNIVERSAL-INGEST-MACHINE-FIRST-2026-09-24.md +215 -0
  78. package/docs/plugins.md +2 -2
  79. package/docs/product/AGENTSAM_PRODUCT_EXPERIENCE_STANDARD.md +280 -0
  80. package/docs/product/LOCAL_STUDIO_DESKTOP_EXPERIENCE.md +139 -0
  81. package/docs/product/README.md +8 -0
  82. package/docs/rapid-rust-wasm.md +35 -0
  83. package/migrations/d1/0016_cloudflare_oauth_user_oauth_tokens_ssot.sql +14 -0
  84. package/package.json +73 -14
  85. package/packages/agentsam-brand/README.md +106 -11
  86. package/packages/agentsam-brand/bin/agentsam-brand.mjs +9 -0
  87. package/packages/agentsam-brand/package.json +39 -7
  88. package/packages/agentsam-brand/src/adapters/cloudflare-images.js +453 -0
  89. package/packages/agentsam-brand/src/adapters/cloudflare-r2.js +74 -0
  90. package/packages/agentsam-brand/src/adapters/d1-registry.js +26 -0
  91. package/packages/agentsam-brand/src/adapters/filesystem.js +59 -0
  92. package/packages/agentsam-brand/src/adapters/memory.js +31 -0
  93. package/packages/agentsam-brand/src/cli/controller.js +503 -0
  94. package/packages/agentsam-brand/src/cli/wizard.js +526 -0
  95. package/packages/agentsam-brand/src/core/capabilities.js +201 -0
  96. package/packages/agentsam-brand/src/core/derivatives.js +211 -0
  97. package/packages/agentsam-brand/src/core/errors.js +12 -0
  98. package/packages/agentsam-brand/src/core/ingest.js +195 -0
  99. package/packages/agentsam-brand/src/core/inspect.js +109 -0
  100. package/packages/agentsam-brand/src/core/keys.js +83 -0
  101. package/packages/agentsam-brand/src/core/naming.js +142 -0
  102. package/packages/agentsam-brand/src/core/optimize.js +316 -0
  103. package/packages/agentsam-brand/src/core/pack.js +305 -0
  104. package/packages/agentsam-brand/src/core/plan.js +276 -0
  105. package/packages/agentsam-brand/src/core/preview.js +159 -0
  106. package/packages/agentsam-brand/src/core/processors/cloudflare.js +68 -0
  107. package/packages/agentsam-brand/src/core/processors/contract.js +203 -0
  108. package/packages/agentsam-brand/src/core/processors/index.js +148 -0
  109. package/packages/agentsam-brand/src/core/processors/native.js +82 -0
  110. package/packages/agentsam-brand/src/core/processors/sharp.js +113 -0
  111. package/packages/agentsam-brand/src/core/processors/squoosh-binary.js +195 -0
  112. package/packages/agentsam-brand/src/core/promote.js +288 -0
  113. package/packages/agentsam-brand/src/core/receipts.js +155 -0
  114. package/packages/agentsam-brand/src/core/v2/compile.js +280 -0
  115. package/packages/agentsam-brand/src/core/v2/derivatives-semantic.js +207 -0
  116. package/packages/agentsam-brand/src/core/v2/ingest-graph.js +387 -0
  117. package/packages/agentsam-brand/src/core/v2/roles.js +180 -0
  118. package/packages/agentsam-brand/src/core/v2/schema.js +209 -0
  119. package/packages/agentsam-brand/src/core/v2/templates.js +114 -0
  120. package/packages/agentsam-brand/src/index.js +193 -36
  121. package/packages/agentsam-brand/src/presets/app-icon.json +16 -0
  122. package/packages/agentsam-brand/src/presets/favicon.json +17 -0
  123. package/packages/agentsam-brand/src/presets/hero.json +12 -0
  124. package/packages/agentsam-brand/src/presets/index.js +40 -0
  125. package/packages/agentsam-brand/src/presets/logo.json +12 -0
  126. package/packages/agentsam-brand/src/presets/model.json +8 -0
  127. package/packages/agentsam-brand/src/presets/og-image.json +10 -0
  128. package/packages/agentsam-brand/src/presets/product-shot.json +11 -0
  129. package/packages/agentsam-brand/src/presets/social-image.json +10 -0
  130. package/packages/agentsam-brand/src/presets/web-image.json +10 -0
  131. package/packages/agentsam-contracts/src/artifacts.ts +41 -0
  132. package/packages/agentsam-knowledge/src/autorag/index.js +1 -1
  133. package/packages/agentsam-knowledge/src/providers/index.js +6 -4
  134. package/packages/agentsam-knowledge/src/providers/ollama-endpoint.js +31 -0
  135. package/packages/agentsam-knowledge/test/autorag.test.mjs +22 -1
  136. package/packages/agentsam-nav/package.json +2 -2
  137. package/packages/agentsam-rapid-rust/Cargo.lock +344 -0
  138. package/packages/agentsam-rapid-rust/Cargo.toml +18 -0
  139. package/packages/agentsam-rapid-rust/README.md +5 -0
  140. package/packages/agentsam-rapid-rust/src/main.rs +718 -0
  141. package/packages/agentsam-rapid-rust/templates/api/Cargo.toml.tmpl +18 -0
  142. package/packages/agentsam-rapid-rust/templates/api/src/lib.rs.tmpl +17 -0
  143. package/packages/agentsam-rapid-rust/templates/common/README.md.tmpl +22 -0
  144. package/packages/agentsam-rapid-rust/templates/common/package.json.tmpl +12 -0
  145. package/packages/agentsam-rapid-rust/templates/common/wrangler.toml.tmpl +6 -0
  146. package/packages/agentsam-rapid-rust/templates/crypto-auth/Cargo.toml.tmpl +21 -0
  147. package/packages/agentsam-rapid-rust/templates/crypto-auth/src/lib.rs.tmpl +32 -0
  148. package/packages/agentsam-rapid-rust/templates/data-processor/Cargo.toml.tmpl +18 -0
  149. package/packages/agentsam-rapid-rust/templates/data-processor/src/lib.rs.tmpl +41 -0
  150. package/packages/agentsam-rapid-rust/templates/shared-core/Cargo.toml.tmpl +9 -0
  151. package/packages/agentsam-rapid-rust/templates/shared-core/core/Cargo.toml.tmpl +7 -0
  152. package/packages/agentsam-rapid-rust/templates/shared-core/core/src/lib.rs.tmpl +23 -0
  153. package/packages/agentsam-rapid-rust/templates/shared-core/worker/Cargo.toml.tmpl +12 -0
  154. package/packages/agentsam-rapid-rust/templates/shared-core/worker/src/lib.rs.tmpl +16 -0
  155. package/packages/agentsam-rapid-rust/templates/shared-core/wrangler.toml.tmpl +6 -0
  156. package/packages/agentsam-repository/src/index.js +1 -0
  157. package/packages/agentsam-repository/src/merkle/semantic.js +3 -10
  158. package/packages/agentsam-repository/src/source-types.js +85 -0
  159. package/packages/agentsam-vault/README.md +96 -0
  160. package/packages/agentsam-vault/package.json +32 -0
  161. package/packages/agentsam-vault/src/contracts/credential.d.ts +78 -0
  162. package/packages/agentsam-vault/src/contracts/credential.js +132 -0
  163. package/packages/agentsam-vault/src/crypto/aes-gcm.d.ts +30 -0
  164. package/packages/agentsam-vault/src/crypto/aes-gcm.js +107 -0
  165. package/packages/agentsam-vault/src/crypto/oauth-envelope.js +150 -0
  166. package/packages/agentsam-vault/src/index.d.ts +9 -0
  167. package/packages/agentsam-vault/src/index.js +27 -0
  168. package/packages/agentsam-vault/src/providers/registry.js +90 -0
  169. package/packages/agentsam-vault/src/runtime/resolver.js +77 -0
  170. package/packages/agentsam-workbench/README.md +24 -0
  171. package/packages/agentsam-workbench/package.json +38 -0
  172. package/packages/agentsam-workbench/src/agent/AgentPanel.tsx +41 -0
  173. package/packages/agentsam-workbench/src/agent/ArtifactCard.tsx +9 -0
  174. package/packages/agentsam-workbench/src/agent/Composer.tsx +102 -0
  175. package/packages/agentsam-workbench/src/agent/ConnectedAgentPanel.tsx +194 -0
  176. package/packages/agentsam-workbench/src/agent/ContextualComposer.tsx +788 -0
  177. package/packages/agentsam-workbench/src/agent/GoalStatusStrip.tsx +38 -0
  178. package/packages/agentsam-workbench/src/agent/MiniAgentSam.tsx +86 -0
  179. package/packages/agentsam-workbench/src/agent/ModelSelect.tsx +19 -0
  180. package/packages/agentsam-workbench/src/agent/Thread.tsx +56 -0
  181. package/packages/agentsam-workbench/src/agent/ToolReceipt.tsx +11 -0
  182. package/packages/agentsam-workbench/src/agent/contextual-composer.css +611 -0
  183. package/packages/agentsam-workbench/src/agent/index.ts +10 -0
  184. package/packages/agentsam-workbench/src/agent/mini-agentsam.css +12 -0
  185. package/packages/agentsam-workbench/src/browser/BrowserFrame.tsx +37 -0
  186. package/packages/agentsam-workbench/src/browser/index.ts +1 -0
  187. package/packages/agentsam-workbench/src/index.ts +6 -0
  188. package/packages/agentsam-workbench/src/project-control.css +160 -0
  189. package/packages/agentsam-workbench/src/projects/ProjectsSurface.tsx +131 -0
  190. package/packages/agentsam-workbench/src/projects/index.ts +1 -0
  191. package/packages/agentsam-workbench/src/shell/Breadcrumb.tsx +35 -0
  192. package/packages/agentsam-workbench/src/shell/ResizableStage.tsx +99 -0
  193. package/packages/agentsam-workbench/src/shell/SidePanel.tsx +5 -0
  194. package/packages/agentsam-workbench/src/shell/index.ts +4 -0
  195. package/packages/agentsam-workbench/src/shell/surfaceRegistry.ts +29 -0
  196. package/packages/agentsam-workbench/src/terminal/TerminalSurface.tsx +5 -0
  197. package/packages/agentsam-workbench/src/terminal/index.ts +1 -0
  198. package/packages/agentsam-workbench/src/timeline/GanttBoard.tsx +258 -0
  199. package/packages/agentsam-workbench/src/timeline/index.ts +1 -0
  200. package/packages/agentsam-workbench/tsconfig.json +13 -0
  201. package/packages/connectors/cloudflare/OAUTH_STORAGE_AUDIT.md +53 -0
  202. package/packages/connectors/cloudflare/package.json +3 -1
  203. package/packages/connectors/cloudflare/src/api-client.js +279 -0
  204. package/packages/connectors/cloudflare/src/capabilities.js +585 -0
  205. package/packages/connectors/cloudflare/src/credential.js +318 -0
  206. package/packages/connectors/cloudflare/src/families/brand-protection.js +123 -0
  207. package/packages/connectors/cloudflare/src/families/keyless.js +114 -0
  208. package/packages/connectors/cloudflare/src/families/pages.js +151 -0
  209. package/packages/connectors/cloudflare/src/families/scanner.js +123 -0
  210. package/packages/connectors/cloudflare/src/families/snippets.js +207 -0
  211. package/packages/connectors/cloudflare/src/families/tag-gateway.js +46 -0
  212. package/packages/connectors/cloudflare/src/families/tags.js +124 -0
  213. package/packages/connectors/cloudflare/src/families/token-validation.js +95 -0
  214. package/packages/connectors/cloudflare/src/families/workflows.js +156 -0
  215. package/packages/connectors/cloudflare/src/index.js +102 -265
  216. package/packages/connectors/cloudflare/src/oauth-persist.js +336 -0
  217. package/packages/connectors/cloudflare/src/owner.js +42 -12
  218. package/packages/connectors/cloudflare/src/permission-options.js +1064 -0
  219. package/packages/connectors/cloudflare/src/routes.js +309 -144
  220. package/packages/connectors/cloudflare/src/scope-catalog.js +345 -0
  221. package/packages/connectors/cloudflare/src/vault.js +2 -2
  222. package/packages/connectors/cloudflare/tests/capabilities.test.mjs +276 -0
  223. package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -30
  224. package/packages/connectors/cloudflare/tests/oauth-persist.test.mjs +125 -0
  225. package/packages/identity/.agentsam/features/oauth-login-portal/providers.json +22 -2
  226. package/packages/identity/.agentsam/features/oauth-login-portal/resources.json +10 -4
  227. package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +20 -0
  228. package/packages/identity/README.md +1 -1
  229. package/packages/identity/backend/README.md +1 -1
  230. package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +3 -2
  231. package/packages/identity/migrations/sqlite/004_identity_oauth_consent_catalog.sql +48 -0
  232. package/packages/identity/package.json +4 -3
  233. package/packages/identity/scripts/local-identity-bridge.mjs +184 -0
  234. package/packages/identity/scripts/preview-auth-portal.mjs +7 -7
  235. package/packages/identity/src/adapters/cloudflare-d1/index.js +31 -2
  236. package/packages/identity/src/adapters/sqlite/index.js +4 -1
  237. package/packages/identity/src/contracts/auth-config.js +28 -8
  238. package/packages/identity/src/contracts/external-identity.js +1 -1
  239. package/packages/identity/src/contracts/provider.js +1 -0
  240. package/packages/identity/src/frontend/auth-portal/README.md +2 -1
  241. package/packages/identity/src/frontend/auth-portal/pages/login.html +6 -5
  242. package/packages/identity/src/frontend/auth-portal/pages/reset.html +6 -5
  243. package/packages/identity/src/frontend/auth-portal/pages/signup.html +6 -5
  244. package/packages/identity/src/frontend/auth-portal/shared/company-branding.js +1 -1
  245. package/packages/identity/src/frontend/auth-portal/shared/desktop-identity-bridge.js +207 -0
  246. package/packages/identity/src/index.js +16 -0
  247. package/packages/identity/src/migrations/0001_identity_core.sql +19 -2
  248. package/packages/identity/src/migrations/0003_accounts_ssot.sql +20 -0
  249. package/packages/identity/src/migrations/README.md +5 -2
  250. package/packages/identity/src/oauth/README.md +10 -5
  251. package/packages/identity/src/oauth/consent-view.js +388 -0
  252. package/packages/identity/src/oauth/credentials.js +31 -11
  253. package/packages/identity/src/oauth/google-desktop-exchange.js +173 -0
  254. package/packages/identity/src/providers/cloudflare/oauth.js +3 -4
  255. package/packages/identity/src/providers/cloudflare/profile.js +49 -14
  256. package/packages/identity/src/providers/iam/index.js +27 -5
  257. package/packages/identity/src/server/identity-service.js +31 -2
  258. package/packages/identity/src/server/worker-router.js +111 -81
  259. package/packages/identity/tests/auth-config.test.mjs +3 -2
  260. package/packages/identity/tests/identity-init-scaffold.test.mjs +3 -0
  261. package/packages/identity/tests/identity-service.test.mjs +16 -0
  262. package/packages/identity/tests/native-session-worker.test.mjs +64 -0
  263. package/packages/identity/tests/oauth-consent-view.test.mjs +106 -0
  264. package/packages/identity/tests/oauth-credentials.test.mjs +17 -4
  265. package/packages/identity/tests/oauth-presets-accounts-ssot.test.mjs +58 -0
  266. package/packages/identity/tests/preview-auth-portal.test.mjs +3 -3
  267. package/packages/runtime-protocol/src/index.js +154 -0
  268. package/packages/theme-scenes/README.md +164 -0
  269. package/packages/theme-scenes/package.json +53 -0
  270. package/packages/theme-scenes/src/blocks/index.js +30 -0
  271. package/packages/theme-scenes/src/contracts/index.js +286 -0
  272. package/packages/theme-scenes/src/demos/index.js +65 -0
  273. package/packages/theme-scenes/src/index.js +75 -0
  274. package/packages/theme-scenes/src/presets/index.js +263 -0
  275. package/packages/theme-scenes/src/runtime/compile.js +119 -0
  276. package/packages/theme-scenes/src/scenes/index.js +146 -0
  277. package/packages/theme-scenes/src/shell/index.js +47 -0
  278. package/packages/theme-scenes/src/themes/index.js +129 -0
  279. package/packages/theme-scenes/src/visuals/index.js +41 -0
  280. package/packages/theme-scenes/test/theme-scenes.test.mjs +179 -0
  281. package/packages/work-graph/src/index.js +34 -1
  282. package/packages/work-graph/src/renderers/gantt-model.js +24 -0
  283. package/packages/work-graph/test/work-graph.test.mjs +27 -7
  284. package/protocol/README.md +15 -7
  285. package/protocol/apps/agentsam.app.v1.schema.json +89 -7
  286. package/protocol/apps/agentsam.host-install.v1.schema.json +36 -0
  287. package/protocol/assets/README.md +9 -0
  288. package/protocol/assets/asset-record.v1.schema.json +64 -0
  289. package/protocol/runtime/agentsam.runtime.v1.schema.json +75 -0
  290. package/protocol/scoring/README.md +10 -0
  291. package/protocol/scoring/scorecard.v1.schema.json +65 -0
  292. package/protocol/ui/icon-registry.mjs +1 -0
  293. package/registry/cms-rust-machine-20260928/installable-products.json +31 -0
  294. package/registry/cms-rust-machine-20260928/manifest.json +22 -0
  295. package/registry/cms-rust-machine-20260928/migration.sql +237 -0
  296. package/registry/cms-rust-machine-20260928/receipt.json +21 -0
  297. package/registry/cms-rust-machine-20260928/receipt.sql +27 -0
  298. package/scripts/build_rapid_rust_cli.py +83 -0
  299. package/src/attachments/index.js +6 -0
  300. package/src/attachments/intake.js +235 -0
  301. package/src/capabilities/repository-snapshot.js +19 -2
  302. package/src/cli/command-catalog.js +8 -0
  303. package/src/cli.js +182 -26
  304. package/src/commands/account-auth.js +94 -8
  305. package/src/commands/app.js +307 -3
  306. package/src/commands/billing.js +127 -0
  307. package/src/commands/brand.js +74 -60
  308. package/src/commands/cheat-sheet.js +154 -0
  309. package/src/commands/cloudflare-login.js +222 -0
  310. package/src/commands/cloudflare.js +453 -23
  311. package/src/commands/codebaseindex.js +6 -1
  312. package/src/commands/compute.js +4 -0
  313. package/src/commands/connections.js +11 -5
  314. package/src/commands/credentials.js +116 -0
  315. package/src/commands/gcloud-auth.js +427 -0
  316. package/src/commands/google-cloud.js +375 -0
  317. package/src/commands/identity-init.js +2 -2
  318. package/src/commands/mcp.js +226 -33
  319. package/src/commands/ollama.js +3 -2
  320. package/src/commands/plugins.js +7 -2
  321. package/src/commands/product.js +36 -0
  322. package/src/commands/runtime-status.js +28 -9
  323. package/src/commands/runtime.js +313 -0
  324. package/src/commands/rust.js +24 -0
  325. package/src/commands/setup.js +279 -0
  326. package/src/commands/shell.js +16 -3
  327. package/src/commands/start-local.js +2 -2
  328. package/src/commands/terminal.js +171 -0
  329. package/src/commands/update.js +140 -0
  330. package/src/commands/whoami.js +329 -65
  331. package/src/features/resolve.js +1 -0
  332. package/src/index.js +4 -0
  333. package/src/indexing/ingest/inventory.js +254 -19
  334. package/src/indexing/ingest/materials.js +3 -9
  335. package/src/indexing/ingest/style-evidence.js +181 -0
  336. package/src/knowledge/source.js +3 -3
  337. package/src/lib/account-session.js +11 -4
  338. package/src/lib/app-authority.js +172 -0
  339. package/src/lib/auth.js +94 -21
  340. package/src/lib/core-client.js +5 -6
  341. package/src/lib/credentials-audit.js +451 -0
  342. package/src/lib/deploy/health.js +24 -6
  343. package/src/lib/detect-context.js +7 -1
  344. package/src/lib/google-cloud-connection.js +270 -0
  345. package/src/lib/google-cloud-inventory.js +385 -0
  346. package/src/lib/google-desktop-oauth.js +372 -0
  347. package/src/lib/google-studio-brokered-oauth.js +158 -0
  348. package/src/lib/identity-scaffold.js +24 -6
  349. package/src/lib/init-options.js +192 -0
  350. package/src/lib/local-scaffold.js +48 -16
  351. package/src/lib/provider-command-remediation.js +308 -0
  352. package/src/lib/provider-credentials.js +8 -0
  353. package/src/lib/scaffold/index.js +8 -2
  354. package/src/lib/scaffold/mcp-server.js +43 -0
  355. package/src/lib/scaffold/templates/mcp-server/index.js +397 -0
  356. package/src/lib/scaffold/templates/mcp-server/presets.js +39 -0
  357. package/src/lib/scaffold/wizards/cms.js +93 -0
  358. package/src/lib/scaffold/wizards/mcp-server.js +61 -0
  359. package/src/lib/setup/contract.js +26 -0
  360. package/src/lib/setup/discover.js +116 -0
  361. package/src/lib/setup/execute.js +205 -0
  362. package/src/lib/setup/planner.js +134 -0
  363. package/src/lib/setup/recipes.js +235 -0
  364. package/src/lib/setup/runtime-persist.js +197 -0
  365. package/src/lib/setup/runtime.js +394 -0
  366. package/src/lib/slash-commands.js +2 -0
  367. package/src/lib/terminal/machine-identity.js +95 -0
  368. package/src/lib/terminal-local.js +164 -0
  369. package/src/lib/terminal-scopes.js +52 -0
  370. package/src/lib/whoami-capabilities.js +184 -0
  371. package/src/local-pty/server.js +34 -5
  372. package/src/mcp/authority.js +58 -108
  373. package/src/mcp/client-adapters.js +68 -144
  374. package/src/mcp/client.js +142 -122
  375. package/src/mcp/cloudflare-bundles.js +225 -0
  376. package/src/mcp/index.js +12 -0
  377. package/src/plugins/cloudflare.js +1 -1
  378. package/src/providers/ollama-chat.js +2 -1
  379. package/src/sam/types.js +27 -2
  380. package/src/security/inventory.js +12 -2
  381. package/src/security/local-vault.js +2 -0
  382. package/src/security/scan.js +1 -1
  383. package/test/account-session.test.mjs +3 -3
  384. package/test/app-authority-context.test.mjs +32 -0
  385. package/test/cli/codebaseindex-plan-ux.test.mjs +1 -1
  386. package/test/cli/mcp-client.test.mjs +21 -10
  387. package/test/cli/runtime-persist.test.mjs +56 -0
  388. package/test/cli/rust-command.test.mjs +21 -0
  389. package/test/credentials-audit.test.mjs +113 -0
  390. package/test/deploy-health-scan.test.mjs +29 -1
  391. package/test/gcloud-auth.test.mjs +111 -0
  392. package/test/google-cloud-connection.test.mjs +83 -0
  393. package/test/google-cloud-inventory.test.mjs +64 -0
  394. package/test/init-options.test.mjs +35 -0
  395. package/test/integration/app-command.test.mjs +30 -0
  396. package/test/integration/cad-project.test.mjs +4 -1
  397. package/test/integration/cms-app-scaffold.test.mjs +71 -0
  398. package/test/integration/cms-site-tenancy-contract.test.mjs +3 -2
  399. package/test/integration/codebaseindex-ingest.test.mjs +48 -0
  400. package/test/integration/features-resolve.test.mjs +30 -11
  401. package/test/integration/inspect-path-cli.test.mjs +46 -0
  402. package/test/integration/inventory-evidence.test.mjs +125 -0
  403. package/test/integration/runtime-status.integration.test.mjs +2 -1
  404. package/test/knowledge.test.mjs +35 -1
  405. package/test/loopback-oauth.test.mjs +53 -0
  406. package/test/ollama.test.mjs +22 -0
  407. package/test/security.test.mjs +23 -0
  408. package/test/setup-planner.test.mjs +74 -0
  409. package/test/setup-runtime.test.mjs +51 -0
  410. package/test/terminal/local-pty.mock.test.mjs +3 -1
  411. package/test/terminal-machine-identity.test.mjs +88 -0
  412. package/test/whoami-resume.test.mjs +27 -6
  413. package/packages/agentsam-brand/test/brand-scan.test.mjs +0 -106
  414. package/packages/work-graph/prototype/README.md +0 -32
  415. package/packages/work-graph/prototype/data/demo-workgraph.js +0 -47
  416. package/packages/work-graph/prototype/renderers/timeline-model.js +0 -21
  417. package/packages/work-graph/prototype/renderers/workgraph-runtime.js +0 -38
  418. package/packages/work-graph/prototype-gnantt.html +0 -655
@@ -7,10 +7,17 @@ import {
7
7
  resolveCloudflareOAuthClient,
8
8
  requestedCloudflareScopes,
9
9
  assertConnectionOwner,
10
+ upsertCloudflareUserOauthToken,
11
+ revokeCloudflareUserOauthTokens,
12
+ loadCloudflareConnectionRecord,
13
+ loadCloudflareFromUserOauthTokens,
10
14
  } from './index.js';
11
15
  import { resolveAuthenticatedOwner } from './owner.js';
12
16
  import { decryptSecret, sealToken, vaultConfigured } from './vault.js';
13
17
 
18
+ const OAUTH_STATE_PROVIDER = 'cloudflare';
19
+ const OAUTH_STATE_TTL_SECONDS = 600;
20
+
14
21
  function json(data, status = 200) {
15
22
  return new Response(JSON.stringify(data), {
16
23
  status,
@@ -41,16 +48,95 @@ function resolveReturnTo(url) {
41
48
  }
42
49
  }
43
50
 
51
+ function bytesToHex(bytes) {
52
+ return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
53
+ }
54
+
55
+ async function hashOAuthState(state) {
56
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(String(state || '')));
57
+ return bytesToHex(new Uint8Array(digest));
58
+ }
59
+
60
+ function oauthStateAad(ownerId) {
61
+ return `oauth_state_nonces:${OAUTH_STATE_PROVIDER}:${ownerId}`;
62
+ }
63
+
44
64
  async function deleteOAuthState(env, state) {
45
65
  if (env.DB) {
66
+ const stateHash = await hashOAuthState(state);
46
67
  await env.DB.prepare(
47
- `DELETE FROM agentsam_cloudflare_oauth_state WHERE state = ?`,
48
- ).bind(state).run();
68
+ `DELETE FROM oauth_state_nonces WHERE state_hash = ? AND provider = ?`,
69
+ ).bind(stateHash, OAUTH_STATE_PROVIDER).run();
49
70
  } else if (env.oauthState instanceof Map) {
50
71
  env.oauthState.delete(state);
51
72
  }
52
73
  }
53
74
 
75
+ async function saveOAuthState(env, { state, ownerId, verifier, returnTo }) {
76
+ // In-memory fixture path (tests / local without vault).
77
+ if (env.oauthState instanceof Map && (!env.DB || !vaultConfigured(env))) {
78
+ env.oauthState.set(state, { ownerId, verifier, returnTo, createdAt: Math.floor(Date.now() / 1000) });
79
+ return { ok: true };
80
+ }
81
+ if (!env.DB) return { ok: false, error: 'db_unavailable' };
82
+ if (!vaultConfigured(env)) return { ok: false, error: 'vault_unavailable' };
83
+ const stateHash = await hashOAuthState(state);
84
+ const now = Math.floor(Date.now() / 1000);
85
+ const expiresAt = now + OAUTH_STATE_TTL_SECONDS;
86
+ const encrypted = await sealToken(env, verifier, oauthStateAad(ownerId));
87
+ const id = `osn_${crypto.randomUUID().replace(/-/g, '').slice(0, 20)}`;
88
+ await env.DB.prepare(
89
+ `DELETE FROM oauth_state_nonces
90
+ WHERE provider = ? AND (expires_at < ? OR consumed_at IS NOT NULL)`,
91
+ ).bind(OAUTH_STATE_PROVIDER, now).run();
92
+ await env.DB.prepare(
93
+ `INSERT INTO oauth_state_nonces (
94
+ id, tenant_id, user_id, provider, state_hash, code_verifier_encrypted,
95
+ redirect_after, metadata_json, expires_at, consumed_at, created_at
96
+ ) VALUES (?, NULL, ?, ?, ?, ?, ?, '{}', ?, NULL, ?)`,
97
+ ).bind(id, ownerId, OAUTH_STATE_PROVIDER, stateHash, encrypted, returnTo || null, expiresAt, now).run();
98
+ return { ok: true };
99
+ }
100
+
101
+ async function consumeOAuthState(env, state) {
102
+ if (env.oauthState instanceof Map && (!env.DB || env.oauthState.has(state))) {
103
+ const stored = env.oauthState.get(state);
104
+ if (!stored) {
105
+ // Fall through to D1 when Map miss and DB present.
106
+ } else {
107
+ env.oauthState.delete(state);
108
+ return {
109
+ owner_id: stored.ownerId,
110
+ code_verifier: stored.verifier,
111
+ return_to: stored.returnTo || '',
112
+ created_at: stored.createdAt || 0,
113
+ };
114
+ }
115
+ }
116
+ if (!env.DB) return null;
117
+ const stateHash = await hashOAuthState(state);
118
+ const row = await env.DB.prepare(
119
+ `SELECT id, user_id, code_verifier_encrypted, redirect_after, expires_at, created_at, consumed_at
120
+ FROM oauth_state_nonces
121
+ WHERE state_hash = ? AND provider = ?
122
+ LIMIT 1`,
123
+ ).bind(stateHash, OAUTH_STATE_PROVIDER).first();
124
+ if (!row || row.consumed_at) return null;
125
+ const now = Math.floor(Date.now() / 1000);
126
+ await env.DB.prepare(
127
+ `UPDATE oauth_state_nonces SET consumed_at = ? WHERE id = ?`,
128
+ ).bind(now, row.id).run();
129
+ if (Number(row.expires_at) <= now) return null;
130
+ if (!vaultConfigured(env) || !row.code_verifier_encrypted) return null;
131
+ const verifier = await decryptSecret(env, row.code_verifier_encrypted, oauthStateAad(row.user_id));
132
+ return {
133
+ owner_id: row.user_id,
134
+ code_verifier: verifier,
135
+ return_to: row.redirect_after || '',
136
+ created_at: row.created_at,
137
+ };
138
+ }
139
+
54
140
  export function isCloudflareConnectionPath(pathname) {
55
141
  return pathname === '/api/connections/cloudflare'
56
142
  || pathname === '/api/connections/cloudflare/start'
@@ -60,24 +146,19 @@ export function isCloudflareConnectionPath(pathname) {
60
146
 
61
147
  async function ensureTables(env) {
62
148
  if (!env?.DB?.prepare) return;
63
- await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_connections (
64
- connection_id TEXT PRIMARY KEY,
65
- owner_id TEXT NOT NULL,
66
- cloudflare_account_id TEXT,
67
- scopes TEXT,
68
- status TEXT,
69
- access_token_encrypted TEXT,
70
- refresh_token_encrypted TEXT,
71
- created_at INTEGER,
72
- updated_at INTEGER,
73
- expires_at INTEGER
74
- )`).run();
75
- await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_oauth_state (
76
- state TEXT PRIMARY KEY,
77
- owner_id TEXT NOT NULL,
78
- code_verifier TEXT NOT NULL,
79
- created_at INTEGER,
80
- return_to TEXT
149
+ // PKCE state + resource inventory only. Connection credentials live in user_oauth_tokens (SSOT).
150
+ await env.DB.prepare(`CREATE TABLE IF NOT EXISTS oauth_state_nonces (
151
+ id TEXT PRIMARY KEY,
152
+ tenant_id TEXT,
153
+ user_id TEXT,
154
+ provider TEXT NOT NULL,
155
+ state_hash TEXT NOT NULL,
156
+ code_verifier_encrypted TEXT,
157
+ redirect_after TEXT,
158
+ metadata_json TEXT DEFAULT '{}',
159
+ expires_at INTEGER NOT NULL,
160
+ consumed_at INTEGER,
161
+ created_at INTEGER DEFAULT (unixepoch())
81
162
  )`).run();
82
163
  await env.DB.prepare(`CREATE TABLE IF NOT EXISTS integration_resources (
83
164
  id TEXT PRIMARY KEY,
@@ -93,23 +174,83 @@ async function ensureTables(env) {
93
174
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
94
175
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
95
176
  )`).run();
177
+ // Best-effort one-shot: copy legacy agentsam_cloudflare_connections → user_oauth_tokens.
178
+ await migrateLegacyCloudflareConnections(env);
179
+ }
180
+
181
+ let legacyMigrateAttempted = false;
182
+
183
+ /**
184
+ * One-shot migrate of connected legacy rows into user_oauth_tokens, then supersede legacy.
185
+ * Safe no-op when the legacy table is absent or vault cannot decrypt.
186
+ */
187
+ async function migrateLegacyCloudflareConnections(env) {
188
+ if (legacyMigrateAttempted || !env?.DB?.prepare) return;
189
+ legacyMigrateAttempted = true;
190
+ if (!vaultConfigured(env)) return;
191
+ let results = [];
192
+ try {
193
+ const out = await env.DB.prepare(`
194
+ SELECT connection_id, owner_id, cloudflare_account_id, scopes,
195
+ access_token_encrypted, refresh_token_encrypted, expires_at
196
+ FROM agentsam_cloudflare_connections
197
+ WHERE status = 'connected' AND access_token_encrypted IS NOT NULL
198
+ `).all();
199
+ results = out?.results || [];
200
+ } catch {
201
+ return;
202
+ }
203
+ for (const row of results) {
204
+ const ownerId = String(row.owner_id || '').trim();
205
+ if (!ownerId) continue;
206
+ try {
207
+ const existing = await loadCloudflareConnectionRecord(env, ownerId);
208
+ if (existing) {
209
+ await env.DB.prepare(`
210
+ UPDATE agentsam_cloudflare_connections
211
+ SET status = 'superseded', access_token_encrypted = NULL,
212
+ refresh_token_encrypted = NULL, updated_at = unixepoch()
213
+ WHERE connection_id = ?
214
+ `).bind(row.connection_id).run();
215
+ continue;
216
+ }
217
+ const aad = `cloudflare-connection:${ownerId}`;
218
+ const accessToken = await decryptSecret(env, row.access_token_encrypted, aad);
219
+ const refreshToken = row.refresh_token_encrypted
220
+ ? await decryptSecret(env, row.refresh_token_encrypted, aad)
221
+ : null;
222
+ const spine = await upsertCloudflareUserOauthToken(env, {
223
+ userId: ownerId,
224
+ accessToken,
225
+ refreshToken,
226
+ scopes: row.scopes || '',
227
+ accountId: row.cloudflare_account_id || null,
228
+ expiresAt: row.expires_at ? Number(row.expires_at) : null,
229
+ });
230
+ if (spine?.ok) {
231
+ await env.DB.prepare(`
232
+ UPDATE agentsam_cloudflare_connections
233
+ SET status = 'superseded', access_token_encrypted = NULL,
234
+ refresh_token_encrypted = NULL, updated_at = unixepoch()
235
+ WHERE connection_id = ?
236
+ `).bind(row.connection_id).run();
237
+ }
238
+ } catch (err) {
239
+ console.error('cloudflare_legacy_migrate_failed', String(err?.message || err));
240
+ }
241
+ }
96
242
  }
97
243
 
244
+ /**
245
+ * Optional host-declared resources only — never bake product account IDs here.
246
+ * Shape: env.AGENTSAM_RESOURCE_SCOPE_JSON = { "resources": [ { key, type, id, name, metadata? } ] }
247
+ */
98
248
  async function seedKnownCloudflareResources(env, ownerId, connectionId, cloudflareAccountId) {
99
249
  if (!env?.DB?.prepare || !cloudflareAccountId) return;
100
- let configuredScopes = {};
101
- try { configuredScopes = JSON.parse(env.AGENTSAM_RESOURCE_SCOPE_JSON || '{}'); } catch { configuredScopes = {}; }
102
- const resources = [
103
- { key: 'agentsam-sdk', type: 'worker', id: 'agentsam-sdk', name: 'AgentSam SDK / Local Studio' },
104
- { key: 'agentsam-cad-creator', type: 'worker', id: 'agentsam-cad-creator', name: 'AgentSam CAD Creator' },
105
- { key: 'agentsam-client-cms-editor', type: 'worker', id: 'agentsam-client-cms-editor', name: 'AgentSam Client CMS Editor' },
106
- { key: 'fuelnfreetime', type: 'worker', id: 'fuelnfreetime', name: 'Fuel & Free Time', metadata: {
107
- zone_id: '816a5d2284103e4481987ceeb16c2ca9',
108
- d1_database_id: '9fd6ff92-e407-4b51-8b01-3c93f3845bb2',
109
- r2_bucket: 'fuelnfreetime',
110
- ...(configuredScopes.fuelnfreetime || {}),
111
- } },
112
- ];
250
+ let configured = {};
251
+ try { configured = JSON.parse(env.AGENTSAM_RESOURCE_SCOPE_JSON || '{}'); } catch { configured = {}; }
252
+ const resources = Array.isArray(configured.resources) ? configured.resources : [];
253
+ if (!resources.length) return;
113
254
  const statements = resources.map((resource) => env.DB.prepare(`
114
255
  INSERT OR REPLACE INTO integration_resources (
115
256
  id, tenant_id, connection_id, provider, resource_type, provider_resource_id,
@@ -119,18 +260,34 @@ async function seedKnownCloudflareResources(env, ownerId, connectionId, cloudfla
119
260
  `ires_cf_${cloudflareAccountId}_${resource.key}`,
120
261
  ownerId,
121
262
  connectionId,
122
- resource.type,
123
- resource.id,
124
- resource.name,
263
+ resource.type || 'worker',
264
+ resource.id || resource.key,
265
+ resource.name || resource.key,
125
266
  JSON.stringify({ cloudflare_account_id: cloudflareAccountId, ...(resource.metadata || {}) }),
126
267
  ));
127
268
  if (typeof env.DB.batch === 'function') await env.DB.batch(statements);
128
269
  else for (const statement of statements) await statement.run();
129
270
  }
130
271
 
131
- export async function handleCloudflareConnectionRequest(request, env) {
272
+ /**
273
+ * @param {Request} request
274
+ * @param {object} env
275
+ * @param {{
276
+ * defaultCapabilities?: string[],
277
+ * app?: { id: string },
278
+ * routeRegistry?: object,
279
+ * loginPath?: string,
280
+ * }} [options]
281
+ * Host app supplies default capability ids when the request omits ?capabilities=.
282
+ * Host MUST supply app + routeRegistry for session owner resolution (APP contract).
283
+ * The connector package itself has no product-specific default set.
284
+ */
285
+ export async function handleCloudflareConnectionRequest(request, env, options = {}) {
132
286
  const url = new URL(request.url);
133
287
  const client = resolveCloudflareOAuthClient(env);
288
+ const hostDefaultCapabilities = Array.isArray(options.defaultCapabilities)
289
+ ? options.defaultCapabilities.filter(Boolean)
290
+ : [];
134
291
  if (request.method === 'OPTIONS') {
135
292
  return new Response(null, { status: 204 });
136
293
  }
@@ -141,12 +298,33 @@ export async function handleCloudflareConnectionRequest(request, env) {
141
298
  }
142
299
 
143
300
  const isCallback = url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET';
301
+ const isStart = url.pathname === '/api/connections/cloudflare/start' && request.method === 'GET';
144
302
  let ownerId = '';
145
303
  try {
146
- ownerId = await resolveAuthenticatedOwner(request, env, url, body);
304
+ ownerId = await resolveAuthenticatedOwner(request, env, url, body, {
305
+ app: options.app,
306
+ routeRegistry: options.routeRegistry,
307
+ });
147
308
  } catch (err) {
148
309
  if (!isCallback || err.code === 'untrusted_owner_hint') {
149
310
  const code = err.code || 'unauthenticated';
311
+ // Browser GET /start without a session → Local Studio login, then return here.
312
+ if (isStart && (code === 'unauthenticated' || code === 'AUTH_APP_UNRESOLVED')) {
313
+ if (code === 'AUTH_APP_UNRESOLVED' && (!options.app?.id || !options.routeRegistry)) {
314
+ return json({
315
+ ok: false,
316
+ error: 'AUTH_APP_UNRESOLVED',
317
+ message: 'Host must pass app + routeRegistry into Cloudflare connection handler',
318
+ }, 500);
319
+ }
320
+ const loginPath = String(options.loginPath || '/auth/login').startsWith('/')
321
+ ? String(options.loginPath || '/auth/login')
322
+ : `/${options.loginPath}`;
323
+ const next = `${url.pathname}${url.search}`;
324
+ const login = new URL(loginPath, url.origin);
325
+ login.searchParams.set('next', next);
326
+ return redirect(login.toString());
327
+ }
150
328
  return json({ ok: false, error: code }, code === 'untrusted_owner_hint' ? 400 : 401);
151
329
  }
152
330
  }
@@ -171,27 +349,9 @@ export async function handleCloudflareConnectionRequest(request, env) {
171
349
  await ensureTables(env);
172
350
 
173
351
  if (url.pathname === '/api/connections/cloudflare' && request.method === 'GET') {
174
- let connection = null;
175
- if (env.DB) {
176
- const row = await env.DB.prepare(
177
- `SELECT connection_id, owner_id, cloudflare_account_id, scopes, status, created_at, updated_at, expires_at
178
- FROM agentsam_cloudflare_connections
179
- WHERE owner_id = ? AND status = 'connected'
180
- ORDER BY updated_at DESC LIMIT 1`,
181
- ).bind(ownerId).first();
182
- if (row) {
183
- connection = {
184
- connectionId: row.connection_id,
185
- ownerId: row.owner_id,
186
- cloudflareAccountId: row.cloudflare_account_id,
187
- scopes: row.scopes ? String(row.scopes).split(' ') : [],
188
- status: row.status,
189
- createdAt: row.created_at,
190
- updatedAt: row.updated_at,
191
- expiresAt: row.expires_at,
192
- };
193
- }
194
- }
352
+ const connection = env.DB
353
+ ? await loadCloudflareConnectionRecord(env, ownerId)
354
+ : null;
195
355
  return json({ ok: true, ...cloudflareConnectionSafeStatus(env, connection, ownerId) });
196
356
  }
197
357
 
@@ -202,23 +362,41 @@ export async function handleCloudflareConnectionRequest(request, env) {
202
362
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
203
363
  const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
204
364
  const returnTo = resolveReturnTo(url);
205
- if (env.DB) {
206
- await env.DB.prepare(
207
- `DELETE FROM agentsam_cloudflare_oauth_state WHERE created_at < unixepoch() - 600`,
208
- ).run();
209
- await env.DB.prepare(
210
- `INSERT INTO agentsam_cloudflare_oauth_state (state, owner_id, code_verifier, created_at, return_to)
211
- VALUES (?, ?, ?, unixepoch(), ?)`,
212
- ).bind(state, ownerId, verifier, returnTo || null).run();
213
- } else if (env.oauthState instanceof Map) {
214
- env.oauthState.set(state, { ownerId, verifier, returnTo });
365
+ const saved = await saveOAuthState(env, { state, ownerId, verifier, returnTo });
366
+ if (!saved.ok) {
367
+ return json({
368
+ ok: false,
369
+ error: saved.error || 'oauth_state_unavailable',
370
+ message: saved.error === 'vault_unavailable'
371
+ ? 'VAULT_MASTER_KEY required to encrypt OAuth PKCE verifier'
372
+ : 'oauth state store unavailable',
373
+ }, 503);
215
374
  }
216
375
  const redirectUri = `${url.origin}${CLOUDFLARE_CALLBACK_PATH}`;
376
+ const capabilityParam = url.searchParams.get('capabilities') || url.searchParams.get('capability') || '';
377
+ const packsParam = url.searchParams.get('packs') || url.searchParams.get('pack') || '';
378
+ const requested = capabilityParam
379
+ .split(',')
380
+ .map((s) => s.trim())
381
+ .filter(Boolean);
382
+ const requestedPacks = packsParam
383
+ .split(',')
384
+ .map((s) => s.trim().toLowerCase())
385
+ .filter(Boolean);
386
+ // Explicit packs win alone. Host defaults only when neither packs nor capabilities were requested.
387
+ const capabilitySet = requested.length
388
+ ? requested
389
+ : (requestedPacks.length ? [] : hostDefaultCapabilities);
390
+ const scopes = requestedCloudflareScopes({
391
+ capabilities: capabilitySet,
392
+ packs: requestedPacks,
393
+ });
217
394
  const authorize = buildAuthorizeUrl({
218
395
  clientId: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
219
396
  redirectUri,
220
397
  state,
221
398
  codeChallenge: challenge,
399
+ scopes,
222
400
  });
223
401
  // Browser navigation → 302 to Cloudflare. XHR/fetch (Integrations UI) → JSON.
224
402
  const accept = String(request.headers.get('accept') || '');
@@ -229,7 +407,13 @@ export async function handleCloudflareConnectionRequest(request, env) {
229
407
  if (!wantsJson) {
230
408
  return Response.redirect(authorize, 302);
231
409
  }
232
- return json({ ok: true, authorize_url: authorize, callback: CLOUDFLARE_CALLBACK_PATH });
410
+ return json({
411
+ ok: true,
412
+ authorize_url: authorize,
413
+ callback: CLOUDFLARE_CALLBACK_PATH,
414
+ capabilities: capabilitySet,
415
+ scopes,
416
+ });
233
417
  }
234
418
 
235
419
  if (url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET') {
@@ -237,14 +421,11 @@ export async function handleCloudflareConnectionRequest(request, env) {
237
421
  const state = url.searchParams.get('state') || '';
238
422
  const providerError = url.searchParams.get('error') || '';
239
423
  let stored = null;
240
- if (env.DB) {
241
- stored = await env.DB.prepare(
242
- `SELECT owner_id, code_verifier, created_at, return_to
243
- FROM agentsam_cloudflare_oauth_state WHERE state = ?`,
244
- ).bind(state).first();
245
- } else if (env.oauthState instanceof Map) {
246
- stored = env.oauthState.get(state);
247
- if (stored) stored = { owner_id: stored.ownerId, code_verifier: stored.verifier, return_to: stored.returnTo || '' };
424
+ try {
425
+ stored = await consumeOAuthState(env, state);
426
+ } catch (err) {
427
+ console.error('cloudflare_oauth_state_consume_failed', String(err?.message || err));
428
+ return settingsRedirect(url, 'error', 'oauth_state_unavailable', '');
248
429
  }
249
430
  if (!stored) {
250
431
  // A callback without a one-time PKCE state is forged or expired. Keep
@@ -257,11 +438,9 @@ export async function handleCloudflareConnectionRequest(request, env) {
257
438
  }
258
439
  const createdAt = Number(stored.created_at || 0);
259
440
  const now = Math.floor(Date.now() / 1000);
260
- if (createdAt && now - createdAt > 10 * 60) {
261
- await deleteOAuthState(env, state);
441
+ if (createdAt && now - createdAt > OAUTH_STATE_TTL_SECONDS) {
262
442
  return settingsRedirect(url, 'error', 'oauth_state_expired', stored.return_to || '');
263
443
  }
264
- await deleteOAuthState(env, state);
265
444
  if (providerError) {
266
445
  return settingsRedirect(url, 'error', providerError, stored.return_to || '');
267
446
  }
@@ -288,47 +467,43 @@ export async function handleCloudflareConnectionRequest(request, env) {
288
467
  return settingsRedirect(url, 'error', 'token_exchange_failed', stored.return_to || '');
289
468
  }
290
469
  const tokens = await tokenRes.json();
291
- const connectionId = `cfconn_${crypto.randomUUID().replace(/-/g, '').slice(0, 16)}`;
292
- if (tokens.access_token && !vaultConfigured(env)) {
293
- return settingsRedirect(url, 'error', 'vault_unavailable', stored.return_to || '');
294
- }
295
- const aad = `cloudflare-connection:${ownerId}`;
296
- let accessEnc = null;
297
- let refreshEnc = null;
470
+ const grantedScopeStr = tokens.scope || requestedCloudflareScopes({ capabilities: hostDefaultCapabilities }).join(' ');
471
+ const capabilitySet = hostDefaultCapabilities.slice();
472
+
473
+ // SSOT: user_oauth_tokens only (scope union + client_id provenance + vault seal).
474
+ let spine = null;
298
475
  try {
299
- accessEnc = await sealToken(env, tokens.access_token, aad);
300
- refreshEnc = await sealToken(env, tokens.refresh_token, aad);
476
+ spine = await upsertCloudflareUserOauthToken(env, {
477
+ userId: ownerId,
478
+ accessToken: tokens.access_token,
479
+ refreshToken: tokens.refresh_token || null,
480
+ scopes: grantedScopeStr,
481
+ accountId: tokens.account_id || null,
482
+ expiresAt: tokens.expires_in
483
+ ? Math.floor(Date.now() / 1000) + Number(tokens.expires_in)
484
+ : null,
485
+ clientId: String(env.CLOUDFLARE_OAUTH_CLIENT_ID || ''),
486
+ capabilitySet,
487
+ });
488
+ if (!spine?.ok) {
489
+ console.error('cloudflare_user_oauth_tokens_upsert_failed', spine?.error || 'unknown');
490
+ return settingsRedirect(url, 'error', spine?.error || 'connection_persist_failed', stored.return_to || '');
491
+ }
301
492
  } catch (err) {
302
- return settingsRedirect(url, 'error', err.code || 'vault_unavailable', stored.return_to || '');
493
+ console.error('cloudflare_user_oauth_tokens_upsert_threw', String(err?.message || err));
494
+ return settingsRedirect(url, 'error', 'connection_persist_failed', stored.return_to || '');
303
495
  }
304
- if (env.DB) {
305
- const removePrevious = env.DB.prepare(
306
- `UPDATE agentsam_cloudflare_connections
307
- SET status = 'superseded', access_token_encrypted = NULL,
308
- refresh_token_encrypted = NULL, updated_at = unixepoch()
309
- WHERE owner_id = ? AND status = 'connected'`,
310
- ).bind(ownerId);
311
- const insertConnection = env.DB.prepare(
312
- `INSERT INTO agentsam_cloudflare_connections (
313
- connection_id, owner_id, cloudflare_account_id, scopes, status,
314
- access_token_encrypted, refresh_token_encrypted, created_at, updated_at, expires_at
315
- ) VALUES (?, ?, ?, ?, 'connected', ?, ?, unixepoch(), unixepoch(), ?)`,
316
- ).bind(
317
- connectionId,
496
+
497
+ const connectionId = spine.connectionId || spine.accountId || `uot_${ownerId}`;
498
+ try {
499
+ await seedKnownCloudflareResources(
500
+ env,
318
501
  ownerId,
319
- tokens.account_id || null,
320
- (tokens.scope || requestedCloudflareScopes().join(' ')),
321
- accessEnc,
322
- refreshEnc,
323
- tokens.expires_in ? Math.floor(Date.now() / 1000) + Number(tokens.expires_in) : null,
502
+ connectionId,
503
+ spine.accountId || tokens.account_id || null,
324
504
  );
325
- if (typeof env.DB.batch === 'function') {
326
- await env.DB.batch([removePrevious, insertConnection]);
327
- } else {
328
- await removePrevious.run();
329
- await insertConnection.run();
330
- }
331
- await seedKnownCloudflareResources(env, ownerId, connectionId, tokens.account_id || null);
505
+ } catch (err) {
506
+ console.error('cloudflare_resource_seed_failed', String(err?.message || err));
332
507
  }
333
508
  return settingsRedirect(url, 'connected', '', stored.return_to || '');
334
509
  }
@@ -336,35 +511,25 @@ export async function handleCloudflareConnectionRequest(request, env) {
336
511
  if (url.pathname === '/api/connections/cloudflare/disconnect' && request.method === 'POST') {
337
512
  let providerRevoked = false;
338
513
  if (env.DB) {
339
- const row = await env.DB.prepare(
340
- `SELECT connection_id, owner_id, access_token_encrypted
341
- FROM agentsam_cloudflare_connections
342
- WHERE owner_id = ? AND status = 'connected'
343
- ORDER BY updated_at DESC LIMIT 1`,
344
- ).bind(ownerId).first();
345
- if (row) {
346
- assertConnectionOwner({ ownerId: row.owner_id, connectionId: row.connection_id }, ownerId);
347
- if (row.access_token_encrypted) {
348
- try {
349
- const token = await decryptSecret(env, row.access_token_encrypted, `cloudflare-connection:${ownerId}`);
350
- const revokeParams = new URLSearchParams({ token, client_id: String(env.CLOUDFLARE_OAUTH_CLIENT_ID) });
351
- const revokeResponse = await fetch(CLOUDFLARE_OAUTH_REVOKE_URL, {
352
- method: 'POST',
353
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
354
- body: revokeParams,
355
- });
356
- providerRevoked = revokeResponse.ok;
357
- } catch (error) {
358
- console.error('cloudflare_token_revoke_failed', String(error?.message || error));
359
- }
514
+ const cred = await loadCloudflareFromUserOauthTokens(env, ownerId);
515
+ if (cred?.bearerToken) {
516
+ assertConnectionOwner({ ownerId, connectionId: cred.connectionId }, ownerId);
517
+ try {
518
+ const revokeParams = new URLSearchParams({
519
+ token: cred.bearerToken,
520
+ client_id: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
521
+ });
522
+ const revokeResponse = await fetch(CLOUDFLARE_OAUTH_REVOKE_URL, {
523
+ method: 'POST',
524
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
525
+ body: revokeParams,
526
+ });
527
+ providerRevoked = revokeResponse.ok;
528
+ } catch (error) {
529
+ console.error('cloudflare_token_revoke_failed', String(error?.message || error));
360
530
  }
361
- await env.DB.prepare(`
362
- UPDATE agentsam_cloudflare_connections
363
- SET status = 'revoked', access_token_encrypted = NULL,
364
- refresh_token_encrypted = NULL, expires_at = unixepoch(), updated_at = unixepoch()
365
- WHERE connection_id = ? AND owner_id = ?
366
- `).bind(row.connection_id, ownerId).run();
367
531
  }
532
+ await revokeCloudflareUserOauthTokens(env, ownerId);
368
533
  }
369
534
  return json({ ok: true, status: 'not_configured', provider_revoked: providerRevoked });
370
535
  }