product-discovery-cli 0.0.1 → 0.0.2
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/package.json +1 -1
- package/src/application/RunDiscoveryFlow.js +15 -13
- package/src/config/cliOptions.js +17 -10
- package/src/infrastructure/ConsolePresenter.js +14 -5
- package/src/infrastructure/ProductDiscoveryApi.js +2 -2
- package/src/infrastructure/PromptService.js +2 -2
- package/src/infrastructure/i18n.js +93 -0
- package/src/presentation/CliController.js +9 -2
package/package.json
CHANGED
|
@@ -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("
|
|
22
|
-
const idea = await this.prompt.askInput("
|
|
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("
|
|
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(
|
|
33
|
+
result = await this.apiClient.runDiscovery(attemptText, apiUrl, lang);
|
|
34
|
+
spinner.succeed(this.i18n.t("discoveryCompleted"));
|
|
33
35
|
} catch (error) {
|
|
34
|
-
spinner.fail(
|
|
36
|
+
spinner.fail(this.i18n.t("discoveryFailed"));
|
|
35
37
|
this.presenter.error(error.message);
|
|
36
|
-
const retry = await this.prompt.askYesNo("
|
|
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("
|
|
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(
|
|
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("
|
|
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("
|
|
76
|
+
const another = await this.prompt.askYesNo(this.i18n.t("askAnother"));
|
|
75
77
|
if (!another) {
|
|
76
78
|
continueOuter = false;
|
|
77
79
|
}
|
package/src/config/cliOptions.js
CHANGED
|
@@ -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("
|
|
8
|
-
.option("-u, --api-url <url>", "
|
|
9
|
-
.option("-
|
|
10
|
-
.option("-
|
|
11
|
-
.option("-
|
|
12
|
-
.option("-
|
|
13
|
-
.option("
|
|
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
|
-
|
|
16
|
-
return
|
|
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("
|
|
18
|
-
const subtitle = chalk.gray(
|
|
19
|
-
const
|
|
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("
|
|
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("
|
|
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 :
|
|
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(
|
|
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
|
|