afk-code 0.1.4 → 0.2.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 CHANGED
@@ -2,29 +2,38 @@
2
2
 
3
3
  Monitor and interact with Claude Code sessions from Slack, Discord, or Telegram. Respond from your phone while AFK.
4
4
 
5
- ![square-image](https://github.com/user-attachments/assets/83083b63-9ca2-4ef0-b83d-fcc51bd2fff9)
5
+ <img src="https://github.com/user-attachments/assets/83083b63-9ca2-4ef0-b83d-fcc51bd2fff9" alt="AFK Code iPhone Slack screenshot" width="400">
6
6
 
7
- ## Quick Start (Slack)
7
+ ## Client Comparison
8
+
9
+ Telegram and Discord are recommended.
10
+
11
+ | | Telegram | Discord | Slack |
12
+ |---|---|---|---|
13
+ | Siri integration | Receive & Send | Receive only | Receive only |
14
+ | Multi-session support | One at a time (switchable) | Yes | Yes |
15
+ | Permissions required | Personal | Personal | Admin |
16
+
17
+ ## Quick Start (Telegram)
8
18
 
9
19
  ```bash
10
- # 1. Create a Slack app at https://api.slack.com/apps
11
- # Click "Create New App" "From manifest" → paste slack-manifest.json
20
+ # 1. Create a bot with @BotFather on Telegram
21
+ # - Send /newbot and follow the prompts
22
+ # - Copy the bot token
12
23
 
13
- # 2. Install to your workspace and get credentials:
14
- # - Bot Token (xoxb-...) from OAuth & Permissions
15
- # - App Token (xapp-...) from Basic Information → App-Level Tokens (needs connections:write)
16
- # - Your User ID from your Slack profile → "..." → Copy member ID
24
+ # 2. Get your Chat ID
25
+ # - Message your bot, then visit:
26
+ # - https://api.telegram.org/bot<TOKEN>/getUpdates
27
+ # - Find "chat":{"id":YOUR_CHAT_ID}
17
28
 
18
29
  # 3. Configure and run
19
- npx afk-code slack setup # Enter your credentials
20
- npx afk-code slack # Start the bot
30
+ npx afk-code telegram setup # Enter your credentials
31
+ npx afk-code telegram # Start the bot
21
32
 
22
33
  # 4. In another terminal, start a monitored Claude session
23
- npx afk-code run -- claude
34
+ npx afk-code claude
24
35
  ```
25
36
 
26
- A new channel is created for each session. Messages relay bidirectionally.
27
-
28
37
  ## Quick Start (Discord)
29
38
 
30
39
  ```bash
@@ -42,39 +51,40 @@ npx afk-code discord setup # Enter your credentials
42
51
  npx afk-code discord # Start the bot
43
52
 
44
53
  # 4. In another terminal, start a monitored Claude session
45
- npx afk-code run -- claude
54
+ npx afk-code claude
46
55
  ```
47
56
 
48
- ## Quick Start (Telegram)
57
+ ## Quick Start (Slack)
49
58
 
50
59
  ```bash
51
- # 1. Create a bot with @BotFather on Telegram
52
- # - Send /newbot and follow the prompts
53
- # - Copy the bot token
60
+ # 1. Create a Slack app at https://api.slack.com/apps
61
+ # Click "Create New App" "From manifest" → paste slack-manifest.json
54
62
 
55
- # 2. Get your Chat ID
56
- # - Message your bot, then visit:
57
- # - https://api.telegram.org/bot<TOKEN>/getUpdates
58
- # - Find "chat":{"id":YOUR_CHAT_ID}
63
+ # 2. Install to your workspace and get credentials:
64
+ # - Bot Token (xoxb-...) from OAuth & Permissions
65
+ # - App Token (xapp-...) from Basic Information → App-Level Tokens (needs connections:write)
66
+ # - Your User ID from your Slack profile → "..." → Copy member ID
59
67
 
60
68
  # 3. Configure and run
61
- npx afk-code telegram setup # Enter your credentials
62
- npx afk-code telegram # Start the bot
69
+ npx afk-code slack setup # Enter your credentials
70
+ npx afk-code slack # Start the bot
63
71
 
64
72
  # 4. In another terminal, start a monitored Claude session
65
- npx afk-code run -- claude
73
+ npx afk-code claude
66
74
  ```
67
75
 
76
+ A new channel is created for each session. Messages relay bidirectionally.
77
+
68
78
  ## Commands
69
79
 
70
80
  ```
71
- afk-code slack setup Configure Slack credentials
72
- afk-code slack Run the Slack bot
73
- afk-code discord setup Configure Discord credentials
74
- afk-code discord Run the Discord bot
75
81
  afk-code telegram setup Configure Telegram credentials
76
82
  afk-code telegram Run the Telegram bot
77
- afk-code run -- <command> Start a monitored session
83
+ afk-code discord setup Configure Discord credentials
84
+ afk-code discord Run the Discord bot
85
+ afk-code slack setup Configure Slack credentials
86
+ afk-code slack Run the Slack bot
87
+ afk-code <command> [args] Start a monitored session
78
88
  afk-code help Show help
79
89
  ```
80
90
 
@@ -103,7 +113,7 @@ npx afk-code <command>
103
113
  git clone https://github.com/clharman/afk-code.git
104
114
  cd afk-code && npm install
105
115
  npm run dev -- slack
106
- npm run dev -- run -- claude
116
+ npm run dev -- claude
107
117
  ```
108
118
 
109
119
  Requires Node.js 18+.
@@ -111,7 +121,7 @@ Requires Node.js 18+.
111
121
  ## How It Works
112
122
 
113
123
  1. `afk-code slack`, `afk-code discord`, or `afk-code telegram` starts a bot that listens for sessions
114
- 2. `afk-code run -- claude` spawns Claude in a PTY and connects to the bot via Unix socket
124
+ 2. `afk-code claude` spawns Claude in a PTY and connects to the bot via Unix socket
115
125
  3. The bot watches Claude's JSONL files for messages and relays them to chat
116
126
  4. Messages you send in chat are forwarded to the terminal
117
127
 
package/dist/cli/index.js CHANGED
@@ -11,7 +11,7 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/slack/session-manager.ts
13
13
  import { watch } from "fs";
14
- import { readdir, readFile, stat, unlink } from "fs/promises";
14
+ import { readdir, readFile, stat, unlink, mkdir } from "fs/promises";
15
15
  import { createServer } from "net";
16
16
  import { createHash } from "crypto";
17
17
  function hash(data) {
@@ -289,6 +289,7 @@ var init_session_manager = __esm({
289
289
  console.log(`[SessionManager] Waiting for JSONL changes in ${session.projectDir}`);
290
290
  }
291
291
  try {
292
+ await mkdir(session.projectDir, { recursive: true });
292
293
  session.watcher = watch(session.projectDir, { recursive: false }, async (_, filename) => {
293
294
  if (!filename?.endsWith(".jsonl")) return;
294
295
  if (!session.watchedFile) {
@@ -1597,13 +1598,18 @@ function createTelegramApp(config) {
1597
1598
  }
1598
1599
  processingQueue = false;
1599
1600
  }
1600
- async function sendMessage(text, parseMode = "Markdown") {
1601
+ async function sendMessage(text, parseMode = "Markdown", options) {
1601
1602
  messageQueue.push(async () => {
1602
1603
  try {
1603
- await bot.api.sendMessage(config.chatId, text, { parse_mode: parseMode });
1604
+ await bot.api.sendMessage(config.chatId, text, {
1605
+ parse_mode: parseMode,
1606
+ disable_notification: options?.disable_notification
1607
+ });
1604
1608
  } catch (err) {
1605
1609
  if (parseMode && err.message?.includes("parse")) {
1606
- await bot.api.sendMessage(config.chatId, text);
1610
+ await bot.api.sendMessage(config.chatId, text, {
1611
+ disable_notification: options?.disable_notification
1612
+ });
1607
1613
  } else {
1608
1614
  throw err;
1609
1615
  }
@@ -1611,13 +1617,11 @@ function createTelegramApp(config) {
1611
1617
  });
1612
1618
  processQueue();
1613
1619
  }
1614
- async function sendChunkedMessage(text, prefix) {
1620
+ async function sendChunkedMessage(text, prefix, options) {
1615
1621
  const chunks = chunkMessage(text, MAX_MESSAGE_LENGTH);
1616
1622
  for (let i = 0; i < chunks.length; i++) {
1617
- const chunk = prefix && i === 0 ? `${prefix}
1618
-
1619
- ${chunks[i]}` : chunks[i];
1620
- await sendMessage(chunk);
1623
+ const chunk = prefix && i === 0 ? `${prefix} ${chunks[i]}` : chunks[i];
1624
+ await sendMessage(chunk, "Markdown", options);
1621
1625
  }
1622
1626
  }
1623
1627
  const sessionManager = new SessionManager({
@@ -1627,17 +1631,20 @@ ${chunks[i]}` : chunks[i];
1627
1631
  sessionName: session.name,
1628
1632
  lastActivity: /* @__PURE__ */ new Date()
1629
1633
  });
1634
+ const parts = session.name.split(" ");
1635
+ const cmd = parts[0];
1636
+ const args2 = parts.slice(1).map((a) => a.replace(/^-+/, ""));
1637
+ const sessionLabel = args2.length > 0 ? `${cmd} (${args2.join(", ")})` : cmd;
1630
1638
  await sendMessage(
1631
- `*[${session.name}]* ${formatSessionStatus(session.status)}
1632
- Session started
1633
- \`${session.cwd}\``
1639
+ `Session started: ${sessionLabel}
1640
+ Directory: \`${session.cwd}\``
1634
1641
  );
1635
1642
  },
1636
1643
  onSessionEnd: async (sessionId) => {
1637
1644
  const tracking = activeSessions.get(sessionId);
1638
1645
  const name = tracking?.sessionName || sessionId;
1639
1646
  activeSessions.delete(sessionId);
1640
- await sendMessage(`*[${name}]* Session ended`);
1647
+ await sendMessage(`Session ended: ${name}`);
1641
1648
  },
1642
1649
  onSessionUpdate: async (sessionId, name) => {
1643
1650
  const tracking = activeSessions.get(sessionId);
@@ -1662,16 +1669,16 @@ Session started
1662
1669
  telegramSentMessages.delete(contentKey);
1663
1670
  return;
1664
1671
  }
1665
- await sendChunkedMessage(content, `*[${tracking.sessionName}]* *User:*`);
1672
+ await sendChunkedMessage(content, `_User (terminal):_`, { disable_notification: true });
1666
1673
  } else {
1667
- await sendChunkedMessage(content, `*[${tracking.sessionName}]* *Claude:*`);
1674
+ await sendChunkedMessage(content, `_Claude Code:_`);
1668
1675
  }
1669
1676
  },
1670
1677
  onTodos: async (sessionId, todos) => {
1671
1678
  const tracking = activeSessions.get(sessionId);
1672
1679
  if (!tracking || todos.length === 0) return;
1673
1680
  const todosText = formatTodos(todos);
1674
- await sendMessage(`*[${tracking.sessionName}]* *Tasks:*
1681
+ await sendMessage(`_Claude Code:_ *Tasks:*
1675
1682
  ${todosText}`);
1676
1683
  },
1677
1684
  onToolCall: async (_sessionId, _tool) => {
@@ -1682,7 +1689,7 @@ ${todosText}`);
1682
1689
  const tracking = activeSessions.get(sessionId);
1683
1690
  if (!tracking) return;
1684
1691
  const status = inPlanMode ? "Planning mode - Claude is designing a solution" : "Execution mode - Claude is implementing";
1685
- await sendMessage(`*[${tracking.sessionName}]* ${status}`);
1692
+ await sendMessage(`_Claude Code:_ ${status}`);
1686
1693
  }
1687
1694
  });
1688
1695
  function getCurrentSession() {
@@ -2007,7 +2014,7 @@ async function run(command2) {
2007
2014
 
2008
2015
  // src/cli/slack.ts
2009
2016
  import { homedir as homedir3 } from "os";
2010
- import { mkdir, writeFile, readFile as readFile2, access } from "fs/promises";
2017
+ import { mkdir as mkdir2, writeFile, readFile as readFile2, access } from "fs/promises";
2011
2018
  import * as readline from "readline";
2012
2019
  var CONFIG_DIR = `${homedir3()}/.afk-code`;
2013
2020
  var SLACK_CONFIG_FILE = `${CONFIG_DIR}/slack.env`;
@@ -2083,7 +2090,7 @@ Now let's collect your tokens:
2083
2090
  console.error("Invalid user ID. Should start with U");
2084
2091
  process.exit(1);
2085
2092
  }
2086
- await mkdir(CONFIG_DIR, { recursive: true });
2093
+ await mkdir2(CONFIG_DIR, { recursive: true });
2087
2094
  const envContent = `# AFK Code Slack Configuration
2088
2095
  SLACK_BOT_TOKEN=${botToken}
2089
2096
  SLACK_APP_TOKEN=${appToken}
@@ -2169,7 +2176,7 @@ async function slackRun() {
2169
2176
 
2170
2177
  // src/cli/discord.ts
2171
2178
  import { homedir as homedir4 } from "os";
2172
- import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, access as access2 } from "fs/promises";
2179
+ import { mkdir as mkdir3, writeFile as writeFile2, readFile as readFile3, access as access2 } from "fs/promises";
2173
2180
  import * as readline2 from "readline";
2174
2181
  var CONFIG_DIR2 = `${homedir4()}/.afk-code`;
2175
2182
  var DISCORD_CONFIG_FILE = `${CONFIG_DIR2}/discord.env`;
@@ -2245,7 +2252,7 @@ Now let's collect your credentials:
2245
2252
  console.error("Invalid user ID. Should be a number.");
2246
2253
  process.exit(1);
2247
2254
  }
2248
- await mkdir2(CONFIG_DIR2, { recursive: true });
2255
+ await mkdir3(CONFIG_DIR2, { recursive: true });
2249
2256
  const envContent = `# AFK Code Discord Configuration
2250
2257
  DISCORD_BOT_TOKEN=${botToken}
2251
2258
  DISCORD_USER_ID=${userId}
@@ -2324,7 +2331,7 @@ async function discordRun() {
2324
2331
 
2325
2332
  // src/cli/telegram.ts
2326
2333
  import { homedir as homedir5 } from "os";
2327
- import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile4, access as access3 } from "fs/promises";
2334
+ import { mkdir as mkdir4, writeFile as writeFile3, readFile as readFile4, access as access3 } from "fs/promises";
2328
2335
  import * as readline3 from "readline";
2329
2336
  var CONFIG_DIR3 = `${homedir5()}/.afk-code`;
2330
2337
  var TELEGRAM_CONFIG_FILE = `${CONFIG_DIR3}/telegram.env`;
@@ -2384,7 +2391,7 @@ Step 2: Get Your Chat ID
2384
2391
  console.error("Invalid chat ID. It should be a number (can be negative for groups).");
2385
2392
  process.exit(1);
2386
2393
  }
2387
- await mkdir3(CONFIG_DIR3, { recursive: true });
2394
+ await mkdir4(CONFIG_DIR3, { recursive: true });
2388
2395
  const envContent = `# AFK Code Telegram Configuration
2389
2396
  TELEGRAM_BOT_TOKEN=${botToken}
2390
2397
  TELEGRAM_CHAT_ID=${chatId}
@@ -2504,30 +2511,29 @@ async function main() {
2504
2511
  AFK Code - Monitor Claude Code sessions from Slack/Discord/Telegram
2505
2512
 
2506
2513
  Commands:
2507
- slack Run the Slack bot
2508
- slack setup Configure Slack integration
2509
- discord Run the Discord bot
2510
- discord setup Configure Discord integration
2511
2514
  telegram Run the Telegram bot
2512
2515
  telegram setup Configure Telegram integration
2513
- run -- <command> Start a monitored session
2516
+ discord Run the Discord bot
2517
+ discord setup Configure Discord integration
2518
+ slack Run the Slack bot
2519
+ slack setup Configure Slack integration
2520
+ <command> [args] Start a monitored session
2514
2521
  help Show this help message
2515
2522
 
2516
2523
  Examples:
2517
- afk-code slack setup # First-time Slack configuration
2518
- afk-code slack # Start the Slack bot
2519
- afk-code discord setup # First-time Discord configuration
2520
- afk-code discord # Start the Discord bot
2521
2524
  afk-code telegram setup # First-time Telegram configuration
2522
2525
  afk-code telegram # Start the Telegram bot
2523
- afk-code run -- claude # Start a Claude Code session
2526
+ afk-code discord setup # First-time Discord configuration
2527
+ afk-code discord # Start the Discord bot
2528
+ afk-code slack setup # First-time Slack configuration
2529
+ afk-code slack # Start the Slack bot
2530
+ afk-code claude # Start a Claude Code session
2524
2531
  `);
2525
2532
  break;
2526
2533
  }
2527
2534
  default: {
2528
- console.error(`Unknown command: ${command}`);
2529
- console.error('Run "afk-code help" for usage');
2530
- process.exit(1);
2535
+ await run(args);
2536
+ break;
2531
2537
  }
2532
2538
  }
2533
2539
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afk-code",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Monitor and interact with Claude Code sessions from Slack/Discord/Telegram",
5
5
  "author": "Colin Harman",
6
6
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "cli"
37
37
  ],
38
38
  "license": "MIT",
39
- "dependencies": {
39
+ "dependencies": {
40
40
  "@slack/bolt": "^4.6.0",
41
41
  "discord.js": "^14.25.1",
42
42
  "grammy": "^1.35.0",