diff --git a/apps/docs/content/docs/cli/cli-reference.mdx b/apps/docs/content/docs/cli/cli-reference.mdx
index 3565651f14d..a0619b5b1c7 100644
--- a/apps/docs/content/docs/cli/cli-reference.mdx
+++ b/apps/docs/content/docs/cli/cli-reference.mdx
@@ -606,7 +606,7 @@ List automations in the active organization.
output="Automation"
>
Get an automation's metadata. The prompt body is omitted — use
-[`automations prompt`](#superset-automations-prompt-id) to read it.
+[`automations prompt get`](#superset-automations-prompt-get-id) to read it.
Use [`automations logs`](#superset-automations-logs-id) for run history.
@@ -659,33 +659,50 @@ superset automations create \
>
Update an automation's metadata (name, schedule, agent, host). All flags
optional. Omitting a flag preserves the existing value — `undefined` means
-"no change", not "clear". Use [`automations prompt`](#superset-automations-prompt-id)
-to read or replace the prompt body.
+"no change", not "clear". Use [`automations prompt get`](#superset-automations-prompt-get-id)
+or [`automations prompt set`](#superset-automations-prompt-set-id) to read or
+replace the prompt body.
+Print an automation's prompt body to stdout. The output is the raw prompt
+with no trailing newline added, so `prompt get` and `prompt set` round-trip
+byte-exactly.
+
+```bash
+# Read to a file
+superset automations prompt get aut_… > prompt.md
+
+# Verify a push landed
+superset automations prompt get aut_… | diff - ./prompt.md
+```
+
+
+", description: "Read the new prompt from a file. Use `-` for stdin." },
+ { flag: "--from-file ", required: true, description: "Read the new prompt from a file. Use `-` for stdin." },
]}
- output="Markdown (read mode) or Automation (write mode)"
+ output="Automation"
>
-Read or replace an automation's prompt body. Without `--from-file`, prints
-the current prompt to stdout. With `--from-file ` or piped stdin,
-replaces the prompt verbatim.
+Replace an automation's prompt body. The new prompt fully overwrites the
+old one.
```bash
-# Read
-superset automations prompt aut_… > prompt.md
-
# Write from file
-superset automations prompt aut_… --from-file ./prompt.md
+superset automations prompt set aut_… --from-file ./prompt.md
# Write from stdin
-cat ./prompt.md | superset automations prompt aut_…
+cat ./prompt.md | superset automations prompt set aut_… --from-file -
```
diff --git a/apps/docs/content/docs/sdk/reference.mdx b/apps/docs/content/docs/sdk/reference.mdx
index bd8ba86f658..b1aa82768b4 100644
--- a/apps/docs/content/docs/sdk/reference.mdx
+++ b/apps/docs/content/docs/sdk/reference.mdx
@@ -187,7 +187,9 @@ Recurring agent runs scheduled by RRULE. Requires a Pro subscription on the org
const automations = await client.automations.list();
```
-Each row includes `scheduleText` — a human-readable rendering of the rrule.
+Each row is an `AutomationSummary` — the `prompt` body is omitted (it can be
+large markdown). Fetch one with `automations.getPrompt(id)`. Each row includes
+`scheduleText`, a human-readable rendering of the rrule.
### `automations.retrieve(id)`
@@ -195,6 +197,8 @@ Each row includes `scheduleText` — a human-readable rendering of the rrule.
const a = await client.automations.retrieve(id);
```
+Returns an `AutomationSummary` (no `prompt` body — call `getPrompt(id)`).
+
### `automations.create(body)`
Create a recurring automation.
diff --git a/packages/cli/src/commands/automations/get/command.ts b/packages/cli/src/commands/automations/get/command.ts
index f25391e0fb1..c72d5da4956 100644
--- a/packages/cli/src/commands/automations/get/command.ts
+++ b/packages/cli/src/commands/automations/get/command.ts
@@ -6,10 +6,7 @@ export default command({
args: [positional("id").required().desc("Automation id")],
run: async ({ ctx, args }) => {
const id = args.id as string;
- const result = await ctx.api.automation.get.query({ id });
- // Prompt is fetched via `superset automations prompt ` (it can be
- // large markdown). Runs are paginated via `superset automations logs `.
- const { prompt: _prompt, ...automation } = result;
+ const automation = await ctx.api.automation.get.query({ id });
return { data: automation };
},
});
diff --git a/packages/cli/src/commands/automations/prompt/command.ts b/packages/cli/src/commands/automations/prompt/command.ts
deleted file mode 100644
index d0113a58b8d..00000000000
--- a/packages/cli/src/commands/automations/prompt/command.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { readFileSync } from "node:fs";
-import { positional, string } from "@superset/cli-framework";
-import { command } from "../../../lib/command";
-
-async function readStdin(): Promise {
- const chunks: Buffer[] = [];
- for await (const chunk of process.stdin) {
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
- }
- return Buffer.concat(chunks).toString("utf-8");
-}
-
-export default command({
- description: "Read or write an automation's prompt",
- args: [positional("id").required().desc("Automation id")],
- options: {
- fromFile: string().desc(
- "Path to a markdown file with the new prompt. Use '-' to read from stdin.",
- ),
- },
- run: async ({ ctx, args, options }) => {
- const id = args.id as string;
- const stdinIsPiped = !process.stdin.isTTY;
- const useStdin =
- options.fromFile === "-" || (!options.fromFile && stdinIsPiped);
-
- if (options.fromFile && options.fromFile !== "-") {
- const next = readFileSync(options.fromFile, "utf-8");
- const result = await ctx.api.automation.setPrompt.mutate({
- id,
- prompt: next,
- });
- return {
- data: { id: result.id, name: result.name, length: next.length },
- message: `Updated prompt for "${result.name}" (${next.length} chars).`,
- };
- }
-
- if (useStdin) {
- const next = await readStdin();
- if (!next.trim()) {
- throw new Error("Refusing to write an empty prompt from stdin.");
- }
- const result = await ctx.api.automation.setPrompt.mutate({
- id,
- prompt: next,
- });
- return {
- data: { id: result.id, name: result.name, length: next.length },
- message: `Updated prompt for "${result.name}" (${next.length} chars).`,
- };
- }
-
- const { prompt } = await ctx.api.automation.getPrompt.query({ id });
- return { data: { id, prompt } };
- },
- display: (data) => {
- const obj = data as { id: string; prompt?: string };
- return obj.prompt ?? "";
- },
-});
diff --git a/packages/cli/src/commands/automations/prompt/get/command.ts b/packages/cli/src/commands/automations/prompt/get/command.ts
new file mode 100644
index 00000000000..b5900c62fa5
--- /dev/null
+++ b/packages/cli/src/commands/automations/prompt/get/command.ts
@@ -0,0 +1,24 @@
+import { isAgentMode, positional } from "@superset/cli-framework";
+import { command } from "../../../../lib/command";
+
+export default command({
+ description: "Print an automation's prompt to stdout",
+ args: [positional("id").required().desc("Automation id")],
+ run: async ({ ctx, args, options }) => {
+ const id = args.id as string;
+ const { prompt } = await ctx.api.automation.getPrompt.query({ id });
+ // `--quiet` is intentionally ignored here: it would route through
+ // `extractIds` and emit only the UUID (which the caller already has
+ // as input), discarding the prompt body. Plain stdout is the right
+ // "machine-friendly" output for this command.
+ const globals = options as Record;
+ if (globals.json === true || isAgentMode()) {
+ return { data: { id, prompt } };
+ }
+ // Default: write the raw prompt with no trailing newline so that
+ // `prompt get > out.md` round-trips byte-exactly with a
+ // subsequent `prompt set --from-file out.md`.
+ process.stdout.write(prompt ?? "");
+ return undefined;
+ },
+});
diff --git a/packages/cli/src/commands/automations/prompt/meta.ts b/packages/cli/src/commands/automations/prompt/meta.ts
new file mode 100644
index 00000000000..71d5919b53f
--- /dev/null
+++ b/packages/cli/src/commands/automations/prompt/meta.ts
@@ -0,0 +1,3 @@
+export default {
+ description: "Read or write an automation's prompt",
+};
diff --git a/packages/cli/src/commands/automations/prompt/set/command.ts b/packages/cli/src/commands/automations/prompt/set/command.ts
new file mode 100644
index 00000000000..3baa55c71d1
--- /dev/null
+++ b/packages/cli/src/commands/automations/prompt/set/command.ts
@@ -0,0 +1,43 @@
+import { readFileSync } from "node:fs";
+import { positional, string } from "@superset/cli-framework";
+import { command } from "../../../../lib/command";
+
+async function readStdin(): Promise {
+ const chunks: Buffer[] = [];
+ for await (const chunk of process.stdin) {
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
+ }
+ return Buffer.concat(chunks).toString("utf-8");
+}
+
+export default command({
+ description: "Replace an automation's prompt from a file or stdin",
+ args: [positional("id").required().desc("Automation id")],
+ options: {
+ fromFile: string()
+ .required()
+ .desc(
+ "Path to a markdown file with the new prompt. Use '-' to read from stdin.",
+ ),
+ },
+ run: async ({ ctx, args, options }) => {
+ const id = args.id as string;
+ const next =
+ options.fromFile === "-"
+ ? await readStdin()
+ : readFileSync(options.fromFile, "utf-8");
+
+ if (!next.trim()) {
+ throw new Error("Refusing to write an empty prompt.");
+ }
+
+ const result = await ctx.api.automation.setPrompt.mutate({
+ id,
+ prompt: next,
+ });
+ return {
+ data: { id: result.id, name: result.name, length: next.length },
+ message: `Updated prompt for "${result.name}" (${next.length} chars).`,
+ };
+ },
+});
diff --git a/packages/mcp-v2/src/tools/automations/get.ts b/packages/mcp-v2/src/tools/automations/get.ts
index d21e0fa72ea..6a06324b96c 100644
--- a/packages/mcp-v2/src/tools/automations/get.ts
+++ b/packages/mcp-v2/src/tools/automations/get.ts
@@ -13,10 +13,7 @@ export function register(server: McpServer): void {
},
handler: async (input, ctx) => {
const caller = createMcpCaller(ctx);
- const { prompt: _prompt, ...rest } = await caller.automation.get({
- id: input.id,
- });
- return rest;
+ return await caller.automation.get({ id: input.id });
},
});
}
diff --git a/packages/mcp-v2/src/tools/automations/list.ts b/packages/mcp-v2/src/tools/automations/list.ts
index 32cc4243f59..09d686f582c 100644
--- a/packages/mcp-v2/src/tools/automations/list.ts
+++ b/packages/mcp-v2/src/tools/automations/list.ts
@@ -6,11 +6,10 @@ export function register(server: McpServer): void {
defineTool(server, {
name: "automations_list",
description:
- "List automations (scheduled agent runs) the calling user owns in the active organization. Returns a summary shape — call automations_get to fetch the full prompt and agentConfig for one automation.",
+ "List automations (scheduled agent runs) the calling user owns in the active organization. Returns a summary shape — call automations_get_prompt to fetch the prompt for one automation, or automations_get for the rest of its config.",
handler: async (_input, ctx) => {
const caller = createMcpCaller(ctx);
- const rows = await caller.automation.list();
- return rows.map(({ prompt: _prompt, ...rest }) => rest);
+ return await caller.automation.list();
},
});
}
diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts
index 5a4a44197ce..98ddc13f10e 100644
--- a/packages/sdk/src/client.ts
+++ b/packages/sdk/src/client.ts
@@ -59,6 +59,7 @@ import {
AutomationRun,
AutomationRunDispatched,
Automations,
+ AutomationSummary,
AutomationUpdateParams,
} from "./resources/automations";
import { Host, HostListResponse, Hosts } from "./resources/hosts";
@@ -1138,6 +1139,7 @@ export declare namespace Superset {
export {
Automations,
Automation,
+ AutomationSummary,
AutomationListResponse,
AutomationCreateParams,
AutomationUpdateParams,
diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts
index 6d4cd708006..234a7ffc554 100644
--- a/packages/sdk/src/index.ts
+++ b/packages/sdk/src/index.ts
@@ -32,6 +32,7 @@ export {
type AutomationRun,
type AutomationRunDispatched,
Automations,
+ type AutomationSummary,
type AutomationUpdateParams,
type Host,
type HostListResponse,
diff --git a/packages/sdk/src/resources/automations.ts b/packages/sdk/src/resources/automations.ts
index 2ba293a081c..ccc995453ba 100644
--- a/packages/sdk/src/resources/automations.ts
+++ b/packages/sdk/src/resources/automations.ts
@@ -4,7 +4,8 @@ import type { RequestOptions } from "../internal/request-options";
export class Automations extends APIResource {
/**
- * List automations in the active organization.
+ * List automations in the active organization. Returned rows omit the
+ * `prompt` body — fetch one prompt with `getPrompt(id)`.
*
* Mirrors `superset automations list`.
*/
@@ -17,12 +18,20 @@ export class Automations extends APIResource {
}
/**
- * Retrieve a single automation by id.
+ * Retrieve a single automation by id. The `prompt` body is omitted —
+ * fetch it separately with `getPrompt(id)`.
*
* Mirrors `superset automations get`.
*/
- retrieve(id: string, options?: RequestOptions): APIPromise {
- return this._client.query("automation.get", { id }, options);
+ retrieve(
+ id: string,
+ options?: RequestOptions,
+ ): APIPromise {
+ return this._client.query(
+ "automation.get",
+ { id },
+ options,
+ );
}
/**
@@ -125,9 +134,10 @@ export class Automations extends APIResource {
}
/**
- * Get the prompt for an automation.
+ * Get the prompt body (markdown) for an automation. `retrieve` and
+ * `list` omit it because it can be large.
*
- * Mirrors `superset automations prompt --get`.
+ * Mirrors `superset automations prompt get`.
*/
getPrompt(
id: string,
@@ -141,9 +151,10 @@ export class Automations extends APIResource {
}
/**
- * Update the prompt for an automation.
+ * Replace the prompt body for an automation. The new prompt fully
+ * overwrites the old one.
*
- * Mirrors `superset automations prompt`.
+ * Mirrors `superset automations prompt set`.
*/
setPrompt(
id: string,
@@ -165,12 +176,15 @@ export interface AgentConfig {
[key: string]: unknown;
}
-export interface Automation {
+/**
+ * Lean automation row returned by `list` and `retrieve`. The `prompt`
+ * body is omitted — call `getPrompt(id)` to fetch it.
+ */
+export interface AutomationSummary {
id: string;
organizationId: string;
ownerUserId: string;
name: string;
- prompt: string;
agentConfig: AgentConfig;
targetHostId: string | null;
v2ProjectId: string;
@@ -187,7 +201,15 @@ export interface Automation {
updatedAt: string;
}
-export type AutomationListResponse = Array;
+/**
+ * Full automation row including the `prompt` body. Returned by mutations
+ * like `create`, `update`, `pause`, `resume`, and `setPrompt`.
+ */
+export interface Automation extends AutomationSummary {
+ prompt: string;
+}
+
+export type AutomationListResponse = Array;
export interface AutomationCreateParams {
name: string;
@@ -252,6 +274,7 @@ export interface AutomationRunDispatched {
export declare namespace Automations {
export type {
Automation,
+ AutomationSummary,
AutomationListResponse,
AutomationCreateParams,
AutomationUpdateParams,
diff --git a/packages/sdk/src/resources/index.ts b/packages/sdk/src/resources/index.ts
index 64380763d6b..562aaf9a0b4 100644
--- a/packages/sdk/src/resources/index.ts
+++ b/packages/sdk/src/resources/index.ts
@@ -8,6 +8,7 @@ export {
type AutomationRun,
type AutomationRunDispatched,
Automations,
+ type AutomationSummary,
type AutomationUpdateParams,
} from "./automations";
export { type Host, type HostListResponse, Hosts } from "./hosts";
diff --git a/packages/trpc/src/router/automation/automation.ts b/packages/trpc/src/router/automation/automation.ts
index 4b5e16652f5..ebf10651fd2 100644
--- a/packages/trpc/src/router/automation/automation.ts
+++ b/packages/trpc/src/router/automation/automation.ts
@@ -18,7 +18,7 @@ import {
parseRrule,
} from "@superset/shared/rrule";
import { TRPCError, type TRPCRouterRecord } from "@trpc/server";
-import { and, desc, eq } from "drizzle-orm";
+import { and, desc, eq, getTableColumns } from "drizzle-orm";
import { z } from "zod";
import { env } from "../../env";
import { protectedProcedure } from "../../trpc";
@@ -156,12 +156,16 @@ async function getAutomationForUser(
}
export const automationRouter = {
- /** List automations scoped to the caller's active organization. */
+ /**
+ * List automations scoped to the caller's active organization. The
+ * `prompt` body is omitted — call `getPrompt` to fetch it for one row.
+ */
list: protectedProcedure.query(async ({ ctx }) => {
const organizationId = await requireActiveOrgMembership(ctx);
+ const { prompt: _prompt, ...summaryCols } = getTableColumns(automations);
const rows = await db
- .select()
+ .select(summaryCols)
.from(automations)
.where(eq(automations.organizationId, organizationId))
.orderBy(desc(automations.createdAt));
@@ -172,20 +176,36 @@ export const automationRouter = {
}));
}),
- /** Get one automation. Use listRuns for run history. */
+ /**
+ * Get one automation's metadata. The `prompt` body is omitted (it can be
+ * large markdown) — call `getPrompt` to fetch it. Use `listRuns` for
+ * run history.
+ */
get: protectedProcedure
.input(z.object({ id: z.string().uuid() }))
.query(async ({ ctx, input }) => {
const organizationId = await requireActiveOrgMembership(ctx);
- const automation = await getAutomationForUser(
- ctx.session.user.id,
- organizationId,
- input.id,
- );
- return {
- ...automation,
- scheduleText: safeDescribeRrule(automation),
- };
+
+ const { prompt: _prompt, ...summaryCols } = getTableColumns(automations);
+ const [row] = await db
+ .select(summaryCols)
+ .from(automations)
+ .where(
+ and(
+ eq(automations.id, input.id),
+ eq(automations.organizationId, organizationId),
+ ),
+ )
+ .limit(1);
+
+ if (!row || row.ownerUserId !== ctx.session.user.id) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Automation not found",
+ });
+ }
+
+ return { ...row, scheduleText: safeDescribeRrule(row) };
}),
create: protectedProcedure