@scrylog/cli 0.3.0 → 0.3.2
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/assets/highlights-eq9cgrbb.scm +604 -0
- package/dist/assets/highlights-ghv9g403.scm +205 -0
- package/dist/assets/highlights-hk7bwhj4.scm +284 -0
- package/dist/assets/highlights-r812a2qc.scm +150 -0
- package/dist/assets/highlights-x6tmsnaa.scm +115 -0
- package/dist/assets/injections-73j83es3.scm +27 -0
- package/dist/assets/parser.worker-xyhf83rf.js +4563 -0
- package/dist/assets/tree-sitter-3jzf13jk.wasm +0 -0
- package/dist/assets/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
- package/dist/assets/tree-sitter-markdown-411r6y9b.wasm +0 -0
- package/dist/assets/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
- package/dist/assets/tree-sitter-typescript-zxjzwt75.wasm +0 -0
- package/dist/assets/tree-sitter-zig-e78zbjpm.wasm +0 -0
- package/dist/cli/index.js +499 -0
- package/dist/cli/index.js.map +61 -0
- package/dist/daemon/index.js +670 -0
- package/dist/daemon/index.js.map +1475 -0
- package/dist/hooks/claude.js +6 -0
- package/dist/hooks/claude.js.map +11 -0
- package/dist/hooks/codex.js +10 -0
- package/dist/hooks/codex.js.map +11 -0
- package/dist/mcp/index.js +100 -0
- package/dist/mcp/index.js.map +231 -0
- package/dist/oc/index.js +21 -0
- package/dist/oc/index.js.map +10 -0
- package/package.json +16 -13
- package/src/api/docs/sessions.keys.ts +0 -6
- package/src/api/docs/sessions.ts +0 -82
- package/src/api/docs/system.keys.ts +0 -5
- package/src/api/docs/system.ts +0 -69
- package/src/api/docs/usage.keys.ts +0 -3
- package/src/api/docs/usage.ts +0 -24
- package/src/api/docs/webhooks.keys.ts +0 -5
- package/src/api/docs/webhooks.ts +0 -78
- package/src/api/index.ts +0 -18
- package/src/api/lib/auth.ts +0 -43
- package/src/api/lib/errors.ts +0 -28
- package/src/api/lib/route.ts +0 -18
- package/src/api/models/claude.ts +0 -4
- package/src/api/models/codex.ts +0 -4
- package/src/api/models/opencode.ts +0 -11
- package/src/api/routes/sessions.ts +0 -51
- package/src/api/routes/system.ts +0 -106
- package/src/api/routes/usage.ts +0 -15
- package/src/api/routes/webhooks/claude.ts +0 -149
- package/src/api/routes/webhooks/codex.ts +0 -175
- package/src/api/routes/webhooks/opencode.ts +0 -112
- package/src/api/routes.ts +0 -93
- package/src/api/schemas/common.ts +0 -29
- package/src/api/schemas/sessions.ts +0 -86
- package/src/api/schemas/usage.ts +0 -51
- package/src/api/schemas/webhooks/claude.ts +0 -32
- package/src/api/schemas/webhooks/codex.ts +0 -34
- package/src/api/schemas/webhooks/opencode.ts +0 -29
- package/src/api/services/claude-session.ts +0 -9
- package/src/api/services/codex-session.ts +0 -8
- package/src/api/services/opencode-session.ts +0 -73
- package/src/classifier.ts +0 -206
- package/src/cli/commands.ts +0 -714
- package/src/cli/index.ts +0 -18
- package/src/cli/program.ts +0 -175
- package/src/cli/prompt.ts +0 -44
- package/src/daemon/cli.ts +0 -27
- package/src/daemon/collectors/claude/README.md +0 -108
- package/src/daemon/collectors/claude/collector.ts +0 -423
- package/src/daemon/collectors/claude/discovery.ts +0 -127
- package/src/daemon/collectors/claude/hooks.ts +0 -181
- package/src/daemon/collectors/claude/index.ts +0 -8
- package/src/daemon/collectors/claude/jsonl.ts +0 -85
- package/src/daemon/collectors/claude/transcript.ts +0 -246
- package/src/daemon/collectors/codex/README.md +0 -147
- package/src/daemon/collectors/codex/collector.ts +0 -661
- package/src/daemon/collectors/codex/discovery.ts +0 -104
- package/src/daemon/collectors/codex/hooks.ts +0 -179
- package/src/daemon/collectors/codex/index.ts +0 -7
- package/src/daemon/collectors/codex/jsonl.ts +0 -108
- package/src/daemon/collectors/codex/transcript.ts +0 -299
- package/src/daemon/collectors/git.ts +0 -353
- package/src/daemon/collectors/herdr/collector.ts +0 -128
- package/src/daemon/collectors/herdr/index.ts +0 -2
- package/src/daemon/collectors/herdr/normalization.ts +0 -111
- package/src/daemon/collectors/opencode/README.md +0 -106
- package/src/daemon/collectors/opencode/collector.ts +0 -545
- package/src/daemon/collectors/opencode/discovery.ts +0 -257
- package/src/daemon/collectors/opencode/index.ts +0 -5
- package/src/daemon/collectors/opencode/session-builder.ts +0 -59
- package/src/daemon/collectors/opencode-db.ts +0 -294
- package/src/daemon/collectors/process.ts +0 -201
- package/src/daemon/collectors/tmux/README.md +0 -128
- package/src/daemon/collectors/tmux/collector.ts +0 -492
- package/src/daemon/collectors/tmux/enrichment.ts +0 -242
- package/src/daemon/collectors/tmux/index.ts +0 -4
- package/src/daemon/collectors/tmux/parse.ts +0 -128
- package/src/daemon/collectors/usage.ts +0 -259
- package/src/daemon/index.ts +0 -209
- package/src/daemon/scheduler.ts +0 -86
- package/src/daemon/store.ts +0 -630
- package/src/detect.ts +0 -143
- package/src/hooks/claude.ts +0 -596
- package/src/hooks/codex.ts +0 -517
- package/src/legacy/classifier.ts +0 -215
- package/src/legacy/detect.ts +0 -143
- package/src/mcp/client-server.ts +0 -21
- package/src/mcp/http-handler.ts +0 -126
- package/src/mcp/index.ts +0 -61
- package/src/mcp/resources/config-current.ts +0 -19
- package/src/mcp/resources/index.ts +0 -11
- package/src/mcp/resources/sessions-active.ts +0 -19
- package/src/mcp/resources/sessions-all.ts +0 -19
- package/src/mcp/schemas/sessions.ts +0 -3
- package/src/mcp/server.ts +0 -17
- package/src/mcp/services/api-adapter.ts +0 -48
- package/src/mcp/services/data-source.ts +0 -22
- package/src/mcp/services/store-adapter.ts +0 -40
- package/src/mcp/tools/get-active-sessions.ts +0 -16
- package/src/mcp/tools/get-session-children.ts +0 -24
- package/src/mcp/tools/get-session-messages.ts +0 -24
- package/src/mcp/tools/get-session.ts +0 -23
- package/src/mcp/tools/index.ts +0 -15
- package/src/mcp/tools/list-sessions.ts +0 -16
- package/src/oc/index.ts +0 -302
- package/src/service/manager.ts +0 -284
- package/src/shared/config.ts +0 -511
- package/src/shared/ipc.ts +0 -80
- package/src/shared/logger.ts +0 -248
- package/src/shared/paths.ts +0 -73
- package/src/shared/platform.ts +0 -110
- package/src/shared/session-exclusions.ts +0 -5
- package/src/shared/session-view.ts +0 -302
- package/src/shared/terminal-capabilities.ts +0 -130
- package/src/shared/types.ts +0 -330
- package/src/shared/utils.ts +0 -339
- package/src/shared/version.ts +0 -44
- package/src/tui/App.tsx +0 -240
- package/src/tui/client.ts +0 -163
- package/src/tui/components/BindingsModal.tsx +0 -82
- package/src/tui/components/DashboardHeader.tsx +0 -256
- package/src/tui/components/KeyBar.tsx +0 -67
- package/src/tui/components/SessionDetailModal.tsx +0 -169
- package/src/tui/components/SessionsView.tsx +0 -184
- package/src/tui/components/TabBar.tsx +0 -40
- package/src/tui/components/UsageSummaryPane.tsx +0 -272
- package/src/tui/components/UsageView.tsx +0 -242
- package/src/tui/components/tool-icons.ts +0 -24
- package/src/tui/index.tsx +0 -106
- package/src/tui/keybindings.ts +0 -49
- package/src/tui/model.ts +0 -295
- package/src/tui/state.ts +0 -77
- package/src/tui/theme.ts +0 -110
- package/src/types.ts +0 -113
- package/src/usage/README.md +0 -323
- package/src/usage/api.ts +0 -99
- package/src/usage/client.ts +0 -3
- package/src/usage/clients/anthropic-oauth-usage.ts +0 -258
- package/src/usage/clients/anthropic-web-usage.ts +0 -329
- package/src/usage/clients/claude-cli-probe.ts +0 -513
- package/src/usage/clients/codex-app-server.ts +0 -354
- package/src/usage/clients/codex-oauth-usage.ts +0 -240
- package/src/usage/clients/opencode-go-api.ts +0 -100
- package/src/usage/clients/opencode-go-usage.ts +0 -577
- package/src/usage/errors.ts +0 -33
- package/src/usage/index.ts +0 -20
- package/src/usage/local/opencode-go-local.ts +0 -194
- package/src/usage/mappers.ts +0 -282
- package/src/usage/planner.ts +0 -118
- package/src/usage/providers/claude.ts +0 -233
- package/src/usage/providers/codex.ts +0 -115
- package/src/usage/providers/opencode-go.ts +0 -115
- package/src/usage/resolver.ts +0 -4
- package/src/usage/resolvers/browser-cookies.ts +0 -808
- package/src/usage/resolvers/claude-auth.ts +0 -208
- package/src/usage/resolvers/claude-cli.ts +0 -24
- package/src/usage/resolvers/claude-desktop-auth.ts +0 -170
- package/src/usage/resolvers/claude-web.ts +0 -82
- package/src/usage/resolvers/codex-auth.ts +0 -174
- package/src/usage/resolvers/keychain-coordinator.ts +0 -397
- package/src/usage/resolvers/opencode-go-web.ts +0 -55
- package/src/usage/types.ts +0 -225
- package/src/usage/web-session-cache.ts +0 -37
- /package/{src/tui → dist/cli}/assets/providers/SOURCES.md +0 -0
- /package/{src/tui → dist/cli}/assets/providers/claude-terminal.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/claude-terminal.svg +0 -0
- /package/{src/tui → dist/cli}/assets/providers/claude.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/copilot.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/copilot.svg +0 -0
- /package/{src/tui → dist/cli}/assets/providers/openai-terminal.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/openai-terminal.svg +0 -0
- /package/{src/tui → dist/cli}/assets/providers/openai.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/opencode-terminal.svg +0 -0
- /package/{src/tui → dist/cli}/assets/providers/opencode.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/shell.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/shell.svg +0 -0
- /package/{src/tui → dist/cli}/assets/providers/unknown.png +0 -0
- /package/{src/tui → dist/cli}/assets/providers/unknown.svg +0 -0
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { Database } from "bun:sqlite";
|
|
2
|
-
import { resolveDbCandidates } from "../../daemon/collectors/opencode-db";
|
|
3
|
-
import type { OpenCodeGoUsageSnapshot } from "../types";
|
|
4
|
-
|
|
5
|
-
const FIVE_HOURS_SEC = 5 * 60 * 60;
|
|
6
|
-
const WEEK_SEC = 7 * 24 * 60 * 60;
|
|
7
|
-
const SESSION_LIMIT_USD = 12;
|
|
8
|
-
const WEEKLY_LIMIT_USD = 30;
|
|
9
|
-
const MONTHLY_LIMIT_USD = 60;
|
|
10
|
-
|
|
11
|
-
export interface OpenCodeGoLocalUsageFetcher {
|
|
12
|
-
fetch(now?: Date): Promise<OpenCodeGoUsageSnapshot | null>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface UsageRow {
|
|
16
|
-
createdMs: number;
|
|
17
|
-
cost: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const MESSAGE_USAGE_SQL = `
|
|
21
|
-
SELECT
|
|
22
|
-
CAST(COALESCE(json_extract(data, '$.time.created'), time_created) AS INTEGER) AS createdMs,
|
|
23
|
-
CAST(json_extract(data, '$.cost') AS REAL) AS cost
|
|
24
|
-
FROM message
|
|
25
|
-
WHERE json_valid(data)
|
|
26
|
-
AND json_extract(data, '$.providerID') = 'opencode-go'
|
|
27
|
-
AND json_extract(data, '$.role') = 'assistant'
|
|
28
|
-
AND json_type(data, '$.cost') IN ('integer', 'real')
|
|
29
|
-
`;
|
|
30
|
-
|
|
31
|
-
const MESSAGE_AND_PART_USAGE_SQL = `
|
|
32
|
-
WITH provider_messages AS (
|
|
33
|
-
SELECT
|
|
34
|
-
id AS messageID,
|
|
35
|
-
CAST(COALESCE(json_extract(data, '$.time.created'), time_created) AS INTEGER) AS createdMs,
|
|
36
|
-
CAST(json_extract(data, '$.cost') AS REAL) AS cost,
|
|
37
|
-
json_type(data, '$.cost') IN ('integer', 'real') AS hasCost
|
|
38
|
-
FROM message
|
|
39
|
-
WHERE json_valid(data)
|
|
40
|
-
AND json_extract(data, '$.providerID') = 'opencode-go'
|
|
41
|
-
AND json_extract(data, '$.role') = 'assistant'
|
|
42
|
-
)
|
|
43
|
-
SELECT
|
|
44
|
-
CAST(COALESCE(json_extract(p.data, '$.time.created'), p.time_created, m.createdMs) AS INTEGER) AS createdMs,
|
|
45
|
-
CAST(json_extract(p.data, '$.cost') AS REAL) AS cost
|
|
46
|
-
FROM part p
|
|
47
|
-
JOIN provider_messages m ON m.messageID = p.message_id
|
|
48
|
-
WHERE json_valid(p.data)
|
|
49
|
-
AND json_extract(p.data, '$.type') = 'step-finish'
|
|
50
|
-
AND json_type(p.data, '$.cost') IN ('integer', 'real')
|
|
51
|
-
UNION ALL
|
|
52
|
-
SELECT createdMs, cost
|
|
53
|
-
FROM provider_messages m
|
|
54
|
-
WHERE hasCost
|
|
55
|
-
AND NOT EXISTS (
|
|
56
|
-
SELECT 1
|
|
57
|
-
FROM part p
|
|
58
|
-
WHERE p.message_id = m.messageID
|
|
59
|
-
AND json_valid(p.data)
|
|
60
|
-
AND json_extract(p.data, '$.type') = 'step-finish'
|
|
61
|
-
AND json_type(p.data, '$.cost') IN ('integer', 'real')
|
|
62
|
-
)
|
|
63
|
-
`;
|
|
64
|
-
|
|
65
|
-
export class OpenCodeGoLocalUsageReader implements OpenCodeGoLocalUsageFetcher {
|
|
66
|
-
async fetch(now: Date = new Date()): Promise<OpenCodeGoUsageSnapshot | null> {
|
|
67
|
-
for (const dbPath of resolveDbCandidates()) {
|
|
68
|
-
let db: Database | undefined;
|
|
69
|
-
try {
|
|
70
|
-
db = new Database(dbPath, { readonly: true });
|
|
71
|
-
const rows = readLocalRows(db);
|
|
72
|
-
if (rows.length > 0) {
|
|
73
|
-
return buildLocalSnapshot(rows, now);
|
|
74
|
-
}
|
|
75
|
-
} catch {
|
|
76
|
-
// Missing or corrupt databases are not usable local usage sources.
|
|
77
|
-
} finally {
|
|
78
|
-
db?.close();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function readLocalRows(db: Database): UsageRow[] {
|
|
86
|
-
const partMatch = db
|
|
87
|
-
.query<unknown, [string]>("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1")
|
|
88
|
-
.get("part");
|
|
89
|
-
// bun:sqlite returns null (not undefined) when a row cannot be resolved.
|
|
90
|
-
const hasPartTable = partMatch !== null && partMatch !== undefined;
|
|
91
|
-
const statement = db.query<RowShape, []>(hasPartTable ? MESSAGE_AND_PART_USAGE_SQL : MESSAGE_USAGE_SQL);
|
|
92
|
-
const rows: UsageRow[] = [];
|
|
93
|
-
for (const row of statement.all()) {
|
|
94
|
-
const createdMs = Number(row.createdMs);
|
|
95
|
-
const cost = Number(row.cost);
|
|
96
|
-
if (Number.isFinite(createdMs) && createdMs > 0 && Number.isFinite(cost) && cost >= 0) {
|
|
97
|
-
rows.push({ createdMs, cost });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return rows;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
interface RowShape {
|
|
104
|
-
createdMs: number | null;
|
|
105
|
-
cost: number | null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
interface MonthBounds {
|
|
109
|
-
startMs: number;
|
|
110
|
-
endMs: number;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function buildLocalSnapshot(rows: UsageRow[], now: Date): OpenCodeGoUsageSnapshot {
|
|
114
|
-
const nowMs = now.getTime();
|
|
115
|
-
const sessionStartMs = nowMs - FIVE_HOURS_SEC * 1000;
|
|
116
|
-
const weekStartMs = startOfUtcWeek(now).getTime();
|
|
117
|
-
const weekEndMs = weekStartMs + WEEK_SEC * 1000;
|
|
118
|
-
const { startMs, endMs } = monthBounds(now, rows);
|
|
119
|
-
|
|
120
|
-
let sessionCost = 0;
|
|
121
|
-
let weekCost = 0;
|
|
122
|
-
let monthCost = 0;
|
|
123
|
-
let oldestSessionMs: number | undefined;
|
|
124
|
-
for (const row of rows) {
|
|
125
|
-
if (row.createdMs >= sessionStartMs && row.createdMs < nowMs) {
|
|
126
|
-
sessionCost += row.cost;
|
|
127
|
-
if (oldestSessionMs === undefined || row.createdMs < oldestSessionMs) oldestSessionMs = row.createdMs;
|
|
128
|
-
}
|
|
129
|
-
if (row.createdMs >= weekStartMs && row.createdMs < weekEndMs) {
|
|
130
|
-
weekCost += row.cost;
|
|
131
|
-
}
|
|
132
|
-
if (row.createdMs >= startMs && row.createdMs < endMs) {
|
|
133
|
-
monthCost += row.cost;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const oldestSession = oldestSessionMs ?? nowMs;
|
|
138
|
-
return {
|
|
139
|
-
source: "local",
|
|
140
|
-
hasMonthlyUsage: true,
|
|
141
|
-
rollingUsagePercent: percentOf(sessionCost, SESSION_LIMIT_USD),
|
|
142
|
-
weeklyUsagePercent: percentOf(weekCost, WEEKLY_LIMIT_USD),
|
|
143
|
-
monthlyUsagePercent: percentOf(monthCost, MONTHLY_LIMIT_USD),
|
|
144
|
-
rollingResetInSec: Math.max(0, Math.round((oldestSession + FIVE_HOURS_SEC * 1000 - nowMs) / 1000)),
|
|
145
|
-
weeklyResetInSec: Math.max(0, Math.round((weekEndMs - nowMs) / 1000)),
|
|
146
|
-
monthlyResetInSec: Math.max(0, Math.round((endMs - nowMs) / 1000)),
|
|
147
|
-
updatedAt: now,
|
|
148
|
-
workspaceId: null,
|
|
149
|
-
workspaceName: null,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function percentOf(used: number, limit: number): number {
|
|
154
|
-
if (!Number.isFinite(used) || limit <= 0) return 0;
|
|
155
|
-
return Math.max(0, Math.min(100, Math.round((used / limit) * 1000) / 10));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function startOfUtcWeek(now: Date): Date {
|
|
159
|
-
const midnight = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
160
|
-
const weekdayIndex = (midnight.getUTCDay() + 6) % 7; // Monday = 0
|
|
161
|
-
return new Date(midnight.getTime() - weekdayIndex * 24 * 60 * 60 * 1000);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function monthBounds(now: Date, rows: UsageRow[]): MonthBounds {
|
|
165
|
-
const earliestMs = rows.length > 0 ? Math.min(...rows.map((row) => row.createdMs)) : undefined;
|
|
166
|
-
const anchor = earliestMs !== undefined ? new Date(earliestMs) : now;
|
|
167
|
-
|
|
168
|
-
// Month window anchored to the earliest local row (billing-cycle estimate),
|
|
169
|
-
// mirroring CodexBar's OpenCodeGoLocalUsageReader month heuristic.
|
|
170
|
-
const start = new Date(Date.UTC(
|
|
171
|
-
now.getUTCFullYear(),
|
|
172
|
-
now.getUTCMonth(),
|
|
173
|
-
anchor.getUTCDate(),
|
|
174
|
-
anchor.getUTCHours(),
|
|
175
|
-
anchor.getUTCMinutes(),
|
|
176
|
-
anchor.getUTCSeconds(),
|
|
177
|
-
));
|
|
178
|
-
const shifted = start.getTime() > now.getTime()
|
|
179
|
-
? new Date(start.getTime() - 31 * 24 * 60 * 60 * 1000)
|
|
180
|
-
: start;
|
|
181
|
-
const end = new Date(Date.UTC(
|
|
182
|
-
shifted.getUTCFullYear() + (shifted.getUTCMonth() === 11 ? 1 : 0),
|
|
183
|
-
shifted.getUTCMonth() === 11 ? 0 : shifted.getUTCMonth() + 1,
|
|
184
|
-
Math.min(shifted.getUTCDate(), daysInUtcMonth(shifted.getUTCFullYear(), shifted.getUTCMonth() === 11 ? 0 : shifted.getUTCMonth() + 1)),
|
|
185
|
-
shifted.getUTCHours(),
|
|
186
|
-
shifted.getUTCMinutes(),
|
|
187
|
-
shifted.getUTCSeconds(),
|
|
188
|
-
));
|
|
189
|
-
return { startMs: shifted.getTime(), endMs: end.getTime() };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function daysInUtcMonth(year: number, month: number): number {
|
|
193
|
-
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
194
|
-
}
|
package/src/usage/mappers.ts
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
UsageProviderEntry,
|
|
3
|
-
UsageWindow,
|
|
4
|
-
ProviderCostSnapshot,
|
|
5
|
-
ClaudeUsageSnapshot,
|
|
6
|
-
CodexUsageSnapshot,
|
|
7
|
-
OpenCodeGoUsageSnapshot,
|
|
8
|
-
RateWindowLegacy,
|
|
9
|
-
ClaudeOAuthCredentials,
|
|
10
|
-
CodexOAuthCredentials,
|
|
11
|
-
} from "./types";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function toIsoString(d: Date | null): string | null {
|
|
15
|
-
return d && !Number.isNaN(d.getTime()) ? d.toISOString() : null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function computeResetInSec(resetsAt: string | null | undefined): number | undefined {
|
|
19
|
-
if (!resetsAt) return undefined;
|
|
20
|
-
const d = new Date(resetsAt);
|
|
21
|
-
if (Number.isNaN(d.getTime())) return undefined;
|
|
22
|
-
return Math.max(0, Math.round(d.getTime() / 1000 - Date.now() / 1000));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function computeResetsAt(resetInSec: number | undefined): string | undefined {
|
|
26
|
-
if (resetInSec === undefined || resetInSec < 0) return undefined;
|
|
27
|
-
return new Date(Date.now() + resetInSec * 1000).toISOString();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function mapRateWindowLegacyToWindow(id: string, title: string, rw: RateWindowLegacy | null): UsageWindow | undefined {
|
|
31
|
-
if (!rw) return undefined;
|
|
32
|
-
const resetsAt = toIsoString(rw.resetsAt);
|
|
33
|
-
return {
|
|
34
|
-
id,
|
|
35
|
-
title,
|
|
36
|
-
usedPercent: rw.usedPercent,
|
|
37
|
-
windowMinutes: rw.windowMinutes,
|
|
38
|
-
resetsAt,
|
|
39
|
-
resetInSec: computeResetInSec(resetsAt),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function simpleHash(input: string): string {
|
|
44
|
-
let hash = 0;
|
|
45
|
-
for (let i = 0; i < input.length; i++) {
|
|
46
|
-
const char = input.charCodeAt(i);
|
|
47
|
-
hash = ((hash << 5) - hash + char) | 0;
|
|
48
|
-
}
|
|
49
|
-
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function buildClaudeAccountId(
|
|
53
|
-
snapshot: ClaudeUsageSnapshot | null,
|
|
54
|
-
oauthCreds: ClaudeOAuthCredentials | null,
|
|
55
|
-
): string | null {
|
|
56
|
-
// Best source: email from Web API
|
|
57
|
-
if (snapshot?.accountEmail) {
|
|
58
|
-
return snapshot.accountEmail;
|
|
59
|
-
}
|
|
60
|
-
// Second best: org UUID from Web API (not in snapshot currently, but we can add if needed)
|
|
61
|
-
// For now, use Keychain metadata as fallback
|
|
62
|
-
if (oauthCreds?.subscriptionType || oauthCreds?.rateLimitTier) {
|
|
63
|
-
const parts = ["claude"];
|
|
64
|
-
if (oauthCreds.subscriptionType) parts.push(oauthCreds.subscriptionType);
|
|
65
|
-
if (oauthCreds.rateLimitTier) parts.push(oauthCreds.rateLimitTier);
|
|
66
|
-
if (oauthCreds.scopes && oauthCreds.scopes.length > 0) {
|
|
67
|
-
// Add a short hash of scopes to distinguish accounts with same tier
|
|
68
|
-
const scopesHash = simpleHash(oauthCreds.scopes.join(","));
|
|
69
|
-
parts.push(scopesHash);
|
|
70
|
-
}
|
|
71
|
-
return parts.join("-");
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function mapClaudeToEntry(
|
|
77
|
-
snapshot: ClaudeUsageSnapshot | null,
|
|
78
|
-
errors: string[] = [],
|
|
79
|
-
available: boolean,
|
|
80
|
-
oauthCreds: ClaudeOAuthCredentials | null,
|
|
81
|
-
): UsageProviderEntry {
|
|
82
|
-
const base: UsageProviderEntry = {
|
|
83
|
-
provider: "claude",
|
|
84
|
-
accountId: buildClaudeAccountId(snapshot, oauthCreds),
|
|
85
|
-
available,
|
|
86
|
-
status: snapshot ? "ok" : available ? "error" : "unavailable",
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
if (!snapshot) {
|
|
90
|
-
if (errors.length > 0) {
|
|
91
|
-
base.errors = errors;
|
|
92
|
-
}
|
|
93
|
-
return base;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const windows: UsageWindow[] = [];
|
|
97
|
-
|
|
98
|
-
const primary = mapRateWindowLegacyToWindow("session", "Current Session", snapshot.primary);
|
|
99
|
-
if (primary) windows.push(primary);
|
|
100
|
-
|
|
101
|
-
const secondary = mapRateWindowLegacyToWindow("weekly", "Weekly", snapshot.secondary);
|
|
102
|
-
if (secondary) windows.push(secondary);
|
|
103
|
-
|
|
104
|
-
const opus = mapRateWindowLegacyToWindow("opus", "Opus", snapshot.opus);
|
|
105
|
-
if (opus) windows.push(opus);
|
|
106
|
-
|
|
107
|
-
for (const extra of snapshot.extraRateWindows) {
|
|
108
|
-
const w = mapRateWindowLegacyToWindow(extra.id, extra.title, extra.window);
|
|
109
|
-
if (w) windows.push(w);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let cost: ProviderCostSnapshot | null = null;
|
|
113
|
-
if (snapshot.providerCost) {
|
|
114
|
-
cost = {
|
|
115
|
-
used: snapshot.providerCost.used,
|
|
116
|
-
limit: snapshot.providerCost.limit,
|
|
117
|
-
currency: snapshot.providerCost.currencyCode,
|
|
118
|
-
period: snapshot.providerCost.period,
|
|
119
|
-
resetsAt: toIsoString(snapshot.providerCost.resetsAt),
|
|
120
|
-
updatedAt: toIsoString(snapshot.providerCost.updatedAt) ?? new Date().toISOString(),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
base.metadata = {
|
|
125
|
-
dataSource: snapshot.source,
|
|
126
|
-
accountEmail: snapshot.accountEmail,
|
|
127
|
-
accountOrganization: snapshot.accountOrganization,
|
|
128
|
-
subscriptionType: snapshot.subscriptionType,
|
|
129
|
-
rateLimitTier: snapshot.rateLimitTier,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
base.windows = windows;
|
|
133
|
-
base.cost = cost;
|
|
134
|
-
base.updatedAt = toIsoString(snapshot.updatedAt) ?? new Date().toISOString();
|
|
135
|
-
|
|
136
|
-
return base;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function buildCodexAccountId(
|
|
140
|
-
snapshot: CodexUsageSnapshot | null,
|
|
141
|
-
oauthCreds: CodexOAuthCredentials | null,
|
|
142
|
-
): string | null {
|
|
143
|
-
if (snapshot?.accountId) {
|
|
144
|
-
return snapshot.accountId;
|
|
145
|
-
}
|
|
146
|
-
if (snapshot?.accountEmail) {
|
|
147
|
-
return snapshot.accountEmail;
|
|
148
|
-
}
|
|
149
|
-
if (oauthCreds?.accountId) {
|
|
150
|
-
return oauthCreds.accountId;
|
|
151
|
-
}
|
|
152
|
-
if (oauthCreds?.accountEmail) {
|
|
153
|
-
return oauthCreds.accountEmail;
|
|
154
|
-
}
|
|
155
|
-
if (snapshot?.planType) {
|
|
156
|
-
return `codex-${snapshot.planType}`;
|
|
157
|
-
}
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function mapCodexToEntry(
|
|
162
|
-
snapshot: CodexUsageSnapshot | null,
|
|
163
|
-
errors: string[] = [],
|
|
164
|
-
available: boolean,
|
|
165
|
-
oauthCreds: CodexOAuthCredentials | null,
|
|
166
|
-
): UsageProviderEntry {
|
|
167
|
-
const base: UsageProviderEntry = {
|
|
168
|
-
provider: "codex",
|
|
169
|
-
accountId: buildCodexAccountId(snapshot, oauthCreds),
|
|
170
|
-
available,
|
|
171
|
-
status: snapshot ? "ok" : available ? "error" : "unavailable",
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
if (!snapshot) {
|
|
175
|
-
if (errors.length > 0) {
|
|
176
|
-
base.errors = errors;
|
|
177
|
-
}
|
|
178
|
-
return base;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const windows: UsageWindow[] = [];
|
|
182
|
-
|
|
183
|
-
const primary = mapRateWindowLegacyToWindow("session", "Current Session", snapshot.primaryWindow);
|
|
184
|
-
if (primary) windows.push(primary);
|
|
185
|
-
|
|
186
|
-
const secondary = mapRateWindowLegacyToWindow("weekly", "Weekly", snapshot.secondaryWindow);
|
|
187
|
-
if (secondary) windows.push(secondary);
|
|
188
|
-
|
|
189
|
-
const balance = snapshot.credits?.hasCredits && !snapshot.credits.unlimited
|
|
190
|
-
? snapshot.credits.balance
|
|
191
|
-
: null;
|
|
192
|
-
const individualLimit = snapshot.individualLimit;
|
|
193
|
-
const cost: ProviderCostSnapshot | null = individualLimit || balance !== null
|
|
194
|
-
? {
|
|
195
|
-
used: individualLimit?.used ?? 0,
|
|
196
|
-
limit: individualLimit?.limit ?? 0,
|
|
197
|
-
currency: "Credits",
|
|
198
|
-
period: "Extra usage",
|
|
199
|
-
...(balance !== null ? { balance } : {}),
|
|
200
|
-
resetsAt: toIsoString(individualLimit?.resetsAt ?? null),
|
|
201
|
-
updatedAt: toIsoString(snapshot.updatedAt) ?? new Date().toISOString(),
|
|
202
|
-
}
|
|
203
|
-
: null;
|
|
204
|
-
|
|
205
|
-
base.metadata = {
|
|
206
|
-
dataSource: snapshot.source,
|
|
207
|
-
accountEmail: snapshot.accountEmail ?? oauthCreds?.accountEmail,
|
|
208
|
-
subscriptionType: snapshot.planType,
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
if (errors.length > 0) {
|
|
212
|
-
base.errors = errors;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
base.windows = windows;
|
|
216
|
-
base.cost = cost;
|
|
217
|
-
base.updatedAt = toIsoString(snapshot.updatedAt) ?? new Date().toISOString();
|
|
218
|
-
|
|
219
|
-
return base;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function mapOpenCodeGoToEntry(
|
|
223
|
-
snapshot: OpenCodeGoUsageSnapshot | null,
|
|
224
|
-
errors: string[] = [],
|
|
225
|
-
available: boolean,
|
|
226
|
-
): UsageProviderEntry {
|
|
227
|
-
const base: UsageProviderEntry = {
|
|
228
|
-
provider: "opencode-go",
|
|
229
|
-
accountId: snapshot?.workspaceId ?? null,
|
|
230
|
-
available,
|
|
231
|
-
status: snapshot ? (errors.length > 0 ? "error" : "ok") : available ? "error" : "unavailable",
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
if (!snapshot) {
|
|
235
|
-
if (errors.length > 0) {
|
|
236
|
-
base.errors = errors;
|
|
237
|
-
}
|
|
238
|
-
return base;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const windows: UsageWindow[] = [
|
|
242
|
-
{
|
|
243
|
-
id: "session",
|
|
244
|
-
title: "Rolling",
|
|
245
|
-
usedPercent: snapshot.rollingUsagePercent,
|
|
246
|
-
resetInSec: snapshot.rollingResetInSec,
|
|
247
|
-
resetsAt: computeResetsAt(snapshot.rollingResetInSec),
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
id: "weekly",
|
|
251
|
-
title: "Weekly",
|
|
252
|
-
usedPercent: snapshot.weeklyUsagePercent,
|
|
253
|
-
resetInSec: snapshot.weeklyResetInSec,
|
|
254
|
-
resetsAt: computeResetsAt(snapshot.weeklyResetInSec),
|
|
255
|
-
},
|
|
256
|
-
];
|
|
257
|
-
|
|
258
|
-
if (snapshot.hasMonthlyUsage) {
|
|
259
|
-
windows.push({
|
|
260
|
-
id: "monthly",
|
|
261
|
-
title: "Monthly",
|
|
262
|
-
usedPercent: snapshot.monthlyUsagePercent,
|
|
263
|
-
resetInSec: snapshot.monthlyResetInSec,
|
|
264
|
-
resetsAt: computeResetsAt(snapshot.monthlyResetInSec),
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
base.metadata = {
|
|
269
|
-
dataSource: snapshot.source,
|
|
270
|
-
workspaceId: snapshot.workspaceId,
|
|
271
|
-
workspaceName: snapshot.workspaceName,
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
if (errors.length > 0) {
|
|
275
|
-
base.errors = errors;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
base.windows = windows;
|
|
279
|
-
base.updatedAt = toIsoString(snapshot.updatedAt) ?? new Date().toISOString();
|
|
280
|
-
|
|
281
|
-
return base;
|
|
282
|
-
}
|
package/src/usage/planner.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { TokenResolver } from "./resolver";
|
|
2
|
-
import type { ClaudePlannerDecision, ClaudeDataSource, ClaudeOAuthCredentials, ClaudePlannedRoute, ClaudeWebSession } from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Web session resolver with planning-safe operations. Planning only observes
|
|
6
|
-
* the in-memory session cache and cheap import-source checks; browser cookie
|
|
7
|
-
* import happens exclusively when a Web route executes.
|
|
8
|
-
*/
|
|
9
|
-
export interface ClaudeWebSourceResolver extends TokenResolver<ClaudeWebSession> {
|
|
10
|
-
/** Cache-only session resolution; never reads browser stores. */
|
|
11
|
-
resolveCached(): Promise<ClaudeWebSession | null>;
|
|
12
|
-
/** True when an import could yield candidates (cheap existence checks). */
|
|
13
|
-
hasImportSources(): Promise<boolean>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ClaudeSourcePlannerConfig {
|
|
17
|
-
dataSource: ClaudeDataSource;
|
|
18
|
-
webExtrasEnabled: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class ClaudeSourcePlanner {
|
|
22
|
-
constructor(
|
|
23
|
-
private readonly config: ClaudeSourcePlannerConfig,
|
|
24
|
-
private readonly oauthResolver: TokenResolver<ClaudeOAuthCredentials>,
|
|
25
|
-
private readonly webResolver: ClaudeWebSourceResolver,
|
|
26
|
-
private readonly cliResolver: TokenResolver<string>,
|
|
27
|
-
) {}
|
|
28
|
-
|
|
29
|
-
async decide(): Promise<ClaudePlannerDecision> {
|
|
30
|
-
const selected = this.config.dataSource;
|
|
31
|
-
const webExtras = this.config.webExtrasEnabled;
|
|
32
|
-
|
|
33
|
-
if (selected === "oauth") {
|
|
34
|
-
const oauth = await this.oauthResolver.resolve();
|
|
35
|
-
if (!oauth) return this.decision([], webExtras);
|
|
36
|
-
const cachedWeb = await this.webResolver.resolveCached();
|
|
37
|
-
const routes: ClaudePlannedRoute[] = [{ type: "oauth", credentials: oauth }];
|
|
38
|
-
if (oauth.owner !== "environment") {
|
|
39
|
-
const cli = await this.cliResolver.resolve();
|
|
40
|
-
if (cli) routes.push({ type: "cli", credentials: cli });
|
|
41
|
-
}
|
|
42
|
-
return this.decision(routes, webExtras, cachedWeb ?? undefined);
|
|
43
|
-
}
|
|
44
|
-
if (selected === "web") {
|
|
45
|
-
const cachedWeb = await this.webResolver.resolveCached();
|
|
46
|
-
if (!cachedWeb && !(await this.webResolver.hasImportSources())) {
|
|
47
|
-
return this.decision([], webExtras);
|
|
48
|
-
}
|
|
49
|
-
const routes: ClaudePlannedRoute[] = [{ type: "web", credentials: cachedWeb ?? this.deferredWebSession() }];
|
|
50
|
-
return this.decision(routes, webExtras, cachedWeb ?? undefined);
|
|
51
|
-
}
|
|
52
|
-
if (selected === "cli") {
|
|
53
|
-
const cli = await this.cliResolver.resolve();
|
|
54
|
-
const cachedWeb = webExtras ? await this.webResolver.resolveCached() : null;
|
|
55
|
-
const routes: ClaudePlannedRoute[] = cli ? [{ type: "cli", credentials: cli }] : [];
|
|
56
|
-
return this.decision(routes, webExtras, cachedWeb ?? undefined);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const [oauth, cli] = await Promise.all([
|
|
60
|
-
this.oauthResolver.resolve(),
|
|
61
|
-
this.cliResolver.resolve(),
|
|
62
|
-
]);
|
|
63
|
-
const cachedWeb = await this.webResolver.resolveCached();
|
|
64
|
-
const routes: ClaudePlannedRoute[] = [];
|
|
65
|
-
const webMayBeAvailable = Boolean(cachedWeb) || (await this.webResolver.hasImportSources());
|
|
66
|
-
if (oauth) {
|
|
67
|
-
routes.push({ type: "oauth", credentials: oauth });
|
|
68
|
-
if (oauth.owner !== "environment") {
|
|
69
|
-
if (cli) routes.push({ type: "cli", credentials: cli });
|
|
70
|
-
if (webMayBeAvailable) routes.push({ type: "web", credentials: cachedWeb ?? this.deferredWebSession() });
|
|
71
|
-
}
|
|
72
|
-
} else if (webMayBeAvailable) {
|
|
73
|
-
routes.push({ type: "web", credentials: cachedWeb ?? this.deferredWebSession() });
|
|
74
|
-
if (cli) routes.push({ type: "cli", credentials: cli });
|
|
75
|
-
} else if (cli) {
|
|
76
|
-
routes.push({ type: "cli", credentials: cli });
|
|
77
|
-
}
|
|
78
|
-
return this.decision(routes, webExtras, cachedWeb ?? undefined);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* A planning-safe Web session without a cookie header. The first cursor
|
|
83
|
-
* advance resolves the session once and returns its imported header;
|
|
84
|
-
* later advances continue the same cursor so a rejected source is not
|
|
85
|
-
* re-imported within the attempt.
|
|
86
|
-
*/
|
|
87
|
-
private deferredWebSession(): ClaudeWebSession {
|
|
88
|
-
let sessionPromise: Promise<ClaudeWebSession | null> | null = null;
|
|
89
|
-
let primed = false;
|
|
90
|
-
return {
|
|
91
|
-
cookieHeader: "",
|
|
92
|
-
importNext: async () => {
|
|
93
|
-
sessionPromise ??= this.webResolver.resolve();
|
|
94
|
-
const session = await sessionPromise;
|
|
95
|
-
if (!session) return null;
|
|
96
|
-
if (!primed) {
|
|
97
|
-
primed = true;
|
|
98
|
-
return session.cookieHeader || null;
|
|
99
|
-
}
|
|
100
|
-
return session.importNext ? await session.importNext() : null;
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private decision(
|
|
106
|
-
routes: ClaudePlannedRoute[],
|
|
107
|
-
webExtras: boolean,
|
|
108
|
-
webExtrasCredentials?: ClaudeWebSession,
|
|
109
|
-
): ClaudePlannerDecision {
|
|
110
|
-
return {
|
|
111
|
-
primary: routes[0]?.type ?? "auto",
|
|
112
|
-
fallback: routes.slice(1).map((route) => route.type),
|
|
113
|
-
webExtras,
|
|
114
|
-
routes,
|
|
115
|
-
webExtrasCredentials: webExtras ? webExtrasCredentials : undefined,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|