@sylphx/flow 1.1.1 → 1.3.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 (47) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/package.json +1 -1
  3. package/src/commands/flow-command.ts +28 -0
  4. package/src/commands/hook-command.ts +10 -230
  5. package/src/composables/index.ts +0 -1
  6. package/src/config/servers.ts +35 -78
  7. package/src/core/interfaces.ts +0 -33
  8. package/src/domains/index.ts +0 -2
  9. package/src/index.ts +0 -4
  10. package/src/services/mcp-service.ts +0 -16
  11. package/src/targets/claude-code.ts +3 -9
  12. package/src/targets/functional/claude-code-logic.ts +4 -22
  13. package/src/targets/opencode.ts +0 -6
  14. package/src/types/mcp.types.ts +29 -38
  15. package/src/types/target.types.ts +0 -2
  16. package/src/types.ts +0 -1
  17. package/src/utils/sync-utils.ts +106 -0
  18. package/src/commands/codebase-command.ts +0 -168
  19. package/src/commands/knowledge-command.ts +0 -161
  20. package/src/composables/useTargetConfig.ts +0 -45
  21. package/src/core/formatting/bytes.test.ts +0 -115
  22. package/src/core/validation/limit.test.ts +0 -155
  23. package/src/core/validation/query.test.ts +0 -44
  24. package/src/domains/codebase/index.ts +0 -5
  25. package/src/domains/codebase/tools.ts +0 -139
  26. package/src/domains/knowledge/index.ts +0 -10
  27. package/src/domains/knowledge/resources.ts +0 -537
  28. package/src/domains/knowledge/tools.ts +0 -174
  29. package/src/services/search/base-indexer.ts +0 -156
  30. package/src/services/search/codebase-indexer-types.ts +0 -38
  31. package/src/services/search/codebase-indexer.ts +0 -647
  32. package/src/services/search/embeddings-provider.ts +0 -455
  33. package/src/services/search/embeddings.ts +0 -316
  34. package/src/services/search/functional-indexer.ts +0 -323
  35. package/src/services/search/index.ts +0 -27
  36. package/src/services/search/indexer.ts +0 -380
  37. package/src/services/search/knowledge-indexer.ts +0 -422
  38. package/src/services/search/semantic-search.ts +0 -244
  39. package/src/services/search/tfidf.ts +0 -559
  40. package/src/services/search/unified-search-service.ts +0 -888
  41. package/src/services/storage/cache-storage.ts +0 -487
  42. package/src/services/storage/drizzle-storage.ts +0 -581
  43. package/src/services/storage/index.ts +0 -15
  44. package/src/services/storage/lancedb-vector-storage.ts +0 -494
  45. package/src/services/storage/memory-storage.ts +0 -268
  46. package/src/services/storage/separated-storage.ts +0 -467
  47. package/src/services/storage/vector-storage.ts +0 -13
@@ -31,7 +31,6 @@ export interface ClaudeCodeSettings {
31
31
  }
32
32
 
33
33
  export interface HookConfig {
34
- sessionCommand?: string;
35
34
  notificationCommand?: string;
36
35
  }
37
36
 
@@ -41,17 +40,15 @@ export interface HookConfig {
41
40
  */
42
41
  export const generateHookCommands = async (targetId: string): Promise<HookConfig> => {
43
42
  return {
44
- sessionCommand: `sylphx-flow hook --type session --target ${targetId}`,
45
43
  notificationCommand: `sylphx-flow hook --type notification --target ${targetId}`,
46
44
  };
47
45
  };
48
46
 
49
47
  /**
50
48
  * Default hook commands (fallback)
51
- * Simplified to only include session and notification hooks
49
+ * Simplified to only include notification hook
52
50
  */
53
51
  export const DEFAULT_HOOKS: HookConfig = {
54
- sessionCommand: 'sylphx-flow hook --type session --target claude-code',
55
52
  notificationCommand: 'sylphx-flow hook --type notification --target claude-code',
56
53
  };
57
54
 
@@ -74,20 +71,9 @@ export const parseSettings = (content: string): Result<ClaudeCodeSettings, Confi
74
71
  export const buildHookConfiguration = (
75
72
  config: HookConfig = DEFAULT_HOOKS
76
73
  ): ClaudeCodeSettings['hooks'] => {
77
- const sessionCommand = config.sessionCommand || DEFAULT_HOOKS.sessionCommand!;
78
74
  const notificationCommand = config.notificationCommand || DEFAULT_HOOKS.notificationCommand!;
79
75
 
80
76
  return {
81
- SessionStart: [
82
- {
83
- hooks: [
84
- {
85
- type: 'command',
86
- command: sessionCommand,
87
- },
88
- ],
89
- },
90
- ],
91
77
  Notification: [
92
78
  {
93
79
  matcher: '',
@@ -140,7 +126,7 @@ export const serializeSettings = (settings: ClaudeCodeSettings): string => {
140
126
  * Get success message (pure)
141
127
  */
142
128
  export const getSuccessMessage = (): string => {
143
- return 'Claude Code hooks configured: SessionStart (static info) + UserPromptSubmit (dynamic info)';
129
+ return 'Claude Code hook configured: Notification';
144
130
  };
145
131
 
146
132
  /**
@@ -173,12 +159,8 @@ export const processSettings = (
173
159
  * Validate hook configuration (pure)
174
160
  */
175
161
  export const validateHookConfig = (config: HookConfig): Result<HookConfig, ConfigError> => {
176
- if (config.sessionCommand !== undefined && config.sessionCommand.trim() === '') {
177
- return failure(configError('Session command cannot be empty'));
178
- }
179
-
180
- if (config.messageCommand !== undefined && config.messageCommand.trim() === '') {
181
- return failure(configError('Message command cannot be empty'));
162
+ if (config.notificationCommand !== undefined && config.notificationCommand.trim() === '') {
163
+ return failure(configError('Notification command cannot be empty'));
182
164
  }
183
165
 
184
166
  return success(config);
@@ -23,12 +23,6 @@ export const opencodeTarget: Target = {
23
23
  isImplemented: true,
24
24
  isDefault: true,
25
25
 
26
- mcpServerConfig: {
27
- disableTime: false,
28
- disableKnowledge: false,
29
- disableCodebase: false,
30
- },
31
-
32
26
  config: {
33
27
  agentDir: '.opencode/agent',
34
28
  agentExtension: '.md',
@@ -1,30 +1,26 @@
1
1
  /**
2
- * MCP (Model Context Protocol) configuration types
3
- * Types for configuring and managing MCP servers
2
+ * MCP (Model Context Protocol) Configuration Types
3
+ * Defines configuration formats for different MCP server implementations
4
4
  */
5
5
 
6
- import type { Resolvable } from './common.types.js';
7
-
8
6
  /**
9
- * MCP server configuration for stdio-based servers
10
- * Communicates via standard input/output
7
+ * Base MCP server configuration with stdio transport
11
8
  */
12
- export interface MCPServerConfig {
9
+ export type MCPServerConfig = {
13
10
  type: 'stdio';
14
- command: Resolvable<string>;
15
- args?: Resolvable<string[]>;
11
+ command: string;
12
+ args?: string[];
16
13
  env?: Record<string, string>;
17
- }
14
+ };
18
15
 
19
16
  /**
20
- * MCP server configuration for HTTP-based servers
21
- * Communicates via HTTP requests
17
+ * MCP server configuration with HTTP transport
22
18
  */
23
- export interface MCPServerConfigHTTP {
19
+ export type MCPServerConfigHTTP = {
24
20
  type: 'http';
25
- url: Resolvable<string>;
21
+ url: string;
26
22
  headers?: Record<string, string>;
27
- }
23
+ };
28
24
 
29
25
  /**
30
26
  * Union of all possible MCP server configurations
@@ -32,38 +28,33 @@ export interface MCPServerConfigHTTP {
32
28
  export type MCPServerConfigUnion = MCPServerConfig | MCPServerConfigHTTP;
33
29
 
34
30
  /**
35
- * MCP server configuration flags
36
- * Used to disable specific server features per target
31
+ * OpenCode-specific configuration format
37
32
  */
38
- export type MCPServerConfigFlags = {
39
- disableTime?: boolean;
40
- disableKnowledge?: boolean;
41
- disableCodebase?: boolean;
33
+ export type OpenCodeConfig = {
34
+ type: 'local' | 'remote';
35
+ command?: string[];
36
+ url?: string;
37
+ headers?: Record<string, string>;
38
+ environment?: Record<string, string>;
42
39
  };
43
40
 
44
41
  /**
45
- * OpenCode-specific configuration format
42
+ * Type guard for stdio config
46
43
  */
47
- export interface OpenCodeConfig {
48
- $schema?: string;
49
- mcp?: Record<string, MCPServerConfigUnion>;
44
+ export function isStdioConfig(config: MCPServerConfigUnion): config is MCPServerConfig {
45
+ return config.type === 'stdio';
50
46
  }
51
47
 
52
48
  /**
53
- * Type guard: Check if config is stdio-based
54
- */
55
- export const isStdioConfig = (config: MCPServerConfigUnion): config is MCPServerConfig =>
56
- config.type === 'stdio';
57
-
58
- /**
59
- * Type guard: Check if config is HTTP-based
49
+ * Type guard for HTTP config
60
50
  */
61
- export const isHttpConfig = (config: MCPServerConfigUnion): config is MCPServerConfigHTTP =>
62
- config.type === 'http';
51
+ export function isHttpConfig(config: MCPServerConfigUnion): config is MCPServerConfigHTTP {
52
+ return config.type === 'http';
53
+ }
63
54
 
64
55
  /**
65
- * Type guard: Check if config is CLI command-based (stdio)
66
- * Alias for isStdioConfig for backward compatibility
56
+ * Type guard for CLI command config (stdio)
67
57
  */
68
- export const isCLICommandConfig = (config: MCPServerConfigUnion): config is MCPServerConfig =>
69
- isStdioConfig(config);
58
+ export function isCLICommandConfig(config: MCPServerConfigUnion): config is MCPServerConfig {
59
+ return isStdioConfig(config);
60
+ }
@@ -69,8 +69,6 @@ export interface Target {
69
69
  // Configuration
70
70
  /** Target-specific configuration */
71
71
  readonly config: TargetConfig;
72
- /** MCP server configuration for this target */
73
- readonly mcpServerConfig?: MCPServerConfigFlags;
74
72
 
75
73
  // Required transformation methods
76
74
  /** Transform agent content for the target */
package/src/types.ts CHANGED
@@ -24,7 +24,6 @@ export type {
24
24
  } from './types/common.types.js';
25
25
  export type {
26
26
  MCPServerConfig,
27
- MCPServerConfigFlags,
28
27
  MCPServerConfigHTTP,
29
28
  MCPServerConfigUnion,
30
29
  OpenCodeConfig,
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import chalk from 'chalk';
4
4
  import type { Target } from '../types.js';
5
+ import { MCP_SERVER_REGISTRY } from '../config/servers.js';
5
6
 
6
7
  /**
7
8
  * Files to delete during sync for each target
@@ -157,3 +158,108 @@ export async function confirmSync(): Promise<boolean> {
157
158
  ]);
158
159
  return confirm;
159
160
  }
161
+
162
+ /**
163
+ * Check MCP servers - find servers not in Flow registry
164
+ */
165
+ export async function checkMCPServers(cwd: string): Promise<string[]> {
166
+ const mcpPath = path.join(cwd, '.mcp.json');
167
+
168
+ if (!fs.existsSync(mcpPath)) {
169
+ return [];
170
+ }
171
+
172
+ try {
173
+ const content = await fs.promises.readFile(mcpPath, 'utf-8');
174
+ const mcpConfig = JSON.parse(content);
175
+
176
+ if (!mcpConfig.mcpServers) {
177
+ return [];
178
+ }
179
+
180
+ const installedServers = Object.keys(mcpConfig.mcpServers);
181
+ const registryServers = Object.keys(MCP_SERVER_REGISTRY);
182
+
183
+ // Find servers not in registry
184
+ return installedServers.filter(id => !registryServers.includes(id));
185
+ } catch (error) {
186
+ console.warn(chalk.yellow('⚠ Failed to read .mcp.json'));
187
+ return [];
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Show non-registry servers
193
+ */
194
+ export function showNonRegistryServers(servers: string[]): void {
195
+ if (servers.length === 0) {
196
+ return;
197
+ }
198
+
199
+ console.log(chalk.cyan.bold('\n📋 MCP Registry Check\n'));
200
+ console.log(chalk.yellow('⚠️ 以下 MCP servers 唔係 Flow registry 入面:\n'));
201
+
202
+ servers.forEach(server => {
203
+ console.log(chalk.dim(` - ${server}`));
204
+ });
205
+
206
+ console.log(chalk.dim('\n可能原因:'));
207
+ console.log(chalk.dim(' 1. Flow registry 已移除'));
208
+ console.log(chalk.dim(' 2. 你自己手動安裝\n'));
209
+ }
210
+
211
+ /**
212
+ * Select servers to remove
213
+ */
214
+ export async function selectServersToRemove(servers: string[]): Promise<string[]> {
215
+ const { default: inquirer } = await import('inquirer');
216
+ const { selected } = await inquirer.prompt([
217
+ {
218
+ type: 'checkbox',
219
+ name: 'selected',
220
+ message: '選擇要刪除既 servers:',
221
+ choices: servers.map(s => ({ name: s, value: s })),
222
+ },
223
+ ]);
224
+ return selected;
225
+ }
226
+
227
+ /**
228
+ * Remove MCP servers from .mcp.json
229
+ */
230
+ export async function removeMCPServers(cwd: string, serversToRemove: string[]): Promise<number> {
231
+ if (serversToRemove.length === 0) {
232
+ return 0;
233
+ }
234
+
235
+ const mcpPath = path.join(cwd, '.mcp.json');
236
+
237
+ try {
238
+ const content = await fs.promises.readFile(mcpPath, 'utf-8');
239
+ const mcpConfig = JSON.parse(content);
240
+
241
+ if (!mcpConfig.mcpServers) {
242
+ return 0;
243
+ }
244
+
245
+ let removedCount = 0;
246
+ for (const server of serversToRemove) {
247
+ if (mcpConfig.mcpServers[server]) {
248
+ delete mcpConfig.mcpServers[server];
249
+ removedCount++;
250
+ }
251
+ }
252
+
253
+ // Write back
254
+ await fs.promises.writeFile(
255
+ mcpPath,
256
+ JSON.stringify(mcpConfig, null, 2) + '\n',
257
+ 'utf-8'
258
+ );
259
+
260
+ return removedCount;
261
+ } catch (error) {
262
+ console.warn(chalk.yellow('⚠ Failed to update .mcp.json'));
263
+ return 0;
264
+ }
265
+ }
@@ -1,168 +0,0 @@
1
- import chalk from 'chalk';
2
- import { Command } from 'commander';
3
- import ora from 'ora';
4
- import { ReindexMonitor } from '../components/reindex-progress.js';
5
- import { CodebaseIndexer } from '../services/search/codebase-indexer.js';
6
- import { getDefaultEmbeddingProvider } from '../services/search/embeddings.js';
7
- import { getSearchService } from '../services/search/unified-search-service.js';
8
- import { CLIError } from '../utils/error-handler.js';
9
-
10
- export const codebaseSearchCommand = new Command('search')
11
- .description('Search codebase files and source code')
12
- .argument('<query>', 'Search query - use natural language, function names, or technical terms')
13
- .option('-l, --limit <number>', 'Maximum number of results to return', '10')
14
- .option('--include-content', 'Include file content snippets in results', true)
15
- .option('--extensions <exts...>', 'Filter by file extensions (e.g., .ts .tsx .js)')
16
- .option('--path <pattern>', 'Filter by path pattern (e.g., src/components)')
17
- .option('--exclude <patterns...>', 'Exclude paths containing these patterns')
18
- .action(async (query, options) => {
19
- try {
20
- console.log('');
21
- console.log(chalk.cyan.bold('▸ Search Codebase'));
22
- console.log(chalk.gray(` Query: "${query}"`));
23
-
24
- const spinner = ora('Searching...').start();
25
- const searchService = getSearchService();
26
- await searchService.initialize();
27
-
28
- const result = await searchService.searchCodebase(query, {
29
- limit: Number.parseInt(String(options.limit), 10) || 10,
30
- include_content: options.includeContent !== false,
31
- file_extensions: options.extensions,
32
- path_filter: options.path,
33
- exclude_paths: options.exclude,
34
- });
35
-
36
- spinner.stop();
37
-
38
- const output = searchService.formatResultsForCLI(result.results, query, result.totalIndexed);
39
- console.log(output);
40
- console.log('');
41
- } catch (error) {
42
- console.error(chalk.red(`\n✗ Error: ${(error as Error).message}\n`));
43
- process.exit(1);
44
- }
45
- });
46
-
47
- export const codebaseReindexCommand = new Command('reindex')
48
- .description('Reindex all codebase files')
49
- .action(async () => {
50
- try {
51
- const indexer = new CodebaseIndexer();
52
-
53
- // Check if API key exists - only use embeddings if key is present
54
- const hasApiKey = !!process.env.OPENAI_API_KEY;
55
- const embeddingProvider = hasApiKey ? await getDefaultEmbeddingProvider() : undefined;
56
- const mode: 'tfidf-only' | 'semantic' = hasApiKey ? 'semantic' : 'tfidf-only';
57
-
58
- // Create monitor for progress display
59
- const monitor = new ReindexMonitor();
60
-
61
- // Start the UI
62
- monitor.start(0); // Will update total when we know file count
63
-
64
- let totalFiles = 0;
65
- let phase: 'tokenizing' | 'calculating' | 'completed' = 'tokenizing';
66
-
67
- // Set initial mode
68
- monitor.updateProgress({ mode });
69
-
70
- const result = await indexer.indexCodebase({
71
- force: true, // Reindex should always force rebuild
72
- embeddingProvider,
73
- onProgress: (progress) => {
74
- // Track total files on first progress update
75
- if (totalFiles === 0 && progress.total > 0) {
76
- totalFiles = progress.total;
77
- }
78
-
79
- // Update Ink UI only (stderr output was interfering with Ink rendering)
80
- monitor.updateProgress({
81
- current: progress.current,
82
- total: progress.total,
83
- fileName: progress.fileName,
84
- status: progress.status,
85
- phase,
86
- });
87
- },
88
- });
89
-
90
- // Handle cache hit - simulate progress to show 100%
91
- if (result.stats.cacheHit) {
92
- monitor.updateProgress({
93
- current: result.stats.totalFiles,
94
- total: result.stats.totalFiles,
95
- fileName: '',
96
- status: 'completed',
97
- phase: 'tokenizing',
98
- });
99
-
100
- // Small delay
101
- await new Promise((resolve) => setTimeout(resolve, 300));
102
- } else {
103
- // Update to calculating phase
104
- phase = 'calculating';
105
- monitor.updateProgress({ phase: 'calculating' });
106
-
107
- // Small delay to show calculating phase
108
- await new Promise((resolve) => setTimeout(resolve, 500));
109
- }
110
-
111
- // Show completion with stats
112
- monitor.updateProgress({
113
- phase: 'completed',
114
- stats: {
115
- documentsProcessed: result.stats.totalFiles,
116
- uniqueTerms: result.tfidfIndex.idf.size,
117
- },
118
- });
119
-
120
- // Give time to see the completion message
121
- await new Promise((resolve) => setTimeout(resolve, 2000));
122
-
123
- monitor.stop();
124
- } catch (error) {
125
- throw new CLIError(`Codebase reindex failed: ${(error as Error).message}`);
126
- }
127
- });
128
-
129
- export const codebaseStatusCommand = new Command('status')
130
- .description('Get codebase search system status')
131
- .action(async () => {
132
- try {
133
- console.log('');
134
- console.log(chalk.cyan.bold('▸ Codebase Status'));
135
-
136
- const searchService = getSearchService();
137
- await searchService.initialize();
138
- const status = await searchService.getStatus();
139
-
140
- if (status.codebase.indexed) {
141
- console.log(chalk.green('\n✓ Indexed and ready'));
142
- console.log(chalk.gray(` Files: ${status.codebase.fileCount}`));
143
- if (status.codebase.indexedAt) {
144
- console.log(
145
- chalk.gray(` Last indexed: ${new Date(status.codebase.indexedAt).toLocaleString()}`)
146
- );
147
- }
148
- } else {
149
- console.log(chalk.yellow('\n⚠ Not indexed'));
150
- console.log(chalk.gray(' Run: sylphx-flow codebase reindex'));
151
- }
152
-
153
- console.log(chalk.cyan('\n▸ Available Commands'));
154
- console.log(chalk.gray(' • codebase search <query>'));
155
- console.log(chalk.gray(' • codebase reindex'));
156
- console.log(chalk.gray(' • codebase status'));
157
- console.log('');
158
- } catch (error) {
159
- console.error(chalk.red(`\n✗ Error: ${(error as Error).message}\n`));
160
- process.exit(1);
161
- }
162
- });
163
-
164
- export const codebaseCommand = new Command('codebase')
165
- .description('Manage codebase indexing and search')
166
- .addCommand(codebaseSearchCommand)
167
- .addCommand(codebaseReindexCommand)
168
- .addCommand(codebaseStatusCommand);
@@ -1,161 +0,0 @@
1
- /**
2
- * Knowledge CLI commands
3
- * Knowledge base search and management functionality
4
- */
5
-
6
- import { Command } from 'commander';
7
- import { getKnowledgeContent } from '../domains/knowledge/resources.js';
8
- import { getSearchService } from '../services/search/unified-search-service.js';
9
- import { CLIError } from '../utils/error-handler.js';
10
-
11
- /**
12
- * Knowledge search command
13
- */
14
- export const knowledgeSearchCommand = new Command('search')
15
- .description('Search knowledge base, documentation, and guides')
16
- .argument('<query>', 'Search query - use natural language, technology names, or topic keywords')
17
- .option('-l, --limit <number>', 'Maximum number of results to return', '10')
18
- .option('--include-content', 'Include full content in results', true)
19
- .action(async (query, options) => {
20
- try {
21
- console.log(`📚 Searching knowledge base for: "${query}"`);
22
-
23
- const searchService = getSearchService();
24
- await searchService.initialize();
25
-
26
- const result = await searchService.searchKnowledge(query, {
27
- limit: Number.parseInt(options.limit, 10) || 10,
28
- include_content: options.includeContent !== false,
29
- });
30
-
31
- const output = searchService.formatResultsForCLI(result.results, query, result.totalIndexed);
32
- console.log(output);
33
- } catch (error) {
34
- throw new CLIError(`Knowledge search failed: ${(error as Error).message}`);
35
- }
36
- });
37
-
38
- /**
39
- * Knowledge get command
40
- */
41
- export const knowledgeGetCommand = new Command('get')
42
- .description('Get specific knowledge document by URI')
43
- .argument('<uri>', 'Knowledge URI to access (e.g., "knowledge://stacks/react-app")')
44
- .action(async (uri) => {
45
- try {
46
- const content = await getKnowledgeContent(uri);
47
- console.log(content);
48
- } catch (error) {
49
- const errorMessage = `Knowledge get failed: ${(error as Error).message}`;
50
-
51
- // Show available URIs
52
- const searchService = getSearchService();
53
- const availableURIs = await searchService.getAvailableKnowledgeURIs();
54
- if (availableURIs.length > 0) {
55
- console.log('\n**Available knowledge URIs:**');
56
- for (const uri of availableURIs) {
57
- console.log(`• ${uri}`);
58
- }
59
- }
60
- throw new CLIError(errorMessage);
61
- }
62
- });
63
-
64
- /**
65
- * Knowledge list command
66
- */
67
- export const knowledgeListCommand = new Command('list')
68
- .description('List all available knowledge resources')
69
- .option('--category <type>', 'Filter by category (stacks, guides, universal, data)')
70
- .action(async (options) => {
71
- try {
72
- const searchService = getSearchService();
73
- await searchService.initialize();
74
- const availableURIs = await searchService.getAvailableKnowledgeURIs();
75
-
76
- if (availableURIs.length === 0) {
77
- console.log('📭 No knowledge documents available');
78
- return;
79
- }
80
-
81
- let filteredURIs = availableURIs;
82
- if (options.category) {
83
- filteredURIs = availableURIs.filter((uri) => uri.includes(`/${options.category}/`));
84
- }
85
-
86
- console.log(`📚 Available Knowledge Resources (${filteredURIs.length} documents):\n`);
87
-
88
- // Group by category
89
- const grouped = filteredURIs.reduce(
90
- (acc, uri) => {
91
- const category = uri.split('/')[2] || 'unknown';
92
- if (!acc[category]) {
93
- acc[category] = [];
94
- }
95
- acc[category].push(uri);
96
- return acc;
97
- },
98
- {} as Record<string, string[]>
99
- );
100
-
101
- for (const [category, uris] of Object.entries(grouped)) {
102
- console.log(`### ${category.charAt(0).toUpperCase() + category.slice(1)}`);
103
- for (const uri of uris) {
104
- const name = uri.split('/').pop() || 'Unknown';
105
- console.log(`• ${name} - ${uri}`);
106
- }
107
- console.log('');
108
- }
109
-
110
- console.log('**Usage:**');
111
- console.log('• sylphx knowledge search <query> - Search knowledge base');
112
- console.log('• sylphx knowledge get <uri> - Get specific document');
113
- } catch (error) {
114
- throw new CLIError(`Knowledge status failed: ${(error as Error).message}`);
115
- }
116
- });
117
-
118
- /**
119
- * Knowledge status command
120
- */
121
- export const knowledgeStatusCommand = new Command('status')
122
- .description('Get knowledge base system status')
123
- .action(async () => {
124
- try {
125
- console.log('\n### 📚 Knowledge Base Status\n');
126
-
127
- const searchService = getSearchService();
128
- await searchService.initialize();
129
- const status = await searchService.getStatus();
130
-
131
- if (status.knowledge.indexed) {
132
- console.log('**Status:** ✓ Ready');
133
- console.log(`**Documents:** ${status.knowledge.documentCount} files`);
134
- } else if (status.knowledge.isIndexing) {
135
- console.log(`**Status:** 🔄 Building index (${status.knowledge.progress || 0}%)`);
136
- console.log('**Note:** Please wait a moment and try again');
137
- } else {
138
- console.log('**Status:** ⚠️ Not initialized');
139
- console.log('**Note:** Will auto-index on first search');
140
- }
141
-
142
- console.log('\n**Available Commands:**');
143
- console.log('• sylphx knowledge search <query> - Search knowledge base');
144
- console.log('• sylphx knowledge get <uri> - Get specific document');
145
- } catch (error) {
146
- throw new CLIError(`Knowledge list failed: ${(error as Error).message}`);
147
- }
148
- });
149
-
150
- /**
151
- * Main knowledge command
152
- */
153
- export const knowledgeCommand = new Command('knowledge').description(
154
- 'Knowledge base search and management commands'
155
- );
156
-
157
- // Add subcommands
158
- knowledgeCommand.addCommand(knowledgeSearchCommand);
159
- knowledgeCommand.addCommand(knowledgeGetCommand);
160
- knowledgeCommand.addCommand(knowledgeListCommand);
161
- knowledgeCommand.addCommand(knowledgeStatusCommand);
@@ -1,45 +0,0 @@
1
- import { getTarget, getDefaultTargetUnsafe } from '../config/targets.js';
2
- import { projectSettings } from '../utils/settings.js';
3
- import type { MCPServerConfigFlags } from '../types.js';
4
-
5
- /**
6
- * Get the current target's MCP server configuration
7
- * Follows the same resolution pattern as targetManager.resolveTarget()
8
- * Returns undefined if no target is set or target has no mcpServerConfig
9
- */
10
- export async function useTargetConfig(): Promise<MCPServerConfigFlags | undefined> {
11
- try {
12
- // Try to get target from project settings first
13
- const settings = await projectSettings.load();
14
- const targetId = settings?.target;
15
-
16
- if (targetId) {
17
- const targetOption = getTarget(targetId);
18
- if (targetOption._tag === 'Some') {
19
- return targetOption.value.mcpServerConfig;
20
- }
21
- }
22
-
23
- // Fall back to default target
24
- const defaultTarget = getDefaultTargetUnsafe();
25
- return defaultTarget.mcpServerConfig;
26
- } catch {
27
- // If no target can be resolved, return undefined
28
- return undefined;
29
- }
30
- }
31
-
32
- /**
33
- * Get a specific target's MCP server configuration by ID
34
- */
35
- export function useTargetConfigById(targetId: string): MCPServerConfigFlags | undefined {
36
- try {
37
- const targetOption = getTarget(targetId);
38
- if (targetOption._tag === 'None') {
39
- return undefined;
40
- }
41
- return targetOption.value.mcpServerConfig;
42
- } catch {
43
- return undefined;
44
- }
45
- }