@sylphx/flow 1.0.2 → 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 +6 -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,444 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Hook command - Dynamic content loading for Claude Code hooks
4
+ *
5
+ * Purpose: Load rules and output styles dynamically via session hooks
6
+ * instead of installing them as static files
7
+ *
8
+ * DESIGN RATIONALE:
9
+ * - Single source of truth: assets/ directory
10
+ * - Dynamic loading: No static file maintenance
11
+ * - Flexible: Easy to extend for different hook types and targets
12
+ * - Consistent: Follows sysinfo command pattern
13
+ */
14
+
15
+ import { exec } from 'node:child_process';
16
+ import fsSync from 'node:fs';
17
+ import os from 'node:os';
18
+ import path from 'node:path';
19
+ import { promisify } from 'node:util';
20
+ import { Command } from 'commander';
21
+ import { cli } from '../utils/cli-output.js';
22
+
23
+ const execAsync = promisify(exec);
24
+
25
+ /**
26
+ * Hook types supported
27
+ */
28
+ type HookType = 'session' | 'message' | 'notification';
29
+
30
+ /**
31
+ * Target platforms supported
32
+ */
33
+ type TargetPlatform = 'claude-code';
34
+
35
+ /**
36
+ * Create the hook command
37
+ */
38
+ export const hookCommand = new Command('hook')
39
+ .description('Load dynamic system information for Claude Code hooks')
40
+ .requiredOption('--type <type>', 'Hook type (session, message)')
41
+ .option('--target <target>', 'Target platform (claude-code)', 'claude-code')
42
+ .option('--verbose', 'Show verbose output', false)
43
+ .action(async (options) => {
44
+ try {
45
+ const hookType = options.type as HookType;
46
+ const target = options.target as TargetPlatform;
47
+
48
+ // Validate hook type
49
+ if (!['session', 'message', 'notification'].includes(hookType)) {
50
+ throw new Error(
51
+ `Invalid hook type: ${hookType}. Must be 'session', 'message', or 'notification'`
52
+ );
53
+ }
54
+
55
+ // Validate target
56
+ if (target !== 'claude-code') {
57
+ throw new Error(`Invalid target: ${target}. Only 'claude-code' is currently supported`);
58
+ }
59
+
60
+ // Load and display content based on hook type
61
+ const content = await loadHookContent(hookType, target, options.verbose);
62
+
63
+ // Output the content (no extra formatting, just the content)
64
+ console.log(content);
65
+
66
+ // Explicitly exit to ensure process terminates
67
+ // REASON: Even with parseAsync(), the process may not exit due to:
68
+ // 1. Logger instances keeping event loop active
69
+ // 2. Other global resources (timers, listeners) not being cleaned up
70
+ // 3. This is a short-lived CLI command that should exit immediately after output
71
+ // Many CLI tools use process.exit() for this reason - it's the right pattern here
72
+ process.exit(0);
73
+ } catch (error) {
74
+ cli.error(
75
+ `Failed to load hook content: ${error instanceof Error ? error.message : String(error)}`
76
+ );
77
+ process.exit(1);
78
+ }
79
+ });
80
+
81
+ /**
82
+ * Load content for a specific hook type and target
83
+ */
84
+ async function loadHookContent(
85
+ hookType: HookType,
86
+ target: TargetPlatform,
87
+ verbose: boolean = false
88
+ ): Promise<string> {
89
+ if (hookType === 'session') {
90
+ return await loadSessionContent(target, verbose);
91
+ }
92
+
93
+ if (hookType === 'message') {
94
+ return await loadMessageContent(target, verbose);
95
+ }
96
+
97
+ if (hookType === 'notification') {
98
+ return await sendNotification(verbose);
99
+ }
100
+
101
+ return '';
102
+ }
103
+
104
+ /**
105
+ * Load content for session start hook
106
+ * Includes: system info only (rules and output styles are static files)
107
+ */
108
+ async function loadSessionContent(_target: TargetPlatform, verbose: boolean): Promise<string> {
109
+ // Load system info for session
110
+ if (verbose) {
111
+ cli.info('Loading system info...');
112
+ }
113
+ return await getSystemInfo('session');
114
+ }
115
+
116
+ /**
117
+ * Load content for message hook
118
+ * Includes: system status
119
+ */
120
+ async function loadMessageContent(_target: TargetPlatform, verbose: boolean): Promise<string> {
121
+ if (verbose) {
122
+ cli.info('Loading system status...');
123
+ }
124
+ return await getSystemInfo('message');
125
+ }
126
+
127
+ /**
128
+ * Get system information
129
+ */
130
+ async function getSystemInfo(hookType: 'session' | 'message'): Promise<string> {
131
+ const currentTime = new Date().toISOString();
132
+ const tempDir = os.tmpdir();
133
+
134
+ // Get memory information
135
+ const totalMem = os.totalmem();
136
+ const freeMem = os.freemem();
137
+ const usedMem = totalMem - freeMem;
138
+ const memoryUsage = ((usedMem / totalMem) * 100).toFixed(1);
139
+
140
+ // Get CPU information
141
+ const cpus = os.cpus();
142
+ const cpuCores = cpus.length;
143
+
144
+ // Get CPU usage (using load average for fast detection)
145
+ const loadAvg = os.loadavg();
146
+ const cpuUsagePercent = Math.round((loadAvg[0] / cpuCores) * 100);
147
+
148
+ // Get platform information
149
+ const platform = os.platform();
150
+ const arch = os.arch();
151
+
152
+ if (hookType === 'session') {
153
+ // Session info includes project information
154
+ const projectInfo = await detectProjectInfo();
155
+
156
+ return `## Session Information
157
+
158
+ **Platform:** ${platform} (${arch})
159
+ **Working Directory:** ${process.cwd()}
160
+ **Temp Directory:** ${tempDir}
161
+ **CPU:** ${cpuCores} cores
162
+ **Total Memory:** ${formatBytes(totalMem)}
163
+
164
+ ## Project Information
165
+
166
+ **Project Type:** ${projectInfo.type}
167
+ **Package Manager:** ${projectInfo.packageManager}${
168
+ projectInfo.name && projectInfo.name !== 'unnamed'
169
+ ? `\n**Project:** ${projectInfo.name} (${projectInfo.version})`
170
+ : ''
171
+ }`;
172
+ }
173
+
174
+ // Message info - just current status
175
+ return `## System Status
176
+
177
+ **Current Time:** ${new Date(currentTime).toLocaleString()}
178
+ **CPU:** ${cpuUsagePercent}%
179
+ **Memory:** ${memoryUsage}% used (${formatBytes(freeMem)} free)`;
180
+ }
181
+
182
+ /**
183
+ * Detect project information
184
+ */
185
+ async function detectProjectInfo() {
186
+ const cwd = process.cwd();
187
+ const packageJsonPath = path.join(cwd, 'package.json');
188
+
189
+ // Check if package.json exists
190
+ if (!fsSync.existsSync(packageJsonPath)) {
191
+ return {
192
+ type: 'unknown',
193
+ packageManager: 'none',
194
+ description: 'No package.json found',
195
+ };
196
+ }
197
+
198
+ try {
199
+ // Read package.json
200
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
201
+
202
+ // Detect project type based on dependencies and scripts
203
+ const projectType = detectProjectType(packageJson);
204
+
205
+ // Detect package manager based on package.json field, then lock files
206
+ const packageManager = detectPackageManager(cwd, packageJson);
207
+
208
+ return {
209
+ type: projectType,
210
+ packageManager: packageManager,
211
+ name: packageJson.name || 'unnamed',
212
+ version: packageJson.version || '0.0.0',
213
+ description: packageJson.description || '',
214
+ };
215
+ } catch (_error) {
216
+ return {
217
+ type: 'js/ts',
218
+ packageManager: 'unknown',
219
+ description: 'Invalid package.json',
220
+ };
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Detect project type from package.json
226
+ */
227
+ function detectProjectType(packageJson: Record<string, unknown>): string {
228
+ // Check for TypeScript
229
+ const hasTypescript =
230
+ packageJson.devDependencies?.typescript ||
231
+ packageJson.dependencies?.typescript ||
232
+ packageJson.devDependencies?.['@types/node'] ||
233
+ packageJson.scripts?.build?.includes('tsc') ||
234
+ packageJson.scripts?.dev?.includes('ts-node');
235
+
236
+ if (hasTypescript) {
237
+ return 'typescript';
238
+ }
239
+
240
+ // Check for React
241
+ const hasReact =
242
+ packageJson.dependencies?.react ||
243
+ packageJson.devDependencies?.react ||
244
+ packageJson.scripts?.dev?.includes('vite') ||
245
+ packageJson.scripts?.build?.includes('vite');
246
+
247
+ if (hasReact) {
248
+ return 'react';
249
+ }
250
+
251
+ // Check for Next.js
252
+ const hasNext =
253
+ packageJson.dependencies?.next ||
254
+ packageJson.devDependencies?.next ||
255
+ packageJson.scripts?.dev === 'next dev' ||
256
+ packageJson.scripts?.build === 'next build';
257
+
258
+ if (hasNext) {
259
+ return 'next.js';
260
+ }
261
+
262
+ // Default to JavaScript
263
+ return 'javascript';
264
+ }
265
+
266
+ /**
267
+ * Detect package manager from lock files
268
+ */
269
+ function detectPackageManager(cwd: string, packageJson?: any): string {
270
+ // First, check package.json for explicit packageManager field (most accurate)
271
+ if (packageJson?.packageManager) {
272
+ const packageManagerField = packageJson.packageManager;
273
+ // Extract manager name from "bun@1.3.1" format
274
+ const managerName = packageManagerField.split('@')[0];
275
+ if (['npm', 'yarn', 'pnpm', 'bun'].includes(managerName)) {
276
+ return managerName;
277
+ }
278
+ }
279
+
280
+ // Fallback: Check for lock files in order of preference
281
+ const lockFiles = [
282
+ { file: 'pnpm-lock.yaml', manager: 'pnpm' },
283
+ { file: 'yarn.lock', manager: 'yarn' },
284
+ { file: 'package-lock.json', manager: 'npm' },
285
+ { file: 'bun.lockb', manager: 'bun' },
286
+ ];
287
+
288
+ for (const { file, manager } of lockFiles) {
289
+ if (fsSync.existsSync(path.join(cwd, file))) {
290
+ return manager;
291
+ }
292
+ }
293
+
294
+ return 'npm'; // Default to npm
295
+ }
296
+
297
+ /**
298
+ * Format bytes to human-readable string
299
+ */
300
+ function formatBytes(bytes: number): string {
301
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
302
+ if (bytes === 0) {
303
+ return '0 Bytes';
304
+ }
305
+
306
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
307
+ return `${Math.round((bytes / 1024 ** i) * 100) / 100} ${sizes[i]}`;
308
+ }
309
+
310
+ /**
311
+ * Send OS-level notification
312
+ */
313
+ async function sendNotification(verbose: boolean): Promise<string> {
314
+ const title = '🔮 Sylphx Flow';
315
+ const message = 'Claude Code is ready';
316
+ const platform = os.platform();
317
+
318
+ if (verbose) {
319
+ cli.info(`Sending notification on ${platform}...`);
320
+ }
321
+
322
+ try {
323
+ switch (platform) {
324
+ case 'darwin':
325
+ await sendMacNotification(title, message);
326
+ break;
327
+ case 'linux':
328
+ await sendLinuxNotification(title, message);
329
+ break;
330
+ case 'win32':
331
+ await sendWindowsNotification(title, message);
332
+ break;
333
+ default:
334
+ throw new Error(`Unsupported platform: ${platform}`);
335
+ }
336
+
337
+ return ''; // Notifications don't output to stdout
338
+ } catch (error) {
339
+ const errorMsg = error instanceof Error ? error.message : String(error);
340
+ if (verbose) {
341
+ cli.error(`Failed to send notification: ${errorMsg}`);
342
+ }
343
+ // Don't fail the hook, just silently skip notification
344
+ return '';
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Send notification on macOS using terminal-notifier or osascript
350
+ */
351
+ async function sendMacNotification(title: string, message: string): Promise<void> {
352
+ const iconPath = '/Users/kyle/flow/assets/icons/flow-notification-icon.png';
353
+
354
+ // Try terminal-notifier if available (supports custom icons)
355
+ try {
356
+ await execAsync('which terminal-notifier');
357
+ await execAsync(
358
+ `terminal-notifier -message "${escapeForShell(message)}" -title "${escapeForShell(title)}" -appIcon "${iconPath}"`
359
+ );
360
+ } catch {
361
+ // Fallback to osascript if terminal-notifier not available
362
+ // Note: osascript doesn't support custom icons, will show Terminal app icon
363
+ const script = `display notification "${escapeForAppleScript(message)}" with title "${escapeForAppleScript(title)}"`;
364
+ await execAsync(`osascript -e '${script}'`);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Send notification on Linux using notify-send
370
+ */
371
+ async function sendLinuxNotification(title: string, message: string): Promise<void> {
372
+ // Try to use notify-send, fail silently if not available
373
+ try {
374
+ await execAsync('which notify-send');
375
+ // Use Flow-themed spiral emoji as icon for Sylphx Flow
376
+ await execAsync(
377
+ `notify-send -i "🌀" "${escapeForShell(title)}" "${escapeForShell(message)}"`
378
+ );
379
+ } catch {
380
+ // notify-send not available, skip notification silently
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Send notification on Windows using PowerShell
386
+ */
387
+ async function sendWindowsNotification(title: string, message: string): Promise<void> {
388
+ const script = `
389
+ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
390
+ [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
391
+
392
+ $template = @"
393
+ <toast>
394
+ <visual>
395
+ <binding template="ToastImageAndText02">
396
+ <image id="1" src="%SystemRoot%\\System32\\Shell32.dll,-16739" alt="info icon"/>
397
+ <text id="1">${escapeForXml(title)}</text>
398
+ <text id="2">${escapeForXml(message)}</text>
399
+ </binding>
400
+ </visual>
401
+ </toast>
402
+ "@
403
+
404
+ $xml = New-Object Windows.Data.Xml.Dom.XmlDocument
405
+ $xml.LoadXml($template)
406
+ $toast = New-Object Windows.UI.Notifications.ToastNotification $xml
407
+ [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Claude Code").Show($toast)
408
+ `;
409
+
410
+ await execAsync(`powershell -Command "${escapeForPowerShell(script)}"`);
411
+ }
412
+
413
+ /**
414
+ * Escape string for AppleScript
415
+ */
416
+ function escapeForAppleScript(str: string): string {
417
+ return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
418
+ }
419
+
420
+ /**
421
+ * Escape string for shell
422
+ */
423
+ function escapeForShell(str: string): string {
424
+ return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
425
+ }
426
+
427
+ /**
428
+ * Escape string for XML
429
+ */
430
+ function escapeForXml(str: string): string {
431
+ return str
432
+ .replace(/&/g, '&amp;')
433
+ .replace(/</g, '&lt;')
434
+ .replace(/>/g, '&gt;')
435
+ .replace(/"/g, '&quot;')
436
+ .replace(/'/g, '&apos;');
437
+ }
438
+
439
+ /**
440
+ * Escape string for PowerShell
441
+ */
442
+ function escapeForPowerShell(str: string): string {
443
+ return str.replace(/"/g, '""');
444
+ }
@@ -0,0 +1,92 @@
1
+ import boxen from 'boxen';
2
+ import chalk from 'chalk';
3
+ import gradient from 'gradient-string';
4
+ import {
5
+ selectAndValidateTarget,
6
+ previewDryRun,
7
+ installComponents,
8
+ type InitOptions,
9
+ } from './init-core.js';
10
+
11
+ /**
12
+ * Legacy init with full UI - used by setup command for backward compatibility
13
+ * The flow command uses init-core functions directly for better integration
14
+ */
15
+ export async function runInit(options: InitOptions): Promise<void> {
16
+ // Create ASCII art title
17
+ const title = `
18
+ ███████╗██╗ ██╗██╗ ██████╗ ██╗ ██╗██╗ ██╗ ███████╗██╗ ██████╗ ██╗ ██╗
19
+ ██╔════╝╚██╗ ██╔╝██║ ██╔══██╗██║ ██║╚██╗██╔╝ ██╔════╝██║ ██╔═══██╗██║ ██║
20
+ ███████╗ ╚████╔╝ ██║ ██████╔╝███████║ ╚███╔╝ █████╗ ██║ ██║ ██║██║ █╗ ██║
21
+ ╚════██║ ╚██╔╝ ██║ ██╔═══╝ ██╔══██║ ██╔██╗ ██╔══╝ ██║ ██║ ██║██║███╗██║
22
+ ███████║ ██║ ███████╗██║ ██║ ██║██╔╝ ██╗ ██║ ███████╗╚██████╔╝╚███╔███╔╝
23
+ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝
24
+ `;
25
+
26
+ console.log(gradient(['cyan', 'blue'])(title));
27
+ console.log(chalk.dim.cyan(' Project Initialization\n'));
28
+
29
+ // Select and validate target using core function
30
+ const targetId = await selectAndValidateTarget(options);
31
+
32
+ // Dry run preview
33
+ if (options.dryRun) {
34
+ console.log(
35
+ boxen(
36
+ chalk.yellow('⚠ Dry Run Mode') + chalk.dim('\nNo changes will be made to your project'),
37
+ {
38
+ padding: 1,
39
+ margin: { top: 0, bottom: 1, left: 0, right: 0 },
40
+ borderStyle: 'round',
41
+ borderColor: 'yellow',
42
+ }
43
+ )
44
+ );
45
+
46
+ await previewDryRun(targetId, options);
47
+
48
+ console.log(
49
+ '\n' +
50
+ boxen(chalk.green.bold('✓ Dry run complete'), {
51
+ padding: { top: 0, bottom: 0, left: 2, right: 2 },
52
+ margin: 0,
53
+ borderStyle: 'round',
54
+ borderColor: 'green',
55
+ }) +
56
+ '\n'
57
+ );
58
+ return;
59
+ }
60
+
61
+ console.log(chalk.cyan.bold('\n━━━ Installing Core Components ━━━\n'));
62
+
63
+ // Install components using core function
64
+ const result = await installComponents(targetId, options);
65
+
66
+ // Success summary
67
+ console.log(
68
+ '\n' +
69
+ boxen(
70
+ chalk.green.bold('✓ Setup complete!') +
71
+ '\n\n' +
72
+ chalk.dim(`Target: ${result.targetName}`) +
73
+ '\n\n' +
74
+ chalk.cyan('Ready to code with Sylphx Flow'),
75
+ {
76
+ padding: 1,
77
+ margin: 0,
78
+ borderStyle: 'round',
79
+ borderColor: 'green',
80
+ }
81
+ ) +
82
+ '\n'
83
+ );
84
+ }
85
+
86
+ /**
87
+ * LEGACY: init command has been integrated into the flow command.
88
+ * Use `flow --init-only` instead of standalone `init` command.
89
+ *
90
+ * This export is kept for backward compatibility but will be removed in future versions.
91
+ * The runInit() function is the core implementation used by flow command.
92
+ */