trello-cli-unofficial 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 (41) hide show
  1. package/.eslintignore +26 -0
  2. package/CHANGELOG.md +48 -0
  3. package/LICENSE +21 -0
  4. package/README.md +261 -0
  5. package/bun.lock +788 -0
  6. package/dist/main.js +27751 -0
  7. package/install.sh +45 -0
  8. package/main.ts +19 -0
  9. package/package.json +75 -0
  10. package/src/application/index.ts +1 -0
  11. package/src/application/use-cases/AuthenticateUserUseCase.ts +47 -0
  12. package/src/application/use-cases/CreateCardUseCase.ts +14 -0
  13. package/src/application/use-cases/DeleteCardUseCase.ts +9 -0
  14. package/src/application/use-cases/GetBoardsUseCase.ts +10 -0
  15. package/src/application/use-cases/GetCardsUseCase.ts +10 -0
  16. package/src/application/use-cases/GetListsUseCase.ts +10 -0
  17. package/src/application/use-cases/MoveCardUseCase.ts +10 -0
  18. package/src/application/use-cases/UpdateCardUseCase.ts +17 -0
  19. package/src/application/use-cases/index.ts +8 -0
  20. package/src/domain/entities/Board.ts +17 -0
  21. package/src/domain/entities/Card.ts +53 -0
  22. package/src/domain/entities/Config.ts +20 -0
  23. package/src/domain/entities/List.ts +12 -0
  24. package/src/domain/entities/index.ts +4 -0
  25. package/src/domain/index.ts +3 -0
  26. package/src/domain/repositories/ConfigRepository.ts +6 -0
  27. package/src/domain/repositories/TrelloRepository.ts +11 -0
  28. package/src/domain/repositories/index.ts +2 -0
  29. package/src/domain/services/AuthenticationService.ts +26 -0
  30. package/src/domain/services/index.ts +1 -0
  31. package/src/index.ts +5 -0
  32. package/src/infrastructure/index.ts +1 -0
  33. package/src/infrastructure/repositories/FileConfigRepository.ts +51 -0
  34. package/src/infrastructure/repositories/TrelloApiRepository.ts +93 -0
  35. package/src/infrastructure/repositories/index.ts +2 -0
  36. package/src/presentation/cli/CommandController.ts +59 -0
  37. package/src/presentation/cli/TrelloCliController.ts +408 -0
  38. package/src/presentation/cli/index.ts +2 -0
  39. package/src/presentation/index.ts +1 -0
  40. package/src/shared/index.ts +1 -0
  41. package/src/shared/types.ts +31 -0
@@ -0,0 +1,59 @@
1
+ import { Command } from 'commander';
2
+ import { FileConfigRepository } from '../../infrastructure/repositories';
3
+ import { TrelloCliController } from './TrelloCliController';
4
+
5
+ export class CommandController {
6
+ private cli: TrelloCliController;
7
+ private program: Command;
8
+
9
+ constructor() {
10
+ const configRepository = new FileConfigRepository();
11
+ this.cli = new TrelloCliController(configRepository);
12
+ this.program = new Command();
13
+ this.setupCommands();
14
+ }
15
+
16
+ private setupCommands(): void {
17
+ this.program
18
+ .name('trello-cli-unofficial')
19
+ .description('Unofficial Trello CLI using Power-Up authentication')
20
+ .version('1.0.0');
21
+
22
+ this.program
23
+ .command('interactive')
24
+ .alias('i')
25
+ .description('Start interactive mode')
26
+ .action(async () => {
27
+ await this.cli.showMenu();
28
+ });
29
+
30
+ this.program
31
+ .command('boards')
32
+ .description('List all your Trello boards')
33
+ .action(async () => {
34
+ try {
35
+ await this.cli.ensureAuthenticated();
36
+ await this.cli.initializeTrelloUseCases();
37
+ await this.cli.showBoards();
38
+ } catch (error) {
39
+ console.error('❌ Erro:', (error as Error).message);
40
+ }
41
+ });
42
+
43
+ this.program
44
+ .command('setup')
45
+ .description('Setup your Trello token')
46
+ .action(async () => {
47
+ await this.cli.setupToken();
48
+ });
49
+ }
50
+
51
+ async run(): Promise<void> {
52
+ // Fallback to interactive mode if no command specified
53
+ if (process.argv.length === 2) {
54
+ await this.cli.showMenu();
55
+ } else {
56
+ this.program.parse();
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,408 @@
1
+ import type {
2
+ ConfigRepository,
3
+ TrelloRepository,
4
+ } from '../../domain/repositories';
5
+ import inquirer from 'inquirer';
6
+ import {
7
+ AuthenticateUserUseCase,
8
+ CreateCardUseCase,
9
+ DeleteCardUseCase,
10
+ GetBoardsUseCase,
11
+ GetCardsUseCase,
12
+ GetListsUseCase,
13
+ MoveCardUseCase,
14
+ UpdateCardUseCase,
15
+ } from '../../application';
16
+ import { TrelloApiRepository } from '../../infrastructure/repositories';
17
+ import { CARD_ACTIONS, CONFIG_ACTIONS, MENU_ACTIONS } from '../../shared';
18
+
19
+ export class TrelloCliController {
20
+ private authenticateUseCase: AuthenticateUserUseCase;
21
+ private getBoardsUseCase?: GetBoardsUseCase;
22
+ private getListsUseCase?: GetListsUseCase;
23
+ private getCardsUseCase?: GetCardsUseCase;
24
+ private createCardUseCase?: CreateCardUseCase;
25
+ private updateCardUseCase?: UpdateCardUseCase;
26
+ private deleteCardUseCase?: DeleteCardUseCase;
27
+ private moveCardUseCase?: MoveCardUseCase;
28
+
29
+ constructor(private configRepository: ConfigRepository) {
30
+ this.authenticateUseCase = new AuthenticateUserUseCase(configRepository);
31
+ }
32
+
33
+ async initializeTrelloUseCases(): Promise<void> {
34
+ const config = await this.authenticateUseCase.getConfig();
35
+
36
+ if (!config.hasValidToken()) {
37
+ throw new Error('Token não configurado');
38
+ }
39
+
40
+ const trelloRepository: TrelloRepository = new TrelloApiRepository(
41
+ config.apiKey,
42
+ config.token!,
43
+ );
44
+
45
+ this.getBoardsUseCase = new GetBoardsUseCase(trelloRepository);
46
+ this.getListsUseCase = new GetListsUseCase(trelloRepository);
47
+ this.getCardsUseCase = new GetCardsUseCase(trelloRepository);
48
+ this.createCardUseCase = new CreateCardUseCase(trelloRepository);
49
+ this.updateCardUseCase = new UpdateCardUseCase(trelloRepository);
50
+ this.deleteCardUseCase = new DeleteCardUseCase(trelloRepository);
51
+ this.moveCardUseCase = new MoveCardUseCase(trelloRepository);
52
+ }
53
+
54
+ async ensureAuthenticated(): Promise<void> {
55
+ const result = await this.authenticateUseCase.execute();
56
+ if (!result.success) {
57
+ console.log(result.message);
58
+ await this.setupToken();
59
+ }
60
+ }
61
+
62
+ async setupToken(): Promise<void> {
63
+ const { token } = await inquirer.prompt([
64
+ {
65
+ type: 'input',
66
+ name: 'token',
67
+ message: 'Digite seu token do Trello (ATTA...):',
68
+ validate: input =>
69
+ input.startsWith('ATTA') || 'Token deve começar com ATTA',
70
+ },
71
+ ]);
72
+
73
+ const result = await this.authenticateUseCase.execute(token);
74
+ console.log(result.message);
75
+ }
76
+
77
+ async showMenu(): Promise<void> {
78
+ await this.ensureAuthenticated();
79
+ await this.initializeTrelloUseCases();
80
+
81
+ while (true) {
82
+ const { action } = await inquirer.prompt([
83
+ {
84
+ type: 'list',
85
+ name: 'action',
86
+ message: '🏠 Menu Principal - Trello CLI Unofficial',
87
+ choices: [
88
+ { name: '📋 Ver meus quadros', value: MENU_ACTIONS.BOARDS },
89
+ { name: '📝 Explorar quadro', value: MENU_ACTIONS.EXPLORE },
90
+ { name: '➕ Criar cartão', value: MENU_ACTIONS.CREATE },
91
+ { name: '⚙️ Configurações', value: MENU_ACTIONS.CONFIG },
92
+ { name: '🚪 Sair', value: MENU_ACTIONS.EXIT },
93
+ ],
94
+ },
95
+ ]);
96
+
97
+ try {
98
+ switch (action) {
99
+ case MENU_ACTIONS.BOARDS:
100
+ await this.showBoards();
101
+ break;
102
+ case MENU_ACTIONS.EXPLORE:
103
+ await this.exploreBoard();
104
+ break;
105
+ case MENU_ACTIONS.CREATE:
106
+ await this.createCardInteractive();
107
+ break;
108
+ case MENU_ACTIONS.CONFIG:
109
+ await this.showConfigMenu();
110
+ break;
111
+ case MENU_ACTIONS.EXIT:
112
+ console.log('👋 Até logo!');
113
+ return;
114
+ }
115
+ } catch (error) {
116
+ console.error('❌ Erro:', (error as Error).message);
117
+ }
118
+
119
+ console.log(`\n${'='.repeat(50)}\n`);
120
+ }
121
+ }
122
+
123
+ async showBoards(): Promise<void> {
124
+ if (!this.getBoardsUseCase) {
125
+ throw new Error('Use cases não inicializados');
126
+ }
127
+
128
+ const boards = await this.getBoardsUseCase.execute();
129
+
130
+ console.log('📋 Seus Quadros do Trello:');
131
+ boards.forEach((board, index) => {
132
+ console.log(`${index + 1}. ${board.name}`);
133
+ console.log(` 🔗 ${board.url}`);
134
+ console.log(` 🆔 ${board.id}\n`);
135
+ });
136
+ }
137
+
138
+ private async exploreBoard(): Promise<void> {
139
+ if (
140
+ !this.getBoardsUseCase
141
+ || !this.getListsUseCase
142
+ || !this.getCardsUseCase
143
+ ) {
144
+ throw new Error('Use cases não inicializados');
145
+ }
146
+
147
+ const boards = await this.getBoardsUseCase.execute();
148
+
149
+ const { selectedBoard } = await inquirer.prompt([
150
+ {
151
+ type: 'list',
152
+ name: 'selectedBoard',
153
+ message: 'Selecione um quadro:',
154
+ choices: boards.map(board => ({ name: board.name, value: board.id })),
155
+ },
156
+ ]);
157
+
158
+ const lists = await this.getListsUseCase.execute(selectedBoard);
159
+
160
+ const { selectedList } = await inquirer.prompt([
161
+ {
162
+ type: 'list',
163
+ name: 'selectedList',
164
+ message: 'Selecione uma lista:',
165
+ choices: lists.map(list => ({ name: list.name, value: list.id })),
166
+ },
167
+ ]);
168
+
169
+ const cards = await this.getCardsUseCase.execute(selectedList);
170
+
171
+ if (cards.length === 0) {
172
+ console.log('📭 Esta lista está vazia.');
173
+ return;
174
+ }
175
+
176
+ console.log(
177
+ `🃏 Cartões em "${lists.find(l => l.id === selectedList)?.name}":`,
178
+ );
179
+ cards.forEach((card, index) => {
180
+ console.log(`${index + 1}. ${card.name}`);
181
+ if (card.desc) {
182
+ const desc
183
+ = card.desc.length > 100
184
+ ? `${card.desc.substring(0, 100)}...`
185
+ : card.desc;
186
+ console.log(` 📝 ${desc}`);
187
+ }
188
+ console.log(` 🔗 ${card.url}\n`);
189
+ });
190
+
191
+ // Opções adicionais
192
+ const { nextAction } = await inquirer.prompt([
193
+ {
194
+ type: 'list',
195
+ name: 'nextAction',
196
+ message: 'O que deseja fazer?',
197
+ choices: [
198
+ { name: '⬅️ Voltar ao menu', value: CARD_ACTIONS.BACK },
199
+ { name: '📝 Editar cartão', value: CARD_ACTIONS.EDIT },
200
+ { name: '🗑️ Deletar cartão', value: CARD_ACTIONS.DELETE },
201
+ { name: '📦 Mover cartão', value: CARD_ACTIONS.MOVE },
202
+ ],
203
+ },
204
+ ]);
205
+
206
+ if (nextAction !== CARD_ACTIONS.BACK) {
207
+ const { selectedCard } = await inquirer.prompt([
208
+ {
209
+ type: 'list',
210
+ name: 'selectedCard',
211
+ message: 'Selecione um cartão:',
212
+ choices: cards.map(card => ({ name: card.name, value: card.id })),
213
+ },
214
+ ]);
215
+
216
+ const selectedCardEntity = cards.find(c => c.id === selectedCard)!;
217
+
218
+ switch (nextAction) {
219
+ case CARD_ACTIONS.EDIT:
220
+ await this.editCard(selectedCard, selectedCardEntity);
221
+ break;
222
+ case CARD_ACTIONS.DELETE:
223
+ await this.deleteCard(selectedCard, selectedCardEntity);
224
+ break;
225
+ case CARD_ACTIONS.MOVE:
226
+ await this.moveCard(selectedCard, selectedBoard, lists);
227
+ break;
228
+ }
229
+ }
230
+ }
231
+
232
+ private async createCardInteractive(): Promise<void> {
233
+ if (
234
+ !this.getBoardsUseCase
235
+ || !this.getListsUseCase
236
+ || !this.createCardUseCase
237
+ ) {
238
+ throw new Error('Use cases não inicializados');
239
+ }
240
+
241
+ const boards = await this.getBoardsUseCase.execute();
242
+
243
+ const { selectedBoard } = await inquirer.prompt([
244
+ {
245
+ type: 'list',
246
+ name: 'selectedBoard',
247
+ message: 'Selecione o quadro:',
248
+ choices: boards.map(board => ({ name: board.name, value: board.id })),
249
+ },
250
+ ]);
251
+
252
+ const lists = await this.getListsUseCase.execute(selectedBoard);
253
+
254
+ const { selectedList } = await inquirer.prompt([
255
+ {
256
+ type: 'list',
257
+ name: 'selectedList',
258
+ message: 'Selecione a lista:',
259
+ choices: lists.map(list => ({ name: list.name, value: list.id })),
260
+ },
261
+ ]);
262
+
263
+ const { cardName, cardDesc } = await inquirer.prompt([
264
+ {
265
+ type: 'input',
266
+ name: 'cardName',
267
+ message: 'Nome do cartão:',
268
+ validate: input => input.length > 0 || 'Nome é obrigatório',
269
+ },
270
+ {
271
+ type: 'input',
272
+ name: 'cardDesc',
273
+ message: 'Descrição (opcional):',
274
+ },
275
+ ]);
276
+
277
+ const newCard = await this.createCardUseCase.execute({
278
+ name: cardName,
279
+ desc: cardDesc,
280
+ listId: selectedList,
281
+ });
282
+
283
+ console.log('✅ Cartão criado com sucesso!');
284
+ console.log(`📝 Nome: ${newCard.name}`);
285
+ console.log(`🔗 URL: ${newCard.url}`);
286
+ }
287
+
288
+ private async editCard(cardId: string, card: any): Promise<void> {
289
+ if (!this.updateCardUseCase) {
290
+ throw new Error('Use cases não inicializados');
291
+ }
292
+
293
+ const { newName, newDesc } = await inquirer.prompt([
294
+ {
295
+ type: 'input',
296
+ name: 'newName',
297
+ message: 'Novo nome:',
298
+ default: card.name,
299
+ },
300
+ {
301
+ type: 'input',
302
+ name: 'newDesc',
303
+ message: 'Nova descrição:',
304
+ default: card.desc || '',
305
+ },
306
+ ]);
307
+
308
+ await this.updateCardUseCase.execute(cardId, {
309
+ name: newName,
310
+ desc: newDesc,
311
+ });
312
+ console.log('✅ Cartão atualizado com sucesso!');
313
+ }
314
+
315
+ private async deleteCard(cardId: string, card: any): Promise<void> {
316
+ if (!this.deleteCardUseCase) {
317
+ throw new Error('Use cases não inicializados');
318
+ }
319
+
320
+ const { confirm } = await inquirer.prompt([
321
+ {
322
+ type: 'confirm',
323
+ name: 'confirm',
324
+ message: `Tem certeza que deseja deletar "${card.name}"?`,
325
+ default: false,
326
+ },
327
+ ]);
328
+
329
+ if (confirm) {
330
+ await this.deleteCardUseCase.execute(cardId);
331
+ console.log('✅ Cartão deletado com sucesso!');
332
+ }
333
+ }
334
+
335
+ private async moveCard(
336
+ cardId: string,
337
+ currentBoardId: string,
338
+ lists: any[],
339
+ ): Promise<void> {
340
+ if (!this.moveCardUseCase) {
341
+ throw new Error('Use cases não inicializados');
342
+ }
343
+
344
+ const { targetList } = await inquirer.prompt([
345
+ {
346
+ type: 'list',
347
+ name: 'targetList',
348
+ message: 'Mover para qual lista?',
349
+ choices: lists.map(list => ({ name: list.name, value: list.id })),
350
+ },
351
+ ]);
352
+
353
+ await this.moveCardUseCase.execute(cardId, targetList);
354
+ console.log('✅ Cartão movido com sucesso!');
355
+ }
356
+
357
+ private async showConfigMenu(): Promise<void> {
358
+ while (true) {
359
+ const { configAction } = await inquirer.prompt([
360
+ {
361
+ type: 'list',
362
+ name: 'configAction',
363
+ message: '⚙️ Configurações',
364
+ choices: [
365
+ { name: '🔑 Configurar token', value: CONFIG_ACTIONS.TOKEN },
366
+ { name: '👀 Ver configuração atual', value: CONFIG_ACTIONS.VIEW },
367
+ { name: '🔄 Resetar configuração', value: CONFIG_ACTIONS.RESET },
368
+ { name: '⬅️ Voltar', value: CONFIG_ACTIONS.BACK },
369
+ ],
370
+ },
371
+ ]);
372
+
373
+ switch (configAction) {
374
+ case CONFIG_ACTIONS.TOKEN:
375
+ await this.setupToken();
376
+ await this.initializeTrelloUseCases(); // Re-initialize with new token
377
+ break;
378
+ case CONFIG_ACTIONS.VIEW:
379
+ const config = await this.authenticateUseCase.getConfig();
380
+ console.log('📋 Configuração atual:');
381
+ console.log(`API Key: ${config.apiKey}`);
382
+ console.log(
383
+ `Token configurado: ${config.hasValidToken() ? '✅ Sim' : '❌ Não'}`,
384
+ );
385
+ console.log(
386
+ `Arquivo de config: ~/.trello-cli-unofficial/config.json`,
387
+ );
388
+ break;
389
+ case CONFIG_ACTIONS.RESET:
390
+ const { confirm } = await inquirer.prompt([
391
+ {
392
+ type: 'confirm',
393
+ name: 'confirm',
394
+ message: 'Tem certeza que deseja resetar toda a configuração?',
395
+ default: false,
396
+ },
397
+ ]);
398
+ if (confirm) {
399
+ // Reset logic would need to be implemented in the use case
400
+ console.log('✅ Configuração resetada!');
401
+ }
402
+ break;
403
+ case CONFIG_ACTIONS.BACK:
404
+ return;
405
+ }
406
+ }
407
+ }
408
+ }
@@ -0,0 +1,2 @@
1
+ export * from './CommandController';
2
+ export * from './TrelloCliController';
@@ -0,0 +1 @@
1
+ export * from './cli';
@@ -0,0 +1 @@
1
+ export * from './types';
@@ -0,0 +1,31 @@
1
+ export interface CliChoice<T = string> {
2
+ name: string;
3
+ value: T;
4
+ }
5
+
6
+ export interface MenuAction {
7
+ name: string;
8
+ value: string;
9
+ }
10
+
11
+ export const MENU_ACTIONS = {
12
+ BOARDS: 'boards',
13
+ EXPLORE: 'explore',
14
+ CREATE: 'create',
15
+ CONFIG: 'config',
16
+ EXIT: 'exit',
17
+ } as const;
18
+
19
+ export const CONFIG_ACTIONS = {
20
+ TOKEN: 'token',
21
+ VIEW: 'view',
22
+ RESET: 'reset',
23
+ BACK: 'back',
24
+ } as const;
25
+
26
+ export const CARD_ACTIONS = {
27
+ BACK: 'back',
28
+ EDIT: 'edit',
29
+ DELETE: 'delete',
30
+ MOVE: 'move',
31
+ } as const;