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.
- package/bin/legacyver.js +1 -1
- package/legacyver-docs/auth.md +71 -0
- package/legacyver-docs/hash.md +37 -0
- package/package.json +3 -2
- package/src/api/auth.js +32 -45
- package/src/cli/commands/analyze.js +20 -1
- package/src/cli/commands/init.js +34 -4
- package/src/cli/commands/login.js +1 -1
- package/src/cli/commands/providers.js +20 -23
- package/src/cli/ui.js +19 -9
- package/src/db/config.js +39 -14
- package/src/db/index.js +69 -88
- package/src/llm/providers/groq.js +4 -2
- package/src/llm/validator.js +2 -2
- package/src/utils/config.js +1 -1
- package/legacyver-docs/bin/legacyver.md +0 -107
- package/legacyver-docs/src/api/auth.md +0 -47
- package/legacyver-docs/src/cache/hash.md +0 -24
- package/legacyver-docs/src/cache/index.md +0 -112
- package/legacyver-docs/src/cli/commands/analyze.md +0 -58
- package/legacyver-docs/src/cli/commands/cache.md +0 -21
- package/legacyver-docs/src/cli/commands/init.md +0 -42
- package/legacyver-docs/src/cli/commands/login.md +0 -70
- package/legacyver-docs/src/cli/commands/logout.md +0 -26
- package/legacyver-docs/src/cli/commands/providers.md +0 -23
- package/legacyver-docs/src/cli/commands/push.md +0 -48
- package/legacyver-docs/src/cli/commands/version.md +0 -26
- package/legacyver-docs/src/cli/ui.md +0 -112
- package/legacyver-docs/src/crawler/filters.md +0 -54
- package/legacyver-docs/src/crawler/index.md +0 -54
- package/legacyver-docs/src/crawler/manifest.md +0 -22
- package/legacyver-docs/src/crawler/walk.md +0 -29
- package/legacyver-docs/src/db/config.md +0 -13
- package/legacyver-docs/src/db/index.md +0 -86
- package/legacyver-docs/src/llm/chunker.md +0 -28
- package/legacyver-docs/src/llm/cost-estimator.md +0 -62
- package/legacyver-docs/src/llm/free-model.md +0 -40
- package/legacyver-docs/src/llm/index.md +0 -29
- package/legacyver-docs/src/llm/prompts.md +0 -150
- package/legacyver-docs/src/llm/providers/gemini.md +0 -51
- package/legacyver-docs/src/llm/providers/groq.md +0 -76
- package/legacyver-docs/src/llm/providers/kimi.md +0 -48
- package/legacyver-docs/src/llm/providers/ollama.md +0 -50
- package/legacyver-docs/src/llm/providers/openrouter.md +0 -55
- package/legacyver-docs/src/llm/queue.md +0 -41
- package/legacyver-docs/src/llm/re-prompter.md +0 -33
- package/legacyver-docs/src/llm/validator.md +0 -34
- package/legacyver-docs/src/parser/ast/generic.md +0 -34
- package/legacyver-docs/src/parser/ast/go.md +0 -59
- package/legacyver-docs/src/parser/ast/java.md +0 -58
- package/legacyver-docs/src/parser/ast/javascript.md +0 -71
- package/legacyver-docs/src/parser/ast/laravel/blade.md +0 -45
- package/legacyver-docs/src/parser/ast/laravel/classifier.md +0 -29
- package/legacyver-docs/src/parser/ast/laravel/controller.md +0 -57
- package/legacyver-docs/src/parser/ast/laravel/index.md +0 -27
- package/legacyver-docs/src/parser/ast/laravel/model.md +0 -34
- package/legacyver-docs/src/parser/ast/laravel/provider.md +0 -31
- package/legacyver-docs/src/parser/ast/laravel/routes.md +0 -31
- package/legacyver-docs/src/parser/ast/php.md +0 -127
- package/legacyver-docs/src/parser/ast/python.md +0 -62
- package/legacyver-docs/src/parser/ast/typescript.md +0 -22
- package/legacyver-docs/src/parser/body-extractor.md +0 -34
- package/legacyver-docs/src/parser/call-graph.md +0 -45
- package/legacyver-docs/src/parser/complexity-scorer.md +0 -25
- package/legacyver-docs/src/parser/index.md +0 -59
- package/legacyver-docs/src/parser/pattern-detector.md +0 -28
- package/legacyver-docs/src/parser/pkg-builder.md +0 -34
- package/legacyver-docs/src/renderer/html.md +0 -36
- package/legacyver-docs/src/renderer/index.md +0 -26
- package/legacyver-docs/src/renderer/json.md +0 -25
- package/legacyver-docs/src/renderer/markdown.md +0 -77
- package/legacyver-docs/src/utils/config.md +0 -62
- package/legacyver-docs/src/utils/errors.md +0 -53
- 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:
|
|
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.
|
|
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 {
|
|
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
|
|
13
|
+
async function validateToken(token) {
|
|
17
14
|
if (!token) return null;
|
|
18
15
|
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
41
|
+
async function revokeToken(token) {
|
|
54
42
|
if (!token) return;
|
|
55
43
|
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
|
package/src/cli/commands/init.js
CHANGED
|
@@ -24,8 +24,8 @@ module.exports = async function initCommand() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const providerRaw = await ask(rl, `LLM provider [openrouter/
|
|
28
|
-
const providerChoice = providerRaw.trim() || '
|
|
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
|
-
? '
|
|
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';
|
|
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://
|
|
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
|
|
7
|
-
{ id: '
|
|
8
|
-
{ id: '
|
|
9
|
-
{ id: '
|
|
10
|
-
{ id: '
|
|
11
|
-
{ id: '
|
|
12
|
-
{ id: '
|
|
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.
|
|
35
|
-
console.log('
|
|
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') +
|
|
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('
|
|
57
|
+
console.log(pc.bold('Available Groq Models (free, no key required):'));
|
|
58
58
|
console.log('');
|
|
59
|
-
const header = ` ${'Model ID'.padEnd(
|
|
59
|
+
const header = ` ${'Model ID'.padEnd(52)} ${'Context'.padEnd(8)}`;
|
|
60
60
|
console.log(pc.dim(header));
|
|
61
|
-
console.log(pc.dim(' ' + '-'.repeat(
|
|
61
|
+
console.log(pc.dim(' ' + '-'.repeat(62)));
|
|
62
62
|
|
|
63
|
-
for (const m of
|
|
64
|
-
const
|
|
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
|
-
|
|
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('
|
|
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('
|
|
107
|
+
console.log(pc.cyan(' Running on built-in Groq key') + pc.dim(' (openai/gpt-oss-120b)'));
|
|
107
108
|
console.log('');
|
|
108
|
-
console.log(
|
|
109
|
-
console.log(pc.dim('
|
|
110
|
-
|
|
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('
|
|
113
|
-
console.log(pc.dim('
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 };
|