devrage 0.5.5 → 0.5.6
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 +460 -38
- package/dist/cli.js.map +4 -4
- package/dist/lib/adapters/index.d.ts.map +1 -1
- package/dist/lib/adapters/index.js +2 -0
- package/dist/lib/adapters/index.js.map +1 -1
- package/dist/lib/adapters/t3code.d.ts +3 -0
- package/dist/lib/adapters/t3code.d.ts.map +1 -0
- package/dist/lib/adapters/t3code.js +436 -0
- package/dist/lib/adapters/t3code.js.map +1 -0
- package/package.json +2 -2
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
|
|
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
|
|
@@ -1689,23 +1689,444 @@ function asRecord5(value) {
|
|
|
1689
1689
|
return value;
|
|
1690
1690
|
}
|
|
1691
1691
|
|
|
1692
|
-
// src/adapters/
|
|
1693
|
-
import { readdir as readdir7, readFile as readFile3 } from "node:fs/promises";
|
|
1692
|
+
// src/adapters/t3code.ts
|
|
1694
1693
|
import { existsSync as existsSync4 } from "node:fs";
|
|
1695
1694
|
import { homedir as homedir8 } from "node:os";
|
|
1696
|
-
import { join as join8 } from "node:path";
|
|
1695
|
+
import { isAbsolute, join as join8, resolve } from "node:path";
|
|
1696
|
+
function t3codeAdapter() {
|
|
1697
|
+
return {
|
|
1698
|
+
name: "t3code",
|
|
1699
|
+
async *messages(options) {
|
|
1700
|
+
for (const location of discoverT3Databases()) {
|
|
1701
|
+
const db = await openT3Db(location.path);
|
|
1702
|
+
if (!db) {
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
try {
|
|
1706
|
+
yield* queryUserMessages2(db, location, options);
|
|
1707
|
+
} finally {
|
|
1708
|
+
db.close();
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
},
|
|
1712
|
+
async *usage(options) {
|
|
1713
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1714
|
+
for (const location of discoverT3Databases()) {
|
|
1715
|
+
const db = await openT3Db(location.path);
|
|
1716
|
+
if (!db) {
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
yield* queryUsageRecords2(db, location, seen, options);
|
|
1721
|
+
} finally {
|
|
1722
|
+
db.close();
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
function discoverT3Databases() {
|
|
1729
|
+
const locations = [];
|
|
1730
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1731
|
+
const stateDir = stringValue6(process.env["T3CODE_STATE_DIR"]);
|
|
1732
|
+
if (stateDir) {
|
|
1733
|
+
addLocation(locations, seen, join8(resolveHomePath(stateDir), "state.sqlite"), "state");
|
|
1734
|
+
}
|
|
1735
|
+
for (const baseDir of uniqueStrings2([
|
|
1736
|
+
stringValue6(process.env["T3CODE_HOME"]),
|
|
1737
|
+
join8(homedir8(), ".t3")
|
|
1738
|
+
])) {
|
|
1739
|
+
addLocation(
|
|
1740
|
+
locations,
|
|
1741
|
+
seen,
|
|
1742
|
+
join8(resolveHomePath(baseDir), "userdata", "state.sqlite"),
|
|
1743
|
+
"userdata"
|
|
1744
|
+
);
|
|
1745
|
+
addLocation(locations, seen, join8(resolveHomePath(baseDir), "dev", "state.sqlite"), "dev");
|
|
1746
|
+
}
|
|
1747
|
+
return locations;
|
|
1748
|
+
}
|
|
1749
|
+
function addLocation(locations, seen, dbPath, scope) {
|
|
1750
|
+
if (seen.has(dbPath) || !existsSync4(dbPath)) {
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
seen.add(dbPath);
|
|
1754
|
+
locations.push({ path: dbPath, scope });
|
|
1755
|
+
}
|
|
1756
|
+
function resolveHomePath(value) {
|
|
1757
|
+
if (value === "~") {
|
|
1758
|
+
return homedir8();
|
|
1759
|
+
}
|
|
1760
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
1761
|
+
return join8(homedir8(), value.slice(2));
|
|
1762
|
+
}
|
|
1763
|
+
return isAbsolute(value) ? value : resolve(value);
|
|
1764
|
+
}
|
|
1765
|
+
async function openT3Db(dbPath) {
|
|
1766
|
+
try {
|
|
1767
|
+
const BetterSqlite3 = await import("better-sqlite3");
|
|
1768
|
+
const Ctor = BetterSqlite3.default ?? BetterSqlite3;
|
|
1769
|
+
return new Ctor(
|
|
1770
|
+
dbPath,
|
|
1771
|
+
{ readonly: true, fileMustExist: true }
|
|
1772
|
+
);
|
|
1773
|
+
} catch {
|
|
1774
|
+
return null;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
function* queryUserMessages2(db, location, options) {
|
|
1778
|
+
if (!hasColumns(db, "projection_thread_messages", ["thread_id", "role", "text", "created_at"])) {
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const orderColumn = hasColumns(db, "projection_thread_messages", ["message_id"]) ? "message_id" : "created_at";
|
|
1782
|
+
let query = `
|
|
1783
|
+
SELECT thread_id, created_at, text
|
|
1784
|
+
FROM projection_thread_messages
|
|
1785
|
+
WHERE role = 'user'
|
|
1786
|
+
`;
|
|
1787
|
+
const params = [];
|
|
1788
|
+
if (options?.since) {
|
|
1789
|
+
query += ` AND created_at >= ?`;
|
|
1790
|
+
params.push(options.since.toISOString());
|
|
1791
|
+
}
|
|
1792
|
+
query += ` ORDER BY created_at ASC, ${orderColumn} ASC`;
|
|
1793
|
+
let rows;
|
|
1794
|
+
try {
|
|
1795
|
+
rows = db.prepare(query).all(...params);
|
|
1796
|
+
} catch {
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
for (const row of rows) {
|
|
1800
|
+
const text = stringValue6(row.text);
|
|
1801
|
+
if (!text) {
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
yield {
|
|
1805
|
+
text,
|
|
1806
|
+
timestamp: stringValue6(row.created_at),
|
|
1807
|
+
session: stringValue6(row.thread_id),
|
|
1808
|
+
project: location.scope
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
function* queryUsageRecords2(db, location, seen, options) {
|
|
1813
|
+
if (!hasColumns(db, "orchestration_events", [
|
|
1814
|
+
"event_id",
|
|
1815
|
+
"stream_id",
|
|
1816
|
+
"event_type",
|
|
1817
|
+
"occurred_at",
|
|
1818
|
+
"payload_json"
|
|
1819
|
+
])) {
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
const threadInfo = readThreadInfo(db);
|
|
1823
|
+
const orderColumn = hasColumns(db, "orchestration_events", ["sequence"]) ? "sequence" : "event_id";
|
|
1824
|
+
let query = `
|
|
1825
|
+
SELECT event_id, stream_id, occurred_at, payload_json
|
|
1826
|
+
FROM orchestration_events
|
|
1827
|
+
WHERE event_type = 'thread.activity-appended'
|
|
1828
|
+
`;
|
|
1829
|
+
const params = [];
|
|
1830
|
+
if (options?.since) {
|
|
1831
|
+
query += ` AND occurred_at >= ?`;
|
|
1832
|
+
params.push(options.since.toISOString());
|
|
1833
|
+
}
|
|
1834
|
+
query += ` ORDER BY occurred_at ASC, ${orderColumn} ASC`;
|
|
1835
|
+
let rows;
|
|
1836
|
+
try {
|
|
1837
|
+
rows = db.prepare(query).all(...params);
|
|
1838
|
+
} catch {
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
for (const row of rows) {
|
|
1842
|
+
const payload = asRecord6(parseJson(row.payload_json));
|
|
1843
|
+
const activity = asRecord6(payload?.["activity"]);
|
|
1844
|
+
if (activity?.["kind"] !== "context-window.updated") {
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
const usage2 = parseUsageSnapshot(activity["payload"]);
|
|
1848
|
+
if (!usage2 || !hasBillableUsage2(usage2)) {
|
|
1849
|
+
continue;
|
|
1850
|
+
}
|
|
1851
|
+
const threadId = stringValue6(payload?.["threadId"]) ?? stringValue6(row.stream_id);
|
|
1852
|
+
const timestamp = stringValue6(activity["createdAt"]) ?? stringValue6(row.occurred_at);
|
|
1853
|
+
const turnId = stringValue6(activity["turnId"]);
|
|
1854
|
+
const info = threadId ? threadInfo.get(threadId) : void 0;
|
|
1855
|
+
const provider = normalizeT3Provider(info?.provider, info?.model);
|
|
1856
|
+
const dedupeKey = t3UsageDedupeKey({
|
|
1857
|
+
scope: location.scope,
|
|
1858
|
+
threadId,
|
|
1859
|
+
turnId,
|
|
1860
|
+
provider,
|
|
1861
|
+
model: info?.model,
|
|
1862
|
+
usage: usage2
|
|
1863
|
+
});
|
|
1864
|
+
if (seen.has(dedupeKey)) {
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
seen.add(dedupeKey);
|
|
1868
|
+
yield {
|
|
1869
|
+
agent: "t3code",
|
|
1870
|
+
provider,
|
|
1871
|
+
model: info?.model,
|
|
1872
|
+
timestamp,
|
|
1873
|
+
session: threadId,
|
|
1874
|
+
inputTokens: usage2.inputTokens,
|
|
1875
|
+
outputTokens: usage2.outputTokens,
|
|
1876
|
+
reasoningTokens: usage2.reasoningTokens,
|
|
1877
|
+
cacheReadTokens: usage2.cacheReadTokens,
|
|
1878
|
+
cacheWriteTokens: usage2.cacheWriteTokens
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
function normalizeT3Provider(provider, model) {
|
|
1883
|
+
const normalized = provider?.trim().toLowerCase();
|
|
1884
|
+
const key = normalized?.replace(/[^a-z0-9]/g, "");
|
|
1885
|
+
switch (key) {
|
|
1886
|
+
case "codex":
|
|
1887
|
+
return "openai";
|
|
1888
|
+
case "claudeagent":
|
|
1889
|
+
case "claudecode":
|
|
1890
|
+
return "anthropic";
|
|
1891
|
+
case "cursor":
|
|
1892
|
+
case "opencode":
|
|
1893
|
+
return void 0;
|
|
1894
|
+
default:
|
|
1895
|
+
if (key?.includes("codex")) {
|
|
1896
|
+
return "openai";
|
|
1897
|
+
}
|
|
1898
|
+
if (key?.includes("claude")) {
|
|
1899
|
+
return "anthropic";
|
|
1900
|
+
}
|
|
1901
|
+
if (normalized === "openai" || normalized === "anthropic") {
|
|
1902
|
+
return normalized;
|
|
1903
|
+
}
|
|
1904
|
+
return providerFromModel(model);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
function providerFromModel(model) {
|
|
1908
|
+
const slash = model?.indexOf("/") ?? -1;
|
|
1909
|
+
if (!model || slash <= 0) {
|
|
1910
|
+
return void 0;
|
|
1911
|
+
}
|
|
1912
|
+
return model.slice(0, slash);
|
|
1913
|
+
}
|
|
1914
|
+
function readThreadInfo(db) {
|
|
1915
|
+
const info = /* @__PURE__ */ new Map();
|
|
1916
|
+
readProjectionThreadModels(db, info);
|
|
1917
|
+
readProjectionThreadProviders(db, info);
|
|
1918
|
+
return info;
|
|
1919
|
+
}
|
|
1920
|
+
function readProjectionThreadModels(db, info) {
|
|
1921
|
+
if (!tableExists(db, "projection_threads")) {
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
const columns = tableColumns(db, "projection_threads");
|
|
1925
|
+
if (!columns.has("thread_id")) {
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
try {
|
|
1929
|
+
if (columns.has("model_selection_json")) {
|
|
1930
|
+
const rows = db.prepare("SELECT thread_id, model_selection_json FROM projection_threads").all();
|
|
1931
|
+
for (const row of rows) {
|
|
1932
|
+
const threadId = stringValue6(row.thread_id);
|
|
1933
|
+
const modelSelection = asRecord6(parseJson(row.model_selection_json));
|
|
1934
|
+
if (!threadId || !modelSelection) {
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
const entry = info.get(threadId) ?? {};
|
|
1938
|
+
entry.model = stringValue6(modelSelection["model"]) ?? entry.model;
|
|
1939
|
+
entry.provider = stringValue6(modelSelection["provider"]) ?? stringValue6(modelSelection["instanceId"]) ?? entry.provider;
|
|
1940
|
+
info.set(threadId, entry);
|
|
1941
|
+
}
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
if (columns.has("model")) {
|
|
1945
|
+
const rows = db.prepare("SELECT thread_id, model FROM projection_threads").all();
|
|
1946
|
+
for (const row of rows) {
|
|
1947
|
+
const threadId = stringValue6(row.thread_id);
|
|
1948
|
+
const model = stringValue6(row.model);
|
|
1949
|
+
if (threadId && model) {
|
|
1950
|
+
info.set(threadId, { ...info.get(threadId), model });
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
} catch {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
function readProjectionThreadProviders(db, info) {
|
|
1959
|
+
if (!hasColumns(db, "projection_thread_sessions", ["thread_id", "provider_name"])) {
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
try {
|
|
1963
|
+
const rows = db.prepare("SELECT thread_id, provider_name FROM projection_thread_sessions").all();
|
|
1964
|
+
for (const row of rows) {
|
|
1965
|
+
const threadId = stringValue6(row.thread_id);
|
|
1966
|
+
const provider = stringValue6(row.provider_name);
|
|
1967
|
+
if (!threadId || !provider) {
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
info.set(threadId, { ...info.get(threadId), provider });
|
|
1971
|
+
}
|
|
1972
|
+
} catch {
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
function parseUsageSnapshot(value) {
|
|
1977
|
+
const usage2 = asRecord6(value);
|
|
1978
|
+
if (!usage2) {
|
|
1979
|
+
return null;
|
|
1980
|
+
}
|
|
1981
|
+
const lastInputTokens = tokenValue(usage2["lastInputTokens"] ?? usage2["last_input_tokens"]);
|
|
1982
|
+
const lastCachedInputTokens = tokenValue(
|
|
1983
|
+
usage2["lastCachedInputTokens"] ?? usage2["last_cached_input_tokens"]
|
|
1984
|
+
);
|
|
1985
|
+
const lastOutputTokens = tokenValue(usage2["lastOutputTokens"] ?? usage2["last_output_tokens"]);
|
|
1986
|
+
const lastReasoningOutputTokens = tokenValue(
|
|
1987
|
+
usage2["lastReasoningOutputTokens"] ?? usage2["last_reasoning_output_tokens"]
|
|
1988
|
+
);
|
|
1989
|
+
const hasLastDetails = [
|
|
1990
|
+
lastInputTokens,
|
|
1991
|
+
lastCachedInputTokens,
|
|
1992
|
+
lastOutputTokens,
|
|
1993
|
+
lastReasoningOutputTokens
|
|
1994
|
+
].some((token) => token !== void 0);
|
|
1995
|
+
if (hasLastDetails) {
|
|
1996
|
+
return splitTokenUsage({
|
|
1997
|
+
inputTokens: lastInputTokens ?? 0,
|
|
1998
|
+
cachedInputTokens: lastCachedInputTokens ?? 0,
|
|
1999
|
+
outputTokens: lastOutputTokens ?? 0,
|
|
2000
|
+
reasoningOutputTokens: lastReasoningOutputTokens ?? 0
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
const inputTokens = tokenValue(usage2["inputTokens"] ?? usage2["input_tokens"]);
|
|
2004
|
+
const cachedInputTokens = tokenValue(usage2["cachedInputTokens"] ?? usage2["cached_input_tokens"]);
|
|
2005
|
+
const outputTokens = tokenValue(usage2["outputTokens"] ?? usage2["output_tokens"]);
|
|
2006
|
+
const reasoningOutputTokens = tokenValue(
|
|
2007
|
+
usage2["reasoningOutputTokens"] ?? usage2["reasoning_output_tokens"]
|
|
2008
|
+
);
|
|
2009
|
+
const hasSnapshotDetails = [
|
|
2010
|
+
inputTokens,
|
|
2011
|
+
cachedInputTokens,
|
|
2012
|
+
outputTokens,
|
|
2013
|
+
reasoningOutputTokens
|
|
2014
|
+
].some((token) => token !== void 0);
|
|
2015
|
+
if (!hasSnapshotDetails) {
|
|
2016
|
+
return null;
|
|
2017
|
+
}
|
|
2018
|
+
return splitTokenUsage({
|
|
2019
|
+
inputTokens: inputTokens ?? 0,
|
|
2020
|
+
cachedInputTokens: cachedInputTokens ?? 0,
|
|
2021
|
+
outputTokens: outputTokens ?? 0,
|
|
2022
|
+
reasoningOutputTokens: reasoningOutputTokens ?? 0
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
function splitTokenUsage(input) {
|
|
2026
|
+
const reasoningTokens = Math.min(input.reasoningOutputTokens, input.outputTokens);
|
|
2027
|
+
return {
|
|
2028
|
+
inputTokens: Math.max(input.inputTokens - input.cachedInputTokens, 0),
|
|
2029
|
+
outputTokens: Math.max(input.outputTokens - reasoningTokens, 0),
|
|
2030
|
+
reasoningTokens,
|
|
2031
|
+
cacheReadTokens: input.cachedInputTokens,
|
|
2032
|
+
cacheWriteTokens: 0
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
function hasBillableUsage2(usage2) {
|
|
2036
|
+
return usage2.inputTokens + usage2.outputTokens + usage2.reasoningTokens + usage2.cacheReadTokens + usage2.cacheWriteTokens > 0;
|
|
2037
|
+
}
|
|
2038
|
+
function t3UsageDedupeKey(input) {
|
|
2039
|
+
return JSON.stringify([
|
|
2040
|
+
input.scope,
|
|
2041
|
+
input.threadId ?? "",
|
|
2042
|
+
input.turnId ?? "",
|
|
2043
|
+
input.provider ?? "",
|
|
2044
|
+
input.model ?? "",
|
|
2045
|
+
input.usage.inputTokens,
|
|
2046
|
+
input.usage.outputTokens,
|
|
2047
|
+
input.usage.reasoningTokens,
|
|
2048
|
+
input.usage.cacheReadTokens,
|
|
2049
|
+
input.usage.cacheWriteTokens
|
|
2050
|
+
]);
|
|
2051
|
+
}
|
|
2052
|
+
function hasColumns(db, table, requiredColumns) {
|
|
2053
|
+
const columns = tableColumns(db, table);
|
|
2054
|
+
return requiredColumns.every((column) => columns.has(column));
|
|
2055
|
+
}
|
|
2056
|
+
function tableExists(db, table) {
|
|
2057
|
+
try {
|
|
2058
|
+
const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1").get(table);
|
|
2059
|
+
return Boolean(row);
|
|
2060
|
+
} catch {
|
|
2061
|
+
return false;
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
function tableColumns(db, table) {
|
|
2065
|
+
if (!tableExists(db, table)) {
|
|
2066
|
+
return /* @__PURE__ */ new Set();
|
|
2067
|
+
}
|
|
2068
|
+
try {
|
|
2069
|
+
const rows = db.prepare(`PRAGMA table_info("${table}")`).all();
|
|
2070
|
+
return new Set(rows.flatMap((row) => stringValue6(row.name) ?? []));
|
|
2071
|
+
} catch {
|
|
2072
|
+
return /* @__PURE__ */ new Set();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function parseJson(value) {
|
|
2076
|
+
if (typeof value !== "string") {
|
|
2077
|
+
return null;
|
|
2078
|
+
}
|
|
2079
|
+
try {
|
|
2080
|
+
return JSON.parse(value);
|
|
2081
|
+
} catch {
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function tokenValue(value) {
|
|
2086
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2087
|
+
return void 0;
|
|
2088
|
+
}
|
|
2089
|
+
return Math.max(Math.round(value), 0);
|
|
2090
|
+
}
|
|
2091
|
+
function uniqueStrings2(values) {
|
|
2092
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2093
|
+
const unique = [];
|
|
2094
|
+
for (const value of values) {
|
|
2095
|
+
if (!value || seen.has(value)) {
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
seen.add(value);
|
|
2099
|
+
unique.push(value);
|
|
2100
|
+
}
|
|
2101
|
+
return unique;
|
|
2102
|
+
}
|
|
2103
|
+
function stringValue6(value) {
|
|
2104
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2105
|
+
}
|
|
2106
|
+
function asRecord6(value) {
|
|
2107
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2108
|
+
return null;
|
|
2109
|
+
}
|
|
2110
|
+
return value;
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
// src/adapters/zed.ts
|
|
2114
|
+
import { readdir as readdir7, readFile as readFile3 } from "node:fs/promises";
|
|
2115
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
2116
|
+
import { homedir as homedir9 } from "node:os";
|
|
2117
|
+
import { join as join9 } from "node:path";
|
|
1697
2118
|
function getZedPaths() {
|
|
1698
2119
|
if (process.platform === "darwin") {
|
|
1699
|
-
const base2 =
|
|
2120
|
+
const base2 = join9(homedir9(), "Library", "Application Support", "Zed");
|
|
1700
2121
|
return {
|
|
1701
|
-
conversations:
|
|
1702
|
-
db:
|
|
2122
|
+
conversations: join9(base2, "conversations"),
|
|
2123
|
+
db: join9(base2, "db")
|
|
1703
2124
|
};
|
|
1704
2125
|
}
|
|
1705
|
-
const base =
|
|
2126
|
+
const base = join9(process.env["XDG_DATA_HOME"] ?? join9(homedir9(), ".local", "share"), "zed");
|
|
1706
2127
|
return {
|
|
1707
|
-
conversations:
|
|
1708
|
-
db:
|
|
2128
|
+
conversations: join9(base, "conversations"),
|
|
2129
|
+
db: join9(base, "db")
|
|
1709
2130
|
};
|
|
1710
2131
|
}
|
|
1711
2132
|
function zedAdapter() {
|
|
@@ -1719,7 +2140,7 @@ function zedAdapter() {
|
|
|
1719
2140
|
};
|
|
1720
2141
|
}
|
|
1721
2142
|
async function* parseTextThreads(dir, _options) {
|
|
1722
|
-
if (!
|
|
2143
|
+
if (!existsSync5(dir)) {
|
|
1723
2144
|
return;
|
|
1724
2145
|
}
|
|
1725
2146
|
let files;
|
|
@@ -1730,7 +2151,7 @@ async function* parseTextThreads(dir, _options) {
|
|
|
1730
2151
|
}
|
|
1731
2152
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
1732
2153
|
for (const file of jsonFiles) {
|
|
1733
|
-
const filePath =
|
|
2154
|
+
const filePath = join9(dir, file);
|
|
1734
2155
|
const session = file.replace(".json", "");
|
|
1735
2156
|
try {
|
|
1736
2157
|
const raw = await readFile3(filePath, "utf-8");
|
|
@@ -1756,7 +2177,7 @@ async function* parseTextThreads(dir, _options) {
|
|
|
1756
2177
|
}
|
|
1757
2178
|
}
|
|
1758
2179
|
async function* parseAgentThreads(dbDir, _options) {
|
|
1759
|
-
if (!
|
|
2180
|
+
if (!existsSync5(dbDir)) {
|
|
1760
2181
|
return;
|
|
1761
2182
|
}
|
|
1762
2183
|
let dbFiles;
|
|
@@ -1777,7 +2198,7 @@ async function* parseAgentThreads(dbDir, _options) {
|
|
|
1777
2198
|
return;
|
|
1778
2199
|
}
|
|
1779
2200
|
for (const dbFile of dbFiles) {
|
|
1780
|
-
const dbPath =
|
|
2201
|
+
const dbPath = join9(dbDir, dbFile);
|
|
1781
2202
|
let db;
|
|
1782
2203
|
try {
|
|
1783
2204
|
db = new Database(dbPath, {
|
|
@@ -1828,6 +2249,7 @@ var ADAPTERS = {
|
|
|
1828
2249
|
amp: ampAdapter,
|
|
1829
2250
|
cline: clineAdapter,
|
|
1830
2251
|
pi: piAdapter,
|
|
2252
|
+
t3code: t3codeAdapter,
|
|
1831
2253
|
zed: zedAdapter
|
|
1832
2254
|
};
|
|
1833
2255
|
function createAdapter(name) {
|
|
@@ -1997,8 +2419,8 @@ function runPattern(_originalText, searchText, matches, seen) {
|
|
|
1997
2419
|
|
|
1998
2420
|
// src/pricing/index.ts
|
|
1999
2421
|
import { mkdir, readFile as readFile4, writeFile } from "node:fs/promises";
|
|
2000
|
-
import { homedir as
|
|
2001
|
-
import { dirname, join as
|
|
2422
|
+
import { homedir as homedir10 } from "node:os";
|
|
2423
|
+
import { dirname, join as join10 } from "node:path";
|
|
2002
2424
|
var MODELS_DEV_URL = "https://models.dev/api.json";
|
|
2003
2425
|
var CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2004
2426
|
var FETCH_TIMEOUT_MS = 2e3;
|
|
@@ -2098,16 +2520,16 @@ async function summarizeUsage(records, pricing) {
|
|
|
2098
2520
|
}
|
|
2099
2521
|
function getPricingCachePath() {
|
|
2100
2522
|
if (process.env["XDG_CACHE_HOME"]) {
|
|
2101
|
-
return
|
|
2523
|
+
return join10(process.env["XDG_CACHE_HOME"], "devrage", "models.dev.json");
|
|
2102
2524
|
}
|
|
2103
2525
|
if (process.platform === "darwin") {
|
|
2104
|
-
return
|
|
2526
|
+
return join10(homedir10(), "Library", "Caches", "devrage", "models.dev.json");
|
|
2105
2527
|
}
|
|
2106
2528
|
if (process.platform === "win32") {
|
|
2107
|
-
const localAppData = process.env["LOCALAPPDATA"] ??
|
|
2108
|
-
return
|
|
2529
|
+
const localAppData = process.env["LOCALAPPDATA"] ?? join10(homedir10(), "AppData", "Local");
|
|
2530
|
+
return join10(localAppData, "devrage", "models.dev.json");
|
|
2109
2531
|
}
|
|
2110
|
-
return
|
|
2532
|
+
return join10(homedir10(), ".cache", "devrage", "models.dev.json");
|
|
2111
2533
|
}
|
|
2112
2534
|
function createCostAccumulator() {
|
|
2113
2535
|
return {
|
|
@@ -2216,7 +2638,7 @@ async function readPricingCache(cachePath) {
|
|
|
2216
2638
|
try {
|
|
2217
2639
|
const raw = await readFile4(cachePath, "utf-8");
|
|
2218
2640
|
const parsed = JSON.parse(raw);
|
|
2219
|
-
const cache =
|
|
2641
|
+
const cache = asRecord7(parsed);
|
|
2220
2642
|
if (cache?.["source"] !== "models.dev" || cache["schemaVersion"] !== 1 || typeof cache["fetchedAt"] !== "string" || !isModelsDevCatalog(cache["catalog"])) {
|
|
2221
2643
|
return null;
|
|
2222
2644
|
}
|
|
@@ -2357,10 +2779,10 @@ function inferProvider(model) {
|
|
|
2357
2779
|
return void 0;
|
|
2358
2780
|
}
|
|
2359
2781
|
function getCatalogRates(catalog, provider, model) {
|
|
2360
|
-
const root =
|
|
2361
|
-
const providerEntry =
|
|
2362
|
-
const models =
|
|
2363
|
-
const modelEntry =
|
|
2782
|
+
const root = asRecord7(catalog);
|
|
2783
|
+
const providerEntry = asRecord7(root?.[provider]);
|
|
2784
|
+
const models = asRecord7(providerEntry?.["models"]);
|
|
2785
|
+
const modelEntry = asRecord7(models?.[model]);
|
|
2364
2786
|
return toRateTable(modelEntry?.["cost"]);
|
|
2365
2787
|
}
|
|
2366
2788
|
function selectContextRates(rates, record) {
|
|
@@ -2368,8 +2790,8 @@ function selectContextRates(rates, record) {
|
|
|
2368
2790
|
let selected = rates;
|
|
2369
2791
|
let selectedSize = 0;
|
|
2370
2792
|
for (const tier of rates.tiers ?? []) {
|
|
2371
|
-
const tierRecord =
|
|
2372
|
-
const tierInfo =
|
|
2793
|
+
const tierRecord = asRecord7(tier);
|
|
2794
|
+
const tierInfo = asRecord7(tierRecord?.["tier"]);
|
|
2373
2795
|
const size = typeof tierInfo?.["size"] === "number" ? tierInfo["size"] : 0;
|
|
2374
2796
|
if (tierInfo?.["type"] !== "context" || contextTokens < size || size < selectedSize) {
|
|
2375
2797
|
continue;
|
|
@@ -2386,7 +2808,7 @@ function selectContextRates(rates, record) {
|
|
|
2386
2808
|
return selected;
|
|
2387
2809
|
}
|
|
2388
2810
|
function toRateTable(value) {
|
|
2389
|
-
const record =
|
|
2811
|
+
const record = asRecord7(value);
|
|
2390
2812
|
if (!record) {
|
|
2391
2813
|
return null;
|
|
2392
2814
|
}
|
|
@@ -2420,10 +2842,10 @@ function numberValue6(value) {
|
|
|
2420
2842
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2421
2843
|
}
|
|
2422
2844
|
function isModelsDevCatalog(value) {
|
|
2423
|
-
const catalog =
|
|
2424
|
-
const openai =
|
|
2425
|
-
const anthropic =
|
|
2426
|
-
return Boolean(
|
|
2845
|
+
const catalog = asRecord7(value);
|
|
2846
|
+
const openai = asRecord7(catalog?.["openai"]);
|
|
2847
|
+
const anthropic = asRecord7(catalog?.["anthropic"]);
|
|
2848
|
+
return Boolean(asRecord7(openai?.["models"]) || asRecord7(anthropic?.["models"]));
|
|
2427
2849
|
}
|
|
2428
2850
|
function isFresh(fetchedAt, ttlMs) {
|
|
2429
2851
|
const fetchedTime = new Date(fetchedAt).getTime();
|
|
@@ -2432,7 +2854,7 @@ function isFresh(fetchedAt, ttlMs) {
|
|
|
2432
2854
|
function mergePricingSource(left, right) {
|
|
2433
2855
|
return left === right ? left : "mixed";
|
|
2434
2856
|
}
|
|
2435
|
-
function
|
|
2857
|
+
function asRecord7(value) {
|
|
2436
2858
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2437
2859
|
return null;
|
|
2438
2860
|
}
|
|
@@ -2537,7 +2959,7 @@ function parseArgs(args) {
|
|
|
2537
2959
|
console.log(`devrage scan \u2014 scan sessions for profanity
|
|
2538
2960
|
|
|
2539
2961
|
Options:
|
|
2540
|
-
--agent, -a <name> Scan only a specific agent (claude, codex, cursor, opencode, amp, cline, pi, zed)
|
|
2962
|
+
--agent, -a <name> Scan only a specific agent (claude, codex, cursor, opencode, amp, cline, pi, t3code, zed)
|
|
2541
2963
|
--since, -s <date> Only scan messages after this date (ISO 8601)
|
|
2542
2964
|
--day, --days [n] Only scan the last n days (default: 1)
|
|
2543
2965
|
--week Only scan the last 7 days
|
|
@@ -2578,7 +3000,7 @@ Usage:
|
|
|
2578
3000
|
devrage cost [options]
|
|
2579
3001
|
|
|
2580
3002
|
Options:
|
|
2581
|
-
--agent, -a <name> Show only a specific agent (claude, codex, cursor, opencode, amp, pi)
|
|
3003
|
+
--agent, -a <name> Show only a specific agent (claude, codex, cursor, opencode, amp, pi, t3code)
|
|
2582
3004
|
--refresh-prices Refresh models.dev pricing before estimating cost
|
|
2583
3005
|
--since, -s <date> Only include usage after this date (ISO 8601)
|
|
2584
3006
|
--day, --days [n] Only include the last n days (default: 1)
|
|
@@ -2792,7 +3214,7 @@ function printCostCommandUnavailable(options) {
|
|
|
2792
3214
|
}
|
|
2793
3215
|
async function writeCostHtmlReport(totals, options) {
|
|
2794
3216
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2795
|
-
const reportPath =
|
|
3217
|
+
const reportPath = join11(
|
|
2796
3218
|
dirname2(getPricingCachePath()),
|
|
2797
3219
|
`cost-report-${safeTimestamp(generatedAt)}.html`
|
|
2798
3220
|
);
|
|
@@ -3245,7 +3667,7 @@ async function main() {
|
|
|
3245
3667
|
process.exit(0);
|
|
3246
3668
|
}
|
|
3247
3669
|
if (command === "--version") {
|
|
3248
|
-
console.log("0.5.
|
|
3670
|
+
console.log("0.5.6");
|
|
3249
3671
|
process.exit(0);
|
|
3250
3672
|
}
|
|
3251
3673
|
const parsed = parseCommand(args);
|