diff --git a/.changeset/slack-destination-update-contract.md b/.changeset/slack-destination-update-contract.md new file mode 100644 index 0000000..9d2727f --- /dev/null +++ b/.changeset/slack-destination-update-contract.md @@ -0,0 +1,5 @@ +--- +"@outlit/cli": patch +--- + +Tighten destination update commands to the Slack-channel public contract. diff --git a/docs/ai-integrations/mcp.mdx b/docs/ai-integrations/mcp.mdx index 15b330d..d920e10 100644 --- a/docs/ai-integrations/mcp.mdx +++ b/docs/ai-integrations/mcp.mdx @@ -160,7 +160,7 @@ API-key authenticated MCP clients can also inspect and manage selected agent and | `outlit_destination_list` | List configured automation destinations with masked configuration | | `outlit_destination_get` | Get one configured automation destination with masked configuration | | `outlit_destination_create` | Create one Slack channel destination; can set it as default | -| `outlit_destination_update` | Update one automation destination; can set a Slack destination as default | +| `outlit_destination_update` | Update one Slack channel automation destination; can set it as default | | `outlit_destination_enable` | Enable one configured automation destination | | `outlit_destination_disable` | Disable one configured automation destination | | `outlit_destination_archive` | Archive one configured automation destination | diff --git a/docs/ai-integrations/platform-actions.mdx b/docs/ai-integrations/platform-actions.mdx index 46aec00..4682729 100644 --- a/docs/ai-integrations/platform-actions.mdx +++ b/docs/ai-integrations/platform-actions.mdx @@ -133,16 +133,16 @@ This workflow lets the agent discover available templates, understand supported | `POST /api/agents/{id}/enable` | Enable one configured agent | | `POST /api/agents/{id}/disable` | Disable one configured agent | | `POST /api/agents/{id}/rename` | Rename one configured agent | -| `GET /api/agents/{id}/runs` | List safe run summaries for one agent | -| `POST /api/agents/{id}/runs` | Start a manual run for one legacy churn agent | -| `GET /api/agents/{id}/runs/{runId}` | Get one safe run detail | +| `GET /api/agents/{agentId}/runs` | List safe run summaries for one agent | +| `POST /api/agents/{agentId}/runs` | Start a manual run for one legacy churn agent | +| `GET /api/agents/{agentId}/runs/{runId}` | Get one safe run detail | | `GET /api/automations/options` | Show automation schemas, constants, and constraints | | `GET /api/automations` | List configured automations | | `POST /api/automations` | Create an agent automation | | `GET /api/automations/{id}` | Get one configured automation | | `PATCH /api/automations/{id}` | Update one agent automation | -| `GET /api/automations/{id}/runs` | List safe automation run summaries | -| `GET /api/automations/{id}/runs/{runId}` | Get one safe automation run detail | +| `GET /api/automations/{automationId}/runs` | List safe automation run summaries | +| `GET /api/automations/{automationId}/runs/{runId}` | Get one safe automation run detail | | `POST /api/automations/{id}/enable` | Enable one configured automation | | `POST /api/automations/{id}/disable` | Disable one configured automation | | `POST /api/automations/{id}/archive` | Archive one configured automation | @@ -156,7 +156,7 @@ This workflow lets the agent discover available templates, understand supported | `GET /api/destinations` | List configured automation destinations with masked configuration | | `GET /api/destinations/{id}` | Get one configured automation destination with masked configuration | | `POST /api/destinations` | Create one Slack channel destination; can set it as default | -| `PATCH /api/destinations/{id}` | Update one automation destination; can set a Slack destination as default | +| `PATCH /api/destinations/{id}` | Update one Slack channel automation destination; can set it as default | | `POST /api/destinations/{id}/enable` | Enable one configured automation destination | | `POST /api/destinations/{id}/disable` | Disable one configured automation destination | | `POST /api/destinations/{id}/archive` | Archive one configured automation destination | diff --git a/docs/cli/commands.mdx b/docs/cli/commands.mdx index 06c43c1..84c84f4 100644 --- a/docs/cli/commands.mdx +++ b/docs/cli/commands.mdx @@ -695,7 +695,7 @@ outlit destinations disable 10000000-0000-4000-8000-000000000003 --json outlit destinations archive 10000000-0000-4000-8000-000000000003 --json ``` -Destination list results are under `result.data.destinations`; get, create, update, enable, and disable results are under `result.data.destination`. New destination creation is Slack-channel backed; use `destinations options` to inspect Slack connection status and available channel IDs. `destinations options` is non-mutating but requires `agents:write` because it lists Slack channel choices used to create destinations. Use `--default` on `destinations create` or `destinations update` to make a Slack destination the default fallback. `destinations update` patches only the fields you pass; use `--type slack` for Slack-backed destinations. Archive results return `result.data.destination.id` and `result.data.destination.archivedAt`. Raw destination secrets and unmasked provider configuration are not returned. +Destination list results are under `result.data.destinations`; get, create, update, enable, and disable results are under `result.data.destination`. Destination creation and update are Slack-channel backed; use `destinations options` to inspect Slack connection status and available channel IDs. `destinations options` is non-mutating but requires `agents:write` because it lists Slack channel choices used to create destinations. Use `--default` on `destinations create` or `destinations update` to make a Slack destination the default fallback. `destinations update` patches only the fields you pass; use `--type slack` for Slack-backed destinations. Archive results return `result.data.destination.id` and `result.data.destination.archivedAt`. Raw destination secrets and unmasked provider configuration are not returned. ## Settings diff --git a/docs/openapi.json b/docs/openapi.json index bd165df..296b07e 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1304,7 +1304,7 @@ } } }, - "/api/automations/{id}/runs": { + "/api/automations/{automationId}/runs": { "get": { "tags": ["Platform API"], "summary": "List automation runs", @@ -1312,7 +1312,7 @@ "operationId": "listAutomationRuns", "parameters": [ { - "name": "id", + "name": "automationId", "in": "path", "required": true, "description": "Automation ID.", @@ -1378,7 +1378,7 @@ } }, "404": { - "description": "No automation was found for the authenticated organization and requested id.", + "description": "No automation was found for the authenticated organization and requested automation ID.", "content": { "application/json": { "schema": { @@ -1403,7 +1403,7 @@ } } }, - "/api/automations/{id}/runs/{runId}": { + "/api/automations/{automationId}/runs/{runId}": { "get": { "tags": ["Platform API"], "summary": "Get automation run", @@ -1411,7 +1411,7 @@ "operationId": "getAutomationRun", "parameters": [ { - "name": "id", + "name": "automationId", "in": "path", "required": true, "description": "Automation ID.", @@ -2459,7 +2459,7 @@ } } }, - "/api/agents/{id}/runs": { + "/api/agents/{agentId}/runs": { "get": { "tags": ["Platform API"], "summary": "List agent runs", @@ -2467,7 +2467,7 @@ "operationId": "listAgentRuns", "parameters": [ { - "name": "id", + "name": "agentId", "in": "path", "required": true, "description": "Agent ID.", @@ -2523,7 +2523,7 @@ } }, "404": { - "description": "No agent was found for the authenticated organization and requested id.", + "description": "No agent was found for the authenticated organization and requested agent ID.", "content": { "application/json": { "schema": { @@ -2554,7 +2554,7 @@ "operationId": "startAgentRun", "parameters": [ { - "name": "id", + "name": "agentId", "in": "path", "required": true, "description": "Agent ID.", @@ -2612,7 +2612,7 @@ } }, "404": { - "description": "No agent was found for the authenticated organization and requested id.", + "description": "No agent was found for the authenticated organization and requested agent ID.", "content": { "application/json": { "schema": { @@ -2647,7 +2647,7 @@ } } }, - "/api/agents/{id}/runs/{runId}": { + "/api/agents/{agentId}/runs/{runId}": { "get": { "tags": ["Platform API"], "summary": "Get agent run", @@ -2655,7 +2655,7 @@ "operationId": "getAgentRun", "parameters": [ { - "name": "id", + "name": "agentId", "in": "path", "required": true, "description": "Agent ID.", @@ -3283,7 +3283,7 @@ "patch": { "tags": ["Platform API"], "summary": "Update destination", - "description": "Update one automation destination. Responses include masked configuration only. Passing isDefault true on a Slack destination makes it the default fallback destination and enables it.", + "description": "Update one Slack channel automation destination. Responses include masked configuration only. Passing isDefault true makes it the default fallback destination and enables it.", "operationId": "updateDestination", "parameters": [ { @@ -8414,62 +8414,29 @@ "additionalProperties": false }, "UpdateDestinationRequest": { - "oneOf": [ - { - "type": "object", - "required": ["type"], - "minProperties": 2, - "properties": { - "type": { - "type": "string", - "const": "WEBHOOK_ENDPOINT" - }, - "name": { - "type": "string", - "minLength": 1, - "maxLength": 120 - }, - "description": { - "type": ["string", "null"], - "maxLength": 1000 - }, - "enabled": { - "type": "boolean" - }, - "url": { - "type": "string", - "format": "uri", - "pattern": "^(https://|http://(localhost|127\\.0\\.0\\.1|\\[::1\\]|::1)([:/]|$))" - } - }, - "additionalProperties": false + "type": "object", + "required": ["type"], + "minProperties": 2, + "properties": { + "type": { + "type": "string", + "const": "SLACK_CHANNEL" }, - { - "type": "object", - "required": ["type"], - "minProperties": 2, - "properties": { - "type": { - "type": "string", - "const": "SLACK_CHANNEL" - }, - "label": { - "type": "string", - "minLength": 1, - "maxLength": 120 - }, - "enabled": { - "type": "boolean" - }, - "isDefault": { - "type": "boolean", - "const": true, - "description": "Set this Slack destination as the default fallback destination." - } - }, - "additionalProperties": false + "label": { + "type": "string", + "minLength": 1, + "maxLength": 120 + }, + "enabled": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean", + "const": true, + "description": "Set this Slack destination as the default fallback destination." } - ] + }, + "additionalProperties": false }, "UpdateAgentCommandSuccess": { "type": "object", diff --git a/packages/cli/src/commands/completions.ts b/packages/cli/src/commands/completions.ts index 277f799..0ef48c1 100644 --- a/packages/cli/src/commands/completions.ts +++ b/packages/cli/src/commands/completions.ts @@ -356,14 +356,11 @@ const COMMANDS: readonly CmdDef[] = [ }, { name: "update", - desc: "Update an automation destination", + desc: "Update a Slack channel destination", flags: [ ...COMMON, { name: "--type", desc: "Destination type" }, - { name: "--name", desc: "Webhook destination name" }, - { name: "--url", desc: "Webhook URL" }, { name: "--label", desc: "Slack channel label" }, - { name: "--description", desc: "Destination description" }, { name: "--default", desc: "Make this the default destination" }, { name: "--enabled", desc: "Enable the destination after update" }, { name: "--disabled", desc: "Disable the destination" }, diff --git a/packages/cli/src/commands/destinations/update.ts b/packages/cli/src/commands/destinations/update.ts index a20de02..9397f5d 100644 --- a/packages/cli/src/commands/destinations/update.ts +++ b/packages/cli/src/commands/destinations/update.ts @@ -5,17 +5,13 @@ import { getClientOrExit, runTool } from "../../lib/api" import { outputError } from "../../lib/output" import { optionalTrimmedString, requiredTrimmedString } from "../../lib/platform-input" -function parseDestinationType(type: string, json: boolean): "WEBHOOK_ENDPOINT" | "SLACK_CHANNEL" { +function parseDestinationType(type: string, json: boolean): "SLACK_CHANNEL" { const normalized = type.trim().toLowerCase() - if (normalized === "webhook" || normalized === "webhook_endpoint") { - return "WEBHOOK_ENDPOINT" - } - if (normalized === "slack" || normalized === "slack_channel") { return "SLACK_CHANNEL" } - return outputError({ message: "--type must be webhook or slack", code: "invalid_input" }, json) + return outputError({ message: "--type must be slack", code: "invalid_input" }, json) } function parseEnabledFlag(args: { enabled?: boolean; disabled?: boolean }, json: boolean) { @@ -36,9 +32,9 @@ export default defineCommand({ meta: { name: "update", description: [ - "Update an Outlit automation destination.", + "Update an Outlit Slack channel automation destination.", "", - "Provide --type slack plus one or more fields to patch. Existing webhook destinations can still be patched with --type webhook.", + "Provide --type slack plus one or more fields to patch.", "", "Examples:", " outlit destinations update 10000000-0000-4000-8000-000000000003 --type slack --label '#customer-ops' --json", @@ -58,12 +54,9 @@ export default defineCommand({ }, type: { type: "string", - description: "Required destination type: webhook or slack", + description: "Required destination type: slack", }, - name: { type: "string", description: "Webhook destination name" }, - url: { type: "string", description: "Optional webhook URL" }, label: { type: "string", description: "Slack channel label" }, - description: { type: "string", description: "Optional destination description" }, default: { type: "boolean", description: "Make this the default destination" }, enabled: { type: "boolean", description: "Enable the destination after update" }, disabled: { type: "boolean", description: "Disable the destination" }, @@ -85,42 +78,16 @@ export default defineCommand({ json, ) } - if (args.default === true && type !== "SLACK_CHANNEL") { - return outputError( - { message: "--default is only supported for Slack destinations", code: "invalid_input" }, - json, - ) + const label = optionalTrimmedString(args.label) + const input = { + id: args.id, + type, + ...(label ? { label } : {}), + ...(enabled !== undefined ? { enabled } : {}), + ...(args.default === true ? { isDefault: true } : {}), } - const input = - type === "SLACK_CHANNEL" - ? { - id: args.id, - type, - ...(optionalTrimmedString(args.label ?? args.name) - ? { label: optionalTrimmedString(args.label ?? args.name) } - : {}), - ...(enabled !== undefined ? { enabled } : {}), - ...(args.default === true ? { isDefault: true } : {}), - } - : { - id: args.id, - type: "WEBHOOK_ENDPOINT", - ...(optionalTrimmedString(args.name) ? { name: optionalTrimmedString(args.name) } : {}), - ...(args.description !== undefined - ? { description: optionalTrimmedString(args.description) } - : {}), - ...(enabled !== undefined ? { enabled } : {}), - ...(optionalTrimmedString(args.url) ? { url: optionalTrimmedString(args.url) } : {}), - } - if ( - !("name" in input) && - !("description" in input) && - !("url" in input) && - !("label" in input) && - !("enabled" in input) && - !("isDefault" in input) - ) { + if (!("label" in input) && !("enabled" in input) && !("isDefault" in input)) { return outputError( { message: "Provide at least one destination field to update", diff --git a/packages/cli/tests/commands/completions.test.ts b/packages/cli/tests/commands/completions.test.ts index ee8b3f0..bc04916 100644 --- a/packages/cli/tests/commands/completions.test.ts +++ b/packages/cli/tests/commands/completions.test.ts @@ -91,7 +91,7 @@ describe("completions command", () => { 'COMPREPLY=($(compgen -W "--api-key --json --type --channel-id --label --default --disabled"', ) expect(out).toContain( - 'COMPREPLY=($(compgen -W "--api-key --json --type --name --url --label --description --default --enabled --disabled"', + 'COMPREPLY=($(compgen -W "--api-key --json --type --label --default --enabled --disabled"', ) expect(out).toContain( 'COMPREPLY=($(compgen -W "--api-key --json --status --confidence --limit"', diff --git a/packages/cli/tests/commands/platform-lifecycle.test.ts b/packages/cli/tests/commands/platform-lifecycle.test.ts index 47aec0d..e0ea479 100644 --- a/packages/cli/tests/commands/platform-lifecycle.test.ts +++ b/packages/cli/tests/commands/platform-lifecycle.test.ts @@ -408,9 +408,9 @@ describe("platform lifecycle commands", () => { updateDestinationCmd.run!({ args: { id: destinationId, - type: "webhook", - name: "Updated webhook", - description: "Updated", + type: "slack", + label: "#updated-ops", + disabled: true, json: true, }, } as Parameters>[0]), @@ -435,9 +435,9 @@ describe("platform lifecycle commands", () => { }) expect(mockCallTool).toHaveBeenNthCalledWith(2, "outlit_destination_update", { id: destinationId, - type: "WEBHOOK_ENDPOINT", - name: "Updated webhook", - description: "Updated", + type: "SLACK_CHANNEL", + label: "#updated-ops", + enabled: false, }) expect(mockCallTool).toHaveBeenNthCalledWith(3, "outlit_destination_update", { id: destinationId, @@ -454,7 +454,7 @@ describe("platform lifecycle commands", () => { updateDestinationCmd.run!({ args: { id: "10000000-0000-4000-8000-000000000003", - type: "webhook", + type: "slack", json: true, }, } as Parameters>[0]), @@ -471,7 +471,7 @@ describe("platform lifecycle commands", () => { updateDestinationCmd.run!({ args: { id: "10000000-0000-4000-8000-000000000003", - name: "Updated webhook", + label: "#updated-ops", json: true, }, } as Parameters>[0]), @@ -488,8 +488,8 @@ describe("platform lifecycle commands", () => { updateDestinationCmd.run!({ args: { id: "10000000-0000-4000-8000-000000000003", - type: "email", - name: "Updated webhook", + type: "webhook", + label: "#updated-ops", enabled: true, json: true, }, diff --git a/packages/cli/tests/lib/client.test.ts b/packages/cli/tests/lib/client.test.ts index c70d2d9..3a360d4 100644 --- a/packages/cli/tests/lib/client.test.ts +++ b/packages/cli/tests/lib/client.test.ts @@ -717,9 +717,8 @@ describe("client.callTool()", () => { }) await client.callTool("outlit_destination_update", { id: "10000000-0000-4000-8000-000000000003", - type: "WEBHOOK_ENDPOINT", - name: "Customer ops", - description: null, + type: "SLACK_CHANNEL", + label: "#customer-ops", enabled: true, })