legacyver 3.1.0 → 3.4.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 (74) hide show
  1. package/bin/legacyver.js +1 -1
  2. package/legacyver-docs/auth.md +71 -0
  3. package/legacyver-docs/hash.md +37 -0
  4. package/package.json +3 -2
  5. package/src/api/auth.js +32 -45
  6. package/src/cli/commands/analyze.js +20 -1
  7. package/src/cli/commands/init.js +34 -4
  8. package/src/cli/commands/login.js +1 -1
  9. package/src/cli/commands/providers.js +20 -23
  10. package/src/cli/ui.js +19 -9
  11. package/src/db/config.js +39 -14
  12. package/src/db/index.js +69 -88
  13. package/src/llm/providers/groq.js +4 -2
  14. package/src/llm/validator.js +2 -2
  15. package/src/utils/config.js +1 -1
  16. package/legacyver-docs/bin/legacyver.md +0 -107
  17. package/legacyver-docs/src/api/auth.md +0 -47
  18. package/legacyver-docs/src/cache/hash.md +0 -24
  19. package/legacyver-docs/src/cache/index.md +0 -112
  20. package/legacyver-docs/src/cli/commands/analyze.md +0 -58
  21. package/legacyver-docs/src/cli/commands/cache.md +0 -21
  22. package/legacyver-docs/src/cli/commands/init.md +0 -42
  23. package/legacyver-docs/src/cli/commands/login.md +0 -70
  24. package/legacyver-docs/src/cli/commands/logout.md +0 -26
  25. package/legacyver-docs/src/cli/commands/providers.md +0 -23
  26. package/legacyver-docs/src/cli/commands/push.md +0 -48
  27. package/legacyver-docs/src/cli/commands/version.md +0 -26
  28. package/legacyver-docs/src/cli/ui.md +0 -112
  29. package/legacyver-docs/src/crawler/filters.md +0 -54
  30. package/legacyver-docs/src/crawler/index.md +0 -54
  31. package/legacyver-docs/src/crawler/manifest.md +0 -22
  32. package/legacyver-docs/src/crawler/walk.md +0 -29
  33. package/legacyver-docs/src/db/config.md +0 -13
  34. package/legacyver-docs/src/db/index.md +0 -86
  35. package/legacyver-docs/src/llm/chunker.md +0 -28
  36. package/legacyver-docs/src/llm/cost-estimator.md +0 -62
  37. package/legacyver-docs/src/llm/free-model.md +0 -40
  38. package/legacyver-docs/src/llm/index.md +0 -29
  39. package/legacyver-docs/src/llm/prompts.md +0 -150
  40. package/legacyver-docs/src/llm/providers/gemini.md +0 -51
  41. package/legacyver-docs/src/llm/providers/groq.md +0 -76
  42. package/legacyver-docs/src/llm/providers/kimi.md +0 -48
  43. package/legacyver-docs/src/llm/providers/ollama.md +0 -50
  44. package/legacyver-docs/src/llm/providers/openrouter.md +0 -55
  45. package/legacyver-docs/src/llm/queue.md +0 -41
  46. package/legacyver-docs/src/llm/re-prompter.md +0 -33
  47. package/legacyver-docs/src/llm/validator.md +0 -34
  48. package/legacyver-docs/src/parser/ast/generic.md +0 -34
  49. package/legacyver-docs/src/parser/ast/go.md +0 -59
  50. package/legacyver-docs/src/parser/ast/java.md +0 -58
  51. package/legacyver-docs/src/parser/ast/javascript.md +0 -71
  52. package/legacyver-docs/src/parser/ast/laravel/blade.md +0 -45
  53. package/legacyver-docs/src/parser/ast/laravel/classifier.md +0 -29
  54. package/legacyver-docs/src/parser/ast/laravel/controller.md +0 -57
  55. package/legacyver-docs/src/parser/ast/laravel/index.md +0 -27
  56. package/legacyver-docs/src/parser/ast/laravel/model.md +0 -34
  57. package/legacyver-docs/src/parser/ast/laravel/provider.md +0 -31
  58. package/legacyver-docs/src/parser/ast/laravel/routes.md +0 -31
  59. package/legacyver-docs/src/parser/ast/php.md +0 -127
  60. package/legacyver-docs/src/parser/ast/python.md +0 -62
  61. package/legacyver-docs/src/parser/ast/typescript.md +0 -22
  62. package/legacyver-docs/src/parser/body-extractor.md +0 -34
  63. package/legacyver-docs/src/parser/call-graph.md +0 -45
  64. package/legacyver-docs/src/parser/complexity-scorer.md +0 -25
  65. package/legacyver-docs/src/parser/index.md +0 -59
  66. package/legacyver-docs/src/parser/pattern-detector.md +0 -28
  67. package/legacyver-docs/src/parser/pkg-builder.md +0 -34
  68. package/legacyver-docs/src/renderer/html.md +0 -36
  69. package/legacyver-docs/src/renderer/index.md +0 -26
  70. package/legacyver-docs/src/renderer/json.md +0 -25
  71. package/legacyver-docs/src/renderer/markdown.md +0 -77
  72. package/legacyver-docs/src/utils/config.md +0 -62
  73. package/legacyver-docs/src/utils/errors.md +0 -53
  74. package/legacyver-docs/src/utils/logger.md +0 -48
package/bin/legacyver.js CHANGED
@@ -21,7 +21,7 @@ program
21
21
  .option('--out <dir>', 'Output directory (default: ./legacyver-docs)')
22
22
  .option('--format <fmt>', 'Output format: markdown | html | json (default: markdown)')
23
23
  .option('--model <model>', 'LLM model to use')
24
- .option('--provider <provider>', 'LLM provider: openrouter | groq | gemini | kimi | ollama (default: openrouter)')
24
+ .option('--provider <provider>', 'LLM provider: groq | openrouter | gemini | kimi | ollama (default: groq)')
25
25
  .option('--concurrency <n>', 'Concurrent LLM requests 1-10 (default: 3)')
26
26
  .option('--dry-run', 'Run AST parsing only, no LLM calls')
27
27
  .option('--incremental', 'Only re-analyze changed files')
@@ -0,0 +1,71 @@
1
+ ## Overview
2
+ `auth.js` provides two asynchronous helpers for handling CLI session tokens stored in the `user_sessions` table: validating a token and revoking (logging out) a token.
3
+
4
+ ## Functions
5
+
6
+ ### `validateToken(token)`
7
+ Validates a raw CLI session token and returns basic user information if the token is active.
8
+
9
+ | Parameter | Type | Description |
10
+ |-----------|------|-------------|
11
+ | `token` | `string` | Raw token read from `~/.legacyver/session.json`. |
12
+
13
+ **Returns**
14
+ `Promise<{ userId: string, username: string, email: string } | null>` – Resolves to an object containing the user’s ID, username, and email when the token is found, not expired, and not revoked; otherwise resolves to `null`.
15
+
16
+ **Logic summary**
17
+ 1. If `token` is falsy, the function returns `null` immediately.
18
+ 2. The token is hashed with SHA‑256 (`crypto.createHash('sha256')`).
19
+ 3. A database client is created via `createDbClient(token)` – the token is passed so the client can bypass row‑level security.
20
+ 4. The client queries the `public.user_sessions` table, selecting `user_id` and the related `users` fields `username` and `email` (inner join).
21
+ 5. The query filters:
22
+ * `token_hash` equals the computed hash,
23
+ * `expires_at` is greater than the current ISO timestamp,
24
+ * `revoked_at` is `null`.
25
+ 6. `maybeSingle()` ensures at most one row is returned.
26
+ 7. If the query yields an error or no data, the function returns `null`.
27
+ 8. Otherwise it builds the result object, converting `user_id` to a string and falling back to default values (`'unknown'` for username, empty string for email) if the joined user record is missing.
28
+
29
+ ---
30
+
31
+ ### `revokeToken(token)`
32
+ Revokes a CLI session token, effectively logging the user out.
33
+
34
+ | Parameter | Type | Description |
35
+ |-----------|------|-------------|
36
+ | `token` | `string` | Raw token to be revoked. |
37
+
38
+ **Returns**
39
+ `Promise<void>` – Resolves when the revocation update completes; throws an error if the database operation fails.
40
+
41
+ **Logic summary**
42
+ 1. If `token` is falsy, the function exits without action.
43
+ 2. The token is hashed with SHA‑256 (same method as `validateToken`).
44
+ 3. A database client is created with `createDbClient(token)`.
45
+ 4. The client updates the `revoked_at` column of the matching `user_sessions` row (where `token_hash` matches) to the current ISO timestamp.
46
+ 5. If the update returns an `error`, the function throws that error; otherwise it completes silently.
47
+
48
+ ## Dependencies
49
+ - `crypto` (Node.js built‑in) – used for SHA‑256 hashing.
50
+ - `../db/config` – imports `supabase` (unused in this file) and `createDbClient` for database access.
51
+
52
+ ## Usage Example
53
+ ```js
54
+ const { validateToken, revokeToken } = require('./auth');
55
+
56
+ // Validate a stored session token
57
+ (async () => {
58
+ const token = 'my‑raw‑session‑token';
59
+ const user = await validateToken(token);
60
+
61
+ if (user) {
62
+ console.log('Authenticated user:', user);
63
+ } else {
64
+ console.log('Invalid or expired token');
65
+ }
66
+
67
+ // When the user logs out
68
+ await revokeToken(token);
69
+ console.log('Token revoked');
70
+ })();
71
+ ```
@@ -0,0 +1,37 @@
1
+ ## Overview
2
+ Provides a utility to compute the SHA‑256 hash of a file's contents, returning the hash as a hex string prefixed with `sha256:`.
3
+
4
+ ## Functions
5
+
6
+ ### `computeHash(filePath)`
7
+ Computes the SHA‑256 hash of the file located at `filePath`.
8
+
9
+ | Parameter | Type | Description |
10
+ |-----------|--------|----------------------------|
11
+ | `filePath`| string | Path to the file to hash. |
12
+
13
+ **Returns**: `string` – A hexadecimal SHA‑256 digest prefixed with `sha256:` (e.g., `sha256:ab12cd...`).
14
+
15
+ **Logic**
16
+ 1. Reads the file synchronously using `fs.readFileSync`.
17
+ 2. Creates a SHA‑256 hash object via `crypto.createHash('sha256')`.
18
+ 3. Feeds the file content to the hash with `.update(content)`.
19
+ 4. Produces a hex‑encoded digest with `.digest('hex')`.
20
+ 5. Concatenates the prefix `sha256:` with the digest and returns the result.
21
+
22
+ ## Dependencies
23
+ - `crypto` – `createHash`
24
+ - `fs` – `readFileSync`
25
+
26
+ ## Usage Example
27
+ ```javascript
28
+ // Import the function
29
+ const { computeHash } = require('./hash');
30
+
31
+ // Compute the hash of a file
32
+ const filePath = 'path/to/your/file.txt';
33
+ const hash = computeHash(filePath);
34
+
35
+ console.log(`SHA-256 hash: ${hash}`);
36
+ // Example output: SHA-256 hash: sha256:3a7bd3e2360a...
37
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "legacyver",
3
- "version": "3.1.0",
3
+ "version": "3.4.0",
4
4
  "description": "AI-powered CLI tool to auto-generate technical documentation from legacy/undocumented codebases",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "author": "",
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
+ "@supabase/supabase-js": "^2.99.3",
30
31
  "chalk": "^4.1.2",
31
32
  "cli-progress": "^3.12.0",
32
33
  "commander": "^11.1.0",
@@ -47,4 +48,4 @@
47
48
  "eslint": "^8.57.0",
48
49
  "vitest": "^1.2.2"
49
50
  }
50
- }
51
+ }
package/src/api/auth.js CHANGED
@@ -1,69 +1,56 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
- const { Pool } = require('pg');
5
- const dbConfig = require('../db/config');
4
+ const { supabase, createDbClient } = require('../db/config');
6
5
 
7
6
  /**
8
7
  * Validate a CLI session token against app.user_sessions.
9
8
  * Returns user info if valid, null if expired/revoked/not found.
10
9
  *
11
10
  * @param {string} token raw token from ~/.legacyver/session.json
12
- * @param {object} [opts] optional overrides for testing
13
- * @param {object} [opts.pool] pg Pool instance
14
11
  * @returns {Promise<{userId: string, username: string, email: string} | null>}
15
12
  */
16
- async function validateToken(token, opts) {
13
+ async function validateToken(token) {
17
14
  if (!token) return null;
18
15
 
19
- const ownPool = !(opts && opts.pool);
20
- const pool = (opts && opts.pool) || new Pool(dbConfig);
21
- try {
22
- const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
23
-
24
- const result = await pool.query(
25
- `SELECT s.user_id, u.username, u.email
26
- FROM app.user_sessions s
27
- JOIN app.users u ON u.id = s.user_id
28
- WHERE s.token_hash = $1
29
- AND s.expires_at > NOW()
30
- AND s.revoked_at IS NULL`,
31
- [tokenHash]
32
- );
33
-
34
- if (result.rows.length === 0) return null;
35
-
36
- const row = result.rows[0];
37
- return {
38
- userId: String(row.user_id),
39
- username: row.username || 'unknown',
40
- email: row.email || '',
41
- };
42
- } finally {
43
- if (ownPool) await pool.end().catch(() => {});
44
- }
16
+ const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
17
+ const client = createDbClient(token); // Use token to bypass RLS
18
+
19
+ const { data, error } = await client
20
+ .schema('public')
21
+ .from('user_sessions')
22
+ .select('user_id, users!inner(username, email)')
23
+ .eq('token_hash', tokenHash)
24
+ .gt('expires_at', new Date().toISOString())
25
+ .is('revoked_at', null)
26
+ .maybeSingle();
27
+
28
+ if (error || !data) return null;
29
+
30
+ return {
31
+ userId: String(data.user_id),
32
+ username: data.users?.username || 'unknown',
33
+ email: data.users?.email || '',
34
+ };
45
35
  }
46
36
 
47
37
  /**
48
38
  * Revoke a CLI session token (logout).
49
39
  * @param {string} token raw token
50
- * @param {object} [opts] optional overrides for testing
51
- * @param {object} [opts.pool] pg Pool instance
52
40
  */
53
- async function revokeToken(token, opts) {
41
+ async function revokeToken(token) {
54
42
  if (!token) return;
55
43
 
56
- const ownPool = !(opts && opts.pool);
57
- const pool = (opts && opts.pool) || new Pool(dbConfig);
58
- try {
59
- const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
60
- await pool.query(
61
- 'UPDATE app.user_sessions SET revoked_at = NOW() WHERE token_hash = $1',
62
- [tokenHash]
63
- );
64
- } finally {
65
- if (ownPool) await pool.end().catch(() => {});
66
- }
44
+ const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
45
+ const client = createDbClient(token);
46
+
47
+ const { error } = await client
48
+ .schema('public')
49
+ .from('user_sessions')
50
+ .update({ revoked_at: new Date().toISOString() })
51
+ .eq('token_hash', tokenHash);
52
+
53
+ if (error) throw error;
67
54
  }
68
55
 
69
56
  module.exports = { validateToken, revokeToken };
@@ -217,7 +217,23 @@ module.exports = async function analyzeCommand(target, flags) {
217
217
 
218
218
  // Merge cached fragments
219
219
  const allFragments = [...docFragments];
220
- // (cached fragments would be loaded here)
220
+ if (config.incremental && cacheHits.length > 0) {
221
+ const fs = require('fs');
222
+ for (const hit of cacheHits) {
223
+ const cachedInfo = cacheMap[hit.relativePath];
224
+ if (cachedInfo && cachedInfo.docFile) {
225
+ try {
226
+ const content = fs.readFileSync(cachedInfo.docFile, 'utf8');
227
+ allFragments.push({
228
+ ...hit,
229
+ content
230
+ });
231
+ } catch (e) {
232
+ logger.warn(`Failed to read cached file for ${hit.relativePath}`);
233
+ }
234
+ }
235
+ }
236
+ }
221
237
 
222
238
  // ─── Stage 4: Renderer ───────────────────────────────────────────────────
223
239
  const renderSpinner = createSpinner('Rendering output...');
@@ -268,8 +284,11 @@ module.exports = async function analyzeCommand(target, flags) {
268
284
 
269
285
  if (!cloudResult.skipped) {
270
286
  syncSpinner.succeed(`Docs synced to cloud (${cloudResult.pushed} files)`);
287
+ } else if (session.token) {
288
+ syncSpinner.stop(); // Stops spinner gracefully if skipped without error
271
289
  }
272
290
  } catch (syncErr) {
291
+ if (typeof syncSpinner !== 'undefined') syncSpinner.fail('Cloud sync failed');
273
292
  logger.warn('Cloud sync failed: ' + syncErr.message);
274
293
  }
275
294
 
@@ -24,8 +24,8 @@ module.exports = async function initCommand() {
24
24
  }
25
25
  }
26
26
 
27
- const providerRaw = await ask(rl, `LLM provider [openrouter/groq/gemini/kimi/ollama] (default: openrouter): `);
28
- const providerChoice = providerRaw.trim() || 'openrouter';
27
+ const providerRaw = await ask(rl, `LLM provider [groq/openrouter/gemini/kimi/ollama] (default: groq): `);
28
+ const providerChoice = providerRaw.trim() || 'groq';
29
29
  const isOllama = providerChoice === 'ollama';
30
30
  const isGroq = providerChoice === 'groq';
31
31
  const isGemini = providerChoice === 'gemini';
@@ -34,7 +34,7 @@ module.exports = async function initCommand() {
34
34
  const defaultModel = isOllama
35
35
  ? 'llama3.2'
36
36
  : isGroq
37
- ? 'llama-3.3-70b-versatile'
37
+ ? 'openai/gpt-oss-120b'
38
38
  : isGemini
39
39
  ? 'gemini-2.0-flash'
40
40
  : isKimi
@@ -75,6 +75,35 @@ module.exports = async function initCommand() {
75
75
  writeFileSync(rcPath, JSON.stringify(config, null, 2), 'utf8');
76
76
  console.log(pc.green('\n✓ Created .legacyverrc'));
77
77
 
78
+ // If user left key blank, show how to set env var (cross-platform)
79
+ if (!isOllama && !apiKey.trim()) {
80
+ const isWin = process.platform === 'win32';
81
+ const envVarMap = {
82
+ groq: { varName: 'GROQ_API_KEY', url: 'https://console.groq.com/keys' },
83
+ openrouter: { varName: 'OPENROUTER_API_KEY', url: 'https://openrouter.ai/keys' },
84
+ gemini: { varName: 'GEMINI_API_KEY', url: 'https://aistudio.google.com/apikey' },
85
+ kimi: { varName: 'MOONSHOT_API_KEY', url: 'https://platform.moonshot.cn/console/api-keys' },
86
+ };
87
+ const envInfo = envVarMap[providerChoice] || envVarMap['openrouter'];
88
+
89
+ console.log('');
90
+ if (isGroq) {
91
+ console.log(pc.dim('i No key entered — using built-in Groq key (openai/gpt-oss-120b).'));
92
+ console.log(pc.dim(' For higher rate limits, set your own key:'));
93
+ } else {
94
+ console.log(pc.yellow(`! No API key saved. Set ${envInfo.varName} before running analyze.`));
95
+ console.log(pc.dim(` Get a key: ${envInfo.url}`));
96
+ }
97
+ console.log('');
98
+ if (isWin) {
99
+ console.log(pc.dim(` PowerShell: $env:${envInfo.varName} = "your_key"`));
100
+ console.log(pc.dim(` CMD: set ${envInfo.varName}=your_key`));
101
+ } else {
102
+ console.log(pc.dim(` Mac/Linux: export ${envInfo.varName}=your_key`));
103
+ }
104
+ console.log(pc.dim(' Or re-run: legacyver init (enter the key when prompted)'));
105
+ }
106
+
78
107
  const exampleCmd = isOllama
79
108
  ? 'legacyver analyze --provider ollama'
80
109
  : isGroq
@@ -83,6 +112,7 @@ module.exports = async function initCommand() {
83
112
  ? 'legacyver analyze --provider gemini'
84
113
  : isKimi
85
114
  ? 'legacyver analyze --provider kimi'
86
- : 'legacyver analyze'; // openrouter is the default, no flag needed
115
+ : 'legacyver analyze';
87
116
  console.log(pc.cyan(`\nRun \`${exampleCmd}\` to generate documentation.`));
88
117
  };
118
+
@@ -5,7 +5,7 @@ const crypto = require('crypto');
5
5
  const pc = require('picocolors');
6
6
  const { saveSession, loadSession } = require('../../utils/config');
7
7
 
8
- const WEB_URL = 'https://weci-holic.hackathon.sev-2.com';
8
+ const WEB_URL = 'https://legac.vercel.app';
9
9
 
10
10
  /**
11
11
  * Open a URL in the default browser (cross-platform).
@@ -3,14 +3,13 @@
3
3
  const pc = require('picocolors');
4
4
  const logger = require('../../utils/logger');
5
5
 
6
- const RECOMMENDED_MODELS = [
7
- { id: 'meta-llama/llama-3.1-8b-instruct', context: '16k', inputCost: 0.02, outputCost: 0.05, free: false },
8
- { id: 'anthropic/claude-haiku-3-5', context: '200k', inputCost: 0.80, outputCost: 4.00, free: false },
9
- { id: 'anthropic/claude-sonnet-4-5', context: '200k', inputCost: 3.00, outputCost: 15.00, free: false },
10
- { id: 'openai/gpt-4o-mini', context: '128k', inputCost: 0.15, outputCost: 0.60, free: false },
11
- { id: 'openai/gpt-4o', context: '128k', inputCost: 5.00, outputCost: 15.00, free: false },
12
- { id: 'google/gemini-flash-1.5', context: '1M', inputCost: 0.075, outputCost: 0.30, free: false },
13
- { id: 'mistralai/mistral-7b-instruct:free', context: '32k', inputCost: 0, outputCost: 0, free: true },
6
+ const GROQ_MODELS = [
7
+ { id: 'openai/gpt-oss-120b', context: '128k', isDefault: true },
8
+ { id: 'llama-3.3-70b-versatile', context: '128k', isDefault: false },
9
+ { id: 'llama-3.1-8b-instant', context: '128k', isDefault: false },
10
+ { id: 'meta-llama/llama-4-scout-17b-16e-instruct', context: '128k', isDefault: false },
11
+ { id: 'meta-llama/llama-4-maverick-17b-128e-instruct', context: '128k', isDefault: false },
12
+ { id: 'gemma2-9b-it', context: '8k', isDefault: false },
14
13
  ];
15
14
 
16
15
  module.exports = async function providersCommand() {
@@ -30,9 +29,10 @@ module.exports = async function providersCommand() {
30
29
 
31
30
  console.log(pc.bold('Legacyver — Supported LLM Providers\n'));
32
31
 
33
- console.log(pc.bold('Groq') + ' (https://groq.com)');
34
- console.log(' Fastest free LLM inference. 30 req/min, 14,400 req/day. Set GROQ_API_KEY env variable.');
35
- console.log(' Status: ' + (process.env.GROQ_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
32
+ console.log(pc.bold('Groq') + pc.green(' [DEFAULT]') + ' (https://groq.com)');
33
+ console.log(' Fastest free LLM inference. 30 req/min, 14,400 req/day.');
34
+ console.log(' Default model: ' + pc.cyan('openai/gpt-oss-120b') + ' override with your own GROQ_API_KEY for higher limits.');
35
+ console.log(' Status: ' + (process.env.GROQ_API_KEY ? pc.green('Own API key detected ✓') : pc.dim('Using built-in shared key (set GROQ_API_KEY for higher rate limits)')));
36
36
  console.log(' Get a free key at: https://console.groq.com/keys');
37
37
  console.log('');
38
38
  console.log(pc.bold('Google Gemini') + ' (https://ai.google.dev)');
@@ -45,7 +45,7 @@ module.exports = async function providersCommand() {
45
45
  console.log(' Status: ' + (process.env.MOONSHOT_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
46
46
  console.log(' Get a key at: https://platform.moonshot.cn/console/api-keys');
47
47
  console.log('');
48
- console.log(pc.bold('OpenRouter') + pc.green(' [DEFAULT]') + ' (https://openrouter.ai)');
48
+ console.log(pc.bold('OpenRouter') + ' (https://openrouter.ai)');
49
49
  console.log(' Unified gateway to 200+ models (Claude, GPT-4o, Llama, etc). Set OPENROUTER_API_KEY env variable.');
50
50
  console.log(' Status: ' + (process.env.OPENROUTER_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
51
51
  console.log(' Get a key at: https://openrouter.ai/keys');
@@ -54,23 +54,20 @@ module.exports = async function providersCommand() {
54
54
  console.log(' Local offline LLM. No API key required. Run `ollama serve` first.');
55
55
  console.log('');
56
56
 
57
- console.log(pc.bold('Recommended Models (via OpenRouter):'));
57
+ console.log(pc.bold('Available Groq Models (free, no key required):'));
58
58
  console.log('');
59
- const header = ` ${'Model ID'.padEnd(48)} ${'Context'.padEnd(8)} ${'Input $/1M'.padEnd(12)} ${'Output $/1M'.padEnd(12)}`;
59
+ const header = ` ${'Model ID'.padEnd(52)} ${'Context'.padEnd(8)}`;
60
60
  console.log(pc.dim(header));
61
- console.log(pc.dim(' ' + '-'.repeat(84)));
61
+ console.log(pc.dim(' ' + '-'.repeat(62)));
62
62
 
63
- for (const m of RECOMMENDED_MODELS) {
64
- const badge = m.free ? pc.green(' [FREE]') : '';
63
+ for (const m of GROQ_MODELS) {
64
+ const defaultBadge = m.isDefault ? pc.green(' [DEFAULT]') : '';
65
65
  const selected = m.id === config.model ? pc.cyan(' ◀ selected') : '';
66
- const inputCostStr = m.free ? 'FREE' : `$${m.inputCost.toFixed(3)}`;
67
- const outputCostStr = m.free ? 'FREE' : `$${m.outputCost.toFixed(3)}`;
68
- console.log(
69
- ` ${m.id.padEnd(48)} ${m.context.padEnd(8)} ${inputCostStr.padEnd(12)} ${outputCostStr.padEnd(12)}${badge}${selected}`
70
- );
66
+ console.log(` ${m.id.padEnd(52)} ${m.context.padEnd(8)}${defaultBadge}${selected}`);
71
67
  }
72
68
 
73
69
  console.log('');
74
- console.log(pc.dim('Fetch live model list from OpenRouter: https://openrouter.ai/api/v1/models'));
70
+ console.log(pc.dim('Full Groq model list: https://console.groq.com/docs/models'));
71
+ console.log(pc.dim('For premium models (Claude, GPT-4o, etc.) use --provider openrouter'));
75
72
  console.log('');
76
73
  };
package/src/cli/ui.js CHANGED
@@ -102,18 +102,28 @@ function printSummary(stats) {
102
102
  process.env.OPENROUTER_API_KEY
103
103
  );
104
104
  if (!usingOwnKey) {
105
+ const isWin = process.platform === 'win32';
105
106
  console.log(pc.dim('─────────────────────────────────────────────────'));
106
- console.log(pc.cyan(' Bring your own API key for better results:'));
107
+ console.log(pc.cyan(' Running on built-in Groq key') + pc.dim(' (openai/gpt-oss-120b)'));
107
108
  console.log('');
108
- console.log(` ${pc.bold('Free tiers')} more quota, same zero cost`);
109
- console.log(pc.dim(' Groq: https://console.groq.com/keys'));
110
- console.log(pc.dim(' Gemini: https://aistudio.google.com/apikey'));
109
+ console.log(' To get higher rate limits, set your own free Groq key:');
110
+ console.log(pc.dim(' Get key: https://console.groq.com/keys'));
111
+ if (isWin) {
112
+ console.log(pc.dim(' PowerShell: $env:GROQ_API_KEY = "your_key"'));
113
+ console.log(pc.dim(' CMD: set GROQ_API_KEY=your_key'));
114
+ } else {
115
+ console.log(pc.dim(' Mac/Linux: export GROQ_API_KEY=your_key'));
116
+ }
117
+ console.log(pc.dim(' Or run: legacyver init'));
111
118
  console.log('');
112
- console.log(` ${pc.bold('Premium')} smarter models (Claude, GPT-4o, etc.)`);
113
- console.log(pc.dim(' OpenRouter: https://openrouter.ai/keys'));
114
- console.log(pc.dim(' Access 200+ models, pay only for what you use.'));
115
- console.log('');
116
- console.log(pc.dim(' Run `legacyver init` to set your key, then:'));
119
+ console.log(` ${pc.bold('Want premium models?')} (Claude, GPT-4o, etc.)`);
120
+ console.log(pc.dim(' Get key: https://openrouter.ai/keys'));
121
+ if (isWin) {
122
+ console.log(pc.dim(' PowerShell: $env:OPENROUTER_API_KEY = "your_key"'));
123
+ console.log(pc.dim(' CMD: set OPENROUTER_API_KEY=your_key'));
124
+ } else {
125
+ console.log(pc.dim(' Mac/Linux: export OPENROUTER_API_KEY=your_key'));
126
+ }
117
127
  console.log(pc.dim(' legacyver analyze --provider openrouter --model anthropic/claude-haiku-3-5'));
118
128
  console.log(pc.dim('─────────────────────────────────────────────────'));
119
129
  console.log('');
package/src/db/config.js CHANGED
@@ -1,18 +1,43 @@
1
1
  'use strict';
2
2
 
3
+ const { createClient } = require('@supabase/supabase-js');
4
+
3
5
  /**
4
- * PostgreSQL connection config for the Legacyver hackathon database.
5
- * Uses SSL with self-signed certificate (rejectUnauthorized: false).
6
+ * Supabase client for Legacyver CLI.
7
+ *
8
+ * Uses the public anon key — safe to bundle in an npm package.
9
+ * Row Level Security (RLS) on Supabase enforces access control.
6
10
  */
7
- module.exports = {
8
- host: '103.185.52.138',
9
- port: 1185,
10
- user: 'weci_holic',
11
- password: 'f==+HLH_bvzLN2fo82f3x239MZE3@bGF',
12
- database: 'weci_holic',
13
- ssl: { rejectUnauthorized: false },
14
- // Keep pool small — CLI is short-lived
15
- max: 3,
16
- idleTimeoutMillis: 5000,
17
- connectionTimeoutMillis: 10000,
18
- };
11
+ const SUPABASE_URL = 'https://kbsxwyoylwhieoljepxr.supabase.co';
12
+
13
+ // anon/public key — safe to commit, RLS is the gatekeeper
14
+ const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imtic3h3eW95bHdoaWVvbGplcHhyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQxMjU4ODksImV4cCI6MjA4OTcwMTg4OX0.GHX8Id1qunhypkN6WurM4UZUgVwrkD_z3bOIJhW2Y7A';
15
+
16
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
17
+ auth: {
18
+ persistSession: false,
19
+ autoRefreshToken: false,
20
+ detectSessionFromUrl: false,
21
+ },
22
+ });
23
+
24
+ /**
25
+ * Create a Supabase client configured with the CLI token for RLS bypass.
26
+ */
27
+ function createDbClient(token) {
28
+ const headers = {};
29
+ if (token) {
30
+ headers['x-cli-token'] = token;
31
+ }
32
+
33
+ return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
34
+ auth: {
35
+ persistSession: false,
36
+ autoRefreshToken: false,
37
+ detectSessionFromUrl: false,
38
+ },
39
+ global: { headers }
40
+ });
41
+ }
42
+
43
+ module.exports = { supabase, createDbClient };