@vibe-cafe/vibe-usage 0.9.16 → 0.10.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.
- package/README.md +5 -3
- package/package.json +1 -1
- package/src/claude-roots.js +81 -0
- package/src/daemon-service.js +23 -4
- package/src/parsers/claude-code.js +250 -188
- package/src/parsers/codex-cache.js +138 -0
- package/src/parsers/codex.js +601 -144
- package/src/sync.js +43 -10
- package/src/tools.js +1 -14
package/src/sync.js
CHANGED
|
@@ -34,6 +34,7 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
34
34
|
const allBuckets = [];
|
|
35
35
|
const allSessions = [];
|
|
36
36
|
const parserResults = [];
|
|
37
|
+
const parserProgress = [];
|
|
37
38
|
// Sources whose parser ran to completion this sync. pruneState() below is
|
|
38
39
|
// scoped to these so a transient parser failure doesn't evict that tool's
|
|
39
40
|
// state and force a full re-upload next run.
|
|
@@ -47,6 +48,14 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
47
48
|
if (!Array.isArray(buckets) || !Array.isArray(sessions)) {
|
|
48
49
|
throw new TypeError('Parser returned an invalid result');
|
|
49
50
|
}
|
|
51
|
+
if (result?.indexing) {
|
|
52
|
+
parserProgress.push({ source, ...result.indexing });
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(result?.warnings)) {
|
|
55
|
+
for (const message of result.warnings) {
|
|
56
|
+
process.stderr.write(`${dim(` ${message}`)}\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
50
59
|
// A parser may deliberately suppress a transient error (Cursor network
|
|
51
60
|
// timeout) to keep daemon logs quiet. Its empty result is not proof that
|
|
52
61
|
// its prior data disappeared, so it must not be pruned this run.
|
|
@@ -71,7 +80,13 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
71
80
|
pruneState(state, new Set(), new Set(), okSources);
|
|
72
81
|
const pruned = before - (Object.keys(state.buckets).length + Object.keys(state.sessions).length);
|
|
73
82
|
if (pruned > 0) saveState(state);
|
|
74
|
-
if (!quiet
|
|
83
|
+
if (!quiet && parserProgress.length > 0) {
|
|
84
|
+
for (const p of parserProgress) {
|
|
85
|
+
console.log(dim(` ${p.source}: 正在建立本地索引 ${p.completed}/${p.total}(下次同步继续)`));
|
|
86
|
+
}
|
|
87
|
+
} else if (!quiet) {
|
|
88
|
+
console.log(dim('暂无新数据。'));
|
|
89
|
+
}
|
|
75
90
|
return 0;
|
|
76
91
|
}
|
|
77
92
|
|
|
@@ -83,6 +98,11 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
83
98
|
console.log(` ${dim(p.source.padEnd(14))}${parts.join(' · ')}`);
|
|
84
99
|
}
|
|
85
100
|
}
|
|
101
|
+
if (!quiet && parserProgress.length > 0) {
|
|
102
|
+
for (const p of parserProgress) {
|
|
103
|
+
console.log(dim(` ${p.source}: 正在建立本地索引 ${p.completed}/${p.total}(下次同步继续)`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
86
106
|
|
|
87
107
|
let host = config.hostname;
|
|
88
108
|
if (!host) {
|
|
@@ -112,10 +132,11 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
112
132
|
for (const s of allSessions) s.project = 'unknown';
|
|
113
133
|
}
|
|
114
134
|
|
|
115
|
-
// Incremental diff: parsers above
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
135
|
+
// Incremental upload diff: parsers above emit a complete view of live local
|
|
136
|
+
// data (Codex may assemble that view from its disposable parser cache). Here
|
|
137
|
+
// we drop anything whose content matches what we already uploaded, so only
|
|
138
|
+
// new/changed items go over the network. A quiet machine sends zero bytes;
|
|
139
|
+
// an active one sends just the current 30-min bucket.
|
|
119
140
|
// Missing/corrupt state.json => empty maps => one-time full upload, then
|
|
120
141
|
// incremental forever after.
|
|
121
142
|
const state = loadState();
|
|
@@ -169,6 +190,8 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
169
190
|
let totalIngested = 0;
|
|
170
191
|
let totalSessionsSynced = 0;
|
|
171
192
|
let totalDroppedBuckets = 0;
|
|
193
|
+
let totalDroppedUnknownModels = 0;
|
|
194
|
+
let totalDroppedImplausible = 0;
|
|
172
195
|
const droppedSources = new Set();
|
|
173
196
|
const bucketBatches = Math.ceil(allBucketsToSend.length / BATCH_SIZE);
|
|
174
197
|
const sessionBatches = Math.ceil(allSessionsToSend.length / SESSION_BATCH_SIZE);
|
|
@@ -189,8 +212,11 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
189
212
|
}, batchSessions.length > 0 ? batchSessions : undefined);
|
|
190
213
|
totalIngested += result.ingested ?? batch.length;
|
|
191
214
|
totalSessionsSynced += result.sessions ?? 0;
|
|
215
|
+
const batchUnknownSources = new Set(result.dropped?.unknownSources || []);
|
|
192
216
|
if (result.dropped) {
|
|
193
217
|
totalDroppedBuckets += Number(result.dropped.buckets) || 0;
|
|
218
|
+
totalDroppedUnknownModels += Number(result.dropped.unknownModels) || 0;
|
|
219
|
+
totalDroppedImplausible += Number(result.dropped.implausible) || 0;
|
|
194
220
|
for (const s of result.dropped.unknownSources || []) droppedSources.add(s);
|
|
195
221
|
}
|
|
196
222
|
|
|
@@ -199,6 +225,10 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
199
225
|
// state, so the next sync re-sends exactly those items — no data loss,
|
|
200
226
|
// no silent gaps.
|
|
201
227
|
for (const b of batch) {
|
|
228
|
+
// A source unknown to an older backend may become valid after deploy.
|
|
229
|
+
// Leave those hashes uncommitted so the next sync retries them instead
|
|
230
|
+
// of turning a temporary release-order mismatch into permanent loss.
|
|
231
|
+
if (batchUnknownSources.has(b.source)) continue;
|
|
202
232
|
const key = bucketKey(b);
|
|
203
233
|
const entry = pendingBucketState.get(key);
|
|
204
234
|
if (entry) state.buckets[key] = entry;
|
|
@@ -219,11 +249,14 @@ export async function runSync({ throws = false, quiet = false } = {}) {
|
|
|
219
249
|
console.log(success(`已同步 ${syncParts.join(' · ')}`));
|
|
220
250
|
|
|
221
251
|
if (totalDroppedBuckets > 0) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
252
|
+
const reasons = [];
|
|
253
|
+
if (droppedSources.size > 0) {
|
|
254
|
+
reasons.push(`服务端未收录的 source: ${Array.from(droppedSources).sort().join(', ')}`);
|
|
255
|
+
}
|
|
256
|
+
if (totalDroppedUnknownModels > 0) reasons.push(`模型未知: ${totalDroppedUnknownModels}`);
|
|
257
|
+
if (totalDroppedImplausible > 0) reasons.push(`超出合理范围: ${totalDroppedImplausible}`);
|
|
258
|
+
if (reasons.length === 0) reasons.push('服务端拒绝');
|
|
259
|
+
console.log(dim(` ${totalDroppedBuckets} buckets dropped (${reasons.join(';')})`));
|
|
227
260
|
}
|
|
228
261
|
|
|
229
262
|
if (!quiet && totalSessionsSynced > 0) {
|
package/src/tools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
+
import { findClaudeCodeDataDirs } from './claude-roots.js';
|
|
4
5
|
|
|
5
6
|
function getCursorStateDbPath() {
|
|
6
7
|
const rel = join('User', 'globalStorage', 'state.vscdb');
|
|
@@ -80,20 +81,6 @@ function findOpenclawDataDirs() {
|
|
|
80
81
|
return dirs;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
// Claude Code lives in ~/.claude/projects, but $CLAUDE_CONFIG_DIR relocates its
|
|
84
|
-
// whole tree. Detect either so a user who only set CLAUDE_CONFIG_DIR is still
|
|
85
|
-
// recognized (the parser scans both roots; see parsers/claude-code.js).
|
|
86
|
-
function findClaudeCodeDataDirs() {
|
|
87
|
-
const dirs = [join(homedir(), '.claude', 'projects')];
|
|
88
|
-
const cfg = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
89
|
-
if (cfg) {
|
|
90
|
-
let custom = cfg.startsWith('~') ? join(homedir(), cfg.slice(1)) : cfg;
|
|
91
|
-
custom = custom.replace(/[/\\]+$/, '') || custom;
|
|
92
|
-
dirs.push(join(custom, 'projects'));
|
|
93
|
-
}
|
|
94
|
-
return dirs.filter(existsSync);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
84
|
// Codex keeps live sessions in ~/.codex/sessions and moves completed ones to
|
|
98
85
|
// ~/.codex/archived_sessions. Detect Codex if either dir exists, so a user
|
|
99
86
|
// whose sessions have all been archived is still recognized.
|