@scrylog/cli 0.1.4 → 0.3.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/README.md +46 -7
- package/package.json +13 -10
- package/src/api/docs/sessions.ts +3 -2
- package/src/api/docs/system.ts +3 -0
- package/src/api/docs/usage.ts +2 -2
- package/src/api/routes/sessions.ts +6 -2
- package/src/api/routes/system.ts +4 -1
- package/src/api/routes/webhooks/claude.ts +9 -21
- package/src/api/routes/webhooks/opencode.ts +6 -1
- package/src/api/routes.ts +1 -1
- package/src/api/schemas/common.ts +8 -0
- package/src/api/schemas/sessions.ts +15 -1
- package/src/api/schemas/usage.ts +1 -0
- package/src/cli/commands.ts +170 -26
- package/src/cli/index.ts +18 -0
- package/src/cli/program.ts +175 -0
- package/src/daemon/collectors/claude/collector.ts +11 -1
- package/src/daemon/collectors/claude/discovery.ts +2 -23
- package/src/daemon/collectors/codex/collector.ts +3 -3
- package/src/daemon/collectors/git.ts +60 -2
- package/src/daemon/collectors/herdr/collector.ts +128 -0
- package/src/daemon/collectors/herdr/index.ts +2 -0
- package/src/daemon/collectors/herdr/normalization.ts +111 -0
- package/src/daemon/collectors/opencode/collector.ts +4 -2
- package/src/daemon/collectors/opencode-db.ts +17 -5
- package/src/daemon/collectors/tmux/collector.ts +21 -1
- package/src/daemon/collectors/tmux/enrichment.ts +1 -0
- package/src/daemon/collectors/usage.ts +115 -49
- package/src/daemon/index.ts +2 -0
- package/src/daemon/store.ts +109 -14
- package/src/postinstall.cjs +11 -0
- package/src/shared/config.ts +33 -0
- package/src/shared/session-exclusions.ts +5 -0
- package/src/shared/session-view.ts +67 -6
- package/src/shared/terminal-capabilities.ts +130 -0
- package/src/shared/types.ts +33 -2
- package/src/shared/version.ts +44 -0
- package/src/tui/App.tsx +240 -0
- package/src/tui/assets/providers/SOURCES.md +21 -0
- package/src/tui/assets/providers/claude-terminal.png +0 -0
- package/src/tui/assets/providers/claude-terminal.svg +62 -0
- package/src/tui/assets/providers/claude.png +0 -0
- package/src/tui/assets/providers/copilot.png +0 -0
- package/src/tui/assets/providers/copilot.svg +44 -0
- package/src/tui/assets/providers/openai-terminal.png +0 -0
- package/src/tui/assets/providers/openai-terminal.svg +62 -0
- package/src/tui/assets/providers/openai.png +0 -0
- package/src/tui/assets/providers/opencode-terminal.png +0 -0
- package/src/tui/assets/providers/opencode-terminal.svg +62 -0
- package/src/tui/assets/providers/opencode.png +0 -0
- package/src/tui/assets/providers/shell.png +0 -0
- package/src/tui/assets/providers/shell.svg +23 -0
- package/src/tui/assets/providers/unknown.png +0 -0
- package/src/tui/assets/providers/unknown.svg +23 -0
- package/src/tui/client.ts +22 -8
- package/src/tui/components/BindingsModal.tsx +82 -0
- package/src/tui/components/DashboardHeader.tsx +256 -0
- package/src/tui/components/KeyBar.tsx +67 -0
- package/src/tui/components/SessionDetailModal.tsx +169 -0
- package/src/tui/components/SessionsView.tsx +184 -0
- package/src/tui/components/TabBar.tsx +40 -0
- package/src/tui/components/UsageSummaryPane.tsx +272 -0
- package/src/tui/components/UsageView.tsx +242 -0
- package/src/tui/components/tool-icons.ts +24 -0
- package/src/tui/index.tsx +106 -0
- package/src/tui/keybindings.ts +49 -0
- package/src/tui/model.ts +295 -0
- package/src/tui/state.ts +77 -0
- package/src/tui/theme.ts +110 -0
- package/src/usage/README.md +3 -3
- package/src/usage/clients/anthropic-oauth-usage.ts +100 -20
- package/src/usage/clients/anthropic-web-usage.ts +133 -49
- package/src/usage/clients/claude-cli-probe.ts +458 -132
- package/src/usage/clients/codex-app-server.ts +354 -0
- package/src/usage/clients/codex-oauth-usage.ts +143 -16
- package/src/usage/clients/opencode-go-api.ts +100 -0
- package/src/usage/clients/opencode-go-usage.ts +163 -27
- package/src/usage/errors.ts +33 -0
- package/src/usage/index.ts +2 -1
- package/src/usage/local/opencode-go-local.ts +194 -0
- package/src/usage/mappers.ts +24 -15
- package/src/usage/planner.ts +89 -25
- package/src/usage/providers/claude.ts +203 -83
- package/src/usage/providers/codex.ts +92 -11
- package/src/usage/providers/opencode-go.ts +94 -16
- package/src/usage/resolvers/browser-cookies.ts +679 -69
- package/src/usage/resolvers/claude-auth.ts +150 -41
- package/src/usage/resolvers/claude-desktop-auth.ts +170 -0
- package/src/usage/resolvers/claude-web.ts +72 -6
- package/src/usage/resolvers/codex-auth.ts +74 -17
- package/src/usage/resolvers/keychain-coordinator.ts +397 -0
- package/src/usage/resolvers/opencode-go-web.ts +47 -6
- package/src/usage/types.ts +37 -2
- package/src/usage/web-session-cache.ts +37 -0
- package/src/neo-blessed.d.ts +0 -4
- package/src/tui/app.ts +0 -920
- package/src/tui/index.ts +0 -193
|
@@ -3,35 +3,113 @@ import type { OpenCodeGoUsageSnapshot } from "../types";
|
|
|
3
3
|
import type { UsageProviderEntry } from "../types";
|
|
4
4
|
import { OpenCodeGoUsageClient } from "../clients/opencode-go-usage";
|
|
5
5
|
import { mapOpenCodeGoToEntry } from "../mappers";
|
|
6
|
+
import { classifyUsageError, isRecoverableAuthenticationError } from "../errors";
|
|
7
|
+
import { usageWebSessionCache, type WebSessionCache } from "../web-session-cache";
|
|
8
|
+
import { OpenCodeGoApiKeyUsageClient } from "../clients/opencode-go-api";
|
|
9
|
+
import { OpenCodeGoLocalUsageReader, type OpenCodeGoLocalUsageFetcher } from "../local/opencode-go-local";
|
|
10
|
+
import type { OpenCodeGoWebCredentials } from "../resolvers/opencode-go-web";
|
|
6
11
|
|
|
7
|
-
export
|
|
8
|
-
|
|
12
|
+
export interface OpenCodeGoApiKeyUsageClientLike {
|
|
13
|
+
fetch(): Promise<OpenCodeGoUsageSnapshot | null>;
|
|
14
|
+
}
|
|
9
15
|
|
|
16
|
+
const CACHE_KEY = "opencode-go";
|
|
17
|
+
|
|
18
|
+
export class OpenCodeGoUsageProvider {
|
|
10
19
|
constructor(
|
|
11
|
-
private readonly resolver: TokenResolver<
|
|
20
|
+
private readonly resolver: TokenResolver<OpenCodeGoWebCredentials>,
|
|
12
21
|
private readonly serverFnId: string,
|
|
22
|
+
private readonly client = new OpenCodeGoUsageClient(),
|
|
23
|
+
private readonly cache: WebSessionCache = usageWebSessionCache,
|
|
24
|
+
private readonly localReader: OpenCodeGoLocalUsageFetcher = new OpenCodeGoLocalUsageReader(),
|
|
25
|
+
private readonly apiKeyClient: OpenCodeGoApiKeyUsageClientLike = new OpenCodeGoApiKeyUsageClient(),
|
|
13
26
|
) {}
|
|
14
27
|
|
|
15
28
|
async fetch(options?: { signal?: AbortSignal }): Promise<{ entry: UsageProviderEntry; snapshot: OpenCodeGoUsageSnapshot | null }> {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
// Official Go usage API is authoritative and independent of browser cookies.
|
|
30
|
+
try {
|
|
31
|
+
const apiSnapshot = await this.apiKeyClient.fetch();
|
|
32
|
+
if (apiSnapshot) {
|
|
33
|
+
return { entry: mapOpenCodeGoToEntry(apiSnapshot, [], true), snapshot: apiSnapshot };
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// Fall through to cookie-based collection.
|
|
20
37
|
}
|
|
38
|
+
return this.fetchWithCookies(options);
|
|
39
|
+
}
|
|
21
40
|
|
|
41
|
+
private async fetchWithCookies(options?: { signal?: AbortSignal }): Promise<{ entry: UsageProviderEntry; snapshot: OpenCodeGoUsageSnapshot | null }> {
|
|
42
|
+
const errors: string[] = [];
|
|
22
43
|
const creds = await this.resolver.resolve();
|
|
23
|
-
|
|
24
|
-
|
|
44
|
+
const observed = this.cache.observe(CACHE_KEY);
|
|
45
|
+
if (!observed.value && !creds?.candidates.length) {
|
|
25
46
|
return { entry: mapOpenCodeGoToEntry(null, [], false), snapshot: null };
|
|
26
47
|
}
|
|
27
48
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
const attempts = [
|
|
50
|
+
...(observed.value ? [{ cookies: observed.value, cached: true }] : []),
|
|
51
|
+
...(creds?.candidates ?? [])
|
|
52
|
+
.filter((candidate) => candidate.cookies !== observed.value)
|
|
53
|
+
.map((candidate) => ({ cookies: candidate.cookies, cached: false })),
|
|
54
|
+
].slice(0, 2);
|
|
55
|
+
let expectedRevision = observed.revision;
|
|
56
|
+
|
|
57
|
+
for (let index = 0; index < attempts.length; index++) {
|
|
58
|
+
const attempt = attempts[index]!;
|
|
59
|
+
let rotated = false;
|
|
60
|
+
try {
|
|
61
|
+
const snapshot = await this.client.fetch(
|
|
62
|
+
{
|
|
63
|
+
cookies: attempt.cookies,
|
|
64
|
+
serverFnId: this.serverFnId,
|
|
65
|
+
onCookieRotation: (cookies) => {
|
|
66
|
+
rotated = true;
|
|
67
|
+
const stored = this.cache.store(CACHE_KEY, cookies, expectedRevision);
|
|
68
|
+
if (stored) expectedRevision = stored.revision;
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
options,
|
|
72
|
+
);
|
|
73
|
+
if (!attempt.cached && !rotated && this.cache.observe(CACHE_KEY).revision === expectedRevision) {
|
|
74
|
+
const stored = this.cache.store(CACHE_KEY, attempt.cookies, expectedRevision);
|
|
75
|
+
if (stored) expectedRevision = stored.revision;
|
|
76
|
+
}
|
|
77
|
+
return { entry: mapOpenCodeGoToEntry(snapshot, [], true), snapshot };
|
|
78
|
+
} catch (error) {
|
|
79
|
+
const failure = classifyUsageError(error);
|
|
80
|
+
if (failure.kind === "cancelled") throw failure;
|
|
81
|
+
if (failure.kind === "invalid-credentials") {
|
|
82
|
+
if (this.cache.clear(CACHE_KEY, expectedRevision)) expectedRevision = 0;
|
|
83
|
+
// Advance the demand-driven cursor at most once per attempt, keeping
|
|
84
|
+
// the two-candidate bound across cached and imported candidates.
|
|
85
|
+
const imported = attempts.length < 2 ? (await creds?.importNext?.()) ?? null : null;
|
|
86
|
+
if (imported && !attempts.some((candidate) => candidate.cookies === imported.cookies)) {
|
|
87
|
+
attempts.push({ cookies: imported.cookies, cached: false });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!(isRecoverableAuthenticationError(failure) && index + 1 < attempts.length)) {
|
|
92
|
+
errors.push(`OpenCode Go: ${failure.message}`);
|
|
93
|
+
return this.fetchLocalFallback(errors);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return this.fetchLocalFallback(errors);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Last resort after every web attempt fails: report locally accumulated
|
|
103
|
+
* opencode-go costs as estimated windows instead of an error. Mirrors the
|
|
104
|
+
* CodexBar "local" source so a stale browser session stays usable.
|
|
105
|
+
*/
|
|
106
|
+
private async fetchLocalFallback(errors: string[]): Promise<{ entry: UsageProviderEntry; snapshot: OpenCodeGoUsageSnapshot | null }> {
|
|
107
|
+
const local = await this.localReader.fetch();
|
|
108
|
+
if (local) {
|
|
109
|
+
// Keeping errors here would force status "error" in the mapper; the local
|
|
110
|
+
// snapshot is reported as a usable estimate instead.
|
|
111
|
+
return { entry: mapOpenCodeGoToEntry(local, [], true), snapshot: local };
|
|
35
112
|
}
|
|
113
|
+
return { entry: mapOpenCodeGoToEntry(null, errors, true), snapshot: null };
|
|
36
114
|
}
|
|
37
115
|
}
|