clisbot 0.1.7 → 0.1.9

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
@@ -1,41 +1,61 @@
1
- # clisbot - tmux-based Agentic Coding CLI & chat bot
2
- The cheapest path to high-end agentic AI for teams.
1
+ # clisbot - Agentic Coding CLI & chat bot
2
+ The cheapest, simplest path to frontier LLMs and agentic CLI workflows for teams and individuals.
3
3
 
4
- `clisbot` exposes native agentic AI tool CLIs like Claude Code / Codex through chat surfaces, with each agent running inside its own durable tmux session and ready to behave like a real bot, a real assistant - with SOUL & IDENTITY, not just a coding tool.
4
+ `clisbot` exposes native agentic AI tool CLIs like Claude Code / Codex through multi-channel chat surfaces, with each agent running inside its own durable tmux session and ready to behave like a real bot, a real assistant - with SOUL, IDENTITY & MEMORY, not just a coding tool.
5
+
6
+ It is not just another tmux bridge or Telegram bridge. `clisbot` is meant to grow into a reusable agent runtime layer that can support many CLI tools, many channels, and many workflow shapes on top of the same durable agent session.
5
7
 
6
8
  Agentic AI is powerful, but only with frontier models. OpenClaw took off because people found many ways to access strong frontier models cheaply through subscription-based OAuth. Recent Anthropic enforcement around third-party and proxy-style usage made that risk harder to ignore.
7
9
 
8
10
  Meanwhile, the strongest agentic coding tools already come from serious enterprise teams with real investment in model quality, security, safety, and operator controls, especially Claude Code, Codex, and Gemini CLI. That naturally leads to a simple question: why not reuse those agents as they already are, keep them alive in tmux, and add communication channels, team workflows, and more toys around them?
9
11
 
10
- The idea has been stuck in my head since Claude Code introduced agent skills back in October 2025, which opened more possibilities for non-developer workers, and the OpenClaw trend recently only made it harder to ignore. Having played around with Anthropic's Agent SDK to address the agentic AI adoption inside my company but somehow it was not working, not stable, not flexible, not fast enough, and the CLI path now looks like the better choice until now. Now feels like the right time to push that idea.
11
-
12
12
  Every company will likely need an OpenClaw-style strategy over time: a personal agentic assistant for each employee, plus shared agents for each team. `clisbot` starts from a team-first angle, with Slack and shared agent workflows as the default center of gravity instead of treating team collaboration as a later add-on.
13
13
 
14
+ ## Important caution
15
+
16
+ Strong vendor investment in security and safety does not make frontier agentic CLI tools inherently safe. `clisbot` exposes those tools more broadly through chat and workflow surfaces, so you should treat the whole system as high-trust software and use it at your own risk.
17
+
18
+ ## Acknowledgements
19
+
20
+ `clisbot` would not exist without the ideas, momentum, and practical inspiration created by OpenClaw. Many configuration, routing, and workspace concepts here were learned from studying OpenClaw, then adapted to `clisbot`'s own direction. Respect and thanks to the OpenClaw project and community.
21
+
14
22
  ## Why clisbot
15
23
 
16
- - Runs native agentic AI coding CLIs in durable tmux sessions
24
+ - Reuses the native CLI tools you already know and subscribe to, such as Claude Code, Codex, and Gemini CLI, then extends them across coding, chatbot, and non-dev workflows without forcing you to switch tools.
17
25
  - Optimized for cheap subscription-backed usage with tools like Codex CLI and Claude CLI... A practical response to the reality that high-quality frontier models are expensive and vendor policies can tighten around third-party usage.
18
26
  - Compatible with OpenClaw-style configuration, commands and some concepts, agent personality for bot usecases, and workspace bootstrap templates, help Openclaw users to quickly get started.
19
27
  - Team-first by design, with agent bootstrap templates that fit shared team agents as well as personal ones.
20
- - Reuses mature agentic tools such as Claude Code, Codex, and Gemini CLI
21
28
  - Fits the emerging pattern of one personal assistant per employee and shared assistants per team.
22
29
  - Useful as a bot for coding, operations, teamwork, and general work in team environment, or on the go
23
30
  - Strong team support in Slack, with Telegram already supported as another first-class channel.
24
31
  - Configurable follow-up policy instead of a fixed TTL model, with a 5-minute default window and route-level controls so teams can tune behavior to how they actually work. Smart follow-up controls help avoid unwanted bot interruption in active threads: keep natural continuation when useful, or pause it when you want the bot to stay quiet until explicitly called again.
25
- - Fast operator shortcuts for shell execution: `!<command>` or `/bash <command>`. Turns Slack / Telegram to terminal interface on the go.
32
+ - Fast operator shortcuts for shell execution: `!<command>` or `/bash <command>`, plus slash-prefix mappings such as `\bash` or `::bash` when Slack slash-command handling is incompatible. Turns Slack / Telegram into a terminal interface on the go.
26
33
  - The proof of concept already shows high potential beyond internal coding workflows, including customer chatbot use cases once messaging MCP or CLI-based skills let the agent send messages proactively in a cleaner way.
27
34
 
28
35
  ## Current Focus
29
36
 
30
- `clisbot` is a communication bridge for long-lived AI agents, organized around the repository architecture contract:
37
+ `clisbot` is growing toward a broader agent runtime layer:
38
+
39
+ - more CLI tool support beyond Claude Code and Codex, including Gemini CLI, OpenCode, Qwen, Kilo, and other agentic CLIs
40
+ - more communication channels beyond Slack and Telegram, including Zalo, WhatsApp, Facebook, Discord, and future API-compatible surfaces
41
+ - simple workflow building blocks such as cronjobs, heartbeat jobs, lightweight Ralph-style loops, and prompt combinations that just work
42
+ - durable agent sessions, workspaces, follow-up policy, commands, attachments, and operator controls that stay reusable across all those surfaces
31
43
 
32
- - `channels`: Slack, Telegram, and future API-compatible surfaces
33
- - `agent-os`: agents, sessions, workspaces, commands, attachments, and follow-up policy
34
- - `runners`: tmux today, with ACP, SDK, and other execution backends later
35
- - `control`: operator-facing inspection, lifecycle, and debugging flows
36
- - `configuration`: the local control plane that wires the system together
44
+ tmux is still the current stability boundary. One agent maps to one durable runner session in one workspace, and every CLI, channel, or workflow layer should route onto that durable runtime instead of recreating the agent from scratch.
37
45
 
38
- tmux is the current stability boundary. One agent maps to one durable runner session in one workspace, and chat surfaces route conversations onto that runtime instead of trying to recreate it.
46
+ ## Showcase
47
+
48
+ Slack
49
+
50
+ ![Slack showcase](https://raw.githubusercontent.com/longbkit/clisbot/main/docs/pics/slack-01.jpg)
51
+
52
+ Telegram
53
+
54
+ ![Telegram topic showcase 1](https://raw.githubusercontent.com/longbkit/clisbot/main/docs/pics/telegram-01.jpg)
55
+
56
+ ![Telegram topic showcase 2](https://raw.githubusercontent.com/longbkit/clisbot/main/docs/pics/telegram-02.jpg)
57
+
58
+ ![Telegram topic showcase 3](https://raw.githubusercontent.com/longbkit/clisbot/main/docs/pics/telegram-03.jpg)
39
59
 
40
60
  ## Quick Start
41
61
 
@@ -75,6 +95,19 @@ clisbot start --cli codex --bootstrap personal-assistant
75
95
  clis start --cli codex --bootstrap personal-assistant
76
96
  ```
77
97
 
98
+ 4. Fastest first conversation path: send a direct message to the bot in Slack or Telegram.
99
+
100
+ `clisbot` defaults direct messages to pairing mode. The bot will reply with a pairing code and approval command.
101
+
102
+ Approve it with:
103
+
104
+ ```bash
105
+ clisbot pairing approve slack <CODE>
106
+ clisbot pairing approve telegram <CODE>
107
+ ```
108
+
109
+ After approval, keep chatting with the bot in that DM or add shared channel or topic routes later.
110
+
78
111
  If you do not want to install globally, you can also run it directly with `npx`:
79
112
 
80
113
  ```bash
@@ -111,6 +144,19 @@ source ~/.zshrc
111
144
  bun run start --cli codex --bootstrap personal-assistant
112
145
  ```
113
146
 
147
+ 4. Fastest first conversation path: send a direct message to the bot in Slack or Telegram.
148
+
149
+ `clisbot` defaults direct messages to pairing mode. The bot will reply with a pairing code and approval command.
150
+
151
+ Approve it with:
152
+
153
+ ```bash
154
+ bun run pairing -- approve slack <CODE>
155
+ bun run pairing -- approve telegram <CODE>
156
+ ```
157
+
158
+ After approval, keep chatting with the bot in that DM or add shared channel or topic routes later.
159
+
114
160
  Fresh config now starts with no configured agents, and first-run `clisbot start` requires both `--cli` and `--bootstrap` before it creates the first `default` agent.
115
161
  Fresh config also starts with no preconfigured Slack channels or Telegram groups/topics. Add those routes manually in `~/.clisbot/clisbot.json`.
116
162
  `clisbot start` now also requires Slack or Telegram token references before it bootstraps anything. By default it looks for `SLACK_APP_TOKEN`, `SLACK_BOT_TOKEN`, and `TELEGRAM_BOT_TOKEN`, but you can pass custom placeholders such as `--slack-app-token '${CUSTOM_SLACK_APP_TOKEN}'`.
@@ -155,7 +201,6 @@ clisbot agents list --bindings
155
201
  Agent setup rules:
156
202
 
157
203
  - `agents add` requires `--cli` and currently supports `codex` and `claude`.
158
- - `--startup-option` is optional; if omitted, clisbot uses the built-in startup options for the selected CLI.
159
204
  - `--bootstrap` accepts `personal-assistant` or `team-assistant` and seeds the workspace from `templates/openclaw` plus the selected customized template.
160
205
  - `personal-assistant` fits one assistant for one human.
161
206
  - `team-assistant` fits one shared assistant for a team, channel, or group workflow.
@@ -80,6 +80,18 @@
80
80
  "mode": "socket",
81
81
  "appToken": "${SLACK_APP_TOKEN}",
82
82
  "botToken": "${SLACK_BOT_TOKEN}",
83
+ "defaultAccount": "default",
84
+ "accounts": {
85
+ "default": {
86
+ "appToken": "${SLACK_APP_TOKEN}",
87
+ "botToken": "${SLACK_BOT_TOKEN}"
88
+ }
89
+ },
90
+ "agentPrompt": {
91
+ "enabled": true,
92
+ "maxProgressMessages": 3,
93
+ "requireFinalResponse": true
94
+ },
83
95
  "ackReaction": "",
84
96
  "typingReaction": "",
85
97
  "processingStatus": {
@@ -107,6 +119,8 @@
107
119
  },
108
120
  "streaming": "all",
109
121
  "response": "final",
122
+ "responseMode": "message-tool",
123
+ "additionalMessageMode": "steer",
110
124
  "followUp": {
111
125
  "mode": "auto",
112
126
  "participationTtlMin": 5
@@ -129,6 +143,17 @@
129
143
  "enabled": true,
130
144
  "mode": "polling",
131
145
  "botToken": "${TELEGRAM_BOT_TOKEN}",
146
+ "defaultAccount": "default",
147
+ "accounts": {
148
+ "default": {
149
+ "botToken": "${TELEGRAM_BOT_TOKEN}"
150
+ }
151
+ },
152
+ "agentPrompt": {
153
+ "enabled": true,
154
+ "maxProgressMessages": 3,
155
+ "requireFinalResponse": true
156
+ },
132
157
  "allowBots": false,
133
158
  "groupPolicy": "allowlist",
134
159
  "defaultAgentId": "default",
@@ -147,6 +172,8 @@
147
172
  },
148
173
  "streaming": "all",
149
174
  "response": "final",
175
+ "responseMode": "message-tool",
176
+ "additionalMessageMode": "steer",
150
177
  "followUp": {
151
178
  "mode": "auto",
152
179
  "participationTtlMin": 5
package/dist/main.js CHANGED
@@ -51156,7 +51156,7 @@ var require_send = __commonJS((exports, module) => {
51156
51156
  var join6 = path2.join;
51157
51157
  var normalize = path2.normalize;
51158
51158
  var resolve = path2.resolve;
51159
- var sep = path2.sep;
51159
+ var sep2 = path2.sep;
51160
51160
  var BYTES_RANGE_REGEXP = /^ *bytes=/;
51161
51161
  var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1000;
51162
51162
  var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
@@ -51317,14 +51317,14 @@ var require_send = __commonJS((exports, module) => {
51317
51317
  var parts;
51318
51318
  if (root !== null) {
51319
51319
  if (path3) {
51320
- path3 = normalize("." + sep + path3);
51320
+ path3 = normalize("." + sep2 + path3);
51321
51321
  }
51322
51322
  if (UP_PATH_REGEXP.test(path3)) {
51323
51323
  debug('malicious path "%s"', path3);
51324
51324
  this.error(403);
51325
51325
  return res;
51326
51326
  }
51327
- parts = path3.split(sep);
51327
+ parts = path3.split(sep2);
51328
51328
  path3 = normalize(join6(root, path3));
51329
51329
  } else {
51330
51330
  if (UP_PATH_REGEXP.test(path3)) {
@@ -51332,7 +51332,7 @@ var require_send = __commonJS((exports, module) => {
51332
51332
  this.error(403);
51333
51333
  return res;
51334
51334
  }
51335
- parts = normalize(path3).split(sep);
51335
+ parts = normalize(path3).split(sep2);
51336
51336
  path3 = resolve(path3);
51337
51337
  }
51338
51338
  if (containsDotFile(parts)) {
@@ -51427,7 +51427,7 @@ var require_send = __commonJS((exports, module) => {
51427
51427
  var self2 = this;
51428
51428
  debug('stat "%s"', path3);
51429
51429
  fs2.stat(path3, function onstat(err, stat) {
51430
- var pathEndsWithSep = path3[path3.length - 1] === sep;
51430
+ var pathEndsWithSep = path3[path3.length - 1] === sep2;
51431
51431
  if (err && err.code === "ENOENT" && !extname(path3) && !pathEndsWithSep) {
51432
51432
  return next(err);
51433
51433
  }
@@ -62697,7 +62697,7 @@ function escapeRegExp(raw) {
62697
62697
  // src/control/clisbot-wrapper.ts
62698
62698
  import { chmod } from "node:fs/promises";
62699
62699
  import { fileURLToPath as fileURLToPath3 } from "node:url";
62700
- import { dirname as dirname7, join as join5 } from "node:path";
62700
+ import { dirname as dirname7, join as join5, sep } from "node:path";
62701
62701
  var DEFAULT_CLISBOT_BIN_DIR = join5(APP_HOME_DIR, "bin");
62702
62702
  var DEFAULT_CLISBOT_WRAPPER_PATH = join5(DEFAULT_CLISBOT_BIN_DIR, "clisbot");
62703
62703
  function shellQuote(value) {
@@ -62709,9 +62709,19 @@ function shellQuote(value) {
62709
62709
  function getClisbotMainScriptPath() {
62710
62710
  return fileURLToPath3(new URL("../main.ts", import.meta.url));
62711
62711
  }
62712
+ function isPackagedRuntime() {
62713
+ const currentModulePath = fileURLToPath3(import.meta.url);
62714
+ return currentModulePath.includes(`${sep}dist${sep}`);
62715
+ }
62712
62716
  function getClisbotWrapperPath() {
62713
62717
  return expandHomePath(process.env.CLISBOT_WRAPPER_PATH || DEFAULT_CLISBOT_WRAPPER_PATH);
62714
62718
  }
62719
+ function getClisbotPromptCommand() {
62720
+ if (process.env.CLISBOT_PROMPT_COMMAND?.trim()) {
62721
+ return process.env.CLISBOT_PROMPT_COMMAND.trim();
62722
+ }
62723
+ return isPackagedRuntime() ? "clis" : getClisbotWrapperPath();
62724
+ }
62715
62725
  function getClisbotWrapperDir() {
62716
62726
  return dirname7(getClisbotWrapperPath());
62717
62727
  }
@@ -62731,7 +62741,7 @@ async function ensureClisbotWrapper() {
62731
62741
  const wrapperDir = dirname7(wrapperPath);
62732
62742
  await ensureDir2(wrapperDir);
62733
62743
  const nextScript = renderClisbotWrapperScript();
62734
- const existing = await fileExists(wrapperPath) ? await Bun.file(wrapperPath).text() : null;
62744
+ const existing = await fileExists(wrapperPath) ? await readTextFile(wrapperPath) : null;
62735
62745
  if (existing !== nextScript) {
62736
62746
  await writeTextFile(wrapperPath, nextScript);
62737
62747
  }
@@ -63247,6 +63257,41 @@ async function monitorTmuxRun(params) {
63247
63257
  }
63248
63258
 
63249
63259
  // src/agents/active-run-manager.ts
63260
+ var OBSERVER_RETRYABLE_FAILURE_LIMIT = 3;
63261
+ function formatObserverError(error) {
63262
+ return error instanceof Error ? error.stack ?? error.message : String(error);
63263
+ }
63264
+ function listObserverErrorCodes(error) {
63265
+ const codes = new Set;
63266
+ const visit = (value) => {
63267
+ if (!value || typeof value !== "object") {
63268
+ return;
63269
+ }
63270
+ const candidate = value;
63271
+ if (typeof candidate.code === "string" && candidate.code.trim()) {
63272
+ codes.add(candidate.code.trim().toUpperCase());
63273
+ }
63274
+ if (Array.isArray(candidate.errors)) {
63275
+ for (const nested of candidate.errors) {
63276
+ visit(nested);
63277
+ }
63278
+ }
63279
+ if (candidate.cause) {
63280
+ visit(candidate.cause);
63281
+ }
63282
+ };
63283
+ visit(error);
63284
+ return [...codes];
63285
+ }
63286
+ function isRetryableObserverDeliveryError(error) {
63287
+ const codes = listObserverErrorCodes(error);
63288
+ if (codes.some((code) => code === "ETIMEDOUT" || code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EHOSTUNREACH" || code === "ENETUNREACH" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "UND_ERR_HEADERS_TIMEOUT" || code === "UND_ERR_SOCKET")) {
63289
+ return true;
63290
+ }
63291
+ const message = error instanceof Error ? `${error.name} ${error.message}` : String(error);
63292
+ return /fetch failed|request timed out|network|socket hang up/i.test(message);
63293
+ }
63294
+
63250
63295
  class ActiveRunInProgressError extends Error {
63251
63296
  update;
63252
63297
  constructor(update) {
@@ -63322,6 +63367,7 @@ class ActiveRunManager {
63322
63367
  this.activeRuns.set(resolved.sessionKey, {
63323
63368
  resolved,
63324
63369
  observers: new Map,
63370
+ observerFailures: new Map,
63325
63371
  initialResult,
63326
63372
  latestUpdate: update
63327
63373
  });
@@ -63356,6 +63402,7 @@ class ActiveRunManager {
63356
63402
  this.activeRuns.set(provisionalResolved.sessionKey, {
63357
63403
  resolved: provisionalResolved,
63358
63404
  observers: new Map([[observer.id, { ...observer }]]),
63405
+ observerFailures: new Map,
63359
63406
  initialResult,
63360
63407
  latestUpdate: this.createRunUpdate({
63361
63408
  resolved: provisionalResolved,
@@ -63412,6 +63459,7 @@ class ActiveRunManager {
63412
63459
  existingRun.observers.set(observer.id, {
63413
63460
  ...observer
63414
63461
  });
63462
+ existingRun.observerFailures.delete(observer.id);
63415
63463
  return {
63416
63464
  active: !isTerminalRunStatus(existingRun.latestUpdate.status),
63417
63465
  update: existingRun.latestUpdate
@@ -63446,6 +63494,7 @@ class ActiveRunManager {
63446
63494
  };
63447
63495
  }
63448
63496
  observer.mode = "passive-final";
63497
+ run.observerFailures.delete(observerId);
63449
63498
  return {
63450
63499
  detached: true
63451
63500
  };
@@ -63472,7 +63521,7 @@ class ActiveRunManager {
63472
63521
  async notifyRunObservers(run, update) {
63473
63522
  run.latestUpdate = update;
63474
63523
  const now = Date.now();
63475
- for (const observer of run.observers.values()) {
63524
+ for (const [observerId, observer] of run.observers.entries()) {
63476
63525
  if (observer.expiresAt && now >= observer.expiresAt && observer.mode !== "passive-final") {
63477
63526
  observer.mode = "passive-final";
63478
63527
  }
@@ -63488,7 +63537,21 @@ class ActiveRunManager {
63488
63537
  continue;
63489
63538
  }
63490
63539
  observer.lastSentAt = now;
63491
- await observer.onUpdate(update);
63540
+ try {
63541
+ await observer.onUpdate(update);
63542
+ run.observerFailures.delete(observerId);
63543
+ } catch (error) {
63544
+ const retryable = isRetryableObserverDeliveryError(error);
63545
+ const nextFailures = retryable ? (run.observerFailures.get(observerId) ?? 0) + 1 : OBSERVER_RETRYABLE_FAILURE_LIMIT;
63546
+ const shouldDetach = !retryable || isTerminalRunStatus(update.status) || nextFailures >= OBSERVER_RETRYABLE_FAILURE_LIMIT;
63547
+ if (shouldDetach) {
63548
+ run.observers.delete(observerId);
63549
+ run.observerFailures.delete(observerId);
63550
+ } else {
63551
+ run.observerFailures.set(observerId, nextFailures);
63552
+ }
63553
+ console.error(shouldDetach ? `run observer '${observerId}' update failed for ${run.resolved.sessionKey}; detaching observer` : `run observer '${observerId}' update failed for ${run.resolved.sessionKey}; keeping observer for retry (${nextFailures}/${OBSERVER_RETRYABLE_FAILURE_LIMIT})`, formatObserverError(error));
63554
+ }
63492
63555
  }
63493
63556
  }
63494
63557
  async finishActiveRun(sessionKey, update) {
@@ -64768,9 +64831,8 @@ function renderAgentPromptInstruction(params) {
64768
64831
  messageToolMode ? "channel auto-delivery is disabled for this conversation; send user-facing progress updates and the final response yourself with the reply command" : "channel auto-delivery remains enabled for this conversation; do not send user-facing progress updates or the final response with clisbot message send"
64769
64832
  ];
64770
64833
  if (messageToolMode) {
64771
- const wrapperPath = getClisbotWrapperPath();
64772
64834
  const replyCommand = buildReplyCommand({
64773
- wrapperPath,
64835
+ command: getClisbotPromptCommand(),
64774
64836
  identity: params.identity
64775
64837
  });
64776
64838
  lines.push("Use the exact command below when you need to send progress updates, media attachments, or the final response back to the user.", "reply command:", replyCommand, `progress updates: at most ${params.config.maxProgressMessages}`, params.config.requireFinalResponse ? "final response: send exactly 1 final user-facing response" : "final response: optional", "keep progress updates short and meaningful", "do not send progress updates for trivial internal steps");
@@ -64843,7 +64905,7 @@ function renderNamedValue(label, name, id) {
64843
64905
  return value ? `${label} ${value}` : "";
64844
64906
  }
64845
64907
  function buildReplyCommand(params) {
64846
- const lines = [`${params.wrapperPath} message send \\`];
64908
+ const lines = [`${params.command} message send \\`];
64847
64909
  if (params.identity.platform === "slack") {
64848
64910
  lines.push(" --channel slack \\");
64849
64911
  lines.push(` --target channel:${params.identity.channelId ?? ""} \\`);
@@ -66151,6 +66213,7 @@ class SlackSocketService {
66151
66213
  // src/channels/slack/message-actions.ts
66152
66214
  var import_bolt = __toESM(require_dist7(), 1);
66153
66215
  import { basename } from "node:path";
66216
+ import { readFile as readFile2 } from "node:fs/promises";
66154
66217
  var { WebClient } = import_bolt.webApi;
66155
66218
  function createSlackClient(botToken) {
66156
66219
  return new WebClient(botToken);
@@ -66210,13 +66273,9 @@ async function loadSlackMedia(media) {
66210
66273
  data: Buffer.from(arrayBuffer)
66211
66274
  };
66212
66275
  }
66213
- const file = Bun.file(media);
66214
- if (!await file.exists()) {
66215
- throw new Error(`Media file not found: ${media}`);
66216
- }
66217
66276
  return {
66218
66277
  filename: basename(media),
66219
- data: Buffer.from(await file.arrayBuffer())
66278
+ data: await readFile2(media)
66220
66279
  };
66221
66280
  }
66222
66281
  async function sendSlackMessage(params) {
@@ -67512,6 +67571,7 @@ function resolveRouteAndTarget(params) {
67512
67571
 
67513
67572
  // src/channels/telegram/message-actions.ts
67514
67573
  import { basename as basename3, extname as extname2 } from "node:path";
67574
+ import { readFile as readFile3 } from "node:fs/promises";
67515
67575
  function parseTelegramChatId(raw) {
67516
67576
  const value = raw.trim();
67517
67577
  if (value.startsWith("@")) {
@@ -67535,13 +67595,9 @@ async function loadTelegramMedia(media) {
67535
67595
  remoteUrl: media
67536
67596
  };
67537
67597
  }
67538
- const file = Bun.file(media);
67539
- if (!await file.exists()) {
67540
- throw new Error(`Media file not found: ${media}`);
67541
- }
67542
67598
  return {
67543
67599
  filename: basename3(media),
67544
- file
67600
+ file: new Blob([await readFile3(media)])
67545
67601
  };
67546
67602
  }
67547
67603
  function inferTelegramMediaKind(filename, forceDocument) {
@@ -67973,7 +68029,7 @@ import { watch } from "node:fs";
67973
68029
  import { basename as basename4, dirname as dirname11 } from "node:path";
67974
68030
 
67975
68031
  // src/channels/processed-events-store.ts
67976
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
68032
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
67977
68033
  var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000;
67978
68034
  var PROCESSING_STALE_MS = 30 * 60 * 1000;
67979
68035
 
@@ -67991,7 +68047,7 @@ class ProcessedEventsStore {
67991
68047
  return;
67992
68048
  }
67993
68049
  try {
67994
- const text = await readFile2(this.filePath, "utf8");
68050
+ const text = await readFile4(this.filePath, "utf8");
67995
68051
  this.document = JSON.parse(text);
67996
68052
  } catch {
67997
68053
  this.document = { events: {} };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clisbot",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "description": "Chat surfaces for durable AI coding agents running in tmux",
6
6
  "license": "MIT",