wyrm-mcp 7.2.1 → 7.2.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/LICENSE +26 -667
- package/NOTICE +14 -33
- package/dist/activation.js +1 -60
- package/dist/agent-daemon.js +4 -281
- package/dist/agent-loop.js +7 -332
- package/dist/analytics.js +13 -236
- package/dist/attribution.js +1 -49
- package/dist/audit.js +2 -457
- package/dist/auto-capture.js +3 -138
- package/dist/auto-orchestrator.js +1 -325
- package/dist/autoconfig.js +39 -840
- package/dist/buddy-runner.js +1 -109
- package/dist/buddy.js +14 -564
- package/dist/build-flags.js +1 -17
- package/dist/capabilities.js +3 -183
- package/dist/capture.js +1 -56
- package/dist/causality.js +6 -107
- package/dist/cli.js +20 -281
- package/dist/cloud/cli.js +5 -541
- package/dist/cloud/client.js +1 -221
- package/dist/cloud/crypto.js +1 -85
- package/dist/cloud/machine-id.js +2 -113
- package/dist/cloud/recovery.js +1 -60
- package/dist/cloud/sync-engine.js +7 -543
- package/dist/cloud-backup.js +5 -579
- package/dist/cloud-profile.js +1 -138
- package/dist/cloud-sync-entrypoint.js +1 -47
- package/dist/cloud-sync.js +2 -309
- package/dist/constellation.js +12 -168
- package/dist/context-build-budgeted.js +4 -144
- package/dist/context-ranking.js +1 -69
- package/dist/crypto.js +1 -179
- package/dist/daemon-write-endpoint.js +1 -290
- package/dist/daemon-writer.js +2 -406
- package/dist/database.js +43 -1110
- package/dist/deprecations.js +2 -162
- package/dist/design.js +13 -141
- package/dist/event-replication.js +1 -112
- package/dist/events-sse.js +7 -43
- package/dist/events.js +6 -238
- package/dist/failure-patterns.js +42 -659
- package/dist/federation.js +12 -236
- package/dist/goals.js +13 -101
- package/dist/golden.js +3 -355
- package/dist/handlers/agent.js +4 -165
- package/dist/handlers/alias-adapters.js +1 -129
- package/dist/handlers/aliases.js +1 -171
- package/dist/handlers/audit.js +1 -87
- package/dist/handlers/boundary.js +1 -221
- package/dist/handlers/capture.js +73 -1109
- package/dist/handlers/causality.js +7 -114
- package/dist/handlers/cloud.js +85 -382
- package/dist/handlers/companion.js +28 -459
- package/dist/handlers/datalake.js +7 -187
- package/dist/handlers/dispatch-context.js +0 -22
- package/dist/handlers/entity.js +25 -256
- package/dist/handlers/events.js +16 -335
- package/dist/handlers/failure.js +13 -340
- package/dist/handlers/goals.js +4 -296
- package/dist/handlers/intelligence.js +126 -674
- package/dist/handlers/invoicing.js +1 -70
- package/dist/handlers/mcpclient.js +6 -137
- package/dist/handlers/orchestration.js +40 -125
- package/dist/handlers/output-schemas.js +1 -24
- package/dist/handlers/presence.js +3 -99
- package/dist/handlers/project.js +28 -182
- package/dist/handlers/prompts.js +6 -157
- package/dist/handlers/quest.js +4 -224
- package/dist/handlers/recall.js +11 -218
- package/dist/handlers/registry.js +1 -167
- package/dist/handlers/resources.js +1 -288
- package/dist/handlers/review.js +11 -74
- package/dist/handlers/run.js +17 -487
- package/dist/handlers/search.js +15 -326
- package/dist/handlers/session.js +28 -615
- package/dist/handlers/share.js +8 -184
- package/dist/handlers/shims.js +1 -464
- package/dist/handlers/skill.js +67 -449
- package/dist/handlers/survivors.js +1 -120
- package/dist/handlers/symbols.js +8 -109
- package/dist/handlers/syncops.js +4 -302
- package/dist/handlers/types.js +1 -27
- package/dist/harvest.js +5 -191
- package/dist/hours.js +7 -156
- package/dist/http-auth.js +3 -321
- package/dist/http-fast.js +21 -1137
- package/dist/icons.js +1 -47
- package/dist/index.js +2 -924
- package/dist/indexer.js +4 -145
- package/dist/intelligence.js +31 -261
- package/dist/internal-dispatch.js +3 -212
- package/dist/keyset.js +1 -110
- package/dist/knowledge-graph.js +12 -176
- package/dist/license.js +2 -441
- package/dist/logger.js +2 -199
- package/dist/maintenance.js +2 -148
- package/dist/mcp-client.js +6 -262
- package/dist/memory-artifacts.js +30 -449
- package/dist/migrate-prompt.js +2 -124
- package/dist/migrations.js +40 -655
- package/dist/performance.js +1 -228
- package/dist/presence.js +11 -140
- package/dist/priority-embed.js +5 -164
- package/dist/providers/embedding-provider.js +1 -196
- package/dist/readonly-gate.js +1 -29
- package/dist/rehydration.js +9 -157
- package/dist/reindex.js +1 -88
- package/dist/render-target.js +21 -514
- package/dist/render.js +4 -280
- package/dist/repl-guard.js +1 -173
- package/dist/replication-daemon-entrypoint.js +1 -31
- package/dist/replication-daemon.js +2 -262
- package/dist/resilience.js +1 -591
- package/dist/reverse-bridge.js +5 -360
- package/dist/security.js +1 -244
- package/dist/session-seen.js +3 -51
- package/dist/setup.js +1 -260
- package/dist/skill-author.js +5 -168
- package/dist/spec-kit.js +1 -191
- package/dist/sqlite-busy.js +1 -154
- package/dist/statusline.js +11 -315
- package/dist/sub-agent.js +13 -262
- package/dist/summarizer.js +13 -139
- package/dist/symbols.js +7 -283
- package/dist/sync.js +5 -359
- package/dist/tasks-dispatch.js +1 -84
- package/dist/tasks.js +1 -282
- package/dist/token-budget.js +1 -143
- package/dist/tool-analytics.js +7 -129
- package/dist/tool-annotations.js +1 -365
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +1 -75
- package/dist/trace-harvest.js +6 -244
- package/dist/types.js +1 -30
- package/dist/ui-dashboard.js +41 -50
- package/dist/ulid.js +1 -81
- package/dist/validate.js +1 -129
- package/dist/vault.js +1 -534
- package/dist/vectors.js +3 -184
- package/dist/version-check.js +4 -136
- package/dist/visibility.js +19 -155
- package/dist/wyrm-cli.js +98 -2464
- package/dist/wyrm-guard.js +14 -424
- package/dist/wyrm-loop.js +3 -150
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +1 -11
- package/dist/wyrm-statusline.js +4 -56
- package/dist/wyrm-ui.js +9 -77
- package/package.json +4 -2
package/dist/analytics.js
CHANGED
|
@@ -1,43 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Wyrm Analytics — Persistent Usage Tracking & Cost Monitoring
|
|
3
|
-
*
|
|
4
|
-
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
5
|
-
* @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
|
|
6
|
-
* @module analytics
|
|
7
|
-
* @version 3.2.0
|
|
8
|
-
*/
|
|
9
|
-
// ==================== CONSTANTS ====================
|
|
10
|
-
/** Claude Sonnet pricing per 1M tokens */
|
|
11
|
-
const COST_PER_MILLION_INPUT = 3;
|
|
12
|
-
const COST_PER_MILLION_OUTPUT = 15;
|
|
13
|
-
const BUFFER_FLUSH_THRESHOLD = 50;
|
|
14
|
-
const FLUSH_INTERVAL_MS = 30_000;
|
|
15
|
-
const DEFAULT_RETAIN_DAYS = 90;
|
|
16
|
-
// ==================== ANALYTICS CLASS ====================
|
|
17
|
-
export class WyrmAnalytics {
|
|
18
|
-
db;
|
|
19
|
-
insertStmt;
|
|
20
|
-
batchBuffer;
|
|
21
|
-
flushInterval;
|
|
22
|
-
constructor(db) {
|
|
23
|
-
this.db = db;
|
|
24
|
-
this.batchBuffer = [];
|
|
25
|
-
this.initTables();
|
|
26
|
-
this.insertStmt = this.db.prepare(`
|
|
1
|
+
const S=3,p=15,h=50,A=3e4,N=90;class k{db;insertStmt;batchBuffer;flushInterval;constructor(_){this.db=_,this.batchBuffer=[],this.initTables(),this.insertStmt=this.db.prepare(`
|
|
27
2
|
INSERT INTO usage_events (tool_name, tokens_in, tokens_out, cached, response_ms, success, error_message, timestamp)
|
|
28
3
|
VALUES (@tool_name, @tokens_in, @tokens_out, @cached, @response_ms, @success, @error_message, @timestamp)
|
|
29
|
-
`)
|
|
30
|
-
this.flushInterval = setInterval(() => {
|
|
31
|
-
this.flush();
|
|
32
|
-
}, FLUSH_INTERVAL_MS);
|
|
33
|
-
// Don't let the timer keep the process alive
|
|
34
|
-
if (this.flushInterval.unref) {
|
|
35
|
-
this.flushInterval.unref();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
// ==================== SCHEMA ====================
|
|
39
|
-
initTables() {
|
|
40
|
-
this.db.exec(`
|
|
4
|
+
`),this.flushInterval=setInterval(()=>{this.flush()},3e4),this.flushInterval.unref&&this.flushInterval.unref()}initTables(){this.db.exec(`
|
|
41
5
|
CREATE TABLE IF NOT EXISTS usage_events (
|
|
42
6
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
7
|
tool_name TEXT NOT NULL,
|
|
@@ -68,59 +32,7 @@ export class WyrmAnalytics {
|
|
|
68
32
|
CREATE INDEX IF NOT EXISTS idx_usage_events_tool ON usage_events(tool_name);
|
|
69
33
|
CREATE INDEX IF NOT EXISTS idx_usage_events_ts ON usage_events(timestamp);
|
|
70
34
|
CREATE INDEX IF NOT EXISTS idx_cost_tracking_period ON cost_tracking(period);
|
|
71
|
-
`);
|
|
72
|
-
}
|
|
73
|
-
// ==================== RECORDING ====================
|
|
74
|
-
/** Record a single tool usage event */
|
|
75
|
-
record(event) {
|
|
76
|
-
this.batchBuffer.push(event);
|
|
77
|
-
if (this.batchBuffer.length >= BUFFER_FLUSH_THRESHOLD) {
|
|
78
|
-
this.flush();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/** Flush buffered events to disk */
|
|
82
|
-
flush() {
|
|
83
|
-
if (this.batchBuffer.length === 0)
|
|
84
|
-
return;
|
|
85
|
-
const events = this.batchBuffer.splice(0);
|
|
86
|
-
const insertMany = this.db.transaction((batch) => {
|
|
87
|
-
for (const event of batch) {
|
|
88
|
-
this.insertStmt.run({
|
|
89
|
-
tool_name: event.tool,
|
|
90
|
-
tokens_in: event.tokens_in,
|
|
91
|
-
tokens_out: event.tokens_out,
|
|
92
|
-
cached: event.cached ? 1 : 0,
|
|
93
|
-
response_ms: event.ms,
|
|
94
|
-
success: event.success !== false ? 1 : 0,
|
|
95
|
-
error_message: event.error ?? null,
|
|
96
|
-
timestamp: event.timestamp,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
insertMany(events);
|
|
101
|
-
this.updateCostTracking(events);
|
|
102
|
-
}
|
|
103
|
-
/** Update cost_tracking aggregates from a batch of events */
|
|
104
|
-
updateCostTracking(events) {
|
|
105
|
-
const groups = new Map();
|
|
106
|
-
for (const event of events) {
|
|
107
|
-
const period = event.timestamp.slice(0, 7); // YYYY-MM
|
|
108
|
-
const key = `${period}:${event.tool}`;
|
|
109
|
-
let group = groups.get(key);
|
|
110
|
-
if (!group) {
|
|
111
|
-
group = { tool: event.tool, period, calls: 0, tokens_in: 0, tokens_out: 0, cached: 0, total_ms: 0, errors: 0 };
|
|
112
|
-
groups.set(key, group);
|
|
113
|
-
}
|
|
114
|
-
group.calls++;
|
|
115
|
-
group.tokens_in += event.tokens_in;
|
|
116
|
-
group.tokens_out += event.tokens_out;
|
|
117
|
-
if (event.cached)
|
|
118
|
-
group.cached++;
|
|
119
|
-
group.total_ms += event.ms;
|
|
120
|
-
if (event.success === false)
|
|
121
|
-
group.errors++;
|
|
122
|
-
}
|
|
123
|
-
const upsertStmt = this.db.prepare(`
|
|
35
|
+
`)}record(_){this.batchBuffer.push(_),this.batchBuffer.length>=50&&this.flush()}flush(){if(this.batchBuffer.length===0)return;const _=this.batchBuffer.splice(0);this.db.transaction(n=>{for(const o of n)this.insertStmt.run({tool_name:o.tool,tokens_in:o.tokens_in,tokens_out:o.tokens_out,cached:o.cached?1:0,response_ms:o.ms,success:o.success!==!1?1:0,error_message:o.error??null,timestamp:o.timestamp})})(_),this.updateCostTracking(_)}updateCostTracking(_){const s=new Map;for(const t of _){const e=t.timestamp.slice(0,7),r=`${e}:${t.tool}`;let a=s.get(r);a||(a={tool:t.tool,period:e,calls:0,tokens_in:0,tokens_out:0,cached:0,total_ms:0,errors:0},s.set(r,a)),a.calls++,a.tokens_in+=t.tokens_in,a.tokens_out+=t.tokens_out,t.cached&&a.cached++,a.total_ms+=t.ms,t.success===!1&&a.errors++}const n=this.db.prepare(`
|
|
124
36
|
INSERT INTO cost_tracking (period, tool_name, call_count, total_tokens_in, total_tokens_out, cached_count, avg_response_ms, error_count, estimated_cost_usd, updated_at)
|
|
125
37
|
VALUES (@period, @tool_name, @call_count, @total_tokens_in, @total_tokens_out, @cached_count, @avg_response_ms, @error_count, @estimated_cost_usd, datetime('now'))
|
|
126
38
|
ON CONFLICT(period, tool_name) DO UPDATE SET
|
|
@@ -132,35 +44,7 @@ export class WyrmAnalytics {
|
|
|
132
44
|
error_count = error_count + @error_count,
|
|
133
45
|
estimated_cost_usd = estimated_cost_usd + @estimated_cost_usd,
|
|
134
46
|
updated_at = datetime('now')
|
|
135
|
-
`);
|
|
136
|
-
const upsertMany = this.db.transaction((entries) => {
|
|
137
|
-
for (const group of entries.values()) {
|
|
138
|
-
const cost = this.estimateCost(group.tokens_in, group.tokens_out);
|
|
139
|
-
upsertStmt.run({
|
|
140
|
-
period: group.period,
|
|
141
|
-
tool_name: group.tool,
|
|
142
|
-
call_count: group.calls,
|
|
143
|
-
total_tokens_in: group.tokens_in,
|
|
144
|
-
total_tokens_out: group.tokens_out,
|
|
145
|
-
cached_count: group.cached,
|
|
146
|
-
avg_response_ms: group.calls > 0 ? group.total_ms / group.calls : 0,
|
|
147
|
-
error_count: group.errors,
|
|
148
|
-
estimated_cost_usd: cost,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
upsertMany(groups);
|
|
153
|
-
}
|
|
154
|
-
// ==================== QUERIES ====================
|
|
155
|
-
/** Get dashboard summary for a time period */
|
|
156
|
-
dashboard(days = 30) {
|
|
157
|
-
// Ensure buffered data is included
|
|
158
|
-
this.flush();
|
|
159
|
-
const start = new Date();
|
|
160
|
-
start.setDate(start.getDate() - days);
|
|
161
|
-
const startStr = start.toISOString().slice(0, 10);
|
|
162
|
-
const endStr = new Date().toISOString().slice(0, 10);
|
|
163
|
-
const summary = this.db.prepare(`
|
|
47
|
+
`);this.db.transaction(t=>{for(const e of t.values()){const r=this.estimateCost(e.tokens_in,e.tokens_out);n.run({period:e.period,tool_name:e.tool,call_count:e.calls,total_tokens_in:e.tokens_in,total_tokens_out:e.tokens_out,cached_count:e.cached,avg_response_ms:e.calls>0?e.total_ms/e.calls:0,error_count:e.errors,estimated_cost_usd:r})}})(s)}dashboard(_=30){this.flush();const s=new Date;s.setDate(s.getDate()-_);const n=s.toISOString().slice(0,10),o=new Date().toISOString().slice(0,10),t=this.db.prepare(`
|
|
164
48
|
SELECT
|
|
165
49
|
COUNT(*) AS total_calls,
|
|
166
50
|
COUNT(DISTINCT tool_name) AS unique_tools,
|
|
@@ -177,8 +61,7 @@ export class WyrmAnalytics {
|
|
|
177
61
|
END AS error_rate
|
|
178
62
|
FROM usage_events
|
|
179
63
|
WHERE timestamp >= @start
|
|
180
|
-
`).get({
|
|
181
|
-
const top_tools = this.db.prepare(`
|
|
64
|
+
`).get({start:n}),e=this.db.prepare(`
|
|
182
65
|
SELECT
|
|
183
66
|
tool_name AS tool,
|
|
184
67
|
COUNT(*) AS calls,
|
|
@@ -188,8 +71,7 @@ export class WyrmAnalytics {
|
|
|
188
71
|
GROUP BY tool_name
|
|
189
72
|
ORDER BY calls DESC
|
|
190
73
|
LIMIT 10
|
|
191
|
-
`).all({
|
|
192
|
-
const daily = this.db.prepare(`
|
|
74
|
+
`).all({start:n}),r=this.db.prepare(`
|
|
193
75
|
SELECT
|
|
194
76
|
DATE(timestamp) AS date,
|
|
195
77
|
COUNT(*) AS calls,
|
|
@@ -199,31 +81,7 @@ export class WyrmAnalytics {
|
|
|
199
81
|
WHERE timestamp >= @start
|
|
200
82
|
GROUP BY DATE(timestamp)
|
|
201
83
|
ORDER BY date ASC
|
|
202
|
-
`).all({
|
|
203
|
-
const estimated_cost_usd = this.estimateCost(summary.total_tokens_in, summary.total_tokens_out);
|
|
204
|
-
return {
|
|
205
|
-
period: { start: startStr, end: endStr },
|
|
206
|
-
summary: {
|
|
207
|
-
total_calls: summary.total_calls,
|
|
208
|
-
unique_tools: summary.unique_tools,
|
|
209
|
-
total_tokens_in: summary.total_tokens_in,
|
|
210
|
-
total_tokens_out: summary.total_tokens_out,
|
|
211
|
-
cache_hit_rate: summary.cache_hit_rate,
|
|
212
|
-
avg_response_ms: summary.avg_response_ms,
|
|
213
|
-
error_rate: summary.error_rate,
|
|
214
|
-
estimated_cost_usd,
|
|
215
|
-
},
|
|
216
|
-
top_tools,
|
|
217
|
-
daily,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
/** Get per-tool breakdown */
|
|
221
|
-
toolBreakdown(toolName, days = 30) {
|
|
222
|
-
this.flush();
|
|
223
|
-
const start = new Date();
|
|
224
|
-
start.setDate(start.getDate() - days);
|
|
225
|
-
const startStr = start.toISOString().slice(0, 10);
|
|
226
|
-
const stats = this.db.prepare(`
|
|
84
|
+
`).all({start:n}),a=this.estimateCost(t.total_tokens_in,t.total_tokens_out);return{period:{start:n,end:o},summary:{total_calls:t.total_calls,unique_tools:t.unique_tools,total_tokens_in:t.total_tokens_in,total_tokens_out:t.total_tokens_out,cache_hit_rate:t.cache_hit_rate,avg_response_ms:t.avg_response_ms,error_rate:t.error_rate,estimated_cost_usd:a},top_tools:e,daily:r}}toolBreakdown(_,s=30){this.flush();const n=new Date;n.setDate(n.getDate()-s);const o=n.toISOString().slice(0,10),t=this.db.prepare(`
|
|
227
85
|
SELECT
|
|
228
86
|
COUNT(*) AS total_calls,
|
|
229
87
|
COALESCE(ROUND(AVG(tokens_in), 2), 0) AS avg_tokens_in,
|
|
@@ -239,8 +97,7 @@ export class WyrmAnalytics {
|
|
|
239
97
|
END AS error_rate
|
|
240
98
|
FROM usage_events
|
|
241
99
|
WHERE tool_name = @tool AND timestamp >= @start
|
|
242
|
-
`).get({
|
|
243
|
-
const daily = this.db.prepare(`
|
|
100
|
+
`).get({tool:_,start:o}),e=this.db.prepare(`
|
|
244
101
|
SELECT
|
|
245
102
|
DATE(timestamp) AS date,
|
|
246
103
|
COUNT(*) AS calls
|
|
@@ -248,23 +105,7 @@ export class WyrmAnalytics {
|
|
|
248
105
|
WHERE tool_name = @tool AND timestamp >= @start
|
|
249
106
|
GROUP BY DATE(timestamp)
|
|
250
107
|
ORDER BY date ASC
|
|
251
|
-
`).all({
|
|
252
|
-
return {
|
|
253
|
-
tool: toolName,
|
|
254
|
-
total_calls: stats.total_calls,
|
|
255
|
-
avg_tokens_in: stats.avg_tokens_in,
|
|
256
|
-
avg_tokens_out: stats.avg_tokens_out,
|
|
257
|
-
avg_response_ms: stats.avg_response_ms,
|
|
258
|
-
cache_hit_rate: stats.cache_hit_rate,
|
|
259
|
-
error_rate: stats.error_rate,
|
|
260
|
-
daily,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
/** Get cost estimate for a period */
|
|
264
|
-
costReport(period) {
|
|
265
|
-
this.flush();
|
|
266
|
-
const targetPeriod = period ?? new Date().toISOString().slice(0, 7);
|
|
267
|
-
const tools = this.db.prepare(`
|
|
108
|
+
`).all({tool:_,start:o});return{tool:_,total_calls:t.total_calls,avg_tokens_in:t.avg_tokens_in,avg_tokens_out:t.avg_tokens_out,avg_response_ms:t.avg_response_ms,cache_hit_rate:t.cache_hit_rate,error_rate:t.error_rate,daily:e}}costReport(_){this.flush();const s=_??new Date().toISOString().slice(0,7),n=this.db.prepare(`
|
|
268
109
|
SELECT
|
|
269
110
|
tool_name AS tool,
|
|
270
111
|
call_count AS calls,
|
|
@@ -274,35 +115,7 @@ export class WyrmAnalytics {
|
|
|
274
115
|
FROM cost_tracking
|
|
275
116
|
WHERE period = @period
|
|
276
117
|
ORDER BY estimated_cost_usd DESC
|
|
277
|
-
`).all({
|
|
278
|
-
const total_cost_usd = tools.reduce((sum, t) => sum + t.cost_usd, 0);
|
|
279
|
-
// Project monthly cost based on days elapsed in the period
|
|
280
|
-
const now = new Date();
|
|
281
|
-
const periodYear = parseInt(targetPeriod.slice(0, 4), 10);
|
|
282
|
-
const periodMonth = parseInt(targetPeriod.slice(5, 7), 10) - 1;
|
|
283
|
-
const periodStart = new Date(periodYear, periodMonth, 1);
|
|
284
|
-
const periodEnd = new Date(periodYear, periodMonth + 1, 0); // last day of month
|
|
285
|
-
const totalDaysInMonth = periodEnd.getDate();
|
|
286
|
-
const elapsed = Math.max(1, Math.ceil((now.getTime() - periodStart.getTime()) / (1000 * 60 * 60 * 24)));
|
|
287
|
-
const daysToProject = Math.min(elapsed, totalDaysInMonth);
|
|
288
|
-
const projected_monthly_usd = totalDaysInMonth > 0
|
|
289
|
-
? Math.round(((total_cost_usd / daysToProject) * totalDaysInMonth) * 100) / 100
|
|
290
|
-
: total_cost_usd;
|
|
291
|
-
return {
|
|
292
|
-
period: targetPeriod,
|
|
293
|
-
tools,
|
|
294
|
-
total_cost_usd: Math.round(total_cost_usd * 100) / 100,
|
|
295
|
-
projected_monthly_usd,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
/** Clean up old events (retention policy) */
|
|
299
|
-
cleanup(retainDays = DEFAULT_RETAIN_DAYS) {
|
|
300
|
-
this.flush();
|
|
301
|
-
const cutoff = new Date();
|
|
302
|
-
cutoff.setDate(cutoff.getDate() - retainDays);
|
|
303
|
-
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
304
|
-
// Aggregate old events into cost_tracking before deleting
|
|
305
|
-
const oldEvents = this.db.prepare(`
|
|
118
|
+
`).all({period:s}),o=n.reduce((d,T)=>d+T.cost_usd,0),t=new Date,e=parseInt(s.slice(0,4),10),r=parseInt(s.slice(5,7),10)-1,a=new Date(e,r,1),l=new Date(e,r+1,0).getDate(),u=Math.max(1,Math.ceil((t.getTime()-a.getTime())/(1e3*60*60*24))),E=Math.min(u,l),i=l>0?Math.round(o/E*l*100)/100:o;return{period:s,tools:n,total_cost_usd:Math.round(o*100)/100,projected_monthly_usd:i}}cleanup(_=90){this.flush();const s=new Date;s.setDate(s.getDate()-_);const n=s.toISOString().slice(0,10),o=this.db.prepare(`
|
|
306
119
|
SELECT
|
|
307
120
|
STRFTIME('%Y-%m', timestamp) AS period,
|
|
308
121
|
tool_name,
|
|
@@ -315,8 +128,7 @@ export class WyrmAnalytics {
|
|
|
315
128
|
FROM usage_events
|
|
316
129
|
WHERE timestamp < @cutoff
|
|
317
130
|
GROUP BY STRFTIME('%Y-%m', timestamp), tool_name
|
|
318
|
-
`).all({
|
|
319
|
-
const upsertStmt = this.db.prepare(`
|
|
131
|
+
`).all({cutoff:n}),t=this.db.prepare(`
|
|
320
132
|
INSERT INTO cost_tracking (period, tool_name, call_count, total_tokens_in, total_tokens_out, cached_count, avg_response_ms, error_count, estimated_cost_usd, updated_at)
|
|
321
133
|
VALUES (@period, @tool_name, @call_count, @total_tokens_in, @total_tokens_out, @cached_count, @avg_response_ms, @error_count, @estimated_cost_usd, datetime('now'))
|
|
322
134
|
ON CONFLICT(period, tool_name) DO UPDATE SET
|
|
@@ -328,41 +140,6 @@ export class WyrmAnalytics {
|
|
|
328
140
|
error_count = error_count + @error_count,
|
|
329
141
|
estimated_cost_usd = estimated_cost_usd + @estimated_cost_usd,
|
|
330
142
|
updated_at = datetime('now')
|
|
331
|
-
`);
|
|
332
|
-
const cleanupTxn = this.db.transaction(() => {
|
|
333
|
-
for (const row of oldEvents) {
|
|
334
|
-
const cost = this.estimateCost(row.total_tokens_in, row.total_tokens_out);
|
|
335
|
-
upsertStmt.run({
|
|
336
|
-
period: row.period,
|
|
337
|
-
tool_name: row.tool_name,
|
|
338
|
-
call_count: row.call_count,
|
|
339
|
-
total_tokens_in: row.total_tokens_in,
|
|
340
|
-
total_tokens_out: row.total_tokens_out,
|
|
341
|
-
cached_count: row.cached_count,
|
|
342
|
-
avg_response_ms: row.avg_response_ms,
|
|
343
|
-
error_count: row.error_count,
|
|
344
|
-
estimated_cost_usd: cost,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
const result = this.db.prepare(`
|
|
143
|
+
`);return{deleted:this.db.transaction(()=>{for(const c of o){const l=this.estimateCost(c.total_tokens_in,c.total_tokens_out);t.run({period:c.period,tool_name:c.tool_name,call_count:c.call_count,total_tokens_in:c.total_tokens_in,total_tokens_out:c.total_tokens_out,cached_count:c.cached_count,avg_response_ms:c.avg_response_ms,error_count:c.error_count,estimated_cost_usd:l})}return this.db.prepare(`
|
|
348
144
|
DELETE FROM usage_events WHERE timestamp < @cutoff
|
|
349
|
-
`).run({
|
|
350
|
-
return result.changes;
|
|
351
|
-
});
|
|
352
|
-
const deleted = cleanupTxn();
|
|
353
|
-
return { deleted };
|
|
354
|
-
}
|
|
355
|
-
/** Shutdown: flush remaining buffer and clear timer */
|
|
356
|
-
shutdown() {
|
|
357
|
-
clearInterval(this.flushInterval);
|
|
358
|
-
this.flush();
|
|
359
|
-
}
|
|
360
|
-
// ==================== HELPERS ====================
|
|
361
|
-
/** Estimate USD cost from token counts (Claude Sonnet rates) */
|
|
362
|
-
estimateCost(tokensIn, tokensOut) {
|
|
363
|
-
const inputCost = (tokensIn / 1_000_000) * COST_PER_MILLION_INPUT;
|
|
364
|
-
const outputCost = (tokensOut / 1_000_000) * COST_PER_MILLION_OUTPUT;
|
|
365
|
-
return Math.round((inputCost + outputCost) * 1_000_000) / 1_000_000;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
//# sourceMappingURL=analytics.js.map
|
|
145
|
+
`).run({cutoff:n}).changes})()}}shutdown(){clearInterval(this.flushInterval),this.flush()}estimateCost(_,s){const n=_/1e6*3,o=s/1e6*15;return Math.round((n+o)*1e6)/1e6}}export{k as WyrmAnalytics};
|
package/dist/attribution.js
CHANGED
|
@@ -1,49 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Wyrm attribution read helpers (v7 F2, T008).
|
|
3
|
-
*
|
|
4
|
-
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
5
|
-
* @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
|
|
6
|
-
*
|
|
7
|
-
* Migration 20 adds nullable `agent_id`/`run_id` attribution columns to 9
|
|
8
|
-
* tables. Every row written before v7 — and any v7 row written outside a fleet
|
|
9
|
-
* run — carries NULL attribution. Article VI: a v6.x DB must open under v7
|
|
10
|
-
* with 100% of its rows, and that history must stay legibly attributed, so at
|
|
11
|
-
* every READ SITE that surfaces attribution, NULL reads as actor='legacy'.
|
|
12
|
-
*
|
|
13
|
-
* Precedence when rendering "who did this" (the same order is documented in
|
|
14
|
-
* migration 20 next to the events.agent_id column):
|
|
15
|
-
* 1. agent_id — machine identity (v7 fleet writes)
|
|
16
|
-
* 2. actor — display identity (the existing 6.x column on
|
|
17
|
-
* events/audit_log; it is EXTENDED, never replaced)
|
|
18
|
-
* 3. LEGACY_ACTOR — 'legacy' (neither set: a pre-v7 row)
|
|
19
|
-
*
|
|
20
|
-
* Wire paths (eventsForPush, ingestRemoteEvent, replication) intentionally do
|
|
21
|
-
* NOT coalesce: NULL stays NULL in storage and on the wire so replication
|
|
22
|
-
* round-trips are byte-faithful. 'legacy' is a READ-TIME presentation value
|
|
23
|
-
* only — it is never written back to the database.
|
|
24
|
-
*/
|
|
25
|
-
/** What NULL attribution reads as at attribution-surfacing read sites. */
|
|
26
|
-
export const LEGACY_ACTOR = 'legacy';
|
|
27
|
-
/**
|
|
28
|
-
* Read a display actor from a nullable actor column.
|
|
29
|
-
* NULL/undefined/blank -> 'legacy'; anything else passes through trimmed.
|
|
30
|
-
*/
|
|
31
|
-
export function readActor(actor) {
|
|
32
|
-
const a = typeof actor === 'string' ? actor.trim() : '';
|
|
33
|
-
return a !== '' ? a : LEGACY_ACTOR;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Resolve the full attribution for one row using the documented precedence.
|
|
37
|
-
* Use this at any read site that surfaces who/which-run produced a row
|
|
38
|
-
* (failure_check verdicts, wyrm_stats view=failures, event displays).
|
|
39
|
-
*/
|
|
40
|
-
export function resolveAttribution(row) {
|
|
41
|
-
const agentId = row.agent_id ?? null;
|
|
42
|
-
const runId = row.run_id ?? null;
|
|
43
|
-
return {
|
|
44
|
-
actor: agentId ?? readActor(row.actor),
|
|
45
|
-
agent_id: agentId,
|
|
46
|
-
run_id: runId,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=attribution.js.map
|
|
1
|
+
const e="legacy";function o(t){const n=typeof t=="string"?t.trim():"";return n!==""?n:e}function i(t){const n=t.agent_id??null,r=t.run_id??null;return{actor:n??o(t.actor),agent_id:n,run_id:r}}export{e as LEGACY_ACTOR,o as readActor,i as resolveAttribution};
|