trelly 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 (46) hide show
  1. package/README.md +191 -0
  2. package/bin/run-ts +31 -0
  3. package/bin/trelly +2 -0
  4. package/bin/trelly-mcp +2 -0
  5. package/package.json +64 -0
  6. package/src/api/client.ts +332 -0
  7. package/src/api/http.ts +86 -0
  8. package/src/auth/browser-flow.ts +217 -0
  9. package/src/auth/profiles.ts +163 -0
  10. package/src/cli/commands/actions.ts +17 -0
  11. package/src/cli/commands/api.ts +46 -0
  12. package/src/cli/commands/auth.ts +248 -0
  13. package/src/cli/commands/boards.ts +152 -0
  14. package/src/cli/commands/cards.ts +194 -0
  15. package/src/cli/commands/checklists.ts +79 -0
  16. package/src/cli/commands/custom-fields.ts +75 -0
  17. package/src/cli/commands/labels.ts +47 -0
  18. package/src/cli/commands/lists.ts +54 -0
  19. package/src/cli/commands/members.ts +14 -0
  20. package/src/cli/commands/orgs.ts +23 -0
  21. package/src/cli/commands/run.ts +32 -0
  22. package/src/cli/commands/search.ts +23 -0
  23. package/src/cli/commands/ui.ts +48 -0
  24. package/src/cli/commands/webhooks.ts +44 -0
  25. package/src/cli/context.ts +70 -0
  26. package/src/cli/index.ts +47 -0
  27. package/src/cli/ui/app.tsx +753 -0
  28. package/src/cli/ui/custom-fields.ts +75 -0
  29. package/src/cli/ui/palette.ts +69 -0
  30. package/src/cli/ui/shapes.ts +68 -0
  31. package/src/cli/ui/static.tsx +382 -0
  32. package/src/index.test.ts +117 -0
  33. package/src/mcp/handlers.ts +61 -0
  34. package/src/mcp/server.ts +17 -0
  35. package/src/mcp/tools/api.ts +27 -0
  36. package/src/mcp/tools/boards.ts +116 -0
  37. package/src/mcp/tools/cards.ts +138 -0
  38. package/src/mcp/tools/checklists.ts +40 -0
  39. package/src/mcp/tools/index.ts +22 -0
  40. package/src/mcp/tools/labels.ts +40 -0
  41. package/src/mcp/tools/lists.ts +37 -0
  42. package/src/mcp/tools/profiles.ts +40 -0
  43. package/src/mcp/tools/search.ts +31 -0
  44. package/src/mcp/tools/webhooks.ts +52 -0
  45. package/src/util/runtime.ts +39 -0
  46. package/src/version.ts +15 -0
@@ -0,0 +1,248 @@
1
+ import type { Command } from "commander";
2
+ import { createClient } from "../../api/client.ts";
3
+ import {
4
+ captureTokenViaBrowser,
5
+ SETUP_ALLOWED_ORIGIN,
6
+ } from "../../auth/browser-flow.ts";
7
+ import {
8
+ type AuthScope,
9
+ authLoginUrl,
10
+ configPath,
11
+ getAppApiKey,
12
+ loadConfig,
13
+ removeProfile,
14
+ setAppApiKey,
15
+ setDefaultProfile,
16
+ upsertProfile,
17
+ } from "../../auth/profiles.ts";
18
+ import { openInBrowser, readLine } from "../../util/runtime.ts";
19
+ import { failure, printResult, success } from "../context.ts";
20
+ import { rootOpts } from "./run.ts";
21
+
22
+ const POWER_UPS_ADMIN = "https://trello.com/power-ups/admin";
23
+
24
+ export function registerAuthCommands(program: Command): void {
25
+ const auth = program.command("auth").description("Manage Trello credentials");
26
+
27
+ auth
28
+ .command("setup")
29
+ .description("One-time app setup: save API key + allowed-origin instructions")
30
+ .option("-k, --api-key <key>", "Trello API key to save")
31
+ .option("--no-open", "Do not open Power-Ups admin in the browser")
32
+ .action(async (opts, cmd) => {
33
+ const root = rootOpts(cmd);
34
+ try {
35
+ if (opts.open !== false) {
36
+ process.stderr.write(`Opening ${POWER_UPS_ADMIN}\n`);
37
+ await openInBrowser(POWER_UPS_ADMIN).catch(() => {
38
+ process.stderr.write(`Open manually: ${POWER_UPS_ADMIN}\n`);
39
+ });
40
+ }
41
+
42
+ process.stderr.write(`
43
+ One-time setup (per Trello account / Power-Up):
44
+
45
+ 1. Create or open a Power-Up → API Key tab → Generate API Key
46
+ (Pick any workspace you admin — personal is fine. This does not
47
+ limit which boards you can use. Never install the app on a board.)
48
+ 2. Allowed Origins → add: ${SETUP_ALLOWED_ORIGIN}
49
+ (required for automatic browser login)
50
+ 3. Paste your API key below
51
+
52
+ `);
53
+
54
+ let apiKey = opts.apiKey as string | undefined;
55
+ if (!apiKey) {
56
+ process.stderr.write("API key: ");
57
+ apiKey = (await readLine()).trim();
58
+ }
59
+
60
+ if (!apiKey) {
61
+ throw new Error("API key is required");
62
+ }
63
+
64
+ setAppApiKey(apiKey);
65
+ printResult(
66
+ success("default", {
67
+ message: "Saved app API key for trelly",
68
+ configPath: configPath(),
69
+ allowedOrigin: SETUP_ALLOWED_ORIGIN,
70
+ next: "Run: trelly auth login",
71
+ }),
72
+ root,
73
+ );
74
+ } catch (err) {
75
+ printResult(failure(err instanceof Error ? err.message : String(err)), root);
76
+ process.exitCode = 1;
77
+ }
78
+ });
79
+
80
+ auth
81
+ .command("login")
82
+ .description("Authorize a profile via browser (default) or manual token paste")
83
+ .option("-p, --profile <name>", "Profile name", "default")
84
+ .option("-k, --api-key <key>", "Override app API key for this login")
85
+ .option("-t, --token <token>", "Token (skips browser flow)")
86
+ .option(
87
+ "--manual",
88
+ "Skip browser callback; paste token after opening authorize URL",
89
+ )
90
+ .option("--no-open", "Do not open a browser automatically")
91
+ .option("--default", "Set as default profile")
92
+ .option("--label <label>", "Human label for this profile")
93
+ .option(
94
+ "--full-access",
95
+ "Request read,write,account scope and never-expiring token",
96
+ )
97
+ .action(async (opts, cmd) => {
98
+ const root = rootOpts(cmd);
99
+ try {
100
+ let apiKey = (opts.apiKey as string | undefined) ?? getAppApiKey() ?? undefined;
101
+ let token = opts.token as string | undefined;
102
+
103
+ if (!apiKey) {
104
+ process.stderr.write("No app API key saved. Running guided setup...\n\n");
105
+ if (opts.open !== false) {
106
+ await openInBrowser(POWER_UPS_ADMIN).catch(() => undefined);
107
+ }
108
+ process.stderr.write(
109
+ `Create a Power-Up API key at ${POWER_UPS_ADMIN}\n` +
110
+ `Add allowed origin: ${SETUP_ALLOWED_ORIGIN}\n` +
111
+ "API key: ",
112
+ );
113
+ apiKey = (await readLine()).trim();
114
+ if (!apiKey) throw new Error("API key is required");
115
+ setAppApiKey(apiKey);
116
+ }
117
+
118
+ const authScope: AuthScope[] = opts.fullAccess
119
+ ? ["read", "write", "account"]
120
+ : ["read", "write"];
121
+ const authExpiration = opts.fullAccess
122
+ ? ("never" as const)
123
+ : ("30days" as const);
124
+
125
+ if (!token) {
126
+ if (opts.manual) {
127
+ const url = authLoginUrl(apiKey, {
128
+ scope: authScope,
129
+ expiration: authExpiration,
130
+ });
131
+ process.stderr.write(`Authorize in browser:\n${url}\n`);
132
+ if (opts.open !== false) {
133
+ await openInBrowser(url).catch(() => undefined);
134
+ }
135
+ process.stderr.write("Paste token: ");
136
+ token = (await readLine()).trim();
137
+ } else {
138
+ process.stderr.write("Opening browser for Trello authorization...\n");
139
+ const result = await captureTokenViaBrowser(apiKey, {
140
+ openBrowser: opts.open !== false,
141
+ scope: authScope,
142
+ });
143
+ if ("error" in result) {
144
+ throw new Error(
145
+ `${result.error}\n` +
146
+ `If redirect was blocked, run:\n` +
147
+ ` trelly auth setup # add ${SETUP_ALLOWED_ORIGIN} to allowed origins\n` +
148
+ ` trelly auth login --manual`,
149
+ );
150
+ }
151
+ token = result.token;
152
+ }
153
+ }
154
+
155
+ if (!token) {
156
+ throw new Error("Token is required");
157
+ }
158
+
159
+ upsertProfile(opts.profile, { apiKey, token, label: opts.label }, opts.default);
160
+
161
+ const client = createClient(apiKey, token);
162
+ const me = await client.memberMe("fullName,username,url");
163
+
164
+ printResult(
165
+ success(opts.profile, {
166
+ message: `Saved profile "${opts.profile}"`,
167
+ configPath: configPath(),
168
+ member: me,
169
+ }),
170
+ root,
171
+ );
172
+ } catch (err) {
173
+ printResult(failure(err instanceof Error ? err.message : String(err)), root);
174
+ process.exitCode = 1;
175
+ }
176
+ });
177
+
178
+ auth
179
+ .command("list")
180
+ .description("List saved profiles")
181
+ .action((_opts, cmd) => {
182
+ const root = rootOpts(cmd);
183
+ const config = loadConfig();
184
+ printResult(
185
+ success(config.defaultProfile, {
186
+ defaultProfile: config.defaultProfile,
187
+ hasAppApiKey: Boolean(config.appApiKey ?? process.env.TRELLO_APP_API_KEY),
188
+ profiles: Object.entries(config.profiles).map(([name, p]) => ({
189
+ name,
190
+ label: p.label,
191
+ isDefault: name === config.defaultProfile,
192
+ })),
193
+ }),
194
+ root,
195
+ );
196
+ });
197
+
198
+ auth
199
+ .command("use <profile>")
200
+ .description("Set default profile")
201
+ .action((profile, _opts, cmd) => {
202
+ const root = rootOpts(cmd);
203
+ try {
204
+ setDefaultProfile(profile);
205
+ printResult(
206
+ success(profile, { message: `Default profile is now "${profile}"` }),
207
+ root,
208
+ );
209
+ } catch (err) {
210
+ printResult(failure(err instanceof Error ? err.message : String(err)), root);
211
+ process.exitCode = 1;
212
+ }
213
+ });
214
+
215
+ auth
216
+ .command("logout")
217
+ .description("Remove a saved profile")
218
+ .option("-p, --profile <name>", "Profile to remove")
219
+ .action((opts, cmd) => {
220
+ const root = rootOpts(cmd);
221
+ const config = loadConfig();
222
+ const profile = opts.profile ?? config.defaultProfile;
223
+ try {
224
+ removeProfile(profile);
225
+ printResult(
226
+ success(profile, { message: `Removed profile "${profile}"` }),
227
+ root,
228
+ );
229
+ } catch (err) {
230
+ printResult(failure(err instanceof Error ? err.message : String(err)), root);
231
+ process.exitCode = 1;
232
+ }
233
+ });
234
+
235
+ auth
236
+ .command("url")
237
+ .description("Print browser authorization URL")
238
+ .option("-k, --api-key <key>", "Trello API key")
239
+ .action(async (opts, cmd) => {
240
+ const root = rootOpts(cmd);
241
+ let apiKey = (opts.apiKey as string | undefined) ?? getAppApiKey() ?? undefined;
242
+ if (!apiKey) {
243
+ process.stderr.write("API key: ");
244
+ apiKey = (await readLine()).trim();
245
+ }
246
+ printResult(success("env", { url: authLoginUrl(apiKey) }), root);
247
+ });
248
+ }
@@ -0,0 +1,152 @@
1
+ import type { Command } from "commander";
2
+ import { getClient, parseJsonFlag, parseKvPairs } from "../context.ts";
3
+ import { rootOpts, run } from "./run.ts";
4
+
5
+ export function registerBoardCommands(program: Command): void {
6
+ const boards = program.command("boards").description("Board operations");
7
+
8
+ boards
9
+ .command("list")
10
+ .description("List boards for the current member")
11
+ .option("--fields <fields>", "Comma-separated fields")
12
+ .option("--filter <filter>", "Board filter e.g. open")
13
+ .action((opts, cmd) =>
14
+ run(cmd, async () => {
15
+ const { client } = getClient(rootOpts(cmd).profile);
16
+ return client.memberBoards("me", {
17
+ fields: opts.fields,
18
+ filter: opts.filter ?? "open",
19
+ });
20
+ }),
21
+ );
22
+
23
+ boards
24
+ .command("get <id>")
25
+ .description("Get a board")
26
+ .option("--fields <fields>", "Comma-separated fields")
27
+ .action((id, opts, cmd) =>
28
+ run(cmd, async () => {
29
+ const { client } = getClient(rootOpts(cmd).profile);
30
+ return client.boardGet(id, { fields: opts.fields });
31
+ }),
32
+ );
33
+
34
+ boards
35
+ .command("create")
36
+ .description("Create a board")
37
+ .requiredOption("--name <name>", "Board name")
38
+ .option("--desc <desc>", "Description")
39
+ .option("--org <id>", "Workspace id")
40
+ .option("--default-lists", "Create default lists")
41
+ .option("--params <kv...>", "Extra key=value params")
42
+ .action((opts, cmd) =>
43
+ run(cmd, async () => {
44
+ const { client } = getClient(rootOpts(cmd).profile);
45
+ return client.boardCreate({
46
+ name: opts.name,
47
+ desc: opts.desc,
48
+ idOrganization: opts.org,
49
+ defaultLists: opts.defaultLists,
50
+ ...parseKvPairs(opts.params),
51
+ });
52
+ }),
53
+ );
54
+
55
+ boards
56
+ .command("update <id>")
57
+ .description("Update a board")
58
+ .option("--params <kv...>", "key=value params")
59
+ .option("--json <json>", "JSON body merged with params")
60
+ .action((id, opts, cmd) =>
61
+ run(cmd, async () => {
62
+ const { client } = getClient(rootOpts(cmd).profile);
63
+ const body = {
64
+ ...parseKvPairs(opts.params),
65
+ ...(parseJsonFlag(opts.json, "--json") as object),
66
+ };
67
+ return client.boardUpdate(id, body);
68
+ }),
69
+ );
70
+
71
+ boards
72
+ .command("archive <id>")
73
+ .description("Close (archive) a board — reversible in Trello UI")
74
+ .action((id, cmd) =>
75
+ run(cmd, async () => {
76
+ const { client } = getClient(rootOpts(cmd).profile);
77
+ return client.boardArchive(id);
78
+ }),
79
+ );
80
+
81
+ boards
82
+ .command("delete <id>")
83
+ .description("Permanently delete a board — irreversible")
84
+ .action((id, cmd) =>
85
+ run(cmd, async () => {
86
+ const { client } = getClient(rootOpts(cmd).profile);
87
+ return client.boardDelete(id);
88
+ }),
89
+ );
90
+
91
+ boards
92
+ .command("lists <boardId>")
93
+ .description("List lists on a board")
94
+ .option("--filter <filter>", "open|closed|all", "open")
95
+ .action((boardId, opts, cmd) =>
96
+ run(cmd, async () => {
97
+ const { client } = getClient(rootOpts(cmd).profile);
98
+ return client.boardLists(boardId, { filter: opts.filter });
99
+ }),
100
+ );
101
+
102
+ boards
103
+ .command("cards <boardId>")
104
+ .description("List cards on a board")
105
+ .action((boardId, cmd) =>
106
+ run(cmd, async () => {
107
+ const { client } = getClient(rootOpts(cmd).profile);
108
+ return client.boardCards(boardId);
109
+ }),
110
+ );
111
+
112
+ boards
113
+ .command("labels <boardId>")
114
+ .description("List labels on a board")
115
+ .action((boardId, cmd) =>
116
+ run(cmd, async () => {
117
+ const { client } = getClient(rootOpts(cmd).profile);
118
+ return client.boardLabels(boardId);
119
+ }),
120
+ );
121
+
122
+ boards
123
+ .command("members <boardId>")
124
+ .description("List board members")
125
+ .action((boardId, cmd) =>
126
+ run(cmd, async () => {
127
+ const { client } = getClient(rootOpts(cmd).profile);
128
+ return client.boardMembers(boardId);
129
+ }),
130
+ );
131
+
132
+ boards
133
+ .command("actions <boardId>")
134
+ .description("Board activity feed")
135
+ .option("--limit <n>", "Max actions", "50")
136
+ .action((boardId, opts, cmd) =>
137
+ run(cmd, async () => {
138
+ const { client } = getClient(rootOpts(cmd).profile);
139
+ return client.boardActions(boardId, { limit: opts.limit });
140
+ }),
141
+ );
142
+
143
+ boards
144
+ .command("custom-fields <boardId>")
145
+ .description("List custom field definitions")
146
+ .action((boardId, cmd) =>
147
+ run(cmd, async () => {
148
+ const { client } = getClient(rootOpts(cmd).profile);
149
+ return client.boardCustomFields(boardId);
150
+ }),
151
+ );
152
+ }
@@ -0,0 +1,194 @@
1
+ import type { Command } from "commander";
2
+ import { getClient, parseJsonFlag, parseKvPairs } from "../context.ts";
3
+ import { rootOpts, run } from "./run.ts";
4
+
5
+ export function registerCardCommands(program: Command): void {
6
+ const cards = program.command("cards").description("Card operations");
7
+
8
+ cards
9
+ .command("get <id>")
10
+ .option("--fields <fields>", "Comma-separated fields")
11
+ .action((id, opts, cmd) =>
12
+ run(cmd, async () => {
13
+ const { client } = getClient(rootOpts(cmd).profile);
14
+ return client.cardGet(id, { fields: opts.fields });
15
+ }),
16
+ );
17
+
18
+ cards
19
+ .command("list")
20
+ .description("List cards on a list")
21
+ .requiredOption("--list <listId>", "List id")
22
+ .action((opts, cmd) =>
23
+ run(cmd, async () => {
24
+ const { client } = getClient(rootOpts(cmd).profile);
25
+ return client.listCards(opts.list);
26
+ }),
27
+ );
28
+
29
+ cards
30
+ .command("create")
31
+ .requiredOption("--list <listId>", "Target list")
32
+ .requiredOption("--name <name>", "Card name")
33
+ .option("--desc <desc>", "Description")
34
+ .option("--due <iso>", "Due date ISO8601")
35
+ .option("--pos <pos>", "Position")
36
+ .option("--params <kv...>", "Extra key=value params")
37
+ .action((opts, cmd) =>
38
+ run(cmd, async () => {
39
+ const { client } = getClient(rootOpts(cmd).profile);
40
+ return client.cardCreate({
41
+ idList: opts.list,
42
+ name: opts.name,
43
+ desc: opts.desc,
44
+ due: opts.due,
45
+ pos: opts.pos,
46
+ ...parseKvPairs(opts.params),
47
+ });
48
+ }),
49
+ );
50
+
51
+ cards
52
+ .command("update <id>")
53
+ .option("--params <kv...>", "key=value params")
54
+ .option("--json <json>", "JSON body")
55
+ .action((id, opts, cmd) =>
56
+ run(cmd, async () => {
57
+ const { client } = getClient(rootOpts(cmd).profile);
58
+ return client.cardUpdate(id, {
59
+ ...parseKvPairs(opts.params),
60
+ ...(parseJsonFlag(opts.json, "--json") as object),
61
+ });
62
+ }),
63
+ );
64
+
65
+ cards
66
+ .command("move <id>")
67
+ .description("Move card to another list")
68
+ .requiredOption("--list <listId>", "Destination list")
69
+ .option("--pos <pos>", "Position")
70
+ .action((id, opts, cmd) =>
71
+ run(cmd, async () => {
72
+ const { client } = getClient(rootOpts(cmd).profile);
73
+ return client.cardUpdate(id, { idList: opts.list, pos: opts.pos });
74
+ }),
75
+ );
76
+
77
+ cards
78
+ .command("comments <id>")
79
+ .description("List comments on a card")
80
+ .option("--limit <n>", "Max comments", "50")
81
+ .action((id, opts, cmd) =>
82
+ run(cmd, async () => {
83
+ const { client } = getClient(rootOpts(cmd).profile);
84
+ return client.cardComments(id, { limit: opts.limit });
85
+ }),
86
+ );
87
+
88
+ cards
89
+ .command("comment <id>")
90
+ .requiredOption("--text <text>", "Comment body")
91
+ .action((id, opts, cmd) =>
92
+ run(cmd, async () => {
93
+ const { client } = getClient(rootOpts(cmd).profile);
94
+ return client.cardComment(id, opts.text);
95
+ }),
96
+ );
97
+
98
+ cards
99
+ .command("archive <id>")
100
+ .description("Close (archive) a card — reversible in Trello UI")
101
+ .action((id, _opts, cmd) =>
102
+ run(cmd, async () => {
103
+ const { client } = getClient(rootOpts(cmd).profile);
104
+ return client.cardArchive(id);
105
+ }),
106
+ );
107
+
108
+ cards
109
+ .command("delete <id>")
110
+ .description("Permanently delete a card — irreversible")
111
+ .action((id, _opts, cmd) =>
112
+ run(cmd, async () => {
113
+ const { client } = getClient(rootOpts(cmd).profile);
114
+ return client.cardDelete(id);
115
+ }),
116
+ );
117
+
118
+ cards.command("members <id>").action((id, _opts, cmd) =>
119
+ run(cmd, async () => {
120
+ const { client } = getClient(rootOpts(cmd).profile);
121
+ return client.cardMembers(id);
122
+ }),
123
+ );
124
+
125
+ cards.command("add-member <id> <memberId>").action((id, memberId, _opts, cmd) =>
126
+ run(cmd, async () => {
127
+ const { client } = getClient(rootOpts(cmd).profile);
128
+ return client.cardAddMember(id, memberId);
129
+ }),
130
+ );
131
+
132
+ cards.command("remove-member <id> <memberId>").action((id, memberId, _opts, cmd) =>
133
+ run(cmd, async () => {
134
+ const { client } = getClient(rootOpts(cmd).profile);
135
+ return client.cardRemoveMember(id, memberId);
136
+ }),
137
+ );
138
+
139
+ cards.command("labels <id>").action((id, _opts, cmd) =>
140
+ run(cmd, async () => {
141
+ const { client } = getClient(rootOpts(cmd).profile);
142
+ return client.cardLabels(id);
143
+ }),
144
+ );
145
+
146
+ cards.command("add-label <id> <labelId>").action((id, labelId, _opts, cmd) =>
147
+ run(cmd, async () => {
148
+ const { client } = getClient(rootOpts(cmd).profile);
149
+ return client.cardAddLabel(id, labelId);
150
+ }),
151
+ );
152
+
153
+ cards.command("remove-label <id> <labelId>").action((id, labelId, _opts, cmd) =>
154
+ run(cmd, async () => {
155
+ const { client } = getClient(rootOpts(cmd).profile);
156
+ return client.cardRemoveLabel(id, labelId);
157
+ }),
158
+ );
159
+
160
+ cards
161
+ .command("actions <id>")
162
+ .option("--limit <n>", "Max actions", "50")
163
+ .action((id, opts, cmd) =>
164
+ run(cmd, async () => {
165
+ const { client } = getClient(rootOpts(cmd).profile);
166
+ return client.cardActions(id, { limit: opts.limit });
167
+ }),
168
+ );
169
+
170
+ cards.command("attachments <id>").action((id, _opts, cmd) =>
171
+ run(cmd, async () => {
172
+ const { client } = getClient(rootOpts(cmd).profile);
173
+ return client.cardAttachments(id);
174
+ }),
175
+ );
176
+
177
+ cards
178
+ .command("add-attachment <id>")
179
+ .requiredOption("--url <url>", "Attachment URL")
180
+ .option("--name <name>", "Attachment name")
181
+ .action((id, opts, cmd) =>
182
+ run(cmd, async () => {
183
+ const { client } = getClient(rootOpts(cmd).profile);
184
+ return client.cardAddAttachment(id, { url: opts.url, name: opts.name });
185
+ }),
186
+ );
187
+
188
+ cards.command("custom-fields <id>").action((id, _opts, cmd) =>
189
+ run(cmd, async () => {
190
+ const { client } = getClient(rootOpts(cmd).profile);
191
+ return client.cardCustomFieldItems(id);
192
+ }),
193
+ );
194
+ }
@@ -0,0 +1,79 @@
1
+ import type { Command } from "commander";
2
+ import { getClient, parseKvPairs } from "../context.ts";
3
+ import { rootOpts, run } from "./run.ts";
4
+
5
+ export function registerChecklistCommands(program: Command): void {
6
+ const checklists = program.command("checklists").description("Checklist operations");
7
+
8
+ checklists.command("get <id>").action((id, _opts, cmd) =>
9
+ run(cmd, async () => {
10
+ const { client } = getClient(rootOpts(cmd).profile);
11
+ return client.checklistGet(id);
12
+ }),
13
+ );
14
+
15
+ checklists
16
+ .command("create")
17
+ .requiredOption("--card <cardId>", "Card id")
18
+ .requiredOption("--name <name>", "Checklist name")
19
+ .action((opts, cmd) =>
20
+ run(cmd, async () => {
21
+ const { client } = getClient(rootOpts(cmd).profile);
22
+ return client.checklistCreate({ idCard: opts.card, name: opts.name });
23
+ }),
24
+ );
25
+
26
+ checklists
27
+ .command("update <id>")
28
+ .option("--params <kv...>", "key=value params")
29
+ .action((id, opts, cmd) =>
30
+ run(cmd, async () => {
31
+ const { client } = getClient(rootOpts(cmd).profile);
32
+ return client.checklistUpdate(id, parseKvPairs(opts.params));
33
+ }),
34
+ );
35
+
36
+ checklists.command("delete <id>").action((id, _opts, cmd) =>
37
+ run(cmd, async () => {
38
+ const { client } = getClient(rootOpts(cmd).profile);
39
+ return client.checklistDelete(id);
40
+ }),
41
+ );
42
+
43
+ checklists
44
+ .command("add-item <id>")
45
+ .requiredOption("--name <name>", "Item name")
46
+ .option("--checked", "Mark checked")
47
+ .action((id, opts, cmd) =>
48
+ run(cmd, async () => {
49
+ const { client } = getClient(rootOpts(cmd).profile);
50
+ return client.checklistAddItem(id, {
51
+ name: opts.name,
52
+ checked: opts.checked,
53
+ });
54
+ }),
55
+ );
56
+
57
+ checklists
58
+ .command("update-item <checklistId> <itemId>")
59
+ .option("--params <kv...>", "key=value params")
60
+ .action((checklistId, itemId, opts, cmd) =>
61
+ run(cmd, async () => {
62
+ const { client } = getClient(rootOpts(cmd).profile);
63
+ return client.checklistUpdateItem(
64
+ checklistId,
65
+ itemId,
66
+ parseKvPairs(opts.params),
67
+ );
68
+ }),
69
+ );
70
+
71
+ checklists
72
+ .command("delete-item <checklistId> <itemId>")
73
+ .action((checklistId, itemId, _opts, cmd) =>
74
+ run(cmd, async () => {
75
+ const { client } = getClient(rootOpts(cmd).profile);
76
+ return client.checklistDeleteItem(checklistId, itemId);
77
+ }),
78
+ );
79
+ }