claude-trello-cli 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/dist/index.js +654 -0
- package/package.json +45 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command6 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/login.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { input, password } from "@inquirer/prompts";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
// src/lib/config.ts
|
|
13
|
+
import {
|
|
14
|
+
existsSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
unlinkSync
|
|
19
|
+
} from "fs";
|
|
20
|
+
import { homedir } from "os";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
var CONFIG_DIR = join(homedir(), ".config", "claude-trello");
|
|
23
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
24
|
+
function ensureDir() {
|
|
25
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
26
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function getConfig() {
|
|
30
|
+
ensureDir();
|
|
31
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
34
|
+
} catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function saveConfig(update) {
|
|
39
|
+
ensureDir();
|
|
40
|
+
const current = getConfig();
|
|
41
|
+
const merged = { ...current, ...update };
|
|
42
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2), { mode: 384 });
|
|
43
|
+
}
|
|
44
|
+
function clearConfig() {
|
|
45
|
+
if (existsSync(CONFIG_FILE)) {
|
|
46
|
+
unlinkSync(CONFIG_FILE);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function getServerUrl() {
|
|
50
|
+
const config = getConfig();
|
|
51
|
+
return config.serverUrl || process.env.CLAUDE_TRELLO_URL || "http://localhost:3000";
|
|
52
|
+
}
|
|
53
|
+
function getSessionCookie() {
|
|
54
|
+
return getConfig().sessionCookie;
|
|
55
|
+
}
|
|
56
|
+
function isLoggedIn() {
|
|
57
|
+
return !!getSessionCookie();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/lib/api.ts
|
|
61
|
+
var ApiError = class extends Error {
|
|
62
|
+
constructor(status, message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.status = status;
|
|
65
|
+
this.name = "ApiError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
async function apiFetch(path, options) {
|
|
69
|
+
const serverUrl = getServerUrl();
|
|
70
|
+
const cookie = getSessionCookie();
|
|
71
|
+
if (!cookie) {
|
|
72
|
+
throw new ApiError(
|
|
73
|
+
401,
|
|
74
|
+
"Not logged in. Run `claude-trello login` first."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const res = await fetch(`${serverUrl}${path}`, {
|
|
78
|
+
...options,
|
|
79
|
+
headers: {
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
Cookie: cookie,
|
|
82
|
+
...options?.headers ?? {}
|
|
83
|
+
},
|
|
84
|
+
redirect: "manual"
|
|
85
|
+
});
|
|
86
|
+
if (res.status === 401) {
|
|
87
|
+
throw new ApiError(
|
|
88
|
+
401,
|
|
89
|
+
"Session expired. Run `claude-trello login` to re-authenticate."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
let errorMsg = `${res.status} ${res.statusText}`;
|
|
94
|
+
try {
|
|
95
|
+
const body = await res.json();
|
|
96
|
+
if (body.error) errorMsg = body.error;
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
throw new ApiError(res.status, errorMsg);
|
|
100
|
+
}
|
|
101
|
+
return res.json();
|
|
102
|
+
}
|
|
103
|
+
async function signIn(serverUrl, email, password2) {
|
|
104
|
+
const res = await fetch(`${serverUrl}/api/auth/sign-in/email`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "Content-Type": "application/json" },
|
|
107
|
+
body: JSON.stringify({ email, password: password2 }),
|
|
108
|
+
redirect: "manual"
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
let errorMsg = "Sign-in failed";
|
|
112
|
+
try {
|
|
113
|
+
const body = await res.json();
|
|
114
|
+
errorMsg = body.message || body.error || errorMsg;
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
throw new ApiError(res.status, errorMsg);
|
|
118
|
+
}
|
|
119
|
+
const setCookieHeaders = res.headers.getSetCookie?.() ?? [];
|
|
120
|
+
const cookies = setCookieHeaders.map((c) => c.split(";")[0]).join("; ");
|
|
121
|
+
if (!cookies) {
|
|
122
|
+
throw new ApiError(500, "No session cookie received from server");
|
|
123
|
+
}
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
return { cookies, user: data.user };
|
|
126
|
+
}
|
|
127
|
+
async function getIntegrationStatus() {
|
|
128
|
+
return apiFetch("/api/settings/status");
|
|
129
|
+
}
|
|
130
|
+
async function getBoards() {
|
|
131
|
+
return apiFetch("/api/trello/boards");
|
|
132
|
+
}
|
|
133
|
+
async function getBoardData(boardId) {
|
|
134
|
+
return apiFetch(
|
|
135
|
+
`/api/trello/cards?boardId=${encodeURIComponent(boardId)}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
async function getCredentials() {
|
|
139
|
+
return apiFetch("/api/cli/credentials");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/commands/login.ts
|
|
143
|
+
var loginCommand = new Command("login").description("Sign in to Claude Trello Bridge").option("-s, --server <url>", "Server URL (default: http://localhost:3000)").action(async (opts) => {
|
|
144
|
+
const serverUrl = opts.server || getServerUrl();
|
|
145
|
+
console.log(chalk.bold("Sign in to Claude Trello Bridge"));
|
|
146
|
+
console.log(chalk.dim(`Server: ${serverUrl}
|
|
147
|
+
`));
|
|
148
|
+
const email = await input({ message: "Email:" });
|
|
149
|
+
const pass = await password({ message: "Password:" });
|
|
150
|
+
const spinner = ora("Signing in...").start();
|
|
151
|
+
try {
|
|
152
|
+
const { cookies, user } = await signIn(serverUrl, email, pass);
|
|
153
|
+
saveConfig({
|
|
154
|
+
serverUrl,
|
|
155
|
+
sessionCookie: cookies,
|
|
156
|
+
userEmail: user.email,
|
|
157
|
+
userName: user.name
|
|
158
|
+
});
|
|
159
|
+
spinner.succeed(
|
|
160
|
+
`Signed in as ${chalk.bold(user.name)} (${user.email})`
|
|
161
|
+
);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
spinner.fail(err instanceof Error ? err.message : "Sign-in failed");
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// src/commands/logout.ts
|
|
169
|
+
import { Command as Command2 } from "commander";
|
|
170
|
+
import chalk2 from "chalk";
|
|
171
|
+
var logoutCommand = new Command2("logout").description("Sign out and clear stored session").action(() => {
|
|
172
|
+
if (!isLoggedIn()) {
|
|
173
|
+
console.log(chalk2.dim("Not currently logged in."));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const config = getConfig();
|
|
177
|
+
clearConfig();
|
|
178
|
+
console.log(
|
|
179
|
+
chalk2.green(
|
|
180
|
+
`Signed out${config.userEmail ? ` (${config.userEmail})` : ""}.`
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// src/commands/run.ts
|
|
186
|
+
import { resolve } from "path";
|
|
187
|
+
import { Command as Command3 } from "commander";
|
|
188
|
+
import { select, confirm, input as input2 } from "@inquirer/prompts";
|
|
189
|
+
import chalk3 from "chalk";
|
|
190
|
+
import ora2 from "ora";
|
|
191
|
+
|
|
192
|
+
// src/lib/runner.ts
|
|
193
|
+
import {
|
|
194
|
+
query,
|
|
195
|
+
tool,
|
|
196
|
+
createSdkMcpServer
|
|
197
|
+
} from "@anthropic-ai/claude-agent-sdk";
|
|
198
|
+
import { z } from "zod";
|
|
199
|
+
|
|
200
|
+
// src/lib/trello.ts
|
|
201
|
+
var TRELLO_BASE = "https://api.trello.com/1";
|
|
202
|
+
function createTrelloClient(apiKey, token) {
|
|
203
|
+
async function trelloFetch(path, options) {
|
|
204
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
205
|
+
const url = `${TRELLO_BASE}${path}${separator}key=${apiKey}&token=${token}`;
|
|
206
|
+
const res = await fetch(url, {
|
|
207
|
+
...options,
|
|
208
|
+
headers: {
|
|
209
|
+
"Content-Type": "application/json",
|
|
210
|
+
...options?.headers ?? {}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
if (!res.ok) {
|
|
214
|
+
throw new Error(`Trello API error: ${res.status} ${res.statusText}`);
|
|
215
|
+
}
|
|
216
|
+
return res.json();
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
async updateCheckItem(cardId, checkItemId, state) {
|
|
220
|
+
await trelloFetch(`/cards/${cardId}/checkItem/${checkItemId}`, {
|
|
221
|
+
method: "PUT",
|
|
222
|
+
body: JSON.stringify({ state })
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
async moveCard(cardId, listId) {
|
|
226
|
+
await trelloFetch(`/cards/${cardId}`, {
|
|
227
|
+
method: "PUT",
|
|
228
|
+
body: JSON.stringify({ idList: listId })
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
async findOrCreateDoneList(boardId) {
|
|
232
|
+
const lists = await trelloFetch(
|
|
233
|
+
`/boards/${boardId}/lists?fields=id,name&filter=open`
|
|
234
|
+
);
|
|
235
|
+
const doneList = lists.find((l) => l.name.toLowerCase() === "done");
|
|
236
|
+
if (doneList) return doneList.id;
|
|
237
|
+
const created = await trelloFetch(
|
|
238
|
+
`/boards/${boardId}/lists`,
|
|
239
|
+
{
|
|
240
|
+
method: "POST",
|
|
241
|
+
body: JSON.stringify({ name: "Done" })
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
return created.id;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/lib/runner.ts
|
|
250
|
+
var SYSTEM_PROMPT = `You are operating on a codebase. You have been given a Trello board containing tasks.
|
|
251
|
+
Work through each card and checklist item in order.
|
|
252
|
+
For each checklist item you complete, call the check_trello_item tool with the checkItemId and cardId.
|
|
253
|
+
Do not mark items complete unless the code change has actually been made and verified.
|
|
254
|
+
After completing ALL checklist items on a card, call move_card_to_done with the cardId to move it to the Done list.
|
|
255
|
+
Once a card is in Done, do not interact with it again \u2014 move on to the next card.
|
|
256
|
+
Focus on one card at a time. Complete all its items, move it to Done, then proceed to the next.`;
|
|
257
|
+
function buildUserPrompt(boardData) {
|
|
258
|
+
return `Here is the Trello board with tasks to complete:
|
|
259
|
+
|
|
260
|
+
${JSON.stringify(boardData, null, 2)}`;
|
|
261
|
+
}
|
|
262
|
+
function launchSession(options) {
|
|
263
|
+
const { credentials, boardData, cwd, abortController } = options;
|
|
264
|
+
const trello = createTrelloClient(
|
|
265
|
+
credentials.trelloApiKey,
|
|
266
|
+
credentials.trelloToken
|
|
267
|
+
);
|
|
268
|
+
const activeBoardData = {
|
|
269
|
+
...boardData,
|
|
270
|
+
cards: boardData.doneListId ? boardData.cards.filter((c) => c.idList !== boardData.doneListId) : boardData.cards
|
|
271
|
+
};
|
|
272
|
+
const checkTrelloItem = tool(
|
|
273
|
+
"check_trello_item",
|
|
274
|
+
"Mark a Trello checklist item as complete once the corresponding code task is done.",
|
|
275
|
+
{
|
|
276
|
+
checkItemId: z.string().describe("The Trello checklist item ID"),
|
|
277
|
+
cardId: z.string().describe("The Trello card ID")
|
|
278
|
+
},
|
|
279
|
+
async ({ checkItemId, cardId }) => {
|
|
280
|
+
await trello.updateCheckItem(cardId, checkItemId, "complete");
|
|
281
|
+
return {
|
|
282
|
+
content: [
|
|
283
|
+
{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: `Marked checklist item ${checkItemId} as complete on card ${cardId}`
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
const moveCardToDone = tool(
|
|
292
|
+
"move_card_to_done",
|
|
293
|
+
"Move a Trello card to the Done list after all its checklist items are completed.",
|
|
294
|
+
{
|
|
295
|
+
cardId: z.string().describe("The Trello card ID to move to Done")
|
|
296
|
+
},
|
|
297
|
+
async ({ cardId }) => {
|
|
298
|
+
const doneListId = await trello.findOrCreateDoneList(boardData.board.id);
|
|
299
|
+
await trello.moveCard(cardId, doneListId);
|
|
300
|
+
return {
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: "text",
|
|
304
|
+
text: `Moved card ${cardId} to Done list`
|
|
305
|
+
}
|
|
306
|
+
]
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
const trelloServer = createSdkMcpServer({
|
|
311
|
+
name: "trello-tools",
|
|
312
|
+
tools: [checkTrelloItem, moveCardToDone]
|
|
313
|
+
});
|
|
314
|
+
return query({
|
|
315
|
+
prompt: buildUserPrompt(activeBoardData),
|
|
316
|
+
options: {
|
|
317
|
+
abortController,
|
|
318
|
+
cwd,
|
|
319
|
+
env: {
|
|
320
|
+
ANTHROPIC_API_KEY: credentials.anthropicApiKey,
|
|
321
|
+
CLAUDE_AGENT_SDK_CLIENT_APP: "claude-trello-cli/0.1.0"
|
|
322
|
+
},
|
|
323
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
324
|
+
permissionMode: "acceptEdits",
|
|
325
|
+
allowedTools: [
|
|
326
|
+
"mcp__trello-tools__check_trello_item",
|
|
327
|
+
"mcp__trello-tools__move_card_to_done"
|
|
328
|
+
],
|
|
329
|
+
maxTurns: 50,
|
|
330
|
+
mcpServers: {
|
|
331
|
+
"trello-tools": trelloServer
|
|
332
|
+
},
|
|
333
|
+
persistSession: false
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/commands/run.ts
|
|
339
|
+
var runCommand = new Command3("run").description("Select a Trello board and start a Claude Code session").option("-b, --board <id>", "Board ID (skip interactive selection)").option("-d, --dir <path>", "Working directory (default: current)").action(async (opts) => {
|
|
340
|
+
if (!isLoggedIn()) {
|
|
341
|
+
console.log(
|
|
342
|
+
chalk3.red("Not logged in. Run `claude-trello login` first.")
|
|
343
|
+
);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
const cwd = opts.dir ? resolve(opts.dir) : process.cwd();
|
|
347
|
+
let boardId = opts.board;
|
|
348
|
+
let boardName = "";
|
|
349
|
+
if (!boardId) {
|
|
350
|
+
const spinner = ora2("Fetching boards...").start();
|
|
351
|
+
let boards;
|
|
352
|
+
try {
|
|
353
|
+
boards = await getBoards();
|
|
354
|
+
spinner.stop();
|
|
355
|
+
} catch (err) {
|
|
356
|
+
spinner.fail(
|
|
357
|
+
err instanceof Error ? err.message : "Failed to fetch boards"
|
|
358
|
+
);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
if (boards.length === 0) {
|
|
362
|
+
console.log(
|
|
363
|
+
chalk3.red("No boards found. Create a board on Trello first.")
|
|
364
|
+
);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
boardId = await select({
|
|
368
|
+
message: "Select a board:",
|
|
369
|
+
choices: boards.map((b) => ({
|
|
370
|
+
name: b.name,
|
|
371
|
+
value: b.id,
|
|
372
|
+
description: b.desc?.slice(0, 60) || void 0
|
|
373
|
+
}))
|
|
374
|
+
});
|
|
375
|
+
boardName = boards.find((b) => b.id === boardId)?.name ?? boardId;
|
|
376
|
+
}
|
|
377
|
+
const dataSpinner = ora2("Fetching cards and checklists...").start();
|
|
378
|
+
let cardsResponse;
|
|
379
|
+
try {
|
|
380
|
+
cardsResponse = await getBoardData(boardId);
|
|
381
|
+
dataSpinner.stop();
|
|
382
|
+
} catch (err) {
|
|
383
|
+
dataSpinner.fail(
|
|
384
|
+
err instanceof Error ? err.message : "Failed to fetch board data"
|
|
385
|
+
);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
const { cards, lists, doneListId } = cardsResponse;
|
|
389
|
+
if (!boardName) boardName = boardId;
|
|
390
|
+
console.log(`
|
|
391
|
+
${chalk3.bold(boardName)}`);
|
|
392
|
+
console.log(chalk3.dim("\u2500".repeat(boardName.length)));
|
|
393
|
+
const doneCards = doneListId ? cards.filter((c) => c.idList === doneListId) : [];
|
|
394
|
+
const activeCards = doneListId ? cards.filter((c) => c.idList !== doneListId) : cards;
|
|
395
|
+
const listMap = /* @__PURE__ */ new Map();
|
|
396
|
+
for (const l of lists) listMap.set(l.id, l);
|
|
397
|
+
const byList = /* @__PURE__ */ new Map();
|
|
398
|
+
for (const card of activeCards) {
|
|
399
|
+
const existing = byList.get(card.idList) ?? [];
|
|
400
|
+
existing.push(card);
|
|
401
|
+
byList.set(card.idList, existing);
|
|
402
|
+
}
|
|
403
|
+
for (const [listId, listCards] of byList) {
|
|
404
|
+
const listName = listMap.get(listId)?.name ?? "Unknown";
|
|
405
|
+
console.log(
|
|
406
|
+
`
|
|
407
|
+
${chalk3.cyan.bold(listName)} (${listCards.length} card${listCards.length === 1 ? "" : "s"}):`
|
|
408
|
+
);
|
|
409
|
+
for (const card of listCards) {
|
|
410
|
+
const total = card.checklists.reduce(
|
|
411
|
+
(sum, cl) => sum + cl.checkItems.length,
|
|
412
|
+
0
|
|
413
|
+
);
|
|
414
|
+
const done = card.checklists.reduce(
|
|
415
|
+
(sum, cl) => sum + cl.checkItems.filter((i) => i.state === "complete").length,
|
|
416
|
+
0
|
|
417
|
+
);
|
|
418
|
+
const progress = total > 0 ? ` [${done}/${total}]` : "";
|
|
419
|
+
console.log(
|
|
420
|
+
` ${chalk3.white("\u2022")} ${card.name}${chalk3.dim(progress)}`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (doneCards.length > 0) {
|
|
425
|
+
console.log(
|
|
426
|
+
`
|
|
427
|
+
${chalk3.dim(`Done (${doneCards.length} card${doneCards.length === 1 ? "" : "s"}) [skipped]`)}`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (activeCards.length === 0) {
|
|
431
|
+
console.log(chalk3.dim("\n No active cards to work on."));
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
console.log(`
|
|
435
|
+
${chalk3.dim(`Working directory: ${cwd}`)}`);
|
|
436
|
+
const proceed = await confirm({
|
|
437
|
+
message: `Start Claude Code session? (${activeCards.length} active card${activeCards.length === 1 ? "" : "s"})`,
|
|
438
|
+
default: true
|
|
439
|
+
});
|
|
440
|
+
if (!proceed) {
|
|
441
|
+
console.log(chalk3.dim("Cancelled."));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const credSpinner = ora2("Loading credentials...").start();
|
|
445
|
+
let credentials;
|
|
446
|
+
try {
|
|
447
|
+
credentials = await getCredentials();
|
|
448
|
+
credSpinner.succeed("Credentials loaded");
|
|
449
|
+
} catch (err) {
|
|
450
|
+
credSpinner.fail(
|
|
451
|
+
err instanceof Error ? err.message : "Failed to load credentials"
|
|
452
|
+
);
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
const boardData = {
|
|
456
|
+
board: { id: boardId, name: boardName },
|
|
457
|
+
cards,
|
|
458
|
+
doneListId: doneListId ?? void 0
|
|
459
|
+
};
|
|
460
|
+
console.log(`
|
|
461
|
+
${chalk3.bold.blue("Starting Claude Code session...")}
|
|
462
|
+
`);
|
|
463
|
+
const abortController = new AbortController();
|
|
464
|
+
const sigintHandler = () => {
|
|
465
|
+
console.log(chalk3.dim("\n\nAborting session..."));
|
|
466
|
+
abortController.abort();
|
|
467
|
+
};
|
|
468
|
+
process.on("SIGINT", sigintHandler);
|
|
469
|
+
try {
|
|
470
|
+
const session = launchSession({
|
|
471
|
+
credentials,
|
|
472
|
+
boardData,
|
|
473
|
+
cwd,
|
|
474
|
+
abortController
|
|
475
|
+
});
|
|
476
|
+
for await (const message of session) {
|
|
477
|
+
await handleMessage(message, session);
|
|
478
|
+
}
|
|
479
|
+
console.log(`
|
|
480
|
+
${chalk3.green.bold("Session complete.")}
|
|
481
|
+
`);
|
|
482
|
+
} catch (err) {
|
|
483
|
+
if (abortController.signal.aborted) {
|
|
484
|
+
console.log(chalk3.dim("\nSession aborted."));
|
|
485
|
+
} else {
|
|
486
|
+
console.error(
|
|
487
|
+
chalk3.red(
|
|
488
|
+
`
|
|
489
|
+
Session error: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
490
|
+
)
|
|
491
|
+
);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
} finally {
|
|
495
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
async function handleMessage(message, session) {
|
|
499
|
+
switch (message.type) {
|
|
500
|
+
case "system": {
|
|
501
|
+
if (message.subtype === "init") {
|
|
502
|
+
console.log(
|
|
503
|
+
chalk3.dim(
|
|
504
|
+
` Session initialized (model: ${message.model})
|
|
505
|
+
`
|
|
506
|
+
)
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
case "assistant": {
|
|
512
|
+
const msg = message.message;
|
|
513
|
+
const content = msg?.content;
|
|
514
|
+
if (!Array.isArray(content)) break;
|
|
515
|
+
for (const block of content) {
|
|
516
|
+
if (block.type === "text" && block.text) {
|
|
517
|
+
console.log(block.text);
|
|
518
|
+
} else if (block.type === "tool_use" && block.name) {
|
|
519
|
+
const name = block.name;
|
|
520
|
+
const toolInput = block.input ?? {};
|
|
521
|
+
if (name === "mcp__trello-tools__check_trello_item") {
|
|
522
|
+
console.log(chalk3.green(` \u2713 Checked item on Trello`));
|
|
523
|
+
} else if (name === "mcp__trello-tools__move_card_to_done") {
|
|
524
|
+
console.log(chalk3.green.bold(` \u2713 Moved card to Done`));
|
|
525
|
+
} else if (name === "AskUserQuestion") {
|
|
526
|
+
const questions = toolInput.questions;
|
|
527
|
+
if (questions && questions.length > 0) {
|
|
528
|
+
const questionText = questions.map((q) => q.question).join("\n");
|
|
529
|
+
console.log(chalk3.yellow(`
|
|
530
|
+
? ${questionText}
|
|
531
|
+
`));
|
|
532
|
+
const answer = await input2({ message: ">" });
|
|
533
|
+
async function* userInput() {
|
|
534
|
+
yield {
|
|
535
|
+
type: "user",
|
|
536
|
+
message: {
|
|
537
|
+
role: "user",
|
|
538
|
+
content: answer
|
|
539
|
+
},
|
|
540
|
+
parent_tool_use_id: null,
|
|
541
|
+
session_id: ""
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
await session.streamInput(userInput());
|
|
545
|
+
}
|
|
546
|
+
} else {
|
|
547
|
+
console.log(chalk3.dim(` [${name}]`));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
case "result": {
|
|
554
|
+
const result = message.result ?? message.subtype;
|
|
555
|
+
console.log(chalk3.dim(`
|
|
556
|
+
Result: ${result}`));
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/commands/boards.ts
|
|
563
|
+
import { Command as Command4 } from "commander";
|
|
564
|
+
import chalk4 from "chalk";
|
|
565
|
+
import ora3 from "ora";
|
|
566
|
+
var boardsCommand = new Command4("boards").description("List your Trello boards").action(async () => {
|
|
567
|
+
if (!isLoggedIn()) {
|
|
568
|
+
console.log(
|
|
569
|
+
chalk4.red("Not logged in. Run `claude-trello login` first.")
|
|
570
|
+
);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
const spinner = ora3("Fetching boards...").start();
|
|
574
|
+
try {
|
|
575
|
+
const boards = await getBoards();
|
|
576
|
+
spinner.stop();
|
|
577
|
+
if (boards.length === 0) {
|
|
578
|
+
console.log(chalk4.dim("No boards found."));
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
console.log(chalk4.bold(`
|
|
582
|
+
Your Trello Boards (${boards.length}):
|
|
583
|
+
`));
|
|
584
|
+
for (const board of boards) {
|
|
585
|
+
console.log(` ${chalk4.cyan(board.name)} ${chalk4.dim(board.id)}`);
|
|
586
|
+
if (board.desc) {
|
|
587
|
+
console.log(` ${chalk4.dim(board.desc.slice(0, 80))}`);
|
|
588
|
+
}
|
|
589
|
+
console.log();
|
|
590
|
+
}
|
|
591
|
+
} catch (err) {
|
|
592
|
+
spinner.fail(
|
|
593
|
+
err instanceof Error ? err.message : "Failed to fetch boards"
|
|
594
|
+
);
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// src/commands/status.ts
|
|
600
|
+
import { Command as Command5 } from "commander";
|
|
601
|
+
import chalk5 from "chalk";
|
|
602
|
+
import ora4 from "ora";
|
|
603
|
+
var statusCommand = new Command5("status").description("Check connection and integration status").action(async () => {
|
|
604
|
+
const config = getConfig();
|
|
605
|
+
console.log(chalk5.bold("\nClaude Trello Bridge \u2014 Status\n"));
|
|
606
|
+
console.log(` Server: ${chalk5.dim(getServerUrl())}`);
|
|
607
|
+
if (!isLoggedIn()) {
|
|
608
|
+
console.log(` Auth: ${chalk5.red("Not logged in")}`);
|
|
609
|
+
console.log(chalk5.dim("\n Run `claude-trello login` to sign in.\n"));
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
console.log(
|
|
613
|
+
` Auth: ${chalk5.green("Signed in")} as ${config.userName ?? config.userEmail ?? "unknown"}`
|
|
614
|
+
);
|
|
615
|
+
const spinner = ora4("Checking integrations...").start();
|
|
616
|
+
try {
|
|
617
|
+
const status = await getIntegrationStatus();
|
|
618
|
+
spinner.stop();
|
|
619
|
+
console.log(
|
|
620
|
+
` Trello: ${status.trelloLinked ? chalk5.green("Connected") : chalk5.red("Not connected")}`
|
|
621
|
+
);
|
|
622
|
+
console.log(
|
|
623
|
+
` API Key: ${status.hasApiKey ? chalk5.green("Configured") : chalk5.red("Not set")}`
|
|
624
|
+
);
|
|
625
|
+
if (!status.trelloLinked || !status.hasApiKey) {
|
|
626
|
+
console.log(
|
|
627
|
+
chalk5.dim(
|
|
628
|
+
"\n Complete setup at your web dashboard to use the CLI.\n"
|
|
629
|
+
)
|
|
630
|
+
);
|
|
631
|
+
} else {
|
|
632
|
+
console.log(
|
|
633
|
+
chalk5.green("\n Ready to go! Run `claude-trello run` to start.\n")
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
} catch (err) {
|
|
637
|
+
spinner.fail(
|
|
638
|
+
err instanceof Error ? err.message : "Failed to check status"
|
|
639
|
+
);
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// src/index.ts
|
|
645
|
+
var program = new Command6();
|
|
646
|
+
program.name("claude-trello").description(
|
|
647
|
+
"Bridge Trello boards and Claude Code \u2014 work through tasks from your terminal"
|
|
648
|
+
).version("0.1.0");
|
|
649
|
+
program.addCommand(loginCommand);
|
|
650
|
+
program.addCommand(logoutCommand);
|
|
651
|
+
program.addCommand(runCommand);
|
|
652
|
+
program.addCommand(boardsCommand);
|
|
653
|
+
program.addCommand(statusCommand);
|
|
654
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-trello-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Claude Trello Bridge — point Claude Code at a Trello board and work through tasks from your terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-trello": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"prepublishOnly": "pnpm build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"trello",
|
|
20
|
+
"cli",
|
|
21
|
+
"ai",
|
|
22
|
+
"anthropic",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"automation"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.81",
|
|
32
|
+
"@anthropic-ai/claude-code": "^2.1.81",
|
|
33
|
+
"@inquirer/prompts": "^7.0.0",
|
|
34
|
+
"chalk": "^5.4.0",
|
|
35
|
+
"commander": "^13.0.0",
|
|
36
|
+
"ora": "^8.2.0",
|
|
37
|
+
"zod": "^3.24.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.10.2",
|
|
41
|
+
"tsup": "^8.4.0",
|
|
42
|
+
"tsx": "^4.19.0",
|
|
43
|
+
"typescript": "^5.7.2"
|
|
44
|
+
}
|
|
45
|
+
}
|