devrage 0.5.5 → 0.5.7

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.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/commands/scan.ts
4
4
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
5
- import { dirname as dirname2, join as join10 } from "node:path";
5
+ import { dirname as dirname2, join as join11 } from "node:path";
6
6
  import { pathToFileURL } from "node:url";
7
7
 
8
8
  // src/adapters/amp.ts
@@ -825,6 +825,97 @@ import { existsSync as existsSync2 } from "node:fs";
825
825
  import { readdir as readdir5 } from "node:fs/promises";
826
826
  import { homedir as homedir5 } from "node:os";
827
827
  import { join as join5 } from "node:path";
828
+
829
+ // src/adapters/sqlite.ts
830
+ async function openReadonlySqliteDatabase(dbPath) {
831
+ const requestedDriver = process.env["DEVRAGE_SQLITE_DRIVER"];
832
+ if (requestedDriver) {
833
+ const loader = driverLoader(requestedDriver);
834
+ if (!loader) {
835
+ return null;
836
+ }
837
+ try {
838
+ return await loader(dbPath);
839
+ } catch {
840
+ return null;
841
+ }
842
+ }
843
+ const loaders = isBunRuntime() ? [openWithBunSqlite, openWithNodeSqlite, openWithBetterSqlite3] : [openWithNodeSqlite, openWithBetterSqlite3];
844
+ for (const loader of loaders) {
845
+ try {
846
+ return await loader(dbPath);
847
+ } catch {
848
+ continue;
849
+ }
850
+ }
851
+ return null;
852
+ }
853
+ function driverLoader(driver) {
854
+ switch (driver) {
855
+ case "bun":
856
+ case "bun:sqlite":
857
+ return openWithBunSqlite;
858
+ case "node":
859
+ case "node:sqlite":
860
+ return openWithNodeSqlite;
861
+ case "better-sqlite3":
862
+ return openWithBetterSqlite3;
863
+ default:
864
+ return null;
865
+ }
866
+ }
867
+ function isBunRuntime() {
868
+ return Boolean(process.versions["bun"]);
869
+ }
870
+ async function openWithBunSqlite(dbPath) {
871
+ const specifier = "bun:sqlite";
872
+ const sqlite = await import(specifier);
873
+ const db = new sqlite.Database(dbPath, { readonly: true, create: false });
874
+ return {
875
+ prepare(sql) {
876
+ const prepare = db.prepare ?? db.query;
877
+ return prepare.call(db, sql);
878
+ },
879
+ close() {
880
+ db.close();
881
+ }
882
+ };
883
+ }
884
+ async function openWithNodeSqlite(dbPath) {
885
+ const sqlite = await importNodeSqlite();
886
+ return new sqlite.DatabaseSync(dbPath, { open: true, readOnly: true, timeout: 5e3 });
887
+ }
888
+ async function openWithBetterSqlite3(dbPath) {
889
+ const BetterSqlite3 = await import("better-sqlite3");
890
+ const Ctor = BetterSqlite3.default ?? BetterSqlite3;
891
+ return new Ctor(dbPath, {
892
+ readonly: true,
893
+ fileMustExist: true
894
+ });
895
+ }
896
+ async function importNodeSqlite() {
897
+ const originalEmitWarning = process.emitWarning;
898
+ process.emitWarning = ((warning, ...args) => {
899
+ const message = typeof warning === "string" ? warning : warning.message;
900
+ const type = typeof args[0] === "string" ? args[0] : void 0;
901
+ if (message === "SQLite is an experimental feature and might change at any time" && type === "ExperimentalWarning") {
902
+ return;
903
+ }
904
+ originalEmitWarning(warning, ...args);
905
+ });
906
+ try {
907
+ const specifier = "node:sqlite";
908
+ const sqlite = await import(specifier);
909
+ if (typeof sqlite.DatabaseSync !== "function") {
910
+ throw new Error("node:sqlite DatabaseSync is unavailable");
911
+ }
912
+ return sqlite;
913
+ } finally {
914
+ process.emitWarning = originalEmitWarning;
915
+ }
916
+ }
917
+
918
+ // src/adapters/cursor.ts
828
919
  var CANDIDATE_KEY_PREFIXES = [
829
920
  "bubbleId:",
830
921
  "composerData:",
@@ -978,16 +1069,7 @@ async function* parseCursorUsageStore(store, options) {
978
1069
  }
979
1070
  }
980
1071
  async function openCursorDb(dbPath) {
981
- try {
982
- const BetterSqlite3 = await import("better-sqlite3");
983
- const Ctor = BetterSqlite3.default ?? BetterSqlite3;
984
- return new Ctor(
985
- dbPath,
986
- { readonly: true }
987
- );
988
- } catch {
989
- return null;
990
- }
1072
+ return openReadonlySqliteDatabase(dbPath);
991
1073
  }
992
1074
  function readStateRows(db) {
993
1075
  const rows = [];
@@ -1048,6 +1130,12 @@ function decodeStateValue(value) {
1048
1130
  if (Buffer.isBuffer(value)) {
1049
1131
  return value.toString("utf-8");
1050
1132
  }
1133
+ if (value instanceof Uint8Array) {
1134
+ return Buffer.from(value).toString("utf-8");
1135
+ }
1136
+ if (ArrayBuffer.isView(value)) {
1137
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("utf-8");
1138
+ }
1051
1139
  return null;
1052
1140
  }
1053
1141
  function extractCursorMessages(root, rowKey) {
@@ -1410,17 +1498,11 @@ async function openOpencodeDb() {
1410
1498
  if (!dbPath) {
1411
1499
  return null;
1412
1500
  }
1413
- try {
1414
- const BetterSqlite3 = await import("better-sqlite3");
1415
- const Ctor = BetterSqlite3.default ?? BetterSqlite3;
1416
- return new Ctor(
1417
- dbPath,
1418
- { readonly: true }
1419
- );
1420
- } catch {
1421
- console.warn("devrage: better-sqlite3 not available, skipping OpenCode sessions");
1422
- return null;
1501
+ const db = await openReadonlySqliteDatabase(dbPath);
1502
+ if (!db) {
1503
+ console.warn("devrage: SQLite support not available, skipping OpenCode sessions");
1423
1504
  }
1505
+ return db;
1424
1506
  }
1425
1507
  function* queryUserMessages(db, options) {
1426
1508
  let query = `
@@ -1689,23 +1771,435 @@ function asRecord5(value) {
1689
1771
  return value;
1690
1772
  }
1691
1773
 
1692
- // src/adapters/zed.ts
1693
- import { readdir as readdir7, readFile as readFile3 } from "node:fs/promises";
1774
+ // src/adapters/t3code.ts
1694
1775
  import { existsSync as existsSync4 } from "node:fs";
1695
1776
  import { homedir as homedir8 } from "node:os";
1696
- import { join as join8 } from "node:path";
1777
+ import { isAbsolute, join as join8, resolve } from "node:path";
1778
+ function t3codeAdapter() {
1779
+ return {
1780
+ name: "t3code",
1781
+ async *messages(options) {
1782
+ for (const location of discoverT3Databases()) {
1783
+ const db = await openT3Db(location.path);
1784
+ if (!db) {
1785
+ continue;
1786
+ }
1787
+ try {
1788
+ yield* queryUserMessages2(db, location, options);
1789
+ } finally {
1790
+ db.close();
1791
+ }
1792
+ }
1793
+ },
1794
+ async *usage(options) {
1795
+ const seen = /* @__PURE__ */ new Set();
1796
+ for (const location of discoverT3Databases()) {
1797
+ const db = await openT3Db(location.path);
1798
+ if (!db) {
1799
+ continue;
1800
+ }
1801
+ try {
1802
+ yield* queryUsageRecords2(db, location, seen, options);
1803
+ } finally {
1804
+ db.close();
1805
+ }
1806
+ }
1807
+ }
1808
+ };
1809
+ }
1810
+ function discoverT3Databases() {
1811
+ const locations = [];
1812
+ const seen = /* @__PURE__ */ new Set();
1813
+ const stateDir = stringValue6(process.env["T3CODE_STATE_DIR"]);
1814
+ if (stateDir) {
1815
+ addLocation(locations, seen, join8(resolveHomePath(stateDir), "state.sqlite"), "state");
1816
+ }
1817
+ for (const baseDir of uniqueStrings2([
1818
+ stringValue6(process.env["T3CODE_HOME"]),
1819
+ join8(homedir8(), ".t3")
1820
+ ])) {
1821
+ addLocation(
1822
+ locations,
1823
+ seen,
1824
+ join8(resolveHomePath(baseDir), "userdata", "state.sqlite"),
1825
+ "userdata"
1826
+ );
1827
+ addLocation(locations, seen, join8(resolveHomePath(baseDir), "dev", "state.sqlite"), "dev");
1828
+ }
1829
+ return locations;
1830
+ }
1831
+ function addLocation(locations, seen, dbPath, scope) {
1832
+ if (seen.has(dbPath) || !existsSync4(dbPath)) {
1833
+ return;
1834
+ }
1835
+ seen.add(dbPath);
1836
+ locations.push({ path: dbPath, scope });
1837
+ }
1838
+ function resolveHomePath(value) {
1839
+ if (value === "~") {
1840
+ return homedir8();
1841
+ }
1842
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
1843
+ return join8(homedir8(), value.slice(2));
1844
+ }
1845
+ return isAbsolute(value) ? value : resolve(value);
1846
+ }
1847
+ async function openT3Db(dbPath) {
1848
+ return openReadonlySqliteDatabase(dbPath);
1849
+ }
1850
+ function* queryUserMessages2(db, location, options) {
1851
+ if (!hasColumns(db, "projection_thread_messages", ["thread_id", "role", "text", "created_at"])) {
1852
+ return;
1853
+ }
1854
+ const orderColumn = hasColumns(db, "projection_thread_messages", ["message_id"]) ? "message_id" : "created_at";
1855
+ let query = `
1856
+ SELECT thread_id, created_at, text
1857
+ FROM projection_thread_messages
1858
+ WHERE role = 'user'
1859
+ `;
1860
+ const params = [];
1861
+ if (options?.since) {
1862
+ query += ` AND created_at >= ?`;
1863
+ params.push(options.since.toISOString());
1864
+ }
1865
+ query += ` ORDER BY created_at ASC, ${orderColumn} ASC`;
1866
+ let rows;
1867
+ try {
1868
+ rows = db.prepare(query).all(...params);
1869
+ } catch {
1870
+ return;
1871
+ }
1872
+ for (const row of rows) {
1873
+ const text = stringValue6(row.text);
1874
+ if (!text) {
1875
+ continue;
1876
+ }
1877
+ yield {
1878
+ text,
1879
+ timestamp: stringValue6(row.created_at),
1880
+ session: stringValue6(row.thread_id),
1881
+ project: location.scope
1882
+ };
1883
+ }
1884
+ }
1885
+ function* queryUsageRecords2(db, location, seen, options) {
1886
+ if (!hasColumns(db, "orchestration_events", [
1887
+ "event_id",
1888
+ "stream_id",
1889
+ "event_type",
1890
+ "occurred_at",
1891
+ "payload_json"
1892
+ ])) {
1893
+ return;
1894
+ }
1895
+ const threadInfo = readThreadInfo(db);
1896
+ const orderColumn = hasColumns(db, "orchestration_events", ["sequence"]) ? "sequence" : "event_id";
1897
+ let query = `
1898
+ SELECT event_id, stream_id, occurred_at, payload_json
1899
+ FROM orchestration_events
1900
+ WHERE event_type = 'thread.activity-appended'
1901
+ `;
1902
+ const params = [];
1903
+ if (options?.since) {
1904
+ query += ` AND occurred_at >= ?`;
1905
+ params.push(options.since.toISOString());
1906
+ }
1907
+ query += ` ORDER BY occurred_at ASC, ${orderColumn} ASC`;
1908
+ let rows;
1909
+ try {
1910
+ rows = db.prepare(query).all(...params);
1911
+ } catch {
1912
+ return;
1913
+ }
1914
+ for (const row of rows) {
1915
+ const payload = asRecord6(parseJson(row.payload_json));
1916
+ const activity = asRecord6(payload?.["activity"]);
1917
+ if (activity?.["kind"] !== "context-window.updated") {
1918
+ continue;
1919
+ }
1920
+ const usage2 = parseUsageSnapshot(activity["payload"]);
1921
+ if (!usage2 || !hasBillableUsage2(usage2)) {
1922
+ continue;
1923
+ }
1924
+ const threadId = stringValue6(payload?.["threadId"]) ?? stringValue6(row.stream_id);
1925
+ const timestamp = stringValue6(activity["createdAt"]) ?? stringValue6(row.occurred_at);
1926
+ const turnId = stringValue6(activity["turnId"]);
1927
+ const info = threadId ? threadInfo.get(threadId) : void 0;
1928
+ const provider = normalizeT3Provider(info?.provider, info?.model);
1929
+ const dedupeKey = t3UsageDedupeKey({
1930
+ scope: location.scope,
1931
+ threadId,
1932
+ turnId,
1933
+ provider,
1934
+ model: info?.model,
1935
+ usage: usage2
1936
+ });
1937
+ if (seen.has(dedupeKey)) {
1938
+ continue;
1939
+ }
1940
+ seen.add(dedupeKey);
1941
+ yield {
1942
+ agent: "t3code",
1943
+ provider,
1944
+ model: info?.model,
1945
+ timestamp,
1946
+ session: threadId,
1947
+ inputTokens: usage2.inputTokens,
1948
+ outputTokens: usage2.outputTokens,
1949
+ reasoningTokens: usage2.reasoningTokens,
1950
+ cacheReadTokens: usage2.cacheReadTokens,
1951
+ cacheWriteTokens: usage2.cacheWriteTokens
1952
+ };
1953
+ }
1954
+ }
1955
+ function normalizeT3Provider(provider, model) {
1956
+ const normalized = provider?.trim().toLowerCase();
1957
+ const key = normalized?.replace(/[^a-z0-9]/g, "");
1958
+ switch (key) {
1959
+ case "codex":
1960
+ return "openai";
1961
+ case "claudeagent":
1962
+ case "claudecode":
1963
+ return "anthropic";
1964
+ case "cursor":
1965
+ case "opencode":
1966
+ return void 0;
1967
+ default:
1968
+ if (key?.includes("codex")) {
1969
+ return "openai";
1970
+ }
1971
+ if (key?.includes("claude")) {
1972
+ return "anthropic";
1973
+ }
1974
+ if (normalized === "openai" || normalized === "anthropic") {
1975
+ return normalized;
1976
+ }
1977
+ return providerFromModel(model);
1978
+ }
1979
+ }
1980
+ function providerFromModel(model) {
1981
+ const slash = model?.indexOf("/") ?? -1;
1982
+ if (!model || slash <= 0) {
1983
+ return void 0;
1984
+ }
1985
+ return model.slice(0, slash);
1986
+ }
1987
+ function readThreadInfo(db) {
1988
+ const info = /* @__PURE__ */ new Map();
1989
+ readProjectionThreadModels(db, info);
1990
+ readProjectionThreadProviders(db, info);
1991
+ return info;
1992
+ }
1993
+ function readProjectionThreadModels(db, info) {
1994
+ if (!tableExists(db, "projection_threads")) {
1995
+ return;
1996
+ }
1997
+ const columns = tableColumns(db, "projection_threads");
1998
+ if (!columns.has("thread_id")) {
1999
+ return;
2000
+ }
2001
+ try {
2002
+ if (columns.has("model_selection_json")) {
2003
+ const rows = db.prepare("SELECT thread_id, model_selection_json FROM projection_threads").all();
2004
+ for (const row of rows) {
2005
+ const threadId = stringValue6(row.thread_id);
2006
+ const modelSelection = asRecord6(parseJson(row.model_selection_json));
2007
+ if (!threadId || !modelSelection) {
2008
+ continue;
2009
+ }
2010
+ const entry = info.get(threadId) ?? {};
2011
+ entry.model = stringValue6(modelSelection["model"]) ?? entry.model;
2012
+ entry.provider = stringValue6(modelSelection["provider"]) ?? stringValue6(modelSelection["instanceId"]) ?? entry.provider;
2013
+ info.set(threadId, entry);
2014
+ }
2015
+ return;
2016
+ }
2017
+ if (columns.has("model")) {
2018
+ const rows = db.prepare("SELECT thread_id, model FROM projection_threads").all();
2019
+ for (const row of rows) {
2020
+ const threadId = stringValue6(row.thread_id);
2021
+ const model = stringValue6(row.model);
2022
+ if (threadId && model) {
2023
+ info.set(threadId, { ...info.get(threadId), model });
2024
+ }
2025
+ }
2026
+ }
2027
+ } catch {
2028
+ return;
2029
+ }
2030
+ }
2031
+ function readProjectionThreadProviders(db, info) {
2032
+ if (!hasColumns(db, "projection_thread_sessions", ["thread_id", "provider_name"])) {
2033
+ return;
2034
+ }
2035
+ try {
2036
+ const rows = db.prepare("SELECT thread_id, provider_name FROM projection_thread_sessions").all();
2037
+ for (const row of rows) {
2038
+ const threadId = stringValue6(row.thread_id);
2039
+ const provider = stringValue6(row.provider_name);
2040
+ if (!threadId || !provider) {
2041
+ continue;
2042
+ }
2043
+ info.set(threadId, { ...info.get(threadId), provider });
2044
+ }
2045
+ } catch {
2046
+ return;
2047
+ }
2048
+ }
2049
+ function parseUsageSnapshot(value) {
2050
+ const usage2 = asRecord6(value);
2051
+ if (!usage2) {
2052
+ return null;
2053
+ }
2054
+ const lastInputTokens = tokenValue(usage2["lastInputTokens"] ?? usage2["last_input_tokens"]);
2055
+ const lastCachedInputTokens = tokenValue(
2056
+ usage2["lastCachedInputTokens"] ?? usage2["last_cached_input_tokens"]
2057
+ );
2058
+ const lastOutputTokens = tokenValue(usage2["lastOutputTokens"] ?? usage2["last_output_tokens"]);
2059
+ const lastReasoningOutputTokens = tokenValue(
2060
+ usage2["lastReasoningOutputTokens"] ?? usage2["last_reasoning_output_tokens"]
2061
+ );
2062
+ const hasLastDetails = [
2063
+ lastInputTokens,
2064
+ lastCachedInputTokens,
2065
+ lastOutputTokens,
2066
+ lastReasoningOutputTokens
2067
+ ].some((token) => token !== void 0);
2068
+ if (hasLastDetails) {
2069
+ return splitTokenUsage({
2070
+ inputTokens: lastInputTokens ?? 0,
2071
+ cachedInputTokens: lastCachedInputTokens ?? 0,
2072
+ outputTokens: lastOutputTokens ?? 0,
2073
+ reasoningOutputTokens: lastReasoningOutputTokens ?? 0
2074
+ });
2075
+ }
2076
+ const inputTokens = tokenValue(usage2["inputTokens"] ?? usage2["input_tokens"]);
2077
+ const cachedInputTokens = tokenValue(usage2["cachedInputTokens"] ?? usage2["cached_input_tokens"]);
2078
+ const outputTokens = tokenValue(usage2["outputTokens"] ?? usage2["output_tokens"]);
2079
+ const reasoningOutputTokens = tokenValue(
2080
+ usage2["reasoningOutputTokens"] ?? usage2["reasoning_output_tokens"]
2081
+ );
2082
+ const hasSnapshotDetails = [
2083
+ inputTokens,
2084
+ cachedInputTokens,
2085
+ outputTokens,
2086
+ reasoningOutputTokens
2087
+ ].some((token) => token !== void 0);
2088
+ if (!hasSnapshotDetails) {
2089
+ return null;
2090
+ }
2091
+ return splitTokenUsage({
2092
+ inputTokens: inputTokens ?? 0,
2093
+ cachedInputTokens: cachedInputTokens ?? 0,
2094
+ outputTokens: outputTokens ?? 0,
2095
+ reasoningOutputTokens: reasoningOutputTokens ?? 0
2096
+ });
2097
+ }
2098
+ function splitTokenUsage(input) {
2099
+ const reasoningTokens = Math.min(input.reasoningOutputTokens, input.outputTokens);
2100
+ return {
2101
+ inputTokens: Math.max(input.inputTokens - input.cachedInputTokens, 0),
2102
+ outputTokens: Math.max(input.outputTokens - reasoningTokens, 0),
2103
+ reasoningTokens,
2104
+ cacheReadTokens: input.cachedInputTokens,
2105
+ cacheWriteTokens: 0
2106
+ };
2107
+ }
2108
+ function hasBillableUsage2(usage2) {
2109
+ return usage2.inputTokens + usage2.outputTokens + usage2.reasoningTokens + usage2.cacheReadTokens + usage2.cacheWriteTokens > 0;
2110
+ }
2111
+ function t3UsageDedupeKey(input) {
2112
+ return JSON.stringify([
2113
+ input.scope,
2114
+ input.threadId ?? "",
2115
+ input.turnId ?? "",
2116
+ input.provider ?? "",
2117
+ input.model ?? "",
2118
+ input.usage.inputTokens,
2119
+ input.usage.outputTokens,
2120
+ input.usage.reasoningTokens,
2121
+ input.usage.cacheReadTokens,
2122
+ input.usage.cacheWriteTokens
2123
+ ]);
2124
+ }
2125
+ function hasColumns(db, table, requiredColumns) {
2126
+ const columns = tableColumns(db, table);
2127
+ return requiredColumns.every((column) => columns.has(column));
2128
+ }
2129
+ function tableExists(db, table) {
2130
+ try {
2131
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1").get(table);
2132
+ return Boolean(row);
2133
+ } catch {
2134
+ return false;
2135
+ }
2136
+ }
2137
+ function tableColumns(db, table) {
2138
+ if (!tableExists(db, table)) {
2139
+ return /* @__PURE__ */ new Set();
2140
+ }
2141
+ try {
2142
+ const rows = db.prepare(`PRAGMA table_info("${table}")`).all();
2143
+ return new Set(rows.flatMap((row) => stringValue6(row.name) ?? []));
2144
+ } catch {
2145
+ return /* @__PURE__ */ new Set();
2146
+ }
2147
+ }
2148
+ function parseJson(value) {
2149
+ if (typeof value !== "string") {
2150
+ return null;
2151
+ }
2152
+ try {
2153
+ return JSON.parse(value);
2154
+ } catch {
2155
+ return null;
2156
+ }
2157
+ }
2158
+ function tokenValue(value) {
2159
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2160
+ return void 0;
2161
+ }
2162
+ return Math.max(Math.round(value), 0);
2163
+ }
2164
+ function uniqueStrings2(values) {
2165
+ const seen = /* @__PURE__ */ new Set();
2166
+ const unique = [];
2167
+ for (const value of values) {
2168
+ if (!value || seen.has(value)) {
2169
+ continue;
2170
+ }
2171
+ seen.add(value);
2172
+ unique.push(value);
2173
+ }
2174
+ return unique;
2175
+ }
2176
+ function stringValue6(value) {
2177
+ return typeof value === "string" && value.trim() ? value : void 0;
2178
+ }
2179
+ function asRecord6(value) {
2180
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2181
+ return null;
2182
+ }
2183
+ return value;
2184
+ }
2185
+
2186
+ // src/adapters/zed.ts
2187
+ import { readdir as readdir7, readFile as readFile3 } from "node:fs/promises";
2188
+ import { existsSync as existsSync5 } from "node:fs";
2189
+ import { homedir as homedir9 } from "node:os";
2190
+ import { join as join9 } from "node:path";
1697
2191
  function getZedPaths() {
1698
2192
  if (process.platform === "darwin") {
1699
- const base2 = join8(homedir8(), "Library", "Application Support", "Zed");
2193
+ const base2 = join9(homedir9(), "Library", "Application Support", "Zed");
1700
2194
  return {
1701
- conversations: join8(base2, "conversations"),
1702
- db: join8(base2, "db")
2195
+ conversations: join9(base2, "conversations"),
2196
+ db: join9(base2, "db")
1703
2197
  };
1704
2198
  }
1705
- const base = join8(process.env["XDG_DATA_HOME"] ?? join8(homedir8(), ".local", "share"), "zed");
2199
+ const base = join9(process.env["XDG_DATA_HOME"] ?? join9(homedir9(), ".local", "share"), "zed");
1706
2200
  return {
1707
- conversations: join8(base, "conversations"),
1708
- db: join8(base, "db")
2201
+ conversations: join9(base, "conversations"),
2202
+ db: join9(base, "db")
1709
2203
  };
1710
2204
  }
1711
2205
  function zedAdapter() {
@@ -1719,7 +2213,7 @@ function zedAdapter() {
1719
2213
  };
1720
2214
  }
1721
2215
  async function* parseTextThreads(dir, _options) {
1722
- if (!existsSync4(dir)) {
2216
+ if (!existsSync5(dir)) {
1723
2217
  return;
1724
2218
  }
1725
2219
  let files;
@@ -1730,7 +2224,7 @@ async function* parseTextThreads(dir, _options) {
1730
2224
  }
1731
2225
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
1732
2226
  for (const file of jsonFiles) {
1733
- const filePath = join8(dir, file);
2227
+ const filePath = join9(dir, file);
1734
2228
  const session = file.replace(".json", "");
1735
2229
  try {
1736
2230
  const raw = await readFile3(filePath, "utf-8");
@@ -1756,7 +2250,7 @@ async function* parseTextThreads(dir, _options) {
1756
2250
  }
1757
2251
  }
1758
2252
  async function* parseAgentThreads(dbDir, _options) {
1759
- if (!existsSync4(dbDir)) {
2253
+ if (!existsSync5(dbDir)) {
1760
2254
  return;
1761
2255
  }
1762
2256
  let dbFiles;
@@ -1769,21 +2263,10 @@ async function* parseAgentThreads(dbDir, _options) {
1769
2263
  if (dbFiles.length === 0) {
1770
2264
  return;
1771
2265
  }
1772
- let Database;
1773
- try {
1774
- const mod = await import("better-sqlite3");
1775
- Database = mod.default ?? mod;
1776
- } catch {
1777
- return;
1778
- }
1779
2266
  for (const dbFile of dbFiles) {
1780
- const dbPath = join8(dbDir, dbFile);
1781
- let db;
1782
- try {
1783
- db = new Database(dbPath, {
1784
- readonly: true
1785
- });
1786
- } catch {
2267
+ const dbPath = join9(dbDir, dbFile);
2268
+ const db = await openReadonlySqliteDatabase(dbPath);
2269
+ if (!db) {
1787
2270
  continue;
1788
2271
  }
1789
2272
  try {
@@ -1793,14 +2276,12 @@ async function* parseAgentThreads(dbDir, _options) {
1793
2276
  (t) => t === "messages" || t === "thread_messages" || t.includes("message")
1794
2277
  );
1795
2278
  if (!msgTable) {
1796
- db.close();
1797
2279
  continue;
1798
2280
  }
1799
2281
  const columns = db.prepare(`PRAGMA table_info("${msgTable}")`).all();
1800
2282
  const colNames = columns.map((c2) => c2.name);
1801
2283
  const hasRole = colNames.includes("role");
1802
2284
  if (!hasRole) {
1803
- db.close();
1804
2285
  continue;
1805
2286
  }
1806
2287
  const contentCol = colNames.includes("content") ? "content" : colNames.includes("body") ? "body" : "text";
@@ -1828,6 +2309,7 @@ var ADAPTERS = {
1828
2309
  amp: ampAdapter,
1829
2310
  cline: clineAdapter,
1830
2311
  pi: piAdapter,
2312
+ t3code: t3codeAdapter,
1831
2313
  zed: zedAdapter
1832
2314
  };
1833
2315
  function createAdapter(name) {
@@ -1997,8 +2479,8 @@ function runPattern(_originalText, searchText, matches, seen) {
1997
2479
 
1998
2480
  // src/pricing/index.ts
1999
2481
  import { mkdir, readFile as readFile4, writeFile } from "node:fs/promises";
2000
- import { homedir as homedir9 } from "node:os";
2001
- import { dirname, join as join9 } from "node:path";
2482
+ import { homedir as homedir10 } from "node:os";
2483
+ import { dirname, join as join10 } from "node:path";
2002
2484
  var MODELS_DEV_URL = "https://models.dev/api.json";
2003
2485
  var CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2004
2486
  var FETCH_TIMEOUT_MS = 2e3;
@@ -2098,16 +2580,16 @@ async function summarizeUsage(records, pricing) {
2098
2580
  }
2099
2581
  function getPricingCachePath() {
2100
2582
  if (process.env["XDG_CACHE_HOME"]) {
2101
- return join9(process.env["XDG_CACHE_HOME"], "devrage", "models.dev.json");
2583
+ return join10(process.env["XDG_CACHE_HOME"], "devrage", "models.dev.json");
2102
2584
  }
2103
2585
  if (process.platform === "darwin") {
2104
- return join9(homedir9(), "Library", "Caches", "devrage", "models.dev.json");
2586
+ return join10(homedir10(), "Library", "Caches", "devrage", "models.dev.json");
2105
2587
  }
2106
2588
  if (process.platform === "win32") {
2107
- const localAppData = process.env["LOCALAPPDATA"] ?? join9(homedir9(), "AppData", "Local");
2108
- return join9(localAppData, "devrage", "models.dev.json");
2589
+ const localAppData = process.env["LOCALAPPDATA"] ?? join10(homedir10(), "AppData", "Local");
2590
+ return join10(localAppData, "devrage", "models.dev.json");
2109
2591
  }
2110
- return join9(homedir9(), ".cache", "devrage", "models.dev.json");
2592
+ return join10(homedir10(), ".cache", "devrage", "models.dev.json");
2111
2593
  }
2112
2594
  function createCostAccumulator() {
2113
2595
  return {
@@ -2216,7 +2698,7 @@ async function readPricingCache(cachePath) {
2216
2698
  try {
2217
2699
  const raw = await readFile4(cachePath, "utf-8");
2218
2700
  const parsed = JSON.parse(raw);
2219
- const cache = asRecord6(parsed);
2701
+ const cache = asRecord7(parsed);
2220
2702
  if (cache?.["source"] !== "models.dev" || cache["schemaVersion"] !== 1 || typeof cache["fetchedAt"] !== "string" || !isModelsDevCatalog(cache["catalog"])) {
2221
2703
  return null;
2222
2704
  }
@@ -2357,10 +2839,10 @@ function inferProvider(model) {
2357
2839
  return void 0;
2358
2840
  }
2359
2841
  function getCatalogRates(catalog, provider, model) {
2360
- const root = asRecord6(catalog);
2361
- const providerEntry = asRecord6(root?.[provider]);
2362
- const models = asRecord6(providerEntry?.["models"]);
2363
- const modelEntry = asRecord6(models?.[model]);
2842
+ const root = asRecord7(catalog);
2843
+ const providerEntry = asRecord7(root?.[provider]);
2844
+ const models = asRecord7(providerEntry?.["models"]);
2845
+ const modelEntry = asRecord7(models?.[model]);
2364
2846
  return toRateTable(modelEntry?.["cost"]);
2365
2847
  }
2366
2848
  function selectContextRates(rates, record) {
@@ -2368,8 +2850,8 @@ function selectContextRates(rates, record) {
2368
2850
  let selected = rates;
2369
2851
  let selectedSize = 0;
2370
2852
  for (const tier of rates.tiers ?? []) {
2371
- const tierRecord = asRecord6(tier);
2372
- const tierInfo = asRecord6(tierRecord?.["tier"]);
2853
+ const tierRecord = asRecord7(tier);
2854
+ const tierInfo = asRecord7(tierRecord?.["tier"]);
2373
2855
  const size = typeof tierInfo?.["size"] === "number" ? tierInfo["size"] : 0;
2374
2856
  if (tierInfo?.["type"] !== "context" || contextTokens < size || size < selectedSize) {
2375
2857
  continue;
@@ -2386,7 +2868,7 @@ function selectContextRates(rates, record) {
2386
2868
  return selected;
2387
2869
  }
2388
2870
  function toRateTable(value) {
2389
- const record = asRecord6(value);
2871
+ const record = asRecord7(value);
2390
2872
  if (!record) {
2391
2873
  return null;
2392
2874
  }
@@ -2420,10 +2902,10 @@ function numberValue6(value) {
2420
2902
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2421
2903
  }
2422
2904
  function isModelsDevCatalog(value) {
2423
- const catalog = asRecord6(value);
2424
- const openai = asRecord6(catalog?.["openai"]);
2425
- const anthropic = asRecord6(catalog?.["anthropic"]);
2426
- return Boolean(asRecord6(openai?.["models"]) || asRecord6(anthropic?.["models"]));
2905
+ const catalog = asRecord7(value);
2906
+ const openai = asRecord7(catalog?.["openai"]);
2907
+ const anthropic = asRecord7(catalog?.["anthropic"]);
2908
+ return Boolean(asRecord7(openai?.["models"]) || asRecord7(anthropic?.["models"]));
2427
2909
  }
2428
2910
  function isFresh(fetchedAt, ttlMs) {
2429
2911
  const fetchedTime = new Date(fetchedAt).getTime();
@@ -2432,7 +2914,7 @@ function isFresh(fetchedAt, ttlMs) {
2432
2914
  function mergePricingSource(left, right) {
2433
2915
  return left === right ? left : "mixed";
2434
2916
  }
2435
- function asRecord6(value) {
2917
+ function asRecord7(value) {
2436
2918
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
2437
2919
  return null;
2438
2920
  }
@@ -2537,7 +3019,7 @@ function parseArgs(args) {
2537
3019
  console.log(`devrage scan \u2014 scan sessions for profanity
2538
3020
 
2539
3021
  Options:
2540
- --agent, -a <name> Scan only a specific agent (claude, codex, cursor, opencode, amp, cline, pi, zed)
3022
+ --agent, -a <name> Scan only a specific agent (claude, codex, cursor, opencode, amp, cline, pi, t3code, zed)
2541
3023
  --since, -s <date> Only scan messages after this date (ISO 8601)
2542
3024
  --day, --days [n] Only scan the last n days (default: 1)
2543
3025
  --week Only scan the last 7 days
@@ -2578,7 +3060,7 @@ Usage:
2578
3060
  devrage cost [options]
2579
3061
 
2580
3062
  Options:
2581
- --agent, -a <name> Show only a specific agent (claude, codex, cursor, opencode, amp, pi)
3063
+ --agent, -a <name> Show only a specific agent (claude, codex, cursor, opencode, amp, pi, t3code)
2582
3064
  --refresh-prices Refresh models.dev pricing before estimating cost
2583
3065
  --since, -s <date> Only include usage after this date (ISO 8601)
2584
3066
  --day, --days [n] Only include the last n days (default: 1)
@@ -2792,7 +3274,7 @@ function printCostCommandUnavailable(options) {
2792
3274
  }
2793
3275
  async function writeCostHtmlReport(totals, options) {
2794
3276
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
2795
- const reportPath = join10(
3277
+ const reportPath = join11(
2796
3278
  dirname2(getPricingCachePath()),
2797
3279
  `cost-report-${safeTimestamp(generatedAt)}.html`
2798
3280
  );
@@ -3245,7 +3727,7 @@ async function main() {
3245
3727
  process.exit(0);
3246
3728
  }
3247
3729
  if (command === "--version") {
3248
- console.log("0.5.5");
3730
+ console.log("0.5.7");
3249
3731
  process.exit(0);
3250
3732
  }
3251
3733
  const parsed = parseCommand(args);