@knpkv/agent-usage 0.1.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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/dist/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  4. package/dist/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  5. package/dist/client/assets/index-CUuLXODk.css +1 -0
  6. package/dist/client/assets/index-DB-VXWqy.js +55 -0
  7. package/dist/client/index.html +15 -0
  8. package/dist/core/Attribution.d.ts +59 -0
  9. package/dist/core/Attribution.js +108 -0
  10. package/dist/core/Attribution.js.map +1 -0
  11. package/dist/core/Balances.d.ts +27 -0
  12. package/dist/core/Balances.js +63 -0
  13. package/dist/core/Balances.js.map +1 -0
  14. package/dist/core/ClaudeLimits.d.ts +58 -0
  15. package/dist/core/ClaudeLimits.js +125 -0
  16. package/dist/core/ClaudeLimits.js.map +1 -0
  17. package/dist/core/ClaudeLimitsLive.d.ts +18 -0
  18. package/dist/core/ClaudeLimitsLive.js +36 -0
  19. package/dist/core/ClaudeLimitsLive.js.map +1 -0
  20. package/dist/core/ClaudeReader.d.ts +27 -0
  21. package/dist/core/ClaudeReader.js +154 -0
  22. package/dist/core/ClaudeReader.js.map +1 -0
  23. package/dist/core/CodexReader.d.ts +91 -0
  24. package/dist/core/CodexReader.js +365 -0
  25. package/dist/core/CodexReader.js.map +1 -0
  26. package/dist/core/Database.d.ts +4 -0
  27. package/dist/core/Database.js +65 -0
  28. package/dist/core/Database.js.map +1 -0
  29. package/dist/core/Ingest.d.ts +55 -0
  30. package/dist/core/Ingest.js +267 -0
  31. package/dist/core/Ingest.js.map +1 -0
  32. package/dist/core/Model.d.ts +160 -0
  33. package/dist/core/Model.js +125 -0
  34. package/dist/core/Model.js.map +1 -0
  35. package/dist/core/Pricing.d.ts +42 -0
  36. package/dist/core/Pricing.js +126 -0
  37. package/dist/core/Pricing.js.map +1 -0
  38. package/dist/core/Readers.d.ts +42 -0
  39. package/dist/core/Readers.js +18 -0
  40. package/dist/core/Readers.js.map +1 -0
  41. package/dist/core/Report.d.ts +53 -0
  42. package/dist/core/Report.js +270 -0
  43. package/dist/core/Report.js.map +1 -0
  44. package/dist/core/Store.d.ts +94 -0
  45. package/dist/core/Store.js +376 -0
  46. package/dist/core/Store.js.map +1 -0
  47. package/dist/core/Tickets.d.ts +42 -0
  48. package/dist/core/Tickets.js +125 -0
  49. package/dist/core/Tickets.js.map +1 -0
  50. package/dist/index.d.ts +14 -0
  51. package/dist/index.js +15 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/main.d.ts +2 -0
  54. package/dist/main.js +65 -0
  55. package/dist/main.js.map +1 -0
  56. package/dist/server/Api.d.ts +304 -0
  57. package/dist/server/Api.js +20 -0
  58. package/dist/server/Api.js.map +1 -0
  59. package/dist/server/Config.d.ts +27 -0
  60. package/dist/server/Config.js +49 -0
  61. package/dist/server/Config.js.map +1 -0
  62. package/dist/server/Handlers.d.ts +5 -0
  63. package/dist/server/Handlers.js +48 -0
  64. package/dist/server/Handlers.js.map +1 -0
  65. package/dist/server/HttpApplication.d.ts +7 -0
  66. package/dist/server/HttpApplication.js +70 -0
  67. package/dist/server/HttpApplication.js.map +1 -0
  68. package/dist/server/OwnerSession.d.ts +60 -0
  69. package/dist/server/OwnerSession.js +206 -0
  70. package/dist/server/OwnerSession.js.map +1 -0
  71. package/dist/server/Runtime.d.ts +57 -0
  72. package/dist/server/Runtime.js +85 -0
  73. package/dist/server/Runtime.js.map +1 -0
  74. package/dist/server/Server.d.ts +15 -0
  75. package/dist/server/Server.js +39 -0
  76. package/dist/server/Server.js.map +1 -0
  77. package/dist/shared/contracts.d.ts +309 -0
  78. package/dist/shared/contracts.js +105 -0
  79. package/dist/shared/contracts.js.map +1 -0
  80. package/dist/tsconfig.tsbuildinfo +1 -0
  81. package/package.json +68 -0
package/dist/main.js ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `agent-usage` — record Claude and Codex usage on this Machine and show it in a browser.
4
+ *
5
+ * - `agent-usage serve` ingests every minute, polls Claude limits every five, and prints the URL
6
+ * that gets you in.
7
+ * - `agent-usage ingest [--json]` runs one ingest pass and reports what it found.
8
+ *
9
+ * @module
10
+ */
11
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
12
+ import { Console, Deferred, Effect, Fiber, Layer, Option, Schema } from "effect";
13
+ import { Command, Flag } from "effect/cli";
14
+ import * as Stdio from "effect/Stdio";
15
+ import * as Stream from "effect/Stream";
16
+ import { hostname } from "node:os";
17
+ import { databaseLayer } from "./core/Database.js";
18
+ import { ingestOnce } from "./core/Ingest.js";
19
+ import { loadConfig } from "./server/Config.js";
20
+ import { makeOwnerSessionSecrets, ownerSessionOrigin, ownerSessionUrl } from "./server/OwnerSession.js";
21
+ import { makeServer, Port, PublicOrigin } from "./server/Server.js";
22
+ import { IngestStatus as IngestStatusSchema } from "./shared/contracts.js";
23
+ const config = loadConfig(hostname());
24
+ const serve = Command.make("serve", {}, Effect.fn(function* () {
25
+ const stdio = yield* Stdio.Stdio;
26
+ const settings = yield* config;
27
+ const port = yield* Port;
28
+ const configuredOrigin = yield* PublicOrigin;
29
+ const security = yield* makeOwnerSessionSecrets(ownerSessionOrigin("127.0.0.1", port), Option.getOrUndefined(configuredOrigin));
30
+ const ready = yield* Deferred.make();
31
+ const server = yield* Layer.launch(makeServer({ config: settings, port, ready, security })).pipe(Effect.forkChild({ startImmediately: true }));
32
+ yield* Effect.raceFirst(Deferred.await(ready), Fiber.join(server));
33
+ // On stdout and nowhere else: this line is the credential, so it is never logged.
34
+ yield* Stream.make(`agent usage: ${ownerSessionUrl(security.browserOrigin, security)}\n`).pipe(Stream.run(stdio.stdout()));
35
+ return yield* Fiber.join(server);
36
+ })).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
+ const encodeStatus = Schema.encodeSync(Schema.fromJsonString(IngestStatusSchema));
48
+ const ingest = Command.make("ingest", {
49
+ json: Flag.Boolean("json").pipe(Flag.withDescription("Print the status as one JSON value"), Flag.withDefault(false))
50
+ }, Effect.fn(function* ({ json }) {
51
+ const settings = yield* config;
52
+ const status = yield* ingestOnce(settings.roots).pipe(
53
+ // This subcommand's entry point: the store opens for this one pass and closes with it.
54
+ // @effect-diagnostics-next-line strictEffectProvide:off
55
+ Effect.provide(databaseLayer(settings.storeDirectory)));
56
+ if (json)
57
+ return yield* Console.log(encodeStatus(status));
58
+ yield* Console.log(describeSource("claude", status.claude));
59
+ yield* Console.log(describeSource("codex", status.codex));
60
+ yield* Console.log(`took ${status.finishedAt - status.startedAt}ms`);
61
+ })).pipe(Command.withDescription("Run one ingest pass and report what it found"));
62
+ const cli = Command.make("agent-usage").pipe(Command.withDescription("Claude and Codex subscription usage over time, per ticket and against limits"), Command.withSubcommands([serve, ingest]), Command.run({ version: "0.1.0" }));
63
+ // Executable entry point: the host platform is provided once for this process.
64
+ NodeRuntime.runMain(cli.pipe(Effect.provide(NodeServices.layer)));
65
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,304 @@
1
+ /** Authenticated HTTP routes. Every route reads; payload schemas live in shared/contracts. */
2
+ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSecurity } from "effect/http-api";
3
+ import { ApiError, ForbiddenApiError, UnauthorizedApiError } from "../shared/contracts.js";
4
+ export * from "../shared/contracts.js";
5
+ declare const OwnerSessionAuth_base: HttpApiMiddleware.ServiceClass<OwnerSessionAuth, "@knpkv/agent-usage/OwnerSessionAuth", {
6
+ requires: never;
7
+ provides: never;
8
+ error: readonly [typeof UnauthorizedApiError, typeof ForbiddenApiError];
9
+ clientError: never;
10
+ requiredForClient: false;
11
+ security: {
12
+ readonly ownerCookie: HttpApiSecurity.ApiKey;
13
+ };
14
+ }, HttpApiMiddleware.HttpApiMiddlewareSecurity<{
15
+ readonly ownerCookie: HttpApiSecurity.ApiKey;
16
+ }, never, readonly [typeof UnauthorizedApiError, typeof ForbiddenApiError], never>>;
17
+ export declare class OwnerSessionAuth extends OwnerSessionAuth_base {
18
+ }
19
+ declare const UsageApiGroup_base: HttpApiGroup.HttpApiGroup<"usage", HttpApiEndpoint.HttpApiEndpoint<"limits", "GET", "/api/limits", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
20
+ readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
21
+ readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
22
+ }>>, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
23
+ readonly series: import("effect/Schema").$Array<import("effect/Schema").Struct<{
24
+ readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
25
+ readonly label: import("effect/Schema").String;
26
+ readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
27
+ readonly points: import("effect/Schema").$Array<import("effect/Schema").Struct<{
28
+ readonly at: import("effect/Schema").Int;
29
+ readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
30
+ readonly usedPercent: import("effect/Schema").Finite;
31
+ readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
32
+ }>, import("effect/Schema").TaggedStruct<"Unknown", {
33
+ readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
34
+ }>]>;
35
+ }>>;
36
+ }>>;
37
+ readonly latest: import("effect/Schema").$Array<import("effect/Schema").Struct<{
38
+ readonly agent: import("effect/Schema").Literals<readonly ["claude", "codex"]>;
39
+ readonly machine: import("effect/Schema").NonEmptyString;
40
+ readonly source: import("effect/Schema").Literals<readonly ["claude-oauth-usage", "codex-rollout"]>;
41
+ readonly label: import("effect/Schema").NonEmptyString;
42
+ readonly windowMinutes: import("effect/Schema").NullOr<import("effect/Schema").Int>;
43
+ readonly observedAt: import("effect/Schema").Int;
44
+ readonly reading: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
45
+ readonly usedPercent: import("effect/Schema").Finite;
46
+ readonly resetsAt: import("effect/Schema").NullOr<import("effect/Schema").Int>;
47
+ }>, import("effect/Schema").TaggedStruct<"Unknown", {
48
+ readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
49
+ }>]>;
50
+ }>>;
51
+ readonly balances: import("effect/Schema").$Array<import("effect/Schema").Struct<{
52
+ readonly kind: import("effect/Schema").Literals<readonly ["claude-extra-usage", "codex-credits"]>;
53
+ readonly machine: import("effect/Schema").NonEmptyString;
54
+ readonly observedAt: import("effect/Schema").Int;
55
+ readonly value: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
56
+ readonly balance: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Amount", {
57
+ readonly leftMinor: import("effect/Schema").Finite;
58
+ readonly limitMinor: import("effect/Schema").Finite;
59
+ readonly decimals: import("effect/Schema").Int;
60
+ readonly currency: import("effect/Schema").String;
61
+ }>, import("effect/Schema").TaggedStruct<"Credits", {
62
+ readonly credits: import("effect/Schema").Finite;
63
+ }>, import("effect/Schema").TaggedStruct<"Unlimited", {}>, import("effect/Schema").TaggedStruct<"Exhausted", {}>, import("effect/Schema").TaggedStruct<"Disabled", {}>]>;
64
+ }>, import("effect/Schema").TaggedStruct<"Unknown", {
65
+ readonly reason: import("effect/Schema").Literals<readonly ["NoAuth", "AuthExpired", "NotSupported", "Fetch", "Parse", "NoData"]>;
66
+ }>]>;
67
+ }>>;
68
+ }>>, 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<{
69
+ readonly machine: import("effect/Schema").String;
70
+ readonly ingest: import("effect/Schema").NullOr<import("effect/Schema").Struct<{
71
+ readonly startedAt: import("effect/Schema").Int;
72
+ readonly finishedAt: import("effect/Schema").Int;
73
+ readonly claude: import("effect/Schema").Struct<{
74
+ readonly rootMissing: import("effect/Schema").Boolean;
75
+ readonly filesScanned: import("effect/Schema").Int;
76
+ readonly filesRead: import("effect/Schema").Int;
77
+ readonly eventsAdded: import("effect/Schema").Int;
78
+ readonly skipped: import("effect/Schema").Struct<{
79
+ readonly unparseableLine: import("effect/Schema").Int;
80
+ readonly missingTimestamp: import("effect/Schema").Int;
81
+ readonly oversizedLine: import("effect/Schema").Int;
82
+ }>;
83
+ readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
84
+ readonly fileKey: import("effect/Schema").String;
85
+ readonly reason: import("effect/Schema").String;
86
+ }>>;
87
+ }>;
88
+ readonly codex: import("effect/Schema").Struct<{
89
+ readonly rootMissing: import("effect/Schema").Boolean;
90
+ readonly filesScanned: import("effect/Schema").Int;
91
+ readonly filesRead: import("effect/Schema").Int;
92
+ readonly eventsAdded: import("effect/Schema").Int;
93
+ readonly skipped: import("effect/Schema").Struct<{
94
+ readonly unparseableLine: import("effect/Schema").Int;
95
+ readonly missingTimestamp: import("effect/Schema").Int;
96
+ readonly oversizedLine: import("effect/Schema").Int;
97
+ }>;
98
+ readonly unreadable: import("effect/Schema").$Array<import("effect/Schema").Struct<{
99
+ readonly fileKey: import("effect/Schema").String;
100
+ readonly reason: import("effect/Schema").String;
101
+ }>>;
102
+ }>;
103
+ }>>;
104
+ readonly ingestFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
105
+ readonly limitsFailure: import("effect/Schema").NullOr<import("effect/Schema").String>;
106
+ readonly ticketLookupFailures: import("effect/Schema").$Array<import("effect/Schema").String>;
107
+ }>>, never, never, never> | HttpApiEndpoint.HttpApiEndpoint<"usage", "GET", "/api/usage", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
108
+ readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
109
+ readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
110
+ readonly timeZone: import("effect/Schema").String;
111
+ readonly bucket: import("effect/Schema").Literals<readonly ["hour", "day", "week"]>;
112
+ readonly agent: import("effect/Schema").Literals<readonly ["all", "claude", "codex"]>;
113
+ }>>, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
114
+ readonly periods: import("effect/Schema").$Array<import("effect/Schema").Struct<{
115
+ readonly key: import("effect/Schema").String;
116
+ readonly start: import("effect/Schema").Int;
117
+ }>>;
118
+ readonly bookings: import("effect/Schema").$Array<import("effect/Schema").Struct<{
119
+ readonly id: import("effect/Schema").String;
120
+ readonly booking: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Ticket", {
121
+ readonly key: import("effect/Schema").String;
122
+ }>, import("effect/Schema").TaggedStruct<"Repo", {
123
+ readonly name: import("effect/Schema").String;
124
+ }>]>;
125
+ readonly title: import("effect/Schema").NullOr<import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
126
+ readonly summary: import("effect/Schema").String;
127
+ }>, import("effect/Schema").TaggedStruct<"Unknown", {
128
+ readonly reason: import("effect/Schema").Literals<readonly ["NotFound", "NotLookedUp"]>;
129
+ }>]>>;
130
+ readonly agents: import("effect/Schema").$Array<import("effect/Schema").Literals<readonly ["claude", "codex"]>>;
131
+ readonly requests: import("effect/Schema").Int;
132
+ readonly tokens: import("effect/Schema").Struct<{
133
+ readonly input: import("effect/Schema").Int;
134
+ readonly output: import("effect/Schema").Int;
135
+ readonly reasoning: import("effect/Schema").Int;
136
+ readonly cacheRead: import("effect/Schema").Int;
137
+ readonly cacheWrite5m: import("effect/Schema").Int;
138
+ readonly cacheWrite1h: import("effect/Schema").Int;
139
+ }>;
140
+ readonly costUsd: import("effect/Schema").Finite;
141
+ readonly unpricedTokens: import("effect/Schema").Int;
142
+ readonly unpricedModels: import("effect/Schema").$Array<import("effect/Schema").String>;
143
+ }>>;
144
+ readonly cells: import("effect/Schema").$Array<import("effect/Schema").Struct<{
145
+ readonly period: import("effect/Schema").Int;
146
+ readonly booking: import("effect/Schema").String;
147
+ readonly tokens: import("effect/Schema").Int;
148
+ readonly costUsd: import("effect/Schema").Finite;
149
+ readonly unpricedTokens: import("effect/Schema").Int;
150
+ }>>;
151
+ readonly unpriced: import("effect/Schema").Struct<{
152
+ readonly tokens: import("effect/Schema").Int;
153
+ readonly models: import("effect/Schema").$Array<import("effect/Schema").String>;
154
+ }>;
155
+ readonly ignoredKeys: import("effect/Schema").$Array<import("effect/Schema").Struct<{
156
+ readonly prefix: import("effect/Schema").String;
157
+ readonly requests: import("effect/Schema").Int;
158
+ }>>;
159
+ }>>, import("effect/Schema").toCodecJson<typeof ApiError>, never, never>, false>;
160
+ export declare class UsageApiGroup extends UsageApiGroup_base {
161
+ }
162
+ declare const AgentUsageApi_base: HttpApi.HttpApi<"AgentUsageApi", HttpApiGroup.HttpApiGroup<"usage", HttpApiEndpoint.HttpApiEndpoint<"usage", "GET", "/api/usage", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
163
+ readonly from: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
164
+ readonly to: import("effect/Schema").compose<import("effect/Schema").Int, import("effect/Schema").NumberFromString>;
165
+ readonly timeZone: import("effect/Schema").String;
166
+ readonly bucket: import("effect/Schema").Literals<readonly ["hour", "day", "week"]>;
167
+ readonly agent: import("effect/Schema").Literals<readonly ["all", "claude", "codex"]>;
168
+ }>>, never, never, import("effect/Schema").toCodecJson<import("effect/Schema").Struct<{
169
+ readonly periods: import("effect/Schema").$Array<import("effect/Schema").Struct<{
170
+ readonly key: import("effect/Schema").String;
171
+ readonly start: import("effect/Schema").Int;
172
+ }>>;
173
+ readonly bookings: import("effect/Schema").$Array<import("effect/Schema").Struct<{
174
+ readonly id: import("effect/Schema").String;
175
+ readonly booking: import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Ticket", {
176
+ readonly key: import("effect/Schema").String;
177
+ }>, import("effect/Schema").TaggedStruct<"Repo", {
178
+ readonly name: import("effect/Schema").String;
179
+ }>]>;
180
+ readonly title: import("effect/Schema").NullOr<import("effect/Schema").Union<readonly [import("effect/Schema").TaggedStruct<"Known", {
181
+ readonly summary: import("effect/Schema").String;
182
+ }>, import("effect/Schema").TaggedStruct<"Unknown", {
183
+ readonly reason: import("effect/Schema").Literals<readonly ["NotFound", "NotLookedUp"]>;
184
+ }>]>>;
185
+ readonly agents: import("effect/Schema").$Array<import("effect/Schema").Literals<readonly ["claude", "codex"]>>;
186
+ readonly requests: import("effect/Schema").Int;
187
+ readonly tokens: import("effect/Schema").Struct<{
188
+ readonly input: import("effect/Schema").Int;
189
+ readonly output: import("effect/Schema").Int;
190
+ readonly reasoning: import("effect/Schema").Int;
191
+ readonly cacheRead: import("effect/Schema").Int;
192
+ readonly cacheWrite5m: import("effect/Schema").Int;
193
+ readonly cacheWrite1h: import("effect/Schema").Int;
194
+ }>;
195
+ readonly costUsd: import("effect/Schema").Finite;
196
+ readonly unpricedTokens: import("effect/Schema").Int;
197
+ readonly unpricedModels: import("effect/Schema").$Array<import("effect/Schema").String>;
198
+ }>>;
199
+ readonly cells: import("effect/Schema").$Array<import("effect/Schema").Struct<{
200
+ readonly period: import("effect/Schema").Int;
201
+ readonly booking: import("effect/Schema").String;
202
+ readonly tokens: import("effect/Schema").Int;
203
+ readonly costUsd: import("effect/Schema").Finite;
204
+ readonly unpricedTokens: import("effect/Schema").Int;
205
+ }>>;
206
+ readonly unpriced: import("effect/Schema").Struct<{
207
+ readonly tokens: import("effect/Schema").Int;
208
+ readonly models: import("effect/Schema").$Array<import("effect/Schema").String>;
209
+ }>;
210
+ readonly ignoredKeys: import("effect/Schema").$Array<import("effect/Schema").Struct<{
211
+ readonly prefix: import("effect/Schema").String;
212
+ readonly requests: import("effect/Schema").Int;
213
+ }>>;
214
+ }>>, import("effect/Schema").toCodecJson<typeof ApiError>, OwnerSessionAuth, never> | HttpApiEndpoint.HttpApiEndpoint<"limits", "GET", "/api/limits", never, import("effect/Schema").toCodecStringTree<import("effect/Schema").Struct<{
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>>;
303
+ export declare class AgentUsageApi extends AgentUsageApi_base {
304
+ }
@@ -0,0 +1,20 @@
1
+ /** Authenticated HTTP routes. Every route reads; payload schemas live in shared/contracts. */
2
+ import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSecurity } from "effect/http-api";
3
+ import { ApiError, ForbiddenApiError, LimitsReport, RangeQuery, ServerStatus, UnauthorizedApiError, UsageQuery, UsageReport } from "../shared/contracts.js";
4
+ export * from "../shared/contracts.js";
5
+ export class OwnerSessionAuth extends HttpApiMiddleware.Service()("@knpkv/agent-usage/OwnerSessionAuth", {
6
+ error: [UnauthorizedApiError, ForbiddenApiError],
7
+ security: {
8
+ ownerCookie: HttpApiSecurity.apiKey({ in: "cookie", key: "agent_usage_owner" })
9
+ }
10
+ }) {
11
+ }
12
+ export class UsageApiGroup extends HttpApiGroup.make("usage")
13
+ .add(HttpApiEndpoint.get("usage", "/usage", { query: UsageQuery, success: UsageReport, error: ApiError }))
14
+ .add(HttpApiEndpoint.get("limits", "/limits", { query: RangeQuery, success: LimitsReport, error: ApiError }))
15
+ .add(HttpApiEndpoint.get("status", "/status", { success: ServerStatus }))
16
+ .prefix("/api") {
17
+ }
18
+ export class AgentUsageApi extends HttpApi.make("AgentUsageApi").add(UsageApiGroup).middleware(OwnerSessionAuth) {
19
+ }
20
+ //# sourceMappingURL=Api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Api.js","sourceRoot":"","sources":["../../src/server/Api.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC5G,OAAO,EACL,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAE/B,cAAc,wBAAwB,CAAA;AAEtC,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB,CAAC,OAAO,EAAoB,CACjF,qCAAqC,EACrC;IACE,KAAK,EAAE,CAAC,oBAAoB,EAAE,iBAAiB,CAAC;IAChD,QAAQ,EAAE;QACR,WAAW,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,mBAAmB,EAAE,CAAC;KAChF;CACF,CACF;CAAG;AAEJ,MAAM,OAAO,aAAc,SAAQ,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;KAC1D,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;KACzG,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;KAC5G,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;KACxE,MAAM,CAAC,MAAM,CAAC;CACf;AAEF,MAAM,OAAO,aAAc,SAAQ,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,gBAAgB,CAAC;CAAG"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Where this Machine's store and agent sessions live, from the environment.
3
+ *
4
+ * - `AGENT_USAGE_HOME` — the store directory (default `~/.local/share/agent-usage`)
5
+ * - `AGENT_USAGE_MACHINE` — the Machine name (default: the short, lower-cased hostname)
6
+ * - `CLAUDE_CONFIG_DIR` / `CODEX_HOME` — the agents' own roots (default `~/.claude`, `~/.codex`)
7
+ * - `AGENT_USAGE_PROJECTS` — extra Known Projects, comma-separated (`RPS,ABC`), for projects typed in
8
+ * sessions before any branch named them
9
+ *
10
+ * @module
11
+ */
12
+ import { Config, Effect, Path } from "effect";
13
+ import type { SourceRoots } from "../core/Ingest.js";
14
+ export interface AgentUsageConfig {
15
+ readonly storeDirectory: string;
16
+ /** Known Projects from configuration, added to those branches and paths name. */
17
+ readonly projects: ReadonlyArray<string>;
18
+ readonly claudeConfigDir: string;
19
+ readonly roots: SourceRoots;
20
+ }
21
+ /**
22
+ * The Machine name a hostname gives: the part before the first dot, lower-cased, so macOS's
23
+ * `Example-MacBook.local` and `Example-MacBook` name the same Machine.
24
+ */
25
+ export declare const machineName: (hostname: string) => string;
26
+ /** Reads the configuration; `hostname` comes from the executable, the only place that may ask. */
27
+ export declare const loadConfig: (hostname: string) => Effect.Effect<AgentUsageConfig, Config.ConfigError, Path.Path>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Where this Machine's store and agent sessions live, from the environment.
3
+ *
4
+ * - `AGENT_USAGE_HOME` — the store directory (default `~/.local/share/agent-usage`)
5
+ * - `AGENT_USAGE_MACHINE` — the Machine name (default: the short, lower-cased hostname)
6
+ * - `CLAUDE_CONFIG_DIR` / `CODEX_HOME` — the agents' own roots (default `~/.claude`, `~/.codex`)
7
+ * - `AGENT_USAGE_PROJECTS` — extra Known Projects, comma-separated (`RPS,ABC`), for projects typed in
8
+ * sessions before any branch named them
9
+ *
10
+ * @module
11
+ */
12
+ import { Config, Effect, Path, Schema, SchemaTransformation } from "effect";
13
+ /**
14
+ * The Machine name a hostname gives: the part before the first dot, lower-cased, so macOS's
15
+ * `Example-MacBook.local` and `Example-MacBook` name the same Machine.
16
+ */
17
+ export const machineName = (hostname) => {
18
+ const short = hostname.split(".")[0]?.trim().toLowerCase() ?? "";
19
+ return short === "" ? "localhost" : short;
20
+ };
21
+ /** A Jira project key as the Booking pattern reads one. */
22
+ const ProjectKey = Schema.String.check(Schema.isPattern(/^[A-Z][A-Z0-9]{1,9}$/u));
23
+ /** `RPS, ABC` → `["RPS", "ABC"]`; an entry that is not a project key fails the configuration. */
24
+ const Projects = Schema.String.pipe(Schema.decodeTo(Schema.Array(ProjectKey), SchemaTransformation.transform({
25
+ decode: (raw) => raw.split(",").map((entry) => entry.trim()).filter((entry) => entry !== ""),
26
+ encode: (keys) => keys.join(",")
27
+ })));
28
+ /** Reads the configuration; `hostname` comes from the executable, the only place that may ask. */
29
+ export const loadConfig = (hostname) => Effect.gen(function* () {
30
+ const path = yield* Path.Path;
31
+ const home = yield* Config.String("HOME");
32
+ const storeDirectory = yield* Config.String("AGENT_USAGE_HOME").pipe(Config.withDefault(path.join(home, ".local", "share", "agent-usage")));
33
+ const machine = yield* Config.NonEmptyString("AGENT_USAGE_MACHINE").pipe(Config.withDefault(machineName(hostname)));
34
+ const claudeConfigDir = yield* Config.String("CLAUDE_CONFIG_DIR").pipe(Config.withDefault(path.join(home, ".claude")));
35
+ const codexHome = yield* Config.String("CODEX_HOME").pipe(Config.withDefault(path.join(home, ".codex")));
36
+ const projects = yield* Config.schema(Projects, "AGENT_USAGE_PROJECTS").pipe(Config.withDefault([]));
37
+ const config = {
38
+ storeDirectory,
39
+ projects,
40
+ claudeConfigDir,
41
+ roots: {
42
+ claudeProjects: path.join(claudeConfigDir, "projects"),
43
+ codexHome,
44
+ machine
45
+ }
46
+ };
47
+ return config;
48
+ });
49
+ //# sourceMappingURL=Config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Config.js","sourceRoot":"","sources":["../../src/server/Config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { UsageStore } from "../core/Store.js";
2
+ import { RuntimeState } from "./Runtime.js";
3
+ /** More periods than any chart can draw legibly; a range that needs more wants a coarser bucket. */
4
+ export declare const MAX_PERIODS = 2500;
5
+ export declare const UsageLive: import("effect/Layer").Layer<import("effect/http-api/HttpApiGroup").Service<"AgentUsageApi", "usage">, never, import("./Api.js").OwnerSessionAuth | RuntimeState | UsageStore>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The read handlers: usage per period and Booking, limit series and balances, and status.
3
+ *
4
+ * @module
5
+ */
6
+ import { Effect, SubscriptionRef } from "effect";
7
+ import { HttpApiBuilder } from "effect/http-api";
8
+ import { attribute } from "../core/Attribution.js";
9
+ import { buildLimitsReport, buildUsageReport, checkTimeZone, periodsOf } from "../core/Report.js";
10
+ import { UsageStore } from "../core/Store.js";
11
+ import { ticketTitles } from "../core/Tickets.js";
12
+ import { AgentUsageApi, ApiError } from "./Api.js";
13
+ import { currentKnownProjects, RuntimeState } from "./Runtime.js";
14
+ /** More periods than any chart can draw legibly; a range that needs more wants a coarser bucket. */
15
+ export const MAX_PERIODS = 2_500;
16
+ const storeUnavailable = (error) => new ApiError({ message: `The usage store could not be read (${error.operation})` });
17
+ const checkRange = (from, to) => from < to ? Effect.void : Effect.fail(new ApiError({ message: "The range must end after it starts" }));
18
+ export const UsageLive = HttpApiBuilder.group(AgentUsageApi, "usage", (handlers) => Effect.gen(function* () {
19
+ const store = yield* UsageStore;
20
+ const state = yield* RuntimeState;
21
+ return handlers
22
+ .handle("usage", ({ query }) => Effect.gen(function* () {
23
+ yield* checkRange(query.from, query.to);
24
+ yield* checkTimeZone(query.timeZone).pipe(Effect.mapError((error) => new ApiError({ message: `Unknown time zone: ${error.zone}` })));
25
+ if ((query.to - query.from) / (query.bucket === "hour" ? 3_600_000 : 86_400_000) > MAX_PERIODS) {
26
+ return yield* new ApiError({ message: "Too many periods: choose a shorter range or a larger bucket" });
27
+ }
28
+ const periods = periodsOf({ ...query });
29
+ const groups = (yield* store.usageGroups({ ...query, machine: state.machine })).filter((group) => query.agent === "all" || group.agent === query.agent);
30
+ const projects = yield* currentKnownProjects.pipe(Effect.provideService(UsageStore, store), Effect.provideService(RuntimeState, state));
31
+ const keys = [
32
+ ...new Set(groups.flatMap((group) => {
33
+ const { booking } = attribute(group.attribution, projects);
34
+ return booking._tag === "Ticket" ? [booking.key] : [];
35
+ }))
36
+ ];
37
+ const titles = yield* ticketTitles(keys).pipe(Effect.provideService(UsageStore, store));
38
+ return buildUsageReport(groups, periods, titles, projects, query.to);
39
+ }).pipe(Effect.catchTag("StoreError", (error) => Effect.fail(storeUnavailable(error)))))
40
+ .handle("limits", ({ query }) => Effect.gen(function* () {
41
+ yield* checkRange(query.from, query.to);
42
+ const snapshots = yield* store.limitSnapshots({ from: query.from, to: query.to, machine: state.machine });
43
+ const balances = yield* store.latestBalances(state.machine);
44
+ return { ...buildLimitsReport(snapshots, query), balances };
45
+ }).pipe(Effect.catchTag("StoreError", (error) => Effect.fail(storeUnavailable(error)))))
46
+ .handle("status", () => SubscriptionRef.get(state.status));
47
+ }));
48
+ //# sourceMappingURL=Handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Handlers.js","sourceRoot":"","sources":["../../src/server/Handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /** The same authenticated routes, bootstrap exchange and static client in production and tests. */
2
+ import { FileSystem, Layer, Path } from "effect";
3
+ import { HttpRouter } from "effect/http";
4
+ /** True only when `candidate` is `root` itself or one of its path-segment descendants. */
5
+ export declare const isWithinDirectory: (path: Path.Path, root: string, candidate: string) => boolean;
6
+ /** Supply the store, runtime state and owner secrets; listener and platform belong to the executable. */
7
+ export declare const application: Layer.Layer<never, never, FileSystem.FileSystem | import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | HttpRouter.HttpRouter | import("./OwnerSession.js").OwnerSessionSecrets | Path.Path | HttpRouter.Request<"Error", import("effect/PlatformError").BadArgument> | HttpRouter.Request<"Error", import("effect/http/HttpBody").HttpBodyError> | HttpRouter.Request<"Error", import("effect/PlatformError").PlatformError> | HttpRouter.Request<"Requires", FileSystem.FileSystem> | HttpRouter.Request<"Requires", import("./OwnerSession.js").OwnerSessionSecrets> | HttpRouter.Request<"Requires", Path.Path> | import("./Runtime.js").RuntimeState | import("../core/Store.js").UsageStore>;