@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/main.js
CHANGED
|
@@ -4,23 +4,38 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - `agent-usage serve` ingests every minute, polls Claude limits every five, and prints the URL
|
|
6
6
|
* that gets you in.
|
|
7
|
+
* - `agent-usage login [--open]` asks the running server for a fresh one-time link and prints it.
|
|
7
8
|
* - `agent-usage ingest [--json]` runs one ingest pass and reports what it found.
|
|
8
9
|
*
|
|
9
10
|
* @module
|
|
10
11
|
*/
|
|
11
12
|
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
12
|
-
import { Console, Deferred, Effect, Fiber, Layer, Option, Schema } from "effect";
|
|
13
|
+
import { ConfigProvider, Console, Deferred, Effect, Fiber, Layer, Option, Schema } from "effect";
|
|
13
14
|
import { Command, Flag } from "effect/cli";
|
|
14
15
|
import * as Stdio from "effect/Stdio";
|
|
15
16
|
import * as Stream from "effect/Stream";
|
|
16
|
-
import { hostname } from "node:os";
|
|
17
|
+
import { hostname, platform, userInfo } from "node:os";
|
|
18
|
+
// Read at run time from the package.json this file ships next to (`dist/../package.json`).
|
|
19
|
+
import pkg from "../package.json" with { type: "json" };
|
|
17
20
|
import { databaseLayer } from "./core/Database.js";
|
|
18
21
|
import { ingestOnce } from "./core/Ingest.js";
|
|
19
22
|
import { loadConfig } from "./server/Config.js";
|
|
20
|
-
import {
|
|
23
|
+
import { describeIngest } from "./server/IngestSummary.js";
|
|
24
|
+
import { login as requestLogin } from "./server/Login.js";
|
|
25
|
+
import { makeOwnerSessionSecrets, ownerSessionOrigin } from "./server/OwnerSession.js";
|
|
21
26
|
import { makeServer, Port, PublicOrigin } from "./server/Server.js";
|
|
22
27
|
import { IngestStatus as IngestStatusSchema } from "./shared/contracts.js";
|
|
23
|
-
|
|
28
|
+
/** The operating system's name for the user; Claude Code's own fallback when it cannot ask. */
|
|
29
|
+
const osUserName = () => {
|
|
30
|
+
try {
|
|
31
|
+
return userInfo().username;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return "claude-code-user";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
// Empty values kept, for the one variable where an empty value means something.
|
|
38
|
+
const config = loadConfig(hostname(), ConfigProvider.fromEnv({ preserveEmptyStrings: true }), osUserName());
|
|
24
39
|
const serve = Command.make("serve", {}, Effect.fn(function* () {
|
|
25
40
|
const stdio = yield* Stdio.Stdio;
|
|
26
41
|
const settings = yield* config;
|
|
@@ -29,21 +44,12 @@ const serve = Command.make("serve", {}, Effect.fn(function* () {
|
|
|
29
44
|
const security = yield* makeOwnerSessionSecrets(ownerSessionOrigin("127.0.0.1", port), Option.getOrUndefined(configuredOrigin));
|
|
30
45
|
const ready = yield* Deferred.make();
|
|
31
46
|
const server = yield* Layer.launch(makeServer({ config: settings, port, ready, security })).pipe(Effect.forkChild({ startImmediately: true }));
|
|
32
|
-
|
|
47
|
+
// The server only ends this race by failing; until it is listening there is no link to print.
|
|
48
|
+
const url = yield* Effect.raceFirst(Deferred.await(ready), Fiber.join(server).pipe(Effect.andThen(Effect.never)));
|
|
33
49
|
// On stdout and nowhere else: this line is the credential, so it is never logged.
|
|
34
|
-
yield* Stream.make(`agent usage: ${
|
|
50
|
+
yield* Stream.make(`agent usage: ${url}\n`).pipe(Stream.run(stdio.stdout()));
|
|
35
51
|
return yield* Fiber.join(server);
|
|
36
52
|
})).pipe(Command.withDescription("Record usage continuously and serve the browser view"));
|
|
37
|
-
const describeSource = (name, status) => {
|
|
38
|
-
if (status.rootMissing)
|
|
39
|
-
return `${name}: no sessions directory`;
|
|
40
|
-
const skipped = status.skipped.unparseableLine + status.skipped.missingTimestamp + status.skipped.oversizedLine;
|
|
41
|
-
return [
|
|
42
|
-
`${name}: ${status.filesRead}/${status.filesScanned} files read, ${status.eventsAdded} events added`,
|
|
43
|
-
skipped > 0 ? `, ${skipped} lines skipped` : "",
|
|
44
|
-
status.unreadable.length > 0 ? `, ${status.unreadable.length} unreadable` : ""
|
|
45
|
-
].join("");
|
|
46
|
-
};
|
|
47
53
|
const encodeStatus = Schema.encodeSync(Schema.fromJsonString(IngestStatusSchema));
|
|
48
54
|
const ingest = Command.make("ingest", {
|
|
49
55
|
json: Flag.Boolean("json").pipe(Flag.withDescription("Print the status as one JSON value"), Flag.withDefault(false))
|
|
@@ -55,11 +61,15 @@ const ingest = Command.make("ingest", {
|
|
|
55
61
|
Effect.provide(databaseLayer(settings.storeDirectory)));
|
|
56
62
|
if (json)
|
|
57
63
|
return yield* Console.log(encodeStatus(status));
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
yield* Console.log(`took ${status.finishedAt - status.startedAt}ms`);
|
|
64
|
+
for (const line of describeIngest(status))
|
|
65
|
+
yield* Console.log(line);
|
|
61
66
|
})).pipe(Command.withDescription("Run one ingest pass and report what it found"));
|
|
62
|
-
const
|
|
67
|
+
const login = Command.make("login", {
|
|
68
|
+
open: Flag.Boolean("open").pipe(Flag.withDescription("Open the link in the browser too"), Flag.withDefault(false))
|
|
69
|
+
},
|
|
70
|
+
// The user id `login` trusts the socket to belong to; -1 (no POSIX ids) matches no owner.
|
|
71
|
+
({ open }) => requestLogin(config, open, platform(), process.geteuid?.() ?? -1)).pipe(Command.withDescription("Print a fresh one-time link to the running server"));
|
|
72
|
+
const cli = Command.make("agent-usage").pipe(Command.withDescription("Claude and Codex subscription usage over time, per ticket and against limits"), Command.withSubcommands([serve, login, ingest]), Command.run({ version: pkg.version }));
|
|
63
73
|
// Executable entry point: the host platform is provided once for this process.
|
|
64
74
|
NodeRuntime.runMain(cli.pipe(Effect.provide(NodeServices.layer)));
|
|
65
75
|
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
|
package/dist/server/Api.d.ts
CHANGED
|
@@ -30,14 +30,15 @@ declare const UsageApiGroup_base: HttpApiGroup.HttpApiGroup<"usage", HttpApiEndp
|
|
|
30
30
|
readonly usedPercent: import("effect/Schema").Finite;
|
|
31
31
|
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
32
32
|
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
33
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
33
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
34
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
34
35
|
}>]>;
|
|
35
36
|
}>>;
|
|
36
37
|
}>>;
|
|
37
38
|
readonly latest: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
38
39
|
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
39
40
|
readonly machine: import("effect/Schema").NonEmptyString;
|
|
40
|
-
readonly source: import("effect/Schema").Literals<readonly ["claude-oauth-usage", "codex-rollout"]>;
|
|
41
|
+
readonly source: import("effect/Schema").Literals<readonly ["claude-oauth-usage", "claude-statusline", "codex-rollout"]>;
|
|
41
42
|
readonly label: import("effect/Schema").NonEmptyString;
|
|
42
43
|
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
43
44
|
readonly observedAt: import("effect/Schema").Int;
|
|
@@ -45,7 +46,8 @@ declare const UsageApiGroup_base: HttpApiGroup.HttpApiGroup<"usage", HttpApiEndp
|
|
|
45
46
|
readonly usedPercent: import("effect/Schema").Finite;
|
|
46
47
|
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
47
48
|
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
48
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
49
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
50
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
49
51
|
}>]>;
|
|
50
52
|
}>>;
|
|
51
53
|
readonly balances: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
@@ -62,7 +64,8 @@ declare const UsageApiGroup_base: HttpApiGroup.HttpApiGroup<"usage", HttpApiEndp
|
|
|
62
64
|
readonly credits: import("effect/Schema").Finite;
|
|
63
65
|
}>, import("effect/Schema").TaggedStruct<"Unlimited", {}>, import("effect/Schema").TaggedStruct<"Exhausted", {}>, import("effect/Schema").TaggedStruct<"Disabled", {}>]>;
|
|
64
66
|
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
65
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
67
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
68
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
66
69
|
}>]>;
|
|
67
70
|
}>>;
|
|
68
71
|
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"status", "GET", "/api/status", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
@@ -100,6 +103,21 @@ declare const UsageApiGroup_base: HttpApiGroup.HttpApiGroup<"usage", HttpApiEndp
|
|
|
100
103
|
readonly reason: import("effect/Schema").String;
|
|
101
104
|
}>>;
|
|
102
105
|
}>;
|
|
106
|
+
readonly claudeLimitSamples: import("effect/Schema").Struct<{
|
|
107
|
+
readonly rootMissing: import("effect/Schema").Boolean;
|
|
108
|
+
readonly filesScanned: import("effect/Schema").Int;
|
|
109
|
+
readonly filesRead: import("effect/Schema").Int;
|
|
110
|
+
readonly eventsAdded: import("effect/Schema").Int;
|
|
111
|
+
readonly skipped: import("effect/Schema").Struct<{
|
|
112
|
+
readonly unparseableLine: import("effect/Schema").Int;
|
|
113
|
+
readonly missingTimestamp: import("effect/Schema").Int;
|
|
114
|
+
readonly oversizedLine: import("effect/Schema").Int;
|
|
115
|
+
}>;
|
|
116
|
+
readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
117
|
+
readonly fileKey: import("effect/Schema").String;
|
|
118
|
+
readonly reason: import("effect/Schema").String;
|
|
119
|
+
}>>;
|
|
120
|
+
}>;
|
|
103
121
|
}>>;
|
|
104
122
|
readonly ingestFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
105
123
|
readonly limitsFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
@@ -159,7 +177,113 @@ declare const UsageApiGroup_base: HttpApiGroup.HttpApiGroup<"usage", HttpApiEndp
|
|
|
159
177
|
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, never, never>, false>;
|
|
160
178
|
export declare class UsageApiGroup extends UsageApiGroup_base {
|
|
161
179
|
}
|
|
162
|
-
declare const AgentUsageApi_base: HttpApi.HttpApi<"AgentUsageApi", HttpApiGroup.HttpApiGroup<"usage", HttpApiEndpoint.HttpApiEndpoint<"
|
|
180
|
+
declare const AgentUsageApi_base: HttpApi.HttpApi<"AgentUsageApi", HttpApiGroup.HttpApiGroup<"usage", HttpApiEndpoint.HttpApiEndpoint<"status", "GET", "/api/status", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
181
|
+
readonly machine: import("effect/Schema").String;
|
|
182
|
+
readonly ingest: import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
183
|
+
readonly startedAt: import("effect/Schema").Int;
|
|
184
|
+
readonly finishedAt: import("effect/Schema").Int;
|
|
185
|
+
readonly claude: import("effect/Schema").Struct<{
|
|
186
|
+
readonly rootMissing: import("effect/Schema").Boolean;
|
|
187
|
+
readonly filesScanned: import("effect/Schema").Int;
|
|
188
|
+
readonly filesRead: import("effect/Schema").Int;
|
|
189
|
+
readonly eventsAdded: import("effect/Schema").Int;
|
|
190
|
+
readonly skipped: import("effect/Schema").Struct<{
|
|
191
|
+
readonly unparseableLine: import("effect/Schema").Int;
|
|
192
|
+
readonly missingTimestamp: import("effect/Schema").Int;
|
|
193
|
+
readonly oversizedLine: import("effect/Schema").Int;
|
|
194
|
+
}>;
|
|
195
|
+
readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
196
|
+
readonly fileKey: import("effect/Schema").String;
|
|
197
|
+
readonly reason: import("effect/Schema").String;
|
|
198
|
+
}>>;
|
|
199
|
+
}>;
|
|
200
|
+
readonly codex: import("effect/Schema").Struct<{
|
|
201
|
+
readonly rootMissing: import("effect/Schema").Boolean;
|
|
202
|
+
readonly filesScanned: import("effect/Schema").Int;
|
|
203
|
+
readonly filesRead: import("effect/Schema").Int;
|
|
204
|
+
readonly eventsAdded: import("effect/Schema").Int;
|
|
205
|
+
readonly skipped: import("effect/Schema").Struct<{
|
|
206
|
+
readonly unparseableLine: import("effect/Schema").Int;
|
|
207
|
+
readonly missingTimestamp: import("effect/Schema").Int;
|
|
208
|
+
readonly oversizedLine: import("effect/Schema").Int;
|
|
209
|
+
}>;
|
|
210
|
+
readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
211
|
+
readonly fileKey: import("effect/Schema").String;
|
|
212
|
+
readonly reason: import("effect/Schema").String;
|
|
213
|
+
}>>;
|
|
214
|
+
}>;
|
|
215
|
+
readonly claudeLimitSamples: import("effect/Schema").Struct<{
|
|
216
|
+
readonly rootMissing: import("effect/Schema").Boolean;
|
|
217
|
+
readonly filesScanned: import("effect/Schema").Int;
|
|
218
|
+
readonly filesRead: import("effect/Schema").Int;
|
|
219
|
+
readonly eventsAdded: import("effect/Schema").Int;
|
|
220
|
+
readonly skipped: import("effect/Schema").Struct<{
|
|
221
|
+
readonly unparseableLine: import("effect/Schema").Int;
|
|
222
|
+
readonly missingTimestamp: import("effect/Schema").Int;
|
|
223
|
+
readonly oversizedLine: import("effect/Schema").Int;
|
|
224
|
+
}>;
|
|
225
|
+
readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
226
|
+
readonly fileKey: import("effect/Schema").String;
|
|
227
|
+
readonly reason: import("effect/Schema").String;
|
|
228
|
+
}>>;
|
|
229
|
+
}>;
|
|
230
|
+
}>>;
|
|
231
|
+
readonly ingestFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
232
|
+
readonly limitsFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
233
|
+
readonly ticketLookupFailures: import("effect/Schema").$Array<import("effect/Schema").String>;
|
|
234
|
+
}>>, never, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"limits", "GET", "/api/limits", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
235
|
+
readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
236
|
+
readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
237
|
+
}>>, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
238
|
+
readonly series: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
239
|
+
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
240
|
+
readonly label: import("effect/Schema").String;
|
|
241
|
+
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
242
|
+
readonly points: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
243
|
+
readonly at: import("effect/Schema").Int;
|
|
244
|
+
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
245
|
+
readonly usedPercent: import("effect/Schema").Finite;
|
|
246
|
+
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
247
|
+
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
248
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
249
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
250
|
+
}>]>;
|
|
251
|
+
}>>;
|
|
252
|
+
}>>;
|
|
253
|
+
readonly latest: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
254
|
+
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
255
|
+
readonly machine: import("effect/Schema").NonEmptyString;
|
|
256
|
+
readonly source: import("effect/Schema").Literals<readonly ["claude-oauth-usage", "claude-statusline", "codex-rollout"]>;
|
|
257
|
+
readonly label: import("effect/Schema").NonEmptyString;
|
|
258
|
+
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
259
|
+
readonly observedAt: import("effect/Schema").Int;
|
|
260
|
+
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
261
|
+
readonly usedPercent: import("effect/Schema").Finite;
|
|
262
|
+
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
263
|
+
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
264
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
265
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
266
|
+
}>]>;
|
|
267
|
+
}>>;
|
|
268
|
+
readonly balances: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
269
|
+
readonly kind: import("effect/Schema").Literals<readonly ["claude-extra-usage", "codex-credits"]>;
|
|
270
|
+
readonly machine: import("effect/Schema").NonEmptyString;
|
|
271
|
+
readonly observedAt: import("effect/Schema").Int;
|
|
272
|
+
readonly value: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
273
|
+
readonly balance: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Amount", {
|
|
274
|
+
readonly leftMinor: import("effect/Schema").Finite;
|
|
275
|
+
readonly limitMinor: import("effect/Schema").Finite;
|
|
276
|
+
readonly decimals: import("effect/Schema").Int;
|
|
277
|
+
readonly currency: import("effect/Schema").String;
|
|
278
|
+
}>, import("effect/Schema").TaggedStruct<"Credits", {
|
|
279
|
+
readonly credits: import("effect/Schema").Finite;
|
|
280
|
+
}>, import("effect/Schema").TaggedStruct<"Unlimited", {}>, import("effect/Schema").TaggedStruct<"Exhausted", {}>, import("effect/Schema").TaggedStruct<"Disabled", {}>]>;
|
|
281
|
+
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
282
|
+
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "KeychainDenied", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
283
|
+
readonly detail: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
284
|
+
}>]>;
|
|
285
|
+
}>>;
|
|
286
|
+
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"usage", "GET", "/api/usage", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
|
|
163
287
|
readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
164
288
|
readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
165
289
|
readonly timeZone: import("effect/Schema").String;
|
|
@@ -211,94 +335,6 @@ declare const AgentUsageApi_base: HttpApi.HttpApi<"AgentUsageApi", HttpApiGroup.
|
|
|
211
335
|
readonly prefix: import("effect/Schema").String;
|
|
212
336
|
readonly requests: import("effect/Schema").Int;
|
|
213
337
|
}>>;
|
|
214
|
-
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, OwnerSessionAuth, never
|
|
215
|
-
readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
216
|
-
readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
|
|
217
|
-
}>>, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
218
|
-
readonly series: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
219
|
-
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
220
|
-
readonly label: import("effect/Schema").String;
|
|
221
|
-
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
222
|
-
readonly points: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
223
|
-
readonly at: import("effect/Schema").Int;
|
|
224
|
-
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
225
|
-
readonly usedPercent: import("effect/Schema").Finite;
|
|
226
|
-
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
227
|
-
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
228
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
229
|
-
}>]>;
|
|
230
|
-
}>>;
|
|
231
|
-
}>>;
|
|
232
|
-
readonly latest: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
233
|
-
readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
|
|
234
|
-
readonly machine: import("effect/Schema").NonEmptyString;
|
|
235
|
-
readonly source: import("effect/Schema").Literals<readonly ["claude-oauth-usage", "codex-rollout"]>;
|
|
236
|
-
readonly label: import("effect/Schema").NonEmptyString;
|
|
237
|
-
readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
238
|
-
readonly observedAt: import("effect/Schema").Int;
|
|
239
|
-
readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
240
|
-
readonly usedPercent: import("effect/Schema").Finite;
|
|
241
|
-
readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
|
|
242
|
-
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
243
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
244
|
-
}>]>;
|
|
245
|
-
}>>;
|
|
246
|
-
readonly balances: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
247
|
-
readonly kind: import("effect/Schema").Literals<readonly ["claude-extra-usage", "codex-credits"]>;
|
|
248
|
-
readonly machine: import("effect/Schema").NonEmptyString;
|
|
249
|
-
readonly observedAt: import("effect/Schema").Int;
|
|
250
|
-
readonly value: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
|
|
251
|
-
readonly balance: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Amount", {
|
|
252
|
-
readonly leftMinor: import("effect/Schema").Finite;
|
|
253
|
-
readonly limitMinor: import("effect/Schema").Finite;
|
|
254
|
-
readonly decimals: import("effect/Schema").Int;
|
|
255
|
-
readonly currency: import("effect/Schema").String;
|
|
256
|
-
}>, import("effect/Schema").TaggedStruct<"Credits", {
|
|
257
|
-
readonly credits: import("effect/Schema").Finite;
|
|
258
|
-
}>, import("effect/Schema").TaggedStruct<"Unlimited", {}>, import("effect/Schema").TaggedStruct<"Exhausted", {}>, import("effect/Schema").TaggedStruct<"Disabled", {}>]>;
|
|
259
|
-
}>, import("effect/Schema").TaggedStruct<"Unknown", {
|
|
260
|
-
readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
|
|
261
|
-
}>]>;
|
|
262
|
-
}>>;
|
|
263
|
-
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"status", "GET", "/api/status", never, never, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
|
|
264
|
-
readonly machine: import("effect/Schema").String;
|
|
265
|
-
readonly ingest: import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
266
|
-
readonly startedAt: import("effect/Schema").Int;
|
|
267
|
-
readonly finishedAt: import("effect/Schema").Int;
|
|
268
|
-
readonly claude: import("effect/Schema").Struct<{
|
|
269
|
-
readonly rootMissing: import("effect/Schema").Boolean;
|
|
270
|
-
readonly filesScanned: import("effect/Schema").Int;
|
|
271
|
-
readonly filesRead: import("effect/Schema").Int;
|
|
272
|
-
readonly eventsAdded: import("effect/Schema").Int;
|
|
273
|
-
readonly skipped: import("effect/Schema").Struct<{
|
|
274
|
-
readonly unparseableLine: import("effect/Schema").Int;
|
|
275
|
-
readonly missingTimestamp: import("effect/Schema").Int;
|
|
276
|
-
readonly oversizedLine: import("effect/Schema").Int;
|
|
277
|
-
}>;
|
|
278
|
-
readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
279
|
-
readonly fileKey: import("effect/Schema").String;
|
|
280
|
-
readonly reason: import("effect/Schema").String;
|
|
281
|
-
}>>;
|
|
282
|
-
}>;
|
|
283
|
-
readonly codex: import("effect/Schema").Struct<{
|
|
284
|
-
readonly rootMissing: import("effect/Schema").Boolean;
|
|
285
|
-
readonly filesScanned: import("effect/Schema").Int;
|
|
286
|
-
readonly filesRead: import("effect/Schema").Int;
|
|
287
|
-
readonly eventsAdded: import("effect/Schema").Int;
|
|
288
|
-
readonly skipped: import("effect/Schema").Struct<{
|
|
289
|
-
readonly unparseableLine: import("effect/Schema").Int;
|
|
290
|
-
readonly missingTimestamp: import("effect/Schema").Int;
|
|
291
|
-
readonly oversizedLine: import("effect/Schema").Int;
|
|
292
|
-
}>;
|
|
293
|
-
readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
294
|
-
readonly fileKey: import("effect/Schema").String;
|
|
295
|
-
readonly reason: import("effect/Schema").String;
|
|
296
|
-
}>>;
|
|
297
|
-
}>;
|
|
298
|
-
}>>;
|
|
299
|
-
readonly ingestFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
300
|
-
readonly limitsFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
301
|
-
readonly ticketLookupFailures: import("effect/Schema").$Array<import("effect/Schema").String>;
|
|
302
|
-
}>>, never, OwnerSessionAuth, never>, false>>;
|
|
338
|
+
}>>, import("effect/Schema").toCodecJson<typeof ApiError>, OwnerSessionAuth, never>, false>>;
|
|
303
339
|
export declare class AgentUsageApi extends AgentUsageApi_base {
|
|
304
340
|
}
|
package/dist/server/Config.d.ts
CHANGED
|
@@ -4,18 +4,32 @@
|
|
|
4
4
|
* - `AGENT_USAGE_HOME` — the store directory (default `~/.local/share/agent-usage`)
|
|
5
5
|
* - `AGENT_USAGE_MACHINE` — the Machine name (default: the short, lower-cased hostname)
|
|
6
6
|
* - `CLAUDE_CONFIG_DIR` / `CODEX_HOME` — the agents' own roots (default `~/.claude`, `~/.codex`)
|
|
7
|
+
* - `CLAUDE_SECURESTORAGE_CONFIG_DIR` — where Claude Code keeps its credentials when it differs from
|
|
8
|
+
* the config directory; with `CLAUDE_CONFIG_DIR` it decides the macOS Keychain item's name
|
|
9
|
+
* - `AGENT_USAGE_CLAUDE_LIMITS` — the Claude limit samples claude-statusline appends (default
|
|
10
|
+
* `${XDG_STATE_HOME:-~/.local/state}/agent-usage/claude-limits.jsonl`)
|
|
7
11
|
* - `AGENT_USAGE_PROJECTS` — extra Known Projects, comma-separated (`RPS,ABC`), for projects typed in
|
|
8
12
|
* sessions before any branch named them
|
|
9
13
|
*
|
|
10
14
|
* @module
|
|
11
15
|
*/
|
|
12
|
-
import { Config, Effect, Path } from "effect";
|
|
16
|
+
import { Config, ConfigProvider, Crypto, Effect, Path } from "effect";
|
|
13
17
|
import type { SourceRoots } from "../core/Ingest.js";
|
|
18
|
+
/** Where Claude Code keeps the OAuth credentials a limit poll reads. */
|
|
19
|
+
export interface ClaudeCredentialsLocation {
|
|
20
|
+
/** Linux: `<secure storage dir>/.credentials.json`. */
|
|
21
|
+
readonly file: string;
|
|
22
|
+
/** macOS: the login Keychain item's service name… */
|
|
23
|
+
readonly keychainService: string;
|
|
24
|
+
/** …and its account, the user name. */
|
|
25
|
+
readonly keychainAccount: string;
|
|
26
|
+
}
|
|
14
27
|
export interface AgentUsageConfig {
|
|
15
28
|
readonly storeDirectory: string;
|
|
16
29
|
/** Known Projects from configuration, added to those branches and paths name. */
|
|
17
30
|
readonly projects: ReadonlyArray<string>;
|
|
18
31
|
readonly claudeConfigDir: string;
|
|
32
|
+
readonly claudeCredentials: ClaudeCredentialsLocation;
|
|
19
33
|
readonly roots: SourceRoots;
|
|
20
34
|
}
|
|
21
35
|
/**
|
|
@@ -23,5 +37,10 @@ export interface AgentUsageConfig {
|
|
|
23
37
|
* `Example-MacBook.local` and `Example-MacBook` name the same Machine.
|
|
24
38
|
*/
|
|
25
39
|
export declare const machineName: (hostname: string) => string;
|
|
26
|
-
/**
|
|
27
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Reads the configuration; `hostname` comes from the executable, the only place that may ask.
|
|
42
|
+
* `exactEnvironment` is the environment with empty values kept, which the executable builds; it
|
|
43
|
+
* is read only where an empty value means something (`CLAUDE_SECURESTORAGE_CONFIG_DIR`).
|
|
44
|
+
* `osUser` is the operating system's name for the user, the Keychain account when `USER` is unset.
|
|
45
|
+
*/
|
|
46
|
+
export declare const loadConfig: (hostname: string, exactEnvironment?: ConfigProvider.ConfigProvider, osUser?: string) => Effect.Effect<AgentUsageConfig, Config.ConfigError | import("effect/PlatformError").PlatformError, Crypto.Crypto | Path.Path>;
|
package/dist/server/Config.js
CHANGED
|
@@ -4,12 +4,49 @@
|
|
|
4
4
|
* - `AGENT_USAGE_HOME` — the store directory (default `~/.local/share/agent-usage`)
|
|
5
5
|
* - `AGENT_USAGE_MACHINE` — the Machine name (default: the short, lower-cased hostname)
|
|
6
6
|
* - `CLAUDE_CONFIG_DIR` / `CODEX_HOME` — the agents' own roots (default `~/.claude`, `~/.codex`)
|
|
7
|
+
* - `CLAUDE_SECURESTORAGE_CONFIG_DIR` — where Claude Code keeps its credentials when it differs from
|
|
8
|
+
* the config directory; with `CLAUDE_CONFIG_DIR` it decides the macOS Keychain item's name
|
|
9
|
+
* - `AGENT_USAGE_CLAUDE_LIMITS` — the Claude limit samples claude-statusline appends (default
|
|
10
|
+
* `${XDG_STATE_HOME:-~/.local/state}/agent-usage/claude-limits.jsonl`)
|
|
7
11
|
* - `AGENT_USAGE_PROJECTS` — extra Known Projects, comma-separated (`RPS,ABC`), for projects typed in
|
|
8
12
|
* sessions before any branch named them
|
|
9
13
|
*
|
|
10
14
|
* @module
|
|
11
15
|
*/
|
|
12
|
-
import { Config, Effect, Path, Schema, SchemaTransformation } from "effect";
|
|
16
|
+
import { Config, ConfigProvider, Crypto, Effect, Path, Schema, SchemaTransformation } from "effect";
|
|
17
|
+
import { Hex } from "effect/encoding";
|
|
18
|
+
const KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
19
|
+
/**
|
|
20
|
+
* Where Claude Code (2.1.289) keeps its credentials, named the way it names them. The secure-storage
|
|
21
|
+
* directory is `CLAUDE_SECURESTORAGE_CONFIG_DIR` when set (empty meaning the default `~/.claude`),
|
|
22
|
+
* else the config directory. The Keychain item is `Claude Code-credentials`, suffixed with the first
|
|
23
|
+
* eight hex digits of the directory's SHA-256 whenever a non-default directory was chosen, under the
|
|
24
|
+
* user's account. Asking for the bare name with a custom directory reads another account's item.
|
|
25
|
+
*
|
|
26
|
+
* An explicitly empty `CLAUDE_SECURESTORAGE_CONFIG_DIR` means the default directory and the bare
|
|
27
|
+
* item name, as in Claude Code, so it is read from an environment that keeps empty values.
|
|
28
|
+
*/
|
|
29
|
+
const claudeCredentialsLocation = (options) => Effect.gen(function* () {
|
|
30
|
+
const path = yield* Path.Path;
|
|
31
|
+
const digests = yield* Crypto.Crypto;
|
|
32
|
+
const unsuffixed = options.secureStorageDir === undefined
|
|
33
|
+
? !options.configDirChosen
|
|
34
|
+
: options.secureStorageDir === "";
|
|
35
|
+
const directory = (options.secureStorageDir === undefined
|
|
36
|
+
? options.claudeConfigDir
|
|
37
|
+
: options.secureStorageDir === ""
|
|
38
|
+
? path.join(options.home, ".claude")
|
|
39
|
+
: options.secureStorageDir).normalize("NFC");
|
|
40
|
+
const suffix = unsuffixed
|
|
41
|
+
? ""
|
|
42
|
+
: `-${Hex.encode(yield* digests.digest("SHA-256", new TextEncoder().encode(directory))).slice(0, 8)}`;
|
|
43
|
+
const location = {
|
|
44
|
+
file: path.join(directory, ".credentials.json"),
|
|
45
|
+
keychainService: `${KEYCHAIN_SERVICE}${suffix}`,
|
|
46
|
+
keychainAccount: options.user
|
|
47
|
+
};
|
|
48
|
+
return location;
|
|
49
|
+
});
|
|
13
50
|
/**
|
|
14
51
|
* The Machine name a hostname gives: the part before the first dot, lower-cased, so macOS's
|
|
15
52
|
* `Example-MacBook.local` and `Example-MacBook` name the same Machine.
|
|
@@ -25,22 +62,47 @@ const Projects = Schema.String.pipe(Schema.decodeTo(Schema.Array(ProjectKey), Sc
|
|
|
25
62
|
decode: (raw) => raw.split(",").map((entry) => entry.trim()).filter((entry) => entry !== ""),
|
|
26
63
|
encode: (keys) => keys.join(",")
|
|
27
64
|
})));
|
|
28
|
-
/**
|
|
29
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Reads the configuration; `hostname` comes from the executable, the only place that may ask.
|
|
67
|
+
* `exactEnvironment` is the environment with empty values kept, which the executable builds; it
|
|
68
|
+
* is read only where an empty value means something (`CLAUDE_SECURESTORAGE_CONFIG_DIR`).
|
|
69
|
+
* `osUser` is the operating system's name for the user, the Keychain account when `USER` is unset.
|
|
70
|
+
*/
|
|
71
|
+
export const loadConfig = (hostname, exactEnvironment, osUser = "claude-code-user") => Effect.gen(function* () {
|
|
30
72
|
const path = yield* Path.Path;
|
|
31
73
|
const home = yield* Config.String("HOME");
|
|
32
74
|
const storeDirectory = yield* Config.String("AGENT_USAGE_HOME").pipe(Config.withDefault(path.join(home, ".local", "share", "agent-usage")));
|
|
33
75
|
const machine = yield* Config.NonEmptyString("AGENT_USAGE_MACHINE").pipe(Config.withDefault(machineName(hostname)));
|
|
34
|
-
const
|
|
76
|
+
const chosenConfigDir = yield* Config.option(Config.String("CLAUDE_CONFIG_DIR"));
|
|
77
|
+
const claudeConfigDir = chosenConfigDir._tag === "Some" ? chosenConfigDir.value : path.join(home, ".claude");
|
|
78
|
+
const secureStorageRead = Config.option(Config.String("CLAUDE_SECURESTORAGE_CONFIG_DIR"));
|
|
79
|
+
const secureStorageDir = yield* (exactEnvironment === undefined
|
|
80
|
+
? secureStorageRead
|
|
81
|
+
: secureStorageRead.pipe(Effect.provideService(ConfigProvider.ConfigProvider, exactEnvironment)));
|
|
82
|
+
// The Keychain account, as Claude Code names it: USER, else the OS user, kept only when it is a
|
|
83
|
+
// plain name.
|
|
84
|
+
const named = yield* Config.String("USER").pipe(Config.withDefault(osUser));
|
|
85
|
+
const user = /^[a-zA-Z0-9._-]+$/u.test(named) ? named : "claude-code-user";
|
|
86
|
+
const claudeCredentials = yield* claudeCredentialsLocation({
|
|
87
|
+
home,
|
|
88
|
+
user,
|
|
89
|
+
claudeConfigDir,
|
|
90
|
+
configDirChosen: chosenConfigDir._tag === "Some",
|
|
91
|
+
secureStorageDir: secureStorageDir._tag === "Some" ? secureStorageDir.value : undefined
|
|
92
|
+
});
|
|
93
|
+
const stateHome = yield* Config.String("XDG_STATE_HOME").pipe(Config.withDefault(path.join(home, ".local", "state")));
|
|
94
|
+
const claudeLimitSamples = yield* Config.String("AGENT_USAGE_CLAUDE_LIMITS").pipe(Config.withDefault(path.join(stateHome, "agent-usage", "claude-limits.jsonl")));
|
|
35
95
|
const codexHome = yield* Config.String("CODEX_HOME").pipe(Config.withDefault(path.join(home, ".codex")));
|
|
36
96
|
const projects = yield* Config.schema(Projects, "AGENT_USAGE_PROJECTS").pipe(Config.withDefault([]));
|
|
37
97
|
const config = {
|
|
38
98
|
storeDirectory,
|
|
39
99
|
projects,
|
|
40
100
|
claudeConfigDir,
|
|
101
|
+
claudeCredentials,
|
|
41
102
|
roots: {
|
|
42
103
|
claudeProjects: path.join(claudeConfigDir, "projects"),
|
|
43
104
|
codexHome,
|
|
105
|
+
claudeLimitSamples,
|
|
44
106
|
machine
|
|
45
107
|
}
|
|
46
108
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Config.js","sourceRoot":"","sources":["../../src/server/Config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Config.js","sourceRoot":"","sources":["../../src/server/Config.ts"],"names":[],"mappings":"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"}
|