@agent-native/core 0.80.11 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +47 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +90 -16
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +5 -19
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +53 -4
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
- package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/design/app/i18n-data.ts +358 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/motion-compiler.ts +58 -7
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.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/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.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 +2 -2
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { executeProviderApiRequest } from "../server/lib/provider-api.js";
|
|
5
|
+
|
|
6
|
+
const FIGMA_FILE_KEY_RE = /^[A-Za-z0-9_-]{8,}$/;
|
|
7
|
+
|
|
8
|
+
const FigmaRenderFormatSchema = z.enum(["svg", "png"]).default("svg");
|
|
9
|
+
|
|
10
|
+
const schemaInput = z
|
|
11
|
+
.object({
|
|
12
|
+
fileUrl: z
|
|
13
|
+
.string()
|
|
14
|
+
.trim()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe(
|
|
17
|
+
"Figma file URL, for example https://www.figma.com/design/<fileKey>/...",
|
|
18
|
+
),
|
|
19
|
+
fileKey: z
|
|
20
|
+
.string()
|
|
21
|
+
.trim()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Figma file key. Used when fileUrl is omitted."),
|
|
24
|
+
query: z
|
|
25
|
+
.string()
|
|
26
|
+
.trim()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Optional case-insensitive search over names/descriptions."),
|
|
29
|
+
limit: z.coerce
|
|
30
|
+
.number()
|
|
31
|
+
.int()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(64)
|
|
34
|
+
.default(48)
|
|
35
|
+
.describe("Maximum number of Figma library assets to return."),
|
|
36
|
+
renderFormat: FigmaRenderFormatSchema.describe(
|
|
37
|
+
"Image format for insertable render URLs.",
|
|
38
|
+
),
|
|
39
|
+
})
|
|
40
|
+
.refine((value) => value.fileUrl || value.fileKey, {
|
|
41
|
+
message: "Pass fileUrl or fileKey.",
|
|
42
|
+
path: ["fileUrl"],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
type FigmaProviderEnvelope = {
|
|
46
|
+
response?: {
|
|
47
|
+
ok?: boolean;
|
|
48
|
+
status?: number;
|
|
49
|
+
statusText?: string;
|
|
50
|
+
json?: unknown;
|
|
51
|
+
text?: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type FigmaAssetKind = "component" | "component_set";
|
|
56
|
+
|
|
57
|
+
type FigmaComponentRecord = {
|
|
58
|
+
key?: unknown;
|
|
59
|
+
file_key?: unknown;
|
|
60
|
+
node_id?: unknown;
|
|
61
|
+
name?: unknown;
|
|
62
|
+
description?: unknown;
|
|
63
|
+
thumbnail_url?: unknown;
|
|
64
|
+
created_at?: unknown;
|
|
65
|
+
updated_at?: unknown;
|
|
66
|
+
containing_frame?: unknown;
|
|
67
|
+
user?: unknown;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type FigmaImageResponse = {
|
|
71
|
+
images?: Record<string, string | null | undefined>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function stringValue(value: unknown): string | null {
|
|
75
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function numberOrDefault(value: number | undefined, fallback: number): number {
|
|
79
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function parseFigmaFileKey(input: string | undefined): string | null {
|
|
83
|
+
const value = input?.trim();
|
|
84
|
+
if (!value) return null;
|
|
85
|
+
if (FIGMA_FILE_KEY_RE.test(value)) return value;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const url = new URL(value);
|
|
89
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
90
|
+
for (const segment of ["design", "file", "proto"]) {
|
|
91
|
+
const index = parts.indexOf(segment);
|
|
92
|
+
if (index >= 0) {
|
|
93
|
+
const candidate = parts[index + 1];
|
|
94
|
+
if (candidate && FIGMA_FILE_KEY_RE.test(candidate)) return candidate;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function figmaNodeUrl(
|
|
105
|
+
fileKey: string,
|
|
106
|
+
nodeId: string,
|
|
107
|
+
fileUrl?: string,
|
|
108
|
+
): string {
|
|
109
|
+
const nodeParam = nodeId.replace(/:/g, "-");
|
|
110
|
+
try {
|
|
111
|
+
const url = new URL(fileUrl || `https://www.figma.com/design/${fileKey}`);
|
|
112
|
+
url.searchParams.set("node-id", nodeParam);
|
|
113
|
+
return url.href;
|
|
114
|
+
} catch {
|
|
115
|
+
return `https://www.figma.com/design/${fileKey}?node-id=${encodeURIComponent(
|
|
116
|
+
nodeParam,
|
|
117
|
+
)}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function recordSearchText(record: FigmaComponentRecord): string {
|
|
122
|
+
const frame =
|
|
123
|
+
record.containing_frame &&
|
|
124
|
+
typeof record.containing_frame === "object" &&
|
|
125
|
+
"name" in record.containing_frame
|
|
126
|
+
? stringValue((record.containing_frame as { name?: unknown }).name)
|
|
127
|
+
: null;
|
|
128
|
+
return [stringValue(record.name), stringValue(record.description), frame]
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.join(" ")
|
|
131
|
+
.toLowerCase();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function matchesQuery(record: FigmaComponentRecord, query: string | undefined) {
|
|
135
|
+
const normalized = query?.trim().toLowerCase();
|
|
136
|
+
if (!normalized) return true;
|
|
137
|
+
return recordSearchText(record).includes(normalized);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeFigmaRecords(
|
|
141
|
+
value: unknown,
|
|
142
|
+
kind: FigmaAssetKind,
|
|
143
|
+
): FigmaComponentRecord[] {
|
|
144
|
+
if (!value || typeof value !== "object") return [];
|
|
145
|
+
const meta = (value as { meta?: unknown }).meta;
|
|
146
|
+
const key = kind === "component" ? "components" : "component_sets";
|
|
147
|
+
const records =
|
|
148
|
+
meta && typeof meta === "object"
|
|
149
|
+
? (meta as Record<string, unknown>)[key]
|
|
150
|
+
: undefined;
|
|
151
|
+
if (!Array.isArray(records)) return [];
|
|
152
|
+
return records.filter(
|
|
153
|
+
(record): record is FigmaComponentRecord =>
|
|
154
|
+
!!record && typeof record === "object",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function providerJson(envelope: unknown, label: string): unknown {
|
|
159
|
+
const response = (envelope as FigmaProviderEnvelope | null)?.response;
|
|
160
|
+
if (!response) throw new Error(`Figma ${label} response was empty.`);
|
|
161
|
+
if (response.ok === false) {
|
|
162
|
+
const detail =
|
|
163
|
+
stringValue(response.text) ||
|
|
164
|
+
response.statusText ||
|
|
165
|
+
`HTTP ${response.status ?? "error"}`;
|
|
166
|
+
throw new Error(`Figma ${label} request failed: ${detail}`);
|
|
167
|
+
}
|
|
168
|
+
return response.json;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function figmaGet(path: string, query?: Record<string, unknown>) {
|
|
172
|
+
return executeProviderApiRequest({
|
|
173
|
+
provider: "figma",
|
|
174
|
+
method: "GET",
|
|
175
|
+
path,
|
|
176
|
+
query,
|
|
177
|
+
maxBytes: 2 * 1024 * 1024,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default defineAction({
|
|
182
|
+
description:
|
|
183
|
+
"List reusable assets from a Figma file for the Design asset panel. Returns components and component sets with thumbnails, insertable render URLs, and source provenance. Requires a saved FIGMA_ACCESS_TOKEN secret.",
|
|
184
|
+
schema: schemaInput,
|
|
185
|
+
http: { method: "GET" },
|
|
186
|
+
readOnly: true,
|
|
187
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
188
|
+
run: async (args) => {
|
|
189
|
+
const fileKey =
|
|
190
|
+
parseFigmaFileKey(args.fileKey) ?? parseFigmaFileKey(args.fileUrl);
|
|
191
|
+
if (!fileKey) {
|
|
192
|
+
throw new Error("Could not find a Figma file key in the provided URL.");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const limit = numberOrDefault(args.limit, 48);
|
|
196
|
+
const [componentsEnvelope, componentSetsEnvelope] = await Promise.all([
|
|
197
|
+
figmaGet(`/files/${fileKey}/components`),
|
|
198
|
+
figmaGet(`/files/${fileKey}/component_sets`),
|
|
199
|
+
]);
|
|
200
|
+
const componentRecords = normalizeFigmaRecords(
|
|
201
|
+
providerJson(componentsEnvelope, "components"),
|
|
202
|
+
"component",
|
|
203
|
+
)
|
|
204
|
+
.filter((record) => matchesQuery(record, args.query))
|
|
205
|
+
.map((record) => ({ kind: "component" as const, record }));
|
|
206
|
+
const componentSetRecords = normalizeFigmaRecords(
|
|
207
|
+
providerJson(componentSetsEnvelope, "component sets"),
|
|
208
|
+
"component_set",
|
|
209
|
+
)
|
|
210
|
+
.filter((record) => matchesQuery(record, args.query))
|
|
211
|
+
.map((record) => ({ kind: "component_set" as const, record }));
|
|
212
|
+
|
|
213
|
+
const rawItems = [...componentRecords, ...componentSetRecords].slice(
|
|
214
|
+
0,
|
|
215
|
+
limit,
|
|
216
|
+
);
|
|
217
|
+
const renderIds = rawItems
|
|
218
|
+
.map((item) => stringValue(item.record.node_id))
|
|
219
|
+
.filter((id): id is string => !!id);
|
|
220
|
+
let rendered: Record<string, string | null | undefined> = {};
|
|
221
|
+
if (renderIds.length > 0) {
|
|
222
|
+
const imagesEnvelope = await figmaGet(`/images/${fileKey}`, {
|
|
223
|
+
ids: renderIds.join(","),
|
|
224
|
+
format: args.renderFormat,
|
|
225
|
+
svg_include_id: true,
|
|
226
|
+
});
|
|
227
|
+
const images = providerJson(
|
|
228
|
+
imagesEnvelope,
|
|
229
|
+
"images",
|
|
230
|
+
) as FigmaImageResponse;
|
|
231
|
+
rendered = images.images ?? {};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const assets = rawItems
|
|
235
|
+
.map(({ kind, record }) => {
|
|
236
|
+
const nodeId = stringValue(record.node_id);
|
|
237
|
+
const key = stringValue(record.key);
|
|
238
|
+
const name = stringValue(record.name) ?? "Untitled Figma asset";
|
|
239
|
+
const description = stringValue(record.description);
|
|
240
|
+
const thumbnailUrl = stringValue(record.thumbnail_url);
|
|
241
|
+
const renderUrl = nodeId ? stringValue(rendered[nodeId]) : null;
|
|
242
|
+
const containingFrame =
|
|
243
|
+
record.containing_frame && typeof record.containing_frame === "object"
|
|
244
|
+
? {
|
|
245
|
+
name:
|
|
246
|
+
stringValue(
|
|
247
|
+
(record.containing_frame as { name?: unknown }).name,
|
|
248
|
+
) ?? null,
|
|
249
|
+
nodeId:
|
|
250
|
+
stringValue(
|
|
251
|
+
(record.containing_frame as { nodeId?: unknown }).nodeId,
|
|
252
|
+
) ??
|
|
253
|
+
stringValue(
|
|
254
|
+
(record.containing_frame as { node_id?: unknown }).node_id,
|
|
255
|
+
),
|
|
256
|
+
}
|
|
257
|
+
: null;
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
id: `figma:${fileKey}:${nodeId ?? key ?? name}`,
|
|
261
|
+
kind,
|
|
262
|
+
fileKey,
|
|
263
|
+
nodeId,
|
|
264
|
+
componentKey: key,
|
|
265
|
+
name,
|
|
266
|
+
description,
|
|
267
|
+
thumbnailUrl,
|
|
268
|
+
renderUrl,
|
|
269
|
+
insertUrl: renderUrl ?? thumbnailUrl,
|
|
270
|
+
sourceUrl: nodeId
|
|
271
|
+
? figmaNodeUrl(fileKey, nodeId, args.fileUrl)
|
|
272
|
+
: null,
|
|
273
|
+
containingFrame,
|
|
274
|
+
createdAt: stringValue(record.created_at),
|
|
275
|
+
updatedAt: stringValue(record.updated_at),
|
|
276
|
+
};
|
|
277
|
+
})
|
|
278
|
+
.filter((asset) => asset.nodeId || asset.componentKey);
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
source: "figma",
|
|
282
|
+
fileKey,
|
|
283
|
+
query: args.query ?? null,
|
|
284
|
+
renderFormat: args.renderFormat,
|
|
285
|
+
total: componentRecords.length + componentSetRecords.length,
|
|
286
|
+
returned: assets.length,
|
|
287
|
+
assets,
|
|
288
|
+
guidance:
|
|
289
|
+
"Use renderUrl/insertUrl with insert-asset and preserve fileKey, nodeId, componentKey, and sourceUrl for provenance. Components/component sets are reusable Figma library assets; styles and variables belong in design-system token sync.",
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
});
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* migrate-board-objects-to-file
|
|
3
|
+
*
|
|
4
|
+
* Lazy, idempotent migration that converts the legacy designs.data.boardObjects
|
|
5
|
+
* JSON blob into the new board file architecture.
|
|
6
|
+
*
|
|
7
|
+
* ## What it does
|
|
8
|
+
*
|
|
9
|
+
* 1. Guards on boardFileId already set — returns early if the migration has
|
|
10
|
+
* already run (idempotent).
|
|
11
|
+
* 2. Reads designs.data.boardObjects and converts each entry into an HTML
|
|
12
|
+
* fragment via boardObjectEntryToHtmlFragment, preserving negative left/top
|
|
13
|
+
* coordinates exactly (no clamping).
|
|
14
|
+
* 3. Inserts the fragments as direct <body> children of a new __board__.html
|
|
15
|
+
* design file (or merges into an existing one if somehow already present).
|
|
16
|
+
* 4. Stores the new file's id into designs.data.boardFileId in a transaction.
|
|
17
|
+
* 5. Nulls out designs.data.boardObjects so the old model no longer conflicts.
|
|
18
|
+
*
|
|
19
|
+
* ## Contract
|
|
20
|
+
*
|
|
21
|
+
* - Requires editor access on the design.
|
|
22
|
+
* - SCHEMA ADDITIVE ONLY — no columns are dropped or renamed; only designs.data
|
|
23
|
+
* JSON keys change.
|
|
24
|
+
* - Safe to call on designs with no board objects: creates the board file with
|
|
25
|
+
* an empty body and sets boardFileId.
|
|
26
|
+
*
|
|
27
|
+
* ## Trigger
|
|
28
|
+
*
|
|
29
|
+
* Called by DesignEditor on design open when designs.data.boardFileId is absent.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { defineAction } from "@agent-native/core";
|
|
33
|
+
import { seedFromText } from "@agent-native/core/collab";
|
|
34
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
35
|
+
import { and, eq } from "drizzle-orm";
|
|
36
|
+
import { nanoid } from "nanoid";
|
|
37
|
+
import { z } from "zod";
|
|
38
|
+
|
|
39
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
40
|
+
import {
|
|
41
|
+
BOARD_FILENAME,
|
|
42
|
+
boardObjectEntryToHtmlFragment,
|
|
43
|
+
emptyBoardHtml,
|
|
44
|
+
} from "../shared/board-file.js";
|
|
45
|
+
import { parseBoardObjects } from "../shared/board-objects.js";
|
|
46
|
+
|
|
47
|
+
export default defineAction({
|
|
48
|
+
description:
|
|
49
|
+
"Migrate the legacy boardObjects blob (designs.data.boardObjects) into the " +
|
|
50
|
+
"new board file architecture. Creates a __board__.html design file, writes " +
|
|
51
|
+
"each board object as an absolute-positioned HTML element (preserving " +
|
|
52
|
+
"negative coordinates), stores boardFileId in designs.data, and nulls " +
|
|
53
|
+
"boardObjects. Idempotent — returns immediately if boardFileId is already " +
|
|
54
|
+
"set. Requires editor access on the design.",
|
|
55
|
+
schema: z.object({
|
|
56
|
+
designId: z
|
|
57
|
+
.string()
|
|
58
|
+
.describe("Design project ID to migrate board objects for."),
|
|
59
|
+
}),
|
|
60
|
+
run: async ({ designId }) => {
|
|
61
|
+
await assertAccess("design", designId, "editor");
|
|
62
|
+
|
|
63
|
+
const db = getDb();
|
|
64
|
+
|
|
65
|
+
// ── 1. Idempotency guard ──────────────────────────────────────────────
|
|
66
|
+
// Read the design data JSON to check whether boardFileId is already set.
|
|
67
|
+
// guard:allow-unscoped — assertAccess "editor" above scopes this read to
|
|
68
|
+
// the requesting user's accessible designs.
|
|
69
|
+
const [existing] = await db
|
|
70
|
+
.select({ data: schema.designs.data })
|
|
71
|
+
.from(schema.designs)
|
|
72
|
+
.where(eq(schema.designs.id, designId))
|
|
73
|
+
.limit(1);
|
|
74
|
+
|
|
75
|
+
if (!existing) {
|
|
76
|
+
throw new Error(`Design "${designId}" not found.`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let parsed: Record<string, unknown>;
|
|
80
|
+
try {
|
|
81
|
+
const raw = JSON.parse(existing.data);
|
|
82
|
+
parsed =
|
|
83
|
+
raw && typeof raw === "object" && !Array.isArray(raw)
|
|
84
|
+
? (raw as Record<string, unknown>)
|
|
85
|
+
: {};
|
|
86
|
+
} catch {
|
|
87
|
+
parsed = {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
typeof parsed["boardFileId"] === "string" &&
|
|
92
|
+
parsed["boardFileId"].length > 0
|
|
93
|
+
) {
|
|
94
|
+
return {
|
|
95
|
+
designId,
|
|
96
|
+
migrated: false,
|
|
97
|
+
boardFileId: parsed["boardFileId"] as string,
|
|
98
|
+
reason: "boardFileId already set — migration already complete.",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── 2. Parse legacy board objects ────────────────────────────────────
|
|
103
|
+
const boardObjects = parseBoardObjects(parsed["boardObjects"]);
|
|
104
|
+
const entries = Object.values(boardObjects);
|
|
105
|
+
|
|
106
|
+
// ── 3. Build the board HTML ───────────────────────────────────────────
|
|
107
|
+
// Start from the canonical empty-board template and inject each entry as
|
|
108
|
+
// a direct <body> child. Negative left/top are preserved — the migration
|
|
109
|
+
// intentionally does NOT clamp coords (appendCanvasPrimitiveToHtml clamps
|
|
110
|
+
// to x/y >= 0 for new screen primitives; the board surface has no such
|
|
111
|
+
// restriction).
|
|
112
|
+
let boardHtml = emptyBoardHtml();
|
|
113
|
+
if (entries.length > 0) {
|
|
114
|
+
const fragments = entries
|
|
115
|
+
.sort((a, b) => {
|
|
116
|
+
// Stable render order: lower z first, then creation order.
|
|
117
|
+
const az = a.geometry.z ?? 0;
|
|
118
|
+
const bz = b.geometry.z ?? 0;
|
|
119
|
+
if (az !== bz) return az - bz;
|
|
120
|
+
return a.createdAt < b.createdAt ? -1 : 1;
|
|
121
|
+
})
|
|
122
|
+
.map((entry) => boardObjectEntryToHtmlFragment(entry))
|
|
123
|
+
.join("\n");
|
|
124
|
+
|
|
125
|
+
// Insert fragments before </body>.
|
|
126
|
+
boardHtml = boardHtml.replace("</body>", `${fragments}\n</body>`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const now = new Date().toISOString();
|
|
130
|
+
|
|
131
|
+
// ── 4. Upsert the board file and update designs.data atomically ───────
|
|
132
|
+
// Run in a transaction so concurrent opens cannot create two board files.
|
|
133
|
+
let boardFileId = "";
|
|
134
|
+
|
|
135
|
+
await db.transaction(async (tx) => {
|
|
136
|
+
// Re-read designs.data inside the transaction to guard against a
|
|
137
|
+
// concurrent migration that raced us.
|
|
138
|
+
const [fresh] = await tx
|
|
139
|
+
.select({ data: schema.designs.data })
|
|
140
|
+
.from(schema.designs)
|
|
141
|
+
.where(eq(schema.designs.id, designId))
|
|
142
|
+
.limit(1);
|
|
143
|
+
|
|
144
|
+
if (!fresh) {
|
|
145
|
+
throw new Error(`Design "${designId}" not found.`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let freshParsed: Record<string, unknown>;
|
|
149
|
+
try {
|
|
150
|
+
const raw = JSON.parse(fresh.data);
|
|
151
|
+
freshParsed =
|
|
152
|
+
raw && typeof raw === "object" && !Array.isArray(raw)
|
|
153
|
+
? (raw as Record<string, unknown>)
|
|
154
|
+
: {};
|
|
155
|
+
} catch {
|
|
156
|
+
freshParsed = {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Double-check inside the transaction.
|
|
160
|
+
if (
|
|
161
|
+
typeof freshParsed["boardFileId"] === "string" &&
|
|
162
|
+
freshParsed["boardFileId"].length > 0
|
|
163
|
+
) {
|
|
164
|
+
boardFileId = freshParsed["boardFileId"] as string;
|
|
165
|
+
return; // Another concurrent call already finished — skip.
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check if a __board__.html file was somehow already created (shouldn't
|
|
169
|
+
// happen in normal flow, but be defensive).
|
|
170
|
+
const [existingBoardFile] = await tx
|
|
171
|
+
.select({ id: schema.designFiles.id })
|
|
172
|
+
.from(schema.designFiles)
|
|
173
|
+
.where(
|
|
174
|
+
and(
|
|
175
|
+
eq(schema.designFiles.designId, designId),
|
|
176
|
+
eq(schema.designFiles.filename, BOARD_FILENAME),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
.limit(1);
|
|
180
|
+
|
|
181
|
+
if (existingBoardFile) {
|
|
182
|
+
// Board file exists but boardFileId was not set — link it and update
|
|
183
|
+
// its content.
|
|
184
|
+
boardFileId = existingBoardFile.id;
|
|
185
|
+
await tx
|
|
186
|
+
.update(schema.designFiles)
|
|
187
|
+
.set({ content: boardHtml, updatedAt: now })
|
|
188
|
+
.where(eq(schema.designFiles.id, boardFileId));
|
|
189
|
+
} else {
|
|
190
|
+
// Create the board file.
|
|
191
|
+
boardFileId = nanoid();
|
|
192
|
+
await tx.insert(schema.designFiles).values({
|
|
193
|
+
id: boardFileId,
|
|
194
|
+
designId,
|
|
195
|
+
filename: BOARD_FILENAME,
|
|
196
|
+
fileType: "html",
|
|
197
|
+
content: boardHtml,
|
|
198
|
+
createdAt: now,
|
|
199
|
+
updatedAt: now,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Write boardFileId into designs.data; null out boardObjects.
|
|
204
|
+
const nextData: Record<string, unknown> = {
|
|
205
|
+
...freshParsed,
|
|
206
|
+
boardFileId,
|
|
207
|
+
boardObjects: null,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
await tx
|
|
211
|
+
.update(schema.designs)
|
|
212
|
+
.set({
|
|
213
|
+
data: JSON.stringify(nextData),
|
|
214
|
+
updatedAt: now,
|
|
215
|
+
})
|
|
216
|
+
.where(eq(schema.designs.id, designId));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ── 5. Seed collab state for the new board file ───────────────────────
|
|
220
|
+
// (Best-effort: collab seeding after the file row is committed.)
|
|
221
|
+
try {
|
|
222
|
+
await seedFromText(boardFileId, boardHtml);
|
|
223
|
+
} catch {
|
|
224
|
+
// Non-fatal — the board file still renders from SQL content.
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
designId,
|
|
229
|
+
migrated: true,
|
|
230
|
+
boardFileId,
|
|
231
|
+
migratedObjectCount: entries.length,
|
|
232
|
+
message:
|
|
233
|
+
entries.length > 0
|
|
234
|
+
? `Migrated ${entries.length} board object(s) to board file ${boardFileId}.`
|
|
235
|
+
: `Created empty board file ${boardFileId} (no legacy board objects).`,
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
});
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* --view View name (list, editor, design-systems, present, templates, settings)
|
|
19
19
|
* --designId Design ID (for editor/present views)
|
|
20
20
|
* --editorView Editor mode for designs: single or overview
|
|
21
|
-
* --inspectorTab Inspector tab for designs: design
|
|
21
|
+
* --inspectorTab Inspector tab for designs: design or tweaks (extensions opens Tools for compatibility)
|
|
22
|
+
* --leftPanel Left editor panel: file, agent, assets, tools, or tokens
|
|
22
23
|
* --fileId Screen/file id to focus in the design editor
|
|
23
24
|
* --filename Screen filename to focus in the design editor
|
|
24
25
|
* --tool Design editor tool to activate
|
|
@@ -47,9 +48,17 @@ const designEditorToolSchema = z.enum([
|
|
|
47
48
|
"scale",
|
|
48
49
|
]);
|
|
49
50
|
|
|
51
|
+
const designLeftPanelSchema = z.enum([
|
|
52
|
+
"file",
|
|
53
|
+
"agent",
|
|
54
|
+
"assets",
|
|
55
|
+
"tools",
|
|
56
|
+
"tokens",
|
|
57
|
+
]);
|
|
58
|
+
|
|
50
59
|
export default defineAction({
|
|
51
60
|
description:
|
|
52
|
-
"Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, templates, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use
|
|
61
|
+
"Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, templates, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens to focus the Figma-style left rail. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
|
|
53
62
|
schema: z
|
|
54
63
|
.object({
|
|
55
64
|
view: z
|
|
@@ -83,6 +92,10 @@ export default defineAction({
|
|
|
83
92
|
.enum(["design", "tweaks", "extensions"])
|
|
84
93
|
.optional()
|
|
85
94
|
.describe("Alias for inspectorTab"),
|
|
95
|
+
leftPanel: designLeftPanelSchema
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Design editor left rail panel to focus"),
|
|
98
|
+
panel: designLeftPanelSchema.optional().describe("Alias for leftPanel"),
|
|
86
99
|
fileId: z.string().optional().describe("Design file/screen ID to focus"),
|
|
87
100
|
screenId: z.string().optional().describe("Alias for fileId"),
|
|
88
101
|
filename: z
|
|
@@ -146,6 +159,8 @@ export default defineAction({
|
|
|
146
159
|
if (editorView) nav.editorView = editorView;
|
|
147
160
|
const inspectorTab = args.inspectorTab ?? args.inspector;
|
|
148
161
|
if (inspectorTab) nav.inspectorTab = inspectorTab;
|
|
162
|
+
const leftPanel = args.leftPanel ?? args.panel;
|
|
163
|
+
if (leftPanel) nav.leftPanel = leftPanel;
|
|
149
164
|
if (args.fileId) nav.fileId = args.fileId;
|
|
150
165
|
if (args.screenId) nav.screenId = args.screenId;
|
|
151
166
|
if (args.filename) nav.filename = args.filename;
|
|
@@ -159,7 +174,7 @@ export default defineAction({
|
|
|
159
174
|
args.designId ? ` (design: ${args.designId})` : ""
|
|
160
175
|
}${editorView ? ` (${editorView} view)` : ""}${
|
|
161
176
|
inspectorTab ? ` (${inspectorTab} inspector)` : ""
|
|
162
|
-
}${
|
|
177
|
+
}${leftPanel ? ` (${leftPanel} panel)` : ""}${
|
|
163
178
|
args.fileId || args.screenId || args.filename || args.screen
|
|
164
179
|
? ` (screen: ${args.fileId ?? args.screenId ?? args.filename ?? args.screen})`
|
|
165
180
|
: ""
|
|
@@ -10,7 +10,7 @@ const ProviderSchema = z.enum(DESIGN_PROVIDER_API_IDS);
|
|
|
10
10
|
|
|
11
11
|
export default defineAction({
|
|
12
12
|
description:
|
|
13
|
-
"List raw HTTP API capabilities for Design-connected providers. Use before provider-api-request when design-token
|
|
13
|
+
"List raw HTTP API capabilities for Design-connected providers such as GitHub and Figma. Use before provider-api-request when design-token, repository, or Figma-library convenience actions are too narrow. Returns provider base URLs, auth style, credential key names, docs/spec URLs, placeholders, and examples; never returns secret values.",
|
|
14
14
|
schema: z.object({
|
|
15
15
|
provider: ProviderSchema.optional().describe(
|
|
16
16
|
"Optional provider id to inspect. Omit to list every Design provider API escape hatch.",
|
|
@@ -22,7 +22,7 @@ export default defineAction({
|
|
|
22
22
|
return {
|
|
23
23
|
providers: await listProviderApiCatalog(provider),
|
|
24
24
|
guidance:
|
|
25
|
-
"Design actions like import-github are workflow shortcuts, not capability limits. When
|
|
25
|
+
"Design actions like import-github and list-figma-library-assets are workflow shortcuts, not capability limits. When GitHub or Figma can answer the question with a better endpoint, query, body, pagination mode, metadata field, or API version, inspect docs/spec URLs and call provider-api-request directly. Auth uses saved scoped secrets such as GITHUB_TOKEN or FIGMA_ACCESS_TOKEN and never exposes token values.",
|
|
26
26
|
};
|
|
27
27
|
},
|
|
28
28
|
});
|
|
@@ -25,7 +25,7 @@ const WebContentSearchSchema = z.object({
|
|
|
25
25
|
|
|
26
26
|
export default defineAction({
|
|
27
27
|
description:
|
|
28
|
-
"Inspect provider API docs/spec metadata, or fetch any public API documentation page, OpenAPI spec, changelog, or web page. Use this before provider-api-request when the exact GitHub endpoint, payload shape, pagination, or API version is uncertain.",
|
|
28
|
+
"Inspect provider API docs/spec metadata, or fetch any public API documentation page, OpenAPI spec, changelog, or web page. Use this before provider-api-request when the exact GitHub/Figma endpoint, payload shape, pagination, or API version is uncertain.",
|
|
29
29
|
schema: z.object({
|
|
30
30
|
provider: ProviderSchema.describe(
|
|
31
31
|
"Provider whose API docs/spec to inspect.",
|
|
@@ -71,8 +71,8 @@ const PaginationSchema = z
|
|
|
71
71
|
export default defineAction({
|
|
72
72
|
description:
|
|
73
73
|
"Make an arbitrary authenticated HTTP request to a Design-connected provider API. " +
|
|
74
|
-
"Use this as the flexible escape hatch when a convenience action cannot express the needed GitHub endpoint, file
|
|
75
|
-
"The request is constrained to the provider host, uses
|
|
74
|
+
"Use this as the flexible escape hatch when a convenience action cannot express the needed GitHub endpoint, Figma file/library endpoint, request body, pagination mode, payload shape, or API version. " +
|
|
75
|
+
"The request is constrained to the provider host, uses saved scoped credentials such as GITHUB_TOKEN or FIGMA_ACCESS_TOKEN automatically, blocks private/internal URLs, and redacts secrets from responses. " +
|
|
76
76
|
"\n\nSTAGING MODE (preferred for large responses): Pass stageAs to write response items into a scratch dataset instead of returning the raw body. " +
|
|
77
77
|
"Returns { dataset, rowCount, columns, sampleRows } so only a compact summary enters context. Use query-staged-dataset to aggregate, filter, and project the data without re-fetching. " +
|
|
78
78
|
"\n\nPAGINATION: When stageAs is set, pass pagination config to fetch all pages server-side into the same dataset. For GitHub page pagination, use pageParam='page', pageSize from per_page, and itemsPath when the response object contains an items array.",
|
|
@@ -83,7 +83,7 @@ export default defineAction({
|
|
|
83
83
|
.string()
|
|
84
84
|
.min(1)
|
|
85
85
|
.describe(
|
|
86
|
-
"Provider API path such as /repos/{owner}/{repo}/contents/{path}, /search/code, /
|
|
86
|
+
"Provider API path such as /repos/{owner}/{repo}/contents/{path}, /search/code, /files/{fileKey}/components, /images/{fileKey}, or a full URL on an allowed provider host. Use placeholders from provider-api-catalog when provided.",
|
|
87
87
|
),
|
|
88
88
|
query: z
|
|
89
89
|
.unknown()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
3
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
+
import { and, eq } from "drizzle-orm";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
8
|
+
|
|
9
|
+
export default defineAction({
|
|
10
|
+
description:
|
|
11
|
+
"Revoke a previously granted localhost write-consent for a design + connection. " +
|
|
12
|
+
"After revocation the agent can no longer write local files until the user re-grants consent. " +
|
|
13
|
+
"Requires editor access on the design.",
|
|
14
|
+
schema: z.object({
|
|
15
|
+
designId: z.string().describe("Design ID."),
|
|
16
|
+
connectionId: z
|
|
17
|
+
.string()
|
|
18
|
+
.describe("Localhost connection ID whose grant should be revoked."),
|
|
19
|
+
}),
|
|
20
|
+
run: async ({ designId, connectionId }) => {
|
|
21
|
+
await assertAccess("design", designId, "editor");
|
|
22
|
+
|
|
23
|
+
const ownerEmail = getRequestUserEmail();
|
|
24
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
25
|
+
|
|
26
|
+
const db = getDb();
|
|
27
|
+
|
|
28
|
+
const deleted = await db
|
|
29
|
+
.delete(schema.designLocalhostWriteGrants)
|
|
30
|
+
.where(
|
|
31
|
+
and(
|
|
32
|
+
eq(schema.designLocalhostWriteGrants.designId, designId),
|
|
33
|
+
eq(schema.designLocalhostWriteGrants.connectionId, connectionId),
|
|
34
|
+
eq(schema.designLocalhostWriteGrants.ownerEmail, ownerEmail),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// `deleted` shape varies by dialect; check rows affected via the object.
|
|
39
|
+
const rowsAffected =
|
|
40
|
+
(deleted as unknown as { rowsAffected?: number })?.rowsAffected ?? 0;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
designId,
|
|
44
|
+
connectionId,
|
|
45
|
+
revoked: rowsAffected > 0,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
});
|