legacyver 2.1.3 → 2.1.5
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/.env +1 -0
- package/.env.example +17 -0
- package/.gitattributes +0 -0
- package/bin/legacyver.js +18 -2
- package/legacyver-docs/SUMMARY.md +1 -3
- package/legacyver-docs/components.md +31 -22
- package/legacyver-docs/index.md +5 -7
- package/package.json +3 -1
- package/src/api/auth.js +69 -0
- package/src/cli/commands/analyze.js +40 -9
- package/src/cli/commands/init.js +14 -7
- package/src/cli/commands/login.js +68 -0
- package/src/cli/commands/logout.js +22 -0
- package/src/cli/commands/providers.js +29 -11
- package/src/cli/ui.js +38 -0
- package/src/db/config.js +18 -0
- package/src/db/index.js +145 -0
- package/src/llm/free-model.js +12 -3
- package/src/llm/index.js +9 -6
- package/src/llm/providers/groq.js +4 -2
- package/src/llm/providers/kimi.js +86 -0
- package/src/llm/validator.js +1 -1
- package/src/utils/config.js +28 -2
- package/temp_credentials/README.md +61 -0
- package/temp_credentials/db-credentials.txt +6 -0
- package/temp_credentials/domain.txt +1 -0
- package/temp_credentials/kubeconfig.yaml +18 -0
- package/temp_credentials/kubernetes-credentials.txt +2 -0
- package/.agent/skills/openspec-apply-change/SKILL.md +0 -156
- package/.agent/skills/openspec-archive-change/SKILL.md +0 -114
- package/.agent/skills/openspec-bulk-archive-change/SKILL.md +0 -246
- package/.agent/skills/openspec-continue-change/SKILL.md +0 -118
- package/.agent/skills/openspec-explore/SKILL.md +0 -290
- package/.agent/skills/openspec-ff-change/SKILL.md +0 -101
- package/.agent/skills/openspec-new-change/SKILL.md +0 -74
- package/.agent/skills/openspec-onboard/SKILL.md +0 -529
- package/.agent/skills/openspec-sync-specs/SKILL.md +0 -138
- package/.agent/skills/openspec-verify-change/SKILL.md +0 -168
- package/.agent/workflows/opsx-apply.md +0 -149
- package/.agent/workflows/opsx-archive.md +0 -154
- package/.agent/workflows/opsx-bulk-archive.md +0 -239
- package/.agent/workflows/opsx-continue.md +0 -111
- package/.agent/workflows/opsx-explore.md +0 -171
- package/.agent/workflows/opsx-ff.md +0 -91
- package/.agent/workflows/opsx-new.md +0 -66
- package/.agent/workflows/opsx-onboard.md +0 -522
- package/.agent/workflows/opsx-sync.md +0 -131
- package/.agent/workflows/opsx-verify.md +0 -161
- package/.github/prompts/opsx-apply.prompt.md +0 -149
- package/.github/prompts/opsx-archive.prompt.md +0 -154
- package/.github/prompts/opsx-bulk-archive.prompt.md +0 -239
- package/.github/prompts/opsx-continue.prompt.md +0 -111
- package/.github/prompts/opsx-explore.prompt.md +0 -171
- package/.github/prompts/opsx-ff.prompt.md +0 -91
- package/.github/prompts/opsx-new.prompt.md +0 -66
- package/.github/prompts/opsx-onboard.prompt.md +0 -522
- package/.github/prompts/opsx-sync.prompt.md +0 -131
- package/.github/prompts/opsx-verify.prompt.md +0 -161
- package/.github/skills/openspec-apply-change/SKILL.md +0 -156
- package/.github/skills/openspec-archive-change/SKILL.md +0 -114
- package/.github/skills/openspec-bulk-archive-change/SKILL.md +0 -246
- package/.github/skills/openspec-continue-change/SKILL.md +0 -118
- package/.github/skills/openspec-explore/SKILL.md +0 -290
- package/.github/skills/openspec-ff-change/SKILL.md +0 -101
- package/.github/skills/openspec-new-change/SKILL.md +0 -74
- package/.github/skills/openspec-onboard/SKILL.md +0 -529
- package/.github/skills/openspec-sync-specs/SKILL.md +0 -138
- package/.github/skills/openspec-verify-change/SKILL.md +0 -168
- package/.legacyverignore.example +0 -43
- package/.legacyverrc +0 -7
- package/.opencode/command/opsx-apply.md +0 -149
- package/.opencode/command/opsx-archive.md +0 -154
- package/.opencode/command/opsx-bulk-archive.md +0 -239
- package/.opencode/command/opsx-continue.md +0 -111
- package/.opencode/command/opsx-explore.md +0 -171
- package/.opencode/command/opsx-ff.md +0 -91
- package/.opencode/command/opsx-new.md +0 -66
- package/.opencode/command/opsx-onboard.md +0 -522
- package/.opencode/command/opsx-sync.md +0 -131
- package/.opencode/command/opsx-verify.md +0 -161
- package/.opencode/skills/openspec-apply-change/SKILL.md +0 -156
- package/.opencode/skills/openspec-archive-change/SKILL.md +0 -114
- package/.opencode/skills/openspec-bulk-archive-change/SKILL.md +0 -246
- package/.opencode/skills/openspec-continue-change/SKILL.md +0 -118
- package/.opencode/skills/openspec-explore/SKILL.md +0 -290
- package/.opencode/skills/openspec-ff-change/SKILL.md +0 -101
- package/.opencode/skills/openspec-new-change/SKILL.md +0 -74
- package/.opencode/skills/openspec-onboard/SKILL.md +0 -529
- package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -138
- package/.opencode/skills/openspec-verify-change/SKILL.md +0 -168
- package/legacyver-docs/config.md +0 -30
- package/legacyver-docs/errors.md +0 -74
- package/legacyver-docs/logger.md +0 -83
- package/nul +0 -2
package/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GROQ_API_KEY = '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'
|
package/.env.example
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Legacyver Environment Variables
|
|
2
|
+
# Copy this file to .env and fill in your API keys
|
|
3
|
+
|
|
4
|
+
# Groq API Key (required for groq provider - free tier available at https://console.groq.com)
|
|
5
|
+
GROQ_API_KEY=your_groq_api_key_here
|
|
6
|
+
|
|
7
|
+
# Ollama API URL (optional, defaults to http://localhost:11434)
|
|
8
|
+
# OLLAMA_BASE_URL=http://localhost:11434
|
|
9
|
+
|
|
10
|
+
# OpenRouter API Key (optional, for openrouter provider)
|
|
11
|
+
# OPENROUTER_API_KEY=your_openrouter_key_here
|
|
12
|
+
|
|
13
|
+
# Gemini API Key (optional, for gemini provider)
|
|
14
|
+
# GEMINI_API_KEY=your_gemini_key_here
|
|
15
|
+
|
|
16
|
+
# Kimi API Key (optional, for kimi provider)
|
|
17
|
+
# KIMI_API_KEY=your_kimi_key_here
|
package/.gitattributes
ADDED
|
Binary file
|
package/bin/legacyver.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
require('dotenv').config();
|
|
5
|
+
|
|
4
6
|
const { program } = require('commander');
|
|
5
7
|
const { readFileSync } = require('fs');
|
|
6
8
|
const { join } = require('path');
|
|
@@ -21,11 +23,11 @@ program
|
|
|
21
23
|
.option('--out <dir>', 'Output directory', './legacyver-docs')
|
|
22
24
|
.option('--format <fmt>', 'Output format: markdown | html | json', 'markdown')
|
|
23
25
|
.option('--model <model>', 'LLM model to use')
|
|
24
|
-
.option('--provider <provider>', 'LLM provider:
|
|
26
|
+
.option('--provider <provider>', 'LLM provider: groq | ollama', 'groq')
|
|
25
27
|
.option('--concurrency <n>', 'Concurrent LLM requests (1-10)', '3')
|
|
26
28
|
.option('--dry-run', 'Run AST parsing only, no LLM calls')
|
|
27
29
|
.option('--incremental', 'Only re-analyze changed files')
|
|
28
|
-
.option('--no-confirm', 'Skip cost confirmation prompt')
|
|
30
|
+
.option('--no-confirm', 'Skip cost confirmation prompt')
|
|
29
31
|
.option('--json-summary', 'Output machine-readable JSON summary')
|
|
30
32
|
.option('--max-file-size <kb>', 'Skip files larger than this size in KB', '500')
|
|
31
33
|
.action(analyzeCmd);
|
|
@@ -53,4 +55,18 @@ program
|
|
|
53
55
|
.description('Delete the .legacyver-cache/ directory')
|
|
54
56
|
.action(cacheCmd);
|
|
55
57
|
|
|
58
|
+
// login command
|
|
59
|
+
const loginCmd = require('../src/cli/commands/login');
|
|
60
|
+
program
|
|
61
|
+
.command('login')
|
|
62
|
+
.description('Log in to sync generated docs to the cloud')
|
|
63
|
+
.action(loginCmd);
|
|
64
|
+
|
|
65
|
+
// logout command
|
|
66
|
+
const logoutCmd = require('../src/cli/commands/logout');
|
|
67
|
+
program
|
|
68
|
+
.command('logout')
|
|
69
|
+
.description('Log out and stop syncing docs to the cloud')
|
|
70
|
+
.action(logoutCmd);
|
|
71
|
+
|
|
56
72
|
program.parse(process.argv);
|
|
@@ -1,54 +1,63 @@
|
|
|
1
1
|
## Overview
|
|
2
|
-
The
|
|
2
|
+
The components.tsx file contains a collection of reusable React components and a utility function for formatting currency.
|
|
3
3
|
|
|
4
4
|
## Functions
|
|
5
5
|
### Button
|
|
6
|
-
The
|
|
6
|
+
The Button function is a React component that renders a button element with a specified label, click handler, and variant.
|
|
7
7
|
#### Parameters
|
|
8
8
|
| Name | Type | Description |
|
|
9
9
|
| --- | --- | --- |
|
|
10
|
-
| label | string | The text
|
|
11
|
-
| onClick | () => void | The function
|
|
12
|
-
| disabled | boolean | Whether the button is disabled (optional) |
|
|
13
|
-
| variant | 'primary' | 'secondary' | 'danger' | The style variant of the button (optional) |
|
|
10
|
+
| label | string | The text displayed on the button |
|
|
11
|
+
| onClick | () => void | The function called when the button is clicked |
|
|
12
|
+
| disabled | boolean | Whether the button is disabled (optional, default: false) |
|
|
13
|
+
| variant | 'primary' | 'secondary' | 'danger' | The style variant of the button (optional, default: 'primary') |
|
|
14
14
|
#### Return Value
|
|
15
|
-
|
|
15
|
+
A React button element
|
|
16
16
|
|
|
17
17
|
### UserCard
|
|
18
|
-
The
|
|
18
|
+
The UserCard function is a React component that fetches and displays user data based on a provided user ID.
|
|
19
19
|
#### Parameters
|
|
20
20
|
| Name | Type | Description |
|
|
21
21
|
| --- | --- | --- |
|
|
22
|
-
| userId | number | The ID of the user |
|
|
23
|
-
| onClose | () => void | The function
|
|
22
|
+
| userId | number | The ID of the user to fetch and display |
|
|
23
|
+
| onClose | () => void | The function called when the close button is clicked |
|
|
24
24
|
#### Return Value
|
|
25
|
-
|
|
25
|
+
A React element containing the user's name, email, and a close button, or a loading indicator if the data is not yet available
|
|
26
26
|
|
|
27
27
|
### formatCurrency
|
|
28
|
-
The
|
|
28
|
+
The formatCurrency function formats a given amount as a currency string.
|
|
29
29
|
#### Parameters
|
|
30
30
|
| Name | Type | Description |
|
|
31
31
|
| --- | --- | --- |
|
|
32
|
-
| amount | number | The amount to
|
|
33
|
-
| currency | string | The currency
|
|
32
|
+
| amount | number | The amount to format |
|
|
33
|
+
| currency | string | The currency to use for formatting (optional, default: 'USD') |
|
|
34
34
|
#### Return Value
|
|
35
|
-
|
|
35
|
+
A string representing the formatted amount
|
|
36
36
|
|
|
37
37
|
## Dependencies
|
|
38
38
|
* React
|
|
39
|
-
*
|
|
39
|
+
* useState
|
|
40
|
+
* useEffect
|
|
40
41
|
|
|
41
42
|
## Usage Example
|
|
42
|
-
No clear usage
|
|
43
|
-
```
|
|
43
|
+
No clear usage example is visible in the provided code, but the components can be used as follows:
|
|
44
|
+
```jsx
|
|
44
45
|
import { Button, UserCard, formatCurrency } from './components';
|
|
45
46
|
|
|
46
|
-
const
|
|
47
|
+
const App = () => {
|
|
48
|
+
const handleButtonClick = () => {
|
|
49
|
+
console.log('Button clicked');
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleUserCardClose = () => {
|
|
53
|
+
console.log('User card closed');
|
|
54
|
+
};
|
|
55
|
+
|
|
47
56
|
return (
|
|
48
57
|
<div>
|
|
49
|
-
<Button label="Click me" onClick={
|
|
50
|
-
<UserCard userId={
|
|
51
|
-
<p>Formatted
|
|
58
|
+
<Button label="Click me" onClick={handleButtonClick} />
|
|
59
|
+
<UserCard userId={123} onClose={handleUserCardClose} />
|
|
60
|
+
<p>Formatted amount: {formatCurrency(12345.67)}</p>
|
|
52
61
|
</div>
|
|
53
62
|
);
|
|
54
63
|
};
|
package/legacyver-docs/index.md
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# src — Documentation
|
|
2
2
|
|
|
3
|
-
**Primary language:**
|
|
4
|
-
**Total files:**
|
|
5
|
-
**Analyzed at:** 2026-02-
|
|
3
|
+
**Primary language:** typescript
|
|
4
|
+
**Total files:** 1
|
|
5
|
+
**Analyzed at:** 2026-02-21T18:11:04.560Z
|
|
6
6
|
|
|
7
7
|
## Files
|
|
8
8
|
|
|
9
|
-
- [
|
|
10
|
-
- [config.js](config.md)
|
|
11
|
-
- [logger.js](logger.md)
|
|
9
|
+
- [components.tsx](components.md)
|
|
12
10
|
|
|
13
11
|
## Dependency Graph
|
|
14
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "legacyver",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
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": {
|
|
@@ -32,12 +32,14 @@
|
|
|
32
32
|
"commander": "^11.1.0",
|
|
33
33
|
"conf": "^10.2.0",
|
|
34
34
|
"cosmiconfig": "^9.0.0",
|
|
35
|
+
"dotenv": "^17.3.1",
|
|
35
36
|
"fast-glob": "^3.3.2",
|
|
36
37
|
"ignore": "^5.3.1",
|
|
37
38
|
"marked": "^11.1.1",
|
|
38
39
|
"ora": "^5.4.1",
|
|
39
40
|
"p-limit": "^3.1.0",
|
|
40
41
|
"p-retry": "^4.6.2",
|
|
42
|
+
"pg": "^8.18.0",
|
|
41
43
|
"picocolors": "^1.0.0",
|
|
42
44
|
"tiktoken": "^1.0.15",
|
|
43
45
|
"web-tree-sitter": "^0.22.6"
|
package/src/api/auth.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Pool } = require('pg');
|
|
4
|
+
const dbConfig = require('../db/config');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find or create a user by email.
|
|
8
|
+
* Direct DB approach — no web API dependency.
|
|
9
|
+
* @param {string} email
|
|
10
|
+
* @param {string} username
|
|
11
|
+
* @param {object} [opts] optional overrides for testing
|
|
12
|
+
* @param {object} [opts.pool] pg Pool instance (skips internal pool creation)
|
|
13
|
+
* @returns {Promise<{userId: string, username: string, email: string, isNew: boolean}>}
|
|
14
|
+
*/
|
|
15
|
+
async function loginOrRegister(email, username, opts) {
|
|
16
|
+
const ownPool = !(opts && opts.pool);
|
|
17
|
+
const pool = (opts && opts.pool) || new Pool(dbConfig);
|
|
18
|
+
try {
|
|
19
|
+
// Try to find existing user by email
|
|
20
|
+
const existing = await pool.query(
|
|
21
|
+
'SELECT id, username, email FROM users WHERE email = $1',
|
|
22
|
+
[email]
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (existing.rows.length > 0) {
|
|
26
|
+
const user = existing.rows[0];
|
|
27
|
+
// Update login_status
|
|
28
|
+
await pool.query('UPDATE users SET login_status = true WHERE id = $1', [user.id]);
|
|
29
|
+
return { userId: user.id, username: user.username, email: user.email, isNew: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if username is taken
|
|
33
|
+
const usernameCheck = await pool.query(
|
|
34
|
+
'SELECT id FROM users WHERE username = $1',
|
|
35
|
+
[username]
|
|
36
|
+
);
|
|
37
|
+
if (usernameCheck.rows.length > 0) {
|
|
38
|
+
throw new Error(`Username "${username}" is already taken. Try a different one.`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create new user
|
|
42
|
+
const inserted = await pool.query(
|
|
43
|
+
'INSERT INTO users (username, email, login_status) VALUES ($1, $2, true) RETURNING id, username, email',
|
|
44
|
+
[username, email]
|
|
45
|
+
);
|
|
46
|
+
const newUser = inserted.rows[0];
|
|
47
|
+
return { userId: newUser.id, username: newUser.username, email: newUser.email, isNew: true };
|
|
48
|
+
} finally {
|
|
49
|
+
if (ownPool) await pool.end().catch(() => {});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set login_status to false for the given user.
|
|
55
|
+
* @param {string} userId UUID
|
|
56
|
+
* @param {object} [opts] optional overrides for testing
|
|
57
|
+
* @param {object} [opts.pool] pg Pool instance (skips internal pool creation)
|
|
58
|
+
*/
|
|
59
|
+
async function logoutUser(userId, opts) {
|
|
60
|
+
const ownPool = !(opts && opts.pool);
|
|
61
|
+
const pool = (opts && opts.pool) || new Pool(dbConfig);
|
|
62
|
+
try {
|
|
63
|
+
await pool.query('UPDATE users SET login_status = false WHERE id = $1', [userId]);
|
|
64
|
+
} finally {
|
|
65
|
+
if (ownPool) await pool.end().catch(() => {});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = { loginOrRegister, logoutUser };
|
|
@@ -144,36 +144,45 @@ module.exports = async function analyzeCommand(target, flags) {
|
|
|
144
144
|
provider = createProvider(config);
|
|
145
145
|
} catch (e) {
|
|
146
146
|
if (e.code === 'NO_API_KEY') {
|
|
147
|
-
const providerName = (config.provider || '').toLowerCase();
|
|
147
|
+
const providerName = (config.provider || 'groq').toLowerCase();
|
|
148
148
|
const isGroq = providerName === 'groq';
|
|
149
149
|
const isGemini = providerName === 'gemini';
|
|
150
|
-
const
|
|
150
|
+
const isKimi = providerName === 'kimi';
|
|
151
|
+
const isOpenRouter = providerName === 'openrouter';
|
|
152
|
+
const label = isKimi ? 'Kimi (Moonshot)' : isGemini ? 'Google Gemini' : isOpenRouter ? 'OpenRouter' : 'Groq';
|
|
151
153
|
console.error(pc.red(`\n No API key found for ${label}.\n`));
|
|
152
154
|
console.error(' To fix, choose one of:\n');
|
|
153
|
-
if (
|
|
155
|
+
if (isKimi) {
|
|
154
156
|
console.error(pc.cyan(' 1. Run the setup wizard:'));
|
|
155
157
|
console.error(' legacyver init\n');
|
|
156
158
|
console.error(pc.cyan(' 2. Set an environment variable:'));
|
|
157
|
-
console.error(' export
|
|
158
|
-
console.error(' Get a
|
|
159
|
+
console.error(' export MOONSHOT_API_KEY=your_key_here\n');
|
|
160
|
+
console.error(' Get a key at: https://platform.moonshot.cn/console/api-keys\n');
|
|
159
161
|
} else if (isGemini) {
|
|
160
162
|
console.error(pc.cyan(' 1. Run the setup wizard:'));
|
|
161
163
|
console.error(' legacyver init\n');
|
|
162
164
|
console.error(pc.cyan(' 2. Set an environment variable:'));
|
|
163
165
|
console.error(' export GEMINI_API_KEY=your_key_here\n');
|
|
164
166
|
console.error(' Get a free key at: https://aistudio.google.com/apikey\n');
|
|
165
|
-
} else {
|
|
167
|
+
} else if (isOpenRouter) {
|
|
166
168
|
console.error(pc.cyan(' 1. Run the setup wizard:'));
|
|
167
169
|
console.error(' legacyver init\n');
|
|
168
170
|
console.error(pc.cyan(' 2. Set an environment variable:'));
|
|
169
171
|
console.error(' export OPENROUTER_API_KEY=your_key_here\n');
|
|
172
|
+
console.error(' Get a free OpenRouter key at: https://openrouter.ai/keys\n');
|
|
173
|
+
} else {
|
|
174
|
+
// Default: Groq
|
|
175
|
+
console.error(pc.cyan(' 1. Run the setup wizard:'));
|
|
176
|
+
console.error(' legacyver init\n');
|
|
177
|
+
console.error(pc.cyan(' 2. Set an environment variable:'));
|
|
178
|
+
console.error(' export GROQ_API_KEY=your_key_here\n');
|
|
179
|
+
console.error(' Get a free Groq key at: https://console.groq.com/keys\n');
|
|
170
180
|
console.error(pc.cyan(' 3. Use Google Gemini instead (free, 15 req/min):'));
|
|
171
181
|
console.error(' legacyver analyze --provider gemini\n');
|
|
172
|
-
console.error(pc.cyan(' 4. Use
|
|
173
|
-
console.error(' legacyver analyze --provider
|
|
182
|
+
console.error(pc.cyan(' 4. Use Kimi (Moonshot) instead (free credits):'));
|
|
183
|
+
console.error(' legacyver analyze --provider kimi\n');
|
|
174
184
|
console.error(pc.cyan(' 5. Use local Ollama instead (no key needed):'));
|
|
175
185
|
console.error(' legacyver analyze --provider ollama\n');
|
|
176
|
-
console.error(' Get a free OpenRouter key at: https://openrouter.ai/keys\n');
|
|
177
186
|
}
|
|
178
187
|
process.exit(1);
|
|
179
188
|
}
|
|
@@ -242,6 +251,28 @@ module.exports = async function analyzeCommand(target, flags) {
|
|
|
242
251
|
cache.autoAddToGitignore(targetDir);
|
|
243
252
|
}
|
|
244
253
|
|
|
254
|
+
// ─── Stage 5: Cloud sync ──────────────────────────────────────────────────
|
|
255
|
+
let cloudResult = { skipped: true };
|
|
256
|
+
try {
|
|
257
|
+
const { pushToDatabase } = require('../../db/index');
|
|
258
|
+
const syncSpinner = createSpinner('Syncing docs to cloud...');
|
|
259
|
+
|
|
260
|
+
// Only show spinner if user is logged in
|
|
261
|
+
const { loadSession } = require('../../utils/config');
|
|
262
|
+
const session = loadSession();
|
|
263
|
+
if (session.userId) {
|
|
264
|
+
syncSpinner.start();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
cloudResult = await pushToDatabase(allFragments, targetDir);
|
|
268
|
+
|
|
269
|
+
if (!cloudResult.skipped) {
|
|
270
|
+
syncSpinner.succeed(`Docs synced to cloud (${cloudResult.pushed} files)`);
|
|
271
|
+
}
|
|
272
|
+
} catch (syncErr) {
|
|
273
|
+
logger.warn('Cloud sync failed: ' + syncErr.message);
|
|
274
|
+
}
|
|
275
|
+
|
|
245
276
|
// ─── Summary ─────────────────────────────────────────────────────────────
|
|
246
277
|
const stats = {
|
|
247
278
|
filesAnalyzed: filesToAnalyze.length,
|
package/src/cli/commands/init.js
CHANGED
|
@@ -24,11 +24,12 @@ module.exports = async function initCommand() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const providerRaw = await ask(rl, `LLM provider [
|
|
28
|
-
const providerChoice = providerRaw.trim() || '
|
|
27
|
+
const providerRaw = await ask(rl, `LLM provider [groq/gemini/kimi/openrouter/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';
|
|
32
|
+
const isKimi = providerChoice === 'kimi';
|
|
32
33
|
|
|
33
34
|
const defaultModel = isOllama
|
|
34
35
|
? 'llama3.2'
|
|
@@ -36,12 +37,14 @@ module.exports = async function initCommand() {
|
|
|
36
37
|
? 'llama-3.3-70b-versatile'
|
|
37
38
|
: isGemini
|
|
38
39
|
? 'gemini-2.0-flash'
|
|
39
|
-
:
|
|
40
|
+
: isKimi
|
|
41
|
+
? 'moonshot-v1-8k'
|
|
42
|
+
: 'meta-llama/llama-3.3-70b-instruct:free';
|
|
40
43
|
|
|
41
44
|
let apiKey = '';
|
|
42
45
|
if (!isOllama) {
|
|
43
|
-
const keyLabel = isGemini ? 'Google Gemini' : isGroq ? 'Groq' : 'OpenRouter';
|
|
44
|
-
const keyHint = isGemini ? 'https://aistudio.google.com/apikey' : isGroq ? 'https://console.groq.com/keys' : 'https://openrouter.ai/keys';
|
|
46
|
+
const keyLabel = isKimi ? 'Kimi (Moonshot)' : isGemini ? 'Google Gemini' : isGroq ? 'Groq' : 'OpenRouter';
|
|
47
|
+
const keyHint = isKimi ? 'https://platform.moonshot.cn/console/api-keys' : isGemini ? 'https://aistudio.google.com/apikey' : isGroq ? 'https://console.groq.com/keys' : 'https://openrouter.ai/keys';
|
|
45
48
|
apiKey = await ask(rl, `${keyLabel} API key (leave blank to set via env var — see ${keyHint}): `);
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -58,7 +61,9 @@ module.exports = async function initCommand() {
|
|
|
58
61
|
};
|
|
59
62
|
|
|
60
63
|
if (apiKey.trim()) {
|
|
61
|
-
if (
|
|
64
|
+
if (isKimi) {
|
|
65
|
+
config.kimiApiKey = apiKey.trim();
|
|
66
|
+
} else if (isGemini) {
|
|
62
67
|
config.geminiApiKey = apiKey.trim();
|
|
63
68
|
} else if (isGroq) {
|
|
64
69
|
config.groqApiKey = apiKey.trim();
|
|
@@ -76,6 +81,8 @@ module.exports = async function initCommand() {
|
|
|
76
81
|
? 'legacyver analyze --provider groq'
|
|
77
82
|
: isGemini
|
|
78
83
|
? 'legacyver analyze --provider gemini'
|
|
79
|
-
:
|
|
84
|
+
: isKimi
|
|
85
|
+
? 'legacyver analyze --provider kimi'
|
|
86
|
+
: 'legacyver analyze';
|
|
80
87
|
console.log(pc.cyan(`\nRun \`${exampleCmd}\` to generate documentation.`));
|
|
81
88
|
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const pc = require('picocolors');
|
|
5
|
+
const { saveSession, loadSession } = require('../../utils/config');
|
|
6
|
+
const { loginOrRegister } = require('../../api/auth');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prompt user for input (visible).
|
|
10
|
+
*/
|
|
11
|
+
function prompt(question) {
|
|
12
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question(question, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = async function loginCommand() {
|
|
22
|
+
const session = loadSession();
|
|
23
|
+
if (session.userId) {
|
|
24
|
+
console.log(pc.yellow(`Already logged in as ${session.username} (${session.email}).`));
|
|
25
|
+
console.log(`Run ${pc.cyan('legacyver logout')} first to switch accounts.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(pc.bold('\nLegacyver Login\n'));
|
|
30
|
+
console.log(pc.dim('Your account is used to sync generated docs to the cloud.'));
|
|
31
|
+
console.log(pc.dim('If you don\'t have an account, one will be created automatically.\n'));
|
|
32
|
+
|
|
33
|
+
const email = await prompt(' Email: ');
|
|
34
|
+
if (!email || !email.includes('@')) {
|
|
35
|
+
console.error(pc.red('Invalid email address.'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const username = await prompt(' Username: ');
|
|
40
|
+
if (!username || username.length < 2) {
|
|
41
|
+
console.error(pc.red('Username must be at least 2 characters.'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const result = await loginOrRegister(email, username);
|
|
47
|
+
saveSession({
|
|
48
|
+
userId: result.userId,
|
|
49
|
+
username: result.username,
|
|
50
|
+
email: result.email,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (result.isNew) {
|
|
54
|
+
console.log(pc.green(`\n Account created! Logged in as ${result.username} (${result.email})`));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(pc.green(`\n Logged in as ${result.username} (${result.email})`));
|
|
57
|
+
}
|
|
58
|
+
console.log(pc.dim(' Generated docs will now sync to the cloud after each analyze run.\n'));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err.message.includes('already taken')) {
|
|
61
|
+
console.error(pc.red(`\n ${err.message}`));
|
|
62
|
+
} else {
|
|
63
|
+
console.error(pc.red(`\n Login failed: ${err.message}`));
|
|
64
|
+
console.error(pc.dim(' Check your internet connection and try again.'));
|
|
65
|
+
}
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const pc = require('picocolors');
|
|
4
|
+
const { loadSession, clearSession } = require('../../utils/config');
|
|
5
|
+
const { logoutUser } = require('../../api/auth');
|
|
6
|
+
|
|
7
|
+
module.exports = async function logoutCommand() {
|
|
8
|
+
const session = loadSession();
|
|
9
|
+
if (!session.userId) {
|
|
10
|
+
console.log('You are not logged in.');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await logoutUser(session.userId);
|
|
16
|
+
} catch {
|
|
17
|
+
// DB update failed — still clear local session
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
clearSession();
|
|
21
|
+
console.log(pc.green('Logged out.'));
|
|
22
|
+
};
|
|
@@ -14,27 +14,45 @@ const RECOMMENDED_MODELS = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
module.exports = async function providersCommand() {
|
|
17
|
-
const { loadConfig } = require('../../utils/config');
|
|
17
|
+
const { loadConfig, loadSession } = require('../../utils/config');
|
|
18
18
|
const config = loadConfig({});
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
console.log('
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
// ─── Legacyver Account ────────────────────────────────────────────────────
|
|
21
|
+
const session = loadSession();
|
|
22
|
+
console.log(pc.bold('\nLegacyver Account'));
|
|
23
|
+
if (session.userId) {
|
|
24
|
+
console.log(` ${pc.green('Logged in')} as ${session.username} (${session.email})`);
|
|
25
|
+
console.log(' Generated docs will sync to the cloud after each analyze run.');
|
|
26
|
+
} else {
|
|
27
|
+
console.log(` ${pc.yellow('Not logged in')} — run ${pc.cyan('legacyver login')} to enable cloud sync`);
|
|
28
|
+
}
|
|
27
29
|
console.log('');
|
|
28
|
-
|
|
29
|
-
console.log('
|
|
30
|
+
|
|
31
|
+
console.log(pc.bold('Legacyver — Supported LLM Providers\n'));
|
|
32
|
+
|
|
33
|
+
console.log(pc.bold('Groq') + pc.green(' [DEFAULT]') + ' (https://groq.com)');
|
|
34
|
+
console.log(' Fastest free LLM inference. 30 req/min, 14,400 req/day. Set GROQ_API_KEY env variable.');
|
|
30
35
|
console.log(' Status: ' + (process.env.GROQ_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
|
|
31
36
|
console.log(' Get a free key at: https://console.groq.com/keys');
|
|
32
37
|
console.log('');
|
|
33
|
-
console.log(pc.bold('Google Gemini') + '
|
|
38
|
+
console.log(pc.bold('Google Gemini') + ' (https://ai.google.dev)');
|
|
34
39
|
console.log(' Free tier: 15 req/min, 1,500 req/day. Set GEMINI_API_KEY env variable.');
|
|
35
40
|
console.log(' Status: ' + (process.env.GEMINI_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
|
|
36
41
|
console.log(' Get a free key at: https://aistudio.google.com/apikey');
|
|
37
42
|
console.log('');
|
|
43
|
+
console.log(pc.bold('Kimi (Moonshot AI)') + ' (https://platform.moonshot.cn)');
|
|
44
|
+
console.log(' Free credits on sign-up. Models: moonshot-v1-8k/32k/128k. Set MOONSHOT_API_KEY env variable.');
|
|
45
|
+
console.log(' Status: ' + (process.env.MOONSHOT_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
|
|
46
|
+
console.log(' Get a key at: https://platform.moonshot.cn/console/api-keys');
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(pc.bold('OpenRouter') + ' (https://openrouter.ai)');
|
|
49
|
+
console.log(' Unified gateway to 200+ models (Claude, GPT-4o, Llama, etc). Set OPENROUTER_API_KEY env variable.');
|
|
50
|
+
console.log(' Status: ' + (process.env.OPENROUTER_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
|
|
51
|
+
console.log(' Get a key at: https://openrouter.ai/keys');
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log(pc.bold('Ollama') + ' (https://ollama.ai)');
|
|
54
|
+
console.log(' Local offline LLM. No API key required. Run `ollama serve` first.');
|
|
55
|
+
console.log('');
|
|
38
56
|
|
|
39
57
|
console.log(pc.bold('Recommended Models (via OpenRouter):'));
|
|
40
58
|
console.log('');
|
package/src/cli/ui.js
CHANGED
|
@@ -80,6 +80,44 @@ function printSummary(stats) {
|
|
|
80
80
|
}
|
|
81
81
|
console.log(` Output: ${stats.outputDir}`);
|
|
82
82
|
console.log('');
|
|
83
|
+
|
|
84
|
+
// Show login tip if user is not logged in
|
|
85
|
+
const { loadSession } = require('../utils/config');
|
|
86
|
+
const session = loadSession();
|
|
87
|
+
if (!session.userId) {
|
|
88
|
+
console.log(pc.dim('─────────────────────────────────────────────────'));
|
|
89
|
+
console.log(pc.cyan(' Sync docs to the cloud:'));
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(` Run ${pc.bold('legacyver login')} to create an account and`);
|
|
92
|
+
console.log(' auto-sync generated docs after every analyze run.');
|
|
93
|
+
console.log(pc.dim('─────────────────────────────────────────────────'));
|
|
94
|
+
console.log('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Show upgrade tip only when user is on the shared/default key (no personal env var set)
|
|
98
|
+
const usingOwnKey = !!(
|
|
99
|
+
process.env.GROQ_API_KEY ||
|
|
100
|
+
process.env.GEMINI_API_KEY ||
|
|
101
|
+
process.env.MOONSHOT_API_KEY ||
|
|
102
|
+
process.env.OPENROUTER_API_KEY
|
|
103
|
+
);
|
|
104
|
+
if (!usingOwnKey) {
|
|
105
|
+
console.log(pc.dim('─────────────────────────────────────────────────'));
|
|
106
|
+
console.log(pc.cyan(' Bring your own API key for better results:'));
|
|
107
|
+
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'));
|
|
111
|
+
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:'));
|
|
117
|
+
console.log(pc.dim(' legacyver analyze --provider openrouter --model anthropic/claude-haiku-3-5'));
|
|
118
|
+
console.log(pc.dim('─────────────────────────────────────────────────'));
|
|
119
|
+
console.log('');
|
|
120
|
+
}
|
|
83
121
|
}
|
|
84
122
|
|
|
85
123
|
module.exports = { createSpinner, createProgressBar, confirmPrompt, printSummary };
|
package/src/db/config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostgreSQL connection config for the Legacyver hackathon database.
|
|
5
|
+
* Uses SSL with self-signed certificate (rejectUnauthorized: false).
|
|
6
|
+
*/
|
|
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
|
+
};
|