@sylphx/flow 2.1.3 → 2.1.5

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 (73) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +44 -0
  3. package/package.json +79 -73
  4. package/src/commands/flow/execute-v2.ts +37 -29
  5. package/src/commands/flow/prompt.ts +5 -3
  6. package/src/commands/flow/types.ts +0 -2
  7. package/src/commands/flow-command.ts +20 -13
  8. package/src/commands/hook-command.ts +1 -3
  9. package/src/commands/settings/checkbox-config.ts +128 -0
  10. package/src/commands/settings/index.ts +6 -0
  11. package/src/commands/settings-command.ts +84 -156
  12. package/src/config/ai-config.ts +60 -41
  13. package/src/core/agent-loader.ts +11 -6
  14. package/src/core/attach/file-attacher.ts +172 -0
  15. package/src/core/attach/index.ts +5 -0
  16. package/src/core/attach-manager.ts +117 -171
  17. package/src/core/backup-manager.ts +35 -29
  18. package/src/core/cleanup-handler.ts +11 -8
  19. package/src/core/error-handling.ts +23 -30
  20. package/src/core/flow-executor.ts +58 -76
  21. package/src/core/formatting/bytes.ts +2 -4
  22. package/src/core/functional/async.ts +5 -4
  23. package/src/core/functional/error-handler.ts +2 -2
  24. package/src/core/git-stash-manager.ts +21 -10
  25. package/src/core/installers/file-installer.ts +0 -1
  26. package/src/core/installers/mcp-installer.ts +0 -1
  27. package/src/core/project-manager.ts +24 -18
  28. package/src/core/secrets-manager.ts +54 -73
  29. package/src/core/session-manager.ts +20 -22
  30. package/src/core/state-detector.ts +139 -80
  31. package/src/core/template-loader.ts +13 -31
  32. package/src/core/upgrade-manager.ts +122 -69
  33. package/src/index.ts +8 -5
  34. package/src/services/auto-upgrade.ts +1 -1
  35. package/src/services/config-service.ts +41 -29
  36. package/src/services/global-config.ts +3 -3
  37. package/src/services/target-installer.ts +11 -26
  38. package/src/targets/claude-code.ts +35 -81
  39. package/src/targets/opencode.ts +28 -68
  40. package/src/targets/shared/index.ts +7 -0
  41. package/src/targets/shared/mcp-transforms.ts +132 -0
  42. package/src/targets/shared/target-operations.ts +135 -0
  43. package/src/types/cli.types.ts +2 -2
  44. package/src/types/provider.types.ts +1 -7
  45. package/src/types/session.types.ts +11 -11
  46. package/src/types/target.types.ts +3 -1
  47. package/src/types/todo.types.ts +1 -1
  48. package/src/types.ts +1 -1
  49. package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
  50. package/src/utils/agent-enhancer.ts +4 -4
  51. package/src/utils/config/paths.ts +3 -1
  52. package/src/utils/config/target-utils.ts +2 -2
  53. package/src/utils/display/banner.ts +2 -2
  54. package/src/utils/display/notifications.ts +58 -45
  55. package/src/utils/display/status.ts +29 -12
  56. package/src/utils/files/file-operations.ts +1 -1
  57. package/src/utils/files/sync-utils.ts +38 -41
  58. package/src/utils/index.ts +19 -27
  59. package/src/utils/package-manager-detector.ts +15 -5
  60. package/src/utils/security/security.ts +8 -4
  61. package/src/utils/target-selection.ts +6 -8
  62. package/src/utils/version.ts +4 -2
  63. package/src/commands/flow-orchestrator.ts +0 -328
  64. package/src/commands/init-command.ts +0 -92
  65. package/src/commands/init-core.ts +0 -331
  66. package/src/core/agent-manager.ts +0 -174
  67. package/src/core/loop-controller.ts +0 -200
  68. package/src/core/rule-loader.ts +0 -147
  69. package/src/core/rule-manager.ts +0 -240
  70. package/src/services/claude-config-service.ts +0 -252
  71. package/src/services/first-run-setup.ts +0 -220
  72. package/src/services/smart-config-service.ts +0 -269
  73. package/src/types/api.types.ts +0 -9
@@ -3,13 +3,15 @@
3
3
  * Interactive configuration for Sylphx Flow
4
4
  */
5
5
 
6
- import { Command } from 'commander';
7
6
  import chalk from 'chalk';
7
+ import { Command } from 'commander';
8
8
  import inquirer from 'inquirer';
9
+ import { getRequiredEnvVars, MCP_SERVER_REGISTRY, type MCPServerID } from '../config/servers.js';
9
10
  import { GlobalConfigService } from '../services/global-config.js';
10
- import { UserCancelledError } from '../utils/errors.js';
11
11
  import { TargetInstaller } from '../services/target-installer.js';
12
- import { promptForDefaultTarget, buildAvailableTargets } from '../utils/target-selection.js';
12
+ import { UserCancelledError } from '../utils/errors.js';
13
+ import { buildAvailableTargets, promptForDefaultTarget } from '../utils/target-selection.js';
14
+ import { handleCheckboxConfig, printHeader, printConfirmation } from './settings/index.js';
13
15
 
14
16
  export const settingsCommand = new Command('settings')
15
17
  .description('Configure Sylphx Flow settings')
@@ -30,9 +32,10 @@ export const settingsCommand = new Command('settings')
30
32
  } else {
31
33
  await showMainMenu(configService);
32
34
  }
33
- } catch (error: any) {
35
+ } catch (error: unknown) {
34
36
  // Handle user cancellation (Ctrl+C)
35
- if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
37
+ const err = error as Error & { name?: string };
38
+ if (err.name === 'ExitPromptError' || err.message?.includes('force closed')) {
36
39
  throw new UserCancelledError('Settings cancelled by user');
37
40
  }
38
41
  throw error;
@@ -105,16 +108,12 @@ async function openSection(section: string, configService: GlobalConfigService):
105
108
  }
106
109
 
107
110
  /**
108
- * Configure Agents
111
+ * Configure Agents - uses shared checkbox handler + default selection
109
112
  */
110
113
  async function configureAgents(configService: GlobalConfigService): Promise<void> {
111
- console.log(chalk.cyan.bold('\n━━━ 🤖 Agent Configuration\n'));
112
-
113
114
  const flowConfig = await configService.loadFlowConfig();
114
115
  const settings = await configService.loadSettings();
115
- const currentAgents = flowConfig.agents || {};
116
116
 
117
- // Available agents
118
117
  const availableAgents = {
119
118
  coder: 'Coder - Write and modify code',
120
119
  writer: 'Writer - Documentation and explanation',
@@ -122,40 +121,22 @@ async function configureAgents(configService: GlobalConfigService): Promise<void
122
121
  orchestrator: 'Orchestrator - Task coordination',
123
122
  };
124
123
 
125
- // Get current enabled agents
126
- const currentEnabled = Object.keys(currentAgents).filter(
127
- (key) => currentAgents[key].enabled
128
- );
129
-
130
- const { selectedAgents } = await inquirer.prompt([
131
- {
132
- type: 'checkbox',
133
- name: 'selectedAgents',
134
- message: 'Select agents to enable:',
135
- choices: Object.entries(availableAgents).map(([key, name]) => ({
136
- name,
137
- value: key,
138
- checked: currentEnabled.includes(key),
139
- })),
140
- },
141
- ]);
142
-
143
- // Update agents
144
- for (const key of Object.keys(availableAgents)) {
145
- if (selectedAgents.includes(key)) {
146
- currentAgents[key] = { enabled: true };
147
- } else {
148
- currentAgents[key] = { enabled: false };
149
- }
150
- }
124
+ const { selected, updated } = await handleCheckboxConfig({
125
+ title: 'Agent Configuration',
126
+ icon: '🤖',
127
+ message: 'Select agents to enable:',
128
+ available: availableAgents,
129
+ current: flowConfig.agents || {},
130
+ itemType: 'Agents',
131
+ });
151
132
 
152
- // Select default agent
133
+ // Additional step: select default agent from enabled ones
153
134
  const { defaultAgent } = await inquirer.prompt([
154
135
  {
155
136
  type: 'list',
156
137
  name: 'defaultAgent',
157
138
  message: 'Select default agent:',
158
- choices: selectedAgents.map((key: string) => ({
139
+ choices: selected.map((key) => ({
159
140
  name: availableAgents[key as keyof typeof availableAgents],
160
141
  value: key,
161
142
  })),
@@ -163,113 +144,57 @@ async function configureAgents(configService: GlobalConfigService): Promise<void
163
144
  },
164
145
  ]);
165
146
 
166
- flowConfig.agents = currentAgents;
147
+ flowConfig.agents = updated;
167
148
  await configService.saveFlowConfig(flowConfig);
168
149
 
169
150
  settings.defaultAgent = defaultAgent;
170
151
  await configService.saveSettings(settings);
171
152
 
172
- console.log(chalk.green(`\n✓ Agent configuration saved`));
173
- console.log(chalk.dim(` Enabled agents: ${selectedAgents.length}`));
174
153
  console.log(chalk.dim(` Default agent: ${defaultAgent}`));
175
154
  }
176
155
 
177
156
  /**
178
- * Configure Rules
157
+ * Configure Rules - uses shared checkbox handler
179
158
  */
180
159
  async function configureRules(configService: GlobalConfigService): Promise<void> {
181
- console.log(chalk.cyan.bold('\n━━━ 📋 Rules Configuration\n'));
182
-
183
160
  const flowConfig = await configService.loadFlowConfig();
184
- const currentRules = flowConfig.rules || {};
185
-
186
- // Available rules
187
- const availableRules = {
188
- core: 'Core - Identity, personality, execution',
189
- 'code-standards': 'Code Standards - Quality, patterns, anti-patterns',
190
- workspace: 'Workspace - Documentation management',
191
- };
192
-
193
- // Get current enabled rules
194
- const currentEnabled = Object.keys(currentRules).filter(
195
- (key) => currentRules[key].enabled
196
- );
197
161
 
198
- const { selectedRules } = await inquirer.prompt([
199
- {
200
- type: 'checkbox',
201
- name: 'selectedRules',
202
- message: 'Select rules to enable:',
203
- choices: Object.entries(availableRules).map(([key, name]) => ({
204
- name,
205
- value: key,
206
- checked: currentEnabled.includes(key),
207
- })),
162
+ const { updated } = await handleCheckboxConfig({
163
+ title: 'Rules Configuration',
164
+ icon: '📋',
165
+ message: 'Select rules to enable:',
166
+ available: {
167
+ core: 'Core - Identity, personality, execution',
168
+ 'code-standards': 'Code Standards - Quality, patterns, anti-patterns',
169
+ workspace: 'Workspace - Documentation management',
208
170
  },
209
- ]);
210
-
211
- // Update rules
212
- for (const key of Object.keys(availableRules)) {
213
- if (selectedRules.includes(key)) {
214
- currentRules[key] = { enabled: true };
215
- } else {
216
- currentRules[key] = { enabled: false };
217
- }
218
- }
171
+ current: flowConfig.rules || {},
172
+ itemType: 'Rules',
173
+ });
219
174
 
220
- flowConfig.rules = currentRules;
175
+ flowConfig.rules = updated;
221
176
  await configService.saveFlowConfig(flowConfig);
222
-
223
- console.log(chalk.green(`\n✓ Rules configuration saved`));
224
- console.log(chalk.dim(` Enabled rules: ${selectedRules.length}`));
225
177
  }
226
178
 
227
179
  /**
228
- * Configure Output Styles
180
+ * Configure Output Styles - uses shared checkbox handler
229
181
  */
230
182
  async function configureOutputStyles(configService: GlobalConfigService): Promise<void> {
231
- console.log(chalk.cyan.bold('\n━━━ 🎨 Output Styles Configuration\n'));
232
-
233
183
  const flowConfig = await configService.loadFlowConfig();
234
- const currentStyles = flowConfig.outputStyles || {};
235
-
236
- // Available output styles
237
- const availableStyles = {
238
- silent: 'Silent - Execution without narration',
239
- };
240
184
 
241
- // Get current enabled styles
242
- const currentEnabled = Object.keys(currentStyles).filter(
243
- (key) => currentStyles[key].enabled
244
- );
245
-
246
- const { selectedStyles } = await inquirer.prompt([
247
- {
248
- type: 'checkbox',
249
- name: 'selectedStyles',
250
- message: 'Select output styles to enable:',
251
- choices: Object.entries(availableStyles).map(([key, name]) => ({
252
- name,
253
- value: key,
254
- checked: currentEnabled.includes(key),
255
- })),
185
+ const { updated } = await handleCheckboxConfig({
186
+ title: 'Output Styles Configuration',
187
+ icon: '🎨',
188
+ message: 'Select output styles to enable:',
189
+ available: {
190
+ silent: 'Silent - Execution without narration',
256
191
  },
257
- ]);
258
-
259
- // Update styles
260
- for (const key of Object.keys(availableStyles)) {
261
- if (selectedStyles.includes(key)) {
262
- currentStyles[key] = { enabled: true };
263
- } else {
264
- currentStyles[key] = { enabled: false };
265
- }
266
- }
192
+ current: flowConfig.outputStyles || {},
193
+ itemType: 'Output styles',
194
+ });
267
195
 
268
- flowConfig.outputStyles = currentStyles;
196
+ flowConfig.outputStyles = updated;
269
197
  await configService.saveFlowConfig(flowConfig);
270
-
271
- console.log(chalk.green(`\n✓ Output styles configuration saved`));
272
- console.log(chalk.dim(` Enabled styles: ${selectedStyles.length}`));
273
198
  }
274
199
 
275
200
  /**
@@ -281,67 +206,62 @@ async function configureMCP(configService: GlobalConfigService): Promise<void> {
281
206
  const mcpConfig = await configService.loadMCPConfig();
282
207
  const currentServers = mcpConfig.servers || {};
283
208
 
284
- // Available MCP servers (from MCP_SERVER_REGISTRY)
285
- const availableServers = {
286
- 'grep': { name: 'GitHub Code Search (grep.app)', requiresEnv: [] },
287
- 'context7': { name: 'Context7 Docs', requiresEnv: [] },
288
- 'playwright': { name: 'Playwright Browser Control', requiresEnv: [] },
289
- 'github': { name: 'GitHub', requiresEnv: ['GITHUB_TOKEN'] },
290
- 'notion': { name: 'Notion', requiresEnv: ['NOTION_API_KEY'] },
291
- };
209
+ // Get all servers from registry
210
+ const allServerIds = Object.keys(MCP_SERVER_REGISTRY) as MCPServerID[];
292
211
 
293
212
  // Get current enabled servers
294
- const currentEnabled = Object.keys(currentServers).filter(
295
- (key) => currentServers[key].enabled
296
- );
213
+ const currentEnabled = Object.keys(currentServers).filter((key) => currentServers[key].enabled);
297
214
 
298
215
  const { selectedServers } = await inquirer.prompt([
299
216
  {
300
217
  type: 'checkbox',
301
218
  name: 'selectedServers',
302
219
  message: 'Select MCP servers to enable:',
303
- choices: Object.entries(availableServers).map(([key, info]) => {
304
- const requiresText = info.requiresEnv.length > 0
305
- ? chalk.dim(` (requires ${info.requiresEnv.join(', ')})`)
306
- : '';
220
+ choices: allServerIds.map((id) => {
221
+ const server = MCP_SERVER_REGISTRY[id];
222
+ const requiredEnvVars = getRequiredEnvVars(id);
223
+ const requiresText =
224
+ requiredEnvVars.length > 0 ? chalk.dim(` (requires ${requiredEnvVars.join(', ')})`) : '';
307
225
  return {
308
- name: `${info.name}${requiresText}`,
309
- value: key,
310
- checked: currentEnabled.includes(key),
226
+ name: `${server.name} - ${server.description}${requiresText}`,
227
+ value: id,
228
+ checked: currentEnabled.includes(id) || server.defaultInInit,
311
229
  };
312
230
  }),
313
231
  },
314
232
  ]);
315
233
 
316
234
  // Update servers
317
- for (const key of Object.keys(availableServers)) {
318
- if (selectedServers.includes(key)) {
319
- if (!currentServers[key]) {
320
- currentServers[key] = { enabled: true, env: {} };
235
+ for (const id of allServerIds) {
236
+ if (selectedServers.includes(id)) {
237
+ if (currentServers[id]) {
238
+ currentServers[id].enabled = true;
321
239
  } else {
322
- currentServers[key].enabled = true;
240
+ currentServers[id] = { enabled: true, env: {} };
323
241
  }
324
- } else if (currentServers[key]) {
325
- currentServers[key].enabled = false;
242
+ } else if (currentServers[id]) {
243
+ currentServers[id].enabled = false;
326
244
  }
327
245
  }
328
246
 
329
247
  // Ask for API keys for newly enabled servers
330
- for (const serverKey of selectedServers) {
331
- const serverInfo = availableServers[serverKey as keyof typeof availableServers];
332
- if (serverInfo.requiresEnv.length > 0) {
333
- const server = currentServers[serverKey];
248
+ for (const serverId of selectedServers as MCPServerID[]) {
249
+ const serverDef = MCP_SERVER_REGISTRY[serverId];
250
+ const requiredEnvVars = getRequiredEnvVars(serverId);
334
251
 
335
- for (const envKey of serverInfo.requiresEnv) {
336
- const hasKey = server.env && server.env[envKey];
252
+ if (requiredEnvVars.length > 0) {
253
+ const server = currentServers[serverId];
254
+
255
+ for (const envKey of requiredEnvVars) {
256
+ const hasKey = server.env?.[envKey];
337
257
 
338
258
  const { shouldConfigure } = await inquirer.prompt([
339
259
  {
340
260
  type: 'confirm',
341
261
  name: 'shouldConfigure',
342
262
  message: hasKey
343
- ? `Update ${envKey} for ${serverInfo.name}?`
344
- : `Configure ${envKey} for ${serverInfo.name}?`,
263
+ ? `Update ${envKey} for ${serverDef.name}?`
264
+ : `Configure ${envKey} for ${serverDef.name}?`,
345
265
  default: !hasKey,
346
266
  },
347
267
  ]);
@@ -406,7 +326,9 @@ async function configureProvider(configService: GlobalConfigService): Promise<vo
406
326
  {
407
327
  type: 'confirm',
408
328
  name: 'shouldConfigure',
409
- message: currentKey ? `Update ${defaultProvider} API key?` : `Configure ${defaultProvider} API key?`,
329
+ message: currentKey
330
+ ? `Update ${defaultProvider} API key?`
331
+ : `Configure ${defaultProvider} API key?`,
410
332
  default: !currentKey,
411
333
  },
412
334
  ]);
@@ -424,8 +346,11 @@ async function configureProvider(configService: GlobalConfigService): Promise<vo
424
346
  if (!providerConfig.claudeCode.providers[defaultProvider]) {
425
347
  providerConfig.claudeCode.providers[defaultProvider] = { enabled: true };
426
348
  }
427
- providerConfig.claudeCode.providers[defaultProvider]!.apiKey = apiKey;
428
- providerConfig.claudeCode.providers[defaultProvider]!.enabled = true;
349
+ const provider = providerConfig.claudeCode.providers[defaultProvider];
350
+ if (provider) {
351
+ provider.apiKey = apiKey;
352
+ provider.enabled = true;
353
+ }
429
354
  }
430
355
  }
431
356
 
@@ -450,7 +375,10 @@ async function configureTarget(configService: GlobalConfigService): Promise<void
450
375
 
451
376
  const defaultTarget = await promptForDefaultTarget(installedTargets, settings.defaultTarget);
452
377
 
453
- settings.defaultTarget = defaultTarget as 'claude-code' | 'opencode' | 'cursor' | 'ask-every-time';
378
+ settings.defaultTarget = defaultTarget as
379
+ | 'claude-code'
380
+ | 'opencode'
381
+ | 'ask-every-time';
454
382
  await configService.saveSettings(settings);
455
383
 
456
384
  if (defaultTarget === 'ask-every-time') {
@@ -10,12 +10,15 @@
10
10
  */
11
11
 
12
12
  import fs from 'node:fs/promises';
13
- import path from 'node:path';
14
13
  import os from 'node:os';
14
+ import path from 'node:path';
15
15
  import { z } from 'zod';
16
- import { type Result, success, tryCatchAsync } from '../core/functional/result.js';
16
+ import { type Result, tryCatchAsync } from '../core/functional/result.js';
17
17
  import { getAllProviders } from '../providers/index.js';
18
- import type { ProviderId, ProviderConfigValue as ProviderConfigValueType } from '../types/provider.types.js';
18
+ import type {
19
+ ProviderConfigValue as ProviderConfigValueType,
20
+ ProviderId,
21
+ } from '../types/provider.types.js';
19
22
 
20
23
  // Re-export types for backward compatibility
21
24
  export type { ProviderId } from '../types/provider.types.js';
@@ -39,14 +42,20 @@ export type ProviderConfigValue = ProviderConfigValueType;
39
42
  * Uses generic Record for provider configs - validation happens at provider level
40
43
  */
41
44
  const aiConfigSchema = z.object({
42
- defaultProvider: z.enum(['anthropic', 'openai', 'google', 'openrouter', 'claude-code', 'zai']).optional(),
45
+ defaultProvider: z
46
+ .enum(['anthropic', 'openai', 'google', 'openrouter', 'claude-code', 'zai'])
47
+ .optional(),
43
48
  defaultModel: z.string().optional(),
44
- providers: z.record(
45
- z.string(),
46
- z.object({
47
- defaultModel: z.string().optional(),
48
- }).passthrough() // Allow additional fields defined by provider
49
- ).optional(),
49
+ providers: z
50
+ .record(
51
+ z.string(),
52
+ z
53
+ .object({
54
+ defaultModel: z.string().optional(),
55
+ })
56
+ .passthrough() // Allow additional fields defined by provider
57
+ )
58
+ .optional(),
50
59
  });
51
60
 
52
61
  export type AIConfig = z.infer<typeof aiConfigSchema>;
@@ -61,7 +70,9 @@ const LOCAL_CONFIG_FILE = '.sylphx-flow/settings.local.json';
61
70
  /**
62
71
  * Get AI config file paths in priority order
63
72
  */
64
- export const getAIConfigPaths = (cwd: string = process.cwd()): {
73
+ export const getAIConfigPaths = (
74
+ cwd: string = process.cwd()
75
+ ): {
65
76
  global: string;
66
77
  project: string;
67
78
  local: string;
@@ -79,8 +90,9 @@ const loadConfigFile = async (filePath: string): Promise<AIConfig | null> => {
79
90
  const content = await fs.readFile(filePath, 'utf8');
80
91
  const parsed = JSON.parse(content);
81
92
  return aiConfigSchema.parse(parsed);
82
- } catch (error: any) {
83
- if (error.code === 'ENOENT') {
93
+ } catch (error: unknown) {
94
+ const err = error as NodeJS.ErrnoException;
95
+ if (err.code === 'ENOENT') {
84
96
  return null; // File doesn't exist
85
97
  }
86
98
  throw error; // Re-throw other errors
@@ -97,7 +109,7 @@ const mergeConfigs = (a: AIConfig, b: AIConfig): AIConfig => {
97
109
  ...Object.keys(b.providers || {}),
98
110
  ]);
99
111
 
100
- const mergedProviders: Record<string, any> = {};
112
+ const mergedProviders: Record<string, Record<string, unknown>> = {};
101
113
  for (const providerId of allProviderIds) {
102
114
  mergedProviders[providerId] = {
103
115
  ...a.providers?.[providerId],
@@ -117,30 +129,31 @@ const mergeConfigs = (a: AIConfig, b: AIConfig): AIConfig => {
117
129
  */
118
130
  export const aiConfigExists = async (cwd: string = process.cwd()): Promise<boolean> => {
119
131
  const paths = getAIConfigPaths(cwd);
120
- try {
121
- // Check any of the config files
122
- await fs.access(paths.global).catch(() => {});
123
- return true;
124
- } catch {}
125
-
126
- try {
127
- await fs.access(paths.project);
128
- return true;
129
- } catch {}
130
132
 
131
- try {
132
- await fs.access(paths.local);
133
- return true;
134
- } catch {}
133
+ // Check if any config file exists
134
+ const checks = await Promise.all([
135
+ fs
136
+ .access(paths.global)
137
+ .then(() => true)
138
+ .catch(() => false),
139
+ fs
140
+ .access(paths.project)
141
+ .then(() => true)
142
+ .catch(() => false),
143
+ fs
144
+ .access(paths.local)
145
+ .then(() => true)
146
+ .catch(() => false),
147
+ ]);
135
148
 
136
- return false;
149
+ return checks.some(Boolean);
137
150
  };
138
151
 
139
152
  /**
140
153
  * Load AI configuration
141
154
  * Merges global, project, and local configs with priority: local > project > global
142
155
  */
143
- export const loadAIConfig = async (cwd: string = process.cwd()): Promise<Result<AIConfig, Error>> => {
156
+ export const loadAIConfig = (cwd: string = process.cwd()): Promise<Result<AIConfig, Error>> => {
144
157
  return tryCatchAsync(
145
158
  async () => {
146
159
  const paths = getAIConfigPaths(cwd);
@@ -156,13 +169,19 @@ export const loadAIConfig = async (cwd: string = process.cwd()): Promise<Result<
156
169
  let merged: AIConfig = {};
157
170
 
158
171
  // Merge in priority order: global < project < local
159
- if (globalConfig) merged = mergeConfigs(merged, globalConfig);
160
- if (projectConfig) merged = mergeConfigs(merged, projectConfig);
161
- if (localConfig) merged = mergeConfigs(merged, localConfig);
172
+ if (globalConfig) {
173
+ merged = mergeConfigs(merged, globalConfig);
174
+ }
175
+ if (projectConfig) {
176
+ merged = mergeConfigs(merged, projectConfig);
177
+ }
178
+ if (localConfig) {
179
+ merged = mergeConfigs(merged, localConfig);
180
+ }
162
181
 
163
182
  return merged;
164
183
  },
165
- (error: any) => new Error(`Failed to load AI config: ${error.message}`)
184
+ (error: unknown) => new Error(`Failed to load AI config: ${(error as Error).message}`)
166
185
  );
167
186
  };
168
187
 
@@ -171,7 +190,7 @@ export const loadAIConfig = async (cwd: string = process.cwd()): Promise<Result<
171
190
  * By default, all configuration (including API keys) goes to ~/.sylphx-flow/settings.json
172
191
  * Automatically sets default provider if not set
173
192
  */
174
- export const saveAIConfig = async (
193
+ export const saveAIConfig = (
175
194
  config: AIConfig,
176
195
  cwd: string = process.cwd()
177
196
  ): Promise<Result<void, Error>> => {
@@ -211,16 +230,16 @@ export const saveAIConfig = async (
211
230
  const validated = aiConfigSchema.parse(configToSave);
212
231
 
213
232
  // Write config
214
- await fs.writeFile(configPath, JSON.stringify(validated, null, 2) + '\n', 'utf8');
233
+ await fs.writeFile(configPath, `${JSON.stringify(validated, null, 2)}\n`, 'utf8');
215
234
  },
216
- (error: any) => new Error(`Failed to save AI config: ${error.message}`)
235
+ (error: unknown) => new Error(`Failed to save AI config: ${(error as Error).message}`)
217
236
  );
218
237
  };
219
238
 
220
239
  /**
221
240
  * Save AI configuration to a specific location
222
241
  */
223
- export const saveAIConfigTo = async (
242
+ export const saveAIConfigTo = (
224
243
  config: AIConfig,
225
244
  location: 'global' | 'project' | 'local',
226
245
  cwd: string = process.cwd()
@@ -237,9 +256,10 @@ export const saveAIConfigTo = async (
237
256
  const validated = aiConfigSchema.parse(config);
238
257
 
239
258
  // Write config
240
- await fs.writeFile(configPath, JSON.stringify(validated, null, 2) + '\n', 'utf8');
259
+ await fs.writeFile(configPath, `${JSON.stringify(validated, null, 2)}\n`, 'utf8');
241
260
  },
242
- (error: any) => new Error(`Failed to save AI config to ${location}: ${error.message}`)
261
+ (error: unknown) =>
262
+ new Error(`Failed to save AI config to ${location}: ${(error as Error).message}`)
243
263
  );
244
264
  };
245
265
 
@@ -306,4 +326,3 @@ export const getConfiguredProviders = async (
306
326
 
307
327
  return providers;
308
328
  };
309
-
@@ -3,9 +3,9 @@
3
3
  * Loads agent definitions from markdown files with front matter
4
4
  */
5
5
 
6
- import { readFile, readdir, access } from 'node:fs/promises';
7
- import { join, parse, relative, dirname } from 'node:path';
6
+ import { access, readdir, readFile } from 'node:fs/promises';
8
7
  import { homedir } from 'node:os';
8
+ import { dirname, join, parse, relative } from 'node:path';
9
9
  import { fileURLToPath } from 'node:url';
10
10
  import matter from 'gray-matter';
11
11
  import type { Agent, AgentMetadata } from '../types/agent.types.js';
@@ -52,7 +52,10 @@ export async function loadAgentFromFile(
52
52
  /**
53
53
  * Load all agents from a directory (recursively)
54
54
  */
55
- export async function loadAgentsFromDirectory(dirPath: string, isBuiltin: boolean = false): Promise<Agent[]> {
55
+ export async function loadAgentsFromDirectory(
56
+ dirPath: string,
57
+ isBuiltin: boolean = false
58
+ ): Promise<Agent[]> {
56
59
  try {
57
60
  // Read directory recursively to support subdirectories
58
61
  const files = await readdir(dirPath, { recursive: true, withFileTypes: true });
@@ -72,7 +75,7 @@ export async function loadAgentsFromDirectory(dirPath: string, isBuiltin: boolea
72
75
  );
73
76
 
74
77
  return agents.filter((agent): agent is Agent => agent !== null);
75
- } catch (error) {
78
+ } catch (_error) {
76
79
  // Directory doesn't exist or can't be read
77
80
  return [];
78
81
  }
@@ -117,7 +120,7 @@ export async function loadAllAgents(cwd: string, targetAgentDir?: string): Promi
117
120
  const systemPath = await getSystemAgentsPath();
118
121
  const [globalPath, projectPath] = getAgentSearchPaths(cwd);
119
122
 
120
- let allAgentPaths = [systemPath, globalPath, projectPath];
123
+ const allAgentPaths = [systemPath, globalPath, projectPath];
121
124
 
122
125
  // If a target-specific agent directory is provided, add it with highest priority
123
126
  if (targetAgentDir) {
@@ -126,7 +129,9 @@ export async function loadAllAgents(cwd: string, targetAgentDir?: string): Promi
126
129
  }
127
130
 
128
131
  // Load agents from all paths
129
- const loadedAgentsPromises = allAgentPaths.map(path => loadAgentsFromDirectory(path, path === systemPath));
132
+ const loadedAgentsPromises = allAgentPaths.map((path) =>
133
+ loadAgentsFromDirectory(path, path === systemPath)
134
+ );
130
135
  const loadedAgentsArrays = await Promise.all(loadedAgentsPromises);
131
136
 
132
137
  // Flatten and deduplicate