trello-cli-unofficial 0.16.0 → 0.17.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/CHANGELOG.md +12 -0
- package/README.md +93 -15
- package/dist/main.js +134 -66
- package/package.json +1 -1
- package/src/application/use-cases/SearchCardsUseCase.ts +41 -0
- package/src/application/use-cases/index.ts +1 -0
- package/src/domain/repositories/TrelloRepository.ts +8 -0
- package/src/i18n/locales/en.json +16 -1
- package/src/i18n/locales/pt-BR.json +16 -1
- package/src/infrastructure/repositories/TrelloApiRepository.ts +17 -1
- package/src/presentation/cli/AuthController.ts +20 -11
- package/src/presentation/cli/CardController.ts +27 -69
- package/src/presentation/cli/CommandController.ts +25 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [0.17.0](https://github.com/JaegerCaiser/trello-cli-unofficial/compare/v0.16.0...v0.17.0) (2026-04-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* handle SIGINT in auth setup prompt with translated cancel message ([f352e4f](https://github.com/JaegerCaiser/trello-cli-unofficial/commit/f352e4f5c4fe62b77573cbdb915cddbd4ae275cb))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **search:** support advanced card search filters and pagination ([a5ebf31](https://github.com/JaegerCaiser/trello-cli-unofficial/commit/a5ebf31c434b4168d9745be1a99331dd536704a8))
|
|
12
|
+
|
|
1
13
|
# [0.16.0](https://github.com/JaegerCaiser/trello-cli-unofficial/compare/v0.15.1...v0.16.0) (2026-04-09)
|
|
2
14
|
|
|
3
15
|
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Trello CLI Unofficial
|
|
2
2
|
|
|
3
|
+
*Read this in other languages: [English](#-english) | [Português](#-português)*
|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/trello-cli-unofficial)
|
|
4
6
|
[](https://opensource.org/licenses/MIT)
|
|
5
7
|
[](https://bun.sh)
|
|
@@ -8,9 +10,93 @@
|
|
|
8
10
|
[](https://github.com/JaegerCaiser/trello-cli-unofficial/actions)
|
|
9
11
|
[](https://github.com/JaegerCaiser/trello-cli-unofficial/actions)
|
|
10
12
|
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🇺🇸 English
|
|
16
|
+
|
|
17
|
+
An unofficial Trello CLI with Power-Up authentication, built with Bun and TypeScript. It allows you to manage boards, lists, and cards with modern outputs and multi-profile support.
|
|
18
|
+
|
|
19
|
+
### Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g trello-cli-unofficial
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tcu setup
|
|
27
|
+
tcu interactive
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 🔑 Configuring the Trello Token
|
|
31
|
+
|
|
32
|
+
On the first run, the CLI will ask for your Trello token (via Power-Up authentication). Follow these steps:
|
|
33
|
+
|
|
34
|
+
**Step 1 — Access the Power-Ups admin dashboard:**
|
|
35
|
+
Open in your browser: **https://trello.com/power-ups/admin**
|
|
36
|
+
Log in to Trello if necessary.
|
|
37
|
+
|
|
38
|
+
**Step 2 — Create a new Power-Up:**
|
|
39
|
+
Click on **"New"** and fill in any name (e.g., `My CLI`). The name doesn't matter — it's just to identify the app in your account.
|
|
40
|
+
> If you have created a Power-Up before, simply select it from the list.
|
|
41
|
+
|
|
42
|
+
**Step 3 — Generate the token manually:**
|
|
43
|
+
Inside your Power-Up's page, you will see a message on the right panel:
|
|
44
|
+
> *"Most developers will need to ask each user to authorize their app. If you are building an app for yourself or doing local testing, you can manually generate a **token**."*
|
|
45
|
+
|
|
46
|
+
Click on the highlighted word **token** in that message. A new page will open asking for confirmation — click **"Allow"**.
|
|
47
|
+
|
|
48
|
+
**Step 4 — Copy the token:**
|
|
49
|
+
The page will display a long sequence of characters starting with `ATTA`. Copy the entire string.
|
|
50
|
+
|
|
51
|
+
**Step 5 — Paste it into the terminal:**
|
|
52
|
+
When the CLI asks `Please enter your Trello token:`, paste the token and press Enter.
|
|
53
|
+
|
|
54
|
+
> **Tip:** The token must always start with `ATTA`. If the CLI rejects your input, make sure you copied the full token without any extra spaces.
|
|
55
|
+
|
|
56
|
+
> **Security:** The token is saved locally in `~/.trello-cli-unofficial/config.json`. To revoke access, go to [trello.com/account](https://trello.com/account) → **Power-Ups & Tokens** and revoke the token for this application.
|
|
57
|
+
|
|
58
|
+
### Features
|
|
59
|
+
|
|
60
|
+
- 🚀 Interactive mode with a guided menu
|
|
61
|
+
- 📊 Output in JSON, table, and CSV formats
|
|
62
|
+
- 🌍 i18n support (English / pt-BR)
|
|
63
|
+
- 🔐 Power-Up authentication for Trello
|
|
64
|
+
- 👤 Multi-profile support with persistent configuration
|
|
65
|
+
|
|
66
|
+
### Commands
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
tcu boards
|
|
70
|
+
|
|
71
|
+
tcu lists
|
|
72
|
+
|
|
73
|
+
tcu cards
|
|
74
|
+
|
|
75
|
+
tcu checklists
|
|
76
|
+
|
|
77
|
+
tcu config
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
*To see all available parameters and options, run `tcu <command> --help` in your terminal.*
|
|
81
|
+
|
|
82
|
+
### 📚 Full Documentation
|
|
83
|
+
|
|
84
|
+
- [./docs/commands.md](./docs/commands.md)
|
|
85
|
+
- [./docs/architecture.md](./docs/architecture.md)
|
|
86
|
+
- [./docs/migration-guide.md](./docs/migration-guide.md)
|
|
87
|
+
- [./docs/troubleshooting.md](./docs/troubleshooting.md)
|
|
88
|
+
|
|
89
|
+
### Contributing
|
|
90
|
+
|
|
91
|
+
Want to help improve the project? See our [Contributing Guide](CONTRIBUTING.md).
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 🇧🇷 Português
|
|
96
|
+
|
|
11
97
|
Um CLI não-oficial para Trello com autenticação Power-Up, feito em Bun e TypeScript. Permite gerenciar boards, lists e cards com saídas modernas e suporte a múltiplos perfis.
|
|
12
98
|
|
|
13
|
-
|
|
99
|
+
### Quick Start
|
|
14
100
|
|
|
15
101
|
```bash
|
|
16
102
|
npm install -g trello-cli-unofficial
|
|
@@ -21,43 +107,35 @@ tcu setup
|
|
|
21
107
|
tcu interactive
|
|
22
108
|
```
|
|
23
109
|
|
|
24
|
-
|
|
110
|
+
### 🔑 Configurando o Token do Trello
|
|
25
111
|
|
|
26
112
|
Na primeira execução o CLI vai pedir seu token do Trello (autenticação via Power-Up). Siga os passos abaixo:
|
|
27
113
|
|
|
28
114
|
**Passo 1 — Acesse o painel de administração de Power-Ups:**
|
|
29
|
-
|
|
30
115
|
Abra no navegador: **https://trello.com/power-ups/admin**
|
|
31
|
-
|
|
32
116
|
Faça login no Trello se necessário.
|
|
33
117
|
|
|
34
118
|
**Passo 2 — Crie um novo Power-Up:**
|
|
35
|
-
|
|
36
119
|
Clique em **"New"** e preencha com qualquer nome (ex: `Meu CLI`). O nome não importa — é só para identificar o app na sua conta.
|
|
37
|
-
|
|
38
120
|
> Se você já criou um Power-Up antes, basta selecioná-lo na lista.
|
|
39
121
|
|
|
40
122
|
**Passo 3 — Gere o token manualmente:**
|
|
41
|
-
|
|
42
123
|
Dentro da página do seu Power-Up, você verá no painel à direita a mensagem:
|
|
43
|
-
|
|
44
124
|
> *"A maioria dos desenvolvedores precisará solicitar a cada usuário que autorize seu aplicativo. Se você deseja criar um aplicativo para si mesmo ou está fazendo testes locais, é possível gerar um **token** manualmente."*
|
|
45
125
|
|
|
46
126
|
Clique na palavra **token** destacada nessa mensagem. Uma nova página abrirá pedindo confirmação — clique em **"Permitir"**.
|
|
47
127
|
|
|
48
128
|
**Passo 4 — Copie o token:**
|
|
49
|
-
|
|
50
129
|
A página exibirá uma longa sequência de caracteres começando com `ATTA`. Copie tudo.
|
|
51
130
|
|
|
52
131
|
**Passo 5 — Cole no terminal:**
|
|
53
|
-
|
|
54
132
|
Quando o CLI perguntar `Por favor, insira seu token do Trello:`, cole o token e pressione Enter.
|
|
55
133
|
|
|
56
134
|
> **Dica:** O token começa obrigatoriamente com `ATTA`. Se o CLI rejeitar sua entrada, verifique se copiou o token completo sem espaços extras.
|
|
57
135
|
|
|
58
136
|
> **Segurança:** O token fica salvo localmente em `~/.trello-cli-unofficial/config.json`. Para revogar o acesso, acesse [trello.com/account](https://trello.com/account) → **Power-Ups & Tokens** e revogue o token desta aplicação.
|
|
59
137
|
|
|
60
|
-
|
|
138
|
+
### Features
|
|
61
139
|
|
|
62
140
|
- 🚀 Modo interativo com menu guiado
|
|
63
141
|
- 📊 Saída em JSON, table e CSV
|
|
@@ -65,7 +143,7 @@ Quando o CLI perguntar `Por favor, insira seu token do Trello:`, cole o token e
|
|
|
65
143
|
- 🔐 Autenticação Power-Up para Trello
|
|
66
144
|
- 👤 Suporte a múltiplos perfis e configuração persistente
|
|
67
145
|
|
|
68
|
-
|
|
146
|
+
### Commands
|
|
69
147
|
|
|
70
148
|
```bash
|
|
71
149
|
tcu boards
|
|
@@ -81,13 +159,13 @@ tcu config
|
|
|
81
159
|
|
|
82
160
|
*Para ver todos os parâmetros e opções, execute `tcu <comando> --help` no seu terminal.*
|
|
83
161
|
|
|
84
|
-
|
|
162
|
+
### 📚 Documentação Completa
|
|
85
163
|
|
|
86
164
|
- [./docs/commands.md](./docs/commands.md)
|
|
87
165
|
- [./docs/architecture.md](./docs/architecture.md)
|
|
88
166
|
- [./docs/migration-guide.md](./docs/migration-guide.md)
|
|
89
167
|
- [./docs/troubleshooting.md](./docs/troubleshooting.md)
|
|
90
168
|
|
|
91
|
-
|
|
169
|
+
### Contribuição
|
|
92
170
|
|
|
93
|
-
Quer ajudar a melhorar o projeto? Veja nosso [Guia de Contribuição](CONTRIBUTING.md).
|
|
171
|
+
Quer ajudar a melhorar o projeto? Veja nosso [Guia de Contribuição](CONTRIBUTING.md).
|
package/dist/main.js
CHANGED
|
@@ -2850,6 +2850,7 @@ var require_en = __commonJS((exports, module) => {
|
|
|
2850
2850
|
enterToken: "Please enter your Trello token:",
|
|
2851
2851
|
tokenInvalid: "❌ Invalid token! Token must start with 'ATTA' and be at least 11 characters long.",
|
|
2852
2852
|
tokenSaved: "✅ Token saved successfully!",
|
|
2853
|
+
setupCancelled: "⚠️ Setup cancelled by the user.",
|
|
2853
2854
|
authenticated: "✅ You are already authenticated!"
|
|
2854
2855
|
},
|
|
2855
2856
|
menu: {
|
|
@@ -2955,7 +2956,8 @@ var require_en = __commonJS((exports, module) => {
|
|
|
2955
2956
|
validation: {
|
|
2956
2957
|
requiredName: "Name is required",
|
|
2957
2958
|
requiredCardName: "Card name is required",
|
|
2958
|
-
cardNameCannotBeEmpty: "Card name cannot be empty"
|
|
2959
|
+
cardNameCannotBeEmpty: "Card name cannot be empty",
|
|
2960
|
+
requiredSearchQuery: "Search query cannot be empty"
|
|
2959
2961
|
},
|
|
2960
2962
|
actions: {
|
|
2961
2963
|
view: "👁️ View Details",
|
|
@@ -2977,6 +2979,11 @@ var require_en = __commonJS((exports, module) => {
|
|
|
2977
2979
|
checklistProgress: " [{{done}}/{{total}}]",
|
|
2978
2980
|
checklistItemDone: " ☑ {{name}}",
|
|
2979
2981
|
checklistItemPending: " ☐ {{name}}"
|
|
2982
|
+
},
|
|
2983
|
+
search: {
|
|
2984
|
+
results: '🔍 Results for "{{query}}" ({{count}} found):',
|
|
2985
|
+
empty: '🔍 No cards found for "{{query}}".',
|
|
2986
|
+
pagination: "📄 Page {{page}} (limit: {{limit}})"
|
|
2980
2987
|
}
|
|
2981
2988
|
},
|
|
2982
2989
|
checklist: {
|
|
@@ -3144,6 +3151,14 @@ var require_en = __commonJS((exports, module) => {
|
|
|
3144
3151
|
},
|
|
3145
3152
|
delete: {
|
|
3146
3153
|
description: "Delete a card"
|
|
3154
|
+
},
|
|
3155
|
+
search: {
|
|
3156
|
+
description: "Search cards by text",
|
|
3157
|
+
boardIdOption: "Filter by board ID",
|
|
3158
|
+
listIdOption: "Filter by list ID",
|
|
3159
|
+
labelsOption: "Filter by labels (comma-separated)",
|
|
3160
|
+
limitOption: "Maximum number of results",
|
|
3161
|
+
pageOption: "Page number (starts at 0)"
|
|
3147
3162
|
}
|
|
3148
3163
|
},
|
|
3149
3164
|
checklists: {
|
|
@@ -3218,6 +3233,7 @@ var require_pt_BR = __commonJS((exports, module) => {
|
|
|
3218
3233
|
enterToken: "Por favor, insira seu token do Trello:",
|
|
3219
3234
|
tokenInvalid: "❌ Token inválido! O token deve começar com 'ATTA' e ter pelo menos 11 caracteres.",
|
|
3220
3235
|
tokenSaved: "✅ Token salvo com sucesso!",
|
|
3236
|
+
setupCancelled: "⚠️ Configuração cancelada pelo usuário.",
|
|
3221
3237
|
authenticated: "✅ Você já está autenticado!"
|
|
3222
3238
|
},
|
|
3223
3239
|
menu: {
|
|
@@ -3323,7 +3339,8 @@ var require_pt_BR = __commonJS((exports, module) => {
|
|
|
3323
3339
|
validation: {
|
|
3324
3340
|
requiredName: "Nome é obrigatório",
|
|
3325
3341
|
requiredCardName: "Nome do cartão é obrigatório",
|
|
3326
|
-
cardNameCannotBeEmpty: "Nome do cartão não pode estar vazio"
|
|
3342
|
+
cardNameCannotBeEmpty: "Nome do cartão não pode estar vazio",
|
|
3343
|
+
requiredSearchQuery: "A consulta de busca não pode estar vazia"
|
|
3327
3344
|
},
|
|
3328
3345
|
actions: {
|
|
3329
3346
|
view: "👁️ Ver Detalhes",
|
|
@@ -3345,6 +3362,11 @@ var require_pt_BR = __commonJS((exports, module) => {
|
|
|
3345
3362
|
checklistProgress: " [{{done}}/{{total}}]",
|
|
3346
3363
|
checklistItemDone: " ☑ {{name}}",
|
|
3347
3364
|
checklistItemPending: " ☐ {{name}}"
|
|
3365
|
+
},
|
|
3366
|
+
search: {
|
|
3367
|
+
results: '🔍 Resultados para "{{query}}" ({{count}} encontrado(s)):',
|
|
3368
|
+
empty: '🔍 Nenhum card encontrado para "{{query}}".',
|
|
3369
|
+
pagination: "📄 Página {{page}} (limite: {{limit}})"
|
|
3348
3370
|
}
|
|
3349
3371
|
},
|
|
3350
3372
|
checklist: {
|
|
@@ -3512,6 +3534,14 @@ var require_pt_BR = __commonJS((exports, module) => {
|
|
|
3512
3534
|
},
|
|
3513
3535
|
delete: {
|
|
3514
3536
|
description: "Deleta um card"
|
|
3537
|
+
},
|
|
3538
|
+
search: {
|
|
3539
|
+
description: "Buscar cards por texto",
|
|
3540
|
+
boardIdOption: "Filtrar por ID de board",
|
|
3541
|
+
listIdOption: "Filtrar por ID de lista",
|
|
3542
|
+
labelsOption: "Filtrar por labels (separadas por vírgula)",
|
|
3543
|
+
limitOption: "Número máximo de resultados",
|
|
3544
|
+
pageOption: "Número da página (começa em 0)"
|
|
3515
3545
|
}
|
|
3516
3546
|
},
|
|
3517
3547
|
checklists: {
|
|
@@ -3954,6 +3984,37 @@ var init_RenameChecklistUseCase = __esm(() => {
|
|
|
3954
3984
|
init_i18n();
|
|
3955
3985
|
});
|
|
3956
3986
|
|
|
3987
|
+
// src/application/use-cases/SearchCardsUseCase.ts
|
|
3988
|
+
class SearchCardsUseCase {
|
|
3989
|
+
trelloRepository;
|
|
3990
|
+
constructor(trelloRepository) {
|
|
3991
|
+
this.trelloRepository = trelloRepository;
|
|
3992
|
+
}
|
|
3993
|
+
async execute(query, filters) {
|
|
3994
|
+
if (!query || query.trim().length === 0) {
|
|
3995
|
+
throw new Error(t3("card.validation.requiredSearchQuery"));
|
|
3996
|
+
}
|
|
3997
|
+
let fullQuery = query.trim();
|
|
3998
|
+
if (filters?.labels) {
|
|
3999
|
+
const labelTerms = filters.labels.split(",").map((l) => `label:"${l.trim()}"`).join(" ");
|
|
4000
|
+
fullQuery = `${fullQuery} ${labelTerms}`;
|
|
4001
|
+
}
|
|
4002
|
+
const options = {
|
|
4003
|
+
boardId: filters?.boardId,
|
|
4004
|
+
limit: filters?.limit,
|
|
4005
|
+
page: filters?.page
|
|
4006
|
+
};
|
|
4007
|
+
const results = await this.trelloRepository.searchCards(fullQuery, options);
|
|
4008
|
+
if (filters?.listId) {
|
|
4009
|
+
return results.filter((card) => card.idList === filters.listId);
|
|
4010
|
+
}
|
|
4011
|
+
return results;
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
var init_SearchCardsUseCase = __esm(() => {
|
|
4015
|
+
init_i18n();
|
|
4016
|
+
});
|
|
4017
|
+
|
|
3957
4018
|
// src/application/use-cases/UpdateCardUseCase.ts
|
|
3958
4019
|
class UpdateCardUseCase {
|
|
3959
4020
|
trelloRepository;
|
|
@@ -3993,6 +4054,7 @@ var init_use_cases = __esm(() => {
|
|
|
3993
4054
|
init_GetBoardDetailsUseCase();
|
|
3994
4055
|
init_RenameChecklistItemUseCase();
|
|
3995
4056
|
init_RenameChecklistUseCase();
|
|
4057
|
+
init_SearchCardsUseCase();
|
|
3996
4058
|
init_UpdateCardUseCase();
|
|
3997
4059
|
});
|
|
3998
4060
|
|
|
@@ -25646,16 +25708,24 @@ class AuthController {
|
|
|
25646
25708
|
console.log(t3("auth.tokenHintUrl"));
|
|
25647
25709
|
console.log(t3("auth.tokenHintStep2"));
|
|
25648
25710
|
console.log(t3("auth.tokenHintStep3"));
|
|
25649
|
-
|
|
25650
|
-
{
|
|
25651
|
-
|
|
25652
|
-
|
|
25653
|
-
|
|
25654
|
-
|
|
25711
|
+
try {
|
|
25712
|
+
const { token } = await dist_default14.prompt([
|
|
25713
|
+
{
|
|
25714
|
+
type: "input",
|
|
25715
|
+
name: "token",
|
|
25716
|
+
message: t3("auth.enterToken"),
|
|
25717
|
+
validate: (input) => input.startsWith("ATTA") || t3("auth.tokenInvalid")
|
|
25718
|
+
}
|
|
25719
|
+
]);
|
|
25720
|
+
const result = await this.authenticateUseCase.execute(token);
|
|
25721
|
+
console.log(result.success ? t3("auth.tokenSaved") : result.message);
|
|
25722
|
+
} catch (error) {
|
|
25723
|
+
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
25724
|
+
console.log(t3("auth.setupCancelled"));
|
|
25725
|
+
process.exit(0);
|
|
25655
25726
|
}
|
|
25656
|
-
|
|
25657
|
-
|
|
25658
|
-
console.log(result.success ? t3("auth.tokenSaved") : result.message);
|
|
25727
|
+
throw error;
|
|
25728
|
+
}
|
|
25659
25729
|
}
|
|
25660
25730
|
async getConfig() {
|
|
25661
25731
|
return await this.authenticateUseCase.getConfig();
|
|
@@ -25875,6 +25945,7 @@ class CardController {
|
|
|
25875
25945
|
deleteCardUseCase;
|
|
25876
25946
|
moveCardUseCase;
|
|
25877
25947
|
getCardUseCase;
|
|
25948
|
+
searchCardsUseCase;
|
|
25878
25949
|
constructor(trelloRepository, boardController, outputFormatter) {
|
|
25879
25950
|
this.trelloRepository = trelloRepository;
|
|
25880
25951
|
this.boardController = boardController;
|
|
@@ -25884,6 +25955,7 @@ class CardController {
|
|
|
25884
25955
|
this.deleteCardUseCase = new DeleteCardUseCase(trelloRepository);
|
|
25885
25956
|
this.moveCardUseCase = new MoveCardUseCase(trelloRepository);
|
|
25886
25957
|
this.getCardUseCase = new GetCardUseCase(trelloRepository);
|
|
25958
|
+
this.searchCardsUseCase = new SearchCardsUseCase(trelloRepository);
|
|
25887
25959
|
}
|
|
25888
25960
|
async createCardInteractive() {
|
|
25889
25961
|
const boards = await this.boardController.getBoards();
|
|
@@ -26096,28 +26168,7 @@ class CardController {
|
|
|
26096
26168
|
throw new Error(t3("card.notFound", { cardId }));
|
|
26097
26169
|
}
|
|
26098
26170
|
async deleteCard(cardId) {
|
|
26099
|
-
const
|
|
26100
|
-
let card;
|
|
26101
|
-
for (const board of boards) {
|
|
26102
|
-
const lists = await this.trelloRepository.getLists(board.id);
|
|
26103
|
-
for (const list of lists) {
|
|
26104
|
-
try {
|
|
26105
|
-
const cards = await this.trelloRepository.getCards(list.id);
|
|
26106
|
-
card = cards.find((c) => c.id === cardId);
|
|
26107
|
-
if (card) {
|
|
26108
|
-
break;
|
|
26109
|
-
}
|
|
26110
|
-
} catch {
|
|
26111
|
-
continue;
|
|
26112
|
-
}
|
|
26113
|
-
}
|
|
26114
|
-
if (card) {
|
|
26115
|
-
break;
|
|
26116
|
-
}
|
|
26117
|
-
}
|
|
26118
|
-
if (!card) {
|
|
26119
|
-
throw new Error(t3("card.notFound", { cardId }));
|
|
26120
|
-
}
|
|
26171
|
+
const card = await this.trelloRepository.getCard(cardId);
|
|
26121
26172
|
await this.deleteCardUseCase.execute(cardId);
|
|
26122
26173
|
console.log(t3("card.deleted"));
|
|
26123
26174
|
console.log(t3("card.cardName", { name: card.name }));
|
|
@@ -26230,44 +26281,32 @@ class CardController {
|
|
|
26230
26281
|
}
|
|
26231
26282
|
}
|
|
26232
26283
|
async moveCardToList(cardId, targetListId) {
|
|
26233
|
-
const
|
|
26234
|
-
|
|
26235
|
-
|
|
26236
|
-
|
|
26237
|
-
for (const list of lists) {
|
|
26238
|
-
try {
|
|
26239
|
-
const cards = await this.trelloRepository.getCards(list.id);
|
|
26240
|
-
card = cards.find((c) => c.id === cardId);
|
|
26241
|
-
if (card) {
|
|
26242
|
-
break;
|
|
26243
|
-
}
|
|
26244
|
-
} catch {
|
|
26245
|
-
continue;
|
|
26246
|
-
}
|
|
26247
|
-
}
|
|
26248
|
-
if (card) {
|
|
26249
|
-
break;
|
|
26250
|
-
}
|
|
26251
|
-
}
|
|
26252
|
-
if (!card) {
|
|
26253
|
-
throw new Error(t3("card.notFound", { cardId }));
|
|
26254
|
-
}
|
|
26255
|
-
let targetList;
|
|
26256
|
-
for (const board of boards) {
|
|
26257
|
-
const lists = await this.trelloRepository.getLists(board.id);
|
|
26258
|
-
targetList = lists.find((l) => l.id === targetListId);
|
|
26259
|
-
if (targetList) {
|
|
26260
|
-
break;
|
|
26261
|
-
}
|
|
26262
|
-
}
|
|
26263
|
-
if (!targetList) {
|
|
26264
|
-
throw new Error(t3("list.notFoundById", { listId: targetListId }));
|
|
26265
|
-
}
|
|
26284
|
+
const [card, targetList] = await Promise.all([
|
|
26285
|
+
this.trelloRepository.getCard(cardId),
|
|
26286
|
+
this.trelloRepository.getList(targetListId)
|
|
26287
|
+
]);
|
|
26266
26288
|
await this.moveCardUseCase.execute(cardId, targetListId);
|
|
26267
26289
|
console.log(t3("card.moved"));
|
|
26268
26290
|
console.log(t3("card.cardName", { name: card.name }));
|
|
26269
26291
|
console.log(t3("card.movedTo", { listName: targetList.name }));
|
|
26270
26292
|
}
|
|
26293
|
+
async searchCards(query, filters) {
|
|
26294
|
+
const cards = await this.searchCardsUseCase.execute(query, filters);
|
|
26295
|
+
if (cards.length === 0) {
|
|
26296
|
+
console.log(t3("card.search.empty", { query }));
|
|
26297
|
+
return;
|
|
26298
|
+
}
|
|
26299
|
+
console.log(t3("card.search.results", { query, count: cards.length }));
|
|
26300
|
+
if (filters?.page !== undefined || filters?.limit !== undefined) {
|
|
26301
|
+
const page = (filters.page ?? 0) + 1;
|
|
26302
|
+
const limit = filters.limit ?? 50;
|
|
26303
|
+
console.log(t3("card.search.pagination", { page, limit }));
|
|
26304
|
+
}
|
|
26305
|
+
this.outputFormatter.output(cards, {
|
|
26306
|
+
fields: ["name", "id", "url"],
|
|
26307
|
+
headers: ["Name", "ID", "URL"]
|
|
26308
|
+
});
|
|
26309
|
+
}
|
|
26271
26310
|
}
|
|
26272
26311
|
var init_CardController = __esm(() => {
|
|
26273
26312
|
init_use_cases();
|
|
@@ -28725,6 +28764,10 @@ ${errorText}`);
|
|
|
28725
28764
|
const data = await this.requestLists(`/boards/${boardId}/lists`);
|
|
28726
28765
|
return data.map((list) => ListEntity.fromApiResponse(list));
|
|
28727
28766
|
}
|
|
28767
|
+
async getList(listId) {
|
|
28768
|
+
const data = await this.request(`/lists/${listId}`);
|
|
28769
|
+
return ListEntity.fromApiResponse(data);
|
|
28770
|
+
}
|
|
28728
28771
|
async createList(boardId, name) {
|
|
28729
28772
|
const body = new URLSearchParams({
|
|
28730
28773
|
idBoard: boardId,
|
|
@@ -28907,6 +28950,16 @@ ${errorText}`);
|
|
|
28907
28950
|
});
|
|
28908
28951
|
return ChecklistItemEntity.fromApiResponse(data);
|
|
28909
28952
|
}
|
|
28953
|
+
async searchCards(query, options) {
|
|
28954
|
+
const limit = options?.limit ?? 50;
|
|
28955
|
+
const page = options?.page ?? 0;
|
|
28956
|
+
let endpoint = `/search?query=${encodeURIComponent(query)}&modelTypes=cards&cards_limit=${limit}&cards_page=${page}&card_fields=id,name,desc,idList,url,pos`;
|
|
28957
|
+
if (options?.boardId) {
|
|
28958
|
+
endpoint += `&idBoards=${encodeURIComponent(options.boardId)}`;
|
|
28959
|
+
}
|
|
28960
|
+
const data = await this.request(endpoint);
|
|
28961
|
+
return data.cards.map((card) => CardEntity.fromApiResponse(card));
|
|
28962
|
+
}
|
|
28910
28963
|
}
|
|
28911
28964
|
var init_TrelloApiRepository = __esm(() => {
|
|
28912
28965
|
init_entities();
|
|
@@ -31546,6 +31599,21 @@ class CommandController {
|
|
|
31546
31599
|
await this.cardController.showCard(cardId);
|
|
31547
31600
|
}, "cards show");
|
|
31548
31601
|
});
|
|
31602
|
+
cardsCmd.command("search <query>").description(t3("commands.cards.search.description")).option("-f, --format <format>", t3("commands.formatOption"), "table").option("--board-id <boardId>", t3("commands.cards.search.boardIdOption")).option("--list-id <listId>", t3("commands.cards.search.listIdOption")).option("--labels <labels>", t3("commands.cards.search.labelsOption")).option("--limit <limit>", t3("commands.cards.search.limitOption")).option("--page <page>", t3("commands.cards.search.pageOption")).action(async (query, options) => {
|
|
31603
|
+
await ErrorHandler.withErrorHandling(async () => {
|
|
31604
|
+
await this.initializeTrelloControllers();
|
|
31605
|
+
if (options.format) {
|
|
31606
|
+
this.outputFormatter.setFormat(options.format);
|
|
31607
|
+
}
|
|
31608
|
+
await this.cardController.searchCards(query, {
|
|
31609
|
+
boardId: options.boardId,
|
|
31610
|
+
listId: options.listId,
|
|
31611
|
+
labels: options.labels,
|
|
31612
|
+
limit: options.limit !== undefined ? Number.parseInt(options.limit, 10) : undefined,
|
|
31613
|
+
page: options.page !== undefined ? Number.parseInt(options.page, 10) : undefined
|
|
31614
|
+
});
|
|
31615
|
+
}, "cards search");
|
|
31616
|
+
});
|
|
31549
31617
|
cardsCmd.command("legacy <boardName> <listName>").description(t3("commands.deprecated.cardsAliasDescription")).action(async (boardName, listName) => {
|
|
31550
31618
|
console.warn(t3("commands.deprecated.cardsAliasWarning"));
|
|
31551
31619
|
await ErrorHandler.withErrorHandling(async () => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trello-cli-unofficial",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"packageManager": "bun@1.3.11",
|
|
7
7
|
"description": "Unofficial Trello CLI using Power-Up authentication, built with Bun for maximum performance",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CardEntity } from '@domain/entities';
|
|
2
|
+
import type { SearchCardsOptions, TrelloRepository } from '@domain/repositories';
|
|
3
|
+
import { t } from '@/i18n';
|
|
4
|
+
|
|
5
|
+
export interface SearchCardsFilters {
|
|
6
|
+
boardId?: string;
|
|
7
|
+
listId?: string;
|
|
8
|
+
labels?: string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
page?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class SearchCardsUseCase {
|
|
14
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
15
|
+
|
|
16
|
+
async execute(query: string, filters?: SearchCardsFilters): Promise<CardEntity[]> {
|
|
17
|
+
if (!query || query.trim().length === 0) {
|
|
18
|
+
throw new Error(t('card.validation.requiredSearchQuery'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let fullQuery = query.trim();
|
|
22
|
+
if (filters?.labels) {
|
|
23
|
+
const labelTerms = filters.labels.split(',').map(l => `label:"${l.trim()}"`).join(' ');
|
|
24
|
+
fullQuery = `${fullQuery} ${labelTerms}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const options: SearchCardsOptions = {
|
|
28
|
+
boardId: filters?.boardId,
|
|
29
|
+
limit: filters?.limit,
|
|
30
|
+
page: filters?.page,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const results = await this.trelloRepository.searchCards(fullQuery, options);
|
|
34
|
+
|
|
35
|
+
if (filters?.listId) {
|
|
36
|
+
return results.filter(card => card.idList === filters.listId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -15,5 +15,6 @@ export * from './GetListsUseCase';
|
|
|
15
15
|
export * from './MoveCardUseCase';
|
|
16
16
|
export * from './RenameChecklistItemUseCase';
|
|
17
17
|
export * from './RenameChecklistUseCase';
|
|
18
|
+
export * from './SearchCardsUseCase';
|
|
18
19
|
export * from './UpdateCardUseCase';
|
|
19
20
|
export * from './UpdateChecklistItemStateUseCase';
|
|
@@ -21,12 +21,19 @@ export interface BoardLabel {
|
|
|
21
21
|
color: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface SearchCardsOptions {
|
|
25
|
+
boardId?: string;
|
|
26
|
+
limit?: number;
|
|
27
|
+
page?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
export interface TrelloRepository {
|
|
25
31
|
getBoards: () => Promise<BoardEntity[]>;
|
|
26
32
|
createBoard: (name: string, description?: string) => Promise<BoardEntity>;
|
|
27
33
|
getBoardMembers: (boardId: string) => Promise<BoardMember[]>;
|
|
28
34
|
getBoardLabels: (boardId: string) => Promise<BoardLabel[]>;
|
|
29
35
|
getLists: (boardId: string) => Promise<ListEntity[]>;
|
|
36
|
+
getList: (listId: string) => Promise<ListEntity>;
|
|
30
37
|
createList: (boardId: string, name: string) => Promise<ListEntity>;
|
|
31
38
|
deleteList: (listId: string) => Promise<void>;
|
|
32
39
|
moveList: (listId: string, position: number) => Promise<ListEntity>;
|
|
@@ -43,4 +50,5 @@ export interface TrelloRepository {
|
|
|
43
50
|
deleteChecklistItem: (checklistId: string, itemId: string) => Promise<void>;
|
|
44
51
|
renameChecklistItem: (cardId: string, itemId: string, name: string) => Promise<ChecklistItemEntity>;
|
|
45
52
|
updateChecklistItemState: (cardId: string, itemId: string, state: 'complete' | 'incomplete') => Promise<ChecklistItemEntity>;
|
|
53
|
+
searchCards: (query: string, options?: SearchCardsOptions) => Promise<CardEntity[]>;
|
|
46
54
|
}
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"enterToken": "Please enter your Trello token:",
|
|
21
21
|
"tokenInvalid": "❌ Invalid token! Token must start with 'ATTA' and be at least 11 characters long.",
|
|
22
22
|
"tokenSaved": "✅ Token saved successfully!",
|
|
23
|
+
"setupCancelled": "⚠️ Setup cancelled by the user.",
|
|
23
24
|
"authenticated": "✅ You are already authenticated!"
|
|
24
25
|
},
|
|
25
26
|
"menu": {
|
|
@@ -125,7 +126,8 @@
|
|
|
125
126
|
"validation": {
|
|
126
127
|
"requiredName": "Name is required",
|
|
127
128
|
"requiredCardName": "Card name is required",
|
|
128
|
-
"cardNameCannotBeEmpty": "Card name cannot be empty"
|
|
129
|
+
"cardNameCannotBeEmpty": "Card name cannot be empty",
|
|
130
|
+
"requiredSearchQuery": "Search query cannot be empty"
|
|
129
131
|
},
|
|
130
132
|
"actions": {
|
|
131
133
|
"view": "👁️ View Details",
|
|
@@ -147,6 +149,11 @@
|
|
|
147
149
|
"checklistProgress": " [{{done}}/{{total}}]",
|
|
148
150
|
"checklistItemDone": " ☑ {{name}}",
|
|
149
151
|
"checklistItemPending": " ☐ {{name}}"
|
|
152
|
+
},
|
|
153
|
+
"search": {
|
|
154
|
+
"results": "🔍 Results for \"{{query}}\" ({{count}} found):",
|
|
155
|
+
"empty": "🔍 No cards found for \"{{query}}\".",
|
|
156
|
+
"pagination": "📄 Page {{page}} (limit: {{limit}})"
|
|
150
157
|
}
|
|
151
158
|
},
|
|
152
159
|
"checklist": {
|
|
@@ -314,6 +321,14 @@
|
|
|
314
321
|
},
|
|
315
322
|
"delete": {
|
|
316
323
|
"description": "Delete a card"
|
|
324
|
+
},
|
|
325
|
+
"search": {
|
|
326
|
+
"description": "Search cards by text",
|
|
327
|
+
"boardIdOption": "Filter by board ID",
|
|
328
|
+
"listIdOption": "Filter by list ID",
|
|
329
|
+
"labelsOption": "Filter by labels (comma-separated)",
|
|
330
|
+
"limitOption": "Maximum number of results",
|
|
331
|
+
"pageOption": "Page number (starts at 0)"
|
|
317
332
|
}
|
|
318
333
|
},
|
|
319
334
|
"checklists": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"enterToken": "Por favor, insira seu token do Trello:",
|
|
21
21
|
"tokenInvalid": "❌ Token inválido! O token deve começar com 'ATTA' e ter pelo menos 11 caracteres.",
|
|
22
22
|
"tokenSaved": "✅ Token salvo com sucesso!",
|
|
23
|
+
"setupCancelled": "⚠️ Configuração cancelada pelo usuário.",
|
|
23
24
|
"authenticated": "✅ Você já está autenticado!"
|
|
24
25
|
},
|
|
25
26
|
"menu": {
|
|
@@ -125,7 +126,8 @@
|
|
|
125
126
|
"validation": {
|
|
126
127
|
"requiredName": "Nome é obrigatório",
|
|
127
128
|
"requiredCardName": "Nome do cartão é obrigatório",
|
|
128
|
-
"cardNameCannotBeEmpty": "Nome do cartão não pode estar vazio"
|
|
129
|
+
"cardNameCannotBeEmpty": "Nome do cartão não pode estar vazio",
|
|
130
|
+
"requiredSearchQuery": "A consulta de busca não pode estar vazia"
|
|
129
131
|
},
|
|
130
132
|
"actions": {
|
|
131
133
|
"view": "👁️ Ver Detalhes",
|
|
@@ -147,6 +149,11 @@
|
|
|
147
149
|
"checklistProgress": " [{{done}}/{{total}}]",
|
|
148
150
|
"checklistItemDone": " ☑ {{name}}",
|
|
149
151
|
"checklistItemPending": " ☐ {{name}}"
|
|
152
|
+
},
|
|
153
|
+
"search": {
|
|
154
|
+
"results": "🔍 Resultados para \"{{query}}\" ({{count}} encontrado(s)):",
|
|
155
|
+
"empty": "🔍 Nenhum card encontrado para \"{{query}}\".",
|
|
156
|
+
"pagination": "📄 Página {{page}} (limite: {{limit}})"
|
|
150
157
|
}
|
|
151
158
|
},
|
|
152
159
|
"checklist": {
|
|
@@ -314,6 +321,14 @@
|
|
|
314
321
|
},
|
|
315
322
|
"delete": {
|
|
316
323
|
"description": "Deleta um card"
|
|
324
|
+
},
|
|
325
|
+
"search": {
|
|
326
|
+
"description": "Buscar cards por texto",
|
|
327
|
+
"boardIdOption": "Filtrar por ID de board",
|
|
328
|
+
"listIdOption": "Filtrar por ID de lista",
|
|
329
|
+
"labelsOption": "Filtrar por labels (separadas por vírgula)",
|
|
330
|
+
"limitOption": "Número máximo de resultados",
|
|
331
|
+
"pageOption": "Número da página (começa em 0)"
|
|
317
332
|
}
|
|
318
333
|
},
|
|
319
334
|
"checklists": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CreateCardData, UpdateCardData } from '@domain/entities';
|
|
2
|
-
import type { TrelloRepository } from '@domain/repositories';
|
|
2
|
+
import type { SearchCardsOptions, TrelloRepository } from '@domain/repositories';
|
|
3
3
|
import { BoardEntity, CardEntity, ChecklistEntity, ChecklistItemEntity, ListEntity } from '@domain/entities';
|
|
4
4
|
import { t } from '@/i18n';
|
|
5
5
|
|
|
@@ -171,6 +171,11 @@ export class TrelloApiRepository implements TrelloRepository {
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
async getList(listId: string): Promise<ListEntity> {
|
|
175
|
+
const data = await this.request(`/lists/${listId}`);
|
|
176
|
+
return ListEntity.fromApiResponse(data as TrelloListResponse);
|
|
177
|
+
}
|
|
178
|
+
|
|
174
179
|
async createList(boardId: string, name: string): Promise<ListEntity> {
|
|
175
180
|
const body = new URLSearchParams({
|
|
176
181
|
idBoard: boardId,
|
|
@@ -400,4 +405,15 @@ export class TrelloApiRepository implements TrelloRepository {
|
|
|
400
405
|
|
|
401
406
|
return ChecklistItemEntity.fromApiResponse(data as TrelloChecklistItemResponse);
|
|
402
407
|
}
|
|
408
|
+
|
|
409
|
+
async searchCards(query: string, options?: SearchCardsOptions): Promise<CardEntity[]> {
|
|
410
|
+
const limit = options?.limit ?? 50;
|
|
411
|
+
const page = options?.page ?? 0;
|
|
412
|
+
let endpoint = `/search?query=${encodeURIComponent(query)}&modelTypes=cards&cards_limit=${limit}&cards_page=${page}&card_fields=id,name,desc,idList,url,pos`;
|
|
413
|
+
if (options?.boardId) {
|
|
414
|
+
endpoint += `&idBoards=${encodeURIComponent(options.boardId)}`;
|
|
415
|
+
}
|
|
416
|
+
const data = await this.request(endpoint) as { cards: TrelloCardResponse[] };
|
|
417
|
+
return data.cards.map(card => CardEntity.fromApiResponse(card));
|
|
418
|
+
}
|
|
403
419
|
}
|
|
@@ -26,17 +26,26 @@ export class AuthController {
|
|
|
26
26
|
console.log(t('auth.tokenHintStep2'));
|
|
27
27
|
console.log(t('auth.tokenHintStep3'));
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
try {
|
|
30
|
+
const { token } = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: 'input',
|
|
33
|
+
name: 'token',
|
|
34
|
+
message: t('auth.enterToken'),
|
|
35
|
+
validate: input => input.startsWith('ATTA') || t('auth.tokenInvalid'),
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const result = await this.authenticateUseCase.execute(token);
|
|
40
|
+
console.log(result.success ? t('auth.tokenSaved') : result.message);
|
|
41
|
+
} catch (error: unknown) {
|
|
42
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
43
|
+
console.log(t('auth.setupCancelled'));
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
async getConfig(): Promise<ConfigEntity> {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SearchCardsFilters } from '@application/use-cases';
|
|
1
2
|
import type { BoardEntity, CardEntity, ListEntity } from '@domain/entities';
|
|
2
3
|
import type { TrelloRepository } from '@domain/repositories';
|
|
3
4
|
import type { BoardController } from './BoardController';
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
DeleteCardUseCase,
|
|
9
10
|
GetCardUseCase,
|
|
10
11
|
MoveCardUseCase,
|
|
12
|
+
SearchCardsUseCase,
|
|
11
13
|
UpdateCardUseCase,
|
|
12
14
|
} from '@application/use-cases';
|
|
13
15
|
|
|
@@ -21,6 +23,7 @@ export class CardController {
|
|
|
21
23
|
private deleteCardUseCase: DeleteCardUseCase;
|
|
22
24
|
private moveCardUseCase: MoveCardUseCase;
|
|
23
25
|
private getCardUseCase: GetCardUseCase;
|
|
26
|
+
private searchCardsUseCase: SearchCardsUseCase;
|
|
24
27
|
|
|
25
28
|
constructor(
|
|
26
29
|
private trelloRepository: TrelloRepository,
|
|
@@ -32,6 +35,7 @@ export class CardController {
|
|
|
32
35
|
this.deleteCardUseCase = new DeleteCardUseCase(trelloRepository);
|
|
33
36
|
this.moveCardUseCase = new MoveCardUseCase(trelloRepository);
|
|
34
37
|
this.getCardUseCase = new GetCardUseCase(trelloRepository);
|
|
38
|
+
this.searchCardsUseCase = new SearchCardsUseCase(trelloRepository);
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
async createCardInteractive(): Promise<void> {
|
|
@@ -309,34 +313,7 @@ export class CardController {
|
|
|
309
313
|
}
|
|
310
314
|
|
|
311
315
|
async deleteCard(cardId: string): Promise<void> {
|
|
312
|
-
|
|
313
|
-
const boards = await this.trelloRepository.getBoards();
|
|
314
|
-
let card: CardEntity | undefined;
|
|
315
|
-
|
|
316
|
-
for (const board of boards) {
|
|
317
|
-
const lists = await this.trelloRepository.getLists(board.id);
|
|
318
|
-
|
|
319
|
-
for (const list of lists) {
|
|
320
|
-
try {
|
|
321
|
-
const cards = await this.trelloRepository.getCards(list.id);
|
|
322
|
-
card = cards.find((c: CardEntity) => c.id === cardId);
|
|
323
|
-
|
|
324
|
-
if (card) {
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
} catch {
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (card) {
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (!card) {
|
|
337
|
-
throw new Error(t('card.notFound', { cardId }));
|
|
338
|
-
}
|
|
339
|
-
|
|
316
|
+
const card = await this.trelloRepository.getCard(cardId);
|
|
340
317
|
await this.deleteCardUseCase.execute(cardId);
|
|
341
318
|
console.log(t('card.deleted'));
|
|
342
319
|
console.log(t('card.cardName', { name: card.name }));
|
|
@@ -494,51 +471,32 @@ export class CardController {
|
|
|
494
471
|
}
|
|
495
472
|
|
|
496
473
|
async moveCardToList(cardId: string, targetListId: string): Promise<void> {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
for (const board of boards) {
|
|
502
|
-
const lists = await this.trelloRepository.getLists(board.id);
|
|
503
|
-
|
|
504
|
-
for (const list of lists) {
|
|
505
|
-
try {
|
|
506
|
-
const cards = await this.trelloRepository.getCards(list.id);
|
|
507
|
-
card = cards.find((c: CardEntity) => c.id === cardId);
|
|
508
|
-
|
|
509
|
-
if (card) {
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
} catch {
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
if (card) {
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (!card) {
|
|
522
|
-
throw new Error(t('card.notFound', { cardId }));
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Verificar se a lista de destino existe procurando em todos os boards
|
|
526
|
-
let targetList: ListEntity | undefined;
|
|
527
|
-
for (const board of boards) {
|
|
528
|
-
const lists = await this.trelloRepository.getLists(board.id);
|
|
529
|
-
targetList = lists.find((l: ListEntity) => l.id === targetListId);
|
|
530
|
-
if (targetList) {
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (!targetList) {
|
|
536
|
-
throw new Error(t('list.notFoundById', { listId: targetListId }));
|
|
537
|
-
}
|
|
474
|
+
const [card, targetList] = await Promise.all([
|
|
475
|
+
this.trelloRepository.getCard(cardId),
|
|
476
|
+
this.trelloRepository.getList(targetListId),
|
|
477
|
+
]);
|
|
538
478
|
|
|
539
479
|
await this.moveCardUseCase.execute(cardId, targetListId);
|
|
540
480
|
console.log(t('card.moved'));
|
|
541
481
|
console.log(t('card.cardName', { name: card.name }));
|
|
542
482
|
console.log(t('card.movedTo', { listName: targetList.name }));
|
|
543
483
|
}
|
|
484
|
+
|
|
485
|
+
async searchCards(query: string, filters?: SearchCardsFilters): Promise<void> {
|
|
486
|
+
const cards = await this.searchCardsUseCase.execute(query, filters);
|
|
487
|
+
if (cards.length === 0) {
|
|
488
|
+
console.log(t('card.search.empty', { query }));
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
console.log(t('card.search.results', { query, count: cards.length }));
|
|
492
|
+
if (filters?.page !== undefined || filters?.limit !== undefined) {
|
|
493
|
+
const page = (filters.page ?? 0) + 1;
|
|
494
|
+
const limit = filters.limit ?? 50;
|
|
495
|
+
console.log(t('card.search.pagination', { page, limit }));
|
|
496
|
+
}
|
|
497
|
+
this.outputFormatter.output(cards, {
|
|
498
|
+
fields: ['name', 'id', 'url'],
|
|
499
|
+
headers: ['Name', 'ID', 'URL'],
|
|
500
|
+
});
|
|
501
|
+
}
|
|
544
502
|
}
|
|
@@ -387,6 +387,31 @@ export class CommandController {
|
|
|
387
387
|
}, 'cards show');
|
|
388
388
|
});
|
|
389
389
|
|
|
390
|
+
cardsCmd
|
|
391
|
+
.command('search <query>')
|
|
392
|
+
.description(t('commands.cards.search.description'))
|
|
393
|
+
.option('-f, --format <format>', t('commands.formatOption'), 'table')
|
|
394
|
+
.option('--board-id <boardId>', t('commands.cards.search.boardIdOption'))
|
|
395
|
+
.option('--list-id <listId>', t('commands.cards.search.listIdOption'))
|
|
396
|
+
.option('--labels <labels>', t('commands.cards.search.labelsOption'))
|
|
397
|
+
.option('--limit <limit>', t('commands.cards.search.limitOption'))
|
|
398
|
+
.option('--page <page>', t('commands.cards.search.pageOption'))
|
|
399
|
+
.action(async (query: string, options: { format?: string; boardId?: string; listId?: string; labels?: string; limit?: string; page?: string }) => {
|
|
400
|
+
await ErrorHandler.withErrorHandling(async () => {
|
|
401
|
+
await this.initializeTrelloControllers();
|
|
402
|
+
if (options.format) {
|
|
403
|
+
this.outputFormatter.setFormat(options.format as OutputFormat);
|
|
404
|
+
}
|
|
405
|
+
await this.cardController.searchCards(query, {
|
|
406
|
+
boardId: options.boardId,
|
|
407
|
+
listId: options.listId,
|
|
408
|
+
labels: options.labels,
|
|
409
|
+
limit: options.limit !== undefined ? Number.parseInt(options.limit, 10) : undefined,
|
|
410
|
+
page: options.page !== undefined ? Number.parseInt(options.page, 10) : undefined,
|
|
411
|
+
});
|
|
412
|
+
}, 'cards search');
|
|
413
|
+
});
|
|
414
|
+
|
|
390
415
|
// Backward compatibility subcommand for legacy behavior
|
|
391
416
|
cardsCmd
|
|
392
417
|
.command('legacy <boardName> <listName>')
|