Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Logs
logs
!packages/cli/src/commands/automations/logs
*.log
npm-debug.log*
yarn-debug.log*
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/commands/automations/create/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export default command({
throw new Error("Provide --prompt <text> or --prompt-file <path>");
}

if (!options.project) {
throw new Error("Provide --project (required)");
if (!options.project && !options.workspace) {
throw new Error("Provide --project or --workspace");
}

const agentConfig = options.agentConfigFile
Expand All @@ -88,8 +88,8 @@ export default command({
prompt,
agentConfig,
targetHostId: options.host ?? null,
v2ProjectId: options.project,
v2WorkspaceId: options.workspace ?? null,
v2ProjectId: options.project ?? undefined,
v2WorkspaceId: options.workspace ?? undefined,
rrule: options.rrule,
dtstart: options.dtstart ? new Date(options.dtstart) : undefined,
timezone: options.timezone ?? DEFAULT_TIMEZONE,
Expand Down
41 changes: 41 additions & 0 deletions packages/cli/src/commands/automations/logs/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { number, positional, table } from "@superset/cli-framework";
import { command } from "../../../lib/command";
import { formatAutomationDate } from "../format";

export default command({
description: "List recent runs of an automation",
args: [positional("id").required().desc("Automation id")],
options: {
limit: number()
.int()
.min(1)
.max(100)
.default(20)
.desc("Max runs to return (1-100)"),
},
run: async ({ ctx, args, options }) => {
const automationId = args.id as string;
return ctx.api.automation.listRuns.query({
automationId,
limit: options.limit,
});
},
display: (data) =>
table(
(data as Record<string, unknown>[]).map((row) => ({
id: row.id,
status: row.status,
scheduledFor: formatAutomationDate(
row.scheduledFor as Date | string | null | undefined,
null,
),
dispatchedAt: formatAutomationDate(
row.dispatchedAt as Date | string | null | undefined,
null,
),
host: row.hostId ?? "—",
})),
["id", "status", "scheduledFor", "dispatchedAt", "host"],
["RUN ID", "STATUS", "SCHEDULED", "DISPATCHED", "HOST"],
),
});
18 changes: 18 additions & 0 deletions packages/cli/src/commands/automations/update/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export default command({
"Path to a JSON file with a full ResolvedAgentConfig (overrides --agent)",
),
host: string().desc("New target host id"),
project: string().desc("New v2 project id"),
workspace: string().desc("New v2 workspace id"),
mcpScope: string().desc("Comma-separated MCP scope strings"),
enabled: boolean().desc("Enable or pause the automation"),
},
run: async ({ ctx, args, options }) => {
Expand All @@ -69,6 +72,14 @@ export default command({
? resolveDefaultAgentConfig(options.agent)
: undefined;

const mcpScope =
options.mcpScope !== undefined
? options.mcpScope
.split(",")
.map((s) => s.trim())
.filter(Boolean)
: undefined;
Comment on lines +75 to +81
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Empty --mcp-scope silently clears all scopes

If a user passes --mcp-scope "" or --mcp-scope " ", the split-trim-filter chain produces [], and mcpScope: [] is then spread into the mutation — clearing every scope on the automation. A guard that rejects an all-whitespace value would make the intent explicit and prevent accidental scope-clearing.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/cli/src/commands/automations/update/command.ts
Line: 75-81

Comment:
**Empty `--mcp-scope` silently clears all scopes**

If a user passes `--mcp-scope ""` or `--mcp-scope " "`, the split-trim-filter chain produces `[]`, and `mcpScope: []` is then spread into the mutation — clearing every scope on the automation. A guard that rejects an all-whitespace value would make the intent explicit and prevent accidental scope-clearing.

How can I resolve this? If you propose a fix, please make it concise.


const result = await ctx.api.automation.update.mutate({
id,
name: options.name,
Expand All @@ -77,6 +88,13 @@ export default command({
dtstart: options.dtstart ? new Date(options.dtstart) : undefined,
agentConfig,
...(options.host !== undefined ? { targetHostId: options.host } : {}),
...(options.project !== undefined
? { v2ProjectId: options.project }
: {}),
...(options.workspace !== undefined
? { v2WorkspaceId: options.workspace }
: {}),
...(mcpScope !== undefined ? { mcpScope } : {}),
});

return {
Expand Down
32 changes: 31 additions & 1 deletion packages/cli/src/commands/tasks/update/command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CLIError, positional, string } from "@superset/cli-framework";
import { CLIError, number, positional, string } from "@superset/cli-framework";
import { isValid, parseISO } from "date-fns";
import { command } from "../../../lib/command";

export default command({
Expand All @@ -11,18 +12,47 @@ export default command({
.enum("urgent", "high", "medium", "low", "none")
.desc("Priority"),
assignee: string().desc("Assignee user ID"),
statusId: string().desc("Status ID"),
prUrl: string().desc("Linked PR URL"),
estimate: number().int().min(1).desc("Story-point estimate"),
dueDate: string().desc("Due date (ISO 8601)"),
labels: string().desc("Comma-separated labels"),
},
run: async ({ ctx, args, options }) => {
const idOrSlug = args.idOrSlug as string;
const task = await ctx.api.task.byIdOrSlug.query(idOrSlug);
if (!task) throw new CLIError(`Task not found: ${idOrSlug}`);

let dueDate: Date | undefined;
if (options.dueDate !== undefined) {
const parsed = parseISO(options.dueDate);
if (!isValid(parsed)) {
throw new CLIError(
`--due-date: invalid ISO 8601 date "${options.dueDate}"`,
);
}
dueDate = parsed;
}

const labels =
options.labels !== undefined
? options.labels
.split(",")
.map((label) => label.trim())
.filter(Boolean)
: undefined;

const result = await ctx.api.task.update.mutate({
id: task.id,
title: options.title ?? undefined,
description: options.description ?? undefined,
priority: options.priority ?? undefined,
assigneeId: options.assignee ?? undefined,
statusId: options.statusId ?? undefined,
prUrl: options.prUrl ?? undefined,
estimate: options.estimate ?? undefined,
dueDate,
labels,
});

return {
Expand Down
Loading