clawvault 2.5.1 → 2.5.3
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 +159 -199
- package/bin/clawvault.js +111 -111
- package/bin/command-registration.test.js +166 -165
- package/bin/command-runtime.js +93 -77
- package/bin/command-runtime.test.js +154 -102
- package/bin/help-contract.test.js +39 -28
- package/bin/register-config-commands.js +153 -153
- package/bin/register-config-route-commands.test.js +121 -121
- package/bin/register-core-commands.js +237 -237
- package/bin/register-kanban-commands.js +56 -56
- package/bin/register-kanban-commands.test.js +83 -83
- package/bin/register-maintenance-commands.js +282 -248
- package/bin/register-project-commands.js +209 -209
- package/bin/register-project-commands.test.js +206 -201
- package/bin/register-query-commands.js +317 -312
- package/bin/register-query-commands.test.js +65 -0
- package/bin/register-resilience-commands.js +182 -182
- package/bin/register-resilience-commands.test.js +81 -81
- package/bin/register-route-commands.js +114 -114
- package/bin/register-session-lifecycle-commands.js +206 -206
- package/bin/register-tailscale-commands.js +106 -106
- package/bin/register-task-commands.js +348 -348
- package/bin/register-task-commands.test.js +69 -69
- package/bin/register-template-commands.js +72 -72
- package/bin/register-vault-operations-commands.js +300 -300
- package/bin/test-helpers/cli-command-fixtures.js +119 -119
- package/dashboard/lib/graph-diff.js +104 -104
- package/dashboard/lib/graph-diff.test.js +75 -75
- package/dashboard/lib/vault-parser.js +556 -556
- package/dashboard/lib/vault-parser.test.js +254 -254
- package/dashboard/public/app.js +796 -796
- package/dashboard/public/index.html +52 -52
- package/dashboard/public/styles.css +221 -221
- package/dashboard/server.js +374 -374
- package/dist/{chunk-G3OQJ2NQ.js → chunk-2YDBJS7M.js} +1 -1
- package/dist/chunk-3FP5BJ42.js +88 -0
- package/dist/{chunk-C3PF7WBA.js → chunk-4IV3R2F5.js} +2 -2
- package/dist/{chunk-7OHQFMJK.js → chunk-AY4PGUVL.js} +5 -4
- package/dist/chunk-FG6RJMCN.js +33 -0
- package/dist/{chunk-WIICLBNF.js → chunk-GFJ3LIIB.js} +1 -1
- package/dist/chunk-IZEY5S74.js +541 -0
- package/dist/chunk-LMEMZGUV.js +332 -0
- package/dist/{chunk-6RQPD7X6.js → chunk-M25QVSJM.js} +4 -3
- package/dist/{chunk-6B3JWM7J.js → chunk-O7XHXF7F.js} +34 -7
- package/dist/chunk-OSMS7QIG.js +406 -0
- package/dist/{chunk-PAYUH64O.js → chunk-QVMXF7FY.js} +11 -1
- package/dist/{chunk-TMZMN7OS.js → chunk-S2IG7VNM.js} +24 -12
- package/dist/{chunk-LMCC5OC7.js → chunk-TPDH3JPP.js} +1 -1
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +31 -0
- package/dist/commands/canvas.js +3 -3
- package/dist/commands/compat.js +1 -1
- package/dist/commands/context.js +4 -4
- package/dist/commands/doctor.js +16 -309
- package/dist/commands/embed.d.ts +17 -0
- package/dist/commands/embed.js +10 -0
- package/dist/commands/migrate-observations.js +2 -2
- package/dist/commands/observe.d.ts +1 -0
- package/dist/commands/observe.js +7 -6
- package/dist/commands/rebuild.js +5 -5
- package/dist/commands/reflect.js +3 -3
- package/dist/commands/replay.js +7 -7
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.d.ts +2 -1
- package/dist/commands/sleep.js +15 -15
- package/dist/commands/status.d.ts +9 -1
- package/dist/commands/status.js +33 -8
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +6 -6
- package/dist/index.d.ts +82 -5
- package/dist/index.js +127 -105
- package/dist/{types-jjuYN2Xn.d.ts → types-C74wgGL1.d.ts} +2 -0
- package/hooks/clawvault/HOOK.md +83 -74
- package/hooks/clawvault/handler.js +816 -812
- package/hooks/clawvault/handler.test.js +263 -263
- package/package.json +94 -125
- package/templates/checkpoint.md +19 -19
- package/templates/daily-note.md +19 -19
- package/templates/daily.md +19 -19
- package/templates/decision.md +17 -17
- package/templates/handoff.md +19 -19
- package/templates/lesson.md +16 -16
- package/templates/person.md +19 -19
- package/templates/project.md +23 -23
- package/dist/chunk-2RK2AG32.js +0 -743
- package/dist/{chunk-FW465EEA.js → chunk-VXEOHTSL.js} +3 -3
- package/dist/{chunk-KCCHROBR.js → chunk-YOSEUUNB.js} +4 -4
|
@@ -1,102 +1,154 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
expect(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
expect(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} finally {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
spawnMock,
|
|
6
|
+
resolveConfiguredVaultPathMock,
|
|
7
|
+
clawvaultCtorMock,
|
|
8
|
+
loadMock
|
|
9
|
+
} = vi.hoisted(() => ({
|
|
10
|
+
spawnMock: vi.fn(),
|
|
11
|
+
resolveConfiguredVaultPathMock: vi.fn(),
|
|
12
|
+
clawvaultCtorMock: vi.fn(),
|
|
13
|
+
loadMock: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('child_process', () => ({
|
|
17
|
+
spawn: spawnMock
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('../dist/index.js', () => ({
|
|
21
|
+
ClawVault: clawvaultCtorMock,
|
|
22
|
+
resolveVaultPath: resolveConfiguredVaultPathMock,
|
|
23
|
+
QmdUnavailableError: class QmdUnavailableError extends Error {},
|
|
24
|
+
QMD_INSTALL_COMMAND: 'install-qmd'
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
async function loadRuntimeModule() {
|
|
28
|
+
vi.resetModules();
|
|
29
|
+
return await import('./command-runtime.js');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.clearAllMocks();
|
|
34
|
+
clawvaultCtorMock.mockImplementation(() => ({
|
|
35
|
+
load: loadMock
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('command runtime helpers', () => {
|
|
40
|
+
it('delegates vault path resolution and loads vaults', async () => {
|
|
41
|
+
resolveConfiguredVaultPathMock.mockReturnValue('/resolved/vault');
|
|
42
|
+
loadMock.mockResolvedValue(undefined);
|
|
43
|
+
const { getVault, resolveVaultPath } = await loadRuntimeModule();
|
|
44
|
+
|
|
45
|
+
const resolved = resolveVaultPath('/explicit');
|
|
46
|
+
expect(resolveConfiguredVaultPathMock).toHaveBeenCalledWith({ explicitPath: '/explicit' });
|
|
47
|
+
expect(resolved).toBe('/resolved/vault');
|
|
48
|
+
|
|
49
|
+
await getVault('/explicit');
|
|
50
|
+
expect(clawvaultCtorMock).toHaveBeenCalledWith('/resolved/vault');
|
|
51
|
+
expect(loadMock).toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('maps qmd ENOENT failures to QmdUnavailableError', async () => {
|
|
55
|
+
const { runQmd, QmdUnavailableError } = await loadRuntimeModule();
|
|
56
|
+
spawnMock.mockImplementation(() => {
|
|
57
|
+
const handlers = {};
|
|
58
|
+
const proc = {
|
|
59
|
+
on: (event, handler) => {
|
|
60
|
+
handlers[event] = handler;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
queueMicrotask(() => {
|
|
64
|
+
handlers.error?.({ code: 'ENOENT' });
|
|
65
|
+
});
|
|
66
|
+
return proc;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await expect(runQmd(['update'])).rejects.toBeInstanceOf(QmdUnavailableError);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('injects qmd index from environment when configured', async () => {
|
|
73
|
+
const previous = process.env.CLAWVAULT_QMD_INDEX;
|
|
74
|
+
process.env.CLAWVAULT_QMD_INDEX = 'clawvault-test';
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const { runQmd } = await loadRuntimeModule();
|
|
78
|
+
spawnMock.mockImplementation((_command, _args) => {
|
|
79
|
+
const handlers = {};
|
|
80
|
+
const proc = {
|
|
81
|
+
on: (event, handler) => {
|
|
82
|
+
handlers[event] = handler;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
queueMicrotask(() => {
|
|
86
|
+
handlers.close?.(0);
|
|
87
|
+
});
|
|
88
|
+
return proc;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await runQmd(['update']);
|
|
92
|
+
expect(spawnMock).toHaveBeenCalledWith(
|
|
93
|
+
'qmd',
|
|
94
|
+
['--index', 'clawvault-test', 'update'],
|
|
95
|
+
{ stdio: 'inherit' }
|
|
96
|
+
);
|
|
97
|
+
} finally {
|
|
98
|
+
if (previous === undefined) {
|
|
99
|
+
delete process.env.CLAWVAULT_QMD_INDEX;
|
|
100
|
+
} else {
|
|
101
|
+
process.env.CLAWVAULT_QMD_INDEX = previous;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('surfaces qmd non-zero exit codes as errors', async () => {
|
|
107
|
+
const { runQmd } = await loadRuntimeModule();
|
|
108
|
+
spawnMock.mockImplementation(() => {
|
|
109
|
+
const handlers = {};
|
|
110
|
+
const proc = {
|
|
111
|
+
on: (event, handler) => {
|
|
112
|
+
handlers[event] = handler;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
queueMicrotask(() => {
|
|
116
|
+
handlers.close?.(2);
|
|
117
|
+
});
|
|
118
|
+
return proc;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await expect(runQmd(['update'])).rejects.toThrow('qmd exited with code 2');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('exports and enforces argument/path sanitization helpers', async () => {
|
|
125
|
+
const { sanitizeQmdArg, validatePathWithinBase } = await loadRuntimeModule();
|
|
126
|
+
expect(sanitizeQmdArg('update')).toBe('update');
|
|
127
|
+
expect(sanitizeQmdArg(42)).toBe('42');
|
|
128
|
+
expect(() => sanitizeQmdArg('bad\0arg')).toThrow('contains null byte');
|
|
129
|
+
|
|
130
|
+
const safePath = validatePathWithinBase('notes/today.md', '/tmp/vault');
|
|
131
|
+
expect(safePath).toBe(path.resolve('/tmp/vault', 'notes/today.md'));
|
|
132
|
+
expect(() => validatePathWithinBase('../etc/passwd', '/tmp/vault')).toThrow('Path traversal detected');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('rejects qmd args with null-byte injection attempts', async () => {
|
|
136
|
+
const { runQmd } = await loadRuntimeModule();
|
|
137
|
+
await expect(runQmd(['up\0date'])).rejects.toThrow('contains null byte');
|
|
138
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('prints consistent qmd missing guidance', async () => {
|
|
142
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
143
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
144
|
+
try {
|
|
145
|
+
const { printQmdMissing } = await loadRuntimeModule();
|
|
146
|
+
printQmdMissing();
|
|
147
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('ClawVault requires qmd.'));
|
|
148
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('install-qmd'));
|
|
149
|
+
} finally {
|
|
150
|
+
errorSpy.mockRestore();
|
|
151
|
+
logSpy.mockRestore();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -1,28 +1,39 @@
|
|
|
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('inject');
|
|
10
|
-
expect(help).toContain('
|
|
11
|
-
expect(help).toContain('
|
|
12
|
-
expect(help).toContain('
|
|
13
|
-
expect(help).toContain('
|
|
14
|
-
expect(help).toContain('
|
|
15
|
-
expect(help).toContain('
|
|
16
|
-
expect(help).toContain('
|
|
17
|
-
expect(help).toContain('
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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('inject');
|
|
10
|
+
expect(help).toContain('doctor');
|
|
11
|
+
expect(help).toContain('embed');
|
|
12
|
+
expect(help).toContain('compat');
|
|
13
|
+
expect(help).toContain('graph');
|
|
14
|
+
expect(help).toContain('reflect');
|
|
15
|
+
expect(help).toContain('replay');
|
|
16
|
+
expect(help).toContain('repair-session');
|
|
17
|
+
expect(help).toContain('project');
|
|
18
|
+
expect(help).toContain('template');
|
|
19
|
+
expect(help).toContain('config');
|
|
20
|
+
expect(help).toContain('route');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('documents context/compat/inject/project help details', () => {
|
|
24
|
+
const program = registerAllCommandModules();
|
|
25
|
+
const contextHelp = program.commands.find((command) => command.name() === 'context')?.helpInformation() ?? '';
|
|
26
|
+
const compatHelp = program.commands.find((command) => command.name() === 'compat')?.helpInformation() ?? '';
|
|
27
|
+
const injectHelp = program.commands.find((command) => command.name() === 'inject')?.helpInformation() ?? '';
|
|
28
|
+
const projectCommand = program.commands.find((command) => command.name() === 'project');
|
|
29
|
+
const projectListHelp = projectCommand?.commands.find((command) => command.name() === 'list')?.helpInformation() ?? '';
|
|
30
|
+
const projectBoardHelp = projectCommand?.commands.find((command) => command.name() === 'board')?.helpInformation() ?? '';
|
|
31
|
+
expect(contextHelp).toContain('--profile <profile>');
|
|
32
|
+
expect(contextHelp).toContain('auto');
|
|
33
|
+
expect(compatHelp).toContain('--strict');
|
|
34
|
+
expect(injectHelp).toContain('inject.maxResults');
|
|
35
|
+
expect(injectHelp).toContain('inject.scope');
|
|
36
|
+
expect(projectListHelp).toContain('archived projects are hidden');
|
|
37
|
+
expect(projectBoardHelp).toContain('default: status');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime config command registrations backed by .clawvault.json.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
function stringifyValue(value) {
|
|
6
|
-
if (Array.isArray(value)) {
|
|
7
|
-
if (value.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean')) {
|
|
8
|
-
return value.join(', ');
|
|
9
|
-
}
|
|
10
|
-
return JSON.stringify(value);
|
|
11
|
-
}
|
|
12
|
-
if (value && typeof value === 'object') {
|
|
13
|
-
return JSON.stringify(value);
|
|
14
|
-
}
|
|
15
|
-
if (value === null || value === undefined) {
|
|
16
|
-
return '(unset)';
|
|
17
|
-
}
|
|
18
|
-
return String(value);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function flattenConfig(value, prefix = '') {
|
|
22
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
23
|
-
return [{ key: prefix || '(root)', value: stringifyValue(value) }];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const rows = [];
|
|
27
|
-
const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
|
|
28
|
-
for (const key of keys) {
|
|
29
|
-
const next = prefix ? `${prefix}.${key}` : key;
|
|
30
|
-
const entry = value[key];
|
|
31
|
-
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
32
|
-
rows.push(...flattenConfig(entry, next));
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
rows.push({ key: next, value: stringifyValue(entry) });
|
|
36
|
-
}
|
|
37
|
-
return rows;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function printTable(rows) {
|
|
41
|
-
if (rows.length === 0) {
|
|
42
|
-
console.log('No config values found.');
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const keyWidth = Math.max(
|
|
46
|
-
'KEY'.length,
|
|
47
|
-
...rows.map((row) => row.key.length)
|
|
48
|
-
);
|
|
49
|
-
const valueWidth = Math.max(
|
|
50
|
-
'VALUE'.length,
|
|
51
|
-
...rows.map((row) => row.value.length)
|
|
52
|
-
);
|
|
53
|
-
const header = `${'KEY'.padEnd(keyWidth)} ${'VALUE'.padEnd(valueWidth)}`;
|
|
54
|
-
const divider = `${'-'.repeat(keyWidth)} ${'-'.repeat(valueWidth)}`;
|
|
55
|
-
console.log(header);
|
|
56
|
-
console.log(divider);
|
|
57
|
-
for (const row of rows) {
|
|
58
|
-
console.log(`${row.key.padEnd(keyWidth)} ${row.value}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function normalizeConfigKey(key) {
|
|
63
|
-
return String(key || '').trim();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function registerConfigCommands(program, { chalk, resolveVaultPath }) {
|
|
67
|
-
const config = program
|
|
68
|
-
.command('config')
|
|
69
|
-
.description('Read and modify runtime
|
|
70
|
-
|
|
71
|
-
config
|
|
72
|
-
.command('get <key>')
|
|
73
|
-
.description('Read a runtime config value (dot-notation supported)')
|
|
74
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
75
|
-
.action(async (key, options) => {
|
|
76
|
-
try {
|
|
77
|
-
const { getConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
|
|
78
|
-
const normalizedKey = normalizeConfigKey(key);
|
|
79
|
-
if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
|
|
80
|
-
throw new Error(`Unsupported config key: ${normalizedKey}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const value = getConfigValue(resolveVaultPath(options.vault), normalizedKey);
|
|
84
|
-
if (Array.isArray(value)) {
|
|
85
|
-
console.log(value.join(','));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (value && typeof value === 'object') {
|
|
89
|
-
console.log(JSON.stringify(value, null, 2));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
console.log(value === undefined || value === null ? '' : String(value));
|
|
93
|
-
} catch (err) {
|
|
94
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
config
|
|
100
|
-
.command('set <key> <value>')
|
|
101
|
-
.description('Set a runtime config value')
|
|
102
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
103
|
-
.action(async (key, value, options) => {
|
|
104
|
-
try {
|
|
105
|
-
const { setConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
|
|
106
|
-
const normalizedKey = normalizeConfigKey(key);
|
|
107
|
-
if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
|
|
108
|
-
throw new Error(`Unsupported config key: ${normalizedKey}`);
|
|
109
|
-
}
|
|
110
|
-
const result = setConfigValue(resolveVaultPath(options.vault), normalizedKey, value);
|
|
111
|
-
console.log(chalk.green(`✓ Updated ${normalizedKey}`));
|
|
112
|
-
console.log(chalk.dim(` Value: ${stringifyValue(result.value)}`));
|
|
113
|
-
} catch (err) {
|
|
114
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
config
|
|
120
|
-
.command('list')
|
|
121
|
-
.description('List all config values')
|
|
122
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
123
|
-
.action(async (options) => {
|
|
124
|
-
try {
|
|
125
|
-
const { listConfig } = await import('../dist/index.js');
|
|
126
|
-
const values = listConfig(resolveVaultPath(options.vault));
|
|
127
|
-
const rows = flattenConfig(values);
|
|
128
|
-
printTable(rows);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
config
|
|
136
|
-
.command('reset')
|
|
137
|
-
.description('Reset runtime config values to defaults')
|
|
138
|
-
.option('--confirm', 'Confirm reset')
|
|
139
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
140
|
-
.action(async (options) => {
|
|
141
|
-
try {
|
|
142
|
-
if (!options.confirm) {
|
|
143
|
-
throw new Error('Refusing to reset config without --confirm.');
|
|
144
|
-
}
|
|
145
|
-
const { resetConfig } = await import('../dist/index.js');
|
|
146
|
-
resetConfig(resolveVaultPath(options.vault));
|
|
147
|
-
console.log(chalk.green('✓ Config reset to defaults'));
|
|
148
|
-
} catch (err) {
|
|
149
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Runtime config command registrations backed by .clawvault.json.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function stringifyValue(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
if (value.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean')) {
|
|
8
|
+
return value.join(', ');
|
|
9
|
+
}
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
}
|
|
12
|
+
if (value && typeof value === 'object') {
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
}
|
|
15
|
+
if (value === null || value === undefined) {
|
|
16
|
+
return '(unset)';
|
|
17
|
+
}
|
|
18
|
+
return String(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function flattenConfig(value, prefix = '') {
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
23
|
+
return [{ key: prefix || '(root)', value: stringifyValue(value) }];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const rows = [];
|
|
27
|
+
const keys = Object.keys(value).sort((left, right) => left.localeCompare(right));
|
|
28
|
+
for (const key of keys) {
|
|
29
|
+
const next = prefix ? `${prefix}.${key}` : key;
|
|
30
|
+
const entry = value[key];
|
|
31
|
+
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
32
|
+
rows.push(...flattenConfig(entry, next));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
rows.push({ key: next, value: stringifyValue(entry) });
|
|
36
|
+
}
|
|
37
|
+
return rows;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function printTable(rows) {
|
|
41
|
+
if (rows.length === 0) {
|
|
42
|
+
console.log('No config values found.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const keyWidth = Math.max(
|
|
46
|
+
'KEY'.length,
|
|
47
|
+
...rows.map((row) => row.key.length)
|
|
48
|
+
);
|
|
49
|
+
const valueWidth = Math.max(
|
|
50
|
+
'VALUE'.length,
|
|
51
|
+
...rows.map((row) => row.value.length)
|
|
52
|
+
);
|
|
53
|
+
const header = `${'KEY'.padEnd(keyWidth)} ${'VALUE'.padEnd(valueWidth)}`;
|
|
54
|
+
const divider = `${'-'.repeat(keyWidth)} ${'-'.repeat(valueWidth)}`;
|
|
55
|
+
console.log(header);
|
|
56
|
+
console.log(divider);
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
console.log(`${row.key.padEnd(keyWidth)} ${row.value}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeConfigKey(key) {
|
|
63
|
+
return String(key || '').trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function registerConfigCommands(program, { chalk, resolveVaultPath }) {
|
|
67
|
+
const config = program
|
|
68
|
+
.command('config')
|
|
69
|
+
.description('Read and modify runtime configuration');
|
|
70
|
+
|
|
71
|
+
config
|
|
72
|
+
.command('get <key>')
|
|
73
|
+
.description('Read a runtime config value (dot-notation supported)')
|
|
74
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
75
|
+
.action(async (key, options) => {
|
|
76
|
+
try {
|
|
77
|
+
const { getConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
|
|
78
|
+
const normalizedKey = normalizeConfigKey(key);
|
|
79
|
+
if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
|
|
80
|
+
throw new Error(`Unsupported config key: ${normalizedKey}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const value = getConfigValue(resolveVaultPath(options.vault), normalizedKey);
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
console.log(value.join(','));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (value && typeof value === 'object') {
|
|
89
|
+
console.log(JSON.stringify(value, null, 2));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
console.log(value === undefined || value === null ? '' : String(value));
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
config
|
|
100
|
+
.command('set <key> <value>')
|
|
101
|
+
.description('Set a runtime config value')
|
|
102
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
103
|
+
.action(async (key, value, options) => {
|
|
104
|
+
try {
|
|
105
|
+
const { setConfigValue, SUPPORTED_CONFIG_KEYS } = await import('../dist/index.js');
|
|
106
|
+
const normalizedKey = normalizeConfigKey(key);
|
|
107
|
+
if (!SUPPORTED_CONFIG_KEYS.includes(normalizedKey)) {
|
|
108
|
+
throw new Error(`Unsupported config key: ${normalizedKey}`);
|
|
109
|
+
}
|
|
110
|
+
const result = setConfigValue(resolveVaultPath(options.vault), normalizedKey, value);
|
|
111
|
+
console.log(chalk.green(`✓ Updated ${normalizedKey}`));
|
|
112
|
+
console.log(chalk.dim(` Value: ${stringifyValue(result.value)}`));
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
config
|
|
120
|
+
.command('list')
|
|
121
|
+
.description('List all config values')
|
|
122
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
123
|
+
.action(async (options) => {
|
|
124
|
+
try {
|
|
125
|
+
const { listConfig } = await import('../dist/index.js');
|
|
126
|
+
const values = listConfig(resolveVaultPath(options.vault));
|
|
127
|
+
const rows = flattenConfig(values);
|
|
128
|
+
printTable(rows);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
config
|
|
136
|
+
.command('reset')
|
|
137
|
+
.description('Reset runtime config values to defaults')
|
|
138
|
+
.option('--confirm', 'Confirm reset (required)')
|
|
139
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
140
|
+
.action(async (options) => {
|
|
141
|
+
try {
|
|
142
|
+
if (!options.confirm) {
|
|
143
|
+
throw new Error('Refusing to reset config without --confirm.');
|
|
144
|
+
}
|
|
145
|
+
const { resetConfig } = await import('../dist/index.js');
|
|
146
|
+
resetConfig(resolveVaultPath(options.vault));
|
|
147
|
+
console.log(chalk.green('✓ Config reset to defaults'));
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|