@agent-native/core 0.66.7 → 0.66.8

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.
@@ -211,7 +211,7 @@ export async function preUploadAttachments(opts) {
211
211
  injectedText = [
212
212
  "<chat-image-attachment-upload-error>",
213
213
  "The user attached one or more images, but no file-upload provider is configured for this app.",
214
- "Tell the user to connect or reconnect Builder.io from Settings → File uploads. If `connect-builder` is available, use it to render the inline connection card. Workspaces with a custom storage provider can also use one registered via registerFileUploadProvider().",
214
+ "If `connect-builder` is available, use it to render the inline Builder.io connection card. Workspaces with a custom storage provider can also use one registered via registerFileUploadProvider().",
215
215
  "Until that's done, you can still SEE the image, but you do NOT have a URL to embed it in HTML or share with other apps.",
216
216
  "</chat-image-attachment-upload-error>",
217
217
  ].join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"pre-upload-attachments.js","sourceRoot":"","sources":["../../src/file-upload/pre-upload-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentChatAttachment } from \"../agent/types.js\";\nimport { getActiveFileUploadProvider, uploadFile } from \"./registry.js\";\n\nexport interface PreUploadedImageAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n}\n\n/**\n * A file/non-image attachment that was successfully uploaded to a hosted URL.\n * Consumers can use the URL in place of the base64 data to avoid persisting\n * large blobs in the thread repo and SQL.\n */\nexport interface PreUploadedFileAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n sizeBytes?: number;\n referenceOnly?: boolean;\n securityNote?: string;\n}\n\nexport interface PreUploadAttachmentsResult {\n /** Same array reference. Each image attachment that was uploaded also gets a\n * `url` property attached (non-breaking; consumers that don't read it are\n * unaffected). */\n attachments: AgentChatAttachment[];\n /** Set when at least one image was uploaded. List of hosted URLs the agent\n * can embed in HTML, slide content, documents, etc. */\n uploaded: PreUploadedImageAttachment[];\n /** Uploaded non-image files (PDF, generic binary). Parallel to `uploaded`\n * but for the file/document attachment type. */\n uploadedFiles: PreUploadedFileAttachment[];\n /** True if at least one image attachment failed to upload because no\n * file-upload provider is configured. Templates use this to render a\n * \"Connect Builder.io\" suggestion. */\n providerMissing: boolean;\n /** A pre-formatted block to inject into the user message text so the agent\n * has each hosted URL inline. Null when nothing was uploaded or no provider\n * is configured. */\n injectedText: string | null;\n}\n\nconst FILE_DATA_URL_RE = /^data:([^;]+);base64,(.+)$/;\nconst SVG_REFERENCE_SECURITY_NOTE =\n \"SVG content may contain active markup; use this URL as a file reference unless the target app sanitizes it.\";\n\nfunction normalizeContentType(value: string | undefined): string | undefined {\n return value?.split(\";\")[0]?.trim().toLowerCase() || undefined;\n}\n\nfunction hasSvgFilename(name: string | undefined): boolean {\n return /\\.svg$/i.test(name ?? \"\");\n}\n\nfunction isSvgAttachment(args: {\n name?: string;\n contentType?: string;\n}): boolean {\n return (\n normalizeContentType(args.contentType) === \"image/svg+xml\" ||\n hasSvgFilename(args.name)\n );\n}\n\nfunction isSvgPayload(args: { name?: string; contentType?: string }): boolean {\n const contentType = normalizeContentType(args.contentType);\n return (\n contentType === \"image/svg+xml\" ||\n ((contentType === undefined ||\n contentType === \"application/octet-stream\") &&\n hasSvgFilename(args.name))\n );\n}\n\nfunction markReferenceOnlySvgAttachment(\n att: AgentChatAttachment,\n contentType: string | undefined,\n) {\n att.type = \"file\";\n att.contentType = normalizeContentType(contentType) ?? \"image/svg+xml\";\n (att as any).referenceOnly = true;\n (att as any).securityNote = SVG_REFERENCE_SECURITY_NOTE;\n}\n\nfunction escapeXmlAttr(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Returns true when a file-upload provider is currently configured.\n * Used to decide whether to attempt upload-first or fall back to base64.\n */\nexport function isFileUploadProviderConfigured(): boolean {\n return getActiveFileUploadProvider() !== null;\n}\n\n/**\n * Pre-upload chat image attachments through the active file-upload provider\n * (Builder.io by default) so the agent can embed hosted URLs in HTML, slide\n * content, and outbound messages. Keeps the original base64 data URL on the\n * attachment so multimodal vision still works — only adds a hosted `url`.\n *\n * Safe to call when no provider is configured: it returns the attachments\n * untouched with `providerMissing: true` so callers can surface a connect-\n * Builder.io hint to the agent.\n */\nexport async function preUploadImageAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n}): Promise<PreUploadAttachmentsResult> {\n return preUploadAttachments({ ...opts, includeFiles: false });\n}\n\n/**\n * Pre-upload ALL chat attachments (images AND files/PDFs) through the active\n * file-upload provider. When a provider is configured, each attachment gets a\n * `url` property injected so downstream code can store/send URLs instead of\n * base64. The base64 data is kept in-memory for the current turn so vision and\n * file-reading still work; callers that persist the attachment can drop the\n * data when a URL exists.\n *\n * Falls back gracefully when no provider is configured: returns untouched\n * attachments with `providerMissing: true` for image-type failures.\n */\nexport async function preUploadAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n /** When false, only images are uploaded (legacy behaviour). Default: true */\n includeFiles?: boolean;\n}): Promise<PreUploadAttachmentsResult> {\n const list = Array.isArray(opts.attachments) ? opts.attachments : [];\n const includeFiles = opts.includeFiles !== false;\n const uploaded: PreUploadedImageAttachment[] = [];\n const uploadedFiles: PreUploadedFileAttachment[] = [];\n let providerMissing = false;\n\n if (list.length === 0) {\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing: false,\n injectedText: null,\n };\n }\n\n for (const att of list) {\n const isImage = att.type === \"image\";\n const isFile = att.type === \"file\" || att.type === \"document\";\n if (!isImage && !(includeFiles && isFile)) continue;\n if (typeof att.data !== \"string\") continue;\n\n if ((att as any).url) {\n // Already pre-uploaded earlier in the pipeline — reuse it.\n const isReferenceOnlySvg = isSvgAttachment(att);\n if (isReferenceOnlySvg) {\n markReferenceOnlySvgAttachment(att, att.contentType);\n }\n const entry = {\n name: att.name,\n url: (att as any).url as string,\n provider: ((att as any).uploadProvider as string) || \"unknown\",\n contentType: att.contentType,\n ...(isReferenceOnlySvg\n ? {\n referenceOnly: true,\n securityNote: SVG_REFERENCE_SECURITY_NOTE,\n }\n : {}),\n };\n if (isImage && !isReferenceOnlySvg) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n continue;\n }\n\n const match = att.data.match(FILE_DATA_URL_RE);\n if (!match) continue;\n const dataUrlMimeType = normalizeContentType(match[1]);\n const mimeType =\n dataUrlMimeType || normalizeContentType(att.contentType) || match[1];\n const uploadAsImage =\n isImage && !isSvgPayload({ name: att.name, contentType: mimeType });\n const uploadAsFile =\n !uploadAsImage && (isImage || (includeFiles && isFile));\n if (!uploadAsImage && !uploadAsFile) continue;\n\n let bytes: Uint8Array;\n try {\n bytes = new Uint8Array(Buffer.from(match[2], \"base64\"));\n } catch {\n continue;\n }\n\n try {\n const result = await uploadFile({\n data: bytes,\n filename: att.name,\n mimeType,\n ownerEmail: opts.ownerEmail || undefined,\n });\n if (!result) {\n if (uploadAsImage) providerMissing = true;\n continue;\n }\n (att as any).url = result.url;\n (att as any).uploadProvider = result.provider;\n const isReferenceOnlySvg = isSvgPayload({\n name: att.name,\n contentType: mimeType,\n });\n if (isReferenceOnlySvg) {\n markReferenceOnlySvgAttachment(att, mimeType);\n }\n const entry = {\n name: att.name,\n url: result.url,\n provider: result.provider,\n contentType: isReferenceOnlySvg ? att.contentType : mimeType,\n sizeBytes: bytes.byteLength,\n ...(isReferenceOnlySvg\n ? {\n referenceOnly: true,\n securityNote: SVG_REFERENCE_SECURITY_NOTE,\n }\n : {}),\n };\n if (uploadAsImage) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n } catch (err) {\n // Real upload failure (network, API). Keep the base64 so the model\n // can still see the image/file, but don't crash the turn.\n console.warn(\n \"[agent-native] pre-upload of chat attachment failed:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n let injectedText: string | null = null;\n if (uploaded.length > 0 || uploadedFiles.length > 0) {\n const lines: string[] = [];\n for (const u of uploaded) {\n const attrs = [\n u.name ? `name=\"${escapeXmlAttr(u.name)}\"` : null,\n `url=\"${escapeXmlAttr(u.url)}\"`,\n u.contentType ? `contentType=\"${escapeXmlAttr(u.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(u.provider)}\"`,\n ].filter(Boolean);\n lines.push(`<chat-image-attachment ${attrs.join(\" \")} />`);\n }\n for (const f of uploadedFiles) {\n const attrs = [\n f.name ? `name=\"${escapeXmlAttr(f.name)}\"` : null,\n `url=\"${escapeXmlAttr(f.url)}\"`,\n f.contentType ? `contentType=\"${escapeXmlAttr(f.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(f.provider)}\"`,\n f.referenceOnly ? `referenceOnly=\"true\"` : null,\n f.securityNote\n ? `securityNote=\"${escapeXmlAttr(f.securityNote)}\"`\n : null,\n ].filter(Boolean);\n lines.push(`<chat-file-attachment ${attrs.join(\" \")} />`);\n }\n const hasReferenceOnlySvg = uploadedFiles.some(\n (file) => file.referenceOnly && isSvgAttachment(file),\n );\n injectedText = [\n hasReferenceOnlySvg\n ? '<chat-attachments note=\"The user attached these files. Image attachment URLs may be used for embedding. File attachment URLs are references; SVG files are unsanitized vector source and must not be inlined as HTML or embedded in outbound content unless the target app sanitizes or stores them safely.\">'\n : '<chat-attachments note=\"The user attached these files. Image attachment URLs may be used for embedding in HTML, slide content, or outbound messages. File attachment URLs are references for reading or attaching in target apps.\">',\n ...lines,\n \"</chat-attachments>\",\n ].join(\"\\n\");\n } else if (providerMissing) {\n injectedText = [\n \"<chat-image-attachment-upload-error>\",\n \"The user attached one or more images, but no file-upload provider is configured for this app.\",\n \"Tell the user to connect or reconnect Builder.io from Settings → File uploads. If `connect-builder` is available, use it to render the inline connection card. Workspaces with a custom storage provider can also use one registered via registerFileUploadProvider().\",\n \"Until that's done, you can still SEE the image, but you do NOT have a URL to embed it in HTML or share with other apps.\",\n \"</chat-image-attachment-upload-error>\",\n ].join(\"\\n\");\n }\n\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing,\n injectedText,\n };\n}\n"]}
1
+ {"version":3,"file":"pre-upload-attachments.js","sourceRoot":"","sources":["../../src/file-upload/pre-upload-attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentChatAttachment } from \"../agent/types.js\";\nimport { getActiveFileUploadProvider, uploadFile } from \"./registry.js\";\n\nexport interface PreUploadedImageAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n}\n\n/**\n * A file/non-image attachment that was successfully uploaded to a hosted URL.\n * Consumers can use the URL in place of the base64 data to avoid persisting\n * large blobs in the thread repo and SQL.\n */\nexport interface PreUploadedFileAttachment {\n name?: string;\n url: string;\n provider: string;\n contentType?: string;\n sizeBytes?: number;\n referenceOnly?: boolean;\n securityNote?: string;\n}\n\nexport interface PreUploadAttachmentsResult {\n /** Same array reference. Each image attachment that was uploaded also gets a\n * `url` property attached (non-breaking; consumers that don't read it are\n * unaffected). */\n attachments: AgentChatAttachment[];\n /** Set when at least one image was uploaded. List of hosted URLs the agent\n * can embed in HTML, slide content, documents, etc. */\n uploaded: PreUploadedImageAttachment[];\n /** Uploaded non-image files (PDF, generic binary). Parallel to `uploaded`\n * but for the file/document attachment type. */\n uploadedFiles: PreUploadedFileAttachment[];\n /** True if at least one image attachment failed to upload because no\n * file-upload provider is configured. Templates use this to render a\n * \"Connect Builder.io\" suggestion. */\n providerMissing: boolean;\n /** A pre-formatted block to inject into the user message text so the agent\n * has each hosted URL inline. Null when nothing was uploaded or no provider\n * is configured. */\n injectedText: string | null;\n}\n\nconst FILE_DATA_URL_RE = /^data:([^;]+);base64,(.+)$/;\nconst SVG_REFERENCE_SECURITY_NOTE =\n \"SVG content may contain active markup; use this URL as a file reference unless the target app sanitizes it.\";\n\nfunction normalizeContentType(value: string | undefined): string | undefined {\n return value?.split(\";\")[0]?.trim().toLowerCase() || undefined;\n}\n\nfunction hasSvgFilename(name: string | undefined): boolean {\n return /\\.svg$/i.test(name ?? \"\");\n}\n\nfunction isSvgAttachment(args: {\n name?: string;\n contentType?: string;\n}): boolean {\n return (\n normalizeContentType(args.contentType) === \"image/svg+xml\" ||\n hasSvgFilename(args.name)\n );\n}\n\nfunction isSvgPayload(args: { name?: string; contentType?: string }): boolean {\n const contentType = normalizeContentType(args.contentType);\n return (\n contentType === \"image/svg+xml\" ||\n ((contentType === undefined ||\n contentType === \"application/octet-stream\") &&\n hasSvgFilename(args.name))\n );\n}\n\nfunction markReferenceOnlySvgAttachment(\n att: AgentChatAttachment,\n contentType: string | undefined,\n) {\n att.type = \"file\";\n att.contentType = normalizeContentType(contentType) ?? \"image/svg+xml\";\n (att as any).referenceOnly = true;\n (att as any).securityNote = SVG_REFERENCE_SECURITY_NOTE;\n}\n\nfunction escapeXmlAttr(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Returns true when a file-upload provider is currently configured.\n * Used to decide whether to attempt upload-first or fall back to base64.\n */\nexport function isFileUploadProviderConfigured(): boolean {\n return getActiveFileUploadProvider() !== null;\n}\n\n/**\n * Pre-upload chat image attachments through the active file-upload provider\n * (Builder.io by default) so the agent can embed hosted URLs in HTML, slide\n * content, and outbound messages. Keeps the original base64 data URL on the\n * attachment so multimodal vision still works — only adds a hosted `url`.\n *\n * Safe to call when no provider is configured: it returns the attachments\n * untouched with `providerMissing: true` so callers can surface a connect-\n * Builder.io hint to the agent.\n */\nexport async function preUploadImageAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n}): Promise<PreUploadAttachmentsResult> {\n return preUploadAttachments({ ...opts, includeFiles: false });\n}\n\n/**\n * Pre-upload ALL chat attachments (images AND files/PDFs) through the active\n * file-upload provider. When a provider is configured, each attachment gets a\n * `url` property injected so downstream code can store/send URLs instead of\n * base64. The base64 data is kept in-memory for the current turn so vision and\n * file-reading still work; callers that persist the attachment can drop the\n * data when a URL exists.\n *\n * Falls back gracefully when no provider is configured: returns untouched\n * attachments with `providerMissing: true` for image-type failures.\n */\nexport async function preUploadAttachments(opts: {\n attachments: AgentChatAttachment[] | undefined;\n ownerEmail: string | null | undefined;\n /** When false, only images are uploaded (legacy behaviour). Default: true */\n includeFiles?: boolean;\n}): Promise<PreUploadAttachmentsResult> {\n const list = Array.isArray(opts.attachments) ? opts.attachments : [];\n const includeFiles = opts.includeFiles !== false;\n const uploaded: PreUploadedImageAttachment[] = [];\n const uploadedFiles: PreUploadedFileAttachment[] = [];\n let providerMissing = false;\n\n if (list.length === 0) {\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing: false,\n injectedText: null,\n };\n }\n\n for (const att of list) {\n const isImage = att.type === \"image\";\n const isFile = att.type === \"file\" || att.type === \"document\";\n if (!isImage && !(includeFiles && isFile)) continue;\n if (typeof att.data !== \"string\") continue;\n\n if ((att as any).url) {\n // Already pre-uploaded earlier in the pipeline — reuse it.\n const isReferenceOnlySvg = isSvgAttachment(att);\n if (isReferenceOnlySvg) {\n markReferenceOnlySvgAttachment(att, att.contentType);\n }\n const entry = {\n name: att.name,\n url: (att as any).url as string,\n provider: ((att as any).uploadProvider as string) || \"unknown\",\n contentType: att.contentType,\n ...(isReferenceOnlySvg\n ? {\n referenceOnly: true,\n securityNote: SVG_REFERENCE_SECURITY_NOTE,\n }\n : {}),\n };\n if (isImage && !isReferenceOnlySvg) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n continue;\n }\n\n const match = att.data.match(FILE_DATA_URL_RE);\n if (!match) continue;\n const dataUrlMimeType = normalizeContentType(match[1]);\n const mimeType =\n dataUrlMimeType || normalizeContentType(att.contentType) || match[1];\n const uploadAsImage =\n isImage && !isSvgPayload({ name: att.name, contentType: mimeType });\n const uploadAsFile =\n !uploadAsImage && (isImage || (includeFiles && isFile));\n if (!uploadAsImage && !uploadAsFile) continue;\n\n let bytes: Uint8Array;\n try {\n bytes = new Uint8Array(Buffer.from(match[2], \"base64\"));\n } catch {\n continue;\n }\n\n try {\n const result = await uploadFile({\n data: bytes,\n filename: att.name,\n mimeType,\n ownerEmail: opts.ownerEmail || undefined,\n });\n if (!result) {\n if (uploadAsImage) providerMissing = true;\n continue;\n }\n (att as any).url = result.url;\n (att as any).uploadProvider = result.provider;\n const isReferenceOnlySvg = isSvgPayload({\n name: att.name,\n contentType: mimeType,\n });\n if (isReferenceOnlySvg) {\n markReferenceOnlySvgAttachment(att, mimeType);\n }\n const entry = {\n name: att.name,\n url: result.url,\n provider: result.provider,\n contentType: isReferenceOnlySvg ? att.contentType : mimeType,\n sizeBytes: bytes.byteLength,\n ...(isReferenceOnlySvg\n ? {\n referenceOnly: true,\n securityNote: SVG_REFERENCE_SECURITY_NOTE,\n }\n : {}),\n };\n if (uploadAsImage) {\n uploaded.push(entry);\n } else {\n uploadedFiles.push(entry);\n }\n } catch (err) {\n // Real upload failure (network, API). Keep the base64 so the model\n // can still see the image/file, but don't crash the turn.\n console.warn(\n \"[agent-native] pre-upload of chat attachment failed:\",\n err instanceof Error ? err.message : String(err),\n );\n }\n }\n\n let injectedText: string | null = null;\n if (uploaded.length > 0 || uploadedFiles.length > 0) {\n const lines: string[] = [];\n for (const u of uploaded) {\n const attrs = [\n u.name ? `name=\"${escapeXmlAttr(u.name)}\"` : null,\n `url=\"${escapeXmlAttr(u.url)}\"`,\n u.contentType ? `contentType=\"${escapeXmlAttr(u.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(u.provider)}\"`,\n ].filter(Boolean);\n lines.push(`<chat-image-attachment ${attrs.join(\" \")} />`);\n }\n for (const f of uploadedFiles) {\n const attrs = [\n f.name ? `name=\"${escapeXmlAttr(f.name)}\"` : null,\n `url=\"${escapeXmlAttr(f.url)}\"`,\n f.contentType ? `contentType=\"${escapeXmlAttr(f.contentType)}\"` : null,\n `provider=\"${escapeXmlAttr(f.provider)}\"`,\n f.referenceOnly ? `referenceOnly=\"true\"` : null,\n f.securityNote\n ? `securityNote=\"${escapeXmlAttr(f.securityNote)}\"`\n : null,\n ].filter(Boolean);\n lines.push(`<chat-file-attachment ${attrs.join(\" \")} />`);\n }\n const hasReferenceOnlySvg = uploadedFiles.some(\n (file) => file.referenceOnly && isSvgAttachment(file),\n );\n injectedText = [\n hasReferenceOnlySvg\n ? '<chat-attachments note=\"The user attached these files. Image attachment URLs may be used for embedding. File attachment URLs are references; SVG files are unsanitized vector source and must not be inlined as HTML or embedded in outbound content unless the target app sanitizes or stores them safely.\">'\n : '<chat-attachments note=\"The user attached these files. Image attachment URLs may be used for embedding in HTML, slide content, or outbound messages. File attachment URLs are references for reading or attaching in target apps.\">',\n ...lines,\n \"</chat-attachments>\",\n ].join(\"\\n\");\n } else if (providerMissing) {\n injectedText = [\n \"<chat-image-attachment-upload-error>\",\n \"The user attached one or more images, but no file-upload provider is configured for this app.\",\n \"If `connect-builder` is available, use it to render the inline Builder.io connection card. Workspaces with a custom storage provider can also use one registered via registerFileUploadProvider().\",\n \"Until that's done, you can still SEE the image, but you do NOT have a URL to embed it in HTML or share with other apps.\",\n \"</chat-image-attachment-upload-error>\",\n ].join(\"\\n\");\n }\n\n return {\n attachments: list,\n uploaded,\n uploadedFiles,\n providerMissing,\n injectedText,\n };\n}\n"]}
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Actions must export a default function: (args: string[]) => Promise<void>
9
9
  *
10
- * Usage: pnpm action <action-name> [--args]
10
+ * Usage: pnpm action <action-name> ['{"arg":"value"}'] [--args]
11
11
  */
12
12
  import type { ActionEntry } from "../agent/production-agent.js";
13
13
  export interface RunScriptOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/scripts/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAKhE,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC7C,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAaD;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwE7E"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/scripts/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAKhE,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC7C,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAaD;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0E7E"}
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Actions must export a default function: (args: string[]) => Promise<void>
9
9
  *
10
- * Usage: pnpm action <action-name> [--args]
10
+ * Usage: pnpm action <action-name> ['{"arg":"value"}'] [--args]
11
11
  */
12
12
  import path from "path";
13
13
  import fs from "fs";
@@ -39,7 +39,7 @@ async function runAppDbPluginIfPresent() {
39
39
  export async function runScript(options = {}) {
40
40
  const actionName = process.argv[2];
41
41
  if (!actionName || actionName === "--help") {
42
- console.log(`Usage: pnpm action <action-name> [--arg value ...]`);
42
+ console.log(`Usage: pnpm action <action-name> ['{"arg":"value"}'] [--arg value ...]`);
43
43
  console.log(`\nRun any action with --help for usage details.`);
44
44
  // List local actions (try actions/ first, then scripts/)
45
45
  const actionsDir = path.resolve(process.cwd(), "actions");
@@ -118,7 +118,8 @@ function setParsedArg(parsed, key, value) {
118
118
  : [existing, value];
119
119
  }
120
120
  function parseActionArgs(args, options = {}) {
121
- const parsed = {};
121
+ const parsed = parsePositionalJsonArg(args);
122
+ const flagArgs = {};
122
123
  const coerceBooleans = options.coerceBooleans ?? false;
123
124
  for (let i = 0; i < args.length; i++) {
124
125
  const arg = args[i];
@@ -126,20 +127,53 @@ function parseActionArgs(args, options = {}) {
126
127
  continue;
127
128
  const eqIdx = arg.indexOf("=");
128
129
  if (eqIdx > 0) {
129
- setParsedArg(parsed, arg.slice(2, eqIdx), coerceCliValue(arg.slice(eqIdx + 1), coerceBooleans));
130
+ setParsedArg(flagArgs, arg.slice(2, eqIdx), coerceCliValue(arg.slice(eqIdx + 1), coerceBooleans));
130
131
  }
131
132
  else {
132
133
  const key = arg.slice(2);
133
134
  const next = args[i + 1];
134
135
  if (next !== undefined && !next.startsWith("--")) {
135
- setParsedArg(parsed, key, coerceCliValue(next, coerceBooleans));
136
+ setParsedArg(flagArgs, key, coerceCliValue(next, coerceBooleans));
136
137
  i++;
137
138
  }
138
139
  else {
139
- setParsedArg(parsed, key, coerceBooleans ? true : "true");
140
+ setParsedArg(flagArgs, key, coerceBooleans ? true : "true");
140
141
  }
141
142
  }
142
143
  }
144
+ return { ...parsed, ...flagArgs };
145
+ }
146
+ function parsePositionalJsonArg(args) {
147
+ let jsonArg;
148
+ for (let i = 0; i < args.length; i++) {
149
+ const arg = args[i];
150
+ if (arg.startsWith("--")) {
151
+ if (!arg.includes("=") && args[i + 1] && !args[i + 1].startsWith("--")) {
152
+ i++;
153
+ }
154
+ continue;
155
+ }
156
+ const trimmed = arg.trim();
157
+ if (!trimmed.startsWith("{"))
158
+ continue;
159
+ if (jsonArg !== undefined) {
160
+ throw new Error("Only one positional JSON object argument is supported.");
161
+ }
162
+ jsonArg = trimmed;
163
+ }
164
+ if (jsonArg === undefined)
165
+ return {};
166
+ let parsed;
167
+ try {
168
+ parsed = JSON.parse(jsonArg);
169
+ }
170
+ catch (error) {
171
+ const message = error instanceof Error ? error.message : String(error);
172
+ throw new Error(`Invalid positional JSON argument: ${message}`);
173
+ }
174
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
175
+ throw new Error("Positional JSON argument must be a JSON object.");
176
+ }
143
177
  return parsed;
144
178
  }
145
179
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/scripts/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic action dispatcher for @agent-native/core apps.\n *\n * Dynamically imports and runs actions from the app's actions/ directory.\n * Falls back to scripts/ directory for backwards compatibility, then to\n * core scripts (db-schema, db-query, db-exec, etc.) when no local action is found.\n *\n * Actions must export a default function: (args: string[]) => Promise<void>\n *\n * Usage: pnpm action <action-name> [--args]\n */\n\nimport path from \"path\";\nimport fs from \"fs\";\nimport { pathToFileURL } from \"url\";\nimport { coreScripts, getCoreScriptNames } from \"./core-scripts.js\";\nimport { closeDbExec } from \"../db/client.js\";\nimport { loadEnv } from \"./utils.js\";\nimport {\n runWithRequestContext,\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\nimport { resolveDevUserEmail } from \"./dev-session.js\";\nimport { notifyActionChange } from \"../server/action-change.js\";\nimport type { ActionEntry } from \"../agent/production-agent.js\";\n\n// Load .env from cwd so DATABASE_URL and other vars are available to all actions.\nloadEnv();\n\nexport interface RunScriptOptions {\n /**\n * Actions contributed by packages rather than the app's local `actions/`\n * directory. Local app actions still win on name collision.\n */\n packageActions?: Record<string, ActionEntry>;\n /** Help-section label for package actions. */\n packageActionLabel?: string;\n}\n\nasync function runAppDbPluginIfPresent(): Promise<void> {\n const dbPluginPath = path.resolve(process.cwd(), \"server/plugins/db.ts\");\n if (!fs.existsSync(dbPluginPath)) return;\n\n const mod = await import(/* @vite-ignore */ pathToFileURL(dbPluginPath).href);\n const plugin = mod.default;\n if (typeof plugin === \"function\") {\n await plugin({});\n }\n}\n\n/**\n * Run the action dispatcher. Call this from your app's actions/run.ts (or scripts/run.ts):\n *\n * import { runScript } from \"@agent-native/core\";\n * runScript();\n */\nexport async function runScript(options: RunScriptOptions = {}): Promise<void> {\n const actionName = process.argv[2];\n\n if (!actionName || actionName === \"--help\") {\n console.log(`Usage: pnpm action <action-name> [--arg value ...]`);\n console.log(`\\nRun any action with --help for usage details.`);\n\n // List local actions (try actions/ first, then scripts/)\n const actionsDir = path.resolve(process.cwd(), \"actions\");\n const scriptsDir = path.resolve(process.cwd(), \"scripts\");\n const localDir = fs.existsSync(actionsDir) ? actionsDir : scriptsDir;\n if (fs.existsSync(localDir)) {\n const locals = fs\n .readdirSync(localDir)\n .filter((f) => f.endsWith(\".ts\") && f !== \"run.ts\")\n .map((f) => f.replace(/\\.ts$/, \"\"));\n if (locals.length > 0) {\n console.log(`\\nApp actions:`);\n for (const name of locals) {\n console.log(` ${name}`);\n }\n }\n }\n\n const packageActionNames = Object.keys(options.packageActions ?? {}).sort();\n if (packageActionNames.length > 0) {\n console.log(`\\n${options.packageActionLabel ?? \"Package actions\"}:`);\n for (const name of packageActionNames) {\n console.log(` ${name}`);\n }\n }\n\n // List core scripts\n const coreNames = getCoreScriptNames();\n if (coreNames.length > 0) {\n console.log(`\\nCore actions (built-in):`);\n for (const name of coreNames) {\n console.log(` ${name}`);\n }\n }\n\n process.exit(0);\n }\n\n // Validate action name (only allow alphanumeric + hyphens)\n if (!/^[a-z][a-z0-9-]*$/.test(actionName)) {\n console.error(`Error: Invalid action name \"${actionName}\"`);\n process.exit(1);\n }\n\n const args = process.argv.slice(3);\n\n // Establish a request context for the duration of this CLI run. Without\n // it, db-exec / db-query / db-patch and any action that calls\n // `getRequestUserEmail()` see no identity and refuse to run. The\n // resolver picks up `AGENT_USER_EMAIL` if explicitly set, otherwise\n // reads the DB session owner only when it is unambiguous (dev-only,\n // narrowly gated — see dev-session.ts).\n //\n // This wrap is intentionally a single point of injection: it covers\n // both the local-action branch and the fall-through to core scripts\n // (db-query, db-exec, …) so every CLI entrypoint runs scoped to a real\n // user. It uses `runWithRequestContext` rather than mutating\n // `process.env.AGENT_USER_EMAIL` because env mutation leaks across\n // boundaries — see the cautionary comment in\n // `server/request-context.ts` about exactly that pattern.\n const userEmail = await resolveDevUserEmail();\n const orgId = process.env.AGENT_ORG_ID || undefined;\n\n return runWithRequestContext({ userEmail, orgId }, () =>\n dispatchAction(actionName, args, options),\n );\n}\n\nfunction coerceCliValue(\n value: string,\n coerceBooleans: boolean,\n): string | boolean {\n if (!coerceBooleans) return value;\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n return value;\n}\n\nfunction setParsedArg(\n parsed: Record<string, unknown>,\n key: string,\n value: unknown,\n) {\n const existing = parsed[key];\n if (existing === undefined) {\n parsed[key] = value;\n return;\n }\n parsed[key] = Array.isArray(existing)\n ? [...existing, value]\n : [existing, value];\n}\n\nfunction parseActionArgs(\n args: string[],\n options: { coerceBooleans?: boolean } = {},\n): Record<string, unknown> {\n const parsed: Record<string, unknown> = {};\n const coerceBooleans = options.coerceBooleans ?? false;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (!arg.startsWith(\"--\")) continue;\n const eqIdx = arg.indexOf(\"=\");\n if (eqIdx > 0) {\n setParsedArg(\n parsed,\n arg.slice(2, eqIdx),\n coerceCliValue(arg.slice(eqIdx + 1), coerceBooleans),\n );\n } else {\n const key = arg.slice(2);\n const next = args[i + 1];\n if (next !== undefined && !next.startsWith(\"--\")) {\n setParsedArg(parsed, key, coerceCliValue(next, coerceBooleans));\n i++;\n } else {\n setParsedArg(parsed, key, coerceBooleans ? true : \"true\");\n }\n }\n }\n return parsed;\n}\n\n/**\n * Build the `ctx` passed as the action's second arg for CLI dispatch. The\n * identity comes from the `runWithRequestContext` wrap in `runScript` (which\n * resolves `AGENT_USER_EMAIL` / the dev session); we never inject a dev\n * identity here beyond what that wrap already established.\n */\nfunction cliActionCtx(): import(\"../action.js\").ActionRunContext {\n return {\n userEmail: getRequestUserEmail(),\n orgId: getRequestOrgId() ?? null,\n caller: \"cli\",\n };\n}\n\nasync function dispatchAction(\n actionName: string,\n args: string[],\n options: RunScriptOptions,\n): Promise<void> {\n // 1. Try local app action first (actions/ then scripts/ for backwards compat)\n const actionsPath = path.resolve(\n process.cwd(),\n \"actions\",\n `${actionName}.ts`,\n );\n const scriptsPath = path.resolve(\n process.cwd(),\n \"scripts\",\n `${actionName}.ts`,\n );\n const localPath = fs.existsSync(actionsPath) ? actionsPath : scriptsPath;\n\n if (fs.existsSync(localPath)) {\n try {\n await runAppDbPluginIfPresent();\n const mod = await import(\n /* @vite-ignore */ pathToFileURL(localPath).href\n );\n const handler = mod.default;\n // Support defineAction-style default exports (object with run method)\n if (\n handler &&\n typeof handler === \"object\" &&\n typeof handler.run === \"function\"\n ) {\n const parsed = parseActionArgs(args, { coerceBooleans: true });\n const result = await handler.run(parsed, cliActionCtx());\n if (handler.readOnly !== true) {\n await notifyActionChange({ actionName }).catch(() => {});\n }\n if (result) console.log(result);\n } else if (typeof handler === \"function\") {\n await handler(args);\n } else {\n console.error(\n `Action \"${actionName}\" does not export a default function or defineAction.`,\n );\n process.exit(1);\n }\n await closeDbExec().catch(() => {});\n process.exit(0);\n } catch (err: any) {\n await closeDbExec().catch(() => {});\n console.error(`Action \"${actionName}\" failed:`, err.message || err);\n process.exit(1);\n }\n }\n\n // 2. Try package-contributed actions (e.g. @agent-native/dispatch)\n const packageAction = options.packageActions?.[actionName];\n if (packageAction) {\n try {\n await runAppDbPluginIfPresent();\n const parsed = parseActionArgs(args, { coerceBooleans: true });\n const result = await packageAction.run(\n parsed as Record<string, string>,\n cliActionCtx(),\n );\n if (packageAction.readOnly !== true) {\n await notifyActionChange({ actionName }).catch(() => {});\n }\n if (result) console.log(result);\n await closeDbExec().catch(() => {});\n process.exit(0);\n } catch (err: any) {\n await closeDbExec().catch(() => {});\n console.error(`Action \"${actionName}\" failed:`, err.message || err);\n process.exit(1);\n }\n }\n\n // 3. Fall back to core scripts\n const coreScript = coreScripts[actionName];\n if (coreScript) {\n try {\n await coreScript(args);\n await closeDbExec().catch(() => {});\n process.exit(0);\n } catch (err: any) {\n await closeDbExec().catch(() => {});\n console.error(`Core action \"${actionName}\" failed:`, err.message || err);\n process.exit(1);\n }\n }\n\n // 4. Not found anywhere\n console.error(\n `Error: Action \"${actionName}\" not found. Run \"pnpm action --help\" for available actions.`,\n );\n process.exit(1);\n}\n"]}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/scripts/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic action dispatcher for @agent-native/core apps.\n *\n * Dynamically imports and runs actions from the app's actions/ directory.\n * Falls back to scripts/ directory for backwards compatibility, then to\n * core scripts (db-schema, db-query, db-exec, etc.) when no local action is found.\n *\n * Actions must export a default function: (args: string[]) => Promise<void>\n *\n * Usage: pnpm action <action-name> ['{\"arg\":\"value\"}'] [--args]\n */\n\nimport path from \"path\";\nimport fs from \"fs\";\nimport { pathToFileURL } from \"url\";\nimport { coreScripts, getCoreScriptNames } from \"./core-scripts.js\";\nimport { closeDbExec } from \"../db/client.js\";\nimport { loadEnv } from \"./utils.js\";\nimport {\n runWithRequestContext,\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\nimport { resolveDevUserEmail } from \"./dev-session.js\";\nimport { notifyActionChange } from \"../server/action-change.js\";\nimport type { ActionEntry } from \"../agent/production-agent.js\";\n\n// Load .env from cwd so DATABASE_URL and other vars are available to all actions.\nloadEnv();\n\nexport interface RunScriptOptions {\n /**\n * Actions contributed by packages rather than the app's local `actions/`\n * directory. Local app actions still win on name collision.\n */\n packageActions?: Record<string, ActionEntry>;\n /** Help-section label for package actions. */\n packageActionLabel?: string;\n}\n\nasync function runAppDbPluginIfPresent(): Promise<void> {\n const dbPluginPath = path.resolve(process.cwd(), \"server/plugins/db.ts\");\n if (!fs.existsSync(dbPluginPath)) return;\n\n const mod = await import(/* @vite-ignore */ pathToFileURL(dbPluginPath).href);\n const plugin = mod.default;\n if (typeof plugin === \"function\") {\n await plugin({});\n }\n}\n\n/**\n * Run the action dispatcher. Call this from your app's actions/run.ts (or scripts/run.ts):\n *\n * import { runScript } from \"@agent-native/core\";\n * runScript();\n */\nexport async function runScript(options: RunScriptOptions = {}): Promise<void> {\n const actionName = process.argv[2];\n\n if (!actionName || actionName === \"--help\") {\n console.log(\n `Usage: pnpm action <action-name> ['{\"arg\":\"value\"}'] [--arg value ...]`,\n );\n console.log(`\\nRun any action with --help for usage details.`);\n\n // List local actions (try actions/ first, then scripts/)\n const actionsDir = path.resolve(process.cwd(), \"actions\");\n const scriptsDir = path.resolve(process.cwd(), \"scripts\");\n const localDir = fs.existsSync(actionsDir) ? actionsDir : scriptsDir;\n if (fs.existsSync(localDir)) {\n const locals = fs\n .readdirSync(localDir)\n .filter((f) => f.endsWith(\".ts\") && f !== \"run.ts\")\n .map((f) => f.replace(/\\.ts$/, \"\"));\n if (locals.length > 0) {\n console.log(`\\nApp actions:`);\n for (const name of locals) {\n console.log(` ${name}`);\n }\n }\n }\n\n const packageActionNames = Object.keys(options.packageActions ?? {}).sort();\n if (packageActionNames.length > 0) {\n console.log(`\\n${options.packageActionLabel ?? \"Package actions\"}:`);\n for (const name of packageActionNames) {\n console.log(` ${name}`);\n }\n }\n\n // List core scripts\n const coreNames = getCoreScriptNames();\n if (coreNames.length > 0) {\n console.log(`\\nCore actions (built-in):`);\n for (const name of coreNames) {\n console.log(` ${name}`);\n }\n }\n\n process.exit(0);\n }\n\n // Validate action name (only allow alphanumeric + hyphens)\n if (!/^[a-z][a-z0-9-]*$/.test(actionName)) {\n console.error(`Error: Invalid action name \"${actionName}\"`);\n process.exit(1);\n }\n\n const args = process.argv.slice(3);\n\n // Establish a request context for the duration of this CLI run. Without\n // it, db-exec / db-query / db-patch and any action that calls\n // `getRequestUserEmail()` see no identity and refuse to run. The\n // resolver picks up `AGENT_USER_EMAIL` if explicitly set, otherwise\n // reads the DB session owner only when it is unambiguous (dev-only,\n // narrowly gated — see dev-session.ts).\n //\n // This wrap is intentionally a single point of injection: it covers\n // both the local-action branch and the fall-through to core scripts\n // (db-query, db-exec, …) so every CLI entrypoint runs scoped to a real\n // user. It uses `runWithRequestContext` rather than mutating\n // `process.env.AGENT_USER_EMAIL` because env mutation leaks across\n // boundaries — see the cautionary comment in\n // `server/request-context.ts` about exactly that pattern.\n const userEmail = await resolveDevUserEmail();\n const orgId = process.env.AGENT_ORG_ID || undefined;\n\n return runWithRequestContext({ userEmail, orgId }, () =>\n dispatchAction(actionName, args, options),\n );\n}\n\nfunction coerceCliValue(\n value: string,\n coerceBooleans: boolean,\n): string | boolean {\n if (!coerceBooleans) return value;\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n return value;\n}\n\nfunction setParsedArg(\n parsed: Record<string, unknown>,\n key: string,\n value: unknown,\n) {\n const existing = parsed[key];\n if (existing === undefined) {\n parsed[key] = value;\n return;\n }\n parsed[key] = Array.isArray(existing)\n ? [...existing, value]\n : [existing, value];\n}\n\nfunction parseActionArgs(\n args: string[],\n options: { coerceBooleans?: boolean } = {},\n): Record<string, unknown> {\n const parsed = parsePositionalJsonArg(args);\n const flagArgs: Record<string, unknown> = {};\n const coerceBooleans = options.coerceBooleans ?? false;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (!arg.startsWith(\"--\")) continue;\n const eqIdx = arg.indexOf(\"=\");\n if (eqIdx > 0) {\n setParsedArg(\n flagArgs,\n arg.slice(2, eqIdx),\n coerceCliValue(arg.slice(eqIdx + 1), coerceBooleans),\n );\n } else {\n const key = arg.slice(2);\n const next = args[i + 1];\n if (next !== undefined && !next.startsWith(\"--\")) {\n setParsedArg(flagArgs, key, coerceCliValue(next, coerceBooleans));\n i++;\n } else {\n setParsedArg(flagArgs, key, coerceBooleans ? true : \"true\");\n }\n }\n }\n return { ...parsed, ...flagArgs };\n}\n\nfunction parsePositionalJsonArg(args: string[]): Record<string, unknown> {\n let jsonArg: string | undefined;\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg.startsWith(\"--\")) {\n if (!arg.includes(\"=\") && args[i + 1] && !args[i + 1].startsWith(\"--\")) {\n i++;\n }\n continue;\n }\n\n const trimmed = arg.trim();\n if (!trimmed.startsWith(\"{\")) continue;\n if (jsonArg !== undefined) {\n throw new Error(\"Only one positional JSON object argument is supported.\");\n }\n jsonArg = trimmed;\n }\n\n if (jsonArg === undefined) return {};\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(jsonArg);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid positional JSON argument: ${message}`);\n }\n\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"Positional JSON argument must be a JSON object.\");\n }\n return parsed as Record<string, unknown>;\n}\n\n/**\n * Build the `ctx` passed as the action's second arg for CLI dispatch. The\n * identity comes from the `runWithRequestContext` wrap in `runScript` (which\n * resolves `AGENT_USER_EMAIL` / the dev session); we never inject a dev\n * identity here beyond what that wrap already established.\n */\nfunction cliActionCtx(): import(\"../action.js\").ActionRunContext {\n return {\n userEmail: getRequestUserEmail(),\n orgId: getRequestOrgId() ?? null,\n caller: \"cli\",\n };\n}\n\nasync function dispatchAction(\n actionName: string,\n args: string[],\n options: RunScriptOptions,\n): Promise<void> {\n // 1. Try local app action first (actions/ then scripts/ for backwards compat)\n const actionsPath = path.resolve(\n process.cwd(),\n \"actions\",\n `${actionName}.ts`,\n );\n const scriptsPath = path.resolve(\n process.cwd(),\n \"scripts\",\n `${actionName}.ts`,\n );\n const localPath = fs.existsSync(actionsPath) ? actionsPath : scriptsPath;\n\n if (fs.existsSync(localPath)) {\n try {\n await runAppDbPluginIfPresent();\n const mod = await import(\n /* @vite-ignore */ pathToFileURL(localPath).href\n );\n const handler = mod.default;\n // Support defineAction-style default exports (object with run method)\n if (\n handler &&\n typeof handler === \"object\" &&\n typeof handler.run === \"function\"\n ) {\n const parsed = parseActionArgs(args, { coerceBooleans: true });\n const result = await handler.run(parsed, cliActionCtx());\n if (handler.readOnly !== true) {\n await notifyActionChange({ actionName }).catch(() => {});\n }\n if (result) console.log(result);\n } else if (typeof handler === \"function\") {\n await handler(args);\n } else {\n console.error(\n `Action \"${actionName}\" does not export a default function or defineAction.`,\n );\n process.exit(1);\n }\n await closeDbExec().catch(() => {});\n process.exit(0);\n } catch (err: any) {\n await closeDbExec().catch(() => {});\n console.error(`Action \"${actionName}\" failed:`, err.message || err);\n process.exit(1);\n }\n }\n\n // 2. Try package-contributed actions (e.g. @agent-native/dispatch)\n const packageAction = options.packageActions?.[actionName];\n if (packageAction) {\n try {\n await runAppDbPluginIfPresent();\n const parsed = parseActionArgs(args, { coerceBooleans: true });\n const result = await packageAction.run(\n parsed as Record<string, string>,\n cliActionCtx(),\n );\n if (packageAction.readOnly !== true) {\n await notifyActionChange({ actionName }).catch(() => {});\n }\n if (result) console.log(result);\n await closeDbExec().catch(() => {});\n process.exit(0);\n } catch (err: any) {\n await closeDbExec().catch(() => {});\n console.error(`Action \"${actionName}\" failed:`, err.message || err);\n process.exit(1);\n }\n }\n\n // 3. Fall back to core scripts\n const coreScript = coreScripts[actionName];\n if (coreScript) {\n try {\n await coreScript(args);\n await closeDbExec().catch(() => {});\n process.exit(0);\n } catch (err: any) {\n await closeDbExec().catch(() => {});\n console.error(`Core action \"${actionName}\" failed:`, err.message || err);\n process.exit(1);\n }\n }\n\n // 4. Not found anywhere\n console.error(\n `Error: Action \"${actionName}\" not found. Run \"pnpm action --help\" for available actions.`,\n );\n process.exit(1);\n}\n"]}
@@ -45,7 +45,7 @@ features. Prefer this app's own `AGENTS.md` for app-specific rules.
45
45
  Run actions from this app root:
46
46
 
47
47
  ```bash
48
- pnpm action hello --name Builder
48
+ pnpm action hello '{"name":"Builder"}'
49
49
  ```
50
50
 
51
51
  Run the app-agent loop against those actions:
@@ -4,7 +4,7 @@ Install dependencies, then run the hello action:
4
4
 
5
5
  ```bash
6
6
  pnpm install
7
- pnpm action hello --name Builder
7
+ pnpm action hello '{"name":"Builder"}'
8
8
  ```
9
9
 
10
10
  `actions/run.ts` is only the shared CLI dispatcher that powers
@@ -56,6 +56,13 @@ export default defineAction({
56
56
 
57
57
  Run it from the same folder:
58
58
 
59
+ ```bash
60
+ pnpm action hello '{"name":"Steve"}'
61
+ ```
62
+
63
+ The CLI accepts a JSON object as the action input, which matches the structured
64
+ tool calls agents already make. Simple flags still work for quick manual runs:
65
+
59
66
  ```bash
60
67
  pnpm action hello --name Steve
61
68
  ```
@@ -415,10 +422,12 @@ headless, render in chat, or grow into a full screen.
415
422
  Every action is runnable via `pnpm action`:
416
423
 
417
424
  ```bash
418
- pnpm action reply-to-email --emailId thread-123 --body "Thanks!"
425
+ pnpm action reply-to-email '{"emailId":"thread-123","body":"Thanks!"}'
419
426
  ```
420
427
 
421
- Flags are parsed into the shape your schema expects. Useful for agent-dev loops, scripts, and cron.
428
+ JSON input is the preferred shape for agents and complex objects. Flags are
429
+ still parsed into the same schema shape for simple manual runs and existing
430
+ scripts. Useful for agent-dev loops, scripts, and cron.
422
431
 
423
432
  ## Calling it from another agent (A2A) {#a2a}
424
433
 
@@ -91,6 +91,31 @@ without requiring you to clone or fork the entire Content app.
91
91
  small sandboxed widgets that can render in app slots while their source stays in
92
92
  the repo.
93
93
 
94
+ ## Install Content Into A Repo
95
+
96
+ For an existing docs, blog, or MDX workspace, install the Content local-files
97
+ skill:
98
+
99
+ ```bash
100
+ npx @agent-native/core@latest skills add content --mode local-files --scope project
101
+ ```
102
+
103
+ This copies the `content` skill into the repo's agent skill folders and writes
104
+ or updates `agent-native.json` with Content defaults:
105
+
106
+ - `mode: "local-files"` at the workspace level
107
+ - `apps.content.mode: "local-files"`
108
+ - content roots for `docs/`, `blog/`, `content/`, and `resources/`
109
+ - `components/` for local MDX components
110
+ - `extensions/` for local extension widgets
111
+
112
+ The installed skill tells coding agents to use Content actions
113
+ (`list-documents`, `get-document`, `edit-document`, `update-document`,
114
+ `share-local-file-document`, and component-file actions) when a local Content app
115
+ or Agent Native Desktop bridge exposes them. If no bridge is running, the skill
116
+ falls back to safe direct repo edits while preserving frontmatter, imports, JSX,
117
+ and unknown MDX.
118
+
94
119
  ## Configuration
95
120
 
96
121
  Add `agent-native.json` to the repo or workspace root:
@@ -202,6 +202,9 @@ offline work, or privacy-sensitive use.
202
202
  npx @agent-native/core@latest skills add visual-plan
203
203
  npx @agent-native/core@latest skills add assets
204
204
 
205
+ # Repo-first Content docs/blog/MDX editing.
206
+ npx @agent-native/core@latest skills add content --mode local-files --scope project
207
+
205
208
  # Vercel/open Skills CLI: exported instructions only, no MCP config.
206
209
  npx skills@latest add BuilderIO/agent-native --skill assets
207
210
 
@@ -144,7 +144,54 @@ Clips is a larger template with a native recorder (it ships a desktop companion
144
144
  1. **Video storage (required).** Connect a storage backend through the onboarding wizard. The easiest path is Builder.io (free during beta, one-click). For self-hosted storage, set `S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, and optionally `S3_REGION` and `S3_PUBLIC_BASE_URL`. Cloudflare R2 and DigitalOcean Spaces use the same env vars with the `R2_*` prefix.
145
145
  2. **Google Calendar (optional).** To sync upcoming meetings, connect a Google Calendar account from Settings. The OAuth callback URL in dev is `http://localhost:8094/_agent-native/google/callback`. Set up a Google OAuth client in [Google Cloud Console](https://console.cloud.google.com/) with the Gmail and Google Calendar APIs enabled.
146
146
  3. **Screen-capture permissions.** On macOS, grant Screen Recording permission to the browser (or the desktop companion app) in System Settings → Privacy & Security → Screen Recording.
147
- 4. **Slack previews (optional).** Create a Slack app with `chat:write`, `links:read`, `links:write`, and `links.embed:write`; subscribe to `link_shared`; add your Clips share domain under **App Unfurl Domains**; and set the Request URL to `https://your-clips.example.com/api/slack/unfurl`. Configure `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` in the Clips deployment.
147
+ 4. **Slack previews (optional).** Create a Slack app with `links:read`, `links:write`, and `links.embed:write`; subscribe to `link_shared`; add your Clips share domain under **App Unfurl Domains**; and set the Request URL to `https://your-clips.example.com/api/slack/unfurl`. Configure `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET` in the Clips deployment.
148
+
149
+ ### Host your own Clips server
150
+
151
+ The hosted Clips app at [clips.agent-native.com](https://clips.agent-native.com)
152
+ is just a deployed copy of the Clips template. To run your own server, scaffold
153
+ the template, deploy it like any other agent-native app, then point the desktop
154
+ tray app at your deployment.
155
+
156
+ 1. **Create the app.**
157
+
158
+ ```bash
159
+ npx @agent-native/core@latest create my-clips --standalone --template clips
160
+ cd my-clips
161
+ pnpm install
162
+ ```
163
+
164
+ 2. **Configure production state.** Set a persistent `DATABASE_URL`, the normal
165
+ production auth/secrets variables from [Deployment](/docs/deployment), and a
166
+ video storage provider. Builder.io Connect is the easiest storage path; for
167
+ self-hosted storage, use `S3_*` or `R2_*` variables for an S3-compatible
168
+ bucket.
169
+
170
+ 3. **Deploy the web app.** For a plain Node deploy:
171
+
172
+ ```bash
173
+ pnpm build
174
+ node .output/server/index.mjs
175
+ ```
176
+
177
+ You can also use any Nitro target from [Deployment](/docs/deployment), such
178
+ as Netlify, Vercel, Cloudflare Pages, AWS Lambda, or Deno Deploy. Make sure
179
+ `BETTER_AUTH_URL` is the public Clips origin, for example
180
+ `https://clips.example.com`.
181
+
182
+ 4. **Connect the desktop tray app.** Open Clips Desktop settings and set
183
+ **Clips server URL** to the public base URL of your deployment, for example
184
+ `https://clips.example.com`. If the app is mounted under a workspace path,
185
+ include that path, such as `https://example.com/clips`. Click **Connect**,
186
+ then sign in with an account on that Clips server.
187
+
188
+ 5. **Connect optional integrations.** Google Calendar powers the Meetings tab,
189
+ `GEMINI_API_KEY` or Builder.io Connect powers transcript cleanup and titles,
190
+ `GROQ_API_KEY` can provide speech-to-text fallback, and Slack credentials
191
+ enable playable Slack unfurls.
192
+
193
+ For local development, run the web app with `pnpm dev` and point the desktop
194
+ tray app at `http://localhost:8094`.
148
195
 
149
196
  ### Key features
150
197
 
@@ -90,6 +90,21 @@ SQL-backed sharing. See [Local File Mode](/docs/local-file-mode) for the
90
90
  standalone repo layout, configuration, custom MDX components, local
91
91
  `extensions/` widgets, and production safety guide.
92
92
 
93
+ To install the Content local-files skill into an existing repo:
94
+
95
+ ```bash
96
+ npx @agent-native/core@latest skills add content --mode local-files --scope project
97
+ ```
98
+
99
+ The installer copies the `content` skill for your coding agent and writes or
100
+ updates `agent-native.json` with Content roots for `docs/`, `blog/`, `content/`,
101
+ and `resources/`. When a local Content app, Agent Native Desktop, or trusted
102
+ local bridge is running, agents should use Content actions such as
103
+ `list-documents`, `get-document`, `edit-document`, `update-document`, and
104
+ `share-local-file-document` instead of raw filesystem writes. Without that local
105
+ bridge, the installed skill still gives the agent the repo-editing contract for
106
+ safe Markdown/MDX edits.
107
+
93
108
  ## For developers
94
109
 
95
110
  The rest of this doc is for anyone forking the Content template or extending it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.66.7",
3
+ "version": "0.66.8",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -45,7 +45,7 @@ features. Prefer this app's own `AGENTS.md` for app-specific rules.
45
45
  Run actions from this app root:
46
46
 
47
47
  ```bash
48
- pnpm action hello --name Builder
48
+ pnpm action hello '{"name":"Builder"}'
49
49
  ```
50
50
 
51
51
  Run the app-agent loop against those actions:
@@ -4,7 +4,7 @@ Install dependencies, then run the hello action:
4
4
 
5
5
  ```bash
6
6
  pnpm install
7
- pnpm action hello --name Builder
7
+ pnpm action hello '{"name":"Builder"}'
8
8
  ```
9
9
 
10
10
  `actions/run.ts` is only the shared CLI dispatcher that powers