fraim-framework 2.0.80 ā 2.0.82
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/README.md +6 -4
- package/dist/src/cli/commands/add-provider.js +383 -0
- package/dist/src/cli/commands/doctor.js +129 -112
- package/dist/src/cli/commands/init-project.js +66 -19
- package/dist/src/cli/commands/sync.js +2 -2
- package/dist/src/cli/doctor/check-runner.js +196 -0
- package/dist/src/cli/doctor/checks/global-setup-checks.js +221 -0
- package/dist/src/cli/doctor/checks/ide-config-checks.js +229 -0
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +203 -0
- package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
- package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
- package/dist/src/cli/doctor/checks/workflow-checks.js +247 -0
- package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
- package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
- package/dist/src/cli/doctor/types.js +6 -0
- package/dist/src/cli/fraim.js +2 -2
- package/dist/src/cli/setup/mcp-config-generator.js +23 -21
- package/dist/src/cli/utils/remote-sync.js +22 -2
- package/package.json +3 -2
|
@@ -9,11 +9,36 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const os_1 = __importDefault(require("os"));
|
|
11
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
12
13
|
const sync_1 = require("./sync");
|
|
13
14
|
const platform_detection_1 = require("../utils/platform-detection");
|
|
14
15
|
const version_utils_1 = require("../utils/version-utils");
|
|
15
16
|
const ide_detector_1 = require("../setup/ide-detector");
|
|
16
17
|
const codex_local_config_1 = require("../setup/codex-local-config");
|
|
18
|
+
const promptForJiraProjectKey = async (jiraBaseUrl) => {
|
|
19
|
+
console.log(chalk_1.default.blue('\nš« Jira Project Configuration'));
|
|
20
|
+
console.log(chalk_1.default.gray(`Jira instance: ${jiraBaseUrl}`));
|
|
21
|
+
console.log(chalk_1.default.gray('Enter the Jira project key for this repository (e.g., TEAM, PROJ, DEV)\n'));
|
|
22
|
+
const response = await (0, prompts_1.default)({
|
|
23
|
+
type: 'text',
|
|
24
|
+
name: 'projectKey',
|
|
25
|
+
message: 'Jira Project Key:',
|
|
26
|
+
validate: (value) => {
|
|
27
|
+
if (!value || value.trim().length === 0) {
|
|
28
|
+
return 'Project key is required';
|
|
29
|
+
}
|
|
30
|
+
if (!/^[A-Z][A-Z0-9]*$/.test(value.trim())) {
|
|
31
|
+
return 'Project key must start with a letter and contain only uppercase letters and numbers';
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (!response.projectKey) {
|
|
37
|
+
console.log(chalk_1.default.red('\nā Jira project key is required for split mode'));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
return response.projectKey.trim().toUpperCase();
|
|
41
|
+
};
|
|
17
42
|
const checkGlobalSetup = () => {
|
|
18
43
|
const fraimUserDir = process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
19
44
|
const globalConfigPath = path_1.default.join(fraimUserDir, 'config.json');
|
|
@@ -25,7 +50,8 @@ const checkGlobalSetup = () => {
|
|
|
25
50
|
return {
|
|
26
51
|
exists: true,
|
|
27
52
|
mode: config.mode || 'integrated',
|
|
28
|
-
tokens: config.tokens || {}
|
|
53
|
+
tokens: config.tokens || {},
|
|
54
|
+
jiraConfig: config.jiraConfig
|
|
29
55
|
};
|
|
30
56
|
}
|
|
31
57
|
catch {
|
|
@@ -69,25 +95,45 @@ const runInitProject = async () => {
|
|
|
69
95
|
else {
|
|
70
96
|
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
71
97
|
if (detection.provider !== 'unknown' && detection.repository) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
98
|
+
// Determine issue tracking configuration
|
|
99
|
+
let issueTracking;
|
|
100
|
+
// In split mode with Jira configured, use Jira for issue tracking
|
|
101
|
+
if (preferredMode === 'split' && globalSetup.tokens?.jira && globalSetup.jiraConfig?.baseUrl) {
|
|
102
|
+
// Prompt for Jira project key (project-specific)
|
|
103
|
+
const projectKey = process.env.FRAIM_JIRA_PROJECT_KEY ||
|
|
104
|
+
await promptForJiraProjectKey(globalSetup.jiraConfig.baseUrl);
|
|
105
|
+
issueTracking = {
|
|
106
|
+
provider: 'jira',
|
|
107
|
+
baseUrl: globalSetup.jiraConfig.baseUrl,
|
|
108
|
+
projectKey: projectKey,
|
|
109
|
+
email: globalSetup.jiraConfig.email
|
|
110
|
+
};
|
|
111
|
+
console.log(chalk_1.default.blue(` Code Repository: ${detection.provider.toUpperCase()}`));
|
|
112
|
+
console.log(chalk_1.default.blue(` Issue Tracking: JIRA (${projectKey})`));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Integrated mode: use same provider for both code and issues
|
|
116
|
+
issueTracking = detection.provider === 'github'
|
|
79
117
|
? {
|
|
80
|
-
provider: '
|
|
81
|
-
|
|
82
|
-
project: detection.repository.project,
|
|
118
|
+
provider: 'github',
|
|
119
|
+
owner: detection.repository.owner,
|
|
83
120
|
name: detection.repository.name
|
|
84
121
|
}
|
|
85
|
-
:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
: detection.provider === 'ado'
|
|
123
|
+
? {
|
|
124
|
+
provider: 'ado',
|
|
125
|
+
organization: detection.repository.organization,
|
|
126
|
+
project: detection.repository.project,
|
|
127
|
+
name: detection.repository.name
|
|
128
|
+
}
|
|
129
|
+
: {
|
|
130
|
+
provider: 'gitlab',
|
|
131
|
+
namespace: detection.repository.namespace,
|
|
132
|
+
name: detection.repository.name,
|
|
133
|
+
projectPath: detection.repository.projectPath
|
|
134
|
+
};
|
|
135
|
+
console.log(chalk_1.default.blue(` Platform: ${detection.provider.toUpperCase()}`));
|
|
136
|
+
}
|
|
91
137
|
config = {
|
|
92
138
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
93
139
|
project: {
|
|
@@ -97,7 +143,6 @@ const runInitProject = async () => {
|
|
|
97
143
|
issueTracking,
|
|
98
144
|
customizations: {}
|
|
99
145
|
};
|
|
100
|
-
console.log(chalk_1.default.blue(` Platform: ${detection.provider.toUpperCase()}`));
|
|
101
146
|
if (detection.provider === 'github') {
|
|
102
147
|
console.log(chalk_1.default.gray(` Repository: ${detection.repository.owner}/${detection.repository.name}`));
|
|
103
148
|
}
|
|
@@ -132,7 +177,9 @@ const runInitProject = async () => {
|
|
|
132
177
|
console.log(chalk_1.default.green(`Created .fraim/${dir}`));
|
|
133
178
|
}
|
|
134
179
|
});
|
|
135
|
-
|
|
180
|
+
if (!process.env.FRAIM_SKIP_SYNC) {
|
|
181
|
+
await (0, sync_1.runSync)({});
|
|
182
|
+
}
|
|
136
183
|
const codexAvailable = (0, ide_detector_1.detectInstalledIDEs)().some((ide) => ide.configType === 'codex');
|
|
137
184
|
if (codexAvailable) {
|
|
138
185
|
const codexLocalResult = (0, codex_local_config_1.ensureCodexLocalConfig)(projectRoot);
|
|
@@ -88,7 +88,7 @@ const runSync = async (options) => {
|
|
|
88
88
|
skipUpdates: true
|
|
89
89
|
});
|
|
90
90
|
if (result.success) {
|
|
91
|
-
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts,
|
|
91
|
+
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, ${result.coachingSynced} coaching files, and ${result.docsSynced} docs from local server`));
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
console.error(chalk_1.default.red(`ā Local sync failed: ${result.error}`));
|
|
@@ -119,7 +119,7 @@ const runSync = async (options) => {
|
|
|
119
119
|
}
|
|
120
120
|
process.exit(1);
|
|
121
121
|
}
|
|
122
|
-
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts,
|
|
122
|
+
console.log(chalk_1.default.green(`ā
Successfully synced ${result.workflowsSynced} workflows, ${result.scriptsSynced} scripts, ${result.coachingSynced} coaching files, and ${result.docsSynced} docs from remote`));
|
|
123
123
|
// Update version in config.json
|
|
124
124
|
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
125
125
|
if (fs_1.default.existsSync(configPath)) {
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Check runner for FRAIM doctor command
|
|
4
|
+
* Handles parallel execution, timeouts, and error handling
|
|
5
|
+
* Issue #144: Enhanced doctor command
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.runChecks = runChecks;
|
|
9
|
+
const CHECK_TIMEOUT = 2000; // 2 seconds per check
|
|
10
|
+
const TOTAL_TIMEOUT = 10000; // 10 seconds total
|
|
11
|
+
// Simple logger for doctor command (optional, falls back to no-op)
|
|
12
|
+
const logger = {
|
|
13
|
+
debug: (...args) => {
|
|
14
|
+
if (process.env.DEBUG)
|
|
15
|
+
console.debug('[DOCTOR]', ...args);
|
|
16
|
+
},
|
|
17
|
+
info: (...args) => {
|
|
18
|
+
if (process.env.VERBOSE)
|
|
19
|
+
console.log('[DOCTOR]', ...args);
|
|
20
|
+
},
|
|
21
|
+
warn: (...args) => {
|
|
22
|
+
console.warn('[DOCTOR]', ...args);
|
|
23
|
+
},
|
|
24
|
+
error: (...args) => {
|
|
25
|
+
console.error('[DOCTOR]', ...args);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
// Simple metric tracker (no-op for now, can be enhanced later)
|
|
29
|
+
const trackMetric = (name, value) => {
|
|
30
|
+
if (process.env.DEBUG) {
|
|
31
|
+
console.debug(`[METRIC] ${name}: ${value}`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Run a single check with timeout
|
|
36
|
+
*/
|
|
37
|
+
async function runCheckWithTimeout(check, timeout = CHECK_TIMEOUT) {
|
|
38
|
+
const checkStartTime = Date.now();
|
|
39
|
+
try {
|
|
40
|
+
logger.debug(`Running check: ${check.name}`, { category: check.category });
|
|
41
|
+
const result = await Promise.race([
|
|
42
|
+
check.run(),
|
|
43
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout))
|
|
44
|
+
]);
|
|
45
|
+
const checkDuration = Date.now() - checkStartTime;
|
|
46
|
+
logger.debug(`Check completed: ${check.name}`, {
|
|
47
|
+
status: result.status,
|
|
48
|
+
duration: checkDuration
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const checkDuration = Date.now() - checkStartTime;
|
|
54
|
+
if (error.message === 'Timeout') {
|
|
55
|
+
logger.warn(`Check timed out: ${check.name}`, { duration: checkDuration });
|
|
56
|
+
return {
|
|
57
|
+
status: 'warning',
|
|
58
|
+
message: `${check.name} timed out`,
|
|
59
|
+
suggestion: 'Check may be slow or unresponsive'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
logger.error(`Check failed: ${check.name}`, { error: error.message, duration: checkDuration });
|
|
63
|
+
return {
|
|
64
|
+
status: 'error',
|
|
65
|
+
message: `${check.name} failed: ${error.message}`,
|
|
66
|
+
suggestion: 'See error details above',
|
|
67
|
+
details: { error: error.message }
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Filter checks based on command options
|
|
73
|
+
*/
|
|
74
|
+
function filterChecks(checks, options) {
|
|
75
|
+
// If no specific flags, run all checks
|
|
76
|
+
if (!options.testMcp && !options.testConfig && !options.testWorkflows) {
|
|
77
|
+
return checks;
|
|
78
|
+
}
|
|
79
|
+
return checks.filter(check => {
|
|
80
|
+
if (options.testMcp && check.category === 'mcpConnectivity')
|
|
81
|
+
return true;
|
|
82
|
+
if (options.testConfig && (check.category === 'globalSetup' || check.category === 'projectSetup' || check.category === 'ideConfiguration'))
|
|
83
|
+
return true;
|
|
84
|
+
if (options.testWorkflows && check.category === 'workflows')
|
|
85
|
+
return true;
|
|
86
|
+
return false;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Run all checks with parallel execution and timeout
|
|
91
|
+
*/
|
|
92
|
+
async function runChecks(checks, options, version) {
|
|
93
|
+
const startTime = Date.now();
|
|
94
|
+
// Filter checks based on options
|
|
95
|
+
const filteredChecks = filterChecks(checks, options);
|
|
96
|
+
logger.info(`Running ${filteredChecks.length} checks`, {
|
|
97
|
+
total: checks.length,
|
|
98
|
+
filtered: filteredChecks.length,
|
|
99
|
+
options
|
|
100
|
+
});
|
|
101
|
+
// Group checks by category for logging
|
|
102
|
+
const categoryCounts = {};
|
|
103
|
+
for (const check of filteredChecks) {
|
|
104
|
+
categoryCounts[check.category] = (categoryCounts[check.category] || 0) + 1;
|
|
105
|
+
}
|
|
106
|
+
for (const [category, count] of Object.entries(categoryCounts)) {
|
|
107
|
+
logger.info(`Category: ${category}`, { checkCount: count });
|
|
108
|
+
}
|
|
109
|
+
// Run checks in parallel with overall timeout
|
|
110
|
+
const checkPromises = filteredChecks.map(check => runCheckWithTimeout(check).then(result => ({ check, result })));
|
|
111
|
+
let checkResults;
|
|
112
|
+
try {
|
|
113
|
+
checkResults = await Promise.race([
|
|
114
|
+
Promise.all(checkPromises),
|
|
115
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Total timeout exceeded')), TOTAL_TIMEOUT))
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
logger.error('Total timeout exceeded for all checks');
|
|
120
|
+
// If total timeout exceeded, return partial results
|
|
121
|
+
checkResults = [];
|
|
122
|
+
}
|
|
123
|
+
// Group results by category
|
|
124
|
+
const categories = {
|
|
125
|
+
globalSetup: { checks: [] },
|
|
126
|
+
projectSetup: { checks: [] },
|
|
127
|
+
workflows: { checks: [] },
|
|
128
|
+
ideConfiguration: { checks: [] },
|
|
129
|
+
mcpConnectivity: { checks: [] },
|
|
130
|
+
scripts: { checks: [] }
|
|
131
|
+
};
|
|
132
|
+
let passed = 0;
|
|
133
|
+
let warnings = 0;
|
|
134
|
+
let errors = 0;
|
|
135
|
+
for (const { check, result } of checkResults) {
|
|
136
|
+
const categoryResult = {
|
|
137
|
+
name: check.name,
|
|
138
|
+
status: result.status,
|
|
139
|
+
message: result.message,
|
|
140
|
+
suggestion: result.suggestion,
|
|
141
|
+
command: result.command,
|
|
142
|
+
details: result.details,
|
|
143
|
+
latency: result.latency
|
|
144
|
+
};
|
|
145
|
+
categories[check.category].checks.push(categoryResult);
|
|
146
|
+
if (result.status === 'passed')
|
|
147
|
+
passed++;
|
|
148
|
+
else if (result.status === 'warning')
|
|
149
|
+
warnings++;
|
|
150
|
+
else if (result.status === 'error')
|
|
151
|
+
errors++;
|
|
152
|
+
}
|
|
153
|
+
const duration = Date.now() - startTime;
|
|
154
|
+
// Track category durations
|
|
155
|
+
for (const [category, categoryResult] of Object.entries(categories)) {
|
|
156
|
+
if (categoryResult.checks.length > 0) {
|
|
157
|
+
trackMetric(`doctor.category.${category}.duration`, duration);
|
|
158
|
+
trackMetric(`doctor.category.${category}.checks`, categoryResult.checks.length);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Track MCP latencies
|
|
162
|
+
for (const { result } of checkResults) {
|
|
163
|
+
if (result.latency !== undefined) {
|
|
164
|
+
trackMetric('doctor.mcp.latency', result.latency);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
logger.info('All checks completed', {
|
|
168
|
+
duration,
|
|
169
|
+
passed,
|
|
170
|
+
warnings,
|
|
171
|
+
errors,
|
|
172
|
+
total: checkResults.length
|
|
173
|
+
});
|
|
174
|
+
// Generate suggestions from errors and warnings
|
|
175
|
+
const suggestions = checkResults
|
|
176
|
+
.filter(({ result }) => result.status !== 'passed' && result.suggestion)
|
|
177
|
+
.map(({ result }, index) => ({
|
|
178
|
+
priority: result.status === 'error' ? index + 1 : index + 100,
|
|
179
|
+
message: result.suggestion,
|
|
180
|
+
command: result.command,
|
|
181
|
+
reason: result.message
|
|
182
|
+
}))
|
|
183
|
+
.sort((a, b) => a.priority - b.priority);
|
|
184
|
+
return {
|
|
185
|
+
timestamp: new Date().toISOString(),
|
|
186
|
+
version,
|
|
187
|
+
summary: {
|
|
188
|
+
passed,
|
|
189
|
+
warnings,
|
|
190
|
+
errors,
|
|
191
|
+
total: checkResults.length
|
|
192
|
+
},
|
|
193
|
+
categories: categories,
|
|
194
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Global setup checks for FRAIM doctor command
|
|
4
|
+
* Validates global configuration and API keys
|
|
5
|
+
* Issue #144: Enhanced doctor command
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getGlobalSetupChecks = getGlobalSetupChecks;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const GLOBAL_CONFIG_PATH = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
16
|
+
/**
|
|
17
|
+
* Check if global config exists
|
|
18
|
+
*/
|
|
19
|
+
function checkGlobalConfigExists() {
|
|
20
|
+
return {
|
|
21
|
+
name: 'Global config exists',
|
|
22
|
+
category: 'globalSetup',
|
|
23
|
+
critical: true,
|
|
24
|
+
run: async () => {
|
|
25
|
+
if (fs_1.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
26
|
+
return {
|
|
27
|
+
status: 'passed',
|
|
28
|
+
message: 'Global config exists',
|
|
29
|
+
details: { path: GLOBAL_CONFIG_PATH }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
status: 'error',
|
|
34
|
+
message: 'Global config missing',
|
|
35
|
+
suggestion: 'Run fraim setup to create global configuration',
|
|
36
|
+
command: 'fraim setup'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if mode is valid
|
|
43
|
+
*/
|
|
44
|
+
function checkModeValid() {
|
|
45
|
+
return {
|
|
46
|
+
name: 'Mode valid',
|
|
47
|
+
category: 'globalSetup',
|
|
48
|
+
critical: false,
|
|
49
|
+
run: async () => {
|
|
50
|
+
try {
|
|
51
|
+
if (!fs_1.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
52
|
+
return {
|
|
53
|
+
status: 'error',
|
|
54
|
+
message: 'Cannot check mode - global config missing'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const config = JSON.parse(fs_1.default.readFileSync(GLOBAL_CONFIG_PATH, 'utf8'));
|
|
58
|
+
const mode = config.mode || 'conversational';
|
|
59
|
+
const validModes = ['conversational', 'integrated', 'split'];
|
|
60
|
+
if (validModes.includes(mode)) {
|
|
61
|
+
return {
|
|
62
|
+
status: 'passed',
|
|
63
|
+
message: `Mode: ${mode}`,
|
|
64
|
+
details: { mode }
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
status: 'error',
|
|
69
|
+
message: `Invalid mode: ${mode}`,
|
|
70
|
+
suggestion: 'Mode must be conversational, integrated, or split',
|
|
71
|
+
details: { mode, validModes }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
status: 'error',
|
|
77
|
+
message: 'Failed to read mode from config',
|
|
78
|
+
details: { error: error.message }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if API key is configured
|
|
86
|
+
*/
|
|
87
|
+
function checkApiKeyConfigured() {
|
|
88
|
+
return {
|
|
89
|
+
name: 'API key configured',
|
|
90
|
+
category: 'globalSetup',
|
|
91
|
+
critical: false,
|
|
92
|
+
run: async () => {
|
|
93
|
+
try {
|
|
94
|
+
if (!fs_1.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
95
|
+
return {
|
|
96
|
+
status: 'error',
|
|
97
|
+
message: 'Cannot check API key - global config missing'
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const config = JSON.parse(fs_1.default.readFileSync(GLOBAL_CONFIG_PATH, 'utf8'));
|
|
101
|
+
if (config.apiKey) {
|
|
102
|
+
const maskedKey = config.apiKey.substring(0, 10) + '...';
|
|
103
|
+
return {
|
|
104
|
+
status: 'passed',
|
|
105
|
+
message: `API key configured (${maskedKey})`,
|
|
106
|
+
details: { keyPrefix: config.apiKey.substring(0, 10) }
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
status: 'warning',
|
|
111
|
+
message: 'API key not configured',
|
|
112
|
+
suggestion: 'Add apiKey to global config for remote features',
|
|
113
|
+
details: { configPath: GLOBAL_CONFIG_PATH }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
status: 'error',
|
|
119
|
+
message: 'Failed to read API key from config',
|
|
120
|
+
details: { error: error.message }
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check if GitHub token is configured
|
|
128
|
+
*/
|
|
129
|
+
function checkGitHubTokenConfigured() {
|
|
130
|
+
return {
|
|
131
|
+
name: 'GitHub token configured',
|
|
132
|
+
category: 'globalSetup',
|
|
133
|
+
critical: false,
|
|
134
|
+
run: async () => {
|
|
135
|
+
try {
|
|
136
|
+
if (!fs_1.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
137
|
+
return {
|
|
138
|
+
status: 'error',
|
|
139
|
+
message: 'Cannot check GitHub token - global config missing'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const config = JSON.parse(fs_1.default.readFileSync(GLOBAL_CONFIG_PATH, 'utf8'));
|
|
143
|
+
// Check if repository provider is GitHub
|
|
144
|
+
if (config.repository?.provider === 'github') {
|
|
145
|
+
// Token would be in IDE MCP configs, not global config
|
|
146
|
+
return {
|
|
147
|
+
status: 'passed',
|
|
148
|
+
message: 'GitHub provider configured',
|
|
149
|
+
details: { provider: 'github' }
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
status: 'passed',
|
|
154
|
+
message: 'GitHub not configured (not required)',
|
|
155
|
+
details: { provider: config.repository?.provider || 'none' }
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
return {
|
|
160
|
+
status: 'error',
|
|
161
|
+
message: 'Failed to check GitHub configuration',
|
|
162
|
+
details: { error: error.message }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Check if remote URL is configured
|
|
170
|
+
*/
|
|
171
|
+
function checkRemoteUrlConfigured() {
|
|
172
|
+
return {
|
|
173
|
+
name: 'Remote URL configured',
|
|
174
|
+
category: 'globalSetup',
|
|
175
|
+
critical: false,
|
|
176
|
+
run: async () => {
|
|
177
|
+
try {
|
|
178
|
+
if (!fs_1.default.existsSync(GLOBAL_CONFIG_PATH)) {
|
|
179
|
+
return {
|
|
180
|
+
status: 'error',
|
|
181
|
+
message: 'Cannot check remote URL - global config missing'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const config = JSON.parse(fs_1.default.readFileSync(GLOBAL_CONFIG_PATH, 'utf8'));
|
|
185
|
+
if (config.remoteUrl) {
|
|
186
|
+
return {
|
|
187
|
+
status: 'passed',
|
|
188
|
+
message: `Remote URL: ${config.remoteUrl}`,
|
|
189
|
+
details: { remoteUrl: config.remoteUrl }
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
status: 'warning',
|
|
194
|
+
message: 'Remote URL not configured',
|
|
195
|
+
suggestion: 'Add remoteUrl to global config for remote sync',
|
|
196
|
+
command: 'fraim sync',
|
|
197
|
+
details: { configPath: GLOBAL_CONFIG_PATH }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
status: 'error',
|
|
203
|
+
message: 'Failed to read remote URL from config',
|
|
204
|
+
details: { error: error.message }
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get all global setup checks
|
|
212
|
+
*/
|
|
213
|
+
function getGlobalSetupChecks() {
|
|
214
|
+
return [
|
|
215
|
+
checkGlobalConfigExists(),
|
|
216
|
+
checkModeValid(),
|
|
217
|
+
checkApiKeyConfigured(),
|
|
218
|
+
checkGitHubTokenConfigured(),
|
|
219
|
+
checkRemoteUrlConfigured()
|
|
220
|
+
];
|
|
221
|
+
}
|