agent-slack 0.8.5 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/dist/index.js +105 -25
- package/dist/index.js.map +7 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,6 +231,8 @@ Attach options for `message send`:
|
|
|
231
231
|
|
|
232
232
|
- `--attach <path>` upload a local file (repeatable)
|
|
233
233
|
|
|
234
|
+
`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.
|
|
235
|
+
|
|
234
236
|
### List, create, and invite channels
|
|
235
237
|
|
|
236
238
|
```bash
|
|
@@ -335,6 +337,81 @@ agent-slack user get U12345678 --workspace "https://workspace.slack.com" | jq .
|
|
|
335
337
|
agent-slack user get "@alice" --workspace "https://workspace.slack.com" | jq .
|
|
336
338
|
```
|
|
337
339
|
|
|
340
|
+
### Unreads (inbox view)
|
|
341
|
+
|
|
342
|
+
See all unread messages across channels, DMs, and threads in one place:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# Show all unreads with message content
|
|
346
|
+
agent-slack unreads
|
|
347
|
+
|
|
348
|
+
# Show only unread counts (no message content)
|
|
349
|
+
agent-slack unreads --counts-only
|
|
350
|
+
|
|
351
|
+
# Limit messages per channel (default 10)
|
|
352
|
+
agent-slack unreads --max-messages 5
|
|
353
|
+
|
|
354
|
+
# Include system messages (joins, leaves, topic changes)
|
|
355
|
+
agent-slack unreads --include-system
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Output includes channels sorted by mention count, then unread count:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"channels": [
|
|
363
|
+
{
|
|
364
|
+
"channel_id": "C123...",
|
|
365
|
+
"channel_name": "general",
|
|
366
|
+
"channel_type": "channel",
|
|
367
|
+
"unread_count": 5,
|
|
368
|
+
"mention_count": 2,
|
|
369
|
+
"messages": [...]
|
|
370
|
+
}
|
|
371
|
+
],
|
|
372
|
+
"threads": {
|
|
373
|
+
"has_unreads": true,
|
|
374
|
+
"mention_count": 3
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Note: This feature uses the `client.counts` API which may be restricted in some Enterprise Grid workspaces (`team_is_restricted` error).
|
|
380
|
+
|
|
381
|
+
### Later (saved messages)
|
|
382
|
+
|
|
383
|
+
Manage your saved-for-later messages (Slack's Later tab):
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# List saved messages (in-progress by default)
|
|
387
|
+
agent-slack later list
|
|
388
|
+
|
|
389
|
+
# Show only counts per state
|
|
390
|
+
agent-slack later list --counts-only
|
|
391
|
+
|
|
392
|
+
# Filter by state: in_progress, completed, archived, all
|
|
393
|
+
agent-slack later list --state completed
|
|
394
|
+
|
|
395
|
+
# Save a message for later
|
|
396
|
+
agent-slack later save "https://workspace.slack.com/archives/C123/p1700000000000000"
|
|
397
|
+
|
|
398
|
+
# Mark as completed
|
|
399
|
+
agent-slack later complete "https://workspace.slack.com/archives/C123/p1700000000000000"
|
|
400
|
+
|
|
401
|
+
# Archive
|
|
402
|
+
agent-slack later archive "https://workspace.slack.com/archives/C123/p1700000000000000"
|
|
403
|
+
|
|
404
|
+
# Move back to in-progress
|
|
405
|
+
agent-slack later reopen "https://workspace.slack.com/archives/C123/p1700000000000000"
|
|
406
|
+
|
|
407
|
+
# Remove from saved
|
|
408
|
+
agent-slack later remove "https://workspace.slack.com/archives/C123/p1700000000000000"
|
|
409
|
+
|
|
410
|
+
# Set a reminder
|
|
411
|
+
agent-slack later remind "https://workspace.slack.com/archives/C123/p1700000000000000" --in 1h
|
|
412
|
+
agent-slack later remind "https://workspace.slack.com/archives/C123/p1700000000000000" --in tomorrow
|
|
413
|
+
```
|
|
414
|
+
|
|
338
415
|
### Fetch a Canvas as Markdown
|
|
339
416
|
|
|
340
417
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -3541,6 +3541,16 @@ function parseSlackMessageUrl(input) {
|
|
|
3541
3541
|
const workspace_url = `${url.protocol}//${url.host}`;
|
|
3542
3542
|
return { workspace_url, channel_id, message_ts, thread_ts_hint, raw: input, possiblyTruncated };
|
|
3543
3543
|
}
|
|
3544
|
+
function buildSlackMessageUrl(input) {
|
|
3545
|
+
const workspaceUrl = input.workspace_url.replace(/\/$/, "");
|
|
3546
|
+
const digits = input.message_ts.replace(".", "");
|
|
3547
|
+
const url = new URL(`${workspaceUrl}/archives/${input.channel_id}/p${digits}`);
|
|
3548
|
+
if (input.thread_ts && input.thread_ts !== input.message_ts) {
|
|
3549
|
+
url.searchParams.set("thread_ts", input.thread_ts);
|
|
3550
|
+
url.searchParams.set("cid", input.channel_id);
|
|
3551
|
+
}
|
|
3552
|
+
return url.toString();
|
|
3553
|
+
}
|
|
3544
3554
|
|
|
3545
3555
|
// src/cli/targets.ts
|
|
3546
3556
|
function parseMsgTarget(input) {
|
|
@@ -3579,14 +3589,32 @@ var CODE_BLOCK_START = /^```/;
|
|
|
3579
3589
|
var BLOCKQUOTE_RE = /^> (.*)$/;
|
|
3580
3590
|
function parseInlineElements(text) {
|
|
3581
3591
|
const elements = [];
|
|
3582
|
-
const re = /`([^`]+)`|\*([^*]+)\*|_([^_]+)_|~([^~]+)
|
|
3592
|
+
const re = /`([^`]+)`|\*([^*]+)\*|_([^_]+)_|~([^~]+)~|<@([UWB][A-Z0-9]+)(?:\|[^>]*)?>|<!(here|channel|everyone)(?:\|[^>]*)?>|<([^>|]+)\|([^>]+)>|<([^>|]+)>|(?:^|(?<=[^A-Za-z0-9_]))@([UWB][A-Z0-9]{6,})\b|(?:^|(?<=[^A-Za-z0-9_]))@(here|channel|everyone)\b/g;
|
|
3583
3593
|
let lastIndex = 0;
|
|
3584
3594
|
let match;
|
|
3595
|
+
const pushText = (slice) => {
|
|
3596
|
+
if (slice) {
|
|
3597
|
+
elements.push({ type: "text", text: slice });
|
|
3598
|
+
}
|
|
3599
|
+
};
|
|
3585
3600
|
while ((match = re.exec(text)) !== null) {
|
|
3586
3601
|
if (match.index > lastIndex) {
|
|
3587
|
-
|
|
3588
|
-
}
|
|
3589
|
-
const [
|
|
3602
|
+
pushText(text.slice(lastIndex, match.index));
|
|
3603
|
+
}
|
|
3604
|
+
const [
|
|
3605
|
+
,
|
|
3606
|
+
code,
|
|
3607
|
+
bold,
|
|
3608
|
+
italic,
|
|
3609
|
+
strike,
|
|
3610
|
+
userToken,
|
|
3611
|
+
broadcastToken,
|
|
3612
|
+
linkUrl,
|
|
3613
|
+
linkText,
|
|
3614
|
+
bareUrl,
|
|
3615
|
+
bareUserId,
|
|
3616
|
+
bareBroadcast
|
|
3617
|
+
] = match;
|
|
3590
3618
|
if (code != null) {
|
|
3591
3619
|
elements.push({ type: "text", text: code, style: { code: true } });
|
|
3592
3620
|
} else if (bold != null) {
|
|
@@ -3595,15 +3623,29 @@ function parseInlineElements(text) {
|
|
|
3595
3623
|
elements.push({ type: "text", text: italic, style: { italic: true } });
|
|
3596
3624
|
} else if (strike != null) {
|
|
3597
3625
|
elements.push({ type: "text", text: strike, style: { strike: true } });
|
|
3626
|
+
} else if (userToken != null) {
|
|
3627
|
+
elements.push({ type: "user", user_id: userToken });
|
|
3628
|
+
} else if (broadcastToken != null) {
|
|
3629
|
+
elements.push({
|
|
3630
|
+
type: "broadcast",
|
|
3631
|
+
range: broadcastToken
|
|
3632
|
+
});
|
|
3598
3633
|
} else if (linkUrl != null && linkText != null) {
|
|
3599
3634
|
elements.push({ type: "link", url: linkUrl, text: linkText });
|
|
3600
3635
|
} else if (bareUrl != null) {
|
|
3601
3636
|
elements.push({ type: "link", url: bareUrl });
|
|
3637
|
+
} else if (bareUserId != null) {
|
|
3638
|
+
elements.push({ type: "user", user_id: bareUserId });
|
|
3639
|
+
} else if (bareBroadcast != null) {
|
|
3640
|
+
elements.push({
|
|
3641
|
+
type: "broadcast",
|
|
3642
|
+
range: bareBroadcast
|
|
3643
|
+
});
|
|
3602
3644
|
}
|
|
3603
3645
|
lastIndex = match.index + match[0].length;
|
|
3604
3646
|
}
|
|
3605
3647
|
if (lastIndex < text.length) {
|
|
3606
|
-
|
|
3648
|
+
pushText(text.slice(lastIndex));
|
|
3607
3649
|
}
|
|
3608
3650
|
return elements.length > 0 ? elements : [{ type: "text", text }];
|
|
3609
3651
|
}
|
|
@@ -3726,6 +3768,23 @@ function collectList(input) {
|
|
|
3726
3768
|
return idx;
|
|
3727
3769
|
}
|
|
3728
3770
|
|
|
3771
|
+
// src/slack/format-outbound.ts
|
|
3772
|
+
function formatOutboundSlackText(text) {
|
|
3773
|
+
if (!text) {
|
|
3774
|
+
return "";
|
|
3775
|
+
}
|
|
3776
|
+
const stash = [];
|
|
3777
|
+
let out = text.replace(/<(?:@[UWB][A-Z0-9]+(?:\|[^>]*)?|#[CG][A-Z0-9]+(?:\|[^>]*)?|![a-zA-Z]+(?:\|[^>]*)?|https?:\/\/[^>]+)>/g, (m) => {
|
|
3778
|
+
stash.push(m);
|
|
3779
|
+
return `\x00${stash.length - 1}\x00`;
|
|
3780
|
+
});
|
|
3781
|
+
out = out.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3782
|
+
out = out.replace(/(^|[^A-Za-z0-9_])@([UWB][A-Z0-9]{6,})\b/g, (_m, pre, id) => `${pre}<@${id}>`);
|
|
3783
|
+
out = out.replace(/(^|[^A-Za-z0-9_])@(here|channel|everyone)\b/g, (_m, pre, name) => `${pre}<!${name}>`);
|
|
3784
|
+
out = out.replace(/\u0000(\d+)\u0000/g, (_m, idx) => stash[Number(idx)]);
|
|
3785
|
+
return out;
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3729
3788
|
// src/slack/upload.ts
|
|
3730
3789
|
import { readFile as readFile5, stat, realpath } from "node:fs/promises";
|
|
3731
3790
|
import { basename as basename2 } from "node:path";
|
|
@@ -4585,68 +4644,69 @@ function requireOldestWhenReactionFiltersUsed(input) {
|
|
|
4585
4644
|
}
|
|
4586
4645
|
async function sendMessage(input) {
|
|
4587
4646
|
const target = parseMsgTarget(String(input.targetInput));
|
|
4647
|
+
const formattedText = formatOutboundSlackText(input.text);
|
|
4588
4648
|
const blocks = input.text ? textToRichTextBlocks(input.text) : null;
|
|
4589
4649
|
const attachPaths = normalizeAttachPaths(input.options.attach);
|
|
4590
4650
|
if (target.kind === "url") {
|
|
4591
4651
|
const { ref } = target;
|
|
4592
4652
|
warnOnTruncatedSlackUrl(ref);
|
|
4593
|
-
await input.ctx.withAutoRefresh({
|
|
4653
|
+
return await input.ctx.withAutoRefresh({
|
|
4594
4654
|
workspaceUrl: ref.workspace_url,
|
|
4595
4655
|
work: async () => {
|
|
4596
|
-
const { client } = await input.ctx.getClientForWorkspace(ref.workspace_url);
|
|
4656
|
+
const { client, workspace_url } = await input.ctx.getClientForWorkspace(ref.workspace_url);
|
|
4597
4657
|
const msg = await fetchMessage(client, { ref });
|
|
4598
4658
|
const threadTs = msg.thread_ts ?? msg.ts;
|
|
4599
|
-
await sendMessageToChannel({
|
|
4659
|
+
return await sendMessageToChannel({
|
|
4600
4660
|
client,
|
|
4661
|
+
workspaceUrl: workspace_url ?? ref.workspace_url,
|
|
4601
4662
|
channelId: ref.channel_id,
|
|
4602
|
-
text:
|
|
4663
|
+
text: formattedText,
|
|
4603
4664
|
blocks,
|
|
4604
4665
|
threadTs,
|
|
4605
4666
|
attachPaths
|
|
4606
4667
|
});
|
|
4607
4668
|
}
|
|
4608
4669
|
});
|
|
4609
|
-
return { ok: true };
|
|
4610
4670
|
}
|
|
4611
4671
|
if (target.kind === "user") {
|
|
4612
4672
|
const workspaceUrl2 = input.ctx.effectiveWorkspaceUrl(input.options.workspace);
|
|
4613
|
-
await input.ctx.withAutoRefresh({
|
|
4673
|
+
return await input.ctx.withAutoRefresh({
|
|
4614
4674
|
workspaceUrl: workspaceUrl2,
|
|
4615
4675
|
work: async () => {
|
|
4616
|
-
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl2);
|
|
4676
|
+
const { client, workspace_url } = await input.ctx.getClientForWorkspace(workspaceUrl2);
|
|
4617
4677
|
const dmChannelId = await openDmChannel(client, target.userId);
|
|
4618
|
-
await sendMessageToChannel({
|
|
4678
|
+
return await sendMessageToChannel({
|
|
4619
4679
|
client,
|
|
4680
|
+
workspaceUrl: workspace_url ?? workspaceUrl2,
|
|
4620
4681
|
channelId: dmChannelId,
|
|
4621
|
-
text:
|
|
4682
|
+
text: formattedText,
|
|
4622
4683
|
blocks,
|
|
4623
4684
|
attachPaths
|
|
4624
4685
|
});
|
|
4625
4686
|
}
|
|
4626
4687
|
});
|
|
4627
|
-
return { ok: true };
|
|
4628
4688
|
}
|
|
4629
4689
|
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(input.options.workspace);
|
|
4630
4690
|
await input.ctx.assertWorkspaceSpecifiedForChannelNames({
|
|
4631
4691
|
workspaceUrl,
|
|
4632
4692
|
channels: [String(target.channel)]
|
|
4633
4693
|
});
|
|
4634
|
-
await input.ctx.withAutoRefresh({
|
|
4694
|
+
return await input.ctx.withAutoRefresh({
|
|
4635
4695
|
workspaceUrl,
|
|
4636
4696
|
work: async () => {
|
|
4637
|
-
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
4697
|
+
const { client, workspace_url } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
4638
4698
|
const channelId = await resolveChannelId(client, String(target.channel));
|
|
4639
|
-
await sendMessageToChannel({
|
|
4699
|
+
return await sendMessageToChannel({
|
|
4640
4700
|
client,
|
|
4701
|
+
workspaceUrl: workspace_url ?? workspaceUrl,
|
|
4641
4702
|
channelId,
|
|
4642
|
-
text:
|
|
4703
|
+
text: formattedText,
|
|
4643
4704
|
blocks,
|
|
4644
4705
|
threadTs: input.options.threadTs ? String(input.options.threadTs) : undefined,
|
|
4645
4706
|
attachPaths
|
|
4646
4707
|
});
|
|
4647
4708
|
}
|
|
4648
4709
|
});
|
|
4649
|
-
return { ok: true };
|
|
4650
4710
|
}
|
|
4651
4711
|
function normalizeAttachPaths(raw) {
|
|
4652
4712
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
@@ -4662,13 +4722,27 @@ function normalizeAttachPaths(raw) {
|
|
|
4662
4722
|
}
|
|
4663
4723
|
async function sendMessageToChannel(input) {
|
|
4664
4724
|
if (input.attachPaths.length === 0) {
|
|
4665
|
-
await input.client.api("chat.postMessage", {
|
|
4725
|
+
const resp = await input.client.api("chat.postMessage", {
|
|
4666
4726
|
channel: input.channelId,
|
|
4667
4727
|
text: input.text,
|
|
4668
4728
|
thread_ts: input.threadTs,
|
|
4669
4729
|
...input.blocks ? { blocks: input.blocks } : {}
|
|
4670
4730
|
});
|
|
4671
|
-
|
|
4731
|
+
const ts = typeof resp.ts === "string" ? resp.ts : undefined;
|
|
4732
|
+
const channelId = typeof resp.channel === "string" ? resp.channel : input.channelId;
|
|
4733
|
+
const permalink = input.workspaceUrl && ts ? buildSlackMessageUrl({
|
|
4734
|
+
workspace_url: input.workspaceUrl,
|
|
4735
|
+
channel_id: channelId,
|
|
4736
|
+
message_ts: ts,
|
|
4737
|
+
thread_ts: input.threadTs
|
|
4738
|
+
}) : undefined;
|
|
4739
|
+
return {
|
|
4740
|
+
ok: true,
|
|
4741
|
+
channel_id: channelId,
|
|
4742
|
+
ts,
|
|
4743
|
+
thread_ts: input.threadTs,
|
|
4744
|
+
permalink
|
|
4745
|
+
};
|
|
4672
4746
|
}
|
|
4673
4747
|
if (input.blocks) {
|
|
4674
4748
|
process.stderr.write(`Warning: rich text formatting is not supported with file attachments; sending as plain text.
|
|
@@ -4685,6 +4759,11 @@ async function sendMessageToChannel(input) {
|
|
|
4685
4759
|
});
|
|
4686
4760
|
initialComment = "";
|
|
4687
4761
|
}
|
|
4762
|
+
return {
|
|
4763
|
+
ok: true,
|
|
4764
|
+
channel_id: input.channelId,
|
|
4765
|
+
thread_ts: input.threadTs
|
|
4766
|
+
};
|
|
4688
4767
|
}
|
|
4689
4768
|
async function editMessage(input) {
|
|
4690
4769
|
const target = parseMsgTarget(String(input.targetInput));
|
|
@@ -4692,6 +4771,7 @@ async function editMessage(input) {
|
|
|
4692
4771
|
throw new Error("message edit does not support user ID targets. Use a channel name, channel ID, or message URL.");
|
|
4693
4772
|
}
|
|
4694
4773
|
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(input.options.workspace);
|
|
4774
|
+
const formattedText = formatOutboundSlackText(input.text);
|
|
4695
4775
|
await input.ctx.withAutoRefresh({
|
|
4696
4776
|
workspaceUrl: target.kind === "url" ? target.ref.workspace_url : workspaceUrl,
|
|
4697
4777
|
work: async () => {
|
|
@@ -4702,7 +4782,7 @@ async function editMessage(input) {
|
|
|
4702
4782
|
await client2.api("chat.update", {
|
|
4703
4783
|
channel: ref.channel_id,
|
|
4704
4784
|
ts: ref.message_ts,
|
|
4705
|
-
text:
|
|
4785
|
+
text: formattedText
|
|
4706
4786
|
});
|
|
4707
4787
|
return;
|
|
4708
4788
|
}
|
|
@@ -4716,7 +4796,7 @@ async function editMessage(input) {
|
|
|
4716
4796
|
await client.api("chat.update", {
|
|
4717
4797
|
channel: channelId,
|
|
4718
4798
|
ts,
|
|
4719
|
-
text:
|
|
4799
|
+
text: formattedText
|
|
4720
4800
|
});
|
|
4721
4801
|
}
|
|
4722
4802
|
});
|
|
@@ -9156,5 +9236,5 @@ if (subcommand && subcommand !== "update") {
|
|
|
9156
9236
|
backgroundUpdateCheck();
|
|
9157
9237
|
}
|
|
9158
9238
|
|
|
9159
|
-
//# debugId=
|
|
9239
|
+
//# debugId=D4BE12D81E739FC964756E2164756E21
|
|
9160
9240
|
//# sourceMappingURL=index.js.map
|