vibeusage 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/README.zh-CN.md +10 -8
- package/node_modules/@insforge/sdk/LICENSE +201 -201
- package/node_modules/@insforge/sdk/README.md +326 -259
- package/node_modules/@insforge/sdk/dist/index.d.mts +377 -182
- package/node_modules/@insforge/sdk/dist/index.d.ts +377 -182
- package/node_modules/@insforge/sdk/dist/index.js +1172 -677
- package/node_modules/@insforge/sdk/dist/index.js.map +1 -1
- package/node_modules/@insforge/sdk/dist/index.mjs +1171 -677
- package/node_modules/@insforge/sdk/dist/index.mjs.map +1 -1
- package/node_modules/@insforge/sdk/package.json +68 -68
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.d.ts +1120 -43
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.js +179 -5
- package/node_modules/@insforge/shared-schemas/dist/ai-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.d.ts +25 -25
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.js +2 -2
- package/node_modules/@insforge/shared-schemas/dist/ai.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.d.ts +197 -51
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.js +87 -23
- package/node_modules/@insforge/shared-schemas/dist/auth-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.d.ts +32 -3
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.js +21 -3
- package/node_modules/@insforge/shared-schemas/dist/auth.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.d.ts +380 -0
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.js +74 -0
- package/node_modules/@insforge/shared-schemas/dist/cloud-events.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/database-api.schema.d.ts +13 -13
- package/node_modules/@insforge/shared-schemas/dist/database-api.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/database-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.d.ts +735 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.js +209 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.d.ts +37 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.js +25 -0
- package/node_modules/@insforge/shared-schemas/dist/deployments.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.d.ts +5 -1
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.js +34 -4
- package/node_modules/@insforge/shared-schemas/dist/docs.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/email-api.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/email-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.d.ts +186 -6
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.js +21 -2
- package/node_modules/@insforge/shared-schemas/dist/functions-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions.schema.d.ts +5 -5
- package/node_modules/@insforge/shared-schemas/dist/functions.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/functions.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/index.d.ts +24 -18
- package/node_modules/@insforge/shared-schemas/dist/index.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/index.js +24 -18
- package/node_modules/@insforge/shared-schemas/dist/index.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs-api.schema.js +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.d.ts +43 -0
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.js +11 -0
- package/node_modules/@insforge/shared-schemas/dist/logs.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.d.ts +229 -172
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.js +27 -7
- package/node_modules/@insforge/shared-schemas/dist/metadata.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.d.ts +51 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.js +31 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.d.ts +31 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.js +12 -0
- package/node_modules/@insforge/shared-schemas/dist/rate-limit.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.d.ts +39 -20
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.js +5 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.d.ts +12 -4
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.js +6 -0
- package/node_modules/@insforge/shared-schemas/dist/realtime.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.d.ts +287 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.js +81 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.d.ts +77 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.js +36 -0
- package/node_modules/@insforge/shared-schemas/dist/schedules.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.d.ts +113 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.js +31 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets-api.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.d.ts +31 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.d.ts.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.js +13 -0
- package/node_modules/@insforge/shared-schemas/dist/secrets.schema.js.map +1 -0
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.d.ts +27 -2
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.js +9 -1
- package/node_modules/@insforge/shared-schemas/dist/storage-api.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.d.ts +17 -0
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.d.ts.map +1 -1
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.js +6 -0
- package/node_modules/@insforge/shared-schemas/dist/storage.schema.js.map +1 -1
- package/node_modules/@insforge/shared-schemas/package.json +2 -1
- package/package.json +2 -2
- package/src/commands/status.js +22 -5
- package/src/commands/sync.js +100 -197
- package/src/commands/uninstall.js +0 -11
- package/src/lib/diagnostics.js +34 -9
- package/src/lib/doctor.js +24 -15
- package/src/lib/insforge-client.js +13 -9
- package/src/lib/integrations/context.js +0 -6
- package/src/lib/integrations/index.js +0 -2
- package/src/lib/openclaw-session-plugin.js +48 -138
- package/src/lib/openclaw-usage-ledger.js +237 -0
- package/src/lib/opencode-sqlite.js +113 -0
- package/src/lib/opencode-usage-audit.js +3 -2
- package/src/lib/rollout.js +229 -153
- package/src/lib/vibeusage-api.js +2 -2
- package/src/lib/integrations/openclaw-legacy.js +0 -123
- package/src/lib/openclaw-hook.js +0 -420
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const crypto = require("node:crypto");
|
|
2
|
+
const fs = require("node:fs/promises");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const { ensureDir, readJson, writeJson } = require("./fs");
|
|
6
|
+
|
|
7
|
+
const OPENCLAW_SOURCE = "openclaw";
|
|
8
|
+
const ALLOWED_EVENT_FIELDS = [
|
|
9
|
+
"eventId",
|
|
10
|
+
"emittedAt",
|
|
11
|
+
"source",
|
|
12
|
+
"agentId",
|
|
13
|
+
"sessionRef",
|
|
14
|
+
"provider",
|
|
15
|
+
"model",
|
|
16
|
+
"channel",
|
|
17
|
+
"chatType",
|
|
18
|
+
"trigger",
|
|
19
|
+
"inputTokens",
|
|
20
|
+
"cachedInputTokens",
|
|
21
|
+
"outputTokens",
|
|
22
|
+
"reasoningOutputTokens",
|
|
23
|
+
"totalTokens",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function resolveOpenclawUsageLedgerPaths({ trackerDir } = {}) {
|
|
27
|
+
if (!trackerDir) throw new Error("trackerDir is required");
|
|
28
|
+
return {
|
|
29
|
+
ledgerPath: path.join(trackerDir, "openclaw-usage-ledger.jsonl"),
|
|
30
|
+
statePath: path.join(trackerDir, "openclaw-usage-ledger.state.json"),
|
|
31
|
+
saltPath: path.join(trackerDir, "openclaw-usage-ledger.salt"),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function buildOpenclawUsageEvent({ trackerDir, payload } = {}) {
|
|
36
|
+
if (!payload || typeof payload !== "object") throw new Error("payload is required");
|
|
37
|
+
|
|
38
|
+
const source = normalizeString(payload.source) || OPENCLAW_SOURCE;
|
|
39
|
+
const emittedAt = normalizeIso(payload.emittedAt) || new Date().toISOString();
|
|
40
|
+
const agentId = normalizeString(payload.agentId);
|
|
41
|
+
const provider = normalizeString(payload.provider);
|
|
42
|
+
const model = normalizeString(payload.model);
|
|
43
|
+
const channel = normalizeString(payload.channel);
|
|
44
|
+
const chatType = normalizeString(payload.chatType);
|
|
45
|
+
const trigger = normalizeString(payload.trigger);
|
|
46
|
+
const sessionKey = normalizeString(payload.sessionKey);
|
|
47
|
+
const existingSessionRef = normalizeHex(payload.sessionRef);
|
|
48
|
+
const sessionRef = sessionKey
|
|
49
|
+
? await hashOpenclawSessionRef({ trackerDir, sessionKey })
|
|
50
|
+
: existingSessionRef;
|
|
51
|
+
|
|
52
|
+
const usage = payload.usage && typeof payload.usage === "object" ? payload.usage : payload;
|
|
53
|
+
const event = {
|
|
54
|
+
eventId: normalizeString(payload.eventId),
|
|
55
|
+
emittedAt,
|
|
56
|
+
source,
|
|
57
|
+
agentId,
|
|
58
|
+
sessionRef,
|
|
59
|
+
provider,
|
|
60
|
+
model,
|
|
61
|
+
channel,
|
|
62
|
+
chatType,
|
|
63
|
+
trigger,
|
|
64
|
+
inputTokens: toNonNegativeInt(usage.inputTokens ?? usage.input_tokens),
|
|
65
|
+
cachedInputTokens: toNonNegativeInt(usage.cachedInputTokens ?? usage.cached_input_tokens),
|
|
66
|
+
outputTokens: toNonNegativeInt(usage.outputTokens ?? usage.output_tokens),
|
|
67
|
+
reasoningOutputTokens: toNonNegativeInt(
|
|
68
|
+
usage.reasoningOutputTokens ?? usage.reasoning_output_tokens,
|
|
69
|
+
),
|
|
70
|
+
totalTokens: toNonNegativeInt(usage.totalTokens ?? usage.total_tokens),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!event.eventId) {
|
|
74
|
+
event.eventId = deriveOpenclawEventId(event);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return stripEmptyAllowedFields(event);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function appendOpenclawUsageEvent({ trackerDir, event, payload } = {}) {
|
|
81
|
+
const nextEvent = event
|
|
82
|
+
? await buildOpenclawUsageEvent({ trackerDir, payload: event })
|
|
83
|
+
: await buildOpenclawUsageEvent({ trackerDir, payload });
|
|
84
|
+
const { ledgerPath, statePath } = resolveOpenclawUsageLedgerPaths({ trackerDir });
|
|
85
|
+
|
|
86
|
+
await ensureDir(path.dirname(ledgerPath));
|
|
87
|
+
|
|
88
|
+
const state = (await readJson(statePath)) || { version: 1, seenEventIds: {}, updatedAt: null };
|
|
89
|
+
if (!state.seenEventIds || typeof state.seenEventIds !== "object") {
|
|
90
|
+
state.seenEventIds = {};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (state.seenEventIds[nextEvent.eventId]) {
|
|
94
|
+
return { appended: false, duplicate: true, event: nextEvent };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await fs.appendFile(ledgerPath, `${JSON.stringify(nextEvent)}\n`, "utf8");
|
|
98
|
+
state.version = 1;
|
|
99
|
+
state.seenEventIds[nextEvent.eventId] = nextEvent.emittedAt || new Date().toISOString();
|
|
100
|
+
state.updatedAt = new Date().toISOString();
|
|
101
|
+
await writeJson(statePath, state);
|
|
102
|
+
|
|
103
|
+
return { appended: true, duplicate: false, event: nextEvent };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function readOpenclawUsageLedger({ trackerDir, offset = 0 } = {}) {
|
|
107
|
+
const { ledgerPath } = resolveOpenclawUsageLedgerPaths({ trackerDir });
|
|
108
|
+
const buffer = await fs.readFile(ledgerPath).catch((err) => {
|
|
109
|
+
if (err && err.code === "ENOENT") return null;
|
|
110
|
+
throw err;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!buffer) {
|
|
114
|
+
return { events: [], endOffset: 0 };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const startOffset = Math.max(0, Number(offset || 0));
|
|
118
|
+
const endOffset = buffer.length;
|
|
119
|
+
if (startOffset >= endOffset) {
|
|
120
|
+
return { events: [], endOffset };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const raw = buffer.subarray(startOffset).toString("utf8");
|
|
124
|
+
const events = raw
|
|
125
|
+
.split(/\r?\n/)
|
|
126
|
+
.map((line) => line.trim())
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.map((line) => {
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(line);
|
|
131
|
+
} catch (_err) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.map(stripToAllowedEventFields);
|
|
137
|
+
|
|
138
|
+
return { events, endOffset };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function hashOpenclawSessionRef({ trackerDir, sessionKey } = {}) {
|
|
142
|
+
const normalized = normalizeString(sessionKey);
|
|
143
|
+
if (!normalized) return null;
|
|
144
|
+
|
|
145
|
+
const salt = await ensureOpenclawLedgerSalt({ trackerDir });
|
|
146
|
+
return crypto.createHmac("sha256", salt).update(normalized).digest("hex");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function ensureOpenclawLedgerSalt({ trackerDir } = {}) {
|
|
150
|
+
const { saltPath } = resolveOpenclawUsageLedgerPaths({ trackerDir });
|
|
151
|
+
await ensureDir(path.dirname(saltPath));
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const existing = (await fs.readFile(saltPath, "utf8")).trim();
|
|
155
|
+
if (existing) return existing;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err?.code !== "ENOENT") throw err;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const salt = crypto.randomBytes(32).toString("hex");
|
|
161
|
+
await fs.writeFile(saltPath, `${salt}\n`, { encoding: "utf8", mode: 0o600 });
|
|
162
|
+
return salt;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function deriveOpenclawEventId(event) {
|
|
166
|
+
const stable = JSON.stringify({
|
|
167
|
+
emittedAt: normalizeIso(event.emittedAt),
|
|
168
|
+
source: normalizeString(event.source) || OPENCLAW_SOURCE,
|
|
169
|
+
agentId: normalizeString(event.agentId),
|
|
170
|
+
sessionRef: normalizeHex(event.sessionRef),
|
|
171
|
+
provider: normalizeString(event.provider),
|
|
172
|
+
model: normalizeString(event.model),
|
|
173
|
+
channel: normalizeString(event.channel),
|
|
174
|
+
chatType: normalizeString(event.chatType),
|
|
175
|
+
trigger: normalizeString(event.trigger),
|
|
176
|
+
inputTokens: toNonNegativeInt(event.inputTokens),
|
|
177
|
+
cachedInputTokens: toNonNegativeInt(event.cachedInputTokens),
|
|
178
|
+
outputTokens: toNonNegativeInt(event.outputTokens),
|
|
179
|
+
reasoningOutputTokens: toNonNegativeInt(event.reasoningOutputTokens),
|
|
180
|
+
totalTokens: toNonNegativeInt(event.totalTokens),
|
|
181
|
+
});
|
|
182
|
+
return crypto.createHash("sha256").update(stable).digest("hex");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function stripEmptyAllowedFields(event) {
|
|
186
|
+
const out = {};
|
|
187
|
+
for (const field of ALLOWED_EVENT_FIELDS) {
|
|
188
|
+
const value = event[field];
|
|
189
|
+
if (value == null) continue;
|
|
190
|
+
out[field] = value;
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function stripToAllowedEventFields(event) {
|
|
196
|
+
const out = {};
|
|
197
|
+
for (const field of ALLOWED_EVENT_FIELDS) {
|
|
198
|
+
if (!Object.prototype.hasOwnProperty.call(event, field)) continue;
|
|
199
|
+
out[field] = event[field];
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function normalizeString(value) {
|
|
205
|
+
if (typeof value !== "string") return null;
|
|
206
|
+
const trimmed = value.trim();
|
|
207
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeIso(value) {
|
|
211
|
+
const normalized = normalizeString(value);
|
|
212
|
+
if (!normalized) return null;
|
|
213
|
+
const time = Date.parse(normalized);
|
|
214
|
+
if (!Number.isFinite(time)) return null;
|
|
215
|
+
return new Date(time).toISOString();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function normalizeHex(value) {
|
|
219
|
+
const normalized = normalizeString(value);
|
|
220
|
+
if (!normalized) return null;
|
|
221
|
+
return /^[a-f0-9]{64}$/i.test(normalized) ? normalized.toLowerCase() : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function toNonNegativeInt(value) {
|
|
225
|
+
const numeric = Number(value || 0);
|
|
226
|
+
if (!Number.isFinite(numeric) || numeric < 0) return 0;
|
|
227
|
+
return Math.floor(numeric);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
ALLOWED_EVENT_FIELDS,
|
|
232
|
+
buildOpenclawUsageEvent,
|
|
233
|
+
appendOpenclawUsageEvent,
|
|
234
|
+
readOpenclawUsageLedger,
|
|
235
|
+
hashOpenclawSessionRef,
|
|
236
|
+
resolveOpenclawUsageLedgerPaths,
|
|
237
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const fs = require("node:fs/promises");
|
|
2
|
+
const cp = require("node:child_process");
|
|
3
|
+
const { promisify } = require("node:util");
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(cp.execFile);
|
|
6
|
+
|
|
7
|
+
function buildMessageQuery(lastTimeCreated) {
|
|
8
|
+
const since = Number.isFinite(lastTimeCreated) ? Math.max(0, Math.trunc(lastTimeCreated)) : 0;
|
|
9
|
+
return [
|
|
10
|
+
"SELECT json_object(",
|
|
11
|
+
"'id', m.id,",
|
|
12
|
+
"'session_id', m.session_id,",
|
|
13
|
+
"'time_created', m.time_created,",
|
|
14
|
+
"'role', json_extract(m.data, '$.role'),",
|
|
15
|
+
"'project_worktree', p.worktree,",
|
|
16
|
+
"'data', m.data",
|
|
17
|
+
")",
|
|
18
|
+
"FROM message m",
|
|
19
|
+
"LEFT JOIN session s ON s.id = m.session_id",
|
|
20
|
+
"LEFT JOIN project p ON p.id = s.project_id",
|
|
21
|
+
`WHERE m.time_created >= ${since}`,
|
|
22
|
+
"ORDER BY m.time_created ASC;",
|
|
23
|
+
].join(" ");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseMessageRows(stdout) {
|
|
27
|
+
const lines = String(stdout || "")
|
|
28
|
+
.split(/\r?\n/)
|
|
29
|
+
.map((line) => line.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(line);
|
|
36
|
+
if (!parsed || typeof parsed !== "object") continue;
|
|
37
|
+
rows.push(parsed);
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
}
|
|
40
|
+
return rows;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeErrorCode(error) {
|
|
44
|
+
return typeof error?.code === "string" && error.code.trim() ? error.code.trim() : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function readOpencodeSqliteRows({
|
|
48
|
+
dbPath,
|
|
49
|
+
lastTimeCreated = 0,
|
|
50
|
+
expectedInode = 0,
|
|
51
|
+
statFn = fs.stat,
|
|
52
|
+
execFileFn = execFileAsync,
|
|
53
|
+
}) {
|
|
54
|
+
const checkedAt = new Date().toISOString();
|
|
55
|
+
const st = await statFn(dbPath).catch(() => null);
|
|
56
|
+
if (!st || !st.isFile()) {
|
|
57
|
+
return {
|
|
58
|
+
status: "missing-db",
|
|
59
|
+
checkedAt,
|
|
60
|
+
rows: [],
|
|
61
|
+
inode: 0,
|
|
62
|
+
cursorReset: false,
|
|
63
|
+
errorCode: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const inode = Number.isFinite(st.ino) ? st.ino : 0;
|
|
69
|
+
const effectiveLastTimeCreated =
|
|
70
|
+
expectedInode && inode && expectedInode !== inode ? 0 : lastTimeCreated;
|
|
71
|
+
const { stdout } = await execFileFn(
|
|
72
|
+
"sqlite3",
|
|
73
|
+
["-readonly", dbPath, buildMessageQuery(effectiveLastTimeCreated)],
|
|
74
|
+
{ maxBuffer: 64 * 1024 * 1024 },
|
|
75
|
+
);
|
|
76
|
+
return {
|
|
77
|
+
status: "ok",
|
|
78
|
+
checkedAt,
|
|
79
|
+
rows: parseMessageRows(stdout),
|
|
80
|
+
inode,
|
|
81
|
+
cursorReset: Boolean(expectedInode && inode && expectedInode !== inode),
|
|
82
|
+
errorCode: null,
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const errorCode = normalizeErrorCode(error);
|
|
86
|
+
if (error && error.code === "ENOENT") {
|
|
87
|
+
return {
|
|
88
|
+
status: "missing-sqlite3",
|
|
89
|
+
checkedAt,
|
|
90
|
+
rows: [],
|
|
91
|
+
inode: Number.isFinite(st.ino) ? st.ino : 0,
|
|
92
|
+
cursorReset: false,
|
|
93
|
+
error,
|
|
94
|
+
errorCode,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
status: "query-failed",
|
|
99
|
+
checkedAt,
|
|
100
|
+
rows: [],
|
|
101
|
+
inode: Number.isFinite(st.ino) ? st.ino : 0,
|
|
102
|
+
cursorReset: false,
|
|
103
|
+
error,
|
|
104
|
+
errorCode,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
buildMessageQuery,
|
|
111
|
+
parseMessageRows,
|
|
112
|
+
readOpencodeSqliteRows,
|
|
113
|
+
};
|
|
@@ -32,13 +32,14 @@ function addTotals(target, delta) {
|
|
|
32
32
|
|
|
33
33
|
async function buildLocalHourlyTotals({ storageDir, source = "opencode" }) {
|
|
34
34
|
const messageFiles = await listOpencodeMessageFiles(storageDir);
|
|
35
|
+
const opencodeDbPath = path.resolve(storageDir, "..", "opencode.db");
|
|
35
36
|
const queuePath = path.join(
|
|
36
37
|
os.tmpdir(),
|
|
37
38
|
`vibeusage-opencode-audit-${process.pid}-${Date.now()}.jsonl`,
|
|
38
39
|
);
|
|
39
|
-
const cursors = { version: 1, files: {}, hourly: null, opencode: null };
|
|
40
|
+
const cursors = { version: 1, files: {}, hourly: null, opencode: null, opencodeSqlite: null };
|
|
40
41
|
|
|
41
|
-
await parseOpencodeIncremental({ messageFiles, cursors, queuePath, source });
|
|
42
|
+
await parseOpencodeIncremental({ messageFiles, opencodeDbPath, cursors, queuePath, source });
|
|
42
43
|
await fs.rm(queuePath, { force: true }).catch(() => {});
|
|
43
44
|
|
|
44
45
|
const byHour = new Map();
|