slkcli 0.1.0 → 0.1.2

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 CHANGED
@@ -20,6 +20,20 @@ npx slkcli auth
20
20
 
21
21
  **Requirements:** macOS, Slack desktop app (installed and logged in), Node.js 18+.
22
22
 
23
+ ## Agent Skill
24
+
25
+ Add to your AI agent (Claude Code, Codex, Moltbot, etc.):
26
+
27
+ ```bash
28
+ # ClawdHub
29
+ clawdhub install slack-personal
30
+
31
+ # skills.sh
32
+ npx skills add therohitdas/slkcli
33
+ ```
34
+
35
+ Browse on [ClawdHub](https://www.clawhub.ai/therohitdas/slack-personal).
36
+
23
37
  ## Quickstart
24
38
 
25
39
  ```bash
@@ -45,6 +59,9 @@ slk unread
45
59
  # See starred items and VIP users
46
60
  slk starred
47
61
 
62
+ # See saved for later items
63
+ slk saved
64
+
48
65
  # See pinned messages in a channel
49
66
  slk pins general
50
67
 
@@ -70,8 +87,26 @@ slk react general 1234567890.123456 thumbsup
70
87
  | `slk activity` | `a` | Show all channel activity with unread/mention counts |
71
88
  | `slk unread` | `ur` | Show only channels with unreads (excludes muted) |
72
89
  | `slk starred` | `star` | Show VIP users and starred items |
90
+ | `slk saved [count]` | `sv` | Show saved for later items (active by default, `--all` includes completed) |
73
91
  | `slk pins <channel>` | `pin` | Show pinned items in a channel |
74
92
 
93
+ ### Flags
94
+
95
+ | Flag | Description |
96
+ |------|-------------|
97
+ | `--ts` | Show raw Slack timestamps (useful for getting ts to read threads) |
98
+ | `--no-emoji` | Disable emoji in output (or set `NO_EMOJI=1`) |
99
+ | `--all` | Include completed items in `slk saved` |
100
+
101
+ ```bash
102
+ # Get timestamps to use with thread command
103
+ slk read general 10 --ts
104
+ # Output: [1/30/2026, 11:41:19 AM ts:1769753479.788949] User [3 replies]:
105
+
106
+ # Then read that thread
107
+ slk thread general 1769753479.788949
108
+ ```
109
+
75
110
  ### Drafts
76
111
 
77
112
  Drafts sync to Slack — they appear in the Slack editor UI.
package/bin/slk.js CHANGED
@@ -11,6 +11,7 @@ const args = process.argv.slice(2);
11
11
  const command = args[0];
12
12
 
13
13
  const supportsEmoji = !process.env.NO_EMOJI && !process.argv.includes("--no-emoji");
14
+ const showTs = process.argv.includes("--ts");
14
15
  const e = (emoji, fallback = "") => supportsEmoji ? emoji + " " : fallback;
15
16
 
16
17
  const HELP = `${e("💬")}slk — Slack CLI for macOS (auto-auth from Slack desktop app)
@@ -27,6 +28,7 @@ Commands:
27
28
  slk activity (a) Channel activity with unread/mention counts
28
29
  slk unread (ur) Channels with unreads (excludes muted)
29
30
  slk starred (star) VIP users + starred items
31
+ slk saved [n] (sv) Saved for later items (--all includes completed)
30
32
  slk pins <ch> (pin) Pinned items in a channel
31
33
 
32
34
  Drafts (synced to Slack UI):
@@ -37,6 +39,7 @@ Drafts (synced to Slack UI):
37
39
  slk draft drop <id> Delete a draft
38
40
 
39
41
  Settings:
42
+ --ts Show raw Slack timestamps (for thread commands)
40
43
  --no-emoji Disable emoji output (or set NO_EMOJI=1)
41
44
 
42
45
  Channels: name ("general") or ID ("C08A8AQ2AFP"). Aliases shown in parens.
@@ -68,8 +71,8 @@ async function main() {
68
71
 
69
72
  case "read":
70
73
  case "r":
71
- if (!args[1]) { console.error("Usage: slk read <channel> [count]"); process.exit(1); }
72
- await cmd.read(args[1], parseInt(args[2]) || 20);
74
+ if (!args[1]) { console.error("Usage: slk read <channel> [count] [--ts]"); process.exit(1); }
75
+ await cmd.read(args[1], parseInt(args[2]) || 20, showTs);
73
76
  break;
74
77
 
75
78
  case "send":
@@ -117,6 +120,11 @@ async function main() {
117
120
  await cmd.starred();
118
121
  break;
119
122
 
123
+ case "saved":
124
+ case "sv":
125
+ await cmd.saved(parseInt(args[1]) || 20, args.includes("--all"));
126
+ break;
127
+
120
128
  case "pins":
121
129
  case "pin":
122
130
  if (!args[1]) { console.error("Usage: slk pins <channel>"); process.exit(1); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slkcli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Slack CLI for macOS, so your agents can read and send messages",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/api.js CHANGED
@@ -29,6 +29,7 @@ export async function slackApi(method, params = {}, retried = false) {
29
29
  "conversations.open",
30
30
  "client.counts",
31
31
  "users.prefs.get",
32
+ "saved.list",
32
33
  ];
33
34
 
34
35
  const isWrite = writeMethods.some((m) => method.startsWith(m));
package/src/commands.js CHANGED
@@ -75,7 +75,7 @@ export async function channels() {
75
75
  }
76
76
  }
77
77
 
78
- export async function read(channelRef, count = 20) {
78
+ export async function read(channelRef, count = 20, showTs = false) {
79
79
  const channel = await resolveChannel(channelRef);
80
80
  const users = await getUsers();
81
81
  const data = await slackApi("conversations.history", {
@@ -91,8 +91,9 @@ export async function read(channelRef, count = 20) {
91
91
  for (const msg of messages) {
92
92
  const who = userName(users, msg.user);
93
93
  const time = formatTs(msg.ts);
94
+ const tsStr = showTs ? ` ts:${msg.ts}` : "";
94
95
  const thread = msg.reply_count ? ` [${msg.reply_count} replies]` : "";
95
- console.log(`[${time}] ${who}${thread}:`);
96
+ console.log(`[${time}${tsStr}] ${who}${thread}:`);
96
97
  console.log(` ${msg.text}`);
97
98
  if (msg.files?.length) {
98
99
  for (const f of msg.files) {
@@ -332,6 +333,72 @@ export async function pins(channelRef) {
332
333
  }
333
334
  }
334
335
 
336
+ export async function saved(count = 20, includeCompleted = false) {
337
+ const users = await getUsers();
338
+
339
+ // Build channel name map
340
+ const chData = await slackPaginate("conversations.list", {
341
+ types: "public_channel,private_channel,mpim,im",
342
+ exclude_archived: true,
343
+ });
344
+ const chMap = {};
345
+ if (chData.ok) {
346
+ for (const ch of chData.channels) {
347
+ chMap[ch.id] = ch.name || (ch.user ? `DM:${userName(users, ch.user)}` : ch.id);
348
+ }
349
+ }
350
+
351
+ const data = await slackApi("saved.list", { count });
352
+ if (!data.ok) {
353
+ console.error(`Error: ${data.error}`);
354
+ process.exit(1);
355
+ }
356
+
357
+ const items = data.saved_items || [];
358
+ const counts = data.counts || {};
359
+ console.log(`📑 Saved for Later — ${counts.uncompleted_count || 0} active, ${counts.completed_count || 0} completed\n`);
360
+
361
+ if (!items.length) {
362
+ console.log("No saved items.");
363
+ return;
364
+ }
365
+
366
+ for (const item of items) {
367
+ if (!includeCompleted && item.state === "completed") continue;
368
+
369
+ const chName = chMap[item.item_id] || item.item_id;
370
+ const savedAt = formatTs(item.date_created);
371
+ const state = item.state === "completed" ? " ✅" : "";
372
+
373
+ // Fetch the actual message
374
+ try {
375
+ const msgData = await slackApi("conversations.history", {
376
+ channel: item.item_id,
377
+ latest: item.ts,
378
+ inclusive: true,
379
+ limit: 1,
380
+ });
381
+ if (msgData.ok && msgData.messages?.[0]) {
382
+ const msg = msgData.messages[0];
383
+ const who = userName(users, msg.user);
384
+ const msgTime = formatTs(msg.ts);
385
+ console.log(`[saved ${savedAt}]${state} #${chName} — ${who} (${msgTime}):`);
386
+ console.log(` ${(msg.text || "").substring(0, 300)}`);
387
+ if (msg.files?.length) {
388
+ for (const f of msg.files) {
389
+ console.log(` 📎 ${f.name} (${f.mimetype})`);
390
+ }
391
+ }
392
+ } else {
393
+ console.log(`[saved ${savedAt}]${state} #${chName} (ts: ${item.ts}) — could not fetch message`);
394
+ }
395
+ } catch {
396
+ console.log(`[saved ${savedAt}]${state} #${chName} (ts: ${item.ts}) — access denied or channel not found`);
397
+ }
398
+ console.log();
399
+ }
400
+ }
401
+
335
402
  export async function react(channelRef, ts, emoji) {
336
403
  const channel = await resolveChannel(channelRef);
337
404
  const data = await slackApi("reactions.add", {