@orchagent/cli 0.2.10 → 0.2.12
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/dist/commands/delete.js +90 -0
- package/dist/commands/doctor.js +27 -0
- package/dist/commands/github.js +543 -0
- package/dist/commands/index.js +6 -0
- package/dist/commands/run.js +10 -3
- package/dist/commands/workspace.js +133 -0
- package/dist/lib/api.js +15 -0
- package/dist/lib/auth-errors.js +27 -0
- package/dist/lib/browser-auth.js +5 -8
- package/dist/lib/doctor/checks/auth.js +115 -0
- package/dist/lib/doctor/checks/config.js +119 -0
- package/dist/lib/doctor/checks/connectivity.js +109 -0
- package/dist/lib/doctor/checks/environment.js +151 -0
- package/dist/lib/doctor/checks/llm.js +108 -0
- package/dist/lib/doctor/index.js +19 -0
- package/dist/lib/doctor/output.js +105 -0
- package/dist/lib/doctor/runner.js +68 -0
- package/dist/lib/doctor/types.js +2 -0
- package/dist/lib/llm-errors.js +79 -0
- package/dist/lib/llm.js +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkNodeVersion = checkNodeVersion;
|
|
7
|
+
exports.checkCliVersion = checkCliVersion;
|
|
8
|
+
exports.checkGitAvailable = checkGitAvailable;
|
|
9
|
+
exports.runEnvironmentChecks = runEnvironmentChecks;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const package_json_1 = __importDefault(require("../../../../package.json"));
|
|
12
|
+
const REQUIRED_NODE_MAJOR = 18;
|
|
13
|
+
/**
|
|
14
|
+
* Check if Node.js version is 18+.
|
|
15
|
+
*/
|
|
16
|
+
async function checkNodeVersion() {
|
|
17
|
+
const version = process.version;
|
|
18
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
19
|
+
if (major >= REQUIRED_NODE_MAJOR) {
|
|
20
|
+
return {
|
|
21
|
+
category: 'environment',
|
|
22
|
+
name: 'node_version',
|
|
23
|
+
status: 'success',
|
|
24
|
+
message: `Node.js ${version} (${REQUIRED_NODE_MAJOR}+ required)`,
|
|
25
|
+
details: { version, required: `${REQUIRED_NODE_MAJOR}.0.0` },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
category: 'environment',
|
|
30
|
+
name: 'node_version',
|
|
31
|
+
status: 'error',
|
|
32
|
+
message: `Node.js ${version} is too old (${REQUIRED_NODE_MAJOR}+ required)`,
|
|
33
|
+
fix: `Install Node.js ${REQUIRED_NODE_MAJOR}+ from https://nodejs.org`,
|
|
34
|
+
details: { version, required: `${REQUIRED_NODE_MAJOR}.0.0` },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if CLI is up to date by comparing with npm registry.
|
|
39
|
+
*/
|
|
40
|
+
async function checkCliVersion() {
|
|
41
|
+
const installedVersion = package_json_1.default.version;
|
|
42
|
+
try {
|
|
43
|
+
// Fetch latest version from npm registry
|
|
44
|
+
const response = await fetch('https://registry.npmjs.org/@orchagent/cli/latest', { signal: AbortSignal.timeout(5000) });
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
return {
|
|
47
|
+
category: 'environment',
|
|
48
|
+
name: 'cli_version',
|
|
49
|
+
status: 'warning',
|
|
50
|
+
message: `CLI v${installedVersion} (could not check for updates)`,
|
|
51
|
+
details: { installed: installedVersion, error: 'npm registry unreachable' },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const data = (await response.json());
|
|
55
|
+
const latestVersion = data.version;
|
|
56
|
+
if (installedVersion === latestVersion) {
|
|
57
|
+
return {
|
|
58
|
+
category: 'environment',
|
|
59
|
+
name: 'cli_version',
|
|
60
|
+
status: 'success',
|
|
61
|
+
message: `CLI v${installedVersion} (up to date)`,
|
|
62
|
+
details: { installed: installedVersion, latest: latestVersion },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Compare versions (simple semver comparison)
|
|
66
|
+
const installedParts = installedVersion.split('.').map(Number);
|
|
67
|
+
const latestParts = latestVersion.split('.').map(Number);
|
|
68
|
+
let isOutdated = false;
|
|
69
|
+
for (let i = 0; i < 3; i++) {
|
|
70
|
+
if ((latestParts[i] || 0) > (installedParts[i] || 0)) {
|
|
71
|
+
isOutdated = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if ((latestParts[i] || 0) < (installedParts[i] || 0)) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (isOutdated) {
|
|
79
|
+
return {
|
|
80
|
+
category: 'environment',
|
|
81
|
+
name: 'cli_version',
|
|
82
|
+
status: 'warning',
|
|
83
|
+
message: `CLI v${installedVersion} (v${latestVersion} available)`,
|
|
84
|
+
fix: 'Run `npm update -g @orchagent/cli` to update',
|
|
85
|
+
details: { installed: installedVersion, latest: latestVersion },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
category: 'environment',
|
|
90
|
+
name: 'cli_version',
|
|
91
|
+
status: 'success',
|
|
92
|
+
message: `CLI v${installedVersion}`,
|
|
93
|
+
details: { installed: installedVersion, latest: latestVersion },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return {
|
|
98
|
+
category: 'environment',
|
|
99
|
+
name: 'cli_version',
|
|
100
|
+
status: 'warning',
|
|
101
|
+
message: `CLI v${installedVersion} (could not check for updates)`,
|
|
102
|
+
details: {
|
|
103
|
+
installed: installedVersion,
|
|
104
|
+
error: err instanceof Error ? err.message : 'unknown error',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if git is available in PATH.
|
|
111
|
+
* Note: Uses execSync with hardcoded command string - no user input, safe from injection.
|
|
112
|
+
*/
|
|
113
|
+
async function checkGitAvailable() {
|
|
114
|
+
try {
|
|
115
|
+
const output = (0, child_process_1.execSync)('git --version', {
|
|
116
|
+
encoding: 'utf-8',
|
|
117
|
+
timeout: 5000,
|
|
118
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
119
|
+
});
|
|
120
|
+
const versionMatch = output.match(/git version (\S+)/);
|
|
121
|
+
const version = versionMatch ? versionMatch[1] : 'unknown';
|
|
122
|
+
return {
|
|
123
|
+
category: 'environment',
|
|
124
|
+
name: 'git_available',
|
|
125
|
+
status: 'success',
|
|
126
|
+
message: `Git available (${version})`,
|
|
127
|
+
details: { version },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return {
|
|
132
|
+
category: 'environment',
|
|
133
|
+
name: 'git_available',
|
|
134
|
+
status: 'warning',
|
|
135
|
+
message: 'Git not found in PATH',
|
|
136
|
+
fix: 'Install git from https://git-scm.com',
|
|
137
|
+
details: { available: false },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Run all environment checks.
|
|
143
|
+
*/
|
|
144
|
+
async function runEnvironmentChecks() {
|
|
145
|
+
const results = await Promise.all([
|
|
146
|
+
checkNodeVersion(),
|
|
147
|
+
checkCliVersion(),
|
|
148
|
+
checkGitAvailable(),
|
|
149
|
+
]);
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkServerLlmKeys = checkServerLlmKeys;
|
|
4
|
+
exports.checkLocalLlmEnvVars = checkLocalLlmEnvVars;
|
|
5
|
+
exports.runLlmChecks = runLlmChecks;
|
|
6
|
+
const config_1 = require("../../config");
|
|
7
|
+
const api_1 = require("../../api");
|
|
8
|
+
// Common LLM provider environment variables
|
|
9
|
+
const LLM_ENV_VARS = [
|
|
10
|
+
{ name: 'OPENAI_API_KEY', provider: 'OpenAI' },
|
|
11
|
+
{ name: 'ANTHROPIC_API_KEY', provider: 'Anthropic' },
|
|
12
|
+
{ name: 'GOOGLE_API_KEY', provider: 'Google' },
|
|
13
|
+
{ name: 'GEMINI_API_KEY', provider: 'Gemini' },
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Check if LLM keys are configured on the server.
|
|
17
|
+
*/
|
|
18
|
+
async function checkServerLlmKeys() {
|
|
19
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
20
|
+
if (!config.apiKey) {
|
|
21
|
+
return {
|
|
22
|
+
category: 'llm',
|
|
23
|
+
name: 'server_llm_keys',
|
|
24
|
+
status: 'warning',
|
|
25
|
+
message: 'Cannot check server LLM keys (not logged in)',
|
|
26
|
+
details: { reason: 'no api key' },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const keys = await (0, api_1.fetchLlmKeys)(config);
|
|
31
|
+
if (keys.length === 0) {
|
|
32
|
+
return {
|
|
33
|
+
category: 'llm',
|
|
34
|
+
name: 'server_llm_keys',
|
|
35
|
+
status: 'warning',
|
|
36
|
+
message: 'No LLM keys configured on server',
|
|
37
|
+
fix: 'Run `orch llm-config` or add keys at orchagent.io/settings',
|
|
38
|
+
details: { count: 0, providers: [] },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const providers = keys.map((k) => k.provider);
|
|
42
|
+
return {
|
|
43
|
+
category: 'llm',
|
|
44
|
+
name: 'server_llm_keys',
|
|
45
|
+
status: 'success',
|
|
46
|
+
message: `Server LLM keys configured (${providers.join(', ')})`,
|
|
47
|
+
details: { count: keys.length, providers },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err instanceof api_1.ApiError && err.status === 401) {
|
|
52
|
+
return {
|
|
53
|
+
category: 'llm',
|
|
54
|
+
name: 'server_llm_keys',
|
|
55
|
+
status: 'warning',
|
|
56
|
+
message: 'Cannot check server LLM keys (auth failed)',
|
|
57
|
+
details: { error: err.message },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
category: 'llm',
|
|
62
|
+
name: 'server_llm_keys',
|
|
63
|
+
status: 'warning',
|
|
64
|
+
message: 'Could not check server LLM keys',
|
|
65
|
+
details: { error: err instanceof Error ? err.message : 'unknown error' },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if common LLM provider API keys are set in environment.
|
|
71
|
+
*/
|
|
72
|
+
async function checkLocalLlmEnvVars() {
|
|
73
|
+
const configuredProviders = [];
|
|
74
|
+
const details = {};
|
|
75
|
+
for (const { name, provider } of LLM_ENV_VARS) {
|
|
76
|
+
const isSet = !!process.env[name];
|
|
77
|
+
details[name] = isSet;
|
|
78
|
+
if (isSet) {
|
|
79
|
+
configuredProviders.push(provider);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (configuredProviders.length === 0) {
|
|
83
|
+
return {
|
|
84
|
+
category: 'llm',
|
|
85
|
+
name: 'local_llm_env',
|
|
86
|
+
status: 'warning',
|
|
87
|
+
message: 'No local LLM API keys found in environment',
|
|
88
|
+
fix: 'Set OPENAI_API_KEY, ANTHROPIC_API_KEY, or similar for local runs',
|
|
89
|
+
details,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Deduplicate (Google and Gemini might both be set)
|
|
93
|
+
const uniqueProviders = [...new Set(configuredProviders)];
|
|
94
|
+
return {
|
|
95
|
+
category: 'llm',
|
|
96
|
+
name: 'local_llm_env',
|
|
97
|
+
status: 'success',
|
|
98
|
+
message: `Local LLM keys found (${uniqueProviders.join(', ')})`,
|
|
99
|
+
details,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Run all LLM configuration checks.
|
|
104
|
+
*/
|
|
105
|
+
async function runLlmChecks() {
|
|
106
|
+
const results = await Promise.all([checkServerLlmKeys(), checkLocalLlmEnvVars()]);
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./runner"), exports);
|
|
19
|
+
__exportStar(require("./output"), exports);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printHumanOutput = printHumanOutput;
|
|
7
|
+
exports.formatJsonOutput = formatJsonOutput;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
// Status symbols
|
|
10
|
+
const SYMBOLS = {
|
|
11
|
+
success: chalk_1.default.green('\u2713'), // checkmark
|
|
12
|
+
warning: chalk_1.default.yellow('\u26a0'), // warning sign
|
|
13
|
+
error: chalk_1.default.red('\u2717'), // X mark
|
|
14
|
+
};
|
|
15
|
+
// Category display names
|
|
16
|
+
const CATEGORY_NAMES = {
|
|
17
|
+
environment: 'Environment',
|
|
18
|
+
configuration: 'Configuration',
|
|
19
|
+
connectivity: 'Connectivity',
|
|
20
|
+
authentication: 'Authentication',
|
|
21
|
+
llm: 'LLM Configuration',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Group check results by category.
|
|
25
|
+
*/
|
|
26
|
+
function groupByCategory(results) {
|
|
27
|
+
const groups = new Map();
|
|
28
|
+
// Define category order
|
|
29
|
+
const categoryOrder = [
|
|
30
|
+
'environment',
|
|
31
|
+
'configuration',
|
|
32
|
+
'connectivity',
|
|
33
|
+
'authentication',
|
|
34
|
+
'llm',
|
|
35
|
+
];
|
|
36
|
+
// Initialize groups in order
|
|
37
|
+
for (const cat of categoryOrder) {
|
|
38
|
+
groups.set(cat, []);
|
|
39
|
+
}
|
|
40
|
+
// Add results to groups
|
|
41
|
+
for (const result of results) {
|
|
42
|
+
const existing = groups.get(result.category) || [];
|
|
43
|
+
existing.push(result);
|
|
44
|
+
groups.set(result.category, existing);
|
|
45
|
+
}
|
|
46
|
+
// Remove empty groups
|
|
47
|
+
for (const [key, value] of groups) {
|
|
48
|
+
if (value.length === 0) {
|
|
49
|
+
groups.delete(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return groups;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Print human-readable output to stdout.
|
|
56
|
+
*/
|
|
57
|
+
function printHumanOutput(results, summary, verbose) {
|
|
58
|
+
// Header
|
|
59
|
+
process.stdout.write('\n');
|
|
60
|
+
process.stdout.write(chalk_1.default.bold('OrchAgent Doctor\n'));
|
|
61
|
+
process.stdout.write('================\n\n');
|
|
62
|
+
// Group and print results
|
|
63
|
+
const groups = groupByCategory(results);
|
|
64
|
+
for (const [category, checks] of groups) {
|
|
65
|
+
const displayName = CATEGORY_NAMES[category] || category;
|
|
66
|
+
process.stdout.write(chalk_1.default.bold(`${displayName}\n`));
|
|
67
|
+
for (const check of checks) {
|
|
68
|
+
const symbol = SYMBOLS[check.status];
|
|
69
|
+
process.stdout.write(` ${symbol} ${check.message}\n`);
|
|
70
|
+
// Show fix suggestion for warnings/errors
|
|
71
|
+
if (check.fix && check.status !== 'success') {
|
|
72
|
+
process.stdout.write(chalk_1.default.dim(` \u2192 ${check.fix}\n`));
|
|
73
|
+
}
|
|
74
|
+
// Show details in verbose mode
|
|
75
|
+
if (verbose && check.details) {
|
|
76
|
+
for (const [key, value] of Object.entries(check.details)) {
|
|
77
|
+
const displayValue = typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
78
|
+
process.stdout.write(chalk_1.default.dim(` ${key}: ${displayValue}\n`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write('\n');
|
|
83
|
+
}
|
|
84
|
+
// Summary line
|
|
85
|
+
const summaryParts = [];
|
|
86
|
+
if (summary.passed > 0) {
|
|
87
|
+
summaryParts.push(chalk_1.default.green(`${summary.passed} passed`));
|
|
88
|
+
}
|
|
89
|
+
if (summary.warnings > 0) {
|
|
90
|
+
summaryParts.push(chalk_1.default.yellow(`${summary.warnings} warning${summary.warnings > 1 ? 's' : ''}`));
|
|
91
|
+
}
|
|
92
|
+
if (summary.errors > 0) {
|
|
93
|
+
summaryParts.push(chalk_1.default.red(`${summary.errors} error${summary.errors > 1 ? 's' : ''}`));
|
|
94
|
+
}
|
|
95
|
+
process.stdout.write(`Summary: ${summaryParts.join(', ')}\n`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Format results as JSON output.
|
|
99
|
+
*/
|
|
100
|
+
function formatJsonOutput(results, summary) {
|
|
101
|
+
return {
|
|
102
|
+
summary,
|
|
103
|
+
checks: results,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAllChecks = runAllChecks;
|
|
4
|
+
exports.calculateSummary = calculateSummary;
|
|
5
|
+
const environment_1 = require("./checks/environment");
|
|
6
|
+
const config_1 = require("./checks/config");
|
|
7
|
+
const auth_1 = require("./checks/auth");
|
|
8
|
+
const connectivity_1 = require("./checks/connectivity");
|
|
9
|
+
const llm_1 = require("./checks/llm");
|
|
10
|
+
/**
|
|
11
|
+
* Run all diagnostic checks in order.
|
|
12
|
+
* Checks are organized by category and run in a logical sequence.
|
|
13
|
+
*/
|
|
14
|
+
async function runAllChecks() {
|
|
15
|
+
const results = [];
|
|
16
|
+
// Environment checks (no dependencies)
|
|
17
|
+
const envResults = await (0, environment_1.runEnvironmentChecks)();
|
|
18
|
+
results.push(...envResults);
|
|
19
|
+
// Config checks (no dependencies)
|
|
20
|
+
const configResults = await (0, config_1.runConfigChecks)();
|
|
21
|
+
results.push(...configResults);
|
|
22
|
+
// Connectivity checks (test gateway before auth)
|
|
23
|
+
const connectivityResults = await (0, connectivity_1.runConnectivityChecks)();
|
|
24
|
+
results.push(...connectivityResults);
|
|
25
|
+
// Check if gateway is reachable before running auth/LLM checks
|
|
26
|
+
const gatewayOk = connectivityResults.some((r) => r.name === 'gateway_reachable' && r.status === 'success');
|
|
27
|
+
if (gatewayOk) {
|
|
28
|
+
// Auth checks (requires gateway)
|
|
29
|
+
const authResults = await (0, auth_1.runAuthChecks)();
|
|
30
|
+
results.push(...authResults);
|
|
31
|
+
// LLM checks (requires auth for server keys)
|
|
32
|
+
const llmResults = await (0, llm_1.runLlmChecks)();
|
|
33
|
+
results.push(...llmResults);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Add placeholder results when gateway is unreachable
|
|
37
|
+
results.push({
|
|
38
|
+
category: 'authentication',
|
|
39
|
+
name: 'api_key_present',
|
|
40
|
+
status: 'warning',
|
|
41
|
+
message: 'Skipped (gateway unreachable)',
|
|
42
|
+
details: { skipped: true, reason: 'gateway unreachable' },
|
|
43
|
+
});
|
|
44
|
+
results.push({
|
|
45
|
+
category: 'llm',
|
|
46
|
+
name: 'server_llm_keys',
|
|
47
|
+
status: 'warning',
|
|
48
|
+
message: 'Skipped (gateway unreachable)',
|
|
49
|
+
details: { skipped: true, reason: 'gateway unreachable' },
|
|
50
|
+
});
|
|
51
|
+
// Still check local LLM env vars since they don't require network
|
|
52
|
+
const localLlmCheck = (await (0, llm_1.runLlmChecks)()).find((r) => r.name === 'local_llm_env');
|
|
53
|
+
if (localLlmCheck) {
|
|
54
|
+
results.push(localLlmCheck);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Calculate summary statistics from check results.
|
|
61
|
+
*/
|
|
62
|
+
function calculateSummary(results) {
|
|
63
|
+
return {
|
|
64
|
+
passed: results.filter((r) => r.status === 'success').length,
|
|
65
|
+
warnings: results.filter((r) => r.status === 'warning').length,
|
|
66
|
+
errors: results.filter((r) => r.status === 'error').length,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseLlmError = parseLlmError;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
// Safe fallback messages by status code
|
|
6
|
+
const FALLBACKS = {
|
|
7
|
+
openai: {
|
|
8
|
+
401: 'Invalid OpenAI API key. Check your OPENAI_API_KEY.',
|
|
9
|
+
403: 'Access denied. Your API key may not have permission for this model.',
|
|
10
|
+
429: 'Rate limit exceeded. Wait a moment and try again.',
|
|
11
|
+
500: 'OpenAI service error. Try again later.',
|
|
12
|
+
502: 'OpenAI is temporarily unavailable.',
|
|
13
|
+
503: 'OpenAI is overloaded. Try again later.',
|
|
14
|
+
},
|
|
15
|
+
anthropic: {
|
|
16
|
+
401: 'Invalid Anthropic API key. Check your ANTHROPIC_API_KEY.',
|
|
17
|
+
403: 'Access denied. Your API key may not have permission for this model.',
|
|
18
|
+
429: 'Rate limit exceeded. Wait a moment and try again.',
|
|
19
|
+
500: 'Anthropic service error. Try again later.',
|
|
20
|
+
502: 'Anthropic is temporarily unavailable.',
|
|
21
|
+
503: 'Anthropic is overloaded. Try again later.',
|
|
22
|
+
},
|
|
23
|
+
gemini: {
|
|
24
|
+
401: 'Invalid Gemini API key. Check your GEMINI_API_KEY.',
|
|
25
|
+
403: 'Access denied. Your API key may not have permission for this model.',
|
|
26
|
+
429: 'Rate limit exceeded. Wait a moment and try again.',
|
|
27
|
+
500: 'Gemini service error. Try again later.',
|
|
28
|
+
502: 'Gemini is temporarily unavailable.',
|
|
29
|
+
503: 'Gemini is overloaded. Try again later.',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const DEFAULT = 'LLM provider error. Check your API key and try again.';
|
|
33
|
+
function isHtml(text) {
|
|
34
|
+
const t = text.trim().toLowerCase();
|
|
35
|
+
return t.startsWith('<!doctype') || t.startsWith('<html');
|
|
36
|
+
}
|
|
37
|
+
function sanitize(msg) {
|
|
38
|
+
if (msg.length > 200)
|
|
39
|
+
msg = msg.slice(0, 200) + '...';
|
|
40
|
+
return msg.replace(/https?:\/\/[^\s]+/g, '[URL]').trim();
|
|
41
|
+
}
|
|
42
|
+
function parseOpenAI(text, status) {
|
|
43
|
+
try {
|
|
44
|
+
const p = JSON.parse(text);
|
|
45
|
+
if (p.error?.message)
|
|
46
|
+
return sanitize(p.error.message);
|
|
47
|
+
}
|
|
48
|
+
catch { }
|
|
49
|
+
return FALLBACKS.openai[status] || DEFAULT;
|
|
50
|
+
}
|
|
51
|
+
function parseAnthropic(text, status) {
|
|
52
|
+
try {
|
|
53
|
+
const p = JSON.parse(text);
|
|
54
|
+
const msg = p.error?.message || p.message;
|
|
55
|
+
if (msg)
|
|
56
|
+
return sanitize(msg);
|
|
57
|
+
}
|
|
58
|
+
catch { }
|
|
59
|
+
return FALLBACKS.anthropic[status] || DEFAULT;
|
|
60
|
+
}
|
|
61
|
+
function parseGemini(text, status) {
|
|
62
|
+
try {
|
|
63
|
+
const p = JSON.parse(text);
|
|
64
|
+
if (p.error?.message)
|
|
65
|
+
return sanitize(p.error.message);
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
return FALLBACKS.gemini[status] || DEFAULT;
|
|
69
|
+
}
|
|
70
|
+
function parseLlmError(provider, text, status) {
|
|
71
|
+
if (isHtml(text)) {
|
|
72
|
+
return new errors_1.CliError(`${provider} error: ${FALLBACKS[provider][status] || DEFAULT}`);
|
|
73
|
+
}
|
|
74
|
+
const msg = provider === 'openai' ? parseOpenAI(text, status)
|
|
75
|
+
: provider === 'anthropic' ? parseAnthropic(text, status)
|
|
76
|
+
: parseGemini(text, status);
|
|
77
|
+
const display = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
78
|
+
return new errors_1.CliError(`${display} API error: ${msg}`);
|
|
79
|
+
}
|
package/dist/lib/llm.js
CHANGED
|
@@ -47,6 +47,7 @@ exports.buildPrompt = buildPrompt;
|
|
|
47
47
|
exports.callLlm = callLlm;
|
|
48
48
|
exports.validateProvider = validateProvider;
|
|
49
49
|
const errors_1 = require("./errors");
|
|
50
|
+
const llm_errors_1 = require("./llm-errors");
|
|
50
51
|
// Environment variable names for each provider
|
|
51
52
|
exports.PROVIDER_ENV_VARS = {
|
|
52
53
|
openai: 'OPENAI_API_KEY',
|
|
@@ -181,7 +182,7 @@ async function callOpenAI(apiKey, model, prompt, outputSchema) {
|
|
|
181
182
|
});
|
|
182
183
|
if (!response.ok) {
|
|
183
184
|
const text = await response.text();
|
|
184
|
-
throw
|
|
185
|
+
throw (0, llm_errors_1.parseLlmError)('openai', text, response.status);
|
|
185
186
|
}
|
|
186
187
|
const data = (await response.json());
|
|
187
188
|
const content = data.choices?.[0]?.message?.content || '';
|
|
@@ -208,7 +209,7 @@ async function callAnthropic(apiKey, model, prompt, _outputSchema) {
|
|
|
208
209
|
});
|
|
209
210
|
if (!response.ok) {
|
|
210
211
|
const text = await response.text();
|
|
211
|
-
throw
|
|
212
|
+
throw (0, llm_errors_1.parseLlmError)('anthropic', text, response.status);
|
|
212
213
|
}
|
|
213
214
|
const data = (await response.json());
|
|
214
215
|
const content = data.content?.[0]?.text || '';
|
|
@@ -230,7 +231,7 @@ async function callGemini(apiKey, model, prompt, _outputSchema) {
|
|
|
230
231
|
});
|
|
231
232
|
if (!response.ok) {
|
|
232
233
|
const text = await response.text();
|
|
233
|
-
throw
|
|
234
|
+
throw (0, llm_errors_1.parseLlmError)('gemini', text, response.status);
|
|
234
235
|
}
|
|
235
236
|
const data = (await response.json());
|
|
236
237
|
const content = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|