afk-code 0.2.1 → 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
@@ -1572,7 +1572,7 @@ var telegram_app_exports = {};
1572
1572
  __export(telegram_app_exports, {
1573
1573
  createTelegramApp: () => createTelegramApp
1574
1574
  });
1575
- import { Bot } from "grammy";
1575
+ import { Bot, InputFile } from "grammy";
1576
1576
  function createTelegramApp(config) {
1577
1577
  const bot = new Bot(config.botToken);
1578
1578
  const activeSessions = /* @__PURE__ */ new Map();
@@ -1626,9 +1626,11 @@ function createTelegramApp(config) {
1626
1626
  }
1627
1627
  const sessionManager = new SessionManager({
1628
1628
  onSessionStart: async (session) => {
1629
+ const projectName = session.cwd.split("/").filter(Boolean).pop() || "unknown";
1629
1630
  activeSessions.set(session.id, {
1630
1631
  sessionId: session.id,
1631
1632
  sessionName: session.name,
1633
+ projectName,
1632
1634
  lastActivity: /* @__PURE__ */ new Date()
1633
1635
  });
1634
1636
  const parts = session.name.split(" ");
@@ -1672,6 +1674,28 @@ Directory: \`${session.cwd}\``
1672
1674
  await sendChunkedMessage(content, `_User (terminal):_`, { disable_notification: true });
1673
1675
  } else {
1674
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
+ }
1675
1699
  }
1676
1700
  },
1677
1701
  onTodos: async (sessionId, todos) => {
@@ -1790,7 +1814,8 @@ Type /help for available commands.`,
1790
1814
  const current = getCurrentSession();
1791
1815
  const list = Array.from(activeSessions.values()).map((s) => {
1792
1816
  const isCurrent = current && s.sessionId === current.sessionId;
1793
- 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}`;
1794
1819
  }).join("\n");
1795
1820
  await ctx.reply(`*Active Sessions:*
1796
1821
  ${list}
@@ -1808,7 +1833,8 @@ Use \`/switch <name>\` to change`, { parse_mode: "Markdown" });
1808
1833
  const current = getCurrentSession();
1809
1834
  const list = Array.from(activeSessions.values()).map((s) => {
1810
1835
  const isCurrent = current && s.sessionId === current.sessionId;
1811
- 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}`;
1812
1838
  }).join("\n");
1813
1839
  await ctx.reply(`*Sessions:*
1814
1840
  ${list}
@@ -1908,6 +1934,7 @@ var init_telegram_app = __esm({
1908
1934
  "use strict";
1909
1935
  init_session_manager();
1910
1936
  init_message_formatter();
1937
+ init_image_extractor();
1911
1938
  MAX_MESSAGE_LENGTH = 4e3;
1912
1939
  }
1913
1940
  });
@@ -1966,6 +1993,19 @@ async function run(command2) {
1966
1993
  const sessionId = randomUUID().slice(0, 8);
1967
1994
  const cwd = process.cwd();
1968
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
+ };
1969
2009
  const cols = process.stdout.columns || 80;
1970
2010
  const rows = process.stdout.rows || 24;
1971
2011
  const ptyProcess = pty.spawn(command2[0], command2.slice(1), {
@@ -1988,6 +2028,7 @@ async function run(command2) {
1988
2028
  process.stdin.setRawMode(true);
1989
2029
  }
1990
2030
  ptyProcess.onData((data) => {
2031
+ stopSpinner();
1991
2032
  process.stdout.write(data);
1992
2033
  });
1993
2034
  const onStdinData = (data) => {
@@ -2232,6 +2273,7 @@ Step 3: Invite the Bot
2232
2273
  \u2022 Send Messages
2233
2274
  \u2022 Manage Channels
2234
2275
  \u2022 Read Message History
2276
+ \u2022 Attach Files
2235
2277
  4. Copy the URL and open it to invite the bot to your server
2236
2278
  `);
2237
2279
  await prompt2("Press Enter when you have created and invited the bot...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afk-code",
3
- "version": "0.2.1",
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"