@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.
- package/CHANGELOG.md +34 -0
- package/package.json +1 -1
- package/src/commands/flow-command.ts +28 -0
- package/src/commands/hook-command.ts +10 -230
- package/src/composables/index.ts +0 -1
- package/src/config/servers.ts +35 -78
- package/src/core/interfaces.ts +0 -33
- package/src/domains/index.ts +0 -2
- package/src/index.ts +0 -4
- package/src/services/mcp-service.ts +0 -16
- package/src/targets/claude-code.ts +3 -9
- package/src/targets/functional/claude-code-logic.ts +4 -22
- package/src/targets/opencode.ts +0 -6
- package/src/types/mcp.types.ts +29 -38
- package/src/types/target.types.ts +0 -2
- package/src/types.ts +0 -1
- package/src/utils/sync-utils.ts +106 -0
- package/src/commands/codebase-command.ts +0 -168
- package/src/commands/knowledge-command.ts +0 -161
- package/src/composables/useTargetConfig.ts +0 -45
- package/src/core/formatting/bytes.test.ts +0 -115
- package/src/core/validation/limit.test.ts +0 -155
- package/src/core/validation/query.test.ts +0 -44
- package/src/domains/codebase/index.ts +0 -5
- package/src/domains/codebase/tools.ts +0 -139
- package/src/domains/knowledge/index.ts +0 -10
- package/src/domains/knowledge/resources.ts +0 -537
- package/src/domains/knowledge/tools.ts +0 -174
- package/src/services/search/base-indexer.ts +0 -156
- package/src/services/search/codebase-indexer-types.ts +0 -38
- package/src/services/search/codebase-indexer.ts +0 -647
- package/src/services/search/embeddings-provider.ts +0 -455
- package/src/services/search/embeddings.ts +0 -316
- package/src/services/search/functional-indexer.ts +0 -323
- package/src/services/search/index.ts +0 -27
- package/src/services/search/indexer.ts +0 -380
- package/src/services/search/knowledge-indexer.ts +0 -422
- package/src/services/search/semantic-search.ts +0 -244
- package/src/services/search/tfidf.ts +0 -559
- package/src/services/search/unified-search-service.ts +0 -888
- package/src/services/storage/cache-storage.ts +0 -487
- package/src/services/storage/drizzle-storage.ts +0 -581
- package/src/services/storage/index.ts +0 -15
- package/src/services/storage/lancedb-vector-storage.ts +0 -494
- package/src/services/storage/memory-storage.ts +0 -268
- package/src/services/storage/separated-storage.ts +0 -467
- 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
|
|
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
|
|
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.
|
|
177
|
-
return failure(configError('
|
|
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);
|
package/src/targets/opencode.ts
CHANGED
|
@@ -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',
|
package/src/types/mcp.types.ts
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP (Model Context Protocol)
|
|
3
|
-
*
|
|
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
|
|
10
|
-
* Communicates via standard input/output
|
|
7
|
+
* Base MCP server configuration with stdio transport
|
|
11
8
|
*/
|
|
12
|
-
export
|
|
9
|
+
export type MCPServerConfig = {
|
|
13
10
|
type: 'stdio';
|
|
14
|
-
command:
|
|
15
|
-
args?:
|
|
11
|
+
command: string;
|
|
12
|
+
args?: string[];
|
|
16
13
|
env?: Record<string, string>;
|
|
17
|
-
}
|
|
14
|
+
};
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
|
-
* MCP server configuration
|
|
21
|
-
* Communicates via HTTP requests
|
|
17
|
+
* MCP server configuration with HTTP transport
|
|
22
18
|
*/
|
|
23
|
-
export
|
|
19
|
+
export type MCPServerConfigHTTP = {
|
|
24
20
|
type: 'http';
|
|
25
|
-
url:
|
|
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
|
-
*
|
|
36
|
-
* Used to disable specific server features per target
|
|
31
|
+
* OpenCode-specific configuration format
|
|
37
32
|
*/
|
|
38
|
-
export type
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
*
|
|
42
|
+
* Type guard for stdio config
|
|
46
43
|
*/
|
|
47
|
-
export
|
|
48
|
-
|
|
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
|
|
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
|
|
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
|
|
66
|
-
* Alias for isStdioConfig for backward compatibility
|
|
56
|
+
* Type guard for CLI command config (stdio)
|
|
67
57
|
*/
|
|
68
|
-
export
|
|
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
package/src/utils/sync-utils.ts
CHANGED
|
@@ -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
|
-
}
|