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
package/dist/stateFile.d.ts
CHANGED
|
@@ -23,6 +23,20 @@ export interface StateFileQuotaWindow {
|
|
|
23
23
|
utilization: number;
|
|
24
24
|
/** ISO timestamp when the window resets; empty when unknown. */
|
|
25
25
|
resetsAt: string;
|
|
26
|
+
/**
|
|
27
|
+
* Present (true) only when the plan has no such window — e.g. Claude Ultra
|
|
28
|
+
* has no five-hour limit. `utilization`/`resetsAt` are then 0/''.
|
|
29
|
+
*/
|
|
30
|
+
absent?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** An extra usage meter beyond the two headline windows, e.g. a Fable weekly cap. */
|
|
33
|
+
export interface StateFileQuotaLimit {
|
|
34
|
+
/** Server meter kind, e.g. `weekly_scoped`. */
|
|
35
|
+
kind: string;
|
|
36
|
+
/** Display label, e.g. `7-Day (Fable)`. */
|
|
37
|
+
label: string;
|
|
38
|
+
utilization: number;
|
|
39
|
+
resetsAt: string;
|
|
26
40
|
}
|
|
27
41
|
export interface StateFileQuota {
|
|
28
42
|
fiveHour: StateFileQuotaWindow;
|
|
@@ -34,6 +48,10 @@ export interface StateFileQuota {
|
|
|
34
48
|
capturedAt: string | null;
|
|
35
49
|
ageMs: number | null;
|
|
36
50
|
freshness: 'fresh' | 'aging' | 'stale' | null;
|
|
51
|
+
/** Subscription plan name when known, e.g. `Ultra`. Omitted when unknown. */
|
|
52
|
+
planLabel?: string;
|
|
53
|
+
/** Extra meters (per-model weekly caps and similar). Omitted when there are none. */
|
|
54
|
+
extraLimits?: StateFileQuotaLimit[];
|
|
37
55
|
}
|
|
38
56
|
export interface StateFileAccount {
|
|
39
57
|
providerId: 'claude-code' | 'codex';
|
package/dist/stateFile.js
CHANGED
|
@@ -131,14 +131,29 @@ function quotaToStateFile(quota, now = Date.now()) {
|
|
|
131
131
|
freshness ??= (0, quota_1.classifyQuotaFreshness)(ageMs);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
const extraLimits = (0, quota_1.extraQuotaRows)(quota).map((row) => ({
|
|
135
|
+
kind: row.kind,
|
|
136
|
+
label: row.label,
|
|
137
|
+
utilization: row.utilization,
|
|
138
|
+
resetsAt: row.resetsAt,
|
|
139
|
+
}));
|
|
134
140
|
return {
|
|
135
|
-
fiveHour:
|
|
136
|
-
sevenDay:
|
|
141
|
+
fiveHour: stateFileWindow(quota.fiveHour),
|
|
142
|
+
sevenDay: stateFileWindow(quota.sevenDay),
|
|
137
143
|
source: quota.source ?? null,
|
|
138
144
|
capturedSource: quota.capturedSource ?? null,
|
|
139
145
|
capturedAt: quota.capturedAt ?? null,
|
|
140
146
|
ageMs,
|
|
141
147
|
freshness,
|
|
148
|
+
...(quota.planLabel ? { planLabel: quota.planLabel } : {}),
|
|
149
|
+
...(extraLimits.length > 0 ? { extraLimits } : {}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function stateFileWindow(window) {
|
|
153
|
+
return {
|
|
154
|
+
utilization: window.utilization,
|
|
155
|
+
resetsAt: window.resetsAt,
|
|
156
|
+
...(window.absent ? { absent: true } : {}),
|
|
142
157
|
};
|
|
143
158
|
}
|
|
144
159
|
/** Project a billing block onto the state-file shape (totals only). */
|
|
@@ -74,9 +74,10 @@ export interface QuotaFromStatuslineOptions {
|
|
|
74
74
|
/** Capture time; defaults to now. */
|
|
75
75
|
now?: Date;
|
|
76
76
|
/**
|
|
77
|
-
* A previously known quota (typically the persisted snapshot).
|
|
78
|
-
* payload omits
|
|
79
|
-
*
|
|
77
|
+
* A previously known quota (typically the persisted snapshot). A window the
|
|
78
|
+
* payload omits is carried over from it while the fallback window has not
|
|
79
|
+
* reset yet, or when the fallback marks it `absent` (the plan has no such
|
|
80
|
+
* window, e.g. Ultra's missing five-hour limit).
|
|
80
81
|
*/
|
|
81
82
|
fallback?: QuotaState | null;
|
|
82
83
|
}
|
|
@@ -119,14 +119,26 @@ function parseClaudeStatuslinePayload(text) {
|
|
|
119
119
|
raw,
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Claude Code leaves a window out of the document once its reset has passed,
|
|
124
|
+
* and always for a window the plan does not have (Ultra has no five-hour
|
|
125
|
+
* limit). A fallback is carried over only while it can still be current: an
|
|
126
|
+
* `absent` marker from an earlier API read, or a window that has not reset yet.
|
|
127
|
+
* An expired fallback would otherwise be re-saved as a fresh sample forever.
|
|
128
|
+
*/
|
|
129
|
+
function toWindow(live, fallback, nowMs) {
|
|
123
130
|
if (live) {
|
|
124
131
|
return {
|
|
125
132
|
utilization: Math.max(0, live.usedPercentage),
|
|
126
133
|
resetsAt: new Date(live.resetsAt * 1000).toISOString(),
|
|
127
134
|
};
|
|
128
135
|
}
|
|
129
|
-
|
|
136
|
+
if (fallback?.absent)
|
|
137
|
+
return { utilization: 0, resetsAt: '', absent: true };
|
|
138
|
+
const fallbackResetMs = fallback ? Date.parse(fallback.resetsAt) : NaN;
|
|
139
|
+
if (fallback && Number.isFinite(fallbackResetMs) && fallbackResetMs > nowMs)
|
|
140
|
+
return fallback;
|
|
141
|
+
return { utilization: 0, resetsAt: '' };
|
|
130
142
|
}
|
|
131
143
|
/**
|
|
132
144
|
* Build a `QuotaState` from the official rate-limit block. Returns `null`
|
|
@@ -141,8 +153,8 @@ function quotaFromStatuslinePayload(payload, options = {}) {
|
|
|
141
153
|
const now = options.now ?? new Date();
|
|
142
154
|
const fallback = options.fallback ?? undefined;
|
|
143
155
|
return {
|
|
144
|
-
fiveHour: toWindow(limits.fiveHour, fallback?.fiveHour),
|
|
145
|
-
sevenDay: toWindow(limits.sevenDay, fallback?.sevenDay),
|
|
156
|
+
fiveHour: toWindow(limits.fiveHour, fallback?.fiveHour, now.getTime()),
|
|
157
|
+
sevenDay: toWindow(limits.sevenDay, fallback?.sevenDay, now.getTime()),
|
|
146
158
|
available: true,
|
|
147
159
|
providerId: 'claude-code',
|
|
148
160
|
source: 'statusline',
|
|
@@ -23,6 +23,11 @@ export declare function selectStatuslineAccount(input: StatuslineInput): Statusl
|
|
|
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
|
*/
|
|
@@ -27,6 +27,11 @@ function selectStatuslineAccount(input) {
|
|
|
27
27
|
* One-line status. Segments, in order, each omitted when unknown:
|
|
28
28
|
* `acct:<label> · 5h 42% resets 14:00 · ~1h20m left · 7d 61% · (2h ago) · ctx 37% · $0.42 · cache 92%`
|
|
29
29
|
*
|
|
30
|
+
* Plans without a five-hour window (Claude Ultra) lead with the weekly window
|
|
31
|
+
* instead: `acct:<label> · 7d 17% resets Fri 01:00 · ~2d3h left · …`. Window
|
|
32
|
+
* labels follow the reported window length, so a Codex weekly primary window
|
|
33
|
+
* reads `7d`, not `5h`.
|
|
34
|
+
*
|
|
30
35
|
* The age segment appears only for cached quota older than five minutes, so
|
|
31
36
|
* a snapshot from yesterday can no longer read like a live figure.
|
|
32
37
|
*/
|
|
@@ -41,20 +46,35 @@ function formatStatusline(input) {
|
|
|
41
46
|
if (!quota?.available) {
|
|
42
47
|
return joinSegments([`acct:${account}`, 'quota unavailable', ...liveSegments]);
|
|
43
48
|
}
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const first = describeWindow(quota.fiveHour, '5h', quota_1.FIVE_HOUR_WINDOW_MS);
|
|
50
|
+
const second = describeWindow(quota.sevenDay, '7d', quota_1.SEVEN_DAY_WINDOW_MS);
|
|
51
|
+
// Lead with the first window whenever the plan has it (even idle); fall back
|
|
52
|
+
// to the second window when the first does not exist for this plan.
|
|
53
|
+
const leadWithFirst = (0, quota_1.quotaWindowApplies)(quota.fiveHour) || !(0, quota_1.quotaWindowHasData)(quota.sevenDay);
|
|
54
|
+
const lead = leadWithFirst ? first : second;
|
|
55
|
+
const segments = [`acct:${account}`, formatLeadSegment(lead, now)];
|
|
56
|
+
const eta = estimateWindowEta(lead, quota.capturedAt, now);
|
|
48
57
|
if (eta != null)
|
|
49
58
|
segments.push(`~${formatMinutes(eta)} left`);
|
|
50
|
-
if (quota.sevenDay
|
|
51
|
-
segments.push(
|
|
59
|
+
if (leadWithFirst && (0, quota_1.quotaWindowHasData)(quota.sevenDay) && quota.sevenDay.utilization > 0) {
|
|
60
|
+
segments.push(`${second.label} ${Math.round(clampPercent(quota.sevenDay.utilization))}%`);
|
|
52
61
|
}
|
|
53
62
|
if (quota.freshness && quota.freshness !== 'fresh') {
|
|
54
63
|
segments.push(`(${(0, quota_1.formatQuotaAge)(quota.ageMs)})`);
|
|
55
64
|
}
|
|
56
65
|
return joinSegments([...segments, ...liveSegments]);
|
|
57
66
|
}
|
|
67
|
+
function describeWindow(window, label, defaultMs) {
|
|
68
|
+
const windowMs = typeof window.windowMs === 'number' && Number.isFinite(window.windowMs) && window.windowMs > 0
|
|
69
|
+
? window.windowMs
|
|
70
|
+
: defaultMs;
|
|
71
|
+
return { label: (0, quota_1.quotaWindowShortLabel)(window.windowMs, label), window, windowMs };
|
|
72
|
+
}
|
|
73
|
+
function formatLeadSegment(lead, now) {
|
|
74
|
+
const percent = `${lead.label} ${Math.round(clampPercent(lead.window.utilization))}%`;
|
|
75
|
+
const reset = formatReset(lead.window.resetsAt, now);
|
|
76
|
+
return reset ? `${percent} resets ${reset}` : percent;
|
|
77
|
+
}
|
|
58
78
|
function formatLiveSegments(live) {
|
|
59
79
|
if (!live)
|
|
60
80
|
return [];
|
|
@@ -80,22 +100,25 @@ function formatCost(value) {
|
|
|
80
100
|
return value.toFixed(1);
|
|
81
101
|
return value.toFixed(2);
|
|
82
102
|
}
|
|
83
|
-
function estimateWindowEta(
|
|
84
|
-
const
|
|
103
|
+
function estimateWindowEta(lead, capturedAtIso, now) {
|
|
104
|
+
const { window, windowMs } = lead;
|
|
105
|
+
if (window.absent)
|
|
106
|
+
return null;
|
|
107
|
+
const resetAt = Date.parse(window.resetsAt);
|
|
85
108
|
if (!Number.isFinite(resetAt))
|
|
86
109
|
return null;
|
|
87
110
|
const nowMs = now.getTime();
|
|
88
111
|
if (resetAt <= nowMs)
|
|
89
112
|
return null;
|
|
90
|
-
const capturedAt =
|
|
113
|
+
const capturedAt = capturedAtIso ? Date.parse(capturedAtIso) : now.getTime();
|
|
91
114
|
const sampleAt = Number.isFinite(capturedAt) ? capturedAt : now.getTime();
|
|
92
115
|
if (sampleAt >= resetAt)
|
|
93
116
|
return null;
|
|
94
|
-
const elapsedMinutes = (sampleAt - (resetAt -
|
|
95
|
-
if (elapsedMinutes <= 0 ||
|
|
117
|
+
const elapsedMinutes = (sampleAt - (resetAt - windowMs)) / 60_000;
|
|
118
|
+
if (elapsedMinutes <= 0 || window.utilization <= 0)
|
|
96
119
|
return null;
|
|
97
|
-
const burnRate =
|
|
98
|
-
const eta = (0, BurnRateCalculator_1.estimateTimeToQuota)(
|
|
120
|
+
const burnRate = window.utilization / elapsedMinutes;
|
|
121
|
+
const eta = (0, BurnRateCalculator_1.estimateTimeToQuota)(window.utilization, 100, burnRate);
|
|
99
122
|
const minutesUntilReset = (resetAt - nowMs) / 60_000;
|
|
100
123
|
return eta !== null && eta < minutesUntilReset ? eta : null;
|
|
101
124
|
}
|
|
@@ -107,20 +130,36 @@ function compactLabel(value) {
|
|
|
107
130
|
function clampPercent(value) {
|
|
108
131
|
return Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0;
|
|
109
132
|
}
|
|
110
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Reset time as `HH:MM`, or `Fri 01:00` when it is more than a day away.
|
|
135
|
+
* Returns '' for an empty or unparseable value — never the current time.
|
|
136
|
+
*/
|
|
137
|
+
function formatReset(value, now) {
|
|
138
|
+
if (!value)
|
|
139
|
+
return '';
|
|
111
140
|
const reset = new Date(value);
|
|
112
|
-
|
|
113
|
-
|
|
141
|
+
if (Number.isNaN(reset.getTime()))
|
|
142
|
+
return '';
|
|
143
|
+
const time = new Intl.DateTimeFormat(undefined, {
|
|
114
144
|
hour: '2-digit',
|
|
115
145
|
minute: '2-digit',
|
|
116
146
|
hour12: false,
|
|
117
|
-
}).format(
|
|
147
|
+
}).format(reset);
|
|
148
|
+
if (reset.getTime() - now.getTime() <= 86_400_000)
|
|
149
|
+
return time;
|
|
150
|
+
const weekday = new Intl.DateTimeFormat(undefined, { weekday: 'short' }).format(reset);
|
|
151
|
+
return `${weekday} ${time}`;
|
|
118
152
|
}
|
|
119
153
|
function formatMinutes(value) {
|
|
120
154
|
const minutes = Math.max(0, Math.round(value));
|
|
121
155
|
if (minutes < 60)
|
|
122
156
|
return `${minutes}min`;
|
|
123
157
|
const hours = Math.floor(minutes / 60);
|
|
158
|
+
if (hours >= 48) {
|
|
159
|
+
const days = Math.floor(hours / 24);
|
|
160
|
+
const remainderHours = hours % 24;
|
|
161
|
+
return remainderHours ? `${days}d${remainderHours}h` : `${days}d`;
|
|
162
|
+
}
|
|
124
163
|
const remainder = minutes % 60;
|
|
125
164
|
return remainder ? `${hours}h${remainder}m` : `${hours}h`;
|
|
126
165
|
}
|
|
@@ -2,14 +2,14 @@ export { getActiveAccountStatus } from '../accountStatus';
|
|
|
2
2
|
export type { ActiveAccountStatus, ActiveAccountStatusOptions, ActiveProviderAccountStatus, } from '../accountStatus';
|
|
3
3
|
export { readQuotaSnapshot, writeQuotaSnapshot } from '../quotaSnapshots';
|
|
4
4
|
export type { QuotaSnapshotProviderId } from '../quotaSnapshots';
|
|
5
|
-
export { appendQuotaHistorySample, getWorkspaceIdFromPath } from '../quotaHistory';
|
|
5
|
+
export { appendQuotaHistorySample, getWorkspaceIdFromPath, quotaHistoryWindow, } from '../quotaHistory';
|
|
6
6
|
export type { QuotaHistorySample } from '../quotaHistory';
|
|
7
|
-
export { classifyQuotaFreshness, formatQuotaAge } from '../quota';
|
|
8
|
-
export type { QuotaFreshness, QuotaState } from '../quota';
|
|
7
|
+
export { classifyQuotaFreshness, formatQuotaAge, quotaWindowApplies, quotaWindowHasData, quotaWindowShortLabel, } from '../quota';
|
|
8
|
+
export type { QuotaFreshness, QuotaState, QuotaWindow } from '../quota';
|
|
9
9
|
export { BurnRateCalculator, estimateTimeToQuota } from './BurnRateCalculator';
|
|
10
10
|
export { formatStatusline, selectStatuslineAccount } from './formatter';
|
|
11
11
|
export type { StatuslineInput, StatuslineSelection } from './formatter';
|
|
12
12
|
export { parseClaudeStatuslinePayload, quotaFromStatuslinePayload, } from './claudeStatuslinePayload';
|
|
13
13
|
export type { ClaudeStatuslinePayload, ClaudeStatuslineRateLimitWindow, QuotaFromStatuslineOptions, } from './claudeStatuslinePayload';
|
|
14
14
|
export { STATE_FILE_NAME, STATE_FILE_SCHEMA_VERSION, billingBlockToStateFile, getStateFilePath, quotaToStateFile, readStateFile, writeStateFile, } from '../stateFile';
|
|
15
|
-
export type { SidekickStateFile, SidekickStateInput, StateFileAccount, StateFileBillingBlock, StateFileContext, StateFileQuota, StateFileQuotaWindow, StateFileSession, StateFileWriter, WriteStateFileOptions, } from '../stateFile';
|
|
15
|
+
export type { SidekickStateFile, SidekickStateInput, StateFileAccount, StateFileBillingBlock, StateFileContext, StateFileQuota, StateFileQuotaLimit, StateFileQuotaWindow, StateFileSession, StateFileWriter, WriteStateFileOptions, } from '../stateFile';
|
package/dist/statusline/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.writeStateFile = exports.readStateFile = exports.quotaToStateFile = exports.getStateFilePath = exports.billingBlockToStateFile = exports.STATE_FILE_SCHEMA_VERSION = exports.STATE_FILE_NAME = exports.quotaFromStatuslinePayload = exports.parseClaudeStatuslinePayload = exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.formatQuotaAge = exports.classifyQuotaFreshness = exports.getWorkspaceIdFromPath = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.getActiveAccountStatus = void 0;
|
|
3
|
+
exports.writeStateFile = exports.readStateFile = exports.quotaToStateFile = exports.getStateFilePath = exports.billingBlockToStateFile = exports.STATE_FILE_SCHEMA_VERSION = exports.STATE_FILE_NAME = exports.quotaFromStatuslinePayload = exports.parseClaudeStatuslinePayload = exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.quotaWindowShortLabel = exports.quotaWindowHasData = exports.quotaWindowApplies = exports.formatQuotaAge = exports.classifyQuotaFreshness = exports.quotaHistoryWindow = exports.getWorkspaceIdFromPath = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.getActiveAccountStatus = void 0;
|
|
4
4
|
var accountStatus_1 = require("../accountStatus");
|
|
5
5
|
Object.defineProperty(exports, "getActiveAccountStatus", { enumerable: true, get: function () { return accountStatus_1.getActiveAccountStatus; } });
|
|
6
6
|
var quotaSnapshots_1 = require("../quotaSnapshots");
|
|
@@ -9,9 +9,13 @@ Object.defineProperty(exports, "writeQuotaSnapshot", { enumerable: true, get: fu
|
|
|
9
9
|
var quotaHistory_1 = require("../quotaHistory");
|
|
10
10
|
Object.defineProperty(exports, "appendQuotaHistorySample", { enumerable: true, get: function () { return quotaHistory_1.appendQuotaHistorySample; } });
|
|
11
11
|
Object.defineProperty(exports, "getWorkspaceIdFromPath", { enumerable: true, get: function () { return quotaHistory_1.getWorkspaceIdFromPath; } });
|
|
12
|
+
Object.defineProperty(exports, "quotaHistoryWindow", { enumerable: true, get: function () { return quotaHistory_1.quotaHistoryWindow; } });
|
|
12
13
|
var quota_1 = require("../quota");
|
|
13
14
|
Object.defineProperty(exports, "classifyQuotaFreshness", { enumerable: true, get: function () { return quota_1.classifyQuotaFreshness; } });
|
|
14
15
|
Object.defineProperty(exports, "formatQuotaAge", { enumerable: true, get: function () { return quota_1.formatQuotaAge; } });
|
|
16
|
+
Object.defineProperty(exports, "quotaWindowApplies", { enumerable: true, get: function () { return quota_1.quotaWindowApplies; } });
|
|
17
|
+
Object.defineProperty(exports, "quotaWindowHasData", { enumerable: true, get: function () { return quota_1.quotaWindowHasData; } });
|
|
18
|
+
Object.defineProperty(exports, "quotaWindowShortLabel", { enumerable: true, get: function () { return quota_1.quotaWindowShortLabel; } });
|
|
15
19
|
var BurnRateCalculator_1 = require("./BurnRateCalculator");
|
|
16
20
|
Object.defineProperty(exports, "BurnRateCalculator", { enumerable: true, get: function () { return BurnRateCalculator_1.BurnRateCalculator; } });
|
|
17
21
|
Object.defineProperty(exports, "estimateTimeToQuota", { enumerable: true, get: function () { return BurnRateCalculator_1.estimateTimeToQuota; } });
|
|
@@ -0,0 +1,10 @@
|
|
|
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';
|
|
10
|
+
export type { ClaudeStatuslinePayload, ClaudeStatuslineRateLimitWindow, QuotaFromStatuslineOptions, } from './claudeStatuslinePayload';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `sidekick-shared/statusline/payload` — the pure Claude Code status-line
|
|
4
|
+
* stdin parser on its own, for hosts with a tight bundle budget that render
|
|
5
|
+
* their own line. Loads no account, credential, quota-snapshot, or network
|
|
6
|
+
* code (the full `sidekick-shared/statusline` entry does).
|
|
7
|
+
*
|
|
8
|
+
* @module statusline/payload
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.quotaFromStatuslinePayload = exports.parseClaudeStatuslinePayload = void 0;
|
|
12
|
+
var claudeStatuslinePayload_1 = require("./claudeStatuslinePayload");
|
|
13
|
+
Object.defineProperty(exports, "parseClaudeStatuslinePayload", { enumerable: true, get: function () { return claudeStatuslinePayload_1.parseClaudeStatuslinePayload; } });
|
|
14
|
+
Object.defineProperty(exports, "quotaFromStatuslinePayload", { enumerable: true, get: function () { return claudeStatuslinePayload_1.quotaFromStatuslinePayload; } });
|
|
@@ -15,6 +15,20 @@ export interface CredentialGuardOptions {
|
|
|
15
15
|
blockedBinaries?: string[];
|
|
16
16
|
/** Extra real paths to protect, besides the Claude, Codex, and Sidekick homes. */
|
|
17
17
|
protectedPaths?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* Home overrides to keep instead of unsetting, for a suite that points one
|
|
20
|
+
* at its own fixture before the guard installs. Accepts only the variables
|
|
21
|
+
* the guard unsets: `CLAUDE_CONFIG_DIR`, `CLAUDE_SECURESTORAGE_CONFIG_DIR`,
|
|
22
|
+
* `CODEX_HOME`, and `SIDEKICK_CONFIG_DIR`.
|
|
23
|
+
*
|
|
24
|
+
* Install throws, before changing anything, for any other name, or when a
|
|
25
|
+
* preserved value is, contains, or lies inside a real protected location:
|
|
26
|
+
* the real home's `.claude`, `.claude.json`, and `.codex`, the default
|
|
27
|
+
* Sidekick config dir, or a `protectedPaths` entry. Symlinks are resolved
|
|
28
|
+
* first. The guard cannot tell a real store kept elsewhere from a fixture,
|
|
29
|
+
* so preserve only paths the suite created. Blank values are kept unchecked.
|
|
30
|
+
*/
|
|
31
|
+
preserveEnv?: readonly string[];
|
|
18
32
|
}
|
|
19
33
|
/**
|
|
20
34
|
* Installs the guard once per process and returns it; later calls return the
|
|
@@ -46,7 +46,8 @@ exports.installCredentialGuard = installCredentialGuard;
|
|
|
46
46
|
* - `HOME` (and the Windows equivalents) point at a per-process temp dir,
|
|
47
47
|
* removed on exit, and the home overrides (`CLAUDE_CONFIG_DIR`,
|
|
48
48
|
* `CLAUDE_SECURESTORAGE_CONFIG_DIR`, `CODEX_HOME`, `SIDEKICK_CONFIG_DIR`)
|
|
49
|
-
* are unset
|
|
49
|
+
* are unset (unless named in `preserveEnv` and pointed at a fixture), so
|
|
50
|
+
* every `os.homedir()`-derived path is isolated structurally;
|
|
50
51
|
* - the real `child_process` module object is patched, which reaches both ESM
|
|
51
52
|
* named imports (after `syncBuiltinESMExports`) and CommonJS code loaded
|
|
52
53
|
* through native `require`; per-file `vi.mock('child_process')` factories
|
|
@@ -64,6 +65,13 @@ const os = __importStar(require("node:os"));
|
|
|
64
65
|
const path = __importStar(require("node:path"));
|
|
65
66
|
const GUARD_KEY = '__sidekickCredentialGuard';
|
|
66
67
|
const DEFAULT_BLOCKED_BINARIES = ['security', 'claude', 'codex'];
|
|
68
|
+
/** Home overrides the guard unsets; only these can be preserved. */
|
|
69
|
+
const SCRUBBED_ENV = [
|
|
70
|
+
'CLAUDE_CONFIG_DIR',
|
|
71
|
+
'CLAUDE_SECURESTORAGE_CONFIG_DIR',
|
|
72
|
+
'CODEX_HOME',
|
|
73
|
+
'SIDEKICK_CONFIG_DIR',
|
|
74
|
+
];
|
|
67
75
|
const CHILD_PROCESS_FUNCTIONS = [
|
|
68
76
|
'spawn',
|
|
69
77
|
'spawnSync',
|
|
@@ -111,6 +119,8 @@ function installCredentialGuard(options = {}) {
|
|
|
111
119
|
// os.userInfo() reads the passwd entry, so it still names the real home
|
|
112
120
|
// after HOME is redirected below.
|
|
113
121
|
const realHome = os.userInfo().homedir;
|
|
122
|
+
// Validated before any side effect, so a refused install changes nothing.
|
|
123
|
+
const preserved = checkPreservedEnv(options, realHome, realFs);
|
|
114
124
|
const testHome = realFs.mkdtempSync(path.join(os.tmpdir(), 'sidekick-test-home-'));
|
|
115
125
|
const removeTestHome = realFs.rmSync.bind(realFs);
|
|
116
126
|
process.once('exit', () => {
|
|
@@ -139,10 +149,9 @@ function installCredentialGuard(options = {}) {
|
|
|
139
149
|
process.env.USERPROFILE = testHome;
|
|
140
150
|
process.env.APPDATA = path.join(testHome, 'AppData', 'Roaming');
|
|
141
151
|
process.env.LOCALAPPDATA = path.join(testHome, 'AppData', 'Local');
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
delete process.env.SIDEKICK_CONFIG_DIR;
|
|
152
|
+
for (const name of SCRUBBED_ENV)
|
|
153
|
+
if (!preserved.has(name))
|
|
154
|
+
delete process.env[name];
|
|
146
155
|
const protectedRoots = path.resolve(realHome) === path.resolve(testHome)
|
|
147
156
|
? []
|
|
148
157
|
: [
|
|
@@ -244,6 +253,79 @@ function installCredentialGuard(options = {}) {
|
|
|
244
253
|
globalThis[GUARD_KEY] = state;
|
|
245
254
|
return state;
|
|
246
255
|
}
|
|
256
|
+
/** The `preserveEnv` names, after refusing unknown names and unsafe values. */
|
|
257
|
+
function checkPreservedEnv(options, realHome, realFs) {
|
|
258
|
+
const preserved = new Set();
|
|
259
|
+
for (const name of options.preserveEnv ?? []) {
|
|
260
|
+
if (!SCRUBBED_ENV.includes(name)) {
|
|
261
|
+
throw new Error(`installCredentialGuard: preserveEnv accepts only ${SCRUBBED_ENV.join(', ')}; got ${JSON.stringify(name)}.`);
|
|
262
|
+
}
|
|
263
|
+
preserved.add(name);
|
|
264
|
+
}
|
|
265
|
+
if (preserved.size === 0)
|
|
266
|
+
return preserved;
|
|
267
|
+
const roots = [
|
|
268
|
+
path.join(realHome, '.claude'),
|
|
269
|
+
path.join(realHome, '.claude.json'),
|
|
270
|
+
path.join(realHome, '.codex'),
|
|
271
|
+
path.join(realHome, '.config', 'sidekick'),
|
|
272
|
+
// The Windows default config dir, read before HOME and APPDATA move.
|
|
273
|
+
...(process.platform === 'win32' && process.env.APPDATA
|
|
274
|
+
? [path.join(process.env.APPDATA, 'sidekick')]
|
|
275
|
+
: []),
|
|
276
|
+
...(options.protectedPaths ?? []),
|
|
277
|
+
];
|
|
278
|
+
for (const name of preserved) {
|
|
279
|
+
const value = process.env[name]?.trim();
|
|
280
|
+
if (!value)
|
|
281
|
+
continue;
|
|
282
|
+
const hit = protectedLocation(value, roots, realFs);
|
|
283
|
+
if (hit) {
|
|
284
|
+
throw new Error(`installCredentialGuard: preserveEnv ${name}=${value} reaches the real protected location ${hit}. Point it at a temporary fixture instead.`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return preserved;
|
|
288
|
+
}
|
|
289
|
+
/** The root `target` equals, contains, or lies inside, comparing plain and symlink-resolved paths. */
|
|
290
|
+
function protectedLocation(target, roots, realFs) {
|
|
291
|
+
// Case-insensitive by default on macOS and Windows; folding can only over-refuse.
|
|
292
|
+
const fold = process.platform === 'darwin' || process.platform === 'win32';
|
|
293
|
+
const normal = (value) => {
|
|
294
|
+
const resolved = path.resolve(value);
|
|
295
|
+
return fold ? resolved.toLowerCase() : resolved;
|
|
296
|
+
};
|
|
297
|
+
const within = (parent, child) => child === parent || child.startsWith(parent.endsWith(path.sep) ? parent : parent + path.sep);
|
|
298
|
+
const related = (a, b) => within(a, b) || within(b, a);
|
|
299
|
+
// Plain paths first, so an obviously unsafe value never reaches the filesystem.
|
|
300
|
+
const plain = normal(target);
|
|
301
|
+
for (const root of roots)
|
|
302
|
+
if (related(normal(root), plain))
|
|
303
|
+
return root;
|
|
304
|
+
const real = normal(realpathOfNearest(target, realFs));
|
|
305
|
+
for (const root of roots) {
|
|
306
|
+
const realRoot = normal(realpathOfNearest(root, realFs));
|
|
307
|
+
if (related(realRoot, real) || related(normal(root), real) || related(realRoot, plain))
|
|
308
|
+
return root;
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
/** `realpath` of the nearest existing ancestor, with the missing tail re-appended. */
|
|
313
|
+
function realpathOfNearest(target, realFs) {
|
|
314
|
+
let current = path.resolve(target);
|
|
315
|
+
let tail = [];
|
|
316
|
+
for (;;) {
|
|
317
|
+
try {
|
|
318
|
+
return path.join(realFs.realpathSync.native(current), ...tail);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
const parent = path.dirname(current);
|
|
322
|
+
if (parent === current)
|
|
323
|
+
return path.resolve(target);
|
|
324
|
+
tail = [path.basename(current), ...tail];
|
|
325
|
+
current = parent;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
247
329
|
/**
|
|
248
330
|
* The real module object of a Node built-in: `process.getBuiltinModule`
|
|
249
331
|
* (Node 20.16+), or `require` in CommonJS on older Node.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locates the session record that issued each of a set of tool calls, by id.
|
|
3
|
+
*
|
|
4
|
+
* Given Claude Code `tool_use` ids (`toolu_…`) and/or Codex `call_id`s, finds
|
|
5
|
+
* the session file, session id and time of each call, plus the ordinal of the
|
|
6
|
+
* human prompt the call answered, so a downstream record (an edit, a commit)
|
|
7
|
+
* can be tied back to the prompt that caused it. Scope, listing, session keys
|
|
8
|
+
* and ordinals are the ones `collectPromptHistory()` uses, so results join
|
|
9
|
+
* directly with prompt-history entries.
|
|
10
|
+
*
|
|
11
|
+
* Cost: one listing of the workspace-scoped session files (`since` prunes by
|
|
12
|
+
* modification time), then a streaming pass over each candidate file in which
|
|
13
|
+
* a line is only parsed when it contains one of the wanted ids. Claude Code
|
|
14
|
+
* subagent transcripts of each session are searched too. Stops as soon as
|
|
15
|
+
* every id is found. Prompt ordinals are read only for files that matched.
|
|
16
|
+
*
|
|
17
|
+
* Node-only: reads session files.
|
|
18
|
+
*
|
|
19
|
+
* @module toolCallOrigins
|
|
20
|
+
*/
|
|
21
|
+
import { type PromptHistoryProvider } from './promptHistory';
|
|
22
|
+
export interface FindToolCallOriginsOptions {
|
|
23
|
+
/** Tool-call ids to locate: Claude `tool_use` ids and/or Codex `call_id`s. */
|
|
24
|
+
ids: Iterable<string>;
|
|
25
|
+
/** Workspace roots that scope the search (as for `collectPromptHistory`). */
|
|
26
|
+
workspacePaths: string[];
|
|
27
|
+
/** Also search the roots' git worktree siblings. Default true. */
|
|
28
|
+
includeWorktrees?: boolean;
|
|
29
|
+
/** Only session files modified at or after this instant. */
|
|
30
|
+
since?: Date;
|
|
31
|
+
/** Default both. */
|
|
32
|
+
providers?: PromptHistoryProvider[];
|
|
33
|
+
/** Stops between files and lines; the promise rejects with the signal's reason. */
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
export interface ToolCallOrigin {
|
|
37
|
+
provider: PromptHistoryProvider;
|
|
38
|
+
/**
|
|
39
|
+
* Session whose prompt history `afterOrdinal` counts in: the session itself,
|
|
40
|
+
* or its parent for a subagent or fork (Claude subagent transcripts live
|
|
41
|
+
* under the parent; a Codex subagent or fork has its own rollout, named in
|
|
42
|
+
* `subagent.agentId` / `filePath`, and no prompts of its own).
|
|
43
|
+
*/
|
|
44
|
+
sessionId: string;
|
|
45
|
+
/** Same key `collectPromptHistory()` reports for that session's file. */
|
|
46
|
+
sessionKey: string;
|
|
47
|
+
/** File the call was recorded in (a subagent transcript for subagent calls). */
|
|
48
|
+
filePath: string;
|
|
49
|
+
/** ISO timestamp of the record that issued the call. */
|
|
50
|
+
timestamp: string;
|
|
51
|
+
/**
|
|
52
|
+
* Ordinal of the last human prompt at or before the call, in the session's
|
|
53
|
+
* prompt history; null when the call precedes every prompt or the session
|
|
54
|
+
* has none (e.g. a Codex subagent whose parent was not found in scope).
|
|
55
|
+
*/
|
|
56
|
+
afterOrdinal: number | null;
|
|
57
|
+
toolName?: string;
|
|
58
|
+
/** Set when a subagent issued the call. */
|
|
59
|
+
subagent?: {
|
|
60
|
+
agentId: string;
|
|
61
|
+
parentSessionId: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Finds where each tool call was issued. Ids that are not found are absent
|
|
66
|
+
* from the result. When an id appears more than once (a forked or resumed
|
|
67
|
+
* session can replay history), the earliest record wins, and a session's own
|
|
68
|
+
* record wins over a fork's copy at the same instant.
|
|
69
|
+
*/
|
|
70
|
+
export declare function findToolCallOrigins(options: FindToolCallOriginsOptions): Promise<Map<string, ToolCallOrigin>>;
|