quilltap 4.12.0-dev.9 → 4.12.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/README.md +3 -3
- package/bin/quilltap.js +5 -4
- package/lib/db-commands.js +14 -31
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ quilltap --update
|
|
|
65
65
|
|
|
66
66
|
## How It Works
|
|
67
67
|
|
|
68
|
-
The `quilltap` npm package is a lightweight CLI launcher (~10 KB). On first run, it downloads the pre-built application from [GitHub Releases](https://github.com/foundry-9/quilltap-server/releases) and caches it in a platform-specific directory.
|
|
68
|
+
The `quilltap` npm package is a lightweight CLI launcher (~10 KB). On first run, it downloads the pre-built application from [GitHub Releases](https://github.com/foundry-9/quilltap-server/releases) and caches it in a platform-specific directory. The SQLCipher database driver (`better-sqlite3`) and `sharp` ship as prebuilt binaries for your platform — the driver is never compiled or rebuilt. `node-pty` (terminals) uses its prebuilds where they exist and compiles on Linux; if a Node.js upgrade leaves `sharp` or `node-pty` built for the old version, the next run rebuilds them automatically. On Linux, the SQLCipher driver needs glibc 2.35 or newer (Ubuntu 22.04, Debian 12, RHEL 9 or newer); older hosts should run the Docker image.
|
|
69
69
|
|
|
70
70
|
### Cache Locations
|
|
71
71
|
|
|
@@ -112,7 +112,7 @@ Every subcommand then accepts `--instance <name>` in place of `--data-dir`. The
|
|
|
112
112
|
|
|
113
113
|
## Database Tool
|
|
114
114
|
|
|
115
|
-
The encrypted SQLite databases (main, LLM logs, mount index) can be queried directly via `quilltap db`. They are
|
|
115
|
+
The encrypted SQLite databases (main, LLM logs, mount index) can be queried directly via `quilltap db`. They are encrypted with ChaCha20-Poly1305 (SQLite3 Multiple Ciphers), so neither the stock `sqlite3` binary nor `sqlcipher` can open them — this is the way in. There are two modes: high-level subcommands that auto-pick the right database and resolve characters/chats/projects by name, and a low-level path for arbitrary SQL.
|
|
116
116
|
|
|
117
117
|
### Subcommands
|
|
118
118
|
|
|
@@ -170,7 +170,7 @@ quilltap db backup # online snapshot of all three DBs
|
|
|
170
170
|
quilltap db backup main --out /tmp/snap # one DB to a chosen directory
|
|
171
171
|
quilltap db backup --json # parseable per-target sizes + durations
|
|
172
172
|
|
|
173
|
-
quilltap db integrity #
|
|
173
|
+
quilltap db integrity # integrity_check (decrypts + verifies every page it reads)
|
|
174
174
|
quilltap db integrity llm-logs # one DB; exit 0 ok, 1 issues, 2 open failure
|
|
175
175
|
```
|
|
176
176
|
|
package/bin/quilltap.js
CHANGED
|
@@ -803,9 +803,10 @@ Subcommands (high-level shortcuts; auto-pick the right database):
|
|
|
803
803
|
Default destination: <dataDir>/backups/<timestamp>/.
|
|
804
804
|
Each snapshot is re-opened with the same key and
|
|
805
805
|
verified with PRAGMA quick_check.
|
|
806
|
-
integrity [target...] Run PRAGMA
|
|
807
|
-
|
|
808
|
-
|
|
806
|
+
integrity [target...] Run PRAGMA integrity_check on the encrypted
|
|
807
|
+
databases; every page read is decrypted and
|
|
808
|
+
its Poly1305 tag verified, so tampered pages
|
|
809
|
+
count as issues. Read-only; safe alongside a live
|
|
809
810
|
instance. Exit code: 0 ok, 1 issues, 2 open
|
|
810
811
|
failure.
|
|
811
812
|
Most subcommands also accept --json and --limit N.
|
|
@@ -851,7 +852,7 @@ Examples:
|
|
|
851
852
|
quilltap db optimize llm-logs # only the LLM logs DB
|
|
852
853
|
quilltap db backup # snapshot all DBs to <dataDir>/backups/<ts>/
|
|
853
854
|
quilltap db backup main --out /tmp/qtap-snap # snapshot only the main DB
|
|
854
|
-
quilltap db integrity #
|
|
855
|
+
quilltap db integrity # integrity_check on every DB
|
|
855
856
|
quilltap db --tables
|
|
856
857
|
quilltap db "SELECT count(*) FROM characters"
|
|
857
858
|
quilltap db --count messages
|
package/lib/db-commands.js
CHANGED
|
@@ -1604,7 +1604,7 @@ async function backupOneDb(key, sourcePath, destDir, ctx) {
|
|
|
1604
1604
|
return { target: target.label, source: sourcePath, sourceSize, dest: destPath, ok: false, error: err.message };
|
|
1605
1605
|
}
|
|
1606
1606
|
|
|
1607
|
-
// The
|
|
1607
|
+
// The SQLite3 Multiple Ciphers build in this driver does not expose `sqlcipher_export`
|
|
1608
1608
|
// and the SQLite online-backup API refuses cross-cipher copies. We
|
|
1609
1609
|
// instead take a brief exclusive lock, force a WAL checkpoint, and copy
|
|
1610
1610
|
// the encrypted .db file at the byte level — the pages are already
|
|
@@ -1742,44 +1742,30 @@ function integrityOneDb(key, dbPath, ctx) {
|
|
|
1742
1742
|
return { target: target.label, ok: false, openable: false, issues: [], reason: err.message };
|
|
1743
1743
|
}
|
|
1744
1744
|
|
|
1745
|
+
// SQLite3 Multiple Ciphers (ChaCha20-Poly1305) has no SQLCipher-style
|
|
1746
|
+
// `cipher_integrity_check` pragma — an unknown pragma returns no rows, so
|
|
1747
|
+
// asking for it reported "ok" without checking anything. The page-level
|
|
1748
|
+
// check is integrity_check itself: every page it reads must decrypt and
|
|
1749
|
+
// pass its Poly1305 tag, and a page that fails throws (SQLITE_CORRUPT /
|
|
1750
|
+
// SQLITE_NOTADB) rather than returning a row, so that is caught as an issue.
|
|
1745
1751
|
const issues = [];
|
|
1746
|
-
let cipherCheck = null;
|
|
1747
1752
|
let integrityCheck = null;
|
|
1748
1753
|
const t0 = Date.now();
|
|
1749
1754
|
try {
|
|
1755
|
+
let lines;
|
|
1750
1756
|
try {
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
cipherCheck = 'ok';
|
|
1754
|
-
console.log(' cipher_integrity_check: ok');
|
|
1755
|
-
} else {
|
|
1756
|
-
const lines = rows.map(r => (r.cipher_integrity_check || Object.values(r)[0] || '')).filter(Boolean);
|
|
1757
|
-
if (lines.length === 1 && lines[0] === 'ok') {
|
|
1758
|
-
cipherCheck = 'ok';
|
|
1759
|
-
console.log(' cipher_integrity_check: ok');
|
|
1760
|
-
} else {
|
|
1761
|
-
cipherCheck = lines.join('; ');
|
|
1762
|
-
for (const line of lines) {
|
|
1763
|
-
console.log(` cipher_integrity_check: ${line}`);
|
|
1764
|
-
issues.push({ pragma: 'cipher_integrity_check', message: line });
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1757
|
+
const integRows = db.pragma('integrity_check');
|
|
1758
|
+
lines = integRows.map(r => (r.integrity_check || Object.values(r)[0] || '')).filter(Boolean);
|
|
1768
1759
|
} catch (err) {
|
|
1769
|
-
|
|
1770
|
-
cipherCheck = `n/a (${err.message})`;
|
|
1771
|
-
console.log(` cipher_integrity_check: n/a (${err.message})`);
|
|
1760
|
+
lines = [`page read failed (${err.code || 'error'}): ${err.message}`];
|
|
1772
1761
|
}
|
|
1773
|
-
|
|
1774
|
-
const integRows = db.pragma('integrity_check');
|
|
1775
|
-
const lines = integRows.map(r => (r.integrity_check || Object.values(r)[0] || '')).filter(Boolean);
|
|
1776
1762
|
if (lines.length === 1 && lines[0] === 'ok') {
|
|
1777
1763
|
integrityCheck = 'ok';
|
|
1778
|
-
console.log(' integrity_check:
|
|
1764
|
+
console.log(' integrity_check: ok');
|
|
1779
1765
|
} else {
|
|
1780
1766
|
integrityCheck = lines.join('; ');
|
|
1781
1767
|
for (const line of lines) {
|
|
1782
|
-
console.log(` integrity_check:
|
|
1768
|
+
console.log(` integrity_check: ${line}`);
|
|
1783
1769
|
issues.push({ pragma: 'integrity_check', message: line });
|
|
1784
1770
|
}
|
|
1785
1771
|
}
|
|
@@ -1789,13 +1775,10 @@ function integrityOneDb(key, dbPath, ctx) {
|
|
|
1789
1775
|
const durationMs = Date.now() - t0;
|
|
1790
1776
|
console.log(` duration: ${formatDuration(durationMs)}`);
|
|
1791
1777
|
|
|
1792
|
-
const ok = (cipherCheck === 'ok' || cipherCheck === null || (typeof cipherCheck === 'string' && cipherCheck.startsWith('n/a')))
|
|
1793
|
-
&& integrityCheck === 'ok';
|
|
1794
1778
|
return {
|
|
1795
1779
|
target: target.label,
|
|
1796
|
-
ok,
|
|
1780
|
+
ok: integrityCheck === 'ok',
|
|
1797
1781
|
openable: true,
|
|
1798
|
-
cipherIntegrityCheck: cipherCheck,
|
|
1799
1782
|
integrityCheck,
|
|
1800
1783
|
issues,
|
|
1801
1784
|
durationMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quilltap",
|
|
3
|
-
"version": "4.12.0
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"description": "Self-hosted AI workspace for writers, worldbuilders, and roleplayers. Run with npx quilltap.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Charles Sebold",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"README.md"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@napi-rs/canvas": "^1.0
|
|
39
|
+
"@napi-rs/canvas": "^1.1.0",
|
|
40
40
|
"better-sqlite3-multiple-ciphers": "^13.0.3",
|
|
41
41
|
"node-pty": "^1.1.0",
|
|
42
42
|
"sharp": "^0.35.5",
|