@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
@@ -2,11 +2,16 @@
2
2
  * `syrin doctor` command implementation.
3
3
  * Validates the Syrin project configuration and setup.
4
4
  */
5
- import { loadConfig } from '../../config/loader.js';
5
+ import { loadConfigOptional } from '../../config/loader.js';
6
6
  import { checkEnvVar, checkCommandExists, extractCommandName, } from '../../config/env-checker.js';
7
7
  import { handleCommandError } from '../../cli/utils/index.js';
8
- import { Messages, TransportTypes, Defaults, LLMProviders } from '../../constants/index.js';
9
- import { displayDoctorReport } from '../../presentation/doctor-ui.js';
8
+ import { showVersionBanner } from '../../cli/utils/version-banner.js';
9
+ import { Messages, TransportTypes, Defaults, LLMProviders, Paths, } from '../../constants/index.js';
10
+ import { displayDoctorReport, displayGlobalConfigValidation, } from '../../presentation/doctor-ui.js';
11
+ import { loadGlobalConfig, getGlobalConfigPath, getGlobalEnvPath, } from '../../config/global-loader.js';
12
+ import * as path from 'path';
13
+ import * as fs from 'fs';
14
+ import * as os from 'os';
10
15
  /**
11
16
  * Check transport configuration.
12
17
  */
@@ -92,7 +97,7 @@ function checkScript(config) {
92
97
  /**
93
98
  * Check LLM provider configuration.
94
99
  */
95
- function checkLLMProviders(config, projectRoot) {
100
+ function checkLLMProviders(config, projectRoot, isGlobalContext = false) {
96
101
  const checks = [];
97
102
  for (const [providerName, providerConfig] of Object.entries(config.llm)) {
98
103
  if (providerName === LLMProviders.OLLAMA) {
@@ -106,8 +111,8 @@ function checkLLMProviders(config, projectRoot) {
106
111
  const modelVar = providerConfig.MODEL_NAME
107
112
  ? String(providerConfig.MODEL_NAME)
108
113
  : '';
109
- const apiKeyCheck = checkEnvVar(apiKeyVar, projectRoot);
110
- const modelCheck = checkEnvVar(modelVar, projectRoot);
114
+ const apiKeyCheck = checkEnvVar(apiKeyVar, projectRoot, isGlobalContext);
115
+ const modelCheck = checkEnvVar(modelVar, projectRoot, isGlobalContext);
111
116
  checks.push({
112
117
  provider: providerName,
113
118
  apiKeyCheck: {
@@ -128,6 +133,7 @@ function checkLLMProviders(config, projectRoot) {
128
133
  : modelCheck.errorMessage || Messages.ENV_SET_INSTRUCTIONS(modelVar),
129
134
  },
130
135
  isDefault: providerConfig.default === true,
136
+ envSource: apiKeyCheck.source || modelCheck.source,
131
137
  });
132
138
  }
133
139
  return checks;
@@ -135,7 +141,7 @@ function checkLLMProviders(config, projectRoot) {
135
141
  /**
136
142
  * Check local LLM providers.
137
143
  */
138
- function checkLocalLLMProviders(config, projectRoot) {
144
+ function checkLocalLLMProviders(config, projectRoot, isGlobalContext = false) {
139
145
  const checks = [];
140
146
  for (const [providerName, providerConfig] of Object.entries(config.llm)) {
141
147
  if (providerName === LLMProviders.OLLAMA) {
@@ -143,7 +149,7 @@ function checkLocalLLMProviders(config, projectRoot) {
143
149
  const modelVar = providerConfig.MODEL_NAME
144
150
  ? String(providerConfig.MODEL_NAME)
145
151
  : '';
146
- const modelCheck = checkEnvVar(modelVar, projectRoot);
152
+ const modelCheck = checkEnvVar(modelVar, projectRoot, isGlobalContext);
147
153
  checks.push({
148
154
  provider: providerName,
149
155
  check: {
@@ -159,12 +165,18 @@ function checkLocalLLMProviders(config, projectRoot) {
159
165
  /**
160
166
  * Generate doctor report.
161
167
  */
162
- function generateReport(config, projectRoot) {
168
+ function generateReport(config, projectRoot, configSource, configPath) {
169
+ // Call checkLLMProviders once and reuse the result
170
+ const llmChecks = checkLLMProviders(config, projectRoot);
171
+ const envSource = llmChecks[0]?.envSource;
163
172
  return {
164
173
  config,
174
+ configSource,
175
+ configPath,
176
+ envSource,
165
177
  transportCheck: checkTransport(config),
166
178
  scriptCheck: checkScript(config),
167
- llmChecks: checkLLMProviders(config, projectRoot),
179
+ llmChecks,
168
180
  localLlmChecks: checkLocalLLMProviders(config, projectRoot),
169
181
  };
170
182
  }
@@ -173,11 +185,35 @@ function generateReport(config, projectRoot) {
173
185
  * @param projectRoot - Project root directory (defaults to current working directory)
174
186
  */
175
187
  export async function executeDoctor(projectRoot = process.cwd()) {
188
+ await showVersionBanner();
176
189
  try {
177
- // Load configuration
178
- const config = loadConfig(projectRoot);
190
+ // Try to load local config first
191
+ const localConfig = loadConfigOptional(projectRoot);
192
+ const globalConfig = loadGlobalConfig();
193
+ let config;
194
+ let configSource;
195
+ let configPath;
196
+ if (localConfig) {
197
+ config = localConfig;
198
+ configSource = 'local';
199
+ configPath = path.join(projectRoot, Paths.CONFIG_FILE);
200
+ }
201
+ else if (globalConfig) {
202
+ // Validate global config (including LLM providers)
203
+ const globalEnvPath = getGlobalEnvPath();
204
+ const globalEnvExists = fs.existsSync(globalEnvPath);
205
+ // Run LLM validation for global config (using home directory as project root)
206
+ const globalProjectRoot = os.homedir();
207
+ const llmChecks = checkLLMProviders(globalConfig, globalProjectRoot, true);
208
+ // Display validation results using presentation layer
209
+ displayGlobalConfigValidation(getGlobalConfigPath(), globalEnvPath, globalEnvExists, llmChecks);
210
+ return;
211
+ }
212
+ else {
213
+ throw new Error('No configuration found. Create a local config with `syrin init` or set up global config with `syrin init --global`.');
214
+ }
179
215
  // Generate report
180
- const report = generateReport(config, projectRoot);
216
+ const report = generateReport(config, projectRoot, configSource, configPath);
181
217
  // Display report using Ink UI
182
218
  await displayDoctorReport(report);
183
219
  // Exit with appropriate code
@@ -7,6 +7,8 @@ export interface InitCommandOptions {
7
7
  yes?: boolean;
8
8
  /** Project root directory (defaults to current working directory) */
9
9
  projectRoot?: string;
10
+ /** Create global configuration (LLM providers only) */
11
+ global?: boolean;
10
12
  }
11
13
  /**
12
14
  * Execute the init command.
@@ -3,19 +3,26 @@
3
3
  * Initializes a new Syrin project with configuration.
4
4
  */
5
5
  import { generateConfigFile, isProjectInitialized } from '../../config/generator.js';
6
- import { promptInitOptions, getDefaultInitOptions, } from '../../cli/prompts/init-prompt.js';
6
+ import { promptInitOptions, getDefaultInitOptions, promptGlobalInitOptions, getDefaultGlobalInitOptions, } from '../../cli/prompts/init-prompt.js';
7
7
  import { ConfigurationError } from '../../utils/errors.js';
8
- import { logger } from '../../utils/logger.js';
8
+ import { log } from '../../utils/logger.js';
9
9
  import { Messages, Paths } from '../../constants/index.js';
10
10
  import { ensureGitignorePattern } from '../../utils/gitignore.js';
11
- import { displayAlreadyInitialized, displayInitSuccess, } from '../../presentation/init-ui.js';
11
+ import { displayAlreadyInitialized, displayInitSuccess, displayGlobalAlreadyInitialized, displayGlobalInitSuccess, } from '../../presentation/init-ui.js';
12
12
  import { showVersionBanner } from '../../cli/utils/version-banner.js';
13
+ import { globalConfigExists, saveGlobalConfig, getGlobalConfigPath, } from '../../config/global-loader.js';
14
+ import { makeSyrinVersion, makeAPIKey, makeModelName } from '../../types/factories.js';
13
15
  /**
14
16
  * Execute the init command.
15
17
  * @param options - Command options
16
18
  */
17
19
  export async function executeInit(options = {}) {
18
20
  await showVersionBanner();
21
+ // Handle global init
22
+ if (options.global) {
23
+ await executeGlobalInit(options);
24
+ return;
25
+ }
19
26
  const projectRoot = options.projectRoot || process.cwd();
20
27
  // Check if project is already initialized
21
28
  if (isProjectInitialized(projectRoot)) {
@@ -26,19 +33,19 @@ export async function executeInit(options = {}) {
26
33
  let initOptions;
27
34
  if (options.yes) {
28
35
  // Non-interactive mode: use defaults
29
- logger.info('Initializing project with default values (non-interactive mode)');
36
+ log.info('Initializing project with default values (non-interactive mode)');
30
37
  initOptions = getDefaultInitOptions(projectRoot);
31
38
  }
32
39
  else {
33
40
  // Interactive mode: prompt user
34
- logger.info('Starting interactive project initialization');
41
+ log.info('Starting interactive project initialization');
35
42
  try {
36
43
  initOptions = await promptInitOptions();
37
44
  }
38
45
  catch (error) {
39
46
  if (error instanceof Error && error.name === 'ExitPromptError') {
40
47
  // User cancelled
41
- logger.info('Initialization cancelled by user');
48
+ log.info('Initialization cancelled by user');
42
49
  process.exit(0);
43
50
  }
44
51
  throw error;
@@ -47,31 +54,25 @@ export async function executeInit(options = {}) {
47
54
  try {
48
55
  // Generate config file
49
56
  const configPath = generateConfigFile(initOptions, projectRoot);
50
- logger.info('Configuration file created successfully', {
51
- configPath,
52
- projectName: initOptions.projectName,
53
- transport: initOptions.transport,
54
- });
57
+ log.info(`Configuration file created successfully: ${configPath}`);
55
58
  // Update .gitignore to exclude event files and dev history
56
59
  try {
57
60
  const eventsPatternAdded = await ensureGitignorePattern(projectRoot, Paths.EVENTS_DIR);
58
61
  if (eventsPatternAdded) {
59
- logger.info(`Added ${Paths.EVENTS_DIR} to .gitignore`);
62
+ log.info(`Added ${Paths.EVENTS_DIR} to .gitignore`);
60
63
  }
61
64
  const historyPatternAdded = await ensureGitignorePattern(projectRoot, Paths.DEV_HISTORY_FILE);
62
65
  if (historyPatternAdded) {
63
- logger.info('Added .syrin/.dev-history to .gitignore');
66
+ log.info('Added .syrin/.dev-history to .gitignore');
64
67
  }
65
68
  const dataPatternAdded = await ensureGitignorePattern(projectRoot, Paths.DATA_DIR);
66
69
  if (dataPatternAdded) {
67
- logger.info(`Added ${Paths.DATA_DIR} to .gitignore`);
70
+ log.info(`Added ${Paths.DATA_DIR} to .gitignore`);
68
71
  }
69
72
  }
70
73
  catch (error) {
71
74
  // Log but don't fail initialization if .gitignore update fails
72
- logger.warn('Failed to update .gitignore', {
73
- error: error instanceof Error ? error.message : String(error),
74
- });
75
+ log.warn(`Failed to update .gitignore: ${error instanceof Error ? error.message : String(error)}`);
75
76
  }
76
77
  // Display success message
77
78
  displayInitSuccess(configPath);
@@ -83,7 +84,77 @@ export async function executeInit(options = {}) {
83
84
  cause: error instanceof Error ? error : new Error(String(error)),
84
85
  context: { projectRoot },
85
86
  });
86
- logger.error('Failed to initialize project', configError);
87
+ log.error(`Error: ${configError.message}`);
88
+ throw configError;
89
+ }
90
+ }
91
+ /**
92
+ * Execute global init command (LLM providers only).
93
+ * Creates ~/.syrin/syrin.yaml with LLM configuration.
94
+ * @param options - Command options
95
+ */
96
+ async function executeGlobalInit(options) {
97
+ // Check if global config already exists
98
+ if (globalConfigExists()) {
99
+ displayGlobalAlreadyInitialized();
100
+ return;
101
+ }
102
+ let globalOptions;
103
+ if (options.yes) {
104
+ // Non-interactive mode: use defaults
105
+ log.info('Initializing global config with default values (non-interactive mode)');
106
+ globalOptions = getDefaultGlobalInitOptions();
107
+ }
108
+ else {
109
+ // Interactive mode: prompt user
110
+ log.info('Starting interactive global initialization');
111
+ try {
112
+ globalOptions = await promptGlobalInitOptions();
113
+ }
114
+ catch (error) {
115
+ if (error instanceof Error && error.name === 'ExitPromptError') {
116
+ // User cancelled
117
+ log.info('Global initialization cancelled by user');
118
+ process.exit(0);
119
+ }
120
+ throw error;
121
+ }
122
+ }
123
+ try {
124
+ // Build GlobalSyrinConfig
125
+ const llmConfig = {};
126
+ for (const [provider, settings] of Object.entries(globalOptions.llmProviders)) {
127
+ llmConfig[provider] = {
128
+ ...('apiKey' in settings && settings.apiKey
129
+ ? { API_KEY: makeAPIKey(String(settings.apiKey)) }
130
+ : {}),
131
+ ...(settings.modelName
132
+ ? { MODEL_NAME: makeModelName(String(settings.modelName)) }
133
+ : {}),
134
+ default: settings.default || false,
135
+ };
136
+ }
137
+ const globalConfig = {
138
+ version: makeSyrinVersion('1.0'),
139
+ project_name: 'GlobalSyrin',
140
+ agent_name: globalOptions.agentName,
141
+ llm: llmConfig,
142
+ };
143
+ // Save global config
144
+ saveGlobalConfig(globalConfig);
145
+ const configPath = getGlobalConfigPath();
146
+ log.info(`Global configuration file created successfully: ${configPath}`);
147
+ // Display success message
148
+ displayGlobalInitSuccess(configPath);
149
+ }
150
+ catch (error) {
151
+ const configError = error instanceof ConfigurationError
152
+ ? error
153
+ : new ConfigurationError(Messages.ERROR_GENERATE_CONFIG, {
154
+ cause: error instanceof Error ? error : new Error(String(error)),
155
+ context: { type: 'global' },
156
+ });
157
+ log.error(`Error: ${configError.message}`);
87
158
  throw configError;
88
159
  }
89
160
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `syrin status` command implementation.
3
+ * Shows a quick overview of project health (like `git status`).
4
+ */
5
+ /**
6
+ * Execute the status command.
7
+ * @param projectRoot - Project root directory (defaults to current working directory)
8
+ */
9
+ export declare function executeStatus(projectRoot?: string): Promise<void>;
10
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `syrin status` command implementation.
3
+ * Shows a quick overview of project health (like `git status`).
4
+ */
5
+ import * as path from 'path';
6
+ import * as fs from 'fs';
7
+ import { loadConfigOptional } from '../../config/loader.js';
8
+ import { loadGlobalConfig, getGlobalConfigPath } from '../../config/global-loader.js';
9
+ import { checkEnvVar } from '../../config/env-checker.js';
10
+ import { handleCommandError } from '../../cli/utils/index.js';
11
+ import { showVersionBanner } from '../../cli/utils/version-banner.js';
12
+ import { log } from '../../utils/logger.js';
13
+ import { Paths, LLMProviders, Icons } from '../../constants/index.js';
14
+ /**
15
+ * Generate status report.
16
+ */
17
+ function generateStatusReport(projectRoot) {
18
+ const localConfig = loadConfigOptional(projectRoot);
19
+ const globalConfig = loadGlobalConfig();
20
+ const localEnvPath = path.join(projectRoot, Paths.ENV_FILE);
21
+ const hasLocalEnv = fs.existsSync(localEnvPath);
22
+ const report = {
23
+ hasLocalConfig: !!localConfig,
24
+ hasGlobalConfig: !!globalConfig,
25
+ llmProviders: [],
26
+ hasEnvFile: hasLocalEnv,
27
+ envFilePath: hasLocalEnv ? localEnvPath : undefined,
28
+ };
29
+ const config = localConfig || globalConfig;
30
+ if (!config) {
31
+ return report;
32
+ }
33
+ if (localConfig) {
34
+ report.configPath = path.join(projectRoot, Paths.CONFIG_FILE);
35
+ report.projectName = String(localConfig.project_name);
36
+ report.transport = String(localConfig.transport);
37
+ report.mcpUrl = localConfig.mcp_url
38
+ ? String(localConfig.mcp_url)
39
+ : undefined;
40
+ report.script = localConfig.script ? String(localConfig.script) : undefined;
41
+ }
42
+ else if (globalConfig) {
43
+ report.configPath = getGlobalConfigPath();
44
+ }
45
+ // Check LLM providers
46
+ for (const [providerName, providerConfig] of Object.entries(config.llm)) {
47
+ let isConfigured = false;
48
+ if (providerName === LLMProviders.OLLAMA) {
49
+ // Ollama only needs MODEL_NAME
50
+ if (providerConfig.MODEL_NAME) {
51
+ const modelCheck = checkEnvVar(String(providerConfig.MODEL_NAME), projectRoot);
52
+ isConfigured = modelCheck.isSet;
53
+ }
54
+ }
55
+ else {
56
+ // Cloud providers need API_KEY
57
+ if (providerConfig.API_KEY) {
58
+ const apiKeyCheck = checkEnvVar(String(providerConfig.API_KEY), projectRoot);
59
+ isConfigured = apiKeyCheck.isSet;
60
+ }
61
+ }
62
+ report.llmProviders.push({
63
+ name: providerName,
64
+ isDefault: providerConfig.default === true,
65
+ isConfigured,
66
+ });
67
+ }
68
+ return report;
69
+ }
70
+ /**
71
+ * Display status report.
72
+ */
73
+ function displayStatusReport(report) {
74
+ log.blank();
75
+ // Configuration Status
76
+ log.heading('Configuration');
77
+ log.plain('─'.repeat(40));
78
+ if (report.hasLocalConfig) {
79
+ log.plain(` ${Icons.SUCCESS} Local config: ${report.configPath}`);
80
+ }
81
+ else {
82
+ log.plain(` ${Icons.FAILURE} Local config: Not found`);
83
+ }
84
+ if (report.hasGlobalConfig) {
85
+ const globalPath = getGlobalConfigPath();
86
+ log.plain(` ${Icons.SUCCESS} Global config: ${globalPath}`);
87
+ }
88
+ else {
89
+ log.plain(` ${log.styleText('○', 'dim')} Global config: Not configured`);
90
+ }
91
+ log.blank();
92
+ // Project Info (if local config exists)
93
+ if (report.hasLocalConfig && report.projectName) {
94
+ log.heading('Project');
95
+ log.plain('─'.repeat(40));
96
+ log.plain(` Name: ${report.projectName}`);
97
+ log.plain(` Transport: ${report.transport}`);
98
+ if (report.mcpUrl) {
99
+ log.plain(` MCP URL: ${report.mcpUrl}`);
100
+ }
101
+ if (report.script) {
102
+ log.plain(` Script: ${report.script}`);
103
+ }
104
+ log.blank();
105
+ }
106
+ // LLM Providers
107
+ if (report.llmProviders.length > 0) {
108
+ log.heading('LLM Providers');
109
+ log.plain('─'.repeat(40));
110
+ for (const provider of report.llmProviders) {
111
+ const icon = provider.isConfigured ? Icons.SUCCESS : Icons.FAILURE;
112
+ const defaultLabel = provider.isDefault
113
+ ? log.styleText(' (default)', 'cyan')
114
+ : '';
115
+ const status = provider.isConfigured
116
+ ? log.styleText('configured', 'green')
117
+ : log.styleText('not configured', 'red');
118
+ log.plain(` ${icon} ${provider.name}${defaultLabel}: ${status}`);
119
+ }
120
+ log.blank();
121
+ }
122
+ // Environment
123
+ log.heading('Environment');
124
+ log.plain('─'.repeat(40));
125
+ if (report.hasEnvFile) {
126
+ log.plain(` ${Icons.SUCCESS} .env file: ${report.envFilePath}`);
127
+ }
128
+ else {
129
+ log.plain(` ${log.styleText('○', 'dim')} .env file: Not found (optional)`);
130
+ }
131
+ log.blank();
132
+ // Quick Actions
133
+ const hasIssues = !report.hasLocalConfig && !report.hasGlobalConfig;
134
+ const hasUnconfiguredProviders = report.llmProviders.some(p => !p.isConfigured);
135
+ if (hasIssues || hasUnconfiguredProviders) {
136
+ log.heading('Suggested Actions');
137
+ log.plain('─'.repeat(40));
138
+ if (!report.hasLocalConfig && !report.hasGlobalConfig) {
139
+ log.plain(` ${Icons.TIP} Run ${log.styleText('syrin init', 'cyan')} to initialize a project`);
140
+ }
141
+ if (hasUnconfiguredProviders) {
142
+ log.plain(` ${Icons.TIP} Run ${log.styleText('syrin doctor', 'cyan')} for detailed diagnostics`);
143
+ log.plain(` ${Icons.TIP} Run ${log.styleText('syrin config edit-env', 'cyan')} to set API keys`);
144
+ }
145
+ log.blank();
146
+ }
147
+ }
148
+ /**
149
+ * Execute the status command.
150
+ * @param projectRoot - Project root directory (defaults to current working directory)
151
+ */
152
+ export async function executeStatus(projectRoot = process.cwd()) {
153
+ await showVersionBanner();
154
+ try {
155
+ const report = generateStatusReport(projectRoot);
156
+ displayStatusReport(report);
157
+ }
158
+ catch (error) {
159
+ handleCommandError(error);
160
+ }
161
+ }
162
+ //# sourceMappingURL=status.js.map