brain-dev 0.1.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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/agents/brain-checker.md +33 -0
  4. package/agents/brain-debugger.md +35 -0
  5. package/agents/brain-executor.md +37 -0
  6. package/agents/brain-mapper.md +44 -0
  7. package/agents/brain-planner.md +49 -0
  8. package/agents/brain-researcher.md +47 -0
  9. package/agents/brain-synthesizer.md +43 -0
  10. package/agents/brain-verifier.md +41 -0
  11. package/bin/brain-tools.cjs +185 -0
  12. package/bin/lib/adr.cjs +283 -0
  13. package/bin/lib/agents.cjs +152 -0
  14. package/bin/lib/anti-patterns.cjs +183 -0
  15. package/bin/lib/audit.cjs +268 -0
  16. package/bin/lib/commands/adr.cjs +126 -0
  17. package/bin/lib/commands/complete.cjs +270 -0
  18. package/bin/lib/commands/config.cjs +306 -0
  19. package/bin/lib/commands/discuss.cjs +237 -0
  20. package/bin/lib/commands/execute.cjs +415 -0
  21. package/bin/lib/commands/health.cjs +103 -0
  22. package/bin/lib/commands/map.cjs +101 -0
  23. package/bin/lib/commands/new-project.cjs +885 -0
  24. package/bin/lib/commands/pause.cjs +142 -0
  25. package/bin/lib/commands/phase-manage.cjs +357 -0
  26. package/bin/lib/commands/plan.cjs +451 -0
  27. package/bin/lib/commands/progress.cjs +167 -0
  28. package/bin/lib/commands/quick.cjs +447 -0
  29. package/bin/lib/commands/resume.cjs +196 -0
  30. package/bin/lib/commands/storm.cjs +590 -0
  31. package/bin/lib/commands/verify.cjs +504 -0
  32. package/bin/lib/commands.cjs +263 -0
  33. package/bin/lib/complexity.cjs +138 -0
  34. package/bin/lib/complexity.test.cjs +108 -0
  35. package/bin/lib/config.cjs +452 -0
  36. package/bin/lib/core.cjs +62 -0
  37. package/bin/lib/detect.cjs +603 -0
  38. package/bin/lib/git.cjs +112 -0
  39. package/bin/lib/health.cjs +356 -0
  40. package/bin/lib/init.cjs +310 -0
  41. package/bin/lib/logger.cjs +100 -0
  42. package/bin/lib/platform.cjs +58 -0
  43. package/bin/lib/requirements.cjs +158 -0
  44. package/bin/lib/roadmap.cjs +228 -0
  45. package/bin/lib/security.cjs +237 -0
  46. package/bin/lib/state.cjs +353 -0
  47. package/bin/lib/templates.cjs +48 -0
  48. package/bin/templates/advocate.md +182 -0
  49. package/bin/templates/checkpoint.md +55 -0
  50. package/bin/templates/debugger.md +148 -0
  51. package/bin/templates/discuss.md +60 -0
  52. package/bin/templates/executor.md +201 -0
  53. package/bin/templates/mapper.md +129 -0
  54. package/bin/templates/plan-checker.md +134 -0
  55. package/bin/templates/planner.md +165 -0
  56. package/bin/templates/researcher.md +78 -0
  57. package/bin/templates/storm.html +376 -0
  58. package/bin/templates/synthesis.md +30 -0
  59. package/bin/templates/verifier.md +181 -0
  60. package/commands/brain/adr.md +34 -0
  61. package/commands/brain/complete.md +37 -0
  62. package/commands/brain/config.md +37 -0
  63. package/commands/brain/discuss.md +35 -0
  64. package/commands/brain/execute.md +38 -0
  65. package/commands/brain/health.md +33 -0
  66. package/commands/brain/map.md +35 -0
  67. package/commands/brain/new-project.md +38 -0
  68. package/commands/brain/pause.md +26 -0
  69. package/commands/brain/plan.md +38 -0
  70. package/commands/brain/progress.md +28 -0
  71. package/commands/brain/quick.md +51 -0
  72. package/commands/brain/resume.md +28 -0
  73. package/commands/brain/storm.md +30 -0
  74. package/commands/brain/verify.md +39 -0
  75. package/hooks/bootstrap.sh +54 -0
  76. package/hooks/post-tool-use.sh +45 -0
  77. package/hooks/statusline.sh +130 -0
  78. package/package.json +36 -0
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { readState, writeState, atomicWriteSync } = require('../state.cjs');
6
+ const { output, prefix, success, error } = require('../core.cjs');
7
+
8
+ /**
9
+ * Parse --note flag from args.
10
+ * @param {string[]} args
11
+ * @returns {string|null}
12
+ */
13
+ function parseNote(args) {
14
+ const idx = args.indexOf('--note');
15
+ if (idx === -1 || idx + 1 >= args.length) return null;
16
+ return args[idx + 1];
17
+ }
18
+
19
+ /**
20
+ * Generate continue-here.md content from state.
21
+ * @param {object} state - brain.json state
22
+ * @param {string|null} note - Optional user note
23
+ * @returns {string} Markdown content
24
+ */
25
+ function generateSnapshot(state, note) {
26
+ const phase = state.phase || {};
27
+ const phases = phase.phases || [];
28
+ const now = new Date().toISOString();
29
+
30
+ // YAML frontmatter
31
+ const lines = [
32
+ '---',
33
+ `phase: ${phase.current || 0}`,
34
+ `status: ${phase.status || 'initialized'}`,
35
+ `paused_at: ${now}`,
36
+ '---',
37
+ ''
38
+ ];
39
+
40
+ // Current State
41
+ lines.push('## Current State');
42
+ if (note) {
43
+ lines.push(note);
44
+ } else {
45
+ const phaseName = phases.find(p => p.number === phase.current)?.name || 'Unknown';
46
+ lines.push(`Phase ${phase.current} (${phaseName}): ${phase.status}`);
47
+ }
48
+ lines.push('');
49
+
50
+ // Completed Work
51
+ lines.push('## Completed Work');
52
+ const completedPhases = phases.filter(p => p.status === 'complete');
53
+ if (completedPhases.length > 0) {
54
+ for (const p of completedPhases) {
55
+ lines.push(`- Phase ${p.number} (${p.name}): ${p.plans?.done || 0}/${p.plans?.total || 0} plans`);
56
+ }
57
+ } else {
58
+ lines.push('- No phases completed yet');
59
+ }
60
+ lines.push('');
61
+
62
+ // Remaining Work
63
+ lines.push('## Remaining Work');
64
+ const remainingPhases = phases.filter(p => p.status !== 'complete');
65
+ if (remainingPhases.length > 0) {
66
+ for (const p of remainingPhases) {
67
+ const remaining = (p.plans?.total || 0) - (p.plans?.done || 0);
68
+ lines.push(`- Phase ${p.number} (${p.name}): ${remaining} plans remaining`);
69
+ }
70
+ } else {
71
+ lines.push('- All phases complete');
72
+ }
73
+ lines.push('');
74
+
75
+ // Decisions Made
76
+ lines.push('## Decisions Made');
77
+ lines.push('- See .brain/brain.json for full state');
78
+ lines.push('');
79
+
80
+ // Conversation Summary
81
+ lines.push('## Conversation Summary');
82
+ lines.push('<!-- Claude: summarize the current conversation context here when presenting to user -->');
83
+ lines.push('');
84
+
85
+ // Next Action
86
+ lines.push('## Next Action');
87
+ const { nextAction } = require('./progress.cjs');
88
+ lines.push(nextAction(state));
89
+ lines.push('');
90
+
91
+ return lines.join('\n');
92
+ }
93
+
94
+ /**
95
+ * Run the pause command.
96
+ * @param {string[]} args - CLI arguments
97
+ * @param {object} [opts] - Options (brainDir for testing)
98
+ * @returns {object} Result
99
+ */
100
+ async function run(args = [], opts = {}) {
101
+ const brainDir = opts.brainDir || path.join(process.cwd(), '.brain');
102
+ const state = readState(brainDir);
103
+
104
+ if (!state) {
105
+ error("No brain state found. Run 'brain-dev init' first.");
106
+ return { error: 'no-state' };
107
+ }
108
+
109
+ const note = parseNote(args);
110
+ const snapshot = generateSnapshot(state, note);
111
+ const now = new Date().toISOString();
112
+
113
+ // Write continue-here.md
114
+ const snapshotPath = path.join(brainDir, 'continue-here.md');
115
+ atomicWriteSync(snapshotPath, snapshot);
116
+
117
+ // Archive to sessions/
118
+ const sessionsDir = path.join(brainDir, 'sessions');
119
+ if (!fs.existsSync(sessionsDir)) {
120
+ fs.mkdirSync(sessionsDir, { recursive: true });
121
+ }
122
+ const sessionFileName = now.replace(/:/g, '-').replace(/\.\d+Z$/, 'Z') + '.md';
123
+ // Simplify to safe filename
124
+ const safeFileName = now.slice(0, 19).replace(/:/g, '-') + '.md';
125
+ fs.copyFileSync(snapshotPath, path.join(sessionsDir, safeFileName));
126
+
127
+ // Update state
128
+ state.session = state.session || {};
129
+ state.session.lastPaused = now;
130
+ state.session.snapshotPath = '.brain/continue-here.md';
131
+ writeState(brainDir, state);
132
+
133
+ success('Session paused. Snapshot saved to .brain/continue-here.md');
134
+ output(
135
+ { paused: true, snapshot: snapshotPath, session: safeFileName },
136
+ prefix(`Archived to .brain/sessions/${safeFileName}`)
137
+ );
138
+
139
+ return { paused: true, snapshot: snapshotPath };
140
+ }
141
+
142
+ module.exports = { run };
@@ -0,0 +1,357 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { readState, writeState } = require('../state.cjs');
5
+ const { parseRoadmap, writeRoadmap, insertPhase, removePhase, reorderPhases } = require('../roadmap.cjs');
6
+ const { output, error, success } = require('../core.cjs');
7
+
8
+ /**
9
+ * Parse a named flag value from args array.
10
+ * @param {string[]} args
11
+ * @param {string} flag - Flag name (e.g. '--after')
12
+ * @returns {string|null}
13
+ */
14
+ function getFlag(args, flag) {
15
+ const idx = args.indexOf(flag);
16
+ if (idx === -1 || idx === args.length - 1) return null;
17
+ return args[idx + 1];
18
+ }
19
+
20
+ /**
21
+ * Check if a flag exists in args (boolean flag).
22
+ * @param {string[]} args
23
+ * @param {string} flag
24
+ * @returns {boolean}
25
+ */
26
+ function hasFlag(args, flag) {
27
+ return args.includes(flag);
28
+ }
29
+
30
+ /**
31
+ * Map phase status to a display indicator type.
32
+ * @param {string} status
33
+ * @returns {string} 'done' | 'current' | 'pending'
34
+ */
35
+ function statusIndicator(status) {
36
+ const s = (status || '').toLowerCase();
37
+ if (s === 'complete' || s === 'completed' || s === 'done') return 'done';
38
+ if (s === 'in progress' || s === 'executing' || s === 'current') return 'current';
39
+ return 'pending';
40
+ }
41
+
42
+ /**
43
+ * Format phase number for display.
44
+ * @param {number} n
45
+ * @returns {string}
46
+ */
47
+ function fmtNum(n) {
48
+ return Number.isInteger(n) ? String(n) : n.toFixed(1);
49
+ }
50
+
51
+ /**
52
+ * Check if a phase is completed (read-only).
53
+ * @param {{ status: string }} phase
54
+ * @returns {boolean}
55
+ */
56
+ function isComplete(phase) {
57
+ return statusIndicator(phase.status) === 'done';
58
+ }
59
+
60
+ /**
61
+ * Handle the 'list' subcommand.
62
+ */
63
+ function handleList(roadmap) {
64
+ const phases = roadmap.phases.map(p => ({
65
+ number: p.number,
66
+ name: p.name,
67
+ goal: p.goal,
68
+ status: p.status,
69
+ indicator: statusIndicator(p.status),
70
+ dependsOn: p.dependsOn,
71
+ requirements: p.requirements
72
+ }));
73
+
74
+ // Human-readable table
75
+ const indicatorSymbol = { done: '[x]', current: '[>]', pending: '[ ]' };
76
+ const lines = ['Phases:', ''];
77
+ for (const p of phases) {
78
+ const sym = indicatorSymbol[p.indicator];
79
+ lines.push(` ${sym} Phase ${fmtNum(p.number)}: ${p.name} (${p.status})`);
80
+ }
81
+
82
+ output({ phases }, lines.join('\n'));
83
+ return { phases };
84
+ }
85
+
86
+ /**
87
+ * Handle the 'add' subcommand.
88
+ */
89
+ function handleAdd(args, roadmap, brainDir) {
90
+ const name = getFlag(args, '--name');
91
+ const goal = getFlag(args, '--goal');
92
+ const after = getFlag(args, '--after');
93
+
94
+ // If no name/goal provided, output instructions for Claude to ask user
95
+ if (!name || !goal) {
96
+ const questions = [
97
+ { id: 'name', question: 'What is the name of the new phase?' },
98
+ { id: 'goal', question: 'What is the goal of this phase?' },
99
+ { id: 'after', question: `After which phase should it be added? (current phases: ${roadmap.phases.map(p => `${fmtNum(p.number)}: ${p.name}`).join(', ')})` },
100
+ { id: 'requirements', question: 'Any requirements to assign? (comma-separated, or skip)' }
101
+ ];
102
+
103
+ const result = {
104
+ action: 'ask-user',
105
+ command: 'phase add',
106
+ questions,
107
+ instructions: 'Use AskUserQuestion to ask each question, then call: brain-dev phase add --name "<name>" --goal "<goal>" --after <N>'
108
+ };
109
+ output(result, 'To add a phase, please provide: name, goal, position, and optional requirements.');
110
+ return result;
111
+ }
112
+
113
+ // Name and goal provided -- execute the add
114
+ const afterNum = after ? parseFloat(after) : roadmap.phases[roadmap.phases.length - 1]?.number || 0;
115
+ const reqStr = getFlag(args, '--requirements');
116
+ const requirements = reqStr ? reqStr.split(',').map(s => s.trim()) : [];
117
+
118
+ const updatedRoadmap = insertPhase(roadmap, afterNum, { name, goal, requirements });
119
+ writeRoadmap(brainDir, updatedRoadmap);
120
+
121
+ // Find the newly created phase
122
+ const newPhase = updatedRoadmap.phases.find(p => p.name === name);
123
+
124
+ // Update state
125
+ const state = readState(brainDir);
126
+ if (state) {
127
+ state.phase.total = updatedRoadmap.phases.length;
128
+ writeState(brainDir, state);
129
+ }
130
+
131
+ success(`Phase ${fmtNum(newPhase.number)}: ${name} added.`);
132
+ return { action: 'added', phase: newPhase };
133
+ }
134
+
135
+ /**
136
+ * Handle the 'insert' subcommand.
137
+ */
138
+ function handleInsert(args, roadmap, brainDir) {
139
+ const after = getFlag(args, '--after');
140
+ if (!after) {
141
+ error('Missing --after flag. Usage: brain-dev phase insert --after <N>');
142
+ return { error: 'missing-flag', message: 'Missing --after flag' };
143
+ }
144
+
145
+ const afterNum = parseFloat(after);
146
+ const afterPhase = roadmap.phases.find(p => p.number === afterNum);
147
+ if (!afterPhase) {
148
+ error(`Phase ${after} not found.`);
149
+ return { error: 'not-found', message: `Phase ${after} not found` };
150
+ }
151
+
152
+ const name = getFlag(args, '--name');
153
+ const goal = getFlag(args, '--goal');
154
+
155
+ // If no name/goal, output instructions for Claude
156
+ if (!name || !goal) {
157
+ const result = {
158
+ action: 'ask-user',
159
+ command: 'phase insert',
160
+ insertAfter: afterNum,
161
+ questions: [
162
+ { id: 'name', question: 'What is the name of the new phase?' },
163
+ { id: 'goal', question: 'What is the goal of this phase?' },
164
+ { id: 'requirements', question: 'Any requirements to assign? (comma-separated, or skip)' }
165
+ ],
166
+ instructions: `Use AskUserQuestion to ask each question, then call: brain-dev phase insert --after ${after} --name "<name>" --goal "<goal>"`
167
+ };
168
+ output(result, `To insert a phase after ${fmtNum(afterNum)}, please provide: name, goal, and optional requirements.`);
169
+ return result;
170
+ }
171
+
172
+ // Execute the insert
173
+ const reqStr = getFlag(args, '--requirements');
174
+ const requirements = reqStr ? reqStr.split(',').map(s => s.trim()) : [];
175
+
176
+ const updatedRoadmap = insertPhase(roadmap, afterNum, { name, goal, requirements });
177
+ writeRoadmap(brainDir, updatedRoadmap);
178
+
179
+ const newPhase = updatedRoadmap.phases.find(p => p.name === name);
180
+
181
+ // Update state
182
+ const state = readState(brainDir);
183
+ if (state) {
184
+ state.phase.total = updatedRoadmap.phases.length;
185
+ writeState(brainDir, state);
186
+ }
187
+
188
+ success(`Phase ${fmtNum(newPhase.number)}: ${name} inserted after phase ${fmtNum(afterNum)}.`);
189
+ return { action: 'inserted', phase: newPhase };
190
+ }
191
+
192
+ /**
193
+ * Handle the 'remove' subcommand.
194
+ */
195
+ function handleRemove(args, roadmap, brainDir) {
196
+ const phaseStr = getFlag(args, '--phase');
197
+ if (!phaseStr) {
198
+ error('Missing --phase flag. Usage: brain-dev phase remove --phase <N>');
199
+ return { error: 'missing-flag', message: 'Missing --phase flag' };
200
+ }
201
+
202
+ const phaseNum = parseFloat(phaseStr);
203
+ const targetPhase = roadmap.phases.find(p => p.number === phaseNum);
204
+
205
+ if (!targetPhase) {
206
+ error(`Phase ${phaseStr} not found.`);
207
+ return { error: 'not-found', message: `Phase ${phaseStr} not found` };
208
+ }
209
+
210
+ // Completed phases are read-only
211
+ if (isComplete(targetPhase)) {
212
+ error(`Phase ${fmtNum(phaseNum)}: ${targetPhase.name} is complete and read-only. Create a new gap closure phase instead.`);
213
+ return {
214
+ error: 'read-only',
215
+ message: `Phase ${fmtNum(phaseNum)} is complete and cannot be modified. Create a new gap closure phase instead.`
216
+ };
217
+ }
218
+
219
+ const confirm = hasFlag(args, '--confirm');
220
+
221
+ // First call: show orphaned requirements and ask for confirmation
222
+ if (!confirm) {
223
+ const { orphanedRequirements } = removePhase(roadmap, phaseNum);
224
+ const result = {
225
+ action: 'ask-user',
226
+ command: 'phase remove',
227
+ phaseToRemove: phaseNum,
228
+ phaseName: targetPhase.name,
229
+ orphanedRequirements,
230
+ questions: orphanedRequirements.length > 0
231
+ ? orphanedRequirements.map(req => ({
232
+ id: `requirement-${req}`,
233
+ question: `Requirement ${req} will be orphaned. Reassign to another phase / Delete / Hold as unassigned?`
234
+ }))
235
+ : [],
236
+ instructions: orphanedRequirements.length > 0
237
+ ? `Phase ${fmtNum(phaseNum)} has ${orphanedRequirements.length} orphaned requirement(s). Ask user about each, then call: brain-dev phase remove --phase ${phaseStr} --confirm`
238
+ : `Phase ${fmtNum(phaseNum)} has no requirements. Confirm removal: brain-dev phase remove --phase ${phaseStr} --confirm`
239
+ };
240
+ output(result, `Phase ${fmtNum(phaseNum)}: ${targetPhase.name} will be removed.`);
241
+ return result;
242
+ }
243
+
244
+ // Confirmed: execute removal
245
+ const { roadmap: updatedRoadmap, orphanedRequirements } = removePhase(roadmap, phaseNum);
246
+ writeRoadmap(brainDir, updatedRoadmap);
247
+
248
+ // Update state
249
+ const state = readState(brainDir);
250
+ if (state) {
251
+ state.phase.total = updatedRoadmap.phases.length;
252
+ writeState(brainDir, state);
253
+ }
254
+
255
+ success(`Phase ${fmtNum(phaseNum)} removed. Dependencies updated.`);
256
+ return { action: 'removed', removed: phaseNum, orphanedRequirements };
257
+ }
258
+
259
+ /**
260
+ * Handle the 'reorder' subcommand.
261
+ */
262
+ function handleReorder(args, roadmap, brainDir) {
263
+ const orderStr = getFlag(args, '--order');
264
+
265
+ // No order provided: show current order and ask
266
+ if (!orderStr) {
267
+ const currentOrder = roadmap.phases.map(p => ({
268
+ number: p.number,
269
+ name: p.name,
270
+ status: p.status,
271
+ indicator: statusIndicator(p.status)
272
+ }));
273
+
274
+ const result = {
275
+ action: 'ask-user',
276
+ command: 'phase reorder',
277
+ currentOrder,
278
+ instructions: `Current order: ${currentOrder.map(p => `${fmtNum(p.number)}: ${p.name}`).join(', ')}. Ask user for new order, then call: brain-dev phase reorder --order "N,N,N,..."`
279
+ };
280
+ output(result, 'Current phase order:\n' + currentOrder.map(p => ` ${fmtNum(p.number)}: ${p.name} (${p.status})`).join('\n'));
281
+ return result;
282
+ }
283
+
284
+ // Parse new order
285
+ const newOrder = orderStr.split(',').map(s => parseFloat(s.trim()));
286
+
287
+ // Validate: completed phases must remain in their original position
288
+ // Find completed phases and check they are in the same relative position
289
+ const completedPhases = roadmap.phases.filter(p => isComplete(p));
290
+ for (const cp of completedPhases) {
291
+ const originalIdx = roadmap.phases.indexOf(cp);
292
+ const newIdx = newOrder.indexOf(cp.number);
293
+ if (newIdx !== originalIdx) {
294
+ error(`Phase ${fmtNum(cp.number)}: ${cp.name} is complete and cannot be moved. Completed phases are read-only.`);
295
+ return {
296
+ error: 'read-only',
297
+ message: `Phase ${fmtNum(cp.number)} is complete and cannot be moved. Completed phases are read-only.`
298
+ };
299
+ }
300
+ }
301
+
302
+ const updatedRoadmap = reorderPhases(roadmap, newOrder);
303
+ writeRoadmap(brainDir, updatedRoadmap);
304
+
305
+ // Update state
306
+ const state = readState(brainDir);
307
+ if (state) {
308
+ state.phase.total = updatedRoadmap.phases.length;
309
+ writeState(brainDir, state);
310
+ }
311
+
312
+ success('Phases reordered. Dependencies updated.');
313
+ return { action: 'reordered', newOrder: updatedRoadmap.phases.map(p => ({ number: p.number, name: p.name })) };
314
+ }
315
+
316
+ /**
317
+ * Run the phase management command.
318
+ * @param {string[]} args - CLI arguments (subcommand + flags)
319
+ * @param {object} [opts] - Options (brainDir for testing)
320
+ * @returns {object} Structured result
321
+ */
322
+ async function run(args = [], opts = {}) {
323
+ const brainDir = opts.brainDir || path.join(process.cwd(), '.brain');
324
+ const subcommand = args[0] || 'list';
325
+
326
+ // Read roadmap
327
+ let roadmap;
328
+ try {
329
+ roadmap = parseRoadmap(brainDir);
330
+ } catch (e) {
331
+ error(`Cannot read roadmap: ${e.message}`);
332
+ return { error: 'no-roadmap', message: e.message };
333
+ }
334
+
335
+ switch (subcommand) {
336
+ case 'list':
337
+ return handleList(roadmap);
338
+
339
+ case 'add':
340
+ return handleAdd(args, roadmap, brainDir);
341
+
342
+ case 'insert':
343
+ return handleInsert(args, roadmap, brainDir);
344
+
345
+ case 'remove':
346
+ return handleRemove(args, roadmap, brainDir);
347
+
348
+ case 'reorder':
349
+ return handleReorder(args, roadmap, brainDir);
350
+
351
+ default:
352
+ error(`Unknown subcommand: '${subcommand}'. Use: list, add, insert, remove, reorder`);
353
+ return { error: 'unknown-subcommand', message: `Unknown subcommand: ${subcommand}` };
354
+ }
355
+ }
356
+
357
+ module.exports = { run };