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 +18 -1
- package/bin/quilltap.js +74 -4
- package/lib/theme-commands.js +1223 -0
- package/lib/theme-validation.js +386 -0
- package/package.json +4 -3
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
}
|