clawless 0.2.0 → 0.2.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 +105 -38
- package/SCHEDULER.md +21 -21
- package/bin/cli.ts +7 -2
- package/{agent-bridge.config.example.json → clawless.config.example.json} +1 -0
- package/dist/bin/cli.js +7 -2
- package/dist/index.js +46 -3
- package/dist/messaging/telegramClient.js +4 -0
- package/index.ts +54 -3
- package/messaging/telegramClient.ts +4 -0
- package/package.json +1 -1
- package/QUICKSTART-SCHEDULER.md +0 -226
- package/QUICKSTART.md +0 -98
package/README.md
CHANGED
|
@@ -1,12 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Clawless — Bring Your Own Agent (Interface + ACP)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Clawless is an interface bridge built around one core idea: **Bring Your Own Agent**.
|
|
4
|
+
|
|
5
|
+
Instead of forcing a built-in runtime, Clawless lets you keep your preferred local ACP-capable CLI (Gemini CLI by default) and adds a reliable interface layer, callbacks, and scheduling on top.
|
|
6
|
+
|
|
7
|
+
Today, Telegram is the first interface adapter; more interfaces are planned.
|
|
8
|
+
|
|
9
|
+
## Bring Your Own Agent (Main Value)
|
|
10
|
+
|
|
11
|
+
Clawless is designed so your messaging layer and automation layer stay stable while your agent runtime can change.
|
|
12
|
+
|
|
13
|
+
- Keep your preferred local agent CLI workflow
|
|
14
|
+
- Keep your existing MCP tools and local files
|
|
15
|
+
- Swap runtimes without rebuilding your bot integration
|
|
16
|
+
- Avoid lock-in to a single all-in-one framework
|
|
17
|
+
|
|
18
|
+
## Why Clawless
|
|
19
|
+
|
|
20
|
+
If you have tried heavier all-in-one agent frameworks, Clawless is the minimal alternative:
|
|
21
|
+
|
|
22
|
+
- **BYO-agent first**: use your preferred local ACP-capable CLI runtime
|
|
23
|
+
- **Lightweight setup**: minimal glue instead of a full platform migration
|
|
24
|
+
- **Local-first control**: your machine, your tools, your data flow
|
|
25
|
+
- **Transport only**: interface layer is separate from the agent runtime
|
|
26
|
+
|
|
27
|
+
## Interface Adapters
|
|
28
|
+
|
|
29
|
+
- **Current adapter**: Telegram
|
|
30
|
+
- **Planned direction**: add more interfaces without changing core agent orchestration
|
|
31
|
+
- **Design goal**: keep one message context contract so new interfaces reuse queueing, callbacks, scheduler, and ACP flow
|
|
4
32
|
|
|
5
33
|
## Features
|
|
6
34
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
35
|
+
- 🔀 **Bring Your Own Agent Runtime**: Keep Telegram/callback/scheduler UX while choosing your preferred local ACP-capable CLI
|
|
36
|
+
- 🔌 **Adapter-Friendly Interface Layer**: Telegram today, additional interfaces planned
|
|
37
|
+
- 🤖 **Telegram (Current Adapter)**: Interact with your local agent runtime through Telegram
|
|
38
|
+
- ⌨️ **Typing Status UX**: Shows Telegram typing indicator while the agent is processing
|
|
39
|
+
- 🛠️ **Rich Tool Support**: Leverages MCP (Model Context Protocol) servers connected to your local CLI runtime
|
|
10
40
|
- 🔒 **Privacy**: Runs on your hardware, you control data flow
|
|
11
41
|
- 💾 **Persistent Context**: Maintains local session unlike standard API calls
|
|
12
42
|
- 📬 **Sequential Queueing**: Processes one message at a time to avoid overlap and races
|
|
@@ -16,22 +46,22 @@ A bridge that connects a Telegram bot to the Gemini CLI using the Agent Communic
|
|
|
16
46
|
## Architecture
|
|
17
47
|
|
|
18
48
|
```
|
|
19
|
-
|
|
20
|
-
│
|
|
21
|
-
│
|
|
22
|
-
|
|
49
|
+
┌──────────────────────┐ ┌────────────────┐ ┌──────────────────────────┐
|
|
50
|
+
│ Interface Adapter │◄───►│ Clawless │◄───►│ Local Agent. │
|
|
51
|
+
│ (Telegram now) │ │ (Node.js) │ ACP │ e.g. Gemini CLI (default)│
|
|
52
|
+
└──────────────────────┘ └────────────────┘ └──────────────────────────┘
|
|
23
53
|
```
|
|
24
54
|
|
|
25
55
|
The bridge:
|
|
26
|
-
1. Receives messages from Telegram
|
|
27
|
-
2. Forwards them to
|
|
28
|
-
3.
|
|
56
|
+
1. Receives messages from the active interface adapter (Telegram today)
|
|
57
|
+
2. Forwards them to **your configured local agent CLI** via ACP (Agent Communication Protocol)
|
|
58
|
+
3. Sends interface-appropriate progress/status updates, then returns a single final response
|
|
29
59
|
|
|
30
60
|
## Prerequisites
|
|
31
61
|
|
|
32
62
|
- **Node.js** 18.0.0 or higher
|
|
33
|
-
- **
|
|
34
|
-
- **Telegram Bot Token** from [@BotFather](https://t.me/BotFather)
|
|
63
|
+
- **A local ACP-capable agent CLI** installed and configured (Gemini CLI is the default setup)
|
|
64
|
+
- **Telegram Bot Token** from [@BotFather](https://t.me/BotFather) for the current Telegram adapter
|
|
35
65
|
|
|
36
66
|
## Installation
|
|
37
67
|
|
|
@@ -63,6 +93,30 @@ ACP_DEBUG_STREAM=false
|
|
|
63
93
|
4. Copy the token provided by BotFather
|
|
64
94
|
5. Paste it into your `.env` file
|
|
65
95
|
|
|
96
|
+
## Authorizing Users (Whitelist)
|
|
97
|
+
|
|
98
|
+
For security, the bot only accepts commands from authorized users. To configure:
|
|
99
|
+
|
|
100
|
+
1. **Use your Telegram username**:
|
|
101
|
+
- You can use your Telegram username (e.g., `your_username` or `@your_username`).
|
|
102
|
+
- If you don't have a username set, you must create one in Telegram settings.
|
|
103
|
+
|
|
104
|
+
2. **Add usernames to whitelist** in `~/.clawless/config.json`:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"telegramToken": "your_bot_token",
|
|
108
|
+
"telegramWhitelist": ["your_username", "another_user"]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
3. **Alternative: Use environment variable**:
|
|
113
|
+
```bash
|
|
114
|
+
# Must be a valid JSON array string
|
|
115
|
+
TELEGRAM_WHITELIST='["your_username", "another_user"]'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
⚠️ **Security Note**: If `telegramWhitelist` is empty or not configured, **all users will be blocked** by default. This is a safety measure to prevent unauthorized access.
|
|
119
|
+
|
|
66
120
|
## Usage
|
|
67
121
|
|
|
68
122
|
### CLI Mode
|
|
@@ -73,6 +127,8 @@ After install, the package exposes a CLI command:
|
|
|
73
127
|
clawless
|
|
74
128
|
```
|
|
75
129
|
|
|
130
|
+
> Note: the binary name is currently `clawless` for compatibility, while the project name is Clawless.
|
|
131
|
+
|
|
76
132
|
Local development alternatives:
|
|
77
133
|
|
|
78
134
|
```bash
|
|
@@ -178,6 +234,7 @@ pm2 save
|
|
|
178
234
|
| Variable | Required | Default | Description |
|
|
179
235
|
|----------|----------|---------|-------------|
|
|
180
236
|
| `TELEGRAM_TOKEN` | Yes | - | Your Telegram bot token from BotFather |
|
|
237
|
+
| `TELEGRAM_WHITELIST` | No | [] | List of authorized Telegram usernames. **Security:** If empty, all users are blocked by default. Format: JSON array `["username1", "username2"]` |
|
|
181
238
|
| `TYPING_INTERVAL_MS` | No | 4000 | Interval (in milliseconds) for refreshing Telegram typing status |
|
|
182
239
|
| `GEMINI_TIMEOUT_MS` | No | 900000 | Overall timeout for a single Gemini CLI run |
|
|
183
240
|
| `GEMINI_NO_OUTPUT_TIMEOUT_MS` | No | 60000 | Idle timeout; aborts if Gemini emits no output for this duration |
|
|
@@ -193,7 +250,7 @@ pm2 save
|
|
|
193
250
|
| `CALLBACK_PORT` | No | 8788 | Bind port for callback server |
|
|
194
251
|
| `CALLBACK_AUTH_TOKEN` | No | - | Optional bearer/token guard for callback endpoint |
|
|
195
252
|
| `CALLBACK_MAX_BODY_BYTES` | No | 65536 | Maximum accepted callback request body size |
|
|
196
|
-
| `AGENT_BRIDGE_HOME` | No | ~/.clawless | Home directory for
|
|
253
|
+
| `AGENT_BRIDGE_HOME` | No | ~/.clawless | Home directory for Clawless runtime files |
|
|
197
254
|
| `MEMORY_FILE_PATH` | No | ~/.clawless/MEMORY.md | Persistent memory file path injected into Gemini prompt context |
|
|
198
255
|
| `MEMORY_MAX_CHARS` | No | 12000 | Max memory-file characters injected into prompt context |
|
|
199
256
|
| `SCHEDULES_FILE_PATH` | No | ~/.clawless/schedules.json | Persistent scheduler storage file |
|
|
@@ -229,7 +286,7 @@ curl -sS -X POST "http://127.0.0.1:8788/callback/telegram" \
|
|
|
229
286
|
|
|
230
287
|
### Scheduler API
|
|
231
288
|
|
|
232
|
-
The bridge includes a built-in cron scheduler that allows you to schedule tasks to be executed through
|
|
289
|
+
The bridge includes a built-in cron scheduler that allows you to schedule tasks to be executed through your configured local agent CLI:
|
|
233
290
|
|
|
234
291
|
- Schedules are persisted to disk and automatically reloaded on restart.
|
|
235
292
|
- Default storage path: `~/.clawless/schedules.json` (override with `SCHEDULES_FILE_PATH`).
|
|
@@ -256,7 +313,7 @@ curl -X POST http://127.0.0.1:8788/api/schedule \
|
|
|
256
313
|
}'
|
|
257
314
|
```
|
|
258
315
|
|
|
259
|
-
When a scheduled job runs, it executes the message through
|
|
316
|
+
When a scheduled job runs, it executes the message through your configured local agent runtime and sends the response to your Telegram chat.
|
|
260
317
|
|
|
261
318
|
**Ask Gemini to create schedules naturally:**
|
|
262
319
|
- "Remind me to take a break in 30 minutes"
|
|
@@ -268,10 +325,10 @@ See [SCHEDULER.md](SCHEDULER.md) for complete API documentation.
|
|
|
268
325
|
### Persistent Memory File
|
|
269
326
|
|
|
270
327
|
- The bridge ensures a memory file exists at `~/.clawless/MEMORY.md` on startup.
|
|
271
|
-
-
|
|
328
|
+
- The configured local agent CLI is started with include access to both `~/.clawless` and your full home directory (`~/`).
|
|
272
329
|
- ACP session setup uses the required `mcpServers` field with an empty array and relies on Gemini CLI runtime defaults for MCP/skills loading.
|
|
273
330
|
- Each prompt includes memory instructions and current `MEMORY.md` content.
|
|
274
|
-
- When asked to memorize/remember something,
|
|
331
|
+
- When asked to memorize/remember something, the agent is instructed to append new notes under `## Notes`.
|
|
275
332
|
|
|
276
333
|
### Timeout Tuning
|
|
277
334
|
|
|
@@ -296,28 +353,31 @@ The `MAX_RESPONSE_LENGTH` prevents memory issues with very long responses:
|
|
|
296
353
|
1. **User sends a message** via Telegram
|
|
297
354
|
2. **Bridge queues** the message if another request is in progress
|
|
298
355
|
3. **Worker dequeues** the next message when prior processing completes
|
|
299
|
-
4. **
|
|
300
|
-
5. **Single final reply** is sent when
|
|
356
|
+
4. **Agent run starts** and typing status is shown in Telegram
|
|
357
|
+
5. **Single final reply** is sent when the run finishes
|
|
301
358
|
|
|
302
359
|
### Queueing Behavior
|
|
303
360
|
|
|
304
361
|
The bridge uses a single-worker in-memory queue:
|
|
305
|
-
- Prevents overlapping
|
|
362
|
+
- Prevents overlapping agent runs
|
|
306
363
|
- Preserves message order
|
|
307
364
|
- Avoids duplicate-edit/fallback races from message updates
|
|
308
365
|
|
|
309
366
|
## Advantages Over Standard API Bots
|
|
310
367
|
|
|
311
|
-
1. **
|
|
312
|
-
2. **
|
|
313
|
-
3. **
|
|
314
|
-
4. **
|
|
315
|
-
5. **
|
|
368
|
+
1. **BYO-Agent Flexibility**: Keep the same bridge while choosing or changing your local CLI runtime
|
|
369
|
+
2. **Persistent Context**: The local agent CLI maintains a local session, unlike stateless API calls
|
|
370
|
+
3. **Local File Access**: Can access files on your server if configured
|
|
371
|
+
4. **MCP Tool Integration**: Uses tools from connected MCP servers (Calendar, Database, etc.)
|
|
372
|
+
5. **Privacy Control**: Runs on your hardware, you control data processing
|
|
373
|
+
6. **Custom Configuration**: Use your specific local CLI setup and preferences
|
|
316
374
|
|
|
317
375
|
## Troubleshooting
|
|
318
376
|
|
|
319
377
|
### Bot doesn't respond
|
|
320
378
|
|
|
379
|
+
For the default Gemini CLI setup:
|
|
380
|
+
|
|
321
381
|
1. Check if Gemini CLI is installed:
|
|
322
382
|
```bash
|
|
323
383
|
which gemini
|
|
@@ -347,22 +407,29 @@ If you see "429 Too Many Requests" errors:
|
|
|
347
407
|
### Project Structure
|
|
348
408
|
|
|
349
409
|
```
|
|
350
|
-
|
|
351
|
-
├── index.ts
|
|
410
|
+
Clawless/
|
|
411
|
+
├── index.ts # Main bridge application
|
|
412
|
+
├── bin/
|
|
413
|
+
│ └── cli.ts # CLI entrypoint
|
|
352
414
|
├── messaging/
|
|
353
|
-
│ └── telegramClient.ts
|
|
354
|
-
├──
|
|
355
|
-
├──
|
|
356
|
-
|
|
357
|
-
├──
|
|
358
|
-
|
|
415
|
+
│ └── telegramClient.ts # Telegram adapter
|
|
416
|
+
├── scheduler/
|
|
417
|
+
│ ├── cronScheduler.ts # Schedule persistence + cron orchestration
|
|
418
|
+
│ └── scheduledJobHandler.ts # Scheduled run execution logic
|
|
419
|
+
├── acp/
|
|
420
|
+
│ ├── tempAcpRunner.ts # Isolated ACP run helper
|
|
421
|
+
│ └── clientHelpers.ts # ACP helper utilities
|
|
422
|
+
├── package.json # Node.js dependencies
|
|
423
|
+
├── ecosystem.config.json # PM2 configuration
|
|
424
|
+
├── clawless.config.example.json # CLI config template
|
|
425
|
+
└── README.md # This file
|
|
359
426
|
```
|
|
360
427
|
|
|
361
428
|
### Adding Features
|
|
362
429
|
|
|
363
430
|
The codebase is designed to be simple and extensible:
|
|
364
431
|
- Core queue + ACP logic is in `index.ts`
|
|
365
|
-
-
|
|
432
|
+
- Interface-specific messaging logic lives in `messaging/telegramClient.ts`
|
|
366
433
|
- New bot platforms can implement the same message context shape (`text`, `startTyping()`, `sendText()`)
|
|
367
434
|
- Error handling is centralized
|
|
368
435
|
- Rate limiting logic is configurable
|
|
@@ -372,7 +439,7 @@ The codebase is designed to be simple and extensible:
|
|
|
372
439
|
- **Never commit** `.env` file with your token (it's in `.gitignore`)
|
|
373
440
|
- **Rotate tokens** if accidentally exposed
|
|
374
441
|
- **Limit bot access** using Telegram's bot settings
|
|
375
|
-
- **Monitor logs** for unusual activity
|
|
442
|
+
- **Monitor logs** for unusual activity and unauthorized access attempts
|
|
376
443
|
|
|
377
444
|
## Contributing
|
|
378
445
|
|
|
@@ -401,4 +468,4 @@ For issues and questions:
|
|
|
401
468
|
|
|
402
469
|
---
|
|
403
470
|
|
|
404
|
-
**Note**: This bridge requires a working Gemini CLI
|
|
471
|
+
**Note**: This bridge requires a working local ACP-capable CLI (Gemini CLI is the default setup). Ensure your CLI is properly configured before running the bridge.
|
package/SCHEDULER.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Scheduler API Documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Clawless includes a cron scheduler API that allows you to schedule tasks to be executed through your local agent CLI at specific times or on a recurring basis (Gemini CLI by default).
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
7
|
When a scheduled job runs:
|
|
8
|
-
1. The scheduled message is sent to a standalone
|
|
9
|
-
2.
|
|
10
|
-
3. The response is sent back
|
|
11
|
-
4.
|
|
8
|
+
1. The scheduled message is sent to a standalone local agent CLI session
|
|
9
|
+
2. The agent processes the message and generates a response
|
|
10
|
+
3. The response is sent back through the active interface adapter
|
|
11
|
+
4. With the current Telegram adapter, the result appears in your Telegram chat
|
|
12
12
|
|
|
13
13
|
Schedules are persisted to disk and reloaded on startup. By default the file is `~/.clawless/schedules.json` and can be overridden via `SCHEDULES_FILE_PATH`.
|
|
14
14
|
|
|
@@ -43,7 +43,7 @@ curl -X POST http://127.0.0.1:8788/api/schedule \
|
|
|
43
43
|
**Request Body:**
|
|
44
44
|
```json
|
|
45
45
|
{
|
|
46
|
-
"message": "The prompt to send to
|
|
46
|
+
"message": "The prompt to send to your local agent CLI",
|
|
47
47
|
"description": "Optional description of the schedule",
|
|
48
48
|
"cronExpression": "0 9 * * *"
|
|
49
49
|
}
|
|
@@ -83,7 +83,7 @@ curl -X POST http://127.0.0.1:8788/api/schedule \
|
|
|
83
83
|
**Request Body:**
|
|
84
84
|
```json
|
|
85
85
|
{
|
|
86
|
-
"message": "The prompt to send to
|
|
86
|
+
"message": "The prompt to send to your local agent CLI",
|
|
87
87
|
"description": "Optional description",
|
|
88
88
|
"oneTime": true,
|
|
89
89
|
"runAt": "2026-02-13T15:30:00Z"
|
|
@@ -180,39 +180,39 @@ curl -X DELETE http://127.0.0.1:8788/api/schedule/schedule_1707835800000_abc123
|
|
|
180
180
|
}
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
## Using with
|
|
183
|
+
## Using with Your Local CLI (Default: Gemini)
|
|
184
184
|
|
|
185
|
-
The
|
|
185
|
+
The configured local CLI is aware of the scheduler API through the system prompt. With the default setup, you can ask Gemini to create schedules naturally:
|
|
186
186
|
|
|
187
187
|
**Examples:**
|
|
188
188
|
|
|
189
189
|
1. **"Remind me to take a break in 30 minutes"**
|
|
190
|
-
|
|
190
|
+
- The agent will create a one-time schedule
|
|
191
191
|
|
|
192
192
|
2. **"Check my calendar every morning at 9am and send me a summary"**
|
|
193
|
-
|
|
193
|
+
- The agent will create a recurring schedule with cron expression `0 9 * * *`
|
|
194
194
|
|
|
195
195
|
3. **"Every Friday at 5pm, remind me to review my weekly goals"**
|
|
196
|
-
|
|
196
|
+
- The agent will create a recurring schedule with cron expression `0 17 * * 5`
|
|
197
197
|
|
|
198
198
|
4. **"List my scheduled tasks"**
|
|
199
|
-
|
|
199
|
+
- The agent will query the schedule API and show you all active schedules
|
|
200
200
|
|
|
201
201
|
5. **"Cancel the calendar summary schedule"**
|
|
202
|
-
|
|
202
|
+
- The agent will find and delete the matching schedule
|
|
203
203
|
|
|
204
204
|
## How It Works
|
|
205
205
|
|
|
206
|
-
1. When you ask
|
|
206
|
+
1. When you ask the agent to create a schedule, it will:
|
|
207
207
|
- Parse your request to determine timing (cron expression or specific date/time)
|
|
208
208
|
- Call the scheduler API with appropriate parameters
|
|
209
209
|
- Confirm the schedule was created
|
|
210
210
|
|
|
211
211
|
2. When the scheduled time arrives:
|
|
212
212
|
- The scheduler executes the job
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
- The message is sent to a new local CLI session
|
|
214
|
+
- The agent processes the message (can use tools, access files, etc.)
|
|
215
|
+
- The response is sent to the active interface destination (Telegram with current adapter)
|
|
216
216
|
|
|
217
217
|
3. For recurring schedules:
|
|
218
218
|
- The job runs according to the cron expression
|
|
@@ -226,9 +226,9 @@ The Gemini CLI is aware of the scheduler API through the system prompt. You can
|
|
|
226
226
|
## Notes
|
|
227
227
|
|
|
228
228
|
- Schedules are stored in memory and will be lost if the bridge restarts
|
|
229
|
-
-
|
|
229
|
+
- For the current Telegram adapter, make sure your bot has received at least one message so it knows where to send results
|
|
230
230
|
- The timezone used for cron schedules is determined by the `TZ` environment variable (defaults to UTC)
|
|
231
|
-
- Scheduled jobs run in separate
|
|
231
|
+
- Scheduled jobs run in separate local CLI sessions, so they have access to all configured tools and MCP servers
|
|
232
232
|
|
|
233
233
|
## Troubleshooting
|
|
234
234
|
|
|
@@ -237,7 +237,7 @@ The Gemini CLI is aware of the scheduler API through the system prompt. You can
|
|
|
237
237
|
- Verify the cron expression is valid using a cron expression tester
|
|
238
238
|
- Ensure the bridge is running continuously
|
|
239
239
|
|
|
240
|
-
### Results not appearing in Telegram
|
|
240
|
+
### Results not appearing in Telegram (current adapter)
|
|
241
241
|
- Send at least one message to your bot first to establish the chat binding
|
|
242
242
|
- Check if `lastIncomingChatId` is set in the logs
|
|
243
243
|
|
package/bin/cli.ts
CHANGED
|
@@ -7,6 +7,7 @@ import process from 'node:process';
|
|
|
7
7
|
|
|
8
8
|
const ENV_KEY_MAP: Record<string, string> = {
|
|
9
9
|
telegramToken: 'TELEGRAM_TOKEN',
|
|
10
|
+
telegramWhitelist: 'TELEGRAM_WHITELIST',
|
|
10
11
|
typingIntervalMs: 'TYPING_INTERVAL_MS',
|
|
11
12
|
geminiCommand: 'GEMINI_COMMAND',
|
|
12
13
|
geminiApprovalMode: 'GEMINI_APPROVAL_MODE',
|
|
@@ -23,7 +24,7 @@ const ENV_KEY_MAP: Record<string, string> = {
|
|
|
23
24
|
callbackPort: 'CALLBACK_PORT',
|
|
24
25
|
callbackAuthToken: 'CALLBACK_AUTH_TOKEN',
|
|
25
26
|
callbackMaxBodyBytes: 'CALLBACK_MAX_BODY_BYTES',
|
|
26
|
-
|
|
27
|
+
ClawlessHome: 'AGENT_BRIDGE_HOME',
|
|
27
28
|
memoryFilePath: 'MEMORY_FILE_PATH',
|
|
28
29
|
memoryMaxChars: 'MEMORY_MAX_CHARS',
|
|
29
30
|
schedulesFilePath: 'SCHEDULES_FILE_PATH',
|
|
@@ -34,6 +35,7 @@ const DEFAULT_AGENT_BRIDGE_HOME = path.join(os.homedir(), '.clawless');
|
|
|
34
35
|
const DEFAULT_MEMORY_FILE_PATH = path.join(DEFAULT_AGENT_BRIDGE_HOME, 'MEMORY.md');
|
|
35
36
|
const DEFAULT_CONFIG_TEMPLATE = {
|
|
36
37
|
telegramToken: 'your_telegram_bot_token_here',
|
|
38
|
+
telegramWhitelist: [],
|
|
37
39
|
typingIntervalMs: 4000,
|
|
38
40
|
geminiCommand: 'gemini',
|
|
39
41
|
geminiApprovalMode: 'yolo',
|
|
@@ -50,7 +52,7 @@ const DEFAULT_CONFIG_TEMPLATE = {
|
|
|
50
52
|
callbackPort: 8788,
|
|
51
53
|
callbackAuthToken: '',
|
|
52
54
|
callbackMaxBodyBytes: 65536,
|
|
53
|
-
|
|
55
|
+
ClawlessHome: '~/.clawless',
|
|
54
56
|
memoryFilePath: '~/.clawless/MEMORY.md',
|
|
55
57
|
memoryMaxChars: 12000,
|
|
56
58
|
schedulesFilePath: '~/.clawless/schedules.json',
|
|
@@ -109,6 +111,9 @@ function toEnvValue(value: unknown) {
|
|
|
109
111
|
if (typeof value === 'string') {
|
|
110
112
|
return value;
|
|
111
113
|
}
|
|
114
|
+
if (typeof value === 'object') {
|
|
115
|
+
return JSON.stringify(value);
|
|
116
|
+
}
|
|
112
117
|
return String(value);
|
|
113
118
|
}
|
|
114
119
|
|
package/dist/bin/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import process from 'node:process';
|
|
6
6
|
const ENV_KEY_MAP = {
|
|
7
7
|
telegramToken: 'TELEGRAM_TOKEN',
|
|
8
|
+
telegramWhitelist: 'TELEGRAM_WHITELIST',
|
|
8
9
|
typingIntervalMs: 'TYPING_INTERVAL_MS',
|
|
9
10
|
geminiCommand: 'GEMINI_COMMAND',
|
|
10
11
|
geminiApprovalMode: 'GEMINI_APPROVAL_MODE',
|
|
@@ -21,7 +22,7 @@ const ENV_KEY_MAP = {
|
|
|
21
22
|
callbackPort: 'CALLBACK_PORT',
|
|
22
23
|
callbackAuthToken: 'CALLBACK_AUTH_TOKEN',
|
|
23
24
|
callbackMaxBodyBytes: 'CALLBACK_MAX_BODY_BYTES',
|
|
24
|
-
|
|
25
|
+
ClawlessHome: 'AGENT_BRIDGE_HOME',
|
|
25
26
|
memoryFilePath: 'MEMORY_FILE_PATH',
|
|
26
27
|
memoryMaxChars: 'MEMORY_MAX_CHARS',
|
|
27
28
|
schedulesFilePath: 'SCHEDULES_FILE_PATH',
|
|
@@ -31,6 +32,7 @@ const DEFAULT_AGENT_BRIDGE_HOME = path.join(os.homedir(), '.clawless');
|
|
|
31
32
|
const DEFAULT_MEMORY_FILE_PATH = path.join(DEFAULT_AGENT_BRIDGE_HOME, 'MEMORY.md');
|
|
32
33
|
const DEFAULT_CONFIG_TEMPLATE = {
|
|
33
34
|
telegramToken: 'your_telegram_bot_token_here',
|
|
35
|
+
telegramWhitelist: [],
|
|
34
36
|
typingIntervalMs: 4000,
|
|
35
37
|
geminiCommand: 'gemini',
|
|
36
38
|
geminiApprovalMode: 'yolo',
|
|
@@ -47,7 +49,7 @@ const DEFAULT_CONFIG_TEMPLATE = {
|
|
|
47
49
|
callbackPort: 8788,
|
|
48
50
|
callbackAuthToken: '',
|
|
49
51
|
callbackMaxBodyBytes: 65536,
|
|
50
|
-
|
|
52
|
+
ClawlessHome: '~/.clawless',
|
|
51
53
|
memoryFilePath: '~/.clawless/MEMORY.md',
|
|
52
54
|
memoryMaxChars: 12000,
|
|
53
55
|
schedulesFilePath: '~/.clawless/schedules.json',
|
|
@@ -98,6 +100,9 @@ function toEnvValue(value) {
|
|
|
98
100
|
if (typeof value === 'string') {
|
|
99
101
|
return value;
|
|
100
102
|
}
|
|
103
|
+
if (typeof value === 'object') {
|
|
104
|
+
return JSON.stringify(value);
|
|
105
|
+
}
|
|
101
106
|
return String(value);
|
|
102
107
|
}
|
|
103
108
|
function resolveEnvKey(configKey) {
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,35 @@ const TYPING_INTERVAL_MS = parseInt(process.env.TYPING_INTERVAL_MS || '4000', 10
|
|
|
48
48
|
const TELEGRAM_STREAM_UPDATE_INTERVAL_MS = 1000;
|
|
49
49
|
// Maximum response length to prevent memory issues (Telegram has 4096 char limit anyway)
|
|
50
50
|
const MAX_RESPONSE_LENGTH = parseInt(process.env.MAX_RESPONSE_LENGTH || '4000', 10);
|
|
51
|
+
// Parse Telegram whitelist from environment variable
|
|
52
|
+
// Expected format: JSON array of usernames (e.g., ["user1", "user2"])
|
|
53
|
+
function parseWhitelistFromEnv(envValue) {
|
|
54
|
+
if (!envValue || envValue.trim() === '') {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(envValue);
|
|
59
|
+
if (Array.isArray(parsed)) {
|
|
60
|
+
return parsed.map((name) => String(name).trim().replace(/^@/, '')).filter(Boolean);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
console.warn('Warning: TELEGRAM_WHITELIST must be a valid JSON array of usernames (e.g., ["user1", "user2"])');
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const TELEGRAM_WHITELIST = parseWhitelistFromEnv(process.env.TELEGRAM_WHITELIST || '');
|
|
69
|
+
function isUserAuthorized(username) {
|
|
70
|
+
// If whitelist is empty, block all users by default (safe default)
|
|
71
|
+
if (TELEGRAM_WHITELIST.length === 0) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (!username) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const normalizedUsername = username.toLowerCase();
|
|
78
|
+
return TELEGRAM_WHITELIST.some(entry => entry.toLowerCase() === normalizedUsername);
|
|
79
|
+
}
|
|
51
80
|
const messagingClient = new TelegramMessagingClient({
|
|
52
81
|
token: process.env.TELEGRAM_TOKEN,
|
|
53
82
|
typingIntervalMs: TYPING_INTERVAL_MS,
|
|
@@ -68,7 +97,7 @@ const GEMINI_STDERR_TAIL_MAX = 4000;
|
|
|
68
97
|
function validateGeminiCommandOrExit() {
|
|
69
98
|
const result = spawnSync(GEMINI_COMMAND, ['--version'], {
|
|
70
99
|
stdio: 'ignore',
|
|
71
|
-
timeout:
|
|
100
|
+
timeout: 10000,
|
|
72
101
|
killSignal: 'SIGKILL',
|
|
73
102
|
});
|
|
74
103
|
if (result.error?.code === 'ENOENT') {
|
|
@@ -492,9 +521,9 @@ function ensureMemoryFile() {
|
|
|
492
521
|
fs.mkdirSync(path.dirname(MEMORY_FILE_PATH), { recursive: true });
|
|
493
522
|
if (!fs.existsSync(MEMORY_FILE_PATH)) {
|
|
494
523
|
const template = [
|
|
495
|
-
'#
|
|
524
|
+
'# Clawless Memory',
|
|
496
525
|
'',
|
|
497
|
-
'This file stores durable memory notes for
|
|
526
|
+
'This file stores durable memory notes for Clawless.',
|
|
498
527
|
'',
|
|
499
528
|
'## Notes',
|
|
500
529
|
'',
|
|
@@ -1034,6 +1063,12 @@ async function processSingleMessage(messageContext, messageRequestId) {
|
|
|
1034
1063
|
* Handles incoming text messages from Telegram
|
|
1035
1064
|
*/
|
|
1036
1065
|
messagingClient.onTextMessage(async (messageContext) => {
|
|
1066
|
+
// Check if user is authorized
|
|
1067
|
+
if (!isUserAuthorized(messageContext.username)) {
|
|
1068
|
+
console.warn(`Unauthorized access attempt from username: ${messageContext.username ?? 'none'} (ID: ${messageContext.userId ?? 'unknown'})`);
|
|
1069
|
+
await messageContext.sendText('🚫 Unauthorized. This bot is restricted to authorized users only.');
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1037
1072
|
if (messageContext.chatId !== undefined && messageContext.chatId !== null) {
|
|
1038
1073
|
lastIncomingChatId = String(messageContext.chatId);
|
|
1039
1074
|
persistCallbackChatId(lastIncomingChatId);
|
|
@@ -1092,7 +1127,15 @@ messagingClient.launch()
|
|
|
1092
1127
|
callbackPort: CALLBACK_PORT,
|
|
1093
1128
|
mcpSkillsSource: 'local Gemini CLI defaults (no MCP override)',
|
|
1094
1129
|
acpMode: `${GEMINI_COMMAND} --experimental-acp`,
|
|
1130
|
+
telegramWhitelist: TELEGRAM_WHITELIST.length > 0 ? `${TELEGRAM_WHITELIST.length} user(s) authorized` : 'NONE (all users blocked)',
|
|
1095
1131
|
});
|
|
1132
|
+
if (TELEGRAM_WHITELIST.length === 0) {
|
|
1133
|
+
console.warn('⚠️ WARNING: Telegram whitelist is empty. All users will be blocked.');
|
|
1134
|
+
console.warn('⚠️ Add usernames to TELEGRAM_WHITELIST config (as a JSON array) to authorize users.');
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
console.log(`✅ Telegram authorization enabled. Authorized usernames: ${TELEGRAM_WHITELIST.join(', ')}`);
|
|
1138
|
+
}
|
|
1096
1139
|
scheduleAcpPrewarm('post-launch');
|
|
1097
1140
|
if (HEARTBEAT_INTERVAL_MS > 0) {
|
|
1098
1141
|
setInterval(() => {
|
|
@@ -22,12 +22,16 @@ class TelegramMessageContext {
|
|
|
22
22
|
maxMessageLength;
|
|
23
23
|
text;
|
|
24
24
|
chatId;
|
|
25
|
+
userId;
|
|
26
|
+
username;
|
|
25
27
|
constructor(ctx, typingIntervalMs, maxMessageLength) {
|
|
26
28
|
this.ctx = ctx;
|
|
27
29
|
this.typingIntervalMs = typingIntervalMs;
|
|
28
30
|
this.maxMessageLength = maxMessageLength;
|
|
29
31
|
this.text = ctx.message?.text || '';
|
|
30
32
|
this.chatId = ctx.chat?.id;
|
|
33
|
+
this.userId = ctx.from?.id;
|
|
34
|
+
this.username = ctx.from?.username;
|
|
31
35
|
}
|
|
32
36
|
startTyping() {
|
|
33
37
|
this.ctx.telegram.sendChatAction(this.ctx.chat.id, 'typing').catch(() => { });
|
package/index.ts
CHANGED
|
@@ -55,6 +55,42 @@ const TELEGRAM_STREAM_UPDATE_INTERVAL_MS = 1000;
|
|
|
55
55
|
// Maximum response length to prevent memory issues (Telegram has 4096 char limit anyway)
|
|
56
56
|
const MAX_RESPONSE_LENGTH = parseInt(process.env.MAX_RESPONSE_LENGTH || '4000', 10);
|
|
57
57
|
|
|
58
|
+
// Parse Telegram whitelist from environment variable
|
|
59
|
+
// Expected format: JSON array of usernames (e.g., ["user1", "user2"])
|
|
60
|
+
function parseWhitelistFromEnv(envValue: string): string[] {
|
|
61
|
+
if (!envValue || envValue.trim() === '') {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(envValue);
|
|
67
|
+
if (Array.isArray(parsed)) {
|
|
68
|
+
return parsed.map((name) => String(name).trim().replace(/^@/, '')).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
console.warn('Warning: TELEGRAM_WHITELIST must be a valid JSON array of usernames (e.g., ["user1", "user2"])');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const TELEGRAM_WHITELIST: string[] = parseWhitelistFromEnv(process.env.TELEGRAM_WHITELIST || '');
|
|
78
|
+
|
|
79
|
+
function isUserAuthorized(username: string | undefined): boolean {
|
|
80
|
+
// If whitelist is empty, block all users by default (safe default)
|
|
81
|
+
if (TELEGRAM_WHITELIST.length === 0) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!username) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const normalizedUsername = username.toLowerCase();
|
|
90
|
+
|
|
91
|
+
return TELEGRAM_WHITELIST.some(entry => entry.toLowerCase() === normalizedUsername);
|
|
92
|
+
}
|
|
93
|
+
|
|
58
94
|
const messagingClient = new TelegramMessagingClient({
|
|
59
95
|
token: process.env.TELEGRAM_TOKEN,
|
|
60
96
|
typingIntervalMs: TYPING_INTERVAL_MS,
|
|
@@ -77,7 +113,7 @@ const GEMINI_STDERR_TAIL_MAX = 4000;
|
|
|
77
113
|
function validateGeminiCommandOrExit() {
|
|
78
114
|
const result = spawnSync(GEMINI_COMMAND, ['--version'], {
|
|
79
115
|
stdio: 'ignore',
|
|
80
|
-
timeout:
|
|
116
|
+
timeout: 10000,
|
|
81
117
|
killSignal: 'SIGKILL',
|
|
82
118
|
});
|
|
83
119
|
|
|
@@ -576,9 +612,9 @@ function ensureMemoryFile() {
|
|
|
576
612
|
|
|
577
613
|
if (!fs.existsSync(MEMORY_FILE_PATH)) {
|
|
578
614
|
const template = [
|
|
579
|
-
'#
|
|
615
|
+
'# Clawless Memory',
|
|
580
616
|
'',
|
|
581
|
-
'This file stores durable memory notes for
|
|
617
|
+
'This file stores durable memory notes for Clawless.',
|
|
582
618
|
'',
|
|
583
619
|
'## Notes',
|
|
584
620
|
'',
|
|
@@ -1182,6 +1218,13 @@ async function processSingleMessage(messageContext: any, messageRequestId: numbe
|
|
|
1182
1218
|
* Handles incoming text messages from Telegram
|
|
1183
1219
|
*/
|
|
1184
1220
|
messagingClient.onTextMessage(async (messageContext) => {
|
|
1221
|
+
// Check if user is authorized
|
|
1222
|
+
if (!isUserAuthorized(messageContext.username)) {
|
|
1223
|
+
console.warn(`Unauthorized access attempt from username: ${messageContext.username ?? 'none'} (ID: ${messageContext.userId ?? 'unknown'})`);
|
|
1224
|
+
await messageContext.sendText('🚫 Unauthorized. This bot is restricted to authorized users only.');
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1185
1228
|
if (messageContext.chatId !== undefined && messageContext.chatId !== null) {
|
|
1186
1229
|
lastIncomingChatId = String(messageContext.chatId);
|
|
1187
1230
|
persistCallbackChatId(lastIncomingChatId);
|
|
@@ -1246,8 +1289,16 @@ messagingClient.launch()
|
|
|
1246
1289
|
callbackPort: CALLBACK_PORT,
|
|
1247
1290
|
mcpSkillsSource: 'local Gemini CLI defaults (no MCP override)',
|
|
1248
1291
|
acpMode: `${GEMINI_COMMAND} --experimental-acp`,
|
|
1292
|
+
telegramWhitelist: TELEGRAM_WHITELIST.length > 0 ? `${TELEGRAM_WHITELIST.length} user(s) authorized` : 'NONE (all users blocked)',
|
|
1249
1293
|
});
|
|
1250
1294
|
|
|
1295
|
+
if (TELEGRAM_WHITELIST.length === 0) {
|
|
1296
|
+
console.warn('⚠️ WARNING: Telegram whitelist is empty. All users will be blocked.');
|
|
1297
|
+
console.warn('⚠️ Add usernames to TELEGRAM_WHITELIST config (as a JSON array) to authorize users.');
|
|
1298
|
+
} else {
|
|
1299
|
+
console.log(`✅ Telegram authorization enabled. Authorized usernames: ${TELEGRAM_WHITELIST.join(', ')}`);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1251
1302
|
scheduleAcpPrewarm('post-launch');
|
|
1252
1303
|
|
|
1253
1304
|
if (HEARTBEAT_INTERVAL_MS > 0) {
|
|
@@ -28,6 +28,8 @@ class TelegramMessageContext {
|
|
|
28
28
|
maxMessageLength: number;
|
|
29
29
|
text: string;
|
|
30
30
|
chatId: string | number | undefined;
|
|
31
|
+
userId: number | undefined;
|
|
32
|
+
username: string | undefined;
|
|
31
33
|
|
|
32
34
|
constructor(ctx: any, typingIntervalMs: number, maxMessageLength: number) {
|
|
33
35
|
this.ctx = ctx;
|
|
@@ -35,6 +37,8 @@ class TelegramMessageContext {
|
|
|
35
37
|
this.maxMessageLength = maxMessageLength;
|
|
36
38
|
this.text = ctx.message?.text || '';
|
|
37
39
|
this.chatId = ctx.chat?.id;
|
|
40
|
+
this.userId = ctx.from?.id;
|
|
41
|
+
this.username = ctx.from?.username;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
startTyping() {
|
package/package.json
CHANGED
package/QUICKSTART-SCHEDULER.md
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# Quick Start Guide: Scheduler Feature
|
|
2
|
-
|
|
3
|
-
This guide will help you start using the scheduler feature in under 5 minutes.
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
1. Clawless is installed and configured
|
|
8
|
-
2. You have sent at least one message to your bot (to establish chat binding)
|
|
9
|
-
3. The bridge is running (`npm run dev` or `npm start`)
|
|
10
|
-
|
|
11
|
-
## Basic Usage
|
|
12
|
-
|
|
13
|
-
### Talk to Gemini Naturally
|
|
14
|
-
|
|
15
|
-
The easiest way to use the scheduler is to just ask Gemini:
|
|
16
|
-
|
|
17
|
-
**Examples:**
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
You: "Remind me to take a break in 30 minutes"
|
|
21
|
-
|
|
22
|
-
You: "Check my calendar every morning at 9am and send me a summary"
|
|
23
|
-
|
|
24
|
-
You: "Every Friday at 5pm, remind me to review my weekly goals"
|
|
25
|
-
|
|
26
|
-
You: "What schedules do I have?"
|
|
27
|
-
|
|
28
|
-
You: "Cancel the daily calendar check"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Gemini will handle all the API calls automatically!
|
|
32
|
-
|
|
33
|
-
## Direct API Usage
|
|
34
|
-
|
|
35
|
-
### 1. Create a Recurring Schedule
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
curl -X POST http://127.0.0.1:8788/api/schedule \
|
|
39
|
-
-H "Content-Type: application/json" \
|
|
40
|
-
-d '{
|
|
41
|
-
"message": "What time is it?",
|
|
42
|
-
"description": "Time check",
|
|
43
|
-
"cronExpression": "0 9 * * *"
|
|
44
|
-
}'
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Response:**
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"ok": true,
|
|
51
|
-
"schedule": {
|
|
52
|
-
"id": "schedule_1707835800000_abc123",
|
|
53
|
-
"message": "What time is it?",
|
|
54
|
-
"description": "Time check",
|
|
55
|
-
"cronExpression": "0 9 * * *",
|
|
56
|
-
"oneTime": false,
|
|
57
|
-
"active": true,
|
|
58
|
-
"createdAt": "2026-02-13T10:00:00.000Z"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 2. Create a One-Time Schedule
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
# Schedule for 30 seconds from now
|
|
67
|
-
if ! RUN_AT=$(date -u -d "+30 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null); then
|
|
68
|
-
# Fallback for macOS/BSD date
|
|
69
|
-
RUN_AT=$(date -u -v+30S +"%Y-%m-%dT%H:%M:%SZ")
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
curl -X POST http://127.0.0.1:8788/api/schedule \
|
|
73
|
-
-H "Content-Type: application/json" \
|
|
74
|
-
-d "{
|
|
75
|
-
\"message\": \"Test reminder\",
|
|
76
|
-
\"oneTime\": true,
|
|
77
|
-
\"runAt\": \"${RUN_AT}\"
|
|
78
|
-
}"
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### 3. List All Schedules
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
curl http://127.0.0.1:8788/api/schedule | jq .
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 4. Delete a Schedule
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
curl -X DELETE http://127.0.0.1:8788/api/schedule/SCHEDULE_ID
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Common Cron Expressions
|
|
94
|
-
|
|
95
|
-
| Expression | Meaning |
|
|
96
|
-
|------------|---------|
|
|
97
|
-
| `0 9 * * *` | Every day at 9:00 AM |
|
|
98
|
-
| `0 */6 * * *` | Every 6 hours |
|
|
99
|
-
| `*/30 * * * *` | Every 30 minutes |
|
|
100
|
-
| `0 9 * * 1-5` | Weekdays at 9:00 AM |
|
|
101
|
-
| `0 17 * * 5` | Every Friday at 5:00 PM |
|
|
102
|
-
| `0 0 1 * *` | First day of month at midnight |
|
|
103
|
-
|
|
104
|
-
**Cron format:** `minute hour day month weekday`
|
|
105
|
-
|
|
106
|
-
## What Happens When a Job Runs?
|
|
107
|
-
|
|
108
|
-
1. **Scheduler triggers** at the scheduled time
|
|
109
|
-
2. **Message is sent** to a new Gemini CLI session
|
|
110
|
-
3. **Gemini processes** the message (can use tools, files, etc.)
|
|
111
|
-
4. **Response is sent** to your Telegram chat automatically
|
|
112
|
-
|
|
113
|
-
Example Telegram message you'll receive:
|
|
114
|
-
```
|
|
115
|
-
🔔 Scheduled task completed:
|
|
116
|
-
|
|
117
|
-
Daily calendar summary
|
|
118
|
-
|
|
119
|
-
Today's events:
|
|
120
|
-
- 9:00 AM: Team standup
|
|
121
|
-
- 2:00 PM: Project review
|
|
122
|
-
- 4:30 PM: 1-on-1 with manager
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Testing Your First Schedule
|
|
126
|
-
|
|
127
|
-
1. **Start the bridge:**
|
|
128
|
-
```bash
|
|
129
|
-
npm run dev
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
2. **Create a test schedule (runs in 30 seconds):**
|
|
133
|
-
```bash
|
|
134
|
-
if ! RUN_AT=$(date -u -d "+30 seconds" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null); then
|
|
135
|
-
# Fallback for macOS/BSD date
|
|
136
|
-
RUN_AT=$(date -u -v+30S +"%Y-%m-%dT%H:%M:%SZ")
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
curl -X POST http://127.0.0.1:8788/api/schedule \
|
|
140
|
-
-H "Content-Type: application/json" \
|
|
141
|
-
-d "{
|
|
142
|
-
\"message\": \"Hello! This is a test scheduled message.\",
|
|
143
|
-
\"description\": \"Test\",
|
|
144
|
-
\"oneTime\": true,
|
|
145
|
-
\"runAt\": \"${RUN_AT}\"
|
|
146
|
-
}"
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
3. **Wait 30 seconds** - You should receive a message in Telegram!
|
|
150
|
-
|
|
151
|
-
4. **Or ask Gemini directly:**
|
|
152
|
-
```
|
|
153
|
-
Send me a test message in 30 seconds
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## With Authentication
|
|
157
|
-
|
|
158
|
-
If you have `CALLBACK_AUTH_TOKEN` set in your config:
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
curl -X POST http://127.0.0.1:8788/api/schedule \
|
|
162
|
-
-H "Content-Type: application/json" \
|
|
163
|
-
-H "x-callback-token: YOUR_TOKEN_HERE" \
|
|
164
|
-
-d '{
|
|
165
|
-
"message": "Test",
|
|
166
|
-
"cronExpression": "0 9 * * *"
|
|
167
|
-
}'
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Troubleshooting
|
|
171
|
-
|
|
172
|
-
### "No target chat available"
|
|
173
|
-
**Solution:** Send at least one message to your bot first.
|
|
174
|
-
|
|
175
|
-
### Schedule not executing
|
|
176
|
-
**Solution:**
|
|
177
|
-
- Check bridge logs for errors
|
|
178
|
-
- Verify cron expression is valid
|
|
179
|
-
- Ensure bridge is still running
|
|
180
|
-
|
|
181
|
-
### Can't reach API
|
|
182
|
-
**Solution:**
|
|
183
|
-
- Verify bridge is running: `curl http://127.0.0.1:8788/healthz`
|
|
184
|
-
- Check if port 8788 is available
|
|
185
|
-
- Look for errors in bridge logs
|
|
186
|
-
|
|
187
|
-
## Next Steps
|
|
188
|
-
|
|
189
|
-
- Read [SCHEDULER.md](SCHEDULER.md) for complete API documentation
|
|
190
|
-
- Read [TESTING.md](TESTING.md) for comprehensive testing guide
|
|
191
|
-
- Run `./scripts/test-scheduler.sh` for automated tests
|
|
192
|
-
- View `./scripts/gemini-scheduler-examples.sh` for more examples
|
|
193
|
-
|
|
194
|
-
## Pro Tips
|
|
195
|
-
|
|
196
|
-
1. **Descriptions matter**: Add clear descriptions to help you remember what each schedule does
|
|
197
|
-
2. **Test with one-time first**: Before setting up recurring jobs, test with a one-time schedule
|
|
198
|
-
3. **Use natural language**: Just ask Gemini - it's easier than curl commands!
|
|
199
|
-
4. **Check regularly**: Use "What schedules do I have?" to review active schedules
|
|
200
|
-
5. **Timezone aware**: Set `TZ` environment variable if needed (default is UTC)
|
|
201
|
-
|
|
202
|
-
## Example Workflows
|
|
203
|
-
|
|
204
|
-
### Daily Morning Routine
|
|
205
|
-
```
|
|
206
|
-
You: "Every morning at 7am, check my calendar and the weather, then send me a summary"
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Work Break Reminders
|
|
210
|
-
```
|
|
211
|
-
You: "Remind me to take a break every 2 hours during work days"
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Weekly Review
|
|
215
|
-
```
|
|
216
|
-
You: "Every Sunday at 6pm, remind me to plan my goals for next week"
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### Custom Notifications
|
|
220
|
-
```
|
|
221
|
-
You: "Check if there are any urgent emails every 30 minutes and notify me if found"
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
---
|
|
225
|
-
|
|
226
|
-
That's it! You're ready to start scheduling with Clawless. 🎉
|
package/QUICKSTART.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# Quick Start Guide
|
|
2
|
-
|
|
3
|
-
Get your Telegram-Gemini bridge running in 5 minutes!
|
|
4
|
-
|
|
5
|
-
## Prerequisites Checklist
|
|
6
|
-
|
|
7
|
-
- [ ] Node.js 18+ installed (`node --version`)
|
|
8
|
-
- [ ] Gemini CLI installed (`gemini --version`)
|
|
9
|
-
- [ ] Telegram bot token from [@BotFather](https://t.me/BotFather)
|
|
10
|
-
|
|
11
|
-
## Setup Steps
|
|
12
|
-
|
|
13
|
-
### 1. Install Dependencies
|
|
14
|
-
```bash
|
|
15
|
-
npm install
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### 2. Configure Environment
|
|
19
|
-
```bash
|
|
20
|
-
cp .env.example .env
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Edit `.env` and add your bot token:
|
|
24
|
-
```env
|
|
25
|
-
TELEGRAM_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### 3. Test Gemini CLI
|
|
29
|
-
Verify Gemini CLI supports ACP:
|
|
30
|
-
```bash
|
|
31
|
-
gemini --protocol acp
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### 4. Run the Bridge
|
|
35
|
-
|
|
36
|
-
**Quick test:**
|
|
37
|
-
```bash
|
|
38
|
-
npm start
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**Development (auto-restart):**
|
|
42
|
-
```bash
|
|
43
|
-
npm run dev
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Production (with PM2):**
|
|
47
|
-
```bash
|
|
48
|
-
npm install -g pm2
|
|
49
|
-
pm2 start ecosystem.config.json
|
|
50
|
-
pm2 logs
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Verify It's Working
|
|
54
|
-
|
|
55
|
-
1. Open Telegram
|
|
56
|
-
2. Find your bot (search for the username you gave it)
|
|
57
|
-
3. Send a message: "Hello!"
|
|
58
|
-
4. You should see "🤔 Thinking..." followed by Gemini's response
|
|
59
|
-
|
|
60
|
-
## Common Issues
|
|
61
|
-
|
|
62
|
-
### "TELEGRAM_TOKEN is required"
|
|
63
|
-
- Check your `.env` file exists
|
|
64
|
-
- Verify the token is on the line `TELEGRAM_TOKEN=...`
|
|
65
|
-
- No quotes needed around the token
|
|
66
|
-
|
|
67
|
-
### "command not found: gemini"
|
|
68
|
-
- Install Gemini CLI first
|
|
69
|
-
- Verify with: `which gemini`
|
|
70
|
-
|
|
71
|
-
### Bot doesn't respond
|
|
72
|
-
- Check logs: `pm2 logs` (if using PM2)
|
|
73
|
-
- Or check console output
|
|
74
|
-
- Verify your bot token is correct
|
|
75
|
-
|
|
76
|
-
### Rate limit errors (429)
|
|
77
|
-
- Increase `UPDATE_INTERVAL_MS` in `.env` to 2000 or higher
|
|
78
|
-
- Restart the bot
|
|
79
|
-
|
|
80
|
-
## Next Steps
|
|
81
|
-
|
|
82
|
-
- Read the full [README.md](README.md) for detailed configuration
|
|
83
|
-
- Configure MCP servers for tool use
|
|
84
|
-
- Set up auto-start with PM2
|
|
85
|
-
|
|
86
|
-
## Getting Help
|
|
87
|
-
|
|
88
|
-
- Check [README.md](README.md) troubleshooting section
|
|
89
|
-
- Review Gemini CLI documentation
|
|
90
|
-
- Open an issue on GitHub
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
**Pro tip:** Keep the bridge running with PM2 and set it to auto-start on boot:
|
|
95
|
-
```bash
|
|
96
|
-
pm2 startup
|
|
97
|
-
pm2 save
|
|
98
|
-
```
|