quilltap 3.2.1 → 3.3.0-dev.108

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 CHANGED
@@ -89,6 +89,23 @@ 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
+ ## Theme Management
93
+
94
+ The CLI includes theme management commands:
95
+
96
+ ```bash
97
+ quilltap themes list # List all installed themes
98
+ quilltap themes install my.qtap-theme # Install a .qtap-theme bundle
99
+ quilltap themes validate my.qtap-theme # Validate a bundle
100
+ quilltap themes uninstall my-theme # Uninstall a bundle theme
101
+ quilltap themes export earl-grey # Export any theme as a bundle
102
+ quilltap themes create sunset # Scaffold a new theme
103
+ quilltap themes search "dark" # Search registries
104
+ quilltap themes update # Check for theme updates
105
+ quilltap themes registry list # List configured registries
106
+ quilltap themes registry add <url> # Add a registry source
107
+ ```
108
+
92
109
  ## Requirements
93
110
 
94
111
  - Node.js 18 or later
@@ -96,7 +113,7 @@ Override with `--data-dir` or the `QUILLTAP_DATA_DIR` environment variable.
96
113
  ## Other Ways to Run Quilltap
97
114
 
98
115
  - **Electron desktop app** (macOS, Windows) - [Download](https://github.com/foundry-9/quilltap/releases)
99
- - **Docker** - `docker run -d -p 3000:3000 -v /path/to/data:/app/quilltap csebold/quilltap`
116
+ - **Docker** - `docker run -d -p 3000:3000 -v /path/to/data:/app/quilltap foundry9/quilltap`
100
117
 
101
118
  ## Links
102
119
 
package/bin/quilltap.js CHANGED
@@ -57,6 +57,7 @@ function parseArgs(argv) {
57
57
  opts.help = true;
58
58
  break;
59
59
  default:
60
+ // Allow subcommands to pass through (they're handled before parseArgs)
60
61
  console.error(`Unknown argument: ${args[i]}`);
61
62
  console.error('Run "quilltap --help" for usage information.');
62
63
  process.exit(1);
@@ -73,6 +74,10 @@ Quilltap - Self-hosted AI workspace
73
74
 
74
75
  Usage: quilltap [options]
75
76
 
77
+ Subcommands:
78
+ db Query encrypted databases
79
+ themes Manage theme bundles
80
+
76
81
  Options:
77
82
  -p, --port <number> Port to listen on (default: 3000)
78
83
  -d, --data-dir <path> Data directory (default: platform-specific)
@@ -386,10 +391,59 @@ function resolveDataDir(overrideDir) {
386
391
  return path.join(home, '.quilltap', 'data');
387
392
  }
388
393
 
394
+ /**
395
+ * Prompt for a passphrase interactively with hidden input.
396
+ * Returns a promise that resolves to the entered passphrase.
397
+ */
398
+ function promptPassphrase(prompt) {
399
+ return new Promise((resolve, reject) => {
400
+ const readline = require('readline');
401
+ if (!process.stdin.isTTY) {
402
+ reject(new Error('This database requires a passphrase. Use --passphrase <pass> or set QUILLTAP_DB_PASSPHRASE'));
403
+ return;
404
+ }
405
+ process.stdout.write(prompt || 'Passphrase: ');
406
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
407
+ // Disable echo by switching stdin to raw mode
408
+ process.stdin.setRawMode(true);
409
+ process.stdin.resume();
410
+ let passphrase = '';
411
+ const onData = (ch) => {
412
+ const c = ch.toString();
413
+ if (c === '\n' || c === '\r' || c === '\u0004') {
414
+ // Enter or Ctrl+D — done
415
+ process.stdin.setRawMode(false);
416
+ process.stdin.removeListener('data', onData);
417
+ process.stdin.pause();
418
+ rl.close();
419
+ process.stdout.write('\n');
420
+ resolve(passphrase);
421
+ } else if (c === '\u0003') {
422
+ // Ctrl+C — abort
423
+ process.stdin.setRawMode(false);
424
+ process.stdin.removeListener('data', onData);
425
+ process.stdin.pause();
426
+ rl.close();
427
+ process.stdout.write('\n');
428
+ process.exit(130);
429
+ } else if (c === '\u007F' || c === '\b') {
430
+ // Backspace
431
+ if (passphrase.length > 0) {
432
+ passphrase = passphrase.slice(0, -1);
433
+ }
434
+ } else {
435
+ passphrase += c;
436
+ }
437
+ };
438
+ process.stdin.on('data', onData);
439
+ });
440
+ }
441
+
389
442
  /**
390
443
  * Read and decrypt the .dbkey file to get the SQLCipher key.
444
+ * If passphrase is needed and not provided, prompts interactively.
391
445
  */
392
- function loadDbKey(dataDir, passphrase) {
446
+ async function loadDbKey(dataDir, passphrase) {
393
447
  const crypto = require('crypto');
394
448
  const dbkeyPath = path.join(dataDir, 'quilltap.dbkey');
395
449
  if (!fs.existsSync(dbkeyPath)) {
@@ -429,9 +483,17 @@ function loadDbKey(dataDir, passphrase) {
429
483
  // Internal passphrase failed — need user passphrase
430
484
  }
431
485
 
432
- // User passphrase required
486
+ // Check environment variable if no CLI passphrase provided
487
+ if (!passphrase && process.env.QUILLTAP_DB_PASSPHRASE) {
488
+ passphrase = process.env.QUILLTAP_DB_PASSPHRASE;
489
+ }
490
+
491
+ // Prompt interactively if still no passphrase
433
492
  if (!passphrase) {
434
- throw new Error('This database requires a passphrase. Use --passphrase <pass>');
493
+ passphrase = await promptPassphrase('Database passphrase: ');
494
+ if (!passphrase) {
495
+ throw new Error('No passphrase provided');
496
+ }
435
497
  }
436
498
 
437
499
  return tryDecrypt(passphrase);
@@ -454,12 +516,17 @@ Options:
454
516
  --passphrase <pass> Provide passphrase for encrypted .dbkey
455
517
  -h, --help Show this help
456
518
 
519
+ If a passphrase is required and not provided via --passphrase, the tool
520
+ will check the QUILLTAP_DB_PASSPHRASE environment variable, then prompt
521
+ interactively (with hidden input) if a TTY is available.
522
+
457
523
  Examples:
458
524
  quilltap db --tables
459
525
  quilltap db "SELECT count(*) FROM characters"
460
526
  quilltap db --count messages
461
527
  quilltap db --repl
462
528
  quilltap db --llm-logs --tables
529
+ QUILLTAP_DB_PASSPHRASE=secret quilltap db --tables
463
530
  `);
464
531
  }
465
532
 
@@ -511,7 +578,7 @@ async function dbCommand(args) {
511
578
  // Load encryption key
512
579
  let pepper;
513
580
  try {
514
- pepper = loadDbKey(dataDir, passphrase);
581
+ pepper = await loadDbKey(dataDir, passphrase);
515
582
  } catch (err) {
516
583
  console.error(`Error: ${err.message}`);
517
584
  process.exit(1);
@@ -613,6 +680,9 @@ async function dbCommand(args) {
613
680
  // Route to subcommand or main
614
681
  if (process.argv[2] === 'db') {
615
682
  dbCommand(process.argv.slice(3));
683
+ } else if (process.argv[2] === 'themes') {
684
+ const { themesCommand } = require('../lib/theme-commands');
685
+ themesCommand(process.argv.slice(3));
616
686
  } else {
617
687
  main();
618
688
  }