@qodalis/cli-todo 2.0.0-beta.8 → 2.0.0-rc.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qodalis/cli-todo",
3
- "version": "2.0.0-beta.8",
3
+ "version": "2.0.0-rc.1",
4
4
  "description": "@qodalis/cli extension for managing TODO tasks with add, list, complete, and remove commands.",
5
5
  "author": "Nicolae Lupei, Qodalis Solutions",
6
6
  "license": "MIT",
@@ -20,7 +20,7 @@
20
20
  "umd": "./umd/index.global.js",
21
21
  "unpkg": "./umd/index.global.js",
22
22
  "dependencies": {
23
- "@qodalis/cli-core": "2.0.0-beta.8"
23
+ "@qodalis/cli-core": "2.0.0-rc.1"
24
24
  },
25
25
  "sideEffects": false,
26
26
  "main": "./public-api.js",
package/public-api.d.mts CHANGED
@@ -1,5 +1,4 @@
1
- import * as _qodalis_cli_core from '@qodalis/cli-core';
2
- import { ICliCommandProcessor, CliProcessorMetadata, CliStateConfiguration, CliProcessCommand, ICliExecutionContext, ICliCompletionProvider, ICliCompletionContext, ICliModule } from '@qodalis/cli-core';
1
+ import { ICliCommandProcessor, CliProcessorMetadata, ICliConfigurationOption, CliStateConfiguration, CliProcessCommand, ICliExecutionContext, ICliCompletionProvider, ICliCompletionContext, ICliModule } from '@qodalis/cli-core';
3
2
 
4
3
  type TodoItem = {
5
4
  id: number;
@@ -18,13 +17,13 @@ declare function lineThroughText(text: string): string;
18
17
  declare class CliTodoCommandProcessor implements ICliCommandProcessor {
19
18
  command: string;
20
19
  description: string;
21
- author: _qodalis_cli_core.ICliCommandAuthor;
20
+ author: any;
22
21
  version: string;
23
22
  processors?: ICliCommandProcessor[];
24
23
  metadata?: CliProcessorMetadata;
24
+ configurationOptions?: ICliConfigurationOption[];
25
25
  stateConfiguration?: CliStateConfiguration;
26
26
  private todos;
27
- private nextId;
28
27
  getTodos(): TodoItem[];
29
28
  constructor();
30
29
  processCommand(command: CliProcessCommand, context: ICliExecutionContext): Promise<void>;
@@ -33,7 +32,12 @@ declare class CliTodoCommandProcessor implements ICliCommandProcessor {
33
32
  private formatTodoLine;
34
33
  private registerSubProcessors;
35
34
  private loadFromOldStorage;
36
- private saveToStorage;
35
+ /**
36
+ * Change the list and save it, atomically: `change` gets the latest saved
37
+ * list (another tab may have changed it) to edit in place, or returns a
38
+ * new one.
39
+ */
40
+ private changeTodos;
37
41
  }
38
42
 
39
43
  declare class CliTodoIdCompletionProvider implements ICliCompletionProvider {
@@ -45,4 +49,5 @@ declare class CliTodoIdCompletionProvider implements ICliCompletionProvider {
45
49
 
46
50
  declare const todoModule: ICliModule;
47
51
 
48
- export { CliTodoCommandProcessor, CliTodoIdCompletionProvider, type TodoItem, formatRelativeDate, isOverdue, lineThroughText, migrateTodoItem, parseDueDate, todoModule };
52
+ export { CliTodoCommandProcessor, CliTodoIdCompletionProvider, formatRelativeDate, isOverdue, lineThroughText, migrateTodoItem, parseDueDate, todoModule };
53
+ export type { TodoItem };
package/public-api.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import * as _qodalis_cli_core from '@qodalis/cli-core';
2
- import { ICliCommandProcessor, CliProcessorMetadata, CliStateConfiguration, CliProcessCommand, ICliExecutionContext, ICliCompletionProvider, ICliCompletionContext, ICliModule } from '@qodalis/cli-core';
1
+ import { ICliCommandProcessor, CliProcessorMetadata, ICliConfigurationOption, CliStateConfiguration, CliProcessCommand, ICliExecutionContext, ICliCompletionProvider, ICliCompletionContext, ICliModule } from '@qodalis/cli-core';
3
2
 
4
3
  type TodoItem = {
5
4
  id: number;
@@ -18,13 +17,13 @@ declare function lineThroughText(text: string): string;
18
17
  declare class CliTodoCommandProcessor implements ICliCommandProcessor {
19
18
  command: string;
20
19
  description: string;
21
- author: _qodalis_cli_core.ICliCommandAuthor;
20
+ author: any;
22
21
  version: string;
23
22
  processors?: ICliCommandProcessor[];
24
23
  metadata?: CliProcessorMetadata;
24
+ configurationOptions?: ICliConfigurationOption[];
25
25
  stateConfiguration?: CliStateConfiguration;
26
26
  private todos;
27
- private nextId;
28
27
  getTodos(): TodoItem[];
29
28
  constructor();
30
29
  processCommand(command: CliProcessCommand, context: ICliExecutionContext): Promise<void>;
@@ -33,7 +32,12 @@ declare class CliTodoCommandProcessor implements ICliCommandProcessor {
33
32
  private formatTodoLine;
34
33
  private registerSubProcessors;
35
34
  private loadFromOldStorage;
36
- private saveToStorage;
35
+ /**
36
+ * Change the list and save it, atomically: `change` gets the latest saved
37
+ * list (another tab may have changed it) to edit in place, or returns a
38
+ * new one.
39
+ */
40
+ private changeTodos;
37
41
  }
38
42
 
39
43
  declare class CliTodoIdCompletionProvider implements ICliCompletionProvider {
@@ -45,4 +49,5 @@ declare class CliTodoIdCompletionProvider implements ICliCompletionProvider {
45
49
 
46
50
  declare const todoModule: ICliModule;
47
51
 
48
- export { CliTodoCommandProcessor, CliTodoIdCompletionProvider, type TodoItem, formatRelativeDate, isOverdue, lineThroughText, migrateTodoItem, parseDueDate, todoModule };
52
+ export { CliTodoCommandProcessor, CliTodoIdCompletionProvider, formatRelativeDate, isOverdue, lineThroughText, migrateTodoItem, parseDueDate, todoModule };
53
+ export type { TodoItem };
package/public-api.js CHANGED
@@ -89,7 +89,7 @@ function lineThroughText(text) {
89
89
  }
90
90
 
91
91
  // src/lib/version.ts
92
- var LIBRARY_VERSION = "2.0.0-beta.8";
92
+ var LIBRARY_VERSION = "2.0.0-rc.1";
93
93
  var API_VERSION = 2;
94
94
 
95
95
  // src/lib/processors/cli-todo-command-processor.ts
@@ -105,13 +105,28 @@ var CliTodoCommandProcessor = class {
105
105
  requiredCoreVersion: ">=2.0.0 <3.0.0",
106
106
  requiredCliVersion: ">=2.0.0 <3.0.0"
107
107
  };
108
+ this.configurationOptions = [
109
+ {
110
+ key: "showCompletedFirst",
111
+ label: "Show Completed First",
112
+ description: "List completed tasks before pending ones",
113
+ type: "boolean",
114
+ defaultValue: false
115
+ },
116
+ {
117
+ key: "confirmBeforeDelete",
118
+ label: "Confirm Before Delete",
119
+ description: "Ask for confirmation before removing tasks",
120
+ type: "boolean",
121
+ defaultValue: false
122
+ }
123
+ ];
108
124
  this.stateConfiguration = {
109
125
  initialState: {
110
126
  todos: this.loadFromOldStorage() ?? []
111
127
  }
112
128
  };
113
129
  this.todos = [];
114
- this.nextId = 1;
115
130
  this.registerSubProcessors();
116
131
  }
117
132
  getTodos() {
@@ -121,26 +136,26 @@ var CliTodoCommandProcessor = class {
121
136
  context.executor.showHelp(command, context);
122
137
  }
123
138
  writeDescription(context) {
124
- const { writer } = context;
125
- writer.writeln(this.description);
139
+ const { writer, translator: t } = context;
140
+ writer.writeln(t.t("cli.todo.description", this.description));
126
141
  writer.writeln();
127
- writer.writeln(writer.wrapInColor("Commands:", cliCore.CliForegroundColor.Yellow));
128
- writer.writeln(` ${writer.wrapInColor("todo ls", cliCore.CliForegroundColor.Cyan)} List all tasks`);
129
- writer.writeln(` ${writer.wrapInColor("todo ls --pending", cliCore.CliForegroundColor.Cyan)} List pending tasks only`);
130
- writer.writeln(` ${writer.wrapInColor("todo ls --completed", cliCore.CliForegroundColor.Cyan)} List completed tasks only`);
131
- writer.writeln(` ${writer.wrapInColor("todo ls --overdue", cliCore.CliForegroundColor.Cyan)} List overdue tasks only`);
132
- writer.writeln(` ${writer.wrapInColor("todo add <text>", cliCore.CliForegroundColor.Cyan)} Add a new task`);
133
- writer.writeln(` ${writer.wrapInColor("todo add <text> --due <date>", cliCore.CliForegroundColor.Cyan)} Add with due date`);
134
- writer.writeln(` ${writer.wrapInColor("todo edit <id> <new text>", cliCore.CliForegroundColor.Cyan)} Edit a task`);
135
- writer.writeln(` ${writer.wrapInColor("todo done <id>", cliCore.CliForegroundColor.Cyan)} Mark a task as done`);
136
- writer.writeln(` ${writer.wrapInColor("todo toggle <id>", cliCore.CliForegroundColor.Cyan)} Toggle done/undone`);
137
- writer.writeln(` ${writer.wrapInColor("todo rm <id>", cliCore.CliForegroundColor.Cyan)} Remove a task`);
138
- writer.writeln(` ${writer.wrapInColor("todo rm --all", cliCore.CliForegroundColor.Cyan)} Remove all tasks`);
142
+ writer.writeln(writer.wrapInColor(t.t("cli.todo.help.commands", "Commands:"), cliCore.CliForegroundColor.Yellow));
143
+ writer.writeln(` ${writer.wrapInColor("todo ls", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.list_all", "List all tasks")}`);
144
+ writer.writeln(` ${writer.wrapInColor("todo ls --pending", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.list_pending", "List pending tasks only")}`);
145
+ writer.writeln(` ${writer.wrapInColor("todo ls --completed", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.list_completed", "List completed tasks only")}`);
146
+ writer.writeln(` ${writer.wrapInColor("todo ls --overdue", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.list_overdue", "List overdue tasks only")}`);
147
+ writer.writeln(` ${writer.wrapInColor("todo add <text>", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.add", "Add a new task")}`);
148
+ writer.writeln(` ${writer.wrapInColor("todo add <text> --due <date>", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.add_due", "Add with due date")}`);
149
+ writer.writeln(` ${writer.wrapInColor("todo edit <id> <new text>", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.edit", "Edit a task")}`);
150
+ writer.writeln(` ${writer.wrapInColor("todo done <id>", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.done", "Mark a task as done")}`);
151
+ writer.writeln(` ${writer.wrapInColor("todo toggle <id>", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.toggle", "Toggle done/undone")}`);
152
+ writer.writeln(` ${writer.wrapInColor("todo rm <id>", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.remove", "Remove a task")}`);
153
+ writer.writeln(` ${writer.wrapInColor("todo rm --all", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.todo.help.remove_all", "Remove all tasks")}`);
139
154
  writer.writeln();
140
- writer.writeln(writer.wrapInColor("Due date formats:", cliCore.CliForegroundColor.Yellow));
155
+ writer.writeln(writer.wrapInColor(t.t("cli.todo.help.due_formats", "Due date formats:"), cliCore.CliForegroundColor.Yellow));
141
156
  writer.writeln(` today, tomorrow, monday..sunday, next week, next month, YYYY-MM-DD`);
142
157
  writer.writeln();
143
- writer.writeln(writer.wrapInColor("Examples:", cliCore.CliForegroundColor.Yellow));
158
+ writer.writeln(writer.wrapInColor(t.t("cli.common.examples", "Examples:"), cliCore.CliForegroundColor.Yellow));
144
159
  writer.writeln(` todo add Buy groceries --due tomorrow`);
145
160
  writer.writeln(` todo ls --pending`);
146
161
  writer.writeln(` todo edit 3 Buy organic groceries`);
@@ -150,7 +165,6 @@ var CliTodoCommandProcessor = class {
150
165
  async initialize(context) {
151
166
  context.state.select((x) => x["todos"]).subscribe((todos) => {
152
167
  this.todos = (todos || []).map(migrateTodoItem);
153
- this.nextId = this.todos.length > 0 ? Math.max(...this.todos.map((t) => t.id)) + 1 : 1;
154
168
  });
155
169
  }
156
170
  formatTodoLine(todo, writer) {
@@ -209,6 +223,10 @@ var CliTodoCommandProcessor = class {
209
223
  } else if (args["overdue"]) {
210
224
  filtered = filtered.filter((t) => isOverdue(t));
211
225
  }
226
+ const showCompletedFirst = cliCore.getPluginConfigValue(context, "todo", "showCompletedFirst", false);
227
+ if (showCompletedFirst) {
228
+ filtered.sort((a, b) => a.completed === b.completed ? 0 : a.completed ? -1 : 1);
229
+ }
212
230
  const doneCount = this.todos.filter((t) => t.completed).length;
213
231
  context.writer.writeln(
214
232
  context.writer.wrapInColor(
@@ -243,7 +261,7 @@ var CliTodoCommandProcessor = class {
243
261
  }
244
262
  ],
245
263
  processCommand: async (command, context) => {
246
- const text = command.value;
264
+ const text = cliCore.joinShellWords(command.value ?? "");
247
265
  if (!text) {
248
266
  context.writer.writeError("Please provide a task description.");
249
267
  return;
@@ -260,15 +278,17 @@ var CliTodoCommandProcessor = class {
260
278
  }
261
279
  dueDate = parsed;
262
280
  }
263
- const newItem = {
264
- id: this.nextId++,
265
- text,
266
- completed: false,
267
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
268
- dueDate
269
- };
270
- this.todos.push(newItem);
271
- await this.saveToStorage(context);
281
+ let newItem;
282
+ await this.changeTodos(context, (todos) => {
283
+ newItem = {
284
+ id: nextIdOf(todos),
285
+ text,
286
+ completed: false,
287
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
288
+ dueDate
289
+ };
290
+ todos.push(newItem);
291
+ });
272
292
  let msg = `Added: "${text}"`;
273
293
  if (dueDate) {
274
294
  msg += ` (${formatRelativeDate(dueDate)})`;
@@ -292,10 +312,8 @@ var CliTodoCommandProcessor = class {
292
312
  }
293
313
  ],
294
314
  processCommand: async (command, context) => {
295
- const raw = command.value || "";
296
- const spaceIndex = raw.indexOf(" ");
297
- const idStr = spaceIndex === -1 ? raw : raw.substring(0, spaceIndex);
298
- const newText = spaceIndex === -1 ? void 0 : raw.substring(spaceIndex + 1).trim();
315
+ const [idStr = "", ...words] = command.positionals ?? [];
316
+ const newText = words.length ? words.join(" ") : void 0;
299
317
  const id = parseInt(idStr, 10);
300
318
  if (isNaN(id)) {
301
319
  context.writer.writeError("Usage: todo edit <id> <new text> [--due <date>]");
@@ -307,32 +325,33 @@ var CliTodoCommandProcessor = class {
307
325
  return;
308
326
  }
309
327
  const dueArg = command.args["due"];
310
- let changed = false;
328
+ const edit = {};
311
329
  if (newText) {
312
- todo.text = newText;
313
- changed = true;
330
+ edit.text = newText;
314
331
  }
315
332
  if (dueArg) {
316
333
  if (dueArg === "none") {
317
- todo.dueDate = void 0;
318
- changed = true;
334
+ edit.dueDate = void 0;
319
335
  } else {
320
336
  const parsed = parseDueDate(dueArg);
321
337
  if (!parsed) {
322
338
  context.writer.writeError(`Invalid due date: "${dueArg}".`);
323
339
  return;
324
340
  }
325
- todo.dueDate = parsed;
326
- changed = true;
341
+ edit.dueDate = parsed;
327
342
  }
328
343
  }
329
- if (!changed) {
344
+ if (Object.keys(edit).length === 0) {
330
345
  context.writer.writeError("Nothing to update. Provide new text or --due.");
331
346
  return;
332
347
  }
333
- await this.saveToStorage(context);
348
+ let updated;
349
+ await this.changeTodos(context, (todos) => {
350
+ const item = todos.find((t) => t.id === id);
351
+ if (item) updated = Object.assign(item, edit);
352
+ });
334
353
  context.writer.writeSuccess(`Updated task #${id}.`);
335
- context.process.output(todo);
354
+ context.process.output(updated ?? { ...todo, ...edit });
336
355
  }
337
356
  },
338
357
  // ── done ────────────────────────────────────────────
@@ -343,7 +362,7 @@ var CliTodoCommandProcessor = class {
343
362
  acceptsRawInput: true,
344
363
  valueRequired: true,
345
364
  processCommand: async (command, context) => {
346
- const id = parseInt(command.value || "", 10);
365
+ const id = parseInt(command.positionals?.[0] ?? "", 10);
347
366
  if (isNaN(id)) {
348
367
  context.writer.writeError("Please provide a valid task ID.");
349
368
  return;
@@ -357,11 +376,16 @@ var CliTodoCommandProcessor = class {
357
376
  context.writer.writeInfo(`Task #${id} is already done.`);
358
377
  return;
359
378
  }
360
- todo.completed = true;
361
- todo.completedAt = (/* @__PURE__ */ new Date()).toISOString();
362
- await this.saveToStorage(context);
379
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
380
+ await this.changeTodos(context, (todos) => {
381
+ const item = todos.find((t) => t.id === id);
382
+ if (item) {
383
+ item.completed = true;
384
+ item.completedAt = completedAt;
385
+ }
386
+ });
363
387
  context.writer.writeSuccess(`Done: "${todo.text}"`);
364
- context.process.output(todo);
388
+ context.process.output({ ...todo, completed: true, completedAt });
365
389
  }
366
390
  },
367
391
  // ── toggle ──────────────────────────────────────────
@@ -371,7 +395,7 @@ var CliTodoCommandProcessor = class {
371
395
  acceptsRawInput: true,
372
396
  valueRequired: true,
373
397
  processCommand: async (command, context) => {
374
- const id = parseInt(command.value || "", 10);
398
+ const id = parseInt(command.positionals?.[0] ?? "", 10);
375
399
  if (isNaN(id)) {
376
400
  context.writer.writeError("Please provide a valid task ID.");
377
401
  return;
@@ -381,12 +405,18 @@ var CliTodoCommandProcessor = class {
381
405
  context.writer.writeError(`Task #${id} not found.`);
382
406
  return;
383
407
  }
384
- todo.completed = !todo.completed;
385
- todo.completedAt = todo.completed ? (/* @__PURE__ */ new Date()).toISOString() : void 0;
386
- await this.saveToStorage(context);
387
- const status = todo.completed ? "done" : "reopened";
408
+ const completed = !todo.completed;
409
+ const completedAt = completed ? (/* @__PURE__ */ new Date()).toISOString() : void 0;
410
+ await this.changeTodos(context, (todos) => {
411
+ const item = todos.find((t) => t.id === id);
412
+ if (item) {
413
+ item.completed = completed;
414
+ item.completedAt = completedAt;
415
+ }
416
+ });
417
+ const status = completed ? "done" : "reopened";
388
418
  context.writer.writeSuccess(`Task #${id} marked as ${status}.`);
389
- context.process.output(todo);
419
+ context.process.output({ ...todo, completed, completedAt });
390
420
  }
391
421
  },
392
422
  // ── rm ──────────────────────────────────────────────
@@ -419,12 +449,11 @@ var CliTodoCommandProcessor = class {
419
449
  context.writer.writeInfo("Cancelled.");
420
450
  return;
421
451
  }
422
- this.todos = [];
423
- await this.saveToStorage(context);
452
+ await this.changeTodos(context, () => []);
424
453
  context.writer.writeSuccess("Removed all tasks.");
425
454
  return;
426
455
  }
427
- const id = parseInt(command.value, 10);
456
+ const id = parseInt(command.positionals?.[0] ?? "", 10);
428
457
  if (isNaN(id)) {
429
458
  context.writer.writeError("Please provide a valid task ID.");
430
459
  return;
@@ -434,8 +463,22 @@ var CliTodoCommandProcessor = class {
434
463
  context.writer.writeError(`Task #${id} not found.`);
435
464
  return;
436
465
  }
437
- const removed = this.todos.splice(index, 1)[0];
438
- await this.saveToStorage(context);
466
+ const confirmBeforeDelete = cliCore.getPluginConfigValue(context, "todo", "confirmBeforeDelete", false);
467
+ if (confirmBeforeDelete) {
468
+ const confirmed = await context.reader.readConfirm(
469
+ `Remove task #${id}: "${this.todos[index].text}"?`,
470
+ false
471
+ );
472
+ if (!confirmed) {
473
+ context.writer.writeInfo("Cancelled.");
474
+ return;
475
+ }
476
+ }
477
+ const removed = this.todos[index];
478
+ await this.changeTodos(
479
+ context,
480
+ (todos) => todos.filter((t) => t.id !== id)
481
+ );
439
482
  context.writer.writeSuccess(`Removed: "${removed.text}"`);
440
483
  }
441
484
  }
@@ -450,12 +493,24 @@ var CliTodoCommandProcessor = class {
450
493
  return [];
451
494
  }
452
495
  }
453
- async saveToStorage(context) {
454
- context.state.updateState({ todos: this.todos });
455
- await context.state.persist();
496
+ /**
497
+ * Change the list and save it, atomically: `change` gets the latest saved
498
+ * list (another tab may have changed it) to edit in place, or returns a
499
+ * new one.
500
+ */
501
+ async changeTodos(context, change) {
502
+ await context.state.update((state) => {
503
+ const todos = (state["todos"] ?? []).map(
504
+ (t) => ({ ...migrateTodoItem(t) })
505
+ );
506
+ return { todos: change(todos) ?? todos };
507
+ });
456
508
  localStorage.removeItem("todo-items");
457
509
  }
458
510
  };
511
+ function nextIdOf(todos) {
512
+ return todos.length > 0 ? Math.max(...todos.map((t) => t.id)) + 1 : 1;
513
+ }
459
514
 
460
515
  // src/lib/completion/cli-todo-id-completion-provider.ts
461
516
  var TODO_ID_SUBCOMMANDS = /* @__PURE__ */ new Set(["rm", "done", "complete", "toggle", "edit"]);