@sylphx/flow 3.16.0 ā 3.17.1
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 +24 -0
- package/assets/agents/builder.md +41 -2
- package/package.json +8 -7
- package/src/commands/flow/execute-v2.ts +44 -58
- package/src/commands/settings/checkbox-config.ts +18 -18
- package/src/commands/settings-command.ts +122 -144
- package/src/core/installers/mcp-installer.ts +42 -34
- package/src/core/target-manager.ts +13 -19
- package/src/core/upgrade-manager.ts +22 -19
- package/src/services/mcp-service.ts +33 -29
- package/src/services/target-installer.ts +28 -57
- package/src/utils/config/mcp-config.ts +31 -56
- package/src/utils/display/logger.ts +95 -172
- package/src/utils/prompt-helpers.ts +16 -5
- package/src/utils/prompts/index.ts +232 -0
- package/src/utils/target-selection.ts +38 -46
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @sylphx/flow
|
|
2
2
|
|
|
3
|
+
## 3.17.1 (2026-02-05)
|
|
4
|
+
|
|
5
|
+
Add Hono RPC patterns and declarative engineering principles
|
|
6
|
+
|
|
7
|
+
### š Documentation
|
|
8
|
+
|
|
9
|
+
- **builder:** prioritize declarative style in engineering principles ([f2e5bc4](https://github.com/SylphxAI/flow/commit/f2e5bc482ce554097e2bc90943c22f3d497f0925))
|
|
10
|
+
- **builder:** add Hono RPC patterns for split clients ([c848796](https://github.com/SylphxAI/flow/commit/c8487965a8341aac1f9644d5de75c1dd36b1c661))
|
|
11
|
+
|
|
12
|
+
## 3.17.0 (2026-02-05)
|
|
13
|
+
|
|
14
|
+
### ā»ļø Refactoring
|
|
15
|
+
|
|
16
|
+
- **flow:** migrate CLI prompts to Clack and logging to Pino ([08aceb7](https://github.com/SylphxAI/flow/commit/08aceb7d))
|
|
17
|
+
|
|
18
|
+
### š Bug Fixes
|
|
19
|
+
|
|
20
|
+
- **flow:** remove unsafe separator pattern from Clack prompts ([aaf17a7](https://github.com/SylphxAI/flow/commit/aaf17a7d))
|
|
21
|
+
|
|
22
|
+
### š Documentation
|
|
23
|
+
|
|
24
|
+
- **builder:** add Zod v4 to tech stack ([2b37327](https://github.com/SylphxAI/flow/commit/2b373279))
|
|
25
|
+
- **builder:** restructure tech stack - Zod as standalone category ([90875ba](https://github.com/SylphxAI/flow/commit/90875baa))
|
|
26
|
+
|
|
3
27
|
## 3.16.0 (2026-02-05)
|
|
4
28
|
|
|
5
29
|
### ⨠Features
|
package/assets/agents/builder.md
CHANGED
|
@@ -46,13 +46,15 @@ State-of-the-art industrial standard. Every time. Would you stake your reputatio
|
|
|
46
46
|
|
|
47
47
|
**Framework & Runtime:** Next.js 16+, React, Bun
|
|
48
48
|
|
|
49
|
+
**Schema & Validation:** Zod v4
|
|
50
|
+
|
|
49
51
|
**Data & API:** Hono + @hono/zod-openapi + hc (type-safe client), React Query, Drizzle ORM
|
|
50
52
|
|
|
51
53
|
**Database & Infrastructure:** Neon PostgreSQL, Upstash Workflow, Vercel, Vercel Blob, Modal (serverless long-running)
|
|
52
54
|
|
|
53
55
|
**UI & Styling:** Base UI, Tailwind CSS v4 (CSS-first), Motion v12 (animation)
|
|
54
56
|
|
|
55
|
-
**Forms:** React Hook Form +
|
|
57
|
+
**Forms:** React Hook Form + @hookform/resolvers
|
|
56
58
|
|
|
57
59
|
**Tables & Lists:** TanStack Table, TanStack Virtual
|
|
58
60
|
|
|
@@ -121,9 +123,10 @@ State-of-the-art industrial standard. Every time. Would you stake your reputatio
|
|
|
121
123
|
|
|
122
124
|
## Engineering
|
|
123
125
|
|
|
126
|
+
- **Declarative over imperative** ā describe WHAT, not HOW; prefer expressions over statements, data over control flow
|
|
127
|
+
- **Pure functions** ā no side effects, deterministic output; isolate impure code at boundaries
|
|
124
128
|
- **Single Source of Truth** ā one authoritative source for every state, behavior, and decision
|
|
125
129
|
- **Type safety** ā end-to-end across all boundaries (Hono RPC, Zod, strict TypeScript)
|
|
126
|
-
- **Pure functions** ā no side effects, deterministic output; isolate impure code at boundaries
|
|
127
130
|
- **Decoupling** ā minimize dependencies, use interfaces and dependency injection
|
|
128
131
|
- **Modularisation** ā single responsibility, clear boundaries, independent deployability
|
|
129
132
|
- **Composition over inheritance** ā build primitives that compose
|
|
@@ -183,6 +186,42 @@ drizzle-kit migrate && drizzle-kit push --dry-run
|
|
|
183
186
|
```
|
|
184
187
|
If there's any diff, migration is incomplete ā fail the build.
|
|
185
188
|
|
|
189
|
+
## Hono RPC
|
|
190
|
+
|
|
191
|
+
**Split clients by entity** ā monolithic `hc<AppType>` kills IDE performance at 100+ routes.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// ā
Split: one Hono app + one client per entity
|
|
195
|
+
const booksApp = new Hono()
|
|
196
|
+
.get('/', (c) => c.json([]))
|
|
197
|
+
.post('/', (c) => c.json({ id: 1 }))
|
|
198
|
+
.get('/:id', (c) => c.json({ id: c.req.param('id') }))
|
|
199
|
+
|
|
200
|
+
const authorsApp = new Hono()
|
|
201
|
+
.get('/', (c) => c.json([]))
|
|
202
|
+
.post('/', (c) => c.json({ id: 1 }))
|
|
203
|
+
|
|
204
|
+
// Main app ā chain with .route()
|
|
205
|
+
const app = new Hono()
|
|
206
|
+
.route('/books', booksApp)
|
|
207
|
+
.route('/authors', authorsApp)
|
|
208
|
+
|
|
209
|
+
// Clients ā split by entity, <100 routes each
|
|
210
|
+
export const booksClient = hc<typeof booksApp>('/api/books')
|
|
211
|
+
export const authorsClient = hc<typeof authorsApp>('/api/authors')
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Chain routes** ā separate `app.get()` calls break type inference:
|
|
215
|
+
```typescript
|
|
216
|
+
// ā
Chained ā types work
|
|
217
|
+
const app = new Hono().get('/', h1).post('/', h2)
|
|
218
|
+
|
|
219
|
+
// ā Separate ā types broken
|
|
220
|
+
const app = new Hono()
|
|
221
|
+
app.get('/', h1)
|
|
222
|
+
app.post('/', h2)
|
|
223
|
+
```
|
|
224
|
+
|
|
186
225
|
## Frontend
|
|
187
226
|
|
|
188
227
|
- **Semantic HTML** ā correct elements (nav, main, article, section, aside, header, footer)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/flow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.1",
|
|
4
4
|
"description": "One CLI to rule them all. Unified orchestration layer for AI coding assistants. Auto-detection, auto-installation, auto-upgrade.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,16 +25,17 @@
|
|
|
25
25
|
"prepublishOnly": "echo 'Using assets from packages/flow/assets'"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"chalk": "^5.6.2",
|
|
28
|
+
"@clack/prompts": "^0.9.0",
|
|
30
29
|
"boxen": "^8.0.1",
|
|
30
|
+
"chalk": "^5.6.2",
|
|
31
|
+
"commander": "^14.0.2",
|
|
32
|
+
"debug": "^4.4.3",
|
|
31
33
|
"gradient-string": "^3.0.0",
|
|
32
|
-
"ora": "^9.0.0",
|
|
33
|
-
"inquirer": "^12.10.0",
|
|
34
34
|
"gray-matter": "^4.0.3",
|
|
35
|
+
"pino": "^9.0.0",
|
|
36
|
+
"pino-pretty": "^11.0.0",
|
|
35
37
|
"yaml": "^2.8.1",
|
|
36
|
-
"zod": "^4.1.12"
|
|
37
|
-
"debug": "^4.4.3"
|
|
38
|
+
"zod": "^4.1.12"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/node": "^24.9.2",
|
|
@@ -7,7 +7,6 @@ import fs from 'node:fs/promises';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
|
-
import inquirer from 'inquirer';
|
|
11
10
|
import { FlowExecutor } from '../../core/flow-executor.js';
|
|
12
11
|
import { targetManager } from '../../core/target-manager.js';
|
|
13
12
|
import { AutoUpgrade } from '../../services/auto-upgrade.js';
|
|
@@ -18,6 +17,7 @@ import { extractAgentInstructions, loadAgentContent } from '../../utils/agent-en
|
|
|
18
17
|
import { showAttachSummary, showHeader } from '../../utils/display/banner.js';
|
|
19
18
|
import { CLIError } from '../../utils/error-handler.js';
|
|
20
19
|
import { UserCancelledError } from '../../utils/errors.js';
|
|
20
|
+
import { log, promptConfirm, promptSelect } from '../../utils/prompts/index.js';
|
|
21
21
|
import { ensureTargetInstalled, promptForTargetSelection } from '../../utils/target-selection.js';
|
|
22
22
|
import { resolvePrompt } from './prompt.js';
|
|
23
23
|
import type { FlowOptions } from './types.js';
|
|
@@ -62,66 +62,52 @@ function configureProviderEnv(provider: 'kimi' | 'zai', apiKey: string): void {
|
|
|
62
62
|
* Select and configure provider for Claude Code (silent unless prompting)
|
|
63
63
|
*/
|
|
64
64
|
async function selectProvider(configService: GlobalConfigService): Promise<void> {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (defaultProvider
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
configureProviderEnv(defaultProvider, provider.apiKey);
|
|
75
|
-
}
|
|
65
|
+
const providerConfig = await configService.loadProviderConfig();
|
|
66
|
+
const defaultProvider = providerConfig.claudeCode.defaultProvider;
|
|
67
|
+
|
|
68
|
+
// If not "ask-every-time", use the default provider silently
|
|
69
|
+
if (defaultProvider !== 'ask-every-time') {
|
|
70
|
+
if (defaultProvider === 'kimi' || defaultProvider === 'zai') {
|
|
71
|
+
const provider = providerConfig.claudeCode.providers[defaultProvider];
|
|
72
|
+
if (provider?.apiKey) {
|
|
73
|
+
configureProviderEnv(defaultProvider, provider.apiKey);
|
|
76
74
|
}
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Ask user which provider to use for this session
|
|
81
|
-
const { selectedProvider, rememberChoice } = await inquirer.prompt([
|
|
82
|
-
{
|
|
83
|
-
type: 'list',
|
|
84
|
-
name: 'selectedProvider',
|
|
85
|
-
message: 'Select provider:',
|
|
86
|
-
choices: [
|
|
87
|
-
{ name: 'Default (Claude Code built-in)', value: 'default' },
|
|
88
|
-
{ name: 'Kimi', value: 'kimi' },
|
|
89
|
-
{ name: 'Z.ai', value: 'zai' },
|
|
90
|
-
],
|
|
91
|
-
default: 'default',
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
type: 'confirm',
|
|
95
|
-
name: 'rememberChoice',
|
|
96
|
-
message: 'Remember this choice?',
|
|
97
|
-
default: true,
|
|
98
|
-
},
|
|
99
|
-
]);
|
|
100
|
-
|
|
101
|
-
// Save choice if user wants to remember
|
|
102
|
-
if (rememberChoice) {
|
|
103
|
-
providerConfig.claudeCode.defaultProvider = selectedProvider;
|
|
104
|
-
await configService.saveProviderConfig(providerConfig);
|
|
105
75
|
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
106
78
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
79
|
+
// Ask user which provider to use for this session
|
|
80
|
+
const selectedProvider = await promptSelect({
|
|
81
|
+
message: 'Select provider:',
|
|
82
|
+
options: [
|
|
83
|
+
{ label: 'Default (Claude Code built-in)', value: 'default' },
|
|
84
|
+
{ label: 'Kimi', value: 'kimi' },
|
|
85
|
+
{ label: 'Z.ai', value: 'zai' },
|
|
86
|
+
],
|
|
87
|
+
initialValue: 'default',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const rememberChoice = await promptConfirm({
|
|
91
|
+
message: 'Remember this choice?',
|
|
92
|
+
initialValue: true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Save choice if user wants to remember
|
|
96
|
+
if (rememberChoice) {
|
|
97
|
+
providerConfig.claudeCode.defaultProvider = selectedProvider;
|
|
98
|
+
await configService.saveProviderConfig(providerConfig);
|
|
99
|
+
}
|
|
110
100
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
101
|
+
// Configure environment variables based on selection
|
|
102
|
+
if (selectedProvider === 'kimi' || selectedProvider === 'zai') {
|
|
103
|
+
const provider = providerConfig.claudeCode.providers[selectedProvider];
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Handle user cancellation (Ctrl+C)
|
|
120
|
-
const err = error as Error & { name?: string };
|
|
121
|
-
if (err.name === 'ExitPromptError' || err.message?.includes('force closed')) {
|
|
122
|
-
throw new UserCancelledError('Provider selection cancelled');
|
|
105
|
+
if (!provider?.apiKey) {
|
|
106
|
+
log.warn('API key not configured. Use: sylphx-flow settings');
|
|
107
|
+
return;
|
|
123
108
|
}
|
|
124
|
-
|
|
109
|
+
|
|
110
|
+
configureProviderEnv(selectedProvider, provider.apiKey);
|
|
125
111
|
}
|
|
126
112
|
}
|
|
127
113
|
|
|
@@ -221,11 +207,11 @@ export async function executeFlowV2(
|
|
|
221
207
|
|
|
222
208
|
if (!installedTargets.includes(selectedTargetId)) {
|
|
223
209
|
const installation = targetInstaller.getInstallationInfo(selectedTargetId);
|
|
224
|
-
|
|
210
|
+
log.warn(`${installation?.name} not installed`);
|
|
225
211
|
const installed = await targetInstaller.install(selectedTargetId, true);
|
|
226
212
|
|
|
227
213
|
if (!installed) {
|
|
228
|
-
|
|
214
|
+
log.error('Cannot proceed: installation failed');
|
|
229
215
|
process.exit(1);
|
|
230
216
|
}
|
|
231
217
|
}
|
|
@@ -313,7 +299,7 @@ export async function executeFlowV2(
|
|
|
313
299
|
} catch (error) {
|
|
314
300
|
// Handle user cancellation gracefully
|
|
315
301
|
if (error instanceof UserCancelledError) {
|
|
316
|
-
|
|
302
|
+
log.warn('Cancelled');
|
|
317
303
|
try {
|
|
318
304
|
await executor.cleanup(projectPath);
|
|
319
305
|
} catch {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import
|
|
7
|
+
import { log, type MultiselectOption, promptMultiselect } from '../../utils/prompts/index.js';
|
|
8
8
|
|
|
9
9
|
// ============================================================================
|
|
10
10
|
// Types
|
|
@@ -47,16 +47,16 @@ export const getEnabledKeys = (config: ConfigMap): string[] =>
|
|
|
47
47
|
Object.keys(config).filter((key) => config[key]?.enabled);
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Build
|
|
50
|
+
* Build multiselect options from available items
|
|
51
51
|
*/
|
|
52
|
-
export const
|
|
52
|
+
export const buildOptions = <T extends string>(
|
|
53
53
|
available: Record<T, string>,
|
|
54
54
|
enabledKeys: string[]
|
|
55
|
-
):
|
|
55
|
+
): MultiselectOption<T>[] =>
|
|
56
56
|
Object.entries(available).map(([key, name]) => ({
|
|
57
|
-
|
|
57
|
+
label: name as string,
|
|
58
58
|
value: key as T,
|
|
59
|
-
|
|
59
|
+
hint: enabledKeys.includes(key) ? 'enabled' : undefined,
|
|
60
60
|
}));
|
|
61
61
|
|
|
62
62
|
/**
|
|
@@ -82,11 +82,11 @@ export const printHeader = (icon: string, title: string): void => {
|
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Print confirmation message
|
|
85
|
+
* Print confirmation message using Clack log
|
|
86
86
|
*/
|
|
87
87
|
export const printConfirmation = (itemType: string, count: number): void => {
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
log.success(`${itemType} configuration saved`);
|
|
89
|
+
log.info(`Enabled ${itemType.toLowerCase()}: ${count}`);
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
// ============================================================================
|
|
@@ -108,15 +108,15 @@ export const handleCheckboxConfig = async <T extends string>(
|
|
|
108
108
|
// Get current enabled items
|
|
109
109
|
const enabledKeys = getEnabledKeys(current);
|
|
110
110
|
|
|
111
|
-
//
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
// Build options for multiselect
|
|
112
|
+
const multiselectOptions = buildOptions(available, enabledKeys);
|
|
113
|
+
|
|
114
|
+
// Show multiselect prompt
|
|
115
|
+
const selected = await promptMultiselect<T>({
|
|
116
|
+
message,
|
|
117
|
+
options: multiselectOptions,
|
|
118
|
+
initialValues: enabledKeys as T[],
|
|
119
|
+
});
|
|
120
120
|
|
|
121
121
|
// Update config
|
|
122
122
|
const updated = updateConfig(available, selected);
|