quilltap 4.6.0-dev → 4.6.0-dev.106
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/bin/quilltap.js +79 -101
- package/lib/completion/bash.template +152 -33
- package/lib/completion/fish.template +262 -87
- package/lib/completion/zsh.template +219 -26
- package/lib/db-commands.js +418 -11
- package/lib/db-helpers.js +90 -9
- package/lib/memories-commands.js +20 -16
- package/lib/native-modules.js +151 -0
- package/package.json +6 -3
package/lib/memories-commands.js
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
printDefaultInstanceHint,
|
|
6
6
|
loadDbKey,
|
|
7
7
|
openMainDb,
|
|
8
|
+
openMountIndexDb,
|
|
8
9
|
UUID_RE,
|
|
9
10
|
resolveCharacter,
|
|
10
11
|
resolveChat,
|
|
@@ -179,7 +180,10 @@ async function openDb(flags) {
|
|
|
179
180
|
const { dataDir, passphrase } = resolved;
|
|
180
181
|
const pepper = await loadDbKey(dataDir, passphrase);
|
|
181
182
|
const db = openMainDb(dataDir, pepper, { readonly: true });
|
|
182
|
-
|
|
183
|
+
// Lazy opener for the mount-index DB so `resolveCharacter` can match
|
|
184
|
+
// vault-stored aliases (4.6 cutover moved them out of the `characters` row).
|
|
185
|
+
const openMounts = () => openMountIndexDb(dataDir, pepper, { readonly: true });
|
|
186
|
+
return { db, dataDir, openMounts };
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
// ---------- filter / sort builders ----------
|
|
@@ -188,13 +192,13 @@ async function openDb(flags) {
|
|
|
188
192
|
// `{ where: 'WHERE m.x = ? AND ...', params: [...], meta: { characterId, ... } }`.
|
|
189
193
|
// `meta` exposes resolved IDs so callers can decide e.g. whether to show a
|
|
190
194
|
// per-row holder column.
|
|
191
|
-
function buildWhereClause(db, flags) {
|
|
195
|
+
function buildWhereClause(db, flags, openMounts = null) {
|
|
192
196
|
const clauses = [];
|
|
193
197
|
const params = [];
|
|
194
198
|
const meta = { characterId: null, aboutId: null, chatId: null, projectId: null, allCharacters: true };
|
|
195
199
|
|
|
196
200
|
if (flags.character && flags.character !== 'all') {
|
|
197
|
-
const c = resolveCharacter(db, flags.character);
|
|
201
|
+
const c = resolveCharacter(db, flags.character, openMounts);
|
|
198
202
|
clauses.push('m.characterId = ?');
|
|
199
203
|
params.push(c.id);
|
|
200
204
|
meta.characterId = c.id;
|
|
@@ -207,7 +211,7 @@ function buildWhereClause(db, flags) {
|
|
|
207
211
|
} else if (flags.about === 'none') {
|
|
208
212
|
clauses.push('m.aboutCharacterId IS NULL');
|
|
209
213
|
} else {
|
|
210
|
-
const a = resolveCharacter(db, flags.about);
|
|
214
|
+
const a = resolveCharacter(db, flags.about, openMounts);
|
|
211
215
|
clauses.push('m.aboutCharacterId = ?');
|
|
212
216
|
params.push(a.id);
|
|
213
217
|
meta.aboutId = a.id;
|
|
@@ -395,9 +399,9 @@ function renderJson(obj) {
|
|
|
395
399
|
// ---------- ls ----------
|
|
396
400
|
|
|
397
401
|
async function cmdLs(flags) {
|
|
398
|
-
const { db } = await openDb(flags);
|
|
402
|
+
const { db, openMounts } = await openDb(flags);
|
|
399
403
|
try {
|
|
400
|
-
const { where, params, meta } = buildWhereClause(db, flags);
|
|
404
|
+
const { where, params, meta } = buildWhereClause(db, flags, openMounts);
|
|
401
405
|
const { order, impField } = buildOrderBy(flags.sort, flags.reverse);
|
|
402
406
|
const limit = flags.limit > 0 ? flags.limit : 50;
|
|
403
407
|
const sql = `${SELECT_BASE} ${where} ORDER BY ${order} LIMIT ?`;
|
|
@@ -512,9 +516,9 @@ async function cmdFind(flags, positional) {
|
|
|
512
516
|
if (!['summary', 'content', 'both'].includes(inWhere)) {
|
|
513
517
|
throw new Error(`--in must be one of: summary, content, both (got '${inWhere}')`);
|
|
514
518
|
}
|
|
515
|
-
const { db } = await openDb(flags);
|
|
519
|
+
const { db, openMounts } = await openDb(flags);
|
|
516
520
|
try {
|
|
517
|
-
const { where, params, meta } = buildWhereClause(db, flags);
|
|
521
|
+
const { where, params, meta } = buildWhereClause(db, flags, openMounts);
|
|
518
522
|
const like = `%${pattern}%`;
|
|
519
523
|
|
|
520
524
|
const matchClauses = [];
|
|
@@ -584,9 +588,9 @@ async function cmdSemanticGrep(flags, query) {
|
|
|
584
588
|
// Resolve character locally so the server gets a stable UUID.
|
|
585
589
|
let characterId;
|
|
586
590
|
{
|
|
587
|
-
const { db } = await openDb(flags);
|
|
591
|
+
const { db, openMounts } = await openDb(flags);
|
|
588
592
|
try {
|
|
589
|
-
const resolved = resolveCharacter(db, flags.character);
|
|
593
|
+
const resolved = resolveCharacter(db, flags.character, openMounts);
|
|
590
594
|
characterId = resolved.id;
|
|
591
595
|
} finally {
|
|
592
596
|
db.close();
|
|
@@ -675,9 +679,9 @@ async function cmdGrep(flags, positional) {
|
|
|
675
679
|
if (flags.semantic) {
|
|
676
680
|
return cmdSemanticGrep(flags, pattern);
|
|
677
681
|
}
|
|
678
|
-
const { db } = await openDb(flags);
|
|
682
|
+
const { db, openMounts } = await openDb(flags);
|
|
679
683
|
try {
|
|
680
|
-
const { where, params } = buildWhereClause(db, flags);
|
|
684
|
+
const { where, params } = buildWhereClause(db, flags, openMounts);
|
|
681
685
|
// Always restrict to rows whose content can match — quick pre-filter so we
|
|
682
686
|
// don't read all 32k rows into JS just to drop most of them.
|
|
683
687
|
const likeNeedle = flags.ignoreCase ? `%${pattern.toLowerCase()}%` : `%${pattern}%`;
|
|
@@ -1044,11 +1048,11 @@ function graphToJson(node) {
|
|
|
1044
1048
|
// ---------- status ----------
|
|
1045
1049
|
|
|
1046
1050
|
async function cmdStatus(flags) {
|
|
1047
|
-
const { db } = await openDb(flags);
|
|
1051
|
+
const { db, openMounts } = await openDb(flags);
|
|
1048
1052
|
try {
|
|
1049
1053
|
let holderRows;
|
|
1050
1054
|
if (flags.character && flags.character !== 'all') {
|
|
1051
|
-
const c = resolveCharacter(db, flags.character);
|
|
1055
|
+
const c = resolveCharacter(db, flags.character, openMounts);
|
|
1052
1056
|
holderRows = [{ id: c.id, name: c.name }];
|
|
1053
1057
|
} else {
|
|
1054
1058
|
holderRows = db.prepare(`
|
|
@@ -1180,12 +1184,12 @@ function renderStatusBlock(holder, stats) {
|
|
|
1180
1184
|
// ---------- validate ----------
|
|
1181
1185
|
|
|
1182
1186
|
async function cmdValidate(flags) {
|
|
1183
|
-
const { db } = await openDb(flags);
|
|
1187
|
+
const { db, openMounts } = await openDb(flags);
|
|
1184
1188
|
try {
|
|
1185
1189
|
let holderIds = null;
|
|
1186
1190
|
let holderRows;
|
|
1187
1191
|
if (flags.character && flags.character !== 'all') {
|
|
1188
|
-
const c = resolveCharacter(db, flags.character);
|
|
1192
|
+
const c = resolveCharacter(db, flags.character, openMounts);
|
|
1189
1193
|
holderRows = [{ id: c.id, name: c.name }];
|
|
1190
1194
|
holderIds = [c.id];
|
|
1191
1195
|
} else {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shared helpers for keeping native modules compiled against the current Node
|
|
4
|
+
// ABI. Used by both the runtime CLI entry (bin/quilltap.js) and the package's
|
|
5
|
+
// `postinstall` hook, so a fresh install picks up the correct binaries up front
|
|
6
|
+
// and a later Node upgrade still self-heals on first run.
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const PACKAGE_DIR = path.resolve(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
// Resolve a native module's directory, handling npm hoisting.
|
|
14
|
+
// Returns the directory containing package.json, or null if not found.
|
|
15
|
+
function resolveModuleDir(moduleName) {
|
|
16
|
+
try {
|
|
17
|
+
const pkgJson = require.resolve(moduleName + '/package.json', { paths: [PACKAGE_DIR] });
|
|
18
|
+
return path.dirname(pkgJson);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// node-pty needs a `spawn-helper` executable beside the pty.node it loads, or
|
|
25
|
+
// pty.spawn() fails with `posix_spawnp failed`. An ABI rebuild lands a fresh
|
|
26
|
+
// build/Release/pty.node (which node-pty's loader prefers over prebuilds/) but
|
|
27
|
+
// emits only the addon, not node-pty's separate spawn-helper target; tar/extract
|
|
28
|
+
// can also drop the exec bit on the shipped prebuilds/*/spawn-helper. spawn-helper
|
|
29
|
+
// is a plain executable (no Node linkage) so the prebuilt copy is ABI-independent
|
|
30
|
+
// and safe to reuse. Best-effort; never throws.
|
|
31
|
+
function reconcileNodePtySpawnHelper() {
|
|
32
|
+
if (process.platform === 'win32') return; // conpty has no spawn-helper
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
try {
|
|
35
|
+
const nodePtyDir = resolveModuleDir('node-pty');
|
|
36
|
+
if (!nodePtyDir) return;
|
|
37
|
+
const prebuildsDir = path.join(nodePtyDir, 'prebuilds');
|
|
38
|
+
const prebuiltHelper = path.join(prebuildsDir, `${process.platform}-${process.arch}`, 'spawn-helper');
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(prebuildsDir)) {
|
|
41
|
+
for (const entry of fs.readdirSync(prebuildsDir)) {
|
|
42
|
+
const helper = path.join(prebuildsDir, entry, 'spawn-helper');
|
|
43
|
+
if (fs.existsSync(helper)) {
|
|
44
|
+
try { fs.chmodSync(helper, 0o755); } catch { /* best-effort */ }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const buildType of ['Release', 'Debug']) {
|
|
50
|
+
const buildDir = path.join(nodePtyDir, 'build', buildType);
|
|
51
|
+
const builtAddon = path.join(buildDir, 'pty.node');
|
|
52
|
+
const builtHelper = path.join(buildDir, 'spawn-helper');
|
|
53
|
+
if (fs.existsSync(builtHelper)) {
|
|
54
|
+
try { fs.chmodSync(builtHelper, 0o755); } catch { /* best-effort */ }
|
|
55
|
+
} else if (fs.existsSync(builtAddon) && fs.existsSync(prebuiltHelper)) {
|
|
56
|
+
fs.copyFileSync(prebuiltHelper, builtHelper);
|
|
57
|
+
fs.chmodSync(builtHelper, 0o755);
|
|
58
|
+
console.log(` node-pty: backfilled build/${buildType}/spawn-helper from prebuilds`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// best-effort — node-pty terminals are optional; never block the CLI
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if native modules are compiled for the current Node.js version.
|
|
67
|
+
// This handles the case where npx caches the package but the user upgrades
|
|
68
|
+
// Node.js — the cached native modules will have a stale NODE_MODULE_VERSION.
|
|
69
|
+
// Returns true if everything was healthy or successfully rebuilt; false on
|
|
70
|
+
// rebuild failure. Never throws.
|
|
71
|
+
function ensureNativeModules() {
|
|
72
|
+
const needsRebuild = [];
|
|
73
|
+
|
|
74
|
+
// Check better-sqlite3-multiple-ciphers (provides SQLCipher encryption support).
|
|
75
|
+
// The main app depends on this via an npm alias as 'better-sqlite3', so we must
|
|
76
|
+
// ensure the SQLCipher-capable version is available and link it as 'better-sqlite3'.
|
|
77
|
+
// We must load the native binding directly to detect NODE_MODULE_VERSION mismatches.
|
|
78
|
+
try {
|
|
79
|
+
const modDir = resolveModuleDir('better-sqlite3-multiple-ciphers')
|
|
80
|
+
|| resolveModuleDir('better-sqlite3');
|
|
81
|
+
if (!modDir) throw Object.assign(new Error('not found'), { code: 'MODULE_NOT_FOUND' });
|
|
82
|
+
const bindingsPath = path.join(modDir, 'build', 'Release', 'better_sqlite3.node');
|
|
83
|
+
require(bindingsPath);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (err.message && err.message.includes('NODE_MODULE_VERSION')) {
|
|
86
|
+
needsRebuild.push('better-sqlite3-multiple-ciphers');
|
|
87
|
+
} else if (err.code === 'MODULE_NOT_FOUND') {
|
|
88
|
+
needsRebuild.push('better-sqlite3-multiple-ciphers');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check sharp: loads its native binding eagerly on require.
|
|
93
|
+
try {
|
|
94
|
+
require.resolve('sharp', { paths: [PACKAGE_DIR] });
|
|
95
|
+
require('sharp');
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err.message && err.message.includes('NODE_MODULE_VERSION')) {
|
|
98
|
+
needsRebuild.push('sharp');
|
|
99
|
+
} else if (err.code === 'MODULE_NOT_FOUND') {
|
|
100
|
+
needsRebuild.push('sharp');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check node-pty: backs the Ariel terminal feature. Loaded dynamically by
|
|
105
|
+
// pty-manager in the standalone server, so resolution must succeed and the
|
|
106
|
+
// native binding's NODE_MODULE_VERSION must match the runtime.
|
|
107
|
+
try {
|
|
108
|
+
require.resolve('node-pty', { paths: [PACKAGE_DIR] });
|
|
109
|
+
require('node-pty');
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err.message && err.message.includes('NODE_MODULE_VERSION')) {
|
|
112
|
+
needsRebuild.push('node-pty');
|
|
113
|
+
} else if (err.code === 'MODULE_NOT_FOUND') {
|
|
114
|
+
needsRebuild.push('node-pty');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (needsRebuild.length === 0) {
|
|
119
|
+
reconcileNodePtySpawnHelper();
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(` Rebuilding native modules for Node.js ${process.version}...`);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
execSync(`npm rebuild ${needsRebuild.join(' ')}`, {
|
|
127
|
+
cwd: PACKAGE_DIR,
|
|
128
|
+
stdio: 'inherit',
|
|
129
|
+
});
|
|
130
|
+
console.log(' Done.');
|
|
131
|
+
console.log('');
|
|
132
|
+
reconcileNodePtySpawnHelper();
|
|
133
|
+
return true;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error('');
|
|
136
|
+
console.error(` Warning: Failed to rebuild native modules: ${err.message}`);
|
|
137
|
+
console.error(' Try running: npm rebuild --prefix ' + PACKAGE_DIR);
|
|
138
|
+
console.error('');
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { resolveModuleDir, ensureNativeModules, reconcileNodePtySpawnHelper, PACKAGE_DIR };
|
|
144
|
+
|
|
145
|
+
// Allow this file to be invoked directly as a postinstall script:
|
|
146
|
+
// node lib/native-modules.js
|
|
147
|
+
// Exits 0 on success or graceful warning; never blocks npm install on failure.
|
|
148
|
+
if (require.main === module) {
|
|
149
|
+
ensureNativeModules();
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quilltap",
|
|
3
|
-
"version": "4.6.0-dev",
|
|
3
|
+
"version": "4.6.0-dev.106",
|
|
4
4
|
"description": "Self-hosted AI workspace for writers, worldbuilders, and roleplayers. Run with npx quilltap.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Charles Sebold",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"bin": {
|
|
28
28
|
"quilltap": "bin/quilltap.js"
|
|
29
29
|
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"postinstall": "node lib/native-modules.js"
|
|
32
|
+
},
|
|
30
33
|
"files": [
|
|
31
34
|
"bin/",
|
|
32
35
|
"lib/",
|
|
@@ -34,11 +37,11 @@
|
|
|
34
37
|
],
|
|
35
38
|
"dependencies": {
|
|
36
39
|
"@napi-rs/canvas": "^0.1.100",
|
|
37
|
-
"better-sqlite3-multiple-ciphers": "^12.
|
|
40
|
+
"better-sqlite3-multiple-ciphers": "^12.10.0",
|
|
38
41
|
"node-pty": "^1.1.0",
|
|
39
42
|
"sharp": "^0.34.5",
|
|
40
43
|
"tar": "^7.5.15",
|
|
41
|
-
"yauzl": "^3.3.
|
|
44
|
+
"yauzl": "^3.3.1"
|
|
42
45
|
},
|
|
43
46
|
"engines": {
|
|
44
47
|
"node": ">=24.0.0"
|