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 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
- - Does not send tool calls or results
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
- return isCurrent ? `\u2022 *${s.sessionName}* \u2190 current` : `\u2022 ${s.sessionName}`;
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
- return isCurrent ? `\u2022 *${s.sessionName}* \u2190 current` : `\u2022 ${s.sessionName}`;
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 mkdir(CONFIG_DIR, { recursive: true });
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 mkdir2, writeFile as writeFile2, readFile as readFile3, access as access2 } from "fs/promises";
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 mkdir2(CONFIG_DIR2, { recursive: true });
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 mkdir3, writeFile as writeFile3, readFile as readFile4, access as access3 } from "fs/promises";
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 mkdir3(CONFIG_DIR3, { recursive: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afk-code",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Monitor and interact with Claude Code sessions from Slack/Discord/Telegram",
5
5
  "author": "Colin Harman",
6
6
  "repository": {
@@ -56,6 +56,7 @@
56
56
  "chat:write",
57
57
  "chat:write.customize",
58
58
  "commands",
59
+ "files:write",
59
60
  "groups:history",
60
61
  "groups:write",
61
62
  "users:read"