epa-testeprojetoia 0.1.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.
Files changed (100) hide show
  1. package/.idea/epa_mcp.iml +8 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/php.xml +19 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/AGENTS.md +10 -0
  6. package/README.md +339 -0
  7. package/dist/agent/agentHelpers.js +21 -0
  8. package/dist/agent/openaiAgent.js +170 -0
  9. package/dist/agent/slidingWindow.js +13 -0
  10. package/dist/api/epaApiClient.js +59 -0
  11. package/dist/cli/index.js +47 -0
  12. package/dist/config/credentialStore.js +92 -0
  13. package/dist/config/ensureCliAuth.js +127 -0
  14. package/dist/config/loadConfig.js +40 -0
  15. package/dist/config/setup.js +23 -0
  16. package/dist/core/createReferenceTool.js +12 -0
  17. package/dist/core/createTool.js +32 -0
  18. package/dist/mocks/requestMocks.js +47 -0
  19. package/dist/server/server.js +41 -0
  20. package/dist/server/stdioServer.js +3 -0
  21. package/dist/services/request/requestFilters.js +1 -0
  22. package/dist/services/requestService.js +81 -0
  23. package/dist/services/teamService.js +18 -0
  24. package/dist/sql/createSqlConnection.js +13 -0
  25. package/dist/sql/fetchSchemaSummary.js +38 -0
  26. package/dist/sql/generateSqlFromQuestion.js +83 -0
  27. package/dist/sql/generateSqlPlan.js +111 -0
  28. package/dist/sql/getSqlAgentErrorMessage.js +15 -0
  29. package/dist/sql/loadSqlAgentConfig.js +24 -0
  30. package/dist/sql/loadSqlConfig.js +43 -0
  31. package/dist/sql/parseSqlQuestionHints.js +29 -0
  32. package/dist/sql/runSqlAgentCli.js +163 -0
  33. package/dist/sql/runSqlCli.js +34 -0
  34. package/dist/sql/selectRelevantTables.js +136 -0
  35. package/dist/sql/sqlGuard.js +21 -0
  36. package/dist/sql/sqlPlan.js +16 -0
  37. package/dist/tests/requestService.test.js +110 -0
  38. package/dist/tools/analytics/teamReport.draft.js +16 -0
  39. package/dist/tools/loadTools.js +40 -0
  40. package/dist/tools/requests/assignees.js +50 -0
  41. package/dist/tools/requests/clients.draft.js +10 -0
  42. package/dist/tools/requests/create.draft.js +51 -0
  43. package/dist/tools/requests/list.js +50 -0
  44. package/dist/tools/requests/priorities.js +2 -0
  45. package/dist/tools/requests/services.draft.js +20 -0
  46. package/dist/tools/requests/types.js +16 -0
  47. package/dist/tools/requests/units.draft.js +10 -0
  48. package/dist/tools/requests/view.draft.js +20 -0
  49. package/dist/utils/buildDateRange.js +18 -0
  50. package/dist/utils/findIdByDescription.js +4 -0
  51. package/dist/utils/resolveAssigneeId.js +14 -0
  52. package/dist/utils/toolNameMaps.js +23 -0
  53. package/package.json +31 -0
  54. package/src/agent/agentHelpers.ts +25 -0
  55. package/src/agent/openaiAgent.ts +205 -0
  56. package/src/agent/slidingWindow.ts +17 -0
  57. package/src/api/epaApiClient.ts +82 -0
  58. package/src/cli/index.ts +61 -0
  59. package/src/config/credentialStore.ts +130 -0
  60. package/src/config/ensureCliAuth.ts +152 -0
  61. package/src/config/loadConfig.ts +62 -0
  62. package/src/config/setup.ts +35 -0
  63. package/src/core/createReferenceTool.ts +17 -0
  64. package/src/core/createTool.ts +51 -0
  65. package/src/mocks/requestMocks.ts +52 -0
  66. package/src/server/server.ts +61 -0
  67. package/src/server/stdioServer.ts +5 -0
  68. package/src/services/request/requestFilters.ts +12 -0
  69. package/src/services/requestService.ts +126 -0
  70. package/src/services/teamService.ts +27 -0
  71. package/src/sql/createSqlConnection.ts +15 -0
  72. package/src/sql/fetchSchemaSummary.ts +64 -0
  73. package/src/sql/generateSqlFromQuestion.ts +105 -0
  74. package/src/sql/generateSqlPlan.ts +133 -0
  75. package/src/sql/getSqlAgentErrorMessage.ts +24 -0
  76. package/src/sql/loadSqlAgentConfig.ts +33 -0
  77. package/src/sql/loadSqlConfig.ts +75 -0
  78. package/src/sql/parseSqlQuestionHints.ts +46 -0
  79. package/src/sql/runSqlAgentCli.ts +204 -0
  80. package/src/sql/runSqlCli.ts +40 -0
  81. package/src/sql/selectRelevantTables.ts +184 -0
  82. package/src/sql/sqlGuard.ts +28 -0
  83. package/src/sql/sqlPlan.ts +28 -0
  84. package/src/tests/requestService.test.ts +152 -0
  85. package/src/tools/analytics/teamReport.draft.ts +25 -0
  86. package/src/tools/loadTools.ts +59 -0
  87. package/src/tools/requests/assignees.ts +59 -0
  88. package/src/tools/requests/clients.draft.ts +18 -0
  89. package/src/tools/requests/create.draft.ts +59 -0
  90. package/src/tools/requests/list.ts +57 -0
  91. package/src/tools/requests/priorities.ts +6 -0
  92. package/src/tools/requests/services.draft.ts +24 -0
  93. package/src/tools/requests/types.ts +18 -0
  94. package/src/tools/requests/units.draft.ts +18 -0
  95. package/src/tools/requests/view.draft.ts +27 -0
  96. package/src/utils/buildDateRange.ts +22 -0
  97. package/src/utils/findIdByDescription.ts +10 -0
  98. package/src/utils/resolveAssigneeId.ts +24 -0
  99. package/src/utils/toolNameMaps.ts +33 -0
  100. package/tsconfig.json +11 -0
@@ -0,0 +1,133 @@
1
+ import OpenAI from "openai"
2
+ import { loadSqlAgentConfig } from "./loadSqlAgentConfig.js"
3
+ import type { SqlQuestionHints } from "./parseSqlQuestionHints.js"
4
+ import type { SqlQueryPlan } from "./sqlPlan.js"
5
+
6
+ function buildHintsSummary(hints: SqlQuestionHints) {
7
+ const lines: string[] = []
8
+
9
+ if (hints.tableNames.length > 0) {
10
+ lines.push(`Tabelas indicadas pelo usuario: ${hints.tableNames.join(", ")}`)
11
+ }
12
+
13
+ if (hints.fieldNames.length > 0) {
14
+ lines.push(`Campos indicados pelo usuario: ${hints.fieldNames.join(", ")}`)
15
+ }
16
+
17
+ if (hints.filters.length > 0) {
18
+ lines.push(`Filtros indicados pelo usuario: ${hints.filters.join(", ")}`)
19
+ }
20
+
21
+ if (hints.orderBy.length > 0) {
22
+ lines.push(`Ordenacao indicada pelo usuario: ${hints.orderBy.join(", ")}`)
23
+ }
24
+
25
+ if (hints.limit) {
26
+ lines.push(`Limite indicado pelo usuario: ${hints.limit}`)
27
+ }
28
+
29
+ return lines.length > 0 ? lines.join("\n") : "Nenhuma pista explicita informada."
30
+ }
31
+
32
+ function extractJson(content: string) {
33
+ const fencedMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/i)
34
+ if (fencedMatch) {
35
+ return fencedMatch[1].trim()
36
+ }
37
+
38
+ return content.trim()
39
+ }
40
+
41
+ function normalizePlan(rawPlan: Partial<SqlQueryPlan>): SqlQueryPlan {
42
+ return {
43
+ goal: String(rawPlan.goal ?? "").trim(),
44
+ mainTable: String(rawPlan.mainTable ?? "").trim(),
45
+ relatedTables: Array.isArray(rawPlan.relatedTables)
46
+ ? rawPlan.relatedTables.map((value) => String(value).trim()).filter(Boolean)
47
+ : [],
48
+ relationshipFields: Array.isArray(rawPlan.relationshipFields)
49
+ ? rawPlan.relationshipFields.map((value) => String(value).trim()).filter(Boolean)
50
+ : [],
51
+ selectedFields: Array.isArray(rawPlan.selectedFields)
52
+ ? rawPlan.selectedFields.map((value) => String(value).trim()).filter(Boolean)
53
+ : [],
54
+ filters: Array.isArray(rawPlan.filters)
55
+ ? rawPlan.filters.map((value) => String(value).trim()).filter(Boolean)
56
+ : [],
57
+ orderBy: Array.isArray(rawPlan.orderBy)
58
+ ? rawPlan.orderBy.map((value) => String(value).trim()).filter(Boolean)
59
+ : [],
60
+ limit: Number.isFinite(Number(rawPlan.limit)) && Number(rawPlan.limit) > 0
61
+ ? Number(rawPlan.limit)
62
+ : 10
63
+ }
64
+ }
65
+
66
+ export async function generateSqlPlan(
67
+ question: string,
68
+ schemaSummary: string,
69
+ hints: SqlQuestionHints,
70
+ additionalGuidance?: string
71
+ ) {
72
+ const sqlAgentConfig = loadSqlAgentConfig()
73
+ const openai = new OpenAI({ apiKey: sqlAgentConfig.openaiApiKey })
74
+
75
+ const completion = await openai.chat.completions.create({
76
+ model: sqlAgentConfig.model,
77
+ temperature: 0,
78
+ messages: [
79
+ {
80
+ role: "system",
81
+ content: `
82
+ Voce e um assistente tecnico que monta um plano de consulta para MySQL/MariaDB antes da SQL.
83
+
84
+ Regras obrigatorias:
85
+ - responda somente com JSON valido
86
+ - use apenas tabelas e colunas presentes no schema informado
87
+ - respeite pistas explicitas do usuario como prioridade
88
+ - quando o usuario indicar tabelas, campos ou relacionamento, reflita isso no plano
89
+ - quando o usuario indicar filtros, ordenacao ou limite, reflita isso no plano
90
+ - preencha o limite com 10 quando o pedido for listar registros sem quantidade especifica
91
+
92
+ Formato do JSON:
93
+ {
94
+ "goal": "string",
95
+ "mainTable": "string",
96
+ "relatedTables": ["string"],
97
+ "relationshipFields": ["string"],
98
+ "selectedFields": ["string"],
99
+ "filters": ["string"],
100
+ "orderBy": ["string"],
101
+ "limit": 10
102
+ }
103
+ `.trim()
104
+ },
105
+ {
106
+ role: "user",
107
+ content: `
108
+ Schema disponivel:
109
+ ${schemaSummary}
110
+
111
+ Pistas explicitas do usuario:
112
+ ${buildHintsSummary(hints)}
113
+
114
+ Orientacoes adicionais:
115
+ ${additionalGuidance?.trim() || "Nenhuma orientacao adicional."}
116
+
117
+ Pedido do usuario:
118
+ ${question}
119
+ `.trim()
120
+ }
121
+ ]
122
+ })
123
+
124
+ const content = completion.choices[0]?.message?.content ?? ""
125
+
126
+ if (!content.trim()) {
127
+ throw new Error("A LLM nao retornou um plano de consulta.")
128
+ }
129
+
130
+ const json = extractJson(content)
131
+ const parsedPlan = JSON.parse(json) as Partial<SqlQueryPlan>
132
+ return normalizePlan(parsedPlan)
133
+ }
@@ -0,0 +1,24 @@
1
+ export function getSqlAgentErrorMessage(error: unknown): string {
2
+ if (!(error instanceof Error)) {
3
+ return `Erro ao executar SQL Agent: ${String(error)}`
4
+ }
5
+
6
+ const openAiError = error as Error & {
7
+ status?: number
8
+ code?: string
9
+ type?: string
10
+ }
11
+
12
+ if (
13
+ openAiError.status === 429 ||
14
+ openAiError.code === "insufficient_quota" ||
15
+ openAiError.type === "insufficient_quota"
16
+ ) {
17
+ return [
18
+ "Nao foi possivel gerar a SQL porque a chave da OpenAI esta sem cota disponivel.",
19
+ 'Verifique billing/limites da conta ou configure outra chave em "OPENAI_API_KEY" ou na CLI.'
20
+ ].join(" ")
21
+ }
22
+
23
+ return `Erro ao executar SQL Agent: ${error.message}`
24
+ }
@@ -0,0 +1,33 @@
1
+ import dotenv from "dotenv"
2
+ import { readStoredCredentials } from "../config/credentialStore.js"
3
+
4
+ dotenv.config()
5
+
6
+ function getEnvValue(...keys: string[]): string | undefined {
7
+ for (const key of keys) {
8
+ const value = process.env[key]
9
+ if (value && value.trim()) {
10
+ return value.trim()
11
+ }
12
+ }
13
+
14
+ return undefined
15
+ }
16
+
17
+ export function loadSqlAgentConfig() {
18
+ const storedCredentials = readStoredCredentials()
19
+ const openaiApiKey =
20
+ getEnvValue("OPENAI_API_KEY", "openaiApiKey") ??
21
+ storedCredentials.openaiApiKey
22
+
23
+ if (!openaiApiKey) {
24
+ throw new Error(
25
+ 'OpenAI API KEY nao encontrada. Defina "OPENAI_API_KEY" no ambiente ou configure a chave pela CLI.'
26
+ )
27
+ }
28
+
29
+ return {
30
+ openaiApiKey,
31
+ model: getEnvValue("SQL_AGENT_MODEL") ?? "gpt-4o-mini"
32
+ }
33
+ }
@@ -0,0 +1,75 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import dotenv from "dotenv"
4
+
5
+ dotenv.config()
6
+
7
+ type SqlConfigFile = {
8
+ sql?: {
9
+ host?: string
10
+ port?: number
11
+ database?: string
12
+ user?: string
13
+ password?: string
14
+ ssl?: boolean
15
+ }
16
+ }
17
+
18
+ export interface SqlConfig {
19
+ host: string
20
+ port: number
21
+ database: string
22
+ user: string
23
+ password: string
24
+ ssl: boolean
25
+ }
26
+
27
+ function readSqlConfigFile(): SqlConfigFile["sql"] {
28
+ const configPath = path.join(process.cwd(), "config", "config.json")
29
+
30
+ if (!fs.existsSync(configPath)) {
31
+ return undefined
32
+ }
33
+
34
+ const raw = fs.readFileSync(configPath, "utf-8")
35
+ const json = JSON.parse(raw) as SqlConfigFile
36
+ return json.sql
37
+ }
38
+
39
+ function getEnvValue(...keys: string[]): string | undefined {
40
+ for (const key of keys) {
41
+ const value = process.env[key]
42
+ if (value && value.trim()) {
43
+ return value.trim()
44
+ }
45
+ }
46
+
47
+ return undefined
48
+ }
49
+
50
+ export function loadSqlConfig(): SqlConfig {
51
+ const fileConfig = readSqlConfigFile()
52
+
53
+ const host = getEnvValue("DB_HOST", "MYSQL_HOST") ?? fileConfig?.host
54
+ const database = getEnvValue("DB_NAME", "MYSQL_DATABASE") ?? fileConfig?.database
55
+ const user = getEnvValue("DB_USER", "MYSQL_USER") ?? fileConfig?.user
56
+ const password = getEnvValue("DB_PASSWORD", "MYSQL_PASSWORD") ?? fileConfig?.password
57
+ const portRaw = getEnvValue("DB_PORT", "MYSQL_PORT") ?? String(fileConfig?.port ?? "3306")
58
+ const sslRaw = String(getEnvValue("DB_SSL", "MYSQL_SSL") ?? fileConfig?.ssl ?? "false").toLowerCase()
59
+ const port = Number(portRaw)
60
+
61
+ if (!host || !database || !user || !password) {
62
+ throw new Error(
63
+ 'Configuracao SQL incompleta. Defina DB_HOST, DB_NAME, DB_USER e DB_PASSWORD nas variaveis de ambiente ou preencha o bloco "sql" em config/config.json.'
64
+ )
65
+ }
66
+
67
+ return {
68
+ host,
69
+ port: Number.isFinite(port) && port > 0 ? port : 3306,
70
+ database,
71
+ user,
72
+ password,
73
+ ssl: sslRaw === "true" || sslRaw === "1"
74
+ }
75
+ }
@@ -0,0 +1,46 @@
1
+ export type SqlQuestionHints = {
2
+ tableNames: string[]
3
+ fieldNames: string[]
4
+ filters: string[]
5
+ orderBy: string[]
6
+ limit?: number
7
+ }
8
+
9
+ function unique(values: string[]) {
10
+ return [...new Set(values)]
11
+ }
12
+
13
+ function extractMatches(question: string, pattern: RegExp, captureIndex = 1) {
14
+ const matches: string[] = []
15
+
16
+ for (const match of question.matchAll(pattern)) {
17
+ const value = match[captureIndex]?.trim()
18
+ if (value) {
19
+ matches.push(value)
20
+ }
21
+ }
22
+
23
+ return matches
24
+ }
25
+
26
+ export function parseSqlQuestionHints(question: string): SqlQuestionHints {
27
+ const explicitTables = extractMatches(question, /tabela\s*:\s*([a-zA-Z0-9_]+)/gi)
28
+ const relationshipTables = extractMatches(
29
+ question,
30
+ /(vincular com tabela|relacionar com tabela|join com tabela)\s*:?\s*([a-zA-Z0-9_]+)/gi,
31
+ 2
32
+ )
33
+ const explicitFields = extractMatches(question, /campo\s*:\s*([a-zA-Z0-9_]+)/gi)
34
+ const explicitFilters = extractMatches(question, /filtro\s*:\s*([^\n\r]+)/gi)
35
+ const explicitOrderBy = extractMatches(question, /ordenar por\s*:\s*([^\n\r]+)/gi)
36
+ const explicitLimitRaw = extractMatches(question, /limite\s*:\s*(\d+)/gi)
37
+ const explicitLimit = explicitLimitRaw.length > 0 ? Number(explicitLimitRaw[0]) : undefined
38
+
39
+ return {
40
+ tableNames: unique([...explicitTables, ...relationshipTables]),
41
+ fieldNames: unique(explicitFields),
42
+ filters: unique(explicitFilters),
43
+ orderBy: unique(explicitOrderBy),
44
+ limit: Number.isFinite(explicitLimit) && explicitLimit && explicitLimit > 0 ? explicitLimit : undefined
45
+ }
46
+ }
@@ -0,0 +1,204 @@
1
+ import readline from "readline"
2
+ import { createSqlConnection } from "./createSqlConnection.js"
3
+ import { fetchAvailableTableNames, fetchSchemaSummary } from "./fetchSchemaSummary.js"
4
+ import { generateSqlPlan } from "./generateSqlPlan.js"
5
+ import { generateSqlFromQuestion } from "./generateSqlFromQuestion.js"
6
+ import { getSqlAgentErrorMessage } from "./getSqlAgentErrorMessage.js"
7
+ import { parseSqlQuestionHints } from "./parseSqlQuestionHints.js"
8
+ import { selectRelevantTables, type RankedTable } from "./selectRelevantTables.js"
9
+ import { validateReadOnlySql } from "./sqlGuard.js"
10
+ import { formatSqlPlan } from "./sqlPlan.js"
11
+
12
+ function askQuestion(rl: readline.Interface, question: string): Promise<string> {
13
+ return new Promise((resolve) => rl.question(question, resolve))
14
+ }
15
+
16
+ function normalizeQuestion(rawQuestion?: string): string {
17
+ return (rawQuestion ?? "").trim()
18
+ }
19
+
20
+ async function askForTableSelection(candidateTables: RankedTable[]) {
21
+ const rl = readline.createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout
24
+ })
25
+
26
+ try {
27
+ console.log("Encontrei mais de uma tabela possivel para esse pedido:")
28
+ candidateTables.forEach((table, index) => {
29
+ console.log(`${index + 1}. ${table.name}`)
30
+ })
31
+ console.log("M. Informar manualmente o nome da tabela")
32
+ console.log("")
33
+
34
+ while (true) {
35
+ const answer = (await askQuestion(rl, "Escolha o numero da tabela correta ou M para informar manualmente: ")).trim()
36
+
37
+ if (answer.toLowerCase() === "m") {
38
+ const manualTableName = (await askQuestion(rl, "Digite o nome exato da tabela: ")).trim()
39
+
40
+ if (manualTableName) {
41
+ return manualTableName
42
+ }
43
+
44
+ console.log("Informe um nome de tabela valido.")
45
+ continue
46
+ }
47
+
48
+ const selectedIndex = Number(answer)
49
+
50
+ if (Number.isInteger(selectedIndex) && selectedIndex >= 1 && selectedIndex <= candidateTables.length) {
51
+ return candidateTables[selectedIndex - 1].name
52
+ }
53
+
54
+ console.log("Opcao invalida. Informe um numero da lista.")
55
+ }
56
+ } finally {
57
+ rl.close()
58
+ }
59
+ }
60
+
61
+ async function askForPlanAction() {
62
+ const rl = readline.createInterface({
63
+ input: process.stdin,
64
+ output: process.stdout
65
+ })
66
+
67
+ try {
68
+ console.log("")
69
+ console.log("1. Executar esse plano")
70
+ console.log("2. Corrigir o plano")
71
+ console.log("3. Cancelar")
72
+ console.log("")
73
+
74
+ while (true) {
75
+ const answer = (await askQuestion(rl, "Escolha uma opcao: ")).trim()
76
+
77
+ if (answer === "1" || answer === "2" || answer === "3") {
78
+ return answer
79
+ }
80
+
81
+ console.log("Opcao invalida. Escolha 1, 2 ou 3.")
82
+ }
83
+ } finally {
84
+ rl.close()
85
+ }
86
+ }
87
+
88
+ async function askForPlanCorrection() {
89
+ const rl = readline.createInterface({
90
+ input: process.stdin,
91
+ output: process.stdout
92
+ })
93
+
94
+ try {
95
+ return normalizeQuestion(
96
+ await askQuestion(rl, "Descreva a correcao do plano: ")
97
+ )
98
+ } finally {
99
+ rl.close()
100
+ }
101
+ }
102
+
103
+ export async function runSqlAgentCli(initialQuestion?: string) {
104
+ let question = normalizeQuestion(initialQuestion)
105
+ let additionalGuidance = ""
106
+
107
+ if (!question) {
108
+ const rl = readline.createInterface({
109
+ input: process.stdin,
110
+ output: process.stdout
111
+ })
112
+
113
+ try {
114
+ question = normalizeQuestion(await askQuestion(rl, "Pergunta para o SQL Agent: "))
115
+ } finally {
116
+ rl.close()
117
+ }
118
+ }
119
+
120
+ let connection: Awaited<ReturnType<typeof createSqlConnection>> | undefined
121
+
122
+ try {
123
+ if (!question) {
124
+ throw new Error("Informe uma pergunta para o SQL Agent.")
125
+ }
126
+
127
+ connection = await createSqlConnection()
128
+
129
+ const allTableNames = await fetchAvailableTableNames(connection)
130
+
131
+ if (allTableNames.length === 0) {
132
+ throw new Error("Nenhuma tabela foi encontrada no schema configurado.")
133
+ }
134
+
135
+ while (true) {
136
+ const composedQuestion = additionalGuidance
137
+ ? `${question}\nOrientacoes adicionais do usuario:\n${additionalGuidance}`
138
+ : question
139
+
140
+ const hints = parseSqlQuestionHints(composedQuestion)
141
+ const tableSelection = selectRelevantTables(composedQuestion, allTableNames, 12, hints.tableNames)
142
+ const selectedTableNames = tableSelection.requiresConfirmation
143
+ ? [await askForTableSelection(tableSelection.candidateTables)]
144
+ : tableSelection.suggestedTables
145
+
146
+ const schemaSummary = await fetchSchemaSummary(connection, selectedTableNames)
147
+ const plan = await generateSqlPlan(question, schemaSummary, hints, additionalGuidance)
148
+
149
+ console.log("")
150
+ console.log(formatSqlPlan(plan))
151
+
152
+ const action = await askForPlanAction()
153
+
154
+ if (action === "3") {
155
+ console.log("Execucao cancelada.")
156
+ return
157
+ }
158
+
159
+ if (action === "2") {
160
+ const correction = await askForPlanCorrection()
161
+
162
+ if (!correction) {
163
+ console.log("Nenhuma correcao informada. Mantendo plano atual.")
164
+ continue
165
+ }
166
+
167
+ additionalGuidance = additionalGuidance
168
+ ? `${additionalGuidance}\n${correction}`
169
+ : correction
170
+
171
+ continue
172
+ }
173
+
174
+ const generatedSql = await generateSqlFromQuestion(
175
+ question,
176
+ schemaSummary,
177
+ hints,
178
+ plan,
179
+ additionalGuidance
180
+ )
181
+ const sql = validateReadOnlySql(generatedSql)
182
+
183
+ const [rows] = await connection.query(sql)
184
+
185
+ console.log("")
186
+ console.log("Pergunta:")
187
+ console.log(question)
188
+ console.log("")
189
+ console.log("SQL gerada:")
190
+ console.log(sql)
191
+ console.log("")
192
+ console.log("Resultado:")
193
+ console.log(JSON.stringify(rows, null, 2))
194
+ return
195
+ }
196
+ } catch (error) {
197
+ console.error(getSqlAgentErrorMessage(error))
198
+ process.exitCode = 1
199
+ } finally {
200
+ if (connection) {
201
+ await connection.end()
202
+ }
203
+ }
204
+ }
@@ -0,0 +1,40 @@
1
+ import readline from "readline"
2
+ import { createSqlConnection } from "./createSqlConnection.js"
3
+ import { validateReadOnlySql } from "./sqlGuard.js"
4
+
5
+ function askQuestion(rl: readline.Interface, question: string): Promise<string> {
6
+ return new Promise((resolve) => rl.question(question, resolve))
7
+ }
8
+
9
+ function normalizeQuery(rawQuery?: string): string {
10
+ return (rawQuery ?? "").trim()
11
+ }
12
+
13
+ export async function runSqlCli(initialQuery?: string) {
14
+ const query = normalizeQuery(initialQuery)
15
+ let finalQuery = query
16
+
17
+ if (!finalQuery) {
18
+ const rl = readline.createInterface({
19
+ input: process.stdin,
20
+ output: process.stdout
21
+ })
22
+
23
+ try {
24
+ finalQuery = normalizeQuery(await askQuestion(rl, "SQL (somente SELECT): "))
25
+ } finally {
26
+ rl.close()
27
+ }
28
+ }
29
+
30
+ validateReadOnlySql(finalQuery)
31
+
32
+ const connection = await createSqlConnection()
33
+
34
+ try {
35
+ const [rows] = await connection.query(finalQuery)
36
+ console.log(JSON.stringify(rows, null, 2))
37
+ } finally {
38
+ await connection.end()
39
+ }
40
+ }