feat: support custom pi model configs, with registry-resolvable model IDs and distinct model error codes (#450)

This commit is contained in:
ezl-keygraph
2026-09-08 14:54:37 +05:30
committed by GitHub
parent 4b8131fdd5
commit d41d52f17d
20 changed files with 441 additions and 124 deletions
+3 -3
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -131,7 +131,7 @@ These reports are from Shannon Open Source scans of Photoview 2.4.0, one of the
- **Docker**: required for the worker container.
- **Node.js 18+**: required for the recommended `npx` workflow.
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url), and a model the catalogue does not yet carry can be described with a [custom model configuration](docs/ai-providers.md#custom-model-configuration). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
- **Cyber safeguards cleared with your provider**: Anthropic and OpenAI apply real-time safeguards to cyber-security workloads, which can interrupt a scan mid-run. Complete their guidance for legitimate security testers before your first run - see [AI providers](docs/ai-providers.md#cyber-safeguards-do-this-before-your-first-scan).
@@ -375,11 +375,11 @@ Yes. Shannon emits SARIF 2.1.0, the OASIS standard format for static analysis re
### Which AI providers does Shannon support?
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. Shannon uses a single unified model setting throughout a pentest.
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. A model the catalogue does not yet carry, such as one released after Shannon's pinned harness version, runs without waiting for a Shannon release. Describe it in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file and pass it with `--models-config`. Shannon uses a single unified model setting throughout a pentest.
### Can I run Shannon on a local or self-hosted model?
Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and LLM gateways such as LiteLLM. Point Shannon at the endpoint with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [AI providers](docs/ai-providers.md#custom-base-url).
Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and LLM gateways such as LiteLLM. A model the harness catalogue does not carry, which most self-hosted models are, is described in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file passed with `--models-config`; routers and gateways can also be reached with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [Local and self-hosted models](docs/ai-providers.md#local-and-self-hosted-models).
### Does Shannon actually exploit vulnerabilities, or just scan?
+8
View File
@@ -22,6 +22,7 @@ import {
FINAL_REPORT_PDF_FILENAME,
INTERNAL_DIR,
resolveConfig,
resolveModelsConfig,
resolveRepo,
resolveRunFile,
} from '../paths.js';
@@ -37,6 +38,7 @@ export interface StartArgs {
url: string;
repo: string;
config?: string;
modelsConfig?: string;
workspace?: string;
output?: string;
pipelineTesting: boolean;
@@ -231,6 +233,7 @@ export async function start(args: StartArgs): Promise<void> {
}
const repo = resolveRepo(args.repo);
const config = args.config ? resolveConfig(args.config) : undefined;
const modelsConfig = args.modelsConfig ? resolveModelsConfig(args.modelsConfig) : undefined;
const workspacesDir = getWorkspacesDir();
const workspace =
args.workspace ?? `${new URL(args.url).hostname.replace(/[^a-zA-Z0-9-]/g, '-')}_shannon-${Date.now()}`;
@@ -322,6 +325,7 @@ export async function start(args: StartArgs): Promise<void> {
containerName,
envFlags: buildEnvFlags(),
...(config && { config }),
...(modelsConfig && { modelsConfig }),
...(promptsDir && { promptsDir }),
...(outputDir && { outputDir }),
workspace,
@@ -530,6 +534,10 @@ function printInfo(args: StartArgs, workspace: string, repoPath: string, workspa
if (args.config) {
console.log(` Config: ${interactive ? path.resolve(args.config) : path.basename(args.config)}`);
}
if (args.modelsConfig) {
const shown = interactive ? path.resolve(args.modelsConfig) : path.basename(args.modelsConfig);
console.log(` Models: ${shown}`);
}
if (args.pipelineTesting) {
console.log(' Mode: Pipeline Testing');
}
+7
View File
@@ -407,6 +407,7 @@ export interface WorkerOptions {
containerName: string;
envFlags: string[];
config?: { hostPath: string; containerPath: string };
modelsConfig?: { hostPath: string; containerPath: string };
promptsDir?: string;
outputDir?: string;
workspace: string;
@@ -469,6 +470,12 @@ export function spawnWorker(opts: WorkerOptions): ChildProcess {
args.push('-v', `${opts.config.hostPath}:${opts.config.containerPath}:ro`);
}
// pi model config. The mount is the only signal the worker gets: it detects the file at
// this fixed path, so nothing about --models-config travels through the environment.
if (opts.modelsConfig) {
args.push('-v', `${opts.modelsConfig.hostPath}:${opts.modelsConfig.containerPath}:ro`);
}
// Customer-copy destination. The workflow surfaces only final report artifacts here.
if (opts.outputDir) {
args.push('-v', `${opts.outputDir}:/app/output`);
+1
View File
@@ -29,6 +29,7 @@ export const START_OPTIONS: readonly (readonly [string, string])[] = [
['-u, --url <url>', 'Target URL (required)'],
['-r, --repo <path>', 'Repository path (required)'],
['-c, --config <path>', 'Configuration file (YAML)'],
['--models-config <path>', "pi model config (models.json) defining models pi's catalogue lacks"],
['-o, --output <path>', 'Copy deliverables to this directory after the run'],
['-w, --workspace <name>', 'Named workspace (auto-resumes if it exists)'],
['-f, --follow', 'Stream the scan log until it finishes'],
+3
View File
@@ -171,6 +171,7 @@ interface ParsedStartArgs {
url: string;
repo: string;
config?: string;
modelsConfig?: string;
workspace?: string;
output?: string;
pipelineTesting: boolean;
@@ -184,6 +185,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs {
url: ['-u', '--url'],
repo: ['-r', '--repo'],
config: ['-c', '--config'],
modelsConfig: ['--models-config'],
output: ['-o', '--output'],
workspace: ['-w', '--workspace'],
},
@@ -213,6 +215,7 @@ function parseStartArgs(argv: string[]): ParsedStartArgs {
keepContainer: !!flags.keepContainer,
follow: !!flags.follow,
...(values.config && { config: values.config }),
...(values.modelsConfig && { modelsConfig: values.modelsConfig }),
...(values.workspace && { workspace: values.workspace }),
...(values.output && { output: values.output }),
};
+31 -2
View File
@@ -1,7 +1,7 @@
/**
* Path resolution for --repo and --config arguments.
* Path resolution for --repo, --config and --models-config arguments.
*
* Both --repo and --config are filesystem paths, absolute or relative to CWD.
* All three are filesystem paths, absolute or relative to CWD.
*/
import fs from 'node:fs';
@@ -108,3 +108,32 @@ export function resolveConfig(configArg: string): MountPair {
containerPath: `/app/configs/${basename}`,
};
}
/**
* Container path for a mounted pi model config. Fixed, not derived from the host filename:
* the worker detects the file here to decide whether models.json is enabled at all. Must
* match MODELS_CONFIG_PATH in the worker package.
*/
export const MODELS_CONFIG_CONTAINER_PATH = '/app/models.json';
/**
* Resolve --models-config to an absolute path and container mount. Content is left
* unparsed: pi's models.json permits comments, so JSON.parse would reject valid input,
* and pi's own loader reports schema faults far better — the worker surfaces those.
*/
export function resolveModelsConfig(modelsConfigArg: string): MountPair {
const hostPath = path.resolve(expandHome(modelsConfigArg));
if (!fs.existsSync(hostPath)) {
fail(`Model config file not found: ${hostPath}`);
}
if (!fs.statSync(hostPath).isFile()) {
fail(`Not a file: ${hostPath}`);
}
return {
hostPath,
containerPath: MODELS_CONFIG_CONTAINER_PATH,
};
}
+3 -3
View File
@@ -39,9 +39,9 @@
"clean": "rm -rf dist"
},
"dependencies": {
"@earendil-works/pi-agent-core": "^0.84.2",
"@earendil-works/pi-ai": "^0.84.2",
"@earendil-works/pi-coding-agent": "^0.84.2",
"@earendil-works/pi-agent-core": "^0.84.4",
"@earendil-works/pi-ai": "^0.84.4",
"@earendil-works/pi-coding-agent": "^0.84.4",
"@gotgenes/pi-permission-system": "^10.9.0",
"@temporalio/activity": "1.15.0",
"@temporalio/client": "1.15.0",
+39 -19
View File
@@ -20,6 +20,11 @@
* Resolution returns a pi `Model` plus the `ModelRuntime` that owns its auth,
* built over an in-memory credential store primed from the environment.
*
* A model too new for the pinned pi release is reachable by passing its descriptor in a
* pi `models.json` (the CLI's `--models-config`), which merges over the catalogue. The
* credential store below outranks any `apiKey` that file carries, so it describes the
* model while the environment still supplies the secret.
*
* The CLI cannot import this module (it ships as a separate bundle), so
* `apps/cli/src/model-spec.ts` mirrors the parse rule and the provider/credential
* tables by hand for its own `status` rendering and setup wizard. The two copies
@@ -32,6 +37,7 @@ import { existsSync } from 'node:fs';
import path from 'node:path';
import type { Api, Credential, CredentialInfo, CredentialStore, Model } from '@earendil-works/pi-ai';
import { getAgentDir, ModelRuntime } from '@earendil-works/pi-coding-agent';
import { MODELS_CONFIG_PATH } from '../paths.js';
/**
* Providers Shannon curates with their own credential variables, config sections,
@@ -196,20 +202,43 @@ export function piAuthPresent(): boolean {
return existsSync(piAuthPath());
}
/** Path of the mounted pi model config, or undefined when the scan supplied none. */
export function modelsConfigPath(): string | undefined {
return existsSync(MODELS_CONFIG_PATH) ? MODELS_CONFIG_PATH : undefined;
}
/**
* Where pi persists remote model catalogues. Pinned to the writable agent dir because pi
* otherwise derives it from `dirname(modelsPath)`, which is a read-only mount.
*/
function modelsStorePath(): string {
return path.join(getAgentDir(), 'models-store.json');
}
/**
* Build a ModelRuntime whose only credential is the one supplied. Model catalogs
* stay offline (`allowModelNetwork` defaults to false) so a scan never blocks on
* a catalog refresh.
*
* `modelsPath` is always explicit, never pi's default of `<agent dir>/models.json`: with no
* `--models-config` it is null, which switches models.json off outright, so a stray file in
* that shared dir cannot feed model definitions to a run that did not ask for them.
*
* When the host's pi auth.json is present, the runtime reads it instead: pi's
* disk-backed store resolves the credential. The mount is writable so OAuth
* refreshes persist to the host for subsequent runs.
*/
export async function createModelRuntime(providerId: string, apiKey: string | undefined): Promise<ModelRuntime> {
const modelsPath = modelsConfigPath();
const modelSources = {
modelsPath: modelsPath ?? null,
...(modelsPath ? { modelsStorePath: modelsStorePath() } : {}),
};
if (piAuthPresent()) {
return ModelRuntime.create({ authPath: piAuthPath() });
return ModelRuntime.create({ ...modelSources, authPath: piAuthPath() });
}
return ModelRuntime.create({ credentials: new RuntimeCredentialStore(providerId, apiKey) });
return ModelRuntime.create({ ...modelSources, credentials: new RuntimeCredentialStore(providerId, apiKey) });
}
export interface ModelSelection {
@@ -221,16 +250,13 @@ export interface ModelSelection {
}
/**
* Resolve a model against a runtime.
* Resolve a model against a runtime, returning undefined when the id is unknown.
*
* Direct to a provider, the model must exist in the catalogue. Behind a custom
* endpoint it need not: a gateway may serve models under its own names, so an
* unknown id is passed through on a descriptor borrowed from the provider's
* catalogue for its API dialect. Cost and context window on such a descriptor
* are the reference model's, so spend figures are approximate there.
*
* Returns undefined when the id is unresolvable — unknown with no endpoint
* override, or a provider carrying no models at all.
* The model must exist in the runtime's registry, whether or not an endpoint override
* is in play — a base URL changes the address and nothing else. A gateway serving a
* model under its own name, or one newer than the pinned pi release, is described in a
* `--models-config` file, which puts a real descriptor in the registry rather than
* guessing one from an unrelated model.
*/
export function resolveModel(
modelRuntime: ModelRuntime,
@@ -239,15 +265,9 @@ export function resolveModel(
baseUrl: string | undefined,
): Model<Api> | undefined {
const found = modelRuntime.getModel(providerId, modelId);
if (found) {
return baseUrl ? { ...found, baseUrl } : found;
}
if (!baseUrl) return undefined;
if (!found) return undefined;
const reference = modelRuntime.getModels(providerId)[0];
if (!reference) return undefined;
return { ...reference, id: modelId, name: modelId, baseUrl };
return baseUrl ? { ...found, baseUrl } : found;
}
/**
@@ -276,7 +276,7 @@ function policy(
}
export const CAPELLA_ACTIVITY_POLICIES = Object.freeze({
capellaArchitecture: policy('architecture', 60 * MINUTE_MS, 60 * MINUTE_MS, 5 * MINUTE_MS, 3, 'large'),
capellaArchitecture: policy('architecture', 90 * MINUTE_MS, 90 * MINUTE_MS, 5 * MINUTE_MS, 3, 'large'),
capellaThreatModel: policy('threat-model', 30 * MINUTE_MS, 30 * MINUTE_MS, 5 * MINUTE_MS, 2, 'medium'),
capellaPlan: policy('plan', 30 * MINUTE_MS, 90 * MINUTE_MS, 5 * MINUTE_MS, 2, 'medium'),
capellaResearch: policy('research', 3 * HOUR_MS, 4.5 * HOUR_MS, 5 * MINUTE_MS, 2, 'small + medium'),
+3
View File
@@ -34,6 +34,9 @@ const SAFE_ERROR_MESSAGES: Readonly<Record<ErrorCode, string>> = {
[ErrorCode.TARGET_UNREACHABLE]: 'The target could not be reached.',
[ErrorCode.AUTH_FAILED]: 'Authentication validation failed.',
[ErrorCode.AUTH_LOGIN_FAILED]: 'The configured login could not be completed.',
[ErrorCode.MODEL_NOT_FOUND]:
'The selected model was not found in the harness catalogue. Check SHANNON_AI_MODEL, or supply the model with --models-config.',
[ErrorCode.MODEL_CONFIG_INVALID]: 'The model configuration file could not be used.',
};
const ERROR_CATEGORIES = new Set<PentestErrorType>([
+6
View File
@@ -15,6 +15,12 @@ export const TYPST_TEMPLATE = path.join(WORKER_ROOT, 'templates', 'typst', 'repo
/** Compiled pi extension dir that enforces bounded `bash` timeouts (resolved from dist/) */
export const BASH_TIMEOUT_EXTENSION_DIR = path.join(import.meta.dirname, 'ai', 'extensions', 'bash-timeout');
/**
* Where the CLI mounts a pi model config passed with `--models-config`; its presence is
* what enables models.json. Must match MODELS_CONFIG_CONTAINER_PATH in the CLI package.
*/
export const MODELS_CONFIG_PATH = '/app/models.json';
/** Default deliverables subdirectory relative to repoPath */
export const DEFAULT_DELIVERABLES_SUBDIR = '.shannon/deliverables';
@@ -332,6 +332,14 @@ function classifyByErrorCode(code: ErrorCode, retryableFromError: boolean): { ty
case ErrorCode.AUTH_FAILED:
return { type: 'AuthenticationError', retryable: false };
// Not AuthenticationError: the credential is not in question, and the pipeline
// appends an "is your API key valid" hint to anything classified that way.
case ErrorCode.MODEL_NOT_FOUND:
return { type: 'ModelNotFoundError', retryable: false };
case ErrorCode.MODEL_CONFIG_INVALID:
return { type: 'ModelConfigError', retryable: false };
case ErrorCode.AUTH_LOGIN_FAILED:
return { type: 'AuthLoginFailedError', retryable: false };
+29 -12
View File
@@ -40,6 +40,7 @@ import {
createModelRuntime,
GENERIC_API_KEY_ENV,
type ModelSpec,
modelsConfigPath,
PI_CATALOG_URL,
piAuthPresent,
resolveModel,
@@ -317,7 +318,7 @@ async function validateCredentials(logger: ActivityLogger): Promise<Result<void,
'config',
false,
{},
ErrorCode.AUTH_FAILED,
ErrorCode.MODEL_NOT_FOUND,
),
);
}
@@ -343,28 +344,44 @@ async function validateCredentials(logger: ActivityLogger): Promise<Result<void,
);
}
// 3. Model must exist in the registry, for every provider — Bedrock IDs are the
// easiest to get wrong, since region prefixes and version suffixes differ per
// model (`us.anthropic.claude-opus-5` exists, bare `anthropic.` does not).
// A custom endpoint is exempt: it may serve models under its own names.
// 3. Model must exist in the registry, for every provider and endpoint — Bedrock IDs
// are the easiest to get wrong, since region prefixes and version suffixes differ
// per model (`us.anthropic.claude-opus-5` exists, bare `anthropic.` does not).
// An id the registry lacks is supplied by --models-config, not guessed at here.
const modelRuntime = await createModelRuntime(spec.providerId, credentials.apiKey);
// A model config that fails to parse or compose leaves pi with an empty or fallback
// provider, which would surface below as "model not found" and blame SHANNON_AI_MODEL
// for the file's fault. Report the real cause first.
const modelsConfig = modelsConfigPath();
if (modelsConfig) {
logger.info(`Model config: ${modelsConfig}`);
}
const modelConfigError = modelRuntime.getError();
if (modelConfigError) {
return err(
new PentestError(
`Model configuration is invalid:\n${modelConfigError}`,
'config',
false,
{ providerId: spec.providerId, ...(modelsConfig && { modelsConfig }) },
ErrorCode.MODEL_CONFIG_INVALID,
),
);
}
const baseModel = resolveModel(modelRuntime, spec.providerId, spec.modelId, credentials.baseUrl);
if (!baseModel) {
return err(
new PentestError(
`Model not found in pi registry: provider="${spec.providerId}" model="${spec.modelId}". Check SHANNON_AI_MODEL — browse valid providers and models at ${PI_CATALOG_URL}.`,
`Model not found in pi registry: provider="${spec.providerId}" model="${spec.modelId}". Check SHANNON_AI_MODEL — browse valid providers and models at ${PI_CATALOG_URL}. A model too new for this pi release can be defined in a model config passed with --models-config.`,
'config',
false,
{ providerId: spec.providerId, modelId: spec.modelId },
ErrorCode.AUTH_FAILED,
ErrorCode.MODEL_NOT_FOUND,
),
);
}
if (!modelRuntime.getModel(spec.providerId, spec.modelId)) {
logger.warn(
`Model "${spec.modelId}" is not in the ${spec.providerId} catalogue; passing it to the custom endpoint as given. Cost figures will be approximate.`,
);
}
if (credentials.baseUrl && spec.providerId === 'openai') {
logger.info(`Gateway API: ${baseModel.api}`);
}
@@ -23,6 +23,8 @@ import { ErrorCode } from '../types/errors.js';
*/
const ERROR_TYPE_TO_CODE: Record<string, ErrorCode> = {
AuthenticationError: ErrorCode.AUTH_FAILED,
ModelNotFoundError: ErrorCode.MODEL_NOT_FOUND,
ModelConfigError: ErrorCode.MODEL_CONFIG_INVALID,
ConfigurationError: ErrorCode.CONFIG_VALIDATION_FAILED,
OutputValidationError: ErrorCode.OUTPUT_VALIDATION_FAILED,
AgentExecutionError: ErrorCode.AGENT_EXECUTION_FAILED,
@@ -54,6 +56,8 @@ export function classifyErrorCode(error: unknown): ErrorCode | undefined {
*/
const REMEDIATION_HINTS: Record<string, string> = {
AuthenticationError: "Verify the selected provider's API key is valid and not expired.",
ModelNotFoundError: 'Check SHANNON_AI_MODEL against pi.dev/models, or supply the model with --models-config.',
ModelConfigError: 'Check the --models-config file parses and matches pis models.json schema.',
ConfigurationError: 'Check your CONFIG file path and contents.',
GitError: 'Check repository path and git state.',
InvalidTargetError: 'Verify the target URL is correct and accessible.',
@@ -69,6 +73,8 @@ const REMEDIATION_HINTS: Record<string, string> = {
*/
const SAFE_WORKFLOW_FAILURE_MESSAGES: Readonly<Record<string, string>> = {
AuthenticationError: 'Provider authentication failed.',
ModelNotFoundError: 'The selected model was not found in the harness catalogue.',
ModelConfigError: 'The model configuration file could not be used.',
ConfigurationError: 'The scan configuration is invalid.',
OutputValidationError: 'A scan step returned an unusable result.',
AgentExecutionError: 'An agent could not complete its work.',
+2
View File
@@ -40,6 +40,8 @@ export enum ErrorCode {
TARGET_UNREACHABLE = 'TARGET_UNREACHABLE',
AUTH_FAILED = 'AUTH_FAILED',
AUTH_LOGIN_FAILED = 'AUTH_LOGIN_FAILED',
MODEL_NOT_FOUND = 'MODEL_NOT_FOUND',
MODEL_CONFIG_INVALID = 'MODEL_CONFIG_INVALID',
}
export type PentestErrorType = 'config' | 'network' | 'prompt' | 'filesystem' | 'validation' | 'unknown';
+122 -2
View File
@@ -35,6 +35,8 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com # optional: route thr
This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported.
A model the catalogue does not yet carry, such as one released after Shannon's pinned Pi version, is reachable by describing it yourself. See [Custom model configuration](#custom-model-configuration).
`npx @keygraph/shannon setup` exposes this as the **Other provider** option.
> [!IMPORTANT]
@@ -107,13 +109,15 @@ Bedrock uses bearer-token authentication only. IAM access keys, session tokens,
`SHANNON_AI_BASE_URL` routes model traffic through a proxy or LLM gateway instead of the provider's default endpoint — an LLM gateway such as LiteLLM, a regional endpoint, or any other host you choose. It is a plain endpoint override: it changes only *where* requests go. The provider half of `SHANNON_AI_MODEL` still decides which credential is sent and which API dialect is spoken, and that is unchanged by the base URL.
This works for **any** provider, curated or not. The one rule is that a provider's dialect is fixed, so the endpoint you point at must speak that provider's dialect:
This works for **any** provider, curated or not, subject to two rules. A provider's dialect is fixed, so the endpoint you point at must speak that provider's dialect:
| Provider prefix | Dialect the endpoint must speak |
| --- | --- |
| `anthropic:` | Anthropic Messages |
| `openai:` | OpenAI Responses |
And the model ID must still resolve in the harness catalogue. A base URL changes only the address; it grants no exemption from that check. A gateway serving a model under its own name needs that name described in a [custom model configuration](#custom-model-configuration) file.
Anthropic Messages LLM gateway:
```bash
@@ -132,6 +136,121 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com/v1
`npx @keygraph/shannon setup` configures a base URL two ways: **Custom Base URL** covers the common Anthropic Messages and OpenAI Responses LLM gateways, and **Other provider** takes any provider ID plus an optional base URL of its own.
## Custom model configuration
A model released after Shannon's pinned Pi version is not in the harness catalogue yet, so `SHANNON_AI_MODEL` alone cannot reach it. Rather than wait for a Shannon release, describe the model yourself and pass the file with `--models-config`:
```bash
npx @keygraph/shannon start -u https://example.com -r /path/to/repo --models-config ./models.json
```
```bash
./shannon start -u https://example.com -r ./my-repo --models-config ./models.json
```
[pi.dev/models](https://pi.dev/models) supplies the file contents. Find the model under the provider you want, since the same model has a different ID per provider, then open its page and expand **Show configuration** for a ready-to-paste snippet:
```json
{
"providers": {
"openrouter": {
"apiKey": "YOUR_API_KEY",
"models": [
{
"id": "z-ai/glm-5.3",
"name": "Z.ai: GLM 5.3",
"reasoning": true,
"input": [
"text"
],
"thinkingLevelMap": {
"off": null,
"minimal": null,
"low": "low",
"medium": null,
"high": "high",
"xhigh": null,
"max": "max"
},
"contextWindow": 1048576,
"maxTokens": 943718,
"cost": {
"input": 1.4,
"output": 4.4,
"cacheRead": 0.26,
"cacheWrite": 0
},
"compat": {
"supportsDeveloperRole": false,
"thinkingFormat": "openrouter"
}
}
],
"api": "openai-completions",
"baseUrl": "https://openrouter.ai/api/v1"
}
}
}
```
Then name the model the usual way:
```bash
export SHANNON_AI_API_KEY=your-api-key
export SHANNON_AI_MODEL=openrouter:z-ai/glm-5.3
```
Leave `YOUR_API_KEY` exactly as it is. Shannon sends the credential from your environment, and that takes precedence over anything the file declares, so the file describes the model and never has to hold a secret.
Pi's [models documentation](https://pi.dev/docs/latest/models) describes the full format, including provider routing preferences and compatibility flags.
## Local and self-hosted models
Ollama, LM Studio, vLLM, and any other OpenAI-compatible server are reached through the same mechanism. Describe the server as a provider in a model config file, then name its model with `SHANNON_AI_MODEL`.
> [!IMPORTANT]
> Use `host.docker.internal`, not `localhost`. The scan runs inside a container, so `localhost` points at the container itself rather than at your machine.
A `models.json` for Ollama:
```json
{
"providers": {
"ollama": {
"baseUrl": "http://host.docker.internal:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{ "id": "<model-id>" }
]
}
}
}
```
Then name the model and run:
```bash
export SHANNON_AI_API_KEY=ollama # any value, see below
export SHANNON_AI_MODEL=ollama:<model-id>
./shannon start -u https://example.com -r ./my-repo --models-config ./models.json
```
LM Studio and vLLM take the same shape on their own ports, `http://host.docker.internal:1234/v1` and `http://host.docker.internal:8000/v1` respectively. The provider name is yours to choose, and only has to match the prefix in `SHANNON_AI_MODEL`.
`SHANNON_AI_API_KEY` is still required even though a local server ignores it. Shannon checks that the selected provider has a credential before it starts, so set it to any placeholder value. It is sent to your server and discarded.
> [!IMPORTANT]
> Shannon drives every phase through multi-turn tool use. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves.
Some servers need compatibility flags. If a reasoning-capable model is rejected, turn off the roles it does not understand, at either provider or model level:
```json
"compat": { "supportsDeveloperRole": false, "supportsReasoningEffort": false }
```
Pi's [models documentation](https://pi.dev/docs/latest/models) lists the full set of compatibility flags and local-runtime options.
## OpenAI Codex (ChatGPT Plus/Pro subscription)
A ChatGPT Plus or Pro Codex subscription can run Shannon. Shannon reuses a login created by Pi.
@@ -197,7 +316,8 @@ These instructions apply only to `shannon-v1`.
Checks run before a scan starts, so mistakes fail immediately rather than partway through a run:
- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). A custom base URL exempts the model ID, since an LLM gateway may serve its own names.
- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). To run a model the catalogue does not carry, describe it with [`--models-config`](#custom-model-configuration).
- **Model configuration** — when `--models-config` is passed, the file is parsed and schema-checked before the scan starts, and a fault fails preflight with the offending field named.
- **Credential presence** — validated for the selected provider, or read from Pi when `SHANNON_USE_PI_AUTH=1`.
- **Credential validity** — one minimal request against the model the scan will use, so a rejected key, an exhausted quota, or a model the account cannot reach fails before any agent runs. Bedrock included: its bearer token and region go through the same probe.
+125 -5
View File
@@ -97,7 +97,7 @@ Sample penetration test reports from intentionally vulnerable applications, prod
- **Docker**: required for the worker container.
- **Node.js 18+**: required for the recommended `npx` workflow.
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
- **AI provider credentials**: Shannon runs on Anthropic, OpenAI, xAI, AWS Bedrock, and [any other provider](docs/ai-providers.md#any-other-provider) in the harness catalogue — each of which you can point at a proxy or LLM gateway through a [custom base URL](docs/ai-providers.md#custom-base-url), and a model the catalogue does not yet carry can be described with a [custom model configuration](docs/ai-providers.md#custom-model-configuration). You bring your own key, and Keygraph never proxies your model traffic. Shannon is provider-agnostic. See [AI providers](docs/ai-providers.md#suggested-models) for suggested model IDs.
- **Cyber safeguards cleared with your provider**: Anthropic and OpenAI apply real-time safeguards to cyber-security workloads, which can interrupt a scan mid-run. Complete their guidance for legitimate security testers before your first run - see [AI providers](docs/ai-providers.md#cyber-safeguards-do-this-before-your-first-scan).
@@ -346,11 +346,11 @@ Yes. Shannon emits SARIF 2.1.0, the OASIS standard format for static analysis re
### Which AI providers does Shannon support?
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. Shannon uses a single unified model setting throughout a pentest.
Anthropic, OpenAI, xAI, and AWS Bedrock are built in and configured directly by provider ID. Beyond those, Shannon runs on any provider in the Pi harness catalogue, named the same `<provider>:<model-id>` way. Any provider can be pointed at a proxy or LLM gateway through a custom base URL, which overrides only the endpoint and keeps that provider's API dialect. A model the catalogue does not yet carry, such as one released after Shannon's pinned harness version, runs without waiting for a Shannon release. Describe it in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file and pass it with `--models-config`. Shannon uses a single unified model setting throughout a pentest.
### Can I run Shannon on a local or self-hosted model?
Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and LLM gateways such as LiteLLM. Point Shannon at the endpoint with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [AI providers](docs/ai-providers.md#custom-base-url).
Shannon works with local models served through Ollama, vLLM, or LM Studio, which expose an OpenAI-compatible endpoint, as well as routers such as OpenRouter and LLM gateways such as LiteLLM. A model the harness catalogue does not carry, which most self-hosted models are, is described in a [custom model configuration](docs/ai-providers.md#custom-model-configuration) file passed with `--models-config`; routers and gateways can also be reached with a custom base URL. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves. See [Local and self-hosted models](docs/ai-providers.md#local-and-self-hosted-models).
### Does Shannon actually exploit vulnerabilities, or just scan?
@@ -754,6 +754,8 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com # optional: route thr
This path covers providers whose credential is a single API key. Providers that need more than that are not currently supported.
A model the catalogue does not yet carry, such as one released after Shannon's pinned Pi version, is reachable by describing it yourself. See [Custom model configuration](#custom-model-configuration).
`npx @keygraph/shannon setup` exposes this as the **Other provider** option.
> [!IMPORTANT]
@@ -826,13 +828,15 @@ Bedrock uses bearer-token authentication only. IAM access keys, session tokens,
`SHANNON_AI_BASE_URL` routes model traffic through a proxy or LLM gateway instead of the provider's default endpoint — an LLM gateway such as LiteLLM, a regional endpoint, or any other host you choose. It is a plain endpoint override: it changes only *where* requests go. The provider half of `SHANNON_AI_MODEL` still decides which credential is sent and which API dialect is spoken, and that is unchanged by the base URL.
This works for **any** provider, curated or not. The one rule is that a provider's dialect is fixed, so the endpoint you point at must speak that provider's dialect:
This works for **any** provider, curated or not, subject to two rules. A provider's dialect is fixed, so the endpoint you point at must speak that provider's dialect:
| Provider prefix | Dialect the endpoint must speak |
| --- | --- |
| `anthropic:` | Anthropic Messages |
| `openai:` | OpenAI Responses |
And the model ID must still resolve in the harness catalogue. A base URL changes only the address; it grants no exemption from that check. A gateway serving a model under its own name needs that name described in a [custom model configuration](#custom-model-configuration) file.
Anthropic Messages LLM gateway:
```bash
@@ -851,6 +855,121 @@ export SHANNON_AI_BASE_URL=https://llm-gateway.example.com/v1
`npx @keygraph/shannon setup` configures a base URL two ways: **Custom Base URL** covers the common Anthropic Messages and OpenAI Responses LLM gateways, and **Other provider** takes any provider ID plus an optional base URL of its own.
## Custom model configuration
A model released after Shannon's pinned Pi version is not in the harness catalogue yet, so `SHANNON_AI_MODEL` alone cannot reach it. Rather than wait for a Shannon release, describe the model yourself and pass the file with `--models-config`:
```bash
npx @keygraph/shannon start -u https://example.com -r /path/to/repo --models-config ./models.json
```
```bash
./shannon start -u https://example.com -r ./my-repo --models-config ./models.json
```
[pi.dev/models](https://pi.dev/models) supplies the file contents. Find the model under the provider you want, since the same model has a different ID per provider, then open its page and expand **Show configuration** for a ready-to-paste snippet:
```json
{
"providers": {
"openrouter": {
"apiKey": "YOUR_API_KEY",
"models": [
{
"id": "z-ai/glm-5.3",
"name": "Z.ai: GLM 5.3",
"reasoning": true,
"input": [
"text"
],
"thinkingLevelMap": {
"off": null,
"minimal": null,
"low": "low",
"medium": null,
"high": "high",
"xhigh": null,
"max": "max"
},
"contextWindow": 1048576,
"maxTokens": 943718,
"cost": {
"input": 1.4,
"output": 4.4,
"cacheRead": 0.26,
"cacheWrite": 0
},
"compat": {
"supportsDeveloperRole": false,
"thinkingFormat": "openrouter"
}
}
],
"api": "openai-completions",
"baseUrl": "https://openrouter.ai/api/v1"
}
}
}
```
Then name the model the usual way:
```bash
export SHANNON_AI_API_KEY=your-api-key
export SHANNON_AI_MODEL=openrouter:z-ai/glm-5.3
```
Leave `YOUR_API_KEY` exactly as it is. Shannon sends the credential from your environment, and that takes precedence over anything the file declares, so the file describes the model and never has to hold a secret.
Pi's [models documentation](https://pi.dev/docs/latest/models) describes the full format, including provider routing preferences and compatibility flags.
## Local and self-hosted models
Ollama, LM Studio, vLLM, and any other OpenAI-compatible server are reached through the same mechanism. Describe the server as a provider in a model config file, then name its model with `SHANNON_AI_MODEL`.
> [!IMPORTANT]
> Use `host.docker.internal`, not `localhost`. The scan runs inside a container, so `localhost` points at the container itself rather than at your machine.
A `models.json` for Ollama:
```json
{
"providers": {
"ollama": {
"baseUrl": "http://host.docker.internal:11434/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{ "id": "<model-id>" }
]
}
}
}
```
Then name the model and run:
```bash
export SHANNON_AI_API_KEY=ollama # any value, see below
export SHANNON_AI_MODEL=ollama:<model-id>
./shannon start -u https://example.com -r ./my-repo --models-config ./models.json
```
LM Studio and vLLM take the same shape on their own ports, `http://host.docker.internal:1234/v1` and `http://host.docker.internal:8000/v1` respectively. The provider name is yours to choose, and only has to match the prefix in `SHANNON_AI_MODEL`.
`SHANNON_AI_API_KEY` is still required even though a local server ignores it. Shannon checks that the selected provider has a credential before it starts, so set it to any placeholder value. It is sent to your server and discarded.
> [!IMPORTANT]
> Shannon drives every phase through multi-turn tool use. Capability varies, and a model that does not follow Shannon's instructions or tool-use constraints reliably will produce weaker pentests than a frontier model, so take this path only if you know how your chosen model behaves.
Some servers need compatibility flags. If a reasoning-capable model is rejected, turn off the roles it does not understand, at either provider or model level:
```json
"compat": { "supportsDeveloperRole": false, "supportsReasoningEffort": false }
```
Pi's [models documentation](https://pi.dev/docs/latest/models) lists the full set of compatibility flags and local-runtime options.
## OpenAI Codex (ChatGPT Plus/Pro subscription)
A ChatGPT Plus or Pro Codex subscription can run Shannon. Shannon reuses a login created by Pi.
@@ -916,7 +1035,8 @@ These instructions apply only to `shannon-v1`.
Checks run before a scan starts, so mistakes fail immediately rather than partway through a run:
- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). A custom base URL exempts the model ID, since an LLM gateway may serve its own names.
- **Provider and model ID** — validated against the Pi harness catalogue. An unknown provider or model ID fails preflight with a pointer to [pi.dev/models](https://pi.dev/models). To run a model the catalogue does not carry, describe it with [`--models-config`](#custom-model-configuration).
- **Model configuration** — when `--models-config` is passed, the file is parsed and schema-checked before the scan starts, and a fault fails preflight with the offending field named.
- **Credential presence** — validated for the selected provider, or read from Pi when `SHANNON_USE_PI_AUTH=1`.
- **Credential validity** — one minimal request against the model the scan will use, so a rejected key, an exhausted quota, or a model the account cannot reach fails before any agent runs. Bedrock included: its bearer token and region go through the same probe.
+1 -1
View File
@@ -13,7 +13,7 @@ Use this file as the concise entry point for AI agents and LLMs reading this rep
- [Development](docs/development.md): Source-build workflow, common CLI commands, repository paths, and output locations.
- [Configuration](docs/configuration.md): Authenticated testing, login flows, rules of engagement, report filters, credential precedence, and rate-limit settings.
- [AI Providers](docs/ai-providers.md): Anthropic, OpenAI, xAI, AWS Bedrock, any other Pi-supported provider, and custom LLM gateway setup.
- [AI Providers](docs/ai-providers.md): Anthropic, OpenAI, xAI, AWS Bedrock, any other Pi-supported provider, custom LLM gateway setup, custom model configuration for models not yet in the Pi catalogue, and local self-hosted runtimes (Ollama, LM Studio, vLLM).
- [Platforms and Networking](docs/platforms.md): Windows/WSL2, Linux, macOS, Docker networking, local applications, and custom hostnames.
- [Workspaces and Resuming](docs/workspaces.md): Workspace storage, naming, resuming interrupted scans, and examples.
- [Safety and Limitations](docs/safety.md): Authorized-use requirements, non-production guidance, mutative effects, model caveats, scope limits, cost, and performance.
+40 -73
View File
@@ -46,17 +46,17 @@ importers:
apps/worker:
dependencies:
'@earendil-works/pi-agent-core':
specifier: ^0.84.2
version: 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
specifier: ^0.84.4
version: 0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-ai':
specifier: ^0.84.2
version: 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
specifier: ^0.84.4
version: 0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-coding-agent':
specifier: ^0.84.2
version: 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
specifier: ^0.84.4
version: 0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@gotgenes/pi-permission-system':
specifier: ^10.9.0
version: 10.9.0(@earendil-works/pi-coding-agent@0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6))(@earendil-works/pi-tui@0.84.2)
version: 10.9.0(@earendil-works/pi-coding-agent@0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6))(@earendil-works/pi-tui@0.84.4)
'@temporalio/activity':
specifier: 1.15.0
version: 1.15.0
@@ -295,34 +295,34 @@ packages:
'@clack/prompts@1.1.0':
resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==}
'@earendil-works/pi-agent-core@0.84.2':
resolution: {integrity: sha512-8Pn3wSCxj0cfo5I6jxQYVB/3uuQRmHhAlEclyjqpOuMEdQMIODHizRogv56FLdbU+dTiGnybeHQ2N+sV1/L2YA==}
'@earendil-works/pi-agent-core@0.84.4':
resolution: {integrity: sha512-HyUnjaOXj6oN/6SNcr8A1J/ElRQA50FtIE0XUTSKAQVqmdlb9qdojOyUQwF/jULE5+yOEtGuVgi/N1RnBiNG+g==}
engines: {node: '>=22.19.0'}
'@earendil-works/pi-ai@0.84.2':
resolution: {integrity: sha512-6MzsrYIYNVlE7SfpbL2yYb67Qo58p/7Q+xWG1RZvoX1P80aRCHSod2/13aFpxkow1lPO2LEh3c495J0Gwmyjig==}
'@earendil-works/pi-ai@0.84.4':
resolution: {integrity: sha512-AClAZxf5+c4RRu44NJPS6wyQy+Nmq+Mzyyrdvm4ZVMNuixelO02RZX4G4Aq1F145Yzp43wnM5S+hLlSI7ypfVw==}
engines: {node: '>=22.19.0'}
hasBin: true
'@earendil-works/pi-client@0.84.2':
resolution: {integrity: sha512-/RFSPhD/bZbpOp1oJj+UneSUFSgZhWxzcSENUY+8+8xhoBrWXMYI2t77XNx4Yf+c8YK2qTHquForhNcelYpXvg==}
'@earendil-works/pi-client@0.84.4':
resolution: {integrity: sha512-q398WY/3ZQHTizk7IKxApzqFV0xt4yM9LkSkwyqeLK5Bj5RwRjOWxESt26z4LgNp4O+8hqhqFPf/8fj4H5rE4A==}
engines: {node: '>=22.19.0'}
'@earendil-works/pi-coding-agent@0.84.2':
resolution: {integrity: sha512-l4E+B7hgXKWddRo8bC/eSue2aWZjEgJ9xIpf5p0Og+lq8a2TArCwJ0HCoCPCgaBP/tN4zbYH/wOwvx9pJpeLCA==}
'@earendil-works/pi-coding-agent@0.84.4':
resolution: {integrity: sha512-jmOlrqUmvhh/siNWFRXjYLJzhKFIHNsAQaysRwzQPQFnPAaV/vhqHsLH/MBsIISA1Rjj7WTUFR3nJrpXoLx39w==}
engines: {node: '>=22.19.0'}
hasBin: true
'@earendil-works/pi-protocol@0.84.2':
resolution: {integrity: sha512-jbBh03fkeckWEroHpcZBr4w5/Ibat8WwdXFlXHivYQImrQNFtLpDeL0t1cku4hmK0q3pceIRQHkw4fwbM4YILQ==}
'@earendil-works/pi-protocol@0.84.4':
resolution: {integrity: sha512-acyE9ozxkMiWiz/xyWpU0O9vwnYv0hyG889Vniv6Sg9c9zfsX+8MePnDNphBacY2Fvm1rxdsGmiVDSZl9yuDFA==}
engines: {node: '>=22.19.0'}
'@earendil-works/pi-telemetry@0.84.2':
resolution: {integrity: sha512-wg5caea7uIv1BHRBm2Y116RvFG4oSAiP5qk9tA2463PDGIr4K8M1Ceyyg5DOpF/shUUl0gk826yQJAeAcHYB9g==}
'@earendil-works/pi-telemetry@0.84.4':
resolution: {integrity: sha512-8e2CuxM+ht+hedQXTZmi5JVl6/xDK9RpSDL2+MbITevKYQhMZ/z6lJOTFgox3HQyGxO8mOZEtYGVeQNaD4OzqA==}
engines: {node: '>=22.19.0'}
'@earendil-works/pi-tui@0.84.2':
resolution: {integrity: sha512-ds2TLihOnM5sLJB3VpXV6y0uR5efVuHf4MN7yDpsty6hA2DUO/EDVzjp/0od0G2JslzVLMjT8T8zavtxVb+qbg==}
'@earendil-works/pi-tui@0.84.4':
resolution: {integrity: sha512-nPUnwDkLtupPXnZQYrCwPFcuTydCDqTY6ZbFqhsL4S4kVq0AT418kPa/6uXwtaCD+MjBNBltb7ScTYX65yeE1w==}
engines: {node: '>=22.19.0'}
'@emnapi/core@1.9.1':
@@ -588,10 +588,6 @@ packages:
'@nodable/entities@2.1.1':
resolution: {integrity: sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==}
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
'@oxc-project/types@0.122.0':
resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
@@ -1360,10 +1356,6 @@ packages:
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
glob@13.0.6:
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
engines: {node: 18 || 20 || >=22}
google-auth-library@10.7.0:
resolution: {integrity: sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==}
engines: {node: '>=18'}
@@ -1575,10 +1567,6 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
minipass@7.1.3:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1665,10 +1653,6 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-scurry@2.0.2:
resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
engines: {node: 18 || 20 || >=22}
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
@@ -2456,10 +2440,10 @@ snapshots:
'@clack/core': 1.1.0
sisteransi: 1.0.5
'@earendil-works/pi-agent-core@0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
'@earendil-works/pi-agent-core@0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-ai': 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-telemetry': 0.84.2
'@earendil-works/pi-ai': 0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-telemetry': 0.84.4
diff: 8.0.4
ignore: 7.0.5
typebox: 1.3.7
@@ -2472,13 +2456,12 @@ snapshots:
- ws
- zod
'@earendil-works/pi-ai@0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
'@earendil-works/pi-ai@0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
'@aws-sdk/client-bedrock-runtime': 3.1048.0
'@earendil-works/pi-telemetry': 0.84.2
'@earendil-works/pi-telemetry': 0.84.4
'@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))
'@opentelemetry/api': 1.9.0
'@smithy/node-http-handler': 4.7.3
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
@@ -2493,22 +2476,21 @@ snapshots:
- ws
- zod
'@earendil-works/pi-client@0.84.2':
'@earendil-works/pi-client@0.84.4':
dependencies:
'@earendil-works/pi-protocol': 0.84.2
'@earendil-works/pi-protocol': 0.84.4
'@earendil-works/pi-coding-agent@0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
'@earendil-works/pi-coding-agent@0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-agent-core': 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-client': 0.84.2
'@earendil-works/pi-protocol': 0.84.2
'@earendil-works/pi-tui': 0.84.2
'@earendil-works/pi-agent-core': 0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-ai': 0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-client': 0.84.4
'@earendil-works/pi-protocol': 0.84.4
'@earendil-works/pi-tui': 0.84.4
'@silvia-odwyer/photon-node': 0.3.4
chalk: 5.6.2
cross-spawn: 7.0.6
diff: 8.0.4
glob: 13.0.6
grok-mermaid: 0.2.2
highlight.js: 10.7.3
hosted-git-info: 9.0.3
@@ -2530,13 +2512,13 @@ snapshots:
- ws
- zod
'@earendil-works/pi-protocol@0.84.2':
'@earendil-works/pi-protocol@0.84.4':
dependencies:
typebox: 1.3.7
'@earendil-works/pi-telemetry@0.84.2': {}
'@earendil-works/pi-telemetry@0.84.4': {}
'@earendil-works/pi-tui@0.84.2':
'@earendil-works/pi-tui@0.84.4':
dependencies:
get-east-asian-width: 1.6.0
marked: 18.0.5
@@ -2570,10 +2552,10 @@ snapshots:
- supports-color
- utf-8-validate
'@gotgenes/pi-permission-system@10.9.0(@earendil-works/pi-coding-agent@0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6))(@earendil-works/pi-tui@0.84.2)':
'@gotgenes/pi-permission-system@10.9.0(@earendil-works/pi-coding-agent@0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6))(@earendil-works/pi-tui@0.84.4)':
dependencies:
'@earendil-works/pi-coding-agent': 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-tui': 0.84.2
'@earendil-works/pi-coding-agent': 0.84.4(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
'@earendil-works/pi-tui': 0.84.4
tree-sitter-bash: 0.25.1
web-tree-sitter: 0.26.9
transitivePeerDependencies:
@@ -2820,8 +2802,6 @@ snapshots:
'@nodable/entities@2.1.1': {}
'@opentelemetry/api@1.9.0': {}
'@oxc-project/types@0.122.0': {}
'@protobufjs/aspromise@1.1.2': {}
@@ -3599,12 +3579,6 @@ snapshots:
glob-to-regexp@0.4.1: {}
glob@13.0.6:
dependencies:
minimatch: 10.2.5
minipass: 7.1.3
path-scurry: 2.0.2
google-auth-library@10.7.0:
dependencies:
base64-js: 1.5.1
@@ -3813,8 +3787,6 @@ snapshots:
minimist@1.2.8: {}
minipass@7.1.3: {}
ms@2.1.3: {}
ms@3.0.0-canary.1: {}
@@ -3877,11 +3849,6 @@ snapshots:
path-key@3.1.1: {}
path-scurry@2.0.2:
dependencies:
lru-cache: 11.5.1
minipass: 7.1.3
path-to-regexp@8.4.2:
optional: true