codebuddy-stats 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,581 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import fsSync from 'node:fs'
3
- import path from 'node:path'
4
- import { createInterface } from 'node:readline'
5
-
6
- import { getIdeDataDir, getProjectsDir, getSettingsPath } from './paths.js'
7
- import { DEFAULT_MODEL_ID, getPricingForModel, tokensToCost } from './pricing.js'
8
- import { loadWorkspaceMappings, type WorkspaceMapping } from './workspace-resolver.js'
9
-
10
- export const BASE_DIR = getProjectsDir()
11
-
12
- export interface RawUsage {
13
- prompt_tokens?: number
14
- completion_tokens?: number
15
- total_tokens?: number
16
- prompt_cache_hit_tokens?: number
17
- prompt_cache_miss_tokens?: number
18
- cache_creation_input_tokens?: number
19
- }
20
-
21
- export interface UsageStats {
22
- promptTokens: number
23
- completionTokens: number
24
- totalTokens: number
25
- cacheHitTokens: number
26
- cacheMissTokens: number
27
- cacheWriteTokens: number
28
- }
29
-
30
- export interface DailyModelStats extends UsageStats {
31
- cost: number
32
- requests: number
33
- }
34
-
35
- export interface SummaryStats {
36
- cost: number
37
- tokens: number
38
- requests: number
39
- }
40
-
41
- export interface GrandTotal extends SummaryStats {
42
- cacheHitTokens: number
43
- cacheMissTokens: number
44
- }
45
-
46
- export type DailyData = Record<string, Record<string, Record<string, DailyModelStats>>>
47
-
48
- export interface AnalysisData {
49
- defaultModelId: string
50
- dailyData: DailyData
51
- dailySummary: Record<string, SummaryStats>
52
- modelTotals: Record<string, SummaryStats>
53
- projectTotals: Record<string, SummaryStats>
54
- grandTotal: GrandTotal
55
- topModel: (SummaryStats & { id: string }) | null
56
- topProject: (SummaryStats & { name: string }) | null
57
- cacheHitRate: number
58
- activeDays: number
59
- /** 工作区 hash -> 路径映射(仅 IDE source 有效) */
60
- workspaceMappings?: Map<string, import('./workspace-resolver.js').WorkspaceMapping>
61
- }
62
-
63
- export type UsageSource = 'code' | 'ide'
64
-
65
- export interface LoadUsageOptions {
66
- days?: number | null
67
- source?: UsageSource
68
- }
69
-
70
- interface SettingsFile {
71
- model?: unknown
72
- }
73
-
74
- interface JsonlRecord {
75
- providerData?: {
76
- rawUsage?: RawUsage
77
- model?: unknown
78
- }
79
- timestamp?: unknown
80
- }
81
-
82
- async function loadModelFromSettings(): Promise<string> {
83
- try {
84
- const settingsPath = getSettingsPath()
85
- const settingsRaw = await fs.readFile(settingsPath, 'utf8')
86
- const settings = JSON.parse(settingsRaw) as SettingsFile
87
- return typeof settings?.model === 'string' ? settings.model : DEFAULT_MODEL_ID
88
- } catch {
89
- return DEFAULT_MODEL_ID
90
- }
91
- }
92
-
93
- async function findJsonlFiles(dir: string): Promise<string[]> {
94
- try {
95
- const dirents = await fs.readdir(dir, { withFileTypes: true })
96
- const files = await Promise.all(
97
- dirents.map(async dirent => {
98
- const res = path.resolve(dir, dirent.name)
99
- if (dirent.isDirectory()) {
100
- return findJsonlFiles(res)
101
- }
102
- return res.endsWith('.jsonl') ? [res] : ([] as string[])
103
- })
104
- )
105
- return files.flat()
106
- } catch (error) {
107
- if (typeof error === 'object' && error && 'code' in error && (error as any).code === 'ENOENT') {
108
- return []
109
- }
110
- throw error
111
- }
112
- }
113
-
114
- function getProjectName(filePath: string): string {
115
- const parts = filePath.split(path.sep)
116
- const projectsIndex = parts.lastIndexOf('projects')
117
- if (projectsIndex !== -1 && projectsIndex < parts.length - 1) {
118
- return parts[projectsIndex + 1] ?? 'unknown-project'
119
- }
120
- return 'unknown-project'
121
- }
122
-
123
- function extractUsageStats(usage: RawUsage): UsageStats {
124
- const promptTokens = usage.prompt_tokens ?? 0
125
- const completionTokens = usage.completion_tokens ?? 0
126
- const totalTokens = usage.total_tokens ?? promptTokens + completionTokens
127
- const cacheHitTokens = usage.prompt_cache_hit_tokens ?? 0
128
- const cacheMissTokens =
129
- usage.prompt_cache_miss_tokens ?? (cacheHitTokens > 0 ? Math.max(promptTokens - cacheHitTokens, 0) : 0)
130
- const cacheWriteTokens = usage.cache_creation_input_tokens ?? 0
131
-
132
- return {
133
- promptTokens,
134
- completionTokens,
135
- totalTokens,
136
- cacheHitTokens,
137
- cacheMissTokens,
138
- cacheWriteTokens,
139
- }
140
- }
141
-
142
- function computeUsageCost(usage: RawUsage, modelId: string): { cost: number; stats: UsageStats } {
143
- const stats = extractUsageStats(usage)
144
- const pricing = getPricingForModel(modelId)
145
-
146
- let inputCost = 0
147
- if (stats.cacheHitTokens || stats.cacheMissTokens || stats.cacheWriteTokens) {
148
- inputCost += tokensToCost(stats.cacheHitTokens, pricing.cacheRead)
149
- inputCost += tokensToCost(stats.cacheMissTokens, pricing.prompt)
150
- inputCost += tokensToCost(stats.cacheWriteTokens, pricing.cacheWrite)
151
- } else {
152
- inputCost += tokensToCost(stats.promptTokens, pricing.prompt)
153
- }
154
-
155
- const outputCost = tokensToCost(stats.completionTokens, pricing.completion)
156
-
157
- return { cost: inputCost + outputCost, stats }
158
- }
159
-
160
- function computeMinDate(days?: number | null): string | null {
161
- if (!days) return null
162
- const d = new Date()
163
- d.setDate(d.getDate() - days + 1)
164
- d.setHours(0, 0, 0, 0)
165
- return d.toISOString().split('T')[0] ?? null
166
- }
167
-
168
- function toISODateString(value: unknown): string | null {
169
- if (typeof value !== 'string') return null
170
- const d = new Date(value)
171
- if (Number.isNaN(d.getTime())) return null
172
- return d.toISOString().split('T')[0] ?? null
173
- }
174
-
175
- async function pathExists(p: string): Promise<boolean> {
176
- try {
177
- await fs.access(p)
178
- return true
179
- } catch {
180
- return false
181
- }
182
- }
183
-
184
- function ensureDailyModelStats(dailyData: DailyData, date: string, project: string, modelId: string): DailyModelStats {
185
- dailyData[date] ??= {}
186
- dailyData[date]![project] ??= {}
187
- dailyData[date]![project]![modelId] ??= {
188
- cost: 0,
189
- promptTokens: 0,
190
- completionTokens: 0,
191
- totalTokens: 0,
192
- cacheHitTokens: 0,
193
- cacheMissTokens: 0,
194
- cacheWriteTokens: 0,
195
- requests: 0,
196
- }
197
- return dailyData[date]![project]![modelId]!
198
- }
199
-
200
- function finalizeAnalysis(
201
- defaultModelId: string,
202
- dailyData: DailyData,
203
- modelTotals: Record<string, SummaryStats>,
204
- projectTotals: Record<string, SummaryStats>,
205
- grandTotal: GrandTotal,
206
- workspaceMappings?: Map<string, WorkspaceMapping>
207
- ): AnalysisData {
208
- const dailySummary: Record<string, SummaryStats> = {}
209
- for (const date of Object.keys(dailyData)) {
210
- dailySummary[date] = { cost: 0, tokens: 0, requests: 0 }
211
- for (const project of Object.values(dailyData[date] ?? {})) {
212
- for (const model of Object.values(project ?? {})) {
213
- dailySummary[date]!.cost += model.cost
214
- dailySummary[date]!.tokens += model.totalTokens
215
- dailySummary[date]!.requests += model.requests
216
- }
217
- }
218
- }
219
-
220
- const topModelEntry = Object.entries(modelTotals).sort((a, b) => b[1].cost - a[1].cost)[0]
221
- const topProjectEntry = Object.entries(projectTotals).sort((a, b) => b[1].cost - a[1].cost)[0]
222
-
223
- const cacheHitRate =
224
- grandTotal.cacheHitTokens + grandTotal.cacheMissTokens > 0
225
- ? grandTotal.cacheHitTokens / (grandTotal.cacheHitTokens + grandTotal.cacheMissTokens)
226
- : 0
227
-
228
- return {
229
- defaultModelId,
230
- dailyData,
231
- dailySummary,
232
- modelTotals,
233
- projectTotals,
234
- grandTotal,
235
- topModel: topModelEntry ? { id: topModelEntry[0], ...topModelEntry[1] } : null,
236
- topProject: topProjectEntry ? { name: topProjectEntry[0], ...topProjectEntry[1] } : null,
237
- cacheHitRate,
238
- activeDays: Object.keys(dailyData).length,
239
- workspaceMappings,
240
- }
241
- }
242
-
243
- /**
244
- * 加载所有用量数据
245
- */
246
- export async function loadUsageData(options: LoadUsageOptions = {}): Promise<AnalysisData> {
247
- const source: UsageSource = options.source ?? 'code'
248
- if (source === 'ide') {
249
- return loadIdeUsageData(options)
250
- }
251
- return loadCodeUsageData(options)
252
- }
253
-
254
- async function loadCodeUsageData(options: LoadUsageOptions = {}): Promise<AnalysisData> {
255
- const defaultModelId = await loadModelFromSettings()
256
- const jsonlFiles = await findJsonlFiles(BASE_DIR)
257
- const minDate = computeMinDate(options.days)
258
-
259
- const dailyData: DailyData = {}
260
- const modelTotals: Record<string, SummaryStats> = {}
261
- const projectTotals: Record<string, SummaryStats> = {}
262
- const grandTotal: GrandTotal = {
263
- cost: 0,
264
- tokens: 0,
265
- requests: 0,
266
- cacheHitTokens: 0,
267
- cacheMissTokens: 0,
268
- }
269
-
270
- for (const filePath of jsonlFiles) {
271
- const fileStat = await fs.stat(filePath)
272
- if (fileStat.size === 0) continue
273
-
274
- const fileStream = fsSync.createReadStream(filePath)
275
- const rl = createInterface({
276
- input: fileStream,
277
- crlfDelay: Number.POSITIVE_INFINITY,
278
- })
279
-
280
- const projectName = getProjectName(filePath)
281
-
282
- for await (const line of rl) {
283
- try {
284
- const record = JSON.parse(line) as JsonlRecord
285
- const usage = record?.providerData?.rawUsage
286
- const timestamp = record?.timestamp
287
-
288
- if (!usage || timestamp == null) continue
289
-
290
- const dateObj = new Date(timestamp as any)
291
- if (Number.isNaN(dateObj.getTime())) continue
292
- const date = dateObj.toISOString().split('T')[0]
293
- if (!date) continue
294
-
295
- if (minDate && date < minDate) continue
296
-
297
- const recordModelId = record?.providerData?.model
298
- const modelFromRecord = typeof recordModelId === 'string' ? recordModelId : null
299
- const usedModelId = modelFromRecord || defaultModelId
300
-
301
- const { cost, stats: usageStats } = computeUsageCost(usage, usedModelId)
302
-
303
- const dayStats = ensureDailyModelStats(dailyData, date, projectName, usedModelId)
304
- dayStats.cost += cost
305
- dayStats.promptTokens += usageStats.promptTokens
306
- dayStats.completionTokens += usageStats.completionTokens
307
- dayStats.totalTokens += usageStats.totalTokens
308
- dayStats.cacheHitTokens += usageStats.cacheHitTokens
309
- dayStats.cacheMissTokens += usageStats.cacheMissTokens
310
- dayStats.cacheWriteTokens += usageStats.cacheWriteTokens
311
- dayStats.requests += 1
312
-
313
- modelTotals[usedModelId] ??= { cost: 0, tokens: 0, requests: 0 }
314
- modelTotals[usedModelId]!.cost += cost
315
- modelTotals[usedModelId]!.tokens += usageStats.totalTokens
316
- modelTotals[usedModelId]!.requests += 1
317
-
318
- projectTotals[projectName] ??= { cost: 0, tokens: 0, requests: 0 }
319
- projectTotals[projectName]!.cost += cost
320
- projectTotals[projectName]!.tokens += usageStats.totalTokens
321
- projectTotals[projectName]!.requests += 1
322
-
323
- grandTotal.cost += cost
324
- grandTotal.tokens += usageStats.totalTokens
325
- grandTotal.requests += 1
326
- grandTotal.cacheHitTokens += usageStats.cacheHitTokens
327
- grandTotal.cacheMissTokens += usageStats.cacheMissTokens
328
- } catch {
329
- // 忽略无法解析的行
330
- }
331
- }
332
- }
333
-
334
- return finalizeAnalysis(defaultModelId, dailyData, modelTotals, projectTotals, grandTotal)
335
- }
336
-
337
- interface IdeConversationMeta {
338
- id?: unknown
339
- createdAt?: unknown
340
- lastMessageAt?: unknown
341
- }
342
-
343
- interface IdeRequestUsage {
344
- inputTokens?: unknown
345
- outputTokens?: unknown
346
- totalTokens?: unknown
347
- }
348
-
349
- interface IdeRequest {
350
- messages?: unknown
351
- usage?: IdeRequestUsage
352
- }
353
-
354
- interface IdeConversationIndex {
355
- requests?: unknown
356
- }
357
-
358
- async function findIdeHistoryDirs(): Promise<string[]> {
359
- const root = getIdeDataDir()
360
- const out = new Set<string>()
361
-
362
- let level1: fsSync.Dirent[] = []
363
- try {
364
- level1 = await fs.readdir(root, { withFileTypes: true })
365
- } catch {
366
- return []
367
- }
368
-
369
- for (const dirent of level1) {
370
- if (!dirent.isDirectory()) continue
371
- const codeBuddyIdeDir = path.join(root, dirent.name, 'CodeBuddyIDE')
372
- if (!(await pathExists(codeBuddyIdeDir))) continue
373
-
374
- const directHistory = path.join(codeBuddyIdeDir, 'history')
375
- if (await pathExists(directHistory)) {
376
- out.add(directHistory)
377
- continue
378
- }
379
-
380
- let nested: fsSync.Dirent[] = []
381
- try {
382
- nested = await fs.readdir(codeBuddyIdeDir, { withFileTypes: true })
383
- } catch {
384
- continue
385
- }
386
-
387
- for (const child of nested) {
388
- if (!child.isDirectory()) continue
389
- const nestedHistory = path.join(codeBuddyIdeDir, child.name, 'history')
390
- if (await pathExists(nestedHistory)) {
391
- out.add(nestedHistory)
392
- }
393
- }
394
- }
395
-
396
- return [...out]
397
- }
398
-
399
- async function readJsonFile(filePath: string): Promise<unknown> {
400
- const raw = await fs.readFile(filePath, 'utf8')
401
- return JSON.parse(raw) as unknown
402
- }
403
-
404
- async function readFileHeadUtf8(filePath: string, bytes = 64 * 1024): Promise<string> {
405
- const fh = await fs.open(filePath, 'r')
406
- try {
407
- const buf = Buffer.alloc(bytes)
408
- const { bytesRead } = await fh.read(buf, 0, buf.length, 0)
409
- return buf.toString('utf8', 0, bytesRead)
410
- } finally {
411
- await fh.close()
412
- }
413
- }
414
-
415
- function extractModelIdFromMessageHead(head: string): string | null {
416
- // extra 是一个转义的 JSON 字符串,形如: "extra": "{\"modelId\":\"gpt-5.1\",...}"
417
- // 正则需要匹配包含转义字符的完整字符串
418
- const extraMatch = head.match(/"extra"\s*:\s*"((?:[^"\\]|\\.)*)"/s)
419
- if (!extraMatch?.[1]) return null
420
-
421
- try {
422
- // extraMatch[1] 是转义后的内容,需要先解析为字符串
423
- const extraStr = JSON.parse(`"${extraMatch[1]}"`) as string
424
- const extra = JSON.parse(extraStr) as { modelId?: unknown; modelName?: unknown }
425
- if (typeof extra.modelId === 'string' && extra.modelId) return extra.modelId
426
- if (typeof extra.modelName === 'string' && extra.modelName) return extra.modelName
427
- return null
428
- } catch {
429
- return null
430
- }
431
- }
432
-
433
- async function inferIdeModelIdForRequest(
434
- conversationDir: string,
435
- request: IdeRequest,
436
- messageModelCache: Map<string, string>
437
- ): Promise<string | null> {
438
- const messages = Array.isArray(request.messages) ? (request.messages as unknown[]) : []
439
-
440
- for (let i = 0; i < Math.min(messages.length, 3); i++) {
441
- const messageId = messages[i]
442
- if (typeof messageId !== 'string' || !messageId) continue
443
-
444
- const cached = messageModelCache.get(messageId)
445
- if (cached) return cached
446
-
447
- const msgPath = path.join(conversationDir, 'messages', `${messageId}.json`)
448
- try {
449
- const head = await readFileHeadUtf8(msgPath)
450
- const modelId = extractModelIdFromMessageHead(head)
451
- if (modelId) {
452
- messageModelCache.set(messageId, modelId)
453
- return modelId
454
- }
455
- } catch {
456
- // ignore
457
- }
458
- }
459
-
460
- return null
461
- }
462
-
463
- async function loadIdeUsageData(options: LoadUsageOptions = {}): Promise<AnalysisData> {
464
- const defaultModelId = await loadModelFromSettings()
465
- const minDate = computeMinDate(options.days)
466
-
467
- // 加载工作区映射
468
- const workspaceMappings = await loadWorkspaceMappings()
469
-
470
- const dailyData: DailyData = {}
471
- const modelTotals: Record<string, SummaryStats> = {}
472
- const projectTotals: Record<string, SummaryStats> = {}
473
- const grandTotal: GrandTotal = {
474
- cost: 0,
475
- tokens: 0,
476
- requests: 0,
477
- cacheHitTokens: 0,
478
- cacheMissTokens: 0,
479
- }
480
-
481
- const historyDirs = await findIdeHistoryDirs()
482
- const messageModelCache = new Map<string, string>()
483
-
484
- for (const historyDir of historyDirs) {
485
- let workspaces: fsSync.Dirent[] = []
486
- try {
487
- workspaces = await fs.readdir(historyDir, { withFileTypes: true })
488
- } catch {
489
- continue
490
- }
491
-
492
- for (const ws of workspaces) {
493
- if (!ws.isDirectory()) continue
494
- const workspaceHash = ws.name
495
- const workspaceDir = path.join(historyDir, workspaceHash)
496
- const workspaceIndexPath = path.join(workspaceDir, 'index.json')
497
-
498
- let convList: IdeConversationMeta[] = []
499
- try {
500
- const parsed = (await readJsonFile(workspaceIndexPath)) as unknown
501
- if (Array.isArray(parsed)) {
502
- convList = parsed as IdeConversationMeta[]
503
- } else if (parsed && typeof parsed === 'object') {
504
- const maybe = (parsed as any).conversations ?? (parsed as any).items ?? (parsed as any).list
505
- if (Array.isArray(maybe)) convList = maybe as IdeConversationMeta[]
506
- }
507
- } catch {
508
- continue
509
- }
510
-
511
- for (const conv of convList) {
512
- const conversationId = typeof conv.id === 'string' ? conv.id : null
513
- if (!conversationId) continue
514
-
515
- const date = toISODateString(conv.lastMessageAt) ?? toISODateString(conv.createdAt)
516
- if (!date) continue
517
- if (minDate && date < minDate) continue
518
-
519
- const conversationDir = path.join(workspaceDir, conversationId)
520
- const convIndexPath = path.join(conversationDir, 'index.json')
521
-
522
- let convIndex: IdeConversationIndex | null = null
523
- try {
524
- convIndex = (await readJsonFile(convIndexPath)) as IdeConversationIndex
525
- } catch {
526
- continue
527
- }
528
-
529
- const requests = Array.isArray(convIndex?.requests) ? (convIndex!.requests as IdeRequest[]) : []
530
- for (const req of requests) {
531
- const usage = req?.usage
532
- const inputTokens = typeof usage?.inputTokens === 'number' ? usage.inputTokens : Number(usage?.inputTokens ?? 0)
533
- const outputTokens = typeof usage?.outputTokens === 'number' ? usage.outputTokens : Number(usage?.outputTokens ?? 0)
534
- const totalTokens =
535
- typeof usage?.totalTokens === 'number'
536
- ? usage.totalTokens
537
- : Number.isFinite(Number(usage?.totalTokens))
538
- ? Number(usage?.totalTokens)
539
- : inputTokens + outputTokens
540
-
541
- if (!Number.isFinite(inputTokens) || !Number.isFinite(outputTokens) || !Number.isFinite(totalTokens)) continue
542
-
543
- const inferredModelId = await inferIdeModelIdForRequest(conversationDir, req, messageModelCache)
544
- const usedModelId = inferredModelId || defaultModelId
545
-
546
- const rawUsage: RawUsage = {
547
- prompt_tokens: Math.max(0, inputTokens),
548
- completion_tokens: Math.max(0, outputTokens),
549
- total_tokens: Math.max(0, totalTokens),
550
- }
551
-
552
- const { cost, stats } = computeUsageCost(rawUsage, usedModelId)
553
-
554
- const projectName = workspaceHash
555
- const dayStats = ensureDailyModelStats(dailyData, date, projectName, usedModelId)
556
- dayStats.cost += cost
557
- dayStats.promptTokens += stats.promptTokens
558
- dayStats.completionTokens += stats.completionTokens
559
- dayStats.totalTokens += stats.totalTokens
560
- dayStats.requests += 1
561
-
562
- modelTotals[usedModelId] ??= { cost: 0, tokens: 0, requests: 0 }
563
- modelTotals[usedModelId]!.cost += cost
564
- modelTotals[usedModelId]!.tokens += stats.totalTokens
565
- modelTotals[usedModelId]!.requests += 1
566
-
567
- projectTotals[projectName] ??= { cost: 0, tokens: 0, requests: 0 }
568
- projectTotals[projectName]!.cost += cost
569
- projectTotals[projectName]!.tokens += stats.totalTokens
570
- projectTotals[projectName]!.requests += 1
571
-
572
- grandTotal.cost += cost
573
- grandTotal.tokens += stats.totalTokens
574
- grandTotal.requests += 1
575
- }
576
- }
577
- }
578
- }
579
-
580
- return finalizeAnalysis(defaultModelId, dailyData, modelTotals, projectTotals, grandTotal, workspaceMappings)
581
- }
package/src/lib/paths.ts DELETED
@@ -1,70 +0,0 @@
1
- import os from 'node:os'
2
- import path from 'node:path'
3
-
4
- /**
5
- * 获取 CodeBuddy 配置目录
6
- * - Windows: %APPDATA%/CodeBuddy
7
- * - macOS: ~/.codebuddy
8
- * - Linux: $XDG_CONFIG_HOME/codebuddy 或 ~/.codebuddy
9
- */
10
- export function getConfigDir(): string {
11
- if (process.platform === 'win32') {
12
- const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
13
- return path.join(appData, 'CodeBuddy')
14
- }
15
- if (process.platform === 'linux') {
16
- const xdgConfigHome = process.env.XDG_CONFIG_HOME
17
- if (xdgConfigHome) {
18
- return path.join(xdgConfigHome, 'codebuddy')
19
- }
20
- }
21
- // macOS 和 Linux 默认
22
- return path.join(os.homedir(), '.codebuddy')
23
- }
24
-
25
- /**
26
- * 获取 CodeBuddy IDE (CodeBuddyExtension) 数据目录
27
- * - macOS: ~/Library/Application Support/CodeBuddyExtension/Data
28
- * - Windows: %APPDATA%/CodeBuddyExtension/Data
29
- * - Linux: $XDG_CONFIG_HOME/CodeBuddyExtension/Data 或 ~/.config/CodeBuddyExtension/Data
30
- */
31
- export function getIdeDataDir(): string {
32
- if (process.platform === 'darwin') {
33
- return path.join(os.homedir(), 'Library', 'Application Support', 'CodeBuddyExtension', 'Data')
34
- }
35
- if (process.platform === 'win32') {
36
- const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
37
- return path.join(appData, 'CodeBuddyExtension', 'Data')
38
- }
39
- const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config')
40
- return path.join(xdgConfigHome, 'CodeBuddyExtension', 'Data')
41
- }
42
-
43
- /**
44
- * 获取 CodeBuddy IDE 的 workspaceStorage 目录
45
- */
46
- export function getWorkspaceStorageDir(): string {
47
- if (process.platform === 'darwin') {
48
- return path.join(os.homedir(), 'Library', 'Application Support', 'CodeBuddy CN', 'User', 'workspaceStorage')
49
- }
50
- if (process.platform === 'win32') {
51
- const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
52
- return path.join(appData, 'CodeBuddy CN', 'User', 'workspaceStorage')
53
- }
54
- const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config')
55
- return path.join(configHome, 'CodeBuddy CN', 'User', 'workspaceStorage')
56
- }
57
-
58
- /**
59
- * 获取项目数据目录
60
- */
61
- export function getProjectsDir(): string {
62
- return path.join(getConfigDir(), 'projects')
63
- }
64
-
65
- /**
66
- * 获取设置文件路径
67
- */
68
- export function getSettingsPath(): string {
69
- return path.join(getConfigDir(), 'settings.json')
70
- }