mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-10-04 23:36:54 +02:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbf2019e7f | ||
|
|
692440135a | ||
|
|
fabf56e574 | ||
|
|
85a29e2ea3 | ||
|
|
82436dafe6 | ||
|
|
8a3fbc5286 | ||
|
|
d9748355e3 | ||
|
|
9a5f201f15 | ||
|
|
945fd2ce57 | ||
|
|
4b26e677dd | ||
|
|
c8ee22f2d1 | ||
|
|
529f6a7a95 | ||
|
|
5a43c6127f | ||
|
|
9f7a349715 | ||
|
|
d017d74519 | ||
|
|
6108de3cfc | ||
|
|
7e0464bf79 |
No files matched your search
@@ -53,3 +53,8 @@ SHANNON_AI_MODEL=anthropic:claude-sonnet-4-6
|
|||||||
# https://github.com/KeygraphHQ/shannon/blob/main/docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription
|
# https://github.com/KeygraphHQ/shannon/blob/main/docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription
|
||||||
# SHANNON_USE_PI_AUTH=1
|
# SHANNON_USE_PI_AUTH=1
|
||||||
# SHANNON_AI_MODEL=openai-codex:gpt-5.5
|
# SHANNON_AI_MODEL=openai-codex:gpt-5.5
|
||||||
|
|
||||||
|
# Or the guide below to use an xAI subscription
|
||||||
|
# https://github.com/KeygraphHQ/shannon/blob/main/docs/ai-providers.md#xai-grok-subscription
|
||||||
|
# SHANNON_USE_PI_AUTH=1
|
||||||
|
# SHANNON_AI_MODEL=xai:grok-4.6
|
||||||
@@ -5,3 +5,5 @@ credentials/
|
|||||||
dist/
|
dist/
|
||||||
repos/
|
repos/
|
||||||
.turbo/
|
.turbo/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
@@ -155,7 +155,7 @@ Durable workflow orchestration with crash recovery, queryable progress, intellig
|
|||||||
- **Configuration** — YAML configs in `apps/worker/configs/` with JSON Schema validation (`config-schema.json`). Supports auth settings (MFA/TOTP), URL/code rule scoping (`rules.avoid`/`rules.focus`), run-scope steering (`vuln_classes`, `exploit`), free-form `rules_of_engagement`, and post-hoc `report` options (`min_severity`, `min_confidence`, `guidance`, and `sarif` for a SARIF 2.1.0 log via `apps/worker/src/services/sarif-renderer.ts`, on by default for exploit runs and opt out with `report.sarif: false`). `code_path` avoid rules are enforced via the `@gotgenes/pi-permission-system` extension: `apps/worker/src/temporal/activities.ts:syncCodePathDenyRules` writes a global `path` deny config once per workflow (`apps/worker/src/ai/pi/permission-system.ts:syncPermissionSystemConfig`), and the executor loads the extension when that config is present (`apps/worker/src/ai/pi/pi-executor.ts`), so denies fire across every tool and child `task` session. `vuln_classes`/`exploit` scope is locked into `session.json` on first run; resumes with a different scope fail fast (`persistOrValidateRunScope`). Credential resolution — local mode: env vars → `./.env`; npx mode: env vars → `~/.shannon/config.toml` (via `npx @keygraph/shannon setup`)
|
- **Configuration** — YAML configs in `apps/worker/configs/` with JSON Schema validation (`config-schema.json`). Supports auth settings (MFA/TOTP), URL/code rule scoping (`rules.avoid`/`rules.focus`), run-scope steering (`vuln_classes`, `exploit`), free-form `rules_of_engagement`, and post-hoc `report` options (`min_severity`, `min_confidence`, `guidance`, and `sarif` for a SARIF 2.1.0 log via `apps/worker/src/services/sarif-renderer.ts`, on by default for exploit runs and opt out with `report.sarif: false`). `code_path` avoid rules are enforced via the `@gotgenes/pi-permission-system` extension: `apps/worker/src/temporal/activities.ts:syncCodePathDenyRules` writes a global `path` deny config once per workflow (`apps/worker/src/ai/pi/permission-system.ts:syncPermissionSystemConfig`), and the executor loads the extension when that config is present (`apps/worker/src/ai/pi/pi-executor.ts`), so denies fire across every tool and child `task` session. `vuln_classes`/`exploit` scope is locked into `session.json` on first run; resumes with a different scope fail fast (`persistOrValidateRunScope`). Credential resolution — local mode: env vars → `./.env`; npx mode: env vars → `~/.shannon/config.toml` (via `npx @keygraph/shannon setup`)
|
||||||
- **Prompts** — Per-phase templates in `apps/worker/prompts/` with variable substitution (`{{TARGET_URL}}`, `{{CONFIG_CONTEXT}}`). Shared partials in `apps/worker/prompts/shared/` via `apps/worker/src/services/prompt-manager.ts`, including `_code-path-rules.txt` (focus/avoid `[FILE]`/`[GLOB]` routing) and `_rules-of-engagement.txt` (free-text engagement rules). When `exploit: false`, `apps/worker/src/services/findings-renderer.ts` deterministically converts each `*_exploitation_queue.json` into a `*_findings.md` for report assembly — no LLM in the loop
|
- **Prompts** — Per-phase templates in `apps/worker/prompts/` with variable substitution (`{{TARGET_URL}}`, `{{CONFIG_CONTEXT}}`). Shared partials in `apps/worker/prompts/shared/` via `apps/worker/src/services/prompt-manager.ts`, including `_code-path-rules.txt` (focus/avoid `[FILE]`/`[GLOB]` routing) and `_rules-of-engagement.txt` (free-text engagement rules). When `exploit: false`, `apps/worker/src/services/findings-renderer.ts` deterministically converts each `*_exploitation_queue.json` into a `*_findings.md` for report assembly — no LLM in the loop
|
||||||
- **Agent Harness (pi)** — Uses the **pi harness** (`@earendil-works/pi-coding-agent`, requires Node ≥ 22.19) via `apps/worker/src/ai/pi/pi-executor.ts` (`runPiPrompt` → `createAgentSession`). Retry is split in `apps/worker/src/ai/pi/retry-settings.ts`: pi's agent-level loop is off so Temporal owns agent restarts, while `provider.maxRetries` stays on — pi reads the `provider` block independently of the `enabled` flag — so transport faults are absorbed in-session rather than costing a full agent re-run. `maxRetryDelayMs` is left at pi's 60s default. One model runs every phase, named by `SHANNON_AI_MODEL=<provider>:<model-id>` (default `anthropic:claude-sonnet-4-6`). `apps/worker/src/ai/models.ts` parses the spec — splitting on the **first** colon only, so Bedrock IDs keep theirs — and resolves it through pi's `ModelRuntime`. pi ships the `CredentialStore` interface but no in-memory implementation (its own reads `auth.json` from disk), so `RuntimeCredentialStore` in that file supplies one: credentials arrive as env vars in an ephemeral container and must never touch disk. `createModelRuntime(providerId, apiKey)` builds the runtime; `allowModelNetwork` stays at its default `false` so a scan never blocks on a catalog refresh. `resolveModelSelection()` is **async** because `ModelRuntime.create()` is. Any pi-ai provider id is accepted — `parseModelSpec` no longer rejects against a hardcoded list, so pi's registry is the authority (an unknown provider/model surfaces as a clear "not found in pi registry" error at preflight, which points to the browsable catalogue at `pi.dev/models` — `PI_CATALOG_URL` in `apps/worker/src/ai/models.ts`, appended to the not-found errors and shown in the setup wizard's "Other provider" hint). Four providers are **curated** (`CURATED_PROVIDERS`: `anthropic`, `openai`, `xai`, `amazon-bedrock`) with their own credential variables, config sections, and setup flows; each provider's API key env var is declared once in `PROVIDER_API_KEY_ENV` — Shannon uses each vendor's own variable name (`OPENAI_API_KEY`, `XAI_API_KEY`, …), never an invented one; Bedrock's entry is `AWS_BEARER_TOKEN_BEDROCK`, paired with `AWS_REGION`, which preflight requires separately as provider config rather than a credential. Any other provider uses the **generic** credential path: `SHANNON_AI_API_KEY` (`GENERIC_API_KEY_ENV`) supplies the key for any provider whose credential is a plain API key. Curated providers' own variables take precedence over it, and it also works as a fallback for them — Bedrock is the sole exception (it authenticates through its AWS_ variables, so the generic key never stands in for it). The CLI forwards `SHANNON_AI_API_KEY` in `COMMON_FORWARD_VARS` (it is provider-neutral, binding to whatever `SHANNON_AI_MODEL` names, so the "only one provider configured" guard counts only named credentials), and stores it under a generic `[provider]` config.toml section (`provider.api_key`). `npx @keygraph/shannon setup` exposes this as the "Other provider" option: free-text provider id + model id + key (a curated provider id is rejected there, since it has its own option). `SHANNON_AI_BASE_URL` overrides the endpoint for any provider (proxies/gateways); the credential is unchanged. `pointAtGateway` (`apps/worker/src/ai/models.ts`) applies the one dialect change: behind a base URL, `openai` follows `SHANNON_AI_OPENAI_FORMAT` (`chat-completions` default, or `responses`). On `chat-completions` it switches the API to `openai-completions` and drops the catalogue's Responses-shaped `compat` block so pi's `detectCompat` derives completions settings; on `responses` the descriptor is unchanged but for the endpoint. `resolveGatewayFormat` rejects the variable when the provider is not `openai` or no base URL is set, since it cannot take effect there. All other providers keep their API. The CLI mirrors the accepted values in `apps/cli/src/model-spec.ts`, forwards the variable in `COMMON_FORWARD_VARS`, and maps it to `openai.format` in config.toml. `buildEnvFlags` forwards only the selected provider's credential into the worker container. The CLI mirrors the parse rule and the provider/credential tables in `apps/cli/src/model-spec.ts` (it cannot import from the worker package); the two must stay in sync. pi ships no JSON-schema output or `Task`/`TodoWrite` built-ins, so structured queues are captured via a `submit_exploitation_queue` custom tool (`apps/worker/src/ai/queue-schemas.ts`), and `task` (child sessions scoped to `read`, `grep`, `find`, `ls`, `write`, and `bash` — no nested `task` or collector tools; `CHILD_TOOLS` in `apps/worker/src/ai/pi/task-tool.ts`) + `todo_write` (`apps/worker/src/ai/pi/session-tools.ts`) are provided as custom tools; the per-phase collectors are pi custom tools (TypeBox `defineTool` in `apps/worker/src/collectors/`). Shannon sets no thinking configuration at all — no `thinkingLevel` is passed to any `createAgentSession` call, so pi's own default applies. There Line truncated
|
- **Agent Harness (pi)** — Uses the **pi harness** (`@earendil-works/pi-coding-agent`, requires Node ≥ 22.19) via `apps/worker/src/ai/pi/pi-executor.ts` (`runPiPrompt` → `createAgentSession`). Retry is split in `apps/worker/src/ai/pi/retry-settings.ts`: pi's agent-level loop is off so Temporal owns agent restarts, while `provider.maxRetries` stays on — pi reads the `provider` block independently of the `enabled` flag — so transport faults are absorbed in-session rather than costing a full agent re-run. `maxRetryDelayMs` is left at pi's 60s default. One model runs every phase, named by `SHANNON_AI_MODEL=<provider>:<model-id>` (default `anthropic:claude-sonnet-4-6`). `apps/worker/src/ai/models.ts` parses the spec — splitting on the **first** colon only, so Bedrock IDs keep theirs — and resolves it through pi's `ModelRuntime`. pi ships the `CredentialStore` interface but no in-memory implementation (its own reads `auth.json` from disk), so `RuntimeCredentialStore` in that file supplies one: credentials arrive as env vars in an ephemeral container and must never touch disk. `createModelRuntime(providerId, apiKey)` builds the runtime; `allowModelNetwork` stays at its default `false` so a scan never blocks on a catalog refresh. `resolveModelSelection()` is **async** because `ModelRuntime.create()` is. Any pi-ai provider id is accepted — `parseModelSpec` no longer rejects against a hardcoded list, so pi's registry is the authority (an unknown provider/model surfaces as a clear "not found in pi registry" error at preflight, which points to the browsable catalogue at `pi.dev/models` — `PI_CATALOG_URL` in `apps/worker/src/ai/models.ts`, appended to the not-found errors and shown in the setup wizard's "Other provider" hint). Four providers are **curated** (`CURATED_PROVIDERS`: `anthropic`, `openai`, `xai`, `amazon-bedrock`) with their own credential variables, config sections, and setup flows; each provider's API key env var is declared once in `PROVIDER_API_KEY_ENV` — Shannon uses each vendor's own variable name (`OPENAI_API_KEY`, `XAI_API_KEY`, …), never an invented one; Bedrock's entry is `AWS_BEARER_TOKEN_BEDROCK`, paired with `AWS_REGION`, which preflight requires separately as provider config rather than a credential. Any other provider uses the **generic** credential path: `SHANNON_AI_API_KEY` (`GENERIC_API_KEY_ENV`) supplies the key for any provider whose credential is a plain API key. Curated providers' own variables take precedence over it, and it also works as a fallback for them — Bedrock is the sole exception (it authenticates through its AWS_ variables, so the generic key never stands in for it). The CLI forwards `SHANNON_AI_API_KEY` in `COMMON_FORWARD_VARS` (it is provider-neutral, binding to whatever `SHANNON_AI_MODEL` names, so the "only one provider configured" guard counts only named credentials), and stores it under a generic `[provider]` config.toml section (`provider.api_key`). `npx @keygraph/shannon setup` exposes this as the "Other provider" option: free-text provider id + model id + key (a curated provider id is rejected there, since it has its own option). `SHANNON_AI_BASE_URL` overrides the endpoint for any provider (proxies/gateways); the credential is unchanged. `pointAtGateway` (`apps/worker/src/ai/models.ts`) applies the one dialect change: behind a base URL, `openai` follows `SHANNON_AI_OPENAI_FORMAT` (`chat-completions` default, or `responses`). On `chat-completions` it switches the API to `openai-completions` and drops the catalogue's Responses-shaped `compat` block so pi's `detectCompat` derives completions settings; on `responses` the descriptor is unchanged but for the endpoint. `resolveGatewayFormat` rejects the variable when the provider is not `openai` or no base URL is set, since it cannot take effect there. All other providers keep their API. The CLI mirrors the accepted values in `apps/cli/src/model-spec.ts`, forwards the variable in `COMMON_FORWARD_VARS`, and maps it to `openai.format` in config.toml. `buildEnvFlags` forwards only the selected provider's credential into the worker container. The CLI mirrors the parse rule and the provider/credential tables in `apps/cli/src/model-spec.ts` (it cannot import from the worker package); the two must stay in sync. pi ships no JSON-schema output or `Task`/`TodoWrite` built-ins, so structured queues are captured via a `submit_exploitation_queue` custom tool (`apps/worker/src/ai/queue-schemas.ts`), and `task` (child sessions scoped to `read`, `grep`, `find`, `ls`, `write`, and `bash` — no nested `task` or collector tools; `CHILD_TOOLS` in `apps/worker/src/ai/pi/task-tool.ts`) + `todo_write` (`apps/worker/src/ai/pi/session-tools.ts`) are provided as custom tools; the per-phase collectors are pi custom tools (TypeBox `defineTool` in `apps/worker/src/collectors/`). Shannon sets no thinking configuration at all — no `thinkingLevel` is passed to any `createAgentSession` call, so pi's own default applies. There Line truncated
|
||||||
- **Pi Credential Reuse** — `SHANNON_USE_PI_AUTH=1` opts into reusing the host's Pi login, including an `openai-codex` ChatGPT Plus/Pro subscription selected with `SHANNON_AI_MODEL=openai-codex:<model-id>`. `apps/cli/src/env.ts` requires `~/.pi/agent/auth.json`; `start.ts` passes its path to `spawnWorker`, which mounts only that file read-write at `/tmp/.pi/agent/auth.json`. The flag itself is not forwarded: the worker detects the file with `piAuthPresent()` and passes its path to `ModelRuntime.create`. CLI and worker API-key presence checks are skipped on this path, but the normal preflight model probe still validates the credential. The image and UID-remapping entrypoint keep `/tmp/.pi/agent` owned by `pentest` so adjacent Pi/Shannon configuration remains writable. Refreshed OAuth state is persisted to the host for subsequent scans.
|
- **Pi Credential Reuse** — `SHANNON_USE_PI_AUTH=1` opts into reusing the host's Pi login, including an `openai-codex` ChatGPT Plus/Pro subscription (`SHANNON_AI_MODEL=openai-codex:<model-id>`) or an `xai` Grok subscription (`SHANNON_AI_MODEL=xai:<model-id>`); the mechanism is provider-agnostic and works for any Pi login. `apps/cli/src/env.ts` requires `~/.pi/agent/auth.json`; `start.ts` passes its path to `spawnWorker`, which mounts only that file read-write at `/tmp/.pi/agent/auth.json`. The flag itself is not forwarded: the worker detects the file with `piAuthPresent()` and passes its path to `ModelRuntime.create`. CLI and worker API-key presence checks are skipped on this path, but the normal preflight model probe still validates the credential. The image and UID-remapping entrypoint keep `/tmp/.pi/agent` owned by `pentest` so adjacent Pi/Shannon configuration remains writable. Refreshed OAuth state is persisted to the host for subsequent scans.
|
||||||
- **Audit System** — Crash-safe append-only logging in `workspaces/{hostname}_{sessionId}/`. The run directory's top level holds the human-facing report in both formats (`Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`, `FINAL_REPORT_PDF_FILENAME`/`FINAL_REPORT_MD_FILENAME` in `apps/worker/src/paths.ts`); everything else — deliverables, per-agent logs, prompts, `session.json`, `workflow.log`, and browser artifacts — is nested under a hidden `.shannon/` internals dir (`INTERNAL_DIR`) so a customer sees only the report. Audit path helpers route through `generateInternalPath` (`apps/worker/src/audit/utils.ts`); the CLI nests the overlay backing dirs under the same `.shannon/` (`apps/cli/src/docker.ts`, `start.ts`). `session.json`/`workflow.log` reads use dual-read resolvers (`resolveSessionJsonPath`, `resolveRunFile`) that prefer `.shannon/` and fall back to the legacy run-root layout, so pre-restructure workspaces stay listable (`workspaces`/`logs`) without migration. Resuming a pre-restructure workspace upgrades it in place first: `migrateLegacyWorkspaceLayout` (`apps/cli/src/commands/start.ts`) renames the flat deliverables/logs/session entries into `.shannon/` (carrying the deliverables `.git` along) before the overlay dirs are mounted, so resume finds the old checkpoints instead of re-running every agent. The report agent writes structured findings to `report.json`, from which `report-renderer.ts` renders the assembled markdown and `report-json-adapter.ts` produces the Typst-shaped JSON that `pdf-renderer.ts` compiles into `comprehensive_security_assessment_report.pdf` using the bundled `apps/worker/templates/typst/report.typ` template (the `typst` binary is installed in the worker image). `copyReportToRunRoot` (`apps/worker/src/services/reporting.ts`) surfaces both the PDF and the markdown to the run root as `Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`; the deliverables-dir copies remain as the git-checkpointed sources. PDF compilation is best-effort — a failure is logged and the run still completes. WorkflowLogger (`apps/worker/src/audit/workflow-logger.ts`) provides unified human-readable per-workflow logs, backed by LogStream (`apps/worker/src/audit/log-stream.ts`) shared stream primitive
|
- **Audit System** — Crash-safe append-only logging in `workspaces/{hostname}_{sessionId}/`. The run directory's top level holds the human-facing report in both formats (`Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`, `FINAL_REPORT_PDF_FILENAME`/`FINAL_REPORT_MD_FILENAME` in `apps/worker/src/paths.ts`); everything else — deliverables, per-agent logs, prompts, `session.json`, `workflow.log`, and browser artifacts — is nested under a hidden `.shannon/` internals dir (`INTERNAL_DIR`) so a customer sees only the report. Audit path helpers route through `generateInternalPath` (`apps/worker/src/audit/utils.ts`); the CLI nests the overlay backing dirs under the same `.shannon/` (`apps/cli/src/docker.ts`, `start.ts`). `session.json`/`workflow.log` reads use dual-read resolvers (`resolveSessionJsonPath`, `resolveRunFile`) that prefer `.shannon/` and fall back to the legacy run-root layout, so pre-restructure workspaces stay listable (`workspaces`/`logs`) without migration. Resuming a pre-restructure workspace upgrades it in place first: `migrateLegacyWorkspaceLayout` (`apps/cli/src/commands/start.ts`) renames the flat deliverables/logs/session entries into `.shannon/` (carrying the deliverables `.git` along) before the overlay dirs are mounted, so resume finds the old checkpoints instead of re-running every agent. The report agent writes structured findings to `report.json`, from which `report-renderer.ts` renders the assembled markdown and `report-json-adapter.ts` produces the Typst-shaped JSON that `pdf-renderer.ts` compiles into `comprehensive_security_assessment_report.pdf` using the bundled `apps/worker/templates/typst/report.typ` template (the `typst` binary is installed in the worker image). `copyReportToRunRoot` (`apps/worker/src/services/reporting.ts`) surfaces both the PDF and the markdown to the run root as `Security-Assessment-Report.pdf` and `Security-Assessment-Report.md`; the deliverables-dir copies remain as the git-checkpointed sources. PDF compilation is best-effort — a failure is logged and the run still completes. WorkflowLogger (`apps/worker/src/audit/workflow-logger.ts`) provides unified human-readable per-workflow logs, backed by LogStream (`apps/worker/src/audit/log-stream.ts`) shared stream primitive
|
||||||
- **Deliverables** — Saved to `.shannon/deliverables/` in the target repo via the `save-deliverable` CLI script (`apps/worker/src/scripts/save-deliverable.ts`)
|
- **Deliverables** — Saved to `.shannon/deliverables/` in the target repo via the `save-deliverable` CLI script (`apps/worker/src/scripts/save-deliverable.ts`)
|
||||||
- **Workspaces & Resume** — Named workspaces via `-w <name>` or auto-named from URL+timestamp. Resume detects completed agents via `session.json`. `loadResumeState()` in `apps/worker/src/temporal/activities.ts` validates deliverable existence, restores git checkpoints, and cleans up incomplete deliverables
|
- **Workspaces & Resume** — Named workspaces via `-w <name>` or auto-named from URL+timestamp. Resume detects completed agents via `session.json`. `loadResumeState()` in `apps/worker/src/temporal/activities.ts` validates deliverable existence, restores git checkpoints, and cleans up incomplete deliverables
|
||||||
|
|||||||
@@ -57,10 +57,16 @@ Thanks to tools like Claude Code and Cursor, your team ships code non-stop. But
|
|||||||
|
|
||||||
Shannon closes that gap by providing on-demand, automated penetration testing that can run against every build or release.
|
Shannon closes that gap by providing on-demand, automated penetration testing that can run against every build or release.
|
||||||
|
|
||||||
|
### Why "Shannon"?
|
||||||
|
|
||||||
|
It's named after Claude Shannon, the father of information theory. At its core, pentesting is an information problem: every probe reduces uncertainty about a system's state. The best tools maximize the signal gained from every request, turning those bits of knowledge into an exploit path.
|
||||||
|
|
||||||
|
Also, we wanted you to be able to say, "Hey Claude, run Shannon" to find all the security flaws in your vibe-coded app.
|
||||||
|
|
||||||
## Shannon in Action
|
## Shannon in Action
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="assets/shannon-action.gif" alt="Shannon running an autonomous pentest" width="100%">
|
<img src="assets/Shannon3GIF.gif" alt="Shannon running an autonomous pentest" width="100%">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Sample penetration test reports from intentionally vulnerable applications, produced by Shannon Open Source:
|
Sample penetration test reports from intentionally vulnerable applications, produced by Shannon Open Source:
|
||||||
@@ -101,6 +107,7 @@ For source builds, authenticated scans, provider-specific setup, and platform no
|
|||||||
> **Prefer to use a subscription instead of API credits?**
|
> **Prefer to use a subscription instead of API credits?**
|
||||||
>
|
>
|
||||||
> - **OpenAI Codex:** The latest version of Shannon supports ChatGPT Plus and Pro subscriptions. Follow the [OpenAI Codex subscription setup guide](docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription) to get started.
|
> - **OpenAI Codex:** The latest version of Shannon supports ChatGPT Plus and Pro subscriptions. Follow the [OpenAI Codex subscription setup guide](docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription) to get started.
|
||||||
|
> - **xAI (Grok):** The latest version of Shannon supports xAI subscriptions. Follow the [xAI subscription setup guide](docs/ai-providers.md#xai-grok-subscription) to get started.
|
||||||
> - **Claude Code:** The latest version of Shannon does not support Claude Code subscriptions. Follow the [Claude Code subscription setup guide](docs/ai-providers.md#claude-code-subscription) to use version `1.9.0`, which is the final release built on the Claude Agent SDK.
|
> - **Claude Code:** The latest version of Shannon does not support Claude Code subscriptions. Follow the [Claude Code subscription setup guide](docs/ai-providers.md#claude-code-subscription) to use version `1.9.0`, which is the final release built on the Claude Agent SDK.
|
||||||
|
|
||||||
## Key Capabilities
|
## Key Capabilities
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* Section chrome — the sunset-ramp hierarchy beneath the splash wordmark.
|
||||||
|
* (Distinct from ui.ts, which holds the spinner/step helpers.)
|
||||||
|
*
|
||||||
|
* Three treatments, one job each:
|
||||||
|
* rule() static blocks that print once — `start`, `status`, a log's header
|
||||||
|
* gutter() streaming output, where a section scrolls off the top of the screen
|
||||||
|
* panel() the single summary block at the end of a run
|
||||||
|
*
|
||||||
|
* Presentation only. None of this is ever written to workflow.log — the file on disk
|
||||||
|
* stays plain text so `tail`, `grep`, and the completion regex in commands/logs.ts keep
|
||||||
|
* working against it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { supportsColor } from './tty.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sunset ramp, yellow at the top row down to burnt orange at the base.
|
||||||
|
* The wordmark paints row i with stop i and edges it with stop i + 1; section chrome
|
||||||
|
* draws from the same seven stops so the hierarchy reads as one family.
|
||||||
|
* `xterm` is the 256-color approximation for terminals without 24-bit color.
|
||||||
|
*/
|
||||||
|
export const SUNSET: ReadonlyArray<{ rgb: readonly [number, number, number]; xterm: number }> = [
|
||||||
|
{ rgb: [247, 203, 45], xterm: 220 },
|
||||||
|
{ rgb: [246, 182, 38], xterm: 220 },
|
||||||
|
{ rgb: [245, 160, 32], xterm: 214 },
|
||||||
|
{ rgb: [242, 141, 28], xterm: 214 },
|
||||||
|
{ rgb: [238, 121, 24], xterm: 208 },
|
||||||
|
{ rgb: [231, 100, 21], xterm: 208 },
|
||||||
|
{ rgb: [222, 82, 19], xterm: 202 },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Half-block bar for streaming sections — the wordmark's █ at one eighth the weight. */
|
||||||
|
const BAR = '▌';
|
||||||
|
|
||||||
|
/** Columns reserved to the left of every section, matching the existing output grid. */
|
||||||
|
const INDENT = 2;
|
||||||
|
|
||||||
|
/** Rules stop here even in a wide terminal; a rule spanning 200 columns reads as a divider, not a header. */
|
||||||
|
const MAX_RULE = 64;
|
||||||
|
|
||||||
|
export interface Palette {
|
||||||
|
color: boolean;
|
||||||
|
RESET: string;
|
||||||
|
WHITE: string;
|
||||||
|
GRAY: string;
|
||||||
|
DIM: string;
|
||||||
|
RED: string;
|
||||||
|
/** The seven sunset stops, ready to emit. Empty strings when color is off. */
|
||||||
|
ramp: string[];
|
||||||
|
/** Ramp stop 0 — the solid yellow used for every static rule. */
|
||||||
|
YELLOW: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the escape set for the current terminal, degrading 24-bit → 256-color → bare text.
|
||||||
|
* Resolved per call rather than at import so NO_COLOR/FORCE_COLOR are honored whenever they land.
|
||||||
|
*/
|
||||||
|
export function palette(): Palette {
|
||||||
|
const color = supportsColor();
|
||||||
|
const truecolor = color && /truecolor|24bit/i.test(process.env.COLORTERM ?? '');
|
||||||
|
|
||||||
|
const ramp = SUNSET.map(({ rgb: [r, g, b], xterm }) => {
|
||||||
|
if (!color) return '';
|
||||||
|
return truecolor ? `\x1b[38;2;${r};${g};${b}m` : `\x1b[38;5;${xterm}m`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
color,
|
||||||
|
RESET: color ? '\x1b[0m' : '',
|
||||||
|
WHITE: color ? '\x1b[1;97m' : '',
|
||||||
|
GRAY: color ? '\x1b[0;37m' : '',
|
||||||
|
DIM: color ? '\x1b[90m' : '',
|
||||||
|
RED: color ? '\x1b[0;31m' : '',
|
||||||
|
ramp,
|
||||||
|
YELLOW: ramp[0] ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Usable width, leaving the indent and a column of breathing room at the right edge. */
|
||||||
|
function columns(): number {
|
||||||
|
return process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Printed width of a string, ignoring any escapes already embedded in it. */
|
||||||
|
export function visibleWidth(text: string): number {
|
||||||
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching SGR escapes is the point
|
||||||
|
return text.replace(/\x1b\[[0-9;]*m/g, '').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option A — a static section header: the existing label, then a solid yellow rule.
|
||||||
|
* The label keeps whatever case and punctuation it already had; only the rule is added.
|
||||||
|
* Degrades to an undecorated label when the terminal is too narrow to carry one.
|
||||||
|
*/
|
||||||
|
export function rule(label: string, indent = INDENT): string {
|
||||||
|
const { WHITE, YELLOW, RESET } = palette();
|
||||||
|
const pad = ' '.repeat(indent);
|
||||||
|
const width = Math.min(MAX_RULE, columns() - indent - 1);
|
||||||
|
const dashes = width - visibleWidth(label) - 1;
|
||||||
|
|
||||||
|
if (dashes < 2) return `${pad}${WHITE}${label}${RESET}`;
|
||||||
|
return `${pad}${WHITE}${label}${RESET} ${YELLOW}${'─'.repeat(dashes)}${RESET}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grey the label half of an aligned `Label: value` line, leaving the value at default
|
||||||
|
* weight. Purely additive: the string's own characters are never rewritten, so alignment
|
||||||
|
* that was already correct stays correct.
|
||||||
|
*/
|
||||||
|
export function field(line: string): string {
|
||||||
|
const { GRAY, RESET } = palette();
|
||||||
|
const match = /^(\s*)([A-Za-z][A-Za-z ]*:)(\s*)(.*)$/.exec(line);
|
||||||
|
if (!match) return line;
|
||||||
|
return `${match[1]}${GRAY}${match[2]}${RESET}${match[3]}${match[4]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option C — one line of a streaming section, carrying the section's bar in the gutter.
|
||||||
|
* `stop` indexes the sunset ramp and wraps, so consecutive sections stay distinguishable
|
||||||
|
* however many a run produces.
|
||||||
|
*/
|
||||||
|
export function gutter(text: string, stop: number, indent = INDENT): string {
|
||||||
|
const { ramp, RESET } = palette();
|
||||||
|
const color = ramp[((stop % ramp.length) + ramp.length) % ramp.length] ?? '';
|
||||||
|
const pad = ' '.repeat(indent);
|
||||||
|
// Trailing space is dropped on empty lines so sections don't emit trailing whitespace.
|
||||||
|
return text ? `${pad}${color}${BAR}${RESET} ${text}` : `${pad}${color}${BAR}${RESET}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option E — the framed summary block, used once at the end of a run.
|
||||||
|
* Falls back to a rule plus indented lines when the terminal is too narrow to hold the
|
||||||
|
* frame, since a box that wraps is worse than no box at all.
|
||||||
|
*/
|
||||||
|
export function panel(title: string, body: string[], indent = INDENT): string[] {
|
||||||
|
const { WHITE, YELLOW, RESET } = palette();
|
||||||
|
const pad = ' '.repeat(indent);
|
||||||
|
const titleWidth = visibleWidth(title);
|
||||||
|
const widest = body.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
|
||||||
|
|
||||||
|
const available = columns() - indent - 6;
|
||||||
|
const inner = Math.max(titleWidth + 1, widest);
|
||||||
|
|
||||||
|
if (available < inner || available < titleWidth + 3) {
|
||||||
|
return [rule(title, indent), '', ...body.map((line) => `${pad} ${line}`)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const frame = (s: string): string => `${YELLOW}${s}${RESET}`;
|
||||||
|
const top = `${pad}${frame('╭─')} ${WHITE}${title}${RESET} ${frame(`${'─'.repeat(inner + 1 - titleWidth)}╮`)}`;
|
||||||
|
const bottom = `${pad}${frame(`╰${'─'.repeat(inner + 4)}╯`)}`;
|
||||||
|
|
||||||
|
const rows = body.map((line) => {
|
||||||
|
const fill = ' '.repeat(inner - visibleWidth(line));
|
||||||
|
return `${pad}${frame('│')} ${line}${fill} ${frame('│')}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [top, ...rows, bottom];
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { setTimeout as sleep } from 'node:timers/promises';
|
import { setTimeout as sleep } from 'node:timers/promises';
|
||||||
import { watch } from 'chokidar';
|
import { watch } from 'chokidar';
|
||||||
|
import { field } from '../chrome.js';
|
||||||
import { fail } from '../errors.js';
|
import { fail } from '../errors.js';
|
||||||
import { getWorkspacesDir } from '../home.js';
|
import { getWorkspacesDir } from '../home.js';
|
||||||
|
import { LogRenderer } from '../log-render.js';
|
||||||
import { resolveRunFile } from '../paths.js';
|
import { resolveRunFile } from '../paths.js';
|
||||||
import { resolveWorkflowId } from '../session.js';
|
import { resolveWorkflowId } from '../session.js';
|
||||||
import { waitForWorkflowClose } from '../temporal-client.js';
|
import { waitForWorkflowClose } from '../temporal-client.js';
|
||||||
@@ -90,6 +92,9 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
|
|||||||
let position = 0;
|
let position = 0;
|
||||||
let done = false;
|
let done = false;
|
||||||
let sawFailure = false;
|
let sawFailure = false;
|
||||||
|
// Decorates the streamed log for the terminal; a pass-through when colour is off, so
|
||||||
|
// piped/redirected output stays byte-identical and the failure check still sees raw text.
|
||||||
|
const renderer = new LogRenderer();
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
let watcher: ReturnType<typeof watch> | undefined;
|
let watcher: ReturnType<typeof watch> | undefined;
|
||||||
|
|
||||||
@@ -99,7 +104,7 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
|
|||||||
const { size } = fs.statSync(logFile);
|
const { size } = fs.statSync(logFile);
|
||||||
if (size <= position) return;
|
if (size <= position) return;
|
||||||
const data = readRange(logFile, position, size);
|
const data = readRange(logFile, position, size);
|
||||||
process.stdout.write(data);
|
process.stdout.write(renderer.write(data));
|
||||||
position = size;
|
position = size;
|
||||||
if (!sawFailure && FAILURE_MARKER.test(data)) {
|
if (!sawFailure && FAILURE_MARKER.test(data)) {
|
||||||
sawFailure = true;
|
sawFailure = true;
|
||||||
@@ -112,6 +117,7 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
|
|||||||
function finish(): void {
|
function finish(): void {
|
||||||
if (done) return;
|
if (done) return;
|
||||||
done = true;
|
done = true;
|
||||||
|
process.stdout.write(renderer.end());
|
||||||
controller.abort();
|
controller.abort();
|
||||||
if (watcher) {
|
if (watcher) {
|
||||||
watcher.close().finally(() => resolve({ sawFailure }));
|
watcher.close().finally(() => resolve({ sawFailure }));
|
||||||
@@ -165,7 +171,7 @@ export function tailUntilComplete(logFile: string, opts: TailOptions = {}): Prom
|
|||||||
export function logs(workspaceId: string): void {
|
export function logs(workspaceId: string): void {
|
||||||
const logFile = resolveLogFile(workspaceId);
|
const logFile = resolveLogFile(workspaceId);
|
||||||
const workflowId = resolveWorkflowId(workspaceId);
|
const workflowId = resolveWorkflowId(workspaceId);
|
||||||
console.error(stdoutIsTerminal() ? `Tailing scan log: ${logFile}` : 'Tailing scan log');
|
console.error(stdoutIsTerminal() ? field(`Tailing scan log: ${logFile}`) : 'Tailing scan log');
|
||||||
|
|
||||||
let unreachable = false;
|
let unreachable = false;
|
||||||
tailUntilComplete(logFile, {
|
tailUntilComplete(logFile, {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import fs from 'node:fs';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { setTimeout as sleep } from 'node:timers/promises';
|
import { setTimeout as sleep } from 'node:timers/promises';
|
||||||
import * as p from '@clack/prompts';
|
import * as p from '@clack/prompts';
|
||||||
|
import { field, rule } from '../chrome.js';
|
||||||
import { ensureDocker, ensureImage, ensureInfra, randomSuffix, spawnWorker } from '../docker.js';
|
import { ensureDocker, ensureImage, ensureInfra, randomSuffix, spawnWorker } from '../docker.js';
|
||||||
import { buildEnvFlags, loadEnv, resolveHostPiAuthPath, shouldUsePiAuth, validateCredentials } from '../env.js';
|
import { buildEnvFlags, loadEnv, resolveHostPiAuthPath, shouldUsePiAuth, validateCredentials } from '../env.js';
|
||||||
import { fail } from '../errors.js';
|
import { fail } from '../errors.js';
|
||||||
@@ -298,9 +299,9 @@ async function followScan(workspace: string, workspacesDir: string): Promise<nev
|
|||||||
|
|
||||||
function printPreservedContainerHint(containerName: string): void {
|
function printPreservedContainerHint(containerName: string): void {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(` Worker container preserved: ${containerName}`);
|
console.log(field(` Worker container preserved: ${containerName}`));
|
||||||
console.log(` Inspect logs: docker logs ${containerName}`);
|
console.log(field(` Inspect logs: docker logs ${containerName}`));
|
||||||
console.log(` Remove: docker rm ${containerName}`);
|
console.log(field(` Remove: docker rm ${containerName}`));
|
||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,19 +313,19 @@ function printInfo(args: StartArgs, workspace: string, repoPath: string, workspa
|
|||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(` Target: ${args.url}`);
|
console.log(field(` Target: ${args.url}`));
|
||||||
console.log(` Repository: ${interactive ? repoPath : path.basename(repoPath)}`);
|
console.log(field(` Repository: ${interactive ? repoPath : path.basename(repoPath)}`));
|
||||||
console.log(` Workspace: ${workspace}`);
|
console.log(field(` Workspace: ${workspace}`));
|
||||||
if (args.config) {
|
if (args.config) {
|
||||||
console.log(` Config: ${interactive ? path.resolve(args.config) : path.basename(args.config)}`);
|
console.log(field(` Config: ${interactive ? path.resolve(args.config) : path.basename(args.config)}`));
|
||||||
}
|
}
|
||||||
if (args.pipelineTesting) {
|
if (args.pipelineTesting) {
|
||||||
console.log(' Mode: Pipeline Testing');
|
console.log(field(' Mode: Pipeline Testing'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const spec = resolveModelSpec();
|
const spec = resolveModelSpec();
|
||||||
if (typeof spec !== 'string') {
|
if (typeof spec !== 'string') {
|
||||||
console.log(` Model: ${spec.providerId}:${spec.modelId}`);
|
console.log(field(` Model: ${spec.providerId}:${spec.modelId}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!interactive) {
|
if (!interactive) {
|
||||||
@@ -338,13 +339,13 @@ function printInfo(args: StartArgs, workspace: string, repoPath: string, workspa
|
|||||||
if (!args.follow) {
|
if (!args.follow) {
|
||||||
const prefix = commandPrefix();
|
const prefix = commandPrefix();
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(' Watch scan progress:');
|
console.log(rule('Watch scan progress:'));
|
||||||
console.log(` Live logs: ${prefix} logs ${workspace}`);
|
console.log(field(` Live logs: ${prefix} logs ${workspace}`));
|
||||||
console.log(` Progress: ${prefix} status ${workspace}`);
|
console.log(field(` Progress: ${prefix} status ${workspace}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(' Report (when the scan finishes):');
|
console.log(rule('Report (when the scan finishes):'));
|
||||||
console.log(` ${reportPath}`);
|
console.log(` ${reportPath}`);
|
||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
* Decorates a tailed workflow.log for the terminal.
|
||||||
|
*
|
||||||
|
* The worker writes workflow.log as plain text and the CLI reads it back, so all of the
|
||||||
|
* chrome lives here on the read side. Nothing in this file changes what the log *says* —
|
||||||
|
* it adds the section treatments the plain file has no way to carry:
|
||||||
|
*
|
||||||
|
* the log header and RESUMED banner -> rule() (their ==== bars become the rule)
|
||||||
|
* everything between phases -> gutter() (one bar per phase, walking the ramp)
|
||||||
|
* the closing Scan COMPLETED block -> panel() (its ==== bars become the frame)
|
||||||
|
*
|
||||||
|
* When stdout is not a terminal the renderer is a pass-through and emits the file's bytes
|
||||||
|
* unchanged, so redirected logs, pipes, and CI keep grepping the same text they always did.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { field, gutter, palette, panel, rule } from './chrome.js';
|
||||||
|
|
||||||
|
/** The ==== bars that open and close a block; replaced by our own chrome. */
|
||||||
|
const BLOCK_BAR = /^={10,}\s*$/;
|
||||||
|
|
||||||
|
/** The ──── bar dividing a block's title from its body; replaced by the panel frame. */
|
||||||
|
const INNER_BAR = /^─{10,}\s*$/;
|
||||||
|
|
||||||
|
/** `[2026-08-26 17:04:11] ` — every streamed event line carries one. */
|
||||||
|
const TIMESTAMP = /^(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\])( .*)$/;
|
||||||
|
|
||||||
|
/** A phase transition opens a new gutter section. */
|
||||||
|
const PHASE_START = /^\[[^\]]*\] \[PHASE\] Starting: /;
|
||||||
|
|
||||||
|
/** Titles of the two banner blocks, which take the static rule treatment. */
|
||||||
|
const BANNER_TITLE = /^(Shannon Pentest - Scan Log|RESUMED)$/;
|
||||||
|
|
||||||
|
/** Title of the final block, which takes the panel treatment. Mirrors logs.ts's completion regex. */
|
||||||
|
const COMPLETION_TITLE = /^Scan (COMPLETED|FAILED)$/;
|
||||||
|
|
||||||
|
/** Dim the timestamp so the message reads first; errors take the semantic red, not a ramp colour. */
|
||||||
|
function colorizeEvent(line: string): string {
|
||||||
|
const { DIM, RED, RESET } = palette();
|
||||||
|
const match = TIMESTAMP.exec(line);
|
||||||
|
if (!match) return line;
|
||||||
|
const rest = match[2] ?? '';
|
||||||
|
const body = rest.includes('[ERROR]') ? `${RED}${rest}${RESET}` : rest;
|
||||||
|
return `${DIM}${match[1]}${RESET}${body}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mode = 'stream' | 'banner' | 'summary';
|
||||||
|
|
||||||
|
export class LogRenderer {
|
||||||
|
/** Bytes past the last newline, held until the rest of the line arrives. */
|
||||||
|
private carry = '';
|
||||||
|
private mode: Mode = 'stream';
|
||||||
|
/** Suppresses a second consecutive blank line; starts true so the stream can't open on one. */
|
||||||
|
private lastBlank = true;
|
||||||
|
/** Current sunset stop for the gutter bar; advanced by each phase transition. */
|
||||||
|
private stop = 0;
|
||||||
|
private summaryTitle = '';
|
||||||
|
private summaryBody: string[] = [];
|
||||||
|
private readonly passthrough: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.passthrough = !palette().color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decorate a chunk of newly appended log text. Incomplete trailing lines are held back. */
|
||||||
|
write(chunk: string): string {
|
||||||
|
if (this.passthrough) return chunk;
|
||||||
|
|
||||||
|
const text = this.carry + chunk;
|
||||||
|
const lines = text.split('\n');
|
||||||
|
// The final element is whatever followed the last newline — possibly a partial line.
|
||||||
|
this.carry = lines.pop() ?? '';
|
||||||
|
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
out.push(...this.renderLine(line));
|
||||||
|
}
|
||||||
|
return this.emit(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Join rendered lines, dropping blank runs left behind by the bars we removed. */
|
||||||
|
private emit(lines: string[]): string {
|
||||||
|
const kept: string[] = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
const blank = line === '';
|
||||||
|
if (blank && this.lastBlank) continue;
|
||||||
|
this.lastBlank = blank;
|
||||||
|
kept.push(line);
|
||||||
|
}
|
||||||
|
return kept.length ? `${kept.join('\n')}\n` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Flush a held partial line and close an unterminated summary block. */
|
||||||
|
end(): string {
|
||||||
|
if (this.passthrough) return '';
|
||||||
|
|
||||||
|
const out: string[] = [];
|
||||||
|
if (this.carry) {
|
||||||
|
out.push(...this.renderLine(this.carry));
|
||||||
|
this.carry = '';
|
||||||
|
}
|
||||||
|
if (this.mode === 'summary') {
|
||||||
|
out.push(...this.closeSummary());
|
||||||
|
}
|
||||||
|
return this.emit(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLine(raw: string): string[] {
|
||||||
|
// Strip the \r from CRLF logs so it never lands in the middle of a decorated line.
|
||||||
|
const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
|
||||||
|
|
||||||
|
// Only a ==== bar closes the summary; its ──── divider is chrome we replace, not a terminator.
|
||||||
|
if (BLOCK_BAR.test(line)) {
|
||||||
|
return this.mode === 'summary' ? this.closeSummary() : [];
|
||||||
|
}
|
||||||
|
if (INNER_BAR.test(line)) return [];
|
||||||
|
|
||||||
|
if (COMPLETION_TITLE.test(line)) {
|
||||||
|
this.mode = 'summary';
|
||||||
|
this.summaryTitle = line;
|
||||||
|
this.summaryBody = [];
|
||||||
|
return [''];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BANNER_TITLE.test(line)) {
|
||||||
|
this.mode = 'banner';
|
||||||
|
return ['', rule(line)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mode === 'summary') {
|
||||||
|
this.summaryBody.push(line);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mode === 'banner') {
|
||||||
|
// The banner runs until the first streamed event.
|
||||||
|
if (!TIMESTAMP.test(line)) {
|
||||||
|
return [line.trim() ? ` ${field(line)}` : ''];
|
||||||
|
}
|
||||||
|
this.mode = 'stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
// A blank line separates sections; the bar resumes on the next line of content.
|
||||||
|
if (!line.trim()) return [''];
|
||||||
|
|
||||||
|
if (PHASE_START.test(line)) {
|
||||||
|
// Two stops per phase, not one: the 256-colour tier collapses the seven stops into
|
||||||
|
// four xterm colours, and a single step would give consecutive phases the same bar.
|
||||||
|
// Seven is odd, so a stride of two still visits every stop before repeating.
|
||||||
|
this.stop += 2;
|
||||||
|
}
|
||||||
|
return [gutter(colorizeEvent(line), this.stop)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeSummary(): string[] {
|
||||||
|
const title = this.summaryTitle;
|
||||||
|
const body = [...this.summaryBody];
|
||||||
|
while (body.length && !body[body.length - 1]?.trim()) body.pop();
|
||||||
|
while (body.length && !body[0]?.trim()) body.shift();
|
||||||
|
|
||||||
|
this.mode = 'stream';
|
||||||
|
this.summaryTitle = '';
|
||||||
|
this.summaryBody = [];
|
||||||
|
|
||||||
|
const rows = body.map((line) => (line.trim() ? field(line) : ''));
|
||||||
|
return [...panel(title, rows), ''];
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-28
@@ -3,7 +3,7 @@
|
|||||||
* Color escapes are gated on terminal support; the Unicode art is always kept.
|
* Color escapes are gated on terminal support; the Unicode art is always kept.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { supportsColor } from './tty.js';
|
import { palette } from './chrome.js';
|
||||||
|
|
||||||
/** SHANNON wordmark. Block glyphs take the row fill; box-drawing strokes take the deeper edge shade. */
|
/** SHANNON wordmark. Block glyphs take the row fill; box-drawing strokes take the deeper edge shade. */
|
||||||
const SHANNON = [
|
const SHANNON = [
|
||||||
@@ -15,34 +15,8 @@ const SHANNON = [
|
|||||||
'╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═══╝',
|
'╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═══╝',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Sunset ramp, yellow at the top row down to burnt orange at the base.
|
|
||||||
* Wordmark row i is filled with stop i and edged with stop i + 1, so the
|
|
||||||
* box-drawing strokes read as a shadow one shade deeper than their row.
|
|
||||||
* `xterm` is the 256-color approximation for terminals without 24-bit color.
|
|
||||||
*/
|
|
||||||
const SUNSET: ReadonlyArray<{ rgb: readonly [number, number, number]; xterm: number }> = [
|
|
||||||
{ rgb: [247, 203, 45], xterm: 220 },
|
|
||||||
{ rgb: [246, 182, 38], xterm: 220 },
|
|
||||||
{ rgb: [245, 160, 32], xterm: 214 },
|
|
||||||
{ rgb: [242, 141, 28], xterm: 214 },
|
|
||||||
{ rgb: [238, 121, 24], xterm: 208 },
|
|
||||||
{ rgb: [231, 100, 21], xterm: 208 },
|
|
||||||
{ rgb: [222, 82, 19], xterm: 202 },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function displaySplash(version?: string): void {
|
export function displaySplash(version?: string): void {
|
||||||
const color = supportsColor();
|
const { color, RESET, WHITE, GRAY, DIM, ramp } = palette();
|
||||||
const truecolor = color && /truecolor|24bit/i.test(process.env.COLORTERM ?? '');
|
|
||||||
const RESET = color ? '\x1b[0m' : '';
|
|
||||||
const WHITE = color ? '\x1b[1;97m' : '';
|
|
||||||
const GRAY = color ? '\x1b[0;37m' : '';
|
|
||||||
const DIM = color ? '\x1b[90m' : '';
|
|
||||||
|
|
||||||
const ramp = SUNSET.map(({ rgb: [r, g, b], xterm }) => {
|
|
||||||
if (!color) return '';
|
|
||||||
return truecolor ? `\x1b[38;2;${r};${g};${b}m` : `\x1b[38;5;${xterm}m`;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Color one wordmark row, emitting an escape only where the run changes. Spaces stay unpainted. */
|
/** Color one wordmark row, emitting an escape only where the run changes. Spaces stay unpainted. */
|
||||||
const paint = (row: string, fill: string, edge: string): string => {
|
const paint = (row: string, fill: string, edge: string): string => {
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
"clean": "rm -rf dist"
|
"clean": "rm -rf dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@earendil-works/pi-agent-core": "^0.82.1",
|
"@earendil-works/pi-agent-core": "^0.84.2",
|
||||||
"@earendil-works/pi-ai": "^0.82.1",
|
"@earendil-works/pi-ai": "^0.84.2",
|
||||||
"@earendil-works/pi-coding-agent": "^0.82.1",
|
"@earendil-works/pi-coding-agent": "^0.84.2",
|
||||||
"@gotgenes/pi-permission-system": "^10.9.0",
|
"@gotgenes/pi-permission-system": "^10.9.0",
|
||||||
"@temporalio/activity": "^1.11.0",
|
"@temporalio/activity": "^1.11.0",
|
||||||
"@temporalio/client": "^1.11.0",
|
"@temporalio/client": "^1.11.0",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 MiB |
+19
-1
@@ -58,7 +58,7 @@ These are the models `npx @keygraph/shannon setup` offers, best-first. They are
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `anthropic` | `claude-sonnet-4-6`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-haiku-4-5-20251001` |
|
| `anthropic` | `claude-sonnet-4-6`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-haiku-4-5-20251001` |
|
||||||
| `openai` | `gpt-5.6-sol`, `gpt-5.5`, `gpt-5.4` |
|
| `openai` | `gpt-5.6-sol`, `gpt-5.5`, `gpt-5.4` |
|
||||||
| `xai` | `grok-4.5` |
|
| `xai` | `grok-4.6`, `grok-4.5` |
|
||||||
| `amazon-bedrock` | `us.anthropic.claude-sonnet-4-6`, `us.anthropic.claude-opus-4-8`, `us.anthropic.claude-opus-4-7` |
|
| `amazon-bedrock` | `us.anthropic.claude-sonnet-4-6`, `us.anthropic.claude-opus-4-8`, `us.anthropic.claude-opus-4-7` |
|
||||||
|
|
||||||
Bedrock IDs are region-prefixed and must be enabled in your account, so the ID that works for you may differ from the one listed here.
|
Bedrock IDs are region-prefixed and must be enabled in your account, so the ID that works for you may differ from the one listed here.
|
||||||
@@ -164,6 +164,24 @@ Before running a pentest, review the [cyber safeguards requirements](#cyber-safe
|
|||||||
|
|
||||||
Supported Codex models are `gpt-5.6-sol`, `gpt-5.5`, and `gpt-5.4`.
|
Supported Codex models are `gpt-5.6-sol`, `gpt-5.5`, and `gpt-5.4`.
|
||||||
|
|
||||||
|
## xAI (Grok subscription)
|
||||||
|
|
||||||
|
An xAI subscription can run Shannon. Shannon reuses a login created by Pi.
|
||||||
|
|
||||||
|
1. Install Pi by following the instructions at [pi.dev](https://pi.dev).
|
||||||
|
2. Log in with your subscription using Pi's [subscription authentication guide](https://pi.dev/docs/latest/providers#subscriptions). This creates `~/.pi/agent/auth.json` with an `xai` entry.
|
||||||
|
|
||||||
|
3. Select an xAI model and enable Pi authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SHANNON_USE_PI_AUTH=1
|
||||||
|
export SHANNON_AI_MODEL=xai:grok-4.6
|
||||||
|
```
|
||||||
|
|
||||||
|
4. In npx mode, run `npx @keygraph/shannon start ...` from the same shell. In source-build mode, add the two variables to `.env` and run `./shannon start ...`.
|
||||||
|
|
||||||
|
Suggested Grok models are `grok-4.6` and `grok-4.5`.
|
||||||
|
|
||||||
## Claude Code subscription
|
## Claude Code subscription
|
||||||
|
|
||||||
The latest version of Shannon does not support Claude Code subscriptions. The [`shannon-v1`](https://github.com/KeygraphHQ/shannon/tree/shannon-v1) branch is the final release built on the Claude Agent SDK and supports Claude Code OAuth.
|
The latest version of Shannon does not support Claude Code subscriptions. The [`shannon-v1`](https://github.com/KeygraphHQ/shannon/tree/shannon-v1) branch is the final release built on the Claude Agent SDK and supports Claude Code OAuth.
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ This guide covers platform-specific notes and Docker networking behavior.
|
|||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
Shannon on Windows is supported through WSL2. Native Windows, including Git Bash, is not supported.
|
Shannon on Windows is supported through WSL2, which behaves like Linux for everything below. Native Windows, including Git Bash, is community-supported: contributions are welcome, but Keygraph does not actively develop or test against it.
|
||||||
|
|
||||||
### Ensure WSL2
|
### Ensure WSL2
|
||||||
|
|
||||||
|
|||||||
+27
-2
@@ -66,6 +66,12 @@ Thanks to tools like Claude Code and Cursor, your team ships code non-stop. But
|
|||||||
|
|
||||||
Shannon closes that gap by providing on-demand, automated penetration testing that can run against every build or release.
|
Shannon closes that gap by providing on-demand, automated penetration testing that can run against every build or release.
|
||||||
|
|
||||||
|
### Why "Shannon"?
|
||||||
|
|
||||||
|
It's named after Claude Shannon, the father of information theory. At its core, pentesting is an information problem: every probe reduces uncertainty about a system's state. The best tools maximize the signal gained from every request, turning those bits of knowledge into an exploit path.
|
||||||
|
|
||||||
|
Also, we wanted you to be able to say, "Hey Claude, run Shannon" to find all the security flaws in your vibe-coded app.
|
||||||
|
|
||||||
## Shannon in Action
|
## Shannon in Action
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -110,6 +116,7 @@ For source builds, authenticated scans, provider-specific setup, and platform no
|
|||||||
> **Prefer to use a subscription instead of API credits?**
|
> **Prefer to use a subscription instead of API credits?**
|
||||||
>
|
>
|
||||||
> - **OpenAI Codex:** The latest version of Shannon supports ChatGPT Plus and Pro subscriptions. Follow the [OpenAI Codex subscription setup guide](docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription) to get started.
|
> - **OpenAI Codex:** The latest version of Shannon supports ChatGPT Plus and Pro subscriptions. Follow the [OpenAI Codex subscription setup guide](docs/ai-providers.md#openai-codex-chatgpt-pluspro-subscription) to get started.
|
||||||
|
> - **xAI (Grok):** The latest version of Shannon supports xAI subscriptions. Follow the [xAI subscription setup guide](docs/ai-providers.md#xai-grok-subscription) to get started.
|
||||||
> - **Claude Code:** The latest version of Shannon does not support Claude Code subscriptions. Follow the [Claude Code subscription setup guide](docs/ai-providers.md#claude-code-subscription) to use version `1.9.0`, which is the final release built on the Claude Agent SDK.
|
> - **Claude Code:** The latest version of Shannon does not support Claude Code subscriptions. Follow the [Claude Code subscription setup guide](docs/ai-providers.md#claude-code-subscription) to use version `1.9.0`, which is the final release built on the Claude Agent SDK.
|
||||||
|
|
||||||
## Key Capabilities
|
## Key Capabilities
|
||||||
@@ -688,7 +695,7 @@ These are the models `npx @keygraph/shannon setup` offers, best-first. They are
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `anthropic` | `claude-sonnet-4-6`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-haiku-4-5-20251001` |
|
| `anthropic` | `claude-sonnet-4-6`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-haiku-4-5-20251001` |
|
||||||
| `openai` | `gpt-5.6-sol`, `gpt-5.5`, `gpt-5.4` |
|
| `openai` | `gpt-5.6-sol`, `gpt-5.5`, `gpt-5.4` |
|
||||||
| `xai` | `grok-4.5` |
|
| `xai` | `grok-4.6`, `grok-4.5` |
|
||||||
| `amazon-bedrock` | `us.anthropic.claude-sonnet-4-6`, `us.anthropic.claude-opus-4-8`, `us.anthropic.claude-opus-4-7` |
|
| `amazon-bedrock` | `us.anthropic.claude-sonnet-4-6`, `us.anthropic.claude-opus-4-8`, `us.anthropic.claude-opus-4-7` |
|
||||||
|
|
||||||
Bedrock IDs are region-prefixed and must be enabled in your account, so the ID that works for you may differ from the one listed here.
|
Bedrock IDs are region-prefixed and must be enabled in your account, so the ID that works for you may differ from the one listed here.
|
||||||
@@ -794,6 +801,24 @@ Before running a pentest, review the [cyber safeguards requirements](#cyber-safe
|
|||||||
|
|
||||||
Supported Codex models are `gpt-5.6-sol`, `gpt-5.5`, and `gpt-5.4`.
|
Supported Codex models are `gpt-5.6-sol`, `gpt-5.5`, and `gpt-5.4`.
|
||||||
|
|
||||||
|
## xAI (Grok subscription)
|
||||||
|
|
||||||
|
An xAI subscription can run Shannon. Shannon reuses a login created by Pi.
|
||||||
|
|
||||||
|
1. Install Pi by following the instructions at [pi.dev](https://pi.dev).
|
||||||
|
2. Log in with your subscription using Pi's [subscription authentication guide](https://pi.dev/docs/latest/providers#subscriptions). This creates `~/.pi/agent/auth.json` with an `xai` entry.
|
||||||
|
|
||||||
|
3. Select an xAI model and enable Pi authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SHANNON_USE_PI_AUTH=1
|
||||||
|
export SHANNON_AI_MODEL=xai:grok-4.6
|
||||||
|
```
|
||||||
|
|
||||||
|
4. In npx mode, run `npx @keygraph/shannon start ...` from the same shell. In source-build mode, add the two variables to `.env` and run `./shannon start ...`.
|
||||||
|
|
||||||
|
Suggested Grok models are `grok-4.6` and `grok-4.5`.
|
||||||
|
|
||||||
## Claude Code subscription
|
## Claude Code subscription
|
||||||
|
|
||||||
The latest version of Shannon does not support Claude Code subscriptions. The [`shannon-v1`](https://github.com/KeygraphHQ/shannon/tree/shannon-v1) branch is the final release built on the Claude Agent SDK and supports Claude Code OAuth.
|
The latest version of Shannon does not support Claude Code subscriptions. The [`shannon-v1`](https://github.com/KeygraphHQ/shannon/tree/shannon-v1) branch is the final release built on the Claude Agent SDK and supports Claude Code OAuth.
|
||||||
@@ -858,7 +883,7 @@ This guide covers platform-specific notes and Docker networking behavior.
|
|||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
Shannon on Windows is supported through WSL2. Native Windows, including Git Bash, is not supported.
|
Shannon on Windows is supported through WSL2, which behaves like Linux for everything below. Native Windows, including Git Bash, is community-supported: contributions are welcome, but Keygraph does not actively develop or test against it.
|
||||||
|
|
||||||
### Ensure WSL2
|
### Ensure WSL2
|
||||||
|
|
||||||
|
|||||||
Generated
+78
-66
@@ -46,17 +46,17 @@ importers:
|
|||||||
apps/worker:
|
apps/worker:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@earendil-works/pi-agent-core':
|
'@earendil-works/pi-agent-core':
|
||||||
specifier: ^0.82.1
|
specifier: ^0.84.2
|
||||||
version: 0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
version: 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
||||||
'@earendil-works/pi-ai':
|
'@earendil-works/pi-ai':
|
||||||
specifier: ^0.82.1
|
specifier: ^0.84.2
|
||||||
version: 0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
version: 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':
|
'@earendil-works/pi-coding-agent':
|
||||||
specifier: ^0.82.1
|
specifier: ^0.84.2
|
||||||
version: 0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
version: 0.84.2(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
||||||
'@gotgenes/pi-permission-system':
|
'@gotgenes/pi-permission-system':
|
||||||
specifier: ^10.9.0
|
specifier: ^10.9.0
|
||||||
version: 10.9.0(@earendil-works/pi-coding-agent@0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6))(@earendil-works/pi-tui@0.82.1)
|
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)
|
||||||
'@temporalio/activity':
|
'@temporalio/activity':
|
||||||
specifier: ^1.11.0
|
specifier: ^1.11.0
|
||||||
version: 1.15.0
|
version: 1.15.0
|
||||||
@@ -292,22 +292,34 @@ packages:
|
|||||||
'@clack/prompts@1.1.0':
|
'@clack/prompts@1.1.0':
|
||||||
resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==}
|
resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==}
|
||||||
|
|
||||||
'@earendil-works/pi-agent-core@0.82.1':
|
'@earendil-works/pi-agent-core@0.84.2':
|
||||||
resolution: {integrity: sha512-Z3kloziJIE2dmrisRckZX8zDca/gIv9/YdFAzeoqpHiLV2wsni6bL4hInNSjVKLbqT+4kqLIkph2JQLKvSepjg==}
|
resolution: {integrity: sha512-8Pn3wSCxj0cfo5I6jxQYVB/3uuQRmHhAlEclyjqpOuMEdQMIODHizRogv56FLdbU+dTiGnybeHQ2N+sV1/L2YA==}
|
||||||
engines: {node: '>=22.19.0'}
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
'@earendil-works/pi-ai@0.82.1':
|
'@earendil-works/pi-ai@0.84.2':
|
||||||
resolution: {integrity: sha512-3WFYRhEp3lQB3444EhPMBcM7zSaEUE3eJgHOR7s4081NLqbw/FsWilIKWXSua0Gv3sRr7m9xMidR3pPDE7jI/A==}
|
resolution: {integrity: sha512-6MzsrYIYNVlE7SfpbL2yYb67Qo58p/7Q+xWG1RZvoX1P80aRCHSod2/13aFpxkow1lPO2LEh3c495J0Gwmyjig==}
|
||||||
engines: {node: '>=22.19.0'}
|
engines: {node: '>=22.19.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@earendil-works/pi-coding-agent@0.82.1':
|
'@earendil-works/pi-client@0.84.2':
|
||||||
resolution: {integrity: sha512-zbkAhoIuDPMF3pKuja0ajZabrMWU29FUMV9A/XMXT/XC1yXs5xt6t6t13GogQFsDrDqbFP4DkZQO1w8rWRAzYA==}
|
resolution: {integrity: sha512-/RFSPhD/bZbpOp1oJj+UneSUFSgZhWxzcSENUY+8+8xhoBrWXMYI2t77XNx4Yf+c8YK2qTHquForhNcelYpXvg==}
|
||||||
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
|
'@earendil-works/pi-coding-agent@0.84.2':
|
||||||
|
resolution: {integrity: sha512-l4E+B7hgXKWddRo8bC/eSue2aWZjEgJ9xIpf5p0Og+lq8a2TArCwJ0HCoCPCgaBP/tN4zbYH/wOwvx9pJpeLCA==}
|
||||||
engines: {node: '>=22.19.0'}
|
engines: {node: '>=22.19.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@earendil-works/pi-tui@0.82.1':
|
'@earendil-works/pi-protocol@0.84.2':
|
||||||
resolution: {integrity: sha512-9yN8hALfKaxZq7n54EMxqhFCWnMi6LHkraMJ/1YjHiATq75XrI6XDMVppn9EDtiK7Fks8hUe1SDXUTrIvwRWfQ==}
|
resolution: {integrity: sha512-jbBh03fkeckWEroHpcZBr4w5/Ibat8WwdXFlXHivYQImrQNFtLpDeL0t1cku4hmK0q3pceIRQHkw4fwbM4YILQ==}
|
||||||
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
|
'@earendil-works/pi-telemetry@0.84.2':
|
||||||
|
resolution: {integrity: sha512-wg5caea7uIv1BHRBm2Y116RvFG4oSAiP5qk9tA2463PDGIr4K8M1Ceyyg5DOpF/shUUl0gk826yQJAeAcHYB9g==}
|
||||||
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
|
'@earendil-works/pi-tui@0.84.2':
|
||||||
|
resolution: {integrity: sha512-ds2TLihOnM5sLJB3VpXV6y0uR5efVuHf4MN7yDpsty6hA2DUO/EDVzjp/0od0G2JslzVLMjT8T8zavtxVb+qbg==}
|
||||||
engines: {node: '>=22.19.0'}
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
'@emnapi/core@1.9.1':
|
'@emnapi/core@1.9.1':
|
||||||
@@ -557,14 +569,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==}
|
resolution: {integrity: sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@mistralai/mistralai@2.2.6':
|
|
||||||
resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@opentelemetry/api': ^1.9.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@opentelemetry/api':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@modelcontextprotocol/sdk@1.29.0':
|
'@modelcontextprotocol/sdk@1.29.0':
|
||||||
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
|
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -585,10 +589,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
'@opentelemetry/semantic-conventions@1.43.0':
|
|
||||||
resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
|
|
||||||
'@oxc-project/types@0.122.0':
|
'@oxc-project/types@0.122.0':
|
||||||
resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
|
resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
|
||||||
|
|
||||||
@@ -1376,6 +1376,10 @@ packages:
|
|||||||
graceful-fs@4.2.11:
|
graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
|
grok-mermaid@0.2.2:
|
||||||
|
resolution: {integrity: sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
has-flag@4.0.0:
|
has-flag@4.0.0:
|
||||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1620,9 +1624,8 @@ packages:
|
|||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
openai@6.26.0:
|
openai@6.40.0:
|
||||||
resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==}
|
resolution: {integrity: sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==}
|
||||||
hasBin: true
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
ws: ^8.18.0
|
ws: ^8.18.0
|
||||||
zod: ^3.25 || ^4.0
|
zod: ^3.25 || ^4.0
|
||||||
@@ -2003,6 +2006,9 @@ packages:
|
|||||||
typebox@1.1.38:
|
typebox@1.1.38:
|
||||||
resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==}
|
resolution: {integrity: sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==}
|
||||||
|
|
||||||
|
typebox@1.3.7:
|
||||||
|
resolution: {integrity: sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==}
|
||||||
|
|
||||||
typescript@5.9.3:
|
typescript@5.9.3:
|
||||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
@@ -2014,8 +2020,8 @@ packages:
|
|||||||
undici-types@7.18.2:
|
undici-types@7.18.2:
|
||||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||||
|
|
||||||
undici@8.5.0:
|
undici@8.9.0:
|
||||||
resolution: {integrity: sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==}
|
resolution: {integrity: sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==}
|
||||||
engines: {node: '>=22.19.0'}
|
engines: {node: '>=22.19.0'}
|
||||||
|
|
||||||
unionfs@4.6.0:
|
unionfs@4.6.0:
|
||||||
@@ -2431,12 +2437,13 @@ snapshots:
|
|||||||
'@clack/core': 1.1.0
|
'@clack/core': 1.1.0
|
||||||
sisteransi: 1.0.5
|
sisteransi: 1.0.5
|
||||||
|
|
||||||
'@earendil-works/pi-agent-core@0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
|
'@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)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@earendil-works/pi-ai': 0.82.1(@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-telemetry': 0.84.2
|
||||||
diff: 8.0.4
|
diff: 8.0.4
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
typebox: 1.1.38
|
typebox: 1.3.7
|
||||||
yaml: 2.9.0
|
yaml: 2.9.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@modelcontextprotocol/sdk'
|
- '@modelcontextprotocol/sdk'
|
||||||
@@ -2446,19 +2453,19 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@earendil-works/pi-ai@0.82.1(@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)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
|
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
|
||||||
'@aws-sdk/client-bedrock-runtime': 3.1048.0
|
'@aws-sdk/client-bedrock-runtime': 3.1048.0
|
||||||
|
'@earendil-works/pi-telemetry': 0.84.2
|
||||||
'@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))
|
'@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))
|
||||||
'@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0)
|
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
'@smithy/node-http-handler': 4.7.3
|
'@smithy/node-http-handler': 4.7.3
|
||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6
|
||||||
openai: 6.26.0(ws@8.21.0)(zod@4.3.6)
|
openai: 6.40.0(ws@8.21.0)(zod@4.3.6)
|
||||||
partial-json: 0.1.7
|
partial-json: 0.1.7
|
||||||
typebox: 1.1.38
|
typebox: 1.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@modelcontextprotocol/sdk'
|
- '@modelcontextprotocol/sdk'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -2467,16 +2474,23 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@earendil-works/pi-coding-agent@0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)':
|
'@earendil-works/pi-client@0.84.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@earendil-works/pi-agent-core': 0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
'@earendil-works/pi-protocol': 0.84.2
|
||||||
'@earendil-works/pi-ai': 0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
|
||||||
'@earendil-works/pi-tui': 0.82.1
|
'@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)':
|
||||||
|
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
|
||||||
'@silvia-odwyer/photon-node': 0.3.4
|
'@silvia-odwyer/photon-node': 0.3.4
|
||||||
chalk: 5.6.2
|
chalk: 5.6.2
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
diff: 8.0.4
|
diff: 8.0.4
|
||||||
glob: 13.0.6
|
glob: 13.0.6
|
||||||
|
grok-mermaid: 0.2.2
|
||||||
highlight.js: 10.7.3
|
highlight.js: 10.7.3
|
||||||
hosted-git-info: 9.0.3
|
hosted-git-info: 9.0.3
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
@@ -2484,8 +2498,8 @@ snapshots:
|
|||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
proper-lockfile: 4.1.2
|
proper-lockfile: 4.1.2
|
||||||
semver: 7.8.0
|
semver: 7.8.0
|
||||||
typebox: 1.1.38
|
typebox: 1.3.7
|
||||||
undici: 8.5.0
|
undici: 8.9.0
|
||||||
yaml: 2.9.0
|
yaml: 2.9.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@mariozechner/clipboard': 0.3.9
|
'@mariozechner/clipboard': 0.3.9
|
||||||
@@ -2497,7 +2511,13 @@ snapshots:
|
|||||||
- ws
|
- ws
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
'@earendil-works/pi-tui@0.82.1':
|
'@earendil-works/pi-protocol@0.84.2':
|
||||||
|
dependencies:
|
||||||
|
typebox: 1.3.7
|
||||||
|
|
||||||
|
'@earendil-works/pi-telemetry@0.84.2': {}
|
||||||
|
|
||||||
|
'@earendil-works/pi-tui@0.84.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
get-east-asian-width: 1.6.0
|
get-east-asian-width: 1.6.0
|
||||||
marked: 18.0.5
|
marked: 18.0.5
|
||||||
@@ -2531,10 +2551,10 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
'@gotgenes/pi-permission-system@10.9.0(@earendil-works/pi-coding-agent@0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6))(@earendil-works/pi-tui@0.82.1)':
|
'@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)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@earendil-works/pi-coding-agent': 0.82.1(@modelcontextprotocol/sdk@1.29.0(zod@4.3.6))(ws@8.21.0)(zod@4.3.6)
|
'@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.82.1
|
'@earendil-works/pi-tui': 0.84.2
|
||||||
tree-sitter-bash: 0.25.1
|
tree-sitter-bash: 0.25.1
|
||||||
web-tree-sitter: 0.26.9
|
web-tree-sitter: 0.26.9
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -2749,18 +2769,6 @@ snapshots:
|
|||||||
'@mariozechner/clipboard-win32-x64-msvc': 0.3.9
|
'@mariozechner/clipboard-win32-x64-msvc': 0.3.9
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)':
|
|
||||||
dependencies:
|
|
||||||
'@opentelemetry/semantic-conventions': 1.43.0
|
|
||||||
ws: 8.21.0
|
|
||||||
zod: 4.3.6
|
|
||||||
zod-to-json-schema: 3.25.2(zod@4.3.6)
|
|
||||||
optionalDependencies:
|
|
||||||
'@opentelemetry/api': 1.9.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- bufferutil
|
|
||||||
- utf-8-validate
|
|
||||||
|
|
||||||
'@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)':
|
'@modelcontextprotocol/sdk@1.29.0(zod@4.3.6)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@hono/node-server': 1.19.14(hono@4.12.14)
|
'@hono/node-server': 1.19.14(hono@4.12.14)
|
||||||
@@ -2795,8 +2803,6 @@ snapshots:
|
|||||||
|
|
||||||
'@opentelemetry/api@1.9.0': {}
|
'@opentelemetry/api@1.9.0': {}
|
||||||
|
|
||||||
'@opentelemetry/semantic-conventions@1.43.0': {}
|
|
||||||
|
|
||||||
'@oxc-project/types@0.122.0': {}
|
'@oxc-project/types@0.122.0': {}
|
||||||
|
|
||||||
'@protobufjs/aspromise@1.1.2': {}
|
'@protobufjs/aspromise@1.1.2': {}
|
||||||
@@ -3598,6 +3604,8 @@ snapshots:
|
|||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
|
|
||||||
|
grok-mermaid@0.2.2: {}
|
||||||
|
|
||||||
has-flag@4.0.0: {}
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
has-symbols@1.1.0:
|
has-symbols@1.1.0:
|
||||||
@@ -3820,7 +3828,7 @@ snapshots:
|
|||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
openai@6.26.0(ws@8.21.0)(zod@4.3.6):
|
openai@6.40.0(ws@8.21.0)(zod@4.3.6):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ws: 8.21.0
|
ws: 8.21.0
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
@@ -4212,6 +4220,8 @@ snapshots:
|
|||||||
|
|
||||||
typebox@1.1.38: {}
|
typebox@1.1.38: {}
|
||||||
|
|
||||||
|
typebox@1.3.7: {}
|
||||||
|
|
||||||
typescript@5.9.3: {}
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
unconfig-core@7.5.0:
|
unconfig-core@7.5.0:
|
||||||
@@ -4221,7 +4231,7 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@7.18.2: {}
|
undici-types@7.18.2: {}
|
||||||
|
|
||||||
undici@8.5.0: {}
|
undici@8.9.0: {}
|
||||||
|
|
||||||
unionfs@4.6.0:
|
unionfs@4.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4324,7 +4334,9 @@ snapshots:
|
|||||||
zod-to-json-schema@3.25.2(zod@4.3.6):
|
zod-to-json-schema@3.25.2(zod@4.3.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
|
optional: true
|
||||||
|
|
||||||
zod@4.3.6: {}
|
zod@4.3.6:
|
||||||
|
optional: true
|
||||||
|
|
||||||
zx@8.8.5: {}
|
zx@8.8.5: {}
|
||||||
Reference in new issue
Block a user