agent-shim 7.7.0 → 7.8.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.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The one boolean vocabulary every agent-shim input shares, CLI flag values and environment variables alike: `true` and `1` mean true, `false` and `0` mean false, and nothing else is a boolean (no case folding, no `yes`/`no`).
3
+ *
4
+ * Lives in a module with no imports so both `src/cli/parsers.ts` (which raises on anything else) and `src/cliError.ts` (which must never raise while reporting another failure) can share it without an import cycle.
5
+ * @param input - The raw text.
6
+ * @returns The boolean it spells, or undefined when it spells none.
7
+ */
8
+ export declare function parseBoolWord(input: string): boolean | undefined;
@@ -0,0 +1,40 @@
1
+ import { UsageError } from "../cliError";
2
+ /** One `<key>=<value>` pair, as parsed by `parsePair`. */
3
+ export interface ParsedPair {
4
+ readonly key: string;
5
+ readonly value: string;
6
+ }
7
+ /**
8
+ * Splits `input` on its *first* `=` into a key and a value. The value may itself contain further `=` characters (they stay part of the value); only the first `=` is treated as the separator.
9
+ *
10
+ * Throws when there is no `=` at all, or when the key half is empty (`=true`, or a leading `=`).
11
+ */
12
+ export declare function parsePair(input: string): ParsedPair;
13
+ /** Parses `input` as a boolean in the shared vocabulary (`true`/`1`, `false`/`0`); throws on anything else. */
14
+ export declare function parseBool(input: string): boolean;
15
+ /** Raised when a boolean environment variable holds something outside the shared vocabulary. */
16
+ export declare class InvalidEnvBoolError extends UsageError {
17
+ readonly variable: string;
18
+ readonly value: string;
19
+ constructor(variable: string, value: string);
20
+ }
21
+ /**
22
+ * Reads a boolean environment variable with the same vocabulary a CLI flag value uses. Unset and the empty string both mean "not given" (undefined), consistent with how every other agent-shim environment variable treats the empty string; anything outside the vocabulary throws `InvalidEnvBoolError` rather than silently reading as false.
23
+ */
24
+ export declare function parseEnvBool(variable: string, value: string | undefined): boolean | undefined;
25
+ /**
26
+ * Parses a comma-separated list of `<key>=<bool>` pairs into a plain object, e.g. `"history=true,knowledge=false"` becomes `{ history: true, knowledge: false }`. Used only for the list-valued environment variables; flags repeat instead.
27
+ *
28
+ * An empty string parses to `{}`. A key repeated within the same list is not an error: the later occurrence wins, matching how a plain object literal with a repeated key behaves.
29
+ */
30
+ export declare function parseBoolPairList(input: string): Record<string, boolean>;
31
+ /**
32
+ * Commander repeatable-option collector for one `--flag <key>=<bool>` occurrence: parses `value` and merges it over `previous`, so `--category history=true --category knowledge=false` accumulates into one object, the later occurrence winning on key collision.
33
+ */
34
+ export declare function collectBoolPair(value: string, previous?: Readonly<Record<string, boolean>>): Record<string, boolean>;
35
+ /** Commander repeatable-option collector for one `--flag <KEY>=<VALUE>` occurrence with a free-text value, merged over `previous` the same way `collectBoolPair` merges. */
36
+ export declare function collectStringPair(value: string, previous?: Readonly<Record<string, string>>): Record<string, string>;
37
+ /** Commander repeatable-option collector for a plain repeated value (`--extends a --extends b`), preserving the order given. */
38
+ export declare function collectRepeated(value: string, previous?: readonly string[]): string[];
39
+ /** Commander option parser for a duration such as `30m`, `5h` or `7d` (a count followed by ms, s, m, h, d or w), returning milliseconds. A malformed value is a usage error naming the option. */
40
+ export declare function parseDurationOption(value: string): number;
@@ -0,0 +1,61 @@
1
+ import { type ConfigProfile, type GlobalConfig, type LaunchFlags } from "./config/schema";
2
+ import { CliError } from "./cliError";
3
+ import type { LayoutPaths } from "./paths";
4
+ /** Raised by any operation that requires a configuration profile to already exist, when it does not. */
5
+ export declare class ProfileNotFoundError extends CliError {
6
+ readonly profileName: string;
7
+ constructor(profileName: string);
8
+ }
9
+ /** Raised by `createProfile` (`profile add`) when a profile with the given name already has a file. */
10
+ export declare class ProfileAlreadyExistsError extends CliError {
11
+ readonly profileName: string;
12
+ constructor(profileName: string);
13
+ }
14
+ /** Raised when a `--category` patch names something other than one of the four overridable categories (e.g. `secret`, or a typo). */
15
+ export declare class InvalidCategoryNameError extends CliError {
16
+ readonly categoryName: string;
17
+ constructor(categoryName: string);
18
+ }
19
+ /** True when a profile file exists for `name`, regardless of whether it validates. */
20
+ export declare function profileExists(paths: LayoutPaths, name: string): boolean;
21
+ export declare function requireProfileExists(paths: LayoutPaths, name: string): void;
22
+ /** Reads and validates one configuration profile, or undefined when it does not exist. */
23
+ export declare function readProfile(paths: LayoutPaths, name: string): ConfigProfile | undefined;
24
+ /**
25
+ * Creates a new configuration profile, empty apart from the optional `extends` list and description. Throws `ProfileAlreadyExistsError` if a profile with this name already has a file, and `ConfigValidationError` when the result fails `ConfigProfileSchema` (e.g. an empty `--extends ""` name), rather than letting the underlying `ZodError` escape as an unhandled crash.
26
+ */
27
+ export declare function createProfile(paths: LayoutPaths, name: string, extendsList?: readonly string[], description?: string): ConfigProfile;
28
+ /** One profile as reported by `listProfiles`. */
29
+ export interface ProfileListEntry {
30
+ readonly name: string;
31
+ readonly profile: ConfigProfile;
32
+ }
33
+ /** Lists every configuration profile under `configProfilesDir` that has a valid `<name>.json`. */
34
+ export declare function listProfiles(paths: LayoutPaths): readonly ProfileListEntry[];
35
+ /** Reads the user-global `~/.agent-shim/config.json`, or undefined when it does not exist. */
36
+ export declare function readGlobalConfig(paths: LayoutPaths): GlobalConfig | undefined;
37
+ /** Sets the user-global default configuration profile in `~/.agent-shim/config.json`, creating the file if it doesn't exist yet. Whether the profile exists is the caller's concern: `profile use` checks it first. */
38
+ export declare function setGlobalDefaultProfile(paths: LayoutPaths, name: string): GlobalConfig;
39
+ /**
40
+ * Merges `patch` (from one or more `--category cat=bool` flags) into `profile`'s own `categories` object and writes it back.
41
+ *
42
+ * Throws `InvalidCategoryNameError` for any key that isn't one of the four overridable categories — `secret` can never be toggled by any configuration layer, this included.
43
+ */
44
+ export declare function setProfileCategories(paths: LayoutPaths, name: string, patch: Readonly<Record<string, boolean>>): ConfigProfile;
45
+ /**
46
+ * Merges `patch` (from one or more `--entry "path"=bool` flags) into `profile`'s own `entries` object and writes it back.
47
+ *
48
+ * Key validity (the `<category>/<real-relative-path>` prefix requirement) is enforced by `ConfigProfileSchema`'s own `EntriesSchema` at write time — an invalid key surfaces as the usual `ConfigValidationError`.
49
+ */
50
+ export declare function setProfileEntries(paths: LayoutPaths, name: string, patch: Readonly<Record<string, boolean>>): ConfigProfile;
51
+ /**
52
+ * Merges `patch` into `profile`'s own `launch` object and writes it back. A key present in `patch` with the value `undefined` removes that setting outright (a `--no-launch-provider`, say) rather than overwriting it, and a `launch` object left with no settings is removed too.
53
+ */
54
+ export declare function setProfileLaunchFlags(paths: LayoutPaths, name: string, patch: Readonly<LaunchFlags>): ConfigProfile;
55
+ /** Replaces `profile`'s own `extends` list and/or description; an empty list or an undefined description removes the field. */
56
+ export declare function setProfileMetadata(paths: LayoutPaths, name: string, patch: Readonly<{
57
+ extends?: readonly string[];
58
+ description?: string | false;
59
+ }>): ConfigProfile;
60
+ /** Deletes a configuration profile's file. Throws `ProfileNotFoundError` when it does not exist. Nothing that references it by name is rewritten; `agent-shim doctor` reports any identity default, directory rule or `extends` left pointing at it. */
61
+ export declare function removeProfile(paths: LayoutPaths, name: string): void;
@@ -0,0 +1,136 @@
1
+ import type { Credential, CredentialSource, CredentialTarget, CredentialTargetVar } from "./config/schema";
2
+ import { type CredentialCachePort } from "./credentialCache";
3
+ /** The exit status for a launch whose selected provider or identity has a credential block none of whose sources yields a token: 64, the conventional `EX_USAGE`, because the invocation asked for a credential the environment cannot supply. */
4
+ export declare const CREDENTIAL_UNAVAILABLE_EXIT = 64;
5
+ /** How long a non-interactive credential command may run. Secret-store CLIs answer from a local cache or agent well within a second, so this leaves a wide margin while still failing a wedged command quickly instead of stalling the launch. */
6
+ export declare const CREDENTIAL_COMMAND_TIMEOUT_MS = 10000;
7
+ /** How long an interactive credential command may run: long enough for a person to notice and approve a biometric or password prompt, short enough that a prompt nobody is watching fails the launch rather than hanging it. */
8
+ export declare const CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 120000;
9
+ /** What reading one `file` source found: nothing at that path, or its contents and permission bits. `mode` is undefined on a platform with no POSIX permission bits (Windows), where the looseness check cannot apply. */
10
+ type SecretFileRead = {
11
+ readonly found: false;
12
+ } | {
13
+ readonly found: true;
14
+ readonly content: string;
15
+ readonly mode: number | undefined;
16
+ };
17
+ /** The outcome of one credential command. `stderr` is empty for an interactive run, whose stderr goes to the terminal so a person sees any prompt. */
18
+ export interface CredentialCommandResult {
19
+ readonly status: number | null;
20
+ readonly stdout: string;
21
+ readonly stderr: string;
22
+ readonly timedOut: boolean;
23
+ }
24
+ /**
25
+ * Everything resolving a credential needs from the outside world, injected so the resolver (and every launcher test) runs without `op`, the Keychain, real secret files or a terminal. `src/realPorts.ts` wires the real one; tests wire a fake.
26
+ */
27
+ export interface CredentialPort {
28
+ /** Reads a `file` source's path (absolute or `~`-rooted). */
29
+ readonly readSecretFile: (filePath: string) => SecretFileRead;
30
+ /** Runs a command source's argv directly (no shell) under a timeout, capturing stdout. An interactive run inherits the terminal's stdin and stderr so a person can answer a prompt. */
31
+ readonly runCommand: (argv: readonly [string, ...string[]], options: {
32
+ readonly timeoutMs: number;
33
+ readonly interactive: boolean;
34
+ }) => CredentialCommandResult;
35
+ /** Whether a person can answer a prompt now: standard input is a terminal, or there is a desktop session to show a dialog in. */
36
+ readonly personPresent: () => boolean;
37
+ /** Where cached credentials live, used only for a credential block that asks for caching. Omit to resolve every launch from the sources. */
38
+ readonly cache?: CredentialCacheEnv;
39
+ }
40
+ /** The sources that run a command: `command` itself and the two presets that compile to one. */
41
+ type CommandBackedSource = Extract<CredentialSource, {
42
+ readonly command: unknown;
43
+ } | {
44
+ readonly op: unknown;
45
+ } | {
46
+ readonly keychain: unknown;
47
+ }>;
48
+ /**
49
+ * The argv a command-backed source runs, with the `op` and `keychain` presets compiled to theirs: `op read <ref>` and `security find-generic-password -s <service> [-a <account>] -w`.
50
+ */
51
+ export declare function commandArgv(source: CommandBackedSource): readonly [string, ...string[]];
52
+ /**
53
+ * Whether a source needs a person present. An explicit `interactive` always wins. Otherwise only `op` defaults to interactive, because the desktop app's integration asks for approval, unless `OP_SERVICE_ACCOUNT_TOKEN` is set, in which case `op` authenticates as the service account with nobody asked. A plain `command` and a `keychain` read default to non-interactive: a Keychain item whose access list already trusts `security` reads silently, and one that does not fails at once with "User interaction is not allowed" rather than hanging.
54
+ */
55
+ export declare function isInteractiveSource(source: CredentialSource, env: Readonly<Record<string, string | undefined>>): boolean;
56
+ /** One source as `check`, `doctor` and every `--json` output report it: its kind plus the same non-secret identifying detail `describeSource` prints. */
57
+ type CredentialSourceSummary = {
58
+ readonly kind: "env";
59
+ readonly variable: string;
60
+ } | {
61
+ readonly kind: "file";
62
+ readonly path: string;
63
+ } | {
64
+ readonly kind: "command";
65
+ readonly program: string;
66
+ } | {
67
+ readonly kind: "op";
68
+ readonly reference: string;
69
+ } | {
70
+ readonly kind: "keychain";
71
+ readonly service: string;
72
+ readonly account?: string;
73
+ } | {
74
+ readonly kind: "literal";
75
+ };
76
+ /** A credential block as it is reported: its effective target and each source's summary, in order. */
77
+ export interface CredentialSummary {
78
+ readonly target: CredentialTarget;
79
+ readonly sources: readonly CredentialSourceSummary[];
80
+ /** The block's cache setting when it asks for caching: how long a token is kept (absent for no expiry) and where. */
81
+ readonly cache?: {
82
+ readonly ttl?: string;
83
+ readonly store?: string;
84
+ };
85
+ }
86
+ /** Summarises a credential block for reporting, with `target` resolved to its default. */
87
+ export declare function summariseCredential(credential: Credential): CredentialSummary;
88
+ /** Renders a source for a message, as `formatSourceSummary` does its summary. */
89
+ export declare function describeSource(source: CredentialSource): string;
90
+ /** Renders a credential summary on one line for human-readable output: `bearer from env Z_API_TOKEN, then op op://...`. */
91
+ export declare function formatCredentialSummary(summary: CredentialSummary): string;
92
+ /** Renders a credential block on one line, as `formatCredentialSummary` does its summary. */
93
+ export declare function describeCredential(credential: Credential): string;
94
+ /** Inputs to `resolveCredential`. */
95
+ export interface ResolveCredentialParams {
96
+ readonly credential: Credential;
97
+ /** The launching environment: where `env` sources are read, and what decides whether `op` is interactive. */
98
+ readonly env: Readonly<Record<string, string | undefined>>;
99
+ readonly port: CredentialPort;
100
+ /** What the credential belongs to, for messages and as its cache key: `provider z` or `identity work`. */
101
+ readonly subject: string;
102
+ /** True to skip a cached token and fetch from the sources, replacing the cached copy: what `credential warm` does. */
103
+ readonly refresh?: boolean;
104
+ }
105
+ /** What caching a credential needs beyond the block's own `cache` setting: the port holding entries, the platform that picks the default store, and the clock the TTL is measured on. */
106
+ export interface CredentialCacheEnv {
107
+ readonly port: CredentialCachePort;
108
+ readonly platform: NodeJS.Platform;
109
+ readonly now: () => number;
110
+ }
111
+ /** A resolved credential: the token, where it goes, and which source produced it. `warnings` carries any source refused on the way for a reason worth reporting (a secret file with loose permissions). */
112
+ export interface ResolvedCredential {
113
+ readonly target: CredentialTarget;
114
+ readonly token: string;
115
+ readonly source: CredentialSource;
116
+ readonly warnings: readonly string[];
117
+ /** When the token was fetched, if it came from the cache rather than a source; undefined for a live fetch. */
118
+ readonly cachedAt?: number;
119
+ }
120
+ /** The outcome of `resolveCredential`: a resolved credential, or a message naming every source and why none yielded a token. */
121
+ export type CredentialResolution = {
122
+ readonly ok: true;
123
+ readonly credential: ResolvedCredential;
124
+ } | {
125
+ readonly ok: false;
126
+ readonly message: string;
127
+ };
128
+ /**
129
+ * Resolves a credential block: a fresh cached token if the block caches one, else tries each source in order and returns the first non-empty token, with the block's target, storing it when the block caches. When none yields one, the message names every source and why, and never a value; for a caching block it also names the command that fills the cache from a terminal. Pure over its injected ports.
130
+ */
131
+ export declare function resolveCredential(params: ResolveCredentialParams): CredentialResolution;
132
+ /**
133
+ * The credential variables a child environment gets for a resolved token: the target's variable set to the token, and the other two set to undefined so that merging this over an environment removes them, and an ambient one inherited from the parent can neither outrank nor sit alongside the credential agent-shim chose. Removing a variable (rather than setting it to the empty string) leaves nothing for Claude Code to interpret.
134
+ */
135
+ export declare function credentialVariables(target: CredentialTarget, token: string): Record<CredentialTargetVar, string | undefined>;
136
+ export {};
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import { type CredentialCache, type CredentialCacheStore } from "./config/schema";
3
+ /** What a cache stores for one credential: the token, when it was fetched, and the source that produced it (a non-secret descriptor, kept so `check` and the launch log can still say where the token came from). */
4
+ export declare const CachedCredentialSchema: z.ZodObject<{
5
+ token: z.ZodString;
6
+ fetchedAt: z.ZodNumber;
7
+ source: z.ZodUnion<readonly [z.ZodObject<{
8
+ env: z.ZodString;
9
+ }, z.core.$strict>, z.ZodObject<{
10
+ file: z.ZodString;
11
+ }, z.core.$strict>, z.ZodObject<{
12
+ interactive: z.ZodOptional<z.ZodBoolean>;
13
+ timeoutMs: z.ZodOptional<z.ZodInt>;
14
+ command: z.ZodTuple<[z.ZodString], z.ZodString>;
15
+ }, z.core.$strict>, z.ZodObject<{
16
+ interactive: z.ZodOptional<z.ZodBoolean>;
17
+ timeoutMs: z.ZodOptional<z.ZodInt>;
18
+ op: z.ZodString;
19
+ }, z.core.$strict>, z.ZodObject<{
20
+ interactive: z.ZodOptional<z.ZodBoolean>;
21
+ timeoutMs: z.ZodOptional<z.ZodInt>;
22
+ keychain: z.ZodObject<{
23
+ service: z.ZodString;
24
+ account: z.ZodOptional<z.ZodString>;
25
+ }, z.core.$strict>;
26
+ }, z.core.$strict>, z.ZodObject<{
27
+ literal: z.ZodString;
28
+ }, z.core.$strict>]>;
29
+ }, z.core.$strict>;
30
+ export type CachedCredential = z.infer<typeof CachedCredentialSchema>;
31
+ /**
32
+ * Where cached credentials live, injected so resolution and its tests need neither the Keychain nor a real directory. `owner` names what the credential belongs to (`identity work`, `provider z`) and is the entry's key; `store` picks the backing store. `read` returns undefined for a missing or unreadable entry, since an entry that cannot be used is the same as no entry.
33
+ */
34
+ export interface CredentialCachePort {
35
+ readonly read: (store: CredentialCacheStore, owner: string) => CachedCredential | undefined;
36
+ readonly write: (store: CredentialCacheStore, owner: string, entry: CachedCredential) => void;
37
+ readonly remove: (store: CredentialCacheStore, owner: string) => void;
38
+ }
39
+ /** The store a block uses: its own choice, else the Keychain on macOS and a file elsewhere. */
40
+ export declare function effectiveStore(cache: Readonly<CredentialCache>, platform: NodeJS.Platform): CredentialCacheStore;
41
+ /** A `ttl` such as `12h` in milliseconds, or undefined for no expiry. */
42
+ export declare function ttlMs(cache: Readonly<CredentialCache>): number | undefined;
43
+ /** Whether a cached entry is still within its TTL at `now`. A cache with no TTL never expires; an entry stamped in the future (a clock that moved back) is treated as expired. */
44
+ export declare function isFresh(entry: CachedCredential, cache: Readonly<CredentialCache>, now: number): boolean;
45
+ /** How old a cached entry is, for `check` and `doctor` to report without the value. */
46
+ export declare function cacheAgeMs(entry: CachedCredential, now: number): number;
@@ -0,0 +1,43 @@
1
+ import { type DirectoryRule, type DirectoryRules } from "./config/schema";
2
+ import { CliError } from "./cliError";
3
+ import type { LayoutPaths } from "./paths";
4
+ /** Raised by `removeDirectoryRule`, `updateDirectoryRule` and `rule show` when no rule matches the given path exactly. */
5
+ export declare class DirectoryRuleNotFoundError extends CliError {
6
+ readonly rulePath: string;
7
+ constructor(rulePath: string);
8
+ }
9
+ /** Raised by `addDirectoryRule` when neither `--config-profile` nor `--identity` is given, and by `updateDirectoryRule` when an update would leave a rule that sets nothing at all: a rule that pins and overrides nothing would do nothing. */
10
+ export declare class DirectoryRuleMissingTargetError extends CliError {
11
+ constructor();
12
+ }
13
+ /** Raised by `addDirectoryRule` when a rule for the exact path already exists: `rule add` creates, `rule set` updates. */
14
+ export declare class DirectoryRuleAlreadyExistsError extends CliError {
15
+ readonly rulePath: string;
16
+ constructor(rulePath: string);
17
+ }
18
+ /** Reads `~/.agent-shim/directory-rules.json`, or an empty rule set when the file does not exist yet. */
19
+ export declare function readDirectoryRules(paths: LayoutPaths): DirectoryRules;
20
+ /** Validates and writes the whole `~/.agent-shim/directory-rules.json` file. Exported so `src/configure.ts` can update a single rule's `categories`/`entries` in place without duplicating this validate-then-write step. Throws `ConfigValidationError` when `rules` fails `DirectoryRulesSchema`, rather than letting the underlying `ZodError` escape as an unhandled crash. */
21
+ export declare function writeDirectoryRules(paths: LayoutPaths, rules: DirectoryRules): void;
22
+ /** Lists every directory rule, in file order. */
23
+ export declare function listDirectoryRules(paths: LayoutPaths): readonly DirectoryRule[];
24
+ /** Inputs to `addDirectoryRule` beyond the path itself. */
25
+ export interface AddDirectoryRuleOptions {
26
+ readonly configProfile?: string;
27
+ readonly identity?: string;
28
+ }
29
+ /**
30
+ * Adds a new directory rule for `rulePath`. Throws `DirectoryRuleAlreadyExistsError` when a rule for that exact path already exists (`updateDirectoryRule` changes one), and `DirectoryRuleMissingTargetError` when neither `configProfile` nor `identity` is given, since a rule that pins neither would do nothing.
31
+ */
32
+ export declare function addDirectoryRule(paths: LayoutPaths, rulePath: string, options: AddDirectoryRuleOptions): DirectoryRule;
33
+ /** The fields `updateDirectoryRule` changes. A value of `false` removes that field from the rule. */
34
+ export interface UpdateDirectoryRuleOptions {
35
+ readonly configProfile?: string | false;
36
+ readonly identity?: string | false;
37
+ }
38
+ /**
39
+ * Updates the existing directory rule for `rulePath` in place, keeping its position in the file and every field not named. Throws `DirectoryRuleNotFoundError` when no rule matches that exact path, and `DirectoryRuleMissingTargetError` when the update would leave a rule that does nothing (remove it with `removeDirectoryRule` instead).
40
+ */
41
+ export declare function updateDirectoryRule(paths: LayoutPaths, rulePath: string, options: UpdateDirectoryRuleOptions): DirectoryRule;
42
+ /** Removes the directory rule for `rulePath`. Throws `DirectoryRuleNotFoundError` when no rule matches that exact path. */
43
+ export declare function removeDirectoryRule(paths: LayoutPaths, rulePath: string): void;
@@ -0,0 +1,11 @@
1
+ /** One `ANTHROPIC_CUSTOM_HEADERS` entry as the child environment carries it. */
2
+ export interface CustomHeaderEntry {
3
+ readonly name: string;
4
+ readonly value: string;
5
+ }
6
+ /**
7
+ * Merges `ANTHROPIC_CUSTOM_HEADERS` blocks: every string in `existing` (as Claude Code writes them, `Name: Value` entries separated by newlines) parsed in order, then every entry of `additions` set on top, so a later block replaces an earlier block's value for the same header name while leaving every other entry untouched. The result is re-serialised in first-seen order with additions updated in place.
8
+ *
9
+ * Blank lines are skipped on parse; a header name keeps its own case exactly as written, since header names are case-insensitive on the wire but a statusline or log reading this value back should see what was set.
10
+ */
11
+ export declare function mergeAnthropicCustomHeaders(existing: readonly (string | undefined)[], additions: readonly CustomHeaderEntry[]): string;
@@ -0,0 +1,85 @@
1
+ import { type CredentialCache, type CredentialSource, type CredentialTarget, type Identity } from "./config/schema";
2
+ import { CliError } from "./cliError";
3
+ import type { LayoutPaths } from "./paths";
4
+ /** Raised by any operation that requires an identity to already exist, when it does not. */
5
+ export declare class IdentityNotFoundError extends CliError {
6
+ readonly name: string;
7
+ constructor(name: string);
8
+ }
9
+ /** Raised by `addIdentity` when an identity with the given name already has an `identity.json`. */
10
+ export declare class IdentityAlreadyExistsError extends CliError {
11
+ readonly identityName: string;
12
+ constructor(identityName: string);
13
+ }
14
+ /** Raised by `addIdentity` when `name` fails `IdentitySchema`'s own naming rule — it must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs, so an email address names an identity directly while a *leading* `@` stays invalid (it would collide with the `@name` selector syntax's first-`@` split). */
15
+ export declare class InvalidIdentityNameError extends CliError {
16
+ readonly attemptedName: string;
17
+ constructor(attemptedName: string);
18
+ }
19
+ export declare function identityExists(paths: LayoutPaths, name: string): boolean;
20
+ /** Reads and validates one identity's `identity.json`, or undefined when it does not exist. */
21
+ export declare function readIdentity(paths: LayoutPaths, name: string): Identity | undefined;
22
+ /**
23
+ * Creates a new identity: validates `name` against `IdentitySchema`'s own naming rule and writes a fresh `identity.json` with `allowAmbientCredential: false` and no `defaultConfigProfile`.
24
+ *
25
+ * Throws `IdentityAlreadyExistsError` if an identity with this name already has an `identity.json` — `add` never silently overwrites an existing identity. Throws `InvalidIdentityNameError` when `name` fails `IdentitySchema`'s naming rule, rather than letting the underlying `ZodError` escape as an unhandled crash.
26
+ */
27
+ export declare function addIdentity(paths: LayoutPaths, name: string): Identity;
28
+ /**
29
+ * Persists `name` as the active identity, written atomically as plain text (not JSON — this file is read by `decideIdentity` in `src/launcher/identity.ts` via a simple UTF-8 read-and-trim, matching the README's documented `~/.agent-shim/active-identity` file).
30
+ *
31
+ * `name` may be a `pool:<name>` selector, which makes launches pick a member of that pool. Throws `IdentityNotFoundError` when no identity with this name exists yet, and `PoolNotFoundError` for a pool that is not defined: selecting either would silently persist a name nothing else can ever load.
32
+ */
33
+ export declare function useIdentity(paths: LayoutPaths, name: string): void;
34
+ /** Reads the persisted active-identity file, or undefined when none is set. */
35
+ export declare function readActiveIdentity(paths: LayoutPaths): string | undefined;
36
+ /**
37
+ * Whether a directory name directly under `identitiesDir` names an actual identity, rather than one of agent-shim's own farm directories.
38
+ *
39
+ * `IdentitySchema` requires an identity name to start with a letter or digit, so a leading `.` can only be a resync's own bookkeeping: a `.<identity>.scratch.<suffix>` tree still being built, or a `.<identity>.previous.<suffix>` superseded farm retained for `agent-shim identity resolve-conflicts`. Neither is an identity, and neither should be reported as a broken one for lacking an `identity.json` a resync never put there.
40
+ */
41
+ export declare function isIdentityDirectoryName(name: string): boolean;
42
+ /** One identity as reported by `listIdentities`, whose `identity.json` parsed and validated cleanly. */
43
+ export interface IdentityListEntry {
44
+ readonly name: string;
45
+ readonly identity: Identity;
46
+ readonly isActive: boolean;
47
+ readonly problem?: never;
48
+ }
49
+ /** One identity whose `identity.json` is present but unreadable — malformed JSON, or valid JSON this version's `IdentitySchema` rejects. `problem` carries the reason, already flattened onto a single line. */
50
+ export interface UnreadableIdentityListEntry {
51
+ readonly name: string;
52
+ readonly identity?: never;
53
+ readonly isActive: boolean;
54
+ readonly problem: string;
55
+ }
56
+ /** Either shape `listIdentities` can report, discriminated by which of `identity`/`problem` is present rather than by a tag field — the two are never simultaneously satisfiable. */
57
+ export type IdentityListing = IdentityListEntry | UnreadableIdentityListEntry;
58
+ /**
59
+ * Lists every identity under `identitiesDir`, marking which one (if any) is currently active.
60
+ *
61
+ * One identity whose `identity.json` cannot be read is reported as its own `UnreadableIdentityListEntry` rather than aborting the whole listing. A single bad file blocking `identity list` outright is exactly the failure mode that hides every *other* identity from view at the moment the user most needs to see them — and the file need not even be corrupt to land here, since a name written by a newer agent-shim whose naming rule has since widened is rejected outright by an older binary's own copy of `IdentitySchema`.
62
+ */
63
+ export declare function listIdentities(paths: LayoutPaths): readonly IdentityListing[];
64
+ /**
65
+ * Sets `identity`'s `defaultConfigProfile` field, or clears it when `profileName` is undefined. Throws `IdentityNotFoundError` when the identity does not exist. Whether the profile exists is the caller's concern: `identity set --default-profile` checks it first.
66
+ */
67
+ export declare function setDefaultConfigProfile(paths: LayoutPaths, identityName: string, profileName: string | undefined): Identity;
68
+ /**
69
+ * Patches `identity`'s `allowAmbientCredential` field. Throws `IdentityNotFoundError` when the identity does not exist.
70
+ */
71
+ export declare function setAllowAmbientCredential(paths: LayoutPaths, identityName: string, allow: boolean): Identity;
72
+ /** The change `setIdentityCredential` makes: new sources and/or a new target for the credential block, or `false` to remove the block and return the identity to its stored login. */
73
+ export type IdentityCredentialChange = {
74
+ readonly sources?: readonly CredentialSource[];
75
+ readonly target?: CredentialTarget;
76
+ readonly cache?: CredentialCache | false;
77
+ } | false;
78
+ /**
79
+ * Sets, changes or removes `identityName`'s credential block. New `sources` replace the whole ordered list (the order is the meaning); a `target` alone keeps the existing sources, and so needs a credential block to exist already. Throws `IdentityNotFoundError` when the identity does not exist, and `UsageError` when only a target is given for an identity with no credential yet.
80
+ */
81
+ export declare function setIdentityCredential(paths: LayoutPaths, identityName: string, change: IdentityCredentialChange): Identity;
82
+ /**
83
+ * Deletes identity `name` entirely: its directory under `identitiesDir` (the farm, its `identity.json`, and whatever that farm holds unshared, credentials included), its resync lock, any scratch or superseded farm a resync left behind, and the active-identity selection when it names this identity. Data shared into `~/.claude` stays there, since the farm only ever symlinks to it. Throws `IdentityNotFoundError` when the identity does not exist.
84
+ */
85
+ export declare function removeIdentity(paths: LayoutPaths, name: string): void;
@@ -7,6 +7,8 @@
7
7
  * - the per-identity usage snapshot a statusline or launcher reads: its schema and the reader over an injected filesystem;
8
8
  * - the state root's layout (`resolveAgentShimHome`, `resolveLayoutPaths`, `buildLayoutPaths`), which includes adopting an existing `~/.claude-use` in place, and the Zod schema and inferred type of every configuration file (identity, configuration profile, provider, pool, directory rules, global config, credential), so a tool can validate or generate them with the same definitions the CLI uses.
9
9
  *
10
+ * Creating and changing identities, configuration profiles, providers, pools and directory rules: the `*Store` modules, which take the state root's `LayoutPaths`, return typed values and throw `CliError` subclasses.
11
+ *
10
12
  * Everything the CLI alone needs (commander wiring, prompts, `doctor`, `check`) is deliberately not exported here.
11
13
  */
12
14
  export { resolveDecisions, topLevelNames, type ResolveDecisionsInput, type ResolvedState } from "./resolve/pipeline";
@@ -24,3 +26,8 @@ export { AMBIENT_CREDENTIAL_VARS, detectAmbientCredential, evaluateAmbientCreden
24
26
  export { buildLayoutPaths, resolveAgentShimHome, resolveClaudeHome, resolveLayoutPaths, type LayoutPaths } from "./paths";
25
27
  export { CliError, EXIT_FAILURE, EXIT_USAGE, UsageError } from "./cliError";
26
28
  export { CategoryClassificationOverlaySchema, CategoryClassificationSchema, CategoryMapSchema, ConfigProfileSchema, CredentialCacheSchema, CredentialSchema, CredentialSourceSchema, DirectoryRuleSchema, DirectoryRulesSchema, EntryValueSchema, GlobalConfigSchema, IdentitySchema, PoolNameSchema, PoolSchema, PortableConfigSchema, ProviderSchema, WhenSchema, type CategoryMap, type ConfigProfile, type Credential, type CredentialSource, type DirectoryRule, type DirectoryRules, type EntryValue, type GlobalConfig, type Identity, type Pool, type PortableConfig, type Provider, type WhenCondition, } from "./config/schema";
29
+ export { addIdentity, IdentityAlreadyExistsError, IdentityNotFoundError, identityExists, InvalidIdentityNameError, isIdentityDirectoryName, listIdentities, readActiveIdentity, readIdentity, removeIdentity, setAllowAmbientCredential, setDefaultConfigProfile, setIdentityCredential, useIdentity, type IdentityCredentialChange, type IdentityListEntry, type IdentityListing, type UnreadableIdentityListEntry, } from "./identityStore";
30
+ export { createProfile, InvalidCategoryNameError, listProfiles, ProfileAlreadyExistsError, ProfileNotFoundError, profileExists, readGlobalConfig, readProfile, removeProfile, setGlobalDefaultProfile, setProfileCategories, setProfileEntries, setProfileLaunchFlags, setProfileMetadata, type ProfileListEntry, } from "./configProfilesStore";
31
+ export { addProvider, describeProviderEndpoint, InvalidProviderNameError, LegacyProviderFileError, listProviders, ProviderAlreadyExistsError, ProviderKindMismatchError, ProviderNotFoundError, providerExists, readProvider, removeProvider, updateProvider, type AddProviderInput, type ProviderListEntry, type UpdateProviderInput, } from "./providersStore";
32
+ export { addPool, PoolNotFoundError, readPools, removePool, requirePool, setPool } from "./poolStore";
33
+ export { addDirectoryRule, DirectoryRuleAlreadyExistsError, DirectoryRuleMissingTargetError, DirectoryRuleNotFoundError, listDirectoryRules, readDirectoryRules, removeDirectoryRule, updateDirectoryRule, writeDirectoryRules, type AddDirectoryRuleOptions, type UpdateDirectoryRuleOptions, } from "./directoryRulesStore";
@@ -0,0 +1,96 @@
1
+ import type { LaunchFlags, Provider } from "../config/schema";
2
+ import { type ResolvedCredential } from "../credential";
3
+ import type { FrontDoorUp, HeadroomUp } from "./ports";
4
+ /** The fully resolved launch flags for one launch. */
5
+ export interface ResolvedLaunchFlags {
6
+ readonly skipPermissions: boolean;
7
+ readonly remoteControl: boolean;
8
+ readonly headroom: boolean;
9
+ /** Whether the launch routes through the front door so its requests are recorded, even with no provider and no headroom. */
10
+ readonly trackUsage: boolean;
11
+ }
12
+ /** The one-off command-line forms of the four boolean launch flags, each undefined when neither its positive nor its `--no-` form was given. */
13
+ interface LaunchFlagOverrides {
14
+ readonly skipPermissions?: boolean;
15
+ readonly remoteControl?: boolean;
16
+ readonly headroom?: boolean;
17
+ readonly trackUsage?: boolean;
18
+ }
19
+ /** Inputs to `resolveLaunchFlags`. */
20
+ export interface ResolveLaunchFlagsParams {
21
+ /** The cascade's resolved `launch` block, when a cascade was loaded for this launch. */
22
+ readonly cascade?: LaunchFlags;
23
+ /** The launch's own `--[no-]skip-permissions`, `--[no-]remote-control`, `--[no-]headroom` and `--[no-]track-usage` flags. */
24
+ readonly flags?: LaunchFlagOverrides;
25
+ readonly env: Readonly<Record<string, string | undefined>>;
26
+ /** What `trackUsage` is when no flag, environment variable or cascade setting says: off, except for a pool launch, whose pick reads the usage the front door records. */
27
+ readonly trackUsageDefault?: boolean;
28
+ }
29
+ /**
30
+ * Resolves `skipPermissions`/`remoteControl`/`headroom`/`trackUsage` for one launch. Each setting has the same three forms, decided in the same order: an explicit command-line flag outright, then its environment variable (`AGENT_SHIM_SKIP_PERMISSIONS`, `AGENT_SHIM_REMOTE_CONTROL`, `AGENT_SHIM_HEADROOM`, `AGENT_SHIM_TRACK_USAGE`), then the cascade, and OFF when none of them says otherwise (`trackUsageDefault` changes that one fallback). The flag outranks the environment variable because it is the more deliberate of the two one-off forms (typed on this very command line, not inherited from a shell profile), and both outrank the cascade because they are one-off overrides of it.
31
+ *
32
+ * Environment variables read with the shared boolean vocabulary (`true`/`1`, `false`/`0`), so `AGENT_SHIM_SKIP_PERMISSIONS=0` switches off a cascade's `skipPermissions: true` for one launch; any other value throws `InvalidEnvBoolError`.
33
+ *
34
+ * This default-off posture is a deliberate change from the legacy bash tool, which passed `--dangerously-skip-permissions` unconditionally on every launch.
35
+ */
36
+ export declare function resolveLaunchFlags(params: ResolveLaunchFlagsParams): ResolvedLaunchFlags;
37
+ /**
38
+ * Builds the tool's own flag arguments from resolved launch flags.
39
+ *
40
+ * `--remote-control=` always carries a literal trailing `=` with an empty value — never bare `--remote-control` — confirmed as a deliberate fix in the legacy script's own git history to stop the flag from consuming the next positional argument as its value.
41
+ */
42
+ export declare function buildFlagArgs(flags: ResolvedLaunchFlags): string[];
43
+ /** Inputs to `buildArgv`. */
44
+ export interface BuildArgvParams {
45
+ readonly toolFlags: readonly string[];
46
+ readonly extraFlags: readonly string[];
47
+ readonly passthrough: readonly string[];
48
+ }
49
+ /**
50
+ * Assembles the final argv passed to the real `claude` binary: tool flags, then `$CLAUDE_EXTRA_FLAGS`, then the user's own forwarded arguments, in that exact order.
51
+ *
52
+ * Extra flags must land BEFORE the passthrough args — confirmed load-bearing against real wrapper scripts (`cpl`, `mp`, `zpl`) that set `CLAUDE_EXTRA_FLAGS="--print"` and then pass a positional prompt afterwards; reversing this order would feed the prompt to `--print` as if it were a flag value instead of a trailing positional.
53
+ */
54
+ export declare function buildArgv(params: BuildArgvParams): string[];
55
+ /** The provider resolved for this launch, with its credential already resolved from the provider's credential block. */
56
+ export interface ResolvedProvider {
57
+ readonly name: string;
58
+ readonly definition: Provider;
59
+ readonly credential: ResolvedCredential;
60
+ }
61
+ /** A resolved provider with the base URL its sessions are sent to: the front door's provider-scoped address, which exists only once the door is up and is the same shape for both kinds (the door is what decides where the request goes from there). */
62
+ export interface RoutedProvider extends ResolvedProvider {
63
+ readonly baseUrl: string;
64
+ }
65
+ /** Inputs to `buildEnv`. */
66
+ export interface BuildEnvParams {
67
+ readonly baseEnv: Readonly<Record<string, string | undefined>>;
68
+ /** True when `CLAUDE_CONFIG_DIR` was already set and the escape hatch applied — `buildEnv` must leave it untouched in that case. */
69
+ readonly configDirEscapeHatch: boolean;
70
+ /** The identity name resolved for this launch, when one was resolved. */
71
+ readonly resolvedIdentityName?: string;
72
+ readonly identitiesDir: string;
73
+ /** The provider resolved for this launch, when one was resolved, with the base URL it routes to. Its credential is resolved by the caller because refusing an unusable one needs the caller's log/exit ports. */
74
+ readonly provider?: RoutedProvider;
75
+ /** The launching identity's own resolved credential, when it has a credential block and no provider was selected. A provider's credential authenticates against the provider's endpoint, so the identity's is never applied alongside one. */
76
+ readonly identityCredential?: ResolvedCredential;
77
+ /** The front door this launch routes through, when one is engaged (a provider is selected, or headroom resolved on). It is what the child talks to: its provider listener for a provider session, its CONNECT surface for an OAuth one. */
78
+ readonly frontdoor?: FrontDoorUp;
79
+ /** The headroom daemon this launch's requests must pass through (as the door's headroom hop), when headroom resolved on. The child never talks to it directly. */
80
+ readonly headroom?: HeadroomUp;
81
+ /** The per-launch session id, stamped into the session header the door identifies this launch's requests by. */
82
+ readonly sessionId: string;
83
+ }
84
+ /**
85
+ * Builds the environment the real `claude` binary is spawned with.
86
+ *
87
+ * When the `CLAUDE_CONFIG_DIR`-already-set escape hatch applied, or no identity was resolved at all (a bare launch with no active identity, matching the legacy script's own "no profile means plain `~/.claude`" behaviour), `CLAUDE_CONFIG_DIR` is left untouched. Otherwise `CLAUDE_CONFIG_DIR` is set to the resolved identity's own directory under `identitiesDir` — farm population into that directory is Phase 5's job, not this function's.
88
+ *
89
+ * A resolved provider is applied regardless of the identity outcome, because it selects which API endpoint the child talks to, not which login's data it sees: `ANTHROPIC_BASE_URL` points at the provider's routed base URL (always the front door's provider-scoped address), `AGENT_SHIM_PROVIDER` names the provider for the statusline, the provider's own `env` entries land verbatim, and its resolved credential is exported as its target's variable (`credentialVariables`), with the other credential variables removed so an ambient one inherited from the parent cannot outrank it. Without a provider, an identity's own resolved credential is exported the same way. The token only ever reaches this environment, never the child's argv or a log line.
90
+ *
91
+ * A resolved front door is the whole of the child's routing, on top of the provider: `ANTHROPIC_CUSTOM_HEADERS` gains the launcher-injected session headers (identity, session id, the launch's capability token, and, when headroom resolved on, the headroom flag and the project identity headroom scopes memory to), merged with any headers the provider's own `env` or the parent environment already set. The door strips every one of them before anything leaves the machine; the token is what authorises the session's requests at the door in the first place. `NODE_EXTRA_CA_CERTS` points at the door's trust bundle in both modes, because the child reaches the door over TLS signed by agent-shim's CA either way. With a provider, the child's HTTPS base URL already names the door's provider listener, so a process that merely binds that port cannot present a certificate the child accepts. Without one (an OAuth launch), the base URL is left exactly as the parent environment had it, because Claude Code enables Remote Control and connectors only against the real `api.anthropic.com`; routing happens one layer down instead, with `HTTPS_PROXY` pointing the child at the door's CONNECT surface and carrying the launch's capability as its proxy credential (the surface refuses any CONNECT without a live one), so the child still believes it is talking to the real `api.anthropic.com` while the surface's terminated TLS feeds the routed paths to the same pipeline. `HEADROOM_PROXY_URL` names the headroom daemon for anything else that wants it; the child never talks to that daemon directly, the door's hop does.
92
+ *
93
+ * `$CLAUDE_EXTRA_FLAGS` is never stripped from the child's environment: some wrappers set it two process-levels up and rely on inheritance through a `claude` invoked from inside a running session.
94
+ */
95
+ export declare function buildEnv(params: BuildEnvParams): Record<string, string | undefined>;
96
+ export {};