Skip to content
Open
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
5 changes: 1 addition & 4 deletions skills/agent-slack/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,7 @@ agent-slack message edit "general" "Updated text" --workspace "myteam" --ts "177
agent-slack message delete "general" --workspace "myteam" --ts "1770165109.628379"
```

Attach options for `message send`:

- `--attach <path>` upload a local file (repeatable)
- `--blocks <path>` send raw [Block Kit](https://docs.slack.dev/block-kit/) blocks from a JSON file (or `-` for stdin). Enables headers, dividers, table blocks, and other structured layouts. Incompatible with `--attach`.
Message options: `message send --attach <path>` uploads files; `message send|edit --blocks <path>` uses raw Block Kit JSON (`-` for stdin).

`message send` returns `channel_id` plus the posted `ts` and a `permalink` (for non-attachment sends). `thread_ts` appears only when replying in a thread.

Expand Down
1 change: 1 addition & 0 deletions skills/agent-slack/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Run `agent-slack --help` (or `agent-slack <command> --help`) for the full option
- Options:
- `--workspace <url-or-unique-substring>` (needed for channel _names_ across multiple workspaces)
- `--ts <seconds>.<micros>` (required for channel targets)
- `--blocks <path>` raw Block Kit blocks JSON (`-` for stdin)

- `agent-slack message delete <target>`
- URL target deletes that exact message.
Expand Down
5 changes: 4 additions & 1 deletion src/cli/message-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ export async function editMessage(input: {
ctx: CliContext;
targetInput: string;
text: string;
options: { workspace?: string; ts?: string };
options: { workspace?: string; ts?: string; blocks?: string };
}): Promise<Record<string, unknown>> {
const target = parseMsgTarget(String(input.targetInput));
if (target.kind === "user") {
Expand All @@ -268,6 +268,7 @@ export async function editMessage(input: {
}
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(input.options.workspace);
const formattedText = formatOutboundSlackText(input.text);
const blocks = input.options.blocks ? loadBlocksFromPath(input.options.blocks) : null;

await input.ctx.withAutoRefresh({
workspaceUrl: target.kind === "url" ? target.ref.workspace_url : workspaceUrl,
Expand All @@ -280,6 +281,7 @@ export async function editMessage(input: {
channel: ref.channel_id,
ts: ref.message_ts,
text: formattedText,
...(blocks ? { blocks } : {}),
});
return;
}
Expand All @@ -295,6 +297,7 @@ export async function editMessage(input: {
channel: channelId,
ts,
text: formattedText,
...(blocks ? { blocks } : {}),
});
},
});
Expand Down
3 changes: 2 additions & 1 deletion src/cli/message-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,12 @@ export function registerMessageCommand(input: { program: Command; ctx: CliContex
"Workspace selector (full URL or unique substring; needed when using #channel/channel id across multiple workspaces)",
)
.option("--ts <ts>", "Message ts (required when using #channel/channel id)")
.option("--blocks <path>", "Read Block Kit blocks JSON from file or '-'")
.action(async (...args) => {
const [targetInput, text, options] = args as [
string,
string,
{ workspace?: string; ts?: string },
{ workspace?: string; ts?: string; blocks?: string },
];
try {
const payload = await editMessage({
Expand Down
85 changes: 84 additions & 1 deletion test/message-send.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { CliContext } from "../src/cli/context.ts";
import { sendMessage } from "../src/cli/message-actions.ts";
import { editMessage, sendMessage } from "../src/cli/message-actions.ts";

function createContext(calls: { method: string; params: Record<string, unknown> }[]) {
const client = {
Expand Down Expand Up @@ -352,3 +352,86 @@ describe("sendMessage", () => {
}
});
});

describe("editMessage", () => {
test("edits a normal message without blocks when no --blocks file is passed", async () => {
const calls: { method: string; params: Record<string, unknown> }[] = [];
const ctx = createContext(calls);

const result = await editMessage({
ctx,
targetInput: "C12345678",
text: "hello",
options: { workspace: "https://workspace.slack.com", ts: "1770165109.628379" },
});

expect(result).toEqual({ ok: true });
expect(calls).toHaveLength(1);
expect(calls[0]?.method).toBe("chat.update");
expect(calls[0]?.params).toEqual({
channel: "C12345678",
ts: "1770165109.628379",
text: "hello",
});
});

test("edits a message URL with Block Kit JSON from file", async () => {
const calls: { method: string; params: Record<string, unknown> }[] = [];
const ctx = createContext(calls);
const dir = await mkdtemp(join(tmpdir(), "agent-slack-edit-test-"));
const blocksPath = join(dir, "blocks.json");
const blocks = [
{ type: "header", text: { type: "plain_text", text: "Edited" } },
{ type: "section", text: { type: "mrkdwn", text: "Updated body" } },
];
await writeFile(blocksPath, JSON.stringify(blocks));

try {
await editMessage({
ctx,
targetInput: "https://workspace.slack.com/archives/C12345678/p1770165109628379",
text: "fallback text",
options: { blocks: blocksPath },
});
} finally {
await rm(dir, { recursive: true, force: true });
}

expect(calls).toHaveLength(1);
expect(calls[0]?.method).toBe("chat.update");
expect(calls[0]?.params).toEqual({
channel: "C12345678",
ts: "1770165109.628379",
text: "fallback text",
blocks,
});
});

test("edits a channel target with --ts and Block Kit JSON from file", async () => {
const calls: { method: string; params: Record<string, unknown> }[] = [];
const ctx = createContext(calls);
const dir = await mkdtemp(join(tmpdir(), "agent-slack-edit-test-"));
const blocksPath = join(dir, "blocks.json");
const blocks = [{ type: "divider" }];
await writeFile(blocksPath, JSON.stringify(blocks));

try {
await editMessage({
ctx,
targetInput: "C12345678",
text: "fallback text",
options: {
workspace: "https://workspace.slack.com",
ts: "1770165109.628379",
blocks: blocksPath,
},
});
} finally {
await rm(dir, { recursive: true, force: true });
}

expect(calls).toHaveLength(1);
expect(calls[0]?.method).toBe("chat.update");
expect(calls[0]?.params.blocks).toEqual(blocks);
});
});