clawvault 3.5.0 → 3.5.2
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 +33 -25
- package/bin/command-registration.test.js +179 -0
- package/bin/command-runtime.test.js +154 -0
- package/bin/help-contract.test.js +55 -0
- package/bin/register-config-route-commands.test.js +121 -0
- package/bin/register-core-commands.test.js +80 -0
- package/bin/register-kanban-commands.test.js +83 -0
- package/bin/register-project-commands.test.js +206 -0
- package/bin/register-query-commands.test.js +80 -0
- package/bin/register-resilience-commands.test.js +81 -0
- package/bin/register-task-commands.test.js +69 -0
- package/bin/register-template-commands.test.js +87 -0
- package/bin/test-helpers/cli-command-fixtures.js +120 -0
- package/dashboard/lib/graph-diff.test.js +75 -0
- package/dashboard/lib/vault-parser.test.js +254 -0
- package/dist/{chunk-DCF4KMFD.js → chunk-DPK6P6BO.js} +3 -3
- package/dist/{chunk-BLQXXX7Q.js → chunk-FNFP7N6A.js} +2 -2
- package/dist/{chunk-JI7VUQV7.js → chunk-IFUHSHN3.js} +146 -118
- package/dist/{chunk-QFWERBDP.js → chunk-J6DW6HBX.js} +1 -1
- package/dist/{chunk-VXAGOLDP.js → chunk-LCBHM3D6.js} +1 -1
- package/dist/{chunk-HGDDW24U.js → chunk-NTQD55S3.js} +3 -3
- package/dist/{chunk-QUFQBAHP.js → chunk-P35SHNAU.js} +93 -147
- package/dist/cli/index.js +8 -8
- package/dist/commands/compat.js +1 -1
- package/dist/commands/inject.js +2 -2
- package/dist/commands/maintain.js +2 -2
- package/dist/commands/observe.js +4 -4
- package/dist/commands/rebuild.js +3 -3
- package/dist/commands/replay.js +4 -4
- package/dist/commands/sleep.js +5 -5
- package/dist/commands/status.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +17 -17
- package/dist/{openclaw-plugin--gqA2BZw.d.ts → openclaw-plugin-9M9qCZgl.d.ts} +2 -2
- package/dist/openclaw-plugin.d.ts +1 -1
- package/dist/openclaw-plugin.js +6 -1
- package/package.json +4 -26
- package/CHANGELOG.md +0 -543
- package/SKILL.md +0 -369
- package/docs/clawhub-security-release-playbook.md +0 -75
- package/docs/getting-started/installation.md +0 -99
- package/docs/openclaw-plugin-usage.md +0 -152
- package/dist/{chunk-7SWP5FKU.js → chunk-FSYISBTU.js} +4 -4
- package/dist/{chunk-D5U3Q4N5.js → chunk-IOKLQR4W.js} +4 -4
- package/dist/{chunk-OFOCU2V4.js → chunk-QL34TMGN.js} +3 -3
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerKanbanCommands } from './register-kanban-commands.js';
|
|
4
|
+
import { chalkStub } from './test-helpers/cli-command-fixtures.js';
|
|
5
|
+
|
|
6
|
+
const { kanbanCommandMock } = vi.hoisted(() => ({
|
|
7
|
+
kanbanCommandMock: vi.fn()
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('../dist/commands/kanban.js', () => ({
|
|
11
|
+
kanbanCommand: kanbanCommandMock
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
function buildProgram() {
|
|
15
|
+
const program = new Command();
|
|
16
|
+
registerKanbanCommands(program, {
|
|
17
|
+
chalk: chalkStub,
|
|
18
|
+
resolveVaultPath: (value) => value ?? '/vault'
|
|
19
|
+
});
|
|
20
|
+
return program;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function runCommand(args) {
|
|
24
|
+
const program = buildProgram();
|
|
25
|
+
await program.parseAsync(args, { from: 'user' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('register-kanban-commands', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('registers sync and import subcommands with expected options', () => {
|
|
34
|
+
const program = buildProgram();
|
|
35
|
+
const kanbanCommand = program.commands.find((command) => command.name() === 'kanban');
|
|
36
|
+
expect(kanbanCommand).toBeDefined();
|
|
37
|
+
|
|
38
|
+
const syncCommand = kanbanCommand?.commands.find((command) => command.name() === 'sync');
|
|
39
|
+
expect(syncCommand).toBeDefined();
|
|
40
|
+
const syncFlags = syncCommand?.options.map((option) => option.flags) ?? [];
|
|
41
|
+
expect(syncFlags).toEqual(expect.arrayContaining([
|
|
42
|
+
'--output <path>',
|
|
43
|
+
'--group-by <field>',
|
|
44
|
+
'--filter-project <project>',
|
|
45
|
+
'--filter-owner <owner>',
|
|
46
|
+
'--include-done'
|
|
47
|
+
]));
|
|
48
|
+
|
|
49
|
+
const importCommand = kanbanCommand?.commands.find((command) => command.name() === 'import');
|
|
50
|
+
expect(importCommand).toBeDefined();
|
|
51
|
+
const importFlags = importCommand?.options.map((option) => option.flags) ?? [];
|
|
52
|
+
expect(importFlags).toEqual(expect.arrayContaining(['--output <path>']));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('dispatches sync and import actions to the kanban command handler', async () => {
|
|
56
|
+
await runCommand([
|
|
57
|
+
'kanban',
|
|
58
|
+
'sync',
|
|
59
|
+
'--group-by',
|
|
60
|
+
'priority',
|
|
61
|
+
'--output',
|
|
62
|
+
'Board.md',
|
|
63
|
+
'--filter-project',
|
|
64
|
+
'apollo',
|
|
65
|
+
'--filter-owner',
|
|
66
|
+
'alice',
|
|
67
|
+
'--include-done'
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect(kanbanCommandMock).toHaveBeenCalledWith('/vault', 'sync', {
|
|
71
|
+
output: 'Board.md',
|
|
72
|
+
groupBy: 'priority',
|
|
73
|
+
filterProject: 'apollo',
|
|
74
|
+
filterOwner: 'alice',
|
|
75
|
+
includeDone: true
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await runCommand(['kanban', 'import', '--output', 'Board.md']);
|
|
79
|
+
expect(kanbanCommandMock).toHaveBeenCalledWith('/vault', 'import', {
|
|
80
|
+
output: 'Board.md'
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerProjectCommands } from './register-project-commands.js';
|
|
4
|
+
import { chalkStub } from './test-helpers/cli-command-fixtures.js';
|
|
5
|
+
|
|
6
|
+
const { projectCommandMock } = vi.hoisted(() => ({
|
|
7
|
+
projectCommandMock: vi.fn()
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('../dist/commands/project.js', () => ({
|
|
11
|
+
projectCommand: projectCommandMock
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
function buildProgram() {
|
|
15
|
+
const program = new Command();
|
|
16
|
+
registerProjectCommands(program, {
|
|
17
|
+
chalk: chalkStub,
|
|
18
|
+
resolveVaultPath: (value) => value ?? '/vault'
|
|
19
|
+
});
|
|
20
|
+
return program;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function runCommand(args) {
|
|
24
|
+
const program = buildProgram();
|
|
25
|
+
await program.parseAsync(args, { from: 'user' });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('register-project-commands', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('registers add/update/archive/list/show/tasks/board subcommands', () => {
|
|
34
|
+
const program = buildProgram();
|
|
35
|
+
const projectCommand = program.commands.find((command) => command.name() === 'project');
|
|
36
|
+
expect(projectCommand).toBeDefined();
|
|
37
|
+
|
|
38
|
+
const subcommandNames = projectCommand?.commands.map((command) => command.name()) ?? [];
|
|
39
|
+
expect(subcommandNames).toEqual(expect.arrayContaining([
|
|
40
|
+
'add',
|
|
41
|
+
'update',
|
|
42
|
+
'archive',
|
|
43
|
+
'list',
|
|
44
|
+
'show',
|
|
45
|
+
'tasks',
|
|
46
|
+
'board'
|
|
47
|
+
]));
|
|
48
|
+
|
|
49
|
+
const addCommand = projectCommand?.commands.find((command) => command.name() === 'add');
|
|
50
|
+
const addFlags = addCommand?.options.map((option) => option.flags) ?? [];
|
|
51
|
+
expect(addFlags).toEqual(expect.arrayContaining([
|
|
52
|
+
'--owner <owner>',
|
|
53
|
+
'--status <status>',
|
|
54
|
+
'--team <team>',
|
|
55
|
+
'--client <client>',
|
|
56
|
+
'--tags <tags>',
|
|
57
|
+
'--description <description>',
|
|
58
|
+
'--deadline <date>',
|
|
59
|
+
'--repo <url>',
|
|
60
|
+
'--url <url>'
|
|
61
|
+
]));
|
|
62
|
+
|
|
63
|
+
const listCommand = projectCommand?.commands.find((command) => command.name() === 'list');
|
|
64
|
+
const listFlags = listCommand?.options.map((option) => option.flags) ?? [];
|
|
65
|
+
expect(listFlags).toEqual(expect.arrayContaining([
|
|
66
|
+
'--status <status>',
|
|
67
|
+
'--owner <owner>',
|
|
68
|
+
'--client <client>',
|
|
69
|
+
'--tag <tag>',
|
|
70
|
+
'--json'
|
|
71
|
+
]));
|
|
72
|
+
|
|
73
|
+
const boardCommand = projectCommand?.commands.find((command) => command.name() === 'board');
|
|
74
|
+
const boardFlags = boardCommand?.options.map((option) => option.flags) ?? [];
|
|
75
|
+
expect(boardFlags).toEqual(expect.arrayContaining([
|
|
76
|
+
'--output <path>',
|
|
77
|
+
'--group-by <field>'
|
|
78
|
+
]));
|
|
79
|
+
|
|
80
|
+
expect(listCommand?.description()).toContain('archived projects are hidden');
|
|
81
|
+
|
|
82
|
+
const boardGroupByOption = boardCommand?.options.find((option) => option.flags === '--group-by <field>');
|
|
83
|
+
expect(boardGroupByOption?.description).toContain('default: status');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('dispatches project subcommands to project command handler', async () => {
|
|
87
|
+
await runCommand([
|
|
88
|
+
'project',
|
|
89
|
+
'add',
|
|
90
|
+
'Apollo Launch',
|
|
91
|
+
'--owner',
|
|
92
|
+
'alice',
|
|
93
|
+
'--status',
|
|
94
|
+
'active',
|
|
95
|
+
'--team',
|
|
96
|
+
'alice,bob',
|
|
97
|
+
'--client',
|
|
98
|
+
'Acme',
|
|
99
|
+
'--tags',
|
|
100
|
+
'platform,release',
|
|
101
|
+
'--description',
|
|
102
|
+
'Launch project',
|
|
103
|
+
'--deadline',
|
|
104
|
+
'2026-03-01',
|
|
105
|
+
'--repo',
|
|
106
|
+
'https://github.com/acme/apollo',
|
|
107
|
+
'--url',
|
|
108
|
+
'https://apollo.acme.dev'
|
|
109
|
+
]);
|
|
110
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'add', {
|
|
111
|
+
title: 'Apollo Launch',
|
|
112
|
+
options: {
|
|
113
|
+
owner: 'alice',
|
|
114
|
+
status: 'active',
|
|
115
|
+
team: ['alice', 'bob'],
|
|
116
|
+
client: 'Acme',
|
|
117
|
+
tags: ['platform', 'release'],
|
|
118
|
+
description: 'Launch project',
|
|
119
|
+
deadline: '2026-03-01',
|
|
120
|
+
repo: 'https://github.com/acme/apollo',
|
|
121
|
+
url: 'https://apollo.acme.dev'
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await runCommand([
|
|
126
|
+
'project',
|
|
127
|
+
'update',
|
|
128
|
+
'apollo-launch',
|
|
129
|
+
'--status',
|
|
130
|
+
'paused',
|
|
131
|
+
'--owner',
|
|
132
|
+
'carol',
|
|
133
|
+
'--team',
|
|
134
|
+
'carol,dave',
|
|
135
|
+
'--client',
|
|
136
|
+
'Acme',
|
|
137
|
+
'--tags',
|
|
138
|
+
'ops',
|
|
139
|
+
'--description',
|
|
140
|
+
'Paused pending review',
|
|
141
|
+
'--deadline',
|
|
142
|
+
'2026-03-10',
|
|
143
|
+
'--repo',
|
|
144
|
+
'https://github.com/acme/apollo-v2',
|
|
145
|
+
'--url',
|
|
146
|
+
'https://staging.acme.dev'
|
|
147
|
+
]);
|
|
148
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'update', {
|
|
149
|
+
slug: 'apollo-launch',
|
|
150
|
+
options: {
|
|
151
|
+
status: 'paused',
|
|
152
|
+
owner: 'carol',
|
|
153
|
+
team: ['carol', 'dave'],
|
|
154
|
+
client: 'Acme',
|
|
155
|
+
tags: ['ops'],
|
|
156
|
+
description: 'Paused pending review',
|
|
157
|
+
deadline: '2026-03-10',
|
|
158
|
+
repo: 'https://github.com/acme/apollo-v2',
|
|
159
|
+
url: 'https://staging.acme.dev'
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await runCommand(['project', 'archive', 'apollo-launch', '--reason', 'Client offboarded']);
|
|
164
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'archive', {
|
|
165
|
+
slug: 'apollo-launch',
|
|
166
|
+
options: {
|
|
167
|
+
reason: 'Client offboarded'
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await runCommand(['project', 'list', '--status', 'active', '--owner', 'alice', '--client', 'Acme', '--tag', 'platform', '--json']);
|
|
172
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'list', {
|
|
173
|
+
options: {
|
|
174
|
+
status: 'active',
|
|
175
|
+
owner: 'alice',
|
|
176
|
+
client: 'Acme',
|
|
177
|
+
tag: 'platform',
|
|
178
|
+
json: true
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await runCommand(['project', 'show', 'apollo-launch']);
|
|
183
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'show', {
|
|
184
|
+
slug: 'apollo-launch',
|
|
185
|
+
options: {
|
|
186
|
+
json: undefined
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await runCommand(['project', 'tasks', 'apollo-launch', '--json']);
|
|
191
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'tasks', {
|
|
192
|
+
slug: 'apollo-launch',
|
|
193
|
+
options: {
|
|
194
|
+
json: true
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await runCommand(['project', 'board', '--output', 'Projects-Board.md', '--group-by', 'client']);
|
|
199
|
+
expect(projectCommandMock).toHaveBeenCalledWith('/vault', 'board', {
|
|
200
|
+
options: {
|
|
201
|
+
output: 'Projects-Board.md',
|
|
202
|
+
groupBy: 'client'
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerQueryCommands } from './register-query-commands.js';
|
|
4
|
+
import {
|
|
5
|
+
chalkStub,
|
|
6
|
+
createGetVaultStub,
|
|
7
|
+
stubResolveVaultPath
|
|
8
|
+
} from './test-helpers/cli-command-fixtures.js';
|
|
9
|
+
|
|
10
|
+
function buildProgram() {
|
|
11
|
+
const program = new Command();
|
|
12
|
+
registerQueryCommands(program, {
|
|
13
|
+
chalk: chalkStub,
|
|
14
|
+
getVault: createGetVaultStub({ find: async () => [], vsearch: async () => [] }),
|
|
15
|
+
resolveVaultPath: stubResolveVaultPath,
|
|
16
|
+
QmdUnavailableError: class extends Error {},
|
|
17
|
+
printQmdMissing: () => {}
|
|
18
|
+
});
|
|
19
|
+
return program;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('register-query-commands', () => {
|
|
23
|
+
it('registers recall command with strategy options', () => {
|
|
24
|
+
const program = buildProgram();
|
|
25
|
+
const recallCommand = program.commands.find((command) => command.name() === 'recall');
|
|
26
|
+
expect(recallCommand).toBeDefined();
|
|
27
|
+
|
|
28
|
+
const flags = recallCommand?.options.map((option) => option.flags) ?? [];
|
|
29
|
+
expect(flags).toEqual(expect.arrayContaining([
|
|
30
|
+
'-n, --limit <n>',
|
|
31
|
+
'--strategy <strategy>',
|
|
32
|
+
'--json',
|
|
33
|
+
'--no-sources',
|
|
34
|
+
'-v, --vault <path>'
|
|
35
|
+
]));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('documents inject command options and config-backed defaults', () => {
|
|
39
|
+
const program = buildProgram();
|
|
40
|
+
const injectCommand = program.commands.find((command) => command.name() === 'inject');
|
|
41
|
+
expect(injectCommand).toBeDefined();
|
|
42
|
+
|
|
43
|
+
const injectFlags = injectCommand?.options.map((option) => option.flags) ?? [];
|
|
44
|
+
expect(injectFlags).toEqual(expect.arrayContaining([
|
|
45
|
+
'-n, --max-results <n>',
|
|
46
|
+
'--scope <scope>',
|
|
47
|
+
'--enable-llm',
|
|
48
|
+
'--disable-llm',
|
|
49
|
+
'--format <format>',
|
|
50
|
+
'--model <model>',
|
|
51
|
+
'-v, --vault <path>'
|
|
52
|
+
]));
|
|
53
|
+
|
|
54
|
+
const maxResultsOption = injectCommand?.options.find((option) => option.flags === '-n, --max-results <n>');
|
|
55
|
+
const scopeOption = injectCommand?.options.find((option) => option.flags === '--scope <scope>');
|
|
56
|
+
const formatOption = injectCommand?.options.find((option) => option.flags === '--format <format>');
|
|
57
|
+
expect(maxResultsOption?.description).toContain('inject.maxResults');
|
|
58
|
+
expect(scopeOption?.description).toContain('inject.scope');
|
|
59
|
+
expect(formatOption?.description).toContain('default: markdown');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('documents observe extraction toggles and threshold defaults', () => {
|
|
63
|
+
const program = buildProgram();
|
|
64
|
+
const observeCommand = program.commands.find((command) => command.name() === 'observe');
|
|
65
|
+
expect(observeCommand).toBeDefined();
|
|
66
|
+
|
|
67
|
+
const observeFlags = observeCommand?.options.map((option) => option.flags) ?? [];
|
|
68
|
+
expect(observeFlags).toEqual(expect.arrayContaining([
|
|
69
|
+
'--extract-tasks',
|
|
70
|
+
'--no-extract-tasks',
|
|
71
|
+
'--threshold <n>',
|
|
72
|
+
'--reflect-threshold <n>'
|
|
73
|
+
]));
|
|
74
|
+
|
|
75
|
+
const thresholdOption = observeCommand?.options.find((option) => option.flags === '--threshold <n>');
|
|
76
|
+
const reflectThresholdOption = observeCommand?.options.find((option) => option.flags === '--reflect-threshold <n>');
|
|
77
|
+
expect(thresholdOption?.description).toContain('default: 30000');
|
|
78
|
+
expect(reflectThresholdOption?.description).toContain('default: 40000');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerResilienceCommands } from './register-resilience-commands.js';
|
|
4
|
+
import { chalkStub } from './test-helpers/cli-command-fixtures.js';
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
recoverMock,
|
|
8
|
+
formatRecoveryInfoMock,
|
|
9
|
+
checkRecoveryStatusMock,
|
|
10
|
+
formatRecoveryCheckStatusMock,
|
|
11
|
+
listCheckpointsMock,
|
|
12
|
+
formatCheckpointListMock
|
|
13
|
+
} = vi.hoisted(() => ({
|
|
14
|
+
recoverMock: vi.fn(),
|
|
15
|
+
formatRecoveryInfoMock: vi.fn(),
|
|
16
|
+
checkRecoveryStatusMock: vi.fn(),
|
|
17
|
+
formatRecoveryCheckStatusMock: vi.fn(),
|
|
18
|
+
listCheckpointsMock: vi.fn(),
|
|
19
|
+
formatCheckpointListMock: vi.fn()
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock('../dist/commands/recover.js', () => ({
|
|
23
|
+
recover: recoverMock,
|
|
24
|
+
formatRecoveryInfo: formatRecoveryInfoMock,
|
|
25
|
+
checkRecoveryStatus: checkRecoveryStatusMock,
|
|
26
|
+
formatRecoveryCheckStatus: formatRecoveryCheckStatusMock,
|
|
27
|
+
listCheckpoints: listCheckpointsMock,
|
|
28
|
+
formatCheckpointList: formatCheckpointListMock
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
function buildProgram() {
|
|
32
|
+
const program = new Command();
|
|
33
|
+
registerResilienceCommands(program, {
|
|
34
|
+
chalk: chalkStub,
|
|
35
|
+
resolveVaultPath: (value) => value ?? '/vault'
|
|
36
|
+
});
|
|
37
|
+
return program;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function runCommand(args) {
|
|
41
|
+
const program = buildProgram();
|
|
42
|
+
await program.parseAsync(args, { from: 'user' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
recoverMock.mockResolvedValue({ died: false });
|
|
48
|
+
formatRecoveryInfoMock.mockReturnValue('recover');
|
|
49
|
+
checkRecoveryStatusMock.mockResolvedValue({ died: false, deathTime: null, checkpoint: null });
|
|
50
|
+
formatRecoveryCheckStatusMock.mockReturnValue('check');
|
|
51
|
+
listCheckpointsMock.mockReturnValue([]);
|
|
52
|
+
formatCheckpointListMock.mockReturnValue('list');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('register-resilience-commands', () => {
|
|
56
|
+
it('routes recover --check through check helpers', async () => {
|
|
57
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
58
|
+
try {
|
|
59
|
+
await runCommand(['recover', '--check']);
|
|
60
|
+
expect(checkRecoveryStatusMock).toHaveBeenCalledWith('/vault');
|
|
61
|
+
expect(formatRecoveryCheckStatusMock).toHaveBeenCalled();
|
|
62
|
+
expect(recoverMock).not.toHaveBeenCalled();
|
|
63
|
+
expect(logSpy).toHaveBeenCalledWith('check');
|
|
64
|
+
} finally {
|
|
65
|
+
logSpy.mockRestore();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('routes recover --list through checkpoint listing helpers', async () => {
|
|
70
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
71
|
+
try {
|
|
72
|
+
await runCommand(['recover', '--list']);
|
|
73
|
+
expect(listCheckpointsMock).toHaveBeenCalledWith('/vault');
|
|
74
|
+
expect(formatCheckpointListMock).toHaveBeenCalled();
|
|
75
|
+
expect(recoverMock).not.toHaveBeenCalled();
|
|
76
|
+
expect(logSpy).toHaveBeenCalledWith('list');
|
|
77
|
+
} finally {
|
|
78
|
+
logSpy.mockRestore();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerTaskCommands } from './register-task-commands.js';
|
|
4
|
+
import { chalkStub, stubResolveVaultPath } from './test-helpers/cli-command-fixtures.js';
|
|
5
|
+
|
|
6
|
+
describe('register-task-commands', () => {
|
|
7
|
+
it('adds enriched task add/list/update options', () => {
|
|
8
|
+
const program = new Command();
|
|
9
|
+
registerTaskCommands(program, {
|
|
10
|
+
chalk: chalkStub,
|
|
11
|
+
resolveVaultPath: stubResolveVaultPath
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const taskCommand = program.commands.find((command) => command.name() === 'task');
|
|
15
|
+
expect(taskCommand).toBeDefined();
|
|
16
|
+
|
|
17
|
+
const addCommand = taskCommand?.commands.find((command) => command.name() === 'add');
|
|
18
|
+
const addFlags = addCommand?.options.map((option) => option.flags) ?? [];
|
|
19
|
+
expect(addFlags).toEqual(expect.arrayContaining([
|
|
20
|
+
'--due <date>',
|
|
21
|
+
'--tags <tags>',
|
|
22
|
+
'--description <description>',
|
|
23
|
+
'--estimate <estimate>',
|
|
24
|
+
'--parent <slug>',
|
|
25
|
+
'--depends-on <slugs>'
|
|
26
|
+
]));
|
|
27
|
+
|
|
28
|
+
const listCommand = taskCommand?.commands.find((command) => command.name() === 'list');
|
|
29
|
+
const listFlags = listCommand?.options.map((option) => option.flags) ?? [];
|
|
30
|
+
expect(listFlags).toEqual(expect.arrayContaining([
|
|
31
|
+
'--due',
|
|
32
|
+
'--tag <tag>',
|
|
33
|
+
'--overdue'
|
|
34
|
+
]));
|
|
35
|
+
|
|
36
|
+
const updateCommand = taskCommand?.commands.find((command) => command.name() === 'update');
|
|
37
|
+
const updateFlags = updateCommand?.options.map((option) => option.flags) ?? [];
|
|
38
|
+
expect(updateFlags).toEqual(expect.arrayContaining([
|
|
39
|
+
'--tags <tags>',
|
|
40
|
+
'--description <description>',
|
|
41
|
+
'--estimate <estimate>',
|
|
42
|
+
'--parent <slug>',
|
|
43
|
+
'--depends-on <slugs>',
|
|
44
|
+
'--clear-due',
|
|
45
|
+
'--clear-tags',
|
|
46
|
+
'--clear-description',
|
|
47
|
+
'--clear-estimate',
|
|
48
|
+
'--clear-parent',
|
|
49
|
+
'--clear-depends-on'
|
|
50
|
+
]));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('adds simplified canvas flags', () => {
|
|
54
|
+
const program = new Command();
|
|
55
|
+
registerTaskCommands(program, {
|
|
56
|
+
chalk: chalkStub,
|
|
57
|
+
resolveVaultPath: stubResolveVaultPath
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const canvasCommand = program.commands.find((command) => command.name() === 'canvas');
|
|
61
|
+
expect(canvasCommand).toBeDefined();
|
|
62
|
+
|
|
63
|
+
const optionFlags = canvasCommand?.options.map((option) => option.flags) ?? [];
|
|
64
|
+
expect(optionFlags).toEqual(expect.arrayContaining([
|
|
65
|
+
'-v, --vault <path>',
|
|
66
|
+
'--output <path>'
|
|
67
|
+
]));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerTemplateCommands } from './register-template-commands.js';
|
|
4
|
+
import { chalkStub } from './test-helpers/cli-command-fixtures.js';
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
listTemplateDefinitionsMock,
|
|
8
|
+
createFromTemplateMock,
|
|
9
|
+
addTemplateMock
|
|
10
|
+
} = vi.hoisted(() => ({
|
|
11
|
+
listTemplateDefinitionsMock: vi.fn(),
|
|
12
|
+
createFromTemplateMock: vi.fn(),
|
|
13
|
+
addTemplateMock: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('../dist/commands/template.js', () => ({
|
|
17
|
+
listTemplateDefinitions: listTemplateDefinitionsMock,
|
|
18
|
+
createFromTemplate: createFromTemplateMock,
|
|
19
|
+
addTemplate: addTemplateMock
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
function buildProgram() {
|
|
23
|
+
const program = new Command();
|
|
24
|
+
registerTemplateCommands(program, { chalk: chalkStub });
|
|
25
|
+
return program;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function runCommand(args) {
|
|
29
|
+
const program = buildProgram();
|
|
30
|
+
await program.parseAsync(args, { from: 'user' });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('register-template-commands', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
listTemplateDefinitionsMock.mockReturnValue([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('registers list/create/add template subcommands', () => {
|
|
40
|
+
const program = buildProgram();
|
|
41
|
+
const templateCommand = program.commands.find((command) => command.name() === 'template');
|
|
42
|
+
expect(templateCommand).toBeDefined();
|
|
43
|
+
expect(templateCommand?.commands.map((command) => command.name())).toEqual(
|
|
44
|
+
expect.arrayContaining(['list', 'create', 'add'])
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('prints template fields in list output', async () => {
|
|
49
|
+
listTemplateDefinitionsMock.mockReturnValue([
|
|
50
|
+
{ name: 'task', fields: ['status', 'owner'] },
|
|
51
|
+
{ name: 'project', fields: ['status', 'client'] }
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
55
|
+
try {
|
|
56
|
+
await runCommand(['template', 'list']);
|
|
57
|
+
expect(logSpy).toHaveBeenCalledWith('- task (status, owner)');
|
|
58
|
+
expect(logSpy).toHaveBeenCalledWith('- project (status, client)');
|
|
59
|
+
} finally {
|
|
60
|
+
logSpy.mockRestore();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('dispatches create and add operations to template command handlers', async () => {
|
|
65
|
+
createFromTemplateMock.mockReturnValue({
|
|
66
|
+
outputPath: '/tmp/task.md',
|
|
67
|
+
templatePath: '/templates/task.md',
|
|
68
|
+
variables: { title: 'Task', type: 'task', date: '2026-02-16', datetime: '2026-02-16T00:00:00.000Z' }
|
|
69
|
+
});
|
|
70
|
+
addTemplateMock.mockReturnValue({
|
|
71
|
+
templatePath: '/vault/templates/custom.md',
|
|
72
|
+
name: 'custom'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await runCommand(['template', 'create', 'task', '--title', 'Ship It']);
|
|
76
|
+
expect(createFromTemplateMock).toHaveBeenCalledWith('task', {
|
|
77
|
+
title: 'Ship It',
|
|
78
|
+
vaultPath: undefined
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await runCommand(['template', 'add', 'source.md', '--name', 'custom']);
|
|
82
|
+
expect(addTemplateMock).toHaveBeenCalledWith('source.md', {
|
|
83
|
+
name: 'custom',
|
|
84
|
+
vaultPath: undefined
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|