@girardmedia/bootspring 2.1.3 → 2.2.1
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/bin/bootspring.js +157 -83
- package/claude-commands/agent.md +34 -0
- package/claude-commands/bs.md +31 -0
- package/claude-commands/build.md +25 -0
- package/claude-commands/skill.md +31 -0
- package/claude-commands/todo.md +25 -0
- package/dist/core/index.d.ts +5814 -0
- package/dist/core.js +5779 -0
- package/dist/index.js +93883 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp-server.js +2298 -0
- package/generators/api-docs.js +3 -3
- package/generators/decisions.js +14 -14
- package/generators/health.js +6 -6
- package/generators/sprint.js +4 -4
- package/generators/templates/build-planning.template.js +2 -2
- package/generators/visual-doc-generator.js +1 -1
- package/package.json +22 -68
- package/cli/agent.js +0 -799
- package/cli/auth.js +0 -896
- package/cli/billing.js +0 -320
- package/cli/build.js +0 -1442
- package/cli/dashboard.js +0 -123
- package/cli/init.js +0 -669
- package/cli/mcp.js +0 -240
- package/cli/orchestrator.js +0 -240
- package/cli/project.js +0 -825
- package/cli/quality.js +0 -281
- package/cli/skill.js +0 -503
- package/cli/switch.js +0 -453
- package/cli/todo.js +0 -629
- package/cli/update.js +0 -132
- package/core/api-client.d.ts +0 -69
- package/core/api-client.js +0 -1482
- package/core/auth.d.ts +0 -98
- package/core/auth.js +0 -737
- package/core/build-orchestrator.js +0 -508
- package/core/build-state.js +0 -612
- package/core/config.d.ts +0 -106
- package/core/config.js +0 -1328
- package/core/context-loader.js +0 -580
- package/core/context.d.ts +0 -61
- package/core/context.js +0 -327
- package/core/entitlements.d.ts +0 -70
- package/core/entitlements.js +0 -322
- package/core/index.d.ts +0 -53
- package/core/index.js +0 -62
- package/core/mcp-config.js +0 -115
- package/core/policies.d.ts +0 -43
- package/core/policies.js +0 -113
- package/core/policy-matrix.js +0 -303
- package/core/project-activity.js +0 -175
- package/core/redaction.d.ts +0 -5
- package/core/redaction.js +0 -63
- package/core/self-update.js +0 -259
- package/core/session.js +0 -353
- package/core/task-extractor.js +0 -1098
- package/core/telemetry.d.ts +0 -55
- package/core/telemetry.js +0 -617
- package/core/tier-enforcement.js +0 -928
- package/core/utils.d.ts +0 -90
- package/core/utils.js +0 -455
- package/core/validation.js +0 -572
- package/mcp/server.d.ts +0 -57
- package/mcp/server.js +0 -264
package/cli/todo.js
DELETED
|
@@ -1,629 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bootspring Todo Command
|
|
3
|
-
* Simple and powerful todo management
|
|
4
|
-
*
|
|
5
|
-
* @package bootspring
|
|
6
|
-
* @command todo
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const config = require('../core/config');
|
|
12
|
-
const utils = require('../core/utils');
|
|
13
|
-
const { validateTodoText, validateNumericId } = require('../core/validation');
|
|
14
|
-
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Utility Functions (for programmatic/test use)
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Parse todo file and return todos
|
|
21
|
-
* @param {string} filePath - Path to todo file
|
|
22
|
-
* @returns {object[]} Array of todos with { done, text } format
|
|
23
|
-
*/
|
|
24
|
-
function parseTodoFile(filePath) {
|
|
25
|
-
try {
|
|
26
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
27
|
-
const todos = [];
|
|
28
|
-
const lines = content.split('\n');
|
|
29
|
-
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
const match = line.match(/^(\s*)-\s*\[([ xX])\]\s*(.+)$/);
|
|
32
|
-
if (match) {
|
|
33
|
-
todos.push({
|
|
34
|
-
done: match[2].toLowerCase() === 'x',
|
|
35
|
-
text: match[3].trim()
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return todos;
|
|
41
|
-
} catch {
|
|
42
|
-
return [];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Write todos to file
|
|
48
|
-
* @param {string} filePath - Path to todo file
|
|
49
|
-
* @param {object[]} items - Array of todos with { done, text } format
|
|
50
|
-
*/
|
|
51
|
-
function writeTodoFile(filePath, items) {
|
|
52
|
-
const lines = ['# Todo', ''];
|
|
53
|
-
|
|
54
|
-
for (const item of items) {
|
|
55
|
-
const checkbox = item.done ? '[x]' : '[ ]';
|
|
56
|
-
lines.push(`- ${checkbox} ${item.text}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
lines.push(''); // trailing newline
|
|
60
|
-
fs.writeFileSync(filePath, lines.join('\n'), 'utf-8');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Add a todo to file (utility function)
|
|
65
|
-
* @param {string} filePath - Path to todo file
|
|
66
|
-
* @param {string} text - Todo text
|
|
67
|
-
*/
|
|
68
|
-
function addTodoUtil(filePath, text) {
|
|
69
|
-
const todos = parseTodoFile(filePath);
|
|
70
|
-
todos.push({ done: false, text });
|
|
71
|
-
writeTodoFile(filePath, todos);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Mark a todo as done (utility function)
|
|
76
|
-
* @param {string} filePath - Path to todo file
|
|
77
|
-
* @param {number} index - Zero-based index
|
|
78
|
-
*/
|
|
79
|
-
function markDone(filePath, index) {
|
|
80
|
-
const todos = parseTodoFile(filePath);
|
|
81
|
-
if (index >= 0 && index < todos.length) {
|
|
82
|
-
todos[index].done = true;
|
|
83
|
-
writeTodoFile(filePath, todos);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Mark a todo as not done (utility function)
|
|
89
|
-
* @param {string} filePath - Path to todo file
|
|
90
|
-
* @param {number} index - Zero-based index
|
|
91
|
-
*/
|
|
92
|
-
function markUndone(filePath, index) {
|
|
93
|
-
const todos = parseTodoFile(filePath);
|
|
94
|
-
if (index >= 0 && index < todos.length) {
|
|
95
|
-
todos[index].done = false;
|
|
96
|
-
writeTodoFile(filePath, todos);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Remove a todo by index (utility function)
|
|
102
|
-
* @param {string} filePath - Path to todo file
|
|
103
|
-
* @param {number} index - Zero-based index
|
|
104
|
-
*/
|
|
105
|
-
function removeTodoUtil(filePath, index) {
|
|
106
|
-
const todos = parseTodoFile(filePath);
|
|
107
|
-
if (index >= 0 && index < todos.length) {
|
|
108
|
-
todos.splice(index, 1);
|
|
109
|
-
writeTodoFile(filePath, todos);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Clear todos (utility function)
|
|
115
|
-
* @param {string} filePath - Path to todo file
|
|
116
|
-
* @param {string} type - 'completed' or 'all'
|
|
117
|
-
*/
|
|
118
|
-
function clearTodos(filePath, type = 'completed') {
|
|
119
|
-
const todos = parseTodoFile(filePath);
|
|
120
|
-
|
|
121
|
-
if (type === 'all') {
|
|
122
|
-
writeTodoFile(filePath, []);
|
|
123
|
-
} else if (type === 'completed') {
|
|
124
|
-
const remaining = todos.filter(t => !t.done);
|
|
125
|
-
writeTodoFile(filePath, remaining);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* List all todos from file (utility function)
|
|
131
|
-
* @param {string} filePath - Path to todo file
|
|
132
|
-
* @returns {object[]} Array of todos
|
|
133
|
-
*/
|
|
134
|
-
function listTodosUtil(filePath) {
|
|
135
|
-
return parseTodoFile(filePath);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ============================================================================
|
|
139
|
-
// Internal Parsing (for CLI use)
|
|
140
|
-
// ============================================================================
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Parse todo.md file content
|
|
144
|
-
* @param {string} content - File content
|
|
145
|
-
* @returns {object[]} Array of todos with full metadata
|
|
146
|
-
*/
|
|
147
|
-
function parseTodos(content) {
|
|
148
|
-
const todos = [];
|
|
149
|
-
const lines = content.split('\n');
|
|
150
|
-
|
|
151
|
-
for (let i = 0; i < lines.length; i++) {
|
|
152
|
-
const line = lines[i];
|
|
153
|
-
|
|
154
|
-
// Match todo items: - [ ] or - [x]
|
|
155
|
-
const match = line.match(/^(\s*)-\s*\[([ xX])\]\s*(.+)$/);
|
|
156
|
-
if (match) {
|
|
157
|
-
todos.push({
|
|
158
|
-
index: todos.length + 1,
|
|
159
|
-
indent: match[1].length,
|
|
160
|
-
completed: match[2].toLowerCase() === 'x',
|
|
161
|
-
text: match[3].trim(),
|
|
162
|
-
line: i
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return todos;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Format todos for display
|
|
172
|
-
* @param {object[]} todos - Array of todos
|
|
173
|
-
* @param {object} options - Display options
|
|
174
|
-
*/
|
|
175
|
-
function displayTodos(todos, options = {}) {
|
|
176
|
-
const { showCompleted = true, showIndex = true } = options;
|
|
177
|
-
|
|
178
|
-
const pending = todos.filter(t => !t.completed);
|
|
179
|
-
const completed = todos.filter(t => t.completed);
|
|
180
|
-
|
|
181
|
-
if (pending.length === 0 && completed.length === 0) {
|
|
182
|
-
utils.print.dim('No todos found. Add one with: bootspring todo add "Your task"');
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Display pending
|
|
187
|
-
if (pending.length > 0) {
|
|
188
|
-
console.log(`\n${utils.COLORS.bold}Pending (${pending.length})${utils.COLORS.reset}\n`);
|
|
189
|
-
for (const todo of pending) {
|
|
190
|
-
const index = showIndex ? `${utils.COLORS.dim}${String(todo.index).padStart(2)}${utils.COLORS.reset} ` : '';
|
|
191
|
-
const indent = ' '.repeat(todo.indent);
|
|
192
|
-
console.log(`${index}${indent}${utils.COLORS.yellow}○${utils.COLORS.reset} ${todo.text}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Display completed
|
|
197
|
-
if (showCompleted && completed.length > 0) {
|
|
198
|
-
console.log(`\n${utils.COLORS.bold}Completed (${completed.length})${utils.COLORS.reset}\n`);
|
|
199
|
-
for (const todo of completed) {
|
|
200
|
-
const index = showIndex ? `${utils.COLORS.dim}${String(todo.index).padStart(2)}${utils.COLORS.reset} ` : '';
|
|
201
|
-
const indent = ' '.repeat(todo.indent);
|
|
202
|
-
console.log(`${index}${indent}${utils.COLORS.green}●${utils.COLORS.reset} ${utils.COLORS.dim}${todo.text}${utils.COLORS.reset}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
console.log();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get todo file path
|
|
211
|
-
* @returns {string} Todo file path
|
|
212
|
-
*/
|
|
213
|
-
function getTodoPath() {
|
|
214
|
-
const cfg = config.load();
|
|
215
|
-
return path.join(cfg._projectRoot, cfg.paths?.todo || 'todo.md');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Read todo file
|
|
220
|
-
* @returns {string} File content
|
|
221
|
-
*/
|
|
222
|
-
function readTodoFile() {
|
|
223
|
-
const todoPath = getTodoPath();
|
|
224
|
-
if (!utils.fileExists(todoPath)) {
|
|
225
|
-
// Create default todo file
|
|
226
|
-
const defaultContent = '# Todo List\n\n## Pending\n\n## Completed\n';
|
|
227
|
-
utils.writeFile(todoPath, defaultContent);
|
|
228
|
-
return defaultContent;
|
|
229
|
-
}
|
|
230
|
-
return utils.readFile(todoPath);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Save todo file content (internal CLI use)
|
|
235
|
-
* @param {string} content - File content
|
|
236
|
-
*/
|
|
237
|
-
function saveTodoContent(content) {
|
|
238
|
-
const todoPath = getTodoPath();
|
|
239
|
-
utils.writeFile(todoPath, content);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* List all todos
|
|
244
|
-
*/
|
|
245
|
-
function listTodos(args) {
|
|
246
|
-
const parsedArgs = utils.parseArgs(args);
|
|
247
|
-
const content = readTodoFile();
|
|
248
|
-
const todos = parseTodos(content);
|
|
249
|
-
|
|
250
|
-
console.log(`${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Todo${utils.COLORS.reset}`);
|
|
251
|
-
|
|
252
|
-
displayTodos(todos, {
|
|
253
|
-
showCompleted: !parsedArgs.pending,
|
|
254
|
-
showIndex: true
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// Summary
|
|
258
|
-
const pending = todos.filter(t => !t.completed).length;
|
|
259
|
-
const completed = todos.filter(t => t.completed).length;
|
|
260
|
-
utils.print.dim(`${pending} pending, ${completed} completed`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Add a new todo
|
|
265
|
-
*/
|
|
266
|
-
function addTodo(args) {
|
|
267
|
-
const rawText = args.join(' ').trim();
|
|
268
|
-
|
|
269
|
-
if (!rawText) {
|
|
270
|
-
utils.print.error('Please provide a todo text');
|
|
271
|
-
utils.print.dim('Usage: bootspring todo add "Your task"');
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Validate and sanitize todo text
|
|
276
|
-
const validation = validateTodoText(rawText);
|
|
277
|
-
if (!validation.valid) {
|
|
278
|
-
utils.print.error(`Invalid todo text: ${validation.error}`);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const text = validation.sanitized;
|
|
282
|
-
|
|
283
|
-
const content = readTodoFile();
|
|
284
|
-
const lines = content.split('\n');
|
|
285
|
-
|
|
286
|
-
// Find the "Pending" section or add after first heading
|
|
287
|
-
let insertIndex = -1;
|
|
288
|
-
for (let i = 0; i < lines.length; i++) {
|
|
289
|
-
if (lines[i].match(/^##?\s*(Pending|In Progress|Todo)/i)) {
|
|
290
|
-
insertIndex = i + 1;
|
|
291
|
-
// Skip empty lines after heading
|
|
292
|
-
while (insertIndex < lines.length && lines[insertIndex].trim() === '') {
|
|
293
|
-
insertIndex++;
|
|
294
|
-
}
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// If no section found, add after first heading
|
|
300
|
-
if (insertIndex === -1) {
|
|
301
|
-
for (let i = 0; i < lines.length; i++) {
|
|
302
|
-
if (lines[i].startsWith('#')) {
|
|
303
|
-
insertIndex = i + 1;
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// If still no good place, add at the end
|
|
310
|
-
if (insertIndex === -1) {
|
|
311
|
-
insertIndex = lines.length;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Insert the new todo
|
|
315
|
-
const newTodo = `- [ ] ${text}`;
|
|
316
|
-
lines.splice(insertIndex, 0, newTodo);
|
|
317
|
-
|
|
318
|
-
saveTodoContent(lines.join('\n'));
|
|
319
|
-
|
|
320
|
-
utils.print.success(`Added: ${text}`);
|
|
321
|
-
|
|
322
|
-
// Show count
|
|
323
|
-
const todos = parseTodos(lines.join('\n'));
|
|
324
|
-
const pending = todos.filter(t => !t.completed).length;
|
|
325
|
-
utils.print.dim(`${pending} pending todos`);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Mark todo as done
|
|
330
|
-
*/
|
|
331
|
-
function doneTodo(args) {
|
|
332
|
-
const indexStr = args[0];
|
|
333
|
-
|
|
334
|
-
if (!indexStr) {
|
|
335
|
-
utils.print.error('Please provide a todo number');
|
|
336
|
-
utils.print.dim('Usage: bootspring todo done <number>');
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Validate numeric ID
|
|
341
|
-
const validation = validateNumericId(indexStr, { min: 1 });
|
|
342
|
-
if (!validation.valid) {
|
|
343
|
-
utils.print.error(`Invalid todo number: ${validation.error}`);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const index = validation.value;
|
|
347
|
-
|
|
348
|
-
const content = readTodoFile();
|
|
349
|
-
const todos = parseTodos(content);
|
|
350
|
-
|
|
351
|
-
const todo = todos.find(t => t.index === index);
|
|
352
|
-
if (!todo) {
|
|
353
|
-
utils.print.error(`Todo #${index} not found`);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (todo.completed) {
|
|
358
|
-
utils.print.warning(`Todo #${index} is already completed`);
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Update the line
|
|
363
|
-
const lines = content.split('\n');
|
|
364
|
-
const originalLine = lines[todo.line];
|
|
365
|
-
lines[todo.line] = originalLine.replace('[ ]', '[x]');
|
|
366
|
-
|
|
367
|
-
saveTodoContent(lines.join('\n'));
|
|
368
|
-
|
|
369
|
-
utils.print.success(`Completed: ${todo.text}`);
|
|
370
|
-
|
|
371
|
-
// Show remaining
|
|
372
|
-
const remaining = todos.filter(t => !t.completed && t.index !== index).length;
|
|
373
|
-
utils.print.dim(`${remaining} pending todos remaining`);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Undo a completed todo
|
|
378
|
-
*/
|
|
379
|
-
function undoTodo(args) {
|
|
380
|
-
const indexStr = args[0];
|
|
381
|
-
|
|
382
|
-
if (!indexStr) {
|
|
383
|
-
utils.print.error('Please provide a todo number');
|
|
384
|
-
utils.print.dim('Usage: bootspring todo undo <number>');
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Validate numeric ID
|
|
389
|
-
const validation = validateNumericId(indexStr, { min: 1 });
|
|
390
|
-
if (!validation.valid) {
|
|
391
|
-
utils.print.error(`Invalid todo number: ${validation.error}`);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
const index = validation.value;
|
|
395
|
-
|
|
396
|
-
const content = readTodoFile();
|
|
397
|
-
const todos = parseTodos(content);
|
|
398
|
-
|
|
399
|
-
const todo = todos.find(t => t.index === index);
|
|
400
|
-
if (!todo) {
|
|
401
|
-
utils.print.error(`Todo #${index} not found`);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (!todo.completed) {
|
|
406
|
-
utils.print.warning(`Todo #${index} is not completed`);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Update the line
|
|
411
|
-
const lines = content.split('\n');
|
|
412
|
-
const originalLine = lines[todo.line];
|
|
413
|
-
lines[todo.line] = originalLine.replace(/\[[xX]\]/, '[ ]');
|
|
414
|
-
|
|
415
|
-
saveTodoContent(lines.join('\n'));
|
|
416
|
-
|
|
417
|
-
utils.print.success(`Reopened: ${todo.text}`);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Remove a todo
|
|
422
|
-
*/
|
|
423
|
-
function removeTodo(args) {
|
|
424
|
-
const indexStr = args[0];
|
|
425
|
-
|
|
426
|
-
if (!indexStr) {
|
|
427
|
-
utils.print.error('Please provide a todo number');
|
|
428
|
-
utils.print.dim('Usage: bootspring todo remove <number>');
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Validate numeric ID
|
|
433
|
-
const validation = validateNumericId(indexStr, { min: 1 });
|
|
434
|
-
if (!validation.valid) {
|
|
435
|
-
utils.print.error(`Invalid todo number: ${validation.error}`);
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const index = validation.value;
|
|
439
|
-
|
|
440
|
-
const content = readTodoFile();
|
|
441
|
-
const todos = parseTodos(content);
|
|
442
|
-
|
|
443
|
-
const todo = todos.find(t => t.index === index);
|
|
444
|
-
if (!todo) {
|
|
445
|
-
utils.print.error(`Todo #${index} not found`);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Remove the line
|
|
450
|
-
const lines = content.split('\n');
|
|
451
|
-
lines.splice(todo.line, 1);
|
|
452
|
-
|
|
453
|
-
saveTodoContent(lines.join('\n'));
|
|
454
|
-
|
|
455
|
-
utils.print.success(`Removed: ${todo.text}`);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Clear completed todos
|
|
460
|
-
*/
|
|
461
|
-
function clearCompleted() {
|
|
462
|
-
const content = readTodoFile();
|
|
463
|
-
const todos = parseTodos(content);
|
|
464
|
-
const completedTodos = todos.filter(t => t.completed);
|
|
465
|
-
|
|
466
|
-
if (completedTodos.length === 0) {
|
|
467
|
-
utils.print.info('No completed todos to clear');
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Remove completed lines (in reverse order to maintain indices)
|
|
472
|
-
const lines = content.split('\n');
|
|
473
|
-
const linesToRemove = completedTodos.map(t => t.line).sort((a, b) => b - a);
|
|
474
|
-
|
|
475
|
-
for (const lineIndex of linesToRemove) {
|
|
476
|
-
lines.splice(lineIndex, 1);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
saveTodoContent(lines.join('\n'));
|
|
480
|
-
|
|
481
|
-
utils.print.success(`Cleared ${completedTodos.length} completed todos`);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Show todo help
|
|
486
|
-
*/
|
|
487
|
-
function showHelp() {
|
|
488
|
-
console.log(`
|
|
489
|
-
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Todo${utils.COLORS.reset}
|
|
490
|
-
${utils.COLORS.dim}Simple and powerful todo management${utils.COLORS.reset}
|
|
491
|
-
|
|
492
|
-
${utils.COLORS.bold}Usage:${utils.COLORS.reset}
|
|
493
|
-
bootspring todo <command> [args]
|
|
494
|
-
|
|
495
|
-
${utils.COLORS.bold}Commands:${utils.COLORS.reset}
|
|
496
|
-
${utils.COLORS.cyan}list${utils.COLORS.reset} List all todos
|
|
497
|
-
${utils.COLORS.cyan}add${utils.COLORS.reset} <text> Add a new todo
|
|
498
|
-
${utils.COLORS.cyan}done${utils.COLORS.reset} <number> Mark todo as completed
|
|
499
|
-
${utils.COLORS.cyan}undo${utils.COLORS.reset} <number> Reopen a completed todo
|
|
500
|
-
${utils.COLORS.cyan}remove${utils.COLORS.reset} <number> Remove a todo
|
|
501
|
-
${utils.COLORS.cyan}clear${utils.COLORS.reset} Clear all completed todos
|
|
502
|
-
|
|
503
|
-
${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
504
|
-
bootspring todo add "Implement user auth"
|
|
505
|
-
bootspring todo done 1
|
|
506
|
-
bootspring todo list --pending
|
|
507
|
-
bootspring todo clear
|
|
508
|
-
`);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Run todo command
|
|
513
|
-
*/
|
|
514
|
-
async function run(args) {
|
|
515
|
-
const subcommand = args[0] || 'list';
|
|
516
|
-
const subargs = args.slice(1);
|
|
517
|
-
|
|
518
|
-
switch (subcommand) {
|
|
519
|
-
case 'list':
|
|
520
|
-
case 'ls':
|
|
521
|
-
listTodos(subargs);
|
|
522
|
-
break;
|
|
523
|
-
|
|
524
|
-
case 'add':
|
|
525
|
-
case 'a':
|
|
526
|
-
case 'new':
|
|
527
|
-
addTodo(subargs);
|
|
528
|
-
break;
|
|
529
|
-
|
|
530
|
-
case 'done':
|
|
531
|
-
case 'd':
|
|
532
|
-
case 'complete':
|
|
533
|
-
case 'check':
|
|
534
|
-
doneTodo(subargs);
|
|
535
|
-
break;
|
|
536
|
-
|
|
537
|
-
case 'undo':
|
|
538
|
-
case 'u':
|
|
539
|
-
case 'reopen':
|
|
540
|
-
undoTodo(subargs);
|
|
541
|
-
break;
|
|
542
|
-
|
|
543
|
-
case 'remove':
|
|
544
|
-
case 'rm':
|
|
545
|
-
case 'delete':
|
|
546
|
-
removeTodo(subargs);
|
|
547
|
-
break;
|
|
548
|
-
|
|
549
|
-
case 'clear':
|
|
550
|
-
clearCompleted();
|
|
551
|
-
break;
|
|
552
|
-
|
|
553
|
-
case 'help':
|
|
554
|
-
case '-h':
|
|
555
|
-
case '--help':
|
|
556
|
-
showHelp();
|
|
557
|
-
break;
|
|
558
|
-
|
|
559
|
-
default:
|
|
560
|
-
// If it looks like a todo text, treat as add
|
|
561
|
-
if (subcommand && !subcommand.startsWith('-')) {
|
|
562
|
-
addTodo([subcommand, ...subargs]);
|
|
563
|
-
} else {
|
|
564
|
-
utils.print.error(`Unknown subcommand: ${subcommand}`);
|
|
565
|
-
showHelp();
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Smart wrapper that detects if called with file path or CLI args
|
|
571
|
-
function addTodoSmart(firstArg, secondArg) {
|
|
572
|
-
if (typeof firstArg === 'string' && typeof secondArg === 'string') {
|
|
573
|
-
// Utility mode: addTodo(filePath, text)
|
|
574
|
-
return addTodoUtil(firstArg, secondArg);
|
|
575
|
-
} else if (Array.isArray(firstArg)) {
|
|
576
|
-
// CLI mode: addTodo(args)
|
|
577
|
-
return addTodo(firstArg);
|
|
578
|
-
} else {
|
|
579
|
-
// Default CLI mode with single arg treated as array
|
|
580
|
-
return addTodo([firstArg, secondArg].filter(Boolean));
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function removeTodoSmart(firstArg, secondArg) {
|
|
585
|
-
if (typeof firstArg === 'string' && typeof secondArg === 'number') {
|
|
586
|
-
// Utility mode: removeTodo(filePath, index)
|
|
587
|
-
return removeTodoUtil(firstArg, secondArg);
|
|
588
|
-
} else if (Array.isArray(firstArg)) {
|
|
589
|
-
// CLI mode: removeTodo(args)
|
|
590
|
-
return removeTodo(firstArg);
|
|
591
|
-
} else {
|
|
592
|
-
return removeTodo([firstArg]);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function listTodosSmart(firstArg) {
|
|
597
|
-
if (typeof firstArg === 'string' && (firstArg.includes('/') || firstArg.includes('\\'))) {
|
|
598
|
-
// Utility mode: listTodos(filePath)
|
|
599
|
-
return listTodosUtil(firstArg);
|
|
600
|
-
} else if (Array.isArray(firstArg)) {
|
|
601
|
-
// CLI mode: listTodos(args)
|
|
602
|
-
return listTodos(firstArg);
|
|
603
|
-
} else {
|
|
604
|
-
return listTodos([firstArg].filter(Boolean));
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
module.exports = {
|
|
609
|
-
// CLI command runner
|
|
610
|
-
run,
|
|
611
|
-
|
|
612
|
-
// CLI-focused internal functions
|
|
613
|
-
parseTodos,
|
|
614
|
-
doneTodo,
|
|
615
|
-
undoTodo,
|
|
616
|
-
clearCompleted,
|
|
617
|
-
|
|
618
|
-
// Utility functions (for programmatic/test use)
|
|
619
|
-
parseTodoFile,
|
|
620
|
-
writeTodoFile,
|
|
621
|
-
markDone,
|
|
622
|
-
markUndone,
|
|
623
|
-
clearTodos,
|
|
624
|
-
|
|
625
|
-
// Smart wrappers that work in both modes
|
|
626
|
-
addTodo: addTodoSmart,
|
|
627
|
-
removeTodo: removeTodoSmart,
|
|
628
|
-
listTodos: listTodosSmart
|
|
629
|
-
};
|