@stevederico/dotbot 0.24.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 +10 -0
- package/README.md +15 -2
- package/bin/dotbot.js +440 -34
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
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
|
+
|
|
1
11
|
0.24
|
|
2
12
|
|
|
3
13
|
Add --system flag for custom prompts
|
package/README.md
CHANGED
|
@@ -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>
|
|
@@ -154,28 +154,38 @@ for await (const event of agent.chat({
|
|
|
154
154
|
## CLI Reference
|
|
155
155
|
|
|
156
156
|
```
|
|
157
|
-
dotbot v0.
|
|
157
|
+
dotbot v0.25 — AI agent CLI
|
|
158
158
|
|
|
159
159
|
Usage:
|
|
160
160
|
dotbot "message" One-shot query
|
|
161
161
|
dotbot Interactive chat
|
|
162
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
|
|
163
165
|
|
|
164
166
|
Commands:
|
|
167
|
+
doctor Check environment and configuration
|
|
165
168
|
tools List all available tools
|
|
166
169
|
stats Show database statistics
|
|
167
170
|
memory [list|search <q>] Manage saved memories
|
|
171
|
+
memory delete <key> Delete a memory by key
|
|
168
172
|
jobs List scheduled jobs
|
|
173
|
+
jobs delete <id> Delete a scheduled job
|
|
169
174
|
tasks List active tasks
|
|
175
|
+
tasks delete <id> Delete a task
|
|
170
176
|
sessions List chat sessions
|
|
177
|
+
sessions delete <id> Delete a session
|
|
171
178
|
events [--summary] View audit log
|
|
172
179
|
|
|
173
180
|
Options:
|
|
174
181
|
--provider, -p AI provider: xai, anthropic, openai, ollama (default: xai)
|
|
175
182
|
--model, -m Model name (default: grok-4-1-fast-reasoning)
|
|
176
183
|
--system, -s Custom system prompt (prepended to default)
|
|
184
|
+
--session Resume a specific session by ID
|
|
177
185
|
--db SQLite database path (default: ./dotbot.db)
|
|
178
186
|
--port Server port for 'serve' command
|
|
187
|
+
--openai Enable OpenAI-compatible API endpoints
|
|
188
|
+
--json Output as JSON (for inspection commands)
|
|
179
189
|
--verbose Show initialization logs
|
|
180
190
|
--help, -h Show help
|
|
181
191
|
--version, -v Show version
|
|
@@ -185,6 +195,9 @@ Environment Variables:
|
|
|
185
195
|
ANTHROPIC_API_KEY API key for Anthropic
|
|
186
196
|
OPENAI_API_KEY API key for OpenAI
|
|
187
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)
|
|
188
201
|
```
|
|
189
202
|
|
|
190
203
|
<br />
|
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,22 +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
|
|
96
101
|
|
|
97
102
|
Commands:
|
|
103
|
+
doctor Check environment and configuration
|
|
98
104
|
tools List all available tools
|
|
99
105
|
stats Show database statistics
|
|
100
106
|
memory [list|search <q>] Manage saved memories
|
|
107
|
+
memory delete <key> Delete a memory by key
|
|
101
108
|
jobs List scheduled jobs
|
|
109
|
+
jobs delete <id> Delete a scheduled job
|
|
102
110
|
tasks List active tasks
|
|
111
|
+
tasks delete <id> Delete a task
|
|
103
112
|
sessions List chat sessions
|
|
113
|
+
sessions delete <id> Delete a session
|
|
104
114
|
events [--summary] View audit log
|
|
105
115
|
|
|
106
116
|
Options:
|
|
107
117
|
--provider, -p AI provider: xai, anthropic, openai, ollama (default: xai)
|
|
108
118
|
--model, -m Model name (default: grok-4-1-fast-reasoning)
|
|
109
119
|
--system, -s Custom system prompt (prepended to default)
|
|
120
|
+
--session Resume a specific session by ID
|
|
110
121
|
--db SQLite database path (default: ${DEFAULT_DB})
|
|
111
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)
|
|
112
125
|
--verbose Show initialization logs
|
|
113
126
|
--help, -h Show this help
|
|
114
127
|
--version, -v Show version
|
|
@@ -119,20 +132,51 @@ Environment Variables:
|
|
|
119
132
|
OPENAI_API_KEY API key for OpenAI
|
|
120
133
|
OLLAMA_BASE_URL Base URL for Ollama (default: http://localhost:11434)
|
|
121
134
|
|
|
135
|
+
Config File:
|
|
136
|
+
~/.dotbotrc JSON config for defaults (provider, model, db)
|
|
137
|
+
|
|
122
138
|
Examples:
|
|
123
139
|
dotbot "What's the weather in SF?"
|
|
124
140
|
dotbot
|
|
125
141
|
dotbot serve --port 8080
|
|
142
|
+
dotbot doctor
|
|
126
143
|
dotbot tools
|
|
127
144
|
dotbot memory search "preferences"
|
|
145
|
+
dotbot memory delete user_pref
|
|
146
|
+
dotbot stats --json
|
|
128
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
|
|
129
151
|
`);
|
|
130
152
|
}
|
|
131
153
|
|
|
132
154
|
/**
|
|
133
|
-
*
|
|
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
|
|
134
176
|
*/
|
|
135
177
|
function parseCliArgs() {
|
|
178
|
+
const config = loadConfig();
|
|
179
|
+
|
|
136
180
|
try {
|
|
137
181
|
const { values, positionals } = parseArgs({
|
|
138
182
|
allowPositionals: true,
|
|
@@ -140,15 +184,29 @@ function parseCliArgs() {
|
|
|
140
184
|
help: { type: 'boolean', short: 'h', default: false },
|
|
141
185
|
version: { type: 'boolean', short: 'v', default: false },
|
|
142
186
|
verbose: { type: 'boolean', default: false },
|
|
143
|
-
provider: { type: 'string', short: 'p'
|
|
144
|
-
model: { type: 'string', short: 'm'
|
|
145
|
-
system: { type: 'string', short: 's'
|
|
187
|
+
provider: { type: 'string', short: 'p' },
|
|
188
|
+
model: { type: 'string', short: 'm' },
|
|
189
|
+
system: { type: 'string', short: 's' },
|
|
146
190
|
summary: { type: 'boolean', default: false },
|
|
147
|
-
|
|
148
|
-
|
|
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: '' },
|
|
149
196
|
},
|
|
150
197
|
});
|
|
151
|
-
|
|
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
|
+
};
|
|
152
210
|
} catch (err) {
|
|
153
211
|
console.error(`Error: ${err.message}`);
|
|
154
212
|
process.exit(1);
|
|
@@ -251,7 +309,20 @@ async function runChat(message, options) {
|
|
|
251
309
|
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
252
310
|
const provider = await getProviderConfig(options.provider);
|
|
253
311
|
|
|
254
|
-
|
|
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
|
+
}
|
|
255
326
|
|
|
256
327
|
const context = {
|
|
257
328
|
userID: 'cli-user',
|
|
@@ -260,8 +331,6 @@ async function runChat(message, options) {
|
|
|
260
331
|
...storesObj,
|
|
261
332
|
};
|
|
262
333
|
|
|
263
|
-
const messages = [{ role: 'user', content: message }];
|
|
264
|
-
|
|
265
334
|
process.stdout.write('\n[thinking] ');
|
|
266
335
|
startSpinner();
|
|
267
336
|
|
|
@@ -310,8 +379,20 @@ async function runRepl(options) {
|
|
|
310
379
|
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
311
380
|
const provider = await getProviderConfig(options.provider);
|
|
312
381
|
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
}
|
|
315
396
|
|
|
316
397
|
const context = {
|
|
317
398
|
userID: 'cli-user',
|
|
@@ -326,6 +407,9 @@ async function runRepl(options) {
|
|
|
326
407
|
});
|
|
327
408
|
|
|
328
409
|
console.log(`\ndotbot v${VERSION} — ${options.provider}/${options.model}`);
|
|
410
|
+
if (options.session) {
|
|
411
|
+
console.log(`Resuming session: ${session.id}`);
|
|
412
|
+
}
|
|
329
413
|
console.log('Type /quit to exit, /clear to reset conversation\n');
|
|
330
414
|
|
|
331
415
|
const prompt = () => {
|
|
@@ -436,6 +520,140 @@ async function runServer(options) {
|
|
|
436
520
|
return;
|
|
437
521
|
}
|
|
438
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
|
+
|
|
439
657
|
// Chat endpoint
|
|
440
658
|
if (req.method === 'POST' && url.pathname === '/chat') {
|
|
441
659
|
let body = '';
|
|
@@ -503,15 +721,30 @@ async function runServer(options) {
|
|
|
503
721
|
console.log(`Listening on http://localhost:${port}`);
|
|
504
722
|
console.log(`\nEndpoints:`);
|
|
505
723
|
console.log(` GET /health Health check`);
|
|
506
|
-
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();
|
|
507
731
|
});
|
|
508
732
|
}
|
|
509
733
|
|
|
510
734
|
/**
|
|
511
735
|
* List all available tools.
|
|
736
|
+
*
|
|
737
|
+
* @param {Object} options - CLI options
|
|
512
738
|
*/
|
|
513
|
-
async function runTools() {
|
|
739
|
+
async function runTools(options) {
|
|
514
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
|
+
|
|
515
748
|
console.log(`\ndotbot tools (${coreTools.length})\n`);
|
|
516
749
|
|
|
517
750
|
// Group tools by category based on name prefix
|
|
@@ -539,27 +772,39 @@ async function runTools() {
|
|
|
539
772
|
async function runStats(options) {
|
|
540
773
|
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
541
774
|
|
|
542
|
-
console.log(`\ndotbot stats\n`);
|
|
543
|
-
console.log(` Database: ${options.db}`);
|
|
544
|
-
|
|
545
775
|
// Sessions
|
|
546
776
|
const sessions = await storesObj.sessionStore.listSessions('cli-user');
|
|
547
|
-
console.log(` Sessions: ${sessions.length}`);
|
|
548
777
|
|
|
549
778
|
// Memory
|
|
550
779
|
const memories = await storesObj.memoryStore.getAllMemories('cli-user');
|
|
551
|
-
console.log(` Memories: ${memories.length}`);
|
|
552
780
|
|
|
553
781
|
// Jobs (need to get session IDs first)
|
|
554
782
|
const jobs = await storesObj.cronStore.listTasksBySessionIds(['default'], 'cli-user');
|
|
555
|
-
console.log(` Jobs: ${jobs.length}`);
|
|
556
783
|
|
|
557
784
|
// Tasks
|
|
558
785
|
const tasks = await storesObj.taskStore.getTasks('cli-user');
|
|
559
|
-
console.log(` Tasks: ${tasks.length}`);
|
|
560
786
|
|
|
561
787
|
// Triggers
|
|
562
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}`);
|
|
563
808
|
console.log(` Triggers: ${triggers.length}`);
|
|
564
809
|
|
|
565
810
|
console.log();
|
|
@@ -569,14 +814,28 @@ async function runStats(options) {
|
|
|
569
814
|
* Manage memories.
|
|
570
815
|
*
|
|
571
816
|
* @param {Object} options - CLI options
|
|
572
|
-
* @param {string} subcommand - list or
|
|
573
|
-
* @param {string} query - Search query
|
|
817
|
+
* @param {string} subcommand - list, search, or delete
|
|
818
|
+
* @param {string} query - Search query or key to delete
|
|
574
819
|
*/
|
|
575
820
|
async function runMemory(options, subcommand, query) {
|
|
576
821
|
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
577
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
|
+
|
|
578
833
|
if (subcommand === 'search' && query) {
|
|
579
834
|
const results = await storesObj.memoryStore.readMemoryPattern('cli-user', `%${query}%`);
|
|
835
|
+
if (options.json) {
|
|
836
|
+
console.log(JSON.stringify(results));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
580
839
|
console.log(`\nMemory search: "${query}" (${results.length} results)\n`);
|
|
581
840
|
for (const mem of results) {
|
|
582
841
|
const val = typeof mem.value === 'string' ? mem.value : JSON.stringify(mem.value);
|
|
@@ -584,6 +843,10 @@ async function runMemory(options, subcommand, query) {
|
|
|
584
843
|
}
|
|
585
844
|
} else {
|
|
586
845
|
const memories = await storesObj.memoryStore.getAllMemories('cli-user');
|
|
846
|
+
if (options.json) {
|
|
847
|
+
console.log(JSON.stringify(memories));
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
587
850
|
console.log(`\nMemories (${memories.length})\n`);
|
|
588
851
|
for (const mem of memories) {
|
|
589
852
|
const val = typeof mem.value === 'string' ? mem.value : JSON.stringify(mem.value);
|
|
@@ -594,14 +857,32 @@ async function runMemory(options, subcommand, query) {
|
|
|
594
857
|
}
|
|
595
858
|
|
|
596
859
|
/**
|
|
597
|
-
*
|
|
860
|
+
* Manage scheduled jobs.
|
|
598
861
|
*
|
|
599
862
|
* @param {Object} options - CLI options
|
|
863
|
+
* @param {string} subcommand - list or delete
|
|
864
|
+
* @param {string} jobId - Job ID to delete
|
|
600
865
|
*/
|
|
601
|
-
async function runJobs(options) {
|
|
866
|
+
async function runJobs(options, subcommand, jobId) {
|
|
602
867
|
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
603
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
|
+
|
|
604
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
|
+
|
|
605
886
|
console.log(`\nScheduled jobs (${jobs.length})\n`);
|
|
606
887
|
|
|
607
888
|
for (const job of jobs) {
|
|
@@ -617,14 +898,32 @@ async function runJobs(options) {
|
|
|
617
898
|
}
|
|
618
899
|
|
|
619
900
|
/**
|
|
620
|
-
*
|
|
901
|
+
* Manage active tasks.
|
|
621
902
|
*
|
|
622
903
|
* @param {Object} options - CLI options
|
|
904
|
+
* @param {string} subcommand - list or delete
|
|
905
|
+
* @param {string} taskId - Task ID to delete
|
|
623
906
|
*/
|
|
624
|
-
async function runTasks(options) {
|
|
907
|
+
async function runTasks(options, subcommand, taskId) {
|
|
625
908
|
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
626
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
|
+
|
|
627
920
|
const tasks = await storesObj.taskStore.getTasks('cli-user');
|
|
921
|
+
|
|
922
|
+
if (options.json) {
|
|
923
|
+
console.log(JSON.stringify(tasks));
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
628
927
|
console.log(`\nTasks (${tasks.length})\n`);
|
|
629
928
|
|
|
630
929
|
for (const task of tasks) {
|
|
@@ -639,14 +938,32 @@ async function runTasks(options) {
|
|
|
639
938
|
}
|
|
640
939
|
|
|
641
940
|
/**
|
|
642
|
-
*
|
|
941
|
+
* Manage chat sessions.
|
|
643
942
|
*
|
|
644
943
|
* @param {Object} options - CLI options
|
|
944
|
+
* @param {string} subcommand - list or delete
|
|
945
|
+
* @param {string} sessionId - Session ID to delete
|
|
645
946
|
*/
|
|
646
|
-
async function runSessions(options) {
|
|
947
|
+
async function runSessions(options, subcommand, sessionId) {
|
|
647
948
|
const storesObj = await initStores(options.db, options.verbose, options.system);
|
|
648
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
|
+
|
|
649
960
|
const sessions = await storesObj.sessionStore.listSessions('cli-user');
|
|
961
|
+
|
|
962
|
+
if (options.json) {
|
|
963
|
+
console.log(JSON.stringify(sessions));
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
650
967
|
console.log(`\nSessions (${sessions.length})\n`);
|
|
651
968
|
|
|
652
969
|
for (const session of sessions) {
|
|
@@ -670,6 +987,10 @@ async function runEvents(options) {
|
|
|
670
987
|
|
|
671
988
|
if (options.summary) {
|
|
672
989
|
const summary = await storesObj.eventStore.summary({ userId: 'cli-user' });
|
|
990
|
+
if (options.json) {
|
|
991
|
+
console.log(JSON.stringify(summary));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
673
994
|
console.log(`\nEvent summary\n`);
|
|
674
995
|
console.log(` Total events: ${summary.total || 0}`);
|
|
675
996
|
if (summary.breakdown) {
|
|
@@ -679,6 +1000,10 @@ async function runEvents(options) {
|
|
|
679
1000
|
}
|
|
680
1001
|
} else {
|
|
681
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
|
+
}
|
|
682
1007
|
console.log(`\nRecent events (${events.length})\n`);
|
|
683
1008
|
|
|
684
1009
|
for (const event of events) {
|
|
@@ -693,6 +1018,71 @@ async function runEvents(options) {
|
|
|
693
1018
|
console.log();
|
|
694
1019
|
}
|
|
695
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
|
+
|
|
696
1086
|
/**
|
|
697
1087
|
* Main entry point.
|
|
698
1088
|
*/
|
|
@@ -711,12 +1101,28 @@ async function main() {
|
|
|
711
1101
|
|
|
712
1102
|
const command = args.positionals[0];
|
|
713
1103
|
|
|
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();
|
|
1111
|
+
if (message) {
|
|
1112
|
+
await runChat(message, args);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
714
1117
|
switch (command) {
|
|
1118
|
+
case 'doctor':
|
|
1119
|
+
await runDoctor(args);
|
|
1120
|
+
break;
|
|
715
1121
|
case 'serve':
|
|
716
1122
|
await runServer(args);
|
|
717
1123
|
break;
|
|
718
1124
|
case 'tools':
|
|
719
|
-
await runTools();
|
|
1125
|
+
await runTools(args);
|
|
720
1126
|
break;
|
|
721
1127
|
case 'stats':
|
|
722
1128
|
await runStats(args);
|
|
@@ -725,13 +1131,13 @@ async function main() {
|
|
|
725
1131
|
await runMemory(args, args.positionals[1], args.positionals.slice(2).join(' '));
|
|
726
1132
|
break;
|
|
727
1133
|
case 'jobs':
|
|
728
|
-
await runJobs(args);
|
|
1134
|
+
await runJobs(args, args.positionals[1], args.positionals[2]);
|
|
729
1135
|
break;
|
|
730
1136
|
case 'tasks':
|
|
731
|
-
await runTasks(args);
|
|
1137
|
+
await runTasks(args, args.positionals[1], args.positionals[2]);
|
|
732
1138
|
break;
|
|
733
1139
|
case 'sessions':
|
|
734
|
-
await runSessions(args);
|
|
1140
|
+
await runSessions(args, args.positionals[1], args.positionals[2]);
|
|
735
1141
|
break;
|
|
736
1142
|
case 'events':
|
|
737
1143
|
await runEvents(args);
|
package/package.json
CHANGED