pokt-cli 1.0.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.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Pokt CLI
2
+
3
+ CLI de **Vibe Coding** com IA: OpenRouter, Ollama (local e cloud), Gemini e provedor Pokt (controller).
4
+
5
+ ## Requisitos
6
+
7
+ - **Node.js** >= 18.0.0
8
+
9
+ ## Instalação
10
+
11
+ ```bash
12
+ # Instalação global
13
+ npm install -g pokt-cli
14
+
15
+ # Ou use sem instalar
16
+ npx pokt-cli
17
+ ```
18
+
19
+ ## Uso
20
+
21
+ Sem argumentos, o Pokt abre um menu interativo:
22
+
23
+ ```bash
24
+ pokt
25
+ ```
26
+
27
+ Ou use os comandos diretamente:
28
+
29
+ ```bash
30
+ pokt chat # Iniciar chat (Vibe Coding)
31
+ pokt models list # Listar modelos
32
+ pokt provider use openrouter
33
+ pokt config show
34
+ pokt --help
35
+ ```
36
+
37
+ ## Comandos
38
+
39
+ | Comando | Descrição |
40
+ |--------|-----------|
41
+ | `pokt` | Menu interativo |
42
+ | `pokt chat` | Iniciar sessão de chat com a IA |
43
+ | `pokt config <action>` | Configurar chaves e tokens |
44
+ | `pokt models <action>` | Gerenciar modelos (listar, adicionar, trocar) |
45
+ | `pokt provider use <provider>` | Trocar provedor de API |
46
+ | `pokt mcp [action]` | Gerenciar servidores MCP (ferramentas externas) |
47
+
48
+ ### Config (`config`)
49
+
50
+ - `pokt config show` — Mostra a configuração atual (tokens mascarados).
51
+ - `pokt config set-openrouter -v <token>` — Token OpenRouter.
52
+ - `pokt config set-ollama -v <url>` — URL base do Ollama local.
53
+ - `pokt config set-ollama-cloud -v <key>` — API key Ollama Cloud.
54
+ - `pokt config set-gemini -v <key>` — API key Google Gemini.
55
+ - `pokt config set-pokt-token -v <token>` — Token do controller Pokt.
56
+ - `pokt config clear-openrouter` — Remove o token OpenRouter.
57
+
58
+ ### Modelos (`models`)
59
+
60
+ - `pokt models list` — Lista modelos registrados e o ativo.
61
+ - `pokt models fetch-openrouter` — Busca modelos disponíveis no OpenRouter.
62
+ - `pokt models fetch-ollama` — Busca modelos do Ollama local.
63
+ - `pokt models fetch-ollama-cloud` — Busca modelos do Ollama Cloud.
64
+ - `pokt models add-openrouter`, `add-ollama`, `add-ollama-cloud` — Adiciona modelo (use `-i <id>`).
65
+ - `pokt models use -i <id> -p <provider>` — Define o modelo ativo.
66
+
67
+ ### Provedores (`provider`)
68
+
69
+ Provedores suportados: `controller` (Pokt), `openrouter`, `gemini`, `ollama`, `ollama-cloud`.
70
+
71
+ ```bash
72
+ pokt provider use openrouter
73
+ pokt provider use ollama
74
+ ```
75
+
76
+ ## Desenvolvimento
77
+
78
+ ```bash
79
+ # Clonar e instalar
80
+ git clone https://github.com/PoktWeb/Pokt_CLI.git
81
+ cd Pokt_CLI
82
+ npm install
83
+
84
+ # Desenvolvimento (executa o TypeScript direto)
85
+ npm run dev
86
+
87
+ # Build
88
+ npm run build
89
+
90
+ # Executar após o build
91
+ npm start
92
+ ```
93
+
94
+ ## Licença
95
+
96
+ ISC · **PoktWeb**
@@ -0,0 +1 @@
1
+ export declare function loginWithGoogle(): Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { OAuth2Client } from 'google-auth-library';
2
+ import http from 'http';
3
+ import url from 'url';
4
+ import open from 'open';
5
+ import { config } from '../config.js';
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ const REDIRECT_URI = 'http://localhost:3000/oauth2callback';
9
+ export async function loginWithGoogle() {
10
+ const clientId = config.get('googleClientId');
11
+ const clientSecret = config.get('googleClientSecret');
12
+ if (!clientId || !clientSecret) {
13
+ console.log(chalk.red('\nError: Google OAuth Client ID or Secret not configured.'));
14
+ console.log(chalk.yellow('To use Google Login, follow these steps:'));
15
+ console.log(chalk.gray('1. Create a project at: https://console.cloud.google.com/'));
16
+ console.log(chalk.gray('2. Enable the "Generative Language API"'));
17
+ console.log(chalk.gray('3. Create OAuth 2.0 Client ID (Type: Web application)'));
18
+ console.log(chalk.gray(`4. Add Authorized redirect URI: ${REDIRECT_URI}`));
19
+ console.log(chalk.yellow('\nThen configure Pokt CLI:'));
20
+ console.log(chalk.cyan(` pokt config set-google-client-id -v YOUR_CLIENT_ID`));
21
+ console.log(chalk.cyan(` pokt config set-google-client-secret -v YOUR_CLIENT_SECRET`));
22
+ return;
23
+ }
24
+ const spinner = ora('Starting Google Login...').start();
25
+ const oAuth2Client = new OAuth2Client(clientId, clientSecret, REDIRECT_URI);
26
+ const authorizeUrl = oAuth2Client.generateAuthUrl({
27
+ access_type: 'offline',
28
+ scope: ['https://www.googleapis.com/auth/generative-language'],
29
+ });
30
+ const server = http.createServer(async (req, res) => {
31
+ try {
32
+ if (req.url && req.url.includes('/oauth2callback') && req.url.includes('code=')) {
33
+ const qs = new url.URL(req.url, 'http://localhost:3000').searchParams;
34
+ const code = qs.get('code');
35
+ res.end('Authentication successful! You can close this tab.');
36
+ server.close();
37
+ const { tokens } = await oAuth2Client.getToken(code);
38
+ config.set('googleToken', tokens);
39
+ console.log(chalk.green('\n✔ Google account connected successfully.'));
40
+ }
41
+ }
42
+ catch (e) {
43
+ console.error(chalk.red(`\nAuthentication failed: ${e.message}`));
44
+ res.end('Authentication failed.');
45
+ }
46
+ }).listen(3000);
47
+ spinner.succeed(chalk.blue('Opening browser for Google login...'));
48
+ await open(authorizeUrl);
49
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+ import yargs from 'yargs';
3
+ import { hideBin } from 'yargs/helpers';
4
+ import { configCommand } from '../commands/config.js';
5
+ import { modelsCommand } from '../commands/models.js';
6
+ import { chatCommand } from '../commands/chat.js';
7
+ import { providerCommand } from '../commands/provider.js';
8
+ import { mcpCommand } from '../commands/mcp.js';
9
+ import prompts from 'prompts';
10
+ import chalk from 'chalk';
11
+ import { ui } from '../ui.js';
12
+ const argv = hideBin(process.argv);
13
+ if (argv.length === 0) {
14
+ showMenu();
15
+ }
16
+ else {
17
+ yargs(argv)
18
+ .scriptName('pokt')
19
+ .usage('$0 <cmd> [args]')
20
+ .command(configCommand)
21
+ .command(modelsCommand)
22
+ .command(chatCommand)
23
+ .command(providerCommand)
24
+ .command(mcpCommand)
25
+ .demandCommand(1, 'You need at least one command before moving on')
26
+ .help()
27
+ .parse();
28
+ }
29
+ async function showMenu() {
30
+ const { getEffectiveActiveModel, PROVIDER_LABELS } = await import('../config.js');
31
+ const active = getEffectiveActiveModel();
32
+ const providerLabel = active ? (PROVIDER_LABELS[active.provider] ?? active.provider) : 'No provider';
33
+ console.log('');
34
+ console.log(ui.banner());
35
+ console.log(ui.statusLine(providerLabel));
36
+ console.log('');
37
+ console.log(ui.separator());
38
+ console.log('');
39
+ const response = await prompts({
40
+ type: 'select',
41
+ name: 'action',
42
+ message: 'What would you like to do?',
43
+ choices: [
44
+ { title: '💬 Start Chat (Vibe Coding)', value: 'chat' },
45
+ { title: '🤖 Select AI Model', value: 'models' },
46
+ { title: '➕ Add / sync models', value: 'add-models' },
47
+ { title: '🏠 Switch API Provider (casa de API)', value: 'provider' },
48
+ { title: '🔌 MCP Servers (tools externos)', value: 'mcp' },
49
+ { title: '⚙️ Configure API Keys / Tokens', value: 'config' },
50
+ { title: '❌ Exit', value: 'exit' }
51
+ ]
52
+ });
53
+ if (!response.action || response.action === 'exit') {
54
+ process.exit(0);
55
+ }
56
+ if (response.action === 'chat') {
57
+ const { chatCommand } = await import('../commands/chat.js');
58
+ chatCommand.handler({ fromMenu: true });
59
+ }
60
+ else if (response.action === 'models') {
61
+ await handleModelsMenu();
62
+ }
63
+ else if (response.action === 'add-models') {
64
+ await handleAddModelsMenu();
65
+ }
66
+ else if (response.action === 'provider') {
67
+ await handleProviderMenu();
68
+ }
69
+ else if (response.action === 'config') {
70
+ await handleConfigMenu();
71
+ }
72
+ else if (response.action === 'mcp') {
73
+ const { mcpCommand } = await import('../commands/mcp.js');
74
+ await mcpCommand.handler({ action: 'list' });
75
+ const mcpResp = await prompts({
76
+ type: 'select',
77
+ name: 'mcpAction',
78
+ message: 'MCP:',
79
+ choices: [
80
+ { title: '🧪 Test connection', value: 'test' },
81
+ { title: '🔙 Back', value: 'back' }
82
+ ]
83
+ });
84
+ if (mcpResp.mcpAction === 'back')
85
+ return showMenu();
86
+ await mcpCommand.handler({ action: mcpResp.mcpAction });
87
+ return showMenu();
88
+ }
89
+ }
90
+ async function handleModelsMenu(providerFilter) {
91
+ const { config, getEffectiveActiveModel, PROVIDER_LABELS, ALL_PROVIDERS } = await import('../config.js');
92
+ let allModels = config.get('registeredModels');
93
+ if (!Array.isArray(allModels)) {
94
+ const defaults = [
95
+ { provider: 'openrouter', id: 'google/gemini-2.0-flash-001' },
96
+ { provider: 'ollama', id: 'llama3' }
97
+ ];
98
+ config.set('registeredModels', defaults);
99
+ allModels = defaults;
100
+ }
101
+ const active = getEffectiveActiveModel();
102
+ // Primeira tela: escolher categoria (provedor)
103
+ if (providerFilter === undefined) {
104
+ const categoryChoices = [
105
+ ...ALL_PROVIDERS.map(p => ({
106
+ title: `${active?.provider === p ? '★ ' : ''}${PROVIDER_LABELS[p] || p}`,
107
+ value: p
108
+ })),
109
+ { title: '➕ Add / sync models', value: 'go-add-models' },
110
+ { title: '🔙 Back', value: 'back' }
111
+ ];
112
+ const cat = await prompts({
113
+ type: 'select',
114
+ name: 'category',
115
+ message: 'Select category (provider):',
116
+ choices: categoryChoices
117
+ });
118
+ if (cat.category === 'back')
119
+ return showMenu();
120
+ if (cat.category === 'go-add-models')
121
+ return handleAddModelsMenu();
122
+ return handleModelsMenu(cat.category);
123
+ }
124
+ // Segunda tela: listar modelos da categoria escolhida
125
+ const providerModels = allModels.filter((m) => m.provider === providerFilter);
126
+ const label = PROVIDER_LABELS[providerFilter] || providerFilter;
127
+ const choices = [
128
+ ...providerModels.map((m, i) => ({
129
+ title: `${active?.id === m.id && active?.provider === m.provider ? '★ ' : ''}${m.id}`,
130
+ value: i
131
+ })),
132
+ ...(providerModels.length === 0
133
+ ? [{ title: '➕ Nenhum modelo — ir para Add / sync models', value: 'go-add-models' }]
134
+ : []),
135
+ { title: '🔙 Back to categories', value: 'back-categories' }
136
+ ];
137
+ const response = await prompts({
138
+ type: 'select',
139
+ name: 'modelIdx',
140
+ message: `${label} — choose a model:`,
141
+ choices
142
+ });
143
+ if (response.modelIdx === 'back-categories')
144
+ return handleModelsMenu();
145
+ if (response.modelIdx === 'go-add-models')
146
+ return handleAddModelsMenu();
147
+ if (typeof response.modelIdx === 'number') {
148
+ const selected = providerModels[response.modelIdx];
149
+ config.set('activeModel', selected);
150
+ console.log(ui.success(`Active model set to [${selected.provider}] ${selected.id}\n`));
151
+ return showMenu();
152
+ }
153
+ }
154
+ /** Menu para adicionar/sincronizar modelos: OpenRouter (API), Ollama local (GET /api/tags), Ollama Cloud (add por ID). */
155
+ async function handleAddModelsMenu() {
156
+ const response = await prompts({
157
+ type: 'select',
158
+ name: 'action',
159
+ message: 'Add or sync models from which provider?',
160
+ choices: [
161
+ { title: 'OpenRouter — sync all from API', value: 'fetch-openrouter' },
162
+ { title: 'Ollama (local) — sync from your Ollama (list models)', value: 'fetch-ollama' },
163
+ { title: 'Ollama Cloud — sync from API', value: 'fetch-ollama-cloud' },
164
+ { title: 'OpenRouter — add model by ID', value: 'add-openrouter' },
165
+ { title: 'Ollama (local) — add model by ID', value: 'add-ollama' },
166
+ { title: 'Ollama Cloud — add model by ID', value: 'add-ollama-cloud' },
167
+ { title: '🔙 Back', value: 'back' }
168
+ ]
169
+ });
170
+ if (response.action === 'back')
171
+ return showMenu();
172
+ const addActions = ['add-openrouter', 'add-ollama', 'add-ollama-cloud'];
173
+ if (addActions.includes(response.action)) {
174
+ const idPrompt = await prompts({
175
+ type: 'text',
176
+ name: 'id',
177
+ message: response.action === 'add-openrouter'
178
+ ? 'Model ID (ex: google/gemini-2.5-flash):'
179
+ : 'Model ID (ex: llama3):'
180
+ });
181
+ const id = typeof idPrompt.id === 'string' ? idPrompt.id.trim() : '';
182
+ if (id) {
183
+ const { modelsCommand } = await import('../commands/models.js');
184
+ await modelsCommand.handler({ action: response.action, id });
185
+ }
186
+ else {
187
+ console.log(ui.warn('No ID entered.'));
188
+ }
189
+ return handleAddModelsMenu();
190
+ }
191
+ const { modelsCommand } = await import('../commands/models.js');
192
+ await modelsCommand.handler({ action: response.action });
193
+ return handleAddModelsMenu();
194
+ }
195
+ async function handleConfigMenu() {
196
+ const { config } = await import('../config.js');
197
+ const response = await prompts({
198
+ type: 'select',
199
+ name: 'type',
200
+ message: 'Which setting to configure?',
201
+ choices: [
202
+ { title: 'View current config', value: 'show' },
203
+ { title: 'Pokt Token (do painel — só isso para usar Controller)', value: 'set-pokt-token' },
204
+ { title: 'OpenRouter Token', value: 'set-openrouter' },
205
+ { title: 'Gemini API Key', value: 'set-gemini' },
206
+ { title: 'Ollama Base URL (local)', value: 'set-ollama' },
207
+ { title: 'Ollama Cloud API Key', value: 'set-ollama-cloud' },
208
+ { title: '🔙 Back', value: 'back' }
209
+ ]
210
+ });
211
+ if (response.type === 'back')
212
+ return showMenu();
213
+ if (response.type === 'show') {
214
+ const { getControllerBaseUrl } = await import('../config.js');
215
+ const openrouter = config.get('openrouterToken');
216
+ const gemini = config.get('geminiApiKey');
217
+ const ollama = config.get('ollamaBaseUrl');
218
+ const ollamaCloud = config.get('ollamaCloudApiKey');
219
+ const poktToken = config.get('poktToken');
220
+ console.log(chalk.blue('\nCurrent config (tokens masked):'));
221
+ console.log(ui.dim(' Controller URL:'), getControllerBaseUrl(), ui.dim('(já configurado)'));
222
+ console.log(ui.dim(' Pokt Token:'), poktToken ? poktToken.slice(0, 10) + '****' : '(not set)');
223
+ console.log(ui.dim(' OpenRouter Token:'), openrouter ? openrouter.slice(0, 8) + '****' : '(not set)');
224
+ console.log(ui.dim(' Gemini API Key:'), gemini ? gemini.slice(0, 8) + '****' : '(not set)');
225
+ console.log(ui.dim(' Ollama Base URL (local):'), ollama || '(not set)');
226
+ console.log(ui.dim(' Ollama Cloud API Key:'), ollamaCloud ? ollamaCloud.slice(0, 8) + '****' : '(not set)');
227
+ console.log(ui.warn('\nTokens are stored in your user config directory. Do not share it.\n'));
228
+ return handleConfigMenu();
229
+ }
230
+ const msg = response.type === 'set-pokt-token'
231
+ ? 'Token gerado no painel (pk_...):'
232
+ : `Enter the value for ${response.type}:`;
233
+ const valueResponse = await prompts({
234
+ type: 'text',
235
+ name: 'val',
236
+ message: msg
237
+ });
238
+ if (valueResponse.val) {
239
+ const keyMap = {
240
+ 'set-gemini': 'geminiApiKey',
241
+ 'set-openrouter': 'openrouterToken',
242
+ 'set-ollama': 'ollamaBaseUrl',
243
+ 'set-ollama-cloud': 'ollamaCloudApiKey',
244
+ 'set-pokt-token': 'poktToken'
245
+ };
246
+ const key = keyMap[response.type];
247
+ config.set(key, key === 'ollamaBaseUrl' ? valueResponse.val.replace(/\/$/, '') : valueResponse.val);
248
+ if (key === 'poktToken') {
249
+ const controllerModel = { provider: 'controller', id: 'default' };
250
+ const models = config.get('registeredModels');
251
+ if (!models.some((m) => m.provider === 'controller' && m.id === 'default')) {
252
+ config.set('registeredModels', [controllerModel, ...models]);
253
+ }
254
+ config.set('activeModel', controllerModel);
255
+ }
256
+ console.log(ui.success('Config updated.\n'));
257
+ }
258
+ return handleConfigMenu();
259
+ }
260
+ async function handleProviderMenu() {
261
+ const { config, getEffectiveActiveModel, PROVIDER_LABELS, ALL_PROVIDERS } = await import('../config.js');
262
+ let models = config.get('registeredModels');
263
+ const hasControllerUrl = !!(config.get('controllerBaseUrl'));
264
+ const hasPoktToken = !!(config.get('poktToken'));
265
+ if ((hasControllerUrl || hasPoktToken) && !models.some((m) => m.provider === 'controller')) {
266
+ models = [{ provider: 'controller', id: 'default' }, ...models];
267
+ config.set('registeredModels', models);
268
+ }
269
+ const active = getEffectiveActiveModel();
270
+ // Mostra todos os provedores (Ollama, Gemini, OpenRouter, Controller e quaisquer outros futuros)
271
+ const choices = ALL_PROVIDERS.map(p => ({
272
+ title: `${active?.provider === p ? '★ ' : ''}${PROVIDER_LABELS[p] || p}`,
273
+ value: p
274
+ }));
275
+ const response = await prompts({
276
+ type: 'select',
277
+ name: 'provider',
278
+ message: 'Choose API provider (casa de API):',
279
+ choices: [...choices, { title: '🔙 Back', value: 'back' }]
280
+ });
281
+ if (response.provider === 'back')
282
+ return showMenu();
283
+ const currentActive = getEffectiveActiveModel();
284
+ const model = (currentActive?.provider === response.provider)
285
+ ? currentActive
286
+ : models.find((m) => m.provider === response.provider);
287
+ if (model) {
288
+ config.set('activeModel', model);
289
+ const label = PROVIDER_LABELS[response.provider] || response.provider;
290
+ console.log(ui.success(`Primary provider set to ${label}.\n`));
291
+ }
292
+ else {
293
+ if (response.provider === 'controller') {
294
+ console.log(ui.error('Controller model not found. Add it with: pokt config set-pokt-token -v <token>'));
295
+ }
296
+ else if (response.provider === 'openrouter') {
297
+ console.log(ui.error('No OpenRouter model. Run: pokt models fetch-openrouter then pokt models list'));
298
+ }
299
+ else {
300
+ console.log(ui.error(`No model for provider "${response.provider}". Run: pokt models list and add one.`));
301
+ }
302
+ }
303
+ return showMenu();
304
+ }
@@ -0,0 +1,3 @@
1
+ import OpenAI from 'openai';
2
+ import { ModelConfig } from '../config.js';
3
+ export declare function getClient(modelConfig: ModelConfig): Promise<InstanceType<typeof OpenAI>>;
@@ -0,0 +1,59 @@
1
+ import OpenAI from 'openai';
2
+ import { config, getControllerBaseUrl } from '../config.js';
3
+ export async function getClient(modelConfig) {
4
+ if (modelConfig.provider === 'controller') {
5
+ const baseUrl = getControllerBaseUrl();
6
+ const token = config.get('poktToken');
7
+ if (!token) {
8
+ throw new Error('Token Pokt não configurado. No painel gere um token e use: pokt config set-pokt-token -v <token>');
9
+ }
10
+ return new OpenAI({
11
+ baseURL: `${baseUrl}/api/v1`,
12
+ apiKey: token,
13
+ defaultHeaders: {
14
+ 'HTTP-Referer': 'https://github.com/pokt-cli',
15
+ 'X-Title': 'Pokt CLI',
16
+ }
17
+ });
18
+ }
19
+ else if (modelConfig.provider === 'openrouter') {
20
+ return new OpenAI({
21
+ baseURL: 'https://openrouter.ai/api/v1',
22
+ apiKey: config.get('openrouterToken'),
23
+ defaultHeaders: {
24
+ 'HTTP-Referer': 'https://github.com/pokt-cli',
25
+ 'X-Title': 'Pokt CLI',
26
+ }
27
+ });
28
+ }
29
+ else if (modelConfig.provider === 'gemini') {
30
+ const apiKey = config.get('geminiApiKey');
31
+ if (!apiKey) {
32
+ throw new Error('Gemini API key not set. Use: pokt config set-gemini -v <key>');
33
+ }
34
+ return new OpenAI({
35
+ baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/',
36
+ apiKey: apiKey,
37
+ });
38
+ }
39
+ else if (modelConfig.provider === 'ollama-cloud') {
40
+ const apiKey = config.get('ollamaCloudApiKey');
41
+ if (!apiKey) {
42
+ throw new Error('Ollama Cloud API key não configurada. Crie uma em https://ollama.com/settings/keys e use: pokt config set-ollama-cloud -v <key>');
43
+ }
44
+ return new OpenAI({
45
+ baseURL: 'https://ollama.com/v1',
46
+ apiKey,
47
+ defaultHeaders: {
48
+ Authorization: `Bearer ${apiKey}`,
49
+ },
50
+ });
51
+ }
52
+ else {
53
+ // Ollama (local) — não precisa de API key
54
+ return new OpenAI({
55
+ baseURL: `${config.get('ollamaBaseUrl')}/v1`,
56
+ apiKey: 'ollama',
57
+ });
58
+ }
59
+ }
@@ -0,0 +1,2 @@
1
+ import { ModelConfig } from '../config.js';
2
+ export declare function startChatLoop(modelConfig: ModelConfig): Promise<void>;