@stevederico/dotbot 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +41 -11
- package/bin/dotbot.js +664 -23
- package/dotbot.db +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
0.25
|
|
2
|
+
|
|
3
|
+
Add delete subcommands for memory, jobs, tasks, sessions
|
|
4
|
+
Add --json flag for machine-readable output
|
|
5
|
+
Add doctor command for environment check
|
|
6
|
+
Add ~/.dotbotrc config file support
|
|
7
|
+
Add --openai flag for OpenAI-compatible API
|
|
8
|
+
Add pipe support (stdin input)
|
|
9
|
+
Add --session flag to resume conversations
|
|
10
|
+
|
|
11
|
+
0.24
|
|
12
|
+
|
|
13
|
+
Add --system flag for custom prompts
|
|
14
|
+
Add tools command
|
|
15
|
+
Add stats command
|
|
16
|
+
Add memory command
|
|
17
|
+
Add jobs command
|
|
18
|
+
Add tasks command
|
|
19
|
+
Add sessions command
|
|
20
|
+
Add events command
|
|
21
|
+
|
|
1
22
|
0.23
|
|
2
23
|
|
|
3
24
|
Fix no-args launches interactive
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h1 align="center" style="border-bottom: none; margin-bottom: 0;">dotbot</h1>
|
|
4
4
|
<h3 align="center" style="margin-top: 0; font-weight: normal;">
|
|
5
5
|
The ultra-lean AI agent.<br>
|
|
6
|
-
11k lines.
|
|
6
|
+
11k lines. 53 tools. 0 dependencies.
|
|
7
7
|
</h3>
|
|
8
8
|
<p align="center">
|
|
9
9
|
<a href="https://opensource.org/licenses/mit">
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<img src="https://img.shields.io/github/stars/stevederico/dotbot?style=social" alt="GitHub stars">
|
|
14
14
|
</a>
|
|
15
15
|
<a href="https://github.com/stevederico/dotbot">
|
|
16
|
-
<img src="https://img.shields.io/badge/version-0.
|
|
16
|
+
<img src="https://img.shields.io/badge/version-0.25-green" alt="version">
|
|
17
17
|
</a>
|
|
18
18
|
<img src="https://img.shields.io/badge/LOC-11k-orange" alt="Lines of Code">
|
|
19
19
|
</p>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
| | dotbot | nanobot | OpenClaw |
|
|
29
29
|
|---|:---:|:---:|:---:|
|
|
30
30
|
| **Lines of Code** | **11k** | 22k | 1M+ |
|
|
31
|
-
| **Tools** | **
|
|
31
|
+
| **Tools** | **53** | ~10 | ~50 |
|
|
32
32
|
| **Dependencies** | Minimal | Heavy | Heavy |
|
|
33
33
|
|
|
34
34
|
Everything you need for AI agents. Nothing you don't. No bloated abstractions. No dependency hell. Just a clean, focused agent that works.
|
|
@@ -42,8 +42,9 @@ A **streaming AI agent** with tool execution, autonomous tasks, and scheduled jo
|
|
|
42
42
|
**As a CLI:**
|
|
43
43
|
```bash
|
|
44
44
|
dotbot "What's the weather in San Francisco?"
|
|
45
|
-
dotbot
|
|
45
|
+
dotbot # Interactive mode
|
|
46
46
|
dotbot serve --port 3000
|
|
47
|
+
dotbot tools # List all 53 tools
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
**As a library:**
|
|
@@ -67,11 +68,16 @@ export XAI_API_KEY=xai-...
|
|
|
67
68
|
# Chat
|
|
68
69
|
dotbot "Summarize the top 3 AI news stories today"
|
|
69
70
|
|
|
70
|
-
# Interactive
|
|
71
|
-
dotbot
|
|
71
|
+
# Interactive mode
|
|
72
|
+
dotbot
|
|
72
73
|
|
|
73
74
|
# Start HTTP server
|
|
74
75
|
dotbot serve --port 3000
|
|
76
|
+
|
|
77
|
+
# Inspect data
|
|
78
|
+
dotbot tools
|
|
79
|
+
dotbot stats
|
|
80
|
+
dotbot memory
|
|
75
81
|
```
|
|
76
82
|
|
|
77
83
|
### Library Usage
|
|
@@ -116,7 +122,7 @@ for await (const event of agent.chat({
|
|
|
116
122
|
- **Abort support** via AbortSignal
|
|
117
123
|
- **Automatic retries** with provider failover
|
|
118
124
|
|
|
119
|
-
### 🔧 **
|
|
125
|
+
### 🔧 **53 Built-in Tools**
|
|
120
126
|
- **Memory** — save, search, update, delete long-term memory
|
|
121
127
|
- **Web** — search, fetch, browser automation with Playwright
|
|
122
128
|
- **Files** — read, write, list, delete, move files
|
|
@@ -148,18 +154,39 @@ for await (const event of agent.chat({
|
|
|
148
154
|
## CLI Reference
|
|
149
155
|
|
|
150
156
|
```
|
|
151
|
-
dotbot v0.
|
|
157
|
+
dotbot v0.25 — AI agent CLI
|
|
152
158
|
|
|
153
159
|
Usage:
|
|
154
|
-
dotbot "message"
|
|
155
|
-
dotbot
|
|
160
|
+
dotbot "message" One-shot query
|
|
161
|
+
dotbot Interactive chat
|
|
156
162
|
dotbot serve [--port N] Start HTTP server (default: 3000)
|
|
163
|
+
dotbot serve --openai Start OpenAI-compatible API server
|
|
164
|
+
echo "msg" | dotbot Pipe input from stdin
|
|
165
|
+
|
|
166
|
+
Commands:
|
|
167
|
+
doctor Check environment and configuration
|
|
168
|
+
tools List all available tools
|
|
169
|
+
stats Show database statistics
|
|
170
|
+
memory [list|search <q>] Manage saved memories
|
|
171
|
+
memory delete <key> Delete a memory by key
|
|
172
|
+
jobs List scheduled jobs
|
|
173
|
+
jobs delete <id> Delete a scheduled job
|
|
174
|
+
tasks List active tasks
|
|
175
|
+
tasks delete <id> Delete a task
|
|
176
|
+
sessions List chat sessions
|
|
177
|
+
sessions delete <id> Delete a session
|
|
178
|
+
events [--summary] View audit log
|
|
157
179
|
|
|
158
180
|
Options:
|
|
159
181
|
--provider, -p AI provider: xai, anthropic, openai, ollama (default: xai)
|
|
160
182
|
--model, -m Model name (default: grok-4-1-fast-reasoning)
|
|
183
|
+
--system, -s Custom system prompt (prepended to default)
|
|
184
|
+
--session Resume a specific session by ID
|
|
161
185
|
--db SQLite database path (default: ./dotbot.db)
|
|
162
186
|
--port Server port for 'serve' command
|
|
187
|
+
--openai Enable OpenAI-compatible API endpoints
|
|
188
|
+
--json Output as JSON (for inspection commands)
|
|
189
|
+
--verbose Show initialization logs
|
|
163
190
|
--help, -h Show help
|
|
164
191
|
--version, -v Show version
|
|
165
192
|
|
|
@@ -168,6 +195,9 @@ Environment Variables:
|
|
|
168
195
|
ANTHROPIC_API_KEY API key for Anthropic
|
|
169
196
|
OPENAI_API_KEY API key for OpenAI
|
|
170
197
|
OLLAMA_BASE_URL Base URL for Ollama (default: http://localhost:11434)
|
|
198
|
+
|
|
199
|
+
Config File:
|
|
200
|
+
~/.dotbotrc JSON config for defaults (provider, model, db)
|
|
171
201
|
```
|
|
172
202
|
|
|
173
203
|
<br />
|
|
@@ -230,7 +260,7 @@ for await (const event of agent.chat({
|
|
|
230
260
|
|
|
231
261
|
<br />
|
|
232
262
|
|
|
233
|
-
## Built-in Tools (
|
|
263
|
+
## Built-in Tools (53)
|
|
234
264
|
|
|
235
265
|
| Category | Tools |
|
|
236
266
|
|----------|-------|
|
package/bin/dotbot.js
CHANGED
|
@@ -22,11 +22,13 @@ process.emit = function (event, error) {
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { parseArgs } from 'node:util';
|
|
25
|
-
import { readFileSync } from 'node:fs';
|
|
25
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
import { dirname, join } from 'node:path';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
28
29
|
import * as readline from 'node:readline';
|
|
29
30
|
import { createServer } from 'node:http';
|
|
31
|
+
import { randomUUID } from 'node:crypto';
|
|
30
32
|
|
|
31
33
|
// Read version from package.json
|
|
32
34
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -59,6 +61,7 @@ async function loadModules() {
|
|
|
59
61
|
}
|
|
60
62
|
const DEFAULT_PORT = 3000;
|
|
61
63
|
const DEFAULT_DB = './dotbot.db';
|
|
64
|
+
const CONFIG_PATH = join(homedir(), '.dotbotrc');
|
|
62
65
|
|
|
63
66
|
// Spinner for tool execution feedback
|
|
64
67
|
let spinnerInterval = null;
|
|
@@ -93,12 +96,32 @@ Usage:
|
|
|
93
96
|
dotbot "message" One-shot query
|
|
94
97
|
dotbot Interactive chat
|
|
95
98
|
dotbot serve [--port N] Start HTTP server (default: ${DEFAULT_PORT})
|
|
99
|
+
dotbot serve --openai Start OpenAI-compatible API server
|
|
100
|
+
echo "msg" | dotbot Pipe input from stdin
|
|
101
|
+
|
|
102
|
+
Commands:
|
|
103
|
+
doctor Check environment and configuration
|
|
104
|
+
tools List all available tools
|
|
105
|
+
stats Show database statistics
|
|
106
|
+
memory [list|search <q>] Manage saved memories
|
|
107
|
+
memory delete <key> Delete a memory by key
|
|
108
|
+
jobs List scheduled jobs
|
|
109
|
+
jobs delete <id> Delete a scheduled job
|
|
110
|
+
tasks List active tasks
|
|
111
|
+
tasks delete <id> Delete a task
|
|
112
|
+
sessions List chat sessions
|
|
113
|
+
sessions delete <id> Delete a session
|
|
114
|
+
events [--summary] View audit log
|
|
96
115
|
|
|
97
116
|
Options:
|
|
98
117
|
--provider, -p AI provider: xai, anthropic, openai, ollama (default: xai)
|
|
99
118
|
--model, -m Model name (default: grok-4-1-fast-reasoning)
|
|
119
|
+
--system, -s Custom system prompt (prepended to default)
|
|
120
|
+
--session Resume a specific session by ID
|
|
100
121
|
--db SQLite database path (default: ${DEFAULT_DB})
|
|
101
122
|
--port Server port for 'serve' command (default: ${DEFAULT_PORT})
|
|
123
|
+
--openai Enable OpenAI-compatible API endpoints (/v1/chat/completions, /v1/models)
|
|
124
|
+
--json Output as JSON (for inspection commands)
|
|
102
125
|
--verbose Show initialization logs
|
|
103
126
|
--help, -h Show this help
|
|
104
127
|
--version, -v Show version
|
|
@@ -109,17 +132,51 @@ Environment Variables:
|
|
|
109
132
|
OPENAI_API_KEY API key for OpenAI
|
|
110
133
|
OLLAMA_BASE_URL Base URL for Ollama (default: http://localhost:11434)
|
|
111
134
|
|
|
135
|
+
Config File:
|
|
136
|
+
~/.dotbotrc JSON config for defaults (provider, model, db)
|
|
137
|
+
|
|
112
138
|
Examples:
|
|
113
139
|
dotbot "What's the weather in SF?"
|
|
114
140
|
dotbot
|
|
115
141
|
dotbot serve --port 8080
|
|
142
|
+
dotbot doctor
|
|
143
|
+
dotbot tools
|
|
144
|
+
dotbot memory search "preferences"
|
|
145
|
+
dotbot memory delete user_pref
|
|
146
|
+
dotbot stats --json
|
|
147
|
+
dotbot --system "You are a pirate" "Hello"
|
|
148
|
+
dotbot --session abc-123 "follow up question"
|
|
149
|
+
echo "What is 2+2?" | dotbot
|
|
150
|
+
cat question.txt | dotbot
|
|
116
151
|
`);
|
|
117
152
|
}
|
|
118
153
|
|
|
119
154
|
/**
|
|
120
|
-
*
|
|
155
|
+
* Load config from ~/.dotbotrc if it exists.
|
|
156
|
+
*
|
|
157
|
+
* @returns {Object} Config object or empty object if not found
|
|
158
|
+
*/
|
|
159
|
+
function loadConfig() {
|
|
160
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const content = readFileSync(CONFIG_PATH, 'utf8');
|
|
165
|
+
return JSON.parse(content);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error(`Warning: Invalid config file at ${CONFIG_PATH}: ${err.message}`);
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Parse CLI arguments with config file fallback.
|
|
174
|
+
*
|
|
175
|
+
* @returns {Object} Merged CLI args and config values
|
|
121
176
|
*/
|
|
122
177
|
function parseCliArgs() {
|
|
178
|
+
const config = loadConfig();
|
|
179
|
+
|
|
123
180
|
try {
|
|
124
181
|
const { values, positionals } = parseArgs({
|
|
125
182
|
allowPositionals: true,
|
|
@@ -127,13 +184,29 @@ function parseCliArgs() {
|
|
|
127
184
|
help: { type: 'boolean', short: 'h', default: false },
|
|
128
185
|
version: { type: 'boolean', short: 'v', default: false },
|
|
129
186
|
verbose: { type: 'boolean', default: false },
|
|
130
|
-
provider: { type: 'string', short: 'p'
|
|
131
|
-
model: { type: 'string', short: 'm'
|
|
132
|
-
|
|
133
|
-
|
|
187
|
+
provider: { type: 'string', short: 'p' },
|
|
188
|
+
model: { type: 'string', short: 'm' },
|
|
189
|
+
system: { type: 'string', short: 's' },
|
|
190
|
+
summary: { type: 'boolean', default: false },
|
|
191
|
+
json: { type: 'boolean', default: false },
|
|
192
|
+
db: { type: 'string' },
|
|
193
|
+
port: { type: 'string' },
|
|
194
|
+
openai: { type: 'boolean', default: false },
|
|
195
|
+
session: { type: 'string', default: '' },
|
|
134
196
|
},
|
|
135
197
|
});
|
|
136
|
-
|
|
198
|
+
|
|
199
|
+
// Merge: CLI args > config file > hardcoded defaults
|
|
200
|
+
return {
|
|
201
|
+
...values,
|
|
202
|
+
provider: values.provider ?? config.provider ?? 'xai',
|
|
203
|
+
model: values.model ?? config.model ?? 'grok-4-1-fast-reasoning',
|
|
204
|
+
system: values.system ?? config.system ?? '',
|
|
205
|
+
db: values.db ?? config.db ?? DEFAULT_DB,
|
|
206
|
+
port: values.port ?? config.port ?? String(DEFAULT_PORT),
|
|
207
|
+
session: values.session ?? '',
|
|
208
|
+
positionals,
|
|
209
|
+
};
|
|
137
210
|
} catch (err) {
|
|
138
211
|
console.error(`Error: ${err.message}`);
|
|
139
212
|
process.exit(1);
|
|
@@ -179,20 +252,30 @@ async function getProviderConfig(providerId) {
|
|
|
179
252
|
*
|
|
180
253
|
* @param {string} dbPath - Path to SQLite database
|
|
181
254
|
* @param {boolean} verbose - Show initialization logs
|
|
255
|
+
* @param {string} customSystemPrompt - Custom system prompt to prepend
|
|
182
256
|
* @returns {Promise<Object>} Initialized stores
|
|
183
257
|
*/
|
|
184
|
-
async function initStores(dbPath, verbose = false) {
|
|
258
|
+
async function initStores(dbPath, verbose = false, customSystemPrompt = '') {
|
|
185
259
|
await loadModules();
|
|
186
260
|
|
|
261
|
+
// Import defaultSystemPrompt for custom builder
|
|
262
|
+
const { defaultSystemPrompt } = await import('../storage/SQLiteAdapter.js');
|
|
263
|
+
|
|
187
264
|
// Suppress init logs unless verbose
|
|
188
265
|
const originalLog = console.log;
|
|
189
266
|
if (!verbose) {
|
|
190
267
|
console.log = () => {};
|
|
191
268
|
}
|
|
192
269
|
|
|
270
|
+
// Build custom systemPromptBuilder that prepends user's text
|
|
271
|
+
const systemPromptBuilder = customSystemPrompt
|
|
272
|
+
? (prefs) => `${customSystemPrompt}\n\n${defaultSystemPrompt(prefs)}`
|
|
273
|
+
: undefined;
|
|
274
|
+
|
|
193
275
|
const sessionStore = new stores.SQLiteSessionStore();
|
|
194
276
|
await sessionStore.init(dbPath, {
|
|
195
277
|
prefsFetcher: async () => ({ agentName: 'Dotbot', agentPersonality: '' }),
|
|
278
|
+
...(systemPromptBuilder && { systemPromptBuilder }),
|
|
196
279
|
});
|
|
197
280
|
|
|
198
281
|
const cronStore = new stores.SQLiteCronStore();
|
|
@@ -223,10 +306,23 @@ async function initStores(dbPath, verbose = false) {
|
|
|
223
306
|
* @param {Object} options - CLI options
|
|
224
307
|
*/
|
|
225
308
|
async function runChat(message, options) {
|
|
226
|
-
const storesObj = await initStores(options.db, options.verbose);
|
|
309
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
227
310
|
const provider = await getProviderConfig(options.provider);
|
|
228
311
|
|
|
229
|
-
|
|
312
|
+
let session;
|
|
313
|
+
let messages;
|
|
314
|
+
|
|
315
|
+
if (options.session) {
|
|
316
|
+
session = await storesObj.sessionStore.getSession(options.session, 'cli-user');
|
|
317
|
+
if (!session) {
|
|
318
|
+
console.error(`Error: Session not found: ${options.session}`);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
messages = [...(session.messages || []), { role: 'user', content: message }];
|
|
322
|
+
} else {
|
|
323
|
+
session = await storesObj.sessionStore.createSession('cli-user', options.model, options.provider);
|
|
324
|
+
messages = [{ role: 'user', content: message }];
|
|
325
|
+
}
|
|
230
326
|
|
|
231
327
|
const context = {
|
|
232
328
|
userID: 'cli-user',
|
|
@@ -235,8 +331,6 @@ async function runChat(message, options) {
|
|
|
235
331
|
...storesObj,
|
|
236
332
|
};
|
|
237
333
|
|
|
238
|
-
const messages = [{ role: 'user', content: message }];
|
|
239
|
-
|
|
240
334
|
process.stdout.write('\n[thinking] ');
|
|
241
335
|
startSpinner();
|
|
242
336
|
|
|
@@ -282,11 +376,23 @@ async function runChat(message, options) {
|
|
|
282
376
|
* @param {Object} options - CLI options
|
|
283
377
|
*/
|
|
284
378
|
async function runRepl(options) {
|
|
285
|
-
const storesObj = await initStores(options.db, options.verbose);
|
|
379
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
286
380
|
const provider = await getProviderConfig(options.provider);
|
|
287
381
|
|
|
288
|
-
|
|
289
|
-
|
|
382
|
+
let session;
|
|
383
|
+
let messages;
|
|
384
|
+
|
|
385
|
+
if (options.session) {
|
|
386
|
+
session = await storesObj.sessionStore.getSession(options.session, 'cli-user');
|
|
387
|
+
if (!session) {
|
|
388
|
+
console.error(`Error: Session not found: ${options.session}`);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
messages = [...(session.messages || [])];
|
|
392
|
+
} else {
|
|
393
|
+
session = await storesObj.sessionStore.createSession('cli-user', options.model, options.provider);
|
|
394
|
+
messages = [];
|
|
395
|
+
}
|
|
290
396
|
|
|
291
397
|
const context = {
|
|
292
398
|
userID: 'cli-user',
|
|
@@ -301,6 +407,9 @@ async function runRepl(options) {
|
|
|
301
407
|
});
|
|
302
408
|
|
|
303
409
|
console.log(`\ndotbot v${VERSION} — ${options.provider}/${options.model}`);
|
|
410
|
+
if (options.session) {
|
|
411
|
+
console.log(`Resuming session: ${session.id}`);
|
|
412
|
+
}
|
|
304
413
|
console.log('Type /quit to exit, /clear to reset conversation\n');
|
|
305
414
|
|
|
306
415
|
const prompt = () => {
|
|
@@ -388,7 +497,7 @@ async function runRepl(options) {
|
|
|
388
497
|
*/
|
|
389
498
|
async function runServer(options) {
|
|
390
499
|
const port = parseInt(options.port, 10);
|
|
391
|
-
const storesObj = await initStores(options.db, options.verbose);
|
|
500
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
392
501
|
|
|
393
502
|
const server = createServer(async (req, res) => {
|
|
394
503
|
// CORS headers
|
|
@@ -411,6 +520,140 @@ async function runServer(options) {
|
|
|
411
520
|
return;
|
|
412
521
|
}
|
|
413
522
|
|
|
523
|
+
// OpenAI-compatible endpoints (when --openai flag is set)
|
|
524
|
+
if (options.openai) {
|
|
525
|
+
// GET /v1/models - list available models
|
|
526
|
+
if (req.method === 'GET' && url.pathname === '/v1/models') {
|
|
527
|
+
const models = [
|
|
528
|
+
{ id: 'grok-3', object: 'model', owned_by: 'xai' },
|
|
529
|
+
{ id: 'grok-4-1-fast-reasoning', object: 'model', owned_by: 'xai' },
|
|
530
|
+
{ id: 'claude-sonnet-4-5', object: 'model', owned_by: 'anthropic' },
|
|
531
|
+
{ id: 'claude-opus-4', object: 'model', owned_by: 'anthropic' },
|
|
532
|
+
{ id: 'gpt-4o', object: 'model', owned_by: 'openai' },
|
|
533
|
+
];
|
|
534
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
535
|
+
res.end(JSON.stringify({ object: 'list', data: models }));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// POST /v1/chat/completions - OpenAI-compatible chat endpoint
|
|
540
|
+
if (req.method === 'POST' && url.pathname === '/v1/chat/completions') {
|
|
541
|
+
let body = '';
|
|
542
|
+
for await (const chunk of req) body += chunk;
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
const { model = 'grok-4-1-fast-reasoning', messages: reqMessages, stream = true } = JSON.parse(body);
|
|
546
|
+
|
|
547
|
+
if (!reqMessages || !Array.isArray(reqMessages)) {
|
|
548
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
549
|
+
res.end(JSON.stringify({ error: { message: 'messages array required', type: 'invalid_request_error' } }));
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Determine provider from model name
|
|
554
|
+
let providerId = 'xai';
|
|
555
|
+
if (model.startsWith('claude')) providerId = 'anthropic';
|
|
556
|
+
else if (model.startsWith('gpt')) providerId = 'openai';
|
|
557
|
+
else if (model.startsWith('llama') || model.startsWith('mistral')) providerId = 'ollama';
|
|
558
|
+
|
|
559
|
+
const provider = await getProviderConfig(providerId);
|
|
560
|
+
const session = await storesObj.sessionStore.createSession('api-user', model, providerId);
|
|
561
|
+
|
|
562
|
+
const context = {
|
|
563
|
+
userID: 'api-user',
|
|
564
|
+
sessionId: session.id,
|
|
565
|
+
providers: { [providerId]: { apiKey: process.env[AI_PROVIDERS[providerId]?.envKey] } },
|
|
566
|
+
...storesObj,
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const completionId = `chatcmpl-${randomUUID()}`;
|
|
570
|
+
const created = Math.floor(Date.now() / 1000);
|
|
571
|
+
|
|
572
|
+
if (stream) {
|
|
573
|
+
res.writeHead(200, {
|
|
574
|
+
'Content-Type': 'text/event-stream',
|
|
575
|
+
'Cache-Control': 'no-cache',
|
|
576
|
+
'Connection': 'keep-alive',
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Send initial role chunk
|
|
580
|
+
const roleChunk = {
|
|
581
|
+
id: completionId,
|
|
582
|
+
object: 'chat.completion.chunk',
|
|
583
|
+
created,
|
|
584
|
+
model,
|
|
585
|
+
choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }],
|
|
586
|
+
};
|
|
587
|
+
res.write(`data: ${JSON.stringify(roleChunk)}\n\n`);
|
|
588
|
+
|
|
589
|
+
for await (const event of agentLoop({
|
|
590
|
+
model,
|
|
591
|
+
messages: reqMessages,
|
|
592
|
+
tools: coreTools,
|
|
593
|
+
provider,
|
|
594
|
+
context,
|
|
595
|
+
})) {
|
|
596
|
+
if (event.type === 'text_delta') {
|
|
597
|
+
const chunk = {
|
|
598
|
+
id: completionId,
|
|
599
|
+
object: 'chat.completion.chunk',
|
|
600
|
+
created,
|
|
601
|
+
model,
|
|
602
|
+
choices: [{ index: 0, delta: { content: event.text }, finish_reason: null }],
|
|
603
|
+
};
|
|
604
|
+
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
605
|
+
} else if (event.type === 'done') {
|
|
606
|
+
const finalChunk = {
|
|
607
|
+
id: completionId,
|
|
608
|
+
object: 'chat.completion.chunk',
|
|
609
|
+
created,
|
|
610
|
+
model,
|
|
611
|
+
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
|
|
612
|
+
};
|
|
613
|
+
res.write(`data: ${JSON.stringify(finalChunk)}\n\n`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
res.write('data: [DONE]\n\n');
|
|
618
|
+
res.end();
|
|
619
|
+
} else {
|
|
620
|
+
// Non-streaming response
|
|
621
|
+
let fullContent = '';
|
|
622
|
+
for await (const event of agentLoop({
|
|
623
|
+
model,
|
|
624
|
+
messages: reqMessages,
|
|
625
|
+
tools: coreTools,
|
|
626
|
+
provider,
|
|
627
|
+
context,
|
|
628
|
+
})) {
|
|
629
|
+
if (event.type === 'text_delta') {
|
|
630
|
+
fullContent += event.text;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const response = {
|
|
635
|
+
id: completionId,
|
|
636
|
+
object: 'chat.completion',
|
|
637
|
+
created,
|
|
638
|
+
model,
|
|
639
|
+
choices: [{
|
|
640
|
+
index: 0,
|
|
641
|
+
message: { role: 'assistant', content: fullContent },
|
|
642
|
+
finish_reason: 'stop',
|
|
643
|
+
}],
|
|
644
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
645
|
+
};
|
|
646
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
647
|
+
res.end(JSON.stringify(response));
|
|
648
|
+
}
|
|
649
|
+
} catch (err) {
|
|
650
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
651
|
+
res.end(JSON.stringify({ error: { message: err.message, type: 'server_error' } }));
|
|
652
|
+
}
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
414
657
|
// Chat endpoint
|
|
415
658
|
if (req.method === 'POST' && url.pathname === '/chat') {
|
|
416
659
|
let body = '';
|
|
@@ -478,10 +721,368 @@ async function runServer(options) {
|
|
|
478
721
|
console.log(`Listening on http://localhost:${port}`);
|
|
479
722
|
console.log(`\nEndpoints:`);
|
|
480
723
|
console.log(` GET /health Health check`);
|
|
481
|
-
console.log(` POST /chat Send message (SSE stream)
|
|
724
|
+
console.log(` POST /chat Send message (SSE stream)`);
|
|
725
|
+
if (options.openai) {
|
|
726
|
+
console.log(`\nOpenAI-compatible API:`);
|
|
727
|
+
console.log(` GET /v1/models List available models`);
|
|
728
|
+
console.log(` POST /v1/chat/completions Chat completions (SSE stream)`);
|
|
729
|
+
}
|
|
730
|
+
console.log();
|
|
482
731
|
});
|
|
483
732
|
}
|
|
484
733
|
|
|
734
|
+
/**
|
|
735
|
+
* List all available tools.
|
|
736
|
+
*
|
|
737
|
+
* @param {Object} options - CLI options
|
|
738
|
+
*/
|
|
739
|
+
async function runTools(options) {
|
|
740
|
+
await loadModules();
|
|
741
|
+
|
|
742
|
+
if (options.json) {
|
|
743
|
+
const toolList = coreTools.map((t) => ({ name: t.name, description: t.description }));
|
|
744
|
+
console.log(JSON.stringify(toolList));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
console.log(`\ndotbot tools (${coreTools.length})\n`);
|
|
749
|
+
|
|
750
|
+
// Group tools by category based on name prefix
|
|
751
|
+
const categories = {};
|
|
752
|
+
for (const tool of coreTools) {
|
|
753
|
+
const prefix = tool.name.split('_')[0];
|
|
754
|
+
if (!categories[prefix]) categories[prefix] = [];
|
|
755
|
+
categories[prefix].push(tool.name);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
for (const [category, tools] of Object.entries(categories).sort()) {
|
|
759
|
+
console.log(` ${category} (${tools.length})`);
|
|
760
|
+
for (const name of tools.sort()) {
|
|
761
|
+
console.log(` ${name}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
console.log();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Show database statistics.
|
|
769
|
+
*
|
|
770
|
+
* @param {Object} options - CLI options
|
|
771
|
+
*/
|
|
772
|
+
async function runStats(options) {
|
|
773
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
774
|
+
|
|
775
|
+
// Sessions
|
|
776
|
+
const sessions = await storesObj.sessionStore.listSessions('cli-user');
|
|
777
|
+
|
|
778
|
+
// Memory
|
|
779
|
+
const memories = await storesObj.memoryStore.getAllMemories('cli-user');
|
|
780
|
+
|
|
781
|
+
// Jobs (need to get session IDs first)
|
|
782
|
+
const jobs = await storesObj.cronStore.listTasksBySessionIds(['default'], 'cli-user');
|
|
783
|
+
|
|
784
|
+
// Tasks
|
|
785
|
+
const tasks = await storesObj.taskStore.getTasks('cli-user');
|
|
786
|
+
|
|
787
|
+
// Triggers
|
|
788
|
+
const triggers = await storesObj.triggerStore.listTriggers('cli-user');
|
|
789
|
+
|
|
790
|
+
if (options.json) {
|
|
791
|
+
console.log(JSON.stringify({
|
|
792
|
+
database: options.db,
|
|
793
|
+
sessions: sessions.length,
|
|
794
|
+
memories: memories.length,
|
|
795
|
+
jobs: jobs.length,
|
|
796
|
+
tasks: tasks.length,
|
|
797
|
+
triggers: triggers.length,
|
|
798
|
+
}));
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
console.log(`\ndotbot stats\n`);
|
|
803
|
+
console.log(` Database: ${options.db}`);
|
|
804
|
+
console.log(` Sessions: ${sessions.length}`);
|
|
805
|
+
console.log(` Memories: ${memories.length}`);
|
|
806
|
+
console.log(` Jobs: ${jobs.length}`);
|
|
807
|
+
console.log(` Tasks: ${tasks.length}`);
|
|
808
|
+
console.log(` Triggers: ${triggers.length}`);
|
|
809
|
+
|
|
810
|
+
console.log();
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Manage memories.
|
|
815
|
+
*
|
|
816
|
+
* @param {Object} options - CLI options
|
|
817
|
+
* @param {string} subcommand - list, search, or delete
|
|
818
|
+
* @param {string} query - Search query or key to delete
|
|
819
|
+
*/
|
|
820
|
+
async function runMemory(options, subcommand, query) {
|
|
821
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
822
|
+
|
|
823
|
+
if (subcommand === 'delete' && query) {
|
|
824
|
+
const result = await storesObj.memoryStore.deleteMemory('cli-user', query);
|
|
825
|
+
if (options.json) {
|
|
826
|
+
console.log(JSON.stringify({ deleted: result, key: query }));
|
|
827
|
+
} else {
|
|
828
|
+
console.log(result ? `\nDeleted memory: ${query}\n` : `\nMemory not found: ${query}\n`);
|
|
829
|
+
}
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (subcommand === 'search' && query) {
|
|
834
|
+
const results = await storesObj.memoryStore.readMemoryPattern('cli-user', `%${query}%`);
|
|
835
|
+
if (options.json) {
|
|
836
|
+
console.log(JSON.stringify(results));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
console.log(`\nMemory search: "${query}" (${results.length} results)\n`);
|
|
840
|
+
for (const mem of results) {
|
|
841
|
+
const val = typeof mem.value === 'string' ? mem.value : JSON.stringify(mem.value);
|
|
842
|
+
console.log(` [${mem.key}] ${val.substring(0, 60)}${val.length > 60 ? '...' : ''}`);
|
|
843
|
+
}
|
|
844
|
+
} else {
|
|
845
|
+
const memories = await storesObj.memoryStore.getAllMemories('cli-user');
|
|
846
|
+
if (options.json) {
|
|
847
|
+
console.log(JSON.stringify(memories));
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
console.log(`\nMemories (${memories.length})\n`);
|
|
851
|
+
for (const mem of memories) {
|
|
852
|
+
const val = typeof mem.value === 'string' ? mem.value : JSON.stringify(mem.value);
|
|
853
|
+
console.log(` [${mem.key}] ${val.substring(0, 60)}${val.length > 60 ? '...' : ''}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
console.log();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Manage scheduled jobs.
|
|
861
|
+
*
|
|
862
|
+
* @param {Object} options - CLI options
|
|
863
|
+
* @param {string} subcommand - list or delete
|
|
864
|
+
* @param {string} jobId - Job ID to delete
|
|
865
|
+
*/
|
|
866
|
+
async function runJobs(options, subcommand, jobId) {
|
|
867
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
868
|
+
|
|
869
|
+
if (subcommand === 'delete' && jobId) {
|
|
870
|
+
const result = await storesObj.cronStore.deleteTask(jobId);
|
|
871
|
+
if (options.json) {
|
|
872
|
+
console.log(JSON.stringify({ deleted: result, id: jobId }));
|
|
873
|
+
} else {
|
|
874
|
+
console.log(result ? `\nDeleted job: ${jobId}\n` : `\nJob not found: ${jobId}\n`);
|
|
875
|
+
}
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const jobs = await storesObj.cronStore.listTasksBySessionIds(['default'], 'cli-user');
|
|
880
|
+
|
|
881
|
+
if (options.json) {
|
|
882
|
+
console.log(JSON.stringify(jobs));
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
console.log(`\nScheduled jobs (${jobs.length})\n`);
|
|
887
|
+
|
|
888
|
+
for (const job of jobs) {
|
|
889
|
+
const status = job.enabled ? 'active' : 'paused';
|
|
890
|
+
const next = job.nextRunAt ? job.nextRunAt.toLocaleString() : 'N/A';
|
|
891
|
+
const interval = job.intervalMs ? `${Math.round(job.intervalMs / 60000)}m` : 'once';
|
|
892
|
+
console.log(` [${job.id}] ${job.name} (${status})`);
|
|
893
|
+
console.log(` Interval: ${interval}`);
|
|
894
|
+
console.log(` Next: ${next}`);
|
|
895
|
+
console.log(` Prompt: ${job.prompt.substring(0, 50)}${job.prompt.length > 50 ? '...' : ''}`);
|
|
896
|
+
console.log();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Manage active tasks.
|
|
902
|
+
*
|
|
903
|
+
* @param {Object} options - CLI options
|
|
904
|
+
* @param {string} subcommand - list or delete
|
|
905
|
+
* @param {string} taskId - Task ID to delete
|
|
906
|
+
*/
|
|
907
|
+
async function runTasks(options, subcommand, taskId) {
|
|
908
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
909
|
+
|
|
910
|
+
if (subcommand === 'delete' && taskId) {
|
|
911
|
+
const result = await storesObj.taskStore.deleteTask('cli-user', taskId);
|
|
912
|
+
if (options.json) {
|
|
913
|
+
console.log(JSON.stringify({ deleted: result, id: taskId }));
|
|
914
|
+
} else {
|
|
915
|
+
console.log(result ? `\nDeleted task: ${taskId}\n` : `\nTask not found: ${taskId}\n`);
|
|
916
|
+
}
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const tasks = await storesObj.taskStore.getTasks('cli-user');
|
|
921
|
+
|
|
922
|
+
if (options.json) {
|
|
923
|
+
console.log(JSON.stringify(tasks));
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
console.log(`\nTasks (${tasks.length})\n`);
|
|
928
|
+
|
|
929
|
+
for (const task of tasks) {
|
|
930
|
+
const steps = task.steps ? JSON.parse(task.steps) : [];
|
|
931
|
+
const progress = `${task.current_step || 0}/${steps.length}`;
|
|
932
|
+
console.log(` [${task.id}] ${task.status} (${progress})`);
|
|
933
|
+
console.log(` Description: ${task.description?.substring(0, 50) || 'N/A'}${task.description?.length > 50 ? '...' : ''}`);
|
|
934
|
+
console.log(` Mode: ${task.mode || 'auto'}`);
|
|
935
|
+
console.log(` Created: ${new Date(task.created_at).toLocaleString()}`);
|
|
936
|
+
console.log();
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Manage chat sessions.
|
|
942
|
+
*
|
|
943
|
+
* @param {Object} options - CLI options
|
|
944
|
+
* @param {string} subcommand - list or delete
|
|
945
|
+
* @param {string} sessionId - Session ID to delete
|
|
946
|
+
*/
|
|
947
|
+
async function runSessions(options, subcommand, sessionId) {
|
|
948
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
949
|
+
|
|
950
|
+
if (subcommand === 'delete' && sessionId) {
|
|
951
|
+
const result = await storesObj.sessionStore.deleteSession(sessionId, 'cli-user');
|
|
952
|
+
if (options.json) {
|
|
953
|
+
console.log(JSON.stringify({ deleted: result, id: sessionId }));
|
|
954
|
+
} else {
|
|
955
|
+
console.log(result ? `\nDeleted session: ${sessionId}\n` : `\nSession not found: ${sessionId}\n`);
|
|
956
|
+
}
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const sessions = await storesObj.sessionStore.listSessions('cli-user');
|
|
961
|
+
|
|
962
|
+
if (options.json) {
|
|
963
|
+
console.log(JSON.stringify(sessions));
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
console.log(`\nSessions (${sessions.length})\n`);
|
|
968
|
+
|
|
969
|
+
for (const session of sessions) {
|
|
970
|
+
const updated = new Date(session.updatedAt).toLocaleString();
|
|
971
|
+
const msgCount = session.messageCount || 0;
|
|
972
|
+
console.log(` [${session.id}]`);
|
|
973
|
+
console.log(` Title: ${session.title || 'Untitled'}`);
|
|
974
|
+
console.log(` Messages: ${msgCount}`);
|
|
975
|
+
console.log(` Updated: ${updated}`);
|
|
976
|
+
console.log();
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* View audit log events.
|
|
982
|
+
*
|
|
983
|
+
* @param {Object} options - CLI options
|
|
984
|
+
*/
|
|
985
|
+
async function runEvents(options) {
|
|
986
|
+
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
987
|
+
|
|
988
|
+
if (options.summary) {
|
|
989
|
+
const summary = await storesObj.eventStore.summary({ userId: 'cli-user' });
|
|
990
|
+
if (options.json) {
|
|
991
|
+
console.log(JSON.stringify(summary));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
console.log(`\nEvent summary\n`);
|
|
995
|
+
console.log(` Total events: ${summary.total || 0}`);
|
|
996
|
+
if (summary.breakdown) {
|
|
997
|
+
for (const [type, count] of Object.entries(summary.breakdown)) {
|
|
998
|
+
console.log(` ${type}: ${count}`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
} else {
|
|
1002
|
+
const events = await storesObj.eventStore.query({ userId: 'cli-user', limit: 20 });
|
|
1003
|
+
if (options.json) {
|
|
1004
|
+
console.log(JSON.stringify(events));
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
console.log(`\nRecent events (${events.length})\n`);
|
|
1008
|
+
|
|
1009
|
+
for (const event of events) {
|
|
1010
|
+
const time = new Date(event.timestamp).toLocaleString();
|
|
1011
|
+
console.log(` [${time}] ${event.type}`);
|
|
1012
|
+
if (event.data) {
|
|
1013
|
+
const data = typeof event.data === 'string' ? event.data : JSON.stringify(event.data);
|
|
1014
|
+
console.log(` ${data.substring(0, 60)}${data.length > 60 ? '...' : ''}`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
console.log();
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Check environment and configuration.
|
|
1023
|
+
*
|
|
1024
|
+
* @param {Object} options - CLI options
|
|
1025
|
+
*/
|
|
1026
|
+
async function runDoctor(options) {
|
|
1027
|
+
console.log(`\ndotbot doctor\n`);
|
|
1028
|
+
|
|
1029
|
+
// Node.js version
|
|
1030
|
+
const nodeVersion = process.version;
|
|
1031
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
1032
|
+
const nodeOk = nodeMajor >= 22;
|
|
1033
|
+
console.log(` Node.js: ${nodeVersion} ${nodeOk ? '\u2713' : '\u2717 (requires >= 22.0.0)'}`);
|
|
1034
|
+
|
|
1035
|
+
// Database check
|
|
1036
|
+
const dbPath = options.db;
|
|
1037
|
+
let dbOk = false;
|
|
1038
|
+
try {
|
|
1039
|
+
if (existsSync(dbPath)) {
|
|
1040
|
+
// Try to open database
|
|
1041
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
1042
|
+
const db = new DatabaseSync(dbPath, { open: true });
|
|
1043
|
+
db.close();
|
|
1044
|
+
dbOk = true;
|
|
1045
|
+
} else {
|
|
1046
|
+
dbOk = true; // Will be created on first use
|
|
1047
|
+
}
|
|
1048
|
+
} catch {
|
|
1049
|
+
dbOk = false;
|
|
1050
|
+
}
|
|
1051
|
+
console.log(` Database: ${dbPath} ${dbOk ? '\u2713' : '\u2717 not accessible'}`);
|
|
1052
|
+
|
|
1053
|
+
// Config file check
|
|
1054
|
+
let configOk = false;
|
|
1055
|
+
let configMsg = '';
|
|
1056
|
+
if (existsSync(CONFIG_PATH)) {
|
|
1057
|
+
try {
|
|
1058
|
+
const content = readFileSync(CONFIG_PATH, 'utf8');
|
|
1059
|
+
JSON.parse(content);
|
|
1060
|
+
configOk = true;
|
|
1061
|
+
configMsg = `${CONFIG_PATH} \u2713`;
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
configMsg = `${CONFIG_PATH} \u2717 invalid JSON`;
|
|
1064
|
+
}
|
|
1065
|
+
} else {
|
|
1066
|
+
configMsg = `${CONFIG_PATH} (not found)`;
|
|
1067
|
+
}
|
|
1068
|
+
console.log(` Config: ${configMsg}`);
|
|
1069
|
+
|
|
1070
|
+
// API Keys
|
|
1071
|
+
console.log(`\n API Keys:`);
|
|
1072
|
+
const apiKeys = [
|
|
1073
|
+
{ name: 'XAI_API_KEY', env: process.env.XAI_API_KEY },
|
|
1074
|
+
{ name: 'ANTHROPIC_API_KEY', env: process.env.ANTHROPIC_API_KEY },
|
|
1075
|
+
{ name: 'OPENAI_API_KEY', env: process.env.OPENAI_API_KEY },
|
|
1076
|
+
];
|
|
1077
|
+
|
|
1078
|
+
for (const key of apiKeys) {
|
|
1079
|
+
const isSet = Boolean(key.env);
|
|
1080
|
+
console.log(` ${key.name}: ${isSet ? '\u2713 set' : '\u2717 not set'}`);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
console.log();
|
|
1084
|
+
}
|
|
1085
|
+
|
|
485
1086
|
/**
|
|
486
1087
|
* Main entry point.
|
|
487
1088
|
*/
|
|
@@ -500,14 +1101,54 @@ async function main() {
|
|
|
500
1101
|
|
|
501
1102
|
const command = args.positionals[0];
|
|
502
1103
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
1104
|
+
// Handle piped input from stdin
|
|
1105
|
+
if (!process.stdin.isTTY && !command) {
|
|
1106
|
+
let input = '';
|
|
1107
|
+
for await (const chunk of process.stdin) {
|
|
1108
|
+
input += chunk;
|
|
1109
|
+
}
|
|
1110
|
+
const message = input.trim();
|
|
507
1111
|
if (message) {
|
|
508
1112
|
await runChat(message, args);
|
|
509
|
-
|
|
510
|
-
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
switch (command) {
|
|
1118
|
+
case 'doctor':
|
|
1119
|
+
await runDoctor(args);
|
|
1120
|
+
break;
|
|
1121
|
+
case 'serve':
|
|
1122
|
+
await runServer(args);
|
|
1123
|
+
break;
|
|
1124
|
+
case 'tools':
|
|
1125
|
+
await runTools(args);
|
|
1126
|
+
break;
|
|
1127
|
+
case 'stats':
|
|
1128
|
+
await runStats(args);
|
|
1129
|
+
break;
|
|
1130
|
+
case 'memory':
|
|
1131
|
+
await runMemory(args, args.positionals[1], args.positionals.slice(2).join(' '));
|
|
1132
|
+
break;
|
|
1133
|
+
case 'jobs':
|
|
1134
|
+
await runJobs(args, args.positionals[1], args.positionals[2]);
|
|
1135
|
+
break;
|
|
1136
|
+
case 'tasks':
|
|
1137
|
+
await runTasks(args, args.positionals[1], args.positionals[2]);
|
|
1138
|
+
break;
|
|
1139
|
+
case 'sessions':
|
|
1140
|
+
await runSessions(args, args.positionals[1], args.positionals[2]);
|
|
1141
|
+
break;
|
|
1142
|
+
case 'events':
|
|
1143
|
+
await runEvents(args);
|
|
1144
|
+
break;
|
|
1145
|
+
default: {
|
|
1146
|
+
const message = args.positionals.join(' ');
|
|
1147
|
+
if (message) {
|
|
1148
|
+
await runChat(message, args);
|
|
1149
|
+
} else {
|
|
1150
|
+
await runRepl(args);
|
|
1151
|
+
}
|
|
511
1152
|
}
|
|
512
1153
|
}
|
|
513
1154
|
}
|
package/dotbot.db
CHANGED
|
Binary file
|
package/package.json
CHANGED