vibeusage 0.2.21 → 0.2.22
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 +306 -173
- package/README.old.md +324 -0
- package/README.zh-CN.md +304 -188
- package/package.json +32 -32
- package/src/cli.js +41 -37
- package/src/commands/activate-if-needed.js +41 -0
- package/src/commands/diagnostics.js +8 -9
- package/src/commands/doctor.js +31 -26
- package/src/commands/init.js +285 -218
- package/src/commands/status.js +86 -83
- package/src/commands/sync.js +182 -130
- package/src/commands/uninstall.js +66 -62
- package/src/lib/activation-check.js +290 -0
- package/src/lib/browser-auth.js +52 -54
- package/src/lib/claude-config.js +25 -25
- package/src/lib/cli-ui.js +35 -35
- package/src/lib/codex-config.js +40 -36
- package/src/lib/debug-flags.js +2 -2
- package/src/lib/diagnostics.js +70 -57
- package/src/lib/doctor.js +139 -132
- package/src/lib/fs.js +17 -17
- package/src/lib/gemini-config.js +44 -40
- package/src/lib/init-flow.js +16 -22
- package/src/lib/insforge-client.js +10 -10
- package/src/lib/insforge.js +9 -3
- package/src/lib/openclaw-hook.js +89 -66
- package/src/lib/openclaw-session-plugin.js +116 -92
- package/src/lib/opencode-config.js +31 -32
- package/src/lib/opencode-usage-audit.js +34 -31
- package/src/lib/progress.js +12 -13
- package/src/lib/project-usage-purge.js +23 -17
- package/src/lib/prompt.js +8 -4
- package/src/lib/rollout.js +342 -241
- package/src/lib/runtime-config.js +34 -22
- package/src/lib/subscriptions.js +94 -92
- package/src/lib/tracker-paths.js +6 -6
- package/src/lib/upload-throttle.js +35 -16
- package/src/lib/uploader.js +33 -29
- package/src/lib/vibeusage-api.js +72 -56
- package/src/lib/vibeusage-public-repo.js +41 -24
package/src/commands/sync.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const os = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const fs = require(
|
|
4
|
-
const cp = require(
|
|
1
|
+
const os = require("node:os");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const fs = require("node:fs/promises");
|
|
4
|
+
const cp = require("node:child_process");
|
|
5
5
|
|
|
6
|
-
const { ensureDir, readJson, writeJson, openLock } = require(
|
|
6
|
+
const { ensureDir, readJson, writeJson, openLock } = require("../lib/fs");
|
|
7
7
|
const {
|
|
8
8
|
listRolloutFiles,
|
|
9
9
|
listClaudeProjectFiles,
|
|
@@ -13,22 +13,23 @@ const {
|
|
|
13
13
|
parseClaudeIncremental,
|
|
14
14
|
parseGeminiIncremental,
|
|
15
15
|
parseOpencodeIncremental,
|
|
16
|
-
parseOpenclawIncremental
|
|
17
|
-
} = require(
|
|
18
|
-
const { drainQueueToCloud } = require(
|
|
19
|
-
const { collectLocalSubscriptions } = require(
|
|
20
|
-
const { createProgress, renderBar, formatNumber, formatBytes } = require(
|
|
21
|
-
const { syncHeartbeat } = require(
|
|
16
|
+
parseOpenclawIncremental,
|
|
17
|
+
} = require("../lib/rollout");
|
|
18
|
+
const { drainQueueToCloud } = require("../lib/uploader");
|
|
19
|
+
const { collectLocalSubscriptions } = require("../lib/subscriptions");
|
|
20
|
+
const { createProgress, renderBar, formatNumber, formatBytes } = require("../lib/progress");
|
|
21
|
+
const { syncHeartbeat } = require("../lib/vibeusage-api");
|
|
22
22
|
const {
|
|
23
23
|
DEFAULTS: UPLOAD_DEFAULTS,
|
|
24
24
|
normalizeState: normalizeUploadState,
|
|
25
25
|
decideAutoUpload,
|
|
26
26
|
recordUploadSuccess,
|
|
27
|
-
recordUploadFailure
|
|
28
|
-
} = require(
|
|
29
|
-
const { purgeProjectUsage } = require(
|
|
30
|
-
const { resolveTrackerPaths } = require(
|
|
31
|
-
const { resolveRuntimeConfig } = require(
|
|
27
|
+
recordUploadFailure,
|
|
28
|
+
} = require("../lib/upload-throttle");
|
|
29
|
+
const { purgeProjectUsage } = require("../lib/project-usage-purge");
|
|
30
|
+
const { resolveTrackerPaths } = require("../lib/tracker-paths");
|
|
31
|
+
const { resolveRuntimeConfig } = require("../lib/runtime-config");
|
|
32
|
+
const { checkAndActivate } = require("../lib/activation-check");
|
|
32
33
|
|
|
33
34
|
async function cmdSync(argv) {
|
|
34
35
|
const opts = parseArgs(argv);
|
|
@@ -40,42 +41,44 @@ async function cmdSync(argv) {
|
|
|
40
41
|
await writeOpenclawSignal(trackerDir);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const lockPath = path.join(trackerDir,
|
|
44
|
+
const lockPath = path.join(trackerDir, "sync.lock");
|
|
44
45
|
const lock = await openLock(lockPath, { quietIfLocked: opts.auto });
|
|
45
46
|
if (!lock) return;
|
|
46
47
|
|
|
47
48
|
let progress = null;
|
|
48
49
|
try {
|
|
49
50
|
progress = !opts.auto ? createProgress({ stream: process.stdout }) : null;
|
|
50
|
-
const configPath = path.join(trackerDir,
|
|
51
|
-
const cursorsPath = path.join(trackerDir,
|
|
52
|
-
const queuePath = path.join(trackerDir,
|
|
53
|
-
const queueStatePath = path.join(trackerDir,
|
|
54
|
-
const projectQueuePath = path.join(trackerDir,
|
|
55
|
-
const projectQueueStatePath = path.join(trackerDir,
|
|
56
|
-
const uploadThrottlePath = path.join(trackerDir,
|
|
51
|
+
const configPath = path.join(trackerDir, "config.json");
|
|
52
|
+
const cursorsPath = path.join(trackerDir, "cursors.json");
|
|
53
|
+
const queuePath = path.join(trackerDir, "queue.jsonl");
|
|
54
|
+
const queueStatePath = path.join(trackerDir, "queue.state.json");
|
|
55
|
+
const projectQueuePath = path.join(trackerDir, "project.queue.jsonl");
|
|
56
|
+
const projectQueueStatePath = path.join(trackerDir, "project.queue.state.json");
|
|
57
|
+
const uploadThrottlePath = path.join(trackerDir, "upload.throttle.json");
|
|
57
58
|
|
|
58
59
|
const config = await readJson(configPath);
|
|
59
60
|
const cursors = (await readJson(cursorsPath)) || { version: 1, files: {}, updatedAt: null };
|
|
60
61
|
const uploadThrottle = normalizeUploadState(await readJson(uploadThrottlePath));
|
|
61
62
|
let uploadThrottleState = uploadThrottle;
|
|
62
63
|
|
|
63
|
-
const codexHome = process.env.CODEX_HOME || path.join(home,
|
|
64
|
-
const codeHome = process.env.CODE_HOME || path.join(home,
|
|
65
|
-
const claudeProjectsDir = path.join(home,
|
|
66
|
-
const geminiHome = process.env.GEMINI_HOME || path.join(home,
|
|
67
|
-
const geminiTmpDir = path.join(geminiHome,
|
|
68
|
-
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home,
|
|
69
|
-
const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome,
|
|
70
|
-
const opencodeStorageDir = path.join(opencodeHome,
|
|
64
|
+
const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
|
|
65
|
+
const codeHome = process.env.CODE_HOME || path.join(home, ".code");
|
|
66
|
+
const claudeProjectsDir = path.join(home, ".claude", "projects");
|
|
67
|
+
const geminiHome = process.env.GEMINI_HOME || path.join(home, ".gemini");
|
|
68
|
+
const geminiTmpDir = path.join(geminiHome, "tmp");
|
|
69
|
+
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, ".local", "share");
|
|
70
|
+
const opencodeHome = process.env.OPENCODE_HOME || path.join(xdgDataHome, "opencode");
|
|
71
|
+
const opencodeStorageDir = path.join(opencodeHome, "storage");
|
|
71
72
|
|
|
72
73
|
// OpenClaw hook integration: allow a hook to request incremental parsing for a single session jsonl.
|
|
73
74
|
// We still parse all regular sources so model/source attribution stays complete (e.g. Kimi sessions).
|
|
74
|
-
const openclawSignal = opts.fromOpenclaw
|
|
75
|
+
const openclawSignal = opts.fromOpenclaw
|
|
76
|
+
? resolveOpenclawSignal({ home, env: process.env })
|
|
77
|
+
: null;
|
|
75
78
|
|
|
76
79
|
const sources = [
|
|
77
|
-
{ source:
|
|
78
|
-
{ source:
|
|
80
|
+
{ source: "codex", sessionsDir: path.join(codexHome, "sessions") },
|
|
81
|
+
{ source: "every-code", sessionsDir: path.join(codeHome, "sessions") },
|
|
79
82
|
];
|
|
80
83
|
|
|
81
84
|
const rolloutFiles = [];
|
|
@@ -89,10 +92,14 @@ async function cmdSync(argv) {
|
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
const openclawFiles = openclawSignal?.sessionFile
|
|
95
|
+
const openclawFiles = openclawSignal?.sessionFile
|
|
96
|
+
? [{ path: openclawSignal.sessionFile, source: "openclaw" }]
|
|
97
|
+
: [];
|
|
93
98
|
|
|
94
99
|
if (progress?.enabled) {
|
|
95
|
-
progress.start(
|
|
100
|
+
progress.start(
|
|
101
|
+
`Parsing ${renderBar(0)} 0/${formatNumber(rolloutFiles.length)} files | buckets 0`,
|
|
102
|
+
);
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
const parseResult = await parseRolloutIncremental({
|
|
@@ -105,10 +112,10 @@ async function cmdSync(argv) {
|
|
|
105
112
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
106
113
|
progress.update(
|
|
107
114
|
`Parsing ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
108
|
-
p.bucketsQueued
|
|
109
|
-
)}
|
|
115
|
+
p.bucketsQueued,
|
|
116
|
+
)}`,
|
|
110
117
|
);
|
|
111
|
-
}
|
|
118
|
+
},
|
|
112
119
|
});
|
|
113
120
|
|
|
114
121
|
let openclawResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
@@ -119,7 +126,7 @@ async function cmdSync(argv) {
|
|
|
119
126
|
cursors,
|
|
120
127
|
queuePath,
|
|
121
128
|
projectQueuePath,
|
|
122
|
-
source:
|
|
129
|
+
source: "openclaw",
|
|
123
130
|
});
|
|
124
131
|
}
|
|
125
132
|
|
|
@@ -128,7 +135,7 @@ async function cmdSync(argv) {
|
|
|
128
135
|
signal: openclawSignal,
|
|
129
136
|
cursors,
|
|
130
137
|
queuePath,
|
|
131
|
-
projectQueuePath
|
|
138
|
+
projectQueuePath,
|
|
132
139
|
});
|
|
133
140
|
openclawResult.filesProcessed += openclawFallback.filesProcessed;
|
|
134
141
|
openclawResult.eventsAggregated += openclawFallback.eventsAggregated;
|
|
@@ -138,7 +145,9 @@ async function cmdSync(argv) {
|
|
|
138
145
|
let claudeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
139
146
|
if (claudeFiles.length > 0) {
|
|
140
147
|
if (progress?.enabled) {
|
|
141
|
-
progress.start(
|
|
148
|
+
progress.start(
|
|
149
|
+
`Parsing Claude ${renderBar(0)} 0/${formatNumber(claudeFiles.length)} files | buckets 0`,
|
|
150
|
+
);
|
|
142
151
|
}
|
|
143
152
|
claudeResult = await parseClaudeIncremental({
|
|
144
153
|
projectFiles: claudeFiles,
|
|
@@ -150,11 +159,11 @@ async function cmdSync(argv) {
|
|
|
150
159
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
151
160
|
progress.update(
|
|
152
161
|
`Parsing Claude ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
153
|
-
p.bucketsQueued
|
|
154
|
-
)}
|
|
162
|
+
p.bucketsQueued,
|
|
163
|
+
)}`,
|
|
155
164
|
);
|
|
156
165
|
},
|
|
157
|
-
source:
|
|
166
|
+
source: "claude",
|
|
158
167
|
});
|
|
159
168
|
}
|
|
160
169
|
|
|
@@ -162,7 +171,9 @@ async function cmdSync(argv) {
|
|
|
162
171
|
let geminiResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
163
172
|
if (geminiFiles.length > 0) {
|
|
164
173
|
if (progress?.enabled) {
|
|
165
|
-
progress.start(
|
|
174
|
+
progress.start(
|
|
175
|
+
`Parsing Gemini ${renderBar(0)} 0/${formatNumber(geminiFiles.length)} files | buckets 0`,
|
|
176
|
+
);
|
|
166
177
|
}
|
|
167
178
|
geminiResult = await parseGeminiIncremental({
|
|
168
179
|
sessionFiles: geminiFiles,
|
|
@@ -174,11 +185,11 @@ async function cmdSync(argv) {
|
|
|
174
185
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
175
186
|
progress.update(
|
|
176
187
|
`Parsing Gemini ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(p.total)} files | buckets ${formatNumber(
|
|
177
|
-
p.bucketsQueued
|
|
178
|
-
)}
|
|
188
|
+
p.bucketsQueued,
|
|
189
|
+
)}`,
|
|
179
190
|
);
|
|
180
191
|
},
|
|
181
|
-
source:
|
|
192
|
+
source: "gemini",
|
|
182
193
|
});
|
|
183
194
|
}
|
|
184
195
|
|
|
@@ -186,7 +197,9 @@ async function cmdSync(argv) {
|
|
|
186
197
|
let opencodeResult = { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
187
198
|
if (opencodeFiles.length > 0) {
|
|
188
199
|
if (progress?.enabled) {
|
|
189
|
-
progress.start(
|
|
200
|
+
progress.start(
|
|
201
|
+
`Parsing Opencode ${renderBar(0)} 0/${formatNumber(opencodeFiles.length)} files | buckets 0`,
|
|
202
|
+
);
|
|
190
203
|
}
|
|
191
204
|
opencodeResult = await parseOpencodeIncremental({
|
|
192
205
|
messageFiles: opencodeFiles,
|
|
@@ -198,23 +211,23 @@ async function cmdSync(argv) {
|
|
|
198
211
|
const pct = p.total > 0 ? p.index / p.total : 1;
|
|
199
212
|
progress.update(
|
|
200
213
|
`Parsing Opencode ${renderBar(pct)} ${formatNumber(p.index)}/${formatNumber(
|
|
201
|
-
p.total
|
|
202
|
-
)} files | buckets ${formatNumber(p.bucketsQueued)}
|
|
214
|
+
p.total,
|
|
215
|
+
)} files | buckets ${formatNumber(p.bucketsQueued)}`,
|
|
203
216
|
);
|
|
204
217
|
},
|
|
205
|
-
source:
|
|
218
|
+
source: "opencode",
|
|
206
219
|
});
|
|
207
220
|
}
|
|
208
221
|
|
|
209
222
|
if (cursors?.projectHourly?.projects && projectQueuePath && projectQueueStatePath) {
|
|
210
223
|
for (const [projectKey, meta] of Object.entries(cursors.projectHourly.projects)) {
|
|
211
|
-
if (!meta || typeof meta !==
|
|
212
|
-
if (meta.status !==
|
|
224
|
+
if (!meta || typeof meta !== "object") continue;
|
|
225
|
+
if (meta.status !== "blocked" || !meta.purge_pending) continue;
|
|
213
226
|
await purgeProjectUsage({
|
|
214
227
|
projectKey,
|
|
215
228
|
projectQueuePath,
|
|
216
229
|
projectQueueStatePath,
|
|
217
|
-
projectState: cursors.projectHourly
|
|
230
|
+
projectState: cursors.projectHourly,
|
|
218
231
|
});
|
|
219
232
|
meta.purge_pending = false;
|
|
220
233
|
meta.purged_at = new Date().toISOString();
|
|
@@ -249,7 +262,7 @@ async function cmdSync(argv) {
|
|
|
249
262
|
autoDecision = decideAutoUpload({
|
|
250
263
|
nowMs: Date.now(),
|
|
251
264
|
pendingBytes,
|
|
252
|
-
state: uploadThrottle
|
|
265
|
+
state: uploadThrottle,
|
|
253
266
|
});
|
|
254
267
|
allowUpload = allowUpload && autoDecision.allowed;
|
|
255
268
|
maxBatches = autoDecision.allowed ? autoDecision.maxBatches : 0;
|
|
@@ -261,18 +274,19 @@ async function cmdSync(argv) {
|
|
|
261
274
|
retryAtMs: autoDecision.blockedUntilMs,
|
|
262
275
|
reason,
|
|
263
276
|
pendingBytes,
|
|
264
|
-
source:
|
|
265
|
-
autoRetryNoSpawn: runtime.autoRetryNoSpawn
|
|
277
|
+
source: "auto-throttled",
|
|
278
|
+
autoRetryNoSpawn: runtime.autoRetryNoSpawn,
|
|
266
279
|
});
|
|
267
280
|
}
|
|
268
281
|
}
|
|
269
282
|
|
|
270
283
|
if (progress?.enabled && pendingBytes > 0 && allowUpload) {
|
|
271
284
|
const totalSize = queueSize + projectQueueSize;
|
|
272
|
-
const totalOffset =
|
|
285
|
+
const totalOffset =
|
|
286
|
+
Number(beforeState.offset || 0) + Number(projectBeforeState.offset || 0);
|
|
273
287
|
const pct = totalSize > 0 ? totalOffset / totalSize : 0;
|
|
274
288
|
progress.start(
|
|
275
|
-
`Uploading ${renderBar(pct)} ${formatBytes(totalOffset)}/${formatBytes(totalSize)} | inserted 0 skipped 0
|
|
289
|
+
`Uploading ${renderBar(pct)} ${formatBytes(totalOffset)}/${formatBytes(totalSize)} | inserted 0 skipped 0`,
|
|
276
290
|
);
|
|
277
291
|
}
|
|
278
292
|
|
|
@@ -282,7 +296,7 @@ async function cmdSync(argv) {
|
|
|
282
296
|
home,
|
|
283
297
|
env: process.env,
|
|
284
298
|
probeKeychain: true,
|
|
285
|
-
probeKeychainDetails: true
|
|
299
|
+
probeKeychainDetails: true,
|
|
286
300
|
});
|
|
287
301
|
try {
|
|
288
302
|
uploadResult = await drainQueueToCloud({
|
|
@@ -300,10 +314,10 @@ async function cmdSync(argv) {
|
|
|
300
314
|
const pct = u.queueSize > 0 ? u.offset / u.queueSize : 1;
|
|
301
315
|
progress.update(
|
|
302
316
|
`Uploading ${renderBar(pct)} ${formatBytes(u.offset)}/${formatBytes(u.queueSize)} | inserted ${formatNumber(
|
|
303
|
-
u.inserted
|
|
304
|
-
)} skipped ${formatNumber(u.skipped)}
|
|
317
|
+
u.inserted,
|
|
318
|
+
)} skipped ${formatNumber(u.skipped)}`,
|
|
305
319
|
);
|
|
306
|
-
}
|
|
320
|
+
},
|
|
307
321
|
});
|
|
308
322
|
if (uploadResult.attempted > 0) {
|
|
309
323
|
const next = recordUploadSuccess({ nowMs: Date.now(), state: uploadThrottleState });
|
|
@@ -311,7 +325,11 @@ async function cmdSync(argv) {
|
|
|
311
325
|
await writeJson(uploadThrottlePath, next);
|
|
312
326
|
}
|
|
313
327
|
} catch (e) {
|
|
314
|
-
const next = recordUploadFailure({
|
|
328
|
+
const next = recordUploadFailure({
|
|
329
|
+
nowMs: Date.now(),
|
|
330
|
+
state: uploadThrottleState,
|
|
331
|
+
error: e,
|
|
332
|
+
});
|
|
315
333
|
uploadThrottleState = next;
|
|
316
334
|
await writeJson(uploadThrottlePath, next);
|
|
317
335
|
if (opts.auto && pendingBytes > 0) {
|
|
@@ -320,10 +338,10 @@ async function cmdSync(argv) {
|
|
|
320
338
|
await scheduleAutoRetry({
|
|
321
339
|
trackerDir,
|
|
322
340
|
retryAtMs,
|
|
323
|
-
reason:
|
|
341
|
+
reason: "backoff",
|
|
324
342
|
pendingBytes,
|
|
325
|
-
source:
|
|
326
|
-
autoRetryNoSpawn: runtime.autoRetryNoSpawn
|
|
343
|
+
source: "auto-error",
|
|
344
|
+
autoRetryNoSpawn: runtime.autoRetryNoSpawn,
|
|
327
345
|
});
|
|
328
346
|
}
|
|
329
347
|
}
|
|
@@ -352,10 +370,10 @@ async function cmdSync(argv) {
|
|
|
352
370
|
await scheduleAutoRetry({
|
|
353
371
|
trackerDir,
|
|
354
372
|
retryAtMs,
|
|
355
|
-
reason:
|
|
373
|
+
reason: "backlog",
|
|
356
374
|
pendingBytes,
|
|
357
|
-
source:
|
|
358
|
-
autoRetryNoSpawn: runtime.autoRetryNoSpawn
|
|
375
|
+
source: "auto-backlog",
|
|
376
|
+
autoRetryNoSpawn: runtime.autoRetryNoSpawn,
|
|
359
377
|
});
|
|
360
378
|
}
|
|
361
379
|
}
|
|
@@ -365,7 +383,7 @@ async function cmdSync(argv) {
|
|
|
365
383
|
deviceToken,
|
|
366
384
|
trackerDir,
|
|
367
385
|
uploadResult,
|
|
368
|
-
pendingBytes
|
|
386
|
+
pendingBytes,
|
|
369
387
|
});
|
|
370
388
|
|
|
371
389
|
if (!opts.auto) {
|
|
@@ -383,25 +401,28 @@ async function cmdSync(argv) {
|
|
|
383
401
|
opencodeResult.bucketsQueued;
|
|
384
402
|
process.stdout.write(
|
|
385
403
|
[
|
|
386
|
-
|
|
404
|
+
"Sync finished:",
|
|
387
405
|
`- Parsed files: ${totalParsed}`,
|
|
388
406
|
`- New 30-min buckets queued: ${totalBuckets}`,
|
|
389
407
|
deviceToken
|
|
390
408
|
? `- Uploaded: ${uploadResult.inserted} inserted, ${uploadResult.skipped} skipped`
|
|
391
|
-
:
|
|
409
|
+
: "- Uploaded: skipped (no device token)",
|
|
392
410
|
deviceToken && pendingBytes > 0 && !opts.drain
|
|
393
411
|
? `- Remaining: ${formatBytes(pendingBytes)} pending (run sync again, or use --drain)`
|
|
394
412
|
: null,
|
|
395
|
-
|
|
413
|
+
"",
|
|
396
414
|
]
|
|
397
415
|
.filter(Boolean)
|
|
398
|
-
.join(
|
|
416
|
+
.join("\n"),
|
|
399
417
|
);
|
|
400
418
|
}
|
|
401
419
|
} finally {
|
|
402
420
|
progress?.stop();
|
|
403
421
|
await lock.release();
|
|
404
422
|
await fs.unlink(lockPath).catch(() => {});
|
|
423
|
+
|
|
424
|
+
// 顺带检测并激活其他 AI CLI 集成(静默执行,不阻塞主流程)
|
|
425
|
+
checkAndActivate({ home, silent: true }).catch(() => {});
|
|
405
426
|
}
|
|
406
427
|
}
|
|
407
428
|
|
|
@@ -411,15 +432,15 @@ function parseArgs(argv) {
|
|
|
411
432
|
fromNotify: false,
|
|
412
433
|
fromRetry: false,
|
|
413
434
|
fromOpenclaw: false,
|
|
414
|
-
drain: false
|
|
435
|
+
drain: false,
|
|
415
436
|
};
|
|
416
437
|
for (let i = 0; i < argv.length; i++) {
|
|
417
438
|
const a = argv[i];
|
|
418
|
-
if (a ===
|
|
419
|
-
else if (a ===
|
|
420
|
-
else if (a ===
|
|
421
|
-
else if (a ===
|
|
422
|
-
else if (a ===
|
|
439
|
+
if (a === "--auto") out.auto = true;
|
|
440
|
+
else if (a === "--from-notify") out.fromNotify = true;
|
|
441
|
+
else if (a === "--from-retry") out.fromRetry = true;
|
|
442
|
+
else if (a === "--from-openclaw") out.fromOpenclaw = true;
|
|
443
|
+
else if (a === "--drain") out.drain = true;
|
|
423
444
|
else throw new Error(`Unknown option: ${a}`);
|
|
424
445
|
}
|
|
425
446
|
return out;
|
|
@@ -428,7 +449,7 @@ function parseArgs(argv) {
|
|
|
428
449
|
module.exports = { cmdSync };
|
|
429
450
|
|
|
430
451
|
function normalizeString(value) {
|
|
431
|
-
if (typeof value !==
|
|
452
|
+
if (typeof value !== "string") return null;
|
|
432
453
|
const trimmed = value.trim();
|
|
433
454
|
return trimmed.length > 0 ? trimmed : null;
|
|
434
455
|
}
|
|
@@ -440,15 +461,16 @@ function resolveOpenclawSignal({ home, env } = {}) {
|
|
|
440
461
|
const sessionId = normalizeString(env.VIBEUSAGE_OPENCLAW_PREV_SESSION_ID);
|
|
441
462
|
if (!agentId || !sessionId) return null;
|
|
442
463
|
|
|
443
|
-
const openclawHome =
|
|
444
|
-
|
|
464
|
+
const openclawHome =
|
|
465
|
+
normalizeString(env.VIBEUSAGE_OPENCLAW_HOME) || path.join(home || os.homedir(), ".openclaw");
|
|
466
|
+
const sessionFile = path.join(openclawHome, "agents", agentId, "sessions", `${sessionId}.jsonl`);
|
|
445
467
|
|
|
446
468
|
const prevTotals = {
|
|
447
469
|
totalTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_TOTAL_TOKENS),
|
|
448
470
|
inputTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_INPUT_TOKENS),
|
|
449
471
|
outputTokens: normalizeNonNegativeInt(env.VIBEUSAGE_OPENCLAW_PREV_OUTPUT_TOKENS),
|
|
450
472
|
model: normalizeString(env.VIBEUSAGE_OPENCLAW_PREV_MODEL),
|
|
451
|
-
updatedAt: normalizeIsoOrEpoch(env.VIBEUSAGE_OPENCLAW_PREV_UPDATED_AT)
|
|
473
|
+
updatedAt: normalizeIsoOrEpoch(env.VIBEUSAGE_OPENCLAW_PREV_UPDATED_AT),
|
|
452
474
|
};
|
|
453
475
|
|
|
454
476
|
return {
|
|
@@ -457,40 +479,55 @@ function resolveOpenclawSignal({ home, env } = {}) {
|
|
|
457
479
|
sessionKey: normalizeString(env.VIBEUSAGE_OPENCLAW_SESSION_KEY),
|
|
458
480
|
openclawHome,
|
|
459
481
|
sessionFile,
|
|
460
|
-
prevTotals
|
|
482
|
+
prevTotals,
|
|
461
483
|
};
|
|
462
484
|
}
|
|
463
485
|
|
|
464
|
-
|
|
465
|
-
|
|
486
|
+
async function applyOpenclawTotalsFallback({
|
|
487
|
+
trackerDir,
|
|
488
|
+
signal,
|
|
489
|
+
cursors,
|
|
490
|
+
queuePath,
|
|
491
|
+
projectQueuePath,
|
|
492
|
+
}) {
|
|
466
493
|
const totalTokens = Number(signal?.prevTotals?.totalTokens || 0);
|
|
467
494
|
if (!trackerDir || !signal || totalTokens <= 0) {
|
|
468
495
|
return { filesProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
469
496
|
}
|
|
470
497
|
|
|
471
498
|
const sessionKey = `${signal.agentId}:${signal.sessionId}`;
|
|
472
|
-
const statePath = path.join(trackerDir,
|
|
473
|
-
const fallbackFilePath = path.join(trackerDir,
|
|
499
|
+
const statePath = path.join(trackerDir, "openclaw.fallback.state.json");
|
|
500
|
+
const fallbackFilePath = path.join(trackerDir, "openclaw.fallback.jsonl");
|
|
474
501
|
const state = (await readJson(statePath)) || { version: 1, sessions: {} };
|
|
475
|
-
const sessions = state.sessions && typeof state.sessions ===
|
|
476
|
-
const prev =
|
|
502
|
+
const sessions = state.sessions && typeof state.sessions === "object" ? state.sessions : {};
|
|
503
|
+
const prev =
|
|
504
|
+
sessions[sessionKey] && typeof sessions[sessionKey] === "object" ? sessions[sessionKey] : null;
|
|
477
505
|
|
|
478
506
|
const current = {
|
|
479
507
|
totalTokens: normalizeNonNegativeInt(signal?.prevTotals?.totalTokens) || 0,
|
|
480
508
|
inputTokens: normalizeNonNegativeInt(signal?.prevTotals?.inputTokens) || 0,
|
|
481
509
|
outputTokens: normalizeNonNegativeInt(signal?.prevTotals?.outputTokens) || 0,
|
|
482
|
-
model: normalizeString(signal?.prevTotals?.model) ||
|
|
510
|
+
model: normalizeString(signal?.prevTotals?.model) || "unknown",
|
|
483
511
|
updatedAt: normalizeIsoOrEpoch(signal?.prevTotals?.updatedAt) || new Date().toISOString(),
|
|
484
|
-
seenAt: new Date().toISOString()
|
|
512
|
+
seenAt: new Date().toISOString(),
|
|
485
513
|
};
|
|
486
514
|
|
|
487
515
|
let deltaTotal = current.totalTokens;
|
|
488
516
|
let deltaInput = current.inputTokens;
|
|
489
517
|
let deltaOutput = current.outputTokens;
|
|
490
518
|
if (prev) {
|
|
491
|
-
deltaTotal = Math.max(
|
|
492
|
-
|
|
493
|
-
|
|
519
|
+
deltaTotal = Math.max(
|
|
520
|
+
0,
|
|
521
|
+
current.totalTokens - (normalizeNonNegativeInt(prev.totalTokens) || 0),
|
|
522
|
+
);
|
|
523
|
+
deltaInput = Math.max(
|
|
524
|
+
0,
|
|
525
|
+
current.inputTokens - (normalizeNonNegativeInt(prev.inputTokens) || 0),
|
|
526
|
+
);
|
|
527
|
+
deltaOutput = Math.max(
|
|
528
|
+
0,
|
|
529
|
+
current.outputTokens - (normalizeNonNegativeInt(prev.outputTokens) || 0),
|
|
530
|
+
);
|
|
494
531
|
}
|
|
495
532
|
|
|
496
533
|
if (deltaTotal > 0 && deltaInput + deltaOutput === 0) {
|
|
@@ -508,29 +545,29 @@ async function applyOpenclawTotalsFallback({ trackerDir, signal, cursors, queueP
|
|
|
508
545
|
|
|
509
546
|
await ensureDir(path.dirname(fallbackFilePath));
|
|
510
547
|
const syntheticMessage = {
|
|
511
|
-
type:
|
|
548
|
+
type: "message",
|
|
512
549
|
timestamp: current.updatedAt,
|
|
513
550
|
message: {
|
|
514
|
-
role:
|
|
551
|
+
role: "assistant",
|
|
515
552
|
model: current.model,
|
|
516
553
|
usage: {
|
|
517
554
|
input: deltaInput,
|
|
518
555
|
output: deltaOutput,
|
|
519
556
|
cacheRead: 0,
|
|
520
557
|
cacheWrite: 0,
|
|
521
|
-
totalTokens: deltaTotal
|
|
522
|
-
}
|
|
523
|
-
}
|
|
558
|
+
totalTokens: deltaTotal,
|
|
559
|
+
},
|
|
560
|
+
},
|
|
524
561
|
};
|
|
525
|
-
await fs.appendFile(fallbackFilePath, `${JSON.stringify(syntheticMessage)}\n`,
|
|
562
|
+
await fs.appendFile(fallbackFilePath, `${JSON.stringify(syntheticMessage)}\n`, "utf8");
|
|
526
563
|
await writeJson(statePath, state);
|
|
527
564
|
|
|
528
565
|
return parseOpenclawIncremental({
|
|
529
|
-
sessionFiles: [{ path: fallbackFilePath, source:
|
|
566
|
+
sessionFiles: [{ path: fallbackFilePath, source: "openclaw" }],
|
|
530
567
|
cursors,
|
|
531
568
|
queuePath,
|
|
532
569
|
projectQueuePath,
|
|
533
|
-
source:
|
|
570
|
+
source: "openclaw",
|
|
534
571
|
});
|
|
535
572
|
}
|
|
536
573
|
|
|
@@ -541,7 +578,7 @@ function normalizeNonNegativeInt(value) {
|
|
|
541
578
|
}
|
|
542
579
|
|
|
543
580
|
function normalizeIsoOrEpoch(value) {
|
|
544
|
-
if (typeof value ===
|
|
581
|
+
if (typeof value === "string") {
|
|
545
582
|
const trimmed = value.trim();
|
|
546
583
|
if (trimmed.length > 0 && !Number.isNaN(Date.parse(trimmed))) return trimmed;
|
|
547
584
|
const numeric = Number(trimmed);
|
|
@@ -569,14 +606,20 @@ async function safeStatSize(p) {
|
|
|
569
606
|
}
|
|
570
607
|
}
|
|
571
608
|
|
|
572
|
-
async function maybeSendHeartbeat({
|
|
609
|
+
async function maybeSendHeartbeat({
|
|
610
|
+
baseUrl,
|
|
611
|
+
deviceToken,
|
|
612
|
+
trackerDir,
|
|
613
|
+
uploadResult,
|
|
614
|
+
pendingBytes,
|
|
615
|
+
}) {
|
|
573
616
|
if (!deviceToken || !uploadResult) return;
|
|
574
617
|
if (pendingBytes > 0) return;
|
|
575
618
|
if (Number(uploadResult.inserted || 0) !== 0) return;
|
|
576
619
|
|
|
577
|
-
const heartbeatPath = path.join(trackerDir,
|
|
620
|
+
const heartbeatPath = path.join(trackerDir, "sync.heartbeat.json");
|
|
578
621
|
const heartbeatState = await readJson(heartbeatPath);
|
|
579
|
-
const lastPingAt = Date.parse(heartbeatState?.lastPingAt ||
|
|
622
|
+
const lastPingAt = Date.parse(heartbeatState?.lastPingAt || "");
|
|
580
623
|
const nowMs = Date.now();
|
|
581
624
|
if (Number.isFinite(lastPingAt) && nowMs - lastPingAt < HEARTBEAT_MIN_INTERVAL_MS) return;
|
|
582
625
|
|
|
@@ -584,7 +627,7 @@ async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResu
|
|
|
584
627
|
await syncHeartbeat({ baseUrl, deviceToken });
|
|
585
628
|
await writeJson(heartbeatPath, {
|
|
586
629
|
lastPingAt: new Date(nowMs).toISOString(),
|
|
587
|
-
minIntervalMinutes: HEARTBEAT_MIN_INTERVAL_MINUTES
|
|
630
|
+
minIntervalMinutes: HEARTBEAT_MIN_INTERVAL_MINUTES,
|
|
588
631
|
});
|
|
589
632
|
} catch (_e) {
|
|
590
633
|
// best-effort heartbeat; ignore failures
|
|
@@ -592,14 +635,21 @@ async function maybeSendHeartbeat({ baseUrl, deviceToken, trackerDir, uploadResu
|
|
|
592
635
|
}
|
|
593
636
|
|
|
594
637
|
function deriveAutoSkipReason({ decision, state }) {
|
|
595
|
-
if (!decision || decision.reason !==
|
|
638
|
+
if (!decision || decision.reason !== "throttled") return decision?.reason || "unknown";
|
|
596
639
|
const backoffUntilMs = Number(state?.backoffUntilMs || 0);
|
|
597
640
|
const nextAllowedAtMs = Number(state?.nextAllowedAtMs || 0);
|
|
598
|
-
if (backoffUntilMs > 0 && backoffUntilMs >= nextAllowedAtMs) return
|
|
599
|
-
return
|
|
641
|
+
if (backoffUntilMs > 0 && backoffUntilMs >= nextAllowedAtMs) return "backoff";
|
|
642
|
+
return "throttled";
|
|
600
643
|
}
|
|
601
644
|
|
|
602
|
-
async function scheduleAutoRetry({
|
|
645
|
+
async function scheduleAutoRetry({
|
|
646
|
+
trackerDir,
|
|
647
|
+
retryAtMs,
|
|
648
|
+
reason,
|
|
649
|
+
pendingBytes,
|
|
650
|
+
source,
|
|
651
|
+
autoRetryNoSpawn,
|
|
652
|
+
}) {
|
|
603
653
|
const retryMs = coerceRetryMs(retryAtMs);
|
|
604
654
|
if (!retryMs) return { scheduled: false, retryAtMs: 0 };
|
|
605
655
|
|
|
@@ -615,10 +665,10 @@ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes,
|
|
|
615
665
|
version: 1,
|
|
616
666
|
retryAtMs: retryMs,
|
|
617
667
|
retryAt: new Date(retryMs).toISOString(),
|
|
618
|
-
reason: typeof reason ===
|
|
668
|
+
reason: typeof reason === "string" && reason.length > 0 ? reason : "throttled",
|
|
619
669
|
pendingBytes: Math.max(0, Number(pendingBytes || 0)),
|
|
620
670
|
scheduledAt: new Date(nowMs).toISOString(),
|
|
621
|
-
source: typeof source ===
|
|
671
|
+
source: typeof source === "string" ? source : "auto",
|
|
622
672
|
};
|
|
623
673
|
|
|
624
674
|
await writeJson(retryPath, payload);
|
|
@@ -631,9 +681,9 @@ async function scheduleAutoRetry({ trackerDir, retryAtMs, reason, pendingBytes,
|
|
|
631
681
|
|
|
632
682
|
spawnAutoRetryProcess({
|
|
633
683
|
retryPath,
|
|
634
|
-
trackerBinPath: path.join(trackerDir,
|
|
635
|
-
fallbackPkg:
|
|
636
|
-
delayMs
|
|
684
|
+
trackerBinPath: path.join(trackerDir, "app", "bin", "tracker.js"),
|
|
685
|
+
fallbackPkg: "vibeusage",
|
|
686
|
+
delayMs,
|
|
637
687
|
});
|
|
638
688
|
return { scheduled: true, retryAtMs: retryMs };
|
|
639
689
|
}
|
|
@@ -646,17 +696,18 @@ async function clearAutoRetry(trackerDir) {
|
|
|
646
696
|
function spawnAutoRetryProcess({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
|
|
647
697
|
const script = buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs });
|
|
648
698
|
try {
|
|
649
|
-
const child = cp.spawn(process.execPath, [
|
|
699
|
+
const child = cp.spawn(process.execPath, ["-e", script], {
|
|
650
700
|
detached: true,
|
|
651
|
-
stdio:
|
|
652
|
-
env: process.env
|
|
701
|
+
stdio: "ignore",
|
|
702
|
+
env: process.env,
|
|
653
703
|
});
|
|
654
704
|
child.unref();
|
|
655
705
|
} catch (_e) {}
|
|
656
706
|
}
|
|
657
707
|
|
|
658
708
|
function buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs }) {
|
|
659
|
-
return
|
|
709
|
+
return (
|
|
710
|
+
`'use strict';\n` +
|
|
660
711
|
`const fs = require('node:fs');\n` +
|
|
661
712
|
`const cp = require('node:child_process');\n` +
|
|
662
713
|
`const retryPath = ${JSON.stringify(retryPath)};\n` +
|
|
@@ -678,7 +729,8 @@ function buildAutoRetryScript({ retryPath, trackerBinPath, fallbackPkg, delayMs
|
|
|
678
729
|
` const child = cp.spawn(cmd[0], cmd.slice(1), { detached: true, stdio: 'ignore', env: process.env });\n` +
|
|
679
730
|
` child.unref();\n` +
|
|
680
731
|
` } catch (_) {}\n` +
|
|
681
|
-
`}, delayMs);\n
|
|
732
|
+
`}, delayMs);\n`
|
|
733
|
+
);
|
|
682
734
|
}
|
|
683
735
|
|
|
684
736
|
function coerceRetryMs(v) {
|
|
@@ -688,9 +740,9 @@ function coerceRetryMs(v) {
|
|
|
688
740
|
}
|
|
689
741
|
|
|
690
742
|
async function writeOpenclawSignal(trackerDir) {
|
|
691
|
-
const openclawSignalPath = path.join(trackerDir,
|
|
743
|
+
const openclawSignalPath = path.join(trackerDir, "openclaw.signal");
|
|
692
744
|
try {
|
|
693
|
-
await fs.writeFile(openclawSignalPath, new Date().toISOString(),
|
|
745
|
+
await fs.writeFile(openclawSignalPath, new Date().toISOString(), "utf8");
|
|
694
746
|
} catch (_e) {
|
|
695
747
|
// best-effort marker
|
|
696
748
|
}
|
|
@@ -698,5 +750,5 @@ async function writeOpenclawSignal(trackerDir) {
|
|
|
698
750
|
|
|
699
751
|
const HEARTBEAT_MIN_INTERVAL_MINUTES = 30;
|
|
700
752
|
const HEARTBEAT_MIN_INTERVAL_MS = HEARTBEAT_MIN_INTERVAL_MINUTES * 60 * 1000;
|
|
701
|
-
const AUTO_RETRY_FILENAME =
|
|
753
|
+
const AUTO_RETRY_FILENAME = "auto.retry.json";
|
|
702
754
|
const AUTO_RETRY_MAX_DELAY_MS = 2 * 60 * 60 * 1000;
|