@mmmbuto/nexuscli 0.8.9 โ†’ 0.9.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.
@@ -1,783 +0,0 @@
1
- # NexusCLI Database Schema - SQLite
2
-
3
- **Version**: 0.1.0
4
- **Created**: 2025-11-17
5
- **Database**: SQLite (better-sqlite3)
6
- **Philosophy**: Minimal, portable, chat-friendly
7
-
8
- ---
9
-
10
- ## ๐ŸŽฏ Why SQLite?
11
-
12
- ### Alignment with Three Pillars
13
-
14
- **1. Lightweight** โœ…
15
- - Single dependency: `better-sqlite3` (~500KB native)
16
- - No server process (embedded)
17
- - Single file database (< 10MB for 1000s of messages)
18
-
19
- **2. Portable (Linux + Termux)** โœ…
20
- - Works on x86_64 and aarch64 (Termux)
21
- - Pre-compiled binaries for Android
22
- - Single `.db` file (easy backup)
23
-
24
- **3. ChatGPT UI** โœ…
25
- - Perfect for conversation history
26
- - Fast full-text search (`FTS5`)
27
- - Efficient pagination
28
-
29
- ### Alternatives Considered
30
-
31
- | Option | Pros | Cons | Verdict |
32
- |--------|------|------|---------|
33
- | **SQLite** | Lightweight, portable, SQL | Native dependency | โœ… **CHOSEN** |
34
- | JSON files | Zero deps, simple | Slow search, no relations | โŒ Not scalable |
35
- | MongoDB | Powerful | Heavy, not Termux-friendly | โŒ Too heavy |
36
- | LevelDB | Fast K-V store | No SQL, harder queries | โŒ Not chat-friendly |
37
-
38
- ---
39
-
40
- ## ๐Ÿ“ฆ Installation
41
-
42
- ### Dependency
43
-
44
- ```json
45
- // backend/package.json
46
- {
47
- "dependencies": {
48
- "express": "^4.18.2",
49
- "cors": "^2.8.5",
50
- "uuid": "^9.0.0",
51
- "better-sqlite3": "^9.2.2" // โ† Only new dependency
52
- }
53
- }
54
- ```
55
-
56
- **Total dependencies**: 4 packages (still < 10 โœ…)
57
-
58
- ### Setup (Linux)
59
-
60
- ```bash
61
- cd backend
62
- npm install better-sqlite3
63
- # Native compilation (uses node-gyp)
64
- ```
65
-
66
- ### Setup (Termux)
67
-
68
- ```bash
69
- # Termux requires build tools
70
- pkg install clang make python
71
-
72
- # Install better-sqlite3 (compiles for aarch64)
73
- npm install better-sqlite3
74
- ```
75
-
76
- **Note**: Pre-built binaries usually available, compilation is fallback.
77
-
78
- ---
79
-
80
- ## ๐Ÿ—„๏ธ Schema Design
81
-
82
- ### Database File Location
83
-
84
- **Linux**:
85
- ```
86
- /var/lib/nexuscli/nexuscli.db
87
- ```
88
-
89
- **Termux**:
90
- ```
91
- /data/data/com.termux/files/home/.nexuscli/nexuscli.db
92
- ```
93
-
94
- **Code**:
95
- ```javascript
96
- // config.js
97
- const isTermux = process.env.PREFIX?.includes('com.termux');
98
-
99
- const DB_PATH = isTermux
100
- ? `${process.env.HOME}/.nexuscli/nexuscli.db`
101
- : '/var/lib/nexuscli/nexuscli.db';
102
- ```
103
-
104
- ---
105
-
106
- ## ๐Ÿ“Š Tables
107
-
108
- ### 1. `conversations`
109
-
110
- Tracks chat sessions (like ChatGPT conversations).
111
-
112
- ```sql
113
- CREATE TABLE conversations (
114
- id TEXT PRIMARY KEY, -- UUID
115
- title TEXT NOT NULL, -- "Deploy to Production"
116
- created_at INTEGER NOT NULL, -- Unix timestamp (ms)
117
- updated_at INTEGER NOT NULL, -- Unix timestamp (ms)
118
- metadata TEXT, -- JSON: {"tags": ["prod"], "archived": false}
119
-
120
- INDEX idx_conversations_updated_at ON conversations(updated_at DESC)
121
- );
122
- ```
123
-
124
- **Example Row**:
125
- ```json
126
- {
127
- "id": "conv-abc123",
128
- "title": "Deploy NexusCLI to Production",
129
- "created_at": 1700000000000,
130
- "updated_at": 1700001000000,
131
- "metadata": "{\"tags\":[\"prod\",\"deploy\"],\"archived\":false}"
132
- }
133
- ```
134
-
135
- ---
136
-
137
- ### 2. `messages`
138
-
139
- Individual messages in conversations (user prompts + assistant responses).
140
-
141
- ```sql
142
- CREATE TABLE messages (
143
- id TEXT PRIMARY KEY, -- UUID
144
- conversation_id TEXT NOT NULL, -- FK to conversations.id
145
- role TEXT NOT NULL, -- 'user' | 'assistant' | 'system'
146
- content TEXT NOT NULL, -- Markdown text
147
- created_at INTEGER NOT NULL, -- Unix timestamp (ms)
148
- metadata TEXT, -- JSON: {"streaming": true, "tokens": 123}
149
-
150
- FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
151
- INDEX idx_messages_conversation_id ON messages(conversation_id),
152
- INDEX idx_messages_created_at ON messages(created_at ASC)
153
- );
154
- ```
155
-
156
- **Example Rows**:
157
- ```json
158
- // User message
159
- {
160
- "id": "msg-001",
161
- "conversation_id": "conv-abc123",
162
- "role": "user",
163
- "content": "Deploy to all production nodes",
164
- "created_at": 1700000000000,
165
- "metadata": null
166
- }
167
-
168
- // Assistant message
169
- {
170
- "id": "msg-002",
171
- "conversation_id": "conv-abc123",
172
- "role": "assistant",
173
- "content": "Deploying to 3 nodes:\n- prod-001 โœ…\n- prod-002 โœ…\n- prod-003 โš ๏ธ",
174
- "created_at": 1700000001000,
175
- "metadata": "{\"tokens\":45,\"duration\":1234}"
176
- }
177
- ```
178
-
179
- ---
180
-
181
- ### 3. `jobs`
182
-
183
- Job execution records (linked to messages).
184
-
185
- ```sql
186
- CREATE TABLE jobs (
187
- id TEXT PRIMARY KEY, -- Job ID (UUID)
188
- conversation_id TEXT, -- FK to conversations.id (optional)
189
- message_id TEXT, -- FK to messages.id (optional)
190
- node_id TEXT NOT NULL, -- Target node
191
- tool TEXT NOT NULL, -- 'bash' | 'git' | 'docker'
192
- command TEXT NOT NULL, -- Command executed
193
- status TEXT NOT NULL, -- 'queued' | 'executing' | 'completed' | 'failed'
194
- exit_code INTEGER, -- Exit code (NULL if not completed)
195
- stdout TEXT, -- Command stdout
196
- stderr TEXT, -- Command stderr
197
- duration INTEGER, -- Execution time (ms)
198
- created_at INTEGER NOT NULL, -- Unix timestamp (ms)
199
- started_at INTEGER, -- Unix timestamp (ms)
200
- completed_at INTEGER, -- Unix timestamp (ms)
201
-
202
- FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE SET NULL,
203
- FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE SET NULL,
204
- INDEX idx_jobs_conversation_id ON jobs(conversation_id),
205
- INDEX idx_jobs_status ON jobs(status),
206
- INDEX idx_jobs_created_at ON jobs(created_at DESC)
207
- );
208
- ```
209
-
210
- **Example Row**:
211
- ```json
212
- {
213
- "id": "job-xyz",
214
- "conversation_id": "conv-abc123",
215
- "message_id": "msg-002",
216
- "node_id": "prod-001",
217
- "tool": "bash",
218
- "command": "systemctl restart nginx",
219
- "status": "completed",
220
- "exit_code": 0,
221
- "stdout": "nginx.service restarted",
222
- "stderr": "",
223
- "duration": 1234,
224
- "created_at": 1700000000000,
225
- "started_at": 1700000000100,
226
- "completed_at": 1700000001334
227
- }
228
- ```
229
-
230
- ---
231
-
232
- ### 4. `nodes` (Optional - for multi-node setups)
233
-
234
- Registered nodes.
235
-
236
- ```sql
237
- CREATE TABLE nodes (
238
- id TEXT PRIMARY KEY, -- Node ID
239
- hostname TEXT NOT NULL, -- Node hostname
240
- ip_address TEXT, -- Node IP
241
- status TEXT NOT NULL, -- 'online' | 'offline'
242
- capabilities TEXT, -- JSON: {"tools": ["bash", "git"]}
243
- last_heartbeat INTEGER, -- Unix timestamp (ms)
244
- created_at INTEGER NOT NULL, -- Unix timestamp (ms)
245
-
246
- INDEX idx_nodes_status ON nodes(status)
247
- );
248
- ```
249
-
250
- ---
251
-
252
- ## ๐Ÿ” Full-Text Search (Optional Enhancement)
253
-
254
- For searching chat history.
255
-
256
- ```sql
257
- -- Virtual FTS5 table for messages
258
- CREATE VIRTUAL TABLE messages_fts USING fts5(
259
- content,
260
- conversation_id UNINDEXED,
261
- content='messages',
262
- content_rowid='rowid'
263
- );
264
-
265
- -- Triggers to keep FTS index synced
266
- CREATE TRIGGER messages_fts_insert AFTER INSERT ON messages BEGIN
267
- INSERT INTO messages_fts(rowid, content, conversation_id)
268
- VALUES (new.rowid, new.content, new.conversation_id);
269
- END;
270
-
271
- CREATE TRIGGER messages_fts_delete AFTER DELETE ON messages BEGIN
272
- DELETE FROM messages_fts WHERE rowid = old.rowid;
273
- END;
274
-
275
- CREATE TRIGGER messages_fts_update AFTER UPDATE ON messages BEGIN
276
- UPDATE messages_fts SET content = new.content WHERE rowid = new.rowid;
277
- END;
278
- ```
279
-
280
- **Usage**:
281
- ```sql
282
- -- Search messages for "nginx"
283
- SELECT m.* FROM messages m
284
- JOIN messages_fts fts ON fts.rowid = m.rowid
285
- WHERE messages_fts MATCH 'nginx'
286
- ORDER BY m.created_at DESC
287
- LIMIT 20;
288
- ```
289
-
290
- ---
291
-
292
- ## ๐Ÿ’พ Database Wrapper
293
-
294
- ### Initialization
295
-
296
- ```javascript
297
- // backend/db.js
298
- const Database = require('better-sqlite3');
299
- const path = require('path');
300
- const fs = require('fs');
301
-
302
- const isTermux = process.env.PREFIX?.includes('com.termux');
303
- const dbDir = isTermux
304
- ? path.join(process.env.HOME, '.nexuscli')
305
- : '/var/lib/nexuscli';
306
-
307
- const dbPath = path.join(dbDir, 'nexuscli.db');
308
-
309
- // Ensure directory exists
310
- if (!fs.existsSync(dbDir)) {
311
- fs.mkdirSync(dbDir, { recursive: true });
312
- }
313
-
314
- // Open database (creates if doesn't exist)
315
- const db = new Database(dbPath);
316
-
317
- // Enable WAL mode for better concurrency
318
- db.pragma('journal_mode = WAL');
319
-
320
- // Enable foreign keys
321
- db.pragma('foreign_keys = ON');
322
-
323
- // Initialize schema
324
- function initSchema() {
325
- db.exec(`
326
- CREATE TABLE IF NOT EXISTS conversations (
327
- id TEXT PRIMARY KEY,
328
- title TEXT NOT NULL,
329
- created_at INTEGER NOT NULL,
330
- updated_at INTEGER NOT NULL,
331
- metadata TEXT
332
- );
333
-
334
- CREATE INDEX IF NOT EXISTS idx_conversations_updated_at
335
- ON conversations(updated_at DESC);
336
-
337
- CREATE TABLE IF NOT EXISTS messages (
338
- id TEXT PRIMARY KEY,
339
- conversation_id TEXT NOT NULL,
340
- role TEXT NOT NULL,
341
- content TEXT NOT NULL,
342
- created_at INTEGER NOT NULL,
343
- metadata TEXT,
344
- FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
345
- );
346
-
347
- CREATE INDEX IF NOT EXISTS idx_messages_conversation_id
348
- ON messages(conversation_id);
349
-
350
- CREATE INDEX IF NOT EXISTS idx_messages_created_at
351
- ON messages(created_at ASC);
352
-
353
- CREATE TABLE IF NOT EXISTS jobs (
354
- id TEXT PRIMARY KEY,
355
- conversation_id TEXT,
356
- message_id TEXT,
357
- node_id TEXT NOT NULL,
358
- tool TEXT NOT NULL,
359
- command TEXT NOT NULL,
360
- status TEXT NOT NULL,
361
- exit_code INTEGER,
362
- stdout TEXT,
363
- stderr TEXT,
364
- duration INTEGER,
365
- created_at INTEGER NOT NULL,
366
- started_at INTEGER,
367
- completed_at INTEGER,
368
- FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE SET NULL,
369
- FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE SET NULL
370
- );
371
-
372
- CREATE INDEX IF NOT EXISTS idx_jobs_conversation_id
373
- ON jobs(conversation_id);
374
-
375
- CREATE INDEX IF NOT EXISTS idx_jobs_status
376
- ON jobs(status);
377
-
378
- CREATE INDEX IF NOT EXISTS idx_jobs_created_at
379
- ON jobs(created_at DESC);
380
- `);
381
-
382
- console.log(`โœ… Database initialized: ${dbPath}`);
383
- }
384
-
385
- initSchema();
386
-
387
- module.exports = db;
388
- ```
389
-
390
- ---
391
-
392
- ### CRUD Operations
393
-
394
- ```javascript
395
- // backend/models/conversation.js
396
- const db = require('../db');
397
- const { v4: uuidv4 } = require('uuid');
398
-
399
- class Conversation {
400
- // Create new conversation
401
- static create(title) {
402
- const id = uuidv4();
403
- const now = Date.now();
404
-
405
- const stmt = db.prepare(`
406
- INSERT INTO conversations (id, title, created_at, updated_at)
407
- VALUES (?, ?, ?, ?)
408
- `);
409
-
410
- stmt.run(id, title, now, now);
411
-
412
- return { id, title, created_at: now, updated_at: now };
413
- }
414
-
415
- // Get conversation by ID
416
- static getById(id) {
417
- const stmt = db.prepare('SELECT * FROM conversations WHERE id = ?');
418
- return stmt.get(id);
419
- }
420
-
421
- // List recent conversations
422
- static listRecent(limit = 20) {
423
- const stmt = db.prepare(`
424
- SELECT * FROM conversations
425
- ORDER BY updated_at DESC
426
- LIMIT ?
427
- `);
428
- return stmt.all(limit);
429
- }
430
-
431
- // Update conversation title
432
- static updateTitle(id, title) {
433
- const stmt = db.prepare(`
434
- UPDATE conversations
435
- SET title = ?, updated_at = ?
436
- WHERE id = ?
437
- `);
438
- stmt.run(title, Date.now(), id);
439
- }
440
-
441
- // Delete conversation (cascade deletes messages)
442
- static delete(id) {
443
- const stmt = db.prepare('DELETE FROM conversations WHERE id = ?');
444
- stmt.run(id);
445
- }
446
- }
447
-
448
- module.exports = Conversation;
449
- ```
450
-
451
- ```javascript
452
- // backend/models/message.js
453
- const db = require('../db');
454
- const { v4: uuidv4 } = require('uuid');
455
-
456
- class Message {
457
- // Add message to conversation
458
- static create(conversationId, role, content, metadata = null) {
459
- const id = uuidv4();
460
- const now = Date.now();
461
-
462
- const stmt = db.prepare(`
463
- INSERT INTO messages (id, conversation_id, role, content, created_at, metadata)
464
- VALUES (?, ?, ?, ?, ?, ?)
465
- `);
466
-
467
- stmt.run(id, conversationId, role, content, now,
468
- metadata ? JSON.stringify(metadata) : null);
469
-
470
- // Update conversation updated_at
471
- const updateConv = db.prepare(`
472
- UPDATE conversations SET updated_at = ? WHERE id = ?
473
- `);
474
- updateConv.run(now, conversationId);
475
-
476
- return { id, conversation_id: conversationId, role, content, created_at: now };
477
- }
478
-
479
- // Get messages for conversation
480
- static getByConversation(conversationId, limit = 100) {
481
- const stmt = db.prepare(`
482
- SELECT * FROM messages
483
- WHERE conversation_id = ?
484
- ORDER BY created_at ASC
485
- LIMIT ?
486
- `);
487
- return stmt.all(conversationId, limit);
488
- }
489
-
490
- // Search messages (if FTS enabled)
491
- static search(query, limit = 20) {
492
- const stmt = db.prepare(`
493
- SELECT m.* FROM messages m
494
- JOIN messages_fts fts ON fts.rowid = m.rowid
495
- WHERE messages_fts MATCH ?
496
- ORDER BY m.created_at DESC
497
- LIMIT ?
498
- `);
499
- return stmt.all(query, limit);
500
- }
501
- }
502
-
503
- module.exports = Message;
504
- ```
505
-
506
- ---
507
-
508
- ## ๐Ÿ”„ Migration Strategy
509
-
510
- ### Initial Setup (v0.1.0)
511
-
512
- ```javascript
513
- // backend/migrations/001-initial.js
514
- module.exports = {
515
- up(db) {
516
- db.exec(`
517
- CREATE TABLE conversations (...);
518
- CREATE TABLE messages (...);
519
- CREATE TABLE jobs (...);
520
- `);
521
- },
522
-
523
- down(db) {
524
- db.exec(`
525
- DROP TABLE IF EXISTS jobs;
526
- DROP TABLE IF EXISTS messages;
527
- DROP TABLE IF EXISTS conversations;
528
- `);
529
- }
530
- };
531
- ```
532
-
533
- ### Migration Runner
534
-
535
- ```javascript
536
- // backend/migrate.js
537
- const db = require('./db');
538
- const fs = require('fs');
539
- const path = require('path');
540
-
541
- function getCurrentVersion() {
542
- try {
543
- const row = db.prepare('SELECT value FROM schema_version WHERE key = "version"').get();
544
- return parseInt(row.value);
545
- } catch {
546
- // First run
547
- db.exec('CREATE TABLE IF NOT EXISTS schema_version (key TEXT PRIMARY KEY, value TEXT)');
548
- db.prepare('INSERT INTO schema_version (key, value) VALUES ("version", "0")').run();
549
- return 0;
550
- }
551
- }
552
-
553
- function runMigrations() {
554
- const currentVersion = getCurrentVersion();
555
- const migrationsDir = path.join(__dirname, 'migrations');
556
-
557
- const migrations = fs.readdirSync(migrationsDir)
558
- .filter(f => f.endsWith('.js'))
559
- .sort();
560
-
561
- for (const file of migrations) {
562
- const migration = require(path.join(migrationsDir, file));
563
- const version = parseInt(file.split('-')[0]);
564
-
565
- if (version > currentVersion) {
566
- console.log(`Running migration ${file}...`);
567
- migration.up(db);
568
- db.prepare('UPDATE schema_version SET value = ? WHERE key = "version"').run(version);
569
- }
570
- }
571
-
572
- console.log('โœ… Migrations complete');
573
- }
574
-
575
- runMigrations();
576
- ```
577
-
578
- ---
579
-
580
- ## ๐Ÿ“ˆ Performance Optimization
581
-
582
- ### WAL Mode (Write-Ahead Logging)
583
-
584
- ```javascript
585
- // Already enabled in db.js
586
- db.pragma('journal_mode = WAL');
587
- ```
588
-
589
- **Benefits**:
590
- - Concurrent reads while writing
591
- - Faster writes
592
- - Better crash recovery
593
-
594
- ### Prepared Statements
595
-
596
- ```javascript
597
- // โœ… GOOD: Reuse prepared statement
598
- const stmt = db.prepare('SELECT * FROM messages WHERE conversation_id = ?');
599
- const messages = stmt.all(conversationId);
600
-
601
- // โŒ BAD: Parse SQL every time
602
- db.prepare('SELECT * FROM messages WHERE conversation_id = ?').all(conversationId);
603
- ```
604
-
605
- ### Indexes
606
-
607
- All critical queries have indexes:
608
- - `conversations.updated_at` (recent list)
609
- - `messages.conversation_id` (get messages)
610
- - `messages.created_at` (chronological order)
611
- - `jobs.status` (filter by status)
612
-
613
- ---
614
-
615
- ## ๐Ÿ’พ Backup & Recovery
616
-
617
- ### Backup Script
618
-
619
- ```bash
620
- #!/bin/bash
621
- # backup-db.sh
622
-
623
- DB_PATH="/var/lib/nexuscli/nexuscli.db"
624
- BACKUP_DIR="/var/backups/nexuscli"
625
- TIMESTAMP=$(date +%Y%m%d_%H%M%S)
626
-
627
- mkdir -p "$BACKUP_DIR"
628
-
629
- # SQLite backup (safe even with active connections)
630
- sqlite3 "$DB_PATH" ".backup $BACKUP_DIR/nexuscli-$TIMESTAMP.db"
631
-
632
- # Keep last 30 days
633
- find "$BACKUP_DIR" -name "nexuscli-*.db" -mtime +30 -delete
634
-
635
- echo "โœ… Backup complete: $BACKUP_DIR/nexuscli-$TIMESTAMP.db"
636
- ```
637
-
638
- ### Restore
639
-
640
- ```bash
641
- # Stop NexusCLI
642
- systemctl stop nexuscli
643
-
644
- # Restore backup
645
- cp /var/backups/nexuscli/nexuscli-20251117.db /var/lib/nexuscli/nexuscli.db
646
-
647
- # Start NexusCLI
648
- systemctl start nexuscli
649
- ```
650
-
651
- ---
652
-
653
- ## ๐ŸŽฏ API Integration
654
-
655
- ### REST Endpoints
656
-
657
- ```javascript
658
- // backend/routes/conversations.js
659
- const express = require('express');
660
- const Conversation = require('../models/conversation');
661
- const Message = require('../models/message');
662
-
663
- const router = express.Router();
664
-
665
- // GET /api/v1/conversations - List recent
666
- router.get('/', (req, res) => {
667
- const conversations = Conversation.listRecent(20);
668
- res.json({ conversations });
669
- });
670
-
671
- // POST /api/v1/conversations - Create new
672
- router.post('/', (req, res) => {
673
- const { title } = req.body;
674
- const conversation = Conversation.create(title || 'New Conversation');
675
- res.status(201).json(conversation);
676
- });
677
-
678
- // GET /api/v1/conversations/:id - Get conversation with messages
679
- router.get('/:id', (req, res) => {
680
- const conversation = Conversation.getById(req.params.id);
681
- if (!conversation) {
682
- return res.status(404).json({ error: 'Conversation not found' });
683
- }
684
-
685
- const messages = Message.getByConversation(req.params.id);
686
- res.json({ ...conversation, messages });
687
- });
688
-
689
- // POST /api/v1/conversations/:id/messages - Add message
690
- router.post('/:id/messages', (req, res) => {
691
- const { role, content } = req.body;
692
- const message = Message.create(req.params.id, role, content);
693
- res.status(201).json(message);
694
- });
695
-
696
- // DELETE /api/v1/conversations/:id - Delete conversation
697
- router.delete('/:id', (req, res) => {
698
- Conversation.delete(req.params.id);
699
- res.json({ success: true });
700
- });
701
-
702
- module.exports = router;
703
- ```
704
-
705
- ---
706
-
707
- ## ๐Ÿงช Testing
708
-
709
- ### Unit Tests (SQLite in-memory)
710
-
711
- ```javascript
712
- // backend/tests/conversation.test.js
713
- const Database = require('better-sqlite3');
714
- const Conversation = require('../models/conversation');
715
-
716
- // Use in-memory DB for tests
717
- const testDb = new Database(':memory:');
718
-
719
- beforeEach(() => {
720
- // Initialize schema
721
- testDb.exec(`CREATE TABLE conversations (...)`);
722
- });
723
-
724
- test('create conversation', () => {
725
- const conv = Conversation.create('Test Conversation');
726
- expect(conv.title).toBe('Test Conversation');
727
- expect(conv.id).toBeTruthy();
728
- });
729
-
730
- test('list recent conversations', () => {
731
- Conversation.create('Conv 1');
732
- Conversation.create('Conv 2');
733
-
734
- const list = Conversation.listRecent();
735
- expect(list.length).toBe(2);
736
- expect(list[0].title).toBe('Conv 2'); // Most recent first
737
- });
738
- ```
739
-
740
- ---
741
-
742
- ## ๐Ÿ“Š Size Estimates
743
-
744
- | Data | Rows | Size per Row | Total Size |
745
- |------|------|--------------|------------|
746
- | Conversations | 100 | ~200 bytes | ~20 KB |
747
- | Messages | 10,000 | ~500 bytes | ~5 MB |
748
- | Jobs | 5,000 | ~1 KB | ~5 MB |
749
- | **Total** | - | - | **~10 MB** |
750
-
751
- **Conclusion**: Even with heavy usage, DB stays < 50MB (lightweight โœ…)
752
-
753
- ---
754
-
755
- ## ๐ŸŽฏ Alignment with Three Pillars
756
-
757
- ### 1. Lightweight โœ…
758
- - Single dependency: `better-sqlite3`
759
- - Embedded DB (no server)
760
- - WAL mode (< 10MB typical size)
761
-
762
- ### 2. Portable โœ…
763
- - Works on Linux x86_64
764
- - Works on Termux aarch64
765
- - Single `.db` file (easy sync)
766
-
767
- ### 3. ChatGPT UI โœ…
768
- - Perfect for conversation history
769
- - Fast message retrieval
770
- - Full-text search support
771
-
772
- ---
773
-
774
- ## ๐Ÿ“š Related Documents
775
-
776
- - [Design Principles](./DESIGN_PRINCIPLES.md) - Three pillars
777
- - [Architecture Overview](./ARCHITECTURE.md) - System design
778
- - [API Contract](./API_WRAPPER_CONTRACT.md) - REST API spec
779
-
780
- ---
781
-
782
- _Database design for NexusCLI - Lightweight, Portable, Chat-Friendly_
783
- _Generated by Claude Code (Sonnet 4.5) - 2025-11-17_