@syrin/cli 1.3.2 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +184 -152
  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 +23 -23
  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
@@ -5,6 +5,20 @@
5
5
  import { z } from 'zod';
6
6
  import { ConfigurationError } from '../utils/errors.js';
7
7
  import { makeProjectName, makeAgentName, makeMCPURL, makeAPIKey, makeModelName, makeScriptCommand, makeSyrinVersion, } from '../types/factories.js';
8
+ /**
9
+ * Helper function to check if at least one LLM provider is set as default.
10
+ * @param llm - Record of LLM provider configurations
11
+ * @returns true if at least one provider has default === true
12
+ */
13
+ function hasDefaultLLMProvider(llm) {
14
+ const providers = Object.values(llm);
15
+ return providers.some(provider => {
16
+ return (provider &&
17
+ typeof provider === 'object' &&
18
+ 'default' in provider &&
19
+ provider.default === true);
20
+ });
21
+ }
8
22
  /**
9
23
  * Schema for LLM provider configuration.
10
24
  */
@@ -86,14 +100,26 @@ export const ConfigSchema = z
86
100
  message: 'script is required when transport is "stdio"',
87
101
  path: ['script'],
88
102
  })
89
- .refine(data => {
90
- // At least one LLM provider should be set as default
91
- const providers = Object.values(data.llm);
92
- const hasDefault = providers.some(provider => {
93
- return (provider && typeof provider === 'object' && provider.default === true);
94
- });
95
- return hasDefault;
96
- }, {
103
+ .refine(data => hasDefaultLLMProvider(data.llm), {
104
+ message: 'At least one LLM provider must be set as default',
105
+ path: ['llm'],
106
+ });
107
+ /**
108
+ * Global configuration schema (LLM settings only).
109
+ * No transport, mcp_url, or script fields.
110
+ */
111
+ export const GlobalConfigSchema = z
112
+ .object({
113
+ version: z.string().min(1, 'Version is required'),
114
+ project_name: z.literal('GlobalSyrin'),
115
+ agent_name: z.string().min(1, 'Agent name is required'),
116
+ llm: z
117
+ .record(z.string(), LLMProviderSchema)
118
+ .refine(obj => Object.keys(obj).length > 0, {
119
+ message: 'At least one LLM provider is required',
120
+ }),
121
+ })
122
+ .refine(data => hasDefaultLLMProvider(data.llm), {
97
123
  message: 'At least one LLM provider must be set as default',
98
124
  path: ['llm'],
99
125
  });
@@ -178,4 +204,43 @@ export function validateConfig(config) {
178
204
  throw error;
179
205
  }
180
206
  }
207
+ /**
208
+ * Validate a global configuration object against the schema.
209
+ * @param config - Global configuration object to validate
210
+ * @returns Validated global configuration with opaque types
211
+ * @throws {ConfigurationError} If validation fails
212
+ */
213
+ export function validateGlobalConfig(config) {
214
+ try {
215
+ const parsed = GlobalConfigSchema.parse(config);
216
+ // Transform to opaque types
217
+ // Note: project_name must be literal 'GlobalSyrin' for global config
218
+ const validated = {
219
+ version: makeSyrinVersion(parsed.version),
220
+ project_name: 'GlobalSyrin',
221
+ agent_name: makeAgentName(parsed.agent_name),
222
+ llm: Object.fromEntries(Object.entries(parsed.llm).map(([key, provider]) => [
223
+ key,
224
+ {
225
+ API_KEY: provider.API_KEY
226
+ ? makeAPIKey(provider.API_KEY)
227
+ : undefined,
228
+ MODEL_NAME: provider.MODEL_NAME
229
+ ? makeModelName(provider.MODEL_NAME)
230
+ : undefined,
231
+ default: provider.default,
232
+ },
233
+ ])),
234
+ };
235
+ return validated;
236
+ }
237
+ catch (error) {
238
+ if (error instanceof z.ZodError) {
239
+ throw new ConfigurationError(formatValidationError(error), {
240
+ context: { errors: error.issues },
241
+ });
242
+ }
243
+ throw error;
244
+ }
245
+ }
181
246
  //# sourceMappingURL=schema.js.map
@@ -50,6 +50,25 @@ export interface SyrinConfig {
50
50
  /** Tool testing configuration (v1.3.0) */
51
51
  check?: CheckConfig;
52
52
  }
53
+ /**
54
+ * Global Syrin configuration (LLM settings only).
55
+ * Stored at ~/.syrin/syrin.yaml
56
+ */
57
+ export interface GlobalSyrinConfig {
58
+ /** Configuration version */
59
+ version: SyrinVersion;
60
+ /** Project name (always "GlobalSyrin") */
61
+ project_name: 'GlobalSyrin';
62
+ /** Agent name (defaults to system username, can be customized) */
63
+ agent_name: AgentName;
64
+ /** LLM provider configurations */
65
+ llm: Record<string, LLMProviderConfig>;
66
+ }
67
+ /**
68
+ * Get default agent name from system.
69
+ * Tries: process.env.USER -> process.env.USERNAME -> os.userInfo().username -> 'Syrin'
70
+ */
71
+ export declare function getDefaultAgentName(): string;
53
72
  /**
54
73
  * Options for initializing a new Syrin project.
55
74
  */
@@ -2,5 +2,30 @@
2
2
  * Configuration type definitions for Syrin.
3
3
  * These types represent the structure of the syrin.yaml file.
4
4
  */
5
- export {};
5
+ import * as os from 'os';
6
+ /**
7
+ * Get default agent name from system.
8
+ * Tries: process.env.USER -> process.env.USERNAME -> os.userInfo().username -> 'Syrin'
9
+ */
10
+ export function getDefaultAgentName() {
11
+ // Try process.env first (safest)
12
+ if (process.env.USER) {
13
+ return process.env.USER;
14
+ }
15
+ if (process.env.USERNAME) {
16
+ return process.env.USERNAME;
17
+ }
18
+ // Try os.userInfo() with error handling (can throw on some platforms)
19
+ try {
20
+ const username = os.userInfo().username;
21
+ if (username) {
22
+ return username;
23
+ }
24
+ }
25
+ catch {
26
+ // Swallow any exceptions from os.userInfo()
27
+ }
28
+ // Fallback to default
29
+ return 'Syrin';
30
+ }
6
31
  //# sourceMappingURL=types.js.map
@@ -17,6 +17,13 @@ export declare const Messages: {
17
17
  readonly INIT_SETUP_ENV_VARS: "Set up your environment variables (API keys, etc.)";
18
18
  readonly INIT_RUN_DOCTOR: (doctorCommand: string) => string;
19
19
  readonly INIT_RUN_DEV: (devCommand: string) => string;
20
+ readonly GLOBAL_INIT_ALREADY_INITIALIZED: "Global Syrin configuration already exists!";
21
+ readonly GLOBAL_INIT_CONFIG_FILE: (configPath: string) => string;
22
+ readonly GLOBAL_INIT_SUCCESS: "Global Syrin configuration created successfully!";
23
+ readonly GLOBAL_INIT_EDIT_CONFIG: "Run `syrin config edit --global` to edit the configuration";
24
+ readonly GLOBAL_INIT_EDIT_ENV: "Run `syrin config edit-env --global` to set API keys";
25
+ readonly GLOBAL_INIT_REINITIALIZE_TIP: "Want to re-initialize?";
26
+ readonly GLOBAL_INIT_REINITIALIZE_INSTRUCTION: "Delete ~/.syrin/syrin.yaml and run `syrin init --global` again.";
20
27
  readonly ERROR_UNEXPECTED: "An unexpected error occurred";
21
28
  readonly ERROR_LOADING_CONFIG: "Failed to load configuration file";
22
29
  readonly ERROR_VALIDATION_FAILED: "Configuration validation failed";
@@ -19,6 +19,14 @@ export const Messages = {
19
19
  INIT_SETUP_ENV_VARS: 'Set up your environment variables (API keys, etc.)',
20
20
  INIT_RUN_DOCTOR: (doctorCommand) => `Run \`${doctorCommand}\` to verify your Syrin setup`,
21
21
  INIT_RUN_DEV: (devCommand) => `Run \`${devCommand}\` to start development mode`,
22
+ // Global Init Command Messages
23
+ GLOBAL_INIT_ALREADY_INITIALIZED: 'Global Syrin configuration already exists!',
24
+ GLOBAL_INIT_CONFIG_FILE: (configPath) => `Configuration file: ${configPath}`,
25
+ GLOBAL_INIT_SUCCESS: 'Global Syrin configuration created successfully!',
26
+ GLOBAL_INIT_EDIT_CONFIG: 'Run `syrin config edit --global` to edit the configuration',
27
+ GLOBAL_INIT_EDIT_ENV: 'Run `syrin config edit-env --global` to set API keys',
28
+ GLOBAL_INIT_REINITIALIZE_TIP: 'Want to re-initialize?',
29
+ GLOBAL_INIT_REINITIALIZE_INSTRUCTION: 'Delete ~/.syrin/syrin.yaml and run `syrin init --global` again.',
22
30
  // Error Messages
23
31
  ERROR_UNEXPECTED: 'An unexpected error occurred',
24
32
  ERROR_LOADING_CONFIG: 'Failed to load configuration file',
@@ -19,6 +19,12 @@ export declare const Paths: {
19
19
  readonly DEV_HISTORY_FILE: ".syrin/.dev-history";
20
20
  /** Data directory for JSON exports (relative to project root) */
21
21
  readonly DATA_DIR: ".syrin/data";
22
+ /** Global Syrin configuration directory (absolute path) */
23
+ readonly GLOBAL_SYRIN_DIR: string;
24
+ /** Global configuration file (absolute path) - derived from GLOBAL_SYRIN_DIR */
25
+ readonly GLOBAL_CONFIG_FILE: string;
26
+ /** Global environment file (absolute path) - derived from GLOBAL_SYRIN_DIR */
27
+ readonly GLOBAL_ENV_FILE: string;
22
28
  };
23
29
  /**
24
30
  * File extension constants.
@@ -2,6 +2,10 @@
2
2
  * File and directory path constants.
3
3
  * Centralized paths used throughout the application.
4
4
  */
5
+ import * as os from 'os';
6
+ import * as path from 'path';
7
+ // Compute global directory once
8
+ const GLOBAL_SYRIN_DIR = path.join(os.homedir(), '.syrin');
5
9
  export const Paths = {
6
10
  /** Syrin configuration directory name */
7
11
  SYRIN_DIR: '.syrin',
@@ -19,6 +23,12 @@ export const Paths = {
19
23
  DEV_HISTORY_FILE: '.syrin/.dev-history',
20
24
  /** Data directory for JSON exports (relative to project root) */
21
25
  DATA_DIR: '.syrin/data',
26
+ /** Global Syrin configuration directory (absolute path) */
27
+ GLOBAL_SYRIN_DIR,
28
+ /** Global configuration file (absolute path) - derived from GLOBAL_SYRIN_DIR */
29
+ GLOBAL_CONFIG_FILE: path.join(GLOBAL_SYRIN_DIR, 'syrin.yaml'),
30
+ /** Global environment file (absolute path) - derived from GLOBAL_SYRIN_DIR */
31
+ GLOBAL_ENV_FILE: path.join(GLOBAL_SYRIN_DIR, '.env'),
22
32
  };
23
33
  /**
24
34
  * File extension constants.
@@ -1,5 +1,5 @@
1
1
  import { makeEventID, makeTimestamp } from '../types/factories.js';
2
- import { logger } from '../utils/logger.js';
2
+ import { log } from '../utils/logger.js';
3
3
  import { EventStoreError } from '../utils/errors.js';
4
4
  /**
5
5
  * Runtime event emitter implementation.
@@ -37,7 +37,7 @@ export class RuntimeEventEmitter {
37
37
  if (appendResult instanceof Promise) {
38
38
  return appendResult
39
39
  .then(() => {
40
- logger.debug(`Event emitted: ${eventType}`, {
40
+ log.debug(`Event emitted: ${eventType}`, {
41
41
  event_id: event.event_id,
42
42
  sequence: event.sequence,
43
43
  });
@@ -47,7 +47,7 @@ export class RuntimeEventEmitter {
47
47
  })
48
48
  .catch((error) => {
49
49
  const err = error instanceof Error ? error : new Error(String(error));
50
- logger.error('Failed to persist event', err, {
50
+ log.error(`Error: ${err.message}`, err, {
51
51
  event_id: event.event_id,
52
52
  event_type: eventType,
53
53
  });
@@ -59,7 +59,7 @@ export class RuntimeEventEmitter {
59
59
  });
60
60
  }
61
61
  // Sync store
62
- logger.debug(`Event emitted: ${eventType}`, {
62
+ log.debug(`Event emitted: ${eventType}`, {
63
63
  event_id: event.event_id,
64
64
  sequence: event.sequence,
65
65
  });
@@ -69,7 +69,7 @@ export class RuntimeEventEmitter {
69
69
  }
70
70
  catch (error) {
71
71
  const err = error instanceof Error ? error : new Error(String(error));
72
- logger.error('Failed to emit event', err, {
72
+ log.error(`Error: ${err.message}`, err, {
73
73
  event_id: event.event_id,
74
74
  event_type: eventType,
75
75
  });
@@ -119,7 +119,7 @@ export class RuntimeEventEmitter {
119
119
  if (result instanceof Promise) {
120
120
  result.catch((error) => {
121
121
  const err = error instanceof Error ? error : new Error(String(error));
122
- logger.error('Error in event subscriber', err, {
122
+ log.error(`Error: ${err.message}`, err, {
123
123
  event_id: event.event_id,
124
124
  event_type: event.event_type,
125
125
  });
@@ -128,7 +128,7 @@ export class RuntimeEventEmitter {
128
128
  }
129
129
  catch (error) {
130
130
  const err = error instanceof Error ? error : new Error(String(error));
131
- logger.error('Error in event subscriber', err, {
131
+ log.error(`Error: ${err.message}`, err, {
132
132
  event_id: event.event_id,
133
133
  event_type: event.event_type,
134
134
  });
package/dist/index.js CHANGED
File without changes
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Presentation layer for config command UI.
3
+ * Separates display logic from business logic.
4
+ */
5
+ import type { SyrinConfig, GlobalSyrinConfig } from '../config/types.js';
6
+ /**
7
+ * Display configuration list/show output.
8
+ */
9
+ export declare function displayConfigList(config: SyrinConfig | GlobalSyrinConfig, context: 'local' | 'global', configPath: string): void;
10
+ /**
11
+ * Display success message for config update.
12
+ */
13
+ export declare function displayConfigUpdated(key: string, value: string, context: 'local' | 'global', configPath: string): void;
14
+ /**
15
+ * Display message for opening editor.
16
+ */
17
+ export declare function displayEditorOpening(context: 'local' | 'global', filePath: string, editorName: string): void;
18
+ /**
19
+ * Display message after editor closes.
20
+ */
21
+ export declare function displayEditorClosed(fileType: 'config' | 'env'): void;
22
+ /**
23
+ * Display setup completion message.
24
+ */
25
+ export declare function displaySetupComplete(isGlobal: boolean): void;
26
+ /**
27
+ * Display default provider set message.
28
+ */
29
+ export declare function displayDefaultProviderSet(provider: string, context: 'local' | 'global'): void;
30
+ /**
31
+ * Display provider removed message.
32
+ */
33
+ export declare function displayProviderRemoved(provider: string, context: 'local' | 'global'): void;
34
+ //# sourceMappingURL=config-ui.d.ts.map
@@ -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
  * ```