@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.
Files changed (150) hide show
  1. package/README.md +36 -0
  2. package/dist/cli/commands/config.d.ts +47 -0
  3. package/dist/cli/commands/config.js +360 -0
  4. package/dist/cli/commands/dev.d.ts +6 -0
  5. package/dist/cli/commands/dev.js +67 -15
  6. package/dist/cli/commands/doctor.js +49 -13
  7. package/dist/cli/commands/init.d.ts +2 -0
  8. package/dist/cli/commands/init.js +89 -18
  9. package/dist/cli/commands/status.d.ts +10 -0
  10. package/dist/cli/commands/status.js +162 -0
  11. package/dist/cli/index.js +211 -12
  12. package/dist/cli/prompts/init-prompt.d.ts +18 -0
  13. package/dist/cli/prompts/init-prompt.js +159 -99
  14. package/dist/cli/utils/command-error-handler.js +2 -5
  15. package/dist/config/env-checker.d.ts +12 -2
  16. package/dist/config/env-checker.js +88 -38
  17. package/dist/config/env-templates.d.ts +15 -0
  18. package/dist/config/env-templates.js +49 -0
  19. package/dist/config/generator.js +17 -0
  20. package/dist/config/global-loader.d.ts +50 -0
  21. package/dist/config/global-loader.js +244 -0
  22. package/dist/config/loader.d.ts +28 -0
  23. package/dist/config/loader.js +95 -9
  24. package/dist/config/merger.d.ts +37 -0
  25. package/dist/config/merger.js +68 -0
  26. package/dist/config/schema.d.ts +26 -1
  27. package/dist/config/schema.js +73 -8
  28. package/dist/config/types.d.ts +19 -0
  29. package/dist/config/types.js +26 -1
  30. package/dist/constants/messages.d.ts +7 -0
  31. package/dist/constants/messages.js +8 -0
  32. package/dist/constants/paths.d.ts +6 -0
  33. package/dist/constants/paths.js +10 -0
  34. package/dist/events/emitter.js +7 -7
  35. package/dist/index.js +0 -0
  36. package/dist/presentation/config-ui.d.ts +34 -0
  37. package/dist/presentation/config-ui.js +139 -0
  38. package/dist/presentation/doctor-ui.d.ts +11 -0
  39. package/dist/presentation/doctor-ui.js +52 -1
  40. package/dist/presentation/init-ui.d.ts +9 -0
  41. package/dist/presentation/init-ui.js +33 -0
  42. package/dist/runtime/analysis/analyser.js +2 -2
  43. package/dist/runtime/analysis/rules/warnings/w104-generic-description.d.ts +1 -1
  44. package/dist/runtime/analysis/rules/warnings/w104-generic-description.js +1 -1
  45. package/dist/runtime/dev/event-mapper.js +19 -3
  46. package/dist/runtime/dev/session.d.ts +4 -0
  47. package/dist/runtime/dev/session.js +52 -3
  48. package/dist/runtime/llm/ollama.js +4 -4
  49. package/dist/runtime/mcp/client/manager.js +3 -3
  50. package/dist/runtime/sandbox/executor.js +5 -5
  51. package/dist/runtime/test/orchestrator.js +4 -4
  52. package/dist/utils/editor.d.ts +37 -0
  53. package/dist/utils/editor.js +137 -0
  54. package/dist/utils/logger.d.ts +24 -6
  55. package/dist/utils/logger.js +51 -8
  56. package/package.json +1 -1
  57. package/dist/runtime/analysis/rules/errors/e001-missing-output-schema.d.ts +0 -22
  58. package/dist/runtime/analysis/rules/errors/e001-missing-output-schema.js +0 -30
  59. package/dist/runtime/analysis/rules/errors/e002-underspecified-input.d.ts +0 -24
  60. package/dist/runtime/analysis/rules/errors/e002-underspecified-input.js +0 -52
  61. package/dist/runtime/analysis/rules/errors/e003-type-mismatch.d.ts +0 -23
  62. package/dist/runtime/analysis/rules/errors/e003-type-mismatch.js +0 -73
  63. package/dist/runtime/analysis/rules/errors/e004-free-text-propagation.d.ts +0 -23
  64. package/dist/runtime/analysis/rules/errors/e004-free-text-propagation.js +0 -47
  65. package/dist/runtime/analysis/rules/errors/e005-tool-ambiguity.d.ts +0 -25
  66. package/dist/runtime/analysis/rules/errors/e005-tool-ambiguity.js +0 -73
  67. package/dist/runtime/analysis/rules/errors/e006-param-not-in-description.d.ts +0 -22
  68. package/dist/runtime/analysis/rules/errors/e006-param-not-in-description.js +0 -57
  69. package/dist/runtime/analysis/rules/errors/e007-output-not-guaranteed.d.ts +0 -23
  70. package/dist/runtime/analysis/rules/errors/e007-output-not-guaranteed.js +0 -56
  71. package/dist/runtime/analysis/rules/errors/e008-circular-dependency.d.ts +0 -22
  72. package/dist/runtime/analysis/rules/errors/e008-circular-dependency.js +0 -84
  73. package/dist/runtime/analysis/rules/errors/e009-implicit-user-input.d.ts +0 -23
  74. package/dist/runtime/analysis/rules/errors/e009-implicit-user-input.js +0 -89
  75. package/dist/runtime/analysis/rules/errors/e010-non-serializable.d.ts +0 -25
  76. package/dist/runtime/analysis/rules/errors/e010-non-serializable.js +0 -46
  77. package/dist/runtime/analysis/rules/errors/e011-missing-tool-description.d.ts +0 -24
  78. package/dist/runtime/analysis/rules/errors/e011-missing-tool-description.js +0 -33
  79. package/dist/runtime/analysis/rules/errors/e012-side-effect-detected.d.ts +0 -39
  80. package/dist/runtime/analysis/rules/errors/e012-side-effect-detected.js +0 -40
  81. package/dist/runtime/analysis/rules/errors/e013-non-deterministic-output.d.ts +0 -37
  82. package/dist/runtime/analysis/rules/errors/e013-non-deterministic-output.js +0 -34
  83. package/dist/runtime/analysis/rules/errors/e013-output-explosion.d.ts +0 -39
  84. package/dist/runtime/analysis/rules/errors/e013-output-explosion.js +0 -36
  85. package/dist/runtime/analysis/rules/errors/e014-hidden-dependency.d.ts +0 -42
  86. package/dist/runtime/analysis/rules/errors/e014-hidden-dependency.js +0 -46
  87. package/dist/runtime/analysis/rules/errors/e014-output-explosion.d.ts +0 -39
  88. package/dist/runtime/analysis/rules/errors/e014-output-explosion.js +0 -36
  89. package/dist/runtime/analysis/rules/errors/e015-hidden-dependency.d.ts +0 -42
  90. package/dist/runtime/analysis/rules/errors/e015-hidden-dependency.js +0 -46
  91. package/dist/runtime/analysis/rules/errors/e015-unbounded-execution.d.ts +0 -44
  92. package/dist/runtime/analysis/rules/errors/e015-unbounded-execution.js +0 -66
  93. package/dist/runtime/analysis/rules/errors/e016-output-validation-failed.d.ts +0 -43
  94. package/dist/runtime/analysis/rules/errors/e016-output-validation-failed.js +0 -42
  95. package/dist/runtime/analysis/rules/errors/e016-unbounded-execution.d.ts +0 -44
  96. package/dist/runtime/analysis/rules/errors/e016-unbounded-execution.js +0 -66
  97. package/dist/runtime/analysis/rules/errors/e017-input-validation-failed.d.ts +0 -57
  98. package/dist/runtime/analysis/rules/errors/e017-input-validation-failed.js +0 -80
  99. package/dist/runtime/analysis/rules/errors/e017-output-validation-failed.d.ts +0 -43
  100. package/dist/runtime/analysis/rules/errors/e017-output-validation-failed.js +0 -42
  101. package/dist/runtime/analysis/rules/errors/e018-input-validation-failed.d.ts +0 -57
  102. package/dist/runtime/analysis/rules/errors/e018-input-validation-failed.js +0 -80
  103. package/dist/runtime/analysis/rules/errors/e018-tool-execution-failed.d.ts +0 -38
  104. package/dist/runtime/analysis/rules/errors/e018-tool-execution-failed.js +0 -37
  105. package/dist/runtime/analysis/rules/errors/e019-tool-execution-failed.d.ts +0 -38
  106. package/dist/runtime/analysis/rules/errors/e019-tool-execution-failed.js +0 -37
  107. package/dist/runtime/analysis/rules/errors/e019-unexpected-test-result.d.ts +0 -65
  108. package/dist/runtime/analysis/rules/errors/e019-unexpected-test-result.js +0 -109
  109. package/dist/runtime/analysis/rules/errors/e020-unexpected-test-result.d.ts +0 -65
  110. package/dist/runtime/analysis/rules/errors/e020-unexpected-test-result.js +0 -109
  111. package/dist/runtime/analysis/rules/warnings/w001-implicit-dependency.d.ts +0 -22
  112. package/dist/runtime/analysis/rules/warnings/w001-implicit-dependency.js +0 -39
  113. package/dist/runtime/analysis/rules/warnings/w002-free-text-without-normalization.d.ts +0 -24
  114. package/dist/runtime/analysis/rules/warnings/w002-free-text-without-normalization.js +0 -40
  115. package/dist/runtime/analysis/rules/warnings/w003-missing-examples.d.ts +0 -22
  116. package/dist/runtime/analysis/rules/warnings/w003-missing-examples.js +0 -84
  117. package/dist/runtime/analysis/rules/warnings/w004-overloaded-responsibility.d.ts +0 -23
  118. package/dist/runtime/analysis/rules/warnings/w004-overloaded-responsibility.js +0 -96
  119. package/dist/runtime/analysis/rules/warnings/w005-generic-description.d.ts +0 -53
  120. package/dist/runtime/analysis/rules/warnings/w005-generic-description.js +0 -108
  121. package/dist/runtime/analysis/rules/warnings/w006-optional-as-required.d.ts +0 -22
  122. package/dist/runtime/analysis/rules/warnings/w006-optional-as-required.js +0 -44
  123. package/dist/runtime/analysis/rules/warnings/w007-broad-output-schema.d.ts +0 -23
  124. package/dist/runtime/analysis/rules/warnings/w007-broad-output-schema.js +0 -37
  125. package/dist/runtime/analysis/rules/warnings/w008-multiple-entry-points.d.ts +0 -22
  126. package/dist/runtime/analysis/rules/warnings/w008-multiple-entry-points.js +0 -97
  127. package/dist/runtime/analysis/rules/warnings/w009-hidden-side-effects.d.ts +0 -23
  128. package/dist/runtime/analysis/rules/warnings/w009-hidden-side-effects.js +0 -88
  129. package/dist/runtime/analysis/rules/warnings/w010-output-not-reusable.d.ts +0 -22
  130. package/dist/runtime/analysis/rules/warnings/w010-output-not-reusable.js +0 -81
  131. package/dist/runtime/analysis/rules/warnings/w021-weak-schema.d.ts +0 -40
  132. package/dist/runtime/analysis/rules/warnings/w021-weak-schema.js +0 -32
  133. package/dist/runtime/analysis/rules/warnings/w022-high-entropy-output.d.ts +0 -39
  134. package/dist/runtime/analysis/rules/warnings/w022-high-entropy-output.js +0 -36
  135. package/dist/runtime/analysis/rules/warnings/w023-unstable-defaults.d.ts +0 -38
  136. package/dist/runtime/analysis/rules/warnings/w023-unstable-defaults.js +0 -36
  137. package/dist/runtime/test/dependency-tracker.d.ts +0 -66
  138. package/dist/runtime/test/dependency-tracker.js +0 -80
  139. package/dist/runtime/test/formatters.d.ts +0 -18
  140. package/dist/runtime/test/formatters.js +0 -172
  141. package/dist/runtime/test/input-generator.d.ts +0 -33
  142. package/dist/runtime/test/input-generator.js +0 -498
  143. package/dist/runtime/test/mcp-root-detector.d.ts +0 -31
  144. package/dist/runtime/test/mcp-root-detector.js +0 -105
  145. package/dist/runtime/test/retry-tester.d.ts +0 -44
  146. package/dist/runtime/test/retry-tester.js +0 -103
  147. package/dist/runtime/test/synthetic-input-generator.d.ts +0 -11
  148. package/dist/runtime/test/synthetic-input-generator.js +0 -154
  149. package/dist/runtime/test/test-runner.d.ts +0 -28
  150. package/dist/runtime/test/test-runner.js +0 -55
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Presentation layer for config command UI.
3
+ * Separates display logic from business logic.
4
+ */
5
+ import { log } from '../utils/logger.js';
6
+ import { Icons } from '../constants/index.js';
7
+ import { getGlobalConfigPath } from '../config/global-loader.js';
8
+ /**
9
+ * Mask an API key to show only first 4 and last 4 characters.
10
+ */
11
+ function maskApiKey(apiKey) {
12
+ if (!apiKey || apiKey.length <= 8) {
13
+ return '****';
14
+ }
15
+ // If it looks like an env var reference (e.g., OPENAI_API_KEY), don't mask
16
+ if (/^[A-Z][A-Z0-9_]*$/.test(apiKey)) {
17
+ return apiKey;
18
+ }
19
+ return `${apiKey.slice(0, 4)}...${apiKey.slice(-4)}`;
20
+ }
21
+ /**
22
+ * Display configuration list/show output.
23
+ */
24
+ export function displayConfigList(config, context, configPath) {
25
+ log.blank();
26
+ log.heading(`${context === 'local' ? 'Local' : 'Global'} Configuration`);
27
+ log.blank();
28
+ log.labelValue(`${Icons.FOLDER} Path`, configPath);
29
+ log.blank();
30
+ if (context === 'local' && 'transport' in config) {
31
+ log.heading('Project Settings');
32
+ log.labelValue(' project_name', String(config.project_name));
33
+ log.labelValue(' agent_name', String(config.agent_name));
34
+ log.labelValue(' transport', config.transport);
35
+ if (config.mcp_url) {
36
+ log.labelValue(' mcp_url', String(config.mcp_url));
37
+ }
38
+ if (config.script) {
39
+ log.labelValue(' script', String(config.script));
40
+ }
41
+ log.blank();
42
+ }
43
+ log.heading('LLM Providers');
44
+ const providers = Object.entries(config.llm);
45
+ if (providers.length === 0) {
46
+ log.plain(' No providers configured');
47
+ }
48
+ else {
49
+ for (const [provider, settings] of providers) {
50
+ const isDefault = settings.default || false;
51
+ const defaultMarker = isDefault ? ' (default)' : '';
52
+ log.label(` ${provider}${defaultMarker}`);
53
+ if (settings.API_KEY) {
54
+ log.labelValue(' API_KEY', maskApiKey(String(settings.API_KEY)));
55
+ }
56
+ if (settings.MODEL_NAME) {
57
+ log.labelValue(' MODEL_NAME', String(settings.MODEL_NAME));
58
+ }
59
+ }
60
+ }
61
+ log.blank();
62
+ }
63
+ /**
64
+ * Display success message for config update.
65
+ */
66
+ export function displayConfigUpdated(key, value, context, configPath) {
67
+ log.blank();
68
+ log.success(`${Icons.CHECK} Configuration updated successfully`);
69
+ log.blank();
70
+ log.labelValue(' Key', key);
71
+ log.labelValue(' Value', value);
72
+ log.labelValue(' Config', `${context} (${configPath})`);
73
+ log.blank();
74
+ }
75
+ /**
76
+ * Display message for opening editor.
77
+ */
78
+ export function displayEditorOpening(context, filePath, editorName) {
79
+ log.blank();
80
+ log.info(`Opening ${context} config in editor...`);
81
+ log.labelValue(` ${Icons.FOLDER} File`, filePath);
82
+ log.labelValue(` ${Icons.DOCUMENT} Editor`, editorName);
83
+ log.blank();
84
+ }
85
+ /**
86
+ * Display message after editor closes.
87
+ */
88
+ export function displayEditorClosed(fileType) {
89
+ log.blank();
90
+ if (fileType === 'config') {
91
+ log.success(`Config file updated`);
92
+ log.blank();
93
+ log.info(`Run \`syrin doctor\` to validate your changes`);
94
+ }
95
+ else {
96
+ log.success(`${Icons.CHECK} Environment file updated`);
97
+ }
98
+ log.blank();
99
+ }
100
+ /**
101
+ * Display setup completion message.
102
+ */
103
+ export function displaySetupComplete(isGlobal) {
104
+ log.blank();
105
+ if (isGlobal) {
106
+ log.success(`${Icons.CHECK} Global configuration created`);
107
+ log.blank();
108
+ log.labelValue(` ${Icons.FOLDER} Config`, getGlobalConfigPath());
109
+ log.blank();
110
+ log.info(`${Icons.DOCUMENT} Next steps:`);
111
+ log.plain(' 1. Set your API keys in ~/.syrin/.env or as environment variables');
112
+ log.plain(' 2. Run `syrin config edit-env --global` to edit the environment file');
113
+ log.plain(' 3. Run `syrin doctor` to validate your setup');
114
+ }
115
+ log.blank();
116
+ }
117
+ /**
118
+ * Display default provider set message.
119
+ */
120
+ export function displayDefaultProviderSet(provider, context) {
121
+ log.blank();
122
+ log.success(`${Icons.CHECK} Default provider updated`);
123
+ log.blank();
124
+ log.labelValue(' Provider', provider);
125
+ log.labelValue(' Config', context);
126
+ log.blank();
127
+ }
128
+ /**
129
+ * Display provider removed message.
130
+ */
131
+ export function displayProviderRemoved(provider, context) {
132
+ log.blank();
133
+ log.success(`${Icons.CHECK} Provider removed`);
134
+ log.blank();
135
+ log.labelValue(' Provider', provider);
136
+ log.labelValue(' Config', context);
137
+ log.blank();
138
+ }
139
+ //# sourceMappingURL=config-ui.js.map
@@ -17,6 +17,9 @@ interface DoctorReport {
17
17
  mcp_url?: unknown;
18
18
  script?: unknown;
19
19
  };
20
+ configSource?: 'local' | 'global';
21
+ configPath?: string;
22
+ envSource?: 'local.env' | 'global.env' | 'process.env';
20
23
  transportCheck: CheckResult;
21
24
  scriptCheck: CheckResult | null;
22
25
  llmChecks: Array<{
@@ -31,6 +34,14 @@ interface DoctorReport {
31
34
  modelName?: string;
32
35
  }>;
33
36
  }
37
+ /**
38
+ * Display global config validation results.
39
+ */
40
+ export declare function displayGlobalConfigValidation(globalConfigPath: string, globalEnvPath: string, globalEnvExists: boolean, llmChecks: Array<{
41
+ provider: string;
42
+ apiKeyCheck: CheckResult;
43
+ modelCheck: CheckResult;
44
+ }>): void;
34
45
  /**
35
46
  * Display doctor report using plain console output.
36
47
  * This avoids Ink taking control of stdin, which disables terminal history.
@@ -1,11 +1,49 @@
1
1
  import { getVersionDisplayString } from '../utils/version-display.js';
2
2
  import { log } from '../utils/logger.js';
3
+ /**
4
+ * Display global config validation results.
5
+ */
6
+ export function displayGlobalConfigValidation(globalConfigPath, globalEnvPath, globalEnvExists, llmChecks) {
7
+ log.blank();
8
+ log.heading('Validating Syrin configuration...');
9
+ log.blank();
10
+ log.label('No local config found at ./syrin.yaml');
11
+ log.label('Using global configuration...');
12
+ log.blank();
13
+ log.labelValue('Global Config:', globalConfigPath);
14
+ if (globalEnvExists) {
15
+ log.labelValue('Global Environment:', globalEnvPath);
16
+ }
17
+ else {
18
+ log.labelValue('Global Environment:', `${globalEnvPath} (not found)`);
19
+ }
20
+ log.blank();
21
+ const hasErrors = llmChecks.some(check => !check.apiKeyCheck.isValid || !check.modelCheck.isValid);
22
+ if (hasErrors) {
23
+ log.heading('Global configuration has issues:');
24
+ log.blank();
25
+ for (const check of llmChecks) {
26
+ if (!check.apiKeyCheck.isValid) {
27
+ log.plain(` • ${check.provider} API Key: ${check.apiKeyCheck.fix || 'Missing'}`);
28
+ }
29
+ if (!check.modelCheck.isValid) {
30
+ log.plain(` • ${check.provider} Model: ${check.modelCheck.fix || 'Missing'}`);
31
+ }
32
+ }
33
+ log.blank();
34
+ }
35
+ else {
36
+ log.success('Global configuration is valid!');
37
+ log.label('To create a local config, run: syrin init');
38
+ log.blank();
39
+ }
40
+ }
3
41
  /**
4
42
  * Display doctor report using plain console output.
5
43
  * This avoids Ink taking control of stdin, which disables terminal history.
6
44
  */
7
45
  export async function displayDoctorReport(report) {
8
- const { config, transportCheck, scriptCheck, llmChecks, localLlmChecks } = report;
46
+ const { config, configSource, configPath, envSource, transportCheck, scriptCheck, llmChecks, localLlmChecks, } = report;
9
47
  const allValid = transportCheck.isValid &&
10
48
  (scriptCheck === null || scriptCheck.isValid) &&
11
49
  llmChecks.every(l => l.apiKeyCheck.isValid && l.modelCheck.isValid) &&
@@ -19,6 +57,19 @@ export async function displayDoctorReport(report) {
19
57
  log.label('═══════════════════');
20
58
  log.labelValue('Version:', versionDisplayString);
21
59
  log.blank();
60
+ // Config Source Info
61
+ if (configSource && configPath) {
62
+ log.labelValue(`${configSource === 'local' ? 'Local' : 'Global'} Config:`, configPath);
63
+ if (envSource) {
64
+ const envSourceLabel = envSource === 'local.env'
65
+ ? 'Local project .env file'
66
+ : envSource === 'global.env'
67
+ ? 'Global .env file'
68
+ : 'process.env';
69
+ log.labelValue('Environment Source:', envSourceLabel);
70
+ }
71
+ log.blank();
72
+ }
22
73
  // Project Info
23
74
  log.labelValue('Project:', String(config.project_name));
24
75
  log.labelValue('Agent:', String(config.agent_name));
@@ -11,4 +11,13 @@ export declare function displayAlreadyInitialized(): void;
11
11
  * @param configPath - Path to the created config file
12
12
  */
13
13
  export declare function displayInitSuccess(configPath: string): void;
14
+ /**
15
+ * Display the "global already initialized" message.
16
+ */
17
+ export declare function displayGlobalAlreadyInitialized(): void;
18
+ /**
19
+ * Display the global initialization success message.
20
+ * @param configPath - Path to the created global config file
21
+ */
22
+ export declare function displayGlobalInitSuccess(configPath: string): void;
14
23
  //# sourceMappingURL=init-ui.d.ts.map
@@ -38,4 +38,37 @@ export function displayInitSuccess(configPath) {
38
38
  log.plain(` 4. ${Messages.INIT_RUN_DEV(ToolCommands.DEV)}`);
39
39
  log.blank();
40
40
  }
41
+ /**
42
+ * Display the "global already initialized" message.
43
+ */
44
+ export function displayGlobalAlreadyInitialized() {
45
+ log.blank();
46
+ log.warning(` ${Messages.GLOBAL_INIT_ALREADY_INITIALIZED}`);
47
+ log.blank();
48
+ log.plain(`${Icons.FOLDER} ${Messages.GLOBAL_INIT_CONFIG_FILE(Paths.GLOBAL_CONFIG_FILE)}`);
49
+ log.blank();
50
+ log.plain(`${Icons.DOCUMENT} ${Messages.INIT_NEXT_STEPS_HEADER}`);
51
+ log.plain(` ${Messages.GLOBAL_INIT_EDIT_CONFIG}`);
52
+ log.plain(` ${Messages.GLOBAL_INIT_EDIT_ENV}`);
53
+ log.blank();
54
+ log.info(Messages.GLOBAL_INIT_REINITIALIZE_TIP);
55
+ log.plain(` ${Messages.GLOBAL_INIT_REINITIALIZE_INSTRUCTION}`);
56
+ log.blank();
57
+ }
58
+ /**
59
+ * Display the global initialization success message.
60
+ * @param configPath - Path to the created global config file
61
+ */
62
+ export function displayGlobalInitSuccess(configPath) {
63
+ log.blank();
64
+ log.success(`${Icons.CHECK} ${Messages.GLOBAL_INIT_SUCCESS}`);
65
+ log.blank();
66
+ log.plain(`${Icons.FOLDER} Configuration file: ${configPath}`);
67
+ log.blank();
68
+ log.plain(`${Icons.DOCUMENT} ${Messages.INIT_NEXT_STEPS_HEADER}`);
69
+ log.plain(` 1. Set your API keys in ~/.syrin/.env or as environment variables`);
70
+ log.plain(` 2. Run \`syrin config edit-env --global\` to edit the environment file`);
71
+ log.plain(` 3. ${Messages.INIT_RUN_DOCTOR(ToolCommands.DOCTOR)}`);
72
+ log.blank();
73
+ }
41
74
  //# sourceMappingURL=init-ui.js.map
@@ -7,7 +7,7 @@ import { normalizeTools } from './normalizer.js';
7
7
  import { buildIndexes } from './indexer.js';
8
8
  import { inferDependencies } from './dependencies.js';
9
9
  import { ALL_RULES } from './rules/index.js';
10
- import { logger } from '../../utils/logger.js';
10
+ import { log } from '../../utils/logger.js';
11
11
  /**
12
12
  * Analyze MCP tools and return diagnostics.
13
13
  * Executes all registered rules.
@@ -32,7 +32,7 @@ function runRules(tools, dependencies, indexes) {
32
32
  }
33
33
  catch (error) {
34
34
  // If a rule fails, log error but don't crash the analysis
35
- logger.error(`Rule ${rule.id} failed`, error instanceof Error ? error : new Error(String(error)), {
35
+ log.error(`Rule ${rule.id} failed`, error instanceof Error ? error : new Error(String(error)), {
36
36
  ruleId: rule.id,
37
37
  ruleName: rule.ruleName,
38
38
  });
@@ -42,7 +42,7 @@ declare class W104GenericDescriptionRule extends BaseRule {
42
42
  *
43
43
  * @example
44
44
  * ```ts
45
- * import { createW104Rule, DEFAULT_CONCRETE_NOUNS } from './w005-generic-description.js';
45
+ * import { createW104Rule, DEFAULT_CONCRETE_NOUNS } from './w005-generic-description';
46
46
  * const customNouns = [...DEFAULT_CONCRETE_NOUNS, 'invoice', 'report'];
47
47
  * const customRule = createW104Rule(customNouns);
48
48
  * ```
@@ -96,7 +96,7 @@ class W104GenericDescriptionRule extends BaseRule {
96
96
  *
97
97
  * @example
98
98
  * ```ts
99
- * import { createW104Rule, DEFAULT_CONCRETE_NOUNS } from './w005-generic-description.js';
99
+ * import { createW104Rule, DEFAULT_CONCRETE_NOUNS } from './w005-generic-description';
100
100
  * const customNouns = [...DEFAULT_CONCRETE_NOUNS, 'invoice', 'report'];
101
101
  * const customRule = createW104Rule(customNouns);
102
102
  * ```
@@ -148,10 +148,26 @@ export class DevEventMapper {
148
148
  */
149
149
  handleValidationFailed(event) {
150
150
  const payload = event.payload;
151
+ // Format each validation error with better structure
151
152
  const errors = payload.validation_errors
152
- .map(err => ` - ${err.message} (${err.code})`)
153
- .join('\n');
154
- this.chatUI.addMessage('system', `❌ Validation failed for **${payload.tool_name}**:\n${errors}`);
153
+ .map(err => {
154
+ // If the message contains newlines, it's already formatted with details
155
+ // Otherwise, format it simply
156
+ if (err.message.includes('\n')) {
157
+ // Multi-line error message - format with proper indentation
158
+ const lines = err.message.split('\n');
159
+ const firstLine = lines[0];
160
+ const details = lines
161
+ .slice(1)
162
+ .map(line => ` ${line}`)
163
+ .join('\n');
164
+ return ` • ${firstLine}\n${details}`;
165
+ }
166
+ // Simple error message
167
+ return ` • ${err.message} (${err.code})`;
168
+ })
169
+ .join('\n\n');
170
+ this.chatUI.addMessage('system', `❌ Validation failed for **${payload.tool_name}**:\n\n${errors}`);
155
171
  }
156
172
  /**
157
173
  * Handle tool execution started event.
@@ -50,6 +50,10 @@ export declare class DevSession {
50
50
  * Validate a tool call.
51
51
  */
52
52
  private validateToolCall;
53
+ /**
54
+ * Format field type information for error messages.
55
+ */
56
+ private formatFieldType;
53
57
  /**
54
58
  * Build prompt after tool execution.
55
59
  */
@@ -5,7 +5,7 @@
5
5
  import { SessionLifecycleEventType, LLMContextEventType, LLMProposalEventType, ValidationEventType, } from '../../events/event-type.js';
6
6
  import { makePromptID } from '../../types/factories.js';
7
7
  import { v4 as uuidv4 } from 'uuid';
8
- import { logger } from '../../utils/logger.js';
8
+ import { log } from '../../utils/logger.js';
9
9
  import { DataManager } from './data-manager.js';
10
10
  /**
11
11
  * Dev Mode Session implementation.
@@ -216,7 +216,7 @@ you can request the full data if needed.`;
216
216
  reason: `Duplicate tool call detected: ${toolCall.name} with identical arguments was already executed`,
217
217
  });
218
218
  // Use logger instead of console.warn for proper event tracking
219
- logger.warn(`Skipping duplicate tool call: ${toolCall.name} with arguments ${normalizedArgs}`);
219
+ log.warn(`Skipping duplicate tool call: ${toolCall.name} with arguments ${normalizedArgs}`);
220
220
  continue;
221
221
  }
222
222
  seenKeys.add(key);
@@ -354,7 +354,27 @@ you can request the full data if needed.`;
354
354
  if (schema.required) {
355
355
  for (const requiredField of schema.required) {
356
356
  if (!(requiredField in toolCall.arguments)) {
357
- const error = `Missing required argument: ${requiredField}`;
357
+ // Extract field information from schema
358
+ const fieldDef = schema.properties?.[requiredField];
359
+ const fieldType = this.formatFieldType(fieldDef);
360
+ const fieldDescription = fieldDef?.description;
361
+ // Build comprehensive error message
362
+ let error = `Missing required field: "${requiredField}"`;
363
+ if (fieldType) {
364
+ error += `\n Expected type: ${fieldType}`;
365
+ }
366
+ if (fieldDescription) {
367
+ error += `\n Description: ${fieldDescription}`;
368
+ }
369
+ if (fieldDef?.enum) {
370
+ const enumValues = fieldDef.enum
371
+ .map(v => (typeof v === 'string' ? `"${v}"` : String(v)))
372
+ .join(', ');
373
+ error += `\n Allowed values: ${enumValues}`;
374
+ }
375
+ if (fieldDef?.format) {
376
+ error += `\n Format: ${fieldDef.format}`;
377
+ }
358
378
  await this.config.eventEmitter.emit(ValidationEventType.TOOL_CALL_VALIDATION_FAILED, {
359
379
  tool_name: toolCall.name,
360
380
  arguments: toolCall.arguments,
@@ -379,6 +399,35 @@ you can request the full data if needed.`;
379
399
  });
380
400
  return { valid: true };
381
401
  }
402
+ /**
403
+ * Format field type information for error messages.
404
+ */
405
+ formatFieldType(fieldDef) {
406
+ if (!fieldDef) {
407
+ return null;
408
+ }
409
+ const type = fieldDef.type;
410
+ if (!type) {
411
+ return null;
412
+ }
413
+ // Handle array types
414
+ if (Array.isArray(type)) {
415
+ return type.join(' | ');
416
+ }
417
+ // Handle object type (could be a complex object)
418
+ if (type === 'object') {
419
+ return 'object';
420
+ }
421
+ // Handle array of items
422
+ if (type === 'array') {
423
+ const itemsType = fieldDef.items?.type;
424
+ if (itemsType) {
425
+ return `array<${itemsType}>`;
426
+ }
427
+ return 'array';
428
+ }
429
+ return type;
430
+ }
382
431
  /**
383
432
  * Build prompt after tool execution.
384
433
  */
@@ -6,7 +6,7 @@ import { Ollama } from 'ollama';
6
6
  import * as childProcess from 'child_process';
7
7
  import { v4 as uuidv4 } from 'uuid';
8
8
  import { ConfigurationError } from '../../utils/errors.js';
9
- import { logger, log } from '../../utils/logger.js';
9
+ import { log } from '../../utils/logger.js';
10
10
  import { checkCommandExists, checkEnvVar } from '../../config/env-checker.js';
11
11
  /**
12
12
  * Static process manager for Ollama service.
@@ -89,12 +89,12 @@ class OllamaProcessManager {
89
89
  const message = data.toString().trim();
90
90
  // Only log errors, not normal startup messages
91
91
  if (message && /error|fatal|critical/i.test(message)) {
92
- logger.warn(`Ollama: ${message}`);
92
+ log.warn(`Ollama: ${message}`);
93
93
  }
94
94
  });
95
95
  // Handle process errors
96
96
  this.ollamaProcess.on('error', (error) => {
97
- logger.error('Failed to start Ollama service', error);
97
+ log.error(`Error: ${error.message}`, error);
98
98
  this.ollamaProcess = null;
99
99
  this.isStarting = false;
100
100
  throw new ConfigurationError(`Failed to start Ollama service: ${error.message}`);
@@ -212,7 +212,7 @@ export class OllamaProvider {
212
212
  const modelsResponse = await this.client.list();
213
213
  const modelExists = modelsResponse.models?.some((m) => m.name === this.modelName || m.name.startsWith(`${this.modelName}:`)) ?? false;
214
214
  if (!modelExists) {
215
- logger.info(`Model ${this.modelName} not found. Downloading...`);
215
+ log.info(`Model ${this.modelName} not found. Downloading...`);
216
216
  log.blank();
217
217
  log.info(`📥 Downloading model: ${this.modelName}`);
218
218
  log.plain('This may take a few minutes depending on your internet connection...');
@@ -7,7 +7,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
7
7
  import { TransportEventType } from '../../../events/event-type.js';
8
8
  import { getConnectedClient } from '../connection.js';
9
9
  import { ConfigurationError } from '../../../utils/errors.js';
10
- import { logger } from '../../../utils/logger.js';
10
+ import { log } from '../../../utils/logger.js';
11
11
  import { parseCommand, setupProcessLogCapture, spawnProcess } from './process.js';
12
12
  import { BaseMCPClientManager } from './base.js';
13
13
  /**
@@ -106,7 +106,7 @@ export class HTTPMCPClientManager extends BaseMCPClientManager {
106
106
  catch (error) {
107
107
  // Log but don't throw - cleanup errors are non-critical
108
108
  const err = error instanceof Error ? error : new Error(String(error));
109
- logger.error('Error closing transport during disconnect', err);
109
+ log.error(`Error: ${err.message}`, err);
110
110
  await this.eventEmitter.emit(TransportEventType.TRANSPORT_ERROR, {
111
111
  transport_type: 'http',
112
112
  error_message: err.message,
@@ -213,7 +213,7 @@ export class StdioMCPClientManager extends BaseMCPClientManager {
213
213
  }
214
214
  catch (error) {
215
215
  const err = error instanceof Error ? error : new Error(String(error));
216
- logger.error('Error closing transport during disconnect', err);
216
+ log.error(`Error: ${err.message}`, err);
217
217
  await this.emitTransportError(err.message, 'DISCONNECT_ERROR', false);
218
218
  }
219
219
  }
@@ -12,7 +12,7 @@ import { parseCommand } from '../../runtime/mcp/client/process.js';
12
12
  import { ConfigurationError } from '../../utils/errors.js';
13
13
  import { formatTimeString } from './time-parser.js';
14
14
  import { ToolExecutionErrorType } from './types.js';
15
- import { logger } from '../../utils/logger.js';
15
+ import { log } from '../../utils/logger.js';
16
16
  // Re-export types for convenience
17
17
  export * from './types.js';
18
18
  /**
@@ -51,7 +51,7 @@ export class SandboxExecutor {
51
51
  if (isNodeCommand) {
52
52
  // For Node.js: inject --max-old-space-size flag
53
53
  args = ['--max-old-space-size', String(memoryMB), ...args];
54
- logger.debug(`Memory limit: ${memoryMB}MB enforced via --max-old-space-size for Node.js`);
54
+ log.debug(`Memory limit: ${memoryMB}MB enforced via --max-old-space-size for Node.js`);
55
55
  }
56
56
  else if (process.platform !== 'win32') {
57
57
  // For POSIX systems: wrap with ulimit -v (virtual memory limit)
@@ -64,12 +64,12 @@ export class SandboxExecutor {
64
64
  executable = '/bin/sh';
65
65
  args = ['-c', `ulimit -v ${memoryKB} && exec ${originalCommand}`];
66
66
  isWrapped = true;
67
- logger.debug(`Memory limit: ${memoryMB}MB (${memoryKB}KB) enforced via ulimit -v`);
67
+ log.debug(`Memory limit: ${memoryMB}MB (${memoryKB}KB) enforced via ulimit -v`);
68
68
  }
69
69
  else {
70
70
  // Windows: Memory limits require different approach (SetProcessWorkingSetSize, Job Objects)
71
71
  // For now, log warning that limit is not enforced on Windows
72
- logger.warn(`Memory limit ${memoryMB}MB specified but not enforced on Windows platform`);
72
+ log.warn(`Memory limit ${memoryMB}MB specified but not enforced on Windows platform`);
73
73
  }
74
74
  }
75
75
  // Suppress stderr output if requested (for CI mode)
@@ -137,7 +137,7 @@ export class SandboxExecutor {
137
137
  : typeof event === 'object' && event !== null && 'message' in event
138
138
  ? String(event.message)
139
139
  : 'Unknown MCP client error';
140
- logger.error(`MCP client connection error: ${errorMessage}`, event instanceof Error ? event : new Error(errorMessage));
140
+ log.error(`Error: ${errorMessage}`, event instanceof Error ? event : new Error(errorMessage));
141
141
  // Errors will also be caught in try-catch blocks during tool execution
142
142
  };
143
143
  // Connect client to server (StdioClientTransport spawns the process here)
@@ -27,7 +27,7 @@ import { E600UnexpectedTestResult } from '../../runtime/analysis/rules/errors/e6
27
27
  // Centralized error code constants - single source of truth for error codes
28
28
  import { ERROR_CODES, ERROR_TYPE_TO_CODE, } from '../../runtime/analysis/rules/error-codes.js';
29
29
  import { applyStrictMode, computeVerdict, } from '../../runtime/analysis/strict-mode.js';
30
- import { logger } from '../../utils/logger.js';
30
+ import { log } from '../../utils/logger.js';
31
31
  import { ConfigurationError } from '../../utils/errors.js';
32
32
  /**
33
33
  * Test orchestrator for tool validation.
@@ -158,7 +158,7 @@ export class TestOrchestrator {
158
158
  const mcpCommand = this.options.mcpCommand || config.script;
159
159
  const scriptName = mcpCommand ? String(mcpCommand) : 'unknown';
160
160
  if (!this.options.ci) {
161
- logger.warn(`Tool "${toolName}" not found in MCP server. Running: ${scriptName}`);
161
+ log.warn(`Tool "${toolName}" not found in MCP server. Running: ${scriptName}`);
162
162
  }
163
163
  // Create a diagnostic error for missing tool using E000 rule
164
164
  const diagnostics = E000ToolNotFound.checkWithRuntimeContext({
@@ -190,12 +190,12 @@ export class TestOrchestrator {
190
190
  try {
191
191
  toolTimeoutMs = parseTimeString(contract.guarantees.max_execution_time);
192
192
  if (!this.options.ci) {
193
- logger.info(`Tool "${toolName}" declared max_execution_time: ${contract.guarantees.max_execution_time} (${toolTimeoutMs}ms)`);
193
+ log.info(`Tool "${toolName}" declared max_execution_time: ${contract.guarantees.max_execution_time} (${toolTimeoutMs}ms)`);
194
194
  }
195
195
  }
196
196
  catch (_error) {
197
197
  if (!this.options.ci) {
198
- logger.warn(`Invalid max_execution_time for tool "${toolName}": ${contract.guarantees.max_execution_time}. Using global default.`);
198
+ log.warn(`Invalid max_execution_time for tool "${toolName}": ${contract.guarantees.max_execution_time}. Using global default.`);
199
199
  }
200
200
  }
201
201
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Editor utility for opening files in system editor.
3
+ * Supports nano (Unix) and notepad (Windows).
4
+ */
5
+ /**
6
+ * Get the appropriate editor command for the current platform.
7
+ * Checks EDITOR and VISUAL environment variables first, then falls back to platform defaults.
8
+ * @returns Editor command
9
+ */
10
+ export declare function getEditor(): string;
11
+ /**
12
+ * Check file permissions and return security status.
13
+ * @param filePath - Path to file to check
14
+ * @returns Permission check result
15
+ */
16
+ export declare function checkFilePermissions(filePath: string): {
17
+ isSecure: boolean;
18
+ currentMode: string;
19
+ recommendedMode: string;
20
+ };
21
+ /**
22
+ * Ensure a file exists, creating it with a template if needed.
23
+ * @param filePath - Path to file
24
+ * @param template - Template content to use if file doesn't exist
25
+ * @param secure - Whether to set secure permissions (default: true for .env files)
26
+ */
27
+ export declare function ensureFileExists(filePath: string, template: string, secure?: boolean): void;
28
+ /**
29
+ * Open a file in the system editor.
30
+ * @param filePath - Path to file to open
31
+ * @param createIfMissing - Whether to create file if it doesn't exist (default: false)
32
+ * @param template - Template content if creating file (optional)
33
+ * @returns Promise that resolves when editor closes
34
+ * @throws {Error} If editor fails to open
35
+ */
36
+ export declare function openInEditor(filePath: string, createIfMissing?: boolean, template?: string): Promise<void>;
37
+ //# sourceMappingURL=editor.d.ts.map