@syrin/cli 1.3.1 → 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 +4 -4
  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,137 @@
1
+ /**
2
+ * Editor utility for opening files in system editor.
3
+ * Supports nano (Unix) and notepad (Windows).
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { spawn } from 'child_process';
8
+ /**
9
+ * Get the appropriate editor command for the current platform.
10
+ * Checks EDITOR and VISUAL environment variables first, then falls back to platform defaults.
11
+ * @returns Editor command
12
+ */
13
+ export function getEditor() {
14
+ // Check environment variables first (user preference)
15
+ if (process.env.EDITOR && process.env.EDITOR.trim() !== '') {
16
+ return process.env.EDITOR.trim();
17
+ }
18
+ if (process.env.VISUAL && process.env.VISUAL.trim() !== '') {
19
+ return process.env.VISUAL.trim();
20
+ }
21
+ // Fall back to platform defaults
22
+ if (process.platform === 'win32') {
23
+ return 'notepad';
24
+ }
25
+ return 'nano';
26
+ }
27
+ /**
28
+ * Get editor arguments for opening a file.
29
+ * @param editor - Editor command
30
+ * @param filePath - Path to file to open
31
+ * @returns Array of arguments for the editor
32
+ */
33
+ function getEditorArgs(editor, filePath) {
34
+ // For notepad and nano, just pass the file path
35
+ return [filePath];
36
+ }
37
+ /**
38
+ * Check file permissions and return security status.
39
+ * @param filePath - Path to file to check
40
+ * @returns Permission check result
41
+ */
42
+ export function checkFilePermissions(filePath) {
43
+ if (!fs.existsSync(filePath)) {
44
+ return {
45
+ isSecure: true, // Non-existent files are considered secure
46
+ currentMode: 'N/A',
47
+ recommendedMode: '600',
48
+ };
49
+ }
50
+ try {
51
+ const stats = fs.statSync(filePath);
52
+ // Normalize octal string to 3 digits for consistent comparison
53
+ const mode = stats.mode.toString(8).slice(-3).padStart(3, '0');
54
+ const isSecure = mode === '600' || mode === '400'; // Read/write owner only, or read-only owner
55
+ return {
56
+ isSecure,
57
+ currentMode: mode,
58
+ recommendedMode: '600',
59
+ };
60
+ }
61
+ catch {
62
+ // If we can't check permissions, assume insecure
63
+ return {
64
+ isSecure: false,
65
+ currentMode: 'unknown',
66
+ recommendedMode: '600',
67
+ };
68
+ }
69
+ }
70
+ /**
71
+ * Ensure a file exists, creating it with a template if needed.
72
+ * @param filePath - Path to file
73
+ * @param template - Template content to use if file doesn't exist
74
+ * @param secure - Whether to set secure permissions (default: true for .env files)
75
+ */
76
+ export function ensureFileExists(filePath, template, secure = true) {
77
+ if (fs.existsSync(filePath)) {
78
+ return;
79
+ }
80
+ // Create directory if needed
81
+ const dir = path.dirname(filePath);
82
+ if (!fs.existsSync(dir)) {
83
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
84
+ }
85
+ // Create file with template
86
+ fs.writeFileSync(filePath, template, 'utf-8');
87
+ // Set secure permissions if requested
88
+ if (secure) {
89
+ try {
90
+ fs.chmodSync(filePath, 0o600);
91
+ }
92
+ catch {
93
+ // Ignore permission errors (may not work on all systems)
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Open a file in the system editor.
99
+ * @param filePath - Path to file to open
100
+ * @param createIfMissing - Whether to create file if it doesn't exist (default: false)
101
+ * @param template - Template content if creating file (optional)
102
+ * @returns Promise that resolves when editor closes
103
+ * @throws {Error} If editor fails to open
104
+ */
105
+ export async function openInEditor(filePath, createIfMissing = false, template) {
106
+ // Ensure file exists if requested
107
+ if (createIfMissing && !fs.existsSync(filePath) && template) {
108
+ ensureFileExists(filePath, template, filePath.endsWith('.env'));
109
+ }
110
+ // Check if file exists
111
+ if (!fs.existsSync(filePath)) {
112
+ throw new Error(`File does not exist: ${filePath}`);
113
+ }
114
+ const editor = getEditor();
115
+ const args = getEditorArgs(editor, filePath);
116
+ return new Promise((resolve, reject) => {
117
+ const proc = spawn(editor, args, {
118
+ stdio: 'inherit',
119
+ shell: true,
120
+ });
121
+ proc.on('exit', code => {
122
+ if (code === 0 || code === null) {
123
+ // Exit code 0 or null (normal exit) - user saved and closed
124
+ resolve();
125
+ }
126
+ else {
127
+ // Non-zero exit code - might be user cancellation or error
128
+ // We'll treat it as success (user may have cancelled, which is fine)
129
+ resolve();
130
+ }
131
+ });
132
+ proc.on('error', error => {
133
+ reject(new Error(`Failed to open editor "${editor}": ${error.message}. Make sure the editor is installed and available in your PATH.`));
134
+ });
135
+ });
136
+ }
137
+ //# sourceMappingURL=editor.js.map
@@ -54,6 +54,14 @@ export declare class Log {
54
54
  * Set the log level.
55
55
  */
56
56
  setLevel(level: LogLevel): void;
57
+ /**
58
+ * Get the current log level.
59
+ */
60
+ getLevel(): LogLevel;
61
+ /**
62
+ * Check if the logger is in quiet mode (only errors shown).
63
+ */
64
+ isQuiet(): boolean;
57
65
  /**
58
66
  * Enable or disable structured logging.
59
67
  */
@@ -67,7 +75,8 @@ export declare class Log {
67
75
  */
68
76
  private structuredLog;
69
77
  /**
70
- * Print styled text to console.
78
+ * Print styled text to console at INFO level.
79
+ * Respects log level - won't print if level is higher than INFO.
71
80
  */
72
81
  private print;
73
82
  /**
@@ -92,6 +101,7 @@ export declare class Log {
92
101
  * Log/print error message (red) with error icon.
93
102
  * For structured logging: logs at ERROR level (can include Error object)
94
103
  * For styled output: prints with red color and error icon
104
+ * Note: Errors always show regardless of log level (for quiet mode).
95
105
  */
96
106
  error(message: string, errorOrIcon?: Error | IconType, contextOrIcon?: Record<string, unknown> | IconType): void;
97
107
  /**
@@ -124,6 +134,7 @@ export declare class Log {
124
134
  debug(text: string, icon?: IconType): void;
125
135
  /**
126
136
  * Print a line with label and value.
137
+ * Respects log level.
127
138
  */
128
139
  labelValue(labelText: string, valueText: string): void;
129
140
  /**
@@ -154,10 +165,12 @@ export declare class Log {
154
165
  warnSymbol(text: string): void;
155
166
  /**
156
167
  * Print plain text (no styling).
168
+ * Respects log level.
157
169
  */
158
170
  plain(text: string): void;
159
171
  /**
160
172
  * Print an empty line.
173
+ * Respects log level.
161
174
  */
162
175
  blank(): void;
163
176
  /**
@@ -167,15 +180,20 @@ export declare class Log {
167
180
  styleText(text: string, ...styles: ColorKey[]): string;
168
181
  }
169
182
  /**
170
- * Default logger instance for styled output (non-structured).
171
- * Use this for UI/presentation output.
183
+ * Default logger instance for styled output.
184
+ * Use this for all logging throughout the application.
172
185
  */
173
186
  export declare const log: Log;
174
187
  /**
175
- * Default structured logger instance.
176
- * Use this for application logging with levels and context.
188
+ * Set quiet mode (errors only).
189
+ * @param enabled - Whether to enable quiet mode
190
+ */
191
+ export declare function setQuietMode(enabled: boolean): void;
192
+ /**
193
+ * Set verbose mode (debug level).
194
+ * @param enabled - Whether to enable verbose mode
177
195
  */
178
- export declare const logger: Log;
196
+ export declare function setVerboseMode(enabled: boolean): void;
179
197
  /**
180
198
  * Create a logger with a specific context.
181
199
  */
@@ -56,6 +56,18 @@ export class Log {
56
56
  setLevel(level) {
57
57
  this.level = level;
58
58
  }
59
+ /**
60
+ * Get the current log level.
61
+ */
62
+ getLevel() {
63
+ return this.level;
64
+ }
65
+ /**
66
+ * Check if the logger is in quiet mode (only errors shown).
67
+ */
68
+ isQuiet() {
69
+ return this.level >= LogLevel.ERROR;
70
+ }
59
71
  /**
60
72
  * Enable or disable structured logging.
61
73
  */
@@ -117,9 +129,13 @@ export class Log {
117
129
  }
118
130
  }
119
131
  /**
120
- * Print styled text to console.
132
+ * Print styled text to console at INFO level.
133
+ * Respects log level - won't print if level is higher than INFO.
121
134
  */
122
135
  print(text, ...styles) {
136
+ if (LogLevel.INFO < this.level) {
137
+ return; // Skip if log level is higher than INFO (e.g., quiet mode)
138
+ }
123
139
  if (this.useStructuredLogging) {
124
140
  this.structuredLog(LogLevel.INFO, text);
125
141
  }
@@ -167,6 +183,7 @@ export class Log {
167
183
  * Log/print error message (red) with error icon.
168
184
  * For structured logging: logs at ERROR level (can include Error object)
169
185
  * For styled output: prints with red color and error icon
186
+ * Note: Errors always show regardless of log level (for quiet mode).
170
187
  */
171
188
  error(message, errorOrIcon, contextOrIcon) {
172
189
  if (this.useStructuredLogging) {
@@ -179,9 +196,10 @@ export class Log {
179
196
  }
180
197
  else {
181
198
  // Styled output: errorOrIcon is optional Icon
199
+ // Errors always print regardless of log level (critical for quiet mode)
182
200
  const icon = errorOrIcon;
183
201
  const iconText = icon ? `${icon} ` : `${Icon.ERROR} `;
184
- this.print(`${iconText}${message}`, 'red');
202
+ console.log(this.style(`${iconText}${message}`, 'red'));
185
203
  }
186
204
  }
187
205
  warning(text, contextOrIcon) {
@@ -238,8 +256,12 @@ export class Log {
238
256
  }
239
257
  /**
240
258
  * Print a line with label and value.
259
+ * Respects log level.
241
260
  */
242
261
  labelValue(labelText, valueText) {
262
+ if (LogLevel.INFO < this.level) {
263
+ return; // Skip if log level is higher than INFO
264
+ }
243
265
  const output = `${this.style(labelText, 'dim')} ${this.style(valueText, 'cyan')}`;
244
266
  if (this.useStructuredLogging) {
245
267
  this.structuredLog(LogLevel.INFO, `${labelText} ${valueText}`);
@@ -288,8 +310,12 @@ export class Log {
288
310
  }
289
311
  /**
290
312
  * Print plain text (no styling).
313
+ * Respects log level.
291
314
  */
292
315
  plain(text) {
316
+ if (LogLevel.INFO < this.level) {
317
+ return; // Skip if log level is higher than INFO
318
+ }
293
319
  if (this.useStructuredLogging) {
294
320
  this.structuredLog(LogLevel.INFO, text);
295
321
  }
@@ -299,8 +325,12 @@ export class Log {
299
325
  }
300
326
  /**
301
327
  * Print an empty line.
328
+ * Respects log level.
302
329
  */
303
330
  blank() {
331
+ if (LogLevel.INFO < this.level) {
332
+ return; // Skip if log level is higher than INFO
333
+ }
304
334
  console.log('');
305
335
  }
306
336
  /**
@@ -312,19 +342,32 @@ export class Log {
312
342
  }
313
343
  }
314
344
  /**
315
- * Default logger instance for styled output (non-structured).
316
- * Use this for UI/presentation output.
345
+ * Default logger instance for styled output.
346
+ * Use this for all logging throughout the application.
317
347
  */
318
348
  export const log = new Log(LogLevel.INFO, {}, false);
319
349
  /**
320
- * Default structured logger instance.
321
- * Use this for application logging with levels and context.
350
+ * Set quiet mode (errors only).
351
+ * @param enabled - Whether to enable quiet mode
352
+ */
353
+ export function setQuietMode(enabled) {
354
+ if (enabled) {
355
+ log.setLevel(LogLevel.ERROR);
356
+ }
357
+ }
358
+ /**
359
+ * Set verbose mode (debug level).
360
+ * @param enabled - Whether to enable verbose mode
322
361
  */
323
- export const logger = new Log(LogLevel.INFO, {}, true);
362
+ export function setVerboseMode(enabled) {
363
+ if (enabled) {
364
+ log.setLevel(LogLevel.DEBUG);
365
+ }
366
+ }
324
367
  /**
325
368
  * Create a logger with a specific context.
326
369
  */
327
370
  export function createLogger(context) {
328
- return logger.child(context);
371
+ return log.child(context);
329
372
  }
330
373
  //# sourceMappingURL=logger.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syrin/cli",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Syrin is a runtime intelligence system that makes MCP servers debuggable, testable, and safe to run in production.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,14 +55,14 @@
55
55
  },
56
56
  "repository": {
57
57
  "type": "git",
58
- "url": "git+https://github.com/syrin-labs/syrin.git"
58
+ "url": "git+https://github.com/syrin-labs/cli.git"
59
59
  },
60
60
  "author": "Syrin Labs Team",
61
61
  "license": "ISC",
62
62
  "bugs": {
63
- "url": "https://github.com/syrin-labs/syrin/issues"
63
+ "url": "https://github.com/syrin-labs/cli/issues"
64
64
  },
65
- "homepage": "https://github.com/syrin-labs/syrin#readme",
65
+ "homepage": "https://github.com/syrin-labs/cli#readme",
66
66
  "publishConfig": {
67
67
  "access": "public"
68
68
  },
@@ -1,22 +0,0 @@
1
- /**
2
- * E001: Missing Output Schema
3
- *
4
- * Condition: Tool does not declare an output schema
5
- *
6
- * Why this is fatal:
7
- * - Downstream tools cannot reason about outputs
8
- * - LLM will invent structure
9
- * - Reproducibility is impossible
10
- */
11
- import { BaseRule } from '../base.js';
12
- import type { AnalysisContext, Diagnostic } from '../../types.js';
13
- declare class E001MissingOutputSchemaRule extends BaseRule {
14
- readonly id: "E001";
15
- readonly severity: "error";
16
- readonly ruleName = "Missing Output Schema";
17
- readonly description = "Tool does not declare an output schema. Downstream tools cannot safely consume its output.";
18
- check(ctx: AnalysisContext): Diagnostic[];
19
- }
20
- export declare const E001MissingOutputSchema: E001MissingOutputSchemaRule;
21
- export {};
22
- //# sourceMappingURL=e001-missing-output-schema.d.ts.map
@@ -1,30 +0,0 @@
1
- /**
2
- * E001: Missing Output Schema
3
- *
4
- * Condition: Tool does not declare an output schema
5
- *
6
- * Why this is fatal:
7
- * - Downstream tools cannot reason about outputs
8
- * - LLM will invent structure
9
- * - Reproducibility is impossible
10
- */
11
- import { BaseRule } from '../base.js';
12
- import { ERROR_CODES } from '../error-codes.js';
13
- class E001MissingOutputSchemaRule extends BaseRule {
14
- id = ERROR_CODES.E001;
15
- severity = 'error';
16
- ruleName = 'Missing Output Schema';
17
- description = 'Tool does not declare an output schema. Downstream tools cannot safely consume its output.';
18
- check(ctx) {
19
- const diagnostics = [];
20
- for (const tool of ctx.tools) {
21
- // Check if tool has no output fields
22
- if (!tool.outputs || tool.outputs.length === 0) {
23
- diagnostics.push(this.createDiagnostic(`Tool "${tool.name}" has no output schema. Downstream tools cannot safely consume its output.`, tool.name, undefined, 'Add an output schema to the tool definition to specify the structure of the output.'));
24
- }
25
- }
26
- return diagnostics;
27
- }
28
- }
29
- export const E001MissingOutputSchema = new E001MissingOutputSchemaRule();
30
- //# sourceMappingURL=e001-missing-output-schema.js.map
@@ -1,24 +0,0 @@
1
- /**
2
- * E002: Underspecified Required Input
3
- *
4
- * Condition:
5
- * - Input parameter is required AND
6
- * - Type is broad (string, any, object) AND
7
- * - No description, constraints, enum, regex, or example
8
- *
9
- * Why:
10
- * - LLM will hallucinate values
11
- * - Tool invocation becomes nondeterministic
12
- */
13
- import { BaseRule } from '../base.js';
14
- import type { AnalysisContext, Diagnostic } from '../../types.js';
15
- declare class E002UnderspecifiedRequiredInputRule extends BaseRule {
16
- readonly id: "E002";
17
- readonly severity: "error";
18
- readonly ruleName = "Underspecified Required Input";
19
- readonly description = "Required parameter is underspecified. LLM may pass invalid or ambiguous values.";
20
- check(ctx: AnalysisContext): Diagnostic[];
21
- }
22
- export declare const E002UnderspecifiedRequiredInput: E002UnderspecifiedRequiredInputRule;
23
- export {};
24
- //# sourceMappingURL=e002-underspecified-input.d.ts.map
@@ -1,52 +0,0 @@
1
- /**
2
- * E002: Underspecified Required Input
3
- *
4
- * Condition:
5
- * - Input parameter is required AND
6
- * - Type is broad (string, any, object) AND
7
- * - No description, constraints, enum, regex, or example
8
- *
9
- * Why:
10
- * - LLM will hallucinate values
11
- * - Tool invocation becomes nondeterministic
12
- */
13
- import { BaseRule } from '../base.js';
14
- import { ERROR_CODES } from '../error-codes.js';
15
- class E002UnderspecifiedRequiredInputRule extends BaseRule {
16
- id = ERROR_CODES.E002;
17
- severity = 'error';
18
- ruleName = 'Underspecified Required Input';
19
- description = 'Required parameter is underspecified. LLM may pass invalid or ambiguous values.';
20
- check(ctx) {
21
- const diagnostics = [];
22
- // Broad types that need constraints
23
- const broadTypes = new Set(['string', 'any', 'object']);
24
- for (const tool of ctx.tools) {
25
- for (const input of tool.inputs) {
26
- // Check if type is broad
27
- if (!broadTypes.has(input.type)) {
28
- continue;
29
- }
30
- // Check if it has any constraints
31
- const hasDescription = Boolean(input.description && input.description.trim());
32
- const hasEnum = Boolean(input.enum && input.enum.length > 0);
33
- const hasPattern = Boolean(input.pattern);
34
- const hasExample = input.example !== undefined;
35
- // If it's broad and has no constraints, it's underspecified
36
- if (!hasDescription && !hasEnum && !hasPattern && !hasExample) {
37
- if (input.required) {
38
- // Required parameter without constraints - error
39
- diagnostics.push(this.createDiagnostic(`Required parameter "${input.name}" in tool "${tool.name}" is underspecified. LLM may pass invalid or ambiguous values.`, tool.name, input.name, `Add constraints to "${input.name}": provide a description, enum values, regex pattern, or example.`));
40
- }
41
- else {
42
- // Optional parameter without constraints - flag as warning since it's less critical
43
- diagnostics.push(this.createDiagnostic(`Optional parameter "${input.name}" in tool "${tool.name}" is underspecified. LLM may pass invalid or ambiguous values.`, tool.name, input.name, `Add constraints to "${input.name}": provide a description, enum values, regex pattern, or example.`, undefined, 'warning'));
44
- }
45
- }
46
- }
47
- }
48
- return diagnostics;
49
- }
50
- }
51
- export const E002UnderspecifiedRequiredInput = new E002UnderspecifiedRequiredInputRule();
52
- //# sourceMappingURL=e002-underspecified-input.js.map
@@ -1,23 +0,0 @@
1
- /**
2
- * E003: Unsafe Tool Chaining (Type Mismatch)
3
- *
4
- * Condition:
5
- * - Tool A output flows into Tool B input AND
6
- * - Output type incompatible with input type
7
- *
8
- * Why:
9
- * - Tool chains silently break
10
- * - Bugs appear "random"
11
- */
12
- import { BaseRule } from '../base.js';
13
- import type { AnalysisContext, Diagnostic } from '../../types.js';
14
- declare class E003TypeMismatchRule extends BaseRule {
15
- readonly id: "E003";
16
- readonly severity: "error";
17
- readonly ruleName = "Unsafe Tool Chaining (Type Mismatch)";
18
- readonly description = "Output type incompatible with downstream input type. Tool chains will break.";
19
- check(ctx: AnalysisContext): Diagnostic[];
20
- }
21
- export declare const E003TypeMismatch: E003TypeMismatchRule;
22
- export {};
23
- //# sourceMappingURL=e003-type-mismatch.d.ts.map
@@ -1,73 +0,0 @@
1
- /**
2
- * E003: Unsafe Tool Chaining (Type Mismatch)
3
- *
4
- * Condition:
5
- * - Tool A output flows into Tool B input AND
6
- * - Output type incompatible with input type
7
- *
8
- * Why:
9
- * - Tool chains silently break
10
- * - Bugs appear "random"
11
- */
12
- import { BaseRule } from '../base.js';
13
- import { ERROR_CODES } from '../error-codes.js';
14
- /**
15
- * Check if two types are incompatible.
16
- */
17
- function areTypesIncompatible(outputType, inputType) {
18
- // Exact match is always compatible
19
- if (outputType === inputType) {
20
- return false;
21
- }
22
- // String can accept almost anything (it's broad)
23
- if (inputType === 'string') {
24
- return false;
25
- }
26
- // Incompatible type pairs
27
- const incompatiblePairs = [
28
- ['string', 'number'],
29
- ['string', 'boolean'],
30
- ['string', 'integer'],
31
- ['number', 'boolean'],
32
- ['boolean', 'number'],
33
- ['array', 'object'],
34
- ['object', 'array'],
35
- ];
36
- for (const [from, to] of incompatiblePairs) {
37
- if (outputType === from && inputType === to) {
38
- return true;
39
- }
40
- }
41
- return false;
42
- }
43
- class E003TypeMismatchRule extends BaseRule {
44
- id = ERROR_CODES.E003;
45
- severity = 'error';
46
- ruleName = 'Unsafe Tool Chaining (Type Mismatch)';
47
- description = 'Output type incompatible with downstream input type. Tool chains will break.';
48
- check(ctx) {
49
- const diagnostics = [];
50
- // Only check high-confidence dependencies (>= 0.8)
51
- const highConfidenceDeps = ctx.dependencies.filter(d => d.confidence >= 0.8);
52
- for (const dep of highConfidenceDeps) {
53
- // Find the output and input fields
54
- const fromTool = ctx.indexes.toolIndex.get(dep.fromTool.toLowerCase());
55
- const toTool = ctx.indexes.toolIndex.get(dep.toTool.toLowerCase());
56
- if (!fromTool || !toTool) {
57
- continue;
58
- }
59
- const fromField = fromTool.outputs.find(f => f.name === dep.fromField);
60
- const toField = toTool.inputs.find(f => f.name === dep.toField);
61
- if (!fromField || !toField) {
62
- continue;
63
- }
64
- // Check type compatibility
65
- if (areTypesIncompatible(fromField.type, toField.type)) {
66
- diagnostics.push(this.createDiagnostic(`Tool "${dep.toTool}" parameter "${dep.toField}" depends on output from "${dep.fromTool}", but types are incompatible (${fromField.type} → ${toField.type}).`, dep.toTool, dep.toField, `Ensure "${dep.fromTool}" outputs ${toField.type}, or modify "${dep.toTool}" to accept ${fromField.type}.`));
67
- }
68
- }
69
- return diagnostics;
70
- }
71
- }
72
- export const E003TypeMismatch = new E003TypeMismatchRule();
73
- //# sourceMappingURL=e003-type-mismatch.js.map
@@ -1,23 +0,0 @@
1
- /**
2
- * E004: Unsafe Tool Chaining (Free Text Propagation)
3
- *
4
- * Condition:
5
- * - Output is unconstrained string
6
- * - Used as input to another tool
7
- *
8
- * Why:
9
- * - LLM passes sentences instead of data
10
- * - Most common real-world failure
11
- */
12
- import { BaseRule } from '../base.js';
13
- import type { AnalysisContext, Diagnostic } from '../../types.js';
14
- declare class E004FreeTextPropagationRule extends BaseRule {
15
- readonly id: "E004";
16
- readonly severity: "error";
17
- readonly ruleName = "Unsafe Tool Chaining (Free Text Propagation)";
18
- readonly description = "Free-text output is used by another tool. This is unsafe without constraints.";
19
- check(ctx: AnalysisContext): Diagnostic[];
20
- }
21
- export declare const E004FreeTextPropagation: E004FreeTextPropagationRule;
22
- export {};
23
- //# sourceMappingURL=e004-free-text-propagation.d.ts.map
@@ -1,47 +0,0 @@
1
- /**
2
- * E004: Unsafe Tool Chaining (Free Text Propagation)
3
- *
4
- * Condition:
5
- * - Output is unconstrained string
6
- * - Used as input to another tool
7
- *
8
- * Why:
9
- * - LLM passes sentences instead of data
10
- * - Most common real-world failure
11
- */
12
- import { BaseRule } from '../base.js';
13
- import { ERROR_CODES } from '../error-codes.js';
14
- class E004FreeTextPropagationRule extends BaseRule {
15
- id = ERROR_CODES.E004;
16
- severity = 'error';
17
- ruleName = 'Unsafe Tool Chaining (Free Text Propagation)';
18
- description = 'Free-text output is used by another tool. This is unsafe without constraints.';
19
- check(ctx) {
20
- const diagnostics = [];
21
- // Only check high-confidence dependencies (>= 0.8)
22
- const highConfidenceDeps = ctx.dependencies.filter(d => d.confidence >= 0.8);
23
- for (const dep of highConfidenceDeps) {
24
- // Find the output field
25
- const fromTool = ctx.indexes.toolIndex.get(dep.fromTool.toLowerCase());
26
- if (!fromTool) {
27
- continue;
28
- }
29
- const fromField = fromTool.outputs.find(f => f.name === dep.fromField);
30
- if (!fromField) {
31
- continue;
32
- }
33
- // Check if output is unconstrained string
34
- const isString = fromField.type === 'string';
35
- const hasEnum = Boolean(fromField.enum && fromField.enum.length > 0);
36
- const hasPattern = Boolean(fromField.pattern);
37
- const hasDescription = Boolean(fromField.description && fromField.description.trim());
38
- // If it's a string with no constraints, it's free text
39
- if (isString && !hasEnum && !hasPattern && !hasDescription) {
40
- diagnostics.push(this.createDiagnostic(`Free-text output from "${dep.fromTool}" (field: "${dep.fromField}") is used by "${dep.toTool}". This is unsafe without constraints.`, dep.toTool, dep.toField, `Constrain the output of "${dep.fromTool}" by adding enum values, regex pattern, or clear description of expected format.`));
41
- }
42
- }
43
- return diagnostics;
44
- }
45
- }
46
- export const E004FreeTextPropagation = new E004FreeTextPropagationRule();
47
- //# sourceMappingURL=e004-free-text-propagation.js.map