@syrin/cli 1.3.2 â 1.4.0
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 +36 -0
- 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 +1 -1
- 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
package/README.md
CHANGED
|
@@ -97,6 +97,42 @@ It makes MCP systems understandable and testable.
|
|
|
97
97
|
|
|
98
98
|
---
|
|
99
99
|
|
|
100
|
+
## Global Configuration
|
|
101
|
+
|
|
102
|
+
Syrin supports both **local** (project-specific) and **global** (user-wide) configurations. This allows you to:
|
|
103
|
+
|
|
104
|
+
- Use Syrin from any directory without initializing a project
|
|
105
|
+
- Share LLM API keys across multiple projects
|
|
106
|
+
- Set default agent names and LLM providers globally
|
|
107
|
+
|
|
108
|
+
### Quick Setup
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Set up global configuration
|
|
112
|
+
syrin config setup --global
|
|
113
|
+
|
|
114
|
+
# Set API keys in global .env
|
|
115
|
+
syrin config edit-env --global
|
|
116
|
+
|
|
117
|
+
# Use Syrin from any directory
|
|
118
|
+
syrin dev --exec --transport http --mcp-url http://localhost:8000/mcp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Configuration Management
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# View global config
|
|
125
|
+
syrin config list --global
|
|
126
|
+
|
|
127
|
+
# Set global LLM provider
|
|
128
|
+
syrin config set openai.model "gpt-4-turbo" --global
|
|
129
|
+
|
|
130
|
+
# Set default provider
|
|
131
|
+
syrin config set-default claude --global
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
See the [Configuration Guide](docs/Commands/syrin-config.md) for more details.
|
|
135
|
+
|
|
100
136
|
## Key Capabilities
|
|
101
137
|
|
|
102
138
|
### Static Tool Contract Analysis (`syrin analyse`)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `syrin config` command implementation.
|
|
3
|
+
* Manages both local and global Syrin configurations.
|
|
4
|
+
*/
|
|
5
|
+
export interface ConfigCommandOptions {
|
|
6
|
+
/** Operate on global config */
|
|
7
|
+
global?: boolean;
|
|
8
|
+
/** Operate on local config */
|
|
9
|
+
local?: boolean;
|
|
10
|
+
/** Key to get/set (e.g., "openai.model", "agent_name") */
|
|
11
|
+
key?: string;
|
|
12
|
+
/** Value to set */
|
|
13
|
+
value?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Execute config set command.
|
|
17
|
+
*/
|
|
18
|
+
export declare function executeConfigSet(key: string, value: string, options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Execute config get command.
|
|
21
|
+
*/
|
|
22
|
+
export declare function executeConfigGet(key: string, options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Execute config list command.
|
|
25
|
+
*/
|
|
26
|
+
export declare function executeConfigList(options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Execute config show command (same as list for now).
|
|
29
|
+
*/
|
|
30
|
+
export declare function executeConfigShow(options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Execute config edit command.
|
|
33
|
+
*/
|
|
34
|
+
export declare function executeConfigEdit(options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Execute config edit-env command.
|
|
37
|
+
*/
|
|
38
|
+
export declare function executeConfigEditEnv(options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Execute config set-default command.
|
|
41
|
+
*/
|
|
42
|
+
export declare function executeConfigSetDefault(provider: string, options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Execute config remove command.
|
|
45
|
+
*/
|
|
46
|
+
export declare function executeConfigRemove(provider: string, options?: ConfigCommandOptions, projectRoot?: string): Promise<void>;
|
|
47
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -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 URL override (for http transport) */
|
|
21
|
+
mcpUrl?: 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.mcpUrl,
|
|
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(' --mcp-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(' --mcp-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
|
})();
|