@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.
Files changed (229) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -9
  3. package/src/commands/codebase-command.ts +168 -0
  4. package/src/commands/flow-command.ts +1137 -0
  5. package/src/commands/flow-orchestrator.ts +296 -0
  6. package/src/commands/hook-command.ts +444 -0
  7. package/src/commands/init-command.ts +92 -0
  8. package/src/commands/init-core.ts +322 -0
  9. package/src/commands/knowledge-command.ts +161 -0
  10. package/src/commands/run-command.ts +120 -0
  11. package/src/components/benchmark-monitor.tsx +331 -0
  12. package/src/components/reindex-progress.tsx +261 -0
  13. package/src/composables/functional/index.ts +14 -0
  14. package/src/composables/functional/useEnvironment.ts +171 -0
  15. package/src/composables/functional/useFileSystem.ts +139 -0
  16. package/src/composables/index.ts +5 -0
  17. package/src/composables/useEnv.ts +13 -0
  18. package/src/composables/useRuntimeConfig.ts +27 -0
  19. package/src/composables/useTargetConfig.ts +45 -0
  20. package/src/config/ai-config.ts +376 -0
  21. package/src/config/constants.ts +35 -0
  22. package/src/config/index.ts +27 -0
  23. package/src/config/rules.ts +43 -0
  24. package/src/config/servers.ts +371 -0
  25. package/src/config/targets.ts +126 -0
  26. package/src/core/agent-loader.ts +141 -0
  27. package/src/core/agent-manager.ts +174 -0
  28. package/src/core/ai-sdk.ts +603 -0
  29. package/src/core/app-factory.ts +381 -0
  30. package/src/core/builtin-agents.ts +9 -0
  31. package/src/core/command-system.ts +550 -0
  32. package/src/core/config-system.ts +550 -0
  33. package/src/core/connection-pool.ts +390 -0
  34. package/src/core/di-container.ts +155 -0
  35. package/src/core/error-handling.ts +519 -0
  36. package/src/core/formatting/bytes.test.ts +115 -0
  37. package/src/core/formatting/bytes.ts +64 -0
  38. package/src/core/functional/async.ts +313 -0
  39. package/src/core/functional/either.ts +109 -0
  40. package/src/core/functional/error-handler.ts +135 -0
  41. package/src/core/functional/error-types.ts +311 -0
  42. package/src/core/functional/index.ts +19 -0
  43. package/src/core/functional/option.ts +142 -0
  44. package/src/core/functional/pipe.ts +189 -0
  45. package/src/core/functional/result.ts +204 -0
  46. package/src/core/functional/validation.ts +138 -0
  47. package/src/core/headless-display.ts +96 -0
  48. package/src/core/index.ts +6 -0
  49. package/src/core/installers/file-installer.ts +303 -0
  50. package/src/core/installers/mcp-installer.ts +213 -0
  51. package/src/core/interfaces/index.ts +22 -0
  52. package/src/core/interfaces/repository.interface.ts +91 -0
  53. package/src/core/interfaces/service.interface.ts +133 -0
  54. package/src/core/interfaces.ts +129 -0
  55. package/src/core/loop-controller.ts +200 -0
  56. package/src/core/result.ts +351 -0
  57. package/src/core/rule-loader.ts +147 -0
  58. package/src/core/rule-manager.ts +240 -0
  59. package/src/core/service-config.ts +252 -0
  60. package/src/core/session-service.ts +121 -0
  61. package/src/core/state-detector.ts +389 -0
  62. package/src/core/storage-factory.ts +115 -0
  63. package/src/core/stream-handler.ts +288 -0
  64. package/src/core/target-manager.ts +161 -0
  65. package/src/core/type-utils.ts +427 -0
  66. package/src/core/unified-storage.ts +456 -0
  67. package/src/core/upgrade-manager.ts +300 -0
  68. package/src/core/validation/limit.test.ts +155 -0
  69. package/src/core/validation/limit.ts +46 -0
  70. package/src/core/validation/query.test.ts +44 -0
  71. package/src/core/validation/query.ts +20 -0
  72. package/src/db/auto-migrate.ts +322 -0
  73. package/src/db/base-database-client.ts +144 -0
  74. package/src/db/cache-db.ts +218 -0
  75. package/src/db/cache-schema.ts +75 -0
  76. package/src/db/database.ts +70 -0
  77. package/src/db/index.ts +252 -0
  78. package/src/db/memory-db.ts +153 -0
  79. package/src/db/memory-schema.ts +29 -0
  80. package/src/db/schema.ts +289 -0
  81. package/src/db/session-repository.ts +733 -0
  82. package/src/domains/codebase/index.ts +5 -0
  83. package/src/domains/codebase/tools.ts +139 -0
  84. package/src/domains/index.ts +8 -0
  85. package/src/domains/knowledge/index.ts +10 -0
  86. package/src/domains/knowledge/resources.ts +537 -0
  87. package/src/domains/knowledge/tools.ts +174 -0
  88. package/src/domains/utilities/index.ts +6 -0
  89. package/src/domains/utilities/time/index.ts +5 -0
  90. package/src/domains/utilities/time/tools.ts +291 -0
  91. package/src/index.ts +211 -0
  92. package/src/services/agent-service.ts +273 -0
  93. package/src/services/claude-config-service.ts +252 -0
  94. package/src/services/config-service.ts +258 -0
  95. package/src/services/evaluation-service.ts +271 -0
  96. package/src/services/functional/evaluation-logic.ts +296 -0
  97. package/src/services/functional/file-processor.ts +273 -0
  98. package/src/services/functional/index.ts +12 -0
  99. package/src/services/index.ts +13 -0
  100. package/src/services/mcp-service.ts +432 -0
  101. package/src/services/memory.service.ts +476 -0
  102. package/src/services/search/base-indexer.ts +156 -0
  103. package/src/services/search/codebase-indexer-types.ts +38 -0
  104. package/src/services/search/codebase-indexer.ts +647 -0
  105. package/src/services/search/embeddings-provider.ts +455 -0
  106. package/src/services/search/embeddings.ts +316 -0
  107. package/src/services/search/functional-indexer.ts +323 -0
  108. package/src/services/search/index.ts +27 -0
  109. package/src/services/search/indexer.ts +380 -0
  110. package/src/services/search/knowledge-indexer.ts +422 -0
  111. package/src/services/search/semantic-search.ts +244 -0
  112. package/src/services/search/tfidf.ts +559 -0
  113. package/src/services/search/unified-search-service.ts +888 -0
  114. package/src/services/smart-config-service.ts +385 -0
  115. package/src/services/storage/cache-storage.ts +487 -0
  116. package/src/services/storage/drizzle-storage.ts +581 -0
  117. package/src/services/storage/index.ts +15 -0
  118. package/src/services/storage/lancedb-vector-storage.ts +494 -0
  119. package/src/services/storage/memory-storage.ts +268 -0
  120. package/src/services/storage/separated-storage.ts +467 -0
  121. package/src/services/storage/vector-storage.ts +13 -0
  122. package/src/shared/agents/index.ts +63 -0
  123. package/src/shared/files/index.ts +99 -0
  124. package/src/shared/index.ts +32 -0
  125. package/src/shared/logging/index.ts +24 -0
  126. package/src/shared/processing/index.ts +153 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/targets/claude-code.ts +574 -0
  129. package/src/targets/functional/claude-code-logic.ts +185 -0
  130. package/src/targets/functional/index.ts +6 -0
  131. package/src/targets/opencode.ts +529 -0
  132. package/src/types/agent.types.ts +32 -0
  133. package/src/types/api/batch.ts +108 -0
  134. package/src/types/api/errors.ts +118 -0
  135. package/src/types/api/index.ts +55 -0
  136. package/src/types/api/requests.ts +76 -0
  137. package/src/types/api/responses.ts +180 -0
  138. package/src/types/api/websockets.ts +85 -0
  139. package/src/types/api.types.ts +9 -0
  140. package/src/types/benchmark.ts +49 -0
  141. package/src/types/cli.types.ts +87 -0
  142. package/src/types/common.types.ts +35 -0
  143. package/src/types/database.types.ts +510 -0
  144. package/src/types/mcp-config.types.ts +448 -0
  145. package/src/types/mcp.types.ts +69 -0
  146. package/src/types/memory-types.ts +63 -0
  147. package/src/types/provider.types.ts +28 -0
  148. package/src/types/rule.types.ts +24 -0
  149. package/src/types/session.types.ts +214 -0
  150. package/src/types/target-config.types.ts +295 -0
  151. package/src/types/target.types.ts +140 -0
  152. package/src/types/todo.types.ts +25 -0
  153. package/src/types.ts +40 -0
  154. package/src/utils/advanced-tokenizer.ts +191 -0
  155. package/src/utils/agent-enhancer.ts +114 -0
  156. package/src/utils/ai-model-fetcher.ts +19 -0
  157. package/src/utils/async-file-operations.ts +516 -0
  158. package/src/utils/audio-player.ts +345 -0
  159. package/src/utils/cli-output.ts +266 -0
  160. package/src/utils/codebase-helpers.ts +211 -0
  161. package/src/utils/console-ui.ts +79 -0
  162. package/src/utils/database-errors.ts +140 -0
  163. package/src/utils/debug-logger.ts +49 -0
  164. package/src/utils/error-handler.ts +53 -0
  165. package/src/utils/file-operations.ts +310 -0
  166. package/src/utils/file-scanner.ts +259 -0
  167. package/src/utils/functional/array.ts +355 -0
  168. package/src/utils/functional/index.ts +15 -0
  169. package/src/utils/functional/object.ts +279 -0
  170. package/src/utils/functional/string.ts +281 -0
  171. package/src/utils/functional.ts +543 -0
  172. package/src/utils/help.ts +20 -0
  173. package/src/utils/immutable-cache.ts +106 -0
  174. package/src/utils/index.ts +78 -0
  175. package/src/utils/jsonc.ts +158 -0
  176. package/src/utils/logger.ts +396 -0
  177. package/src/utils/mcp-config.ts +249 -0
  178. package/src/utils/memory-tui.ts +414 -0
  179. package/src/utils/models-dev.ts +91 -0
  180. package/src/utils/notifications.ts +169 -0
  181. package/src/utils/object-utils.ts +51 -0
  182. package/src/utils/parallel-operations.ts +487 -0
  183. package/src/utils/paths.ts +143 -0
  184. package/src/utils/process-manager.ts +155 -0
  185. package/src/utils/prompts.ts +120 -0
  186. package/src/utils/search-tool-builder.ts +214 -0
  187. package/src/utils/secret-utils.ts +179 -0
  188. package/src/utils/security.ts +537 -0
  189. package/src/utils/session-manager.ts +168 -0
  190. package/src/utils/session-title.ts +87 -0
  191. package/src/utils/settings.ts +182 -0
  192. package/src/utils/simplified-errors.ts +410 -0
  193. package/src/utils/sync-utils.ts +159 -0
  194. package/src/utils/target-config.ts +570 -0
  195. package/src/utils/target-utils.ts +394 -0
  196. package/src/utils/template-engine.ts +94 -0
  197. package/src/utils/test-audio.ts +71 -0
  198. package/src/utils/todo-context.ts +46 -0
  199. package/src/utils/token-counter.ts +288 -0
  200. package/dist/index.d.ts +0 -10
  201. package/dist/index.js +0 -59554
  202. package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
  203. package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
  204. package/dist/shared/chunk-25dwp0dp.js +0 -89
  205. package/dist/shared/chunk-3pjb6063.js +0 -208
  206. package/dist/shared/chunk-4d6ydpw7.js +0 -2854
  207. package/dist/shared/chunk-4wjcadjk.js +0 -225
  208. package/dist/shared/chunk-5j4w74t6.js +0 -30
  209. package/dist/shared/chunk-5j8m3dh3.js +0 -58
  210. package/dist/shared/chunk-5thh3qem.js +0 -91
  211. package/dist/shared/chunk-6g9xy73m.js +0 -252
  212. package/dist/shared/chunk-7eq34c42.js +0 -23
  213. package/dist/shared/chunk-c2gwgx3r.js +0 -115
  214. package/dist/shared/chunk-cjd3mk4c.js +0 -1320
  215. package/dist/shared/chunk-g5cv6703.js +0 -368
  216. package/dist/shared/chunk-hpkhykhq.js +0 -574
  217. package/dist/shared/chunk-m2322pdk.js +0 -122
  218. package/dist/shared/chunk-nd5fdvaq.js +0 -26
  219. package/dist/shared/chunk-pgd3m6zf.js +0 -108
  220. package/dist/shared/chunk-qk8n91hw.js +0 -494
  221. package/dist/shared/chunk-rkkn8szp.js +0 -16855
  222. package/dist/shared/chunk-t16rfxh0.js +0 -61
  223. package/dist/shared/chunk-t4fbfa5v.js +0 -19
  224. package/dist/shared/chunk-t77h86w6.js +0 -276
  225. package/dist/shared/chunk-v0ez4aef.js +0 -71
  226. package/dist/shared/chunk-v29j2r3s.js +0 -32051
  227. package/dist/shared/chunk-vfbc6ew5.js +0 -765
  228. package/dist/shared/chunk-vmeqwm1c.js +0 -204
  229. 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,6 @@
1
+ /**
2
+ * Utilities Domain
3
+ * Common utility tools and helpers
4
+ */
5
+
6
+ export * from './time/index.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Time utilities domain - Time and date operations
3
+ */
4
+
5
+ export { registerTimeTools } from './tools.js';
@@ -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
+ })();