diff --git a/.cbmignore b/.cbmignore new file mode 100644 index 0000000..bfee505 --- /dev/null +++ b/.cbmignore @@ -0,0 +1,13 @@ +# Exclusions for the codebase-memory knowledge graph only. +# +# This file is NOT a gitignore. Everything listed here is tracked, shipped, and +# required at runtime — it simply has no business in a code-intelligence index. +# Syntax is gitignore syntax; the indexer reads .gitignore, .git/info/exclude +# and this file. + +# Vendored asciinema-player bundle and the demo recording assets for the landing +# page. The minified bundle alone contributes hundreds of single-letter symbols +# ($, $A, $e, $t, ...) that outrank real source in BM25 full-text search, and no +# structural query about this codebase should ever resolve into third-party +# minified output. Regenerated by scripts/record-demo.sh, never hand-edited. +site/assets/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18aea2e..5e9b63d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,49 +22,25 @@ Choose based on what you need and how much time you have: ## Development Setup -### Prerequisites - -- Node.js >= 18 -- pnpm 10+ (the repo pins the exact version via `packageManager` in `package.json`) - -### Quick start +Full setup — prerequisites, build, the podman test environment, failure +injection, and the project layout — lives in +**[GETTING_STARTED.md](GETTING_STARTED.md)**. The short version: ```bash git clone https://github.com/trs-80/crisismode.git cd crisismode -pnpm install -pnpm run build # Compile to dist/ (also builds the agent-sdk workspace) -pnpm run typecheck # Verify types -pnpm test # Run unit tests -``` - -### Running the CLI - -```bash -npx tsx src/cli/index.ts scan # Health scan -npx tsx src/cli/index.ts demo # Simulator demo -npx tsx src/cli/index.ts diagnose # AI-powered diagnosis -``` - -### Test environment - -For testing against real infrastructure: - -```bash -./test/podman/scripts/start.sh # Start PG, Prometheus, etc. -./test/smoke/run-all.sh # Validate the test environment -pnpm run live # Dry-run against test PG +pnpm install && pnpm run build +pnpm run typecheck && pnpm test +npx tsx src/cli/index.ts scan # run the CLI from source ``` -See [GETTING_STARTED.md](GETTING_STARTED.md) for full setup instructions. - ## Your First Contribution ### Check plugin (simplest) Check plugins are standalone shell scripts. No TypeScript needed. -1. Scaffold with `crisismode init --agent ` (creates the directory, +1. Scaffold with `crisismode init --plugin ` (creates the directory, `manifest.json`, and a stub `check.sh`), or create a directory in `checks/` by hand with a `manifest.json` and a `check.sh` 2. Test with `crisismode scan` @@ -89,12 +65,41 @@ Agents are TypeScript modules that follow the 6-file pattern (`backend.ts`, `sim 1. Build the simulator first -- it enables testing without real infrastructure 2. Implement `assessHealth`, `diagnose`, `plan`, `replan` -3. Register in `src/config/builtin-agents.ts` -4. Add tests in `src/__tests__/` -5. Submit a PR +3. Declare `metadata.plugin.maturity` honestly (see below) +4. Register in `src/config/builtin-agents.ts` +5. Add tests in `src/__tests__/` +6. Submit a PR See the [Your First Agent](docs/guides/your-first-agent.md) tutorial for a step-by-step walkthrough. The PostgreSQL agent at `src/agent/pg-replication/` is the canonical reference implementation. +### Declaring maturity honestly + +Your manifest's `metadata.plugin.maturity` is a claim operators rely on during an +incident, so it is the one field never to be optimistic about. Valid values are +`experimental`, `simulator_only`, `dry_run_only`, `live_validated`, and +`production_certified`. + +**A new agent is `simulator_only`.** That is not a placeholder to be upgraded when +the code feels finished — CrisisMode's honesty layer +(`src/framework/agent-maturity.ts`) treats everything except `live_validated` as +best-effort and labels its findings as leads rather than conclusions in operator +output. That default is the point. + +Only claim `live_validated` when the agent has actually been run against a real +deployment of its target system and diagnosed it correctly, and say in the PR +description what you ran it against. "The live client compiles" is not validation. +The label describes *diagnosis*: a diagnosis-only agent can be `live_validated` +without any mutating run — `src/agent/tls/`, `src/agent/disk/`, and +`src/agent/backup/` are. If your agent has a mutating recovery path and its only +live exposure was dry-run, `dry_run_only` is the accurate label. Whether a +mutating plan ran and the fault was verified resolved is a separate, stricter +claim, tracked under +[Execute-verified recovery](docs/coverage.md#execute-verified-recovery). + +Where each agent currently stands is tracked in +[docs/coverage.md](docs/coverage.md) — update it in the same PR that changes a +maturity value. + ## Code Standards - **TypeScript strict mode** with ESM modules (`"type": "module"`) @@ -108,7 +113,7 @@ See the [Your First Agent](docs/guides/your-first-agent.md) tutorial for a step- // Copyright 2026 CrisisMode Contributors ``` - **Conventional Commits** for all commit messages: - ``` + ```text feat(agent): add MySQL recovery agent fix(engine): handle timeout in step execution test(redis): add memory pressure scenario tests @@ -243,8 +248,11 @@ When you open a pull request: ## Further Reading +- [GETTING_STARTED.md](GETTING_STARTED.md) -- development setup, test environment, project layout - [Agent Development Guide](docs/guides/creating-a-recovery-agent.md) -- full agent contract, manifest reference, and safety checklist - [Check Plugin Reference](docs/guides/creating-a-check-plugin.md) -- wire protocol, Nagios/Goss/Sensu adapters - [Architecture Overview](docs/architecture.md) -- system architecture and key abstractions +- [CLI Reference](docs/cli-reference.md) -- every command, flag, exit code, and output format +- [Coverage & Validation Status](docs/coverage.md) -- what each agent has actually been validated against - [Recovery Agent Contract](specs/foundational/recovery-agent-contract.md) -- the authoritative specification - [Plugin Platform Guide](specs/architecture/plugin-platform.md) -- plugin taxonomy and platform architecture diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 81c2110..5a8d303 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -1,263 +1,263 @@ -# Getting Started +# Getting Started (Development) -This guide covers everything you need to start developing on CrisisMode — from first clone to running recovery agents against real infrastructure. +Setting up a CrisisMode development environment — first clone through running +agents against real degraded PostgreSQL. + +> **Just want to use the CLI?** That's [QUICKSTART.md](QUICKSTART.md) — install a +> binary and scan your stack. This guide is about building and modifying +> CrisisMode itself. +> +> **Ready to contribute?** [CONTRIBUTING.md](CONTRIBUTING.md) covers the +> contribution tracks, code standards, and PR expectations. ## Prerequisites -- **Node.js** >= 18 (recommended: use [fnm](https://github.com/Schniz/fnm) or [nvm](https://github.com/nvm-sh/nvm)) -- **pnpm** — `npm install -g pnpm` -- **Podman** — for the containerized test environment. `brew install podman && podman machine init && podman machine start` -- **Git** — with the repo cloned: `git clone git@github.com:trs-80/crisismode.git` +- **Node.js** >= 22 (recommended: [fnm](https://github.com/Schniz/fnm) or + [nvm](https://github.com/nvm-sh/nvm)) — Node 18 and 20 are end-of-life; CI + tests 22 and 24 +- **pnpm** — install the version pinned by `packageManager` in `package.json` + (currently `pnpm@10.30.3`): `npm install -g pnpm@10.30.3`. A bare + `npm install -g pnpm` gets the latest release instead, which can resolve the + lockfile differently than CI — CI takes its version from that same + `packageManager` field +- **Podman** — for the containerized test environment: + `brew install podman && podman machine init && podman machine start` +- **Git** — `git clone git@github.com:trs-80/crisismode.git` -### Optional +Optional, but the pre-commit hooks want them: -- **shellcheck** — `brew install shellcheck` (required by pre-commit hooks for `.sh` files) -- **gitleaks** — `brew install gitleaks` (required by pre-commit hooks for secret scanning) +- **shellcheck** — `brew install shellcheck` (lints staged `.sh` files) +- **gitleaks** — `brew install gitleaks` (secret scanning) -## First-time Setup +## First-time setup ```bash cd crisismode -pnpm install +pnpm install # also installs husky pre-commit hooks via `prepare` +pnpm run build # compiles to dist/, including the agent-sdk workspace ``` -This installs dependencies and sets up husky pre-commit hooks automatically (via the `prepare` script). - -Verify the hooks are working: +Verify the toolchain: ```bash -pnpm run typecheck # TypeScript compilation check +pnpm run typecheck # tsc --noEmit +pnpm test # vitest unit tests +pnpm run lint # eslint ``` -## Running the Demo - -The demo runs entirely in-memory using simulators — no database or infrastructure required: +Then run the in-memory demo — no database or infrastructure required: ```bash pnpm dev ``` -This walks through the full recovery pipeline for a PostgreSQL replication lag cascade: trigger → diagnosis → plan → validation → execution → forensic record. - -## Setting Up the Test Environment +That walks the full recovery pipeline for a PostgreSQL replication lag cascade: +trigger → diagnosis → plan → validation → execution → forensic record. -The test environment gives you real PostgreSQL with streaming replication, Prometheus, AlertManager, and a mock hub API. - -### Start the stack +To run the CLI from source without building: ```bash -./test/podman/scripts/start.sh +npx tsx src/cli/index.ts scan +npx tsx src/cli/index.ts agent list ``` -This pulls container images and starts: -- **PostgreSQL primary** on `localhost:5432` (user: `crisismode`, password: `crisismode`) -- **PostgreSQL replica** on `localhost:5433` (streaming replication from primary) -- **Prometheus** on `http://localhost:9090` (scraping PG metrics) -- **AlertManager** on `http://localhost:9093` (configured to webhook to `localhost:3000`) -- **postgres_exporter** on `http://localhost:9187` -- **Mock Hub API** on `http://localhost:8080` +## The test environment -### Validate +Real PostgreSQL with streaming replication, Prometheus, AlertManager, and a mock +hub API. ```bash -./test/smoke/run-all.sh # 16 checks: services, replication, metrics, hub API -./test/smoke/test-failure-injection.sh # 6 checks: inject failures, verify, reset +./test/podman/scripts/start.sh # start +./test/podman/scripts/status.sh # check +./test/podman/scripts/stop.sh # tear down ``` -### Inject failures +`start.sh` pulls images and brings up: + +| Service | Where | Notes | +|---|---|---| +| PostgreSQL primary | `localhost:5432` | user `crisismode`, password `crisismode` | +| PostgreSQL replica | `localhost:5433` | streaming replication from primary | +| Prometheus | `http://localhost:9090` | scraping PG metrics | +| AlertManager | `http://localhost:9093` | webhooks to `localhost:3000` | +| postgres_exporter | `http://localhost:9187` | | +| Mock Hub API | `http://localhost:8080` | | -These scripts create real degraded states in the test PostgreSQL: +Validate it came up correctly: ```bash -./test/failures/inject-replication-lag.sh # Pause WAL replay → growing lag -./test/failures/inject-connection-flood.sh # Open 200 idle connections -./test/failures/inject-long-queries.sh # Hold row locks + expensive scans -./test/failures/inject-slot-overflow.sh # Abandoned slot accumulating WAL -./test/failures/reset.sh # Restore everything to healthy +./test/smoke/run-all.sh # services, replication, metrics, hub API +./test/smoke/test-failure-injection.sh # inject, verify, reset round-trip +./test/smoke/test-alert-pipeline.sh # AlertManager → spoke webhook path ``` -### Run the spoke against real PostgreSQL +Each script prints a `passed/total` tally at the end. -```bash -# Dry-run: reads from real PG, logs mutations -pnpm run live +### Injecting real failures -# With lag injected first: -./test/failures/inject-replication-lag.sh -pnpm run live +These create genuinely degraded states in the test PostgreSQL — not mocks: -# Execute mode: actually runs SQL mutations -pnpm run live -- --execute +```bash +./test/failures/inject-replication-lag.sh # pause WAL replay → growing lag +./test/failures/inject-connection-flood.sh # 200 idle connections +./test/failures/inject-long-queries.sh # hold row locks + expensive scans +./test/failures/inject-slot-overflow.sh # abandoned slot accumulating WAL +./test/failures/inject-pgvector-unindexed.sh # vector column with no ANN index +./test/failures/reset.sh # restore everything to healthy +./test/failures/reset-pgvector.sh # undo just the pgvector injection ``` -### Run the webhook receiver +### Running against real PostgreSQL ```bash -pnpm run webhook # dry-run, listens on :3000 -pnpm run webhook --execute # execute mode +pnpm run live # dry-run: reads real PG, logs mutations +pnpm run live -- --execute # execute mode: runs real SQL mutations -# Then inject lag — AlertManager will fire an alert to the spoke -./test/failures/inject-replication-lag.sh +# A useful loop: +./test/failures/inject-replication-lag.sh && pnpm run live ``` -### Tear down +### Webhook receiver ```bash -./test/podman/scripts/stop.sh +pnpm run webhook # dry-run, listens on :3000 +pnpm run webhook --execute # execute mode + +# Inject lag; AlertManager fires an alert at the spoke +./test/failures/inject-replication-lag.sh ``` -## Project Layout +## Project layout ### CLI (`src/cli/`) -The unified `crisismode` CLI provides commands: `scan` (default), `diagnose`, `recover`, `status`, `ask`, `demo`, `init`, `webhook`, `watch`, `readiness`, plus subcommand groups `bundle` (ingest/respond/execute for SRE evidence bundles), `playbook` (list/validate/dry-run), `agent` (list/info), `registry` (list/search/install), and `mcp` (stdio MCP server) and `completions` (shell completions). Running `crisismode` with no arguments performs a zero-config health scan. Supporting modules handle system detection (`detect.ts`), zero-config agent discovery (`autodiscovery.ts`), structured output (`output.ts`), and error formatting (`errors.ts`); escalation levels live in `src/framework/escalation.ts`. See the [README CLI reference](README.md#cli-reference) for the full command list. +`src/cli/index.ts` is the entry point. Commands live in `src/cli/commands/`. +Supporting modules: `detect.ts` (port probing), `autodiscovery.ts` (zero-config +agent detection), `output.ts` (structured output), `errors.ts` (error +formatting), `status-presentation.ts` (single source for status → presentation +mappings), `visibility.ts` (coverage and maturity reporting). + +Full command surface: [docs/cli-reference.md](docs/cli-reference.md). -### Core Framework (`src/framework/`) +### Framework (`src/framework/`) | File | Purpose | |---|---| -| `engine.ts` | Executes recovery plans step-by-step. Handles dry-run vs execute mode. | -| `graph-engine.ts` | LangGraph-based graph execution engine for complex workflows | -| `backend.ts` | ExecutionBackend contract — shared interface for all execution backends | -| `safety.ts` | State capture, blast radius validation | -| `coordinator.ts` | Human approval logic (auto-approve decisions based on trust + catalog) | -| `validator.ts` | Validates plans against agent manifests | +| `engine.ts` | `LegacyExecutionEngine` — sequential step execution, dry-run vs execute | +| `graph-engine.ts` | `RecoveryGraphEngine` — LangGraph-based, supports checkpoint/resume | +| `backend.ts` | `ExecutionBackend` contract shared by all backends | +| `safety.ts` | State capture, blast-radius validation | +| `validator.ts` | Validates plans against agent manifests and safety rules | +| `coordinator.ts` | Human approval logic (trust + catalog driven) | | `catalog.ts` | Pre-authorized action catalog matching | | `forensics.ts` | Forensic record assembly and persistence | -| `hub-client.ts` | Spoke ↔ hub communication (bootstrap, heartbeat, forensics, policies) | -| `capability-registry.ts` | Global registry of standard recovery capabilities | +| `escalation.ts` | The five-level progressive escalation model | +| `agent-maturity.ts` | Collapses manifest maturity to the two labels operators see | +| `capability-registry.ts` | Registry of standard recovery capabilities | | `provider-registry.ts` | Resolves capability providers for plan steps | -| `operator-summary.ts` | Builds operator-facing health and readiness summaries | -| `symptom-router.ts` | Routes symptoms to appropriate recovery agents | +| `symptom-router.ts` | Routes symptoms to the appropriate agent | +| `root-cause-synthesis.ts` | Correlation clustering (the rule set is frozen — see CONTRIBUTING) | | `ai-diagnosis-universal.ts` | Universal AI-powered diagnosis for any agent | +| `operator-summary.ts` | Operator-facing health and readiness summaries | | `incident-report.ts` | Structured incident report generation | | `network-profile.ts` | Network diagnostics and profiling | -| `index.ts` | Barrel export for all framework modules | +| `triage.ts` / `triage-probes.ts` | Offline layered localization and verdict synthesis | +| `service-status/` | Third-party status checker — status page + reachability, never conflated | +| `guidance/` | Static `RemediationGuide` registry for console-only fixes | +| `hooks/` | 9-point lifecycle hook system | +| `playbook/` | Markdown playbook parser, runtime, discovery | +| `registry/` | Agent plugin discovery and manifest handling | +| `hub-client.ts` | Spoke ↔ hub communication | + +`src/readiness/` holds the scale-readiness rule registry and capacity ceilings — +see [docs/readiness.md](docs/readiness.md). ### Agents (`src/agent/`) -Each agent follows the same pattern: +Every agent follows the same six-file pattern: +```text +src/agent// + backend.ts # Interface both simulator and live client implement + simulator.ts # In-memory implementation for demos and tests + live-client.ts # Real infrastructure client + manifest.ts # Capabilities, risk profile, triggers, maturity + agent.ts # RecoveryAgent implementation + registration.ts # Lazy factory for the agent registry ``` -agent/ - / - backend.ts # Interface that both simulator and live client implement - simulator.ts # In-memory implementation for demos and tests - live-client.ts # Real infrastructure client (connects to actual systems) - manifest.ts # Agent manifest (capabilities, risk profile, triggers) - agent.ts # RecoveryAgent implementation (diagnose, plan, replan) - registration.ts # Lazy factory for the agent registry -``` - -**PostgreSQL Replication** (`pg-replication/`) — the MVP agent. Has a full live client that queries real `pg_stat_replication`. - -**Redis Memory** (`redis/`) — cache recovery agent. Live client complete; execute-verified via the torture harness. - -**etcd Recovery** (`etcd/`) — consensus cluster recovery. Handles leader election loops, NOSPACE alarms, member failures. Simulator complete. - -**Kafka Recovery** (`kafka/`) — broker recovery agent. Handles under-replicated partitions, leader imbalance, consumer lag cascades. Simulator complete. - -**Kubernetes Recovery** (`kubernetes/`) — cluster recovery agent. Handles node failures, pod crash loops, stuck deployments, PVC issues. Live client complete. - -**Ceph Storage** (`ceph/`) — distributed storage recovery. Handles OSD failures, degraded placement groups, pool near-full conditions. Simulator complete. - -**Flink Stream Processing** (`flink/`) — stream job recovery. Handles checkpoint failure cascades, savepoint corruption, backpressure. Simulator complete. - -**AI Provider** (`ai-provider/`) — AI service failover and fallback. Handles provider failover routing, rate limit management. Live client complete. - -**Config Drift** (`config-drift/`) — configuration drift detection and remediation. Handles drift detection, compliance enforcement. Live client complete; diagnosis validated in dry-run via the torture harness. - -**DB Migration** (`db-migration/`) — database migration safety. Handles migration safety checks, rollback orchestration. Live client complete; diagnosis validated in dry-run via the torture harness. -**Deploy Rollback** (`deploy-rollback/`) — deployment rollback orchestration (Vercel, requires `VERCEL_TOKEN`). Handles rollback coordination, canary failure response. Live client complete. +`src/config/builtin-agents.ts` is the authoritative roster. For the live list +with maturity labels, ask the CLI: -**Queue Backlog** (`queue-backlog/`) — queue backlog and lag recovery. Handles backlog reduction, consumer lag recovery. Live client complete; diagnosis validated in dry-run via the torture harness. - -**AWS S3 / DynamoDB / RDS** (`aws-s3/`, `aws-dynamodb/`, `aws-rds/`) — AWS backup verification and recovery (bucket versioning/lifecycle, PITR status, backup retention and snapshot recency). Live clients validated in dry-run against real AWS. - -**DNS** (`dns/`) — DNS resolution failure recovery. Live diagnosis plus local cache flush. - -**TLS** (`tls/`) — certificate expiry and chain health. Live diagnosis only. - -**Disk** (`disk/`) — local disk exhaustion detection. Live diagnosis only. +```bash +crisismode agent list # 26 registrations +crisismode agent info postgresql-replication-recovery +``` -**Backup Verification** (`backup/`) — backup verification and DR readiness across pluggable providers (filesystem, `aws_s3`, `aws_rds`). Live diagnosis only. +**Do not duplicate that roster in prose.** Which agents are validated versus +best-effort is tracked in one place: [docs/coverage.md](docs/coverage.md). +`src/agent/pg-replication/` is the reference implementation — read it first. -See `src/config/builtin-agents.ts` for the authoritative roster (19 agents) and the README status tables for what is execute-verified versus dry-run validated. +### Types (`src/types/`) -### Type System (`src/types/`) +Public types are defined in `packages/agent-sdk` (zero runtime dependencies) and +re-exported from `src/types/index.ts`. Key ones: -All contract types are defined here. Key types: -- `RecoveryPlan` — the plan structure with steps, impact analysis, rollback strategy -- `RecoveryStep` — 7 step types (system_action, diagnosis_action, human_notification, human_approval, checkpoint, replanning_checkpoint, conditional) -- `AgentManifest` — agent capabilities declaration +- `RecoveryPlan` — steps, impact analysis, rollback strategy +- `RecoveryStep` — the 7 step types +- `AgentManifest` — capability and risk declaration, including `plugin.maturity` - `ForensicRecord` — immutable audit trail - `AgentContext` — trigger, topology, trust levels, policies -- `HealthAssessment` / `OperatorSummary` — health assessment and operator-facing readiness types -- `PluginKind` / `CapabilityProviderDescriptor` — plugin ecosystem types for capability providers - -## Building a New Agent - -1. Create `src/agent//backend.ts` — define the interface for querying your target system -2. Create `src/agent//simulator.ts` — implement the interface with canned data -3. Create `src/agent//manifest.ts` — declare what your agent targets and its risk profile -4. Create `src/agent//agent.ts` — implement `RecoveryAgent` (diagnose, plan, replan) -5. Create `src/agent//registration.ts` — lazy factory for the agent registry -6. Register your agent in `src/config/builtin-agents.ts` via the agent registry (`src/config/agent-registry.ts`) -7. Add capabilities to `src/framework/capability-registry.ts` if your agent uses new capability domains - -The framework handles validation, approval workflows, forensic recording, and hub communication — your agent only needs to diagnose and produce plans. - -## Commit Conventions - -This repo uses [Conventional Commits](https://www.conventionalcommits.org/): - -``` -type(scope): description - -feat(agent): add Redis memory pressure recovery -fix: correct replication lag threshold check -docs: update README with webhook instructions -chore: update dependencies -test: add failure injection round-trip tests -``` - -Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `ci`, `perf`, `build` - -The commit-msg hook enforces this format. - -## Pre-commit Hooks +- `HealthAssessment` / `OperatorSummary` — health and operator-facing readiness +- `PluginKind` / `PluginMaturity` / `CapabilityProviderDescriptor` — plugin types -All hooks run automatically on `git commit`. To bypass (e.g., for WIP commits): +## Pre-commit hooks -```bash -git commit --no-verify -m "wip: work in progress" -``` +All hooks run on `git commit`. Bypass with `--no-verify` only for genuine WIP. -| Hook | What it checks | +| Hook | Checks | |---|---| | TypeScript typecheck | `tsc --noEmit` on staged `.ts` files | -| gitleaks | Secret detection (API keys, tokens, passwords) | +| gitleaks | Secrets (API keys, tokens, passwords) | | Sensitive files | Blocks `.pem`, `.key`, `.p12`, `.env`, `kubeconfig`, `tfstate` | -| shellcheck | Lints staged `.sh` files | +| shellcheck | Staged `.sh` files | | Large files | Blocks files >1MB | -| Conflict markers | Catches leftover `<<<<<<<` / `>>>>>>>` | -| Conventional commits | Enforces commit message format | +| Conflict markers | Leftover `<<<<<<<` / `>>>>>>>` | +| Conventional commits | Commit message format | + +Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/): +`type(scope): description`, where type is one of `feat`, `fix`, `docs`, `style`, +`refactor`, `test`, `chore`, `ci`, `perf`, `build`. -## Useful Commands +## Command reference ```bash pnpm dev # Demo mode (simulated) pnpm run live # Live mode against test PG (dry-run) pnpm run live -- --execute # Live mode with mutations pnpm run webhook # Start webhook receiver -pnpm test # Run unit tests (vitest) -pnpm run test:watch # Run tests in watch mode +pnpm test # Unit tests (vitest) +pnpm run test:watch # Tests in watch mode +pnpm run test:coverage # Tests with coverage +pnpm run test:cli # CLI smoke test pnpm run typecheck # TypeScript check +pnpm run lint # ESLint (lint:fix to autofix) pnpm run build # Compile to dist/ -./test/podman/scripts/start.sh # Start test environment -./test/podman/scripts/status.sh # Check test environment status -./test/smoke/run-all.sh # Run smoke tests -./test/failures/inject-replication-lag.sh # Inject test failure -./test/failures/reset.sh # Reset to healthy +pnpm run build:bundle # esbuild single-file bundle +pnpm run build:binary # Standalone binary +pnpm run eval:diagnosis # Diagnosis eval, writes eval/reports/ +pnpm run eval:diagnosis:gate # Fails below the 13/14 score gate +pnpm run guides:walkthrough # Generate a remediation-guide checklist +pnpm run guides:apply # Stamp verifiedOn for verified guides ``` + +Lint-time TypeScript is pinned to 6.0.2 via `.pnpmfile.cjs` because +typescript-eslint does not yet support the TS 7 native compiler; `tsc` itself +stays on TS 7. + +## Next steps + +- [CONTRIBUTING.md](CONTRIBUTING.md) — contribution tracks, code standards, PR expectations +- [docs/architecture.md](docs/architecture.md) — layers, engines, safety, plugin model +- [Your First Agent](docs/guides/your-first-agent.md) — build an agent start to finish +- [specs/foundational/recovery-agent-contract.md](specs/foundational/recovery-agent-contract.md) — the authoritative contract diff --git a/QUICKSTART.md b/QUICKSTART.md index 577289b..adaa400 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -1,110 +1,170 @@ # Quick Start -Get CrisisMode running in under 5 minutes. No configuration required. +Running CrisisMode against your own stack, from nothing to a useful answer. No +configuration required to start. -> **Fastest path:** prebuilt binaries (no Node.js required) are on -> [GitHub Releases](https://github.com/trs-80/crisismode/releases/latest) — -> see the [README install section](README.md#install). This guide covers -> running from source, which you'll want for development. +> **Want to build or modify CrisisMode instead?** That's +> [GETTING_STARTED.md](GETTING_STARTED.md) — clone, build, test environment, +> project layout. This guide is about *using* the CLI. -## Prerequisites +## Install -- **Node.js** >= 18 -- **pnpm** — `npm install -g pnpm` +Grab a prebuilt binary — no Node.js needed: -## Install +```bash +# macOS (Apple Silicon); see README for other platforms +curl -fsSL https://github.com/trs-80/crisismode/releases/latest/download/crisismode-darwin-arm64 -o crisismode +chmod +x crisismode +``` + +Put it on your `PATH`, then optionally enable tab completion: ```bash -git clone https://github.com/trs-80/crisismode.git -cd crisismode -pnpm install -pnpm run build +crisismode completions zsh # or bash, fish ``` -## Run your first scan +Running from a source checkout instead? Use `node dist/cli/index.js` in place of +`crisismode` after `pnpm install && pnpm run build`. -CrisisMode auto-detects services on your machine and checks what it finds: +## Your first scan + +CrisisMode auto-detects services on the machine and checks whatever it finds: ```bash -node dist/cli/index.js scan +crisismode ``` -No config file needed. The scan: -- Probes common ports for databases, caches, and message brokers -- Runs bundled check plugins (disk, memory, DNS, HTTP, TLS certificates) -- Scores overall system health 0–100 -- Suggests a next command for anything it finds +`scan` is the default command, so bare `crisismode` and `crisismode scan` do the +same thing. -Example output: +```text + Scanning for services... + Detected services: + ✓ postgresql at localhost:5432 + ✓ dns at localhost:53 -``` - System Health Score: 83/100 - Scanned at 2026-03-18T16:47:28.181Z (52ms) + Running health checks on 7 target(s)... - ID Service Status Level Summary - -------------------------------------------------------------------------------- - PLUG-001 plugin (check-certificate-expiry)UNKNOWN Diagnose Could not connect to localhost:443 - PLUG-002 plugin (check-disk-usage)OK Observe Disk usage normal, max 45% - PLUG-003 plugin (check-dns-resolution)OK Observe DNS resolution healthy - PLUG-004 plugin (check-http-endpoint)UNKNOWN Diagnose Endpoint unreachable - PLUG-005 plugin (check-memory-usage)OK Observe Memory usage normal at 53% + System Health Score: 100/100 + Scanned at 2026-08-09T15:09:00.480Z (586ms) - → Run `crisismode diagnose PLUG-001` to investigate plugin (check-certificate-expiry) + HEALTHY + PG-001 OK postgresql (default-postgres) — PostgreSQL replication is healthy. + All replicas are streaming and worst replay lag is 0s. + DNS-002 OK dns (local-dns) — DNS resolution is healthy. All configured + resolvers are reachable and returning consistent answers. + DISK-003 OK disk (local-disk) — Disk usage is healthy. All filesystems have + sufficient free space and inodes. ``` +No config file needed. The scan: + +- TCP-probes common ports for databases, caches, and brokers +- Picks up LLM providers from environment variables (`ANTHROPIC_API_KEY`, + `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`) +- Scores overall health 0–100 +- Suggests a next command for anything it flags + +Findings from best-effort agents are labelled as such in the output — they're +leads, not conclusions. [docs/coverage.md](docs/coverage.md) explains which is +which and why. + +### What gets detected without config + +These ports are probed by default: + +| Service | Port | +|---|---| +| PostgreSQL | 5432 | +| Redis | 6379 | +| etcd | 2379 | +| Kafka | 9092 | +| Kubernetes | 6443 | +| Ceph | 6789 | +| Flink | 8081 | +| DNS | 53 | +| RabbitMQ / AMQP | 5672 | + +Check plugins are discovered from `~/.crisismode/checks/`, `./checks/`, or +`$CRISISMODE_CHECK_PATH`. The prebuilt binary ships none — the example plugins +(disk usage, memory, DNS resolution, HTTP endpoint, certificate expiry, plus +Nagios/Goss/Sensu adapter samples) live in `checks/` in a repo checkout, so +you'll see them when running from source or after copying them into +`~/.crisismode/checks/`. + ## Drill into a finding -The scan tells you what to run next. Copy the suggested command: +Scan findings are labelled by target — `PG-001` above is the PostgreSQL target +`default-postgres`. To investigate one, pass the **target name**: ```bash -node dist/cli/index.js diagnose PLUG-002 +crisismode diagnose default-postgres +# equivalently +crisismode diagnose --target default-postgres ``` -For check plugins this runs the plugin's diagnose verb — a deeper inspection that lists individual findings with severity levels: +> Note: `diagnose` resolves its positional argument as a *target name*, not a +> finding id, so `crisismode diagnose PG-001` returns +> `Target "PG-001" not found in config` followed by the names you can use. +> Check-plugin findings are the exception — `PLUG-` ids are routed straight to +> the plugin's diagnose verb, so `crisismode diagnose PLUG-002` works as written. +> Scan's own "Investigate:" suggestion accounts for both. -``` - Plugin: check-disk-usage +For infrastructure agents this connects to the live service and runs AI-powered +analysis when `ANTHROPIC_API_KEY` is set, falling back to rule-based diagnosis +otherwise: - ✓ Healthy: All partitions below 70% usage +```bash +export ANTHROPIC_API_KEY=sk-ant-... +crisismode diagnose --target default-postgres ``` -For infrastructure agents (PostgreSQL, Redis, etcd, etc.), diagnose connects to the live service and runs AI-powered analysis if you have an `ANTHROPIC_API_KEY` set: +`diagnose` is read-only. It never mutates anything. + +## Three questions before you touch anything + +These are the commands worth reaching for first during an actual incident, +because all three are read-only and fast. + +**Is it me, my network, or them?** ```bash -export ANTHROPIC_API_KEY=sk-ant-... -node dist/cli/index.js diagnose --target my-postgres +crisismode triage ``` -## What services can CrisisMode detect? +Works offline. Walks outward from local interfaces → gateway → DNS → captive +portal → internet → your configured targets, and tells you which layer broke. +Exits 1 when the cause is local, network, or mixed — so it works in a script. + +**Is it down for everyone, or just me?** -Without any configuration, `scan` probes these ports: +```bash +crisismode down stripe github +``` + +Keeps two facts separate: what the provider's status page says, and whether this +machine can actually reach it. A status-page hiccup is never reported as an +outage. Exits 1 if anything looks down. -| Service | Port | -|-------------|-------| -| PostgreSQL | 5432 | -| Redis | 6379 | -| etcd | 2379 | -| Kafka | 9092 | +**Will this break under load?** -It also runs these bundled check plugins automatically: +```bash +crisismode readiness +``` -| Plugin | What it checks | -|--------------------------|----------------------------------------| -| check-disk-usage | Filesystem usage (warns at 80%, critical at 90%) | -| check-memory-usage | System memory utilization | -| check-dns-resolution | DNS resolver health | -| check-http-endpoint | HTTP connectivity to localhost | -| check-certificate-expiry | TLS certificate validity on localhost:443 | +Forward-looking and read-only: connection headroom, pooling, missing indexes, +slow queries, pgvector index health, plus honest capacity ceilings. It reports +`unknown` rather than guessing — see [docs/readiness.md](docs/readiness.md). -## Add a config file (optional) +## Add a config file -For non-default ports or remote hosts, generate a config: +For non-default ports, remote hosts, or credentials: ```bash -node dist/cli/index.js init +crisismode init ``` -This creates `crisismode.yaml`. Edit it to point at your infrastructure: +That writes `crisismode.yaml`. Point it at your infrastructure: ```yaml apiVersion: crisismode/v1 @@ -127,27 +187,37 @@ targets: passwordVar: PG_PASSWORD ``` -### AWS targets +Credentials are always read from environment variables or K8s Secrets at runtime +— never stored in the config file. + +### Third-party services -CrisisMode can verify backups stored in AWS services. Add an `aws` block with service-specific fields: +A `services:` list works on its own, with no `targets:` block. Bare +`crisismode down` then checks all of them: + +```yaml +services: + - stripe + - github + - example.com # raw domain — reachability only, no known status page +``` + +### AWS targets ```yaml targets: - # S3 backup bucket - name: s3-backups kind: aws-s3 aws: bucket: my-backup-bucket region: us-east-1 - # DynamoDB table (point-in-time recovery check) - name: orders-table kind: aws-dynamodb aws: table: orders region: us-east-1 - # RDS instance (automated backup / snapshot check) - name: prod-rds kind: aws-rds aws: @@ -155,51 +225,59 @@ targets: region: us-east-1 ``` -AWS credentials are resolved via the standard SDK chain (environment variables, `~/.aws/credentials`, IAM roles, IRSA). +Credentials resolve through the standard AWS SDK chain — environment variables, +`~/.aws/credentials`, IAM roles, IRSA. The AWS agents are best-effort: diagnosis +has been validated in dry-run against real AWS, but no execute path has been +verified. + +Then `crisismode scan` picks the config up automatically. -Then scan picks it up automatically: +## Recovery ```bash -node dist/cli/index.js scan +crisismode recover # dry-run: reads live systems, plans, executes nothing +crisismode recover --execute # actually runs recovery actions ``` -## Recovery (dry-run by default) +Dry-run is the default and you always see the plan first. Before trusting +`--execute` against anything you care about, read +[docs/coverage.md](docs/coverage.md) — end-to-end mutating recovery is verified +for exactly three scenarios today, and everything else should be treated as +experimental. -When scan finds an unhealthy service, step through the full recovery flow: +To watch the whole pipeline safely, run the simulator: ```bash -# Dry-run — reads from live systems, plans recovery, but does not execute -node dist/cli/index.js recover - -# Execute mode — actually runs recovery actions -node dist/cli/index.js recover --execute +crisismode demo ``` -Dry-run is the default. You always see the plan before anything is executed. - -## Demo mode +That walks a PostgreSQL replication lag cascade from trigger through diagnosis, +plan, validation, execution, and forensic record — entirely in memory. -See the full recovery pipeline with simulated data — no infrastructure needed: +## Output for scripts and pipelines ```bash -node dist/cli/index.js demo +crisismode scan --json # JSON Lines, one object per line +crisismode scan | cat # plain tab-separated (auto-detected when piped) +crisismode scan --terse # drop the plain-language explanations ``` -Walks through a PostgreSQL replication lag cascade: trigger → diagnosis → plan → validation → execution → forensic record. - -## Output modes - ```bash -# Machine-readable JSON (pipe to jq, scripts, dashboards) -node dist/cli/index.js scan --json - -# Disable color (auto-detected when piping) -node dist/cli/index.js scan | cat +crisismode readiness --json | jq 'select(.type == "readiness") | .verdict' ``` -## What's next - -- `node dist/cli/index.js ask "why is my database slow"` — natural language diagnosis -- `node dist/cli/index.js watch` — continuous health monitoring -- `node dist/cli/index.js webhook` — receive alerts from Prometheus AlertManager -- See [GETTING_STARTED.md](GETTING_STARTED.md) for the full development setup +Field-by-field format contracts are in +[docs/cli-reference.md](docs/cli-reference.md#output-modes). + +## Where to go next + +| Goal | Go to | +|---|---| +| Look up any command, flag, or exit code | [docs/cli-reference.md](docs/cli-reference.md) | +| Know what's actually been validated | [docs/coverage.md](docs/coverage.md) | +| Let Claude or another AI client drive it | [MCP server](docs/cli-reference.md#mcp-server) | +| Receive Prometheus AlertManager alerts | `crisismode webhook` | +| Monitor continuously | `crisismode watch` | +| Ask in plain English | `crisismode ask "why is my database slow"` | +| Write a recovery procedure | [Playbook Authoring](docs/playbook-authoring.md) | +| Build or modify CrisisMode | [GETTING_STARTED.md](GETTING_STARTED.md) | diff --git a/README.md b/README.md index 3d40998..64879aa 100644 --- a/README.md +++ b/README.md @@ -4,75 +4,33 @@ [![codecov](https://codecov.io/gh/trs-80/crisismode/graph/badge.svg)](https://codecov.io/gh/trs-80/crisismode) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) -[![Node](https://img.shields.io/badge/Node-%3E%3D18-green?logo=node.js&logoColor=white)](https://nodejs.org/) +[![Node](https://img.shields.io/badge/Node-%3E%3D22-green?logo=node.js&logoColor=white)](https://nodejs.org/) -CrisisMode is the recovery layer for your infrastructure. Monitoring tells you something is wrong. CrisisMode tells you what to do about it — safely. +**Monitoring tells you something is wrong. CrisisMode tells you what to do about it — safely.** -It diagnoses issues using AI, builds validated recovery plans with blast-radius controls, and executes them with human-in-the-loop oversight. Every action is preceded by a state capture. Every execution produces an immutable forensic record. Domain experts contribute recovery knowledge as agents and check plugins — the framework ensures that knowledge is applied safely when infrastructure is degraded and the cost of wrong actions is highest. +CrisisMode diagnoses infrastructure failures, builds validated recovery plans with +blast-radius controls, and executes them under human-in-the-loop oversight. Every +mutation is preceded by a state capture. Every execution leaves an immutable +forensic record. -**Website:** [crisismode.ai](https://crisismode.ai) - -## Who This Is For - -- SREs and platform engineers who get paged and need to act under pressure. -- AI app builders operating managed infrastructure with limited ops depth. -- On-call engineers who inherit systems they didn't build. -- Domain experts (database specialists, Kafka engineers, storage admins) who want to codify recovery knowledge. - -## From Alert to Recovery - -Live mode diagnosing real PostgreSQL replication lag (local podman test -environment; the excerpt shows a single elevated-risk step executing — see -[Validation status](#validation-status) for what is and isn't verified today): - -``` - Connecting to PostgreSQL... - ✅ Primary connected — 3 active connections - ✅ Replication: 1 replica(s) streaming - ✅ Replica connected — recovery mode: true, lag: 636s - - ── Live Replication Status ── - 🔴 10.89.0.5/32 | streaming | lag: 41s | sent: 0/63704F0 | replay: 0/5EDE5F8 +It is built for the moment when infrastructure is degraded and the cost of a wrong +action is highest — which is also why it reports what it *hasn't* verified. See +[Maturity and validation](#maturity-and-validation). - Phase 3: Diagnosis (Live — AI-Powered) - ────────────────────────────────────── - 🤖 AI analyzing system state... - Status: identified - Scenario: replication_lag_cascade - Confidence: 94% - Root cause: WAL replay paused on replica — sent LSN is advancing - but replay LSN is static, indicating a deliberate pause - or I/O bottleneck on the replica, not a network issue. +**Website:** [crisismode.ai](https://crisismode.ai) - Phase 4: Plan Creation - ────────────────────── - # Type Risk Name - ──────────────────────────────────────────────────────────── - 1 diagnosis_action — Assess replication lag - 2 human_notification — Notify on-call DBA - 3 checkpoint — Pre-recovery state capture - 4 system_action elevated Disconnect lagging replica - 5 system_action routine Redirect read traffic - 6 replanning_checkpoint — Assess progress - 7 human_approval — Approve resynchronization - 8 system_action high pg_basebackup + resync - 9 conditional — Restore traffic or notify - 10 human_notification — Recovery summary +## Who it's for - Phase 7: Execution (Live — EXECUTE MODE) - ───────────────────────────────────────── - 🔴 EXECUTE MODE — SQL mutations WILL be run against real PostgreSQL. - - Step step-004 [system_action] - Disconnect lagging replica 10.89.0.5/32 from replication - ✓ Precondition: Replica 10.89.0.5/32 is currently connected - ✓ Success: WAL sender for 10.89.0.5/32 is no longer present - ● SUCCESS (6ms) -``` +- **SREs and platform engineers** who get paged and need to act under pressure. +- **AI app builders** running managed infrastructure without deep ops backup. +- **On-call engineers** who inherit systems they didn't build. +- **Domain experts** — DBAs, Kafka and storage specialists — who want to codify + recovery knowledge as something executable instead of a wiki page. ## Install -Download a prebuilt binary from [GitHub Releases](https://github.com/trs-80/crisismode/releases/latest) — no Node.js required. macOS binaries are codesigned and notarized; every artifact ships with a SHA256 checksum. +Prebuilt binaries need no Node.js. macOS builds are codesigned and notarized, and +every artifact ships a SHA256 checksum. ```bash # Linux (x64) @@ -81,370 +39,184 @@ curl -fsSL https://github.com/trs-80/crisismode/releases/latest/download/crisism # macOS (Apple Silicon) curl -fsSL https://github.com/trs-80/crisismode/releases/latest/download/crisismode-darwin-arm64 -o crisismode -chmod +x crisismode -./crisismode scan +chmod +x crisismode && ./crisismode scan ``` -Also available: `crisismode-linux-arm64` and `crisismode-darwin-x64`. To verify a download, fetch the matching `.sha256` file and run `shasum -a 256 -c`. - -Enable tab completion for your shell: - -```bash -crisismode completions bash|zsh|fish -``` +Also built: `crisismode-linux-arm64`, `crisismode-darwin-x64`. To verify a +download, fetch the matching `.sha256` and run `shasum -a 256 -c`. Building from +source needs Node.js >= 22 — see [GETTING_STARTED.md](GETTING_STARTED.md). -## Quick Start +## 60-second demo -**Zero-config scan** — CrisisMode autodiscovers databases, caches, and brokers on your machine and checks what it finds: +No config, no infrastructure. CrisisMode autodiscovers what's running locally and +checks it: ```bash -crisismode +crisismode # zero-config health scan (the default command) ``` -**Demo mode** (no infrastructure required, building from source): +```text + System Health Score: 100/100 + Scanned at 2026-08-09T15:09:00.480Z (586ms) -```bash -pnpm install && pnpm dev + HEALTHY + PG-001 OK postgresql (default-postgres) — PostgreSQL replication is healthy. + DNS-002 OK dns (local-dns) — DNS resolution is healthy. + DISK-003 OK disk (local-disk) — Disk usage is healthy. ``` -**Real PostgreSQL** (requires [test environment setup](test/podman/scripts/start.sh)): +Three questions it answers without touching your infrastructure: ```bash -pnpm run live # Dry-run — reads real PG, logs mutations -pnpm run live -- --execute # Execute mode — runs recovery commands +crisismode triage # is it me, my network, or them? (exit 1 on local/network) +crisismode down stripe # is it down for everyone, or just me? +crisismode readiness # will this stack break under load? ``` -**AlertManager webhook:** +Then the full pipeline against a simulator — trigger → diagnosis → plan → +validation → execution → forensic record: ```bash -pnpm run webhook # Dry-run, listens on :3000 -pnpm run webhook --execute # Execute mode +crisismode demo ``` -See [QUICKSTART.md](QUICKSTART.md) for a full walkthrough. - -## What CrisisMode Recovers +Recovery is **dry-run by default**. `crisismode recover` reads live systems and +shows you the plan; mutations require an explicit `--execute`. -Status legend: +## From alert to recovery -- **Live (execute-capable)** — queries real infrastructure and can run recovery mutations in `--execute` mode -- **Live (diagnosis only)** — queries real infrastructure; recovery output is advisory (no mutating actions) -- **Simulator** — full agent logic against an in-memory simulator; live client not yet wired into the CLI +Live mode against real PostgreSQL replication lag (local podman test +environment), abridged: -### Modern Application Incidents - -| Scenario | Agent | Status | -|---|---|---| -| Bad deploy rollback | Deploy Rollback | Live (execute-capable) -- Vercel, requires `VERCEL_TOKEN` | -| LLM provider failures (key, quota, rate limit, model, outage) | LLM Provider | Live (diagnosis only) -- validated against real Anthropic and OpenAI keys; Google and OpenRouter paths implemented, best-effort validated | -| AI provider degradation / failover | AI Provider | Simulator -- explicit config and demo only; no longer auto-detected from API keys | -| Database migration failures | DB Migration | Live (execute-capable) -- diagnosis validated in dry-run via torture harness | -| Queue and worker backlog | Queue Backlog | Live (execute-capable) -- diagnosis validated in dry-run via torture harness | -| Config and environment drift | Config Drift | Live (execute-capable) -- diagnosis validated in dry-run via torture harness | - -### Stateful Infrastructure Recovery +```text + Phase 3: Diagnosis (Live — AI-Powered) + 🤖 AI analyzing system state... + Scenario: replication_lag_cascade + Confidence: 94% + Root cause: WAL replay paused on replica — sent LSN is advancing + but replay LSN is static, indicating a deliberate pause + or I/O bottleneck on the replica, not a network issue. -| System | Scenarios | Status | -|---|---|---| -| PostgreSQL | Replication lag, slot overflow, replica divergence, connection-pool exhaustion | Live (execute-capable) -- see validation status below | -| Redis | Memory pressure, client exhaustion, slow queries, cluster health | Live (execute-capable) | -| etcd | Leader election loop, member thrashing, snapshot corruption | Simulator | -| Kafka | Under-replicated partitions, consumer lag cascade | Simulator | -| Kubernetes | Node not-ready cascade, pod crashloop, stuck reconciliation | Live (execute-capable) | -| Ceph | OSD down cascade, degraded PGs, pool near-full | Simulator | -| Flink | Checkpoint failure cascade, TaskManager loss, backpressure | Simulator | -| AWS | S3, DynamoDB, and RDS recovery; backup verification, PITR, snapshot staleness | Live (execute-capable) -- validated in dry-run against real AWS | -| Terraform | Drift detection: intended state vs. observed AWS resources (RDS, S3, DynamoDB); read-only, suggests reconciliation | Simulator | - -### Host & Platform Health - -| Check | Scenarios | Status | -|---|---|---| -| DNS | Resolution failures, resolver health | Live (diagnosis + local cache flush) | -| TLS | Certificate expiry and chain health | Live (diagnosis only) | -| Disk | Local disk exhaustion | Live (diagnosis only) | -| Backup | Backup verification and DR readiness | Live (diagnosis only) | - -### Validation status - -The [crisismode-torture](https://github.com/trs-80/crisismode-torture) harness -runs CrisisMode against real degraded infrastructure (PostgreSQL replication, -Redis, 3-node etcd, 3-broker Kafka, Redis Cluster partitions, cascading -failures, and real AWS RDS/S3/DynamoDB). What it currently proves: - -- **Validated:** failure detection (typically 3–5s), AI diagnosis, and dry-run - recovery planning against real infrastructure, including real AWS and Vercel. -- **Execute-verified (as of 2026-07-13):** end-to-end `--execute` recovery — - a mutating recovery plan that actually ran, plus post-recovery health - verification confirming the underlying fault was resolved (not just that - the engine exited without error) — for exactly three scenarios: Redis - memory pressure, PostgreSQL WAL-replay-paused replication lag, and - PostgreSQL connection-pool exhaustion. All three are reproducible via the - crisismode-torture harness. -- **Not yet validated:** end-to-end `--execute` recovery for every other - agent/scenario in the tables above. Execute mode is functional for - individual actions, and the engine correctly refuses to run a plan when a - required live provider is missing (a blocked run is never counted as a - recovery), but no torture scenario besides the three above has completed a - full mutating recovery with post-recovery verification — treat the rest as - experimental until the harness verifies them. AWS and Vercel scenarios - remain dry-run/skipped in `--execute` mode. - -## Building Agents - -CrisisMode is extensible through recovery agents. Two contribution tracks: - -### Markdown Playbooks (low-code) - -Write recovery procedures as Markdown files with YAML frontmatter: - -```markdown ---- -name: "my-recovery-playbook" -version: "1.0.0" -description: "Recovery for my system" -severity: elevated -tags: [postgresql, replication] ---- - -### 1. Diagnose the issue -- type: diagnosis_action -- target: primary - -### 2. Notify the team -- type: human_notification -- channel: default -``` + Phase 4: Plan Creation + # Type Risk Name + 1 diagnosis_action — Assess replication lag + 2 human_notification — Notify on-call DBA + 3 checkpoint — Pre-recovery state capture + 4 system_action elevated Disconnect lagging replica + 7 human_approval — Approve resynchronization + 8 system_action high pg_basebackup + resync + 10 human_notification — Recovery summary -Validate and test: -```sh -crisismode playbook validate my-playbook.md -crisismode playbook dry-run my-playbook.md + Phase 7: Execution (Live — EXECUTE MODE) + Step step-004 [system_action] + ✓ Precondition: Replica 10.89.0.5/32 is currently connected + ✓ Success: WAL sender for 10.89.0.5/32 is no longer present + ● SUCCESS (6ms) ``` -See [Playbook Authoring Guide](docs/playbook-authoring.md) for details. +## Safety model -### TypeScript Agents - -For complex recovery logic, build agents with the SDK. The types-only SDK lives at [`packages/agent-sdk`](packages/agent-sdk) and is consumed from a repo checkout via the pnpm workspace (`@crisismode/agent-sdk`). - -Implement the `RecoveryAgent` interface with `assessHealth()`, `diagnose()`, `plan()`, and `replan()` methods. - -See the [Agent Development Guide](docs/guides/creating-a-recovery-agent.md) for a full tutorial. - -## Architecture - -``` -Alert Source (Prometheus) → Spoke Webhook Receiver - ↓ - Diagnose (query real systems) - ↓ - Plan (build recovery steps) - ↓ - Validate (manifest + policy checks) - ↓ - Execute (dry-run or live) - ↓ - Forensic Record → Hub API -``` +- Dry-run by default — reads real systems, logs mutations without running them. +- Pre-mutation state capture on every `elevated`-or-higher action. +- Blast-radius validation against the agent's declared manifest. +- Human approval gates; `--execute` fails closed when confirmation can't be + collected (non-interactive stdin). +- Five escalation levels: observe → diagnose → suggest → repair-safe → + repair-destructive. +- Immutable forensic record per execution, and a rollback strategy on every plan. -Hub-and-spoke topology: **spokes** (Layers 1-2) run close to target systems and handle execution and safety; the **hub** (Layers 3-4) provides coordination, analytics, and AI enrichment. Recovery actions progress through five escalation levels: observe, diagnose, suggest, repair-safe, and repair-destructive. +These are enforced by the plan validator, not by convention — see +[docs/architecture.md](docs/architecture.md). -See [Architecture Overview](docs/architecture.md) for details. +## Maturity and validation -## Safety Model +CrisisMode registers more agents than it has validated against real +infrastructure, and it says so at runtime rather than in a footnote. Its honesty +layer collapses every agent to one of two labels: -- Blast radius validation on every system action -- Pre-mutation state capture (checkpoint before any change) -- Human approval gates for elevated-risk operations -- Dry-run mode by default (reads real systems, logs mutations without executing) -- `--execute` fails closed when confirmation can't be collected (non-interactive stdin) -- Five-level progressive escalation (observe → diagnose → suggest → repair-safe → repair-destructive) -- Immutable forensic record for every execution +- **live-validated** — exercised against a real deployment of that system. +- **best-effort** — the checks exist and run, but have never been proven against + a live system. Findings are leads, not conclusions. This is the default; an + agent has to earn the other label. -## CLI Reference +Ask the tool directly — this is the authoritative roster, not this README: ```bash -crisismode # Zero-config health scan (default) -crisismode scan # Health scan with scored summary and next-action hints -crisismode diagnose # Health check + AI-powered diagnosis (read-only) -crisismode recover # Full recovery flow with plain-English AI summaries -crisismode status # Quick health probe -crisismode triage # Is it me, my network, or them? Offline localization — exits 1 when the cause is this machine or its network -crisismode down [...] # Is it down for everyone, or just me? Bare invocation checks the config's services: list — exits 1 if anything looks down, 2 on bad usage -crisismode ask "" # Natural language AI diagnosis -crisismode ask # Interactive diagnostic REPL -crisismode demo # Simulator demo mode -crisismode init # Generate crisismode.yaml configuration -crisismode init --agent # Scaffold a check plugin -crisismode webhook # Start webhook receiver for AlertManager -crisismode watch # Continuous shadow observation -crisismode readiness # Scale-readiness report (read-only): will this stack break under load, and where are the capacity ceilings? See docs/readiness.md - -crisismode bundle ingest # Ingest an SRE evidence bundle (v1) for AI diagnosis -crisismode bundle respond # Emit AdapterResponse v1 ("-" reads from stdin) -crisismode bundle execute # Translate a bundle to a RecoveryPlan (dry-run) - -crisismode playbook list # List discovered playbooks -crisismode playbook validate # Validate a playbook file -crisismode playbook dry-run # Preview compiled recovery plan - -crisismode agent list # List all registered agents -crisismode agent info # Show agent details - -crisismode registry list # List available check plugins -crisismode registry search # Search check plugins -crisismode registry install # Install a check plugin - -crisismode mcp # Start MCP server on stdio (read-only diagnosis tools) - -crisismode completions bash|zsh|fish # Generate shell completions +crisismode agent list # 26 registrations, each with its maturity label ``` -Output modes: `--json` for machine-readable output, plain text auto-detected when piped, colored TTY output by default. +Today **9 of 26** registrations are live-validated: PostgreSQL replication, +Kubernetes, DNS, TLS, disk, backup verification, third-party service status, and +the LLM-provider agent for Anthropic and OpenAI (it registers once per provider). +Everything else — Redis, etcd, Kafka, Ceph, Flink, AWS (S3/DynamoDB/RDS), +Terraform drift, deploy rollback, DB migration, queue backlog, config drift, +vector stores, AI-provider failover — is best-effort. -### Pipe output format +Separately, end-to-end `--execute` recovery (a mutating plan that ran, plus +post-recovery verification that the fault was actually gone) is proven for +**exactly three scenarios** as of 2026-07-13, all reproducible in the +[crisismode-torture](https://github.com/trs-80/crisismode-torture) harness: +Redis memory pressure, PostgreSQL WAL-replay-paused replication lag, and +PostgreSQL connection-pool exhaustion. Treat every other execute path as +experimental. AWS and Vercel scenarios remain dry-run only. -When stdout is not a TTY and `--json` isn't set, `crisismode scan` emits plain, tab-separated rows instead of colored output — stable enough to feed to `cut`/`awk`. +Full per-system coverage tables and what each harness proves: +**[docs/coverage.md](docs/coverage.md)**. -A `scan` row has 4 fields: +## Extending it -``` -scan\t\t\t -``` +Two contribution tracks, depending on whether your recovery knowledge is a +procedure or a decision tree: -Each `finding` row has 7 fixed fields, so every row has the same column count regardless of whether a finding has a guide: - -``` -finding\t\t\t\t\t\t -``` - -| # | Field | Notes | +| Track | You write | Good for | |---|---|---| -| 1 | `finding` | literal row-type marker | -| 2 | `id` | finding id | -| 3 | `service` | e.g. `postgresql (prod-db-01)` | -| 4 | `status` | `healthy` / `recovering` / `unhealthy` / `unknown` | -| 5 | `confidence` | 0–1 | -| 6 | `summary` | free text; tab/newline characters are stripped so they can't shift later columns | -| 7 | `guide_refs` | `guide:[,...]`, empty string when the finding has no matched remediation guide | - -### JSON output format +| **Playbook** | Markdown + YAML frontmatter | A runbook with fixed steps | +| **Agent** | TypeScript implementing `RecoveryAgent` | Diagnosis that builds different plans for different failures | -The `--json` flag emits **JSON lines** (one JSON object per line), not a single JSON document. Each line has a `type` field indicating the data it carries: - -| Type | Description | -|---|---| -| `health` | Health assessment with `status` and `signals` array | -| `diagnosis` | AI-powered diagnosis with `scenario`, `confidence`, and root cause | -| `plan` | Recovery plan with `steps` array | -| `triage` | Localization verdict (`local`/`network`/`remote`/`mixed`/`healthy`) with per-layer results. Exit code 0 for `healthy`/`remote`, 1 for `local`/`network`/`mixed` | - -Example usage: +Playbooks compile to the same `RecoveryPlan` the engine runs for code-based +agents, through the same validator — no shortcuts, no second safety path. ```bash -# Pipe to jq for human-readable inspection -crisismode recover --target my-db --json | jq 'select(.type == "diagnosis")' - -# Extract just the plan steps -crisismode recover --target my-db --json | jq 'select(.type == "plan") | .plan.steps' +crisismode playbook validate my-playbook.md +crisismode playbook dry-run my-playbook.md ``` -### Third-party service checks (`down`) +Check plugins are a third, smaller entry point: a shell script that probes a +system and reports health on a JSON wire protocol. CrisisMode also adapts +existing Nagios/Icinga/Checkmk plugins, Goss assertions, and Sensu checks, so +established checks work without a rewrite. -`crisismode down` checks third-party dependencies (Stripe, GitHub, Vercel, an LLM provider, ...) against two separate facts: what the provider's own status page reports, and whether this machine can reach it — a status-page hiccup is never reported as an outage. Read-only (escalation level 2: Diagnose). +- [Playbook Authoring Guide](docs/playbook-authoring.md) +- [Your First Agent](docs/guides/your-first-agent.md) · [Agent reference](docs/guides/creating-a-recovery-agent.md) +- [Your First Check Plugin](docs/guides/your-first-check-plugin.md) · [Check plugin reference](docs/guides/creating-a-check-plugin.md) -Exit codes: `0` nothing checked looks like a problem, `1` at least one service does, `2` the command itself was called wrong (an unrecognized flag — the only CrisisMode command that does this). +## Works with your tools -Check specific services ad hoc — catalog ids (`stripe`, `github`, `vercel`, `netlify`, `supabase`, `cloudflare`, `npm`, `twilio`, `sendgrid`, `render`, `fly`, `upstash`), aliases (`flyio` → `fly`), raw domains (reachability only, no known status page), or `anthropic`/`openai` (routed through the LLM Provider agent's own status source — `down`-only; see below): +**MCP server** — `crisismode mcp` exposes 8 read-only diagnosis tools over stdio, +so Claude Code or any MCP client can inspect your infrastructure. The MCP surface +never mutates anything. ```bash -crisismode down stripe github -``` - -```text - ✅ Stripe (healthy) - Stripe is healthy and reachable. - ✅ GitHub (healthy) - GitHub is healthy and reachable. -``` - -Or configure a standing list in `crisismode.yaml` and run `crisismode down` with no arguments. A `services:` list is valid on its own — no `targets:` block required: - -```yaml -apiVersion: crisismode/v1 -kind: SiteConfig -metadata: - name: my-stack -services: - - stripe - - example.com +claude mcp add crisismode -- crisismode mcp ``` -Each `services:` entry is a catalog id/alias, a raw domain (reachability-only — no known status page, so a `down` verdict there means "can't reach it," not "provider incident"), or `{ host, port }` for a non-default port. `anthropic`/`openai` are rejected here (config validation error) — they're covered automatically by the LLM Provider agent whenever the corresponding API key is set, and are only meant for the ad-hoc `crisismode down anthropic` form above. Listing them in `services:` would make `scan`/`watch` DNS-probe the literal hostname instead of routing through the provider's real status source. - -## Evidence Bundles - -CrisisMode speaks the SRE evidence-bundle v1 format, so external incident tooling can hand it a bundle of evidence (logs, metrics, operator notes) and get back a ranked diagnosis with policy-checked recovery actions: - -- `bundle ingest` — read-only AI diagnosis of the evidence -- `bundle respond` — full AdapterResponse v1: ranked hypotheses with evidence citations, proposed actions gated by action-class policy, and explicit abstention when evidence is insufficient -- `bundle execute` — translate a bundle into a validated RecoveryPlan (dry-run) - -All three accept a file path or `-` for stdin, making them easy to wire into pipelines: +**Evidence bundles** — CrisisMode speaks the SRE evidence-bundle v1 format, so +external incident tooling can hand it logs, metrics, and operator notes and get +back ranked hypotheses with policy-gated actions. Reads a path or stdin: ```bash cat incident-bundle.json | crisismode bundle respond - ``` -## MCP Server - -`crisismode mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io) server on stdio, so AI agents (Claude Code, Claude Desktop, or any MCP client) can diagnose your infrastructure directly: +**Pipelines** — `--json` emits JSON lines; plain tab-separated output is +auto-detected when stdout isn't a TTY. ```bash -# Claude Code -claude mcp add crisismode -- crisismode mcp -``` - -Or in `.mcp.json`: - -```json -{ - "mcpServers": { - "crisismode": { "command": "crisismode", "args": ["mcp"] } - } -} +crisismode recover --target my-db --json | jq 'select(.type == "diagnosis")' ``` -Every MCP tool is read-only — the MCP surface never mutates infrastructure: - -| Tool | What it does | -|---|---| -| `crisismode_scan` | Zero-config health scan with a 0–100 score and per-service findings | -| `crisismode_diagnose` | Health assessment + diagnosis for one target (AI-powered with `ANTHROPIC_API_KEY`, rule-based otherwise) | -| `crisismode_status` | Quick UP/DOWN probe of configured or detected services | -| `crisismode_list_agents` | The built-in recovery agent roster | -| `crisismode_bundle_ingest` | Read-only diagnosis of an SRE evidence bundle (v1) | -| `crisismode_bundle_respond` | Ranked hypotheses with evidence citations and policy-gated proposed actions | -| `crisismode_bundle_plan` | Translate a bundle into a dry-run RecoveryPlan (returned, never executed) | -| `crisismode_readiness` | Forward-looking scale-readiness report: connection headroom, pooling, indexes, slow queries; includes capacity ceilings and a conditional weak-link verdict — see [docs/readiness.md](docs/readiness.md) | - -## Check Plugin Ecosystem - -CrisisMode consumes external health checks through a unified adapter layer, making thousands of existing checks available without rewriting them: - -- **Native check plugins** — JSON wire protocol for purpose-built CrisisMode checks -- **Nagios/Icinga/Checkmk plugins** — thousands of battle-tested infrastructure checks -- **Goss YAML health assertions** — declarative system state validation -- **Sensu checks** — Graphite, InfluxDB, OpenTSDB, and Prometheus metric formats - -See [docs/guides/creating-a-check-plugin.md](docs/guides/creating-a-check-plugin.md) for the check plugin authoring guide. - -## Contributing - -Domain experts contribute recovery knowledge as agents, playbooks, and check plugins — the framework handles safety, validation, and execution. - -See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution workflows and [GETTING_STARTED.md](GETTING_STARTED.md) for developer setup. +Full command list, flags, exit codes, and output-format contracts: +**[docs/cli-reference.md](docs/cli-reference.md)**. ## Deployment @@ -456,22 +228,30 @@ helm install crisis-spoke deploy/helm/crisismode-spoke/ \ --set targetNamespaces='{default,production}' ``` -The spoke runs in 256Mi and operates autonomously when the hub is unreachable. +Spokes (Layers 1–2) run close to target systems and own execution and safety; +the hub (Layers 3–4) adds coordination, analytics, and AI enrichment. A spoke +runs in 256Mi and keeps working when the hub is unreachable. -## Specifications +## Documentation -- [Recovery Agent Contract](specs/foundational/recovery-agent-contract.md) — the authoritative agent interface definition -- [Deployment & Operations](specs/deployment/operations.md) — hub-and-spoke architecture, integration patterns, operational management -- [Plugin Platform Architecture Guide](specs/architecture/plugin-platform.md) — how the repo evolves from bespoke agents to a scalable plugin ecosystem -- [Operator Health & AI Services](specs/architecture/operator-health-and-ai-services.md) — operator summary, AI diagnosis, and site config spec +| If you want to… | Read | +|---|---| +| Use the CLI on your own stack | [QUICKSTART.md](QUICKSTART.md) | +| Build and hack on CrisisMode | [GETTING_STARTED.md](GETTING_STARTED.md) | +| Contribute an agent or playbook | [CONTRIBUTING.md](CONTRIBUTING.md) | +| Understand the design | [docs/architecture.md](docs/architecture.md) | +| Look up a command or output format | [docs/cli-reference.md](docs/cli-reference.md) | +| Know what's actually validated | [docs/coverage.md](docs/coverage.md) | +| Check scale readiness | [docs/readiness.md](docs/readiness.md) | + +Specifications: [Recovery Agent Contract](specs/foundational/recovery-agent-contract.md) +(authoritative) · [Deployment & Operations](specs/deployment/operations.md) · +[Plugin Platform](specs/architecture/plugin-platform.md) · +[Operator Health & AI Services](specs/architecture/operator-health-and-ai-services.md) ## License -The spoke runtime, agent SDK, and specifications are licensed under **Apache 2.0**. See [LICENSE](LICENSE) and [NOTICE](NOTICE) for details. - -| Component | License | -|---|---| -| Spoke runtime (`src/framework/`, `src/agent/`, `src/types/`) | Apache 2.0 | -| Agent SDK and contract spec (`specs/foundational/`) | Apache 2.0 | -| Test infrastructure (`test/`, `deploy/`) | Apache 2.0 | -| Hub API, coordination, and management UI | Commercial (not in this repo) | +Apache 2.0 for everything in this repo — spoke runtime, agent SDK, specs, and +test infrastructure. See [LICENSE](LICENSE) and [NOTICE](NOTICE). The hub API, +coordination service, and management UI are commercial and not part of this +repository. diff --git a/docs/agents/backup-verification.md b/docs/agents/backup-verification.md index f2d0d13..3497421 100644 --- a/docs/agents/backup-verification.md +++ b/docs/agents/backup-verification.md @@ -41,7 +41,7 @@ crisismode scan The agent appears in scan results with finding ID prefix `BKUP`: -``` +```text BKUP-001 unhealthy Backup failures detected. Missing or corrupted backups require immediate attention. ``` @@ -198,7 +198,7 @@ Backups are healthy, but their size means estimated restore time may exceed reco The agent uses a **strategy pattern** where backup technologies are pluggable providers: -``` +```text BackupVerificationAgent (coordinator) └── BackupBackend └── verifyAll(configs[]) → BackupVerificationReport @@ -278,7 +278,7 @@ The agent reports four health signals during `assessHealth`: The live client recognizes these file patterns: -``` +```text .sql .sql.gz .sql.bz2 .sql.xz .sql.zst .dump .dump.gz .tar .tar.gz .tgz .tar.bz2 .tar.xz .tar.zst @@ -387,7 +387,7 @@ Simulator states: `no_backups_found`, `stale_backup`, `size_anomaly`, `integrity ## File structure -``` +```text src/agent/backup/ backend.ts # BackupBackend + BackupProvider interfaces simulator.ts # In-memory simulator (7 states, 6 failure scenarios) diff --git a/docs/architecture.md b/docs/architecture.md index 1e00317..68ff175 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -46,7 +46,7 @@ graph TB The execution kernel runs recovery plans step by step. Two execution engines are available: - **LegacyExecutionEngine** — Sequential, callback-based. Each step runs in order. Failures halt execution. Suitable for simple recovery flows. -- **RecoveryGraphEngine** — Built on LangGraph. Steps become graph nodes with conditional edges. Supports durable checkpointing (MemorySaver for dev, PostgresSaver for production), interrupt/resume for human-in-the-loop approval, and crash recovery from the last checkpoint. +- **RecoveryGraphEngine** — Built on LangGraph. Steps become graph nodes with conditional edges. Supports checkpointing, interrupt/resume for human-in-the-loop approval, and recovery from the last checkpoint. The checkpointer is injectable and defaults to LangGraph's in-memory `MemorySaver`; a durable store (e.g. `PostgresSaver`) is the intended hub deployment but is not wired up in this repo. Both engines share the same `ExecutionBackend` interface for command execution and check evaluation. @@ -130,7 +130,7 @@ A `RecoveryPlan` contains metadata, impact assessment, an ordered list of steps, Playbooks are Markdown files with YAML frontmatter that describe recovery procedures. They compile to the same `RecoveryPlan` structure used by code-based agents. -``` +```text Markdown + YAML frontmatter | v @@ -193,8 +193,93 @@ Two execution modes control whether mutations actually run: - **`dry-run`** (default) — Reads from real systems, logs what mutations would happen, but does not execute them. All safety checks still run. State transitions are simulated. - **`execute`** — Runs all operations including system mutations. Requires explicit opt-in via `--execute` flag. +## Escalation Levels + +Every action CrisisMode can take is placed on a five-level scale +(`src/framework/escalation.ts`), which is what lets output say how far a +suggestion goes before anyone runs it: + +| Level | Name | Touches the system? | +|---|---|---| +| 1 | Observe | No — read-only health checks, no interaction | +| 2 | Diagnose | Reads only — live queries | +| 3 | Suggest | Generates plans, executes nothing | +| 4 | Repair (safe) | Executes `routine` and `elevated` risk actions | +| 5 | Repair (destructive) | Executes `high` and `critical` risk actions | + +`triage`, `down`, and `readiness` never exceed level 2 by design. + +## The Honesty Layer + +CrisisMode registers more agents than it has validated, so it treats "how much do +we actually know" as a first-class part of the architecture rather than a +documentation problem. + +- **Maturity collapse** (`src/framework/agent-maturity.ts`) — a manifest's + five-value `PluginMaturity` is reduced to two operator-facing labels: + `live_validated`, or best-effort for everything else. Unknown and unregistered + kinds default to best-effort, and a *kind* only counts as live-validated when + every agent registered for it says so. Best-effort findings carry an explicit + "treat this as a lead, not a conclusion" suffix in human output. +- **Unknown is a real status.** Health findings, readiness rules, and provider + checks can return `unknown` with a `reason` instead of guessing. `unknown` + never moves a readiness score or verdict — demanding zero unknowns would + invite fabrication. +- **Omit rather than estimate.** A capacity ceiling that cannot be computed from + declared or measured inputs is dropped with a reason, never approximated. +- **Failures degrade coverage, not delivery.** Per-rule and per-agent error + isolation turns a thrown error into an `unknown` finding, leaving the rest of + the report intact. +- **Blocked is not recovered.** When a required live capability provider is + missing, the engine refuses to run the plan, and a blocked run is never counted + as a successful recovery. + +See [coverage.md](coverage.md) for where each agent currently stands. + +## Localization and Outward Checks + +Not every incident is inside your infrastructure, so three read-only subsystems +answer questions that precede recovery: + +- **Triage** (`triage.ts`, `triage-probes.ts`) — offline layered localization. + Probes outward through interfaces → gateway → DNS → captive portal → internet + → configured targets, then synthesizes a verdict of `local`, `network`, + `remote`, `mixed`, or `healthy`. It works with no connectivity at all, which is + the situation it exists for. +- **Service status** (`service-status/`) — combines a provider's own status page + (Statuspage v2 parsing, plus a catalog of known services) with an independent + reachability probe, and never conflates the two: a status-page hiccup is not + reported as an outage, and an unreachable host is not reported as a provider + incident. +- **Readiness** (`src/readiness/`) — forward-looking rules and capacity ceilings + answering "will this break under load?" rather than "is it broken now?". See + [readiness.md](readiness.md). + +## Remediation Guidance + +Some fixes CrisisMode must not perform itself — rotating a key, changing a +billing plan, resizing a managed instance through a vendor console. Rather than +staying silent, it ships a static `RemediationGuide` registry +(`src/framework/guidance/`) keyed to readiness rule ids and agent `checkId`s. +Each guide carries console steps, a CLI equivalent, the expected outcome, and a +`verifiedOn` date. + +Two properties keep this from rotting into stale instructions: + +- A guide's `applicableFindingTypes` must name something the codebase actually + emits; a test fails otherwise, so a renamed rule cannot silently orphan its + guidance. +- A test fails when any guide's `verifiedOn` is more than 12 months old, so + console paths are re-walked on a schedule instead of during someone's incident. + +`scan`, `diagnose`, `readiness`, and `recover` all render guidance identically, +from the same registry. + ## Further Reading - [Recovery Agent Contract](../specs/foundational/recovery-agent-contract.md) — authoritative specification - [Deployment & Operations](../specs/deployment/operations.md) — hub-and-spoke deployment details - [Agent Development Guide](guides/creating-a-recovery-agent.md) — building a new agent from scratch +- [CLI Reference](cli-reference.md) — commands, flags, exit codes, output formats +- [Coverage & Validation Status](coverage.md) — what has actually been validated +- [Scale Readiness](readiness.md) — readiness rules, ceilings, and the honesty contract diff --git a/docs/cli-reference.md b/docs/cli-reference.md new file mode 100644 index 0000000..4cb0a2d --- /dev/null +++ b/docs/cli-reference.md @@ -0,0 +1,282 @@ +# CLI Reference + +Every command, flag, exit code, and output-format contract. Run +`crisismode --help` for the same command list from the binary itself. + +From a source checkout, substitute `node dist/cli/index.js` (after +`pnpm run build`) or `npx tsx src/cli/index.ts` for `crisismode`. + +## Commands + +### Diagnosis and recovery + +| Command | What it does | +|---|---| +| `crisismode` | Zero-config health scan — the default when no command is given | +| `crisismode scan` | Health scan with a 0–100 score, findings, and next-action hints | +| `crisismode diagnose []` | Health check plus diagnosis (read-only). The positional is a **target name** from config (`--target` is equivalent), except that a `PLUG-` finding id is routed to that check plugin's diagnose verb | +| `crisismode recover` | Full recovery flow. Dry-run unless `--execute` | +| `crisismode status` | Quick health probe | +| `crisismode ask ""` | Natural-language AI diagnosis | +| `crisismode ask` | Interactive diagnostic REPL | + +### Localization and outlook + +| Command | What it does | +|---|---| +| `crisismode triage` | Is the problem this machine, its network, or the remote services? Offline. Exits 1 on `local`/`network`/`mixed` | +| `crisismode down [...]` | Is it down for everyone, or just me? Bare form checks the config's `services:` list. Exits 0/1, or 2 on bad usage | +| `crisismode readiness` | Scale-readiness report (read-only): will this stack break under load, and where are the capacity ceilings? See [readiness.md](readiness.md) | + +### Setup and operation + +| Command | What it does | +|---|---| +| `crisismode init [path]` | Generate `crisismode.yaml` | +| `crisismode init --plugin ` | Scaffold a check plugin (`--agent` is a deprecated alias) | +| `crisismode demo` | Simulator demo — full pipeline, no infrastructure | +| `crisismode webhook` | Start a webhook receiver for Prometheus AlertManager | +| `crisismode watch` | Continuous shadow observation | +| `crisismode mcp` | Start the MCP server on stdio (read-only tools) | +| `crisismode completions bash\|zsh\|fish` | Generate shell completions | + +### Subcommand groups + +| Command | What it does | +|---|---| +| `crisismode agent list` | All registered agents with maturity labels | +| `crisismode agent info ` | One agent's targets, risk profile, and maturity | +| `crisismode playbook list` | Discovered playbooks | +| `crisismode playbook validate ` | Validate a playbook and its compiled plan | +| `crisismode playbook dry-run ` | Preview the compiled recovery plan | +| `crisismode bundle ingest ` | Read-only AI diagnosis of an SRE evidence bundle (v1) | +| `crisismode bundle respond ` | Emit AdapterResponse v1 | +| `crisismode bundle execute ` | Translate a bundle into a RecoveryPlan (dry-run) | +| `crisismode registry list` | Available check plugins | +| `crisismode registry search ` | Search check plugins | +| `crisismode registry install ` | Install a check plugin | + +## Flags + +This is the complete set — there are no per-command flags beyond these. + +| Flag | Applies to | Effect | +|---|---|---| +| `--config ` | all | Path to `crisismode.yaml` | +| `--target ` | all | Target name from config | +| `--category ` | `scan` | Comma-separated service kinds to scan | +| `--plugin ` | `init` | Scaffold a check plugin instead of a config file | +| `--agent ` | `init` | Deprecated alias for `--plugin`. Still scaffolds, prints a deprecation notice; `--plugin` wins if both are given | +| `--execute` | `recover`, `webhook` | Enable mutations. Without it, everything is dry-run | +| `--health-only` | `recover` | Health check only, no diagnosis | +| `--local` | `registry install` | Install to `./checks/` instead of `~/.crisismode/checks/` | +| `--force` | `registry install` | Overwrite an existing plugin installation | +| `--terse` | all | Suppress plain-language explanations and risk framing. Human output only — `--json` always carries the full data | +| `--json` | all | Machine-readable JSON output | +| `--no-color` | all | Disable colored output | +| `--verbose` | all | Additional detail | +| `-h`, `--help` | all | Show help | +| `-v`, `--version` | all | Show version | + +## Exit codes + +Most commands exit 0 on success and non-zero when they fail to run. Two +commands use the exit code to carry a *verdict*, so they can be used in scripts +and CI: + +| Command | 0 | 1 | 2 | +|---|---|---|---| +| `triage` | verdict is `healthy` or `remote` | verdict is `local`, `network`, or `mixed` | — | +| `down` | nothing checked looks like a problem | at least one service does | the command was called wrong (unrecognized flag) — the only CrisisMode command that does this | + +## Output modes + +Three modes, auto-selected: + +- **human** (default on a TTY) — colored, emoji severity indicators, and + plain-language explanations. Suppress the explanations with `--terse`. +- **pipe** (auto-detected when stdout is not a TTY) — plain text, no ANSI, + tab-separated. +- **machine** (`--json`) — JSON Lines with metadata. + +### Pipe format + +`crisismode scan` emits tab-separated rows, stable enough to feed `cut`/`awk`. + +A `scan` row has 4 fields: + +```text +scan\t\t\t +``` + +Every `finding` row has the same 7 fields, whether or not the finding has a +remediation guide, so the column count never shifts: + +```text +finding\t\t\t\t\t\t +``` + +| # | Field | Notes | +|---|---|---| +| 1 | `finding` | Literal row-type marker | +| 2 | `id` | Finding id, e.g. `PG-001` | +| 3 | `service` | e.g. `postgresql (default-postgres)` | +| 4 | `status` | `healthy` / `recovering` / `unhealthy` / `unknown` | +| 5 | `confidence` | 0–1 | +| 6 | `summary` | Free text; tabs and newlines are stripped so they can't shift later columns | +| 7 | `guide_refs` | `guide:[,...]`, empty when no remediation guide matched | + +`triage` uses the same shape with a `triage` row and one `layer` row per probe: + +```text +triage\t\t\t +layer\t\t\t +``` + +### JSON format + +`--json` emits **JSON Lines** — one object per line, not a single document. Each +line carries a `type`: + +| Type | Contents | +|---|---| +| `health` | Health assessment with `status` and a `signals` array | +| `diagnosis` | Diagnosis with `scenario`, `confidence`, and root cause | +| `plan` | Recovery plan with a `steps` array | +| `triage` | Localization verdict (`local`/`network`/`remote`/`mixed`/`healthy`) with per-layer results | +| `readiness` | Scale-readiness report, fields spread at the top level (no sub-key) | + +```bash +# Human-readable inspection +crisismode recover --target my-db --json | jq 'select(.type == "diagnosis")' + +# Just the plan steps +crisismode recover --target my-db --json | jq 'select(.type == "plan") | .plan.steps' + +# Readiness verdict only +crisismode readiness --json | jq 'select(.type == "readiness") | .verdict' +``` + +## Third-party service checks (`down`) + +`crisismode down` keeps two facts separate: what the provider's own status page +reports, and whether *this machine* can reach it. A status-page hiccup is never +reported as an outage. Read-only — escalation level 2 (Diagnose). + +```bash +crisismode down stripe github +``` + +```text + ✅ Stripe (healthy) + Stripe is healthy and reachable. + ✅ GitHub (healthy) + GitHub is healthy and reachable. +``` + +Accepted arguments: + +- **Catalog ids** — `stripe`, `github`, `vercel`, `netlify`, `supabase`, + `cloudflare`, `npm`, `twilio`, `sendgrid`, `render`, `fly`, `upstash` +- **Aliases** — e.g. `flyio` → `fly` +- **Raw domains** — reachability only, since there's no known status page. A + `down` verdict there means "can't reach it," not "provider incident" +- **`anthropic` / `openai`** — routed through the LLM Provider agent's own status + source. Ad-hoc only; see the config note below + +### Standing list in config + +Run bare `crisismode down` to check a configured list. A `services:` block is +valid on its own — no `targets:` required: + +```yaml +apiVersion: crisismode/v1 +kind: SiteConfig +metadata: + name: my-stack +services: + - stripe + - example.com +``` + +Each entry is a catalog id/alias, a raw domain, or `{ host, port }` for a +non-default port. + +`anthropic` and `openai` are **rejected** here with a config validation error. +They're already covered by the LLM Provider agent in `scan`/`watch` whenever the +matching API key is set. Listing them under `services:` would make CrisisMode DNS-probe +the literal hostname instead of reading the provider's real status source — a +strictly worse check, so it's refused rather than silently accepted. + +## Evidence bundles + +CrisisMode speaks the SRE evidence-bundle v1 format, so external incident tooling +can hand it a bundle (logs, metrics, operator notes) and get a diagnosis back. + +| Command | Output | +|---|---| +| `bundle ingest` | Read-only AI diagnosis of the evidence | +| `bundle respond` | AdapterResponse v1: ranked hypotheses with evidence citations, actions gated by action-class policy, and explicit abstention when evidence is insufficient | +| `bundle execute` | A validated RecoveryPlan (dry-run) | + +All three take a file path or `-` for stdin: + +```bash +cat incident-bundle.json | crisismode bundle respond - +``` + +## MCP server + +`crisismode mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io) +server on stdio. + +```bash +claude mcp add crisismode -- crisismode mcp +``` + +Or in `.mcp.json`: + +```json +{ + "mcpServers": { + "crisismode": { "command": "crisismode", "args": ["mcp"] } + } +} +``` + +All 8 tools are read-only and annotated `readOnlyHint: true` — the MCP surface +never mutates infrastructure: + +| Tool | What it does | +|---|---| +| `crisismode_scan` | Zero-config health scan with a 0–100 score and per-service findings | +| `crisismode_diagnose` | Health assessment plus diagnosis for one target (AI-powered with `ANTHROPIC_API_KEY`, rule-based otherwise) | +| `crisismode_status` | Quick UP/DOWN probe of configured or detected services | +| `crisismode_list_agents` | The registered recovery agent roster with maturity labels | +| `crisismode_bundle_ingest` | Read-only diagnosis of an SRE evidence bundle (v1) | +| `crisismode_bundle_respond` | Ranked hypotheses with evidence citations and policy-gated actions | +| `crisismode_bundle_plan` | Translate a bundle into a dry-run RecoveryPlan (returned, never executed) | +| `crisismode_readiness` | Scale-readiness report including capacity ceilings and the conditional weak-link verdict — see [readiness.md](readiness.md) | + +## Check plugin ecosystem + +CrisisMode consumes external health checks through an adapter layer, so existing +checks work without a rewrite: + +- **Native check plugins** — JSON wire protocol for purpose-built checks +- **Nagios / Icinga / Checkmk plugins** — thousands of existing infrastructure checks +- **Goss** — declarative YAML system-state assertions +- **Sensu checks** — Graphite, InfluxDB, OpenTSDB, and Prometheus metric formats + +Discovered from `~/.crisismode/checks/`, `./checks/`, or `$CRISISMODE_CHECK_PATH` +(colon-separated). See +[Your First Check Plugin](guides/your-first-check-plugin.md) to write one, and the +[check plugin reference](guides/creating-a-check-plugin.md) for the wire protocol +and adapters. + +## See also + +- [QUICKSTART.md](../QUICKSTART.md) — guided first run +- [coverage.md](coverage.md) — what each agent has actually been validated against +- [readiness.md](readiness.md) — the readiness rules, ceilings, and honesty contract +- [architecture.md](architecture.md) — how the engine, safety layers, and plugins fit together diff --git a/docs/coverage.md b/docs/coverage.md new file mode 100644 index 0000000..9db42d7 --- /dev/null +++ b/docs/coverage.md @@ -0,0 +1,159 @@ +# Coverage and Validation Status + +What CrisisMode can look at, and how far each part has actually been proven. This +page exists because "we support Kafka" is a claim that means nothing without +saying how it was tested. + +The tool is the authoritative source, not this file: + +```bash +crisismode agent list # every registration with its maturity label +crisismode agent info # one agent's targets, risk profile, and maturity +``` + +## The two labels + +CrisisMode's manifests carry a five-value `PluginMaturity` +(`experimental`, `simulator_only`, `dry_run_only`, `live_validated`, +`production_certified`), but the honesty layer that operators see collapses it to +two (`src/framework/agent-maturity.ts`): + +| Label | Means | +|---|---| +| **live-validated** | The manifest declares `live_validated` — the agent has been exercised against a real deployment of that system. | +| **best-effort** | Everything else. The checks exist and run, but have never been proven against a live system. Findings are leads, not conclusions. | + +Two deliberate consequences: + +- **best-effort is the default.** Unknown kinds, unregistered kinds, and plugin + manifests that declare no maturity all land here. The pessimistic reading is + the honest one. +- **A kind is only live-validated when *every* agent registered for it says so.** + With several agents per kind, the framework can't know which one will run, so + it claims the weaker label. + +Best-effort findings say so in the output — human scan output appends a +"never been validated against real infrastructure — treat this as a lead, not a +conclusion" suffix rather than presenting them as equal to validated ones. + +## Live-validated today + +9 of 26 registrations. The LLM-provider agent registers once per provider, so it +accounts for two of the nine (Anthropic and OpenAI) from the single row below. + +| System | Agent | Notes | +|---|---|---| +| PostgreSQL | `postgresql-replication-recovery` | Replication lag, slot overflow, replica divergence, connection-pool exhaustion. The reference implementation. | +| Kubernetes | `kubernetes-recovery` | Node not-ready cascade, pod crashloop, stuck reconciliation | +| DNS | `dns-recovery` | Resolution failures, resolver health; can flush the local cache | +| TLS | `tls-certificate-recovery` | Certificate expiry and chain health (diagnosis only) | +| Disk | `disk-exhaustion-recovery` | Local disk exhaustion (diagnosis only) | +| Backup | `backup-verification` | Backup verification and DR readiness across filesystem, `aws_s3`, `aws_rds` providers (diagnosis only) | +| Third-party status | `service-status-diagnosis` | All 12 catalog entries passed live validation | +| LLM providers | `llm-provider-diagnosis` | **Anthropic and OpenAI only.** Key validity, quota/billing, rate-limit headroom, model deprecation, provider status | + +## Best-effort today + +17 of 26 registrations. The logic and simulators are complete and unit-tested; +none has been validated against a live deployment of its target system. The +Google Gemini and OpenRouter rows are two separate registrations of the +LLM-provider agent, which is why the table below has 16 rows. + +| System | Agent | Scenarios covered | +|---|---|---| +| Redis | `redis-memory-recovery` | Memory pressure, client exhaustion, slow queries, cluster health | +| etcd | `etcd-recovery` | Leader election loop, member thrashing, NOSPACE alarms, snapshot corruption | +| Kafka | `kafka-recovery` | Under-replicated partitions, leader imbalance, consumer lag cascade | +| Ceph | `ceph-storage-recovery` | OSD down cascade, degraded PGs, pool near-full | +| Flink | `flink-recovery` | Checkpoint failure cascade, TaskManager loss, backpressure | +| AWS S3 | `aws-s3-recovery` | Backup bucket versioning and lifecycle configuration | +| AWS DynamoDB | `aws-dynamodb-recovery` | Point-in-time-recovery verification | +| AWS RDS | `aws-rds-recovery` | Control-plane health, metrics, reachability, backup retention and snapshot recency | +| Terraform | `iac-drift-recovery` | Drift detection: intended state vs. observed AWS resources. Read-only; suggests reconciliation | +| Deploy rollback | `deploy-rollback-recovery` | Bad-deploy rollback (Vercel; needs `VERCEL_TOKEN`) | +| DB migration | `db-migration-recovery` | Migration safety checks, rollback orchestration | +| Queue backlog | `queue-backlog-recovery` | Backlog reduction, consumer lag recovery | +| Config drift | `config-drift-recovery` | Configuration and environment drift, compliance enforcement | +| Vector stores | `vector-store-diagnosis` | Managed vector store reachability (Pinecone, Upstash Vector) | +| AI provider | `ai-provider-failover-recovery` | Provider failover and fallback routing | +| LLM providers | `llm-provider-diagnosis` | Google Gemini and OpenRouter paths (implemented; not live-validated) | + +Some of these have been exercised in *dry-run* against real infrastructure, which +is worth more than simulator-only but is still not `live_validated`: + +- **AWS S3, DynamoDB, RDS** — diagnosis validated in dry-run against real AWS + accounts. No execute path has been verified. +- **DB migration, queue backlog, config drift** — diagnosis validated in dry-run + through the torture harness. +- **Deploy rollback** — exercised against the real Vercel API in dry-run. + +### Vector stores: read this before believing the table + +`vector-store-diagnosis` is `simulator_only` and neither provider has been +validated against a real account. Two narrower things *were* checked against the +live Pinecone API: that an invalid key is rejected, and that secrets are redacted +from output. Upstash Vector was exercised only in simulator and mocked tests. +Nothing about healthy-path behaviour against a real index is verified. + +## Execute-verified recovery + +Maturity labels describe *diagnosis*. Whether a mutating recovery plan actually +ran and actually fixed the fault is a stricter and much narrower claim. + +The [crisismode-torture](https://github.com/trs-80/crisismode-torture) harness +runs CrisisMode against real degraded infrastructure — PostgreSQL replication, +Redis, 3-node etcd, 3-broker Kafka, Redis Cluster partitions, cascading failures, +and real AWS RDS/S3/DynamoDB. + +**Validated:** failure detection (typically 3–5s), AI diagnosis, and dry-run +recovery planning against real infrastructure, including real AWS and Vercel. + +**Execute-verified (as of 2026-07-13)** — a mutating plan that ran, *plus* +post-recovery health verification confirming the underlying fault was resolved, +not merely that the engine exited without error. Exactly three scenarios: + +1. Redis memory pressure +2. PostgreSQL WAL-replay-paused replication lag +3. PostgreSQL connection-pool exhaustion + +All three are reproducible through the harness. + +**Not verified:** end-to-end `--execute` recovery for every other +agent/scenario on this page. Execute mode is functional for individual actions, +and the engine correctly refuses to run a plan when a required live provider is +missing — a blocked run is never counted as a recovery — but no other torture +scenario has completed a full mutating recovery with post-recovery verification. +Treat those execute paths as experimental. AWS and Vercel scenarios remain +dry-run/skipped under `--execute`. + +Note that Redis is execute-verified for memory pressure while its manifest still +declares `simulator_only`. That is not a typo: the two claims measure different +things, and the manifest deliberately makes the weaker one. + +## Readiness rules + +`crisismode readiness` is a separate, strictly read-only surface with its own +honesty contract (unknown is never scored, ceilings are never fabricated). Two +torture scenarios validate it against real PostgreSQL. Its uncovered paths — +the `blocking` verdict, the `pg_stat_statements`-present positive path, and the +`serverless-pooling` heuristic — are listed in +[docs/readiness.md](readiness.md#validation). + +## Other validation surfaces + +**Diagnosis eval** — 14 incident families from the external +[sre-incident-agent-skills](https://github.com/Dbochman/sre-incident-agent-skills) +benchmark, run against the real CLI with a 13/14 score gate +(`pnpm run eval:diagnosis:gate`). A recorded run is in +[docs/evals/2026-07-04-diagnosis-eval.md](evals/2026-07-04-diagnosis-eval.md). + +**Remediation guide verification** — the console-step guidance CrisisMode prints +for fixes it must not perform itself is re-walked against live consoles on a +schedule; a test fails when any guide's `verifiedOn` is more than 12 months old. +The most recent walk-through is in +[docs/guide-verification/2026-08-08-walkthrough.md](guide-verification/2026-08-08-walkthrough.md). +Guides on platforms nobody has an account for are marked `BLOCKED` and reported +as a coverage gap rather than quietly assumed correct. + +**Unit tests and typecheck** — `pnpm test` and `pnpm run typecheck`, both run in +CI on Node 22 and 24. diff --git a/docs/guide-verification/2026-08-09-docs-pass-report.md b/docs/guide-verification/2026-08-09-docs-pass-report.md new file mode 100644 index 0000000..9b96d5c --- /dev/null +++ b/docs/guide-verification/2026-08-09-docs-pass-report.md @@ -0,0 +1,211 @@ +# Docs Pass Report — 2026-08-09 + +Repo-presentation docs pass on branch `chore/repo-presentation`. Goal: make the +repo read well to an employer reviewing it cold, without inflating any claim. + +Pre-existing uncommitted edits (Node >= 22 accuracy fixes across the four root +docs) were preserved and extended, not reverted. + +## Structure decision + +The three-competing-onboarding-paths problem was resolved by **sharply +differentiating** rather than deleting, because the two guides were actually +serving different audiences that had bled into each other. Every concern now has +exactly one home: + +| Doc | Audience | Owns | +|---|---|---| +| `README.md` | Cold visitor | What it is, who it's for, install, 60-second demo, honest maturity summary, where to go next | +| `QUICKSTART.md` | Operator using the CLI | Install a binary, first scan, drill in, config, recovery, output modes | +| `GETTING_STARTED.md` | Developer building on it | Clone, build, podman test env, failure injection, project layout, pnpm scripts | +| `CONTRIBUTING.md` | Contributor | Contribution tracks, maturity honesty rule, code standards, testing, PR expectations | +| `docs/cli-reference.md` (new) | Reference lookup | Every command, flag, exit code, output-format contract, `down`, bundles, MCP | +| `docs/coverage.md` (new) | Anyone checking claims | Per-agent maturity, what each harness proves | + +Each root doc opens with a one-line pointer to its siblings so a cold reader +routes correctly instead of reading all three. + +`README.md` went 477 → 258 lines. Depth moved into the two new `docs/` files +rather than being deleted. + +## Honesty corrections + +Every one of these was a case where existing text claimed more than the codebase +supports. All were verified against source, not inferred. + +### 1. Agent status table overstated live capability (README) + +The old table labelled Redis, deploy rollback, DB migration, queue backlog, +config drift, and all three AWS agents as **"Live (execute-capable)"**. Their +manifests declare `maturity: 'simulator_only'`, and CrisisMode's own honesty +layer (`src/framework/agent-maturity.ts`) reports everything except +`live_validated` as best-effort. An employer running `crisismode agent list` +would have seen the README and the tool disagree. + +Replaced with the tool's own two-label vocabulary and the real count: **9 of 26 +registrations are live-validated** (verified by counting `agent list` output, not +by eye — the first count of 8 was wrong because the LLM-provider agent registers +once per provider). + +### 2. `checks/` does not ship with the binary (QUICKSTART) + +Old text: scan "Runs bundled check plugins (disk, memory, DNS, HTTP, TLS +certificates)". `package.json` `files` is `dist/**`, `README`, `LICENSE`, +`NOTICE` — `checks/` is not published. A binary or npm user gets **no** check +plugins by default; a real scan from outside the repo prints "No check plugins +found." Softened to state the discovery paths and that the examples live in a +repo checkout. + +### 3. `PostgresSaver for production` (docs/architecture.md) + +`graph-engine.ts` imports only `MemorySaver` and defaults to it; `PostgresSaver` +appears solely in a source comment. Reworded to "the checkpointer is injectable +and defaults to `MemorySaver`; a durable store is the intended hub deployment but +is not wired up in this repo." + +### 4. Vector store maturity (docs/coverage.md) + +Given a dedicated call-out rather than a table row, since the table alone would +mislead: `simulator_only`, no real Pinecone or Upstash account ever validated. +The only live checks were invalid-key rejection and secret redaction against the +real Pinecone API; Upstash is simulator/mocked only. + +### 5. Smoke-test check counts + +`GETTING_STARTED.md` asserted "16 checks" and "6 checks". The scripts compute +`TOTAL` dynamically and I could not run them (no podman session), so I replaced +the hard numbers with "each script prints a `passed/total` tally" rather than +repeat unverified figures. + +### 6. Redis execute-verified vs. `simulator_only` + +Left as an explicit, labelled tension in `docs/coverage.md` instead of silently +picking a side. Redis memory pressure is execute-verified in the torture harness +(2026-07-13) while its manifest says `simulator_only`. The two claims measure +different things; the doc says so and notes the manifest deliberately makes the +weaker claim. + +## Drift fixed + +- **Invalid maturity values in both agent guides (3 places).** Guides told readers + to set `plugin.maturity` to `"beta"` or `"stable"`. Neither is a valid + `PluginMaturity` — the real set is `experimental`, `simulator_only`, + `dry_run_only`, `live_validated`, `production_certified`. Fixed, and tied to a + new CONTRIBUTING section explaining why `simulator_only` is the honest default. +- **`playbook-authoring.md`'s flagship example did not validate.** Verified by + extracting the fenced block and running it: step 4 lacked `capability`, failing + with `Missing capabilities on steps: redis-memory-pressure-step-4`. Added + `capability: cache.expiry.trigger`; the example now passes 13 safety checks + verbatim from the doc. Also documented where valid capability ids come from, + since that was the missing information behind the bug. +- **`GETTING_STARTED.md` said "19 agents"** — actual count is 26. Rather than + re-listing the roster (the redundancy that caused this drift), it now points at + `crisismode agent list` and `docs/coverage.md`. +- **Missing recent subsystems.** `docs/architecture.md` had no coverage of + escalation levels, the honesty layer, triage, service-status, or the + remediation-guidance registry. Added sections for each. +- **New test scripts undocumented:** `inject-pgvector-unindexed.sh`, + `reset-pgvector.sh`, `test-alert-pipeline.sh`. + +## Verification performed + +Everything documented was checked at the real surface after `pnpm run build`. + +- **Flags:** the complete flag set was taken from `node dist/cli/index.js --help`. + No invented flags. (Subcommand `--help` prints the global help, so the top-level + list is authoritative.) + + *Correction (same day):* this bullet originally read that an audit of every + `--flag` across all docs found only `--json`, `--target`, and `--agent`. Those + three are the flags that appear in the *onboarding* docs; `docs/cli-reference.md` + documents the full surface. Re-verified against a fresh `pnpm run build`, its + flag table matches `--help` exactly, all 15 entries: `--plugin`, `--agent`, + `--config`, `--target`, `--category`, `--terse`, `--execute`, `--health-only`, + `--local`, `--force`, `--json`, `--no-color`, `--verbose`, `-h`/`--help`, + `-v`/`--version`. `--agent` has since become a deprecated alias for `--plugin` + (see the superseded note under "Scan suggested a flag that does not exist" + below). +- **Commands run:** `scan`, `scan --category dns`, `scan --json`/pipe, `triage`, + `down stripe github`, `down --badflag` (exit 2 confirmed), `agent list`, + `completions zsh`, `init --agent`, `init --plugin`, `playbook validate`, + `playbook dry-run`, `diagnose PG-001`, `diagnose default-postgres`. +- **Output-format contracts confirmed against real output:** the 4-field `scan` + row and 7-field `finding` row (including a populated `guide_refs` cell), and + the `triage`/`layer` rows. +- **Counts confirmed:** 26 agent registrations, 9 live-validated, 8 MCP tools, 8 + readiness rules, 9 hook points, 12 service-catalog ids, 9 default probe ports. +- **Links:** 0 broken file links and 0 broken anchors across all root docs and + `docs/**` (excluding `docs/superpowers/`, untouched — 26 files intact). Also + confirmed nothing in the repo still links to README anchors I removed + (`#cli-reference`, `#install`, `#validation-status`). + +## Two `src/` bugs — found during the audit, fixed in a follow-up + +Both were cases where the CLI printed a copy-pasteable command that does not +work. Initially reported as out-of-scope (the docs dispatch excluded `src/`), then +fixed on explicit follow-up instruction. + +### 1. Scan suggested a `diagnose` command that fails + +`src/cli/incident-summary.ts` emitted +``Investigate: `crisismode diagnose ${first.id}` `` using a finding id such as +`PG-001`. `diagnose` resolves its positional as a **target name**, so the +suggested command died with `Target "PG-001" not found in config`. + +The nuance that made this more than a one-liner: `runDiagnose` *does* special-case +`/^PLUG-(\d+)$/i` and route it to a check plugin's diagnose verb by index. So the +old hint was correct for plugin findings and broken for every agent finding — a +blanket replacement would have broken the working half. + +Fix: extracted `diagnoseCommandFor(finding)`, which keeps the id for `PLUG-*`, +otherwise uses the target name (the trailing parenthesized group of scan's +`${kind} (${target.name})` service string), and falls back to bare +`crisismode diagnose` when no name is recoverable — never a dead end. + +Two existing tests asserted the broken output (`toContain('crisismode diagnose +PG-001')`) using fixtures whose `service` had no parenthesized target, which is +not a shape real scan produces. Fixtures made realistic, assertions corrected, and +6 dedicated tests added for the helper. + +### 2. Scan suggested a flag that does not exist + +`src/cli/commands/scan.ts` printed +`scaffold one with: crisismode init --plugin my-check`. `--plugin` is not +recognized: it falls through to the positional path argument and writes a *config +file* named `my-check` instead of scaffolding a plugin. Corrected to `--agent`, +which I verified produces `manifest.json` + an executable `check.sh`. + +*Superseded (same day):* `--agent` was the wrong name for a flag that scaffolds a +check plugin — "agent" means `RecoveryAgent` everywhere else in this codebase. +`--plugin` is now the canonical flag and `--agent` a deprecated alias, so both the +scan hint and this fix now read `crisismode init --plugin my-check`. + +### Verification + +`pnpm run typecheck`, `pnpm run lint`, and `pnpm test` (3179 passed / 24 skipped, +0 failures) all clean, then rebuilt and confirmed at the real surface: + +- Failing check-plugin fixture → `Investigate: crisismode diagnose PLUG-001`, and + that command runs the plugin's diagnose verb. +- PostgreSQL target on a wrong port → `Investigate: crisismode diagnose prod-db`, + and `diagnose prod-db` resolves (`Target: prod-db (postgresql)`) while the old + `diagnose PG-001` still errors. +- Scan with no health plugins discovered → hint now reads + `crisismode init --plugin my-check`. The message says "No health check plugins + found" rather than "No check plugins found", because the count it reports is + filtered to plugins declaring the `health` verb — a diagnose-only or plan-only + plugin is discovered but not counted here. + +## Noted, not changed + +`specs/foundational/recovery-agent-contract.md` was not modified. An anchor there +initially looked broken but is valid — GitHub's em-dash slugging produces the +double hyphen the link uses. + +## Not drifted + +Audited and found accurate, so left alone: `docs/readiness.md` (already the +strongest honesty document in the repo — 8 rules, thresholds, and its uncovered +validation paths all verified correct), the check-plugin wire protocol in +`docs/guides/your-first-check-plugin.md` (matches the live scaffold's manifest +and verb handling), and `docs/agents/backup-verification.md` (all commands valid). diff --git a/docs/guides/creating-a-check-plugin.md b/docs/guides/creating-a-check-plugin.md index 4a991fb..ef34352 100644 --- a/docs/guides/creating-a-check-plugin.md +++ b/docs/guides/creating-a-check-plugin.md @@ -20,7 +20,7 @@ Three verbs are supported: Each check plugin lives in its own directory under `checks/`: -``` +```text checks/ check-mysql-connection/ manifest.json # Plugin metadata and capabilities @@ -410,7 +410,7 @@ Thousands of Nagios/Icinga/Checkmk plugins exist. Wrap any of them: Nagios plugins receive **no stdin**. They output a status line optionally followed by `|` and performance data: -``` +```text OK - Load 0.85 (16 CPUs), up 5d 3h | load=0.85;32;64;0; uptime=445736s;;;; ``` diff --git a/docs/guides/creating-a-recovery-agent.md b/docs/guides/creating-a-recovery-agent.md index 939efec..08283fe 100644 --- a/docs/guides/creating-a-recovery-agent.md +++ b/docs/guides/creating-a-recovery-agent.md @@ -44,7 +44,7 @@ The agent never executes recovery actions directly. It builds a `RecoveryPlan` c Every agent follows this structure: -``` +```text src/agent// backend.ts # Interface for querying the target system simulator.ts # In-memory implementation for demos and tests @@ -197,7 +197,7 @@ export const mySystemManifest: AgentManifest = { **`metadata`** -- identity and authorship: - `name` -- unique agent name, used as the registry key. - `version` -- semver version of the agent. -- `plugin.maturity` -- set to `"simulator_only"` until a live client exists; change to `"beta"` or `"stable"` later. +- `plugin.maturity` -- one of `"experimental"`, `"simulator_only"`, `"dry_run_only"`, `"live_validated"`, `"production_certified"`. Start at `"simulator_only"`; CrisisMode reports every value except `"live_validated"` as best-effort, and only real validation against a live system earns the upgrade. See [Declaring maturity honestly](../../CONTRIBUTING.md#declaring-maturity-honestly). **`spec.targetSystems`** -- what systems this agent manages: ```typescript diff --git a/docs/guides/your-first-agent.md b/docs/guides/your-first-agent.md index b5babd8..28a4f9f 100644 --- a/docs/guides/your-first-agent.md +++ b/docs/guides/your-first-agent.md @@ -30,7 +30,7 @@ If you just need to check a port or validate a certificate, a [check plugin](you Every agent follows this structure: -``` +```text src/agent// backend.ts # Interface for querying the target system simulator.ts # In-memory implementation for demos and tests @@ -275,7 +275,7 @@ export const websiteManifest: AgentManifest = { ### Important manifest fields -- **`plugin.maturity`** -- set to `"simulator_only"` until you build a live client. Change to `"beta"` or `"stable"` later. +- **`plugin.maturity`** -- one of `"experimental"`, `"simulator_only"`, `"dry_run_only"`, `"live_validated"`, `"production_certified"`. Start at `"simulator_only"` and only move up when you have actually validated against a real system -- CrisisMode reports anything other than `"live_validated"` as best-effort, which is deliberate. See [Declaring maturity honestly](../../CONTRIBUTING.md#declaring-maturity-honestly). - **`riskProfile.maxRiskLevel`** -- the maximum risk any step in your plans can declare. Do not set to `"critical"` without discussion with maintainers. - **`failureScenarios`** -- string identifiers that your `diagnose()` method returns. The framework uses these to match agents to incidents. @@ -769,7 +769,25 @@ Two rules the helper enforces (and that you must not work around): not connect" finding. Never catch a live-client error and silently fall back to simulated data — that reports fake health for a real system. -Update the manifest's `plugin.maturity` from `"simulator_only"` to `"beta"` once the live client is working. +A working live client on its own does **not** change your maturity label. The label +tracks how far your **diagnosis** has been proven against real infrastructure: + +- `"simulator_only"` — where every new agent starts, and where it stays until you + have confirmed diagnosis against a real deployment. CrisisMode labels its + findings as leads, not conclusions. +- `"dry_run_only"` — diagnosis validated only in dry-run against real + infrastructure (real reads, mutations logged instead of executed), with the + agent's mutating recovery path never exercised live. +- `"live_validated"` — you have run the agent against a real deployment of its + target system and confirmed it diagnoses correctly there. This does **not** + require a mutating recovery: several in-tree agents are `live_validated` on + diagnosis alone (`src/agent/tls/`, `src/agent/disk/`, `src/agent/backup/`). + +Whether a mutating plan actually ran and the fault was verified resolved is a +separate, stricter claim that the maturity label does not encode — it is tracked +under [Execute-verified recovery](../coverage.md#execute-verified-recovery), which +today covers three scenarios. Say in the PR description what you validated +against, and update [docs/coverage.md](../coverage.md) in the same PR. ## Distributing as a Plugin diff --git a/docs/playbook-authoring.md b/docs/playbook-authoring.md index 4f98819..0975430 100644 --- a/docs/playbook-authoring.md +++ b/docs/playbook-authoring.md @@ -120,6 +120,13 @@ Capture system state before mutations. The framework snapshots the configured ta ### `system_action` Execute a command that mutates system state. Must declare `risk` and `capability`; at `elevated` risk or higher must also declare `preserve` (state captured before the step runs, blocking on failure). Should have `precondition`, `success`, and `blast_radius`. Include a code block with the command. `crisismode playbook validate` runs the compiled plan through the same safety validator as code-based agents and reports exactly which rule a step violates. +`capability` values must be ids that are already registered in +`src/framework/capability-registry.ts` — for example `db.query.read`, +`db.replica.disconnect`, `cache.expiry.trigger`, `k8s.node.drain`, +`queue.workers.restart`. An unregistered id fails validation with +`Missing capabilities on steps: `, which is the most common reason a +playbook parses but will not validate. + ### `human_approval` Pause execution until a human approves. Set `timeout` and `escalation`. The body text is shown to the approver as context. @@ -208,7 +215,7 @@ The rollback section is captured as free-text and included in the compiled plan' ## Complete Example -```markdown +````markdown --- name: "redis-memory-pressure" version: "1.0.0" @@ -233,7 +240,7 @@ estimated_duration: "15m" - target: redis-primary ```sh -redis-cli info memory +redis-cli -e info memory ``` ### 2. Notify on-call @@ -244,15 +251,54 @@ redis-cli info memory ### 3. Capture state - type: checkpoint -### 4. Evict expired keys +### 4. Reclaim keys already past their TTL - type: system_action - risk: routine +- capability: cache.expiry.trigger - target: redis-primary +- preserve: keyspace_before - precondition: "Redis is accepting commands" - success: "Memory usage below 85%" ```sh -redis-cli --scan --pattern '*' | head -1000 | xargs redis-cli unlink +# Bounded sample of the keyspace. Reading a key that is already past its TTL is +# what makes Redis reclaim it; keys still inside their TTL, and keys with no TTL +# at all, are read and left alone. This step deletes nothing itself. +# Every redis-cli call runs with -e and is checked, so a failed SCAN or TTL fails +# the step rather than reporting a cleanup that never happened. Without -e, +# redis-cli exits 0 even when Redis answers with an error reply such as NOAUTH. +set -u +limit=1000 +cursor=0 +scanned=0 +rounds=0 + +while [ "$scanned" -lt "$limit" ] && [ "$rounds" -lt 50 ]; do + rounds=$((rounds + 1)) + if ! reply=$(redis-cli -e SCAN "$cursor" COUNT 100); then + echo "SCAN failed at cursor $cursor" >&2 + exit 1 + fi + + # First line of a SCAN reply is the next cursor; the rest are keys. + first=1 + while IFS= read -r key; do + if [ "$first" -eq 1 ]; then cursor=$key; first=0; continue; fi + [ -n "$key" ] || continue + if ! redis-cli -e TTL "$key" > /dev/null; then + echo "TTL failed for key: $key" >&2 + exit 1 + fi + scanned=$((scanned + 1)) + [ "$scanned" -lt "$limit" ] || break + done <&2 + exit 1 + fi + + # First line of a SCAN reply is the next cursor; the rest are keys. + first=1 + while IFS= read -r key; do + if [ "$first" -eq 1 ]; then cursor=$key; first=0; continue; fi + [ -n "$key" ] || continue + if ! redis-cli -e TTL "$key" > /dev/null; then + echo "TTL failed for key: $key" >&2 + exit 1 + fi + scanned=$((scanned + 1)) + [ "$scanned" -lt "$limit" ] || break + done < + */ + +import { readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'; +import { basename, dirname, join } from 'node:path'; + +// ── Pacing model (seconds) ─────────────────────────────────────────────── +const DELAY = { + /** Ordinary content line. */ + line: 0.1, + /** Blank line — a beat, not a full line. */ + blank: 0.05, + /** Box-drawing rule; part of a heading, should land with it. */ + rule: 0.04, + /** Before a `Phase N:` heading — the main structural beat. */ + phase: 0.85, + /** Before an execution step heading. */ + step: 0.45, + /** Before a finding/result marker line. */ + marker: 0.16, +}; + +/** + * Ceiling on any single gap between events. Sized above the longest deliberate + * pause in the expect driver (2.5s to read the approval panel, 2.5s to hold the + * final frame) so those survive untouched, and below the live AI call's real + * 28-40s so that one becomes a readable beat instead of a stall. + */ +const MAX_IDLE_GAP = 4.0; + +const ANSI = /\x1b\[[0-9;]*[A-Za-z]/g; + +function stripAnsi(text) { + return text.replace(ANSI, ''); +} + +/** + * An output event is left at its original timing if it looks like interactive + * echo (the operator typing at the approval gate) rather than program output: + * no newline and only a few characters. + */ +function isTypingEcho(data) { + return !data.includes('\n') && stripAnsi(data).length <= 3; +} + +function delayForLine(line) { + // Strip ANSI so the classifiers match on visible text. + const plain = stripAnsi(line); + const trimmed = plain.trim(); + + if (trimmed === '') return DELAY.blank; + if (/^[─═│┌┐└┘├┤┬┴┼]+$/.test(trimmed)) return DELAY.rule; + if (/^Phase \d+:/.test(trimmed)) return DELAY.phase; + if (/^Step step-/.test(trimmed)) return DELAY.step; + if (/^[✓✗●◆◇◈↻⚠→•]/.test(trimmed) || /^\[(CRITICAL|WARNING|INFO)\]/.test(trimmed)) { + return DELAY.marker; + } + return DELAY.line; +} + +const [, , inPath, outPath] = process.argv; +if (!inPath || !outPath) { + console.error('usage: node scripts/pace-cast.mjs '); + process.exit(1); +} + +const raw = readFileSync(inPath, 'utf8').trim().split('\n'); +const header = JSON.parse(raw[0]); +if (header.version !== 3) { + console.error(`pace-cast: expected asciicast v3, got version ${header.version}`); + process.exit(1); +} + +// The capture runs through a temp expect driver, so the recorded `command` is a +// throwaway /var/folders path. Replace it with the command it actually drove. +header.command = 'crisismode demo'; + +const out = [JSON.stringify(header)]; +let originalWall = 0; +let pacedWall = 0; +let splitCount = 0; +let cappedCount = 0; +let longestOriginalGap = 0; + +for (const rawLine of raw.slice(1)) { + if (!rawLine.trim()) continue; + const event = JSON.parse(rawLine); + const [interval, code, data] = event; + originalWall += interval; + if (interval > longestOriginalGap) longestOriginalGap = interval; + if (interval > MAX_IDLE_GAP) cappedCount += 1; + + if (code !== 'o' || isTypingEcho(data) || !data.includes('\n')) { + const gap = Math.min(interval, MAX_IDLE_GAP); + out.push(JSON.stringify([Number(gap.toFixed(3)), code, data])); + pacedWall += gap; + continue; + } + + // Split on newline, keeping the terminator attached to its line so the + // reconstructed stream is byte-identical to the original. + const chunks = data.split(/(?<=\n)/); + splitCount += 1; + + let first = true; + for (const chunk of chunks) { + if (chunk === '') continue; + // The original gap is a floor: never speed the recording up — except that + // no single gap may exceed MAX_IDLE_GAP. + const paced = delayForLine(chunk); + const gap = first ? Math.min(Math.max(interval, paced), MAX_IDLE_GAP) : paced; + out.push(JSON.stringify([Number(gap.toFixed(3)), 'o', chunk])); + pacedWall += gap; + first = false; + } +} + +// Write to a sibling temp file and rename into place only once the byte check +// below has passed. record-demo.sh passes a committed site asset as outPath, so +// writing there before verifying would overwrite the good cast with the rejected +// one — while printing "refusing to ship". The temp file is a sibling so the +// rename stays on one filesystem and is therefore atomic. +const tmpPath = join(dirname(outPath), `.${basename(outPath)}.pace-cast.${process.pid}.tmp`); + +let tmpPending = false; +const removeTmp = () => { + if (!tmpPending) return; + tmpPending = false; + try { + unlinkSync(tmpPath); + } catch { + // Already gone: renamed into place, or removed by an earlier handler. + } +}; + +// Covers every exit path Node can observe — normal return, process.exit(), and +// uncaught throws. Signals do not run 'exit' handlers by themselves, so they are +// wired to it explicitly. (SIGKILL is uncatchable and cannot be covered.) +process.on('exit', removeTmp); +for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) { + process.on(signal, () => { + removeTmp(); + process.exit(1); + }); +} + +// Set before the write, not after: a write that throws part-way through still +// leaves a partial file to clean up. +tmpPending = true; +writeFileSync(tmpPath, out.join('\n') + '\n'); + +// Verify content is unchanged — pacing must be timing-only. +const bytesOf = (path) => + readFileSync(path, 'utf8') + .trim() + .split('\n') + .slice(1) + .filter((l) => l.trim()) + .map((l) => JSON.parse(l)) + .filter((e) => e[1] === 'o') + .map((e) => e[2]) + .join(''); + +if (bytesOf(inPath) !== bytesOf(tmpPath)) { + console.error('pace-cast: FAILED — output bytes differ from input; refusing to ship'); + console.error(`pace-cast: ${outPath} left untouched`); + // The 'exit' handler discards the rejected cast. + process.exit(1); +} + +renameSync(tmpPath, outPath); +tmpPending = false; + +console.log( + `pace-cast: ${originalWall.toFixed(1)}s -> ${pacedWall.toFixed(1)}s ` + + `(${splitCount} bursts split, output bytes verified identical)`, +); +console.log( + `pace-cast: longest original gap ${longestOriginalGap.toFixed(1)}s; ` + + `${cappedCount} gap(s) capped at ${MAX_IDLE_GAP}s`, +); diff --git a/scripts/package-site.sh b/scripts/package-site.sh new file mode 100755 index 0000000..078f0ef --- /dev/null +++ b/scripts/package-site.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 CrisisMode Contributors +# +# Package site/ as a zip for Cloudflare Pages direct upload. +# +# Pages resolves the site from the ZIP ROOT, so index.html must be a top-level +# entry — a zip containing `site/index.html` deploys to /site/index.html and the +# landing page 404s. This script therefore zips from inside site/ with relative +# paths, and asserts the root layout afterwards rather than trusting it. +# +# WHAT GOES IN: index.html, blog/, assets/. Nothing else — the zip is a publish +# artifact, not a copy of the working tree. +# +# WHAT IS KEPT OUT, and why each one matters: +# - .DS_Store Finder litter; site/.DS_Store exists today. +# - *.zip a previously built bundle must never nest inside the +# next one (site/index.html.zip is a stale committed +# artifact that would otherwise ride along). +# - ._* / __MACOSX/ AppleDouble resource forks, which `zip -X` also +# suppresses by not recording Apple extra fields. +# +# REPRODUCIBILITY: the entry set and entry order are deterministic — the file +# list is built by find and LC_ALL=C sort, and passed to zip on stdin, so +# directory iteration order cannot leak in. The output is removed before each +# build so a rerun never appends to a stale archive. The archive is NOT +# byte-identical across runs: Info-ZIP records each entry's filesystem mtime and +# offers no way to override it, so re-recording an asset changes those bytes. +# Same inputs, same tree -> same bytes; touched inputs -> new bytes. +# +# Usage: bash scripts/package-site.sh + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SITE_DIR="$REPO_ROOT/site" +OUT_DIR="$REPO_ROOT/output" +ZIP="$OUT_DIR/crisismode-site.zip" + +for tool in zip unzip; do + command -v "$tool" >/dev/null 2>&1 || { + echo "error: '$tool' not found." >&2 + exit 1 + } +done + +[ -f "$SITE_DIR/index.html" ] || { + echo "error: $SITE_DIR/index.html is missing; nothing to publish." >&2 + exit 1 +} + +cd "$SITE_DIR" + +# Every local asset the page references must be present. This is the exact +# failure the stale site/index.html.zip demonstrates: it carries index.html and +# blog/ but no assets/, so the deployed page loads with a dead player and a dead +# GIF. Catch that here instead of in production. +# +# Match any quoted assets//blog/ path, not just src=/href= attributes: the cast +# is loaded from script (AsciinemaPlayer.create('assets/demo.cast', ...)), and an +# attribute-only scan would happily ship a bundle with no recording in it. +missing=0 +while IFS= read -r ref; do + [ -z "$ref" ] && continue + [ -e "$ref" ] || { + echo "error: index.html references '$ref', which does not exist in site/" >&2 + missing=1 + } +done < packaging site/ -> $ZIP" +find index.html blog assets \ + -type f \ + ! -name '.DS_Store' \ + ! -name '*.zip' \ + ! -name '._*' \ + -print \ + | LC_ALL=C sort \ + | zip -q -X -@ "$ZIP" -x '.DS_Store' '*/.DS_Store' '*.zip' '._*' '*/._*' '__MACOSX/*' + +# Assert the layout Pages needs rather than asking the reader to eyeball it. +unzip -Z1 "$ZIP" | grep -qx 'index.html' || { + echo "error: index.html is not at the zip root; Pages would 404 on /." >&2 + exit 1 +} +if unzip -Z1 "$ZIP" | grep -qE '(^|/)(\.DS_Store|\._|__MACOSX/)|\.zip$'; then + echo "error: excluded files leaked into the archive:" >&2 + unzip -Z1 "$ZIP" | grep -E '(^|/)(\.DS_Store|\._|__MACOSX/)|\.zip$' >&2 + exit 1 +fi + +ZIP_BYTES="$(wc -c < "$ZIP" | tr -d ' ')" + +echo +unzip -l "$ZIP" +echo +echo "done: $ZIP" +echo " $((ZIP_BYTES / 1024)) KiB ($ZIP_BYTES bytes), $(unzip -Z1 "$ZIP" | wc -l | tr -d ' ') entries" +echo " upload this file to Cloudflare Pages (Create project -> Direct Upload)." diff --git a/scripts/record-demo.sh b/scripts/record-demo.sh new file mode 100644 index 0000000..5117fb8 --- /dev/null +++ b/scripts/record-demo.sh @@ -0,0 +1,241 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 CrisisMode Contributors +# +# Record `crisismode demo` as an asciinema cast (+ GIF fallback) for the website. +# +# The demo blocks on stdin at the human_approval gate, so the run is driven by +# expect: it waits for the prompt and types the decision with human-like timing. +# +# THIS RECORDING MAKES A LIVE ANTHROPIC API CALL. Phase 5 runs the real AI +# diagnosis, so ANTHROPIC_API_KEY must be exported in the environment or the +# demo silently falls back to rule-based heuristics and the capture is wrong. +# Consequences of that being live: +# - The cast is NOT byte-reproducible. The model's root cause, findings and +# confidence differ on every run; so does the wall-clock latency (measured +# 28-40s for claude-sonnet-5 against this prompt). +# - The result is pace-adjusted afterwards by scripts/pace-cast.mjs, which +# re-times every line for readability and clamps any single idle gap to 4s. +# Output bytes are never altered; only timing is synthesized. +# - Do not set CRISISMODE_AI_MODEL here. The demo must show the +# out-of-the-box path, which is whatever ai-model.ts defaults to. +# After recording, confirm the cast really shows model reasoning rather than the +# fallback — the rule-based path prints a fixed 92%-confidence three-finding +# diagnosis, so a cast that matches it byte-for-byte across runs is a red flag. +# +# Usage: bash scripts/record-demo.sh [approve|skip|reject] + +set -euo pipefail + +DECISION="${1:-approve}" + +# Validate before anything expensive happens: the recording makes a live billed +# API call, so a typo must not be discovered only when the driver mistypes it at +# the approval gate and the whole capture has to be thrown away. +case "$DECISION" in + approve | skip | reject) ;; + *) + echo "error: invalid decision '$DECISION'. Expected: approve, skip, or reject." >&2 + echo "Usage: bash scripts/record-demo.sh [approve|skip|reject]" >&2 + exit 2 + ;; +esac + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT_DIR="$REPO_ROOT/site/assets" +CAST="$OUT_DIR/demo.cast" +# The unpaced capture is a build artifact, not a site asset — keep it out of +# site/assets so it is not published. /output/ is gitignored. +RAW_CAST="$REPO_ROOT/output/demo.raw.cast" +GIF="$OUT_DIR/demo.gif" + +# 100 cols keeps the demo's 72-char rules and plan table intact while limiting +# how often the long prose lines (risk/what descriptions) wrap. +COLS=100 +ROWS=30 + +for tool in asciinema agg expect; do + command -v "$tool" >/dev/null 2>&1 || { + echo "error: '$tool' not found. Install with: brew install asciinema agg" >&2 + exit 1 + } +done + +# agg's --select is load-bearing for the content-anchored GIF cut below and only +# exists from agg 1.9.0 (released 2026-05-29). On an older agg the run dies at the +# very last step with a bare clap "unexpected argument" error — after the live, +# billed recording has already been spent. Check the version up front instead. +AGG_VERSION="$(agg --version 2>/dev/null | awk 'NR == 1 {print $2}')" +AGG_MAJOR="${AGG_VERSION%%.*}" +AGG_MINOR="${AGG_VERSION#*.}" +AGG_MINOR="${AGG_MINOR%%.*}" +if [[ "$AGG_MAJOR" =~ ^[0-9]+$ && "$AGG_MINOR" =~ ^[0-9]+$ ]]; then + if [ "$AGG_MAJOR" -lt 1 ] || { [ "$AGG_MAJOR" -eq 1 ] && [ "$AGG_MINOR" -lt 9 ]; }; then + echo "error: agg $AGG_VERSION is too old; --select needs agg >= 1.9.0." >&2 + echo " Upgrade with: brew upgrade agg" >&2 + exit 1 + fi +else + # Only our own parsing failed (nightly build, patched --version output, ...). + # Refusing here would block a perfectly new agg, and the fallback is just + # today's behaviour: agg rejects --select itself if it really is too old. + echo "warning: could not parse agg version from '${AGG_VERSION:-}'; assuming >= 1.9.0" >&2 +fi + +[ -f "$REPO_ROOT/dist/cli/index.js" ] || { + echo "error: dist/cli/index.js missing. Run 'pnpm run build' first." >&2 + exit 1 +} + +# Fail fast rather than ship a cast that quietly shows the rule-based fallback: +# a missing key is indistinguishable from a working recording until someone +# reads the diagnosis text closely. +[ -n "${ANTHROPIC_API_KEY:-}" ] || { + echo "error: ANTHROPIC_API_KEY is not set. This recording requires a live AI" >&2 + echo " diagnosis; without the key the demo falls back to rule-based" >&2 + echo " heuristics and the capture would misrepresent the product." >&2 + exit 1 +} + +mkdir -p "$OUT_DIR" "$(dirname "$RAW_CAST")" + +# A private directory, not a bare mktemp file: `mktemp -t X` creates a file at the +# path it returns, so appending `.exp` to that path produced a second, unmanaged +# file and leaked the mktemp one on every run. The 0700 directory also means the +# driver is written into a location nothing else can pre-create or observe. +TMP_BASE="${TMPDIR:-/tmp}" +DRIVER_DIR="$(mktemp -d "${TMP_BASE%/}/crisismode-demo-driver.XXXXXX")" +trap 'rm -rf "$DRIVER_DIR"' EXIT +DRIVER="$DRIVER_DIR/driver.exp" + +# The here-doc is QUOTED: nothing is interpolated into Tcl source. Tcl substitutes +# [...] and $... while parsing, so a repo path containing brackets would be +# mis-parsed as a command, and an interpolated decision would be Tcl code rather +# than a keystroke. Both values reach the driver through the environment instead, +# where Tcl reads them as data and never re-parses them. +cat > "$DRIVER" <<'EXPECT_SCRIPT' +#!/usr/bin/env expect -f +# Drives the demo through its interactive approval gate. +# +# 180s still has room after the move to live AI: the diagnosis call is bounded +# at 60s by the framework's own deadline (src/framework/ai-diagnosis.ts) and the +# rest of the run to the approval prompt takes ~15s, so the worst case is ~75s. +set timeout 180 +# Human-like typing: avg 90ms/char, 20ms stddev, 0.2 min, 0.4s at word end. +set send_human {0.09 0.02 1 0.20 0.40} + +# A beat before the first keystroke so the recording does not open mid-command. +sleep 1.2 +spawn -noecho node $env(CRISISMODE_DEMO_CLI) demo +expect { + -re {Enter your decision \(approve/skip/reject\): } { + # Let the viewer read the approval panel before answering. + sleep 2.5 + send -h -- $env(CRISISMODE_DEMO_DECISION) + sleep 0.6 + send -- "\r" + } + timeout { puts stderr "\nrecord-demo: timed out waiting for approval prompt"; exit 1 } + eof { puts stderr "\nrecord-demo: demo exited before approval prompt"; exit 1 } +} +expect eof +# Hold on the final frame so the summary is readable at the end of the loop. +sleep 2.5 +EXPECT_SCRIPT + +chmod +x "$DRIVER" + +echo "==> recording ${COLS}x${ROWS} -> $RAW_CAST" +rm -f "$RAW_CAST" + +# ANTHROPIC_API_KEY is inherited deliberately: Phase 5 must reach the real API. +# CRISISMODE_DEMO_* carry the two values the expect driver needs, as data rather +# than as interpolated Tcl source. +env TERM=xterm-256color \ + FORCE_COLOR=3 \ + COLUMNS="$COLS" \ + LINES="$ROWS" \ + CRISISMODE_DEMO_CLI="$REPO_ROOT/dist/cli/index.js" \ + CRISISMODE_DEMO_DECISION="$DECISION" \ + asciinema rec "$RAW_CAST" \ + --window-size "${COLS}x${ROWS}" \ + --overwrite \ + --return \ + --title "crisismode demo — PostgreSQL replication lag cascade recovery" \ + --command "$DRIVER" + +# The demo emits each phase in one instantaneous write, so re-time the cast to +# reading speed. Timing only — pace-cast verifies output bytes are unchanged. +echo "==> pacing -> $CAST" +node "$REPO_ROOT/scripts/pace-cast.mjs" "$RAW_CAST" "$CAST" + +# Theme matched to the site palette in site/index.html (--bg-primary, --amber, +# --red, --green, --cyan) so the GIF sits in the page without clashing. +THEME="0a0a0b,e4e4e7,27272a,ef4444,22c55e,f59e0b,3b82f6,ec4899,06b6d4,e4e4e7,52525b,f87171,4ade80,fbbf24,60a5fa,f472b6,22d3ee,fafafa" + +# The GIF is a fallback/social teaser, not the whole story: a full-length 100x30 +# GIF is >7MB at any usable quality. Cover the opening arc (alert -> live AI +# diagnosis) and let the player carry the rest. +# +# The cut point is found in the cast rather than fixed at a percentage: the live +# AI diagnosis varies in length run to run, so a percentage lands somewhere +# different every time (36% used to reach the plan table; with a wordier model +# response it stops short of it). Anchor on the Phase 6 heading instead, which +# is exactly where the diagnosis verdict ends. +# +# Phase 6 and not Phase 7: the repo refuses to commit a file over 1MB, and +# rendering through the plan table costs 1.3MB even at font-size 10 / 4fps, +# which is past legible. Ending on the diagnosis keeps the part that matters — +# the model's own root-cause paragraph, which is the whole reason this is +# recorded live — inside the budget. +CUT="$(node -e ' +const fs = require("node:fs"); +const lines = fs.readFileSync(process.argv[1], "utf8").trim().split("\n").slice(1); +let t = 0; +let cut = null; +for (const line of lines) { + if (!line.trim()) continue; + const [interval, code, data] = JSON.parse(line); + t += interval; + if (code === "o" && String(data).replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").includes("Phase 6:")) { + // A beat past the heading so the transition is visible in the last frame. + cut = t + 0.8; + break; + } +} +if (cut === null) { + console.error("record-demo: could not find the Phase 6 heading; cannot place the GIF cut"); + process.exit(1); +} +process.stdout.write(cut.toFixed(2)); +' "$CAST")" + +# 2fps, not 6: the window has to span a real 8-20s AI call, and the diagnosis +# text is expensive to render (6fps costs 2.9MB, 3fps still 1.4MB). At 2fps the +# GIF lands near 900KB, inside the repo's 1MB commit ceiling. The demo reveals +# text in bursts and the cast is already re-timed for reading speed, so the +# frame rate costs smoothness rather than content. +echo "==> rendering GIF teaser (0..${CUT}s) -> $GIF" +agg -q \ + --select "0..$CUT" \ + --fps-cap 2 \ + --font-size 14 \ + --line-height 1.4 \ + --theme "$THEME" \ + "$CAST" "$GIF" + +# The teaser is embedded in the landing page, so guard the budget out loud +# rather than discovering a 5MB GIF in production. +# 1MB is not a soft budget: the pre-commit hook rejects files above it, so a +# regression here blocks the commit rather than quietly shipping a heavy page. +GIF_BYTES="$(wc -c < "$GIF" | tr -d ' ')" +if [ "$GIF_BYTES" -gt 1048576 ]; then + echo "error: GIF is $((GIF_BYTES / 1024))KiB, over the 1MB pre-commit ceiling." >&2 + echo " Lower --fps-cap or --font-size, or move the cut anchor earlier." >&2 + exit 1 +fi + +echo +echo "done:" +ls -lh "$CAST" "$GIF" | awk '{print " " $5 "\t" $9}' +echo " (raw unpaced capture kept at $RAW_CAST)" diff --git a/site/assets/asciinema-player.css b/site/assets/asciinema-player.css new file mode 100644 index 0000000..1857794 --- /dev/null +++ b/site/assets/asciinema-player.css @@ -0,0 +1,762 @@ +.ap-default-term-ff { + --term-font-family: "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace, "Symbols Nerd Font"; +} +div.ap-wrapper { + outline: none; + height: 100%; + display: flex; + justify-content: center; +} +div.ap-wrapper .title-bar { + display: none; + top: -78px; + transition: top 0.15s linear; + position: absolute; + left: 0; + right: 0; + box-sizing: content-box; + font-size: 20px; + line-height: 1em; + padding: 15px; + font-family: sans-serif; + color: white; + background-color: rgba(0, 0, 0, 0.8); +} +div.ap-wrapper .title-bar img { + vertical-align: middle; + height: 48px; + margin-right: 16px; +} +div.ap-wrapper .title-bar a { + color: white; + text-decoration: underline; +} +div.ap-wrapper .title-bar a:hover { + text-decoration: none; +} +div.ap-wrapper:fullscreen { + background-color: #000; + width: 100%; + align-items: center; +} +div.ap-wrapper:fullscreen .title-bar { + display: initial; +} +div.ap-wrapper:fullscreen.hud .title-bar { + top: 0; +} +div.ap-wrapper div.ap-player { + text-align: left; + display: inline-block; + padding: 0px; + position: relative; + box-sizing: content-box; + overflow: hidden; + max-width: 100%; + border-radius: 4px; + font-size: 15px; + background-color: var(--term-color-background); +} +.ap-player { + --term-color-foreground: #ffffff; + --term-color-background: #000000; + --term-color-0: var(--term-color-foreground); + --term-color-1: var(--term-color-foreground); + --term-color-2: var(--term-color-foreground); + --term-color-3: var(--term-color-foreground); + --term-color-4: var(--term-color-foreground); + --term-color-5: var(--term-color-foreground); + --term-color-6: var(--term-color-foreground); + --term-color-7: var(--term-color-foreground); + --term-color-8: var(--term-color-0); + --term-color-9: var(--term-color-1); + --term-color-10: var(--term-color-2); + --term-color-11: var(--term-color-3); + --term-color-12: var(--term-color-4); + --term-color-13: var(--term-color-5); + --term-color-14: var(--term-color-6); + --term-color-15: var(--term-color-7); +} +div.ap-term { + position: relative; + font-family: var(--term-font-family); + border-width: 0.75em; + border-radius: 0; + border-style: solid; + border-color: var(--term-color-background); + box-sizing: content-box; +} +div.ap-term canvas { + position: absolute; + inset: 0; + display: block; + width: 100%; + height: 100%; +} +div.ap-term svg.ap-term-symbols { + position: absolute; + inset: 0; + display: block; + width: 100%; + height: 100%; + overflow: hidden; + pointer-events: none; +} +div.ap-term svg.ap-term-symbols use { + color: var(--term-color-foreground); +} +div.ap-term svg.ap-term-symbols:not(.ap-blink) .ap-blink { + opacity: 0; +} +div.ap-term pre.ap-term-text { + position: absolute; + inset: 0; + box-sizing: content-box; + overflow: hidden; + padding: 0; + margin: 0px; + display: block; + white-space: pre; + word-wrap: normal; + word-break: normal; + cursor: text; + color: var(--term-color-foreground); + outline: none; + line-height: var(--term-line-height); + font-family: inherit; + font-size: inherit; + font-variant-ligatures: none; + border: 0; + border-radius: 0; + background-color: transparent !important; +} +pre.ap-term-text .ap-line { + display: block; + width: 100%; + height: var(--term-line-height); + position: absolute; + top: calc(100% * var(--row) / var(--term-rows)); + letter-spacing: normal; + overflow: hidden; +} +pre.ap-term-text .ap-line span { + position: absolute; + left: calc(100% * var(--offset) / var(--term-cols)); + padding: 0; + display: inline-block; + height: 100%; +} +pre.ap-term-text:not(.ap-blink) .ap-line .ap-blink { + color: transparent; + border-color: transparent; +} +pre.ap-term-text .ap-bold { + font-weight: bold; +} +pre.ap-term-text .ap-faint { + opacity: 0.5; +} +pre.ap-term-text .ap-underline { + text-decoration: underline; +} +pre.ap-term-text .ap-italic { + font-style: italic; +} +pre.ap-term-text .ap-strike { + text-decoration: line-through; +} +.ap-line span { + color: var(--term-color-foreground); +} +div.ap-player div.ap-control-bar { + width: 100%; + height: 32px; + display: flex; + justify-content: space-between; + align-items: stretch; + color: var(--term-color-foreground); + box-sizing: content-box; + line-height: 1; + position: absolute; + bottom: 0; + left: 0; + opacity: 0; + transition: opacity 0.15s linear; + user-select: none; + border-top: 2px solid color-mix(in oklab, var(--term-color-background) 80%, var(--term-color-foreground)); + z-index: 30; +} +div.ap-player div.ap-control-bar * { + box-sizing: inherit; +} +div.ap-control-bar svg.ap-icon path { + fill: var(--term-color-foreground); +} +div.ap-control-bar button.ap-button { + display: flex; + flex: 0 0 auto; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + background: transparent; + border: 0; + margin: 0; + padding: 0; + color: inherit; + font: inherit; + line-height: inherit; +} +div.ap-control-bar button.ap-playback-button { + width: 12px; + height: 12px; + padding: 10px; + margin: 0 0 0 2px; +} +div.ap-control-bar button.ap-playback-button svg { + height: 12px; + width: 12px; +} +div.ap-control-bar span.ap-timer { + display: flex; + flex: 0 0 auto; + min-width: 50px; + margin: 0 10px; + height: 100%; + text-align: center; + font-size: 13px; + line-height: 100%; + cursor: default; +} +div.ap-control-bar span.ap-timer span { + font-family: var(--term-font-family); + font-size: inherit; + font-weight: 600; + margin: auto; +} +div.ap-control-bar span.ap-timer .ap-time-remaining { + display: none; +} +div.ap-control-bar span.ap-timer:hover .ap-time-elapsed { + display: none; +} +div.ap-control-bar span.ap-timer:hover .ap-time-remaining { + display: flex; +} +div.ap-control-bar .ap-progressbar { + display: block; + flex: 1 1 auto; + height: 100%; + padding: 0 10px; +} +div.ap-control-bar .ap-progressbar .ap-bar { + display: block; + position: relative; + cursor: default; + height: 100%; + font-size: 0; +} +div.ap-control-bar .ap-progressbar .ap-bar .ap-gutter { + display: block; + position: absolute; + top: 15px; + left: 0; + right: 0; + height: 3px; +} +div.ap-control-bar .ap-progressbar .ap-bar .ap-gutter-empty { + background-color: color-mix(in oklab, var(--term-color-foreground) 20%, var(--term-color-background)); +} +div.ap-control-bar .ap-progressbar .ap-bar .ap-gutter-full { + width: 100%; + transform-origin: left center; + background-color: var(--term-color-foreground); + border-radius: 3px; +} +div.ap-control-bar.ap-seekable .ap-progressbar .ap-bar { + cursor: pointer; +} +div.ap-control-bar button.ap-fullscreen-button { + width: 14px; + height: 14px; + padding: 9px; + margin: 0 2px 0 4px; +} +div.ap-control-bar button.ap-fullscreen-button svg { + width: 14px; + height: 14px; +} +div.ap-control-bar button.ap-fullscreen-button svg.ap-icon-fullscreen-on { + display: inline; +} +div.ap-control-bar button.ap-fullscreen-button svg.ap-icon-fullscreen-off { + display: none; +} +div.ap-control-bar button.ap-fullscreen-button .ap-tooltip { + right: 5px; + left: initial; + transform: none; +} +div.ap-control-bar button.ap-kbd-button { + height: 14px; + padding: 9px; + margin: 0 0 0 4px; +} +div.ap-control-bar button.ap-kbd-button svg { + width: 26px; + height: 14px; +} +div.ap-control-bar button.ap-kbd-button .ap-tooltip { + right: 5px; + left: initial; + transform: none; +} +div.ap-control-bar button.ap-speaker-button { + width: 19px; + padding: 6px 9px; + margin: 0 0 0 4px; + position: relative; +} +div.ap-control-bar button.ap-speaker-button svg { + width: 19px; +} +div.ap-control-bar button.ap-speaker-button .ap-tooltip { + left: -50%; + transform: none; +} +div.ap-wrapper.ap-hud .ap-control-bar { + opacity: 1; +} +div.ap-wrapper:fullscreen button.ap-fullscreen-button svg.ap-icon-fullscreen-on { + display: none; +} +div.ap-wrapper:fullscreen button.ap-fullscreen-button svg.ap-icon-fullscreen-off { + display: inline; +} +span.ap-progressbar span.ap-marker-container { + display: block; + top: 0; + bottom: 0; + width: 21px; + position: absolute; + margin-left: -10px; +} +span.ap-marker-container span.ap-marker { + display: block; + top: 13px; + bottom: 12px; + left: 7px; + right: 7px; + background-color: color-mix(in oklab, var(--term-color-foreground) 33%, var(--term-color-background)); + position: absolute; + transition: top 0.1s, bottom 0.1s, left 0.1s, right 0.1s, background-color 0.1s; + border-radius: 50%; +} +span.ap-marker-container span.ap-marker.ap-marker-past { + background-color: var(--term-color-foreground); +} +span.ap-marker-container span.ap-marker:hover, +span.ap-marker-container:hover span.ap-marker { + background-color: var(--term-color-foreground); + top: 11px; + bottom: 10px; + left: 5px; + right: 5px; +} +.ap-tooltip-container span.ap-tooltip { + visibility: hidden; + background-color: var(--term-color-foreground); + color: var(--term-color-background); + font-family: var(--term-font-family); + font-weight: bold; + text-align: center; + padding: 0 0.5em; + border-radius: 4px; + position: absolute; + z-index: 1; + white-space: nowrap; + /* Prevents the text from wrapping and makes sure the tooltip width adapts to the text length */ + font-size: 13px; + line-height: 2em; + bottom: 100%; + left: 50%; + transform: translateX(-50%); +} +.ap-tooltip-container:hover span.ap-tooltip { + visibility: visible; +} +.ap-player .ap-overlay { + z-index: 10; + background-repeat: no-repeat; + background-position: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; +} +.ap-player .ap-overlay-start { + cursor: pointer; +} +.ap-player .ap-overlay-start .ap-play-button { + font-size: 0px; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + text-align: center; + color: white; + height: 80px; + max-height: 66%; + margin: auto; +} +.ap-player .ap-overlay-start .ap-play-button div { + height: 100%; +} +.ap-player .ap-overlay-start .ap-play-button div span { + height: 100%; + display: block; +} +.ap-player .ap-overlay-start .ap-play-button div span svg { + height: 100%; + display: inline-block; +} +.ap-player .ap-overlay-start .ap-play-button svg { + filter: drop-shadow(0px 0px 5px rgba(0, 0, 0, 0.4)); +} +.ap-player .ap-overlay-loading .ap-loader { + width: 48px; + height: 48px; + border-radius: 50%; + display: inline-block; + position: relative; + border: 10px solid; + border-color: rgba(255, 255, 255, 0.3) rgba(255, 255, 255, 0.5) rgba(255, 255, 255, 0.7) #ffffff; + border-color: color-mix(in srgb, var(--term-color-foreground) 30%, var(--term-color-background)) color-mix(in srgb, var(--term-color-foreground) 50%, var(--term-color-background)) color-mix(in srgb, var(--term-color-foreground) 70%, var(--term-color-background)) color-mix(in srgb, var(--term-color-foreground) 100%, var(--term-color-background)); + box-sizing: border-box; + animation: ap-loader-rotation 1s linear infinite; +} +.ap-player .ap-overlay-info { + background-color: var(--term-color-background); +} +.ap-player .ap-overlay-info span { + font-family: var(--term-font-family); + font-size: 2em; + font-weight: bold; + color: var(--term-color-background); + background-color: var(--term-color-foreground); + padding: 0.5em 0.75em; + text-transform: uppercase; +} +.ap-player .ap-overlay-keystrokes { + position: absolute; + top: auto; + left: auto; + right: 20px; + bottom: var(--ap-keystrokes-bottom); + z-index: 20; + width: auto; + height: auto; + font-size: 18px; + text-align: right; + padding: 0; + display: flex; + justify-content: flex-end; + align-items: center; + gap: 0.35em; + pointer-events: none; +} +.ap-player .ap-overlay-keystrokes .ap-keystroke-pill { + font-family: var(--term-font-family); + font-size: inherit; + opacity: 1; + transition: opacity 80ms ease; +} +.ap-player .ap-overlay-keystrokes .ap-keystroke-pill kbd { + display: inline-flex; + justify-content: center; + min-width: 1.2em; + color: var(--term-color-background); + background-color: var(--term-color-foreground); + padding: 0.3em 0.5em; + border-radius: 0.2em; + font-family: inherit; + font-size: 0.85em; + font-weight: 600; + border: none; + margin: 0; +} +.ap-player .ap-overlay-keystrokes .ap-keystroke-pill.fading { + opacity: 0; + transition: opacity 700ms ease; +} +.ap-player .ap-overlay-help { + background-color: rgba(0, 0, 0, 0.8); + container-type: inline-size; +} +.ap-player .ap-overlay-help > div { + font-family: var(--term-font-family); + max-width: 85%; + max-height: 85%; + font-size: 18px; + color: var(--term-color-foreground); + box-sizing: border-box; + margin-bottom: 32px; +} +.ap-player .ap-overlay-help > div div { + padding: calc(min(4cqw, 40px)); + font-size: calc(min(1.9cqw, 18px)); + background-color: var(--term-color-background); + border: 1px solid color-mix(in oklab, var(--term-color-background) 90%, var(--term-color-foreground)); + border-radius: 6px; +} +.ap-player .ap-overlay-help > div div p { + font-weight: bold; + margin: 0 0 2em 0; +} +.ap-player .ap-overlay-help > div div ul { + list-style: none; + padding: 0; +} +.ap-player .ap-overlay-help > div div ul li { + margin: 0 0 0.75em 0; +} +.ap-player .ap-overlay-help > div div kbd { + color: var(--term-color-background); + background-color: var(--term-color-foreground); + padding: 0.2em 0.5em; + border-radius: 0.2em; + font-family: inherit; + font-size: 0.85em; + border: none; + margin: 0; +} +.ap-player .ap-overlay-error span { + font-size: 8em; +} +.ap-player .slide-enter-active { + transition: opacity 0.2s; +} +.ap-player .slide-enter-active.ap-was-playing { + transition: top 0.2s ease-out, opacity 0.2s; +} +.ap-player .slide-exit-active { + transition: top 0.2s ease-in, opacity 0.2s; +} +.ap-player .slide-enter { + top: -50%; + opacity: 0; +} +.ap-player .slide-enter-to { + top: 0%; +} +.ap-player .slide-enter, +.ap-player .slide-enter-to, +.ap-player .slide-exit, +.ap-player .slide-exit-to { + bottom: auto; + height: 100%; +} +.ap-player .slide-exit { + top: 0%; +} +.ap-player .slide-exit-to { + top: -50%; + opacity: 0; +} +@keyframes ap-loader-rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +.asciinema-player-theme-asciinema { + --term-color-foreground: #cccccc; + --term-color-background: #121314; + --term-color-0: #000000; + --term-color-1: #dd3c69; + --term-color-2: #4ebf22; + --term-color-3: #ddaf3c; + --term-color-4: #26b0d7; + --term-color-5: #b954e1; + --term-color-6: #54e1b9; + --term-color-7: #d9d9d9; + --term-color-8: #4d4d4d; + --term-color-9: #dd3c69; + --term-color-10: #4ebf22; + --term-color-11: #ddaf3c; + --term-color-12: #26b0d7; + --term-color-13: #b954e1; + --term-color-14: #54e1b9; + --term-color-15: #ffffff; +} +/* + Based on Dracula: https://draculatheme.com + */ +.asciinema-player-theme-dracula { + --term-color-foreground: #f8f8f2; + --term-color-background: #282a36; + --term-color-0: #21222c; + --term-color-1: #ff5555; + --term-color-2: #50fa7b; + --term-color-3: #f1fa8c; + --term-color-4: #bd93f9; + --term-color-5: #ff79c6; + --term-color-6: #8be9fd; + --term-color-7: #f8f8f2; + --term-color-8: #6272a4; + --term-color-9: #ff6e6e; + --term-color-10: #69ff94; + --term-color-11: #ffffa5; + --term-color-12: #d6acff; + --term-color-13: #ff92df; + --term-color-14: #a4ffff; + --term-color-15: #ffffff; +} +/* Based on Monokai from base16 collection - https://github.com/chriskempson/base16 */ +.asciinema-player-theme-monokai { + --term-color-foreground: #f8f8f2; + --term-color-background: #272822; + --term-color-0: #272822; + --term-color-1: #f92672; + --term-color-2: #a6e22e; + --term-color-3: #f4bf75; + --term-color-4: #66d9ef; + --term-color-5: #ae81ff; + --term-color-6: #a1efe4; + --term-color-7: #f8f8f2; + --term-color-8: #75715e; + --term-color-15: #f9f8f5; +} +/* + Based on Nord: https://github.com/arcticicestudio/nord + Via: https://github.com/neilotoole/asciinema-theme-nord + */ +.asciinema-player-theme-nord { + --term-color-foreground: #eceff4; + --term-color-background: #2e3440; + --term-color-0: #3b4252; + --term-color-1: #bf616a; + --term-color-2: #a3be8c; + --term-color-3: #ebcb8b; + --term-color-4: #81a1c1; + --term-color-5: #b48ead; + --term-color-6: #88c0d0; + --term-color-7: #eceff4; +} +.asciinema-player-theme-seti { + --term-color-foreground: #cacecd; + --term-color-background: #111213; + --term-color-0: #323232; + --term-color-1: #c22832; + --term-color-2: #8ec43d; + --term-color-3: #e0c64f; + --term-color-4: #43a5d5; + --term-color-5: #8b57b5; + --term-color-6: #8ec43d; + --term-color-7: #eeeeee; + --term-color-15: #ffffff; +} +/* + Based on Solarized Dark: https://ethanschoonover.com/solarized/ + */ +.asciinema-player-theme-solarized-dark { + --term-color-foreground: #839496; + --term-color-background: #002b36; + --term-color-0: #073642; + --term-color-1: #dc322f; + --term-color-2: #859900; + --term-color-3: #b58900; + --term-color-4: #268bd2; + --term-color-5: #d33682; + --term-color-6: #2aa198; + --term-color-7: #eee8d5; + --term-color-8: #002b36; + --term-color-9: #cb4b16; + --term-color-10: #586e75; + --term-color-11: #657b83; + --term-color-12: #839496; + --term-color-13: #6c71c4; + --term-color-14: #93a1a1; + --term-color-15: #fdf6e3; +} +/* + Based on Solarized Light: https://ethanschoonover.com/solarized/ + */ +.asciinema-player-theme-solarized-light { + --term-color-foreground: #657b83; + --term-color-background: #fdf6e3; + --term-color-0: #073642; + --term-color-1: #dc322f; + --term-color-2: #859900; + --term-color-3: #b58900; + --term-color-4: #268bd2; + --term-color-5: #d33682; + --term-color-6: #2aa198; + --term-color-7: #eee8d5; + --term-color-8: #002b36; + --term-color-9: #cb4b16; + --term-color-10: #586e75; + --term-color-11: #657c83; + --term-color-12: #839496; + --term-color-13: #6c71c4; + --term-color-14: #93a1a1; + --term-color-15: #fdf6e3; +} +.asciinema-player-theme-solarized-light .ap-overlay-start .ap-play-button svg .ap-play-btn-fill { + fill: var(--term-color-1); +} +.asciinema-player-theme-solarized-light .ap-overlay-start .ap-play-button svg .ap-play-btn-stroke { + stroke: var(--term-color-1); +} +/* + Based on Tango: https://en.wikipedia.org/wiki/Tango_Desktop_Project + */ +.asciinema-player-theme-tango { + --term-color-foreground: #cccccc; + --term-color-background: #121314; + --term-color-0: #000000; + --term-color-1: #cc0000; + --term-color-2: #4e9a06; + --term-color-3: #c4a000; + --term-color-4: #3465a4; + --term-color-5: #75507b; + --term-color-6: #06989a; + --term-color-7: #d3d7cf; + --term-color-8: #555753; + --term-color-9: #ef2929; + --term-color-10: #8ae234; + --term-color-11: #fce94f; + --term-color-12: #729fcf; + --term-color-13: #ad7fa8; + --term-color-14: #34e2e2; + --term-color-15: #eeeeec; +} +/* + Based on gruvbox: https://github.com/morhetz/gruvbox + */ +.asciinema-player-theme-gruvbox-dark { + --term-color-foreground: #fbf1c7; + --term-color-background: #282828; + --term-color-0: #282828; + --term-color-1: #cc241d; + --term-color-2: #98971a; + --term-color-3: #d79921; + --term-color-4: #458588; + --term-color-5: #b16286; + --term-color-6: #689d6a; + --term-color-7: #a89984; + --term-color-8: #7c6f65; + --term-color-9: #fb4934; + --term-color-10: #b8bb26; + --term-color-11: #fabd2f; + --term-color-12: #83a598; + --term-color-13: #d3869b; + --term-color-14: #8ec07c; + --term-color-15: #fbf1c7; +} diff --git a/site/assets/asciinema-player.min.js b/site/assets/asciinema-player.min.js new file mode 100644 index 0000000..2d14ba0 --- /dev/null +++ b/site/assets/asciinema-player.min.js @@ -0,0 +1,3 @@ +/* @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt Apache-2.0 */ +var AsciinemaPlayer=function(A){"use strict";function e(A){return"number"==typeof A?A:"string"==typeof A?A.split(":").reverse().map(parseFloat).reduce(((A,e,t)=>A+e*Math.pow(60,t))):void 0}function t(A){return{name:"string"==typeof A?.name?A.name:"Error",message:"string"==typeof A?.message?A.message:String(A)}}const V=/^#[0-9a-f]{6}$/,g=/^#[0-9a-f]{3}$/;function n(A,e=void 0){if("string"!=typeof A)return e;const t=A.trim().toLowerCase();return V.test(t)?t:g.test(t)?`#${t[1]}${t[1]}${t[2]}${t[2]}${t[3]}${t[3]}`:e}function r(A,e,t){return[e[0]+A*(t[0]-e[0]),e[1]+A*(t[1]-e[1]),e[2]+A*(t[2]-e[2])]}function i(A){const[e,t,V]=function(A){return[Number.parseInt(A.slice(1,3),16)/255,Number.parseInt(A.slice(3,5),16)/255,Number.parseInt(A.slice(5,7),16)/255]}(A).map(Q),g=.4122214708*e+.5363325363*t+.0514459929*V,n=.2119034982*e+.6806995451*t+.1073969566*V,r=.0883024619*e+.2817188376*t+.6299787005*V,i=Math.cbrt(g),o=Math.cbrt(n),I=Math.cbrt(r);return[.2104542553*i+.793617785*o-.0040720468*I,1.9779984951*i-2.428592205*o+.4505937099*I,.0259040371*i+.7827717662*o-.808675766*I]}function o(A){const e=I(A);if(E(e))return C(e);const[t,V,g]=function([A,e,t]){return[A,Math.hypot(e,t),Math.atan2(t,e)]}(A);let n=0,r=V,i=[t,0,g];for(let A=0;A<24;A+=1){const A=(n+r)/2,e=[t,A,g];E(I(B(e)))?(n=A,i=e):r=A}return C(I(B(i)))}function I(A){const e=c(A[0],0,1),t=A[1],V=A[2],g=(e+.3963377774*t+.2158037573*V)**3,n=(e-.1055613458*t-.0638541728*V)**3,r=(e-.0894841775*t-1.291485548*V)**3,i=-1.2684380046*g+2.6097574011*n-.3413193965*r,o=-.0041960863*g-.7034186147*n+1.707614701*r;return[a(4.0767416621*g-3.3077115913*n+.2309699292*r),a(i),a(o)]}function B([A,e,t]){return[A,e*Math.cos(t),e*Math.sin(t)]}function s(A,e,t){return`#${l(A)}${l(e)}${l(t)}`}function C(A){return s(255*A[0],255*A[1],255*A[2])}function Q(A){return A<=.04045?A/12.92:((A+.055)/1.055)**2.4}function a(A){return A<=.0031308?12.92*A:1.055*A**(1/2.4)-.055}function E([A,e,t]){return A>=0&&A<=1&&e>=0&&e<=1&&t>=0&&t<=1}function c(A,e,t){return Math.max(e,Math.min(t,A))}function l(A){return Math.round(c(A,0,255)).toString(16).padStart(2,"0")}class u{log(...A){}debug(...A){}info(...A){}warn(...A){}error(...A){}}class d{constructor(A,e){this.logger=A,this.prefix=e}log(A,...e){this.logger.log(`${this.prefix}${A}`,...e)}debug(A,...e){this.logger.debug(`${this.prefix}${A}`,...e)}info(A,...e){this.logger.info(`${this.prefix}${A}`,...e)}warn(A,...e){this.logger.warn(`${this.prefix}${A}`,...e)}error(A,...e){this.logger.error(`${this.prefix}${A}`,...e)}}class f{constructor(A=1){this.speed=A,this.startTime=performance.now()}getTime(){return this.speed*(performance.now()-this.startTime)}setTime(A){this.startTime=performance.now()-A/this.speed}}class w{constructor(){}getTime(A){}setTime(A){}}class h{constructor(A,e){this.input="function"==typeof A.next?A:A[Symbol.iterator](),this.xfs=e??[]}map(A){return this.transform(function(A){return e=>t=>{e(A(t))}}(A))}flatMap(A){return this.transform(function(A){return e=>t=>{A(t).forEach(e)}}(A))}filter(A){return this.transform(function(A){return e=>t=>{A(t)&&e(t)}}(A))}take(A){return this.transform(function(A){let e=0;return t=>V=>{eV=>{e+=1,e>A&&t(V)}}(A))}transform(A){return new h(this.input,this.xfs.concat([A]))}multiplex(A,e){return new h(new y(this[Symbol.iterator](),A[Symbol.iterator](),e))}toArray(){return Array.from(this)}[Symbol.iterator](){let A=0,e=[],t=!1;const V=(g=this.xfs,n=A=>e.push(A),g.reverse().reduce(((A,e)=>{const t=D(e(A.step));return{step:t.step,flush:()=>{t.flush(),A.flush()}}}),D(n)));var g,n;return{next:()=>{for(A===e.length&&(e=[],A=0);0===e.length;){const A=this.input.next();if(A.done)break;V.step(A.value)}return 0!==e.length||t||(V.flush(),t=!0),e.length>0?{done:!1,value:e[A++]}:{done:!0}}}}}function D(A){return"function"==typeof A?{step:A,flush:()=>{}}:A}class y{constructor(A,e,t){this.left=A,this.right=e,this.comparator=t}[Symbol.iterator](){let A,e;return{next:()=>{if(void 0===A&&void 0!==this.left){const e=this.left.next();e.done?this.left=void 0:A=e.value}if(void 0===e&&void 0!==this.right){const A=this.right.next();A.done?this.right=void 0:e=A.value}if(void 0===A&&void 0===e)return{done:!0};if(void 0===A){const A=e;return e=void 0,{done:!1,value:A}}if(void 0===e){const e=A;return A=void 0,{done:!1,value:e}}if(this.comparator(A,e)){const e=A;return A=void 0,{done:!1,value:e}}{const A=e;return e=void 0,{done:!1,value:A}}}}}}async function p(A,e){return function(A){const e={start:0},t=A.events.filter((A=>"m"===A[1])).map((A=>[A[0],A[2].label]));return{cols:A.cols,rows:A.rows,theme:A.theme,duration:A.duration,effectiveStartAt:A.effectiveStartAt,markers:t,segments:[e],async loadSegment(e){if(0!==e)throw new Error("unknown recording segment");return{snapshot:{cols:A.cols,rows:A.rows,init:""},events:A.events}}}}(function(A,{startAt:e=0,idleTimeLimit:t,inputOffset:V,markers:g}){let{events:n}=A;n instanceof h||(n=new h(n));e*=1e3,t=t??A.idleTimeLimit,t=void 0!==t?1e3*t:1/0,V=void 0!==V?1e3*V:void 0;const r={offset:0};n=n.map(function(A,e,t){let V=0,g=0;return function(n){const r=n[0]-V-A;return V=n[0],r>0&&(g+=r,n[0]"m"!==A[1])).multiplex(g,((A,e)=>A[0]"i"===A[1]?[A[0]+V,A[1],A[2]]:A)),n.sort(((A,e)=>A[0]-e[0])));if(0===n.length)throw new Error("recording is missing events");const i=n[n.length-1][0],o=e-r.offset;return{...A,events:n,duration:i,effectiveStartAt:o}}(await async function(A){const{parser:e,encoding:t="utf-8"}=A,V=await async function({url:A,data:e,fetchOpts:t={}}){if("string"==typeof A)return await k(A,t);if(Array.isArray(A))return await Promise.all(A.map((A=>k(A,t))));if(void 0!==e){"function"==typeof e&&(e=e()),e instanceof Promise||(e=Promise.resolve(e));const A=await e;return"string"==typeof A||A instanceof ArrayBuffer?new Response(A):A}throw new Error("failed fetching recording file: url/data missing in src")}(A);return await e(V,{encoding:t})}(A),e))}async function k(A,e){const t=await fetch(A,e);if(!t.ok)throw new Error(`failed fetching recording from ${A}: ${t.status} ${t.statusText}`);return t}function m(A){return"number"==typeof A?[1e3*A,"m",""]:[1e3*A[0],"m",A[1]]}function q(A){const e=n(A.foreground),t=n(A.background),V=A.palette;if(void 0===V)return;if(!e||!t||V.length<8)return;const g=[],r=Math.min(V.length,16);for(let A=0;A0)throw new Error(`segmented recordings do not support option: ${V.join(", ")}`)}(A,e),async function(A,{startAt:e=0}){const t=await F(A.url,A.fetchOpts??{});let V;try{V=await t.json()}catch(e){throw new Error(`failed parsing segmented recording index from ${A.url}: ${e.message}`)}!function(A){if(1!==A?.version)throw new Error(`unsupported segmented recording version: ${JSON.stringify(A?.version)}`);if(M(A.duration,"recording duration"),R(A.term,"recording terminal"),!Array.isArray(A.segments)||0===A.segments.length)throw new Error("segmented recording index is missing segments");let e=-1;if(A.segments.forEach(((t,V)=>{if(M(t?.start,`segment ${V} start`),"string"!=typeof t?.url||0===t.url.length)throw new Error(`segment ${V} is missing its URL`);if(0===V&&0!==t.start)throw new Error("first segment must start at 0");if(V>0&&(t.start<=e||t.start>=A.duration))throw new Error(`segment ${V} start must be strictly increasing and before duration`);e=t.start})),void 0!==A.markers&&!Array.isArray(A.markers))throw new Error("segmented recording markers must be an array");let t=-1;for(const[e,V]of(A.markers??[]).entries()){if(!Array.isArray(V)||2!==V.length||"string"!=typeof V[1])throw new Error(`invalid marker ${e} in segmented recording index`);if(M(V[0],`marker ${e} time`),V[0]A.duration)throw new Error(`marker ${e} time is out of order or range`);t=V[0]}}(V);const g=1e3*V.duration,n=(V.markers??[]).map((([A,e])=>[1e3*A,e])),r=V.segments.map((e=>({start:1e3*e.start,url:v(e.url,t.url||A.url)}))),i={cols:V.term.cols,rows:V.term.rows,theme:N(V.term.theme),duration:g,effectiveStartAt:Math.min(Math.max(1e3*e,0),g),markers:n,segments:r,async loadSegment(e){if(!Number.isInteger(e)||e<0||e>=r.length)throw new Error("unknown recording segment");const t=r[e],V=await F(t.url,A.fetchOpts??{});let g;try{g=await V.json()}catch(A){throw new Error(`failed parsing recording segment from ${t.url}: ${A.message}`)}return function(A,e,t){const V=t?.snapshot;if(R(V,`segment ${e} snapshot`),"string"!=typeof V.init)throw new Error(`segment ${e} snapshot init must be a string`);if(!Array.isArray(t.events)||0===t.events.length)throw new Error(`segment ${e} is missing events`);const g=A.segments[e].start,n=A.segments[e+1]?.start??A.duration;let r=-1,i=A.markers.findIndex((([A])=>A>=g));-1===i&&(i=A.markers.length);const o=t.events.map(((t,V)=>{if(!Array.isArray(t)||3!==t.length||"string"!=typeof t[1])throw new Error(`invalid event ${V} in segment ${e}`);const o=1e3*t[0];if(M(o,`event ${V} time in segment ${e}`,!0),o=n:o>n))throw new Error(`event ${V} time is out of range in segment ${e}`);if(r=o,"m"===t[1]){if("string"!=typeof t[2])throw new Error(`marker event ${V} in segment ${e} must have a string label`);return[o,"m",{index:i++,time:o,label:t[2]}]}return[o,t[1],t[2]]}));if(e>0&&o[0][0]!==g)throw new Error(`segment ${e} first event must match its start`);if(e===A.segments.length-1&&o[o.length-1][0]!==A.duration)throw new Error("final segment event must match recording duration");return{snapshot:{cols:V.cols,rows:V.rows,init:V.init},events:o}}(i,e,g)}};return i}(A,e)}async function F(A,e){const t=await fetch(A,e);if(!t.ok)throw new Error(`failed fetching recording from ${A}: ${t.status} ${t.statusText}`);return t}function M(A,e,t=!1){if(!Number.isFinite(A)||A<0)throw new Error(`${e} must be a finite non-negative ${t?"millisecond":"second"} value`)}function R(A,e){if(!Number.isInteger(A?.cols)||A.cols<=0||!Number.isInteger(A?.rows)||A.rows<=0)throw new Error(`${e} must have positive integer cols and rows`)}function N(A){return q({foreground:A?.fg,background:A?.bg,palette:"string"==typeof A?.palette?A.palette.split(":"):void 0})}function v(A,e){return new URL(A,new URL(e,globalThis.location?.href??"http://localhost/")).href}function b(A,e){let t=0,V=A.segments.length;for(;t+1"["===A[0])).map(JSON.parse);return{header:t,events:V}}(e);if(void 0!==t){const{header:A,events:e}=t;if(2===A.version)return S(A,e);if(3===A.version)return J(A,e);throw new Error(`asciicast v${A.version} format not supported`)}{const A=JSON.parse(e);if(1===A.version)return U(A)}}else{if("object"==typeof A&&1===A.version)return U(A);if(Array.isArray(A)){const e=A[0];if(2===e.version){return S(e,A.slice(1,A.length))}if(3===e.version){return J(e,A.slice(1,A.length))}throw new Error(`asciicast v${e.version} format not supported`)}}throw new Error("invalid data")}function U(A){let e=0;const t=new h(A.stdout).map((A=>(e+=1e3*A[0],[e,"o",A[1]])));return{cols:0===A.width?80:A.width,rows:0===A.height?24:A.height,events:t}}function S(A,e){return e instanceof h||(e=new h(e)),e=e.map((A=>[1e3*A[0],A[1],A[2]])),{cols:0===A.width?80:A.width,rows:0===A.height?24:A.height,theme:Y(A.theme),events:e,idleTimeLimit:A.idle_time_limit}}function J(A,e){e instanceof h||(e=new h(e));let t=0;return e=e.map((A=>(t+=1e3*A[0],[t,A[1],A[2]]))),{cols:0===A.term.cols?80:A.term.cols,rows:0===A.term.rows?24:A.term.rows,theme:Y(A.term?.theme),events:e,idleTimeLimit:A.idle_time_limit}}function Y(A){const e="string"==typeof A?.palette?A.palette.split(":"):void 0;return q({foreground:A?.fg,background:A?.bg,palette:e})}function T(A,e,t,V,g,n){const r=function(A){return function(e,t){"o"===e?A("output",t):"i"===e?A("input",{data:t}):"r"===e?A("resize",t):"m"===e&&A("marker",t)}}(e);if(0===A)return n.debug("using no buffer"),function(A){return{pushEvent(e){A(e[1],e[2])},pushText(e){A("o",e)},stop(){}}}(r);{let e;return"number"==typeof(A=A??{})?(n.debug(`using fixed time buffer (${A} ms)`),e=e=>A):"function"==typeof A?(n.debug("using custom dynamic buffer"),e=A({logger:n})):(n.debug("using adaptive buffer",A),e=function({logger:A}={},{minBufferTime:e=50,bufferLevelStep:t=100,maxBufferLevel:V=50,transitionDuration:g=500,peakHalfLifeUp:n=100,peakHalfLifeDown:r=1e4,floorHalfLifeUp:i=5e3,floorHalfLifeDown:o=100,idealHalfLifeUp:I=1e3,idealHalfLifeDown:B=5e3,safetyMultiplier:s=1.2,minImprovementDuration:C=3e3}={}){function Q(A){return 0===A?e:t*A}let a=1,E=Q(a),c=performance.now(),l=null,u=null,d=null,f=null,w=null,h=null;return function(D){const y=performance.now(),p=Math.max(0,y-c);if(c=y,null===l)l=D;else if(D>l){const A=1-Math.pow(2,-p/n);l+=A*(D-l)}else{const A=1-Math.pow(2,-p/r);l+=A*(D-l)}if(l=Math.max(l,0),null===u)u=D;else if(D>u){const A=1-Math.pow(2,-p/i);u+=A*(D-u)}else{const A=1-Math.pow(2,-p/o);u+=A*(D-u)}u=Math.max(u,0);const k=s*(l+(l-u));if(null===d)d=k;else if(k>d){const A=1-Math.pow(2,-p/I);d+=+A*(k-d)}else{const A=1-Math.pow(2,-p/B);d+=+A*(k-d)}let m;var q,L,F;return d<=e?m=0:(q=Math.ceil(d/t),L=1,F=V,m=Math.min(F,Math.max(L,q))),D>E&&A.debug("buffer underrun",{latency:D,bufferTime:E}),m>a?(D>E?a=Math.min(m,a+3):a+=1,w=Q(a),h=(w-E)/g,f=null,A.debug("raising buffer",{latency:D,bufferTime:E,targetBufferTime:w})):m=C&&(a-=1,w=Q(a),h=(w-E)/g,f=y,A.debug("lowering buffer",{latency:D,bufferTime:E,targetBufferTime:w}))):f=null,null!==w&&(E+=h*p,(h>=0&&E>w||h<0&&E0){const A=B;return B=[],A}return new Promise((A=>{i=A}))}async function c(){for(;!C;){const A=await E();if(C)return;let e=0;for(;e=r){const e=n-Q();if(e>0&&(await(i=e,new Promise((A=>{setTimeout(A,i)}))),C))return A.length;t(g[0]),s=n}var i;return"o"===g[1]?u(A,V):(e(g[1],g[2]),V+1)}function u(A,t){const V=A[t],g=V[0]+r,n=[];let i=V;for(;void 0!==i&&"o"===i[1]&&i[0]0&&(Q=e.decode(new Uint8Array(t,A.offset,C))),g=o,{time:r,term:{size:{cols:i,rows:I},theme:s,init:Q}}}function o(o){const I=new $(new DataView(o)),B=I.getUint8();return 1===B?i(I,o):111===B?function(A,t){A.decodeVarUint();const g=A.decodeVarUint();V+=g;const n=A.decodeVarUint(),r=e.decode(new Uint8Array(t,A.offset,n));return[V/K,"o",r]}(I,o):105===B?function(A,e){A.decodeVarUint();const g=A.decodeVarUint();V+=g;const n=A.decodeVarUint(),r=t.decode(new Uint8Array(e,A.offset,n));return[V/K,"i",r]}(I,o):114===B?function(A){A.decodeVarUint();const e=A.decodeVarUint();V+=e;const t=A.decodeVarUint(),g=A.decodeVarUint();return[V/K,"r",{cols:t,rows:g}]}(I):109===B?function(A,e){A.decodeVarUint();const t=A.decodeVarUint();V+=t;const g=A.decodeVarUint(),r=new TextDecoder,i=n++,o=V/K,I=V/H,B=r.decode(new Uint8Array(e,A.offset,g));return[o,"m",{index:i,time:I,label:B}]}(I,o):120===B?function(A){A.decodeVarUint();const e=A.decodeVarUint();V+=e;const t=A.decodeVarUint();return[V/K,"x",{status:t}]}(I):4===B?(g=r,!1):void A.debug(`alis: unknown frame type: ${B}`)}return function(A){return g(A)}}function x(A){const e=A.length/3,t=Z(A[0],A[1],A[2]),V=Z(A[3],A[4],A[5]),g=[];for(let t=2;t127;)t&=127,A+=BigInt(t)<e(A,t,V)}throw new Error(`unsupported driver: ${JSON.stringify(A)}`)}(A),this.driver=null,this.cols=t.cols,this.rows=t.rows,this.speed=t.speed,this.loop=t.loop,this.autoPlay=t.autoPlay,this.idleTimeLimit=t.idleTimeLimit,this.preload=t.preload,this.startAt=e(t.startAt),this.poster=this._parsePoster(t.poster),this.markers=t.markers,this.pauseOnMarkers=t.pauseOnMarkers,this.audioUrl=t.audioUrl,this.initPromise=null,this.commandQueue=Promise.resolve(),this.startupPromise=new Promise((A=>{this.resolveStartup=A})),this.eventHandlers=new Map([["ended",[]],["error",[]],["input",[]],["loading",[]],["marker",[]],["metadata",[]],["muted",[]],["offline",[]],["output",[]],["pause",[]],["play",[]],["playing",[]],["reset",[]],["resize",[]],["ready",[]],["seeked",[]]])}init(){return null===this.initPromise&&(this.initPromise=this._init()),this.initPromise}terminalReady(){this.resolveStartup()}async _init(){await this.startupPromise,this.driver=this.driverFactory({dispatch:this._dispatchEvent.bind(this),logger:this.logger},{cols:this.cols,rows:this.rows,speed:this.speed,idleTimeLimit:this.idleTimeLimit,startAt:this.startAt,preload:this.preload,loop:this.loop,poster:this.autoPlay?void 0:this.poster,markers:this.markers,pauseOnMarkers:this.pauseOnMarkers,audioUrl:this.audioUrl});const A={isPausable:!!this.driver.pause,isSeekable:!!this.driver.seek};this._installDriverDefaults(),this.driver.init&&await this.driver.init(),this.autoPlay&&await this.driver.play(),this._dispatchEvent("ready",A)}_installDriverDefaults(){if(void 0===this.driver.stop&&(this.driver.stop=()=>{}),void 0===this.driver.pause&&(this.driver.pause=()=>{}),void 0===this.driver.seek&&(this.driver.seek=A=>!1),void 0===this.driver.step&&(this.driver.step=A=>{}),void 0===this.driver.mute&&(this.driver.mute=()=>{}),void 0===this.driver.unmute&&(this.driver.unmute=()=>{}),void 0===this.driver.getDuration&&(this.driver.getDuration=()=>{}),void 0===this.driver.getCurrentTime){const A=this.driver.play;let e=new w;this.driver.play=()=>(e=new f(this.speed),A()),this.driver.getCurrentTime=()=>{const A=e.getTime();return"number"==typeof A?A/1e3:A}}}_enqueue(A){const e=async()=>(await this.init(),A.call(this)),t=this.commandQueue.then(e,e);return this.commandQueue=t.catch((()=>{})),t}play(){return this._enqueue((function(){return this.driver.play()}))}pause(){return this._enqueue((function(){return this.driver.pause()}))}seek(A){return this._enqueue((function(){return this.driver.seek(A)}))}step(A){return this._enqueue((function(){return this.driver.step(A)}))}stop(){return this._enqueue((function(){return this.driver.stop()}))}mute(){return this._enqueue((function(){return this.driver.mute()}))}unmute(){return this._enqueue((function(){return this.driver.unmute()}))}getCurrentTime(){return this.driver?this.driver.getCurrentTime():0}getRemainingTime(){const A=this.getDuration();if("number"==typeof A)return A-Math.min(this.getCurrentTime(),A)}getProgress(){const A=this.getDuration();if("number"==typeof A)return Math.min(this.getCurrentTime(),A)/A}getDuration(){if(this.driver)return this.driver.getDuration()}addEventListener(A,e){this.eventHandlers.get(A).push(e)}removeEventListener(A,e){const t=this.eventHandlers.get(A);if(!t)return;const V=t.indexOf(e);-1!==V&&t.splice(V,1)}_dispatchEvent(A,e={}){for(const t of[...this.eventHandlers.get(A)])try{t(e)}catch(e){this.logger.error(`event handler for "${A}" failed`,e),"function"==typeof globalThis.reportError?globalThis.reportError(e):setTimeout((()=>{throw e}),0)}}_parsePoster(A){if("string"==typeof A)return"data:text/plain,"==A.substring(0,16)?{type:"text",value:A.substring(16)}:"npt:"==A.substring(0,4)?{type:"npt",value:e(A.substring(4))}:void 0}}const AA=new Map([["benchmark",function({url:A,iterations:e=10},{dispatch:t}){let V,g=0;return{async init(){const e=await G(await fetch(A)),{cols:n,rows:r,events:i}=e;V=Array.from(i).filter((([A,e,t])=>"o"===e)).map((([A,e,t])=>[A,t]));for(const[A,e]of V)g+=new Blob([e]).size;t("reset",{size:{cols:n,rows:r}})},play(){const A=performance.now();for(let A=0;A{t("ended")}),0),!0}}}],["clock",function({hourColor:A=3,minuteColor:e=4,separatorColor:t=9},{dispatch:V},{cols:g=5,rows:n=1}){const r=Math.floor(n/2),i=Math.floor(g/2)-2,o=`[?25l[${r}B`;let I;const B=()=>{(()=>{const V=new Date,g=V.getHours(),n=V.getMinutes(),r=[];r.push("\r");for(let A=0;A{V("output",A)}))};return{init:()=>{V("reset",{size:{cols:g,rows:n}}),V("output",o),B()},play:()=>(void 0!==I||(V("play"),V("playing"),V("output",o),B(),I=setInterval(B,1e3)),!0),stop:()=>{clearInterval(I)},getCurrentTime:()=>{const A=new Date;return 60*A.getHours()+A.getMinutes()}}}],["eventsource",function({url:A,bufferTime:e,minFrameTime:t},{dispatch:V,logger:g}){let n,r;g=new d(g,"eventsource: ");let i=new w;function o(A){void 0!==r&&r.stop(),r=T(e,V,(A=>i.setTime(A)),A,t,g)}return{play:()=>(n||(V("play"),n=new EventSource(A),n.addEventListener("open",(()=>{g.info("opened"),o()})),n.addEventListener("error",(A=>{g.info("errored"),g.debug({e:A}),V("loading")})),n.addEventListener("message",(A=>{const e=JSON.parse(A.data);if(Array.isArray(e))r.pushEvent([1e3*e[0],e[1],e[2]]);else if(void 0!==e.cols||void 0!==e.width){const A=e.cols??e.width,t=e.rows??e.height,n="number"==typeof e.time?1e3*e.time:void 0;g.debug(`vt reset (${A}x${t})`),o(n),V("reset",{size:{cols:A,rows:t},init:e.init??void 0}),i=new f,void 0!==n&&i.setTime(n),V("playing")}else"offline"===e.state&&(g.info("stream offline"),V("offline",{message:"Stream offline"}),i=new w)})),n.addEventListener("done",(()=>{g.info("closed"),n.close(),V("ended",{message:"Stream ended"})}))),!0),stop:()=>{void 0!==r&&r.stop(),void 0!==n&&n.close()},getCurrentTime:()=>{const A=i.getTime();return"number"==typeof A?A/1e3:A}}}],["random",function(A,{dispatch:e},{speed:t}){const V=" ".charCodeAt(0),g="~".charCodeAt(0)-V;let n;const r=()=>{const A=Math.pow(5,4*Math.random());n=setTimeout(i,A/t)},i=()=>{r();const A=String.fromCharCode(V+Math.floor(Math.random()*g));e("output",A)};return{play(){if(void 0!==n)return!0;e("play"),e("playing"),r()},stop(){clearInterval(n)}}}],["recording",function(A,{dispatch:e,logger:V},{speed:g,idleTimeLimit:n,startAt:r,preload:i,loop:o,poster:I,markers:B,pauseOnMarkers:s,cols:C,rows:Q,audioUrl:a}){const E="cold",c="loading",l="ready.initial",u="ready.paused",d="ready.starting",f="ready.playing",w="ready.buffering.whilePaused",h="ready.buffering.toResume",D="ready.ended",y="failed",k="stopped",m="initRequested",q="playRequested",F="deferredPlayReady",M="pauseRequested",R="seekRequested",N="stepRequested",v="stopRequested",G="loadSucceeded",U="loadFailed",S="playbackStartConfirmed",J="playbackStartRejected",Y="playbackEnded",T="audioWaiting",K="audioPlaying",H="segmentWaiting",O="segmentReady",x="markerReached",Z="play",W="seek",$=1e3*(A.minFrameTime??1/60);let j=()=>performance.now()*g,z=E,X=[],P=!1;const _={recording:void 0,segmentIndex:void 0,segment:void 0,segmentCache:new Map,positionGeneration:0,markers:void 0,duration:void 0,effectiveStartAt:void 0,recordingEventTimeoutId:void 0,nextEventIndex:0,lastEventTime:0,startTime:void 0,pauseElapsedTime:void 0,playCount:0,waitingTimeout:void 0,loadingTimeout:void 0,audioCtx:void 0,audioElement:void 0,audioSeekable:!1,loaded:void 0,posterVisible:!1,posterRenderableAfterLoad:void 0!==I,failureError:null,segmentWaiting:!1};function AA(A=z){return A===w||A===h}function eA(){return!0===o||"number"==typeof o&&_.playCount{e("loading")}),3e3);try{const e=function(A,e){if("segmented"===A.format)return L(A,e);return p(A,e)}(A,{idleTimeLimit:n,startAt:r,markers:B,inputOffset:A.inputOffset}),i=async function(A){if(!A)return!1;_.audioElement=await async function(A){const e=new Audio;let t,V;e.preload="metadata",e.loop=!1,e.crossOrigin="anonymous";const g=new Promise(((A,e)=>{t=A,V=e}));function n(){e.removeEventListener("canplay",r),e.removeEventListener("error",i),e.removeEventListener("abort",o)}function r(){n(),t()}function i(){n(),V(new Error(`failed loading audio from ${A}`))}function o(){n(),V(new Error(`audio loading aborted for ${A}`))}return e.addEventListener("canplay",r),e.addEventListener("error",i),e.addEventListener("abort",o),e.src=A,e.load(),await g,e}(A),_.audioSeekable=!Number.isNaN(_.audioElement.duration)&&_.audioElement.duration!==1/0&&_.audioElement.seekable.length>0&&_.audioElement.seekable.end(_.audioElement.seekable.length-1)===_.audioElement.duration,_.audioSeekable?(_.audioElement.addEventListener("playing",vA),_.audioElement.addEventListener("waiting",NA)):V.warn(`audio is not seekable - you must enable range request support on the server providing ${_.audioElement.src} for audio seeking to work`);return!0}(a).catch((A=>(V.warn(`audio load failed: ${A.message}`),!1))),o=await e;if(g!==_.positionGeneration)return!1;_.recording=o,_.duration=o.duration,_.effectiveStartAt=o.effectiveStartAt,_.markers=o.markers;const s=b(o,t??("npt"===I?.type?1e3*I.value:_.effectiveStartAt)??0),C=await aA(s,!0);if(g!==_.positionGeneration)return!1;QA(s,C);const Q=await i;if(g!==_.positionGeneration)return!1;IA(G,{hasAudio:Q})}catch(A){throw BA(U,{error:A}),A}finally{WA()}}(t),_.loaded.catch((()=>{}))),_.loaded}function VA(A,n,r={}){switch(n){case m:if(A===E){if(i||"npt"==I?.type)return{nextState:c,action:()=>tA()};if("text"==I?.type)return{nextState:A,action:()=>(sA(),void(_.posterRenderableAfterLoad=!1))}}return{nextState:A};case G:return A!==c?{nextState:A}:{nextState:l,action:()=>{e("metadata",{duration:_.duration/1e3,markers:_.markers.map((([A,e])=>[A/1e3,e])),hasAudio:r.hasAudio}),YA(_.segment),sA()}};case U:return A!==c?{nextState:A}:{nextState:y,action:()=>{_.failureError=r.error,e("error",t(r.error))}};case q:return A===E?{nextState:c,action:()=>(CA(),e("play"),tA().then((()=>IA(F))))}:A===l||A===u||A===D?{nextState:d,action:()=>(e("play"),CA(),HA(Z))}:A===w?{nextState:h,action:()=>(e("play"),!!_.segmentWaiting||(!_.audioElement||_.audioElement.play().catch((A=>{throw IA(J),A}))))}:A===h||A===f?{nextState:A,action:()=>(e("play"),!0)}:{nextState:A};case F:return A===l?{nextState:d,action:()=>(CA(),HA(Z))}:{nextState:A};case S:return A!==d?{nextState:A}:{nextState:f,action:()=>(qA(),r.reason===W?e("seeked"):e("playing"),!0)};case J:return A===d?{nextState:u}:A===h?{nextState:w}:{nextState:A};case Y:return A!==f?{nextState:A}:eA()?{nextState:f,action:jA}:{nextState:D,action:zA};case T:return A===f?{nextState:h,action:()=>{V.debug("pausing session playback"),kA(),xA()}}:A===w||A===h?{nextState:A,action:xA}:{nextState:A};case K:return _.segmentWaiting?{nextState:A}:A===h?{nextState:f,action:()=>{V.debug("resuming session playback"),ZA(),qA(),e("playing")}}:A===w?{nextState:u,action:()=>{ZA()}}:{nextState:A};case H:return A===f?{nextState:h,action:()=>{var A;_.segmentWaiting=!0,A=r.time,yA(),_.pauseElapsedTime=A,xA(),_.audioElement&&_.audioElement.pause()}}:{nextState:A};case O:return _.segmentWaiting=!1,A===h?{nextState:A,action:dA}:A===w?{nextState:u,action:ZA}:{nextState:A};case M:return A===f?{nextState:u,action:OA}:A===h?{nextState:w,action:()=>(_.audioElement&&_.audioElement.pause(),!0)}:{nextState:A,action:()=>!0};case R:{if(A===E)return{nextState:c,action:()=>tA("number"==typeof r.where?1e3*r.where:void 0).then((()=>LA(r.where)))};if(AA(A)&&!_.segmentWaiting)return{nextState:A,action:()=>!1};if(A!==l&&A!==u&&A!==D&&A!==f&&A!==w&&A!==h)return{nextState:A};const t=r.seekOperation;return t.noOp?{nextState:A,action:()=>!1}:{nextState:t.reachedEnd?D:A===f||A===h?d:u,action:()=>(CA(),async function(A,t){const V=t===f||t===h,g=++_.positionGeneration;t===f&&kA();_.segmentWaiting=!1,ZA(),_.audioElement&&_.audioElement.pause();if(!await KA(A.targetTime,g))return!1;if(g!==_.positionGeneration)return!1;if(A.reachedEnd)return e("seeked"),e("ended"),!0;if(V)return await HA(W);return e("seeked"),!0}(t,A))}}case N:return A===E?{nextState:c,action:()=>tA().then((()=>oA(N,r)))}:A===f||AA(A)||A!==l&&A!==u&&A!==D?{nextState:A}:{nextState:A,action:()=>async function(A=1){const t=++_.positionGeneration,V=await async function(A,e){let t,V=Math.abs(A),g=_.segmentIndex,n=A>0?_.nextEventIndex:_.nextEventIndex-2;for(;g>=0&&g<_.recording.segments.length;){if(e!==_.positionGeneration)return;cA([g-1,g,g+1]);const r=await EA(g,e);if(e!==_.positionGeneration)return;if(A>0){for(let A=Math.max(n,0);A=0;A--)if("o"===r.events[A][1]&&0===--V){t={time:r.events[A][0]};break}if(t)break;g--,n=Number.MAX_SAFE_INTEGER}}t&&(t.reachedEnd=t.time>=_.duration);return t}(A,t);if(void 0===V||t!==_.positionGeneration)return;if(CA(),!await KA(V.time,t,A<0))return;_.audioElement&&_.audioSeekable&&(_.audioElement.currentTime=V.time/1e3/g);V.reachedEnd?(z=D,e("ended")):z=u}(r.n)};case x:return A!==f?{nextState:A}:s?{nextState:u,action:()=>(GA(r.data),OA(r.time))}:{nextState:A,action:()=>GA(r.data)};case v:return{nextState:k,action:XA};default:return{nextState:A}}}function gA(A,e={}){X.push({event:A,payload:e})}function nA(A,e={}){const t=z,{nextState:V,action:g}=VA(t,A,e);return V!==z&&(z=V),g?.()}function rA(A){_.failureError||z===k||(X.length=0,_.segmentWaiting=!1,$A(),_.audioElement&&_.audioElement.pause(),_.failureError=A,z=y,e("error",t(A)))}function iA(){if(_.failureError)throw _.failureError;if(z===k)throw new Error("driver has been stopped")}function oA(A,e={}){return iA(),IA(A,e)}function IA(A,e={}){if(!_.failureError&&z!==k){if(P)throw new Error("re-entrant sendDriverEvent() is not allowed during queue processing");P=!0;try{const t=nA(A,e);for(;X.length>0;){const A=X.shift();nA(A.event,A.payload)}return t}catch(A){throw rA(A),A}finally{P=!1,X.length=0}}}function BA(A,e={}){return P?(gA(A,e),!0):IA(A,e)}function sA(){_.posterRenderableAfterLoad&&("npt"==I.type?TA(1e3*I.value,!1,!1):"text"==I.type&&bA(I.value),_.posterVisible=!0)}function CA(){_.posterVisible&&bA("c"),_.posterVisible=!1,_.posterRenderableAfterLoad=!1}function QA(A,e){_.segmentIndex=A,_.segment=e,_.nextEventIndex=0,_.lastEventTime=_.recording.segments[A].start}function aA(A,e=!1){let t=_.segmentCache.get(A);return void 0===t&&(t={},t.promise=_.recording.loadSegment(A).then((e=>(_.segmentCache.get(A)===t&&(t.data=e),e)),(g=>{throw _.segmentCache.get(A)===t&&_.segmentCache.delete(A),e||V.warn(`segment prefetch failed: ${g.message}`),g})),_.segmentCache.set(A,t),e||t.promise.catch((()=>{}))),t.promise}async function EA(A,e,t){void 0===_.segmentCache.get(A)?.data&&t?.();try{return await aA(A,!0)}catch(A){throw e===_.positionGeneration&&z!==k&&rA(A),A}}function cA(A){const e=new Set(A.filter((A=>A>=0)));for(const A of _.segmentCache.keys())e.has(A)||_.segmentCache.delete(A)}function lA(){const A=_.recording.segments.length-1,e=_.segmentIndex{IA(H,{time:e})}));if(t!==_.positionGeneration||z===k)return;QA(A,V),lA(),z===h||z===w?await IA(O):z===f&&fA()}catch{}}function dA(){if(ZA(),_.audioElement)return _.audioElement.play().then((()=>IA(K)),(A=>(IA(J),V.warn(`audio resume failed: ${A.message}`),!1)));gA(K)}function fA(){const A=_.segment.events[_.nextEventIndex];if(A)_.recordingEventTimeoutId=wA(hA,A[0]);else if(_.segmentIndex<_.recording.segments.length-1){const A=_.recording.segments[_.segmentIndex+1].start;_.recordingEventTimeoutId=wA(uA,A)}else BA(Y)}function wA(A,e){let t=(e-(j()-_.startTime))/g;return t<0&&(t=0),setTimeout(A,t)}function hA(){for(;void 0!==_.segment.events[_.nextEventIndex];){if(DA())return;const A=_.segment.events[_.nextEventIndex];if(void 0===A)break;if(j()-_.startTime<=A[0])break}fA()}function DA(){const A=_.segment.events[_.nextEventIndex];return"o"===A[1]?(function(){const A=_.segment.events[_.nextEventIndex],e=A[0]+$,t=[];let V=A;for(;void 0!==V&&"o"===V[1]&&V[0]Number.parseInt(A,10)));e("resize",JA(A,t))}else if("m"===V)return!0===IA(x,{data:g,time:t});return!1}function kA(){yA(),_.pauseElapsedTime=j()-_.startTime}function mA(){_.audioElement&&!_.audioCtx&&function(){_.audioCtx=new AudioContext({latencyHint:"interactive"});const A=_.audioCtx.createMediaElementSource(_.audioElement);A.connect(_.audioCtx.destination),j=RA}()}function qA(){_.startTime=j()-_.pauseElapsedTime,_.pauseElapsedTime=null,fA()}function LA(A){return iA(),function(A){if("number"==typeof A){if(Number.isFinite(A))return}else if("string"==typeof A){if(function(A){return"<<"===A||">>"===A||"<<<"===A||">>>"===A}(A)||void 0!==UA(A))return}else if("object"==typeof A&&null!==A&&("prev"===A.marker||"next"===A.marker||Number.isInteger(A.marker)&&A.marker>=0))return;throw new Error(`invalid seek target: ${JSON.stringify(A)}`)}(A),IA(R,z===E?{where:A}:{seekOperation:SA(z,A)})}function FA(A){if(0==_.markers.length)return;let e,t=0,V=_.markers[t];for(;V&&V[0]>"===g?g=t+5e3:"<<<"===g?g=t-.1*_.duration:">>>"===g?g=t+.1*_.duration:"%"===g[g.length-1]&&(g=UA(g)/100*_.duration);else if("object"==typeof g)if("prev"===g.marker)g=FA(t)??0,V&&t-g<1e3&&(g=FA(g)??0);else if("next"===g.marker)g=function(A){if(0==_.markers.length)return;let e,t=_.markers.length-1,V=_.markers[t];for(;V&&V[0]>A;)e=V[0],V=_.markers[--t];return e}(t)??_.duration;else if("number"==typeof g.marker){const A=_.markers[g.marker];if(void 0===A)throw new Error(`invalid marker index: ${g.marker}`);g=A[0]}const n=Math.min(Math.max(g,0),_.duration);return{targetTime:n,reachedEnd:n>=_.duration,noOp:n===_.pauseElapsedTime}}function JA(A,e){return{cols:C??A,rows:Q??e}}function YA(A,t=!1){t&&bA("c"),e("reset",{size:JA(A.snapshot.cols,A.snapshot.rows),init:A.snapshot.init,theme:_.recording.theme??null})}function TA(A,e=!0,t=!0){let V=_.segment.events[_.nextEventIndex],n=[];for(;V&&(t?V[0]<=A:V[0]0&&(bA(n),n=[]),pA(V)),_.lastEventTime=V[0],V=_.segment.events[++_.nextEventIndex];n.length>0&&bA(n),_.pauseElapsedTime=A,e&&(_.effectiveStartAt=null),_.audioElement&&_.audioSeekable&&(_.audioElement.currentTime=A/1e3/g)}async function KA(A,e,t=!1){const V=b(_.recording,A);if(e!==_.positionGeneration)return!1;if(!t&&V===_.segmentIndex&&A>=_.lastEventTime)return TA(A),!0;cA([V-1,V,V+1]);const g=await EA(V,e);return e===_.positionGeneration&&(QA(V,g),YA(g,!0),TA(A),!0)}async function HA(A){const e=_.positionGeneration;if(_.segmentIndex===_.recording.segments.length-1&&void 0===_.segment.events[_.nextEventIndex]){if(!await KA(0,e,!0))return!1}else if(null!==_.effectiveStartAt&&!await KA(_.effectiveStartAt,e))return!1;if(e!==_.positionGeneration)return!1;if(lA(),mA(),_.audioElement)try{return await _.audioElement.play(),IA(S,{reason:A})}catch(A){throw IA(J),A}return BA(S,{reason:A})}function OA(A){return _.audioElement&&_.audioElement.pause(),kA(),void 0!==A&&(_.pauseElapsedTime=A),e("pause"),!0}function xA(){clearTimeout(_.waitingTimeout),_.waitingTimeout=setTimeout((()=>{e("loading")}),1e3)}function ZA(){clearTimeout(_.waitingTimeout),_.waitingTimeout=null}function WA(){clearTimeout(_.loadingTimeout),_.loadingTimeout=null}function $A(){WA(),ZA(),yA()}async function jA(){yA(),_.playCount++;const A=++_.positionGeneration;try{const e=await EA(0,A,(()=>{gA(H,{time:_.duration})}));if(A!==_.positionGeneration)return;QA(0,e),YA(e,!0),_.pauseElapsedTime=0,_.startTime=j(),lA(),_.audioElement&&_.audioSeekable&&(_.audioElement.currentTime=0),z===h||z===w?await IA(O):(_.pauseElapsedTime=null,fA())}catch{}}function zA(){yA(),_.playCount++,_.pauseElapsedTime=_.duration,_.audioElement&&_.audioElement.pause(),cA([_.segmentIndex-1,_.segmentIndex]),e("ended")}function XA(){return _.positionGeneration++,_.segmentCache.clear(),$A(),async function(){clearTimeout(_.waitingTimeout),_.audioElement&&(_.audioElement.removeEventListener("playing",vA),_.audioElement.removeEventListener("waiting",NA),_.audioElement.pause(),_.audioElement.src="",_.audioElement.load(),_.audioElement=void 0),_.audioCtx&&(await _.audioCtx.close(),_.audioCtx=void 0)}()}return{init:function(){return oA(m)},stop:function(){return oA(v)},getDuration:function(){return void 0===_.duration?void 0:_.duration/1e3},getCurrentTime:function(){return MA()/1e3},play:function(){return oA(q)},pause:function(){return oA(M)},seek:LA,step:function(A){return oA(N,{n:A})},mute:function(){if(_.audioElement)return _.audioElement.muted=!0,e("muted",!0),!0},unmute:function(){if(_.audioElement)return _.audioElement.muted=!1,e("muted",!1),!0}}}],["websocket",function({url:A,bufferTime:e,reconnectDelay:t=z,minFrameTime:V},{dispatch:g,logger:n},{audioUrl:r}){let i,o;n=new d(n,"websocket: ");let I,B,s,C=new w,Q=0,a=!1,E=!1,c=!1,l=!1;function u(){let e;i=new WebSocket(A,["v1.alis","v2.asciicast","v3.asciicast","raw"]),i.binaryType="arraybuffer",i.onopen=()=>{e=i.protocol||"raw",n.info("opened"),n.info(`activating ${e} protocol handler`),"v1.alis"===e?i.onmessage=h(O(n)):"v2.asciicast"===e?i.onmessage=h(function(){let A=function(t){const V=JSON.parse(t);if(2!==V.version)throw new Error("not an asciicast v2 stream");return A=e,{time:0,term:{size:{cols:V.width,rows:V.height}}}};function e(A){const e=JSON.parse(A),t=1e3*e[0];if("r"===e[1]){const[A,V]=e[2].split("x");return[t,"r",{cols:parseInt(A,10),rows:parseInt(V,10)}]}return[t,e[1],e[2]]}return function(e){return A(e)}}()):"v3.asciicast"===e?i.onmessage=h(function(){let A=function(e){const V=JSON.parse(e);if(3!==V.version)throw new Error("not an asciicast v3 stream");A=t;const g={size:{cols:V.term.cols,rows:V.term.rows}};if(V.term.theme){const A="string"==typeof V.term.theme.palette?V.term.theme.palette.split(":"):void 0,e=q({foreground:V.term.theme.fg,background:V.term.theme.bg,palette:A});e&&(g.theme=e)}return{time:0,term:g}},e=0;function t(A){const t=JSON.parse(A),[V,g,n]=t;if(e+=1e3*V,"r"===g){const[A,t]=n.split("x");return[e,"r",{cols:parseInt(A,10),rows:parseInt(t,10)}]}return[e,g,n]}return function(e){return A(e)}}()):"raw"===e&&(i.onmessage=h(j())),I=setTimeout((()=>{Q=0}),1e3)},i.onclose=A=>{if(clearTimeout(B),p(),a)return;let V=!1,r="Stream ended";if("v1.alis"===e?(l||A.code>=4e3&&A.code<=4100)&&(V=!0,r=A.reason||r):(c||1e3===A.code||1005===A.code)&&(V=!0),V)n.info("closed"),g("ended",{message:r});else if(1002===A.code)n.debug(`close reason: ${A.reason}`),g("ended",{message:"Err: Player not compatible with the server"});else{clearTimeout(I);const A=t(Q++);n.info(`unexpected close, reconnecting in ${A}...`),g("loading"),setTimeout(u,A)}},E=!1}function h(A){return B=setTimeout(y,5e3),function(e){try{const t=A(e.data);if(o)if(Array.isArray(t))o.pushEvent(t),"x"===t[1]&&(c=!0);else if("string"==typeof t)o.pushText(t);else if("object"!=typeof t||Array.isArray(t)){if(!1===t)y(),l=!0;else if(void 0!==t)throw new Error(`unexpected value from protocol handler: ${t}`)}else D(t);else if("object"!=typeof t||Array.isArray(t)){if(void 0!==t)throw clearTimeout(B),new Error(`unexpected value from protocol handler: ${t}`);clearTimeout(B),B=setTimeout(y,1e3)}else D(t),clearTimeout(B)}catch(A){throw i.close(),A}}}function D({time:A,term:t}){const{size:r,init:i,theme:I}=t,{cols:B,rows:s}=r;n.info(`stream reset (${B}x${s} @${A})`),p(),o=T(e,g,(A=>C.setTime(A)),A,V,n),g("reset",{size:{cols:B,rows:s},init:i,theme:I??null}),C=new f,E=!0,c=!1,l=!1,"number"==typeof A&&C.setTime(A),g("playing")}function y(){p(),E?(n.info("stream ended"),g("offline",{message:"Stream ended"})):(n.info("stream offline"),g("offline",{message:"Stream offline"})),C=new w}function p(){o&&o.stop(),o=null}return{init:()=>{g("metadata",{hasAudio:!!r})},play:()=>(i||(g("play"),u(),r&&(s=new Audio,s.preload="auto",s.crossOrigin="anonymous",s.src=r,s.play())),!0),stop:()=>{a=!0,p(),void 0!==i&&i.close(),s&&s.pause()},mute:function(){if(s)return s.muted=!0,!0},unmute:function(){if(s)return s.muted=!1,!0},getCurrentTime:()=>{const A=C.getTime();return"number"==typeof A?A/1e3:A}}}]]),eA=new Map([["asciicast",G],["typescript",async function(A,{encoding:e}){const t=new TextDecoder(e);let V,g,n=(await A[0].text()).split("\n").filter((A=>A.length>0)).map((A=>A.split(" ")));n[0].length<3&&(n=n.map((A=>["O",A[0],A[1]])));const r=await A[1].arrayBuffer(),i=new Uint8Array(r),o=i.findIndex((A=>10==A))+1,I=t.decode(i.subarray(0,o)).match(/COLUMNS="(\d+)" LINES="(\d+)"/);null!==I&&(V=parseInt(I[1],10),g=parseInt(I[2],10));const B={array:i,cursor:o};let s=B;if(void 0!==A[2]){const e=await A[2].arrayBuffer();s={array:new Uint8Array(e),cursor:o}}const C=[];let Q=0;for(const A of n)if(Q+=1e3*parseFloat(A[1]),"O"===A[0]){const e=parseInt(A[2],10),V=B.array.subarray(B.cursor,B.cursor+e),g=t.decode(V);C.push([Q,"o",g]),B.cursor+=e}else if("I"===A[0]){const e=parseInt(A[2],10),V=s.array.subarray(s.cursor,s.cursor+e),g=t.decode(V);C.push([Q,"i",g]),s.cursor+=e}else if("S"===A[0]&&"SIGWINCH"===A[2]){const e=parseInt(A[4].slice(5),10),t=parseInt(A[3].slice(5),10);C.push([Q,"r",`${e}x${t}`])}else"H"===A[0]&&"COLUMNS"===A[2]?V=parseInt(A[3],10):"H"===A[0]&&"LINES"===A[2]&&(g=parseInt(A[3],10));return V=V??80,g=g??24,{cols:V,rows:g,events:C}}],["ttyrec",async function(A,{encoding:e}){const t=new TextDecoder(e),V=await A.arrayBuffer(),g=new Uint8Array(V),n=X(g),r=n.time,i=t.decode(n.data).match(/\x1b\[8;(\d+);(\d+)t/),o=[];let I=80,B=24;null!==i&&(I=parseInt(i[2],10),B=parseInt(i[1],10));let s=0,C=X(g);for(;void 0!==C;){const A=1e3*(C.time-r),e=t.decode(C.data);o.push([A,"o",e]),s+=C.len,C=X(g.subarray(s))}return{cols:I,rows:B,events:o}}]]);const tA=Symbol("solid-track"),VA={equals:(A,e)=>A===e};let gA=RA;const nA=1,rA=2,iA={owned:null,cleanups:null,context:null,owner:null};var oA=null;let IA=null,BA=null,sA=null,CA=null,QA=0;function aA(A,e){const t=BA,V=oA,g=0===A.length,n=void 0===e?V:e,r=g?iA:{owned:null,cleanups:null,context:n?n.context:null,owner:n},i=g?A:()=>A((()=>fA((()=>GA(r)))));oA=r,BA=null;try{return MA(i,!0)}finally{BA=t,oA=V}}function EA(A,e){const t={value:A,observers:null,observerSlots:null,comparator:(e=e?Object.assign({},VA,e):VA).equals||void 0};return[kA.bind(t),A=>("function"==typeof A&&(A=A(t.value)),mA(t,A))]}function cA(A,e,t){qA(LA(A,e,!1,nA))}function lA(A,e,t){gA=NA;const V=LA(A,e,!1,nA);V.user=!0,CA?CA.push(V):qA(V)}function uA(A,e,t){t=t?Object.assign({},VA,t):VA;const V=LA(A,e,!0,0);return V.observers=null,V.observerSlots=null,V.comparator=t.equals||void 0,qA(V),kA.bind(V)}function dA(A){return MA(A,!1)}function fA(A){if(null===BA)return A();const e=BA;BA=null;try{return A()}finally{BA=e}}function wA(A){lA((()=>fA(A)))}function hA(A){return null===oA||(null===oA.cleanups?oA.cleanups=[A]:oA.cleanups.push(A)),A}function DA(A){const e=BA,t=oA;return Promise.resolve().then((()=>{BA=e,oA=t,MA(A,!1),BA=oA=null}))}const[yA]=EA(!1);function pA(A){const e=uA(A),t=uA((()=>SA(e())));return t.toArray=()=>{const A=t();return Array.isArray(A)?A:null!=A?[A]:[]},t}function kA(){if(this.sources&&this.state)if(this.state===nA)qA(this);else{const A=sA;sA=null,MA((()=>vA(this)),!1),sA=A}if(BA){const A=this.observers?this.observers.length:0;BA.sources?(BA.sources.push(this),BA.sourceSlots.push(A)):(BA.sources=[this],BA.sourceSlots=[A]),this.observers?(this.observers.push(BA),this.observerSlots.push(BA.sources.length-1)):(this.observers=[BA],this.observerSlots=[BA.sources.length-1])}return this.value}function mA(A,e,t){let V=A.value;return A.comparator&&A.comparator(V,e)||(A.value=e,A.observers&&A.observers.length&&MA((()=>{for(let e=0;e1e6)throw sA=[],new Error}),!1)),e}function qA(A){if(!A.fn)return;GA(A);const e=QA;!function(A,e,t){let V;const g=oA,n=BA;BA=oA=A;try{V=A.fn(e)}catch(e){return A.pure&&(A.state=nA,A.owned&&A.owned.forEach(GA),A.owned=null),A.updatedAt=t+1,UA(e)}finally{BA=n,oA=g}(!A.updatedAt||A.updatedAt<=t)&&(null!=A.updatedAt&&"observers"in A?mA(A,V):A.value=V,A.updatedAt=t)}(A,A.value,e)}function LA(A,e,t,V=nA,g){const n={fn:A,state:V,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:e,owner:oA,context:oA?oA.context:null,pure:t};return null===oA||oA!==iA&&(oA.owned?oA.owned.push(n):oA.owned=[n]),n}function FA(A){if(0===A.state)return;if(A.state===rA)return vA(A);if(A.suspense&&fA(A.suspense.inFallback))return A.suspense.effects.push(A);const e=[A];for(;(A=A.owner)&&(!A.updatedAt||A.updatedAt=0;t--)if((A=e[t]).state===nA)qA(A);else if(A.state===rA){const t=sA;sA=null,MA((()=>vA(A,e[0])),!1),sA=t}}function MA(A,e){if(sA)return A();let t=!1;e||(sA=[]),CA?t=!0:CA=[],QA++;try{const e=A();return function(A){sA&&(RA(sA),sA=null);if(A)return;const e=CA;CA=null,e.length&&MA((()=>gA(e)),!1)}(t),e}catch(A){t||(CA=null),sA=null,UA(A)}}function RA(A){for(let e=0;e=0;e--)GA(A.tOwned[e]);delete A.tOwned}if(A.owned){for(e=A.owned.length-1;e>=0;e--)GA(A.owned[e]);A.owned=null}if(A.cleanups){for(e=A.cleanups.length-1;e>=0;e--)A.cleanups[e]();A.cleanups=null}A.state=0}function UA(A,e=oA){const t=function(A){return A instanceof Error?A:new Error("string"==typeof A?A:"Unknown error",{cause:A})}(A);throw t}function SA(A){if("function"==typeof A&&!A.length)return SA(A());if(Array.isArray(A)){const e=[];for(let t=0;tA(e||{})))}const KA=A=>`Stale read from <${A}>.`;function HA(A){const e="fallback"in A&&{fallback:()=>A.fallback};return uA(function(A,e,t={}){let V=[],g=[],n=[],r=0,i=e.length>1?[]:null;return hA((()=>YA(n))),()=>{let o,I,B=A()||[],s=B.length;return B[tA],fA((()=>{let A,e,Q,a,E,c,l,u,d;if(0===s)0!==r&&(YA(n),n=[],V=[],g=[],r=0,i&&(i=[])),t.fallback&&(V=[JA],g[0]=aA((A=>(n[0]=A,t.fallback()))),r=1);else if(0===r){for(g=new Array(s),I=0;I=c&&u>=c&&V[l]===B[u];l--,u--)Q[u]=g[l],a[u]=n[l],i&&(E[u]=i[l]);for(A=new Map,e=new Array(u+1),I=u;I>=c;I--)d=B[I],o=A.get(d),e[I]=void 0===o?-1:o,A.set(d,I);for(o=c;o<=l;o++)d=V[o],I=A.get(d),void 0!==I&&-1!==I?(Q[I]=g[o],a[I]=n[o],i&&(E[I]=i[o]),I=e[I],A.set(d,I)):n[o]();for(I=c;IA.each),A.children,e||void 0))}function OA(A){const e=A.keyed,t=uA((()=>A.when),void 0,void 0),V=e?t:uA(t,void 0,{equals:(A,e)=>!A==!e});return uA((()=>{const g=V();if(g){const n=A.children;return"function"==typeof n&&n.length>0?fA((()=>n(e?g:()=>{if(!fA(V))throw KA("Show");return t()}))):n}return A.fallback}),void 0,void 0)}function xA(A){const e=pA((()=>A.children)),t=uA((()=>{const A=e(),t=Array.isArray(A)?A:[A];let V=()=>{};for(let A=0;An()?void 0:g.when),void 0,void 0),i=g.keyed?r:uA(r,void 0,{equals:(A,e)=>!A==!e});V=()=>n()||(i()?[e,r,g]:void 0)}return V}));return uA((()=>{const e=t()();if(!e)return A.fallback;const[V,g,n]=e,r=n.children;return"function"==typeof r&&r.length>0?fA((()=>r(n.keyed?g():()=>{if(fA(t)()?.[0]!==V)throw KA("Match");return g()}))):r}),void 0,void 0)}function ZA(A){return A}const WA="_$DX_DELEGATE";function $A(A,e,t,V){let g;const n=()=>{const e=document.createElement("template");return e.innerHTML=A,e.content.firstChild},r=e?()=>fA((()=>document.importNode(g||(g=n()),!0))):()=>(g||(g=n())).cloneNode(!0);return r.cloneNode=r,r}function jA(A,e=window.document){const t=e[WA]||(e[WA]=new Set);for(let V=0,g=A.length;VA(e,t)))}function ee(A,e,t,V){if(void 0===t||V||(V=[]),"function"!=typeof e)return Ve(A,e,V,t);cA((V=>Ve(A,e(),V,t)),V)}function te(A){let e=A.target;const t=`$$${A.type}`,V=A.target,g=A.currentTarget,n=e=>Object.defineProperty(A,"target",{configurable:!0,value:e}),r=()=>{const V=e[t];if(V&&!e.disabled){const g=e[`${t}Data`];if(void 0!==g?V.call(e,g,A):V.call(e,A),A.cancelBubble)return}return e.host&&"string"!=typeof e.host&&!e.host._$host&&e.contains(A.target)&&n(e.host),!0},i=()=>{for(;r()&&(e=e._$host||e.parentNode||e.host););};if(Object.defineProperty(A,"currentTarget",{configurable:!0,get:()=>e||document}),A.composedPath){const t=A.composedPath();n(t[0]);for(let A=0;A{let g=e();for(;"function"==typeof g;)g=g();t=Ve(A,g,t,V)})),()=>t;if(Array.isArray(e)){const n=[],i=t&&Array.isArray(t);if(ge(n,e,t,g))return cA((()=>t=Ve(A,n,t,V,!0))),()=>t;if(0===n.length){if(t=re(A,t,V),r)return t}else i?0===t.length?ne(A,n,V):function(A,e,t){let V=t.length,g=e.length,n=V,r=0,i=0,o=e[g-1].nextSibling,I=null;for(;rV-i){const g=e[r];for(;i=0;n--){const r=e[n];if(g!==r){const e=r.parentNode===A;V||n?e&&r.remove():e?A.replaceChild(g,r):A.insertBefore(g,t)}else V=!0}}else A.insertBefore(g,t);return[g]}const ie=()=>{},oe=(A,e)=>e();function Ie(A,e){const t=fA(A),V=t?[t]:[],{onEnter:g=oe,onExit:n=oe}=e,[r,i]=EA(e.appear?[]:V),[o]=[yA,DA];let I,B=!1;function s(A,e){if(!A)return e&&e();B=!0,n(A,(()=>{dA((()=>{B=!1,i((e=>e.filter((e=>e!==A)))),e&&e()}))}))}function C(A){const e=I;if(!e)return A&&A();I=void 0,i((A=>[e,...A])),g(e,A??ie)}const Q="out-in"===e.mode?A=>B||s(A,C):"in-out"===e.mode?A=>C((()=>s(A))):A=>{s(A),C()};var a,E;return a=e=>{const t=A();return fA(o)?(o(),e):(t!==e&&(I=t,dA((()=>fA((()=>Q(e)))))),t)},E=e.appear?void 0:t,qA(LA(a,E,!0,nA)),r}const Be=A=>A instanceof Element;function se(A,e){if(e(A))return A;if("function"==typeof A&&!A.length)return se(A(),e);if(Array.isArray(A))for(const t of A){const A=se(t,e);if(A)return A}return null}function Ce(A,e=Be,t=Be){const V=uA(A);return uA((()=>se(V(),e)))}function Qe(A){requestAnimationFrame((()=>requestAnimationFrame(A)))}var ae={inout:"in-out",outin:"out-in"},Ee=A=>{const e=function(A){return uA((()=>{const e=A.name||"s";return{enterActive:(A.enterActiveClass||e+"-enter-active").split(" "),enter:(A.enterClass||e+"-enter").split(" "),enterTo:(A.enterToClass||e+"-enter-to").split(" "),exitActive:(A.exitActiveClass||e+"-exit-active").split(" "),exit:(A.exitClass||e+"-exit").split(" "),exitTo:(A.exitToClass||e+"-exit-to").split(" "),move:(A.moveClass||e+"-move").split(" ")}}))}(A);return Ie(Ce((()=>A.children)),{mode:ae[A.mode],appear:A.appear,onEnter(t,V){!function(A,e,t,V){const{onBeforeEnter:g,onEnter:n,onAfterEnter:r}=e;function i(e){e&&e.target!==t||(V?.(),t.removeEventListener("transitionend",i),t.removeEventListener("animationend",i),t.classList.remove(...A.enterActive),t.classList.remove(...A.enterTo),r?.(t))}g?.(t),t.classList.add(...A.enter),t.classList.add(...A.enterActive),queueMicrotask((()=>{if(!t.parentNode)return V?.();n?.(t,(()=>i()))})),Qe((()=>{t.classList.remove(...A.enter),t.classList.add(...A.enterTo),(!n||n.length<2)&&(t.addEventListener("transitionend",i),t.addEventListener("animationend",i))}))}(e(),A,t,V)},onExit(t,V){!function(A,e,t,V){const{onBeforeExit:g,onExit:n,onAfterExit:r}=e;if(!t.parentNode)return V?.();function i(e){e&&e.target!==t||(V?.(),t.removeEventListener("transitionend",i),t.removeEventListener("animationend",i),t.classList.remove(...A.exitActive),t.classList.remove(...A.exitTo),r?.(t))}g?.(t),t.classList.add(...A.exit),t.classList.add(...A.exitActive),n?.(t,(()=>i())),Qe((()=>{t.classList.remove(...A.exit),t.classList.add(...A.exitTo),(!n||n.length<2)&&(t.addEventListener("transitionend",i),t.addEventListener("animationend",i))}))}(e(),A,t,V)}})};let ce;function le(A){qe===me.length&&me.push(me.length+1);const e=qe;return qe=me[e],me[e]=A,e}function ue(A){const e=typeof A;if("number"==e||"boolean"==e||null==A)return`${A}`;if("string"==e)return`"${A}"`;if("symbol"==e){const e=A.description;return null==e?"Symbol":`Symbol(${e})`}if("function"==e){const e=A.name;return"string"==typeof e&&e.length>0?`Function(${e})`:"Function"}if(Array.isArray(A)){const e=A.length;let t="[";e>0&&(t+=ue(A[0]));for(let V=1;V1))return toString.call(A);if(V=t[1],"Object"==V)try{return"Object("+JSON.stringify(A)+")"}catch(A){return"Object"}return A instanceof Error?`${A.name}: ${A.message}\n${A.stack}`:V}function de(A,e){return A>>>=0,function(){null!==De&&0!==De.byteLength||(De=new Uint32Array(ce.memory.buffer));return De}().subarray(A/4,A/4+e)}let fe=null;function we(){return(null===fe||!0===fe.buffer.detached||void 0===fe.buffer.detached&&fe.buffer!==ce.memory.buffer)&&(fe=new DataView(ce.memory.buffer)),fe}function he(A,e){return function(A,e){Ne+=e,Ne>=Re&&(Me=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),Me.decode(),Ne=e);return Me.decode(pe().subarray(A,A+e))}(A>>>=0,e)}let De=null;let ye=null;function pe(){return null!==ye&&0!==ye.byteLength||(ye=new Uint8Array(ce.memory.buffer)),ye}function ke(A){return me[A]}let me=new Array(128).fill(void 0);me.push(void 0,null,!0,!1);let qe=me.length;function Le(A,e,t){if(void 0===t){const t=ve.encode(A),V=e(t.length,1)>>>0;return pe().subarray(V,V+t.length).set(t),be=t.length,V}let V=A.length,g=e(V,1)>>>0;const n=pe();let r=0;for(;r127)break;n[g+r]=e}if(r!==V){0!==r&&(A=A.slice(r)),g=t(g,V,V=r+3*A.length,1)>>>0;const e=pe().subarray(g+r,g+V);r+=ve.encodeInto(A,e).written,g=t(g,V,r,1)>>>0}return be=r,g}function Fe(A){const e=ke(A);return function(A){A<132||(me[A]=qe,qe=A)}(A),e}let Me=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});Me.decode();const Re=2146435072;let Ne=0;const ve=new TextEncoder;"encodeInto"in ve||(ve.encodeInto=function(A,e){const t=ve.encode(A);return e.set(t),{read:A.length,written:t.length}});let be=0;const Ge="undefined"==typeof FinalizationRegistry?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry((A=>ce.__wbg_vt_free(A>>>0,1)));let Ue=class A{static __wrap(e){e>>>=0;const t=Object.create(A.prototype);return t.__wbg_ptr=e,Ge.register(t,t.__wbg_ptr,t),t}__destroy_into_raw(){const A=this.__wbg_ptr;return this.__wbg_ptr=0,Ge.unregister(this),A}free(){const A=this.__destroy_into_raw();ce.__wbg_vt_free(A,0)}feed(A){const e=Le(A,ce.__wbindgen_export,ce.__wbindgen_export2),t=be;return Fe(ce.vt_feed(this.__wbg_ptr,e,t))}resize(A,e){return Fe(ce.vt_resize(this.__wbg_ptr,A,e))}getSize(){try{const V=ce.__wbindgen_add_to_stack_pointer(-16);ce.vt_getSize(V,this.__wbg_ptr);var A=we().getInt32(V+0,!0),e=we().getInt32(V+4,!0),t=de(A,e).slice();return ce.__wbindgen_export3(A,4*e,4),t}finally{ce.__wbindgen_add_to_stack_pointer(16)}}getLine(A,e){return Fe(ce.vt_getLine(this.__wbg_ptr,A,e))}getCursor(){return Fe(ce.vt_getCursor(this.__wbg_ptr))}};Symbol.dispose&&(Ue.prototype[Symbol.dispose]=Ue.prototype.free);const Se=new Set(["basic","cors","default"]);function Je(){const A={wbg:{}};return A.wbg.__wbg___wbindgen_debug_string_adfb662ae34724b6=function(A,e){const t=Le(ue(ke(e)),ce.__wbindgen_export,ce.__wbindgen_export2),V=be;we().setInt32(A+4,V,!0),we().setInt32(A+0,t,!0)},A.wbg.__wbg___wbindgen_throw_dd24417ed36fc46e=function(A,e){throw new Error(he(A,e))},A.wbg.__wbg_new_13317ed16189158e=function(){return le(new Array)},A.wbg.__wbg_new_4ceb6a766bf78b04=function(){return le(new Object)},A.wbg.__wbg_set_3f1d0b984ed272ed=function(A,e,t){ke(A)[Fe(e)]=Fe(t)},A.wbg.__wbg_set_8b6a9a61e98a8881=function(A,e,t){ke(A)[e>>>0]=Fe(t)},A.wbg.__wbindgen_cast_2241b6af4c4b2941=function(A,e){return le(he(A,e))},A.wbg.__wbindgen_cast_4625c577ab2ec9ee=function(A){return le(BigInt.asUintN(64,A))},A.wbg.__wbindgen_cast_d6cd19b81560fd6e=function(A){return le(A)},A.wbg.__wbindgen_object_clone_ref=function(A){return le(ke(A))},A.wbg.__wbindgen_object_drop_ref=function(A){Fe(A)},A}function Ye(A,e){return ce=A.exports,Te.__wbindgen_wasm_module=e,fe=null,De=null,ye=null,ce}async function Te(A){if(void 0!==ce)return ce;void 0!==A&&(Object.getPrototypeOf(A)===Object.prototype?({module_or_path:A}=A):console.warn("using deprecated parameters for the initialization function; pass a single object instead"));const e=Je();("string"==typeof A||"function"==typeof Request&&A instanceof Request||"function"==typeof URL&&A instanceof URL)&&(A=fetch(A));const{instance:t,module:V}=await async function(A,e){if("function"==typeof Response&&A instanceof Response){if("function"==typeof WebAssembly.instantiateStreaming)try{return await WebAssembly.instantiateStreaming(A,e)}catch(e){if(!A.ok||!Se.has(A.type)||"application/wasm"===A.headers.get("Content-Type"))throw e;console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",e)}const t=await A.arrayBuffer();return await WebAssembly.instantiate(t,e)}{const t=await WebAssembly.instantiate(A,e);return t instanceof WebAssembly.Instance?{instance:t,module:A}:t}}(await A,e);return Ye(t,V)}var Ke=Object.freeze({__proto__:null,Vt:Ue,create:function(A,e,t,V){const g=ce.create(A,e,t,V);return Ue.__wrap(g)},default:Te,initSync:function(A){if(void 0!==ce)return ce;void 0!==A&&(Object.getPrototypeOf(A)===Object.prototype?({module:A}=A):console.warn("using deprecated parameters for `initSync()`; pass a single object instead"));const e=Je();return A instanceof WebAssembly.Module||(A=new WebAssembly.Module(A)),Ye(new WebAssembly.Instance(A,e),A)}});const He=[62,0,0,0,63,52,53,54,55,56,57,58,59,60,61,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51];function Oe(A){return He[A-43]}var xe=function(A){let e,t=A.endsWith("==")?2:A.endsWith("=")?1:0,V=A.length,g=new Uint8Array(V/4*3);for(let t=0,n=0;t>16,g[n+1]=e>>8&255,g[n+2]=255&e;return g.subarray(0,g.length-t)}("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ACABLwEIOwECDCQLIABBAToAACAAIAEvAQg7AQIMIwsgAEECOgAAIAAgAS8BCDsBAgwiCyAAQQo6AAAgACABLwEIOwECDCELIABBIjoAACAAIAEvAQg7AQIMIAsgAEEvOgAAIAAgAS8BCDsBAgwfCyAAQTA6AAAgACABLwEIOwECDB4LIABBCzoAACAAIAEvARg7AQQgACABLwEIOwECDB0LIAEvAQgOBBQTExUTCyADIAQgASgChARBhNzAABBtIANBQGsiASADKAIAIgIgAiADKAIEQQR0ahAmIANBO2ogAUEIaigCADYAACADIAMpAkA3ADMgAEErOgAAIAAgAykAMDcAASAAQQhqIANBN2opAAA3AAAMGwsgA0EIaiAEIAEoAoQEQZTcwAAQbSADQUBrIgEgAygCCCICIAIgAygCDEEEdGoQJiADQTtqIAFBCGooAgA2AAAgAyADKQJANwAzIABBJToAACAAIAMpADA3AAEgAEEIaiADQTdqKQAANwAADBoLIANBGGogBCABKAKEBEGk3MAAEG0gAyADKQMYNwJMIANB1gBqIANBzABqEAsCfyADLQBWQRJGBEBBACEBQQAhBEEBDAELIANBEGpBBEEBQQVBlMjAABBXIANB2gBqLQAAIQEgAygCECECIAMoAhQiBCADKABWNgAAIARBBGogAToAACADQQE2AjggAyAENgI0IAMgAjYCMCADIAMpAkw3AkBBBSECQQEhAQNAIANB2wBqIANBQGsQCyADLQBbQRJGRQRAIAMoAjAgAUYEQCADQTBqIAFBAUEBQQUQZCADKAI0IQQLIAIgBGoiBSADKABbNgAAIAVBBGogA0HfAGotAAA6AAAgAyABQQFqIgE2AjggAkEFaiECDAELCyADKAIwIQQgAygCNAshAiAAIAE2AgwgACACNgIIIAAgBDYCBCAAQSk6AAAMGQsgAEETOgAAIAAgAS8BGDsBBCAAIAEvAQg7AQIMGAsgAEEnOgAADBcLIABBJjoAAAwWCyAAQTI6AAAMFQsgAEEXOwEADBQLIABBlwI7AQAMEwsgAEGXBDsBAAwSCyAAQZcGOwEADBELIABBMjoAAAwQCyAAQRg7AQAMDwsgAEGYAjsBAAwOCyAAQZgEOwEADA0LIABBMjoAAAwMCyAAQQc7AQAMCwsgAEGHAjsBAAwKCyAAQYcEOwEADAkLIABBMjoAAAwICyAAQS47AQAMBwsgAEGuAjsBAAwGCyABLwEIQQhGDQMgAEEyOgAADAULIAVBIUcNAyAAQRQ6AAAMBAsgBUE/Rw0CIANBIGogBCABKAKEBEG03MAAEG0gA0FAayIBIAMoAiAiAiACIAMoAiRBBHRqECcgA0E7aiABQQhqKAIANgAAIAMgAykCQDcAMyAAQRI6AAAgACADKQAwNwABIABBCGogA0E3aikAADcAAAwDCyAFQT9HDQEgA0EoaiAEIAEoAoQEQcTcwAAQbSADQUBrIgEgAygCKCICIAIgAygCLEEEdGoQJyADQTtqIAFBCGooAgA2AAAgAyADKQJANwAzIABBEDoAACAAIAMpADA3AAEgAEEIaiADQTdqKQAANwAADAILIABBMToAACAAIAEvARg7AQQgACABLwEoOwECDAELIABBMjoAAAsgA0HgAGokAAvaCwIPfwJ+IwBB0ABrIgIkACABQQRqIQwgAkFAayENIAJBJWohDiACQRxqIQ8gASgCJCEFIAEoAhQhECABKAIQIQMCQAJAAn8CQANAIAEoAgAhBiABQYCAgIB4NgIAIAEoAgQhCwJAAkACQAJAAkAgBkGAgICAeEcEQCABKQIIIREgCyEHDAELAkAgAyAQRgRAQYCAgIB4IQYMAQsgASADQRBqIgg2AhAgAykCCCERIAMoAgQhByADKAIAIQYgCCEDC0GAgICAeCALEJgBIAZBgICAgHhGDQELIAIgBzYCDCACIAY2AgggAiARNwIQIBFCIIghEkF/IAUgEaciBEcgBCAFSxtB/wFxDgICAwELQYCAgIB4IAcQmAEgAEGAgICAeDYCACABQYCAgIB4NgIADAcLAkAgEqdBAXENACAFIAQgByAEEDBrIgMgAyAFSRsiAyAESw0AIAIgAzYCECADIQQLAn9BgICAgHggBCAFTQ0AGgJAAkAgByAEIAVBuNrAABCGASgCBEUEQCACQThqIgMgAkEIaiIIIAVBAWsQOSACQTBqIANBCGooAgA2AgAgAiACKQI4NwMoIAItABQhBCADQRBqIAIoAgwgAigCECIHIAdBAWtB2NrAABCGASIHQRBqLwEAOwEAIAJCoICAgBA3AjggAiAHKQIINwJAIAggA0Ho2sAAEE4gAiAEOgA0IAItABRBAXFFDQEMAgsgAkE4aiIDIAJBCGogBRA5IAJBMGogA0EIaigCADYCACACIAIpAjg3AyggAiACLQAUIgM6ADQgAw0BCyACQShqEIEBCyACKAIwBEAgAkFAayACQTRqKAIANgIAIAJBAToAFCACIAIpAiw3AzggAigCKAwBCyACKAIoIAIoAixBFBCVAUGAgICAeAshA0GAgICAeCALEJgBIAEgAzYCACAMIAIpAzg3AgAgDEEIaiACQUBrKAIANgIAIABBCGogAkEQaikCADcCACAAIAIpAgg3AgAMBgsgACARNwIIIAAgBzYCBCAAIAY2AgAMBQsCQCADIBBHBEAgASADQRBqIgg2AhAgAygCACIGQYCAgIB4Rw0BCyACQQA7AEAgAkECOgA8IAJBAjoAOCACQQhqIgEgBSACQThqEDogACACKQIINwIAIAJBADoAFCAAQQhqIAFBCGopAgA3AgAMBQsgA0EMaigCACEJIA8gAykCBDcCACAPQQhqIAk2AgAgAiAGNgIYIAUgBGsiCUUNASASp0EBcUUEQCACQQA7AEAgAkECOgA8IAJBAjoAOCACQQhqIAUgAkE4ahA6DAILIAItACRFBEAgAkEYahCBAQsgAigCHCEDIAIoAiAiCiAJTQRAIAJBCGoiBCADIAoQdgJAIAItACQiBg0AIAJBADoAFCACKAIQIAVPDQAgAkEAOwBAIAJBAjoAPCACQQI6ADggBCAFIAJBOGoQOgsgAigCGCADQRQQlQEgBkUNBEGAgICAeCALEJgBIAFBCGogAkEQaikCADcCACABIAIpAgg3AgBBgICAgHggAhCYASAIIQMMAQsLIAMgCiAJQfjZwAAQhgEoAgRFBEAgDUEIaiAHIAQgBEEBa0GI2sAAEIYBIghBEGovAQA7AQAgDSAIKQIINwIAIAJCoICAgBA3AjggAkEIaiACQThqQZjawAAQTiAJQQFrIQkLIAkgCk0EQCACQQhqIAMgCRB2IAIoAhghBiADIAogCRB+IAZBgICAgHhGDQMgCiAKIAlrIgggCCAKSxshBCACLQAkDAILIAkgCkGo2sAAEKIBAAsgAkEqaiAOQQJqLQAAOgAAIAIgDi8AADsBKCACKAIgIQQgAigCHCEDIAItACQLIQhBgICAgHggCxCYASABIAg6AAwgASAENgIIIAEgAzYCBCABIAY2AgAgASACLwEoOwAN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AiAAEHogAiADEHogASgCGAVBgAELIAEoAgggASgCDBCXASABQSBqJAALpwEBAn8jAEEgayICJAAgAiAAKAJoNgIMIAJBADoAHCACIAAoAlQiAzYCECACIAMgACgCWEECdGo2AhQgAiACQQxqNgIYIAACfwJAAkADQCABQQFrIgEEQCACQRBqEEANAQwCCwsgAkEQahBAIgENAQsgACgCnAEiA0EBayIADAELIAAoApwBIgNBAWshACABKAIACyIBIAAgASADSRs2AmggAkEgaiQAC6MBAQF/IwBBQGoiAyQAIANBHGogABBVIAMoAhwiACABIAIQIyADQShqIABB4ABqKAIAIABB5ABqKAIAECAgA0EQaiAAECEgAyADKQMQNwI0IANBCGogAygCLCADKAIwEFIgAygCDCEAIAMoAghBAXEEQCADIAA2AjwgA0E8akHswsAAEDwACyADQShqEGwgAygCICADKAIkEKEBIANBQGskACAAC5kBAQN/IAFBbGwhAiABQf////8DcSEDIAAgAUEUbGohAUEAIQACQANAIAJFDQECQCABQRRrIgQoAgBBIEcNACABQRBrKAIAQQFHDQAgAUEMay0AAEECRw0AIAFBCGstAABBAkcNACABQQRrLQAADQAgAUEDay0AAEEfcQ0AIAJBFGohAiAAQQFqIQAgBCEBDAELCyAAIQMLIAMLoAEBA38jAEEQayIFJAAgBUEIaiAAIAEgAkHgzsAAEF8gBSgCDCIGIAMgAiABayIHIAMgB0kbIgNPBEAgBiADayIGIAUoAgggBkEEdGogAxATIAAgASABIANqIAQQKCABBEAgACABQQFrQfDOwAAQWkEAOgAMCyAAIAJBAWtBgM/AABBaQQA6AAwgBUEQaiQADwtBzMnAAEEhQfDJwAAQZwALiwEBAn8jAEEQayICJAAgAkKAgICAwAA3AgQgAkEANgIMIAFBCGsiA0EAIAEgA08bIgFBA3YgAUEHcUEAR2ohAUEIIQMDQCABBEAgAkEEaiADQaziwAAQcSABQQFrIQEgA0EIaiEDDAEFIAAgAikCBDcCACAAQQhqIAJBDGooAgA2AgAgAkEQaiQACwsLjQEBBH8gASAAKAIAIAAoAggiBGtLBEAgACAEIAFBAUEBEGQgACgCCCEECyAAKAIEIARqIQVBASABIAFBAU0bIgZB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dGo2AgALRAEBfyABKAIAIgIgASgCBEYEQCAAQYCAgIB4NgIADwsgASACQRBqNgIAIAAgAikCADcCACAAQQhqIAJBCGopAgA3AgALOwEDfwNAIAJBFEZFBEAgACACaiIDKAIAIQQgAyABIAJqIgMoAgA2AgAgAyAENgIAIAJBBGohAgwBCwsLOwEDfwNAIAJBJEZFBEAgACACaiIDKAIAIQQgAyABIAJqIgMoAgA2AgAgAyAENgIAIAJBBGohAgwBCwsLQAECfyAAKAIAIAAoAgRBBBCVASAAKAIMIQEgACgCECIAKAIAIgIEQCABIAIRAgALIAAoAgQiAARAIAEgABA7Cws6AQF/AkAgAkF/RwRAIAJBAWohBCACQSBJDQEgBEEgIAMQogEACyADEHMACyAAIAQ2AgQgACABNgIACzgAAkAgAWlBAUcNAEGAgICAeCABayAASQ0AIAAEQEH4qsEALQAAGiABIAAQFSIBRQ0BCyABDwsACy0BAX8gASAAKAIATwR/IAAoAgQhAiAALQAIRQRAIAEgAk0PCyABIAJJBUEACwtwAQN/IAAoAgAiBCAAKAIIIgVGBEAjAEEQayIDJAAgA0EIaiAAIARBAUECQQgQJCADKAIIIgRBgYCAgHhHBEAgAygCDBogBCACEJ0BAAsgA0EQaiQACyAAIAVBAWo2AgggACgCBCAFQQN0aiABNwEACzQBAX8gACgCCCIDIAAoAgBGBEAgACACEGILIAAgA0EBajYCCCAAKAIEIANBAnRqIAE2AgALLgEBfyMAQRBrIgIkACACQQhqIAEgABCCASACKAIIIgAEQCACQRBqJAAgAA8LAAs3AQF/IwBBIGsiASQAIAFBADYCGCABQQE2AgwgAUGM6cAANgIIIAFCBDcCECABQQhqIAAQgAEACyUAIABBgQJPBEAgAEEeIABnIgBrdiAAQQF0a0E8ag8LIABBBHYLKwAgAiADSQRAIAMgAiAEEKMBAAsgACACIANrNgIEIAAgASADQRRsajYCAAsvAQF/IAAgAhCEASAAKAIEIAAoAggiA0EUbGogASACQRRsEBQaIAAgAiADajYCCAsrACABIANLBEAgASADIAQQowEACyAAIAMgAWs2AgQgACACIAFBBHRqNgIACy8AAkACQCADaUEBRw0AQYCAgIB4IANrIAFJDQAgACABIAMgAhAiIgANAQsACyAACywAA0AgAQRAIAAoAgAgAEEEaigCAEEUEJUBIAFBAWshASAAQRBqIQAMAQsLCzIBAX8gACgCCCECIAEgACgCAEECai0AABCRASEBIAAoAgQgAiABEAEgACACQQFqNgIICyoAIAAgACgCaCABaiIBIAAoApwBIgBBAWsgACABSxtBACABQQBOGzYCaAszAQJ/IAAgACgCqAEiAiAAKAKsAUEBaiIDIAEgAEGyAWoQMSAAKAJgIAAoAmQgAiADEEoLMwECfyAAIAAoAqgBIgIgACgCrAFBAWoiAyABIABBsgFqEBogACgCYCAAKAJkIAIgAxBKCyoAIAEgAkkEQEGkyMAAQSNBvMnAABBnAAsgAiAAIAJBFGxqIAEgAmsQFws1ACAAIAApAnQ3AmggACAAKQF8NwGyASAAIAAvAYYBOwG+ASAAQboBaiAAQYQBai8BADsBAAvsAQICfwF+IwBBEGsiAiQAIAJBATsBDCACIAE2AgggAiAANgIEIwBBEGsiASQAIAJBBGoiACkCACEEIAEgADYCDCABIAQ3AgQjAEEQayIAJAAgAUEEaiIBKAIAIgIoAgwhAwJAAkACQAJAIAIoAgQOAgABAgsgAw0BQQEhAkEAIQMMAgsgAw0AIAIoAgAiAigCBCEDIAIoAgAhAgwBCyAAQYCAgIB4NgIAIAAgATYCDCABKAIIIgEtAAkaIABBFSABLQAIEEEACyAAIAM2AgQgACACNgIAIAEoAggiAS0ACRogAEEWIAEtAAgQQQALKwECfwJAIAAoAgQgACgCCCIBEDAiAkUNACABIAJJDQAgACABIAJrNgIICwsmACACBEBB+KrBAC0AABogASACEBUhAQsgACACNgIEIAAgATYCAAsjAQF/IAEgACgCACAAKAIIIgJrSwRAIAAgAiABQQRBEBBkCwsjAQF/IAEgACgCACAAKAIIIgJrSwRAIAAgAiABQQRBFBBkCwslACAAQQE2AgQgACABKAIEIAEoAgBrQQR2IgE2AgggACABNgIACxsAIAEgAk0EQCACIAEgAxBCAAsgACACQRRsagsgACABIAJNBEAgAiABQeTjwAAQQgALIAAgAmpBAToAAAsbACABIAJNBEAgAiABIAMQQgALIAAgAkEEdGoLAwAACwMAAAsDAAALAwAACwMAAAsDAAALGgBB+KrBAC0AABpBBCAAEBUiAARAIAAPCwALAwAACxYAIAFBAXFFBEAgALgQBw8LIACtEAgLRAEBfyAAIAAoAgBBAWsiATYCACABRQRAIABBDGoQRwJAIABBf0YNACAAIAAoAgRBAWsiATYCBCABDQAgAEGcBhA7CwsLHgEBfyAAKAIQIgEgACgCFBB5IAAoAgwgAUEQEJUBCx4BAX8gACgCBCIBIAAoAggQeSAAKAIAIAFBEBCVAQsQACAABEAgASAAIAJsEDsLCxYAIABBEGoQTyAAKAIAIAAoAgQQmAELFAAgACAAKAIAQQFrNgIAIAEQkgELFwAgAEGAgICAeEcEQCAAIAFBFBCVAQsLEwAgAARADwtBpKnBAEEbEKgBAAsPACAAQYQBTwRAIAAQAgsLDQAgAQRAIAAgARA7CwsNACABBEAgACABEDsLCzwAIABFBEAjAEEgayIAJAAgAEEANgIYIABBATYCDCAAQdDEwAA2AgggAEIENwIQIABBCGogARCAAQALAAsRACAAKAIIIAAoAgBBAhCVAQsUACAAQQA2AgggAEKAgICAEDcCAAsSACAAIAFBkM3AABBaQQE6AAwLDgAgAEEANgIAIAEQkgELawEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBAjYCDCADQejpwAA2AgggA0ICNwIUIAMgA0EEaq1CgICAgIABhDcDKCADIAOtQoCAgICAAYQ3AyAgAyADQSBqNgIQIANBCGogAhCAAQALawEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBAjYCDCADQcjpwAA2AgggA0ICNwIUIAMgA0EEaq1CgICAgIABhDcDKCADIAOtQoCAgICAAYQ3AyAgAyADQSBqNgIQIANBCGogAhCAAQALawEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANBAjYCDCADQZzqwAA2AgggA0ICNwIUIAMgA0EEaq1CgICAgIABhDcDKCADIAOtQoCAgICAAYQ3AyAgAyADQSBqNgIQIANBCGogAhCAAQALDgBB8OXAAEErIAAQZwALCwAgACMAaiQAIwALDgBBv6nBAEHPABCoAQALCQAgACABEAYACwwAIAAgASkCADcDAAsKACAAKAIAEJoBCw0AIABBgICAgHg2AgALCQAgAEEANgIACwYAIAAQTwsL0m0gAEGAgMAAC0AXAAAABAAAAAQAAAAYAAAAY2FsbGVkIGBSZXN1bHQ6OnVud3JhcCgpYCBvbiBhbiBgRXJyYCB2YWx1ZUVycm9yAEG/icAACwF4AEHgicAACxD/////////////////////AEGGisAACw8BAAAAAAAgAAAAAAAAAAIAQcCKwAALIP//////////////////////////////////////////AEGki8AACwgQAAAAAAAAAQBBwLjAAAsC/wcAQdS4wAALBw8A////9f8AQYC5wAALFv///////////////////////////wMAQaC5wAALHf////////////////////////////////////8PAEH/ucAACxj8//////////////////////////////8AQaC6wAALPv//////////////////////////////////////////////////////////////////////////////////AEGMu8AACzj/////////////////////////////////////////////////////////////////////////fwBB4LvAAAvRAf////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8DAEHAvcAACyf//////////////////////////////////////////////////w8AQcDAwAALwSNzcmMvbGliLnJzAAABAA8A8BoPAAAAAADiJQAA5SUAAAAAAACw4AAAv+AAAAAAAAA8+wEAafsBAAAAAABq+wEAbPsBAAAAAACAJQAAnyUAAAAAAAAA+wEAO/sBAAAAAABhdHRlbXB0ZWQgdG8gdGFrZSBvd25lcnNoaXAgb2YgUnVzdCB2YWx1ZSB3aGlsZSBpdCB3YXMgYm9ycm93ZWRiZ3RleHRjb2RlcG9pbnRzcmFzdGVyX3N5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VVVVVRVRVVVVVVVVVQAAAAAAAEBVVVVVVVVVVVXVV1VVVVVVVVVVVVVVBQAAVFVVVVVVVVVVVVVVVVUVAABVVVFVVVVVVQUQAAABAVBVVVVVVVVVVVVVAVVVVVVV/////39VVVVQVQAAVVVVVVVVVVVVVQUAQcD8wAALmARAVVVVVVVVVVVVVVVVVUVUAQBUUQEAVVUFVVVVVVVVVVFVVVVVVVVVVVVVVVVVVUQBVFVRVRVVVQVVVVVVVVVFQVVVVVVVVVVVVVVVVVVVVEEVFFBRVVVVVVVVVVBRVVVBVVVVVVVVVVVVVVVVVVVUARBUUVVVVVUFVVVVVVUFAFFVVVVVVVVVVVVVVVVVVQQBVFVRVQFVVQVVVVVVVVVVRVVVVVVVVVVVVVVVVVVVRVRVVVFVFVVVVVVVVVVVVVVUVFVVVVVVVVVVVVVVVVUEVAUEUFVBVVUFVVVVVVVVVVFVVVVVVVVVVVVVVVVVVRREBQRQVUFVVQVVVVVVVVVVUFVVVVVVVVVVVVVVVVUVRAFUVUFVFVVVBVVVVVVVVVVRVVVVVVVVVVVVVVVVVVVVVVVFFQVEVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVEAQFVVFQBAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUQAAVFVVAEBVVVVVVVVVVVVVVVVVVVVVVVVQVVVVVVVVEVFVVVVVVVVVVVVVVVVVAQAAQAAEVQEAAAEAAAAAAAAAAFRVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUBBABBQVVVVVVVVVAFVFVVVQFUVVVFQVVRVVVVUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqgBBgIHBAAuQA1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAVVVVVVVVVVVVVVVVQVUVVVVVVVVBVVVVVVVVVUFVVVVVVVVVQVVVVV///33//3XX3fW1ddVEABQVUUBAABVV1FVVVVVVVVVVVVVFQBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUFVVVVVVVVVVVFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAVVFVFVQFVVVVVVVVVVVVVVVVVVVVVVVVVVVVXFRRVVVVVVVVVVVVVVVVVVUUAQEQBAFQVAAAUVVVVVVVVVVVVVVVVAAAAAAAAAEBVVVVVVVVVVVVVVVUAVVVVVVVVVVVVVVVVAABQBVVVVVVVVVVVVRUAAFVVVVBVVVVVVVVVBVAQUFVVVVVVVVVVVVVVVVVFUBFQVVVVVVVVVVVVVVVVVVUAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAAAAAEAFRRVVRQVVVVVVVVVVVVVVVVVVVVVVUAQaCEwQALkwhVVRUAVVVVVVVVBUBVVVVVVVVVVVVVVVUAAAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAAAAAAAAAABUVVVVVVVVVVVV9VVVVWlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf1X11VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV9VVVVVVVfVVVVVVVVVVVVVVVV////VVVVVVVVVVVVVdVVVVVV1VVVVV1V9VVVVVV9VV9VdVVXVVVVVXVV9V11XVVd9VVVVVVVVVVXVVVVVVVVVVV31d9VVVVVVVVVVVVVVVVVVVX9VVVVVVVVV1VV1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVXVV1VVVVVVVVVVVVVVVVddVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRVQVVVVVVVVVVVVVVVVVVVV/f///////////////19V1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAAAAAAAAAAKqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqVVVVqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpaVVVVVVVVqqqqqqqqqqqqqqqqqqoKAKqqqmqpqqqqqqqqqqqqqqqqqqqqqqqqqqpqgaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpVqaqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqKqqqqqqqqqqqmqqqqqqqqqqqqqqqqqqqqqqqqqqqqpVVZWqqqqqqqqqqqqqqmqqqqqqqqqqqqqqVVWqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqVVVVVVVVVVVVVVVVVVVVVaqqqlaqqqqqqqqqqqqqqqqqalVVVVVVVVVVVVVVVVVfVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFUAAAFBVVVVVVVVVBVVVVVVVVVVVVVVVVVVVVVVVVVVVUFVVVUVFFVVVVVVVVUFVVFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQVVVVVVVVAAAAAFBVRRVVVVVVVVVVVVUFAFBVVVVVVRUAAFBVVVWqqqqqqqqqVkBVVVVVVVVVVVVVVRUFUFBVVVVVVVVVVVVRVVVVVVVVVVVVVVVVVVVVVQFAQUFVVRVVVVRVVVVVVVVVVVVVVVRVVVVVVVVVVVVVVVUEFFQFUVVVVVVVVVVVVVVQVUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRVFFVVVVVqqqqqqqqqqqqVVVVAAAAAABAFQBBv4zBAAvhDFVVVVVVVVVVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQAAAPCqqlpVAAAAAKqqqqqqqqqqaqqqqqpqqlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRWpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpWVVVVVVVVVVVVVVVVVVUFVFVVVVVVVVVVVVVVVVVVVapqVVUAAFRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVBUBVAUFVAFVVVVVVVVVVVVVAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQVVVVVVVVdVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFVRVVVVVVVVVVVVVVVVVVVVVVVVVAVVVVVVVVVVVVVVVVVVVVVVVBQAAVFVVVVVVVVVVVVVVBVBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRVVVVVVVVVVVVVVVVVQAAAEBVVVVVVVVVVVVVFFRVFVBVVVVVVVVVVVVVVRVAQVVFVVVVVVVVVVVVVVVVVVVVQFVVVVVVVVVVFQABAFRVVVVVVVVVVVVVVVVVVRVVVVVQVVVVVVVVVVVVVVVVBQBABVUBFFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFVAEVUVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUVFQBAVVVVVVVQVVVVVVVVVVVVVVVVVRVEVFVVVVUVVVVVBQBUAFRVVVVVVVVVVVVVVVVVVVVVAAAFRFVVVVVVRVVVVVVVVVVVVVVVVVVVVVVVVVVVFABEEQRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRUFUFUQVFVVVVVVVVBVVVVVVVVVVVVVVVVVVVVVVVVVVRUAQBFUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRVRABBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAQUQAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFQAAQVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFUVBBFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUABVVUVVVVVVVVVQEAQFVVVVVVVVVVVRUABEBVFVVVAUABVVVVVVVVVVVVVQAAAABAUFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAQAAQVVVVVVVVVVVVVVVVVVVVVVVVVVUFAAAAAAAFAARBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAUBFEAAAVVVVVVVVVVVVVVVVVVVVVVVVUBFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUVVFVVQFVVVVVVVVVVVVVVVQVAVURVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVBUAAABQVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAVFVVVVVVVVVVVVVVVVVVAEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFVVVVVVVVVVVVVVVVVVVVRVAVVVVVVVVVVVVVVVVVVVVVVVVVapUVVVaVVVVqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqVVWqqqqqqqqqqqqqqqqqqqqqqqqqqqpaVVVVVVVVVVVVVaqqVlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaqpqmmqqqqqqqqqqmpVVVVlVVVVVVVVVWpZVVVVqlVVqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpVVVVVVVVVVUEAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUAQauZwQALdVAAAAAAAEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVURUAUAAAAAQAEAVVVVVVVVVQVQVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVBVRVVVVVVVVVVVVVVVVVVQBBrZrBAAsCQBUAQbuawQALxQZUVVFVVVVUVVVVVRUAAQAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVAEAAAAAAFAAQBEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRVVVVVVVVVVVVVVVVVVVVQBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQBAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVAEBVVVVVVVVVVVVVVVVVVVdVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV1VVVVVVVVVVVVVVVVVVVVXX9/39VVVVVVVVVVVVVVVVVVVVVVVX1////////blVVVaqquqqqqqrq+r+/VaqqVlVfVVVVqlpVVVVVVVX//////////1dVVf3/3///////////////////////9///////VVVV/////////////3/V/1VVVf////9XV///////////////////////f/f/////////////////////////////////////////////////////////////1////////////////////19VVdV/////////VVVVVXVVVVVVVVV9VVVVV1VVVVVVVVVVVVVVVVVVVVVVVVVV1f///////////////////////////1VVVVVVVVVVVVVVVf//////////////////////X1VXf/1V/1VV1VdV//9XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV////VVdVVVVVVVX//////////////3///9//////////////////////////////////////////////////////////////VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf///1f//1dV///////////////f/19V9f///1X//1dV//9XVaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpaVVVVVVVVVVVZllVhqqVZqlVVVVVVlVVVVVVVVVWVVVUAQY6hwQALAQMAQZyhwQALiQlVVVVVVZVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRUAlmpaWmqqBUCmWZVlVVVVVVVVVVUAAAAAVVZVValWVVVVVVVVVVVVVlVVVVVVVVVVAAAAAAAAAABUVVVVlVlZVVVlVVVpVVVVVVVVVVVVVVWVVpVqqqqqVaqqWlVVVVlVqqqqVVVVVWVVVVpVVVVVpWVWVVVVlVVVVVVVVaaWmpZZWWWplqqqZlWqVVpZVVpWZVVVVWqqpaVaVVVVpapaVVVZWVVVWVVVVVVVlVVVVVVVVVVVVVVVVVVVVVVVVVVVZVX1VVVVaVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqVaqqqqqqqqqqqlVVVaqqqqqlWlVVmqpaVaWlVVpapZalWlVVVaVaVZVVVVV9VWlZpVVfVWZVVVVVVVVVVWZV////VVVVmppqmlVVVdVVVVVV1VVVpV1V9VVVVVW9Va+quqqrqqqaVbqq+q66rlVd9VVVVVVVVVVXVVVVVVlVVVV31d9VVVVVVVVVpaqqVVVVVVVV1VdVVVVVVVVVVVVVVVVXrVpVVVVVVVVVVVWqqqqqqqqqaqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqgAAAMCqqlpVAAAAAKqqqqqqqqqqaqqqqqpqqlVVVVVVVVVVVVVVVQVUVVVVVVVVVVVVVVVVVVVVqmpVVQAAVFmqqmpVqqqqqqqqqlqqqqqqqqqqqqqqqqqqqlpVqqqqqqqqqrr+/7+qqqqqVlVVVVVVVVVVVVVVVVX1////////L25peC9zdG9yZS8yOGh5emZsMzM4a3M0YW1oYTd2cHBubGJxMXMxbnFhdi1ydXN0LWRlZmF1bHQtMS44NS4wL2xpYi9ydXN0bGliL3NyYy9ydXN0L2xpYnJhcnkvYWxsb2Mvc3JjL3Jhd192ZWMucnMvaG9tZS9ydW5uZXIvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9pbmRleC5jcmF0ZXMuaW8tMTk0OWNmOGM2YjViNTU3Zi93YXNtLWJpbmRnZW4tMC4yLjEwNi9zcmMvY29udmVydC9zbGljZXMucnMvaG9tZS9ydW5uZXIvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9pbmRleC5jcmF0ZXMuaW8tMTk0OWNmOGM2YjViNTU3Zi93YXNtLWJpbmRnZW4tMC4yLjEwNi9zcmMvZXh0ZXJucmVmLnJzHVQQAGcAAAB/AAAAEQAAAB1UEABnAAAAjAAAABEAAABudWxsIHBvaW50ZXIgcGFzc2VkIHRvIHJ1c3RyZWN1cnNpdmUgdXNlIG9mIGFuIG9iamVjdCBkZXRlY3RlZCB3aGljaCB3b3VsZCBsZWFkIHRvIHVuc2FmZSBhbGlhc2luZyBpbiBydXN0SnNWYWx1ZSgpAA5VEAAIAAAAFlUQAAEAQaiqwQALAQQASAlwcm9kdWNlcnMBDHByb2Nlc3NlZC1ieQIGd2FscnVzBjAuMjQuNAx3YXNtLWJpbmRnZW4TMC4yLjEwNiAoMTE4MzFmYjg5KQ==");const Ze=async function(A){return await Te({module_or_path:await A.module,memory:A.memory}),Ke}({module:xe}),We=Ze.then((A=>A.default())).then((A=>A.memory));class $e{static async build(A,e,t,V){return new $e(await Ze,await We,V,A,e,t)}constructor(A,e,t,V,g,n){this.wasm=A,this.memory=e,this.logger=t,this.cols=V,this.rows=g,this.boldIsBright=n,this.vt=A.create(V,g,100,n)}feed(A){return this.vt.feed(A)}reset(A,e,t=void 0){return this.logger.debug(`vt: reset (${A}x${e})`),this.vt=this.wasm.create(A,e,100,this.boldIsBright),this.cols=A,this.rows=e,void 0!==t&&""!==t&&this.vt.feed(t),Array.from({length:e},((A,e)=>e))}resize(A,e){if(A===this.cols&&e===this.rows)return;this.logger.debug(`vt: resize (${A}x${e})`);const t=this.vt.resize(A,e);return this.cols=A,this.rows=e,t}getLine(A,e){return this.vt.getLine(A,e)}getDataView([A,e],t){return new DataView(this.memory.buffer,A,e*t)}getUint32Array([A,e]){return new Uint32Array(this.memory.buffer,A,e)}getCursor(){const A=this.vt.getCursor();return A?{col:A[0],row:A[1],visible:!0}:{col:0,row:0,visible:!1}}}const je=$A('
',12),ze="http://www.w3.org/2000/svg",Xe=24;var Pe=A=>{const e=A.core,t=[],V=[],g=[],r=new Set,i=new Map,o=new Map,I=$e.build(A.cols,A.rows,A.boldIsBright,A.logger);let B;const[s,C]=EA({cols:A.cols,rows:A.rows},{equals:(A,e)=>A.cols===e.cols&&A.rows===e.rows}),[Q,a]=EA(_e(it,A.adaptivePalette)),E=()=>A.lineHeight??1.3333333333,[c,l]=EA(!0),u=uA((()=>"hidden"!==A.cursorMode&&("steady"===A.cursorMode||(c()||R)))),d=uA((()=>({width:`${s().cols}ch`,height:E()*s().rows+"em","font-size":100*(A.scale||1)+"%","--term-line-height":`${E()}em`,"--term-cols":s().cols,"--term-rows":s().rows})));let f,w,h,D,y,p,k,m,q,L,F={col:0,row:0,visible:!1},M={size:void 0,theme:void 0,rows:new Set},R=!1;function N({size:A,theme:e,init:t}){G({size:A,theme:e,changedRows:B.reset(A.cols,A.rows,t)})}function v(A){const e=B.resize(A.cols,A.rows);void 0!==e&&G({size:A,changedRows:e})}function b(A){let e;if(Array.isArray(A)){e=new Set;for(const t of A)for(const A of B.feed(t))e.add(A)}else e=B.feed(A);G({changedRows:e})}function G({size:e,theme:t,changedRows:V}){let g=!1;if(void 0!==V)for(const A of V)M.rows.add(A),R=!0,g=!0;if(void 0!==t&&A.preferEmbeddedTheme){M.theme=t;for(let A=0;A=e.rows&&M.rows.delete(A)}g&&F.visible&&M.rows.add(F.row),S()}function U(){l((A=>(A||(R=!1),!A)))}function S(){void 0===m&&(m=requestAnimationFrame(J))}function J(){m=void 0;const{size:e,theme:t,rows:V}=M;dA((function(){void 0!==e&&(!function({cols:A,rows:e}){w.width=8*A,w.height=e*Xe,h.imageSmoothingEnabled=!1}(e),H(e.rows),O(e.rows),C(e)),void 0!==t&&(a(_e(null===t?L:t,A.adaptivePalette)),i.clear());const g=Q(),n=u();for(const A of V)Y(A,g,n)})),M.size=void 0,M.theme=void 0,M.rows.clear(),A.stats.renders+=1}function Y(A,e,t){const V=B.getLine(A,t);!function(A){h.clearRect(0,A*Xe,8*s().cols,Xe)}(A),function(A,e,t){const V=B.getDataView(e,8),g=A*Xe;let n=0;for(;nA;){const A=D.lastElementChild;D.removeChild(A),t.push(A)}}function O(A){let e=k.children.length;if(eA;){const A=k.lastElementChild;k.removeChild(A),V.push(A)}}function x(){let A=t.pop();return void 0===A&&(A=document.createElement("span"),A.className="ap-line"),A}function Z(){let A=V.pop();return void 0===A&&(A=document.createElementNS(ze,"g"),A.setAttribute("class","ap-symbol-line")),A}function W(A,e,t,V){if(!function(A){const e=function(A){const e=`stroke="currentColor" stroke-width="${Vt}" stroke-linejoin="miter" stroke-linecap="square"`,t=`stroke="currentColor" stroke-width="${Vt}" stroke-linejoin="miter" stroke-linecap="butt"`,V=A=>``,g=1/3,n=2/3;switch(A){case 9698:return''+V("M1,1 L1,0 L0,1 Z");case 9699:return''+V("M0,1 L0,0 L1,1 Z");case 9700:return''+V("M0,0 L1,0 L0,1 Z");case 9701:return''+V("M1,0 L1,1 L0,0 Z");case 9871:{const A=.17,e=.15/2,t=.2/2,V=A=>.5+A*gt,g=.5-e,n=.5+e,r=1.02,i=V(-t-A),o=V(-t),I=V(t),B=V(t+A),s=(A,e,t,V)=>`M${A},${t} L${e},${t} L${e},${V} L${A},${V} Z`;return``}case 129852:return``+V(`M0,${n} L0,1 L0.5,1 Z`);case 129853:return``+V(`M0,${n} L0,1 L1,1 Z`);case 129854:return``+V(`M0,${g} L0.5,1 L0,1 Z`);case 129855:return``+V(`M0,${g} L1,1 L0,1 Z`);case 129856:return''+V("M0,0 L0.5,1 L0,1 Z");case 129857:return``+V(`M0,${g} L0,1 L1,1 L1,0 L0.5,0 Z`);case 129858:return``+V(`M0,${g} L0,1 L1,1 L1,0 Z`);case 129859:return``+V(`M0,${n} L0,1 L1,1 L1,0 L0.5,0 Z`);case 129860:return``+V(`M0,${n} L0,1 L1,1 L1,0 Z`);case 129861:return''+V("M0.5,0 L1,0 L1,1 L0,1 Z");case 129862:return``+V(`M0,${n} L0,1 L1,1 L1,${g} Z`);case 129863:return``+V(`M0.5,1 L1,1 L1,${n} Z`);case 129864:return``+V(`M0,1 L1,1 L1,${n} Z`);case 129865:return``+V(`M0.5,1 L1,1 L1,${g} Z`);case 129866:return``+V(`M0,1 L1,1 L1,${g} Z`);case 129867:return''+V("M0.5,1 L1,0 L1,1 Z");case 129868:return``+V(`M0,0 L0.5,0 L1,${g} L1,1 L0,1 Z`);case 129869:return``+V(`M0,0 L0,1 L1,1 L1,${g} Z`);case 129870:return``+V(`M0,0 L0.5,0 L1,${n} L1,1 L0,1 Z`);case 129871:return``+V(`M0,0 L1,${n} L1,1 L0,1 Z`);case 129872:return''+V("M0,0 L0.5,0 L1,1 L0,1 Z");case 129873:return``+V(`M0,${g} L1,${n} L1,1 L0,1 Z`);case 129874:return``+V(`M0,${n} L0,0 L1,0 L1,1 L0.5,1 Z`);case 129875:return``+V(`M0,${n} L0,0 L1,0 L1,1 Z`);case 129876:return``+V(`M0,${g} L0,0 L1,0 L1,1 L0.5,1 Z`);case 129877:return``+V(`M0,${g} L0,0 L1,0 L1,1 Z`);case 129878:return''+V("M0,0 L1,0 L1,1 L0.5,1 Z");case 129879:return``+V(`M0,${g} L0.5,0 L0,0 Z`);case 129880:return``+V(`M0,0 L1,0 L0,${g} Z`);case 129881:return``+V(`M0,0 L0.5,0 L0,${n} Z`);case 129882:return``+V(`M0,0 L1,0 L0,${n} Z`);case 129883:return''+V("M0,0 L0.5,0 L0,1 Z");case 129884:return``+V(`M0,0 L1,0 L1,${g} L0,${n} Z`);case 129885:return``+V(`M0,0 L1,0 L1,${n} L0.5,1 L0,1 Z`);case 129886:return``+V(`M0,0 L1,0 L1,${n} L0,1 Z`);case 129887:return``+V(`M0,0 L1,0 L1,${g} L0.5,1 L0,1 Z`);case 129888:return``+V(`M0,0 L1,0 L1,${g} L0,1 Z`);case 129889:return''+V("M0,0 L1,0 L0.5,1 L0,1 Z");case 129890:return``+V(`M0.5,0 L1,0 L1,${g} Z`);case 129891:return``+V(`M0,0 L1,0 L1,${g} Z`);case 129892:return``+V(`M0.5,0 L1,0 L1,${n} Z`);case 129893:return``+V(`M0,0 L1,0 L1,${n} Z`);case 129894:return''+V("M0.5,0 L1,0 L1,1 Z");case 129895:return``+V(`M0,${g} L0,0 L1,0 L1,${n} Z`);case 129896:return``;case 129897:return``;case 129898:return``;case 129899:return``;case 129900:return''+V("M0,0 L0,1 L0.5,0.5 Z");case 57520:return'';case 57521:return'';case 57522:return'';case 57523:return'';case 57524:return'';case 57525:return'';case 57526:return'';case 57527:return'';case 57528:return'';case 57529:case 57535:return'';case 57530:return'';case 57531:case 57533:return'';case 57532:return'';case 57534:return'';default:return null}}(A);if(!e)return!1;if(r.has(A))return!0;const t=`sym-${A}`,V=document.createElementNS(ze,"symbol");return V.setAttribute("id",t),V.setAttribute("viewBox","0 0 1 1"),V.setAttribute("preserveAspectRatio","none"),V.setAttribute("overflow","visible"),V.innerHTML=e,p.appendChild(V),r.add(A),!0}(A))return null;const n=nt.has(A),i=n?e-rt:e,o=n?1+2*rt:1,I=function(){let A=g.pop();void 0===A&&(A=document.createElementNS(ze,"use"));return A}();return I.setAttribute("href",`#sym-${A}`),I.setAttribute("x",i),I.setAttribute("y",0),I.setAttribute("width",o),I.setAttribute("height","1"),t?I.style.setProperty("color",t):I.style.removeProperty("color"),V?I.classList.add("ap-blink"):I.classList.remove("ap-blink"),I}return wA((()=>{!function(){if(h=w.getContext("2d"),!h)throw new Error("2D ctx not available");const{cols:A,rows:e}=s();w.width=8*A,w.height=e*Xe,w.style.imageRendering="pixelated",h.imageSmoothingEnabled=!1}(),L=function(A){const e=getComputedStyle(A),t=n(e.getPropertyValue("--term-color-foreground"),it.foreground),V=n(e.getPropertyValue("--term-color-background"),it.background),g=[];for(let A=0;A<16;A++){const t=A>=8?g[A-8]:it.palette[A];g[A]=n(e.getPropertyValue(`--term-color-${A}`),t)}return{foreground:t,background:V,palette:g}}(f),M.theme=L,H(s().rows),O(s().rows),I.then((t=>{B=t,e.addEventListener("reset",N),e.addEventListener("resize",v),e.addEventListener("output",b),A.onReady?.()}))})),hA((()=>{e.removeEventListener("reset",N),e.removeEventListener("resize",v),e.removeEventListener("output",b),clearInterval(q),cancelAnimationFrame(m)})),lA((()=>{A.blinking&&void 0===q?q=setInterval(U,600):(clearInterval(q),q=void 0,l(!0))})),lA((()=>{u(),F.visible&&(M.rows.add(F.row),S())})),(()=>{const A=je.cloneNode(!0),e=A.firstChild,t=e.nextSibling,V=t.firstChild,g=V.nextSibling,n=t.nextSibling;"function"==typeof f?Ae(f,A):f=A;"function"==typeof w?Ae(w,e):w=e;"function"==typeof y?Ae(y,t):y=t;"function"==typeof p?Ae(p,V):p=V;"function"==typeof k?Ae(k,g):k=g;return"function"==typeof D?Ae(D,n):D=n,cA((e=>{const V=d(),g=`0 0 ${s().cols} ${s().rows}`,r=!!c(),i=!!c();return e._v$=_A(A,V,e._v$),g!==e._v$2&&zA(t,"viewBox",e._v$2=g),r!==e._v$3&&t.classList.toggle("ap-blink",e._v$3=r),i!==e._v$4&&n.classList.toggle("ap-blink",e._v$4=i),e}),{_v$:void 0,_v$2:void 0,_v$3:void 0,_v$4:void 0}),A})()};function _e(A,e=!1){return{fg:A.foreground,bg:A.background,palette:e?At(A.palette,A.background,A.foreground):et(A.palette)}}function At(A,e,t){const V=i(e),g=i(t),n=i(A[1]),I=i(A[2]),B=i(A[3]),s=i(A[4]),C=i(A[5]),Q=i(A[6]),a=[...A];for(let A=0;A<6;A+=1){const e=A/5,t=r(e,V,n),i=r(e,I,B),E=r(e,s,C),c=r(e,Q,g);for(let A=0;A<6;A+=1){const e=A/5,V=r(e,t,i),g=r(e,E,c);for(let A=0;A<6;A+=1){const e=r(A/5,V,g);a.push(o(e))}}}for(let A=0;A<24;A+=1){const e=(A+1)/25;a.push(o(r(e,V,g)))}return a}function et(A){const e=[...A],t=[0,95,135,175,215,255];for(let A=0;A<6;A+=1)for(let V=0;V<6;V+=1)for(let g=0;g<6;g+=1)e.push(s(t[A],t[V],t[g]));for(let A=0;A<24;A+=1){const t=8+10*A;e.push(s(t,t,t))}return e}function tt(A,e,t,V){const g=12;switch(e){case 9475:A.fillRect(t+3,V,2,Xe);break;case 9593:A.fillRect(t+3,V,2,g);break;case 9595:A.fillRect(t+3,V+g,2,g);break;case 9600:A.fillRect(t,V,8,g);break;case 9601:A.fillRect(t,V+21,8,3);break;case 9602:A.fillRect(t,V+18,8,6);break;case 9603:A.fillRect(t,V+15,8,9);break;case 9604:A.fillRect(t,V+g,8,g);break;case 9605:A.fillRect(t,V+9,8,15);break;case 9606:A.fillRect(t,V+6,8,18);break;case 9607:A.fillRect(t,V+3,8,21);break;case 9608:A.fillRect(t,V,8,Xe);break;case 9632:A.fillRect(t,V+6,8,12);break;case 9609:A.fillRect(t,V,7,Xe);break;case 9610:A.fillRect(t,V,6,Xe);break;case 9611:A.fillRect(t,V,5,Xe);break;case 9612:A.fillRect(t,V,4,Xe);break;case 9613:A.fillRect(t,V,3,Xe);break;case 9614:A.fillRect(t,V,2,Xe);break;case 9615:A.fillRect(t,V,1,Xe);break;case 9616:A.fillRect(t+4,V,4,Xe);break;case 9617:A.save(),A.globalAlpha=.25,A.fillRect(t,V,8,Xe),A.restore();break;case 9618:A.save(),A.globalAlpha=.5,A.fillRect(t,V,8,Xe),A.restore();break;case 9619:A.save(),A.globalAlpha=.75,A.fillRect(t,V,8,Xe),A.restore();break;case 9620:A.fillRect(t,V,8,3);break;case 9621:A.fillRect(t+7,V,1,Xe);break;case 9622:A.fillRect(t,V+g,4,g);break;case 9623:A.fillRect(t+4,V+g,4,g);break;case 9624:A.fillRect(t,V,4,g);break;case 9625:A.fillRect(t,V,4,Xe),A.fillRect(t+4,V+g,4,g);break;case 9626:A.fillRect(t,V,4,g),A.fillRect(t+4,V+g,4,g);break;case 9627:A.fillRect(t,V,8,g),A.fillRect(t,V+g,4,g);break;case 9628:A.fillRect(t,V,8,g),A.fillRect(t+4,V+g,4,g);break;case 9629:A.fillRect(t+4,V,4,g);break;case 9630:A.fillRect(t+4,V,4,g),A.fillRect(t,V+g,4,g);break;case 9631:A.fillRect(t+4,V,4,Xe),A.fillRect(t,V+g,4,g);break;case 129792:A.fillRect(t,V,4,8);break;case 129793:A.fillRect(t+4,V,4,8);break;case 129794:A.fillRect(t,V,8,8);break;case 129795:A.fillRect(t,V+8,4,8);break;case 129796:A.fillRect(t,V,4,8),A.fillRect(t,V+8,4,8);break;case 129797:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,4,8);break;case 129798:A.fillRect(t,V,8,8),A.fillRect(t,V+8,4,8);break;case 129799:A.fillRect(t+4,V+8,4,8);break;case 129800:A.fillRect(t,V,4,8),A.fillRect(t+4,V+8,4,8);break;case 129801:A.fillRect(t+4,V,4,8),A.fillRect(t+4,V+8,4,8);break;case 129802:A.fillRect(t,V,8,8),A.fillRect(t+4,V+8,4,8);break;case 129803:A.fillRect(t,V+8,8,8);break;case 129804:A.fillRect(t,V,4,8),A.fillRect(t,V+8,8,8);break;case 129805:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,8,8);break;case 129806:A.fillRect(t,V,8,8),A.fillRect(t,V+8,8,8);break;case 129807:A.fillRect(t,V+16,4,8);break;case 129808:A.fillRect(t,V,4,8),A.fillRect(t,V+16,4,8);break;case 129809:A.fillRect(t+4,V,4,8),A.fillRect(t,V+16,4,8);break;case 129810:A.fillRect(t,V,8,8),A.fillRect(t,V+16,4,8);break;case 129811:A.fillRect(t,V+8,4,16);break;case 129812:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,4,16);break;case 129813:A.fillRect(t,V,8,8),A.fillRect(t,V+8,4,16);break;case 129814:A.fillRect(t+4,V+8,4,8),A.fillRect(t,V+16,4,8);break;case 129815:A.fillRect(t,V,4,8),A.fillRect(t+4,V+8,4,8),A.fillRect(t,V+16,4,8);break;case 129816:A.fillRect(t+4,V,4,16),A.fillRect(t,V+16,4,8);break;case 129817:A.fillRect(t,V,8,8),A.fillRect(t+4,V+8,4,8),A.fillRect(t,V+16,4,8);break;case 129818:A.fillRect(t,V+8,8,8),A.fillRect(t,V+16,4,8);break;case 129819:A.fillRect(t,V,4,24),A.fillRect(t+4,V+8,4,8);break;case 129820:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,8,8),A.fillRect(t,V+16,4,8);break;case 129821:A.fillRect(t,V,8,16),A.fillRect(t,V+16,4,8);break;case 129822:A.fillRect(t+4,V+16,4,8);break;case 129823:A.fillRect(t,V,4,8),A.fillRect(t+4,V+16,4,8);break;case 129824:A.fillRect(t+4,V,4,8),A.fillRect(t+4,V+16,4,8);break;case 129825:A.fillRect(t,V,8,8),A.fillRect(t+4,V+16,4,8);break;case 129826:A.fillRect(t,V+8,4,8),A.fillRect(t+4,V+16,4,8);break;case 129827:A.fillRect(t,V,4,16),A.fillRect(t+4,V+16,4,8);break;case 129828:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,4,8),A.fillRect(t+4,V+16,4,8);break;case 129829:A.fillRect(t,V,8,8),A.fillRect(t,V+8,4,8),A.fillRect(t+4,V+16,4,8);break;case 129830:A.fillRect(t+4,V+8,4,16);break;case 129831:A.fillRect(t,V,4,8),A.fillRect(t+4,V+8,4,16);break;case 129832:A.fillRect(t,V,8,8),A.fillRect(t+4,V+8,4,16);break;case 129833:A.fillRect(t,V+8,8,8),A.fillRect(t+4,V+16,4,8);break;case 129834:A.fillRect(t,V,4,16),A.fillRect(t+4,V+8,4,16);break;case 129835:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,8,8),A.fillRect(t+4,V+16,4,8);break;case 129836:A.fillRect(t,V,8,16),A.fillRect(t+4,V+16,4,8);break;case 129837:A.fillRect(t,V+16,8,8);break;case 129838:A.fillRect(t,V,4,8),A.fillRect(t,V+16,8,8);break;case 129839:A.fillRect(t+4,V,4,8),A.fillRect(t,V+16,8,8);break;case 129840:A.fillRect(t,V,8,8),A.fillRect(t,V+16,8,8);break;case 129841:A.fillRect(t,V+8,4,8),A.fillRect(t,V+16,8,8);break;case 129842:A.fillRect(t,V,4,16),A.fillRect(t,V+16,8,8);break;case 129843:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,4,8),A.fillRect(t,V+16,8,8);break;case 129844:A.fillRect(t,V,8,8),A.fillRect(t,V+8,4,8),A.fillRect(t,V+16,8,8);break;case 129845:A.fillRect(t+4,V+8,4,8),A.fillRect(t,V+16,8,8);break;case 129846:A.fillRect(t,V,4,8),A.fillRect(t+4,V+8,4,8),A.fillRect(t,V+16,8,8);break;case 129847:A.fillRect(t+4,V,4,16),A.fillRect(t,V+16,8,8);break;case 129848:A.fillRect(t,V,8,8),A.fillRect(t+4,V+8,4,8),A.fillRect(t,V+16,8,8);break;case 129849:A.fillRect(t,V+8,8,16);break;case 129850:A.fillRect(t,V,4,24),A.fillRect(t+4,V+8,4,8),A.fillRect(t+4,V+16,4,8);break;case 129851:A.fillRect(t+4,V,4,8),A.fillRect(t,V+8,8,16)}}const Vt=.05,gt=9.0375/20;const nt=new Set([57520,57521,57522,57523,57524,57525,57526,57527,57528,57529,57530,57531,57532,57533,57534,57535]),rt=.02,it={foreground:"#000000",background:"#000000",palette:["#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000","#000000"]},ot=$A('',6);var It=A=>ot.cloneNode(!0);const Bt=$A('',4);var st=A=>Bt.cloneNode(!0);const Ct=$A('',6);var Qt=A=>Ct.cloneNode(!0);const at=$A('',4);var Et=A=>at.cloneNode(!0);const ct=$A('',6);var lt=A=>ct.cloneNode(!0);const ut=$A('',6);var dt=A=>ut.cloneNode(!0);const ft=$A('',4);var wt=A=>ft.cloneNode(!0);const ht=$A('',2),Dt=$A('',6),yt=$A('Unmute (m)',2),pt=$A('Mute (m)',2),kt=$A('',2),mt=$A('
',18),qt=$A('',6);function Lt(A){let e=Math.floor(A);const t=Math.floor(e/86400);e%=86400;const V=Math.floor(e/3600);e%=3600;const g=Math.floor(e/60);return e%=60,t>0?`${Ft(t)}:${Ft(V)}:${Ft(g)}:${Ft(e)}`:V>0?`${Ft(V)}:${Ft(g)}:${Ft(e)}`:`${Ft(g)}:${Ft(e)}`}function Ft(A){return A<10?`0${A}`:A.toString()}var Mt=A=>{const e=A=>e=>{e.preventDefault(),A(e)},t=()=>"number"==typeof A.currentTime?Lt(A.currentTime):"--:--",V=()=>"number"==typeof A.remainingTime?"-"+Lt(A.remainingTime):t(),g=uA((()=>"number"==typeof A.duration?A.markers.filter((e=>e[0]{const e=A.currentTarget.offsetWidth,t=A.currentTarget.getBoundingClientRect(),V=A.clientX-t.left;return 100*Math.max(0,V/e)+"%"},[r,i]=EA(!1),o=function(A,e){let t=!0;return function(...V){t&&(t=!1,A.apply(this,V),setTimeout((()=>t=!0),e))}}(A.onSeekClick,50),I=e=>{e._marker||e.altKey||e.shiftKey||e.metaKey||e.ctrlKey||0!==e.button||(i(!0),A.onSeekClick(n(e)))},B=A=>{A.altKey||A.shiftKey||A.metaKey||A.ctrlKey||r()&&o(n(A))},s=()=>{i(!1)};return document.addEventListener("mouseup",s),hA((()=>{document.removeEventListener("mouseup",s)})),(()=>{const n=mt.cloneNode(!0),r=n.firstChild,i=r.firstChild,o=i.nextSibling,s=r.nextSibling,C=s.nextSibling,Q=C.firstChild,a=C.nextSibling,E=a.firstChild,c=A.ref;return"function"==typeof c?Ae(c,n):A.ref=n,ee(n,TA(OA,{get when(){return A.isPausable},get children(){const t=ht.cloneNode(!0);return PA(t,"click",e(A.onPlayClick)),ee(t,TA(xA,{get children(){return[TA(ZA,{get when(){return A.isPlaying},get children(){return TA(Qt,{})}}),TA(ZA,{when:!0,get children(){return TA(Et,{})}})]}})),cA((()=>zA(t,"aria-label",A.isPlaying?"Pause":"Play"))),t}}),r),ee(i,t),ee(o,V),ee(s,TA(OA,{get when(){return"number"==typeof A.progress||A.isSeekable},get children(){const t=Dt.cloneNode(!0),V=t.firstChild.nextSibling;return t.$$mousemove=B,t.$$mousedown=I,ee(t,TA(HA,{get each(){return g()},children:(t,V)=>(()=>{const g=qt.cloneNode(!0),n=g.firstChild,r=n.nextSibling;var i;return g.$$mousedown=A=>{A._marker=!0},PA(g,"click",(i=V(),e((()=>{A.onSeekClick({marker:i})})))),ee(r,(()=>(A=>""===A[1]?Lt(A[0]):`${Lt(A[0])} - ${A[1]}`)(t))),cA((e=>{const V=(e=>e[0]/A.duration*100+"%")(t),r=!!(e=>"number"==typeof A.currentTime&&e[0]<=A.currentTime)(t);return V!==e._v$&&g.style.setProperty("left",e._v$=V),r!==e._v$2&&n.classList.toggle("ap-marker-past",e._v$2=r),e}),{_v$:void 0,_v$2:void 0}),g})()}),null),cA((e=>_A(V,{transform:`scaleX(${A.progress||0}`},e))),t}})),ee(n,TA(OA,{get when(){return void 0!==A.isMuted},get children(){const t=kt.cloneNode(!0);return PA(t,"click",e(A.onMuteClick)),ee(t,TA(xA,{get children(){return[TA(ZA,{get when(){return!0===A.isMuted},get children(){return[TA(wt,{}),yt.cloneNode(!0)]}}),TA(ZA,{get when(){return!1===A.isMuted},get children(){return[TA(dt,{}),pt.cloneNode(!0)]}})]}})),t}}),C),PA(C,"click",e(A.onHelpClick)),ee(C,TA(st,{}),Q),PA(a,"click",e(A.onFullscreenClick)),ee(a,TA(lt,{}),E),ee(a,TA(It,{}),E),cA((()=>n.classList.toggle("ap-seekable",!!A.isSeekable))),n})()};jA(["click","mousedown","mousemove"]);const Rt=$A('
💥
',4);var Nt=A=>Rt.cloneNode(!0);const vt=$A('
',4);var bt=A=>vt.cloneNode(!0);const Gt=$A('
',4);var Ut=A=>(()=>{const e=Gt.cloneNode(!0);return ee(e.firstChild,(()=>A.message)),cA((()=>e.classList.toggle("ap-was-playing",!!A.wasPlaying))),e})();const St=$A('
',22);var Jt=A=>(()=>{const e=St.cloneNode(!0);var t;return PA(e,"click",(t=A.onClick,A=>{A.preventDefault(),t(A)})),e})();jA(["click"]);const Yt=$A("
  • space - pause / resume
  • ",4),Tt=$A("
  • ← / → - rewind / fast-forward by 5 seconds
  • ",6),Kt=$A("
  • Shift + ← / → - rewind / fast-forward by 10%
  • ",8),Ht=$A("
  • [ / ] - jump to the previous / next marker
  • ",6),Ot=$A("
  • 0, 1, 2 ... 9 - jump to 0%, 10%, 20% ... 90%
  • ",10),xt=$A("
  • , / . - step back / forward, a frame at a time (when paused)
  • ",6),Zt=$A("
  • m - mute / unmute audio
  • ",4),Wt=$A('

    Keyboard shortcuts

    • f - toggle fullscreen mode
    • k - toggle keystroke overlay
    • ? - show this help popup
    ',22);var $t=A=>(()=>{const e=Wt.cloneNode(!0),t=e.firstChild,V=t.firstChild.firstChild.nextSibling,g=V.firstChild,n=g.nextSibling.nextSibling;var r;return PA(e,"click",(r=A.onClose,A=>{A.preventDefault(),r(A)})),t.$$click=A=>{A.stopPropagation()},ee(V,TA(OA,{get when(){return A.isPausable},get children(){return Yt.cloneNode(!0)}}),g),ee(V,TA(OA,{get when(){return A.isSeekable},get children(){return[Tt.cloneNode(!0),Kt.cloneNode(!0),Ht.cloneNode(!0),Ot.cloneNode(!0),xt.cloneNode(!0)]}}),g),ee(V,TA(OA,{get when(){return A.hasAudio},get children(){return Zt.cloneNode(!0)}}),n),e})();jA(["click"]);const jt=$A("
    ",4),zt=$A('
    ',2);function Xt(A){const[e,t]=EA(!1);return lA((()=>{const{id:e}=A.keystroke;A.keystroke.rev(),t(!1);const V=setTimeout((function(){t(!0)}),2e3),g=setTimeout((function(){A.onExpired(e)}),2700);hA((()=>{clearTimeout(V),clearTimeout(g)}))})),(()=>{const t=jt.cloneNode(!0);return ee(t.firstChild,(()=>A.keystroke.label())),cA((()=>XA(t,e()?"ap-keystroke-pill fading":"ap-keystroke-pill"))),t})()}var Pt=A=>(()=>{const e=zt.cloneNode(!0);return ee(e,TA(HA,{get each(){return A.keystrokes},children:e=>TA(Xt,{keystroke:e,get onExpired(){return A.onExpired}})})),cA((()=>e.style.setProperty("--ap-keystrokes-bottom",`${(A.bottomOffset??0)+12}px`))),e})();const _t={...Object.fromEntries(Array.from({length:26},((A,e)=>[String.fromCharCode(e+1),`C-${String.fromCharCode(97+e)}`]))),"\b":"Back","\r":"Ret","\t":"Tab","":"Esc","":"Back"},AV={" ":"Spc"},eV={57358:"Caps",57359:"Scroll",57360:"Num",57361:"Print",57362:"Pause",57363:"Menu",57414:"Enter",57421:"PgUp",57422:"PgDn"},tV={A:"↑",B:"↓",C:"→",D:"←",F:"End",H:"Home",P:"F1",Q:"F2",R:"F3",S:"F4"},VV={2:"Ins",3:"Del",5:"PgUp",6:"PgDn",15:"F5",17:"F6",18:"F7",19:"F8",20:"F9",21:"F10",23:"F11",24:"F12"};function gV(A,e){const t=Number.parseInt(e.split(":")[0],10);if(!Number.isFinite(t)||t<=1)return A;const V=t-1,g=[];return 4&V&&g.push("C"),2&V&&g.push("A"),1&V&&g.push("S"),0===g.length?A:`${g.join("-")}-${A}`}function nV(A){if(A in eV)return eV[A];const e=String.fromCodePoint(A);return e in _t?_t[e]:e in AV?AV[e]:e}function rV(A){const e=A.match(/^(\d+);;(\d+)u$/);if(null!==e)return`A-${nV(Number.parseInt(e[2],10))}`;const t=A.match(/^(\d+)(?:;([\d:]+))?u$/);if(null!==t){const A=nV(Number.parseInt(t[1],10));return void 0===t[2]?A:gV(A,t[2])}const V=A.match(/^O?([A-Z])$/);if(null!==V&&V[1]in tV)return tV[V[1]];const g=A.match(/^(\d+)~$/);if(null!==g&&g[1]in VV)return VV[g[1]];const n=A.match(/^27;([\d:]+);(\d+)~$/);if(null!==n){return gV(nV(Number.parseInt(n[2],10)),n[1])}const r=A.match(/^(?:1;)?([\d:]+)([A-Z])$/);if(null!==r&&r[2]in tV)return gV(tV[r[2]],r[1]);const i=A.match(/^(\d+);([\d:]+)~$/);return null!==i&&i[1]in VV?gV(VV[i[1]],i[2]):""}function iV(A){if(A in _t)return{kind:"special",label:_t[A]};if(1===A.length)return A in AV?{kind:"special",label:AV[A]}:{kind:"text",label:A};if(A.startsWith("")){const e=function(A){const e=A.slice(1);return 1===e.length?e in _t?"A-"+_t[e]:e in AV?"A-"+AV[e]:"A-"+e:e.startsWith("[")?rV(e.slice(1)):e.startsWith("O")?rV(e):""}(A);if(""!==e)return{kind:"special",label:e}}return null}const oV=$A('
    ',4);var IV=A=>{const e=A.logger,t=A.core,V=A.autoPlay,g=A.charW,n=A.charH,r=A.bordersW,i=A.bordersH,o=A.theme??"auto/asciinema",I="auto/"===o.slice(0,5),B=I?o.slice(5):o,[s,C]=(Q=A.cols,a=A.rows,EA({cols:Q,rows:a},{equals:(A,e)=>A.cols===e.cols&&A.rows===e.rows}));var Q,a;const[E,c]=EA({width:0,height:0},{equals:(A,e)=>A.width===e.width&&A.height===e.height}),[l,u]=EA(!0),[d,f]=EA(!0),[w,h]=EA(!1),[D,y]=EA(null),[p,k]=EA(null),[m,q]=EA(null),[L,F]=EA(!1),[M,R]=EA(void 0),[N,v]=EA(!1),[b,G]=EA(V?null:"start"),[U,S]=EA(null),[J,Y]=EA(!1),[T,K]=EA(null),[H,O]=EA([]),[x,Z]=EA(!1),[W,$]=EA(!1),[j,z]=EA(null),X=uA((()=>s().cols||80)),P=uA((()=>s().rows||24)),_=()=>!1===A.controls?0:32,[AA,eA]=EA(!1!==A.keystrokeOverlay),[tA,VA]=EA([]);let gA,nA,rA,iA,oA,IA,BA=1;function sA(){Y(!1),jA(),zA()}const CA=({isPausable:A,isSeekable:e})=>{dA((()=>{u(A),f(e)}))},QA=A=>{dA((()=>{void 0!==A.duration&&(K(A.duration),y(0),k(A.duration),q(0)),void 0!==A.markers&&O(A.markers),void 0!==A.hasAudio&&R(!A.hasAudio&&void 0)}))},aA=({size:A,theme:e})=>{dA((()=>{C(A),void 0!==e&&z(e)}))},lA=A=>{C(A)},fA=()=>{G(null)},DA=()=>{dA((()=>{F(!0),v(!0),G(null),Y(!0),$A()}))},yA=()=>{dA((()=>{F(!1),sA()}))},pA=()=>{dA((()=>{F(!1),sA(),G("loader"),NA()}))},kA=({message:A})=>{dA((()=>{F(!1),sA(),NA(),void 0!==A&&(S(A),G("info"))}))},mA=A=>{R(A)},qA={renders:0},LA=({message:A})=>{dA((()=>{F(!1),sA(),void 0!==A&&(S(A),G("info"))})),e.debug("stats",qA)},FA=()=>{NA(),G("error")},MA=({data:A})=>{if(!AA())return;const e=iV(A);if(null===e)return;const t=tA(),V=t[t.length-1];"text"!==V?.kind||"text"!==e.kind?"special"!==V?.kind||"special"!==e.kind||V.key!==e.label?VA([...t,bA(e)].slice(-4)):V.increment():V.append(e.label)},RA=()=>{zA(),NA()},NA=()=>{VA([])},vA=A=>{VA((e=>e.filter((e=>e.id!==A))))},bA=({kind:A,label:e})=>{const[t,V]=EA(e),[g,n]=EA(1),[r,i]=EA(0);return{id:BA++,kind:A,key:e,label:()=>1===g()?t():`${t()} × ${g()}`,rev:r,append:A=>{V((e=>(e+A).slice(-10))),i((A=>A+1))},increment:()=>{n((A=>A+1)),i((A=>A+1))}}};t.addEventListener("ready",CA),t.addEventListener("metadata",QA),t.addEventListener("play",fA),t.addEventListener("playing",DA),t.addEventListener("pause",yA),t.addEventListener("loading",pA),t.addEventListener("offline",kA),t.addEventListener("muted",mA),t.addEventListener("ended",LA),t.addEventListener("error",FA),t.addEventListener("input",MA),t.addEventListener("seeked",RA),t.addEventListener("reset",aA),t.addEventListener("resize",lA);const GA=()=>{IA=new ResizeObserver(function(A,e){let t;return function(...V){clearTimeout(t),t=setTimeout((()=>A.apply(this,V)),e)}}((A=>{c({width:rA.offsetWidth,height:rA.offsetHeight}),rA.dispatchEvent(new CustomEvent("resize",{detail:{el:iA}}))}),10)),IA.observe(rA)};wA((async()=>{e.info("view: mounted"),e.debug("view: font measurements",{charW:g,charH:n}),GA(),c({width:rA.offsetWidth,height:rA.offsetHeight})})),hA((()=>{t.removeEventListener("ready",CA),t.removeEventListener("metadata",QA),t.removeEventListener("play",fA),t.removeEventListener("playing",DA),t.removeEventListener("pause",yA),t.removeEventListener("loading",pA),t.removeEventListener("offline",kA),t.removeEventListener("muted",mA),t.removeEventListener("ended",LA),t.removeEventListener("error",FA),t.removeEventListener("input",MA),t.removeEventListener("seeked",RA),t.removeEventListener("reset",aA),t.removeEventListener("resize",lA),t.stop(),jA(),IA.disconnect()}));const UA=uA((()=>{const e=g*X()+r,t=n*P()+i;let V=A.fit??"width";const o=E();if("both"===V||w()){V=o.width/(o.height-_())>e/t?"height":"width"}if(!1===V||"none"===V)return{};if("width"===V){const A=o.width/e;return{scale:A,width:o.width,height:t*A+_()}}if("height"===V){const A=(o.height-_())/t;return{scale:A,width:e*A,height:o.height}}throw new Error(`unsupported fit mode: ${V}`)})),SA=()=>{h(document.fullscreenElement??document.webkitFullscreenElement)},JA=()=>{w()?(document.exitFullscreen??document.webkitExitFullscreen??(()=>{})).apply(document):(rA.requestFullscreen??rA.webkitRequestFullscreen??(()=>{})).apply(rA)},YA=()=>{W()?$(!1):(t.pause(),$(!0))},KA=A=>{if(!(A.altKey||A.metaKey||A.ctrlKey||(" "==A.key||"Enter"==A.key)&&A.target instanceof HTMLButtonElement)){if(" "==A.key)ge();else if(","==A.key)t.step(-1).then(zA);else if("."==A.key)t.step().then(zA);else if("f"==A.key)JA();else if("m"==A.key)ne();else if("["==A.key)t.seek({marker:"prev"});else if("]"==A.key)t.seek({marker:"next"});else if(A.key.charCodeAt(0)>=48&&A.key.charCodeAt(0)<=57){const e=(A.key.charCodeAt(0)-48)/10;t.seek(100*e+"%")}else if("?"==A.key)YA();else if("k"==A.key)AA()?(NA(),eA(!1)):eA(!0);else if("ArrowLeft"==A.key)A.shiftKey?t.seek("<<<"):t.seek("<<");else if("ArrowRight"==A.key)A.shiftKey?t.seek(">>>"):t.seek(">>");else{if("Escape"!=A.key)return;$(!1)}A.stopPropagation(),A.preventDefault()}},HA=()=>{w()&&PA(!0)},WA=()=>{w()||PA(!1)},$A=()=>{clearInterval(nA),nA=setInterval(zA,100)},jA=()=>{clearInterval(nA)},zA=async()=>{const A=await t.getCurrentTime(),e=await t.getRemainingTime(),V=await t.getProgress();dA((()=>{y(A),k(e),q(V)}))},PA=A=>{clearTimeout(gA),A&&(gA=setTimeout((()=>PA(!1)),2e3)),Z(A)},te=uA((()=>I?j():null)),Ve=()=>{t.play()},ge=()=>{L()?t.pause():t.play()},ne=()=>{!0===M()?t.unmute():t.mute()},re=A=>{t.seek(A)},ie=(()=>{const V=oV.cloneNode(!0),g=V.firstChild;"function"==typeof rA?Ae(rA,V):rA=V,V.addEventListener("webkitfullscreenchange",SA),V.addEventListener("fullscreenchange",SA),V.$$mousemove=HA,V.$$keydown=KA;return"function"==typeof iA?Ae(iA,g):iA=g,g.$$mousemove=()=>PA(!0),g.addEventListener("mouseleave",WA),ee(g,TA(Pe,{get cols(){return X()},get rows(){return P()},get scale(){return UA()?.scale},get blinking(){return J()},get cursorMode(){return A.cursorMode},get boldIsBright(){return A.boldIsBright},get adaptivePalette(){return A.adaptivePalette},get lineHeight(){return A.terminalLineHeight},preferEmbeddedTheme:I,core:t,logger:e,get onReady(){return A.onTerminalReady},get stats(){return qA}}),null),ee(g,TA(OA,{get when(){return!1!==A.controls},get children(){return TA(Mt,{get duration(){return T()},get currentTime(){return D()},get remainingTime(){return p()},get progress(){return m()},get markers(){return H()},get isPlaying(){return L()||"loader"==b()},get isPausable(){return l()},get isSeekable(){return d()},get isMuted(){return M()},onPlayClick:ge,onFullscreenClick:JA,onHelpClick:YA,onSeekClick:re,onMuteClick:ne,ref(A){"function"==typeof oA?oA(A):oA=A}})}}),null),ee(g,TA(OA,{get when(){return tA().length>0},get children(){return TA(Pt,{get bottomOffset(){return _()},get keystrokes(){return tA()},onExpired:vA})}}),null),ee(g,TA(xA,{get children(){return[TA(ZA,{get when(){return"start"==b()},get children(){return TA(Jt,{onClick:Ve})}}),TA(ZA,{get when(){return"loader"==b()},get children(){return TA(bt,{})}}),TA(ZA,{get when(){return"error"==b()},get children(){return TA(Nt,{})}})]}}),null),ee(g,TA(Ee,{name:"slide",get children(){return TA(OA,{get when(){return"info"==b()},get children(){return TA(Ut,{get message(){return U()},get wasPlaying(){return N()}})}})}}),null),ee(g,TA(OA,{get when(){return W()},get children(){return TA($t,{onClose:()=>$(!1),get isPausable(){return l()},get isSeekable(){return d()},get hasAudio(){return void 0!==M()}})}}),null),cA((e=>{const t=!!(!0===A.controls||"auto"===A.controls&&x()),n=`ap-player ap-default-term-ff asciinema-player-theme-${B}`,r=(()=>{const e={};!1!==A.fit&&"none"!==A.fit||void 0===A.terminalFontSize||("small"===A.terminalFontSize?e["font-size"]="12px":"medium"===A.terminalFontSize?e["font-size"]="18px":"big"===A.terminalFontSize?e["font-size"]="24px":e["font-size"]=A.terminalFontSize);const t=UA();void 0!==t.width&&(e.width=`${t.width}px`,e.height=`${t.height}px`),void 0!==A.terminalFontFamily&&(e["--term-font-family"]=A.terminalFontFamily);const V=te();return V&&(e["--term-color-foreground"]=V.foreground,e["--term-color-background"]=V.background),e})();return t!==e._v$&&V.classList.toggle("ap-hud",e._v$=t),n!==e._v$2&&XA(g,e._v$2=n),e._v$3=_A(g,r,e._v$3),e}),{_v$:void 0,_v$2:void 0,_v$3:void 0}),V})();return ie};function BV(A,e,t={}){const V=function(A,e){const t=80,V=24,g=document.createElement("div");g.className="ap-default-term-ff",g.style.height="0px",g.style.overflow="hidden",g.style.fontSize="15px",void 0!==A&&g.style.setProperty("--term-font-family",A);const n=document.createElement("div");n.className="ap-term",n.style.width=`${t}ch`,n.style.height=V*(e??1.3333333333)+"em",n.style.fontSize="100%",g.appendChild(n),document.body.appendChild(g);const r={charW:n.clientWidth/t,charH:n.clientHeight/V,bordersW:n.offsetWidth-n.clientWidth,bordersH:n.offsetHeight-n.clientHeight};return document.body.removeChild(g),r}(t.terminalFontFamily,t.terminalLineHeight),g={core:A,logger:t.logger,cols:t.cols,rows:t.rows,fit:t.fit,controls:t.controls,cursorMode:t.cursorMode,keystrokeOverlay:t.keystrokeOverlay,autoPlay:t.autoPlay,boldIsBright:t.boldIsBright,adaptivePalette:t.adaptivePalette,terminalFontSize:t.terminalFontSize,terminalFontFamily:t.terminalFontFamily,terminalLineHeight:t.terminalLineHeight,theme:t.theme,onTerminalReady:t.onTerminalReady,...V};let n;const r=function(A,e,t,V={}){let g;return aA((V=>{g=V,e===document?A():ee(e,A(),e.firstChild?null:void 0,t)}),V.owner),()=>{g(),e.textContent=""}}((()=>(n=TA(IV,g),n)),e);return{el:n,dispose:r}}jA(["keydown","mousemove"]);const sV=["audioUrl","autoPlay","autoplay","cols","idleTimeLimit","loop","markers","pauseOnMarkers","poster","preload","rows","speed","startAt"],CV=["autoPlay","autoplay","boldIsBright","cols","adaptivePalette","controls","cursorMode","fit","keystrokeOverlay","rows","terminalFontFamily","terminalFontSize","terminalLineHeight","theme"];return A.create=function(A,e,t={}){const V=t.logger??new u,g=new _(A,function(A,e={}){const t=Object.fromEntries(Object.entries(A).filter((([A])=>sV.includes(A))));return t.autoPlay??=t.autoplay,t.speed??=1,{...t,...e}}(t,{logger:V})),{el:n,dispose:r}=BV(g,e,function(A,e={}){const t=Object.fromEntries(Object.entries(A).filter((([A])=>CV.includes(A))));if(t.autoPlay??=t.autoplay,t.adaptivePalette??=!1,t.controls??="auto",t.cursorMode??="blinking",t.keystrokeOverlay??=!1,!["blinking","steady","hidden"].includes(t.cursorMode))throw new Error(`unsupported cursor mode: ${t.cursorMode}`);if("boolean"!=typeof t.keystrokeOverlay)throw new Error(`unsupported keystroke overlay option: ${t.keystrokeOverlay}`);return{...t,...e}}(t,{logger:V,onTerminalReady:()=>g.terminalReady()}));g.init().catch((()=>{}));const i={el:n,dispose:r,getCurrentTime:()=>g.getCurrentTime(),getDuration:()=>g.getDuration(),play:()=>g.play(),pause:()=>g.pause(),seek:A=>g.seek(A),addEventListener:(A,e)=>g.addEventListener(A,e.bind(i))};return i},A}({}); +/* @license-end */ diff --git a/site/assets/demo.cast b/site/assets/demo.cast new file mode 100644 index 0000000..d290731 --- /dev/null +++ b/site/assets/demo.cast @@ -0,0 +1,315 @@ +{"version":3,"term":{"cols":100,"rows":30},"timestamp":1786289836,"command":"crisismode demo","title":"crisismode demo — PostgreSQL replication lag cascade recovery","env":{"SHELL":"/bin/zsh"}} +[1.45,"o","\r\r\n"] +[0.04,"o","\u001b[2m════════════════════════════════════════════════════════════════════════\u001b[22m\r\r\n"] +[0.16,"o","\u001b[1m\u001b[31m ⚠ CRISISMODE — Recovery Agent Framework Demo\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Recovery Agent Contract Specification v0.2.1\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m PostgreSQL Replication Lag Cascade Recovery\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m════════════════════════════════════════════════════════════════════════\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[2m This demo walks through the full execution flow of the Recovery Agent\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Contract using a simulated PostgreSQL replication recovery scenario.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m All system interactions are simulated — no real databases are touched.\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.501,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 1: Trigger\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 1. Prometheus alert fires\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[1m\u001b[33m ALERT FIRED\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[33m Source: prometheus\u001b[39m\r\r\n"] +[0.1,"o","\u001b[33m Type: alert\u001b[39m\r\r\n"] +[0.1,"o","\u001b[33m Label: alertname=PostgresReplicationLagCritical\u001b[39m\r\r\n"] +[0.1,"o","\u001b[33m Time: 2026-08-09T15:37:18.237Z\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.302,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 2: Pre-Authorized Catalog Check\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 2. Checking pre-authorized action catalogs\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Catalog ID: pg-replication-standard-recovery\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Name: Standard PostgreSQL Replication Recovery\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Approved by: jane.chen@example.com\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Expires: 2026-05-15T10:00:00Z\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Covers: routine, elevated risk levels\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.16,"o","\u001b[32m ✓ Catalog entry found for this scenario\u001b[39m\r\r\n"] +[0.302,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 3: Agent Selection\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 3. Selecting recovery agent based on trigger\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Agent: postgresql-replication-recovery v1.2.0\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Description: Recovers PostgreSQL streaming replication failures including lag cascades, slot overflow, and replica divergence.\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.16,"o","\u001b[32m ✓ Agent selected: trigger matches manifest conditions\u001b[39m\r\r\n"] +[0.302,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 4: Context Assembly\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 4. Assembling agent context from trigger and topology\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Topology:\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m pg-primary-us-east-1 \u001b[22m\u001b[33mdegraded \u001b[39m\u001b[2mprimary\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m pg-replica-us-east-1a \u001b[22m\u001b[33mdegraded \u001b[39m\u001b[2mreplica\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m pg-replica-us-east-1b \u001b[22m\u001b[31munhealthy \u001b[39m\u001b[2mreplica\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m pg-replica-us-east-1c \u001b[22m\u001b[33mdegraded \u001b[39m\u001b[2mreplica\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m haproxy-us-east-1 \u001b[22m\u001b[32mhealthy \u001b[39m\u001b[2mload_balancer\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[2m Trust level: copilot (scenario override: autopilot)\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Layers: L1=available, L2=available, L3=unavailable, L4=available\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Policies: requireApprovalForAllElevated=false\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.305,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 5: Diagnosis\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 5. Agent performing read-only investigation\u001b[39m\u001b[22m\r\r\n"] +[4,"o","\u001b[2m Status: \u001b[22m\u001b[32midentified\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Scenario: replication_lag_cascade\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Confidence: 78%\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.001,"o","\u001b[36m Root cause: \u001b[39m\u001b[37mAll three replicas are behind the same sent_lsn (0/5000000), but each is behind by a different and substantial margin, with lag_seconds climbing from 45s to 78s to 342s in lockstep with increasing LSN gaps at the write/flush/replay stages. Because every replica is affected rather than a single outlier, this points to a primary-side condition — most likely a sustained spike in WAL generation (heavy write/DML load, consistent with the 247 active sessions on the primary) that is outpacing the replicas' ability to receive, write, and apply WAL. The weakest replica (10.0.1.52) is falling behind even at the write stage (write_lsn 0/3000000 vs sent 0/5000000), suggesting it also has a secondary bottleneck (network throughput or disk I/O) that is amplifying the shared primary-side pressure. Replication slots are still 'reserved' (not 'lost'), so WAL has not yet been recycled out from under any replica, but if lag continues to grow at this rate, especially on 10.0.1.52, WAL retention"] +[0.1,"o"," risk will escalate quickly.\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[36m Recommendations:\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m - Check current WAL generation rate on the primary (pg_stat_wal or pg_current_wal_lsn delta over time) to confirm a write/DML volume spike is driving the cascade.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m - Inspect primary pg_stat_activity for the top write-heavy queries/transactions among the 247 active connections that may be generating excessive WAL.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m - Check network throughput/latency and disk I/O saturation specifically on 10.0.1.52, since it lags even at the write stage — this replica likely has an independent bottleneck.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m - Monitor pg_replication_slots restart_lsn vs current WAL location closely; if the gap keeps growing, increase wal_keep_size or add slot retention headroom to avoid transitioning to wal_status 'lost'.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m - If read traffic is being redirected to the primary due to replica lag, verify load balancer/read-routing logic isn't creating a feedback loop that increases primary load fu"] +[0,"o","rther.\u001b"] +[0.1,"o","[22m\r\r\n"] +[0.1,"o","\u001b[2m - If lag continues to widen despite normal WAL rate, consider throttling or batching bulk write operations on the primary until replicas catch up.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m - Re-check pg_is_wal_replay_paused() and replica system metrics (CPU, disk, network) directly on 10.0.1.52 to rule out a replica-local resource constraint.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m - Once WAL generation rate and replica I/O are confirmed healthy, allow replicas time to catch up and re-measure lag_seconds trend before declaring resolution.\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.16,"o","\u001b[31m [CRITICAL] \u001b[39m\u001b[2mpg_stat_replication: All three replicas share the same sent_lsn but show escalating lag (45s, 78s, 342s), indicating a systemic primary-side WAL generation or fan-out issue rather than an isolated replica fault.\u001b[22m\r\r\n"] +[0.16,"o","\u001b[31m [CRITICAL] \u001b[39m\u001b[2mpg_stat_replication: Replica 10.0.1.52 lags not only in replay but also in write/flush stages, meaning it is failing to even receive/persist WAL at the rate the primary is sending it — a compounding replica-side (net"] +[0,"o","work o"] +[0.1,"o","r disk) constraint on top of the primary-side pressure.\u001b[22m\r\r\n"] +[0.16,"o","\u001b[33m [WARNING] \u001b[39m\u001b[2mpg_stat_replication: Replicas 10.0.1.50 and 10.0.1.51 are current at write/flush but lag noticeably at replay, showing replay itself is the bottleneck for the healthier nodes.\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m [INFO] \u001b[22m\u001b[2mpg_replication_slots: All slots report wal_status 'reserved', meaning WAL has not yet been recycled out from under any replica — no immediate slot-overflow risk, but this could change if the trend continues, especially for the slot behind 10.0.1.52.\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m [INFO] \u001b[22m\u001b[2mReplica self-reported state: pg_is_wal_replay_paused() explicitly reports false, ruling out a deliberate or stuck replay pause as the cause of lag.\u001b[22m\r\r\n"] +[0.16,"o","\u001b[33m [WARNING] \u001b[39m\u001b[2mpg_stat_activity / connection pool: Primary shows 247 active connections while the connection pool usage metric shows only 4/25 in use with no idle-in-transaction sessions, so connection pool exhaustion is not indicated at this layer — but the raw active"] +[0,"o","-ses"] +[0.1,"o","sion count on the primary is notably high and consistent with heavy write throughput driving WAL volume.\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.16,"o","\u001b[32m ✓ Diagnosis complete: replication_lag_cascade (78% confidence)\u001b[39m\r\r\n"] +[0.304,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 6: Plan Creation\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 6. Agent building recovery plan\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Plan ID: rp-20260809153749-pg-repl-001\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Duration: PT15M\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Summary: Recover PostgreSQL replication by disconnecting lagging replica 10.0.1.52, stabilizing the primary, and re-syncing.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Rollback: stepwise\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m\u001b[1m# \u001b[22m\u001b[1mType \u001b[22m\u001b[1mRisk \u001b[22m\u001b[1mName\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m ────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m1 diagnosis_action \u001b[2m— \u001b[22mAssess current replication lag across all replicas\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m2 human_notification \u001b[2m— \u001b[22mNotify on-call DBA of replication recovery initiation\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m3 checkpoint \u001b[2m— \u001b[22mPre-recovery checkpoint\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m4 system_action \u001b[33melevated \u001b[39mDisconnect lagging replica 10.0.1.52 from replication\r\r\n"] +[0.1,"o","\u001b[2m what: Terminates the WAL sender process for pg-replica-10-0-1-52 to prevent the primary from being blocked by a slow consumer.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[33m risk: \u001b[39m\u001b[2mThis changes a live system. Affects: pg-replica-10-0-1-52, read-pool. Worst case: single_replica_disconnected.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m undo: Replica will automatically attempt to reconnect. No explicit rollback needed.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m5 system_action \u001b[32mroutine \u001b[39mRedirect read traffic away from disconnected replica\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m6 replanning_checkpoint \u001b[2m— \u001b[22mAssess recovery progress before proceeding\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m7 human_approval \u001b[2m— \u001b[22mApprove replica resynchronization\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m8 system_action \u001b[31mhigh \u001b[39mInitiate pg_basebackup and re-establish replication for 10.0.1.52\r\r\n"] +[0.1,"o","\u001b[2m what: Performs a full base backup from the primary to pg-replica-10-0-1-52 and configures streaming replication.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[33m risk: \u001b[39m\u001b[2mThis makes a significant change to a live system — a mistake here causes real downtime. Affects: pg-replica-10-0-1-52, pg-primary-us-east-1, read-pool. Worst case: increased_primary_io_load_during_basebackup.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m undo: If pg_basebackup fails, manual intervention required to restart the process.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m9 conditional \u001b[2m— \u001b[22mRestore traffic or notify for manual intervention\r\r\n"] +[0.1,"o","\u001b[2m \u001b[22m10 human_notification \u001b[2m— \u001b[22mSend recovery summary\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[2m Impact:\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m pg-primary-us-east-1 (primary): reduced_read_capacity\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m pg-replica-10-0-1-52 (replica): temporary_unavailability\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m User impact: Read queries may experience elevated latency during recovery. No write impact expected.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Data loss: none\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.302,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 7: Plan Validation\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 7. Framework validating plan against manifest and policies\u001b[39m\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Scenario declared in manifest\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Execution contexts declared\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Risk levels within manifest maximum\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Unique step IDs\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ State preservation for elevated+ steps\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Human notification for elevated+ plans\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Human notification guide ids resolve\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Rollback strategy declared\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Blast radius declares affected components\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ No nested conditionals\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ System actions declare required capabilities\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Manifest capabilities are registered\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Step capabilities are registered\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.16,"o","\u001b[32m ✓ Plan validation passed\u001b[39m\r\r\n"] +[0.302,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 8: Catalog Matching\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 8. Matching plan against pre-authorized catalog\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Agent name matches: postgresql-replication-recovery\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Scenario matches: replication_lag_cascade\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Step count (10) within limit (15)\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Required step patterns present (checkpoint, notification)\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m No forbidden operations detected\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Catalog entry 'pg-replication-standard-recovery' matched\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Catalog match: covers routine, elevated risk levels\u001b[39m\r\r\n"] +[0.1,"o","\u001b[33m ! Steps with high/critical risk still require manual approval\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.302,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 9: Execution\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 9. Beginning plan execution\u001b[39m\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-001 \u001b[22m\u001b[2m[diagnosis_action]\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Assess current replication lag across all replicas\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (1ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-002 \u001b[22m\u001b[34m[human_notification]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Notify on-call DBA of replication recovery initiation\u001b[22m\r\r\n"] +[0.16,"o","\u001b[34m → Notification: Automated recovery initiated for PostgreSQL replication lag cascade\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Recipients: on_call_dba (high)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (0ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-003 \u001b[22m\u001b[2m[checkpoint]\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Pre-recovery checkpoint\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◆ Capture: full_replication_config — captured\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◆ Capture: replication_slot_state — captured\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (1ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-004 \u001b[22m\u001b[33m[system_action]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Disconnect lagging replica 10.0.1.52 from replication\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◈ Blast radius: pg-replica-10-0-1-52 (cascade: low)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◆ Capture: replication_state_snapshot — captured\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Precondition: Replica 10.0.1.52 is currently connected\u001b[39m\r\r\n"] +[0.16,"o","\u001b[33m ◇ Capture: [DRY-RUN] Disconnect lagging replica 10.0.1.52 from replication — skipped\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (3ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-005 \u001b[22m\u001b[33m[system_action]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Redirect read traffic away from disconnected replica\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◈ Blast radius: load-balancer (cascade: none)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[33m ◇ Capture: [DRY-RUN] Redirect read traffic away from disconnected replica — skipped\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (1ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-006 \u001b[22m\u001b[36m[replanning_checkpoint]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Assess recovery progress before proceeding\u001b[22m\r\r\n"] +[0.16,"o","\u001b[36m ↻ Replanning checkpoint reached — invoking agent.replan()\u001b[39m\r\r\n"] +[0.16,"o","\u001b[2m ◆ Capture: post_stabilization_replication_state — captured\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◆ Capture: post_stabilization_slot_state — captured\u001b[22m\r\r\n"] +[0.16,"o","\u001b[36m ↻ Agent returned revised plan\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Revised plan: Revised plan: drop and recreate invalid slot 'replica_us_east_1b' before proceeding.. Fast replan: approved\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-006a \u001b[22m\u001b[33m[system_action]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Drop invalid replication slot\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◈ Blast radius: pg-primary-us-east-1 (cascade: low)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◆ Capture: slot_state_before_drop — captured\u001b[22m\r\r\n"] +[0.16,"o","\u001b[33m ◇ Capture: [DRY-RUN] Drop invalid replication slot — skipped\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (0ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-006b \u001b[22m\u001b[33m[system_action]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Recreate replication slot\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◈ Blast radius: pg-primary-us-east-1 (cascade: none)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[33m ◇ Capture: [DRY-RUN] Recreate replication slot — skipped\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (0ms)\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (1ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-007 \u001b[22m\u001b[35m[human_approval]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Approve replica resynchronization\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.04,"o","\u001b[1m\u001b[35m ┌─────────────────────────────────────────────────────┐\u001b[39m\u001b[22m\r\r\n"] +[0.1,"o","\u001b[1m\u001b[35m │ HUMAN APPROVAL REQUIRED │\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[1m\u001b[35m └─────────────────────────────────────────────────────┘\u001b[39m\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m Ready to begin resynchronization for 10.0.1.52\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m The primary has been stabilized. The next phase will resynchronize pg-replica-10-0-1-52, which requires a pg_basebackup that will temporarily increase I/O load on the primary.\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m Proposed actions:\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m • Drop and recreate invalid replication slot for pg-replica-10-0-1-52\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m • Initiate pg_basebackup from primary to pg-replica-10-0-1-52\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m • Re-establish streaming replication\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m • Verify replication lag returns to < 10 seconds\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[33m Risk: Primary I/O load will increase during pg_basebackup. No data loss risk.\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[2m Alternatives:\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m [skip] Skip resynchronization. Replication will remain broken for pg-replica-10-0-1-52 until manually repaired.\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m [abort] Abort the recovery plan. Replica disconnect will remain in effect.\u001b[22m\r\r\n"] +[0.002,"o","\u001b[1G\u001b[0J"] +[0.05,"o","\r\r\n"] +[0.1,"o"," Enter your decision (approve/skip/reject): \u001b[48G"] +[4,"o","approve"] +[0.05,"o","\r\r\r\n"] +[0.16,"o","\u001b[32m ✓ Approval received: approved\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (4403ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-008 \u001b[22m\u001b[33m[system_action]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Initiate pg_basebackup and re-establish replication for 10.0.1.52\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◈ Blast radius: pg-replica-10-0-1-52, pg-primary-us-east-1 (cascade: medium)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◆ Capture: pre_basebackup_primary_state — captured\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ✓ Precondition: Primary is reachable and accepting connections\u001b[39m\r\r\n"] +[0.16,"o","\u001b[33m ◇ Capture: [DRY-RUN] Initiate pg_basebackup and re-establish replication for 10.0.1.52 — skipped\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (1ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-009 \u001b[22m\u001b[37m[conditional]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Restore traffic or notify for manual intervention\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m → Condition TRUE — executing thenStep\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-009a \u001b[22m\u001b[33m[system_action]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Restore read traffic to recovered replica\u001b[22m\r\r\n"] +[0.16,"o","\u001b[2m ◈ Blast radius: load-balancer (cascade: none)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[33m ◇ Capture: [DRY-RUN] Restore read traffic to recovered replica — skipped\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (0ms)\u001b[39m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (1ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.45,"o","\u001b[1m Step step-010 \u001b[22m\u001b[34m[human_notification]\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Send recovery summary\u001b[22m\r\r\n"] +[0.16,"o","\u001b[34m → Notification: PostgreSQL replication recovery completed\u001b[39m\r\r\n"] +[0.1,"o","\u001b[2m Recipients: on_call_dba (medium), incident_commander (medium)\u001b[22m\r\r\n"] +[0.16,"o","\u001b[32m ● SUCCESS (0ms)\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.85,"o","\u001b[1m\u001b[36m Phase 10: Completion\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m────────────────────────────────────────────────────────────────────────\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[1m\u001b[37m 10. Writing forensic record\u001b[39m\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.04,"o","\u001b[2m════════════════════════════════════════════════════════════════════════\u001b[22m\r\r\n"] +[0.1,"o","\u001b[1m Forensic Record Summary\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m════════════════════════════════════════════════════════════════════════\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[2m Record ID: fr-1786289874893\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Completeness: complete\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Outcome: success\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Duration: 4414ms\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[2m Steps: 13 completed, 0 failed, 0 skipped (13 total)\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Captures: 7 succeeded, 0 skipped (7 total)\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Catalog match: yes\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Replans: 1\u001b[22m\r\r\n"] +[0.1,"o","\u001b[2m Plans: 2\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.1,"o","\u001b[32m Forensic record written to: output/forensic-record.json\u001b[39m\r\r\n"] +[0.05,"o","\r\r\n"] +[0.04,"o","\u001b[2m════════════════════════════════════════════════════════════════════════\u001b[22m\r\r\n"] +[0.1,"o","\u001b[1m\u001b[32m Demo Complete\u001b[39m\u001b[22m\r\r\n"] +[0.04,"o","\u001b[2m════════════════════════════════════════════════════════════════════════\u001b[22m\r\r\n"] +[0.05,"o","\r\r\n"] +[2.52,"x","0"] diff --git a/site/assets/demo.gif b/site/assets/demo.gif new file mode 100644 index 0000000..0f2312e Binary files /dev/null and b/site/assets/demo.gif differ diff --git a/site/index.html b/site/index.html index 799ecc5..e9349ea 100644 --- a/site/index.html +++ b/site/index.html @@ -8,6 +8,7 @@ +