tabclaw 0.1.0 → 0.1.2
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 +45 -8
- package/dist/cli/index.js +64 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/process.js +62 -29
- package/dist/daemon/process.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
# Tabclaw
|
|
2
2
|
|
|
3
|
-
Tabclaw is a local gateway
|
|
3
|
+
Tabclaw is a local gateway that bridges AI programming assistants (like Claude Code) with chat platforms (like Telegram). It listens for agent events and forwards them to your channels in real time.
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
### Overview
|
|
8
|
+
|
|
9
|
+
- Runs a local HTTP gateway daemon to capture agent hook events
|
|
10
|
+
- Attaches hook-enabled agents to the gateway for event streaming
|
|
11
|
+
- Routes session events, permission requests, and task notifications to configured channels
|
|
12
|
+
- Manages agent-initiated tool execution permissions via chat platform interactions
|
|
13
|
+
|
|
14
|
+
### Claude Code + Telegram integration
|
|
15
|
+
|
|
16
|
+
The core workflow bridges Claude Code with Telegram:
|
|
17
|
+
|
|
18
|
+
1. **Session Monitoring** — Tabclaw listens for Claude Code session start/end events and notifies you on Telegram
|
|
19
|
+
2. **Permission Control** — When Claude requests to execute a tool, you receive a permission prompt on Telegram with **Allow/Deny** buttons
|
|
20
|
+
3. **Real-time Decisions** — Click the button in Telegram to grant or deny the tool execution
|
|
21
|
+
4. **Task Completion** — Receive final notifications when tasks complete, including execution duration, token usage, and results
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+

|
|
11
26
|
|
|
12
27
|
## Installation
|
|
13
28
|
|
|
@@ -61,9 +76,30 @@ tabclaw gateway status
|
|
|
61
76
|
|
|
62
77
|
## Configuration
|
|
63
78
|
|
|
64
|
-
|
|
79
|
+
Most configuration is handled by `tabclaw init` interactive wizard, which guides you through:
|
|
80
|
+
|
|
81
|
+
1. Setting up your Claude Code hook server port
|
|
82
|
+
2. Configuring Telegram bot credentials
|
|
83
|
+
3. Testing channel connectivity
|
|
84
|
+
|
|
85
|
+
For manual setup or advanced options, edit `~/.tabclaw/config.toml`. Key sections:
|
|
86
|
+
|
|
87
|
+
**General settings:**
|
|
88
|
+
- `default_agent` — default agent to use (currently `claude`)
|
|
89
|
+
- `hook_server_port` — local port for receiving agent hooks (default `9876`)
|
|
90
|
+
- `log_level` — logging verbosity (`debug`, `info`, `warn`, `error`)
|
|
91
|
+
|
|
92
|
+
**Telegram channel:**
|
|
93
|
+
- `bot_token` — your Telegram bot token from BotFather
|
|
94
|
+
- `allowed_users` — list of Telegram user IDs allowed to interact with the bot (e.g., `[123456789, 987654321]`)
|
|
95
|
+
- `notify_types` — which event types to forward (`permission_request`, `finished`, `session_start`, `error`, etc.)
|
|
96
|
+
|
|
97
|
+
**Claude agent:**
|
|
98
|
+
- `adapter` — integration method (always `hooks`)
|
|
99
|
+
- `binary` — Claude Code binary name
|
|
100
|
+
- `permission_mode` — how to handle tool permissions
|
|
65
101
|
|
|
66
|
-
Example
|
|
102
|
+
Example `~/.tabclaw/config.toml`:
|
|
67
103
|
|
|
68
104
|
```toml
|
|
69
105
|
[general]
|
|
@@ -74,7 +110,8 @@ hook_server_port = 9876
|
|
|
74
110
|
[channel.telegram]
|
|
75
111
|
enabled = true
|
|
76
112
|
bot_token = "YOUR_BOT_TOKEN"
|
|
77
|
-
allowed_users = [123456789]
|
|
113
|
+
allowed_users = ["123456789"]
|
|
114
|
+
notify_types = ["permission_request", "finished", "session_start", "error"]
|
|
78
115
|
|
|
79
116
|
[agent.claude]
|
|
80
117
|
adapter = "hooks"
|
package/dist/cli/index.js
CHANGED
|
@@ -95,6 +95,16 @@ function getOrCreateHookToken() {
|
|
|
95
95
|
function getHookToken() {
|
|
96
96
|
return loadCredentials()?.hookToken ?? null;
|
|
97
97
|
}
|
|
98
|
+
function resetCredentials() {
|
|
99
|
+
const newToken = generateToken();
|
|
100
|
+
const credentials = {
|
|
101
|
+
version: CREDENTIALS_VERSION,
|
|
102
|
+
hookToken: newToken,
|
|
103
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
104
|
+
};
|
|
105
|
+
saveCredentials(credentials);
|
|
106
|
+
return newToken;
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
// src/cli/attach.ts
|
|
100
110
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
@@ -235,6 +245,7 @@ var en_default = {
|
|
|
235
245
|
detected: "Claude Code detected.",
|
|
236
246
|
hooksInstalled: "Hooks already installed.",
|
|
237
247
|
hooksNotInstalled: "Hooks not installed.",
|
|
248
|
+
hooksTokenMismatch: "Hooks have outdated token - reconfiguration required.",
|
|
238
249
|
actionQuestion: "Select action:",
|
|
239
250
|
injectAction: "Inject hooks configuration",
|
|
240
251
|
updateAction: "Update hooks configuration",
|
|
@@ -603,44 +614,75 @@ function detectClaude() {
|
|
|
603
614
|
settingsPath
|
|
604
615
|
};
|
|
605
616
|
}
|
|
617
|
+
const currentToken = getHookToken();
|
|
606
618
|
let hooked = false;
|
|
619
|
+
let tokenMismatch = false;
|
|
607
620
|
if (existsSync4(settingsPath)) {
|
|
608
621
|
try {
|
|
609
622
|
const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
610
|
-
|
|
623
|
+
const hookStatus = checkTabclawHooks(settings, currentToken);
|
|
624
|
+
hooked = hookStatus.matched;
|
|
625
|
+
tokenMismatch = hookStatus.mismatched;
|
|
611
626
|
} catch {
|
|
612
627
|
}
|
|
613
628
|
}
|
|
629
|
+
let message;
|
|
630
|
+
if (hooked) {
|
|
631
|
+
message = "Claude Code hooks already configured.";
|
|
632
|
+
} else if (tokenMismatch) {
|
|
633
|
+
message = "Claude Code hooks have mismatched token. Reconfiguration required.";
|
|
634
|
+
} else {
|
|
635
|
+
message = "Claude Code detected.";
|
|
636
|
+
}
|
|
614
637
|
return {
|
|
615
638
|
supported: true,
|
|
616
639
|
hooked,
|
|
617
|
-
|
|
640
|
+
tokenMismatch,
|
|
641
|
+
message,
|
|
618
642
|
binaryPath,
|
|
619
643
|
settingsPath
|
|
620
644
|
};
|
|
621
645
|
}
|
|
622
|
-
function
|
|
646
|
+
function checkTabclawHooks(settings, expectedToken) {
|
|
623
647
|
const hooks = settings.hooks;
|
|
624
|
-
if (!hooks) return false;
|
|
648
|
+
if (!hooks) return { matched: false, mismatched: false };
|
|
649
|
+
let hasAnyTabclawHook = false;
|
|
650
|
+
let hasMatchingToken = false;
|
|
625
651
|
for (const key of Object.keys(hooks)) {
|
|
626
652
|
const entries = hooks[key];
|
|
627
653
|
for (const entry of entries) {
|
|
628
654
|
if (!entry || typeof entry !== "object") continue;
|
|
629
655
|
const e = entry;
|
|
630
656
|
const inner = e.hooks;
|
|
631
|
-
if (Array.isArray(inner)
|
|
632
|
-
|
|
657
|
+
if (Array.isArray(inner)) {
|
|
658
|
+
for (const h of inner) {
|
|
659
|
+
const hookInfo = extractTabclawHookInfo(h);
|
|
660
|
+
if (hookInfo.isTabclaw) {
|
|
661
|
+
hasAnyTabclawHook = true;
|
|
662
|
+
if (expectedToken && hookInfo.token === expectedToken) {
|
|
663
|
+
hasMatchingToken = true;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
633
667
|
}
|
|
634
668
|
}
|
|
635
669
|
}
|
|
636
|
-
return
|
|
670
|
+
return {
|
|
671
|
+
matched: hasMatchingToken,
|
|
672
|
+
mismatched: hasAnyTabclawHook && !hasMatchingToken
|
|
673
|
+
};
|
|
637
674
|
}
|
|
638
|
-
function
|
|
639
|
-
if (!entry || typeof entry !== "object") return false;
|
|
675
|
+
function extractTabclawHookInfo(entry) {
|
|
676
|
+
if (!entry || typeof entry !== "object") return { isTabclaw: false };
|
|
640
677
|
const e = entry;
|
|
641
|
-
if (e.type !== "http") return false;
|
|
678
|
+
if (e.type !== "http") return { isTabclaw: false };
|
|
642
679
|
const url = e.url;
|
|
643
|
-
|
|
680
|
+
if (typeof url !== "string") return { isTabclaw: false };
|
|
681
|
+
const match = url.match(/^http:\/\/localhost:\d+\/hook\/([^\/]+)/);
|
|
682
|
+
if (match) {
|
|
683
|
+
return { isTabclaw: true, token: match[1] };
|
|
684
|
+
}
|
|
685
|
+
return { isTabclaw: false };
|
|
644
686
|
}
|
|
645
687
|
|
|
646
688
|
// src/cli/wizard/prompts.ts
|
|
@@ -745,6 +787,8 @@ ${t("wizard.section.cli")}
|
|
|
745
787
|
console.log(pc.gray(` Gateway: http://localhost:${port}/hook/${token ? token.substring(0, 8) + "..." : "unknown"}`));
|
|
746
788
|
if (detection.hooked) {
|
|
747
789
|
console.log(pc.green(` ${t("wizard.cli.hooksInstalled")}`));
|
|
790
|
+
} else if (detection.tokenMismatch) {
|
|
791
|
+
console.log(pc.red(` ${t("wizard.cli.hooksTokenMismatch")}`));
|
|
748
792
|
} else {
|
|
749
793
|
console.log(pc.yellow(` ${t("wizard.cli.hooksNotInstalled")}`));
|
|
750
794
|
}
|
|
@@ -874,7 +918,10 @@ async function stepCliMenu(state) {
|
|
|
874
918
|
${t("wizard.section.cli")}
|
|
875
919
|
`));
|
|
876
920
|
const cliStatus = state.itemStatus.get("claude") || "unconfigured";
|
|
877
|
-
|
|
921
|
+
let label = getChannelStatusLabel(cliStatus);
|
|
922
|
+
if (state.claudeDetection?.tokenMismatch) {
|
|
923
|
+
label = pc2.yellow("[update needed]");
|
|
924
|
+
}
|
|
878
925
|
const choice = await prompts.channelMenuSelect([{
|
|
879
926
|
name: "claude",
|
|
880
927
|
status: label
|
|
@@ -1323,6 +1370,11 @@ var initCommand = new Command2("init").description("Initialize tabclaw configura
|
|
|
1323
1370
|
return;
|
|
1324
1371
|
}
|
|
1325
1372
|
}
|
|
1373
|
+
if (options.force) {
|
|
1374
|
+
resetCredentials();
|
|
1375
|
+
} else {
|
|
1376
|
+
getOrCreateHookToken();
|
|
1377
|
+
}
|
|
1326
1378
|
const wizard = new WizardEngine();
|
|
1327
1379
|
await wizard.run();
|
|
1328
1380
|
});
|