letmecode 0.1.16 → 0.1.18
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/ink-app/dist/index.js
CHANGED
|
@@ -195,7 +195,7 @@ function App(props) {
|
|
|
195
195
|
moveSelectedTableRow(-1);
|
|
196
196
|
}
|
|
197
197
|
});
|
|
198
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsx(Text, { bold: true, color: "cyan", children: "LetMeCode Usage Dashboard" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Provider " }), sortedProviderStates.map((state) => (_jsx(ProviderTab, { label: state.provider.label, active: state.provider.id === selectedProvider.provider.id, status: state.status, regionRef: getRegionRef(`provider:${state.provider.id}`) }, state.provider.id)))] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "View " }), DETAIL_TABS.map((tab, index) => (_jsx(DetailTab, { label: tab.label, active: index === selectedDetailTabIndex, regionRef: getRegionRef(`vtab:${index}`) }, tab.id)))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexGrow: 1, overflow: "hidden", children: _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) }) }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRow: selectedLimitRow, selectedDayRow: selectedDayRow, selectedModelRow: selectedModelRow }), _jsx(CopilotActionsPanel, { providerState: selectedProvider, actionMessage: copilotActionMessage, selectedActionIndex: selectedCopilotActionIndex }), selectedProvider.status === "ready" && selectedProvider.stats.warnings.length > 0 ? (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] }), _jsx(Text, { color: "gray", children: "Tab provider \u00B7 \u2190/\u2192 view \u00B7 \u2191/\u2193 row \u00B7 q quit" })] }));
|
|
198
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: viewportHeight, overflow: "hidden", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "LetMeCode Usage Dashboard" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: "black", backgroundColor: "green", children: " beta " })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "gray", children: "Provider " }), sortedProviderStates.map((state) => (_jsx(ProviderTab, { label: state.provider.label, active: state.provider.id === selectedProvider.provider.id, status: state.status, regionRef: getRegionRef(`provider:${state.provider.id}`) }, state.provider.id)))] }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "View " }), DETAIL_TABS.map((tab, index) => (_jsx(DetailTab, { label: tab.label, active: index === selectedDetailTabIndex, regionRef: getRegionRef(`vtab:${index}`) }, tab.id)))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [_jsx(Box, { flexGrow: 1, overflow: "hidden", children: _jsx(Box, { ref: contentPanelRef, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: _jsx(ContentPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRowKey: selectedLimitRow ? getLimitRowKey(selectedLimitRow) : undefined, selectedDayKey: selectedDayRow?.dayKey, selectedModelId: selectedModelRow?.modelId, availableHeight: contentPanelHeight }) }) }), _jsx(SelectionDetailsPanel, { providerState: selectedProvider, tabId: selectedDetailTab.id, selectedLimitRow: selectedLimitRow, selectedDayRow: selectedDayRow, selectedModelRow: selectedModelRow }), _jsx(CopilotActionsPanel, { providerState: selectedProvider, actionMessage: copilotActionMessage, selectedActionIndex: selectedCopilotActionIndex }), selectedProvider.status === "ready" && selectedProvider.stats.warnings.length > 0 ? (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", overflow: "hidden", children: [_jsx(Text, { color: "yellow", children: "Warnings" }), selectedProvider.stats.warnings.map((warning) => (_jsx(Text, { children: warning }, warning)))] })) : null] }), _jsx(Text, { color: "gray", children: "Tab provider \u00B7 \u2190/\u2192 view \u00B7 \u2191/\u2193 row \u00B7 q quit" })] }));
|
|
199
199
|
}
|
|
200
200
|
function CopilotActionsPanel(props) {
|
|
201
201
|
if (props.providerState.provider.id !== "copilot") {
|
|
@@ -97,8 +97,8 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
const selectedEvents = [
|
|
100
|
-
...parsedEvents.keyedEvents.values(),
|
|
101
|
-
...parsedEvents.unkeyedEvents
|
|
100
|
+
...new Set(parsedEvents.keyedEvents.values()),
|
|
101
|
+
...parsedEvents.unkeyedEvents
|
|
102
102
|
];
|
|
103
103
|
traceClaude(options.traceLogger, [
|
|
104
104
|
`Transcript selection summary: filesWithMatches=${parseTotals.filesScanned}/${parsedSessionFiles.length}`,
|
|
@@ -128,7 +128,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
128
128
|
warnings.push(`Detected ${parsedEvents.duplicateUsageKeyCollisions} Claude usage key collision(s) with different token usage; merged same-key rows by per-field maxima to avoid double-counting cumulative snapshots.`);
|
|
129
129
|
}
|
|
130
130
|
if (options.verbose && parsedEvents.duplicateUnkeyedEvents > 0) {
|
|
131
|
-
warnings.push(`Collapsed ${parsedEvents.duplicateUnkeyedEvents} duplicate unkeyed Claude usage event(s) by usage signature.`);
|
|
131
|
+
warnings.push(`Collapsed ${parsedEvents.duplicateUnkeyedEvents} adjacent duplicate unkeyed Claude usage event(s) by usage signature.`);
|
|
132
132
|
}
|
|
133
133
|
const modelUsage = [...byModel.entries()]
|
|
134
134
|
.map(([modelId, totals]) => ({ modelId, totals }))
|
|
@@ -446,12 +446,14 @@ async function parseSessionFile(filePath, sessionsRoot) {
|
|
|
446
446
|
const entrypoint = typeof payloadObject.entrypoint === "string" ? payloadObject.entrypoint : "";
|
|
447
447
|
const rateLimits = extractRateLimits(payloadObject, message);
|
|
448
448
|
const normalizedUsage = normalizeUsage(usage);
|
|
449
|
-
const
|
|
449
|
+
const usageKeys = buildUsageEventKeys(payloadObject, message);
|
|
450
450
|
const usageSignature = buildUsageSignature(payloadObject, modelId, normalizedUsage);
|
|
451
451
|
assistantEntryPoints.add(entrypoint);
|
|
452
452
|
events.push({
|
|
453
453
|
entrypoint,
|
|
454
|
-
|
|
454
|
+
filePath,
|
|
455
|
+
lineNumber: linesRead,
|
|
456
|
+
usageKeys,
|
|
455
457
|
usageSignature,
|
|
456
458
|
timestampMs: eventTimeMs,
|
|
457
459
|
modelId,
|
|
@@ -556,14 +558,14 @@ function extractStringArray(value) {
|
|
|
556
558
|
}
|
|
557
559
|
return value.filter((item) => typeof item === "string");
|
|
558
560
|
}
|
|
559
|
-
function
|
|
561
|
+
function buildUsageEventKeys(payloadObject, message) {
|
|
560
562
|
const sessionId = String(payloadObject.sessionId ?? "");
|
|
561
563
|
const requestId = typeof payloadObject.requestId === "string" ? payloadObject.requestId : "";
|
|
562
564
|
const messageId = typeof message?.id === "string" ? message.id : "";
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
565
|
+
return [...new Set([
|
|
566
|
+
requestId ? `${sessionId}|request:${requestId}` : "",
|
|
567
|
+
messageId ? `${sessionId}|message:${messageId}` : ""
|
|
568
|
+
].filter(Boolean))];
|
|
567
569
|
}
|
|
568
570
|
function buildUsageSignature(payloadObject, modelId, usage) {
|
|
569
571
|
return buildUsageSignatureFromParts(String(payloadObject.sessionId ?? ""), modelId, usage);
|
|
@@ -584,44 +586,66 @@ function buildUsageSignatureFromParts(sessionId, modelId, usage) {
|
|
|
584
586
|
function createParsedUsageEventAccumulator() {
|
|
585
587
|
return {
|
|
586
588
|
keyedEvents: new Map(),
|
|
587
|
-
unkeyedEvents:
|
|
589
|
+
unkeyedEvents: [],
|
|
590
|
+
lastUnkeyedEventsBySignature: new Map(),
|
|
588
591
|
duplicateUsageKeys: 0,
|
|
589
592
|
duplicateUsageKeyCollisions: 0,
|
|
590
593
|
duplicateUnkeyedEvents: 0
|
|
591
594
|
};
|
|
592
595
|
}
|
|
593
596
|
function recordParsedUsageEvent(parsedEvents, event) {
|
|
594
|
-
if (event.
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
597
|
+
if (event.usageKeys.length > 0) {
|
|
598
|
+
const previousMatches = [...new Set(event.usageKeys
|
|
599
|
+
.map((usageKey) => parsedEvents.keyedEvents.get(usageKey))
|
|
600
|
+
.filter((candidate) => Boolean(candidate)))];
|
|
601
|
+
if (previousMatches.length === 0) {
|
|
602
|
+
for (const usageKey of event.usageKeys) {
|
|
603
|
+
parsedEvents.keyedEvents.set(usageKey, event);
|
|
604
|
+
}
|
|
598
605
|
return;
|
|
599
606
|
}
|
|
600
607
|
parsedEvents.duplicateUsageKeys += 1;
|
|
601
|
-
|
|
608
|
+
const distinctUsageSignatures = new Set([
|
|
609
|
+
event.usageSignature,
|
|
610
|
+
...previousMatches.map((candidate) => candidate.usageSignature)
|
|
611
|
+
]);
|
|
612
|
+
if (distinctUsageSignatures.size > 1) {
|
|
602
613
|
parsedEvents.duplicateUsageKeyCollisions += 1;
|
|
603
614
|
}
|
|
604
|
-
|
|
615
|
+
const mergedEvent = previousMatches.reduce(mergeParsedUsageEvents, event);
|
|
616
|
+
for (const usageKey of mergedEvent.usageKeys) {
|
|
617
|
+
parsedEvents.keyedEvents.set(usageKey, mergedEvent);
|
|
618
|
+
}
|
|
605
619
|
return;
|
|
606
620
|
}
|
|
607
|
-
const
|
|
608
|
-
if (!
|
|
609
|
-
parsedEvents.unkeyedEvents.
|
|
621
|
+
const previousRecord = parsedEvents.lastUnkeyedEventsBySignature.get(event.usageSignature);
|
|
622
|
+
if (!previousRecord || !canCollapseAdjacentUnkeyedUsageEvents(previousRecord.event, event)) {
|
|
623
|
+
parsedEvents.unkeyedEvents.push(event);
|
|
624
|
+
parsedEvents.lastUnkeyedEventsBySignature.set(event.usageSignature, {
|
|
625
|
+
event,
|
|
626
|
+
index: parsedEvents.unkeyedEvents.length - 1
|
|
627
|
+
});
|
|
610
628
|
return;
|
|
611
629
|
}
|
|
612
630
|
parsedEvents.duplicateUnkeyedEvents += 1;
|
|
613
|
-
if (normalizeTimestamp(event.timestampMs) > normalizeTimestamp(
|
|
614
|
-
parsedEvents.unkeyedEvents.
|
|
631
|
+
if (normalizeTimestamp(event.timestampMs) > normalizeTimestamp(previousRecord.event.timestampMs)) {
|
|
632
|
+
parsedEvents.unkeyedEvents[previousRecord.index] = event;
|
|
633
|
+
parsedEvents.lastUnkeyedEventsBySignature.set(event.usageSignature, {
|
|
634
|
+
event,
|
|
635
|
+
index: previousRecord.index
|
|
636
|
+
});
|
|
615
637
|
}
|
|
616
638
|
}
|
|
617
639
|
function mergeParsedUsageEvents(previous, next) {
|
|
618
640
|
const mergedUsage = mergeClaudeUsage(previous.usage, next.usage);
|
|
619
641
|
const modelId = selectMergedEventModelId(previous, next);
|
|
620
642
|
const latestEvent = normalizeTimestamp(next.timestampMs) >= normalizeTimestamp(previous.timestampMs) ? next : previous;
|
|
621
|
-
const sessionId = extractUsageKeySessionId(previous.
|
|
643
|
+
const sessionId = extractUsageKeySessionId(previous.usageKeys) || extractUsageKeySessionId(next.usageKeys);
|
|
622
644
|
return {
|
|
623
645
|
entrypoint: latestEvent.entrypoint || previous.entrypoint || next.entrypoint,
|
|
624
|
-
|
|
646
|
+
filePath: latestEvent.filePath,
|
|
647
|
+
lineNumber: latestEvent.lineNumber,
|
|
648
|
+
usageKeys: [...new Set([...previous.usageKeys, ...next.usageKeys])],
|
|
625
649
|
usageSignature: buildUsageSignatureFromParts(sessionId, modelId, mergedUsage),
|
|
626
650
|
timestampMs: Math.max(normalizeTimestamp(previous.timestampMs), normalizeTimestamp(next.timestampMs)),
|
|
627
651
|
modelId,
|
|
@@ -655,7 +679,11 @@ function selectMergedEventModelId(previous, next) {
|
|
|
655
679
|
? next.modelId
|
|
656
680
|
: previous.modelId;
|
|
657
681
|
}
|
|
658
|
-
function
|
|
682
|
+
function canCollapseAdjacentUnkeyedUsageEvents(previous, next) {
|
|
683
|
+
return previous.filePath === next.filePath && next.lineNumber === previous.lineNumber + 1;
|
|
684
|
+
}
|
|
685
|
+
function extractUsageKeySessionId(usageKeys) {
|
|
686
|
+
const usageKey = usageKeys[0];
|
|
659
687
|
if (!usageKey) {
|
|
660
688
|
return "";
|
|
661
689
|
}
|