Skip to content

Commit 37184b8

Browse files
committed
feat: add workflow discovery and execution
Add `workflow` command with subcommands: - `list` — discover workflows bookmarked in a channel - `preview` — get trigger metadata - `get` — get workflow definition including form fields - `run` — trip a workflow trigger
1 parent b63a2b2 commit 37184b8

File tree

6 files changed

+474
-0
lines changed

6 files changed

+474
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ agent-slack
9090
│ ├── all <query> # messages + files
9191
│ ├── messages <query>
9292
│ └── files <query>
93+
├── workflow
94+
│ ├── list <channel> # workflows bookmarked in a channel
95+
│ ├── preview <trigger-id> # trigger metadata (no side effects)
96+
│ ├── get <id> # workflow definition + form fields
97+
│ └── run <trigger-id> # trip a workflow trigger
9398
└── canvas
9499
└── get <canvas-url-or-id> # canvas → markdown
95100
```

skills/agent-slack/SKILL.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ description: |
1212
- Looking up Slack users
1313
- Marking channels/DMs as read
1414
- Opening DM or group DM channels
15+
- Discovering and running Slack workflows
1516
Triggers: "slack message", "slack thread", "slack URL", "slack link", "read slack", "reply on slack", "search slack", "channel history", "recent messages", "channel messages", "latest messages", "mark as read", "mark read"
1617
---
1718

@@ -218,6 +219,25 @@ To make a specific message appear unread, set `--ts` to just before it (subtract
218219
agent-slack channel mark "general" --workspace "myteam" --ts "1770165109.628378"
219220
```
220221

222+
## Workflows
223+
224+
Discover and run Slack workflows bookmarked in channels:
225+
226+
```bash
227+
# List workflows in a channel
228+
agent-slack workflow list "#ops"
229+
230+
# Preview trigger metadata (no side effects)
231+
agent-slack workflow preview "Ft123ABC"
232+
233+
# Get workflow definition including form fields and steps
234+
agent-slack workflow get "Ft123ABC"
235+
agent-slack workflow get "Wf456DEF"
236+
237+
# Trip a workflow trigger
238+
agent-slack workflow run "Ft123ABC" --channel "#ops"
239+
```
240+
221241
## Canvas + Users
222242

223243
```bash

skills/agent-slack/references/commands.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@ Common options:
125125
- `--workspace <url-or-unique-substring>` (required when passing an id and multiple workspaces)
126126
- `--max-chars <n>` (default `20000`, `-1` unlimited)
127127

128+
## Workflows
129+
130+
- `agent-slack workflow list <channel> [--workspace <url-or-unique-substring>]` — list workflows bookmarked or featured in a channel
131+
- `agent-slack workflow preview <trigger-id> [--workspace <url-or-unique-substring>]` — get workflow metadata from a trigger ID (no side effects)
132+
- `agent-slack workflow get <id> [--workspace <url-or-unique-substring>]` — get workflow definition including form fields and steps (accepts `Ft...` or `Wf...`)
133+
- `agent-slack workflow run <trigger-id> --channel <id-or-name> [--workspace <url-or-unique-substring>]` — trip a workflow trigger
134+
128135
## Users
129136

130137
- `agent-slack user list [--workspace <url-or-unique-substring>] [--limit <n>] [--cursor <cursor>] [--include-bots]`

src/cli/workflow-command.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import type { Command } from "commander";
2+
import type { CliContext } from "./context.ts";
3+
import { pruneEmpty } from "../lib/compact-json.ts";
4+
import { resolveChannelId } from "../slack/channels.ts";
5+
import {
6+
getWorkflowSchema,
7+
listChannelWorkflows,
8+
previewWorkflow,
9+
resolveShortcutUrl,
10+
runWorkflow,
11+
} from "../slack/workflows.ts";
12+
13+
type WorkspaceOption = {
14+
workspace?: string;
15+
};
16+
17+
export function registerWorkflowCommand(input: { program: Command; ctx: CliContext }): void {
18+
const workflowCmd = input.program
19+
.command("workflow")
20+
.description("Discover and interact with Slack workflows");
21+
22+
workflowCmd
23+
.command("list")
24+
.description("List workflows bookmarked or featured in a channel")
25+
.argument("<channel>", "Channel id or name (#channel, channel, C...)")
26+
.option(
27+
"--workspace <url>",
28+
"Workspace selector (full URL or unique substring; required if you have multiple workspaces)",
29+
)
30+
.action(async (...args) => {
31+
const [channel, options] = args as [string, WorkspaceOption];
32+
try {
33+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
34+
const payload = await input.ctx.withAutoRefresh({
35+
workspaceUrl,
36+
work: async () => {
37+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
38+
const channelId = await resolveChannelId(client, channel);
39+
return await listChannelWorkflows(client, channelId);
40+
},
41+
});
42+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
43+
} catch (err: unknown) {
44+
console.error(input.ctx.errorMessage(err));
45+
process.exitCode = 1;
46+
}
47+
});
48+
49+
workflowCmd
50+
.command("preview")
51+
.description("Get workflow metadata from a trigger ID (no side effects)")
52+
.argument("<trigger-id>", "Trigger ID (Ft...)")
53+
.option(
54+
"--workspace <url>",
55+
"Workspace selector (full URL or unique substring; required if you have multiple workspaces)",
56+
)
57+
.action(async (...args) => {
58+
const [triggerId, options] = args as [string, WorkspaceOption];
59+
try {
60+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
61+
const payload = await input.ctx.withAutoRefresh({
62+
workspaceUrl,
63+
work: async () => {
64+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
65+
return await previewWorkflow(client, triggerId);
66+
},
67+
});
68+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
69+
} catch (err: unknown) {
70+
console.error(input.ctx.errorMessage(err));
71+
process.exitCode = 1;
72+
}
73+
});
74+
75+
workflowCmd
76+
.command("get")
77+
.description("Get workflow definition including form fields and steps (accepts Ft... or Wf...)")
78+
.argument("<id>", "Trigger ID (Ft...) or Workflow ID (Wf...)")
79+
.option(
80+
"--workspace <url>",
81+
"Workspace selector (full URL or unique substring; required if you have multiple workspaces)",
82+
)
83+
.action(async (...args) => {
84+
const [id, options] = args as [string, WorkspaceOption];
85+
try {
86+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
87+
const payload = await input.ctx.withAutoRefresh({
88+
workspaceUrl,
89+
work: async () => {
90+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
91+
let workflowId = id;
92+
if (id.startsWith("Ft")) {
93+
const preview = await previewWorkflow(client, id);
94+
workflowId = preview.workflow.id;
95+
}
96+
return await getWorkflowSchema(client, workflowId);
97+
},
98+
});
99+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
100+
} catch (err: unknown) {
101+
console.error(input.ctx.errorMessage(err));
102+
process.exitCode = 1;
103+
}
104+
});
105+
106+
workflowCmd
107+
.command("run")
108+
.description("Trip a workflow trigger")
109+
.argument("<trigger-id>", "Trigger ID (Ft...)")
110+
.requiredOption("--channel <id-or-name>", "Channel where the workflow is bookmarked")
111+
.option(
112+
"--workspace <url>",
113+
"Workspace selector (full URL or unique substring; required if you have multiple workspaces)",
114+
)
115+
.action(async (...args) => {
116+
const [triggerId, options] = args as [string, WorkspaceOption & { channel: string }];
117+
try {
118+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
119+
const payload = await input.ctx.withAutoRefresh({
120+
workspaceUrl,
121+
work: async () => {
122+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
123+
const channelId = await resolveChannelId(client, options.channel);
124+
const shortcutUrl = await resolveShortcutUrl(client, { channelId, triggerId });
125+
return await runWorkflow(client, { shortcutUrl, channelId, triggerId });
126+
},
127+
});
128+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
129+
} catch (err: unknown) {
130+
console.error(input.ctx.errorMessage(err));
131+
process.exitCode = 1;
132+
}
133+
});
134+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { registerSearchCommand } from "./cli/search-command.ts";
88
import { registerUpdateCommand } from "./cli/update-command.ts";
99
import { registerUserCommand } from "./cli/user-command.ts";
1010
import { registerChannelCommand } from "./cli/channel-command.ts";
11+
import { registerWorkflowCommand } from "./cli/workflow-command.ts";
1112
import { backgroundUpdateCheck } from "./lib/update.ts";
1213

1314
const program = new Command();
@@ -25,6 +26,7 @@ registerSearchCommand({ program, ctx });
2526
registerUpdateCommand({ program });
2627
registerUserCommand({ program, ctx });
2728
registerChannelCommand({ program, ctx });
29+
registerWorkflowCommand({ program, ctx });
2830

2931
program.parse(process.argv);
3032
if (!process.argv.slice(2).length) {

0 commit comments

Comments
 (0)