diff --git a/README.md b/README.md index e3309b3..e114a97 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # polylane

Agent-focused CLI for the Polylane platform.
-Investigate production issues, explore cloud infrastructure, search code, run automations, and drive threads — from any agent or terminal.

+Investigate production issues, explore cloud infrastructure, search code, and drive threads — from any agent or terminal.

Your coding agent writes the code. It can't see production. Polylane is the missing sense.

diff --git a/codegen/parse-spec.ts b/codegen/parse-spec.ts index b7c3ea4..dcf96c5 100644 --- a/codegen/parse-spec.ts +++ b/codegen/parse-spec.ts @@ -10,10 +10,23 @@ import { schemaToTypescript, type TypeGenOptions } from './type-utils'; const CLIENT_OPTS: TypeGenOptions = { refPrefix: 'T.' }; +// The automations feature is retired; drop its endpoints and schemas from the +// generated client even while the API still serves them. +const EXCLUDED_PATH = /\/automations(\/|$)/; +const EXCLUDED_SCHEMAS = new Set([ + 'Automation', + 'AutomationExecution', + 'AutomationNotification', + 'AutomationActionExecution', + 'TriggerAutomationBody', + 'CreateAutomationFromTemplateBody', +]); + export function parseSpec(spec: OpenAPISpec): ParsedSpec { const schemas = new Map(); if (spec.components?.schemas) { for (const [name, schema] of Object.entries(spec.components.schemas)) { + if (EXCLUDED_SCHEMAS.has(name)) continue; schemas.set(name, schema); } } @@ -22,7 +35,7 @@ export function parseSpec(spec: OpenAPISpec): ParsedSpec { const methods: Array = ['get', 'post', 'put', 'patch', 'delete']; for (const [path, pathItem] of Object.entries(spec.paths)) { - if (!pathItem) continue; + if (!pathItem || EXCLUDED_PATH.test(path)) continue; for (const method of methods) { const op = pathItem[method]; if (!op) continue; diff --git a/skill/SKILL.md b/skill/SKILL.md index d08d2fa..d8b4966 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1,6 +1,6 @@ --- name: polylane-cli -description: Use `polylane` to investigate production issues, explore cloud infrastructure (logs / metrics / dependency graphs), search code, save memories, run automations from a catalog, connect observability tools and cloud accounts, and drive threads with the Polylane agent. Use when the user wants to debug a production issue, look up a service, search their codebase, manage integrations, connect a cloud provider, or talk to the Polylane agent from the terminal. +description: Use `polylane` to investigate production issues, explore cloud infrastructure (logs / metrics / dependency graphs), search code, save memories, connect observability tools and cloud accounts, and drive threads with the Polylane agent. Use when the user wants to debug a production issue, look up a service, search their codebase, manage integrations, connect a cloud provider, or talk to the Polylane agent from the terminal. --- # Polylane CLI — Agent Skill Guide @@ -10,8 +10,7 @@ description: Use `polylane` to investigate production issues, explore cloud infr > **Full docs: ** (model-readable, no auth required). Start at > , and see for an > agent-oriented index. This skill is the quick reference; the docs are the source of truth for -> concepts, the automation schema (triggers / actions / destinations), and the full API. When a task -> goes beyond what's below, read the docs rather than guessing. +> concepts and the full API. When a task goes beyond what's below, read the docs rather than guessing. ## Prerequisites @@ -137,15 +136,10 @@ polylane integration catalog --category cloud polylane integration connect --type # see --help for that type polylane cloud connect --provider # see --help for that provider -# 5. Add automations from templates -polylane automation catalog -polylane automation from-template - -# 6. Verify +# 5. Verify polylane integration list polylane cloud list polylane service list # cloud infra discovered from connected accounts -polylane automation list ``` `integration connect` and `cloud connect` dispatch on `--type` / `--provider`. Some options open a browser for an install URL; others take API credentials directly. Use `--help` on each to see the required flags and optional `--no-browser`. Browser flows wait in the terminal until the connection appears (interactive TTY only); with `--output json` or in non-interactive runs they print the URL and exit, so poll `integration list` / `cloud list` to confirm. @@ -215,59 +209,6 @@ polylane thread continue "" Attach resources as context by passing their IDs — the CLI infers the resource type from the ID prefix. -### Automations - -An automation is **trigger → instructions → tools → actions**: a typed event fires, an agent runs -with your instructions and a set of skills (tools), and optional actions apply side-effects (open an -issue, roll back a deploy, open a PR) — each gated by a `mode` (`smart` = act only when warranted, -`always`). Full schema and every trigger/action type: . - -```bash -polylane automation catalog # browse prebuilt templates -polylane automation from-template # create from a template -polylane automation from-template --pass-count 2 --provider cloudflare -polylane automation create ... # create an ARBITRARY automation (see below) - -polylane automation list -polylane automation trigger # manual run -polylane automation executions # list runs -polylane automation execution # one run + result -polylane automation rerun -``` - -**Templates vs. custom.** Use `from-template` when a catalog entry matches as-is. Use -`automation create` for anything else — a scoped trigger, custom instructions, a combination no -template covers. `automation create` is a first-class command; you do **not** need `polylane api call`. - -```bash -# Build from flags. --trigger/--action take JSON, or a bare type for filterless ones (e.g. `webhook`). -# --tool attaches a skill by slug (repeatable); see `polylane skill list`. -polylane automation create \ - --name "Triage Datadog alerts" \ - --trigger '{"type":"alert","filters":{"sources":["datadog"]}}' \ - --instructions "Correlate the alert with recent deploys and open an issue with the findings." \ - --tool investigate-errors --tool investigate-latency \ - --action '{"type":"openIssue","mode":"smart","defaultSeverity":"high"}' \ - --delay 600000 # wait 10m (e.g. let a deploy settle) before the run - -# Or pass a full JSON body (--body-file '-' reads stdin); flags layer on top. -polylane automation create --body-file automation.json - -# Scope a template's broad trigger: crib its body, then narrow the trigger. -polylane automation catalog --output json # find the slug + shape -polylane automation create --body-file template.json \ - --trigger '{"type":"cloudflare.deployment","filters":{"environments":["production"]}}' -``` - -Run `polylane automation create --help` for every flag. Common trigger types: `alert`, `cron` -(`{"type":"cron","expression":"0 9 * * *"}`), `webhook`, `github.*` (push/pull_request/deployment/…), -`{cloudflare,vercel,render,fly}.deployment`, `slack.message`, `polylane.*` (issue.triaged, -cloud_account.synced, codebase.synced, …). Actions include `openIssue`, -`rollback{Cloudflare,Vercel,Render,Fly}Deployment`, `submitPr`, `commentPr`, `mergePr`, -`commentGithubIssue`, `createGithubIssue`, `autofix`, `handoffTo{Devin,Cursor,Factory,Conductor}`. The -authoritative, always-current list of types and filters is in the docs and in -`polylane api describe automations.post`. - --- ## Piping patterns diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 6215a69..922ea6f 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -47,9 +47,6 @@ export const DEFAULT_SCOPES = [ 'autofixes:delete', 'autofixes:read', 'autofixes:write', - 'automations:delete', - 'automations:read', - 'automations:write', 'billing:read', 'billing:write', 'cloud_accounts:delete', diff --git a/src/commands/auth/signup.ts b/src/commands/auth/signup.ts index 51e08b7..0795da0 100644 --- a/src/commands/auth/signup.ts +++ b/src/commands/auth/signup.ts @@ -123,15 +123,10 @@ export function nextSteps(landing?: Landing): string { ` 4. Connect a cloud account`, ` polylane cloud connect --provider # see --help`, ``, - ` 5. Add an automation from the catalog`, - ` polylane automation catalog`, - ` polylane automation from-template `, - ``, - ` 6. Verify what's wired up`, + ` 5. Verify what's wired up`, ` polylane integration list`, ` polylane cloud list`, ` polylane service list`, - ` polylane automation list`, ``, `Once things are connected, try:`, ` polylane thread ask "summarise production"`, diff --git a/src/commands/autofix/list.ts b/src/commands/autofix/list.ts index 79919c0..a355407 100644 --- a/src/commands/autofix/list.ts +++ b/src/commands/autofix/list.ts @@ -13,7 +13,7 @@ export const autofixListCommand: Command = { operationId: 'autofixes.list', options: [ { flag: '--status ', description: 'Filter by status (started, branch_pushed, pr_opened, merged, failed, no_fix_needed)', type: 'string' }, - { flag: '--origin ', description: 'Filter by origin (automation, chat, investigation, system)', type: 'string' }, + { flag: '--origin ', description: 'Filter by origin (chat, investigation, system)', type: 'string' }, { flag: '--repo ', description: 'Filter by repo name', type: 'string' }, { flag: '--limit ', description: 'Max items (default 20)', type: 'number' }, { flag: '--full', description: 'Return full objects', type: 'boolean' }, diff --git a/src/commands/automation/catalog.ts b/src/commands/automation/catalog.ts deleted file mode 100644 index ba00a5d..0000000 --- a/src/commands/automation/catalog.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatList } from '../../output/formatter'; -import { projectItems } from '../../output/project'; -import { getArgBoolean } from '../helpers'; - -const FIELDS = ['slug', 'name', 'category', 'triggerTypes', 'description', '_html_url']; - -export const automationCatalogCommand: Command = { - name: 'automation catalog', - description: 'List public automation templates', - operationId: 'automations.public.catalog', - options: [ - { flag: '--full', description: 'Return full objects', type: 'boolean' }, - ], - async execute(config: Config, _flags, args: Record): Promise { - const full = getArgBoolean(args, 'full') === true; - const api = new PolylaneAPI(config); - const result = await api.automationsPublicCatalog(); - - if (full) { - formatList(config, { - items: result.items as unknown as Array>, - count: result.items.length, - }); - return; - } - - const projected = projectItems(result.items as unknown as Array>, FIELDS); - formatList( - config, - { items: projected, count: projected.length }, - { - headers: ['Slug', 'Name', 'Category', 'Triggers', 'Description', 'Console'], - rows: (item) => [ - String(item.slug ?? ''), - String(item.name ?? ''), - String(item.category ?? ''), - Array.isArray(item.triggerTypes) ? item.triggerTypes.join(',') : '', - truncate(String(item.description ?? ''), 60), - String(item._html_url ?? ''), - ], - } - ); - }, -}; - -function truncate(s: string, max: number): string { - return s.length > max ? s.slice(0, max - 1) + '…' : s; -} diff --git a/src/commands/automation/create.ts b/src/commands/automation/create.ts deleted file mode 100644 index 4fda052..0000000 --- a/src/commands/automation/create.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { readFileSync, readSync } from 'node:fs'; -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatOutput } from '../../output/formatter'; -import { requireWorkspace, getArgString, getArgArray, getArgNumber, getArgBoolean, parseJsonArg } from '../helpers'; -import { CLIError } from '../../errors/base'; -import { ExitCode } from '../../errors/codes'; - -type Automation = Parameters[0]; - -function readBodyFromFile(path: string): string { - if (path === '-') { - const chunks: Buffer[] = []; - const buf = Buffer.alloc(65536); - while (true) { - let bytesRead = 0; - try { - bytesRead = readSync(0, buf, 0, buf.length, null); - } catch { - break; - } - if (bytesRead === 0) break; - chunks.push(Buffer.from(buf.subarray(0, bytesRead))); - } - return Buffer.concat(chunks).toString('utf-8'); - } - return readFileSync(path, 'utf-8'); -} - -function resolveBaseBody(args: Record): Record { - const raw = getArgString(args, 'body'); - if (raw !== undefined) { - const parsed = parseJsonArg(raw, '--body'); - if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new CLIError('--body must be a JSON object', ExitCode.USAGE); - } - return parsed as Record; - } - const file = getArgString(args, 'bodyFile'); - if (file !== undefined) { - const parsed = parseJsonArg(readBodyFromFile(file), '--body-file'); - if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new CLIError('--body-file must contain a JSON object', ExitCode.USAGE); - } - return parsed as Record; - } - return {}; -} - -// A repeatable flag value is either inline JSON (`{"type":"cron","expression":"0 9 * * *"}`) -// or a bare type shorthand (`webhook`) that expands to `{ type: }`. -function parseTypedFlag(values: string[] | undefined, flag: string): Array> | undefined { - if (!values || values.length === 0) return undefined; - return values.map((v) => { - const trimmed = v.trim(); - if (trimmed.startsWith('{')) { - const parsed = parseJsonArg(trimmed, flag); - if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) { - throw new CLIError(`${flag} JSON must be an object`, ExitCode.USAGE); - } - return parsed as Record; - } - return { type: trimmed }; - }); -} - -export const automationCreateCommand: Command = { - name: 'automation create', - description: 'Create an arbitrary automation (trigger → instructions → tools → actions)', - operationId: 'automations.post', - usage: 'polylane automation create [--name ...] [--trigger ]... [--instructions ...] [--body-file ]', - options: [ - { flag: '--name ', description: 'Automation name', type: 'string' }, - { flag: '--instructions ', description: 'Agent instructions (the system prompt for the run)', type: 'string' }, - { flag: '--trigger ', description: 'Trigger as JSON, or a bare type for filterless triggers (repeatable). e.g. \'{"type":"alert","filters":{"sources":["datadog"]}}\' or webhook', type: 'array' }, - { flag: '--action ', description: 'Action as JSON, or a bare type (repeatable). e.g. \'{"type":"openIssue","mode":"smart"}\'', type: 'array' }, - { flag: '--destination ', description: 'Notification destination as JSON (repeatable). e.g. \'{"type":"slack","name":"ops","channelId":"C123"}\'', type: 'array' }, - { flag: '--tool ', description: 'Skill slug to attach as a tool (repeatable). Alias: --skill', type: 'array' }, - { flag: '--skill ', description: 'Alias for --tool', type: 'array' }, - { flag: '--delay ', description: 'Delay before the run, in milliseconds (e.g. post-deploy wait)', type: 'number' }, - { flag: '--passes ', description: 'Number of parallel agent passes', type: 'number' }, - { flag: '--disabled', description: 'Create the automation in a disabled state', type: 'boolean' }, - { flag: '--body ', description: 'Full automation body as JSON; flags layer on top of it', type: 'string' }, - { flag: '--body-file ', description: 'Full automation body from a file ("-" for stdin); flags layer on top', type: 'string' }, - ], - examples: [ - 'polylane automation create --name "Triage Datadog alerts" --trigger \'{"type":"alert","filters":{"sources":["datadog"]}}\' --instructions "Correlate the alert with recent deploys and open an issue with findings." --action \'{"type":"openIssue","mode":"smart"}\' --tool investigate-errors', - 'polylane automation catalog --output json | jq \'...\' # crib a template body, then:', - 'polylane automation create --body-file my-automation.json', - 'polylane automation create --body-file template.json --trigger \'{"type":"cloudflare.deployment","filters":{"environments":["production"]}}\' # take a template body, narrow the trigger', - ], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - - const body = resolveBaseBody(args); - - // Flags layer on top of any --body/--body-file base: scalars override, arrays replace when provided. - const name = getArgString(args, 'name'); - if (name !== undefined) body.name = name; - - const instructions = getArgString(args, 'instructions'); - const passes = getArgNumber(args, 'passes'); - if (instructions !== undefined || passes !== undefined) { - const agentConfig = (typeof body.agentConfig === 'object' && body.agentConfig !== null ? body.agentConfig : {}) as Record; - if (instructions !== undefined) agentConfig.instructions = instructions; - if (passes !== undefined) agentConfig.passes = { count: passes }; - body.agentConfig = agentConfig; - } - - const triggers = parseTypedFlag(getArgArray(args, 'trigger'), '--trigger'); - if (triggers !== undefined) body.triggers = triggers; - - const actions = parseTypedFlag(getArgArray(args, 'action'), '--action'); - if (actions !== undefined) body.actions = actions; - - const destinations = parseTypedFlag(getArgArray(args, 'destination'), '--destination'); - if (destinations !== undefined) body.destinations = destinations; - - const tools = [...(getArgArray(args, 'tool') ?? []), ...(getArgArray(args, 'skill') ?? [])]; - if (tools.length > 0) body.skills = tools.map((slug) => ({ skillSlug: slug })); - - const delay = getArgNumber(args, 'delay'); - if (delay !== undefined) body.delayMs = delay; - - const disabled = getArgBoolean(args, 'disabled'); - if (disabled !== undefined) body.disabled = disabled; - - if (!Array.isArray(body.triggers) || body.triggers.length === 0) { - throw new CLIError( - 'An automation needs at least one trigger', - ExitCode.USAGE, - "Pass --trigger '' (or a bare type like webhook), or supply --body/--body-file.\n" + - 'See trigger and action types at https://docs.polylane.com/fix-and-automate/automations/triggers' - ); - } - - const api = new PolylaneAPI(config); - const automation = await api.automationsPost({ workspaceId, ...body } as Automation); - formatOutput(config, automation); - }, -}; diff --git a/src/commands/automation/execution.ts b/src/commands/automation/execution.ts deleted file mode 100644 index 7d93d36..0000000 --- a/src/commands/automation/execution.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatOutput } from '../../output/formatter'; -import { requireWorkspace, requirePositional } from '../helpers'; - -export const automationExecutionCommand: Command = { - name: 'automation execution', - description: 'Show an automation execution (includes result)', - operationId: 'automations.executions.get', - positional: [ - { name: 'automation-id', description: 'The automation ID' }, - { name: 'execution-id', description: 'The execution ID' }, - ], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - const automationId = requirePositional(args, 0, 'automation-id'); - const executionId = requirePositional(args, 1, 'execution-id'); - const api = new PolylaneAPI(config); - const result = await api.automationsExecutionsGet(workspaceId, automationId, executionId); - formatOutput(config, result); - }, -}; diff --git a/src/commands/automation/executions.ts b/src/commands/automation/executions.ts deleted file mode 100644 index deca4b6..0000000 --- a/src/commands/automation/executions.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatList } from '../../output/formatter'; -import { projectItems } from '../../output/project'; -import { requireWorkspace, requirePositional, getArgNumber, getArgBoolean } from '../helpers'; - -const FIELDS = ['id', 'status', 'summary', 'passThreadIds', 'durationMs', 'triggeredAt']; - -export const automationExecutionsCommand: Command = { - name: 'automation executions', - description: 'List executions for an automation', - operationId: 'automations.executions.list', - positional: [{ name: 'automation-id', description: 'The automation ID' }], - options: [ - { flag: '--limit ', description: 'Max items (default 20)', type: 'number' }, - { flag: '--full', description: 'Return full objects', type: 'boolean' }, - ], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - const automationId = requirePositional(args, 0, 'automation-id'); - const limit = getArgNumber(args, 'limit') ?? 20; - const full = getArgBoolean(args, 'full') === true; - - const api = new PolylaneAPI(config); - const result = await api.automationsExecutionsList(workspaceId, automationId, { perPage: limit }); - - if (full) { - formatList(config, result as { items: Array>; count: number }); - return; - } - - formatList( - config, - { - items: projectItems(result.items as unknown as Array>, FIELDS), - count: result.count, - }, - { - headers: ['ID', 'Status', 'Summary', 'Threads', 'Duration (ms)', 'Triggered'], - rows: (item) => [ - String(item.id ?? ''), - String(item.status ?? ''), - String(item.summary ?? ''), - Array.isArray(item.passThreadIds) ? item.passThreadIds.join(',') : '', - item.durationMs === null || item.durationMs === undefined ? '' : String(item.durationMs), - String(item.triggeredAt ?? ''), - ], - } - ); - }, -}; diff --git a/src/commands/automation/find.ts b/src/commands/automation/find.ts deleted file mode 100644 index 480c05c..0000000 --- a/src/commands/automation/find.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatList } from '../../output/formatter'; -import { projectItems } from '../../output/project'; -import { requireWorkspace, requirePositional, getAllPositional } from '../helpers'; - -const FIELDS = ['id', 'name', 'triggerTypes', 'disabled', 'source', 'created']; - -export const automationFindCommand: Command = { - name: 'automation find', - description: 'Search automations by prompt', - operationId: 'automations.search', - positional: [{ name: 'prompt', description: 'Search query', variadic: true }], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - requirePositional(args, 0, 'prompt'); - const prompt = getAllPositional(args).join(' '); - - const api = new PolylaneAPI(config); - const items = await api.automationsSearch({ workspaceId, prompt }); - const projected = projectItems(items as unknown as Array>, FIELDS); - - formatList( - config, - { items: projected, count: projected.length }, - { - headers: ['ID', 'Name', 'Triggers', 'Disabled', 'Source', 'Created'], - rows: (item) => [ - String(item.id ?? ''), - String(item.name ?? ''), - Array.isArray(item.triggerTypes) ? item.triggerTypes.join(',') : '', - item.disabled ? 'yes' : 'no', - String(item.source ?? ''), - String(item.created ?? ''), - ], - } - ); - }, -}; diff --git a/src/commands/automation/from-template.ts b/src/commands/automation/from-template.ts deleted file mode 100644 index 0bd3f9e..0000000 --- a/src/commands/automation/from-template.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import type { CreateAutomationFromTemplateBody } from '../../generated/types'; -import { formatOutput } from '../../output/formatter'; -import { requireWorkspace, requirePositional, getArgString, getArgNumber } from '../helpers'; - -type Provider = NonNullable; - -export const automationFromTemplateCommand: Command = { - name: 'automation from-template', - description: 'Create an automation from a catalog template slug', - operationId: 'automations.postFromTemplate', - positional: [{ name: 'template-slug', description: 'Slug from `polylane automation catalog`' }], - options: [ - { flag: '--pass-count ', description: "Override the template's number of parallel agent passes (1-10)", type: 'number' }, - { flag: '--provider ', description: 'Sanity-check the template targets this provider; fails on mismatch', type: 'string' }, - ], - examples: [ - 'polylane automation catalog', - 'polylane automation from-template alert-triage', - 'polylane automation from-template babysit-cloudflare-deployment --pass-count 2 --provider cloudflare', - '# To scope a template (e.g. narrow the trigger), crib its body and use `automation create`:', - 'polylane automation create --body-file template.json --trigger \'{"type":"cloudflare.deployment","filters":{"environments":["production"]}}\'', - ], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - const templateSlug = requirePositional(args, 0, 'template-slug'); - const passCount = getArgNumber(args, 'passCount'); - const provider = getArgString(args, 'provider') as Provider | undefined; - const api = new PolylaneAPI(config); - const automation = await api.automationsPostFromTemplate({ - workspaceId, - templateSlug, - ...(passCount !== undefined ? { passCount } : {}), - ...(provider !== undefined ? { provider } : {}), - }); - formatOutput(config, automation); - }, -}; diff --git a/src/commands/automation/index.ts b/src/commands/automation/index.ts deleted file mode 100644 index 58f5e22..0000000 --- a/src/commands/automation/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { automationListCommand } from './list'; -import { automationFindCommand } from './find'; -import { automationShowCommand } from './show'; -import { automationTriggerCommand } from './trigger'; -import { automationExecutionsCommand } from './executions'; -import { automationExecutionCommand } from './execution'; -import { automationRerunCommand } from './rerun'; -import { automationCatalogCommand } from './catalog'; -import { automationFromTemplateCommand } from './from-template'; -import { automationCreateCommand } from './create'; - -export const automationCommands = [ - automationListCommand, - automationFindCommand, - automationShowCommand, - automationTriggerCommand, - automationExecutionsCommand, - automationExecutionCommand, - automationRerunCommand, - automationCatalogCommand, - automationCreateCommand, - automationFromTemplateCommand, -]; diff --git a/src/commands/automation/list.ts b/src/commands/automation/list.ts deleted file mode 100644 index d561920..0000000 --- a/src/commands/automation/list.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatList } from '../../output/formatter'; -import { projectItems } from '../../output/project'; -import { requireWorkspace, getArgNumber, getArgBoolean } from '../helpers'; - -const FIELDS = ['id', 'name', 'triggerTypes', 'disabled', 'source', 'created']; - -export const automationListCommand: Command = { - name: 'automation list', - description: 'List automations', - operationId: 'automations.list', - options: [ - { flag: '--limit ', description: 'Max items (default 20)', type: 'number' }, - { flag: '--full', description: 'Return full objects', type: 'boolean' }, - ], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - const limit = getArgNumber(args, 'limit') ?? 20; - const full = getArgBoolean(args, 'full') === true; - - const api = new PolylaneAPI(config); - const result = await api.automationsList(workspaceId, { perPage: limit }); - - if (full) { - formatList(config, result as { items: Array>; count: number }); - return; - } - - formatList( - config, - { - items: projectItems(result.items as unknown as Array>, FIELDS), - count: result.count, - }, - { - headers: ['ID', 'Name', 'Triggers', 'Disabled', 'Source', 'Created'], - rows: (item) => [ - String(item.id ?? ''), - truncate(String(item.name ?? ''), 40), - Array.isArray(item.triggerTypes) ? item.triggerTypes.join(',') : '', - item.disabled ? 'yes' : 'no', - String(item.source ?? ''), - String(item.created ?? ''), - ], - } - ); - }, -}; - -function truncate(s: string, max: number): string { - return s.length > max ? s.slice(0, max - 1) + '…' : s; -} diff --git a/src/commands/automation/rerun.ts b/src/commands/automation/rerun.ts deleted file mode 100644 index b0b9143..0000000 --- a/src/commands/automation/rerun.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatOutput } from '../../output/formatter'; -import { requireWorkspace, requirePositional } from '../helpers'; - -export const automationRerunCommand: Command = { - name: 'automation rerun', - description: 'Rerun an automation execution', - operationId: 'automations.executions.rerun', - positional: [ - { name: 'automation-id', description: 'The automation ID' }, - { name: 'execution-id', description: 'The execution to rerun' }, - ], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - const automationId = requirePositional(args, 0, 'automation-id'); - const executionId = requirePositional(args, 1, 'execution-id'); - const api = new PolylaneAPI(config); - const execution = await api.automationsExecutionsRerun({ workspaceId, automationId, executionId }); - formatOutput(config, execution); - }, -}; diff --git a/src/commands/automation/show.ts b/src/commands/automation/show.ts deleted file mode 100644 index 2663685..0000000 --- a/src/commands/automation/show.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatOutput } from '../../output/formatter'; -import { requireWorkspace, requirePositional } from '../helpers'; - -export const automationShowCommand: Command = { - name: 'automation show', - description: 'Show an automation with its full configuration', - operationId: 'automations.get', - positional: [{ name: 'automation-id', description: 'The automation ID' }], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - const id = requirePositional(args, 0, 'automation-id'); - const api = new PolylaneAPI(config); - const result = await api.automationsGet(workspaceId, id); - formatOutput(config, result); - }, -}; diff --git a/src/commands/automation/trigger.ts b/src/commands/automation/trigger.ts deleted file mode 100644 index 9b062d6..0000000 --- a/src/commands/automation/trigger.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Command } from '../../command'; -import type { Config } from '../../config/schema'; -import { PolylaneAPI } from '../../generated/client'; -import { formatOutput } from '../../output/formatter'; -import { requireWorkspace, requirePositional } from '../helpers'; - -export const automationTriggerCommand: Command = { - name: 'automation trigger', - description: 'Manually trigger an automation to create an execution', - operationId: 'automations.trigger', - positional: [{ name: 'automation-id', description: 'The automation ID' }], - examples: ['polylane automation trigger auto_xxx'], - async execute(config: Config, _flags, args: Record): Promise { - const workspaceId = await requireWorkspace(config); - const id = requirePositional(args, 0, 'automation-id'); - const api = new PolylaneAPI(config); - const execution = await api.automationsTrigger({ workspaceId, id }); - formatOutput(config, execution); - }, -}; diff --git a/src/commands/feed/list.ts b/src/commands/feed/list.ts index 79f166d..5c46eae 100644 --- a/src/commands/feed/list.ts +++ b/src/commands/feed/list.ts @@ -15,9 +15,9 @@ import { ExitCode } from '../../errors/codes'; type Category = NonNullable[1]>['category']>; -const CATEGORY_COVERAGE: Record = { +// Retired category; the Exclude keeps this compiling until the API drops it too. +const CATEGORY_COVERAGE: Record, true> = { autofix: true, - automation: true, change: true, release: true, issue: true, @@ -43,7 +43,7 @@ const FIELDS = [ export const feedListCommand: Command = { name: 'feed list', - description: 'Workspace agent activity feed (autofixes, automations, changes, releases, issues…)', + description: 'Workspace agent activity feed (autofixes, changes, releases, issues…)', operationId: 'feed.list', options: [ { flag: '--category ', description: VALID_CATEGORIES.join(' | '), type: 'string' }, diff --git a/src/commands/index.ts b/src/commands/index.ts index c992dd0..3aa47cb 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -12,7 +12,6 @@ import { serviceCommands } from './service'; import { repoCommands } from './repo'; import { memoryCommands } from './memory'; import { threadCommands } from './thread'; -import { automationCommands } from './automation'; import { integrationCommands } from './integration'; import { cloudCommands } from './cloud'; import { workspaceCommands } from './workspace'; @@ -38,7 +37,6 @@ export function registerAllCommands(): void { ...memoryCommands, ...threadCommands, ...skillCommands, - ...automationCommands, ...integrationCommands, ...cloudCommands, ...workspaceCommands, diff --git a/src/commands/thread/list.ts b/src/commands/thread/list.ts index 442f1dc..4aa3395 100644 --- a/src/commands/thread/list.ts +++ b/src/commands/thread/list.ts @@ -8,7 +8,7 @@ import { CLIError } from '../../errors/base'; import { ExitCode } from '../../errors/codes'; const FIELDS = ['id', 'name', 'type', 'created']; -const VALID_TYPES = ['chat', 'hypothesis', 'automation', 'autofix', 'investigation', 'pr_review'] as const; +const VALID_TYPES = ['chat', 'hypothesis', 'autofix', 'investigation', 'pr_review'] as const; type ThreadType = (typeof VALID_TYPES)[number]; export const threadListCommand: Command = { @@ -16,7 +16,7 @@ export const threadListCommand: Command = { description: 'List conversation threads', operationId: 'threads.list', options: [ - { flag: '--type ', description: 'Filter by thread type (chat | hypothesis | automation | autofix | investigation | pr_review; comma-separated)', type: 'string' }, + { flag: '--type ', description: 'Filter by thread type (chat | hypothesis | autofix | investigation | pr_review; comma-separated)', type: 'string' }, { flag: '--labels ', description: 'Filter by labels (comma-separated)', type: 'string' }, { flag: '--limit ', description: 'Max items (default 20)', type: 'number' }, { flag: '--full', description: 'Return full objects', type: 'boolean' }, diff --git a/src/registry.ts b/src/registry.ts index ed646be..8ed1b70 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -26,7 +26,6 @@ const RESOURCE_ORDER: Record = { thread: { name: 'thread', description: 'Conversation threads', order: 60 }, artifact: { name: 'artifact', description: 'Per-thread artifacts (reports, diagrams, PRs)', order: 61 }, skill: { name: 'skill', description: 'Reusable agent skills (catalog, install, docs)', order: 63 }, - automation: { name: 'automation', description: 'Automations and executions', order: 65 }, autofix: { name: 'autofix', description: 'Automated code fixes (read-only)', order: 66 }, integration: { name: 'integration', description: 'Integrations (code hosts, chat, observability, issue tracking, coding agents, MCP)', order: 68 }, cloud: { name: 'cloud', description: 'Cloud accounts (aws, cloudflare, vercel, clickhouse, turso, ...)', order: 69 }, diff --git a/test/resolve.test.ts b/test/resolve.test.ts index 314b7fd..d52b014 100644 --- a/test/resolve.test.ts +++ b/test/resolve.test.ts @@ -48,7 +48,6 @@ describe('command resolution', () => { 'artifact', 'auth', 'autofix', - 'automation', 'cloud', 'config', 'feed',