sessionpipe 0.10.0 → 0.12.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 (45) hide show
  1. package/README.md +74 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +5165 -2399
  4. package/dist/cli.js.map +4 -4
  5. package/dist/control/cli.d.ts +4 -0
  6. package/dist/control/cli.d.ts.map +1 -1
  7. package/dist/control/daemon.d.ts +24 -0
  8. package/dist/control/daemon.d.ts.map +1 -1
  9. package/dist/control/local.d.ts +24 -0
  10. package/dist/control/local.d.ts.map +1 -1
  11. package/dist/control/pool.d.ts +49 -0
  12. package/dist/control/pool.d.ts.map +1 -0
  13. package/dist/control/route.d.ts +1 -1
  14. package/dist/control/route.d.ts.map +1 -1
  15. package/dist/help.d.ts.map +1 -1
  16. package/dist/hook.d.ts.map +1 -1
  17. package/dist/hook.js +38 -2
  18. package/dist/hook.js.map +2 -2
  19. package/dist/index.js +1135 -880
  20. package/dist/index.js.map +4 -4
  21. package/dist/pool/cli.d.ts +39 -0
  22. package/dist/pool/cli.d.ts.map +1 -0
  23. package/dist/pool/credentials.d.ts +44 -0
  24. package/dist/pool/credentials.d.ts.map +1 -0
  25. package/dist/pool/import.d.ts +23 -0
  26. package/dist/pool/import.d.ts.map +1 -0
  27. package/dist/pool/index.d.ts +138 -0
  28. package/dist/pool/index.d.ts.map +1 -0
  29. package/dist/pool/install.d.ts +46 -0
  30. package/dist/pool/install.d.ts.map +1 -0
  31. package/dist/pool/login.d.ts +27 -0
  32. package/dist/pool/login.d.ts.map +1 -0
  33. package/dist/pool/policy.d.ts +100 -0
  34. package/dist/pool/policy.d.ts.map +1 -0
  35. package/dist/pool/server.d.ts +76 -0
  36. package/dist/pool/server.d.ts.map +1 -0
  37. package/dist/pool/store.d.ts +89 -0
  38. package/dist/pool/store.d.ts.map +1 -0
  39. package/dist/pool/types.d.ts +92 -0
  40. package/dist/pool/types.d.ts.map +1 -0
  41. package/dist/run.d.ts +14 -1
  42. package/dist/run.d.ts.map +1 -1
  43. package/dist/worker.js +1143 -888
  44. package/dist/worker.js.map +4 -4
  45. package/package.json +1 -1
@@ -0,0 +1,39 @@
1
+ import { type LocalReply, type LocalRequest } from "../control/local.js";
2
+ import { type Pool } from "./index.js";
3
+ export declare const POOL_HELP = " sessionpipe pool [--json] (the accounts in order, the room each has left)\n sessionpipe pool on | off (point every Claude Code here at the pool, or stop)\n sessionpipe pool add --import | --login [--code -]\n sessionpipe pool remove|enable|disable <id|email> | order <id|email>\u2026 | refresh [<id|email>]";
4
+ type PoolAsk = Extract<LocalRequest, {
5
+ op: "pool";
6
+ }>;
7
+ export interface PoolCliDeps {
8
+ /** One request to the running daemon; null when none answers (control/local.ts). */
9
+ ask: (req: PoolAsk, timeoutMs: number) => Promise<LocalReply | null>;
10
+ /** The engine in this process, for when no daemon runs. */
11
+ open: () => Pool;
12
+ /** Write and start the daemon's service; what it did, in words (control/pair.ts). */
13
+ installService: () => string;
14
+ /** The pasted `code#state`, read without echo. */
15
+ readSecret: () => Promise<string>;
16
+ now: () => number;
17
+ }
18
+ /** The installed CLI by its real path, so the service outlives a reinstall's symlink. */
19
+ export declare function cliPath(distDir: string): string;
20
+ export declare function poolMain(argv: string[], out: (s?: string) => void, distDir: string, over?: Partial<PoolCliDeps>): Promise<void>;
21
+ /** `pool off` for `sessionpipe uninstall` (DESIGN §2.4): nothing to say when the pool
22
+ * was never on; otherwise the same lines `pool off` prints. */
23
+ export declare function poolOffForUninstall(out: (s?: string) => void, over?: Partial<PoolCliDeps>): Promise<void>;
24
+ /** A terminal's input, as far as reading one secret line needs. */
25
+ export interface SecretInput {
26
+ isTTY?: boolean;
27
+ setEncoding(enc: BufferEncoding): unknown;
28
+ on(event: "data" | "end", cb: (d: string) => void): unknown;
29
+ off(event: "data", cb: (d: string) => void): unknown;
30
+ setRawMode?(on: boolean): unknown;
31
+ resume(): unknown;
32
+ pause(): unknown;
33
+ }
34
+ /** The code, pasted: everything from a pipe, or one line typed at the terminal with
35
+ * echo off (raw mode; backspace edits, Ctrl-C leaves). Never an argument: a command
36
+ * line is in the shell's history and in `ps`. */
37
+ export declare function readSecret(stdin?: SecretInput, err?: (s: string) => void, exit?: (code: number) => void): Promise<string>;
38
+ export {};
39
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/pool/cli.ts"],"names":[],"mappings":"AAWA,OAAO,EAAO,KAAK,UAAU,EAAE,KAAK,YAAY,EAAc,MAAM,qBAAqB,CAAC;AAI1F,OAAO,EAAY,KAAK,IAAI,EAAgC,MAAM,YAAY,CAAC;AAE/E,eAAO,MAAM,SAAS,0WAGyE,CAAC;AAEhG,KAAK,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,oFAAoF;IACpF,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACrE,2DAA2D;IAC3D,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,qFAAqF;IACrF,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,kDAAkD;IAClD,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AASD,yFAAyF;AACzF,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO/C;AAUD,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,OAAO,CAAC,WAAW,CAAM,GAC9B,OAAO,CAAC,IAAI,CAAC,CA+Gf;AAED;gEACgE;AAChE,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAInH;AA8ED,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IAC1C,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAC5D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IACrD,UAAU,CAAC,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC;IAClC,MAAM,IAAI,OAAO,CAAC;IAClB,KAAK,IAAI,OAAO,CAAC;CAClB;AAED;;kDAEkD;AAClD,wBAAgB,UAAU,CACxB,KAAK,GAAE,WAA2B,EAClC,GAAG,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAA0C,EAC9D,IAAI,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAA6B,GACpD,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
@@ -0,0 +1,44 @@
1
+ import type { PoolCredential } from "./types.js";
2
+ /** Refresh this long before `expires_at`. */
3
+ export declare const REFRESH_EARLY_MS: number;
4
+ /** Don't ask a store that just refused again before this. */
5
+ export declare const STORE_RETRY_MS = 30000;
6
+ /** After a refresh the token endpoint failed (not refused), wait this long before the
7
+ * next try: the sinks' cadence. Never one call per request. */
8
+ export declare const REFRESH_BACKOFF_MS: readonly [30000, 60000, 300000];
9
+ export interface CredentialDeps {
10
+ now: () => number;
11
+ /** From the secrets store; null when it can't be read right now. */
12
+ load: (id: string) => PoolCredential | null;
13
+ /** Into the secrets store; false when it refused (the memory copy still serves). */
14
+ save: (id: string, c: PoolCredential) => boolean;
15
+ refresh: (c: PoolCredential) => Promise<PoolCredential>;
16
+ isAuthError: (e: unknown) => boolean;
17
+ log: (line: string) => void;
18
+ }
19
+ export type Fetched = {
20
+ ok: true;
21
+ access: string /** Refreshed within this call or the last minute. */;
22
+ fresh: boolean;
23
+ } | {
24
+ ok: false;
25
+ why: "unreadable" | "sign-in" | "upstream";
26
+ detail: string;
27
+ };
28
+ export declare class Credentials {
29
+ private readonly deps;
30
+ private readonly cache;
31
+ private readonly inflight;
32
+ private readonly refusedAt;
33
+ private readonly refreshedAt;
34
+ private readonly failed;
35
+ constructor(deps: CredentialDeps);
36
+ /** Put a credential just obtained (login, import) into memory and the store. */
37
+ set(id: string, c: PoolCredential): boolean;
38
+ forget(id: string): void;
39
+ /** The access token to send now. `force` refreshes whatever the expiry says (a 401
40
+ * on a token that wasn't fresh). */
41
+ get(id: string, force?: boolean): Promise<Fetched>;
42
+ private fetch;
43
+ }
44
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/pool/credentials.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,6CAA6C;AAC7C,eAAO,MAAM,gBAAgB,QAAa,CAAC;AAC3C,6DAA6D;AAC7D,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC;gEACgE;AAChE,eAAO,MAAM,kBAAkB,YAAI,KAAM,EAAE,KAAM,EAAE,MAAO,CAAU,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,oEAAoE;IACpE,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,cAAc,GAAG,IAAI,CAAC;IAC5C,oFAAoF;IACpF,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC;IACjD,OAAO,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IACrC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,MAAM,OAAO,GACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,qDAAqD,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAClG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,qBAAa,WAAW;IAMV,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgE;IACvF,YAA6B,IAAI,EAAE,cAAc,EAAI;IAErD,gFAAgF;IAChF,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,GAAG,OAAO,CAI1C;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAKvB;IAED;yCACqC;IACrC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/C;YAEa,KAAK;CAuCpB"}
@@ -0,0 +1,23 @@
1
+ import type { PoolAccount } from "./store.js";
2
+ import type { PoolCredential } from "./types.js";
3
+ export interface ResolvedLogin {
4
+ dir: string;
5
+ account_uuid: string;
6
+ email: string;
7
+ credential: PoolCredential;
8
+ }
9
+ export interface Imported {
10
+ added: {
11
+ account: PoolAccount;
12
+ credential: PoolCredential;
13
+ dir: string;
14
+ }[];
15
+ /** Logins left out: already pooled, seen twice in this run, or unreadable. */
16
+ skipped: {
17
+ dir: string;
18
+ email?: string;
19
+ why: string;
20
+ }[];
21
+ }
22
+ export declare function importLogins(logins: readonly ResolvedLogin[], accounts: readonly PoolAccount[], now: number): Imported;
23
+ //# sourceMappingURL=import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import.d.ts","sourceRoot":"","sources":["../../src/pool/import.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,UAAU,EAAE,cAAc,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3E,8EAA8E;IAC9E,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzD;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,GAAG,EAAE,MAAM,GACV,QAAQ,CAkBV"}
@@ -0,0 +1,138 @@
1
+ import { type InstallReport } from "@sessionpipe/core";
2
+ import { type Runner, type StoreName } from "../secrets.js";
3
+ import { type Imported } from "./import.js";
4
+ import { type PoolEnvState } from "./install.js";
5
+ import { type AccountSummary } from "./policy.js";
6
+ import { type PoolAccount, type PoolConfig } from "./store.js";
7
+ import type { Exec, LimitWindow, PoolAdapter, PoolLimits } from "./types.js";
8
+ export type { Credentials } from "./credentials.js";
9
+ export { cleanEnv, installedPoolEnv, installPoolEnv, poolEnv, uninstallPoolEnv } from "./install.js";
10
+ export { accountId } from "./login.js";
11
+ export { type AccountStatus, type AccountSummary, choose, expirePins, HOLD_MS, observe, PIN_IDLE_MS, type PolicyState, SWITCH_PERCENT, summary, } from "./policy.js";
12
+ export { type PoolServer, type PoolStats, startPoolServer } from "./server.js";
13
+ export { type PoolAccount, type PoolConfig, poolFile, poolStateFile, readPool } from "./store.js";
14
+ export type * from "./types.js";
15
+ /** Idle accounts are asked for their usage this often (and at once on add/enable/refresh). */
16
+ export declare const POLL_MS: number;
17
+ /** The hello lists at most this many (core's hello schema). */
18
+ export declare const ACCOUNTS_MAX = 20;
19
+ /** How long the listener trusts its copy of the bearer before asking the store again. */
20
+ export declare const BEARER_CACHE_MS = 5000;
21
+ /** How long state.json may lag the pins and holds in memory. */
22
+ export declare const PERSIST_MS = 1000;
23
+ /** After a rotation the bearer before it is still accepted this long: a Claude Code
24
+ * session keeps the token it read at its start (bb gives the same grace). */
25
+ export declare const ROTATION_GRACE_MS: number;
26
+ export interface PoolDeps {
27
+ /** core's `claudePool` unless a test says otherwise (types.ts says what of it is used). */
28
+ adapter?: PoolAdapter;
29
+ fetch?: typeof fetch;
30
+ now?: () => number;
31
+ log?: (line: string) => void;
32
+ env?: NodeJS.ProcessEnv;
33
+ /** Runs `security` / `secret-tool` for the secrets store. */
34
+ run?: Runner;
35
+ /** Runs the keychain reader for `addImport`; by default `run`, failing loudly. */
36
+ exec?: Exec;
37
+ claudeDirs?: () => string[];
38
+ /** PKCE randomness (tests). */
39
+ random?: (n: number) => Buffer;
40
+ onLimits?: (accountUuid: string, session: string | null, limits: PoolLimits) => void;
41
+ onChange?: () => void;
42
+ /** Where requests go (tests: the fake upstream). */
43
+ upstream?: string;
44
+ pollMs?: number;
45
+ /** How long state.json may lag memory (tests shorten it). */
46
+ persistMs?: number;
47
+ }
48
+ export type PoolErrorCode = "pool_account" | "pool_order" | "pool_off" | "login" | "pool";
49
+ export declare class PoolError extends Error {
50
+ readonly code: PoolErrorCode;
51
+ constructor(code: PoolErrorCode, message: string);
52
+ }
53
+ export interface HelloPoolAccount {
54
+ /** The account uuid: joins with `limits.observed.account_id` and `session.account_id`. */
55
+ id: string;
56
+ email?: string;
57
+ label?: string;
58
+ enabled: boolean;
59
+ status: AccountSummary["status"];
60
+ current: boolean;
61
+ until?: string;
62
+ windows?: LimitWindow[];
63
+ limited_by?: string;
64
+ }
65
+ export interface HelloPool {
66
+ on: boolean;
67
+ harness: "claude-code";
68
+ accounts: HelloPoolAccount[];
69
+ }
70
+ export interface PoolStatus {
71
+ on: boolean;
72
+ port: number;
73
+ /** The listener is up in this process. */
74
+ listening: boolean;
75
+ /** Another daemon answers on pool.json's port; this process stood down (start()). */
76
+ another_daemon?: true;
77
+ /** The last bearer rotation; the one before is accepted until ROTATION_GRACE_MS after. */
78
+ rotated_at?: string;
79
+ /** Requests it is answering right now (0 when it isn't up). */
80
+ in_flight: number;
81
+ settings: {
82
+ file: string;
83
+ state: PoolEnvState;
84
+ note?: string;
85
+ }[];
86
+ accounts: (PoolAccount & AccountSummary & {
87
+ limits?: PoolLimits;
88
+ })[];
89
+ pending?: {
90
+ id: string;
91
+ url: string;
92
+ expires_at: string;
93
+ };
94
+ }
95
+ export interface Pool {
96
+ start(): Promise<{
97
+ port: number;
98
+ moved: boolean;
99
+ other?: true;
100
+ }>;
101
+ stop(): Promise<void>;
102
+ setOn(on: boolean): Promise<InstallReport[]>;
103
+ beginLogin(): {
104
+ id: string;
105
+ url: string;
106
+ expires_at: string;
107
+ };
108
+ completeLogin(code: string, pendingId?: string): Promise<PoolAccount>;
109
+ requireOn(): void;
110
+ addImport(): Promise<Imported>;
111
+ remove(ref: string): PoolAccount;
112
+ enable(ref: string): PoolAccount;
113
+ disable(ref: string): PoolAccount;
114
+ order(refs: readonly string[]): PoolAccount[];
115
+ refresh(ref?: string): Promise<void>;
116
+ find(ref: string): PoolAccount | null;
117
+ accounts(): PoolAccount[];
118
+ status(): PoolStatus;
119
+ helloPool(): HelloPool;
120
+ /** Move the bearer and every credential into another store and pin it there. */
121
+ moveSecrets(to: StoreName): {
122
+ moved: string[];
123
+ kept: string[];
124
+ };
125
+ /** A new bearer now (settings rewritten while on); the old one is good for
126
+ * ROTATION_GRACE_MS more. */
127
+ rotate(): InstallReport[];
128
+ readonly config: PoolConfig;
129
+ }
130
+ /** The pool in one line, for an ack's `detail` and the first line of `sessionpipe pool`
131
+ * (CONTROL.md §6): `on · 3 accounts · 2 with room · serving Fiction`. */
132
+ export declare function summaryLine(s: Pick<PoolStatus, "on" | "accounts">): string;
133
+ /** The origins the adapter talks to: the API and its OAuth endpoints. */
134
+ export declare function vendorOrigins(adapter: Pick<PoolAdapter, "POOL_ENDPOINTS">): string[];
135
+ /** A fetch whose requests to any of `origins` go to `to` instead, path and query kept. */
136
+ export declare function rerouted(fetchFn: typeof fetch, origins: readonly string[], to: string): typeof fetch;
137
+ export declare function openPool(deps: PoolDeps): Pool;
138
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pool/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { InstallReport } from "@sessionpipe/core";
2
+ export declare const POOL_ENV_KEYS: readonly ["ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL"];
3
+ export declare const poolBaseUrl: (port: number) => string;
4
+ /** The three values for a listener on `port` with bearer `token`. */
5
+ export declare function poolEnv(port: number, token: string): Record<(typeof POOL_ENV_KEYS)[number], string>;
6
+ /** What identifies a block as ours: this listener, or the one it moved from, or this
7
+ * token, or the one before it while a rotation is under way. */
8
+ export interface Ours {
9
+ port: number;
10
+ token: string;
11
+ /** The port before the listener moved (pool.json's `last_port`). */
12
+ lastPort?: number | undefined;
13
+ /** The bearer before `pool on` minted this one. */
14
+ lastToken?: string | undefined;
15
+ /** Where the original bytes of a changed file are kept for `off` (see the header). */
16
+ keep?: string | undefined;
17
+ }
18
+ /** Write the block into each config dir's settings.json. */
19
+ export declare function installPoolEnv(dirs: readonly string[], o: Ours): InstallReport[];
20
+ /** Remove exactly what we wrote. `created` names the files `pool on` made, deleted
21
+ * again once nothing else is in them. */
22
+ export declare function uninstallPoolEnv(dirs: readonly string[], o: Ours & {
23
+ created?: readonly string[];
24
+ }): InstallReport[];
25
+ export type PoolEnvState =
26
+ /** Our block, pointing at this port with this token. */
27
+ "current"
28
+ /** Our block, but another port or token: `pool on` again rewrites it. */
29
+ | "stale" | "missing"
30
+ /** Someone else's base URL or token: that config dir does not go through the pool. */
31
+ | "other"
32
+ /** A config dir anyone else can write to: `pool on` won't put a token there. */
33
+ | "unsafe" | "unreadable";
34
+ /** Per settings.json: does Claude Code there go through this listener? */
35
+ export declare function installedPoolEnv(dirs: readonly string[], o: Ours): {
36
+ file: string;
37
+ state: PoolEnvState;
38
+ note?: string;
39
+ }[];
40
+ /** An environment for a child that must talk to the vendor directly: the pool's
41
+ * variables (and bb's, which are the same names) taken out. Note what it is not: Claude
42
+ * Code also reads `env` from settings.json, so a `claude` child still goes through the
43
+ * pool while it is on; `claude auth status` needs no network and is unaffected either
44
+ * way. The daemon's own usage polls use fetch, never Claude Code. */
45
+ export declare function cleanEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
46
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/pool/install.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,eAAO,MAAM,aAAa,YACxB,oBAAoB,EACpB,sBAAsB,EACtB,0CAA0C,CAClC,CAAC;AAEX,eAAO,MAAM,WAAW,SAAU,MAAM,KAAG,MAAoC,CAAC;AAEhF,qEAAqE;AACrE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAMnG;AAED;iEACiE;AACjE,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AA+FD,4DAA4D;AAC5D,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,aAAa,EAAE,CAyChF;AAED;0CAC0C;AAC1C,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG;IAAE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,aAAa,EAAE,CA8BpH;AAED,MAAM,MAAM,YAAY;AACtB,wDAAwD;AACtD,SAAS;AACX,yEAAyE;GACvE,OAAO,GACP,SAAS;AACX,sFAAsF;GACpF,OAAO;AACT,gFAAgF;GAC9E,QAAQ,GACR,YAAY,CAAC;AAEjB,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,CAAC,EAAE,IAAI,GACN;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAcxD;AAED;;;;sEAIsE;AACtE,wBAAgB,QAAQ,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAAC,UAAU,CAIhF"}
@@ -0,0 +1,27 @@
1
+ import type { PoolAccount, PoolState } from "./store.js";
2
+ import type { PoolAdapter, PoolCredential, PoolProfile } from "./types.js";
3
+ export declare const LOGIN_TTL_MS: number;
4
+ export type Pending = NonNullable<PoolState["pending"]>;
5
+ /** The pool's id for an account: eight hex digits of its uuid, like a session's short
6
+ * id, the same on every machine that pools it. */
7
+ export declare const accountId: (accountUuid: string) => string;
8
+ export declare function beginLogin(adapter: Pick<PoolAdapter, "beginLogin">, now: number, random?: (n: number) => Buffer): Pending;
9
+ /** The pending login `id` names, still valid; null with why not. */
10
+ export declare function pendingLogin(pending: Pending | undefined, id: string | undefined, now: number): {
11
+ pending: Pending;
12
+ } | {
13
+ why: "no_pending" | "login_expired" | "unknown_login";
14
+ };
15
+ /** A record for a profile, or the existing record of the same account brought up to
16
+ * date (a login repeated for an account already in the pool refreshes its credential
17
+ * and its email, never makes a twin). */
18
+ export declare function accountFor(accounts: readonly PoolAccount[], profile: PoolProfile, now: number): {
19
+ account: PoolAccount;
20
+ existed: boolean;
21
+ };
22
+ export declare function completeLogin(adapter: Pick<PoolAdapter, "completeLogin">, fetch: typeof globalThis.fetch, pending: Pending, code: string, accounts: readonly PoolAccount[], now: number): Promise<{
23
+ account: PoolAccount;
24
+ existed: boolean;
25
+ credential: PoolCredential;
26
+ }>;
27
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/pool/login.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE3E,eAAO,MAAM,YAAY,QAAc,CAAC;AAExC,MAAM,MAAM,OAAO,GAAG,WAAW,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;AAExD;mDACmD;AACnD,eAAO,MAAM,SAAS,gBAAiB,MAAM,KAAG,MAAkD,CAAC;AAEnG,wBAAgB,UAAU,CACxB,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,EACxC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAC7B,OAAO,CAST;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,YAAY,GAAG,eAAe,GAAG,eAAe,CAAA;CAAE,CAKlF;AAED;;0CAE0C;AAC1C,wBAAgB,UAAU,CACxB,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAa5C;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,EAC3C,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,EAC9B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,cAAc,CAAA;CAAE,CAAC,CAGjF"}
@@ -0,0 +1,100 @@
1
+ import type { LimitWindow, PoolLimits } from "./types.js";
2
+ /** A window this full sends new work elsewhere (bb's threshold; Claude Code's own
3
+ * footer turns red about there). */
4
+ export declare const SWITCH_PERCENT = 98;
5
+ /** A session that sent nothing for this long is no longer pinned. */
6
+ export declare const PIN_IDLE_MS: number;
7
+ /** How long an account rests after an upstream 5xx or a 429 that named no reset. */
8
+ export declare const HOLD_MS = 60000;
9
+ /** A `rejected` status with no window naming a reset is believed this long. */
10
+ export declare const REJECTED_MS: number;
11
+ /** The longest a Retry-After is believed: a vendor's typo must not park an account for a year. */
12
+ export declare const MAX_HOLD_MS: number;
13
+ /** Pins kept at most; the oldest go first. A caller inventing session ids fills nothing
14
+ * but this. */
15
+ export declare const PINS_MAX = 4096;
16
+ export interface AccountState {
17
+ id: string;
18
+ enabled: boolean;
19
+ /** Why the account can't serve until a person acts (`sign in again`). */
20
+ error?: string;
21
+ /** Out of the rotation until this time (ms): a 429's Retry-After, a 5xx rest. */
22
+ hold_until?: number;
23
+ /** When the hold was set. A clock that then went backwards (NTP on a laptop) would
24
+ * stretch a minute's rest into an hour; a hold set in the future is cut to HOLD_MS. */
25
+ hold_at?: number;
26
+ /** Why: `limit` (a 429 or a `rejected`: the pool knows the answer and says it itself)
27
+ * or `upstream` (a 5xx rest: the upstream's own answer still passes through). */
28
+ hold_why?: "limit" | "upstream";
29
+ /** The freshest numbers, whatever their source, and when they were taken (ms). */
30
+ limits?: PoolLimits;
31
+ limits_at?: number;
32
+ }
33
+ export interface Pin {
34
+ account: string;
35
+ /** Last request at (ms). */
36
+ used: number;
37
+ }
38
+ export interface PolicyState {
39
+ /** In the person's order. */
40
+ accounts: AccountState[];
41
+ /** Session uuid → the account serving it. */
42
+ pins: Record<string, Pin>;
43
+ }
44
+ export type Observation = PoolLimits | {
45
+ status: 429;
46
+ retryAfterMs?: number;
47
+ } | {
48
+ upstream5xx: true;
49
+ } | {
50
+ error: string;
51
+ } | {
52
+ error: null;
53
+ };
54
+ export type Reason = "pinned" | "first-with-room" | "moved" | "none";
55
+ /** When the account has room again, as far as its numbers say; null when it has room
56
+ * now, or when nothing says (an error, disabled). */
57
+ export declare function roomAt(a: AccountState, now: number): number | null;
58
+ export declare const hasRoom: (a: AccountState, now: number) => boolean;
59
+ /** The account a request goes to. A pinned session stays while its account has room
60
+ * (and isn't excluded: the retry after a 429 names the account that just refused). */
61
+ export declare function choose(state: PolicyState, o: {
62
+ session?: string | undefined;
63
+ now: number;
64
+ exclude?: readonly string[];
65
+ }): {
66
+ account: AccountState | null;
67
+ reason: Reason;
68
+ };
69
+ /** Record that `session` was just served by `account`. Only a uuid-shaped session id
70
+ * (Claude Code's) is pinned: anything else is a caller's string, not a session. */
71
+ export declare function pin(state: PolicyState, session: string | undefined, account: string, now: number): void;
72
+ /** Forget pins idle for PIN_IDLE_MS and pins to accounts no longer in the pool. */
73
+ export declare function expirePins(state: PolicyState, now: number): number;
74
+ /** Take in what was just learnt about an account. Newest wins. Returns whether the
75
+ * account's windows changed (the daemon appends a `limits.observed` then) — a 429, a
76
+ * 5xx or an error changes its standing, not its numbers. */
77
+ export declare function observe(state: PolicyState, accountId: string, what: Observation, now: number): boolean;
78
+ export type AccountStatus = "ready" | "limited" | "error" | "disabled";
79
+ export interface AccountSummary {
80
+ id: string;
81
+ status: AccountStatus;
82
+ /** Serving new sessions now: the first with room. */
83
+ current: boolean;
84
+ /** Limited: when it has room again, RFC 3339, if known. */
85
+ until?: string;
86
+ /** The fullest window, for a status line. */
87
+ used_percent?: number;
88
+ /** The freshest numbers, from headers or the idle poll (1–8 windows). */
89
+ windows?: LimitWindow[];
90
+ /** What took it out: the window that is full (the one that resets last when
91
+ * several are), else the window a `rejected` status named (`claim`), else
92
+ * `rejected` for a 429 / a 5xx rest, `error` for a sign-in needed. Only with
93
+ * status limited or error. */
94
+ limited_by?: string;
95
+ }
96
+ /** What the hello and `pool status` say per account. */
97
+ export declare function summary(state: PolicyState, now: number): AccountSummary[];
98
+ /** How many accounts have room now (the /health answer). */
99
+ export declare const withRoom: (state: PolicyState, now: number) => number;
100
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/pool/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import type { Credentials } from "./credentials.js";
2
+ import { type PolicyState } from "./policy.js";
3
+ import type { PoolAdapter, PoolLimits } from "./types.js";
4
+ export declare const BODY_CAP: number;
5
+ export interface PoolServerDeps {
6
+ adapter: Pick<PoolAdapter, "POOL_ENDPOINTS" | "readLimits" | "parseUserId" | "withAccount" | "upstreamHeaders">;
7
+ fetch: typeof fetch;
8
+ now: () => number;
9
+ /** Nothing secret ever goes in: account ids, statuses, timings. */
10
+ log: (line: string) => void;
11
+ /** The port pool.json remembers; 0 or taken: a free one is picked. */
12
+ port: number;
13
+ /** The bearers Claude Code may present: the current one (settings.json) and, for a
14
+ * while after a rotation, the one before it, so a session that started with the old
15
+ * token keeps working. Read on every request, since `pool on` mints in another
16
+ * process. Null: the secrets store can't be read right now, which is a 503, not an
17
+ * unknown bearer. */
18
+ token: () => readonly string[] | null;
19
+ on: () => boolean;
20
+ /** Shared with the façade and mutated here through choose/observe/pin. */
21
+ state: PolicyState;
22
+ accountUuid: (id: string) => string | null;
23
+ credentials: Credentials;
24
+ /** The state changed (a pin, a hold, fresh numbers): write it down. */
25
+ persist: () => void;
26
+ /** An account's windows changed on a request: the daemon's `limits.observed`.
27
+ * `accountId` is the pool's id; the façade maps it to the account uuid. */
28
+ onLimits: (accountId: string, session: string | null, limits: PoolLimits) => void;
29
+ /** An account's standing changed (held, in error): the daemon sends a hello. */
30
+ onStanding?: () => void;
31
+ /** Where requests go (tests: the fake upstream). */
32
+ upstream?: string;
33
+ bodyCap?: number;
34
+ }
35
+ export interface PoolStats {
36
+ requests: number;
37
+ upstream: number;
38
+ retries: number;
39
+ /** Answered with our own 429: no account could even be tried. */
40
+ refused: number;
41
+ /** Requests whose client left before the answer ended. */
42
+ aborted: number;
43
+ /** Requests being answered right now (the daemon's busy(): never restart mid-stream). */
44
+ in_flight: number;
45
+ }
46
+ export interface PoolServer {
47
+ port: number;
48
+ /** True when the port isn't the one asked for: settings.json must be rewritten. */
49
+ moved: boolean;
50
+ stats: PoolStats;
51
+ close(): Promise<void>;
52
+ }
53
+ /** The path as the upstream will read it, or null for one it must never see: each
54
+ * segment percent-decoded once (an upstream router decodes before it routes, so the
55
+ * deny list has to read what the router reads), then refused if anything is still
56
+ * encoded (`%` left over: a double encoding), if a decoded byte is a slash, a
57
+ * backslash, whitespace or a control character (an encoded separator or an odd byte
58
+ * Claude Code never sends), if a segment is empty or a dot segment (`//`, `%2e%2e`),
59
+ * or if a segment does not decode at all. What comes back is re-encoded per segment,
60
+ * so what was checked is what is sent. */
61
+ export declare function normalPath(pathname: string): string | null;
62
+ /** The upstream URL for a request-target, or why not. Built from the parsed, then
63
+ * normalised path (normalPath), compared lower-cased: the upstream's routes are not
64
+ * case-sensitive in any way the pool should bet on. */
65
+ export declare function upstreamUrl(target: string, base: string): {
66
+ url: string;
67
+ } | {
68
+ status: 400 | 403;
69
+ why: string;
70
+ };
71
+ /** The Host this loopback answers to: itself, by address or name, with its own port. */
72
+ export declare function hostOk(host: string | undefined, port: number): boolean;
73
+ /** A bearer compared in constant time, whatever its length: both sides are hashed first. */
74
+ export declare function bearerOk(header: string | undefined, token: string): boolean;
75
+ export declare function startPoolServer(deps: PoolServerDeps): Promise<PoolServer>;
76
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/pool/server.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAsC,KAAK,WAAW,EAAyB,MAAM,aAAa,CAAC;AAC1G,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D,eAAO,MAAM,QAAQ,QAAmB,CAAC;AAEzC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,gBAAgB,GAAG,YAAY,GAAG,aAAa,GAAG,aAAa,GAAG,iBAAiB,CAAC,CAAC;IAChH,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,mEAAmE;IACnE,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb;;;;0BAIsB;IACtB,KAAK,EAAE,MAAM,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACtC,EAAE,EAAE,MAAM,OAAO,CAAC;IAClB,0EAA0E;IAC1E,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC3C,WAAW,EAAE,WAAW,CAAC;IACzB,uEAAuE;IACvE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;gFAC4E;IAC5E,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAClF,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,mFAAmF;IACnF,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAqCD;;;;;;;2CAO2C;AAC3C,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc1D;AAED;;wDAEwD;AACxD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAU9G;AAED,wFAAwF;AACxF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtE;AAED,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAQ3E;AAuCD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAwRzE"}
@@ -0,0 +1,89 @@
1
+ import { type Runner, type StoreName } from "../secrets.js";
2
+ import type { AccountState, Pin } from "./policy.js";
3
+ import type { PendingLogin, PoolCredential, PoolLimits } from "./types.js";
4
+ export interface PoolAccount {
5
+ /** The pool's short id for the account (eight hex digits of its uuid, short-id.ts). */
6
+ id: string;
7
+ label?: string;
8
+ email: string;
9
+ /** Claude Code's account uuid: what events and the hello name (DESIGN §2.3). */
10
+ account_uuid: string;
11
+ enabled: boolean;
12
+ added_at: string;
13
+ }
14
+ export interface PoolConfig {
15
+ on: boolean;
16
+ /** 0 until the listener first picks one. */
17
+ port: number;
18
+ /** The port before the last move: a settings.json still naming it is still ours. */
19
+ last_port?: number;
20
+ /** Where the secrets live, pinned when the first one is written (`file` included, so
21
+ * a later terminal with a keychain never picks another store); absent only before
22
+ * that, or in a pool.json from before this field, which means the file. */
23
+ secrets_in?: StoreName;
24
+ accounts: PoolAccount[];
25
+ /** settings.json files `pool on` created, so `pool off` can delete them again. */
26
+ created?: string[];
27
+ /** When the bearer was last rotated (`pool on` again, `rotate`): the one before it is
28
+ * still accepted for a while after, for sessions that started with it. */
29
+ rotated_at?: string;
30
+ }
31
+ export interface PoolStateAccount {
32
+ hold_until?: number;
33
+ hold_at?: number;
34
+ hold_why?: "limit" | "upstream";
35
+ error?: string;
36
+ limits?: PoolLimits;
37
+ limits_at?: number;
38
+ }
39
+ export interface PoolState {
40
+ pins: Record<string, Pin>;
41
+ accounts: Record<string, PoolStateAccount>;
42
+ pending?: PendingLogin & {
43
+ id: string;
44
+ expires_at: number;
45
+ };
46
+ }
47
+ export declare const poolFile: (env?: NodeJS.ProcessEnv) => string;
48
+ export declare const poolSecretsFile: (env?: NodeJS.ProcessEnv) => string;
49
+ export declare const poolStateFile: (env?: NodeJS.ProcessEnv) => string;
50
+ /** pool.json as read: defensive, every account checked, anything off the shape dropped. */
51
+ export declare function readPool(env?: NodeJS.ProcessEnv): PoolConfig;
52
+ export declare const writePool: (c: PoolConfig, env?: NodeJS.ProcessEnv) => void;
53
+ export declare function readPoolState(env?: NodeJS.ProcessEnv): PoolState;
54
+ export declare const writePoolState: (s: PoolState, env?: NodeJS.ProcessEnv) => void;
55
+ /** The policy's view: the config's accounts in order, each with its remembered standing. */
56
+ export declare function policyAccounts(cfg: PoolConfig, st: PoolState): AccountState[];
57
+ /** Write the policy's accounts back into the state (the config keeps `enabled`). */
58
+ export declare function keepStanding(st: PoolState, accounts: AccountState[]): void;
59
+ export declare const TOKEN_NAME = "pool:token";
60
+ /** The bearer before the last rotation, kept for the grace period. */
61
+ export declare const PREVIOUS_TOKEN_NAME = "pool:token.previous";
62
+ export declare const credentialName: (id: string) => string;
63
+ /** The pool's secrets, wherever they live. `secrets_in` picks the keychain or the
64
+ * Secret Service; otherwise pool.secrets.json, which is what a headless machine has. A
65
+ * store that can't be read right now (a keychain locked with nobody at the screen)
66
+ * answers null; the caller keeps what it has in memory and asks again later. */
67
+ export declare class PoolSecrets {
68
+ private readonly env;
69
+ private readonly run;
70
+ constructor(env?: NodeJS.ProcessEnv, run?: Runner);
71
+ /** Where secrets go: `secrets_in` from pool.json, else the file. */
72
+ store(cfg: PoolConfig): StoreName;
73
+ get(cfg: PoolConfig, name: string): string | null;
74
+ /** The value, and whether a missing one is really missing or the store can't be
75
+ * read right now (a 0000 file, a locked keychain): the listener answers those two
76
+ * differently. The keychain is probed only when an item came back empty. */
77
+ read(cfg: PoolConfig, name: string): {
78
+ value: string | null;
79
+ unreadable: boolean;
80
+ };
81
+ /** False when the store refused (then nothing was written anywhere). */
82
+ set(cfg: PoolConfig, name: string, value: string): boolean;
83
+ del(cfg: PoolConfig, name: string): void;
84
+ /** A credential is JSON, base64url: `security -i` takes a double-quoted word, so
85
+ * the quotes and spaces of JSON could never go into the keychain as they are. */
86
+ credential(cfg: PoolConfig, id: string): PoolCredential | null;
87
+ setCredential(cfg: PoolConfig, id: string, c: PoolCredential): boolean;
88
+ }
89
+ //# sourceMappingURL=store.d.ts.map