pokt-cli 1.0.8 → 1.0.9

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.
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Fallback: alguns modelos emitem invocações MCP em blocos ```bash``` em vez de tool_calls.
3
+ * Extraímos linhas `mcp_* ...` e executamos contra os servidores MCP conectados.
4
+ */
5
+ import { ui } from '../ui.js';
6
+ import { callMcpTool, getMcpExposedToolNames, getMcpToolParameterKeys } from '../mcp/client.js';
7
+ const BASH_BLOCK_RE = /```(?:bash|sh|shell|zsh|powershell|ps1|cmd)\s*\n([\s\S]*?)```/gi;
8
+ function mcpBashFallbackVerbose() {
9
+ return process.env.POKT_VERBOSE_MCP === '1' || process.env.POKT_VERBOSE === '1';
10
+ }
11
+ /**
12
+ * Remove blocos ```bash``` que são só invocações MCP (às vezes `mcp_*` numa linha e JSON na seguinte).
13
+ */
14
+ export function stripExecutedStyleMcpBashBlocks(content) {
15
+ const re = new RegExp(BASH_BLOCK_RE.source, BASH_BLOCK_RE.flags);
16
+ return content.replace(re, (full, body) => {
17
+ if (!/mcp_[A-Za-z0-9_-]+/i.test(body))
18
+ return full;
19
+ const lower = body.toLowerCase();
20
+ const shellish = /\b(if |then|\bfi\b|\bfor\b|\bdo\b|\bdone\b|\bwhile\b|curl |wget |\bgit\b|\bnpm\b|\bpnpm\b|\bcd |\bexport |\$\{)/.test(lower);
21
+ if (shellish)
22
+ return full;
23
+ const lines = body
24
+ .split('\n')
25
+ .map((l) => l.trim())
26
+ .filter(Boolean);
27
+ if (lines.length === 0)
28
+ return full;
29
+ const mcpOrJsonLine = (l) => {
30
+ if (parseMcpShellLine(l) !== null)
31
+ return true;
32
+ const t = l.trim();
33
+ if (t.startsWith('{') && t.endsWith('}'))
34
+ return true;
35
+ if (t.startsWith('[') && t.endsWith(']'))
36
+ return true;
37
+ return false;
38
+ };
39
+ if (lines.every(mcpOrJsonLine))
40
+ return '\n';
41
+ return full;
42
+ });
43
+ }
44
+ function trunc(s, max) {
45
+ if (s.length <= max)
46
+ return s;
47
+ return `${s.slice(0, max)}\n\n… (${s.length - max} caracteres omitidos)`;
48
+ }
49
+ /** Uma linha tipo: mcp_Neon_run_sql "SELECT 1" ou mcp_Neon_list_projects */
50
+ export function parseMcpShellLine(line) {
51
+ const m = line.trim().match(/^(mcp_[A-Za-z0-9_-]+)(?:\s+(.*))?$/);
52
+ if (!m)
53
+ return null;
54
+ return { tool: m[1], args: m[2] ?? '' };
55
+ }
56
+ function extractStringArg(raw) {
57
+ const t = raw.trim();
58
+ if (!t)
59
+ return null;
60
+ if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) {
61
+ return t.slice(1, -1).replace(/\\"/g, '"');
62
+ }
63
+ if (t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
64
+ return t.slice(1, -1).replace(/\\'/g, "'");
65
+ }
66
+ if (/^\s*(SELECT|WITH|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|TRUNCATE|SHOW)\b/i.test(t)) {
67
+ return t;
68
+ }
69
+ if (/^[a-zA-Z0-9_.-]+$/.test(t))
70
+ return t;
71
+ return t;
72
+ }
73
+ /** neonctl / modelo: --project-id "x" --branch-id "y" --sql "SELECT 1" */
74
+ function neonFlagNameToJsonKey(flagRaw) {
75
+ const f = flagRaw.toLowerCase();
76
+ if (f === 'project-id')
77
+ return 'projectId';
78
+ if (f === 'branch-id')
79
+ return 'branchId';
80
+ if (f === 'database-name' || f === 'database')
81
+ return 'databaseName';
82
+ return flagRaw.replace(/-([a-z])/gi, (_, c) => c.toUpperCase());
83
+ }
84
+ function readQuotedSegment(s, pos) {
85
+ const q = s[pos];
86
+ if (q !== '"' && q !== "'")
87
+ return null;
88
+ let j = pos + 1;
89
+ let buf = '';
90
+ while (j < s.length) {
91
+ if (s[j] === '\\' && j + 1 < s.length) {
92
+ buf += s[j + 1];
93
+ j += 2;
94
+ continue;
95
+ }
96
+ if (s[j] === q)
97
+ return { value: buf, next: j + 1 };
98
+ buf += s[j];
99
+ j++;
100
+ }
101
+ return { value: buf, next: j };
102
+ }
103
+ /**
104
+ * Extrai flags estilo CLI a partir de uma string (linha bash ou valor errado em `sql`).
105
+ */
106
+ function parseNeonCtlStyleFlags(argsRaw) {
107
+ const s = argsRaw.trim();
108
+ if (!s || !/--[a-zA-Z]/.test(s))
109
+ return null;
110
+ const out = {};
111
+ let i = 0;
112
+ while (i < s.length) {
113
+ while (i < s.length && /\s/.test(s[i]))
114
+ i++;
115
+ if (i >= s.length)
116
+ break;
117
+ if (!s.startsWith('--', i))
118
+ break;
119
+ i += 2;
120
+ const nameStart = i;
121
+ while (i < s.length && /[a-zA-Z0-9-]/.test(s[i]))
122
+ i++;
123
+ const flagRaw = s.slice(nameStart, i);
124
+ if (!flagRaw)
125
+ break;
126
+ while (i < s.length && /\s/.test(s[i]))
127
+ i++;
128
+ let value = '';
129
+ if (i < s.length && s[i] === '=') {
130
+ i++;
131
+ while (i < s.length && /\s/.test(s[i]))
132
+ i++;
133
+ }
134
+ if (i >= s.length) {
135
+ out[neonFlagNameToJsonKey(flagRaw)] = value;
136
+ break;
137
+ }
138
+ const quoted = readQuotedSegment(s, i);
139
+ if (quoted) {
140
+ value = quoted.value;
141
+ i = quoted.next;
142
+ }
143
+ else {
144
+ const us = i;
145
+ while (i < s.length) {
146
+ if (/\s/.test(s[i]))
147
+ break;
148
+ if (s.startsWith('--', i))
149
+ break;
150
+ i++;
151
+ }
152
+ value = s.slice(us, i);
153
+ }
154
+ out[neonFlagNameToJsonKey(flagRaw)] = value;
155
+ }
156
+ return Object.keys(out).length > 0 ? out : null;
157
+ }
158
+ function alignNeonRunSqlArgsToSchema(parsed, schemaKeys) {
159
+ const keys = new Set(schemaKeys);
160
+ const out = {};
161
+ const trySet = (schemaKey, val) => {
162
+ if (!val || val.trim() === '')
163
+ return;
164
+ if (keys.has(schemaKey))
165
+ out[schemaKey] = val;
166
+ };
167
+ trySet('projectId', parsed.projectId);
168
+ trySet('project_id', parsed.projectId);
169
+ trySet('branchId', parsed.branchId);
170
+ trySet('branch_id', parsed.branchId);
171
+ trySet('sql', parsed.sql);
172
+ trySet('query', parsed.query);
173
+ trySet('databaseName', parsed.databaseName);
174
+ trySet('database', parsed.databaseName);
175
+ return out;
176
+ }
177
+ function fixRunSqlJsonObject(resolvedTool, jo) {
178
+ if (!/_run_sql$/i.test(resolvedTool))
179
+ return null;
180
+ const sqlField = jo.sql;
181
+ if (typeof sqlField !== 'string' || !/--(project-id|branch-id)\b/.test(sqlField))
182
+ return null;
183
+ const merged = parseNeonCtlStyleFlags(sqlField.trim());
184
+ if (!merged || !merged.sql?.trim())
185
+ return null;
186
+ const schemaKeys = getMcpToolParameterKeys(resolvedTool);
187
+ const keySet = new Set(schemaKeys);
188
+ let aligned = alignNeonRunSqlArgsToSchema(merged, schemaKeys);
189
+ for (const [k, v] of Object.entries(jo)) {
190
+ if (k === 'sql')
191
+ continue;
192
+ if (typeof v === 'string' && v.trim() && keySet.has(k)) {
193
+ aligned = { ...aligned, [k]: v };
194
+ }
195
+ }
196
+ return Object.keys(aligned).length > 0 ? aligned : null;
197
+ }
198
+ function buildJsonArgsString(resolvedTool, argsRaw) {
199
+ const t = argsRaw.trim();
200
+ if (!t)
201
+ return '{}';
202
+ try {
203
+ const j = JSON.parse(t);
204
+ if (j !== null && typeof j === 'object' && !Array.isArray(j)) {
205
+ const jo = j;
206
+ const fixedObj = fixRunSqlJsonObject(resolvedTool, jo);
207
+ if (fixedObj)
208
+ return JSON.stringify(fixedObj);
209
+ return JSON.stringify(j);
210
+ }
211
+ }
212
+ catch {
213
+ // continua
214
+ }
215
+ if (/_run_sql$/i.test(resolvedTool)) {
216
+ const merged = parseNeonCtlStyleFlags(t);
217
+ if (merged) {
218
+ const keys = getMcpToolParameterKeys(resolvedTool);
219
+ const aligned = alignNeonRunSqlArgsToSchema(merged, keys);
220
+ if (Object.keys(aligned).length > 0)
221
+ return JSON.stringify(aligned);
222
+ }
223
+ }
224
+ const keys = getMcpToolParameterKeys(resolvedTool);
225
+ const unq = extractStringArg(t);
226
+ if (unq !== null) {
227
+ if (keys.includes('sql'))
228
+ return JSON.stringify({ sql: unq });
229
+ if (keys.includes('query'))
230
+ return JSON.stringify({ query: unq });
231
+ if (keys.includes('project_id'))
232
+ return JSON.stringify({ project_id: unq });
233
+ if (keys.includes('projectId'))
234
+ return JSON.stringify({ projectId: unq });
235
+ if (keys.length === 1)
236
+ return JSON.stringify({ [keys[0]]: unq });
237
+ }
238
+ return '{}';
239
+ }
240
+ function resolveToolName(requested, argsRaw, registered) {
241
+ if (registered.has(requested))
242
+ return requested;
243
+ const underscored = requested.replace(/-/g, '_');
244
+ if (registered.has(underscored))
245
+ return underscored;
246
+ const rl = requested.toLowerCase();
247
+ for (const n of registered) {
248
+ if (n.toLowerCase() === rl)
249
+ return n;
250
+ if (n.replace(/-/g, '_').toLowerCase() === underscored.toLowerCase())
251
+ return n;
252
+ }
253
+ const arg = extractStringArg(argsRaw);
254
+ const looksSql = arg !== null && /^\s*(SELECT|WITH|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|TRUNCATE|SHOW)\b/i.test(arg);
255
+ if (looksSql || /neon.*postgres.*execute|postgres.*execute/i.test(requested)) {
256
+ const runSqls = [...registered].filter((n) => /_run_sql$/i.test(n));
257
+ if (runSqls.length === 1)
258
+ return runSqls[0];
259
+ if (runSqls.length > 1) {
260
+ const neon = runSqls.find((n) => /neon/i.test(n));
261
+ return neon ?? runSqls[0];
262
+ }
263
+ }
264
+ return null;
265
+ }
266
+ function collectInvocationsFromMarkdown(content) {
267
+ if (getMcpExposedToolNames().length === 0)
268
+ return [];
269
+ const out = [];
270
+ const seen = new Set();
271
+ let m;
272
+ const re = new RegExp(BASH_BLOCK_RE.source, BASH_BLOCK_RE.flags);
273
+ while ((m = re.exec(content)) !== null) {
274
+ const body = m[1] ?? '';
275
+ for (const line of body.split('\n')) {
276
+ const parsed = parseMcpShellLine(line);
277
+ if (!parsed)
278
+ continue;
279
+ const key = `${parsed.tool}|${parsed.args}`;
280
+ if (seen.has(key))
281
+ continue;
282
+ seen.add(key);
283
+ out.push(parsed);
284
+ }
285
+ }
286
+ return out;
287
+ }
288
+ export async function runMcpFromBashMarkdown(content, options) {
289
+ const invocations = collectInvocationsFromMarkdown(content);
290
+ if (invocations.length === 0) {
291
+ return { invocationCount: 0, executedCount: 0, augmentedAssistantText: content };
292
+ }
293
+ const registered = new Set(getMcpExposedToolNames());
294
+ const sections = [];
295
+ let executedCount = 0;
296
+ const verbose = mcpBashFallbackVerbose();
297
+ for (const inv of invocations) {
298
+ const resolved = resolveToolName(inv.tool, inv.args, registered);
299
+ if (!resolved) {
300
+ sections.push(`\n### ${inv.tool}\n_Ferramenta não encontrada nos MCPs conectados._`);
301
+ continue;
302
+ }
303
+ const jsonArgs = buildJsonArgsString(resolved, inv.args);
304
+ if (verbose) {
305
+ console.log(ui.warn(`\n[MCP · texto/bash] ${resolved}`));
306
+ console.log(ui.dim(jsonArgs.length > 220 ? `${jsonArgs.slice(0, 220)}…` : jsonArgs));
307
+ }
308
+ else {
309
+ console.log(ui.dim(`[MCP] ${resolved} (fallback bash)`));
310
+ }
311
+ const result = await callMcpTool(resolved, jsonArgs);
312
+ executedCount++;
313
+ sections.push(`\n### ${resolved}\n\`\`\`text\n${trunc(result, 12000)}\n\`\`\``);
314
+ }
315
+ const appendixBody = sections.join('\n');
316
+ const alreadyHasMcpSection = options?.skipDuplicateAppendix === true &&
317
+ /\n##\s+Resultados\s+MCP\b/i.test(content);
318
+ const appendix = alreadyHasMcpSection
319
+ ? ''
320
+ : '\n\n---\n## Resultados MCP (fallback bash — Pokt CLI)\n' + appendixBody;
321
+ return {
322
+ invocationCount: invocations.length,
323
+ executedCount,
324
+ augmentedAssistantText: content + appendix,
325
+ };
326
+ }
327
+ function lastUserPlainText(messages) {
328
+ for (let i = messages.length - 1; i >= 0; i--) {
329
+ const m = messages[i];
330
+ if (m.role !== 'user')
331
+ continue;
332
+ const c = m.content;
333
+ if (typeof c === 'string')
334
+ return c;
335
+ if (Array.isArray(c)) {
336
+ return c
337
+ .map((part) => typeof part === 'object' && part != null && 'text' in part ? part.text : String(part))
338
+ .join('');
339
+ }
340
+ }
341
+ return '';
342
+ }
343
+ /**
344
+ * Quando o modelo devolve vazio e o pedido parece “listar bancos”, executa mcp_*_run_sql uma vez.
345
+ */
346
+ export async function tryAutoMcpForListDatabases(messages) {
347
+ const text = lastUserPlainText(messages);
348
+ if (!/\b(bancos?\s+de\s+dados|banco\s+de\s+dados|listar.*bancos?|quais\s+bancos|databases?\b|postgresql|postgres|neon|\bmcp\b)\b/i.test(text)) {
349
+ return null;
350
+ }
351
+ const runSqls = getMcpExposedToolNames().filter((n) => /_run_sql$/i.test(n));
352
+ if (runSqls.length === 0)
353
+ return null;
354
+ const tool = runSqls.find((n) => /neon/i.test(n)) ?? runSqls[0];
355
+ const sql = 'SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY 1;';
356
+ if (mcpBashFallbackVerbose()) {
357
+ console.log(ui.warn(`\n[MCP · automático] ${tool} — listando bancos (modelo sem resposta útil)`));
358
+ }
359
+ else {
360
+ console.log(ui.dim(`[MCP] ${tool} (automático — listar bancos)`));
361
+ }
362
+ const out = await callMcpTool(tool, JSON.stringify({ sql }));
363
+ return `## Bancos de dados (Postgres)\n\n**Ferramenta:** \`${tool}\`\n\n\`\`\`text\n${trunc(out, 12000)}\n\`\`\`\n\n_A automação do Pokt executou esta consulta porque o pedido parecia listar bancos e o modelo não retornou conteúdo._`;
364
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Reduz o tamanho do array `tools` enviado à API (ex.: Pokt Controller com body limit baixo).
3
+ * Schemas MCP (Neon etc.) podem ter dezenas de KB por tool.
4
+ */
5
+ import type { ChatCompletionTool } from 'openai/resources/chat/completions/completions.js';
6
+ export declare function slimToolsForUpstreamPayload(tools: ChatCompletionTool[]): ChatCompletionTool[];
@@ -0,0 +1,65 @@
1
+ const MAX_FUNCTION_DESCRIPTION_CHARS = 520;
2
+ const MAX_SCHEMA_STRING_CHARS = 480;
3
+ const MAX_TOOL_PARAMETERS_JSON_CHARS = 22_000;
4
+ function deepTruncateStrings(obj, maxLen, depth = 0) {
5
+ if (depth > 14)
6
+ return obj;
7
+ if (typeof obj === 'string') {
8
+ if (obj.length <= maxLen)
9
+ return obj;
10
+ return `${obj.slice(0, Math.max(0, maxLen - 1))}…`;
11
+ }
12
+ if (Array.isArray(obj)) {
13
+ const cap = 80;
14
+ const arr = obj.length > cap ? obj.slice(0, cap) : obj;
15
+ return arr.map((x) => deepTruncateStrings(x, maxLen, depth + 1));
16
+ }
17
+ if (obj !== null && typeof obj === 'object') {
18
+ const o = obj;
19
+ const out = {};
20
+ for (const k of Object.keys(o)) {
21
+ out[k] = deepTruncateStrings(o[k], maxLen, depth + 1);
22
+ }
23
+ return out;
24
+ }
25
+ return obj;
26
+ }
27
+ /** Último recurso: schema aberto (o modelo já vê nomes exatos no system prompt). */
28
+ function fallbackOpenEndedParameters() {
29
+ return {
30
+ type: 'object',
31
+ additionalProperties: true,
32
+ description: 'JSON com os argumentos da tool MCP (ex.: projectId, branchId, sql).',
33
+ };
34
+ }
35
+ export function slimToolsForUpstreamPayload(tools) {
36
+ return tools.map((t) => {
37
+ if (t.type !== 'function' || !t.function)
38
+ return t;
39
+ const fn = t.function;
40
+ const rawDesc = fn.description ?? '';
41
+ const description = rawDesc.length <= MAX_FUNCTION_DESCRIPTION_CHARS
42
+ ? rawDesc
43
+ : `${rawDesc.slice(0, MAX_FUNCTION_DESCRIPTION_CHARS - 1)}…`;
44
+ let parameters = fn.parameters;
45
+ if (parameters && typeof parameters === 'object') {
46
+ parameters = deepTruncateStrings(parameters, MAX_SCHEMA_STRING_CHARS);
47
+ }
48
+ let jsonLen = JSON.stringify(parameters ?? {}).length;
49
+ if (jsonLen > MAX_TOOL_PARAMETERS_JSON_CHARS) {
50
+ parameters = deepTruncateStrings(parameters, 220);
51
+ jsonLen = JSON.stringify(parameters ?? {}).length;
52
+ }
53
+ if (jsonLen > MAX_TOOL_PARAMETERS_JSON_CHARS) {
54
+ parameters = fallbackOpenEndedParameters();
55
+ }
56
+ return {
57
+ type: 'function',
58
+ function: {
59
+ name: fn.name,
60
+ description,
61
+ parameters,
62
+ },
63
+ };
64
+ });
65
+ }
@@ -7,6 +7,29 @@ import * as diff from 'diff';
7
7
  const execAsync = promisify(exec);
8
8
  /** Máximo de linhas de diff exibidas; além disso mostra resumo para não imprimir arquivo inteiro */
9
9
  const MAX_DIFF_LINES = 45;
10
+ function isGeneratedNoisePath(relPath) {
11
+ const base = path.basename(relPath);
12
+ return /^generated\.(text|json)$/i.test(base);
13
+ }
14
+ function looksLikeMcpJsonDump(s) {
15
+ const t = s.trim();
16
+ if (!t)
17
+ return false;
18
+ try {
19
+ const j = JSON.parse(t);
20
+ if (j !== null && typeof j === 'object' && !Array.isArray(j)) {
21
+ const o = j;
22
+ return 'success' in o && 'result' in o;
23
+ }
24
+ if (Array.isArray(j) && j.length > 0 && j.every((x) => x && typeof x === 'object')) {
25
+ return true;
26
+ }
27
+ }
28
+ catch {
29
+ /* ignore */
30
+ }
31
+ return false;
32
+ }
10
33
  function showDiff(filePath, oldContent, newContent) {
11
34
  const relativePath = path.relative(process.cwd(), filePath);
12
35
  console.log(chalk.blue.bold(`\n📝 Edit ${relativePath}:`));
@@ -170,7 +193,13 @@ export async function executeTool(name, argsStr) {
170
193
  const oldContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
171
194
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
172
195
  fs.writeFileSync(filePath, args.content, 'utf8');
173
- showDiff(filePath, oldContent, args.content);
196
+ const rel = path.relative(process.cwd(), filePath);
197
+ if (isGeneratedNoisePath(rel) && looksLikeMcpJsonDump(args.content)) {
198
+ console.log(chalk.dim(`\n📝 ${rel} (omitido diff — parece resposta MCP/JSON; use tabela no texto do assistente)\n`));
199
+ }
200
+ else {
201
+ showDiff(filePath, oldContent, args.content);
202
+ }
174
203
  return `Successfully wrote to ${args.path}`;
175
204
  }
176
205
  if (name === 'run_command') {
@@ -1,4 +1,4 @@
1
- import { config, getEffectiveActiveModel } from '../config.js';
1
+ import { getEffectiveActiveModel, getOpenAIApiKey, getGrokApiKey, getOpenRouterToken, getGeminiApiKey, getPoktToken } from '../config.js';
2
2
  import { startChatLoop } from '../chat/loop.js';
3
3
  import { ui } from '../ui.js';
4
4
  export const chatCommand = {
@@ -11,16 +11,24 @@ export const chatCommand = {
11
11
  console.log(ui.error('No active model selected. Run: pokt models list then pokt models use -p <provider> -i <id>'));
12
12
  return;
13
13
  }
14
- if (activeModel.provider === 'openrouter' && !config.get('openrouterToken')) {
14
+ if (activeModel.provider === 'openrouter' && !getOpenRouterToken()) {
15
15
  console.log(ui.error('OpenRouter token not set. Use: pokt config set-openrouter -v <token>'));
16
16
  return;
17
17
  }
18
- if (activeModel.provider === 'gemini' && !config.get('geminiApiKey')) {
18
+ if (activeModel.provider === 'openai' && !getOpenAIApiKey()) {
19
+ console.log(ui.error('OpenAI API key not set. Use: pokt config set-openai -v <key>'));
20
+ return;
21
+ }
22
+ if (activeModel.provider === 'grok' && !getGrokApiKey()) {
23
+ console.log(ui.error('Grok (xAI) API key not set. Use: pokt config set-grok -v <key>'));
24
+ return;
25
+ }
26
+ if (activeModel.provider === 'gemini' && !getGeminiApiKey()) {
19
27
  console.log(ui.error('Gemini API key not set. Use: pokt config set-gemini -v <key>'));
20
28
  return;
21
29
  }
22
30
  if (activeModel.provider === 'controller') {
23
- if (!config.get('poktToken')) {
31
+ if (!getPoktToken()) {
24
32
  console.log(ui.error('Pokt token not set. Generate one at the panel and: pokt config set-pokt-token -v <token>'));
25
33
  return;
26
34
  }
@@ -8,7 +8,7 @@ export const configCommand = {
8
8
  .positional('action', {
9
9
  describe: 'Action to perform',
10
10
  type: 'string',
11
- choices: ['set-openrouter', 'set-ollama', 'set-ollama-cloud', 'set-gemini', 'set-pokt-token', 'clear-openrouter', 'show']
11
+ choices: ['set-openai', 'set-grok', 'set-openrouter', 'set-ollama', 'set-ollama-cloud', 'set-gemini', 'set-pokt-token', 'clear-openrouter', 'clear-openai', 'clear-grok', 'show']
12
12
  })
13
13
  .option('value', {
14
14
  describe: 'The value to set',
@@ -18,12 +18,16 @@ export const configCommand = {
18
18
  handler: (argv) => {
19
19
  const { action, value } = argv;
20
20
  if (action === 'show') {
21
+ const openai = config.get('openaiApiKey');
22
+ const grok = config.get('grokApiKey');
21
23
  const openrouter = config.get('openrouterToken');
22
24
  const gemini = config.get('geminiApiKey');
23
25
  const ollama = config.get('ollamaBaseUrl');
24
26
  const ollamaCloud = config.get('ollamaCloudApiKey');
25
27
  const poktToken = config.get('poktToken');
26
28
  console.log(chalk.blue('\nCurrent config (tokens masked):'));
29
+ console.log(ui.dim(' OpenAI API Key:'), openai ? openai.slice(0, 8) + '****' : '(not set)');
30
+ console.log(ui.dim(' Grok (xAI) API Key:'), grok ? grok.slice(0, 8) + '****' : '(not set)');
27
31
  console.log(ui.dim(' OpenRouter Token:'), openrouter ? openrouter.slice(0, 8) + '****' : '(not set)');
28
32
  console.log(ui.dim(' Gemini API Key:'), gemini ? gemini.slice(0, 8) + '****' : '(not set)');
29
33
  console.log(ui.dim(' Ollama Base URL (local):'), ollama || '(not set)');
@@ -38,7 +42,17 @@ export const configCommand = {
38
42
  console.log(ui.success('OpenRouter token cleared.'));
39
43
  return;
40
44
  }
41
- if (action !== 'set-openrouter' && action !== 'set-ollama' && action !== 'set-ollama-cloud' && action !== 'set-gemini' && action !== 'set-pokt-token')
45
+ if (action === 'clear-openai') {
46
+ config.set('openaiApiKey', '');
47
+ console.log(ui.success('OpenAI API key cleared.'));
48
+ return;
49
+ }
50
+ if (action === 'clear-grok') {
51
+ config.set('grokApiKey', '');
52
+ console.log(ui.success('Grok (xAI) API key cleared.'));
53
+ return;
54
+ }
55
+ if (action !== 'set-openai' && action !== 'set-grok' && action !== 'set-openrouter' && action !== 'set-ollama' && action !== 'set-ollama-cloud' && action !== 'set-gemini' && action !== 'set-pokt-token')
42
56
  return;
43
57
  const raw = Array.isArray(value) ? value[0] : value;
44
58
  const strValue = typeof raw === 'string' ? raw : (raw != null ? String(raw) : '');
@@ -46,7 +60,15 @@ export const configCommand = {
46
60
  console.log(ui.error('Error: --value is required. Use: pokt config ' + action + ' -v <value>'));
47
61
  return;
48
62
  }
49
- if (action === 'set-openrouter') {
63
+ if (action === 'set-openai') {
64
+ config.set('openaiApiKey', strValue);
65
+ console.log(ui.success('OpenAI API key saved successfully.'));
66
+ }
67
+ else if (action === 'set-grok') {
68
+ config.set('grokApiKey', strValue);
69
+ console.log(ui.success('Grok (xAI) API key saved successfully.'));
70
+ }
71
+ else if (action === 'set-openrouter') {
50
72
  config.set('openrouterToken', strValue);
51
73
  console.log(ui.success('OpenRouter token saved successfully.'));
52
74
  }
@@ -0,0 +1,2 @@
1
+ import type * as Yargs from 'yargs';
2
+ export declare const doctorCommand: Yargs.CommandModule;
@@ -0,0 +1,82 @@
1
+ import ora from 'ora';
2
+ import { ui } from '../ui.js';
3
+ import { PROVIDER_LABELS, getEffectiveActiveModel, getOpenAIApiKey, getGrokApiKey, getOpenRouterToken, getGeminiApiKey, getOllamaBaseUrl, getOllamaCloudApiKey, getPoktToken, } from '../config.js';
4
+ import { getClient } from '../chat/client.js';
5
+ function mask(value) {
6
+ if (!value)
7
+ return '(not set)';
8
+ if (value.length <= 8)
9
+ return '****';
10
+ return value.slice(0, 4) + '****' + value.slice(-2);
11
+ }
12
+ async function checkModelsEndpoint(client) {
13
+ // OpenAI SDK chama GET /models; funciona na maioria dos OpenAI-compat.
14
+ const res = await client.models.list();
15
+ const data = Array.isArray(res?.data) ? res.data : [];
16
+ return data.length;
17
+ }
18
+ async function checkOllamaTags() {
19
+ const baseUrl = getOllamaBaseUrl().replace(/\/$/, '');
20
+ const url = `${baseUrl}/api/tags`;
21
+ const r = await fetch(url);
22
+ if (!r.ok)
23
+ throw new Error(`HTTP ${r.status}`);
24
+ const data = await r.json();
25
+ return Array.isArray(data?.models) ? data.models.length : 0;
26
+ }
27
+ export const doctorCommand = {
28
+ command: 'doctor',
29
+ describe: 'Diagnosticar credenciais e conectividade do provider/model atual',
30
+ handler: async () => {
31
+ const active = getEffectiveActiveModel();
32
+ if (!active) {
33
+ console.log(ui.error('Nenhum modelo ativo. Rode: pokt models list'));
34
+ return;
35
+ }
36
+ const label = PROVIDER_LABELS[active.provider] ?? active.provider;
37
+ console.log(ui.dim(`\nProvider ativo: ${label}`));
38
+ console.log(ui.dim(`Model ativo: ${active.id}\n`));
39
+ // 1) checar credenciais (env ou conf)
40
+ const required = {
41
+ controller: { name: 'POKT_TOKEN', value: getPoktToken(), hint: 'pokt config set-pokt-token -v <token>' },
42
+ openai: { name: 'OPENAI_API_KEY', value: getOpenAIApiKey(), hint: 'pokt config set-openai -v <key>' },
43
+ grok: { name: 'XAI_API_KEY', value: getGrokApiKey(), hint: 'pokt config set-grok -v <key>' },
44
+ openrouter: { name: 'OPENROUTER_API_KEY', value: getOpenRouterToken(), hint: 'pokt config set-openrouter -v <token>' },
45
+ gemini: { name: 'GEMINI_API_KEY', value: getGeminiApiKey(), hint: 'pokt config set-gemini -v <key>' },
46
+ 'ollama-cloud': { name: 'OLLAMA_CLOUD_API_KEY', value: getOllamaCloudApiKey(), hint: 'pokt config set-ollama-cloud -v <key>' },
47
+ ollama: null,
48
+ };
49
+ const req = required[active.provider] ?? null;
50
+ if (req) {
51
+ if (!req.value) {
52
+ console.log(ui.error(`Faltando credencial: ${req.name}`));
53
+ if (req.hint)
54
+ console.log(ui.dim(`Dica: ${req.hint}`));
55
+ return;
56
+ }
57
+ console.log(ui.success(`Credencial OK: ${req.name} = ${mask(req.value)}`));
58
+ }
59
+ else if (active.provider === 'ollama') {
60
+ console.log(ui.success(`Ollama (local) não precisa de chave. Base URL: ${getOllamaBaseUrl()}`));
61
+ }
62
+ // 2) checar conectividade
63
+ const spinner = ora('Testando conexão...').start();
64
+ try {
65
+ if (active.provider === 'ollama') {
66
+ const count = await checkOllamaTags();
67
+ spinner.succeed(ui.success(`Conexão OK (Ollama). Modelos encontrados: ${count}`));
68
+ return;
69
+ }
70
+ const client = await getClient(active);
71
+ const count = await checkModelsEndpoint(client);
72
+ spinner.succeed(ui.success(`Conexão OK (${label}). /models retornou ${count} modelos.`));
73
+ }
74
+ catch (e) {
75
+ spinner.fail(ui.error(`Falha ao testar conexão: ${e?.message ?? String(e)}`));
76
+ console.log(ui.warn('Sugestões:'));
77
+ console.log(ui.dim('- Verifique sua chave/token'));
78
+ console.log(ui.dim('- Verifique sua rede/proxy/firewall'));
79
+ console.log(ui.dim('- Rode: pokt config show'));
80
+ }
81
+ }
82
+ };
@@ -6,6 +6,8 @@ interface McpArgs {
6
6
  command?: string;
7
7
  args?: string;
8
8
  url?: string;
9
+ oauth?: boolean;
10
+ transport?: string;
9
11
  }
10
12
  export declare const mcpCommand: Yargs.CommandModule<{}, McpArgs>;
11
13
  export {};