@agent-native/core 0.80.11 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +47 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +419 -19
  5. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  6. package/corpus/core/src/client/chat/index.ts +1 -0
  7. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  8. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  9. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  10. package/corpus/core/src/client/index.ts +1 -0
  11. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  12. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  13. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  14. package/corpus/core/src/db/client.ts +29 -2
  15. package/corpus/core/src/provider-api/index.ts +29 -0
  16. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  17. package/corpus/core/src/styles/agent-native.css +54 -0
  18. package/corpus/core/src/usage/store.ts +2 -2
  19. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  20. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  21. package/corpus/templates/analytics/app/global.css +8 -0
  22. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  23. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  24. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  25. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  26. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  27. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  28. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  33. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  34. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  35. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  36. package/corpus/templates/assets/app/global.css +15 -0
  37. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  38. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  39. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  40. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  41. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  42. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  43. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  45. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  46. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  47. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  48. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  49. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  50. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  51. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  52. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  55. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  56. package/corpus/templates/design/AGENTS.md +27 -0
  57. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  58. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  59. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  60. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  61. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  62. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  63. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  64. package/corpus/templates/design/actions/export-zip.ts +23 -8
  65. package/corpus/templates/design/actions/generate-design.ts +53 -1
  66. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  67. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  68. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  69. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  70. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  71. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  72. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  73. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  74. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  75. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  76. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  77. package/corpus/templates/design/actions/navigate.ts +18 -3
  78. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  79. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  80. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  81. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  82. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  83. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  84. package/corpus/templates/design/actions/view-screen.ts +1 -1
  85. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  86. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  87. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  88. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  89. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  90. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  91. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  92. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  93. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  94. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  95. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  96. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  97. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  98. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  99. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  100. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  101. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  102. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  103. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  104. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  105. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  106. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  107. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  108. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  109. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  110. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  111. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  112. package/corpus/templates/design/app/global.css +7 -0
  113. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  114. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  115. package/corpus/templates/design/app/i18n-data.ts +358 -0
  116. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  117. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  118. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  119. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  120. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  143. package/corpus/templates/design/e2e/helpers.ts +168 -65
  144. package/corpus/templates/design/package.json +3 -1
  145. package/corpus/templates/design/server/db/schema.ts +23 -0
  146. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  147. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  148. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  149. package/corpus/templates/design/server/plugins/db.ts +39 -0
  150. package/corpus/templates/design/server/register-secrets.ts +38 -0
  151. package/corpus/templates/design/shared/board-file.ts +228 -0
  152. package/corpus/templates/design/shared/board-objects.ts +288 -0
  153. package/corpus/templates/design/shared/code-layer.ts +52 -0
  154. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  155. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  156. package/corpus/templates/design/tsconfig.json +2 -1
  157. package/corpus/templates/slides/AGENTS.md +9 -0
  158. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  159. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  160. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  161. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  162. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  163. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  164. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  166. package/corpus/templates/slides/package.json +1 -0
  167. package/corpus/templates/slides/vite.config.ts +1 -0
  168. package/dist/cli/design-connect.d.ts +25 -0
  169. package/dist/cli/design-connect.d.ts.map +1 -1
  170. package/dist/cli/design-connect.js +357 -18
  171. package/dist/cli/design-connect.js.map +1 -1
  172. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  173. package/dist/client/ConnectBuilderCard.js +1 -0
  174. package/dist/client/ConnectBuilderCard.js.map +1 -1
  175. package/dist/client/chat/index.d.ts +1 -1
  176. package/dist/client/chat/index.d.ts.map +1 -1
  177. package/dist/client/chat/index.js +1 -1
  178. package/dist/client/chat/index.js.map +1 -1
  179. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  180. package/dist/client/chat/run-recovery.js +5 -5
  181. package/dist/client/chat/run-recovery.js.map +1 -1
  182. package/dist/client/chat-view-transition.d.ts +6 -0
  183. package/dist/client/chat-view-transition.d.ts.map +1 -1
  184. package/dist/client/chat-view-transition.js +23 -2
  185. package/dist/client/chat-view-transition.js.map +1 -1
  186. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  187. package/dist/client/composer/PromptComposer.js +3 -2
  188. package/dist/client/composer/PromptComposer.js.map +1 -1
  189. package/dist/client/index.d.ts +1 -1
  190. package/dist/client/index.d.ts.map +1 -1
  191. package/dist/client/index.js +1 -1
  192. package/dist/client/index.js.map +1 -1
  193. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  194. package/dist/client/org/OrgSwitcher.js +11 -4
  195. package/dist/client/org/OrgSwitcher.js.map +1 -1
  196. package/dist/client/sharing/ShareButton.js +3 -3
  197. package/dist/client/sharing/ShareButton.js.map +1 -1
  198. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  199. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  200. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  201. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  202. package/dist/collab/awareness.d.ts +2 -2
  203. package/dist/collab/awareness.d.ts.map +1 -1
  204. package/dist/collab/routes.d.ts +2 -2
  205. package/dist/db/client.d.ts +1 -0
  206. package/dist/db/client.d.ts.map +1 -1
  207. package/dist/db/client.js +28 -2
  208. package/dist/db/client.js.map +1 -1
  209. package/dist/notifications/routes.d.ts +3 -3
  210. package/dist/observability/routes.d.ts +2 -2
  211. package/dist/progress/routes.d.ts +1 -1
  212. package/dist/provider-api/index.d.ts +5 -4
  213. package/dist/provider-api/index.d.ts.map +1 -1
  214. package/dist/provider-api/index.js +28 -0
  215. package/dist/provider-api/index.js.map +1 -1
  216. package/dist/resources/handlers.d.ts +3 -3
  217. package/dist/server/core-routes-plugin.d.ts +25 -0
  218. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  219. package/dist/server/core-routes-plugin.js +43 -18
  220. package/dist/server/core-routes-plugin.js.map +1 -1
  221. package/dist/server/transcribe-voice.d.ts +1 -1
  222. package/dist/styles/agent-native.css +54 -0
  223. package/dist/usage/store.js +2 -2
  224. package/dist/usage/store.js.map +1 -1
  225. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2043
31
- - template files: 4729
31
+ - template files: 4801
@@ -1,5 +1,52 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.81.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3164729: Add secure localhost bridge write endpoints to the design connect bridge.
8
+
9
+ `startDesignConnectBridge` now mints a cryptographically random per-session
10
+ `bridgeToken` (exposed on the returned `DesignConnectBridge` object) and
11
+ serves three new token-gated POST endpoints on the same localhost-only server:
12
+ - `POST /read-file` — reads any file within the root (no extension restriction)
13
+ - `POST /write-file` — writes `.html`, `.htm`, or `.css` files within the root
14
+ - `POST /apply-edit` — patches an existing file via `{search, replace}` or
15
+ replaces it entirely via `{content}`
16
+
17
+ All three endpoints require the `X-Bridge-Token` header to match the minted
18
+ token (constant-time comparison). Path confinement is enforced via
19
+ `fs.realpath` on both the root and the target parent directory, blocking
20
+ directory traversal and symlink escape attacks. The token is never serialised
21
+ into the public `/manifest.json` response.
22
+
23
+ The `DesignConnectManifest` capabilities array now marks `readFile`,
24
+ `applyEdit`, and `writeFile` as `"available"` (previously `"planned"`).
25
+
26
+ ### Patch Changes
27
+
28
+ - 3164729: Builder waitlist submissions now include a use-case field so Forms and Slack routing can distinguish background coding requests from Design publish requests.
29
+ - 3164729: Fix bridge token registration so design localhost write-back works end-to-end.
30
+
31
+ `registerConnectionWithServer` is a new exported function that POSTs the
32
+ bridge's real `bridgeToken` (minted by `startDesignConnectBridge`) to the
33
+ design app's `connect-localhost` action endpoint on startup. This stores the
34
+ token on the `designLocalhostConnections` row so `grant-localhost-write-consent`
35
+ can read it instead of minting an unrelated token, which previously caused every
36
+ bridge write to return 401.
37
+
38
+ `DesignConnectArgs` gains an optional `appUrl` field (populated by the new
39
+ `--app-url <url>` CLI flag or the `AGENT_NATIVE_URL` / `DESIGN_APP_URL` env
40
+ vars) that controls where self-registration is sent. Registration is
41
+ best-effort: if no app URL is configured or the request fails, the bridge
42
+ continues running normally.
43
+
44
+ - 3164729: Register Figma as a Design provider API so Design actions can browse and render Figma library components through scoped `FIGMA_ACCESS_TOKEN` credentials.
45
+ - 3164729: Make the Connect AI setup card adapt to narrow chat sidebars with container queries.
46
+ - 3164729: Keep reserved organization switcher slots stable with a disabled loading placeholder.
47
+ - 3164729: Redirect relative PGlite data directories to writable `/tmp` paths on serverless runtimes.
48
+ - 3164729: Make the default shared share button outline trigger transparent at rest.
49
+
3
50
  ## 0.80.11
4
51
 
5
52
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.80.11",
3
+ "version": "0.81.0",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -1,3 +1,4 @@
1
+ import crypto from "node:crypto";
1
2
  import fsSync from "node:fs";
2
3
  import fs from "node:fs/promises";
3
4
  import http, {
@@ -35,6 +36,11 @@ export interface DesignConnectArgs {
35
36
  port: number;
36
37
  root: string;
37
38
  routeManifest?: string;
39
+ /** Optional deployed design app URL used to self-register the bridge on
40
+ * startup. When set (or when AGENT_NATIVE_URL / DESIGN_APP_URL env vars
41
+ * are present) the CLI POSTs to `/_agent-native/actions/connect-localhost`
42
+ * with the real bridge token so the server can store it for grant minting. */
43
+ appUrl?: string;
38
44
  json: boolean;
39
45
  once: boolean;
40
46
  dryRun: boolean;
@@ -72,6 +78,10 @@ export interface DesignConnectManifest {
72
78
  export interface DesignConnectBridge {
73
79
  server: Server;
74
80
  manifest: DesignConnectManifest;
81
+ /** Per-rootPath bridge token. Kept in-process only; never serialised into
82
+ * the manifest JSON so it is not exposed over the network via GET /manifest.
83
+ * The server-side grant action reads it from the running bridge instance. */
84
+ bridgeToken: string;
75
85
  }
76
86
 
77
87
  function stringFlagValue(argv: string[], index: number, flag: string) {
@@ -132,6 +142,11 @@ export function parseDesignConnectArgs(argv: string[]): DesignConnectArgs {
132
142
  index += 1;
133
143
  } else if (arg.startsWith("--route-manifest=")) {
134
144
  parsed.routeManifest = arg.slice("--route-manifest=".length);
145
+ } else if (arg === "--app-url") {
146
+ parsed.appUrl = stringFlagValue(args, index, arg);
147
+ index += 1;
148
+ } else if (arg.startsWith("--app-url=")) {
149
+ parsed.appUrl = arg.slice("--app-url=".length);
135
150
  } else if (arg === "--json") {
136
151
  parsed.json = true;
137
152
  parsed.once = true;
@@ -358,23 +373,16 @@ export async function prepareDesignConnectManifest(
358
373
  generatedAt,
359
374
  capabilities: BRIDGE_OPERATIONS.map((operation) => ({
360
375
  operation,
361
- status:
362
- operation === "readFile" ||
363
- operation === "applyEdit" ||
364
- operation === "writeFile"
365
- ? "planned"
366
- : "available",
376
+ status: "available" as const,
367
377
  reason:
368
- operation === "writeFile"
369
- ? "The bridge advertises the contract before enabling local file writes."
370
- : operation === "resolveNodeToFile"
371
- ? // resolveNodeToFile maps a runtime DOM node id (from the editor's
372
- // 'select' payload) to { file, line, component } provenance. The
373
- // bridge endpoint exists; per-element provenance data must be
374
- // emitted by the connected app at build time see the provenance
375
- // note in the help text below.
376
- "Requires the connected app to emit data-source-file / data-source-line / data-component-name attributes (e.g. via @vitejs/plugin-react jsxDEV or a Babel source plugin)."
377
- : undefined,
378
+ operation === "resolveNodeToFile"
379
+ ? // resolveNodeToFile maps a runtime DOM node id (from the editor's
380
+ // 'select' payload) to { file, line, component } provenance. The
381
+ // bridge endpoint exists; per-element provenance data must be
382
+ // emitted by the connected app at build time — see the provenance
383
+ // note in the help text below.
384
+ "Requires the connected app to emit data-source-file / data-source-line / data-component-name attributes (e.g. via @vitejs/plugin-react jsxDEV or a Babel source plugin)."
385
+ : undefined,
378
386
  })),
379
387
  };
380
388
  }
@@ -387,23 +395,106 @@ function sendJson(
387
395
  res.writeHead(statusCode, {
388
396
  "content-type": "application/json; charset=utf-8",
389
397
  "access-control-allow-origin": "*",
390
- "access-control-allow-methods": "GET, OPTIONS",
391
- "access-control-allow-headers": "content-type",
398
+ "access-control-allow-methods": "GET, POST, OPTIONS",
399
+ "access-control-allow-headers": "content-type, x-bridge-token",
392
400
  "access-control-allow-private-network": "true",
393
401
  });
394
402
  res.end(`${JSON.stringify(body, null, 2)}\n`);
395
403
  }
396
404
 
405
+ /** Read the full request body as a UTF-8 string. */
406
+ async function readRequestBody(req: IncomingMessage): Promise<string> {
407
+ return new Promise<string>((resolve, reject) => {
408
+ const chunks: Buffer[] = [];
409
+ req.on("data", (chunk: Buffer) => chunks.push(chunk));
410
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
411
+ req.on("error", reject);
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Resolve `targetDir` under `rootPath` with realpath so that symlinks and
417
+ * traversal sequences (../../etc) cannot escape the root. Throws if the
418
+ * resolved path does not start with the resolved root.
419
+ */
420
+ async function assertPathInside(
421
+ rootPath: string,
422
+ targetPath: string,
423
+ ): Promise<void> {
424
+ const resolvedRoot = await fs.realpath(rootPath).catch(() => {
425
+ throw new Error(`Bridge root path does not exist: ${rootPath}`);
426
+ });
427
+
428
+ // Resolve the parent directory (the file itself may not exist yet for writes).
429
+ const targetParent = path.dirname(path.resolve(rootPath, targetPath));
430
+ const resolvedParent = await fs.realpath(targetParent).catch(async () => {
431
+ // Parent may not exist yet; walk up until we find a real ancestor.
432
+ let candidate = targetParent;
433
+ for (let i = 0; i < 32; i++) {
434
+ const up = path.dirname(candidate);
435
+ if (up === candidate) break;
436
+ candidate = up;
437
+ try {
438
+ return await fs.realpath(candidate);
439
+ } catch {
440
+ // keep walking
441
+ }
442
+ }
443
+ throw new Error(`Cannot resolve parent directory: ${targetParent}`);
444
+ });
445
+
446
+ if (
447
+ !resolvedParent.startsWith(resolvedRoot + path.sep) &&
448
+ resolvedParent !== resolvedRoot
449
+ ) {
450
+ throw new Error(`Path traversal detected: resolved target is outside root`);
451
+ }
452
+ }
453
+
454
+ /** Allowed file extensions for write/apply-edit operations. */
455
+ const ALLOWED_WRITE_EXTENSIONS = new Set([".html", ".htm", ".css"]);
456
+
457
+ function assertAllowedExtension(relPath: string): void {
458
+ const ext = path.extname(relPath).toLowerCase();
459
+ if (!ALLOWED_WRITE_EXTENSIONS.has(ext)) {
460
+ throw new Error(
461
+ `Write rejected: only .html, .htm, and .css files may be written via the bridge (got ${ext || "(no extension)"})`,
462
+ );
463
+ }
464
+ }
465
+
466
+ function countOccurrences(haystack: string, needle: string): number {
467
+ if (!needle) return 0;
468
+ let count = 0;
469
+ let index = haystack.indexOf(needle);
470
+ while (index !== -1) {
471
+ count += 1;
472
+ if (count > 1) return count;
473
+ index = haystack.indexOf(needle, index + 1);
474
+ }
475
+ return count;
476
+ }
477
+
397
478
  export async function startDesignConnectBridge(
398
479
  manifest: DesignConnectManifest,
399
480
  ): Promise<DesignConnectBridge> {
481
+ // Mint a cryptographically random per-rootPath bridge token. This token is
482
+ // kept in-process only and is never emitted via the public GET routes so that
483
+ // an unauthenticated caller cannot read it. The server-side grant action
484
+ // obtains it out-of-band (via the exported bridge reference).
485
+ const bridgeToken = crypto.randomBytes(32).toString("hex");
486
+
400
487
  const server = http.createServer(
401
488
  (req: IncomingMessage, res: ServerResponse) => {
402
489
  if (req.method === "OPTIONS") {
403
490
  sendJson(res, 204, {});
404
491
  return;
405
492
  }
493
+
406
494
  const pathname = new URL(req.url ?? "/", manifest.bridgeUrl).pathname;
495
+
496
+ // ── Public read-only routes (no token required) ──────────────────────
497
+
407
498
  if (pathname === "/" || pathname === "/manifest.json") {
408
499
  sendJson(res, 200, manifest as unknown as Record<string, unknown>);
409
500
  return;
@@ -423,6 +514,192 @@ export async function startDesignConnectBridge(
423
514
  sendJson(res, 200, { ok: true, source: manifest.source });
424
515
  return;
425
516
  }
517
+
518
+ // ── Token-gated write endpoints (POST only) ───────────────────────────
519
+
520
+ if (
521
+ pathname === "/read-file" ||
522
+ pathname === "/write-file" ||
523
+ pathname === "/apply-edit"
524
+ ) {
525
+ if (req.method !== "POST") {
526
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
527
+ return;
528
+ }
529
+
530
+ // Authenticate with constant-time comparison to prevent timing attacks.
531
+ const tokenHeader = req.headers["x-bridge-token"];
532
+ const providedToken =
533
+ typeof tokenHeader === "string" ? tokenHeader : "";
534
+ let tokenValid = false;
535
+ try {
536
+ tokenValid =
537
+ providedToken.length === bridgeToken.length &&
538
+ crypto.timingSafeEqual(
539
+ Buffer.from(providedToken, "utf8"),
540
+ Buffer.from(bridgeToken, "utf8"),
541
+ );
542
+ } catch {
543
+ tokenValid = false;
544
+ }
545
+ if (!tokenValid) {
546
+ sendJson(res, 401, {
547
+ ok: false,
548
+ error: "invalid or missing bridge token",
549
+ });
550
+ return;
551
+ }
552
+
553
+ // Handle asynchronously so we can use await.
554
+ void (async () => {
555
+ try {
556
+ const raw = await readRequestBody(req);
557
+ const body = JSON.parse(raw) as Record<string, unknown>;
558
+ const relPath =
559
+ typeof body["relPath"] === "string" ? body["relPath"] : undefined;
560
+
561
+ if (!relPath) {
562
+ sendJson(res, 400, { ok: false, error: "relPath is required" });
563
+ return;
564
+ }
565
+
566
+ await assertPathInside(manifest.rootPath, relPath);
567
+ const absolutePath = path.resolve(manifest.rootPath, relPath);
568
+
569
+ if (pathname === "/read-file") {
570
+ // Read-file: no extension restriction — agents need to read any file.
571
+ let content: string;
572
+ try {
573
+ content = await fs.readFile(absolutePath, "utf8");
574
+ } catch (err: unknown) {
575
+ const code =
576
+ err instanceof Error &&
577
+ "code" in err &&
578
+ (err as NodeJS.ErrnoException).code;
579
+ if (code === "ENOENT") {
580
+ sendJson(res, 404, { ok: false, error: "file not found" });
581
+ } else {
582
+ sendJson(res, 500, {
583
+ ok: false,
584
+ error: `read failed: ${err instanceof Error ? err.message : String(err)}`,
585
+ });
586
+ }
587
+ return;
588
+ }
589
+ sendJson(res, 200, { ok: true, content });
590
+ return;
591
+ }
592
+
593
+ // write-file and apply-edit only allow .html/.htm/.css.
594
+ assertAllowedExtension(relPath);
595
+
596
+ if (pathname === "/write-file") {
597
+ const content =
598
+ typeof body["content"] === "string"
599
+ ? body["content"]
600
+ : undefined;
601
+ if (content === undefined) {
602
+ sendJson(res, 400, {
603
+ ok: false,
604
+ error: "content is required for write-file",
605
+ });
606
+ return;
607
+ }
608
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true });
609
+ await fs.writeFile(absolutePath, content, "utf8");
610
+ sendJson(res, 200, { ok: true, relPath });
611
+ return;
612
+ }
613
+
614
+ // /apply-edit: supports either full replace ({content}) or
615
+ // search-and-replace ({search, replace}).
616
+ if (typeof body["content"] === "string") {
617
+ // Full-file replace via apply-edit — same as write-file but keeps
618
+ // the endpoint semantically separate for callers that want to
619
+ // distinguish intent.
620
+ await fs.mkdir(path.dirname(absolutePath), { recursive: true });
621
+ await fs.writeFile(
622
+ absolutePath,
623
+ body["content"] as string,
624
+ "utf8",
625
+ );
626
+ sendJson(res, 200, { ok: true, relPath, method: "replace" });
627
+ return;
628
+ }
629
+
630
+ const search =
631
+ typeof body["search"] === "string" ? body["search"] : undefined;
632
+ const replace =
633
+ typeof body["replace"] === "string" ? body["replace"] : undefined;
634
+ if (search === undefined || replace === undefined) {
635
+ sendJson(res, 400, {
636
+ ok: false,
637
+ error:
638
+ "apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
639
+ });
640
+ return;
641
+ }
642
+
643
+ let existing: string;
644
+ try {
645
+ existing = await fs.readFile(absolutePath, "utf8");
646
+ } catch (err: unknown) {
647
+ const code =
648
+ err instanceof Error &&
649
+ "code" in err &&
650
+ (err as NodeJS.ErrnoException).code;
651
+ if (code === "ENOENT") {
652
+ sendJson(res, 404, {
653
+ ok: false,
654
+ error: "file not found — use write-file to create new files",
655
+ });
656
+ } else {
657
+ sendJson(res, 500, {
658
+ ok: false,
659
+ error: `read failed: ${err instanceof Error ? err.message : String(err)}`,
660
+ });
661
+ }
662
+ return;
663
+ }
664
+
665
+ if (search.length === 0) {
666
+ sendJson(res, 400, {
667
+ ok: false,
668
+ error: "search string must not be empty",
669
+ });
670
+ return;
671
+ }
672
+
673
+ const occurrenceCount = countOccurrences(existing, search);
674
+ if (occurrenceCount === 0) {
675
+ sendJson(res, 422, {
676
+ ok: false,
677
+ error: "search string not found in file",
678
+ });
679
+ return;
680
+ }
681
+ if (occurrenceCount > 1) {
682
+ sendJson(res, 422, {
683
+ ok: false,
684
+ error:
685
+ "search string is ambiguous; it appears more than once in the file",
686
+ });
687
+ return;
688
+ }
689
+
690
+ const updated = existing.replace(search, replace);
691
+ await fs.writeFile(absolutePath, updated, "utf8");
692
+ sendJson(res, 200, { ok: true, relPath, method: "patch" });
693
+ } catch (err: unknown) {
694
+ sendJson(res, 500, {
695
+ ok: false,
696
+ error: err instanceof Error ? err.message : String(err),
697
+ });
698
+ }
699
+ })();
700
+ return;
701
+ }
702
+
426
703
  sendJson(res, 404, { ok: false, error: "not found" });
427
704
  },
428
705
  );
@@ -435,7 +712,117 @@ export async function startDesignConnectBridge(
435
712
  });
436
713
  });
437
714
 
438
- return { server, manifest };
715
+ return { server, manifest, bridgeToken };
716
+ }
717
+
718
+ /**
719
+ * Resolve the design app URL from an explicit value or environment variables.
720
+ * Returns undefined when no URL is configured (registration is optional).
721
+ */
722
+ export function resolveAppUrl(explicit?: string): string | undefined {
723
+ const raw =
724
+ explicit ||
725
+ process.env["AGENT_NATIVE_URL"] ||
726
+ process.env["DESIGN_APP_URL"] ||
727
+ process.env["APP_URL"] ||
728
+ process.env["VITE_APP_URL"] ||
729
+ process.env["BETTER_AUTH_URL"] ||
730
+ process.env["VITE_BETTER_AUTH_URL"];
731
+ if (!raw) return undefined;
732
+ try {
733
+ const parsed = new URL(raw.trim());
734
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
735
+ return undefined;
736
+ }
737
+ return parsed.toString().replace(/\/+$/, "");
738
+ } catch {
739
+ return undefined;
740
+ }
741
+ }
742
+
743
+ /**
744
+ * Resolve a bearer token from environment variables for authenticating the
745
+ * self-registration POST. The CLI does not perform a device-code flow — it
746
+ * relies on a pre-minted token supplied via env var (e.g. the same token
747
+ * already written into the agent's MCP config).
748
+ */
749
+ function resolveAuthToken(): string | undefined {
750
+ return (
751
+ process.env["AGENT_NATIVE_TOKEN"] ||
752
+ process.env["DESIGN_ACCESS_TOKEN"] ||
753
+ process.env["ACCESS_TOKEN"] ||
754
+ undefined
755
+ );
756
+ }
757
+
758
+ /**
759
+ * POST to the design app's `connect-localhost` action endpoint to register
760
+ * (or refresh) the bridge connection and persist the real bridge token on the
761
+ * server row. This is a best-effort call: failures are logged but do not
762
+ * abort the bridge process.
763
+ *
764
+ * @param appUrl - Deployed design app base URL (e.g. https://design.agent-native.com)
765
+ * @param bridge - The running bridge returned by startDesignConnectBridge
766
+ * @param authToken - Optional bearer token for the authenticated action route
767
+ */
768
+ export async function registerConnectionWithServer(
769
+ appUrl: string,
770
+ bridge: DesignConnectBridge,
771
+ authToken?: string,
772
+ ): Promise<void> {
773
+ const endpoint = `${appUrl}/_agent-native/actions/connect-localhost`;
774
+ const { manifest, bridgeToken } = bridge;
775
+ const payload = {
776
+ devServerUrl: manifest.devServerUrl,
777
+ bridgeUrl: manifest.bridgeUrl,
778
+ rootPath: manifest.rootPath,
779
+ capabilities: manifest.capabilities,
780
+ routeManifest: {
781
+ version: 1 as const,
782
+ sourceType: "localhost" as const,
783
+ devServerUrl: manifest.devServerUrl,
784
+ rootPath: manifest.rootPath,
785
+ routes: manifest.routes,
786
+ generatedAt: manifest.generatedAt,
787
+ },
788
+ // Include the real bridge token so the server stores it on the connection
789
+ // row. grant-localhost-write-consent then reads it from the row instead of
790
+ // minting its own unrelated token, which would always produce a 401.
791
+ bridgeToken,
792
+ status: "connected" as const,
793
+ };
794
+
795
+ const headers: Record<string, string> = {
796
+ "content-type": "application/json",
797
+ };
798
+ if (authToken) {
799
+ headers["authorization"] = `Bearer ${authToken}`;
800
+ }
801
+
802
+ const controller = new AbortController();
803
+ const timeout = setTimeout(() => controller.abort(), 15_000);
804
+ try {
805
+ const res = await fetch(endpoint, {
806
+ method: "POST",
807
+ headers,
808
+ body: JSON.stringify(payload),
809
+ signal: controller.signal,
810
+ });
811
+ if (!res.ok) {
812
+ const message = await res.text().catch(() => res.statusText);
813
+ throw new Error(`${res.status} ${message || res.statusText}`);
814
+ }
815
+ } catch (error) {
816
+ // Best-effort: network errors or auth issues are non-fatal, but make the
817
+ // problem discoverable before the user tries "Apply to source".
818
+ console.error(
819
+ `[design connect] Could not register bridge with ${endpoint}: ${
820
+ error instanceof Error ? error.message : String(error)
821
+ }`,
822
+ );
823
+ } finally {
824
+ clearTimeout(timeout);
825
+ }
439
826
  }
440
827
 
441
828
  function printHelp() {
@@ -447,6 +834,8 @@ Options:
447
834
  --port <number> Local bridge port (default ${DEFAULT_BRIDGE_PORT})
448
835
  --root <path> App/repo root for route discovery (default cwd)
449
836
  --route-manifest <path> Non-destructive route manifest output path
837
+ --app-url <url> Deployed design app URL for self-registration
838
+ (also reads AGENT_NATIVE_URL / DESIGN_APP_URL env)
450
839
  --json Print the manifest JSON and exit
451
840
  --once Prepare/scaffold the manifest and exit
452
841
  --dry-run Print what would be exposed without writing files
@@ -505,6 +894,17 @@ export async function runDesign(argv: string[]) {
505
894
  console.error(`Routes: ${manifest.routeCount}`);
506
895
  console.error(`Dev URL: ${manifest.devServerUrl}`);
507
896
 
897
+ // Self-register with the design app server (best-effort). When an app URL
898
+ // is available (via --app-url or env var) the CLI POSTs the bridge token to
899
+ // connect-localhost so the server row stores the real token. Without this
900
+ // step grant-localhost-write-consent would mint a different token, causing
901
+ // every bridge write to return 401.
902
+ const appUrl = resolveAppUrl(parsed.appUrl);
903
+ if (appUrl) {
904
+ const authToken = resolveAuthToken();
905
+ void registerConnectionWithServer(appUrl, bridge, authToken);
906
+ }
907
+
508
908
  return await new Promise<number>((resolve) => {
509
909
  const stop = () => {
510
910
  bridge.server.close(() => resolve(0));
@@ -154,6 +154,7 @@ export function ConnectBuilderCard({
154
154
  orgName,
155
155
  pageUrl: window.location.href,
156
156
  source: "connect_builder_card",
157
+ useCase: "builder_agent_background_coding",
157
158
  }),
158
159
  },
159
160
  );
@@ -16,6 +16,7 @@ export {
16
16
  AGENT_CHAT_VIEW_TRANSITION_NAME,
17
17
  consumeAgentChatHomeHandoff,
18
18
  getAgentChatViewTransitionStyle,
19
+ isAgentChatHomeHandoffActive,
19
20
  markAgentChatHomeHandoff,
20
21
  navigateWithAgentChatViewTransition,
21
22
  startAgentChatViewTransition,
@@ -195,7 +195,7 @@ export function BuilderConnectCta({
195
195
  if (variant === "compact") {
196
196
  if (configured) {
197
197
  return (
198
- <span className="inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-md border border-border bg-background px-2.5 text-[11px] font-medium text-foreground">
198
+ <span className="agent-builder-setup-card__builder-button inline-flex h-8 items-center gap-1.5 whitespace-nowrap rounded-md border border-border bg-background px-2.5 text-[11px] font-medium text-foreground">
199
199
  <IconCheck size={11} className="text-emerald-500" />
200
200
  {orgName ? `Connected to ${orgName}` : "Connected"}
201
201
  </span>
@@ -203,12 +203,12 @@ export function BuilderConnectCta({
203
203
  }
204
204
 
205
205
  return (
206
- <div className="flex min-w-0 flex-col items-start gap-1 sm:items-end">
206
+ <div className="agent-builder-setup-card__builder-cta flex min-w-0 flex-col items-start gap-1 sm:items-end">
207
207
  <button
208
208
  type="button"
209
209
  onClick={() => start()}
210
210
  disabled={connecting}
211
- className="inline-flex h-8 shrink-0 items-center gap-1 whitespace-nowrap rounded-md bg-foreground px-3 text-[11px] font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-60"
211
+ className="agent-builder-setup-card__builder-button inline-flex h-8 shrink-0 items-center gap-1 whitespace-nowrap rounded-md bg-foreground px-3 text-[11px] font-medium text-background hover:opacity-90 disabled:cursor-wait disabled:opacity-60"
212
212
  aria-busy={connecting}
213
213
  >
214
214
  {connecting ? (
@@ -433,6 +433,8 @@ export function BuilderSetupCard({
433
433
  <div
434
434
  ref={cardRef}
435
435
  className={cn(
436
+ "agent-builder-setup-card",
437
+ sidebarLayout && "agent-builder-setup-card--sidebar",
436
438
  fullWidth
437
439
  ? "w-full pb-2"
438
440
  : sidebarLayout
@@ -441,8 +443,8 @@ export function BuilderSetupCard({
441
443
  )}
442
444
  >
443
445
  <div className="rounded-lg border border-border/80 bg-background/80 p-3 shadow-sm backdrop-blur">
444
- <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
445
- <div className="min-w-0">
446
+ <div className="agent-builder-setup-card__content flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
447
+ <div className="agent-builder-setup-card__copy min-w-0">
446
448
  <h3 className="text-[13px] font-medium text-foreground">
447
449
  Connect AI
448
450
  </h3>
@@ -452,7 +454,7 @@ export function BuilderSetupCard({
452
454
  </div>
453
455
  <div
454
456
  className={cn(
455
- "flex shrink-0",
457
+ "agent-builder-setup-card__actions flex shrink-0",
456
458
  sidebarLayout
457
459
  ? "flex-col items-start gap-0 sm:items-center"
458
460
  : "flex-nowrap items-center gap-2",
@@ -463,7 +465,7 @@ export function BuilderSetupCard({
463
465
  type="button"
464
466
  onClick={() => setKeyOpen((open) => !open)}
465
467
  className={cn(
466
- "inline-flex shrink-0 items-center whitespace-nowrap rounded-md text-[11px] font-medium",
468
+ "agent-builder-setup-card__key-button inline-flex shrink-0 items-center whitespace-nowrap rounded-md text-[11px] font-medium",
467
469
  sidebarLayout
468
470
  ? "h-7 border-0 bg-transparent px-0 text-muted-foreground hover:bg-transparent hover:text-foreground"
469
471
  : "h-8 border border-border bg-background px-3 text-foreground hover:bg-accent",