clisbot 0.1.7 → 0.1.8

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
  }
@@ -64768,9 +64778,8 @@ function renderAgentPromptInstruction(params) {
64768
64778
  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
64779
  ];
64770
64780
  if (messageToolMode) {
64771
- const wrapperPath = getClisbotWrapperPath();
64772
64781
  const replyCommand = buildReplyCommand({
64773
- wrapperPath,
64782
+ command: getClisbotPromptCommand(),
64774
64783
  identity: params.identity
64775
64784
  });
64776
64785
  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 +64852,7 @@ function renderNamedValue(label, name, id) {
64843
64852
  return value ? `${label} ${value}` : "";
64844
64853
  }
64845
64854
  function buildReplyCommand(params) {
64846
- const lines = [`${params.wrapperPath} message send \\`];
64855
+ const lines = [`${params.command} message send \\`];
64847
64856
  if (params.identity.platform === "slack") {
64848
64857
  lines.push(" --channel slack \\");
64849
64858
  lines.push(` --target channel:${params.identity.channelId ?? ""} \\`);
@@ -66151,6 +66160,7 @@ class SlackSocketService {
66151
66160
  // src/channels/slack/message-actions.ts
66152
66161
  var import_bolt = __toESM(require_dist7(), 1);
66153
66162
  import { basename } from "node:path";
66163
+ import { readFile as readFile2 } from "node:fs/promises";
66154
66164
  var { WebClient } = import_bolt.webApi;
66155
66165
  function createSlackClient(botToken) {
66156
66166
  return new WebClient(botToken);
@@ -66210,13 +66220,9 @@ async function loadSlackMedia(media) {
66210
66220
  data: Buffer.from(arrayBuffer)
66211
66221
  };
66212
66222
  }
66213
- const file = Bun.file(media);
66214
- if (!await file.exists()) {
66215
- throw new Error(`Media file not found: ${media}`);
66216
- }
66217
66223
  return {
66218
66224
  filename: basename(media),
66219
- data: Buffer.from(await file.arrayBuffer())
66225
+ data: await readFile2(media)
66220
66226
  };
66221
66227
  }
66222
66228
  async function sendSlackMessage(params) {
@@ -67512,6 +67518,7 @@ function resolveRouteAndTarget(params) {
67512
67518
 
67513
67519
  // src/channels/telegram/message-actions.ts
67514
67520
  import { basename as basename3, extname as extname2 } from "node:path";
67521
+ import { readFile as readFile3 } from "node:fs/promises";
67515
67522
  function parseTelegramChatId(raw) {
67516
67523
  const value = raw.trim();
67517
67524
  if (value.startsWith("@")) {
@@ -67535,13 +67542,9 @@ async function loadTelegramMedia(media) {
67535
67542
  remoteUrl: media
67536
67543
  };
67537
67544
  }
67538
- const file = Bun.file(media);
67539
- if (!await file.exists()) {
67540
- throw new Error(`Media file not found: ${media}`);
67541
- }
67542
67545
  return {
67543
67546
  filename: basename3(media),
67544
- file
67547
+ file: new Blob([await readFile3(media)])
67545
67548
  };
67546
67549
  }
67547
67550
  function inferTelegramMediaKind(filename, forceDocument) {
@@ -67973,7 +67976,7 @@ import { watch } from "node:fs";
67973
67976
  import { basename as basename4, dirname as dirname11 } from "node:path";
67974
67977
 
67975
67978
  // src/channels/processed-events-store.ts
67976
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
67979
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
67977
67980
  var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000;
67978
67981
  var PROCESSING_STALE_MS = 30 * 60 * 1000;
67979
67982
 
@@ -67991,7 +67994,7 @@ class ProcessedEventsStore {
67991
67994
  return;
67992
67995
  }
67993
67996
  try {
67994
- const text = await readFile2(this.filePath, "utf8");
67997
+ const text = await readFile4(this.filePath, "utf8");
67995
67998
  this.document = JSON.parse(text);
67996
67999
  } catch {
67997
68000
  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.8",
4
4
  "private": false,
5
5
  "description": "Chat surfaces for durable AI coding agents running in tmux",
6
6
  "license": "MIT",