@vaultcompass/vault-guard-telemetry 1.0.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/LICENSE +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/store.d.ts +236 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +514 -0
- package/dist/store.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vault & Compass LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { TelemetryStore, TelemetryUnavailableError, getDefaultDbPath, getDbSidecarPaths, getOrCreateTelemetrySalt, getTelemetryRetentionDays, type UsageRecordInput, type SessionRecordInput, type StatuslineJson, type ModelSuggestion, type DataStatusJson, type UsageEventRow, type SessionEventRow, } from './store';
|
|
2
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,yBAAyB,EACzB,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,yBAAyB,EACzB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTelemetryRetentionDays = exports.getOrCreateTelemetrySalt = exports.getDbSidecarPaths = exports.getDefaultDbPath = exports.TelemetryUnavailableError = exports.TelemetryStore = void 0;
|
|
4
|
+
var store_1 = require("./store");
|
|
5
|
+
Object.defineProperty(exports, "TelemetryStore", { enumerable: true, get: function () { return store_1.TelemetryStore; } });
|
|
6
|
+
Object.defineProperty(exports, "TelemetryUnavailableError", { enumerable: true, get: function () { return store_1.TelemetryUnavailableError; } });
|
|
7
|
+
Object.defineProperty(exports, "getDefaultDbPath", { enumerable: true, get: function () { return store_1.getDefaultDbPath; } });
|
|
8
|
+
Object.defineProperty(exports, "getDbSidecarPaths", { enumerable: true, get: function () { return store_1.getDbSidecarPaths; } });
|
|
9
|
+
Object.defineProperty(exports, "getOrCreateTelemetrySalt", { enumerable: true, get: function () { return store_1.getOrCreateTelemetrySalt; } });
|
|
10
|
+
Object.defineProperty(exports, "getTelemetryRetentionDays", { enumerable: true, get: function () { return store_1.getTelemetryRetentionDays; } });
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iCAciB;AAbf,uGAAA,cAAc,OAAA;AACd,kHAAA,yBAAyB,OAAA;AACzB,yGAAA,gBAAgB,OAAA;AAChB,0GAAA,iBAAiB,OAAA;AACjB,iHAAA,wBAAwB,OAAA;AACxB,kHAAA,yBAAyB,OAAA"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when `better-sqlite3` native bindings are missing or incompatible.
|
|
3
|
+
*
|
|
4
|
+
* This happens when:
|
|
5
|
+
* - The package was installed with `--ignore-scripts` (skips node-gyp compile)
|
|
6
|
+
* - The Node.js ABI changed after install (e.g. nvm version switch)
|
|
7
|
+
* - The pre-built binary is missing for the current platform/arch
|
|
8
|
+
*
|
|
9
|
+
* Callers that don't strictly need telemetry should catch this and degrade
|
|
10
|
+
* gracefully (e.g. `statusline` and `suggest-model`). The `proxy` command
|
|
11
|
+
* intentionally lets this propagate — it is the primary telemetry writer and
|
|
12
|
+
* should fail loudly rather than silently discard usage data.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* try {
|
|
17
|
+
* const store = new TelemetryStore();
|
|
18
|
+
* const payload = store.getStatuslinePayload();
|
|
19
|
+
* console.log(payload);
|
|
20
|
+
* } catch (err) {
|
|
21
|
+
* if (err instanceof TelemetryUnavailableError) {
|
|
22
|
+
* console.error('Telemetry unavailable:', err.message);
|
|
23
|
+
* } else {
|
|
24
|
+
* throw err;
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare class TelemetryUnavailableError extends Error {
|
|
30
|
+
constructor(cause: unknown);
|
|
31
|
+
}
|
|
32
|
+
export interface UsageRecordInput {
|
|
33
|
+
createdAt?: Date;
|
|
34
|
+
provider?: 'anthropic' | 'openai' | 'unknown';
|
|
35
|
+
model?: string | null;
|
|
36
|
+
cwd?: string | null;
|
|
37
|
+
inputTokens: number;
|
|
38
|
+
outputTokens: number;
|
|
39
|
+
estCostUsd?: number;
|
|
40
|
+
source?: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface SessionRecordInput {
|
|
43
|
+
createdAt?: Date;
|
|
44
|
+
eventType: string;
|
|
45
|
+
model?: string | null;
|
|
46
|
+
cwd?: string | null;
|
|
47
|
+
language?: string | null;
|
|
48
|
+
linesAccepted?: number;
|
|
49
|
+
linesSuggested?: number;
|
|
50
|
+
linesReverted?: number;
|
|
51
|
+
extra?: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
export interface StatuslineJson {
|
|
54
|
+
secrets_today: number;
|
|
55
|
+
tokens_today_input: number;
|
|
56
|
+
tokens_today_output: number;
|
|
57
|
+
est_cost_usd: number;
|
|
58
|
+
model: string | null;
|
|
59
|
+
window_start_utc: string;
|
|
60
|
+
}
|
|
61
|
+
export interface ModelSuggestion {
|
|
62
|
+
suggested_model: string | null;
|
|
63
|
+
reason: string;
|
|
64
|
+
by_model: Array<{
|
|
65
|
+
model: string;
|
|
66
|
+
usage_events: number;
|
|
67
|
+
revert_rate: number | null;
|
|
68
|
+
avg_cost_usd: number;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Per-machine random salt (32 bytes, hex-encoded on disk, mode `0600`).
|
|
73
|
+
* Used only to hash `cwd` before persistence — not a secret, but treated as
|
|
74
|
+
* private local state alongside `usage.sqlite`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getOrCreateTelemetrySalt(): Buffer;
|
|
77
|
+
/**
|
|
78
|
+
* Default **90** days. Set `VG_TELEMETRY_RETENTION_DAYS=0` to disable automatic
|
|
79
|
+
* deletion (database still grows unbounded).
|
|
80
|
+
*/
|
|
81
|
+
export declare function getTelemetryRetentionDays(): number;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the default `~/.vault-guard/usage.sqlite` path without instantiating
|
|
84
|
+
* a {@link TelemetryStore}. Useful for `vault-guard data reset` which needs
|
|
85
|
+
* the path even when telemetry native bindings are unavailable.
|
|
86
|
+
*/
|
|
87
|
+
export declare function getDefaultDbPath(): string;
|
|
88
|
+
/**
|
|
89
|
+
* SQLite WAL mode produces two sidecar files alongside the main DB. They must
|
|
90
|
+
* be deleted as a set during `vault-guard data reset`, otherwise the next
|
|
91
|
+
* reader recovers stale rows from the WAL.
|
|
92
|
+
*/
|
|
93
|
+
export declare function getDbSidecarPaths(dbPath: string): readonly string[];
|
|
94
|
+
/**
|
|
95
|
+
* Raw row shape for the `usage_events` table. Returned by
|
|
96
|
+
* {@link TelemetryStore.exportUsageEvents}. The `cwd` column stores a
|
|
97
|
+
* **64-char hex HMAC-SHA256** of the original path (see `docs/PRIVACY.md`), not
|
|
98
|
+
* the plaintext working directory.
|
|
99
|
+
*/
|
|
100
|
+
export interface UsageEventRow {
|
|
101
|
+
id: number;
|
|
102
|
+
created_at: string;
|
|
103
|
+
provider: string | null;
|
|
104
|
+
model: string | null;
|
|
105
|
+
cwd: string | null;
|
|
106
|
+
input_tokens: number;
|
|
107
|
+
output_tokens: number;
|
|
108
|
+
est_cost_usd: number;
|
|
109
|
+
source: string | null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Raw row shape for the `session_events` table. Returned by
|
|
113
|
+
* {@link TelemetryStore.exportSessionEvents}. The `cwd` column is an HMAC digest
|
|
114
|
+
* (same scheme as {@link UsageEventRow}); `extra_json` is stored as provided.
|
|
115
|
+
*/
|
|
116
|
+
export interface SessionEventRow {
|
|
117
|
+
id: number;
|
|
118
|
+
created_at: string;
|
|
119
|
+
event_type: string;
|
|
120
|
+
model: string | null;
|
|
121
|
+
cwd: string | null;
|
|
122
|
+
language: string | null;
|
|
123
|
+
lines_accepted: number | null;
|
|
124
|
+
lines_suggested: number | null;
|
|
125
|
+
lines_reverted: number | null;
|
|
126
|
+
extra_json: string | null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Privacy-respecting summary of `~/.vault-guard/usage.sqlite`.
|
|
130
|
+
*
|
|
131
|
+
* **What this intentionally does NOT expose:** raw `cwd` strings (which
|
|
132
|
+
* include OS username and project names — see `docs/PRIVACY.md`),
|
|
133
|
+
* `extra_json` payloads, model prompts, or token totals (those are already
|
|
134
|
+
* available via `vault-guard statusline --json`).
|
|
135
|
+
*
|
|
136
|
+
* Only counts and aggregates land here, so the JSON output is safe to paste
|
|
137
|
+
* into a support ticket or screenshot.
|
|
138
|
+
*/
|
|
139
|
+
export interface DataStatusJson {
|
|
140
|
+
/** Absolute path to the SQLite database file. */
|
|
141
|
+
db_path: string;
|
|
142
|
+
/** True if the database file exists on disk. */
|
|
143
|
+
db_exists: boolean;
|
|
144
|
+
/** File size in bytes (0 when `db_exists` is false). */
|
|
145
|
+
db_size_bytes: number;
|
|
146
|
+
/** Last write time of the database file as ISO timestamp, or null. */
|
|
147
|
+
last_write_iso: string | null;
|
|
148
|
+
/** Sidecar files (WAL, SHM, journal) that exist alongside the main DB. */
|
|
149
|
+
sidecars: Array<{
|
|
150
|
+
path: string;
|
|
151
|
+
size_bytes: number;
|
|
152
|
+
}>;
|
|
153
|
+
/** Row count in `usage_events`. */
|
|
154
|
+
usage_events: number;
|
|
155
|
+
/** Row count in `session_events`. */
|
|
156
|
+
session_events: number;
|
|
157
|
+
/** Earliest `created_at` across both tables (ISO), or null when empty. */
|
|
158
|
+
earliest_event_iso: string | null;
|
|
159
|
+
/** Latest `created_at` across both tables (ISO), or null when empty. */
|
|
160
|
+
latest_event_iso: string | null;
|
|
161
|
+
/** Distinct non-null `cwd` values across both tables (count only — never the values). */
|
|
162
|
+
distinct_cwd_count: number;
|
|
163
|
+
/** Distinct non-null `model` values across both tables (count only). */
|
|
164
|
+
distinct_model_count: number;
|
|
165
|
+
/** Most recently observed model name, or null when no usage rows exist. */
|
|
166
|
+
last_model: string | null;
|
|
167
|
+
}
|
|
168
|
+
export declare class TelemetryStore {
|
|
169
|
+
private readonly db;
|
|
170
|
+
private readonly counter;
|
|
171
|
+
private readonly saltBuf;
|
|
172
|
+
private lastRetentionPurgeMs;
|
|
173
|
+
constructor(dbPath?: string);
|
|
174
|
+
/**
|
|
175
|
+
* One-time migration (pragma `user_version` < 2): replace plaintext `cwd`
|
|
176
|
+
* values with HMAC-SHA256 hex digests using the current salt file.
|
|
177
|
+
*/
|
|
178
|
+
private applyTelemetryMigrations;
|
|
179
|
+
/**
|
|
180
|
+
* Delete rows older than {@link getTelemetryRetentionDays}. Throttled to at
|
|
181
|
+
* most once per hour per process to avoid hammering SQLite on hot paths.
|
|
182
|
+
*/
|
|
183
|
+
private maybePurgeStaleRows;
|
|
184
|
+
private initSchema;
|
|
185
|
+
close(): void;
|
|
186
|
+
/**
|
|
187
|
+
* Force a WAL checkpoint (TRUNCATE) and close the database.
|
|
188
|
+
*
|
|
189
|
+
* Why: in WAL mode the main DB file lags behind the `-wal` sidecar until a
|
|
190
|
+
* checkpoint runs. If the process is killed (SIGINT/SIGTERM during proxy
|
|
191
|
+
* shutdown) without checkpointing, recent rows live only in the WAL and
|
|
192
|
+
* survive only as long as that file does. TRUNCATE both checkpoints and
|
|
193
|
+
* shrinks the WAL to zero so the next reader sees an up-to-date main file
|
|
194
|
+
* with no recovery surprises.
|
|
195
|
+
*
|
|
196
|
+
* Best-effort: if the pragma fails (e.g. handle already closed by another
|
|
197
|
+
* shutdown path) we still attempt to close the underlying handle.
|
|
198
|
+
*/
|
|
199
|
+
closeAndCheckpoint(): void;
|
|
200
|
+
recordUsage(input: UsageRecordInput): void;
|
|
201
|
+
recordSession(input: SessionRecordInput): void;
|
|
202
|
+
/** Count session events that represent blocked secrets today (UTC date). */
|
|
203
|
+
secretsBlockedToday(day?: string): number;
|
|
204
|
+
getStatuslinePayload(now?: Date): StatuslineJson;
|
|
205
|
+
/**
|
|
206
|
+
* Heuristic model hint from the last 7 days of session + usage data.
|
|
207
|
+
* Prefer models with more usage and lower revert_rate when session metrics exist.
|
|
208
|
+
*/
|
|
209
|
+
suggestModel(opts?: {
|
|
210
|
+
cwd?: string;
|
|
211
|
+
language?: string;
|
|
212
|
+
}): ModelSuggestion;
|
|
213
|
+
/**
|
|
214
|
+
* Read all rows from `usage_events` ordered by `id ASC`.
|
|
215
|
+
*
|
|
216
|
+
* Intended for `vault-guard data export`. Returns raw `cwd` strings — see
|
|
217
|
+
* {@link DataStatusJson} for the privacy-respecting alternative.
|
|
218
|
+
*/
|
|
219
|
+
exportUsageEvents(): UsageEventRow[];
|
|
220
|
+
/**
|
|
221
|
+
* Read all rows from `session_events` ordered by `id ASC`.
|
|
222
|
+
*
|
|
223
|
+
* Intended for `vault-guard data export`. Returns raw `cwd` strings and
|
|
224
|
+
* the `extra_json` payload as stored.
|
|
225
|
+
*/
|
|
226
|
+
exportSessionEvents(): SessionEventRow[];
|
|
227
|
+
/**
|
|
228
|
+
* Compute a privacy-respecting summary of the local telemetry database
|
|
229
|
+
* (file location, size, row counts, distinct-value *counts*). Never
|
|
230
|
+
* returns raw `cwd` strings or `extra_json`.
|
|
231
|
+
*
|
|
232
|
+
* Used by `vault-guard data status`. Safe to paste into a support ticket.
|
|
233
|
+
*/
|
|
234
|
+
getDataStatus(dbFilePath: string): DataStatusJson;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TelemetryStore = exports.TelemetryUnavailableError = void 0;
|
|
7
|
+
exports.getOrCreateTelemetrySalt = getOrCreateTelemetrySalt;
|
|
8
|
+
exports.getTelemetryRetentionDays = getTelemetryRetentionDays;
|
|
9
|
+
exports.getDefaultDbPath = getDefaultDbPath;
|
|
10
|
+
exports.getDbSidecarPaths = getDbSidecarPaths;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const module_1 = require("module");
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const vault_guard_core_1 = require("@vaultcompass/vault-guard-core");
|
|
17
|
+
// Lazy native-binding loader uses createRequire so that:
|
|
18
|
+
// (a) the native binding is not loaded at module import time (saves startup
|
|
19
|
+
// cost and allows graceful degradation on platforms without pre-built binaries)
|
|
20
|
+
// (b) @typescript-eslint/no-require-imports is not violated
|
|
21
|
+
const _require = (0, module_1.createRequire)(__filename);
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// TelemetryUnavailableError
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Thrown when `better-sqlite3` native bindings are missing or incompatible.
|
|
27
|
+
*
|
|
28
|
+
* This happens when:
|
|
29
|
+
* - The package was installed with `--ignore-scripts` (skips node-gyp compile)
|
|
30
|
+
* - The Node.js ABI changed after install (e.g. nvm version switch)
|
|
31
|
+
* - The pre-built binary is missing for the current platform/arch
|
|
32
|
+
*
|
|
33
|
+
* Callers that don't strictly need telemetry should catch this and degrade
|
|
34
|
+
* gracefully (e.g. `statusline` and `suggest-model`). The `proxy` command
|
|
35
|
+
* intentionally lets this propagate — it is the primary telemetry writer and
|
|
36
|
+
* should fail loudly rather than silently discard usage data.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* try {
|
|
41
|
+
* const store = new TelemetryStore();
|
|
42
|
+
* const payload = store.getStatuslinePayload();
|
|
43
|
+
* console.log(payload);
|
|
44
|
+
* } catch (err) {
|
|
45
|
+
* if (err instanceof TelemetryUnavailableError) {
|
|
46
|
+
* console.error('Telemetry unavailable:', err.message);
|
|
47
|
+
* } else {
|
|
48
|
+
* throw err;
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class TelemetryUnavailableError extends Error {
|
|
54
|
+
constructor(cause) {
|
|
55
|
+
const msg = `better-sqlite3 native bindings could not be loaded.\n` +
|
|
56
|
+
`Re-install to rebuild them: npm install -g @vaultcompass/vault-guard\n` +
|
|
57
|
+
`Underlying error: ${String(cause)}`;
|
|
58
|
+
super(msg);
|
|
59
|
+
this.name = 'TelemetryUnavailableError';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.TelemetryUnavailableError = TelemetryUnavailableError;
|
|
63
|
+
let _DbClass = null;
|
|
64
|
+
function getDbClass() {
|
|
65
|
+
if (_DbClass)
|
|
66
|
+
return _DbClass;
|
|
67
|
+
try {
|
|
68
|
+
// Dynamic import to defer native binding load until first use.
|
|
69
|
+
// We need synchronous access here (TelemetryStore constructor is sync),
|
|
70
|
+
// so we use createRequire from the module system rather than top-level await.
|
|
71
|
+
const mod = _require('better-sqlite3');
|
|
72
|
+
_DbClass = mod;
|
|
73
|
+
return _DbClass;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
throw new TelemetryUnavailableError(err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function defaultDbPath() {
|
|
80
|
+
const dir = path_1.default.join(os_1.default.homedir(), '.vault-guard');
|
|
81
|
+
return path_1.default.join(dir, 'usage.sqlite');
|
|
82
|
+
}
|
|
83
|
+
/** 64-char lowercase hex = SHA-256 digest written to the `cwd` column. */
|
|
84
|
+
const HASHED_CWD_RE = /^[a-f0-9]{64}$/;
|
|
85
|
+
function telemetrySaltPath() {
|
|
86
|
+
return path_1.default.join(os_1.default.homedir(), '.vault-guard', 'salt');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Per-machine random salt (32 bytes, hex-encoded on disk, mode `0600`).
|
|
90
|
+
* Used only to hash `cwd` before persistence — not a secret, but treated as
|
|
91
|
+
* private local state alongside `usage.sqlite`.
|
|
92
|
+
*/
|
|
93
|
+
function getOrCreateTelemetrySalt() {
|
|
94
|
+
const p = telemetrySaltPath();
|
|
95
|
+
fs_1.default.mkdirSync(path_1.default.dirname(p), { recursive: true });
|
|
96
|
+
if (fs_1.default.existsSync(p)) {
|
|
97
|
+
const hex = fs_1.default.readFileSync(p, 'utf8').trim();
|
|
98
|
+
if (/^[a-f0-9]{64}$/i.test(hex)) {
|
|
99
|
+
return Buffer.from(hex, 'hex');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const b = (0, crypto_1.randomBytes)(32);
|
|
103
|
+
fs_1.default.writeFileSync(p, b.toString('hex'), { mode: 0o600 });
|
|
104
|
+
return b;
|
|
105
|
+
}
|
|
106
|
+
function hashCwdForStore(plain, salt) {
|
|
107
|
+
if (plain == null || plain === '')
|
|
108
|
+
return null;
|
|
109
|
+
return (0, crypto_1.createHmac)('sha256', salt).update(plain, 'utf8').digest('hex');
|
|
110
|
+
}
|
|
111
|
+
function isStoredCwdDigest(v) {
|
|
112
|
+
return v != null && v !== '' && HASHED_CWD_RE.test(v);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Default **90** days. Set `VG_TELEMETRY_RETENTION_DAYS=0` to disable automatic
|
|
116
|
+
* deletion (database still grows unbounded).
|
|
117
|
+
*/
|
|
118
|
+
function getTelemetryRetentionDays() {
|
|
119
|
+
const raw = process.env.VG_TELEMETRY_RETENTION_DAYS;
|
|
120
|
+
if (raw === undefined || raw === '')
|
|
121
|
+
return 90;
|
|
122
|
+
const n = Number.parseInt(raw, 10);
|
|
123
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
124
|
+
return 0;
|
|
125
|
+
return Math.min(n, 3650);
|
|
126
|
+
}
|
|
127
|
+
const MIN_MS_BETWEEN_RETENTION_PURGES = 3600 * 1000;
|
|
128
|
+
function retentionThrottleMs() {
|
|
129
|
+
if (process.env.VG_TELEMETRY_RETENTION_TEST_NO_THROTTLE === '1')
|
|
130
|
+
return 0;
|
|
131
|
+
return MIN_MS_BETWEEN_RETENTION_PURGES;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve the default `~/.vault-guard/usage.sqlite` path without instantiating
|
|
135
|
+
* a {@link TelemetryStore}. Useful for `vault-guard data reset` which needs
|
|
136
|
+
* the path even when telemetry native bindings are unavailable.
|
|
137
|
+
*/
|
|
138
|
+
function getDefaultDbPath() {
|
|
139
|
+
return defaultDbPath();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* SQLite WAL mode produces two sidecar files alongside the main DB. They must
|
|
143
|
+
* be deleted as a set during `vault-guard data reset`, otherwise the next
|
|
144
|
+
* reader recovers stale rows from the WAL.
|
|
145
|
+
*/
|
|
146
|
+
function getDbSidecarPaths(dbPath) {
|
|
147
|
+
return [`${dbPath}-wal`, `${dbPath}-shm`, `${dbPath}-journal`];
|
|
148
|
+
}
|
|
149
|
+
function utcDayStart(d = new Date()) {
|
|
150
|
+
return d.toISOString().slice(0, 10);
|
|
151
|
+
}
|
|
152
|
+
class TelemetryStore {
|
|
153
|
+
db;
|
|
154
|
+
counter = new vault_guard_core_1.TokenCounter();
|
|
155
|
+
saltBuf;
|
|
156
|
+
lastRetentionPurgeMs = 0;
|
|
157
|
+
constructor(dbPath) {
|
|
158
|
+
const resolved = dbPath ?? defaultDbPath();
|
|
159
|
+
const dir = path_1.default.dirname(resolved);
|
|
160
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
161
|
+
this.saltBuf = getOrCreateTelemetrySalt();
|
|
162
|
+
// getDbClass() throws TelemetryUnavailableError if bindings missing.
|
|
163
|
+
this.db = new (getDbClass())(resolved);
|
|
164
|
+
this.db.pragma('journal_mode = WAL');
|
|
165
|
+
this.initSchema();
|
|
166
|
+
this.applyTelemetryMigrations();
|
|
167
|
+
this.maybePurgeStaleRows();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* One-time migration (pragma `user_version` < 2): replace plaintext `cwd`
|
|
171
|
+
* values with HMAC-SHA256 hex digests using the current salt file.
|
|
172
|
+
*/
|
|
173
|
+
applyTelemetryMigrations() {
|
|
174
|
+
const ver = Number(this.db.pragma('user_version', { simple: true }));
|
|
175
|
+
if (ver >= 2)
|
|
176
|
+
return;
|
|
177
|
+
const salt = this.saltBuf;
|
|
178
|
+
const uRows = this.db
|
|
179
|
+
.prepare(`SELECT id, cwd FROM usage_events WHERE cwd IS NOT NULL AND cwd != ''`)
|
|
180
|
+
.all();
|
|
181
|
+
const uUpd = this.db.prepare(`UPDATE usage_events SET cwd = ? WHERE id = ?`);
|
|
182
|
+
for (const r of uRows) {
|
|
183
|
+
if (isStoredCwdDigest(r.cwd))
|
|
184
|
+
continue;
|
|
185
|
+
uUpd.run(hashCwdForStore(r.cwd, salt), r.id);
|
|
186
|
+
}
|
|
187
|
+
const sRows = this.db
|
|
188
|
+
.prepare(`SELECT id, cwd FROM session_events WHERE cwd IS NOT NULL AND cwd != ''`)
|
|
189
|
+
.all();
|
|
190
|
+
const sUpd = this.db.prepare(`UPDATE session_events SET cwd = ? WHERE id = ?`);
|
|
191
|
+
for (const r of sRows) {
|
|
192
|
+
if (isStoredCwdDigest(r.cwd))
|
|
193
|
+
continue;
|
|
194
|
+
sUpd.run(hashCwdForStore(r.cwd, salt), r.id);
|
|
195
|
+
}
|
|
196
|
+
this.db.pragma('user_version = 2');
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Delete rows older than {@link getTelemetryRetentionDays}. Throttled to at
|
|
200
|
+
* most once per hour per process to avoid hammering SQLite on hot paths.
|
|
201
|
+
*/
|
|
202
|
+
maybePurgeStaleRows() {
|
|
203
|
+
const days = getTelemetryRetentionDays();
|
|
204
|
+
if (days <= 0)
|
|
205
|
+
return;
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
if (now - this.lastRetentionPurgeMs < retentionThrottleMs())
|
|
208
|
+
return;
|
|
209
|
+
this.lastRetentionPurgeMs = now;
|
|
210
|
+
const cutoff = new Date(Date.now() - days * 86400000).toISOString();
|
|
211
|
+
this.db.prepare(`DELETE FROM usage_events WHERE created_at < ?`).run(cutoff);
|
|
212
|
+
this.db.prepare(`DELETE FROM session_events WHERE created_at < ?`).run(cutoff);
|
|
213
|
+
}
|
|
214
|
+
initSchema() {
|
|
215
|
+
this.db.exec(`
|
|
216
|
+
CREATE TABLE IF NOT EXISTS usage_events (
|
|
217
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
218
|
+
created_at TEXT NOT NULL,
|
|
219
|
+
provider TEXT,
|
|
220
|
+
model TEXT,
|
|
221
|
+
cwd TEXT,
|
|
222
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
223
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
224
|
+
est_cost_usd REAL NOT NULL DEFAULT 0,
|
|
225
|
+
source TEXT
|
|
226
|
+
);
|
|
227
|
+
CREATE INDEX IF NOT EXISTS idx_usage_created ON usage_events(created_at);
|
|
228
|
+
|
|
229
|
+
CREATE TABLE IF NOT EXISTS session_events (
|
|
230
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
231
|
+
created_at TEXT NOT NULL,
|
|
232
|
+
event_type TEXT NOT NULL,
|
|
233
|
+
model TEXT,
|
|
234
|
+
cwd TEXT,
|
|
235
|
+
language TEXT,
|
|
236
|
+
lines_accepted INTEGER,
|
|
237
|
+
lines_suggested INTEGER,
|
|
238
|
+
lines_reverted INTEGER,
|
|
239
|
+
extra_json TEXT
|
|
240
|
+
);
|
|
241
|
+
CREATE INDEX IF NOT EXISTS idx_session_created ON session_events(created_at);
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_session_type ON session_events(event_type);
|
|
243
|
+
`);
|
|
244
|
+
}
|
|
245
|
+
close() {
|
|
246
|
+
this.db.close();
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Force a WAL checkpoint (TRUNCATE) and close the database.
|
|
250
|
+
*
|
|
251
|
+
* Why: in WAL mode the main DB file lags behind the `-wal` sidecar until a
|
|
252
|
+
* checkpoint runs. If the process is killed (SIGINT/SIGTERM during proxy
|
|
253
|
+
* shutdown) without checkpointing, recent rows live only in the WAL and
|
|
254
|
+
* survive only as long as that file does. TRUNCATE both checkpoints and
|
|
255
|
+
* shrinks the WAL to zero so the next reader sees an up-to-date main file
|
|
256
|
+
* with no recovery surprises.
|
|
257
|
+
*
|
|
258
|
+
* Best-effort: if the pragma fails (e.g. handle already closed by another
|
|
259
|
+
* shutdown path) we still attempt to close the underlying handle.
|
|
260
|
+
*/
|
|
261
|
+
closeAndCheckpoint() {
|
|
262
|
+
try {
|
|
263
|
+
this.db.pragma('wal_checkpoint(TRUNCATE)');
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Handle may already be in a shutting-down state; safe to ignore.
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
this.db.close();
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Best-effort: nothing useful we can do on shutdown if close throws.
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
recordUsage(input) {
|
|
276
|
+
this.maybePurgeStaleRows();
|
|
277
|
+
const created = (input.createdAt ?? new Date()).toISOString();
|
|
278
|
+
let cost = input.estCostUsd;
|
|
279
|
+
if (cost === undefined) {
|
|
280
|
+
const p = input.provider ?? 'anthropic';
|
|
281
|
+
if (p === 'anthropic' || p === 'openai') {
|
|
282
|
+
cost = this.counter.calculateCost(p, input.inputTokens, input.outputTokens);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
cost = 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const stmt = this.db.prepare(`
|
|
289
|
+
INSERT INTO usage_events (created_at, provider, model, cwd, input_tokens, output_tokens, est_cost_usd, source)
|
|
290
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
291
|
+
`);
|
|
292
|
+
stmt.run(created, input.provider ?? 'unknown', input.model ?? null, hashCwdForStore(input.cwd, this.saltBuf), input.inputTokens, input.outputTokens, cost, input.source ?? null);
|
|
293
|
+
}
|
|
294
|
+
recordSession(input) {
|
|
295
|
+
this.maybePurgeStaleRows();
|
|
296
|
+
const created = (input.createdAt ?? new Date()).toISOString();
|
|
297
|
+
const extra = input.extra && Object.keys(input.extra).length > 0 ? JSON.stringify(input.extra) : null;
|
|
298
|
+
const stmt = this.db.prepare(`
|
|
299
|
+
INSERT INTO session_events (
|
|
300
|
+
created_at, event_type, model, cwd, language,
|
|
301
|
+
lines_accepted, lines_suggested, lines_reverted, extra_json
|
|
302
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
303
|
+
`);
|
|
304
|
+
stmt.run(created, input.eventType, input.model ?? null, hashCwdForStore(input.cwd, this.saltBuf), input.language ?? null, input.linesAccepted ?? null, input.linesSuggested ?? null, input.linesReverted ?? null, extra);
|
|
305
|
+
}
|
|
306
|
+
/** Count session events that represent blocked secrets today (UTC date). */
|
|
307
|
+
secretsBlockedToday(day = utcDayStart()) {
|
|
308
|
+
const row = this.db
|
|
309
|
+
.prepare(`SELECT COUNT(*) AS c FROM session_events
|
|
310
|
+
WHERE event_type = 'secret_blocked' AND substr(created_at, 1, 10) = ?`)
|
|
311
|
+
.get(day);
|
|
312
|
+
return row.c;
|
|
313
|
+
}
|
|
314
|
+
getStatuslinePayload(now = new Date()) {
|
|
315
|
+
const day = utcDayStart(now);
|
|
316
|
+
const windowStart = `${day}T00:00:00.000Z`;
|
|
317
|
+
const usage = this.db
|
|
318
|
+
.prepare(`SELECT
|
|
319
|
+
COALESCE(SUM(input_tokens), 0) AS tin,
|
|
320
|
+
COALESCE(SUM(output_tokens), 0) AS tout,
|
|
321
|
+
COALESCE(SUM(est_cost_usd), 0) AS cost
|
|
322
|
+
FROM usage_events
|
|
323
|
+
WHERE substr(created_at, 1, 10) = ?`)
|
|
324
|
+
.get(day);
|
|
325
|
+
const lastModel = this.db
|
|
326
|
+
.prepare(`SELECT model FROM usage_events
|
|
327
|
+
WHERE substr(created_at, 1, 10) = ? AND model IS NOT NULL AND model != ''
|
|
328
|
+
ORDER BY created_at DESC LIMIT 1`)
|
|
329
|
+
.get(day);
|
|
330
|
+
return {
|
|
331
|
+
secrets_today: this.secretsBlockedToday(day),
|
|
332
|
+
tokens_today_input: usage.tin,
|
|
333
|
+
tokens_today_output: usage.tout,
|
|
334
|
+
est_cost_usd: Math.round(usage.cost * 10_000) / 10_000,
|
|
335
|
+
model: lastModel?.model ?? null,
|
|
336
|
+
window_start_utc: windowStart,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Heuristic model hint from the last 7 days of session + usage data.
|
|
341
|
+
* Prefer models with more usage and lower revert_rate when session metrics exist.
|
|
342
|
+
*/
|
|
343
|
+
suggestModel(opts = {}) {
|
|
344
|
+
const since = new Date();
|
|
345
|
+
since.setUTCDate(since.getUTCDate() - 7);
|
|
346
|
+
const sinceIso = since.toISOString();
|
|
347
|
+
const usageByModel = this.db
|
|
348
|
+
.prepare(`SELECT model AS m,
|
|
349
|
+
COUNT(*) AS n,
|
|
350
|
+
AVG(est_cost_usd) AS avg_cost
|
|
351
|
+
FROM usage_events
|
|
352
|
+
WHERE created_at >= ? AND model IS NOT NULL AND model != ''
|
|
353
|
+
GROUP BY model`)
|
|
354
|
+
.all(sinceIso);
|
|
355
|
+
const revertRows = this.db
|
|
356
|
+
.prepare(`SELECT model AS m,
|
|
357
|
+
SUM(COALESCE(lines_reverted, 0)) AS rev,
|
|
358
|
+
SUM(COALESCE(lines_suggested, 0)) AS sug
|
|
359
|
+
FROM session_events
|
|
360
|
+
WHERE created_at >= ?
|
|
361
|
+
AND event_type IN ('apply', 'completion', 'revert')
|
|
362
|
+
AND model IS NOT NULL AND model != ''
|
|
363
|
+
GROUP BY model`)
|
|
364
|
+
.all(sinceIso);
|
|
365
|
+
const revertMap = new Map();
|
|
366
|
+
for (const r of revertRows) {
|
|
367
|
+
revertMap.set(r.m, { rev: r.rev, sug: r.sug });
|
|
368
|
+
}
|
|
369
|
+
const by_model = usageByModel.map(u => {
|
|
370
|
+
const rr = revertMap.get(u.m);
|
|
371
|
+
let revert_rate = null;
|
|
372
|
+
if (rr && rr.sug > 0) {
|
|
373
|
+
revert_rate = rr.rev / rr.sug;
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
model: u.m,
|
|
377
|
+
usage_events: u.n,
|
|
378
|
+
revert_rate,
|
|
379
|
+
avg_cost_usd: Math.round((u.avg_cost ?? 0) * 10_000) / 10_000,
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
if (by_model.length === 0) {
|
|
383
|
+
return {
|
|
384
|
+
suggested_model: null,
|
|
385
|
+
reason: 'No recent usage with a model label. Use the proxy or MCP report_token_usage first.',
|
|
386
|
+
by_model: [],
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
by_model.sort((a, b) => {
|
|
390
|
+
const ar = a.revert_rate ?? 0.5;
|
|
391
|
+
const br = b.revert_rate ?? 0.5;
|
|
392
|
+
if (ar !== br)
|
|
393
|
+
return ar - br;
|
|
394
|
+
return b.usage_events - a.usage_events;
|
|
395
|
+
});
|
|
396
|
+
const best = by_model[0];
|
|
397
|
+
let reason = `Lowest observed revert pressure among recent models (${best.usage_events} logged usage row(s)).`;
|
|
398
|
+
if (opts.language)
|
|
399
|
+
reason += ` Context: language=${opts.language}.`;
|
|
400
|
+
if (opts.cwd)
|
|
401
|
+
reason += ` cwd hint recorded.`;
|
|
402
|
+
return {
|
|
403
|
+
suggested_model: best.model,
|
|
404
|
+
reason,
|
|
405
|
+
by_model,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Read all rows from `usage_events` ordered by `id ASC`.
|
|
410
|
+
*
|
|
411
|
+
* Intended for `vault-guard data export`. Returns raw `cwd` strings — see
|
|
412
|
+
* {@link DataStatusJson} for the privacy-respecting alternative.
|
|
413
|
+
*/
|
|
414
|
+
exportUsageEvents() {
|
|
415
|
+
return this.db
|
|
416
|
+
.prepare(`SELECT id, created_at, provider, model, cwd, input_tokens, output_tokens, est_cost_usd, source
|
|
417
|
+
FROM usage_events ORDER BY id ASC`)
|
|
418
|
+
.all();
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Read all rows from `session_events` ordered by `id ASC`.
|
|
422
|
+
*
|
|
423
|
+
* Intended for `vault-guard data export`. Returns raw `cwd` strings and
|
|
424
|
+
* the `extra_json` payload as stored.
|
|
425
|
+
*/
|
|
426
|
+
exportSessionEvents() {
|
|
427
|
+
return this.db
|
|
428
|
+
.prepare(`SELECT id, created_at, event_type, model, cwd, language,
|
|
429
|
+
lines_accepted, lines_suggested, lines_reverted, extra_json
|
|
430
|
+
FROM session_events ORDER BY id ASC`)
|
|
431
|
+
.all();
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Compute a privacy-respecting summary of the local telemetry database
|
|
435
|
+
* (file location, size, row counts, distinct-value *counts*). Never
|
|
436
|
+
* returns raw `cwd` strings or `extra_json`.
|
|
437
|
+
*
|
|
438
|
+
* Used by `vault-guard data status`. Safe to paste into a support ticket.
|
|
439
|
+
*/
|
|
440
|
+
getDataStatus(dbFilePath) {
|
|
441
|
+
let dbExists = false;
|
|
442
|
+
let dbSize = 0;
|
|
443
|
+
let lastWriteIso = null;
|
|
444
|
+
try {
|
|
445
|
+
const stat = fs_1.default.statSync(dbFilePath);
|
|
446
|
+
dbExists = true;
|
|
447
|
+
dbSize = stat.size;
|
|
448
|
+
lastWriteIso = stat.mtime.toISOString();
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// File missing is a valid state — open() can create it; status reports it.
|
|
452
|
+
}
|
|
453
|
+
const sidecars = getDbSidecarPaths(dbFilePath)
|
|
454
|
+
.map(p => {
|
|
455
|
+
try {
|
|
456
|
+
const s = fs_1.default.statSync(p);
|
|
457
|
+
return { path: p, size_bytes: s.size };
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
.filter((x) => x !== null);
|
|
464
|
+
// COUNT(*) on indexed tables is cheap; we don't need to bound it.
|
|
465
|
+
const usageEvents = this.db.prepare('SELECT COUNT(*) AS n FROM usage_events').get().n;
|
|
466
|
+
const sessionEvents = this.db.prepare('SELECT COUNT(*) AS n FROM session_events').get().n;
|
|
467
|
+
// Earliest / latest across the union of both tables. NULL when both tables
|
|
468
|
+
// are empty, in which case MIN/MAX return NULL on the union.
|
|
469
|
+
const range = this.db
|
|
470
|
+
.prepare(`SELECT MIN(created_at) AS earliest, MAX(created_at) AS latest
|
|
471
|
+
FROM (
|
|
472
|
+
SELECT created_at FROM usage_events
|
|
473
|
+
UNION ALL
|
|
474
|
+
SELECT created_at FROM session_events
|
|
475
|
+
)`)
|
|
476
|
+
.get();
|
|
477
|
+
const distinctCwd = this.db
|
|
478
|
+
.prepare(`SELECT COUNT(DISTINCT cwd) AS n FROM (
|
|
479
|
+
SELECT cwd FROM usage_events WHERE cwd IS NOT NULL AND cwd != ''
|
|
480
|
+
UNION
|
|
481
|
+
SELECT cwd FROM session_events WHERE cwd IS NOT NULL AND cwd != ''
|
|
482
|
+
)`)
|
|
483
|
+
.get().n;
|
|
484
|
+
const distinctModel = this.db
|
|
485
|
+
.prepare(`SELECT COUNT(DISTINCT model) AS n FROM (
|
|
486
|
+
SELECT model FROM usage_events WHERE model IS NOT NULL AND model != ''
|
|
487
|
+
UNION
|
|
488
|
+
SELECT model FROM session_events WHERE model IS NOT NULL AND model != ''
|
|
489
|
+
)`)
|
|
490
|
+
.get().n;
|
|
491
|
+
const lastModelRow = this.db
|
|
492
|
+
.prepare(`SELECT model FROM usage_events
|
|
493
|
+
WHERE model IS NOT NULL AND model != ''
|
|
494
|
+
ORDER BY created_at DESC
|
|
495
|
+
LIMIT 1`)
|
|
496
|
+
.get();
|
|
497
|
+
return {
|
|
498
|
+
db_path: dbFilePath,
|
|
499
|
+
db_exists: dbExists,
|
|
500
|
+
db_size_bytes: dbSize,
|
|
501
|
+
last_write_iso: lastWriteIso,
|
|
502
|
+
sidecars,
|
|
503
|
+
usage_events: usageEvents,
|
|
504
|
+
session_events: sessionEvents,
|
|
505
|
+
earliest_event_iso: range.earliest,
|
|
506
|
+
latest_event_iso: range.latest,
|
|
507
|
+
distinct_cwd_count: distinctCwd,
|
|
508
|
+
distinct_model_count: distinctModel,
|
|
509
|
+
last_model: lastModelRow?.model ?? null,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
exports.TelemetryStore = TelemetryStore;
|
|
514
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vaultcompass/vault-guard-telemetry",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Opt-in local telemetry store for Vault Guard (usage + session events)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/vaultcompasshq/vault-guard.git",
|
|
16
|
+
"directory": "packages/telemetry"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.0.0",
|
|
20
|
+
"npm": ">=9.0.0",
|
|
21
|
+
"pnpm": ">=9.0.0"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"better-sqlite3": "^12.9.0",
|
|
26
|
+
"@vaultcompass/vault-guard-core": "1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
30
|
+
"@types/jest": "^30.0.0",
|
|
31
|
+
"@types/node": "^25.9.1",
|
|
32
|
+
"jest": "^30.4.2",
|
|
33
|
+
"ts-jest": "^29.4.11",
|
|
34
|
+
"typescript": "^5.3.3"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"test": "jest",
|
|
39
|
+
"test:coverage": "jest --coverage"
|
|
40
|
+
}
|
|
41
|
+
}
|