@agent-native/core 0.102.1 → 0.103.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 +15 -0
- package/corpus/core/docs/content/local-file-mode.mdx +99 -489
- package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/corpus/core/docs/content/template-content.mdx +26 -42
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/content-local.ts +86 -18
- package/corpus/core/src/cli/index.ts +2 -2
- package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
- package/corpus/core/src/cli/skills.ts +47 -13
- package/corpus/core/src/client/AgentPanel.tsx +7 -0
- package/corpus/core/src/client/AssistantChat.tsx +7 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +7 -1
- package/corpus/core/src/client/chat/message-components.tsx +17 -3
- package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +96 -6
- package/corpus/core/src/client/composer/asset-picker-url.ts +80 -0
- package/corpus/core/src/client/error-format.ts +24 -0
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +8 -1
- package/corpus/core/src/local-artifacts/index.ts +65 -0
- package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/assets/app/i18n-data.ts +215 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
- package/corpus/templates/assets/app/routes/library.tsx +69 -0
- package/corpus/templates/assets/changelog/2026-07-15-fixed-top-level-image-picker-selections-returning-to-the-com.md +6 -0
- package/corpus/templates/clips/actions/disconnect-calendar.ts +57 -9
- package/corpus/templates/clips/actions/generate-workflow.ts +102 -59
- package/corpus/templates/clips/actions/get-recording-player-data.ts +64 -1
- package/corpus/templates/clips/actions/regenerate-summary.ts +7 -0
- package/corpus/templates/clips/actions/regenerate-title.ts +29 -11
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +34 -13
- package/corpus/templates/clips/app/components/editor/thumbnail-picker.tsx +87 -9
- package/corpus/templates/clips/app/components/editor/trim-handles.tsx +15 -0
- package/corpus/templates/clips/app/components/editor/waveform.tsx +21 -8
- package/corpus/templates/clips/app/components/library/folder-tree.tsx +52 -0
- package/corpus/templates/clips/app/components/player/settings-panel.tsx +116 -58
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +43 -37
- package/corpus/templates/clips/app/components/player/video-player.tsx +50 -2
- package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +5 -2
- package/corpus/templates/clips/app/i18n/de-DE.ts +6 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +5 -2
- package/corpus/templates/clips/app/i18n/es-ES.ts +6 -2
- package/corpus/templates/clips/app/i18n/fr-FR.ts +5 -2
- package/corpus/templates/clips/app/i18n/hi-IN.ts +5 -2
- package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +5 -2
- package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -2
- package/corpus/templates/clips/app/i18n/zh-CN.ts +5 -2
- package/corpus/templates/clips/app/i18n/zh-TW.ts +5 -2
- package/corpus/templates/clips/app/lib/timestamp-mapping.ts +14 -0
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +1 -2
- package/corpus/templates/clips/app/routes/_index.tsx +1 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +98 -32
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +4 -1
- package/corpus/templates/clips/changelog/2026-07-15-ai-workflow-generation-now-stays-visible-avoids-duplicate-re.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-clip-playback-and-cta-controls-remain-interactive-through-ed.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-clip-titles-no-longer-describe-the-product-as-a-loom-alterna.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-description-regeneration-now-opens-the-agent-chat-so-progres.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-direct-recording-urls-now-respect-share-link-and-password-ac.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-disconnecting-a-calendar-now-clears-unrecorded-synced-meetin.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-folders-now-expose-rename-and-delete-actions-directly-in-the.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-recording-pages-now-explain-sign-in-access-and-avoid-showing.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-the-clips-extension-quick-actions-toolbar-can-be-moved-out-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-the-editor-now-skips-cut-sections-during-preview-and-keeps-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-thumbnail-capture-now-updates-previews-while-scrubbing-and-l.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-windows-permission-links-open-the-matching-settings-page.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +117 -2
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +27 -0
- package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -1
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +139 -101
- package/corpus/templates/clips/server/lib/recording-page-access.ts +35 -0
- package/corpus/templates/clips/server/lib/share-password.ts +6 -3
- package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
- package/corpus/templates/content/AGENTS.md +25 -30
- package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
- package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
- package/corpus/templates/content/actions/_content-files.ts +338 -0
- package/corpus/templates/content/actions/_content-space-access.ts +135 -0
- package/corpus/templates/content/actions/_content-spaces.ts +640 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
- package/corpus/templates/content/actions/_database-utils.ts +7 -1
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
- package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
- package/corpus/templates/content/actions/_property-utils.ts +9 -4
- package/corpus/templates/content/actions/add-database-item.ts +108 -65
- package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
- package/corpus/templates/content/actions/create-content-database.ts +129 -3
- package/corpus/templates/content/actions/create-document.ts +65 -55
- package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
- package/corpus/templates/content/actions/delete-content-database.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +7 -11
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
- package/corpus/templates/content/actions/edit-document.ts +2 -28
- package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
- package/corpus/templates/content/actions/export-content-source.ts +47 -9
- package/corpus/templates/content/actions/get-document.ts +0 -14
- package/corpus/templates/content/actions/import-content-source.ts +43 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
- package/corpus/templates/content/actions/list-documents.ts +12 -9
- package/corpus/templates/content/actions/move-document.ts +3 -14
- package/corpus/templates/content/actions/pull-document.ts +0 -23
- package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
- package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
- package/corpus/templates/content/actions/search-documents.ts +0 -34
- package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
- package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
- package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
- package/corpus/templates/content/actions/update-document.ts +0 -15
- package/corpus/templates/content/actions/view-screen.ts +2 -50
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
- package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
- package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
- package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
- package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
- package/corpus/templates/content/app/i18n-data.ts +75 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
- package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +37 -1
- package/corpus/templates/content/scripts/dev-database.mjs +7 -5
- package/corpus/templates/content/server/db/schema.ts +113 -40
- package/corpus/templates/content/server/lib/document-context.ts +15 -1
- package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
- package/corpus/templates/content/server/plugins/db.ts +153 -0
- package/corpus/templates/content/shared/api.ts +17 -3
- package/corpus/templates/content/shared/builder-mdx.ts +163 -16
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
- package/corpus/templates/design/AGENTS.md +6 -1
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
- package/corpus/templates/design/actions/list-design-templates.ts +69 -21
- package/corpus/templates/design/actions/navigate.ts +2 -2
- package/corpus/templates/design/actions/view-screen.ts +15 -2
- package/corpus/templates/design/agent-native.app-skill.json +5 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +7 -3
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
- package/corpus/templates/design/app/i18n-data.ts +21 -2
- package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
- package/corpus/templates/design/app/pages/Index.tsx +195 -24
- package/corpus/templates/design/app/pages/Templates.tsx +144 -55
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
- package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-prototype-print-and-download-buttons-now-work-in-preview.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/mail/AGENTS.md +23 -18
- package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
- package/corpus/templates/mail/actions/mark-read.ts +131 -20
- package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
- package/corpus/templates/mail/actions/move-email.ts +30 -24
- package/corpus/templates/mail/actions/send-email.ts +73 -68
- package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +218 -209
- package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
- package/corpus/templates/mail/server/lib/email-state.ts +198 -113
- package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
- package/corpus/templates/mail/server/lib/jobs.ts +69 -74
- package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/corpus/templates/slides/actions/extract-pdf.ts +1 -1
- package/corpus/templates/slides/actions/import-file.ts +4 -1
- package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/ImageSearchPanel.tsx +9 -12
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -21
- package/corpus/templates/slides/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/slides/changelog/2026-07-15-image-search-and-generation-controls-now-use-the-registered-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-15-pdf-deck-references-can-be-imported-for-slide-inspiration-ag.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-15-stable-style-inspector-dock.md +6 -0
- package/dist/cli/content-local.d.ts +1 -0
- package/dist/cli/content-local.d.ts.map +1 -1
- package/dist/cli/content-local.js +75 -17
- package/dist/cli/content-local.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills-content/content-skill.d.ts +1 -1
- package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/content-skill.js +36 -25
- package/dist/cli/skills-content/content-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +47 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +5 -0
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -0
- 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 +8 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +4 -0
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +10 -3
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.js +58 -6
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/composer/asset-picker-url.d.ts +16 -0
- package/dist/client/composer/asset-picker-url.d.ts.map +1 -0
- package/dist/client/composer/asset-picker-url.js +64 -0
- package/dist/client/composer/asset-picker-url.js.map +1 -0
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +17 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +6 -1
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/local-artifacts/index.d.ts +10 -0
- package/dist/local-artifacts/index.d.ts.map +1 -1
- package/dist/local-artifacts/index.js +39 -0
- package/dist/local-artifacts/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/local-file-mode.mdx +99 -489
- package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/docs/content/template-content.mdx +26 -42
- package/package.json +1 -1
- package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
|
@@ -3,7 +3,7 @@ name: content
|
|
|
3
3
|
description: >-
|
|
4
4
|
Use Content for repo-backed Markdown/MDX docs, blogs, resources, rich
|
|
5
5
|
document editing, Notion-style databases and boards, structured intake
|
|
6
|
-
forms, local components, shareable copies, and
|
|
6
|
+
forms, local components, shareable copies, and connected local folders.
|
|
7
7
|
Prefer Content actions over raw filesystem writes when available.
|
|
8
8
|
metadata:
|
|
9
9
|
visibility: exported
|
|
@@ -17,7 +17,7 @@ marketing pages, internal notes, and local MDX components. Also use Content for
|
|
|
17
17
|
Notion-style databases, tables, boards, structured request intake, and forms
|
|
18
18
|
whose submissions become database row pages. Content gives the agent a document
|
|
19
19
|
tree, a rich editor, structured database properties and views, normal document
|
|
20
|
-
actions, and
|
|
20
|
+
actions, and local folders that sync into the same database model.
|
|
21
21
|
|
|
22
22
|
## Choose The Path
|
|
23
23
|
|
|
@@ -33,9 +33,9 @@ actions, and an optional local-file source of truth.
|
|
|
33
33
|
its exact title, then inspect it with `get-content-database` before creating
|
|
34
34
|
or submitting anything. Do not create a second database when the canonical
|
|
35
35
|
one already exists.
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
- Local folders are sources attached to a space's canonical Files database.
|
|
37
|
+
Imported pages are normal SQL-backed Content documents; the trusted local
|
|
38
|
+
bridge handles pull, export, stable file identity, and conflict review.
|
|
39
39
|
- If Content tools are not visible and no local Content app or Desktop bridge is
|
|
40
40
|
running, treat this skill as repo-editing guidance. Edit configured
|
|
41
41
|
`.md`/`.mdx` files directly, preserve frontmatter and MDX imports, and tell
|
|
@@ -47,10 +47,10 @@ Prefer JSON input for action calls:
|
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
pnpm action list-documents
|
|
50
|
-
pnpm action get-document '{"id":"
|
|
51
|
-
pnpm action edit-document '{"id":"
|
|
52
|
-
pnpm action update-document '{"id":"
|
|
53
|
-
pnpm action
|
|
50
|
+
pnpm action get-document '{"id":"<document-id>"}'
|
|
51
|
+
pnpm action edit-document '{"id":"<document-id>","find":"old copy","replace":"new copy"}'
|
|
52
|
+
pnpm action update-document '{"id":"<document-id>","content":"# Updated\n\nBody"}'
|
|
53
|
+
pnpm action connect-local-folder-source '{"connectionId":"<opaque-bridge-id>","label":"Docs","createSourceBackedSpace":true,"truthPolicy":"source_primary"}'
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Run `refresh-list` after create/update/delete operations when you need the open
|
|
@@ -132,7 +132,7 @@ guesswork:
|
|
|
132
132
|
writing. If a named person cannot be resolved unambiguously, clarify in the
|
|
133
133
|
originating Slack thread; never omit, downgrade, or silently drop the person.
|
|
134
134
|
|
|
135
|
-
## Local
|
|
135
|
+
## Local Folder Sources
|
|
136
136
|
|
|
137
137
|
Install into an existing repo with:
|
|
138
138
|
|
|
@@ -140,40 +140,27 @@ Install into an existing repo with:
|
|
|
140
140
|
npx @agent-native/core@latest skills add content --mode local-files --scope project
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
The
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
The compatibility spelling still installs the skill, but it no longer enables
|
|
144
|
+
a separate data mode. The CLI writes or updates `agent-native.json` with
|
|
145
|
+
declarative local-folder sources and launches normal database-backed Content.
|
|
146
|
+
A typical root looks like:
|
|
146
147
|
|
|
147
148
|
```json
|
|
148
149
|
{
|
|
149
150
|
"version": 1,
|
|
150
151
|
"apps": {
|
|
151
152
|
"content": {
|
|
152
|
-
"mode": "local-files",
|
|
153
153
|
"roots": [
|
|
154
154
|
{
|
|
155
155
|
"name": "Docs",
|
|
156
156
|
"path": "docs",
|
|
157
157
|
"kind": "docs",
|
|
158
|
-
"extensions": [".md", ".mdx"]
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"extensions": [".md", ".mdx"]
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
"name": "Content",
|
|
168
|
-
"path": "content",
|
|
169
|
-
"kind": "content",
|
|
170
|
-
"extensions": [".md", ".mdx"]
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
"name": "Resources",
|
|
174
|
-
"path": "resources",
|
|
175
|
-
"kind": "resources",
|
|
176
|
-
"extensions": [".md", ".mdx"]
|
|
158
|
+
"extensions": [".md", ".mdx"],
|
|
159
|
+
"source": {
|
|
160
|
+
"type": "local-folder",
|
|
161
|
+
"connectionId": "local-folder:<opaque-id>",
|
|
162
|
+
"truthPolicy": "source_primary"
|
|
163
|
+
}
|
|
177
164
|
}
|
|
178
165
|
],
|
|
179
166
|
"components": "components",
|
|
@@ -184,9 +171,10 @@ Content roots for `docs/`, `blog/`, `content/`, and `resources/`, plus a
|
|
|
184
171
|
}
|
|
185
172
|
```
|
|
186
173
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
174
|
+
Content never stores an absolute local path or raw file body in source metadata.
|
|
175
|
+
File access still requires a local Content app, Agent Native Desktop, or another
|
|
176
|
+
trusted bridge. Disconnecting a folder leaves the SQL pages and disk files in
|
|
177
|
+
place. Concurrent edits and missing source files require explicit review.
|
|
190
178
|
|
|
191
179
|
## MDX And Components
|
|
192
180
|
|
|
@@ -202,9 +190,8 @@ local Content app, Agent Native Desktop, or another trusted local bridge.
|
|
|
202
190
|
|
|
203
191
|
## Boundaries
|
|
204
192
|
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
then let Content rediscover the file tree.
|
|
193
|
+
- Preserve a page's frontmatter `id` when renaming a source file so the next
|
|
194
|
+
sync recognizes it as the same global Content page.
|
|
208
195
|
- Do not push/pull Notion, Builder.io, or other provider-backed content unless
|
|
209
196
|
the user explicitly asks for provider sync.
|
|
210
197
|
- Do not paste secrets, private provider data, or credential-looking values into
|
|
@@ -96,6 +96,11 @@ cd templates/content && pnpm action <name> [args]
|
|
|
96
96
|
| `list-documents` | `[--format json]` | List document metadata/tree; no full bodies |
|
|
97
97
|
| `export-content-source` | `[--format json]` | Export editable docs as `content/*.mdx` source files |
|
|
98
98
|
| `import-content-source` | `--files <json> [--dryRun true\|false]` | Import `.md`/`.mdx` source files into editable docs |
|
|
99
|
+
| `connect-local-folder-source` | `--connectionId <opaque-id> --label <name> [--spaceId <id>\|--databaseId <id>\|--createSourceBackedSpace true] [--truthPolicy database_primary\|source_primary\|reviewed_bidirectional]` | Connect a trusted browser/Desktop folder registry entry to canonical Files without storing its path or handle in SQL |
|
|
100
|
+
| `sync-local-folder-source` | `--sourceId <id> --files <json> [--dryRun true\|false]` | Materialize a connected folder through Files source rows and record concurrent changes for review |
|
|
101
|
+
| `sync-manifest-local-folder-source` | `--connectionId <opaque-id> [--file <relative-path>] [--spaceId <id>] [--dryRun true\|false]` | Use the trusted local server bridge to materialize a manifest-declared folder into normal SQL-backed Content |
|
|
102
|
+
| `resolve-local-folder-conflict` | `--changeSetId <id> --decision keep_content\|accept_source [--sourceContent <text>]` | Resolve a reviewed folder conflict; accepting requires the exact hashed folder revision |
|
|
103
|
+
| `disconnect-local-folder-source` | `--sourceId <id>` | Disconnect a folder adapter without deleting local files or its SQL-backed Content pages |
|
|
99
104
|
| `list-builder-docs` | `[--model docs-content\|blog-article] [--limit <n>]` | List Builder docs/blog entries available for `.builder.mdx` pull |
|
|
100
105
|
| `pull-builder-doc` | `--model <model> --entryId <builder-entry-id> [--dryRun true\|false]` | Pull one Builder entry into Content and return `.builder.mdx` plus `content/builder/.raw` sidecar files |
|
|
101
106
|
| `check-builder-doc` | `--files <json> [--path <file.builder.mdx>]` or `--documentId <id>` | Validate Builder MDX round-trip, sidecar hashes, and remote conflict status before push |
|
|
@@ -112,7 +117,10 @@ cd templates/content && pnpm action <name> [args]
|
|
|
112
117
|
| `remove-local-file-source` | `[--sourceRootPath <path>]` | Unlink local-file sources from Content without deleting local Markdown/MDX files |
|
|
113
118
|
| `list-local-component-files` | | List registered local MDX component source files |
|
|
114
119
|
| `write-local-component-file` | `--workspaceId <id> --path <relative-component-path> --content <source>` | Create or update a file in a registered local `components/` folder |
|
|
115
|
-
| `
|
|
120
|
+
| `ensure-content-spaces` | | Idempotently provision the signed-in user's personal and current organization Content spaces, system databases, and catalog references |
|
|
121
|
+
| `backfill-content-files` | | Assign legacy pages to Content spaces and reconcile exactly one canonical Files database membership per non-system page |
|
|
122
|
+
| `list-content-spaces` | | List already-provisioned Content spaces authorized by the signed-in user's current ownership or organization memberships |
|
|
123
|
+
| `create-content-database` | `[--documentId <id>] [--spaceId <id>] [--parentId <id>] [--title <text>] [--description <text>]` | Create a described database page in a Content space or convert an existing page into a database |
|
|
116
124
|
| `create-inline-content-database` | `--hostDocumentId <id> [--title <text>] [--description <text>]` | Create a described database owned by an inline database block in the host document |
|
|
117
125
|
| `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database with its description, property/option schema guidance, item pages, and computed ancestry context |
|
|
118
126
|
| `list-trashed-content-databases` | | List soft-deleted databases visible in the sidebar Trash surface |
|
|
@@ -193,37 +201,24 @@ strip of the markdown.
|
|
|
193
201
|
|
|
194
202
|
### Local Source Files
|
|
195
203
|
|
|
196
|
-
Content has
|
|
204
|
+
Content has one database-backed document model with optional folder sources:
|
|
197
205
|
|
|
198
|
-
- **
|
|
206
|
+
- **Local folder sources:** the `/local-files` view links one or more
|
|
199
207
|
browser or Agent Native Desktop folders to SQL-backed documents. The UI uses
|
|
200
|
-
folder rows: **Pull**
|
|
201
|
-
`
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
source folder may contain unexpected files.
|
|
215
|
-
- **Local File Mode editing:** when the app runs with `AGENT_NATIVE_MODE=local-files`
|
|
216
|
-
or an `agent-native.json` whose app config enables local files, the standard
|
|
217
|
-
Content editor reads and writes configured repo files directly. The left
|
|
218
|
-
sidebar is populated from local roots such as `docs/`, `blog/`, `content/`,
|
|
219
|
-
and `resources/`; selecting a file opens `/page/<local-file-id>` and
|
|
220
|
-
`update-document` writes the selected `.md`/`.mdx` file. The document id is
|
|
221
|
-
derived from the file path, and unknown frontmatter is preserved when title,
|
|
222
|
-
content, icon, or favorite state changes.
|
|
223
|
-
Repos can opt into `profile: "docs/no-bookkeeping"` to keep docs edits from
|
|
224
|
-
adding absent `updatedAt`, `icon`, or `isFavorite` frontmatter. Content-only
|
|
225
|
-
edits under that profile preserve existing frontmatter but do not create new
|
|
226
|
-
bookkeeping keys; explicit title/icon/favorite edits still persist.
|
|
208
|
+
folder rows: **Pull** calls `sync-local-folder-source`, **Check** runs the
|
|
209
|
+
same action with `dryRun: true`, and **Push** uses the source-scoped
|
|
210
|
+
`export-content-source`. Imported files become ordinary SQL documents in the
|
|
211
|
+
target space's canonical Files database. Preserve frontmatter `id` across
|
|
212
|
+
renames; missing files and concurrent changes become reviewable incoming
|
|
213
|
+
change sets instead of silently deleting or overwriting a page. SQL stores
|
|
214
|
+
only opaque connection identity, relative paths, and hashes. Disconnecting a
|
|
215
|
+
folder keeps both the Content pages and disk files.
|
|
216
|
+
- **Manifest/CLI bridge:** `agent-native content local-files <target>` remains
|
|
217
|
+
as a compatibility spelling, but it launches normal database-backed Content.
|
|
218
|
+
`agent-native.json` declares `source.type: "local-folder"` and an opaque
|
|
219
|
+
`connectionId`; it does not select a separate application mode. The trusted
|
|
220
|
+
local server bridge materializes that source through
|
|
221
|
+
`sync-manifest-local-folder-source`.
|
|
227
222
|
- **Local MDX components:** local file workspaces can expose React components
|
|
228
223
|
from the configured `components` folder. Export PascalCase components such as
|
|
229
224
|
`ImpactCounter` from `.tsx` files, then use `<ImpactCounter />` in MDX or pick
|
|
@@ -36,6 +36,12 @@ import {
|
|
|
36
36
|
type BuilderCmsSourceEntry,
|
|
37
37
|
} from "./_builder-cms-source-adapter.js";
|
|
38
38
|
import { executeBuilderCmsWrite } from "./_builder-cms-write-client.js";
|
|
39
|
+
import { ensureDocumentFilesMembership } from "./_content-files.js";
|
|
40
|
+
import {
|
|
41
|
+
organizationContentSpaceId,
|
|
42
|
+
personalContentSpaceId,
|
|
43
|
+
provisionContentSpaces,
|
|
44
|
+
} from "./_content-spaces.js";
|
|
39
45
|
import { flushOpenDocumentEditorToSql } from "./_document-flush.js";
|
|
40
46
|
|
|
41
47
|
type FetchLike = typeof fetch;
|
|
@@ -524,6 +530,17 @@ export async function pullBuilderDocIntoContent(args: {
|
|
|
524
530
|
if (!ownerEmail) throw new Error("no authenticated user");
|
|
525
531
|
const orgId = getRequestOrgId() ?? null;
|
|
526
532
|
const db = getDb();
|
|
533
|
+
const provisioned = args.dryRun
|
|
534
|
+
? null
|
|
535
|
+
: await provisionContentSpaces(db, ownerEmail);
|
|
536
|
+
const targetSpaceId = orgId
|
|
537
|
+
? organizationContentSpaceId(orgId)
|
|
538
|
+
: personalContentSpaceId(ownerEmail);
|
|
539
|
+
if (provisioned && !provisioned.spaceIds.includes(targetSpaceId)) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
"The active organization does not have a writable Content space.",
|
|
542
|
+
);
|
|
543
|
+
}
|
|
527
544
|
const entry = await readFullBuilderDocsEntry({
|
|
528
545
|
model: args.model,
|
|
529
546
|
entryId: args.entryId,
|
|
@@ -554,6 +571,9 @@ export async function pullBuilderDocIntoContent(args: {
|
|
|
554
571
|
`Requires editor access to update document "${documentId}".`,
|
|
555
572
|
);
|
|
556
573
|
}
|
|
574
|
+
if (existing?.spaceId && existing.spaceId !== targetSpaceId) {
|
|
575
|
+
throw new Error("Builder document belongs to a different Content space.");
|
|
576
|
+
}
|
|
557
577
|
|
|
558
578
|
const sourceFields = builderDocumentSourceFields(bundle, now);
|
|
559
579
|
|
|
@@ -562,6 +582,7 @@ export async function pullBuilderDocIntoContent(args: {
|
|
|
562
582
|
await db
|
|
563
583
|
.update(schema.documents)
|
|
564
584
|
.set({
|
|
585
|
+
spaceId: existing.spaceId ?? targetSpaceId,
|
|
565
586
|
title: bundle.mdx.title,
|
|
566
587
|
content: bundle.mdx.body,
|
|
567
588
|
updatedAt: now,
|
|
@@ -571,6 +592,7 @@ export async function pullBuilderDocIntoContent(args: {
|
|
|
571
592
|
} else {
|
|
572
593
|
await db.insert(schema.documents).values({
|
|
573
594
|
id: documentId,
|
|
595
|
+
spaceId: targetSpaceId,
|
|
574
596
|
ownerEmail,
|
|
575
597
|
orgId,
|
|
576
598
|
parentId: null,
|
|
@@ -586,6 +608,7 @@ export async function pullBuilderDocIntoContent(args: {
|
|
|
586
608
|
...sourceFields,
|
|
587
609
|
});
|
|
588
610
|
}
|
|
611
|
+
await ensureDocumentFilesMembership(db, documentId, now);
|
|
589
612
|
await replaceBuilderDocumentSidecars({
|
|
590
613
|
db,
|
|
591
614
|
documentId,
|
|
@@ -68,13 +68,31 @@ const notionDatabaseAdapter: ContentDatabaseSourceAdapter = {
|
|
|
68
68
|
read: readNotionDatabaseSource,
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
+
const localFolderAdapter: ContentDatabaseSourceAdapter = {
|
|
72
|
+
sourceType: "local-folder",
|
|
73
|
+
async read() {
|
|
74
|
+
return {
|
|
75
|
+
state: "error",
|
|
76
|
+
entries: [],
|
|
77
|
+
fields: [],
|
|
78
|
+
fetchedAt: new Date().toISOString(),
|
|
79
|
+
message:
|
|
80
|
+
"Local folder refresh requires the trusted browser or Desktop bridge.",
|
|
81
|
+
metadata: { bridgeRequired: true },
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
71
86
|
const adapters = new Map<
|
|
72
87
|
ContentDatabaseSourceType,
|
|
73
88
|
ContentDatabaseSourceAdapter
|
|
74
89
|
>(
|
|
75
|
-
[
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
[
|
|
91
|
+
builderCmsAdapter,
|
|
92
|
+
localTableAdapter,
|
|
93
|
+
notionDatabaseAdapter,
|
|
94
|
+
localFolderAdapter,
|
|
95
|
+
].map((adapter) => [adapter.sourceType, adapter]),
|
|
78
96
|
);
|
|
79
97
|
|
|
80
98
|
export function getContentDatabaseSourceAdapter(
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { accessFilter } from "@agent-native/core/sharing";
|
|
4
|
+
import { and, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
|
5
|
+
|
|
6
|
+
import { schema } from "../server/db/index.js";
|
|
7
|
+
import {
|
|
8
|
+
listContentOrganizationMemberships,
|
|
9
|
+
normalizeContentSpaceEmail,
|
|
10
|
+
resolveContentSpaceAccess,
|
|
11
|
+
} from "./_content-space-access.js";
|
|
12
|
+
import {
|
|
13
|
+
organizationContentSpaceId,
|
|
14
|
+
personalContentSpaceId,
|
|
15
|
+
} from "./_content-spaces.js";
|
|
16
|
+
|
|
17
|
+
type Db = any;
|
|
18
|
+
|
|
19
|
+
export type ContentFilesReconciliation = {
|
|
20
|
+
assignedSpaces: number;
|
|
21
|
+
insertedMemberships: number;
|
|
22
|
+
removedMemberships: number;
|
|
23
|
+
documents: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function contentFilesItemId(databaseId: string, documentId: string) {
|
|
27
|
+
return `content_database_item_${createHash("sha256")
|
|
28
|
+
.update(`${databaseId}:${documentId}`)
|
|
29
|
+
.digest("hex")
|
|
30
|
+
.slice(0, 32)}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function remapItemReferences(db: Db, replacements: Map<string, string>) {
|
|
34
|
+
if (!replacements.size) return;
|
|
35
|
+
const duplicateIds = [...replacements.keys()];
|
|
36
|
+
const remap = (column: any) => {
|
|
37
|
+
let expression = sql`CASE ${column}`;
|
|
38
|
+
for (const [duplicateId, canonicalId] of replacements)
|
|
39
|
+
expression = sql`${expression} WHEN ${duplicateId} THEN ${canonicalId}`;
|
|
40
|
+
return sql`${expression} ELSE ${column} END`;
|
|
41
|
+
};
|
|
42
|
+
for (const table of [
|
|
43
|
+
schema.contentSpaceCatalogItems,
|
|
44
|
+
schema.contentDatabaseBodyHydrationQueue,
|
|
45
|
+
schema.contentDatabaseSourceRows,
|
|
46
|
+
schema.contentDatabaseSourceChangeSets,
|
|
47
|
+
]) {
|
|
48
|
+
await db
|
|
49
|
+
.update(table)
|
|
50
|
+
.set({ databaseItemId: remap(table.databaseItemId) })
|
|
51
|
+
.where(inArray(table.databaseItemId, duplicateIds));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function reconcileDocuments(args: {
|
|
56
|
+
db: Db;
|
|
57
|
+
documents: Array<typeof schema.documents.$inferSelect>;
|
|
58
|
+
filesDatabases: Array<typeof schema.contentDatabases.$inferSelect>;
|
|
59
|
+
now: string;
|
|
60
|
+
}) {
|
|
61
|
+
const filesBySpace = new Map(
|
|
62
|
+
args.filesDatabases.map((database) => [database.spaceId, database]),
|
|
63
|
+
);
|
|
64
|
+
const filesDatabaseIds = args.filesDatabases.map((database) => database.id);
|
|
65
|
+
const filesBackingDocumentIds = new Set(
|
|
66
|
+
args.filesDatabases.map((database) => database.documentId),
|
|
67
|
+
);
|
|
68
|
+
const existingItems = filesDatabaseIds.length
|
|
69
|
+
? await args.db
|
|
70
|
+
.select()
|
|
71
|
+
.from(schema.contentDatabaseItems)
|
|
72
|
+
.where(
|
|
73
|
+
inArray(schema.contentDatabaseItems.databaseId, filesDatabaseIds),
|
|
74
|
+
)
|
|
75
|
+
: [];
|
|
76
|
+
const itemsByDocument = new Map<
|
|
77
|
+
string,
|
|
78
|
+
Array<typeof schema.contentDatabaseItems.$inferSelect>
|
|
79
|
+
>();
|
|
80
|
+
const nextPosition = new Map<string, number>();
|
|
81
|
+
for (const item of existingItems) {
|
|
82
|
+
const documentItems = itemsByDocument.get(item.documentId) ?? [];
|
|
83
|
+
documentItems.push(item);
|
|
84
|
+
itemsByDocument.set(item.documentId, documentItems);
|
|
85
|
+
nextPosition.set(
|
|
86
|
+
item.databaseId,
|
|
87
|
+
Math.max(nextPosition.get(item.databaseId) ?? 0, item.position + 1),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const deleteIds = new Set<string>();
|
|
92
|
+
const replacements = new Map<string, string>();
|
|
93
|
+
const inserts: Array<typeof schema.contentDatabaseItems.$inferInsert> = [];
|
|
94
|
+
for (const document of args.documents) {
|
|
95
|
+
const existing = itemsByDocument.get(document.id) ?? [];
|
|
96
|
+
if (filesBackingDocumentIds.has(document.id)) {
|
|
97
|
+
for (const item of existing) deleteIds.add(item.id);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const canonicalDatabase = filesBySpace.get(document.spaceId);
|
|
101
|
+
if (!canonicalDatabase) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Content space for document "${document.id}" has no Files database`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const canonicalItems = existing
|
|
107
|
+
.filter((item) => item.databaseId === canonicalDatabase.id)
|
|
108
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
109
|
+
const canonicalItemId =
|
|
110
|
+
canonicalItems[0]?.id ??
|
|
111
|
+
contentFilesItemId(canonicalDatabase.id, document.id);
|
|
112
|
+
for (const item of existing) {
|
|
113
|
+
if (item.databaseId !== canonicalDatabase.id) {
|
|
114
|
+
deleteIds.add(item.id);
|
|
115
|
+
replacements.set(item.id, canonicalItemId);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const duplicate of canonicalItems.slice(1)) {
|
|
119
|
+
deleteIds.add(duplicate.id);
|
|
120
|
+
replacements.set(duplicate.id, canonicalItemId);
|
|
121
|
+
}
|
|
122
|
+
if (canonicalItems.length === 0) {
|
|
123
|
+
const position = nextPosition.get(canonicalDatabase.id) ?? 0;
|
|
124
|
+
nextPosition.set(canonicalDatabase.id, position + 1);
|
|
125
|
+
inserts.push({
|
|
126
|
+
id: contentFilesItemId(canonicalDatabase.id, document.id),
|
|
127
|
+
ownerEmail: document.ownerEmail,
|
|
128
|
+
orgId: document.orgId,
|
|
129
|
+
databaseId: canonicalDatabase.id,
|
|
130
|
+
documentId: document.id,
|
|
131
|
+
position,
|
|
132
|
+
createdAt: args.now,
|
|
133
|
+
updatedAt: args.now,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
await remapItemReferences(args.db, replacements);
|
|
138
|
+
if (deleteIds.size) {
|
|
139
|
+
await args.db
|
|
140
|
+
.delete(schema.contentDatabaseItems)
|
|
141
|
+
.where(inArray(schema.contentDatabaseItems.id, [...deleteIds]));
|
|
142
|
+
}
|
|
143
|
+
if (inserts.length) {
|
|
144
|
+
await args.db
|
|
145
|
+
.insert(schema.contentDatabaseItems)
|
|
146
|
+
.values(inserts)
|
|
147
|
+
.onConflictDoNothing();
|
|
148
|
+
}
|
|
149
|
+
return { inserted: inserts.length, removed: deleteIds.size };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function ensureDocumentFilesMembership(
|
|
153
|
+
db: Db,
|
|
154
|
+
documentId: string,
|
|
155
|
+
now = new Date().toISOString(),
|
|
156
|
+
accessContext?: { userEmail?: string; orgId?: string },
|
|
157
|
+
) {
|
|
158
|
+
const [document] = await db
|
|
159
|
+
.select()
|
|
160
|
+
.from(schema.documents)
|
|
161
|
+
.where(
|
|
162
|
+
and(
|
|
163
|
+
eq(schema.documents.id, documentId),
|
|
164
|
+
accessFilter(schema.documents, schema.documentShares, accessContext),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
if (!document) throw new Error(`Document "${documentId}" not found`);
|
|
168
|
+
if (!document.spaceId)
|
|
169
|
+
throw new Error(`Document "${documentId}" does not have a Content space`);
|
|
170
|
+
const filesDatabases = await db
|
|
171
|
+
.select()
|
|
172
|
+
.from(schema.contentDatabases)
|
|
173
|
+
.where(
|
|
174
|
+
and(
|
|
175
|
+
eq(schema.contentDatabases.systemRole, "files"),
|
|
176
|
+
eq(schema.contentDatabases.spaceId, document.spaceId),
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
return reconcileDocuments({ db, documents: [document], filesDatabases, now });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function ensureDocumentsFilesMembership(
|
|
183
|
+
db: Db,
|
|
184
|
+
documentIds: string[],
|
|
185
|
+
now = new Date().toISOString(),
|
|
186
|
+
ownerEmail?: string,
|
|
187
|
+
) {
|
|
188
|
+
const ids = [...new Set(documentIds)];
|
|
189
|
+
if (ids.length === 0) return { inserted: 0, removed: 0 };
|
|
190
|
+
const documents = await db
|
|
191
|
+
.select()
|
|
192
|
+
.from(schema.documents)
|
|
193
|
+
.where(
|
|
194
|
+
and(
|
|
195
|
+
inArray(schema.documents.id, ids),
|
|
196
|
+
ownerEmail
|
|
197
|
+
? or(
|
|
198
|
+
eq(schema.documents.ownerEmail, ownerEmail),
|
|
199
|
+
accessFilter(schema.documents, schema.documentShares),
|
|
200
|
+
)
|
|
201
|
+
: accessFilter(schema.documents, schema.documentShares),
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
if (documents.length !== ids.length) {
|
|
205
|
+
const found = new Set(documents.map((document: any) => document.id));
|
|
206
|
+
const missing = ids.filter((id) => !found.has(id));
|
|
207
|
+
throw new Error(`Documents not found: ${missing.join(", ")}`);
|
|
208
|
+
}
|
|
209
|
+
for (const document of documents) {
|
|
210
|
+
if (!document.spaceId) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Document "${document.id}" does not have a Content space`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const spaceIds: string[] = Array.from(
|
|
217
|
+
new Set<string>(documents.map((document: any) => String(document.spaceId))),
|
|
218
|
+
);
|
|
219
|
+
const filesDatabases = await db
|
|
220
|
+
.select()
|
|
221
|
+
.from(schema.contentDatabases)
|
|
222
|
+
.where(
|
|
223
|
+
and(
|
|
224
|
+
eq(schema.contentDatabases.systemRole, "files"),
|
|
225
|
+
or(
|
|
226
|
+
...spaceIds.map((spaceId) =>
|
|
227
|
+
eq(schema.contentDatabases.spaceId, spaceId),
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
return reconcileDocuments({ db, documents, filesDatabases, now });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function reconcileContentFilesMemberships(
|
|
236
|
+
db: Db,
|
|
237
|
+
userEmail: string,
|
|
238
|
+
): Promise<ContentFilesReconciliation> {
|
|
239
|
+
const email = normalizeContentSpaceEmail(userEmail);
|
|
240
|
+
const memberships = await listContentOrganizationMemberships(email);
|
|
241
|
+
const personalSpaceId = personalContentSpaceId(email);
|
|
242
|
+
const orgSpaceIds = new Map<string, string>();
|
|
243
|
+
for (const membership of memberships) {
|
|
244
|
+
const spaceId = organizationContentSpaceId(membership.orgId);
|
|
245
|
+
try {
|
|
246
|
+
await resolveContentSpaceAccess(spaceId, "editor");
|
|
247
|
+
orgSpaceIds.set(membership.orgId, spaceId);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (
|
|
250
|
+
error instanceof Error &&
|
|
251
|
+
error.message ===
|
|
252
|
+
`Editor access is required for Content space "${spaceId}"`
|
|
253
|
+
) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const accessibleSpaceIds = [personalSpaceId, ...orgSpaceIds.values()];
|
|
260
|
+
const now = new Date().toISOString();
|
|
261
|
+
const result: ContentFilesReconciliation = {
|
|
262
|
+
assignedSpaces: 0,
|
|
263
|
+
insertedMemberships: 0,
|
|
264
|
+
removedMemberships: 0,
|
|
265
|
+
documents: 0,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
await db.transaction(async (tx: Db) => {
|
|
269
|
+
const personalLegacyDocuments = await tx
|
|
270
|
+
.select({ id: schema.documents.id })
|
|
271
|
+
.from(schema.documents)
|
|
272
|
+
.where(
|
|
273
|
+
and(
|
|
274
|
+
eq(schema.documents.ownerEmail, email),
|
|
275
|
+
isNull(schema.documents.orgId),
|
|
276
|
+
isNull(schema.documents.spaceId),
|
|
277
|
+
),
|
|
278
|
+
);
|
|
279
|
+
if (personalLegacyDocuments.length) {
|
|
280
|
+
await tx
|
|
281
|
+
.update(schema.documents)
|
|
282
|
+
.set({ spaceId: personalSpaceId, updatedAt: now })
|
|
283
|
+
.where(
|
|
284
|
+
inArray(
|
|
285
|
+
schema.documents.id,
|
|
286
|
+
personalLegacyDocuments.map((row: any) => row.id),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
result.assignedSpaces += personalLegacyDocuments.length;
|
|
290
|
+
}
|
|
291
|
+
for (const [orgId, spaceId] of orgSpaceIds) {
|
|
292
|
+
const legacyDocuments = await tx
|
|
293
|
+
.select({ id: schema.documents.id })
|
|
294
|
+
.from(schema.documents)
|
|
295
|
+
.where(
|
|
296
|
+
and(
|
|
297
|
+
eq(schema.documents.orgId, orgId),
|
|
298
|
+
isNull(schema.documents.spaceId),
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
if (!legacyDocuments.length) continue;
|
|
302
|
+
await tx
|
|
303
|
+
.update(schema.documents)
|
|
304
|
+
.set({ spaceId, updatedAt: now })
|
|
305
|
+
.where(
|
|
306
|
+
inArray(
|
|
307
|
+
schema.documents.id,
|
|
308
|
+
legacyDocuments.map((row: any) => row.id),
|
|
309
|
+
),
|
|
310
|
+
);
|
|
311
|
+
result.assignedSpaces += legacyDocuments.length;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const documents = await tx
|
|
315
|
+
.select()
|
|
316
|
+
.from(schema.documents)
|
|
317
|
+
.where(inArray(schema.documents.spaceId, accessibleSpaceIds));
|
|
318
|
+
const filesDatabases = await tx
|
|
319
|
+
.select()
|
|
320
|
+
.from(schema.contentDatabases)
|
|
321
|
+
.where(
|
|
322
|
+
and(
|
|
323
|
+
eq(schema.contentDatabases.systemRole, "files"),
|
|
324
|
+
inArray(schema.contentDatabases.spaceId, accessibleSpaceIds),
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
const membershipResult = await reconcileDocuments({
|
|
328
|
+
db: tx,
|
|
329
|
+
documents,
|
|
330
|
+
filesDatabases,
|
|
331
|
+
now,
|
|
332
|
+
});
|
|
333
|
+
result.documents = documents.length;
|
|
334
|
+
result.insertedMemberships = membershipResult.inserted;
|
|
335
|
+
result.removedMemberships = membershipResult.removed;
|
|
336
|
+
});
|
|
337
|
+
return result;
|
|
338
|
+
}
|