@socketsecurity/lib 6.0.7 → 6.0.8
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/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/dist/ai/agent-context.d.mts +103 -0
- package/dist/ai/agent-context.js +157 -0
- package/dist/ai/backends.d.mts +83 -0
- package/dist/ai/backends.js +173 -0
- package/dist/ai/credentials.d.mts +49 -0
- package/dist/ai/credentials.js +82 -0
- package/dist/ai/discover.d.mts +4 -0
- package/dist/ai/discover.js +1 -1
- package/dist/ai/exec.d.mts +52 -0
- package/dist/ai/exec.js +92 -0
- package/dist/ai/http.d.mts +132 -0
- package/dist/ai/http.js +130 -0
- package/dist/ai/profiles.d.mts +41 -6
- package/dist/ai/profiles.js +52 -10
- package/dist/ai/route.d.mts +69 -0
- package/dist/ai/route.js +156 -0
- package/dist/ai/spawn.d.mts +10 -2
- package/dist/ai/spawn.js +55 -31
- package/dist/ai/subagent-status.d.mts +48 -0
- package/dist/ai/subagent-status.js +57 -0
- package/dist/ai/tier.d.mts +60 -0
- package/dist/ai/tier.js +53 -0
- package/dist/ai/types.d.mts +15 -2
- package/dist/ai/worktree.js +4 -0
- package/dist/archives/tar.js +1 -1
- package/dist/archives/zip.js +2 -2
- package/dist/argv/parse.d.ts +19 -2
- package/dist/argv/parse.js +1 -1
- package/dist/arrays/join.js +4 -0
- package/dist/bin/find.js +4 -4
- package/dist/bin/prim.cjs +3915 -3781
- package/dist/bin/resolve.js +1 -1
- package/dist/cache/ttl/store.js +1 -1
- package/dist/cli/check-primordials.d.ts +8 -3
- package/dist/cli/check-primordials.js +4 -4
- package/dist/compression/_internal.js +1 -1
- package/dist/compression/brotli.d.ts +1 -2
- package/dist/compression/brotli.js +6 -2
- package/dist/compression/gzip.js +6 -2
- package/dist/constants/packages.d.ts +3 -0
- package/dist/constants/packages.js +2 -1
- package/dist/constants/socket.d.ts +2 -6
- package/dist/constants/socket.js +12 -14
- package/dist/cover/code.js +2 -2
- package/dist/crypto/hash.d.ts +4 -1
- package/dist/crypto/hash.js +4 -1
- package/dist/debug/caller-info.js +1 -1
- package/dist/dlx/arborist.js +13 -3
- package/dist/dlx/binary-cache.js +1 -1
- package/dist/dlx/binary-resolution.js +1 -1
- package/dist/dlx/detect.d.ts +8 -0
- package/dist/dlx/firewall.d.ts +8 -0
- package/dist/dlx/firewall.js +1 -1
- package/dist/dlx/lockfile.js +4 -1
- package/dist/dlx/manifest.js +1 -1
- package/dist/dlx/package.js +4 -0
- package/dist/eco/cargo/parse-lockfile.d.ts +1 -2
- package/dist/eco/cargo/parse-lockfile.js +3 -3
- package/dist/eco/manifest/detect-format.js +1 -1
- package/dist/eco/npm/npm/parse-lockfile.d.ts +3 -4
- package/dist/eco/npm/npm/parse-lockfile.js +2 -2
- package/dist/eco/npm/parse-package-json.d.ts +11 -0
- package/dist/eco/npm/parse-package-json.js +1 -1
- package/dist/eco/npm/pnpm/parse-lockfile.d.ts +5 -3
- package/dist/eco/npm/pnpm/parse-lockfile.js +3 -3
- package/dist/eco/npm/yarnpkg/yarn/exec.js +1 -1
- package/dist/eco/npm/yarnpkg/yarn/parse-lockfile.d.ts +1 -2
- package/dist/eco/npm/yarnpkg/yarn/parse-lockfile.js +1 -1
- package/dist/env/proxy.js +1 -1
- package/dist/env/rewire.d.ts +1 -0
- package/dist/env/rewire.js +1 -1
- package/dist/env/socket.d.ts +7 -0
- package/dist/env/socket.js +10 -0
- package/dist/errors/predicates.js +1 -1
- package/dist/external/@npmcli/promise-spawn.js +3 -1
- package/dist/external/pico-pack.js +4 -2
- package/dist/external/which.js +3 -1
- package/dist/external-tools/bazel/asset-names.d.ts +1 -1
- package/dist/external-tools/bazel/asset-names.js +5 -2
- package/dist/external-tools/bazel/from-download.d.ts +1 -1
- package/dist/external-tools/bazel/from-download.js +5 -2
- package/dist/external-tools/bazel/resolve-bazel-version.js +4 -0
- package/dist/external-tools/bazel/resolve.d.ts +3 -3
- package/dist/external-tools/bazel/resolve.js +16 -8
- package/dist/external-tools/cdxgen/asset-names.d.ts +1 -1
- package/dist/external-tools/cdxgen/asset-names.js +5 -2
- package/dist/external-tools/cdxgen/from-download.d.ts +1 -1
- package/dist/external-tools/cdxgen/from-download.js +7 -4
- package/dist/external-tools/cdxgen/resolve.d.ts +3 -3
- package/dist/external-tools/cdxgen/resolve.js +16 -8
- package/dist/external-tools/from-download.d.ts +2 -2
- package/dist/external-tools/from-download.js +11 -5
- package/dist/external-tools/from-pip-venv.d.ts +1 -1
- package/dist/external-tools/from-pip-venv.js +12 -5
- package/dist/external-tools/janus/asset-names.d.ts +1 -1
- package/dist/external-tools/janus/asset-names.js +5 -2
- package/dist/external-tools/janus/from-download.d.ts +1 -1
- package/dist/external-tools/janus/from-download.js +5 -2
- package/dist/external-tools/janus/resolve.d.ts +3 -3
- package/dist/external-tools/janus/resolve.js +16 -8
- package/dist/external-tools/jre/asset-names.d.ts +1 -1
- package/dist/external-tools/jre/asset-names.js +5 -2
- package/dist/external-tools/jre/from-download.d.ts +1 -1
- package/dist/external-tools/jre/from-download.js +7 -4
- package/dist/external-tools/jre/from-java-home.js +2 -2
- package/dist/external-tools/jre/from-vfs.js +2 -2
- package/dist/external-tools/jre/resolve.d.ts +3 -3
- package/dist/external-tools/jre/resolve.js +16 -8
- package/dist/external-tools/manifest.d.ts +18 -0
- package/dist/external-tools/manifest.js +1 -1
- package/dist/external-tools/opengrep/asset-names.d.ts +1 -1
- package/dist/external-tools/opengrep/asset-names.js +5 -2
- package/dist/external-tools/opengrep/from-download.d.ts +1 -1
- package/dist/external-tools/opengrep/from-download.js +5 -2
- package/dist/external-tools/opengrep/resolve.d.ts +3 -3
- package/dist/external-tools/opengrep/resolve.js +16 -8
- package/dist/external-tools/python/asset-names.d.ts +1 -1
- package/dist/external-tools/python/asset-names.js +10 -3
- package/dist/external-tools/python/dlx.d.ts +3 -3
- package/dist/external-tools/python/dlx.js +20 -9
- package/dist/external-tools/python/from-download.d.ts +1 -1
- package/dist/external-tools/python/from-download.js +12 -5
- package/dist/external-tools/python/pin.js +6 -3
- package/dist/external-tools/python/pip-install.js +6 -3
- package/dist/external-tools/python/resolve.d.ts +3 -3
- package/dist/external-tools/python/resolve.js +19 -11
- package/dist/external-tools/sbt/asset-names.d.ts +1 -1
- package/dist/external-tools/sbt/asset-names.js +5 -2
- package/dist/external-tools/sbt/from-download.d.ts +1 -1
- package/dist/external-tools/sbt/from-download.js +5 -2
- package/dist/external-tools/sbt/resolve.d.ts +3 -3
- package/dist/external-tools/sbt/resolve.js +16 -8
- package/dist/external-tools/skillspector/from-dlx.d.ts +1 -1
- package/dist/external-tools/skillspector/from-dlx.js +10 -3
- package/dist/external-tools/skillspector/resolve.d.ts +2 -2
- package/dist/external-tools/skillspector/resolve.js +14 -6
- package/dist/external-tools/synp/asset-names.d.ts +1 -1
- package/dist/external-tools/synp/asset-names.js +6 -2
- package/dist/external-tools/synp/from-download.d.ts +1 -1
- package/dist/external-tools/synp/from-download.js +5 -2
- package/dist/external-tools/synp/resolve.d.ts +3 -3
- package/dist/external-tools/synp/resolve.js +16 -8
- package/dist/external-tools/trivy/asset-names.d.ts +1 -1
- package/dist/external-tools/trivy/asset-names.js +5 -2
- package/dist/external-tools/trivy/from-download.d.ts +1 -1
- package/dist/external-tools/trivy/from-download.js +7 -4
- package/dist/external-tools/trivy/resolve.d.ts +3 -3
- package/dist/external-tools/trivy/resolve.js +16 -8
- package/dist/external-tools/trufflehog/asset-names.d.ts +1 -1
- package/dist/external-tools/trufflehog/asset-names.js +5 -2
- package/dist/external-tools/trufflehog/from-download.d.ts +1 -1
- package/dist/external-tools/trufflehog/from-download.js +7 -4
- package/dist/external-tools/trufflehog/resolve.d.ts +3 -3
- package/dist/external-tools/trufflehog/resolve.js +16 -8
- package/dist/fs/allowed-dirs-cache.d.ts +27 -1
- package/dist/fs/allowed-dirs-cache.js +38 -3
- package/dist/fs/find.js +1 -1
- package/dist/fs/read-json-cache.d.ts +7 -0
- package/dist/fs/resolve-module.js +6 -2
- package/dist/fs/safe.js +1 -1
- package/dist/git/_internal.js +2 -2
- package/dist/git/repo.js +2 -4
- package/dist/git/staged.js +8 -0
- package/dist/git/tracked.d.ts +84 -0
- package/dist/git/tracked.js +163 -0
- package/dist/git/unstaged.js +8 -0
- package/dist/github/refs-graphql.js +4 -0
- package/dist/github/refs-rest.js +4 -0
- package/dist/github/refs.js +15 -10
- package/dist/globs/_internal.js +1 -1
- package/dist/globs/match.js +9 -1
- package/dist/globs/matcher.js +5 -1
- package/dist/http-request/browser.js +6 -2
- package/dist/http-request/{browser-fetch.d.ts → fetch/browser.d.ts} +2 -2
- package/dist/http-request/{browser-fetch.js → fetch/browser.js} +4 -4
- package/dist/http-request/headers.js +1 -1
- package/dist/http-request/request-attempt.js +2 -2
- package/dist/http-request/user-agent.js +1 -1
- package/dist/integrity.d.ts +10 -4
- package/dist/integrity.js +10 -4
- package/dist/json/edit.js +38 -30
- package/dist/json/format.js +1 -1
- package/dist/native-messaging/install.d.ts +1 -1
- package/dist/native-messaging/install.js +7 -4
- package/dist/native-messaging/rate-limit.d.ts +7 -0
- package/dist/native-messaging/rate-limit.js +4 -0
- package/dist/node/async-hooks.js +1 -1
- package/dist/node/child-process.js +1 -1
- package/dist/node/crypto.js +1 -1
- package/dist/node/events.js +1 -1
- package/dist/node/fs-promises.js +1 -1
- package/dist/node/fs.d.ts +22 -6
- package/dist/node/fs.js +16 -3
- package/dist/node/http.js +1 -1
- package/dist/node/https.js +1 -1
- package/dist/node/module.js +1 -1
- package/dist/node/os.d.ts +10 -2
- package/dist/node/os.js +11 -4
- package/dist/node/path.d.ts +11 -2
- package/dist/node/path.js +17 -4
- package/dist/node/timers-promises.js +1 -1
- package/dist/node/url.js +1 -1
- package/dist/node/util.js +1 -1
- package/dist/objects/getters.js +1 -1
- package/dist/objects/mutate.js +2 -2
- package/dist/objects/predicates.js +1 -1
- package/dist/packages/edit-class.d.ts +2 -3
- package/dist/packages/edit-class.js +41 -35
- package/dist/packages/exports.js +4 -4
- package/dist/packages/fetch.js +1 -1
- package/dist/packages/isolation.js +1 -1
- package/dist/packages/licenses.js +2 -2
- package/dist/packages/manifest.js +4 -4
- package/dist/packages/normalize.js +1 -1
- package/dist/packages/provenance.js +2 -2
- package/dist/packages/specs.js +1 -1
- package/dist/packages/tarball.js +4 -2
- package/dist/packages/types.d.ts +1 -2
- package/dist/paths/dirnames.d.ts +1 -0
- package/dist/paths/dirnames.js +2 -0
- package/dist/paths/resolve.js +14 -19
- package/dist/paths/rewire.d.ts +5 -0
- package/dist/paths/socket.d.ts +74 -111
- package/dist/paths/socket.js +99 -132
- package/dist/primordials/process.d.ts +88 -0
- package/dist/primordials/process.js +132 -0
- package/dist/primordials/uncurry.d.ts +1 -2
- package/dist/process/spawn/child.js +8 -2
- package/dist/process/spawn/errors.js +1 -1
- package/dist/regexps/spec.js +1 -1
- package/dist/releases/github-archives.js +1 -1
- package/dist/releases/github-listing.d.ts +1 -2
- package/dist/schema/types.d.ts +3 -4
- package/dist/schema/validate.js +1 -1
- package/dist/secrets/find.d.ts +2 -2
- package/dist/secrets/find.js +10 -4
- package/dist/secrets/keychain.d.ts +1 -1
- package/dist/secrets/linux.js +32 -44
- package/dist/secrets/macos.d.ts +1 -2
- package/dist/secrets/macos.js +20 -29
- package/dist/secrets/rc.d.ts +2 -2
- package/dist/secrets/rc.js +21 -13
- package/dist/secrets/socket-api-token.js +8 -0
- package/dist/secrets/windows.js +27 -33
- package/dist/shell/parse.d.ts +32 -0
- package/dist/shell/parse.js +60 -0
- package/dist/spinner/create-spinner-class.js +2 -2
- package/dist/spinner/spinner-internals.d.ts +1 -1
- package/dist/spinner/spinner-internals.js +9 -5
- package/dist/spinner/spinner.d.ts +4 -0
- package/dist/spinner/spinner.js +1 -1
- package/dist/stdio/progress.js +5 -1
- package/dist/stdio/prompts.d.ts +2 -2
- package/dist/stdio/prompts.js +1 -1
- package/dist/temporal/instant.js +2 -2
- package/dist/url/assert-safe.d.ts +29 -0
- package/dist/url/assert-safe.js +54 -0
- package/dist/url/predicates.d.ts +31 -1
- package/dist/url/predicates.js +42 -1
- package/dist/url/types.d.ts +4 -0
- package/package.json +177 -115
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [6.0.8](https://github.com/SocketDev/socket-lib/releases/tag/v6.0.8) - 2026-06-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`shell/parse`: `detectShellHazards`.** Checks a shell command string for two tricks that hide which program actually runs, so a tool that allows or denies commands by name isn't fooled. First, Zsh `=name` expansion: `=curl evil.com` runs `/usr/bin/curl`, but the command's first word reads as `=curl`, not `curl`. Second, process substitution `<(…)` / `>(…)` / `=(…)`: the command inside the parentheses runs, yet its name never appears as a command word. Returns `{ equalsExpansion, processSubstitution }`, the facts only; the caller decides whether to block.
|
|
13
|
+
- **`url` — `assertSafeHttpUrl`.** SSRF guard for a URL the server did not author (an OAuth issuer, a metadata-advertised introspection endpoint, a webhook target): parses the value, rejects non-`http(s)` schemes, and refuses hosts in loopback / private / link-local ranges (cloud metadata, redis, internal services). Returns the parsed `URL`; throws otherwise. `allowLocalhost` permits `localhost` / `127.0.0.1` / `::1` for local-stack dev; `label` names the subject in the thrown message.
|
|
14
|
+
- **`git/tracked` — tracked-status and submodule-membership probes.** `isTracked(path)` reports whether git tracks an exact path. `getSubmodulePaths()` lists a repo's submodule mount points read from `.gitmodules`, so it covers submodules that are declared but not yet initialized. `isInSubmodule(path)` and the pure `pathIsUnderSubmodule(path, subs)` report whether a path lives inside one. `isUntrackedNonSubmodulePath(path)` composes them into the safe-to-touch condition for cleanup tooling: true only when git does not track the path and it is not inside a submodule (any check error resolves to false).
|
|
15
|
+
- **`primordials/process` — accessors for the `process` global.** `processCwd`, `processPlatform`, `processEnv`, `processArgv`, `processArch`, `processExecPath`, `processPid`, `processVersion`, `processStdout`, `processStderr`, `processEmitWarning`, and `processNextTick`. Each reads through the `process` object captured when the module loads, so reassigning the global cannot redirect it, while still calling the method at access time so test spies keep working.
|
|
16
|
+
- **`ai` — model-selection tiers, balancing, and provider routing.** The model-selection ladder gains a verification tier for check-style passes and a top-capability tier for the hardest work. Requests load-balance across a provider's backends so they spread instead of pinning one. A shared multi-agent backend registry centralizes routing, and a provider-credential resolver reads from environment variables and falls back to the OS keychain.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **`http-request` browser entry — `fetch/browser`.** The browser build of `httpJson` / `httpText` now resolves through `http-request/fetch/browser` (was `http-request/browser-fetch`), and the package's `browser` field maps Node-only builtins to their browser stubs. Bundlers targeting the browser pick the right entry automatically.
|
|
21
|
+
- **Node-builtin accessors are browser-bundler friendly.** The internal Node-builtin accessor layer requires builtins by their bare name so a browser bundler's builtin replacement (the package `browser` field, a consumer's bundler fallback config) resolves them; the `node:`-prefixed form bypasses that replacement in some bundlers. No public API change.
|
|
22
|
+
- **Caller-supplied `options` are prototype-pollution hardened.** Functions that take an `options` argument normalize it before reading, so an object with a tampered prototype cannot leak inherited properties into the library's behavior.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **`ai` — codex reasoning effort.** Setting `effort` on a `spawnAiAgent` call now reaches the codex backend (emitted as codex's reasoning-effort config), where it was previously accepted but silently ignored for every agent except claude. The claude-only `max` level maps to codex's `xhigh` ceiling.
|
|
27
|
+
|
|
8
28
|
## [6.0.7](https://github.com/SocketDev/socket-lib/releases/tag/v6.0.7) - 2026-06-03
|
|
9
29
|
|
|
10
30
|
### Added
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ import { httpJson } from '@socketsecurity/lib/http-request'
|
|
|
39
39
|
import { safeDelete } from '@socketsecurity/lib/fs'
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Start with the [API
|
|
42
|
+
Start with the [API reference](./docs/api.md) — every subpath export with a one-line description.
|
|
43
43
|
|
|
44
44
|
## Development
|
|
45
45
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Agent-awareness for hooks + scripts: WHICH agent is running right now,
|
|
3
|
+
* and WHERE that agent keeps its config / memory on the current platform. Two
|
|
4
|
+
* distinct questions, two functions:
|
|
5
|
+
*
|
|
6
|
+
* - `detectAgent()` — who is invoking this process? Read from the environment
|
|
7
|
+
* the running agent injects, NOT from any stdin payload (a Claude Code hook
|
|
8
|
+
* gets `{tool_name,…}` on stdin but no agent id; Codex/OpenCode use
|
|
9
|
+
* entirely different invocation contracts). The cross-agent signal is
|
|
10
|
+
* `AI_AGENT` (Claude Code sets `AI_AGENT=claude-code_<ver>_agent`);
|
|
11
|
+
* tool-specific flags (`CLAUDECODE`, `CODEX_*`, `OPENCODE`) are the
|
|
12
|
+
* fallback. Returns `undefined` when nothing identifies an agent (a plain
|
|
13
|
+
* shell / CI).
|
|
14
|
+
* - `agentPaths()` — given an agent, the config + memory directories it uses on
|
|
15
|
+
* THIS OS. Built on the cross-platform `getHome()` (HOME → USERPROFILE) and
|
|
16
|
+
* XDG helpers so a Windows path differs from mac/linux correctly. This is
|
|
17
|
+
* the runtime complement to `discoverAiAgents()` (which agents are
|
|
18
|
+
* INSTALLED on PATH); this answers which one is DRIVING + where it lives.
|
|
19
|
+
* Memory caveat baked into the data: only Claude Code maintains an
|
|
20
|
+
* agent-written memory store (`~/.claude/projects/<slug>/memory/`). Codex +
|
|
21
|
+
* OpenCode have NO self-written memory — their only persistent context is
|
|
22
|
+
* the human-authored AGENTS.md. So `agentPaths(...).memoryDir` is defined
|
|
23
|
+
* only for `claude`; for the others it is `undefined` (there is no memory
|
|
24
|
+
* dir to point at), and the shared cross-tool memory surface is the
|
|
25
|
+
* committed AGENTS.md (which the fleet symlinks to CLAUDE.md).
|
|
26
|
+
*/
|
|
27
|
+
import type { AiAgentName } from './types.mts';
|
|
28
|
+
/**
|
|
29
|
+
* The detected running agent + the raw version token from `AI_AGENT`, when
|
|
30
|
+
* present. `agent` is the normalized `AiAgentName`; `raw` is the full env value
|
|
31
|
+
* (e.g. `claude-code_2-1-169_agent`) for callers that want the version.
|
|
32
|
+
*/
|
|
33
|
+
export interface DetectedAgent {
|
|
34
|
+
readonly agent: AiAgentName;
|
|
35
|
+
readonly raw: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
export declare function agentFromAiAgentEnv(value: string): AiAgentName | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The config + memory directories an agent uses on the current platform.
|
|
40
|
+
*
|
|
41
|
+
* `configDir` is where the agent keeps global config / instructions.
|
|
42
|
+
* `memoryDir` is the agent-written persistent-memory store — defined ONLY for
|
|
43
|
+
* `claude` (Codex/OpenCode have no self-written memory; their durable context
|
|
44
|
+
* is the human-authored AGENTS.md). For non-claude agents `memoryDir` is
|
|
45
|
+
* `undefined`.
|
|
46
|
+
*
|
|
47
|
+
* All paths derive from `getHome()` (HOME → USERPROFILE, cross-platform) so the
|
|
48
|
+
* Windows location differs from mac/linux correctly. Returns `undefined` when
|
|
49
|
+
* the home dir can't be resolved.
|
|
50
|
+
*/
|
|
51
|
+
export interface AgentPaths {
|
|
52
|
+
readonly agent: AiAgentName;
|
|
53
|
+
readonly configDir: string;
|
|
54
|
+
readonly memoryDir: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve an agent's config (and, for Claude, memory) directories on this OS.
|
|
58
|
+
*
|
|
59
|
+
* Per-agent / per-platform (verified against each tool's docs; flagged where a
|
|
60
|
+
* platform path is best-effort):
|
|
61
|
+
*
|
|
62
|
+
* - **claude**: `~/.claude` on every OS. Memory:
|
|
63
|
+
* `~/.claude/projects/<cwd-slug>/memory/` (slug = cwd with `/`→`-`). Pass
|
|
64
|
+
* `options.cwd` to compute the memory dir for a specific project.
|
|
65
|
+
* - **codex**: `$CODEX_HOME` if set, else `~/.codex` (all OSes, incl. Windows
|
|
66
|
+
* `%USERPROFILE%\.codex` — Codex uses a dotdir, not %APPDATA%). No memory.
|
|
67
|
+
* - **opencode**: XDG — `$XDG_CONFIG_HOME/opencode` else `~/.config/opencode` on
|
|
68
|
+
* mac/linux; on Windows `%APPDATA%\opencode` (best-effort: OpenCode's docs
|
|
69
|
+
* don't pin the Windows user-config path; APPDATA is the conventional
|
|
70
|
+
* fallback and is overridable via `$XDG_CONFIG_HOME`). No memory.
|
|
71
|
+
* - **gemini**: `~/.gemini` (all OSes). No memory.
|
|
72
|
+
*
|
|
73
|
+
* @returns The resolved paths, or `undefined` if the home dir is unresolvable.
|
|
74
|
+
*/
|
|
75
|
+
export declare function agentPaths(agent: AiAgentName, options?: {
|
|
76
|
+
cwd?: string | undefined;
|
|
77
|
+
} | undefined): AgentPaths | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Detect which AI agent is invoking the current process, from the environment.
|
|
80
|
+
*
|
|
81
|
+
* Resolution order:
|
|
82
|
+
*
|
|
83
|
+
* 1. `AI_AGENT` — the cross-agent signal (Claude Code sets it). Its leading token
|
|
84
|
+
* names the family.
|
|
85
|
+
* 2. Tool-specific flags as a fallback: `CLAUDECODE=1` / `CLAUDE_CODE_*` → claude;
|
|
86
|
+
* `CODEX_*` → codex; `OPENCODE` → opencode.
|
|
87
|
+
*
|
|
88
|
+
* Returns `undefined` when no agent signal is present (a plain shell, CI, a
|
|
89
|
+
* non-agent subprocess) — callers should treat that as "agent-agnostic", not an
|
|
90
|
+
* error.
|
|
91
|
+
*
|
|
92
|
+
* Note: a hook receives NO agent id in its stdin payload; this env read is the
|
|
93
|
+
* only reliable signal. Different agents also invoke hooks differently (Claude:
|
|
94
|
+
* stdin JSON; Codex: its own hooks; OpenCode: plugin callbacks), so a
|
|
95
|
+
* `.claude/hooks/` script is fundamentally Claude-invoked — `detectAgent()` is
|
|
96
|
+
* most useful for scripts/skills that want to branch on the active agent, or
|
|
97
|
+
* when an agent delegates to another.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const detected = detectAgent()
|
|
101
|
+
* if (detected?.agent === 'claude') { ... }
|
|
102
|
+
*/
|
|
103
|
+
export declare function detectAgent(): DetectedAgent | undefined;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Socket Lib - Built with rolldown */
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
4
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.js');
|
|
5
|
+
const require_constants_platform = require('../constants/platform.js');
|
|
6
|
+
const require_env_rewire = require('../env/rewire.js');
|
|
7
|
+
const require_env_home = require('../env/home.js');
|
|
8
|
+
const require_env_xdg = require('../env/xdg.js');
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
node_path = require_runtime.__toESM(node_path, 1);
|
|
11
|
+
|
|
12
|
+
//#region src/ai/agent-context.mts
|
|
13
|
+
/**
|
|
14
|
+
* @file Agent-awareness for hooks + scripts: WHICH agent is running right now,
|
|
15
|
+
* and WHERE that agent keeps its config / memory on the current platform. Two
|
|
16
|
+
* distinct questions, two functions:
|
|
17
|
+
*
|
|
18
|
+
* - `detectAgent()` — who is invoking this process? Read from the environment
|
|
19
|
+
* the running agent injects, NOT from any stdin payload (a Claude Code hook
|
|
20
|
+
* gets `{tool_name,…}` on stdin but no agent id; Codex/OpenCode use
|
|
21
|
+
* entirely different invocation contracts). The cross-agent signal is
|
|
22
|
+
* `AI_AGENT` (Claude Code sets `AI_AGENT=claude-code_<ver>_agent`);
|
|
23
|
+
* tool-specific flags (`CLAUDECODE`, `CODEX_*`, `OPENCODE`) are the
|
|
24
|
+
* fallback. Returns `undefined` when nothing identifies an agent (a plain
|
|
25
|
+
* shell / CI).
|
|
26
|
+
* - `agentPaths()` — given an agent, the config + memory directories it uses on
|
|
27
|
+
* THIS OS. Built on the cross-platform `getHome()` (HOME → USERPROFILE) and
|
|
28
|
+
* XDG helpers so a Windows path differs from mac/linux correctly. This is
|
|
29
|
+
* the runtime complement to `discoverAiAgents()` (which agents are
|
|
30
|
+
* INSTALLED on PATH); this answers which one is DRIVING + where it lives.
|
|
31
|
+
* Memory caveat baked into the data: only Claude Code maintains an
|
|
32
|
+
* agent-written memory store (`~/.claude/projects/<slug>/memory/`). Codex +
|
|
33
|
+
* OpenCode have NO self-written memory — their only persistent context is
|
|
34
|
+
* the human-authored AGENTS.md. So `agentPaths(...).memoryDir` is defined
|
|
35
|
+
* only for `claude`; for the others it is `undefined` (there is no memory
|
|
36
|
+
* dir to point at), and the shared cross-tool memory surface is the
|
|
37
|
+
* committed AGENTS.md (which the fleet symlinks to CLAUDE.md).
|
|
38
|
+
*/
|
|
39
|
+
function agentFromAiAgentEnv(value) {
|
|
40
|
+
const lower = value.toLowerCase();
|
|
41
|
+
if (lower.startsWith("claude")) return "claude";
|
|
42
|
+
if (lower.startsWith("codex")) return "codex";
|
|
43
|
+
if (lower.startsWith("opencode")) return "opencode";
|
|
44
|
+
if (lower.startsWith("gemini")) return "gemini";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve an agent's config (and, for Claude, memory) directories on this OS.
|
|
48
|
+
*
|
|
49
|
+
* Per-agent / per-platform (verified against each tool's docs; flagged where a
|
|
50
|
+
* platform path is best-effort):
|
|
51
|
+
*
|
|
52
|
+
* - **claude**: `~/.claude` on every OS. Memory:
|
|
53
|
+
* `~/.claude/projects/<cwd-slug>/memory/` (slug = cwd with `/`→`-`). Pass
|
|
54
|
+
* `options.cwd` to compute the memory dir for a specific project.
|
|
55
|
+
* - **codex**: `$CODEX_HOME` if set, else `~/.codex` (all OSes, incl. Windows
|
|
56
|
+
* `%USERPROFILE%\.codex` — Codex uses a dotdir, not %APPDATA%). No memory.
|
|
57
|
+
* - **opencode**: XDG — `$XDG_CONFIG_HOME/opencode` else `~/.config/opencode` on
|
|
58
|
+
* mac/linux; on Windows `%APPDATA%\opencode` (best-effort: OpenCode's docs
|
|
59
|
+
* don't pin the Windows user-config path; APPDATA is the conventional
|
|
60
|
+
* fallback and is overridable via `$XDG_CONFIG_HOME`). No memory.
|
|
61
|
+
* - **gemini**: `~/.gemini` (all OSes). No memory.
|
|
62
|
+
*
|
|
63
|
+
* @returns The resolved paths, or `undefined` if the home dir is unresolvable.
|
|
64
|
+
*/
|
|
65
|
+
function agentPaths(agent, options) {
|
|
66
|
+
const opts = {
|
|
67
|
+
__proto__: null,
|
|
68
|
+
...options
|
|
69
|
+
};
|
|
70
|
+
const home = require_env_home.getHome();
|
|
71
|
+
if (!home) return;
|
|
72
|
+
switch (agent) {
|
|
73
|
+
case "claude": {
|
|
74
|
+
const configDir = node_path.default.join(home, ".claude");
|
|
75
|
+
const cwd = opts.cwd;
|
|
76
|
+
return {
|
|
77
|
+
agent,
|
|
78
|
+
configDir,
|
|
79
|
+
memoryDir: cwd ? node_path.default.join(configDir, "projects", cwd.replace(/[/\\]/g, "-"), "memory") : void 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
case "codex": return {
|
|
83
|
+
agent,
|
|
84
|
+
configDir: require_env_rewire.getEnvValue("CODEX_HOME") || node_path.default.join(home, ".codex"),
|
|
85
|
+
memoryDir: void 0
|
|
86
|
+
};
|
|
87
|
+
case "opencode": {
|
|
88
|
+
const xdg = require_env_xdg.getXdgConfigHome();
|
|
89
|
+
let base;
|
|
90
|
+
if (xdg) base = xdg;
|
|
91
|
+
else if (require_constants_platform.WIN32) base = require_env_rewire.getEnvValue("APPDATA") || node_path.default.join(home, ".config");
|
|
92
|
+
else base = node_path.default.join(home, ".config");
|
|
93
|
+
return {
|
|
94
|
+
agent,
|
|
95
|
+
configDir: node_path.default.join(base, "opencode"),
|
|
96
|
+
memoryDir: void 0
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
case "gemini": return {
|
|
100
|
+
agent,
|
|
101
|
+
configDir: node_path.default.join(home, ".gemini"),
|
|
102
|
+
memoryDir: void 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Detect which AI agent is invoking the current process, from the environment.
|
|
108
|
+
*
|
|
109
|
+
* Resolution order:
|
|
110
|
+
*
|
|
111
|
+
* 1. `AI_AGENT` — the cross-agent signal (Claude Code sets it). Its leading token
|
|
112
|
+
* names the family.
|
|
113
|
+
* 2. Tool-specific flags as a fallback: `CLAUDECODE=1` / `CLAUDE_CODE_*` → claude;
|
|
114
|
+
* `CODEX_*` → codex; `OPENCODE` → opencode.
|
|
115
|
+
*
|
|
116
|
+
* Returns `undefined` when no agent signal is present (a plain shell, CI, a
|
|
117
|
+
* non-agent subprocess) — callers should treat that as "agent-agnostic", not an
|
|
118
|
+
* error.
|
|
119
|
+
*
|
|
120
|
+
* Note: a hook receives NO agent id in its stdin payload; this env read is the
|
|
121
|
+
* only reliable signal. Different agents also invoke hooks differently (Claude:
|
|
122
|
+
* stdin JSON; Codex: its own hooks; OpenCode: plugin callbacks), so a
|
|
123
|
+
* `.claude/hooks/` script is fundamentally Claude-invoked — `detectAgent()` is
|
|
124
|
+
* most useful for scripts/skills that want to branch on the active agent, or
|
|
125
|
+
* when an agent delegates to another.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* const detected = detectAgent()
|
|
129
|
+
* if (detected?.agent === 'claude') { ... }
|
|
130
|
+
*/
|
|
131
|
+
function detectAgent() {
|
|
132
|
+
const aiAgent = require_env_rewire.getEnvValue("AI_AGENT");
|
|
133
|
+
if (aiAgent) {
|
|
134
|
+
const agent = agentFromAiAgentEnv(aiAgent);
|
|
135
|
+
if (agent) return {
|
|
136
|
+
agent,
|
|
137
|
+
raw: aiAgent
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (require_env_rewire.getEnvValue("CLAUDECODE") || require_env_rewire.getEnvValue("CLAUDE_CODE_ENTRYPOINT")) return {
|
|
141
|
+
agent: "claude",
|
|
142
|
+
raw: aiAgent
|
|
143
|
+
};
|
|
144
|
+
if (require_env_rewire.getEnvValue("OPENCODE")) return {
|
|
145
|
+
agent: "opencode",
|
|
146
|
+
raw: aiAgent
|
|
147
|
+
};
|
|
148
|
+
if (require_env_rewire.getEnvValue("CODEX_SANDBOX") || require_env_rewire.getEnvValue("CODEX_HOME")) return {
|
|
149
|
+
agent: "codex",
|
|
150
|
+
raw: aiAgent
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
exports.agentFromAiAgentEnv = agentFromAiAgentEnv;
|
|
156
|
+
exports.agentPaths = agentPaths;
|
|
157
|
+
exports.detectAgent = detectAgent;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Multi-agent CLI backend registry + role routing. The fleet's review /
|
|
3
|
+
* scan / fix skills delegate work to whichever agent CLIs are installed
|
|
4
|
+
* (`codex`, `claude`, `kimi`, `opencode`), falling back through a per-role
|
|
5
|
+
* preference order and skipping a pass when nothing usable is present. Before
|
|
6
|
+
* this module each skill copied the same registry + detection block inline
|
|
7
|
+
* (the canonical copy lived in
|
|
8
|
+
* `.claude/skills/fleet/reviewing-code/run.mts`); the shared policy doc
|
|
9
|
+
* (`_shared/multi-agent-backends.md`) flagged the extraction. Import
|
|
10
|
+
* `BACKENDS` / `detectAvailableBackends` / `resolveBackendForRole` here
|
|
11
|
+
* instead so a new backend or a routing-policy change is a single edit. The
|
|
12
|
+
* registry is prompt-agnostic: it owns WHICH CLI runs and HOW its argv is
|
|
13
|
+
* built ({ argv, outMode }), not WHAT to ask. A skill keeps its own role
|
|
14
|
+
* table (prompts + per-role `preferenceOrder` + timeouts) and passes the
|
|
15
|
+
* order into `resolveBackendForRole`. Detection uses `which` (cross-platform)
|
|
16
|
+
* rather than `command -v` under `shell: true`, which mangles on Windows. The
|
|
17
|
+
* resolver is pure — it returns a structured result (chosen backend + why)
|
|
18
|
+
* and never logs, so the caller decides how to surface a fallback or a skip.
|
|
19
|
+
* `opencode` is hybrid (it dispatches to whatever provider its own config
|
|
20
|
+
* selects, e.g. Fireworks / Synthetic), so it is NEVER auto-picked from a
|
|
21
|
+
* preference order — only when a caller names it explicitly — to keep model
|
|
22
|
+
* attribution accurate.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* A CLI backend the fleet can delegate a pass to.
|
|
26
|
+
*/
|
|
27
|
+
export type BackendName = 'claude' | 'codex' | 'kimi' | 'opencode';
|
|
28
|
+
/**
|
|
29
|
+
* How a backend's process emits its result.
|
|
30
|
+
*/
|
|
31
|
+
export type BackendOutMode = 'file' | 'stdout';
|
|
32
|
+
export interface BackendRun {
|
|
33
|
+
readonly argv: readonly string[];
|
|
34
|
+
readonly outMode: BackendOutMode;
|
|
35
|
+
}
|
|
36
|
+
export interface BackendDescriptor {
|
|
37
|
+
readonly bin: string;
|
|
38
|
+
readonly hybrid: boolean;
|
|
39
|
+
readonly name: BackendName;
|
|
40
|
+
readonly run: (promptFile: string, outFile: string) => BackendRun;
|
|
41
|
+
}
|
|
42
|
+
export declare const BACKENDS: Readonly<Record<BackendName, BackendDescriptor>>;
|
|
43
|
+
/**
|
|
44
|
+
* The set of backends whose CLI is installed. Fans the `which` lookups out
|
|
45
|
+
* concurrently rather than awaiting one at a time.
|
|
46
|
+
*/
|
|
47
|
+
export declare function detectAvailableBackends(): Promise<ReadonlySet<BackendName>>;
|
|
48
|
+
/**
|
|
49
|
+
* True when `value` names a known backend.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isBackendName(value: string): value is BackendName;
|
|
52
|
+
/**
|
|
53
|
+
* True when the named CLI resolves on PATH (cross-platform via `which`).
|
|
54
|
+
*/
|
|
55
|
+
export declare function isCommandAvailable(bin: string): Promise<boolean>;
|
|
56
|
+
export type BackendResolution = {
|
|
57
|
+
readonly backend: BackendName;
|
|
58
|
+
readonly reason: 'override';
|
|
59
|
+
} | {
|
|
60
|
+
readonly backend: BackendName;
|
|
61
|
+
readonly reason: 'preference';
|
|
62
|
+
} | {
|
|
63
|
+
readonly backend: BackendName;
|
|
64
|
+
readonly reason: 'preference';
|
|
65
|
+
readonly overrideMissing: BackendName;
|
|
66
|
+
} | {
|
|
67
|
+
readonly backend: undefined;
|
|
68
|
+
readonly reason: 'unavailable';
|
|
69
|
+
};
|
|
70
|
+
export interface ResolveBackendOptions {
|
|
71
|
+
readonly preferenceOrder: readonly BackendName[];
|
|
72
|
+
readonly available: ReadonlySet<BackendName>;
|
|
73
|
+
readonly override?: BackendName | undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve which backend runs a pass, encoding the fleet detection policy
|
|
77
|
+
* (`_shared/multi-agent-backends.md`): an installed explicit override wins;
|
|
78
|
+
* else the first installed non-hybrid entry in the preference order; else
|
|
79
|
+
* nothing (skip the pass). Pure — returns the decision + why, never logs. An
|
|
80
|
+
* override that isn't installed is reported via `overrideMissing` so the caller
|
|
81
|
+
* can warn.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveBackendForRole(options: ResolveBackendOptions): BackendResolution;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Socket Lib - Built with rolldown */
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
4
|
+
const require_bin_which = require('../bin/which.js');
|
|
5
|
+
const require_ai_spawn = require('./spawn.js');
|
|
6
|
+
|
|
7
|
+
//#region src/ai/backends.mts
|
|
8
|
+
/**
|
|
9
|
+
* @file Multi-agent CLI backend registry + role routing. The fleet's review /
|
|
10
|
+
* scan / fix skills delegate work to whichever agent CLIs are installed
|
|
11
|
+
* (`codex`, `claude`, `kimi`, `opencode`), falling back through a per-role
|
|
12
|
+
* preference order and skipping a pass when nothing usable is present. Before
|
|
13
|
+
* this module each skill copied the same registry + detection block inline
|
|
14
|
+
* (the canonical copy lived in
|
|
15
|
+
* `.claude/skills/fleet/reviewing-code/run.mts`); the shared policy doc
|
|
16
|
+
* (`_shared/multi-agent-backends.md`) flagged the extraction. Import
|
|
17
|
+
* `BACKENDS` / `detectAvailableBackends` / `resolveBackendForRole` here
|
|
18
|
+
* instead so a new backend or a routing-policy change is a single edit. The
|
|
19
|
+
* registry is prompt-agnostic: it owns WHICH CLI runs and HOW its argv is
|
|
20
|
+
* built ({ argv, outMode }), not WHAT to ask. A skill keeps its own role
|
|
21
|
+
* table (prompts + per-role `preferenceOrder` + timeouts) and passes the
|
|
22
|
+
* order into `resolveBackendForRole`. Detection uses `which` (cross-platform)
|
|
23
|
+
* rather than `command -v` under `shell: true`, which mangles on Windows. The
|
|
24
|
+
* resolver is pure — it returns a structured result (chosen backend + why)
|
|
25
|
+
* and never logs, so the caller decides how to surface a fallback or a skip.
|
|
26
|
+
* `opencode` is hybrid (it dispatches to whatever provider its own config
|
|
27
|
+
* selects, e.g. Fireworks / Synthetic), so it is NEVER auto-picked from a
|
|
28
|
+
* preference order — only when a caller names it explicitly — to keep model
|
|
29
|
+
* attribution accurate.
|
|
30
|
+
*/
|
|
31
|
+
const BACKENDS = {
|
|
32
|
+
__proto__: null,
|
|
33
|
+
claude: {
|
|
34
|
+
bin: "claude",
|
|
35
|
+
hybrid: false,
|
|
36
|
+
name: "claude",
|
|
37
|
+
run(_promptFile, _outFile) {
|
|
38
|
+
const model = process.env["CLAUDE_MODEL"] ?? "opus";
|
|
39
|
+
const effort = process.env["CLAUDE_EFFORT"] ?? "high";
|
|
40
|
+
return {
|
|
41
|
+
argv: [
|
|
42
|
+
"--print",
|
|
43
|
+
"--model",
|
|
44
|
+
model,
|
|
45
|
+
...require_ai_spawn.isAdaptiveOnlyModel(model) ? [] : ["--effort", effort],
|
|
46
|
+
"--no-session-persistence",
|
|
47
|
+
"--permission-mode",
|
|
48
|
+
"dontAsk"
|
|
49
|
+
],
|
|
50
|
+
outMode: "stdout"
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
codex: {
|
|
55
|
+
bin: "codex",
|
|
56
|
+
hybrid: false,
|
|
57
|
+
name: "codex",
|
|
58
|
+
run(_promptFile, outFile) {
|
|
59
|
+
return {
|
|
60
|
+
argv: [
|
|
61
|
+
"exec",
|
|
62
|
+
"--model",
|
|
63
|
+
process.env["CODEX_MODEL"] ?? "gpt-5.5",
|
|
64
|
+
"-c",
|
|
65
|
+
`model_reasoning_effort=${process.env["CODEX_REASONING"] ?? "xhigh"}`,
|
|
66
|
+
"--full-auto",
|
|
67
|
+
"--ephemeral",
|
|
68
|
+
"-o",
|
|
69
|
+
outFile,
|
|
70
|
+
"-"
|
|
71
|
+
],
|
|
72
|
+
outMode: "file"
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
kimi: {
|
|
77
|
+
bin: "kimi",
|
|
78
|
+
hybrid: false,
|
|
79
|
+
name: "kimi",
|
|
80
|
+
run(_promptFile, _outFile) {
|
|
81
|
+
return {
|
|
82
|
+
argv: [
|
|
83
|
+
"chat",
|
|
84
|
+
"--model",
|
|
85
|
+
process.env["KIMI_MODEL"] ?? "kimi-latest",
|
|
86
|
+
"--no-stream"
|
|
87
|
+
],
|
|
88
|
+
outMode: "stdout"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
opencode: {
|
|
93
|
+
bin: "opencode",
|
|
94
|
+
hybrid: true,
|
|
95
|
+
name: "opencode",
|
|
96
|
+
run(_promptFile, _outFile) {
|
|
97
|
+
const model = process.env["OPENCODE_MODEL"];
|
|
98
|
+
return {
|
|
99
|
+
argv: model ? [
|
|
100
|
+
"run",
|
|
101
|
+
"--model",
|
|
102
|
+
model
|
|
103
|
+
] : ["run"],
|
|
104
|
+
outMode: "stdout"
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* The set of backends whose CLI is installed. Fans the `which` lookups out
|
|
111
|
+
* concurrently rather than awaiting one at a time.
|
|
112
|
+
*/
|
|
113
|
+
async function detectAvailableBackends() {
|
|
114
|
+
const names = Object.keys(BACKENDS);
|
|
115
|
+
const results = await Promise.all(names.map(async (name) => ({
|
|
116
|
+
available: await isCommandAvailable(BACKENDS[name].bin),
|
|
117
|
+
name
|
|
118
|
+
})));
|
|
119
|
+
return new Set(results.filter((r) => r.available).map((r) => r.name));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* True when `value` names a known backend.
|
|
123
|
+
*/
|
|
124
|
+
function isBackendName(value) {
|
|
125
|
+
return value in BACKENDS;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* True when the named CLI resolves on PATH (cross-platform via `which`).
|
|
129
|
+
*/
|
|
130
|
+
async function isCommandAvailable(bin) {
|
|
131
|
+
return await require_bin_which.which(bin) !== null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve which backend runs a pass, encoding the fleet detection policy
|
|
135
|
+
* (`_shared/multi-agent-backends.md`): an installed explicit override wins;
|
|
136
|
+
* else the first installed non-hybrid entry in the preference order; else
|
|
137
|
+
* nothing (skip the pass). Pure — returns the decision + why, never logs. An
|
|
138
|
+
* override that isn't installed is reported via `overrideMissing` so the caller
|
|
139
|
+
* can warn.
|
|
140
|
+
*/
|
|
141
|
+
function resolveBackendForRole(options) {
|
|
142
|
+
const { available, override, preferenceOrder } = {
|
|
143
|
+
__proto__: null,
|
|
144
|
+
...options
|
|
145
|
+
};
|
|
146
|
+
if (override && available.has(override)) return {
|
|
147
|
+
backend: override,
|
|
148
|
+
reason: "override"
|
|
149
|
+
};
|
|
150
|
+
for (let i = 0, { length } = preferenceOrder; i < length; i += 1) {
|
|
151
|
+
const candidate = preferenceOrder[i];
|
|
152
|
+
if (BACKENDS[candidate]?.hybrid) continue;
|
|
153
|
+
if (available.has(candidate)) return override ? {
|
|
154
|
+
backend: candidate,
|
|
155
|
+
overrideMissing: override,
|
|
156
|
+
reason: "preference"
|
|
157
|
+
} : {
|
|
158
|
+
backend: candidate,
|
|
159
|
+
reason: "preference"
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
backend: void 0,
|
|
164
|
+
reason: "unavailable"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
exports.BACKENDS = BACKENDS;
|
|
170
|
+
exports.detectAvailableBackends = detectAvailableBackends;
|
|
171
|
+
exports.isBackendName = isBackendName;
|
|
172
|
+
exports.isCommandAvailable = isCommandAvailable;
|
|
173
|
+
exports.resolveBackendForRole = resolveBackendForRole;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Layered provider-credential resolver for AI backends. One call site,
|
|
3
|
+
* dev and CI: a provider token resolves from an explicit override, then the
|
|
4
|
+
* provider's env var, then the OS keychain — mirroring the
|
|
5
|
+
* `readSocketApiToken` env → keychain precedence
|
|
6
|
+
* (`secrets/socket-api-token.ts`). Why a single resolver: `ai/http.mts` read
|
|
7
|
+
* `process.env[tokenEnv]` inline, so every consumer hard-coded the env-only
|
|
8
|
+
* path and none could reach the keychain. Routing skills also need a uniform
|
|
9
|
+
* way to ask "do I have a credential for provider X?" without knowing its
|
|
10
|
+
* env-var name. This centralizes the provider → { tokenEnv, keychainService }
|
|
11
|
+
* map (the HTTP providers reuse `AI_HTTP_PROVIDERS` so the env var isn't
|
|
12
|
+
* duplicated) and the precedence. CI vs dev: pass `allowEnvOnly: true` (the
|
|
13
|
+
* resolver's existing escape) in headless contexts so a missing token returns
|
|
14
|
+
* `undefined` immediately instead of triggering a keychain auth prompt. CI
|
|
15
|
+
* sets the token as a GH-secret env var (e.g. `ANTHROPIC_API_KEY`); the same
|
|
16
|
+
* `resolveProviderCredential` call reads it there with no keychain. proteus
|
|
17
|
+
* hook-point: the forthcoming biometric credential daemon
|
|
18
|
+
* (`.claude/plans/proteus-credential-broker.md`) slots in as a layer between
|
|
19
|
+
* the env check and the keychain read inside `resolve()`'s implementation —
|
|
20
|
+
* call sites here do not change when it lands (resolver decision #4 in that
|
|
21
|
+
* plan). This module is the stable seam.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* A provider whose credential this module can resolve: the HTTP providers
|
|
25
|
+
* (fireworks, synthetic) plus the CLI/first-party providers (anthropic, openai,
|
|
26
|
+
* xai) for CI env + keychain.
|
|
27
|
+
*/
|
|
28
|
+
export type CredentialProvider = 'anthropic' | 'fireworks' | 'openai' | 'synthetic' | 'xai';
|
|
29
|
+
export interface ProviderCredentialSpec {
|
|
30
|
+
readonly tokenEnv: string;
|
|
31
|
+
readonly keychainService: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const PROVIDER_CREDENTIALS: Readonly<Record<CredentialProvider, ProviderCredentialSpec>>;
|
|
34
|
+
/**
|
|
35
|
+
* True when `value` names a provider with a resolvable credential.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isCredentialProvider(value: string): value is CredentialProvider;
|
|
38
|
+
export interface ResolveProviderCredentialOptions {
|
|
39
|
+
readonly provider: CredentialProvider;
|
|
40
|
+
readonly explicit?: string | undefined;
|
|
41
|
+
readonly allowEnvOnly?: boolean | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a provider's bearer token: explicit override → env var → keychain →
|
|
45
|
+
* undefined. The token never appears inline or in logs — callers pass the
|
|
46
|
+
* result straight to an `Authorization` header. Returns `undefined` when no
|
|
47
|
+
* source has it (the caller decides whether that's fatal).
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveProviderCredential(options: ResolveProviderCredentialOptions): Promise<string | undefined>;
|