claude-use 5.1.1 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -37,6 +37,8 @@ __export(index_exports, {
37
37
  FrontDoorStartError: () => FrontDoorStartError,
38
38
  HeadroomStartError: () => HeadroomStartError,
39
39
  ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
40
+ UsageSnapshotError: () => UsageSnapshotError,
41
+ UsageSnapshotSchema: () => UsageSnapshotSchema,
40
42
  buildEntryFacts: () => buildEntryFacts,
41
43
  carryOver: () => carryOver,
42
44
  createLeafCache: () => createLeafCache,
@@ -49,9 +51,11 @@ __export(index_exports, {
49
51
  forwardableHeaders: () => forwardableHeaders,
50
52
  generateCa: () => generateCa,
51
53
  isInterceptedHost: () => isInterceptedHost,
54
+ listUsageSnapshots: () => listUsageSnapshots,
52
55
  mintLeaf: () => mintLeaf,
53
56
  parseConnectTarget: () => parseConnectTarget,
54
57
  readFarmManifest: () => readFarmManifest,
58
+ readUsageSnapshot: () => readUsageSnapshot,
55
59
  realConnectCertStore: () => realConnectCertStore,
56
60
  realConnectEffects: () => realConnectEffects,
57
61
  recoverFarm: () => recoverFarm,
@@ -62,6 +66,7 @@ __export(index_exports, {
62
66
  runFrontDoorSupervisor: () => runFrontDoorSupervisor,
63
67
  runSupervisor: () => runSupervisor,
64
68
  servedByPipeline: () => servedByPipeline,
69
+ snapshotPath: () => snapshotPath,
65
70
  startConnectServer: () => startConnectServer,
66
71
  topLevelNames: () => topLevelNames
67
72
  });
@@ -2346,8 +2351,8 @@ function parseConnectTarget(authority) {
2346
2351
  function isInterceptedHost(host, interceptHost) {
2347
2352
  return host === interceptHost;
2348
2353
  }
2349
- function servedByPipeline(path7) {
2350
- return path7?.startsWith(ROUTED_PATH_PREFIX) === true;
2354
+ function servedByPipeline(path8) {
2355
+ return path8?.startsWith(ROUTED_PATH_PREFIX) === true;
2351
2356
  }
2352
2357
  var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
2353
2358
  "connection",
@@ -2952,6 +2957,183 @@ function ensureFrontDoor(params) {
2952
2957
  }
2953
2958
  }
2954
2959
 
2960
+ // src/usage/read.ts
2961
+ var import_node_path7 = __toESM(require("node:path"), 1);
2962
+
2963
+ // src/usage/schema.ts
2964
+ var import_zod5 = require("zod");
2965
+ var USAGE_SCHEMA_VERSION = 1;
2966
+ var VersionSchema = import_zod5.z.literal(USAGE_SCHEMA_VERSION);
2967
+ var InstantSchema = import_zod5.z.iso.datetime();
2968
+ var TokenCountSchema = import_zod5.z.number().int().nonnegative();
2969
+ var TokenUsageSchema = import_zod5.z.strictObject({
2970
+ inputTokens: TokenCountSchema.optional(),
2971
+ outputTokens: TokenCountSchema.optional(),
2972
+ cacheCreationInputTokens: TokenCountSchema.optional(),
2973
+ cacheReadInputTokens: TokenCountSchema.optional()
2974
+ });
2975
+ var LIMIT_KINDS = ["rate-limited", "quota-exhausted"];
2976
+ var LimitClassificationSchema = import_zod5.z.strictObject({
2977
+ kind: import_zod5.z.enum(LIMIT_KINDS),
2978
+ /** Seconds to wait before retrying, from the response's `retry-after`. */
2979
+ retryAfterSeconds: import_zod5.z.number().nonnegative().optional(),
2980
+ /** When the limiting window resets, from the upstream's own reset header. */
2981
+ resetAt: InstantSchema.optional(),
2982
+ /** The window the upstream named as the binding one (`anthropic-ratelimit-unified-representative-claim`), such as `five_hour` or `seven_day`. */
2983
+ window: import_zod5.z.string().optional(),
2984
+ /** The signals the classification rests on, each `<name>=<value>` from a response header or the error body's `type` or `code`. Never the error message. */
2985
+ evidence: import_zod5.z.array(import_zod5.z.string())
2986
+ });
2987
+ var UsageRecordSchema = import_zod5.z.strictObject({
2988
+ schemaVersion: VersionSchema,
2989
+ /** When the front door received the request. */
2990
+ at: InstantSchema,
2991
+ /** The launching identity, when the launch resolved one. */
2992
+ identity: import_zod5.z.string().optional(),
2993
+ /** `anthropic` for an OAuth session's request to Claude Code's own API, otherwise the provider's name. */
2994
+ provider: import_zod5.z.string(),
2995
+ /** The launch's session id, generated per launch. */
2996
+ sessionId: import_zod5.z.string().optional(),
2997
+ /** The project the launch ran in (its git repository root), when the launch sent one: headroom launches do. */
2998
+ project: import_zod5.z.string().optional(),
2999
+ /** The front-door route that served the request. */
3000
+ route: import_zod5.z.string(),
3001
+ method: import_zod5.z.string(),
3002
+ /** The API path as the upstream sees it (`/v1/messages`), without the provider prefix or a query string. */
3003
+ endpoint: import_zod5.z.string(),
3004
+ /** The model the response named. */
3005
+ model: import_zod5.z.string().optional(),
3006
+ status: import_zod5.z.number().int(),
3007
+ /** Milliseconds from the request's arrival to its response head. */
3008
+ latencyMs: import_zod5.z.number().int().nonnegative(),
3009
+ /** Milliseconds from the request's arrival to the end of its response body. */
3010
+ durationMs: import_zod5.z.number().int().nonnegative(),
3011
+ /** Whether the client received the whole body. */
3012
+ outcome: import_zod5.z.enum(["completed", "aborted"]),
3013
+ usage: TokenUsageSchema.optional(),
3014
+ /** The response's quota and rate-limit headers, by name (see `isRateLimitHeader`). */
3015
+ rateLimitHeaders: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.string()).optional(),
3016
+ limit: LimitClassificationSchema.optional(),
3017
+ /** The upstream's request id (`request-id`), for matching a record to the provider's own logs. */
3018
+ requestId: import_zod5.z.string().optional()
3019
+ });
3020
+ var QuotaWindowSchema = import_zod5.z.strictObject({
3021
+ /** The fraction of the window used, as the upstream reports it (0 to 1). */
3022
+ utilization: import_zod5.z.number().nonnegative().optional(),
3023
+ resetsAt: InstantSchema.optional(),
3024
+ /** The window's own status (`allowed`, `allowed_warning`, `rejected`). */
3025
+ status: import_zod5.z.string().optional()
3026
+ });
3027
+ var UnifiedRateLimitSchema = import_zod5.z.strictObject({
3028
+ /** The overall status: `allowed`, `allowed_warning` or `rejected`. */
3029
+ status: import_zod5.z.string().optional(),
3030
+ fiveHour: QuotaWindowSchema.optional(),
3031
+ sevenDay: QuotaWindowSchema.optional(),
3032
+ /** The window that currently binds. */
3033
+ representativeClaim: import_zod5.z.string().optional(),
3034
+ /** When the binding window resets. */
3035
+ resetAt: InstantSchema.optional(),
3036
+ /** Whether usage beyond the plan (extra usage) is available: `allowed` or `rejected`. */
3037
+ overageStatus: import_zod5.z.string().optional()
3038
+ });
3039
+ var RateLimitStateSchema = import_zod5.z.strictObject({
3040
+ observedAt: InstantSchema,
3041
+ /** Every quota and rate-limit header on that response, by name. */
3042
+ headers: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.string()),
3043
+ unified: UnifiedRateLimitSchema.optional()
3044
+ });
3045
+ var LimitEventSchema = LimitClassificationSchema.extend({
3046
+ observedAt: InstantSchema,
3047
+ status: import_zod5.z.number().int()
3048
+ });
3049
+ var ProviderUsageStateSchema = import_zod5.z.strictObject({
3050
+ lastRequestAt: InstantSchema,
3051
+ lastStatus: import_zod5.z.number().int(),
3052
+ lastModel: import_zod5.z.string().optional(),
3053
+ /** The latest rate-limit state, from the last response that carried any rate-limit header. */
3054
+ rateLimit: RateLimitStateSchema.optional(),
3055
+ /** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
3056
+ lastLimit: LimitEventSchema.optional()
3057
+ });
3058
+ var AccountMetadataSchema = import_zod5.z.strictObject({
3059
+ accountUuid: import_zod5.z.string().optional(),
3060
+ emailAddress: import_zod5.z.string().optional(),
3061
+ displayName: import_zod5.z.string().optional(),
3062
+ organizationUuid: import_zod5.z.string().optional(),
3063
+ organizationName: import_zod5.z.string().optional(),
3064
+ organizationType: import_zod5.z.string().optional(),
3065
+ organizationRole: import_zod5.z.string().optional(),
3066
+ workspaceRole: import_zod5.z.string().optional(),
3067
+ billingType: import_zod5.z.string().optional(),
3068
+ seatTier: import_zod5.z.string().optional(),
3069
+ organizationRateLimitTier: import_zod5.z.string().optional(),
3070
+ userRateLimitTier: import_zod5.z.string().optional(),
3071
+ hasExtraUsageEnabled: import_zod5.z.boolean().optional(),
3072
+ subscriptionCreatedAt: import_zod5.z.string().optional(),
3073
+ accountCreatedAt: import_zod5.z.string().optional()
3074
+ });
3075
+ var UsageSnapshotSchema = import_zod5.z.strictObject({
3076
+ schemaVersion: VersionSchema,
3077
+ identity: import_zod5.z.string(),
3078
+ updatedAt: InstantSchema,
3079
+ /** The identity's account metadata as of `updatedAt`. */
3080
+ account: AccountMetadataSchema.optional(),
3081
+ /** Latest state per provider, keyed by provider name (`anthropic` for OAuth sessions). */
3082
+ providers: import_zod5.z.record(import_zod5.z.string(), ProviderUsageStateSchema)
3083
+ });
3084
+
3085
+ // src/usage/account.ts
3086
+ function isIdentityName(name) {
3087
+ return IdentitySchema.shape.name.safeParse(name).success;
3088
+ }
3089
+
3090
+ // src/usage/read.ts
3091
+ var UsageSnapshotError = class extends Error {
3092
+ constructor(file, detail) {
3093
+ super(`${file} is not a usage snapshot this claude-use can read (schema version ${String(USAGE_SCHEMA_VERSION)}): ${detail}`);
3094
+ this.file = file;
3095
+ this.name = "UsageSnapshotError";
3096
+ }
3097
+ file;
3098
+ };
3099
+ function snapshotPath(snapshotsDir, identity) {
3100
+ if (!isIdentityName(identity)) {
3101
+ throw new Error(`"${identity}" is not a valid identity name.`);
3102
+ }
3103
+ return import_node_path7.default.join(snapshotsDir, `${identity}.json`);
3104
+ }
3105
+ function readUsageSnapshot(fs2, snapshotsDir, identity) {
3106
+ const file = snapshotPath(snapshotsDir, identity);
3107
+ const raw = fs2.readFileUtf8(file);
3108
+ if (raw === void 0) {
3109
+ return void 0;
3110
+ }
3111
+ let parsed;
3112
+ try {
3113
+ parsed = JSON.parse(raw);
3114
+ } catch {
3115
+ throw new UsageSnapshotError(file, "not valid JSON");
3116
+ }
3117
+ const snapshot = UsageSnapshotSchema.safeParse(parsed);
3118
+ if (!snapshot.success) {
3119
+ throw new UsageSnapshotError(file, snapshot.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
3120
+ }
3121
+ return snapshot.data;
3122
+ }
3123
+ function listUsageSnapshots(fs2, snapshotsDir) {
3124
+ return fs2.readdir(snapshotsDir).flatMap((name) => {
3125
+ if (!name.endsWith(".json")) {
3126
+ return [];
3127
+ }
3128
+ const identity = name.slice(0, -".json".length);
3129
+ if (!isIdentityName(identity)) {
3130
+ return [];
3131
+ }
3132
+ const snapshot = readUsageSnapshot(fs2, snapshotsDir, identity);
3133
+ return snapshot === void 0 ? [] : [snapshot];
3134
+ }).sort((left, right) => left.identity.localeCompare(right.identity));
3135
+ }
3136
+
2955
3137
  // src/headroom/ensure.ts
2956
3138
  var HEADROOM_START_TIMEOUT_MS = 12e4;
2957
3139
  var HEADROOM_LAUNCHER_POLL_MS = 250;
@@ -3326,6 +3508,8 @@ function evaluateAmbientCredentialGuard(params) {
3326
3508
  FrontDoorStartError,
3327
3509
  HeadroomStartError,
3328
3510
  ROUTED_PATH_PREFIX,
3511
+ UsageSnapshotError,
3512
+ UsageSnapshotSchema,
3329
3513
  buildEntryFacts,
3330
3514
  carryOver,
3331
3515
  createLeafCache,
@@ -3338,9 +3522,11 @@ function evaluateAmbientCredentialGuard(params) {
3338
3522
  forwardableHeaders,
3339
3523
  generateCa,
3340
3524
  isInterceptedHost,
3525
+ listUsageSnapshots,
3341
3526
  mintLeaf,
3342
3527
  parseConnectTarget,
3343
3528
  readFarmManifest,
3529
+ readUsageSnapshot,
3344
3530
  realConnectCertStore,
3345
3531
  realConnectEffects,
3346
3532
  recoverFarm,
@@ -3351,6 +3537,7 @@ function evaluateAmbientCredentialGuard(params) {
3351
3537
  runFrontDoorSupervisor,
3352
3538
  runSupervisor,
3353
3539
  servedByPipeline,
3540
+ snapshotPath,
3354
3541
  startConnectServer,
3355
3542
  topLevelNames
3356
3543
  });
package/dist/index.mjs CHANGED
@@ -2277,8 +2277,8 @@ function parseConnectTarget(authority) {
2277
2277
  function isInterceptedHost(host, interceptHost) {
2278
2278
  return host === interceptHost;
2279
2279
  }
2280
- function servedByPipeline(path7) {
2281
- return path7?.startsWith(ROUTED_PATH_PREFIX) === true;
2280
+ function servedByPipeline(path8) {
2281
+ return path8?.startsWith(ROUTED_PATH_PREFIX) === true;
2282
2282
  }
2283
2283
  var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
2284
2284
  "connection",
@@ -2883,6 +2883,183 @@ function ensureFrontDoor(params) {
2883
2883
  }
2884
2884
  }
2885
2885
 
2886
+ // src/usage/read.ts
2887
+ import path7 from "node:path";
2888
+
2889
+ // src/usage/schema.ts
2890
+ import { z as z5 } from "zod";
2891
+ var USAGE_SCHEMA_VERSION = 1;
2892
+ var VersionSchema = z5.literal(USAGE_SCHEMA_VERSION);
2893
+ var InstantSchema = z5.iso.datetime();
2894
+ var TokenCountSchema = z5.number().int().nonnegative();
2895
+ var TokenUsageSchema = z5.strictObject({
2896
+ inputTokens: TokenCountSchema.optional(),
2897
+ outputTokens: TokenCountSchema.optional(),
2898
+ cacheCreationInputTokens: TokenCountSchema.optional(),
2899
+ cacheReadInputTokens: TokenCountSchema.optional()
2900
+ });
2901
+ var LIMIT_KINDS = ["rate-limited", "quota-exhausted"];
2902
+ var LimitClassificationSchema = z5.strictObject({
2903
+ kind: z5.enum(LIMIT_KINDS),
2904
+ /** Seconds to wait before retrying, from the response's `retry-after`. */
2905
+ retryAfterSeconds: z5.number().nonnegative().optional(),
2906
+ /** When the limiting window resets, from the upstream's own reset header. */
2907
+ resetAt: InstantSchema.optional(),
2908
+ /** The window the upstream named as the binding one (`anthropic-ratelimit-unified-representative-claim`), such as `five_hour` or `seven_day`. */
2909
+ window: z5.string().optional(),
2910
+ /** The signals the classification rests on, each `<name>=<value>` from a response header or the error body's `type` or `code`. Never the error message. */
2911
+ evidence: z5.array(z5.string())
2912
+ });
2913
+ var UsageRecordSchema = z5.strictObject({
2914
+ schemaVersion: VersionSchema,
2915
+ /** When the front door received the request. */
2916
+ at: InstantSchema,
2917
+ /** The launching identity, when the launch resolved one. */
2918
+ identity: z5.string().optional(),
2919
+ /** `anthropic` for an OAuth session's request to Claude Code's own API, otherwise the provider's name. */
2920
+ provider: z5.string(),
2921
+ /** The launch's session id, generated per launch. */
2922
+ sessionId: z5.string().optional(),
2923
+ /** The project the launch ran in (its git repository root), when the launch sent one: headroom launches do. */
2924
+ project: z5.string().optional(),
2925
+ /** The front-door route that served the request. */
2926
+ route: z5.string(),
2927
+ method: z5.string(),
2928
+ /** The API path as the upstream sees it (`/v1/messages`), without the provider prefix or a query string. */
2929
+ endpoint: z5.string(),
2930
+ /** The model the response named. */
2931
+ model: z5.string().optional(),
2932
+ status: z5.number().int(),
2933
+ /** Milliseconds from the request's arrival to its response head. */
2934
+ latencyMs: z5.number().int().nonnegative(),
2935
+ /** Milliseconds from the request's arrival to the end of its response body. */
2936
+ durationMs: z5.number().int().nonnegative(),
2937
+ /** Whether the client received the whole body. */
2938
+ outcome: z5.enum(["completed", "aborted"]),
2939
+ usage: TokenUsageSchema.optional(),
2940
+ /** The response's quota and rate-limit headers, by name (see `isRateLimitHeader`). */
2941
+ rateLimitHeaders: z5.record(z5.string(), z5.string()).optional(),
2942
+ limit: LimitClassificationSchema.optional(),
2943
+ /** The upstream's request id (`request-id`), for matching a record to the provider's own logs. */
2944
+ requestId: z5.string().optional()
2945
+ });
2946
+ var QuotaWindowSchema = z5.strictObject({
2947
+ /** The fraction of the window used, as the upstream reports it (0 to 1). */
2948
+ utilization: z5.number().nonnegative().optional(),
2949
+ resetsAt: InstantSchema.optional(),
2950
+ /** The window's own status (`allowed`, `allowed_warning`, `rejected`). */
2951
+ status: z5.string().optional()
2952
+ });
2953
+ var UnifiedRateLimitSchema = z5.strictObject({
2954
+ /** The overall status: `allowed`, `allowed_warning` or `rejected`. */
2955
+ status: z5.string().optional(),
2956
+ fiveHour: QuotaWindowSchema.optional(),
2957
+ sevenDay: QuotaWindowSchema.optional(),
2958
+ /** The window that currently binds. */
2959
+ representativeClaim: z5.string().optional(),
2960
+ /** When the binding window resets. */
2961
+ resetAt: InstantSchema.optional(),
2962
+ /** Whether usage beyond the plan (extra usage) is available: `allowed` or `rejected`. */
2963
+ overageStatus: z5.string().optional()
2964
+ });
2965
+ var RateLimitStateSchema = z5.strictObject({
2966
+ observedAt: InstantSchema,
2967
+ /** Every quota and rate-limit header on that response, by name. */
2968
+ headers: z5.record(z5.string(), z5.string()),
2969
+ unified: UnifiedRateLimitSchema.optional()
2970
+ });
2971
+ var LimitEventSchema = LimitClassificationSchema.extend({
2972
+ observedAt: InstantSchema,
2973
+ status: z5.number().int()
2974
+ });
2975
+ var ProviderUsageStateSchema = z5.strictObject({
2976
+ lastRequestAt: InstantSchema,
2977
+ lastStatus: z5.number().int(),
2978
+ lastModel: z5.string().optional(),
2979
+ /** The latest rate-limit state, from the last response that carried any rate-limit header. */
2980
+ rateLimit: RateLimitStateSchema.optional(),
2981
+ /** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
2982
+ lastLimit: LimitEventSchema.optional()
2983
+ });
2984
+ var AccountMetadataSchema = z5.strictObject({
2985
+ accountUuid: z5.string().optional(),
2986
+ emailAddress: z5.string().optional(),
2987
+ displayName: z5.string().optional(),
2988
+ organizationUuid: z5.string().optional(),
2989
+ organizationName: z5.string().optional(),
2990
+ organizationType: z5.string().optional(),
2991
+ organizationRole: z5.string().optional(),
2992
+ workspaceRole: z5.string().optional(),
2993
+ billingType: z5.string().optional(),
2994
+ seatTier: z5.string().optional(),
2995
+ organizationRateLimitTier: z5.string().optional(),
2996
+ userRateLimitTier: z5.string().optional(),
2997
+ hasExtraUsageEnabled: z5.boolean().optional(),
2998
+ subscriptionCreatedAt: z5.string().optional(),
2999
+ accountCreatedAt: z5.string().optional()
3000
+ });
3001
+ var UsageSnapshotSchema = z5.strictObject({
3002
+ schemaVersion: VersionSchema,
3003
+ identity: z5.string(),
3004
+ updatedAt: InstantSchema,
3005
+ /** The identity's account metadata as of `updatedAt`. */
3006
+ account: AccountMetadataSchema.optional(),
3007
+ /** Latest state per provider, keyed by provider name (`anthropic` for OAuth sessions). */
3008
+ providers: z5.record(z5.string(), ProviderUsageStateSchema)
3009
+ });
3010
+
3011
+ // src/usage/account.ts
3012
+ function isIdentityName(name) {
3013
+ return IdentitySchema.shape.name.safeParse(name).success;
3014
+ }
3015
+
3016
+ // src/usage/read.ts
3017
+ var UsageSnapshotError = class extends Error {
3018
+ constructor(file, detail) {
3019
+ super(`${file} is not a usage snapshot this claude-use can read (schema version ${String(USAGE_SCHEMA_VERSION)}): ${detail}`);
3020
+ this.file = file;
3021
+ this.name = "UsageSnapshotError";
3022
+ }
3023
+ file;
3024
+ };
3025
+ function snapshotPath(snapshotsDir, identity) {
3026
+ if (!isIdentityName(identity)) {
3027
+ throw new Error(`"${identity}" is not a valid identity name.`);
3028
+ }
3029
+ return path7.join(snapshotsDir, `${identity}.json`);
3030
+ }
3031
+ function readUsageSnapshot(fs2, snapshotsDir, identity) {
3032
+ const file = snapshotPath(snapshotsDir, identity);
3033
+ const raw = fs2.readFileUtf8(file);
3034
+ if (raw === void 0) {
3035
+ return void 0;
3036
+ }
3037
+ let parsed;
3038
+ try {
3039
+ parsed = JSON.parse(raw);
3040
+ } catch {
3041
+ throw new UsageSnapshotError(file, "not valid JSON");
3042
+ }
3043
+ const snapshot = UsageSnapshotSchema.safeParse(parsed);
3044
+ if (!snapshot.success) {
3045
+ throw new UsageSnapshotError(file, snapshot.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
3046
+ }
3047
+ return snapshot.data;
3048
+ }
3049
+ function listUsageSnapshots(fs2, snapshotsDir) {
3050
+ return fs2.readdir(snapshotsDir).flatMap((name) => {
3051
+ if (!name.endsWith(".json")) {
3052
+ return [];
3053
+ }
3054
+ const identity = name.slice(0, -".json".length);
3055
+ if (!isIdentityName(identity)) {
3056
+ return [];
3057
+ }
3058
+ const snapshot = readUsageSnapshot(fs2, snapshotsDir, identity);
3059
+ return snapshot === void 0 ? [] : [snapshot];
3060
+ }).sort((left, right) => left.identity.localeCompare(right.identity));
3061
+ }
3062
+
2886
3063
  // src/headroom/ensure.ts
2887
3064
  var HEADROOM_START_TIMEOUT_MS = 12e4;
2888
3065
  var HEADROOM_LAUNCHER_POLL_MS = 250;
@@ -3256,6 +3433,8 @@ export {
3256
3433
  FrontDoorStartError,
3257
3434
  HeadroomStartError,
3258
3435
  ROUTED_PATH_PREFIX,
3436
+ UsageSnapshotError,
3437
+ UsageSnapshotSchema,
3259
3438
  buildEntryFacts,
3260
3439
  carryOver,
3261
3440
  createLeafCache,
@@ -3268,9 +3447,11 @@ export {
3268
3447
  forwardableHeaders,
3269
3448
  generateCa,
3270
3449
  isInterceptedHost,
3450
+ listUsageSnapshots,
3271
3451
  mintLeaf,
3272
3452
  parseConnectTarget,
3273
3453
  readFarmManifest,
3454
+ readUsageSnapshot,
3274
3455
  realConnectCertStore,
3275
3456
  realConnectEffects,
3276
3457
  recoverFarm,
@@ -3281,6 +3462,7 @@ export {
3281
3462
  runFrontDoorSupervisor,
3282
3463
  runSupervisor,
3283
3464
  servedByPipeline,
3465
+ snapshotPath,
3284
3466
  startConnectServer,
3285
3467
  topLevelNames
3286
3468
  };
@@ -1,9 +1,10 @@
1
1
  /**
2
- * The library surface of claude-use: the pure, port-injected parts other tools can call in-process instead of shelling out to the CLI. Three groups, none of which imports interactive prompting or argument parsing:
2
+ * The library surface of claude-use: the pure, port-injected parts other tools can call in-process instead of shelling out to the CLI. Four groups, none of which imports interactive prompting or argument parsing:
3
3
  *
4
4
  * - identity and configuration-profile resolution and farm sync (`resolveDecisions`, `resyncFarm`, `buildEntryFacts`, ...), pure over facts and ports passed in;
5
5
  * - routing: the front door's Remote-Control-preserving, capability-authenticated CONNECT surface and its certificate authority, the front-door supervisor and ensure lifecycle, and the headroom supervisor and ensure lifecycle, each pure over its injected ports;
6
- * - the ambient-credential guard.
6
+ * - the ambient-credential guard;
7
+ * - the per-identity usage snapshot a statusline or launcher reads: its schema and the reader over an injected filesystem.
7
8
  *
8
9
  * Everything the CLI alone needs (commander wiring, prompts, `doctor`, `check`) is deliberately not exported here.
9
10
  */
@@ -13,6 +14,8 @@ export { buildEntryFacts, carryOver, FARM_MANIFEST_FILENAME, readFarmManifest, r
13
14
  export { CONNECT_INTERCEPT_HOST, CONNECT_LIMITS, createLeafCache, ensureCa, forwardableHeaders, generateCa, isInterceptedHost, mintLeaf, parseConnectTarget, realConnectCertStore, realConnectEffects, ROUTED_PATH_PREFIX, servedByPipeline, startConnectServer, type CaMaterial, type ConnectCertStore, type ConnectEffects, type ConnectLimits, type ConnectServerConfig, type ConnectServerHandle, type ConnectTarget, type LeafCert, } from "./frontdoor/connect";
14
15
  export { ensureFrontDoor, FrontDoorStartError, type EnsureFrontDoorPorts } from "./frontdoor/ensure";
15
16
  export { runFrontDoorSupervisor, type FrontDoorSupervisorPorts, type RunFrontDoorSupervisorOptions } from "./frontdoor/supervisor";
17
+ export { listUsageSnapshots, readUsageSnapshot, snapshotPath, UsageSnapshotError, type UsageReadFs } from "./usage/read";
18
+ export { UsageSnapshotSchema, type UsageSnapshot } from "./usage/schema";
16
19
  export { ensureHeadroom, HeadroomStartError, type EnsureHeadroomPorts } from "./headroom/ensure";
17
20
  export { resolveSupervisorConfig, runSupervisor, type HeadroomSupervisorConfig, type RunSupervisorOptions, type SupervisorPorts } from "./headroom/supervisor";
18
21
  export { AMBIENT_CREDENTIAL_VARS, detectAmbientCredential, evaluateAmbientCredentialGuard, formatAmbientCredentialGuardMessage, type AmbientCredentialDetection, type AmbientCredentialGuardResult, type AmbientCredentialVar, type EvaluateAmbientCredentialGuardParams, type InjectedCredential, } from "./launcher/guard";
@@ -46,6 +46,8 @@ export interface FarmFs {
46
46
  readonly mkdirPrivate: (path: string) => void;
47
47
  /** Writes a file's full contents as UTF-8 text readable only by its owner (mode 0600), atomically: a temporary sibling is written at that mode and renamed into place, so no reader ever sees a partial file or a wider mode. */
48
48
  readonly writeFilePrivate: (path: string, contents: string) => void;
49
+ /** Appends UTF-8 text to a file readable only by its owner (mode 0600), creating it at that mode when it is missing and narrowing an existing wider mode. The file is opened in append mode, so the text always lands at the end however the file grew; a reader running at the same moment can still see a trailing partial write. It is the primitive an append-only log with one writer per file is built on. */
50
+ readonly appendFilePrivate: (path: string, contents: string) => void;
49
51
  /** Creates a file only if it does not already exist, returning false when it does. This is the exclusive-create the identity lock depends on for its mutual exclusion — a read-then-write pair would race. */
50
52
  readonly writeFileExclusive: (path: string, contents: string) => boolean;
51
53
  /** A stable content hash of one file, or undefined when it is missing or is not a regular file. Used only to recognise farm data that has already been adopted into the canonical tree. */
@@ -52,6 +52,12 @@ export interface LayoutPaths {
52
52
  readonly frontdoorSessionsDir: string;
53
53
  /** Path to the front door's log: one line per lifecycle event and routed failure, never a token. */
54
54
  readonly frontdoorLogPath: string;
55
+ /** Directory holding the usage store, mode 0700: the request log and the per-identity snapshots. Metadata only, never content or credentials. */
56
+ readonly usageDir: string;
57
+ /** Directory of the append-only usage log: one `<YYYY-MM-DD>.<writer-pid>.jsonl` segment per writing process per UTC day. */
58
+ readonly usageLogDir: string;
59
+ /** Directory of the latest-state snapshots, one `<identity>.json` per identity: the stable file other tools read. */
60
+ readonly usageSnapshotsDir: string;
55
61
  }
56
62
  /**
57
63
  * Resolves the current CLAUDE_USE_HOME root: the environment variable when set to a non-empty string, otherwise `~/.claude-use`. An empty string counts as unset, consistent with how this project treats empty-string environment variables elsewhere (see the ambient-credential guard).
@@ -0,0 +1,20 @@
1
+ import { CliError } from "../cliError";
2
+ import type { FarmFs } from "../launcher/ports";
3
+ import { type AccountMetadata } from "./schema";
4
+ /** Raised when an identity's `.claude.json` exists but cannot be read as account metadata (not a JSON object, or an `oauthAccount` field of an unexpected type): reported, never read as "no account". */
5
+ export declare class AccountMetadataError extends CliError {
6
+ readonly file: string;
7
+ constructor(file: string, detail: string);
8
+ }
9
+ /** Whether `name` is a valid identity name, which is also what makes it safe as one path component under the identities and snapshots directories (no separator, no leading dot). */
10
+ export declare function isIdentityName(name: string): boolean;
11
+ /** The path of an identity's `.claude.json`. */
12
+ export declare function claudeJsonPath(identitiesDir: string, identity: string): string;
13
+ /**
14
+ * Reads an identity's account metadata from the `oauthAccount` block Claude Code writes into the identity's `.claude.json` when it logs in: the fields `AccountMetadataSchema` names and nothing else (the token is never in this file; Claude Code keeps it in the Keychain or `.credentials.json`). Undefined when the identity has no `.claude.json` or no `oauthAccount` (a setup-token identity, or one never logged in). Throws `AccountMetadataError` for a file that is not a JSON object, and on an invalid identity name, which could otherwise name a path outside the identities directory.
15
+ */
16
+ export declare function readAccountMetadata(fs: Pick<FarmFs, "readFileUtf8">, identitiesDir: string, identity: string): AccountMetadata | undefined;
17
+ /**
18
+ * An account reader for the long-running door: re-reads an identity's `.claude.json` only when its modification time or size has changed, since the door records every request and the file (which also holds Claude Code's per-project history) can be large while its account block rarely changes.
19
+ */
20
+ export declare function createAccountReader(fs: Pick<FarmFs, "readFileUtf8" | "lstat">, identitiesDir: string): (identity: string) => AccountMetadata | undefined;
@@ -0,0 +1,70 @@
1
+ import type { FarmFs } from "../launcher/ports";
2
+ import { type LimitKind, type UsageRecord, type UsageSnapshot } from "./schema";
3
+ /**
4
+ * The read side of the usage store: pure over an injected filesystem, so the CLI, the library surface and tests all read the same way, and nothing here writes.
5
+ */
6
+ /** The filesystem reads the usage store's readers need. */
7
+ export type UsageReadFs = Pick<FarmFs, "readFileUtf8" | "readdir">;
8
+ /** The UTC day an instant falls on, as a segment names it. */
9
+ export declare function segmentDay(epochMs: number): string;
10
+ /** A segment's file name for one writer on one day. */
11
+ export declare function segmentName(day: string, pid: number): string;
12
+ /** One log segment found on disk. */
13
+ export interface LogSegment {
14
+ readonly name: string;
15
+ readonly day: string;
16
+ /** The epoch milliseconds the segment's day ends at: no record in it is later. */
17
+ readonly endsAt: number;
18
+ }
19
+ /** Lists the log's segments, oldest day first; files that are not segments (a stray temporary file) are not the log's and are ignored. */
20
+ export declare function listLogSegments(fs: Pick<FarmFs, "readdir">, logDir: string): readonly LogSegment[];
21
+ /** What a log read found. */
22
+ export interface UsageLogRead {
23
+ /** Every valid record at or after the cut-off, oldest first. */
24
+ readonly records: readonly UsageRecord[];
25
+ /** Complete lines that did not parse as a record of this schema version: a corrupted line, or one a newer claude-use wrote. Reported, never silently dropped. */
26
+ readonly invalidLines: number;
27
+ }
28
+ /**
29
+ * Reads the usage log, optionally only from `sinceMs` on. Each segment has a single writer that appends whole lines, so the only incomplete line a reader can meet is the last one of a segment still being written, which has no trailing newline yet; it is left for the next read rather than counted as invalid.
30
+ */
31
+ export declare function readUsageLog(fs: UsageReadFs, logDir: string, options?: {
32
+ readonly sinceMs?: number;
33
+ }): UsageLogRead;
34
+ /** Raised for a snapshot file that exists but does not hold a snapshot of this schema version. */
35
+ export declare class UsageSnapshotError extends Error {
36
+ readonly file: string;
37
+ constructor(file: string, detail: string);
38
+ }
39
+ /** The path of an identity's snapshot. Throws for an invalid identity name, which could otherwise name a path outside the snapshots directory. */
40
+ export declare function snapshotPath(snapshotsDir: string, identity: string): string;
41
+ /** Reads one identity's snapshot: undefined when it has none yet, `UsageSnapshotError` when the file is there but unreadable. */
42
+ export declare function readUsageSnapshot(fs: Pick<FarmFs, "readFileUtf8">, snapshotsDir: string, identity: string): UsageSnapshot | undefined;
43
+ /** Reads every identity's snapshot, by identity name. */
44
+ export declare function listUsageSnapshots(fs: UsageReadFs, snapshotsDir: string): readonly UsageSnapshot[];
45
+ /** Token totals across a set of records. */
46
+ interface TokenTotals {
47
+ readonly inputTokens: number;
48
+ readonly outputTokens: number;
49
+ readonly cacheCreationInputTokens: number;
50
+ readonly cacheReadInputTokens: number;
51
+ }
52
+ /** The totals for one identity and provider over a set of records. */
53
+ export interface UsageSummary {
54
+ /** The identity, or undefined for requests from a launch that resolved none. */
55
+ readonly identity: string | undefined;
56
+ readonly provider: string;
57
+ readonly requests: number;
58
+ /** Responses with a status of 400 or above (refusals included). */
59
+ readonly failed: number;
60
+ /** Classified refusals, by kind. */
61
+ readonly limits: Readonly<Record<LimitKind, number>>;
62
+ readonly tokens: TokenTotals;
63
+ readonly firstAt: string;
64
+ readonly lastAt: string;
65
+ /** The models the responses named, most-used first. */
66
+ readonly models: readonly string[];
67
+ }
68
+ /** Groups records by identity and provider and totals each group, ordered by identity then provider. */
69
+ export declare function summariseUsage(records: readonly UsageRecord[]): readonly UsageSummary[];
70
+ export {};