@agent-native/core 0.129.2 → 0.130.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 (589) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +72 -0
  3. package/corpus/core/docs/content/cloneable-saas.mdx +114 -0
  4. package/corpus/core/docs/content/creating-templates.mdx +28 -2
  5. package/corpus/core/docs/content/syncing-template-changes.mdx +7 -0
  6. package/corpus/core/docs/content/template-clips-developers.mdx +12 -5
  7. package/corpus/core/docs/content/template-dispatch-developers.mdx +1 -1
  8. package/corpus/core/docs/content/template-dispatch.mdx +1 -1
  9. package/corpus/core/package.json +1 -1
  10. package/corpus/core/src/action-ui.ts +1 -0
  11. package/corpus/core/src/action.ts +5 -1
  12. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +15 -5
  13. package/corpus/core/src/agent/harness/runner.ts +5 -0
  14. package/corpus/core/src/agent/production-agent.ts +33 -0
  15. package/corpus/core/src/agent/run-loop-with-resume.ts +5 -0
  16. package/corpus/core/src/agent/run-manager.ts +126 -1
  17. package/corpus/core/src/agent/thread-data-builder.ts +47 -2
  18. package/corpus/core/src/agent/types.ts +2 -0
  19. package/corpus/core/src/chat-threads/store.ts +4 -5
  20. package/corpus/core/src/cli/create.ts +1139 -65
  21. package/corpus/core/src/cli/design-connect.ts +3 -7
  22. package/corpus/core/src/cli/index.ts +5 -3
  23. package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +83 -47
  24. package/corpus/core/src/client/AssistantChat.tsx +27 -16
  25. package/corpus/core/src/client/agent-chat-adapter.ts +24 -0
  26. package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +17 -2
  27. package/corpus/core/src/client/chat/index.ts +1 -0
  28. package/corpus/core/src/client/chat/message-components.tsx +104 -1
  29. package/corpus/core/src/client/chat/run-recovery.tsx +15 -3
  30. package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +10 -29
  31. package/corpus/core/src/client/chat/widgets/WorkspaceFileWidget.tsx +71 -0
  32. package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +22 -0
  33. package/corpus/core/src/client/chat/widgets/workspace-file-result.ts +49 -0
  34. package/corpus/core/src/client/embed-auth.ts +2 -0
  35. package/corpus/core/src/client/org/InvitationBanner.tsx +14 -1
  36. package/corpus/core/src/client/org/OrgSwitcher.tsx +23 -0
  37. package/corpus/core/src/client/org/TeamPage.tsx +130 -0
  38. package/corpus/core/src/client/org/WorkspaceNotice.tsx +85 -0
  39. package/corpus/core/src/client/org/hooks.ts +14 -0
  40. package/corpus/core/src/client/org/index.ts +2 -0
  41. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +27 -40
  42. package/corpus/core/src/client/resources/ResourcesPanel.tsx +134 -51
  43. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +7 -0
  44. package/corpus/core/src/client/resources/use-resources.ts +6 -0
  45. package/corpus/core/src/client/use-action.ts +3 -1
  46. package/corpus/core/src/client/widgets/index.ts +1 -0
  47. package/corpus/core/src/coding-tools/run-code.ts +2 -1
  48. package/corpus/core/src/db/client.ts +28 -4
  49. package/corpus/core/src/deploy/build.ts +31 -3
  50. package/corpus/core/src/eject/remote-mcp-presets.ts +1 -1
  51. package/corpus/core/src/index.browser.ts +1 -0
  52. package/corpus/core/src/index.ts +1 -0
  53. package/corpus/core/src/integrations/google-docs-poller.ts +3 -0
  54. package/corpus/core/src/integrations/webhook-handler.ts +28 -11
  55. package/corpus/core/src/jobs/scheduler.ts +4 -0
  56. package/corpus/core/src/localization/default-messages.ts +3 -2
  57. package/corpus/core/src/observability/traces.ts +48 -12
  58. package/corpus/core/src/org/handlers.ts +52 -1
  59. package/corpus/core/src/org/migrations.ts +4 -0
  60. package/corpus/core/src/org/plugin.ts +14 -0
  61. package/corpus/core/src/org/schema.ts +1 -0
  62. package/corpus/core/src/org/types.ts +8 -0
  63. package/corpus/core/src/org/workspace-url.ts +74 -0
  64. package/corpus/core/src/resources/handlers.ts +35 -5
  65. package/corpus/core/src/scripts/runner.ts +183 -5
  66. package/corpus/core/src/server/action-routes.ts +32 -2
  67. package/corpus/core/src/server/agent-chat-plugin.ts +29 -0
  68. package/corpus/core/src/server/agent-teams.ts +10 -1
  69. package/corpus/core/src/server/auth.ts +33 -7
  70. package/corpus/core/src/server/embed-route.ts +9 -0
  71. package/corpus/core/src/server/embed-session.ts +62 -3
  72. package/corpus/core/src/server/open-route.ts +20 -6
  73. package/corpus/core/src/server/prompts/framework-core-compact.ts +1 -1
  74. package/corpus/core/src/server/prompts/framework-core.ts +1 -0
  75. package/corpus/core/src/server/request-context.ts +36 -1
  76. package/corpus/core/src/shared/embed-auth.ts +1 -0
  77. package/corpus/core/src/shared/html-document.ts +115 -0
  78. package/corpus/core/src/shared/index.ts +1 -0
  79. package/corpus/core/src/sharing/access.ts +8 -1
  80. package/corpus/core/src/sharing/registry.ts +11 -2
  81. package/corpus/core/src/workspace-files/actions.ts +100 -0
  82. package/corpus/core/src/workspace-files/tool.ts +4 -0
  83. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +4 -0
  84. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +9 -1
  85. package/corpus/templates/analytics/AGENTS.md +17 -24
  86. package/corpus/templates/analytics/actions/data-source-status.ts +11 -1
  87. package/corpus/templates/analytics/app/i18n-data.ts +34 -0
  88. package/corpus/templates/analytics/app/lib/data-source-status.ts +86 -0
  89. package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +9 -0
  90. package/corpus/templates/analytics/app/pages/DataSources.tsx +184 -20
  91. package/corpus/templates/analytics/changelog/2026-07-28-native-analytics-data-sources-no-longer-show-duplicate-mc.md +6 -0
  92. package/corpus/templates/analytics/changelog/2026-07-29-exports-now-appear-in-chat-with-a-direct-download-instead-of.md +6 -0
  93. package/corpus/templates/analytics/changelog/2026-07-29-queued-chat-prompts-no-longer-resend-after-analytics-navigat.md +6 -0
  94. package/corpus/templates/analytics/changelog/2026-07-29-scheduled-dashboard-emails-load-every-panel-reliably-without.md +6 -0
  95. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +6 -3
  96. package/corpus/templates/analytics/server/lib/credential-keys.ts +1 -1
  97. package/corpus/templates/analytics/server/lib/hubspot.ts +1 -1
  98. package/corpus/templates/analytics/server/lib/provider-credentials.ts +55 -1
  99. package/corpus/templates/analytics/server/plugins/agent-chat.ts +123 -27
  100. package/corpus/templates/analytics/server/routes/[...page].get.ts +2 -7
  101. package/corpus/templates/analytics/vite.config.ts +7 -0
  102. package/corpus/templates/brain/actions/list-connection-providers.ts +100 -2
  103. package/corpus/templates/brain/app/lib/brain.ts +10 -0
  104. package/corpus/templates/brain/app/routes/sources.tsx +15 -4
  105. package/corpus/templates/brain/changelog/2026-07-29-brain-automatically-retries-failed-source-syncs-and-guides-y.md +6 -0
  106. package/corpus/templates/brain/jobs/process-ingest-queue.ts +13 -0
  107. package/corpus/templates/brain/server/jobs/sync-sources.ts +27 -22
  108. package/corpus/templates/brain/server/plugins/agent-chat.ts +2 -1
  109. package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +7 -1
  110. package/corpus/templates/clips/AGENTS.md +1 -1
  111. package/corpus/templates/clips/actions/create-recording-agent-link.ts +11 -1
  112. package/corpus/templates/clips/actions/update-comment.ts +68 -0
  113. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +0 -1
  114. package/corpus/templates/clips/app/components/player/comments-panel.tsx +221 -65
  115. package/corpus/templates/clips/app/components/player/video-player.tsx +101 -27
  116. package/corpus/templates/clips/app/i18n/en-US.ts +2 -1
  117. package/corpus/templates/clips/app/routes/bug-report.done.tsx +48 -27
  118. package/corpus/templates/clips/app/routes/bug-report.tsx +43 -4
  119. package/corpus/templates/clips/app/routes/record.tsx +2 -0
  120. package/corpus/templates/clips/changelog/2026-07-29-clip-playback-now-recovers-instead-of-getting-stuck-when-a-b.md +6 -0
  121. package/corpus/templates/clips/changelog/2026-07-29-comments-can-now-be-edited-after-posting.md +6 -0
  122. package/corpus/templates/clips/changelog/2026-07-29-download-actions-now-use-a-shorter-clearer-label.md +6 -0
  123. package/corpus/templates/clips/changelog/2026-07-29-embedded-bug-reports-now-return-a-temporary-agent-readable.md +6 -0
  124. package/corpus/templates/clips/shared/bug-report.ts +99 -0
  125. package/corpus/templates/content/actions/_content-database-personal-view.ts +7 -1
  126. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +5 -2
  127. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +20 -8
  128. package/corpus/templates/content/changelog/2026-07-29-missing-databases-no-longer-trigger-repeated-personal-view-e.md +6 -0
  129. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +83 -47
  130. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +474 -55
  131. package/corpus/templates/design/.generated/bridge/embedded-wheel.generated.ts +3 -0
  132. package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +18 -1
  133. package/corpus/templates/design/.generated/bridge/source-location.generated.ts +348 -0
  134. package/corpus/templates/design/actions/add-localhost-screens.ts +2 -7
  135. package/corpus/templates/design/actions/apply-visual-edit.ts +7 -1
  136. package/corpus/templates/design/actions/connect-localhost.ts +5 -9
  137. package/corpus/templates/design/actions/create-component.ts +16 -13
  138. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +2 -7
  139. package/corpus/templates/design/actions/list-design-source-capabilities.ts +4 -7
  140. package/corpus/templates/design/actions/list-local-files.ts +26 -50
  141. package/corpus/templates/design/actions/list-localhost-connections.ts +2 -7
  142. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +2 -2
  143. package/corpus/templates/design/actions/open-visual-edit.ts +234 -135
  144. package/corpus/templates/design/actions/read-local-file.ts +26 -50
  145. package/corpus/templates/design/actions/request-localhost-write-consent.ts +2 -7
  146. package/corpus/templates/design/actions/write-local-file.ts +31 -73
  147. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +307 -133
  148. package/corpus/templates/design/app/components/design/EditPanel.tsx +94 -22
  149. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +137 -60
  150. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +75 -14
  151. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +755 -97
  152. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +8 -0
  153. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +24 -1
  154. package/corpus/templates/design/app/components/design/bridge/source-location.bridge.ts +530 -0
  155. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbenchLoader.tsx +278 -0
  156. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +32 -12
  157. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-setup.ts +14 -3
  158. package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +87 -24
  159. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +36 -0
  160. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +2 -1
  161. package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +48 -8
  162. package/corpus/templates/design/app/components/design/design-canvas/content-size-report.ts +3 -13
  163. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +4 -2
  164. package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +3 -13
  165. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +3 -1
  166. package/corpus/templates/design/app/components/design/edit-panel/inspect-code-source.ts +93 -0
  167. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +4 -2
  168. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +35 -0
  169. package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +24 -0
  170. package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -0
  171. package/corpus/templates/design/app/components/design/types.ts +29 -0
  172. package/corpus/templates/design/app/components/layout/Layout.tsx +7 -4
  173. package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -0
  174. package/corpus/templates/design/app/i18n-data.ts +153 -46
  175. package/corpus/templates/design/app/i18n-responsive-interact.ts +12 -12
  176. package/corpus/templates/design/app/lib/design-editor-route.ts +23 -0
  177. package/corpus/templates/design/app/pages/VisualEdit.tsx +19 -6
  178. package/corpus/templates/design/app/pages/design-editor/action-error.ts +19 -0
  179. package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +29 -0
  180. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +6 -0
  181. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +277 -15
  182. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +75 -9
  183. package/corpus/templates/design/app/pages/design-editor/layer-state-scope.ts +28 -0
  184. package/corpus/templates/design/app/pages/design-editor/live-screen-layer-drop.ts +41 -0
  185. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +496 -41
  186. package/corpus/templates/design/app/pages/design-editor/pending-live-edit-unload-guard.ts +27 -0
  187. package/corpus/templates/design/app/pages/design-editor/pending-structure-verification.ts +10 -0
  188. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +168 -9
  189. package/corpus/templates/design/app/pages/design-editor/responsive-interact.ts +41 -3
  190. package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +5 -0
  191. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +42 -13
  192. package/corpus/templates/design/app/pages/design-editor/source-location.ts +180 -0
  193. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +17 -0
  194. package/corpus/templates/design/app/public-routes.ts +1 -0
  195. package/corpus/templates/design/app/routes/visual-edit.$id.tsx +15 -0
  196. package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +1 -1
  197. package/corpus/templates/design/changelog/2026-07-28-deleting-a-selected-element-now-removes-it-from-the-live-pre.md +6 -0
  198. package/corpus/templates/design/changelog/2026-07-28-deleting-an-element-on-a-connected-localhost-screen-now-queu.md +6 -0
  199. package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-shows-a-responsive-device-preview-with-pre.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-28-layer-hide-and-lock-now-apply-to-the-live-preview-instead-of.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-28-layers-panel-selections-on-a-connected-localhost-screen-can-.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-28-locked-layers-now-show-a-subtle-dashed-outline-in-the-live-p.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-28-the-apply-design-updates-button-now-counts-pending-live-text.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-28-the-design-canvas-no-longer-stalls-on-a-render-loop-while-a-.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-28-visual-edit-on-a-local-dev-server-no-longer-requires-signing.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-28-visual-edits-on-a-react-19-app-no-longer-report-a-source-lin.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-29-an-edit-that-cannot-be-written-to-source-now-names-the-reaso.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-29-applying-a-primitive-dropped-onto-a-connected-localhost-scre.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-29-changing-a-style-in-the-inspector-now-updates-a-connected-lo.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-29-choosing-edit-or-annotate-from-a-focused-screen-now-returns-.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-29-concurrent-source-edits-now-return-a-clear-conflict-response.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-29-creating-a-component-from-an-element-that-repeats-in-the-sou.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-29-deleting-an-element-you-just-dropped-onto-a-live-screen-no-l.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-29-dragging-a-layer-across-screens-no-longer-dies-when-the-poin.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-29-dragging-an-element-from-a-stored-screen-into-a-live-localho.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-29-drawing-a-box-on-the-canvas-and-dragging-it-into-a-connected.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-29-fixed-a-loading-screen-flicker-when-reopening-designs-with-a.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-29-interact-mode-has-edit-and-annotate-buttons-again-so-you-can.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-29-layer-groups-can-be-dropped-onto-live-localhost-screens.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-29-localhost-source-errors-now-say-what-actually-went-wrong-whi.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-29-opening-or-refreshing-a-live-localhost-screen-in-single-scre.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-29-redo-after-undoing-a-drop-onto-a-live-screen-puts-the-elemen.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-29-shapes-can-be-drawn-directly-onto-live-localhost-screens.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-29-shapes-drawn-on-the-canvas-board-now-appear-immediately-inst.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-29-style-edits-on-a-running-localhost-screen-now-preview-live-a.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-29-the-apply-design-updates-control-now-appears-as-a-clean-conn.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-29-visual-edits-on-a-mapped-component-instance-now-tell-the-age.md +6 -0
  228. package/corpus/templates/design/server/lib/design-data-access.ts +21 -7
  229. package/corpus/templates/design/server/lib/localhost-connection.ts +240 -0
  230. package/corpus/templates/design/server/routes/[...page].get.ts +2 -5
  231. package/corpus/templates/design/server/source-workspace.ts +2 -0
  232. package/corpus/templates/design/shared/code-layer.ts +43 -0
  233. package/corpus/templates/design/shared/html-content.ts +26 -0
  234. package/corpus/templates/design/shared/html-integrity.ts +21 -1
  235. package/corpus/templates/design/shared/local-jsx-visual-edit.ts +15 -0
  236. package/corpus/templates/design/shared/source-mode.ts +76 -0
  237. package/corpus/templates/dispatch/changelog/2026-07-29-workspace-members-without-admin-access-now-see-a-clear-permi.md +6 -0
  238. package/corpus/templates/dispatch/server/lib/usage-metrics.ts +2 -1
  239. package/corpus/templates/plan/app/lib/plan-annotation-runtime.ts +3 -4
  240. package/corpus/templates/plan/server/routes/[...page].get.ts +2 -5
  241. package/corpus/templates/slides/actions/patch-deck.ts +29 -17
  242. package/corpus/toolkit/CHANGELOG.md +6 -0
  243. package/corpus/toolkit/package.json +1 -1
  244. package/corpus/toolkit/src/composer/ComposerPlusMenu.tsx +3 -2
  245. package/dist/action-ui.d.ts +1 -0
  246. package/dist/action-ui.d.ts.map +1 -1
  247. package/dist/action-ui.js +1 -0
  248. package/dist/action-ui.js.map +1 -1
  249. package/dist/action.d.ts +5 -1
  250. package/dist/action.d.ts.map +1 -1
  251. package/dist/action.js.map +1 -1
  252. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  253. package/dist/agent/engine/ai-sdk-engine.js +13 -5
  254. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  255. package/dist/agent/harness/runner.d.ts.map +1 -1
  256. package/dist/agent/harness/runner.js +5 -0
  257. package/dist/agent/harness/runner.js.map +1 -1
  258. package/dist/agent/production-agent.d.ts +16 -0
  259. package/dist/agent/production-agent.d.ts.map +1 -1
  260. package/dist/agent/production-agent.js +18 -1
  261. package/dist/agent/production-agent.js.map +1 -1
  262. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  263. package/dist/agent/run-loop-with-resume.js +6 -0
  264. package/dist/agent/run-loop-with-resume.js.map +1 -1
  265. package/dist/agent/run-manager.d.ts +14 -0
  266. package/dist/agent/run-manager.d.ts.map +1 -1
  267. package/dist/agent/run-manager.js +91 -1
  268. package/dist/agent/run-manager.js.map +1 -1
  269. package/dist/agent/thread-data-builder.d.ts +3 -0
  270. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  271. package/dist/agent/thread-data-builder.js +35 -3
  272. package/dist/agent/thread-data-builder.js.map +1 -1
  273. package/dist/agent/types.d.ts +2 -0
  274. package/dist/agent/types.d.ts.map +1 -1
  275. package/dist/agent/types.js.map +1 -1
  276. package/dist/chat-threads/store.d.ts.map +1 -1
  277. package/dist/chat-threads/store.js +4 -6
  278. package/dist/chat-threads/store.js.map +1 -1
  279. package/dist/cli/create.d.ts +66 -6
  280. package/dist/cli/create.d.ts.map +1 -1
  281. package/dist/cli/create.js +765 -66
  282. package/dist/cli/create.js.map +1 -1
  283. package/dist/cli/design-connect.d.ts.map +1 -1
  284. package/dist/cli/design-connect.js +2 -7
  285. package/dist/cli/design-connect.js.map +1 -1
  286. package/dist/cli/index.js +5 -3
  287. package/dist/cli/index.js.map +1 -1
  288. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  289. package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
  290. package/dist/cli/skills-content/design-visual-edit-skill.js +83 -47
  291. package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
  292. package/dist/client/AssistantChat.d.ts.map +1 -1
  293. package/dist/client/AssistantChat.js +16 -9
  294. package/dist/client/AssistantChat.js.map +1 -1
  295. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  296. package/dist/client/agent-chat-adapter.js +21 -0
  297. package/dist/client/agent-chat-adapter.js.map +1 -1
  298. package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
  299. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
  300. package/dist/client/blocks/library/wireframe-kit.js +8 -2
  301. package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
  302. package/dist/client/chat/index.d.ts +1 -1
  303. package/dist/client/chat/index.d.ts.map +1 -1
  304. package/dist/client/chat/index.js +1 -1
  305. package/dist/client/chat/index.js.map +1 -1
  306. package/dist/client/chat/message-components.d.ts +17 -0
  307. package/dist/client/chat/message-components.d.ts.map +1 -1
  308. package/dist/client/chat/message-components.js +24 -2
  309. package/dist/client/chat/message-components.js.map +1 -1
  310. package/dist/client/chat/run-recovery.d.ts +6 -0
  311. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  312. package/dist/client/chat/run-recovery.js +13 -3
  313. package/dist/client/chat/run-recovery.js.map +1 -1
  314. package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -1
  315. package/dist/client/chat/widgets/DataTableWidget.js +2 -3
  316. package/dist/client/chat/widgets/DataTableWidget.js.map +1 -1
  317. package/dist/client/chat/widgets/WorkspaceFileWidget.d.ts +5 -0
  318. package/dist/client/chat/widgets/WorkspaceFileWidget.d.ts.map +1 -0
  319. package/dist/client/chat/widgets/WorkspaceFileWidget.js +34 -0
  320. package/dist/client/chat/widgets/WorkspaceFileWidget.js.map +1 -0
  321. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  322. package/dist/client/chat/widgets/builtin-tool-renderers.js +13 -1
  323. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  324. package/dist/client/chat/widgets/workspace-file-result.d.ts +12 -0
  325. package/dist/client/chat/widgets/workspace-file-result.d.ts.map +1 -0
  326. package/dist/client/chat/widgets/workspace-file-result.js +34 -0
  327. package/dist/client/chat/widgets/workspace-file-result.js.map +1 -0
  328. package/dist/client/embed-auth.d.ts.map +1 -1
  329. package/dist/client/embed-auth.js +2 -1
  330. package/dist/client/embed-auth.js.map +1 -1
  331. package/dist/client/org/InvitationBanner.d.ts +5 -2
  332. package/dist/client/org/InvitationBanner.d.ts.map +1 -1
  333. package/dist/client/org/InvitationBanner.js +9 -2
  334. package/dist/client/org/InvitationBanner.js.map +1 -1
  335. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  336. package/dist/client/org/OrgSwitcher.js +3 -2
  337. package/dist/client/org/OrgSwitcher.js.map +1 -1
  338. package/dist/client/org/TeamPage.d.ts.map +1 -1
  339. package/dist/client/org/TeamPage.js +27 -3
  340. package/dist/client/org/TeamPage.js.map +1 -1
  341. package/dist/client/org/WorkspaceNotice.d.ts +18 -0
  342. package/dist/client/org/WorkspaceNotice.d.ts.map +1 -0
  343. package/dist/client/org/WorkspaceNotice.js +56 -0
  344. package/dist/client/org/WorkspaceNotice.js.map +1 -0
  345. package/dist/client/org/hooks.d.ts +1 -0
  346. package/dist/client/org/hooks.d.ts.map +1 -1
  347. package/dist/client/org/hooks.js +12 -0
  348. package/dist/client/org/hooks.js.map +1 -1
  349. package/dist/client/org/index.d.ts +2 -1
  350. package/dist/client/org/index.d.ts.map +1 -1
  351. package/dist/client/org/index.js +2 -1
  352. package/dist/client/org/index.js.map +1 -1
  353. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  354. package/dist/client/resources/McpIntegrationDialog.js +8 -12
  355. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  356. package/dist/client/resources/ResourcesPanel.d.ts +3 -0
  357. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  358. package/dist/client/resources/ResourcesPanel.js +49 -20
  359. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  360. package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
  361. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  362. package/dist/client/resources/mcp-integration-catalog.js +3 -0
  363. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  364. package/dist/client/resources/use-resources.d.ts +1 -0
  365. package/dist/client/resources/use-resources.d.ts.map +1 -1
  366. package/dist/client/resources/use-resources.js +3 -0
  367. package/dist/client/resources/use-resources.js.map +1 -1
  368. package/dist/client/use-action.d.ts +3 -1
  369. package/dist/client/use-action.d.ts.map +1 -1
  370. package/dist/client/use-action.js +3 -1
  371. package/dist/client/use-action.js.map +1 -1
  372. package/dist/client/widgets/index.d.ts +1 -1
  373. package/dist/client/widgets/index.d.ts.map +1 -1
  374. package/dist/client/widgets/index.js +1 -1
  375. package/dist/client/widgets/index.js.map +1 -1
  376. package/dist/coding-tools/run-code.js +2 -1
  377. package/dist/coding-tools/run-code.js.map +1 -1
  378. package/dist/collab/awareness.d.ts +2 -2
  379. package/dist/collab/awareness.d.ts.map +1 -1
  380. package/dist/db/client.d.ts +1 -0
  381. package/dist/db/client.d.ts.map +1 -1
  382. package/dist/db/client.js +24 -3
  383. package/dist/db/client.js.map +1 -1
  384. package/dist/deploy/build.d.ts.map +1 -1
  385. package/dist/deploy/build.js +25 -3
  386. package/dist/deploy/build.js.map +1 -1
  387. package/dist/eject/remote-mcp-presets.js +1 -1
  388. package/dist/eject/remote-mcp-presets.js.map +1 -1
  389. package/dist/index.browser.d.ts +1 -1
  390. package/dist/index.browser.d.ts.map +1 -1
  391. package/dist/index.browser.js +1 -1
  392. package/dist/index.browser.js.map +1 -1
  393. package/dist/index.d.ts +1 -1
  394. package/dist/index.d.ts.map +1 -1
  395. package/dist/index.js +1 -1
  396. package/dist/index.js.map +1 -1
  397. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  398. package/dist/integrations/google-docs-poller.js +4 -1
  399. package/dist/integrations/google-docs-poller.js.map +1 -1
  400. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  401. package/dist/integrations/webhook-handler.js +23 -11
  402. package/dist/integrations/webhook-handler.js.map +1 -1
  403. package/dist/jobs/scheduler.js +4 -0
  404. package/dist/jobs/scheduler.js.map +1 -1
  405. package/dist/localization/default-messages.d.ts +3 -2
  406. package/dist/localization/default-messages.d.ts.map +1 -1
  407. package/dist/localization/default-messages.js +3 -2
  408. package/dist/localization/default-messages.js.map +1 -1
  409. package/dist/notifications/routes.d.ts +1 -1
  410. package/dist/observability/routes.d.ts +3 -3
  411. package/dist/observability/traces.d.ts.map +1 -1
  412. package/dist/observability/traces.js +27 -7
  413. package/dist/observability/traces.js.map +1 -1
  414. package/dist/org/handlers.d.ts +15 -0
  415. package/dist/org/handlers.d.ts.map +1 -1
  416. package/dist/org/handlers.js +45 -1
  417. package/dist/org/handlers.js.map +1 -1
  418. package/dist/org/migrations.d.ts.map +1 -1
  419. package/dist/org/migrations.js +4 -0
  420. package/dist/org/migrations.js.map +1 -1
  421. package/dist/org/plugin.d.ts +1 -0
  422. package/dist/org/plugin.d.ts.map +1 -1
  423. package/dist/org/plugin.js +10 -1
  424. package/dist/org/plugin.js.map +1 -1
  425. package/dist/org/schema.d.ts +19 -0
  426. package/dist/org/schema.d.ts.map +1 -1
  427. package/dist/org/schema.js +1 -0
  428. package/dist/org/schema.js.map +1 -1
  429. package/dist/org/types.d.ts +8 -0
  430. package/dist/org/types.d.ts.map +1 -1
  431. package/dist/org/types.js.map +1 -1
  432. package/dist/org/workspace-url.d.ts +32 -0
  433. package/dist/org/workspace-url.d.ts.map +1 -0
  434. package/dist/org/workspace-url.js +63 -0
  435. package/dist/org/workspace-url.js.map +1 -0
  436. package/dist/progress/routes.d.ts +1 -1
  437. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  438. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  439. package/dist/resources/handlers.d.ts +2 -3
  440. package/dist/resources/handlers.d.ts.map +1 -1
  441. package/dist/resources/handlers.js +25 -5
  442. package/dist/resources/handlers.js.map +1 -1
  443. package/dist/scripts/runner.d.ts +17 -0
  444. package/dist/scripts/runner.d.ts.map +1 -1
  445. package/dist/scripts/runner.js +130 -5
  446. package/dist/scripts/runner.js.map +1 -1
  447. package/dist/server/action-routes.d.ts.map +1 -1
  448. package/dist/server/action-routes.js +24 -2
  449. package/dist/server/action-routes.js.map +1 -1
  450. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  451. package/dist/server/agent-chat-plugin.js +26 -1
  452. package/dist/server/agent-chat-plugin.js.map +1 -1
  453. package/dist/server/agent-teams.d.ts.map +1 -1
  454. package/dist/server/agent-teams.js +10 -1
  455. package/dist/server/agent-teams.js.map +1 -1
  456. package/dist/server/auth.d.ts +22 -0
  457. package/dist/server/auth.d.ts.map +1 -1
  458. package/dist/server/auth.js +33 -7
  459. package/dist/server/auth.js.map +1 -1
  460. package/dist/server/embed-route.d.ts.map +1 -1
  461. package/dist/server/embed-route.js +6 -1
  462. package/dist/server/embed-route.js.map +1 -1
  463. package/dist/server/embed-session.d.ts +7 -0
  464. package/dist/server/embed-session.d.ts.map +1 -1
  465. package/dist/server/embed-session.js +46 -4
  466. package/dist/server/embed-session.js.map +1 -1
  467. package/dist/server/open-route.d.ts.map +1 -1
  468. package/dist/server/open-route.js +17 -7
  469. package/dist/server/open-route.js.map +1 -1
  470. package/dist/server/prompts/framework-core-compact.js +1 -1
  471. package/dist/server/prompts/framework-core-compact.js.map +1 -1
  472. package/dist/server/prompts/framework-core.d.ts.map +1 -1
  473. package/dist/server/prompts/framework-core.js +1 -0
  474. package/dist/server/prompts/framework-core.js.map +1 -1
  475. package/dist/server/realtime-token.d.ts +1 -1
  476. package/dist/server/request-context.d.ts +26 -0
  477. package/dist/server/request-context.d.ts.map +1 -1
  478. package/dist/server/request-context.js +18 -1
  479. package/dist/server/request-context.js.map +1 -1
  480. package/dist/server/transcribe-voice.d.ts +1 -1
  481. package/dist/shared/embed-auth.d.ts +1 -0
  482. package/dist/shared/embed-auth.d.ts.map +1 -1
  483. package/dist/shared/embed-auth.js +1 -0
  484. package/dist/shared/embed-auth.js.map +1 -1
  485. package/dist/shared/html-document.d.ts +12 -0
  486. package/dist/shared/html-document.d.ts.map +1 -0
  487. package/dist/shared/html-document.js +92 -0
  488. package/dist/shared/html-document.js.map +1 -0
  489. package/dist/shared/index.d.ts +1 -0
  490. package/dist/shared/index.d.ts.map +1 -1
  491. package/dist/shared/index.js +1 -0
  492. package/dist/shared/index.js.map +1 -1
  493. package/dist/sharing/access.d.ts +1 -0
  494. package/dist/sharing/access.d.ts.map +1 -1
  495. package/dist/sharing/access.js +8 -2
  496. package/dist/sharing/access.js.map +1 -1
  497. package/dist/sharing/registry.d.ts +3 -0
  498. package/dist/sharing/registry.d.ts.map +1 -1
  499. package/dist/sharing/registry.js.map +1 -1
  500. package/dist/workspace-files/actions.d.ts +28 -0
  501. package/dist/workspace-files/actions.d.ts.map +1 -0
  502. package/dist/workspace-files/actions.js +75 -0
  503. package/dist/workspace-files/actions.js.map +1 -0
  504. package/dist/workspace-files/tool.d.ts.map +1 -1
  505. package/dist/workspace-files/tool.js +4 -0
  506. package/dist/workspace-files/tool.js.map +1 -1
  507. package/docs/content/cloneable-saas.mdx +114 -0
  508. package/docs/content/creating-templates.mdx +28 -2
  509. package/docs/content/syncing-template-changes.mdx +7 -0
  510. package/docs/content/template-clips-developers.mdx +12 -5
  511. package/docs/content/template-dispatch-developers.mdx +1 -1
  512. package/docs/content/template-dispatch.mdx +1 -1
  513. package/package.json +2 -2
  514. package/src/action-ui.ts +1 -0
  515. package/src/action.ts +5 -1
  516. package/src/agent/engine/ai-sdk-engine.ts +15 -5
  517. package/src/agent/harness/runner.ts +5 -0
  518. package/src/agent/production-agent.ts +33 -0
  519. package/src/agent/run-loop-with-resume.ts +5 -0
  520. package/src/agent/run-manager.ts +126 -1
  521. package/src/agent/thread-data-builder.ts +47 -2
  522. package/src/agent/types.ts +2 -0
  523. package/src/chat-threads/store.ts +4 -5
  524. package/src/cli/create.ts +1139 -65
  525. package/src/cli/design-connect.ts +3 -7
  526. package/src/cli/index.ts +5 -3
  527. package/src/cli/skills-content/design-visual-edit-skill.ts +83 -47
  528. package/src/client/AssistantChat.tsx +27 -16
  529. package/src/client/agent-chat-adapter.ts +24 -0
  530. package/src/client/blocks/library/wireframe-kit.tsx +17 -2
  531. package/src/client/chat/index.ts +1 -0
  532. package/src/client/chat/message-components.tsx +104 -1
  533. package/src/client/chat/run-recovery.tsx +15 -3
  534. package/src/client/chat/widgets/DataTableWidget.tsx +10 -29
  535. package/src/client/chat/widgets/WorkspaceFileWidget.tsx +71 -0
  536. package/src/client/chat/widgets/builtin-tool-renderers.tsx +22 -0
  537. package/src/client/chat/widgets/workspace-file-result.ts +49 -0
  538. package/src/client/embed-auth.ts +2 -0
  539. package/src/client/org/InvitationBanner.tsx +14 -1
  540. package/src/client/org/OrgSwitcher.tsx +23 -0
  541. package/src/client/org/TeamPage.tsx +130 -0
  542. package/src/client/org/WorkspaceNotice.tsx +85 -0
  543. package/src/client/org/hooks.ts +14 -0
  544. package/src/client/org/index.ts +2 -0
  545. package/src/client/resources/McpIntegrationDialog.tsx +27 -40
  546. package/src/client/resources/ResourcesPanel.tsx +134 -51
  547. package/src/client/resources/mcp-integration-catalog.ts +7 -0
  548. package/src/client/resources/use-resources.ts +6 -0
  549. package/src/client/use-action.ts +3 -1
  550. package/src/client/widgets/index.ts +1 -0
  551. package/src/coding-tools/run-code.ts +2 -1
  552. package/src/db/client.ts +28 -4
  553. package/src/deploy/build.ts +31 -3
  554. package/src/eject/remote-mcp-presets.ts +1 -1
  555. package/src/index.browser.ts +1 -0
  556. package/src/index.ts +1 -0
  557. package/src/integrations/google-docs-poller.ts +3 -0
  558. package/src/integrations/webhook-handler.ts +28 -11
  559. package/src/jobs/scheduler.ts +4 -0
  560. package/src/localization/default-messages.ts +3 -2
  561. package/src/observability/traces.ts +48 -12
  562. package/src/org/handlers.ts +52 -1
  563. package/src/org/migrations.ts +4 -0
  564. package/src/org/plugin.ts +14 -0
  565. package/src/org/schema.ts +1 -0
  566. package/src/org/types.ts +8 -0
  567. package/src/org/workspace-url.ts +74 -0
  568. package/src/resources/handlers.ts +35 -5
  569. package/src/scripts/runner.ts +183 -5
  570. package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +2 -1
  571. package/src/server/action-routes.ts +32 -2
  572. package/src/server/agent-chat-plugin.ts +29 -0
  573. package/src/server/agent-teams.ts +10 -1
  574. package/src/server/auth.ts +33 -7
  575. package/src/server/embed-route.ts +9 -0
  576. package/src/server/embed-session.ts +62 -3
  577. package/src/server/open-route.ts +20 -6
  578. package/src/server/prompts/framework-core-compact.ts +1 -1
  579. package/src/server/prompts/framework-core.ts +1 -0
  580. package/src/server/request-context.ts +36 -1
  581. package/src/shared/embed-auth.ts +1 -0
  582. package/src/shared/html-document.ts +115 -0
  583. package/src/shared/index.ts +1 -0
  584. package/src/sharing/access.ts +8 -1
  585. package/src/sharing/registry.ts +11 -2
  586. package/src/workspace-files/actions.ts +100 -0
  587. package/src/workspace-files/tool.ts +4 -0
  588. package/corpus/templates/design/changelog/2026-07-28-interact-mode-can-preview-designs-in-responsive-device-sizes.md +0 -6
  589. package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-opens-a-full-screen-responsive-preview-wit.md +0 -6
@@ -91,6 +91,12 @@ const HEADLESS_OPTION = {
91
91
  hint: "Action-first app with one hello primitive and no UI shell",
92
92
  };
93
93
 
94
+ const COMMUNITY_OPTION = {
95
+ name: "community",
96
+ label: "Community template",
97
+ hint: "Install a third-party Agent Native app from a public GitHub repository",
98
+ };
99
+
94
100
  export interface CreateAppOptions {
95
101
  /** Pre-select these templates in the picker. Comma-separated string or array. */
96
102
  template?: string;
@@ -183,6 +189,11 @@ export async function createApp(
183
189
  await createStandaloneApp(name, { ...opts, template: shape }, clack);
184
190
  return;
185
191
  }
192
+ if (shape === "community") {
193
+ const template = await promptCommunityTemplate(clack);
194
+ await createStandaloneApp(name, { ...opts, template }, clack);
195
+ return;
196
+ }
186
197
  // shape === "template" → full app(s) in a workspace.
187
198
  await createWorkspaceInteractive(name, opts, clack);
188
199
  return;
@@ -197,6 +208,7 @@ export async function createApp(
197
208
  * choice made here implies the project structure, so we deliberately avoid a
198
209
  * separate "workspace or standalone?" question:
199
210
  * - "template" → full app(s) in a workspace (the multi-select picker)
211
+ * - "community" → a single standalone app from a public GitHub repository
200
212
  * - "chat" → a single standalone chat UI app
201
213
  * - "headless" → a single standalone action-first app with no UI shell
202
214
  * Chat and headless are standalone on purpose: a monorepo is unnecessary
@@ -205,13 +217,13 @@ export async function createApp(
205
217
  */
206
218
  async function promptStartShape(
207
219
  clack: typeof import("@clack/prompts"),
208
- ): Promise<"template" | "chat" | "headless"> {
220
+ ): Promise<"template" | "chat" | "community" | "headless"> {
209
221
  const choice = await clack.select(startShapePromptOptions());
210
222
  if (clack.isCancel(choice)) {
211
223
  clack.cancel("Cancelled.");
212
224
  process.exit(0);
213
225
  }
214
- return choice as "template" | "chat" | "headless";
226
+ return choice as "template" | "chat" | "community" | "headless";
215
227
  }
216
228
 
217
229
  function startShapePromptOptions() {
@@ -226,8 +238,13 @@ function startShapePromptOptions() {
226
238
  },
227
239
  {
228
240
  value: "template",
229
- label: "Full template(s)",
230
- hint: "Clone complete apps (Mail, Calendar, Slides, ...) into a workspace",
241
+ label: "First-party template(s)",
242
+ hint: "Clone official apps (Mail, Calendar, Slides, ...) into a workspace",
243
+ },
244
+ {
245
+ value: COMMUNITY_OPTION.name,
246
+ label: COMMUNITY_OPTION.label,
247
+ hint: COMMUNITY_OPTION.hint,
231
248
  },
232
249
  {
233
250
  value: "headless",
@@ -238,6 +255,70 @@ function startShapePromptOptions() {
238
255
  };
239
256
  }
240
257
 
258
+ async function promptCommunityTemplate(
259
+ clack: typeof import("@clack/prompts"),
260
+ ): Promise<string> {
261
+ const selection = await clack.text({
262
+ message: "Which community template do you want to install?",
263
+ placeholder: "https://github.com/owner/repo[?app=id][#ref]",
264
+ validate(value) {
265
+ try {
266
+ parseCommunityPromptValue(String(value ?? ""));
267
+ } catch (error) {
268
+ return error instanceof Error ? error.message : String(error);
269
+ }
270
+ },
271
+ });
272
+ if (clack.isCancel(selection)) {
273
+ clack.cancel("Cancelled.");
274
+ process.exit(0);
275
+ }
276
+ return parseCommunityPromptValue(String(selection)).canonical;
277
+ }
278
+
279
+ interface CommunityWorkspaceAppOption {
280
+ name: string;
281
+ label: string;
282
+ }
283
+
284
+ async function promptCommunityWorkspaceApp(
285
+ apps: CommunityWorkspaceAppOption[],
286
+ clack: typeof import("@clack/prompts"),
287
+ ): Promise<string> {
288
+ const selection = await clack.select({
289
+ message: "Which app from this community workspace do you want to install?",
290
+ options: apps.map((app) => ({
291
+ value: app.name,
292
+ label: app.label,
293
+ hint: app.label === app.name ? undefined : app.name,
294
+ })),
295
+ });
296
+ if (clack.isCancel(selection)) {
297
+ clack.cancel("Cancelled.");
298
+ process.exit(0);
299
+ }
300
+ return String(selection);
301
+ }
302
+
303
+ function communityScaffoldOptions(
304
+ clack: typeof import("@clack/prompts"),
305
+ shape: "standalone" | "workspace",
306
+ destinationAppName: string,
307
+ targetWorkspaceCoreName?: string,
308
+ ): ScaffoldAppTemplateOptions {
309
+ return {
310
+ shape,
311
+ destinationAppName,
312
+ targetWorkspaceCoreName,
313
+ ...(process.stdin.isTTY && process.stdout.isTTY
314
+ ? {
315
+ selectCommunityWorkspaceApp: (apps: CommunityWorkspaceAppOption[]) =>
316
+ promptCommunityWorkspaceApp(apps, clack),
317
+ }
318
+ : {}),
319
+ };
320
+ }
321
+
241
322
  /**
242
323
  * Validate a project name supplied on the command line. Mirrors the rule in
243
324
  * `promptNameIfMissing` so an invalid name is rejected before any interactive
@@ -304,6 +385,9 @@ async function createWorkspaceInteractive(
304
385
  }
305
386
 
306
387
  const s = clack.spinner();
388
+ for (const template of templates) {
389
+ showCommunityTemplateTrustNote(template, clack);
390
+ }
307
391
  s.start(`Scaffolding workspace "${name}"...`);
308
392
  const appNames = new Set<string>();
309
393
  const scaffoldedApps: string[] = [];
@@ -330,21 +414,39 @@ async function createWorkspaceInteractive(
330
414
  // GitHub fetch doesn't look like a frozen "Scaffolding..." message.
331
415
  // Mirrors the local-vs-remote decision inside scaffoldAppTemplate.
332
416
  const willDownload =
333
- templateName !== "headless" && templateName.startsWith("github:")
334
- ? true
335
- : !findLocalTemplate(normalizeTemplateName(templateName));
417
+ templateName !== "headless" &&
418
+ (isCommunityTemplateSelection(templateName) ||
419
+ !findLocalTemplate(normalizeTemplateName(templateName)));
336
420
  s.message(
337
421
  willDownload
338
422
  ? `Downloading ${titleCase(appName)} template (${i + 1}/${templates.length})...`
339
423
  : `Scaffolding ${titleCase(appName)} (${i + 1}/${templates.length})...`,
340
424
  );
341
425
  const appDir = path.join(targetDir, "apps", appName);
342
- await scaffoldAppTemplate(appDir, templateName);
426
+ const resolution = await scaffoldAppTemplate(
427
+ appDir,
428
+ templateName,
429
+ communityScaffoldOptions(
430
+ clack,
431
+ "workspace",
432
+ appName,
433
+ workspaceCoreName,
434
+ ),
435
+ );
343
436
  s.message(
344
437
  `Configuring ${titleCase(appName)} (${i + 1}/${templates.length})...`,
345
438
  );
346
439
  replacePlaceholders(appDir, appName, appTitleForScaffold(appName), name);
347
- rewriteTrackingAppId(appDir, appName, templateName);
440
+ if (resolution.sourceIdentity) {
441
+ applyScaffoldIdentity(
442
+ appDir,
443
+ appName,
444
+ templateName,
445
+ resolution.sourceIdentity,
446
+ );
447
+ } else {
448
+ rewriteTrackingAppId(appDir, appName, templateName);
449
+ }
348
450
  workspacifyApp({
349
451
  appDir,
350
452
  appName,
@@ -355,8 +457,16 @@ async function createWorkspaceInteractive(
355
457
  dispatchDependencyVersion: getDispatchDependencyVersion(),
356
458
  toolkitDependencyVersion: getToolkitDependencyVersion(),
357
459
  });
358
- fixPackageJsonName(appDir, appName, templateName);
359
- fixWebManifestName(appDir, appName, templateName);
460
+ fixPackageJsonName(appDir, appName, templateName, {
461
+ ...resolution,
462
+ shape: "workspace",
463
+ });
464
+ fixWebManifestName(
465
+ appDir,
466
+ appName,
467
+ templateName,
468
+ resolution.sourceIdentity,
469
+ );
360
470
  rewriteNetlifyToml(appDir, appName, "workspace");
361
471
  renameGitignore(appDir);
362
472
  // Each app owns its own .claude / .agents symlinks.
@@ -432,9 +542,9 @@ async function createWorkspaceInteractive(
432
542
 
433
543
  function workspaceAppNameForTemplateSelection(templateName: string): string {
434
544
  const normalized = normalizeTemplateName(templateName);
435
- if (!normalized.startsWith("github:")) return templateName;
436
- const repo = normalized.slice("github:".length).trim();
437
- const repoName = repo.split("/").filter(Boolean).pop() ?? "app";
545
+ const community = parseCommunityTemplateSelection(normalized, false);
546
+ if (!community) return templateName;
547
+ const repoName = community.app ?? community.repo.split("/").pop() ?? "app";
438
548
  let appName = repoName
439
549
  .toLowerCase()
440
550
  .replace(/[^a-z0-9-]+/g, "-")
@@ -636,19 +746,38 @@ async function scaffoldOneAppIntoWorkspace(
636
746
  }
637
747
 
638
748
  const s = clack.spinner();
749
+ showCommunityTemplateTrustNote(templateName, clack);
639
750
  s.start(
640
751
  `Working... no action needed. Scaffolding apps/${appName} from ${templateName}.`,
641
752
  );
642
753
 
643
754
  try {
644
- const resolution = await scaffoldAppTemplate(appDir, templateName);
755
+ const resolution = await scaffoldAppTemplate(
756
+ appDir,
757
+ templateName,
758
+ communityScaffoldOptions(
759
+ clack,
760
+ "workspace",
761
+ appName,
762
+ workspace.workspaceCoreName,
763
+ ),
764
+ );
645
765
  replacePlaceholders(
646
766
  appDir,
647
767
  appName,
648
768
  appTitleForScaffold(appName),
649
769
  path.basename(workspace.workspaceRoot),
650
770
  );
651
- rewriteTrackingAppId(appDir, appName, templateName);
771
+ if (resolution.sourceIdentity) {
772
+ applyScaffoldIdentity(
773
+ appDir,
774
+ appName,
775
+ templateName,
776
+ resolution.sourceIdentity,
777
+ );
778
+ } else {
779
+ rewriteTrackingAppId(appDir, appName, templateName);
780
+ }
652
781
  workspacifyApp({
653
782
  appDir,
654
783
  appName,
@@ -663,7 +792,12 @@ async function scaffoldOneAppIntoWorkspace(
663
792
  ...resolution,
664
793
  shape: "workspace",
665
794
  });
666
- fixWebManifestName(appDir, appName, templateName);
795
+ fixWebManifestName(
796
+ appDir,
797
+ appName,
798
+ templateName,
799
+ resolution.sourceIdentity,
800
+ );
667
801
  rewriteNetlifyToml(appDir, appName, "workspace");
668
802
  renameGitignore(appDir);
669
803
  setupAgentSymlinks(appDir);
@@ -713,20 +847,7 @@ async function createStandaloneApp(
713
847
  if (!template) {
714
848
  const picked = await clack.select({
715
849
  message: "Which template would you like to use?",
716
- options: [
717
- {
718
- value: HEADLESS_OPTION.name,
719
- label: HEADLESS_OPTION.label,
720
- hint: HEADLESS_OPTION.hint,
721
- },
722
- ...onRampFirst(coreTemplates())
723
- .filter((t) => t.name !== HEADLESS_OPTION.name)
724
- .map((t) => ({
725
- value: t.name,
726
- label: t.label,
727
- hint: t.hint,
728
- })),
729
- ],
850
+ options: standaloneTemplatePromptOptions(),
730
851
  });
731
852
  if (clack.isCancel(picked)) {
732
853
  clack.cancel("Cancelled.");
@@ -735,15 +856,24 @@ async function createStandaloneApp(
735
856
  template = picked as string;
736
857
  }
737
858
  template = normalizeTemplateName(template);
859
+ if (template === COMMUNITY_OPTION.name) {
860
+ template = await promptCommunityTemplate(clack);
861
+ }
738
862
 
739
863
  const s = clack.spinner();
864
+ showCommunityTemplateTrustNote(template, clack);
740
865
  s.start(
741
866
  template === "headless"
742
867
  ? "Scaffolding the headless agent app..."
743
- : `Downloading the ${template} template from GitHub...`,
868
+ : (communityTemplateDownloadMessage(template) ??
869
+ `Downloading the ${template} template from GitHub...`),
744
870
  );
745
871
  try {
746
- const resolution = await scaffoldAppTemplate(targetDir, template);
872
+ const resolution = await scaffoldAppTemplate(
873
+ targetDir,
874
+ template,
875
+ communityScaffoldOptions(clack, "standalone", name),
876
+ );
747
877
  s.message(`Setting up ${name}…`);
748
878
  postProcessStandalone(name, targetDir, template, resolution);
749
879
  s.stop("App created!");
@@ -776,6 +906,28 @@ async function createStandaloneApp(
776
906
  }
777
907
  }
778
908
 
909
+ function standaloneTemplatePromptOptions() {
910
+ return [
911
+ {
912
+ value: HEADLESS_OPTION.name,
913
+ label: HEADLESS_OPTION.label,
914
+ hint: HEADLESS_OPTION.hint,
915
+ },
916
+ ...onRampFirst(coreTemplates())
917
+ .filter((t) => t.name !== HEADLESS_OPTION.name)
918
+ .map((t) => ({
919
+ value: t.name,
920
+ label: t.label,
921
+ hint: t.hint,
922
+ })),
923
+ {
924
+ value: COMMUNITY_OPTION.name,
925
+ label: COMMUNITY_OPTION.label,
926
+ hint: COMMUNITY_OPTION.hint,
927
+ },
928
+ ];
929
+ }
930
+
779
931
  /**
780
932
  * Remove a partially-scaffolded target directory after a scaffold failure so a
781
933
  * retry doesn't hit the "Directory already exists" guard. Best-effort — the
@@ -801,6 +953,26 @@ function cleanupOnFailure(targetDir: string): void {
801
953
  export interface ScaffoldTemplateResolution {
802
954
  templateRef?: string;
803
955
  templateSource?: "github" | "bundled" | "local-checkout";
956
+ sourceIdentity?: ScaffoldSourceIdentity;
957
+ communityTemplate?: {
958
+ source: string;
959
+ ref: string;
960
+ app?: string;
961
+ };
962
+ }
963
+
964
+ interface ScaffoldSourceIdentity {
965
+ appName: string;
966
+ appTitle: string;
967
+ }
968
+
969
+ interface ScaffoldAppTemplateOptions {
970
+ shape?: "standalone" | "workspace";
971
+ destinationAppName?: string;
972
+ targetWorkspaceCoreName?: string;
973
+ selectCommunityWorkspaceApp?: (
974
+ apps: CommunityWorkspaceAppOption[],
975
+ ) => Promise<string>;
804
976
  }
805
977
 
806
978
  export interface ScaffoldProvenance extends ScaffoldTemplateResolution {
@@ -810,13 +982,15 @@ export interface ScaffoldProvenance extends ScaffoldTemplateResolution {
810
982
  /**
811
983
  * Scaffold a single app template into `targetDir`. Resolves:
812
984
  * - "headless" / legacy "blank" → bundled action-first template
813
- * - "github:user/repo" → download the whole repo
985
+ * - "community:user/repo[#ref]" → download and validate the whole repo
986
+ * - legacy "github:user/repo[#ref]" and clean GitHub HTTPS URLs → community
814
987
  * - first-party template name → use a bundled copy or download its subdir
815
988
  * from BuilderIO/agent-native
816
989
  */
817
990
  async function scaffoldAppTemplate(
818
991
  targetDir: string,
819
992
  template: string,
993
+ options: ScaffoldAppTemplateOptions = {},
820
994
  ): Promise<ScaffoldTemplateResolution> {
821
995
  fs.mkdirSync(path.dirname(targetDir), { recursive: true });
822
996
 
@@ -838,15 +1012,55 @@ async function scaffoldAppTemplate(
838
1012
  };
839
1013
  }
840
1014
 
841
- if (resolved.startsWith("github:")) {
842
- const repo = resolved.slice("github:".length);
843
- await downloadGitHubRepo(repo, targetDir);
844
- return {};
1015
+ const community = parseCommunityTemplateSelection(resolved, false);
1016
+ if (community) {
1017
+ const stagingDir = path.join(
1018
+ path.dirname(targetDir),
1019
+ `.agent-native-community-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1020
+ );
1021
+ try {
1022
+ const ref = await downloadGitHubRepo(
1023
+ community.repo,
1024
+ stagingDir,
1025
+ community.ref,
1026
+ );
1027
+ const source = await resolveCommunityTemplateSource(
1028
+ stagingDir,
1029
+ community,
1030
+ options,
1031
+ );
1032
+ copyDir(source.sourceDir, targetDir);
1033
+ if (source.workspaceRoot) {
1034
+ normalizeCommunityWorkspaceAppDependencies(
1035
+ targetDir,
1036
+ source.workspaceRoot,
1037
+ {
1038
+ shape: options.shape,
1039
+ destinationAppName:
1040
+ options.destinationAppName ?? path.basename(targetDir),
1041
+ targetWorkspaceCoreName: options.targetWorkspaceCoreName,
1042
+ sourceIdentity: source.sourceIdentity,
1043
+ },
1044
+ );
1045
+ }
1046
+ return {
1047
+ templateSource: "github",
1048
+ templateRef: ref,
1049
+ sourceIdentity: source.sourceIdentity,
1050
+ communityTemplate: {
1051
+ source: `https://github.com/${community.repo}`,
1052
+ ref,
1053
+ ...(source.app ? { app: source.app } : {}),
1054
+ },
1055
+ };
1056
+ } finally {
1057
+ fs.rmSync(stagingDir, { recursive: true, force: true });
1058
+ }
845
1059
  }
846
1060
 
847
1061
  if (!getTemplate(resolved)) {
848
1062
  throw new Error(
849
- `Unknown template "${template}". Known: ${allTemplateNames().join(", ")} or use github:user/repo for community templates.`,
1063
+ `Unknown template "${template}". Known first-party templates: ${allTemplateNames().join(", ")}. For a community template, use community:owner/repo.`,
850
1064
  );
851
1065
  }
852
1066
 
@@ -921,9 +1135,202 @@ function normalizeTemplateName(template: string): string {
921
1135
  if (template === "image" || template === "images" || template === "asset") {
922
1136
  return "assets";
923
1137
  }
1138
+ const community = parseCommunityTemplateSelection(template, false);
1139
+ if (community) return community.canonical;
924
1140
  return template;
925
1141
  }
926
1142
 
1143
+ interface CommunityTemplateSelection {
1144
+ repo: string;
1145
+ app?: string;
1146
+ ref?: string;
1147
+ canonical: string;
1148
+ }
1149
+
1150
+ function parseCommunityTemplateSelection(
1151
+ selection: string,
1152
+ ): CommunityTemplateSelection;
1153
+ function parseCommunityTemplateSelection(
1154
+ selection: string,
1155
+ required: true,
1156
+ ): CommunityTemplateSelection;
1157
+ function parseCommunityTemplateSelection(
1158
+ selection: string,
1159
+ required: false,
1160
+ ): CommunityTemplateSelection | undefined;
1161
+ function parseCommunityTemplateSelection(
1162
+ selection: string,
1163
+ required = true,
1164
+ ): CommunityTemplateSelection | undefined {
1165
+ const value = selection.trim();
1166
+ let raw: string | undefined;
1167
+ if (value.startsWith("community:")) {
1168
+ raw = value.slice("community:".length).trim();
1169
+ } else if (value.startsWith("github:")) {
1170
+ raw = value.slice("github:".length).trim();
1171
+ } else if (value.startsWith("https://github.com/")) {
1172
+ raw = value;
1173
+ } else {
1174
+ if (!required) return undefined;
1175
+ throw new ValidationError(
1176
+ `Invalid community template "${selection}". Use community:owner/repo, optionally with #branch, #tag, or #commit.`,
1177
+ );
1178
+ }
1179
+
1180
+ if (!raw) {
1181
+ throw new ValidationError(
1182
+ "A community template repository is required. Use owner/repo or a GitHub URL.",
1183
+ );
1184
+ }
1185
+
1186
+ let repo: string;
1187
+ let app: string | undefined;
1188
+ let ref: string | undefined;
1189
+ if (raw.includes("://")) {
1190
+ let url: URL;
1191
+ try {
1192
+ url = new URL(raw);
1193
+ } catch {
1194
+ throw new ValidationError(
1195
+ `Invalid GitHub repository URL "${raw}". Expected https://github.com/owner/repo.`,
1196
+ );
1197
+ }
1198
+ const parts = url.pathname.split("/").filter(Boolean);
1199
+ if (
1200
+ url.protocol !== "https:" ||
1201
+ url.hostname !== "github.com" ||
1202
+ url.port ||
1203
+ url.username ||
1204
+ url.password ||
1205
+ parts.length !== 2
1206
+ ) {
1207
+ throw new ValidationError(
1208
+ `Invalid GitHub repository URL "${raw}". Expected https://github.com/owner/repo with an optional #ref.`,
1209
+ );
1210
+ }
1211
+ repo = `${parts[0]}/${parts[1]!.replace(/\.git$/, "")}`;
1212
+ app = parseCommunityAppSearchParams(url.searchParams, raw);
1213
+ if (url.hash.length > 1) {
1214
+ try {
1215
+ ref = decodeURIComponent(url.hash.slice(1));
1216
+ } catch {
1217
+ throw new ValidationError(`Invalid encoded Git ref in "${raw}".`);
1218
+ }
1219
+ }
1220
+ } else {
1221
+ const hash = raw.indexOf("#");
1222
+ const beforeHash = hash === -1 ? raw : raw.slice(0, hash);
1223
+ const query = beforeHash.indexOf("?");
1224
+ repo = (query === -1 ? beforeHash : beforeHash.slice(0, query)).replace(
1225
+ /\.git$/,
1226
+ "",
1227
+ );
1228
+ if (query !== -1) {
1229
+ app = parseCommunityAppSearchParams(
1230
+ new URLSearchParams(beforeHash.slice(query + 1)),
1231
+ raw,
1232
+ );
1233
+ }
1234
+ ref = hash === -1 ? undefined : raw.slice(hash + 1);
1235
+ }
1236
+
1237
+ validateRepoName(repo);
1238
+ if (app !== undefined) validateCommunityAppSelector(app);
1239
+ if (ref !== undefined) validateGitRef(ref);
1240
+ return {
1241
+ repo,
1242
+ ...(app ? { app } : {}),
1243
+ ...(ref ? { ref } : {}),
1244
+ canonical: `community:${repo}${app ? `?app=${app}` : ""}${ref ? `#${ref}` : ""}`,
1245
+ };
1246
+ }
1247
+
1248
+ function parseCommunityAppSearchParams(
1249
+ params: URLSearchParams,
1250
+ source: string,
1251
+ ): string | undefined {
1252
+ const entries = [...params.entries()];
1253
+ if (entries.length === 0) return undefined;
1254
+ if (entries.length !== 1 || entries[0]?.[0] !== "app" || !entries[0][1]) {
1255
+ throw new ValidationError(
1256
+ `Invalid community template selector in "${source}". Only ?app=<app-name> is supported.`,
1257
+ );
1258
+ }
1259
+ return entries[0][1];
1260
+ }
1261
+
1262
+ function validateCommunityAppSelector(app: string): void {
1263
+ if (app === "dispatch") {
1264
+ throw new ValidationError(
1265
+ "Dispatch cannot be installed from a community workspace.",
1266
+ );
1267
+ }
1268
+ const error = getWorkspaceAppIdValidationError(app);
1269
+ if (error) throw new ValidationError(error);
1270
+ }
1271
+
1272
+ function isCommunityTemplateSelection(selection: string): boolean {
1273
+ return parseCommunityTemplateSelection(selection, false) !== undefined;
1274
+ }
1275
+
1276
+ function parseCommunityPromptValue(value: string): CommunityTemplateSelection {
1277
+ const trimmed = value.trim();
1278
+ const selection =
1279
+ trimmed.startsWith("community:") ||
1280
+ trimmed.startsWith("github:") ||
1281
+ trimmed.startsWith("https://github.com/")
1282
+ ? trimmed
1283
+ : `community:${trimmed}`;
1284
+ return parseCommunityTemplateSelection(selection);
1285
+ }
1286
+
1287
+ function validateGitRef(ref: string): void {
1288
+ if (
1289
+ !ref ||
1290
+ !/^[a-zA-Z0-9][a-zA-Z0-9._/+-]*$/.test(ref) ||
1291
+ ref.includes("..") ||
1292
+ ref.includes("//") ||
1293
+ ref.includes("@{") ||
1294
+ ref.endsWith("/") ||
1295
+ ref.endsWith(".") ||
1296
+ ref.split("/").some((part) => !part || part.endsWith(".lock"))
1297
+ ) {
1298
+ throw new ValidationError(
1299
+ `Invalid Git ref "${ref}". Use a branch, tag, or commit SHA without spaces or Git ref control characters.`,
1300
+ );
1301
+ }
1302
+ }
1303
+
1304
+ function communityTemplateTrustMessage(selection: string): string | undefined {
1305
+ const community = parseCommunityTemplateSelection(selection, false);
1306
+ if (!community) return undefined;
1307
+ return [
1308
+ `${community.repo} is third-party code and is not reviewed or maintained by Agent Native.`,
1309
+ "The CLI downloads source only; it does not install dependencies or run template scripts.",
1310
+ "Review the generated files before running pnpm install.",
1311
+ community.ref
1312
+ ? `Requested ref: ${community.ref}`
1313
+ : "No ref supplied; the CLI will try main, then master.",
1314
+ ].join("\n");
1315
+ }
1316
+
1317
+ function communityTemplateDownloadMessage(
1318
+ selection: string,
1319
+ ): string | undefined {
1320
+ const community = parseCommunityTemplateSelection(selection, false);
1321
+ return community
1322
+ ? `Downloading community template ${community.repo} from GitHub...`
1323
+ : undefined;
1324
+ }
1325
+
1326
+ function showCommunityTemplateTrustNote(
1327
+ selection: string,
1328
+ clack: typeof import("@clack/prompts"),
1329
+ ): void {
1330
+ const message = communityTemplateTrustMessage(selection);
1331
+ if (message) clack.note(message, "Community template — review before use");
1332
+ }
1333
+
927
1334
  /**
928
1335
  * Find a local packages/<name> directory (for framework development).
929
1336
  * Returns undefined when running as a published npm package.
@@ -1091,13 +1498,17 @@ function postProcessStandalone(
1091
1498
  ): void {
1092
1499
  const appTitle = appTitleForScaffold(name);
1093
1500
  replacePlaceholders(targetDir, name, appTitle);
1094
- rewriteTrackingAppId(targetDir, name, templateName);
1095
- rewriteAgentChatAppId(targetDir, name, templateName);
1501
+ applyScaffoldIdentity(
1502
+ targetDir,
1503
+ name,
1504
+ templateName,
1505
+ resolution?.sourceIdentity,
1506
+ );
1096
1507
  fixPackageJsonName(targetDir, name, templateName, {
1097
1508
  ...resolution,
1098
1509
  shape: "standalone",
1099
1510
  });
1100
- fixWebManifestName(targetDir, name, templateName);
1511
+ fixWebManifestName(targetDir, name, templateName, resolution?.sourceIdentity);
1101
1512
  rewriteNetlifyToml(targetDir, name, "standalone");
1102
1513
 
1103
1514
  for (const base of ["learnings"]) {
@@ -1443,6 +1854,16 @@ export {
1443
1854
  findLocalTemplateFrom as _findLocalTemplateFrom,
1444
1855
  workspaceAppNameForTemplateSelection as _workspaceAppNameForTemplateSelection,
1445
1856
  startShapePromptOptions as _startShapePromptOptions,
1857
+ standaloneTemplatePromptOptions as _standaloneTemplatePromptOptions,
1858
+ parseCommunityTemplateSelection as _parseCommunityTemplateSelection,
1859
+ communityTemplateTrustMessage as _communityTemplateTrustMessage,
1860
+ communityTemplateTarballUrl as _communityTemplateTarballUrl,
1861
+ assertCommunityTemplateRoot as _assertCommunityTemplateRoot,
1862
+ assertSafeCommunityArchiveListing as _assertSafeCommunityArchiveListing,
1863
+ validateCommunityArchive as _validateCommunityArchive,
1864
+ resolveCommunityTemplateSource as _resolveCommunityTemplateSource,
1865
+ discoverCommunityWorkspaceApps as _discoverCommunityWorkspaceApps,
1866
+ normalizeCommunityWorkspaceAppDependencies as _normalizeCommunityWorkspaceAppDependencies,
1446
1867
  shouldSkipScaffoldEntry as _shouldSkipScaffoldEntry,
1447
1868
  tarExtractArgs as _tarExtractArgs,
1448
1869
  downloadGitHubSubdir as _downloadGitHubSubdir,
@@ -1453,6 +1874,7 @@ export {
1453
1874
  replacePlaceholders as _replacePlaceholders,
1454
1875
  rewriteTrackingAppId as _rewriteTrackingAppId,
1455
1876
  rewriteAgentChatAppId as _rewriteAgentChatAppId,
1877
+ applyScaffoldIdentity as _applyScaffoldIdentity,
1456
1878
  fixWebManifestName as _fixWebManifestName,
1457
1879
  copyDir as _copyDir,
1458
1880
  localTemplateSourceKind as _localTemplateSourceKind,
@@ -1465,7 +1887,11 @@ export {
1465
1887
  * ───────────────────────────────────────────────────────────────────────── */
1466
1888
 
1467
1889
  function validateRepoName(repo: string): void {
1468
- if (!/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo)) {
1890
+ const parts = repo.split("/");
1891
+ if (
1892
+ !/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(repo) ||
1893
+ parts.some((part) => part === "." || part === "..")
1894
+ ) {
1469
1895
  throw new ValidationError(
1470
1896
  `Invalid repository name "${repo}". Expected format: user/repo`,
1471
1897
  );
@@ -1475,7 +1901,10 @@ function validateRepoName(repo: string): void {
1475
1901
  function tarExtractArgs(
1476
1902
  tarPath: string,
1477
1903
  destDir: string,
1478
- options: { skipAgentSymlinks?: boolean } = {},
1904
+ options: {
1905
+ skipAgentSymlinks?: boolean;
1906
+ untrustedCommunityArchive?: boolean;
1907
+ } = {},
1479
1908
  ): string[] {
1480
1909
  const excludes = options.skipAgentSymlinks
1481
1910
  ? FIRST_PARTY_TARBALL_SYMLINK_EXCLUDES.flatMap((pattern) => [
@@ -1483,7 +1912,18 @@ function tarExtractArgs(
1483
1912
  pattern,
1484
1913
  ])
1485
1914
  : [];
1486
- return ["xzf", tarPath, "--strip-components=1", ...excludes, "-C", destDir];
1915
+ const safeOwnership = options.untrustedCommunityArchive
1916
+ ? ["--no-same-owner", "--no-same-permissions"]
1917
+ : [];
1918
+ return [
1919
+ "xzf",
1920
+ tarPath,
1921
+ "--strip-components=1",
1922
+ ...safeOwnership,
1923
+ ...excludes,
1924
+ "-C",
1925
+ destDir,
1926
+ ];
1487
1927
  }
1488
1928
 
1489
1929
  function execFileBuffer(
@@ -1512,7 +1952,10 @@ function execFileBuffer(
1512
1952
  async function downloadAndExtract(
1513
1953
  url: string,
1514
1954
  destDir: string,
1515
- options: { skipAgentSymlinks?: boolean } = {},
1955
+ options: {
1956
+ skipAgentSymlinks?: boolean;
1957
+ untrustedCommunityArchive?: boolean;
1958
+ } = {},
1516
1959
  ): Promise<void> {
1517
1960
  fs.mkdirSync(destDir, { recursive: true });
1518
1961
  // --fail-with-body so curl exits non-zero on HTTP 4xx/5xx instead of writing
@@ -1536,6 +1979,9 @@ async function downloadAndExtract(
1536
1979
  const tarPath = path.join(destDir, ".download.tar.gz");
1537
1980
  fs.writeFileSync(tarPath, tarball);
1538
1981
  try {
1982
+ if (options.untrustedCommunityArchive) {
1983
+ validateCommunityArchive(tarPath);
1984
+ }
1539
1985
  execFileSync("tar", tarExtractArgs(tarPath, destDir, options), {
1540
1986
  stdio: "pipe",
1541
1987
  });
@@ -1544,6 +1990,48 @@ async function downloadAndExtract(
1544
1990
  }
1545
1991
  }
1546
1992
 
1993
+ function validateCommunityArchive(tarPath: string): void {
1994
+ const listing = execFileSync("tar", ["tvzf", tarPath], {
1995
+ encoding: "utf-8",
1996
+ stdio: ["ignore", "pipe", "pipe"],
1997
+ });
1998
+ assertSafeCommunityArchiveListing(listing);
1999
+ }
2000
+
2001
+ function assertSafeCommunityArchiveListing(listing: string): void {
2002
+ const unsafeEntry = listing.split(/\r?\n/).find((line) => {
2003
+ if (line.startsWith("h")) return true;
2004
+ if (!line.startsWith("l")) return false;
2005
+ return !isCanonicalAgentSymlinkListing(line);
2006
+ });
2007
+ if (unsafeEntry) {
2008
+ throw new ValidationError(
2009
+ "Community template archives may only contain Agent Native's canonical internal symlinks (CLAUDE.md and .claude/skills). Remove other symbolic or hard links and try again.",
2010
+ );
2011
+ }
2012
+ }
2013
+
2014
+ function isCanonicalAgentSymlinkListing(line: string): boolean {
2015
+ const match = line.match(/\s(\S+)\s+->\s+(\S+)\s*$/);
2016
+ if (!match) return false;
2017
+ const archivePath = match[1]!;
2018
+ const target = match[2]!;
2019
+ const parts = archivePath.split("/");
2020
+ if (
2021
+ parts.some(
2022
+ (part) => !part || part === "." || part === ".." || part.includes("\\"),
2023
+ )
2024
+ ) {
2025
+ return false;
2026
+ }
2027
+ return (
2028
+ (parts.at(-1) === "CLAUDE.md" && target === "AGENTS.md") ||
2029
+ (parts.at(-2) === ".claude" &&
2030
+ parts.at(-1) === "skills" &&
2031
+ target === "../.agents/skills")
2032
+ );
2033
+ }
2034
+
1547
2035
  /** Resolves to the ref that actually succeeded so callers can record it. */
1548
2036
  async function downloadGitHubSubdir(
1549
2037
  repo: string,
@@ -1596,10 +2084,480 @@ async function downloadGitHubSubdir(
1596
2084
  async function downloadGitHubRepo(
1597
2085
  repo: string,
1598
2086
  targetDir: string,
1599
- ): Promise<void> {
2087
+ requestedRef?: string,
2088
+ ): Promise<string> {
1600
2089
  validateRepoName(repo);
1601
- const tarUrl = githubTarballUrl(repo, "main", "branch");
1602
- await downloadAndExtract(tarUrl, targetDir);
2090
+ const refs = requestedRef ? [requestedRef] : ["main", "master"];
2091
+ const errors: string[] = [];
2092
+ for (const ref of refs) {
2093
+ try {
2094
+ await downloadAndExtract(
2095
+ communityTemplateTarballUrl(repo, ref),
2096
+ targetDir,
2097
+ { untrustedCommunityArchive: true },
2098
+ );
2099
+ return ref;
2100
+ } catch (error) {
2101
+ errors.push(
2102
+ ` ${ref}: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`,
2103
+ );
2104
+ cleanupOnFailure(targetDir);
2105
+ }
2106
+ }
2107
+ throw new Error(
2108
+ `Failed to download community template ${repo}. Tried refs:\n${errors.join("\n")}\nThe repository must be public, and the requested branch, tag, or commit must exist.`,
2109
+ );
2110
+ }
2111
+
2112
+ function communityTemplateTarballUrl(repo: string, ref: string): string {
2113
+ return `https://codeload.github.com/${repo}/tar.gz/${encodeURIComponent(ref)}`;
2114
+ }
2115
+
2116
+ function assertCommunityTemplateRoot(targetDir: string, repo: string): void {
2117
+ const packagePath = path.join(targetDir, "package.json");
2118
+ if (!fs.existsSync(packagePath)) {
2119
+ throw new ValidationError(
2120
+ `Community template ${repo} is not an Agent Native app at the repository root: package.json was not found. Point to a repository whose root is the app.`,
2121
+ );
2122
+ }
2123
+
2124
+ let pkg: Record<string, unknown>;
2125
+ try {
2126
+ pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
2127
+ } catch {
2128
+ throw new ValidationError(
2129
+ `Community template ${repo} has an unreadable package.json at the repository root.`,
2130
+ );
2131
+ }
2132
+ const dependencyGroups = [
2133
+ pkg.dependencies,
2134
+ pkg.devDependencies,
2135
+ pkg.peerDependencies,
2136
+ ];
2137
+ const usesAgentNativeCore = dependencyGroups.some(
2138
+ (group) =>
2139
+ group !== null &&
2140
+ typeof group === "object" &&
2141
+ !Array.isArray(group) &&
2142
+ typeof (group as Record<string, unknown>)["@agent-native/core"] ===
2143
+ "string",
2144
+ );
2145
+ if (!usesAgentNativeCore) {
2146
+ throw new ValidationError(
2147
+ `Community template ${repo} is not an Agent Native app at the repository root. Its package.json must directly depend on @agent-native/core in dependencies, devDependencies, or peerDependencies.`,
2148
+ );
2149
+ }
2150
+ }
2151
+
2152
+ interface ResolvedCommunityTemplateSource {
2153
+ sourceDir: string;
2154
+ sourceIdentity: ScaffoldSourceIdentity;
2155
+ app?: string;
2156
+ workspaceRoot?: string;
2157
+ }
2158
+
2159
+ async function resolveCommunityTemplateSource(
2160
+ stagingDir: string,
2161
+ selection: CommunityTemplateSelection,
2162
+ options: ScaffoldAppTemplateOptions = {},
2163
+ ): Promise<ResolvedCommunityTemplateSource> {
2164
+ const rootPkg = readPackageJsonObject(path.join(stagingDir, "package.json"));
2165
+ const agentNative =
2166
+ rootPkg?.["agent-native"] &&
2167
+ typeof rootPkg["agent-native"] === "object" &&
2168
+ !Array.isArray(rootPkg["agent-native"])
2169
+ ? (rootPkg["agent-native"] as Record<string, unknown>)
2170
+ : undefined;
2171
+ const hasWorkspaceCoreMarker =
2172
+ agentNative && Object.hasOwn(agentNative, "workspaceCore");
2173
+ const workspaceCore = agentNative?.workspaceCore;
2174
+ if (
2175
+ hasWorkspaceCoreMarker &&
2176
+ (typeof workspaceCore !== "string" || !workspaceCore.trim())
2177
+ ) {
2178
+ throw new ValidationError(
2179
+ `Community repository ${selection.repo} has invalid agent-native.workspaceCore metadata. Expected a non-empty workspace package name.`,
2180
+ );
2181
+ }
2182
+ const isWorkspace = typeof workspaceCore === "string";
2183
+
2184
+ if (!isWorkspace) {
2185
+ assertCommunityTemplateRoot(stagingDir, selection.repo);
2186
+ const sourceIdentity = readCommunitySourceIdentity(
2187
+ stagingDir,
2188
+ selection.repo,
2189
+ );
2190
+ if (selection.app) {
2191
+ throw new ValidationError(
2192
+ `Community repository ${selection.repo} is a single app at its root. Remove ?app=${selection.app}; app selectors are only for workspace repositories.`,
2193
+ );
2194
+ }
2195
+ return { sourceDir: stagingDir, sourceIdentity };
2196
+ }
2197
+
2198
+ const workspaceApps = discoverCommunityWorkspaceApps(stagingDir);
2199
+ if (workspaceApps.length === 0) {
2200
+ throw new ValidationError(
2201
+ `Community workspace ${selection.repo} has no installable apps. Dispatch cannot be installed from a community workspace, and apps must directly depend on @agent-native/core.`,
2202
+ );
2203
+ }
2204
+
2205
+ let selectedApp = selection.app;
2206
+ if (!selectedApp) {
2207
+ if (!options.selectCommunityWorkspaceApp) {
2208
+ throw new ValidationError(
2209
+ `Community workspace ${selection.repo} requires an app selection, but no interactive terminal is available. Select one with ?app=<app-name>. Available apps: ${workspaceApps.map((app) => app.name).join(", ")}.`,
2210
+ );
2211
+ } else {
2212
+ selectedApp = await options.selectCommunityWorkspaceApp(
2213
+ workspaceApps.map(({ name, label }) => ({ name, label })),
2214
+ );
2215
+ validateCommunityAppSelector(selectedApp);
2216
+ }
2217
+ }
2218
+ const selected = workspaceApps.find((app) => app.name === selectedApp);
2219
+ if (!selected) {
2220
+ throw new ValidationError(
2221
+ `App "${selectedApp}" was not found in community workspace ${selection.repo}. Available apps: ${workspaceApps.map((app) => app.name).join(", ")}.`,
2222
+ );
2223
+ }
2224
+ return {
2225
+ sourceDir: selected.dir,
2226
+ sourceIdentity: selected.sourceIdentity,
2227
+ app: selected.name,
2228
+ workspaceRoot: stagingDir,
2229
+ };
2230
+ }
2231
+
2232
+ interface DiscoveredCommunityWorkspaceApp extends CommunityWorkspaceAppOption {
2233
+ dir: string;
2234
+ sourceIdentity: ScaffoldSourceIdentity;
2235
+ }
2236
+
2237
+ function discoverCommunityWorkspaceApps(
2238
+ workspaceRoot: string,
2239
+ ): DiscoveredCommunityWorkspaceApp[] {
2240
+ const appsDir = path.join(workspaceRoot, "apps");
2241
+ if (!fs.existsSync(appsDir)) return [];
2242
+ return fs
2243
+ .readdirSync(appsDir, { withFileTypes: true })
2244
+ .filter((entry) => entry.isDirectory())
2245
+ .flatMap((entry): DiscoveredCommunityWorkspaceApp[] => {
2246
+ const appDir = path.join(appsDir, entry.name);
2247
+ const packagePath = path.join(appDir, "package.json");
2248
+ if (!fs.existsSync(packagePath)) return [];
2249
+ const pkg = readPackageJsonObject(packagePath);
2250
+ if (!pkg) {
2251
+ throw new ValidationError(
2252
+ `Community workspace app "${entry.name}" has an unreadable package.json.`,
2253
+ );
2254
+ }
2255
+ if (getWorkspaceAppIdValidationError(entry.name)) return [];
2256
+ if (!packageDirectlyDependsOnAgentNativeCore(pkg)) return [];
2257
+ const sourceIdentity = readCommunitySourceIdentity(
2258
+ appDir,
2259
+ `${path.basename(workspaceRoot)}/${entry.name}`,
2260
+ );
2261
+ const packageName =
2262
+ sourceIdentity.appName.split("/").pop() ?? sourceIdentity.appName;
2263
+ if (packageName === "dispatch") return [];
2264
+ return [
2265
+ {
2266
+ name: entry.name,
2267
+ label: sourceIdentity.appTitle,
2268
+ dir: appDir,
2269
+ sourceIdentity,
2270
+ },
2271
+ ];
2272
+ })
2273
+ .sort((a, b) => a.name.localeCompare(b.name));
2274
+ }
2275
+
2276
+ function readPackageJsonObject(
2277
+ packagePath: string,
2278
+ ): Record<string, unknown> | undefined {
2279
+ try {
2280
+ const value = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
2281
+ return value && typeof value === "object" && !Array.isArray(value)
2282
+ ? value
2283
+ : undefined;
2284
+ } catch {
2285
+ return undefined;
2286
+ }
2287
+ }
2288
+
2289
+ function packageDirectlyDependsOnAgentNativeCore(
2290
+ pkg: Record<string, unknown>,
2291
+ ): boolean {
2292
+ return [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies].some(
2293
+ (group) =>
2294
+ group !== null &&
2295
+ typeof group === "object" &&
2296
+ !Array.isArray(group) &&
2297
+ typeof (group as Record<string, unknown>)["@agent-native/core"] ===
2298
+ "string",
2299
+ );
2300
+ }
2301
+
2302
+ function normalizeCommunityWorkspaceAppDependencies(
2303
+ appDir: string,
2304
+ sourceWorkspaceRoot: string,
2305
+ options: {
2306
+ shape?: "standalone" | "workspace";
2307
+ destinationAppName: string;
2308
+ targetWorkspaceCoreName?: string;
2309
+ sourceIdentity: ScaffoldSourceIdentity;
2310
+ },
2311
+ ): void {
2312
+ const appPackagePath = path.join(appDir, "package.json");
2313
+ const pkg = readPackageJsonObject(appPackagePath);
2314
+ if (!pkg) {
2315
+ throw new ValidationError(
2316
+ "Selected community workspace app has an unreadable package.json.",
2317
+ );
2318
+ }
2319
+ const rootPkg = readPackageJsonObject(
2320
+ path.join(sourceWorkspaceRoot, "package.json"),
2321
+ );
2322
+ const agentNative =
2323
+ rootPkg?.["agent-native"] &&
2324
+ typeof rootPkg["agent-native"] === "object" &&
2325
+ !Array.isArray(rootPkg["agent-native"])
2326
+ ? (rootPkg["agent-native"] as Record<string, unknown>)
2327
+ : undefined;
2328
+ const sourceWorkspaceCore =
2329
+ typeof agentNative?.workspaceCore === "string"
2330
+ ? agentNative.workspaceCore
2331
+ : undefined;
2332
+ const catalog = loadCommunityWorkspaceCatalog(sourceWorkspaceRoot);
2333
+ const supportedWorkspacePackages = new Set([
2334
+ "@agent-native/core",
2335
+ "@agent-native/dispatch",
2336
+ "@agent-native/toolkit",
2337
+ ]);
2338
+
2339
+ for (const depType of [
2340
+ "dependencies",
2341
+ "devDependencies",
2342
+ "peerDependencies",
2343
+ ] as const) {
2344
+ const deps = pkg[depType];
2345
+ if (!deps || typeof deps !== "object" || Array.isArray(deps)) continue;
2346
+ for (const [key, value] of Object.entries(
2347
+ deps as Record<string, unknown>,
2348
+ )) {
2349
+ if (key === sourceWorkspaceCore) {
2350
+ delete (deps as Record<string, unknown>)[key];
2351
+ continue;
2352
+ }
2353
+ if (value === "catalog:") {
2354
+ const resolved = catalog[key];
2355
+ if (!resolved) {
2356
+ throw new ValidationError(
2357
+ `Community workspace app dependency "${key}" uses catalog:, but no value was found in the source pnpm-workspace.yaml catalog.`,
2358
+ );
2359
+ }
2360
+ (deps as Record<string, unknown>)[key] = resolved;
2361
+ continue;
2362
+ }
2363
+ if (typeof value === "string" && value.startsWith("catalog:")) {
2364
+ throw new ValidationError(
2365
+ `Community workspace app dependency "${key}" uses unsupported named catalog reference "${value}". Use a concrete version or the default catalog.`,
2366
+ );
2367
+ }
2368
+ if (
2369
+ typeof value === "string" &&
2370
+ value.startsWith("workspace:") &&
2371
+ supportedWorkspacePackages.has(key)
2372
+ ) {
2373
+ (deps as Record<string, unknown>)[key] =
2374
+ key === "@agent-native/core"
2375
+ ? getCoreDependencyVersion()
2376
+ : key === "@agent-native/dispatch"
2377
+ ? getDispatchDependencyVersion()
2378
+ : getToolkitDependencyVersion();
2379
+ continue;
2380
+ }
2381
+ if (typeof value === "string" && value.startsWith("workspace:")) {
2382
+ throw new ValidationError(
2383
+ `Community workspace app dependency "${key}" uses "${value}", but that source workspace package is not included when installing one app. Publish it with a concrete version or remove the dependency.`,
2384
+ );
2385
+ }
2386
+ }
2387
+ }
2388
+ fs.writeFileSync(appPackagePath, JSON.stringify(pkg, null, 2) + "\n");
2389
+ if (sourceWorkspaceCore) {
2390
+ rewriteCanonicalCommunityWorkspacePlugins(
2391
+ appDir,
2392
+ sourceWorkspaceCore,
2393
+ options,
2394
+ );
2395
+ }
2396
+ if (
2397
+ sourceWorkspaceCore &&
2398
+ findSourceImportSpecifier(appDir, sourceWorkspaceCore)
2399
+ ) {
2400
+ throw new ValidationError(
2401
+ `Community workspace app imports its source shared package "${sourceWorkspaceCore}", which is not included when installing one app. Move that code into the app or publish it as a concrete dependency.`,
2402
+ );
2403
+ }
2404
+ }
2405
+
2406
+ function rewriteCanonicalCommunityWorkspacePlugins(
2407
+ appDir: string,
2408
+ sourceWorkspaceCore: string,
2409
+ options: {
2410
+ shape?: "standalone" | "workspace";
2411
+ destinationAppName: string;
2412
+ targetWorkspaceCoreName?: string;
2413
+ sourceIdentity: ScaffoldSourceIdentity;
2414
+ },
2415
+ ): void {
2416
+ const authPath = path.join(appDir, "server", "plugins", "auth.ts");
2417
+ const agentChatPath = path.join(appDir, "server", "plugins", "agent-chat.ts");
2418
+ if (options.shape === "standalone") {
2419
+ if (fs.existsSync(authPath)) {
2420
+ const auth = fs.readFileSync(authPath, "utf-8");
2421
+ if (
2422
+ containsModuleSpecifier(auth, sourceWorkspaceCore) &&
2423
+ auth.includes("defaultAuthPlugin") &&
2424
+ auth.includes("workspaceServer")
2425
+ ) {
2426
+ fs.writeFileSync(
2427
+ authPath,
2428
+ 'export { defaultAuthPlugin as default } from "@agent-native/core/server";\n',
2429
+ );
2430
+ }
2431
+ }
2432
+ if (fs.existsSync(agentChatPath)) {
2433
+ const agentChat = fs.readFileSync(agentChatPath, "utf-8");
2434
+ if (
2435
+ containsModuleSpecifier(agentChat, sourceWorkspaceCore) &&
2436
+ agentChat.includes("createWorkspaceAgentChatPlugin") &&
2437
+ agentChat.includes("actionsRegistry")
2438
+ ) {
2439
+ fs.writeFileSync(
2440
+ agentChatPath,
2441
+ [
2442
+ 'import { createAgentChatPlugin, loadActionsFromStaticRegistry } from "@agent-native/core/server";',
2443
+ 'import actionsRegistry from "../../.generated/actions-registry.js";',
2444
+ "",
2445
+ "export default createAgentChatPlugin({",
2446
+ ` appId: ${JSON.stringify(options.destinationAppName)},`,
2447
+ " actions: loadActionsFromStaticRegistry(actionsRegistry),",
2448
+ "});",
2449
+ "",
2450
+ ].join("\n"),
2451
+ );
2452
+ }
2453
+ }
2454
+ return;
2455
+ }
2456
+
2457
+ if (options.shape === "workspace" && options.targetWorkspaceCoreName) {
2458
+ for (const pluginPath of [authPath, agentChatPath]) {
2459
+ if (!fs.existsSync(pluginPath)) continue;
2460
+ const content = fs.readFileSync(pluginPath, "utf-8");
2461
+ const next = replaceModuleSpecifierPrefix(
2462
+ content,
2463
+ sourceWorkspaceCore,
2464
+ options.targetWorkspaceCoreName,
2465
+ );
2466
+ if (next !== content) fs.writeFileSync(pluginPath, next);
2467
+ }
2468
+ }
2469
+ }
2470
+
2471
+ function containsModuleSpecifier(content: string, moduleName: string): boolean {
2472
+ return new RegExp(`(["'])${escapeRegExp(moduleName)}(?:/[^"']*)?\\1`).test(
2473
+ content,
2474
+ );
2475
+ }
2476
+
2477
+ function replaceModuleSpecifierPrefix(
2478
+ content: string,
2479
+ sourceModule: string,
2480
+ targetModule: string,
2481
+ ): string {
2482
+ return content.replace(
2483
+ new RegExp(`(["'])${escapeRegExp(sourceModule)}((?:/[^"']*)?)\\1`, "g"),
2484
+ (_match, quote: string, suffix: string) =>
2485
+ `${quote}${targetModule}${suffix}${quote}`,
2486
+ );
2487
+ }
2488
+
2489
+ function findSourceImportSpecifier(
2490
+ dir: string,
2491
+ moduleName: string,
2492
+ ): string | undefined {
2493
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2494
+ if (entry.isSymbolicLink()) continue;
2495
+ const filePath = path.join(dir, entry.name);
2496
+ if (entry.isDirectory()) {
2497
+ const nested = findSourceImportSpecifier(filePath, moduleName);
2498
+ if (nested) return nested;
2499
+ continue;
2500
+ }
2501
+ if (!/\.(?:[cm]?[jt]sx?)$/.test(entry.name)) continue;
2502
+ try {
2503
+ const content = fs.readFileSync(filePath, "utf-8");
2504
+ const escaped = escapeRegExp(moduleName);
2505
+ if (
2506
+ new RegExp(
2507
+ `(?:from\\s*|import\\s*\\(\\s*|import\\s*|require\\s*\\(\\s*)(["'])${escaped}(?:/[^"']*)?\\1`,
2508
+ ).test(content)
2509
+ ) {
2510
+ return filePath;
2511
+ }
2512
+ } catch {
2513
+ // Ignore unreadable source files; package validation reports main issues.
2514
+ }
2515
+ }
2516
+ return undefined;
2517
+ }
2518
+
2519
+ function loadCommunityWorkspaceCatalog(
2520
+ workspaceRoot: string,
2521
+ ): Record<string, string> {
2522
+ const workspacePath = path.join(workspaceRoot, "pnpm-workspace.yaml");
2523
+ if (!fs.existsSync(workspacePath)) return {};
2524
+ const result: Record<string, string> = {};
2525
+ let inCatalog = false;
2526
+ for (const line of fs.readFileSync(workspacePath, "utf-8").split("\n")) {
2527
+ if (/^catalog:\s*$/.test(line)) {
2528
+ inCatalog = true;
2529
+ continue;
2530
+ }
2531
+ if (!inCatalog) continue;
2532
+ if (/^\S/.test(line)) break;
2533
+ const match = line.match(/^\s+"?([^":]+)"?\s*:\s*"?([^"]+)"?\s*$/);
2534
+ if (match) result[match[1]!] = match[2]!;
2535
+ }
2536
+ return result;
2537
+ }
2538
+
2539
+ function readCommunitySourceIdentity(
2540
+ targetDir: string,
2541
+ repo: string,
2542
+ ): ScaffoldSourceIdentity {
2543
+ const fallbackName = repo.split("/").pop() ?? "app";
2544
+ try {
2545
+ const pkg = JSON.parse(
2546
+ fs.readFileSync(path.join(targetDir, "package.json"), "utf-8"),
2547
+ );
2548
+ const appName =
2549
+ typeof pkg.name === "string" && pkg.name.trim()
2550
+ ? pkg.name.trim()
2551
+ : fallbackName;
2552
+ const unscopedName = appName.split("/").pop() ?? appName;
2553
+ const appTitle =
2554
+ typeof pkg.displayName === "string" && pkg.displayName.trim()
2555
+ ? pkg.displayName.trim()
2556
+ : titleCase(unscopedName);
2557
+ return { appName, appTitle };
2558
+ } catch {
2559
+ return { appName: fallbackName, appTitle: titleCase(fallbackName) };
2560
+ }
1603
2561
  }
1604
2562
 
1605
2563
  function githubTarballUrl(
@@ -1730,6 +2688,9 @@ function isChatOnRampTemplate(templateName: string | undefined): boolean {
1730
2688
  function trackingTemplateName(
1731
2689
  templateName: string | undefined,
1732
2690
  ): string | undefined {
2691
+ if (templateName && isCommunityTemplateSelection(templateName)) {
2692
+ return undefined;
2693
+ }
1733
2694
  return templateName === "starter" ? "chat" : templateName;
1734
2695
  }
1735
2696
 
@@ -1767,28 +2728,44 @@ function fixPackageJsonName(
1767
2728
  (isChatOnRampTemplate(templateName) && name !== templateName)
1768
2729
  ) {
1769
2730
  pkg.description = defaultPackageDescriptionForScaffold(name);
2731
+ } else if (
2732
+ provenance?.sourceIdentity &&
2733
+ typeof pkg.description === "string"
2734
+ ) {
2735
+ pkg.description = replaceSourceIdentityText(
2736
+ pkg.description,
2737
+ provenance.sourceIdentity,
2738
+ name,
2739
+ appTitle,
2740
+ );
1770
2741
  }
1771
2742
  const scaffoldGuidance = scaffoldGuidanceForTemplate(templateName);
1772
- if (scaffoldGuidance) {
2743
+ if (scaffoldGuidance || provenance?.communityTemplate) {
1773
2744
  const agentNative =
1774
2745
  pkg["agent-native"] &&
1775
2746
  typeof pkg["agent-native"] === "object" &&
1776
2747
  !Array.isArray(pkg["agent-native"])
1777
2748
  ? pkg["agent-native"]
1778
2749
  : {};
1779
- const coreVersion = getCorePackageVersion();
1780
- agentNative.scaffold = {
1781
- template: trackingTemplateName(templateName),
1782
- frameworkSkills: scaffoldGuidance,
1783
- ...(provenance?.templateRef
1784
- ? { templateRef: provenance.templateRef }
1785
- : {}),
1786
- ...(provenance?.templateSource
1787
- ? { templateSource: provenance.templateSource }
1788
- : {}),
1789
- ...(coreVersion ? { coreVersion } : {}),
1790
- ...(provenance?.shape ? { shape: provenance.shape } : {}),
1791
- };
2750
+ if (scaffoldGuidance) {
2751
+ const coreVersion = getCorePackageVersion();
2752
+ agentNative.scaffold = {
2753
+ template: trackingTemplateName(templateName),
2754
+ frameworkSkills: scaffoldGuidance,
2755
+ ...(provenance?.templateRef
2756
+ ? { templateRef: provenance.templateRef }
2757
+ : {}),
2758
+ ...(provenance?.templateSource
2759
+ ? { templateSource: provenance.templateSource }
2760
+ : {}),
2761
+ ...(coreVersion ? { coreVersion } : {}),
2762
+ ...(provenance?.shape ? { shape: provenance.shape } : {}),
2763
+ };
2764
+ }
2765
+ if (provenance?.communityTemplate) {
2766
+ delete agentNative.scaffold;
2767
+ agentNative.communityTemplate = provenance.communityTemplate;
2768
+ }
1792
2769
  pkg["agent-native"] = agentNative;
1793
2770
  }
1794
2771
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
@@ -1808,8 +2785,14 @@ function fixWebManifestName(
1808
2785
  appDir: string,
1809
2786
  name: string,
1810
2787
  templateName?: string,
2788
+ sourceIdentity?: ScaffoldSourceIdentity,
1811
2789
  ): void {
1812
- if (!isChatOnRampTemplate(templateName) || name === templateName) return;
2790
+ if (
2791
+ (!isChatOnRampTemplate(templateName) || name === templateName) &&
2792
+ !sourceIdentity
2793
+ ) {
2794
+ return;
2795
+ }
1813
2796
  const manifestPath = path.join(appDir, "public", "manifest.json");
1814
2797
  if (!fs.existsSync(manifestPath)) return;
1815
2798
  try {
@@ -1817,7 +2800,14 @@ function fixWebManifestName(
1817
2800
  const appTitle = titleCase(name);
1818
2801
  manifest.name = appTitle;
1819
2802
  manifest.short_name = appTitle;
1820
- if (
2803
+ if (sourceIdentity && typeof manifest.description === "string") {
2804
+ manifest.description = replaceSourceIdentityText(
2805
+ manifest.description,
2806
+ sourceIdentity,
2807
+ name,
2808
+ appTitle,
2809
+ );
2810
+ } else if (
1821
2811
  typeof manifest.description !== "string" ||
1822
2812
  /\b(blank app|starter|chat-first)\b/i.test(manifest.description)
1823
2813
  ) {
@@ -1827,6 +2817,27 @@ function fixWebManifestName(
1827
2817
  } catch {}
1828
2818
  }
1829
2819
 
2820
+ function replaceSourceIdentityText(
2821
+ value: string,
2822
+ sourceIdentity: ScaffoldSourceIdentity,
2823
+ appName: string,
2824
+ appTitle: string,
2825
+ ): string {
2826
+ const unscopedSourceName =
2827
+ sourceIdentity.appName.split("/").pop() ?? sourceIdentity.appName;
2828
+ const replacements = new Map<string, string>([
2829
+ [sourceIdentity.appTitle, appTitle],
2830
+ [sourceIdentity.appName, appName],
2831
+ [unscopedSourceName, appName],
2832
+ ]);
2833
+ const sourceValues = [...replacements.keys()]
2834
+ .filter(Boolean)
2835
+ .sort((a, b) => b.length - a.length);
2836
+ if (sourceValues.length === 0) return value;
2837
+ const pattern = new RegExp(sourceValues.map(escapeRegExp).join("|"), "g");
2838
+ return value.replace(pattern, (match) => replacements.get(match) ?? match);
2839
+ }
2840
+
1830
2841
  function getCoreDependencyVersion(): string {
1831
2842
  if (process.env.AGENT_NATIVE_CREATE_USE_LOCAL_CORE === "1") {
1832
2843
  const localCore = findLocalPackage("core");
@@ -2158,16 +3169,43 @@ function rewriteNetlifyToml(
2158
3169
  } catch {}
2159
3170
  }
2160
3171
 
3172
+ function applyScaffoldIdentity(
3173
+ appDir: string,
3174
+ appName: string,
3175
+ templateName?: string,
3176
+ sourceIdentity?: ScaffoldSourceIdentity,
3177
+ ): void {
3178
+ rewriteTrackingAppId(appDir, appName, templateName, sourceIdentity);
3179
+ rewriteAgentChatAppId(appDir, appName, templateName, sourceIdentity);
3180
+ rewriteAppConfigIdentity(appDir, appName, sourceIdentity);
3181
+ }
3182
+
2161
3183
  function rewriteAgentChatAppId(
2162
3184
  appDir: string,
2163
3185
  appName: string,
2164
3186
  templateName?: string,
3187
+ sourceIdentity?: ScaffoldSourceIdentity,
2165
3188
  ): void {
2166
3189
  const pluginPath = path.join(appDir, "server", "plugins", "agent-chat.ts");
2167
3190
  if (!fs.existsSync(pluginPath)) return;
2168
3191
 
2169
3192
  try {
2170
3193
  const content = fs.readFileSync(pluginPath, "utf-8");
3194
+ if (sourceIdentity) {
3195
+ const next = content
3196
+ .replace(
3197
+ /(createAgentChatPlugin\(\{[\s\S]*?\bappId:\s*)(["'])[^"']+\2/,
3198
+ (_match, prefix: string, quote: string) =>
3199
+ `${prefix}${quote}${appName}${quote}`,
3200
+ )
3201
+ .replace(
3202
+ /(const\s+options\s*=\s*\{[\s\S]*?\bappId:\s*)(["'])[^"']+\2/,
3203
+ (_match, prefix: string, quote: string) =>
3204
+ `${prefix}${quote}${appName}${quote}`,
3205
+ );
3206
+ if (next !== content) fs.writeFileSync(pluginPath, next);
3207
+ return;
3208
+ }
2171
3209
  const sourceAppIds = ["chat", "starter"];
2172
3210
  if (templateName && templateName !== appName) {
2173
3211
  sourceAppIds.push(templateName);
@@ -2192,6 +3230,7 @@ function rewriteTrackingAppId(
2192
3230
  appDir: string,
2193
3231
  appName: string,
2194
3232
  templateName?: string,
3233
+ sourceIdentity?: ScaffoldSourceIdentity,
2195
3234
  ): void {
2196
3235
  const rootPath = path.join(appDir, "app", "root.tsx");
2197
3236
  if (!fs.existsSync(rootPath)) return;
@@ -2199,6 +3238,15 @@ function rewriteTrackingAppId(
2199
3238
  try {
2200
3239
  const content = fs.readFileSync(rootPath, "utf-8");
2201
3240
  const trackedTemplateName = trackingTemplateName(templateName);
3241
+ if (sourceIdentity) {
3242
+ const next = content.replace(
3243
+ /(configureTracking\(\{[\s\S]*?\bapp:\s*)(["'])[^"']+\2/,
3244
+ (_match, prefix: string, quote: string) =>
3245
+ `${prefix}${quote}${appName}${quote}`,
3246
+ );
3247
+ if (next !== content) fs.writeFileSync(rootPath, next);
3248
+ return;
3249
+ }
2202
3250
  const sourceAppIds = ["agent-native-[^\"']+", "\\{\\{APP_NAME\\}\\}"];
2203
3251
  if (templateName && templateName !== appName) {
2204
3252
  sourceAppIds.push(escapeRegExp(templateName));
@@ -2236,6 +3284,32 @@ function rewriteTrackingAppId(
2236
3284
  } catch {}
2237
3285
  }
2238
3286
 
3287
+ function rewriteAppConfigIdentity(
3288
+ appDir: string,
3289
+ appName: string,
3290
+ sourceIdentity?: ScaffoldSourceIdentity,
3291
+ ): void {
3292
+ if (!sourceIdentity) return;
3293
+ const configPath = path.join(appDir, "app", "lib", "app-config.ts");
3294
+ if (!fs.existsSync(configPath)) return;
3295
+ try {
3296
+ const content = fs.readFileSync(configPath, "utf-8");
3297
+ const appTitle = appTitleForScaffold(appName);
3298
+ const next = content
3299
+ .replace(
3300
+ /(const\s+rawAppName\s*=\s*)(["'])[^"']*\2/,
3301
+ (_match, prefix: string, quote: string) =>
3302
+ `${prefix}${quote}${appName}${quote}`,
3303
+ )
3304
+ .replace(
3305
+ /(const\s+rawAppTitle\s*=\s*)(["'])[^"']*\2/,
3306
+ (_match, prefix: string, quote: string) =>
3307
+ `${prefix}${quote}${appTitle}${quote}`,
3308
+ );
3309
+ if (next !== content) fs.writeFileSync(configPath, next);
3310
+ } catch {}
3311
+ }
3312
+
2239
3313
  function escapeRegExp(value: string): string {
2240
3314
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2241
3315
  }