sidekick-shared 0.28.2 → 0.29.0
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/CHANGELOG.md +83 -0
- package/README.md +176 -30
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +14 -6
- package/dist/browser.js +26 -1
- package/dist/codex.d.ts +23 -0
- package/dist/codex.js +44 -0
- package/dist/codexProfiles.d.ts +1 -0
- package/dist/codexProfiles.js +8 -11
- package/dist/codexQuota.js +17 -4
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +6 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/codexQuota.js +18 -5
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +12 -9
- package/dist/esm/modelCatalog.js +2 -1
- package/dist/esm/modelInfo.js +422 -45
- package/dist/esm/multiProviderQuotaService.js +1 -1
- package/dist/esm/node.js +9 -3
- package/dist/esm/parsers/codexParser.js +175 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +83 -21
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +14 -7
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/codex.js +2 -24
- package/dist/esm/providers/codexDatabase.js +3 -19
- package/dist/esm/providers/codexRollouts.js +24 -1
- package/dist/esm/providers/openCode.js +9 -17
- package/dist/esm/quota.js +356 -24
- package/dist/esm/quotaHistory.js +33 -8
- package/dist/esm/quotaPoller.js +5 -2
- package/dist/esm/quotaResolver.js +31 -6
- package/dist/esm/quotaSnapshots.js +65 -10
- package/dist/esm/quotaThresholds.js +7 -2
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/index.js +2 -2
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/quota.js +30 -0
- package/dist/esm/schemas/quotaHistory.js +10 -2
- package/dist/esm/schemas/stateFile.js +9 -0
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/stateFile.js +18 -3
- package/dist/esm/statusline/claudeStatuslinePayload.js +16 -4
- package/dist/esm/statusline/formatter.js +57 -18
- package/dist/esm/statusline/index.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +42 -3
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/jsonlTail.js +378 -59
- package/dist/esm/zaiQuotaApi.js +4 -2
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -16
- package/dist/index.js +48 -12
- package/dist/modelCatalog.js +2 -1
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +431 -47
- package/dist/multiProviderQuotaService.js +1 -1
- package/dist/node.d.ts +16 -5
- package/dist/node.js +31 -2
- package/dist/parsers/codexParser.d.ts +55 -0
- package/dist/parsers/codexParser.js +175 -54
- package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
- package/dist/parsers/codexRolloutOrigin.js +111 -0
- package/dist/parsers/openCodeParser.d.ts +38 -0
- package/dist/parsers/openCodeParser.js +86 -21
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +13 -6
- package/dist/promptHistory.d.ts +65 -4
- package/dist/promptHistory.js +68 -5
- package/dist/providerFailure.d.ts +44 -0
- package/dist/providerFailure.js +126 -4
- package/dist/providers/codex.d.ts +3 -22
- package/dist/providers/codex.js +3 -26
- package/dist/providers/codexDatabase.d.ts +3 -2
- package/dist/providers/codexDatabase.js +4 -22
- package/dist/providers/codexRollouts.d.ts +22 -0
- package/dist/providers/codexRollouts.js +24 -0
- package/dist/providers/openCode.js +8 -16
- package/dist/quota.d.ts +131 -1
- package/dist/quota.js +366 -24
- package/dist/quotaHistory.d.ts +10 -0
- package/dist/quotaHistory.js +34 -8
- package/dist/quotaPoller.d.ts +11 -3
- package/dist/quotaPoller.js +5 -2
- package/dist/quotaResolver.js +30 -5
- package/dist/quotaSnapshots.js +64 -9
- package/dist/quotaThresholds.js +7 -2
- package/dist/report/htmlReportGenerator.js +42 -5
- package/dist/report/transcriptParser.d.ts +10 -1
- package/dist/report/transcriptParser.js +13 -11
- package/dist/schemas/index.d.ts +3 -3
- package/dist/schemas/index.js +5 -2
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/quota.d.ts +258 -0
- package/dist/schemas/quota.js +31 -1
- package/dist/schemas/quotaHistory.d.ts +2 -0
- package/dist/schemas/quotaHistory.js +10 -2
- package/dist/schemas/stateFile.d.ts +34 -0
- package/dist/schemas/stateFile.js +10 -1
- package/dist/schemas/transcript.d.ts +2 -0
- package/dist/schemas/transcript.js +4 -0
- package/dist/sessionPromptHistory.d.ts +7 -1
- package/dist/sessionPromptHistory.js +1 -1
- package/dist/stateFile.d.ts +18 -0
- package/dist/stateFile.js +17 -2
- package/dist/statusline/claudeStatuslinePayload.d.ts +4 -3
- package/dist/statusline/claudeStatuslinePayload.js +16 -4
- package/dist/statusline/formatter.d.ts +5 -0
- package/dist/statusline/formatter.js +56 -17
- package/dist/statusline/index.d.ts +4 -4
- package/dist/statusline/index.js +5 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -0
- package/dist/testing/credentialGuard.d.ts +14 -0
- package/dist/testing/credentialGuard.js +87 -5
- package/dist/toolCallOrigins.d.ts +70 -0
- package/dist/toolCallOrigins.js +346 -0
- package/dist/transcript.d.ts +12 -0
- package/dist/transcript.js +42 -3
- package/dist/types/opencode.d.ts +2 -0
- package/dist/usage/usageEvents.d.ts +54 -6
- package/dist/usage/usageEvents.js +146 -17
- package/dist/usage/usageReports.d.ts +35 -4
- package/dist/usage/usageReports.js +39 -6
- package/dist/watchers/jsonlTail.d.ts +101 -4
- package/dist/watchers/jsonlTail.js +380 -60
- package/dist/zaiQuotaApi.js +3 -1
- package/package.json +41 -1
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { getConfigDir } from './paths';
|
|
4
4
|
import { atomicWriteJsonSync, withFileLockSync } from './writers/atomic';
|
|
5
|
-
import { classifyQuotaFreshness } from './quota';
|
|
5
|
+
import { classifyQuotaFreshness, extraQuotaRows } from './quota';
|
|
6
6
|
import { isAggregateCodexLimit } from './types/codex';
|
|
7
7
|
function getQuotaSnapshotPath() {
|
|
8
8
|
return path.join(getConfigDir(), 'quota-snapshots.json');
|
|
@@ -46,6 +46,27 @@ function windowResetMs(value) {
|
|
|
46
46
|
const ms = Date.parse(value);
|
|
47
47
|
return Number.isFinite(ms) ? ms : 0;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Rank two samples by one window's reset time. A later reset means a newer
|
|
51
|
+
* window, so it wins — except that an existing window which had already reset
|
|
52
|
+
* by the time the newer sample was captured is obsolete and always yields.
|
|
53
|
+
* Without that exception a pre-reset sample outranks every later sample whose
|
|
54
|
+
* window has not started yet (empty reset), and the snapshot freezes.
|
|
55
|
+
* Returns `undefined` when the reset times tie.
|
|
56
|
+
*/
|
|
57
|
+
function compareWindowResets(existing, next, asOfMs) {
|
|
58
|
+
const existingReset = windowResetMs(existing.resetsAt);
|
|
59
|
+
const nextReset = windowResetMs(next.resetsAt);
|
|
60
|
+
if (existingReset === nextReset)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (existingReset > 0 && asOfMs > 0 && existingReset <= asOfMs)
|
|
63
|
+
return false;
|
|
64
|
+
return existingReset > nextReset;
|
|
65
|
+
}
|
|
66
|
+
function windowPresenceChanged(existing, next) {
|
|
67
|
+
return (Boolean(existing.fiveHour.absent) !== Boolean(next.fiveHour.absent) ||
|
|
68
|
+
Boolean(existing.sevenDay.absent) !== Boolean(next.sevenDay.absent));
|
|
69
|
+
}
|
|
49
70
|
// Preserve the best-known same-window snapshot while still allowing lower
|
|
50
71
|
// utilization after Codex advances to a newer reset window.
|
|
51
72
|
function shouldKeepExistingSnapshot(existing, next) {
|
|
@@ -58,20 +79,50 @@ function shouldKeepExistingSnapshot(existing, next) {
|
|
|
58
79
|
const nextAggregate = isAggregateCodexLimit(next.limitId);
|
|
59
80
|
if (existingAggregate !== nextAggregate)
|
|
60
81
|
return existingAggregate;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
// The plan's window layout changed (a window appeared or disappeared): the
|
|
83
|
+
// reset times are not comparable, so the newer capture wins.
|
|
84
|
+
if (windowPresenceChanged(existing, next)) {
|
|
85
|
+
return snapshotTimeMs(existing) > snapshotTimeMs(next);
|
|
86
|
+
}
|
|
87
|
+
const asOfMs = snapshotTimeMs(next);
|
|
88
|
+
const primary = compareWindowResets(existing.fiveHour, next.fiveHour, asOfMs);
|
|
89
|
+
if (primary !== undefined)
|
|
90
|
+
return primary;
|
|
91
|
+
const secondary = compareWindowResets(existing.sevenDay, next.sevenDay, asOfMs);
|
|
92
|
+
if (secondary !== undefined)
|
|
93
|
+
return secondary;
|
|
69
94
|
const existingUtilization = existing.fiveHour.utilization + existing.sevenDay.utilization;
|
|
70
95
|
const nextUtilization = next.fiveHour.utilization + next.sevenDay.utilization;
|
|
71
96
|
if (existingUtilization !== nextUtilization)
|
|
72
97
|
return existingUtilization > nextUtilization;
|
|
73
98
|
return snapshotTimeMs(existing) > snapshotTimeMs(next);
|
|
74
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Status-line and history samples only know the two headline windows. Keep
|
|
102
|
+
* the plan label, extra usage, and still-running extra meters (e.g. a Fable
|
|
103
|
+
* weekly cap) an earlier API sample recorded, so a fresh status-line write
|
|
104
|
+
* does not erase them from every snapshot reader.
|
|
105
|
+
*/
|
|
106
|
+
function carryApiOnlyFields(existing, next) {
|
|
107
|
+
const merged = { ...next };
|
|
108
|
+
if (merged.planLabel === undefined && existing.planLabel !== undefined) {
|
|
109
|
+
merged.planLabel = existing.planLabel;
|
|
110
|
+
}
|
|
111
|
+
if (merged.planType === undefined && existing.planType !== undefined) {
|
|
112
|
+
merged.planType = existing.planType;
|
|
113
|
+
}
|
|
114
|
+
if (merged.extraUsage === undefined && existing.extraUsage !== undefined) {
|
|
115
|
+
merged.extraUsage = existing.extraUsage;
|
|
116
|
+
}
|
|
117
|
+
if (merged.limits === undefined && existing.limits !== undefined) {
|
|
118
|
+
const asOfMs = snapshotTimeMs(next);
|
|
119
|
+
// Headline meters duplicate the fresher windows on `next`; carry only the extras.
|
|
120
|
+
const running = extraQuotaRows(existing).filter((row) => windowResetMs(row.resetsAt) > asOfMs);
|
|
121
|
+
if (running.length > 0)
|
|
122
|
+
merged.limits = running;
|
|
123
|
+
}
|
|
124
|
+
return merged;
|
|
125
|
+
}
|
|
75
126
|
export function writeQuotaSnapshot(providerId, accountId, quota) {
|
|
76
127
|
withSnapshotStoreLock(() => {
|
|
77
128
|
const store = readStore();
|
|
@@ -93,7 +144,11 @@ export function writeQuotaSnapshot(providerId, accountId, quota) {
|
|
|
93
144
|
};
|
|
94
145
|
if (index >= 0 && shouldKeepExistingSnapshot(store.snapshots[index].quota, snapshot))
|
|
95
146
|
return;
|
|
96
|
-
const record = {
|
|
147
|
+
const record = {
|
|
148
|
+
providerId,
|
|
149
|
+
accountId,
|
|
150
|
+
quota: existingQuota ? carryApiOnlyFields(existingQuota, snapshot) : snapshot,
|
|
151
|
+
};
|
|
97
152
|
if (index >= 0)
|
|
98
153
|
store.snapshots[index] = record;
|
|
99
154
|
else
|
|
@@ -50,8 +50,13 @@ function evaluateWindow(window, quota, thresholds, previous) {
|
|
|
50
50
|
export function evaluateQuotaThresholds(quota, config = DEFAULT_QUOTA_THRESHOLDS, memory = {}) {
|
|
51
51
|
if (!quota.available)
|
|
52
52
|
return { alerts: [], memory };
|
|
53
|
-
|
|
54
|
-
const
|
|
53
|
+
// A window the plan does not have (Ultra's missing five-hour limit) never alerts.
|
|
54
|
+
const fiveHour = quota.fiveHour.absent
|
|
55
|
+
? { alert: null, memory: memory.fiveHour ?? { resetsAt: '', level: 0 } }
|
|
56
|
+
: evaluateWindow('fiveHour', quota.fiveHour, config.fiveHour, memory.fiveHour);
|
|
57
|
+
const sevenDay = quota.sevenDay.absent
|
|
58
|
+
? { alert: null, memory: memory.sevenDay ?? { resetsAt: '', level: 0 } }
|
|
59
|
+
: evaluateWindow('sevenDay', quota.sevenDay, config.sevenDay, memory.sevenDay);
|
|
55
60
|
return {
|
|
56
61
|
alerts: [fiveHour.alert, sevenDay.alert].filter((alert) => alert !== null),
|
|
57
62
|
memory: { fiveHour: fiveHour.memory, sevenDay: sevenDay.memory },
|
|
@@ -596,14 +596,51 @@ function renderContentBlock(block, includeThinking, includeToolDetail) {
|
|
|
596
596
|
return '';
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
|
-
|
|
599
|
+
/** Prefix of the bookkeeping keys session parsers stash inside tool inputs. */
|
|
600
|
+
const SIDEKICK_INPUT_KEY_PREFIX = '_sidekick';
|
|
601
|
+
/**
|
|
602
|
+
* The tool's display identity and user-facing input. Entries built outside
|
|
603
|
+
* the canonical projection may still carry the parsers' `_sidekick*` keys in
|
|
604
|
+
* `toolInput`; those are hidden here and read as the raw identity.
|
|
605
|
+
*/
|
|
606
|
+
function toolDisplay(block) {
|
|
600
607
|
const name = block.toolName || 'unknown';
|
|
608
|
+
const raw = block.toolInput;
|
|
609
|
+
let input = raw;
|
|
610
|
+
let rawToolName = block.rawToolName;
|
|
611
|
+
let mcpServerName = block.mcpServerName;
|
|
612
|
+
if (raw && Object.keys(raw).some((key) => key.startsWith(SIDEKICK_INPUT_KEY_PREFIX))) {
|
|
613
|
+
const clean = {};
|
|
614
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
615
|
+
if (!key.startsWith(SIDEKICK_INPUT_KEY_PREFIX))
|
|
616
|
+
clean[key] = value;
|
|
617
|
+
}
|
|
618
|
+
input = clean;
|
|
619
|
+
if (!rawToolName && typeof raw._sidekickRawToolName === 'string') {
|
|
620
|
+
rawToolName = raw._sidekickRawToolName;
|
|
621
|
+
}
|
|
622
|
+
if (!mcpServerName && typeof raw._sidekickMcpServerName === 'string') {
|
|
623
|
+
mcpServerName = raw._sidekickMcpServerName;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const parts = [];
|
|
627
|
+
if (rawToolName && rawToolName.trim() && rawToolName !== name)
|
|
628
|
+
parts.push(rawToolName);
|
|
629
|
+
if (mcpServerName && mcpServerName.trim())
|
|
630
|
+
parts.push(`MCP: ${mcpServerName}`);
|
|
631
|
+
const hint = parts.length
|
|
632
|
+
? ` <span class="tool-raw-name" style="color:var(--text-muted);font-size:11px">${escapeHtml(parts.join(' · '))}</span>`
|
|
633
|
+
: '';
|
|
634
|
+
return { name, hint, input };
|
|
635
|
+
}
|
|
636
|
+
function renderToolUse(block, includeDetail) {
|
|
637
|
+
const { name, hint, input } = toolDisplay(block);
|
|
601
638
|
if (!includeDetail) {
|
|
602
|
-
return `<div class="tool-block" data-block-type="tool"><div style="padding:8px 12px;background:rgba(245,166,35,0.08);color:var(--accent-orange);font-family:monospace;font-size:13px;">${escapeHtml(name)}</div></div>`;
|
|
639
|
+
return `<div class="tool-block" data-block-type="tool"><div style="padding:8px 12px;background:rgba(245,166,35,0.08);color:var(--accent-orange);font-family:monospace;font-size:13px;">${escapeHtml(name)}${hint}</div></div>`;
|
|
603
640
|
}
|
|
604
641
|
let inputHtml = '';
|
|
605
|
-
if (
|
|
606
|
-
const inputJson = JSON.stringify(
|
|
642
|
+
if (input && Object.keys(input).length > 0) {
|
|
643
|
+
const inputJson = JSON.stringify(input, null, 2);
|
|
607
644
|
const highlighted = highlightCodeBlock(inputJson, 'json');
|
|
608
645
|
inputHtml = `<div class="tool-io">
|
|
609
646
|
<div class="tool-label">Input</div>
|
|
@@ -611,7 +648,7 @@ function renderToolUse(block, includeDetail) {
|
|
|
611
648
|
</div>`;
|
|
612
649
|
}
|
|
613
650
|
return `<details class="tool-block" data-block-type="tool">
|
|
614
|
-
<summary>${escapeHtml(name)}${block.toolUseId ? ` <span style="color:var(--text-muted);font-size:11px">${escapeHtml(block.toolUseId)}</span>` : ''}</summary>
|
|
651
|
+
<summary>${escapeHtml(name)}${hint}${block.toolUseId ? ` <span style="color:var(--text-muted);font-size:11px">${escapeHtml(block.toolUseId)}</span>` : ''}</summary>
|
|
615
652
|
<div class="tool-content">
|
|
616
653
|
${inputHtml}
|
|
617
654
|
</div>
|
|
@@ -42,17 +42,19 @@ function toReportBlock(block) {
|
|
|
42
42
|
return block.text ? [{ type: 'text', text: block.text }] : [];
|
|
43
43
|
case 'thinking':
|
|
44
44
|
return block.text ? [{ type: 'thinking', text: block.text }] : [];
|
|
45
|
-
case 'tool_use':
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
|
|
45
|
+
case 'tool_use': {
|
|
46
|
+
const toolBlock = {
|
|
47
|
+
type: 'tool_use',
|
|
48
|
+
toolName: block.toolName ?? 'unknown',
|
|
49
|
+
toolInput: block.input && typeof block.input === 'object'
|
|
50
|
+
? block.input
|
|
51
|
+
: {},
|
|
52
|
+
toolUseId: block.toolUseId,
|
|
53
|
+
...(block.rawToolName ? { rawToolName: block.rawToolName } : {}),
|
|
54
|
+
...(block.mcpServerName ? { mcpServerName: block.mcpServerName } : {}),
|
|
55
|
+
};
|
|
56
|
+
return [toolBlock];
|
|
57
|
+
}
|
|
56
58
|
case 'tool_result':
|
|
57
59
|
return [
|
|
58
60
|
{
|
|
@@ -17,9 +17,9 @@ export { tokenEstimateSchema } from './tokenEstimation';
|
|
|
17
17
|
export { providerAuthenticationEvidenceSchema, providerFailureEvidenceSchema, providerRetryAfterSchema, providerFailureDiagnosisSchema, } from './providerFailure';
|
|
18
18
|
export { transcriptSourceProvenanceSchema, canonicalTranscriptBlockSchema, canonicalToolCallSchema, canonicalTranscriptMessageSchema, canonicalSessionTranscriptSchema, } from './transcript';
|
|
19
19
|
export { observationProvenanceV1Schema, observedAgentSessionV1Schema, observedValueV1Schema, pendingUserRequestV1Schema, providerCapabilitiesV1Schema, providerSessionAdapterV1Schema, sessionEvidenceRefV1Schema, } from './observedSessionV1';
|
|
20
|
-
export { quotaWindowSchema, quotaStateSchema, quotaFailureKindSchema, quotaProviderIdSchema, quotaSourceSchema, codexResetCreditSchema, codexResetCreditsSnapshotSchema, peakHoursStateSchema, quotaFailureDescriptorSchema, runtimeQuotaProviderSchema, providerQuotaStateSchema, claudeProviderQuotaStateSchema, codexProviderQuotaStateSchema, zaiProviderQuotaStateSchema, providerQuotaMapSchema, } from './quota';
|
|
20
|
+
export { quotaWindowSchema, quotaStateSchema, quotaLimitRowSchema, quotaExtraUsageSchema, quotaFailureKindSchema, quotaProviderIdSchema, quotaSourceSchema, codexResetCreditSchema, codexResetCreditsSnapshotSchema, peakHoursStateSchema, quotaFailureDescriptorSchema, runtimeQuotaProviderSchema, providerQuotaStateSchema, claudeProviderQuotaStateSchema, codexProviderQuotaStateSchema, zaiProviderQuotaStateSchema, providerQuotaMapSchema, } from './quota';
|
|
21
21
|
export { quotaHistoryRuntimeProviderSchema, quotaHistorySampleSchema, quotaHistoryDailyBucketSchema, } from './quotaHistory';
|
|
22
22
|
export { activeProviderAccountStatusSchema, activeAccountStatusSchema } from './accountStatus';
|
|
23
23
|
export { accountProviderIdSchema, accountManagerResultSchema, beginAccountLoginResultSchema, accountLoginStatusSchema, accountEntrySchema, savedAccountProfileSchema, listAllAccountsResultSchema, accountHealthStateSchema, accountHealthSourceSchema, accountHealthSidecarSchema, accountHealthSchema, accountViewSchema, runningProcessSchema, accountConsumerKindSchema, runningAccountConsumerSchema, switchAccountResultSchema, lastSwitchRecordSchema, providerSyncReportSchema, syncReportSchema, accountLaunchEnvSchema, } from './accountManager';
|
|
24
24
|
export { assistantTurnEventSchema, assistantTurnEventTypeSchema, assistantTurnNarrationStepSchema, assistantTurnProcessSchema, assistantTurnProcessStepSchema, assistantTurnProjectionSchema, assistantTurnReasoningTimelineItemSchema, assistantTurnSubagentSchema, assistantTurnSubagentStatusSchema, assistantTurnTimelineItemSchema, assistantTurnToolGroupStepSchema, assistantTurnToolRefSchema, } from './assistantTurn';
|
|
25
|
-
export { sidekickStateFileSchema, stateFileAccountSchema, stateFileBillingBlockSchema, stateFileContextSchema, stateFileQuotaSchema, stateFileQuotaWindowSchema, stateFileSessionSchema, stateFileWriterSchema, } from './stateFile';
|
|
25
|
+
export { sidekickStateFileSchema, stateFileAccountSchema, stateFileBillingBlockSchema, stateFileContextSchema, stateFileQuotaSchema, stateFileQuotaWindowSchema, stateFileQuotaLimitSchema, stateFileSessionSchema, stateFileWriterSchema, } from './stateFile';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { ACCOUNT_PROVIDER_IDS } from '../providerIds';
|
|
12
|
-
import { PROVIDER_AUTHENTICATION_SOURCES, PROVIDER_AUTHENTICATION_STATES, PROVIDER_CREDENTIAL_KINDS, PROVIDER_FAILURE_CODES, PROVIDER_FAILURE_EVIDENCE_SOURCES, PROVIDER_RECOVERY_ACTIONS, } from '../providerFailure';
|
|
12
|
+
import { CODEX_ERROR_INFO_KINDS, PROVIDER_AUTHENTICATION_SOURCES, PROVIDER_AUTHENTICATION_STATES, PROVIDER_CREDENTIAL_KINDS, PROVIDER_FAILURE_CODES, PROVIDER_FAILURE_EVIDENCE_SOURCES, PROVIDER_RECOVERY_ACTIONS, } from '../providerFailure';
|
|
13
13
|
export const providerAuthenticationEvidenceSchema = z.object({
|
|
14
14
|
credentialKind: z.enum(PROVIDER_CREDENTIAL_KINDS),
|
|
15
15
|
state: z.enum(PROVIDER_AUTHENTICATION_STATES),
|
|
@@ -20,6 +20,7 @@ export const providerFailureEvidenceSchema = z.object({
|
|
|
20
20
|
source: z.enum(PROVIDER_FAILURE_EVIDENCE_SOURCES),
|
|
21
21
|
rule: z.enum(PROVIDER_FAILURE_CODES),
|
|
22
22
|
checkedAt: z.string().optional(),
|
|
23
|
+
codexErrorInfo: z.enum(CODEX_ERROR_INFO_KINDS).optional(),
|
|
23
24
|
});
|
|
24
25
|
export const providerRetryAfterSchema = z.discriminatedUnion('kind', [
|
|
25
26
|
z.object({ kind: z.literal('delay'), delayMs: z.number() }),
|
|
@@ -30,6 +31,7 @@ export const providerFailureDiagnosisSchema = z.object({
|
|
|
30
31
|
credentialKind: z.enum(PROVIDER_CREDENTIAL_KINDS),
|
|
31
32
|
diagnosis: z.enum(PROVIDER_FAILURE_CODES),
|
|
32
33
|
recovery: z.enum(PROVIDER_RECOVERY_ACTIONS),
|
|
34
|
+
retryable: z.boolean().optional(),
|
|
33
35
|
evidence: z.array(providerFailureEvidenceSchema),
|
|
34
36
|
authentication: providerAuthenticationEvidenceSchema.optional(),
|
|
35
37
|
httpStatus: z.number().optional(),
|
|
@@ -14,6 +14,33 @@ import { QUOTA_PROVIDER_IDS, RUNTIME_QUOTA_PROVIDER_IDS } from '../providerIds';
|
|
|
14
14
|
export const quotaWindowSchema = z.object({
|
|
15
15
|
utilization: z.number(),
|
|
16
16
|
resetsAt: z.string(),
|
|
17
|
+
absent: z.boolean().optional(),
|
|
18
|
+
windowMs: z.number().optional(),
|
|
19
|
+
});
|
|
20
|
+
// ── QuotaLimitRow / QuotaExtraUsage ──
|
|
21
|
+
export const quotaLimitRowSchema = z.object({
|
|
22
|
+
kind: z.string(),
|
|
23
|
+
group: z.string(),
|
|
24
|
+
label: z.string(),
|
|
25
|
+
utilization: z.number(),
|
|
26
|
+
resetsAt: z.string(),
|
|
27
|
+
windowMs: z.number().optional(),
|
|
28
|
+
projected: z.number().optional(),
|
|
29
|
+
scopeModel: z.string().optional(),
|
|
30
|
+
scopeSurface: z.string().optional(),
|
|
31
|
+
severity: z.string().optional(),
|
|
32
|
+
isActive: z.boolean().optional(),
|
|
33
|
+
allowancePercentOfWeekly: z.number().optional(),
|
|
34
|
+
usedPercentOfWeekly: z.number().optional(),
|
|
35
|
+
});
|
|
36
|
+
export const quotaExtraUsageSchema = z.object({
|
|
37
|
+
enabled: z.boolean(),
|
|
38
|
+
monthlyLimit: z.number().nullable(),
|
|
39
|
+
usedCredits: z.number().nullable(),
|
|
40
|
+
utilization: z.number().nullable(),
|
|
41
|
+
currency: z.string().optional(),
|
|
42
|
+
decimalPlaces: z.number().optional(),
|
|
43
|
+
disabledReason: z.string().optional(),
|
|
17
44
|
});
|
|
18
45
|
// ── QuotaState ──
|
|
19
46
|
export const quotaFailureKindSchema = z.enum([
|
|
@@ -74,7 +101,10 @@ export const quotaStateSchema = z.object({
|
|
|
74
101
|
credits: z.unknown().optional(),
|
|
75
102
|
resetCredits: codexResetCreditsSnapshotSchema.optional(),
|
|
76
103
|
planType: z.string().optional(),
|
|
104
|
+
planLabel: z.string().optional(),
|
|
77
105
|
rateLimitReachedType: z.string().optional(),
|
|
106
|
+
limits: z.array(quotaLimitRowSchema).optional(),
|
|
107
|
+
extraUsage: quotaExtraUsageSchema.optional(),
|
|
78
108
|
});
|
|
79
109
|
// ── PeakHoursState ──
|
|
80
110
|
export const peakHoursStateSchema = z.object({
|
|
@@ -21,8 +21,16 @@ export const quotaHistorySampleSchema = z.object({
|
|
|
21
21
|
runtimeProvider: quotaHistoryRuntimeProviderSchema,
|
|
22
22
|
providerId: z.string(),
|
|
23
23
|
workspaceId: z.string(),
|
|
24
|
-
fiveHour: z.object({
|
|
25
|
-
|
|
24
|
+
fiveHour: z.object({
|
|
25
|
+
utilization: z.number(),
|
|
26
|
+
resetsAt: z.string(),
|
|
27
|
+
absent: z.boolean().optional(),
|
|
28
|
+
}),
|
|
29
|
+
sevenDay: z.object({
|
|
30
|
+
utilization: z.number(),
|
|
31
|
+
resetsAt: z.string(),
|
|
32
|
+
absent: z.boolean().optional(),
|
|
33
|
+
}),
|
|
26
34
|
available: z.boolean(),
|
|
27
35
|
error: z.string().optional(),
|
|
28
36
|
source: z.enum(['session', 'cache', 'api', 'statusline']).optional(),
|
|
@@ -16,6 +16,13 @@ export const stateFileWriterSchema = z.enum([
|
|
|
16
16
|
export const stateFileQuotaWindowSchema = z.object({
|
|
17
17
|
utilization: z.number(),
|
|
18
18
|
resetsAt: z.string(),
|
|
19
|
+
absent: z.boolean().optional(),
|
|
20
|
+
});
|
|
21
|
+
export const stateFileQuotaLimitSchema = z.object({
|
|
22
|
+
kind: z.string(),
|
|
23
|
+
label: z.string(),
|
|
24
|
+
utilization: z.number(),
|
|
25
|
+
resetsAt: z.string(),
|
|
19
26
|
});
|
|
20
27
|
export const stateFileQuotaSchema = z.object({
|
|
21
28
|
fiveHour: stateFileQuotaWindowSchema,
|
|
@@ -25,6 +32,8 @@ export const stateFileQuotaSchema = z.object({
|
|
|
25
32
|
capturedAt: z.string().nullable(),
|
|
26
33
|
ageMs: z.number().nullable(),
|
|
27
34
|
freshness: z.enum(['fresh', 'aging', 'stale']).nullable(),
|
|
35
|
+
planLabel: z.string().optional(),
|
|
36
|
+
extraLimits: z.array(stateFileQuotaLimitSchema).optional(),
|
|
28
37
|
});
|
|
29
38
|
export const stateFileAccountSchema = z.object({
|
|
30
39
|
providerId: z.enum(['claude-code', 'codex']),
|
|
@@ -17,6 +17,8 @@ export const canonicalTranscriptBlockSchema = z.object({
|
|
|
17
17
|
type: z.enum(['text', 'thinking', 'tool_use', 'tool_result', 'image', 'unknown']),
|
|
18
18
|
text: z.string().optional(),
|
|
19
19
|
toolName: z.string().optional(),
|
|
20
|
+
rawToolName: z.string().optional(),
|
|
21
|
+
mcpServerName: z.string().optional(),
|
|
20
22
|
toolUseId: z.string().optional(),
|
|
21
23
|
input: z.unknown().optional(),
|
|
22
24
|
output: z.unknown().optional(),
|
|
@@ -28,6 +30,8 @@ export const canonicalTranscriptBlockSchema = z.object({
|
|
|
28
30
|
export const canonicalToolCallSchema = z.object({
|
|
29
31
|
id: z.string().optional(),
|
|
30
32
|
name: z.string(),
|
|
33
|
+
rawToolName: z.string().optional(),
|
|
34
|
+
mcpServerName: z.string().optional(),
|
|
31
35
|
timestamp: z.string(),
|
|
32
36
|
input: z.unknown().optional(),
|
|
33
37
|
output: z.unknown().optional(),
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
* and replies or signals that belong to an out-of-scope prompt are dropped.
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from 'fs';
|
|
17
|
-
import { DEFAULT_BOUNDS, compareStrings, emptyStats, listCandidates, resolveRoots, scanFile, } from './promptHistory';
|
|
17
|
+
import { DEFAULT_BOUNDS, PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES, compareStrings, emptyStats, listCandidates, resolveRoots, scanFile, } from './promptHistory';
|
|
18
18
|
import { createSessionRecordExtractor, } from './sessionPromptSignals';
|
|
19
19
|
const DEFAULT_SESSION_BOUNDS = {
|
|
20
20
|
maxSessions: 1000,
|
|
21
21
|
maxSessionBytes: 256 * 1024 * 1024,
|
|
22
22
|
maxTotalBytes: 256 * 1024 * 1024,
|
|
23
|
-
maxRecordBytes:
|
|
23
|
+
maxRecordBytes: PROMPT_HISTORY_ORDINAL_MAX_RECORD_BYTES,
|
|
24
24
|
deadlineMs: 60_000,
|
|
25
25
|
};
|
|
26
26
|
const MAX_EXCLUSIONS = 1000;
|
package/dist/esm/stateFile.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from 'fs';
|
|
17
17
|
import { getGlobalDataPath } from './paths';
|
|
18
|
-
import { classifyQuotaFreshness } from './quota';
|
|
18
|
+
import { classifyQuotaFreshness, extraQuotaRows } from './quota';
|
|
19
19
|
import { atomicWriteJsonSync } from './writers/atomic';
|
|
20
20
|
export const STATE_FILE_SCHEMA_VERSION = 1;
|
|
21
21
|
export const STATE_FILE_NAME = 'state.json';
|
|
@@ -90,14 +90,29 @@ export function quotaToStateFile(quota, now = Date.now()) {
|
|
|
90
90
|
freshness ??= classifyQuotaFreshness(ageMs);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
const extraLimits = extraQuotaRows(quota).map((row) => ({
|
|
94
|
+
kind: row.kind,
|
|
95
|
+
label: row.label,
|
|
96
|
+
utilization: row.utilization,
|
|
97
|
+
resetsAt: row.resetsAt,
|
|
98
|
+
}));
|
|
93
99
|
return {
|
|
94
|
-
fiveHour:
|
|
95
|
-
sevenDay:
|
|
100
|
+
fiveHour: stateFileWindow(quota.fiveHour),
|
|
101
|
+
sevenDay: stateFileWindow(quota.sevenDay),
|
|
96
102
|
source: quota.source ?? null,
|
|
97
103
|
capturedSource: quota.capturedSource ?? null,
|
|
98
104
|
capturedAt: quota.capturedAt ?? null,
|
|
99
105
|
ageMs,
|
|
100
106
|
freshness,
|
|
107
|
+
...(quota.planLabel ? { planLabel: quota.planLabel } : {}),
|
|
108
|
+
...(extraLimits.length > 0 ? { extraLimits } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function stateFileWindow(window) {
|
|
112
|
+
return {
|
|
113
|
+
utilization: window.utilization,
|
|
114
|
+
resetsAt: window.resetsAt,
|
|
115
|
+
...(window.absent ? { absent: true } : {}),
|
|
101
116
|
};
|
|
102
117
|
}
|
|
103
118
|
/** Project a billing block onto the state-file shape (totals only). */
|
|
@@ -115,14 +115,26 @@ export function parseClaudeStatuslinePayload(text) {
|
|
|
115
115
|
raw,
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Claude Code leaves a window out of the document once its reset has passed,
|
|
120
|
+
* and always for a window the plan does not have (Ultra has no five-hour
|
|
121
|
+
* limit). A fallback is carried over only while it can still be current: an
|
|
122
|
+
* `absent` marker from an earlier API read, or a window that has not reset yet.
|
|
123
|
+
* An expired fallback would otherwise be re-saved as a fresh sample forever.
|
|
124
|
+
*/
|
|
125
|
+
function toWindow(live, fallback, nowMs) {
|
|
119
126
|
if (live) {
|
|
120
127
|
return {
|
|
121
128
|
utilization: Math.max(0, live.usedPercentage),
|
|
122
129
|
resetsAt: new Date(live.resetsAt * 1000).toISOString(),
|
|
123
130
|
};
|
|
124
131
|
}
|
|
125
|
-
|
|
132
|
+
if (fallback?.absent)
|
|
133
|
+
return { utilization: 0, resetsAt: '', absent: true };
|
|
134
|
+
const fallbackResetMs = fallback ? Date.parse(fallback.resetsAt) : NaN;
|
|
135
|
+
if (fallback && Number.isFinite(fallbackResetMs) && fallbackResetMs > nowMs)
|
|
136
|
+
return fallback;
|
|
137
|
+
return { utilization: 0, resetsAt: '' };
|
|
126
138
|
}
|
|
127
139
|
/**
|
|
128
140
|
* Build a `QuotaState` from the official rate-limit block. Returns `null`
|
|
@@ -137,8 +149,8 @@ export function quotaFromStatuslinePayload(payload, options = {}) {
|
|
|
137
149
|
const now = options.now ?? new Date();
|
|
138
150
|
const fallback = options.fallback ?? undefined;
|
|
139
151
|
return {
|
|
140
|
-
fiveHour: toWindow(limits.fiveHour, fallback?.fiveHour),
|
|
141
|
-
sevenDay: toWindow(limits.sevenDay, fallback?.sevenDay),
|
|
152
|
+
fiveHour: toWindow(limits.fiveHour, fallback?.fiveHour, now.getTime()),
|
|
153
|
+
sevenDay: toWindow(limits.sevenDay, fallback?.sevenDay, now.getTime()),
|
|
142
154
|
available: true,
|
|
143
155
|
providerId: 'claude-code',
|
|
144
156
|
source: 'statusline',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FIVE_HOUR_WINDOW_MS, formatQuotaAge } from '../quota';
|
|
1
|
+
import { FIVE_HOUR_WINDOW_MS, SEVEN_DAY_WINDOW_MS, formatQuotaAge, quotaWindowApplies, quotaWindowHasData, quotaWindowShortLabel, } from '../quota';
|
|
2
2
|
import { estimateTimeToQuota } from './BurnRateCalculator';
|
|
3
3
|
export function selectStatuslineAccount(input) {
|
|
4
4
|
if (input.accounts.claude.present) {
|
|
@@ -23,6 +23,11 @@ export function selectStatuslineAccount(input) {
|
|
|
23
23
|
* One-line status. Segments, in order, each omitted when unknown:
|
|
24
24
|
* `acct:<label> · 5h 42% resets 14:00 · ~1h20m left · 7d 61% · (2h ago) · ctx 37% · $0.42 · cache 92%`
|
|
25
25
|
*
|
|
26
|
+
* Plans without a five-hour window (Claude Ultra) lead with the weekly window
|
|
27
|
+
* instead: `acct:<label> · 7d 17% resets Fri 01:00 · ~2d3h left · …`. Window
|
|
28
|
+
* labels follow the reported window length, so a Codex weekly primary window
|
|
29
|
+
* reads `7d`, not `5h`.
|
|
30
|
+
*
|
|
26
31
|
* The age segment appears only for cached quota older than five minutes, so
|
|
27
32
|
* a snapshot from yesterday can no longer read like a live figure.
|
|
28
33
|
*/
|
|
@@ -37,20 +42,35 @@ export function formatStatusline(input) {
|
|
|
37
42
|
if (!quota?.available) {
|
|
38
43
|
return joinSegments([`acct:${account}`, 'quota unavailable', ...liveSegments]);
|
|
39
44
|
}
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
const first = describeWindow(quota.fiveHour, '5h', FIVE_HOUR_WINDOW_MS);
|
|
46
|
+
const second = describeWindow(quota.sevenDay, '7d', SEVEN_DAY_WINDOW_MS);
|
|
47
|
+
// Lead with the first window whenever the plan has it (even idle); fall back
|
|
48
|
+
// to the second window when the first does not exist for this plan.
|
|
49
|
+
const leadWithFirst = quotaWindowApplies(quota.fiveHour) || !quotaWindowHasData(quota.sevenDay);
|
|
50
|
+
const lead = leadWithFirst ? first : second;
|
|
51
|
+
const segments = [`acct:${account}`, formatLeadSegment(lead, now)];
|
|
52
|
+
const eta = estimateWindowEta(lead, quota.capturedAt, now);
|
|
44
53
|
if (eta != null)
|
|
45
54
|
segments.push(`~${formatMinutes(eta)} left`);
|
|
46
|
-
if (quota.sevenDay
|
|
47
|
-
segments.push(
|
|
55
|
+
if (leadWithFirst && quotaWindowHasData(quota.sevenDay) && quota.sevenDay.utilization > 0) {
|
|
56
|
+
segments.push(`${second.label} ${Math.round(clampPercent(quota.sevenDay.utilization))}%`);
|
|
48
57
|
}
|
|
49
58
|
if (quota.freshness && quota.freshness !== 'fresh') {
|
|
50
59
|
segments.push(`(${formatQuotaAge(quota.ageMs)})`);
|
|
51
60
|
}
|
|
52
61
|
return joinSegments([...segments, ...liveSegments]);
|
|
53
62
|
}
|
|
63
|
+
function describeWindow(window, label, defaultMs) {
|
|
64
|
+
const windowMs = typeof window.windowMs === 'number' && Number.isFinite(window.windowMs) && window.windowMs > 0
|
|
65
|
+
? window.windowMs
|
|
66
|
+
: defaultMs;
|
|
67
|
+
return { label: quotaWindowShortLabel(window.windowMs, label), window, windowMs };
|
|
68
|
+
}
|
|
69
|
+
function formatLeadSegment(lead, now) {
|
|
70
|
+
const percent = `${lead.label} ${Math.round(clampPercent(lead.window.utilization))}%`;
|
|
71
|
+
const reset = formatReset(lead.window.resetsAt, now);
|
|
72
|
+
return reset ? `${percent} resets ${reset}` : percent;
|
|
73
|
+
}
|
|
54
74
|
function formatLiveSegments(live) {
|
|
55
75
|
if (!live)
|
|
56
76
|
return [];
|
|
@@ -76,22 +96,25 @@ function formatCost(value) {
|
|
|
76
96
|
return value.toFixed(1);
|
|
77
97
|
return value.toFixed(2);
|
|
78
98
|
}
|
|
79
|
-
function estimateWindowEta(
|
|
80
|
-
const
|
|
99
|
+
function estimateWindowEta(lead, capturedAtIso, now) {
|
|
100
|
+
const { window, windowMs } = lead;
|
|
101
|
+
if (window.absent)
|
|
102
|
+
return null;
|
|
103
|
+
const resetAt = Date.parse(window.resetsAt);
|
|
81
104
|
if (!Number.isFinite(resetAt))
|
|
82
105
|
return null;
|
|
83
106
|
const nowMs = now.getTime();
|
|
84
107
|
if (resetAt <= nowMs)
|
|
85
108
|
return null;
|
|
86
|
-
const capturedAt =
|
|
109
|
+
const capturedAt = capturedAtIso ? Date.parse(capturedAtIso) : now.getTime();
|
|
87
110
|
const sampleAt = Number.isFinite(capturedAt) ? capturedAt : now.getTime();
|
|
88
111
|
if (sampleAt >= resetAt)
|
|
89
112
|
return null;
|
|
90
|
-
const elapsedMinutes = (sampleAt - (resetAt -
|
|
91
|
-
if (elapsedMinutes <= 0 ||
|
|
113
|
+
const elapsedMinutes = (sampleAt - (resetAt - windowMs)) / 60_000;
|
|
114
|
+
if (elapsedMinutes <= 0 || window.utilization <= 0)
|
|
92
115
|
return null;
|
|
93
|
-
const burnRate =
|
|
94
|
-
const eta = estimateTimeToQuota(
|
|
116
|
+
const burnRate = window.utilization / elapsedMinutes;
|
|
117
|
+
const eta = estimateTimeToQuota(window.utilization, 100, burnRate);
|
|
95
118
|
const minutesUntilReset = (resetAt - nowMs) / 60_000;
|
|
96
119
|
return eta !== null && eta < minutesUntilReset ? eta : null;
|
|
97
120
|
}
|
|
@@ -103,20 +126,36 @@ function compactLabel(value) {
|
|
|
103
126
|
function clampPercent(value) {
|
|
104
127
|
return Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0;
|
|
105
128
|
}
|
|
106
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Reset time as `HH:MM`, or `Fri 01:00` when it is more than a day away.
|
|
131
|
+
* Returns '' for an empty or unparseable value — never the current time.
|
|
132
|
+
*/
|
|
133
|
+
function formatReset(value, now) {
|
|
134
|
+
if (!value)
|
|
135
|
+
return '';
|
|
107
136
|
const reset = new Date(value);
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
if (Number.isNaN(reset.getTime()))
|
|
138
|
+
return '';
|
|
139
|
+
const time = new Intl.DateTimeFormat(undefined, {
|
|
110
140
|
hour: '2-digit',
|
|
111
141
|
minute: '2-digit',
|
|
112
142
|
hour12: false,
|
|
113
|
-
}).format(
|
|
143
|
+
}).format(reset);
|
|
144
|
+
if (reset.getTime() - now.getTime() <= 86_400_000)
|
|
145
|
+
return time;
|
|
146
|
+
const weekday = new Intl.DateTimeFormat(undefined, { weekday: 'short' }).format(reset);
|
|
147
|
+
return `${weekday} ${time}`;
|
|
114
148
|
}
|
|
115
149
|
function formatMinutes(value) {
|
|
116
150
|
const minutes = Math.max(0, Math.round(value));
|
|
117
151
|
if (minutes < 60)
|
|
118
152
|
return `${minutes}min`;
|
|
119
153
|
const hours = Math.floor(minutes / 60);
|
|
154
|
+
if (hours >= 48) {
|
|
155
|
+
const days = Math.floor(hours / 24);
|
|
156
|
+
const remainderHours = hours % 24;
|
|
157
|
+
return remainderHours ? `${days}d${remainderHours}h` : `${days}d`;
|
|
158
|
+
}
|
|
120
159
|
const remainder = minutes % 60;
|
|
121
160
|
return remainder ? `${hours}h${remainder}m` : `${hours}h`;
|
|
122
161
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { getActiveAccountStatus } from '../accountStatus';
|
|
2
2
|
export { readQuotaSnapshot, writeQuotaSnapshot } from '../quotaSnapshots';
|
|
3
|
-
export { appendQuotaHistorySample, getWorkspaceIdFromPath } from '../quotaHistory';
|
|
4
|
-
export { classifyQuotaFreshness, formatQuotaAge } from '../quota';
|
|
3
|
+
export { appendQuotaHistorySample, getWorkspaceIdFromPath, quotaHistoryWindow, } from '../quotaHistory';
|
|
4
|
+
export { classifyQuotaFreshness, formatQuotaAge, quotaWindowApplies, quotaWindowHasData, quotaWindowShortLabel, } from '../quota';
|
|
5
5
|
export { BurnRateCalculator, estimateTimeToQuota } from './BurnRateCalculator';
|
|
6
6
|
export { formatStatusline, selectStatuslineAccount } from './formatter';
|
|
7
7
|
export { parseClaudeStatuslinePayload, quotaFromStatuslinePayload, } from './claudeStatuslinePayload';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sidekick-shared/statusline/payload` — the pure Claude Code status-line
|
|
3
|
+
* stdin parser on its own, for hosts with a tight bundle budget that render
|
|
4
|
+
* their own line. Loads no account, credential, quota-snapshot, or network
|
|
5
|
+
* code (the full `sidekick-shared/statusline` entry does).
|
|
6
|
+
*
|
|
7
|
+
* @module statusline/payload
|
|
8
|
+
*/
|
|
9
|
+
export { parseClaudeStatuslinePayload, quotaFromStatuslinePayload, } from './claudeStatuslinePayload';
|