afk-code 0.2.0 → 0.3.0
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 +8 -2
- package/dist/cli/index.js +53 -10
- package/package.json +1 -1
- package/slack-manifest.json +1 -0
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Telegram and Discord are recommended.
|
|
|
13
13
|
| Siri integration | Receive & Send | Receive only | Receive only |
|
|
14
14
|
| Multi-session support | One at a time (switchable) | Yes | Yes |
|
|
15
15
|
| Permissions required | Personal | Personal | Admin |
|
|
16
|
+
| Image support | Yes | Yes | Yes |
|
|
16
17
|
|
|
17
18
|
## Quick Start (Telegram)
|
|
18
19
|
|
|
@@ -41,7 +42,7 @@ npx afk-code claude
|
|
|
41
42
|
# - Go to Bot → Reset Token → copy it
|
|
42
43
|
# - Enable "Message Content Intent"
|
|
43
44
|
# - Go to OAuth2 → URL Generator → select "bot" scope
|
|
44
|
-
# - Select permissions: Send Messages, Manage Channels, Read Message History
|
|
45
|
+
# - Select permissions: Send Messages, Manage Channels, Read Message History, Attach Files
|
|
45
46
|
# - Open the generated URL to invite the bot
|
|
46
47
|
|
|
47
48
|
# 2. Get your User ID (enable Developer Mode, right-click your name → Copy User ID)
|
|
@@ -75,6 +76,10 @@ npx afk-code claude
|
|
|
75
76
|
|
|
76
77
|
A new channel is created for each session. Messages relay bidirectionally.
|
|
77
78
|
|
|
79
|
+
## Image Support
|
|
80
|
+
|
|
81
|
+
When Claude references image paths in responses (e.g., `/path/to/screenshot.png`), the bot automatically detects and uploads them to the chat. Supports PNG, JPG, GIF, WebP, and other common formats.
|
|
82
|
+
|
|
78
83
|
## Commands
|
|
79
84
|
|
|
80
85
|
```
|
|
@@ -128,7 +133,8 @@ Requires Node.js 18+.
|
|
|
128
133
|
## Limitations
|
|
129
134
|
|
|
130
135
|
- Does not support plan mode or responding to Claude Code's form-based questions (AskUserQuestion)
|
|
131
|
-
-
|
|
136
|
+
- You can bypass this using the `/mode` command or by sending any message
|
|
137
|
+
- Does not send tool calls or results (would encounter rate limits)
|
|
132
138
|
|
|
133
139
|
## Disclaimer
|
|
134
140
|
|
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) {
|
|
@@ -1571,7 +1572,7 @@ var telegram_app_exports = {};
|
|
|
1571
1572
|
__export(telegram_app_exports, {
|
|
1572
1573
|
createTelegramApp: () => createTelegramApp
|
|
1573
1574
|
});
|
|
1574
|
-
import { Bot } from "grammy";
|
|
1575
|
+
import { Bot, InputFile } from "grammy";
|
|
1575
1576
|
function createTelegramApp(config) {
|
|
1576
1577
|
const bot = new Bot(config.botToken);
|
|
1577
1578
|
const activeSessions = /* @__PURE__ */ new Map();
|
|
@@ -1625,9 +1626,11 @@ function createTelegramApp(config) {
|
|
|
1625
1626
|
}
|
|
1626
1627
|
const sessionManager = new SessionManager({
|
|
1627
1628
|
onSessionStart: async (session) => {
|
|
1629
|
+
const projectName = session.cwd.split("/").filter(Boolean).pop() || "unknown";
|
|
1628
1630
|
activeSessions.set(session.id, {
|
|
1629
1631
|
sessionId: session.id,
|
|
1630
1632
|
sessionName: session.name,
|
|
1633
|
+
projectName,
|
|
1631
1634
|
lastActivity: /* @__PURE__ */ new Date()
|
|
1632
1635
|
});
|
|
1633
1636
|
const parts = session.name.split(" ");
|
|
@@ -1671,6 +1674,28 @@ Directory: \`${session.cwd}\``
|
|
|
1671
1674
|
await sendChunkedMessage(content, `_User (terminal):_`, { disable_notification: true });
|
|
1672
1675
|
} else {
|
|
1673
1676
|
await sendChunkedMessage(content, `_Claude Code:_`);
|
|
1677
|
+
const session = sessionManager.getSession(sessionId);
|
|
1678
|
+
const images = extractImagePaths(content, session?.cwd);
|
|
1679
|
+
for (const image of images) {
|
|
1680
|
+
try {
|
|
1681
|
+
console.log(`[Telegram] Uploading image: ${image.resolvedPath}`);
|
|
1682
|
+
const isGif = image.resolvedPath.toLowerCase().endsWith(".gif");
|
|
1683
|
+
messageQueue.push(async () => {
|
|
1684
|
+
if (isGif) {
|
|
1685
|
+
await bot.api.sendAnimation(config.chatId, new InputFile(image.resolvedPath), {
|
|
1686
|
+
caption: `\u{1F4CE} ${image.originalPath}`
|
|
1687
|
+
});
|
|
1688
|
+
} else {
|
|
1689
|
+
await bot.api.sendPhoto(config.chatId, new InputFile(image.resolvedPath), {
|
|
1690
|
+
caption: `\u{1F4CE} ${image.originalPath}`
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
processQueue();
|
|
1695
|
+
} catch (err) {
|
|
1696
|
+
console.error("[Telegram] Failed to upload image:", err);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1674
1699
|
}
|
|
1675
1700
|
},
|
|
1676
1701
|
onTodos: async (sessionId, todos) => {
|
|
@@ -1789,7 +1814,8 @@ Type /help for available commands.`,
|
|
|
1789
1814
|
const current = getCurrentSession();
|
|
1790
1815
|
const list = Array.from(activeSessions.values()).map((s) => {
|
|
1791
1816
|
const isCurrent = current && s.sessionId === current.sessionId;
|
|
1792
|
-
|
|
1817
|
+
const displayName = `${s.projectName}/${s.sessionName}`;
|
|
1818
|
+
return isCurrent ? `\u2022 *${displayName}* \u2190 current` : `\u2022 ${displayName}`;
|
|
1793
1819
|
}).join("\n");
|
|
1794
1820
|
await ctx.reply(`*Active Sessions:*
|
|
1795
1821
|
${list}
|
|
@@ -1807,7 +1833,8 @@ Use \`/switch <name>\` to change`, { parse_mode: "Markdown" });
|
|
|
1807
1833
|
const current = getCurrentSession();
|
|
1808
1834
|
const list = Array.from(activeSessions.values()).map((s) => {
|
|
1809
1835
|
const isCurrent = current && s.sessionId === current.sessionId;
|
|
1810
|
-
|
|
1836
|
+
const displayName = `${s.projectName}/${s.sessionName}`;
|
|
1837
|
+
return isCurrent ? `\u2022 *${displayName}* \u2190 current` : `\u2022 ${displayName}`;
|
|
1811
1838
|
}).join("\n");
|
|
1812
1839
|
await ctx.reply(`*Sessions:*
|
|
1813
1840
|
${list}
|
|
@@ -1907,6 +1934,7 @@ var init_telegram_app = __esm({
|
|
|
1907
1934
|
"use strict";
|
|
1908
1935
|
init_session_manager();
|
|
1909
1936
|
init_message_formatter();
|
|
1937
|
+
init_image_extractor();
|
|
1910
1938
|
MAX_MESSAGE_LENGTH = 4e3;
|
|
1911
1939
|
}
|
|
1912
1940
|
});
|
|
@@ -1965,6 +1993,19 @@ async function run(command2) {
|
|
|
1965
1993
|
const sessionId = randomUUID().slice(0, 8);
|
|
1966
1994
|
const cwd = process.cwd();
|
|
1967
1995
|
const projectDir = getClaudeProjectDir(cwd);
|
|
1996
|
+
const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1997
|
+
let spinnerIndex = 0;
|
|
1998
|
+
let spinnerInterval = setInterval(() => {
|
|
1999
|
+
process.stdout.write(`\r${spinnerFrames[spinnerIndex]} Starting...`);
|
|
2000
|
+
spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
|
|
2001
|
+
}, 80);
|
|
2002
|
+
const stopSpinner = () => {
|
|
2003
|
+
if (spinnerInterval) {
|
|
2004
|
+
clearInterval(spinnerInterval);
|
|
2005
|
+
spinnerInterval = null;
|
|
2006
|
+
process.stdout.write("\r\x1B[K");
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
1968
2009
|
const cols = process.stdout.columns || 80;
|
|
1969
2010
|
const rows = process.stdout.rows || 24;
|
|
1970
2011
|
const ptyProcess = pty.spawn(command2[0], command2.slice(1), {
|
|
@@ -1987,6 +2028,7 @@ async function run(command2) {
|
|
|
1987
2028
|
process.stdin.setRawMode(true);
|
|
1988
2029
|
}
|
|
1989
2030
|
ptyProcess.onData((data) => {
|
|
2031
|
+
stopSpinner();
|
|
1990
2032
|
process.stdout.write(data);
|
|
1991
2033
|
});
|
|
1992
2034
|
const onStdinData = (data) => {
|
|
@@ -2013,7 +2055,7 @@ async function run(command2) {
|
|
|
2013
2055
|
|
|
2014
2056
|
// src/cli/slack.ts
|
|
2015
2057
|
import { homedir as homedir3 } from "os";
|
|
2016
|
-
import { mkdir, writeFile, readFile as readFile2, access } from "fs/promises";
|
|
2058
|
+
import { mkdir as mkdir2, writeFile, readFile as readFile2, access } from "fs/promises";
|
|
2017
2059
|
import * as readline from "readline";
|
|
2018
2060
|
var CONFIG_DIR = `${homedir3()}/.afk-code`;
|
|
2019
2061
|
var SLACK_CONFIG_FILE = `${CONFIG_DIR}/slack.env`;
|
|
@@ -2089,7 +2131,7 @@ Now let's collect your tokens:
|
|
|
2089
2131
|
console.error("Invalid user ID. Should start with U");
|
|
2090
2132
|
process.exit(1);
|
|
2091
2133
|
}
|
|
2092
|
-
await
|
|
2134
|
+
await mkdir2(CONFIG_DIR, { recursive: true });
|
|
2093
2135
|
const envContent = `# AFK Code Slack Configuration
|
|
2094
2136
|
SLACK_BOT_TOKEN=${botToken}
|
|
2095
2137
|
SLACK_APP_TOKEN=${appToken}
|
|
@@ -2175,7 +2217,7 @@ async function slackRun() {
|
|
|
2175
2217
|
|
|
2176
2218
|
// src/cli/discord.ts
|
|
2177
2219
|
import { homedir as homedir4 } from "os";
|
|
2178
|
-
import { mkdir as
|
|
2220
|
+
import { mkdir as mkdir3, writeFile as writeFile2, readFile as readFile3, access as access2 } from "fs/promises";
|
|
2179
2221
|
import * as readline2 from "readline";
|
|
2180
2222
|
var CONFIG_DIR2 = `${homedir4()}/.afk-code`;
|
|
2181
2223
|
var DISCORD_CONFIG_FILE = `${CONFIG_DIR2}/discord.env`;
|
|
@@ -2231,6 +2273,7 @@ Step 3: Invite the Bot
|
|
|
2231
2273
|
\u2022 Send Messages
|
|
2232
2274
|
\u2022 Manage Channels
|
|
2233
2275
|
\u2022 Read Message History
|
|
2276
|
+
\u2022 Attach Files
|
|
2234
2277
|
4. Copy the URL and open it to invite the bot to your server
|
|
2235
2278
|
`);
|
|
2236
2279
|
await prompt2("Press Enter when you have created and invited the bot...");
|
|
@@ -2251,7 +2294,7 @@ Now let's collect your credentials:
|
|
|
2251
2294
|
console.error("Invalid user ID. Should be a number.");
|
|
2252
2295
|
process.exit(1);
|
|
2253
2296
|
}
|
|
2254
|
-
await
|
|
2297
|
+
await mkdir3(CONFIG_DIR2, { recursive: true });
|
|
2255
2298
|
const envContent = `# AFK Code Discord Configuration
|
|
2256
2299
|
DISCORD_BOT_TOKEN=${botToken}
|
|
2257
2300
|
DISCORD_USER_ID=${userId}
|
|
@@ -2330,7 +2373,7 @@ async function discordRun() {
|
|
|
2330
2373
|
|
|
2331
2374
|
// src/cli/telegram.ts
|
|
2332
2375
|
import { homedir as homedir5 } from "os";
|
|
2333
|
-
import { mkdir as
|
|
2376
|
+
import { mkdir as mkdir4, writeFile as writeFile3, readFile as readFile4, access as access3 } from "fs/promises";
|
|
2334
2377
|
import * as readline3 from "readline";
|
|
2335
2378
|
var CONFIG_DIR3 = `${homedir5()}/.afk-code`;
|
|
2336
2379
|
var TELEGRAM_CONFIG_FILE = `${CONFIG_DIR3}/telegram.env`;
|
|
@@ -2390,7 +2433,7 @@ Step 2: Get Your Chat ID
|
|
|
2390
2433
|
console.error("Invalid chat ID. It should be a number (can be negative for groups).");
|
|
2391
2434
|
process.exit(1);
|
|
2392
2435
|
}
|
|
2393
|
-
await
|
|
2436
|
+
await mkdir4(CONFIG_DIR3, { recursive: true });
|
|
2394
2437
|
const envContent = `# AFK Code Telegram Configuration
|
|
2395
2438
|
TELEGRAM_BOT_TOKEN=${botToken}
|
|
2396
2439
|
TELEGRAM_CHAT_ID=${chatId}
|
package/package.json
CHANGED