clawvault 2.5.2 → 2.5.4

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.
Files changed (71) hide show
  1. package/README.md +159 -200
  2. package/bin/clawvault.js +111 -111
  3. package/bin/command-registration.test.js +166 -166
  4. package/bin/command-runtime.js +93 -93
  5. package/bin/command-runtime.test.js +154 -154
  6. package/bin/help-contract.test.js +39 -39
  7. package/bin/register-config-commands.js +153 -153
  8. package/bin/register-config-route-commands.test.js +121 -121
  9. package/bin/register-core-commands.js +237 -237
  10. package/bin/register-kanban-commands.js +56 -56
  11. package/bin/register-kanban-commands.test.js +83 -83
  12. package/bin/register-maintenance-commands.js +282 -282
  13. package/bin/register-project-commands.js +209 -209
  14. package/bin/register-project-commands.test.js +206 -206
  15. package/bin/register-query-commands.js +317 -317
  16. package/bin/register-query-commands.test.js +65 -65
  17. package/bin/register-resilience-commands.js +182 -182
  18. package/bin/register-resilience-commands.test.js +81 -81
  19. package/bin/register-route-commands.js +114 -114
  20. package/bin/register-session-lifecycle-commands.js +206 -206
  21. package/bin/register-tailscale-commands.js +106 -106
  22. package/bin/register-task-commands.js +348 -348
  23. package/bin/register-task-commands.test.js +69 -69
  24. package/bin/register-template-commands.js +72 -72
  25. package/bin/register-vault-operations-commands.js +300 -300
  26. package/bin/test-helpers/cli-command-fixtures.js +119 -119
  27. package/dashboard/lib/graph-diff.js +104 -104
  28. package/dashboard/lib/graph-diff.test.js +75 -75
  29. package/dashboard/lib/vault-parser.js +556 -556
  30. package/dashboard/lib/vault-parser.test.js +254 -254
  31. package/dashboard/public/app.js +796 -796
  32. package/dashboard/public/index.html +52 -52
  33. package/dashboard/public/styles.css +221 -221
  34. package/dashboard/server.js +374 -374
  35. package/dist/{chunk-3FP5BJ42.js → chunk-4QYGFWRM.js} +1 -1
  36. package/dist/{chunk-M25QVSJM.js → chunk-AXKYDCNN.js} +1 -1
  37. package/dist/{chunk-CLE2HHNT.js → chunk-IVRIKYFE.js} +18 -11
  38. package/dist/{chunk-HRTPQQF2.js → chunk-IZEY5S74.js} +1 -1
  39. package/dist/{chunk-HWUNREDJ.js → chunk-JDLOL2PL.js} +4 -4
  40. package/dist/{chunk-AY4PGUVL.js → chunk-KL4NAOMO.js} +1 -1
  41. package/dist/{chunk-O7XHXF7F.js → chunk-MAKNAHAW.js} +4 -4
  42. package/dist/{chunk-PLZKZW4I.js → chunk-OSMS7QIG.js} +1 -1
  43. package/dist/{chunk-NZ4ZZNSR.js → chunk-THRJVD4L.js} +1 -1
  44. package/dist/{chunk-4GBPTBFJ.js → chunk-TIGW564L.js} +1 -1
  45. package/dist/{chunk-BHO7WSAY.js → chunk-W2HNZC22.js} +3 -3
  46. package/dist/{chunk-GFJ3LIIB.js → chunk-XAVB4GB4.js} +1 -1
  47. package/dist/cli/index.js +10 -10
  48. package/dist/commands/context.js +3 -3
  49. package/dist/commands/doctor.js +4 -4
  50. package/dist/commands/embed.js +2 -2
  51. package/dist/commands/observe.js +2 -2
  52. package/dist/commands/setup.js +2 -2
  53. package/dist/commands/sleep.js +2 -2
  54. package/dist/commands/status.js +3 -3
  55. package/dist/commands/tailscale.js +3 -3
  56. package/dist/commands/wake.js +2 -2
  57. package/dist/index.js +12 -12
  58. package/dist/lib/tailscale.js +2 -2
  59. package/dist/lib/webdav.js +1 -1
  60. package/hooks/clawvault/HOOK.md +83 -74
  61. package/hooks/clawvault/handler.js +816 -816
  62. package/hooks/clawvault/handler.test.js +263 -263
  63. package/package.json +94 -125
  64. package/templates/checkpoint.md +19 -19
  65. package/templates/daily-note.md +19 -19
  66. package/templates/daily.md +19 -19
  67. package/templates/decision.md +17 -17
  68. package/templates/handoff.md +19 -19
  69. package/templates/lesson.md +16 -16
  70. package/templates/person.md +19 -19
  71. package/templates/project.md +23 -23
@@ -1,166 +1,166 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { Command } from 'commander';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import { registerCoreCommands } from './register-core-commands.js';
6
- import { registerMaintenanceCommands } from './register-maintenance-commands.js';
7
- import { registerQueryCommands } from './register-query-commands.js';
8
- import { registerResilienceCommands } from './register-resilience-commands.js';
9
- import { registerSessionLifecycleCommands } from './register-session-lifecycle-commands.js';
10
- import { registerTemplateCommands } from './register-template-commands.js';
11
- import { registerVaultOperationsCommands } from './register-vault-operations-commands.js';
12
- import { registerConfigCommands } from './register-config-commands.js';
13
- import { registerRouteCommands } from './register-route-commands.js';
14
- import {
15
- chalkStub,
16
- createGetVaultStub,
17
- registerAllCommandModules,
18
- stubResolveVaultPath
19
- } from './test-helpers/cli-command-fixtures.js';
20
-
21
- function listCommandNames(program) {
22
- return program.commands.map((command) => command.name()).sort((a, b) => a.localeCompare(b));
23
- }
24
-
25
- describe('CLI command registration modules', () => {
26
- it('registers core lifecycle commands', () => {
27
- const program = new Command();
28
- registerCoreCommands(program, {
29
- chalk: chalkStub,
30
- path,
31
- fs,
32
- createVault: async () => ({ getCategories: () => [], getQmdRoot: () => '', getQmdCollection: () => '' }),
33
- getVault: createGetVaultStub({ store: async () => ({}), capture: async () => ({}) }),
34
- runQmd: async () => {}
35
- });
36
-
37
- const names = listCommandNames(program);
38
- expect(names).toEqual(expect.arrayContaining(['init', 'setup', 'store', 'capture']));
39
- });
40
-
41
- it('registers query commands with profile option', () => {
42
- const program = new Command();
43
- registerQueryCommands(program, {
44
- chalk: chalkStub,
45
- getVault: createGetVaultStub({ find: async () => [], vsearch: async () => [] }),
46
- resolveVaultPath: stubResolveVaultPath,
47
- QmdUnavailableError: class extends Error {},
48
- printQmdMissing: () => {}
49
- });
50
-
51
- const names = listCommandNames(program);
52
- expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'inject', 'observe', 'reflect', 'session-recap']));
53
-
54
- const contextCommand = program.commands.find((command) => command.name() === 'context');
55
- const profileOption = contextCommand?.options.find((option) => option.flags.includes('--profile <profile>'));
56
- expect(profileOption?.description).toContain('auto');
57
- });
58
-
59
- it('registers vault operation commands', () => {
60
- const program = new Command();
61
- registerVaultOperationsCommands(program, {
62
- chalk: chalkStub,
63
- fs,
64
- getVault: createGetVaultStub({
65
- list: async () => [],
66
- get: async () => null,
67
- stats: async () => ({ tags: [], categories: {} }),
68
- sync: async () => ({ copied: [], deleted: [], unchanged: [], errors: [] }),
69
- reindex: async () => 0,
70
- remember: async () => ({ id: '' }),
71
- getQmdCollection: () => ''
72
- }),
73
- runQmd: async () => {},
74
- resolveVaultPath: stubResolveVaultPath,
75
- path
76
- });
77
-
78
- const names = listCommandNames(program);
79
- expect(names).toEqual(expect.arrayContaining([
80
- 'list',
81
- 'get',
82
- 'stats',
83
- 'sync',
84
- 'reindex',
85
- 'remember',
86
- 'shell-init',
87
- 'dashboard'
88
- ]));
89
- });
90
-
91
- it('registers maintenance, resilience, session-lifecycle and template commands', () => {
92
- const program = new Command();
93
- registerMaintenanceCommands(program, { chalk: chalkStub });
94
- registerResilienceCommands(program, {
95
- chalk: chalkStub,
96
- resolveVaultPath: stubResolveVaultPath
97
- });
98
- registerSessionLifecycleCommands(program, {
99
- chalk: chalkStub,
100
- resolveVaultPath: stubResolveVaultPath,
101
- QmdUnavailableError: class extends Error {},
102
- printQmdMissing: () => {},
103
- getVault: createGetVaultStub({
104
- createHandoff: async () => ({ id: '', path: '' }),
105
- getQmdCollection: () => '',
106
- generateRecap: async () => ({}),
107
- formatRecap: () => ''
108
- }),
109
- runQmd: async () => {}
110
- });
111
- registerTemplateCommands(program, { chalk: chalkStub });
112
- registerConfigCommands(program, {
113
- chalk: chalkStub,
114
- resolveVaultPath: stubResolveVaultPath
115
- });
116
- registerRouteCommands(program, {
117
- chalk: chalkStub,
118
- resolveVaultPath: stubResolveVaultPath
119
- });
120
-
121
- const names = listCommandNames(program);
122
- expect(names).toEqual(expect.arrayContaining([
123
- 'doctor',
124
- 'embed',
125
- 'compat',
126
- 'graph',
127
- 'entities',
128
- 'link',
129
- 'rebuild',
130
- 'archive',
131
- 'migrate-observations',
132
- 'replay',
133
- 'sync-bd',
134
- 'checkpoint',
135
- 'recover',
136
- 'status',
137
- 'clean-exit',
138
- 'repair-session',
139
- 'wake',
140
- 'sleep',
141
- 'handoff',
142
- 'recap',
143
- 'template',
144
- 'config',
145
- 'route'
146
- ]));
147
-
148
- const templateCommand = program.commands.find((command) => command.name() === 'template');
149
- const templateSubcommands = templateCommand?.commands.map((command) => command.name()) ?? [];
150
- expect(templateSubcommands).toEqual(expect.arrayContaining(['list', 'create', 'add']));
151
-
152
- const compatCommand = program.commands.find((command) => command.name() === 'compat');
153
- const strictOption = compatCommand?.options.find((option) => option.flags.includes('--strict'));
154
- const baseDirOption = compatCommand?.options.find((option) => option.flags.includes('--base-dir <path>'));
155
- expect(strictOption).toBeTruthy();
156
- expect(baseDirOption).toBeTruthy();
157
- });
158
-
159
- it('keeps top-level command names unique when modules are combined', () => {
160
- const program = registerAllCommandModules(new Command());
161
-
162
- const names = program.commands.map((command) => command.name());
163
- const unique = new Set(names);
164
- expect(unique.size).toBe(names.length);
165
- });
166
- });
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Command } from 'commander';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { registerCoreCommands } from './register-core-commands.js';
6
+ import { registerMaintenanceCommands } from './register-maintenance-commands.js';
7
+ import { registerQueryCommands } from './register-query-commands.js';
8
+ import { registerResilienceCommands } from './register-resilience-commands.js';
9
+ import { registerSessionLifecycleCommands } from './register-session-lifecycle-commands.js';
10
+ import { registerTemplateCommands } from './register-template-commands.js';
11
+ import { registerVaultOperationsCommands } from './register-vault-operations-commands.js';
12
+ import { registerConfigCommands } from './register-config-commands.js';
13
+ import { registerRouteCommands } from './register-route-commands.js';
14
+ import {
15
+ chalkStub,
16
+ createGetVaultStub,
17
+ registerAllCommandModules,
18
+ stubResolveVaultPath
19
+ } from './test-helpers/cli-command-fixtures.js';
20
+
21
+ function listCommandNames(program) {
22
+ return program.commands.map((command) => command.name()).sort((a, b) => a.localeCompare(b));
23
+ }
24
+
25
+ describe('CLI command registration modules', () => {
26
+ it('registers core lifecycle commands', () => {
27
+ const program = new Command();
28
+ registerCoreCommands(program, {
29
+ chalk: chalkStub,
30
+ path,
31
+ fs,
32
+ createVault: async () => ({ getCategories: () => [], getQmdRoot: () => '', getQmdCollection: () => '' }),
33
+ getVault: createGetVaultStub({ store: async () => ({}), capture: async () => ({}) }),
34
+ runQmd: async () => {}
35
+ });
36
+
37
+ const names = listCommandNames(program);
38
+ expect(names).toEqual(expect.arrayContaining(['init', 'setup', 'store', 'capture']));
39
+ });
40
+
41
+ it('registers query commands with profile option', () => {
42
+ const program = new Command();
43
+ registerQueryCommands(program, {
44
+ chalk: chalkStub,
45
+ getVault: createGetVaultStub({ find: async () => [], vsearch: async () => [] }),
46
+ resolveVaultPath: stubResolveVaultPath,
47
+ QmdUnavailableError: class extends Error {},
48
+ printQmdMissing: () => {}
49
+ });
50
+
51
+ const names = listCommandNames(program);
52
+ expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'inject', 'observe', 'reflect', 'session-recap']));
53
+
54
+ const contextCommand = program.commands.find((command) => command.name() === 'context');
55
+ const profileOption = contextCommand?.options.find((option) => option.flags.includes('--profile <profile>'));
56
+ expect(profileOption?.description).toContain('auto');
57
+ });
58
+
59
+ it('registers vault operation commands', () => {
60
+ const program = new Command();
61
+ registerVaultOperationsCommands(program, {
62
+ chalk: chalkStub,
63
+ fs,
64
+ getVault: createGetVaultStub({
65
+ list: async () => [],
66
+ get: async () => null,
67
+ stats: async () => ({ tags: [], categories: {} }),
68
+ sync: async () => ({ copied: [], deleted: [], unchanged: [], errors: [] }),
69
+ reindex: async () => 0,
70
+ remember: async () => ({ id: '' }),
71
+ getQmdCollection: () => ''
72
+ }),
73
+ runQmd: async () => {},
74
+ resolveVaultPath: stubResolveVaultPath,
75
+ path
76
+ });
77
+
78
+ const names = listCommandNames(program);
79
+ expect(names).toEqual(expect.arrayContaining([
80
+ 'list',
81
+ 'get',
82
+ 'stats',
83
+ 'sync',
84
+ 'reindex',
85
+ 'remember',
86
+ 'shell-init',
87
+ 'dashboard'
88
+ ]));
89
+ });
90
+
91
+ it('registers maintenance, resilience, session-lifecycle and template commands', () => {
92
+ const program = new Command();
93
+ registerMaintenanceCommands(program, { chalk: chalkStub });
94
+ registerResilienceCommands(program, {
95
+ chalk: chalkStub,
96
+ resolveVaultPath: stubResolveVaultPath
97
+ });
98
+ registerSessionLifecycleCommands(program, {
99
+ chalk: chalkStub,
100
+ resolveVaultPath: stubResolveVaultPath,
101
+ QmdUnavailableError: class extends Error {},
102
+ printQmdMissing: () => {},
103
+ getVault: createGetVaultStub({
104
+ createHandoff: async () => ({ id: '', path: '' }),
105
+ getQmdCollection: () => '',
106
+ generateRecap: async () => ({}),
107
+ formatRecap: () => ''
108
+ }),
109
+ runQmd: async () => {}
110
+ });
111
+ registerTemplateCommands(program, { chalk: chalkStub });
112
+ registerConfigCommands(program, {
113
+ chalk: chalkStub,
114
+ resolveVaultPath: stubResolveVaultPath
115
+ });
116
+ registerRouteCommands(program, {
117
+ chalk: chalkStub,
118
+ resolveVaultPath: stubResolveVaultPath
119
+ });
120
+
121
+ const names = listCommandNames(program);
122
+ expect(names).toEqual(expect.arrayContaining([
123
+ 'doctor',
124
+ 'embed',
125
+ 'compat',
126
+ 'graph',
127
+ 'entities',
128
+ 'link',
129
+ 'rebuild',
130
+ 'archive',
131
+ 'migrate-observations',
132
+ 'replay',
133
+ 'sync-bd',
134
+ 'checkpoint',
135
+ 'recover',
136
+ 'status',
137
+ 'clean-exit',
138
+ 'repair-session',
139
+ 'wake',
140
+ 'sleep',
141
+ 'handoff',
142
+ 'recap',
143
+ 'template',
144
+ 'config',
145
+ 'route'
146
+ ]));
147
+
148
+ const templateCommand = program.commands.find((command) => command.name() === 'template');
149
+ const templateSubcommands = templateCommand?.commands.map((command) => command.name()) ?? [];
150
+ expect(templateSubcommands).toEqual(expect.arrayContaining(['list', 'create', 'add']));
151
+
152
+ const compatCommand = program.commands.find((command) => command.name() === 'compat');
153
+ const strictOption = compatCommand?.options.find((option) => option.flags.includes('--strict'));
154
+ const baseDirOption = compatCommand?.options.find((option) => option.flags.includes('--base-dir <path>'));
155
+ expect(strictOption).toBeTruthy();
156
+ expect(baseDirOption).toBeTruthy();
157
+ });
158
+
159
+ it('keeps top-level command names unique when modules are combined', () => {
160
+ const program = registerAllCommandModules(new Command());
161
+
162
+ const names = program.commands.map((command) => command.name());
163
+ const unique = new Set(names);
164
+ expect(unique.size).toBe(names.length);
165
+ });
166
+ });
@@ -1,93 +1,93 @@
1
- import { spawn } from 'child_process';
2
- import chalk from 'chalk';
3
- import path from 'path';
4
- import {
5
- ClawVault,
6
- QmdUnavailableError,
7
- QMD_INSTALL_COMMAND,
8
- resolveVaultPath as resolveConfiguredVaultPath
9
- } from '../dist/index.js';
10
-
11
- const QMD_INDEX_ENV_VAR = 'CLAWVAULT_QMD_INDEX';
12
-
13
- /**
14
- * Validates that a path is within an allowed base directory.
15
- * Prevents path traversal attacks.
16
- * @param {string} inputPath - The path to validate
17
- * @param {string} basePath - The allowed base directory
18
- * @returns {string} The resolved, validated path
19
- * @throws {Error} If the path escapes the base directory
20
- */
21
- export function validatePathWithinBase(inputPath, basePath) {
22
- const resolvedBase = path.resolve(basePath);
23
- const resolvedPath = path.resolve(basePath, inputPath);
24
-
25
- if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
26
- throw new Error(`Path traversal detected: ${inputPath} escapes ${basePath}`);
27
- }
28
-
29
- return resolvedPath;
30
- }
31
-
32
- /**
33
- * Sanitizes an argument that may contain a path to prevent injection.
34
- * @param {unknown} arg - The argument to sanitize
35
- * @returns {string} The sanitized argument
36
- */
37
- export function sanitizeQmdArg(arg) {
38
- const normalizedArg = String(arg);
39
- // Reject arguments with null bytes (injection attempt)
40
- if (normalizedArg.includes('\0')) {
41
- throw new Error('Invalid argument: contains null byte');
42
- }
43
- return normalizedArg;
44
- }
45
-
46
- function withQmdIndex(args) {
47
- if (args.includes('--index')) {
48
- return [...args];
49
- }
50
-
51
- const indexName = process.env[QMD_INDEX_ENV_VAR]?.trim();
52
- if (!indexName) {
53
- return [...args];
54
- }
55
-
56
- return ['--index', indexName, ...args];
57
- }
58
-
59
- export function resolveVaultPath(vaultPath) {
60
- return resolveConfiguredVaultPath({ explicitPath: vaultPath });
61
- }
62
-
63
- export async function getVault(vaultPath) {
64
- const vault = new ClawVault(resolveVaultPath(vaultPath));
65
- await vault.load();
66
- return vault;
67
- }
68
-
69
- export async function runQmd(args) {
70
- return new Promise((resolve, reject) => {
71
- // Sanitize all arguments before passing to spawn
72
- const sanitizedArgs = withQmdIndex(args).map(sanitizeQmdArg);
73
- const proc = spawn('qmd', sanitizedArgs, { stdio: 'inherit' });
74
- proc.on('close', (code) => {
75
- if (code === 0) resolve();
76
- else reject(new Error(`qmd exited with code ${code}`));
77
- });
78
- proc.on('error', (err) => {
79
- if (err?.code === 'ENOENT') {
80
- reject(new QmdUnavailableError());
81
- } else {
82
- reject(err);
83
- }
84
- });
85
- });
86
- }
87
-
88
- export function printQmdMissing() {
89
- console.error(chalk.red('Error: ClawVault requires qmd.'));
90
- console.log(chalk.dim(`Install: ${QMD_INSTALL_COMMAND}`));
91
- }
92
-
93
- export { QmdUnavailableError };
1
+ import { spawn } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import path from 'path';
4
+ import {
5
+ ClawVault,
6
+ QmdUnavailableError,
7
+ QMD_INSTALL_COMMAND,
8
+ resolveVaultPath as resolveConfiguredVaultPath
9
+ } from '../dist/index.js';
10
+
11
+ const QMD_INDEX_ENV_VAR = 'CLAWVAULT_QMD_INDEX';
12
+
13
+ /**
14
+ * Validates that a path is within an allowed base directory.
15
+ * Prevents path traversal attacks.
16
+ * @param {string} inputPath - The path to validate
17
+ * @param {string} basePath - The allowed base directory
18
+ * @returns {string} The resolved, validated path
19
+ * @throws {Error} If the path escapes the base directory
20
+ */
21
+ export function validatePathWithinBase(inputPath, basePath) {
22
+ const resolvedBase = path.resolve(basePath);
23
+ const resolvedPath = path.resolve(basePath, inputPath);
24
+
25
+ if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
26
+ throw new Error(`Path traversal detected: ${inputPath} escapes ${basePath}`);
27
+ }
28
+
29
+ return resolvedPath;
30
+ }
31
+
32
+ /**
33
+ * Sanitizes an argument that may contain a path to prevent injection.
34
+ * @param {unknown} arg - The argument to sanitize
35
+ * @returns {string} The sanitized argument
36
+ */
37
+ export function sanitizeQmdArg(arg) {
38
+ const normalizedArg = String(arg);
39
+ // Reject arguments with null bytes (injection attempt)
40
+ if (normalizedArg.includes('\0')) {
41
+ throw new Error('Invalid argument: contains null byte');
42
+ }
43
+ return normalizedArg;
44
+ }
45
+
46
+ function withQmdIndex(args) {
47
+ if (args.includes('--index')) {
48
+ return [...args];
49
+ }
50
+
51
+ const indexName = process.env[QMD_INDEX_ENV_VAR]?.trim();
52
+ if (!indexName) {
53
+ return [...args];
54
+ }
55
+
56
+ return ['--index', indexName, ...args];
57
+ }
58
+
59
+ export function resolveVaultPath(vaultPath) {
60
+ return resolveConfiguredVaultPath({ explicitPath: vaultPath });
61
+ }
62
+
63
+ export async function getVault(vaultPath) {
64
+ const vault = new ClawVault(resolveVaultPath(vaultPath));
65
+ await vault.load();
66
+ return vault;
67
+ }
68
+
69
+ export async function runQmd(args) {
70
+ return new Promise((resolve, reject) => {
71
+ // Sanitize all arguments before passing to spawn
72
+ const sanitizedArgs = withQmdIndex(args).map(sanitizeQmdArg);
73
+ const proc = spawn('qmd', sanitizedArgs, { stdio: 'inherit' });
74
+ proc.on('close', (code) => {
75
+ if (code === 0) resolve();
76
+ else reject(new Error(`qmd exited with code ${code}`));
77
+ });
78
+ proc.on('error', (err) => {
79
+ if (err?.code === 'ENOENT') {
80
+ reject(new QmdUnavailableError());
81
+ } else {
82
+ reject(err);
83
+ }
84
+ });
85
+ });
86
+ }
87
+
88
+ export function printQmdMissing() {
89
+ console.error(chalk.red('Error: ClawVault requires qmd.'));
90
+ console.log(chalk.dim(`Install: ${QMD_INSTALL_COMMAND}`));
91
+ }
92
+
93
+ export { QmdUnavailableError };