@mindtnv/todoist-cli 0.4.0 → 0.5.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.
- package/package.json +6 -6
- package/src/api/activity.ts +8 -0
- package/src/api/client.ts +214 -0
- package/src/api/comments.ts +18 -0
- package/src/api/completed.ts +15 -0
- package/src/api/labels.ts +18 -0
- package/src/api/projects.ts +22 -0
- package/src/api/sections.ts +20 -0
- package/src/api/stats.ts +38 -0
- package/src/api/tasks.ts +34 -0
- package/src/api/types.ts +202 -0
- package/src/cli/auth.ts +40 -0
- package/src/cli/commands/task/add.ts +328 -0
- package/src/cli/commands/task/complete.ts +62 -0
- package/src/cli/commands/task/delete.ts +62 -0
- package/src/cli/commands/task/helpers.ts +289 -0
- package/src/cli/commands/task/index.ts +27 -0
- package/src/cli/commands/task/list.ts +151 -0
- package/src/cli/commands/task/move.ts +49 -0
- package/src/cli/commands/task/reopen.ts +43 -0
- package/src/cli/commands/task/show.ts +115 -0
- package/src/cli/commands/task/update.ts +122 -0
- package/src/cli/comment.ts +83 -0
- package/src/cli/completed.ts +87 -0
- package/src/cli/completion.ts +360 -0
- package/src/cli/filter.ts +115 -0
- package/src/cli/index.ts +638 -0
- package/src/cli/label.ts +120 -0
- package/src/cli/log.ts +57 -0
- package/src/cli/matrix.ts +100 -0
- package/src/cli/plugin-loader.ts +38 -0
- package/src/cli/plugin.ts +289 -0
- package/src/cli/project.ts +172 -0
- package/src/cli/review.ts +116 -0
- package/src/cli/section.ts +98 -0
- package/src/cli/stats.ts +62 -0
- package/src/cli/template.ts +89 -0
- package/src/config/index.ts +229 -0
- package/src/plugins/api-proxy.ts +70 -0
- package/src/plugins/extension-registry.ts +53 -0
- package/src/plugins/hook-registry.ts +36 -0
- package/src/plugins/loader.ts +200 -0
- package/src/plugins/marketplace-types.ts +55 -0
- package/src/plugins/marketplace.ts +576 -0
- package/src/plugins/palette-registry.ts +21 -0
- package/src/plugins/storage.ts +101 -0
- package/src/plugins/types.ts +226 -0
- package/src/plugins/view-registry.ts +19 -0
- package/src/ui/App.tsx +234 -0
- package/src/ui/components/Breadcrumb.tsx +18 -0
- package/src/ui/components/CommandPalette.tsx +237 -0
- package/src/ui/components/ConfirmDialog.tsx +28 -0
- package/src/ui/components/EditTaskModal.tsx +484 -0
- package/src/ui/components/HelpOverlay.tsx +195 -0
- package/src/ui/components/InputPrompt.tsx +109 -0
- package/src/ui/components/LabelPicker.tsx +110 -0
- package/src/ui/components/ModalManager.tsx +275 -0
- package/src/ui/components/ProjectPicker.tsx +95 -0
- package/src/ui/components/Sidebar.tsx +282 -0
- package/src/ui/components/SortMenu.tsx +77 -0
- package/src/ui/components/StatusBar.tsx +67 -0
- package/src/ui/components/TaskList.tsx +258 -0
- package/src/ui/components/TaskRow.tsx +105 -0
- package/src/ui/hooks/useKeyboardHandler.ts +291 -0
- package/src/ui/hooks/useStatusMessage.ts +32 -0
- package/src/ui/hooks/useTaskOperations.ts +558 -0
- package/src/ui/hooks/useUndoSystem.ts +218 -0
- package/src/ui/views/ActivityView.tsx +213 -0
- package/src/ui/views/CompletedView.tsx +337 -0
- package/src/ui/views/StatsView.tsx +178 -0
- package/src/ui/views/TaskDetailView.tsx +438 -0
- package/src/ui/views/TasksView.tsx +851 -0
- package/src/utils/colors.ts +27 -0
- package/src/utils/date-format.ts +54 -0
- package/src/utils/errors.ts +159 -0
- package/src/utils/exit.ts +11 -0
- package/src/utils/format.ts +46 -0
- package/src/utils/open-url.ts +9 -0
- package/src/utils/output.ts +29 -0
- package/src/utils/quick-add.ts +202 -0
- package/src/utils/resolve.ts +359 -0
- package/src/utils/sorting.ts +27 -0
- package/src/utils/validation.ts +88 -0
- package/dist/index.js +0 -11355
package/src/cli/label.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getLabels, createLabel, updateLabel, deleteLabel } from "../api/labels.ts";
|
|
4
|
+
import { handleError } from "../utils/errors.ts";
|
|
5
|
+
import { cliExit } from "../utils/exit.ts";
|
|
6
|
+
import { ID_WIDTH } from "../utils/format.ts";
|
|
7
|
+
import { saveLastList, resolveLabelArg } from "../utils/resolve.ts";
|
|
8
|
+
|
|
9
|
+
const NAME_WIDTH = 25;
|
|
10
|
+
const COLOR_WIDTH = 12;
|
|
11
|
+
|
|
12
|
+
export function registerLabelCommand(program: Command): void {
|
|
13
|
+
const label = program
|
|
14
|
+
.command("label")
|
|
15
|
+
.description("Manage labels");
|
|
16
|
+
|
|
17
|
+
label
|
|
18
|
+
.command("list")
|
|
19
|
+
.description("List all labels")
|
|
20
|
+
.option("--json <fields>", "Output JSON with specified fields (comma-separated)")
|
|
21
|
+
.option("-q, --quiet", "Print only label IDs")
|
|
22
|
+
.action(async (opts: { json?: string; quiet?: boolean }) => {
|
|
23
|
+
try {
|
|
24
|
+
const labels = await getLabels();
|
|
25
|
+
|
|
26
|
+
if (opts.quiet) {
|
|
27
|
+
for (const l of labels) console.log(l.id);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (opts.json !== undefined) {
|
|
32
|
+
const fields = opts.json.split(",").map((f) => f.trim());
|
|
33
|
+
const data = labels.map((l) => {
|
|
34
|
+
const obj: Record<string, unknown> = {};
|
|
35
|
+
for (const f of fields) {
|
|
36
|
+
if (f in l) obj[f] = (l as unknown as Record<string, unknown>)[f];
|
|
37
|
+
}
|
|
38
|
+
return obj;
|
|
39
|
+
});
|
|
40
|
+
console.log(JSON.stringify(data, null, 2));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (labels.length === 0) {
|
|
45
|
+
console.log(chalk.dim("No labels found."));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const header = `${"#".padStart(3)} ${"ID".padEnd(ID_WIDTH)} ${"Name".padEnd(NAME_WIDTH)} ${"Color".padEnd(COLOR_WIDTH)} Favorite`;
|
|
50
|
+
console.log(chalk.bold(header));
|
|
51
|
+
console.log(chalk.dim("-".repeat(3 + 1 + ID_WIDTH + 1 + NAME_WIDTH + 1 + COLOR_WIDTH + 1 + 8)));
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < labels.length; i++) {
|
|
54
|
+
const l = labels[i]!;
|
|
55
|
+
const num = chalk.dim(String(i + 1).padStart(3));
|
|
56
|
+
const id = l.id.padEnd(ID_WIDTH);
|
|
57
|
+
const name = l.name.padEnd(NAME_WIDTH);
|
|
58
|
+
const color = l.color.padEnd(COLOR_WIDTH);
|
|
59
|
+
const fav = l.is_favorite ? chalk.yellow("*") : " ";
|
|
60
|
+
console.log(`${num} ${id} ${name} ${color} ${fav}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
saveLastList("label", labels.map(l => ({ id: l.id, label: l.name })));
|
|
64
|
+
} catch (err) {
|
|
65
|
+
handleError(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
label
|
|
70
|
+
.command("create")
|
|
71
|
+
.description("Create a new label")
|
|
72
|
+
.argument("<name>", "Label name")
|
|
73
|
+
.action(async (name: string) => {
|
|
74
|
+
try {
|
|
75
|
+
const result = await createLabel({ name });
|
|
76
|
+
console.log(chalk.green(`Label created: ${result.name} (${result.id})`));
|
|
77
|
+
} catch (err) {
|
|
78
|
+
handleError(err);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
label
|
|
83
|
+
.command("update")
|
|
84
|
+
.description("Update a label")
|
|
85
|
+
.argument("<id>", "Label ID")
|
|
86
|
+
.option("--name <name>", "New label name")
|
|
87
|
+
.option("--color <color>", "New color")
|
|
88
|
+
.action(async (rawId: string, opts: { name?: string; color?: string }) => {
|
|
89
|
+
try {
|
|
90
|
+
const id = await resolveLabelArg(rawId);
|
|
91
|
+
const params: Record<string, unknown> = {};
|
|
92
|
+
if (opts.name) params.name = opts.name;
|
|
93
|
+
if (opts.color) params.color = opts.color;
|
|
94
|
+
|
|
95
|
+
if (Object.keys(params).length === 0) {
|
|
96
|
+
console.error(chalk.red("No update options provided. Use --name or --color."));
|
|
97
|
+
cliExit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = await updateLabel(id, params);
|
|
101
|
+
console.log(chalk.green(`Label ${result.id} updated: ${result.name}`));
|
|
102
|
+
} catch (err) {
|
|
103
|
+
handleError(err);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
label
|
|
108
|
+
.command("delete")
|
|
109
|
+
.description("Delete a label")
|
|
110
|
+
.argument("<id>", "Label ID")
|
|
111
|
+
.action(async (rawId: string) => {
|
|
112
|
+
try {
|
|
113
|
+
const id = await resolveLabelArg(rawId);
|
|
114
|
+
await deleteLabel(id);
|
|
115
|
+
console.log(chalk.green(`Label ${id} deleted.`));
|
|
116
|
+
} catch (err) {
|
|
117
|
+
handleError(err);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
package/src/cli/log.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getActivity } from "../api/activity.ts";
|
|
4
|
+
import { padEnd } from "../utils/format.ts";
|
|
5
|
+
import { handleError } from "../utils/errors.ts";
|
|
6
|
+
|
|
7
|
+
function eventColor(eventType: string): (text: string) => string {
|
|
8
|
+
switch (eventType) {
|
|
9
|
+
case "completed": return chalk.green;
|
|
10
|
+
case "added":
|
|
11
|
+
case "created": return chalk.blue;
|
|
12
|
+
case "updated": return chalk.yellow;
|
|
13
|
+
case "deleted": return chalk.red;
|
|
14
|
+
default: return chalk.white;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatTimestamp(iso: string): string {
|
|
19
|
+
const d = new Date(iso);
|
|
20
|
+
return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false }) +
|
|
21
|
+
" " + d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function registerLogCommand(program: Command): void {
|
|
25
|
+
program
|
|
26
|
+
.command("log")
|
|
27
|
+
.description("Show activity log")
|
|
28
|
+
.option("-n, --limit <number>", "Number of events to show", "30")
|
|
29
|
+
.action(async (opts: { limit: string }) => {
|
|
30
|
+
try {
|
|
31
|
+
const events = await getActivity(parseInt(opts.limit, 10));
|
|
32
|
+
|
|
33
|
+
if (events.length === 0) {
|
|
34
|
+
console.log(chalk.dim("No activity found."));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(chalk.bold("Activity log:"));
|
|
39
|
+
console.log(chalk.dim("-".repeat(70)));
|
|
40
|
+
|
|
41
|
+
for (const e of events) {
|
|
42
|
+
const time = chalk.dim(formatTimestamp(e.event_date));
|
|
43
|
+
const colorFn = eventColor(e.event_type);
|
|
44
|
+
const type = padEnd(colorFn(e.event_type), 12);
|
|
45
|
+
const extra = e.extra_data && typeof e.extra_data === "object" && "content" in e.extra_data
|
|
46
|
+
? chalk.dim(` — ${String(e.extra_data.content)}`)
|
|
47
|
+
: `${e.object_type}`;
|
|
48
|
+
console.log(` ${time} ${type} ${extra}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log("");
|
|
52
|
+
console.log(chalk.dim(`Total: ${events.length} event${events.length === 1 ? "" : "s"}`));
|
|
53
|
+
} catch (err) {
|
|
54
|
+
handleError(err);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import type { Task, Priority } from "../api/types.ts";
|
|
4
|
+
import { getTasks } from "../api/tasks.ts";
|
|
5
|
+
import { padEnd, truncate } from "../utils/format.ts";
|
|
6
|
+
import { handleError } from "../utils/errors.ts";
|
|
7
|
+
|
|
8
|
+
const COL_WIDTH = 35;
|
|
9
|
+
|
|
10
|
+
function formatTaskLine(task: Task): string {
|
|
11
|
+
const content = truncate(task.content, COL_WIDTH - 4);
|
|
12
|
+
return ` ${content}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderQuadrant(title: string, tasks: Task[], color: (s: string) => string): string[] {
|
|
16
|
+
const lines: string[] = [];
|
|
17
|
+
lines.push(color(` ${title}`));
|
|
18
|
+
if (tasks.length === 0) {
|
|
19
|
+
lines.push(chalk.dim(" (empty)"));
|
|
20
|
+
} else {
|
|
21
|
+
for (const t of tasks.slice(0, 8)) {
|
|
22
|
+
lines.push(formatTaskLine(t));
|
|
23
|
+
}
|
|
24
|
+
if (tasks.length > 8) {
|
|
25
|
+
lines.push(chalk.dim(` +${tasks.length - 8} more`));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return lines;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function mergeColumns(left: string[], right: string[], colWidth: number): string[] {
|
|
32
|
+
const maxLen = Math.max(left.length, right.length);
|
|
33
|
+
const result: string[] = [];
|
|
34
|
+
for (let i = 0; i < maxLen; i++) {
|
|
35
|
+
const l = padEnd(left[i] ?? "", colWidth);
|
|
36
|
+
const r = padEnd(right[i] ?? "", colWidth);
|
|
37
|
+
result.push(`${l} ${chalk.dim("\u2502")} ${r}`);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function registerMatrixCommand(program: Command): void {
|
|
43
|
+
program
|
|
44
|
+
.command("matrix")
|
|
45
|
+
.description("Eisenhower matrix view")
|
|
46
|
+
.option("--today", "Only show tasks due today")
|
|
47
|
+
.action(async (opts: { today?: boolean }) => {
|
|
48
|
+
try {
|
|
49
|
+
let tasks: Task[];
|
|
50
|
+
if (opts.today) {
|
|
51
|
+
tasks = await getTasks({ filter: "today" });
|
|
52
|
+
} else {
|
|
53
|
+
tasks = await getTasks();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const buckets: Record<Priority, Task[]> = { 1: [], 2: [], 3: [], 4: [] };
|
|
57
|
+
for (const t of tasks) {
|
|
58
|
+
buckets[t.priority].push(t);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const totalWidth = COL_WIDTH * 2 + 3;
|
|
62
|
+
const hLine = chalk.dim("\u2500".repeat(COL_WIDTH));
|
|
63
|
+
const hSep = `${hLine}${chalk.dim("\u253C")}${hLine}`;
|
|
64
|
+
const topBorder = `${chalk.dim("\u250C")}${hLine}${chalk.dim("\u252C")}${hLine}${chalk.dim("\u2510")}`;
|
|
65
|
+
const midBorder = `${chalk.dim("\u251C")}${hSep.replace(/\u253C/, "\u253C")}${chalk.dim("\u2524")}`;
|
|
66
|
+
const botBorder = `${chalk.dim("\u2514")}${hLine}${chalk.dim("\u2534")}${hLine}${chalk.dim("\u2518")}`;
|
|
67
|
+
|
|
68
|
+
const header = chalk.dim(" ".repeat(Math.floor((totalWidth - 20) / 2))) + chalk.bold("Eisenhower Matrix");
|
|
69
|
+
console.log("");
|
|
70
|
+
console.log(header);
|
|
71
|
+
console.log("");
|
|
72
|
+
|
|
73
|
+
// Top quadrants: p4 (DO FIRST / urgent) | p3 (SCHEDULE)
|
|
74
|
+
const q1 = renderQuadrant("DO FIRST (p4)", buckets[4], chalk.red.bold);
|
|
75
|
+
const q2 = renderQuadrant("SCHEDULE (p3)", buckets[3], chalk.yellow.bold);
|
|
76
|
+
const topRows = mergeColumns(q1, q2, COL_WIDTH);
|
|
77
|
+
|
|
78
|
+
// Bottom quadrants: p2 (DELEGATE) | p1 (ELIMINATE / normal)
|
|
79
|
+
const q3 = renderQuadrant("DELEGATE (p2)", buckets[2], chalk.blue.bold);
|
|
80
|
+
const q4 = renderQuadrant("ELIMINATE (p1)", buckets[1], chalk.white.bold);
|
|
81
|
+
const botRows = mergeColumns(q3, q4, COL_WIDTH);
|
|
82
|
+
|
|
83
|
+
console.log(topBorder);
|
|
84
|
+
for (const row of topRows) {
|
|
85
|
+
console.log(`${chalk.dim("\u2502")}${row}${chalk.dim("\u2502")}`);
|
|
86
|
+
}
|
|
87
|
+
console.log(midBorder);
|
|
88
|
+
for (const row of botRows) {
|
|
89
|
+
console.log(`${chalk.dim("\u2502")}${row}${chalk.dim("\u2502")}`);
|
|
90
|
+
}
|
|
91
|
+
console.log(botBorder);
|
|
92
|
+
console.log("");
|
|
93
|
+
|
|
94
|
+
const total = tasks.length;
|
|
95
|
+
console.log(chalk.dim(`Total: ${total} task${total === 1 ? "" : "s"} — p4: ${buckets[4].length}, p3: ${buckets[3].length}, p2: ${buckets[2].length}, p1: ${buckets[1].length}`));
|
|
96
|
+
} catch (err) {
|
|
97
|
+
handleError(err);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { createHookRegistry } from "../plugins/hook-registry.ts";
|
|
3
|
+
import { createViewRegistry } from "../plugins/view-registry.ts";
|
|
4
|
+
import { createExtensionRegistry } from "../plugins/extension-registry.ts";
|
|
5
|
+
import { createPaletteRegistry } from "../plugins/palette-registry.ts";
|
|
6
|
+
import { loadPlugins, type LoadedPlugins } from "../plugins/loader.ts";
|
|
7
|
+
import type { HookRegistry } from "../plugins/types.ts";
|
|
8
|
+
|
|
9
|
+
let loadedPlugins: LoadedPlugins | null = null;
|
|
10
|
+
let hookRegistry: HookRegistry | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Load all plugins and register their CLI commands on the Commander program.
|
|
14
|
+
*/
|
|
15
|
+
export async function loadCliPlugins(program: Command): Promise<void> {
|
|
16
|
+
const hooks = createHookRegistry();
|
|
17
|
+
const views = createViewRegistry();
|
|
18
|
+
const extensions = createExtensionRegistry();
|
|
19
|
+
const palette = createPaletteRegistry();
|
|
20
|
+
|
|
21
|
+
const loaded = await loadPlugins(hooks, views, extensions, palette);
|
|
22
|
+
loadedPlugins = loaded;
|
|
23
|
+
hookRegistry = hooks;
|
|
24
|
+
|
|
25
|
+
for (const { plugin, ctx } of loaded.plugins) {
|
|
26
|
+
if (plugin.registerCommands) {
|
|
27
|
+
plugin.registerCommands(program, ctx);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getCliHookRegistry(): HookRegistry | null {
|
|
33
|
+
return hookRegistry;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getCliLoadedPlugins(): LoadedPlugins | null {
|
|
37
|
+
return loadedPlugins;
|
|
38
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import {
|
|
4
|
+
discoverPlugins,
|
|
5
|
+
installPlugin,
|
|
6
|
+
removePlugin,
|
|
7
|
+
updatePlugin,
|
|
8
|
+
updateAllPlugins,
|
|
9
|
+
enablePlugin,
|
|
10
|
+
disablePlugin,
|
|
11
|
+
getRegisteredMarketplaces,
|
|
12
|
+
addMarketplace,
|
|
13
|
+
removeMarketplace,
|
|
14
|
+
refreshMarketplaceCache,
|
|
15
|
+
} from "../plugins/marketplace.ts";
|
|
16
|
+
import { getConfig } from "../config/index.ts";
|
|
17
|
+
|
|
18
|
+
export function registerPluginCommand(program: Command): void {
|
|
19
|
+
const plugin = program
|
|
20
|
+
.command("plugin")
|
|
21
|
+
.description("Manage plugins and marketplaces");
|
|
22
|
+
|
|
23
|
+
// ── todoist plugin list ──
|
|
24
|
+
plugin
|
|
25
|
+
.command("list")
|
|
26
|
+
.description("List installed plugins")
|
|
27
|
+
.action(() => {
|
|
28
|
+
try {
|
|
29
|
+
const config = getConfig();
|
|
30
|
+
const plugins = config.plugins;
|
|
31
|
+
|
|
32
|
+
if (!plugins || Object.keys(plugins).length === 0) {
|
|
33
|
+
console.log(chalk.dim("No plugins installed."));
|
|
34
|
+
console.log(chalk.dim("Discover plugins with: todoist plugin discover"));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(chalk.bold("Installed Plugins"));
|
|
39
|
+
console.log("");
|
|
40
|
+
|
|
41
|
+
for (const [name, entry] of Object.entries(plugins)) {
|
|
42
|
+
const isEnabled = entry.enabled !== false;
|
|
43
|
+
const status = isEnabled ? chalk.green("●") : chalk.yellow("○");
|
|
44
|
+
const statusLabel = isEnabled ? chalk.green("enabled") : chalk.yellow("disabled");
|
|
45
|
+
const version = (entry.version as string) ?? "unknown";
|
|
46
|
+
const source = (entry.source as string) ?? "";
|
|
47
|
+
|
|
48
|
+
console.log(
|
|
49
|
+
` ${status} ${chalk.bold(name)} ${chalk.cyan("v" + version)} ${statusLabel} ${chalk.dim(source)}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ── todoist plugin discover ──
|
|
59
|
+
plugin
|
|
60
|
+
.command("discover")
|
|
61
|
+
.description("Browse available plugins from all marketplaces")
|
|
62
|
+
.action(async () => {
|
|
63
|
+
try {
|
|
64
|
+
console.log(chalk.dim("Fetching plugins from marketplaces..."));
|
|
65
|
+
const discovered = await discoverPlugins();
|
|
66
|
+
|
|
67
|
+
if (discovered.length === 0) {
|
|
68
|
+
console.log(chalk.dim("No plugins found in any marketplace."));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Group by marketplace
|
|
73
|
+
const grouped = new Map<string, typeof discovered>();
|
|
74
|
+
for (const plugin of discovered) {
|
|
75
|
+
const group = grouped.get(plugin.marketplace) ?? [];
|
|
76
|
+
group.push(plugin);
|
|
77
|
+
grouped.set(plugin.marketplace, group);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const [marketplace, plugins] of grouped) {
|
|
81
|
+
console.log("");
|
|
82
|
+
console.log(chalk.bold.underline(marketplace));
|
|
83
|
+
console.log("");
|
|
84
|
+
|
|
85
|
+
for (const p of plugins) {
|
|
86
|
+
let indicator: string;
|
|
87
|
+
if (p.installed && p.enabled) {
|
|
88
|
+
indicator = chalk.green("●");
|
|
89
|
+
} else if (p.installed && !p.enabled) {
|
|
90
|
+
indicator = chalk.yellow("◐");
|
|
91
|
+
} else {
|
|
92
|
+
indicator = chalk.dim("○");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const version = p.version ? chalk.cyan("v" + p.version) : "";
|
|
96
|
+
const description = p.description ? chalk.dim(p.description) : "";
|
|
97
|
+
|
|
98
|
+
console.log(` ${indicator} ${chalk.bold(p.name)} ${version} ${description}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ── todoist plugin install <name> ──
|
|
108
|
+
plugin
|
|
109
|
+
.command("install")
|
|
110
|
+
.description("Install a plugin (optionally name@marketplace)")
|
|
111
|
+
.argument("<name>", "Plugin name (or name@marketplace)")
|
|
112
|
+
.action(async (nameArg: string) => {
|
|
113
|
+
try {
|
|
114
|
+
let pluginName = nameArg;
|
|
115
|
+
let marketplaceName: string | undefined;
|
|
116
|
+
|
|
117
|
+
// Parse name@marketplace syntax
|
|
118
|
+
const atIndex = nameArg.lastIndexOf("@");
|
|
119
|
+
if (atIndex > 0) {
|
|
120
|
+
pluginName = nameArg.substring(0, atIndex);
|
|
121
|
+
marketplaceName = nameArg.substring(atIndex + 1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(chalk.dim(`Installing plugin "${pluginName}"...`));
|
|
125
|
+
const result = await installPlugin(pluginName, marketplaceName);
|
|
126
|
+
console.log(chalk.green(`Installed ${result.name} v${result.version} from ${result.marketplace}`));
|
|
127
|
+
if (result.description) {
|
|
128
|
+
console.log(chalk.dim(` ${result.description}`));
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error(chalk.red(`Failed to install: ${err instanceof Error ? err.message : String(err)}`));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ── todoist plugin remove <name> ──
|
|
137
|
+
plugin
|
|
138
|
+
.command("remove")
|
|
139
|
+
.description("Remove an installed plugin")
|
|
140
|
+
.argument("<name>", "Plugin name")
|
|
141
|
+
.action((name: string) => {
|
|
142
|
+
try {
|
|
143
|
+
removePlugin(name);
|
|
144
|
+
console.log(chalk.green(`Removed ${name}`));
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ── todoist plugin update [name] ──
|
|
152
|
+
plugin
|
|
153
|
+
.command("update")
|
|
154
|
+
.description("Update a specific plugin or all plugins")
|
|
155
|
+
.argument("[name]", "Plugin name (omit to update all)")
|
|
156
|
+
.action(async (name?: string) => {
|
|
157
|
+
try {
|
|
158
|
+
if (name) {
|
|
159
|
+
const result = await updatePlugin(name);
|
|
160
|
+
if (result.updated) {
|
|
161
|
+
console.log(chalk.green(`${result.name}: ${result.message}`));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.dim(`${result.name}: ${result.message}`));
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
console.log(chalk.dim("Updating all plugins..."));
|
|
167
|
+
const results = await updateAllPlugins();
|
|
168
|
+
|
|
169
|
+
if (results.length === 0) {
|
|
170
|
+
console.log(chalk.dim("No plugins installed."));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const result of results) {
|
|
175
|
+
if (result.updated) {
|
|
176
|
+
console.log(chalk.green(` ${result.name}: ${result.message}`));
|
|
177
|
+
} else {
|
|
178
|
+
console.log(chalk.dim(` ${result.name}: ${result.message}`));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// ── todoist plugin enable <name> ──
|
|
189
|
+
plugin
|
|
190
|
+
.command("enable")
|
|
191
|
+
.description("Enable a disabled plugin")
|
|
192
|
+
.argument("<name>", "Plugin name")
|
|
193
|
+
.action((name: string) => {
|
|
194
|
+
try {
|
|
195
|
+
enablePlugin(name);
|
|
196
|
+
console.log(chalk.green(`Enabled ${name}`));
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ── todoist plugin disable <name> ──
|
|
204
|
+
plugin
|
|
205
|
+
.command("disable")
|
|
206
|
+
.description("Disable a plugin without removing it")
|
|
207
|
+
.argument("<name>", "Plugin name")
|
|
208
|
+
.action((name: string) => {
|
|
209
|
+
try {
|
|
210
|
+
disablePlugin(name);
|
|
211
|
+
console.log(chalk.yellow(`Disabled ${name}`));
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ── todoist plugin marketplace ──
|
|
219
|
+
const marketplace = plugin
|
|
220
|
+
.command("marketplace")
|
|
221
|
+
.description("Manage plugin marketplaces");
|
|
222
|
+
|
|
223
|
+
// ── todoist plugin marketplace list ──
|
|
224
|
+
marketplace
|
|
225
|
+
.command("list")
|
|
226
|
+
.description("List registered marketplaces")
|
|
227
|
+
.action(() => {
|
|
228
|
+
try {
|
|
229
|
+
const marketplaces = getRegisteredMarketplaces();
|
|
230
|
+
|
|
231
|
+
console.log(chalk.bold("Registered Marketplaces"));
|
|
232
|
+
console.log("");
|
|
233
|
+
|
|
234
|
+
for (const m of marketplaces) {
|
|
235
|
+
const autoUpdate = m.autoUpdate ? chalk.green("auto-update") : chalk.dim("manual");
|
|
236
|
+
console.log(` ${chalk.bold(m.name)} ${chalk.dim(m.source)} ${autoUpdate}`);
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ── todoist plugin marketplace add <source> ──
|
|
245
|
+
marketplace
|
|
246
|
+
.command("add")
|
|
247
|
+
.description("Add a marketplace (e.g. github:user/repo)")
|
|
248
|
+
.argument("<source>", "Marketplace source (github:user/repo)")
|
|
249
|
+
.action((source: string) => {
|
|
250
|
+
try {
|
|
251
|
+
const name = addMarketplace(source);
|
|
252
|
+
console.log(chalk.green(`Added marketplace "${name}" from ${source}`));
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// ── todoist plugin marketplace remove <name> ──
|
|
260
|
+
marketplace
|
|
261
|
+
.command("remove")
|
|
262
|
+
.description("Remove a registered marketplace")
|
|
263
|
+
.argument("<name>", "Marketplace name")
|
|
264
|
+
.action((name: string) => {
|
|
265
|
+
try {
|
|
266
|
+
removeMarketplace(name);
|
|
267
|
+
console.log(chalk.green(`Removed marketplace "${name}"`));
|
|
268
|
+
} catch (err) {
|
|
269
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ── todoist plugin marketplace refresh [name] ──
|
|
275
|
+
marketplace
|
|
276
|
+
.command("refresh")
|
|
277
|
+
.description("Refresh marketplace cache")
|
|
278
|
+
.argument("[name]", "Marketplace name (omit to refresh all)")
|
|
279
|
+
.action(async (name?: string) => {
|
|
280
|
+
try {
|
|
281
|
+
console.log(chalk.dim(`Refreshing ${name ? `"${name}"` : "all marketplaces"}...`));
|
|
282
|
+
await refreshMarketplaceCache(name);
|
|
283
|
+
console.log(chalk.green(`Marketplace cache refreshed.`));
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|