quilltap 4.10.0-dev.59 → 4.10.0-dev.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/bin/quilltap.js +13 -22
- package/lib/db-commands.js +12 -0
- package/lib/db-helpers.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -193,6 +193,8 @@ quilltap db --mount-points --tables # Target the mount index DB
|
|
|
193
193
|
|
|
194
194
|
The database is opened **read-only by default**. Add `--write` to make changes: it opens the database read-write, **claims the instance lock** (`<dataDir>/quilltap.lock`) for the duration, and releases it on exit. It **refuses — with no override — if a running server or another instance holds the lock**, so stop the server first. `--repl` is read-only unless combined with `--write`. Attempting a write without `--write` fails with a hint to re-run with the flag. What "held" means, and how to tell a live lock from a stale one, is [below](#locking).
|
|
195
195
|
|
|
196
|
+
Compressed text columns (`chat_messages.content`, `llm_logs.request` / `response` and friends) are stored as BLOBs. Wrap them in `qt_text()` to read the text: `SELECT qt_text(content) …`.
|
|
197
|
+
|
|
196
198
|
In the REPL, `.cols <table>` and `.find <text>` mirror the subcommand helpers.
|
|
197
199
|
|
|
198
200
|
## Locking
|
package/bin/quilltap.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
printDefaultInstanceHint,
|
|
12
12
|
promptPassphrase,
|
|
13
13
|
loadDbKey,
|
|
14
|
+
openEncryptedDb,
|
|
14
15
|
} = require('../lib/db-helpers');
|
|
15
16
|
const { resolveInstance } = require('../lib/instances');
|
|
16
17
|
const { resolveModuleDir, ensureNativeModules, ensureDatabaseNativeModule } = require('../lib/native-modules');
|
|
@@ -982,9 +983,10 @@ async function dbCommand(args) {
|
|
|
982
983
|
}
|
|
983
984
|
|
|
984
985
|
let dbFilename;
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
else dbFilename = 'quilltap.db';
|
|
986
|
+
let dbFriendlyName;
|
|
987
|
+
if (useLlmLogs) { dbFilename = 'quilltap-llm-logs.db'; dbFriendlyName = 'LLM logs database'; }
|
|
988
|
+
else if (useMountPoints) { dbFilename = 'quilltap-mount-index.db'; dbFriendlyName = 'mount index database'; }
|
|
989
|
+
else { dbFilename = 'quilltap.db'; dbFriendlyName = 'main database'; }
|
|
988
990
|
const dbPath = path.join(dataDir, dbFilename);
|
|
989
991
|
|
|
990
992
|
if (!fs.existsSync(dbPath)) {
|
|
@@ -1014,27 +1016,16 @@ async function dbCommand(args) {
|
|
|
1014
1016
|
}
|
|
1015
1017
|
}
|
|
1016
1018
|
|
|
1017
|
-
// Open
|
|
1018
|
-
|
|
1019
|
+
// Open through the one shared opener. It picks the SQLCipher-capable build,
|
|
1020
|
+
// keys the connection, verifies it, and — the reason the low-level path may
|
|
1021
|
+
// not roll its own — registers qt_text(). Without it raw SQL cannot read
|
|
1022
|
+
// inside a compressed text column, and a --write to chat_messages fails
|
|
1023
|
+
// outright because the search-index triggers call it (bug 162).
|
|
1024
|
+
let db;
|
|
1019
1025
|
try {
|
|
1020
|
-
|
|
1021
|
-
} catch {
|
|
1022
|
-
Database = require('better-sqlite3');
|
|
1023
|
-
}
|
|
1024
|
-
const db = new Database(dbPath, { readonly: !writable });
|
|
1025
|
-
|
|
1026
|
-
if (pepper) {
|
|
1027
|
-
const keyHex = Buffer.from(pepper, 'base64').toString('hex');
|
|
1028
|
-
db.pragma(`key = "x'${keyHex}'"`);
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
try {
|
|
1032
|
-
// Verify database is readable
|
|
1033
|
-
db.prepare('SELECT 1').get();
|
|
1026
|
+
db = openEncryptedDb(dbPath, pepper, { readonly: !writable, friendlyName: dbFriendlyName });
|
|
1034
1027
|
} catch (err) {
|
|
1035
|
-
console.error(
|
|
1036
|
-
console.error('The database may be encrypted with a different key, or the .dbkey file may be missing.');
|
|
1037
|
-
db.close();
|
|
1028
|
+
console.error(err.message);
|
|
1038
1029
|
process.exit(1);
|
|
1039
1030
|
}
|
|
1040
1031
|
|
package/lib/db-commands.js
CHANGED
|
@@ -478,6 +478,11 @@ function cmdMessages(args, ctx) {
|
|
|
478
478
|
params.push(last);
|
|
479
479
|
const rows = db.prepare(sql).all(...params).reverse(); // oldest first
|
|
480
480
|
|
|
481
|
+
// `content` may be a brotli BLOB (see lib/text-codec.js). Decode before
|
|
482
|
+
// anything prints or JSON-serializes it — a raw BLOB prints as binary and
|
|
483
|
+
// JSON.stringify turns it into a byte array.
|
|
484
|
+
for (const r of rows) r.content = decodeText(r.content);
|
|
485
|
+
|
|
481
486
|
if (json) return printJson({ chat, total: totalRow.n, returned: rows.length, messages: rows });
|
|
482
487
|
|
|
483
488
|
console.log(`Chat: ${chat.title} (${chat.id}) — showing ${rows.length} of ${totalRow.n} matching messages`);
|
|
@@ -558,6 +563,13 @@ function cmdMessage(args, ctx) {
|
|
|
558
563
|
try {
|
|
559
564
|
const row = db.prepare('SELECT * FROM chat_messages WHERE id = ?').get(id);
|
|
560
565
|
if (!row) throw new Error(`No chat_message with id ${id}`);
|
|
566
|
+
|
|
567
|
+
// The four large text columns may be brotli BLOBs (see lib/text-codec.js).
|
|
568
|
+
// Decode before anything prints or JSON-serializes them.
|
|
569
|
+
for (const col of ['content', 'opaqueContent', 'description', 'context']) {
|
|
570
|
+
if (col in row) row[col] = decodeText(row[col]);
|
|
571
|
+
}
|
|
572
|
+
|
|
561
573
|
if (json) return printJson(row);
|
|
562
574
|
|
|
563
575
|
printRecord(`Message ${row.id}`, {
|
package/lib/db-helpers.js
CHANGED
|
@@ -202,8 +202,11 @@ function openEncryptedDb(dbPath, pepper, { readonly = true, friendlyName = 'data
|
|
|
202
202
|
'The database may be encrypted with a different key, or the .dbkey file may be missing.');
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
// Compressed text columns (llm_logs.request/response
|
|
206
|
-
// BLOBs; qt_text() lets raw SQL and the repl read inside
|
|
205
|
+
// Compressed text columns (llm_logs.request/response, chat_messages.content
|
|
206
|
+
// and friends) are BLOBs; qt_text() lets raw SQL and the repl read inside
|
|
207
|
+
// them. It is also REQUIRED for any --write that touches chat_messages: the
|
|
208
|
+
// message search triggers call it, so a connection without it fails the
|
|
209
|
+
// write loudly rather than letting the index drift.
|
|
207
210
|
try {
|
|
208
211
|
registerTextCodecFunction(db);
|
|
209
212
|
} catch {
|