@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
@@ -3,15 +3,17 @@
3
3
  | Lane | Env vars | When |
4
4
  |------|----------|------|
5
5
  | **IAM platform (default)** | `IAM_CLIENT_ID` + `IAM_CLIENT_SECRET` | Minted at install/build for every customer worker |
6
- | Developer Google | `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` | Takes `/api/oauth/google/start` when set |
6
+ | Developer Google (web) | `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` | Takes `/api/oauth/google/start` when set |
7
+ | Google Desktop / CLI | `GOOGLE_DESKTOP_CLIENT_ID` (+ optional `GOOGLE_DESKTOP_CLIENT_SECRET`) | `POST /api/oauth/google/desktop-exchange` (loopback PKCE) |
7
8
  | Developer GitHub | `GITHUB_CLIENT_ID` + `GITHUB_CLIENT_SECRET` | Takes `/api/oauth/github/start` when set |
9
+ | Cloudflare sign-in | `CLOUDFLARE_OAUTH_CLIENT_ID` (+ optional secret) | Takes `/api/oauth/cloudflare/start` when set |
8
10
 
9
11
  Optional: `IAM_ORIGIN` (default `https://inneranimalmedia.com`).
10
12
 
11
13
  ## Secrets law
12
14
 
13
- - `IAM_CLIENT_ID` / `GOOGLE_CLIENT_ID` / `GITHUB_CLIENT_ID` — plaintext Wrangler vars (public by OAuth design).
14
- - `*_CLIENT_SECRET` — **Wrangler secrets only** — never plaintext in `wrangler.toml`.
15
+ - `IAM_CLIENT_ID` / `GOOGLE_CLIENT_ID` / `GOOGLE_DESKTOP_CLIENT_ID` / `GITHUB_CLIENT_ID` / `CLOUDFLARE_OAUTH_CLIENT_ID` — plaintext Wrangler vars (public by OAuth design).
16
+ - `*_CLIENT_SECRET` — **Wrangler secrets only** — never plaintext in `wrangler.toml`. Desktop clients normally have **no** secret.
15
17
 
16
18
  ```bash
17
19
  npx wrangler secret put IAM_CLIENT_SECRET
@@ -24,8 +26,10 @@ These routes live in the **customer** worker (`handleIdentityWorkerRequest`):
24
26
  1. `/api/oauth/inneranimalmedia/start` → redirects to IAM AS (requires minted `IAM_CLIENT_*`)
25
27
  - Legacy alias: `/api/oauth/iam/start`
26
28
  2. `/api/oauth/google/start` → BYOK Google if `GOOGLE_*` set, else platform lane if minted, else 503
27
- 3. `/api/oauth/github/start` → same for GitHub
28
- 4. Callback: `/api/oauth/inneranimalmedia/callback` (platform lane) or `/api/oauth/{google|github|cloudflare}/callback` (BYOK)
29
+ 3. `/api/oauth/google/desktop-exchange` → CLI/desktop PKCE broker (`GOOGLE_DESKTOP_CLIENT_ID`; loopback only)
30
+ 4. `/api/oauth/github/start` → same for GitHub
31
+ 5. `/api/oauth/cloudflare/start` → Cloudflare sign-in when `CLOUDFLARE_OAUTH_CLIENT_ID` set
32
+ 6. Callback: `/api/oauth/inneranimalmedia/callback` (platform lane) or `/api/oauth/{google|github|cloudflare}/callback` (BYOK)
29
33
  - Legacy alias: `/api/oauth/iam/callback`
30
34
 
31
35
  Register platform redirect URI on the IAM client:
@@ -49,6 +53,7 @@ OIDC scopes: `openid profile email` (not a bespoke `identity:*` namespace).
49
53
  ## Code
50
54
 
51
55
  - `credentials.js` — lane resolution
56
+ - `google-desktop-exchange.js` — desktop PKCE token broker
52
57
  - `providers/iam/` — IAM AS client (`getIamAuthUrl`, `exchangeIamCode`, `fetchIamProfile`)
53
58
  - `iam-platform.js` — customer-worker start/callback wiring (uses `providers/iam/`)
54
59
  - `pkce.js` — shared PKCE helpers
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Portable OAuth consent view contract.
3
+ *
4
+ * company = issuer branding SSOT
5
+ * resource = protected API/MCP/product
6
+ * client = connecting app (ChatGPT, Claude, Cursor, etc.)
7
+ */
8
+
9
+ function clean(value) {
10
+ return value == null ? '' : String(value).trim();
11
+ }
12
+
13
+ function nullable(value) {
14
+ const v = clean(value);
15
+ return v || null;
16
+ }
17
+
18
+ function safeHttpUrl(value) {
19
+ const raw = nullable(value);
20
+ if (!raw) return null;
21
+ try {
22
+ const parsed = new URL(raw);
23
+ if (!['https:', 'http:'].includes(parsed.protocol)) return null;
24
+ return parsed.toString();
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function safeColor(value, fallback) {
31
+ const raw = clean(value);
32
+ return /^#[0-9a-f]{3,8}$/i.test(raw) ? raw : fallback;
33
+ }
34
+
35
+ export function normalizeOAuthConsentCompany(company = {}) {
36
+ return Object.freeze({
37
+ id: nullable(company.id),
38
+ slug: nullable(company.slug),
39
+ name: clean(company.name) || 'Your App',
40
+ legalName: nullable(company.legalName ?? company.legal_name),
41
+ logoUrl: safeHttpUrl(company.logoUrl ?? company.logo_url),
42
+ faviconUrl: safeHttpUrl(company.faviconUrl ?? company.favicon_url),
43
+ primaryColor: safeColor(company.primaryColor ?? company.primary_color, '#171717'),
44
+ authBgColor: safeColor(company.authBgColor ?? company.auth_bg_color, '#0b1020'),
45
+ websiteUrl: safeHttpUrl(company.websiteUrl ?? company.website_url),
46
+ tagline: nullable(company.tagline),
47
+ });
48
+ }
49
+
50
+ export function normalizeOAuthConsentResource(resource = {}) {
51
+ const audience = clean(resource.audience);
52
+ const displayName = clean(resource.displayName ?? resource.display_name);
53
+ if (!audience) throw new Error('oauth_consent_resource_audience_required');
54
+ if (!displayName) throw new Error('oauth_consent_resource_display_name_required');
55
+ return Object.freeze({
56
+ id: nullable(resource.id),
57
+ audience,
58
+ resourceType: clean(resource.resourceType ?? resource.resource_type) || 'mcp',
59
+ displayName,
60
+ description: nullable(resource.description),
61
+ logoUrl: safeHttpUrl(resource.logoUrl ?? resource.logo_url),
62
+ homepageUrl: safeHttpUrl(resource.homepageUrl ?? resource.homepage_url),
63
+ metadata: resource.metadata && typeof resource.metadata === 'object' ? resource.metadata : {},
64
+ });
65
+ }
66
+
67
+ export function normalizeOAuthConsentClient(client = {}) {
68
+ const clientId = clean(client.clientId ?? client.client_id);
69
+ const displayName = clean(client.displayName ?? client.display_name ?? client.name);
70
+ if (!clientId) throw new Error('oauth_consent_client_id_required');
71
+ if (!displayName) throw new Error('oauth_consent_client_display_name_required');
72
+ return Object.freeze({
73
+ clientId,
74
+ displayName,
75
+ logoUrl: safeHttpUrl(client.logoUrl ?? client.logo_url),
76
+ homepageUrl: safeHttpUrl(client.homepageUrl ?? client.homepage_url),
77
+ });
78
+ }
79
+
80
+ export function normalizeOAuthConsentScope(scope = {}, index = 0) {
81
+ const key = clean(scope.scope);
82
+ if (!key) throw new Error(`oauth_consent_scope_required:${index}`);
83
+ const sensitivity = clean(scope.sensitivity);
84
+ return Object.freeze({
85
+ scope: key,
86
+ label: clean(scope.label) || key,
87
+ description: clean(scope.description) || key,
88
+ category: clean(scope.category) || 'general',
89
+ sensitivity: ['normal', 'elevated', 'sensitive'].includes(sensitivity) ? sensitivity : 'normal',
90
+ required: Boolean(scope.required),
91
+ sortOrder: Number.isFinite(Number(scope.sortOrder ?? scope.sort_order))
92
+ ? Number(scope.sortOrder ?? scope.sort_order)
93
+ : 50,
94
+ });
95
+ }
96
+
97
+ export function buildOAuthConsentView(input = {}) {
98
+ const issuer = normalizeOAuthConsentCompany(input.company || input.issuer || {});
99
+ const resource = normalizeOAuthConsentResource(input.resource || {});
100
+ const client = normalizeOAuthConsentClient(input.client || {});
101
+ const scopes = (Array.isArray(input.scopes) ? input.scopes : [])
102
+ .map(normalizeOAuthConsentScope)
103
+ .sort((a, b) => a.sortOrder - b.sortOrder || a.scope.localeCompare(b.scope));
104
+
105
+ return Object.freeze({
106
+ schemaVersion: 'agentsam.identity.oauth-consent.v1',
107
+ issuer,
108
+ resource,
109
+ client,
110
+ scopes,
111
+ signedInEmail: nullable(input.signedInEmail ?? input.signed_in_email),
112
+ authorization: Object.freeze({
113
+ id: nullable(input.authorization?.id),
114
+ expiresAt: input.authorization?.expiresAt ?? input.authorization?.expires_at ?? null,
115
+ }),
116
+ copy: Object.freeze({
117
+ headline: `${resource.displayName} wants to connect to ${client.displayName}`,
118
+ helper: resource.description || `${client.displayName} is requesting access to ${resource.displayName}.`,
119
+ footer: `Authorization secured by ${issuer.name}`,
120
+ }),
121
+ });
122
+ }
123
+
124
+ function escapeHtml(value) {
125
+ return String(value ?? '')
126
+ .replaceAll('&', '&')
127
+ .replaceAll('<', '&lt;')
128
+ .replaceAll('>', '&gt;')
129
+ .replaceAll('"', '&quot;')
130
+ .replaceAll("'", '&#39;');
131
+ }
132
+
133
+ function consentMark(entity, className) {
134
+ const label = clean(entity?.displayName ?? entity?.name) || '?';
135
+ if (entity?.logoUrl) {
136
+ return `<img class="${className}" src="${escapeHtml(entity.logoUrl)}" alt="${escapeHtml(label)}" />`;
137
+ }
138
+ const initials = label
139
+ .split(/\s+/)
140
+ .slice(0, 2)
141
+ .map((part) => part[0] || '')
142
+ .join('')
143
+ .toUpperCase();
144
+ return `<span class="${className} ${className}--fallback" aria-hidden="true">${escapeHtml(initials || '?')}</span>`;
145
+ }
146
+
147
+ /**
148
+ * Render the portable OAuth consent card.
149
+ *
150
+ * Presentation is fixed/trusted code. D1/company supplies safe brand values,
151
+ * never arbitrary CSS or HTML.
152
+ */
153
+ export function renderOAuthConsentHtml(viewInput, options = {}) {
154
+ const view = viewInput?.schemaVersion
155
+ ? viewInput
156
+ : buildOAuthConsentView(viewInput);
157
+
158
+ const action = clean(options.action) || '/api/oauth/consent';
159
+ const authorizationId = nullable(
160
+ options.authorizationId ??
161
+ options.authorization_id ??
162
+ view.authorization?.id
163
+ );
164
+ const csrfToken = nullable(options.csrfToken ?? options.csrf_token);
165
+
166
+ const issuerLogo = consentMark(
167
+ { ...view.issuer, displayName: view.issuer.name },
168
+ 'consent-issuer-logo',
169
+ );
170
+ const resourceLogo = consentMark(view.resource, 'consent-resource-logo');
171
+ const clientLogo = consentMark(view.client, 'consent-client-logo');
172
+
173
+ const scopes = view.scopes.map((scope) => `
174
+ <li class="consent-scope" data-sensitivity="${escapeHtml(scope.sensitivity)}">
175
+ <span class="consent-scope-dot" aria-hidden="true"></span>
176
+ <span class="consent-scope-copy">
177
+ <strong>${escapeHtml(scope.label)}${scope.required ? ' <small>Required</small>' : ''}</strong>
178
+ <span>${escapeHtml(scope.description)}</span>
179
+ </span>
180
+ </li>`).join('');
181
+
182
+ return `<!doctype html>
183
+ <html lang="en">
184
+ <head>
185
+ <meta charset="utf-8" />
186
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
187
+ <meta name="color-scheme" content="light" />
188
+ <title>${escapeHtml(view.copy.headline)}</title>
189
+ ${view.issuer.faviconUrl ? `<link rel="icon" href="${escapeHtml(view.issuer.faviconUrl)}" />` : ''}
190
+ <style>
191
+ :root {
192
+ --consent-brand: ${escapeHtml(view.issuer.primaryColor)};
193
+ --consent-bg: ${escapeHtml(view.issuer.authBgColor)};
194
+ --consent-ink: #171a1f;
195
+ --consent-muted: #6d7480;
196
+ --consent-line: #e7e9ed;
197
+ --consent-panel: rgba(255,255,255,.97);
198
+ }
199
+ * { box-sizing: border-box; }
200
+ body {
201
+ margin: 0;
202
+ min-height: 100vh;
203
+ padding: 28px;
204
+ display: grid;
205
+ place-items: center;
206
+ background: var(--consent-bg);
207
+ color: var(--consent-ink);
208
+ font: 14px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
209
+ }
210
+ .consent-card {
211
+ width: min(460px, 100%);
212
+ overflow: hidden;
213
+ border: 1px solid rgba(255,255,255,.48);
214
+ border-radius: 18px;
215
+ background: var(--consent-panel);
216
+ box-shadow: 0 28px 90px rgba(0,0,0,.28);
217
+ backdrop-filter: blur(18px);
218
+ }
219
+ .consent-issuer {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 10px;
223
+ padding: 18px 22px;
224
+ border-bottom: 1px solid var(--consent-line);
225
+ color: #68707b;
226
+ font-size: 11px;
227
+ font-weight: 750;
228
+ letter-spacing: .12em;
229
+ text-transform: uppercase;
230
+ }
231
+ .consent-issuer-logo,
232
+ .consent-resource-logo,
233
+ .consent-client-logo {
234
+ display: grid;
235
+ place-items: center;
236
+ object-fit: contain;
237
+ background: #fff;
238
+ border: 1px solid #dfe3e8;
239
+ border-radius: 10px;
240
+ color: #30343a;
241
+ font-weight: 800;
242
+ }
243
+ .consent-issuer-logo { width: 28px; height: 28px; font-size: 10px; }
244
+ .consent-resource-logo { width: 44px; height: 44px; font-size: 11px; }
245
+ .consent-client-logo { width: 28px; height: 28px; font-size: 10px; }
246
+ .consent-main { padding: 22px; }
247
+ .consent-resource {
248
+ display: flex;
249
+ gap: 12px;
250
+ align-items: center;
251
+ margin-bottom: 18px;
252
+ }
253
+ .consent-resource strong { display: block; font-size: 15px; }
254
+ .consent-resource span {
255
+ display: block;
256
+ max-width: 330px;
257
+ margin-top: 2px;
258
+ overflow-wrap: anywhere;
259
+ color: #747c87;
260
+ font-size: 12px;
261
+ }
262
+ .consent-title {
263
+ margin: 0 0 8px;
264
+ font-size: 18px;
265
+ line-height: 1.25;
266
+ letter-spacing: -.015em;
267
+ }
268
+ .consent-helper { margin: 0 0 16px; color: var(--consent-muted); }
269
+ .consent-client {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 9px;
273
+ margin: 0 0 16px;
274
+ padding: 11px 12px;
275
+ border-radius: 11px;
276
+ background: #f5f6f8;
277
+ }
278
+ .consent-client strong { font-size: 13px; }
279
+ .consent-signed-in {
280
+ margin: 0 0 16px;
281
+ color: #747c87;
282
+ font-size: 12px;
283
+ }
284
+ .consent-signed-in b { color: #31363d; }
285
+ .consent-section-label {
286
+ margin: 16px 0 8px;
287
+ color: #777f89;
288
+ font-size: 11px;
289
+ font-weight: 700;
290
+ letter-spacing: .1em;
291
+ text-transform: uppercase;
292
+ }
293
+ .consent-scopes {
294
+ margin: 0;
295
+ padding: 0;
296
+ list-style: none;
297
+ border-top: 1px solid #edf0f2;
298
+ }
299
+ .consent-scope {
300
+ display: grid;
301
+ grid-template-columns: 10px 1fr;
302
+ gap: 10px;
303
+ padding: 12px 2px;
304
+ border-bottom: 1px solid #edf0f2;
305
+ }
306
+ .consent-scope-dot {
307
+ width: 7px;
308
+ height: 7px;
309
+ margin-top: 6px;
310
+ border-radius: 50%;
311
+ background: var(--consent-brand);
312
+ }
313
+ .consent-scope[data-sensitivity="sensitive"] .consent-scope-dot { background: #b7791f; }
314
+ .consent-scope-copy strong { display: block; font-size: 13px; }
315
+ .consent-scope-copy strong small {
316
+ color: #777f89;
317
+ font-size: 10px;
318
+ font-weight: 650;
319
+ }
320
+ .consent-scope-copy > span {
321
+ display: block;
322
+ margin-top: 2px;
323
+ color: #747c87;
324
+ font-size: 12px;
325
+ }
326
+ .consent-actions {
327
+ display: grid;
328
+ gap: 8px;
329
+ margin-top: 20px;
330
+ }
331
+ .consent-actions button {
332
+ border: 0;
333
+ border-radius: 9px;
334
+ padding: 11px 14px;
335
+ cursor: pointer;
336
+ font: inherit;
337
+ font-weight: 750;
338
+ }
339
+ .consent-authorize { background: var(--consent-brand); color: #fff; }
340
+ .consent-cancel { background: transparent; color: #5f6670; }
341
+ .consent-footer {
342
+ padding: 14px 22px 18px;
343
+ border-top: 1px solid #edf0f2;
344
+ color: #8a929c;
345
+ font-size: 11px;
346
+ text-align: center;
347
+ }
348
+ </style>
349
+ </head>
350
+ <body>
351
+ <article class="consent-card">
352
+ <header class="consent-issuer">${issuerLogo}<span>${escapeHtml(view.issuer.name)}</span></header>
353
+ <main class="consent-main">
354
+ <div class="consent-resource">
355
+ ${resourceLogo}
356
+ <div>
357
+ <strong>${escapeHtml(view.resource.displayName)}</strong>
358
+ <span>${escapeHtml(view.resource.audience)}</span>
359
+ </div>
360
+ </div>
361
+
362
+ <h1 class="consent-title">${escapeHtml(view.copy.headline)}</h1>
363
+ <p class="consent-helper">${escapeHtml(view.copy.helper)}</p>
364
+
365
+ <div class="consent-client">
366
+ ${clientLogo}
367
+ <strong>${escapeHtml(view.client.displayName)}</strong>
368
+ </div>
369
+
370
+ ${view.signedInEmail
371
+ ? `<p class="consent-signed-in">Signed in as <b>${escapeHtml(view.signedInEmail)}</b></p>`
372
+ : ''}
373
+
374
+ <p class="consent-section-label">Access requested</p>
375
+ <ul class="consent-scopes">${scopes || '<li class="consent-scope"><span class="consent-scope-dot"></span><span class="consent-scope-copy"><strong>No additional scopes</strong></span></li>'}</ul>
376
+
377
+ <form class="consent-actions" action="${escapeHtml(action)}" method="post">
378
+ ${authorizationId ? `<input type="hidden" name="authorization_id" value="${escapeHtml(authorizationId)}" />` : ''}
379
+ ${csrfToken ? `<input type="hidden" name="csrf_token" value="${escapeHtml(csrfToken)}" />` : ''}
380
+ <button class="consent-authorize" type="submit" name="decision" value="approve">Authorize</button>
381
+ <button class="consent-cancel" type="submit" name="decision" value="deny">Cancel</button>
382
+ </form>
383
+ </main>
384
+ <footer class="consent-footer">${escapeHtml(view.copy.footer)}</footer>
385
+ </article>
386
+ </body>
387
+ </html>`;
388
+ }
@@ -1,19 +1,20 @@
1
- import { DEFAULT_IAM_ORIGIN, resolveIamOrigin } from '../contracts/auth-config.js';
1
+ import { resolveIamOrigin } from '../contracts/auth-config.js';
2
2
 
3
3
  /**
4
4
  * OAuth credential lanes for customer apps.
5
5
  *
6
- * Default (minted at install/build): IAM_CLIENT_ID + IAM_CLIENT_SECRET
6
+ * Required: IAM_CLIENT_ID + IAM_CLIENT_SECRET + IAM_OAUTH_ISSUER
7
7
  * — ID may be a plaintext Wrangler var (public by OAuth design).
8
8
  * — SECRET is wrangler secret put only (encryption is law, not luxury).
9
+ * — ISSUER has no DEFAULT_* — unset means not configured.
9
10
  *
10
- * Developer BYOK: GOOGLE_CLIENT_* / GITHUB_CLIENT_* when set for that provider
11
- * take the /api/oauth/{provider}/start button; otherwise the button uses the
12
- * Inner Animal Media platform lane (`inneranimalmedia`).
11
+ * Developer BYOK:
12
+ * GOOGLE_CLIENT_* / GITHUB_CLIENT_* — browser OAuth start buttons
13
+ * GOOGLE_DESKTOP_CLIENT_ID — CLI/desktop PKCE (+ optional DESKTOP secret)
14
+ * CLOUDFLARE_OAUTH_CLIENT_ID — Cloudflare sign-in (secret optional for PKCE)
15
+ * When unset, Google/GitHub buttons fall back to the IAM platform lane.
13
16
  */
14
17
 
15
- /** @deprecated Use DEFAULT_IAM_ORIGIN from the auth configuration contract. */
16
- export const DEFAULT_IAM_OAUTH_ISSUER = DEFAULT_IAM_ORIGIN;
17
18
  export const IAM_PLATFORM_STATE_PROVIDER = 'iam_platform';
18
19
  /** Canonical platform OAuth callback (protocol id = inneranimalmedia). */
19
20
  export const IAM_PLATFORM_CALLBACK_PATH = '/api/oauth/inneranimalmedia/callback';
@@ -28,7 +29,12 @@ export function resolveIamPlatformCredentials(env) {
28
29
  const clientId = String(env?.IAM_CLIENT_ID || '').trim();
29
30
  const clientSecret = String(env?.IAM_CLIENT_SECRET || '').trim();
30
31
  if (!clientId || !clientSecret) return null;
31
- const origin = resolveIamOrigin(env);
32
+ let origin;
33
+ try {
34
+ origin = resolveIamOrigin(env);
35
+ } catch {
36
+ return null;
37
+ }
32
38
  // `issuer` remains for one migration window so existing consumers do not break.
33
39
  return { clientId, clientSecret, origin, issuer: origin };
34
40
  }
@@ -46,9 +52,9 @@ export function requireIamPlatformCredentials(env) {
46
52
 
47
53
  /**
48
54
  * @param {Record<string, unknown> | null | undefined} env
49
- * @param {'google' | 'github' | 'cloudflare' | 'inneranimalmedia' | 'iam'} provider
55
+ * @param {'google' | 'google_desktop' | 'github' | 'cloudflare' | 'inneranimalmedia' | 'iam'} provider
50
56
  * @returns {{
51
- * lane: 'iam_platform' | 'byok_google' | 'byok_github' | 'byok_cloudflare',
57
+ * lane: 'iam_platform' | 'byok_google' | 'byok_google_desktop' | 'byok_github' | 'byok_cloudflare',
52
58
  * clientId: string,
53
59
  * clientSecret: string,
54
60
  * origin?: string,
@@ -57,7 +63,7 @@ export function requireIamPlatformCredentials(env) {
57
63
  * } | null}
58
64
  */
59
65
  export function resolveOAuthCredentialLane(env, provider) {
60
- const key = String(provider || '').trim().toLowerCase();
66
+ const key = String(provider || '').trim().toLowerCase().replace(/-/g, '_');
61
67
 
62
68
  // Protocol/selection id is inneranimalmedia; `iam` remains a legacy alias.
63
69
  if (key === 'inneranimalmedia' || key === 'iam') {
@@ -81,6 +87,20 @@ export function resolveOAuthCredentialLane(env, provider) {
81
87
  }
82
88
  }
83
89
 
90
+ if (key === 'google_desktop') {
91
+ const clientId = String(env?.GOOGLE_DESKTOP_CLIENT_ID || '').trim();
92
+ const clientSecret = String(env?.GOOGLE_DESKTOP_CLIENT_SECRET || '').trim();
93
+ if (clientId) {
94
+ return {
95
+ lane: 'byok_google_desktop',
96
+ clientId,
97
+ clientSecret,
98
+ provider: 'google_desktop',
99
+ };
100
+ }
101
+ return null;
102
+ }
103
+
84
104
  if (key === 'github') {
85
105
  const clientId = String(env?.GITHUB_CLIENT_ID || '').trim();
86
106
  const clientSecret = String(env?.GITHUB_CLIENT_SECRET || '').trim();
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Server-side Google token exchange for CLI / desktop PKCE.
3
+ *
4
+ * Desktop/"installed" OAuth clients must be type Desktop in Google Console (no secret).
5
+ * If the configured desktop client_id was created as a Web client, Google demands a secret.
6
+ * Secrets never leave the Worker: GOOGLE_DESKTOP_CLIENT_SECRET (optional) or GOOGLE_CLIENT_SECRET
7
+ * when client_id matches the web client.
8
+ */
9
+
10
+ const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
11
+
12
+ function clean(value) {
13
+ return value == null ? '' : String(value).trim();
14
+ }
15
+
16
+ function json(data, status = 200) {
17
+ return new Response(JSON.stringify(data), {
18
+ status,
19
+ headers: {
20
+ 'content-type': 'application/json; charset=utf-8',
21
+ 'cache-control': 'no-store',
22
+ },
23
+ });
24
+ }
25
+
26
+ function isLoopbackRedirect(uri) {
27
+ try {
28
+ const u = new URL(String(uri || ''));
29
+ if (u.protocol !== 'http:') return false;
30
+ return u.hostname === '127.0.0.1' || u.hostname === 'localhost' || u.hostname === '[::1]';
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Resolve which secret (if any) the Worker may attach for this client_id.
38
+ */
39
+ export function resolveGoogleExchangeSecret(env, clientId) {
40
+ const id = clean(clientId);
41
+ const desktopId = clean(env?.GOOGLE_DESKTOP_CLIENT_ID);
42
+ const webId = clean(env?.GOOGLE_CLIENT_ID);
43
+ const desktopSecret = clean(env?.GOOGLE_DESKTOP_CLIENT_SECRET);
44
+ const webSecret = clean(env?.GOOGLE_CLIENT_SECRET);
45
+
46
+ if (id && desktopId && id === desktopId && desktopSecret) {
47
+ return { clientId: id, clientSecret: desktopSecret, mode: 'desktop_with_optional_secret' };
48
+ }
49
+ if (id && webId && id === webId && webSecret) {
50
+ return { clientId: id, clientSecret: webSecret, mode: 'web_confidential' };
51
+ }
52
+ if (id && desktopId && id === desktopId) {
53
+ return { clientId: id, clientSecret: null, mode: 'desktop_public_pkce' };
54
+ }
55
+ return { clientId: id || null, clientSecret: null, mode: 'unknown_client' };
56
+ }
57
+
58
+ export async function exchangeGoogleAuthorizationCode({
59
+ code,
60
+ codeVerifier,
61
+ clientId,
62
+ redirectUri,
63
+ clientSecret = null,
64
+ fetchImpl = fetch,
65
+ } = {}) {
66
+ const body = new URLSearchParams({
67
+ grant_type: 'authorization_code',
68
+ code: clean(code),
69
+ code_verifier: clean(codeVerifier),
70
+ client_id: clean(clientId),
71
+ redirect_uri: clean(redirectUri),
72
+ });
73
+ if (clean(clientSecret)) body.set('client_secret', clean(clientSecret));
74
+
75
+ const res = await fetchImpl(GOOGLE_TOKEN_URL, {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
78
+ body,
79
+ });
80
+ const data = await res.json().catch(() => ({}));
81
+ return { ok: res.ok && Boolean(data.access_token), status: res.status, data };
82
+ }
83
+
84
+ /**
85
+ * POST /api/oauth/google/desktop-exchange
86
+ * Body JSON: { code, code_verifier, redirect_uri, client_id }
87
+ */
88
+ export async function handleGoogleDesktopExchangeRequest(request, env, opts = {}) {
89
+ if (request.method !== 'POST') {
90
+ return json({ ok: false, error: 'method_not_allowed' }, 405);
91
+ }
92
+
93
+ let payload = {};
94
+ try {
95
+ payload = await request.json();
96
+ } catch {
97
+ return json({ ok: false, error: 'invalid_json' }, 400);
98
+ }
99
+
100
+ const code = clean(payload.code);
101
+ const codeVerifier = clean(payload.code_verifier || payload.codeVerifier);
102
+ const redirectUri = clean(payload.redirect_uri || payload.redirectUri);
103
+ const clientId = clean(payload.client_id || payload.clientId);
104
+
105
+ if (!code || !codeVerifier || !redirectUri || !clientId) {
106
+ return json({
107
+ ok: false,
108
+ error: 'code_code_verifier_redirect_uri_client_id_required',
109
+ }, 400);
110
+ }
111
+ if (!isLoopbackRedirect(redirectUri)) {
112
+ return json({ ok: false, error: 'redirect_uri_must_be_loopback' }, 400);
113
+ }
114
+
115
+ const allowed = new Set(
116
+ [clean(env?.GOOGLE_DESKTOP_CLIENT_ID), clean(env?.GOOGLE_CLIENT_ID)].filter(Boolean),
117
+ );
118
+ if (!allowed.has(clientId)) {
119
+ return json({ ok: false, error: 'client_id_not_configured_on_host' }, 403);
120
+ }
121
+
122
+ const resolved = resolveGoogleExchangeSecret(env, clientId);
123
+ if (resolved.mode === 'unknown_client') {
124
+ return json({ ok: false, error: 'client_id_not_configured_on_host' }, 403);
125
+ }
126
+
127
+ const fetchImpl = opts.fetchImpl || fetch;
128
+ const result = await exchangeGoogleAuthorizationCode({
129
+ code,
130
+ codeVerifier,
131
+ clientId,
132
+ redirectUri,
133
+ clientSecret: resolved.clientSecret,
134
+ fetchImpl,
135
+ });
136
+
137
+ const detail = String(result.data?.error_description || result.data?.error || '');
138
+ if (
139
+ !result.ok
140
+ && /client_secret/i.test(detail)
141
+ && resolved.mode === 'desktop_public_pkce'
142
+ && !resolved.clientSecret
143
+ ) {
144
+ return json({
145
+ ok: false,
146
+ error: 'desktop_client_requires_secret',
147
+ detail,
148
+ remediation: [
149
+ 'In Google Cloud Console create OAuth client type Desktop app (no secret) and set GOOGLE_DESKTOP_CLIENT_ID to that client id.',
150
+ 'Or if this client id is a Web app, put its secret on the Worker: wrangler secret put GOOGLE_DESKTOP_CLIENT_SECRET',
151
+ 'Web browser login continues to use GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET.',
152
+ ],
153
+ }, 502);
154
+ }
155
+
156
+ if (!result.ok) {
157
+ return json({
158
+ ok: false,
159
+ error: 'google_token_exchange_failed',
160
+ detail: detail || `http_${result.status}`,
161
+ }, 502);
162
+ }
163
+
164
+ return json({
165
+ ok: true,
166
+ access_token: result.data.access_token,
167
+ refresh_token: result.data.refresh_token || null,
168
+ token_type: result.data.token_type || 'Bearer',
169
+ expires_in: result.data.expires_in || null,
170
+ scope: result.data.scope || null,
171
+ exchange_mode: resolved.mode,
172
+ });
173
+ }