quilltap 4.5.0-dev → 4.5.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 +178 -0
- package/bin/quilltap.js +226 -20
- package/lib/completion/bash.template +121 -0
- package/lib/completion/fish.template +93 -0
- package/lib/completion/zsh.template +209 -0
- package/lib/completion-commands.js +77 -0
- package/lib/db-commands.js +1142 -0
- package/lib/db-helpers.js +173 -4
- package/lib/docs-commands.js +2157 -172
- package/lib/graph-integrity.js +105 -0
- package/lib/instances-commands.js +342 -0
- package/lib/instances.js +335 -0
- package/lib/lock-helpers.js +117 -0
- package/lib/logs-commands.js +383 -0
- package/lib/memories-commands.js +1374 -0
- package/lib/memory-diff-command.js +19 -3
- package/lib/migrations-commands.js +324 -0
- package/lib/theme-commands.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,109 @@ Quilltap stores its database, files, and logs in a platform-specific directory:
|
|
|
89
89
|
|
|
90
90
|
Override with `--data-dir` or the `QUILLTAP_DATA_DIR` environment variable.
|
|
91
91
|
|
|
92
|
+
## Database Tool
|
|
93
|
+
|
|
94
|
+
The encrypted SQLite databases (main, LLM logs, mount index) can be queried directly via `quilltap db`. 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.
|
|
95
|
+
|
|
96
|
+
### Subcommands
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
quilltap db schema # Tables grouped by domain
|
|
100
|
+
quilltap db schema chat_messages # Columns, indexes, DDL.md link
|
|
101
|
+
quilltap db schema --grep memory # Find tables/columns by substring
|
|
102
|
+
|
|
103
|
+
quilltap db find character Friday # Resolve a name to a UUID (fuzzy)
|
|
104
|
+
quilltap db find chat "physical prompts"
|
|
105
|
+
quilltap db find project "Quilltap"
|
|
106
|
+
|
|
107
|
+
quilltap db chats --character Friday # All chats containing a character
|
|
108
|
+
quilltap db chats --project "Quilltap" # All chats in a project
|
|
109
|
+
quilltap db messages --chat <id|title> --last 50 --full
|
|
110
|
+
quilltap db logs --chat <id|title> # LLM logs for a chat
|
|
111
|
+
quilltap db logs --message <id> # LLM logs for a single message
|
|
112
|
+
quilltap db logs --character Friday # LLM logs by character
|
|
113
|
+
quilltap db logs --tail 20 # Recent LLM logs
|
|
114
|
+
|
|
115
|
+
quilltap db message <id> # Full content of one message
|
|
116
|
+
quilltap db log <id> [--field request|response|both]
|
|
117
|
+
quilltap db memories --character Friday [--about Amy] [--source AUTO]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Maintenance and Snapshots
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
quilltap db optimize # VACUUM + ANALYZE + PRAGMA optimize (all DBs)
|
|
124
|
+
quilltap db optimize main # one DB; refuses while server is running
|
|
125
|
+
|
|
126
|
+
quilltap db backup # online snapshot of all three DBs
|
|
127
|
+
quilltap db backup main --out /tmp/snap # one DB to a chosen directory
|
|
128
|
+
quilltap db backup --json # parseable per-target sizes + durations
|
|
129
|
+
|
|
130
|
+
quilltap db integrity # cipher_integrity_check + integrity_check
|
|
131
|
+
quilltap db integrity llm-logs # one DB; exit 0 ok, 1 issues, 2 open failure
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`backup` and `integrity` are safe to run while the server is up; `optimize` refuses while a live lock is held. Backups default to `<dataDir>/backups/<timestamp>/` and inherit the source's encryption key transparently.
|
|
135
|
+
|
|
136
|
+
Most subcommands accept `--json` (for piping) and `--limit N`. Names are case-insensitive; aliases are searched alongside character names. Ambiguous matches print all candidates and exit non-zero.
|
|
137
|
+
|
|
138
|
+
### Low-level options
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
quilltap db --tables # List tables in active DB
|
|
142
|
+
quilltap db --count chat_messages # Row count
|
|
143
|
+
quilltap db "SELECT id FROM characters LIMIT 5" # Raw SQL
|
|
144
|
+
quilltap db --repl # Interactive prompt
|
|
145
|
+
quilltap db --llm-logs --tables # Target the LLM logs DB
|
|
146
|
+
quilltap db --mount-points --tables # Target the mount index DB
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
In the REPL, `.cols <table>` and `.find <text>` mirror the subcommand helpers.
|
|
150
|
+
|
|
151
|
+
## Document Stores (Scriptorium)
|
|
152
|
+
|
|
153
|
+
`quilltap docs` exposes the document-store machinery from the command line. Read-only verbs open the mount-index DB directly and work without the server; write and pipeline verbs talk to the running server via `/api/v1/mount-points/[id]`.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Read
|
|
157
|
+
quilltap docs list # All mounts
|
|
158
|
+
quilltap docs show <mount> # One mount, with counts
|
|
159
|
+
quilltap docs ls <mount> [path] [--links] # POSIX-flavoured listing (alias: dir)
|
|
160
|
+
quilltap docs read [--rendered] <mount> <path> # File contents → stdout
|
|
161
|
+
quilltap docs export <mount> <outputDir> # Mount → directory
|
|
162
|
+
quilltap docs find <pattern> # Substring match on file names (--mount, --ext, --type, --limit)
|
|
163
|
+
quilltap docs grep <pattern> # Substring match on extracted text (--mount, --ignore-case, -l, --max, --context)
|
|
164
|
+
quilltap docs status # Per-mount extraction + embedding rollup (--mount, --top)
|
|
165
|
+
|
|
166
|
+
# Server-required
|
|
167
|
+
quilltap docs scan <mount> # Trigger a rescan
|
|
168
|
+
quilltap docs reindex <mount> [path] [--force] # Re-extract + re-chunk
|
|
169
|
+
quilltap docs embed <mount> [path] [--force] [--wait] # Enqueue embedding jobs
|
|
170
|
+
quilltap docs write [--force] <mount> <path> [file] # Stdin or file → mount
|
|
171
|
+
quilltap docs delete <mount> <path> # Idempotent delete
|
|
172
|
+
quilltap docs mkdir <mount> <path> # Idempotent folder create
|
|
173
|
+
quilltap docs move <srcMount> <srcPath> <dstMount> <dstPath> # Move (hard-link when possible)
|
|
174
|
+
quilltap docs copy [--force] <srcMount> <srcPath> <dstMount> <dstPath>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Mount arguments accept the mount name (case-insensitive) or a UUID; ambiguous names print candidates and exit non-zero. `--json` is supported by every verb; `reindex` and `embed` refuse to run without a reachable server.
|
|
178
|
+
|
|
179
|
+
## Memories
|
|
180
|
+
|
|
181
|
+
`quilltap memories` exposes the same Commonplace Book that each character carries — searchable, sortable, graphable, but never writable. All verbs open the main encrypted DB read-only.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
quilltap memories ls # All holders, default sort: reinforcedImportance DESC
|
|
185
|
+
quilltap memories ls --character Ariadne --sort created --limit 10 # One holder, newest first
|
|
186
|
+
quilltap memories find "concrete examples" # Substring match on summary (--in content|both)
|
|
187
|
+
quilltap memories grep -i --max 3 --context 1 "concrete examples" # Pattern search inside content, with snippets
|
|
188
|
+
quilltap memories show <id|prefix> [--depth N] [--no-related] # Full record + related-memory neighbourhood
|
|
189
|
+
quilltap memories tree <id|prefix> [--depth N] [--max-nodes N] # ASCII walk of the bidirectional related-memory graph
|
|
190
|
+
quilltap memories status [--character <name|id>] # Per-holder rollup + dangling-edge check
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Shared filter flags apply to `ls`, `find`, `grep`, and `status` where they make sense: `--character`, `--about` (with `self` / `none` shortcuts), `--source`, `--chat` (with `none` for manual entries), `--project`, `--since`, `--until`, `--min-importance`, `--min-reinforced`, `--has-embedding` / `--no-embedding`. Sort flags (`--sort reinforced|importance|created|accessed|reinforcement-count|links`, plus `-r` to reverse) apply to `ls`, `find`, and `grep`. Names accept fuzzy substrings; ambiguous names print candidates and exit 2. `--json` is supported by every verb. The legacy `quilltap db memories --character <name>` verb remains undisturbed.
|
|
194
|
+
|
|
92
195
|
## Theme Management
|
|
93
196
|
|
|
94
197
|
The CLI includes theme management commands:
|
|
@@ -106,6 +209,81 @@ quilltap themes registry list # List configured registries
|
|
|
106
209
|
quilltap themes registry add <url> # Add a registry source
|
|
107
210
|
```
|
|
108
211
|
|
|
212
|
+
## Shell Completion
|
|
213
|
+
|
|
214
|
+
Tab-completion for bash, zsh, and fish. Pick the block that matches your shell.
|
|
215
|
+
|
|
216
|
+
### Bash
|
|
217
|
+
|
|
218
|
+
Append the generated script to `~/.bashrc`:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
quilltap completion bash >> ~/.bashrc
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Or drop it into a system completion directory:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
quilltap completion bash > /usr/local/etc/bash_completion.d/quilltap
|
|
228
|
+
# Linux with admin rights: /etc/bash_completion.d/quilltap
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Restart the shell, or `source ~/.bashrc`.
|
|
232
|
+
|
|
233
|
+
### Zsh
|
|
234
|
+
|
|
235
|
+
Two reasonable ways to wire this up; pick one.
|
|
236
|
+
|
|
237
|
+
**Option A — one line in `.zshrc`** (simpler; adds noticeable shell-startup latency because `quilltap` runs every time you open a new shell):
|
|
238
|
+
|
|
239
|
+
```zsh
|
|
240
|
+
# In ~/.zshrc:
|
|
241
|
+
source <(quilltap completion zsh)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Option B — canonical `fpath` setup** (faster; what zsh expects):
|
|
245
|
+
|
|
246
|
+
```zsh
|
|
247
|
+
# In ~/.zshrc, before compinit runs:
|
|
248
|
+
fpath=(~/.zsh/completions $fpath)
|
|
249
|
+
autoload -Uz compinit
|
|
250
|
+
compinit
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Then once, from any shell:
|
|
254
|
+
|
|
255
|
+
```zsh
|
|
256
|
+
mkdir -p ~/.zsh/completions
|
|
257
|
+
quilltap completion zsh > ~/.zsh/completions/_quilltap
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The leading underscore on `_quilltap` is the zsh convention — it tells `compinit` this is a completion definition file rather than a regular autoloaded function.
|
|
261
|
+
|
|
262
|
+
**oh-my-zsh users:** the framework runs `compinit` for you, so either set the `fpath` line *before* the framework loads, or delete the cache (`rm -f ~/.zcompdump*`) after dropping the file in and start a new shell. The more idiomatic location under oh-my-zsh is:
|
|
263
|
+
|
|
264
|
+
```zsh
|
|
265
|
+
mkdir -p ~/.oh-my-zsh/custom/plugins/quilltap
|
|
266
|
+
quilltap completion zsh > ~/.oh-my-zsh/custom/plugins/quilltap/_quilltap
|
|
267
|
+
# then add `quilltap` to the plugins=(...) line in ~/.zshrc
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Fish
|
|
271
|
+
|
|
272
|
+
```fish
|
|
273
|
+
quilltap completion fish > ~/.config/fish/completions/quilltap.fish
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Fish picks new completion files up automatically — no shell restart needed.
|
|
277
|
+
|
|
278
|
+
### What gets completed
|
|
279
|
+
|
|
280
|
+
- **Subcommands**: `quilltap d<TAB>` → `db docs`
|
|
281
|
+
- **Sub-verbs per namespace**: `quilltap db s<TAB>` → `schema show`
|
|
282
|
+
- **Instance names**: `quilltap --instance Fr<TAB>` → registered instances
|
|
283
|
+
- **Mount names**: `quilltap docs ls --mount Qu<TAB>` → mount points in the active instance
|
|
284
|
+
|
|
285
|
+
Dynamic completions shell out to `quilltap`'s own subcommands. If the active instance is encrypted and no passphrase is reachable, the completion silently returns nothing rather than prompting in the middle of a tab.
|
|
286
|
+
|
|
109
287
|
## Requirements
|
|
110
288
|
|
|
111
289
|
- Node.js 18 or later
|
package/bin/quilltap.js
CHANGED
|
@@ -5,7 +5,14 @@ const { fork, exec, execSync } = require('child_process');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const { getCacheDir, isCacheValid, ensureStandalone } = require('../lib/download-manager');
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
resolveDataDir,
|
|
10
|
+
resolveDataDirAndPassphrase,
|
|
11
|
+
printDefaultInstanceHint,
|
|
12
|
+
promptPassphrase,
|
|
13
|
+
loadDbKey,
|
|
14
|
+
} = require('../lib/db-helpers');
|
|
15
|
+
const { resolveInstance } = require('../lib/instances');
|
|
9
16
|
|
|
10
17
|
const PACKAGE_DIR = path.resolve(__dirname, '..');
|
|
11
18
|
|
|
@@ -20,6 +27,7 @@ function parseArgs(argv) {
|
|
|
20
27
|
const opts = {
|
|
21
28
|
port: 3000,
|
|
22
29
|
dataDir: '',
|
|
30
|
+
instance: '',
|
|
23
31
|
open: false,
|
|
24
32
|
help: false,
|
|
25
33
|
version: false,
|
|
@@ -42,6 +50,10 @@ function parseArgs(argv) {
|
|
|
42
50
|
case '-d':
|
|
43
51
|
opts.dataDir = args[++i];
|
|
44
52
|
break;
|
|
53
|
+
case '--instance':
|
|
54
|
+
case '-i':
|
|
55
|
+
opts.instance = args[++i];
|
|
56
|
+
break;
|
|
45
57
|
case '--open':
|
|
46
58
|
case '-o':
|
|
47
59
|
opts.open = true;
|
|
@@ -79,11 +91,14 @@ Subcommands:
|
|
|
79
91
|
db Query encrypted databases
|
|
80
92
|
themes Manage theme bundles
|
|
81
93
|
docs Inspect, read, and export document mounts
|
|
94
|
+
memories Search, browse, and graph memories
|
|
95
|
+
instances Register / inspect named Quilltap instances
|
|
82
96
|
memory-diff <chatId> Dump existing memories and dry-run re-extraction for a chat
|
|
83
97
|
|
|
84
98
|
Options:
|
|
85
99
|
-p, --port <number> Port to listen on (default: 3000)
|
|
86
100
|
-d, --data-dir <path> Data directory (default: platform-specific)
|
|
101
|
+
-i, --instance <name> Use a registered instance (see 'quilltap instances')
|
|
87
102
|
-o, --open Open browser after server starts
|
|
88
103
|
-v, --version Show version number
|
|
89
104
|
--update Force re-download of application files
|
|
@@ -346,7 +361,20 @@ async function main() {
|
|
|
346
361
|
HOSTNAME: '0.0.0.0',
|
|
347
362
|
};
|
|
348
363
|
|
|
349
|
-
if (opts.dataDir) {
|
|
364
|
+
if (opts.dataDir && opts.instance) {
|
|
365
|
+
console.error('Error: Specify either --instance or --data-dir, not both.');
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
if (opts.instance) {
|
|
369
|
+
try {
|
|
370
|
+
const inst = resolveInstance(opts.instance);
|
|
371
|
+
env.QUILLTAP_DATA_DIR = inst.path;
|
|
372
|
+
opts.dataDir = inst.path; // surfaced in the startup banner below
|
|
373
|
+
} catch (err) {
|
|
374
|
+
console.error(`Error: ${err.message}`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
} else if (opts.dataDir) {
|
|
350
378
|
env.QUILLTAP_DATA_DIR = path.resolve(opts.dataDir);
|
|
351
379
|
}
|
|
352
380
|
|
|
@@ -690,16 +718,55 @@ function printDbHelp() {
|
|
|
690
718
|
Quilltap Database Tool
|
|
691
719
|
|
|
692
720
|
Usage: quilltap db [options] [sql]
|
|
721
|
+
quilltap db <subcommand> [args]
|
|
693
722
|
|
|
694
723
|
Query your encrypted Quilltap database directly.
|
|
695
724
|
|
|
696
|
-
|
|
697
|
-
|
|
725
|
+
Subcommands (high-level shortcuts; auto-pick the right database):
|
|
726
|
+
schema [table] Schema overview, or details for one table
|
|
727
|
+
schema --grep <text> Find tables/columns whose name matches
|
|
728
|
+
find character [query] List or look up characters by name/id
|
|
729
|
+
find chat [query] List or look up chats by title/id
|
|
730
|
+
find project [query] List or look up projects by name/id
|
|
731
|
+
chats --character <name|id> Chats containing a character
|
|
732
|
+
chats --project <name|id> Chats in a project
|
|
733
|
+
messages --chat <id|title> Recent messages in a chat
|
|
734
|
+
(flags: --last N --full --from <pid> --type <t>)
|
|
735
|
+
logs --chat <id|title> LLM logs for a chat
|
|
736
|
+
logs --message <id> LLM logs for a single message
|
|
737
|
+
logs --character <name|id> LLM logs for a character
|
|
738
|
+
logs --tail N Most recent LLM logs
|
|
739
|
+
message <id> Full content of a single chat message
|
|
740
|
+
log <id> Full request/response of a single LLM log
|
|
741
|
+
memories --character <id> Memories held by a character
|
|
742
|
+
(flags: --about <id|name> --source AUTO|MANUAL)
|
|
743
|
+
optimize [target...] Run maintenance (VACUUM + ANALYZE + PRAGMA optimize)
|
|
744
|
+
on the named databases, or all of them if no
|
|
745
|
+
target is given. Targets: main, llm-logs,
|
|
746
|
+
mount-points (or "all"). Refuses to run while a
|
|
747
|
+
live Quilltap instance holds the lock.
|
|
748
|
+
backup [target...] [--out <dir>]
|
|
749
|
+
Online consistent snapshot of the encrypted
|
|
750
|
+
databases. Safe while the server is running.
|
|
751
|
+
Default destination: <dataDir>/backups/<timestamp>/.
|
|
752
|
+
Each snapshot is re-opened with the same key and
|
|
753
|
+
verified with PRAGMA quick_check.
|
|
754
|
+
integrity [target...] Run PRAGMA cipher_integrity_check and
|
|
755
|
+
PRAGMA integrity_check on the encrypted
|
|
756
|
+
databases. Read-only; safe alongside a live
|
|
757
|
+
instance. Exit code: 0 ok, 1 issues, 2 open
|
|
758
|
+
failure.
|
|
759
|
+
Most subcommands also accept --json and --limit N.
|
|
760
|
+
|
|
761
|
+
Low-level options (legacy; still supported):
|
|
762
|
+
--tables List all tables in the active database
|
|
698
763
|
--count <table> Show row count for a table
|
|
699
|
-
--repl Interactive SQL prompt
|
|
764
|
+
--repl Interactive SQL prompt (extras: .cols, .find)
|
|
700
765
|
--llm-logs Target the LLM logs database
|
|
701
766
|
--mount-points Target the document mount-index database
|
|
702
|
-
--data-dir <path> Override data directory
|
|
767
|
+
--data-dir <path> Override data directory (pass instance root)
|
|
768
|
+
-i, --instance <name> Use a registered instance from instances.json
|
|
769
|
+
(see 'quilltap instances --help')
|
|
703
770
|
--passphrase <pass> Provide passphrase for encrypted .dbkey
|
|
704
771
|
-h, --help Show this help
|
|
705
772
|
|
|
@@ -713,22 +780,85 @@ will check the QUILLTAP_DB_PASSPHRASE environment variable, then prompt
|
|
|
713
780
|
interactively (with hidden input) if a TTY is available.
|
|
714
781
|
|
|
715
782
|
Examples:
|
|
783
|
+
quilltap db schema # tables grouped by domain
|
|
784
|
+
quilltap db schema chat_messages # columns + indexes + DDL link
|
|
785
|
+
quilltap db find character Friday # name → uuid
|
|
786
|
+
quilltap db chats --character Friday # all chats with Friday
|
|
787
|
+
quilltap db messages --chat <id> --last 50 --full # recent messages
|
|
788
|
+
quilltap db logs --chat "physical prompts" # llm logs for a chat
|
|
789
|
+
quilltap db log <log-id> # full request/response
|
|
790
|
+
quilltap db optimize # VACUUM + ANALYZE all DBs
|
|
791
|
+
quilltap db optimize llm-logs # only the LLM logs DB
|
|
792
|
+
quilltap db backup # snapshot all DBs to <dataDir>/backups/<ts>/
|
|
793
|
+
quilltap db backup main --out /tmp/qtap-snap # snapshot only the main DB
|
|
794
|
+
quilltap db integrity # cipher_integrity_check + integrity_check
|
|
716
795
|
quilltap db --tables
|
|
717
796
|
quilltap db "SELECT count(*) FROM characters"
|
|
718
797
|
quilltap db --count messages
|
|
719
798
|
quilltap db --repl
|
|
720
|
-
quilltap db --llm-logs --tables
|
|
721
|
-
quilltap db --mount-points --tables
|
|
722
|
-
quilltap db --mount-points "SELECT id, name FROM doc_mount_points"
|
|
723
799
|
quilltap db --lock-status
|
|
724
|
-
quilltap db --lock-clean
|
|
725
800
|
QUILLTAP_DB_PASSPHRASE=secret quilltap db --tables
|
|
726
801
|
`);
|
|
727
802
|
}
|
|
728
803
|
|
|
729
804
|
async function dbCommand(args) {
|
|
805
|
+
// ---- Pre-flight: extract dataDir/passphrase, then check for a verb subcommand
|
|
806
|
+
// before the legacy flag loop. This lets `quilltap db <verb>` dispatch
|
|
807
|
+
// to the new high-level commands without touching the legacy path.
|
|
808
|
+
const { isVerb, runVerb, makeCtx } = require('../lib/db-commands');
|
|
809
|
+
|
|
810
|
+
// Strip --data-dir / --passphrase / --instance out so they can appear anywhere
|
|
811
|
+
// on the line, including before or after the verb.
|
|
812
|
+
const cleaned = [];
|
|
730
813
|
let dataDirOverride = '';
|
|
731
814
|
let passphrase = '';
|
|
815
|
+
let instanceName = '';
|
|
816
|
+
for (let j = 0; j < args.length; j++) {
|
|
817
|
+
const a = args[j];
|
|
818
|
+
if (a === '--data-dir' || a === '-d') { dataDirOverride = args[++j]; continue; }
|
|
819
|
+
if (a === '--passphrase') { passphrase = args[++j]; continue; }
|
|
820
|
+
if (a === '--instance' || a === '-i') { instanceName = args[++j]; continue; }
|
|
821
|
+
cleaned.push(a);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Find first non-flag arg to test for verb
|
|
825
|
+
const firstPositional = cleaned.find(a => !a.startsWith('-'));
|
|
826
|
+
if (firstPositional && isVerb(firstPositional)) {
|
|
827
|
+
let resolved;
|
|
828
|
+
try {
|
|
829
|
+
resolved = resolveDataDirAndPassphrase({
|
|
830
|
+
dataDir: dataDirOverride,
|
|
831
|
+
instance: instanceName,
|
|
832
|
+
passphrase,
|
|
833
|
+
});
|
|
834
|
+
} catch (err) {
|
|
835
|
+
console.error(`Error: ${err.message}`);
|
|
836
|
+
process.exit(1);
|
|
837
|
+
}
|
|
838
|
+
printDefaultInstanceHint(resolved);
|
|
839
|
+
const dataDir = resolved.dataDir;
|
|
840
|
+
let pepper;
|
|
841
|
+
try {
|
|
842
|
+
pepper = await loadDbKey(dataDir, resolved.passphrase);
|
|
843
|
+
} catch (err) {
|
|
844
|
+
console.error(`Error: ${err.message}`);
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
// Re-strip global flags but preserve everything else (verb + its flags)
|
|
848
|
+
const ctx = makeCtx(dataDir, pepper);
|
|
849
|
+
try {
|
|
850
|
+
await runVerb(cleaned, ctx);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
if (!err.silent) {
|
|
853
|
+
console.error(`Error: ${err.message}`);
|
|
854
|
+
}
|
|
855
|
+
const code = err.exitCode != null ? err.exitCode : (err.ambiguous ? 2 : 1);
|
|
856
|
+
process.exit(code);
|
|
857
|
+
}
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// ---- Legacy flag-based path (unchanged) ----
|
|
732
862
|
let useLlmLogs = false;
|
|
733
863
|
let useMountPoints = false;
|
|
734
864
|
let showTables = false;
|
|
@@ -741,25 +871,23 @@ async function dbCommand(args) {
|
|
|
741
871
|
let lockOverride = false;
|
|
742
872
|
|
|
743
873
|
let i = 0;
|
|
744
|
-
while (i <
|
|
745
|
-
switch (
|
|
746
|
-
case '--data-dir': case '-d': dataDirOverride = args[++i]; break;
|
|
747
|
-
case '--passphrase': passphrase = args[++i]; break;
|
|
874
|
+
while (i < cleaned.length) {
|
|
875
|
+
switch (cleaned[i]) {
|
|
748
876
|
case '--llm-logs': useLlmLogs = true; break;
|
|
749
877
|
case '--mount-points': useMountPoints = true; break;
|
|
750
878
|
case '--tables': showTables = true; break;
|
|
751
|
-
case '--count': countTable =
|
|
879
|
+
case '--count': countTable = cleaned[++i]; break;
|
|
752
880
|
case '--repl': repl = true; break;
|
|
753
881
|
case '--help': case '-h': showHelp = true; break;
|
|
754
882
|
case '--lock-status': lockStatus = true; break;
|
|
755
883
|
case '--lock-clean': lockClean = true; break;
|
|
756
884
|
case '--lock-override': lockOverride = true; break;
|
|
757
885
|
default:
|
|
758
|
-
if (
|
|
759
|
-
console.error(`Unknown option: ${
|
|
886
|
+
if (cleaned[i].startsWith('-')) {
|
|
887
|
+
console.error(`Unknown option: ${cleaned[i]}`);
|
|
760
888
|
process.exit(1);
|
|
761
889
|
}
|
|
762
|
-
sql =
|
|
890
|
+
sql = cleaned[i];
|
|
763
891
|
break;
|
|
764
892
|
}
|
|
765
893
|
i++;
|
|
@@ -770,7 +898,20 @@ async function dbCommand(args) {
|
|
|
770
898
|
process.exit(0);
|
|
771
899
|
}
|
|
772
900
|
|
|
773
|
-
|
|
901
|
+
let legacyResolved;
|
|
902
|
+
try {
|
|
903
|
+
legacyResolved = resolveDataDirAndPassphrase({
|
|
904
|
+
dataDir: dataDirOverride,
|
|
905
|
+
instance: instanceName,
|
|
906
|
+
passphrase,
|
|
907
|
+
});
|
|
908
|
+
} catch (err) {
|
|
909
|
+
console.error(`Error: ${err.message}`);
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
printDefaultInstanceHint(legacyResolved);
|
|
913
|
+
const dataDir = legacyResolved.dataDir;
|
|
914
|
+
passphrase = legacyResolved.passphrase;
|
|
774
915
|
|
|
775
916
|
// ---- Instance lock commands (no database open required) ----
|
|
776
917
|
if (lockStatus || lockClean || lockOverride) {
|
|
@@ -851,7 +992,7 @@ async function dbCommand(args) {
|
|
|
851
992
|
const readline = require('readline');
|
|
852
993
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: 'quilltap> ' });
|
|
853
994
|
console.log(`Connected to ${dbPath}`);
|
|
854
|
-
console.log('Type .tables, .schema <table>, or SQL. Ctrl+D to exit.\n');
|
|
995
|
+
console.log('Type .tables, .schema <table>, .cols <table>, .find <text>, or SQL. Ctrl+D to exit.\n');
|
|
855
996
|
rl.prompt();
|
|
856
997
|
rl.on('line', (line) => {
|
|
857
998
|
const trimmed = line.trim();
|
|
@@ -867,6 +1008,38 @@ async function dbCommand(args) {
|
|
|
867
1008
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE name = ?").get(table);
|
|
868
1009
|
console.log(row ? row.sql : `Table '${table}' not found`);
|
|
869
1010
|
}
|
|
1011
|
+
} else if (trimmed.startsWith('.cols')) {
|
|
1012
|
+
const table = trimmed.split(/\s+/)[1];
|
|
1013
|
+
if (!table) { console.log('Usage: .cols <table>'); }
|
|
1014
|
+
else {
|
|
1015
|
+
const cols = db.prepare(`PRAGMA table_info("${table.replace(/"/g, '""')}")`).all();
|
|
1016
|
+
if (cols.length === 0) console.log(`Table '${table}' not found`);
|
|
1017
|
+
else console.table(cols.map(c => ({
|
|
1018
|
+
name: c.name, type: c.type,
|
|
1019
|
+
notNull: c.notnull ? 'NOT NULL' : '',
|
|
1020
|
+
default: c.dflt_value === null ? '' : c.dflt_value,
|
|
1021
|
+
pk: c.pk ? 'pk' : '',
|
|
1022
|
+
})));
|
|
1023
|
+
}
|
|
1024
|
+
} else if (trimmed.startsWith('.find')) {
|
|
1025
|
+
const needle = trimmed.slice(5).trim();
|
|
1026
|
+
if (!needle) { console.log('Usage: .find <text>'); }
|
|
1027
|
+
else {
|
|
1028
|
+
const lc = needle.toLowerCase();
|
|
1029
|
+
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all().map(r => r.name);
|
|
1030
|
+
const matches = [];
|
|
1031
|
+
for (const t of tables) {
|
|
1032
|
+
const tableHit = t.toLowerCase().includes(lc);
|
|
1033
|
+
const cols = db.prepare(`PRAGMA table_info("${t.replace(/"/g, '""')}")`).all();
|
|
1034
|
+
const colHits = cols.filter(c => c.name.toLowerCase().includes(lc)).map(c => c.name);
|
|
1035
|
+
if (tableHit || colHits.length) matches.push({ table: t, tableMatch: tableHit, columns: colHits });
|
|
1036
|
+
}
|
|
1037
|
+
if (matches.length === 0) console.log(`No tables or columns matching '${needle}'`);
|
|
1038
|
+
else for (const m of matches) {
|
|
1039
|
+
const flag = m.tableMatch ? '*' : ' ';
|
|
1040
|
+
console.log(`${flag} ${m.table}${m.columns.length ? ' ' + m.columns.map(c => '.' + c).join(', ') : ''}`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
870
1043
|
} else {
|
|
871
1044
|
const stmt = db.prepare(trimmed);
|
|
872
1045
|
if (stmt.reader) {
|
|
@@ -905,12 +1078,45 @@ if (process.argv[2] === 'db') {
|
|
|
905
1078
|
} else if (process.argv[2] === 'docs') {
|
|
906
1079
|
const { docsCommand } = require('../lib/docs-commands');
|
|
907
1080
|
docsCommand(process.argv.slice(3));
|
|
1081
|
+
} else if (process.argv[2] === 'memories') {
|
|
1082
|
+
const { memoriesCommand } = require('../lib/memories-commands');
|
|
1083
|
+
memoriesCommand(process.argv.slice(3)).catch(err => {
|
|
1084
|
+
if (!err.silent) {
|
|
1085
|
+
console.error(`Error: ${err.message}`);
|
|
1086
|
+
}
|
|
1087
|
+
const code = err.exitCode != null ? err.exitCode : (err.ambiguous ? 2 : 1);
|
|
1088
|
+
process.exit(code);
|
|
1089
|
+
});
|
|
1090
|
+
} else if (process.argv[2] === 'instances') {
|
|
1091
|
+
const { instancesCommand } = require('../lib/instances-commands');
|
|
1092
|
+
instancesCommand(process.argv.slice(3)).catch(err => {
|
|
1093
|
+
console.error(`Error: ${err.message}`);
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
});
|
|
908
1096
|
} else if (process.argv[2] === 'memory-diff') {
|
|
909
1097
|
const { memoryDiffCommand } = require('../lib/memory-diff-command');
|
|
910
1098
|
memoryDiffCommand(process.argv.slice(3)).catch(err => {
|
|
911
1099
|
console.error(`Error: ${err.message}`);
|
|
912
1100
|
process.exit(1);
|
|
913
1101
|
});
|
|
1102
|
+
} else if (process.argv[2] === 'completion') {
|
|
1103
|
+
const { completionCommand } = require('../lib/completion-commands');
|
|
1104
|
+
completionCommand(process.argv.slice(3)).catch(err => {
|
|
1105
|
+
console.error(`Error: ${err.message}`);
|
|
1106
|
+
process.exit(1);
|
|
1107
|
+
});
|
|
1108
|
+
} else if (process.argv[2] === 'logs') {
|
|
1109
|
+
const { logsCommand } = require('../lib/logs-commands');
|
|
1110
|
+
logsCommand(process.argv.slice(3)).catch(err => {
|
|
1111
|
+
console.error(`Error: ${err.message}`);
|
|
1112
|
+
process.exit(1);
|
|
1113
|
+
});
|
|
1114
|
+
} else if (process.argv[2] === 'migrations') {
|
|
1115
|
+
const { migrationsCommand } = require('../lib/migrations-commands');
|
|
1116
|
+
migrationsCommand(process.argv.slice(3)).catch(err => {
|
|
1117
|
+
console.error(`Error: ${err.message}`);
|
|
1118
|
+
process.exit(1);
|
|
1119
|
+
});
|
|
914
1120
|
} else {
|
|
915
1121
|
main();
|
|
916
1122
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Bash completion for quilltap
|
|
4
|
+
# Source this file or place it in /etc/bash_completion.d/ or ~/.bash_completion.d/
|
|
5
|
+
|
|
6
|
+
_quilltap_complete() {
|
|
7
|
+
local cur prev words cword
|
|
8
|
+
COMPREPLY=()
|
|
9
|
+
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
10
|
+
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
11
|
+
words=("${COMP_WORDS[@]}")
|
|
12
|
+
cword=$COMP_CWORD
|
|
13
|
+
|
|
14
|
+
# Global options that can appear before subcommands
|
|
15
|
+
local global_opts="-d --data-dir -i --instance -p --port -o --open -v --version -h --help --update"
|
|
16
|
+
|
|
17
|
+
# Get the subcommand (first non-option word after quilltap)
|
|
18
|
+
local subcommand=""
|
|
19
|
+
local i=1
|
|
20
|
+
while [[ $i -lt $cword ]]; do
|
|
21
|
+
local word="${words[$i]}"
|
|
22
|
+
case "$word" in
|
|
23
|
+
-d|--data-dir|-i|--instance|-p|--port)
|
|
24
|
+
# These take a value, skip the next word
|
|
25
|
+
((i += 2))
|
|
26
|
+
;;
|
|
27
|
+
-o|--open|-v|--version|-h|--help|--update)
|
|
28
|
+
# Flags without values
|
|
29
|
+
((i += 1))
|
|
30
|
+
;;
|
|
31
|
+
-*)
|
|
32
|
+
# Unknown flag, skip
|
|
33
|
+
((i += 1))
|
|
34
|
+
;;
|
|
35
|
+
*)
|
|
36
|
+
# This is the subcommand
|
|
37
|
+
subcommand="$word"
|
|
38
|
+
break
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
# If we're completing a global flag value
|
|
44
|
+
if [[ "$prev" == "-d" ]] || [[ "$prev" == "--data-dir" ]] || \
|
|
45
|
+
[[ "$prev" == "-p" ]] || [[ "$prev" == "--port" ]]; then
|
|
46
|
+
# No completion for paths or ports
|
|
47
|
+
return
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if [[ "$prev" == "-i" ]] || [[ "$prev" == "--instance" ]]; then
|
|
51
|
+
# Complete with instance names
|
|
52
|
+
local instances=$(command quilltap instances list --names-only 2>/dev/null)
|
|
53
|
+
COMPREPLY=($(compgen -W "$instances" -- "$cur"))
|
|
54
|
+
return
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# If no subcommand yet, complete with subcommands and global options
|
|
58
|
+
if [[ -z "$subcommand" ]]; then
|
|
59
|
+
if [[ "$cur" == -* ]]; then
|
|
60
|
+
COMPREPLY=($(compgen -W "$global_opts" -- "$cur"))
|
|
61
|
+
else
|
|
62
|
+
COMPREPLY=($(compgen -W "db docs themes instances memories memory-diff completion" -- "$cur"))
|
|
63
|
+
fi
|
|
64
|
+
return
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Subcommand-specific completion
|
|
68
|
+
local subcommand_opts=""
|
|
69
|
+
case "$subcommand" in
|
|
70
|
+
db)
|
|
71
|
+
subcommand_opts="schema find chats messages logs message log memories optimize backup integrity"
|
|
72
|
+
if [[ "$cur" == -* ]]; then
|
|
73
|
+
subcommand_opts="$subcommand_opts --instance --data-dir --json --help"
|
|
74
|
+
fi
|
|
75
|
+
COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
|
|
76
|
+
;;
|
|
77
|
+
docs)
|
|
78
|
+
local docs_verbs="list show files ls dir read export scan write delete mkdir move copy status find grep reindex embed"
|
|
79
|
+
if [[ "$cur" == -* ]]; then
|
|
80
|
+
docs_verbs="$docs_verbs --mount --instance --data-dir --port --json --help --force --rendered --links"
|
|
81
|
+
fi
|
|
82
|
+
COMPREPLY=($(compgen -W "$docs_verbs" -- "$cur"))
|
|
83
|
+
;;
|
|
84
|
+
themes)
|
|
85
|
+
subcommand_opts="list install uninstall validate export create search update registry"
|
|
86
|
+
if [[ "$cur" == -* ]]; then
|
|
87
|
+
subcommand_opts="$subcommand_opts --instance --data-dir --output --help"
|
|
88
|
+
fi
|
|
89
|
+
COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
|
|
90
|
+
;;
|
|
91
|
+
instances)
|
|
92
|
+
subcommand_opts="list ls show path where add create remove rm delete set-passphrase passphrase"
|
|
93
|
+
if [[ "$cur" == -* ]]; then
|
|
94
|
+
subcommand_opts="$subcommand_opts --help"
|
|
95
|
+
fi
|
|
96
|
+
COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
|
|
97
|
+
;;
|
|
98
|
+
memories)
|
|
99
|
+
local mem_verbs="ls find grep show tree status validate"
|
|
100
|
+
if [[ "$cur" == -* ]]; then
|
|
101
|
+
mem_verbs="$mem_verbs --character --about --source --chat --project --since --until --min-importance --min-reinforced --has-embedding --no-embedding --sort --instance --data-dir --json --help"
|
|
102
|
+
fi
|
|
103
|
+
COMPREPLY=($(compgen -W "$mem_verbs" -- "$cur"))
|
|
104
|
+
;;
|
|
105
|
+
memory-diff)
|
|
106
|
+
if [[ "$cur" == -* ]]; then
|
|
107
|
+
subcommand_opts="--instance --data-dir --help"
|
|
108
|
+
COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
|
|
109
|
+
fi
|
|
110
|
+
;;
|
|
111
|
+
completion)
|
|
112
|
+
if [[ "$cur" == -* ]]; then
|
|
113
|
+
COMPREPLY=($(compgen -W "--help" -- "$cur"))
|
|
114
|
+
else
|
|
115
|
+
COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur"))
|
|
116
|
+
fi
|
|
117
|
+
;;
|
|
118
|
+
esac
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
complete -F _quilltap_complete quilltap
|