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 +42 -32
- package/dist/cli/index.js +42 -36
- package/package.json +2 -2
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
|
-
|
|
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
|
-
##
|
|
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
|
|
11
|
-
#
|
|
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.
|
|
14
|
-
# -
|
|
15
|
-
# -
|
|
16
|
-
# -
|
|
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
|
|
20
|
-
npx afk-code
|
|
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
|
|
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
|
|
54
|
+
npx afk-code claude
|
|
46
55
|
```
|
|
47
56
|
|
|
48
|
-
## Quick Start (
|
|
57
|
+
## Quick Start (Slack)
|
|
49
58
|
|
|
50
59
|
```bash
|
|
51
|
-
# 1. Create a
|
|
52
|
-
#
|
|
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.
|
|
56
|
-
# -
|
|
57
|
-
# -
|
|
58
|
-
# -
|
|
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
|
|
62
|
-
npx afk-code
|
|
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
|
|
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
|
|
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 --
|
|
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
|
|
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, {
|
|
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
|
-
|
|
1632
|
-
|
|
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(
|
|
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,
|
|
1672
|
+
await sendChunkedMessage(content, `_User (terminal):_`, { disable_notification: true });
|
|
1666
1673
|
} else {
|
|
1667
|
-
await sendChunkedMessage(content,
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2529
|
-
|
|
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
|
|
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
|
-
|
|
39
|
+
"dependencies": {
|
|
40
40
|
"@slack/bolt": "^4.6.0",
|
|
41
41
|
"discord.js": "^14.25.1",
|
|
42
42
|
"grammy": "^1.35.0",
|