@knpkv/agent-usage 0.1.0 → 0.2.1
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 +129 -20
- package/dist/client/assets/index-BWajfpNv.js +55 -0
- package/dist/client/assets/{index-CUuLXODk.css → index-B_xl-_rU.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/core/ClaudeLimitSamples.d.ts +30 -0
- package/dist/core/ClaudeLimitSamples.js +69 -0
- package/dist/core/ClaudeLimitSamples.js.map +1 -0
- package/dist/core/ClaudeLimits.d.ts +38 -4
- package/dist/core/ClaudeLimits.js +66 -19
- package/dist/core/ClaudeLimits.js.map +1 -1
- package/dist/core/ClaudeLimitsLive.d.ts +33 -7
- package/dist/core/ClaudeLimitsLive.js +54 -10
- package/dist/core/ClaudeLimitsLive.js.map +1 -1
- package/dist/core/Database.d.ts +9 -1
- package/dist/core/Database.js +6 -2
- package/dist/core/Database.js.map +1 -1
- package/dist/core/Ingest.d.ts +7 -1
- package/dist/core/Ingest.js +36 -6
- package/dist/core/Ingest.js.map +1 -1
- package/dist/core/Model.d.ts +19 -7
- package/dist/core/Model.js +15 -3
- package/dist/core/Model.js.map +1 -1
- package/dist/core/Report.js +21 -7
- package/dist/core/Report.js.map +1 -1
- package/dist/core/Store.js +14 -12
- package/dist/core/Store.js.map +1 -1
- package/dist/main.js +30 -20
- package/dist/main.js.map +1 -1
- package/dist/server/Api.d.ts +130 -94
- package/dist/server/Config.d.ts +22 -3
- package/dist/server/Config.js +66 -4
- package/dist/server/Config.js.map +1 -1
- package/dist/server/ControlSocket.d.ts +73 -0
- package/dist/server/ControlSocket.js +270 -0
- package/dist/server/ControlSocket.js.map +1 -0
- package/dist/server/HttpApplication.d.ts +1 -1
- package/dist/server/HttpApplication.js +2 -1
- package/dist/server/HttpApplication.js.map +1 -1
- package/dist/server/IngestSummary.d.ts +8 -0
- package/dist/server/IngestSummary.js +32 -0
- package/dist/server/IngestSummary.js.map +1 -0
- package/dist/server/Live.d.ts +4 -0
- package/dist/server/Live.js +49 -0
- package/dist/server/Live.js.map +1 -0
- package/dist/server/Login.d.ts +34 -0
- package/dist/server/Login.js +74 -0
- package/dist/server/Login.js.map +1 -0
- package/dist/server/OwnerSession.d.ts +15 -8
- package/dist/server/OwnerSession.js +33 -25
- package/dist/server/OwnerSession.js.map +1 -1
- package/dist/server/Runtime.d.ts +3 -1
- package/dist/server/Runtime.js +32 -8
- package/dist/server/Runtime.js.map +1 -1
- package/dist/server/Server.d.ts +2 -2
- package/dist/server/Server.js +19 -6
- package/dist/server/Server.js.map +1 -1
- package/dist/shared/contracts.d.ts +51 -6
- package/dist/shared/contracts.js +11 -1
- package/dist/shared/contracts.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/dist/client/assets/index-DB-VXWqy.js +0 -55
package/dist/client/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<!-- The bootstrap code rides in the fragment; keep it out of any outbound request. -->
|
|
7
7
|
<meta content="no-referrer" name="referrer" />
|
|
8
8
|
<title>Agent usage</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BWajfpNv.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B_xl-_rU.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude limit samples written by claude-statusline: one JSON line each time a window's reading
|
|
3
|
+
* moves, read like a transcript (byte cursor, complete lines only) into Claude Limit Snapshots.
|
|
4
|
+
*
|
|
5
|
+
* **Mental model**
|
|
6
|
+
*
|
|
7
|
+
* - **A second witness to the same windows.** Samples land next to the OAuth polls under source
|
|
8
|
+
* `claude-statusline`; a Claude window is one series whichever source observed it.
|
|
9
|
+
* - **The file is local.** Samples are stored under this Machine; the hostname a line carries only
|
|
10
|
+
* tells duplicates apart.
|
|
11
|
+
* - **Duplicates are expected.** Two renders, or a crash between the append and the writer's own
|
|
12
|
+
* bookkeeping, can repeat a line, so a sample equal to its window's last kept one is dropped.
|
|
13
|
+
* - **Nothing is passed over quietly.** A line that does not decode, or speaks another format
|
|
14
|
+
* version, is skipped and counted.
|
|
15
|
+
*
|
|
16
|
+
* Format v1: `{"v":1,"observedAt":<epoch ms>,"machine":"<hostname>","window":"five_hour"|"seven_day"|"spend",
|
|
17
|
+
* "usedPercentage":<number>,"resetsAt":<epoch ms|null>}`; unknown fields are ignored.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { Schema } from "effect";
|
|
22
|
+
import { type ReadResult, type SourceFile, type SourceLine } from "./Readers.js";
|
|
23
|
+
/** The last sample kept per machine and window, carried in the cursor to drop repeats. */
|
|
24
|
+
export declare const SamplesState: Schema.Struct<{
|
|
25
|
+
readonly kept: Schema.$Record<Schema.String, Schema.String>;
|
|
26
|
+
}>;
|
|
27
|
+
export type SamplesState = typeof SamplesState.Type;
|
|
28
|
+
export declare const initialSamplesState: SamplesState;
|
|
29
|
+
/** Reads one chunk of samples. Pure: the ingest pass owns the file and the cursor. */
|
|
30
|
+
export declare const readClaudeLimitSamples: (file: SourceFile, lines: ReadonlyArray<SourceLine>, state: SamplesState) => ReadResult<SamplesState>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude limit samples written by claude-statusline: one JSON line each time a window's reading
|
|
3
|
+
* moves, read like a transcript (byte cursor, complete lines only) into Claude Limit Snapshots.
|
|
4
|
+
*
|
|
5
|
+
* **Mental model**
|
|
6
|
+
*
|
|
7
|
+
* - **A second witness to the same windows.** Samples land next to the OAuth polls under source
|
|
8
|
+
* `claude-statusline`; a Claude window is one series whichever source observed it.
|
|
9
|
+
* - **The file is local.** Samples are stored under this Machine; the hostname a line carries only
|
|
10
|
+
* tells duplicates apart.
|
|
11
|
+
* - **Duplicates are expected.** Two renders, or a crash between the append and the writer's own
|
|
12
|
+
* bookkeeping, can repeat a line, so a sample equal to its window's last kept one is dropped.
|
|
13
|
+
* - **Nothing is passed over quietly.** A line that does not decode, or speaks another format
|
|
14
|
+
* version, is skipped and counted.
|
|
15
|
+
*
|
|
16
|
+
* Format v1: `{"v":1,"observedAt":<epoch ms>,"machine":"<hostname>","window":"five_hour"|"seven_day"|"spend",
|
|
17
|
+
* "usedPercentage":<number>,"resetsAt":<epoch ms|null>}`; unknown fields are ignored.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { Option, Schema } from "effect";
|
|
22
|
+
import { countSkip, noSkips } from "./Readers.js";
|
|
23
|
+
/** Epoch milliseconds a `Date` can hold (±8.64e15); anything further out would break the page. */
|
|
24
|
+
const Instant = Schema.Int.check(Schema.isGreaterThanOrEqualTo(-8_640_000_000_000_000), Schema.isLessThanOrEqualTo(8_640_000_000_000_000));
|
|
25
|
+
const ClaudeLimitSample = Schema.fromJsonString(Schema.Struct({
|
|
26
|
+
v: Schema.Literal(1),
|
|
27
|
+
observedAt: Instant,
|
|
28
|
+
machine: Schema.NonEmptyString,
|
|
29
|
+
window: Schema.Literals(["five_hour", "seven_day", "spend"]),
|
|
30
|
+
usedPercentage: Schema.Finite,
|
|
31
|
+
resetsAt: Schema.NullOr(Instant)
|
|
32
|
+
}));
|
|
33
|
+
const decodeSample = Schema.decodeUnknownOption(ClaudeLimitSample);
|
|
34
|
+
/** The last sample kept per machine and window, carried in the cursor to drop repeats. */
|
|
35
|
+
export const SamplesState = Schema.Struct({
|
|
36
|
+
kept: Schema.Record(Schema.String, Schema.String)
|
|
37
|
+
});
|
|
38
|
+
export const initialSamplesState = { kept: {} };
|
|
39
|
+
const windowMinutes = (window) => window === "five_hour" ? 300 : window === "seven_day" ? 10_080 : null;
|
|
40
|
+
/** Reads one chunk of samples. Pure: the ingest pass owns the file and the cursor. */
|
|
41
|
+
export const readClaudeLimitSamples = (file, lines, state) => {
|
|
42
|
+
const kept = new Map(Object.entries(state.kept));
|
|
43
|
+
const snapshots = [];
|
|
44
|
+
let skipped = noSkips;
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const decoded = decodeSample(line.text);
|
|
47
|
+
if (Option.isNone(decoded)) {
|
|
48
|
+
skipped = countSkip(skipped, "unparseableLine");
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const sample = decoded.value;
|
|
52
|
+
const key = `${sample.machine}\u0000${sample.window}`;
|
|
53
|
+
const signature = `${sample.resetsAt ?? "null"}\u0000${sample.usedPercentage}`;
|
|
54
|
+
if (kept.get(key) === signature)
|
|
55
|
+
continue;
|
|
56
|
+
kept.set(key, signature);
|
|
57
|
+
snapshots.push({
|
|
58
|
+
agent: "claude",
|
|
59
|
+
machine: file.machine,
|
|
60
|
+
source: "claude-statusline",
|
|
61
|
+
label: sample.window,
|
|
62
|
+
windowMinutes: windowMinutes(sample.window),
|
|
63
|
+
observedAt: sample.observedAt,
|
|
64
|
+
reading: { _tag: "Known", usedPercent: sample.usedPercentage, resetsAt: sample.resetsAt }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { events: [], snapshots, balances: [], skipped, state: { kept: Object.fromEntries(kept) } };
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=ClaudeLimitSamples.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClaudeLimitSamples.js","sourceRoot":"","sources":["../../src/core/ClaudeLimitSamples.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,7 +23,40 @@ import type { BalanceReading, LimitSnapshot } from "./Model.js";
|
|
|
23
23
|
declare const CredentialsMissing_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
24
24
|
readonly _tag: "CredentialsMissing";
|
|
25
25
|
} & Readonly<A>;
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* No usable credentials. `where` names the last place looked: `file`, `keychain:<service>` (the item
|
|
28
|
+
* is absent), or `credentials` (found, but not Claude Code's OAuth credentials).
|
|
29
|
+
*/
|
|
30
|
+
export declare class CredentialsMissing extends CredentialsMissing_base<{
|
|
31
|
+
readonly where: string;
|
|
32
|
+
}> {
|
|
33
|
+
}
|
|
34
|
+
declare const CredentialsUnreadable_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
35
|
+
readonly _tag: "CredentialsUnreadable";
|
|
36
|
+
} & Readonly<A>;
|
|
37
|
+
/** The credentials file exists but could not be read; `reason` is the platform's failure kind. */
|
|
38
|
+
export declare class CredentialsUnreadable extends CredentialsUnreadable_base<{
|
|
39
|
+
readonly reason: string;
|
|
40
|
+
}> {
|
|
41
|
+
}
|
|
42
|
+
declare const KeychainDenied_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
43
|
+
readonly _tag: "KeychainDenied";
|
|
44
|
+
} & Readonly<A>;
|
|
45
|
+
/**
|
|
46
|
+
* The Keychain would not hand over the item: `security` exited with `exitCode` (locked Keychain, no
|
|
47
|
+
* GUI session, access denied), or, when `exitCode` is null, did not answer before the deadline.
|
|
48
|
+
*/
|
|
49
|
+
export declare class KeychainDenied extends KeychainDenied_base<{
|
|
50
|
+
readonly exitCode: number | null;
|
|
51
|
+
}> {
|
|
52
|
+
}
|
|
53
|
+
declare const KeychainFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
54
|
+
readonly _tag: "KeychainFailed";
|
|
55
|
+
} & Readonly<A>;
|
|
56
|
+
/** `security` started but the lookup broke off: reading its answer failed, or it did not exit normally. */
|
|
57
|
+
export declare class KeychainFailed extends KeychainFailed_base<{
|
|
58
|
+
readonly stage: "output" | "exit";
|
|
59
|
+
}> {
|
|
27
60
|
}
|
|
28
61
|
declare const TokenExpired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
29
62
|
readonly _tag: "TokenExpired";
|
|
@@ -33,8 +66,9 @@ export declare class TokenExpired extends TokenExpired_base<{}> {
|
|
|
33
66
|
declare const UsageFetchFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
34
67
|
readonly _tag: "UsageFetchFailed";
|
|
35
68
|
} & Readonly<A>;
|
|
69
|
+
/** The request itself failed; `cause` is the failure's kind only, never the request carrying the token. */
|
|
36
70
|
export declare class UsageFetchFailed extends UsageFetchFailed_base<{
|
|
37
|
-
readonly cause:
|
|
71
|
+
readonly cause: string;
|
|
38
72
|
}> {
|
|
39
73
|
}
|
|
40
74
|
export interface UsageReply {
|
|
@@ -43,7 +77,7 @@ export interface UsageReply {
|
|
|
43
77
|
}
|
|
44
78
|
/** How a poll reaches the token and the endpoint; the live pair lives at the executable boundary. */
|
|
45
79
|
export interface ClaudeUsageDeps {
|
|
46
|
-
readonly readToken: Effect.Effect<string, CredentialsMissing | TokenExpired>;
|
|
80
|
+
readonly readToken: Effect.Effect<string, CredentialsMissing | CredentialsUnreadable | KeychainDenied | KeychainFailed | TokenExpired>;
|
|
47
81
|
readonly get: (token: string) => Effect.Effect<UsageReply, UsageFetchFailed>;
|
|
48
82
|
}
|
|
49
83
|
export declare const USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
@@ -53,6 +87,6 @@ interface Observations {
|
|
|
53
87
|
readonly snapshots: ReadonlyArray<LimitSnapshot>;
|
|
54
88
|
readonly balances: ReadonlyArray<BalanceReading>;
|
|
55
89
|
}
|
|
56
|
-
/** One poll's observations. Never fails: every failure is an Unknown reading. */
|
|
90
|
+
/** One poll's observations. Never fails: every failure is an Unknown reading, with its detail. */
|
|
57
91
|
export declare const pollClaudeLimits: (deps: ClaudeUsageDeps, machine: string) => Effect.Effect<Observations>;
|
|
58
92
|
export {};
|
|
@@ -20,10 +20,27 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { Clock, Data, Effect, Option, Predicate, Schema } from "effect";
|
|
22
22
|
import { classifyExtraUsage, ExtraUsage } from "./Balances.js";
|
|
23
|
+
/**
|
|
24
|
+
* No usable credentials. `where` names the last place looked: `file`, `keychain:<service>` (the item
|
|
25
|
+
* is absent), or `credentials` (found, but not Claude Code's OAuth credentials).
|
|
26
|
+
*/
|
|
23
27
|
export class CredentialsMissing extends Data.TaggedError("CredentialsMissing") {
|
|
24
28
|
}
|
|
29
|
+
/** The credentials file exists but could not be read; `reason` is the platform's failure kind. */
|
|
30
|
+
export class CredentialsUnreadable extends Data.TaggedError("CredentialsUnreadable") {
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The Keychain would not hand over the item: `security` exited with `exitCode` (locked Keychain, no
|
|
34
|
+
* GUI session, access denied), or, when `exitCode` is null, did not answer before the deadline.
|
|
35
|
+
*/
|
|
36
|
+
export class KeychainDenied extends Data.TaggedError("KeychainDenied") {
|
|
37
|
+
}
|
|
38
|
+
/** `security` started but the lookup broke off: reading its answer failed, or it did not exit normally. */
|
|
39
|
+
export class KeychainFailed extends Data.TaggedError("KeychainFailed") {
|
|
40
|
+
}
|
|
25
41
|
export class TokenExpired extends Data.TaggedError("TokenExpired") {
|
|
26
42
|
}
|
|
43
|
+
/** The request itself failed; `cause` is the failure's kind only, never the request carrying the token. */
|
|
27
44
|
export class UsageFetchFailed extends Data.TaggedError("UsageFetchFailed") {
|
|
28
45
|
}
|
|
29
46
|
export const USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
@@ -38,7 +55,7 @@ const decodeCredentials = Schema.decodeUnknownOption(Credentials);
|
|
|
38
55
|
export const tokenFromCredentials = (raw) => Effect.gen(function* () {
|
|
39
56
|
const credentials = decodeCredentials(raw);
|
|
40
57
|
if (Option.isNone(credentials))
|
|
41
|
-
return yield* new CredentialsMissing();
|
|
58
|
+
return yield* new CredentialsMissing({ where: "credentials" });
|
|
42
59
|
const { accessToken, expiresAt } = credentials.value.claudeAiOauth;
|
|
43
60
|
const now = yield* Clock.currentTimeMillis;
|
|
44
61
|
if (expiresAt !== undefined && expiresAt <= now)
|
|
@@ -50,10 +67,11 @@ const Window = Schema.Struct({
|
|
|
50
67
|
resets_at: Schema.optionalKey(Schema.NullOr(Schema.String))
|
|
51
68
|
});
|
|
52
69
|
const decodeWindow = Schema.decodeUnknownOption(Window);
|
|
53
|
-
const
|
|
70
|
+
const decodeJson = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
|
|
71
|
+
const decodeReply = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Unknown));
|
|
54
72
|
const decodeExtraUsage = Schema.decodeUnknownOption(Schema.NullOr(ExtraUsage));
|
|
55
73
|
const windowMinutes = (label) => label === "five_hour" ? 300 : label.startsWith("seven_day") ? 10_080 : null;
|
|
56
|
-
const unknownObservations = (machine, observedAt, reason) => ({
|
|
74
|
+
const unknownObservations = (machine, observedAt, reason, detail) => ({
|
|
57
75
|
snapshots: [{
|
|
58
76
|
agent: "claude",
|
|
59
77
|
machine,
|
|
@@ -61,18 +79,23 @@ const unknownObservations = (machine, observedAt, reason) => ({
|
|
|
61
79
|
label: "*",
|
|
62
80
|
windowMinutes: null,
|
|
63
81
|
observedAt,
|
|
64
|
-
reading: { _tag: "Unknown", reason }
|
|
82
|
+
reading: { _tag: "Unknown", reason, detail }
|
|
65
83
|
}],
|
|
66
|
-
balances: [{ kind: "claude-extra-usage", machine, observedAt, value: { _tag: "Unknown", reason } }]
|
|
84
|
+
balances: [{ kind: "claude-extra-usage", machine, observedAt, value: { _tag: "Unknown", reason, detail } }]
|
|
67
85
|
});
|
|
68
86
|
const classify = (machine, observedAt, reply) => {
|
|
69
|
-
if (reply.status === 401 || reply.status === 403)
|
|
70
|
-
return unknownObservations(machine, observedAt, "AuthExpired");
|
|
87
|
+
if (reply.status === 401 || reply.status === 403) {
|
|
88
|
+
return unknownObservations(machine, observedAt, "AuthExpired", `HTTP ${reply.status}`);
|
|
89
|
+
}
|
|
71
90
|
if (reply.status !== 200)
|
|
72
|
-
return unknownObservations(machine, observedAt, "Fetch");
|
|
73
|
-
const
|
|
74
|
-
if (Option.isNone(
|
|
75
|
-
return unknownObservations(machine, observedAt, "Parse");
|
|
91
|
+
return unknownObservations(machine, observedAt, "Fetch", `HTTP ${reply.status}`);
|
|
92
|
+
const json = decodeJson(reply.body);
|
|
93
|
+
if (Option.isNone(json))
|
|
94
|
+
return unknownObservations(machine, observedAt, "Parse", "reply was not JSON");
|
|
95
|
+
const decoded = Array.isArray(json.value) ? Option.none() : decodeReply(json.value);
|
|
96
|
+
if (Option.isNone(decoded)) {
|
|
97
|
+
return unknownObservations(machine, observedAt, "Parse", "reply was JSON but not an object");
|
|
98
|
+
}
|
|
76
99
|
// Extra usage is a balance, not a percentage window, even when it reports a utilization.
|
|
77
100
|
const windows = Object.entries(decoded.value).filter(([label]) => label !== "extra_usage");
|
|
78
101
|
const snapshots = windows.flatMap(([label, value]) => {
|
|
@@ -90,7 +113,15 @@ const classify = (machine, observedAt, reply) => {
|
|
|
90
113
|
// A window that is there but does not read is a failed reading of it, not silence: its last
|
|
91
114
|
// level must not carry on.
|
|
92
115
|
const claimsWindow = Predicate.hasProperty(value, "utilization") && value.utilization !== null;
|
|
93
|
-
return claimsWindow
|
|
116
|
+
return claimsWindow
|
|
117
|
+
? snapshot({
|
|
118
|
+
_tag: "Unknown",
|
|
119
|
+
reason: "Parse",
|
|
120
|
+
detail: Predicate.isNumber(value.utilization)
|
|
121
|
+
? `${label}: resets_at is not a time`
|
|
122
|
+
: `${label}: utilization is not a number`
|
|
123
|
+
})
|
|
124
|
+
: [];
|
|
94
125
|
}
|
|
95
126
|
const resets = window.value.resets_at;
|
|
96
127
|
if (resets === undefined || resets === null) {
|
|
@@ -99,27 +130,43 @@ const classify = (machine, observedAt, reply) => {
|
|
|
99
130
|
const resetsAt = Date.parse(resets);
|
|
100
131
|
return Number.isFinite(resetsAt)
|
|
101
132
|
? snapshot({ _tag: "Known", usedPercent: window.value.utilization, resetsAt })
|
|
102
|
-
: snapshot({ _tag: "Unknown", reason: "Parse" });
|
|
133
|
+
: snapshot({ _tag: "Unknown", reason: "Parse", detail: `${label}: resets_at is not a time` });
|
|
103
134
|
});
|
|
104
135
|
const extra = decodeExtraUsage(decoded.value["extra_usage"] ?? null);
|
|
105
136
|
const balance = {
|
|
106
137
|
kind: "claude-extra-usage",
|
|
107
138
|
machine,
|
|
108
139
|
observedAt,
|
|
109
|
-
value: Option.isNone(extra)
|
|
140
|
+
value: Option.isNone(extra)
|
|
141
|
+
? { _tag: "Unknown", reason: "Parse", detail: "extra_usage not understood" }
|
|
142
|
+
: classifyExtraUsage(extra.value)
|
|
110
143
|
};
|
|
111
144
|
return {
|
|
112
|
-
snapshots: snapshots.length === 0
|
|
145
|
+
snapshots: snapshots.length === 0
|
|
146
|
+
? unknownObservations(machine, observedAt, "NoData", "the reply named no limit window").snapshots
|
|
147
|
+
: snapshots,
|
|
113
148
|
balances: [balance]
|
|
114
149
|
};
|
|
115
150
|
};
|
|
116
|
-
|
|
151
|
+
const describeMissing = (where) => where === "file"
|
|
152
|
+
? "no credentials file"
|
|
153
|
+
: where === "credentials"
|
|
154
|
+
? "the stored credentials are not Claude Code's OAuth credentials"
|
|
155
|
+
: `no Keychain item ${where.replace(/^keychain:/u, "")}`;
|
|
156
|
+
/** One poll's observations. Never fails: every failure is an Unknown reading, with its detail. */
|
|
117
157
|
export const pollClaudeLimits = (deps, machine) => Effect.gen(function* () {
|
|
118
158
|
const observedAt = yield* Clock.currentTimeMillis;
|
|
119
159
|
return yield* deps.readToken.pipe(Effect.flatMap(deps.get), Effect.map((reply) => classify(machine, observedAt, reply)), Effect.catchTags({
|
|
120
|
-
CredentialsMissing: () => Effect.succeed(unknownObservations(machine, observedAt, "NoAuth")),
|
|
121
|
-
|
|
122
|
-
|
|
160
|
+
CredentialsMissing: (error) => Effect.succeed(unknownObservations(machine, observedAt, "NoAuth", describeMissing(error.where))),
|
|
161
|
+
CredentialsUnreadable: (error) => Effect.succeed(unknownObservations(machine, observedAt, "NoAuth", `the credentials file could not be read (${error.reason})`)),
|
|
162
|
+
KeychainDenied: (error) => Effect.succeed(unknownObservations(machine, observedAt, "KeychainDenied", error.exitCode === null
|
|
163
|
+
? "the Keychain did not answer in time (locked, or waiting on a password prompt)"
|
|
164
|
+
: `the Keychain refused access (security exited ${error.exitCode})`)),
|
|
165
|
+
KeychainFailed: (error) => Effect.succeed(unknownObservations(machine, observedAt, "KeychainDenied", error.stage === "output"
|
|
166
|
+
? "the Keychain lookup broke off while its answer was being read"
|
|
167
|
+
: "the Keychain process did not exit normally")),
|
|
168
|
+
TokenExpired: () => Effect.succeed(unknownObservations(machine, observedAt, "AuthExpired", "the stored token expired; Claude Code renews it on its next use")),
|
|
169
|
+
UsageFetchFailed: (error) => Effect.succeed(unknownObservations(machine, observedAt, "Fetch", `request failed: ${error.cause}`))
|
|
123
170
|
}));
|
|
124
171
|
});
|
|
125
172
|
//# sourceMappingURL=ClaudeLimits.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClaudeLimits.js","sourceRoot":"","sources":["../../src/core/ClaudeLimits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAG9D,MAAM,OAAO,kBAAmB,SAAQ,IAAI,CAAC,WAAW,CAAC,oBAAoB,
|
|
1
|
+
{"version":3,"file":"ClaudeLimits.js","sourceRoot":"","sources":["../../src/core/ClaudeLimits.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The live token and endpoint behind {@link pollClaudeLimits}.
|
|
3
3
|
*
|
|
4
|
-
* Linux keeps Claude Code's credentials in `<
|
|
5
|
-
* login Keychain. The file is tried first and the Keychain second, so no platform check is
|
|
6
|
-
* on Linux the `security` binary does not exist and the lookup
|
|
4
|
+
* Linux keeps Claude Code's credentials in `<secure storage dir>/.credentials.json`; macOS keeps them
|
|
5
|
+
* in the login Keychain. The file is tried first and the Keychain second, so no platform check is
|
|
6
|
+
* needed: on Linux the `security` binary does not exist and the lookup reports the missing file.
|
|
7
|
+
*
|
|
8
|
+
* The Keychain item is the one Claude Code itself reads: its service name carries a hash of a
|
|
9
|
+
* non-default config directory and the account is the user (see `ClaudeCredentialsLocation`).
|
|
10
|
+
* `security`'s exit code tells a missing item (44) from one this process may not read, such as a
|
|
11
|
+
* locked Keychain or a session without a GUI, so neither shows up as "not signed in".
|
|
7
12
|
*
|
|
8
13
|
* The executable provides `FetchHttpClient.layer`; redirects are refused so the token cannot
|
|
9
14
|
* follow one off Anthropic's host.
|
|
10
15
|
*
|
|
11
16
|
* @module
|
|
12
17
|
*/
|
|
13
|
-
import { Effect, FileSystem
|
|
18
|
+
import { Duration, Effect, FileSystem } from "effect";
|
|
14
19
|
import { HttpClient } from "effect/http";
|
|
15
20
|
import { ChildProcessSpawner } from "effect/process";
|
|
16
|
-
import { type ClaudeUsageDeps } from "./ClaudeLimits.js";
|
|
17
|
-
/**
|
|
18
|
-
export declare const
|
|
21
|
+
import { type ClaudeUsageDeps, CredentialsMissing, CredentialsUnreadable, KeychainDenied, KeychainFailed } from "./ClaudeLimits.js";
|
|
22
|
+
/** How long `security` may take: a locked Keychain or a password prompt can wait forever. */
|
|
23
|
+
export declare const KEYCHAIN_DEADLINE: Duration.Duration;
|
|
24
|
+
/** A Keychain lookup bounded by {@link KEYCHAIN_DEADLINE}, so a hung lookup cannot stall polling. */
|
|
25
|
+
export declare const withKeychainDeadline: <A, E, R>(lookup: Effect.Effect<A, E, R>) => Effect.Effect<A, E | KeychainDenied, R>;
|
|
26
|
+
/** A failed read of the credentials file: only a missing file sends the lookup on to the Keychain. */
|
|
27
|
+
export declare const credentialsFileFailure: (reason: string) => CredentialsMissing | CredentialsUnreadable;
|
|
28
|
+
/** Where Claude Code's credentials live; built by the configuration. */
|
|
29
|
+
export interface ClaudeCredentialsPlaces {
|
|
30
|
+
readonly file: string;
|
|
31
|
+
readonly keychainService: string;
|
|
32
|
+
readonly keychainAccount: string;
|
|
33
|
+
}
|
|
34
|
+
/** The `security` arguments that print Claude Code's own Keychain item. */
|
|
35
|
+
export declare const keychainArgs: (places: Pick<ClaudeCredentialsPlaces, "keychainService" | "keychainAccount">) => ReadonlyArray<string>;
|
|
36
|
+
/** What a finished `security` lookup means: the item's contents, or why there are none. */
|
|
37
|
+
export declare const keychainOutcome: (exitCode: number, stdout: string, service: string) => Effect.Effect<string, CredentialsMissing | KeychainDenied>;
|
|
38
|
+
/**
|
|
39
|
+
* The item a started `security` lookup returns, from its output and exit code. Once it has started,
|
|
40
|
+
* a failure is the Keychain's, never a missing credentials file.
|
|
41
|
+
*/
|
|
42
|
+
export declare const readKeychainItem: <E1, E2>(stdout: Effect.Effect<string, E1>, exitCode: Effect.Effect<number, E2>, service: string) => Effect.Effect<string, CredentialsMissing | KeychainDenied | KeychainFailed>;
|
|
43
|
+
/** Builds the live dependencies for where Claude Code keeps its credentials. */
|
|
44
|
+
export declare const liveClaudeUsageDeps: (places: ClaudeCredentialsPlaces) => Effect.Effect<ClaudeUsageDeps, never, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | HttpClient.HttpClient>;
|
|
@@ -1,28 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The live token and endpoint behind {@link pollClaudeLimits}.
|
|
3
3
|
*
|
|
4
|
-
* Linux keeps Claude Code's credentials in `<
|
|
5
|
-
* login Keychain. The file is tried first and the Keychain second, so no platform check is
|
|
6
|
-
* on Linux the `security` binary does not exist and the lookup
|
|
4
|
+
* Linux keeps Claude Code's credentials in `<secure storage dir>/.credentials.json`; macOS keeps them
|
|
5
|
+
* in the login Keychain. The file is tried first and the Keychain second, so no platform check is
|
|
6
|
+
* needed: on Linux the `security` binary does not exist and the lookup reports the missing file.
|
|
7
|
+
*
|
|
8
|
+
* The Keychain item is the one Claude Code itself reads: its service name carries a hash of a
|
|
9
|
+
* non-default config directory and the account is the user (see `ClaudeCredentialsLocation`).
|
|
10
|
+
* `security`'s exit code tells a missing item (44) from one this process may not read, such as a
|
|
11
|
+
* locked Keychain or a session without a GUI, so neither shows up as "not signed in".
|
|
7
12
|
*
|
|
8
13
|
* The executable provides `FetchHttpClient.layer`; redirects are refused so the token cannot
|
|
9
14
|
* follow one off Anthropic's host.
|
|
10
15
|
*
|
|
11
16
|
* @module
|
|
12
17
|
*/
|
|
13
|
-
import { Duration, Effect, FileSystem,
|
|
18
|
+
import { Duration, Effect, FileSystem, Stream } from "effect";
|
|
14
19
|
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http";
|
|
15
20
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
16
|
-
import { CredentialsMissing, tokenFromCredentials, USAGE_URL, UsageFetchFailed } from "./ClaudeLimits.js";
|
|
21
|
+
import { CredentialsMissing, CredentialsUnreadable, KeychainDenied, KeychainFailed, tokenFromCredentials, USAGE_URL, UsageFetchFailed } from "./ClaudeLimits.js";
|
|
17
22
|
const TIMEOUT = Duration.seconds(5);
|
|
18
|
-
/**
|
|
19
|
-
|
|
23
|
+
/** `security`'s exit code for an item that does not exist (errSecItemNotFound). */
|
|
24
|
+
const ITEM_NOT_FOUND = 44;
|
|
25
|
+
/** How long `security` may take: a locked Keychain or a password prompt can wait forever. */
|
|
26
|
+
export const KEYCHAIN_DEADLINE = Duration.seconds(10);
|
|
27
|
+
/** A Keychain lookup bounded by {@link KEYCHAIN_DEADLINE}, so a hung lookup cannot stall polling. */
|
|
28
|
+
export const withKeychainDeadline = (lookup) => lookup.pipe(Effect.timeoutOrElse({
|
|
29
|
+
duration: KEYCHAIN_DEADLINE,
|
|
30
|
+
orElse: () => Effect.fail(new KeychainDenied({ exitCode: null }))
|
|
31
|
+
}));
|
|
32
|
+
/** A failed read of the credentials file: only a missing file sends the lookup on to the Keychain. */
|
|
33
|
+
export const credentialsFileFailure = (reason) => reason === "NotFound" ? new CredentialsMissing({ where: "file" }) : new CredentialsUnreadable({ reason });
|
|
34
|
+
/** The `security` arguments that print Claude Code's own Keychain item. */
|
|
35
|
+
export const keychainArgs = (places) => [
|
|
36
|
+
"find-generic-password",
|
|
37
|
+
"-a",
|
|
38
|
+
places.keychainAccount,
|
|
39
|
+
"-w",
|
|
40
|
+
"-s",
|
|
41
|
+
places.keychainService
|
|
42
|
+
];
|
|
43
|
+
/** What a finished `security` lookup means: the item's contents, or why there are none. */
|
|
44
|
+
export const keychainOutcome = (exitCode, stdout, service) => exitCode === 0
|
|
45
|
+
? Effect.succeed(stdout)
|
|
46
|
+
: exitCode === ITEM_NOT_FOUND
|
|
47
|
+
? Effect.fail(new CredentialsMissing({ where: `keychain:${service}` }))
|
|
48
|
+
: Effect.fail(new KeychainDenied({ exitCode }));
|
|
49
|
+
/**
|
|
50
|
+
* The item a started `security` lookup returns, from its output and exit code. Once it has started,
|
|
51
|
+
* a failure is the Keychain's, never a missing credentials file.
|
|
52
|
+
*/
|
|
53
|
+
export const readKeychainItem = (stdout, exitCode, service) => Effect.gen(function* () {
|
|
54
|
+
const output = yield* stdout.pipe(Effect.mapError(() => new KeychainFailed({ stage: "output" })));
|
|
55
|
+
const code = yield* exitCode.pipe(Effect.mapError(() => new KeychainFailed({ stage: "exit" })));
|
|
56
|
+
return yield* keychainOutcome(code, output, service);
|
|
57
|
+
});
|
|
58
|
+
/** Builds the live dependencies for where Claude Code keeps its credentials. */
|
|
59
|
+
export const liveClaudeUsageDeps = (places) => Effect.gen(function* () {
|
|
20
60
|
const fs = yield* FileSystem.FileSystem;
|
|
21
|
-
const path = yield* Path.Path;
|
|
22
61
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
23
62
|
const client = yield* HttpClient.HttpClient;
|
|
24
|
-
const fromFile = fs.readFileString(
|
|
25
|
-
const fromKeychain =
|
|
63
|
+
const fromFile = fs.readFileString(places.file).pipe(Effect.mapError((error) => credentialsFileFailure(error.reason._tag)));
|
|
64
|
+
const fromKeychain = withKeychainDeadline(Effect.scoped(Effect.gen(function* () {
|
|
65
|
+
const handle = yield* spawner.spawn(ChildProcess.make("security", keychainArgs(places))).pipe(
|
|
66
|
+
// No `security` binary (Linux) or no way to start it: the file was the only place to look.
|
|
67
|
+
Effect.mapError(() => new CredentialsMissing({ where: "file" })));
|
|
68
|
+
return yield* readKeychainItem(Stream.mkString(Stream.decodeText(handle.stdout)), handle.exitCode, places.keychainService);
|
|
69
|
+
})));
|
|
26
70
|
const deps = {
|
|
27
71
|
readToken: fromFile.pipe(Effect.catchTag("CredentialsMissing", () => fromKeychain), Effect.flatMap(tokenFromCredentials)),
|
|
28
72
|
get: (token) => client.execute(HttpClientRequest.get(USAGE_URL).pipe(HttpClientRequest.bearerToken(token), HttpClientRequest.setHeaders({ "anthropic-beta": "oauth-2025-04-20", "User-Agent": "agent-usage" }))).pipe(Effect.flatMap((response) => Effect.map(response.text, (body) => ({ status: response.status, body }))), Effect.timeout(TIMEOUT),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClaudeLimitsLive.js","sourceRoot":"","sources":["../../src/core/ClaudeLimitsLive.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ClaudeLimitsLive.js","sourceRoot":"","sources":["../../src/core/ClaudeLimitsLive.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Database.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import { FileSystem, Layer, Path } from "effect";
|
|
1
|
+
import { Effect, FileSystem, Layer, Path } from "effect";
|
|
2
2
|
import { StoreError, UsageStore } from "./Store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Checks the store directory the way the store needs it: no symlink, `0700`, created so when
|
|
5
|
+
* missing. Runs before the store's lock is taken, and again when the store opens.
|
|
6
|
+
*/
|
|
7
|
+
export declare const prepareStoreDirectory: (directory: string) => Effect.Effect<{
|
|
8
|
+
filename: string;
|
|
9
|
+
files: string[];
|
|
10
|
+
}, StoreError, FileSystem.FileSystem | Path.Path>;
|
|
3
11
|
/** The store, opened privately in `directory` (`usage.db`), migrated and ready. */
|
|
4
12
|
export declare const databaseLayer: (directory: string) => Layer.Layer<UsageStore, StoreError, FileSystem.FileSystem | Path.Path>;
|
package/dist/core/Database.js
CHANGED
|
@@ -26,7 +26,11 @@ const refuseSymlink = (fs, path) => fs.readLink(path).pipe(Effect.matchEffect({
|
|
|
26
26
|
: fail("secure.readlink", error),
|
|
27
27
|
onSuccess: (target) => fail("secure.symlink", { path, target })
|
|
28
28
|
}));
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Checks the store directory the way the store needs it: no symlink, `0700`, created so when
|
|
31
|
+
* missing. Runs before the store's lock is taken, and again when the store opens.
|
|
32
|
+
*/
|
|
33
|
+
export const prepareStoreDirectory = Effect.fnUntraced(function* (directory) {
|
|
30
34
|
const fs = yield* FileSystem.FileSystem;
|
|
31
35
|
const path = yield* Path.Path;
|
|
32
36
|
if (path.sep !== "/")
|
|
@@ -59,7 +63,7 @@ const restrictFiles = Effect.fnUntraced(function* (files) {
|
|
|
59
63
|
});
|
|
60
64
|
/** The store, opened privately in `directory` (`usage.db`), migrated and ready. */
|
|
61
65
|
export const databaseLayer = (directory) => Layer.unwrap(Effect.gen(function* () {
|
|
62
|
-
const { filename, files } = yield*
|
|
66
|
+
const { filename, files } = yield* prepareStoreDirectory(directory);
|
|
63
67
|
return UsageStore.layer.pipe(Layer.provide(SqliteClient.layer({ filename })), Layer.tap(() => restrictFiles(files)));
|
|
64
68
|
}));
|
|
65
69
|
//# sourceMappingURL=Database.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Database.js","sourceRoot":"","sources":["../../src/core/Database.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEnD,MAAM,aAAa,GAAG,UAAU,CAAA;AAEhC,MAAM,IAAI,GAAG,CAAC,SAAiB,EAAE,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AAErG,qGAAqG;AACrG,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;AAE/E,MAAM,aAAa,GAAG,CAAC,EAAyB,EAAE,IAAY,EAAE,EAAE,CAChE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CACpB,MAAM,CAAC,WAAW,CAAC;IACjB,wFAAwF;IACxF,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CACnB,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,IAAI,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;QACzD,CAAC,CAAC,MAAM,CAAC,IAAI;QACb,CAAC,CAAC,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC;IACpC,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;CAChE,CAAC,CACH,CAAA;AAEH,MAAM,
|
|
1
|
+
{"version":3,"file":"Database.js","sourceRoot":"","sources":["../../src/core/Database.ts"],"names":[],"mappings":"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"}
|
package/dist/core/Ingest.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface SourceRoots {
|
|
|
23
23
|
readonly claudeProjects: string;
|
|
24
24
|
/** `<CODEX_HOME>`: rollouts live in `sessions/`, and Codex moves archived ones to `archived_sessions/`. */
|
|
25
25
|
readonly codexHome: string;
|
|
26
|
+
/** The Claude limit samples claude-statusline appends (`AGENT_USAGE_CLAUDE_LIMITS`). */
|
|
27
|
+
readonly claudeLimitSamples: string;
|
|
26
28
|
readonly machine: string;
|
|
27
29
|
}
|
|
28
30
|
export interface UnreadableFile {
|
|
@@ -44,6 +46,7 @@ export interface IngestStatus {
|
|
|
44
46
|
readonly finishedAt: number;
|
|
45
47
|
readonly claude: SourceStatus;
|
|
46
48
|
readonly codex: SourceStatus;
|
|
49
|
+
readonly claudeLimitSamples: SourceStatus;
|
|
47
50
|
}
|
|
48
51
|
export interface IngestOptions {
|
|
49
52
|
/** How many bytes to read and commit at a time; bounds memory during a backfill. */
|
|
@@ -51,5 +54,8 @@ export interface IngestOptions {
|
|
|
51
54
|
/** Lines longer than this are skipped and counted rather than held in memory. */
|
|
52
55
|
readonly maxLineBytes?: number;
|
|
53
56
|
}
|
|
54
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Reads everything appended since the last pass in every source: Claude transcripts, Codex rollouts
|
|
59
|
+
* and Claude limit samples. Fails only when the store does.
|
|
60
|
+
*/
|
|
55
61
|
export declare const ingestOnce: (roots: SourceRoots, options?: IngestOptions) => Effect.Effect<IngestStatus, StoreError, UsageStore | FileSystem.FileSystem | Path.Path>;
|