@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
@@ -4,10 +4,12 @@
4
4
  */
5
5
  import * as fs from 'fs';
6
6
  import * as path from 'path';
7
- import { load } from 'js-yaml';
7
+ import { load, dump } from 'js-yaml';
8
8
  import { validateConfig } from './schema.js';
9
9
  import { ConfigurationError } from '../utils/errors.js';
10
10
  import { Paths, Messages, ToolCommands } from '../constants/index.js';
11
+ import { loadGlobalConfig } from './global-loader.js';
12
+ import { mergeConfigs, createConfigFromGlobal, } from './merger.js';
11
13
  /**
12
14
  * Load configuration from syrin.yaml file.
13
15
  * @param projectRoot - Root directory of the project (defaults to current working directory)
@@ -15,13 +17,37 @@ import { Paths, Messages, ToolCommands } from '../constants/index.js';
15
17
  * @throws {ConfigurationError} If config file is missing or invalid
16
18
  */
17
19
  export function loadConfig(projectRoot = process.cwd()) {
18
- const configPath = path.join(projectRoot, Paths.CONFIG_FILE);
19
- // Check if config file exists
20
- if (!fs.existsSync(configPath)) {
20
+ const config = loadConfigOptional(projectRoot);
21
+ if (config === null) {
22
+ const configPath = path.join(projectRoot, Paths.CONFIG_FILE);
21
23
  throw new ConfigurationError(Messages.ERROR_CONFIG_NOT_FOUND(ToolCommands.INIT), {
22
24
  context: { projectRoot, configPath },
23
25
  });
24
26
  }
27
+ return config;
28
+ }
29
+ /**
30
+ * Check if a configuration file exists.
31
+ * @param projectRoot - Root directory of the project
32
+ * @returns true if config file exists
33
+ */
34
+ export function configExists(projectRoot = process.cwd()) {
35
+ const configPath = path.join(projectRoot, Paths.CONFIG_FILE);
36
+ return fs.existsSync(configPath);
37
+ }
38
+ /**
39
+ * Load configuration from syrin.yaml file (optional).
40
+ * Returns null if file doesn't exist instead of throwing.
41
+ * @param projectRoot - Root directory of the project (defaults to current working directory)
42
+ * @returns Loaded and validated configuration, or null if file doesn't exist
43
+ * @throws {ConfigurationError} If config file exists but is invalid
44
+ */
45
+ export function loadConfigOptional(projectRoot = process.cwd()) {
46
+ const configPath = path.join(projectRoot, Paths.CONFIG_FILE);
47
+ // Check if config file exists
48
+ if (!fs.existsSync(configPath)) {
49
+ return null;
50
+ }
25
51
  try {
26
52
  // Read and parse YAML file
27
53
  const configContent = fs.readFileSync(configPath, 'utf-8');
@@ -46,12 +72,72 @@ export function loadConfig(projectRoot = process.cwd()) {
46
72
  }
47
73
  }
48
74
  /**
49
- * Check if a configuration file exists.
50
- * @param projectRoot - Root directory of the project
51
- * @returns true if config file exists
75
+ * Load configuration with global fallback.
76
+ * Tries local config first, then global config, then throws error.
77
+ * @param projectRoot - Root directory of the project (defaults to current working directory)
78
+ * @param flags - CLI flags that can override config values
79
+ * @returns Loaded and merged configuration
80
+ * @throws {Error} If neither local nor global config exists
52
81
  */
53
- export function configExists(projectRoot = process.cwd()) {
82
+ export function loadConfigWithGlobal(projectRoot = process.cwd(), flags = {}) {
83
+ const localConfig = loadConfigOptional(projectRoot);
84
+ const globalConfig = loadGlobalConfig();
85
+ if (localConfig) {
86
+ return { config: mergeConfigs(localConfig, globalConfig), source: 'local' };
87
+ }
88
+ if (globalConfig) {
89
+ const adaptedConfig = createConfigFromGlobal(globalConfig, flags);
90
+ return { config: adaptedConfig, source: 'global' };
91
+ }
92
+ throw new ConfigurationError(Messages.ERROR_CONFIG_NOT_FOUND(ToolCommands.INIT), {
93
+ context: { projectRoot },
94
+ });
95
+ }
96
+ /**
97
+ * Save local configuration to syrin.yaml file.
98
+ * @param config - Configuration to save
99
+ * @param projectRoot - Root directory of the project (defaults to current working directory)
100
+ * @throws {ConfigurationError} If saving fails
101
+ */
102
+ export function saveLocalConfig(config, projectRoot = process.cwd()) {
54
103
  const configPath = path.join(projectRoot, Paths.CONFIG_FILE);
55
- return fs.existsSync(configPath);
104
+ try {
105
+ // Convert config to plain object for YAML serialization
106
+ const configObject = {
107
+ version: config.version,
108
+ project_name: config.project_name,
109
+ agent_name: config.agent_name,
110
+ transport: config.transport,
111
+ ...(config.mcp_url ? { mcp_url: config.mcp_url } : {}),
112
+ ...(config.script ? { script: config.script } : {}),
113
+ llm: Object.fromEntries(Object.entries(config.llm).map(([key, provider]) => [
114
+ key,
115
+ {
116
+ ...(provider.API_KEY !== undefined
117
+ ? { API_KEY: provider.API_KEY }
118
+ : {}),
119
+ ...(provider.MODEL_NAME !== undefined
120
+ ? { MODEL_NAME: provider.MODEL_NAME }
121
+ : {}),
122
+ default: provider.default,
123
+ },
124
+ ])),
125
+ ...(config.check ? { check: config.check } : {}),
126
+ };
127
+ // Serialize to YAML
128
+ const yamlContent = dump(configObject, {
129
+ indent: 2,
130
+ lineWidth: 100,
131
+ quotingType: '"',
132
+ });
133
+ // Write to file
134
+ fs.writeFileSync(configPath, yamlContent, 'utf-8');
135
+ }
136
+ catch (error) {
137
+ throw new ConfigurationError(`Failed to save local configuration: ${error instanceof Error ? error.message : String(error)}`, {
138
+ cause: error instanceof Error ? error : new Error(String(error)),
139
+ context: { configPath },
140
+ });
141
+ }
56
142
  }
57
143
  //# sourceMappingURL=loader.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Configuration merger.
3
+ * Merges local and global configurations with proper precedence.
4
+ */
5
+ import type { SyrinConfig, GlobalSyrinConfig } from './types.js';
6
+ /**
7
+ * CLI flags that can override config values.
8
+ */
9
+ export interface CLIConfigFlags {
10
+ /** Transport type override */
11
+ transport?: 'stdio' | 'http';
12
+ /** MCP URL override (for http transport) */
13
+ mcp_url?: string;
14
+ /** Script command override (for stdio transport) */
15
+ script?: string;
16
+ /** LLM provider override */
17
+ llm?: string;
18
+ }
19
+ /**
20
+ * Merge local and global configurations.
21
+ * Local LLM providers override global, but other global providers are kept.
22
+ * @param local - Local configuration (from ./syrin.yaml) or null
23
+ * @param global - Global configuration (from ~/.syrin/syrin.yaml) or null
24
+ * @param flags - CLI flags that override config values
25
+ * @returns Merged configuration
26
+ * @throws {Error} If neither local nor global config exists
27
+ */
28
+ export declare function mergeConfigs(local: SyrinConfig | null, global: GlobalSyrinConfig | null, flags?: CLIConfigFlags): SyrinConfig;
29
+ /**
30
+ * Create a full SyrinConfig from global config and CLI flags.
31
+ * @param global - Global configuration
32
+ * @param flags - CLI flags that provide required fields
33
+ * @returns Full configuration
34
+ * @throws {Error} If required fields are missing
35
+ */
36
+ export declare function createConfigFromGlobal(global: GlobalSyrinConfig, flags?: CLIConfigFlags): SyrinConfig;
37
+ //# sourceMappingURL=merger.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Configuration merger.
3
+ * Merges local and global configurations with proper precedence.
4
+ */
5
+ import { makeProjectName, makeMCPURL, makeScriptCommand, } from '../types/factories.js';
6
+ /**
7
+ * Merge local and global configurations.
8
+ * Local LLM providers override global, but other global providers are kept.
9
+ * @param local - Local configuration (from ./syrin.yaml) or null
10
+ * @param global - Global configuration (from ~/.syrin/syrin.yaml) or null
11
+ * @param flags - CLI flags that override config values
12
+ * @returns Merged configuration
13
+ * @throws {Error} If neither local nor global config exists
14
+ */
15
+ export function mergeConfigs(local, global, flags = {}) {
16
+ // If local config exists, use it as base and merge LLM providers
17
+ if (local) {
18
+ const mergedLLM = {
19
+ ...(global?.llm || {}), // Start with global LLM providers
20
+ ...local.llm, // Override with local LLM providers
21
+ };
22
+ // Apply CLI flag overrides
23
+ const finalConfig = {
24
+ ...local,
25
+ llm: mergedLLM,
26
+ transport: flags.transport || local.transport,
27
+ mcp_url: flags.mcp_url ? makeMCPURL(flags.mcp_url) : local.mcp_url,
28
+ script: flags.script ? makeScriptCommand(flags.script) : local.script,
29
+ };
30
+ return finalConfig;
31
+ }
32
+ // No local config - create from global with defaults
33
+ if (!global) {
34
+ throw new Error('No configuration found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
35
+ }
36
+ // Create config from global
37
+ return createConfigFromGlobal(global, flags);
38
+ }
39
+ /**
40
+ * Create a full SyrinConfig from global config and CLI flags.
41
+ * @param global - Global configuration
42
+ * @param flags - CLI flags that provide required fields
43
+ * @returns Full configuration
44
+ * @throws {Error} If required fields are missing
45
+ */
46
+ export function createConfigFromGlobal(global, flags = {}) {
47
+ // Transport is required - must come from flags
48
+ if (!flags.transport) {
49
+ throw new Error('Transport type is required when using global config. Use --transport <stdio|http> flag.');
50
+ }
51
+ // Validate transport-specific requirements
52
+ if (flags.transport === 'http' && !flags.mcp_url) {
53
+ throw new Error('MCP URL is required for HTTP transport. Use --mcp-url <url> flag.');
54
+ }
55
+ if (flags.transport === 'stdio' && !flags.script) {
56
+ throw new Error('Script command is required for stdio transport. Use --script <command> flag.');
57
+ }
58
+ return {
59
+ version: global.version,
60
+ project_name: makeProjectName('GlobalSyrin'),
61
+ agent_name: global.agent_name,
62
+ transport: flags.transport,
63
+ mcp_url: flags.mcp_url ? makeMCPURL(flags.mcp_url) : undefined,
64
+ script: flags.script ? makeScriptCommand(flags.script) : undefined,
65
+ llm: global.llm,
66
+ };
67
+ }
68
+ //# sourceMappingURL=merger.js.map
@@ -3,7 +3,7 @@
3
3
  * Provides runtime validation for the Syrin configuration file.
4
4
  */
5
5
  import { z } from 'zod';
6
- import type { SyrinConfig } from '../config/types.js';
6
+ import type { SyrinConfig, GlobalSyrinConfig } from '../config/types.js';
7
7
  /**
8
8
  * Main configuration schema.
9
9
  */
@@ -38,5 +38,30 @@ export declare const ConfigSchema: z.ZodObject<{
38
38
  * Type inference from schema.
39
39
  */
40
40
  export type ConfigSchemaType = z.infer<typeof ConfigSchema>;
41
+ /**
42
+ * Global configuration schema (LLM settings only).
43
+ * No transport, mcp_url, or script fields.
44
+ */
45
+ export declare const GlobalConfigSchema: z.ZodObject<{
46
+ version: z.ZodString;
47
+ project_name: z.ZodLiteral<"GlobalSyrin">;
48
+ agent_name: z.ZodString;
49
+ llm: z.ZodRecord<z.ZodString, z.ZodObject<{
50
+ API_KEY: z.ZodOptional<z.ZodString>;
51
+ MODEL_NAME: z.ZodOptional<z.ZodString>;
52
+ default: z.ZodOptional<z.ZodBoolean>;
53
+ }, z.core.$strip>>;
54
+ }, z.core.$strip>;
55
+ /**
56
+ * Type inference from global schema.
57
+ */
58
+ export type GlobalConfigSchemaType = z.infer<typeof GlobalConfigSchema>;
41
59
  export declare function validateConfig(config: unknown): SyrinConfig;
60
+ /**
61
+ * Validate a global configuration object against the schema.
62
+ * @param config - Global configuration object to validate
63
+ * @returns Validated global configuration with opaque types
64
+ * @throws {ConfigurationError} If validation fails
65
+ */
66
+ export declare function validateGlobalConfig(config: unknown): GlobalSyrinConfig;
42
67
  //# sourceMappingURL=schema.d.ts.map
@@ -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