@hasna/economy 0.2.41 → 0.2.42
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/dist/cli/commands/extras.d.ts.map +1 -1
- package/dist/cli/index.js +379 -77
- package/dist/db/database.d.ts +4 -3
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +299 -49
- package/dist/lib/fleet-sync.d.ts +61 -0
- package/dist/lib/fleet-sync.d.ts.map +1 -0
- package/dist/mcp/index.js +66 -16
- package/dist/otel/index.js +41 -5
- package/dist/server/index.js +68 -17
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.js +2746 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/db/database.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SqliteAdapter as Database } from '@hasna/cloud';
|
|
2
|
-
import type { EconomyRequest, EconomySession, EconomyProject, Budget, BudgetStatus, CostSummary, ModelBreakdown, ProjectBreakdown, AgentBreakdown, AccountBreakdown, Period, SessionFilter } from '../types/index.js';
|
|
2
|
+
import type { EconomyRequest, EconomySession, EconomyProject, Budget, BudgetStatus, CostSummary, ZeroCostModelBreakdown, ModelBreakdown, ProjectBreakdown, AgentBreakdown, AccountBreakdown, Period, Agent, SessionFilter } from '../types/index.js';
|
|
3
3
|
export declare function getMachineId(): string;
|
|
4
4
|
export declare function getDataDir(): string;
|
|
5
5
|
export declare function getDbPath(): string;
|
|
@@ -8,8 +8,9 @@ export declare function upsertRequest(db: Database, req: EconomyRequest): void;
|
|
|
8
8
|
export declare function upsertSession(db: Database, session: EconomySession): void;
|
|
9
9
|
export declare function rollupSession(db: Database, sessionId: string): void;
|
|
10
10
|
export declare function querySessions(db: Database, filter?: SessionFilter): EconomySession[];
|
|
11
|
-
export declare function queryTopSessions(db: Database, n?: number, agent?: string): EconomySession[];
|
|
12
|
-
export declare function querySummary(db: Database, period: Period, machine?: string, allMachines?: boolean): CostSummary;
|
|
11
|
+
export declare function queryTopSessions(db: Database, n?: number, agent?: string, since?: string): EconomySession[];
|
|
12
|
+
export declare function querySummary(db: Database, period: Period, machine?: string, allMachines?: boolean, agent?: Agent): CostSummary;
|
|
13
|
+
export declare function queryZeroCostTokenizedModels(db: Database, limit?: number): ZeroCostModelBreakdown[];
|
|
13
14
|
export declare function queryModelBreakdown(db: Database): ModelBreakdown[];
|
|
14
15
|
export declare function queryAgentBreakdown(db: Database, period?: Period, machine?: string): AgentBreakdown[];
|
|
15
16
|
export declare function queryProjectBreakdown(db: Database, period?: Period, machine?: string): ProjectBreakdown[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAMxD,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,MAAM,EACN,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACd,MAAM,mBAAmB,CAAA;AAyB1B,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAkBnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './lib/gatherer.js';
|
|
|
5
5
|
export * from './lib/model-config.js';
|
|
6
6
|
export * from './lib/open-projects.js';
|
|
7
7
|
export * from './lib/peer-sync.js';
|
|
8
|
+
export * from './lib/fleet-sync.js';
|
|
8
9
|
export * from './ingest/claude.js';
|
|
9
10
|
export * from './ingest/codex.js';
|
|
10
11
|
export * from './ingest/gemini.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -572,6 +572,32 @@ function getDbPath() {
|
|
|
572
572
|
return process.env["ECONOMY_DB"];
|
|
573
573
|
return join(getDataDir(), "economy.db");
|
|
574
574
|
}
|
|
575
|
+
function isSqliteBusyError(error) {
|
|
576
|
+
const candidate = error;
|
|
577
|
+
const code = typeof candidate.code === "string" ? candidate.code : "";
|
|
578
|
+
const message = typeof candidate.message === "string" ? candidate.message : String(error);
|
|
579
|
+
return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_RECOVERY" || /database is locked|SQLITE_BUSY/i.test(message);
|
|
580
|
+
}
|
|
581
|
+
function retryDelayMs(attempt) {
|
|
582
|
+
return Math.min(1000, 50 * 2 ** attempt);
|
|
583
|
+
}
|
|
584
|
+
function withSqliteBusyRetry(operation, context) {
|
|
585
|
+
const maxAttempts = 8;
|
|
586
|
+
let lastError;
|
|
587
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
588
|
+
try {
|
|
589
|
+
return operation();
|
|
590
|
+
} catch (error) {
|
|
591
|
+
if (!isSqliteBusyError(error))
|
|
592
|
+
throw error;
|
|
593
|
+
lastError = error;
|
|
594
|
+
if (attempt === maxAttempts - 1)
|
|
595
|
+
break;
|
|
596
|
+
Bun.sleepSync(retryDelayMs(attempt));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
throw new Error(`SQLite database is locked after ${maxAttempts} attempts while ${context}. Another economy sync/merge may be recovering the database; retry shortly.`, { cause: lastError });
|
|
600
|
+
}
|
|
575
601
|
function openDatabase(dbPath, skipSeed = false) {
|
|
576
602
|
const path = dbPath ?? getDbPath();
|
|
577
603
|
if (path !== ":memory:") {
|
|
@@ -579,11 +605,21 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
579
605
|
if (dir && !existsSync(dir))
|
|
580
606
|
mkdirSync(dir, { recursive: true });
|
|
581
607
|
}
|
|
582
|
-
const db =
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
608
|
+
const db = withSqliteBusyRetry(() => {
|
|
609
|
+
const opened = new Database(path);
|
|
610
|
+
try {
|
|
611
|
+
opened.exec("PRAGMA busy_timeout = 10000");
|
|
612
|
+
opened.exec("PRAGMA journal_mode = WAL");
|
|
613
|
+
opened.exec("PRAGMA foreign_keys = ON");
|
|
614
|
+
initSchema(opened);
|
|
615
|
+
return opened;
|
|
616
|
+
} catch (error) {
|
|
617
|
+
try {
|
|
618
|
+
opened.close();
|
|
619
|
+
} catch {}
|
|
620
|
+
throw error;
|
|
621
|
+
}
|
|
622
|
+
}, `opening ${path}`);
|
|
587
623
|
if (!skipSeed) {
|
|
588
624
|
Promise.resolve().then(() => (init_pricing(), exports_pricing)).then(({ ensurePricingSeeded: ensurePricingSeeded2 }) => ensurePricingSeeded2(db)).catch(() => {});
|
|
589
625
|
}
|
|
@@ -945,36 +981,50 @@ function querySessions(db, filter = {}) {
|
|
|
945
981
|
SELECT * FROM sessions ${where} ORDER BY started_at DESC LIMIT ? OFFSET ?
|
|
946
982
|
`).all(...params, limit, offset);
|
|
947
983
|
}
|
|
948
|
-
function queryTopSessions(db, n = 10, agent) {
|
|
984
|
+
function queryTopSessions(db, n = 10, agent, since) {
|
|
985
|
+
const conditions = [];
|
|
986
|
+
const params = [];
|
|
949
987
|
if (agent) {
|
|
950
|
-
|
|
988
|
+
conditions.push("agent = ?");
|
|
989
|
+
params.push(agent);
|
|
990
|
+
}
|
|
991
|
+
if (since) {
|
|
992
|
+
conditions.push("started_at >= ?");
|
|
993
|
+
params.push(since);
|
|
951
994
|
}
|
|
952
|
-
|
|
995
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
996
|
+
return db.prepare(`SELECT * FROM sessions ${where} ORDER BY total_cost_usd DESC LIMIT ?`).all(...params, n);
|
|
953
997
|
}
|
|
954
|
-
function querySummary(db, period, machine, allMachines = false) {
|
|
998
|
+
function querySummary(db, period, machine, allMachines = false, agent) {
|
|
955
999
|
const rWhere = periodWhere(period);
|
|
956
1000
|
const sWhere = sessionPeriodWhere(period);
|
|
957
|
-
const machineClause = !allMachines && machine ?
|
|
1001
|
+
const machineClause = !allMachines && machine ? " AND machine_id = ?" : "";
|
|
1002
|
+
const agentClause = agent ? " AND agent = ?" : "";
|
|
1003
|
+
const params = [];
|
|
1004
|
+
if (!allMachines && machine)
|
|
1005
|
+
params.push(machine);
|
|
1006
|
+
if (agent)
|
|
1007
|
+
params.push(agent);
|
|
958
1008
|
const r = db.prepare(`
|
|
959
1009
|
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
960
1010
|
COUNT(*) as requests,
|
|
961
1011
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
962
|
-
FROM requests WHERE ${rWhere}${machineClause}
|
|
963
|
-
`).get();
|
|
1012
|
+
FROM requests WHERE ${rWhere}${machineClause}${agentClause}
|
|
1013
|
+
`).get(...params);
|
|
964
1014
|
const codexTotals = db.prepare(`
|
|
965
1015
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
966
1016
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
967
1017
|
COALESCE(SUM(request_count), 0) as requests,
|
|
968
1018
|
COUNT(*) as sessions
|
|
969
1019
|
FROM sessions
|
|
970
|
-
WHERE ${sWhere}${machineClause}
|
|
1020
|
+
WHERE ${sWhere}${machineClause}${agentClause}
|
|
971
1021
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
972
|
-
`).get();
|
|
1022
|
+
`).get(...params);
|
|
973
1023
|
const requestSessionCount = db.prepare(`
|
|
974
1024
|
SELECT COUNT(DISTINCT session_id) as sessions
|
|
975
1025
|
FROM requests
|
|
976
|
-
WHERE ${rWhere}${machineClause}
|
|
977
|
-
`).get();
|
|
1026
|
+
WHERE ${rWhere}${machineClause}${agentClause}
|
|
1027
|
+
`).get(...params);
|
|
978
1028
|
return {
|
|
979
1029
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
980
1030
|
requests: r.requests + codexTotals.requests,
|
|
@@ -983,6 +1033,21 @@ function querySummary(db, period, machine, allMachines = false) {
|
|
|
983
1033
|
period
|
|
984
1034
|
};
|
|
985
1035
|
}
|
|
1036
|
+
function queryZeroCostTokenizedModels(db, limit = 10) {
|
|
1037
|
+
return db.prepare(`
|
|
1038
|
+
SELECT agent,
|
|
1039
|
+
model,
|
|
1040
|
+
COUNT(*) as requests,
|
|
1041
|
+
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1042
|
+
MAX(timestamp) as last_seen
|
|
1043
|
+
FROM requests
|
|
1044
|
+
WHERE cost_usd = 0
|
|
1045
|
+
AND (input_tokens > 0 OR output_tokens > 0 OR cache_read_tokens > 0 OR cache_create_tokens > 0)
|
|
1046
|
+
GROUP BY agent, model
|
|
1047
|
+
ORDER BY requests DESC, total_tokens DESC
|
|
1048
|
+
LIMIT ?
|
|
1049
|
+
`).all(limit);
|
|
1050
|
+
}
|
|
986
1051
|
function queryModelBreakdown(db) {
|
|
987
1052
|
return db.prepare(`
|
|
988
1053
|
SELECT model, agent,
|
|
@@ -2186,12 +2251,192 @@ function mergePeerDatabase(target, sourcePath, opts = {}) {
|
|
|
2186
2251
|
tables: tables.filter((t) => t.inserted || t.updated || t.skipped || t.collisions)
|
|
2187
2252
|
};
|
|
2188
2253
|
}
|
|
2254
|
+
// src/lib/fleet-sync.ts
|
|
2255
|
+
init_database();
|
|
2256
|
+
import { spawnSync } from "child_process";
|
|
2257
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2258
|
+
import { join as join3, resolve } from "path";
|
|
2259
|
+
import { homedir as homedir2 } from "os";
|
|
2260
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
2261
|
+
var DEFAULT_TIMEOUT_MS = 120000;
|
|
2262
|
+
var DEFAULT_MACHINES = ["spark02", "apple03"];
|
|
2263
|
+
function runEconomyFleetSync(options = {}) {
|
|
2264
|
+
const generatedAt = (options.now ?? new Date).toISOString();
|
|
2265
|
+
const timeoutMs = normalizePositiveInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
2266
|
+
const runner = options.runner ?? spawnCommand;
|
|
2267
|
+
const dryRun = Boolean(options.dryRun);
|
|
2268
|
+
const machines = ((options.machines?.length) ? options.machines : DEFAULT_MACHINES).filter(Boolean);
|
|
2269
|
+
const cacheDir = resolve(options.cacheDir ?? join3(homedir2(), ".hasna", "economy", "fleet-sync-cache"));
|
|
2270
|
+
const remoteSnapshotDir = options.remoteSnapshotDir ?? "$HOME/.hasna/economy/fleet-sync-snapshots";
|
|
2271
|
+
const localSync = options.localSync !== false;
|
|
2272
|
+
const remoteSync = options.remoteSync !== false;
|
|
2273
|
+
const local = {
|
|
2274
|
+
status: dryRun ? "dry_run" : localSync ? "skipped" : "skipped",
|
|
2275
|
+
db_path: getDbPath()
|
|
2276
|
+
};
|
|
2277
|
+
if (localSync) {
|
|
2278
|
+
if (dryRun) {
|
|
2279
|
+
local.status = "dry_run";
|
|
2280
|
+
} else {
|
|
2281
|
+
local.sync = runner("economy", ["sync"], { timeoutMs });
|
|
2282
|
+
local.status = local.sync.code === 0 ? "synced" : "failed";
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
const remote = machines.map((machine) => syncMachine(machine, {
|
|
2286
|
+
generatedAt,
|
|
2287
|
+
timeoutMs,
|
|
2288
|
+
runner,
|
|
2289
|
+
dryRun,
|
|
2290
|
+
remoteSync,
|
|
2291
|
+
cacheDir,
|
|
2292
|
+
remoteSnapshotDir
|
|
2293
|
+
}));
|
|
2294
|
+
return {
|
|
2295
|
+
schema: "open-economy.fleet-sync.v1",
|
|
2296
|
+
generated_at: generatedAt,
|
|
2297
|
+
dry_run: dryRun,
|
|
2298
|
+
local,
|
|
2299
|
+
remote,
|
|
2300
|
+
summary: {
|
|
2301
|
+
machines: remote.length,
|
|
2302
|
+
merged: remote.filter((row) => row.status === "merged").length,
|
|
2303
|
+
failed: remote.filter((row) => row.status === "failed").length,
|
|
2304
|
+
dry_run: remote.filter((row) => row.status === "dry_run").length + (dryRun ? 1 : 0)
|
|
2305
|
+
}
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
function syncMachine(machine, context) {
|
|
2309
|
+
const safeMachine = machine.replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
2310
|
+
const stamp = context.generatedAt.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
2311
|
+
const snapshotName = `${safeMachine}-${stamp}.economy.db`;
|
|
2312
|
+
const remoteSnapshotPath = `${context.remoteSnapshotDir.replace(/\/$/, "")}/${snapshotName}`;
|
|
2313
|
+
const tmpLocal = join3(context.cacheDir, `${snapshotName}.tmp`);
|
|
2314
|
+
const verifiedLocal = join3(context.cacheDir, snapshotName);
|
|
2315
|
+
if (context.dryRun) {
|
|
2316
|
+
return { machine, status: "dry_run", remote_snapshot_path: remoteSnapshotPath };
|
|
2317
|
+
}
|
|
2318
|
+
mkdirSync3(context.cacheDir, { recursive: true });
|
|
2319
|
+
rmSync(tmpLocal, { force: true });
|
|
2320
|
+
const result = {
|
|
2321
|
+
machine,
|
|
2322
|
+
status: "failed",
|
|
2323
|
+
remote_snapshot_path: remoteSnapshotPath
|
|
2324
|
+
};
|
|
2325
|
+
if (context.remoteSync) {
|
|
2326
|
+
result.remote_sync = context.runner("ssh", [machine, "economy sync"], { timeoutMs: context.timeoutMs });
|
|
2327
|
+
if (result.remote_sync.code !== 0) {
|
|
2328
|
+
result.error = "remote economy sync failed";
|
|
2329
|
+
return result;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
result.remote_snapshot = context.runner("ssh", [machine, remoteSnapshotCommand(context.remoteSnapshotDir, snapshotName)], { timeoutMs: context.timeoutMs });
|
|
2333
|
+
if (result.remote_snapshot.code !== 0) {
|
|
2334
|
+
result.error = "remote snapshot failed";
|
|
2335
|
+
return result;
|
|
2336
|
+
}
|
|
2337
|
+
const emittedPath = result.remote_snapshot.stdout.trim().split(/\r?\n/).at(-1)?.trim() || remoteSnapshotPath;
|
|
2338
|
+
if (!isSafeRemoteSnapshotPath(emittedPath, context.remoteSnapshotDir)) {
|
|
2339
|
+
result.error = `remote snapshot path refused: ${emittedPath}`;
|
|
2340
|
+
return result;
|
|
2341
|
+
}
|
|
2342
|
+
result.remote_snapshot_path = emittedPath;
|
|
2343
|
+
result.pull = context.runner("scp", [`${machine}:${emittedPath}`, tmpLocal], { timeoutMs: context.timeoutMs });
|
|
2344
|
+
context.runner("ssh", [machine, `rm -f ${shellQuote(emittedPath)}`], { timeoutMs: context.timeoutMs });
|
|
2345
|
+
if (result.pull.code !== 0) {
|
|
2346
|
+
result.error = "snapshot pull failed; no cached DB merged";
|
|
2347
|
+
rmSync(tmpLocal, { force: true });
|
|
2348
|
+
return result;
|
|
2349
|
+
}
|
|
2350
|
+
const integrity = verifySqliteIntegrity(tmpLocal);
|
|
2351
|
+
result.local_integrity = integrity;
|
|
2352
|
+
if (!integrity.ok) {
|
|
2353
|
+
result.error = "pulled snapshot failed local integrity check";
|
|
2354
|
+
rmSync(tmpLocal, { force: true });
|
|
2355
|
+
return result;
|
|
2356
|
+
}
|
|
2357
|
+
renameSync(tmpLocal, verifiedLocal);
|
|
2358
|
+
const db = openDatabase();
|
|
2359
|
+
result.merge = mergePeerDatabase(db, verifiedLocal, { sourceMachine: machine });
|
|
2360
|
+
result.status = "merged";
|
|
2361
|
+
return result;
|
|
2362
|
+
}
|
|
2363
|
+
function remoteSnapshotCommand(remoteDir, filename) {
|
|
2364
|
+
const dirExpr = remoteDir.startsWith("$HOME/") ? `"${remoteDir.replace(/"/g, "\\\"")}"` : shellQuote(remoteDir);
|
|
2365
|
+
const safeFilename = filename.replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
2366
|
+
return [
|
|
2367
|
+
"set -euo pipefail",
|
|
2368
|
+
'db="${HASNA_ECONOMY_DB_PATH:-${ECONOMY_DB:-$HOME/.hasna/economy/economy.db}}"',
|
|
2369
|
+
`snapshot_dir=${dirExpr}`,
|
|
2370
|
+
`out="$snapshot_dir/${safeFilename}"`,
|
|
2371
|
+
'test -f "$db"',
|
|
2372
|
+
'mkdir -p "$snapshot_dir"',
|
|
2373
|
+
'rm -f "$out"',
|
|
2374
|
+
`sqlite3 "$db" "VACUUM INTO '$out'"`,
|
|
2375
|
+
'test "$(sqlite3 "$out" "PRAGMA integrity_check;")" = "ok"',
|
|
2376
|
+
'printf "%s\\n" "$out"'
|
|
2377
|
+
].join("; ");
|
|
2378
|
+
}
|
|
2379
|
+
function isSafeRemoteSnapshotPath(path, remoteSnapshotDir = "$HOME/.hasna/economy/fleet-sync-snapshots") {
|
|
2380
|
+
if (!path || path.includes("\x00") || path.includes(`
|
|
2381
|
+
`) || path.includes("\r"))
|
|
2382
|
+
return false;
|
|
2383
|
+
if (!path.endsWith(".db"))
|
|
2384
|
+
return false;
|
|
2385
|
+
if (path.includes(".."))
|
|
2386
|
+
return false;
|
|
2387
|
+
const prefix = remoteSnapshotDir.replace(/\/$/, "");
|
|
2388
|
+
return path.startsWith(`${prefix}/`) || path.startsWith("$HOME/.hasna/economy/fleet-sync-snapshots/") || path.includes("/.hasna/economy/fleet-sync-snapshots/");
|
|
2389
|
+
}
|
|
2390
|
+
function verifySqliteIntegrity(path) {
|
|
2391
|
+
if (!existsSync4(path))
|
|
2392
|
+
return { ok: false, detail: "file does not exist" };
|
|
2393
|
+
let db;
|
|
2394
|
+
try {
|
|
2395
|
+
db = new Database2(path, { readonly: true });
|
|
2396
|
+
const row = db.query("PRAGMA integrity_check").get();
|
|
2397
|
+
const detail = Object.values(row ?? {})[0] ?? "";
|
|
2398
|
+
return { ok: detail.toLowerCase() === "ok", detail: detail || "no integrity result" };
|
|
2399
|
+
} catch (error) {
|
|
2400
|
+
return { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
2401
|
+
} finally {
|
|
2402
|
+
db?.close();
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
function writeFleetSyncReport(reportDir, result) {
|
|
2406
|
+
mkdirSync3(reportDir, { recursive: true });
|
|
2407
|
+
const path = join3(resolve(reportDir), `${result.generated_at.replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z")}.json`);
|
|
2408
|
+
writeFileSync2(path, `${JSON.stringify(result, null, 2)}
|
|
2409
|
+
`);
|
|
2410
|
+
return path;
|
|
2411
|
+
}
|
|
2412
|
+
function spawnCommand(command, args, options) {
|
|
2413
|
+
const result = spawnSync(command, args, {
|
|
2414
|
+
encoding: "utf8",
|
|
2415
|
+
timeout: options.timeoutMs,
|
|
2416
|
+
maxBuffer: 1024 * 1024,
|
|
2417
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2418
|
+
});
|
|
2419
|
+
return {
|
|
2420
|
+
code: result.status,
|
|
2421
|
+
stdout: result.stdout ?? "",
|
|
2422
|
+
stderr: result.stderr ?? "",
|
|
2423
|
+
...result.error ? { error: result.error.message } : {}
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
function shellQuote(value) {
|
|
2427
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
2428
|
+
}
|
|
2429
|
+
function normalizePositiveInteger(value, fallback) {
|
|
2430
|
+
if (value == null || !Number.isFinite(value) || value < 1)
|
|
2431
|
+
return fallback;
|
|
2432
|
+
return Math.floor(value);
|
|
2433
|
+
}
|
|
2189
2434
|
// src/ingest/claude.ts
|
|
2190
2435
|
init_database();
|
|
2191
2436
|
init_pricing();
|
|
2192
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as
|
|
2193
|
-
import { homedir as
|
|
2194
|
-
import { join as
|
|
2437
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3, existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
2438
|
+
import { homedir as homedir3 } from "os";
|
|
2439
|
+
import { join as join4, basename } from "path";
|
|
2195
2440
|
|
|
2196
2441
|
// src/lib/savings.ts
|
|
2197
2442
|
function defaultCostBasisForAgent(agent) {
|
|
@@ -2338,8 +2583,8 @@ function withAccount(record, account) {
|
|
|
2338
2583
|
function autoDetectProject(cwd, projects) {
|
|
2339
2584
|
return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
|
|
2340
2585
|
}
|
|
2341
|
-
var CLAUDE_PROJECTS_DIR =
|
|
2342
|
-
var TAKUMI_PROJECTS_DIR =
|
|
2586
|
+
var CLAUDE_PROJECTS_DIR = join4(homedir3(), ".claude", "projects");
|
|
2587
|
+
var TAKUMI_PROJECTS_DIR = join4(homedir3(), ".takumi", "projects");
|
|
2343
2588
|
function dirNameToPath(dirName) {
|
|
2344
2589
|
return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
|
|
2345
2590
|
}
|
|
@@ -2349,9 +2594,9 @@ function collectJsonlFiles(projectDir) {
|
|
|
2349
2594
|
try {
|
|
2350
2595
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
2351
2596
|
if (entry.isDirectory())
|
|
2352
|
-
walk(
|
|
2597
|
+
walk(join4(dir, entry.name));
|
|
2353
2598
|
else if (entry.name.endsWith(".jsonl"))
|
|
2354
|
-
files.push(
|
|
2599
|
+
files.push(join4(dir, entry.name));
|
|
2355
2600
|
}
|
|
2356
2601
|
} catch {}
|
|
2357
2602
|
}
|
|
@@ -2365,7 +2610,7 @@ async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_D
|
|
|
2365
2610
|
return ingestJsonlProjects(db, projectsDir, "takumi", verbose);
|
|
2366
2611
|
}
|
|
2367
2612
|
async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false) {
|
|
2368
|
-
if (!
|
|
2613
|
+
if (!existsSync5(projectsDir)) {
|
|
2369
2614
|
if (verbose)
|
|
2370
2615
|
console.log(`${agentName} projects dir not found:`, projectsDir);
|
|
2371
2616
|
return { files: 0, requests: 0, sessions: 0 };
|
|
@@ -2378,7 +2623,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
2378
2623
|
const account = await resolveAccountForAgent(agentName);
|
|
2379
2624
|
const projectDirs = readdirSync2(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
2380
2625
|
for (const projectDirEntry of projectDirs) {
|
|
2381
|
-
const projectDirPath =
|
|
2626
|
+
const projectDirPath = join4(projectsDir, projectDirEntry.name);
|
|
2382
2627
|
const projectPath = dirNameToPath(projectDirEntry.name);
|
|
2383
2628
|
const jsonlFiles = collectJsonlFiles(projectDirPath);
|
|
2384
2629
|
for (const filePath of jsonlFiles) {
|
|
@@ -2513,14 +2758,14 @@ function supportsClaudeDataResidencyPricing(model) {
|
|
|
2513
2758
|
// src/ingest/codex.ts
|
|
2514
2759
|
init_database();
|
|
2515
2760
|
init_pricing();
|
|
2516
|
-
import { existsSync as
|
|
2517
|
-
import { homedir as
|
|
2518
|
-
import { join as
|
|
2761
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
2762
|
+
import { homedir as homedir4 } from "os";
|
|
2763
|
+
import { join as join5, basename as basename2 } from "path";
|
|
2519
2764
|
import { Database as BunDatabase2 } from "bun:sqlite";
|
|
2520
|
-
var DEFAULT_CODEX_DB_PATH =
|
|
2521
|
-
var DEFAULT_CODEX_CONFIG_PATH =
|
|
2522
|
-
var DEFAULT_CODEWITH_DB_PATH =
|
|
2523
|
-
var DEFAULT_CODEWITH_CONFIG_PATH =
|
|
2765
|
+
var DEFAULT_CODEX_DB_PATH = join5(homedir4(), ".codex", "state_5.sqlite");
|
|
2766
|
+
var DEFAULT_CODEX_CONFIG_PATH = join5(homedir4(), ".codex", "config.toml");
|
|
2767
|
+
var DEFAULT_CODEWITH_DB_PATH = join5(homedir4(), ".codewith", "state_5.sqlite");
|
|
2768
|
+
var DEFAULT_CODEWITH_CONFIG_PATH = join5(homedir4(), ".codewith", "config.toml");
|
|
2524
2769
|
var CODEX_INGEST_VERSION = "rollout-aggregate-v3";
|
|
2525
2770
|
function codexDbPath() {
|
|
2526
2771
|
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
|
|
@@ -2562,7 +2807,7 @@ function codexSources() {
|
|
|
2562
2807
|
});
|
|
2563
2808
|
}
|
|
2564
2809
|
function readCodexModel(configPath = codexConfigPath()) {
|
|
2565
|
-
if (!
|
|
2810
|
+
if (!existsSync6(configPath))
|
|
2566
2811
|
return "gpt-5-codex";
|
|
2567
2812
|
try {
|
|
2568
2813
|
const content = readFileSync4(configPath, "utf-8");
|
|
@@ -2603,7 +2848,7 @@ function openCodexDb(dbPath, verbose) {
|
|
|
2603
2848
|
return null;
|
|
2604
2849
|
}
|
|
2605
2850
|
function readTokenEvents(rolloutPath) {
|
|
2606
|
-
if (!rolloutPath || !
|
|
2851
|
+
if (!rolloutPath || !existsSync6(rolloutPath))
|
|
2607
2852
|
return [];
|
|
2608
2853
|
const fallbackUsages = new Map;
|
|
2609
2854
|
let fallbackTimestamp;
|
|
@@ -2684,7 +2929,7 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2684
2929
|
let requests = 0;
|
|
2685
2930
|
const account = await resolveAccountForAgent("codex");
|
|
2686
2931
|
for (const source of codexSources()) {
|
|
2687
|
-
if (!
|
|
2932
|
+
if (!existsSync6(source.dbPath)) {
|
|
2688
2933
|
if (verbose)
|
|
2689
2934
|
console.log(`${source.label} DB not found:`, source.dbPath);
|
|
2690
2935
|
continue;
|
|
@@ -2764,11 +3009,11 @@ async function ingestCodex(db, verbose = false) {
|
|
|
2764
3009
|
// src/ingest/gemini.ts
|
|
2765
3010
|
init_database();
|
|
2766
3011
|
init_pricing();
|
|
2767
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync5, existsSync as
|
|
2768
|
-
import { homedir as
|
|
2769
|
-
import { join as
|
|
2770
|
-
var DEFAULT_GEMINI_TMP_DIR =
|
|
2771
|
-
var DEFAULT_GEMINI_HISTORY_DIR =
|
|
3012
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
3013
|
+
import { homedir as homedir5 } from "os";
|
|
3014
|
+
import { join as join6, basename as basename3 } from "path";
|
|
3015
|
+
var DEFAULT_GEMINI_TMP_DIR = join6(homedir5(), ".gemini", "tmp");
|
|
3016
|
+
var DEFAULT_GEMINI_HISTORY_DIR = join6(homedir5(), ".gemini", "history");
|
|
2772
3017
|
function geminiTmpDir() {
|
|
2773
3018
|
return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
|
|
2774
3019
|
}
|
|
@@ -2785,12 +3030,12 @@ function numberField(...values) {
|
|
|
2785
3030
|
function listProjectDirs(...roots) {
|
|
2786
3031
|
const dirs = new Set;
|
|
2787
3032
|
for (const root of roots) {
|
|
2788
|
-
if (!
|
|
3033
|
+
if (!existsSync7(root))
|
|
2789
3034
|
continue;
|
|
2790
3035
|
try {
|
|
2791
3036
|
for (const entry of readdirSync3(root, { withFileTypes: true })) {
|
|
2792
3037
|
if (entry.isDirectory())
|
|
2793
|
-
dirs.add(
|
|
3038
|
+
dirs.add(join6(root, entry.name));
|
|
2794
3039
|
}
|
|
2795
3040
|
} catch {}
|
|
2796
3041
|
}
|
|
@@ -2801,9 +3046,9 @@ function projectRoot(projectDir, chatData) {
|
|
|
2801
3046
|
return chatData.projectPath;
|
|
2802
3047
|
if (chatData.project_path)
|
|
2803
3048
|
return chatData.project_path;
|
|
2804
|
-
const rootFile =
|
|
3049
|
+
const rootFile = join6(projectDir, ".project_root");
|
|
2805
3050
|
try {
|
|
2806
|
-
if (
|
|
3051
|
+
if (existsSync7(rootFile))
|
|
2807
3052
|
return readFileSync5(rootFile, "utf-8").trim();
|
|
2808
3053
|
} catch {}
|
|
2809
3054
|
return "";
|
|
@@ -2811,7 +3056,7 @@ function projectRoot(projectDir, chatData) {
|
|
|
2811
3056
|
async function ingestGemini(db, verbose) {
|
|
2812
3057
|
const tmpDir = geminiTmpDir();
|
|
2813
3058
|
const historyDir = geminiHistoryDir();
|
|
2814
|
-
if (!
|
|
3059
|
+
if (!existsSync7(tmpDir) && !existsSync7(historyDir)) {
|
|
2815
3060
|
if (verbose)
|
|
2816
3061
|
console.log("Gemini tmp/history dirs not found:", tmpDir, historyDir);
|
|
2817
3062
|
return { sessions: 0, requests: 0 };
|
|
@@ -2823,17 +3068,17 @@ async function ingestGemini(db, verbose) {
|
|
|
2823
3068
|
const account = await resolveAccountForAgent("gemini");
|
|
2824
3069
|
const projectDirs = listProjectDirs(tmpDir, historyDir);
|
|
2825
3070
|
for (const projectDir of projectDirs) {
|
|
2826
|
-
const chatsDir =
|
|
2827
|
-
if (!
|
|
3071
|
+
const chatsDir = join6(projectDir, "chats");
|
|
3072
|
+
if (!existsSync7(chatsDir))
|
|
2828
3073
|
continue;
|
|
2829
3074
|
let chatFiles = [];
|
|
2830
3075
|
try {
|
|
2831
|
-
chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) =>
|
|
3076
|
+
chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join6(chatsDir, f));
|
|
2832
3077
|
} catch {
|
|
2833
3078
|
continue;
|
|
2834
3079
|
}
|
|
2835
3080
|
for (const filePath of chatFiles) {
|
|
2836
|
-
const stateKey = filePath.replace(
|
|
3081
|
+
const stateKey = filePath.replace(homedir5(), "~");
|
|
2837
3082
|
let fileMtime = "0";
|
|
2838
3083
|
try {
|
|
2839
3084
|
fileMtime = statSync3(filePath).mtimeMs.toString();
|
|
@@ -2920,6 +3165,8 @@ async function ingestGemini(db, verbose) {
|
|
|
2920
3165
|
return { sessions: totalSessions, requests: totalRequests };
|
|
2921
3166
|
}
|
|
2922
3167
|
export {
|
|
3168
|
+
writeFleetSyncReport,
|
|
3169
|
+
verifySqliteIntegrity,
|
|
2923
3170
|
upsertUsageSnapshot,
|
|
2924
3171
|
upsertSubscription,
|
|
2925
3172
|
upsertSession,
|
|
@@ -2933,8 +3180,10 @@ export {
|
|
|
2933
3180
|
setIngestState,
|
|
2934
3181
|
setActiveModel,
|
|
2935
3182
|
seedModelPricing,
|
|
3183
|
+
runEconomyFleetSync,
|
|
2936
3184
|
rollupSession,
|
|
2937
3185
|
readCodexModel,
|
|
3186
|
+
queryZeroCostTokenizedModels,
|
|
2938
3187
|
queryUsageSnapshots,
|
|
2939
3188
|
queryTopSessions,
|
|
2940
3189
|
querySummary,
|
|
@@ -2957,6 +3206,7 @@ export {
|
|
|
2957
3206
|
listMachineRegistry,
|
|
2958
3207
|
listGoals,
|
|
2959
3208
|
listBudgets,
|
|
3209
|
+
isSafeRemoteSnapshotPath,
|
|
2960
3210
|
isAgent,
|
|
2961
3211
|
ingestTakumi,
|
|
2962
3212
|
ingestJsonlProjects,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type PeerMergeResult } from './peer-sync.js';
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
code: number | null;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export type FleetCommandRunner = (command: string, args: string[], options: {
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
}) => CommandResult;
|
|
11
|
+
export interface EconomyFleetSyncOptions {
|
|
12
|
+
machines?: string[];
|
|
13
|
+
localSync?: boolean;
|
|
14
|
+
remoteSync?: boolean;
|
|
15
|
+
cacheDir?: string;
|
|
16
|
+
reportDir?: string;
|
|
17
|
+
remoteSnapshotDir?: string;
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
runner?: FleetCommandRunner;
|
|
21
|
+
now?: Date;
|
|
22
|
+
}
|
|
23
|
+
export interface EconomyFleetSyncMachineResult {
|
|
24
|
+
machine: string;
|
|
25
|
+
status: 'merged' | 'failed' | 'dry_run';
|
|
26
|
+
remote_sync?: CommandResult;
|
|
27
|
+
remote_snapshot?: CommandResult;
|
|
28
|
+
remote_snapshot_path?: string;
|
|
29
|
+
pull?: CommandResult;
|
|
30
|
+
local_integrity?: {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
detail: string;
|
|
33
|
+
};
|
|
34
|
+
merge?: PeerMergeResult;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface EconomyFleetSyncResult {
|
|
38
|
+
schema: 'open-economy.fleet-sync.v1';
|
|
39
|
+
generated_at: string;
|
|
40
|
+
dry_run: boolean;
|
|
41
|
+
local: {
|
|
42
|
+
status: 'synced' | 'failed' | 'skipped' | 'dry_run';
|
|
43
|
+
sync?: CommandResult;
|
|
44
|
+
db_path: string;
|
|
45
|
+
};
|
|
46
|
+
remote: EconomyFleetSyncMachineResult[];
|
|
47
|
+
summary: {
|
|
48
|
+
machines: number;
|
|
49
|
+
merged: number;
|
|
50
|
+
failed: number;
|
|
51
|
+
dry_run: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export declare function runEconomyFleetSync(options?: EconomyFleetSyncOptions): EconomyFleetSyncResult;
|
|
55
|
+
export declare function isSafeRemoteSnapshotPath(path: string, remoteSnapshotDir?: string): boolean;
|
|
56
|
+
export declare function verifySqliteIntegrity(path: string): {
|
|
57
|
+
ok: boolean;
|
|
58
|
+
detail: string;
|
|
59
|
+
};
|
|
60
|
+
export declare function writeFleetSyncReport(reportDir: string, result: EconomyFleetSyncResult): string;
|
|
61
|
+
//# sourceMappingURL=fleet-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fleet-sync.d.ts","sourceRoot":"","sources":["../../src/lib/fleet-sync.ts"],"names":[],"mappings":"AAMA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAExE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,KAC3B,aAAa,CAAA;AAElB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,GAAG,CAAC,EAAE,IAAI,CAAA;CACX;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAA;IACvC,WAAW,CAAC,EAAE,aAAa,CAAA;IAC3B,eAAe,CAAC,EAAE,aAAa,CAAA;IAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,eAAe,CAAC,EAAE;QAChB,EAAE,EAAE,OAAO,CAAA;QACX,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;IACD,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,4BAA4B,CAAA;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE;QACL,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAA;QACnD,IAAI,CAAC,EAAE,aAAa,CAAA;QACpB,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;IACD,MAAM,EAAE,6BAA6B,EAAE,CAAA;IACvC,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;KAChB,CAAA;CACF;AAKD,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,uBAA4B,GAAG,sBAAsB,CAgDjG;AAiGD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,SAA8C,GAAG,OAAO,CAQ/H;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAanF;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAK9F"}
|