ccjk 9.6.1 → 9.8.0
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/dist/chunks/boost.mjs +246 -7
- package/dist/chunks/ccjk-mcp.mjs +1 -1
- package/dist/chunks/ccr.mjs +25 -28
- package/dist/chunks/check-updates.mjs +4 -3
- package/dist/chunks/claude-code-config-manager.mjs +1 -1
- package/dist/chunks/claude-code-incremental-manager.mjs +1 -1
- package/dist/chunks/claude-config.mjs +1 -1
- package/dist/chunks/codex-config-switch.mjs +3 -4
- package/dist/chunks/codex-provider-manager.mjs +1 -2
- package/dist/chunks/codex.mjs +204 -3
- package/dist/chunks/config-switch.mjs +2 -3
- package/dist/chunks/config.mjs +1 -1
- package/dist/chunks/doctor.mjs +1 -1
- package/dist/chunks/features.mjs +24 -15
- package/dist/chunks/hook-installer.mjs +44 -0
- package/dist/chunks/index3.mjs +32 -32
- package/dist/chunks/init.mjs +129 -87
- package/dist/chunks/installer2.mjs +1 -1
- package/dist/chunks/interview.mjs +1 -1
- package/dist/chunks/mcp.mjs +1058 -17
- package/dist/chunks/menu.mjs +140 -56
- package/dist/chunks/package.mjs +2 -210
- package/dist/chunks/platform.mjs +1 -1
- package/dist/chunks/quick-setup.mjs +35 -18
- package/dist/chunks/simple-config.mjs +1 -1
- package/dist/{shared/ccjk.q1koQxEE.mjs → chunks/smart-defaults.mjs} +77 -79
- package/dist/chunks/status.mjs +208 -101
- package/dist/chunks/thinking.mjs +1 -1
- package/dist/chunks/uninstall.mjs +6 -4
- package/dist/chunks/update.mjs +4 -7
- package/dist/chunks/version-checker.mjs +1 -1
- package/dist/cli.mjs +4 -80
- package/dist/index.d.mts +17 -1482
- package/dist/index.d.ts +17 -1482
- package/dist/index.mjs +12 -4191
- package/dist/shared/{ccjk.CSkyCZIM.mjs → ccjk.Bndhan7G.mjs} +4 -242
- package/dist/shared/ccjk.CeE8RLG2.mjs +62 -0
- package/dist/shared/ccjk.DKojSRzw.mjs +266 -0
- package/dist/shared/{ccjk.CItD1fpl.mjs → ccjk.DvIrK0wz.mjs} +1 -1
- package/dist/shared/ccjk.LsPZ2PYo.mjs +1048 -0
- package/package.json +1 -1
- package/dist/chunks/api-adapter.mjs +0 -180
- package/dist/chunks/cli.mjs +0 -2227
- package/dist/chunks/context-menu.mjs +0 -913
- package/dist/chunks/hooks-sync.mjs +0 -1627
- package/dist/chunks/mcp-market.mjs +0 -1077
- package/dist/chunks/mcp-server.mjs +0 -776
- package/dist/chunks/project-detector.mjs +0 -131
- package/dist/chunks/provider-registry.mjs +0 -92
- package/dist/chunks/setup-wizard.mjs +0 -362
- package/dist/chunks/tools.mjs +0 -143
- package/dist/chunks/workflows2.mjs +0 -232
- package/dist/shared/ccjk.C0pb50xH.mjs +0 -347
- package/dist/shared/ccjk.ChMkBmdL.mjs +0 -490
- package/dist/shared/ccjk.CtSfXUSh.mjs +0 -209
- package/dist/shared/ccjk.xfAjmbJp.mjs +0 -75
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
5
|
-
import { EventEmitter } from 'node:events';
|
|
6
|
-
import chokidar from 'chokidar';
|
|
7
|
-
import process__default from 'node:process';
|
|
8
|
-
import ansis from 'ansis';
|
|
9
|
-
import inquirer from 'inquirer';
|
|
10
1
|
import { exec, execSync, spawn } from 'node:child_process';
|
|
11
2
|
import { promises, existsSync, readdirSync, readFileSync, statSync, createReadStream } from 'node:fs';
|
|
12
3
|
import * as os from 'node:os';
|
|
13
4
|
import { homedir } from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
14
6
|
import { promisify } from 'node:util';
|
|
7
|
+
import ansis from 'ansis';
|
|
15
8
|
import { version } from './chunks/package.mjs';
|
|
16
9
|
import { join as join$1 } from 'pathe';
|
|
17
10
|
export { c as config } from './chunks/config.mjs';
|
|
@@ -21,2716 +14,18 @@ import { Transform } from 'node:stream';
|
|
|
21
14
|
import { pipeline } from 'node:stream/promises';
|
|
22
15
|
import 'node:url';
|
|
23
16
|
import 'dayjs';
|
|
17
|
+
import 'inquirer';
|
|
24
18
|
import './chunks/constants.mjs';
|
|
25
19
|
import './chunks/index.mjs';
|
|
20
|
+
import 'node:process';
|
|
26
21
|
import 'i18next';
|
|
27
|
-
import 'i18next-fs-backend';
|
|
28
|
-
import './chunks/claude-config.mjs';
|
|
29
|
-
import './chunks/json-config.mjs';
|
|
30
|
-
import './chunks/fs-operations.mjs';
|
|
31
|
-
import '
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
cache;
|
|
35
|
-
head = null;
|
|
36
|
-
tail = null;
|
|
37
|
-
maxSize;
|
|
38
|
-
ttl;
|
|
39
|
-
hits = 0;
|
|
40
|
-
misses = 0;
|
|
41
|
-
constructor(maxSize = 1e3) {
|
|
42
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
43
|
-
this.maxSize = maxSize;
|
|
44
|
-
this.ttl = 1e3 * 60 * 30;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Get entry from cache
|
|
48
|
-
*/
|
|
49
|
-
get(key) {
|
|
50
|
-
const node = this.cache.get(key);
|
|
51
|
-
if (!node) {
|
|
52
|
-
this.misses++;
|
|
53
|
-
return void 0;
|
|
54
|
-
}
|
|
55
|
-
if (Date.now() - node.timestamp > this.ttl) {
|
|
56
|
-
this.delete(key);
|
|
57
|
-
this.misses++;
|
|
58
|
-
return void 0;
|
|
59
|
-
}
|
|
60
|
-
this.moveToFront(node);
|
|
61
|
-
this.hits++;
|
|
62
|
-
return node.value;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Set entry in cache
|
|
66
|
-
*/
|
|
67
|
-
set(key, entry) {
|
|
68
|
-
const existing = this.cache.get(key);
|
|
69
|
-
if (existing) {
|
|
70
|
-
existing.value = entry;
|
|
71
|
-
existing.timestamp = Date.now();
|
|
72
|
-
this.moveToFront(existing);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const node = {
|
|
76
|
-
key,
|
|
77
|
-
value: entry,
|
|
78
|
-
timestamp: Date.now(),
|
|
79
|
-
prev: null,
|
|
80
|
-
next: null
|
|
81
|
-
};
|
|
82
|
-
this.cache.set(key, node);
|
|
83
|
-
this.addToFront(node);
|
|
84
|
-
if (this.cache.size > this.maxSize) {
|
|
85
|
-
this.evictLRU();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Check if key exists
|
|
90
|
-
*/
|
|
91
|
-
has(key) {
|
|
92
|
-
const node = this.cache.get(key);
|
|
93
|
-
if (!node) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
if (Date.now() - node.timestamp > this.ttl) {
|
|
97
|
-
this.delete(key);
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Delete entry from cache
|
|
104
|
-
*/
|
|
105
|
-
delete(key) {
|
|
106
|
-
const node = this.cache.get(key);
|
|
107
|
-
if (!node) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
this.removeNode(node);
|
|
111
|
-
return this.cache.delete(key);
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Clear all entries
|
|
115
|
-
*/
|
|
116
|
-
clear() {
|
|
117
|
-
this.cache.clear();
|
|
118
|
-
this.head = null;
|
|
119
|
-
this.tail = null;
|
|
120
|
-
this.hits = 0;
|
|
121
|
-
this.misses = 0;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Get cache statistics
|
|
125
|
-
*/
|
|
126
|
-
getStats() {
|
|
127
|
-
const total = this.hits + this.misses;
|
|
128
|
-
const hitRate = total > 0 ? this.hits / total : 0;
|
|
129
|
-
return {
|
|
130
|
-
hits: this.hits,
|
|
131
|
-
misses: this.misses,
|
|
132
|
-
size: this.cache.size,
|
|
133
|
-
hitRate
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Get cache size
|
|
138
|
-
*/
|
|
139
|
-
getSize() {
|
|
140
|
-
return this.cache.size;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Check if cache is empty
|
|
144
|
-
*/
|
|
145
|
-
isEmpty() {
|
|
146
|
-
return this.cache.size === 0;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Get all keys
|
|
150
|
-
*/
|
|
151
|
-
keys() {
|
|
152
|
-
return Array.from(this.cache.keys());
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Get all values
|
|
156
|
-
*/
|
|
157
|
-
values() {
|
|
158
|
-
return Array.from(this.cache.values()).map((node) => node.value);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Dump cache contents (for debugging)
|
|
162
|
-
*/
|
|
163
|
-
dump() {
|
|
164
|
-
const map = /* @__PURE__ */ new Map();
|
|
165
|
-
const entries = Array.from(this.cache.entries());
|
|
166
|
-
for (const [key, node] of entries) {
|
|
167
|
-
map.set(key, node.value);
|
|
168
|
-
}
|
|
169
|
-
return map;
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Load multiple entries into cache
|
|
173
|
-
*/
|
|
174
|
-
loadMany(entries) {
|
|
175
|
-
for (const [key, entry] of entries) {
|
|
176
|
-
this.set(key, entry);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Delete multiple entries
|
|
181
|
-
*/
|
|
182
|
-
deleteMany(keys) {
|
|
183
|
-
let deleted = 0;
|
|
184
|
-
for (const key of keys) {
|
|
185
|
-
if (this.cache.delete(key)) {
|
|
186
|
-
deleted++;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return deleted;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Reset statistics
|
|
193
|
-
*/
|
|
194
|
-
resetStats() {
|
|
195
|
-
this.hits = 0;
|
|
196
|
-
this.misses = 0;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Get estimated memory usage
|
|
200
|
-
*/
|
|
201
|
-
getEstimatedSize() {
|
|
202
|
-
let size = 0;
|
|
203
|
-
const entries = Array.from(this.cache.entries());
|
|
204
|
-
for (const [key, node] of entries) {
|
|
205
|
-
size += key.length * 2;
|
|
206
|
-
size += JSON.stringify(node.value).length * 2;
|
|
207
|
-
}
|
|
208
|
-
return size;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Prune cache (remove expired entries)
|
|
212
|
-
*/
|
|
213
|
-
prune() {
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Set cache size limit
|
|
217
|
-
*/
|
|
218
|
-
setMaxSize(maxSize) {
|
|
219
|
-
this.maxSize = maxSize;
|
|
220
|
-
while (this.cache.size > this.maxSize) {
|
|
221
|
-
this.evictLRU();
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Get cache configuration
|
|
226
|
-
*/
|
|
227
|
-
getConfig() {
|
|
228
|
-
return {
|
|
229
|
-
maxSize: this.maxSize,
|
|
230
|
-
ttl: this.ttl
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Move node to front of list
|
|
235
|
-
*/
|
|
236
|
-
moveToFront(node) {
|
|
237
|
-
if (node === this.head) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
this.removeNode(node);
|
|
241
|
-
this.addToFront(node);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Add node to front of list
|
|
245
|
-
*/
|
|
246
|
-
addToFront(node) {
|
|
247
|
-
node.next = this.head;
|
|
248
|
-
node.prev = null;
|
|
249
|
-
if (this.head) {
|
|
250
|
-
this.head.prev = node;
|
|
251
|
-
}
|
|
252
|
-
this.head = node;
|
|
253
|
-
if (!this.tail) {
|
|
254
|
-
this.tail = node;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Remove node from list
|
|
259
|
-
*/
|
|
260
|
-
removeNode(node) {
|
|
261
|
-
if (node.prev) {
|
|
262
|
-
node.prev.next = node.next;
|
|
263
|
-
} else {
|
|
264
|
-
this.head = node.next;
|
|
265
|
-
}
|
|
266
|
-
if (node.next) {
|
|
267
|
-
node.next.prev = node.prev;
|
|
268
|
-
} else {
|
|
269
|
-
this.tail = node.prev;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Evict least recently used entry
|
|
274
|
-
*/
|
|
275
|
-
evictLRU() {
|
|
276
|
-
if (!this.tail) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
this.cache.delete(this.tail.key);
|
|
280
|
-
this.removeNode(this.tail);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
let globalCache = null;
|
|
284
|
-
function getGlobalCache(maxSize = 1e3) {
|
|
285
|
-
if (!globalCache) {
|
|
286
|
-
globalCache = new MemoryCache(maxSize);
|
|
287
|
-
}
|
|
288
|
-
return globalCache;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
class LevelDBStorage {
|
|
292
|
-
dbPath;
|
|
293
|
-
compressionEnabled;
|
|
294
|
-
cache;
|
|
295
|
-
initialized = false;
|
|
296
|
-
constructor(dbPath, compressionEnabled = true) {
|
|
297
|
-
this.dbPath = dbPath;
|
|
298
|
-
this.compressionEnabled = compressionEnabled;
|
|
299
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Initialize storage
|
|
303
|
-
*/
|
|
304
|
-
async initialize() {
|
|
305
|
-
if (this.initialized) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
try {
|
|
309
|
-
await fs.mkdir(this.dbPath, { recursive: true });
|
|
310
|
-
await this.loadFromDisk();
|
|
311
|
-
this.initialized = true;
|
|
312
|
-
} catch (error) {
|
|
313
|
-
console.error("Failed to initialize storage:", error);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Load data from disk
|
|
318
|
-
*/
|
|
319
|
-
async loadFromDisk() {
|
|
320
|
-
try {
|
|
321
|
-
const files = await fs.readdir(this.dbPath);
|
|
322
|
-
for (const file of files) {
|
|
323
|
-
if (file.endsWith(".json")) {
|
|
324
|
-
const filePath = path.join(this.dbPath, file);
|
|
325
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
326
|
-
const entry = JSON.parse(content);
|
|
327
|
-
this.cache.set(entry.key, entry);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
} catch (_error) {
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Get file path for a key
|
|
335
|
-
*/
|
|
336
|
-
getFilePath(key) {
|
|
337
|
-
const hash = createHash("md5").update(key).digest("hex");
|
|
338
|
-
return path.join(this.dbPath, `${hash}.json`);
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Store a cache entry
|
|
342
|
-
*/
|
|
343
|
-
async put(entry) {
|
|
344
|
-
await this.initialize();
|
|
345
|
-
const dataToStore = { ...entry };
|
|
346
|
-
if (this.compressionEnabled && shouldCompress(entry.type)) {
|
|
347
|
-
dataToStore.data = compressData(entry.data);
|
|
348
|
-
dataToStore.compressed = true;
|
|
349
|
-
}
|
|
350
|
-
this.cache.set(entry.key, dataToStore);
|
|
351
|
-
const filePath = this.getFilePath(entry.key);
|
|
352
|
-
await fs.writeFile(filePath, JSON.stringify(dataToStore, null, 2), "utf-8");
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Retrieve a cache entry
|
|
356
|
-
*/
|
|
357
|
-
async get(key) {
|
|
358
|
-
await this.initialize();
|
|
359
|
-
const entry = this.cache.get(key);
|
|
360
|
-
if (!entry) {
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
if (entry.compressed) {
|
|
364
|
-
const decompressed = { ...entry };
|
|
365
|
-
decompressed.data = decompressData(entry.data);
|
|
366
|
-
decompressed.compressed = false;
|
|
367
|
-
return decompressed;
|
|
368
|
-
}
|
|
369
|
-
return entry;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Delete a cache entry
|
|
373
|
-
*/
|
|
374
|
-
async del(key) {
|
|
375
|
-
await this.initialize();
|
|
376
|
-
this.cache.delete(key);
|
|
377
|
-
const filePath = this.getFilePath(key);
|
|
378
|
-
try {
|
|
379
|
-
await fs.unlink(filePath);
|
|
380
|
-
} catch (_error) {
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Check if key exists
|
|
385
|
-
*/
|
|
386
|
-
async has(key) {
|
|
387
|
-
await this.initialize();
|
|
388
|
-
return this.cache.has(key);
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Get all entries matching a prefix
|
|
392
|
-
*/
|
|
393
|
-
async getByPrefix(prefix) {
|
|
394
|
-
await this.initialize();
|
|
395
|
-
const entries = [];
|
|
396
|
-
const cacheEntries = Array.from(this.cache.entries());
|
|
397
|
-
for (const [key, entry] of cacheEntries) {
|
|
398
|
-
if (key.startsWith(prefix)) {
|
|
399
|
-
if (entry.compressed) {
|
|
400
|
-
const decompressed = { ...entry };
|
|
401
|
-
decompressed.data = decompressData(entry.data);
|
|
402
|
-
decompressed.compressed = false;
|
|
403
|
-
entries.push(decompressed);
|
|
404
|
-
} else {
|
|
405
|
-
entries.push(entry);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return entries;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Batch write multiple entries
|
|
413
|
-
*/
|
|
414
|
-
async batch(entries) {
|
|
415
|
-
await this.initialize();
|
|
416
|
-
await Promise.all(entries.map((entry) => this.put(entry)));
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Clear all entries
|
|
420
|
-
*/
|
|
421
|
-
async clear() {
|
|
422
|
-
await this.initialize();
|
|
423
|
-
this.cache.clear();
|
|
424
|
-
try {
|
|
425
|
-
const files = await fs.readdir(this.dbPath);
|
|
426
|
-
await Promise.all(
|
|
427
|
-
files.filter((file) => file.endsWith(".json")).map((file) => fs.unlink(path.join(this.dbPath, file)))
|
|
428
|
-
);
|
|
429
|
-
} catch (_error) {
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Close database connection
|
|
434
|
-
*/
|
|
435
|
-
async close() {
|
|
436
|
-
this.initialized = false;
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Get approximate database size
|
|
440
|
-
*/
|
|
441
|
-
async getSize() {
|
|
442
|
-
await this.initialize();
|
|
443
|
-
return this.cache.size;
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Compact database
|
|
447
|
-
*/
|
|
448
|
-
async compact() {
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Generate checksum for data
|
|
452
|
-
*/
|
|
453
|
-
generateChecksum(data) {
|
|
454
|
-
const json = JSON.stringify(data);
|
|
455
|
-
return createHash("sha256").update(json).digest("hex");
|
|
456
|
-
}
|
|
457
|
-
/**
|
|
458
|
-
* Verify checksum
|
|
459
|
-
*/
|
|
460
|
-
verifyChecksum(entry) {
|
|
461
|
-
const calculated = this.generateChecksum(entry.data);
|
|
462
|
-
return calculated === entry.checksum;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
function shouldCompress(type) {
|
|
466
|
-
return type === "ast" || type === "call-graph";
|
|
467
|
-
}
|
|
468
|
-
function compressData(data) {
|
|
469
|
-
const json = JSON.stringify(data);
|
|
470
|
-
const compressed = gzipSync(Buffer.from(json, "utf-8"));
|
|
471
|
-
return compressed.toString("base64");
|
|
472
|
-
}
|
|
473
|
-
function decompressData(compressedData) {
|
|
474
|
-
const buffer = Buffer.from(compressedData, "base64");
|
|
475
|
-
const decompressed = gunzipSync(buffer);
|
|
476
|
-
return JSON.parse(decompressed.toString("utf-8"));
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
class MultiLevelIndex {
|
|
480
|
-
l1;
|
|
481
|
-
l2;
|
|
482
|
-
config;
|
|
483
|
-
constructor(dbPath, config) {
|
|
484
|
-
this.l1 = getGlobalCache(config?.l1?.maxSize || 1e3);
|
|
485
|
-
this.l2 = new LevelDBStorage(dbPath);
|
|
486
|
-
this.config = {
|
|
487
|
-
l1: {
|
|
488
|
-
name: "L1-Memory",
|
|
489
|
-
maxSize: config?.l1?.maxSize || 1e3,
|
|
490
|
-
ttl: config?.l1?.ttl || 1e3 * 60 * 30
|
|
491
|
-
// 30 minutes
|
|
492
|
-
},
|
|
493
|
-
l2: {
|
|
494
|
-
name: "L2-Disk",
|
|
495
|
-
maxSize: config?.l2?.maxSize || 1e5,
|
|
496
|
-
ttl: config?.l2?.ttl || 1e3 * 60 * 60 * 24 * 7
|
|
497
|
-
// 7 days
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Get entry from cache (L1 first, then L2)
|
|
503
|
-
*/
|
|
504
|
-
async get(key) {
|
|
505
|
-
const l1Entry = this.l1.get(key);
|
|
506
|
-
if (l1Entry) {
|
|
507
|
-
l1Entry.accessCount++;
|
|
508
|
-
l1Entry.lastAccess = Date.now();
|
|
509
|
-
l1Entry.level = 1;
|
|
510
|
-
return l1Entry;
|
|
511
|
-
}
|
|
512
|
-
const l2Entry = await this.l2.get(key);
|
|
513
|
-
if (l2Entry) {
|
|
514
|
-
if (l2Entry.accessCount > 5) {
|
|
515
|
-
await this.promoteToL1(key, l2Entry);
|
|
516
|
-
}
|
|
517
|
-
l2Entry.accessCount++;
|
|
518
|
-
l2Entry.lastAccess = Date.now();
|
|
519
|
-
l2Entry.level = 2;
|
|
520
|
-
return l2Entry;
|
|
521
|
-
}
|
|
522
|
-
return null;
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* Set entry in cache (writes to both levels)
|
|
526
|
-
*/
|
|
527
|
-
async set(key, entry) {
|
|
528
|
-
const indexedEntry = {
|
|
529
|
-
...entry,
|
|
530
|
-
accessCount: 0,
|
|
531
|
-
lastAccess: Date.now(),
|
|
532
|
-
level: 1
|
|
533
|
-
};
|
|
534
|
-
this.l1.set(key, indexedEntry);
|
|
535
|
-
await this.l2.put(indexedEntry);
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Delete entry from both levels
|
|
539
|
-
*/
|
|
540
|
-
async delete(key) {
|
|
541
|
-
this.l1.delete(key);
|
|
542
|
-
await this.l2.del(key);
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Check if key exists
|
|
546
|
-
*/
|
|
547
|
-
async has(key) {
|
|
548
|
-
if (this.l1.has(key)) {
|
|
549
|
-
return true;
|
|
550
|
-
}
|
|
551
|
-
return await this.l2.has(key);
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* Get all entries for a file path
|
|
555
|
-
*/
|
|
556
|
-
async getByFilePath(filePath) {
|
|
557
|
-
const prefix = `${filePath}|`;
|
|
558
|
-
const l1Entries = [];
|
|
559
|
-
for (const key of this.l1.keys()) {
|
|
560
|
-
if (key.startsWith(prefix)) {
|
|
561
|
-
const entry = this.l1.get(key);
|
|
562
|
-
if (entry) {
|
|
563
|
-
l1Entries.push(entry);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
const l2Entries = await this.l2.getByPrefix(prefix);
|
|
568
|
-
return [...l1Entries, ...l2Entries];
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Promote entry to L1 cache
|
|
572
|
-
*/
|
|
573
|
-
async promoteToL1(key, entry) {
|
|
574
|
-
entry.level = 1;
|
|
575
|
-
this.l1.set(key, entry);
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Demote entry from L1 to L2
|
|
579
|
-
*/
|
|
580
|
-
async demoteToL2(key) {
|
|
581
|
-
const entry = this.l1.get(key);
|
|
582
|
-
if (entry) {
|
|
583
|
-
entry.level = 2;
|
|
584
|
-
await this.l2.put(entry);
|
|
585
|
-
this.l1.delete(key);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Get combined statistics
|
|
590
|
-
*/
|
|
591
|
-
async getStats() {
|
|
592
|
-
const l1Stats = this.l1.getStats();
|
|
593
|
-
const l2Size = await this.l2.getSize();
|
|
594
|
-
const totalHits = l1Stats.hits;
|
|
595
|
-
const totalMisses = l1Stats.misses;
|
|
596
|
-
const combinedHitRate = totalHits + totalMisses > 0 ? totalHits / (totalHits + totalMisses) : 0;
|
|
597
|
-
return {
|
|
598
|
-
l1: l1Stats,
|
|
599
|
-
l2: { size: l2Size },
|
|
600
|
-
combined: {
|
|
601
|
-
hitRate: combinedHitRate
|
|
602
|
-
}
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Clear all caches
|
|
607
|
-
*/
|
|
608
|
-
async clear() {
|
|
609
|
-
this.l1.clear();
|
|
610
|
-
await this.l2.clear();
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Warm up L1 cache with frequently accessed entries
|
|
614
|
-
*/
|
|
615
|
-
async warmup(filePath) {
|
|
616
|
-
const entries = await this.getByFilePath(filePath);
|
|
617
|
-
entries.sort((a, b) => b.accessCount - a.accessCount).slice(0, 100).forEach(async (entry) => {
|
|
618
|
-
await this.promoteToL1(`${filePath}|${entry.type}`, entry);
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Evict stale entries from L1
|
|
623
|
-
*/
|
|
624
|
-
async evictStale() {
|
|
625
|
-
const keys = this.l1.keys();
|
|
626
|
-
const now = Date.now();
|
|
627
|
-
for (const key of keys) {
|
|
628
|
-
const entry = this.l1.get(key);
|
|
629
|
-
if (entry && now - entry.lastAccess > this.config.l1.ttl) {
|
|
630
|
-
await this.demoteToL2(key);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Close cache system
|
|
636
|
-
*/
|
|
637
|
-
async close() {
|
|
638
|
-
await this.l2.close();
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Compact L2 storage
|
|
642
|
-
*/
|
|
643
|
-
async compact() {
|
|
644
|
-
await this.l2.compact();
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Get cache configuration
|
|
648
|
-
*/
|
|
649
|
-
getConfig() {
|
|
650
|
-
return { ...this.config };
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Update cache configuration
|
|
654
|
-
*/
|
|
655
|
-
updateConfig(config) {
|
|
656
|
-
if (config.l1) {
|
|
657
|
-
this.config.l1 = { ...this.config.l1, ...config.l1 };
|
|
658
|
-
if (config.l1.maxSize) {
|
|
659
|
-
this.l1.setMaxSize(config.l1.maxSize);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
if (config.l2) {
|
|
663
|
-
this.config.l2 = { ...this.config.l2, ...config.l2 };
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
let globalIndex = null;
|
|
668
|
-
function getGlobalIndex(dbPath = "./actionbook-cache") {
|
|
669
|
-
if (!globalIndex) {
|
|
670
|
-
globalIndex = new MultiLevelIndex(dbPath);
|
|
671
|
-
}
|
|
672
|
-
return globalIndex;
|
|
673
|
-
}
|
|
674
|
-
async function closeGlobalIndex() {
|
|
675
|
-
if (globalIndex) {
|
|
676
|
-
await globalIndex.close();
|
|
677
|
-
globalIndex = null;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
async function queryAST(filePath, offset) {
|
|
682
|
-
const index = getGlobalIndex();
|
|
683
|
-
const key = offset ? `${filePath}|ast|${offset}` : `${filePath}|ast`;
|
|
684
|
-
const entry = await index.get(key);
|
|
685
|
-
if (!entry) {
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
return entry.data;
|
|
689
|
-
}
|
|
690
|
-
async function queryASTAtPosition(filePath, line, column) {
|
|
691
|
-
const ast = await queryAST(filePath);
|
|
692
|
-
if (!ast) {
|
|
693
|
-
return null;
|
|
694
|
-
}
|
|
695
|
-
return findNodeAtPosition(ast, line, column);
|
|
696
|
-
}
|
|
697
|
-
function findNodeAtPosition(node, line, column) {
|
|
698
|
-
const isBefore = line < node.range.start.line || line === node.range.start.line && column < node.range.start.column;
|
|
699
|
-
const isAfter = line > node.range.end.line || line === node.range.end.line && column > node.range.end.column;
|
|
700
|
-
if (isBefore || isAfter) {
|
|
701
|
-
return null;
|
|
702
|
-
}
|
|
703
|
-
for (const child of node.children) {
|
|
704
|
-
const found = findNodeAtPosition(child, line, column);
|
|
705
|
-
if (found) {
|
|
706
|
-
return found;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
return node;
|
|
710
|
-
}
|
|
711
|
-
async function queryASTByType(filePath, nodeType) {
|
|
712
|
-
const ast = await queryAST(filePath);
|
|
713
|
-
if (!ast) {
|
|
714
|
-
return [];
|
|
715
|
-
}
|
|
716
|
-
return findNodesByType(ast, nodeType);
|
|
717
|
-
}
|
|
718
|
-
function findNodesByType(node, nodeType) {
|
|
719
|
-
const results = [];
|
|
720
|
-
if (node.type === nodeType) {
|
|
721
|
-
results.push(node);
|
|
722
|
-
}
|
|
723
|
-
for (const child of node.children) {
|
|
724
|
-
results.push(...findNodesByType(child, nodeType));
|
|
725
|
-
}
|
|
726
|
-
return results;
|
|
727
|
-
}
|
|
728
|
-
async function queryASTByName(filePath, name) {
|
|
729
|
-
const ast = await queryAST(filePath);
|
|
730
|
-
if (!ast) {
|
|
731
|
-
return [];
|
|
732
|
-
}
|
|
733
|
-
return findNodesByName(ast, name);
|
|
734
|
-
}
|
|
735
|
-
function findNodesByName(node, name) {
|
|
736
|
-
const results = [];
|
|
737
|
-
if (node.name === name) {
|
|
738
|
-
results.push(node);
|
|
739
|
-
}
|
|
740
|
-
for (const child of node.children) {
|
|
741
|
-
results.push(...findNodesByName(child, name));
|
|
742
|
-
}
|
|
743
|
-
return results;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
const astQueries = {
|
|
747
|
-
__proto__: null,
|
|
748
|
-
queryAST: queryAST,
|
|
749
|
-
queryASTAtPosition: queryASTAtPosition,
|
|
750
|
-
queryASTByName: queryASTByName,
|
|
751
|
-
queryASTByType: queryASTByType
|
|
752
|
-
};
|
|
753
|
-
|
|
754
|
-
async function queryCallGraph(filePath) {
|
|
755
|
-
const index = getGlobalIndex();
|
|
756
|
-
const key = `${filePath}|call-graph`;
|
|
757
|
-
const entry = await index.get(key);
|
|
758
|
-
if (!entry) {
|
|
759
|
-
return null;
|
|
760
|
-
}
|
|
761
|
-
return entry.data;
|
|
762
|
-
}
|
|
763
|
-
async function queryEntryPoints(filePath) {
|
|
764
|
-
const callGraph = await queryCallGraph(filePath);
|
|
765
|
-
if (!callGraph) {
|
|
766
|
-
return [];
|
|
767
|
-
}
|
|
768
|
-
const entryNodes = callGraph.nodes.filter(
|
|
769
|
-
(node) => callGraph.entryPoints.includes(node.id)
|
|
770
|
-
);
|
|
771
|
-
return entryNodes;
|
|
772
|
-
}
|
|
773
|
-
async function queryFunction(filePath, name) {
|
|
774
|
-
const callGraph = await queryCallGraph(filePath);
|
|
775
|
-
if (!callGraph) {
|
|
776
|
-
return null;
|
|
777
|
-
}
|
|
778
|
-
return callGraph.nodes.find((node) => node.name === name) || null;
|
|
779
|
-
}
|
|
780
|
-
async function queryCallers(filePath, functionName) {
|
|
781
|
-
const callGraph = await queryCallGraph(filePath);
|
|
782
|
-
if (!callGraph) {
|
|
783
|
-
return [];
|
|
784
|
-
}
|
|
785
|
-
const targetNode = callGraph.nodes.find((n) => n.name === functionName);
|
|
786
|
-
if (!targetNode) {
|
|
787
|
-
return [];
|
|
788
|
-
}
|
|
789
|
-
return callGraph.edges.filter((edge) => edge.to === targetNode.id);
|
|
790
|
-
}
|
|
791
|
-
async function queryCallees(filePath, functionName) {
|
|
792
|
-
const callGraph = await queryCallGraph(filePath);
|
|
793
|
-
if (!callGraph) {
|
|
794
|
-
return [];
|
|
795
|
-
}
|
|
796
|
-
const sourceNode = callGraph.nodes.find((n) => n.name === functionName);
|
|
797
|
-
if (!sourceNode) {
|
|
798
|
-
return [];
|
|
799
|
-
}
|
|
800
|
-
return callGraph.edges.filter((edge) => edge.from === sourceNode.id);
|
|
801
|
-
}
|
|
802
|
-
async function queryCallChain(filePath, from, to) {
|
|
803
|
-
const callGraph = await queryCallGraph(filePath);
|
|
804
|
-
if (!callGraph) {
|
|
805
|
-
return [];
|
|
806
|
-
}
|
|
807
|
-
const fromNode = callGraph.nodes.find((n) => n.name === from);
|
|
808
|
-
const toNode = callGraph.nodes.find((n) => n.name === to);
|
|
809
|
-
if (!fromNode || !toNode) {
|
|
810
|
-
return [];
|
|
811
|
-
}
|
|
812
|
-
const queue = [
|
|
813
|
-
{ node: fromNode.id, path: [fromNode.id] }
|
|
814
|
-
];
|
|
815
|
-
const visited = /* @__PURE__ */ new Set([fromNode.id]);
|
|
816
|
-
while (queue.length > 0) {
|
|
817
|
-
const { node, path } = queue.shift();
|
|
818
|
-
if (node === toNode.id) {
|
|
819
|
-
return path.map((id) => callGraph.nodes.find((n) => n.id === id).name);
|
|
820
|
-
}
|
|
821
|
-
const edges = callGraph.edges.filter((e) => e.from === node);
|
|
822
|
-
for (const edge of edges) {
|
|
823
|
-
if (!visited.has(edge.to)) {
|
|
824
|
-
visited.add(edge.to);
|
|
825
|
-
queue.push({ node: edge.to, path: [...path, edge.to] });
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
return [];
|
|
830
|
-
}
|
|
831
|
-
async function queryRecursiveFunctions(filePath) {
|
|
832
|
-
const callGraph = await queryCallGraph(filePath);
|
|
833
|
-
if (!callGraph) {
|
|
834
|
-
return [];
|
|
835
|
-
}
|
|
836
|
-
const recursiveNodes = [];
|
|
837
|
-
for (const node of callGraph.nodes) {
|
|
838
|
-
const edges = callGraph.edges.filter((e) => e.from === node.id);
|
|
839
|
-
if (edges.some((e) => e.to === node.id)) {
|
|
840
|
-
recursiveNodes.push(node);
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
return recursiveNodes;
|
|
844
|
-
}
|
|
845
|
-
async function queryCallFrequency(filePath) {
|
|
846
|
-
const callGraph = await queryCallGraph(filePath);
|
|
847
|
-
if (!callGraph) {
|
|
848
|
-
return /* @__PURE__ */ new Map();
|
|
849
|
-
}
|
|
850
|
-
const frequency = /* @__PURE__ */ new Map();
|
|
851
|
-
for (const edge of callGraph.edges) {
|
|
852
|
-
const toNode = callGraph.nodes.find((n) => n.id === edge.to);
|
|
853
|
-
if (toNode) {
|
|
854
|
-
const currentCount = frequency.get(toNode.name) || 0;
|
|
855
|
-
frequency.set(toNode.name, currentCount + edge.callCount);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return frequency;
|
|
859
|
-
}
|
|
860
|
-
async function queryMostCalledFunctions(filePath, limit = 10) {
|
|
861
|
-
const frequency = await queryCallFrequency(filePath);
|
|
862
|
-
return Array.from(frequency.entries()).sort((a, b) => b[1] - a[1]).slice(0, limit).map(([name, count]) => ({ name, count }));
|
|
863
|
-
}
|
|
864
|
-
async function queryFunctionDepth(filePath, functionName) {
|
|
865
|
-
const callGraph = await queryCallGraph(filePath);
|
|
866
|
-
if (!callGraph) {
|
|
867
|
-
return 0;
|
|
868
|
-
}
|
|
869
|
-
const targetNode = callGraph.nodes.find((n) => n.name === functionName);
|
|
870
|
-
if (!targetNode) {
|
|
871
|
-
return 0;
|
|
872
|
-
}
|
|
873
|
-
const queue = callGraph.entryPoints.map((id) => ({
|
|
874
|
-
node: id,
|
|
875
|
-
depth: 0
|
|
876
|
-
}));
|
|
877
|
-
const visited = new Set(callGraph.entryPoints);
|
|
878
|
-
while (queue.length > 0) {
|
|
879
|
-
const { node, depth } = queue.shift();
|
|
880
|
-
if (node === targetNode.id) {
|
|
881
|
-
return depth;
|
|
882
|
-
}
|
|
883
|
-
const edges = callGraph.edges.filter((e) => e.from === node);
|
|
884
|
-
for (const edge of edges) {
|
|
885
|
-
if (!visited.has(edge.to)) {
|
|
886
|
-
visited.add(edge.to);
|
|
887
|
-
queue.push({ node: edge.to, depth: depth + 1 });
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
return 0;
|
|
892
|
-
}
|
|
893
|
-
async function queryDynamicCalls(filePath) {
|
|
894
|
-
const callGraph = await queryCallGraph(filePath);
|
|
895
|
-
if (!callGraph) {
|
|
896
|
-
return [];
|
|
897
|
-
}
|
|
898
|
-
return callGraph.edges.filter((edge) => edge.isDynamic);
|
|
899
|
-
}
|
|
900
|
-
async function queryAsyncFunctions(filePath) {
|
|
901
|
-
const callGraph = await queryCallGraph(filePath);
|
|
902
|
-
if (!callGraph) {
|
|
903
|
-
return [];
|
|
904
|
-
}
|
|
905
|
-
return callGraph.nodes.filter((node) => node.isAsync);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
const callGraphQueries = {
|
|
909
|
-
__proto__: null,
|
|
910
|
-
queryAsyncFunctions: queryAsyncFunctions,
|
|
911
|
-
queryCallChain: queryCallChain,
|
|
912
|
-
queryCallFrequency: queryCallFrequency,
|
|
913
|
-
queryCallGraph: queryCallGraph,
|
|
914
|
-
queryCallees: queryCallees,
|
|
915
|
-
queryCallers: queryCallers,
|
|
916
|
-
queryDynamicCalls: queryDynamicCalls,
|
|
917
|
-
queryEntryPoints: queryEntryPoints,
|
|
918
|
-
queryFunction: queryFunction,
|
|
919
|
-
queryFunctionDepth: queryFunctionDepth,
|
|
920
|
-
queryMostCalledFunctions: queryMostCalledFunctions,
|
|
921
|
-
queryRecursiveFunctions: queryRecursiveFunctions
|
|
922
|
-
};
|
|
923
|
-
|
|
924
|
-
async function querySymbols(filePath) {
|
|
925
|
-
const index = getGlobalIndex();
|
|
926
|
-
const key = `${filePath}|symbol`;
|
|
927
|
-
const entry = await index.get(key);
|
|
928
|
-
if (!entry) {
|
|
929
|
-
return null;
|
|
930
|
-
}
|
|
931
|
-
return entry.data;
|
|
932
|
-
}
|
|
933
|
-
async function queryExports(filePath) {
|
|
934
|
-
const symbolTable = await querySymbols(filePath);
|
|
935
|
-
if (!symbolTable) {
|
|
936
|
-
return [];
|
|
937
|
-
}
|
|
938
|
-
return symbolTable.exports;
|
|
939
|
-
}
|
|
940
|
-
async function queryImports(filePath) {
|
|
941
|
-
const symbolTable = await querySymbols(filePath);
|
|
942
|
-
if (!symbolTable) {
|
|
943
|
-
return [];
|
|
944
|
-
}
|
|
945
|
-
return symbolTable.imports;
|
|
946
|
-
}
|
|
947
|
-
async function querySymbolByName(filePath, name) {
|
|
948
|
-
const symbolTable = await querySymbols(filePath);
|
|
949
|
-
if (!symbolTable) {
|
|
950
|
-
return null;
|
|
951
|
-
}
|
|
952
|
-
return symbolTable.symbols.find((s) => s.name === name) || null;
|
|
953
|
-
}
|
|
954
|
-
async function querySymbolsByKind(filePath, kind) {
|
|
955
|
-
const symbolTable = await querySymbols(filePath);
|
|
956
|
-
if (!symbolTable) {
|
|
957
|
-
return [];
|
|
958
|
-
}
|
|
959
|
-
return symbolTable.symbols.filter((s) => s.kind === kind);
|
|
960
|
-
}
|
|
961
|
-
async function querySymbolAtPosition(filePath, line, column) {
|
|
962
|
-
const symbolTable = await querySymbols(filePath);
|
|
963
|
-
if (!symbolTable) {
|
|
964
|
-
return null;
|
|
965
|
-
}
|
|
966
|
-
return symbolTable.symbols.find((symbol) => {
|
|
967
|
-
const isBefore = line < symbol.range.start.line || line === symbol.range.start.line && column < symbol.range.start.column;
|
|
968
|
-
const isAfter = line > symbol.range.end.line || line === symbol.range.end.line && column > symbol.range.end.column;
|
|
969
|
-
return !isBefore && !isAfter;
|
|
970
|
-
}) || null;
|
|
971
|
-
}
|
|
972
|
-
async function queryFunctions(filePath) {
|
|
973
|
-
return querySymbolsByKind(filePath, "function");
|
|
974
|
-
}
|
|
975
|
-
async function queryClasses(filePath) {
|
|
976
|
-
return querySymbolsByKind(filePath, "class");
|
|
977
|
-
}
|
|
978
|
-
async function queryInterfaces(filePath) {
|
|
979
|
-
return querySymbolsByKind(filePath, "interface");
|
|
980
|
-
}
|
|
981
|
-
async function queryTypes(filePath) {
|
|
982
|
-
return querySymbolsByKind(filePath, "type");
|
|
983
|
-
}
|
|
984
|
-
async function queryVariables(filePath) {
|
|
985
|
-
return querySymbolsByKind(filePath, "variable");
|
|
986
|
-
}
|
|
987
|
-
async function queryScopeAtPosition(filePath, line, column) {
|
|
988
|
-
const symbolTable = await querySymbols(filePath);
|
|
989
|
-
if (!symbolTable) {
|
|
990
|
-
return null;
|
|
991
|
-
}
|
|
992
|
-
for (const scope of symbolTable.scopes) {
|
|
993
|
-
const isBefore = line < scope.range.start.line || line === scope.range.start.line && column < scope.range.start.column;
|
|
994
|
-
const isAfter = line > scope.range.end.line || line === scope.range.end.line && column > scope.range.end.column;
|
|
995
|
-
if (!isBefore && !isAfter) {
|
|
996
|
-
return scope.id;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
return null;
|
|
1000
|
-
}
|
|
1001
|
-
async function querySymbolsInScope(filePath, scopeId) {
|
|
1002
|
-
const symbolTable = await querySymbols(filePath);
|
|
1003
|
-
if (!symbolTable) {
|
|
1004
|
-
return [];
|
|
1005
|
-
}
|
|
1006
|
-
const scope = symbolTable.scopes.find((s) => s.id === scopeId);
|
|
1007
|
-
if (!scope) {
|
|
1008
|
-
return [];
|
|
1009
|
-
}
|
|
1010
|
-
return symbolTable.symbols.filter((s) => scope.symbols.includes(s.id));
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
const symbolQueries = {
|
|
1014
|
-
__proto__: null,
|
|
1015
|
-
queryClasses: queryClasses,
|
|
1016
|
-
queryExports: queryExports,
|
|
1017
|
-
queryFunctions: queryFunctions,
|
|
1018
|
-
queryImports: queryImports,
|
|
1019
|
-
queryInterfaces: queryInterfaces,
|
|
1020
|
-
queryScopeAtPosition: queryScopeAtPosition,
|
|
1021
|
-
querySymbolAtPosition: querySymbolAtPosition,
|
|
1022
|
-
querySymbolByName: querySymbolByName,
|
|
1023
|
-
querySymbols: querySymbols,
|
|
1024
|
-
querySymbolsByKind: querySymbolsByKind,
|
|
1025
|
-
querySymbolsInScope: querySymbolsInScope,
|
|
1026
|
-
queryTypes: queryTypes,
|
|
1027
|
-
queryVariables: queryVariables
|
|
1028
|
-
};
|
|
1029
|
-
|
|
1030
|
-
class QueryAPIRouter {
|
|
1031
|
-
/**
|
|
1032
|
-
* Query AST for a file
|
|
1033
|
-
*/
|
|
1034
|
-
async queryAST(filePath, offset) {
|
|
1035
|
-
return queryAST(filePath, offset);
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Query symbol table for a file
|
|
1039
|
-
*/
|
|
1040
|
-
async querySymbols(filePath) {
|
|
1041
|
-
return querySymbols(filePath);
|
|
1042
|
-
}
|
|
1043
|
-
/**
|
|
1044
|
-
* Query call graph for a file
|
|
1045
|
-
*/
|
|
1046
|
-
async queryCallGraph(filePath) {
|
|
1047
|
-
return queryCallGraph(filePath);
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Query complexity metrics for a file
|
|
1051
|
-
*/
|
|
1052
|
-
async queryComplexity(filePath) {
|
|
1053
|
-
const index = getGlobalIndex();
|
|
1054
|
-
const key = `${filePath}|complexity`;
|
|
1055
|
-
const entry = await index.get(key);
|
|
1056
|
-
if (!entry) {
|
|
1057
|
-
return null;
|
|
1058
|
-
}
|
|
1059
|
-
return entry.data;
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Query patterns for a file
|
|
1063
|
-
*/
|
|
1064
|
-
async queryPatterns(filePath) {
|
|
1065
|
-
const index = getGlobalIndex();
|
|
1066
|
-
const key = `${filePath}|patterns`;
|
|
1067
|
-
const entry = await index.get(key);
|
|
1068
|
-
if (!entry) {
|
|
1069
|
-
return null;
|
|
1070
|
-
}
|
|
1071
|
-
return entry.data;
|
|
1072
|
-
}
|
|
1073
|
-
/**
|
|
1074
|
-
* Query all precomputed data for a file
|
|
1075
|
-
*/
|
|
1076
|
-
async queryAll(filePath) {
|
|
1077
|
-
const index = getGlobalIndex();
|
|
1078
|
-
const entries = await index.getByFilePath(filePath);
|
|
1079
|
-
const result = {
|
|
1080
|
-
ast: null,
|
|
1081
|
-
symbols: null,
|
|
1082
|
-
callGraph: null,
|
|
1083
|
-
complexity: null,
|
|
1084
|
-
patterns: null
|
|
1085
|
-
};
|
|
1086
|
-
for (const entry of entries) {
|
|
1087
|
-
switch (entry.type) {
|
|
1088
|
-
case "ast":
|
|
1089
|
-
result.ast = entry.data;
|
|
1090
|
-
break;
|
|
1091
|
-
case "symbol":
|
|
1092
|
-
result.symbols = entry.data;
|
|
1093
|
-
break;
|
|
1094
|
-
case "call-graph":
|
|
1095
|
-
result.callGraph = entry.data;
|
|
1096
|
-
break;
|
|
1097
|
-
case "complexity":
|
|
1098
|
-
result.complexity = entry.data;
|
|
1099
|
-
break;
|
|
1100
|
-
case "patterns":
|
|
1101
|
-
result.patterns = entry.data;
|
|
1102
|
-
break;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
return result;
|
|
1106
|
-
}
|
|
1107
|
-
/**
|
|
1108
|
-
* Get cache statistics
|
|
1109
|
-
*/
|
|
1110
|
-
async getStats() {
|
|
1111
|
-
const index = getGlobalIndex();
|
|
1112
|
-
return index.getStats();
|
|
1113
|
-
}
|
|
1114
|
-
/**
|
|
1115
|
-
* Clear all caches
|
|
1116
|
-
*/
|
|
1117
|
-
async clear() {
|
|
1118
|
-
const index = getGlobalIndex();
|
|
1119
|
-
await index.clear();
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Warm up cache for a file
|
|
1123
|
-
*/
|
|
1124
|
-
async warmup(filePath) {
|
|
1125
|
-
const index = getGlobalIndex();
|
|
1126
|
-
await index.warmup(filePath);
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
let globalQueryAPI = null;
|
|
1130
|
-
function getQueryAPI() {
|
|
1131
|
-
if (!globalQueryAPI) {
|
|
1132
|
-
globalQueryAPI = new QueryAPIRouter();
|
|
1133
|
-
}
|
|
1134
|
-
return globalQueryAPI;
|
|
1135
|
-
}
|
|
1136
|
-
const ast = astQueries;
|
|
1137
|
-
const symbols = symbolQueries;
|
|
1138
|
-
const callGraph = callGraphQueries;
|
|
1139
|
-
|
|
1140
|
-
class DependencyTracker {
|
|
1141
|
-
graph;
|
|
1142
|
-
watchMode = false;
|
|
1143
|
-
constructor() {
|
|
1144
|
-
this.graph = {
|
|
1145
|
-
nodes: /* @__PURE__ */ new Map(),
|
|
1146
|
-
edges: /* @__PURE__ */ new Map()
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
/**
|
|
1150
|
-
* Track dependencies from symbol table
|
|
1151
|
-
*/
|
|
1152
|
-
trackDependencies(filePath, symbols) {
|
|
1153
|
-
const node = this.getOrCreateNode(filePath);
|
|
1154
|
-
node.dependencies.clear();
|
|
1155
|
-
for (const importDecl of symbols.imports) {
|
|
1156
|
-
const resolvedPath = this.resolveImportPath(filePath, importDecl.module);
|
|
1157
|
-
if (resolvedPath) {
|
|
1158
|
-
node.dependencies.add(resolvedPath);
|
|
1159
|
-
if (!this.graph.edges.has(filePath)) {
|
|
1160
|
-
this.graph.edges.set(filePath, /* @__PURE__ */ new Set());
|
|
1161
|
-
}
|
|
1162
|
-
this.graph.edges.get(filePath).add(resolvedPath);
|
|
1163
|
-
const depNode = this.getOrCreateNode(resolvedPath);
|
|
1164
|
-
depNode.dependents.add(filePath);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
node.lastIndexed = Date.now();
|
|
1168
|
-
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Get dependents of a file (files that import this file)
|
|
1171
|
-
*/
|
|
1172
|
-
getDependents(filePath) {
|
|
1173
|
-
const node = this.graph.nodes.get(filePath);
|
|
1174
|
-
return node ? new Set(node.dependents) : /* @__PURE__ */ new Set();
|
|
1175
|
-
}
|
|
1176
|
-
/**
|
|
1177
|
-
* Get dependencies of a file (files this file imports)
|
|
1178
|
-
*/
|
|
1179
|
-
getDependencies(filePath) {
|
|
1180
|
-
const node = this.graph.nodes.get(filePath);
|
|
1181
|
-
return node ? new Set(node.dependencies) : /* @__PURE__ */ new Set();
|
|
1182
|
-
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Get transitive dependents (all files that indirectly depend on this file)
|
|
1185
|
-
*/
|
|
1186
|
-
getTransitiveDependents(filePath) {
|
|
1187
|
-
const transitive = /* @__PURE__ */ new Set();
|
|
1188
|
-
const queue = [filePath];
|
|
1189
|
-
while (queue.length > 0) {
|
|
1190
|
-
const current = queue.shift();
|
|
1191
|
-
const directDependents = this.getDependents(current);
|
|
1192
|
-
const dependentsArray = Array.from(directDependents);
|
|
1193
|
-
for (const dependent of dependentsArray) {
|
|
1194
|
-
if (!transitive.has(dependent)) {
|
|
1195
|
-
transitive.add(dependent);
|
|
1196
|
-
queue.push(dependent);
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
return transitive;
|
|
1201
|
-
}
|
|
1202
|
-
/**
|
|
1203
|
-
* Get transitive dependencies (all files this file indirectly depends on)
|
|
1204
|
-
*/
|
|
1205
|
-
getTransitiveDependencies(filePath) {
|
|
1206
|
-
const transitive = /* @__PURE__ */ new Set();
|
|
1207
|
-
const queue = [filePath];
|
|
1208
|
-
const visited = /* @__PURE__ */ new Set([filePath]);
|
|
1209
|
-
while (queue.length > 0) {
|
|
1210
|
-
const current = queue.shift();
|
|
1211
|
-
const directDeps = this.getDependencies(current);
|
|
1212
|
-
const depsArray = Array.from(directDeps);
|
|
1213
|
-
for (const dep of depsArray) {
|
|
1214
|
-
if (!visited.has(dep)) {
|
|
1215
|
-
visited.add(dep);
|
|
1216
|
-
transitive.add(dep);
|
|
1217
|
-
queue.push(dep);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
return transitive;
|
|
1222
|
-
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Detect circular dependencies
|
|
1225
|
-
*/
|
|
1226
|
-
detectCircularDependencies() {
|
|
1227
|
-
const cycles = [];
|
|
1228
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1229
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
1230
|
-
const path2 = [];
|
|
1231
|
-
const dfs = (node) => {
|
|
1232
|
-
visited.add(node);
|
|
1233
|
-
recStack.add(node);
|
|
1234
|
-
path2.push(node);
|
|
1235
|
-
const neighbors = this.graph.edges.get(node) || /* @__PURE__ */ new Set();
|
|
1236
|
-
const neighborsArray = Array.from(neighbors);
|
|
1237
|
-
for (const neighbor of neighborsArray) {
|
|
1238
|
-
if (!visited.has(neighbor)) {
|
|
1239
|
-
if (dfs(neighbor)) {
|
|
1240
|
-
return true;
|
|
1241
|
-
}
|
|
1242
|
-
} else if (recStack.has(neighbor)) {
|
|
1243
|
-
const cycleStart = path2.indexOf(neighbor);
|
|
1244
|
-
cycles.push([...path2.slice(cycleStart), neighbor]);
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
path2.pop();
|
|
1248
|
-
recStack.delete(node);
|
|
1249
|
-
return false;
|
|
1250
|
-
};
|
|
1251
|
-
const nodesArray = Array.from(this.graph.nodes.keys());
|
|
1252
|
-
for (const node of nodesArray) {
|
|
1253
|
-
if (!visited.has(node)) {
|
|
1254
|
-
dfs(node);
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
return cycles;
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* Get files affected by a change (need reindexing)
|
|
1261
|
-
*/
|
|
1262
|
-
getAffectedFiles(filePath) {
|
|
1263
|
-
const affected = /* @__PURE__ */ new Set();
|
|
1264
|
-
const dependents = this.getTransitiveDependents(filePath);
|
|
1265
|
-
const dependentsArray = Array.from(dependents);
|
|
1266
|
-
for (const dependent of dependentsArray) {
|
|
1267
|
-
affected.add(dependent);
|
|
1268
|
-
}
|
|
1269
|
-
affected.add(filePath);
|
|
1270
|
-
return affected;
|
|
1271
|
-
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Remove file from graph
|
|
1274
|
-
*/
|
|
1275
|
-
removeFile(filePath) {
|
|
1276
|
-
const node = this.graph.nodes.get(filePath);
|
|
1277
|
-
if (node) {
|
|
1278
|
-
const depsArray = Array.from(node.dependencies);
|
|
1279
|
-
for (const dep of depsArray) {
|
|
1280
|
-
const depNode = this.graph.nodes.get(dep);
|
|
1281
|
-
if (depNode) {
|
|
1282
|
-
depNode.dependents.delete(filePath);
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
this.graph.nodes.delete(filePath);
|
|
1286
|
-
}
|
|
1287
|
-
this.graph.edges.delete(filePath);
|
|
1288
|
-
const edgesArray = Array.from(this.graph.edges);
|
|
1289
|
-
for (const [_source, targets] of edgesArray) {
|
|
1290
|
-
targets.delete(filePath);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
/**
|
|
1294
|
-
* Clear entire graph
|
|
1295
|
-
*/
|
|
1296
|
-
clear() {
|
|
1297
|
-
this.graph.nodes.clear();
|
|
1298
|
-
this.graph.edges.clear();
|
|
1299
|
-
}
|
|
1300
|
-
/**
|
|
1301
|
-
* Get graph statistics
|
|
1302
|
-
*/
|
|
1303
|
-
getStats() {
|
|
1304
|
-
let totalDependencies = 0;
|
|
1305
|
-
let totalDependents = 0;
|
|
1306
|
-
const nodesArray = Array.from(this.graph.nodes.values());
|
|
1307
|
-
for (const node of nodesArray) {
|
|
1308
|
-
totalDependencies += node.dependencies.size;
|
|
1309
|
-
totalDependents += node.dependents.size;
|
|
1310
|
-
}
|
|
1311
|
-
const nodeCount = this.graph.nodes.size;
|
|
1312
|
-
const avgDependencies = nodeCount > 0 ? totalDependencies / nodeCount : 0;
|
|
1313
|
-
const avgDependents = nodeCount > 0 ? totalDependents / nodeCount : 0;
|
|
1314
|
-
let edgeCount = 0;
|
|
1315
|
-
const edgesValuesArray = Array.from(this.graph.edges.values());
|
|
1316
|
-
for (const targets of edgesValuesArray) {
|
|
1317
|
-
edgeCount += targets.size;
|
|
1318
|
-
}
|
|
1319
|
-
return {
|
|
1320
|
-
nodeCount,
|
|
1321
|
-
edgeCount,
|
|
1322
|
-
avgDependencies,
|
|
1323
|
-
avgDependents
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* Serialize graph to JSON
|
|
1328
|
-
*/
|
|
1329
|
-
toJSON() {
|
|
1330
|
-
const serialized = {
|
|
1331
|
-
nodes: Array.from(this.graph.nodes.entries()).map(([path2, node]) => ({
|
|
1332
|
-
path: path2,
|
|
1333
|
-
dependencies: Array.from(node.dependencies),
|
|
1334
|
-
dependents: Array.from(node.dependents),
|
|
1335
|
-
lastIndexed: node.lastIndexed
|
|
1336
|
-
})),
|
|
1337
|
-
edges: Array.from(this.graph.edges.entries()).map(([source, targets]) => ({
|
|
1338
|
-
source,
|
|
1339
|
-
targets: Array.from(targets)
|
|
1340
|
-
}))
|
|
1341
|
-
};
|
|
1342
|
-
return serialized;
|
|
1343
|
-
}
|
|
1344
|
-
/**
|
|
1345
|
-
* Load graph from JSON
|
|
1346
|
-
*/
|
|
1347
|
-
fromJSON(json) {
|
|
1348
|
-
this.clear();
|
|
1349
|
-
for (const nodeData of json.nodes || []) {
|
|
1350
|
-
const node = {
|
|
1351
|
-
filePath: nodeData.path,
|
|
1352
|
-
dependencies: new Set(nodeData.dependencies),
|
|
1353
|
-
dependents: new Set(nodeData.dependents),
|
|
1354
|
-
lastIndexed: nodeData.lastIndexed
|
|
1355
|
-
};
|
|
1356
|
-
this.graph.nodes.set(nodeData.path, node);
|
|
1357
|
-
}
|
|
1358
|
-
for (const edgeData of json.edges || []) {
|
|
1359
|
-
this.graph.edges.set(edgeData.source, new Set(edgeData.targets));
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Resolve import path to absolute file path
|
|
1364
|
-
*/
|
|
1365
|
-
resolveImportPath(fromFile, importPath) {
|
|
1366
|
-
if (importPath.startsWith(".")) {
|
|
1367
|
-
const fromDir = path.dirname(fromFile);
|
|
1368
|
-
const resolved = path.resolve(fromDir, importPath);
|
|
1369
|
-
if (!path.extname(resolved)) {
|
|
1370
|
-
return `${resolved}.ts`;
|
|
1371
|
-
}
|
|
1372
|
-
return resolved;
|
|
1373
|
-
}
|
|
1374
|
-
return null;
|
|
1375
|
-
}
|
|
1376
|
-
/**
|
|
1377
|
-
* Get or create node in graph
|
|
1378
|
-
*/
|
|
1379
|
-
getOrCreateNode(filePath) {
|
|
1380
|
-
let node = this.graph.nodes.get(filePath);
|
|
1381
|
-
if (!node) {
|
|
1382
|
-
node = {
|
|
1383
|
-
filePath,
|
|
1384
|
-
dependencies: /* @__PURE__ */ new Set(),
|
|
1385
|
-
dependents: /* @__PURE__ */ new Set(),
|
|
1386
|
-
lastIndexed: 0
|
|
1387
|
-
};
|
|
1388
|
-
this.graph.nodes.set(filePath, node);
|
|
1389
|
-
}
|
|
1390
|
-
return node;
|
|
1391
|
-
}
|
|
1392
|
-
/**
|
|
1393
|
-
* Enable watch mode
|
|
1394
|
-
*/
|
|
1395
|
-
enableWatchMode() {
|
|
1396
|
-
this.watchMode = true;
|
|
1397
|
-
}
|
|
1398
|
-
/**
|
|
1399
|
-
* Disable watch mode
|
|
1400
|
-
*/
|
|
1401
|
-
disableWatchMode() {
|
|
1402
|
-
this.watchMode = false;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Check if watch mode is enabled
|
|
1406
|
-
*/
|
|
1407
|
-
isWatchModeEnabled() {
|
|
1408
|
-
return this.watchMode;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
let globalTracker = null;
|
|
1412
|
-
function getGlobalTracker() {
|
|
1413
|
-
if (!globalTracker) {
|
|
1414
|
-
globalTracker = new DependencyTracker();
|
|
1415
|
-
}
|
|
1416
|
-
return globalTracker;
|
|
1417
|
-
}
|
|
1418
|
-
function resetGlobalTracker() {
|
|
1419
|
-
globalTracker = null;
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
async function parseAST(filePath) {
|
|
1423
|
-
try {
|
|
1424
|
-
const source = await fs.readFile(filePath, "utf-8");
|
|
1425
|
-
return parseSourceToAST(source, filePath);
|
|
1426
|
-
} catch (error) {
|
|
1427
|
-
console.error(`Failed to parse AST for ${filePath}:`, error);
|
|
1428
|
-
return null;
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
function parseSourceToAST(source, filePath) {
|
|
1432
|
-
try {
|
|
1433
|
-
const parser = require("@typescript-eslint/parser");
|
|
1434
|
-
const estree = parser.parse(source, {
|
|
1435
|
-
sourceType: "module",
|
|
1436
|
-
ecmaVersion: "latest",
|
|
1437
|
-
project: "./tsconfig.json",
|
|
1438
|
-
filePath
|
|
1439
|
-
});
|
|
1440
|
-
return convertESTreeToASTNode(estree);
|
|
1441
|
-
} catch (error) {
|
|
1442
|
-
console.error(`Failed to parse source for ${filePath}:`, error);
|
|
1443
|
-
return null;
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
function convertESTreeToASTNode(node, _parent) {
|
|
1447
|
-
const astNode = {
|
|
1448
|
-
type: node.type,
|
|
1449
|
-
name: getNodeName(node),
|
|
1450
|
-
range: {
|
|
1451
|
-
start: {
|
|
1452
|
-
line: node.loc?.start.line || 0,
|
|
1453
|
-
column: node.loc?.start.column || 0
|
|
1454
|
-
},
|
|
1455
|
-
end: {
|
|
1456
|
-
line: node.loc?.end.line || 0,
|
|
1457
|
-
column: node.loc?.end.column || 0
|
|
1458
|
-
}
|
|
1459
|
-
},
|
|
1460
|
-
children: [],
|
|
1461
|
-
metadata: extractNodeMetadata(node)
|
|
1462
|
-
};
|
|
1463
|
-
for (const key of Object.keys(node)) {
|
|
1464
|
-
if (key === "parent" || key === "loc" || key === "range")
|
|
1465
|
-
continue;
|
|
1466
|
-
const child = node[key];
|
|
1467
|
-
if (Array.isArray(child)) {
|
|
1468
|
-
for (const item of child) {
|
|
1469
|
-
if (item && typeof item === "object" && item.type) {
|
|
1470
|
-
astNode.children.push(convertESTreeToASTNode(item));
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
} else if (child && typeof child === "object" && child.type) {
|
|
1474
|
-
astNode.children.push(convertESTreeToASTNode(child));
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
return astNode;
|
|
1478
|
-
}
|
|
1479
|
-
function getNodeName(node) {
|
|
1480
|
-
if (node.id?.name)
|
|
1481
|
-
return node.id.name;
|
|
1482
|
-
if (node.key?.name)
|
|
1483
|
-
return node.key.name;
|
|
1484
|
-
if (node.name)
|
|
1485
|
-
return node.name;
|
|
1486
|
-
if (node.type === "Identifier")
|
|
1487
|
-
return node.name;
|
|
1488
|
-
return void 0;
|
|
1489
|
-
}
|
|
1490
|
-
function extractNodeMetadata(node) {
|
|
1491
|
-
const metadata = {};
|
|
1492
|
-
if (node.declarations)
|
|
1493
|
-
metadata.declarationCount = node.declarations.length;
|
|
1494
|
-
if (node.params)
|
|
1495
|
-
metadata.paramCount = node.params.length;
|
|
1496
|
-
if (node.async)
|
|
1497
|
-
metadata.isAsync = true;
|
|
1498
|
-
if (node.generator)
|
|
1499
|
-
metadata.isGenerator = true;
|
|
1500
|
-
if (node.kind)
|
|
1501
|
-
metadata.kind = node.kind;
|
|
1502
|
-
if (node.method)
|
|
1503
|
-
metadata.isMethod = true;
|
|
1504
|
-
return metadata;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
function generateCallGraph(filePath, ast) {
|
|
1508
|
-
const callGraph = {
|
|
1509
|
-
filePath,
|
|
1510
|
-
nodes: [],
|
|
1511
|
-
edges: [],
|
|
1512
|
-
entryPoints: []
|
|
1513
|
-
};
|
|
1514
|
-
const functionStack = [];
|
|
1515
|
-
const nodeIdCounter = 0;
|
|
1516
|
-
extractFunctions(ast, callGraph, functionStack, nodeIdCounter);
|
|
1517
|
-
functionStack.length = 0;
|
|
1518
|
-
extractCallEdges(ast, callGraph, functionStack);
|
|
1519
|
-
const calledFunctions = new Set(callGraph.edges.map((e) => e.to));
|
|
1520
|
-
callGraph.entryPoints = callGraph.nodes.filter((node) => !calledFunctions.has(node.id)).map((node) => node.id);
|
|
1521
|
-
return callGraph;
|
|
1522
|
-
}
|
|
1523
|
-
function extractFunctions(node, callGraph, functionStack, nodeIdCounter) {
|
|
1524
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
1525
|
-
const callNode = {
|
|
1526
|
-
id: `func-${nodeIdCounter++}`,
|
|
1527
|
-
name: node.name || "<anonymous>",
|
|
1528
|
-
kind: node.type.includes("Arrow") ? "arrow" : node.type === "FunctionExpression" ? "function" : "function",
|
|
1529
|
-
range: node.range,
|
|
1530
|
-
isAsync: node.metadata.isAsync || false,
|
|
1531
|
-
isGenerator: node.metadata.isGenerator || false
|
|
1532
|
-
};
|
|
1533
|
-
callGraph.nodes.push(callNode);
|
|
1534
|
-
functionStack.push(callNode.id);
|
|
1535
|
-
}
|
|
1536
|
-
for (const child of node.children) {
|
|
1537
|
-
nodeIdCounter = extractFunctions(child, callGraph, functionStack, nodeIdCounter);
|
|
1538
|
-
}
|
|
1539
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
1540
|
-
functionStack.pop();
|
|
1541
|
-
}
|
|
1542
|
-
return nodeIdCounter;
|
|
1543
|
-
}
|
|
1544
|
-
function extractCallEdges(node, callGraph, functionStack) {
|
|
1545
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
1546
|
-
const funcNode = callGraph.nodes.find((n) => n.range === node.range);
|
|
1547
|
-
if (funcNode) {
|
|
1548
|
-
functionStack.push(funcNode.id);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
if (node.type === "CallExpression" || node.type === "NewExpression") {
|
|
1552
|
-
const callee = node.children.find((c) => c.type === "Identifier" || c.type === "MemberExpression");
|
|
1553
|
-
if (callee && functionStack.length > 0) {
|
|
1554
|
-
const callerId = functionStack[functionStack.length - 1];
|
|
1555
|
-
const calleeName = callee.name || getMemberExpressionName(callee);
|
|
1556
|
-
const calleeNode = callGraph.nodes.find((n) => n.name === calleeName);
|
|
1557
|
-
if (calleeNode) {
|
|
1558
|
-
const existingEdge = callGraph.edges.find(
|
|
1559
|
-
(e) => e.from === callerId && e.to === calleeNode.id && e.range.start.line === node.range.start.line
|
|
1560
|
-
);
|
|
1561
|
-
if (existingEdge) {
|
|
1562
|
-
existingEdge.callCount++;
|
|
1563
|
-
} else {
|
|
1564
|
-
const edge = {
|
|
1565
|
-
from: callerId,
|
|
1566
|
-
to: calleeNode.id,
|
|
1567
|
-
range: node.range,
|
|
1568
|
-
isDynamic: node.type === "NewExpression" || callee.type === "MemberExpression",
|
|
1569
|
-
callCount: 1
|
|
1570
|
-
};
|
|
1571
|
-
callGraph.edges.push(edge);
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
for (const child of node.children) {
|
|
1577
|
-
extractCallEdges(child, callGraph, functionStack);
|
|
1578
|
-
}
|
|
1579
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
1580
|
-
functionStack.pop();
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
function getMemberExpressionName(node) {
|
|
1584
|
-
const property = node.children.find((c) => c.type === "Identifier");
|
|
1585
|
-
return property?.name;
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
function calculateComplexity(ast, source) {
|
|
1589
|
-
const cyclomatic = calculateCyclomaticComplexity(ast);
|
|
1590
|
-
const cognitive = calculateCognitiveComplexity(ast);
|
|
1591
|
-
const halstead = calculateHalsteadMetrics(ast);
|
|
1592
|
-
const maintainability = calculateMaintainabilityIndex(ast, source, cyclomatic, halstead);
|
|
1593
|
-
const linesOfCode = countLinesOfCode(source);
|
|
1594
|
-
const commentRatio = calculateCommentRatio(source);
|
|
1595
|
-
return {
|
|
1596
|
-
cyclomatic,
|
|
1597
|
-
cognitive,
|
|
1598
|
-
halstead,
|
|
1599
|
-
maintainabilityIndex: maintainability,
|
|
1600
|
-
linesOfCode,
|
|
1601
|
-
commentRatio
|
|
1602
|
-
};
|
|
1603
|
-
}
|
|
1604
|
-
function calculateCyclomaticComplexity(ast) {
|
|
1605
|
-
let complexity = 1;
|
|
1606
|
-
for (const child of ast.children) {
|
|
1607
|
-
complexity += calculateCyclomaticComplexity(child);
|
|
1608
|
-
if (child.type === "IfStatement" || child.type === "ConditionalExpression" || child.type === "ForStatement" || child.type === "ForInStatement" || child.type === "ForOfStatement" || child.type === "WhileStatement" || child.type === "DoWhileStatement" || child.type === "SwitchCase") {
|
|
1609
|
-
complexity++;
|
|
1610
|
-
}
|
|
1611
|
-
if (child.type === "CatchClause") {
|
|
1612
|
-
complexity++;
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
return complexity;
|
|
1616
|
-
}
|
|
1617
|
-
function calculateCognitiveComplexity(ast, nestingLevel = 0) {
|
|
1618
|
-
let complexity = 0;
|
|
1619
|
-
for (const child of ast.children) {
|
|
1620
|
-
if (child.type === "IfStatement" || child.type === "ConditionalExpression" || child.type === "ForStatement" || child.type === "ForInStatement" || child.type === "ForOfStatement" || child.type === "WhileStatement" || child.type === "DoWhileStatement" || child.type === "CatchClause") {
|
|
1621
|
-
complexity += 1 + nestingLevel;
|
|
1622
|
-
}
|
|
1623
|
-
if (child.type === "SwitchCase") {
|
|
1624
|
-
complexity += 1 + nestingLevel;
|
|
1625
|
-
}
|
|
1626
|
-
complexity += calculateCognitiveComplexity(child, nestingLevel + 1);
|
|
1627
|
-
}
|
|
1628
|
-
return complexity;
|
|
1629
|
-
}
|
|
1630
|
-
function calculateHalsteadMetrics(ast) {
|
|
1631
|
-
const operators = /* @__PURE__ */ new Set();
|
|
1632
|
-
const operands = /* @__PURE__ */ new Set();
|
|
1633
|
-
const totalOperators = 0;
|
|
1634
|
-
const totalOperands = 0;
|
|
1635
|
-
extractHalsteadTokens(ast, operators, operands);
|
|
1636
|
-
const n1 = operators.size;
|
|
1637
|
-
const n2 = operands.size;
|
|
1638
|
-
const N1 = totalOperators;
|
|
1639
|
-
const N2 = totalOperands;
|
|
1640
|
-
const vocabulary = n1 + n2;
|
|
1641
|
-
const length = N1 + N2;
|
|
1642
|
-
const volume = length * Math.log2(vocabulary);
|
|
1643
|
-
const difficulty = n1 / 2 * (N2 / n2);
|
|
1644
|
-
const effort = difficulty * volume;
|
|
1645
|
-
const time = effort / 18;
|
|
1646
|
-
const bugs = effort / 3e3;
|
|
1647
|
-
return {
|
|
1648
|
-
n1,
|
|
1649
|
-
n2,
|
|
1650
|
-
N1,
|
|
1651
|
-
N2,
|
|
1652
|
-
vocabulary,
|
|
1653
|
-
difficulty,
|
|
1654
|
-
effort,
|
|
1655
|
-
bugs,
|
|
1656
|
-
time
|
|
1657
|
-
};
|
|
1658
|
-
}
|
|
1659
|
-
function extractHalsteadTokens(node, operators, operands, totalOperators, totalOperands) {
|
|
1660
|
-
const operatorTypes = [
|
|
1661
|
-
"BinaryExpression",
|
|
1662
|
-
"LogicalExpression",
|
|
1663
|
-
"UnaryExpression",
|
|
1664
|
-
"AssignmentExpression",
|
|
1665
|
-
"UpdateExpression",
|
|
1666
|
-
"IfStatement",
|
|
1667
|
-
"ForStatement",
|
|
1668
|
-
"WhileStatement",
|
|
1669
|
-
"DoWhileStatement",
|
|
1670
|
-
"SwitchStatement",
|
|
1671
|
-
"ConditionalExpression"
|
|
1672
|
-
];
|
|
1673
|
-
if (operatorTypes.includes(node.type)) {
|
|
1674
|
-
operators.add(node.type);
|
|
1675
|
-
}
|
|
1676
|
-
if (node.type === "Identifier" && node.name) {
|
|
1677
|
-
operands.add(node.name);
|
|
1678
|
-
} else if (node.type === "Literal") {
|
|
1679
|
-
operands.add("literal");
|
|
1680
|
-
}
|
|
1681
|
-
for (const child of node.children) {
|
|
1682
|
-
extractHalsteadTokens(child, operators, operands);
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
function calculateMaintainabilityIndex(ast, source, cyclomatic, halstead) {
|
|
1686
|
-
const loc = countLinesOfCode(source);
|
|
1687
|
-
const volume = (halstead.N1 + halstead.N2) * Math.log2(halstead.vocabulary);
|
|
1688
|
-
const mi = 171 - 5.2 * Math.log(volume) - 0.23 * cyclomatic - 16.2 * Math.log(loc);
|
|
1689
|
-
return Math.max(0, Math.min(100, mi));
|
|
1690
|
-
}
|
|
1691
|
-
function countLinesOfCode(source) {
|
|
1692
|
-
const lines = source.split("\n");
|
|
1693
|
-
return lines.filter((line) => line.trim().length > 0).length;
|
|
1694
|
-
}
|
|
1695
|
-
function calculateCommentRatio(source) {
|
|
1696
|
-
const lines = source.split("\n");
|
|
1697
|
-
const totalLines = lines.filter((line) => line.trim().length > 0).length;
|
|
1698
|
-
const commentLines = lines.filter((line) => /^\s*\/\//.test(line) || /^\s*\/\*/.test(line)).length;
|
|
1699
|
-
return totalLines > 0 ? commentLines / totalLines : 0;
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
function detectPatterns(ast) {
|
|
1703
|
-
const patterns = [];
|
|
1704
|
-
patterns.push(...detectAntiPatterns(ast));
|
|
1705
|
-
patterns.push(...detectCodeSmells(ast));
|
|
1706
|
-
patterns.push(...detectSecurityRisks(ast));
|
|
1707
|
-
patterns.push(...detectPerformanceIssues(ast));
|
|
1708
|
-
return patterns;
|
|
1709
|
-
}
|
|
1710
|
-
function detectAntiPatterns(node, patterns = []) {
|
|
1711
|
-
if (node.children.length > 50) {
|
|
1712
|
-
patterns.push({
|
|
1713
|
-
id: `pattern-god-object-${node.range.start.line}`,
|
|
1714
|
-
type: "anti-pattern",
|
|
1715
|
-
name: "God Object",
|
|
1716
|
-
range: node.range,
|
|
1717
|
-
description: "This element has too many children and may be doing too much",
|
|
1718
|
-
suggestions: [
|
|
1719
|
-
"Consider splitting into smaller, more focused components",
|
|
1720
|
-
"Apply Single Responsibility Principle"
|
|
1721
|
-
],
|
|
1722
|
-
severity: "warning"
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
if (node.type === "Literal" && typeof node.metadata.value === "number") {
|
|
1726
|
-
const value = node.metadata.value;
|
|
1727
|
-
if (value !== 0 && value !== 1 && Math.abs(value) > 10) {
|
|
1728
|
-
patterns.push({
|
|
1729
|
-
id: `pattern-magic-number-${node.range.start.line}`,
|
|
1730
|
-
type: "anti-pattern",
|
|
1731
|
-
name: "Magic Number",
|
|
1732
|
-
range: node.range,
|
|
1733
|
-
description: "Unnamed numeric literal",
|
|
1734
|
-
suggestions: [
|
|
1735
|
-
"Replace with named constant",
|
|
1736
|
-
"Use configuration object for related values"
|
|
1737
|
-
],
|
|
1738
|
-
severity: "info"
|
|
1739
|
-
});
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
for (const child of node.children) {
|
|
1743
|
-
detectAntiPatterns(child, patterns);
|
|
1744
|
-
}
|
|
1745
|
-
return patterns;
|
|
1746
|
-
}
|
|
1747
|
-
function detectCodeSmells(node, patterns = []) {
|
|
1748
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") {
|
|
1749
|
-
const loc = node.range.end.line - node.range.start.line;
|
|
1750
|
-
if (loc > 50) {
|
|
1751
|
-
patterns.push({
|
|
1752
|
-
id: `pattern-long-function-${node.range.start.line}`,
|
|
1753
|
-
type: "code-smell",
|
|
1754
|
-
name: "Long Function",
|
|
1755
|
-
range: node.range,
|
|
1756
|
-
description: `Function has ${loc} lines (consider < 20)`,
|
|
1757
|
-
suggestions: [
|
|
1758
|
-
"Break down into smaller functions",
|
|
1759
|
-
"Extract logical blocks into helper functions"
|
|
1760
|
-
],
|
|
1761
|
-
severity: "warning"
|
|
1762
|
-
});
|
|
1763
|
-
}
|
|
1764
|
-
const paramCount = node.metadata.paramCount || 0;
|
|
1765
|
-
if (paramCount > 5) {
|
|
1766
|
-
patterns.push({
|
|
1767
|
-
id: `pattern-too-many-params-${node.range.start.line}`,
|
|
1768
|
-
type: "code-smell",
|
|
1769
|
-
name: "Too Many Parameters",
|
|
1770
|
-
range: node.range,
|
|
1771
|
-
description: `Function has ${paramCount} parameters (consider < 5)`,
|
|
1772
|
-
suggestions: [
|
|
1773
|
-
"Use parameter object",
|
|
1774
|
-
"Split function into smaller functions"
|
|
1775
|
-
],
|
|
1776
|
-
severity: "info"
|
|
1777
|
-
});
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") {
|
|
1781
|
-
const callbackDepth = countCallbackDepth(node);
|
|
1782
|
-
if (callbackDepth > 3) {
|
|
1783
|
-
patterns.push({
|
|
1784
|
-
id: `pattern-callback-hell-${node.range.start.line}`,
|
|
1785
|
-
type: "code-smell",
|
|
1786
|
-
name: "Callback Hell",
|
|
1787
|
-
range: node.range,
|
|
1788
|
-
description: `Deep callback nesting (${callbackDepth} levels)`,
|
|
1789
|
-
suggestions: [
|
|
1790
|
-
"Use async/await instead of callbacks",
|
|
1791
|
-
"Extract callbacks to named functions"
|
|
1792
|
-
],
|
|
1793
|
-
severity: "warning"
|
|
1794
|
-
});
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
for (const child of node.children) {
|
|
1798
|
-
detectCodeSmells(child, patterns);
|
|
1799
|
-
}
|
|
1800
|
-
return patterns;
|
|
1801
|
-
}
|
|
1802
|
-
function detectSecurityRisks(node, patterns = []) {
|
|
1803
|
-
if (node.type === "CallExpression" && node.name === "eval") {
|
|
1804
|
-
patterns.push({
|
|
1805
|
-
id: `pattern-eval-${node.range.start.line}`,
|
|
1806
|
-
type: "security-risk",
|
|
1807
|
-
name: "Use of eval()",
|
|
1808
|
-
range: node.range,
|
|
1809
|
-
description: "eval() can execute arbitrary code and is a security risk",
|
|
1810
|
-
suggestions: [
|
|
1811
|
-
"Avoid eval() completely",
|
|
1812
|
-
"Use JSON.parse() for JSON data",
|
|
1813
|
-
"Use object property access for dynamic properties"
|
|
1814
|
-
],
|
|
1815
|
-
severity: "error"
|
|
1816
|
-
});
|
|
1817
|
-
}
|
|
1818
|
-
if ((node.type === "AssignmentExpression" || node.type === "CallExpression") && node.name?.includes("innerHTML")) {
|
|
1819
|
-
patterns.push({
|
|
1820
|
-
id: `pattern-innerhtml-${node.range.start.line}`,
|
|
1821
|
-
type: "security-risk",
|
|
1822
|
-
name: "innerHTML Usage",
|
|
1823
|
-
range: node.range,
|
|
1824
|
-
description: "Setting innerHTML can lead to XSS vulnerabilities",
|
|
1825
|
-
suggestions: [
|
|
1826
|
-
"Use textContent instead",
|
|
1827
|
-
"Sanitize HTML before insertion",
|
|
1828
|
-
"Use DOM methods (createElement, appendChild)"
|
|
1829
|
-
],
|
|
1830
|
-
severity: "warning"
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
if (node.type === "VariableDeclarator") {
|
|
1834
|
-
const name = node.name?.toLowerCase() || "";
|
|
1835
|
-
if (name.includes("password") || name.includes("secret") || name.includes("token") || name.includes("api_key")) {
|
|
1836
|
-
patterns.push({
|
|
1837
|
-
id: `pattern-hardcoded-secret-${node.range.start.line}`,
|
|
1838
|
-
type: "security-risk",
|
|
1839
|
-
name: "Hardcoded Secret",
|
|
1840
|
-
range: node.range,
|
|
1841
|
-
description: "Potential hardcoded credential or secret",
|
|
1842
|
-
suggestions: [
|
|
1843
|
-
"Use environment variables",
|
|
1844
|
-
"Use secret management service",
|
|
1845
|
-
"Never commit secrets to code"
|
|
1846
|
-
],
|
|
1847
|
-
severity: "error"
|
|
1848
|
-
});
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
for (const child of node.children) {
|
|
1852
|
-
detectSecurityRisks(child, patterns);
|
|
1853
|
-
}
|
|
1854
|
-
return patterns;
|
|
1855
|
-
}
|
|
1856
|
-
function detectPerformanceIssues(node, patterns = []) {
|
|
1857
|
-
if (node.type === "ForStatement" || node.type === "WhileStatement") {
|
|
1858
|
-
const hasHeavyOps = node.children.some(
|
|
1859
|
-
(child) => child.type === "CallExpression" && (child.name?.includes("querySelector") || child.name?.includes("innerHTML"))
|
|
1860
|
-
);
|
|
1861
|
-
if (hasHeavyOps) {
|
|
1862
|
-
patterns.push({
|
|
1863
|
-
id: `pattern-loop-heavy-ops-${node.range.start.line}`,
|
|
1864
|
-
type: "performance-issue",
|
|
1865
|
-
name: "Heavy Operation in Loop",
|
|
1866
|
-
range: node.range,
|
|
1867
|
-
description: "Loop contains DOM or heavy I/O operations",
|
|
1868
|
-
suggestions: [
|
|
1869
|
-
"Move heavy operations outside loop",
|
|
1870
|
-
"Cache DOM queries before loop",
|
|
1871
|
-
"Use batch processing"
|
|
1872
|
-
],
|
|
1873
|
-
severity: "warning"
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
if (node.type === "ForStatement" || node.type === "WhileStatement") {
|
|
1878
|
-
const loopNestingDepth = countLoopNesting(node);
|
|
1879
|
-
if (loopNestingDepth > 2) {
|
|
1880
|
-
patterns.push({
|
|
1881
|
-
id: `pattern-nested-loops-${node.range.start.line}`,
|
|
1882
|
-
type: "performance-issue",
|
|
1883
|
-
name: "Nested Loops",
|
|
1884
|
-
range: node.range,
|
|
1885
|
-
description: `${loopNestingDepth} levels of loop nesting (O(n^${loopNestingDepth}) complexity)`,
|
|
1886
|
-
suggestions: [
|
|
1887
|
-
"Consider using data structures with better lookup (Map, Set)",
|
|
1888
|
-
"Break into multiple loops",
|
|
1889
|
-
"Use functional methods (map, filter, reduce)"
|
|
1890
|
-
],
|
|
1891
|
-
severity: "info"
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
for (const child of node.children) {
|
|
1896
|
-
detectPerformanceIssues(child, patterns);
|
|
1897
|
-
}
|
|
1898
|
-
return patterns;
|
|
1899
|
-
}
|
|
1900
|
-
function countCallbackDepth(node, depth = 0) {
|
|
1901
|
-
let maxDepth = depth;
|
|
1902
|
-
for (const child of node.children) {
|
|
1903
|
-
if (child.type === "ArrowFunctionExpression" || child.type === "FunctionExpression") {
|
|
1904
|
-
const childDepth = countCallbackDepth(child, depth + 1);
|
|
1905
|
-
maxDepth = Math.max(maxDepth, childDepth);
|
|
1906
|
-
} else {
|
|
1907
|
-
const childDepth = countCallbackDepth(child, depth);
|
|
1908
|
-
maxDepth = Math.max(maxDepth, childDepth);
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
return maxDepth;
|
|
1912
|
-
}
|
|
1913
|
-
function countLoopNesting(node, depth = 1) {
|
|
1914
|
-
let maxDepth = depth;
|
|
1915
|
-
for (const child of node.children) {
|
|
1916
|
-
if (child.type === "ForStatement" || child.type === "WhileStatement" || child.type === "ForInStatement" || child.type === "ForOfStatement") {
|
|
1917
|
-
const childDepth = countLoopNesting(child, depth + 1);
|
|
1918
|
-
maxDepth = Math.max(maxDepth, childDepth);
|
|
1919
|
-
} else {
|
|
1920
|
-
const childDepth = countLoopNesting(child, depth);
|
|
1921
|
-
maxDepth = Math.max(maxDepth, childDepth);
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
return maxDepth;
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
function extractSymbols(filePath, ast) {
|
|
1928
|
-
const symbolTable = {
|
|
1929
|
-
filePath,
|
|
1930
|
-
symbols: [],
|
|
1931
|
-
exports: [],
|
|
1932
|
-
imports: [],
|
|
1933
|
-
scopes: []
|
|
1934
|
-
};
|
|
1935
|
-
const scopeStack = [];
|
|
1936
|
-
let scopeIdCounter = 0;
|
|
1937
|
-
const moduleScope = {
|
|
1938
|
-
id: `scope-${scopeIdCounter++}`,
|
|
1939
|
-
kind: "module",
|
|
1940
|
-
range: ast.range,
|
|
1941
|
-
children: [],
|
|
1942
|
-
symbols: []
|
|
1943
|
-
};
|
|
1944
|
-
symbolTable.scopes.push(moduleScope);
|
|
1945
|
-
scopeStack.push(moduleScope);
|
|
1946
|
-
extractSymbolsFromNode(ast, symbolTable, scopeStack, scopeIdCounter);
|
|
1947
|
-
return symbolTable;
|
|
1948
|
-
}
|
|
1949
|
-
function extractSymbolsFromNode(node, symbolTable, scopeStack, scopeIdCounter) {
|
|
1950
|
-
const currentScope = scopeStack[scopeStack.length - 1];
|
|
1951
|
-
switch (node.type) {
|
|
1952
|
-
case "FunctionDeclaration":
|
|
1953
|
-
case "FunctionExpression":
|
|
1954
|
-
case "ArrowFunctionExpression": {
|
|
1955
|
-
const symbol = createFunctionSymbol(node, currentScope.id);
|
|
1956
|
-
symbolTable.symbols.push(symbol);
|
|
1957
|
-
currentScope.symbols.push(symbol.id);
|
|
1958
|
-
const functionScope = {
|
|
1959
|
-
id: `scope-${scopeIdCounter++}`,
|
|
1960
|
-
kind: "function",
|
|
1961
|
-
range: node.range,
|
|
1962
|
-
parent: currentScope.id,
|
|
1963
|
-
children: [],
|
|
1964
|
-
symbols: []
|
|
1965
|
-
};
|
|
1966
|
-
symbolTable.scopes.push(functionScope);
|
|
1967
|
-
currentScope.children.push(functionScope.id);
|
|
1968
|
-
scopeStack.push(functionScope);
|
|
1969
|
-
break;
|
|
1970
|
-
}
|
|
1971
|
-
case "ClassDeclaration":
|
|
1972
|
-
case "ClassExpression": {
|
|
1973
|
-
const symbol = createClassSymbol(node, currentScope.id);
|
|
1974
|
-
symbolTable.symbols.push(symbol);
|
|
1975
|
-
currentScope.symbols.push(symbol.id);
|
|
1976
|
-
const classScope = {
|
|
1977
|
-
id: `scope-${scopeIdCounter++}`,
|
|
1978
|
-
kind: "class",
|
|
1979
|
-
range: node.range,
|
|
1980
|
-
parent: currentScope.id,
|
|
1981
|
-
children: [],
|
|
1982
|
-
symbols: []
|
|
1983
|
-
};
|
|
1984
|
-
symbolTable.scopes.push(classScope);
|
|
1985
|
-
currentScope.children.push(classScope.id);
|
|
1986
|
-
scopeStack.push(classScope);
|
|
1987
|
-
break;
|
|
1988
|
-
}
|
|
1989
|
-
case "VariableDeclaration":
|
|
1990
|
-
case "VariableDeclarator": {
|
|
1991
|
-
const symbol = createVariableSymbol(node, currentScope.id);
|
|
1992
|
-
symbolTable.symbols.push(symbol);
|
|
1993
|
-
currentScope.symbols.push(symbol.id);
|
|
1994
|
-
break;
|
|
1995
|
-
}
|
|
1996
|
-
case "TSInterfaceDeclaration":
|
|
1997
|
-
case "TSTypeAliasDeclaration": {
|
|
1998
|
-
const symbol = createTypeSymbol(node, currentScope.id);
|
|
1999
|
-
symbolTable.symbols.push(symbol);
|
|
2000
|
-
symbolTable.exports.push(symbol);
|
|
2001
|
-
currentScope.symbols.push(symbol.id);
|
|
2002
|
-
break;
|
|
2003
|
-
}
|
|
2004
|
-
case "ImportDeclaration": {
|
|
2005
|
-
const importDecl = createImportDeclaration(node);
|
|
2006
|
-
symbolTable.imports.push(importDecl);
|
|
2007
|
-
break;
|
|
2008
|
-
}
|
|
2009
|
-
case "ExportNamedDeclaration":
|
|
2010
|
-
case "ExportDefaultDeclaration": {
|
|
2011
|
-
if (node.name) {
|
|
2012
|
-
const symbol = symbolTable.symbols.find((s) => s.name === node.name);
|
|
2013
|
-
if (symbol && !symbolTable.exports.includes(symbol)) {
|
|
2014
|
-
symbolTable.exports.push(symbol);
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
break;
|
|
2018
|
-
}
|
|
2019
|
-
case "BlockStatement": {
|
|
2020
|
-
const blockScope = {
|
|
2021
|
-
id: `scope-${scopeIdCounter++}`,
|
|
2022
|
-
kind: "block",
|
|
2023
|
-
range: node.range,
|
|
2024
|
-
parent: currentScope.id,
|
|
2025
|
-
children: [],
|
|
2026
|
-
symbols: []
|
|
2027
|
-
};
|
|
2028
|
-
symbolTable.scopes.push(blockScope);
|
|
2029
|
-
currentScope.children.push(blockScope.id);
|
|
2030
|
-
scopeStack.push(blockScope);
|
|
2031
|
-
break;
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
for (const child of node.children) {
|
|
2035
|
-
scopeIdCounter = extractSymbolsFromNode(child, symbolTable, scopeStack, scopeIdCounter);
|
|
2036
|
-
if (child.type === "FunctionDeclaration" || child.type === "FunctionExpression" || child.type === "ClassDeclaration" || child.type === "ClassExpression" || child.type === "BlockStatement") {
|
|
2037
|
-
const childScopeEnd = symbolTable.scopes.findIndex((s) => s.range === child.range);
|
|
2038
|
-
if (childScopeEnd !== -1 && childScopeEnd < symbolTable.scopes.length - 1) {
|
|
2039
|
-
scopeStack.pop();
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
return scopeIdCounter;
|
|
2044
|
-
}
|
|
2045
|
-
function createFunctionSymbol(node, parentId) {
|
|
2046
|
-
return {
|
|
2047
|
-
id: `symbol-${node.type}-${node.range.start.line}-${node.range.start.column}`,
|
|
2048
|
-
name: node.name || "<anonymous>",
|
|
2049
|
-
kind: node.type.includes("Method") ? "method" : "function",
|
|
2050
|
-
range: node.range,
|
|
2051
|
-
definingScope: parentId,
|
|
2052
|
-
references: [],
|
|
2053
|
-
metadata: {
|
|
2054
|
-
isAsync: node.metadata.isAsync || false,
|
|
2055
|
-
isGenerator: node.metadata.isGenerator || false,
|
|
2056
|
-
paramCount: node.metadata.paramCount || 0
|
|
2057
|
-
}
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
|
-
function createClassSymbol(node, parentId) {
|
|
2061
|
-
return {
|
|
2062
|
-
id: `symbol-class-${node.range.start.line}-${node.range.start.column}`,
|
|
2063
|
-
name: node.name || "<anonymous>",
|
|
2064
|
-
kind: "class",
|
|
2065
|
-
range: node.range,
|
|
2066
|
-
definingScope: parentId,
|
|
2067
|
-
references: [],
|
|
2068
|
-
metadata: {}
|
|
2069
|
-
};
|
|
2070
|
-
}
|
|
2071
|
-
function createVariableSymbol(node, parentId) {
|
|
2072
|
-
const isConst = node.metadata.kind === "const";
|
|
2073
|
-
return {
|
|
2074
|
-
id: `symbol-variable-${node.range.start.line}-${node.range.start.column}`,
|
|
2075
|
-
name: node.name || "<unknown>",
|
|
2076
|
-
kind: isConst ? "constant" : "variable",
|
|
2077
|
-
range: node.range,
|
|
2078
|
-
definingScope: parentId,
|
|
2079
|
-
references: [],
|
|
2080
|
-
metadata: {}
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
function createTypeSymbol(node, parentId) {
|
|
2084
|
-
return {
|
|
2085
|
-
id: `symbol-type-${node.range.start.line}-${node.range.start.column}`,
|
|
2086
|
-
name: node.name || "<anonymous>",
|
|
2087
|
-
kind: node.type.includes("Interface") ? "interface" : "type",
|
|
2088
|
-
range: node.range,
|
|
2089
|
-
definingScope: parentId,
|
|
2090
|
-
references: [],
|
|
2091
|
-
metadata: {}
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
function createImportDeclaration(node) {
|
|
2095
|
-
const imports = [];
|
|
2096
|
-
for (const child of node.children) {
|
|
2097
|
-
if (child.type === "ImportSpecifier" || child.type === "ImportDefaultSpecifier") {
|
|
2098
|
-
imports.push({
|
|
2099
|
-
name: child.name,
|
|
2100
|
-
alias: child.metadata.local,
|
|
2101
|
-
kind: child.type.replace("Import", "").toLowerCase()
|
|
2102
|
-
});
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
return {
|
|
2106
|
-
module: node.metadata.source || "",
|
|
2107
|
-
imports,
|
|
2108
|
-
range: node.range,
|
|
2109
|
-
isDynamic: node.type === "ImportExpression"
|
|
2110
|
-
};
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
class IncrementalIndexer {
|
|
2114
|
-
index;
|
|
2115
|
-
storage;
|
|
2116
|
-
dependencyGraph = /* @__PURE__ */ new Map();
|
|
2117
|
-
reverseDependencyGraph = /* @__PURE__ */ new Map();
|
|
2118
|
-
constructor() {
|
|
2119
|
-
this.index = getGlobalIndex();
|
|
2120
|
-
this.storage = new LevelDBStorage("./actionbook-cache");
|
|
2121
|
-
}
|
|
2122
|
-
/**
|
|
2123
|
-
* Index a single file
|
|
2124
|
-
*/
|
|
2125
|
-
async indexFile(filePath) {
|
|
2126
|
-
try {
|
|
2127
|
-
const stats = await fs.stat(filePath).catch(() => null);
|
|
2128
|
-
if (!stats?.isFile()) {
|
|
2129
|
-
return null;
|
|
2130
|
-
}
|
|
2131
|
-
const source = await fs.readFile(filePath, "utf-8");
|
|
2132
|
-
const ast = await parseAST(filePath);
|
|
2133
|
-
if (!ast) {
|
|
2134
|
-
return null;
|
|
2135
|
-
}
|
|
2136
|
-
const symbols = extractSymbols(filePath, ast);
|
|
2137
|
-
const callGraph = generateCallGraph(filePath, ast);
|
|
2138
|
-
const complexity = calculateComplexity(ast, source);
|
|
2139
|
-
const patterns = detectPatterns(ast);
|
|
2140
|
-
const data = {
|
|
2141
|
-
filePath,
|
|
2142
|
-
ast,
|
|
2143
|
-
symbols,
|
|
2144
|
-
callGraph,
|
|
2145
|
-
complexity,
|
|
2146
|
-
patterns,
|
|
2147
|
-
lastModified: stats.mtimeMs,
|
|
2148
|
-
checksum: this.generateChecksum(source)
|
|
2149
|
-
};
|
|
2150
|
-
await this.storePrecomputedData(data);
|
|
2151
|
-
await this.updateDependencies(filePath, symbols);
|
|
2152
|
-
return data;
|
|
2153
|
-
} catch (error) {
|
|
2154
|
-
console.error(`Failed to index file ${filePath}:`, error);
|
|
2155
|
-
return null;
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
/**
|
|
2159
|
-
* Index multiple files
|
|
2160
|
-
*/
|
|
2161
|
-
async indexFiles(filePaths) {
|
|
2162
|
-
const startTime = Date.now();
|
|
2163
|
-
const stats = {
|
|
2164
|
-
filesIndexed: 0,
|
|
2165
|
-
totalFiles: filePaths.length,
|
|
2166
|
-
errors: [],
|
|
2167
|
-
duration: 0
|
|
2168
|
-
};
|
|
2169
|
-
const batchSize = 10;
|
|
2170
|
-
for (let i = 0; i < filePaths.length; i += batchSize) {
|
|
2171
|
-
const batch = filePaths.slice(i, i + batchSize);
|
|
2172
|
-
const results = await Promise.allSettled(
|
|
2173
|
-
batch.map((path) => this.indexFile(path))
|
|
2174
|
-
);
|
|
2175
|
-
for (const result of results) {
|
|
2176
|
-
if (result.status === "fulfilled" && result.value) {
|
|
2177
|
-
stats.filesIndexed++;
|
|
2178
|
-
} else if (result.status === "rejected") {
|
|
2179
|
-
stats.errors.push(result.reason?.message || "Unknown error");
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
stats.duration = Date.now() - startTime;
|
|
2184
|
-
return stats;
|
|
2185
|
-
}
|
|
2186
|
-
/**
|
|
2187
|
-
* Remove file from index
|
|
2188
|
-
*/
|
|
2189
|
-
async removeFile(filePath) {
|
|
2190
|
-
await this.index.delete(`${filePath}|ast`);
|
|
2191
|
-
await this.index.delete(`${filePath}|symbol`);
|
|
2192
|
-
await this.index.delete(`${filePath}|call-graph`);
|
|
2193
|
-
await this.index.delete(`${filePath}|complexity`);
|
|
2194
|
-
await this.index.delete(`${filePath}|patterns`);
|
|
2195
|
-
const dependencies = this.dependencyGraph.get(filePath);
|
|
2196
|
-
if (dependencies) {
|
|
2197
|
-
const depsArray = Array.from(dependencies);
|
|
2198
|
-
for (const dep of depsArray) {
|
|
2199
|
-
const dependents = this.reverseDependencyGraph.get(dep);
|
|
2200
|
-
if (dependents) {
|
|
2201
|
-
dependents.delete(filePath);
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
this.dependencyGraph.delete(filePath);
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
/**
|
|
2208
|
-
* Remove multiple files
|
|
2209
|
-
*/
|
|
2210
|
-
async removeFiles(filePaths) {
|
|
2211
|
-
await Promise.all(filePaths.map((path) => this.removeFile(path)));
|
|
2212
|
-
}
|
|
2213
|
-
/**
|
|
2214
|
-
* Reindex file and its dependents
|
|
2215
|
-
*/
|
|
2216
|
-
async reindexFile(filePath) {
|
|
2217
|
-
const dependents = this.reverseDependencyGraph.get(filePath) || /* @__PURE__ */ new Set();
|
|
2218
|
-
const stats = await this.indexFiles([filePath]);
|
|
2219
|
-
if (dependents.size > 0) {
|
|
2220
|
-
const dependentStats = await this.indexFiles(Array.from(dependents));
|
|
2221
|
-
stats.filesIndexed += dependentStats.filesIndexed;
|
|
2222
|
-
stats.errors.push(...dependentStats.errors);
|
|
2223
|
-
}
|
|
2224
|
-
return stats;
|
|
2225
|
-
}
|
|
2226
|
-
/**
|
|
2227
|
-
* Check if file needs reindexing
|
|
2228
|
-
*/
|
|
2229
|
-
async needsReindex(filePath) {
|
|
2230
|
-
try {
|
|
2231
|
-
const stats = await fs.stat(filePath);
|
|
2232
|
-
const key = `${filePath}|ast`;
|
|
2233
|
-
const entry = await this.index.get(key);
|
|
2234
|
-
if (!entry) {
|
|
2235
|
-
return true;
|
|
2236
|
-
}
|
|
2237
|
-
return stats.mtimeMs > entry.timestamp;
|
|
2238
|
-
} catch {
|
|
2239
|
-
return true;
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
/**
|
|
2243
|
-
* Store precomputed data in cache
|
|
2244
|
-
*/
|
|
2245
|
-
async storePrecomputedData(data) {
|
|
2246
|
-
const timestamp = Date.now();
|
|
2247
|
-
await this.index.set(`${data.filePath}|ast`, {
|
|
2248
|
-
key: `${data.filePath}|ast`,
|
|
2249
|
-
type: "ast",
|
|
2250
|
-
data: data.ast,
|
|
2251
|
-
checksum: data.checksum,
|
|
2252
|
-
timestamp,
|
|
2253
|
-
compressed: false
|
|
2254
|
-
});
|
|
2255
|
-
await this.index.set(`${data.filePath}|symbol`, {
|
|
2256
|
-
key: `${data.filePath}|symbol`,
|
|
2257
|
-
type: "symbol",
|
|
2258
|
-
data: data.symbols,
|
|
2259
|
-
checksum: data.checksum,
|
|
2260
|
-
timestamp,
|
|
2261
|
-
compressed: false
|
|
2262
|
-
});
|
|
2263
|
-
await this.index.set(`${data.filePath}|call-graph`, {
|
|
2264
|
-
key: `${data.filePath}|call-graph`,
|
|
2265
|
-
type: "call-graph",
|
|
2266
|
-
data: data.callGraph,
|
|
2267
|
-
checksum: data.checksum,
|
|
2268
|
-
timestamp,
|
|
2269
|
-
compressed: false
|
|
2270
|
-
});
|
|
2271
|
-
await this.index.set(`${data.filePath}|complexity`, {
|
|
2272
|
-
key: `${data.filePath}|complexity`,
|
|
2273
|
-
type: "complexity",
|
|
2274
|
-
data: data.complexity,
|
|
2275
|
-
checksum: data.checksum,
|
|
2276
|
-
timestamp,
|
|
2277
|
-
compressed: false
|
|
2278
|
-
});
|
|
2279
|
-
await this.index.set(`${data.filePath}|patterns`, {
|
|
2280
|
-
key: `${data.filePath}|patterns`,
|
|
2281
|
-
type: "patterns",
|
|
2282
|
-
data: data.patterns,
|
|
2283
|
-
checksum: data.checksum,
|
|
2284
|
-
timestamp,
|
|
2285
|
-
compressed: false
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2288
|
-
/**
|
|
2289
|
-
* Update dependency graph based on imports
|
|
2290
|
-
*/
|
|
2291
|
-
async updateDependencies(filePath, symbols) {
|
|
2292
|
-
const imports = /* @__PURE__ */ new Set();
|
|
2293
|
-
for (const importDecl of symbols.imports) {
|
|
2294
|
-
const resolvedPath = await this.resolveImportPath(filePath, importDecl.module);
|
|
2295
|
-
if (resolvedPath) {
|
|
2296
|
-
imports.add(resolvedPath);
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
this.dependencyGraph.set(filePath, imports);
|
|
2300
|
-
const importsArray = Array.from(imports);
|
|
2301
|
-
for (const dep of importsArray) {
|
|
2302
|
-
if (!this.reverseDependencyGraph.has(dep)) {
|
|
2303
|
-
this.reverseDependencyGraph.set(dep, /* @__PURE__ */ new Set());
|
|
2304
|
-
}
|
|
2305
|
-
this.reverseDependencyGraph.get(dep).add(filePath);
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
/**
|
|
2309
|
-
* Resolve import path to absolute file path
|
|
2310
|
-
*/
|
|
2311
|
-
async resolveImportPath(fromFile, importPath) {
|
|
2312
|
-
if (importPath.startsWith(".")) {
|
|
2313
|
-
const fromDir = fromFile.substring(0, fromFile.lastIndexOf("/"));
|
|
2314
|
-
const resolved = `${fromDir}/${importPath}`;
|
|
2315
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".json"];
|
|
2316
|
-
for (const ext of extensions) {
|
|
2317
|
-
const path = resolved + ext;
|
|
2318
|
-
try {
|
|
2319
|
-
await fs.access(path);
|
|
2320
|
-
return path;
|
|
2321
|
-
} catch {
|
|
2322
|
-
try {
|
|
2323
|
-
const indexPath = `${resolved}/index${ext}`;
|
|
2324
|
-
await fs.access(indexPath);
|
|
2325
|
-
return indexPath;
|
|
2326
|
-
} catch {
|
|
2327
|
-
continue;
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
return null;
|
|
2333
|
-
}
|
|
2334
|
-
/**
|
|
2335
|
-
* Generate checksum for source code
|
|
2336
|
-
*/
|
|
2337
|
-
generateChecksum(source) {
|
|
2338
|
-
const crypto = require("node:crypto");
|
|
2339
|
-
return crypto.createHash("sha256").update(source).digest("hex");
|
|
2340
|
-
}
|
|
2341
|
-
/**
|
|
2342
|
-
* Get dependency graph
|
|
2343
|
-
*/
|
|
2344
|
-
getDependencyGraph() {
|
|
2345
|
-
return new Map(this.dependencyGraph);
|
|
2346
|
-
}
|
|
2347
|
-
/**
|
|
2348
|
-
* Get reverse dependency graph
|
|
2349
|
-
*/
|
|
2350
|
-
getReverseDependencyGraph() {
|
|
2351
|
-
return new Map(this.reverseDependencyGraph);
|
|
2352
|
-
}
|
|
2353
|
-
/**
|
|
2354
|
-
* Clear all indexes
|
|
2355
|
-
*/
|
|
2356
|
-
async clear() {
|
|
2357
|
-
await this.index.clear();
|
|
2358
|
-
this.dependencyGraph.clear();
|
|
2359
|
-
this.reverseDependencyGraph.clear();
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
let globalIndexer = null;
|
|
2363
|
-
function getGlobalIndexer() {
|
|
2364
|
-
if (!globalIndexer) {
|
|
2365
|
-
globalIndexer = new IncrementalIndexer();
|
|
2366
|
-
}
|
|
2367
|
-
return globalIndexer;
|
|
2368
|
-
}
|
|
2369
|
-
function resetGlobalIndexer() {
|
|
2370
|
-
globalIndexer = null;
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
|
-
class FileWatcher extends EventEmitter {
|
|
2374
|
-
watcher = null;
|
|
2375
|
-
indexer;
|
|
2376
|
-
watchedPaths = /* @__PURE__ */ new Set();
|
|
2377
|
-
changeQueue = /* @__PURE__ */ new Map();
|
|
2378
|
-
flushTimer = null;
|
|
2379
|
-
FLUSH_DELAY = 100;
|
|
2380
|
-
// ms
|
|
2381
|
-
constructor(indexer) {
|
|
2382
|
-
super();
|
|
2383
|
-
this.indexer = indexer;
|
|
2384
|
-
}
|
|
2385
|
-
/**
|
|
2386
|
-
* Start watching a path
|
|
2387
|
-
*/
|
|
2388
|
-
watch(path, options = {}) {
|
|
2389
|
-
if (this.watchedPaths.has(path)) {
|
|
2390
|
-
return;
|
|
2391
|
-
}
|
|
2392
|
-
const watcherOptions = {
|
|
2393
|
-
ignored: [
|
|
2394
|
-
/(^|[\\/])\../,
|
|
2395
|
-
// dot files
|
|
2396
|
-
"**/node_modules/**",
|
|
2397
|
-
"**/.git/**",
|
|
2398
|
-
"**/dist/**",
|
|
2399
|
-
"**/build/**",
|
|
2400
|
-
"**/.next/**",
|
|
2401
|
-
"**/coverage/**"
|
|
2402
|
-
],
|
|
2403
|
-
persistent: options.persistent ?? true,
|
|
2404
|
-
ignoreInitial: options.ignoreInitial ?? true,
|
|
2405
|
-
ignorePermissionErrors: options.ignorePermissionErrors ?? true,
|
|
2406
|
-
awaitWriteFinish: options.awaitWriteFinish ?? {
|
|
2407
|
-
stabilityThreshold: 300,
|
|
2408
|
-
pollInterval: 100
|
|
2409
|
-
}
|
|
2410
|
-
};
|
|
2411
|
-
this.watcher = chokidar.watch(path, watcherOptions);
|
|
2412
|
-
this.watcher.on("add", (filePath) => this.handleFileChange("add", filePath)).on("change", (filePath) => this.handleFileChange("change", filePath)).on("unlink", (filePath) => this.handleFileChange("unlink", filePath)).on("addDir", (dirPath) => this.handleFileChange("addDir", dirPath)).on("unlinkDir", (dirPath) => this.handleFileChange("unlinkDir", dirPath)).on("error", (error) => this.emit("error", error)).on("ready", () => this.emit("ready"));
|
|
2413
|
-
this.watchedPaths.add(path);
|
|
2414
|
-
}
|
|
2415
|
-
/**
|
|
2416
|
-
* Stop watching a path
|
|
2417
|
-
*/
|
|
2418
|
-
async unwatch(path) {
|
|
2419
|
-
if (!this.watchedPaths.has(path)) {
|
|
2420
|
-
return;
|
|
2421
|
-
}
|
|
2422
|
-
if (this.watcher) {
|
|
2423
|
-
await this.watcher.unwatch(path);
|
|
2424
|
-
}
|
|
2425
|
-
this.watchedPaths.delete(path);
|
|
2426
|
-
}
|
|
2427
|
-
/**
|
|
2428
|
-
* Stop all watching
|
|
2429
|
-
*/
|
|
2430
|
-
async close() {
|
|
2431
|
-
if (this.watcher) {
|
|
2432
|
-
await this.watcher.close();
|
|
2433
|
-
this.watcher = null;
|
|
2434
|
-
}
|
|
2435
|
-
this.watchedPaths.clear();
|
|
2436
|
-
this.changeQueue.clear();
|
|
2437
|
-
if (this.flushTimer) {
|
|
2438
|
-
clearTimeout(this.flushTimer);
|
|
2439
|
-
this.flushTimer = null;
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
/**
|
|
2443
|
-
* Handle file change event
|
|
2444
|
-
*/
|
|
2445
|
-
handleFileChange(type, path) {
|
|
2446
|
-
const event = {
|
|
2447
|
-
type,
|
|
2448
|
-
path,
|
|
2449
|
-
timestamp: Date.now()
|
|
2450
|
-
};
|
|
2451
|
-
this.changeQueue.set(path, event);
|
|
2452
|
-
this.emit("change", event);
|
|
2453
|
-
this.scheduleFlush();
|
|
2454
|
-
}
|
|
2455
|
-
/**
|
|
2456
|
-
* Schedule flushing the change queue
|
|
2457
|
-
*/
|
|
2458
|
-
scheduleFlush() {
|
|
2459
|
-
if (this.flushTimer) {
|
|
2460
|
-
return;
|
|
2461
|
-
}
|
|
2462
|
-
this.flushTimer = setTimeout(() => {
|
|
2463
|
-
this.flushChanges().catch((error) => this.emit("error", error));
|
|
2464
|
-
this.flushTimer = null;
|
|
2465
|
-
}, this.FLUSH_DELAY);
|
|
2466
|
-
}
|
|
2467
|
-
/**
|
|
2468
|
-
* Flush queued changes to indexer
|
|
2469
|
-
*/
|
|
2470
|
-
async flushChanges() {
|
|
2471
|
-
if (this.changeQueue.size === 0) {
|
|
2472
|
-
return;
|
|
2473
|
-
}
|
|
2474
|
-
const changes = Array.from(this.changeQueue.values());
|
|
2475
|
-
this.changeQueue.clear();
|
|
2476
|
-
const toAdd = changes.filter((c) => c.type === "add" || c.type === "change").map((c) => c.path);
|
|
2477
|
-
const toRemove = changes.filter((c) => c.type === "unlink").map((c) => c.path);
|
|
2478
|
-
try {
|
|
2479
|
-
if (toAdd.length > 0) {
|
|
2480
|
-
await this.indexer.indexFiles(toAdd);
|
|
2481
|
-
}
|
|
2482
|
-
if (toRemove.length > 0) {
|
|
2483
|
-
await this.indexer.removeFiles(toRemove);
|
|
2484
|
-
}
|
|
2485
|
-
this.emit("indexed", { added: toAdd.length, removed: toRemove.length });
|
|
2486
|
-
} catch (error) {
|
|
2487
|
-
this.emit("error", error);
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
/**
|
|
2491
|
-
* Get list of watched paths
|
|
2492
|
-
*/
|
|
2493
|
-
getWatchedPaths() {
|
|
2494
|
-
return Array.from(this.watchedPaths);
|
|
2495
|
-
}
|
|
2496
|
-
/**
|
|
2497
|
-
* Check if path is being watched
|
|
2498
|
-
*/
|
|
2499
|
-
isWatching(path) {
|
|
2500
|
-
return this.watchedPaths.has(path);
|
|
2501
|
-
}
|
|
2502
|
-
/**
|
|
2503
|
-
* Get current change queue size
|
|
2504
|
-
*/
|
|
2505
|
-
getQueueSize() {
|
|
2506
|
-
return this.changeQueue.size;
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
let globalWatcher = null;
|
|
2510
|
-
function getGlobalWatcher() {
|
|
2511
|
-
if (!globalWatcher) {
|
|
2512
|
-
const indexer = new IncrementalIndexer();
|
|
2513
|
-
globalWatcher = new FileWatcher(indexer);
|
|
2514
|
-
}
|
|
2515
|
-
return globalWatcher;
|
|
2516
|
-
}
|
|
2517
|
-
async function closeGlobalWatcher() {
|
|
2518
|
-
if (globalWatcher) {
|
|
2519
|
-
await globalWatcher.close();
|
|
2520
|
-
globalWatcher = null;
|
|
2521
|
-
}
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
class ActionbookEngine {
|
|
2525
|
-
config;
|
|
2526
|
-
queryAPI;
|
|
2527
|
-
indexer;
|
|
2528
|
-
watcher;
|
|
2529
|
-
tracker;
|
|
2530
|
-
isInitialized = false;
|
|
2531
|
-
constructor(config = {}) {
|
|
2532
|
-
this.config = {
|
|
2533
|
-
cachePath: config.cachePath || "./.actionbook-cache",
|
|
2534
|
-
watchMode: config.watchMode ?? true,
|
|
2535
|
-
compressionEnabled: config.compressionEnabled ?? true,
|
|
2536
|
-
maxMemoryCacheSize: config.maxMemoryCacheSize || 1e3,
|
|
2537
|
-
logLevel: config.logLevel || "info"
|
|
2538
|
-
};
|
|
2539
|
-
this.queryAPI = getQueryAPI();
|
|
2540
|
-
this.indexer = getGlobalIndexer();
|
|
2541
|
-
this.watcher = getGlobalWatcher();
|
|
2542
|
-
this.tracker = getGlobalTracker();
|
|
2543
|
-
}
|
|
2544
|
-
/**
|
|
2545
|
-
* Initialize the engine
|
|
2546
|
-
*/
|
|
2547
|
-
async initialize() {
|
|
2548
|
-
if (this.isInitialized) {
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
this.log("info", "Initializing Actionbook Engine...");
|
|
2552
|
-
const index = getGlobalIndex(this.config.cachePath);
|
|
2553
|
-
await index.compact();
|
|
2554
|
-
if (this.config.watchMode) {
|
|
2555
|
-
this.tracker.enableWatchMode();
|
|
2556
|
-
this.setupWatcher();
|
|
2557
|
-
}
|
|
2558
|
-
this.isInitialized = true;
|
|
2559
|
-
this.log("info", "Actionbook Engine initialized");
|
|
2560
|
-
}
|
|
2561
|
-
/**
|
|
2562
|
-
* Index a single file
|
|
2563
|
-
*/
|
|
2564
|
-
async indexFile(filePath) {
|
|
2565
|
-
this.ensureInitialized();
|
|
2566
|
-
this.log("debug", `Indexing file: ${filePath}`);
|
|
2567
|
-
return this.indexer.indexFile(filePath);
|
|
2568
|
-
}
|
|
2569
|
-
/**
|
|
2570
|
-
* Index multiple files
|
|
2571
|
-
*/
|
|
2572
|
-
async indexFiles(filePaths) {
|
|
2573
|
-
this.ensureInitialized();
|
|
2574
|
-
this.log("info", `Indexing ${filePaths.length} files...`);
|
|
2575
|
-
return this.indexer.indexFiles(filePaths);
|
|
2576
|
-
}
|
|
2577
|
-
/**
|
|
2578
|
-
* Index a directory recursively
|
|
2579
|
-
*/
|
|
2580
|
-
async indexDirectory(dirPath, extensions = [".ts", ".tsx", ".js", ".jsx"]) {
|
|
2581
|
-
this.ensureInitialized();
|
|
2582
|
-
this.log("info", `Indexing directory: ${dirPath}`);
|
|
2583
|
-
const files = await this.getFilesRecursively(dirPath, extensions);
|
|
2584
|
-
return this.indexFiles(files);
|
|
2585
|
-
}
|
|
2586
|
-
/**
|
|
2587
|
-
* Watch a directory for changes
|
|
2588
|
-
*/
|
|
2589
|
-
async watchDirectory(dirPath) {
|
|
2590
|
-
this.ensureInitialized();
|
|
2591
|
-
this.log("info", `Watching directory: ${dirPath}`);
|
|
2592
|
-
if (!this.config.watchMode) {
|
|
2593
|
-
this.log("warn", "Watch mode is disabled");
|
|
2594
|
-
return;
|
|
2595
|
-
}
|
|
2596
|
-
this.watcher.watch(dirPath);
|
|
2597
|
-
}
|
|
2598
|
-
/**
|
|
2599
|
-
* Query precomputed data
|
|
2600
|
-
*/
|
|
2601
|
-
get query() {
|
|
2602
|
-
return this.queryAPI;
|
|
2603
|
-
}
|
|
2604
|
-
/**
|
|
2605
|
-
* Get cache statistics
|
|
2606
|
-
*/
|
|
2607
|
-
async getCacheStats() {
|
|
2608
|
-
const index = getGlobalIndex();
|
|
2609
|
-
return index.getStats();
|
|
2610
|
-
}
|
|
2611
|
-
/**
|
|
2612
|
-
* Get dependency graph
|
|
2613
|
-
*/
|
|
2614
|
-
getDependencyGraph() {
|
|
2615
|
-
return this.indexer.getDependencyGraph();
|
|
2616
|
-
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Get circular dependencies
|
|
2619
|
-
*/
|
|
2620
|
-
getCircularDependencies() {
|
|
2621
|
-
return this.tracker.detectCircularDependencies();
|
|
2622
|
-
}
|
|
2623
|
-
/**
|
|
2624
|
-
* Warm up cache for specific files
|
|
2625
|
-
*/
|
|
2626
|
-
async warmup(files) {
|
|
2627
|
-
this.ensureInitialized();
|
|
2628
|
-
this.log("info", `Warming up cache for ${files.length} files...`);
|
|
2629
|
-
const index = getGlobalIndex();
|
|
2630
|
-
await Promise.all(files.map((file) => index.warmup(file)));
|
|
2631
|
-
}
|
|
2632
|
-
/**
|
|
2633
|
-
* Clear all caches
|
|
2634
|
-
*/
|
|
2635
|
-
async clearCache() {
|
|
2636
|
-
this.log("info", "Clearing all caches...");
|
|
2637
|
-
const index = getGlobalIndex();
|
|
2638
|
-
await index.clear();
|
|
2639
|
-
}
|
|
2640
|
-
/**
|
|
2641
|
-
* Shutdown the engine
|
|
2642
|
-
*/
|
|
2643
|
-
async shutdown() {
|
|
2644
|
-
if (!this.isInitialized) {
|
|
2645
|
-
return;
|
|
2646
|
-
}
|
|
2647
|
-
this.log("info", "Shutting down Actionbook Engine...");
|
|
2648
|
-
if (this.config.watchMode) {
|
|
2649
|
-
await closeGlobalWatcher();
|
|
2650
|
-
}
|
|
2651
|
-
await closeGlobalIndex();
|
|
2652
|
-
resetGlobalIndexer();
|
|
2653
|
-
resetGlobalTracker();
|
|
2654
|
-
this.isInitialized = false;
|
|
2655
|
-
this.log("info", "Actionbook Engine shutdown complete");
|
|
2656
|
-
}
|
|
2657
|
-
/**
|
|
2658
|
-
* Setup file watcher
|
|
2659
|
-
*/
|
|
2660
|
-
setupWatcher() {
|
|
2661
|
-
this.watcher.on("change", (event) => {
|
|
2662
|
-
this.log("debug", `File changed: ${event.path} (${event.type})`);
|
|
2663
|
-
});
|
|
2664
|
-
this.watcher.on("indexed", (stats) => {
|
|
2665
|
-
this.log("info", `Indexed ${stats.added + stats.removed} files`);
|
|
2666
|
-
});
|
|
2667
|
-
this.watcher.on("error", (error) => {
|
|
2668
|
-
this.log("error", "Watcher error:", error);
|
|
2669
|
-
});
|
|
2670
|
-
}
|
|
2671
|
-
/**
|
|
2672
|
-
* Get files recursively
|
|
2673
|
-
*/
|
|
2674
|
-
async getFilesRecursively(dir, extensions) {
|
|
2675
|
-
const files = [];
|
|
2676
|
-
try {
|
|
2677
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2678
|
-
for (const entry of entries) {
|
|
2679
|
-
const fullPath = `${dir}/${entry.name}`;
|
|
2680
|
-
if (entry.isDirectory()) {
|
|
2681
|
-
if (!this.shouldIgnoreDirectory(entry.name)) {
|
|
2682
|
-
const subFiles = await this.getFilesRecursively(fullPath, extensions);
|
|
2683
|
-
files.push(...subFiles);
|
|
2684
|
-
}
|
|
2685
|
-
} else if (entry.isFile()) {
|
|
2686
|
-
if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
2687
|
-
files.push(fullPath);
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
} catch (error) {
|
|
2692
|
-
this.log("warn", `Failed to read directory ${dir}:`, error);
|
|
2693
|
-
}
|
|
2694
|
-
return files;
|
|
2695
|
-
}
|
|
2696
|
-
/**
|
|
2697
|
-
* Check if directory should be ignored
|
|
2698
|
-
*/
|
|
2699
|
-
shouldIgnoreDirectory(name) {
|
|
2700
|
-
const ignorePatterns = [
|
|
2701
|
-
"node_modules",
|
|
2702
|
-
".git",
|
|
2703
|
-
"dist",
|
|
2704
|
-
"build",
|
|
2705
|
-
".next",
|
|
2706
|
-
"coverage",
|
|
2707
|
-
".actionbook-cache"
|
|
2708
|
-
];
|
|
2709
|
-
return ignorePatterns.some((pattern) => name.includes(pattern));
|
|
2710
|
-
}
|
|
2711
|
-
/**
|
|
2712
|
-
* Ensure engine is initialized
|
|
2713
|
-
*/
|
|
2714
|
-
ensureInitialized() {
|
|
2715
|
-
if (!this.isInitialized) {
|
|
2716
|
-
throw new Error("Actionbook Engine not initialized. Call initialize() first.");
|
|
2717
|
-
}
|
|
2718
|
-
}
|
|
2719
|
-
/**
|
|
2720
|
-
* Log message
|
|
2721
|
-
*/
|
|
2722
|
-
log(level, message, ...args) {
|
|
2723
|
-
const levels = ["debug", "info", "warn", "error"];
|
|
2724
|
-
const currentLevel = levels.indexOf(this.config.logLevel);
|
|
2725
|
-
const messageLevel = levels.indexOf(level);
|
|
2726
|
-
if (messageLevel >= currentLevel) {
|
|
2727
|
-
console.log(`[Actionbook:${level.toUpperCase()}] ${message}`, ...args);
|
|
2728
|
-
}
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
function createEngine(config) {
|
|
2732
|
-
return new ActionbookEngine(config);
|
|
2733
|
-
}
|
|
22
|
+
import 'i18next-fs-backend';
|
|
23
|
+
import './chunks/claude-config.mjs';
|
|
24
|
+
import './chunks/json-config.mjs';
|
|
25
|
+
import './chunks/fs-operations.mjs';
|
|
26
|
+
import 'node:crypto';
|
|
27
|
+
import 'node:fs/promises';
|
|
28
|
+
import 'tinyexec';
|
|
2734
29
|
|
|
2735
30
|
const execAsync$1 = promisify(exec);
|
|
2736
31
|
class BaseCodeTool {
|
|
@@ -3494,1480 +789,6 @@ function createTool(name, config) {
|
|
|
3494
789
|
return factory.createTool(name, config);
|
|
3495
790
|
}
|
|
3496
791
|
|
|
3497
|
-
const VERSION = "1.0.0";
|
|
3498
|
-
function isCommandGroup(entry) {
|
|
3499
|
-
return "subcommands" in entry;
|
|
3500
|
-
}
|
|
3501
|
-
function parseArgs(argv) {
|
|
3502
|
-
const args = argv.slice(2);
|
|
3503
|
-
const command = [];
|
|
3504
|
-
const options = {};
|
|
3505
|
-
for (let i = 0; i < args.length; i++) {
|
|
3506
|
-
const arg = args[i];
|
|
3507
|
-
if (arg.startsWith("--")) {
|
|
3508
|
-
const key = arg.slice(2);
|
|
3509
|
-
const nextArg = args[i + 1];
|
|
3510
|
-
if (nextArg && !nextArg.startsWith("-")) {
|
|
3511
|
-
options[key] = nextArg;
|
|
3512
|
-
i++;
|
|
3513
|
-
} else {
|
|
3514
|
-
options[key] = true;
|
|
3515
|
-
}
|
|
3516
|
-
} else if (arg.startsWith("-")) {
|
|
3517
|
-
const key = arg.slice(1);
|
|
3518
|
-
const nextArg = args[i + 1];
|
|
3519
|
-
if (nextArg && !nextArg.startsWith("-")) {
|
|
3520
|
-
options[key] = nextArg;
|
|
3521
|
-
i++;
|
|
3522
|
-
} else {
|
|
3523
|
-
options[key] = true;
|
|
3524
|
-
}
|
|
3525
|
-
} else {
|
|
3526
|
-
command.push(arg);
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
return { command, options };
|
|
3530
|
-
}
|
|
3531
|
-
function showHelp() {
|
|
3532
|
-
console.log(`
|
|
3533
|
-
${ansis.bold("CCJK")} - Claude Code JK \u589E\u5F3A\u7248\u914D\u7F6E\u5DE5\u5177
|
|
3534
|
-
|
|
3535
|
-
${ansis.bold("\u7528\u6CD5:")}
|
|
3536
|
-
ccjk [\u547D\u4EE4] [\u9009\u9879]
|
|
3537
|
-
|
|
3538
|
-
${ansis.bold("\u547D\u4EE4:")}
|
|
3539
|
-
${ansis.cyan("menu")} \u542F\u52A8\u4EA4\u4E92\u5F0F\u83DC\u5355
|
|
3540
|
-
${ansis.cyan("api setup")} \u5FEB\u901F\u914D\u7F6E API
|
|
3541
|
-
${ansis.cyan("api status")} \u67E5\u770B API \u914D\u7F6E\u72B6\u6001
|
|
3542
|
-
${ansis.cyan("api switch")} \u5207\u6362 API \u914D\u7F6E
|
|
3543
|
-
${ansis.cyan("api providers")} \u67E5\u770B\u53EF\u7528\u7684 API \u63D0\u4F9B\u5546
|
|
3544
|
-
${ansis.cyan("init")} \u521D\u59CB\u5316 CCJK \u914D\u7F6E
|
|
3545
|
-
${ansis.cyan("mcp list")} \u5217\u51FA\u5DF2\u5B89\u88C5\u7684 MCP \u670D\u52A1\u5668
|
|
3546
|
-
${ansis.cyan("mcp search")} <q> \u641C\u7D22 MCP \u670D\u52A1\u5668
|
|
3547
|
-
${ansis.cyan("mcp install")} <n> \u5B89\u88C5 MCP \u670D\u52A1\u5668
|
|
3548
|
-
${ansis.cyan("mcp uninstall")} <n> \u5378\u8F7D MCP \u670D\u52A1\u5668
|
|
3549
|
-
${ansis.cyan("doctor")} \u8FD0\u884C\u8BCA\u65AD\u68C0\u67E5
|
|
3550
|
-
${ansis.cyan("update")} \u68C0\u67E5\u5E76\u5B89\u88C5\u66F4\u65B0
|
|
3551
|
-
${ansis.cyan("config show")} \u663E\u793A\u5F53\u524D\u914D\u7F6E
|
|
3552
|
-
${ansis.cyan("config reset")} \u91CD\u7F6E\u914D\u7F6E
|
|
3553
|
-
|
|
3554
|
-
${ansis.bold("\u9009\u9879:")}
|
|
3555
|
-
${ansis.cyan("-h, --help")} \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
|
|
3556
|
-
${ansis.cyan("-v, --version")} \u663E\u793A\u7248\u672C\u53F7
|
|
3557
|
-
${ansis.cyan("-l, --locale")} \u8BED\u8A00\u8BBE\u7F6E (\u9ED8\u8BA4: zh-CN)
|
|
3558
|
-
|
|
3559
|
-
${ansis.bold("\u793A\u4F8B:")}
|
|
3560
|
-
ccjk \u542F\u52A8\u4EA4\u4E92\u5F0F\u83DC\u5355
|
|
3561
|
-
ccjk api setup \u5FEB\u901F\u914D\u7F6E API
|
|
3562
|
-
ccjk mcp install github \u5B89\u88C5 GitHub MCP \u670D\u52A1\u5668
|
|
3563
|
-
`);
|
|
3564
|
-
}
|
|
3565
|
-
function getCommands() {
|
|
3566
|
-
return {
|
|
3567
|
-
menu: {
|
|
3568
|
-
description: "\u542F\u52A8\u4EA4\u4E92\u5F0F\u83DC\u5355",
|
|
3569
|
-
action: async (args) => {
|
|
3570
|
-
const { startMenu } = await Promise.resolve().then(function () { return index$7; });
|
|
3571
|
-
const locale = args[0] || "zh-CN";
|
|
3572
|
-
await startMenu({ locale });
|
|
3573
|
-
}
|
|
3574
|
-
},
|
|
3575
|
-
api: {
|
|
3576
|
-
description: "API \u914D\u7F6E\u7BA1\u7406",
|
|
3577
|
-
subcommands: {
|
|
3578
|
-
setup: {
|
|
3579
|
-
description: "\u5FEB\u901F\u914D\u7F6E API",
|
|
3580
|
-
action: async (args) => {
|
|
3581
|
-
const { quickApiSetup } = await import('./chunks/api-adapter.mjs');
|
|
3582
|
-
const locale = args[0] || "zh-CN";
|
|
3583
|
-
await quickApiSetup(locale);
|
|
3584
|
-
}
|
|
3585
|
-
},
|
|
3586
|
-
status: {
|
|
3587
|
-
description: "\u67E5\u770B API \u914D\u7F6E\u72B6\u6001",
|
|
3588
|
-
action: async (args) => {
|
|
3589
|
-
const { detectApiStatus, getApiStatusSummary } = await import('./chunks/api-adapter.mjs');
|
|
3590
|
-
const status = await detectApiStatus();
|
|
3591
|
-
const locale = args[0] || "zh-CN";
|
|
3592
|
-
console.log(getApiStatusSummary(status, locale));
|
|
3593
|
-
}
|
|
3594
|
-
},
|
|
3595
|
-
switch: {
|
|
3596
|
-
description: "\u5207\u6362 API \u914D\u7F6E",
|
|
3597
|
-
action: async () => {
|
|
3598
|
-
const { configSwitchCommand } = await import('./chunks/config-switch.mjs');
|
|
3599
|
-
await configSwitchCommand({ codeType: "claude-code" });
|
|
3600
|
-
}
|
|
3601
|
-
},
|
|
3602
|
-
providers: {
|
|
3603
|
-
description: "\u67E5\u770B\u53EF\u7528\u7684 API \u63D0\u4F9B\u5546",
|
|
3604
|
-
action: async () => {
|
|
3605
|
-
const { providerRegistry } = await import('./chunks/provider-registry.mjs');
|
|
3606
|
-
const providers = providerRegistry.getAllMetadata();
|
|
3607
|
-
console.log("\n\u53EF\u7528\u7684 API \u63D0\u4F9B\u5546:\n");
|
|
3608
|
-
providers.forEach((p) => {
|
|
3609
|
-
const popular = p.popular ? " \u2B50" : "";
|
|
3610
|
-
console.log(` ${p.icon || "\u2022"} ${p.name}${popular}`);
|
|
3611
|
-
console.log(` ${p.description}`);
|
|
3612
|
-
console.log(` \u8BBE\u7F6E\u65F6\u95F4: ${p.setupTime}
|
|
3613
|
-
`);
|
|
3614
|
-
});
|
|
3615
|
-
}
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
},
|
|
3619
|
-
init: {
|
|
3620
|
-
description: "\u521D\u59CB\u5316 CCJK \u914D\u7F6E",
|
|
3621
|
-
action: async () => {
|
|
3622
|
-
const { init } = await import('./chunks/init.mjs');
|
|
3623
|
-
await init({});
|
|
3624
|
-
}
|
|
3625
|
-
},
|
|
3626
|
-
mcp: {
|
|
3627
|
-
description: "MCP \u670D\u52A1\u5668\u7BA1\u7406",
|
|
3628
|
-
subcommands: {
|
|
3629
|
-
list: {
|
|
3630
|
-
description: "\u5217\u51FA\u5DF2\u5B89\u88C5\u7684 MCP \u670D\u52A1\u5668",
|
|
3631
|
-
action: async () => {
|
|
3632
|
-
const { mcpList } = await import('./chunks/mcp-market.mjs');
|
|
3633
|
-
await mcpList();
|
|
3634
|
-
}
|
|
3635
|
-
},
|
|
3636
|
-
search: {
|
|
3637
|
-
description: "\u641C\u7D22 MCP \u670D\u52A1\u5668",
|
|
3638
|
-
action: async (args) => {
|
|
3639
|
-
const query = args[0];
|
|
3640
|
-
if (!query) {
|
|
3641
|
-
console.log(ansis.red("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u641C\u7D22\u5173\u952E\u8BCD"));
|
|
3642
|
-
return;
|
|
3643
|
-
}
|
|
3644
|
-
const { mcpSearch } = await import('./chunks/mcp-market.mjs');
|
|
3645
|
-
await mcpSearch(query);
|
|
3646
|
-
}
|
|
3647
|
-
},
|
|
3648
|
-
install: {
|
|
3649
|
-
description: "\u5B89\u88C5 MCP \u670D\u52A1\u5668",
|
|
3650
|
-
action: async (args) => {
|
|
3651
|
-
const name = args[0];
|
|
3652
|
-
if (!name) {
|
|
3653
|
-
console.log(ansis.red("\u9519\u8BEF: \u8BF7\u63D0\u4F9B MCP \u670D\u52A1\u5668\u540D\u79F0"));
|
|
3654
|
-
return;
|
|
3655
|
-
}
|
|
3656
|
-
const { mcpInstall } = await import('./chunks/mcp-market.mjs');
|
|
3657
|
-
await mcpInstall(name);
|
|
3658
|
-
}
|
|
3659
|
-
},
|
|
3660
|
-
uninstall: {
|
|
3661
|
-
description: "\u5378\u8F7D MCP \u670D\u52A1\u5668",
|
|
3662
|
-
action: async (args) => {
|
|
3663
|
-
const name = args[0];
|
|
3664
|
-
if (!name) {
|
|
3665
|
-
console.log(ansis.red("\u9519\u8BEF: \u8BF7\u63D0\u4F9B MCP \u670D\u52A1\u5668\u540D\u79F0"));
|
|
3666
|
-
return;
|
|
3667
|
-
}
|
|
3668
|
-
const { mcpUninstall } = await import('./chunks/mcp-market.mjs');
|
|
3669
|
-
await mcpUninstall(name);
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
}
|
|
3673
|
-
},
|
|
3674
|
-
doctor: {
|
|
3675
|
-
description: "\u8FD0\u884C\u8BCA\u65AD\u68C0\u67E5",
|
|
3676
|
-
action: async () => {
|
|
3677
|
-
const { doctor } = await import('./chunks/doctor.mjs');
|
|
3678
|
-
await doctor();
|
|
3679
|
-
}
|
|
3680
|
-
},
|
|
3681
|
-
update: {
|
|
3682
|
-
description: "\u68C0\u67E5\u5E76\u5B89\u88C5\u66F4\u65B0",
|
|
3683
|
-
action: async () => {
|
|
3684
|
-
const { update } = await import('./chunks/update.mjs');
|
|
3685
|
-
await update({});
|
|
3686
|
-
}
|
|
3687
|
-
},
|
|
3688
|
-
config: {
|
|
3689
|
-
description: "\u914D\u7F6E\u7BA1\u7406",
|
|
3690
|
-
subcommands: {
|
|
3691
|
-
show: {
|
|
3692
|
-
description: "\u663E\u793A\u5F53\u524D\u914D\u7F6E",
|
|
3693
|
-
action: async () => {
|
|
3694
|
-
const { readZcfConfig } = await import('./chunks/ccjk-config.mjs');
|
|
3695
|
-
const config = readZcfConfig();
|
|
3696
|
-
console.log(JSON.stringify(config, null, 2));
|
|
3697
|
-
}
|
|
3698
|
-
},
|
|
3699
|
-
reset: {
|
|
3700
|
-
description: "\u91CD\u7F6E\u914D\u7F6E",
|
|
3701
|
-
action: async () => {
|
|
3702
|
-
const { confirm } = await inquirer.prompt([
|
|
3703
|
-
{
|
|
3704
|
-
type: "confirm",
|
|
3705
|
-
name: "confirm",
|
|
3706
|
-
message: "\u786E\u5B9A\u8981\u91CD\u7F6E\u6240\u6709\u914D\u7F6E\u5417\uFF1F",
|
|
3707
|
-
default: false
|
|
3708
|
-
}
|
|
3709
|
-
]);
|
|
3710
|
-
if (!confirm) {
|
|
3711
|
-
console.log("\u5DF2\u53D6\u6D88");
|
|
3712
|
-
return;
|
|
3713
|
-
}
|
|
3714
|
-
console.log("\u914D\u7F6E\u5DF2\u91CD\u7F6E");
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
};
|
|
3720
|
-
}
|
|
3721
|
-
async function defaultAction() {
|
|
3722
|
-
const { needsApiSetup, quickApiSetup } = await import('./chunks/api-adapter.mjs');
|
|
3723
|
-
if (await needsApiSetup()) {
|
|
3724
|
-
console.log("");
|
|
3725
|
-
console.log(ansis.yellow("\u26A0\uFE0F \u68C0\u6D4B\u5230 API \u5C1A\u672A\u914D\u7F6E"));
|
|
3726
|
-
console.log(ansis.dim("\u5EFA\u8BAE\u5148\u914D\u7F6E API \u4EE5\u4F7F\u7528 Claude Code"));
|
|
3727
|
-
console.log("");
|
|
3728
|
-
const { action } = await inquirer.prompt([
|
|
3729
|
-
{
|
|
3730
|
-
type: "list",
|
|
3731
|
-
name: "action",
|
|
3732
|
-
message: "\u8BF7\u9009\u62E9:",
|
|
3733
|
-
choices: [
|
|
3734
|
-
{ name: "\u26A1 \u5FEB\u901F\u914D\u7F6E API", value: "setup" },
|
|
3735
|
-
{ name: "\u{1F4CB} \u8FDB\u5165\u4E3B\u83DC\u5355", value: "menu" },
|
|
3736
|
-
{ name: "\u274C \u9000\u51FA", value: "exit" }
|
|
3737
|
-
]
|
|
3738
|
-
}
|
|
3739
|
-
]);
|
|
3740
|
-
if (action === "setup") {
|
|
3741
|
-
await quickApiSetup("zh-CN");
|
|
3742
|
-
} else if (action === "menu") {
|
|
3743
|
-
const { startMenu } = await Promise.resolve().then(function () { return index$7; });
|
|
3744
|
-
await startMenu();
|
|
3745
|
-
}
|
|
3746
|
-
} else {
|
|
3747
|
-
const { startMenu } = await Promise.resolve().then(function () { return index$7; });
|
|
3748
|
-
await startMenu();
|
|
3749
|
-
}
|
|
3750
|
-
}
|
|
3751
|
-
async function runCli() {
|
|
3752
|
-
const { command, options } = parseArgs(process__default.argv);
|
|
3753
|
-
if (options.help || options.h) {
|
|
3754
|
-
showHelp();
|
|
3755
|
-
return;
|
|
3756
|
-
}
|
|
3757
|
-
if (options.version || options.v) {
|
|
3758
|
-
console.log(`ccjk v${VERSION}`);
|
|
3759
|
-
return;
|
|
3760
|
-
}
|
|
3761
|
-
const commands = getCommands();
|
|
3762
|
-
if (command.length === 0) {
|
|
3763
|
-
await defaultAction();
|
|
3764
|
-
return;
|
|
3765
|
-
}
|
|
3766
|
-
const mainCmd = command[0];
|
|
3767
|
-
const cmdEntry = commands[mainCmd];
|
|
3768
|
-
if (!cmdEntry) {
|
|
3769
|
-
console.log(ansis.red(`\u9519\u8BEF: \u672A\u77E5\u547D\u4EE4 '${mainCmd}'`));
|
|
3770
|
-
console.log(ansis.dim("\u8FD0\u884C ccjk --help \u67E5\u770B\u53EF\u7528\u547D\u4EE4"));
|
|
3771
|
-
process__default.exit(1);
|
|
3772
|
-
}
|
|
3773
|
-
if (isCommandGroup(cmdEntry)) {
|
|
3774
|
-
const subCmd = command[1];
|
|
3775
|
-
if (!subCmd) {
|
|
3776
|
-
console.log(ansis.yellow(`${mainCmd} \u5B50\u547D\u4EE4:`));
|
|
3777
|
-
Object.entries(cmdEntry.subcommands).forEach(([name, def]) => {
|
|
3778
|
-
console.log(` ${ansis.cyan(name)} ${def.description}`);
|
|
3779
|
-
});
|
|
3780
|
-
return;
|
|
3781
|
-
}
|
|
3782
|
-
const subCmdDef = cmdEntry.subcommands[subCmd];
|
|
3783
|
-
if (!subCmdDef) {
|
|
3784
|
-
console.log(ansis.red(`\u9519\u8BEF: \u672A\u77E5\u5B50\u547D\u4EE4 '${mainCmd} ${subCmd}'`));
|
|
3785
|
-
console.log(ansis.dim(`\u8FD0\u884C ccjk ${mainCmd} \u67E5\u770B\u53EF\u7528\u5B50\u547D\u4EE4`));
|
|
3786
|
-
process__default.exit(1);
|
|
3787
|
-
}
|
|
3788
|
-
await subCmdDef.action(command.slice(2));
|
|
3789
|
-
} else {
|
|
3790
|
-
await cmdEntry.action(command.slice(1));
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
|
|
3794
|
-
const cli = {
|
|
3795
|
-
__proto__: null,
|
|
3796
|
-
runCli: runCli
|
|
3797
|
-
};
|
|
3798
|
-
|
|
3799
|
-
const coreGroup = {
|
|
3800
|
-
id: "core",
|
|
3801
|
-
label: {
|
|
3802
|
-
"en": "Core Features",
|
|
3803
|
-
"zh-CN": "\u6838\u5FC3\u529F\u80FD"
|
|
3804
|
-
},
|
|
3805
|
-
icon: "\u2B50",
|
|
3806
|
-
priority: "core",
|
|
3807
|
-
items: [
|
|
3808
|
-
{
|
|
3809
|
-
id: "api-config",
|
|
3810
|
-
label: {
|
|
3811
|
-
"en": "\u{1F511} API Configuration",
|
|
3812
|
-
"zh-CN": "\u{1F511} API \u914D\u7F6E\u7BA1\u7406"
|
|
3813
|
-
},
|
|
3814
|
-
description: {
|
|
3815
|
-
"en": "One-click setup for AI providers (Anthropic, OpenAI, Azure, etc.)",
|
|
3816
|
-
"zh-CN": "\u4E00\u952E\u914D\u7F6E AI \u670D\u52A1\u63D0\u4F9B\u5546\uFF08Anthropic\u3001OpenAI\u3001Azure \u7B49\uFF09"
|
|
3817
|
-
},
|
|
3818
|
-
shortcut: "a",
|
|
3819
|
-
action: {
|
|
3820
|
-
type: "command",
|
|
3821
|
-
handler: "api-config"
|
|
3822
|
-
},
|
|
3823
|
-
priority: "core"
|
|
3824
|
-
}
|
|
3825
|
-
]
|
|
3826
|
-
};
|
|
3827
|
-
const featuresGroup = {
|
|
3828
|
-
id: "features",
|
|
3829
|
-
label: {
|
|
3830
|
-
"en": "Features",
|
|
3831
|
-
"zh-CN": "\u529F\u80FD\u6A21\u5757"
|
|
3832
|
-
},
|
|
3833
|
-
icon: "\u{1F6E0}\uFE0F",
|
|
3834
|
-
priority: "feature",
|
|
3835
|
-
items: [
|
|
3836
|
-
{
|
|
3837
|
-
id: "skills",
|
|
3838
|
-
label: {
|
|
3839
|
-
"en": "\u{1F4DA} Skills Management",
|
|
3840
|
-
"zh-CN": "\u{1F4DA} Skills \u7BA1\u7406"
|
|
3841
|
-
},
|
|
3842
|
-
description: {
|
|
3843
|
-
"en": "Manage and configure AI skill templates",
|
|
3844
|
-
"zh-CN": "\u7BA1\u7406\u548C\u914D\u7F6E AI \u6280\u80FD\u6A21\u677F"
|
|
3845
|
-
},
|
|
3846
|
-
shortcut: "s",
|
|
3847
|
-
action: {
|
|
3848
|
-
type: "command",
|
|
3849
|
-
handler: "skills"
|
|
3850
|
-
},
|
|
3851
|
-
priority: "feature"
|
|
3852
|
-
},
|
|
3853
|
-
{
|
|
3854
|
-
id: "mcp",
|
|
3855
|
-
label: {
|
|
3856
|
-
"en": "\u{1F50C} MCP Servers",
|
|
3857
|
-
"zh-CN": "\u{1F50C} MCP \u670D\u52A1\u5668"
|
|
3858
|
-
},
|
|
3859
|
-
description: {
|
|
3860
|
-
"en": "Configure Model Context Protocol servers",
|
|
3861
|
-
"zh-CN": "\u914D\u7F6E Model Context Protocol \u670D\u52A1\u5668"
|
|
3862
|
-
},
|
|
3863
|
-
shortcut: "m",
|
|
3864
|
-
action: {
|
|
3865
|
-
type: "command",
|
|
3866
|
-
handler: "mcp"
|
|
3867
|
-
},
|
|
3868
|
-
priority: "feature"
|
|
3869
|
-
},
|
|
3870
|
-
{
|
|
3871
|
-
id: "session",
|
|
3872
|
-
label: {
|
|
3873
|
-
"en": "\u{1F4BE} Session Management",
|
|
3874
|
-
"zh-CN": "\u{1F4BE} Session \u7BA1\u7406"
|
|
3875
|
-
},
|
|
3876
|
-
description: {
|
|
3877
|
-
"en": "Manage session history and recovery",
|
|
3878
|
-
"zh-CN": "\u7BA1\u7406\u4F1A\u8BDD\u5386\u53F2\u548C\u6062\u590D"
|
|
3879
|
-
},
|
|
3880
|
-
shortcut: "e",
|
|
3881
|
-
action: {
|
|
3882
|
-
type: "command",
|
|
3883
|
-
handler: "session"
|
|
3884
|
-
},
|
|
3885
|
-
priority: "feature"
|
|
3886
|
-
}
|
|
3887
|
-
]
|
|
3888
|
-
};
|
|
3889
|
-
const settingsGroup$1 = {
|
|
3890
|
-
id: "settings",
|
|
3891
|
-
label: {
|
|
3892
|
-
"en": "Settings",
|
|
3893
|
-
"zh-CN": "\u7CFB\u7EDF\u8BBE\u7F6E"
|
|
3894
|
-
},
|
|
3895
|
-
icon: "\u2699\uFE0F",
|
|
3896
|
-
priority: "setting",
|
|
3897
|
-
items: [
|
|
3898
|
-
{
|
|
3899
|
-
id: "settings",
|
|
3900
|
-
label: {
|
|
3901
|
-
"en": "\u2699\uFE0F Settings",
|
|
3902
|
-
"zh-CN": "\u2699\uFE0F \u8BBE\u7F6E"
|
|
3903
|
-
},
|
|
3904
|
-
description: {
|
|
3905
|
-
"en": "Configure CCJK global settings",
|
|
3906
|
-
"zh-CN": "\u914D\u7F6E CCJK \u5168\u5C40\u8BBE\u7F6E"
|
|
3907
|
-
},
|
|
3908
|
-
shortcut: "t",
|
|
3909
|
-
action: {
|
|
3910
|
-
type: "command",
|
|
3911
|
-
handler: "settings"
|
|
3912
|
-
},
|
|
3913
|
-
priority: "setting"
|
|
3914
|
-
}
|
|
3915
|
-
]
|
|
3916
|
-
};
|
|
3917
|
-
const footerItems = [
|
|
3918
|
-
{
|
|
3919
|
-
id: "help",
|
|
3920
|
-
label: {
|
|
3921
|
-
"en": "\u2753 Help",
|
|
3922
|
-
"zh-CN": "\u2753 \u5E2E\u52A9"
|
|
3923
|
-
},
|
|
3924
|
-
description: {
|
|
3925
|
-
"en": "View help information and documentation",
|
|
3926
|
-
"zh-CN": "\u67E5\u770B\u5E2E\u52A9\u4FE1\u606F\u548C\u6587\u6863"
|
|
3927
|
-
},
|
|
3928
|
-
shortcut: "h",
|
|
3929
|
-
action: {
|
|
3930
|
-
type: "command",
|
|
3931
|
-
handler: "help"
|
|
3932
|
-
},
|
|
3933
|
-
priority: "help"
|
|
3934
|
-
},
|
|
3935
|
-
{
|
|
3936
|
-
id: "exit",
|
|
3937
|
-
label: {
|
|
3938
|
-
"en": "\u{1F6AA} Exit",
|
|
3939
|
-
"zh-CN": "\u{1F6AA} \u9000\u51FA"
|
|
3940
|
-
},
|
|
3941
|
-
description: {
|
|
3942
|
-
"en": "Exit CCJK menu",
|
|
3943
|
-
"zh-CN": "\u9000\u51FA CCJK \u83DC\u5355"
|
|
3944
|
-
},
|
|
3945
|
-
shortcut: "q",
|
|
3946
|
-
action: {
|
|
3947
|
-
type: "command",
|
|
3948
|
-
handler: "exit"
|
|
3949
|
-
},
|
|
3950
|
-
priority: "help"
|
|
3951
|
-
}
|
|
3952
|
-
];
|
|
3953
|
-
const mainMenuConfig$1 = {
|
|
3954
|
-
title: {
|
|
3955
|
-
"en": "CCJK - Claude Code Chinese Enhanced",
|
|
3956
|
-
"zh-CN": "CCJK - Claude Code \u4E2D\u6587\u589E\u5F3A\u7248"
|
|
3957
|
-
},
|
|
3958
|
-
groups: [coreGroup, featuresGroup, settingsGroup$1],
|
|
3959
|
-
footer: footerItems
|
|
3960
|
-
};
|
|
3961
|
-
|
|
3962
|
-
const apiConfigMenu = {
|
|
3963
|
-
id: "api-config",
|
|
3964
|
-
label: {
|
|
3965
|
-
"en": "API Configuration",
|
|
3966
|
-
"zh-CN": "API \u914D\u7F6E\u7BA1\u7406"
|
|
3967
|
-
},
|
|
3968
|
-
description: {
|
|
3969
|
-
"en": "One-click API setup, start using Claude",
|
|
3970
|
-
"zh-CN": "\u4E00\u952E\u914D\u7F6E API\uFF0C\u5F00\u59CB\u4F7F\u7528 Claude"
|
|
3971
|
-
},
|
|
3972
|
-
icon: "\u{1F511}",
|
|
3973
|
-
shortcut: "1",
|
|
3974
|
-
priority: "core",
|
|
3975
|
-
submenu: [
|
|
3976
|
-
{
|
|
3977
|
-
id: "api-official",
|
|
3978
|
-
label: {
|
|
3979
|
-
"en": "Use Official Login (No API needed)",
|
|
3980
|
-
"zh-CN": "\u4F7F\u7528\u5B98\u65B9\u767B\u5F55\uFF08\u4E0D\u914D\u7F6E API\uFF09"
|
|
3981
|
-
},
|
|
3982
|
-
description: {
|
|
3983
|
-
"en": "Recommended for new users",
|
|
3984
|
-
"zh-CN": "\u63A8\u8350\u65B0\u7528\u6237\u4F7F\u7528"
|
|
3985
|
-
},
|
|
3986
|
-
icon: "\u2728",
|
|
3987
|
-
shortcut: "1",
|
|
3988
|
-
action: { type: "command", handler: "api:official" }
|
|
3989
|
-
},
|
|
3990
|
-
{
|
|
3991
|
-
id: "api-custom",
|
|
3992
|
-
label: {
|
|
3993
|
-
"en": "Custom API Configuration",
|
|
3994
|
-
"zh-CN": "\u81EA\u5B9A\u4E49 API \u914D\u7F6E"
|
|
3995
|
-
},
|
|
3996
|
-
description: {
|
|
3997
|
-
"en": "Configure API Key and URL",
|
|
3998
|
-
"zh-CN": "\u914D\u7F6E API Key \u548C URL"
|
|
3999
|
-
},
|
|
4000
|
-
icon: "\u2699\uFE0F",
|
|
4001
|
-
shortcut: "2",
|
|
4002
|
-
action: { type: "command", handler: "api:custom" }
|
|
4003
|
-
},
|
|
4004
|
-
{
|
|
4005
|
-
id: "api-ccr",
|
|
4006
|
-
label: {
|
|
4007
|
-
"en": "Use CCR Proxy",
|
|
4008
|
-
"zh-CN": "\u4F7F\u7528 CCR \u4EE3\u7406"
|
|
4009
|
-
},
|
|
4010
|
-
description: {
|
|
4011
|
-
"en": "Access API through proxy",
|
|
4012
|
-
"zh-CN": "\u901A\u8FC7\u4EE3\u7406\u8BBF\u95EE API"
|
|
4013
|
-
},
|
|
4014
|
-
icon: "\u{1F310}",
|
|
4015
|
-
shortcut: "3",
|
|
4016
|
-
action: { type: "command", handler: "api:ccr" }
|
|
4017
|
-
},
|
|
4018
|
-
{
|
|
4019
|
-
id: "api-switch",
|
|
4020
|
-
label: {
|
|
4021
|
-
"en": "Switch API Configuration",
|
|
4022
|
-
"zh-CN": "\u5207\u6362 API \u914D\u7F6E"
|
|
4023
|
-
},
|
|
4024
|
-
description: {
|
|
4025
|
-
"en": "Switch between configurations",
|
|
4026
|
-
"zh-CN": "\u5728\u591A\u4E2A\u914D\u7F6E\u95F4\u5207\u6362"
|
|
4027
|
-
},
|
|
4028
|
-
icon: "\u{1F504}",
|
|
4029
|
-
shortcut: "4",
|
|
4030
|
-
action: { type: "command", handler: "api:switch" }
|
|
4031
|
-
},
|
|
4032
|
-
{
|
|
4033
|
-
id: "api-status",
|
|
4034
|
-
label: {
|
|
4035
|
-
"en": "View Current Configuration",
|
|
4036
|
-
"zh-CN": "\u67E5\u770B\u5F53\u524D\u914D\u7F6E"
|
|
4037
|
-
},
|
|
4038
|
-
description: {
|
|
4039
|
-
"en": "Show current API settings",
|
|
4040
|
-
"zh-CN": "\u663E\u793A\u5F53\u524D API \u8BBE\u7F6E"
|
|
4041
|
-
},
|
|
4042
|
-
icon: "\u{1F4CB}",
|
|
4043
|
-
shortcut: "5",
|
|
4044
|
-
action: { type: "command", handler: "api:status" }
|
|
4045
|
-
},
|
|
4046
|
-
{
|
|
4047
|
-
id: "api-skip",
|
|
4048
|
-
label: {
|
|
4049
|
-
"en": "Skip API Configuration",
|
|
4050
|
-
"zh-CN": "\u8DF3\u8FC7 API \u914D\u7F6E"
|
|
4051
|
-
},
|
|
4052
|
-
description: {
|
|
4053
|
-
"en": "Configure later",
|
|
4054
|
-
"zh-CN": "\u7A0D\u540E\u914D\u7F6E"
|
|
4055
|
-
},
|
|
4056
|
-
icon: "\u23ED\uFE0F",
|
|
4057
|
-
shortcut: "6",
|
|
4058
|
-
action: { type: "function", handler: async () => {
|
|
4059
|
-
} }
|
|
4060
|
-
}
|
|
4061
|
-
]
|
|
4062
|
-
};
|
|
4063
|
-
const quickStartGroup = {
|
|
4064
|
-
id: "quick-start",
|
|
4065
|
-
label: {
|
|
4066
|
-
"en": "Quick Start",
|
|
4067
|
-
"zh-CN": "\u5FEB\u901F\u5F00\u59CB"
|
|
4068
|
-
},
|
|
4069
|
-
icon: "\u{1F680}",
|
|
4070
|
-
priority: "feature",
|
|
4071
|
-
items: [
|
|
4072
|
-
{
|
|
4073
|
-
id: "init-project",
|
|
4074
|
-
label: {
|
|
4075
|
-
"en": "Initialize Project",
|
|
4076
|
-
"zh-CN": "\u521D\u59CB\u5316\u9879\u76EE"
|
|
4077
|
-
},
|
|
4078
|
-
description: {
|
|
4079
|
-
"en": "Configure CCJK for current project",
|
|
4080
|
-
"zh-CN": "\u4E3A\u5F53\u524D\u9879\u76EE\u914D\u7F6E CCJK"
|
|
4081
|
-
},
|
|
4082
|
-
icon: "\u{1F4E6}",
|
|
4083
|
-
action: { type: "command", handler: "ccjk:init" }
|
|
4084
|
-
},
|
|
4085
|
-
{
|
|
4086
|
-
id: "install-skills",
|
|
4087
|
-
label: {
|
|
4088
|
-
"en": "Install Skills",
|
|
4089
|
-
"zh-CN": "\u5B89\u88C5\u6280\u80FD"
|
|
4090
|
-
},
|
|
4091
|
-
description: {
|
|
4092
|
-
"en": "One-click install common skill packs",
|
|
4093
|
-
"zh-CN": "\u4E00\u952E\u5B89\u88C5\u5E38\u7528\u6280\u80FD\u5305"
|
|
4094
|
-
},
|
|
4095
|
-
icon: "\u{1F3AF}",
|
|
4096
|
-
action: { type: "command", handler: "ccjk:skills" }
|
|
4097
|
-
},
|
|
4098
|
-
{
|
|
4099
|
-
id: "setup-mcp",
|
|
4100
|
-
label: {
|
|
4101
|
-
"en": "Setup MCP Services",
|
|
4102
|
-
"zh-CN": "\u914D\u7F6E MCP \u670D\u52A1"
|
|
4103
|
-
},
|
|
4104
|
-
description: {
|
|
4105
|
-
"en": "Configure MCP service connections",
|
|
4106
|
-
"zh-CN": "\u8BBE\u7F6E MCP \u670D\u52A1\u8FDE\u63A5"
|
|
4107
|
-
},
|
|
4108
|
-
icon: "\u{1F50C}",
|
|
4109
|
-
action: { type: "command", handler: "ccjk:mcp" }
|
|
4110
|
-
}
|
|
4111
|
-
]
|
|
4112
|
-
};
|
|
4113
|
-
const projectManagementGroup = {
|
|
4114
|
-
id: "project-management",
|
|
4115
|
-
label: {
|
|
4116
|
-
"en": "Project Management",
|
|
4117
|
-
"zh-CN": "\u9879\u76EE\u7BA1\u7406"
|
|
4118
|
-
},
|
|
4119
|
-
icon: "\u{1F6E0}\uFE0F",
|
|
4120
|
-
priority: "feature",
|
|
4121
|
-
items: [
|
|
4122
|
-
{
|
|
4123
|
-
id: "skills-management",
|
|
4124
|
-
label: {
|
|
4125
|
-
"en": "Skills Management",
|
|
4126
|
-
"zh-CN": "Skills \u6280\u80FD\u7BA1\u7406"
|
|
4127
|
-
},
|
|
4128
|
-
description: {
|
|
4129
|
-
"en": "Install, view, remove skills",
|
|
4130
|
-
"zh-CN": "\u5B89\u88C5\u3001\u67E5\u770B\u3001\u5220\u9664\u6280\u80FD"
|
|
4131
|
-
},
|
|
4132
|
-
icon: "\u{1F3AF}",
|
|
4133
|
-
action: { type: "command", handler: "ccjk:skills" }
|
|
4134
|
-
},
|
|
4135
|
-
{
|
|
4136
|
-
id: "mcp-management",
|
|
4137
|
-
label: {
|
|
4138
|
-
"en": "MCP Services",
|
|
4139
|
-
"zh-CN": "MCP \u670D\u52A1\u7BA1\u7406"
|
|
4140
|
-
},
|
|
4141
|
-
description: {
|
|
4142
|
-
"en": "Configure, test MCP services",
|
|
4143
|
-
"zh-CN": "\u914D\u7F6E\u3001\u6D4B\u8BD5 MCP \u670D\u52A1"
|
|
4144
|
-
},
|
|
4145
|
-
icon: "\u{1F50C}",
|
|
4146
|
-
action: { type: "command", handler: "ccjk:mcp" }
|
|
4147
|
-
},
|
|
4148
|
-
{
|
|
4149
|
-
id: "agents-management",
|
|
4150
|
-
label: {
|
|
4151
|
-
"en": "Agents Management",
|
|
4152
|
-
"zh-CN": "Agents \u4EE3\u7406\u7BA1\u7406"
|
|
4153
|
-
},
|
|
4154
|
-
description: {
|
|
4155
|
-
"en": "Create, edit AI agents",
|
|
4156
|
-
"zh-CN": "\u521B\u5EFA\u3001\u7F16\u8F91 AI \u4EE3\u7406"
|
|
4157
|
-
},
|
|
4158
|
-
icon: "\u{1F916}",
|
|
4159
|
-
action: { type: "command", handler: "ccjk:agents" }
|
|
4160
|
-
},
|
|
4161
|
-
{
|
|
4162
|
-
id: "hooks-management",
|
|
4163
|
-
label: {
|
|
4164
|
-
"en": "Hooks Management",
|
|
4165
|
-
"zh-CN": "Hooks \u94A9\u5B50\u7BA1\u7406"
|
|
4166
|
-
},
|
|
4167
|
-
description: {
|
|
4168
|
-
"en": "Configure Git hooks",
|
|
4169
|
-
"zh-CN": "\u914D\u7F6E Git \u94A9\u5B50"
|
|
4170
|
-
},
|
|
4171
|
-
icon: "\u{1FA9D}",
|
|
4172
|
-
action: { type: "command", handler: "ccjk:hooks" }
|
|
4173
|
-
}
|
|
4174
|
-
]
|
|
4175
|
-
};
|
|
4176
|
-
const sessionManagementGroup = {
|
|
4177
|
-
id: "session-management",
|
|
4178
|
-
label: {
|
|
4179
|
-
"en": "Session & Context",
|
|
4180
|
-
"zh-CN": "\u4F1A\u8BDD\u7BA1\u7406"
|
|
4181
|
-
},
|
|
4182
|
-
icon: "\u{1F4AC}",
|
|
4183
|
-
priority: "feature",
|
|
4184
|
-
items: [
|
|
4185
|
-
{
|
|
4186
|
-
id: "session-create",
|
|
4187
|
-
label: {
|
|
4188
|
-
"en": "Create New Session",
|
|
4189
|
-
"zh-CN": "\u521B\u5EFA\u65B0\u4F1A\u8BDD"
|
|
4190
|
-
},
|
|
4191
|
-
description: {
|
|
4192
|
-
"en": "Start a new conversation session",
|
|
4193
|
-
"zh-CN": "\u5F00\u59CB\u65B0\u7684\u5BF9\u8BDD\u4F1A\u8BDD"
|
|
4194
|
-
},
|
|
4195
|
-
icon: "\u2795",
|
|
4196
|
-
action: { type: "command", handler: "session:create" }
|
|
4197
|
-
},
|
|
4198
|
-
{
|
|
4199
|
-
id: "session-restore",
|
|
4200
|
-
label: {
|
|
4201
|
-
"en": "Restore Session",
|
|
4202
|
-
"zh-CN": "\u6062\u590D\u4F1A\u8BDD"
|
|
4203
|
-
},
|
|
4204
|
-
description: {
|
|
4205
|
-
"en": "Continue from history",
|
|
4206
|
-
"zh-CN": "\u4ECE\u5386\u53F2\u4F1A\u8BDD\u7EE7\u7EED"
|
|
4207
|
-
},
|
|
4208
|
-
icon: "\u{1F504}",
|
|
4209
|
-
action: { type: "command", handler: "session:restore" }
|
|
4210
|
-
},
|
|
4211
|
-
{
|
|
4212
|
-
id: "session-list",
|
|
4213
|
-
label: {
|
|
4214
|
-
"en": "Session List",
|
|
4215
|
-
"zh-CN": "\u4F1A\u8BDD\u5217\u8868"
|
|
4216
|
-
},
|
|
4217
|
-
description: {
|
|
4218
|
-
"en": "View all saved sessions",
|
|
4219
|
-
"zh-CN": "\u67E5\u770B\u6240\u6709\u4FDD\u5B58\u7684\u4F1A\u8BDD"
|
|
4220
|
-
},
|
|
4221
|
-
icon: "\u{1F4CB}",
|
|
4222
|
-
action: { type: "command", handler: "session:list" }
|
|
4223
|
-
},
|
|
4224
|
-
{
|
|
4225
|
-
id: "context-management",
|
|
4226
|
-
label: {
|
|
4227
|
-
"en": "Context Management",
|
|
4228
|
-
"zh-CN": "\u4E0A\u4E0B\u6587\u7BA1\u7406"
|
|
4229
|
-
},
|
|
4230
|
-
description: {
|
|
4231
|
-
"en": "Compact, clean context",
|
|
4232
|
-
"zh-CN": "\u538B\u7F29\u3001\u6E05\u7406\u4E0A\u4E0B\u6587"
|
|
4233
|
-
},
|
|
4234
|
-
icon: "\u{1F4CA}",
|
|
4235
|
-
action: { type: "command", handler: "context:manage" }
|
|
4236
|
-
},
|
|
4237
|
-
{
|
|
4238
|
-
id: "session-export",
|
|
4239
|
-
label: {
|
|
4240
|
-
"en": "Export Session",
|
|
4241
|
-
"zh-CN": "\u5BFC\u51FA\u4F1A\u8BDD"
|
|
4242
|
-
},
|
|
4243
|
-
description: {
|
|
4244
|
-
"en": "Export as Markdown",
|
|
4245
|
-
"zh-CN": "\u5BFC\u51FA\u4E3A Markdown"
|
|
4246
|
-
},
|
|
4247
|
-
icon: "\u{1F4E4}",
|
|
4248
|
-
action: { type: "command", handler: "session:export" }
|
|
4249
|
-
},
|
|
4250
|
-
{
|
|
4251
|
-
id: "session-cleanup",
|
|
4252
|
-
label: {
|
|
4253
|
-
"en": "Cleanup Cache",
|
|
4254
|
-
"zh-CN": "\u6E05\u7406\u7F13\u5B58"
|
|
4255
|
-
},
|
|
4256
|
-
description: {
|
|
4257
|
-
"en": "Clean session cache data",
|
|
4258
|
-
"zh-CN": "\u6E05\u7406\u4F1A\u8BDD\u7F13\u5B58\u6570\u636E"
|
|
4259
|
-
},
|
|
4260
|
-
icon: "\u{1F9F9}",
|
|
4261
|
-
action: { type: "command", handler: "session:cleanup" }
|
|
4262
|
-
}
|
|
4263
|
-
]
|
|
4264
|
-
};
|
|
4265
|
-
const settingsGroup = {
|
|
4266
|
-
id: "settings",
|
|
4267
|
-
label: {
|
|
4268
|
-
"en": "Settings",
|
|
4269
|
-
"zh-CN": "\u7CFB\u7EDF\u8BBE\u7F6E"
|
|
4270
|
-
},
|
|
4271
|
-
icon: "\u2699\uFE0F",
|
|
4272
|
-
priority: "setting",
|
|
4273
|
-
items: [
|
|
4274
|
-
{
|
|
4275
|
-
id: "language-setting",
|
|
4276
|
-
label: {
|
|
4277
|
-
"en": "Language",
|
|
4278
|
-
"zh-CN": "\u8BED\u8A00\u8BBE\u7F6E"
|
|
4279
|
-
},
|
|
4280
|
-
description: {
|
|
4281
|
-
"en": "Switch interface language",
|
|
4282
|
-
"zh-CN": "\u5207\u6362\u754C\u9762\u8BED\u8A00 (en/zh-CN)"
|
|
4283
|
-
},
|
|
4284
|
-
icon: "\u{1F310}",
|
|
4285
|
-
action: { type: "command", handler: "settings:language" }
|
|
4286
|
-
},
|
|
4287
|
-
{
|
|
4288
|
-
id: "advanced-setting",
|
|
4289
|
-
label: {
|
|
4290
|
-
"en": "Advanced Settings",
|
|
4291
|
-
"zh-CN": "\u9AD8\u7EA7\u8BBE\u7F6E"
|
|
4292
|
-
},
|
|
4293
|
-
description: {
|
|
4294
|
-
"en": "Debug, logs, performance",
|
|
4295
|
-
"zh-CN": "\u8C03\u8BD5\u3001\u65E5\u5FD7\u3001\u6027\u80FD"
|
|
4296
|
-
},
|
|
4297
|
-
icon: "\u{1F527}",
|
|
4298
|
-
action: { type: "command", handler: "settings:advanced" }
|
|
4299
|
-
},
|
|
4300
|
-
{
|
|
4301
|
-
id: "reset-setting",
|
|
4302
|
-
label: {
|
|
4303
|
-
"en": "Reset Settings",
|
|
4304
|
-
"zh-CN": "\u91CD\u7F6E\u8BBE\u7F6E"
|
|
4305
|
-
},
|
|
4306
|
-
description: {
|
|
4307
|
-
"en": "Restore default settings",
|
|
4308
|
-
"zh-CN": "\u6062\u590D\u9ED8\u8BA4\u8BBE\u7F6E"
|
|
4309
|
-
},
|
|
4310
|
-
icon: "\u21A9\uFE0F",
|
|
4311
|
-
action: { type: "command", handler: "settings:reset" }
|
|
4312
|
-
}
|
|
4313
|
-
]
|
|
4314
|
-
};
|
|
4315
|
-
const helpGroup = {
|
|
4316
|
-
id: "help",
|
|
4317
|
-
label: {
|
|
4318
|
-
"en": "Help & Documentation",
|
|
4319
|
-
"zh-CN": "\u5E2E\u52A9\u6587\u6863"
|
|
4320
|
-
},
|
|
4321
|
-
icon: "\u{1F4DA}",
|
|
4322
|
-
priority: "help",
|
|
4323
|
-
items: [
|
|
4324
|
-
{
|
|
4325
|
-
id: "command-reference",
|
|
4326
|
-
label: {
|
|
4327
|
-
"en": "Command Reference",
|
|
4328
|
-
"zh-CN": "\u547D\u4EE4\u53C2\u8003"
|
|
4329
|
-
},
|
|
4330
|
-
description: {
|
|
4331
|
-
"en": "Detailed command documentation",
|
|
4332
|
-
"zh-CN": "\u6240\u6709\u547D\u4EE4\u7684\u8BE6\u7EC6\u8BF4\u660E"
|
|
4333
|
-
},
|
|
4334
|
-
icon: "\u{1F4D6}",
|
|
4335
|
-
action: { type: "command", handler: "help:commands" }
|
|
4336
|
-
},
|
|
4337
|
-
{
|
|
4338
|
-
id: "quick-tutorial",
|
|
4339
|
-
label: {
|
|
4340
|
-
"en": "Quick Tutorial",
|
|
4341
|
-
"zh-CN": "\u5FEB\u901F\u6559\u7A0B"
|
|
4342
|
-
},
|
|
4343
|
-
description: {
|
|
4344
|
-
"en": "5-minute getting started guide",
|
|
4345
|
-
"zh-CN": "5 \u5206\u949F\u4E0A\u624B\u6307\u5357"
|
|
4346
|
-
},
|
|
4347
|
-
icon: "\u{1F393}",
|
|
4348
|
-
action: { type: "command", handler: "help:tutorial" }
|
|
4349
|
-
},
|
|
4350
|
-
{
|
|
4351
|
-
id: "faq",
|
|
4352
|
-
label: {
|
|
4353
|
-
"en": "FAQ",
|
|
4354
|
-
"zh-CN": "\u5E38\u89C1\u95EE\u9898"
|
|
4355
|
-
},
|
|
4356
|
-
description: {
|
|
4357
|
-
"en": "FAQ and troubleshooting",
|
|
4358
|
-
"zh-CN": "FAQ \u548C\u6545\u969C\u6392\u9664"
|
|
4359
|
-
},
|
|
4360
|
-
icon: "\u2753",
|
|
4361
|
-
action: { type: "command", handler: "help:faq" }
|
|
4362
|
-
},
|
|
4363
|
-
{
|
|
4364
|
-
id: "about",
|
|
4365
|
-
label: {
|
|
4366
|
-
"en": "About CCJK",
|
|
4367
|
-
"zh-CN": "\u5173\u4E8E CCJK"
|
|
4368
|
-
},
|
|
4369
|
-
description: {
|
|
4370
|
-
"en": "Version info and credits",
|
|
4371
|
-
"zh-CN": "\u7248\u672C\u4FE1\u606F\u548C\u81F4\u8C22"
|
|
4372
|
-
},
|
|
4373
|
-
icon: "\u2139\uFE0F",
|
|
4374
|
-
action: { type: "command", handler: "help:about" }
|
|
4375
|
-
}
|
|
4376
|
-
]
|
|
4377
|
-
};
|
|
4378
|
-
const mainMenuConfig = {
|
|
4379
|
-
title: {
|
|
4380
|
-
"en": "CCJK - Claude Code JK",
|
|
4381
|
-
"zh-CN": "CCJK - Claude Code JK"
|
|
4382
|
-
},
|
|
4383
|
-
groups: [
|
|
4384
|
-
// 核心功能组(API 配置在第一位)
|
|
4385
|
-
{
|
|
4386
|
-
id: "core",
|
|
4387
|
-
label: {
|
|
4388
|
-
"en": "Core Features",
|
|
4389
|
-
"zh-CN": "\u6838\u5FC3\u529F\u80FD"
|
|
4390
|
-
},
|
|
4391
|
-
icon: "\u2605",
|
|
4392
|
-
priority: "core",
|
|
4393
|
-
items: [apiConfigMenu]
|
|
4394
|
-
},
|
|
4395
|
-
// 功能模块组
|
|
4396
|
-
quickStartGroup,
|
|
4397
|
-
projectManagementGroup,
|
|
4398
|
-
sessionManagementGroup,
|
|
4399
|
-
settingsGroup,
|
|
4400
|
-
helpGroup
|
|
4401
|
-
],
|
|
4402
|
-
footer: [
|
|
4403
|
-
{
|
|
4404
|
-
id: "exit",
|
|
4405
|
-
label: {
|
|
4406
|
-
"en": "Exit",
|
|
4407
|
-
"zh-CN": "\u9000\u51FA"
|
|
4408
|
-
},
|
|
4409
|
-
icon: "\u{1F6AA}",
|
|
4410
|
-
shortcut: "q",
|
|
4411
|
-
action: { type: "function", handler: async () => process.exit(0) }
|
|
4412
|
-
}
|
|
4413
|
-
]
|
|
4414
|
-
};
|
|
4415
|
-
function getLocalizedLabel(label, locale = "zh-CN") {
|
|
4416
|
-
if (typeof label === "string") {
|
|
4417
|
-
return label;
|
|
4418
|
-
}
|
|
4419
|
-
return label[locale] || label.en || Object.values(label)[0] || "";
|
|
4420
|
-
}
|
|
4421
|
-
|
|
4422
|
-
class MenuRenderer {
|
|
4423
|
-
locale;
|
|
4424
|
-
showStatusBar;
|
|
4425
|
-
showBreadcrumb;
|
|
4426
|
-
showShortcuts;
|
|
4427
|
-
constructor(options = {}) {
|
|
4428
|
-
this.locale = options.locale || "zh-CN";
|
|
4429
|
-
this.showStatusBar = options.showStatusBar ?? true;
|
|
4430
|
-
this.showBreadcrumb = options.showBreadcrumb ?? true;
|
|
4431
|
-
this.showShortcuts = options.showShortcuts ?? true;
|
|
4432
|
-
}
|
|
4433
|
-
/**
|
|
4434
|
-
* 渲染状态栏
|
|
4435
|
-
*/
|
|
4436
|
-
renderStatusBar(projectInfo, apiStatus) {
|
|
4437
|
-
if (!this.showStatusBar)
|
|
4438
|
-
return "";
|
|
4439
|
-
const lines = [];
|
|
4440
|
-
const width = 60;
|
|
4441
|
-
const border = "\u2500".repeat(width - 2);
|
|
4442
|
-
lines.push(ansis.dim(`\u256D${border}\u256E`));
|
|
4443
|
-
if (projectInfo) {
|
|
4444
|
-
const projectLine = [
|
|
4445
|
-
`Project: ${ansis.cyan(projectInfo.name || "Unknown")}`,
|
|
4446
|
-
projectInfo.type ? `Type: ${ansis.yellow(projectInfo.type)}` : "",
|
|
4447
|
-
projectInfo.language ? `Lang: ${ansis.green(projectInfo.language)}` : ""
|
|
4448
|
-
].filter(Boolean).join(" | ");
|
|
4449
|
-
lines.push(ansis.dim("\u2502 ") + projectLine.padEnd(width - 4) + ansis.dim(" \u2502"));
|
|
4450
|
-
}
|
|
4451
|
-
if (apiStatus) {
|
|
4452
|
-
const statusIcon = apiStatus.configured ? ansis.green("\u2713") : ansis.yellow("\u26A0");
|
|
4453
|
-
const statusText = apiStatus.configured ? `API: ${statusIcon} ${apiStatus.mode || "configured"}` : `API: ${statusIcon} \u672A\u914D\u7F6E`;
|
|
4454
|
-
lines.push(ansis.dim("\u2502 ") + statusText.padEnd(width - 4) + ansis.dim(" \u2502"));
|
|
4455
|
-
}
|
|
4456
|
-
lines.push(ansis.dim(`\u2570${border}\u256F`));
|
|
4457
|
-
return `${lines.join("\n")}
|
|
4458
|
-
`;
|
|
4459
|
-
}
|
|
4460
|
-
/**
|
|
4461
|
-
* 渲染面包屑导航
|
|
4462
|
-
*/
|
|
4463
|
-
renderBreadcrumb(path) {
|
|
4464
|
-
if (!this.showBreadcrumb || path.length === 0)
|
|
4465
|
-
return "";
|
|
4466
|
-
const breadcrumb = ["Home", ...path].join(" > ");
|
|
4467
|
-
return `${ansis.dim(`\u{1F4CD} ${breadcrumb}`)}
|
|
4468
|
-
|
|
4469
|
-
`;
|
|
4470
|
-
}
|
|
4471
|
-
/**
|
|
4472
|
-
* 渲染菜单项
|
|
4473
|
-
*/
|
|
4474
|
-
formatMenuItem(item, index) {
|
|
4475
|
-
const icon = item.icon || "";
|
|
4476
|
-
const label = getLocalizedLabel(item.label, this.locale);
|
|
4477
|
-
const description = item.description ? ansis.dim(getLocalizedLabel(item.description, this.locale)) : "";
|
|
4478
|
-
item.shortcut ? `${item.shortcut}.` : `${index + 1}.`;
|
|
4479
|
-
const mainText = `${icon} ${label}`.trim();
|
|
4480
|
-
const padding = " ".repeat(Math.max(1, 30 - mainText.length));
|
|
4481
|
-
return `${mainText}${padding}${description}`;
|
|
4482
|
-
}
|
|
4483
|
-
/**
|
|
4484
|
-
* 渲染菜单组分隔符
|
|
4485
|
-
*/
|
|
4486
|
-
renderGroupSeparator(group) {
|
|
4487
|
-
const label = getLocalizedLabel(group.label, this.locale);
|
|
4488
|
-
const icon = group.icon || "\u25C6";
|
|
4489
|
-
const separator = "\u2500".repeat(50);
|
|
4490
|
-
if (group.priority === "core") {
|
|
4491
|
-
return `
|
|
4492
|
-
${ansis.bold("\u2605")} ${ansis.bold(label)} ${ansis.dim(separator)}`;
|
|
4493
|
-
}
|
|
4494
|
-
return `
|
|
4495
|
-
${icon} ${label} ${ansis.dim(separator)}`;
|
|
4496
|
-
}
|
|
4497
|
-
/**
|
|
4498
|
-
* 渲染主菜单
|
|
4499
|
-
*/
|
|
4500
|
-
async renderMainMenu(config, projectInfo, apiStatus) {
|
|
4501
|
-
console.clear();
|
|
4502
|
-
console.log(this.renderStatusBar(projectInfo, apiStatus));
|
|
4503
|
-
const choices = [];
|
|
4504
|
-
for (const group of config.groups) {
|
|
4505
|
-
choices.push(new inquirer.Separator(this.renderGroupSeparator(group)));
|
|
4506
|
-
for (let i = 0; i < group.items.length; i++) {
|
|
4507
|
-
const item = group.items[i];
|
|
4508
|
-
if (item.condition) {
|
|
4509
|
-
const visible = await item.condition();
|
|
4510
|
-
if (!visible)
|
|
4511
|
-
continue;
|
|
4512
|
-
}
|
|
4513
|
-
choices.push({
|
|
4514
|
-
name: this.formatMenuItem(item, i),
|
|
4515
|
-
value: item,
|
|
4516
|
-
short: getLocalizedLabel(item.label, this.locale)
|
|
4517
|
-
});
|
|
4518
|
-
}
|
|
4519
|
-
}
|
|
4520
|
-
choices.push(new inquirer.Separator(ansis.dim(`
|
|
4521
|
-
${"\u2500".repeat(55)}`)));
|
|
4522
|
-
if (config.footer) {
|
|
4523
|
-
for (const item of config.footer) {
|
|
4524
|
-
choices.push({
|
|
4525
|
-
name: this.formatMenuItem(item, 0),
|
|
4526
|
-
value: item,
|
|
4527
|
-
short: getLocalizedLabel(item.label, this.locale)
|
|
4528
|
-
});
|
|
4529
|
-
}
|
|
4530
|
-
}
|
|
4531
|
-
if (this.showShortcuts) {
|
|
4532
|
-
const shortcuts = ansis.dim(" q. \u9000\u51FA h. \u5E2E\u52A9 /. \u641C\u7D22");
|
|
4533
|
-
choices.push(new inquirer.Separator(shortcuts));
|
|
4534
|
-
}
|
|
4535
|
-
const { selection } = await inquirer.prompt([
|
|
4536
|
-
{
|
|
4537
|
-
type: "list",
|
|
4538
|
-
name: "selection",
|
|
4539
|
-
message: this.locale === "zh-CN" ? "\u8BF7\u9009\u62E9\u64CD\u4F5C:" : "Select an action:",
|
|
4540
|
-
choices,
|
|
4541
|
-
pageSize: 20,
|
|
4542
|
-
loop: false
|
|
4543
|
-
}
|
|
4544
|
-
]);
|
|
4545
|
-
return {
|
|
4546
|
-
item: selection,
|
|
4547
|
-
action: "select"
|
|
4548
|
-
};
|
|
4549
|
-
}
|
|
4550
|
-
/**
|
|
4551
|
-
* 渲染子菜单
|
|
4552
|
-
*/
|
|
4553
|
-
async renderSubmenu(item, breadcrumb) {
|
|
4554
|
-
if (!item.submenu || item.submenu.length === 0) {
|
|
4555
|
-
return { item, action: "select" };
|
|
4556
|
-
}
|
|
4557
|
-
console.clear();
|
|
4558
|
-
console.log(this.renderBreadcrumb(breadcrumb));
|
|
4559
|
-
const title = getLocalizedLabel(item.label, this.locale);
|
|
4560
|
-
const description = item.description ? getLocalizedLabel(item.description, this.locale) : "";
|
|
4561
|
-
console.log(`${item.icon || ""} ${ansis.bold(title)}`);
|
|
4562
|
-
if (description) {
|
|
4563
|
-
console.log(ansis.dim(description));
|
|
4564
|
-
}
|
|
4565
|
-
console.log("");
|
|
4566
|
-
const choices = [];
|
|
4567
|
-
for (let i = 0; i < item.submenu.length; i++) {
|
|
4568
|
-
const subItem = item.submenu[i];
|
|
4569
|
-
if (subItem.condition) {
|
|
4570
|
-
const visible = await subItem.condition();
|
|
4571
|
-
if (!visible)
|
|
4572
|
-
continue;
|
|
4573
|
-
}
|
|
4574
|
-
choices.push({
|
|
4575
|
-
name: this.formatMenuItem(subItem, i),
|
|
4576
|
-
value: subItem,
|
|
4577
|
-
short: getLocalizedLabel(subItem.label, this.locale)
|
|
4578
|
-
});
|
|
4579
|
-
}
|
|
4580
|
-
choices.push(new inquirer.Separator(ansis.dim(`
|
|
4581
|
-
${"\u2500".repeat(55)}`)));
|
|
4582
|
-
choices.push({
|
|
4583
|
-
name: `${ansis.dim("\u2190")} ${this.locale === "zh-CN" ? "\u8FD4\u56DE\u4E3B\u83DC\u5355" : "Back to main menu"}`,
|
|
4584
|
-
value: "back",
|
|
4585
|
-
short: "Back"
|
|
4586
|
-
});
|
|
4587
|
-
const { selection } = await inquirer.prompt([
|
|
4588
|
-
{
|
|
4589
|
-
type: "list",
|
|
4590
|
-
name: "selection",
|
|
4591
|
-
message: this.locale === "zh-CN" ? "\u8BF7\u9009\u62E9:" : "Select:",
|
|
4592
|
-
choices,
|
|
4593
|
-
pageSize: 15,
|
|
4594
|
-
loop: false
|
|
4595
|
-
}
|
|
4596
|
-
]);
|
|
4597
|
-
if (selection === "back") {
|
|
4598
|
-
return { item, action: "back" };
|
|
4599
|
-
}
|
|
4600
|
-
return {
|
|
4601
|
-
item: selection,
|
|
4602
|
-
action: "select"
|
|
4603
|
-
};
|
|
4604
|
-
}
|
|
4605
|
-
/**
|
|
4606
|
-
* 设置语言
|
|
4607
|
-
*/
|
|
4608
|
-
setLocale(locale) {
|
|
4609
|
-
this.locale = locale;
|
|
4610
|
-
}
|
|
4611
|
-
/**
|
|
4612
|
-
* 获取当前语言
|
|
4613
|
-
*/
|
|
4614
|
-
getLocale() {
|
|
4615
|
-
return this.locale;
|
|
4616
|
-
}
|
|
4617
|
-
}
|
|
4618
|
-
function createMenuRenderer(options) {
|
|
4619
|
-
return new MenuRenderer(options);
|
|
4620
|
-
}
|
|
4621
|
-
|
|
4622
|
-
class MenuEngine {
|
|
4623
|
-
renderer;
|
|
4624
|
-
state;
|
|
4625
|
-
config;
|
|
4626
|
-
commandHandlers;
|
|
4627
|
-
constructor(options = {}) {
|
|
4628
|
-
this.renderer = new MenuRenderer(options);
|
|
4629
|
-
this.state = {
|
|
4630
|
-
currentPath: [],
|
|
4631
|
-
history: [],
|
|
4632
|
-
searchQuery: void 0,
|
|
4633
|
-
projectInfo: void 0,
|
|
4634
|
-
apiStatus: void 0
|
|
4635
|
-
};
|
|
4636
|
-
this.config = mainMenuConfig;
|
|
4637
|
-
this.commandHandlers = /* @__PURE__ */ new Map();
|
|
4638
|
-
}
|
|
4639
|
-
/**
|
|
4640
|
-
* 注册命令处理器
|
|
4641
|
-
*/
|
|
4642
|
-
registerHandler(command, handler) {
|
|
4643
|
-
this.commandHandlers.set(command, handler);
|
|
4644
|
-
}
|
|
4645
|
-
/**
|
|
4646
|
-
* 批量注册命令处理器
|
|
4647
|
-
*/
|
|
4648
|
-
registerHandlers(handlers) {
|
|
4649
|
-
for (const [command, handler] of Object.entries(handlers)) {
|
|
4650
|
-
this.commandHandlers.set(command, handler);
|
|
4651
|
-
}
|
|
4652
|
-
}
|
|
4653
|
-
/**
|
|
4654
|
-
* 设置项目信息
|
|
4655
|
-
*/
|
|
4656
|
-
setProjectInfo(info) {
|
|
4657
|
-
this.state.projectInfo = info;
|
|
4658
|
-
}
|
|
4659
|
-
/**
|
|
4660
|
-
* 设置 API 状态
|
|
4661
|
-
*/
|
|
4662
|
-
setApiStatus(status) {
|
|
4663
|
-
this.state.apiStatus = status;
|
|
4664
|
-
}
|
|
4665
|
-
/**
|
|
4666
|
-
* 刷新项目信息(按需分析)
|
|
4667
|
-
*/
|
|
4668
|
-
async refreshProjectInfo() {
|
|
4669
|
-
try {
|
|
4670
|
-
const { detectProjectInfo } = await import('./chunks/project-detector.mjs');
|
|
4671
|
-
this.state.projectInfo = await detectProjectInfo();
|
|
4672
|
-
} catch {
|
|
4673
|
-
this.state.projectInfo = {
|
|
4674
|
-
name: "Unknown",
|
|
4675
|
-
type: void 0,
|
|
4676
|
-
language: void 0
|
|
4677
|
-
};
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4680
|
-
/**
|
|
4681
|
-
* 刷新 API 状态
|
|
4682
|
-
*/
|
|
4683
|
-
async refreshApiStatus() {
|
|
4684
|
-
try {
|
|
4685
|
-
const { detectApiStatus } = await import('./chunks/api-adapter.mjs');
|
|
4686
|
-
this.state.apiStatus = await detectApiStatus();
|
|
4687
|
-
} catch {
|
|
4688
|
-
this.state.apiStatus = {
|
|
4689
|
-
configured: false,
|
|
4690
|
-
mode: "none"
|
|
4691
|
-
};
|
|
4692
|
-
}
|
|
4693
|
-
}
|
|
4694
|
-
/**
|
|
4695
|
-
* 执行菜单动作
|
|
4696
|
-
*/
|
|
4697
|
-
async executeAction(item) {
|
|
4698
|
-
if (!item.action)
|
|
4699
|
-
return false;
|
|
4700
|
-
const { type, handler } = item.action;
|
|
4701
|
-
switch (type) {
|
|
4702
|
-
case "command": {
|
|
4703
|
-
if (typeof handler === "string") {
|
|
4704
|
-
const commandHandler = this.commandHandlers.get(handler);
|
|
4705
|
-
if (commandHandler) {
|
|
4706
|
-
await commandHandler();
|
|
4707
|
-
return true;
|
|
4708
|
-
}
|
|
4709
|
-
console.log(`Command not found: ${handler}`);
|
|
4710
|
-
return false;
|
|
4711
|
-
}
|
|
4712
|
-
return false;
|
|
4713
|
-
}
|
|
4714
|
-
case "function": {
|
|
4715
|
-
if (typeof handler === "function") {
|
|
4716
|
-
await handler();
|
|
4717
|
-
return true;
|
|
4718
|
-
}
|
|
4719
|
-
return false;
|
|
4720
|
-
}
|
|
4721
|
-
case "submenu": {
|
|
4722
|
-
return true;
|
|
4723
|
-
}
|
|
4724
|
-
case "external": {
|
|
4725
|
-
if (typeof handler === "string") {
|
|
4726
|
-
const { exec } = await import('node:child_process');
|
|
4727
|
-
exec(`open ${handler}`);
|
|
4728
|
-
return true;
|
|
4729
|
-
}
|
|
4730
|
-
return false;
|
|
4731
|
-
}
|
|
4732
|
-
default:
|
|
4733
|
-
return false;
|
|
4734
|
-
}
|
|
4735
|
-
}
|
|
4736
|
-
/**
|
|
4737
|
-
* 处理菜单选择
|
|
4738
|
-
*/
|
|
4739
|
-
async handleSelection(selection) {
|
|
4740
|
-
const { item, action } = selection;
|
|
4741
|
-
switch (action) {
|
|
4742
|
-
case "exit":
|
|
4743
|
-
return "exit";
|
|
4744
|
-
case "back":
|
|
4745
|
-
if (this.state.currentPath.length > 0) {
|
|
4746
|
-
this.state.currentPath.pop();
|
|
4747
|
-
this.state.history.pop();
|
|
4748
|
-
}
|
|
4749
|
-
return "continue";
|
|
4750
|
-
case "select": {
|
|
4751
|
-
if (item.id === "exit") {
|
|
4752
|
-
return "exit";
|
|
4753
|
-
}
|
|
4754
|
-
if (item.submenu && item.submenu.length > 0) {
|
|
4755
|
-
const label = getLocalizedLabel(item.label, this.renderer.getLocale());
|
|
4756
|
-
this.state.currentPath.push(label);
|
|
4757
|
-
this.state.history.push(this.state.currentPath.slice());
|
|
4758
|
-
const subSelection = await this.renderer.renderSubmenu(
|
|
4759
|
-
item,
|
|
4760
|
-
this.state.currentPath
|
|
4761
|
-
);
|
|
4762
|
-
return this.handleSelection(subSelection);
|
|
4763
|
-
}
|
|
4764
|
-
await this.executeAction(item);
|
|
4765
|
-
return "continue";
|
|
4766
|
-
}
|
|
4767
|
-
default:
|
|
4768
|
-
return "continue";
|
|
4769
|
-
}
|
|
4770
|
-
}
|
|
4771
|
-
/**
|
|
4772
|
-
* 启动菜单
|
|
4773
|
-
*/
|
|
4774
|
-
async start() {
|
|
4775
|
-
let running = true;
|
|
4776
|
-
while (running) {
|
|
4777
|
-
try {
|
|
4778
|
-
const selection = await this.renderer.renderMainMenu(
|
|
4779
|
-
this.config,
|
|
4780
|
-
this.state.projectInfo,
|
|
4781
|
-
this.state.apiStatus
|
|
4782
|
-
);
|
|
4783
|
-
const result = await this.handleSelection(selection);
|
|
4784
|
-
if (result === "exit") {
|
|
4785
|
-
running = false;
|
|
4786
|
-
}
|
|
4787
|
-
} catch (error) {
|
|
4788
|
-
if (error?.isTtyError || error?.message?.includes("User force closed")) {
|
|
4789
|
-
running = false;
|
|
4790
|
-
} else {
|
|
4791
|
-
console.error("Menu error:", error);
|
|
4792
|
-
running = false;
|
|
4793
|
-
}
|
|
4794
|
-
}
|
|
4795
|
-
}
|
|
4796
|
-
process.exit(0);
|
|
4797
|
-
}
|
|
4798
|
-
/**
|
|
4799
|
-
* 导航到指定菜单
|
|
4800
|
-
*/
|
|
4801
|
-
async navigate(path) {
|
|
4802
|
-
this.state.currentPath = path;
|
|
4803
|
-
this.state.history = [path.slice()];
|
|
4804
|
-
}
|
|
4805
|
-
/**
|
|
4806
|
-
* 返回上级
|
|
4807
|
-
*/
|
|
4808
|
-
async back() {
|
|
4809
|
-
if (this.state.currentPath.length > 0) {
|
|
4810
|
-
this.state.currentPath.pop();
|
|
4811
|
-
this.state.history.pop();
|
|
4812
|
-
}
|
|
4813
|
-
}
|
|
4814
|
-
/**
|
|
4815
|
-
* 退出菜单
|
|
4816
|
-
*/
|
|
4817
|
-
exit() {
|
|
4818
|
-
process.exit(0);
|
|
4819
|
-
}
|
|
4820
|
-
/**
|
|
4821
|
-
* 获取当前状态
|
|
4822
|
-
*/
|
|
4823
|
-
getState() {
|
|
4824
|
-
return { ...this.state };
|
|
4825
|
-
}
|
|
4826
|
-
/**
|
|
4827
|
-
* 设置语言
|
|
4828
|
-
*/
|
|
4829
|
-
setLocale(locale) {
|
|
4830
|
-
this.renderer.setLocale(locale);
|
|
4831
|
-
}
|
|
4832
|
-
}
|
|
4833
|
-
function createMenuEngine(options) {
|
|
4834
|
-
return new MenuEngine(options);
|
|
4835
|
-
}
|
|
4836
|
-
|
|
4837
|
-
const menuEngine = {
|
|
4838
|
-
__proto__: null,
|
|
4839
|
-
MenuEngine: MenuEngine,
|
|
4840
|
-
createMenuEngine: createMenuEngine
|
|
4841
|
-
};
|
|
4842
|
-
|
|
4843
|
-
async function showMenu() {
|
|
4844
|
-
const { runCli: runCli2 } = await Promise.resolve().then(function () { return cli; });
|
|
4845
|
-
await runCli2();
|
|
4846
|
-
}
|
|
4847
|
-
async function startMenu(options = {}) {
|
|
4848
|
-
const { createMenuEngine: createMenuEngine2 } = await Promise.resolve().then(function () { return menuEngine; });
|
|
4849
|
-
const engine = createMenuEngine2({
|
|
4850
|
-
locale: options.locale,
|
|
4851
|
-
showStatusBar: options.showStatusBar ?? true,
|
|
4852
|
-
showBreadcrumb: options.showBreadcrumb ?? true,
|
|
4853
|
-
showShortcuts: options.showShortcuts ?? true
|
|
4854
|
-
});
|
|
4855
|
-
await registerDefaultHandlers(engine);
|
|
4856
|
-
await engine.start();
|
|
4857
|
-
}
|
|
4858
|
-
async function registerDefaultHandlers(engine) {
|
|
4859
|
-
engine.registerHandlers({
|
|
4860
|
-
"api:official": async () => {
|
|
4861
|
-
console.log("\u4F7F\u7528\u5B98\u65B9\u767B\u5F55...");
|
|
4862
|
-
},
|
|
4863
|
-
"api:custom": async () => {
|
|
4864
|
-
const { runWizard } = await import('./chunks/api.mjs');
|
|
4865
|
-
await runWizard();
|
|
4866
|
-
},
|
|
4867
|
-
"api:ccr": async () => {
|
|
4868
|
-
const { runCcrMenuFeature } = await import('./chunks/tools.mjs').then(function (n) { return n.t; });
|
|
4869
|
-
await runCcrMenuFeature();
|
|
4870
|
-
},
|
|
4871
|
-
"api:switch": async () => {
|
|
4872
|
-
const { configSwitchCommand } = await import('./chunks/config-switch.mjs');
|
|
4873
|
-
await configSwitchCommand({ codeType: "claude-code" });
|
|
4874
|
-
},
|
|
4875
|
-
"api:status": async () => {
|
|
4876
|
-
console.log("\u67E5\u770B\u5F53\u524D API \u914D\u7F6E...");
|
|
4877
|
-
}
|
|
4878
|
-
});
|
|
4879
|
-
engine.registerHandlers({
|
|
4880
|
-
"ccjk:init": async () => {
|
|
4881
|
-
const { init } = await import('./chunks/init.mjs');
|
|
4882
|
-
await init({ skipBanner: true });
|
|
4883
|
-
},
|
|
4884
|
-
"ccjk:skills": async () => {
|
|
4885
|
-
console.log("\u6280\u80FD\u7BA1\u7406...");
|
|
4886
|
-
},
|
|
4887
|
-
"ccjk:mcp": async () => {
|
|
4888
|
-
const { mcpHelp } = await import('./chunks/mcp.mjs');
|
|
4889
|
-
mcpHelp();
|
|
4890
|
-
},
|
|
4891
|
-
"ccjk:agents": async () => {
|
|
4892
|
-
console.log("\u4EE3\u7406\u7BA1\u7406...");
|
|
4893
|
-
},
|
|
4894
|
-
"ccjk:hooks": async () => {
|
|
4895
|
-
const { hooksSync } = await import('./chunks/hooks-sync.mjs');
|
|
4896
|
-
await hooksSync({});
|
|
4897
|
-
}
|
|
4898
|
-
});
|
|
4899
|
-
engine.registerHandlers({
|
|
4900
|
-
"session:create": async () => {
|
|
4901
|
-
console.log("\u521B\u5EFA\u65B0\u4F1A\u8BDD...");
|
|
4902
|
-
},
|
|
4903
|
-
"session:restore": async () => {
|
|
4904
|
-
console.log("\u6062\u590D\u4F1A\u8BDD...");
|
|
4905
|
-
},
|
|
4906
|
-
"session:list": async () => {
|
|
4907
|
-
console.log("\u4F1A\u8BDD\u5217\u8868...");
|
|
4908
|
-
},
|
|
4909
|
-
"context:manage": async () => {
|
|
4910
|
-
const { showContextMenu } = await import('./chunks/context-menu.mjs');
|
|
4911
|
-
await showContextMenu();
|
|
4912
|
-
},
|
|
4913
|
-
"session:export": async () => {
|
|
4914
|
-
console.log("\u5BFC\u51FA\u4F1A\u8BDD...");
|
|
4915
|
-
},
|
|
4916
|
-
"session:cleanup": async () => {
|
|
4917
|
-
console.log("\u6E05\u7406\u7F13\u5B58...");
|
|
4918
|
-
}
|
|
4919
|
-
});
|
|
4920
|
-
engine.registerHandlers({
|
|
4921
|
-
"settings:language": async () => {
|
|
4922
|
-
const { changeScriptLanguageFeature } = await import('./chunks/features.mjs');
|
|
4923
|
-
const { i18n } = await import('./chunks/index.mjs');
|
|
4924
|
-
await changeScriptLanguageFeature(i18n.language);
|
|
4925
|
-
},
|
|
4926
|
-
"settings:advanced": async () => {
|
|
4927
|
-
console.log("\u9AD8\u7EA7\u8BBE\u7F6E...");
|
|
4928
|
-
},
|
|
4929
|
-
"settings:reset": async () => {
|
|
4930
|
-
console.log("\u91CD\u7F6E\u8BBE\u7F6E...");
|
|
4931
|
-
}
|
|
4932
|
-
});
|
|
4933
|
-
engine.registerHandlers({
|
|
4934
|
-
"help:commands": async () => {
|
|
4935
|
-
console.log("\u547D\u4EE4\u53C2\u8003...");
|
|
4936
|
-
},
|
|
4937
|
-
"help:tutorial": async () => {
|
|
4938
|
-
console.log("\u5FEB\u901F\u6559\u7A0B...");
|
|
4939
|
-
},
|
|
4940
|
-
"help:faq": async () => {
|
|
4941
|
-
console.log("\u5E38\u89C1\u95EE\u9898...");
|
|
4942
|
-
},
|
|
4943
|
-
"help:about": async () => {
|
|
4944
|
-
const VERSION = "1.0.0";
|
|
4945
|
-
console.log(`CCJK - Claude Code JK v${VERSION}`);
|
|
4946
|
-
console.log("https://github.com/anthropics/claude-code");
|
|
4947
|
-
}
|
|
4948
|
-
});
|
|
4949
|
-
}
|
|
4950
|
-
|
|
4951
|
-
const index$7 = {
|
|
4952
|
-
__proto__: null,
|
|
4953
|
-
MenuEngine: MenuEngine,
|
|
4954
|
-
MenuRenderer: MenuRenderer,
|
|
4955
|
-
apiConfigMenu: apiConfigMenu,
|
|
4956
|
-
createMenuEngine: createMenuEngine,
|
|
4957
|
-
createMenuRenderer: createMenuRenderer,
|
|
4958
|
-
getLocalizedLabel: getLocalizedLabel,
|
|
4959
|
-
helpGroup: helpGroup,
|
|
4960
|
-
mainMenuConfig: mainMenuConfig,
|
|
4961
|
-
newMainMenuConfig: mainMenuConfig$1,
|
|
4962
|
-
projectManagementGroup: projectManagementGroup,
|
|
4963
|
-
quickStartGroup: quickStartGroup,
|
|
4964
|
-
runCli: runCli,
|
|
4965
|
-
sessionManagementGroup: sessionManagementGroup,
|
|
4966
|
-
settingsGroup: settingsGroup,
|
|
4967
|
-
showMenu: showMenu,
|
|
4968
|
-
startMenu: startMenu
|
|
4969
|
-
};
|
|
4970
|
-
|
|
4971
792
|
function unique(arr) {
|
|
4972
793
|
return [...new Set(arr)];
|
|
4973
794
|
}
|
|
@@ -7844,4 +3665,4 @@ function assert(condition, message) {
|
|
|
7844
3665
|
}
|
|
7845
3666
|
}
|
|
7846
3667
|
|
|
7847
|
-
export {
|
|
3668
|
+
export { AiderTool, BaseCodeTool, BaseError, ClaudeCodeTool, ClineTool, CodexTool, ConfigManager, ConfigValidator, ConfigurationError, ContinueTool, CursorTool, InternalError, Logger, Mutex, NotFoundError, Semaphore, TimeoutError, ToolFactory, ToolRegistry, UnauthorizedError, ValidationError, index$6 as array, assert, assertDefined, index$5 as async, batchProcessFiles, camelCase, capitalize, chunk, index$4 as command, commandExists, copyFile, countLines, createConfigManager, createLogger, createTool, createValidator, debounce, deepClone, deepMerge, deleteDir, deleteFile, difference, ensureDir, index$3 as error, executeCommand, executeCommandStream, exists, flatten, flatten$1 as flattenArray, formatError, index$2 as fs, generateCompactWelcome, generateRecommendations, generateWelcome, get, getArchitecture, getCacheDir, getCapabilitiesByType, getCapability, getCommandPath, getCommandVersion, getConfigDir, getDataDir, getErrorMessage, getFileInfo, getFileSize, getHomeDir, getPlatform, getPlatformInfo, getRegistry, getTempDir, has, intersection, isArray, isBoolean, isDefined, isDirectory, isEmail, isFile, isLargeFile, isLinux, isMacOS, isNumber, isObject, isString, isURL, isUnix, isWindows, kebabCase, listDirs, listFiles, logger, logger$2 as loggerUtils, moveFile, index$1 as object, omit, parallelLimit, partition, pascalCase, pick, processLargeFile, processLineByLine, readFile, readJSON, retry, scanCapabilities, sequence, set, shuffle, sleep, slugify, snakeCase, streamJSON, streamWriteJSON, index as string, template, throttle, timeout, truncate, tryCatch, tryCatchAsync, unflatten, union, unique, validation, validators, waitFor, wrapError, writeFile, writeJSON };
|