diff --git a/README.md b/README.md index 81de74d..c01dd2c 100644 --- a/README.md +++ b/README.md @@ -59,19 +59,6 @@ smithery auth token # Mint a service token smithery auth token --policy '' # Mint a restricted token ``` -### Automations - -Create and run deterministic MCP tool scripts — no AI involved, just direct tool calls. - -```bash -smithery automation init # Initialize the ~/.smithery automations project -smithery automation create # Create a new automation from a template -smithery automation list # List all automations -smithery automation get # Show automation details and source code -smithery automation run [key=value...] # Run an automation with arguments -smithery automation remove # Delete an automation -``` - ### Namespaces ```bash @@ -100,11 +87,6 @@ smithery tool call github create_issue '{"title":"Bug fix","body":"..."}' smithery skill search "frontend" --json --page 2 smithery skill add anthropics/frontend-design --agent claude-code -# Create and run an automation -smithery automation init -smithery automation create create-ticket -smithery automation run create-ticket title="Bug fix" priority=high - # Publish your MCP server URL smithery mcp publish "https://my-mcp-server.com" -n myorg/my-server ``` diff --git a/build.mjs b/build.mjs index 7857339..e4f20bf 100644 --- a/build.mjs +++ b/build.mjs @@ -101,23 +101,6 @@ await esbuild.build({ define, }) -// Build automation SDK entry point (for programmatic usage) -await esbuild.build({ - entryPoints: ["src/commands/automation/context.ts"], - bundle: true, - platform: "node", - target: "node20", - format: "esm", - minify: true, - treeShaking: true, - outfile: "dist/automation.js", - external: nativePackages, - banner: { - js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url);`, - }, - define, -}) - // Copy runtime files to dist/runtime/ const runtimeDir = "dist/runtime" if (!existsSync(runtimeDir)) { diff --git a/package.json b/package.json index 35e1c5d..ee6a3b2 100644 --- a/package.json +++ b/package.json @@ -86,9 +86,6 @@ "exports": { ".": { "import": "./dist/index.js" - }, - "./automation": { - "import": "./dist/automation.js" } }, "engines": { diff --git a/skills/smithery-ai-cli/SKILL.md b/skills/smithery-ai-cli/SKILL.md index f8bbdb9..93c191a 100644 --- a/skills/smithery-ai-cli/SKILL.md +++ b/skills/smithery-ai-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: smithery-ai-cli -description: Find, connect, and use MCP tools and skills via the Smithery CLI. Use when the user searches for new tools or skills, wants to discover integrations, connect to an MCP, install a skill, create or run automations, or wants to interact with an external service (email, Slack, Discord, GitHub, Jira, Notion, databases, cloud APIs, monitoring, etc.). +description: Find, connect, and use MCP tools and skills via the Smithery CLI. Use when the user searches for new tools or skills, wants to discover integrations, connect to an MCP, install a skill, or wants to interact with an external service (email, Slack, Discord, GitHub, Jira, Notion, databases, cloud APIs, monitoring, etc.). metadata: { "openclaw": { "requires": { "bins": ["smithery"] }, "homepage": "https://smithery.ai" } } --- @@ -119,77 +119,6 @@ smithery auth token --policy '{ }' ``` -### [Automations](https://smithery.ai/docs/use/automations.md) - -Automations are deterministic TypeScript scripts that call MCP tools without AI. -They live in `~/.smithery/automations/` and each file exports a `servers` array (MCP URLs) and a `run` function. -Smithery handles connection management and auth — the automation just calls tools. - -Canonical flow: - -```bash -# 1. Initialize the ~/.smithery project (once) -smithery automation init - -# 2. Create an automation -smithery automation create create-linear-ticket - -# 3. Edit ~/.smithery/automations/create-linear-ticket.ts -# - Add server URLs to the servers array -# - Implement the run function using ctx.callTool() - -# 4. Run it with key=value arguments -smithery automation run create-linear-ticket ticket-name="Fix login bug" priority=high -``` - -Automation file structure: - -```typescript -export const servers = ["https://server.smithery.ai/linear"] - -export async function run( - args: Record, - ctx: { - callTool: (server: string, toolName: string, toolArgs: Record) => Promise - }, -) { - const result = await ctx.callTool( - "https://server.smithery.ai/linear", - "create_issue", - { title: args["ticket-name"], priority: args["priority"] } - ) - console.log("Created:", result) -} -``` - -CRUD commands: `automation create`, `automation list`, `automation get`, `automation remove`. -On first run, connections are auto-created for each server URL. If auth is required, the CLI prints the authorization URL. - -### Programmatic Automations (SDK) - -The same automation context is available as a library import for any TypeScript project. -Install `@smithery/cli` and import `createAutomationContext` from `@smithery/cli/automation`. - -```typescript -import { createAutomationContext } from "@smithery/cli/automation" - -const ctx = await createAutomationContext({ - servers: ["https://server.smithery.ai/linear"], - apiKey: process.env.SMITHERY_API_KEY, // or omit to use stored key -}) - -const result = await ctx.callTool( - "https://server.smithery.ai/linear", - "create_issue", - { title: "Bug: login page broken", priority: "high" }, -) -``` - -This works anywhere — standalone scripts, API routes, background jobs, CI pipelines, etc. -Connections are auto-created and MCP clients are managed for you, same as the CLI. -Pass `apiKey` explicitly (e.g. from env vars) instead of relying on `smithery auth login`. -Auth errors throw with an `authorizationUrl` property for your code to handle. - ### Piped Output When output is piped, Smithery commands emit JSONL (one JSON object per line): diff --git a/src/commands/automation/context.ts b/src/commands/automation/context.ts deleted file mode 100644 index 42be7ce..0000000 --- a/src/commands/automation/context.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Client } from "@modelcontextprotocol/sdk/client/index.js" -import { - type CreateConnectionOptions, - createConnection as createSmitheryConnection, -} from "@smithery/api/mcp" -import { createSmitheryClient } from "../../lib/smithery-client" -import { normalizeMcpUrl } from "../mcp/normalize-url" - -/** - * Context passed to automation run functions. - * Provides a `callTool` method that routes calls to the correct MCP connection by server URL. - */ -export interface AutomationContext { - callTool: ( - server: string, - toolName: string, - toolArgs: Record, - ) => Promise -} - -/** - * Options for creating an automation context. - */ -export interface CreateAutomationContextOptions { - /** MCP server URLs to connect to. */ - servers: string[] - /** Smithery API key. Falls back to SMITHERY_API_KEY env var or stored key. */ - apiKey?: string - /** Smithery namespace. Falls back to stored namespace. */ - namespace?: string -} - -interface ResolvedConnection { - connectionId: string - client: Client -} - -/** - * Create an automation context for programmatic use. - * - * Resolves MCP server URLs to Smithery connections (creating them if needed), - * establishes MCP clients, and returns a `callTool` function. - * - * Throws if a connection requires authorization (check error.authorizationUrl). - * - * @example - * ```typescript - * import { createAutomationContext } from "@smithery/cli/automation" - * - * const ctx = await createAutomationContext({ - * servers: ["https://server.smithery.ai/linear"], - * apiKey: process.env.SMITHERY_API_KEY, - * }) - * - * const result = await ctx.callTool( - * "https://server.smithery.ai/linear", - * "create_issue", - * { title: "My ticket" }, - * ) - * ``` - */ -export async function createAutomationContext( - options: CreateAutomationContextOptions, -): Promise { - const smitheryClient = await createSmitheryClient(options.apiKey) - - // Resolve namespace - let namespace = options.namespace - if (!namespace) { - const { getNamespace } = await import("../../utils/smithery-settings") - namespace = await getNamespace() - if (!namespace) { - const { namespaces } = await smitheryClient.namespaces.list() - if (namespaces.length === 0) { - const created = await smitheryClient.namespaces.create() - namespace = created.name - } else { - namespace = namespaces[0].name - } - } - } - - // Resolve connections for each server URL - const urlToConnection = new Map() - - for (const url of options.servers) { - const normalizedUrl = normalizeMcpUrl(url) - - // Check for existing connection - const { connections } = await smitheryClient.connections.list(namespace, { - mcpUrl: normalizedUrl, - }) - - let connectionId: string - - if (connections.length > 0) { - const conn = connections[0] - if (conn.status?.state === "auth_required") { - const authUrl = (conn.status as { authorizationUrl?: string }) - ?.authorizationUrl - const err = new Error( - `Connection for ${url} requires authorization`, - ) as Error & { authorizationUrl?: string } - err.authorizationUrl = authUrl - throw err - } - connectionId = conn.connectionId - } else { - // Auto-create - const conn = await smitheryClient.connections.create(namespace, { - mcpUrl: normalizedUrl, - }) - - if (conn.status?.state === "auth_required") { - const authUrl = (conn.status as { authorizationUrl?: string }) - ?.authorizationUrl - const err = new Error( - `Connection for ${url} requires authorization`, - ) as Error & { authorizationUrl?: string } - err.authorizationUrl = authUrl - throw err - } - connectionId = conn.connectionId - } - - // Create MCP client - const { transport } = await createSmitheryConnection({ - client: smitheryClient as unknown as CreateConnectionOptions["client"], - namespace, - connectionId, - }) - - const mcpClient = new Client({ - name: "smithery-automation", - version: "1.0.0", - }) - await mcpClient.connect(transport) - - urlToConnection.set(url, { connectionId, client: mcpClient }) - } - - return { - callTool: async ( - server: string, - toolName: string, - toolArgs: Record, - ): Promise => { - const resolved = urlToConnection.get(server) - if (!resolved) { - throw new Error( - `Server "${server}" was not included in the servers array.`, - ) - } - return resolved.client.callTool({ - name: toolName, - arguments: toolArgs, - }) - }, - } -} diff --git a/src/commands/automation/create.ts b/src/commands/automation/create.ts deleted file mode 100644 index 9a2b29d..0000000 --- a/src/commands/automation/create.ts +++ /dev/null @@ -1,65 +0,0 @@ -import fs from "node:fs" -import pc from "picocolors" -import { fatal } from "../../lib/cli-error" -import { ensureInitialized } from "./ensure-init" -import { AUTOMATIONS_DIR, automationPath } from "./paths" - -const TEMPLATE = (name: string) => `// Automation: ${name} -// -// Discover available tools before writing your automation: -// smithery tool list # browse tools from a connection -// smithery tool find # search tools by name or intent -// -// Install extra dependencies in ~/.smithery (do NOT edit package.json directly): -// cd ~/.smithery && npm install -import { z } from "zod" - -// MCP server URLs this automation connects to. -// Find servers at https://smithery.ai or use "smithery search ". -export const servers: string[] = [ - // e.g. "https://server.smithery.ai/@anthropic/slack-mcp" -] - -// Define your input schema for validated, strongly-typed arguments. -// All CLI values arrive as strings — use z.coerce for numeric/boolean fields. -export const argsSchema = z.object({ - // e.g. title: z.string(), - // e.g. priority: z.coerce.number().optional(), -}) - -// The run function receives parsed & validated args matching your schema. -export async function run( - args: z.infer, - ctx: { - callTool: ( - server: string, - toolName: string, - toolArgs: Record, - ) => Promise - }, -): Promise { - // Use ctx.callTool(serverUrl, toolName, toolArgs) to call MCP tools. - // Discover exact tool names and arguments with: - // smithery tool list - console.log("Running ${name} with args:", args) -} -` - -export async function createAutomation(name: string): Promise { - ensureInitialized() - - const filePath = automationPath(name) - if (fs.existsSync(filePath)) { - fatal(`Automation "${name}" already exists at ${filePath}`) - } - - fs.mkdirSync(AUTOMATIONS_DIR, { recursive: true }) - fs.writeFileSync(filePath, TEMPLATE(name)) - - console.log(pc.green(`Created automation: ${name}`)) - console.log(pc.dim(filePath)) - console.log() - console.log(pc.dim("Edit the file to add your MCP servers and logic.")) - console.log(pc.dim("Discover tools with: smithery tool list ")) - console.log(pc.dim(`Run it with: smithery automation run ${name} key=value`)) -} diff --git a/src/commands/automation/ensure-init.ts b/src/commands/automation/ensure-init.ts deleted file mode 100644 index 7c2ac86..0000000 --- a/src/commands/automation/ensure-init.ts +++ /dev/null @@ -1,14 +0,0 @@ -import fs from "node:fs" -import path from "node:path" -import { fatal } from "../../lib/cli-error" -import { SMITHERY_HOME } from "./paths" - -/** Ensure ~/.smithery has been initialized. Exits with an error if not. */ -export function ensureInitialized(): void { - const pkgPath = path.join(SMITHERY_HOME, "package.json") - if (!fs.existsSync(pkgPath)) { - fatal( - '~/.smithery is not initialized. Run "smithery automation init" first.', - ) - } -} diff --git a/src/commands/automation/get.ts b/src/commands/automation/get.ts deleted file mode 100644 index 15145b5..0000000 --- a/src/commands/automation/get.ts +++ /dev/null @@ -1,25 +0,0 @@ -import fs from "node:fs" -import { fatal } from "../../lib/cli-error" -import { outputDetail } from "../../utils/output" -import { ensureInitialized } from "./ensure-init" -import { automationPath } from "./paths" - -export async function getAutomation(name: string): Promise { - ensureInitialized() - - const filePath = automationPath(name) - if (!fs.existsSync(filePath)) { - fatal(`Automation "${name}" not found at ${filePath}`) - } - - const source = fs.readFileSync(filePath, "utf-8") - - outputDetail({ - data: { - name, - path: filePath, - source, - }, - tip: `Run it with: smithery automation run ${name} key=value`, - }) -} diff --git a/src/commands/automation/index.ts b/src/commands/automation/index.ts deleted file mode 100644 index 2680807..0000000 --- a/src/commands/automation/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { - type AutomationContext, - type CreateAutomationContextOptions, - createAutomationContext, -} from "./context" -export { createAutomation } from "./create" -export { getAutomation } from "./get" -export { initAutomations } from "./init" -export { listAutomations } from "./list" -export { removeAutomation } from "./remove" -export { runAutomation } from "./run" diff --git a/src/commands/automation/init.ts b/src/commands/automation/init.ts deleted file mode 100644 index de9d1b7..0000000 --- a/src/commands/automation/init.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { execSync } from "node:child_process" -import fs from "node:fs" -import path from "node:path" -import pc from "picocolors" -import { AUTOMATIONS_DIR, SMITHERY_HOME } from "./paths" - -const PACKAGE_JSON = { - name: "smithery-automations", - private: true, - type: "module", - dependencies: {}, -} - -const TSCONFIG = { - compilerOptions: { - target: "ES2022", - module: "ES2022", - moduleResolution: "bundler", - esModuleInterop: true, - strict: true, - skipLibCheck: true, - }, -} - -export async function initAutomations(): Promise { - const pkgPath = path.join(SMITHERY_HOME, "package.json") - - if (fs.existsSync(pkgPath)) { - console.log(pc.yellow("~/.smithery already initialized")) - return - } - - fs.mkdirSync(AUTOMATIONS_DIR, { recursive: true }) - - fs.writeFileSync(pkgPath, JSON.stringify(PACKAGE_JSON, null, 2)) - fs.writeFileSync( - path.join(SMITHERY_HOME, "tsconfig.json"), - JSON.stringify(TSCONFIG, null, 2), - ) - - // Install zod as a default dependency - try { - execSync("npm install zod", { cwd: SMITHERY_HOME, stdio: "pipe" }) - } catch { - console.warn( - pc.yellow( - "Warning: failed to install zod. Run manually: cd ~/.smithery && npm install zod", - ), - ) - } - - console.log(pc.green("Initialized ~/.smithery")) - console.log(pc.dim("Created package.json, tsconfig.json, automations/")) -} diff --git a/src/commands/automation/list.ts b/src/commands/automation/list.ts deleted file mode 100644 index 9d67cc4..0000000 --- a/src/commands/automation/list.ts +++ /dev/null @@ -1,51 +0,0 @@ -import fs from "node:fs" -import path from "node:path" -import pc from "picocolors" -import { isJsonMode, outputTable } from "../../utils/output" -import { ensureInitialized } from "./ensure-init" -import { AUTOMATIONS_DIR } from "./paths" - -export async function listAutomations(): Promise { - ensureInitialized() - - if (!fs.existsSync(AUTOMATIONS_DIR)) { - if (isJsonMode()) { - console.log(JSON.stringify({ automations: [] })) - } else { - console.log(pc.yellow("No automations found.")) - console.log(pc.dim("Create one with: smithery automation create ")) - } - return - } - - const files = fs - .readdirSync(AUTOMATIONS_DIR) - .filter((f) => f.endsWith(".ts")) - .map((f) => path.basename(f, ".ts")) - - if (files.length === 0) { - if (isJsonMode()) { - console.log(JSON.stringify({ automations: [] })) - } else { - console.log(pc.yellow("No automations found.")) - console.log(pc.dim("Create one with: smithery automation create ")) - } - return - } - - const data = files.map((name) => ({ - name, - path: path.join(AUTOMATIONS_DIR, `${name}.ts`), - })) - - outputTable({ - data, - columns: [ - { key: "name", header: "NAME" }, - { key: "path", header: "PATH" }, - ], - json: isJsonMode(), - jsonData: { automations: data }, - tip: "Run an automation with: smithery automation run key=value", - }) -} diff --git a/src/commands/automation/paths.ts b/src/commands/automation/paths.ts deleted file mode 100644 index cead67a..0000000 --- a/src/commands/automation/paths.ts +++ /dev/null @@ -1,13 +0,0 @@ -import os from "node:os" -import path from "node:path" - -/** Root directory for the user's Smithery automations project. */ -export const SMITHERY_HOME = path.join(os.homedir(), ".smithery") - -/** Directory containing individual automation files. */ -export const AUTOMATIONS_DIR = path.join(SMITHERY_HOME, "automations") - -/** Resolve the file path for an automation by name. */ -export function automationPath(name: string): string { - return path.join(AUTOMATIONS_DIR, `${name}.ts`) -} diff --git a/src/commands/automation/remove.ts b/src/commands/automation/remove.ts deleted file mode 100644 index 671845e..0000000 --- a/src/commands/automation/remove.ts +++ /dev/null @@ -1,17 +0,0 @@ -import fs from "node:fs" -import pc from "picocolors" -import { fatal } from "../../lib/cli-error" -import { ensureInitialized } from "./ensure-init" -import { automationPath } from "./paths" - -export async function removeAutomation(name: string): Promise { - ensureInitialized() - - const filePath = automationPath(name) - if (!fs.existsSync(filePath)) { - fatal(`Automation "${name}" not found at ${filePath}`) - } - - fs.unlinkSync(filePath) - console.log(pc.green(`Removed automation: ${name}`)) -} diff --git a/src/commands/automation/run.ts b/src/commands/automation/run.ts deleted file mode 100644 index 46287ca..0000000 --- a/src/commands/automation/run.ts +++ /dev/null @@ -1,115 +0,0 @@ -import fs from "node:fs" -import pc from "picocolors" -import type { ZodTypeAny } from "zod" -import { errorMessage, fatal } from "../../lib/cli-error" -import { isJsonMode, outputJson } from "../../utils/output" -import { getApiKey } from "../../utils/smithery-settings" -import { type AutomationContext, createAutomationContext } from "./context" -import { ensureInitialized } from "./ensure-init" -import { automationPath } from "./paths" - -interface AutomationModule { - servers: string[] - argsSchema?: ZodTypeAny - run: (args: Record, ctx: AutomationContext) => Promise -} - -/** - * Parse "key=value" pairs from CLI arguments into a record. - */ -function parseArgs(rawArgs: string[]): Record { - const result: Record = {} - for (const arg of rawArgs) { - const eqIndex = arg.indexOf("=") - if (eqIndex === -1) { - fatal(`Invalid argument "${arg}". Expected key=value format.`) - } - const key = arg.slice(0, eqIndex) - const value = arg.slice(eqIndex + 1) - result[key] = value - } - return result -} - -export async function runAutomation( - name: string, - rawArgs: string[], - options: { namespace?: string }, -): Promise { - ensureInitialized() - - // Require authentication before running automations - const apiKey = await getApiKey() - if (!apiKey) { - fatal( - "Not logged in. Run 'smithery login' to authenticate before running automations.", - ) - } - - const filePath = automationPath(name) - if (!fs.existsSync(filePath)) { - fatal(`Automation "${name}" not found at ${filePath}`) - } - - const rawParsed = parseArgs(rawArgs) - - // Dynamically import the automation file using tsx loader - let mod: AutomationModule - try { - mod = await import(filePath) - } catch (e) { - fatal(`Failed to load automation "${name}": ${errorMessage(e)}`) - } - - if (!Array.isArray(mod.servers)) { - fatal( - `Automation "${name}" must export a "servers" array of MCP server URLs.`, - ) - } - if (typeof mod.run !== "function") { - fatal(`Automation "${name}" must export a "run" function.`) - } - - // Validate args against schema if provided - let args: Record = rawParsed - if (mod.argsSchema) { - const result = mod.argsSchema.safeParse(rawParsed) - if (!result.success) { - const issues = result.error.issues - .map( - (i: { path: PropertyKey[]; message: string }) => - ` ${i.path.map(String).join(".")}: ${i.message}`, - ) - .join("\n") - fatal(`Invalid arguments for "${name}":\n${issues}`) - } - args = result.data as Record - } - - // Create the automation context (resolves connections, handles auth) - let ctx: AutomationContext - try { - ctx = await createAutomationContext({ - servers: mod.servers, - namespace: options.namespace, - }) - } catch (e) { - const err = e as Error & { authorizationUrl?: string } - if (err.authorizationUrl) { - console.error(pc.yellow(err.message)) - console.error(pc.yellow(`Authorize at: ${err.authorizationUrl}`)) - console.error(pc.dim("After authorizing, re-run this automation.")) - process.exit(1) - } - fatal(`Failed to connect: ${errorMessage(e)}`) - } - - try { - await mod.run(args, ctx) - } catch (e) { - if (isJsonMode()) { - outputJson({ error: errorMessage(e) }) - } - fatal(`Automation "${name}" failed: ${errorMessage(e)}`) - } -} diff --git a/src/index.ts b/src/index.ts index a1e9033..6db1806 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1121,90 +1121,6 @@ skillReview await voteReview(skill, reviewId, "down") }) -// ═══════════════════════════════════════════════════════════════════════════════ -// Automation command — Create and run deterministic automations -// ═══════════════════════════════════════════════════════════════════════════════ - -const automationCmd = program - .command("automation") - .description("Create and run deterministic automations") - -automationCmd - .command("init") - .description("Initialize ~/.smithery automations project") - .action(async () => { - const { initAutomations } = await import("./commands/automation") - await initAutomations() - }) - -automationCmd - .command("create ") - .description("Create a new automation") - .addHelpText( - "after", - ` -Examples: - smithery automation create create-linear-ticket - smithery automation create deploy-notification`, - ) - .action(async (name) => { - const { createAutomation } = await import("./commands/automation") - await createAutomation(name) - }) - -automationCmd - .command("list") - .description("List all automations") - .action(async () => { - const { listAutomations } = await import("./commands/automation") - await listAutomations() - }) - -automationCmd - .command("get ") - .description("Show automation details and source") - .action(async (name) => { - const { getAutomation } = await import("./commands/automation") - await getAutomation(name) - }) - -const automationRemoveCmd = automationCmd - .command("remove ") - .description("Delete an automation") - .action(async (name) => { - const { removeAutomation } = await import("./commands/automation") - await removeAutomation(name) - }) - -registerAlias( - automationCmd, - "rm ", - automationRemoveCmd, - async (name: string) => { - const { removeAutomation } = await import("./commands/automation") - await removeAutomation(name) - }, -) - -automationCmd - .command("run [args...]") - .description("Run an automation with key=value arguments") - .option("--namespace ", "Namespace for connections") - .addHelpText( - "after", - ` -Arguments are passed as key=value pairs: - smithery automation run create-linear-ticket ticket-name="my ticket" priority=high - -Examples: - smithery automation run create-linear-ticket ticket-name="Fix login bug" - smithery automation run deploy-notification env=production version=1.2.3`, - ) - .action(async (name, args, options) => { - const { runAutomation } = await import("./commands/automation") - await runAutomation(name, args, options) - }) - // ═══════════════════════════════════════════════════════════════════════════════ // Auth command — Authentication and permissions // ═══════════════════════════════════════════════════════════════════════════════ @@ -1296,10 +1212,6 @@ and combine with rpcReqMatch to also restrict which tools can be called (as show await createToken(options) }) -// ═══════════════════════════════════════════════════════════════════════════════ -// Automation command — Create and run deterministic MCP automations -// ═══════════════════════════════════════════════════════════════════════════════ - // ═══════════════════════════════════════════════════════════════════════════════ // Management // ═══════════════════════════════════════════════════════════════════════════════ @@ -1437,7 +1349,6 @@ const COMMAND_ALIASES: Record = { tools: "tool", skills: "skill", events: "event", - automations: "automation", } const argv = process.argv.slice() if (argv[2] && argv[2] in COMMAND_ALIASES) {