product-discovery-cli 0.0.6 → 0.0.8

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.
@@ -1,63 +1,63 @@
1
- const fs = require("node:fs");
2
- const path = require("node:path");
3
- const os = require("node:os");
4
- const { sanitizeForFolderName, timestampForPath, ensureJsonExtension } = require("../domain/PathNaming");
5
-
6
- class JsonFileStorage {
7
- resolveDesktopPath() {
8
- const home = os.homedir();
9
- const desktop = path.join(home, "Desktop");
10
- if (fs.existsSync(desktop)) return desktop;
11
- return home;
12
- }
13
-
14
- resolveSaveTarget({ result, directoryInput, filenameInput, defaultDir, defaultFileName }) {
15
- const productName = sanitizeForFolderName(result?.name || "product");
16
- const defaultFolderName = `product-${productName}-${timestampForPath()}`;
17
-
18
- let targetDir = "";
19
- let targetFilename = "";
20
-
21
- if (!directoryInput && !filenameInput) {
22
- const baseDir = defaultDir || this.resolveDesktopPath();
23
- targetDir = path.join(baseDir, defaultFolderName);
24
- targetFilename = defaultFileName || "discovery.json";
25
- } else if (directoryInput && !filenameInput) {
26
- targetDir = path.join(directoryInput, defaultFolderName);
27
- targetFilename = defaultFileName || "discovery.json";
28
- } else if (!directoryInput && filenameInput) {
29
- targetDir = defaultDir || this.resolveDesktopPath();
30
- targetFilename = filenameInput;
31
- } else {
32
- targetDir = directoryInput;
33
- targetFilename = filenameInput;
34
- }
35
-
36
- targetFilename = ensureJsonExtension(targetFilename);
37
- return { targetDir, targetFilename };
38
- }
39
-
40
- async saveJson(result, options) {
41
- const wantsSave = options.autoSave ?? (await options.prompt.askYesNo("Do you want to save the JSON file?"));
42
- if (!wantsSave) return { saved: false };
43
-
44
- const directoryInput = options.directory ?? (await options.prompt.askInput("Directory (optional, press Enter to skip):"));
45
- const filenameInput = options.filename ?? (await options.prompt.askInput("Filename (optional, press Enter to skip):"));
46
-
47
- const { targetDir, targetFilename } = this.resolveSaveTarget({
48
- result,
49
- directoryInput,
50
- filenameInput,
51
- defaultDir: options.defaultDir,
52
- defaultFileName: options.defaultFileName
53
- });
54
-
55
- fs.mkdirSync(targetDir, { recursive: true });
56
- const fullPath = path.join(targetDir, targetFilename);
57
-
58
- fs.writeFileSync(fullPath, JSON.stringify(result, null, 2), "utf-8");
59
- return { saved: true, fullPath };
60
- }
61
- }
62
-
63
- module.exports = { JsonFileStorage };
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+ const os = require("node:os");
4
+ const { sanitizeForFolderName, timestampForPath, ensureJsonExtension } = require("../domain/PathNaming");
5
+
6
+ class JsonFileStorage {
7
+ resolveDesktopPath() {
8
+ const home = os.homedir();
9
+ const desktop = path.join(home, "Desktop");
10
+ if (fs.existsSync(desktop)) return desktop;
11
+ return home;
12
+ }
13
+
14
+ resolveSaveTarget({ result, directoryInput, filenameInput, defaultDir, defaultFileName }) {
15
+ const productName = sanitizeForFolderName(result?.name || "product");
16
+ const defaultFolderName = `product-${productName}-${timestampForPath()}`;
17
+
18
+ let targetDir = "";
19
+ let targetFilename = "";
20
+
21
+ if (!directoryInput && !filenameInput) {
22
+ const baseDir = defaultDir || this.resolveDesktopPath();
23
+ targetDir = path.join(baseDir, defaultFolderName);
24
+ targetFilename = defaultFileName || "discovery.json";
25
+ } else if (directoryInput && !filenameInput) {
26
+ targetDir = path.join(directoryInput, defaultFolderName);
27
+ targetFilename = defaultFileName || "discovery.json";
28
+ } else if (!directoryInput && filenameInput) {
29
+ targetDir = defaultDir || this.resolveDesktopPath();
30
+ targetFilename = filenameInput;
31
+ } else {
32
+ targetDir = directoryInput;
33
+ targetFilename = filenameInput;
34
+ }
35
+
36
+ targetFilename = ensureJsonExtension(targetFilename);
37
+ return { targetDir, targetFilename };
38
+ }
39
+
40
+ async saveJson(result, options) {
41
+ const wantsSave = options.autoSave ?? (await options.prompt.askYesNo("Do you want to save the JSON file?"));
42
+ if (!wantsSave) return { saved: false };
43
+
44
+ const directoryInput = options.directory ?? (await options.prompt.askInput("Directory (optional, press Enter to skip):"));
45
+ const filenameInput = options.filename ?? (await options.prompt.askInput("Filename (optional, press Enter to skip):"));
46
+
47
+ const { targetDir, targetFilename } = this.resolveSaveTarget({
48
+ result,
49
+ directoryInput,
50
+ filenameInput,
51
+ defaultDir: options.defaultDir,
52
+ defaultFileName: options.defaultFileName
53
+ });
54
+
55
+ fs.mkdirSync(targetDir, { recursive: true });
56
+ const fullPath = path.join(targetDir, targetFilename);
57
+
58
+ fs.writeFileSync(fullPath, JSON.stringify(result, null, 2), "utf-8");
59
+ return { saved: true, fullPath };
60
+ }
61
+ }
62
+
63
+ module.exports = { JsonFileStorage };
@@ -1,26 +1,26 @@
1
- class ProductDiscoveryApi {
2
- async runDiscovery(problemText, apiUrl, lang = "pt-br") {
3
- const response = await fetch(apiUrl, {
4
- method: "POST",
5
- headers: { "Content-Type": "application/json" },
6
- body: JSON.stringify({ problem: problemText, lang })
7
- });
8
-
9
- const text = await response.text();
10
- let payload = null;
11
- try {
12
- payload = text ? JSON.parse(text) : null;
13
- } catch {
14
- payload = text;
15
- }
16
-
17
- if (!response.ok) {
18
- const message = payload?.message || response.statusText || "Unknown error";
19
- throw new Error(`API error (${response.status}): ${message}`);
20
- }
21
-
22
- return payload;
23
- }
24
- }
25
-
26
- module.exports = { ProductDiscoveryApi };
1
+ class ProductDiscoveryApi {
2
+ async runDiscovery(problemText, apiUrl, lang = "pt-br") {
3
+ const response = await fetch(apiUrl, {
4
+ method: "POST",
5
+ headers: { "Content-Type": "application/json" },
6
+ body: JSON.stringify({ problem: problemText, lang })
7
+ });
8
+
9
+ const text = await response.text();
10
+ let payload = null;
11
+ try {
12
+ payload = text ? JSON.parse(text) : null;
13
+ } catch {
14
+ payload = text;
15
+ }
16
+
17
+ if (!response.ok) {
18
+ const message = payload?.message || response.statusText || "Unknown error";
19
+ throw new Error(`API error (${response.status}): ${message}`);
20
+ }
21
+
22
+ return payload;
23
+ }
24
+ }
25
+
26
+ module.exports = { ProductDiscoveryApi };
@@ -1,31 +1,31 @@
1
- const inquirer = require("inquirer");
2
-
3
- class PromptService {
4
- async askYesNo(message) {
5
- const { answer } = await inquirer.prompt([
6
- {
7
- type: "confirm",
8
- name: "answer",
9
- message,
10
- default: false
11
- }
12
- ]);
13
- return answer;
14
- }
15
-
16
- async askInput(message, { required = false, requiredMessage = "This field is required." } = {}) {
17
- const { answer } = await inquirer.prompt([
18
- {
19
- type: "input",
20
- name: "answer",
21
- message,
22
- validate: required
23
- ? (value) => (value && value.trim() ? true : requiredMessage)
24
- : () => true
25
- }
26
- ]);
27
- return answer.trim();
28
- }
29
- }
30
-
31
- module.exports = { PromptService };
1
+ const inquirer = require("inquirer");
2
+
3
+ class PromptService {
4
+ async askYesNo(message) {
5
+ const { answer } = await inquirer.prompt([
6
+ {
7
+ type: "confirm",
8
+ name: "answer",
9
+ message,
10
+ default: false
11
+ }
12
+ ]);
13
+ return answer;
14
+ }
15
+
16
+ async askInput(message, { required = false, requiredMessage = "This field is required." } = {}) {
17
+ const { answer } = await inquirer.prompt([
18
+ {
19
+ type: "input",
20
+ name: "answer",
21
+ message,
22
+ validate: required
23
+ ? (value) => (value && value.trim() ? true : requiredMessage)
24
+ : () => true
25
+ }
26
+ ]);
27
+ return answer.trim();
28
+ }
29
+ }
30
+
31
+ module.exports = { PromptService };
@@ -1,101 +1,101 @@
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
- // Goodbye
42
- thankYou: "Obrigado por usar o Product Discovery CLI!",
43
- closeConsole: "Você pode fechar esta janela ou pressionar Ctrl+C"
44
- },
45
-
46
- "en-us": {
47
- // CLI Options
48
- cliDescription: "Generate a structured product discovery using the Product Discovery Agent API",
49
- optApiUrl: "API URL",
50
- optLang: "Language code (pt-br, en-us)",
51
- optConfig: "Path to JSON config file",
52
- optSave: "Auto-save the JSON result without prompting",
53
- optOutput: "Default output directory",
54
- optFile: "Default output filename",
55
- optNoSave: "Disable saving prompt",
56
-
57
- // Header
58
- headerTitle: "Product Discovery CLI",
59
- headerSubtitle: "Generate a structured product discovery via the Product Discovery Agent API.",
60
- headerAuthor: "Author",
61
- headerVersion: "Version",
62
- headerLicense: "License",
63
-
64
- // ConsolePresenter
65
- generatedDiscovery: "Generated discovery JSON:",
66
- error: "Error:",
67
-
68
- // RunDiscoveryFlow
69
- askIdea: "What do you want to run discovery for?",
70
- describeIdea: "Describe your idea/problem/application/pain:",
71
- callingApi: "Calling the discovery API...",
72
- discoveryCompleted: "Discovery completed.",
73
- discoveryFailed: "Discovery failed.",
74
- askRetry: "Do you want to try again?",
75
- askImprove: "Do you want to improve the result?",
76
- savedTo: "Saved to:",
77
- askAnother: "Do you want to run discovery for another idea?",
78
-
79
- // CliController
80
- configError: "Config error:",
81
-
82
- // Validation
83
- required: "This field is required",
84
-
85
- // Goodbye
86
- thankYou: "Thank you for using Product Discovery CLI!",
87
- closeConsole: "You can close this window or press Ctrl+C"
88
- }
89
- };
90
-
91
- function getTranslator(lang = "pt-br") {
92
- const normalizedLang = lang.toLowerCase();
93
- const texts = translations[normalizedLang] || translations["pt-br"];
94
-
95
- return {
96
- t: (key) => texts[key] || key,
97
- lang: normalizedLang
98
- };
99
- }
100
-
101
- module.exports = { getTranslator };
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
+ // Goodbye
42
+ thankYou: "Obrigado por usar o Product Discovery CLI!",
43
+ closeConsole: "Você pode fechar esta janela ou pressionar Ctrl+C"
44
+ },
45
+
46
+ "en-us": {
47
+ // CLI Options
48
+ cliDescription: "Generate a structured product discovery using the Product Discovery Agent API",
49
+ optApiUrl: "API URL",
50
+ optLang: "Language code (pt-br, en-us)",
51
+ optConfig: "Path to JSON config file",
52
+ optSave: "Auto-save the JSON result without prompting",
53
+ optOutput: "Default output directory",
54
+ optFile: "Default output filename",
55
+ optNoSave: "Disable saving prompt",
56
+
57
+ // Header
58
+ headerTitle: "Product Discovery CLI",
59
+ headerSubtitle: "Generate a structured product discovery via the Product Discovery Agent API.",
60
+ headerAuthor: "Author",
61
+ headerVersion: "Version",
62
+ headerLicense: "License",
63
+
64
+ // ConsolePresenter
65
+ generatedDiscovery: "Generated discovery JSON:",
66
+ error: "Error:",
67
+
68
+ // RunDiscoveryFlow
69
+ askIdea: "What do you want to run discovery for?",
70
+ describeIdea: "Describe your idea/problem/application/pain:",
71
+ callingApi: "Calling the discovery API...",
72
+ discoveryCompleted: "Discovery completed.",
73
+ discoveryFailed: "Discovery failed.",
74
+ askRetry: "Do you want to try again?",
75
+ askImprove: "Do you want to improve the result?",
76
+ savedTo: "Saved to:",
77
+ askAnother: "Do you want to run discovery for another idea?",
78
+
79
+ // CliController
80
+ configError: "Config error:",
81
+
82
+ // Validation
83
+ required: "This field is required",
84
+
85
+ // Goodbye
86
+ thankYou: "Thank you for using Product Discovery CLI!",
87
+ closeConsole: "You can close this window or press Ctrl+C"
88
+ }
89
+ };
90
+
91
+ function getTranslator(lang = "pt-br") {
92
+ const normalizedLang = lang.toLowerCase();
93
+ const texts = translations[normalizedLang] || translations["pt-br"];
94
+
95
+ return {
96
+ t: (key) => texts[key] || key,
97
+ lang: normalizedLang
98
+ };
99
+ }
100
+
101
+ module.exports = { getTranslator };
@@ -1,45 +1,45 @@
1
- const { buildOptions } = require("../config/cliOptions");
2
- const { ConfigLoader } = require("../infrastructure/ConfigLoader");
3
- const { getTranslator } = require("../infrastructure/i18n");
4
-
5
- class CliController {
6
- constructor({ defaultApiUrl, prompt, presenter, apiClient, storage, useCase }) {
7
- this.defaultApiUrl = defaultApiUrl;
8
- this.prompt = prompt;
9
- this.presenter = presenter;
10
- this.apiClient = apiClient;
11
- this.storage = storage;
12
- this.useCase = useCase;
13
- }
14
-
15
- async start() {
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
-
23
- let config = {};
24
- try {
25
- config = new ConfigLoader().load(cliOptions.config);
26
- } catch (error) {
27
- this.presenter.error(`${i18n.t("configError")} ${error.message}`);
28
- return;
29
- }
30
-
31
- const apiUrl = cliOptions.apiUrl || config.apiUrl || process.env.PRODUCT_DISCOVERY_API_URL || this.defaultApiUrl;
32
- const saveDefaults = {
33
- autoSave: typeof cliOptions.save === "boolean" ? cliOptions.save : config.autoSave,
34
- directory: cliOptions.output || config.defaultOutputDir,
35
- filename: cliOptions.file || config.defaultFileName,
36
- defaultDir: cliOptions.output || config.defaultOutputDir,
37
- defaultFileName: cliOptions.file || config.defaultFileName,
38
- prompt: this.prompt
39
- };
40
-
41
- await this.useCase.execute({ apiUrl, lang, saveDefaults, i18n });
42
- }
43
- }
44
-
45
- module.exports = { CliController };
1
+ const { buildOptions } = require("../config/cliOptions");
2
+ const { ConfigLoader } = require("../infrastructure/ConfigLoader");
3
+ const { getTranslator } = require("../infrastructure/i18n");
4
+
5
+ class CliController {
6
+ constructor({ defaultApiUrl, prompt, presenter, apiClient, storage, useCase }) {
7
+ this.defaultApiUrl = defaultApiUrl;
8
+ this.prompt = prompt;
9
+ this.presenter = presenter;
10
+ this.apiClient = apiClient;
11
+ this.storage = storage;
12
+ this.useCase = useCase;
13
+ }
14
+
15
+ async start() {
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
+
23
+ let config = {};
24
+ try {
25
+ config = new ConfigLoader().load(cliOptions.config);
26
+ } catch (error) {
27
+ this.presenter.error(`${i18n.t("configError")} ${error.message}`);
28
+ return;
29
+ }
30
+
31
+ const apiUrl = cliOptions.apiUrl || config.apiUrl || process.env.PRODUCT_DISCOVERY_API_URL || this.defaultApiUrl;
32
+ const saveDefaults = {
33
+ autoSave: typeof cliOptions.save === "boolean" ? cliOptions.save : config.autoSave,
34
+ directory: cliOptions.output || config.defaultOutputDir,
35
+ filename: cliOptions.file || config.defaultFileName,
36
+ defaultDir: cliOptions.output || config.defaultOutputDir,
37
+ defaultFileName: cliOptions.file || config.defaultFileName,
38
+ prompt: this.prompt
39
+ };
40
+
41
+ await this.useCase.execute({ apiUrl, lang, saveDefaults, i18n });
42
+ }
43
+ }
44
+
45
+ module.exports = { CliController };