quilltap 4.6.0-dev → 4.6.0-dev.39

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 CHANGED
@@ -266,25 +266,67 @@ function linkNativeModules(standaloneDir) {
266
266
  || resolveModuleDir('better-sqlite3');
267
267
  linkModule('better-sqlite3', betterSqlite3Dir);
268
268
 
269
- // Link node-pty — the standalone tarball strips it (platform-specific),
270
- // and pty-manager loads it via a dynamic require, so it needs to resolve
271
- // from standaloneDir/node_modules.
272
- const nodePtyDir = resolveModuleDir('node-pty');
273
- linkModule('node-pty', nodePtyDir);
274
- if (nodePtyDir) {
275
- // Some npm cache extractions strip the executable bit on spawn-helper,
276
- // causing pty.spawn() to fail with `posix_spawnp failed`. Restore it.
277
- const prebuildsDir = path.join(nodePtyDir, 'prebuilds');
278
- if (fs.existsSync(prebuildsDir)) {
279
- try {
280
- for (const entry of fs.readdirSync(prebuildsDir, { withFileTypes: true })) {
281
- if (!entry.isDirectory()) continue;
282
- const helper = path.join(prebuildsDir, entry.name, 'spawn-helper');
283
- if (fs.existsSync(helper)) {
284
- try { fs.chmodSync(helper, 0o755); } catch { /* best-effort */ }
269
+ // Link node-pty — but only when we have to. The standalone tarball ships
270
+ // node-pty intact with cross-platform prebuilds (darwin-arm64, darwin-x64,
271
+ // win32-arm64, win32-x64). On those platforms the tarball-shipped copy is
272
+ // already correct, and we MUST NOT replace it with a symlink to the
273
+ // npm-installed copy: `sudo npm install -g quilltap` on macOS can strip the
274
+ // executable bit off `spawn-helper`, and we can't chmod a root-owned file
275
+ // back as a non-root runtime user pty.spawn() then fails with
276
+ // `posix_spawnp failed`. Only Linux (which has no node-pty prebuild) and
277
+ // rebuild-failure cases need the symlink.
278
+ const standaloneNodePtyPath = path.join(standaloneNodeModules, 'node-pty');
279
+ const standaloneHasUsablePty = (() => {
280
+ try {
281
+ const stat = fs.lstatSync(standaloneNodePtyPath);
282
+ if (stat.isSymbolicLink()) return false; // prior broken-symlink state — replace it
283
+ if (!stat.isDirectory()) return false;
284
+ } catch {
285
+ return false;
286
+ }
287
+ const platformPrebuildDir = path.join(
288
+ standaloneNodePtyPath,
289
+ 'prebuilds',
290
+ `${process.platform}-${process.arch}`,
291
+ );
292
+ return fs.existsSync(platformPrebuildDir);
293
+ })();
294
+
295
+ if (!standaloneHasUsablePty) {
296
+ const nodePtyDir = resolveModuleDir('node-pty');
297
+ linkModule('node-pty', nodePtyDir);
298
+ if (nodePtyDir) {
299
+ // Some npm cache extractions (notably `sudo npm install -g`) strip the
300
+ // executable bit on spawn-helper. Restore it where we can. If chmod
301
+ // fails because we don't own the file (typical when the CLI was
302
+ // installed with sudo and is run as a non-root user), warn loudly —
303
+ // silent failure here produces the `posix_spawnp failed` runtime error
304
+ // with no actionable hint.
305
+ const prebuildsDir = path.join(nodePtyDir, 'prebuilds');
306
+ if (fs.existsSync(prebuildsDir)) {
307
+ try {
308
+ for (const entry of fs.readdirSync(prebuildsDir, { withFileTypes: true })) {
309
+ if (!entry.isDirectory()) continue;
310
+ const helper = path.join(prebuildsDir, entry.name, 'spawn-helper');
311
+ if (!fs.existsSync(helper)) continue;
312
+ try {
313
+ fs.chmodSync(helper, 0o755);
314
+ } catch (err) {
315
+ if (err && err.code === 'EPERM') {
316
+ console.error('');
317
+ console.error(` Warning: Could not make node-pty spawn-helper executable:`);
318
+ console.error(` ${helper}`);
319
+ console.error(` The file is owned by another user (likely root from a sudo'd`);
320
+ console.error(` global install). Terminal sessions will fail to spawn with`);
321
+ console.error(` "posix_spawnp failed" until this is fixed. Run:`);
322
+ console.error(` sudo chmod 755 "${helper}"`);
323
+ console.error('');
324
+ }
325
+ // Other errors are non-fatal — node-pty may still work if the bit was already set.
326
+ }
285
327
  }
286
- }
287
- } catch { /* best-effort */ }
328
+ } catch { /* best-effort */ }
329
+ }
288
330
  }
289
331
  }
290
332
 
@@ -740,6 +782,11 @@ Subcommands (high-level shortcuts; auto-pick the right database):
740
782
  log <id> Full request/response of a single LLM log
741
783
  memories --character <id> Memories held by a character
742
784
  (flags: --about <id|name> --source AUTO|MANUAL)
785
+ characters status Per-character vault readiness report:
786
+ flag value, vault present, single-file count,
787
+ Prompts/ and Scenarios/ folder counts, and any
788
+ divergence between DB columns and vault content.
789
+ (flags: --id <id|name> --diverged --blocked --limit N)
743
790
  optimize [target...] Run maintenance (VACUUM + ANALYZE + PRAGMA optimize)
744
791
  on the named databases, or all of them if no
745
792
  target is given. Targets: main, llm-logs,
@@ -762,6 +809,8 @@ Low-level options (legacy; still supported):
762
809
  --tables List all tables in the active database
763
810
  --count <table> Show row count for a table
764
811
  --repl Interactive SQL prompt (extras: .cols, .find)
812
+ --json Emit machine-readable JSON instead of a table
813
+ (works with --tables, --count, and raw SQL)
765
814
  --llm-logs Target the LLM logs database
766
815
  --mount-points Target the document mount-index database
767
816
  --data-dir <path> Override data directory (pass instance root)
@@ -869,6 +918,7 @@ async function dbCommand(args) {
869
918
  let lockStatus = false;
870
919
  let lockClean = false;
871
920
  let lockOverride = false;
921
+ let asJson = false;
872
922
 
873
923
  let i = 0;
874
924
  while (i < cleaned.length) {
@@ -878,6 +928,7 @@ async function dbCommand(args) {
878
928
  case '--tables': showTables = true; break;
879
929
  case '--count': countTable = cleaned[++i]; break;
880
930
  case '--repl': repl = true; break;
931
+ case '--json': asJson = true; break;
881
932
  case '--help': case '-h': showHelp = true; break;
882
933
  case '--lock-status': lockStatus = true; break;
883
934
  case '--lock-clean': lockClean = true; break;
@@ -971,22 +1022,27 @@ async function dbCommand(args) {
971
1022
  try {
972
1023
  if (showTables) {
973
1024
  const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all();
974
- for (const t of tables) console.log(t.name);
1025
+ if (asJson) console.log(JSON.stringify(tables.map(t => t.name), null, 2));
1026
+ else for (const t of tables) console.log(t.name);
975
1027
  } else if (countTable) {
976
1028
  const row = db.prepare(`SELECT count(*) as count FROM "${countTable}"`).get();
977
- console.log(row.count);
1029
+ if (asJson) console.log(JSON.stringify({ table: countTable, count: row.count }, null, 2));
1030
+ else console.log(row.count);
978
1031
  } else if (sql) {
979
1032
  const stmt = db.prepare(sql);
980
1033
  if (stmt.reader) {
981
1034
  const rows = stmt.all();
982
- if (rows.length === 0) {
1035
+ if (asJson) {
1036
+ console.log(JSON.stringify(rows, null, 2));
1037
+ } else if (rows.length === 0) {
983
1038
  console.log('(no results)');
984
1039
  } else {
985
1040
  console.table(rows);
986
1041
  }
987
1042
  } else {
988
1043
  const info = stmt.run();
989
- console.log(`Changes: ${info.changes}`);
1044
+ if (asJson) console.log(JSON.stringify({ changes: info.changes, lastInsertRowid: Number(info.lastInsertRowid) }, null, 2));
1045
+ else console.log(`Changes: ${info.changes}`);
990
1046
  }
991
1047
  } else if (repl) {
992
1048
  const readline = require('readline');
@@ -12,15 +12,19 @@ _quilltap_complete() {
12
12
  cword=$COMP_CWORD
13
13
 
14
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"
15
+ local global_opts="-d --data-dir -i --instance -p --port -o --open -v --version -h --help --update --passphrase"
16
+
17
+ # Top-level subcommands
18
+ local top_cmds="db docs themes instances memories memory-diff logs migrations completion"
16
19
 
17
20
  # Get the subcommand (first non-option word after quilltap)
18
21
  local subcommand=""
22
+ local subverb=""
19
23
  local i=1
20
24
  while [[ $i -lt $cword ]]; do
21
25
  local word="${words[$i]}"
22
26
  case "$word" in
23
- -d|--data-dir|-i|--instance|-p|--port)
27
+ -d|--data-dir|-i|--instance|-p|--port|--passphrase)
24
28
  # These take a value, skip the next word
25
29
  ((i += 2))
26
30
  ;;
@@ -33,22 +37,26 @@ _quilltap_complete() {
33
37
  ((i += 1))
34
38
  ;;
35
39
  *)
36
- # This is the subcommand
37
- subcommand="$word"
38
- break
40
+ if [[ -z "$subcommand" ]]; then
41
+ subcommand="$word"
42
+ elif [[ -z "$subverb" ]]; then
43
+ subverb="$word"
44
+ fi
45
+ ((i += 1))
39
46
  ;;
40
47
  esac
41
48
  done
42
49
 
43
50
  # 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
51
+ if [[ "$prev" == "-d" ]] || [[ "$prev" == "--data-dir" ]]; then
52
+ # Complete with directories
53
+ COMPREPLY=($(compgen -d -- "$cur"))
54
+ return
55
+ fi
56
+ if [[ "$prev" == "-p" ]] || [[ "$prev" == "--port" ]] || [[ "$prev" == "--passphrase" ]]; then
47
57
  return
48
58
  fi
49
-
50
59
  if [[ "$prev" == "-i" ]] || [[ "$prev" == "--instance" ]]; then
51
- # Complete with instance names
52
60
  local instances=$(command quilltap instances list --names-only 2>/dev/null)
53
61
  COMPREPLY=($(compgen -W "$instances" -- "$cur"))
54
62
  return
@@ -59,53 +67,164 @@ _quilltap_complete() {
59
67
  if [[ "$cur" == -* ]]; then
60
68
  COMPREPLY=($(compgen -W "$global_opts" -- "$cur"))
61
69
  else
62
- COMPREPLY=($(compgen -W "db docs themes instances memories memory-diff completion" -- "$cur"))
70
+ COMPREPLY=($(compgen -W "$top_cmds" -- "$cur"))
63
71
  fi
64
72
  return
65
73
  fi
66
74
 
75
+ # Shared flag-value completions for any subcommand
76
+ case "$prev" in
77
+ --mount)
78
+ local mounts=$(command quilltap docs list --names-only 2>/dev/null)
79
+ COMPREPLY=($(compgen -W "$mounts" -- "$cur"))
80
+ return
81
+ ;;
82
+ --character|--about)
83
+ # No live source; suggest common literals
84
+ COMPREPLY=($(compgen -W "all self none" -- "$cur"))
85
+ return
86
+ ;;
87
+ --source)
88
+ COMPREPLY=($(compgen -W "AUTO MANUAL" -- "$cur"))
89
+ return
90
+ ;;
91
+ --sort)
92
+ COMPREPLY=($(compgen -W "name path size modified created reinforced importance accessed reinforcement-count links" -- "$cur"))
93
+ return
94
+ ;;
95
+ --field)
96
+ COMPREPLY=($(compgen -W "request response both" -- "$cur"))
97
+ return
98
+ ;;
99
+ --type)
100
+ COMPREPLY=($(compgen -W "file folder" -- "$cur"))
101
+ return
102
+ ;;
103
+ --stream)
104
+ COMPREPLY=($(compgen -W "combined error stdout stderr startup" -- "$cur"))
105
+ return
106
+ ;;
107
+ esac
108
+
67
109
  # Subcommand-specific completion
68
- local subcommand_opts=""
69
110
  case "$subcommand" in
70
111
  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"
112
+ local db_verbs="schema find chats messages logs message log memories optimize backup integrity"
113
+ local db_flags="--instance --data-dir --passphrase --json --limit --grep \
114
+ --character --project --about --source --chat --message --rendered --field \
115
+ --tail --last --full --from --type --out --help \
116
+ --tables --count --repl --llm-logs --mount-points \
117
+ --lock-status --lock-clean --lock-override"
118
+ if [[ -z "$subverb" ]]; then
119
+ if [[ "$cur" == -* ]]; then
120
+ COMPREPLY=($(compgen -W "$db_flags" -- "$cur"))
121
+ else
122
+ COMPREPLY=($(compgen -W "$db_verbs" -- "$cur"))
123
+ fi
124
+ else
125
+ COMPREPLY=($(compgen -W "$db_flags" -- "$cur"))
74
126
  fi
75
- COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
76
127
  ;;
77
128
  docs)
78
129
  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"
130
+ local docs_flags="--mount --instance --data-dir --passphrase --port --json --help \
131
+ --force --rendered --links --folder --type --ext --limit --max --context --top --threshold \
132
+ --ignore-case -l --wait -R --recursive --sort -r --reverse --depth --max-nodes --long --semantic"
133
+ if [[ -z "$subverb" ]]; then
134
+ if [[ "$cur" == -* ]]; then
135
+ COMPREPLY=($(compgen -W "$docs_flags" -- "$cur"))
136
+ else
137
+ COMPREPLY=($(compgen -W "$docs_verbs" -- "$cur"))
138
+ fi
139
+ else
140
+ COMPREPLY=($(compgen -W "$docs_flags" -- "$cur"))
81
141
  fi
82
- COMPREPLY=($(compgen -W "$docs_verbs" -- "$cur"))
83
142
  ;;
84
143
  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"
144
+ local themes_verbs="list install uninstall validate export create search update registry"
145
+ local themes_flags="--instance --data-dir --output -o --help"
146
+ if [[ -z "$subverb" ]]; then
147
+ if [[ "$cur" == -* ]]; then
148
+ COMPREPLY=($(compgen -W "$themes_flags" -- "$cur"))
149
+ else
150
+ COMPREPLY=($(compgen -W "$themes_verbs" -- "$cur"))
151
+ fi
152
+ elif [[ "$subverb" == "registry" ]]; then
153
+ local registry_verbs="list add remove refresh keygen sign"
154
+ local registry_flags="--key -k --name -n --output -o --help"
155
+ if [[ "$cur" == -* ]]; then
156
+ COMPREPLY=($(compgen -W "$registry_flags" -- "$cur"))
157
+ else
158
+ COMPREPLY=($(compgen -W "$registry_verbs" -- "$cur"))
159
+ fi
160
+ else
161
+ COMPREPLY=($(compgen -W "$themes_flags" -- "$cur"))
88
162
  fi
89
- COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
90
163
  ;;
91
164
  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"
165
+ local inst_verbs="list ls show path where add create remove rm delete set-passphrase passphrase default rename"
166
+ local inst_flags="--names-only --json --clear --help"
167
+ if [[ -z "$subverb" ]]; then
168
+ if [[ "$cur" == -* ]]; then
169
+ COMPREPLY=($(compgen -W "$inst_flags" -- "$cur"))
170
+ else
171
+ COMPREPLY=($(compgen -W "$inst_verbs" -- "$cur"))
172
+ fi
173
+ else
174
+ # Most instances verbs take a name as positional; offer registered names
175
+ case "$subverb" in
176
+ show|remove|rm|delete|set-passphrase|passphrase|default|rename)
177
+ local instances=$(command quilltap instances list --names-only 2>/dev/null)
178
+ if [[ "$cur" == -* ]]; then
179
+ COMPREPLY=($(compgen -W "$inst_flags" -- "$cur"))
180
+ else
181
+ COMPREPLY=($(compgen -W "$instances" -- "$cur"))
182
+ fi
183
+ ;;
184
+ *)
185
+ COMPREPLY=($(compgen -W "$inst_flags" -- "$cur"))
186
+ ;;
187
+ esac
95
188
  fi
96
- COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
97
189
  ;;
98
190
  memories)
99
191
  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"
192
+ local mem_flags="--character --about --source --chat --project --since --until \
193
+ --min-importance --min-reinforced --has-embedding --no-embedding \
194
+ --sort -r --reverse --limit --full-titles --in --no-related --list \
195
+ --ignore-case -i --paths-only -l --max --context --depth --max-nodes \
196
+ --semantic --top --threshold \
197
+ --instance --data-dir --passphrase --port --json --help"
198
+ if [[ -z "$subverb" ]]; then
199
+ if [[ "$cur" == -* ]]; then
200
+ COMPREPLY=($(compgen -W "$mem_flags" -- "$cur"))
201
+ else
202
+ COMPREPLY=($(compgen -W "$mem_verbs" -- "$cur"))
203
+ fi
204
+ else
205
+ COMPREPLY=($(compgen -W "$mem_flags" -- "$cur"))
102
206
  fi
103
- COMPREPLY=($(compgen -W "$mem_verbs" -- "$cur"))
104
207
  ;;
105
208
  memory-diff)
106
- if [[ "$cur" == -* ]]; then
107
- subcommand_opts="--instance --data-dir --help"
108
- COMPREPLY=($(compgen -W "$subcommand_opts" -- "$cur"))
209
+ local md_flags="--instance --data-dir --passphrase --port --concurrency --out --help"
210
+ COMPREPLY=($(compgen -W "$md_flags" -- "$cur"))
211
+ ;;
212
+ logs)
213
+ local logs_flags="--stream --tail -f --follow --grep \
214
+ --instance --data-dir --passphrase --help"
215
+ COMPREPLY=($(compgen -W "$logs_flags" -- "$cur"))
216
+ ;;
217
+ migrations)
218
+ local mig_verbs="status pending run"
219
+ local mig_flags="--dry-run --json --instance --data-dir --passphrase --help"
220
+ if [[ -z "$subverb" ]]; then
221
+ if [[ "$cur" == -* ]]; then
222
+ COMPREPLY=($(compgen -W "$mig_flags" -- "$cur"))
223
+ else
224
+ COMPREPLY=($(compgen -W "$mig_verbs" -- "$cur"))
225
+ fi
226
+ else
227
+ COMPREPLY=($(compgen -W "$mig_flags" -- "$cur"))
109
228
  fi
110
229
  ;;
111
230
  completion)