devrage 0.0.5 → 0.5.3
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 +141 -63
- package/dist/cli.js.map +3 -3
- package/dist/lib/adapters/codex.js +56 -14
- package/dist/lib/adapters/codex.js.map +1 -1
- package/dist/lib/adapters/cursor.js +61 -45
- package/dist/lib/adapters/cursor.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -654,6 +654,7 @@ async function* parseCodexUsageJsonl(filePath, context) {
|
|
|
654
654
|
});
|
|
655
655
|
let model;
|
|
656
656
|
let previousTotal = null;
|
|
657
|
+
let previousUsageSignature = null;
|
|
657
658
|
for await (const line of rl) {
|
|
658
659
|
if (!line.trim()) {
|
|
659
660
|
continue;
|
|
@@ -669,13 +670,31 @@ async function* parseCodexUsageJsonl(filePath, context) {
|
|
|
669
670
|
continue;
|
|
670
671
|
}
|
|
671
672
|
const info = asRecord3(payload["info"]);
|
|
672
|
-
|
|
673
|
-
if (!total) {
|
|
673
|
+
if (!info) {
|
|
674
674
|
continue;
|
|
675
675
|
}
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
676
|
+
const lastUsageValue = info["last_token_usage"];
|
|
677
|
+
const lastUsage = parseCodexTokenUsage(lastUsageValue);
|
|
678
|
+
const total = parseCodexTokenUsage(info["total_token_usage"]);
|
|
679
|
+
let usage2 = null;
|
|
680
|
+
if (lastUsageValue !== void 0) {
|
|
681
|
+
if (lastUsage && hasBillableUsage(lastUsage)) {
|
|
682
|
+
const signature = codexUsageSignature(lastUsage, total);
|
|
683
|
+
if (signature !== previousUsageSignature) {
|
|
684
|
+
usage2 = lastUsage;
|
|
685
|
+
}
|
|
686
|
+
previousUsageSignature = signature;
|
|
687
|
+
}
|
|
688
|
+
} else if (total) {
|
|
689
|
+
const delta = previousTotal ? subtractCodexUsage(total, previousTotal) : total;
|
|
690
|
+
if (hasBillableUsage(delta)) {
|
|
691
|
+
usage2 = delta;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (total && hasBillableUsage(total)) {
|
|
695
|
+
previousTotal = total;
|
|
696
|
+
}
|
|
697
|
+
if (!usage2) {
|
|
679
698
|
continue;
|
|
680
699
|
}
|
|
681
700
|
const timestamp = stringValue2(entry["timestamp"]);
|
|
@@ -685,17 +704,17 @@ async function* parseCodexUsageJsonl(filePath, context) {
|
|
|
685
704
|
continue;
|
|
686
705
|
}
|
|
687
706
|
}
|
|
688
|
-
const reasoningTokens = Math.min(
|
|
707
|
+
const reasoningTokens = Math.min(usage2.reasoningOutputTokens, usage2.outputTokens);
|
|
689
708
|
yield {
|
|
690
709
|
agent: "codex",
|
|
691
710
|
provider: "openai",
|
|
692
711
|
model,
|
|
693
712
|
timestamp,
|
|
694
713
|
session: context.session,
|
|
695
|
-
inputTokens: Math.max(
|
|
696
|
-
outputTokens: Math.max(
|
|
714
|
+
inputTokens: Math.max(usage2.inputTokens - usage2.cachedInputTokens, 0),
|
|
715
|
+
outputTokens: Math.max(usage2.outputTokens - reasoningTokens, 0),
|
|
697
716
|
reasoningTokens,
|
|
698
|
-
cacheReadTokens:
|
|
717
|
+
cacheReadTokens: usage2.cachedInputTokens,
|
|
699
718
|
cacheWriteTokens: 0
|
|
700
719
|
};
|
|
701
720
|
} catch {
|
|
@@ -707,14 +726,23 @@ function parseCodexTokenUsage(value) {
|
|
|
707
726
|
if (!usage2) {
|
|
708
727
|
return null;
|
|
709
728
|
}
|
|
710
|
-
const
|
|
729
|
+
const hasUsageField = [
|
|
730
|
+
"input_tokens",
|
|
731
|
+
"cached_input_tokens",
|
|
732
|
+
"output_tokens",
|
|
733
|
+
"reasoning_output_tokens",
|
|
734
|
+
"total_tokens"
|
|
735
|
+
].some((key) => typeof usage2[key] === "number" && Number.isFinite(usage2[key]));
|
|
736
|
+
if (!hasUsageField) {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
711
740
|
inputTokens: numberValue2(usage2["input_tokens"]),
|
|
712
741
|
cachedInputTokens: numberValue2(usage2["cached_input_tokens"]),
|
|
713
742
|
outputTokens: numberValue2(usage2["output_tokens"]),
|
|
714
743
|
reasoningOutputTokens: numberValue2(usage2["reasoning_output_tokens"]),
|
|
715
744
|
totalTokens: numberValue2(usage2["total_tokens"])
|
|
716
745
|
};
|
|
717
|
-
return hasPositiveUsage(parsed) ? parsed : null;
|
|
718
746
|
}
|
|
719
747
|
function subtractCodexUsage(current, previous) {
|
|
720
748
|
return {
|
|
@@ -728,8 +756,20 @@ function subtractCodexUsage(current, previous) {
|
|
|
728
756
|
totalTokens: Math.max(current.totalTokens - previous.totalTokens, 0)
|
|
729
757
|
};
|
|
730
758
|
}
|
|
731
|
-
function
|
|
732
|
-
return usage2.inputTokens + usage2.cachedInputTokens + usage2.outputTokens + usage2.
|
|
759
|
+
function hasBillableUsage(usage2) {
|
|
760
|
+
return usage2.inputTokens + usage2.cachedInputTokens + usage2.outputTokens + usage2.reasoningOutputTokens > 0;
|
|
761
|
+
}
|
|
762
|
+
function codexUsageSignature(usage2, total) {
|
|
763
|
+
return [
|
|
764
|
+
usage2.inputTokens,
|
|
765
|
+
usage2.cachedInputTokens,
|
|
766
|
+
usage2.outputTokens,
|
|
767
|
+
usage2.reasoningOutputTokens,
|
|
768
|
+
total?.inputTokens ?? "",
|
|
769
|
+
total?.cachedInputTokens ?? "",
|
|
770
|
+
total?.outputTokens ?? "",
|
|
771
|
+
total?.reasoningOutputTokens ?? ""
|
|
772
|
+
].join(":");
|
|
733
773
|
}
|
|
734
774
|
function numberValue2(value) {
|
|
735
775
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
@@ -805,10 +845,12 @@ async function discoverCursorStateStores() {
|
|
|
805
845
|
return stores;
|
|
806
846
|
}
|
|
807
847
|
function getCursorUserDirs() {
|
|
848
|
+
const configHome = envOrDefault("XDG_CONFIG_HOME", join5(homedir5(), ".config"));
|
|
849
|
+
const appData = envOrDefault("APPDATA", join5(homedir5(), "AppData", "Roaming"));
|
|
808
850
|
return uniqueStrings([
|
|
809
851
|
join5(homedir5(), "Library", "Application Support", "Cursor", "User"),
|
|
810
|
-
join5(
|
|
811
|
-
join5(
|
|
852
|
+
join5(configHome, "Cursor", "User"),
|
|
853
|
+
join5(appData, "Cursor", "User")
|
|
812
854
|
]);
|
|
813
855
|
}
|
|
814
856
|
async function* parseCursorStore(store, options) {
|
|
@@ -820,36 +862,40 @@ async function* parseCursorStore(store, options) {
|
|
|
820
862
|
const rows = readStateRows(db);
|
|
821
863
|
const seen = /* @__PURE__ */ new Set();
|
|
822
864
|
for (const row of rows) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
}
|
|
826
|
-
const parsed = parseJsonValue(row.value);
|
|
827
|
-
if (parsed === void 0) {
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
for (const message of extractCursorMessages(parsed, row.key)) {
|
|
831
|
-
const text = message.text.trim();
|
|
832
|
-
if (!isLikelyMessageText(text)) {
|
|
865
|
+
try {
|
|
866
|
+
if (!isCandidateKey(row.key)) {
|
|
833
867
|
continue;
|
|
834
868
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
869
|
+
const parsed = parseJsonValue(row.value);
|
|
870
|
+
if (parsed === void 0) {
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
for (const message of extractCursorMessages(parsed, row.key)) {
|
|
874
|
+
const text = message.text.trim();
|
|
875
|
+
if (!isLikelyMessageText(text)) {
|
|
838
876
|
continue;
|
|
839
877
|
}
|
|
878
|
+
if (options?.since && message.timestamp) {
|
|
879
|
+
const timestamp = new Date(message.timestamp);
|
|
880
|
+
if (Number.isFinite(timestamp.getTime()) && timestamp < options.since) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const session = message.session ?? `${store.scope}:${row.key}`;
|
|
885
|
+
const dedupeKey = `${session}\0${message.timestamp ?? ""}\0${text}`;
|
|
886
|
+
if (seen.has(dedupeKey)) {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
seen.add(dedupeKey);
|
|
890
|
+
yield {
|
|
891
|
+
text,
|
|
892
|
+
timestamp: message.timestamp,
|
|
893
|
+
session,
|
|
894
|
+
project: store.project
|
|
895
|
+
};
|
|
840
896
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
if (seen.has(dedupeKey)) {
|
|
844
|
-
continue;
|
|
845
|
-
}
|
|
846
|
-
seen.add(dedupeKey);
|
|
847
|
-
yield {
|
|
848
|
-
text,
|
|
849
|
-
timestamp: message.timestamp,
|
|
850
|
-
session,
|
|
851
|
-
project: store.project
|
|
852
|
-
};
|
|
897
|
+
} catch {
|
|
898
|
+
continue;
|
|
853
899
|
}
|
|
854
900
|
}
|
|
855
901
|
} finally {
|
|
@@ -866,26 +912,30 @@ async function* parseCursorUsageStore(store, options) {
|
|
|
866
912
|
const composerModels = collectComposerModels(rows);
|
|
867
913
|
const seen = /* @__PURE__ */ new Set();
|
|
868
914
|
for (const row of rows) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
}
|
|
872
|
-
const parsed = parseJsonValue(row.value);
|
|
873
|
-
const usage2 = extractCursorBubbleUsage(parsed, row.key, composerModels);
|
|
874
|
-
if (!usage2) {
|
|
875
|
-
continue;
|
|
876
|
-
}
|
|
877
|
-
if (options?.since && usage2.timestamp) {
|
|
878
|
-
const timestamp = new Date(usage2.timestamp);
|
|
879
|
-
if (Number.isFinite(timestamp.getTime()) && timestamp < options.since) {
|
|
915
|
+
try {
|
|
916
|
+
if (!row.key.startsWith("bubbleId:")) {
|
|
880
917
|
continue;
|
|
881
918
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
919
|
+
const parsed = parseJsonValue(row.value);
|
|
920
|
+
const usage2 = extractCursorBubbleUsage(parsed, row.key, composerModels);
|
|
921
|
+
if (!usage2) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
if (options?.since && usage2.timestamp) {
|
|
925
|
+
const timestamp = new Date(usage2.timestamp);
|
|
926
|
+
if (Number.isFinite(timestamp.getTime()) && timestamp < options.since) {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
const dedupeKey = `${usage2.session ?? ""}\0${usage2.timestamp ?? ""}\0${usage2.model ?? ""}\0${usage2.inputTokens}\0${usage2.outputTokens}`;
|
|
931
|
+
if (seen.has(dedupeKey)) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
seen.add(dedupeKey);
|
|
935
|
+
yield usage2;
|
|
936
|
+
} catch {
|
|
885
937
|
continue;
|
|
886
938
|
}
|
|
887
|
-
seen.add(dedupeKey);
|
|
888
|
-
yield usage2;
|
|
889
939
|
}
|
|
890
940
|
} finally {
|
|
891
941
|
db.close();
|
|
@@ -907,18 +957,22 @@ function readStateRows(db) {
|
|
|
907
957
|
const rows = [];
|
|
908
958
|
try {
|
|
909
959
|
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all();
|
|
910
|
-
const availableTables = new Set(tables.
|
|
960
|
+
const availableTables = new Set(tables.flatMap((table) => stringValue3(table.name) ?? []));
|
|
911
961
|
for (const table of STATE_TABLES) {
|
|
912
962
|
if (!availableTables.has(table)) {
|
|
913
963
|
continue;
|
|
914
964
|
}
|
|
915
965
|
const columns = db.prepare(`PRAGMA table_info("${table}")`).all();
|
|
916
|
-
const columnNames = new Set(columns.
|
|
966
|
+
const columnNames = new Set(columns.flatMap((column) => stringValue3(column.name) ?? []));
|
|
917
967
|
if (!columnNames.has("key") || !columnNames.has("value")) {
|
|
918
968
|
continue;
|
|
919
969
|
}
|
|
920
970
|
const tableRows = db.prepare(`SELECT key, value FROM "${table}"`).all();
|
|
921
|
-
rows.push(
|
|
971
|
+
rows.push(
|
|
972
|
+
...tableRows.flatMap(
|
|
973
|
+
(row) => typeof row.key === "string" ? [{ key: row.key, value: row.value }] : []
|
|
974
|
+
)
|
|
975
|
+
);
|
|
922
976
|
}
|
|
923
977
|
} catch {
|
|
924
978
|
return rows;
|
|
@@ -1250,6 +1304,10 @@ function uniqueMessages(messages) {
|
|
|
1250
1304
|
function uniqueStrings(values) {
|
|
1251
1305
|
return Array.from(new Set(values));
|
|
1252
1306
|
}
|
|
1307
|
+
function envOrDefault(name, fallback) {
|
|
1308
|
+
const value = process.env[name];
|
|
1309
|
+
return value && value.trim() ? value : fallback;
|
|
1310
|
+
}
|
|
1253
1311
|
function numberValue3(value) {
|
|
1254
1312
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1255
1313
|
}
|
|
@@ -3088,6 +3146,7 @@ var COMMANDS = {
|
|
|
3088
3146
|
cost,
|
|
3089
3147
|
scan
|
|
3090
3148
|
};
|
|
3149
|
+
var OPTIONS_WITH_VALUES = /* @__PURE__ */ new Set(["--agent", "-a", "--since", "-s"]);
|
|
3091
3150
|
function usage() {
|
|
3092
3151
|
console.log(`devrage \u2014 count how many times you swear at your coding agents
|
|
3093
3152
|
|
|
@@ -3116,16 +3175,35 @@ async function main() {
|
|
|
3116
3175
|
process.exit(0);
|
|
3117
3176
|
}
|
|
3118
3177
|
if (command === "--version") {
|
|
3119
|
-
console.log("0.
|
|
3178
|
+
console.log("0.5.3");
|
|
3120
3179
|
process.exit(0);
|
|
3121
3180
|
}
|
|
3122
|
-
const
|
|
3123
|
-
if (
|
|
3124
|
-
await handler(args
|
|
3181
|
+
const parsed = parseCommand(args);
|
|
3182
|
+
if (parsed) {
|
|
3183
|
+
await parsed.handler(parsed.args);
|
|
3125
3184
|
} else {
|
|
3126
3185
|
await scan(args);
|
|
3127
3186
|
}
|
|
3128
3187
|
}
|
|
3188
|
+
function parseCommand(args) {
|
|
3189
|
+
for (let index = 0; index < args.length; index++) {
|
|
3190
|
+
const arg = args[index];
|
|
3191
|
+
if (!arg) {
|
|
3192
|
+
continue;
|
|
3193
|
+
}
|
|
3194
|
+
const handler = COMMANDS[arg];
|
|
3195
|
+
if (handler) {
|
|
3196
|
+
return {
|
|
3197
|
+
handler,
|
|
3198
|
+
args: [...args.slice(0, index), ...args.slice(index + 1)]
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
if (OPTIONS_WITH_VALUES.has(arg) && index + 1 < args.length) {
|
|
3202
|
+
index++;
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
return null;
|
|
3206
|
+
}
|
|
3129
3207
|
main().catch((err) => {
|
|
3130
3208
|
console.error(err);
|
|
3131
3209
|
process.exit(1);
|