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.
Files changed (94) hide show
  1. package/.env +1 -0
  2. package/.env.example +17 -0
  3. package/.gitattributes +0 -0
  4. package/bin/legacyver.js +18 -2
  5. package/legacyver-docs/SUMMARY.md +1 -3
  6. package/legacyver-docs/components.md +31 -22
  7. package/legacyver-docs/index.md +5 -7
  8. package/package.json +3 -1
  9. package/src/api/auth.js +69 -0
  10. package/src/cli/commands/analyze.js +40 -9
  11. package/src/cli/commands/init.js +14 -7
  12. package/src/cli/commands/login.js +68 -0
  13. package/src/cli/commands/logout.js +22 -0
  14. package/src/cli/commands/providers.js +29 -11
  15. package/src/cli/ui.js +38 -0
  16. package/src/db/config.js +18 -0
  17. package/src/db/index.js +145 -0
  18. package/src/llm/free-model.js +12 -3
  19. package/src/llm/index.js +9 -6
  20. package/src/llm/providers/groq.js +4 -2
  21. package/src/llm/providers/kimi.js +86 -0
  22. package/src/llm/validator.js +1 -1
  23. package/src/utils/config.js +28 -2
  24. package/temp_credentials/README.md +61 -0
  25. package/temp_credentials/db-credentials.txt +6 -0
  26. package/temp_credentials/domain.txt +1 -0
  27. package/temp_credentials/kubeconfig.yaml +18 -0
  28. package/temp_credentials/kubernetes-credentials.txt +2 -0
  29. package/.agent/skills/openspec-apply-change/SKILL.md +0 -156
  30. package/.agent/skills/openspec-archive-change/SKILL.md +0 -114
  31. package/.agent/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  32. package/.agent/skills/openspec-continue-change/SKILL.md +0 -118
  33. package/.agent/skills/openspec-explore/SKILL.md +0 -290
  34. package/.agent/skills/openspec-ff-change/SKILL.md +0 -101
  35. package/.agent/skills/openspec-new-change/SKILL.md +0 -74
  36. package/.agent/skills/openspec-onboard/SKILL.md +0 -529
  37. package/.agent/skills/openspec-sync-specs/SKILL.md +0 -138
  38. package/.agent/skills/openspec-verify-change/SKILL.md +0 -168
  39. package/.agent/workflows/opsx-apply.md +0 -149
  40. package/.agent/workflows/opsx-archive.md +0 -154
  41. package/.agent/workflows/opsx-bulk-archive.md +0 -239
  42. package/.agent/workflows/opsx-continue.md +0 -111
  43. package/.agent/workflows/opsx-explore.md +0 -171
  44. package/.agent/workflows/opsx-ff.md +0 -91
  45. package/.agent/workflows/opsx-new.md +0 -66
  46. package/.agent/workflows/opsx-onboard.md +0 -522
  47. package/.agent/workflows/opsx-sync.md +0 -131
  48. package/.agent/workflows/opsx-verify.md +0 -161
  49. package/.github/prompts/opsx-apply.prompt.md +0 -149
  50. package/.github/prompts/opsx-archive.prompt.md +0 -154
  51. package/.github/prompts/opsx-bulk-archive.prompt.md +0 -239
  52. package/.github/prompts/opsx-continue.prompt.md +0 -111
  53. package/.github/prompts/opsx-explore.prompt.md +0 -171
  54. package/.github/prompts/opsx-ff.prompt.md +0 -91
  55. package/.github/prompts/opsx-new.prompt.md +0 -66
  56. package/.github/prompts/opsx-onboard.prompt.md +0 -522
  57. package/.github/prompts/opsx-sync.prompt.md +0 -131
  58. package/.github/prompts/opsx-verify.prompt.md +0 -161
  59. package/.github/skills/openspec-apply-change/SKILL.md +0 -156
  60. package/.github/skills/openspec-archive-change/SKILL.md +0 -114
  61. package/.github/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  62. package/.github/skills/openspec-continue-change/SKILL.md +0 -118
  63. package/.github/skills/openspec-explore/SKILL.md +0 -290
  64. package/.github/skills/openspec-ff-change/SKILL.md +0 -101
  65. package/.github/skills/openspec-new-change/SKILL.md +0 -74
  66. package/.github/skills/openspec-onboard/SKILL.md +0 -529
  67. package/.github/skills/openspec-sync-specs/SKILL.md +0 -138
  68. package/.github/skills/openspec-verify-change/SKILL.md +0 -168
  69. package/.legacyverignore.example +0 -43
  70. package/.legacyverrc +0 -7
  71. package/.opencode/command/opsx-apply.md +0 -149
  72. package/.opencode/command/opsx-archive.md +0 -154
  73. package/.opencode/command/opsx-bulk-archive.md +0 -239
  74. package/.opencode/command/opsx-continue.md +0 -111
  75. package/.opencode/command/opsx-explore.md +0 -171
  76. package/.opencode/command/opsx-ff.md +0 -91
  77. package/.opencode/command/opsx-new.md +0 -66
  78. package/.opencode/command/opsx-onboard.md +0 -522
  79. package/.opencode/command/opsx-sync.md +0 -131
  80. package/.opencode/command/opsx-verify.md +0 -161
  81. package/.opencode/skills/openspec-apply-change/SKILL.md +0 -156
  82. package/.opencode/skills/openspec-archive-change/SKILL.md +0 -114
  83. package/.opencode/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  84. package/.opencode/skills/openspec-continue-change/SKILL.md +0 -118
  85. package/.opencode/skills/openspec-explore/SKILL.md +0 -290
  86. package/.opencode/skills/openspec-ff-change/SKILL.md +0 -101
  87. package/.opencode/skills/openspec-new-change/SKILL.md +0 -74
  88. package/.opencode/skills/openspec-onboard/SKILL.md +0 -529
  89. package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -138
  90. package/.opencode/skills/openspec-verify-change/SKILL.md +0 -168
  91. package/legacyver-docs/config.md +0 -30
  92. package/legacyver-docs/errors.md +0 -74
  93. package/legacyver-docs/logger.md +0 -83
  94. 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: openrouter | ollama', 'openrouter')
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,5 +1,3 @@
1
1
  # Summary
2
2
 
3
- * [errors.js](errors.md)
4
- * [config.js](config.md)
5
- * [logger.js](logger.md)
3
+ * [components.tsx](components.md)
@@ -1,54 +1,63 @@
1
1
  ## Overview
2
- The provided code is a collection of React components and a utility function written in TypeScript. It contains two React components, `Button` and `UserCard`, and a function `formatCurrency` for formatting currency.
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 `Button` component is a React functional component that takes in several props and returns a `button` element.
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 to be displayed on the button |
11
- | onClick | () => void | The function to be called when the button is clicked |
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
- The `Button` component returns a `button` element with the specified props.
15
+ A React button element
16
16
 
17
17
  ### UserCard
18
- The `UserCard` component is a React functional component that takes in several props and returns a user card.
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 to be called when the close button is clicked |
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
- The `UserCard` component returns a `div` element containing the user's information, or a loading message if the data is not available.
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 `formatCurrency` function formats a given amount as a currency string.
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 be formatted |
33
- | currency | string | The currency of the amount (optional, defaults to 'USD') |
32
+ | amount | number | The amount to format |
33
+ | currency | string | The currency to use for formatting (optional, default: 'USD') |
34
34
  #### Return Value
35
- The `formatCurrency` function returns a string representing the formatted currency.
35
+ A string representing the formatted amount
36
36
 
37
37
  ## Dependencies
38
38
  * React
39
- * Intl.NumberFormat (for currency formatting)
39
+ * useState
40
+ * useEffect
40
41
 
41
42
  ## Usage Example
42
- No clear usage pattern is visible in the provided code. However, the components and function can be used as follows:
43
- ```tsx
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 Example = () => {
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={() => console.log('Button clicked')} />
50
- <UserCard userId={1} onClose={() => console.log('User card closed')} />
51
- <p>Formatted currency: {formatCurrency(1000, 'USD')}</p>
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
  };
@@ -1,14 +1,12 @@
1
- # utils — Documentation
1
+ # src — Documentation
2
2
 
3
- **Primary language:** javascript
4
- **Total files:** 3
5
- **Analyzed at:** 2026-02-21T08:58:31.525Z
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
- - [errors.js](errors.md)
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",
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"
@@ -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 label = isGemini ? 'Google Gemini' : isGroq ? 'Groq' : 'OpenRouter';
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 (isGroq) {
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 GROQ_API_KEY=your_key_here\n');
158
- console.error(' Get a free Groq key at: https://console.groq.com/keys\n');
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 Groq instead (fast & free):'));
173
- console.error(' legacyver analyze --provider groq\n');
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,
@@ -24,11 +24,12 @@ module.exports = async function initCommand() {
24
24
  }
25
25
  }
26
26
 
27
- const providerRaw = await ask(rl, `LLM provider [openrouter/gemini/groq/ollama] (default: openrouter): `);
28
- const providerChoice = providerRaw.trim() || 'openrouter';
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
- : 'meta-llama/llama-3.3-70b-instruct:free';
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 (isGemini) {
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
- : 'legacyver analyze';
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
- console.log(pc.bold('\nLegacyver Supported LLM Providers\n'));
21
- console.log(pc.bold('OpenRouter') + ' (https://openrouter.ai)');
22
- console.log(' Unified gateway to 200+ models. Set OPENROUTER_API_KEY env variable.');
23
- console.log(' Status: ' + (process.env.OPENROUTER_API_KEY ? pc.green('API key detected') : pc.yellow('No API key found')));
24
- console.log('');
25
- console.log(pc.bold('Ollama') + ' (https://ollama.ai)');
26
- console.log(' Local offline LLM. No API key required. Run `ollama serve` first.');
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
- console.log(pc.bold('Groq') + ' (https://groq.com)');
29
- console.log(' Fastest free LLM inference. Set GROQ_API_KEY env variable.');
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') + ' (https://ai.google.dev)');
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 };
@@ -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
+ };