product-discovery-cli 0.0.1 → 0.0.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.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # Product Discovery CLI
2
+
3
+ [![npm version](https://img.shields.io/npm/v/product-discovery-cli.svg)](https://www.npmjs.com/package/product-discovery-cli)
4
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
5
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
6
+
7
+ A professional command-line interface to interact with the Product Discovery Agent API. Generate structured product discoveries powered by AI using an intuitive and colorful CLI experience.
8
+
9
+ ## ✨ Features
10
+
11
+ - 🌐 **Internationalization**: Support for Portuguese (pt-br) and English (en-us)
12
+ - 🎨 **Professional UX**: Colorful output, interactive prompts, and loading spinners
13
+ - 🏗️ **Clean Architecture**: Built with SOLID principles and layered architecture
14
+ - 💾 **Auto-save**: Optional automatic JSON result saving
15
+ - 🔄 **Iterative Improvement**: Refine discoveries with additional context
16
+ - ⚙️ **Configurable**: CLI flags, config files, and environment variables
17
+ - ✅ **Well-tested**: Comprehensive unit tests with 90%+ coverage
18
+ - 📦 **Easy to install**: Available as a global npm package
19
+
20
+ ## 📦 Installation
21
+
22
+ ### Global Installation (Recommended)
23
+
24
+ ```bash
25
+ npm install -g product-discovery-cli
26
+ ```
27
+
28
+ ### Local Installation
29
+
30
+ ```bash
31
+ npm install product-discovery-cli
32
+ ```
33
+
34
+ ## 🚀 Quick Start
35
+
36
+ After global installation, simply run:
37
+
38
+ ```bash
39
+ product-discovery
40
+ ```
41
+
42
+ The CLI will guide you through an interactive flow to generate product discoveries.
43
+
44
+ ## 📖 Usage
45
+
46
+ ### Basic Usage
47
+
48
+ ```bash
49
+ # Run with default settings (Portuguese)
50
+ product-discovery
51
+
52
+ # Run in English
53
+ product-discovery --lang en-us
54
+
55
+ # Specify API URL
56
+ product-discovery --api-url http://localhost:3000/api/v1/discovery
57
+
58
+ # Auto-save results without prompting
59
+ product-discovery --save --output ./results
60
+ ```
61
+
62
+ ### CLI Options
63
+
64
+ | Option | Alias | Description | Default |
65
+ |--------|-------|-------------|---------|
66
+ | `--api-url <url>` | `-u` | API endpoint URL | `http://localhost:3000/api/v1/discovery` |
67
+ | `--lang <code>` | `-l` | Language (pt-br, en-us) | `pt-br` |
68
+ | `--config <path>` | `-c` | Path to JSON config file | - |
69
+ | `--save` | `-s` | Auto-save without prompting | `false` |
70
+ | `--output <dir>` | `-o` | Default output directory | - |
71
+ | `--file <name>` | `-f` | Default output filename | - |
72
+ | `--no-save` | - | Disable saving prompt | - |
73
+
74
+ ### Configuration File
75
+
76
+ Create a JSON configuration file to set default options:
77
+
78
+ ```json
79
+ {
80
+ "apiUrl": "http://localhost:3000/api/v1/discovery",
81
+ "lang": "en-us",
82
+ "autoSave": true,
83
+ "defaultOutputDir": "./discoveries",
84
+ "defaultFileName": "discovery.json"
85
+ }
86
+ ```
87
+
88
+ Use it with:
89
+
90
+ ```bash
91
+ product-discovery --config ./config.json
92
+ ```
93
+
94
+ ### Environment Variables
95
+
96
+ Set environment variables for configuration:
97
+
98
+ ```bash
99
+ export PRODUCT_DISCOVERY_API_URL=http://localhost:3000/api/v1/discovery
100
+ product-discovery
101
+ ```
102
+
103
+ ## 🌍 Internationalization
104
+
105
+ The CLI supports multiple languages:
106
+
107
+ - **Portuguese (pt-br)**: Default language
108
+ - **English (en-us)**: Full translation of all messages
109
+
110
+ Switch languages using the `--lang` flag:
111
+
112
+ ```bash
113
+ product-discovery --lang en-us
114
+ ```
115
+
116
+ ## 🏗️ Architecture
117
+
118
+ This project follows **Clean Architecture** principles with clear separation of concerns:
119
+
120
+ ```
121
+ src/
122
+ ├── application/ # Use cases and business logic
123
+ ├── domain/ # Domain entities and utilities
124
+ ├── infrastructure/ # External dependencies (API, storage, i18n)
125
+ ├── presentation/ # CLI controllers and routes
126
+ └── config/ # Configuration and CLI options
127
+ ```
128
+
129
+ ### Key Principles
130
+
131
+ - **SOLID**: Single Responsibility, Open/Closed, Dependency Inversion
132
+ - **Dependency Injection**: All dependencies injected via constructors
133
+ - **Testability**: Easy to mock and test each layer independently
134
+
135
+ ## 🛠️ Development
136
+
137
+ ### Prerequisites
138
+
139
+ - Node.js >= 20
140
+
141
+ ### Setup
142
+
143
+ ```bash
144
+ # Clone the repository
145
+ git clone https://github.com/AuronForge/product-discovery-cli.git
146
+ cd product-discovery-cli
147
+
148
+ # Install dependencies
149
+ npm install
150
+
151
+ # Run locally
152
+ npm start
153
+
154
+ # Run tests
155
+ npm test
156
+
157
+ # Run tests with coverage
158
+ npm run test:coverage
159
+ ```
160
+
161
+ ### Testing
162
+
163
+ The project includes comprehensive unit tests with Jest:
164
+
165
+ ```bash
166
+ # Run all tests
167
+ npm test
168
+
169
+ # Run with coverage report
170
+ npm run test:coverage
171
+ ```
172
+
173
+ Coverage thresholds are set to 90% for all metrics.
174
+
175
+ ## 📝 Interactive Flow
176
+
177
+ The CLI guides you through an intuitive workflow:
178
+
179
+ 1. **Welcome**: Display branded banner with metadata
180
+ 2. **Input**: Describe your idea/problem/application
181
+ 3. **Processing**: Call the Product Discovery API
182
+ 4. **Results**: View the generated JSON discovery
183
+ 5. **Save**: Optionally save results to a file
184
+ 6. **Improve**: Refine with additional context (optional)
185
+ 7. **Repeat**: Run discovery for another idea (optional)
186
+
187
+ ## 🎨 Output Example
188
+
189
+ ```
190
+ ╭────────────────────────────────────────────────────────────────────╮
191
+ │ │
192
+ │ ____ _ _ ____ _ │
193
+ │ | _ \ _ __ ___ __| |_ _ ___| |_ | _ \(_)___ ___ ___ │
194
+ │ | |_) | '__/ _ \ / _ | | | |/ __| __| | | | | / __|/ __/ _ \ │
195
+ │ | __/| | | (_) | (_| | |_| | (__| |_ | |_| | \__ \ (_| (_) | │
196
+ │ |_| |_| \___/ \__,_|\__,_|\___|\__| |____/|_|___/\___\___/ │
197
+ │ │
198
+ │ Product Discovery CLI │
199
+ │ Generate a structured product discovery via the API. │
200
+ │ │
201
+ │ Author: AuronForge │
202
+ │ Version: 0.0.2 │
203
+ │ License: Apache-2.0 │
204
+ │ │
205
+ ╰────────────────────────────────────────────────────────────────────╯
206
+ ```
207
+
208
+ ## 📄 License
209
+
210
+ This project is licensed under the Apache-2.0 License.
211
+
212
+ ## 👤 Author
213
+
214
+ **José Eduardo Trindade E Marques**
215
+
216
+ - Company: AuronForge 🚀
217
+ - Email: edu.temarques@gmail.com
218
+ - LinkedIn: [linkedin.com/in/edu-marques29](https://linkedin.com/in/edu-marques29)
219
+
220
+ ## 🤝 Contributing
221
+
222
+ Contributions are welcome! Please feel free to submit a Pull Request.
223
+
224
+ ## 🐛 Issues
225
+
226
+ Found a bug or have a suggestion? Please open an issue on [GitHub](https://github.com/AuronForge/product-discovery-cli/issues).
227
+
228
+ ## 🔗 Links
229
+
230
+ - [npm Package](https://www.npmjs.com/package/product-discovery-cli)
231
+ - [GitHub Repository](https://github.com/AuronForge/product-discovery-cli)
232
+ - [Product Discovery Agent API](https://github.com/AuronForge/product-discovery-agent)
233
+
234
+ ---
235
+
236
+ Made with ❤️ by AuronForge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "product-discovery-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "CLI to consume the Product Discovery Agent API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "AuronForge",
@@ -8,7 +8,8 @@ class RunDiscoveryFlow {
8
8
  this.presenter = presenter;
9
9
  }
10
10
 
11
- async execute({ apiUrl, saveDefaults }) {
11
+ async execute({ apiUrl, lang, saveDefaults, i18n }) {
12
+ this.i18n = i18n;
12
13
  this.presenter.printHeader();
13
14
 
14
15
  let continueOuter = true;
@@ -18,22 +19,23 @@ class RunDiscoveryFlow {
18
19
  let shouldImprove = true;
19
20
 
20
21
  while (shouldImprove) {
21
- this.presenter.info("What do you want to run discovery for?");
22
- const idea = await this.prompt.askInput("Describe your idea/problem/application/pain:", {
23
- required: true
22
+ this.presenter.info(this.i18n.t("askIdea"));
23
+ const idea = await this.prompt.askInput(this.i18n.t("describeIdea"), {
24
+ required: true,
25
+ requiredMessage: this.i18n.t("required")
24
26
  });
25
27
 
26
28
  const attemptText = session.addIdea(idea);
27
29
 
28
- const spinner = this.presenter.spinner("Calling the discovery API...");
30
+ const spinner = this.presenter.spinner(this.i18n.t("callingApi"));
29
31
  let result;
30
32
  try {
31
- result = await this.apiClient.runDiscovery(attemptText, apiUrl);
32
- spinner.succeed("Discovery completed.");
33
+ result = await this.apiClient.runDiscovery(attemptText, apiUrl, lang);
34
+ spinner.succeed(this.i18n.t("discoveryCompleted"));
33
35
  } catch (error) {
34
- spinner.fail("Discovery failed.");
36
+ spinner.fail(this.i18n.t("discoveryFailed"));
35
37
  this.presenter.error(error.message);
36
- const retry = await this.prompt.askYesNo("Do you want to try again?");
38
+ const retry = await this.prompt.askYesNo(this.i18n.t("askRetry"));
37
39
  if (!retry) {
38
40
  return;
39
41
  }
@@ -43,7 +45,7 @@ class RunDiscoveryFlow {
43
45
  this.presenter.json(result);
44
46
 
45
47
  if (saveDefaults.autoSave === false) {
46
- const improve = await this.prompt.askYesNo("Do you want to improve the result?");
48
+ const improve = await this.prompt.askYesNo(this.i18n.t("askImprove"));
47
49
  if (!improve) {
48
50
  shouldImprove = false;
49
51
  continueOuter = false;
@@ -54,12 +56,12 @@ class RunDiscoveryFlow {
54
56
 
55
57
  const saveInfo = await this.storage.saveJson(result, saveDefaults);
56
58
  if (saveInfo.saved) {
57
- this.presenter.success(`Saved to: ${saveInfo.fullPath}`);
59
+ this.presenter.success(`${this.i18n.t("savedTo")} ${saveInfo.fullPath}`);
58
60
  shouldImprove = false;
59
61
  continue;
60
62
  }
61
63
 
62
- const improve = await this.prompt.askYesNo("Do you want to improve the result?");
64
+ const improve = await this.prompt.askYesNo(this.i18n.t("askImprove"));
63
65
  if (!improve) {
64
66
  shouldImprove = false;
65
67
  continueOuter = false;
@@ -71,7 +73,7 @@ class RunDiscoveryFlow {
71
73
  break;
72
74
  }
73
75
 
74
- const another = await this.prompt.askYesNo("Do you want to run discovery for another idea?");
76
+ const another = await this.prompt.askYesNo(this.i18n.t("askAnother"));
75
77
  if (!another) {
76
78
  continueOuter = false;
77
79
  }
@@ -1,19 +1,26 @@
1
1
  const { Command } = require("commander");
2
+ const { getTranslator } = require("../infrastructure/i18n");
2
3
 
3
4
  function buildOptions(defaultApiUrl) {
4
5
  const program = new Command();
5
- program
6
+ program.parse(process.argv);
7
+ const preOpts = program.opts();
8
+ const i18n = getTranslator(preOpts.lang || "pt-br");
9
+
10
+ const programWithDesc = new Command();
11
+ programWithDesc
6
12
  .name("product-discovery")
7
- .description("Generate a structured product discovery using the Product Discovery Agent API")
8
- .option("-u, --api-url <url>", "API URL", defaultApiUrl)
9
- .option("-c, --config <path>", "Path to JSON config file")
10
- .option("-s, --save", "Auto-save the JSON result without prompting")
11
- .option("-o, --output <dir>", "Default output directory")
12
- .option("-f, --file <name>", "Default output filename")
13
- .option("--no-save", "Disable saving prompt");
13
+ .description(i18n.t("cliDescription"))
14
+ .option("-u, --api-url <url>", i18n.t("optApiUrl"), defaultApiUrl)
15
+ .option("-l, --lang <language>", i18n.t("optLang"), "pt-br")
16
+ .option("-c, --config <path>", i18n.t("optConfig"))
17
+ .option("-s, --save", i18n.t("optSave"))
18
+ .option("-o, --output <dir>", i18n.t("optOutput"))
19
+ .option("-f, --file <name>", i18n.t("optFile"))
20
+ .option("--no-save", i18n.t("optNoSave"));
14
21
 
15
- program.parse(process.argv);
16
- return program.opts();
22
+ programWithDesc.parse(process.argv);
23
+ return programWithDesc.opts();
17
24
  }
18
25
 
19
26
  module.exports = { buildOptions };
@@ -2,9 +2,15 @@ const { stdout: output } = require("node:process");
2
2
  const chalk = require("chalk");
3
3
  const boxen = require("boxen");
4
4
  const ora = require("ora");
5
+ const pkg = require("../../package.json");
5
6
 
6
7
  class ConsolePresenter {
8
+ constructor(i18n = null) {
9
+ this.i18n = i18n;
10
+ }
11
+
7
12
  printHeader() {
13
+ if (!this.i18n) return;
8
14
  const logo = chalk.cyan(
9
15
  String.raw`
10
16
  ____ _ _ ____ _
@@ -14,9 +20,12 @@ class ConsolePresenter {
14
20
  |_| |_| \___/ \__,_|\__,_|\___|\__| |____/|_|___/\___\___/ \_/ \___|_|
15
21
  `
16
22
  );
17
- const title = chalk.bold.cyan("Product Discovery CLI");
18
- const subtitle = chalk.gray("Generate a structured product discovery via the Product Discovery Agent API.");
19
- const banner = `${logo}\n${title}\n${subtitle}`;
23
+ const title = chalk.bold.cyan(this.i18n.t("headerTitle"));
24
+ const subtitle = chalk.gray(this.i18n.t("headerSubtitle"));
25
+ const author = chalk.gray(`${this.i18n.t("headerAuthor")}: ${pkg.author}`);
26
+ const version = chalk.gray(`${this.i18n.t("headerVersion")}: ${pkg.version}`);
27
+ const license = chalk.gray(`${this.i18n.t("headerLicense")}: ${pkg.license}`);
28
+ const banner = `${logo}\n${title}\n${subtitle}\n\n${author}\n${version}\n${license}`;
20
29
  output.write(
21
30
  boxen(banner, {
22
31
  padding: 1,
@@ -37,11 +46,11 @@ class ConsolePresenter {
37
46
  }
38
47
 
39
48
  error(message) {
40
- output.write(`${chalk.red("Error:")} ${message}\n\n`);
49
+ output.write(`${chalk.red(this.i18n.t("error"))} ${message}\n\n`);
41
50
  }
42
51
 
43
52
  json(payload) {
44
- output.write(`\n${chalk.bold("Generated discovery JSON:")}\n\n`);
53
+ output.write(`\n${chalk.bold(this.i18n.t("generatedDiscovery"))}\n\n`);
45
54
  output.write(`${chalk.gray(JSON.stringify(payload, null, 2))}\n\n`);
46
55
  }
47
56
 
@@ -1,9 +1,9 @@
1
1
  class ProductDiscoveryApi {
2
- async runDiscovery(problemText, apiUrl) {
2
+ async runDiscovery(problemText, apiUrl, lang = "pt-br") {
3
3
  const response = await fetch(apiUrl, {
4
4
  method: "POST",
5
5
  headers: { "Content-Type": "application/json" },
6
- body: JSON.stringify({ problem: problemText })
6
+ body: JSON.stringify({ problem: problemText, lang })
7
7
  });
8
8
 
9
9
  const text = await response.text();
@@ -13,14 +13,14 @@ class PromptService {
13
13
  return answer;
14
14
  }
15
15
 
16
- async askInput(message, { required = false } = {}) {
16
+ async askInput(message, { required = false, requiredMessage = "This field is required." } = {}) {
17
17
  const { answer } = await inquirer.prompt([
18
18
  {
19
19
  type: "input",
20
20
  name: "answer",
21
21
  message,
22
22
  validate: required
23
- ? (value) => (value && value.trim() ? true : "This field is required.")
23
+ ? (value) => (value && value.trim() ? true : requiredMessage)
24
24
  : () => true
25
25
  }
26
26
  ]);
@@ -0,0 +1,93 @@
1
+ const translations = {
2
+ "pt-br": {
3
+ // CLI Options
4
+ cliDescription: "Gerar uma descoberta de produto estruturada usando a API do Product Discovery Agent",
5
+ optApiUrl: "URL da API",
6
+ optLang: "Código do idioma (pt-br, en-us)",
7
+ optConfig: "Caminho para o arquivo de configuração JSON",
8
+ optSave: "Salvar automaticamente o resultado JSON sem perguntar",
9
+ optOutput: "Diretório de saída padrão",
10
+ optFile: "Nome do arquivo de saída padrão",
11
+ optNoSave: "Desabilitar prompt de salvamento",
12
+
13
+ // Header
14
+ headerTitle: "Product Discovery CLI",
15
+ headerSubtitle: "Gerar uma descoberta de produto estruturada via API do Product Discovery Agent.",
16
+ headerAuthor: "Autor",
17
+ headerVersion: "Versão",
18
+ headerLicense: "Licença",
19
+
20
+ // ConsolePresenter
21
+ generatedDiscovery: "Discovery JSON gerado:",
22
+ error: "Erro:",
23
+
24
+ // RunDiscoveryFlow
25
+ askIdea: "O que você quer descobrir?",
26
+ describeIdea: "Descreva sua ideia/problema/aplicação/dor:",
27
+ callingApi: "Chamando a API de discovery...",
28
+ discoveryCompleted: "Discovery completado.",
29
+ discoveryFailed: "Discovery falhou.",
30
+ askRetry: "Você quer tentar novamente?",
31
+ askImprove: "Você quer melhorar o resultado?",
32
+ savedTo: "Salvo em:",
33
+ askAnother: "Você quer rodar discovery para outra ideia?",
34
+
35
+ // CliController
36
+ configError: "Erro no config:",
37
+
38
+ // Validation
39
+ required: "Este campo é obrigatório"
40
+ },
41
+
42
+ "en-us": {
43
+ // CLI Options
44
+ cliDescription: "Generate a structured product discovery using the Product Discovery Agent API",
45
+ optApiUrl: "API URL",
46
+ optLang: "Language code (pt-br, en-us)",
47
+ optConfig: "Path to JSON config file",
48
+ optSave: "Auto-save the JSON result without prompting",
49
+ optOutput: "Default output directory",
50
+ optFile: "Default output filename",
51
+ optNoSave: "Disable saving prompt",
52
+
53
+ // Header
54
+ headerTitle: "Product Discovery CLI",
55
+ headerSubtitle: "Generate a structured product discovery via the Product Discovery Agent API.",
56
+ headerAuthor: "Author",
57
+ headerVersion: "Version",
58
+ headerLicense: "License",
59
+
60
+ // ConsolePresenter
61
+ generatedDiscovery: "Generated discovery JSON:",
62
+ error: "Error:",
63
+
64
+ // RunDiscoveryFlow
65
+ askIdea: "What do you want to run discovery for?",
66
+ describeIdea: "Describe your idea/problem/application/pain:",
67
+ callingApi: "Calling the discovery API...",
68
+ discoveryCompleted: "Discovery completed.",
69
+ discoveryFailed: "Discovery failed.",
70
+ askRetry: "Do you want to try again?",
71
+ askImprove: "Do you want to improve the result?",
72
+ savedTo: "Saved to:",
73
+ askAnother: "Do you want to run discovery for another idea?",
74
+
75
+ // CliController
76
+ configError: "Config error:",
77
+
78
+ // Validation
79
+ required: "This field is required"
80
+ }
81
+ };
82
+
83
+ function getTranslator(lang = "pt-br") {
84
+ const normalizedLang = lang.toLowerCase();
85
+ const texts = translations[normalizedLang] || translations["pt-br"];
86
+
87
+ return {
88
+ t: (key) => texts[key] || key,
89
+ lang: normalizedLang
90
+ };
91
+ }
92
+
93
+ module.exports = { getTranslator };
@@ -1,5 +1,6 @@
1
1
  const { buildOptions } = require("../config/cliOptions");
2
2
  const { ConfigLoader } = require("../infrastructure/ConfigLoader");
3
+ const { getTranslator } = require("../infrastructure/i18n");
3
4
 
4
5
  class CliController {
5
6
  constructor({ defaultApiUrl, prompt, presenter, apiClient, storage, useCase }) {
@@ -13,11 +14,17 @@ class CliController {
13
14
 
14
15
  async start() {
15
16
  const cliOptions = buildOptions(this.defaultApiUrl);
17
+ const lang = cliOptions.lang || "pt-br";
18
+ const i18n = getTranslator(lang);
19
+
20
+ // Update presenter with i18n
21
+ this.presenter.i18n = i18n;
22
+
16
23
  let config = {};
17
24
  try {
18
25
  config = new ConfigLoader().load(cliOptions.config);
19
26
  } catch (error) {
20
- this.presenter.error(`Config error: ${error.message}`);
27
+ this.presenter.error(`${i18n.t("configError")} ${error.message}`);
21
28
  return;
22
29
  }
23
30
 
@@ -31,7 +38,7 @@ class CliController {
31
38
  prompt: this.prompt
32
39
  };
33
40
 
34
- await this.useCase.execute({ apiUrl, saveDefaults });
41
+ await this.useCase.execute({ apiUrl, lang, saveDefaults, i18n });
35
42
  }
36
43
  }
37
44