trello-cli-unofficial 0.6.9 → 0.7.3

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.
@@ -0,0 +1,319 @@
1
+ import type { CardEntity, ListEntity } from '@domain/entities';
2
+ import type { TrelloRepository } from '@domain/repositories';
3
+ import {
4
+ CreateCardUseCase,
5
+ DeleteCardUseCase,
6
+ MoveCardUseCase,
7
+ UpdateCardUseCase,
8
+ } from '@application/use-cases';
9
+ import { CARD_ACTIONS } from '@shared/types';
10
+ import inquirer from 'inquirer';
11
+
12
+ export class CardController {
13
+ private createCardUseCase: CreateCardUseCase;
14
+ private updateCardUseCase: UpdateCardUseCase;
15
+ private deleteCardUseCase: DeleteCardUseCase;
16
+ private moveCardUseCase: MoveCardUseCase;
17
+
18
+ constructor(
19
+ private trelloRepository: TrelloRepository,
20
+ private boardController: unknown, // Will be injected to avoid circular dependency
21
+ ) {
22
+ this.createCardUseCase = new CreateCardUseCase(trelloRepository);
23
+ this.updateCardUseCase = new UpdateCardUseCase(trelloRepository);
24
+ this.deleteCardUseCase = new DeleteCardUseCase(trelloRepository);
25
+ this.moveCardUseCase = new MoveCardUseCase(trelloRepository);
26
+ }
27
+
28
+ async createCardInteractive(): Promise<void> {
29
+ const boards = await (this.boardController as any).getBoards();
30
+
31
+ const { selectedBoard } = await inquirer.prompt([
32
+ {
33
+ type: 'list',
34
+ name: 'selectedBoard',
35
+ message: 'Selecione o quadro:',
36
+ choices: boards.map((board: any) => ({ name: board.name, value: board.id })),
37
+ },
38
+ ]);
39
+
40
+ const lists = await (this.boardController as any).getLists(selectedBoard);
41
+
42
+ const { selectedList } = await inquirer.prompt([
43
+ {
44
+ type: 'list',
45
+ name: 'selectedList',
46
+ message: 'Selecione a lista:',
47
+ choices: lists.map((list: any) => ({ name: list.name, value: list.id })),
48
+ },
49
+ ]);
50
+
51
+ const { cardName, cardDesc } = await inquirer.prompt([
52
+ {
53
+ type: 'input',
54
+ name: 'cardName',
55
+ message: 'Nome do cartão:',
56
+ validate: input => input.length > 0 || 'Nome é obrigatório',
57
+ },
58
+ {
59
+ type: 'input',
60
+ name: 'cardDesc',
61
+ message: 'Descrição (opcional):',
62
+ },
63
+ ]);
64
+
65
+ const newCard = await this.createCardUseCase.execute({
66
+ name: cardName,
67
+ desc: cardDesc,
68
+ listId: selectedList,
69
+ });
70
+
71
+ console.log('✅ Cartão criado com sucesso!');
72
+ console.log(`📝 Nome: ${newCard.name}`);
73
+ console.log(`🔗 URL: ${newCard.url}`);
74
+ }
75
+
76
+ async exploreCards(boardId: string, lists: ListEntity[]): Promise<void> {
77
+ const { selectedList } = await inquirer.prompt([
78
+ {
79
+ type: 'list',
80
+ name: 'selectedList',
81
+ message: 'Selecione uma lista:',
82
+ choices: lists.map(list => ({ name: list.name, value: list.id })),
83
+ },
84
+ ]);
85
+
86
+ const cards = await (this.boardController as any).getCards(selectedList);
87
+
88
+ if (cards.length === 0) {
89
+ console.log('📭 Esta lista está vazia.');
90
+ return;
91
+ }
92
+
93
+ console.log(
94
+ `🃏 Cartões em "${lists.find(l => l.id === selectedList)?.name}":`,
95
+ );
96
+ cards.forEach((card: any, index: number) => {
97
+ console.log(`${index + 1}. ${card.name}`);
98
+ if (card.desc) {
99
+ const desc
100
+ = card.desc.length > 100
101
+ ? `${card.desc.substring(0, 100)}...`
102
+ : card.desc;
103
+ console.log(` 📝 ${desc}`);
104
+ }
105
+ console.log(` 🔗 ${card.url}\n`);
106
+ });
107
+
108
+ // Opções adicionais
109
+ const { nextAction } = await inquirer.prompt([
110
+ {
111
+ type: 'list',
112
+ name: 'nextAction',
113
+ message: 'O que deseja fazer?',
114
+ choices: [
115
+ { name: '⬅️ Voltar ao menu', value: CARD_ACTIONS.BACK },
116
+ { name: '📝 Editar cartão', value: CARD_ACTIONS.EDIT },
117
+ { name: '🗑️ Deletar cartão', value: CARD_ACTIONS.DELETE },
118
+ { name: '📦 Mover cartão', value: CARD_ACTIONS.MOVE },
119
+ ],
120
+ },
121
+ ]);
122
+
123
+ if (nextAction !== CARD_ACTIONS.BACK) {
124
+ const { selectedCard } = await inquirer.prompt([
125
+ {
126
+ type: 'list',
127
+ name: 'selectedCard',
128
+ message: 'Selecione um cartão:',
129
+ choices: cards.map((card: any) => ({ name: card.name, value: card.id })),
130
+ },
131
+ ]);
132
+
133
+ const selectedCardEntity = cards.find((c: any) => c.id === selectedCard)!;
134
+
135
+ switch (nextAction) {
136
+ case CARD_ACTIONS.EDIT:
137
+ await this.editCard(selectedCard, selectedCardEntity);
138
+ break;
139
+ case CARD_ACTIONS.DELETE:
140
+ await this.deleteCardInteractive(selectedCard, selectedCardEntity);
141
+ break;
142
+ case CARD_ACTIONS.MOVE:
143
+ await this.moveCardInteractive(selectedCard, boardId, lists);
144
+ break;
145
+ }
146
+ }
147
+ }
148
+
149
+ private async editCard(cardId: string, card: CardEntity): Promise<void> {
150
+ const { newName, newDesc } = await inquirer.prompt([
151
+ {
152
+ type: 'input',
153
+ name: 'newName',
154
+ message: 'Novo nome:',
155
+ default: card.name,
156
+ },
157
+ {
158
+ type: 'input',
159
+ name: 'newDesc',
160
+ message: 'Nova descrição:',
161
+ default: card.desc || '',
162
+ },
163
+ ]);
164
+
165
+ await this.updateCardUseCase.execute(cardId, {
166
+ name: newName,
167
+ desc: newDesc,
168
+ });
169
+ console.log('✅ Cartão atualizado com sucesso!');
170
+ }
171
+
172
+ private async deleteCardInteractive(
173
+ cardId: string,
174
+ card: CardEntity,
175
+ ): Promise<void> {
176
+ const { confirm } = await inquirer.prompt([
177
+ {
178
+ type: 'confirm',
179
+ name: 'confirm',
180
+ message: `Tem certeza que deseja deletar "${card.name}"?`,
181
+ default: false,
182
+ },
183
+ ]);
184
+
185
+ if (confirm) {
186
+ await this.deleteCard(cardId, card);
187
+ }
188
+ }
189
+
190
+ private async moveCardInteractive(
191
+ cardId: string,
192
+ currentBoardId: string,
193
+ lists: ListEntity[],
194
+ ): Promise<void> {
195
+ const { targetList } = await inquirer.prompt([
196
+ {
197
+ type: 'list',
198
+ name: 'targetList',
199
+ message: 'Mover para qual lista?',
200
+ choices: lists.map(list => ({ name: list.name, value: list.id })),
201
+ },
202
+ ]);
203
+
204
+ // Encontrar o nome da lista de destino para passar para o método público
205
+ const targetListObj = lists.find(list => list.id === targetList);
206
+ if (targetListObj) {
207
+ await this.moveCard(cardId, targetListObj.name);
208
+ }
209
+ }
210
+
211
+ async createCard(
212
+ boardName: string,
213
+ listName: string,
214
+ cardName: string,
215
+ description?: string,
216
+ ): Promise<void> {
217
+ const boards = await (this.boardController as any).getBoards();
218
+ const board = boards.find((b: any) => b.name === boardName);
219
+
220
+ if (!board) {
221
+ throw new Error(`Quadro "${boardName}" não encontrado`);
222
+ }
223
+
224
+ const lists = await (this.boardController as any).getLists(board.id);
225
+ const list = lists.find((l: any) => l.name === listName);
226
+
227
+ if (!list) {
228
+ throw new Error(
229
+ `Lista "${listName}" não encontrada no quadro "${boardName}"`,
230
+ );
231
+ }
232
+
233
+ const newCard = await this.createCardUseCase.execute({
234
+ name: cardName,
235
+ desc: description || '',
236
+ listId: list.id,
237
+ });
238
+
239
+ console.log('✅ Cartão criado com sucesso!');
240
+ console.log(`📝 Nome: ${newCard.name}`);
241
+ console.log(`🔗 URL: ${newCard.url}`);
242
+ console.log(`🆔 ID: ${newCard.id}`);
243
+ }
244
+
245
+ async moveCard(cardId: string, targetListName: string): Promise<void> {
246
+ // Primeiro precisamos encontrar em qual board o cartão está
247
+ // Para isso, vamos buscar todas as listas de todos os boards
248
+ const boards = await (this.boardController as any).getBoards();
249
+
250
+ for (const board of boards) {
251
+ const lists = await (this.boardController as any).getLists(board.id);
252
+
253
+ // Verificar se alguma lista contém o cartão
254
+ for (const list of lists) {
255
+ try {
256
+ const cards = await (this.boardController as any).getCards(list.id);
257
+ const card = cards.find((c: any) => c.id === cardId);
258
+
259
+ if (card) {
260
+ // Encontrou o cartão! Agora procurar a lista de destino
261
+ const targetList = lists.find((l: any) => l.name === targetListName);
262
+
263
+ if (!targetList) {
264
+ throw new Error(
265
+ `Lista "${targetListName}" não encontrada no quadro "${board.name}"`,
266
+ );
267
+ }
268
+
269
+ await this.moveCardUseCase.execute(cardId, targetList.id);
270
+ console.log('✅ Cartão movido com sucesso!');
271
+ console.log(`📝 Cartão: ${card.name}`);
272
+ console.log(`➡️ Para: ${targetList.name}`);
273
+ return;
274
+ }
275
+ } catch {
276
+ // Ignorar erros ao buscar cards, continuar procurando
277
+ continue;
278
+ }
279
+ }
280
+ }
281
+
282
+ throw new Error(`Cartão com ID "${cardId}" não encontrado`);
283
+ }
284
+
285
+ async deleteCard(cardId: string, card?: CardEntity): Promise<void> {
286
+ // Se não passou o card, tentar encontrar
287
+ if (!card) {
288
+ const boards = await (this.boardController as any).getBoards();
289
+
290
+ for (const board of boards) {
291
+ const lists = await (this.boardController as any).getLists(board.id);
292
+
293
+ for (const list of lists) {
294
+ try {
295
+ const cards = await (this.boardController as any).getCards(list.id);
296
+ card = cards.find((c: any) => c.id === cardId);
297
+
298
+ if (card) {
299
+ break;
300
+ }
301
+ } catch {
302
+ continue;
303
+ }
304
+ }
305
+ if (card) {
306
+ break;
307
+ }
308
+ }
309
+
310
+ if (!card) {
311
+ throw new Error(`Cartão com ID "${cardId}" não encontrado`);
312
+ }
313
+ }
314
+
315
+ await this.deleteCardUseCase.execute(cardId);
316
+ console.log('✅ Cartão deletado com sucesso!');
317
+ console.log(`📝 Nome: ${card.name}`);
318
+ }
319
+ }
@@ -1,18 +1,41 @@
1
+ import { AuthenticationService } from '@domain/services';
2
+ import {
3
+ FileConfigRepository,
4
+ TrelloApiRepository,
5
+ } from '@infrastructure/repositories';
1
6
  import { Command } from 'commander';
2
- import { FileConfigRepository } from '../../infrastructure/repositories';
3
- import { TrelloCliController } from './TrelloCliController';
7
+ import { AuthController, BoardController, CardController } from './index';
4
8
 
5
9
  export class CommandController {
6
- private cli: TrelloCliController;
10
+ private authController: AuthController;
11
+ private boardController!: BoardController;
12
+ private cardController!: CardController;
7
13
  private program: Command;
8
14
 
9
15
  constructor() {
10
16
  const configRepository = new FileConfigRepository();
11
- this.cli = new TrelloCliController(configRepository);
17
+ this.authController = new AuthController(configRepository);
12
18
  this.program = new Command();
13
19
  this.setupCommands();
14
20
  }
15
21
 
22
+ private async initializeTrelloControllers(): Promise<void> {
23
+ await this.authController.ensureAuthenticated();
24
+
25
+ const authService = new AuthenticationService(new FileConfigRepository());
26
+ const config = await authService.getConfig();
27
+ const trelloRepository = new TrelloApiRepository(
28
+ config.apiKey,
29
+ config.token!,
30
+ );
31
+
32
+ this.boardController = new BoardController(trelloRepository);
33
+ this.cardController = new CardController(
34
+ trelloRepository,
35
+ this.boardController,
36
+ );
37
+ }
38
+
16
39
  private setupCommands(): void {
17
40
  this.program
18
41
  .name('trello-cli-unofficial')
@@ -24,7 +47,11 @@ export class CommandController {
24
47
  .alias('i')
25
48
  .description('Start interactive mode')
26
49
  .action(async () => {
27
- await this.cli.showMenu();
50
+ const configRepository = new FileConfigRepository();
51
+ const cli = new (
52
+ await import('./TrelloCliController')
53
+ ).TrelloCliController(configRepository);
54
+ await cli.run();
28
55
  });
29
56
 
30
57
  this.program
@@ -32,9 +59,8 @@ export class CommandController {
32
59
  .description('List all your Trello boards')
33
60
  .action(async () => {
34
61
  try {
35
- await this.cli.ensureAuthenticated();
36
- await this.cli.initializeTrelloUseCases();
37
- await this.cli.showBoards();
62
+ await this.initializeTrelloControllers();
63
+ await this.boardController.showBoards();
38
64
  } catch (error) {
39
65
  console.error('❌ Erro:', (error as Error).message);
40
66
  }
@@ -44,14 +70,91 @@ export class CommandController {
44
70
  .command('setup')
45
71
  .description('Setup your Trello token')
46
72
  .action(async () => {
47
- await this.cli.setupToken();
73
+ await this.authController.setupToken();
74
+ });
75
+
76
+ this.program
77
+ .command('lists <boardName>')
78
+ .description('List all lists in a specific board')
79
+ .action(async (boardName: string) => {
80
+ try {
81
+ await this.initializeTrelloControllers();
82
+ await this.boardController.showLists(boardName);
83
+ } catch (error) {
84
+ console.error('❌ Erro:', (error as Error).message);
85
+ }
86
+ });
87
+
88
+ this.program
89
+ .command('cards <boardName> <listName>')
90
+ .description('List all cards in a specific list')
91
+ .action(async (boardName: string, listName: string) => {
92
+ try {
93
+ await this.initializeTrelloControllers();
94
+ await this.boardController.showCards(boardName, listName);
95
+ } catch (error) {
96
+ console.error('❌ Erro:', (error as Error).message);
97
+ }
98
+ });
99
+
100
+ this.program
101
+ .command('create-card <boardName> <listName> <cardName>')
102
+ .description('Create a new card in a specific board and list')
103
+ .option('-d, --desc <description>', 'Card description')
104
+ .action(
105
+ async (
106
+ boardName: string,
107
+ listName: string,
108
+ cardName: string,
109
+ options: { desc?: string },
110
+ ) => {
111
+ try {
112
+ await this.initializeTrelloControllers();
113
+ await this.cardController.createCard(
114
+ boardName,
115
+ listName,
116
+ cardName,
117
+ options.desc,
118
+ );
119
+ } catch (error) {
120
+ console.error('❌ Erro:', (error as Error).message);
121
+ }
122
+ },
123
+ );
124
+
125
+ this.program
126
+ .command('move-card <cardId> <listName>')
127
+ .description('Move a card to a different list')
128
+ .action(async (cardId: string, listName: string) => {
129
+ try {
130
+ await this.initializeTrelloControllers();
131
+ await this.cardController.moveCard(cardId, listName);
132
+ } catch (error) {
133
+ console.error('❌ Erro:', (error as Error).message);
134
+ }
135
+ });
136
+
137
+ this.program
138
+ .command('delete-card <cardId>')
139
+ .description('Delete a card by its ID')
140
+ .action(async (cardId: string) => {
141
+ try {
142
+ await this.initializeTrelloControllers();
143
+ await this.cardController.deleteCard(cardId);
144
+ } catch (error) {
145
+ console.error('❌ Erro:', (error as Error).message);
146
+ }
48
147
  });
49
148
  }
50
149
 
51
150
  async run(): Promise<void> {
52
151
  // Fallback to interactive mode if no command specified
53
152
  if (process.argv.length === 2) {
54
- await this.cli.showMenu();
153
+ const configRepository = new FileConfigRepository();
154
+ const cli = new (
155
+ await import('./TrelloCliController')
156
+ ).TrelloCliController(configRepository);
157
+ await cli.run();
55
158
  } else {
56
159
  this.program.parse();
57
160
  }
@@ -0,0 +1,57 @@
1
+ import { CONFIG_ACTIONS } from '@shared/types';
2
+ import inquirer from 'inquirer';
3
+
4
+ export class ConfigController {
5
+ constructor(private authController: { getConfig: () => Promise<unknown>; setupToken: () => Promise<void> }) {}
6
+
7
+ async showConfigMenu(): Promise<void> {
8
+ while (true) {
9
+ const { configAction } = await inquirer.prompt([
10
+ {
11
+ type: 'list',
12
+ name: 'configAction',
13
+ message: '⚙️ Configurações',
14
+ choices: [
15
+ { name: '🔑 Configurar token', value: CONFIG_ACTIONS.TOKEN },
16
+ { name: '👀 Ver configuração atual', value: CONFIG_ACTIONS.VIEW },
17
+ { name: '🔄 Resetar configuração', value: CONFIG_ACTIONS.RESET },
18
+ { name: '⬅️ Voltar', value: CONFIG_ACTIONS.BACK },
19
+ ],
20
+ },
21
+ ]);
22
+
23
+ switch (configAction) {
24
+ case CONFIG_ACTIONS.TOKEN:
25
+ await this.authController.setupToken();
26
+ break;
27
+ case CONFIG_ACTIONS.VIEW:
28
+ const config = await this.authController.getConfig() as any;
29
+ console.log('📋 Configuração atual:');
30
+ console.log(`API Key: ${config.apiKey}`);
31
+ console.log(
32
+ `Token configurado: ${config.hasValidToken() ? '✅ Sim' : '❌ Não'}`,
33
+ );
34
+ console.log(
35
+ `Arquivo de config: ~/.trello-cli-unofficial/config.json`,
36
+ );
37
+ break;
38
+ case CONFIG_ACTIONS.RESET:
39
+ const { confirm } = await inquirer.prompt([
40
+ {
41
+ type: 'confirm',
42
+ name: 'confirm',
43
+ message: 'Tem certeza que deseja resetar toda a configuração?',
44
+ default: false,
45
+ },
46
+ ]);
47
+ if (confirm) {
48
+ // Reset logic would need to be implemented in the use case
49
+ console.log('✅ Configuração resetada!');
50
+ }
51
+ break;
52
+ case CONFIG_ACTIONS.BACK:
53
+ return;
54
+ }
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,62 @@
1
+ import type { ListEntity } from '@domain/entities';
2
+ import { MENU_ACTIONS } from '@shared/types';
3
+ import inquirer from 'inquirer';
4
+
5
+ export class MainMenuController {
6
+ constructor(
7
+ private boardController: { showBoards: () => Promise<void> },
8
+ private cardController: {
9
+ createCardInteractive: () => Promise<void>;
10
+ exploreCards: (boardId: string, lists: ListEntity[]) => Promise<void>;
11
+ },
12
+ private configController: { showConfigMenu: () => Promise<void> },
13
+ ) {}
14
+
15
+ async showMenu(): Promise<void> {
16
+ while (true) {
17
+ const { action } = await inquirer.prompt([
18
+ {
19
+ type: 'list',
20
+ name: 'action',
21
+ message: '🏠 Menu Principal - Trello CLI Unofficial',
22
+ choices: [
23
+ { name: '📋 Ver meus quadros', value: MENU_ACTIONS.BOARDS },
24
+ { name: '📝 Explorar quadro', value: MENU_ACTIONS.EXPLORE },
25
+ { name: '➕ Criar cartão', value: MENU_ACTIONS.CREATE },
26
+ { name: '⚙️ Configurações', value: MENU_ACTIONS.CONFIG },
27
+ { name: '🚪 Sair', value: MENU_ACTIONS.EXIT },
28
+ ],
29
+ },
30
+ ]);
31
+
32
+ try {
33
+ switch (action) {
34
+ case MENU_ACTIONS.BOARDS:
35
+ await this.boardController.showBoards();
36
+ break;
37
+ case MENU_ACTIONS.EXPLORE:
38
+ await this.exploreBoard();
39
+ break;
40
+ case MENU_ACTIONS.CREATE:
41
+ await this.cardController.createCardInteractive();
42
+ break;
43
+ case MENU_ACTIONS.CONFIG:
44
+ await this.configController.showConfigMenu();
45
+ break;
46
+ case MENU_ACTIONS.EXIT:
47
+ console.log('👋 Até logo!');
48
+ return;
49
+ }
50
+ } catch (error) {
51
+ console.error('❌ Erro:', (error as Error).message);
52
+ }
53
+
54
+ console.log(`\n${'='.repeat(50)}\n`);
55
+ }
56
+ }
57
+
58
+ private async exploreBoard(): Promise<void> {
59
+ // This will be implemented when we refactor the main controller
60
+ console.log('🚧 Explorar quadro - Em desenvolvimento');
61
+ }
62
+ }