@sylphx/flow 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- package/dist/shared/chunk-x66eh37x.js +0 -137
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import type { MCPServerID } from '../config/servers.js';
|
|
5
|
+
import { getAllServerIDs, MCP_SERVER_REGISTRY } from '../config/servers.js';
|
|
6
|
+
import { targetManager } from '../core/target-manager.js';
|
|
7
|
+
import { getNestedProperty, setNestedProperty } from '../utils/target-config.js';
|
|
8
|
+
|
|
9
|
+
interface MCPConfigOptions {
|
|
10
|
+
serverId?: MCPServerID;
|
|
11
|
+
existingValues: Record<string, string>;
|
|
12
|
+
targetId: string;
|
|
13
|
+
cwd: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ServerConfiguration {
|
|
17
|
+
id: MCPServerID;
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
fields: ConfigField[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ConfigField {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
required: boolean;
|
|
27
|
+
secret: boolean;
|
|
28
|
+
defaultValue?: string;
|
|
29
|
+
currentValue?: string;
|
|
30
|
+
options?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class MCPConfigurator {
|
|
34
|
+
private targetId: string;
|
|
35
|
+
private cwd: string;
|
|
36
|
+
private serverId?: MCPServerID;
|
|
37
|
+
private existingValues: Record<string, string>;
|
|
38
|
+
|
|
39
|
+
constructor(options: MCPConfigOptions) {
|
|
40
|
+
this.targetId = options.targetId;
|
|
41
|
+
this.cwd = options.cwd;
|
|
42
|
+
this.serverId = options.serverId;
|
|
43
|
+
this.existingValues = options.existingValues;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async configure(): Promise<{ values: Record<string, string>; serverId?: MCPServerID }> {
|
|
47
|
+
console.clear();
|
|
48
|
+
console.log(chalk.cyan.bold('⚙️ MCP Configuration'));
|
|
49
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
50
|
+
|
|
51
|
+
// Step 1: Select server if not provided
|
|
52
|
+
if (!this.serverId) {
|
|
53
|
+
this.serverId = await this.selectServer();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const server = MCP_SERVER_REGISTRY[this.serverId];
|
|
57
|
+
if (!server) {
|
|
58
|
+
throw new Error(`Server not found: ${this.serverId}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(chalk.blue(`\n▸ ${server.name}`));
|
|
62
|
+
console.log(chalk.gray(` ${server.description}`));
|
|
63
|
+
|
|
64
|
+
// Step 2: Configure server if it has environment variables
|
|
65
|
+
if (server.envVars && Object.keys(server.envVars).length > 0) {
|
|
66
|
+
const values = await this.configureServer(server);
|
|
67
|
+
return { values, serverId: this.serverId };
|
|
68
|
+
}
|
|
69
|
+
console.log(chalk.gray('\n✓ No configuration required for this server'));
|
|
70
|
+
return { values: {}, serverId: this.serverId };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async selectServer(): Promise<MCPServerID> {
|
|
74
|
+
const availableServers = getAllServerIDs().map((id) => {
|
|
75
|
+
const server = MCP_SERVER_REGISTRY[id];
|
|
76
|
+
return {
|
|
77
|
+
name: `${server?.name || id} - ${server?.description || 'Unknown server'}`,
|
|
78
|
+
value: id,
|
|
79
|
+
short: server?.name || id,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const { serverId } = await inquirer.prompt([
|
|
84
|
+
{
|
|
85
|
+
type: 'list',
|
|
86
|
+
name: 'serverId',
|
|
87
|
+
message: 'Select MCP server to configure:',
|
|
88
|
+
choices: availableServers,
|
|
89
|
+
pageSize: 15,
|
|
90
|
+
},
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
return serverId as MCPServerID;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async configureServer(server: any): Promise<Record<string, string>> {
|
|
97
|
+
const fields = this.buildConfigFields(server);
|
|
98
|
+
const values: Record<string, string> = {};
|
|
99
|
+
|
|
100
|
+
console.log(chalk.cyan('\n▸ Configuration'));
|
|
101
|
+
console.log(chalk.gray('─'.repeat(30)));
|
|
102
|
+
|
|
103
|
+
for (const field of fields) {
|
|
104
|
+
const value = await this.configureField(field);
|
|
105
|
+
values[field.name] = value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return values;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private buildConfigFields(server: any): ConfigField[] {
|
|
112
|
+
const fields: ConfigField[] = [];
|
|
113
|
+
|
|
114
|
+
if (server.envVars) {
|
|
115
|
+
Object.entries(server.envVars).forEach(([key, config]: [string, any]) => {
|
|
116
|
+
let options: string[] | undefined;
|
|
117
|
+
|
|
118
|
+
if (key === 'EMBEDDING_MODEL') {
|
|
119
|
+
options = ['text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002'];
|
|
120
|
+
} else if (key === 'GEMINI_MODEL') {
|
|
121
|
+
options = ['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-1.5-flash', 'gemini-1.5-pro'];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fields.push({
|
|
125
|
+
name: key,
|
|
126
|
+
description: config.description,
|
|
127
|
+
required: config.required,
|
|
128
|
+
secret: config.secret || false,
|
|
129
|
+
defaultValue: config.default,
|
|
130
|
+
currentValue: this.existingValues[key],
|
|
131
|
+
options,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return fields;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async configureField(field: ConfigField): Promise<string> {
|
|
140
|
+
const currentValue = field.currentValue || field.defaultValue || '';
|
|
141
|
+
const _isRequired = field.required && !currentValue;
|
|
142
|
+
|
|
143
|
+
if (field.options) {
|
|
144
|
+
// Use select input for options
|
|
145
|
+
const { value } = await inquirer.prompt([
|
|
146
|
+
{
|
|
147
|
+
type: 'list',
|
|
148
|
+
name: 'value',
|
|
149
|
+
message: `${field.name}${field.required ? chalk.red('*') : ''}:`,
|
|
150
|
+
choices: field.options,
|
|
151
|
+
default: currentValue || field.options[0],
|
|
152
|
+
when: () => true,
|
|
153
|
+
},
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
console.log(chalk.gray(` ${field.description}`));
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
if (field.secret) {
|
|
160
|
+
// Use password input for secrets
|
|
161
|
+
const { value } = await inquirer.prompt([
|
|
162
|
+
{
|
|
163
|
+
type: 'password',
|
|
164
|
+
name: 'value',
|
|
165
|
+
message: `${field.name}${field.required ? chalk.red('*') : ''}:`,
|
|
166
|
+
default: currentValue,
|
|
167
|
+
when: () => true,
|
|
168
|
+
validate: (input) => {
|
|
169
|
+
if (field.required && !input.trim()) {
|
|
170
|
+
return `${field.name} is required`;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
console.log(chalk.gray(` ${field.description}`));
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
// Use regular input for regular fields
|
|
181
|
+
const { value } = await inquirer.prompt([
|
|
182
|
+
{
|
|
183
|
+
type: 'input',
|
|
184
|
+
name: 'value',
|
|
185
|
+
message: `${field.name}${field.required ? chalk.red('*') : ''}:`,
|
|
186
|
+
default: currentValue,
|
|
187
|
+
when: () => true,
|
|
188
|
+
validate: (input) => {
|
|
189
|
+
if (field.required && !input.trim()) {
|
|
190
|
+
return `${field.name} is required`;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
console.log(chalk.gray(` ${field.description}`));
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function configureMCP(
|
|
203
|
+
serverId: MCPServerID | undefined,
|
|
204
|
+
existingValues: Record<string, string>,
|
|
205
|
+
targetId: string,
|
|
206
|
+
cwd: string
|
|
207
|
+
): Promise<{ values: Record<string, string>; serverId?: MCPServerID }> {
|
|
208
|
+
const configurator = new MCPConfigurator({
|
|
209
|
+
serverId,
|
|
210
|
+
existingValues,
|
|
211
|
+
targetId,
|
|
212
|
+
cwd,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const result = await configurator.configure();
|
|
216
|
+
|
|
217
|
+
// Save configuration
|
|
218
|
+
if (result.serverId) {
|
|
219
|
+
const targetOption = targetManager.getTarget(targetId);
|
|
220
|
+
if (targetOption._tag === 'None') {
|
|
221
|
+
throw new Error(`Target not found: ${targetId}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const target = targetOption.value;
|
|
225
|
+
const config = await target.readConfig(cwd);
|
|
226
|
+
const mcpConfigPath = target.config.mcpConfigPath;
|
|
227
|
+
const mcpSection = getNestedProperty(config, mcpConfigPath) || {};
|
|
228
|
+
|
|
229
|
+
const server = MCP_SERVER_REGISTRY[result.serverId];
|
|
230
|
+
const serverConfig_env = server.config.type === 'local' ? server.config.environment : {};
|
|
231
|
+
|
|
232
|
+
const updatedEnv = { ...serverConfig_env };
|
|
233
|
+
for (const [key, value] of Object.entries(result.values)) {
|
|
234
|
+
if (value && value.trim() !== '') {
|
|
235
|
+
updatedEnv[key] = value;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
mcpSection[server.name] = {
|
|
240
|
+
...server.config,
|
|
241
|
+
environment: updatedEnv,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
setNestedProperty(config, mcpConfigPath, mcpSection);
|
|
245
|
+
await target.writeConfig(cwd, config);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { DrizzleMemoryStorage, type MemoryEntry } from '../utils/drizzle-storage.js';
|
|
5
|
+
|
|
6
|
+
interface MemoryEntryChoice extends inquirer.ChoiceBase {
|
|
7
|
+
value: MemoryEntry;
|
|
8
|
+
name: string;
|
|
9
|
+
short: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface MemoryActionChoice extends inquirer.ChoiceBase {
|
|
13
|
+
value: string;
|
|
14
|
+
name: string;
|
|
15
|
+
short: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface MemoryFormData {
|
|
19
|
+
namespace: string;
|
|
20
|
+
key: string;
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class MemoryTUI {
|
|
25
|
+
private memory: DrizzleMemoryStorage;
|
|
26
|
+
private entries: MemoryEntry[] = [];
|
|
27
|
+
private running = true;
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
this.memory = new DrizzleMemoryStorage();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async start(): Promise<void> {
|
|
34
|
+
console.clear();
|
|
35
|
+
console.log(chalk.cyan.bold('🧠 Memory Manager'));
|
|
36
|
+
console.log(chalk.gray('Interactive memory management for Sylphx Flow\n'));
|
|
37
|
+
|
|
38
|
+
await this.loadEntries();
|
|
39
|
+
|
|
40
|
+
while (this.running) {
|
|
41
|
+
try {
|
|
42
|
+
await this.showMainMenu();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(chalk.red(`Error: ${error}`));
|
|
45
|
+
await inquirer.prompt([
|
|
46
|
+
{
|
|
47
|
+
type: 'input',
|
|
48
|
+
name: 'continue',
|
|
49
|
+
message: 'Press Enter to continue...',
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async loadEntries(): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
this.entries = await this.memory.getAll();
|
|
59
|
+
this.entries.sort(
|
|
60
|
+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
61
|
+
);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(chalk.red(`Failed to load entries: ${error}`));
|
|
64
|
+
this.entries = [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async showMainMenu(): Promise<void> {
|
|
69
|
+
if (this.entries.length === 0) {
|
|
70
|
+
console.log(chalk.yellow('No memory entries found.'));
|
|
71
|
+
|
|
72
|
+
const { action } = await inquirer.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'list',
|
|
75
|
+
name: 'action',
|
|
76
|
+
message: 'What would you like to do?',
|
|
77
|
+
choices: [
|
|
78
|
+
{ name: '➕ Add new entry', value: 'add' },
|
|
79
|
+
{ name: '🔄 Refresh entries', value: 'refresh' },
|
|
80
|
+
{ name: '✗ Exit', value: 'exit' },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
if (action === 'add') {
|
|
86
|
+
await this.showAddEntry();
|
|
87
|
+
} else if (action === 'refresh') {
|
|
88
|
+
await this.loadEntries();
|
|
89
|
+
} else {
|
|
90
|
+
this.running = false;
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const choices: MemoryActionChoice[] = [
|
|
96
|
+
{ name: '📝 View entry details', value: 'view', short: 'View' },
|
|
97
|
+
{ name: '✏️ Edit entry', value: 'edit', short: 'Edit' },
|
|
98
|
+
{ name: '➕ Add new entry', value: 'add', short: 'Add' },
|
|
99
|
+
{ name: '🗑️ Delete entry', value: 'delete', short: 'Delete' },
|
|
100
|
+
{ name: '🔍 Search entries', value: 'search', short: 'Search' },
|
|
101
|
+
{ name: '🔄 Refresh entries', value: 'refresh', short: 'Refresh' },
|
|
102
|
+
{ name: '✗ Exit', value: 'exit', short: 'Exit' },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const { action } = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: 'list',
|
|
108
|
+
name: 'action',
|
|
109
|
+
message: 'What would you like to do?',
|
|
110
|
+
choices,
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
switch (action) {
|
|
115
|
+
case 'view':
|
|
116
|
+
await this.showViewEntry();
|
|
117
|
+
break;
|
|
118
|
+
case 'edit':
|
|
119
|
+
await this.showEditEntry();
|
|
120
|
+
break;
|
|
121
|
+
case 'add':
|
|
122
|
+
await this.showAddEntry();
|
|
123
|
+
break;
|
|
124
|
+
case 'delete':
|
|
125
|
+
await this.showDeleteEntry();
|
|
126
|
+
break;
|
|
127
|
+
case 'search':
|
|
128
|
+
await this.showSearchEntries();
|
|
129
|
+
break;
|
|
130
|
+
case 'refresh':
|
|
131
|
+
await this.loadEntries();
|
|
132
|
+
console.log(chalk.green('✓ Entries refreshed'));
|
|
133
|
+
break;
|
|
134
|
+
case 'exit':
|
|
135
|
+
this.running = false;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private async selectEntry(message: string, allowEmpty = false): Promise<MemoryEntry | null> {
|
|
141
|
+
if (this.entries.length === 0) {
|
|
142
|
+
if (allowEmpty) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
throw new Error('No entries available');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const choices: MemoryEntryChoice[] = this.entries.map((entry, index) => ({
|
|
149
|
+
value: entry,
|
|
150
|
+
name: `${chalk.cyan(`${index + 1}.`)} ${chalk.bold(entry.namespace)}:${chalk.bold(entry.key)}`,
|
|
151
|
+
short: `${entry.namespace}:${entry.key}`,
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
if (allowEmpty) {
|
|
155
|
+
choices.unshift({ name: '← Back to menu', value: null as any, short: 'Back' });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { selected } = await inquirer.prompt([
|
|
159
|
+
{
|
|
160
|
+
type: 'list',
|
|
161
|
+
name: 'selected',
|
|
162
|
+
message,
|
|
163
|
+
choices,
|
|
164
|
+
pageSize: 15,
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
return selected;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private async showViewEntry(): Promise<void> {
|
|
172
|
+
const entry = await this.selectEntry('Select entry to view:', true);
|
|
173
|
+
if (!entry) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.clear();
|
|
178
|
+
console.log(chalk.cyan.bold('📄 Entry Details'));
|
|
179
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
180
|
+
|
|
181
|
+
console.log(chalk.blue.bold('Namespace:'), entry.namespace);
|
|
182
|
+
console.log(chalk.blue.bold('Key:'), entry.key);
|
|
183
|
+
console.log(chalk.blue.bold('Updated:'), entry.updated_at);
|
|
184
|
+
|
|
185
|
+
console.log(chalk.blue.bold('\nValue:'));
|
|
186
|
+
const valueStr = JSON.stringify(entry.value, null, 2);
|
|
187
|
+
console.log(chalk.gray(valueStr));
|
|
188
|
+
|
|
189
|
+
await inquirer.prompt([
|
|
190
|
+
{
|
|
191
|
+
type: 'input',
|
|
192
|
+
name: 'continue',
|
|
193
|
+
message: 'Press Enter to continue...',
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async showEditEntry(): Promise<void> {
|
|
199
|
+
const entry = await this.selectEntry('Select entry to edit:', true);
|
|
200
|
+
if (!entry) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.clear();
|
|
205
|
+
console.log(chalk.yellow.bold('✏️ Edit Entry'));
|
|
206
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
207
|
+
console.log(`${chalk.blue('Editing:')} ${entry.namespace}:${entry.key}\n`);
|
|
208
|
+
|
|
209
|
+
const formData: MemoryFormData = await inquirer.prompt([
|
|
210
|
+
{
|
|
211
|
+
type: 'input',
|
|
212
|
+
name: 'namespace',
|
|
213
|
+
message: 'Namespace:',
|
|
214
|
+
default: entry.namespace,
|
|
215
|
+
validate: (input) => input.trim().length > 0 || 'Namespace is required',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: 'input',
|
|
219
|
+
name: 'key',
|
|
220
|
+
message: 'Key:',
|
|
221
|
+
default: entry.key,
|
|
222
|
+
validate: (input) => input.trim().length > 0 || 'Key is required',
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: 'editor',
|
|
226
|
+
name: 'value',
|
|
227
|
+
message: 'Value (JSON):',
|
|
228
|
+
default: JSON.stringify(entry.value, null, 2),
|
|
229
|
+
validate: (input) => {
|
|
230
|
+
try {
|
|
231
|
+
JSON.parse(input);
|
|
232
|
+
return true;
|
|
233
|
+
} catch {
|
|
234
|
+
return 'Invalid JSON format';
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const parsedValue = JSON.parse(formData.value);
|
|
242
|
+
await this.memory.set(formData.key, parsedValue, formData.namespace);
|
|
243
|
+
|
|
244
|
+
// Remove old entry if namespace or key changed
|
|
245
|
+
if (formData.namespace !== entry.namespace || formData.key !== entry.key) {
|
|
246
|
+
await this.memory.delete(entry.key, entry.namespace);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await this.loadEntries();
|
|
250
|
+
console.log(chalk.green(`✓ Updated ${formData.namespace}:${formData.key}`));
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error(chalk.red(`Failed to update entry: ${error}`));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await inquirer.prompt([
|
|
256
|
+
{
|
|
257
|
+
type: 'input',
|
|
258
|
+
name: 'continue',
|
|
259
|
+
message: 'Press Enter to continue...',
|
|
260
|
+
},
|
|
261
|
+
]);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private async showAddEntry(): Promise<void> {
|
|
265
|
+
console.clear();
|
|
266
|
+
console.log(chalk.green.bold('➕ Add New Entry'));
|
|
267
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
268
|
+
|
|
269
|
+
const formData: MemoryFormData = await inquirer.prompt([
|
|
270
|
+
{
|
|
271
|
+
type: 'input',
|
|
272
|
+
name: 'namespace',
|
|
273
|
+
message: 'Namespace:',
|
|
274
|
+
default: 'default',
|
|
275
|
+
validate: (input) => input.trim().length > 0 || 'Namespace is required',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
type: 'input',
|
|
279
|
+
name: 'key',
|
|
280
|
+
message: 'Key:',
|
|
281
|
+
validate: (input) => input.trim().length > 0 || 'Key is required',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
type: 'editor',
|
|
285
|
+
name: 'value',
|
|
286
|
+
message: 'Value (JSON):',
|
|
287
|
+
default: '{\n \n}',
|
|
288
|
+
validate: (input) => {
|
|
289
|
+
try {
|
|
290
|
+
JSON.parse(input);
|
|
291
|
+
return true;
|
|
292
|
+
} catch {
|
|
293
|
+
return 'Invalid JSON format';
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const parsedValue = JSON.parse(formData.value);
|
|
301
|
+
await this.memory.set(formData.key, parsedValue, formData.namespace);
|
|
302
|
+
await this.loadEntries();
|
|
303
|
+
console.log(chalk.green(`✓ Added ${formData.namespace}:${formData.key}`));
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(chalk.red(`Failed to add entry: ${error}`));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
await inquirer.prompt([
|
|
309
|
+
{
|
|
310
|
+
type: 'input',
|
|
311
|
+
name: 'continue',
|
|
312
|
+
message: 'Press Enter to continue...',
|
|
313
|
+
},
|
|
314
|
+
]);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private async showDeleteEntry(): Promise<void> {
|
|
318
|
+
const entry = await this.selectEntry('Select entry to delete:', true);
|
|
319
|
+
if (!entry) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.clear();
|
|
324
|
+
console.log(chalk.red.bold('🗑️ Delete Entry'));
|
|
325
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
326
|
+
console.log(`${chalk.blue('Entry:')} ${entry.namespace}:${entry.key}`);
|
|
327
|
+
|
|
328
|
+
const valuePreview = JSON.stringify(entry.value);
|
|
329
|
+
const preview =
|
|
330
|
+
valuePreview.length > 100 ? `${valuePreview.substring(0, 100)}...` : valuePreview;
|
|
331
|
+
console.log(`${chalk.blue('Value:')} ${chalk.gray(preview)}`);
|
|
332
|
+
|
|
333
|
+
const { confirmed } = await inquirer.prompt([
|
|
334
|
+
{
|
|
335
|
+
type: 'confirm',
|
|
336
|
+
name: 'confirmed',
|
|
337
|
+
message: chalk.yellow('Are you sure you want to delete this entry?'),
|
|
338
|
+
default: false,
|
|
339
|
+
},
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
if (confirmed) {
|
|
343
|
+
try {
|
|
344
|
+
await this.memory.delete(entry.key, entry.namespace);
|
|
345
|
+
await this.loadEntries();
|
|
346
|
+
console.log(chalk.green(`✓ Deleted ${entry.namespace}:${entry.key}`));
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error(chalk.red(`Failed to delete entry: ${error}`));
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
console.log(chalk.gray('Delete cancelled'));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
await inquirer.prompt([
|
|
355
|
+
{
|
|
356
|
+
type: 'input',
|
|
357
|
+
name: 'continue',
|
|
358
|
+
message: 'Press Enter to continue...',
|
|
359
|
+
},
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private async showSearchEntries(): Promise<void> {
|
|
364
|
+
console.clear();
|
|
365
|
+
console.log(chalk.magenta.bold('🔍 Search Entries'));
|
|
366
|
+
console.log(chalk.gray('─'.repeat(50)));
|
|
367
|
+
|
|
368
|
+
const { query } = await inquirer.prompt([
|
|
369
|
+
{
|
|
370
|
+
type: 'input',
|
|
371
|
+
name: 'query',
|
|
372
|
+
message: 'Search query:',
|
|
373
|
+
validate: (input) => input.trim().length > 0 || 'Search query is required',
|
|
374
|
+
},
|
|
375
|
+
]);
|
|
376
|
+
|
|
377
|
+
const filteredEntries = this.entries.filter(
|
|
378
|
+
(entry) =>
|
|
379
|
+
entry.namespace.toLowerCase().includes(query.toLowerCase()) ||
|
|
380
|
+
entry.key.toLowerCase().includes(query.toLowerCase()) ||
|
|
381
|
+
JSON.stringify(entry.value).toLowerCase().includes(query.toLowerCase())
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
if (filteredEntries.length === 0) {
|
|
385
|
+
console.log(chalk.yellow('No entries found matching your search.'));
|
|
386
|
+
} else {
|
|
387
|
+
console.log(chalk.cyan(`\nFound ${filteredEntries.length} matching entries:\n`));
|
|
388
|
+
|
|
389
|
+
filteredEntries.forEach((entry, index) => {
|
|
390
|
+
const valuePreview = JSON.stringify(entry.value);
|
|
391
|
+
const preview =
|
|
392
|
+
valuePreview.length > 80 ? `${valuePreview.substring(0, 80)}...` : valuePreview;
|
|
393
|
+
|
|
394
|
+
console.log(
|
|
395
|
+
`${chalk.cyan(`${index + 1}.`)} ${chalk.bold(entry.namespace)}:${chalk.bold(entry.key)}`
|
|
396
|
+
);
|
|
397
|
+
console.log(` ${chalk.gray(preview)}\n`);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
await inquirer.prompt([
|
|
402
|
+
{
|
|
403
|
+
type: 'input',
|
|
404
|
+
name: 'continue',
|
|
405
|
+
message: 'Press Enter to continue...',
|
|
406
|
+
},
|
|
407
|
+
]);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export const handleMemoryTui = async (): Promise<void> => {
|
|
412
|
+
const tui = new MemoryTUI();
|
|
413
|
+
await tui.start();
|
|
414
|
+
};
|