@sylphx/flow 1.8.0 → 1.8.2
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 +72 -0
- package/assets/output-styles/silent.md +145 -8
- package/assets/rules/core.md +19 -2
- package/package.json +2 -12
- package/src/commands/flow/execute.ts +470 -0
- package/src/commands/flow/index.ts +11 -0
- package/src/commands/flow/prompt.ts +35 -0
- package/src/commands/flow/setup.ts +312 -0
- package/src/commands/flow/targets.ts +18 -0
- package/src/commands/flow/types.ts +47 -0
- package/src/commands/flow-command.ts +18 -967
- package/src/commands/flow-orchestrator.ts +14 -5
- package/src/commands/hook-command.ts +1 -1
- package/src/commands/init-core.ts +12 -3
- package/src/commands/run-command.ts +1 -1
- package/src/config/rules.ts +1 -1
- package/src/core/error-handling.ts +1 -1
- package/src/core/loop-controller.ts +1 -1
- package/src/core/state-detector.ts +1 -1
- package/src/core/target-manager.ts +1 -1
- package/src/index.ts +1 -1
- package/src/shared/files/index.ts +1 -1
- package/src/shared/processing/index.ts +1 -1
- package/src/targets/claude-code.ts +3 -3
- package/src/targets/opencode.ts +3 -3
- package/src/utils/agent-enhancer.ts +2 -2
- package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
- package/src/utils/{paths.ts → config/paths.ts} +1 -1
- package/src/utils/{settings.ts → config/settings.ts} +1 -1
- package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
- package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
- package/src/utils/display/banner.ts +25 -0
- package/src/utils/display/status.ts +55 -0
- package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
- package/src/utils/files/jsonc.ts +36 -0
- package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
- package/src/utils/index.ts +42 -61
- package/src/utils/version.ts +47 -0
- package/src/components/benchmark-monitor.tsx +0 -331
- package/src/components/reindex-progress.tsx +0 -261
- package/src/composables/functional/index.ts +0 -14
- package/src/composables/functional/useEnvironment.ts +0 -171
- package/src/composables/functional/useFileSystem.ts +0 -139
- package/src/composables/index.ts +0 -4
- package/src/composables/useEnv.ts +0 -13
- package/src/composables/useRuntimeConfig.ts +0 -27
- package/src/core/ai-sdk.ts +0 -603
- package/src/core/app-factory.ts +0 -381
- package/src/core/builtin-agents.ts +0 -9
- package/src/core/command-system.ts +0 -550
- package/src/core/config-system.ts +0 -550
- package/src/core/connection-pool.ts +0 -390
- package/src/core/di-container.ts +0 -155
- package/src/core/headless-display.ts +0 -96
- package/src/core/interfaces/index.ts +0 -22
- package/src/core/interfaces/repository.interface.ts +0 -91
- package/src/core/interfaces/service.interface.ts +0 -133
- package/src/core/interfaces.ts +0 -96
- package/src/core/result.ts +0 -351
- package/src/core/service-config.ts +0 -252
- package/src/core/session-service.ts +0 -121
- package/src/core/storage-factory.ts +0 -115
- package/src/core/stream-handler.ts +0 -288
- package/src/core/type-utils.ts +0 -427
- package/src/core/unified-storage.ts +0 -456
- package/src/core/validation/limit.ts +0 -46
- package/src/core/validation/query.ts +0 -20
- package/src/db/auto-migrate.ts +0 -322
- package/src/db/base-database-client.ts +0 -144
- package/src/db/cache-db.ts +0 -218
- package/src/db/cache-schema.ts +0 -75
- package/src/db/database.ts +0 -70
- package/src/db/index.ts +0 -252
- package/src/db/memory-db.ts +0 -153
- package/src/db/memory-schema.ts +0 -29
- package/src/db/schema.ts +0 -289
- package/src/db/session-repository.ts +0 -733
- package/src/domains/index.ts +0 -6
- package/src/domains/utilities/index.ts +0 -6
- package/src/domains/utilities/time/index.ts +0 -5
- package/src/domains/utilities/time/tools.ts +0 -291
- package/src/services/agent-service.ts +0 -273
- package/src/services/evaluation-service.ts +0 -271
- package/src/services/functional/evaluation-logic.ts +0 -296
- package/src/services/functional/file-processor.ts +0 -273
- package/src/services/functional/index.ts +0 -12
- package/src/services/memory.service.ts +0 -476
- package/src/types/api/batch.ts +0 -108
- package/src/types/api/errors.ts +0 -118
- package/src/types/api/index.ts +0 -55
- package/src/types/api/requests.ts +0 -76
- package/src/types/api/responses.ts +0 -180
- package/src/types/api/websockets.ts +0 -85
- package/src/types/benchmark.ts +0 -49
- package/src/types/database.types.ts +0 -510
- package/src/types/memory-types.ts +0 -63
- package/src/utils/advanced-tokenizer.ts +0 -191
- package/src/utils/ai-model-fetcher.ts +0 -19
- package/src/utils/async-file-operations.ts +0 -516
- package/src/utils/audio-player.ts +0 -345
- package/src/utils/codebase-helpers.ts +0 -211
- package/src/utils/console-ui.ts +0 -79
- package/src/utils/database-errors.ts +0 -140
- package/src/utils/debug-logger.ts +0 -49
- package/src/utils/file-scanner.ts +0 -259
- package/src/utils/help.ts +0 -20
- package/src/utils/immutable-cache.ts +0 -106
- package/src/utils/jsonc.ts +0 -158
- package/src/utils/memory-tui.ts +0 -414
- package/src/utils/models-dev.ts +0 -91
- package/src/utils/parallel-operations.ts +0 -487
- package/src/utils/process-manager.ts +0 -155
- package/src/utils/prompts.ts +0 -120
- package/src/utils/search-tool-builder.ts +0 -214
- package/src/utils/session-manager.ts +0 -168
- package/src/utils/session-title.ts +0 -87
- package/src/utils/simplified-errors.ts +0 -410
- package/src/utils/template-engine.ts +0 -94
- package/src/utils/test-audio.ts +0 -71
- package/src/utils/todo-context.ts +0 -46
- package/src/utils/token-counter.ts +0 -288
- /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
- /package/src/utils/{logger.ts → display/logger.ts} +0 -0
- /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
- /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
- /package/src/utils/{security.ts → security/security.ts} +0 -0
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Advanced Code Tokenizer - 純粹 StarCoder2,無任何多餘處理
|
|
3
|
-
* 完全信任 StarCoder2 嘅世界級 tokenization 能力
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { AutoTokenizer } from '@huggingface/transformers';
|
|
7
|
-
|
|
8
|
-
export interface AdvancedToken {
|
|
9
|
-
text: string;
|
|
10
|
-
id: number;
|
|
11
|
-
score: number;
|
|
12
|
-
confidence: number;
|
|
13
|
-
relevance: 'high' | 'medium' | 'low';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface AdvancedTokenizerResult {
|
|
17
|
-
tokens: AdvancedToken[];
|
|
18
|
-
metadata: {
|
|
19
|
-
totalTokens: number;
|
|
20
|
-
vocabSize: number;
|
|
21
|
-
processingTime: number;
|
|
22
|
-
averageConfidence: number;
|
|
23
|
-
};
|
|
24
|
-
raw: {
|
|
25
|
-
inputIds: number[];
|
|
26
|
-
decodedText: string;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Advanced Code Tokenizer - 純粹 StarCoder2
|
|
32
|
-
*/
|
|
33
|
-
export class AdvancedCodeTokenizer {
|
|
34
|
-
private tokenizer: any;
|
|
35
|
-
private initialized = false;
|
|
36
|
-
private modelPath: string;
|
|
37
|
-
|
|
38
|
-
constructor(
|
|
39
|
-
options: {
|
|
40
|
-
modelPath?: string;
|
|
41
|
-
} = {}
|
|
42
|
-
) {
|
|
43
|
-
this.modelPath = options.modelPath || './models/starcoder2';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 初始化 tokenizer
|
|
48
|
-
*/
|
|
49
|
-
async initialize(): Promise<void> {
|
|
50
|
-
if (this.initialized) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
this.tokenizer = await AutoTokenizer.from_pretrained(this.modelPath);
|
|
56
|
-
this.initialized = true;
|
|
57
|
-
} catch (error) {
|
|
58
|
-
throw new Error(`Tokenizer initialization failed: ${error.message}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 純粹 StarCoder2 tokenization - 無任何安全限制或多餘分析
|
|
64
|
-
*/
|
|
65
|
-
async tokenize(content: string): Promise<AdvancedTokenizerResult> {
|
|
66
|
-
if (!this.initialized) {
|
|
67
|
-
await this.initialize();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const startTime = Date.now();
|
|
71
|
-
|
|
72
|
-
// Handle empty content
|
|
73
|
-
if (!content || content.trim().length === 0) {
|
|
74
|
-
return {
|
|
75
|
-
tokens: [],
|
|
76
|
-
metadata: {
|
|
77
|
-
totalTokens: 0,
|
|
78
|
-
vocabSize: 49152,
|
|
79
|
-
processingTime: Date.now() - startTime,
|
|
80
|
-
averageConfidence: 0,
|
|
81
|
-
},
|
|
82
|
-
raw: {
|
|
83
|
-
inputIds: [],
|
|
84
|
-
decodedText: '',
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
// 完全信任 StarCoder2,直接處理所有內容
|
|
91
|
-
const encoded = await this.tokenizer(content);
|
|
92
|
-
const inputIds = encoded.input_ids.tolist()[0];
|
|
93
|
-
|
|
94
|
-
// 解碼獲得原文
|
|
95
|
-
const decodedText = await this.tokenizer.decode(inputIds);
|
|
96
|
-
|
|
97
|
-
// 直接用 StarCoder2 嘅輸出,唔做多餘分析
|
|
98
|
-
const tokens = await this.createDirectTokens(decodedText, inputIds);
|
|
99
|
-
|
|
100
|
-
const processingTime = Date.now() - startTime;
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
tokens,
|
|
104
|
-
metadata: {
|
|
105
|
-
totalTokens: tokens.length,
|
|
106
|
-
vocabSize: 49152,
|
|
107
|
-
processingTime,
|
|
108
|
-
averageConfidence: 0.95, // StarCoder2 本身就係高質量
|
|
109
|
-
},
|
|
110
|
-
raw: {
|
|
111
|
-
inputIds,
|
|
112
|
-
decodedText,
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
} catch (error) {
|
|
116
|
-
throw new Error(`Tokenization failed: ${error.message}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 純粹 StarCoder2 tokenization - 完全信任,無任何額外處理
|
|
122
|
-
* 直接使用 StarCoder2 嘅 token IDs,逐個解碼成文字
|
|
123
|
-
*/
|
|
124
|
-
private async createDirectTokens(
|
|
125
|
-
_decodedText: string,
|
|
126
|
-
inputIds: number[]
|
|
127
|
-
): Promise<AdvancedToken[]> {
|
|
128
|
-
const tokens: AdvancedToken[] = [];
|
|
129
|
-
|
|
130
|
-
// 完全信任 StarCoder2,直接使用佢嘅 tokenization
|
|
131
|
-
// 逐個 token ID 解碼,得到 StarCoder2 認為嘅最佳分割
|
|
132
|
-
for (let i = 0; i < inputIds.length; i++) {
|
|
133
|
-
const tokenId = inputIds[i];
|
|
134
|
-
try {
|
|
135
|
-
// 直接使用 StarCoder2 嘅解碼結果
|
|
136
|
-
const tokenText = await this.tokenizer.decode([tokenId], { skip_special_tokens: true });
|
|
137
|
-
const cleaned = tokenText.trim().toLowerCase();
|
|
138
|
-
|
|
139
|
-
// 只過濾空白 token,其他全部保留(完全信任 StarCoder2)
|
|
140
|
-
if (cleaned.length > 0) {
|
|
141
|
-
tokens.push({
|
|
142
|
-
text: cleaned,
|
|
143
|
-
id: tokenId,
|
|
144
|
-
score: 1.0, // StarCoder2 嘅所有 token 都係高質量
|
|
145
|
-
confidence: 1.0, // 完全信任 StarCoder2
|
|
146
|
-
relevance: 'high' as const,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
} catch (_error) {}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return tokens;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 便利方法:只返回高質量 tokens
|
|
157
|
-
*/
|
|
158
|
-
async getTopTokens(content: string, limit = 20): Promise<AdvancedToken[]> {
|
|
159
|
-
const result = await this.tokenize(content);
|
|
160
|
-
return result.tokens.slice(0, limit);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 便利方法:返回所有 tokens (StarCoder2 嘅輸出全部都係高質量)
|
|
165
|
-
*/
|
|
166
|
-
async getTechnicalTokens(content: string): Promise<AdvancedToken[]> {
|
|
167
|
-
const result = await this.tokenize(content);
|
|
168
|
-
return result.tokens; // StarCoder2 嘅所有輸出都係技術相關
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* 解碼 token IDs 回文本
|
|
173
|
-
*/
|
|
174
|
-
async decode(tokenIds: number[]): Promise<string> {
|
|
175
|
-
if (!this.initialized) {
|
|
176
|
-
throw new Error('Tokenizer not initialized. Call initialize() first.');
|
|
177
|
-
}
|
|
178
|
-
return await this.tokenizer.decode(tokenIds);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 編碼文本為 token IDs
|
|
183
|
-
*/
|
|
184
|
-
async encode(text: string): Promise<number[]> {
|
|
185
|
-
if (!this.initialized) {
|
|
186
|
-
throw new Error('Tokenizer not initialized. Call initialize() first.');
|
|
187
|
-
}
|
|
188
|
-
const result = await this.tokenizer(text);
|
|
189
|
-
return result.input_ids.tolist()[0];
|
|
190
|
-
}
|
|
191
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Model Fetcher
|
|
3
|
-
* Dynamically fetch available models from providers using provider registry
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ProviderId } from '../types/provider.types.js';
|
|
7
|
-
import type { ProviderConfig, ModelInfo } from '../providers/base-provider.js';
|
|
8
|
-
import { getProvider } from '../providers/index.js';
|
|
9
|
-
|
|
10
|
-
// Re-export ModelInfo for backward compatibility
|
|
11
|
-
export type { ModelInfo } from '../providers/base-provider.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Fetch models for a provider using provider registry
|
|
15
|
-
*/
|
|
16
|
-
export async function fetchModels(provider: ProviderId, config: ProviderConfig = {}): Promise<ModelInfo[]> {
|
|
17
|
-
const providerInstance = getProvider(provider);
|
|
18
|
-
return providerInstance.fetchModels(config);
|
|
19
|
-
}
|
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Async File Operations Utility
|
|
3
|
-
*
|
|
4
|
-
* Provides async wrappers for common file operations with better error handling,
|
|
5
|
-
* concurrency control, and performance optimizations
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createHash } from 'node:crypto';
|
|
9
|
-
import * as fs from 'node:fs/promises';
|
|
10
|
-
import * as path from 'node:path';
|
|
11
|
-
|
|
12
|
-
export interface FileOperationOptions {
|
|
13
|
-
encoding?: BufferEncoding;
|
|
14
|
-
signal?: AbortSignal;
|
|
15
|
-
retryAttempts?: number;
|
|
16
|
-
retryDelay?: number;
|
|
17
|
-
timeout?: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface FileStats {
|
|
21
|
-
size: number;
|
|
22
|
-
created: Date;
|
|
23
|
-
modified: Date;
|
|
24
|
-
accessed: Date;
|
|
25
|
-
isFile: boolean;
|
|
26
|
-
isDirectory: boolean;
|
|
27
|
-
permissions: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface DirectoryEntry {
|
|
31
|
-
name: string;
|
|
32
|
-
path: string;
|
|
33
|
-
isFile: boolean;
|
|
34
|
-
isDirectory: boolean;
|
|
35
|
-
stats?: FileStats;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface CopyOptions {
|
|
39
|
-
overwrite?: boolean;
|
|
40
|
-
preserveTimestamps?: boolean;
|
|
41
|
-
filter?: (source: string, dest: string) => boolean;
|
|
42
|
-
concurrency?: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ReadDirOptions {
|
|
46
|
-
withFileTypes?: boolean;
|
|
47
|
-
recursive?: boolean;
|
|
48
|
-
includeStats?: boolean;
|
|
49
|
-
filter?: (entry: DirectoryEntry) => boolean;
|
|
50
|
-
maxDepth?: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Async file operations with error handling and retries
|
|
55
|
-
*/
|
|
56
|
-
export class AsyncFileOperations {
|
|
57
|
-
private readonly defaultOptions: Required<FileOperationOptions> = {
|
|
58
|
-
encoding: 'utf8',
|
|
59
|
-
retryAttempts: 3,
|
|
60
|
-
retryDelay: 1000,
|
|
61
|
-
timeout: 30000,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Read file content with retries
|
|
66
|
-
*/
|
|
67
|
-
async readFile(filePath: string, options: FileOperationOptions = {}): Promise<string | Buffer> {
|
|
68
|
-
const opts = { ...this.defaultOptions, ...options };
|
|
69
|
-
|
|
70
|
-
return this.withRetry(
|
|
71
|
-
async () => {
|
|
72
|
-
if (opts.timeout) {
|
|
73
|
-
return await this.withTimeout(() => fs.readFile(filePath, opts.encoding), opts.timeout);
|
|
74
|
-
}
|
|
75
|
-
return await fs.readFile(filePath, opts.encoding);
|
|
76
|
-
},
|
|
77
|
-
opts.retryAttempts,
|
|
78
|
-
opts.retryDelay
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Write file content with retries and backup
|
|
84
|
-
*/
|
|
85
|
-
async writeFile(
|
|
86
|
-
filePath: string,
|
|
87
|
-
content: string | Buffer | NodeJS.ArrayBufferView,
|
|
88
|
-
options: FileOperationOptions & { createBackup?: boolean } = {}
|
|
89
|
-
): Promise<void> {
|
|
90
|
-
const opts = { ...this.defaultOptions, createBackup: true, ...options };
|
|
91
|
-
|
|
92
|
-
// Create backup if requested and file exists
|
|
93
|
-
if (opts.createBackup && (await this.exists(filePath))) {
|
|
94
|
-
await this.createBackup(filePath);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return this.withRetry(
|
|
98
|
-
async () => {
|
|
99
|
-
// Ensure directory exists
|
|
100
|
-
await this.ensureDir(path.dirname(filePath));
|
|
101
|
-
|
|
102
|
-
if (opts.timeout) {
|
|
103
|
-
return await this.withTimeout(
|
|
104
|
-
() => fs.writeFile(filePath, content, opts.encoding),
|
|
105
|
-
opts.timeout
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
return await fs.writeFile(filePath, content, opts.encoding);
|
|
109
|
-
},
|
|
110
|
-
opts.retryAttempts,
|
|
111
|
-
opts.retryDelay
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Append to file with retries
|
|
117
|
-
*/
|
|
118
|
-
async appendFile(
|
|
119
|
-
filePath: string,
|
|
120
|
-
content: string | Buffer,
|
|
121
|
-
options: FileOperationOptions = {}
|
|
122
|
-
): Promise<void> {
|
|
123
|
-
const opts = { ...this.defaultOptions, ...options };
|
|
124
|
-
|
|
125
|
-
return this.withRetry(
|
|
126
|
-
async () => {
|
|
127
|
-
// Ensure directory exists
|
|
128
|
-
await this.ensureDir(path.dirname(filePath));
|
|
129
|
-
|
|
130
|
-
if (opts.timeout) {
|
|
131
|
-
return await this.withTimeout(
|
|
132
|
-
() => fs.appendFile(filePath, content, opts.encoding),
|
|
133
|
-
opts.timeout
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
return await fs.appendFile(filePath, content, opts.encoding);
|
|
137
|
-
},
|
|
138
|
-
opts.retryAttempts,
|
|
139
|
-
opts.retryDelay
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Check if file or directory exists
|
|
145
|
-
*/
|
|
146
|
-
async exists(filePath: string): Promise<boolean> {
|
|
147
|
-
try {
|
|
148
|
-
await fs.access(filePath);
|
|
149
|
-
return true;
|
|
150
|
-
} catch {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get file statistics
|
|
157
|
-
*/
|
|
158
|
-
async getStats(filePath: string): Promise<FileStats> {
|
|
159
|
-
const stats = await fs.stat(filePath);
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
size: stats.size,
|
|
163
|
-
created: stats.birthtime,
|
|
164
|
-
modified: stats.mtime,
|
|
165
|
-
accessed: stats.atime,
|
|
166
|
-
isFile: stats.isFile(),
|
|
167
|
-
isDirectory: stats.isDirectory(),
|
|
168
|
-
permissions: stats.mode.toString(8),
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Create directory recursively
|
|
174
|
-
*/
|
|
175
|
-
async ensureDir(dirPath: string): Promise<void> {
|
|
176
|
-
try {
|
|
177
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
178
|
-
} catch (error) {
|
|
179
|
-
// Check if directory already exists
|
|
180
|
-
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST')) {
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Remove file or directory recursively
|
|
188
|
-
*/
|
|
189
|
-
async remove(
|
|
190
|
-
targetPath: string,
|
|
191
|
-
options: { recursive?: boolean; force?: boolean } = {}
|
|
192
|
-
): Promise<void> {
|
|
193
|
-
const { recursive = true, force = false } = options;
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
const stats = await fs.lstat(targetPath);
|
|
197
|
-
|
|
198
|
-
if (stats.isDirectory() && recursive) {
|
|
199
|
-
await fs.rm(targetPath, { recursive: true, force });
|
|
200
|
-
} else if (stats.isDirectory() && !recursive) {
|
|
201
|
-
throw new Error('Cannot remove directory without recursive option');
|
|
202
|
-
} else {
|
|
203
|
-
await fs.unlink(targetPath);
|
|
204
|
-
}
|
|
205
|
-
} catch (error) {
|
|
206
|
-
if (!force || !(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
207
|
-
throw error;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Copy file or directory
|
|
214
|
-
*/
|
|
215
|
-
async copy(source: string, destination: string, options: CopyOptions = {}): Promise<void> {
|
|
216
|
-
const { overwrite = false, preserveTimestamps = true, filter, concurrency = 10 } = options;
|
|
217
|
-
|
|
218
|
-
const sourceStats = await fs.lstat(source);
|
|
219
|
-
|
|
220
|
-
if (sourceStats.isDirectory()) {
|
|
221
|
-
await this.copyDirectory(source, destination, {
|
|
222
|
-
overwrite,
|
|
223
|
-
preserveTimestamps,
|
|
224
|
-
filter,
|
|
225
|
-
concurrency,
|
|
226
|
-
});
|
|
227
|
-
} else {
|
|
228
|
-
await this.copyFile(source, destination, {
|
|
229
|
-
overwrite,
|
|
230
|
-
preserveTimestamps,
|
|
231
|
-
filter,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Move file or directory
|
|
238
|
-
*/
|
|
239
|
-
async move(
|
|
240
|
-
source: string,
|
|
241
|
-
destination: string,
|
|
242
|
-
options: { overwrite?: boolean } = {}
|
|
243
|
-
): Promise<void> {
|
|
244
|
-
const { overwrite = false } = options;
|
|
245
|
-
|
|
246
|
-
// Check if destination exists
|
|
247
|
-
if ((await this.exists(destination)) && !overwrite) {
|
|
248
|
-
throw new Error(`Destination already exists: ${destination}`);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
await fs.rename(source, destination);
|
|
253
|
-
} catch (error) {
|
|
254
|
-
// Fallback to copy + delete if rename fails (cross-device)
|
|
255
|
-
if (error instanceof Error && 'code' in error && error.code === 'EXDEV') {
|
|
256
|
-
await this.copy(source, destination, { overwrite });
|
|
257
|
-
await this.remove(source, { recursive: true });
|
|
258
|
-
} else {
|
|
259
|
-
throw error;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Read directory contents
|
|
266
|
-
*/
|
|
267
|
-
async readDir(dirPath: string, options: ReadDirOptions = {}): Promise<DirectoryEntry[]> {
|
|
268
|
-
const {
|
|
269
|
-
withFileTypes = false,
|
|
270
|
-
recursive = false,
|
|
271
|
-
includeStats = false,
|
|
272
|
-
filter,
|
|
273
|
-
maxDepth = 10,
|
|
274
|
-
} = options;
|
|
275
|
-
|
|
276
|
-
const results: DirectoryEntry[] = [];
|
|
277
|
-
|
|
278
|
-
const processDirectory = async (currentPath: string, currentDepth: number): Promise<void> => {
|
|
279
|
-
if (currentDepth > maxDepth) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
const entries = await fs.readdir(currentPath, { withFileTypes });
|
|
285
|
-
|
|
286
|
-
const promises = entries.map(async (entry) => {
|
|
287
|
-
const fullPath = path.join(currentPath, entry.name);
|
|
288
|
-
const directoryEntry: DirectoryEntry = {
|
|
289
|
-
name: entry.name,
|
|
290
|
-
path: fullPath,
|
|
291
|
-
isFile: entry.isFile(),
|
|
292
|
-
isDirectory: entry.isDirectory(),
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
if (includeStats) {
|
|
296
|
-
try {
|
|
297
|
-
directoryEntry.stats = await this.getStats(fullPath);
|
|
298
|
-
} catch (_error) {
|
|
299
|
-
// Skip if stats can't be retrieved
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Apply filter if provided
|
|
304
|
-
if (filter && !filter(directoryEntry)) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
results.push(directoryEntry);
|
|
309
|
-
|
|
310
|
-
// Recursively process subdirectories
|
|
311
|
-
if (recursive && entry.isDirectory()) {
|
|
312
|
-
await processDirectory(fullPath, currentDepth + 1);
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
await Promise.all(promises);
|
|
317
|
-
} catch (error) {
|
|
318
|
-
throw new Error(`Failed to read directory ${currentPath}: ${error}`);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
await processDirectory(dirPath, 0);
|
|
323
|
-
return results;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Calculate file hash
|
|
328
|
-
*/
|
|
329
|
-
async calculateHash(filePath: string, algorithm = 'sha256'): Promise<string> {
|
|
330
|
-
const content = await this.readFile(filePath);
|
|
331
|
-
return createHash(algorithm)
|
|
332
|
-
.update(content as Buffer)
|
|
333
|
-
.digest('hex');
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Watch file or directory for changes
|
|
338
|
-
*/
|
|
339
|
-
async watch(
|
|
340
|
-
targetPath: string,
|
|
341
|
-
callback: (eventType: string, filename: string | null) => void,
|
|
342
|
-
options: { recursive?: boolean } = {}
|
|
343
|
-
): Promise<fs.FSWatcher> {
|
|
344
|
-
const { recursive = false } = options;
|
|
345
|
-
|
|
346
|
-
return new Promise((resolve, reject) => {
|
|
347
|
-
try {
|
|
348
|
-
const watcher = fs.watch(targetPath, { recursive }, callback);
|
|
349
|
-
resolve(watcher);
|
|
350
|
-
} catch (error) {
|
|
351
|
-
reject(error);
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Create backup of file
|
|
358
|
-
*/
|
|
359
|
-
private async createBackup(filePath: string): Promise<string> {
|
|
360
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
361
|
-
const backupPath = `${filePath}.backup.${timestamp}`;
|
|
362
|
-
await this.copy(filePath, backupPath);
|
|
363
|
-
return backupPath;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Copy file with options
|
|
368
|
-
*/
|
|
369
|
-
private async copyFile(
|
|
370
|
-
source: string,
|
|
371
|
-
destination: string,
|
|
372
|
-
options: {
|
|
373
|
-
overwrite?: boolean;
|
|
374
|
-
preserveTimestamps?: boolean;
|
|
375
|
-
filter?: (source: string, dest: string) => boolean;
|
|
376
|
-
}
|
|
377
|
-
): Promise<void> {
|
|
378
|
-
const { overwrite = false, preserveTimestamps = true, filter } = options;
|
|
379
|
-
|
|
380
|
-
// Apply filter
|
|
381
|
-
if (filter && !filter(source, destination)) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Check if destination exists
|
|
386
|
-
if ((await this.exists(destination)) && !overwrite) {
|
|
387
|
-
throw new Error(`Destination already exists: ${destination}`);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Ensure destination directory exists
|
|
391
|
-
await this.ensureDir(path.dirname(destination));
|
|
392
|
-
|
|
393
|
-
// Copy file
|
|
394
|
-
await fs.copyFile(source, destination);
|
|
395
|
-
|
|
396
|
-
// Preserve timestamps if requested
|
|
397
|
-
if (preserveTimestamps) {
|
|
398
|
-
const sourceStats = await fs.stat(source);
|
|
399
|
-
await fs.utimes(destination, sourceStats.atime, sourceStats.mtime);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Copy directory with options
|
|
405
|
-
*/
|
|
406
|
-
private async copyDirectory(
|
|
407
|
-
source: string,
|
|
408
|
-
destination: string,
|
|
409
|
-
options: {
|
|
410
|
-
overwrite?: boolean;
|
|
411
|
-
preserveTimestamps?: boolean;
|
|
412
|
-
filter?: (source: string, dest: string) => boolean;
|
|
413
|
-
concurrency?: number;
|
|
414
|
-
}
|
|
415
|
-
): Promise<void> {
|
|
416
|
-
const { concurrency = 10 } = options;
|
|
417
|
-
|
|
418
|
-
// Ensure destination directory exists
|
|
419
|
-
await this.ensureDir(destination);
|
|
420
|
-
|
|
421
|
-
// Get all entries
|
|
422
|
-
const entries = await this.readDir(source, { recursive: true });
|
|
423
|
-
|
|
424
|
-
// Process entries in batches
|
|
425
|
-
for (let i = 0; i < entries.length; i += concurrency) {
|
|
426
|
-
const batch = entries.slice(i, i + concurrency);
|
|
427
|
-
|
|
428
|
-
await Promise.all(
|
|
429
|
-
batch.map(async (entry) => {
|
|
430
|
-
const relativePath = path.relative(source, entry.path);
|
|
431
|
-
const destPath = path.join(destination, relativePath);
|
|
432
|
-
|
|
433
|
-
if (entry.isFile) {
|
|
434
|
-
await this.copyFile(entry.path, destPath, options);
|
|
435
|
-
} else if (entry.isDirectory) {
|
|
436
|
-
await this.ensureDir(destPath);
|
|
437
|
-
}
|
|
438
|
-
})
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Execute operation with retry logic
|
|
445
|
-
*/
|
|
446
|
-
private async withRetry<T>(
|
|
447
|
-
operation: () => Promise<T>,
|
|
448
|
-
attempts: number,
|
|
449
|
-
delay: number
|
|
450
|
-
): Promise<T> {
|
|
451
|
-
let lastError: Error;
|
|
452
|
-
|
|
453
|
-
for (let i = 0; i < attempts; i++) {
|
|
454
|
-
try {
|
|
455
|
-
return await operation();
|
|
456
|
-
} catch (error) {
|
|
457
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
458
|
-
|
|
459
|
-
if (i < attempts - 1) {
|
|
460
|
-
await this.sleep(delay * 2 ** i); // Exponential backoff
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
throw lastError!;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Execute operation with timeout
|
|
470
|
-
*/
|
|
471
|
-
private async withTimeout<T>(operation: () => Promise<T>, timeoutMs: number): Promise<T> {
|
|
472
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
473
|
-
setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
return Promise.race([operation(), timeoutPromise]);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Sleep utility
|
|
481
|
-
*/
|
|
482
|
-
private sleep(ms: number): Promise<void> {
|
|
483
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Export singleton instance
|
|
488
|
-
export const asyncFileOps = new AsyncFileOperations();
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Convenience functions for common operations
|
|
492
|
-
*/
|
|
493
|
-
export const readFile = (filePath: string, options?: FileOperationOptions) =>
|
|
494
|
-
asyncFileOps.readFile(filePath, options);
|
|
495
|
-
|
|
496
|
-
export const writeFile = (
|
|
497
|
-
filePath: string,
|
|
498
|
-
content: string | Buffer,
|
|
499
|
-
options?: FileOperationOptions
|
|
500
|
-
) => asyncFileOps.writeFile(filePath, content, options);
|
|
501
|
-
|
|
502
|
-
export const exists = (filePath: string) => asyncFileOps.exists(filePath);
|
|
503
|
-
|
|
504
|
-
export const ensureDir = (dirPath: string) => asyncFileOps.ensureDir(dirPath);
|
|
505
|
-
|
|
506
|
-
export const remove = (targetPath: string, options?: { recursive?: boolean; force?: boolean }) =>
|
|
507
|
-
asyncFileOps.remove(targetPath, options);
|
|
508
|
-
|
|
509
|
-
export const copy = (source: string, destination: string, options?: CopyOptions) =>
|
|
510
|
-
asyncFileOps.copy(source, destination, options);
|
|
511
|
-
|
|
512
|
-
export const move = (source: string, destination: string, options?: { overwrite?: boolean }) =>
|
|
513
|
-
asyncFileOps.move(source, destination, options);
|
|
514
|
-
|
|
515
|
-
export const readDir = (dirPath: string, options?: ReadDirOptions) =>
|
|
516
|
-
asyncFileOps.readDir(dirPath, options);
|