@meller/tokentalos 1.0.0 → 1.0.4

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/api/setup.js ADDED
@@ -0,0 +1,330 @@
1
+ import inquirer from 'inquirer';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import chalk from 'chalk';
6
+
7
+ const CONFIG_PATH = path.join(os.homedir(), '.tokentalosrc');
8
+
9
+ export async function runSetup() {
10
+ if (!process.stdout.isTTY) {
11
+ console.log(chalk.gray('Non-interactive environment detected. Using default configuration.'));
12
+ const defaults = {
13
+ databaseType: 'sqlite',
14
+ sqlitePath: path.join(os.homedir(), '.tokentalos', 'data.db'),
15
+ enableCollector: true,
16
+ gatewayPort: 8060,
17
+ enableDashboard: true,
18
+ dashboardPort: 8060,
19
+ llmProvider: 'gemini',
20
+ defaultModel: 'gemini-3-flash-preview',
21
+ location: 'global',
22
+ formattingFeatures: ['compress', 'pii', 'neutralize'],
23
+ intelligenceFeatures: ['cache', 'explain'],
24
+ securityFeatures: ['injection', 'secrets'],
25
+ securityAction: 'warn',
26
+ piiAction: 'mask',
27
+ databaseSchema: 'tokentalos',
28
+ maxTokens: 12000,
29
+ thresholdAction: 'warning'
30
+ };
31
+ const dbDir = path.dirname(defaults.sqlitePath);
32
+ await fs.ensureDir(dbDir);
33
+ await fs.writeJson(CONFIG_PATH, defaults, { spaces: 2 });
34
+ return defaults;
35
+ }
36
+
37
+ console.log(chalk.blue.bold('\n--- Token Talos Setup ---\n'));
38
+ console.log(chalk.gray('This wizard will configure your Database, Collector (API), and Dashboard.'));
39
+ console.log(chalk.white('\n [Collector]: ') + chalk.gray('The ingestion endpoint that receives and analyzes LLM data.'));
40
+ console.log(chalk.white(' [Dashboard]: ') + chalk.gray('The visual interface for monitoring your AI performance.\n'));
41
+
42
+ // Load previous configuration if it exists to use as defaults
43
+ let previousAnswers = {};
44
+ if (await fs.pathExists(CONFIG_PATH)) {
45
+ try {
46
+ previousAnswers = await fs.readJson(CONFIG_PATH);
47
+ } catch (e) { /* ignore */ }
48
+ }
49
+
50
+ const answers = await inquirer.prompt([
51
+ // --- DATABASE SECTION ---
52
+ {
53
+ type: 'list',
54
+ name: 'databaseType',
55
+ message: chalk.cyan('DATABASE: ') + 'Select storage engine:',
56
+ choices: [
57
+ { name: 'SQLite (Local file, zero-install)', value: 'sqlite' },
58
+ { name: 'PostgreSQL (External server)', value: 'postgres' },
59
+ ],
60
+ default: 'sqlite',
61
+ },
62
+ // ... rest of database questions ...
63
+ {
64
+ type: 'input',
65
+ name: 'sqlitePath',
66
+ message: ' Where should the SQLite database be stored?',
67
+ default: path.join(os.homedir(), '.tokentalos', 'data.db'),
68
+ when: (ans) => ans.databaseType === 'sqlite',
69
+ },
70
+ {
71
+ type: 'input',
72
+ name: 'pgHost',
73
+ message: ' Postgres Host:',
74
+ default: 'localhost',
75
+ when: (ans) => ans.databaseType === 'postgres',
76
+ },
77
+ {
78
+ type: 'input',
79
+ name: 'pgPort',
80
+ message: ' Postgres Port:',
81
+ default: 5432,
82
+ when: (ans) => ans.databaseType === 'postgres',
83
+ },
84
+ {
85
+ type: 'input',
86
+ name: 'pgUser',
87
+ message: ' Postgres User:',
88
+ default: 'postgres',
89
+ when: (ans) => ans.databaseType === 'postgres',
90
+ },
91
+ {
92
+ type: 'password',
93
+ name: 'pgPassword',
94
+ message: ' Postgres Password:',
95
+ when: (ans) => ans.databaseType === 'postgres',
96
+ },
97
+ {
98
+ type: 'input',
99
+ name: 'pgDatabase',
100
+ message: ' Postgres Database Name:',
101
+ default: 'tokentalos',
102
+ when: (ans) => ans.databaseType === 'postgres',
103
+ },
104
+ {
105
+ type: 'input',
106
+ name: 'databaseSchema',
107
+ message: ' Database schema name:',
108
+ default: 'tokentalos',
109
+ },
110
+
111
+ // --- COLLECTOR (API) SECTION ---
112
+ {
113
+ type: 'confirm',
114
+ name: 'enableCollector',
115
+ message: chalk.green('COLLECTOR: ') + 'Enable API ingestion endpoint?',
116
+ default: true,
117
+ },
118
+ {
119
+ type: 'input',
120
+ name: 'gatewayPort',
121
+ message: ' Collector Port:',
122
+ default: 8060,
123
+ when: (ans) => ans.enableCollector,
124
+ validate: (input) => !isNaN(parseInt(input)) || 'Please enter a valid port number',
125
+ },
126
+
127
+ // --- DASHBOARD SECTION ---
128
+ {
129
+ type: 'confirm',
130
+ name: 'enableDashboard',
131
+ message: chalk.magenta('DASHBOARD: ') + 'Enable web interface?',
132
+ default: true,
133
+ },
134
+ {
135
+ type: 'input',
136
+ name: 'dashboardPort',
137
+ message: ' Dashboard Port:',
138
+ default: 8060,
139
+ when: (ans) => ans.enableDashboard,
140
+ validate: (input) => !isNaN(parseInt(input)) || 'Please enter a valid port number',
141
+ },
142
+
143
+ // --- LLM CONFIGURATION (Common) ---
144
+ {
145
+ type: 'list',
146
+ name: 'llmProvider',
147
+ message: chalk.yellow('LLM CONFIG: ') + 'Select provider for OPV/Analysis/Ingest:',
148
+ choices: [
149
+ { name: 'Gemini (via ADC/Vertex)', value: 'gemini' },
150
+ { name: 'Anthropic', value: 'anthropic' },
151
+ { name: 'OpenAI', value: 'openai' },
152
+ { name: 'Skip / Configure Later', value: 'none' },
153
+ ],
154
+ default: 'none',
155
+ when: (ans) => ans.enableCollector || ans.enableDashboard,
156
+ },
157
+ {
158
+ type: 'input',
159
+ name: 'defaultModel',
160
+ message: ' Default model name:',
161
+ default: (ans) => {
162
+ if (ans.llmProvider === 'gemini') return 'gemini-3-flash-preview';
163
+ if (ans.llmProvider === 'anthropic') return 'claude-3-5-sonnet-latest';
164
+ if (ans.llmProvider === 'openai') return 'gpt-4o-mini';
165
+ return 'none';
166
+ },
167
+ when: (ans) => (ans.enableCollector || ans.enableDashboard) && ans.llmProvider !== 'none',
168
+ },
169
+ {
170
+ type: 'input',
171
+ name: 'location',
172
+ message: ' LLM Model Location:',
173
+ default: (ans) => ans.llmProvider === 'gemini' ? 'global' : 'us-central1',
174
+ when: (ans) => (ans.enableCollector || ans.enableDashboard) && ans.llmProvider !== 'none',
175
+ },
176
+ {
177
+ type: 'list',
178
+ name: 'geminiAuthType',
179
+ message: ' Gemini Authentication Method:',
180
+ choices: [
181
+ { name: 'Application Default Credentials (ADC)', value: 'adc' },
182
+ { name: 'API Key', value: 'apikey' }
183
+ ],
184
+ when: (ans) => (ans.enableCollector || ans.enableDashboard) && ans.llmProvider === 'gemini',
185
+ default: (ans) => previousAnswers?.geminiAuthType || 'adc',
186
+ },
187
+ {
188
+ type: 'input',
189
+ name: 'gcpProjectId',
190
+ message: ' Google Cloud Project ID (required for ADC):',
191
+ when: (ans) => (ans.enableCollector || ans.enableDashboard) && ans.llmProvider === 'gemini' && ans.geminiAuthType === 'adc',
192
+ default: (ans) => previousAnswers?.gcpProjectId || '',
193
+ validate: (input) => input.length > 0 || 'Project ID is required for Vertex AI',
194
+ },
195
+ {
196
+ type: 'input',
197
+ name: 'geminiApiKey',
198
+ message: ' Gemini API Key:',
199
+ when: (ans) => (ans.enableCollector || ans.enableDashboard) && ans.llmProvider === 'gemini' && ans.geminiAuthType === 'apikey',
200
+ default: (ans) => previousAnswers?.geminiApiKey || '',
201
+ validate: (input) => input.length > 0 || 'API Key is required',
202
+ },
203
+
204
+ // --- SAFETY FEATURES (Collector Only) ---
205
+ {
206
+ type: 'checkbox',
207
+ name: 'formattingFeatures',
208
+ message: chalk.green('SAFETY: ') + 'Select active guard features:',
209
+ choices: [
210
+ { name: 'Compress (Lossless compression)', value: 'compress', checked: true },
211
+ { name: 'Neutralize (XML wrapping)', value: 'neutralize', checked: true },
212
+ { name: 'PII Redaction (Masking)', value: 'pii', checked: true },
213
+ ],
214
+ when: (ans) => ans.enableCollector,
215
+ },
216
+ {
217
+ type: 'checkbox',
218
+ name: 'securityFeatures',
219
+ message: ' Select Security scanning:',
220
+ choices: [
221
+ { name: 'Injection Scanning (Jailbreak detection)', value: 'injection', checked: true },
222
+ { name: 'Secret Detection (API Keys/Secrets)', value: 'secrets', checked: true },
223
+ ],
224
+ when: (ans) => ans.enableCollector,
225
+ },
226
+ {
227
+ type: 'list',
228
+ name: 'securityAction',
229
+ message: ' Default action for security threats:',
230
+ choices: [
231
+ { name: 'Warn (Log to dashboard, proceed)', value: 'warn' },
232
+ { name: 'Reject (Fail request with error)', value: 'reject' },
233
+ ],
234
+ default: 'warn',
235
+ when: (ans) => ans.enableCollector,
236
+ },
237
+ {
238
+ type: 'input',
239
+ name: 'maxTokens',
240
+ message: ' Max token threshold (per prompt):',
241
+ default: 12000,
242
+ when: (ans) => ans.enableCollector,
243
+ validate: (input) => !isNaN(parseInt(input)) || 'Please enter a valid number',
244
+ },
245
+ {
246
+ type: 'list',
247
+ name: 'thresholdAction',
248
+ message: ' Action when threshold exceeded:',
249
+ choices: [
250
+ { name: 'Warning (Flag in dashboard)', value: 'warning' },
251
+ { name: 'Reject (Fail request)', value: 'reject' },
252
+ ],
253
+ default: 'warning',
254
+ when: (ans) => ans.enableCollector,
255
+ },
256
+
257
+ // --- ANALYTICS FEATURES (Dashboard Only) ---
258
+ {
259
+ type: 'checkbox',
260
+ name: 'intelligenceFeatures',
261
+ message: chalk.magenta('ANALYTICS: ') + 'Select Dashboard features:',
262
+ choices: [
263
+ { name: 'Semantic Caching', value: 'cache', checked: true },
264
+ { name: 'OPV (Reasoning Analysis)', value: 'opv', checked: true },
265
+ { name: 'Explain Plan (Heuristic Detection)', value: 'explain', checked: true },
266
+ ],
267
+ when: (ans) => ans.enableDashboard,
268
+ },
269
+ {
270
+ type: 'checkbox',
271
+ name: 'comparisonProviders',
272
+ message: ' Include in cost-comparison:',
273
+ choices: [
274
+ { name: 'OpenAI', value: 'openai', checked: true },
275
+ { name: 'Anthropic', value: 'anthropic', checked: true },
276
+ { name: 'Gemini', value: 'gemini', checked: true },
277
+ { name: 'DeepSeek', value: 'deepseek', checked: true },
278
+ { name: 'Mistral', value: 'mistral', checked: true },
279
+ { name: 'Meta', value: 'meta', checked: false },
280
+ { name: 'Amazon', value: 'amazon', checked: false },
281
+ { name: 'Alibaba', value: 'alibaba', checked: false },
282
+ { name: 'xAI', value: 'xai', checked: false },
283
+ { name: 'Cohere', value: 'cohere', checked: false },
284
+ ],
285
+ when: (ans) => ans.enableDashboard,
286
+ },
287
+ {
288
+ type: 'list',
289
+ name: 'exportTarget',
290
+ message: ' Default export format:',
291
+ choices: ['jsonl', 'langsmith', 'none'],
292
+ default: 'jsonl',
293
+ when: (ans) => ans.enableDashboard,
294
+ }
295
+ ]);
296
+
297
+ // Create directory for database if using sqlite
298
+ if (answers.databaseType === 'sqlite') {
299
+ const dbDir = path.dirname(answers.sqlitePath);
300
+ await fs.ensureDir(dbDir);
301
+ }
302
+
303
+ // Save config
304
+ await fs.writeJson(CONFIG_PATH, answers, { spaces: 2 });
305
+
306
+ console.log(chalk.green.bold(`\n✅ Setup complete! Configuration saved to ${CONFIG_PATH}`));
307
+
308
+ if (answers.enableCollector) {
309
+ console.log(chalk.white(`\n Collector (API) will run on port: `) + chalk.green.bold(answers.gatewayPort));
310
+ console.log(chalk.gray(` Endpoint: http://localhost:${answers.gatewayPort}/api/v1`));
311
+ }
312
+
313
+ if (answers.enableDashboard) {
314
+ console.log(chalk.white(`\n Dashboard will run on port: `) + chalk.magenta.bold(answers.dashboardPort));
315
+ console.log(chalk.gray(` URL: http://localhost:${answers.dashboardPort}`));
316
+ }
317
+
318
+ if (answers.llmProvider === 'none') {
319
+ console.log(chalk.yellow(`\nNotice: OPV and AI Analysis are currently disabled.\n`));
320
+ }
321
+
322
+ return answers;
323
+ }
324
+
325
+ export async function loadConfig() {
326
+ if (await fs.pathExists(CONFIG_PATH)) {
327
+ return await fs.readJson(CONFIG_PATH);
328
+ }
329
+ return null;
330
+ }
package/bin/tokentalos.js CHANGED
@@ -19,7 +19,7 @@ const getPidFile = (service) => path.join(process.cwd(), `.tokentalos-${service
19
19
  program
20
20
  .name('tokentalos')
21
21
  .description('Standalone LLM Token Usage Analyzer and Proxy')
22
- .version('0.1.0');
22
+ .version('1.0.2');
23
23
 
24
24
  program
25
25
  .command('setup')
@@ -24,14 +24,17 @@ export class LLMGateway {
24
24
  async getVertex() {
25
25
  if (!this.clients.vertex) {
26
26
  const location = this.config.location || process.env.GCP_REGION || 'us-central1';
27
- let project = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || this.config.gcpProjectId || this.config.project;
27
+ let project = this.config.gcpProjectId || this.config.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
28
28
 
29
29
  if (!project) {
30
30
  try {
31
31
  // Try to auto-detect project ID from ADC
32
32
  project = await this.auth.getProjectId();
33
+ if (project) {
34
+ console.log(`[TokenTalos] Auto-detected Project ID from ADC: ${project}`);
35
+ }
33
36
  } catch (e) {
34
- // Ignore auth errors here
37
+ console.warn(`[TokenTalos] Failed to auto-detect Project ID: ${e.message}`);
35
38
  }
36
39
  }
37
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meller/tokentalos",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "Token Talos: The ORM for LLMs. A standalone gateway and library for cost-optimized, secure, and tracked prompt orchestration.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -20,6 +20,7 @@
20
20
  "bin/tokentalos.js",
21
21
  "lib/engine/",
22
22
  "api/index.js",
23
+ "api/setup.js",
23
24
  "api/api/",
24
25
  "api/middleware/",
25
26
  "package.json"