agent-shim 7.8.0 → 7.10.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/README.md +2 -1
- package/dist/cli.cjs +534 -511
- package/dist/index.cjs +2315 -153
- package/dist/index.mjs +2307 -153
- package/dist/types/checkReport.d.ts +154 -0
- package/dist/types/claudeShim.d.ts +114 -0
- package/dist/types/cli/commandDeps.d.ts +28 -0
- package/dist/types/cli/envBool.d.ts +11 -0
- package/dist/types/codex/http.d.ts +15 -0
- package/dist/types/configure.d.ts +170 -0
- package/dist/types/doctorReport.d.ts +117 -0
- package/dist/types/frontdoor/custody.d.ts +32 -0
- package/dist/types/frontdoor/headroomHop.d.ts +22 -0
- package/dist/types/frontdoor/pipeline.d.ts +109 -0
- package/dist/types/frontdoor/server.d.ts +44 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/launcher/cascade.d.ts +42 -0
- package/dist/types/launcher/pool.d.ts +44 -0
- package/dist/types/pathNorm.d.ts +14 -0
- package/dist/types/poolPickReport.d.ts +44 -0
- package/dist/types/realPorts.d.ts +102 -0
- package/dist/types/usage/middleware.d.ts +17 -0
- package/dist/types/usage/pick.d.ts +62 -0
- package/dist/types/usage/plan.d.ts +18 -0
- package/dist/types/usage/poolPick.d.ts +31 -0
- package/dist/types/usage/preflight.d.ts +20 -0
- package/dist/types/usage/rateLimit.d.ts +25 -0
- package/dist/types/usage/scan.d.ts +24 -0
- package/dist/types/usage/store.d.ts +43 -0
- package/dist/types/versionDiscovery.d.ts +47 -0
- package/package.json +1 -1
- package/dist/types/cli/parsers.d.ts +0 -40
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { type CategoryClassification, type CategoryClassificationOverlay, type Identity, type Provider } from "./config/schema";
|
|
2
|
+
import { type CredentialSummary } from "./credential";
|
|
3
|
+
import { type AmbientCredentialGuardResult } from "./launcher/guard";
|
|
4
|
+
import type { ConfigProfileDecisionSource, IdentityDecisionSource } from "./launcher/identity";
|
|
5
|
+
import type { FarmFs, RunPort } from "./launcher/ports";
|
|
6
|
+
import type { LayoutPaths } from "./paths";
|
|
7
|
+
import type { PoolPickReport } from "./poolPickReport";
|
|
8
|
+
import { type EncodingAmbiguity } from "./resolve/projects";
|
|
9
|
+
import { type ResolvedState } from "./resolve/pipeline";
|
|
10
|
+
import type { CascadeInput } from "./resolve/walk";
|
|
11
|
+
import type { Decision, EntryFacts, FlattenedCascade } from "./resolve/types";
|
|
12
|
+
/**
|
|
13
|
+
* Flags every `history/projects/` entries rule in scope whose encoded form could plausibly correspond to more than one real path.
|
|
14
|
+
*
|
|
15
|
+
* Reuses `src/resolve/projects.ts`'s own `detectEncodingAmbiguity` rather than reimplementing the detection — this function's whole job is recovering the as-written fragments from the flattened cascade's compiled rules and handing them to that function, plus the real project directory names already present in the fact manifest so the report can say how many a pattern actually matches today.
|
|
16
|
+
*/
|
|
17
|
+
export declare function flagAmbiguousEncodings(flattened: FlattenedCascade, facts: EntryFacts): EncodingAmbiguity[];
|
|
18
|
+
/** Renders one entry's resolved decision as a single explanatory line, for `agent-shim check`'s printout. */
|
|
19
|
+
export declare function formatDecision(decision: Decision): string;
|
|
20
|
+
/** The result of looking up the macOS Keychain service name Claude Code is actually using for one identity's farm. */
|
|
21
|
+
export interface KeychainLookupResult {
|
|
22
|
+
readonly checked: true;
|
|
23
|
+
readonly found: boolean;
|
|
24
|
+
readonly serviceName?: string;
|
|
25
|
+
readonly note: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Looks up the macOS Keychain entry Claude Code is using for one identity's configuration directory, via `security find-generic-password`, run through the injected `RunPort` so no test ever shells out for real.
|
|
29
|
+
*
|
|
30
|
+
* This is real OS state: the exact account/service naming Claude Code uses in the Keychain is empirically observed (per this project's README), not a documented contract, so genuinely exercising this against a real Keychain is a manual/integration check on a macOS runner, never something a unit test fakes convincingly — a unit test here can only prove that this function parses `security`'s own output shape correctly, not that the shape matches what a real installation produces.
|
|
31
|
+
*/
|
|
32
|
+
export declare function lookupKeychainService(run: RunPort, farmRoot: string): KeychainLookupResult;
|
|
33
|
+
/** One file's reported settings exposure: names and counts only, never the underlying values. */
|
|
34
|
+
export interface SettingsExposureReport {
|
|
35
|
+
readonly file: string;
|
|
36
|
+
readonly envKeyNames: readonly string[];
|
|
37
|
+
readonly hookEventNames: readonly string[];
|
|
38
|
+
readonly hookCommandCount: number;
|
|
39
|
+
}
|
|
40
|
+
/** Inputs to `inspectSettingsExposure`. */
|
|
41
|
+
export interface InspectSettingsExposureParams {
|
|
42
|
+
/** Whether the `settings` category resolves shared for this launch — the advisory only has anything to report when it does. */
|
|
43
|
+
readonly settingsShared: boolean;
|
|
44
|
+
/** Raw file contents keyed by filename (`settings.json`, `settings.local.json`), undefined when the file does not exist. */
|
|
45
|
+
readonly files: Readonly<Record<string, string | undefined>>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Reports how many `env` keys and `hooks` commands `settings.json`/`settings.local.json` would share, by name and count only — never a value, per the README's own warning that a hook command or an `env` entry there can easily hold a real secret with nothing in Claude Code's own documentation warning against it.
|
|
49
|
+
*
|
|
50
|
+
* Returns nothing at all when `settingsShared` is false, and nothing for a file whose `env`/`hooks` fields are both empty or absent — the whole point of this advisory is to be silent unless there is something worth a second look.
|
|
51
|
+
*/
|
|
52
|
+
export declare function inspectSettingsExposure(params: InspectSettingsExposureParams): SettingsExposureReport[];
|
|
53
|
+
/** The provider the cascade selects for this directory, as the wiring layer loaded it: its definition, or why it could not be used (no such provider, an invalid or old-format file). */
|
|
54
|
+
type CheckProviderInput = {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly definition: Provider;
|
|
57
|
+
readonly problem?: never;
|
|
58
|
+
} | {
|
|
59
|
+
readonly name: string;
|
|
60
|
+
readonly definition?: never;
|
|
61
|
+
readonly problem: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Which credential a launch here would use, by source kind and target only: a selected provider's credential block always wins; otherwise the identity's own credential block; otherwise the login stored in the identity's directory.
|
|
65
|
+
*/
|
|
66
|
+
interface CredentialReport {
|
|
67
|
+
readonly applies: "provider" | "identity" | "stored-login";
|
|
68
|
+
readonly provider?: {
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly credential?: CredentialSummary;
|
|
71
|
+
readonly problem?: string;
|
|
72
|
+
};
|
|
73
|
+
readonly identity?: CredentialSummary;
|
|
74
|
+
}
|
|
75
|
+
/** Inputs to `runCheck` — everything already loaded/injected, exactly like the resolver core and the launcher: nothing in this function touches a real filesystem, git repository, clock, or environment itself. */
|
|
76
|
+
export interface RunCheckParams {
|
|
77
|
+
readonly cwd: string;
|
|
78
|
+
readonly home: string;
|
|
79
|
+
readonly claudeHome: string;
|
|
80
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
81
|
+
readonly branch?: string;
|
|
82
|
+
readonly branchDetached?: boolean;
|
|
83
|
+
readonly nowMs: number;
|
|
84
|
+
/** Used only to build the fact manifest by reading the canonical `~/.claude` tree — `runCheck` never mutates the farm and never spawns anything. */
|
|
85
|
+
readonly farmFs: FarmFs;
|
|
86
|
+
readonly cascade: CascadeInput;
|
|
87
|
+
readonly classification: {
|
|
88
|
+
readonly defaults: CategoryClassification;
|
|
89
|
+
readonly overlay?: CategoryClassificationOverlay;
|
|
90
|
+
};
|
|
91
|
+
readonly identityName?: string;
|
|
92
|
+
readonly identitySource: IdentityDecisionSource;
|
|
93
|
+
/** The pool the launch selected and how it ranks right now, when it named a pool instead of an identity; `identityName` is then the member it would pick. */
|
|
94
|
+
readonly poolPick?: PoolPickReport;
|
|
95
|
+
readonly configProfileName?: string;
|
|
96
|
+
readonly configProfileSource: ConfigProfileDecisionSource;
|
|
97
|
+
/** The resolved identity's own `identity.json`, when one was found. */
|
|
98
|
+
readonly identity?: Identity;
|
|
99
|
+
/** Raw `settings.json`/`settings.local.json` contents, keyed by filename, undefined when a file does not exist. */
|
|
100
|
+
readonly settingsFiles: Readonly<Record<string, string | undefined>>;
|
|
101
|
+
/** Runs `security find-generic-password` for the Keychain diagnostic. Omit to skip that diagnostic entirely (e.g. off macOS, or when no identity/farm root is known). */
|
|
102
|
+
readonly run?: RunPort;
|
|
103
|
+
/** The identity's own farm root — `security`'s lookup account. Required alongside `run` for the Keychain diagnostic to run at all. */
|
|
104
|
+
readonly farmRoot?: string;
|
|
105
|
+
/** `process.platform` in real use; the Keychain diagnostic only ever runs when this is `"darwin"`. */
|
|
106
|
+
readonly platform: string;
|
|
107
|
+
/** The provider a launch here would route through (`launch.provider` in the cascade), when one is selected. */
|
|
108
|
+
readonly provider?: CheckProviderInput;
|
|
109
|
+
}
|
|
110
|
+
/** Everything `agent-shim check` reports about one directory/identity, without touching the farm or spawning anything. */
|
|
111
|
+
export interface CheckReport {
|
|
112
|
+
readonly identityName?: string;
|
|
113
|
+
readonly identitySource: IdentityDecisionSource;
|
|
114
|
+
readonly poolPick?: PoolPickReport;
|
|
115
|
+
readonly configProfileName?: string;
|
|
116
|
+
readonly configProfileSource: ConfigProfileDecisionSource;
|
|
117
|
+
readonly resolved: ResolvedState;
|
|
118
|
+
readonly decisionLines: readonly string[];
|
|
119
|
+
readonly projectEncodingAmbiguities: readonly EncodingAmbiguity[];
|
|
120
|
+
readonly ambientCredential: AmbientCredentialGuardResult;
|
|
121
|
+
readonly keychain?: KeychainLookupResult;
|
|
122
|
+
readonly settingsExposure: readonly SettingsExposureReport[];
|
|
123
|
+
readonly credential: CredentialReport;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolves the full cascade for one directory/identity and reports everything `agent-shim check` documents: every entry's resolved state and which layer/condition decided it, any ambiguous `history/projects/` encoding in scope, and the three always-on diagnostics (ambient-credential exposure, macOS Keychain service name, settings-secrets exposure).
|
|
127
|
+
*
|
|
128
|
+
* Deliberately reuses the same cascade machinery a real launch uses — `resolveDecisions` and `buildEntryFacts` — rather than reimplementing any part of resolution. The one thing this function never does that a launch does is touch the farm or spawn anything: it only reads the canonical `~/.claude` tree to build the fact manifest resolution needs, and every other input (cascade, classification, settings file contents, the Keychain lookup) is handed in already loaded.
|
|
129
|
+
*/
|
|
130
|
+
export declare function runCheck(params: RunCheckParams): CheckReport;
|
|
131
|
+
/** Renders a full `CheckReport` as plain text lines, in the order `agent-shim check` prints them. */
|
|
132
|
+
export declare function formatCheckReport(report: CheckReport): string[];
|
|
133
|
+
/**
|
|
134
|
+
* The `check --json` form of a report: every field the text form prints, as plain data (no `Map`s, no compiled matchers), so a script can read the same verdicts a person would.
|
|
135
|
+
*/
|
|
136
|
+
export declare function checkReportToJson(report: CheckReport): Record<string, unknown>;
|
|
137
|
+
/**
|
|
138
|
+
* Whether a report carries anything `check --strict` fails on: a resolver diagnostic of warning or error severity, an ambiguous `history/projects/` encoding, an ambient credential a launch would refuse, or a selected provider a launch could not use. Informational diagnostics and the settings-exposure advisory (names and counts only, for a person to review) never fail it.
|
|
139
|
+
*/
|
|
140
|
+
export declare function checkReportHasWarnings(report: CheckReport): boolean;
|
|
141
|
+
/** What `collectCheckReport` needs from its caller. */
|
|
142
|
+
export interface CollectCheckReportParams {
|
|
143
|
+
readonly paths: LayoutPaths;
|
|
144
|
+
/** The directory a launch would run in; resolved against the working directory by the caller. */
|
|
145
|
+
readonly cwd: string;
|
|
146
|
+
/** The identity to check, as `--identity` names it; absent means the identity a launch there would resolve. */
|
|
147
|
+
readonly identity?: string;
|
|
148
|
+
readonly env: NodeJS.ProcessEnv;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Resolves what a launch in `params.cwd` would share or hide, and why, against this machine: the edge adapter that reads the real filesystem, clock, git and keychain, resolves the identity, configuration profile and cascade exactly as a launch does, and hands the facts to the pure `runCheck`. Never touches the farm or spawns claude.
|
|
152
|
+
*/
|
|
153
|
+
export declare function collectCheckReport(params: CollectCheckReportParams): CheckReport;
|
|
154
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { type CommandDeps } from "./cli/commandDeps";
|
|
4
|
+
import { CliError } from "./cliError";
|
|
5
|
+
import type { LayoutPaths } from "./paths";
|
|
6
|
+
export declare const ClaudeShimStateSchema: z.ZodObject<{
|
|
7
|
+
targetPath: z.ZodString;
|
|
8
|
+
method: z.ZodEnum<{
|
|
9
|
+
copy: "copy";
|
|
10
|
+
hardlink: "hardlink";
|
|
11
|
+
}>;
|
|
12
|
+
installedAtMs: z.ZodNumber;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
export type ClaudeShimState = z.infer<typeof ClaudeShimStateSchema>;
|
|
15
|
+
/** Raised by enableClaudeShim/disableClaudeShim when the target path exists but doesn't look like agent-shim's own doing (no matching claude-shim.json marker, and not a hardlink of the currently-running executable) — refuses rather than silently clobbering or deleting something an installer never created. Bypassed by --force. */
|
|
16
|
+
export declare class ForeignClaudeEntryError extends CliError {
|
|
17
|
+
readonly targetPath: string;
|
|
18
|
+
readonly action: "enable" | "disable";
|
|
19
|
+
constructor(targetPath: string, action: "enable" | "disable");
|
|
20
|
+
}
|
|
21
|
+
/** Raised by enableClaudeShim when the running executable cannot possibly be turned into a directly-runnable `claude` command: an npm/Node install of agent-shim on Windows, which has no bundled .exe and no shebang-based dispatch the way POSIX does. Not bypassed by --force — this isn't a safety refusal, it's "this would produce a broken file." */
|
|
22
|
+
export declare class UnsupportedShimSourceError extends CliError {
|
|
23
|
+
readonly contentSourcePath: string;
|
|
24
|
+
constructor(contentSourcePath: string);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The exact filename to create: `claude.exe` when the running executable is itself a `.exe` (Windows needs the extension to recognise it as directly executable at all), `claude` for every other case.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately not "preserve whatever extension the source has" — an npm install's own file is `cli.cjs`, and a bare `claude` (not `claude.cjs`) is what makes it invocable as the expected command on POSIX, where a shebang plus the executable bit is what matters, not the filename's extension.
|
|
30
|
+
*/
|
|
31
|
+
export declare function claudeTargetFilename(ownExecutablePath: string): string;
|
|
32
|
+
/** The same rule generalised to any command name this tool owns, so `doctor`'s PATH-resolution check can ask "what filename would a bare `agent-shim` be" without restating the Windows `.exe` condition and letting the two drift apart. */
|
|
33
|
+
export declare function commandFilename(ownExecutablePath: string, commandName: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* `cli.ts`'s own name-dispatch check: is `invokedName` (the basename of however this binary was invoked) one of the two names `shim enable` ever creates, per `claudeTargetFilename` above? `path.basename` includes the file extension, so a bare `=== "claude"` comparison never matches the Windows case, where the file must be named `claude.exe` to be directly invocable at all.
|
|
36
|
+
*
|
|
37
|
+
* Confirmed as a real, previously-undetected bug against an actual Scoop install: the shim-placement and hardlink-source bugs fixed in prior releases had never let a Windows `claude.exe` invocation reach this far before, so this exact mismatch never surfaced until the shim itself finally worked end-to-end — invoking the newly-created `claude.exe` fell through to agent-shim's own CLI (printing its own `--version`) instead of the launcher.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isInvokedAsClaude(invokedName: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The directories `discoverClaudeBinary`'s PATH-fallback search must exclude to avoid recursively discovering/spawning this very tool: wherever the running executable itself lives, plus wherever `shim enable` last placed a `claude`-named copy, when that's a *different* directory (i.e. `--dir` was used). Without folding the recorded shim directory in here too, `agent-shim run`/`agent-shim doctor` would only exclude their own directory, not a `--dir`-placed shim living elsewhere on PATH.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveOwnInstallDirs(paths: LayoutPaths, ownExecutablePath: string): string[];
|
|
44
|
+
/** Where `shim enable` places `claude` absent a --dir override: alongside the running executable. */
|
|
45
|
+
export declare function resolveClaudeTargetPath(ownExecutablePath: string, dir?: string): string;
|
|
46
|
+
/** The two low-level fs calls the hardlink-then-copy-fallback needs, matching `src/config/store.ts`'s own `StoreFs`/`nodeStoreFs`-with-a-real-default pattern — the one seam in this file that isn't plain `node:fs`, added so the EXDEV fallback branch gets real unit coverage without a genuine cross-filesystem test rig. */
|
|
47
|
+
export interface LinkFs {
|
|
48
|
+
readonly link: (existingPath: string, newPath: string) => void;
|
|
49
|
+
readonly copyFile: (src: string, dest: string) => void;
|
|
50
|
+
readonly chmod: (target: string, mode: number) => void;
|
|
51
|
+
}
|
|
52
|
+
/** Inputs to `enableClaudeShim`. */
|
|
53
|
+
export interface EnableShimParams {
|
|
54
|
+
readonly paths: LayoutPaths;
|
|
55
|
+
/** `realOwnExecutablePath()` (see realPorts.ts) — the PATH-visible location `claude` should be placed alongside, supplied by the wiring layer. Deliberately separate from `contentSourcePath` below: for a Scoop install, this is `~/scoop/shims/agent-shim.exe` (Scoop's own proxy binary, not agent-shim's real content), and using it as the link/copy source would hardlink that proxy instead of the genuine executable. */
|
|
56
|
+
readonly ownExecutablePath: string;
|
|
57
|
+
/** `realContentSourcePath()` (see realPorts.ts) — the actual file whose content is this running process, used as the hardlink/copy source and for "is the existing target mine" inode comparisons. */
|
|
58
|
+
readonly contentSourcePath: string;
|
|
59
|
+
/** process.platform, supplied by the wiring layer — only used for the Windows/npm-source guard. */
|
|
60
|
+
readonly platform: string;
|
|
61
|
+
readonly dir?: string;
|
|
62
|
+
readonly force: boolean;
|
|
63
|
+
}
|
|
64
|
+
type EnableShimAction = "enabled" | "reenabled";
|
|
65
|
+
export interface EnableShimResult {
|
|
66
|
+
readonly action: EnableShimAction;
|
|
67
|
+
readonly targetPath: string;
|
|
68
|
+
readonly method: "hardlink" | "copy";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a `claude`-named hardlink (falling back to a copy on a cross-device filesystem) of the currently running `agent-shim` executable, so `claude @<name>` works directly instead of needing `agent-shim run`.
|
|
72
|
+
*
|
|
73
|
+
* A target that already exists is only ever overwritten when it's provably agent-shim's own doing — either it shares an inode with the currently-running executable (covers a pre-existing install from before this feature existed, where no marker could possibly exist yet), or claude-shim.json records this exact target path (covers refreshing after a agent-shim upgrade, when the old target's inode no longer matches the new binary's content). Anything else is refused unless `force` is set — this could otherwise silently delete or overwrite someone's genuine, unrelated `claude` binary.
|
|
74
|
+
*/
|
|
75
|
+
export declare function enableClaudeShim(params: EnableShimParams, linkFs?: LinkFs): EnableShimResult;
|
|
76
|
+
type DisableShimAction = "disabled" | "not-enabled";
|
|
77
|
+
export interface DisableShimResult {
|
|
78
|
+
readonly action: DisableShimAction;
|
|
79
|
+
readonly targetPath: string;
|
|
80
|
+
}
|
|
81
|
+
export interface DisableShimParams {
|
|
82
|
+
readonly paths: LayoutPaths;
|
|
83
|
+
/** `realOwnExecutablePath()` — the PATH-visible location, used to derive the default target path. */
|
|
84
|
+
readonly ownExecutablePath: string;
|
|
85
|
+
/** `realContentSourcePath()` — used for the "is the existing target mine" inode comparison; see `EnableShimParams` for why this must be separate from `ownExecutablePath`. */
|
|
86
|
+
readonly contentSourcePath: string;
|
|
87
|
+
readonly dir?: string;
|
|
88
|
+
readonly force: boolean;
|
|
89
|
+
}
|
|
90
|
+
/** The inverse of `enableClaudeShim`: removes the `claude` command it created, using the same "is this ours" safety check. A no-op, not an error, when nothing is enabled — including clearing a dangling marker whose target has since been deleted by other means. */
|
|
91
|
+
export declare function disableClaudeShim(params: DisableShimParams): DisableShimResult;
|
|
92
|
+
export type PathShadowStatus = {
|
|
93
|
+
readonly status: "ok";
|
|
94
|
+
} | {
|
|
95
|
+
readonly status: "not-on-path";
|
|
96
|
+
} | {
|
|
97
|
+
readonly status: "shadowed";
|
|
98
|
+
readonly by: string;
|
|
99
|
+
};
|
|
100
|
+
export interface FindPathShadowParams {
|
|
101
|
+
readonly pathDirs: readonly string[];
|
|
102
|
+
readonly targetDir: string;
|
|
103
|
+
readonly targetFilename: string;
|
|
104
|
+
readonly findExecutableInDir: (dir: string, name: string) => string | undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Would something else on PATH run before the just-enabled `claude`? Reuses the exact PATH-scan semantics `discoverClaudeBinary`'s own fallback already uses (via the injected `findExecutableInDir`), rather than shelling out to `command -v` — this answers a more directly relevant question ("would this tool's own binary discovery find this file first") without spawning a process at all. */
|
|
107
|
+
export declare function findPathShadow(params: FindPathShadowParams): PathShadowStatus;
|
|
108
|
+
/**
|
|
109
|
+
* Registers `agent-shim shim enable`/`agent-shim shim disable` onto `program`.
|
|
110
|
+
*
|
|
111
|
+
* `shim` is a deliberate noun-group, matching `identity`/`profile`/`rule`: it names the actual mechanism (a PATH-level executable that dispatches by invoked name, the same concept Scoop itself calls a "shim"), so `enable`/`disable` read as toggling one clearly-scoped thing rather than "installing"/"uninstalling" software, which could be misread as installing Claude Code itself.
|
|
112
|
+
*/
|
|
113
|
+
export declare function registerShimCommand(program: Command, deps: CommandDeps): void;
|
|
114
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { PromptsPort } from "../configure";
|
|
3
|
+
import type { LayoutPaths } from "../paths";
|
|
4
|
+
/**
|
|
5
|
+
* What every `agent-shim` command's registration needs from `buildProgram`: where the config tree lives, the prompt surface, whether standard input is a terminal those prompts can run on, and how to end the process.
|
|
6
|
+
*
|
|
7
|
+
* `isInteractive` is a function rather than a snapshot so the real wiring reads `process.stdin.isTTY` at the moment a command runs, and a test can script both answers against the same program.
|
|
8
|
+
*/
|
|
9
|
+
export interface CommandDeps {
|
|
10
|
+
readonly paths: LayoutPaths;
|
|
11
|
+
readonly prompts: PromptsPort;
|
|
12
|
+
readonly isInteractive: () => boolean;
|
|
13
|
+
/** Ends the process with `code` immediately. Only the long-running `__headroom-supervisor` and `__codex-supervisor` need it (a signal must terminate it, and its servers would otherwise keep the event loop alive); every other command sets `process.exitCode` or throws, so its output is never truncated. */
|
|
14
|
+
readonly exit: (code: number) => never;
|
|
15
|
+
}
|
|
16
|
+
/** Prints `value` as indented JSON on standard output: the one `--json` output format every `list`, `show`, `check`, `doctor` and `headroom status` command shares. */
|
|
17
|
+
export declare function printJson(value: unknown): void;
|
|
18
|
+
/**
|
|
19
|
+
* Appends an `Examples:` block to `command`'s help output, one `$ agent-shim ...` line per entry. Every command registers at least one, so `--help` on any of them shows a runnable invocation, not just its option list.
|
|
20
|
+
*/
|
|
21
|
+
export declare function withExamples(command: Command, examples: readonly string[]): Command;
|
|
22
|
+
/**
|
|
23
|
+
* The confirmation every destructive `remove` verb shares. `--yes` skips it; otherwise a terminal gets a yes/no prompt (declining throws `PromptCancelledError`), and a non-terminal throws `MissingInputError` naming `--yes`, since deleting without either would be a silent destructive default.
|
|
24
|
+
* @param deps - The prompt surface and terminal check.
|
|
25
|
+
* @param yes - Whether `--yes` was passed.
|
|
26
|
+
* @param description - What would be removed, phrased to follow "Remove ", e.g. `identity "work" and its directory`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function confirmRemoval(deps: CommandDeps, yes: boolean | undefined, description: string): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { UsageError } from "../cliError";
|
|
2
|
+
/** Raised when a boolean environment variable holds something outside the shared vocabulary. */
|
|
3
|
+
export declare class InvalidEnvBoolError extends UsageError {
|
|
4
|
+
readonly variable: string;
|
|
5
|
+
readonly value: string;
|
|
6
|
+
constructor(variable: string, value: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 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.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseEnvBool(variable: string, value: string | undefined): boolean | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The HTTP statuses the codex route and its listener answer with, named so every use says what it means. */
|
|
2
|
+
export declare const HTTP_STATUS: {
|
|
3
|
+
readonly ok: 200;
|
|
4
|
+
readonly badRequest: 400;
|
|
5
|
+
readonly unauthorized: 401;
|
|
6
|
+
readonly forbidden: 403;
|
|
7
|
+
readonly notFound: 404;
|
|
8
|
+
readonly methodNotAllowed: 405;
|
|
9
|
+
readonly payloadTooLarge: 413;
|
|
10
|
+
readonly tooManyRequests: 429;
|
|
11
|
+
/** Not sent to anyone: the client is gone. Recorded for a response abandoned because the client disconnected, following the convention nginx uses for the same case. */
|
|
12
|
+
readonly clientClosedRequest: 499;
|
|
13
|
+
readonly internalServerError: 500;
|
|
14
|
+
readonly badGateway: 502;
|
|
15
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type CommandDeps } from "./cli/commandDeps";
|
|
3
|
+
import { CliError, UsageError } from "./cliError";
|
|
4
|
+
import type { LogPort } from "./launcher/ports";
|
|
5
|
+
import { type LayoutPaths } from "./paths";
|
|
6
|
+
/** Raised by `configure` when no configuration profile resolves for the identity at the working directory, so there is no tier-three file for a toggle to land in. */
|
|
7
|
+
export declare class NoConfigProfileResolvedError extends CliError {
|
|
8
|
+
readonly identityName: string;
|
|
9
|
+
readonly cwd: string;
|
|
10
|
+
constructor(identityName: string, cwd: string);
|
|
11
|
+
}
|
|
12
|
+
/** Raised by `configure` when standard input is not a terminal: every step of it is a prompt, so there is no non-interactive form to fall back to. */
|
|
13
|
+
export declare class ConfigureNeedsTerminalError extends UsageError {
|
|
14
|
+
constructor();
|
|
15
|
+
}
|
|
16
|
+
/** Raised by `configure` when no `--identity` was given and nothing else (the environment, a directory pin, the active identity) resolves one. */
|
|
17
|
+
export declare class NoIdentityResolvedError extends UsageError {
|
|
18
|
+
readonly cwd: string;
|
|
19
|
+
constructor(cwd: string);
|
|
20
|
+
}
|
|
21
|
+
/** One selectable option, shared by `select` and `multiselect`. */
|
|
22
|
+
interface PromptOption<Value extends string> {
|
|
23
|
+
readonly value: Value;
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly hint?: string;
|
|
26
|
+
/** Visible but not selectable — used for the `secret` category, which no configuration layer may ever toggle. */
|
|
27
|
+
readonly disabled?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Parameters shared by a single-select prompt. */
|
|
30
|
+
export interface SelectParams<Value extends string> {
|
|
31
|
+
readonly message: string;
|
|
32
|
+
readonly options: readonly PromptOption<Value>[];
|
|
33
|
+
/** Which option the cursor starts on. Omit to let the prompt library fall back to its own default (the first option) — pass this explicitly whenever the safest or most common answer isn't the first one listed, so the on-screen reading order and the default-on-Enter answer can differ deliberately rather than being forced to match. */
|
|
34
|
+
readonly initialValue?: Value;
|
|
35
|
+
}
|
|
36
|
+
/** Parameters for a multi-select prompt. */
|
|
37
|
+
export interface MultiselectParams<Value extends string> {
|
|
38
|
+
readonly message: string;
|
|
39
|
+
readonly options: readonly PromptOption<Value>[];
|
|
40
|
+
readonly initialValues?: readonly Value[];
|
|
41
|
+
}
|
|
42
|
+
/** Parameters for a free-text prompt. */
|
|
43
|
+
export interface TextParams {
|
|
44
|
+
readonly message: string;
|
|
45
|
+
readonly placeholder?: string;
|
|
46
|
+
/** Returns an error string to reject the value and re-prompt, or `undefined` to accept it. */
|
|
47
|
+
readonly validate?: (value: string) => string | undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The interactive-prompt surface `runConfigure` needs, abstracted so it can be driven by a scripted sequence of pre-programmed answers in tests instead of a real TTY — the same injected-ports pattern `src/launcher/ports.ts` already uses for the filesystem, spawn, and clock.
|
|
51
|
+
*
|
|
52
|
+
* `select`/`multiselect`/`text` resolve to a `symbol` when the user cancels (Ctrl-C), mirroring `@clack/prompts`' own cancellation contract exactly, so `isCancel` is the one place that distinguishes a real answer from a cancellation.
|
|
53
|
+
*/
|
|
54
|
+
export interface PromptsPort {
|
|
55
|
+
readonly select: <Value extends string>(params: SelectParams<Value>) => Promise<Value | symbol>;
|
|
56
|
+
readonly multiselect: <Value extends string>(params: MultiselectParams<Value>) => Promise<readonly Value[] | symbol>;
|
|
57
|
+
readonly text: (params: TextParams) => Promise<string | symbol>;
|
|
58
|
+
/** A type guard, mirroring `@clack/prompts`' own `isCancel` exactly, so a passing check narrows `Value | symbol` down to `Value` at every call site without a further cast. */
|
|
59
|
+
readonly isCancel: (value: unknown) => value is symbol;
|
|
60
|
+
readonly cancel: (message?: string) => void;
|
|
61
|
+
readonly intro: (message?: string) => void;
|
|
62
|
+
readonly outro: (message?: string) => void;
|
|
63
|
+
}
|
|
64
|
+
export declare const realPromptsPort: PromptsPort;
|
|
65
|
+
/** Where one toggle should be written, per the README's "agent-shim configure: which file it writes to" precedence. */
|
|
66
|
+
export type WriteTarget = {
|
|
67
|
+
readonly tier: "portable-local";
|
|
68
|
+
readonly localConfigPath: string;
|
|
69
|
+
} | {
|
|
70
|
+
readonly tier: "directory-rule";
|
|
71
|
+
readonly rulePath: string;
|
|
72
|
+
} | {
|
|
73
|
+
readonly tier: "config-profile";
|
|
74
|
+
readonly profileName: string;
|
|
75
|
+
};
|
|
76
|
+
/** One directory level's portable-config presence, shallowest-first — as produced by `walkDirectoryAncestors` plus a filesystem existence check at each level. */
|
|
77
|
+
export interface DirectoryLevelPresence {
|
|
78
|
+
readonly dir: string;
|
|
79
|
+
/** Whether a committed `.agent-shim.json` already exists at this level. */
|
|
80
|
+
readonly hasPortable: boolean;
|
|
81
|
+
/** Whether a gitignored `.agent-shim.local.json` already exists at this level, independent of whether a committed sibling does — this is what makes tier one apply even when only a personal local override was ever created, with no committed file alongside it. */
|
|
82
|
+
readonly hasPortableLocal: boolean;
|
|
83
|
+
/** The personal local file a toggle at this level is written into: the former `.claude-use.local.json` when that is the only one that exists, so its settings are not shadowed by a new file, otherwise `.agent-shim.local.json`. */
|
|
84
|
+
readonly portableLocalFilename: string;
|
|
85
|
+
}
|
|
86
|
+
/** Inputs to `chooseWriteTarget`. */
|
|
87
|
+
export interface ChooseWriteTargetParams {
|
|
88
|
+
readonly cwd: string;
|
|
89
|
+
readonly home: string;
|
|
90
|
+
/** `cwd`'s ancestor levels, shallowest-first, each flagged with whether a portable/portable-local file already exists there. */
|
|
91
|
+
readonly levels: readonly DirectoryLevelPresence[];
|
|
92
|
+
/** Every directory rule's own `path` field, exactly as written in `~/.agent-shim/directory-rules.json` (not yet normalised). */
|
|
93
|
+
readonly directoryRulePaths: readonly string[];
|
|
94
|
+
/** The identity's resolved active configuration profile — the tier-three fallback. */
|
|
95
|
+
readonly activeConfigProfile: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Decides which file a `configure` toggle should be written into, in the exact three-tier precedence the README documents:
|
|
99
|
+
*
|
|
100
|
+
* 1. If `$PWD` is inside a directory covered by a committed `.agent-shim.json`, or a `.agent-shim.local.json` already exists there (even without a committed sibling), the toggle goes into `.agent-shim.local.json` in that same directory — the deepest such directory, when more than one ancestor qualifies.
|
|
101
|
+
* 2. Otherwise, if an existing directory rule in the user's own `directory-rules.json` already applies to `$PWD` (its `path`, once resolved, is an ancestor of or equal to `$PWD`), the toggle is written into that rule — the most specific (longest-resolved-path) matching rule, when more than one applies.
|
|
102
|
+
* 3. Otherwise, the toggle is written into the identity's own active configuration profile.
|
|
103
|
+
*
|
|
104
|
+
* Never touches a filesystem or a config file itself — every input is already gathered by the caller, which is what makes this function directly unit-testable with synthetic levels and rule paths.
|
|
105
|
+
*/
|
|
106
|
+
export declare function chooseWriteTarget(params: ChooseWriteTargetParams): WriteTarget;
|
|
107
|
+
/** Renders a `WriteTarget` as a short, human-readable description for `configure`'s own confirmation output. */
|
|
108
|
+
export declare function describeWriteTarget(target: WriteTarget): string;
|
|
109
|
+
/** Everything `runConfigure` needs beyond the identity/path the user typed. */
|
|
110
|
+
export interface RunConfigureDeps {
|
|
111
|
+
readonly paths: LayoutPaths;
|
|
112
|
+
readonly prompts: PromptsPort;
|
|
113
|
+
readonly log: Pick<LogPort, "info">;
|
|
114
|
+
}
|
|
115
|
+
/** Inputs to `runConfigure` beyond its injected dependencies. */
|
|
116
|
+
export interface RunConfigureParams {
|
|
117
|
+
readonly identityName: string;
|
|
118
|
+
/** A real `~/.claude`-relative path (never category-prefixed — that prefix is an entries-*key* convention, not a filesystem path). Given: entries mode. Omitted: categories mode. */
|
|
119
|
+
readonly path?: string;
|
|
120
|
+
readonly cwd: string;
|
|
121
|
+
readonly home: string;
|
|
122
|
+
readonly claudeHome: string;
|
|
123
|
+
}
|
|
124
|
+
/** Validates a candidate profile name for the wizard's text prompt: non-empty, matches the allowed shape, and (only when `existingNames` is given) not already taken. */
|
|
125
|
+
export declare function validateProfileName(value: string, existingNames?: readonly string[]): string | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Inputs to `runProfileWizard` beyond the injected prompt/log ports. Split out so the launch path (which only has a `LayoutPaths`, not a full `ConfigureContext`) can call the wizard too.
|
|
128
|
+
*
|
|
129
|
+
* A discriminated union over three modes:
|
|
130
|
+
* - `{ existingName }`: edit an existing profile's categories.
|
|
131
|
+
* - `{ createName }`: create a new profile with this exact name (no name prompt), then choose categories. Used by the launch path, where the name is already decided.
|
|
132
|
+
* - `{ defaultNewName? }`: create a new profile, prompting for a name first (with an optional pre-filled default). Used by the manual commands.
|
|
133
|
+
*/
|
|
134
|
+
export type RunProfileWizardParams = {
|
|
135
|
+
readonly paths: LayoutPaths;
|
|
136
|
+
readonly existingName: string;
|
|
137
|
+
} | {
|
|
138
|
+
readonly paths: LayoutPaths;
|
|
139
|
+
readonly createName: string;
|
|
140
|
+
} | {
|
|
141
|
+
readonly paths: LayoutPaths;
|
|
142
|
+
readonly defaultNewName?: string;
|
|
143
|
+
};
|
|
144
|
+
/** What `runProfileWizard` produced. */
|
|
145
|
+
export interface ProfileWizardResult {
|
|
146
|
+
readonly name: string;
|
|
147
|
+
readonly created: boolean;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The unified create-or-edit flow for a configuration profile: one guided prompt sequence covering both cases that would otherwise split across a bare `profile add` (empty, no categories) and `configure`'s "edit a profile directly" branch (categories only, profile must already exist).
|
|
151
|
+
*
|
|
152
|
+
* Given an `existingName`: edits that profile's categories in place via a multiselect, seeded from its current values. Given a `createName`: creates the empty file with that exact name, no name prompt, then runs the category multiselect. Given neither: prompts for a name first (validated against the allowed shape and against every existing profile name), creates the empty file, then runs the same multiselect. Either way, a cancel at any prompt writes nothing and returns `undefined` — the caller decides what to do (e.g. the launch path proceeds without the profile, the manual command just stops).
|
|
153
|
+
*
|
|
154
|
+
* Driven entirely by the injected `PromptsPort` so the whole flow is unit-testable with a scripted sequence of answers, the same way `runConfigure` already is.
|
|
155
|
+
*/
|
|
156
|
+
export declare function runProfileWizard(prompts: PromptsPort, params: RunProfileWizardParams): Promise<ProfileWizardResult | undefined>;
|
|
157
|
+
/**
|
|
158
|
+
* Runs `agent-shim configure [path] --identity <name>`'s interactive flow.
|
|
159
|
+
*
|
|
160
|
+
* Two modes, exactly per the README's "agent-shim configure: which file it writes to" section:
|
|
161
|
+
*
|
|
162
|
+
* - No `path`: shows the identity's resolved top-level categories (the only mode that ever touches categories), plus an option to edit a named configuration profile's own stored values directly instead.
|
|
163
|
+
* - Given a `path` (a real `~/.claude`-relative path, never category-prefixed): shows that path's direct children with their resolved sharing state, for fine-grained entries overrides. Never shows or edits categories.
|
|
164
|
+
*
|
|
165
|
+
* Every actual toggle (in either mode, other than the "edit a profile directly" branch, which is an explicit target the user chose) is written via `chooseWriteTarget`'s three-tier precedence.
|
|
166
|
+
*/
|
|
167
|
+
export declare function runConfigure(deps: RunConfigureDeps, params: RunConfigureParams): Promise<void>;
|
|
168
|
+
/** Registers `agent-shim configure [path] [--identity <name>]` onto `program`. */
|
|
169
|
+
export declare function registerConfigureCommand(program: Command, deps: CommandDeps): void;
|
|
170
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type ClaudeShimState, type PathShadowStatus } from "./claudeShim";
|
|
2
|
+
import type { LayoutPaths } from "./paths";
|
|
3
|
+
import type { RunPort } from "./launcher/ports";
|
|
4
|
+
import type { DiscoveredClaudeBinary } from "./versionDiscovery";
|
|
5
|
+
type DoctorSeverity = "pass" | "warn" | "fail";
|
|
6
|
+
type DoctorSection = "ambient-credential" | "binary-discovery" | "claude-shim" | "path-resolution" | "legacy-name" | "config-profile" | "identity" | "pool" | "provider" | "keychain" | "directory-rules" | "global-config" | "categories-local" | "active-identity" | "headroom";
|
|
7
|
+
/** One line of `agent-shim doctor`'s report. `subject` names the identity/profile/rule the finding is about, when the section has more than one of those. */
|
|
8
|
+
interface DoctorFinding {
|
|
9
|
+
readonly section: DoctorSection;
|
|
10
|
+
readonly subject?: string;
|
|
11
|
+
readonly severity: DoctorSeverity;
|
|
12
|
+
readonly message: string;
|
|
13
|
+
}
|
|
14
|
+
/** The full result of `runDoctor`. `ok` is false iff any finding is a `fail` — a `warn` never fails the report on its own. */
|
|
15
|
+
export interface DoctorReport {
|
|
16
|
+
readonly findings: readonly DoctorFinding[];
|
|
17
|
+
readonly ok: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** One identity's raw `identity.json`, unparsed — `runDoctor` does its own JSON.parse/schema validation so one malformed file never aborts the rest of the report. */
|
|
20
|
+
export interface DoctorIdentityInput {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly path: string;
|
|
23
|
+
readonly raw: string | undefined;
|
|
24
|
+
/** The identity's own farm root (`identitiesDir/<name>`) — the account name the Keychain lookup uses. */
|
|
25
|
+
readonly farmRoot: string;
|
|
26
|
+
}
|
|
27
|
+
/** One configuration profile's raw `<name>.json`, unparsed. */
|
|
28
|
+
export interface DoctorConfigProfileInput {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly path: string;
|
|
31
|
+
readonly raw: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** One provider's raw `<name>.json`, unparsed. */
|
|
34
|
+
export interface DoctorProviderInput {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly raw: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
/** A single optional top-level file `runDoctor` validates against a schema when present — absent is a legitimate, unconfigured state, not a failure. */
|
|
40
|
+
interface DoctorFileInput {
|
|
41
|
+
readonly path: string;
|
|
42
|
+
readonly raw: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
/** The outcome of resolving the real Claude Code binary, pre-resolved by the wiring layer since `discoverClaudeBinary` is not itself a parse-shaped pure operation and already has its own dedicated test coverage. */
|
|
45
|
+
type DoctorBinaryDiscovery = {
|
|
46
|
+
readonly ok: true;
|
|
47
|
+
readonly binary: DiscoveredClaudeBinary;
|
|
48
|
+
} | {
|
|
49
|
+
readonly ok: false;
|
|
50
|
+
readonly message: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Where a bare command name resolves for the two names this tool owns.
|
|
54
|
+
*
|
|
55
|
+
* `ownExecutablePath` is this process's own PATH-visible location (`realOwnExecutablePath()`); `agentShim` is `findPathShadow`'s verdict for a bare `agent-shim` against the directory that executable lives in. `claude` is only populated when a shim is actually enabled — without one, a `claude` on PATH is Claude Code's own binary, which is not a shadow of anything.
|
|
56
|
+
*/
|
|
57
|
+
interface DoctorPathResolution {
|
|
58
|
+
readonly ownExecutablePath: string;
|
|
59
|
+
readonly agentShim: PathShadowStatus;
|
|
60
|
+
readonly claude?: PathShadowStatus;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Collapses a `shadowed` verdict back to `ok` when the shadowing entry and this executable are literally the same file reached by two names — `findPathShadow` compares *directories*, so a symlink on PATH pointing at the running executable's own real location otherwise reads as a shadow of itself.
|
|
64
|
+
*
|
|
65
|
+
* `realpath` must resolve symlinks, and must return its argument unchanged rather than throwing when the path cannot be resolved (a broken symlink, a race with an uninstall), so an unresolvable path simply stays unequal and the shadow verdict stands.
|
|
66
|
+
*/
|
|
67
|
+
export declare function refinePathShadow(status: PathShadowStatus, ownExecutablePath: string, realpath: (target: string) => string): PathShadowStatus;
|
|
68
|
+
/** Everything `runDoctor` needs, all of it already loaded/injected — nothing in `runDoctor` itself reads a file, shells out, or touches the farm. */
|
|
69
|
+
export interface RunDoctorParams {
|
|
70
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
71
|
+
readonly identities: readonly DoctorIdentityInput[];
|
|
72
|
+
readonly configProfiles: readonly DoctorConfigProfileInput[];
|
|
73
|
+
readonly providers: readonly DoctorProviderInput[];
|
|
74
|
+
readonly directoryRules: DoctorFileInput;
|
|
75
|
+
readonly globalConfig: DoctorFileInput;
|
|
76
|
+
readonly categoriesLocal: DoctorFileInput;
|
|
77
|
+
readonly activeIdentity: DoctorFileInput;
|
|
78
|
+
readonly binaryDiscovery: DoctorBinaryDiscovery;
|
|
79
|
+
/** Whether `agent-shim shim enable` has been run, and whether its recorded target still exists on disk — pre-resolved by the wiring layer, since checking a file's existence is real I/O, not a parse-shaped pure operation. */
|
|
80
|
+
readonly claudeShim: {
|
|
81
|
+
readonly state: ClaudeShimState | undefined;
|
|
82
|
+
readonly targetExists: boolean;
|
|
83
|
+
};
|
|
84
|
+
/** Which executables a bare `agent-shim` (and, when the shim is enabled, a bare `claude`) would actually run — pre-resolved by the wiring layer, since scanning PATH is real I/O. */
|
|
85
|
+
readonly pathResolution: DoctorPathResolution;
|
|
86
|
+
/** The resolved state root (`paths.root`), whose directory name says whether this installation still lives under the former `.claude-use` name. */
|
|
87
|
+
readonly rootPath: string;
|
|
88
|
+
/** Runs `security find-generic-password` for the per-identity Keychain check. Omit to skip that check entirely (e.g. off macOS). */
|
|
89
|
+
readonly run?: RunPort;
|
|
90
|
+
/** `process.platform` in real use; the Keychain check only ever runs when this is `"darwin"`. */
|
|
91
|
+
readonly platform: string;
|
|
92
|
+
/** The headroom daemon's state.json plus a zombie-aware liveness predicate for the pids it names (a defunct daemon holds no port but still answers signal 0). Omit the raw text when the daemon has never run; that is a pass, not a failure. */
|
|
93
|
+
readonly headroom: {
|
|
94
|
+
readonly state: DoctorFileInput;
|
|
95
|
+
readonly isRunning: (pid: number) => boolean;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Audits the whole `~/.agent-shim` config graph for internal consistency: every identity, every configuration profile's own `extends` chain, every provider, `directory-rules.json`, `config.json`, `categories.local.json`, `active-identity`, plus real Claude Code binary discoverability and ambient-credential exposure.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately identity/directory-agnostic, unlike `runCheck` — there is no single cascade to resolve `doctor` against, so it never touches settings-exposure (which only means anything relative to one resolved cascade).
|
|
102
|
+
*
|
|
103
|
+
* Every check aggregates rather than throws: a malformed file becomes one `fail` finding for that file, not an aborted report. This is the one place in the codebase that deliberately breaks the "throw a validation error and let it propagate" convention every other command relies on — `doctor`'s whole purpose is to survive a broken file and keep auditing everything else.
|
|
104
|
+
*/
|
|
105
|
+
export declare function runDoctor(params: RunDoctorParams): DoctorReport;
|
|
106
|
+
/** Renders a full `DoctorReport` as plain text lines, one section header at a time, in the order `agent-shim doctor` prints them. */
|
|
107
|
+
export declare function formatDoctorReport(report: DoctorReport): string[];
|
|
108
|
+
/** What `collectDoctorReport` needs from its caller. */
|
|
109
|
+
export interface CollectDoctorReportParams {
|
|
110
|
+
readonly paths: LayoutPaths;
|
|
111
|
+
readonly env: NodeJS.ProcessEnv;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Audits the whole state root against this machine: the edge adapter that enumerates every identity, configuration profile and provider on disk, reads every top-level config file as raw text (never pre-parsing, so one malformed file cannot abort the report), discovers the real Claude Code binary and resolves where a bare command name runs from, and hands the facts to the pure `runDoctor`.
|
|
115
|
+
*/
|
|
116
|
+
export declare function collectDoctorReport(params: CollectDoctorReportParams): DoctorReport;
|
|
117
|
+
export {};
|