@agent-native/core 0.129.1 → 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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +78 -0
- package/corpus/core/docs/content/cloneable-saas.mdx +114 -0
- package/corpus/core/docs/content/creating-templates.mdx +28 -2
- package/corpus/core/docs/content/syncing-template-changes.mdx +7 -0
- package/corpus/core/docs/content/template-clips-developers.mdx +12 -5
- package/corpus/core/docs/content/template-dispatch-developers.mdx +1 -1
- package/corpus/core/docs/content/template-dispatch.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action-ui.ts +1 -0
- package/corpus/core/src/action.ts +5 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +15 -5
- package/corpus/core/src/agent/harness/runner.ts +5 -0
- package/corpus/core/src/agent/production-agent.ts +33 -0
- package/corpus/core/src/agent/run-loop-with-resume.ts +5 -0
- package/corpus/core/src/agent/run-manager.ts +126 -1
- package/corpus/core/src/agent/thread-data-builder.ts +47 -2
- package/corpus/core/src/agent/types.ts +2 -0
- package/corpus/core/src/chat-threads/store.ts +4 -5
- package/corpus/core/src/cli/create.ts +1139 -65
- package/corpus/core/src/cli/design-connect.ts +3 -7
- package/corpus/core/src/cli/index.ts +5 -3
- package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +83 -47
- package/corpus/core/src/client/AssistantChat.tsx +27 -16
- package/corpus/core/src/client/agent-chat-adapter.ts +24 -0
- package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +17 -2
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/message-components.tsx +104 -1
- package/corpus/core/src/client/chat/run-recovery.tsx +15 -3
- package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +10 -29
- package/corpus/core/src/client/chat/widgets/WorkspaceFileWidget.tsx +71 -0
- package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +22 -0
- package/corpus/core/src/client/chat/widgets/workspace-file-result.ts +49 -0
- package/corpus/core/src/client/embed-auth.ts +2 -0
- package/corpus/core/src/client/org/InvitationBanner.tsx +14 -1
- package/corpus/core/src/client/org/OrgSwitcher.tsx +23 -0
- package/corpus/core/src/client/org/TeamPage.tsx +130 -0
- package/corpus/core/src/client/org/WorkspaceNotice.tsx +85 -0
- package/corpus/core/src/client/org/hooks.ts +14 -0
- package/corpus/core/src/client/org/index.ts +2 -0
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +27 -40
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +134 -51
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +7 -0
- package/corpus/core/src/client/resources/use-resources.ts +6 -0
- package/corpus/core/src/client/use-action.ts +3 -1
- package/corpus/core/src/client/widgets/index.ts +1 -0
- package/corpus/core/src/coding-tools/run-code.ts +2 -1
- package/corpus/core/src/db/client.ts +28 -4
- package/corpus/core/src/deploy/build.ts +31 -3
- package/corpus/core/src/eject/remote-mcp-presets.ts +1 -1
- package/corpus/core/src/index.browser.ts +1 -0
- package/corpus/core/src/index.ts +1 -0
- package/corpus/core/src/integrations/google-docs-poller.ts +3 -0
- package/corpus/core/src/integrations/webhook-handler.ts +28 -11
- package/corpus/core/src/jobs/scheduler.ts +4 -0
- package/corpus/core/src/localization/default-messages.ts +3 -2
- package/corpus/core/src/mcp/build-server.ts +20 -2
- package/corpus/core/src/observability/traces.ts +48 -12
- package/corpus/core/src/org/handlers.ts +52 -1
- package/corpus/core/src/org/migrations.ts +4 -0
- package/corpus/core/src/org/plugin.ts +14 -0
- package/corpus/core/src/org/schema.ts +1 -0
- package/corpus/core/src/org/types.ts +8 -0
- package/corpus/core/src/org/workspace-url.ts +74 -0
- package/corpus/core/src/resources/handlers.ts +35 -5
- package/corpus/core/src/scripts/runner.ts +183 -5
- package/corpus/core/src/server/action-routes.ts +32 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +29 -0
- package/corpus/core/src/server/agent-teams.ts +10 -1
- package/corpus/core/src/server/auth.ts +33 -7
- package/corpus/core/src/server/embed-route.ts +9 -0
- package/corpus/core/src/server/embed-session.ts +62 -3
- package/corpus/core/src/server/open-route.ts +20 -6
- package/corpus/core/src/server/prompts/framework-core-compact.ts +1 -1
- package/corpus/core/src/server/prompts/framework-core.ts +1 -0
- package/corpus/core/src/server/request-context.ts +36 -1
- package/corpus/core/src/shared/embed-auth.ts +1 -0
- package/corpus/core/src/shared/html-document.ts +115 -0
- package/corpus/core/src/shared/index.ts +1 -0
- package/corpus/core/src/sharing/access.ts +8 -1
- package/corpus/core/src/sharing/registry.ts +11 -2
- package/corpus/core/src/workspace-files/actions.ts +100 -0
- package/corpus/core/src/workspace-files/tool.ts +4 -0
- package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +4 -0
- package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +9 -1
- package/corpus/templates/analytics/AGENTS.md +17 -24
- package/corpus/templates/analytics/actions/data-source-status.ts +11 -1
- package/corpus/templates/analytics/app/i18n-data.ts +34 -0
- package/corpus/templates/analytics/app/lib/data-source-status.ts +86 -0
- package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +9 -0
- package/corpus/templates/analytics/app/pages/DataSources.tsx +184 -20
- package/corpus/templates/analytics/changelog/2026-07-28-native-analytics-data-sources-no-longer-show-duplicate-mc.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-29-exports-now-appear-in-chat-with-a-direct-download-instead-of.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-29-queued-chat-prompts-no-longer-resend-after-analytics-navigat.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-29-scheduled-dashboard-emails-load-every-panel-reliably-without.md +6 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +6 -3
- package/corpus/templates/analytics/server/lib/credential-keys.ts +1 -1
- package/corpus/templates/analytics/server/lib/hubspot.ts +1 -1
- package/corpus/templates/analytics/server/lib/provider-credentials.ts +55 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +123 -27
- package/corpus/templates/analytics/server/routes/[...page].get.ts +2 -7
- package/corpus/templates/analytics/vite.config.ts +7 -0
- package/corpus/templates/brain/actions/list-connection-providers.ts +100 -2
- package/corpus/templates/brain/app/lib/brain.ts +10 -0
- package/corpus/templates/brain/app/routes/sources.tsx +15 -4
- package/corpus/templates/brain/changelog/2026-07-29-brain-automatically-retries-failed-source-syncs-and-guides-y.md +6 -0
- package/corpus/templates/brain/jobs/process-ingest-queue.ts +13 -0
- package/corpus/templates/brain/server/jobs/sync-sources.ts +27 -22
- package/corpus/templates/brain/server/plugins/agent-chat.ts +2 -1
- package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +7 -1
- package/corpus/templates/clips/AGENTS.md +1 -1
- package/corpus/templates/clips/actions/create-recording-agent-link.ts +11 -1
- package/corpus/templates/clips/actions/update-comment.ts +68 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +0 -1
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +221 -65
- package/corpus/templates/clips/app/components/player/video-player.tsx +101 -27
- package/corpus/templates/clips/app/i18n/en-US.ts +2 -1
- package/corpus/templates/clips/app/routes/bug-report.done.tsx +48 -27
- package/corpus/templates/clips/app/routes/bug-report.tsx +43 -4
- package/corpus/templates/clips/app/routes/record.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-07-29-clip-playback-now-recovers-instead-of-getting-stuck-when-a-b.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-comments-can-now-be-edited-after-posting.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-download-actions-now-use-a-shorter-clearer-label.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-embedded-bug-reports-now-return-a-temporary-agent-readable.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +21 -1
- package/corpus/templates/clips/shared/bug-report.ts +99 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +7 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +5 -2
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +20 -8
- package/corpus/templates/content/changelog/2026-07-29-missing-databases-no-longer-trigger-repeated-personal-view-e.md +6 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +83 -47
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +474 -55
- package/corpus/templates/design/.generated/bridge/embedded-wheel.generated.ts +3 -0
- package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +18 -1
- package/corpus/templates/design/.generated/bridge/source-location.generated.ts +348 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +2 -7
- package/corpus/templates/design/actions/apply-visual-edit.ts +7 -1
- package/corpus/templates/design/actions/connect-localhost.ts +5 -9
- package/corpus/templates/design/actions/create-component.ts +16 -13
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +2 -7
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +4 -7
- package/corpus/templates/design/actions/list-local-files.ts +26 -50
- package/corpus/templates/design/actions/list-localhost-connections.ts +2 -7
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +2 -2
- package/corpus/templates/design/actions/open-visual-edit.ts +234 -135
- package/corpus/templates/design/actions/read-local-file.ts +26 -50
- package/corpus/templates/design/actions/request-localhost-write-consent.ts +2 -7
- package/corpus/templates/design/actions/write-local-file.ts +31 -73
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +307 -133
- package/corpus/templates/design/app/components/design/EditPanel.tsx +94 -22
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +137 -60
- package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +75 -14
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +755 -97
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +8 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +24 -1
- package/corpus/templates/design/app/components/design/bridge/source-location.bridge.ts +530 -0
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbenchLoader.tsx +278 -0
- package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +32 -12
- package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-setup.ts +14 -3
- package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +87 -24
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +36 -0
- package/corpus/templates/design/app/components/design/code-workbench/store.tsx +2 -1
- package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +48 -8
- package/corpus/templates/design/app/components/design/design-canvas/content-size-report.ts +3 -13
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +4 -2
- package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +3 -13
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +3 -1
- package/corpus/templates/design/app/components/design/edit-panel/inspect-code-source.ts +93 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +4 -2
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +35 -0
- package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +24 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -0
- package/corpus/templates/design/app/components/design/types.ts +29 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +7 -4
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +153 -46
- package/corpus/templates/design/app/i18n-responsive-interact.ts +12 -12
- package/corpus/templates/design/app/lib/design-editor-route.ts +23 -0
- package/corpus/templates/design/app/pages/VisualEdit.tsx +19 -6
- package/corpus/templates/design/app/pages/design-editor/action-error.ts +19 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +29 -0
- package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +6 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +277 -15
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +75 -9
- package/corpus/templates/design/app/pages/design-editor/layer-state-scope.ts +28 -0
- package/corpus/templates/design/app/pages/design-editor/live-screen-layer-drop.ts +41 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +496 -41
- package/corpus/templates/design/app/pages/design-editor/pending-live-edit-unload-guard.ts +27 -0
- package/corpus/templates/design/app/pages/design-editor/pending-structure-verification.ts +10 -0
- package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +168 -9
- package/corpus/templates/design/app/pages/design-editor/responsive-interact.ts +41 -3
- package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +42 -13
- package/corpus/templates/design/app/pages/design-editor/source-location.ts +180 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +17 -0
- package/corpus/templates/design/app/public-routes.ts +1 -0
- package/corpus/templates/design/app/routes/visual-edit.$id.tsx +15 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +1 -1
- package/corpus/templates/design/changelog/2026-07-28-deleting-a-selected-element-now-removes-it-from-the-live-pre.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-deleting-an-element-on-a-connected-localhost-screen-now-queu.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-shows-a-responsive-device-preview-with-pre.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-layer-hide-and-lock-now-apply-to-the-live-preview-instead-of.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-layers-panel-selections-on-a-connected-localhost-screen-can-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-locked-layers-now-show-a-subtle-dashed-outline-in-the-live-p.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-the-apply-design-updates-button-now-counts-pending-live-text.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-the-design-canvas-no-longer-stalls-on-a-render-loop-while-a-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-visual-edit-on-a-local-dev-server-no-longer-requires-signing.md +6 -0
- package/corpus/templates/design/changelog/2026-07-28-visual-edits-on-a-react-19-app-no-longer-report-a-source-lin.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-an-edit-that-cannot-be-written-to-source-now-names-the-reaso.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-applying-a-primitive-dropped-onto-a-connected-localhost-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-changing-a-style-in-the-inspector-now-updates-a-connected-lo.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-choosing-edit-or-annotate-from-a-focused-screen-now-returns-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-concurrent-source-edits-now-return-a-clear-conflict-response.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-creating-a-component-from-an-element-that-repeats-in-the-sou.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-deleting-an-element-you-just-dropped-onto-a-live-screen-no-l.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-dragging-a-layer-across-screens-no-longer-dies-when-the-poin.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-dragging-an-element-from-a-stored-screen-into-a-live-localho.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-drawing-a-box-on-the-canvas-and-dragging-it-into-a-connected.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-fixed-a-loading-screen-flicker-when-reopening-designs-with-a.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-interact-mode-has-edit-and-annotate-buttons-again-so-you-can.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-layer-groups-can-be-dropped-onto-live-localhost-screens.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-localhost-source-errors-now-say-what-actually-went-wrong-whi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-opening-or-refreshing-a-live-localhost-screen-in-single-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-redo-after-undoing-a-drop-onto-a-live-screen-puts-the-elemen.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-shapes-can-be-drawn-directly-onto-live-localhost-screens.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-shapes-drawn-on-the-canvas-board-now-appear-immediately-inst.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-style-edits-on-a-running-localhost-screen-now-preview-live-a.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-the-apply-design-updates-control-now-appears-as-a-clean-conn.md +6 -0
- package/corpus/templates/design/changelog/2026-07-29-visual-edits-on-a-mapped-component-instance-now-tell-the-age.md +6 -0
- package/corpus/templates/design/server/lib/design-data-access.ts +21 -7
- package/corpus/templates/design/server/lib/localhost-connection.ts +240 -0
- package/corpus/templates/design/server/routes/[...page].get.ts +2 -5
- package/corpus/templates/design/server/source-workspace.ts +2 -0
- package/corpus/templates/design/shared/code-layer.ts +43 -0
- package/corpus/templates/design/shared/html-content.ts +26 -0
- package/corpus/templates/design/shared/html-integrity.ts +21 -1
- package/corpus/templates/design/shared/local-jsx-visual-edit.ts +15 -0
- package/corpus/templates/design/shared/source-mode.ts +76 -0
- package/corpus/templates/dispatch/changelog/2026-07-29-workspace-members-without-admin-access-now-see-a-clear-permi.md +6 -0
- package/corpus/templates/dispatch/server/lib/usage-metrics.ts +2 -1
- package/corpus/templates/plan/app/lib/plan-annotation-runtime.ts +3 -4
- package/corpus/templates/plan/server/routes/[...page].get.ts +2 -5
- package/corpus/templates/slides/actions/patch-deck.ts +29 -17
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/ComposerPlusMenu.tsx +3 -2
- package/dist/action-ui.d.ts +1 -0
- package/dist/action-ui.d.ts.map +1 -1
- package/dist/action-ui.js +1 -0
- package/dist/action-ui.js.map +1 -1
- package/dist/action.d.ts +5 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +13 -5
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/harness/runner.d.ts.map +1 -1
- package/dist/agent/harness/runner.js +5 -0
- package/dist/agent/harness/runner.js.map +1 -1
- package/dist/agent/production-agent.d.ts +16 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +6 -0
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-manager.d.ts +14 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +91 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +3 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +35 -3
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +4 -6
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/create.d.ts +66 -6
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +765 -66
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +2 -7
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/index.js +5 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.js +83 -47
- package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +16 -9
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +21 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.d.ts +2 -0
- package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
- package/dist/client/blocks/library/wireframe-kit.js +8 -2
- package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +17 -0
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +24 -2
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts +6 -0
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +13 -3
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -1
- package/dist/client/chat/widgets/DataTableWidget.js +2 -3
- package/dist/client/chat/widgets/DataTableWidget.js.map +1 -1
- package/dist/client/chat/widgets/WorkspaceFileWidget.d.ts +5 -0
- package/dist/client/chat/widgets/WorkspaceFileWidget.d.ts.map +1 -0
- package/dist/client/chat/widgets/WorkspaceFileWidget.js +34 -0
- package/dist/client/chat/widgets/WorkspaceFileWidget.js.map +1 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
- package/dist/client/chat/widgets/builtin-tool-renderers.js +13 -1
- package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
- package/dist/client/chat/widgets/workspace-file-result.d.ts +12 -0
- package/dist/client/chat/widgets/workspace-file-result.d.ts.map +1 -0
- package/dist/client/chat/widgets/workspace-file-result.js +34 -0
- package/dist/client/chat/widgets/workspace-file-result.js.map +1 -0
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +2 -1
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/org/InvitationBanner.d.ts +5 -2
- package/dist/client/org/InvitationBanner.d.ts.map +1 -1
- package/dist/client/org/InvitationBanner.js +9 -2
- package/dist/client/org/InvitationBanner.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +3 -2
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +27 -3
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/org/WorkspaceNotice.d.ts +18 -0
- package/dist/client/org/WorkspaceNotice.d.ts.map +1 -0
- package/dist/client/org/WorkspaceNotice.js +56 -0
- package/dist/client/org/WorkspaceNotice.js.map +1 -0
- package/dist/client/org/hooks.d.ts +1 -0
- package/dist/client/org/hooks.d.ts.map +1 -1
- package/dist/client/org/hooks.js +12 -0
- package/dist/client/org/hooks.js.map +1 -1
- package/dist/client/org/index.d.ts +2 -1
- package/dist/client/org/index.d.ts.map +1 -1
- package/dist/client/org/index.js +2 -1
- package/dist/client/org/index.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +8 -12
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts +3 -0
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +49 -20
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +3 -0
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/resources/use-resources.d.ts +1 -0
- package/dist/client/resources/use-resources.d.ts.map +1 -1
- package/dist/client/resources/use-resources.js +3 -0
- package/dist/client/resources/use-resources.js.map +1 -1
- package/dist/client/use-action.d.ts +3 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +3 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/widgets/index.d.ts +1 -1
- package/dist/client/widgets/index.d.ts.map +1 -1
- package/dist/client/widgets/index.js +1 -1
- package/dist/client/widgets/index.js.map +1 -1
- package/dist/coding-tools/run-code.js +2 -1
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +24 -3
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +25 -3
- package/dist/deploy/build.js.map +1 -1
- package/dist/eject/remote-mcp-presets.js +1 -1
- package/dist/eject/remote-mcp-presets.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +4 -1
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +23 -11
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/jobs/scheduler.js +4 -0
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/localization/default-messages.d.ts +3 -2
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +3 -2
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +10 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +27 -7
- package/dist/observability/traces.js.map +1 -1
- package/dist/org/handlers.d.ts +15 -0
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +45 -1
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/migrations.d.ts.map +1 -1
- package/dist/org/migrations.js +4 -0
- package/dist/org/migrations.js.map +1 -1
- package/dist/org/plugin.d.ts +1 -0
- package/dist/org/plugin.d.ts.map +1 -1
- package/dist/org/plugin.js +10 -1
- package/dist/org/plugin.js.map +1 -1
- package/dist/org/schema.d.ts +19 -0
- package/dist/org/schema.d.ts.map +1 -1
- package/dist/org/schema.js +1 -0
- package/dist/org/schema.js.map +1 -1
- package/dist/org/types.d.ts +8 -0
- package/dist/org/types.d.ts.map +1 -1
- package/dist/org/types.js.map +1 -1
- package/dist/org/workspace-url.d.ts +32 -0
- package/dist/org/workspace-url.d.ts.map +1 -0
- package/dist/org/workspace-url.js +63 -0
- package/dist/org/workspace-url.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -3
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js +25 -5
- package/dist/resources/handlers.js.map +1 -1
- package/dist/scripts/runner.d.ts +17 -0
- package/dist/scripts/runner.d.ts.map +1 -1
- package/dist/scripts/runner.js +130 -5
- package/dist/scripts/runner.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +24 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +26 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +10 -1
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth.d.ts +22 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +33 -7
- package/dist/server/auth.js.map +1 -1
- package/dist/server/embed-route.d.ts.map +1 -1
- package/dist/server/embed-route.js +6 -1
- package/dist/server/embed-route.js.map +1 -1
- package/dist/server/embed-session.d.ts +7 -0
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +46 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/server/open-route.d.ts.map +1 -1
- package/dist/server/open-route.js +17 -7
- package/dist/server/open-route.js.map +1 -1
- package/dist/server/prompts/framework-core-compact.js +1 -1
- package/dist/server/prompts/framework-core-compact.js.map +1 -1
- package/dist/server/prompts/framework-core.d.ts.map +1 -1
- package/dist/server/prompts/framework-core.js +1 -0
- package/dist/server/prompts/framework-core.js.map +1 -1
- package/dist/server/request-context.d.ts +26 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +18 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/shared/embed-auth.d.ts +1 -0
- package/dist/shared/embed-auth.d.ts.map +1 -1
- package/dist/shared/embed-auth.js +1 -0
- package/dist/shared/embed-auth.js.map +1 -1
- package/dist/shared/html-document.d.ts +12 -0
- package/dist/shared/html-document.d.ts.map +1 -0
- package/dist/shared/html-document.js +92 -0
- package/dist/shared/html-document.js.map +1 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/sharing/access.d.ts +1 -0
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +8 -2
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/registry.d.ts +3 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/workspace-files/actions.d.ts +28 -0
- package/dist/workspace-files/actions.d.ts.map +1 -0
- package/dist/workspace-files/actions.js +75 -0
- package/dist/workspace-files/actions.js.map +1 -0
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +4 -0
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/cloneable-saas.mdx +114 -0
- package/docs/content/creating-templates.mdx +28 -2
- package/docs/content/syncing-template-changes.mdx +7 -0
- package/docs/content/template-clips-developers.mdx +12 -5
- package/docs/content/template-dispatch-developers.mdx +1 -1
- package/docs/content/template-dispatch.mdx +1 -1
- package/package.json +2 -2
- package/src/action-ui.ts +1 -0
- package/src/action.ts +5 -1
- package/src/agent/engine/ai-sdk-engine.ts +15 -5
- package/src/agent/harness/runner.ts +5 -0
- package/src/agent/production-agent.ts +33 -0
- package/src/agent/run-loop-with-resume.ts +5 -0
- package/src/agent/run-manager.ts +126 -1
- package/src/agent/thread-data-builder.ts +47 -2
- package/src/agent/types.ts +2 -0
- package/src/chat-threads/store.ts +4 -5
- package/src/cli/create.ts +1139 -65
- package/src/cli/design-connect.ts +3 -7
- package/src/cli/index.ts +5 -3
- package/src/cli/skills-content/design-visual-edit-skill.ts +83 -47
- package/src/client/AssistantChat.tsx +27 -16
- package/src/client/agent-chat-adapter.ts +24 -0
- package/src/client/blocks/library/wireframe-kit.tsx +17 -2
- package/src/client/chat/index.ts +1 -0
- package/src/client/chat/message-components.tsx +104 -1
- package/src/client/chat/run-recovery.tsx +15 -3
- package/src/client/chat/widgets/DataTableWidget.tsx +10 -29
- package/src/client/chat/widgets/WorkspaceFileWidget.tsx +71 -0
- package/src/client/chat/widgets/builtin-tool-renderers.tsx +22 -0
- package/src/client/chat/widgets/workspace-file-result.ts +49 -0
- package/src/client/embed-auth.ts +2 -0
- package/src/client/org/InvitationBanner.tsx +14 -1
- package/src/client/org/OrgSwitcher.tsx +23 -0
- package/src/client/org/TeamPage.tsx +130 -0
- package/src/client/org/WorkspaceNotice.tsx +85 -0
- package/src/client/org/hooks.ts +14 -0
- package/src/client/org/index.ts +2 -0
- package/src/client/resources/McpIntegrationDialog.tsx +27 -40
- package/src/client/resources/ResourcesPanel.tsx +134 -51
- package/src/client/resources/mcp-integration-catalog.ts +7 -0
- package/src/client/resources/use-resources.ts +6 -0
- package/src/client/use-action.ts +3 -1
- package/src/client/widgets/index.ts +1 -0
- package/src/coding-tools/run-code.ts +2 -1
- package/src/db/client.ts +28 -4
- package/src/deploy/build.ts +31 -3
- package/src/eject/remote-mcp-presets.ts +1 -1
- package/src/index.browser.ts +1 -0
- package/src/index.ts +1 -0
- package/src/integrations/google-docs-poller.ts +3 -0
- package/src/integrations/webhook-handler.ts +28 -11
- package/src/jobs/scheduler.ts +4 -0
- package/src/localization/default-messages.ts +3 -2
- package/src/mcp/build-server.ts +20 -2
- package/src/observability/traces.ts +48 -12
- package/src/org/handlers.ts +52 -1
- package/src/org/migrations.ts +4 -0
- package/src/org/plugin.ts +14 -0
- package/src/org/schema.ts +1 -0
- package/src/org/types.ts +8 -0
- package/src/org/workspace-url.ts +74 -0
- package/src/resources/handlers.ts +35 -5
- package/src/scripts/runner.ts +183 -5
- package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +2 -1
- package/src/server/action-routes.ts +32 -2
- package/src/server/agent-chat-plugin.ts +29 -0
- package/src/server/agent-teams.ts +10 -1
- package/src/server/auth.ts +33 -7
- package/src/server/embed-route.ts +9 -0
- package/src/server/embed-session.ts +62 -3
- package/src/server/open-route.ts +20 -6
- package/src/server/prompts/framework-core-compact.ts +1 -1
- package/src/server/prompts/framework-core.ts +1 -0
- package/src/server/request-context.ts +36 -1
- package/src/shared/embed-auth.ts +1 -0
- package/src/shared/html-document.ts +115 -0
- package/src/shared/index.ts +1 -0
- package/src/sharing/access.ts +8 -1
- package/src/sharing/registry.ts +11 -2
- package/src/workspace-files/actions.ts +100 -0
- package/src/workspace-files/tool.ts +4 -0
- package/corpus/templates/design/changelog/2026-07-28-interact-mode-can-preview-designs-in-responsive-device-sizes.md +0 -6
- package/corpus/templates/design/changelog/2026-07-28-interact-mode-now-opens-a-full-screen-responsive-preview-wit.md +0 -6
package/src/cli/create.ts
CHANGED
|
@@ -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: "
|
|
230
|
-
hint: "Clone
|
|
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" &&
|
|
334
|
-
|
|
335
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
const repoName = repo.split("/").
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
:
|
|
868
|
+
: (communityTemplateDownloadMessage(template) ??
|
|
869
|
+
`Downloading the ${template} template from GitHub...`),
|
|
744
870
|
);
|
|
745
871
|
try {
|
|
746
|
-
const resolution = await scaffoldAppTemplate(
|
|
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
|
-
* - "
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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(", ")}
|
|
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
|
-
|
|
1095
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
2087
|
+
requestedRef?: string,
|
|
2088
|
+
): Promise<string> {
|
|
1600
2089
|
validateRepoName(repo);
|
|
1601
|
-
const
|
|
1602
|
-
|
|
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
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
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 (
|
|
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
|
}
|