clawvault 1.11.2 → 2.0.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 +135 -1
- package/bin/clawvault.js +51 -1252
- package/bin/command-registration.test.js +148 -0
- package/bin/command-runtime.js +42 -0
- package/bin/command-runtime.test.js +102 -0
- package/bin/help-contract.test.js +23 -0
- package/bin/register-core-commands.js +139 -0
- package/bin/register-maintenance-commands.js +137 -0
- package/bin/register-query-commands.js +225 -0
- package/bin/register-resilience-commands.js +147 -0
- package/bin/register-session-lifecycle-commands.js +204 -0
- package/bin/register-template-commands.js +72 -0
- package/bin/register-vault-operations-commands.js +295 -0
- package/bin/test-helpers/cli-command-fixtures.js +94 -0
- package/dashboard/lib/graph-diff.js +3 -1
- package/dashboard/lib/graph-diff.test.js +19 -0
- package/dashboard/lib/vault-parser.js +330 -26
- package/dashboard/lib/vault-parser.test.js +191 -11
- package/dashboard/public/app.js +22 -9
- package/dist/chunk-MXSSG3QU.js +42 -0
- package/dist/chunk-O5V7SD5C.js +398 -0
- package/dist/chunk-PAYUH64O.js +284 -0
- package/dist/{chunk-3HFB7EMU.js → chunk-QFBKWDYR.js} +12 -0
- package/dist/{chunk-UBRYOIII.js → chunk-TBVI4N53.js} +210 -21
- package/dist/chunk-TXO34J3O.js +56 -0
- package/dist/commands/compat.d.ts +28 -0
- package/dist/commands/compat.js +10 -0
- package/dist/commands/context.d.ts +2 -33
- package/dist/commands/context.js +3 -2
- package/dist/commands/doctor.js +61 -3
- package/dist/commands/entities.d.ts +1 -0
- package/dist/commands/entities.js +4 -4
- package/dist/commands/graph.d.ts +21 -0
- package/dist/commands/graph.js +10 -0
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.js +14 -5
- package/dist/commands/sleep.js +7 -6
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +63 -3
- package/dist/commands/wake.js +5 -4
- package/dist/context-COo8oq1k.d.ts +45 -0
- package/dist/index.d.ts +63 -2
- package/dist/index.js +53 -15
- package/dist/lib/config.d.ts +6 -1
- package/dist/lib/config.js +7 -3
- package/hooks/clawvault/HOOK.md +6 -1
- package/hooks/clawvault/handler.js +44 -3
- package/hooks/clawvault/handler.test.js +161 -0
- package/package.json +34 -2
- package/dashboard/public/graph.js +0 -376
- package/dashboard/public/style.css +0 -154
- package/dist/chunk-4KDZZW4X.js +0 -13
|
@@ -0,0 +1,148 @@
|
|
|
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 {
|
|
13
|
+
chalkStub,
|
|
14
|
+
createGetVaultStub,
|
|
15
|
+
registerAllCommandModules,
|
|
16
|
+
stubResolveVaultPath
|
|
17
|
+
} from './test-helpers/cli-command-fixtures.js';
|
|
18
|
+
|
|
19
|
+
function listCommandNames(program) {
|
|
20
|
+
return program.commands.map((command) => command.name()).sort((a, b) => a.localeCompare(b));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('CLI command registration modules', () => {
|
|
24
|
+
it('registers core lifecycle commands', () => {
|
|
25
|
+
const program = new Command();
|
|
26
|
+
registerCoreCommands(program, {
|
|
27
|
+
chalk: chalkStub,
|
|
28
|
+
path,
|
|
29
|
+
fs,
|
|
30
|
+
createVault: async () => ({ getCategories: () => [], getQmdRoot: () => '', getQmdCollection: () => '' }),
|
|
31
|
+
getVault: createGetVaultStub({ store: async () => ({}), capture: async () => ({}) }),
|
|
32
|
+
runQmd: async () => {}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const names = listCommandNames(program);
|
|
36
|
+
expect(names).toEqual(expect.arrayContaining(['init', 'setup', 'store', 'capture']));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('registers query commands with profile option', () => {
|
|
40
|
+
const program = new Command();
|
|
41
|
+
registerQueryCommands(program, {
|
|
42
|
+
chalk: chalkStub,
|
|
43
|
+
getVault: createGetVaultStub({ find: async () => [], vsearch: async () => [] }),
|
|
44
|
+
resolveVaultPath: stubResolveVaultPath,
|
|
45
|
+
QmdUnavailableError: class extends Error {},
|
|
46
|
+
printQmdMissing: () => {}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const names = listCommandNames(program);
|
|
50
|
+
expect(names).toEqual(expect.arrayContaining(['search', 'vsearch', 'context', 'observe', 'session-recap']));
|
|
51
|
+
|
|
52
|
+
const contextCommand = program.commands.find((command) => command.name() === 'context');
|
|
53
|
+
const profileOption = contextCommand?.options.find((option) => option.flags.includes('--profile <profile>'));
|
|
54
|
+
expect(profileOption?.description).toContain('auto');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('registers vault operation commands', () => {
|
|
58
|
+
const program = new Command();
|
|
59
|
+
registerVaultOperationsCommands(program, {
|
|
60
|
+
chalk: chalkStub,
|
|
61
|
+
fs,
|
|
62
|
+
getVault: createGetVaultStub({
|
|
63
|
+
list: async () => [],
|
|
64
|
+
get: async () => null,
|
|
65
|
+
stats: async () => ({ tags: [], categories: {} }),
|
|
66
|
+
sync: async () => ({ copied: [], deleted: [], unchanged: [], errors: [] }),
|
|
67
|
+
reindex: async () => 0,
|
|
68
|
+
remember: async () => ({ id: '' }),
|
|
69
|
+
getQmdCollection: () => ''
|
|
70
|
+
}),
|
|
71
|
+
runQmd: async () => {},
|
|
72
|
+
resolveVaultPath: stubResolveVaultPath,
|
|
73
|
+
path
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const names = listCommandNames(program);
|
|
77
|
+
expect(names).toEqual(expect.arrayContaining([
|
|
78
|
+
'list',
|
|
79
|
+
'get',
|
|
80
|
+
'stats',
|
|
81
|
+
'sync',
|
|
82
|
+
'reindex',
|
|
83
|
+
'remember',
|
|
84
|
+
'shell-init',
|
|
85
|
+
'dashboard'
|
|
86
|
+
]));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('registers maintenance, resilience, session-lifecycle and template commands', () => {
|
|
90
|
+
const program = new Command();
|
|
91
|
+
registerMaintenanceCommands(program, { chalk: chalkStub });
|
|
92
|
+
registerResilienceCommands(program, {
|
|
93
|
+
chalk: chalkStub,
|
|
94
|
+
resolveVaultPath: stubResolveVaultPath
|
|
95
|
+
});
|
|
96
|
+
registerSessionLifecycleCommands(program, {
|
|
97
|
+
chalk: chalkStub,
|
|
98
|
+
resolveVaultPath: stubResolveVaultPath,
|
|
99
|
+
QmdUnavailableError: class extends Error {},
|
|
100
|
+
printQmdMissing: () => {},
|
|
101
|
+
getVault: createGetVaultStub({
|
|
102
|
+
createHandoff: async () => ({ id: '', path: '' }),
|
|
103
|
+
getQmdCollection: () => '',
|
|
104
|
+
generateRecap: async () => ({}),
|
|
105
|
+
formatRecap: () => ''
|
|
106
|
+
}),
|
|
107
|
+
runQmd: async () => {}
|
|
108
|
+
});
|
|
109
|
+
registerTemplateCommands(program, { chalk: chalkStub });
|
|
110
|
+
|
|
111
|
+
const names = listCommandNames(program);
|
|
112
|
+
expect(names).toEqual(expect.arrayContaining([
|
|
113
|
+
'doctor',
|
|
114
|
+
'compat',
|
|
115
|
+
'graph',
|
|
116
|
+
'entities',
|
|
117
|
+
'link',
|
|
118
|
+
'checkpoint',
|
|
119
|
+
'recover',
|
|
120
|
+
'status',
|
|
121
|
+
'clean-exit',
|
|
122
|
+
'repair-session',
|
|
123
|
+
'wake',
|
|
124
|
+
'sleep',
|
|
125
|
+
'handoff',
|
|
126
|
+
'recap',
|
|
127
|
+
'template'
|
|
128
|
+
]));
|
|
129
|
+
|
|
130
|
+
const templateCommand = program.commands.find((command) => command.name() === 'template');
|
|
131
|
+
const templateSubcommands = templateCommand?.commands.map((command) => command.name()) ?? [];
|
|
132
|
+
expect(templateSubcommands).toEqual(expect.arrayContaining(['list', 'create', 'add']));
|
|
133
|
+
|
|
134
|
+
const compatCommand = program.commands.find((command) => command.name() === 'compat');
|
|
135
|
+
const strictOption = compatCommand?.options.find((option) => option.flags.includes('--strict'));
|
|
136
|
+
const baseDirOption = compatCommand?.options.find((option) => option.flags.includes('--base-dir <path>'));
|
|
137
|
+
expect(strictOption).toBeTruthy();
|
|
138
|
+
expect(baseDirOption).toBeTruthy();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('keeps top-level command names unique when modules are combined', () => {
|
|
142
|
+
const program = registerAllCommandModules(new Command());
|
|
143
|
+
|
|
144
|
+
const names = program.commands.map((command) => command.name());
|
|
145
|
+
const unique = new Set(names);
|
|
146
|
+
expect(unique.size).toBe(names.length);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import {
|
|
4
|
+
ClawVault,
|
|
5
|
+
QmdUnavailableError,
|
|
6
|
+
QMD_INSTALL_COMMAND,
|
|
7
|
+
resolveVaultPath as resolveConfiguredVaultPath
|
|
8
|
+
} from '../dist/index.js';
|
|
9
|
+
|
|
10
|
+
export function resolveVaultPath(vaultPath) {
|
|
11
|
+
return resolveConfiguredVaultPath({ explicitPath: vaultPath });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function getVault(vaultPath) {
|
|
15
|
+
const vault = new ClawVault(resolveVaultPath(vaultPath));
|
|
16
|
+
await vault.load();
|
|
17
|
+
return vault;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function runQmd(args) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const proc = spawn('qmd', args, { stdio: 'inherit' });
|
|
23
|
+
proc.on('close', (code) => {
|
|
24
|
+
if (code === 0) resolve();
|
|
25
|
+
else reject(new Error(`qmd exited with code ${code}`));
|
|
26
|
+
});
|
|
27
|
+
proc.on('error', (err) => {
|
|
28
|
+
if (err?.code === 'ENOENT') {
|
|
29
|
+
reject(new QmdUnavailableError());
|
|
30
|
+
} else {
|
|
31
|
+
reject(err);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function printQmdMissing() {
|
|
38
|
+
console.error(chalk.red('Error: ClawVault requires qmd.'));
|
|
39
|
+
console.log(chalk.dim(`Install: ${QMD_INSTALL_COMMAND}`));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { QmdUnavailableError };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
spawnMock,
|
|
5
|
+
resolveConfiguredVaultPathMock,
|
|
6
|
+
clawvaultCtorMock,
|
|
7
|
+
loadMock
|
|
8
|
+
} = vi.hoisted(() => ({
|
|
9
|
+
spawnMock: vi.fn(),
|
|
10
|
+
resolveConfiguredVaultPathMock: vi.fn(),
|
|
11
|
+
clawvaultCtorMock: vi.fn(),
|
|
12
|
+
loadMock: vi.fn()
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('child_process', () => ({
|
|
16
|
+
spawn: spawnMock
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('../dist/index.js', () => ({
|
|
20
|
+
ClawVault: clawvaultCtorMock,
|
|
21
|
+
resolveVaultPath: resolveConfiguredVaultPathMock,
|
|
22
|
+
QmdUnavailableError: class QmdUnavailableError extends Error {},
|
|
23
|
+
QMD_INSTALL_COMMAND: 'install-qmd'
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
async function loadRuntimeModule() {
|
|
27
|
+
vi.resetModules();
|
|
28
|
+
return await import('./command-runtime.js');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
clawvaultCtorMock.mockImplementation(() => ({
|
|
34
|
+
load: loadMock
|
|
35
|
+
}));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('command runtime helpers', () => {
|
|
39
|
+
it('delegates vault path resolution and loads vaults', async () => {
|
|
40
|
+
resolveConfiguredVaultPathMock.mockReturnValue('/resolved/vault');
|
|
41
|
+
loadMock.mockResolvedValue(undefined);
|
|
42
|
+
const { getVault, resolveVaultPath } = await loadRuntimeModule();
|
|
43
|
+
|
|
44
|
+
const resolved = resolveVaultPath('/explicit');
|
|
45
|
+
expect(resolveConfiguredVaultPathMock).toHaveBeenCalledWith({ explicitPath: '/explicit' });
|
|
46
|
+
expect(resolved).toBe('/resolved/vault');
|
|
47
|
+
|
|
48
|
+
await getVault('/explicit');
|
|
49
|
+
expect(clawvaultCtorMock).toHaveBeenCalledWith('/resolved/vault');
|
|
50
|
+
expect(loadMock).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('maps qmd ENOENT failures to QmdUnavailableError', async () => {
|
|
54
|
+
const { runQmd, QmdUnavailableError } = await loadRuntimeModule();
|
|
55
|
+
spawnMock.mockImplementation(() => {
|
|
56
|
+
const handlers = {};
|
|
57
|
+
const proc = {
|
|
58
|
+
on: (event, handler) => {
|
|
59
|
+
handlers[event] = handler;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
queueMicrotask(() => {
|
|
63
|
+
handlers.error?.({ code: 'ENOENT' });
|
|
64
|
+
});
|
|
65
|
+
return proc;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await expect(runQmd(['update'])).rejects.toBeInstanceOf(QmdUnavailableError);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('surfaces qmd non-zero exit codes as errors', async () => {
|
|
72
|
+
const { runQmd } = await loadRuntimeModule();
|
|
73
|
+
spawnMock.mockImplementation(() => {
|
|
74
|
+
const handlers = {};
|
|
75
|
+
const proc = {
|
|
76
|
+
on: (event, handler) => {
|
|
77
|
+
handlers[event] = handler;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
queueMicrotask(() => {
|
|
81
|
+
handlers.close?.(2);
|
|
82
|
+
});
|
|
83
|
+
return proc;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await expect(runQmd(['update'])).rejects.toThrow('qmd exited with code 2');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('prints consistent qmd missing guidance', async () => {
|
|
90
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
91
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
92
|
+
try {
|
|
93
|
+
const { printQmdMissing } = await loadRuntimeModule();
|
|
94
|
+
printQmdMissing();
|
|
95
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('ClawVault requires qmd.'));
|
|
96
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('install-qmd'));
|
|
97
|
+
} finally {
|
|
98
|
+
errorSpy.mockRestore();
|
|
99
|
+
logSpy.mockRestore();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { registerAllCommandModules } from './test-helpers/cli-command-fixtures.js';
|
|
3
|
+
|
|
4
|
+
describe('CLI help contract', () => {
|
|
5
|
+
it('includes expected high-level command surface', () => {
|
|
6
|
+
const help = registerAllCommandModules().helpInformation();
|
|
7
|
+
expect(help).toContain('init');
|
|
8
|
+
expect(help).toContain('context');
|
|
9
|
+
expect(help).toContain('compat');
|
|
10
|
+
expect(help).toContain('graph');
|
|
11
|
+
expect(help).toContain('repair-session');
|
|
12
|
+
expect(help).toContain('template');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('documents context auto profile and compat strict options', () => {
|
|
16
|
+
const program = registerAllCommandModules();
|
|
17
|
+
const contextHelp = program.commands.find((command) => command.name() === 'context')?.helpInformation() ?? '';
|
|
18
|
+
const compatHelp = program.commands.find((command) => command.name() === 'compat')?.helpInformation() ?? '';
|
|
19
|
+
expect(contextHelp).toContain('--profile <profile>');
|
|
20
|
+
expect(contextHelp).toContain('auto');
|
|
21
|
+
expect(compatHelp).toContain('--strict');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core vault lifecycle and write command registrations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function registerCoreCommands(
|
|
6
|
+
program,
|
|
7
|
+
{ chalk, path, fs, createVault, getVault, runQmd }
|
|
8
|
+
) {
|
|
9
|
+
// === INIT ===
|
|
10
|
+
program
|
|
11
|
+
.command('init [path]')
|
|
12
|
+
.description('Initialize a new ClawVault')
|
|
13
|
+
.option('-n, --name <name>', 'Vault name')
|
|
14
|
+
.option('--qmd', 'Set up qmd semantic search collection')
|
|
15
|
+
.option('--qmd-collection <name>', 'qmd collection name (defaults to vault name)')
|
|
16
|
+
.action(async (vaultPath, options) => {
|
|
17
|
+
const targetPath = vaultPath || '.';
|
|
18
|
+
console.log(chalk.cyan(`\n🐘 Initializing ClawVault at ${path.resolve(targetPath)}...\n`));
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const vault = await createVault(targetPath, {
|
|
22
|
+
name: options.name || path.basename(path.resolve(targetPath)),
|
|
23
|
+
qmdCollection: options.qmdCollection
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log(chalk.green('✓ Vault created'));
|
|
27
|
+
console.log(chalk.dim(` Categories: ${vault.getCategories().join(', ')}`));
|
|
28
|
+
|
|
29
|
+
console.log(chalk.cyan('\nSetting up qmd collection...'));
|
|
30
|
+
try {
|
|
31
|
+
await runQmd([
|
|
32
|
+
'collection',
|
|
33
|
+
'add',
|
|
34
|
+
vault.getQmdRoot(),
|
|
35
|
+
'--name',
|
|
36
|
+
vault.getQmdCollection(),
|
|
37
|
+
'--mask',
|
|
38
|
+
'**/*.md'
|
|
39
|
+
]);
|
|
40
|
+
console.log(chalk.green('✓ qmd collection created'));
|
|
41
|
+
} catch {
|
|
42
|
+
console.log(chalk.yellow('⚠ qmd collection may already exist'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(chalk.green('\n✅ ClawVault ready!\n'));
|
|
46
|
+
console.log(chalk.dim('Next steps:'));
|
|
47
|
+
console.log(chalk.dim(' clawvault store --category inbox --title "My note" --content "Hello world"'));
|
|
48
|
+
console.log(chalk.dim(' clawvault search "hello"'));
|
|
49
|
+
console.log();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// === SETUP ===
|
|
57
|
+
program
|
|
58
|
+
.command('setup')
|
|
59
|
+
.description('Auto-discover and configure a ClawVault')
|
|
60
|
+
.action(async () => {
|
|
61
|
+
try {
|
|
62
|
+
const { setupCommand } = await import('../dist/commands/setup.js');
|
|
63
|
+
await setupCommand();
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// === STORE ===
|
|
71
|
+
program
|
|
72
|
+
.command('store')
|
|
73
|
+
.description('Store a new memory')
|
|
74
|
+
.requiredOption('-c, --category <category>', 'Category (preferences, decisions, patterns, people, projects, goals, transcripts, inbox)')
|
|
75
|
+
.requiredOption('-t, --title <title>', 'Document title')
|
|
76
|
+
.option('--content <content>', 'Content body')
|
|
77
|
+
.option('-f, --file <file>', 'Read content from file')
|
|
78
|
+
.option('--stdin', 'Read content from stdin')
|
|
79
|
+
.option('--overwrite', 'Overwrite if exists')
|
|
80
|
+
.option('--no-index', 'Skip qmd index update (auto-updates by default)')
|
|
81
|
+
.option('--embed', 'Also update qmd embeddings for vector search')
|
|
82
|
+
.option('-v, --vault <path>', 'Vault path (default: find nearest)')
|
|
83
|
+
.action(async (options) => {
|
|
84
|
+
try {
|
|
85
|
+
const vault = await getVault(options.vault);
|
|
86
|
+
let content = options.content || '';
|
|
87
|
+
|
|
88
|
+
if (options.file) {
|
|
89
|
+
content = fs.readFileSync(options.file, 'utf-8');
|
|
90
|
+
} else if (options.stdin) {
|
|
91
|
+
content = fs.readFileSync(0, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const doc = await vault.store({
|
|
95
|
+
category: options.category,
|
|
96
|
+
title: options.title,
|
|
97
|
+
content,
|
|
98
|
+
overwrite: options.overwrite
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
console.log(chalk.green(`✓ Stored: ${doc.id}`));
|
|
102
|
+
console.log(chalk.dim(` Path: ${doc.path}`));
|
|
103
|
+
|
|
104
|
+
if (options.index !== false) {
|
|
105
|
+
const collection = vault.getQmdCollection();
|
|
106
|
+
await runQmd(collection ? ['update', '-c', collection] : ['update']);
|
|
107
|
+
if (options.embed) {
|
|
108
|
+
await runQmd(collection ? ['embed', '-c', collection] : ['embed']);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// === CAPTURE ===
|
|
118
|
+
program
|
|
119
|
+
.command('capture <note>')
|
|
120
|
+
.description('Quick capture to inbox')
|
|
121
|
+
.option('-t, --title <title>', 'Note title')
|
|
122
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
123
|
+
.option('--no-index', 'Skip qmd index update')
|
|
124
|
+
.action(async (note, options) => {
|
|
125
|
+
try {
|
|
126
|
+
const vault = await getVault(options.vault);
|
|
127
|
+
const doc = await vault.capture(note, options.title);
|
|
128
|
+
console.log(chalk.green(`✓ Captured: ${doc.id}`));
|
|
129
|
+
|
|
130
|
+
if (options.index !== false) {
|
|
131
|
+
const collection = vault.getQmdCollection();
|
|
132
|
+
await runQmd(collection ? ['update', '-c', collection] : ['update']);
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maintenance and graph-oriented command registrations.
|
|
3
|
+
* Split from the main CLI entrypoint to keep bin/clawvault.js maintainable.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function registerMaintenanceCommands(program, { chalk }) {
|
|
7
|
+
// === DOCTOR (health check) ===
|
|
8
|
+
program
|
|
9
|
+
.command('doctor')
|
|
10
|
+
.description('Check ClawVault setup health')
|
|
11
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
try {
|
|
14
|
+
const { doctor } = await import('../dist/commands/doctor.js');
|
|
15
|
+
const report = await doctor(options.vault);
|
|
16
|
+
|
|
17
|
+
console.log(chalk.cyan('\n🩺 ClawVault Health Check\n'));
|
|
18
|
+
if (report.vaultPath) {
|
|
19
|
+
console.log(chalk.dim(`Vault: ${report.vaultPath}`));
|
|
20
|
+
console.log();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const check of report.checks) {
|
|
24
|
+
const prefix = check.status === 'ok'
|
|
25
|
+
? chalk.green('✓')
|
|
26
|
+
: check.status === 'warn'
|
|
27
|
+
? chalk.yellow('⚠')
|
|
28
|
+
: chalk.red('✗');
|
|
29
|
+
const detail = check.detail ? ` — ${check.detail}` : '';
|
|
30
|
+
console.log(`${prefix} ${check.label}${detail}`);
|
|
31
|
+
if (check.hint) {
|
|
32
|
+
console.log(chalk.dim(` ${check.hint}`));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const issues = report.warnings + report.errors;
|
|
37
|
+
console.log();
|
|
38
|
+
if (issues === 0) {
|
|
39
|
+
console.log(chalk.green('✅ ClawVault is healthy!\n'));
|
|
40
|
+
} else {
|
|
41
|
+
console.log(chalk.yellow(`⚠ ${issues} issue(s) found\n`));
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// === COMPAT (OpenClaw compatibility) ===
|
|
50
|
+
program
|
|
51
|
+
.command('compat')
|
|
52
|
+
.description('Check OpenClaw compatibility status')
|
|
53
|
+
.option('--strict', 'Exit non-zero when warnings are present')
|
|
54
|
+
.option('--base-dir <path>', 'Validate compatibility against alternate project root')
|
|
55
|
+
.option('--json', 'Output as JSON')
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
try {
|
|
58
|
+
const { compatCommand, compatibilityExitCode } = await import('../dist/commands/compat.js');
|
|
59
|
+
const report = await compatCommand({
|
|
60
|
+
json: options.json,
|
|
61
|
+
strict: options.strict,
|
|
62
|
+
baseDir: options.baseDir
|
|
63
|
+
});
|
|
64
|
+
const exitCode = compatibilityExitCode(report, { strict: options.strict });
|
|
65
|
+
if (exitCode !== 0) {
|
|
66
|
+
process.exitCode = exitCode;
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// === GRAPH ===
|
|
75
|
+
program
|
|
76
|
+
.command('graph')
|
|
77
|
+
.description('Show typed memory graph summary')
|
|
78
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
79
|
+
.option('--refresh', 'Rebuild graph index before showing summary')
|
|
80
|
+
.option('--json', 'Output as JSON')
|
|
81
|
+
.action(async (options) => {
|
|
82
|
+
try {
|
|
83
|
+
const { graphCommand } = await import('../dist/commands/graph.js');
|
|
84
|
+
await graphCommand({
|
|
85
|
+
vaultPath: options.vault,
|
|
86
|
+
refresh: options.refresh,
|
|
87
|
+
json: options.json
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// === ENTITIES ===
|
|
96
|
+
program
|
|
97
|
+
.command('entities')
|
|
98
|
+
.description('List all linkable entities in the vault')
|
|
99
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
100
|
+
.option('--json', 'Output as JSON')
|
|
101
|
+
.action(async (options) => {
|
|
102
|
+
try {
|
|
103
|
+
const { entitiesCommand } = await import('../dist/commands/entities.js');
|
|
104
|
+
await entitiesCommand({ json: options.json, vaultPath: options.vault });
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// === LINK ===
|
|
112
|
+
program
|
|
113
|
+
.command('link [file]')
|
|
114
|
+
.description('Auto-link entity mentions in markdown files')
|
|
115
|
+
.option('--all', 'Link all files in vault')
|
|
116
|
+
.option('--backlinks <file>', 'Show backlinks to a file')
|
|
117
|
+
.option('--dry-run', 'Show what would be linked without changing files')
|
|
118
|
+
.option('--orphans', 'List broken wiki-links')
|
|
119
|
+
.option('--rebuild', 'Rebuild backlinks index')
|
|
120
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
121
|
+
.action(async (file, options) => {
|
|
122
|
+
try {
|
|
123
|
+
const { linkCommand } = await import('../dist/commands/link.js');
|
|
124
|
+
await linkCommand(file, {
|
|
125
|
+
all: options.all,
|
|
126
|
+
dryRun: options.dryRun,
|
|
127
|
+
backlinks: options.backlinks,
|
|
128
|
+
orphans: options.orphans,
|
|
129
|
+
rebuild: options.rebuild,
|
|
130
|
+
vaultPath: options.vault
|
|
131
|
+
});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|