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.
- package/README.md +191 -0
- package/bin/run-ts +31 -0
- package/bin/trelly +2 -0
- package/bin/trelly-mcp +2 -0
- package/package.json +64 -0
- package/src/api/client.ts +332 -0
- package/src/api/http.ts +86 -0
- package/src/auth/browser-flow.ts +217 -0
- package/src/auth/profiles.ts +163 -0
- package/src/cli/commands/actions.ts +17 -0
- package/src/cli/commands/api.ts +46 -0
- package/src/cli/commands/auth.ts +248 -0
- package/src/cli/commands/boards.ts +152 -0
- package/src/cli/commands/cards.ts +194 -0
- package/src/cli/commands/checklists.ts +79 -0
- package/src/cli/commands/custom-fields.ts +75 -0
- package/src/cli/commands/labels.ts +47 -0
- package/src/cli/commands/lists.ts +54 -0
- package/src/cli/commands/members.ts +14 -0
- package/src/cli/commands/orgs.ts +23 -0
- package/src/cli/commands/run.ts +32 -0
- package/src/cli/commands/search.ts +23 -0
- package/src/cli/commands/ui.ts +48 -0
- package/src/cli/commands/webhooks.ts +44 -0
- package/src/cli/context.ts +70 -0
- package/src/cli/index.ts +47 -0
- package/src/cli/ui/app.tsx +753 -0
- package/src/cli/ui/custom-fields.ts +75 -0
- package/src/cli/ui/palette.ts +69 -0
- package/src/cli/ui/shapes.ts +68 -0
- package/src/cli/ui/static.tsx +382 -0
- package/src/index.test.ts +117 -0
- package/src/mcp/handlers.ts +61 -0
- package/src/mcp/server.ts +17 -0
- package/src/mcp/tools/api.ts +27 -0
- package/src/mcp/tools/boards.ts +116 -0
- package/src/mcp/tools/cards.ts +138 -0
- package/src/mcp/tools/checklists.ts +40 -0
- package/src/mcp/tools/index.ts +22 -0
- package/src/mcp/tools/labels.ts +40 -0
- package/src/mcp/tools/lists.ts +37 -0
- package/src/mcp/tools/profiles.ts +40 -0
- package/src/mcp/tools/search.ts +31 -0
- package/src/mcp/tools/webhooks.ts +52 -0
- package/src/util/runtime.ts +39 -0
- 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
|
+
}
|