@sylphx/flow 1.0.1 → 1.0.3
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 +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- package/dist/shared/chunk-x66eh37x.js +0 -137
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge tools
|
|
3
|
+
* All tools for working with knowledge base, documentation, and guides
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { getSearchService } from '../../services/search/unified-search-service.js';
|
|
9
|
+
import { getKnowledgeContent } from './resources.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Register knowledge search tool
|
|
13
|
+
*/
|
|
14
|
+
export function registerKnowledgeSearchTool(server: McpServer): void {
|
|
15
|
+
server.registerTool(
|
|
16
|
+
'knowledge_search',
|
|
17
|
+
{
|
|
18
|
+
description: `Search knowledge base, documentation, guides, and reference materials. Use this for domain knowledge, best practices, setup instructions, and conceptual information.
|
|
19
|
+
|
|
20
|
+
**IMPORTANT: Use this tool PROACTIVELY before starting work, not reactively when stuck.**
|
|
21
|
+
|
|
22
|
+
This tool searches across all knowledge resources and returns the most relevant matches. Use include_content=false to reduce context usage, then use knowledge_get for specific documents.
|
|
23
|
+
|
|
24
|
+
When to use this tool (BEFORE starting work):
|
|
25
|
+
- **Before research/clarification**: Check relevant stack/universal knowledge to understand domain constraints
|
|
26
|
+
- **Before design/architecture**: Review architecture patterns, security, and performance best practices
|
|
27
|
+
- **Before implementation**: Consult framework-specific patterns, common pitfalls, and best practices
|
|
28
|
+
- **Before testing/QA**: Review testing strategies, coverage requirements, and quality standards
|
|
29
|
+
- **Before deployment**: Check deployment patterns, infrastructure, and monitoring guidance
|
|
30
|
+
|
|
31
|
+
Available knowledge categories:
|
|
32
|
+
- **stacks**: Framework-specific patterns (React, Next.js, Node.js)
|
|
33
|
+
- **data**: Database patterns (SQL, indexing, migrations)
|
|
34
|
+
- **guides**: Architecture guidance (SaaS, tech stack, UI/UX)
|
|
35
|
+
- **universal**: Cross-cutting concerns (security, performance, testing, deployment)
|
|
36
|
+
|
|
37
|
+
The knowledge is curated for LLM code generation - includes decision trees, common bugs, and practical patterns.
|
|
38
|
+
|
|
39
|
+
**Best Practice**: Check relevant knowledge BEFORE making decisions or writing code, not after encountering issues.`,
|
|
40
|
+
inputSchema: {
|
|
41
|
+
query: z
|
|
42
|
+
.string()
|
|
43
|
+
.describe('Search query - use natural language, technology names, or topic keywords'),
|
|
44
|
+
limit: z
|
|
45
|
+
.number()
|
|
46
|
+
.default(10)
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Maximum number of results to return (default: 10)'),
|
|
49
|
+
include_content: z
|
|
50
|
+
.boolean()
|
|
51
|
+
.default(true)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe(
|
|
54
|
+
'Include full content in results (default: true). Use false to reduce context, then knowledge_get for specific docs'
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async ({ query, limit = 10, include_content = true }) => {
|
|
59
|
+
try {
|
|
60
|
+
// Use unified search service - same logic as CLI
|
|
61
|
+
const searchService = getSearchService();
|
|
62
|
+
await searchService.initialize();
|
|
63
|
+
|
|
64
|
+
// Check knowledge base status
|
|
65
|
+
const status = await searchService.getStatus();
|
|
66
|
+
|
|
67
|
+
if (status.knowledge.isIndexing) {
|
|
68
|
+
const progressBar =
|
|
69
|
+
'█'.repeat(Math.floor((status.knowledge.progress || 0) / 5)) +
|
|
70
|
+
'░'.repeat(20 - Math.floor((status.knowledge.progress || 0) / 5));
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: `⏳ **Knowledge Base Indexing In Progress**\n\nThe knowledge base is currently being indexed. Please wait...\n\n**Progress:** ${status.knowledge.progress || 0}%\n\`${progressBar}\`\n\n**Status:**\n- Documents: ${status.knowledge.documentCount || 0}\n- Building search index for knowledge resources\n\n**Estimated time:** ${status.knowledge.progress && status.knowledge.progress > 0 ? 'Less than 10 seconds' : 'Starting...'}\n\n💡 **Tip:** Knowledge base indexing is very fast. Try your search again in a few seconds.`,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!status.knowledge.indexed) {
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: '📭 **No Knowledge Documents Available**\n\nThe knowledge base appears to be empty or not properly initialized.\n\n**To fix:**\n- Check if knowledge files exist in assets/knowledge/\n- Try restarting the MCP server to trigger indexing\n- Use CLI: `sylphx search status` for diagnostics\n\n**Expected knowledge files:**\n- stacks/ (framework-specific patterns)\n- guides/ (architecture guidance)\n- universal/ (cross-cutting concerns)\n- data/ (database patterns)',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Search knowledge base using unified service
|
|
93
|
+
const result = await searchService.searchKnowledge(query, {
|
|
94
|
+
limit,
|
|
95
|
+
include_content,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Return MCP format using unified service formatter
|
|
99
|
+
return searchService.formatResultsForMCP(result.results, query, result.totalIndexed);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: `✗ Knowledge search error: ${(error as Error).message}`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register get_knowledge tool (for retrieving specific knowledge documents)
|
|
116
|
+
*/
|
|
117
|
+
export function registerGetKnowledgeTool(server: McpServer): void {
|
|
118
|
+
server.registerTool(
|
|
119
|
+
'knowledge_get',
|
|
120
|
+
{
|
|
121
|
+
description: `Get knowledge resource by exact URI.
|
|
122
|
+
|
|
123
|
+
**NOTE: Prefer using 'knowledge_search' with include_content=false first, then use this tool for specific documents.**
|
|
124
|
+
|
|
125
|
+
This tool retrieves a specific knowledge resource when you already know its exact URI from search results.
|
|
126
|
+
|
|
127
|
+
The available URIs are dynamically generated from the indexed knowledge base. Use 'knowledge_search' to discover relevant URIs first.`,
|
|
128
|
+
inputSchema: {
|
|
129
|
+
uri: z.string().describe('Knowledge URI to access (e.g., "knowledge://stacks/react-app")'),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
async ({ uri }) => {
|
|
133
|
+
try {
|
|
134
|
+
const content = await getKnowledgeContent(uri);
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{
|
|
138
|
+
type: 'text',
|
|
139
|
+
text: content,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
} catch (error: unknown) {
|
|
144
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
145
|
+
|
|
146
|
+
// Dynamically get available URIs
|
|
147
|
+
const searchService = getSearchService();
|
|
148
|
+
const availableURIs = await searchService.getAvailableKnowledgeURIs();
|
|
149
|
+
const uriList =
|
|
150
|
+
availableURIs.length > 0
|
|
151
|
+
? availableURIs.map((uri) => `• ${uri}`).join('\n')
|
|
152
|
+
: 'No knowledge documents available';
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: 'text',
|
|
158
|
+
text: `✗ Error: ${errorMessage}\n\nAvailable knowledge URIs:\n${uriList}`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
isError: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Register all knowledge tools
|
|
170
|
+
*/
|
|
171
|
+
export function registerKnowledgeTools(server: McpServer): void {
|
|
172
|
+
registerKnowledgeSearchTool(server);
|
|
173
|
+
registerGetKnowledgeTool(server);
|
|
174
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
// Logger utility
|
|
6
|
+
const Logger = {
|
|
7
|
+
info: (message: string) => console.error(`[INFO] ${message}`),
|
|
8
|
+
success: (message: string) => console.error(`[SUCCESS] ${message}`),
|
|
9
|
+
error: (message: string, error?: unknown) => {
|
|
10
|
+
console.error(`[ERROR] ${message}`);
|
|
11
|
+
if (error) {
|
|
12
|
+
console.error(error);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Helper function to validate IANA timezone
|
|
18
|
+
function isValidTimezone(timezone: string): boolean {
|
|
19
|
+
try {
|
|
20
|
+
Intl.DateTimeFormat(undefined, { timeZone: timezone });
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Helper function to validate time format (HH:MM)
|
|
28
|
+
function isValidTimeFormat(time: string): boolean {
|
|
29
|
+
const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/;
|
|
30
|
+
return timeRegex.test(time);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get current time in a specific timezone
|
|
34
|
+
function getCurrentTime(args: { timezone: string }): CallToolResult {
|
|
35
|
+
try {
|
|
36
|
+
const { timezone } = args;
|
|
37
|
+
|
|
38
|
+
// Validate timezone
|
|
39
|
+
if (!isValidTimezone(timezone)) {
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: `✗ Invalid timezone: ${timezone}. Please use a valid IANA timezone name (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo').`,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
isError: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Get current time in specified timezone
|
|
52
|
+
const now = new Date();
|
|
53
|
+
const timeFormatter = new Intl.DateTimeFormat('en-US', {
|
|
54
|
+
timeZone: timezone,
|
|
55
|
+
year: 'numeric',
|
|
56
|
+
month: 'long',
|
|
57
|
+
day: 'numeric',
|
|
58
|
+
hour: '2-digit',
|
|
59
|
+
minute: '2-digit',
|
|
60
|
+
second: '2-digit',
|
|
61
|
+
timeZoneName: 'long',
|
|
62
|
+
hour12: false,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const parts = timeFormatter.formatToParts(now);
|
|
66
|
+
const formatObject: Record<string, string> = {};
|
|
67
|
+
for (const part of parts) {
|
|
68
|
+
formatObject[part.type] = part.value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const time24 = new Intl.DateTimeFormat('en-US', {
|
|
72
|
+
timeZone: timezone,
|
|
73
|
+
hour: '2-digit',
|
|
74
|
+
minute: '2-digit',
|
|
75
|
+
hour12: false,
|
|
76
|
+
}).format(now);
|
|
77
|
+
|
|
78
|
+
const isoString = now.toLocaleString('sv-SE', { timeZone: timezone });
|
|
79
|
+
|
|
80
|
+
Logger.info(`Retrieved current time for timezone: ${timezone}`);
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: JSON.stringify(
|
|
86
|
+
{
|
|
87
|
+
timezone,
|
|
88
|
+
current_time: {
|
|
89
|
+
date: `${formatObject.month} ${formatObject.day}, ${formatObject.year}`,
|
|
90
|
+
time_24h: time24,
|
|
91
|
+
time_with_seconds: timeFormatter.format(now),
|
|
92
|
+
timezone_name: formatObject.timeZoneName,
|
|
93
|
+
iso_format: `${isoString.replace(' ', 'T')}Z`,
|
|
94
|
+
unix_timestamp: Math.floor(now.getTime() / 1000),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
null,
|
|
98
|
+
2
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
} catch (error: unknown) {
|
|
104
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
105
|
+
Logger.error('Error getting current time', error);
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: 'text',
|
|
110
|
+
text: `✗ Error getting current time: ${errorMessage}`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
isError: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Convert time between timezones
|
|
119
|
+
function convertTime(args: {
|
|
120
|
+
source_timezone: string;
|
|
121
|
+
time: string;
|
|
122
|
+
target_timezone: string;
|
|
123
|
+
}): CallToolResult {
|
|
124
|
+
try {
|
|
125
|
+
const { source_timezone, time, target_timezone } = args;
|
|
126
|
+
|
|
127
|
+
// Validate timezones
|
|
128
|
+
if (!isValidTimezone(source_timezone)) {
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `✗ Invalid source timezone: ${source_timezone}. Please use a valid IANA timezone name.`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!isValidTimezone(target_timezone)) {
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'text',
|
|
145
|
+
text: `✗ Invalid target timezone: ${target_timezone}. Please use a valid IANA timezone name.`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
isError: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Validate time format
|
|
153
|
+
if (!isValidTimeFormat(time)) {
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: 'text',
|
|
158
|
+
text: `✗ Invalid time format: ${time}. Please use 24-hour format (HH:MM).`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
isError: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Parse the time and create a date object for today in source timezone
|
|
166
|
+
const [hours, minutes] = time.split(':').map(Number);
|
|
167
|
+
const now = new Date();
|
|
168
|
+
|
|
169
|
+
// Create a date object representing the time in source timezone
|
|
170
|
+
const sourceDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes);
|
|
171
|
+
|
|
172
|
+
// Format the source time to get the correct representation
|
|
173
|
+
const sourceFormatter = new Intl.DateTimeFormat('en-US', {
|
|
174
|
+
timeZone: source_timezone,
|
|
175
|
+
year: 'numeric',
|
|
176
|
+
month: '2-digit',
|
|
177
|
+
day: '2-digit',
|
|
178
|
+
hour: '2-digit',
|
|
179
|
+
minute: '2-digit',
|
|
180
|
+
second: '2-digit',
|
|
181
|
+
hour12: false,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const sourceParts = sourceFormatter.formatToParts(sourceDate);
|
|
185
|
+
const sourceFormatObject: Record<string, string> = {};
|
|
186
|
+
for (const part of sourceParts) {
|
|
187
|
+
sourceFormatObject[part.type] = part.value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Convert to target timezone
|
|
191
|
+
const targetFormatter = new Intl.DateTimeFormat('en-US', {
|
|
192
|
+
timeZone: target_timezone,
|
|
193
|
+
year: 'numeric',
|
|
194
|
+
month: 'long',
|
|
195
|
+
day: 'numeric',
|
|
196
|
+
hour: '2-digit',
|
|
197
|
+
minute: '2-digit',
|
|
198
|
+
second: '2-digit',
|
|
199
|
+
timeZoneName: 'long',
|
|
200
|
+
hour12: false,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const targetTime24 = new Intl.DateTimeFormat('en-US', {
|
|
204
|
+
timeZone: target_timezone,
|
|
205
|
+
hour: '2-digit',
|
|
206
|
+
minute: '2-digit',
|
|
207
|
+
hour12: false,
|
|
208
|
+
}).format(sourceDate);
|
|
209
|
+
|
|
210
|
+
const targetParts = targetFormatter.formatToParts(sourceDate);
|
|
211
|
+
const targetFormatObject: Record<string, string> = {};
|
|
212
|
+
for (const part of targetParts) {
|
|
213
|
+
targetFormatObject[part.type] = part.value;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const targetDate = new Date(sourceDate.toLocaleString('en-US', { timeZone: target_timezone }));
|
|
217
|
+
const timeDiffMs = targetDate.getTime() - sourceDate.getTime();
|
|
218
|
+
const timeDiffHours = Math.round(timeDiffMs / (1000 * 60 * 60));
|
|
219
|
+
|
|
220
|
+
Logger.info(`Converted time from ${source_timezone} to ${target_timezone}`);
|
|
221
|
+
return {
|
|
222
|
+
content: [
|
|
223
|
+
{
|
|
224
|
+
type: 'text',
|
|
225
|
+
text: JSON.stringify(
|
|
226
|
+
{
|
|
227
|
+
conversion: {
|
|
228
|
+
source: {
|
|
229
|
+
timezone: source_timezone,
|
|
230
|
+
time: time,
|
|
231
|
+
formatted: sourceFormatter.format(sourceDate),
|
|
232
|
+
},
|
|
233
|
+
target: {
|
|
234
|
+
timezone: target_timezone,
|
|
235
|
+
time_24h: targetTime24,
|
|
236
|
+
formatted: targetFormatter.format(sourceDate),
|
|
237
|
+
date: `${targetFormatObject.month} ${targetFormatObject.day}, ${targetFormatObject.year}`,
|
|
238
|
+
timezone_name: targetFormatObject.timeZoneName,
|
|
239
|
+
},
|
|
240
|
+
time_difference_hours: timeDiffHours,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
null,
|
|
244
|
+
2
|
|
245
|
+
),
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
};
|
|
249
|
+
} catch (error: unknown) {
|
|
250
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
251
|
+
Logger.error('Error converting time', error);
|
|
252
|
+
return {
|
|
253
|
+
content: [
|
|
254
|
+
{
|
|
255
|
+
type: 'text',
|
|
256
|
+
text: `✗ Error converting time: ${errorMessage}`,
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
isError: true,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Register all time tools
|
|
265
|
+
export function registerTimeTools(server: McpServer) {
|
|
266
|
+
server.registerTool(
|
|
267
|
+
'get_current_time',
|
|
268
|
+
{
|
|
269
|
+
description: 'Get current time in a specific timezone or system timezone',
|
|
270
|
+
inputSchema: {
|
|
271
|
+
timezone: z
|
|
272
|
+
.string()
|
|
273
|
+
.describe("IANA timezone name (e.g., 'America/New_York', 'Europe/London')"),
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
getCurrentTime
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
server.registerTool(
|
|
280
|
+
'convert_time',
|
|
281
|
+
{
|
|
282
|
+
description: 'Convert time between timezones',
|
|
283
|
+
inputSchema: {
|
|
284
|
+
source_timezone: z.string().describe('Source IANA timezone name'),
|
|
285
|
+
time: z.string().describe('Time in 24-hour format (HH:MM)'),
|
|
286
|
+
target_timezone: z.string().describe('Target IANA timezone name'),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
convertTime
|
|
290
|
+
);
|
|
291
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Sylphx Flow - Legacy CLI
|
|
4
|
+
* Project initialization and development flow management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { codebaseCommand } from './commands/codebase-command.js';
|
|
12
|
+
import { hookCommand } from './commands/hook-command.js';
|
|
13
|
+
import { knowledgeCommand } from './commands/knowledge-command.js';
|
|
14
|
+
import {
|
|
15
|
+
flowCommand,
|
|
16
|
+
statusCommand,
|
|
17
|
+
setupCommand,
|
|
18
|
+
doctorCommand,
|
|
19
|
+
upgradeCommand,
|
|
20
|
+
executeFlow,
|
|
21
|
+
} from './commands/flow-command.js';
|
|
22
|
+
|
|
23
|
+
// Read version from package.json
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const __dirname = dirname(__filename);
|
|
26
|
+
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
27
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
28
|
+
const VERSION = packageJson.version;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create the main CLI application with enhanced Commander.js configuration
|
|
32
|
+
*/
|
|
33
|
+
export function createCLI(): Command {
|
|
34
|
+
const program = new Command();
|
|
35
|
+
|
|
36
|
+
// Configure main program with better defaults
|
|
37
|
+
program
|
|
38
|
+
.name('sylphx-flow')
|
|
39
|
+
.description('Sylphx Flow - Type-safe development flow CLI')
|
|
40
|
+
.version(VERSION, '-V, --version', 'Show version number')
|
|
41
|
+
.helpOption('-h, --help', 'Display help for command')
|
|
42
|
+
.configureHelp({
|
|
43
|
+
sortSubcommands: true,
|
|
44
|
+
showGlobalOptions: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Enable strict mode for better error handling
|
|
48
|
+
program.configureOutput({
|
|
49
|
+
writeErr: (str) => process.stderr.write(str),
|
|
50
|
+
writeOut: (str) => process.stdout.write(str),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Default action: delegate to flow command for convenience
|
|
54
|
+
// This allows `sylphx-flow "prompt"` instead of requiring `sylphx-flow flow "prompt"`
|
|
55
|
+
program
|
|
56
|
+
.argument('[prompt]', 'Prompt to execute with agent (optional, supports @file.txt for file input)')
|
|
57
|
+
.option('--init-only', 'Only initialize, do not run')
|
|
58
|
+
.option('--run-only', 'Only run, skip initialization')
|
|
59
|
+
.option('--sync', 'Synchronize with Flow templates (delete and re-install template files)')
|
|
60
|
+
.option('--upgrade', 'Upgrade Sylphx Flow to latest version')
|
|
61
|
+
.option('--upgrade-target', 'Upgrade target platform (Claude Code/OpenCode)')
|
|
62
|
+
.option('--quick', 'Quick mode: use saved defaults and skip all prompts')
|
|
63
|
+
.option('--select-provider', 'Prompt to select provider each run')
|
|
64
|
+
.option('--select-agent', 'Prompt to select agent each run')
|
|
65
|
+
.option('--use-defaults', 'Skip prompts, use saved defaults')
|
|
66
|
+
.option('--provider <provider>', 'Override provider for this run (anthropic|z.ai|kimi)')
|
|
67
|
+
.option('--target <type>', 'Target platform (opencode, claude-code, auto-detect)')
|
|
68
|
+
.option('--verbose', 'Show detailed output')
|
|
69
|
+
.option('--dry-run', 'Show what would be done without making changes')
|
|
70
|
+
.option('--no-mcp', 'Skip MCP installation')
|
|
71
|
+
.option('--no-agents', 'Skip agents installation')
|
|
72
|
+
.option('--no-rules', 'Skip rules installation')
|
|
73
|
+
.option('--no-output-styles', 'Skip output styles installation')
|
|
74
|
+
.option('--no-slash-commands', 'Skip slash commands installation')
|
|
75
|
+
.option('--no-hooks', 'Skip hooks setup')
|
|
76
|
+
.option('--agent <name>', 'Agent to use (default: coder)', 'coder')
|
|
77
|
+
.option('--agent-file <path>', 'Load agent from specific file')
|
|
78
|
+
.option('-p, --print', 'Headless print mode (output only, no interactive)')
|
|
79
|
+
.option('-c, --continue', 'Continue previous conversation (requires print mode)')
|
|
80
|
+
|
|
81
|
+
// Loop mode options
|
|
82
|
+
.option('--loop [seconds]', 'Loop mode: wait N seconds between runs (default: 0 = immediate)', (value) => {
|
|
83
|
+
// If no value provided, default to 0 (no wait time)
|
|
84
|
+
return value ? parseInt(value) : 0;
|
|
85
|
+
})
|
|
86
|
+
.option('--max-runs <count>', 'Maximum iterations before stopping (default: infinite)', parseInt)
|
|
87
|
+
|
|
88
|
+
.action(async (prompt, options) => {
|
|
89
|
+
await executeFlow(prompt, options);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Add subcommands - these can still be used explicitly
|
|
93
|
+
program.addCommand(flowCommand);
|
|
94
|
+
program.addCommand(setupCommand);
|
|
95
|
+
program.addCommand(statusCommand);
|
|
96
|
+
program.addCommand(doctorCommand);
|
|
97
|
+
program.addCommand(upgradeCommand);
|
|
98
|
+
program.addCommand(codebaseCommand);
|
|
99
|
+
program.addCommand(knowledgeCommand);
|
|
100
|
+
program.addCommand(hookCommand);
|
|
101
|
+
|
|
102
|
+
return program;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Run the CLI application with enhanced error handling and process management
|
|
107
|
+
*/
|
|
108
|
+
export async function runCLI(): Promise<void> {
|
|
109
|
+
const program = createCLI();
|
|
110
|
+
|
|
111
|
+
// Set up global error handling before parsing
|
|
112
|
+
setupGlobalErrorHandling();
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Parse and execute commands - use parseAsync for async actions
|
|
116
|
+
await program.parseAsync(process.argv);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
handleCommandError(error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set up global error handlers for uncaught exceptions and unhandled rejections
|
|
124
|
+
*/
|
|
125
|
+
function setupGlobalErrorHandling(): void {
|
|
126
|
+
// Handle uncaught exceptions
|
|
127
|
+
process.on('uncaughtException', (error) => {
|
|
128
|
+
console.error('✗ Uncaught Exception:');
|
|
129
|
+
console.error(` ${error.message}`);
|
|
130
|
+
if (process.env.NODE_ENV === 'development') {
|
|
131
|
+
console.error(' Stack trace:', error.stack);
|
|
132
|
+
}
|
|
133
|
+
process.exit(1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Handle unhandled promise rejections
|
|
137
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
138
|
+
// Ignore AbortError - this is expected when user cancels operations
|
|
139
|
+
if (reason instanceof Error && reason.name === 'AbortError') {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Only log unhandled rejections in development mode
|
|
144
|
+
// Don't exit the process - let the application handle errors gracefully
|
|
145
|
+
if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
|
|
146
|
+
console.error('✗ Unhandled Promise Rejection:');
|
|
147
|
+
console.error(` Reason: ${reason}`);
|
|
148
|
+
console.error(' Promise:', promise);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Handle process termination gracefully
|
|
153
|
+
process.on('SIGINT', () => {
|
|
154
|
+
console.log('\nSylphx Flow CLI terminated by user');
|
|
155
|
+
process.exit(0);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
process.on('SIGTERM', () => {
|
|
159
|
+
console.log('\nSylphx Flow CLI terminated');
|
|
160
|
+
process.exit(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Ensure clean exit by allowing the event loop to drain
|
|
164
|
+
process.on('beforeExit', () => {
|
|
165
|
+
// Node.js will exit automatically after this handler completes
|
|
166
|
+
// No explicit process.exit() needed
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Handle command execution errors with proper formatting
|
|
172
|
+
*/
|
|
173
|
+
function handleCommandError(error: unknown): void {
|
|
174
|
+
if (error instanceof Error) {
|
|
175
|
+
// Handle Commander.js specific errors
|
|
176
|
+
if (error.name === 'CommanderError') {
|
|
177
|
+
const commanderError = error as any;
|
|
178
|
+
|
|
179
|
+
// Don't exit for help or version commands - they should already be handled
|
|
180
|
+
if (commanderError.code === 'commander.help' || commanderError.code === 'commander.version') {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// For other Commander.js errors, show the message and exit
|
|
185
|
+
console.error(`✗ ${commanderError.message}`);
|
|
186
|
+
process.exit(commanderError.exitCode || 1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle CLI errors with better formatting
|
|
190
|
+
console.error(`✗ Error: ${error.message}`);
|
|
191
|
+
|
|
192
|
+
// Show stack trace in development mode
|
|
193
|
+
if (process.env.NODE_ENV === 'development' && error.stack) {
|
|
194
|
+
console.error('\nStack trace:');
|
|
195
|
+
console.error(error.stack);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
console.error(`✗ Unknown error: ${String(error)}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Execute CLI when run as script
|
|
205
|
+
(async () => {
|
|
206
|
+
try {
|
|
207
|
+
await runCLI();
|
|
208
|
+
} catch (error) {
|
|
209
|
+
handleCommandError(error);
|
|
210
|
+
}
|
|
211
|
+
})();
|