@syrin/cli 1.3.2 → 1.4.1
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 +184 -152
- package/dist/cli/commands/config.d.ts +47 -0
- package/dist/cli/commands/config.js +360 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +67 -15
- package/dist/cli/commands/doctor.js +49 -13
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +89 -18
- package/dist/cli/commands/status.d.ts +10 -0
- package/dist/cli/commands/status.js +162 -0
- package/dist/cli/index.js +211 -12
- package/dist/cli/prompts/init-prompt.d.ts +18 -0
- package/dist/cli/prompts/init-prompt.js +159 -99
- package/dist/cli/utils/command-error-handler.js +2 -5
- package/dist/config/env-checker.d.ts +12 -2
- package/dist/config/env-checker.js +88 -38
- package/dist/config/env-templates.d.ts +15 -0
- package/dist/config/env-templates.js +49 -0
- package/dist/config/generator.js +17 -0
- package/dist/config/global-loader.d.ts +50 -0
- package/dist/config/global-loader.js +244 -0
- package/dist/config/loader.d.ts +28 -0
- package/dist/config/loader.js +95 -9
- package/dist/config/merger.d.ts +37 -0
- package/dist/config/merger.js +68 -0
- package/dist/config/schema.d.ts +26 -1
- package/dist/config/schema.js +73 -8
- package/dist/config/types.d.ts +19 -0
- package/dist/config/types.js +26 -1
- package/dist/constants/messages.d.ts +7 -0
- package/dist/constants/messages.js +8 -0
- package/dist/constants/paths.d.ts +6 -0
- package/dist/constants/paths.js +10 -0
- package/dist/events/emitter.js +7 -7
- package/dist/index.js +0 -0
- package/dist/presentation/config-ui.d.ts +34 -0
- package/dist/presentation/config-ui.js +139 -0
- package/dist/presentation/doctor-ui.d.ts +11 -0
- package/dist/presentation/doctor-ui.js +52 -1
- package/dist/presentation/init-ui.d.ts +9 -0
- package/dist/presentation/init-ui.js +33 -0
- package/dist/runtime/analysis/analyser.js +2 -2
- package/dist/runtime/analysis/rules/warnings/w104-generic-description.d.ts +1 -1
- package/dist/runtime/analysis/rules/warnings/w104-generic-description.js +1 -1
- package/dist/runtime/dev/event-mapper.js +19 -3
- package/dist/runtime/dev/session.d.ts +4 -0
- package/dist/runtime/dev/session.js +52 -3
- package/dist/runtime/llm/ollama.js +4 -4
- package/dist/runtime/mcp/client/manager.js +3 -3
- package/dist/runtime/sandbox/executor.js +5 -5
- package/dist/runtime/test/orchestrator.js +4 -4
- package/dist/utils/editor.d.ts +37 -0
- package/dist/utils/editor.js +137 -0
- package/dist/utils/logger.d.ts +24 -6
- package/dist/utils/logger.js +51 -8
- package/package.json +23 -23
- package/dist/runtime/analysis/rules/errors/e001-missing-output-schema.d.ts +0 -22
- package/dist/runtime/analysis/rules/errors/e001-missing-output-schema.js +0 -30
- package/dist/runtime/analysis/rules/errors/e002-underspecified-input.d.ts +0 -24
- package/dist/runtime/analysis/rules/errors/e002-underspecified-input.js +0 -52
- package/dist/runtime/analysis/rules/errors/e003-type-mismatch.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e003-type-mismatch.js +0 -73
- package/dist/runtime/analysis/rules/errors/e004-free-text-propagation.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e004-free-text-propagation.js +0 -47
- package/dist/runtime/analysis/rules/errors/e005-tool-ambiguity.d.ts +0 -25
- package/dist/runtime/analysis/rules/errors/e005-tool-ambiguity.js +0 -73
- package/dist/runtime/analysis/rules/errors/e006-param-not-in-description.d.ts +0 -22
- package/dist/runtime/analysis/rules/errors/e006-param-not-in-description.js +0 -57
- package/dist/runtime/analysis/rules/errors/e007-output-not-guaranteed.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e007-output-not-guaranteed.js +0 -56
- package/dist/runtime/analysis/rules/errors/e008-circular-dependency.d.ts +0 -22
- package/dist/runtime/analysis/rules/errors/e008-circular-dependency.js +0 -84
- package/dist/runtime/analysis/rules/errors/e009-implicit-user-input.d.ts +0 -23
- package/dist/runtime/analysis/rules/errors/e009-implicit-user-input.js +0 -89
- package/dist/runtime/analysis/rules/errors/e010-non-serializable.d.ts +0 -25
- package/dist/runtime/analysis/rules/errors/e010-non-serializable.js +0 -46
- package/dist/runtime/analysis/rules/errors/e011-missing-tool-description.d.ts +0 -24
- package/dist/runtime/analysis/rules/errors/e011-missing-tool-description.js +0 -33
- package/dist/runtime/analysis/rules/errors/e012-side-effect-detected.d.ts +0 -39
- package/dist/runtime/analysis/rules/errors/e012-side-effect-detected.js +0 -40
- package/dist/runtime/analysis/rules/errors/e013-non-deterministic-output.d.ts +0 -37
- package/dist/runtime/analysis/rules/errors/e013-non-deterministic-output.js +0 -34
- package/dist/runtime/analysis/rules/errors/e013-output-explosion.d.ts +0 -39
- package/dist/runtime/analysis/rules/errors/e013-output-explosion.js +0 -36
- package/dist/runtime/analysis/rules/errors/e014-hidden-dependency.d.ts +0 -42
- package/dist/runtime/analysis/rules/errors/e014-hidden-dependency.js +0 -46
- package/dist/runtime/analysis/rules/errors/e014-output-explosion.d.ts +0 -39
- package/dist/runtime/analysis/rules/errors/e014-output-explosion.js +0 -36
- package/dist/runtime/analysis/rules/errors/e015-hidden-dependency.d.ts +0 -42
- package/dist/runtime/analysis/rules/errors/e015-hidden-dependency.js +0 -46
- package/dist/runtime/analysis/rules/errors/e015-unbounded-execution.d.ts +0 -44
- package/dist/runtime/analysis/rules/errors/e015-unbounded-execution.js +0 -66
- package/dist/runtime/analysis/rules/errors/e016-output-validation-failed.d.ts +0 -43
- package/dist/runtime/analysis/rules/errors/e016-output-validation-failed.js +0 -42
- package/dist/runtime/analysis/rules/errors/e016-unbounded-execution.d.ts +0 -44
- package/dist/runtime/analysis/rules/errors/e016-unbounded-execution.js +0 -66
- package/dist/runtime/analysis/rules/errors/e017-input-validation-failed.d.ts +0 -57
- package/dist/runtime/analysis/rules/errors/e017-input-validation-failed.js +0 -80
- package/dist/runtime/analysis/rules/errors/e017-output-validation-failed.d.ts +0 -43
- package/dist/runtime/analysis/rules/errors/e017-output-validation-failed.js +0 -42
- package/dist/runtime/analysis/rules/errors/e018-input-validation-failed.d.ts +0 -57
- package/dist/runtime/analysis/rules/errors/e018-input-validation-failed.js +0 -80
- package/dist/runtime/analysis/rules/errors/e018-tool-execution-failed.d.ts +0 -38
- package/dist/runtime/analysis/rules/errors/e018-tool-execution-failed.js +0 -37
- package/dist/runtime/analysis/rules/errors/e019-tool-execution-failed.d.ts +0 -38
- package/dist/runtime/analysis/rules/errors/e019-tool-execution-failed.js +0 -37
- package/dist/runtime/analysis/rules/errors/e019-unexpected-test-result.d.ts +0 -65
- package/dist/runtime/analysis/rules/errors/e019-unexpected-test-result.js +0 -109
- package/dist/runtime/analysis/rules/errors/e020-unexpected-test-result.d.ts +0 -65
- package/dist/runtime/analysis/rules/errors/e020-unexpected-test-result.js +0 -109
- package/dist/runtime/analysis/rules/warnings/w001-implicit-dependency.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w001-implicit-dependency.js +0 -39
- package/dist/runtime/analysis/rules/warnings/w002-free-text-without-normalization.d.ts +0 -24
- package/dist/runtime/analysis/rules/warnings/w002-free-text-without-normalization.js +0 -40
- package/dist/runtime/analysis/rules/warnings/w003-missing-examples.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w003-missing-examples.js +0 -84
- package/dist/runtime/analysis/rules/warnings/w004-overloaded-responsibility.d.ts +0 -23
- package/dist/runtime/analysis/rules/warnings/w004-overloaded-responsibility.js +0 -96
- package/dist/runtime/analysis/rules/warnings/w005-generic-description.d.ts +0 -53
- package/dist/runtime/analysis/rules/warnings/w005-generic-description.js +0 -108
- package/dist/runtime/analysis/rules/warnings/w006-optional-as-required.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w006-optional-as-required.js +0 -44
- package/dist/runtime/analysis/rules/warnings/w007-broad-output-schema.d.ts +0 -23
- package/dist/runtime/analysis/rules/warnings/w007-broad-output-schema.js +0 -37
- package/dist/runtime/analysis/rules/warnings/w008-multiple-entry-points.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w008-multiple-entry-points.js +0 -97
- package/dist/runtime/analysis/rules/warnings/w009-hidden-side-effects.d.ts +0 -23
- package/dist/runtime/analysis/rules/warnings/w009-hidden-side-effects.js +0 -88
- package/dist/runtime/analysis/rules/warnings/w010-output-not-reusable.d.ts +0 -22
- package/dist/runtime/analysis/rules/warnings/w010-output-not-reusable.js +0 -81
- package/dist/runtime/analysis/rules/warnings/w021-weak-schema.d.ts +0 -40
- package/dist/runtime/analysis/rules/warnings/w021-weak-schema.js +0 -32
- package/dist/runtime/analysis/rules/warnings/w022-high-entropy-output.d.ts +0 -39
- package/dist/runtime/analysis/rules/warnings/w022-high-entropy-output.js +0 -36
- package/dist/runtime/analysis/rules/warnings/w023-unstable-defaults.d.ts +0 -38
- package/dist/runtime/analysis/rules/warnings/w023-unstable-defaults.js +0 -36
- package/dist/runtime/test/dependency-tracker.d.ts +0 -66
- package/dist/runtime/test/dependency-tracker.js +0 -80
- package/dist/runtime/test/formatters.d.ts +0 -18
- package/dist/runtime/test/formatters.js +0 -172
- package/dist/runtime/test/input-generator.d.ts +0 -33
- package/dist/runtime/test/input-generator.js +0 -498
- package/dist/runtime/test/mcp-root-detector.d.ts +0 -31
- package/dist/runtime/test/mcp-root-detector.js +0 -105
- package/dist/runtime/test/retry-tester.d.ts +0 -44
- package/dist/runtime/test/retry-tester.js +0 -103
- package/dist/runtime/test/synthetic-input-generator.d.ts +0 -11
- package/dist/runtime/test/synthetic-input-generator.js +0 -154
- package/dist/runtime/test/test-runner.d.ts +0 -28
- package/dist/runtime/test/test-runner.js +0 -55
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `syrin config` command implementation.
|
|
3
|
+
* Manages both local and global Syrin configurations.
|
|
4
|
+
*/
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import { loadConfigOptional, saveLocalConfig } from '../../config/loader.js';
|
|
9
|
+
import { loadGlobalConfig, saveGlobalConfig, getGlobalConfigPath, globalConfigExists, } from '../../config/global-loader.js';
|
|
10
|
+
import { getGlobalEnvPath } from '../../config/global-loader.js';
|
|
11
|
+
import { Paths } from '../../constants/index.js';
|
|
12
|
+
import { log } from '../../utils/logger.js';
|
|
13
|
+
import { openInEditor } from '../../utils/editor.js';
|
|
14
|
+
import { displayConfigList, displayConfigUpdated, displayEditorOpening, displayEditorClosed, displayDefaultProviderSet, displayProviderRemoved, } from '../../presentation/config-ui.js';
|
|
15
|
+
import { getGlobalEnvTemplate, getLocalEnvTemplate, } from '../../config/env-templates.js';
|
|
16
|
+
import { makeAgentName, makeAPIKey, makeModelName } from '../../types/factories.js';
|
|
17
|
+
/**
|
|
18
|
+
* Detect config context (local or global).
|
|
19
|
+
* @param projectRoot - Project root directory
|
|
20
|
+
* @param options - Command options
|
|
21
|
+
* @returns 'local' | 'global' | null
|
|
22
|
+
*/
|
|
23
|
+
function detectConfigContext(projectRoot, options) {
|
|
24
|
+
// Explicit flags take precedence
|
|
25
|
+
if (options.local)
|
|
26
|
+
return 'local';
|
|
27
|
+
if (options.global)
|
|
28
|
+
return 'global';
|
|
29
|
+
// Auto-detect: local if exists, else global
|
|
30
|
+
const localConfigPath = path.join(projectRoot, Paths.CONFIG_FILE);
|
|
31
|
+
const localExists = fs.existsSync(localConfigPath);
|
|
32
|
+
if (localExists)
|
|
33
|
+
return 'local';
|
|
34
|
+
if (globalConfigExists())
|
|
35
|
+
return 'global';
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get config based on context.
|
|
40
|
+
* @param skipValidation - Skip validation (for editing operations)
|
|
41
|
+
*/
|
|
42
|
+
function getConfig(projectRoot, context, skipValidation = false) {
|
|
43
|
+
if (context === 'local') {
|
|
44
|
+
return loadConfigOptional(projectRoot);
|
|
45
|
+
}
|
|
46
|
+
return loadGlobalConfig(skipValidation);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Save config based on context.
|
|
50
|
+
*/
|
|
51
|
+
function saveConfig(config, context, projectRoot, skipValidation = true) {
|
|
52
|
+
if (context === 'global') {
|
|
53
|
+
if ('transport' in config) {
|
|
54
|
+
throw new Error('Cannot save full config to global. Global config only supports LLM settings.');
|
|
55
|
+
}
|
|
56
|
+
saveGlobalConfig(config, skipValidation);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (!('transport' in config)) {
|
|
60
|
+
throw new Error('Cannot save global config to local. Local config requires transport and other fields.');
|
|
61
|
+
}
|
|
62
|
+
saveLocalConfig(config, projectRoot);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parse a config key path (e.g., "openai.model" -> { provider: "openai", field: "model" }).
|
|
67
|
+
*/
|
|
68
|
+
function parseConfigKey(key) {
|
|
69
|
+
const parts = key.split('.');
|
|
70
|
+
if (parts.length === 1) {
|
|
71
|
+
const field = parts[0];
|
|
72
|
+
if (!field) {
|
|
73
|
+
throw new Error(`Invalid config key: ${key}. Field cannot be empty.`);
|
|
74
|
+
}
|
|
75
|
+
return { field };
|
|
76
|
+
}
|
|
77
|
+
if (parts.length === 2) {
|
|
78
|
+
const provider = parts[0];
|
|
79
|
+
const field = parts[1];
|
|
80
|
+
if (!provider || !field) {
|
|
81
|
+
throw new Error(`Invalid config key: ${key}. Provider and field cannot be empty.`);
|
|
82
|
+
}
|
|
83
|
+
return { provider, field };
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`Invalid config key: ${key}. Use format "field" or "provider.field"`);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Set a config value.
|
|
89
|
+
*/
|
|
90
|
+
function setConfigValue(config, key, value, context) {
|
|
91
|
+
const parsed = parseConfigKey(key);
|
|
92
|
+
// Validate that global config only allows LLM-related fields
|
|
93
|
+
if (context === 'global') {
|
|
94
|
+
const allowedFields = ['agent_name', 'openai', 'claude', 'ollama'];
|
|
95
|
+
const allowedLLMFields = ['api_key', 'model_name', 'model', 'default'];
|
|
96
|
+
if (parsed.provider) {
|
|
97
|
+
// LLM provider field
|
|
98
|
+
if (!allowedFields.includes(parsed.provider)) {
|
|
99
|
+
throw new Error(`Cannot set "${key}" in global config. Global config only supports LLM provider settings.`);
|
|
100
|
+
}
|
|
101
|
+
if (!allowedLLMFields.includes(parsed.field)) {
|
|
102
|
+
throw new Error(`Invalid LLM field: ${parsed.field}. Allowed: api_key, model_name, model, default`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Top-level field
|
|
107
|
+
if (parsed.field !== 'agent_name') {
|
|
108
|
+
throw new Error(`Cannot set "${key}" in global config. Global config only supports "agent_name" and LLM provider settings.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Clone config
|
|
113
|
+
// Note: JSON.parse(JSON.stringify()) strips branded/opaque types (e.g., makeAPIKey, makeModelName)
|
|
114
|
+
// This is intentional - we reapply the factories when setting new values below
|
|
115
|
+
const updated = JSON.parse(JSON.stringify(config));
|
|
116
|
+
if (parsed.provider) {
|
|
117
|
+
// LLM provider field
|
|
118
|
+
if (!updated.llm[parsed.provider]) {
|
|
119
|
+
updated.llm[parsed.provider] = {};
|
|
120
|
+
}
|
|
121
|
+
const fieldMap = {
|
|
122
|
+
api_key: 'API_KEY',
|
|
123
|
+
model_name: 'MODEL_NAME',
|
|
124
|
+
model: 'MODEL_NAME',
|
|
125
|
+
};
|
|
126
|
+
const actualField = fieldMap[parsed.field] || parsed.field.toUpperCase();
|
|
127
|
+
if (parsed.field === 'default') {
|
|
128
|
+
// Set default provider
|
|
129
|
+
Object.keys(updated.llm).forEach(p => {
|
|
130
|
+
updated.llm[p].default = p === parsed.provider;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
updated.llm[parsed.provider][actualField] =
|
|
135
|
+
actualField === 'API_KEY' ? makeAPIKey(value) : makeModelName(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Top-level field
|
|
140
|
+
if (parsed.field === 'agent_name') {
|
|
141
|
+
updated.agent_name = makeAgentName(value);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new Error(`Cannot set top-level field "${parsed.field}" via config command.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return updated;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get a config value.
|
|
151
|
+
*/
|
|
152
|
+
function getConfigValue(config, key) {
|
|
153
|
+
const parsed = parseConfigKey(key);
|
|
154
|
+
if (parsed.provider) {
|
|
155
|
+
// LLM provider field
|
|
156
|
+
const provider = config.llm[parsed.provider];
|
|
157
|
+
if (!provider) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const fieldMap = {
|
|
161
|
+
api_key: 'API_KEY',
|
|
162
|
+
model_name: 'MODEL_NAME',
|
|
163
|
+
model: 'MODEL_NAME',
|
|
164
|
+
};
|
|
165
|
+
const actualField = fieldMap[parsed.field] || parsed.field.toUpperCase();
|
|
166
|
+
if (parsed.field === 'default') {
|
|
167
|
+
return provider.default || false;
|
|
168
|
+
}
|
|
169
|
+
return provider[actualField];
|
|
170
|
+
}
|
|
171
|
+
// Top-level field
|
|
172
|
+
const configRecord = config;
|
|
173
|
+
return configRecord[parsed.field];
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Execute config set command.
|
|
177
|
+
*/
|
|
178
|
+
export async function executeConfigSet(key, value, options = {}, projectRoot = process.cwd()) {
|
|
179
|
+
const context = detectConfigContext(projectRoot, options);
|
|
180
|
+
if (!context) {
|
|
181
|
+
throw new Error('No config found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
182
|
+
}
|
|
183
|
+
// For set operations, skip validation to allow partial provider configs
|
|
184
|
+
const config = getConfig(projectRoot, context, true);
|
|
185
|
+
if (!config) {
|
|
186
|
+
if (context === 'global') {
|
|
187
|
+
throw new Error('No global config found. Run `syrin init --global` to create one.');
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
throw new Error('No local config found. Run `syrin init` to create one, or use `--global` flag for global config.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const updated = setConfigValue(config, key, value, context);
|
|
194
|
+
// Save without strict validation (allow partial provider configs during editing)
|
|
195
|
+
// Validation will happen when config is actually used (in dev mode, etc.)
|
|
196
|
+
saveConfig(updated, context, projectRoot);
|
|
197
|
+
const configPath = context === 'local'
|
|
198
|
+
? path.join(projectRoot, Paths.CONFIG_FILE)
|
|
199
|
+
: getGlobalConfigPath();
|
|
200
|
+
displayConfigUpdated(key, value, context, configPath);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Execute config get command.
|
|
204
|
+
*/
|
|
205
|
+
export async function executeConfigGet(key, options = {}, projectRoot = process.cwd()) {
|
|
206
|
+
const context = detectConfigContext(projectRoot, options);
|
|
207
|
+
if (!context) {
|
|
208
|
+
throw new Error('No config found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
209
|
+
}
|
|
210
|
+
const config = getConfig(projectRoot, context);
|
|
211
|
+
if (!config) {
|
|
212
|
+
if (context === 'global') {
|
|
213
|
+
throw new Error('No global config found. Run `syrin init --global` to create one.');
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
throw new Error('No local config found. Run `syrin init` to create one, or use `--global` flag for global config.');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const value = getConfigValue(config, key);
|
|
220
|
+
if (value === undefined) {
|
|
221
|
+
log.error(`Key "${key}" not found in ${context} config.`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
log.plain(String(value));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Execute config list command.
|
|
228
|
+
*/
|
|
229
|
+
export async function executeConfigList(options = {}, projectRoot = process.cwd()) {
|
|
230
|
+
const context = detectConfigContext(projectRoot, options);
|
|
231
|
+
if (!context) {
|
|
232
|
+
throw new Error('No config found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
233
|
+
}
|
|
234
|
+
const config = getConfig(projectRoot, context);
|
|
235
|
+
if (!config) {
|
|
236
|
+
if (context === 'global') {
|
|
237
|
+
throw new Error('No global config found. Run `syrin init --global` to create one.');
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
throw new Error('No local config found. Run `syrin init` to create one, or use `--global` flag for global config.');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const configPath = context === 'local'
|
|
244
|
+
? path.join(projectRoot, Paths.CONFIG_FILE)
|
|
245
|
+
: getGlobalConfigPath();
|
|
246
|
+
displayConfigList(config, context, configPath);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Execute config show command (same as list for now).
|
|
250
|
+
*/
|
|
251
|
+
export async function executeConfigShow(options = {}, projectRoot = process.cwd()) {
|
|
252
|
+
await executeConfigList(options, projectRoot);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Execute config edit command.
|
|
256
|
+
*/
|
|
257
|
+
export async function executeConfigEdit(options = {}, projectRoot = process.cwd()) {
|
|
258
|
+
const context = detectConfigContext(projectRoot, options);
|
|
259
|
+
if (!context) {
|
|
260
|
+
throw new Error('No config found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
261
|
+
}
|
|
262
|
+
const configPath = context === 'local'
|
|
263
|
+
? path.join(projectRoot, Paths.CONFIG_FILE)
|
|
264
|
+
: getGlobalConfigPath();
|
|
265
|
+
const editorName = os.platform() === 'win32' ? 'notepad' : 'nano';
|
|
266
|
+
displayEditorOpening(context, configPath, editorName);
|
|
267
|
+
await openInEditor(configPath);
|
|
268
|
+
displayEditorClosed('config');
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Execute config edit-env command.
|
|
272
|
+
*/
|
|
273
|
+
export async function executeConfigEditEnv(options = {}, projectRoot = process.cwd()) {
|
|
274
|
+
const context = detectConfigContext(projectRoot, options);
|
|
275
|
+
if (!context) {
|
|
276
|
+
throw new Error('No config found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
277
|
+
}
|
|
278
|
+
const envPath = context === 'local'
|
|
279
|
+
? path.join(projectRoot, Paths.ENV_FILE)
|
|
280
|
+
: getGlobalEnvPath();
|
|
281
|
+
const template = context === 'local' ? getLocalEnvTemplate() : getGlobalEnvTemplate();
|
|
282
|
+
const editorName = os.platform() === 'win32' ? 'notepad' : 'nano';
|
|
283
|
+
displayEditorOpening(context, envPath, editorName);
|
|
284
|
+
await openInEditor(envPath, true, template);
|
|
285
|
+
displayEditorClosed('env');
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Execute config set-default command.
|
|
289
|
+
*/
|
|
290
|
+
export async function executeConfigSetDefault(provider, options = {}, projectRoot = process.cwd()) {
|
|
291
|
+
const context = detectConfigContext(projectRoot, options);
|
|
292
|
+
if (!context) {
|
|
293
|
+
throw new Error('No config found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
294
|
+
}
|
|
295
|
+
const config = getConfig(projectRoot, context);
|
|
296
|
+
if (!config) {
|
|
297
|
+
if (context === 'global') {
|
|
298
|
+
throw new Error('No global config found. Run `syrin init --global` to create one.');
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
throw new Error('No local config found. Run `syrin init` to create one, or use `--global` flag for global config.');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (!config.llm[provider]) {
|
|
305
|
+
throw new Error(`LLM provider "${provider}" not found in ${context} config.`);
|
|
306
|
+
}
|
|
307
|
+
// Clone config to avoid mutating the original
|
|
308
|
+
const updated = JSON.parse(JSON.stringify(config));
|
|
309
|
+
// Set all providers to non-default, then set the specified one to default
|
|
310
|
+
Object.keys(updated.llm).forEach(p => {
|
|
311
|
+
const providerConfig = updated.llm[p];
|
|
312
|
+
if (providerConfig) {
|
|
313
|
+
providerConfig.default = p === provider;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
saveConfig(updated, context, projectRoot);
|
|
317
|
+
displayDefaultProviderSet(provider, context);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Execute config remove command.
|
|
321
|
+
*/
|
|
322
|
+
export async function executeConfigRemove(provider, options = {}, projectRoot = process.cwd()) {
|
|
323
|
+
const context = detectConfigContext(projectRoot, options);
|
|
324
|
+
if (!context) {
|
|
325
|
+
throw new Error('No config found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
326
|
+
}
|
|
327
|
+
const config = getConfig(projectRoot, context);
|
|
328
|
+
if (!config) {
|
|
329
|
+
if (context === 'global') {
|
|
330
|
+
throw new Error('No global config found. Run `syrin init --global` to create one.');
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
throw new Error('No local config found. Run `syrin init` to create one, or use `--global` flag for global config.');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (!config.llm[provider]) {
|
|
337
|
+
throw new Error(`LLM provider "${provider}" not found in ${context} config.`);
|
|
338
|
+
}
|
|
339
|
+
// Check if it's the only provider
|
|
340
|
+
if (Object.keys(config.llm).length === 1) {
|
|
341
|
+
throw new Error('Cannot remove the last LLM provider. At least one provider is required.');
|
|
342
|
+
}
|
|
343
|
+
// Clone config to avoid mutating the original
|
|
344
|
+
const updated = JSON.parse(JSON.stringify(config));
|
|
345
|
+
// Check if it's the default provider
|
|
346
|
+
if (updated.llm[provider]?.default) {
|
|
347
|
+
// Set another provider as default
|
|
348
|
+
const otherProvider = Object.keys(updated.llm).find(p => p !== provider);
|
|
349
|
+
if (otherProvider) {
|
|
350
|
+
const otherProviderConfig = updated.llm[otherProvider];
|
|
351
|
+
if (otherProviderConfig) {
|
|
352
|
+
otherProviderConfig.default = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
delete updated.llm[provider];
|
|
357
|
+
saveConfig(updated, context, projectRoot);
|
|
358
|
+
displayProviderRemoved(provider, context);
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -15,6 +15,12 @@ export interface DevCommandOptions {
|
|
|
15
15
|
eventFile?: string;
|
|
16
16
|
/** Run script to spawn server internally */
|
|
17
17
|
runScript?: boolean;
|
|
18
|
+
/** Transport type override */
|
|
19
|
+
transport?: 'stdio' | 'http';
|
|
20
|
+
/** MCP server URL override (for http transport) */
|
|
21
|
+
url?: string;
|
|
22
|
+
/** Script command override (for stdio transport) */
|
|
23
|
+
script?: string;
|
|
18
24
|
}
|
|
19
25
|
/**
|
|
20
26
|
* Execute the dev command.
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import {
|
|
7
|
+
import { loadConfigWithGlobal, configExists } from '../../config/loader.js';
|
|
8
|
+
import { loadGlobalConfig } from '../../config/global-loader.js';
|
|
8
9
|
import { getLLMProvider } from '../../runtime/llm/factory.js';
|
|
9
10
|
import { createMCPClientManager } from '../../runtime/mcp/client/manager.js';
|
|
10
11
|
import { RuntimeEventEmitter } from '../../events/emitter.js';
|
|
@@ -15,7 +16,7 @@ import { ChatUI } from '../../presentation/dev/chat-ui.js';
|
|
|
15
16
|
import { DevEventMapper } from '../../runtime/dev/event-mapper.js';
|
|
16
17
|
import { ConfigurationError } from '../../utils/errors.js';
|
|
17
18
|
import { handleCommandError } from '../../cli/utils/index.js';
|
|
18
|
-
import {
|
|
19
|
+
import { log } from '../../utils/logger.js';
|
|
19
20
|
import { Messages, Paths, TransportTypes, FileExtensions } from '../../constants/index.js';
|
|
20
21
|
import { makeSessionID } from '../../types/factories.js';
|
|
21
22
|
import { v4 as uuidv4 } from 'uuid';
|
|
@@ -51,8 +52,59 @@ function resolveServerCommand(config, runScript) {
|
|
|
51
52
|
export async function executeDev(options = {}) {
|
|
52
53
|
const projectRoot = options.projectRoot || process.cwd();
|
|
53
54
|
try {
|
|
54
|
-
// Load configuration
|
|
55
|
-
const
|
|
55
|
+
// Load configuration (with global fallback)
|
|
56
|
+
const localExists = configExists(projectRoot);
|
|
57
|
+
const globalExists = loadGlobalConfig() !== null;
|
|
58
|
+
let config;
|
|
59
|
+
let configSource;
|
|
60
|
+
try {
|
|
61
|
+
const configResult = loadConfigWithGlobal(projectRoot, {
|
|
62
|
+
transport: options.transport,
|
|
63
|
+
mcp_url: options.url,
|
|
64
|
+
script: options.script,
|
|
65
|
+
});
|
|
66
|
+
config = configResult.config;
|
|
67
|
+
configSource = configResult.source;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof Error &&
|
|
71
|
+
error.message.includes('No configuration found')) {
|
|
72
|
+
log.blank();
|
|
73
|
+
log.error('❌ No configuration found.');
|
|
74
|
+
log.blank();
|
|
75
|
+
if (!localExists && !globalExists) {
|
|
76
|
+
log.info('💡 Options:');
|
|
77
|
+
log.info(' 1. Create local config: syrin init');
|
|
78
|
+
log.info(' 2. Set up global config: syrin init --global');
|
|
79
|
+
log.blank();
|
|
80
|
+
}
|
|
81
|
+
else if (!localExists) {
|
|
82
|
+
log.info('💡 Using global config requires CLI flags:');
|
|
83
|
+
log.info(' --transport <stdio|http>');
|
|
84
|
+
if (options.transport === 'http') {
|
|
85
|
+
log.info(' --url <url>');
|
|
86
|
+
}
|
|
87
|
+
else if (options.transport === 'stdio') {
|
|
88
|
+
log.info(' --script <command>');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Show both options when transport is not specified
|
|
92
|
+
log.info(' --url <url> (for http transport)');
|
|
93
|
+
log.info(' --script <command> (for stdio transport)');
|
|
94
|
+
}
|
|
95
|
+
log.blank();
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
// Show config source
|
|
102
|
+
if (configSource === 'global') {
|
|
103
|
+
log.blank();
|
|
104
|
+
log.info('ℹ️ Using global configuration from ~/.syrin/syrin.yaml');
|
|
105
|
+
log.info(' (No local syrin.yaml found in current directory)');
|
|
106
|
+
log.blank();
|
|
107
|
+
}
|
|
56
108
|
// Validate transport configuration
|
|
57
109
|
if (config.transport === TransportTypes.HTTP && !config.mcp_url) {
|
|
58
110
|
throw new ConfigurationError(Messages.TRANSPORT_URL_REQUIRED_CONFIG);
|
|
@@ -88,7 +140,7 @@ export async function executeDev(options = {}) {
|
|
|
88
140
|
}
|
|
89
141
|
eventStore = new FileEventStore(eventsDir);
|
|
90
142
|
const eventFilePath = path.join(eventsDir, `${sessionId}${FileExtensions.JSONL}`);
|
|
91
|
-
|
|
143
|
+
log.info(`Events will be saved to: ${eventFilePath}`);
|
|
92
144
|
log.blank();
|
|
93
145
|
log.info(`Events are being saved to: ${eventFilePath}`);
|
|
94
146
|
log.blank();
|
|
@@ -117,7 +169,7 @@ export async function executeDev(options = {}) {
|
|
|
117
169
|
}
|
|
118
170
|
catch (error) {
|
|
119
171
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
120
|
-
|
|
172
|
+
log.error(`Error: ${err.message}`);
|
|
121
173
|
// Even if disconnect fails, try to kill the process directly
|
|
122
174
|
// This is a safety net to ensure cleanup happens
|
|
123
175
|
}
|
|
@@ -133,7 +185,7 @@ export async function executeDev(options = {}) {
|
|
|
133
185
|
// Handle uncaught exceptions
|
|
134
186
|
process.on('uncaughtException', (error) => {
|
|
135
187
|
void (async () => {
|
|
136
|
-
|
|
188
|
+
log.error(`Error: ${error.message}`);
|
|
137
189
|
await cleanup();
|
|
138
190
|
process.exit(1);
|
|
139
191
|
})();
|
|
@@ -141,7 +193,7 @@ export async function executeDev(options = {}) {
|
|
|
141
193
|
// Handle unhandled promise rejections
|
|
142
194
|
process.on('unhandledRejection', (reason) => {
|
|
143
195
|
void (async () => {
|
|
144
|
-
|
|
196
|
+
log.error(`Error: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
145
197
|
await cleanup();
|
|
146
198
|
process.exit(1);
|
|
147
199
|
})();
|
|
@@ -302,14 +354,14 @@ export async function executeDev(options = {}) {
|
|
|
302
354
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
303
355
|
chatUI.addMessage('system', `❌ Error loading tool result: ${errorMessage}`);
|
|
304
356
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
305
|
-
|
|
357
|
+
log.error(`Error: ${err.message}`);
|
|
306
358
|
}
|
|
307
359
|
}
|
|
308
360
|
catch (error) {
|
|
309
361
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
310
362
|
chatUI.addMessage('system', `❌ Error saving JSON: ${errorMessage}`);
|
|
311
363
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
312
|
-
|
|
364
|
+
log.error(`Error: ${err.message}`);
|
|
313
365
|
}
|
|
314
366
|
return;
|
|
315
367
|
}
|
|
@@ -333,7 +385,7 @@ export async function executeDev(options = {}) {
|
|
|
333
385
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
334
386
|
chatUI.addMessage('system', Messages.DEV_ERROR_READING_HISTORY(errorMessage));
|
|
335
387
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
336
|
-
|
|
388
|
+
log.error(`Error: ${err.message}`);
|
|
337
389
|
}
|
|
338
390
|
return;
|
|
339
391
|
}
|
|
@@ -352,7 +404,7 @@ export async function executeDev(options = {}) {
|
|
|
352
404
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
353
405
|
chatUI.addMessage('system', `❌ Error: ${errorMessage}`);
|
|
354
406
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
355
|
-
|
|
407
|
+
log.error(`Error: ${err.message}`);
|
|
356
408
|
}
|
|
357
409
|
},
|
|
358
410
|
onExit: async () => {
|
|
@@ -365,12 +417,12 @@ export async function executeDev(options = {}) {
|
|
|
365
417
|
// Close file event store if used
|
|
366
418
|
if (options.saveEvents && eventStore instanceof FileEventStore) {
|
|
367
419
|
await eventStore.close();
|
|
368
|
-
|
|
420
|
+
log.info('Event store closed');
|
|
369
421
|
}
|
|
370
422
|
}
|
|
371
423
|
catch (error) {
|
|
372
424
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
373
|
-
|
|
425
|
+
log.error(`Error: ${err.message}`);
|
|
374
426
|
}
|
|
375
427
|
},
|
|
376
428
|
});
|
|
@@ -406,7 +458,7 @@ export async function executeDev(options = {}) {
|
|
|
406
458
|
}
|
|
407
459
|
catch (error) {
|
|
408
460
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
409
|
-
|
|
461
|
+
log.error(`Error: ${err.message}`);
|
|
410
462
|
}
|
|
411
463
|
process.exit(0);
|
|
412
464
|
})();
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
* `syrin doctor` command implementation.
|
|
3
3
|
* Validates the Syrin project configuration and setup.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { loadConfigOptional } from '../../config/loader.js';
|
|
6
6
|
import { checkEnvVar, checkCommandExists, extractCommandName, } from '../../config/env-checker.js';
|
|
7
7
|
import { handleCommandError } from '../../cli/utils/index.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { showVersionBanner } from '../../cli/utils/version-banner.js';
|
|
9
|
+
import { Messages, TransportTypes, Defaults, LLMProviders, Paths, } from '../../constants/index.js';
|
|
10
|
+
import { displayDoctorReport, displayGlobalConfigValidation, } from '../../presentation/doctor-ui.js';
|
|
11
|
+
import { loadGlobalConfig, getGlobalConfigPath, getGlobalEnvPath, } from '../../config/global-loader.js';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as os from 'os';
|
|
10
15
|
/**
|
|
11
16
|
* Check transport configuration.
|
|
12
17
|
*/
|
|
@@ -92,7 +97,7 @@ function checkScript(config) {
|
|
|
92
97
|
/**
|
|
93
98
|
* Check LLM provider configuration.
|
|
94
99
|
*/
|
|
95
|
-
function checkLLMProviders(config, projectRoot) {
|
|
100
|
+
function checkLLMProviders(config, projectRoot, isGlobalContext = false) {
|
|
96
101
|
const checks = [];
|
|
97
102
|
for (const [providerName, providerConfig] of Object.entries(config.llm)) {
|
|
98
103
|
if (providerName === LLMProviders.OLLAMA) {
|
|
@@ -106,8 +111,8 @@ function checkLLMProviders(config, projectRoot) {
|
|
|
106
111
|
const modelVar = providerConfig.MODEL_NAME
|
|
107
112
|
? String(providerConfig.MODEL_NAME)
|
|
108
113
|
: '';
|
|
109
|
-
const apiKeyCheck = checkEnvVar(apiKeyVar, projectRoot);
|
|
110
|
-
const modelCheck = checkEnvVar(modelVar, projectRoot);
|
|
114
|
+
const apiKeyCheck = checkEnvVar(apiKeyVar, projectRoot, isGlobalContext);
|
|
115
|
+
const modelCheck = checkEnvVar(modelVar, projectRoot, isGlobalContext);
|
|
111
116
|
checks.push({
|
|
112
117
|
provider: providerName,
|
|
113
118
|
apiKeyCheck: {
|
|
@@ -128,6 +133,7 @@ function checkLLMProviders(config, projectRoot) {
|
|
|
128
133
|
: modelCheck.errorMessage || Messages.ENV_SET_INSTRUCTIONS(modelVar),
|
|
129
134
|
},
|
|
130
135
|
isDefault: providerConfig.default === true,
|
|
136
|
+
envSource: apiKeyCheck.source || modelCheck.source,
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
return checks;
|
|
@@ -135,7 +141,7 @@ function checkLLMProviders(config, projectRoot) {
|
|
|
135
141
|
/**
|
|
136
142
|
* Check local LLM providers.
|
|
137
143
|
*/
|
|
138
|
-
function checkLocalLLMProviders(config, projectRoot) {
|
|
144
|
+
function checkLocalLLMProviders(config, projectRoot, isGlobalContext = false) {
|
|
139
145
|
const checks = [];
|
|
140
146
|
for (const [providerName, providerConfig] of Object.entries(config.llm)) {
|
|
141
147
|
if (providerName === LLMProviders.OLLAMA) {
|
|
@@ -143,7 +149,7 @@ function checkLocalLLMProviders(config, projectRoot) {
|
|
|
143
149
|
const modelVar = providerConfig.MODEL_NAME
|
|
144
150
|
? String(providerConfig.MODEL_NAME)
|
|
145
151
|
: '';
|
|
146
|
-
const modelCheck = checkEnvVar(modelVar, projectRoot);
|
|
152
|
+
const modelCheck = checkEnvVar(modelVar, projectRoot, isGlobalContext);
|
|
147
153
|
checks.push({
|
|
148
154
|
provider: providerName,
|
|
149
155
|
check: {
|
|
@@ -159,12 +165,18 @@ function checkLocalLLMProviders(config, projectRoot) {
|
|
|
159
165
|
/**
|
|
160
166
|
* Generate doctor report.
|
|
161
167
|
*/
|
|
162
|
-
function generateReport(config, projectRoot) {
|
|
168
|
+
function generateReport(config, projectRoot, configSource, configPath) {
|
|
169
|
+
// Call checkLLMProviders once and reuse the result
|
|
170
|
+
const llmChecks = checkLLMProviders(config, projectRoot);
|
|
171
|
+
const envSource = llmChecks[0]?.envSource;
|
|
163
172
|
return {
|
|
164
173
|
config,
|
|
174
|
+
configSource,
|
|
175
|
+
configPath,
|
|
176
|
+
envSource,
|
|
165
177
|
transportCheck: checkTransport(config),
|
|
166
178
|
scriptCheck: checkScript(config),
|
|
167
|
-
llmChecks
|
|
179
|
+
llmChecks,
|
|
168
180
|
localLlmChecks: checkLocalLLMProviders(config, projectRoot),
|
|
169
181
|
};
|
|
170
182
|
}
|
|
@@ -173,11 +185,35 @@ function generateReport(config, projectRoot) {
|
|
|
173
185
|
* @param projectRoot - Project root directory (defaults to current working directory)
|
|
174
186
|
*/
|
|
175
187
|
export async function executeDoctor(projectRoot = process.cwd()) {
|
|
188
|
+
await showVersionBanner();
|
|
176
189
|
try {
|
|
177
|
-
//
|
|
178
|
-
const
|
|
190
|
+
// Try to load local config first
|
|
191
|
+
const localConfig = loadConfigOptional(projectRoot);
|
|
192
|
+
const globalConfig = loadGlobalConfig();
|
|
193
|
+
let config;
|
|
194
|
+
let configSource;
|
|
195
|
+
let configPath;
|
|
196
|
+
if (localConfig) {
|
|
197
|
+
config = localConfig;
|
|
198
|
+
configSource = 'local';
|
|
199
|
+
configPath = path.join(projectRoot, Paths.CONFIG_FILE);
|
|
200
|
+
}
|
|
201
|
+
else if (globalConfig) {
|
|
202
|
+
// Validate global config (including LLM providers)
|
|
203
|
+
const globalEnvPath = getGlobalEnvPath();
|
|
204
|
+
const globalEnvExists = fs.existsSync(globalEnvPath);
|
|
205
|
+
// Run LLM validation for global config (using home directory as project root)
|
|
206
|
+
const globalProjectRoot = os.homedir();
|
|
207
|
+
const llmChecks = checkLLMProviders(globalConfig, globalProjectRoot, true);
|
|
208
|
+
// Display validation results using presentation layer
|
|
209
|
+
displayGlobalConfigValidation(getGlobalConfigPath(), globalEnvPath, globalEnvExists, llmChecks);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
throw new Error('No configuration found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
|
|
214
|
+
}
|
|
179
215
|
// Generate report
|
|
180
|
-
const report = generateReport(config, projectRoot);
|
|
216
|
+
const report = generateReport(config, projectRoot, configSource, configPath);
|
|
181
217
|
// Display report using Ink UI
|
|
182
218
|
await displayDoctorReport(report);
|
|
183
219
|
// Exit with appropriate code
|
|
@@ -7,6 +7,8 @@ export interface InitCommandOptions {
|
|
|
7
7
|
yes?: boolean;
|
|
8
8
|
/** Project root directory (defaults to current working directory) */
|
|
9
9
|
projectRoot?: string;
|
|
10
|
+
/** Create global configuration (LLM providers only) */
|
|
11
|
+
global?: boolean;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Execute the init command.
|