@sylphx/flow 2.1.3 → 2.1.5
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 +28 -0
- package/README.md +44 -0
- package/package.json +79 -73
- package/src/commands/flow/execute-v2.ts +37 -29
- package/src/commands/flow/prompt.ts +5 -3
- package/src/commands/flow/types.ts +0 -2
- package/src/commands/flow-command.ts +20 -13
- package/src/commands/hook-command.ts +1 -3
- package/src/commands/settings/checkbox-config.ts +128 -0
- package/src/commands/settings/index.ts +6 -0
- package/src/commands/settings-command.ts +84 -156
- package/src/config/ai-config.ts +60 -41
- package/src/core/agent-loader.ts +11 -6
- package/src/core/attach/file-attacher.ts +172 -0
- package/src/core/attach/index.ts +5 -0
- package/src/core/attach-manager.ts +117 -171
- package/src/core/backup-manager.ts +35 -29
- package/src/core/cleanup-handler.ts +11 -8
- package/src/core/error-handling.ts +23 -30
- package/src/core/flow-executor.ts +58 -76
- package/src/core/formatting/bytes.ts +2 -4
- package/src/core/functional/async.ts +5 -4
- package/src/core/functional/error-handler.ts +2 -2
- package/src/core/git-stash-manager.ts +21 -10
- package/src/core/installers/file-installer.ts +0 -1
- package/src/core/installers/mcp-installer.ts +0 -1
- package/src/core/project-manager.ts +24 -18
- package/src/core/secrets-manager.ts +54 -73
- package/src/core/session-manager.ts +20 -22
- package/src/core/state-detector.ts +139 -80
- package/src/core/template-loader.ts +13 -31
- package/src/core/upgrade-manager.ts +122 -69
- package/src/index.ts +8 -5
- package/src/services/auto-upgrade.ts +1 -1
- package/src/services/config-service.ts +41 -29
- package/src/services/global-config.ts +3 -3
- package/src/services/target-installer.ts +11 -26
- package/src/targets/claude-code.ts +35 -81
- package/src/targets/opencode.ts +28 -68
- package/src/targets/shared/index.ts +7 -0
- package/src/targets/shared/mcp-transforms.ts +132 -0
- package/src/targets/shared/target-operations.ts +135 -0
- package/src/types/cli.types.ts +2 -2
- package/src/types/provider.types.ts +1 -7
- package/src/types/session.types.ts +11 -11
- package/src/types/target.types.ts +3 -1
- package/src/types/todo.types.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
- package/src/utils/agent-enhancer.ts +4 -4
- package/src/utils/config/paths.ts +3 -1
- package/src/utils/config/target-utils.ts +2 -2
- package/src/utils/display/banner.ts +2 -2
- package/src/utils/display/notifications.ts +58 -45
- package/src/utils/display/status.ts +29 -12
- package/src/utils/files/file-operations.ts +1 -1
- package/src/utils/files/sync-utils.ts +38 -41
- package/src/utils/index.ts +19 -27
- package/src/utils/package-manager-detector.ts +15 -5
- package/src/utils/security/security.ts +8 -4
- package/src/utils/target-selection.ts +6 -8
- package/src/utils/version.ts +4 -2
- package/src/commands/flow-orchestrator.ts +0 -328
- package/src/commands/init-command.ts +0 -92
- package/src/commands/init-core.ts +0 -331
- package/src/core/agent-manager.ts +0 -174
- package/src/core/loop-controller.ts +0 -200
- package/src/core/rule-loader.ts +0 -147
- package/src/core/rule-manager.ts +0 -240
- package/src/services/claude-config-service.ts +0 -252
- package/src/services/first-run-setup.ts +0 -220
- package/src/services/smart-config-service.ts +0 -269
- package/src/types/api.types.ts +0 -9
package/src/core/rule-loader.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rule Loader
|
|
3
|
-
* Loads rule definitions from markdown files with front matter
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFile, readdir, access } from 'node:fs/promises';
|
|
7
|
-
import { join, parse, relative, dirname } from 'node:path';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import matter from 'gray-matter';
|
|
11
|
-
import type { Rule, RuleMetadata } from '../types/rule.types.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Load a single rule from a markdown file
|
|
15
|
-
*/
|
|
16
|
-
export async function loadRuleFromFile(
|
|
17
|
-
filePath: string,
|
|
18
|
-
isBuiltin: boolean = false,
|
|
19
|
-
ruleId?: string
|
|
20
|
-
): Promise<Rule | null> {
|
|
21
|
-
try {
|
|
22
|
-
const fileContent = await readFile(filePath, 'utf-8');
|
|
23
|
-
const { data, content } = matter(fileContent);
|
|
24
|
-
|
|
25
|
-
// Validate front matter
|
|
26
|
-
if (!data.name || typeof data.name !== 'string') {
|
|
27
|
-
console.error(`Rule file ${filePath} missing required 'name' field`);
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const metadata: RuleMetadata = {
|
|
32
|
-
name: data.name,
|
|
33
|
-
description: data.description || '',
|
|
34
|
-
enabled: data.enabled !== undefined ? Boolean(data.enabled) : true,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Get rule ID from parameter or filename
|
|
38
|
-
const id = ruleId || parse(filePath).name;
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
id,
|
|
42
|
-
metadata,
|
|
43
|
-
content: content.trim(),
|
|
44
|
-
isBuiltin,
|
|
45
|
-
filePath,
|
|
46
|
-
};
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(`Failed to load rule from ${filePath}:`, error);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Load all rules from a directory (recursively)
|
|
55
|
-
*/
|
|
56
|
-
export async function loadRulesFromDirectory(dirPath: string, isBuiltin: boolean = false): Promise<Rule[]> {
|
|
57
|
-
try {
|
|
58
|
-
// Read directory recursively to support subdirectories
|
|
59
|
-
const files = await readdir(dirPath, { recursive: true, withFileTypes: true });
|
|
60
|
-
|
|
61
|
-
// Filter for .md files and calculate rule IDs from relative paths
|
|
62
|
-
const ruleFiles = files
|
|
63
|
-
.filter((f) => f.isFile() && f.name.endsWith('.md'))
|
|
64
|
-
.map((f) => {
|
|
65
|
-
const fullPath = join(f.parentPath || f.path, f.name);
|
|
66
|
-
// Calculate relative path from dirPath and remove .md extension
|
|
67
|
-
const relativePath = relative(dirPath, fullPath).replace(/\.md$/, '');
|
|
68
|
-
return { fullPath, ruleId: relativePath };
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const rules = await Promise.all(
|
|
72
|
-
ruleFiles.map(({ fullPath, ruleId }) => loadRuleFromFile(fullPath, isBuiltin, ruleId))
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
return rules.filter((rule): rule is Rule => rule !== null);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
// Directory doesn't exist or can't be read
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Get system rules path (bundled with the app)
|
|
84
|
-
*/
|
|
85
|
-
export async function getSystemRulesPath(): Promise<string> {
|
|
86
|
-
// Get the directory of the current module (cross-platform)
|
|
87
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
88
|
-
const currentDir = dirname(currentFile);
|
|
89
|
-
|
|
90
|
-
// In production (dist), assets are at dist/assets/rules
|
|
91
|
-
// In development (src), go up to project root: src/core -> project root
|
|
92
|
-
const distPath = join(currentDir, '..', 'assets', 'rules');
|
|
93
|
-
const devPath = join(currentDir, '..', '..', 'assets', 'rules');
|
|
94
|
-
|
|
95
|
-
// Check which one exists (try dist first, then dev)
|
|
96
|
-
try {
|
|
97
|
-
await access(distPath);
|
|
98
|
-
return distPath;
|
|
99
|
-
} catch {
|
|
100
|
-
return devPath;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get all rule search paths
|
|
106
|
-
*/
|
|
107
|
-
export function getRuleSearchPaths(cwd: string): string[] {
|
|
108
|
-
const globalPath = join(homedir(), '.sylphx-flow', 'rules');
|
|
109
|
-
const projectPath = join(cwd, '.sylphx-flow', 'rules');
|
|
110
|
-
|
|
111
|
-
return [globalPath, projectPath];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Load all available rules from all sources
|
|
116
|
-
*/
|
|
117
|
-
export async function loadAllRules(cwd: string): Promise<Rule[]> {
|
|
118
|
-
const systemPath = await getSystemRulesPath();
|
|
119
|
-
const [globalPath, projectPath] = getRuleSearchPaths(cwd);
|
|
120
|
-
|
|
121
|
-
const [systemRules, globalRules, projectRules] = await Promise.all([
|
|
122
|
-
loadRulesFromDirectory(systemPath, true), // System rules are marked as builtin
|
|
123
|
-
loadRulesFromDirectory(globalPath, false),
|
|
124
|
-
loadRulesFromDirectory(projectPath, false),
|
|
125
|
-
]);
|
|
126
|
-
|
|
127
|
-
// Priority: system < global < project
|
|
128
|
-
// Use Map to deduplicate by ID (later entries override earlier ones)
|
|
129
|
-
const ruleMap = new Map<string, Rule>();
|
|
130
|
-
|
|
131
|
-
// Add system rules first (lowest priority)
|
|
132
|
-
for (const rule of systemRules) {
|
|
133
|
-
ruleMap.set(rule.id, rule);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Add global rules (override system)
|
|
137
|
-
for (const rule of globalRules) {
|
|
138
|
-
ruleMap.set(rule.id, rule);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Add project rules (override globals and system)
|
|
142
|
-
for (const rule of projectRules) {
|
|
143
|
-
ruleMap.set(rule.id, rule);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return Array.from(ruleMap.values());
|
|
147
|
-
}
|
package/src/core/rule-manager.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rule Manager
|
|
3
|
-
* Manages rule state and operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Rule } from '../types/rule.types.js';
|
|
7
|
-
import { loadAllRules } from './rule-loader.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Rule manager state
|
|
11
|
-
*/
|
|
12
|
-
interface RuleManagerState {
|
|
13
|
-
rules: Map<string, Rule>;
|
|
14
|
-
cwd: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let state: RuleManagerState | null = null;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get the app store (lazy import to avoid circular dependencies)
|
|
21
|
-
*/
|
|
22
|
-
let getAppStore: (() => any) | null = null;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Set the app store getter (called during initialization)
|
|
26
|
-
*/
|
|
27
|
-
export function setRuleAppStoreGetter(getter: () => any): void {
|
|
28
|
-
getAppStore = getter;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Initialize rule manager
|
|
33
|
-
*/
|
|
34
|
-
export async function initializeRuleManager(cwd: string): Promise<void> {
|
|
35
|
-
const allRules = await loadAllRules(cwd);
|
|
36
|
-
|
|
37
|
-
const ruleMap = new Map<string, Rule>();
|
|
38
|
-
for (const rule of allRules) {
|
|
39
|
-
ruleMap.set(rule.id, rule);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
state = {
|
|
43
|
-
rules: ruleMap,
|
|
44
|
-
cwd,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Initialize store with default enabled rules
|
|
48
|
-
if (getAppStore) {
|
|
49
|
-
const store = getAppStore();
|
|
50
|
-
if (store.getState) {
|
|
51
|
-
const currentEnabledRules = store.getState().enabledRuleIds || [];
|
|
52
|
-
|
|
53
|
-
// If no rules are enabled yet, enable all rules that have enabled: true in metadata
|
|
54
|
-
if (currentEnabledRules.length === 0) {
|
|
55
|
-
const defaultEnabledRules = allRules
|
|
56
|
-
.filter((rule) => rule.metadata.enabled !== false)
|
|
57
|
-
.map((rule) => rule.id);
|
|
58
|
-
|
|
59
|
-
if (defaultEnabledRules.length > 0) {
|
|
60
|
-
store.getState().setEnabledRuleIds(defaultEnabledRules);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get all available rules
|
|
69
|
-
*/
|
|
70
|
-
export function getAllRules(): Rule[] {
|
|
71
|
-
if (!state) {
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
return Array.from(state.rules.values());
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get rule by ID
|
|
79
|
-
*/
|
|
80
|
-
export function getRuleById(id: string): Rule | null {
|
|
81
|
-
if (!state) {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
return state.rules.get(id) || null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Get enabled rule IDs from store
|
|
89
|
-
*/
|
|
90
|
-
export function getEnabledRuleIds(): string[] {
|
|
91
|
-
if (getAppStore) {
|
|
92
|
-
const store = getAppStore();
|
|
93
|
-
if (store.getState) {
|
|
94
|
-
return store.getState().enabledRuleIds || [];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return [];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Get enabled rules
|
|
102
|
-
*/
|
|
103
|
-
export function getEnabledRules(): Rule[] {
|
|
104
|
-
if (!state) {
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const enabledIds = getEnabledRuleIds();
|
|
109
|
-
return enabledIds
|
|
110
|
-
.map((id) => state!.rules.get(id))
|
|
111
|
-
.filter((rule): rule is Rule => rule !== null);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Toggle a rule on/off
|
|
116
|
-
*/
|
|
117
|
-
export function toggleRule(ruleId: string): boolean {
|
|
118
|
-
if (!state || !state.rules.has(ruleId)) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (getAppStore) {
|
|
123
|
-
const store = getAppStore();
|
|
124
|
-
if (store.getState) {
|
|
125
|
-
const currentEnabled = store.getState().enabledRuleIds || [];
|
|
126
|
-
|
|
127
|
-
if (currentEnabled.includes(ruleId)) {
|
|
128
|
-
// Disable: remove from list
|
|
129
|
-
store.getState().setEnabledRuleIds(currentEnabled.filter((id) => id !== ruleId));
|
|
130
|
-
} else {
|
|
131
|
-
// Enable: add to list
|
|
132
|
-
store.getState().setEnabledRuleIds([...currentEnabled, ruleId]);
|
|
133
|
-
}
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Enable a rule
|
|
143
|
-
*/
|
|
144
|
-
export function enableRule(ruleId: string): boolean {
|
|
145
|
-
if (!state || !state.rules.has(ruleId)) {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (getAppStore) {
|
|
150
|
-
const store = getAppStore();
|
|
151
|
-
if (store.getState) {
|
|
152
|
-
const currentEnabled = store.getState().enabledRuleIds || [];
|
|
153
|
-
|
|
154
|
-
if (!currentEnabled.includes(ruleId)) {
|
|
155
|
-
store.getState().setEnabledRuleIds([...currentEnabled, ruleId]);
|
|
156
|
-
}
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Disable a rule
|
|
166
|
-
*/
|
|
167
|
-
export function disableRule(ruleId: string): boolean {
|
|
168
|
-
if (!state || !state.rules.has(ruleId)) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (getAppStore) {
|
|
173
|
-
const store = getAppStore();
|
|
174
|
-
if (store.getState) {
|
|
175
|
-
const currentEnabled = store.getState().enabledRuleIds || [];
|
|
176
|
-
store.getState().setEnabledRuleIds(currentEnabled.filter((id) => id !== ruleId));
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Reload rules from disk
|
|
186
|
-
*/
|
|
187
|
-
export async function reloadRules(): Promise<void> {
|
|
188
|
-
if (!state) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const cwd = state.cwd;
|
|
193
|
-
const currentEnabled = getEnabledRuleIds();
|
|
194
|
-
|
|
195
|
-
await initializeRuleManager(cwd);
|
|
196
|
-
|
|
197
|
-
// Keep only enabled rules that still exist
|
|
198
|
-
if (state && getAppStore) {
|
|
199
|
-
const store = getAppStore();
|
|
200
|
-
if (store.getState) {
|
|
201
|
-
const validEnabled = currentEnabled.filter((id) => state!.rules.has(id));
|
|
202
|
-
store.getState().setEnabledRuleIds(validEnabled);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Set enabled rules (replaces current enabled rules)
|
|
209
|
-
*/
|
|
210
|
-
export function setEnabledRules(ruleIds: string[]): boolean {
|
|
211
|
-
if (!state) {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Validate all rule IDs exist
|
|
216
|
-
const validRuleIds = ruleIds.filter((id) => state!.rules.has(id));
|
|
217
|
-
|
|
218
|
-
if (getAppStore) {
|
|
219
|
-
const store = getAppStore();
|
|
220
|
-
if (store.getState) {
|
|
221
|
-
store.getState().setEnabledRuleIds(validRuleIds);
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Get combined content of all enabled rules
|
|
231
|
-
*/
|
|
232
|
-
export function getEnabledRulesContent(): string {
|
|
233
|
-
const enabledRules = getEnabledRules();
|
|
234
|
-
|
|
235
|
-
if (enabledRules.length === 0) {
|
|
236
|
-
return '';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return enabledRules.map((rule) => rule.content).join('\n\n');
|
|
240
|
-
}
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Configuration Service
|
|
3
|
-
* Handles Claude Code provider configuration with layered settings
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { ConfigService } from './config-service.js';
|
|
9
|
-
import { loadAllAgents } from '../core/agent-loader.js';
|
|
10
|
-
|
|
11
|
-
export interface ClaudeConfig {
|
|
12
|
-
claudeProvider?: string;
|
|
13
|
-
claudeProviderConfig?: {
|
|
14
|
-
ANTHROPIC_BASE_URL: string;
|
|
15
|
-
description: string;
|
|
16
|
-
};
|
|
17
|
-
claudeApiKey?: string;
|
|
18
|
-
defaultAgent?: string;
|
|
19
|
-
target?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class ClaudeConfigService {
|
|
23
|
-
/**
|
|
24
|
-
* Load layered Claude configuration from all sources
|
|
25
|
-
*/
|
|
26
|
-
static async loadConfig(): Promise<ClaudeConfig> {
|
|
27
|
-
// API keys are in home directory
|
|
28
|
-
const userSettings = await ConfigService.loadHomeSettings();
|
|
29
|
-
|
|
30
|
-
// Other settings are in project directory
|
|
31
|
-
const projectSettings = await ConfigService.loadProjectSettings();
|
|
32
|
-
|
|
33
|
-
// Merge with project settings taking precedence over home (except API key)
|
|
34
|
-
const merged = {
|
|
35
|
-
claudeProvider: userSettings.claudeProvider || projectSettings.claudeProvider,
|
|
36
|
-
claudeProviderConfig: userSettings.claudeProviderConfig || projectSettings.claudeProviderConfig,
|
|
37
|
-
claudeApiKey: userSettings.claudeApiKey,
|
|
38
|
-
defaultAgent: projectSettings.defaultAgent,
|
|
39
|
-
target: projectSettings.target,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Local settings have highest priority for everything except API key
|
|
43
|
-
const localSettings = await ConfigService.loadLocalSettings();
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
...merged,
|
|
47
|
-
...localSettings,
|
|
48
|
-
// Keep API key from user settings
|
|
49
|
-
claudeApiKey: merged.claudeApiKey,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Save user-specific config (API keys to home, project settings to project)
|
|
55
|
-
*/
|
|
56
|
-
static async saveConfig(config: ClaudeConfig): Promise<void> {
|
|
57
|
-
// Separate user-specific settings (API keys)
|
|
58
|
-
const userSettings = {
|
|
59
|
-
claudeApiKey: config.claudeApiKey,
|
|
60
|
-
claudeProvider: config.claudeProvider,
|
|
61
|
-
claudeProviderConfig: config.claudeProviderConfig,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Other settings go to project (shareable)
|
|
65
|
-
const projectSettings = {
|
|
66
|
-
claudeProvider: config.claudeProvider,
|
|
67
|
-
defaultAgent: config.defaultAgent,
|
|
68
|
-
target: config.target,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Save API keys to home directory (never commit)
|
|
72
|
-
if (userSettings.claudeApiKey) {
|
|
73
|
-
await ConfigService.saveHomeSettings(userSettings);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Save project settings
|
|
77
|
-
await ConfigService.saveProjectSettings(projectSettings);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Configure Claude provider interactively - saves API keys to home dir
|
|
82
|
-
*/
|
|
83
|
-
static async configureProvider(verbose: boolean = false): Promise<ClaudeConfig> {
|
|
84
|
-
const config = await this.loadConfig();
|
|
85
|
-
|
|
86
|
-
// Check if we already have API key configured
|
|
87
|
-
if (!config.claudeApiKey || !config.claudeProvider || verbose) {
|
|
88
|
-
console.log(chalk.cyan('📋 Claude Code Configuration\n'));
|
|
89
|
-
|
|
90
|
-
const providerAnswer = await inquirer.prompt([
|
|
91
|
-
{
|
|
92
|
-
type: 'list',
|
|
93
|
-
name: 'provider',
|
|
94
|
-
message: 'Select Claude API Provider:',
|
|
95
|
-
choices: [
|
|
96
|
-
{ name: 'Anthropic (Official)', value: 'anthropic' },
|
|
97
|
-
{ name: 'Z.ai (Recommended)', value: 'z.ai' },
|
|
98
|
-
{ name: 'Kimi', value: 'kimi' },
|
|
99
|
-
],
|
|
100
|
-
default: config.claudeProvider || 'z.ai',
|
|
101
|
-
},
|
|
102
|
-
]);
|
|
103
|
-
|
|
104
|
-
// Ask for API Key
|
|
105
|
-
const keyAnswer = await inquirer.prompt([
|
|
106
|
-
{
|
|
107
|
-
type: 'password',
|
|
108
|
-
name: 'apiKey',
|
|
109
|
-
message: `Enter API Key for ${providerAnswer.provider}:`,
|
|
110
|
-
mask: '*',
|
|
111
|
-
validate: (input) => input.length > 10 || 'API Key appears too short',
|
|
112
|
-
},
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
// Provider configurations
|
|
116
|
-
const providerEnvs = {
|
|
117
|
-
'anthropic': {
|
|
118
|
-
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
|
|
119
|
-
description: 'Anthropic Official API',
|
|
120
|
-
},
|
|
121
|
-
'z.ai': {
|
|
122
|
-
ANTHROPIC_BASE_URL: 'https://api.z.ai/api/anthropic',
|
|
123
|
-
description: 'Z.ai Proxy',
|
|
124
|
-
},
|
|
125
|
-
'kimi': {
|
|
126
|
-
ANTHROPIC_BASE_URL: 'https://api.kimi.com/coding/',
|
|
127
|
-
description: 'Kimi Proxy',
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const providerConfig = providerEnvs[providerAnswer.provider as keyof typeof providerEnvs];
|
|
132
|
-
|
|
133
|
-
// Save API keys to home directory (never commit)
|
|
134
|
-
await ConfigService.saveHomeSettings({
|
|
135
|
-
claudeProvider: providerAnswer.provider,
|
|
136
|
-
claudeProviderConfig: providerConfig,
|
|
137
|
-
claudeApiKey: keyAnswer.apiKey,
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
console.log(chalk.green(`✓ API Key saved to ~/.sylphx-flow/settings.json (secure)\n`));
|
|
141
|
-
console.log(chalk.dim(` Provider: ${providerConfig.description}`));
|
|
142
|
-
console.log(chalk.dim(` API Key: ${keyAnswer.apiKey.slice(0, 5)}...${keyAnswer.apiKey.slice(-4)}\n`));
|
|
143
|
-
|
|
144
|
-
// Update config for return
|
|
145
|
-
config.claudeProvider = providerAnswer.provider;
|
|
146
|
-
config.claudeProviderConfig = providerConfig;
|
|
147
|
-
config.claudeApiKey = keyAnswer.apiKey;
|
|
148
|
-
|
|
149
|
-
return config;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (verbose) {
|
|
153
|
-
console.log(chalk.green('✓ Claude provider already configured\n'));
|
|
154
|
-
console.log(chalk.dim(` Provider: ${config.claudeProviderConfig?.description}\n`));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return config;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Configure agent interactively - saves to project config (shareable)
|
|
162
|
-
* Dynamically loads available agents instead of hardcoded list
|
|
163
|
-
*/
|
|
164
|
-
static async configureAgent(verbose: boolean = false): Promise<string> {
|
|
165
|
-
const config = await this.loadConfig();
|
|
166
|
-
|
|
167
|
-
if (!config.defaultAgent || verbose) {
|
|
168
|
-
try {
|
|
169
|
-
// Dynamically load all available agents
|
|
170
|
-
const agents = await loadAllAgents(process.cwd());
|
|
171
|
-
|
|
172
|
-
if (agents.length === 0) {
|
|
173
|
-
console.log(chalk.yellow('⚠ No agents found. Defaulting to "coder".\n'));
|
|
174
|
-
const defaultAgent = 'coder';
|
|
175
|
-
await ConfigService.saveProjectSettings({
|
|
176
|
-
defaultAgent,
|
|
177
|
-
});
|
|
178
|
-
return defaultAgent;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Create choices from dynamically loaded agents
|
|
182
|
-
const choices = agents.map(agent => ({
|
|
183
|
-
name: agent.metadata.name || agent.id,
|
|
184
|
-
value: agent.id,
|
|
185
|
-
}));
|
|
186
|
-
|
|
187
|
-
const agentAnswer = await inquirer.prompt([
|
|
188
|
-
{
|
|
189
|
-
type: 'list',
|
|
190
|
-
name: 'agent',
|
|
191
|
-
message: 'Select default Agent:',
|
|
192
|
-
choices,
|
|
193
|
-
default: config.defaultAgent || (agents.find(a => a.id === 'coder')?.id || agents[0].id),
|
|
194
|
-
},
|
|
195
|
-
]);
|
|
196
|
-
|
|
197
|
-
// Save to project-level config (shareable)
|
|
198
|
-
await ConfigService.saveProjectSettings({
|
|
199
|
-
defaultAgent: agentAnswer.agent,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const selectedAgent = agents.find(a => a.id === agentAnswer.agent);
|
|
203
|
-
const displayName = selectedAgent?.metadata.name || agentAnswer.agent;
|
|
204
|
-
console.log(chalk.green(`✓ Agent set to: ${displayName}\n`));
|
|
205
|
-
|
|
206
|
-
return agentAnswer.agent;
|
|
207
|
-
} catch (error) {
|
|
208
|
-
console.log(chalk.yellow('⚠ Failed to load agents. Defaulting to "coder".\n'));
|
|
209
|
-
console.error(error);
|
|
210
|
-
|
|
211
|
-
const defaultAgent = 'coder';
|
|
212
|
-
await ConfigService.saveProjectSettings({
|
|
213
|
-
defaultAgent,
|
|
214
|
-
});
|
|
215
|
-
return defaultAgent;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return config.defaultAgent;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Setup environment variables for Claude Code
|
|
224
|
-
*/
|
|
225
|
-
static async setupEnvironment(verbose: boolean = false): Promise<void> {
|
|
226
|
-
const config = await this.loadConfig();
|
|
227
|
-
|
|
228
|
-
if (!config.claudeProviderConfig) {
|
|
229
|
-
throw new Error('Provider not configured. Run configureProvider() first.');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Set environment variables
|
|
233
|
-
process.env.ANTHROPIC_BASE_URL = config.claudeProviderConfig.ANTHROPIC_BASE_URL;
|
|
234
|
-
|
|
235
|
-
if (config.claudeApiKey) {
|
|
236
|
-
process.env.ANTHROPIC_API_KEY = config.claudeApiKey;
|
|
237
|
-
|
|
238
|
-
if (verbose) {
|
|
239
|
-
console.log(chalk.dim(` Provider: ${config.claudeProviderConfig.description}`));
|
|
240
|
-
console.log(chalk.dim(` API Key: ${config.claudeApiKey.slice(0, 5)}...${config.claudeApiKey.slice(-4)}\n`));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Get the default agent
|
|
247
|
-
*/
|
|
248
|
-
static async getDefaultAgent(): Promise<string> {
|
|
249
|
-
const config = await this.loadConfig();
|
|
250
|
-
return config.defaultAgent || 'coder';
|
|
251
|
-
}
|
|
252
|
-
}
|