@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
@@ -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
package/dist/cli/index.js CHANGED
@@ -11,7 +11,9 @@ import { executeAnalyse } from '../cli/commands/analyse.js';
11
11
  import { executeDev } from '../cli/commands/dev.js';
12
12
  import { executeUpdate } from '../cli/commands/update.js';
13
13
  import { executeRollback } from '../cli/commands/rollback.js';
14
- import { logger, log } from '../utils/logger.js';
14
+ import { executeStatus } from '../cli/commands/status.js';
15
+ import { executeConfigSet, executeConfigGet, executeConfigList, executeConfigShow, executeConfigEdit, executeConfigEditEnv, executeConfigSetDefault, executeConfigRemove, } from '../cli/commands/config.js';
16
+ import { log, setQuietMode, setVerboseMode } from '../utils/logger.js';
15
17
  import { Messages, ListTypes } from '../constants/index.js';
16
18
  import { getCurrentVersion } from '../utils/version-checker.js';
17
19
  /**
@@ -19,7 +21,6 @@ import { getCurrentVersion } from '../utils/version-checker.js';
19
21
  * Logs full error to internal logger and concise message to user.
20
22
  */
21
23
  function reportAnalyseError(error) {
22
- logger.error('Analyse command failed', error);
23
24
  log.error(`Analyse command failed: ${error.message}`);
24
25
  }
25
26
  const program = new Command();
@@ -32,29 +33,46 @@ export function setupCLI() {
32
33
  program
33
34
  .name('syrin')
34
35
  .description('Syrin - Runtime intelligence system for MCP servers')
35
- .version(currentVersion, '-v, --version', 'Display version number');
36
+ .version(currentVersion, '-v, --version', 'Display version number')
37
+ .option('-q, --quiet', 'Minimal output (errors only)')
38
+ .option('--verbose', 'Verbose output for debugging')
39
+ .hook('preAction', (_thisCommand, actionCommand) => {
40
+ const opts = actionCommand.optsWithGlobals();
41
+ if (opts.quiet) {
42
+ setQuietMode(true);
43
+ }
44
+ if (opts.verbose) {
45
+ setVerboseMode(true);
46
+ }
47
+ })
48
+ .addHelpText('after', '\nAliases:\n' +
49
+ ' ls Alias for list\n' +
50
+ ' doc Alias for doctor\n' +
51
+ ' cfg Alias for config');
36
52
  // init command
37
53
  program
38
54
  .command('init')
39
55
  .description('Initialize a new Syrin project')
40
56
  .option('-y, --yes', 'Skip interactive prompts and use default values')
41
57
  .option('--project-root <path>', 'Project root directory (defaults to current directory)')
58
+ .option('--global', 'Create global configuration (LLM providers only)')
42
59
  .action(async (options) => {
43
60
  try {
44
61
  await executeInit({
45
62
  yes: options.yes || false,
46
63
  projectRoot: options.projectRoot,
64
+ global: options.global || false,
47
65
  });
48
66
  }
49
67
  catch (error) {
50
68
  // Handle ALREADY_INITIALIZED case first with early return
51
- if (error instanceof Error && error.message === 'ALREADY_INITIALIZED') {
69
+ if (error instanceof Error &&
70
+ error.message === 'ALREADY_INITIALIZED') {
52
71
  // Already handled in executeInit, just exit
53
72
  return;
54
73
  }
55
74
  // Handle other Error cases
56
75
  if (error instanceof Error) {
57
- logger.error('Init command failed', error);
58
76
  log.blank();
59
77
  log.error(`Error: ${error.message}`);
60
78
  log.blank();
@@ -67,6 +85,7 @@ export function setupCLI() {
67
85
  // doctor command
68
86
  program
69
87
  .command('doctor')
88
+ .alias('doc')
70
89
  .description('Validate Syrin project configuration and setup')
71
90
  .option('--project-root <path>', 'Project root directory (defaults to current directory)')
72
91
  .action(async (options) => {
@@ -76,8 +95,9 @@ export function setupCLI() {
76
95
  catch (error) {
77
96
  // Error handling is done in executeDoctor, this is a safety net
78
97
  if (error instanceof Error) {
79
- logger.error('Doctor command failed', error);
98
+ log.error(`Error: ${error.message}`);
80
99
  }
100
+ process.exit(1);
81
101
  }
82
102
  });
83
103
  // test command
@@ -171,13 +191,15 @@ export function setupCLI() {
171
191
  catch (error) {
172
192
  // Error handling is done in executeTest
173
193
  if (error instanceof Error) {
174
- logger.error('Test command failed', error);
194
+ log.error(`Error: ${error.message}`);
195
+ process.exit(1);
175
196
  }
176
197
  }
177
198
  });
178
199
  // list command
179
200
  program
180
201
  .command('list')
202
+ .alias('ls')
181
203
  .description('List tools, resources, or prompts from an MCP server')
182
204
  .argument('[type]', `Type to list: ${ListTypes.TOOLS}, ${ListTypes.RESOURCES}, or ${ListTypes.PROMPTS} (default: ${ListTypes.TOOLS})`, ListTypes.TOOLS)
183
205
  .option('--transport <type>', 'Transport type (http or stdio). If not provided, uses transport from config.yaml')
@@ -216,7 +238,8 @@ export function setupCLI() {
216
238
  catch (error) {
217
239
  // Error handling is done in executeList
218
240
  if (error instanceof Error) {
219
- logger.error('List command failed', error);
241
+ log.error(`Error: ${error.message}`);
242
+ process.exit(1);
220
243
  }
221
244
  }
222
245
  });
@@ -273,6 +296,9 @@ export function setupCLI() {
273
296
  .option('--save-events', 'Save events to file for debugging')
274
297
  .option('--event-file <path>', 'Directory path for event files (default: .syrin/events). Events are saved as {sessionId}.jsonl')
275
298
  .option('--run-script', 'Run script to spawn server internally. If not provided, stdio uses script automatically, http connects to existing server')
299
+ .option('--transport <type>', 'Transport type (stdio or http). Required when using global config.')
300
+ .option('--url <url>', 'MCP server URL (required for http transport when using global config)')
301
+ .option('--script <command>', 'Script command to run MCP server (required for stdio transport when using global config)')
276
302
  .action(async (options) => {
277
303
  try {
278
304
  await executeDev({
@@ -282,12 +308,16 @@ export function setupCLI() {
282
308
  saveEvents: options.saveEvents || false,
283
309
  eventFile: options.eventFile,
284
310
  runScript: options.runScript || false,
311
+ transport: options.transport,
312
+ url: options.url,
313
+ script: options.script,
285
314
  });
286
315
  }
287
316
  catch (error) {
288
317
  // Error handling is done in executeDev
289
318
  if (error instanceof Error) {
290
- logger.error('Dev command failed', error);
319
+ log.error(`Error: ${error.message}`);
320
+ process.exit(1);
291
321
  }
292
322
  }
293
323
  });
@@ -302,7 +332,8 @@ export function setupCLI() {
302
332
  catch (error) {
303
333
  // Error handling is done in executeUpdate
304
334
  if (error instanceof Error) {
305
- logger.error('Update command failed', error);
335
+ log.error(`Error: ${error.message}`);
336
+ process.exit(1);
306
337
  }
307
338
  }
308
339
  });
@@ -318,10 +349,178 @@ export function setupCLI() {
318
349
  catch (error) {
319
350
  // Error handling is done in executeRollback
320
351
  if (error instanceof Error) {
321
- logger.error('Rollback command failed', error);
352
+ log.error(`Error: ${error.message}`);
353
+ process.exit(1);
354
+ }
355
+ }
356
+ });
357
+ // status command
358
+ program
359
+ .command('status')
360
+ .description('Show project health overview (config, LLM, MCP connection)')
361
+ .option('--project-root <path>', 'Project root directory (defaults to current directory)')
362
+ .action(async (options) => {
363
+ try {
364
+ await executeStatus(options.projectRoot);
365
+ }
366
+ catch (error) {
367
+ if (error instanceof Error) {
368
+ log.error(`Error: ${error.message}`);
369
+ process.exit(1);
322
370
  }
323
371
  }
324
372
  });
373
+ // config command
374
+ const configCommand = program
375
+ .command('config')
376
+ .alias('cfg')
377
+ .description('Manage Syrin configuration (local or global)');
378
+ // config set
379
+ configCommand
380
+ .command('set')
381
+ .description('Set a configuration value')
382
+ .argument('<key>', 'Configuration key (e.g., "openai.model", "agent_name")')
383
+ .argument('<value>', 'Value to set')
384
+ .option('--global', 'Operate on global config')
385
+ .option('--local', 'Operate on local config')
386
+ .action(async (key, value, options) => {
387
+ try {
388
+ await executeConfigSet(key, value, options);
389
+ }
390
+ catch (error) {
391
+ if (error instanceof Error) {
392
+ log.error(`Error: ${error.message}`);
393
+ }
394
+ process.exit(1);
395
+ }
396
+ });
397
+ // config get
398
+ configCommand
399
+ .command('get')
400
+ .description('Get a configuration value')
401
+ .argument('<key>', 'Configuration key')
402
+ .option('--global', 'Operate on global config')
403
+ .option('--local', 'Operate on local config')
404
+ .action(async (key, options) => {
405
+ try {
406
+ await executeConfigGet(key, options);
407
+ }
408
+ catch (error) {
409
+ if (error instanceof Error) {
410
+ log.error(`Error: ${error.message}`);
411
+ }
412
+ process.exit(1);
413
+ }
414
+ });
415
+ // config list
416
+ configCommand
417
+ .command('list')
418
+ .description('List all configuration values')
419
+ .option('--global', 'Operate on global config')
420
+ .option('--local', 'Operate on local config')
421
+ .action(async (options) => {
422
+ try {
423
+ await executeConfigList(options);
424
+ }
425
+ catch (error) {
426
+ if (error instanceof Error) {
427
+ log.error(`Error: ${error.message}`);
428
+ }
429
+ process.exit(1);
430
+ }
431
+ });
432
+ // config show
433
+ configCommand
434
+ .command('show')
435
+ .description('Show full configuration')
436
+ .option('--global', 'Operate on global config')
437
+ .option('--local', 'Operate on local config')
438
+ .action(async (options) => {
439
+ try {
440
+ await executeConfigShow(options);
441
+ }
442
+ catch (error) {
443
+ if (error instanceof Error) {
444
+ log.error(`Error: ${error.message}`);
445
+ }
446
+ process.exit(1);
447
+ }
448
+ });
449
+ // config edit
450
+ configCommand
451
+ .command('edit')
452
+ .description('Edit configuration file in editor')
453
+ .option('--global', 'Operate on global config')
454
+ .option('--local', 'Operate on local config')
455
+ .action(async (options) => {
456
+ try {
457
+ await executeConfigEdit(options);
458
+ }
459
+ catch (error) {
460
+ if (error instanceof Error) {
461
+ log.error(`Error: ${error.message}`);
462
+ }
463
+ process.exit(1);
464
+ }
465
+ });
466
+ // config edit-env
467
+ configCommand
468
+ .command('edit-env')
469
+ .description('Edit environment file in editor')
470
+ .option('--global', 'Operate on global config')
471
+ .option('--local', 'Operate on local config')
472
+ .action(async (options) => {
473
+ try {
474
+ await executeConfigEditEnv(options);
475
+ }
476
+ catch (error) {
477
+ if (error instanceof Error) {
478
+ log.error(`Error: ${error.message}`);
479
+ }
480
+ process.exit(1);
481
+ }
482
+ });
483
+ // config set-default
484
+ configCommand
485
+ .command('set-default')
486
+ .description('Set default LLM provider')
487
+ .argument('<provider>', 'LLM provider name (e.g., openai, claude)')
488
+ .option('--global', 'Operate on global config')
489
+ .option('--local', 'Operate on local config')
490
+ .action(async (provider, options) => {
491
+ try {
492
+ await executeConfigSetDefault(provider, options);
493
+ }
494
+ catch (error) {
495
+ if (error instanceof Error) {
496
+ log.error(`Error: ${error.message}`);
497
+ }
498
+ process.exit(1);
499
+ }
500
+ });
501
+ // config remove
502
+ configCommand
503
+ .command('remove')
504
+ .description('Remove an LLM provider from configuration')
505
+ .argument('<provider>', 'LLM provider name to remove')
506
+ .option('--global', 'Operate on global config')
507
+ .option('--local', 'Operate on local config')
508
+ .action(async (provider, options) => {
509
+ try {
510
+ await executeConfigRemove(provider, options);
511
+ }
512
+ catch (error) {
513
+ if (error instanceof Error) {
514
+ log.error(`Error: ${error.message}`);
515
+ }
516
+ process.exit(1);
517
+ }
518
+ });
519
+ // Handle no command provided - show help and exit with 0
520
+ if (process.argv.length <= 2) {
521
+ program.outputHelp();
522
+ process.exit(0);
523
+ }
325
524
  // Parse command line arguments
326
525
  program.parse();
327
526
  }
@@ -334,7 +533,7 @@ export function run() {
334
533
  }
335
534
  catch (error) {
336
535
  const err = error instanceof Error ? error : new Error(String(error));
337
- logger.error('CLI setup failed', err);
536
+ log.error(`Error: ${err.message}`);
338
537
  log.error(Messages.CLI_START_FAILED);
339
538
  process.exit(1);
340
539
  }