From 8b531732fe116b80b07658e7e7287b24672f40d6 Mon Sep 17 00:00:00 2001 From: AirCoding Date: Mon, 1 Jun 2026 18:11:30 +0800 Subject: [PATCH] Stop tracking reference/; add .gitignore for third-party source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reference/ tree (OpenCode, Codex, Hermes, Claude Code 2.1.88, etc., ~0.5GB) was partially tracked (287 files) from an earlier accidental add. Per the "reference is working-copy only" decision (DD §23), this removes all reference/ files from the index (disk copies retained) and adds a root .gitignore rule so future commits never pull third-party / leaked source into the repo. No documentation or source-of-truth content changed. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 2 + reference/air-suite-20260518/.gitkeep | 0 .../.claude-plugin/marketplace.json | 55 - reference/anthropic-skills/.gitignore | 5 - reference/anthropic-skills/.gitkeep | 0 reference/asciinema-3.2.0/.cargo/config.toml | 2 - reference/asciinema-3.2.0/.gitattributes | 1 - .../.github/ISSUE_TEMPLATE/bug-report.yml | 101 -- .../.github/ISSUE_TEMPLATE/config.yml | 8 - .../asciinema-3.2.0/.github/workflows/ci.yml | 45 - .../.github/workflows/release.yml | 90 - reference/asciinema-3.2.0/.gitignore | 4 - reference/asciinema-3.2.0/.gitkeep | 0 .../.atuin/skills/release/SKILL.md | 213 --- reference/atuin-18.16.1/.cargo/audit.toml | 9 - .../.claude/skills/hunk/SKILL.md | 154 -- .../.claude/skills/release/SKILL.md | 269 --- reference/atuin-18.16.1/.codespellrc | 7 - .../.depot/workflows/codespell.yml | 28 - .../.depot/workflows/installer.yml | 36 - .../atuin-18.16.1/.depot/workflows/nix.yml | 33 - .../atuin-18.16.1/.depot/workflows/rust.yml | 187 -- .../.depot/workflows/shellcheck.yml | 20 - .../.depot/workflows/update-nix-deps.yml | 25 - reference/atuin-18.16.1/.dockerignore | 2 - reference/atuin-18.16.1/.gitattributes | 5 - .../.github/DISCUSSION_TEMPLATE/support.yml | 84 - reference/atuin-18.16.1/.github/FUNDING.yml | 13 - .../.github/ISSUE_TEMPLATE/bug.yaml | 39 - .../atuin-18.16.1/.github/dependabot.yml | 19 - .../.github/pull_request_template.md | 5 - .../.github/workflows/codespell.yml | 28 - .../.github/workflows/docker.yaml | 61 - .../.github/workflows/installer.yml | 38 - .../atuin-18.16.1/.github/workflows/nix.yml | 34 - .../.github/workflows/release.yml | 304 ---- .../atuin-18.16.1/.github/workflows/rust.yml | 230 --- .../.github/workflows/shellcheck.yml | 18 - .../.github/workflows/update-nix-deps.yml | 21 - reference/atuin-18.16.1/.gitignore | 17 - reference/atuin-18.16.1/.gitkeep | 0 reference/atuin-18.16.1/.mailmap | 14 - reference/atuin-18.16.1/.rustfmt.toml | 4 - reference/claude-code-2.1.88-leak/.gitkeep | 0 reference/claude-code-cli/.gitkeep | 0 .../.claude-plugin/marketplace.json | 20 - .../.claude-plugin/plugin.json | 17 - reference/claude-hud-0.0.12/.editorconfig | 12 - .../claude-hud-0.0.12/.github/CODEOWNERS | 1 - .../.github/ISSUE_TEMPLATE/bug_report.md | 21 - .../.github/ISSUE_TEMPLATE/config.yml | 5 - .../.github/ISSUE_TEMPLATE/feature_request.md | 15 - .../claude-hud-0.0.12/.github/dependabot.yml | 7 - .../.github/pull_request_template.md | 11 - .../.github/workflows/build-dist.yml | 42 - .../.github/workflows/ci.yml | 23 - .../.github/workflows/claude.yml | 48 - .../.github/workflows/release.yml | 40 - reference/claude-hud-0.0.12/.gitignore | 59 - reference/claude-hud-0.0.12/.gitkeep | 0 .../hermes-agent-2026.5.16/.dockerignore | 31 - reference/hermes-agent-2026.5.16/.env.example | 469 ----- reference/hermes-agent-2026.5.16/.envrc | 5 - .../hermes-agent-2026.5.16/.gitattributes | 2 - .../.github/ISSUE_TEMPLATE/bug_report.yml | 162 -- .../.github/ISSUE_TEMPLATE/config.yml | 11 - .../ISSUE_TEMPLATE/feature_request.yml | 85 - .../.github/ISSUE_TEMPLATE/setup_help.yml | 112 -- .../.github/PULL_REQUEST_TEMPLATE.md | 75 - .../actions/hermes-smoke-test/action.yml | 47 - .../.github/actions/nix-setup/action.yml | 18 - .../.github/dependabot.yml | 44 - .../.github/workflows/contributor-check.yml | 73 - .../.github/workflows/deploy-site.yml | 97 - .../.github/workflows/docker-publish.yml | 534 ------ .../.github/workflows/docs-site-checks.yml | 48 - .../.github/workflows/history-check.yml | 58 - .../.github/workflows/lint.yml | 202 --- .../.github/workflows/nix-lockfile-fix.yml | 254 --- .../.github/workflows/nix.yml | 117 -- .../.github/workflows/osv-scanner.yml | 67 - .../.github/workflows/skills-index.yml | 101 -- .../.github/workflows/supply-chain-audit.yml | 205 --- .../.github/workflows/tests.yml | 85 - .../.github/workflows/upload_to_pypi.yml | 163 -- .../.github/workflows/uv-lockfile-check.yml | 119 -- reference/hermes-agent-2026.5.16/.gitignore | 75 - reference/hermes-agent-2026.5.16/.gitkeep | 0 reference/hermes-agent-2026.5.16/.mailmap | 108 -- .../.plans/openai-api-server.md | 291 --- .../.plans/streaming-support.md | 705 -------- reference/openai-codex/.bazelignore | 4 - reference/openai-codex/.bazelrc | 202 --- reference/openai-codex/.bazelversion | 1 - reference/openai-codex/.codespellignore | 6 - reference/openai-codex/.codespellrc | 6 - .../.codex/environments/environment.toml | 11 - .../.codex/skills/babysit-pr/SKILL.md | 194 -- .../skills/babysit-pr/agents/openai.yaml | 4 - .../babysit-pr/references/github-api-notes.md | 82 - .../babysit-pr/references/heuristics.md | 66 - .../skills/babysit-pr/scripts/gh_pr_watch.py | 869 --------- .../babysit-pr/scripts/test_gh_pr_watch.py | 217 --- .../code-review-breaking-changes/SKILL.md | 12 - .../skills/code-review-change-size/SKILL.md | 11 - .../skills/code-review-context/SKILL.md | 13 - .../skills/code-review-testing/SKILL.md | 14 - .../.codex/skills/code-review/SKILL.md | 14 - .../.codex/skills/codex-bug/SKILL.md | 48 - .../.codex/skills/codex-issue-digest/SKILL.md | 127 -- .../codex-issue-digest/agents/openai.yaml | 4 - .../scripts/collect_issue_digest.py | 1013 ----------- .../scripts/test_collect_issue_digest.py | 749 -------- .../.codex/skills/codex-pr-body/SKILL.md | 59 - .../.codex/skills/remote-tests/SKILL.md | 16 - .../.codex/skills/test-tui/SKILL.md | 14 - .../.codex/skills/update-v8-version/SKILL.md | 72 - .../update-v8-version/agents/openai.yaml | 4 - .../openai-codex/.devcontainer/Dockerfile | 27 - .../.devcontainer/Dockerfile.secure | 82 - .../openai-codex/.devcontainer/README.md | 49 - .../.devcontainer/codex-install/package.json | 13 - .../codex-install/pnpm-lock.yaml | 85 - .../codex-install/pnpm-workspace.yaml | 12 - .../.devcontainer/devcontainer.json | 27 - .../.devcontainer/devcontainer.secure.json | 83 - .../.devcontainer/init-firewall.sh | 170 -- .../openai-codex/.devcontainer/post-start.sh | 36 - .../.devcontainer/post_install.py | 113 -- reference/openai-codex/.gitattributes | 2 - reference/openai-codex/.github/CODEOWNERS | 6 - .../.github/ISSUE_TEMPLATE/1-codex-app.yml | 54 - .../.github/ISSUE_TEMPLATE/2-extension.yml | 61 - .../.github/ISSUE_TEMPLATE/3-cli.yml | 81 - .../.github/ISSUE_TEMPLATE/4-bug-report.yml | 37 - .../ISSUE_TEMPLATE/5-feature-request.yml | 32 - .../.github/ISSUE_TEMPLATE/6-docs-issue.yml | 27 - .../actions/linux-code-sign/action.yml | 49 - .../actions/macos-code-sign/action.yml | 259 --- .../macos-code-sign/codex.entitlements.plist | 8 - .../actions/macos-code-sign/notary_helpers.sh | 46 - .../actions/prepare-bazel-ci/action.yml | 64 - .../run-argument-comment-lint/action.yml | 54 - .../.github/actions/setup-bazel-ci/action.yml | 127 -- .../.github/actions/setup-msvc-env/action.yml | 17 - .../actions/setup-msvc-env/setup-msvc-env.ps1 | 257 --- .../.github/actions/setup-rusty-v8/action.yml | 42 - .../actions/windows-code-sign/action.yml | 73 - .../.github/blob-size-allowlist.txt | 10 - .../openai-codex/.github/codex-cli-splash.png | Bin 838131 -> 0 bytes .../.github/codex/home/config.toml | 3 - .../.github/codex/labels/codex-attempt.md | 9 - .../.github/codex/labels/codex-review.md | 7 - .../.github/codex/labels/codex-rust-review.md | 139 -- .../.github/codex/labels/codex-triage.md | 7 - .../openai-codex/.github/dependabot.yaml | 42 - ...dotslash-argument-comment-lint-config.json | 24 - .../openai-codex/.github/dotslash-config.json | 124 -- .../.github/dotslash-zsh-config.json | 28 - .../.github/pull_request_template.md | 8 - .../scripts/build-codex-package-archive.sh | 172 -- .../scripts/build-zsh-release-artifact.sh | 61 - .../scripts/compute-bazel-windows-path.ps1 | 113 -- .../scripts/install-musl-build-tools.sh | 279 --- .../run-argument-comment-lint-bazel.sh | 80 - .../.github/scripts/run-bazel-ci.sh | 453 ----- .../.github/scripts/run-bazel-query-ci.sh | 84 - .../.github/scripts/rusty_v8_bazel.py | 412 ----- .../.github/scripts/rusty_v8_module_bazel.py | 243 --- .../.github/scripts/setup-dev-drive.ps1 | 62 - .../.github/scripts/test_rusty_v8_bazel.py | 413 ----- .../scripts/verify_bazel_clippy_lints.py | 234 --- .../verify_cargo_workspace_manifests.py | 391 ----- .../scripts/verify_tui_core_boundary.py | 89 - .../.github/workflows/Dockerfile.bazel | 20 - .../openai-codex/.github/workflows/README.md | 34 - .../openai-codex/.github/workflows/bazel.yml | 518 ------ .../.github/workflows/blob-size-policy.yml | 34 - .../.github/workflows/cargo-deny.yml | 29 - .../openai-codex/.github/workflows/ci.yml | 78 - .../openai-codex/.github/workflows/cla.yml | 49 - .../workflows/close-stale-contributor-prs.yml | 107 -- .../.github/workflows/codespell.yml | 30 - .../.github/workflows/issue-deduplicator.yml | 420 ----- .../.github/workflows/issue-labeler.yml | 151 -- .../rust-ci-full-nextest-platform.yml | 464 ----- .../.github/workflows/rust-ci-full.yml | 643 ------- .../.github/workflows/rust-ci.yml | 245 --- .../rust-release-argument-comment-lint.yml | 108 -- .../workflows/rust-release-prepare.yml | 57 - .../workflows/rust-release-windows.yml | 342 ---- .../.github/workflows/rust-release-zsh.yml | 103 -- .../.github/workflows/rust-release.yml | 1562 ----------------- .../.github/workflows/rusty-v8-release.yml | 313 ---- .../openai-codex/.github/workflows/sdk.yml | 160 -- .../.github/workflows/v8-canary.yml | 411 ----- reference/openai-codex/.github/workflows/zstd | 46 - reference/openai-codex/.gitignore | 94 - reference/openai-codex/.gitkeep | 0 .../openai-codex/.markdownlint-cli2.yaml | 6 - reference/openai-codex/.npmrc | 4 - reference/openai-codex/.prettierignore | 7 - reference/openai-codex/.prettierrc.toml | 8 - reference/opencode-1.15.5/.editorconfig | 9 - reference/opencode-1.15.5/.github/CODEOWNERS | 5 - .../.github/ISSUE_TEMPLATE/bug-report.yml | 66 - .../.github/ISSUE_TEMPLATE/config.yml | 5 - .../ISSUE_TEMPLATE/feature-request.yml | 19 - .../.github/ISSUE_TEMPLATE/question.yml | 10 - .../opencode-1.15.5/.github/TEAM_MEMBERS | 16 - .../.github/actions/setup-bun/action.yml | 66 - .../actions/setup-git-committer/action.yml | 43 - .../.github/publish-python-sdk.yml | 71 - .../.github/pull_request_template.md | 29 - .../.github/workflows/beta.yml | 37 - .../.github/workflows/close-issues.yml | 24 - .../.github/workflows/close-prs.yml | 50 - .../.github/workflows/compliance-close.yml | 95 - .../.github/workflows/containers.yml | 45 - .../.github/workflows/deploy.yml | 45 - .../.github/workflows/docs-locale-sync.yml | 100 -- .../.github/workflows/docs-update.yml | 72 - .../.github/workflows/duplicate-issues.yml | 177 -- .../.github/workflows/generate.yml | 51 - .../.github/workflows/nix-eval.yml | 95 - .../.github/workflows/nix-hashes.yml | 152 -- .../.github/workflows/notify-discord.yml | 14 - .../.github/workflows/opencode.yml | 34 - .../.github/workflows/pr-management.yml | 95 - .../.github/workflows/pr-standards.yml | 351 ---- .../workflows/publish-github-action.yml | 30 - .../.github/workflows/publish-vscode.yml | 37 - .../.github/workflows/publish.yml | 491 ------ .../workflows/release-github-action.yml | 29 - .../.github/workflows/review.yml | 83 - .../.github/workflows/stats.yml | 35 - .../.github/workflows/storybook.yml | 38 - .../.github/workflows/sync-zed-extension.yml | 35 - .../.github/workflows/test.yml | 166 -- .../.github/workflows/triage.yml | 37 - .../.github/workflows/typecheck.yml | 21 - reference/opencode-1.15.5/.gitignore | 32 - reference/opencode-1.15.5/.gitkeep | 0 reference/opencode-1.15.5/.gitleaksignore | 5 - reference/opencode-1.15.5/.husky/pre-push | 20 - .../.opencode/agent/duplicate-pr.md | 26 - .../opencode-1.15.5/.opencode/agent/triage.md | 43 - .../.opencode/command/ai-deps.md | 24 - .../.opencode/command/changelog.md | 49 - .../.opencode/command/commit.md | 37 - .../.opencode/command/issues.md | 23 - .../.opencode/command/learn.md | 42 - .../.opencode/command/rmslop.md | 15 - .../.opencode/command/spellcheck.md | 5 - .../.opencode/command/translate.md | 14 - reference/opencode-1.15.5/.opencode/env.d.ts | 4 - .../.opencode/glossary/README.md | 63 - .../opencode-1.15.5/.opencode/glossary/ar.md | 28 - .../opencode-1.15.5/.opencode/glossary/br.md | 34 - .../opencode-1.15.5/.opencode/glossary/bs.md | 33 - .../opencode-1.15.5/.opencode/glossary/da.md | 27 - .../opencode-1.15.5/.opencode/glossary/de.md | 27 - .../opencode-1.15.5/.opencode/glossary/es.md | 27 - .../opencode-1.15.5/.opencode/glossary/fr.md | 27 - .../opencode-1.15.5/.opencode/glossary/ja.md | 33 - .../opencode-1.15.5/.opencode/glossary/ko.md | 27 - .../opencode-1.15.5/.opencode/glossary/no.md | 38 - .../opencode-1.15.5/.opencode/glossary/pl.md | 27 - .../opencode-1.15.5/.opencode/glossary/ru.md | 27 - .../opencode-1.15.5/.opencode/glossary/th.md | 34 - .../opencode-1.15.5/.opencode/glossary/tr.md | 38 - .../.opencode/glossary/zh-cn.md | 42 - .../.opencode/glossary/zh-tw.md | 42 - .../opencode-1.15.5/.opencode/opencode.jsonc | 10 - .../.opencode/plugins/smoke-theme.json | 223 --- .../.opencode/plugins/tui-smoke.tsx | 1019 ----------- .../.opencode/skills/effect/SKILL.md | 38 - .../DEEPENING.md | 37 - .../INTERFACE-DESIGN.md | 44 - .../improve-codebase-architecture/LANGUAGE.md | 53 - .../improve-codebase-architecture/SKILL.md | 71 - .../.opencode/themes/mytheme.json | 223 --- .../.opencode/tool/github-pr-search.ts | 64 - .../.opencode/tool/github-triage.ts | 60 - reference/opencode-1.15.5/.opencode/tui.json | 19 - reference/opencode-1.15.5/.oxlintrc.json | 51 - reference/opencode-1.15.5/.prettierignore | 2 - reference/opencode-1.15.5/.zed/settings.json | 9 - 288 files changed, 2 insertions(+), 27881 deletions(-) create mode 100644 .gitignore delete mode 100644 reference/air-suite-20260518/.gitkeep delete mode 100644 reference/anthropic-skills/.claude-plugin/marketplace.json delete mode 100644 reference/anthropic-skills/.gitignore delete mode 100644 reference/anthropic-skills/.gitkeep delete mode 100755 reference/asciinema-3.2.0/.cargo/config.toml delete mode 100755 reference/asciinema-3.2.0/.gitattributes delete mode 100755 reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/bug-report.yml delete mode 100755 reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/config.yml delete mode 100755 reference/asciinema-3.2.0/.github/workflows/ci.yml delete mode 100755 reference/asciinema-3.2.0/.github/workflows/release.yml delete mode 100755 reference/asciinema-3.2.0/.gitignore delete mode 100644 reference/asciinema-3.2.0/.gitkeep delete mode 100755 reference/atuin-18.16.1/.atuin/skills/release/SKILL.md delete mode 100755 reference/atuin-18.16.1/.cargo/audit.toml delete mode 100755 reference/atuin-18.16.1/.claude/skills/hunk/SKILL.md delete mode 100755 reference/atuin-18.16.1/.claude/skills/release/SKILL.md delete mode 100755 reference/atuin-18.16.1/.codespellrc delete mode 100755 reference/atuin-18.16.1/.depot/workflows/codespell.yml delete mode 100755 reference/atuin-18.16.1/.depot/workflows/installer.yml delete mode 100755 reference/atuin-18.16.1/.depot/workflows/nix.yml delete mode 100755 reference/atuin-18.16.1/.depot/workflows/rust.yml delete mode 100755 reference/atuin-18.16.1/.depot/workflows/shellcheck.yml delete mode 100755 reference/atuin-18.16.1/.depot/workflows/update-nix-deps.yml delete mode 100755 reference/atuin-18.16.1/.dockerignore delete mode 100755 reference/atuin-18.16.1/.gitattributes delete mode 100755 reference/atuin-18.16.1/.github/DISCUSSION_TEMPLATE/support.yml delete mode 100755 reference/atuin-18.16.1/.github/FUNDING.yml delete mode 100755 reference/atuin-18.16.1/.github/ISSUE_TEMPLATE/bug.yaml delete mode 100755 reference/atuin-18.16.1/.github/dependabot.yml delete mode 100755 reference/atuin-18.16.1/.github/pull_request_template.md delete mode 100755 reference/atuin-18.16.1/.github/workflows/codespell.yml delete mode 100755 reference/atuin-18.16.1/.github/workflows/docker.yaml delete mode 100755 reference/atuin-18.16.1/.github/workflows/installer.yml delete mode 100755 reference/atuin-18.16.1/.github/workflows/nix.yml delete mode 100755 reference/atuin-18.16.1/.github/workflows/release.yml delete mode 100755 reference/atuin-18.16.1/.github/workflows/rust.yml delete mode 100755 reference/atuin-18.16.1/.github/workflows/shellcheck.yml delete mode 100755 reference/atuin-18.16.1/.github/workflows/update-nix-deps.yml delete mode 100755 reference/atuin-18.16.1/.gitignore delete mode 100644 reference/atuin-18.16.1/.gitkeep delete mode 100755 reference/atuin-18.16.1/.mailmap delete mode 100755 reference/atuin-18.16.1/.rustfmt.toml delete mode 100644 reference/claude-code-2.1.88-leak/.gitkeep delete mode 100644 reference/claude-code-cli/.gitkeep delete mode 100755 reference/claude-hud-0.0.12/.claude-plugin/marketplace.json delete mode 100755 reference/claude-hud-0.0.12/.claude-plugin/plugin.json delete mode 100755 reference/claude-hud-0.0.12/.editorconfig delete mode 100755 reference/claude-hud-0.0.12/.github/CODEOWNERS delete mode 100755 reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100755 reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/config.yml delete mode 100755 reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/feature_request.md delete mode 100755 reference/claude-hud-0.0.12/.github/dependabot.yml delete mode 100755 reference/claude-hud-0.0.12/.github/pull_request_template.md delete mode 100755 reference/claude-hud-0.0.12/.github/workflows/build-dist.yml delete mode 100755 reference/claude-hud-0.0.12/.github/workflows/ci.yml delete mode 100755 reference/claude-hud-0.0.12/.github/workflows/claude.yml delete mode 100755 reference/claude-hud-0.0.12/.github/workflows/release.yml delete mode 100755 reference/claude-hud-0.0.12/.gitignore delete mode 100644 reference/claude-hud-0.0.12/.gitkeep delete mode 100755 reference/hermes-agent-2026.5.16/.dockerignore delete mode 100755 reference/hermes-agent-2026.5.16/.env.example delete mode 100755 reference/hermes-agent-2026.5.16/.envrc delete mode 100755 reference/hermes-agent-2026.5.16/.gitattributes delete mode 100755 reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/bug_report.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/config.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/feature_request.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/setup_help.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/PULL_REQUEST_TEMPLATE.md delete mode 100755 reference/hermes-agent-2026.5.16/.github/actions/hermes-smoke-test/action.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/actions/nix-setup/action.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/dependabot.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/contributor-check.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/deploy-site.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/docker-publish.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/docs-site-checks.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/history-check.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/lint.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/nix-lockfile-fix.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/nix.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/osv-scanner.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/skills-index.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/supply-chain-audit.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/tests.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/upload_to_pypi.yml delete mode 100755 reference/hermes-agent-2026.5.16/.github/workflows/uv-lockfile-check.yml delete mode 100755 reference/hermes-agent-2026.5.16/.gitignore delete mode 100644 reference/hermes-agent-2026.5.16/.gitkeep delete mode 100755 reference/hermes-agent-2026.5.16/.mailmap delete mode 100755 reference/hermes-agent-2026.5.16/.plans/openai-api-server.md delete mode 100755 reference/hermes-agent-2026.5.16/.plans/streaming-support.md delete mode 100644 reference/openai-codex/.bazelignore delete mode 100644 reference/openai-codex/.bazelrc delete mode 100644 reference/openai-codex/.bazelversion delete mode 100644 reference/openai-codex/.codespellignore delete mode 100644 reference/openai-codex/.codespellrc delete mode 100644 reference/openai-codex/.codex/environments/environment.toml delete mode 100644 reference/openai-codex/.codex/skills/babysit-pr/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/babysit-pr/agents/openai.yaml delete mode 100644 reference/openai-codex/.codex/skills/babysit-pr/references/github-api-notes.md delete mode 100644 reference/openai-codex/.codex/skills/babysit-pr/references/heuristics.md delete mode 100755 reference/openai-codex/.codex/skills/babysit-pr/scripts/gh_pr_watch.py delete mode 100644 reference/openai-codex/.codex/skills/babysit-pr/scripts/test_gh_pr_watch.py delete mode 100644 reference/openai-codex/.codex/skills/code-review-breaking-changes/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/code-review-change-size/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/code-review-context/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/code-review-testing/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/code-review/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/codex-bug/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/codex-issue-digest/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/codex-issue-digest/agents/openai.yaml delete mode 100755 reference/openai-codex/.codex/skills/codex-issue-digest/scripts/collect_issue_digest.py delete mode 100644 reference/openai-codex/.codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py delete mode 100644 reference/openai-codex/.codex/skills/codex-pr-body/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/remote-tests/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/test-tui/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/update-v8-version/SKILL.md delete mode 100644 reference/openai-codex/.codex/skills/update-v8-version/agents/openai.yaml delete mode 100644 reference/openai-codex/.devcontainer/Dockerfile delete mode 100644 reference/openai-codex/.devcontainer/Dockerfile.secure delete mode 100644 reference/openai-codex/.devcontainer/README.md delete mode 100644 reference/openai-codex/.devcontainer/codex-install/package.json delete mode 100644 reference/openai-codex/.devcontainer/codex-install/pnpm-lock.yaml delete mode 100644 reference/openai-codex/.devcontainer/codex-install/pnpm-workspace.yaml delete mode 100644 reference/openai-codex/.devcontainer/devcontainer.json delete mode 100644 reference/openai-codex/.devcontainer/devcontainer.secure.json delete mode 100644 reference/openai-codex/.devcontainer/init-firewall.sh delete mode 100644 reference/openai-codex/.devcontainer/post-start.sh delete mode 100644 reference/openai-codex/.devcontainer/post_install.py delete mode 100644 reference/openai-codex/.gitattributes delete mode 100644 reference/openai-codex/.github/CODEOWNERS delete mode 100644 reference/openai-codex/.github/ISSUE_TEMPLATE/1-codex-app.yml delete mode 100644 reference/openai-codex/.github/ISSUE_TEMPLATE/2-extension.yml delete mode 100644 reference/openai-codex/.github/ISSUE_TEMPLATE/3-cli.yml delete mode 100644 reference/openai-codex/.github/ISSUE_TEMPLATE/4-bug-report.yml delete mode 100644 reference/openai-codex/.github/ISSUE_TEMPLATE/5-feature-request.yml delete mode 100644 reference/openai-codex/.github/ISSUE_TEMPLATE/6-docs-issue.yml delete mode 100644 reference/openai-codex/.github/actions/linux-code-sign/action.yml delete mode 100644 reference/openai-codex/.github/actions/macos-code-sign/action.yml delete mode 100644 reference/openai-codex/.github/actions/macos-code-sign/codex.entitlements.plist delete mode 100644 reference/openai-codex/.github/actions/macos-code-sign/notary_helpers.sh delete mode 100644 reference/openai-codex/.github/actions/prepare-bazel-ci/action.yml delete mode 100644 reference/openai-codex/.github/actions/run-argument-comment-lint/action.yml delete mode 100644 reference/openai-codex/.github/actions/setup-bazel-ci/action.yml delete mode 100644 reference/openai-codex/.github/actions/setup-msvc-env/action.yml delete mode 100644 reference/openai-codex/.github/actions/setup-msvc-env/setup-msvc-env.ps1 delete mode 100644 reference/openai-codex/.github/actions/setup-rusty-v8/action.yml delete mode 100644 reference/openai-codex/.github/actions/windows-code-sign/action.yml delete mode 100644 reference/openai-codex/.github/blob-size-allowlist.txt delete mode 100644 reference/openai-codex/.github/codex-cli-splash.png delete mode 100644 reference/openai-codex/.github/codex/home/config.toml delete mode 100644 reference/openai-codex/.github/codex/labels/codex-attempt.md delete mode 100644 reference/openai-codex/.github/codex/labels/codex-review.md delete mode 100644 reference/openai-codex/.github/codex/labels/codex-rust-review.md delete mode 100644 reference/openai-codex/.github/codex/labels/codex-triage.md delete mode 100644 reference/openai-codex/.github/dependabot.yaml delete mode 100644 reference/openai-codex/.github/dotslash-argument-comment-lint-config.json delete mode 100644 reference/openai-codex/.github/dotslash-config.json delete mode 100644 reference/openai-codex/.github/dotslash-zsh-config.json delete mode 100644 reference/openai-codex/.github/pull_request_template.md delete mode 100644 reference/openai-codex/.github/scripts/build-codex-package-archive.sh delete mode 100755 reference/openai-codex/.github/scripts/build-zsh-release-artifact.sh delete mode 100644 reference/openai-codex/.github/scripts/compute-bazel-windows-path.ps1 delete mode 100644 reference/openai-codex/.github/scripts/install-musl-build-tools.sh delete mode 100755 reference/openai-codex/.github/scripts/run-argument-comment-lint-bazel.sh delete mode 100755 reference/openai-codex/.github/scripts/run-bazel-ci.sh delete mode 100755 reference/openai-codex/.github/scripts/run-bazel-query-ci.sh delete mode 100644 reference/openai-codex/.github/scripts/rusty_v8_bazel.py delete mode 100644 reference/openai-codex/.github/scripts/rusty_v8_module_bazel.py delete mode 100644 reference/openai-codex/.github/scripts/setup-dev-drive.ps1 delete mode 100644 reference/openai-codex/.github/scripts/test_rusty_v8_bazel.py delete mode 100644 reference/openai-codex/.github/scripts/verify_bazel_clippy_lints.py delete mode 100644 reference/openai-codex/.github/scripts/verify_cargo_workspace_manifests.py delete mode 100644 reference/openai-codex/.github/scripts/verify_tui_core_boundary.py delete mode 100644 reference/openai-codex/.github/workflows/Dockerfile.bazel delete mode 100644 reference/openai-codex/.github/workflows/README.md delete mode 100644 reference/openai-codex/.github/workflows/bazel.yml delete mode 100644 reference/openai-codex/.github/workflows/blob-size-policy.yml delete mode 100644 reference/openai-codex/.github/workflows/cargo-deny.yml delete mode 100644 reference/openai-codex/.github/workflows/ci.yml delete mode 100644 reference/openai-codex/.github/workflows/cla.yml delete mode 100644 reference/openai-codex/.github/workflows/close-stale-contributor-prs.yml delete mode 100644 reference/openai-codex/.github/workflows/codespell.yml delete mode 100644 reference/openai-codex/.github/workflows/issue-deduplicator.yml delete mode 100644 reference/openai-codex/.github/workflows/issue-labeler.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-ci-full-nextest-platform.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-ci-full.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-ci.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-release-argument-comment-lint.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-release-prepare.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-release-windows.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-release-zsh.yml delete mode 100644 reference/openai-codex/.github/workflows/rust-release.yml delete mode 100644 reference/openai-codex/.github/workflows/rusty-v8-release.yml delete mode 100644 reference/openai-codex/.github/workflows/sdk.yml delete mode 100644 reference/openai-codex/.github/workflows/v8-canary.yml delete mode 100755 reference/openai-codex/.github/workflows/zstd delete mode 100644 reference/openai-codex/.gitignore delete mode 100644 reference/openai-codex/.gitkeep delete mode 100644 reference/openai-codex/.markdownlint-cli2.yaml delete mode 100644 reference/openai-codex/.npmrc delete mode 100644 reference/openai-codex/.prettierignore delete mode 100644 reference/openai-codex/.prettierrc.toml delete mode 100755 reference/opencode-1.15.5/.editorconfig delete mode 100755 reference/opencode-1.15.5/.github/CODEOWNERS delete mode 100755 reference/opencode-1.15.5/.github/ISSUE_TEMPLATE/bug-report.yml delete mode 100755 reference/opencode-1.15.5/.github/ISSUE_TEMPLATE/config.yml delete mode 100755 reference/opencode-1.15.5/.github/ISSUE_TEMPLATE/feature-request.yml delete mode 100755 reference/opencode-1.15.5/.github/ISSUE_TEMPLATE/question.yml delete mode 100755 reference/opencode-1.15.5/.github/TEAM_MEMBERS delete mode 100755 reference/opencode-1.15.5/.github/actions/setup-bun/action.yml delete mode 100755 reference/opencode-1.15.5/.github/actions/setup-git-committer/action.yml delete mode 100755 reference/opencode-1.15.5/.github/publish-python-sdk.yml delete mode 100755 reference/opencode-1.15.5/.github/pull_request_template.md delete mode 100755 reference/opencode-1.15.5/.github/workflows/beta.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/close-issues.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/close-prs.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/compliance-close.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/containers.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/deploy.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/docs-locale-sync.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/docs-update.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/duplicate-issues.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/generate.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/nix-eval.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/nix-hashes.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/notify-discord.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/opencode.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/pr-management.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/pr-standards.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/publish-github-action.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/publish-vscode.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/publish.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/release-github-action.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/review.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/stats.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/storybook.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/sync-zed-extension.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/test.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/triage.yml delete mode 100755 reference/opencode-1.15.5/.github/workflows/typecheck.yml delete mode 100755 reference/opencode-1.15.5/.gitignore delete mode 100644 reference/opencode-1.15.5/.gitkeep delete mode 100755 reference/opencode-1.15.5/.gitleaksignore delete mode 100755 reference/opencode-1.15.5/.husky/pre-push delete mode 100755 reference/opencode-1.15.5/.opencode/agent/duplicate-pr.md delete mode 100755 reference/opencode-1.15.5/.opencode/agent/triage.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/ai-deps.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/changelog.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/commit.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/issues.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/learn.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/rmslop.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/spellcheck.md delete mode 100755 reference/opencode-1.15.5/.opencode/command/translate.md delete mode 100755 reference/opencode-1.15.5/.opencode/env.d.ts delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/README.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/ar.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/br.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/bs.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/da.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/de.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/es.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/fr.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/ja.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/ko.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/no.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/pl.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/ru.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/th.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/tr.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/zh-cn.md delete mode 100755 reference/opencode-1.15.5/.opencode/glossary/zh-tw.md delete mode 100755 reference/opencode-1.15.5/.opencode/opencode.jsonc delete mode 100755 reference/opencode-1.15.5/.opencode/plugins/smoke-theme.json delete mode 100755 reference/opencode-1.15.5/.opencode/plugins/tui-smoke.tsx delete mode 100755 reference/opencode-1.15.5/.opencode/skills/effect/SKILL.md delete mode 100755 reference/opencode-1.15.5/.opencode/skills/improve-codebase-architecture/DEEPENING.md delete mode 100755 reference/opencode-1.15.5/.opencode/skills/improve-codebase-architecture/INTERFACE-DESIGN.md delete mode 100755 reference/opencode-1.15.5/.opencode/skills/improve-codebase-architecture/LANGUAGE.md delete mode 100755 reference/opencode-1.15.5/.opencode/skills/improve-codebase-architecture/SKILL.md delete mode 100755 reference/opencode-1.15.5/.opencode/themes/mytheme.json delete mode 100755 reference/opencode-1.15.5/.opencode/tool/github-pr-search.ts delete mode 100755 reference/opencode-1.15.5/.opencode/tool/github-triage.ts delete mode 100755 reference/opencode-1.15.5/.opencode/tui.json delete mode 100755 reference/opencode-1.15.5/.oxlintrc.json delete mode 100755 reference/opencode-1.15.5/.prettierignore delete mode 100755 reference/opencode-1.15.5/.zed/settings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..167fb96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Third-party reference source (working-copy only, see AirPlan DD §23) — not committed +/reference/ diff --git a/reference/air-suite-20260518/.gitkeep b/reference/air-suite-20260518/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/anthropic-skills/.claude-plugin/marketplace.json b/reference/anthropic-skills/.claude-plugin/marketplace.json deleted file mode 100644 index 03d8e71..0000000 --- a/reference/anthropic-skills/.claude-plugin/marketplace.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "anthropic-agent-skills", - "owner": { - "name": "Keith Lazuka", - "email": "klazuka@anthropic.com" - }, - "metadata": { - "description": "Anthropic example skills", - "version": "1.0.0" - }, - "plugins": [ - { - "name": "document-skills", - "description": "Collection of document processing suite including Excel, Word, PowerPoint, and PDF capabilities", - "source": "./", - "strict": false, - "skills": [ - "./skills/xlsx", - "./skills/docx", - "./skills/pptx", - "./skills/pdf" - ] - }, - { - "name": "example-skills", - "description": "Collection of example skills demonstrating various capabilities including skill creation, MCP building, visual design, algorithmic art, internal communications, web testing, artifact building, Slack GIFs, and theme styling", - "source": "./", - "strict": false, - "skills": [ - "./skills/algorithmic-art", - "./skills/brand-guidelines", - "./skills/canvas-design", - "./skills/doc-coauthoring", - "./skills/frontend-design", - "./skills/internal-comms", - "./skills/mcp-builder", - "./skills/skill-creator", - "./skills/slack-gif-creator", - "./skills/theme-factory", - "./skills/web-artifacts-builder", - "./skills/webapp-testing" - ] - } - , - { - "name": "claude-api", - "description": "Claude API and SDK documentation skill for building LLM-powered applications", - "source": "./", - "strict": false, - "skills": [ - "./skills/claude-api" - ] - } - ] -} diff --git a/reference/anthropic-skills/.gitignore b/reference/anthropic-skills/.gitignore deleted file mode 100644 index 4ff6017..0000000 --- a/reference/anthropic-skills/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.DS_Store -__pycache__/ -.idea/ -.vscode/ - diff --git a/reference/anthropic-skills/.gitkeep b/reference/anthropic-skills/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/asciinema-3.2.0/.cargo/config.toml b/reference/asciinema-3.2.0/.cargo/config.toml deleted file mode 100755 index 1309df4..0000000 --- a/reference/asciinema-3.2.0/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[env] -RUST_TEST_THREADS = "1" diff --git a/reference/asciinema-3.2.0/.gitattributes b/reference/asciinema-3.2.0/.gitattributes deleted file mode 100755 index fbbfa57..0000000 --- a/reference/asciinema-3.2.0/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -assets/asciinema-player.* linguist-vendored diff --git a/reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/bug-report.yml b/reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100755 index 2a04093..0000000 --- a/reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Bug Report -description: Report a bug to help improve asciinema CLI -body: - - type: markdown - attributes: - value: | - **This is a bug tracker for asciinema CLI (the recorder).** - - - If your issue is with the JavaScript player or server, please open an issue in the related repository - - If you're experiencing issues with asciinema.org, contact admin@asciinema.org - - For feature requests, questions, and discussions, use the [forum](https://discourse.asciinema.org) or [GitHub discussions](https://github.com/orgs/asciinema/discussions) - - Thanks for taking the time to report a bug! Please fill out the sections below. - - - type: checkboxes - id: checks - attributes: - label: Pre-submission checks - description: Please confirm the following before submitting your bug report - options: - - label: I have searched existing issues and this bug has not been reported yet - required: true - - label: This is a bug report for asciinema CLI (not player or server) - required: true - - - type: textarea - id: bug-description - attributes: - label: Bug Description - description: A clear and concise description of what the bug is. - placeholder: Describe the bug... - validations: - required: true - - - type: textarea - id: reproduction-steps - attributes: - label: Steps to Reproduce - description: Provide detailed steps to reproduce the behavior - placeholder: | - 1. Run command `asciinema ...` - 2. Do action '...' - 3. Observe error - validations: - required: true - - - type: textarea - id: expected-behavior - attributes: - label: Expected Behavior - description: A clear and concise description of what you expected to happen. - placeholder: What should have happened instead? - validations: - required: true - - - type: input - id: os-version - attributes: - label: Operating System - description: Which OS and version are you using? - placeholder: e.g., Ubuntu 24.04, macOS 14.0, Fedora 39 - validations: - required: true - - - type: input - id: cli-version - attributes: - label: asciinema CLI Version - description: What version of asciinema CLI are you using? Run `asciinema --version` to check. - placeholder: e.g., 2.4.0 - validations: - required: true - - - type: dropdown - id: installation-method - attributes: - label: Installation Method - description: How did you install asciinema CLI? - options: - - Package manager (apt, yum, brew, etc.) - - pip/pipx - - Built from source - - Downloaded binary - - Other - validations: - required: true - - - type: textarea - id: terminal-info - attributes: - label: Terminal Information - description: What terminal emulator and shell are you using? - placeholder: | - Terminal: e.g., GNOME Terminal, iTerm2, Ghostty - Shell: e.g., bash 5.1, zsh 5.8, fish 3.6 - - - type: textarea - id: additional-context - attributes: - label: Additional Context - description: Add any other context, screenshots, or relevant information about the problem here. diff --git a/reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/config.yml b/reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100755 index 10beddd..0000000 --- a/reference/asciinema-3.2.0/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Forum - url: https://discourse.asciinema.org/ - about: Ideas, feature requests, help requests, questions and general discussions should be posted here. - - name: GitHub discussions - url: https://github.com/orgs/asciinema/discussions - about: Ideas, feature requests, help requests, questions and general discussions should be posted here. diff --git a/reference/asciinema-3.2.0/.github/workflows/ci.yml b/reference/asciinema-3.2.0/.github/workflows/ci.yml deleted file mode 100755 index bbafd6c..0000000 --- a/reference/asciinema-3.2.0/.github/workflows/ci.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: CI - -on: - push: - branches: ["develop"] - pull_request: - branches: ["develop"] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - rust: [default, msrv] - - steps: - - uses: actions/checkout@v5 - - - name: Install Nix - uses: nixbuild/nix-quick-install-action@v34 - - - name: Setup Nix cache - uses: nix-community/cache-nix-action@v6 - with: - primary-key: nix-${{ runner.os }}-${{ matrix.rust }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}-${{ matrix.rust }}- - - - name: Build - run: nix develop .#${{ matrix.rust }} --command cargo build --verbose - - - name: Run cargo tests - run: nix develop .#${{ matrix.rust }} --command cargo test --verbose - - - name: Run integration tests - run: nix develop .#${{ matrix.rust }} --command tests/integration.sh - - - name: Check formatting - run: nix develop .#${{ matrix.rust }} --command cargo fmt --check - - - name: Lint with clippy - run: nix develop .#${{ matrix.rust }} --command cargo clippy diff --git a/reference/asciinema-3.2.0/.github/workflows/release.yml b/reference/asciinema-3.2.0/.github/workflows/release.yml deleted file mode 100755 index af2b04a..0000000 --- a/reference/asciinema-3.2.0/.github/workflows/release.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Release - -permissions: - contents: write - -on: - push: - tags: - - v[0-9]+.* - -jobs: - create-release: - name: Create GH release draft - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v5 - - - name: Create the release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release create ${{ github.ref_name }} --draft --verify-tag --title ${{ github.ref_name }} - - upload-binary: - needs: create-release - name: ${{ matrix.target }} - runs-on: ${{ matrix.os }} - - strategy: - matrix: - include: - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - use-cross: false - - - os: ubuntu-latest - target: x86_64-unknown-linux-musl - use-cross: false - - - os: ubuntu-latest - target: aarch64-unknown-linux-gnu - use-cross: true - - - os: macos-latest - target: x86_64-apple-darwin - use-cross: false - - - os: macos-latest - target: aarch64-apple-darwin - use-cross: false - - env: - CARGO: cargo - - steps: - - uses: actions/checkout@v5 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Install cross - if: matrix.use-cross - uses: taiki-e/install-action@v2 - with: - tool: cross - - - name: Overwrite build command env variable - if: matrix.use-cross - shell: bash - run: echo "CARGO=cross" >> $GITHUB_ENV - - - name: Install build deps - shell: bash - run: | - if [[ ${{ matrix.target }} == x86_64-unknown-linux-musl ]]; then - sudo apt-get update - sudo apt-get install -y musl-tools - fi - - - name: Build release binary - run: ${{ env.CARGO }} build --release --locked --target ${{ matrix.target }} - - - name: Upload the binary to the release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - mv target/${{ matrix.target }}/release/asciinema target/release/asciinema-${{ matrix.target }} - gh release upload ${{ github.ref_name }} target/release/asciinema-${{ matrix.target }} diff --git a/reference/asciinema-3.2.0/.gitignore b/reference/asciinema-3.2.0/.gitignore deleted file mode 100755 index 500c22a..0000000 --- a/reference/asciinema-3.2.0/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target/ -.envrc -.direnv -/result diff --git a/reference/asciinema-3.2.0/.gitkeep b/reference/asciinema-3.2.0/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/atuin-18.16.1/.atuin/skills/release/SKILL.md b/reference/atuin-18.16.1/.atuin/skills/release/SKILL.md deleted file mode 100755 index cace07b..0000000 --- a/reference/atuin-18.16.1/.atuin/skills/release/SKILL.md +++ /dev/null @@ -1,213 +0,0 @@ ---- -name: release -description: > - Orchestrate a multi-step Atuin CLI release — version bumping, changelog - generation, PR creation, tagging, and crates.io publishing. Invoke with - /release or /release . -disable-model-invocation: true -argument-hint: [version] ---- - -# Atuin CLI Release - -You are orchestrating a release of the Atuin CLI. Follow the steps below -**in order**, pausing at each checkpoint for user confirmation. Do not skip -steps or combine them. - -## Current State - -- Workspace version: !`sed -n '/^\[workspace\.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml` -- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo "none"` -- Suggested next version: !`git-cliff --bumped-version 2>/dev/null | sed 's/^v//' || echo "(unknown)"` - ---- - -## Step 1 — Check Dependencies - -Verify these tools are installed: `git`, `gsed`, `cargo`, `gh`, `git-cliff`. - -Use `command -v` for each. If any are missing, report which ones and stop. - -Remember to use `gsed`, or else macOS flags to regular `sed`, later in the workflow. - ---- - -## Step 2 — Determine Version - -The target version may be provided as `$ARGUMENTS`. If it's empty, use -AskUserQuestion to ask for the new version (show the current state above -for reference). - -After determining the version: -- If it contains a `-` (e.g. `18.15.0-beta.1`), it is a **prerelease**. - Note this — it affects changelog and publish behavior later. -- Show the user: `current → new` and whether it's a prerelease. -- **Checkpoint:** Ask the user to confirm before proceeding. - ---- - -## Step 3 — Set Up Working Directory - -Clone a fresh copy into a temp directory: - -```bash -WORKDIR=$(mktemp -d) -git clone git@github.com:atuinsh/atuin.git "$WORKDIR" -``` - -Print the working directory path so the user can find it if needed. - -NOTE: -ALL subsequent Bash commands run from `$WORKDIR`. - ---- - -## Step 4 — Create Branch & Update Versions - -1. Create a release branch named after the version (no `v` prefix): - `git checkout -b ` - -2. Replace the old version with the new one in all `Cargo.toml` files. - **Escape dots** in the old version so sed treats them literally: - - ```bash - VERSION_PATTERN="${OLD_VERSION//./\\.}" - find . -type f -name 'Cargo.toml' -not -path './.git/*' \ - -exec gsed -i "s/$VERSION_PATTERN/$NEW_VERSION/g" {} \; - ``` - -3. Run `cargo check` to update `Cargo.lock`. - -4. Show `git diff --stat` and the version-related lines from the diff: - ```bash - git diff --unified=0 -- '*.toml' | grep '^\+.*version' | grep -vF '+++' - ``` - Remember to use macOS grep arguments on macOS systems. - -5. Verify the workspace version was actually updated by re-reading it - from `Cargo.toml`. - -6. **Checkpoint:** Show the diff summary and ask the user to confirm the - version changes look correct. - ---- - -## Step 5 — Update Changelog - -The changelog strategy differs for prereleases vs stable releases: - -- **Prerelease:** Maintain a running `## [unreleased]` section containing - all changes since the last stable release. Use: - `git-cliff --unreleased --strip all` - (cliff.toml's `ignore_tags` already ignores beta/alpha tags, so - `--unreleased` spans back to the last stable release automatically.) - -- **Stable release:** Generate a versioned entry that replaces the - `[unreleased]` section. Use: - `git-cliff --unreleased --tag "v" --strip all` - -Then update `CHANGELOG.md`: - -1. If an existing `## [unreleased]` or `## [Unreleased]` section exists, - **remove it entirely** (the heading and all content up to the next - `## ` heading). - -2. Insert the new entry before the first existing `## ` version heading. - -3. **Checkpoint:** Read and display the new changelog entry to the user. - Ask if they want any edits. If so, make the requested changes using - the Edit tool. Repeat until they're satisfied. - ---- - -## Step 6 — Commit & Push - -Stage all changes and commit: - -``` -chore(release): prepare for release -``` - -Push the branch with `--set-upstream origin`. - ---- - -## Step 7 — Create PR & Wait for Merge - -### Create the PR - -Extract the changelog entry body (everything between the new `## ` heading -and the next one) for the PR description. - -For prereleases, the heading to match is `## [unreleased]`. -For stable releases, it's `## ` (escape dots in the awk pattern). - -Create the PR: -```bash -gh pr create \ - --title "chore(release): prepare for release " \ - --body "" \ - --repo atuinsh/atuin - --draft -``` - -Show the PR URL to the user. Tell the user to go review and merge the PR. - -When the user reports the PR is merged, proceed to the next step. - ---- - -## Step 8 — Tag Release - -Back in the working directory: - -```bash -git checkout main -git pull -git tag "v" -git push --tags -``` - -Tell the user the tag was pushed and the release CI workflow has been -triggered. - ---- - -## Step 9 — Publish to crates.io - -**If this is a prerelease**, skip this step entirely and tell the user. - -**If this is a stable release**, ask the user whether to publish. - -If yes, publish each crate **in dependency order** using `--no-verify` -(the code already passed CI, and verification fails when crates.io -hasn't indexed a freshly-published dependency yet): - -``` -atuin-common, atuin-client, atuin-ai, atuin-dotfiles, atuin-history, -atuin-nucleo/matcher, atuin-nucleo, atuin-daemon, atuin-kv, -atuin-scripts, atuin-server-database, atuin-server-postgres, -atuin-server-sqlite, atuin-server, atuin-pty-proxy, atuin -``` - -For each crate, run from `crates/`: -```bash -cargo publish --no-verify 2>&1 -``` - -If it fails with "already uploaded", report it as a skip (not an error) — -some crates like `atuin-nucleo` are versioned independently and may -already be published at their current version. - -If it fails for any other reason, stop and report the error. - ---- - -## Completion - -Summarize what was done: -- Version released -- PR URL -- Tag name -- Which crates were published (if any) -- Working directory path and how to clean it up (`rm -rf`) diff --git a/reference/atuin-18.16.1/.cargo/audit.toml b/reference/atuin-18.16.1/.cargo/audit.toml deleted file mode 100755 index 11cef85..0000000 --- a/reference/atuin-18.16.1/.cargo/audit.toml +++ /dev/null @@ -1,9 +0,0 @@ -[advisories] -ignore = [ - # This is a vuln on RSA. RSA is in our lockfile, but not in cargo-tree. - # It is a issue with sqlx/cargo, and does not affect Atuin. - # See: - # - https://github.com/launchbadge/sqlx/issues/3211 - # - https://github.com/rust-lang/cargo/issues/10801 - "RUSTSEC-2023-0071" -] diff --git a/reference/atuin-18.16.1/.claude/skills/hunk/SKILL.md b/reference/atuin-18.16.1/.claude/skills/hunk/SKILL.md deleted file mode 100755 index 6670322..0000000 --- a/reference/atuin-18.16.1/.claude/skills/hunk/SKILL.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -name: hunk-review -description: Interacts with live Hunk diff review sessions via CLI. Inspects review focus, navigates files and hunks, reloads session contents, and adds inline review comments. Use when the user has a Hunk session running or wants to review diffs interactively. ---- - -# Hunk Review - -Hunk is an interactive terminal diff viewer. The TUI is for the user -- do NOT run `hunk diff`, `hunk show`, or other interactive commands directly. Use `hunk session *` CLI commands to inspect and control live sessions through the local daemon. - -If no session exists, ask the user to launch Hunk in their terminal first. - -## Workflow - -```text -1. hunk session list # find live sessions -2. hunk session get --repo . # inspect path / repo / source -3. hunk session review --repo . --json # inspect file/hunk structure first -4. hunk session review --repo . --include-patch --json # opt into raw diff text only when needed -5. hunk session context --repo . # check current focus when needed -6. hunk session navigate ... # move to the right place -7. hunk session reload -- # swap contents if needed -8. hunk session comment add ... # leave one review note -9. hunk session comment apply ... # apply many agent notes in one stdin batch -``` - -## Session selection - -Most session commands accept: - -- `--repo ` -- match the live session by its current loaded repo root (most common) -- `` -- match by exact ID (use when multiple sessions share a repo) -- If only one session exists, it auto-resolves - -`reload` also supports: - -- `--session-path ` -- match the live Hunk window by its current working directory -- `--source ` -- load the replacement `diff` / `show` command from a different directory - -Use `--source` only for advanced reloads where the live session you want to control is not already associated with the checkout you want to load next. For a normal worktree session, prefer selecting it directly with `--repo /path/to/worktree`. - -## Commands - -### Inspect - -```bash -hunk session list [--json] -hunk session get (--repo . | ) [--json] -hunk session context (--repo . | ) [--json] -hunk session review (--repo . | ) [--json] [--include-patch] -``` - -- `get` shows the session `Path`, `Repo`, and `Source`, which helps when choosing between `--repo` and `--session-path` -- `Repo` is what `--repo` matches; `Path` is what `--session-path` matches -- `review --json` returns file and hunk structure by default; add `--include-patch` only when a caller truly needs raw unified diff text - -### Navigate - -Absolute navigation requires `--file` and exactly one of `--hunk`, `--new-line`, or `--old-line`: - -```bash -hunk session navigate --repo . --file src/App.tsx --hunk 2 -hunk session navigate --repo . --file src/App.tsx --new-line 372 -hunk session navigate --repo . --file src/App.tsx --old-line 355 -``` - -Relative comment navigation jumps between annotated hunks and does not require `--file`: - -```bash -hunk session navigate --repo . --next-comment -hunk session navigate --repo . --prev-comment -``` - -- `--hunk ` is 1-based -- `--new-line` / `--old-line` are 1-based line numbers on that diff side -- Use either `--next-comment` or `--prev-comment`, not both - -### Reload - -Swaps the live session's contents. Pass a Hunk review command after `--`: - -```bash -hunk session reload --repo . -- diff -hunk session reload --repo . -- diff main...feature -- src/ui -hunk session reload --repo . -- show HEAD~1 -hunk session reload --repo . -- show HEAD~1 -- README.md -hunk session reload --repo /path/to/worktree -- diff -hunk session reload --session-path /path/to/live-window --source /path/to/other-checkout -- diff -``` - -- Always include `--` before the nested Hunk command -- `--repo` or `` usually selects the session you want -- `--source` is advanced: it does not select the session; it only changes where the replacement review command runs -- If the live session is already showing the target worktree, prefer `hunk session reload --repo /path/to/worktree -- diff` -- `--session-path` targets the live window when you need to keep session selection separate from reload source - -### Comments - -```bash -hunk session comment add --repo . --file README.md --new-line 103 --summary "Tighten this wording" [--rationale "..."] [--author "agent"] [--focus] -printf '%s\n' '{"comments":[{"filePath":"README.md","newLine":103,"summary":"Tighten this wording"}]}' | hunk session comment apply --repo . --stdin [--focus] -hunk session comment list --repo . [--file README.md] -hunk session comment rm --repo . -hunk session comment clear --repo . --yes [--file README.md] -``` - -- `comment add` is best for one note; `comment apply` is best when an agent already has several notes ready -- `comment add` requires `--file`, `--summary`, and exactly one of `--old-line` or `--new-line` -- `comment apply` payload items require `filePath`, `summary`, and exactly one target such as `hunk`, `hunkNumber`, `oldLine`, or `newLine` -- `comment apply` reads a JSON batch from stdin and validates the full batch before mutating the live session -- Pass `--focus` when you want to jump to the new note or the first note in a batch -- `comment list` and `comment clear` accept optional `--file` -- Quote `--summary` and `--rationale` defensively in the shell - -## New files in working-tree reviews - -`hunk diff` includes untracked files by default. If the user wants tracked changes only, reload with `--exclude-untracked`: - -```bash -hunk session reload --repo . -- diff --exclude-untracked -``` - -## Guiding a review - -The user may ask you to walk them through a changeset or review code using Hunk. Start with `hunk session review --json` to understand the file/hunk structure without inflating agent context, then use `--include-patch` only for the files you truly need to read in raw diff form. Use `context` and `navigate` to line up the user's current view before adding comments. - -Your role is to narrate: steer the user's view to what matters and leave comments that explain what they're looking at. - -Typical flow: - -1. Load the right content (`reload` if needed) -2. Navigate to the first interesting file / hunk -3. Add a comment explaining what's happening and why -4. If you already have several notes ready, prefer one `comment apply` batch over many separate shell invocations -5. Summarize when done - -Guidelines: - -- Work in the order that tells the clearest story, not necessarily file order -- Navigate before commenting so the user sees the code you're discussing -- Use `comment apply` for agent-generated batches and `comment add` for one-off notes -- Use `--focus` sparingly when the note itself should actively steer the review -- Keep comments focused: intent, structure, risks, or follow-ups -- Don't comment on every hunk -- highlight what the user wouldn't spot themselves - -## Common errors - -- **"No visible diff file matches ..."** -- the file is not in the loaded review. Check `context`, then `reload` if needed. -- **"No active Hunk sessions"** -- ask the user to open Hunk in their terminal. -- **"Multiple active sessions match"** -- pass `` explicitly. -- **"No active Hunk session matches session path ..."** -- for advanced split-path reloads, verify the live window `Path` via `hunk session get` or `list`, then use `--session-path`. -- **"Pass the replacement Hunk command after `--`"** -- include `--` before the nested `diff` / `show` command. -- **"Pass --stdin to read batch comments from stdin JSON."** -- `comment apply` only reads its batch payload from stdin. -- **"Specify exactly one navigation target"** -- pick one of `--hunk`, `--old-line`, or `--new-line`. -- **"Specify either --next-comment or --prev-comment, not both."** -- choose one comment-navigation direction. diff --git a/reference/atuin-18.16.1/.claude/skills/release/SKILL.md b/reference/atuin-18.16.1/.claude/skills/release/SKILL.md deleted file mode 100755 index 7884923..0000000 --- a/reference/atuin-18.16.1/.claude/skills/release/SKILL.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -name: release -description: > - Orchestrate a multi-step Atuin CLI release — version bumping, changelog - generation, PR creation, tagging, and crates.io publishing. Invoke with - /release or /release . -disable-model-invocation: true -argument-hint: [version] ---- - -# Atuin CLI Release - -You are orchestrating a release of the Atuin CLI. Follow the steps below -**in order**, pausing at each checkpoint for user confirmation. Do not skip -steps or combine them. - -## Current State - -- Workspace version: !`sed -n '/^\[workspace\.package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml` -- Latest tag: !`git describe --tags --abbrev=0 2>/dev/null || echo "none"` -- Suggested next version: !`git-cliff --bumped-version 2>/dev/null | sed 's/^v//' || echo "(unknown)"` - ---- - -## Step 1 — Check Dependencies - -Verify these tools are installed: `git`, `gsed`, `cargo`, `gh`, `git-cliff`. - -Use `command -v` for each. If any are missing, report which ones and stop. - ---- - -## Step 2 — Determine Version - -The target version may be provided as `$ARGUMENTS`. If it's empty, use -AskUserQuestion to ask for the new version (show the current state above -for reference). - -After determining the version: -- If it contains a `-` (e.g. `18.15.0-beta.1`), it is a **prerelease**. - Note this — it affects changelog and publish behavior later. -- Show the user: `current → new` and whether it's a prerelease. -- **Checkpoint:** Ask the user to confirm before proceeding. - ---- - -## Step 3 — Set Up Working Directory - -Clone a fresh copy into a temp directory: - -```bash -WORKDIR=$(mktemp -d) -git clone git@github.com:atuinsh/atuin.git "$WORKDIR" -``` - -Print the working directory path so the user can find it if needed. -All subsequent Bash commands run from `$WORKDIR`. - ---- - -## Step 4 — Create Branch & Update Versions - -1. Create a release branch named after the version (no `v` prefix): - `git checkout -b ` - -2. Replace the old version with the new one in all `Cargo.toml` files. - **Escape dots** in the old version so sed treats them literally: - - ```bash - VERSION_PATTERN="${OLD_VERSION//./\\.}" - find . -type f -name 'Cargo.toml' -not -path './.git/*' \ - -exec gsed -i "s/$VERSION_PATTERN/$NEW_VERSION/g" {} \; - ``` - -3. Run `cargo check` to update `Cargo.lock`. - -4. Show `git diff --stat` and the version-related lines from the diff: - ```bash - git diff --unified=0 -- '*.toml' | grep -E '^\+.*version' | grep -v '^\+\+\+' - ``` - -5. Verify the workspace version was actually updated by re-reading it - from `Cargo.toml`. - -6. **Checkpoint:** Show the diff summary and ask the user to confirm the - version changes look correct. - ---- - -## Step 5 — Update Changelog - -The changelog strategy differs for prereleases vs stable releases: - -- **Prerelease:** Maintain a running `## [unreleased]` section containing - all changes since the last stable release. Use: - `git-cliff --unreleased --strip all` - (cliff.toml's `ignore_tags` already ignores beta/alpha tags, so - `--unreleased` spans back to the last stable release automatically.) - -- **Stable release:** Generate a versioned entry that replaces the - `[unreleased]` section. Use: - `git-cliff --unreleased --tag "v" --strip all` - -Then update `CHANGELOG.md`: - -1. If an existing `## [unreleased]` or `## [Unreleased]` section exists, - **remove it entirely** (the heading and all content up to the next - `## ` heading). - -2. Insert the new entry before the first existing `## ` version heading. - -3. **Checkpoint:** Read and display the new changelog entry to the user. - Ask if they want any edits. If so, make the requested changes using - the Edit tool. Repeat until they're satisfied. - ---- - -## Step 6 — Commit & Push - -Stage all changes and commit: - -``` -chore(release): prepare for release -``` - -Push the branch with `--set-upstream origin`. - ---- - -## Step 7 — Create PR & Wait for Merge - -### Create the PR - -Extract the changelog entry body (everything between the new `## ` heading -and the next one) for the PR description. - -For prereleases, the heading to match is `## [unreleased]`. -For stable releases, it's `## ` (escape dots in the awk pattern). - -Create the PR: -```bash -gh pr create \ - --title "chore(release): prepare for release " \ - --body "" \ - --repo atuinsh/atuin -``` - -Show the PR URL to the user. - -### Wait for merge - -Start a **persistent Monitor** that polls the PR status every 30 seconds. -The monitor script must: -- **Only emit output** on meaningful state changes: all checks green, PR - merged, or PR closed. Silent polls keep the monitor quiet and avoid - flooding notifications. -- Handle transient API errors gracefully (don't crash on a single failure) -- Exit 0 on `MERGED`, exit 1 on `CLOSED` - -The rollup mixes two entry shapes: `CheckRun` entries use `status` + -`conclusion`, while `StatusContext` entries use `state`. A check counts -as "passing" when it's in a terminal state with a non-failing outcome. -Treat `SUCCESS`, `SKIPPED`, and `NEUTRAL` as passing — some release -workflows (e.g. `announce`, `build-global-artifacts`) are conditional -and report `SKIPPED` on non-tag events, which is expected, not a -failure. - -Example monitor script (substitute the actual PR number): -```bash -checks_passed=false -while true; do - json=$(gh pr view PR_NUM --repo atuinsh/atuin --json state,statusCheckRollup 2>/dev/null) || { sleep 30; continue; } - state=$(echo "$json" | jq -r '.state') - case "$state" in - MERGED) echo "PR #PR_NUM has been merged!"; exit 0 ;; - CLOSED) echo "PR #PR_NUM was closed without merging."; exit 1 ;; - esac - # Only notify once when all checks reach a terminal passing state. - # CheckRun entries carry `status`/`conclusion`; StatusContext entries - # carry `state`. SKIPPED and NEUTRAL count as passing. - if [ "$checks_passed" = false ]; then - counts=$(echo "$json" | jq -r ' - [.statusCheckRollup[]?] as $all - | ($all | map(select( - (.status == "COMPLETED" and (.conclusion | IN("SUCCESS","SKIPPED","NEUTRAL"))) - or .state == "SUCCESS" - )) | length) as $passing - | ($all | map(select( - (.status == "COMPLETED" and (.conclusion | IN("FAILURE","TIMED_OUT","CANCELLED","ACTION_REQUIRED","STALE"))) - or (.state | IN("FAILURE","ERROR")) - )) | length) as $failing - | "\($all | length) \($passing) \($failing)" - ' 2>/dev/null) - read -r total passing failing <<<"$counts" - if [ "${failing:-0}" -gt 0 ] 2>/dev/null; then - echo "PR #PR_NUM has $failing failing check(s) — investigate before merging." - checks_passed=true # don't re-notify - elif [ "${total:-0}" -gt 0 ] 2>/dev/null && [ "$total" = "$passing" ]; then - echo "All $total checks passed on PR #PR_NUM — ready to merge!" - checks_passed=true - fi - fi - sleep 30 -done -``` - -Tell the user to go review and merge the PR. While the monitor runs, you -can respond to other questions — the monitor notifications will arrive -asynchronously. - -When the monitor reports `MERGED`, proceed to the next step. -If it reports `CLOSED`, inform the user and stop the release. - ---- - -## Step 8 — Tag Release - -Back in the working directory: - -```bash -git checkout main -git pull -git tag "v" -git push --tags -``` - -Tell the user the tag was pushed and the release CI workflow has been -triggered. - ---- - -## Step 9 — Publish to crates.io - -**If this is a prerelease**, skip this step entirely and tell the user. - -**If this is a stable release**, ask the user whether to publish. - -If yes, publish each crate **in dependency order** using `--no-verify` -(the code already passed CI, and verification fails when crates.io -hasn't indexed a freshly-published dependency yet): - -``` -atuin-common, atuin-client, atuin-ai, atuin-dotfiles, atuin-history, -atuin-nucleo/matcher, atuin-nucleo, atuin-daemon, atuin-kv, -atuin-scripts, atuin-server-database, atuin-server-postgres, -atuin-server-sqlite, atuin-server, atuin-pty-proxy, atuin -``` - -For each crate, run from `crates/`: -```bash -cargo publish --no-verify 2>&1 -``` - -If it fails with "already uploaded", report it as a skip (not an error) — -some crates like `atuin-nucleo` are versioned independently and may -already be published at their current version. - -If it fails for any other reason, stop and report the error. - ---- - -## Completion - -Summarize what was done: -- Version released -- PR URL -- Tag name -- Which crates were published (if any) -- Working directory path and how to clean it up (`rm -rf`) diff --git a/reference/atuin-18.16.1/.codespellrc b/reference/atuin-18.16.1/.codespellrc deleted file mode 100755 index bd9ada0..0000000 --- a/reference/atuin-18.16.1/.codespellrc +++ /dev/null @@ -1,7 +0,0 @@ -[codespell] -# Ref: https://github.com/codespell-project/codespell#using-a-config-file -skip = .git*,*.lock,.codespellrc,CODE_OF_CONDUCT.md,CONTRIBUTORS -check-hidden = true -# ignore-regex = -ignore-words-list = crate,ratatui,inbetween,iterm,fo,brunch - diff --git a/reference/atuin-18.16.1/.depot/workflows/codespell.yml b/reference/atuin-18.16.1/.depot/workflows/codespell.yml deleted file mode 100755 index 1ad4692..0000000 --- a/reference/atuin-18.16.1/.depot/workflows/codespell.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Depot CI Migration -# Source: .github/workflows/codespell.yml -# -# No changes were necessary. - -# Codespell configuration is within .codespellrc -name: Codespell -on: - push: - branches: [main] - pull_request: - branches: [main] -permissions: - contents: read -jobs: - codespell: - name: Check for spelling errors - runs-on: depot-ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Codespell - uses: codespell-project/actions-codespell@v2 - with: - # This is regenerated from commit history - # we cannot rewrite commit history, and I'd rather not correct it - # every time - exclude_file: CHANGELOG.md diff --git a/reference/atuin-18.16.1/.depot/workflows/installer.yml b/reference/atuin-18.16.1/.depot/workflows/installer.yml deleted file mode 100755 index da09ec5..0000000 --- a/reference/atuin-18.16.1/.depot/workflows/installer.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Depot CI Migration -# Source: .github/workflows/installer.yml -# -# No changes were necessary. - -name: Install -on: - push: - branches: [main] - pull_request: - paths: .github/workflows/installer.yml -env: - CARGO_TERM_COLOR: always -jobs: - install: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - - name: Install zsh for ubuntu - if: matrix.os == 'depot-ubuntu-24.04' - run: | - sudo apt install zsh - - name: Test install script on bash - run: | - /bin/bash -c "$(curl --proto '=https' --tlsv1.2 -sSf https://setup.atuin.sh)" - [ -d "$HOME/.atuin" ] && source $HOME/.atuin/bin/env - atuin --help - - name: Test install script on zsh - shell: zsh {0} - run: | - /bin/bash -c "$(curl --proto '=https' --tlsv1.2 -sSf https://setup.atuin.sh)" - [ -d "$HOME/.atuin" ] && source $HOME/.atuin/bin/env - atuin --help diff --git a/reference/atuin-18.16.1/.depot/workflows/nix.yml b/reference/atuin-18.16.1/.depot/workflows/nix.yml deleted file mode 100755 index 571688b..0000000 --- a/reference/atuin-18.16.1/.depot/workflows/nix.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Depot CI Migration -# Source: .github/workflows/nix.yml -# -# No changes were necessary. - -# Verify the Nix build is working -# Failures will usually occur due to an out of date Rust version -# That can be updated to the latest version in nixpkgs-unstable with `nix flake update` -name: Nix -on: - push: - branches: [main] - paths-ignore: - - 'ui/**' - pull_request: - branches: [main] - paths-ignore: - - 'ui/**' -jobs: - check: - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@v31 - - name: Run nix flake check - run: nix flake check --print-build-logs - build-test: - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@v31 - - name: Run nix build - run: nix build --print-build-logs diff --git a/reference/atuin-18.16.1/.depot/workflows/rust.yml b/reference/atuin-18.16.1/.depot/workflows/rust.yml deleted file mode 100755 index 8dfdad1..0000000 --- a/reference/atuin-18.16.1/.depot/workflows/rust.yml +++ /dev/null @@ -1,187 +0,0 @@ -# Depot CI Migration -# Source: .github/workflows/rust.yml -# -# No changes were necessary. - -name: Rust -on: - push: - branches: [main] - paths-ignore: - - "ui/**" - pull_request: - branches: [main] - paths-ignore: - - "ui/**" -env: - CARGO_TERM_COLOR: always -jobs: - build: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.94.0 - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }} - - name: Run cargo build common - run: cargo build -p atuin-common --locked --release - - name: Run cargo build client - run: cargo build -p atuin-client --locked --release - - name: Run cargo build server - run: cargo build -p atuin-server --locked --release - - name: Run cargo build main - run: cargo build --all --locked --release - cross-compile: - strategy: - matrix: - # There was an attempt to make cross-compiles also work on FreeBSD, but that failed with: - # - # warning: libelf.so.2, needed by <...>/libkvm.so, not found (try using -rpath or -rpath-link) - target: [x86_64-unknown-illumos] - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - name: Install cross - uses: taiki-e/install-action@v2 - with: - tool: cross - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ matrix.target }}-cross-compile-${{ hashFiles('**/Cargo.lock') }} - - name: Run cross build common - run: cross build -p atuin-common --locked --target ${{ matrix.target }} - - name: Run cross build client - run: cross build -p atuin-client --locked --target ${{ matrix.target }} - - name: Run cross build server - run: cross build -p atuin-server --locked --target ${{ matrix.target }} - - name: Run cross build main - run: | - cross build --all --locked --target ${{ matrix.target }} - unit-test: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.94.0 - - uses: taiki-e/install-action@v2 - name: Install nextest - with: - tool: cargo-nextest - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - name: Run cargo test - run: cargo nextest run --lib --bins - check: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.94.0 - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - name: Run cargo check (all features) - run: cargo check --all-features --workspace - - name: Run cargo check (no features) - run: cargo check --no-default-features --workspace - - name: Run cargo check (sync) - run: cargo check --no-default-features --features sync --workspace - - name: Run cargo check (server) - run: cargo check -p atuin-server - - name: Run cargo check (client only) - run: cargo check --no-default-features --features client --workspace - integration-test: - runs-on: depot-ubuntu-24.04 - services: - postgres: - image: postgres - env: - POSTGRES_USER: atuin - POSTGRES_PASSWORD: pass - POSTGRES_DB: atuin - ports: - - 5432:5432 - steps: - - uses: actions/checkout@v6 - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.94.0 - - uses: taiki-e/install-action@v2 - name: Install nextest - with: - tool: cargo-nextest - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - name: Run cargo test - run: cargo nextest run --test '*' - env: - ATUIN_DB_URI: postgres://atuin:pass@localhost:5432/atuin - clippy: - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - name: Install latest rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.94.0 - components: clippy - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - name: Run clippy - run: cargo clippy -- -D warnings -D clippy::redundant_clone - format: - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - name: Install latest rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.94.0 - components: rustfmt - - name: Format - run: cargo fmt -- --check diff --git a/reference/atuin-18.16.1/.depot/workflows/shellcheck.yml b/reference/atuin-18.16.1/.depot/workflows/shellcheck.yml deleted file mode 100755 index 28c16c2..0000000 --- a/reference/atuin-18.16.1/.depot/workflows/shellcheck.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Depot CI Migration -# Source: .github/workflows/shellcheck.yml -# -# No changes were necessary. - -name: Shellcheck -on: - push: - branches: [main] - pull_request: - branches: [main] -jobs: - shellcheck: - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - name: Run shellcheck - uses: ludeeus/action-shellcheck@master - env: - SHELLCHECK_OPTS: "-e SC2148" diff --git a/reference/atuin-18.16.1/.depot/workflows/update-nix-deps.yml b/reference/atuin-18.16.1/.depot/workflows/update-nix-deps.yml deleted file mode 100755 index 96a35cd..0000000 --- a/reference/atuin-18.16.1/.depot/workflows/update-nix-deps.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Depot CI Migration -# Source: .github/workflows/update-nix-deps.yml -# -# No changes were necessary. - -name: Update Nix Deps -on: - workflow_dispatch: # allows manual triggering - schedule: - - cron: '0 0 1 * *' # runs monthly on the first day of the month at 00:00 -jobs: - lockfile: - runs-on: depot-ubuntu-24.04 - if: github.repository == 'atuinsh/atuin' - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@main - - name: Update flake.lock - uses: DeterminateSystems/update-flake-lock@main - with: - pr-title: "chore(deps): update flake.lock" - pr-labels: | - dependencies diff --git a/reference/atuin-18.16.1/.dockerignore b/reference/atuin-18.16.1/.dockerignore deleted file mode 100755 index 91e4ecf..0000000 --- a/reference/atuin-18.16.1/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -./target -Dockerfile diff --git a/reference/atuin-18.16.1/.gitattributes b/reference/atuin-18.16.1/.gitattributes deleted file mode 100755 index 95eedf9..0000000 --- a/reference/atuin-18.16.1/.gitattributes +++ /dev/null @@ -1,5 +0,0 @@ -*.sh eol=lf -*.nix eol=lf -*.zsh eol=lf - -*.sql eol=lf diff --git a/reference/atuin-18.16.1/.github/DISCUSSION_TEMPLATE/support.yml b/reference/atuin-18.16.1/.github/DISCUSSION_TEMPLATE/support.yml deleted file mode 100755 index 5dd663e..0000000 --- a/reference/atuin-18.16.1/.github/DISCUSSION_TEMPLATE/support.yml +++ /dev/null @@ -1,84 +0,0 @@ -body: - - type: input - attributes: - label: Operating System - description: What operating system are you using? - placeholder: "Example: macOS Big Sur" - validations: - required: true - - - type: input - attributes: - label: Shell - description: What shell are you using? - placeholder: "Example: zsh 5.8.1" - validations: - required: true - - - type: dropdown - attributes: - label: Version - description: What version of atuin are you running? - multiple: false - options: # how often will I forget to update this? a lot. - - v17.0.0 (Default) - - v16.0.0 - - v15.0.0 - - v14.0.1 - - v14.0.0 - - v13.0.1 - - v13.0.0 - - v12.0.0 - - v11.0.0 - - v0.10.0 - - v0.9.1 - - v0.9.0 - - v0.8.1 - - v0.8.0 - - v0.7.2 - - v0.7.1 - - v0.7.0 - - v0.6.4 - - v0.6.3 - default: 0 - validations: - required: true - - - type: checkboxes - attributes: - label: Self hosted - description: Are you self hosting atuin server? - options: - - label: I am self hosting atuin server - - - type: checkboxes - attributes: - label: Search the issues - description: Did you search the issues and discussions for your problem? - options: - - label: I checked that someone hasn't already asked about the same issue - required: true - - - type: textarea - attributes: - label: Behaviour - description: "Please describe the issue - what you expected to happen, what actually happened" - - - type: textarea - attributes: - label: Logs - description: "If possible, please include logs from atuin, especially if you self host the server - ATUIN_LOG=debug" - - - type: textarea - attributes: - label: Extra information - description: "Anything else you'd like to add?" - - - type: checkboxes - attributes: - label: Code of Conduct - description: The Code of Conduct helps create a safe space for everyone. We require - that everyone agrees to it. - options: - - label: I agree to follow this project's [Code of Conduct](https://github.com/atuinsh/atuin/blob/main/CODE_OF_CONDUCT.md) - required: true diff --git a/reference/atuin-18.16.1/.github/FUNDING.yml b/reference/atuin-18.16.1/.github/FUNDING.yml deleted file mode 100755 index bbbb246..0000000 --- a/reference/atuin-18.16.1/.github/FUNDING.yml +++ /dev/null @@ -1,13 +0,0 @@ -# These are supported funding model platforms - -github: [atuinsh] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/reference/atuin-18.16.1/.github/ISSUE_TEMPLATE/bug.yaml b/reference/atuin-18.16.1/.github/ISSUE_TEMPLATE/bug.yaml deleted file mode 100755 index d1b8e64..0000000 --- a/reference/atuin-18.16.1/.github/ISSUE_TEMPLATE/bug.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Bug Report -description: File a bug report -title: "[Bug]: " -labels: ["bug", "triage"] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - type: textarea - id: what-expected - attributes: - label: What did you expect to happen? - placeholder: Tell us what you expected to see! - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: What happened? - placeholder: Tell us what you see! - validations: - required: true - - type: textarea - id: doctor - validations: - required: true - attributes: - label: Atuin doctor output - description: Please run 'atuin doctor' and share the output. If it fails to run, share any errors. This requires Atuin >=v18.1.0 - render: yaml - - type: checkboxes - id: terms - attributes: - label: Code of Conduct - description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/atuinsh/atuin/blob/main/CODE_OF_CONDUCT.md) - options: - - label: I agree to follow this project's Code of Conduct - required: true diff --git a/reference/atuin-18.16.1/.github/dependabot.yml b/reference/atuin-18.16.1/.github/dependabot.yml deleted file mode 100755 index 6091393..0000000 --- a/reference/atuin-18.16.1/.github/dependabot.yml +++ /dev/null @@ -1,19 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "cargo" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" - - package-ecosystem: "docker" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" diff --git a/reference/atuin-18.16.1/.github/pull_request_template.md b/reference/atuin-18.16.1/.github/pull_request_template.md deleted file mode 100755 index a037ef0..0000000 --- a/reference/atuin-18.16.1/.github/pull_request_template.md +++ /dev/null @@ -1,5 +0,0 @@ - - -## Checks -- [ ] I am happy for maintainers to push small adjustments to this PR, to speed up the review cycle -- [ ] I have checked that there are no existing pull requests for the same thing diff --git a/reference/atuin-18.16.1/.github/workflows/codespell.yml b/reference/atuin-18.16.1/.github/workflows/codespell.yml deleted file mode 100755 index d39dd87..0000000 --- a/reference/atuin-18.16.1/.github/workflows/codespell.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Codespell configuration is within .codespellrc ---- -name: Codespell - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - contents: read - -jobs: - codespell: - name: Check for spelling errors - runs-on: depot-ubuntu-24.04 - - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Codespell - uses: codespell-project/actions-codespell@v2 - with: - # This is regenerated from commit history - # we cannot rewrite commit history, and I'd rather not correct it - # every time - exclude_file: CHANGELOG.md diff --git a/reference/atuin-18.16.1/.github/workflows/docker.yaml b/reference/atuin-18.16.1/.github/workflows/docker.yaml deleted file mode 100755 index 9dd687c..0000000 --- a/reference/atuin-18.16.1/.github/workflows/docker.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: build-docker - -on: - push: - branches: [main] - tags: - - 'v*' - -jobs: - publish: - concurrency: - group: ${{ github.ref }}-docker - cancel-in-progress: true - permissions: - packages: write - contents: read - id-token: write - - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Get Repo Owner - id: get_repo_owner - run: echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" > $GITHUB_ENV - - - uses: depot/setup-action@v1 - - - name: Login to container Registry - uses: docker/login-action@v3 - with: - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - registry: ghcr.io - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ env.REPO_OWNER }}/atuin - flavor: | - latest=false - tags: | - type=ref,event=branch - type=sha,prefix= - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Build and push - uses: depot/build-push-action@v1 - with: - push: true - platforms: linux/amd64,linux/arm64 - file: ./Dockerfile - context: . - provenance: false - build-args: | - Version=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] || 'dev' }} - GitCommit=${{ github.sha }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/reference/atuin-18.16.1/.github/workflows/installer.yml b/reference/atuin-18.16.1/.github/workflows/installer.yml deleted file mode 100755 index c4c4aab..0000000 --- a/reference/atuin-18.16.1/.github/workflows/installer.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Install - -on: - push: - branches: [main] - pull_request: - paths: .github/workflows/installer.yml - -env: - CARGO_TERM_COLOR: always - -jobs: - install: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v6 - - - name: Install zsh for ubuntu - if: matrix.os == 'depot-ubuntu-24.04' - run: | - sudo apt install zsh - - - name: Test install script on bash - run: | - /bin/bash -c "$(curl --proto '=https' --tlsv1.2 -sSf https://setup.atuin.sh)" - [ -d "$HOME/.atuin" ] && source $HOME/.atuin/bin/env - atuin --help - - - name: Test install script on zsh - shell: zsh {0} - run: | - /bin/bash -c "$(curl --proto '=https' --tlsv1.2 -sSf https://setup.atuin.sh)" - [ -d "$HOME/.atuin" ] && source $HOME/.atuin/bin/env - atuin --help diff --git a/reference/atuin-18.16.1/.github/workflows/nix.yml b/reference/atuin-18.16.1/.github/workflows/nix.yml deleted file mode 100755 index 6e763d4..0000000 --- a/reference/atuin-18.16.1/.github/workflows/nix.yml +++ /dev/null @@ -1,34 +0,0 @@ -# Verify the Nix build is working -# Failures will usually occur due to an out of date Rust version -# That can be updated to the latest version in nixpkgs-unstable with `nix flake update` -name: Nix -on: - push: - branches: [ main ] - paths-ignore: - - 'ui/**' - pull_request: - branches: [ main ] - paths-ignore: - - 'ui/**' - -jobs: - check: - runs-on: depot-ubuntu-24.04 - - steps: - - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@v31 - - - name: Run nix flake check - run: nix flake check --print-build-logs - - build-test: - runs-on: depot-ubuntu-24.04 - - steps: - - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@v31 - - - name: Run nix build - run: nix build --print-build-logs diff --git a/reference/atuin-18.16.1/.github/workflows/release.yml b/reference/atuin-18.16.1/.github/workflows/release.yml deleted file mode 100755 index d742a92..0000000 --- a/reference/atuin-18.16.1/.github/workflows/release.yml +++ /dev/null @@ -1,304 +0,0 @@ -# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist -# -# Copyright 2022-2024, axodotdev -# SPDX-License-Identifier: MIT or Apache-2.0 -# -# CI that: -# -# * checks for a Git Tag that looks like a release -# * builds artifacts with dist (archives, installers, hashes) -# * uploads those artifacts to temporary workflow zip -# * on success, uploads the artifacts to a GitHub Release -# -# Note that the GitHub Release will be created with a generated -# title/body based on your changelogs. - -name: Release -permissions: - "contents": "write" - -# This task will run whenever you push a git tag that looks like a version -# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. -# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where -# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION -# must be a Cargo-style SemVer Version (must have at least major.minor.patch). -# -# If PACKAGE_NAME is specified, then the announcement will be for that -# package (erroring out if it doesn't have the given version or isn't dist-able). -# -# If PACKAGE_NAME isn't specified, then the announcement will be for all -# (dist-able) packages in the workspace with that version (this mode is -# intended for workspaces with only one dist-able package, or with all dist-able -# packages versioned/released in lockstep). -# -# If you push multiple tags at once, separate instances of this workflow will -# spin up, creating an independent announcement for each one. However, GitHub -# will hard limit this to 3 tags per commit, as it will assume more tags is a -# mistake. -# -# If there's a prerelease-style suffix to the version, then the release(s) -# will be marked as a prerelease. -on: - pull_request: - push: - tags: - - '**[0-9]+.[0-9]+.[0-9]+*' - -jobs: - # Run 'dist plan' (or host) to determine what tasks we need to do - plan: - runs-on: "ubuntu-22.04" - outputs: - val: ${{ steps.plan.outputs.manifest }} - tag: ${{ !github.event.pull_request && github.ref_name || '' }} - tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} - publishing: ${{ !github.event.pull_request }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install dist - # we specify bash to get pipefail; it guards against the `curl` command - # failing. otherwise `sh` won't catch that `curl` returned non-0 - shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh" - - name: Cache dist - uses: actions/upload-artifact@v6 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/dist - # sure would be cool if github gave us proper conditionals... - # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible - # functionality based on whether this is a pull_request, and whether it's from a fork. - # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* - # but also really annoying to build CI around when it needs secrets to work right.) - - id: plan - run: | - dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json - echo "dist ran successfully" - cat plan-dist-manifest.json - echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v6 - with: - name: artifacts-plan-dist-manifest - path: plan-dist-manifest.json - - # Build and packages all the platform-specific things - build-local-artifacts: - name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) - # Let the initial task tell us to not run (currently very blunt) - needs: - - plan - if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} - strategy: - fail-fast: false - # Target platforms/runners are computed by dist in create-release. - # Each member of the matrix has the following arguments: - # - # - runner: the github runner - # - dist-args: cli flags to pass to dist - # - install-dist: expression to run to install dist on the runner - # - # Typically there will be: - # - 1 "global" task that builds universal installers - # - N "local" tasks that build each platform's binaries and platform-specific installers - matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} - runs-on: ${{ matrix.runner }} - container: ${{ matrix.container && matrix.container.image || null }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json - permissions: - "attestations": "write" - "contents": "read" - "id-token": "write" - steps: - - name: enable windows longpaths - run: | - git config --global core.longpaths true - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install Rust non-interactively if not already installed - if: ${{ matrix.container }} - run: | - if ! command -v cargo > /dev/null 2>&1; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - fi - - name: Install dist - run: ${{ matrix.install_dist.run }} - # Get the dist-manifest - - name: Fetch local artifacts - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - name: Install dependencies - run: | - ${{ matrix.packages_install }} - - name: Build artifacts - run: | - # Actually do builds and make zips and whatnot - dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json - echo "dist ran successfully" - - name: Attest - uses: actions/attest-build-provenance@v3 - with: - subject-path: "target/distrib/*${{ join(matrix.targets, ', ') }}*" - - id: cargo-dist - name: Post-build - # We force bash here just because github makes it really hard to get values up - # to "real" actions without writing to env-vars, and writing to env-vars has - # inconsistent syntax between shell and powershell. - shell: bash - run: | - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v6 - with: - name: artifacts-build-local-${{ join(matrix.targets, '_') }} - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - - # Build and package all the platform-agnostic(ish) things - build-global-artifacts: - needs: - - plan - - build-local-artifacts - runs-on: "ubuntu-22.04" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v7 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Get all the local artifacts for the global tasks to use (for e.g. checksums) - - name: Fetch local artifacts - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: cargo-dist - shell: bash - run: | - dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json - echo "dist ran successfully" - - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v6 - with: - name: artifacts-build-global - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - # Determines if we should publish/announce - host: - needs: - - plan - - build-local-artifacts - - build-global-artifacts - # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine) - if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - runs-on: "ubuntu-22.04" - outputs: - val: ${{ steps.host.outputs.manifest }} - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v7 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Fetch artifacts from scratch-storage - - name: Fetch artifacts - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: host - shell: bash - run: | - dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json - echo "artifacts uploaded and released successfully" - cat dist-manifest.json - echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v6 - with: - # Overwrite the previous copy - name: artifacts-dist-manifest - path: dist-manifest.json - # Create a GitHub Release while uploading all files to it - - name: "Download GitHub Artifacts" - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: artifacts - merge-multiple: true - - name: Cleanup - run: | - # Remove the granular manifests - rm -f artifacts/*-dist-manifest.json - - name: Create GitHub Release - env: - PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" - ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" - ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" - RELEASE_COMMIT: "${{ github.sha }}" - run: | - # Write and read notes from a file to avoid quoting breaking things - echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt - - gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* - - announce: - needs: - - plan - - host - # use "always() && ..." to allow us to wait for all publish jobs while - # still allowing individual publish jobs to skip themselves (for prereleases). - # "host" however must run to completion, no skipping allowed! - if: ${{ always() && needs.host.result == 'success' }} - runs-on: "ubuntu-22.04" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive diff --git a/reference/atuin-18.16.1/.github/workflows/rust.yml b/reference/atuin-18.16.1/.github/workflows/rust.yml deleted file mode 100755 index 1941e0a..0000000 --- a/reference/atuin-18.16.1/.github/workflows/rust.yml +++ /dev/null @@ -1,230 +0,0 @@ -name: Rust - -on: - push: - branches: [main] - paths-ignore: - - "ui/**" - pull_request: - branches: [main] - paths-ignore: - - "ui/**" - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14, windows-latest] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v6 - - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.95.0 - - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }} - - - name: Run cargo build common - run: cargo build -p atuin-common --locked --release - - - name: Run cargo build client - run: cargo build -p atuin-client --locked --release - - - name: Run cargo build server - run: cargo build -p atuin-server --locked --release - - - name: Run cargo build main - run: cargo build --all --locked --release - - cross-compile: - strategy: - matrix: - # There was an attempt to make cross-compiles also work on FreeBSD, but that failed with: - # - # warning: libelf.so.2, needed by <...>/libkvm.so, not found (try using -rpath or -rpath-link) - target: [x86_64-unknown-illumos] - runs-on: depot-ubuntu-24.04 - steps: - - uses: actions/checkout@v6 - - - name: Install cross - uses: taiki-e/install-action@v2 - with: - tool: cross - - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ matrix.target }}-cross-compile-${{ hashFiles('**/Cargo.lock') }} - - - name: Run cross build common - run: cross build -p atuin-common --locked --target ${{ matrix.target }} - - - name: Run cross build client - run: cross build -p atuin-client --locked --target ${{ matrix.target }} - - - name: Run cross build server - run: cross build -p atuin-server --locked --target ${{ matrix.target }} - - - name: Run cross build main - run: | - cross build --all --locked --target ${{ matrix.target }} - - unit-test: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14, windows-latest] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v6 - - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.95.0 - - - uses: taiki-e/install-action@v2 - name: Install nextest - with: - tool: cargo-nextest - - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - - name: Run cargo test - run: cargo nextest run --lib --bins - - check: - strategy: - matrix: - os: [depot-ubuntu-24.04, macos-14, windows-latest] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v6 - - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.95.0 - - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - - name: Run cargo check (all features) - run: cargo check --all-features --workspace - - - name: Run cargo check (no features) - run: cargo check --no-default-features --workspace - - - name: Run cargo check (sync) - run: cargo check --no-default-features --features sync --workspace - - - name: Run cargo check (server) - run: cargo check -p atuin-server - - - name: Run cargo check (client only) - run: cargo check --no-default-features --features client --workspace - - integration-test: - runs-on: depot-ubuntu-24.04 - - services: - postgres: - image: postgres - env: - POSTGRES_USER: atuin - POSTGRES_PASSWORD: pass - POSTGRES_DB: atuin - ports: - - 5432:5432 - - steps: - - uses: actions/checkout@v6 - - - name: Install rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.95.0 - - - uses: taiki-e/install-action@v2 - name: Install nextest - with: - tool: cargo-nextest - - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - - name: Run cargo test - run: cargo nextest run --test '*' - env: - ATUIN_DB_URI: postgres://atuin:pass@localhost:5432/atuin - - clippy: - runs-on: depot-ubuntu-24.04 - - steps: - - uses: actions/checkout@v6 - - - name: Install latest rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.95.0 - components: clippy - - - uses: actions/cache@v5 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-debug-${{ hashFiles('**/Cargo.lock') }} - - - name: Run clippy - run: cargo clippy -- -D warnings -D clippy::redundant_clone - - format: - runs-on: depot-ubuntu-24.04 - - steps: - - uses: actions/checkout@v6 - - - name: Install latest rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.95.0 - components: rustfmt - - - name: Format - run: cargo fmt -- --check diff --git a/reference/atuin-18.16.1/.github/workflows/shellcheck.yml b/reference/atuin-18.16.1/.github/workflows/shellcheck.yml deleted file mode 100755 index 12abbeb..0000000 --- a/reference/atuin-18.16.1/.github/workflows/shellcheck.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Shellcheck - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - shellcheck: - runs-on: depot-ubuntu-24.04 - - steps: - - uses: actions/checkout@v6 - - name: Run shellcheck - uses: ludeeus/action-shellcheck@master - env: - SHELLCHECK_OPTS: "-e SC2148" diff --git a/reference/atuin-18.16.1/.github/workflows/update-nix-deps.yml b/reference/atuin-18.16.1/.github/workflows/update-nix-deps.yml deleted file mode 100755 index a6d65c4..0000000 --- a/reference/atuin-18.16.1/.github/workflows/update-nix-deps.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Update Nix Deps -on: - workflow_dispatch: # allows manual triggering - schedule: - - cron: '0 0 1 * *' # runs monthly on the first day of the month at 00:00 - -jobs: - lockfile: - runs-on: depot-ubuntu-24.04 - if: github.repository == 'atuinsh/atuin' - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@main - - name: Update flake.lock - uses: DeterminateSystems/update-flake-lock@main - with: - pr-title: "chore(deps): update flake.lock" - pr-labels: | - dependencies diff --git a/reference/atuin-18.16.1/.gitignore b/reference/atuin-18.16.1/.gitignore deleted file mode 100755 index 78ae9ef..0000000 --- a/reference/atuin-18.16.1/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -.DS_Store -/target -*/target -.env -.idea/ -.vscode/ -result -publish.sh -.envrc -.planning/ - -ui/backend/target -ui/backend/gen - -sqlite-server.db* - -.atuin/permissions.*.toml diff --git a/reference/atuin-18.16.1/.gitkeep b/reference/atuin-18.16.1/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/atuin-18.16.1/.mailmap b/reference/atuin-18.16.1/.mailmap deleted file mode 100755 index e408a9b..0000000 --- a/reference/atuin-18.16.1/.mailmap +++ /dev/null @@ -1,14 +0,0 @@ -networkException -Violet Shreve -Chris Rose -Conrad Ludgate -Cristian Le -Dennis Trautwein -Ellie Huxtable -Ellie Huxtable -Frank Hamand -Jakob Schrettenbrunner -Nemo157 -Richard de Boer -Sandro -TymanWasTaken diff --git a/reference/atuin-18.16.1/.rustfmt.toml b/reference/atuin-18.16.1/.rustfmt.toml deleted file mode 100755 index 0e363b3..0000000 --- a/reference/atuin-18.16.1/.rustfmt.toml +++ /dev/null @@ -1,4 +0,0 @@ -reorder_imports = true -# uncomment once stable -#imports_granularity = "crate" -#group_imports = "StdExternalCrate" diff --git a/reference/claude-code-2.1.88-leak/.gitkeep b/reference/claude-code-2.1.88-leak/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/claude-code-cli/.gitkeep b/reference/claude-code-cli/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/claude-hud-0.0.12/.claude-plugin/marketplace.json b/reference/claude-hud-0.0.12/.claude-plugin/marketplace.json deleted file mode 100755 index e953105..0000000 --- a/reference/claude-hud-0.0.12/.claude-plugin/marketplace.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "claude-hud", - "owner": { - "name": "Jarrod Watts", - "email": "jarrodwattsyt@gmail.com" - }, - "metadata": { - "description": "Real-time statusline HUD for Claude Code - context health, tool activity, agent tracking, and todo progress", - "version": "0.0.12" - }, - "plugins": [ - { - "name": "claude-hud", - "source": "./", - "description": "Real-time statusline showing context usage, active tools, running agents, and todo progress. Always visible below your input, zero config required.", - "category": "monitoring", - "tags": ["hud", "statusline", "monitoring", "context", "tools", "agents", "todos"] - } - ] -} diff --git a/reference/claude-hud-0.0.12/.claude-plugin/plugin.json b/reference/claude-hud-0.0.12/.claude-plugin/plugin.json deleted file mode 100755 index e416b20..0000000 --- a/reference/claude-hud-0.0.12/.claude-plugin/plugin.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "claude-hud", - "description": "Real-time statusline HUD for Claude Code - context health, tool activity, agent tracking, and todo progress", - "version": "0.0.12", - "author": { - "name": "Jarrod Watts", - "url": "https://github.com/jarrodwatts" - }, - "commands": [ - "./commands/setup.md", - "./commands/configure.md" - ], - "homepage": "https://github.com/jarrodwatts/claude-hud", - "repository": "https://github.com/jarrodwatts/claude-hud", - "license": "MIT", - "keywords": ["hud", "monitoring", "statusline", "context", "tools", "agents", "todos", "claude-code"] -} diff --git a/reference/claude-hud-0.0.12/.editorconfig b/reference/claude-hud-0.0.12/.editorconfig deleted file mode 100755 index c63be93..0000000 --- a/reference/claude-hud-0.0.12/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false diff --git a/reference/claude-hud-0.0.12/.github/CODEOWNERS b/reference/claude-hud-0.0.12/.github/CODEOWNERS deleted file mode 100755 index 0cc2fbf..0000000 --- a/reference/claude-hud-0.0.12/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @jarrodwatts diff --git a/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/bug_report.md b/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100755 index 2c923fb..0000000 --- a/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Bug report -about: Report a reproducible problem -labels: bug ---- - -## Summary - -## Steps to Reproduce - -## Expected Behavior - -## Actual Behavior - -## Environment - -- OS: -- Node/Bun version: -- Claude Code version: - -## Logs or Screenshots diff --git a/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/config.yml b/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100755 index bff1611..0000000 --- a/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Security report - url: mailto:jarrodwttsyt@gmail.com - about: Please report security vulnerabilities via email. diff --git a/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/feature_request.md b/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100755 index 11235dd..0000000 --- a/reference/claude-hud-0.0.12/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -name: Feature request -about: Suggest an idea or enhancement -labels: enhancement ---- - -## Summary - -## Problem to Solve - -## Proposed Solution - -## Alternatives Considered - -## Additional Context diff --git a/reference/claude-hud-0.0.12/.github/dependabot.yml b/reference/claude-hud-0.0.12/.github/dependabot.yml deleted file mode 100755 index 4796d06..0000000 --- a/reference/claude-hud-0.0.12/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 5 diff --git a/reference/claude-hud-0.0.12/.github/pull_request_template.md b/reference/claude-hud-0.0.12/.github/pull_request_template.md deleted file mode 100755 index a4362ad..0000000 --- a/reference/claude-hud-0.0.12/.github/pull_request_template.md +++ /dev/null @@ -1,11 +0,0 @@ -## Summary - -## Testing - -- [ ] `npm test` -- [ ] `npm run test:coverage` - -## Checklist - -- [ ] Tests updated or not needed -- [ ] Docs updated if behavior changed diff --git a/reference/claude-hud-0.0.12/.github/workflows/build-dist.yml b/reference/claude-hud-0.0.12/.github/workflows/build-dist.yml deleted file mode 100755 index 8e192b1..0000000 --- a/reference/claude-hud-0.0.12/.github/workflows/build-dist.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Build dist - -on: - push: - branches: [main] - -concurrency: - group: build-dist - cancel-in-progress: false - -permissions: - contents: write - -jobs: - build: - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[auto]')" - - steps: - - uses: actions/checkout@v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'npm' - - - run: npm ci - - run: npm test - - run: npm run build - - - name: Verify build output - run: test -f dist/index.js || exit 1 - - - name: Commit dist/ - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add dist/ --force - git diff --staged --quiet || git commit -m "build: compile dist/ [auto]" - git push diff --git a/reference/claude-hud-0.0.12/.github/workflows/ci.yml b/reference/claude-hud-0.0.12/.github/workflows/ci.yml deleted file mode 100755 index ac2c4db..0000000 --- a/reference/claude-hud-0.0.12/.github/workflows/ci.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: [main] - paths-ignore: - - 'dist/**' - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [18.x, 20.x] - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: ${{ matrix.node-version }} - cache: npm - - run: npm ci - - run: npm run test:coverage diff --git a/reference/claude-hud-0.0.12/.github/workflows/claude.yml b/reference/claude-hud-0.0.12/.github/workflows/claude.yml deleted file mode 100755 index 5a35acc..0000000 --- a/reference/claude-hud-0.0.12/.github/workflows/claude.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - claude_args: '--model claude-opus-4-5-20251101' - diff --git a/reference/claude-hud-0.0.12/.github/workflows/release.yml b/reference/claude-hud-0.0.12/.github/workflows/release.yml deleted file mode 100755 index e65a385..0000000 --- a/reference/claude-hud-0.0.12/.github/workflows/release.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*.*.*" - -permissions: - contents: write - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 20.x - cache: npm - - run: npm ci - - run: npm run build - - run: npm test - - run: npm run test:coverage - - name: Extract release notes from CHANGELOG - run: | - version="${GITHUB_REF_NAME#v}" - awk -v version="$version" ' - $0 ~ "^## \\[" version "\\]" { in_section = 1; next } - in_section && $0 ~ "^## \\[" { exit } - in_section { print } - ' CHANGELOG.md > RELEASE_NOTES.md - - if [ ! -s RELEASE_NOTES.md ]; then - echo "No changelog section found for version $version" - exit 1 - fi - - name: Create release - uses: softprops/action-gh-release@v2 - with: - body_path: RELEASE_NOTES.md diff --git a/reference/claude-hud-0.0.12/.gitignore b/reference/claude-hud-0.0.12/.gitignore deleted file mode 100755 index 8d2fa88..0000000 --- a/reference/claude-hud-0.0.12/.gitignore +++ /dev/null @@ -1,59 +0,0 @@ -# Dependencies -node_modules/ - -# Build artifacts -# dist/ is gitignored but exists on main - CI builds and commits it after each merge. -# See .github/workflows/build-dist.yml -dist/ -*.tsbuildinfo - -# Logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids/ -*.pid -*.seed -*.fifo - -# OS files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# IDE -.idea/ -.vscode/ -*.swp -*.swo -*~ - -# Environment/secrets (safety) -.env -.env.* -.claude/settings.json -.claude/*.local.json -*.pem -*.key -secrets/ -credentials/ - -# Test coverage -coverage/ -.nyc_output/ - -# Temp files -tmp/ -temp/ -*.tmp - -# Lock files (keep package-lock.json for npm) -yarn.lock -bun.lock diff --git a/reference/claude-hud-0.0.12/.gitkeep b/reference/claude-hud-0.0.12/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/hermes-agent-2026.5.16/.dockerignore b/reference/hermes-agent-2026.5.16/.dockerignore deleted file mode 100755 index f4a0248..0000000 --- a/reference/hermes-agent-2026.5.16/.dockerignore +++ /dev/null @@ -1,31 +0,0 @@ -# Git -.git -.gitignore -.gitmodules - -# Dependencies -node_modules -**/node_modules -.venv -**/.venv - -# Built artifacts that are regenerated inside the image. Excluded so local -# rebuilds on the developer's machine don't invalidate the npm-install layer -# that now depends on the full ui-tui/packages/hermes-ink/ tree being present. -ui-tui/dist/ -ui-tui/packages/hermes-ink/dist/ - -# CI/CD -.github - -# Environment files -.env - -*.md - -# Runtime data (bind-mounted at /opt/data; must not leak into build context) -data/ - -# Compose/profile runtime state (bind-mounted; avoid ownership/secret issues) -hermes-config/ -runtime/ diff --git a/reference/hermes-agent-2026.5.16/.env.example b/reference/hermes-agent-2026.5.16/.env.example deleted file mode 100755 index 812986d..0000000 --- a/reference/hermes-agent-2026.5.16/.env.example +++ /dev/null @@ -1,469 +0,0 @@ -# Hermes Agent Environment Configuration -# Copy this file to .env and fill in your API keys - -# ============================================================================= -# LLM PROVIDER (OpenRouter) -# ============================================================================= -# OpenRouter provides access to many models through one API -# All LLM calls go through OpenRouter - no direct provider keys needed -# Get your key at: https://openrouter.ai/keys -# OPENROUTER_API_KEY= - -# Default model is configured in ~/.hermes/config.yaml (model.default). -# Use 'hermes model' or 'hermes setup' to change it. -# LLM_MODEL is no longer read from .env — this line is kept for reference only. -# LLM_MODEL=anthropic/claude-opus-4.6 - -# ============================================================================= -# LLM PROVIDER (NovitaAI) -# ============================================================================= -# NovitaAI — 90+ models, pay-per-use -# Get your key at: https://novita.ai/settings/key-management -# NOVITA_API_KEY= -# NOVITA_BASE_URL=https://api.novita.ai/openai/v1 # Override default base URL - -# ============================================================================= -# LLM PROVIDER (Google AI Studio / Gemini) -# ============================================================================= -# Native Gemini API via Google's OpenAI-compatible endpoint. -# Get your key at: https://aistudio.google.com/app/apikey -# GOOGLE_API_KEY=your_google_ai_studio_key_here -# GEMINI_API_KEY=your_gemini_key_here # alias for GOOGLE_API_KEY -# Optional base URL override (default: Google's OpenAI-compatible endpoint) -# GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai - -# ============================================================================= -# LLM PROVIDER (Ollama Cloud) -# ============================================================================= -# Cloud-hosted open models via Ollama's OpenAI-compatible endpoint. -# Get your key at: https://ollama.com/settings -# OLLAMA_API_KEY=your_ollama_key_here -# Optional base URL override (default: https://ollama.com/v1) -# OLLAMA_BASE_URL=https://ollama.com/v1 - -# ============================================================================= -# LLM PROVIDER (z.ai / GLM) -# ============================================================================= -# z.ai provides access to ZhipuAI GLM models (GLM-4-Plus, etc.) -# Get your key at: https://z.ai or https://open.bigmodel.cn -# GLM_API_KEY= -# GLM_BASE_URL=https://api.z.ai/api/paas/v4 # Override default base URL - -# ============================================================================= -# LLM PROVIDER (Kimi / Moonshot) -# ============================================================================= -# Kimi Code provides access to Moonshot AI coding models (kimi-k2.5, etc.) -# Get your key at: https://platform.kimi.ai (Kimi Code console) -# Keys prefixed sk-kimi- use the Kimi Code API (api.kimi.com) by default. -# Legacy keys from platform.moonshot.ai need KIMI_BASE_URL override below. -# KIMI_API_KEY= -# KIMI_BASE_URL=https://api.kimi.com/coding/v1 # Default for sk-kimi- keys -# KIMI_BASE_URL=https://api.moonshot.ai/v1 # For legacy Moonshot keys -# KIMI_BASE_URL=https://api.moonshot.cn/v1 # For Moonshot China keys -# KIMI_CN_API_KEY= # Dedicated Moonshot China key - -# ============================================================================= -# LLM PROVIDER (Arcee AI) -# ============================================================================= -# Arcee AI provides access to Trinity models (trinity-mini, trinity-large-*) -# Get an Arcee key at: https://chat.arcee.ai/ -# ARCEEAI_API_KEY= -# ARCEE_BASE_URL= # Override default base URL - -# ============================================================================= -# LLM PROVIDER (MiniMax) -# ============================================================================= -# MiniMax provides access to MiniMax models (global endpoint) -# Get your key at: https://www.minimax.io -# MINIMAX_API_KEY= -# MINIMAX_BASE_URL=https://api.minimax.io/v1 # Override default base URL - -# MiniMax China endpoint (for users in mainland China) -# MINIMAX_CN_API_KEY= -# MINIMAX_CN_BASE_URL=https://api.minimaxi.com/v1 # Override default base URL - -# ============================================================================= -# LLM PROVIDER (OpenCode Zen) -# ============================================================================= -# OpenCode Zen provides curated, tested models (GPT, Claude, Gemini, MiniMax, GLM, Kimi) -# Pay-as-you-go pricing. Get your key at: https://opencode.ai/auth -# OPENCODE_ZEN_API_KEY= -# OPENCODE_ZEN_BASE_URL=https://opencode.ai/zen/v1 # Override default base URL - -# ============================================================================= -# LLM PROVIDER (OpenCode Go) -# ============================================================================= -# OpenCode Go provides access to open models (GLM-5, Kimi K2.5, MiniMax M2.5) -# $10/month subscription. Get your key at: https://opencode.ai/auth -# OPENCODE_GO_API_KEY= - -# ============================================================================= -# LLM PROVIDER (Hugging Face Inference Providers) -# ============================================================================= -# Hugging Face routes to 20+ open models via unified OpenAI-compatible endpoint. -# Free tier included ($0.10/month), no markup on provider rates. -# Get your token at: https://huggingface.co/settings/tokens -# Required permission: "Make calls to Inference Providers" -# HF_TOKEN= -# OPENCODE_GO_BASE_URL=https://opencode.ai/zen/go/v1 # Override default base URL - -# ============================================================================= -# LLM PROVIDER (Qwen OAuth) -# ============================================================================= -# Qwen OAuth reuses your local Qwen CLI login (qwen auth qwen-oauth). -# No API key needed — credentials come from ~/.qwen/oauth_creds.json. -# Optional base URL override: -# HERMES_QWEN_BASE_URL=https://portal.qwen.ai/v1 - -# ============================================================================= -# LLM PROVIDER (Xiaomi MiMo) -# ============================================================================= -# Xiaomi MiMo models (mimo-v2-pro, mimo-v2-omni, mimo-v2-flash). -# Get your key at: https://platform.xiaomimimo.com -# XIAOMI_API_KEY=your_key_here -# Optional base URL override: -# XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1 - -# ============================================================================= -# TOOL API KEYS -# ============================================================================= - -# Exa API Key - AI-native web search and contents -# Get at: https://exa.ai -# EXA_API_KEY= - -# Parallel API Key - AI-native web search and extract -# Get at: https://parallel.ai -# PARALLEL_API_KEY= - -# Firecrawl API Key - Web search, extract, and crawl -# Get at: https://firecrawl.dev/ -# FIRECRAWL_API_KEY= - - -# FAL.ai API Key - Image generation -# Get at: https://fal.ai/ -# FAL_KEY= - -# Honcho - Cross-session AI-native user modeling (optional) -# Builds a persistent understanding of the user across sessions and tools. -# Get at: https://app.honcho.dev -# Also requires ~/.honcho/config.json with enabled=true (see README). -# HONCHO_API_KEY= - -# ============================================================================= -# HYPERLIQUID OPTIONAL SKILL -# ============================================================================= -# Optional defaults for the Hyperliquid skill in optional-skills/blockchain/hyperliquid -# -# Hyperliquid API base URL override -# Default: https://api.hyperliquid.xyz -# HYPERLIQUID_API_URL=https://api.hyperliquid-testnet.xyz -# -# Default address for account-level commands like state, fills, orders, and review -# HYPERLIQUID_USER_ADDRESS=0x0000000000000000000000000000000000000000 - -# ============================================================================= -# TERMINAL TOOL CONFIGURATION -# ============================================================================= -# Backend type: "local", "singularity", "docker", "modal", or "ssh" -# Terminal backend is configured in ~/.hermes/config.yaml (terminal.backend). -# Use 'hermes setup' or 'hermes config set terminal.backend docker' to change. -# Supported: local, docker, singularity, modal, ssh -# -# Only override here if you need to force a backend without touching config.yaml: -# TERMINAL_ENV=local - -# Override the container runtime binary (e.g. to use Podman instead of Docker). -# Useful on systems where Docker's storage driver is broken or unavailable. -# HERMES_DOCKER_BINARY=/usr/local/bin/podman - -# Container images (for singularity/docker/modal backends) -# TERMINAL_DOCKER_IMAGE=nikolaik/python-nodejs:python3.11-nodejs20 -# TERMINAL_SINGULARITY_IMAGE=docker://nikolaik/python-nodejs:python3.11-nodejs20 -TERMINAL_MODAL_IMAGE=nikolaik/python-nodejs:python3.11-nodejs20 - - -# Working directory for terminal commands -# For local backend: "." means current directory (resolved automatically) -# For remote backends (ssh/docker/modal/singularity): use an absolute path -# INSIDE the target environment, or leave unset for the backend's default -# (/root for modal, / for docker, ~ for ssh). Do NOT use a host-local path. -# Usually managed by config.yaml (terminal.cwd) — uncomment to override -# TERMINAL_CWD=. - -# Default command timeout in seconds -TERMINAL_TIMEOUT=60 - -# Cleanup inactive environments after this many seconds -TERMINAL_LIFETIME_SECONDS=300 - -# ============================================================================= -# SSH REMOTE EXECUTION (for TERMINAL_ENV=ssh) -# ============================================================================= -# Run terminal commands on a remote server via SSH. -# Agent code stays on your machine, commands execute remotely. -# -# SECURITY BENEFITS: -# - Agent cannot read your .env file (API keys protected) -# - Agent cannot modify its own code -# - Remote server acts as isolated sandbox -# - Can safely configure passwordless sudo on remote -# -# TERMINAL_SSH_HOST=192.168.1.100 -# TERMINAL_SSH_USER=agent -# TERMINAL_SSH_PORT=22 -# TERMINAL_SSH_KEY=~/.ssh/id_rsa - -# ============================================================================= -# SUDO SUPPORT (works with ALL terminal backends) -# ============================================================================= -# If set, enables sudo commands by piping password via `sudo -S`. -# Works with: local, docker, singularity, modal, and ssh backends. -# -# SECURITY WARNING: Password stored in plaintext. Only use on trusted machines. -# -# ALTERNATIVES: -# - For SSH backend: Configure passwordless sudo on the remote server -# - For containers: Run as root inside the container (no sudo needed) -# - For local: Configure /etc/sudoers for specific commands -# - For CLI: Leave unset - you'll be prompted interactively with 45s timeout -# -# SUDO_PASSWORD=your_password_here - -# ============================================================================= -# MODAL CLOUD BACKEND (Optional - for TERMINAL_ENV=modal) -# ============================================================================= -# Modal uses CLI authentication, not environment variables. -# Run: pip install modal && modal setup -# This will authenticate via browser and store credentials locally. -# No API key needed in .env - Modal handles auth automatically. - -# ============================================================================= -# BROWSER TOOL CONFIGURATION (agent-browser + Browserbase) -# ============================================================================= -# Browser automation requires Browserbase cloud service for remote browser execution. -# This allows the agent to navigate websites, fill forms, and extract information. -# -# STEALTH MODES: -# - Basic Stealth: ALWAYS active (random fingerprints, auto CAPTCHA solving) -# - Advanced Stealth: Requires BROWSERBASE_ADVANCED_STEALTH=true (Scale Plan only) - -# Browserbase API Key - Cloud browser execution -# Get at: https://browserbase.com/ -# BROWSERBASE_API_KEY= - -# Browserbase Project ID - From your Browserbase dashboard -# BROWSERBASE_PROJECT_ID= - -# Enable residential proxies for better CAPTCHA solving (default: true) -# Routes traffic through residential IPs, significantly improves success rate -BROWSERBASE_PROXIES=true - -# Enable advanced stealth mode (default: false, requires Scale Plan) -# Uses custom Chromium build to avoid bot detection altogether -BROWSERBASE_ADVANCED_STEALTH=false - -# Browser engine for local mode (default: auto = Chrome) -# "auto" — use Chrome (don't pass --engine flag) -# "lightpanda" — use Lightpanda (1.3-5.8x faster navigation, no screenshots) -# "chrome" — explicitly request Chrome -# Requires agent-browser v0.25.3+. Lightpanda commands that fail or return -# empty results are automatically retried with Chrome. -# Also configurable via browser.engine in config.yaml. -# AGENT_BROWSER_ENGINE=auto - -# Browser session timeout in seconds (default: 300) -# Sessions are cleaned up after this duration of inactivity -BROWSER_SESSION_TIMEOUT=300 - -# Browser inactivity timeout - auto-cleanup inactive sessions (default: 120 = 2 min) -# Browser sessions are automatically closed after this period of no activity -BROWSER_INACTIVITY_TIMEOUT=120 - -# Extra Chromium launch flags passed to agent-browser, comma- or newline-separated. -# Hermes auto-injects "--no-sandbox,--disable-dev-shm-usage" when it detects root -# or AppArmor-restricted unprivileged user namespaces (Ubuntu 23.10+, DGX Spark, -# many container images), so leave this unset unless you need extra flags. -# Setting this disables the auto-injection. -# AGENT_BROWSER_ARGS=--no-sandbox - -# Camofox local anti-detection browser (Camoufox-based Firefox). -# Set CAMOFOX_URL to route the browser tools through a local Camofox server -# instead of agent-browser/Browserbase. See docs/user-guide/features/browser.md. -# CAMOFOX_URL=http://localhost:9377 - -# Externally managed Camofox sessions — when another app owns the visible -# Camofox browser, set these so Hermes shares the same userId/profile instead -# of creating its own isolated session. -# CAMOFOX_USER_ID= -# CAMOFOX_SESSION_KEY= -# Set to true to reuse an already-open Camofox tab for this identity before -# creating a new one (useful for gateway restarts). -# CAMOFOX_ADOPT_EXISTING_TAB=false - -# ============================================================================= -# SESSION LOGGING -# ============================================================================= -# Session trajectories are automatically saved to logs/ directory -# Format: logs/session_YYYYMMDD_HHMMSS_UUID.json -# Contains full conversation history in trajectory format for debugging/replay - -# ============================================================================= -# VOICE TRANSCRIPTION & OPENAI TTS -# ============================================================================= -# Required for voice message transcription (Whisper) and OpenAI TTS voices. -# Uses OpenAI's API directly (not via OpenRouter). -# Named VOICE_TOOLS_OPENAI_KEY to avoid interference with OpenRouter. -# Get at: https://platform.openai.com/api-keys -# VOICE_TOOLS_OPENAI_KEY= - -# ============================================================================= -# SLACK INTEGRATION -# ============================================================================= -# Slack Bot Token - From Slack App settings (OAuth & Permissions) -# Get at: https://api.slack.com/apps -# SLACK_BOT_TOKEN=xoxb-... - -# Slack App Token - For Socket Mode (App-Level Tokens in Slack App settings) -# SLACK_APP_TOKEN=xapp-... - -# Slack allowed users (comma-separated Slack user IDs) -# SLACK_ALLOWED_USERS= - -# ============================================================================= -# TELEGRAM INTEGRATION -# ============================================================================= -# Telegram Bot Token - From @BotFather (https://t.me/BotFather) -# TELEGRAM_BOT_TOKEN= -# TELEGRAM_ALLOWED_USERS= # Comma-separated user IDs -# TELEGRAM_HOME_CHANNEL= # Default chat for cron delivery -# TELEGRAM_HOME_CHANNEL_NAME= # Display name for home channel - -# Webhook mode (optional — for cloud deployments like Fly.io/Railway) -# Default is long polling. Setting TELEGRAM_WEBHOOK_URL switches to webhook mode. -# TELEGRAM_WEBHOOK_URL=https://my-app.fly.dev/telegram -# TELEGRAM_WEBHOOK_PORT=8443 -# TELEGRAM_WEBHOOK_SECRET= # Recommended for production - -# WhatsApp (built-in Baileys bridge — run `hermes whatsapp` to pair) -# WHATSAPP_ENABLED=false -# WHATSAPP_ALLOWED_USERS=15551234567 - -# Email (IMAP/SMTP — send and receive emails as Hermes) -# For Gmail: enable 2FA → create App Password at https://myaccount.google.com/apppasswords -# EMAIL_ADDRESS=hermes@gmail.com -# EMAIL_PASSWORD=xxxx xxxx xxxx xxxx -# EMAIL_IMAP_HOST=imap.gmail.com -# EMAIL_IMAP_PORT=993 -# EMAIL_SMTP_HOST=smtp.gmail.com -# EMAIL_SMTP_PORT=587 -# EMAIL_POLL_INTERVAL=15 -# EMAIL_ALLOWED_USERS=your@email.com -# EMAIL_HOME_ADDRESS=your@email.com - -# Gateway-wide: allow ALL users without an allowlist (default: false = deny) -# Only set to true if you intentionally want open access. -# GATEWAY_ALLOW_ALL_USERS=false - -# ============================================================================= -# RESPONSE PACING -# ============================================================================= -# Human-like delays between message chunks on messaging platforms. -# Makes the bot feel less robotic. -# HERMES_HUMAN_DELAY_MODE=off # off | natural | custom -# HERMES_HUMAN_DELAY_MIN_MS=800 # Min delay in ms (custom mode) -# HERMES_HUMAN_DELAY_MAX_MS=2500 # Max delay in ms (custom mode) - -# ============================================================================= -# DEBUG OPTIONS -# ============================================================================= -WEB_TOOLS_DEBUG=false -VISION_TOOLS_DEBUG=false -MOA_TOOLS_DEBUG=false -IMAGE_TOOLS_DEBUG=false - -# ============================================================================= -# CONTEXT COMPRESSION (Auto-shrinks long conversations) -# ============================================================================= -# When conversation approaches model's context limit, middle turns are -# automatically summarized to free up space. -# -# Context compression is configured in ~/.hermes/config.yaml under compression: -# CONTEXT_COMPRESSION_ENABLED=true # Enable auto-compression (default: true) -# CONTEXT_COMPRESSION_THRESHOLD=0.85 # Compress at 85% of context limit -# Model is set via compression.summary_model in config.yaml (default: google/gemini-3-flash-preview) - -# ============================================================================= -# SKILLS HUB (GitHub integration for skill search/install/publish) -# ============================================================================= - -# GitHub Personal Access Token — for higher API rate limits on skill search/install -# Get at: https://github.com/settings/tokens (Fine-grained recommended) -# GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx - -# GitHub App credentials (optional — for bot identity on PRs) -# GITHUB_APP_ID= -# GITHUB_APP_PRIVATE_KEY_PATH= -# GITHUB_APP_INSTALLATION_ID= - -# Groq API key (free tier — used for Whisper STT in voice mode) -# GROQ_API_KEY= - -# ============================================================================= -# STT PROVIDER SELECTION -# ============================================================================= -# Default STT provider is "local" (faster-whisper) — runs on your machine, no API key needed. -# Install with: pip install faster-whisper -# Model downloads automatically on first use (~150 MB for "base"). -# To use cloud providers instead, set GROQ_API_KEY or VOICE_TOOLS_OPENAI_KEY above. -# Provider priority: local > groq > openai -# Configure in config.yaml: stt.provider: local | groq | openai - -# ============================================================================= -# STT ADVANCED OVERRIDES (optional) -# ============================================================================= -# Override default STT models per provider (normally set via stt.model in config.yaml) -# STT_GROQ_MODEL=whisper-large-v3-turbo -# STT_OPENAI_MODEL=whisper-1 - -# Override STT provider endpoints (for proxies or self-hosted instances) -# GROQ_BASE_URL=https://api.groq.com/openai/v1 -# STT_OPENAI_BASE_URL=https://api.openai.com/v1 - -# ============================================================================= -# MICROSOFT TEAMS INTEGRATION -# ============================================================================= -# Register a Bot in Azure: https://dev.botframework.com/ → "Register a bot" -# Or use Azure Portal: Azure Active Directory → App registrations → New registration -# Then add the bot to Teams via the Bot Framework or App Studio. -# -# TEAMS_CLIENT_ID= # Azure AD App (client) ID -# TEAMS_CLIENT_SECRET= # Azure AD client secret value -# TEAMS_TENANT_ID= # Azure AD tenant ID (or "common" for multi-tenant) -# TEAMS_ALLOWED_USERS= # Comma-separated AAD object IDs or UPNs -# TEAMS_ALLOW_ALL_USERS=false # Set true to skip the allowlist -# TEAMS_HOME_CHANNEL= # Default channel/chat ID for cron delivery -# TEAMS_HOME_CHANNEL_NAME= # Display name for the home channel -# TEAMS_PORT=3978 # Webhook listen port (Bot Framework default) - -# ============================================================================= -# GOOGLE CHAT INTEGRATION -# ============================================================================= -# Connects via Cloud Pub/Sub pull subscription (no public URL required). -# Setup walkthrough: website/docs/user-guide/messaging/google_chat.md. -# 1. Create a GCP project, enable the Google Chat API and Cloud Pub/Sub. -# 2. Create a Service Account with roles/pubsub.subscriber on the -# subscription (NOT project-wide); download the JSON key. -# 3. Configure your Chat app at console.cloud.google.com/apis/credentials -# → Google Chat API → Configuration → Cloud Pub/Sub topic. -# 4. (Optional, for native attachment delivery) Each user runs -# `/setup-files` once in their own DM after Pub/Sub is wired up. -# -# GOOGLE_CHAT_PROJECT_ID= # GCP project hosting the topic (or set GOOGLE_CLOUD_PROJECT) -# GOOGLE_CHAT_SUBSCRIPTION_NAME= # Full path: projects//subscriptions/ -# GOOGLE_CHAT_SERVICE_ACCOUNT_JSON= # Path to SA JSON (or set GOOGLE_APPLICATION_CREDENTIALS) -# GOOGLE_CHAT_ALLOWED_USERS= # Comma-separated emails allowed to talk to the bot -# GOOGLE_CHAT_ALLOW_ALL_USERS=false # Set true to skip the allowlist -# GOOGLE_CHAT_HOME_CHANNEL= # Default space (spaces/XXXX) for cron delivery -# GOOGLE_CHAT_HOME_CHANNEL_NAME= # Display name for the home channel diff --git a/reference/hermes-agent-2026.5.16/.envrc b/reference/hermes-agent-2026.5.16/.envrc deleted file mode 100755 index 45c5952..0000000 --- a/reference/hermes-agent-2026.5.16/.envrc +++ /dev/null @@ -1,5 +0,0 @@ -watch_file pyproject.toml uv.lock -watch_file ui-tui/package-lock.json ui-tui/package.json -watch_file flake.nix flake.lock nix/devShell.nix nix/tui.nix nix/package.nix nix/python.nix - -use flake diff --git a/reference/hermes-agent-2026.5.16/.gitattributes b/reference/hermes-agent-2026.5.16/.gitattributes deleted file mode 100755 index 8726216..0000000 --- a/reference/hermes-agent-2026.5.16/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto-generated files — collapse diffs and exclude from language stats -web/package-lock.json linguist-generated=true diff --git a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/bug_report.yml b/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100755 index 67a3f64..0000000 --- a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,162 +0,0 @@ -name: "🐛 Bug Report" -description: Report a bug — something that's broken, crashes, or behaves incorrectly. -title: "[Bug]: " -labels: ["bug"] -body: - - type: markdown - attributes: - value: | - Thanks for reporting a bug! Please fill out the sections below so we can reproduce and fix it quickly. - - **Before submitting**, please: - - [ ] Search [existing issues](https://github.com/NousResearch/hermes-agent/issues) to avoid duplicates - - [ ] Update to the latest version (`hermes update`) and confirm the bug still exists - - [ ] Run `hermes debug share` and paste the links below (see Debug Report section) - - - type: textarea - id: description - attributes: - label: Bug Description - description: A clear description of what's broken. Include error messages, tracebacks, or screenshots if relevant. - placeholder: | - What happened? What did you expect to happen instead? - validations: - required: true - - - type: textarea - id: reproduction - attributes: - label: Steps to Reproduce - description: Minimal steps to trigger the bug. The more specific, the faster we can fix it. - placeholder: | - 1. Run `hermes chat` - 2. Send the message "..." - 3. Agent calls tool X - 4. Error appears: ... - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected Behavior - description: What should have happened instead? - validations: - required: true - - - type: textarea - id: actual - attributes: - label: Actual Behavior - description: What actually happened? Include full error output if available. - validations: - required: true - - - type: dropdown - id: component - attributes: - label: Affected Component - description: Which part of Hermes is affected? - multiple: true - options: - - CLI (interactive chat) - - Gateway (Telegram/Discord/Slack/WhatsApp) - - Setup / Installation - - Tools (terminal, file ops, web, code execution, etc.) - - Skills (skill loading, skill hub, skill guard) - - Agent Core (conversation loop, context compression, memory) - - Configuration (config.yaml, .env, hermes setup) - - Other - validations: - required: true - - - type: dropdown - id: platform - attributes: - label: Messaging Platform (if gateway-related) - description: Which platform adapter is affected? - multiple: true - options: - - N/A (CLI only) - - Telegram - - Discord - - Slack - - WhatsApp - - - type: textarea - id: debug-report - attributes: - label: Debug Report - description: | - Run `hermes debug share` from your terminal and paste the links it prints here. - This uploads your system info, config, and recent logs to a paste service automatically. - - If you're in an interactive chat session, you can also use the `/debug` slash command — it does the same thing. - - If the upload fails, run `hermes debug share --local` and paste the output directly. - placeholder: | - Report https://paste.rs/abc123 - agent.log https://paste.rs/def456 - gateway.log https://paste.rs/ghi789 - render: shell - validations: - required: true - - - type: input - id: os - attributes: - label: Operating System - description: e.g. Ubuntu 24.04, macOS 15.2, Windows 11 - placeholder: Ubuntu 24.04 - validations: - required: true - - - type: input - id: python-version - attributes: - label: Python Version - description: Output of `python --version` - placeholder: "3.11.9" - - - type: input - id: hermes-version - attributes: - label: Hermes Version - description: Output of `hermes version` - placeholder: "2.1.0" - - - type: textarea - id: logs - attributes: - label: Additional Logs / Traceback (optional) - description: | - The debug report above covers most logs. Use this field for any extra error output, - tracebacks, or screenshots not captured by `hermes debug share`. - render: shell - - - type: textarea - id: root-cause - attributes: - label: Root Cause Analysis (optional) - description: | - If you've dug into the code and identified the root cause, share it here. - Include file paths, line numbers, and code snippets if possible. This massively speeds up fixes. - placeholder: | - The bug is in `gateway/run.py` line 949. `len(history)` counts session_meta entries - but `agent_messages` was built from filtered history... - - - type: textarea - id: proposed-fix - attributes: - label: Proposed Fix (optional) - description: If you have a fix in mind (or a PR ready), describe it here. - placeholder: | - Replace `.get()` with `.pop()` on line 289 of `gateway/platforms/base.py` - to actually clear the pending message after retrieval. - - - type: checkboxes - id: pr-ready - attributes: - label: Are you willing to submit a PR for this? - options: - - label: I'd like to fix this myself and submit a PR diff --git a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/config.yml b/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100755 index 0daa52c..0000000 --- a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,11 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: 💬 Nous Research Discord - url: https://discord.gg/NousResearch - about: For quick questions, showcasing projects, sharing skills, and community chat. - - name: 📖 Documentation - url: https://github.com/NousResearch/hermes-agent/blob/main/README.md - about: Check the README and docs before opening an issue. - - name: 🤝 Contributing Guide - url: https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md - about: Read this before submitting a PR. diff --git a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/feature_request.yml b/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100755 index 720cc8f..0000000 --- a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: "✨ Feature Request" -description: Suggest a new feature or improvement. -title: "[Feature]: " -labels: ["enhancement"] -body: - - type: markdown - attributes: - value: | - Thanks for the suggestion! Before submitting, please consider: - - - **Is this a new skill?** Most capabilities should be [skills, not tools](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#should-it-be-a-skill-or-a-tool). If it's a specialized integration (crypto, NFT, niche SaaS), it belongs on the Skills Hub, not bundled. - - **Search [existing issues](https://github.com/NousResearch/hermes-agent/issues)** — someone may have already proposed this. - - - type: textarea - id: problem - attributes: - label: Problem or Use Case - description: What problem does this solve? What are you trying to do that you can't today? - placeholder: | - I'm trying to use Hermes with [provider/platform/workflow] but currently - there's no way to... - validations: - required: true - - - type: textarea - id: solution - attributes: - label: Proposed Solution - description: How do you think this should work? Be as specific as you can — CLI flags, config options, UI behavior. - placeholder: | - Add a `--foo` flag to `hermes chat` that enables... - Or: Add a config key `bar.baz` that controls... - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Alternatives Considered - description: What other approaches did you consider? Why is the proposed solution better? - - - type: dropdown - id: type - attributes: - label: Feature Type - options: - - New tool - - New bundled skill - - CLI improvement - - Gateway / messaging improvement - - Configuration option - - Performance / reliability - - Developer experience (tests, docs, CI) - - Other - validations: - required: true - - - type: dropdown - id: scope - attributes: - label: Scope - description: How big is this change? - options: - - Small (single file, < 50 lines) - - Medium (few files, < 300 lines) - - Large (new module or significant refactor) - - - type: checkboxes - id: pr-ready - attributes: - label: Contribution - options: - - label: I'd like to implement this myself and submit a PR - - - type: textarea - id: debug-report - attributes: - label: Debug Report (optional) - description: | - If this feature request is related to a problem you're experiencing, run `hermes debug share` and paste the links here. - In an interactive chat session, you can use `/debug` instead. - This helps us understand your environment and any related logs. - placeholder: | - Report https://paste.rs/abc123 - render: shell diff --git a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/setup_help.yml b/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/setup_help.yml deleted file mode 100755 index 974181b..0000000 --- a/reference/hermes-agent-2026.5.16/.github/ISSUE_TEMPLATE/setup_help.yml +++ /dev/null @@ -1,112 +0,0 @@ -name: "🔧 Setup / Installation Help" -description: Having trouble installing or configuring Hermes? Ask here. -title: "[Setup]: " -labels: ["setup"] -body: - - type: markdown - attributes: - value: | - Sorry you're having trouble! Please fill out the details below so we can help. - - **Quick checks first:** - - Run `hermes debug share` and paste the links in the Debug Report section below - - If you're in a chat session, you can use `/debug` instead — it does the same thing - - Try `hermes update` to get the latest version - - Check the [README troubleshooting section](https://github.com/NousResearch/hermes-agent#troubleshooting) - - For general questions, consider the [Nous Research Discord](https://discord.gg/NousResearch) for faster help - - - type: textarea - id: description - attributes: - label: What's Going Wrong? - description: Describe what you're trying to do and where it fails. - placeholder: | - I ran `hermes setup` and selected Nous Portal, but when I try to - start the gateway I get... - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Steps Taken - description: What did you do? Include the exact commands you ran. - placeholder: | - 1. Ran the install script: `curl -fsSL ... | bash` - 2. Ran `hermes setup` and chose "Quick setup" - 3. Selected OpenRouter, entered API key - 4. Ran `hermes chat` and got error... - validations: - required: true - - - type: dropdown - id: install-method - attributes: - label: Installation Method - options: - - Install script (curl | bash) - - Manual clone + pip/uv install - - PowerShell installer (Windows) - - Docker - - Other - validations: - required: true - - - type: input - id: os - attributes: - label: Operating System - placeholder: Ubuntu 24.04 / macOS 15.2 / Windows 11 - validations: - required: true - - - type: input - id: python-version - attributes: - label: Python Version - description: Output of `python --version` (or `python3 --version`) - placeholder: "3.11.9" - - - type: input - id: hermes-version - attributes: - label: Hermes Version - description: Output of `hermes version` (if install got that far) - placeholder: "2.1.0" - - - type: textarea - id: debug-report - attributes: - label: Debug Report - description: | - Run `hermes debug share` from your terminal and paste the links it prints here. - This uploads your system info, config, and recent logs to a paste service automatically. - - If you're in an interactive chat session, you can also use the `/debug` slash command — it does the same thing. - - If the upload fails or install didn't get that far, run `hermes debug share --local` and paste the output directly. - If even that doesn't work, run `hermes doctor` and paste that output instead. - placeholder: | - Report https://paste.rs/abc123 - agent.log https://paste.rs/def456 - gateway.log https://paste.rs/ghi789 - render: shell - - - type: textarea - id: error-output - attributes: - label: Full Error Output - description: Paste the complete error message or traceback. This will be auto-formatted. - render: shell - validations: - required: true - - - type: textarea - id: tried - attributes: - label: What I've Already Tried - description: List any fixes or workarounds you've already attempted. - placeholder: | - - Ran `hermes update` - - Tried reinstalling with `pip install -e ".[all]"` - - Checked that OPENROUTER_API_KEY is set in ~/.hermes/.env diff --git a/reference/hermes-agent-2026.5.16/.github/PULL_REQUEST_TEMPLATE.md b/reference/hermes-agent-2026.5.16/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100755 index 5496eb5..0000000 --- a/reference/hermes-agent-2026.5.16/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,75 +0,0 @@ -## What does this PR do? - - - - - -## Related Issue - - - -Fixes # - -## Type of Change - - - -- [ ] 🐛 Bug fix (non-breaking change that fixes an issue) -- [ ] ✨ New feature (non-breaking change that adds functionality) -- [ ] 🔒 Security fix -- [ ] 📝 Documentation update -- [ ] ✅ Tests (adding or improving test coverage) -- [ ] ♻️ Refactor (no behavior change) -- [ ] 🎯 New skill (bundled or hub) - -## Changes Made - - - -- - -## How to Test - - - -1. -2. -3. - -## Checklist - - - -### Code - -- [ ] I've read the [Contributing Guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md) -- [ ] My commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) (`fix(scope):`, `feat(scope):`, etc.) -- [ ] I searched for [existing PRs](https://github.com/NousResearch/hermes-agent/pulls) to make sure this isn't a duplicate -- [ ] My PR contains **only** changes related to this fix/feature (no unrelated commits) -- [ ] I've run `pytest tests/ -q` and all tests pass -- [ ] I've added tests for my changes (required for bug fixes, strongly encouraged for features) -- [ ] I've tested on my platform: - -### Documentation & Housekeeping - - - -- [ ] I've updated relevant documentation (README, `docs/`, docstrings) — or N/A -- [ ] I've updated `cli-config.yaml.example` if I added/changed config keys — or N/A -- [ ] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — or N/A -- [ ] I've considered cross-platform impact (Windows, macOS) per the [compatibility guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#cross-platform-compatibility) — or N/A -- [ ] I've updated tool descriptions/schemas if I changed tool behavior — or N/A - -## For New Skills - - - -- [ ] This skill is **broadly useful** to most users (if bundled) — see [Contributing Guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#should-the-skill-be-bundled) -- [ ] SKILL.md follows the [standard format](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#skillmd-format) (frontmatter, trigger conditions, steps, pitfalls) -- [ ] No external dependencies that aren't already available (prefer stdlib, curl, existing Hermes tools) -- [ ] I've tested the skill end-to-end: `hermes --toolsets skills -q "Use the X skill to do Y"` - -## Screenshots / Logs - - - diff --git a/reference/hermes-agent-2026.5.16/.github/actions/hermes-smoke-test/action.yml b/reference/hermes-agent-2026.5.16/.github/actions/hermes-smoke-test/action.yml deleted file mode 100755 index 08b9f93..0000000 --- a/reference/hermes-agent-2026.5.16/.github/actions/hermes-smoke-test/action.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Hermes smoke test -description: > - Run the image's built-in entrypoint against `--help` and `dashboard --help` - to catch basic runtime regressions before publishing. Requires the image - to already be loaded into the local Docker daemon under `image`. - - Works identically on amd64 and arm64 runners. - -inputs: - image: - description: Fully-qualified image tag (e.g. nousresearch/hermes-agent:test) - required: true - -runs: - using: composite - steps: - - name: Ensure /tmp/hermes-test is hermes-writable - shell: bash - run: | - # The image runs as the hermes user (UID 10000). GitHub Actions - # creates /tmp/hermes-test root-owned by default, which hermes - # can't write to — chown it to match the in-container UID before - # bind-mounting. Real users doing `docker run -v ~/.hermes:...` - # with their own UID hit the same issue and have their own - # remediations (HERMES_UID env var, or chown locally). - mkdir -p /tmp/hermes-test - sudo chown -R 10000:10000 /tmp/hermes-test - - - name: hermes --help - shell: bash - run: | - docker run --rm \ - -v /tmp/hermes-test:/opt/data \ - --entrypoint /opt/hermes/docker/entrypoint.sh \ - "${{ inputs.image }}" --help - - - name: hermes dashboard --help - shell: bash - run: | - # Regression guard for #9153: dashboard was present in source but - # missing from the published image. If this fails, something in - # the Dockerfile is excluding the dashboard subcommand from the - # installed package. - docker run --rm \ - -v /tmp/hermes-test:/opt/data \ - --entrypoint /opt/hermes/docker/entrypoint.sh \ - "${{ inputs.image }}" dashboard --help diff --git a/reference/hermes-agent-2026.5.16/.github/actions/nix-setup/action.yml b/reference/hermes-agent-2026.5.16/.github/actions/nix-setup/action.yml deleted file mode 100755 index 0aeaf91..0000000 --- a/reference/hermes-agent-2026.5.16/.github/actions/nix-setup/action.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: 'Setup Nix' -description: 'Install Nix and configure Cachix binary cache' - -inputs: - cachix-auth-token: - description: 'Cachix auth token (enables push). Omit for read-only.' - required: false - default: '' - -runs: - using: composite - steps: - - uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22 - - uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17 - with: - name: hermes-agent - authToken: ${{ inputs.cachix-auth-token }} - continue-on-error: true diff --git a/reference/hermes-agent-2026.5.16/.github/dependabot.yml b/reference/hermes-agent-2026.5.16/.github/dependabot.yml deleted file mode 100755 index 3854c8f..0000000 --- a/reference/hermes-agent-2026.5.16/.github/dependabot.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Dependabot configuration for hermes-agent. -# -# Deliberately scoped to github-actions only. -# -# We do NOT enable Dependabot for pip / npm / any source-dependency ecosystem -# because we pin source dependencies exactly (uv.lock, package-lock.json) as -# part of our supply-chain posture. Automatic version-bump PRs against those -# pins would undermine the strategy — pins are moved deliberately, after -# review, not on a schedule. -# -# github-actions is the exception: action pins (we use full commit SHAs per -# supply-chain policy) must be updated when upstream actions publish -# patches — usually themselves security fixes. Dependabot opens a PR with -# the new SHA and release notes; we review and merge like any other PR. -# -# Security-update PRs for source dependencies (opened ONLY when a CVE is -# published affecting a currently-pinned version) are enabled separately -# via the repo's Dependabot security updates setting -# (Settings → Code security → Dependabot → Dependabot security updates). -# Those are CVE-only, not schedule-driven, and do not conflict with our -# pinning strategy — they fire when a pinned version becomes known-bad, -# which is exactly when we want to move the pin. - -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - day: "monday" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "github-actions" - commit-message: - prefix: "chore(actions)" - include: "scope" - groups: - # Batch routine action bumps into one PR per week to reduce noise. - # Security updates still open individually and bypass grouping. - actions-minor-patch: - update-types: - - "minor" - - "patch" diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/contributor-check.yml b/reference/hermes-agent-2026.5.16/.github/workflows/contributor-check.yml deleted file mode 100755 index 3ca4991..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/contributor-check.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Contributor Attribution Check - -on: - pull_request: - branches: [main] - paths: - # Only run when code files change (not docs-only PRs) - - '*.py' - - '**/*.py' - - '.github/workflows/contributor-check.yml' - -permissions: - contents: read - -jobs: - check-attribution: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 # Full history needed for git log - - - name: Check for unmapped contributor emails - run: | - # Get the merge base between this PR and main - MERGE_BASE=$(git merge-base origin/main HEAD) - - # Find any new author emails in this PR's commits - NEW_EMAILS=$(git log ${MERGE_BASE}..HEAD --format='%ae' --no-merges | sort -u) - - if [ -z "$NEW_EMAILS" ]; then - echo "No new commits to check." - exit 0 - fi - - # Check each email against AUTHOR_MAP in release.py - MISSING="" - while IFS= read -r email; do - # Skip teknium and bot emails - case "$email" in - *teknium*|*noreply@github.com*|*dependabot*|*github-actions*|*anthropic.com*|*cursor.com*) - continue ;; - esac - - # Check if email is in AUTHOR_MAP (either as a key or matches noreply pattern) - if echo "$email" | grep -qP '\+.*@users\.noreply\.github\.com'; then - continue # GitHub noreply emails auto-resolve - fi - - if ! grep -qF "\"${email}\"" scripts/release.py 2>/dev/null; then - AUTHOR=$(git log --author="$email" --format='%an' -1) - MISSING="${MISSING}\n ${email} (${AUTHOR})" - fi - done <<< "$NEW_EMAILS" - - if [ -n "$MISSING" ]; then - echo "" - echo "⚠️ New contributor email(s) not in AUTHOR_MAP:" - echo -e "$MISSING" - echo "" - echo "Please add mappings to scripts/release.py AUTHOR_MAP:" - echo -e "$MISSING" | while read -r line; do - email=$(echo "$line" | sed 's/^ *//' | cut -d' ' -f1) - [ -z "$email" ] && continue - echo " \"${email}\": \"\"," - done - echo "" - echo "To find the GitHub username for an email:" - echo " gh api 'search/users?q=EMAIL+in:email' --jq '.items[0].login'" - exit 1 - else - echo "✅ All contributor emails are mapped in AUTHOR_MAP." - fi diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/deploy-site.yml b/reference/hermes-agent-2026.5.16/.github/workflows/deploy-site.yml deleted file mode 100755 index 8df74c0..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/deploy-site.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Deploy Site - -on: - release: - types: [published] - push: - branches: [main] - paths: - - 'website/**' - - 'skills/**' - - 'optional-skills/**' - - '.github/workflows/deploy-site.yml' - workflow_dispatch: - -permissions: - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: false - -jobs: - deploy-vercel: - if: github.event_name == 'release' - runs-on: ubuntu-latest - steps: - - name: Trigger Vercel Deploy - run: curl -X POST "${{ secrets.VERCEL_DEPLOY_HOOK }}" - - deploy-docs: - if: github.repository == 'NousResearch/hermes-agent' - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deploy.outputs.page_url }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: 20 - cache: npm - cache-dependency-path: website/package-lock.json - - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: '3.11' - - - name: Install PyYAML for skill extraction - run: pip install pyyaml==6.0.2 httpx==0.28.1 - - - name: Extract skill metadata for dashboard - run: python3 website/scripts/extract-skills.py - - - name: Regenerate per-skill docs pages + catalogs - run: python3 website/scripts/generate-skill-docs.py - - - name: Build skills index (if not already present) - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [ ! -f website/static/api/skills-index.json ]; then - python3 scripts/build_skills_index.py || echo "Skills index build failed (non-fatal)" - fi - - - name: Install dependencies - run: npm ci - working-directory: website - - - name: Build Docusaurus - run: npm run build - working-directory: website - - - name: Stage deployment - run: | - mkdir -p _site/docs - cp -r website/build/* _site/docs/ - # llms.txt / llms-full.txt are also published at the site root - # (https://hermes-agent.nousresearch.com/llms.txt) because some - # agents and IDE plugins probe the classic root-level path rather - # than /docs/llms.txt. Same file, two URLs, one source of truth. - if [ -f website/build/llms.txt ]; then - cp website/build/llms.txt _site/llms.txt - fi - if [ -f website/build/llms-full.txt ]; then - cp website/build/llms-full.txt _site/llms-full.txt - fi - - - name: Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 - with: - path: _site - - - name: Deploy to GitHub Pages - id: deploy - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/docker-publish.yml b/reference/hermes-agent-2026.5.16/.github/workflows/docker-publish.yml deleted file mode 100755 index cccb8f3..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,534 +0,0 @@ -name: Docker Build and Publish - -on: - push: - branches: [main] - paths: - - '**/*.py' - - 'pyproject.toml' - - 'uv.lock' - - 'Dockerfile' - - 'docker/**' - - '.github/workflows/docker-publish.yml' - - '.github/actions/hermes-smoke-test/**' - pull_request: - branches: [main] - paths: - - '**/*.py' - - 'pyproject.toml' - - 'uv.lock' - - 'Dockerfile' - - 'docker/**' - - '.github/workflows/docker-publish.yml' - - '.github/actions/hermes-smoke-test/**' - release: - types: [published] - -permissions: - contents: read - -# Concurrency: push/release runs are NEVER cancelled so every merge gets its -# own SHA-tagged image; :main and :latest are guarded separately by the -# move-main and move-latest jobs. PR runs reuse a PR-scoped group with -# cancel-in-progress: true so rapid pushes to the same PR collapse to the -# latest commit. -concurrency: - group: docker-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -env: - IMAGE_NAME: nousresearch/hermes-agent - -jobs: - # --------------------------------------------------------------------------- - # Build amd64 natively. This job also runs the smoke tests (basic --help - # and the dashboard subcommand regression guard from #9153), because amd64 - # is the only arch we can `load` into the local daemon on an amd64 runner. - # --------------------------------------------------------------------------- - build-amd64: - # Only run on the upstream repository, not on forks - if: github.repository == 'NousResearch/hermes-agent' - runs-on: ubuntu-latest - timeout-minutes: 45 - outputs: - digest: ${{ steps.push.outputs.digest }} - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - submodules: recursive - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - # Build once, load into the local daemon for smoke testing. Cached - # to gha with a per-arch scope; the push step below reuses every - # layer from this build. - - name: Build image (amd64, smoke test) - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - context: . - file: Dockerfile - load: true - platforms: linux/amd64 - tags: ${{ env.IMAGE_NAME }}:test - cache-from: type=gha,scope=docker-amd64 - cache-to: type=gha,mode=max,scope=docker-amd64 - - - name: Smoke test image - uses: ./.github/actions/hermes-smoke-test - with: - image: ${{ env.IMAGE_NAME }}:test - - - name: Log in to Docker Hub - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - # Push amd64 by digest only (no tag). The merge job assembles the - # tagged manifest list. `push-by-digest=true` is docker's recommended - # pattern for multi-runner multi-platform builds. - # - # We apply the OCI revision label here (and again on arm64) because - # the move-main / move-latest jobs read it off the linux/amd64 - # sub-manifest config of the floating tag to decide whether it's safe - # to advance. The label must be on each per-arch image — manifest - # lists themselves don't carry image config labels. - - name: Push amd64 by digest - id: push - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - context: . - file: Dockerfile - platforms: linux/amd64 - labels: | - org.opencontainers.image.revision=${{ github.sha }} - outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=gha,scope=docker-amd64 - cache-to: type=gha,mode=max,scope=docker-amd64 - - # Write the digest to a file and upload it as an artifact so the - # merge job can stitch both per-arch digests into a manifest list. - - name: Export digest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - run: | - mkdir -p /tmp/digests - digest="${{ steps.push.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest artifact - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: digest-amd64 - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - # --------------------------------------------------------------------------- - # Build arm64 natively on GitHub's free arm64 runner. This replaces the - # previous QEMU-emulated arm64 build, which was ~5-10x slower and shared - # a cache scope with amd64. Matches the amd64 job's shape: build+load, - # smoke test, then on push/release push by digest. - # --------------------------------------------------------------------------- - build-arm64: - if: github.repository == 'NousResearch/hermes-agent' - runs-on: ubuntu-24.04-arm - timeout-minutes: 45 - outputs: - digest: ${{ steps.push.outputs.digest }} - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - submodules: recursive - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - # Build once, load into the local daemon for smoke testing. Cached - # to gha with a per-arch scope; the push step below reuses every - # layer from this build. - - name: Build image (arm64, smoke test) - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - context: . - file: Dockerfile - load: true - platforms: linux/arm64 - tags: ${{ env.IMAGE_NAME }}:test - cache-from: type=gha,scope=docker-arm64 - cache-to: type=gha,mode=max,scope=docker-arm64 - - - name: Smoke test image - uses: ./.github/actions/hermes-smoke-test - with: - image: ${{ env.IMAGE_NAME }}:test - - - name: Log in to Docker Hub - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Push arm64 by digest - id: push - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - context: . - file: Dockerfile - platforms: linux/arm64 - labels: | - org.opencontainers.image.revision=${{ github.sha }} - outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=gha,scope=docker-arm64 - cache-to: type=gha,mode=max,scope=docker-arm64 - - - name: Export digest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - run: | - mkdir -p /tmp/digests - digest="${{ steps.push.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest artifact - if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: digest-arm64 - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - # --------------------------------------------------------------------------- - # Stitch both per-arch digests into a single tagged multi-arch manifest. - # This is a registry-side operation — no building, no layer re-push — - # so it runs in ~30 seconds. On main pushes it produces :sha-. - # On releases it produces :. - # --------------------------------------------------------------------------- - merge: - if: github.repository == 'NousResearch/hermes-agent' && (github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release') - runs-on: ubuntu-latest - needs: [build-amd64, build-arm64] - timeout-minutes: 10 - outputs: - pushed_sha_tag: ${{ steps.mark_pushed.outputs.pushed }} - pushed_release_tag: ${{ steps.mark_release_pushed.outputs.pushed }} - release_tag: ${{ steps.tag.outputs.tag }} - steps: - - name: Download digests - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - path: /tmp/digests - pattern: digest-* - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - # Compute the tag for this run. Main pushes use sha- (so every - # commit gets its own immutable tag); releases use the release tag name. - - name: Compute tag - id: tag - run: | - if [ "${{ github.event_name }}" = "release" ]; then - echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" - else - echo "tag=sha-${{ github.sha }}" >> "$GITHUB_OUTPUT" - fi - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - set -euo pipefail - # Build the arg array from each digest file (filename = the digest - # hex, with no sha256: prefix; empty file content, only the name - # matters). Using an array avoids shellcheck SC2046 and keeps - # every digest a single argv token even under pathological names. - args=() - for digest_file in *; do - args+=("${IMAGE_NAME}@sha256:${digest_file}") - done - docker buildx imagetools create \ - -t "${IMAGE_NAME}:${TAG}" \ - "${args[@]}" - env: - IMAGE_NAME: ${{ env.IMAGE_NAME }} - TAG: ${{ steps.tag.outputs.tag }} - - - name: Inspect image - run: | - docker buildx imagetools inspect "${IMAGE_NAME}:${TAG}" - env: - IMAGE_NAME: ${{ env.IMAGE_NAME }} - TAG: ${{ steps.tag.outputs.tag }} - - # Signal to move-main that the SHA tag is live. Only on main pushes; - # releases set pushed_release_tag instead. - - name: Mark SHA tag pushed - id: mark_pushed - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: echo "pushed=true" >> "$GITHUB_OUTPUT" - - # Signal to move-latest that the release tag is live. - - name: Mark release tag pushed - id: mark_release_pushed - if: github.event_name == 'release' - run: echo "pushed=true" >> "$GITHUB_OUTPUT" - - # --------------------------------------------------------------------------- - # Move :main to point at the SHA tag the merge job pushed. - # - # :main is the floating tag that tracks the tip of the main branch. Every - # merge to main retags :main forward. Users who want "latest dev build" - # pull :main; users who want stable releases pull :latest. - # - # The real serialization guarantee comes from the top-level concurrency - # group (`docker-${{ github.ref }}` with `cancel-in-progress: false`), - # which ensures at most one workflow run for this ref executes at a time. - # That means two move-main steps for the same ref cannot overlap. - # - # This job has its own concurrency group as defense-in-depth: if the - # top-level group is ever loosened, queued move-mains will run serially - # in arrival order, each one running the ancestor check below and either - # advancing :main or skipping. `cancel-in-progress: false` matches the - # top-level setting — we don't want rapid pushes to cancel a queued - # move-main, because the ancestor check is the real safety mechanism - # and queueing is cheap (move-main is a ~30s registry op). - # - # Combined with the ancestor check, this means :main only ever moves - # forward in git history. - # --------------------------------------------------------------------------- - move-main: - if: | - github.repository == 'NousResearch/hermes-agent' - && github.event_name == 'push' - && github.ref == 'refs/heads/main' - && needs.merge.outputs.pushed_sha_tag == 'true' - needs: merge - runs-on: ubuntu-latest - timeout-minutes: 10 - concurrency: - group: docker-move-main-${{ github.ref }} - cancel-in-progress: false - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 1000 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - # Read the git revision label off the current :main manifest, then - # use `git merge-base --is-ancestor` to check whether our commit is a - # descendant of it. If :main doesn't exist yet, or its label is - # missing, we treat that as "safe to publish". If another run already - # advanced :main past us (or diverged), we skip and leave it alone. - - name: Decide whether to move :main - id: main_check - run: | - set -euo pipefail - image=nousresearch/hermes-agent - - # Pull the JSON for the linux/amd64 sub-manifest's config and extract - # the OCI revision label with jq — Go template field access can't - # handle dots in map keys, so using json+jq is the robust route. - image_json=$( - docker buildx imagetools inspect "${image}:main" \ - --format '{{ json (index .Image "linux/amd64") }}' \ - 2>/dev/null || true - ) - - if [ -z "${image_json}" ]; then - echo "No existing :main (or inspect failed) — safe to publish." - echo "push_main=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - current_sha=$( - printf '%s' "${image_json}" \ - | jq -r '.config.Labels."org.opencontainers.image.revision" // ""' - ) - - if [ -z "${current_sha}" ]; then - echo "Registry :main has no revision label — safe to publish." - echo "push_main=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "Registry :main is at ${current_sha}" - echo "This run is at ${GITHUB_SHA}" - - if [ "${current_sha}" = "${GITHUB_SHA}" ]; then - echo ":main already points at our SHA — nothing to do." - echo "push_main=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Make sure we have the :main commit locally for merge-base. - if ! git cat-file -e "${current_sha}^{commit}" 2>/dev/null; then - git fetch --no-tags --prune origin \ - "+refs/heads/main:refs/remotes/origin/main" \ - || true - fi - - if ! git cat-file -e "${current_sha}^{commit}" 2>/dev/null; then - echo "Registry :main points at an unknown commit (${current_sha}); refusing to overwrite." - echo "push_main=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Our SHA must be a descendant of the current :main to be safe. - if git merge-base --is-ancestor "${current_sha}" "${GITHUB_SHA}"; then - echo "Our commit is a descendant of :main — safe to advance." - echo "push_main=true" >> "$GITHUB_OUTPUT" - else - echo "Another run advanced :main past us (or diverged) — leaving it alone." - echo "push_main=false" >> "$GITHUB_OUTPUT" - fi - - # Retag the already-pushed SHA manifest as :main. This is a registry- - # side operation — no rebuild, no layer re-push — so it's quick and - # atomic per-tag. The ancestor check above plus the cancel-in-progress - # concurrency on this job together guarantee we only ever move :main - # forward in git history. - - name: Move :main to this SHA - if: steps.main_check.outputs.push_main == 'true' - run: | - set -euo pipefail - image=nousresearch/hermes-agent - docker buildx imagetools create \ - --tag "${image}:main" \ - "${image}:sha-${GITHUB_SHA}" - - # --------------------------------------------------------------------------- - # Move :latest to point at the release tag the merge job pushed. - # - # :latest is the floating tag that tracks the most recent stable release. - # Only `release: published` events advance it — never main pushes. - # - # We still run an ancestor check against the existing :latest so that a - # backport release on an older branch (e.g. patching v1.1.5 after v1.2.3 - # is out) doesn't drag :latest backwards. The check is the same shape as - # move-main: read the OCI revision label off the current :latest, look up - # that commit in git, and only advance if our release commit is a strict - # descendant. - # --------------------------------------------------------------------------- - move-latest: - if: | - github.repository == 'NousResearch/hermes-agent' - && github.event_name == 'release' - && needs.merge.outputs.pushed_release_tag == 'true' - needs: merge - runs-on: ubuntu-latest - timeout-minutes: 10 - concurrency: - group: docker-move-latest - cancel-in-progress: false - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 1000 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Decide whether to move :latest - id: latest_check - run: | - set -euo pipefail - image=nousresearch/hermes-agent - - image_json=$( - docker buildx imagetools inspect "${image}:latest" \ - --format '{{ json (index .Image "linux/amd64") }}' \ - 2>/dev/null || true - ) - - if [ -z "${image_json}" ]; then - echo "No existing :latest (or inspect failed) — safe to publish." - echo "push_latest=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - current_sha=$( - printf '%s' "${image_json}" \ - | jq -r '.config.Labels."org.opencontainers.image.revision" // ""' - ) - - if [ -z "${current_sha}" ]; then - echo "Registry :latest has no revision label — safe to publish." - echo "push_latest=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "Registry :latest is at ${current_sha}" - echo "This release is at ${GITHUB_SHA}" - - if [ "${current_sha}" = "${GITHUB_SHA}" ]; then - echo ":latest already points at our SHA — nothing to do." - echo "push_latest=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Make sure we have the :latest commit locally for merge-base. - # Releases can be cut from any branch, so fetch broadly. - if ! git cat-file -e "${current_sha}^{commit}" 2>/dev/null; then - git fetch --no-tags --prune origin \ - "+refs/heads/main:refs/remotes/origin/main" \ - || true - fi - - if ! git cat-file -e "${current_sha}^{commit}" 2>/dev/null; then - echo "Registry :latest points at an unknown commit (${current_sha}); refusing to overwrite." - echo "push_latest=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Our release SHA must be a descendant of the current :latest. - # Backport releases on older branches won't satisfy this and will - # be left alone — :latest stays on the newer release. - if git merge-base --is-ancestor "${current_sha}" "${GITHUB_SHA}"; then - echo "Our release commit is a descendant of :latest — safe to advance." - echo "push_latest=true" >> "$GITHUB_OUTPUT" - else - echo "Existing :latest is newer than this release (likely a backport) — leaving it alone." - echo "push_latest=false" >> "$GITHUB_OUTPUT" - fi - - # Retag the already-pushed release manifest as :latest. - - name: Move :latest to this release tag - if: steps.latest_check.outputs.push_latest == 'true' - env: - RELEASE_TAG: ${{ needs.merge.outputs.release_tag }} - run: | - set -euo pipefail - image=nousresearch/hermes-agent - docker buildx imagetools create \ - --tag "${image}:latest" \ - "${image}:${RELEASE_TAG}" diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/docs-site-checks.yml b/reference/hermes-agent-2026.5.16/.github/workflows/docs-site-checks.yml deleted file mode 100755 index 80fe9ea..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/docs-site-checks.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Docs Site Checks - -on: - pull_request: - paths: - - 'website/**' - - '.github/workflows/docs-site-checks.yml' - workflow_dispatch: - -permissions: - contents: read - -jobs: - docs-site-checks: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: 20 - cache: npm - cache-dependency-path: website/package-lock.json - - - name: Install website dependencies - run: npm ci - working-directory: website - - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: '3.11' - - - name: Install ascii-guard - run: python -m pip install ascii-guard==2.3.0 pyyaml==6.0.3 - - - name: Extract skill metadata for dashboard - run: python3 website/scripts/extract-skills.py - - - name: Regenerate per-skill docs pages + catalogs - run: python3 website/scripts/generate-skill-docs.py - - - name: Lint docs diagrams - run: npm run lint:diagrams - working-directory: website - - - name: Build Docusaurus - run: npm run build - working-directory: website diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/history-check.yml b/reference/hermes-agent-2026.5.16/.github/workflows/history-check.yml deleted file mode 100755 index bd66f19..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/history-check.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: History Check - -# Rejects PRs whose branch has no common ancestor with main. -# -# In May 2026 PR #25045 was merged from a branch that had been disconnected -# from main's history (likely an accidental `git checkout --orphan` or -# `.git/` re-init). GitHub's merge UI does not refuse merges of unrelated -# histories, so the PR landed cleanly with the intended one-file change — -# but its parent-less root commit (413990c94) got grafted into main as a -# second root, and ~1500 files' worth of `git blame` history collapsed -# onto that single commit. -# -# This check catches the failure mode by requiring `git merge-base` between -# the PR head and main to be non-empty. - -on: - pull_request: - branches: [main] - -permissions: - contents: read - -jobs: - check-common-ancestor: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 # full history both sides for merge-base - - - name: Reject PRs with no common ancestor on main - run: | - # `git merge-base` exits non-zero AND prints nothing when the two - # commits share no ancestor. We check both conditions explicitly - # so the failure message is clear regardless of which signal fires - # first. - if ! BASE=$(git merge-base origin/main HEAD 2>/dev/null) || [ -z "$BASE" ]; then - echo "" - echo "::error::This PR has no common ancestor with main." - echo "" - echo "Your branch's history is disconnected from main. Common causes:" - echo " - the branch was created with 'git checkout --orphan'" - echo " - '.git/' was re-initialized at some point during the work" - echo " - the branch was force-pushed from an unrelated repository" - echo "" - echo "Merging an unrelated-history PR grafts a parent-less root commit" - echo "into main and collapses git blame for every file in that snapshot." - echo "Reference: PR #25045 caused this and re-rooted blame on ~1500" - echo "files to a single orphan commit." - echo "" - echo "To fix, rebase your changes onto current main:" - echo " git fetch origin main" - echo " git checkout -b fix-branch origin/main" - echo " # re-apply your changes (cherry-pick, copy files, etc.)" - echo " git push -f origin fix-branch" - exit 1 - fi - echo "::notice::Common ancestor with main: $BASE" diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/lint.yml b/reference/hermes-agent-2026.5.16/.github/workflows/lint.yml deleted file mode 100755 index 807d5b6..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/lint.yml +++ /dev/null @@ -1,202 +0,0 @@ -name: Lint (ruff + ty) - -# Two things here: -# 1. Advisory diff — ruff + ty diagnostics as a diff vs the target branch. -# Posts a Markdown summary and a PR comment. Exit zero always. -# 2. Blocking ``ruff check .`` — enforces the explicit rules in -# ``[tool.ruff.lint.select]`` (currently PLW1514). Failure blocks merge. -# Separate job so the advisory diff still runs and posts even when -# enforcement fails. - -on: - push: - branches: [main] - paths-ignore: - - "**/*.md" - - "docs/**" - - "website/**" - pull_request: - branches: [main] - paths-ignore: - - "**/*.md" - - "docs/**" - - "website/**" - -permissions: - contents: read - pull-requests: write # needed to post/update PR comments - -concurrency: - group: lint-${{ github.ref }} - cancel-in-progress: true - -jobs: - lint-diff: - name: ruff + ty diff - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 # need full history for merge-base + worktree - - - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 - - - name: Install ruff + ty - run: | - uv tool install ruff - uv tool install ty - - - name: Determine base ref - id: base - run: | - # For PRs, diff against the merge base with the target branch. - # For pushes to main, diff against the previous commit on main. - if [ "${{ github.event_name }}" = "pull_request" ]; then - BASE_SHA=$(git merge-base "origin/${{ github.base_ref }}" HEAD) - BASE_REF="origin/${{ github.base_ref }}" - else - BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD) - BASE_REF="HEAD~1" - fi - echo "sha=${BASE_SHA}" >> "$GITHUB_OUTPUT" - echo "ref=${BASE_REF}" >> "$GITHUB_OUTPUT" - echo "Base SHA: ${BASE_SHA}" - echo "Base ref: ${BASE_REF}" - - - name: Run ruff + ty on HEAD - run: | - mkdir -p .lint-reports/head - ruff check --output-format json --exit-zero \ - > .lint-reports/head/ruff.json || true - ty check --output-format gitlab --exit-zero \ - > .lint-reports/head/ty.json || true - echo "HEAD ruff: $(wc -c < .lint-reports/head/ruff.json) bytes" - echo "HEAD ty: $(wc -c < .lint-reports/head/ty.json) bytes" - - - name: Run ruff + ty on base (via git worktree) - run: | - mkdir -p .lint-reports/base - # Use a worktree so we don't clobber the main checkout. If the basex - # SHA is identical to HEAD (e.g. first commit), skip and leave the - # base reports empty — the diff script handles missing files. - HEAD_SHA=$(git rev-parse HEAD) - BASE_SHA="${{ steps.base.outputs.sha }}" - if [ "$BASE_SHA" = "$HEAD_SHA" ]; then - echo "Base SHA == HEAD SHA, skipping base scan." - echo '[]' > .lint-reports/base/ruff.json - echo '[]' > .lint-reports/base/ty.json - else - git worktree add --detach /tmp/lint-base "$BASE_SHA" - ( - cd /tmp/lint-base - ruff check --output-format json --exit-zero \ - > "$GITHUB_WORKSPACE/.lint-reports/base/ruff.json" || true - ty check --output-format gitlab --exit-zero \ - > "$GITHUB_WORKSPACE/.lint-reports/base/ty.json" || true - ) - git worktree remove --force /tmp/lint-base - fi - echo "base ruff: $(wc -c < .lint-reports/base/ruff.json) bytes" - echo "base ty: $(wc -c < .lint-reports/base/ty.json) bytes" - - - name: Generate diff summary - run: | - python scripts/lint_diff.py \ - --base-ruff .lint-reports/base/ruff.json \ - --head-ruff .lint-reports/head/ruff.json \ - --base-ty .lint-reports/base/ty.json \ - --head-ty .lint-reports/head/ty.json \ - --base-ref "${{ steps.base.outputs.ref }}" \ - --head-ref "${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}" \ - --output .lint-reports/summary.md - cat .lint-reports/summary.md >> "$GITHUB_STEP_SUMMARY" - - - name: Upload reports as artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: lint-reports - path: .lint-reports/ - retention-days: 14 - - - name: Post / update PR comment - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - continue-on-error: true - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 - with: - script: | - const fs = require('fs'); - const body = fs.readFileSync('.lint-reports/summary.md', 'utf8'); - const marker = ''; - const fullBody = marker + '\n' + body; - - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - }); - const existing = comments.find(c => c.body && c.body.includes(marker)); - if (existing) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: existing.id, - body: fullBody, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: fullBody, - }); - } - - - ruff-blocking: - # Enforce the rules in pyproject.toml [tool.ruff.lint.select]. Currently - # PLW1514 (unspecified-encoding) — catches bare ``open()`` / - # ``read_text()`` / ``write_text()`` calls that default to locale - # encoding on Windows. Failure here blocks merge; the advisory - # ``lint-diff`` job above runs independently so reviewers still get - # the diff comment even when enforcement fails. - name: ruff enforcement (blocking) - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 - - - name: Install ruff - run: uv tool install ruff - - - name: ruff check . - # No --exit-zero, no || true. Exit code propagates to the job, - # which propagates to the required-check gate. - run: | - ruff check . - - windows-footguns: - # Static guardrails on Windows-unsafe Python primitives — os.kill(pid, 0), - # os.killpg, os.setsid, signal.SIGKILL without getattr fallback, - # shebang scripts via subprocess, bare open() without encoding=, etc. - # See scripts/check-windows-footguns.py for the full rule list. - name: Windows footguns (blocking) - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5 - with: - python-version: "3.11" - - - name: Run footgun checker - run: python scripts/check-windows-footguns.py --all diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/nix-lockfile-fix.yml b/reference/hermes-agent-2026.5.16/.github/workflows/nix-lockfile-fix.yml deleted file mode 100755 index b5e02c3..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/nix-lockfile-fix.yml +++ /dev/null @@ -1,254 +0,0 @@ -name: Nix Lockfile Fix - -on: - push: - branches: [main] - paths: - - 'ui-tui/package-lock.json' - - 'ui-tui/package.json' - - 'web/package-lock.json' - - 'web/package.json' - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to fix (leave empty to run on the selected branch)' - required: false - type: string - issue_comment: - types: [edited] - -permissions: - contents: write - pull-requests: write - -concurrency: - group: nix-lockfile-fix-${{ github.event.issue.number || github.event.inputs.pr_number || github.ref }} - cancel-in-progress: false - -jobs: - # ── Auto-fix on main ─────────────────────────────────────────────── - # Fires when a push to main touches package.json or package-lock.json - # in ui-tui/ or web/. Runs fix-lockfiles and pushes the hash - # update commit directly to main so Nix builds never stay broken. - # - # Safety invariants: - # 1. The fix commit only touches nix/*.nix files, which are NOT in - # the paths filter above, so this cannot re-trigger itself. - # 2. An explicit file-whitelist check before commit aborts if - # fix-lockfiles ever modifies unexpected files. - # 3. Job-level concurrency with cancel-in-progress: true ensures - # back-to-back pushes collapse to the newest; ref: main checkout - # always operates on the latest branch state. - # 4. Uses a GitHub App token (not GITHUB_TOKEN) so the fix commit - # triggers downstream nix.yml verification. - auto-fix-main: - if: github.event_name == 'push' - runs-on: ubuntu-latest - timeout-minutes: 25 - concurrency: - group: auto-fix-main - cancel-in-progress: true - steps: - - name: Generate GitHub App token - id: app-token - uses: actions/create-github-app-token@7bfa3a4717ef143a604ee0a99d859b8886a96d00 # v1.9.3 - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - ref: main - token: ${{ steps.app-token.outputs.token }} - - - uses: ./.github/actions/nix-setup - with: - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - - - name: Apply lockfile hashes - id: apply - run: nix run .#fix-lockfiles -- --apply - - - name: Commit & push - if: steps.apply.outputs.changed == 'true' - shell: bash - run: | - set -euo pipefail - - # Ensure only nix files were modified — prevents accidental - # self-triggering if fix-lockfiles ever touches package files. - unexpected="$(git diff --name-only | grep -Ev '^nix/(tui|web)\.nix$' || true)" - if [ -n "$unexpected" ]; then - echo "::error::Unexpected modified files: $unexpected" - exit 1 - fi - - # Record the base SHA before committing — used to detect package - # file changes if we need to rebase after a non-fast-forward push. - BASE_SHA="$(git rev-parse HEAD)" - - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add nix/tui.nix nix/web.nix - git commit -m "fix(nix): auto-refresh npm lockfile hashes" \ - -m "Source: $GITHUB_SHA" \ - -m "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" - - # Retry push with rebase in case main advanced with an unrelated - # commit during the nix build. Without this, a non-fast-forward - # rejection silently loses the fix. If package files changed during - # the rebase, abort — a fresh auto-fix run will handle the new state. - for attempt in 1 2 3; do - if git push origin HEAD:main; then - exit 0 - fi - echo "::warning::Push attempt $attempt failed (non-fast-forward?), rebasing…" - git fetch origin main - - # If package files changed between our base and the new main, - # our computed hashes are stale. Abort and let the next triggered - # run recompute from the correct package-lock state. - pkg_changed="$(git diff --name-only "$BASE_SHA"..origin/main -- \ - 'ui-tui/package-lock.json' 'ui-tui/package.json' \ - 'web/package-lock.json' 'web/package.json' || true)" - if [ -n "$pkg_changed" ]; then - echo "::warning::Package files changed since hash computation — aborting; a fresh run will recompute" - exit 0 - fi - - git rebase origin/main - done - echo "::error::Failed to push after 3 rebase attempts" - exit 1 - - # ── PR fix (manual / checkbox) ───────────────────────────────────── - # Existing behavior: run on manual dispatch OR when a task-list - # checkbox in the sticky lockfile-check comment flips from [ ] to [x]. - fix: - if: | - github.event_name == 'workflow_dispatch' || - (github.event_name == 'issue_comment' - && github.event.issue.pull_request != null - && contains(github.event.comment.body, '[x] **Apply lockfile fix**') - && !contains(github.event.changes.body.from, '[x] **Apply lockfile fix**')) - runs-on: ubuntu-latest - timeout-minutes: 25 - steps: - - name: Authorize & resolve PR - id: resolve - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - script: | - // 1. Verify the actor has write access — applies to both checkbox - // clicks and manual dispatch. - const { data: perm } = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: context.actor, - }); - if (!['admin', 'write', 'maintain'].includes(perm.permission)) { - core.setFailed( - `${context.actor} lacks write access (has: ${perm.permission})` - ); - return; - } - - // 2. Resolve which ref to check out. - let prNumber = ''; - if (context.eventName === 'issue_comment') { - prNumber = String(context.payload.issue.number); - } else if (context.eventName === 'workflow_dispatch') { - prNumber = context.payload.inputs.pr_number || ''; - } - - if (!prNumber) { - core.setOutput('ref', context.ref.replace(/^refs\/heads\//, '')); - core.setOutput('repo', context.repo.repo); - core.setOutput('owner', context.repo.owner); - core.setOutput('pr', ''); - return; - } - - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: Number(prNumber), - }); - core.setOutput('ref', pr.head.ref); - core.setOutput('repo', pr.head.repo.name); - core.setOutput('owner', pr.head.repo.owner.login); - core.setOutput('pr', String(pr.number)); - - # Wipe the sticky lockfile-check comment to a "running" state as soon - # as the job is authorized, so the user sees their click was picked up - # before the ~minute of nix build work. - - name: Mark sticky as running - if: steps.resolve.outputs.pr != '' - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1 - with: - header: nix-lockfile-check - number: ${{ steps.resolve.outputs.pr }} - message: | - ### 🔄 Applying lockfile fix… - - Triggered by @${{ github.actor }} — [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). - - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - repository: ${{ steps.resolve.outputs.owner }}/${{ steps.resolve.outputs.repo }} - ref: ${{ steps.resolve.outputs.ref }} - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - uses: ./.github/actions/nix-setup - with: - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - - - name: Apply lockfile hashes - id: apply - run: nix run .#fix-lockfiles - - - name: Commit & push - if: steps.apply.outputs.changed == 'true' - shell: bash - run: | - set -euo pipefail - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add nix/tui.nix nix/web.nix - git commit -m "fix(nix): refresh npm lockfile hashes" - git push - - - name: Update sticky (applied) - if: steps.apply.outputs.changed == 'true' && steps.resolve.outputs.pr != '' - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1 - with: - header: nix-lockfile-check - number: ${{ steps.resolve.outputs.pr }} - message: | - ### ✅ Lockfile fix applied - - Pushed a commit refreshing the npm lockfile hashes — [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). - - - name: Update sticky (already current) - if: steps.apply.outputs.changed == 'false' && steps.resolve.outputs.pr != '' - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1 - with: - header: nix-lockfile-check - number: ${{ steps.resolve.outputs.pr }} - message: | - ### ✅ Lockfile hashes already current - - Nothing to commit — [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). - - - name: Update sticky (failed) - if: failure() && steps.resolve.outputs.pr != '' - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1 - with: - header: nix-lockfile-check - number: ${{ steps.resolve.outputs.pr }} - message: | - ### ❌ Lockfile fix failed - - See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for logs. diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/nix.yml b/reference/hermes-agent-2026.5.16/.github/workflows/nix.yml deleted file mode 100755 index 9a8f45a..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/nix.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Nix - -on: - push: - branches: [main] - pull_request: - -permissions: - contents: read - pull-requests: write - -concurrency: - group: nix-${{ github.ref }} - cancel-in-progress: true - -jobs: - nix: - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - timeout-minutes: 30 - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: ./.github/actions/nix-setup - with: - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - - - name: Resolve head SHA - if: github.event_name == 'pull_request' - id: sha - shell: bash - run: | - FULL="${{ github.event.pull_request.head.sha || github.sha }}" - echo "full=$FULL" >> "$GITHUB_OUTPUT" - echo "short=${FULL:0:7}" >> "$GITHUB_OUTPUT" - - - name: Check flake - id: flake - if: runner.os == 'Linux' - continue-on-error: true - run: nix flake check --print-build-logs - - - name: Build package - id: build - if: runner.os == 'Linux' - continue-on-error: true - run: nix build --print-build-logs - - # When the real Nix build fails, run a targeted diagnostic to see if - # the failure is specifically a stale npm lockfile hash in one of the - # known npm subpackages (tui / web). This avoids surfacing a generic - # "build failed" message when the fix is a single known command. - - name: Diagnose npm lockfile hashes - id: hash_check - if: (steps.flake.outcome == 'failure' || steps.build.outcome == 'failure') && runner.os == 'Linux' - continue-on-error: true - env: - LINK_SHA: ${{ steps.sha.outputs.full }} - run: nix run .#fix-lockfiles -- --check - - # If fix-lockfiles itself crashes (infrastructure blip, cache throttle, - # etc.) it won't set stale=true/false. Treat that as a distinct failure - # mode rather than silently ignoring it. - - name: Fail if hash check crashed without reporting - if: steps.hash_check.outcome == 'failure' && steps.hash_check.outputs.stale != 'true' && steps.hash_check.outputs.stale != 'false' - run: | - echo "::error::fix-lockfiles exited without reporting stale status — likely an infrastructure or script failure" - exit 1 - - - name: Post sticky PR comment (stale hashes) - if: steps.hash_check.outputs.stale == 'true' && github.event_name == 'pull_request' - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1 - with: - header: nix-lockfile-check - message: | - ### ⚠️ npm lockfile hash out of date - - Checked against commit [`${{ steps.sha.outputs.short }}`](${{ github.server_url }}/${{ github.repository }}/commit/${{ steps.sha.outputs.full }}) (PR head at check time). - - The `hash = "sha256-..."` line in these nix files no longer matches the committed `package-lock.json`: - - ${{ steps.hash_check.outputs.report }} - - #### Apply the fix - - - [ ] **Apply lockfile fix** — tick to push a commit with the correct hashes to this PR branch - - Or [run the Nix Lockfile Fix workflow](${{ github.server_url }}/${{ github.repository }}/actions/workflows/nix-lockfile-fix.yml) manually (pass PR `#${{ github.event.pull_request.number }}`) - - Or locally: `nix run .#fix-lockfiles` and commit the diff - - # Clear the sticky comment when either the build passed outright (no - # hash check needed) or the hash check explicitly returned stale=false - # (build failed for a non-hash reason). - - name: Clear sticky PR comment (resolved) - if: | - github.event_name == 'pull_request' && - runner.os == 'Linux' && - (steps.hash_check.outputs.stale == 'false' || - (steps.flake.outcome == 'success' && steps.build.outcome == 'success')) - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.1 - with: - header: nix-lockfile-check - delete: true - - - name: Final fail if build or flake failed - if: steps.flake.outcome == 'failure' || steps.build.outcome == 'failure' - run: | - if [ "${{ steps.hash_check.outputs.stale }}" == "true" ]; then - echo "::error::Nix build failed due to stale npm lockfile hash. Run: nix run .#fix-lockfiles" - else - echo "::error::Nix build/flake check failed. See logs above." - fi - exit 1 - - - name: Evaluate flake (macOS) - if: runner.os == 'macOS' - run: nix flake show --json > /dev/null diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/osv-scanner.yml b/reference/hermes-agent-2026.5.16/.github/workflows/osv-scanner.yml deleted file mode 100755 index db8c3d7..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/osv-scanner.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: OSV-Scanner - -# Scans lockfiles (uv.lock, package-lock.json) against the OSV vulnerability -# database. Runs on every PR that touches a lockfile and on a weekly schedule -# against main. -# -# This is detection-only — OSV-Scanner does NOT open PRs or modify pins. -# It reports known CVEs in currently-pinned dependency versions so we can -# decide when and how to patch on our own schedule. Our pinning strategy -# (full SHA / exact version) is preserved; only the notification signal -# is added. -# -# Complements the existing supply-chain-audit.yml workflow (which scans -# for malicious code patterns in PR diffs) by covering the orthogonal -# "currently-pinned dep became known-vulnerable" case. -# -# Uses Google's officially-recommended reusable workflow, pinned by SHA. -# Findings land in the repo's Security tab (Code Scanning > OSV-Scanner). -# fail-on-vuln is disabled so the job does not block merges on pre-existing -# vulnerabilities in pinned deps that we may need to patch deliberately. - -on: - pull_request: - branches: [main] - paths: - - 'uv.lock' - - 'pyproject.toml' - - 'package.json' - - 'package-lock.json' - - 'ui-tui/package.json' - - 'ui-tui/package-lock.json' - - 'website/package.json' - - 'website/package-lock.json' - - '.github/workflows/osv-scanner.yml' - push: - branches: [main] - paths: - - 'uv.lock' - - 'pyproject.toml' - - 'package.json' - - 'package-lock.json' - - 'ui-tui/package-lock.json' - - 'website/package-lock.json' - schedule: - # Weekly scan against main — catches CVEs published after merge for - # deps that haven't changed since. - - cron: '0 9 * * 1' - workflow_dispatch: - -permissions: - # Required by the reusable workflow to upload SARIF to the Security tab. - actions: read - contents: read - security-events: write - -jobs: - scan: - name: Scan lockfiles - uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@c51854704019a247608d928f370c98740469d4b5 # v2.3.5 - with: - # Scan explicit lockfiles rather than recursing, so we only look at - # the three sources of truth and skip vendored / test / worktree dirs. - scan-args: |- - --lockfile=uv.lock - --lockfile=ui-tui/package-lock.json - --lockfile=website/package-lock.json - fail-on-vuln: false diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/skills-index.yml b/reference/hermes-agent-2026.5.16/.github/workflows/skills-index.yml deleted file mode 100755 index 8beda19..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/skills-index.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Build Skills Index - -on: - schedule: - # Run twice daily: 6 AM and 6 PM UTC - - cron: '0 6,18 * * *' - workflow_dispatch: # Manual trigger - push: - branches: [main] - paths: - - 'scripts/build_skills_index.py' - - '.github/workflows/skills-index.yml' - -permissions: - contents: read - -jobs: - build-index: - # Only run on the upstream repository, not on forks - if: github.repository == 'NousResearch/hermes-agent' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: '3.11' - - - name: Install dependencies - run: pip install httpx==0.28.1 pyyaml==6.0.2 - - - name: Build skills index - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: python scripts/build_skills_index.py - - - name: Upload index artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: skills-index - path: website/static/api/skills-index.json - retention-days: 7 - - deploy-with-index: - needs: build-index - runs-on: ubuntu-latest - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deploy.outputs.page_url }} - # Only deploy on schedule or manual trigger (not on every push to the script) - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: skills-index - path: website/static/api/ - - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: 20 - cache: npm - cache-dependency-path: website/package-lock.json - - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: '3.11' - - - name: Install PyYAML for skill extraction - run: pip install pyyaml==6.0.2 - - - name: Extract skill metadata for dashboard - run: python3 website/scripts/extract-skills.py - - - name: Install dependencies - run: npm ci - working-directory: website - - - name: Build Docusaurus - run: npm run build - working-directory: website - - - name: Stage deployment - run: | - mkdir -p _site/docs - cp -r landingpage/* _site/ - cp -r website/build/* _site/docs/ - echo "hermes-agent.nousresearch.com" > _site/CNAME - - - name: Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 - with: - path: _site - - - name: Deploy to GitHub Pages - id: deploy - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/supply-chain-audit.yml b/reference/hermes-agent-2026.5.16/.github/workflows/supply-chain-audit.yml deleted file mode 100755 index 69a9a11..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/supply-chain-audit.yml +++ /dev/null @@ -1,205 +0,0 @@ -name: Supply Chain Audit - -on: - pull_request: - types: [opened, synchronize, reopened] - paths: - - '**/*.py' - - '**/*.pth' - - '**/setup.py' - - '**/setup.cfg' - - '**/sitecustomize.py' - - '**/usercustomize.py' - - '**/__init__.pth' - - 'pyproject.toml' - -permissions: - pull-requests: write - contents: read - -# Narrow, high-signal scanner. Only fires on critical indicators of supply -# chain attacks (e.g. the litellm-style payloads). Low-signal heuristics -# (plain base64, plain exec/eval, dependency/Dockerfile/workflow edits, -# Actions version unpinning, outbound POST/PUT) were intentionally -# removed — they fired on nearly every PR and trained reviewers to ignore -# the scanner. Keep this file's checks ruthlessly narrow: if you find -# yourself adding WARNING-tier patterns here again, make a separate -# advisory-only workflow instead. - -jobs: - scan: - name: Scan PR for critical supply chain risks - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Scan diff for critical patterns - id: scan - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - BASE="${{ github.event.pull_request.base.sha }}" - HEAD="${{ github.event.pull_request.head.sha }}" - - # Added lines only, excluding lockfiles. - DIFF=$(git diff "$BASE".."$HEAD" -- . ':!uv.lock' ':!*.lock' ':!package-lock.json' ':!yarn.lock' || true) - - FINDINGS="" - - # --- .pth files (auto-execute on Python startup) --- - # The exact mechanism used in the litellm supply chain attack: - # https://github.com/BerriAI/litellm/issues/24512 - PTH_FILES=$(git diff --name-only "$BASE".."$HEAD" | grep '\.pth$' || true) - if [ -n "$PTH_FILES" ]; then - FINDINGS="${FINDINGS} - ### 🚨 CRITICAL: .pth file added or modified - Python \`.pth\` files in \`site-packages/\` execute automatically when the interpreter starts — no import required. - - **Files:** - \`\`\` - ${PTH_FILES} - \`\`\` - " - fi - - # --- base64 decode + exec/eval on the same line (the litellm attack pattern) --- - B64_EXEC_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -iE 'base64\.(b64decode|decodebytes|urlsafe_b64decode)' | grep -iE 'exec\(|eval\(' | head -10 || true) - if [ -n "$B64_EXEC_HITS" ]; then - FINDINGS="${FINDINGS} - ### 🚨 CRITICAL: base64 decode + exec/eval combo - Base64-decoded strings passed directly to exec/eval — the signature of hidden credential-stealing payloads. - - **Matches:** - \`\`\` - ${B64_EXEC_HITS} - \`\`\` - " - fi - - # --- subprocess with encoded/obfuscated command argument --- - PROC_HITS=$(echo "$DIFF" | grep -n '^\+' | grep -E 'subprocess\.(Popen|call|run)\s*\(' | grep -iE 'base64|\\x[0-9a-f]{2}|chr\(' | head -10 || true) - if [ -n "$PROC_HITS" ]; then - FINDINGS="${FINDINGS} - ### 🚨 CRITICAL: subprocess with encoded/obfuscated command - Subprocess calls whose command strings are base64- or hex-encoded are a strong indicator of payload execution. - - **Matches:** - \`\`\` - ${PROC_HITS} - \`\`\` - " - fi - - # --- Install-hook files (setup.py/sitecustomize/usercustomize/__init__.pth) --- - # These execute during pip install or interpreter startup. - SETUP_HITS=$(git diff --name-only "$BASE".."$HEAD" | grep -E '(^|/)(setup\.py|setup\.cfg|sitecustomize\.py|usercustomize\.py|__init__\.pth)$' || true) - if [ -n "$SETUP_HITS" ]; then - FINDINGS="${FINDINGS} - ### 🚨 CRITICAL: Install-hook file added or modified - These files can execute code during package installation or interpreter startup. - - **Files:** - \`\`\` - ${SETUP_HITS} - \`\`\` - " - fi - - if [ -n "$FINDINGS" ]; then - echo "found=true" >> "$GITHUB_OUTPUT" - echo "$FINDINGS" > /tmp/findings.md - else - echo "found=false" >> "$GITHUB_OUTPUT" - fi - - - name: Post critical finding comment - if: steps.scan.outputs.found == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - BODY="## 🚨 CRITICAL Supply Chain Risk Detected - - This PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. - - $(cat /tmp/findings.md) - - --- - *Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting.*" - - gh pr comment "${{ github.event.pull_request.number }}" --body "$BODY" || echo "::warning::Could not post PR comment (expected for fork PRs — GITHUB_TOKEN is read-only)" - - - name: Fail on critical findings - if: steps.scan.outputs.found == 'true' - run: | - echo "::error::CRITICAL supply chain risk patterns detected in this PR. See the PR comment for details." - exit 1 - - dep-bounds: - name: Check PyPI dependency upper bounds - runs-on: ubuntu-latest - if: contains(github.event.pull_request.changed_files_url, 'pyproject.toml') || true - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Check for unbounded PyPI deps - id: bounds - run: | - set -euo pipefail - - BASE="${{ github.event.pull_request.base.sha }}" - HEAD="${{ github.event.pull_request.head.sha }}" - - # Only check added lines in pyproject.toml - ADDED=$(git diff "$BASE".."$HEAD" -- pyproject.toml | grep '^+' | grep -v '^+++' || true) - - if [ -z "$ADDED" ]; then - echo "found=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Match PyPI dep specs that have >= but no < ceiling. - # Pattern: "package>=version" without a following ",<" bound. - # Excludes git+ URLs (which use commit SHAs) and comments. - UNBOUNDED=$(echo "$ADDED" | grep -oE '"[a-zA-Z0-9_-]+(\[[^\]]*\])?>=[ 0-9.]+"' | grep -v ',<' || true) - - if [ -n "$UNBOUNDED" ]; then - echo "found=true" >> "$GITHUB_OUTPUT" - echo "$UNBOUNDED" > /tmp/unbounded.txt - else - echo "found=false" >> "$GITHUB_OUTPUT" - fi - - - name: Post unbounded dep warning - if: steps.bounds.outputs.found == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - BODY="## ⚠️ Unbounded PyPI Dependency Detected - - This PR adds PyPI dependencies without a \`=floor,=1.2.0,<2\"\` - - --- - *See PR #2810 and CONTRIBUTING.md for the full policy rationale.*" - - gh pr comment "${{ github.event.pull_request.number }}" --body "$BODY" || echo "::warning::Could not post PR comment (expected for fork PRs)" - - - name: Fail on unbounded deps - if: steps.bounds.outputs.found == 'true' - run: | - echo "::error::PyPI dependencies without upper bounds detected. Add /dev/null 2>&1; then - echo "Release $GITHUB_REF_NAME found" - exit 0 - fi - echo "Waiting for release... ($i/30)" - sleep 10 - done - echo "::warning::Release $GITHUB_REF_NAME not found after 5 minutes — skipping signature upload" - echo "skip_sign=true" >> "$GITHUB_ENV" - - - name: Sign with Sigstore - if: env.skip_sign != 'true' - uses: sigstore/gh-action-sigstore-python@f514d46b907ebcd5bedc05145c03b69c1edd8b46 # v3.0.0 - with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - - name: Attach signed artifacts to GitHub Release - if: env.skip_sign != 'true' - env: - GITHUB_TOKEN: ${{ github.token }} - # release.py already created the GitHub Release — just upload - # the Sigstore signatures alongside the existing assets. - run: >- - gh release upload - "$GITHUB_REF_NAME" dist/*.sigstore.json - --repo "$GITHUB_REPOSITORY" - --clobber diff --git a/reference/hermes-agent-2026.5.16/.github/workflows/uv-lockfile-check.yml b/reference/hermes-agent-2026.5.16/.github/workflows/uv-lockfile-check.yml deleted file mode 100755 index 190a162..0000000 --- a/reference/hermes-agent-2026.5.16/.github/workflows/uv-lockfile-check.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: uv.lock check - -# Verify uv.lock is in sync with pyproject.toml. Blocking check — PRs -# that modify pyproject.toml without regenerating uv.lock (or vice versa) -# must not merge, because the Docker build's `uv sync --frozen` step will -# fail on a stale lockfile and we'd rather catch it here than in the -# docker-publish workflow on main. -# -# ───────────────────────────────────────────────────────────────────────── -# IMPORTANT: this check runs against the MERGED state, not just your branch -# ───────────────────────────────────────────────────────────────────────── -# -# For `pull_request` events, GitHub checks out `refs/pull//merge` by -# default — a synthetic commit that merges your PR branch into the CURRENT -# state of `main`. That means the pyproject.toml evaluated here is -# `main's pyproject.toml + your PR's changes to pyproject.toml`, not just -# what's on your branch. -# -# Failure mode this creates: if `main` has advanced since you branched -# (e.g. someone merged a PR that added a dep to pyproject.toml + its -# corresponding uv.lock entries), your branch's uv.lock is missing those -# new entries. `uv lock --check` resolves against the merged pyproject -# and sees a lockfile that doesn't cover all the current deps → fails -# with "The lockfile at uv.lock needs to be updated." -# -# This can be confusing: `uv lock --check` passes locally (your branch -# is internally consistent) but fails in CI (merged state isn't). -# -# Fix is to sync your branch with main and regenerate the lockfile: -# -# git fetch origin main -# git rebase origin/main # or merge, whatever the repo prefers -# uv lock # regenerates uv.lock against new pyproject.toml -# git add uv.lock -# git commit -m "chore: refresh uv.lock after rebase onto main" -# git push --force-with-lease # if you rebased -# -# If you also changed pyproject.toml in your PR, `uv lock` handles that -# at the same time — one regeneration covers both your changes and the -# drift from main. -# -# This is the correct behavior! The check is protecting main's Docker -# build: a post-merge build would see the same merged state and fail -# the same way. Better to catch it here than after merge. - -on: - push: - branches: [main] - paths: - - 'pyproject.toml' - - 'uv.lock' - - '.github/workflows/uv-lockfile-check.yml' - pull_request: - branches: [main] - paths: - - 'pyproject.toml' - - 'uv.lock' - - '.github/workflows/uv-lockfile-check.yml' - -permissions: - contents: read - -concurrency: - group: uv-lockfile-check-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - check: - name: uv lock --check - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 - - # `uv lock --check` re-resolves the project from pyproject.toml and - # compares the result to uv.lock, exiting non-zero if they disagree. - # No network writes, no file modifications. - # - # On PRs this runs against the merge commit (see comment at the top - # of this file) — failures often mean "your branch is behind main, - # rebase and regenerate uv.lock." - - name: Verify uv.lock is up-to-date - run: | - if ! uv lock --check; then - cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" - ## ❌ uv.lock is out of sync with pyproject.toml - - **If this is a PR:** this check runs against the merged state - (your branch + current `main`), not just your branch. If - `uv lock --check` passes locally, your branch is likely behind - `main` — recent changes to `pyproject.toml` on `main` aren't - reflected in your branch's `uv.lock` yet. - - To fix, sync with main and regenerate the lockfile: - - ```bash - git fetch origin main - git rebase origin/main # or `git merge origin/main` - uv lock # regenerate against new pyproject.toml - git add uv.lock - git commit -m "chore: refresh uv.lock after syncing with main" - git push --force-with-lease # drop --force-with-lease if you merged - ``` - - **If you only changed pyproject.toml:** run `uv lock` locally - and commit the result. - - This check is blocking because the Docker image build uses - `uv sync --frozen --extra all`, which rejects stale lockfiles - — catching it here avoids a ~15 min failed docker-publish run - on `main` post-merge. - EOF - echo "::error title=uv.lock out of sync::Run \`uv lock\` locally and commit the result. If on a PR, sync with main first." - exit 1 - fi diff --git a/reference/hermes-agent-2026.5.16/.gitignore b/reference/hermes-agent-2026.5.16/.gitignore deleted file mode 100755 index 37b1f60..0000000 --- a/reference/hermes-agent-2026.5.16/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -.DS_Store -/venv/ -/_pycache/ -*.pyc* -__pycache__/ -.venv/ -.vscode/ -.env -.env.local -.env.development.local -.env.test.local -.env.production.local -.env.development -.env.test -export* -__pycache__/model_tools.cpython-310.pyc -__pycache__/web_tools.cpython-310.pyc -logs/ -data/ -.pytest_cache/ -tmp/ -temp_vision_images/ -hermes-*/* -examples/ -tests/quick_test_dataset.jsonl -tests/sample_dataset.jsonl -run_datagen_kimik2-thinking.sh -run_datagen_megascience_glm4-6.sh -run_datagen_sonnet.sh -source-data/* -run_datagen_megascience_glm4-6.sh -data/* -node_modules/ -browser-use/ -agent-browser/ -# Private keys -*.ppk -*.pem -privvy* -images/ -__pycache__/ -hermes_agent.egg-info/ -wandb/ -testlogs - -# CLI config (may contain sensitive SSH paths) -cli-config.yaml - -# Skills Hub state (lives in ~/.hermes/skills/.hub/ at runtime, but just in case) -skills/.hub/ -ignored/ -.worktrees/ -environments/benchmarks/evals/ - -# Web UI build output -hermes_cli/web_dist/ - -# Web UI assets — synced from @nous-research/ui at build time via -# `npm run sync-assets` (see web/package.json). -web/public/fonts/ -web/public/ds-assets/ - -# Release script temp files -.release_notes.md -mini-swe-agent/ - -# Nix -.direnv/ -.nix-stamps/ -result -website/static/api/skills-index.json -models-dev-upstream/ -hermes_cli/tui_dist/* -hermes_cli/scripts/ -docs/superpowers/* \ No newline at end of file diff --git a/reference/hermes-agent-2026.5.16/.gitkeep b/reference/hermes-agent-2026.5.16/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/reference/hermes-agent-2026.5.16/.mailmap b/reference/hermes-agent-2026.5.16/.mailmap deleted file mode 100755 index 3f093fb..0000000 --- a/reference/hermes-agent-2026.5.16/.mailmap +++ /dev/null @@ -1,108 +0,0 @@ -# .mailmap — canonical author mapping for git shortlog / git log / GitHub -# Format: Canonical Name -# See: https://git-scm.com/docs/gitmailmap -# -# This maps commit emails to GitHub noreply addresses so that: -# 1. `git shortlog -sn` shows deduplicated contributor counts -# 2. GitHub's contributor graph can attribute commits correctly -# 3. Contributors with personal/work emails get proper credit -# -# When adding entries: use the contributor's GitHub noreply email as canonical -# so GitHub can link commits to their profile. - -# === Teknium (multiple emails) === -Teknium <127238744+teknium1@users.noreply.github.com> -Teknium <127238744+teknium1@users.noreply.github.com> - -# === Contributors — personal/work emails mapped to GitHub noreply === -# Format: Canonical Name - -# Verified via GH API email search -luyao618 <364939526@qq.com> <364939526@qq.com> -ethernet8023 -nicoloboschi -cherifya -BongSuCHOI -dsocolobsky -pefontana -Helmi -hata1234 - -# Verified via PR investigation / salvage PR bodies -DeployFaith -flobo3 -gaixianggeng -KUSH42 -konsisumer -WorldInnovationsDepartment -m0n5t3r -sprmn24 -fancydirty -fxfitz -limars874 -AaronWong1999 -dippwho -duerzy -geoffwellman -hcshen0111 -jamesarch -stephenschoettler -Tranquil-Flow -Dusk1e -Awsh1 -WAXLYY -donrhmexe -hqhq1025 <1506751656@qq.com> <1506751656@qq.com> -BlackishGreen33 -tomqiaozc -MagicRay1217 -aaronagent <1115117931@qq.com> <1115117931@qq.com> -YoungYang963 -LongOddCode -Cafexss -Cygra -DomGrieco - -# Duplicate email mapping (same person, multiple emails) -Sertug17 <104278804+Sertug17@users.noreply.github.com> -yyovil -DomGrieco -dsocolobsky -olafthiele - -# Verified via git display name matching GH contributor username -cokemine -dalianmao000 -emozilla -jjovalle99 -kagura-agent -spniyant -olafthiele -r266-tech -xingkongliang -win4r -zhouboli -yongtenglei - -# Nous Research team -benbarclay -jquesnelle - -# GH contributor list verified -spideystreet -dorukardahan -MustafaKara7 -Hmbown -kamil-gwozdz -kira-ariaki -knopki -Unayung -SeeYangZhi -Julientalbot -lesterli -JiayuuWang -tesseracttars-creator -xinbenlv -SaulJWu -angelos -MestreY0d4-Uninter <241404605+MestreY0d4-Uninter@users.noreply.github.com> diff --git a/reference/hermes-agent-2026.5.16/.plans/openai-api-server.md b/reference/hermes-agent-2026.5.16/.plans/openai-api-server.md deleted file mode 100755 index 59038cb..0000000 --- a/reference/hermes-agent-2026.5.16/.plans/openai-api-server.md +++ /dev/null @@ -1,291 +0,0 @@ -# OpenAI-Compatible API Server for Hermes Agent - -## Motivation - -Every major chat frontend (Open WebUI 126k★, LobeChat 73k★, LibreChat 34k★, -AnythingLLM 56k★, NextChat 87k★, ChatBox 39k★, Jan 26k★, HF Chat-UI 8k★, -big-AGI 7k★) connects to backends via the OpenAI-compatible REST API with -SSE streaming. By exposing this endpoint, hermes-agent becomes instantly -usable as a backend for all of them — no custom adapters needed. - -## What It Enables - -``` -┌──────────────────┐ -│ Open WebUI │──┐ -│ LobeChat │ │ POST /v1/chat/completions -│ LibreChat │ ├──► Authorization: Bearer ┌─────────────────┐ -│ AnythingLLM │ │ {"messages": [...]} │ hermes-agent │ -│ NextChat │ │ │ gateway │ -│ Any OAI client │──┘ ◄── SSE streaming response │ (API server) │ -└──────────────────┘ └─────────────────┘ -``` - -A user would: -1. Set `API_SERVER_ENABLED=true` in `~/.hermes/.env` -2. Run `hermes gateway` (API server starts alongside Telegram/Discord/etc.) -3. Point Open WebUI (or any frontend) at `http://localhost:8642/v1` -4. Chat with hermes-agent through any OpenAI-compatible UI - -## Endpoints - -| Method | Path | Purpose | -|--------|------|---------| -| POST | `/v1/chat/completions` | Chat with the agent (streaming + non-streaming) | -| GET | `/v1/models` | List available "models" (returns hermes-agent as a model) | -| GET | `/health` | Health check | - -## Architecture - -### Option A: Gateway Platform Adapter (recommended) - -Create `gateway/platforms/api_server.py` as a new platform adapter that -extends `BasePlatformAdapter`. This is the cleanest approach because: - -- Reuses all gateway infrastructure (session management, auth, context building) -- Runs in the same async loop as other adapters -- Gets message handling, interrupt support, and session persistence for free -- Follows the established pattern (like Telegram, Discord, etc.) -- Uses `aiohttp.web` (already a dependency) for the HTTP server - -The adapter would start an `aiohttp.web.Application` server in `connect()` -and route incoming HTTP requests through the standard `handle_message()` pipeline. - -### Option B: Standalone Component - -A separate HTTP server class in `gateway/api_server.py` that creates its own -AIAgent instances directly. Simpler but duplicates session/auth logic. - -**Recommendation: Option A** — fits the existing architecture, less code to -maintain, gets all gateway features for free. - -## Request/Response Format - -### Chat Completions (non-streaming) - -``` -POST /v1/chat/completions -Authorization: Bearer hermes-api-key-here -Content-Type: application/json - -{ - "model": "hermes-agent", - "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What files are in the current directory?"} - ], - "stream": false, - "temperature": 0.7 -} -``` - -Response: -```json -{ - "id": "chatcmpl-abc123", - "object": "chat.completion", - "created": 1710000000, - "model": "hermes-agent", - "choices": [{ - "index": 0, - "message": { - "role": "assistant", - "content": "Here are the files in the current directory:\n..." - }, - "finish_reason": "stop" - }], - "usage": { - "prompt_tokens": 50, - "completion_tokens": 200, - "total_tokens": 250 - } -} -``` - -### Chat Completions (streaming) - -Same request with `"stream": true`. Response is SSE: - -``` -data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]} - -data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Here "},"finish_reason":null}]} - -data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"are "},"finish_reason":null}]} - -data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} - -data: [DONE] -``` - -### Models List - -``` -GET /v1/models -Authorization: Bearer hermes-api-key-here -``` - -Response: -```json -{ - "object": "list", - "data": [{ - "id": "hermes-agent", - "object": "model", - "created": 1710000000, - "owned_by": "hermes-agent" - }] -} -``` - -## Key Design Decisions - -### 1. Session Management - -The OpenAI API is stateless — each request includes the full conversation. -But hermes-agent sessions have persistent state (memory, skills, tool context). - -**Approach: Hybrid** -- Default: Stateless. Each request is independent. The `messages` array IS - the conversation. No session persistence between requests. -- Opt-in persistent sessions via `X-Session-ID` header. When provided, the - server maintains session state across requests (conversation history, - memory context, tool state). This enables richer agent behavior. -- The session ID also enables interrupt support — a subsequent request with - the same session ID while one is running triggers an interrupt. - -### 2. Streaming - -The agent's `run_conversation()` is synchronous and returns the full response. -For real SSE streaming, we need to emit chunks as they're generated. - -**Phase 1 (MVP):** Run agent in a thread, return the complete response as -a single SSE chunk + `[DONE]`. This works with all frontends — they just see -a fast single-chunk response. Not true streaming but functional. - -**Phase 2:** Add a response callback to AIAgent that emits text chunks as the -LLM generates them. The API server captures these via a queue and streams them -as SSE events. This gives real token-by-token streaming. - -**Phase 3:** Stream tool execution progress too — emit tool call/result events -as the agent works, giving frontends visibility into what the agent is doing. - -### 3. Tool Transparency - -Two modes: -- **Opaque (default):** Frontends see only the final response. Tool calls - happen server-side and are invisible. Best for general-purpose UIs. -- **Transparent (opt-in via header):** Tool calls are emitted as OpenAI-format - tool_call/tool_result messages in the stream. Useful for agent-aware frontends. - -### 4. Authentication - -- Bearer token via `Authorization: Bearer ` header -- Token configured via `API_SERVER_KEY` env var -- Optional: allow unauthenticated local-only access (127.0.0.1 bind) -- Follows the same pattern as other platform adapters - -### 5. Model Mapping - -Frontends send `"model": "hermes-agent"` (or whatever). The actual LLM model -used is configured server-side in config.yaml. The API server maps any -requested model name to the configured hermes-agent model. - -Optionally, allow model passthrough: if the frontend sends -`"model": "anthropic/claude-sonnet-4"`, the agent uses that model. Controlled -by a config flag. - -## Configuration - -```yaml -# In config.yaml -api_server: - enabled: true - port: 8642 - host: "127.0.0.1" # localhost only by default - key: "your-secret-key" # or via API_SERVER_KEY env var - allow_model_override: false # let clients choose the model - max_concurrent: 5 # max simultaneous requests -``` - -Environment variables: -```bash -API_SERVER_ENABLED=true -API_SERVER_PORT=8642 -API_SERVER_HOST=127.0.0.1 -API_SERVER_KEY=your-secret-key -``` - -## Implementation Plan - -### Phase 1: MVP (non-streaming) — PR - -1. `gateway/platforms/api_server.py` — new adapter - - aiohttp.web server with endpoints: - - `POST /v1/chat/completions` — Chat Completions API (universal compat) - - `POST /v1/responses` — Responses API (server-side state, tool preservation) - - `GET /v1/models` — list available models - - `GET /health` — health check - - Bearer token auth middleware - - Non-streaming responses (run agent, return full result) - - Chat Completions: stateless, messages array is the conversation - - Responses API: server-side conversation storage via previous_response_id - - Store full internal conversation (including tool calls) keyed by response ID - - On subsequent requests, reconstruct full context from stored chain - - Frontend system prompt layered on top of hermes-agent's core prompt - -2. `gateway/config.py` — add `Platform.API_SERVER` enum + config - -3. `gateway/run.py` — register adapter in `_create_adapter()` - -4. Tests in `tests/gateway/test_api_server.py` - -### Phase 2: SSE Streaming - -1. Add response streaming to both endpoints - - Chat Completions: `choices[0].delta.content` SSE format - - Responses API: semantic events (response.output_text.delta, etc.) - - Run agent in thread, collect output via callback queue - - Handle client disconnect (cancel agent) - -2. Add `stream_callback` parameter to `AIAgent.run_conversation()` - -### Phase 3: Enhanced Features - -1. Tool call transparency mode (opt-in) -2. Model passthrough/override -3. Concurrent request limiting -4. Usage tracking / rate limiting -5. CORS headers for browser-based frontends -6. GET /v1/responses/{id} — retrieve stored response -7. DELETE /v1/responses/{id} — delete stored response - -## Files Changed - -| File | Change | -|------|--------| -| `gateway/platforms/api_server.py` | NEW — main adapter (~300 lines) | -| `gateway/config.py` | Add Platform.API_SERVER + config (~20 lines) | -| `gateway/run.py` | Register adapter in _create_adapter() (~10 lines) | -| `tests/gateway/test_api_server.py` | NEW — tests (~200 lines) | -| `cli-config.yaml.example` | Add api_server section | -| `README.md` | Mention API server in platform list | - -## Compatibility Matrix - -Once implemented, hermes-agent works as a drop-in backend for: - -| Frontend | Stars | How to Connect | -|----------|-------|---------------| -| Open WebUI | 126k | Settings → Connections → Add OpenAI API, URL: `http://localhost:8642/v1` | -| NextChat | 87k | BASE_URL env var | -| LobeChat | 73k | Custom provider endpoint | -| AnythingLLM | 56k | LLM Provider → Generic OpenAI | -| Oobabooga | 42k | Already a backend, not a frontend | -| ChatBox | 39k | API Host setting | -| LibreChat | 34k | librechat.yaml custom endpoint | -| Chatbot UI | 29k | Custom API endpoint | -| Jan | 26k | Remote model config | -| AionUI | 18k | Custom API endpoint | -| HF Chat-UI | 8k | OPENAI_BASE_URL env var | -| big-AGI | 7k | Custom endpoint | diff --git a/reference/hermes-agent-2026.5.16/.plans/streaming-support.md b/reference/hermes-agent-2026.5.16/.plans/streaming-support.md deleted file mode 100755 index cb4ec11..0000000 --- a/reference/hermes-agent-2026.5.16/.plans/streaming-support.md +++ /dev/null @@ -1,705 +0,0 @@ -# Streaming LLM Response Support for Hermes Agent - -## Overview - -Add token-by-token streaming of LLM responses across all platforms. When enabled, -users see the response typing out live instead of waiting for the full generation. -Streaming is opt-in via config, defaults to off, and all existing non-streaming -code paths remain intact as the default. - -## Design Principles - -1. **Feature-flagged**: `streaming.enabled: true` in config.yaml. Off by default. - When off, all existing code paths are unchanged — zero risk to current behavior. -2. **Callback-based**: A simple `stream_callback(text_delta: str)` function injected - into AIAgent. The agent doesn't know or care what the consumer does with tokens. -3. **Graceful degradation**: If the provider doesn't support streaming, or streaming - fails for any reason, silently fall back to the non-streaming path. -4. **Platform-agnostic core**: The streaming mechanism in AIAgent works the same - regardless of whether the consumer is CLI, Telegram, Discord, or the API server. - ---- - -## Architecture - -``` - stream_callback(delta) - │ - ┌─────────────┐ ┌─────────────▼──────────────┐ - │ LLM API │ │ queue.Queue() │ - │ (stream) │───►│ thread-safe bridge between │ - │ │ │ agent thread & consumer │ - └─────────────┘ └─────────────┬──────────────┘ - │ - ┌──────────────┼──────────────┐ - │ │ │ - ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ - │ CLI │ │ Gateway │ │ API Server│ - │ print to │ │ edit msg │ │ SSE event │ - │ terminal │ │ on Tg/Dc │ │ to client │ - └───────────┘ └───────────┘ └───────────┘ -``` - -The agent runs in a thread. The callback puts tokens into a thread-safe queue. -Each consumer reads the queue in its own context (async task, main thread, etc.). - ---- - -## Configuration - -### config.yaml - -```yaml -streaming: - enabled: false # Master switch. Default off. - # Per-platform overrides (optional): - # cli: true # Override for CLI only - # telegram: true # Override for Telegram only - # discord: false # Keep Discord non-streaming - # api_server: true # Override for API server -``` - -### Environment variables - -``` -HERMES_STREAMING_ENABLED=true # Master switch via env -``` - -### How the flag is read - -- **CLI**: `load_cli_config()` reads `streaming.enabled`, sets env var. AIAgent - checks at init time. -- **Gateway**: `_run_agent()` reads config, decides whether to pass - `stream_callback` to the AIAgent constructor. -- **API server**: For Chat Completions `stream=true` requests, always uses streaming - regardless of config (the client is explicitly requesting it). For non-stream - requests, uses config. - -### Precedence - -1. API server: client's `stream` field overrides everything -2. Per-platform config override (e.g., `streaming.telegram: true`) -3. Master `streaming.enabled` flag -4. Default: off - ---- - -## Implementation Plan - -### Phase 1: Core streaming infrastructure in AIAgent - -**File: run_agent.py** - -#### 1a. Add stream_callback parameter to __init__ (~5 lines) - -```python -def __init__(self, ..., stream_callback: callable = None, ...): - self.stream_callback = stream_callback -``` - -No other init changes. The callback is optional — when None, everything -works exactly as before. - -#### 1b. Add _run_streaming_chat_completion() method (~65 lines) - -New method for Chat Completions API streaming: - -```python -def _run_streaming_chat_completion(self, api_kwargs: dict): - """Stream a chat completion, emitting text tokens via stream_callback. - - Returns a fake response object compatible with the non-streaming code path. - Falls back to non-streaming on any error. - """ - stream_kwargs = dict(api_kwargs) - stream_kwargs["stream"] = True - stream_kwargs["stream_options"] = {"include_usage": True} - - accumulated_content = [] - accumulated_tool_calls = {} # index -> {id, name, arguments} - final_usage = None - - try: - stream = self.client.chat.completions.create(**stream_kwargs) - - for chunk in stream: - if not chunk.choices: - # Usage-only chunk (final) - if chunk.usage: - final_usage = chunk.usage - continue - - delta = chunk.choices[0].delta - - # Text content — emit via callback - if delta.content: - accumulated_content.append(delta.content) - if self.stream_callback: - try: - self.stream_callback(delta.content) - except Exception: - pass - - # Tool call deltas — accumulate silently - if delta.tool_calls: - for tc_delta in delta.tool_calls: - idx = tc_delta.index - if idx not in accumulated_tool_calls: - accumulated_tool_calls[idx] = { - "id": tc_delta.id or "", - "name": "", "arguments": "" - } - if tc_delta.function: - if tc_delta.function.name: - accumulated_tool_calls[idx]["name"] = tc_delta.function.name - if tc_delta.function.arguments: - accumulated_tool_calls[idx]["arguments"] += tc_delta.function.arguments - - # Build fake response compatible with existing code - tool_calls = [] - for idx in sorted(accumulated_tool_calls): - tc = accumulated_tool_calls[idx] - if tc["name"]: - tool_calls.append(SimpleNamespace( - id=tc["id"], type="function", - function=SimpleNamespace(name=tc["name"], arguments=tc["arguments"]), - )) - - return SimpleNamespace( - choices=[SimpleNamespace( - message=SimpleNamespace( - content="".join(accumulated_content) or "", - tool_calls=tool_calls or None, - role="assistant", - ), - finish_reason="tool_calls" if tool_calls else "stop", - )], - usage=final_usage, - model=self.model, - ) - - except Exception as e: - logger.debug("Streaming failed, falling back to non-streaming: %s", e) - return self.client.chat.completions.create(**api_kwargs) -``` - -#### 1c. Modify _run_codex_stream() for Responses API (~10 lines) - -The method already iterates the stream. Add callback emission: - -```python -def _run_codex_stream(self, api_kwargs: dict): - with self.client.responses.stream(**api_kwargs) as stream: - for event in stream: - # Emit text deltas if streaming callback is set - if self.stream_callback and hasattr(event, 'type'): - if event.type == 'response.output_text.delta': - try: - self.stream_callback(event.delta) - except Exception: - pass - return stream.get_final_response() -``` - -#### 1d. Modify _interruptible_api_call() (~5 lines) - -Add the streaming branch: - -```python -def _call(): - try: - if self.api_mode == "codex_responses": - result["response"] = self._run_codex_stream(api_kwargs) - elif self.stream_callback is not None: - result["response"] = self._run_streaming_chat_completion(api_kwargs) - else: - result["response"] = self.client.chat.completions.create(**api_kwargs) - except Exception as e: - result["error"] = e -``` - -#### 1e. Signal end-of-stream to consumers (~5 lines) - -After the API call returns, signal the callback that streaming is done -so consumers can finalize (remove cursor, close SSE, etc.): - -```python -# In run_conversation(), after _interruptible_api_call returns: -if self.stream_callback: - try: - self.stream_callback(None) # None = end of stream signal - except Exception: - pass -``` - -Consumers check: `if delta is None: finalize()` - -**Tests for Phase 1:** (~150 lines) -- Test _run_streaming_chat_completion with mocked stream -- Test fallback to non-streaming on error -- Test tool_call accumulation during streaming -- Test stream_callback receives correct deltas -- Test None signal at end of stream -- Test streaming disabled when callback is None - ---- - -### Phase 2: Gateway consumers (Telegram, Discord, etc.) - -**File: gateway/run.py** - -#### 2a. Read streaming config (~15 lines) - -In `_run_agent()`, before creating the AIAgent: - -```python -# Read streaming config -_streaming_enabled = False -try: - # Check per-platform override first - platform_key = source.platform.value if source.platform else "" - _stream_cfg = {} # loaded from config.yaml streaming section - if _stream_cfg.get(platform_key) is not None: - _streaming_enabled = bool(_stream_cfg[platform_key]) - else: - _streaming_enabled = bool(_stream_cfg.get("enabled", False)) -except Exception: - pass -# Env var override -if os.getenv("HERMES_STREAMING_ENABLED", "").lower() in ("true", "1", "yes"): - _streaming_enabled = True -``` - -#### 2b. Set up queue + callback (~15 lines) - -```python -_stream_q = None -_stream_done = None -_stream_msg_id = [None] # mutable ref for the async task - -if _streaming_enabled: - import queue as _q - _stream_q = _q.Queue() - _stream_done = threading.Event() - - def _on_token(delta): - if delta is None: - _stream_done.set() - else: - _stream_q.put(delta) -``` - -Pass `stream_callback=_on_token` to the AIAgent constructor. - -#### 2c. Telegram/Discord stream preview task (~50 lines) - -```python -async def stream_preview(): - """Progressively edit a message with streaming tokens.""" - if not _stream_q: - return - adapter = self.adapters.get(source.platform) - if not adapter: - return - - accumulated = [] - token_count = 0 - last_edit = 0.0 - MIN_TOKENS = 20 # Don't show until enough context - EDIT_INTERVAL = 1.5 # Respect Telegram rate limits - - try: - while not _stream_done.is_set(): - try: - chunk = _stream_q.get(timeout=0.1) - accumulated.append(chunk) - token_count += 1 - except queue.Empty: - continue - - now = time.monotonic() - if token_count >= MIN_TOKENS and (now - last_edit) >= EDIT_INTERVAL: - preview = "".join(accumulated) + " ▌" - if _stream_msg_id[0] is None: - r = await adapter.send( - chat_id=source.chat_id, - content=preview, - metadata=_thread_metadata, - ) - if r.success and r.message_id: - _stream_msg_id[0] = r.message_id - else: - await adapter.edit_message( - chat_id=source.chat_id, - message_id=_stream_msg_id[0], - content=preview, - ) - last_edit = now - - # Drain remaining tokens - while not _stream_q.empty(): - accumulated.append(_stream_q.get_nowait()) - - # Final edit — remove cursor, show complete text - if _stream_msg_id[0] and accumulated: - await adapter.edit_message( - chat_id=source.chat_id, - message_id=_stream_msg_id[0], - content="".join(accumulated), - ) - - except asyncio.CancelledError: - # Clean up on cancel - if _stream_msg_id[0] and accumulated: - try: - await adapter.edit_message( - chat_id=source.chat_id, - message_id=_stream_msg_id[0], - content="".join(accumulated), - ) - except Exception: - pass - except Exception as e: - logger.debug("stream_preview error: %s", e) -``` - -#### 2d. Skip final send if already streamed (~10 lines) - -In `_process_message_background()` (base.py), after getting the response, -if streaming was active and `_stream_msg_id[0]` is set, the final response -was already delivered via progressive edits. Skip the normal `self.send()` -call to avoid duplicating the message. - -This is the most delicate integration point — we need to communicate from -the gateway's `_run_agent` back to the base adapter's response sender that -the response was already delivered. Options: - -- **Option A**: Return a special marker in the result dict: - `result["_streamed_msg_id"] = _stream_msg_id[0]` - The base adapter checks this and skips `send()`. - -- **Option B**: Edit the already-sent message with the final response - (which may differ slightly from accumulated tokens due to think-block - stripping, etc.) and don't send a new one. - -- **Option C**: The stream preview task handles the FULL final response - (including any post-processing), and the handler returns None to skip - the normal send path. - -Recommended: **Option A** — cleanest separation. The result dict already -carries metadata; adding one more field is low-risk. - -**Platform-specific considerations:** - -| Platform | Edit support | Rate limits | Streaming approach | -|----------|-------------|-------------|-------------------| -| Telegram | ✅ edit_message_text | ~20 edits/min | Edit every 1.5s | -| Discord | ✅ message.edit | 5 edits/5s per message | Edit every 1.2s | -| Slack | ✅ chat.update | Tier 3 (~50/min) | Edit every 1.5s | -| WhatsApp | ❌ no edit support | N/A | Skip streaming, use normal path | -| HomeAssistant | ❌ no edit | N/A | Skip streaming | -| API Server | ✅ SSE native | No limit | Real SSE events | - -WhatsApp and HomeAssistant fall back to non-streaming automatically because -they don't support message editing. - -**Tests for Phase 2:** (~100 lines) -- Test stream_preview sends/edits correctly -- Test skip-final-send when streaming delivered -- Test WhatsApp/HA graceful fallback -- Test streaming disabled per-platform config -- Test thread_id metadata forwarded in stream messages - ---- - -### Phase 3: CLI streaming - -**File: cli.py** - -#### 3a. Set up callback in the CLI chat loop (~20 lines) - -In `_chat_once()` or wherever the agent is invoked: - -```python -if streaming_enabled: - _stream_q = queue.Queue() - _stream_done = threading.Event() - - def _cli_stream_callback(delta): - if delta is None: - _stream_done.set() - else: - _stream_q.put(delta) - - agent.stream_callback = _cli_stream_callback -``` - -#### 3b. Token display thread/task (~30 lines) - -Start a thread that reads the queue and prints tokens: - -```python -def _stream_display(): - """Print tokens to terminal as they arrive.""" - first_token = True - while not _stream_done.is_set(): - try: - delta = _stream_q.get(timeout=0.1) - except queue.Empty: - continue - if first_token: - # Print response box top border - _cprint(f"\n{top}") - first_token = False - sys.stdout.write(delta) - sys.stdout.flush() - # Drain remaining - while not _stream_q.empty(): - sys.stdout.write(_stream_q.get_nowait()) - sys.stdout.flush() - # Print bottom border - _cprint(f"\n\n{bot}") -``` - -**Integration challenge: prompt_toolkit** - -The CLI uses prompt_toolkit which controls the terminal. Writing directly -to stdout while prompt_toolkit is active can cause display corruption. -The existing KawaiiSpinner already solves this by using prompt_toolkit's -`patch_stdout` context. The streaming display would need to do the same. - -Alternative: use `_cprint()` for each token chunk (routes through -prompt_toolkit's renderer). But this might be slow for individual tokens. - -Recommended approach: accumulate tokens in small batches (e.g., every 50ms) -and `_cprint()` the batch. This balances display responsiveness with -prompt_toolkit compatibility. - -**Tests for Phase 3:** (~50 lines) -- Test CLI streaming callback setup -- Test response box borders with streaming -- Test fallback when streaming disabled - ---- - -### Phase 4: API Server real streaming - -**File: gateway/platforms/api_server.py** - -Replace the pseudo-streaming `_write_sse_chat_completion()` with real -token-by-token SSE when the agent supports it. - -#### 4a. Wire streaming callback for stream=true requests (~20 lines) - -```python -if stream: - _stream_q = queue.Queue() - - def _api_stream_callback(delta): - _stream_q.put(delta) # None = done - - # Pass callback to _run_agent - result, usage = await self._run_agent( - ..., stream_callback=_api_stream_callback, - ) -``` - -#### 4b. Real SSE writer (~40 lines) - -```python -async def _write_real_sse(self, request, completion_id, model, stream_q): - response = web.StreamResponse( - headers={"Content-Type": "text/event-stream", "Cache-Control": "no-cache"}, - ) - await response.prepare(request) - - # Role chunk - await response.write(...) - - # Stream content chunks as they arrive - while True: - try: - delta = await asyncio.get_event_loop().run_in_executor( - None, lambda: stream_q.get(timeout=0.1) - ) - except queue.Empty: - continue - - if delta is None: # End of stream - break - - chunk = {"id": completion_id, "object": "chat.completion.chunk", ... - "choices": [{"delta": {"content": delta}, ...}]} - await response.write(f"data: {json.dumps(chunk)}\n\n".encode()) - - # Finish + [DONE] - await response.write(...) - await response.write(b"data: [DONE]\n\n") - return response -``` - -**Challenge: concurrent execution** - -The agent runs in a thread executor. SSE writing happens in the async event -loop. The queue bridges them. But `_run_agent()` currently awaits the full -result before returning. For real streaming, we need to start the agent in -the background and stream tokens while it runs: - -```python -# Start agent in background -agent_task = asyncio.create_task(self._run_agent_async(...)) - -# Stream tokens while agent runs -await self._write_real_sse(request, ..., stream_q) - -# Agent is done by now (stream_q received None) -result, usage = await agent_task -``` - -This requires splitting `_run_agent` into an async version that doesn't -block waiting for the result, or running it in a separate task. - -**Responses API SSE format:** - -For `/v1/responses` with `stream=true`, the SSE events are different: - -``` -event: response.output_text.delta -data: {"type":"response.output_text.delta","delta":"Hello"} - -event: response.completed -data: {"type":"response.completed","response":{...}} -``` - -This needs a separate SSE writer that emits Responses API format events. - -**Tests for Phase 4:** (~80 lines) -- Test real SSE streaming with mocked agent -- Test SSE event format (Chat Completions vs Responses) -- Test client disconnect during streaming -- Test fallback to pseudo-streaming when callback not available - ---- - -## Integration Issues & Edge Cases - -### 1. Tool calls during streaming - -When the model returns tool calls instead of text, no text tokens are emitted. -The stream_callback is simply never called with text. After tools execute, the -next API call may produce the final text response — streaming picks up again. - -The stream preview task needs to handle this: if no tokens arrive during a -tool-call round, don't send/edit any message. The tool progress messages -continue working as before. - -### 2. Duplicate messages - -The biggest risk: the agent sends the final response normally (via the -existing send path) AND the stream preview already showed it. The user -sees the response twice. - -Prevention: when streaming is active and tokens were delivered, the final -response send must be suppressed. The `result["_streamed_msg_id"]` marker -tells the base adapter to skip its normal send. - -### 3. Response post-processing - -The final response may differ from the accumulated streamed tokens: -- Think block stripping (`...` removed) -- Trailing whitespace cleanup -- Tool result media tag appending - -The stream preview shows raw tokens. The final edit should use the -post-processed version. This means the final edit (removing the cursor) -should use the post-processed `final_response`, not just the accumulated -stream text. - -### 4. Context compression during streaming - -If the agent triggers context compression mid-conversation, the streaming -tokens from BEFORE compression are from a different context than those -after. This isn't a problem in practice — compression happens between -API calls, not during streaming. - -### 5. Interrupt during streaming - -User sends a new message while streaming → interrupt. The stream is killed -(HTTP connection closed), accumulated tokens are shown as-is (no cursor), -and the interrupt message is processed normally. This is already handled by -`_interruptible_api_call` closing the client. - -### 6. Multi-model / fallback - -If the primary model fails and the agent falls back to a different model, -streaming state resets. The fallback call may or may not support streaming. -The graceful fallback in `_run_streaming_chat_completion` handles this. - -### 7. Rate limiting on edits - -Telegram: ~20 edits/minute (~1 every 3 seconds to be safe) -Discord: 5 edits per 5 seconds per message -Slack: ~50 API calls/minute - -The 1.5s edit interval is conservative enough for all platforms. If we get -429 rate limit errors on edits, just skip that edit cycle and try next time. - ---- - -## Files Changed Summary - -| File | Phase | Changes | -|------|-------|---------| -| `run_agent.py` | 1 | +stream_callback param, +_run_streaming_chat_completion(), modify _run_codex_stream(), modify _interruptible_api_call() | -| `gateway/run.py` | 2 | +streaming config reader, +queue/callback setup, +stream_preview task, +skip-final-send logic | -| `gateway/platforms/base.py` | 2 | +check for _streamed_msg_id in response handler | -| `cli.py` | 3 | +streaming setup, +token display, +response box integration | -| `gateway/platforms/api_server.py` | 4 | +real SSE writer, +streaming callback wiring | -| `hermes_cli/config.py` | 1 | +streaming config defaults | -| `cli-config.yaml.example` | 1 | +streaming section | -| `tests/test_streaming.py` | 1-4 | NEW — ~380 lines of tests | - -**Total new code**: ~500 lines across all phases -**Total test code**: ~380 lines - ---- - -## Rollout Plan - -1. **Phase 1** (core): Merge to main. Streaming disabled by default. - Zero impact on existing behavior. Can be tested with env var. - -2. **Phase 2** (gateway): Merge to main. Test on Telegram manually. - Enable per-platform: `streaming.telegram: true` in config. - -3. **Phase 3** (CLI): Merge to main. Test in terminal. - Enable: `streaming.cli: true` or `streaming.enabled: true`. - -4. **Phase 4** (API server): Merge to main. Test with Open WebUI. - Auto-enabled when client sends `stream: true`. - -Each phase is independently mergeable and testable. Streaming stays -off by default throughout. Once all phases are stable, consider -changing the default to enabled. - ---- - -## Config Reference (final state) - -```yaml -# config.yaml -streaming: - enabled: false # Master switch (default: off) - cli: true # Per-platform override - telegram: true - discord: true - slack: true - api_server: true # API server always streams when client requests it - edit_interval: 1.5 # Seconds between message edits (default: 1.5) - min_tokens: 20 # Tokens before first display (default: 20) -``` - -```bash -# Environment variable override -HERMES_STREAMING_ENABLED=true -``` diff --git a/reference/openai-codex/.bazelignore b/reference/openai-codex/.bazelignore deleted file mode 100644 index 2e90753..0000000 --- a/reference/openai-codex/.bazelignore +++ /dev/null @@ -1,4 +0,0 @@ -# Without this, Bazel will consider BUILD.bazel files in -# .git/sl/origbackups (which can be populated by Sapling SCM). -.git -codex-rs/target diff --git a/reference/openai-codex/.bazelrc b/reference/openai-codex/.bazelrc deleted file mode 100644 index 6357eb8..0000000 --- a/reference/openai-codex/.bazelrc +++ /dev/null @@ -1,202 +0,0 @@ -common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 -common --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 -# Dummy xcode config so we don't need to build xcode_locator in repo rule. -common --xcode_version_config=//:disable_xcode - -common --disk_cache=~/.cache/bazel-disk-cache -common --repo_contents_cache=~/.cache/bazel-repo-contents-cache -common --repository_cache=~/.cache/bazel-repo-cache -common --remote_cache_compression -startup --experimental_remote_repo_contents_cache - -common --experimental_platform_in_output_dir - -# Runfiles strategy rationale: codex-rs/utils/cargo-bin/README.md -common --noenable_runfiles - -common --enable_platform_specific_config -common:linux --host_platform=//:local_linux -common:windows --host_platform=//:local_windows -common --@rules_cc//cc/toolchains/args/archiver_flags:use_libtool_on_macos=False -common --@llvm//config:experimental_stub_libgcc_s - -# TODO(zbarsky): rules_rust doesn't implement this flag properly with remote exec... -# common --@rules_rust//rust/settings:pipelined_compilation - -common --incompatible_strict_action_env -# Not ideal, but We need to allow dotslash to be found -common:linux --test_env=PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -common:macos --test_env=PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin - -# Pass through some env vars Windows needs to use powershell? -common:windows --test_env=SYSTEMROOT -common:windows --test_env=COMSPEC -common:windows --test_env=WINDIR -# Rust's libtest harness runs test bodies on std-spawned threads. The default -# 2 MiB stack can be too small for large async test futures on Windows CI; see -# https://github.com/openai/codex/pull/19067 for the motivating failure. -common --test_env=RUST_MIN_STACK=8388608 # 8 MiB - -common --test_output=errors -common --bes_results_url=https://app.buildbuddy.io/invocation/ -common --bes_backend=grpcs://remote.buildbuddy.io -common --remote_cache=grpcs://remote.buildbuddy.io -common --remote_download_toplevel -common --nobuild_runfile_links -common --remote_timeout=3600 -common --noexperimental_throttle_remote_action_building -common --experimental_remote_execution_keepalive -common --grpc_keepalive_time=30s -common --experimental_remote_downloader=grpcs://remote.buildbuddy.io - -# This limits both in-flight executions and concurrent downloads. Even with high number -# of jobs execution will still be limited by CPU cores, so this just pays a bit of -# memory in exchange for higher download concurrency. -common --jobs=30 - -common:remote --extra_execution_platforms=//:rbe -common:remote --remote_executor=grpcs://remote.buildbuddy.io -common:remote --jobs=800 -# TODO(team): Evaluate if this actually helps, zbarsky is not sure, everything seems bottlenecked on `core` either way. -# Enable pipelined compilation since we are not bound by local CPU count. -#common:remote --@rules_rust//rust/settings:pipelined_compilation - -# GitHub Actions CI configs. -common:ci --remote_download_minimal -common:ci --keep_going -common:ci --verbose_failures -common:ci --build_metadata=REPO_URL=https://github.com/openai/codex.git -common:ci --build_metadata=ROLE=CI -common:ci --build_metadata=VISIBILITY=PUBLIC -# rules_rust derives debug level from Bazel toolchain/compilation-mode settings, -# not Cargo profiles. Keep CI Rust actions explicit and lean. -common:ci --@rules_rust//rust/settings:extra_rustc_flag=-Cdebuginfo=0 -common:ci --@rules_rust//rust/settings:extra_exec_rustc_flag=-Cdebuginfo=0 - -# Disable disk cache in CI since we have a remote one and aren't using persistent workers. -common:ci --disk_cache= - -# Shared config for the main Bazel CI workflow. -common:ci-bazel --config=ci -common:ci-bazel --build_metadata=TAG_workflow=bazel -# Bazel CI cross-compiles in several legs, and the V8-backed code-mode tests -# are not stable in that setup yet. Keep running the rest of the Rust -# integration suites through the workspace-root launcher. -common:ci-bazel --test_env=CODEX_BAZEL_TEST_SKIP_FILTERS=suite::code_mode:: - -# Shared config for Bazel-backed Rust linting. -build:clippy --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect -build:clippy --output_groups=+clippy_checks -build:clippy --@rules_rust//rust/settings:clippy.toml=//codex-rs:clippy.toml -# Keep this deny-list in sync with `codex-rs/Cargo.toml` `[workspace.lints.clippy]`. -# Cargo applies those lint levels to member crates that opt into `[lints] workspace = true` -# in their own `Cargo.toml`, but `rules_rust` Bazel clippy does not read Cargo lint levels. -# `clippy.toml` can configure lint behavior, but it cannot set allow/warn/deny/forbid levels. -build:clippy --@rules_rust//rust/settings:clippy_flag=-Dwarnings -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::await_holding_invalid_type -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::await_holding_lock -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::expect_used -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::identity_op -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_clamp -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_filter -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_find -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_flatten -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_map -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_memcpy -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_non_exhaustive -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_ok_or -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_range_contains -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_retain -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_strip -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_try_fold -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::manual_unwrap_or -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::needless_borrow -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::needless_borrowed_reference -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::needless_collect -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::needless_late_init -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::needless_option_as_deref -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::needless_question_mark -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::needless_update -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::redundant_clone -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::redundant_closure -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::redundant_closure_for_method_calls -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::redundant_static_lifetimes -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::trivially_copy_pass_by_ref -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::uninlined_format_args -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::unnecessary_filter_map -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::unnecessary_lazy_evaluations -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::unnecessary_sort_by -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::unnecessary_to_owned -build:clippy --@rules_rust//rust/settings:clippy_flag=--deny=clippy::unwrap_used - -# Shared config for Bazel-backed argument-comment-lint. -build:argument-comment-lint --aspects=//tools/argument-comment-lint:lint_aspect.bzl%rust_argument_comment_lint_aspect -build:argument-comment-lint --output_groups=argument_comment_lint_checks -build:argument-comment-lint --@rules_rust//rust/toolchain/channel=nightly - -# Rearrange caches on Windows so they're on the same volume as the checkout. -common:ci-windows --config=ci-bazel -common:ci-windows --build_metadata=TAG_os=windows -common:ci-windows --repo_contents_cache=D:/a/.cache/bazel-repo-contents-cache - -# We prefer to run the build actions entirely remotely so we can dial up the concurrency. -# We have platform-specific tests, so we want to execute the tests on all platforms using the strongest sandboxing available on each platform. - -# On linux, we can do a full remote build/test, by targeting the right (x86/arm) runners, so we have coverage of both. -# Linux crossbuilds don't work until we untangle the libc constraint mess. -common:ci-linux --config=ci-bazel -common:ci-linux --build_metadata=TAG_os=linux -common:ci-linux --config=remote -common:ci-linux --strategy=remote -common:ci-linux --platforms=//:rbe - -# On mac, we can run all the build actions remotely but test actions locally. -common:ci-macos --config=ci-bazel -common:ci-macos --build_metadata=TAG_os=macos -common:ci-macos --config=remote -common:ci-macos --strategy=remote -common:ci-macos --strategy=TestRunner=darwin-sandbox,local - -# On Windows, use Linux remote execution for build actions but keep test actions -# on the Windows runner so Bazel's normal test sharding and flaky-test retries -# still run against Windows binaries. -common:ci-windows-cross --config=ci-windows -common:ci-windows-cross --build_metadata=TAG_windows_cross_compile=true -common:ci-windows-cross --config=remote -common:ci-windows-cross --host_platform=//:rbe -common:ci-windows-cross --strategy=remote -common:ci-windows-cross --strategy=TestRunner=local -common:ci-windows-cross --local_test_jobs=4 -common:ci-windows-cross --test_env=RUST_TEST_THREADS=1 -# Native Windows CI still covers the PowerShell tests. The cross-built gnullvm -# binaries currently hang in PowerShell AST parser tests when those binaries are -# run on the Windows runner. -common:ci-windows-cross --test_env=CODEX_BAZEL_TEST_SKIP_FILTERS=suite::code_mode::,powershell -common:ci-windows-cross --platforms=//:windows_x86_64_gnullvm -common:ci-windows-cross --extra_execution_platforms=//:rbe,//:windows_x86_64_msvc -common:ci-windows-cross --extra_toolchains=//:windows_gnullvm_tests_on_msvc_host_toolchain - -# Linux-only V8 CI config. -common:ci-v8 --config=ci -common:ci-v8 --build_metadata=TAG_workflow=v8 -common:ci-v8 --build_metadata=TAG_os=linux -common:ci-v8 --config=remote -common:ci-v8 --strategy=remote - -# Source-built Bazel V8 artifacts use the in-process sandbox by default. This -# does not affect Cargo's default prebuilt rusty_v8 path. -common --@v8//:v8_enable_pointer_compression=True -common --@v8//:v8_enable_sandbox=True - -# Keep currently published rusty_v8 release artifacts non-sandboxed until the -# artifact migration ships matching Rust feature selection for Cargo consumers. -common:v8-release-compat --@v8//:v8_enable_pointer_compression=False -common:v8-release-compat --@v8//:v8_enable_sandbox=False - -# Match rusty_v8's upstream GN release contract for published artifacts: every -# target object uses Chromium's custom libc++ headers and the archive folds in -# the matching runtime objects. -common:rusty-v8-upstream-libcxx --@v8//:v8_use_rusty_v8_custom_libcxx=True - -# Optional per-user local overrides. -try-import %workspace%/user.bazelrc diff --git a/reference/openai-codex/.bazelversion b/reference/openai-codex/.bazelversion deleted file mode 100644 index f7ee066..0000000 --- a/reference/openai-codex/.bazelversion +++ /dev/null @@ -1 +0,0 @@ -9.0.0 diff --git a/reference/openai-codex/.codespellignore b/reference/openai-codex/.codespellignore deleted file mode 100644 index 23924fe..0000000 --- a/reference/openai-codex/.codespellignore +++ /dev/null @@ -1,6 +0,0 @@ -iTerm -iTerm2 -psuedo -SOM -te -TE diff --git a/reference/openai-codex/.codespellrc b/reference/openai-codex/.codespellrc deleted file mode 100644 index 838b7e8..0000000 --- a/reference/openai-codex/.codespellrc +++ /dev/null @@ -1,6 +0,0 @@ -[codespell] -# Ref: https://github.com/codespell-project/codespell#using-a-config-file -skip = .git*,vendor,*-lock.yaml,*.lock,.codespellrc,*test.ts,*.jsonl,frame*.txt,*.snap,*.snap.new -check-hidden = true -ignore-regex = ^\s*"image/\S+": ".*|\b(afterAll)\b -ignore-words-list = ratatui,ser,iTerm,iterm2,iterm,te,TE,PASE,SEH diff --git a/reference/openai-codex/.codex/environments/environment.toml b/reference/openai-codex/.codex/environments/environment.toml deleted file mode 100644 index f67f198..0000000 --- a/reference/openai-codex/.codex/environments/environment.toml +++ /dev/null @@ -1,11 +0,0 @@ -# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY -version = 1 -name = "codex" - -[setup] -script = "" - -[[actions]] -name = "Run" -icon = "run" -command = "cargo +1.93.0 run --manifest-path=codex-rs/Cargo.toml --bin codex -- -c mcp_oauth_credentials_store=file" diff --git a/reference/openai-codex/.codex/skills/babysit-pr/SKILL.md b/reference/openai-codex/.codex/skills/babysit-pr/SKILL.md deleted file mode 100644 index 1b95144..0000000 --- a/reference/openai-codex/.codex/skills/babysit-pr/SKILL.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -name: babysit-pr -description: Babysit a GitHub pull request after creation by continuously polling review comments, CI checks/workflow runs, and mergeability state until the PR is merged/closed or user help is required. Diagnose failures, retry likely flaky failures up to 3 times, auto-fix/push branch-related issues when appropriate, and keep watching open PRs so fresh review feedback is surfaced promptly. Use when the user asks Codex to monitor a PR, watch CI, handle review comments, or keep an eye on failures and feedback on an open PR. ---- - -# PR Babysitter - -## Objective -Babysit a PR persistently until one of these terminal outcomes occurs: - -- The PR is merged or closed. -- A situation requires user help (for example CI infrastructure issues, repeated flaky failures after retry budget is exhausted, permission problems, or ambiguity that cannot be resolved safely). -- Optional handoff milestone: the PR is currently green + mergeable + review-clean. Treat this as a progress state, not a watcher stop, so late-arriving review comments are still surfaced promptly while the PR remains open. - -Do not stop merely because a single snapshot returns `idle` while checks are still pending. - -## Inputs -Accept any of the following: - -- No PR argument: infer the PR from the current branch (`--pr auto`) -- PR number -- PR URL - -## Core Workflow - -1. When the user asks to "monitor"/"watch"/"babysit" a PR, start with the watcher's continuous mode (`--watch`) unless you are intentionally doing a one-shot diagnostic snapshot. -2. Run the watcher script to snapshot PR/review/CI state (or consume each streamed snapshot from `--watch`). -3. Inspect the `actions` list in the JSON response. -4. If `diagnose_ci_failure` is present, inspect failed run logs and classify the failure. -5. If the failure is likely caused by the current branch, patch code locally, commit, and push. Do not patch random flaky tests, CI infrastructure, dependency outages, runner issues, or other failures that are unrelated to the branch. -6. If `process_review_comment` is present, inspect surfaced review items and decide whether to address them. -7. If a review item is actionable and correct, patch code locally, commit, push, and then mark the associated review thread/comment as resolved once the fix is on GitHub. -8. Do not post replies to human-authored review comments/threads unless the user explicitly confirms the exact response. If a human review item is non-actionable, already addressed, or not valid, surface the item and recommended response to the user instead of replying on GitHub. -9. If the failure is likely flaky/unrelated and `retry_failed_checks` is present, rerun failed jobs with `--retry-failed-now`. -10. If both actionable review feedback and `retry_failed_checks` are present, prioritize review feedback first; a new commit will retrigger CI, so avoid rerunning flaky checks on the old SHA unless you intentionally defer the review change. -11. On every loop, look for newly surfaced review feedback before acting on CI failures or mergeability state, then verify mergeability / merge-conflict status (for example via `gh pr view`) alongside CI. -12. After any push or rerun action, immediately return to step 1 and continue polling on the updated SHA/state. -13. If you had been using `--watch` before pausing to patch/commit/push, relaunch `--watch` yourself in the same turn immediately after the push (do not wait for the user to re-invoke the skill). -14. Repeat polling until `stop_pr_closed` appears or a user-help-required blocker is reached. A green + review-clean + mergeable PR is a progress milestone, not a reason to stop the watcher while the PR is still open. -15. Maintain terminal/session ownership: while babysitting is active, keep consuming watcher output in the same turn; do not leave a detached `--watch` process running and then end the turn as if monitoring were complete. - -## Commands - -### One-shot snapshot - -```bash -python3 .codex/skills/babysit-pr/scripts/gh_pr_watch.py --pr auto --once -``` - -### Continuous watch (JSONL) - -```bash -python3 .codex/skills/babysit-pr/scripts/gh_pr_watch.py --pr auto --watch -``` - -### Trigger flaky retry cycle (only when watcher indicates) - -```bash -python3 .codex/skills/babysit-pr/scripts/gh_pr_watch.py --pr auto --retry-failed-now -``` - -### Explicit PR target - -```bash -python3 .codex/skills/babysit-pr/scripts/gh_pr_watch.py --pr --once -``` - -## CI Failure Classification -Use `gh` commands to inspect failed runs before deciding to rerun. - -- `gh run view --json jobs,name,workflowName,conclusion,status,url,headSha` -- `gh api repos///actions/runs//jobs -X GET -f per_page=100` -- `gh api repos///actions/jobs//logs > /tmp/codex-gh-job--logs.zip` -- `gh run view --log-failed` as a fallback after the overall workflow run is complete - -`gh run view --log-failed` is workflow-run scoped and may not expose failed-job logs until the overall run finishes. For faster diagnosis, poll the run's jobs first and, as soon as a specific job has failed, fetch that job's logs directly from the Actions job logs endpoint. The watcher includes a `failed_jobs` list with each failed job's `job_id` and `logs_endpoint` when GitHub exposes one. - -Prefer treating failures as branch-related when failed-job logs point to changed code (compile/test/lint/typecheck/snapshots/static analysis in touched areas). - -Prefer treating failures as flaky/unrelated when logs show transient infra/external issues (timeouts, runner provisioning failures, registry/network outages, GitHub Actions infra errors). - -Do not attempt to fix flaky/unrelated failures by changing tests, build scripts, CI configuration, dependency pins, or infrastructure-adjacent code unless the logs clearly connect the failure to the PR branch. For flaky/unrelated failures, rerun only when the watcher recommends `retry_failed_checks`; otherwise wait or stop for user help. - -If classification is ambiguous, perform one manual diagnosis attempt before choosing rerun. - -Read `.codex/skills/babysit-pr/references/heuristics.md` for a concise checklist. - -## Review Comment Handling -The watcher surfaces review items from: - -- PR issue comments -- Inline review comments -- Review submissions (COMMENT / APPROVED / CHANGES_REQUESTED) - -It intentionally surfaces Codex reviewer bot feedback (for example comments/reviews from `chatgpt-codex-connector[bot]`) in addition to human reviewer feedback. Most unrelated bot noise should still be ignored. -For safety, the watcher only auto-surfaces trusted human review authors (for example repo OWNER/MEMBER/COLLABORATOR, plus the authenticated operator) and approved review bots such as Codex. -On a fresh watcher state file, existing pending review feedback may be surfaced immediately (not only comments that arrive after monitoring starts). This is intentional so already-open review comments are not missed. - -When you agree with a comment and it is actionable: - -1. Patch code locally. -2. Commit with `codex: address PR review feedback (#)`. -3. Push to the PR head branch. -4. After the push succeeds, mark the associated GitHub review thread/comment as resolved. -5. Resume watching on the new SHA immediately (do not stop after reporting the push). -6. If monitoring was running in `--watch` mode, restart `--watch` immediately after the push in the same turn; do not wait for the user to ask again. - -Do not post replies to human-authored GitHub review comments/threads automatically. If you disagree with a human comment, believe it is non-actionable/already addressed, or need to answer a question, report the item to the user with a suggested response and wait for explicit confirmation before posting anything on GitHub. If the user approves a response, prefix it with `[codex]` so it is clear the response is automated and not from the human user. -If the watcher later surfaces your own approved reply because the authenticated operator is treated as a trusted review author, treat that self-authored item as already handled and do not reply again. -If a code review comment/thread is already marked as resolved in GitHub, treat it as non-actionable and safely ignore it unless new unresolved follow-up feedback appears. - -## Git Safety Rules - -- Work only on the PR head branch. -- Avoid destructive git commands. -- Do not switch branches unless necessary to recover context. -- Before editing, check for unrelated uncommitted changes. If present, stop and ask the user. -- After each successful fix, commit and `git push`, then re-run the watcher. -- If you interrupted a live `--watch` session to make the fix, restart `--watch` immediately after the push in the same turn. -- Do not run multiple concurrent `--watch` processes for the same PR/state file; keep one watcher session active and reuse it until it stops or you intentionally restart it. -- A push is not a terminal outcome; continue the monitoring loop unless a strict stop condition is met. - -Commit message defaults: - -- `codex: fix CI failure on PR #` -- `codex: address PR review feedback (#)` - -## Monitoring Loop Pattern -Use this loop in a live Codex session: - -1. Run `--once`. -2. Read `actions`. -3. First check whether the PR is now merged or otherwise closed; if so, report that terminal state and stop polling immediately. -4. Check CI summary, new review items, and mergeability/conflict status. -5. Diagnose CI failures and classify branch-related vs flaky/unrelated. If the overall run is still pending but `failed_jobs` already includes a failed job, fetch that job's logs and diagnose immediately instead of waiting for the whole workflow run to finish. Patch only when the failure is branch-related. -6. For each surfaced review item from another author, patch/commit/push and then resolve it if it is actionable. If it is non-actionable, already addressed, or requires a written answer, surface it to the user with a suggested response instead of posting automatically. If a later snapshot surfaces your own approved reply, treat it as informational and continue without responding again. -7. Process actionable review comments before flaky reruns when both are present; if a review fix requires a commit, push it and skip rerunning failed checks on the old SHA. -8. Retry failed checks only when `retry_failed_checks` is present and you are not about to replace the current SHA with a review/CI fix commit. Do not make code changes for unrelated flakes or infrastructure failures just to get CI green. -9. If you pushed a commit, resolved a review thread, or triggered a rerun, report the action briefly and continue polling (do not stop). If a human review comment needs a written GitHub response, stop and ask for confirmation before posting. -10. After a review-fix push, proactively restart continuous monitoring (`--watch`) in the same turn unless a strict stop condition has already been reached. -11. If everything is passing, mergeable, not blocked on required review approval, and there are no unaddressed review items, report that the PR is currently ready to merge but keep the watcher running so new review comments are surfaced quickly while the PR remains open. -12. If blocked on a user-help-required issue (infra outage, exhausted flaky retries, unclear reviewer request, permissions), report the blocker and stop. -13. Otherwise sleep according to the polling cadence below and repeat. - -When the user explicitly asks to monitor/watch/babysit a PR, prefer `--watch` so polling continues autonomously in one command. Use repeated `--once` snapshots only for debugging, local testing, or when the user explicitly asks for a one-shot check. -Do not stop to ask the user whether to continue polling; continue autonomously until a strict stop condition is met or the user explicitly interrupts. -Do not hand control back to the user after a review-fix push just because a new SHA was created; restarting the watcher and re-entering the poll loop is part of the same babysitting task. -If a `--watch` process is still running and no strict stop condition has been reached, the babysitting task is still in progress; keep streaming/consuming watcher output instead of ending the turn. - -## Polling Cadence -Keep review polling aggressive and continue monitoring even after CI turns green: - -- While CI is not green (pending/running/queued or failing): poll every 1 minute. -- After CI turns green: keep polling at the base cadence while the PR remains open so newly posted review comments are surfaced promptly instead of waiting on a long green-state backoff. -- Reset the cadence immediately whenever anything changes (new commit/SHA, check status changes, new review comments, mergeability changes, review decision changes). -- If CI stops being green again (new commit, rerun, or regression): stay on the base polling cadence. -- If any poll shows the PR is merged or otherwise closed: stop polling immediately and report the terminal state. - -## Stop Conditions (Strict) -Stop only when one of the following is true: - -- PR merged or closed (stop as soon as a poll/snapshot confirms this). -- User intervention is required and Codex cannot safely proceed alone. - -Keep polling when: - -- `actions` contains only `idle` but checks are still pending. -- CI is still running/queued. -- Review state is quiet but CI is not terminal. -- CI is green but mergeability is unknown/pending. -- CI is green and mergeable, but the PR is still open and you are waiting for possible new review comments or merge-conflict changes. -- The PR is green but blocked on review approval (`REVIEW_REQUIRED` / similar); continue polling at the base cadence and surface any new review comments without asking for confirmation to keep watching. - -## Output Expectations -Provide concise progress updates while monitoring and a final summary that includes: - -- During long unchanged monitoring periods, avoid emitting a full update on every poll; summarize only status changes plus occasional heartbeat updates. -- Treat push confirmations, intermediate CI snapshots, ready-to-merge snapshots, and review-action updates as progress updates only; do not emit the final summary or end the babysitting session unless a strict stop condition is met. -- A user request to "monitor" is not satisfied by a couple of sample polls; remain in the loop until a strict stop condition or an explicit user interruption. -- A review-fix commit + push is not a completion event; immediately resume live monitoring (`--watch`) in the same turn and continue reporting progress updates. -- When CI first transitions to all green for the current SHA, emit a one-time celebratory progress update (do not repeat it on every green poll). Preferred style: `🚀 CI is all green! 33/33 passed. Still on watch for review approval.` -- Do not send the final summary while a watcher terminal is still running unless the watcher has emitted/confirmed a strict stop condition; otherwise continue with progress updates. - -- Final PR SHA -- CI status summary -- Mergeability / conflict status -- Fixes pushed -- Flaky retry cycles used -- Remaining unresolved failures or review comments - -## References - -- Heuristics and decision tree: `.codex/skills/babysit-pr/references/heuristics.md` -- GitHub CLI/API details used by the watcher: `.codex/skills/babysit-pr/references/github-api-notes.md` diff --git a/reference/openai-codex/.codex/skills/babysit-pr/agents/openai.yaml b/reference/openai-codex/.codex/skills/babysit-pr/agents/openai.yaml deleted file mode 100644 index c6946cf..0000000 --- a/reference/openai-codex/.codex/skills/babysit-pr/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "PR Babysitter" - short_description: "Watch PR review comments, CI, and merge conflicts" - default_prompt: "Babysit the current PR: monitor reviewer comments, CI, and merge-conflict status (prefer the watcher’s --watch mode for live monitoring); surface new review feedback before acting on CI or mergeability work, fix valid issues, push updates, and rerun flaky failures up to 3 times. Do not post replies to human-authored review comments unless the user explicitly confirms the exact response. Do not patch unrelated flaky tests, CI infrastructure, dependency outages, runner issues, or other failures that are not caused by the branch. Keep exactly one watcher session active for the PR (do not leave duplicate --watch terminals running). If you pause monitoring to patch review/CI feedback, restart --watch yourself immediately after the push in the same turn. If a watcher is still running and no strict stop condition has been reached, the task is still in progress: keep consuming watcher output and sending progress updates instead of ending the turn. Do not treat a green + mergeable PR as a terminal stop while it is still open; continue polling autonomously after any push/rerun so newly posted review comments are surfaced until a strict terminal stop condition is reached or the user interrupts." diff --git a/reference/openai-codex/.codex/skills/babysit-pr/references/github-api-notes.md b/reference/openai-codex/.codex/skills/babysit-pr/references/github-api-notes.md deleted file mode 100644 index 8c0a7c8..0000000 --- a/reference/openai-codex/.codex/skills/babysit-pr/references/github-api-notes.md +++ /dev/null @@ -1,82 +0,0 @@ -# GitHub CLI / API Notes For `babysit-pr` - -## Primary commands used - -### PR metadata - -- `gh pr view --json number,url,state,mergedAt,closedAt,headRefName,headRefOid,headRepository,headRepositoryOwner` - -Used to resolve PR number, URL, branch, head SHA, and closed/merged state. - -### PR checks summary - -- `gh pr checks --json name,state,bucket,link,workflow,event,startedAt,completedAt` - -Used to compute pending/failed/passed counts and whether the current CI round is terminal. - -### Workflow runs for head SHA - -- `gh api repos/{owner}/{repo}/actions/runs -X GET -f head_sha= -f per_page=100` - -Used to discover failed workflow runs and rerunnable run IDs. - -### Failed log inspection - -- `gh run view --json jobs,name,workflowName,conclusion,status,url,headSha` -- `gh api repos/{owner}/{repo}/actions/runs/{run_id}/jobs -X GET -f per_page=100` -- `gh api repos/{owner}/{repo}/actions/jobs/{job_id}/logs > /tmp/codex-gh-job-{job_id}-logs.zip` -- `gh run view --log-failed` - -Used by Codex to classify branch-related vs flaky/unrelated failures. Prefer the direct job log endpoint as soon as a job has failed because `gh run view --log-failed` may not produce failed-job logs until the overall workflow run completes. - -### Retry failed jobs only - -- `gh run rerun --failed` - -Reruns only failed jobs (and dependencies) for a workflow run. - -## Review-related endpoints - -- Issue comments on PR: - - `gh api repos/{owner}/{repo}/issues//comments?per_page=100` -- Inline PR review comments: - - `gh api repos/{owner}/{repo}/pulls//comments?per_page=100` -- Review submissions: - - `gh api repos/{owner}/{repo}/pulls//reviews?per_page=100` - -## JSON fields consumed by the watcher - -### `gh pr view` - -- `number` -- `url` -- `state` -- `mergedAt` -- `closedAt` -- `headRefName` -- `headRefOid` - -### `gh pr checks` - -- `bucket` (`pass`, `fail`, `pending`, `skipping`) -- `state` -- `name` -- `workflow` -- `link` - -### Actions runs API (`workflow_runs[]`) - -- `id` -- `name` -- `status` -- `conclusion` -- `html_url` -- `head_sha` - -### Actions run jobs API (`jobs[]`) - -- `id` -- `name` -- `status` -- `conclusion` -- `html_url` diff --git a/reference/openai-codex/.codex/skills/babysit-pr/references/heuristics.md b/reference/openai-codex/.codex/skills/babysit-pr/references/heuristics.md deleted file mode 100644 index ee44c4a..0000000 --- a/reference/openai-codex/.codex/skills/babysit-pr/references/heuristics.md +++ /dev/null @@ -1,66 +0,0 @@ -# CI / Review Heuristics - -## CI classification checklist - -Treat as **branch-related** when logs clearly indicate a regression caused by the PR branch: - -- Compile/typecheck/lint failures in files or modules touched by the branch -- Deterministic unit/integration test failures in changed areas -- Snapshot output changes caused by UI/text changes in the branch -- Static analysis violations introduced by the latest push -- Build script/config changes in the PR causing a deterministic failure - -Treat as **likely flaky or unrelated** when evidence points to transient or external issues: - -- DNS/network/registry timeout errors while fetching dependencies -- Runner image provisioning or startup failures -- GitHub Actions infrastructure/service outages -- Cloud/service rate limits or transient API outages -- Non-deterministic failures in unrelated integration tests with known flake patterns - -Do not patch likely flaky/unrelated failures. Use the retry budget for rerunnable failures, wait for pending jobs, or stop and report the blocker when the failure is persistent or infrastructure-owned. - -If uncertain, inspect failed logs once before choosing rerun. - -## Decision tree (fix vs rerun vs stop) - -1. If PR is merged/closed: stop. -2. If there are failed checks: - - Diagnose first. - - If checks are still pending but an individual job has already failed: fetch that job's logs and diagnose now. - - If branch-related: fix locally, commit, push. - - If likely flaky/unrelated and all checks for the current SHA are terminal: rerun failed jobs. - - If likely flaky/unrelated and not safely rerunnable: stop and report the blocker; do not edit unrelated tests, build scripts, CI configuration, dependency pins, or infrastructure code. - - If checks are still pending and no failed job is available yet: wait. -3. If flaky reruns for the same SHA reach the configured limit (default 3): stop and report persistent failure. -4. Independently, process any new human review comments. - -## Review comment agreement criteria - -Address the comment when: - -- The comment is technically correct. -- The change is actionable in the current branch. -- The requested change does not conflict with the user’s intent or recent guidance. -- The change can be made safely without unrelated refactors. - -Fix valid human review feedback in code when possible, but do not post a GitHub reply to a human-authored comment/thread unless the user explicitly confirms the exact response. - -Do not auto-fix when: - -- The comment is ambiguous and needs clarification. -- The request conflicts with explicit user instructions. -- The proposed change requires product/design decisions the user has not made. -- The codebase is in a dirty/unrelated state that makes safe editing uncertain. -- The comment only needs a written answer or disagreement response; propose the reply to the user instead of posting it automatically. - -## Stop-and-ask conditions - -Stop and ask the user instead of continuing automatically when: - -- The local worktree has unrelated uncommitted changes. -- `gh` auth/permissions fail. -- The PR branch cannot be pushed. -- CI failures persist after the flaky retry budget. -- Reviewer feedback requires a product decision or cross-team coordination. -- A human review comment requires a written GitHub reply instead of a code change. diff --git a/reference/openai-codex/.codex/skills/babysit-pr/scripts/gh_pr_watch.py b/reference/openai-codex/.codex/skills/babysit-pr/scripts/gh_pr_watch.py deleted file mode 100755 index face4e6..0000000 --- a/reference/openai-codex/.codex/skills/babysit-pr/scripts/gh_pr_watch.py +++ /dev/null @@ -1,869 +0,0 @@ -#!/usr/bin/env python3 -"""Watch GitHub PR CI and review activity for Codex PR babysitting workflows.""" - -import argparse -import json -import os -import re -import subprocess -import sys -import tempfile -import time -from pathlib import Path -from urllib.parse import urlparse - -FAILED_RUN_CONCLUSIONS = { - "failure", - "timed_out", - "cancelled", - "action_required", - "startup_failure", - "stale", -} -PENDING_CHECK_STATES = { - "QUEUED", - "IN_PROGRESS", - "PENDING", - "WAITING", - "REQUESTED", -} -REVIEW_BOT_LOGIN_KEYWORDS = { - "codex", -} -TRUSTED_AUTHOR_ASSOCIATIONS = { - "OWNER", - "MEMBER", - "COLLABORATOR", -} -MERGE_BLOCKING_REVIEW_DECISIONS = { - "REVIEW_REQUIRED", - "CHANGES_REQUESTED", -} -MERGE_CONFLICT_OR_BLOCKING_STATES = { - "BLOCKED", - "DIRTY", - "DRAFT", - "UNKNOWN", -} - - -class GhCommandError(RuntimeError): - pass - - -def parse_args(): - parser = argparse.ArgumentParser( - description=( - "Normalize PR/CI/review state for Codex PR babysitting and optionally " - "trigger flaky reruns." - ) - ) - parser.add_argument("--pr", default="auto", help="auto, PR number, or PR URL") - parser.add_argument("--repo", help="Optional OWNER/REPO override") - parser.add_argument("--poll-seconds", type=int, default=30, help="Watch poll interval") - parser.add_argument( - "--max-flaky-retries", - type=int, - default=3, - help="Max rerun cycles per head SHA before stop recommendation", - ) - parser.add_argument("--state-file", help="Path to state JSON file") - parser.add_argument("--once", action="store_true", help="Emit one snapshot and exit") - parser.add_argument("--watch", action="store_true", help="Continuously emit JSONL snapshots") - parser.add_argument( - "--retry-failed-now", - action="store_true", - help="Rerun failed jobs for current failed workflow runs when policy allows", - ) - parser.add_argument( - "--json", - action="store_true", - help="Emit machine-readable output (default behavior for --once and --retry-failed-now)", - ) - args = parser.parse_args() - - if args.poll_seconds <= 0: - parser.error("--poll-seconds must be > 0") - if args.max_flaky_retries < 0: - parser.error("--max-flaky-retries must be >= 0") - if args.watch and args.retry_failed_now: - parser.error("--watch cannot be combined with --retry-failed-now") - if not args.once and not args.watch and not args.retry_failed_now: - args.once = True - return args - - -def _format_gh_error(cmd, err): - stdout = (err.stdout or "").strip() - stderr = (err.stderr or "").strip() - parts = [f"GitHub CLI command failed: {' '.join(cmd)}"] - if stdout: - parts.append(f"stdout: {stdout}") - if stderr: - parts.append(f"stderr: {stderr}") - return "\n".join(parts) - - -def gh_text(args, repo=None): - cmd = ["gh"] - # `gh api` does not accept `-R/--repo` on all gh versions. The watcher's - # API calls use explicit endpoints (e.g. repos/{owner}/{repo}/...), so the - # repo flag is unnecessary there. - if repo and (not args or args[0] != "api"): - cmd.extend(["-R", repo]) - cmd.extend(args) - try: - proc = subprocess.run(cmd, check=True, capture_output=True, text=True) - except FileNotFoundError as err: - raise GhCommandError("`gh` command not found") from err - except subprocess.CalledProcessError as err: - raise GhCommandError(_format_gh_error(cmd, err)) from err - return proc.stdout - - -def gh_json(args, repo=None): - raw = gh_text(args, repo=repo).strip() - if not raw: - return None - try: - return json.loads(raw) - except json.JSONDecodeError as err: - raise GhCommandError(f"Failed to parse JSON from gh output for {' '.join(args)}") from err - - -def parse_pr_spec(pr_spec): - if pr_spec == "auto": - return {"mode": "auto", "value": None} - if re.fullmatch(r"\d+", pr_spec): - return {"mode": "number", "value": pr_spec} - parsed = urlparse(pr_spec) - if parsed.scheme and parsed.netloc and "/pull/" in parsed.path: - return {"mode": "url", "value": pr_spec} - raise ValueError("--pr must be 'auto', a PR number, or a PR URL") - - -def pr_view_fields(): - return ( - "number,url,state,mergedAt,closedAt,headRefName,headRefOid," - "headRepository,headRepositoryOwner,mergeable,mergeStateStatus,reviewDecision" - ) - - -def checks_fields(): - return "name,state,bucket,link,workflow,event,startedAt,completedAt" - - -def resolve_pr(pr_spec, repo_override=None): - parsed = parse_pr_spec(pr_spec) - cmd = ["pr", "view"] - if parsed["value"] is not None: - cmd.append(parsed["value"]) - cmd.extend(["--json", pr_view_fields()]) - data = gh_json(cmd, repo=repo_override) - if not isinstance(data, dict): - raise GhCommandError("Unexpected PR payload from `gh pr view`") - - pr_url = str(data.get("url") or "") - repo = ( - repo_override - or extract_repo_from_pr_url(pr_url) - or extract_repo_from_pr_view(data) - ) - if not repo: - raise GhCommandError("Unable to determine OWNER/REPO for the PR") - - state = str(data.get("state") or "") - merged = bool(data.get("mergedAt")) - closed = bool(data.get("closedAt")) or state.upper() == "CLOSED" - - return { - "number": int(data["number"]), - "url": pr_url, - "repo": repo, - "head_sha": str(data.get("headRefOid") or ""), - "head_branch": str(data.get("headRefName") or ""), - "state": state, - "merged": merged, - "closed": closed, - "mergeable": str(data.get("mergeable") or ""), - "merge_state_status": str(data.get("mergeStateStatus") or ""), - "review_decision": str(data.get("reviewDecision") or ""), - } - - -def extract_repo_from_pr_view(data): - head_repo = data.get("headRepository") - head_owner = data.get("headRepositoryOwner") - owner = None - name = None - if isinstance(head_owner, dict): - owner = head_owner.get("login") or head_owner.get("name") - elif isinstance(head_owner, str): - owner = head_owner - if isinstance(head_repo, dict): - name = head_repo.get("name") - repo_owner = head_repo.get("owner") - if not owner and isinstance(repo_owner, dict): - owner = repo_owner.get("login") or repo_owner.get("name") - elif isinstance(head_repo, str): - name = head_repo - if owner and name: - return f"{owner}/{name}" - return None -def extract_repo_from_pr_url(pr_url): - parsed = urlparse(pr_url) - parts = [p for p in parsed.path.split("/") if p] - if len(parts) >= 4 and parts[2] == "pull": - return f"{parts[0]}/{parts[1]}" - return None - - -def load_state(path): - if path.exists(): - try: - data = json.loads(path.read_text()) - except json.JSONDecodeError as err: - raise RuntimeError(f"State file is not valid JSON: {path}") from err - if not isinstance(data, dict): - raise RuntimeError(f"State file must contain an object: {path}") - return data, False - return { - "pr": {}, - "started_at": None, - "last_seen_head_sha": None, - "retries_by_sha": {}, - "seen_issue_comment_ids": [], - "seen_review_comment_ids": [], - "seen_review_ids": [], - "last_snapshot_at": None, - }, True - - -def save_state(path, state): - path.parent.mkdir(parents=True, exist_ok=True) - payload = json.dumps(state, indent=2, sort_keys=True) + "\n" - fd, tmp_name = tempfile.mkstemp(prefix=f"{path.name}.", suffix=".tmp", dir=path.parent) - tmp_path = Path(tmp_name) - try: - with os.fdopen(fd, "w", encoding="utf-8") as tmp_file: - tmp_file.write(payload) - os.replace(tmp_path, path) - except Exception: - try: - tmp_path.unlink(missing_ok=True) - except OSError: - pass - raise - - -def default_state_file_for(pr): - repo_slug = pr["repo"].replace("/", "-") - return Path(f"/tmp/codex-babysit-pr-{repo_slug}-pr{pr['number']}.json") - - -def get_pr_checks(pr_spec, repo): - parsed = parse_pr_spec(pr_spec) - cmd = ["pr", "checks"] - if parsed["value"] is not None: - cmd.append(parsed["value"]) - cmd.extend(["--json", checks_fields()]) - data = gh_json(cmd, repo=repo) - if data is None: - return [] - if not isinstance(data, list): - raise GhCommandError("Unexpected payload from `gh pr checks`") - return data - - -def is_pending_check(check): - bucket = str(check.get("bucket") or "").lower() - state = str(check.get("state") or "").upper() - return bucket == "pending" or state in PENDING_CHECK_STATES - - -def summarize_checks(checks): - pending_count = 0 - failed_count = 0 - passed_count = 0 - for check in checks: - bucket = str(check.get("bucket") or "").lower() - if is_pending_check(check): - pending_count += 1 - if bucket == "fail": - failed_count += 1 - if bucket == "pass": - passed_count += 1 - return { - "pending_count": pending_count, - "failed_count": failed_count, - "passed_count": passed_count, - "all_terminal": pending_count == 0, - } - - -def get_workflow_runs_for_sha(repo, head_sha): - endpoint = f"repos/{repo}/actions/runs" - data = gh_json( - ["api", endpoint, "-X", "GET", "-f", f"head_sha={head_sha}", "-f", "per_page=100"], - repo=repo, - ) - if not isinstance(data, dict): - raise GhCommandError("Unexpected payload from actions runs API") - runs = data.get("workflow_runs") or [] - if not isinstance(runs, list): - raise GhCommandError("Expected `workflow_runs` to be a list") - return runs - - -def failed_runs_from_workflow_runs(runs, head_sha): - failed_runs = [] - for run in runs: - if not isinstance(run, dict): - continue - if str(run.get("head_sha") or "") != head_sha: - continue - conclusion = str(run.get("conclusion") or "") - if conclusion not in FAILED_RUN_CONCLUSIONS: - continue - failed_runs.append( - { - "run_id": run.get("id"), - "workflow_name": run.get("name") or run.get("display_title") or "", - "status": str(run.get("status") or ""), - "conclusion": conclusion, - "html_url": str(run.get("html_url") or ""), - } - ) - failed_runs.sort(key=lambda item: (str(item.get("workflow_name") or ""), str(item.get("run_id") or ""))) - return failed_runs - - -def get_jobs_for_run(repo, run_id): - endpoint = f"repos/{repo}/actions/runs/{run_id}/jobs" - data = gh_json(["api", endpoint, "-X", "GET", "-f", "per_page=100"], repo=repo) - if not isinstance(data, dict): - raise GhCommandError("Unexpected payload from actions run jobs API") - jobs = data.get("jobs") or [] - if not isinstance(jobs, list): - raise GhCommandError("Expected `jobs` to be a list") - return jobs - - -def failed_jobs_from_workflow_runs(repo, runs, head_sha): - failed_jobs = [] - for run in runs: - if not isinstance(run, dict): - continue - if str(run.get("head_sha") or "") != head_sha: - continue - run_id = run.get("id") - if run_id in (None, ""): - continue - run_status = str(run.get("status") or "") - run_conclusion = str(run.get("conclusion") or "") - if run_status.lower() == "completed" and run_conclusion not in FAILED_RUN_CONCLUSIONS: - continue - jobs = get_jobs_for_run(repo, run_id) - for job in jobs: - if not isinstance(job, dict): - continue - conclusion = str(job.get("conclusion") or "") - if conclusion not in FAILED_RUN_CONCLUSIONS: - continue - job_id = job.get("id") - logs_endpoint = None - if job_id not in (None, ""): - logs_endpoint = f"repos/{repo}/actions/jobs/{job_id}/logs" - failed_jobs.append( - { - "run_id": run_id, - "workflow_name": run.get("name") or run.get("display_title") or "", - "run_status": run_status, - "run_conclusion": run_conclusion, - "job_id": job_id, - "job_name": str(job.get("name") or ""), - "status": str(job.get("status") or ""), - "conclusion": conclusion, - "html_url": str(job.get("html_url") or ""), - "logs_endpoint": logs_endpoint, - } - ) - failed_jobs.sort( - key=lambda item: ( - str(item.get("workflow_name") or ""), - str(item.get("job_name") or ""), - str(item.get("job_id") or ""), - ) - ) - return failed_jobs - - -def get_authenticated_login(): - data = gh_json(["api", "user"]) - if not isinstance(data, dict) or not data.get("login"): - raise GhCommandError("Unable to determine authenticated GitHub login from `gh api user`") - return str(data["login"]) - - -def comment_endpoints(repo, pr_number): - return { - "issue_comment": f"repos/{repo}/issues/{pr_number}/comments", - "review_comment": f"repos/{repo}/pulls/{pr_number}/comments", - "review": f"repos/{repo}/pulls/{pr_number}/reviews", - } - - -def gh_api_list_paginated(endpoint, repo=None, per_page=100): - items = [] - page = 1 - while True: - sep = "&" if "?" in endpoint else "?" - page_endpoint = f"{endpoint}{sep}per_page={per_page}&page={page}" - payload = gh_json(["api", page_endpoint], repo=repo) - if payload is None: - break - if not isinstance(payload, list): - raise GhCommandError(f"Unexpected paginated payload from gh api {endpoint}") - items.extend(payload) - if len(payload) < per_page: - break - page += 1 - return items - - -def normalize_issue_comments(items): - out = [] - for item in items: - if not isinstance(item, dict): - continue - out.append( - { - "kind": "issue_comment", - "id": str(item.get("id") or ""), - "author": extract_login(item.get("user")), - "author_association": str(item.get("author_association") or ""), - "created_at": str(item.get("created_at") or ""), - "body": str(item.get("body") or ""), - "path": None, - "line": None, - "url": str(item.get("html_url") or ""), - } - ) - return out - - -def normalize_review_comments(items): - out = [] - for item in items: - if not isinstance(item, dict): - continue - line = item.get("line") - if line is None: - line = item.get("original_line") - out.append( - { - "kind": "review_comment", - "id": str(item.get("id") or ""), - "author": extract_login(item.get("user")), - "author_association": str(item.get("author_association") or ""), - "created_at": str(item.get("created_at") or ""), - "body": str(item.get("body") or ""), - "path": item.get("path"), - "line": line, - "url": str(item.get("html_url") or ""), - } - ) - return out - - -def normalize_reviews(items): - out = [] - for item in items: - if not isinstance(item, dict): - continue - out.append( - { - "kind": "review", - "id": str(item.get("id") or ""), - "author": extract_login(item.get("user")), - "author_association": str(item.get("author_association") or ""), - "created_at": str(item.get("submitted_at") or item.get("created_at") or ""), - "body": str(item.get("body") or ""), - "path": None, - "line": None, - "url": str(item.get("html_url") or ""), - } - ) - return out - - -def extract_login(user_obj): - if isinstance(user_obj, dict): - return str(user_obj.get("login") or "") - return "" - - -def is_bot_login(login): - return bool(login) and login.endswith("[bot]") - - -def is_actionable_review_bot_login(login): - if not is_bot_login(login): - return False - lower_login = login.lower() - return any(keyword in lower_login for keyword in REVIEW_BOT_LOGIN_KEYWORDS) - - -def is_trusted_human_review_author(item, authenticated_login): - author = str(item.get("author") or "") - if not author: - return False - if authenticated_login and author == authenticated_login: - return True - association = str(item.get("author_association") or "").upper() - return association in TRUSTED_AUTHOR_ASSOCIATIONS - - -def fetch_new_review_items(pr, state, fresh_state, authenticated_login=None): - repo = pr["repo"] - pr_number = pr["number"] - endpoints = comment_endpoints(repo, pr_number) - - issue_payload = gh_api_list_paginated(endpoints["issue_comment"], repo=repo) - review_comment_payload = gh_api_list_paginated(endpoints["review_comment"], repo=repo) - review_payload = gh_api_list_paginated(endpoints["review"], repo=repo) - - issue_items = normalize_issue_comments(issue_payload) - review_comment_items = normalize_review_comments(review_comment_payload) - review_items = normalize_reviews(review_payload) - all_items = issue_items + review_comment_items + review_items - - seen_issue = {str(x) for x in state.get("seen_issue_comment_ids") or []} - seen_review_comment = {str(x) for x in state.get("seen_review_comment_ids") or []} - seen_review = {str(x) for x in state.get("seen_review_ids") or []} - - # On a brand-new state file, surface existing review activity instead of - # silently treating it as seen. This avoids missing already-pending review - # feedback when monitoring starts after comments were posted. - - new_items = [] - for item in all_items: - item_id = item.get("id") - if not item_id: - continue - author = item.get("author") or "" - if not author: - continue - if is_bot_login(author): - if not is_actionable_review_bot_login(author): - continue - elif not is_trusted_human_review_author(item, authenticated_login): - continue - - kind = item["kind"] - if kind == "issue_comment" and item_id in seen_issue: - continue - if kind == "review_comment" and item_id in seen_review_comment: - continue - if kind == "review" and item_id in seen_review: - continue - - new_items.append(item) - if kind == "issue_comment": - seen_issue.add(item_id) - elif kind == "review_comment": - seen_review_comment.add(item_id) - elif kind == "review": - seen_review.add(item_id) - - new_items.sort(key=lambda item: (item.get("created_at") or "", item.get("kind") or "", item.get("id") or "")) - state["seen_issue_comment_ids"] = sorted(seen_issue) - state["seen_review_comment_ids"] = sorted(seen_review_comment) - state["seen_review_ids"] = sorted(seen_review) - return new_items - - -def current_retry_count(state, head_sha): - retries = state.get("retries_by_sha") or {} - value = retries.get(head_sha, 0) - try: - return int(value) - except (TypeError, ValueError): - return 0 - - -def set_retry_count(state, head_sha, count): - retries = state.get("retries_by_sha") - if not isinstance(retries, dict): - retries = {} - retries[head_sha] = int(count) - state["retries_by_sha"] = retries - - -def unique_actions(actions): - out = [] - seen = set() - for action in actions: - if action not in seen: - out.append(action) - seen.add(action) - return out - - -def is_pr_ready_to_merge(pr, checks_summary, new_review_items): - if pr["closed"] or pr["merged"]: - return False - if not checks_summary["all_terminal"]: - return False - if checks_summary["failed_count"] > 0 or checks_summary["pending_count"] > 0: - return False - if new_review_items: - return False - if str(pr.get("mergeable") or "") != "MERGEABLE": - return False - if str(pr.get("merge_state_status") or "") in MERGE_CONFLICT_OR_BLOCKING_STATES: - return False - if str(pr.get("review_decision") or "") in MERGE_BLOCKING_REVIEW_DECISIONS: - return False - return True - - -def recommend_actions(pr, checks_summary, failed_runs, failed_jobs, new_review_items, retries_used, max_retries): - actions = [] - if pr["closed"] or pr["merged"]: - if new_review_items: - actions.append("process_review_comment") - actions.append("stop_pr_closed") - return unique_actions(actions) - - if is_pr_ready_to_merge(pr, checks_summary, new_review_items): - actions.append("ready_to_merge") - return unique_actions(actions) - - if new_review_items: - actions.append("process_review_comment") - - has_failed_pr_checks = checks_summary["failed_count"] > 0 or bool(failed_jobs) - if has_failed_pr_checks: - if checks_summary["all_terminal"] and retries_used >= max_retries: - actions.append("stop_exhausted_retries") - else: - actions.append("diagnose_ci_failure") - if checks_summary["all_terminal"] and failed_runs and retries_used < max_retries: - actions.append("retry_failed_checks") - - if not actions: - actions.append("idle") - return unique_actions(actions) - - -def collect_snapshot(args): - pr = resolve_pr(args.pr, repo_override=args.repo) - state_path = Path(args.state_file) if args.state_file else default_state_file_for(pr) - state, fresh_state = load_state(state_path) - - if not state.get("started_at"): - state["started_at"] = int(time.time()) - - authenticated_login = get_authenticated_login() - new_review_items = fetch_new_review_items( - pr, - state, - fresh_state=fresh_state, - authenticated_login=authenticated_login, - ) - # Surface review feedback before drilling into CI and mergeability details. - # That keeps the babysitter responsive to new comments even when other - # actions are also available. - # `gh pr checks -R ` requires an explicit PR/branch/url argument. - # After resolving `--pr auto`, reuse the concrete PR number. - checks = get_pr_checks(str(pr["number"]), repo=pr["repo"]) - checks_summary = summarize_checks(checks) - workflow_runs = get_workflow_runs_for_sha(pr["repo"], pr["head_sha"]) - failed_runs = failed_runs_from_workflow_runs(workflow_runs, pr["head_sha"]) - failed_jobs = failed_jobs_from_workflow_runs(pr["repo"], workflow_runs, pr["head_sha"]) - - retries_used = current_retry_count(state, pr["head_sha"]) - actions = recommend_actions( - pr, - checks_summary, - failed_runs, - failed_jobs, - new_review_items, - retries_used, - args.max_flaky_retries, - ) - - state["pr"] = {"repo": pr["repo"], "number": pr["number"]} - state["last_seen_head_sha"] = pr["head_sha"] - state["last_snapshot_at"] = int(time.time()) - save_state(state_path, state) - - snapshot = { - "pr": pr, - "checks": checks_summary, - "failed_runs": failed_runs, - "failed_jobs": failed_jobs, - "new_review_items": new_review_items, - "actions": actions, - "retry_state": { - "current_sha_retries_used": retries_used, - "max_flaky_retries": args.max_flaky_retries, - }, - } - return snapshot, state_path - - -def retry_failed_now(args): - snapshot, state_path = collect_snapshot(args) - pr = snapshot["pr"] - checks_summary = snapshot["checks"] - failed_runs = snapshot["failed_runs"] - retries_used = snapshot["retry_state"]["current_sha_retries_used"] - max_retries = snapshot["retry_state"]["max_flaky_retries"] - - result = { - "snapshot": snapshot, - "state_file": str(state_path), - "rerun_attempted": False, - "rerun_count": 0, - "rerun_run_ids": [], - "reason": None, - } - - if pr["closed"] or pr["merged"]: - result["reason"] = "pr_closed" - return result - if checks_summary["failed_count"] <= 0: - result["reason"] = "no_failed_pr_checks" - return result - if not failed_runs: - result["reason"] = "no_failed_runs" - return result - if not checks_summary["all_terminal"]: - result["reason"] = "checks_still_pending" - return result - if retries_used >= max_retries: - result["reason"] = "retry_budget_exhausted" - return result - - for run in failed_runs: - run_id = run.get("run_id") - if run_id in (None, ""): - continue - gh_text(["run", "rerun", str(run_id), "--failed"], repo=pr["repo"]) - result["rerun_run_ids"].append(run_id) - - if result["rerun_run_ids"]: - state, _ = load_state(state_path) - new_count = current_retry_count(state, pr["head_sha"]) + 1 - set_retry_count(state, pr["head_sha"], new_count) - state["last_snapshot_at"] = int(time.time()) - save_state(state_path, state) - result["rerun_attempted"] = True - result["rerun_count"] = len(result["rerun_run_ids"]) - result["reason"] = "rerun_triggered" - else: - result["reason"] = "failed_runs_missing_ids" - - return result - - -def print_json(obj): - sys.stdout.write(json.dumps(obj, sort_keys=True) + "\n") - sys.stdout.flush() - - -def print_event(event, payload): - print_json({"event": event, "payload": payload}) - - -def is_ci_green(snapshot): - checks = snapshot.get("checks") or {} - return ( - bool(checks.get("all_terminal")) - and int(checks.get("failed_count") or 0) == 0 - and int(checks.get("pending_count") or 0) == 0 - ) - - -def snapshot_change_key(snapshot): - pr = snapshot.get("pr") or {} - checks = snapshot.get("checks") or {} - review_items = snapshot.get("new_review_items") or [] - return ( - str(pr.get("head_sha") or ""), - str(pr.get("state") or ""), - str(pr.get("mergeable") or ""), - str(pr.get("merge_state_status") or ""), - str(pr.get("review_decision") or ""), - int(checks.get("passed_count") or 0), - int(checks.get("failed_count") or 0), - int(checks.get("pending_count") or 0), - tuple( - (str(item.get("kind") or ""), str(item.get("id") or "")) - for item in review_items - if isinstance(item, dict) - ), - tuple(snapshot.get("actions") or []), - ) - - -def run_watch(args): - poll_seconds = args.poll_seconds - last_change_key = None - while True: - snapshot, state_path = collect_snapshot(args) - print_event( - "snapshot", - { - "snapshot": snapshot, - "state_file": str(state_path), - "next_poll_seconds": poll_seconds, - }, - ) - actions = set(snapshot.get("actions") or []) - if ( - "stop_pr_closed" in actions - or "stop_exhausted_retries" in actions - ): - print_event("stop", {"actions": snapshot.get("actions"), "pr": snapshot.get("pr")}) - return 0 - - current_change_key = snapshot_change_key(snapshot) - changed = current_change_key != last_change_key - green = is_ci_green(snapshot) - pr = snapshot.get("pr") or {} - pr_open = not bool(pr.get("closed")) and not bool(pr.get("merged")) - - if not green or pr_open: - poll_seconds = args.poll_seconds - elif changed or last_change_key is None: - poll_seconds = args.poll_seconds - - last_change_key = current_change_key - time.sleep(poll_seconds) - - -def main(): - args = parse_args() - try: - if args.retry_failed_now: - print_json(retry_failed_now(args)) - return 0 - if args.watch: - return run_watch(args) - snapshot, state_path = collect_snapshot(args) - snapshot["state_file"] = str(state_path) - print_json(snapshot) - return 0 - except (GhCommandError, RuntimeError, ValueError) as err: - sys.stderr.write(f"gh_pr_watch.py error: {err}\n") - return 1 - except KeyboardInterrupt: - sys.stderr.write("gh_pr_watch.py interrupted\n") - return 130 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/reference/openai-codex/.codex/skills/babysit-pr/scripts/test_gh_pr_watch.py b/reference/openai-codex/.codex/skills/babysit-pr/scripts/test_gh_pr_watch.py deleted file mode 100644 index b636ee4..0000000 --- a/reference/openai-codex/.codex/skills/babysit-pr/scripts/test_gh_pr_watch.py +++ /dev/null @@ -1,217 +0,0 @@ -import argparse -import importlib.util -from pathlib import Path - -import pytest - - -MODULE_PATH = Path(__file__).with_name("gh_pr_watch.py") -MODULE_SPEC = importlib.util.spec_from_file_location("gh_pr_watch", MODULE_PATH) -gh_pr_watch = importlib.util.module_from_spec(MODULE_SPEC) -assert MODULE_SPEC.loader is not None -MODULE_SPEC.loader.exec_module(gh_pr_watch) - - -def sample_pr(): - return { - "number": 123, - "url": "https://github.com/openai/codex/pull/123", - "repo": "openai/codex", - "head_sha": "abc123", - "head_branch": "feature", - "state": "OPEN", - "merged": False, - "closed": False, - "mergeable": "MERGEABLE", - "merge_state_status": "CLEAN", - "review_decision": "", - } - - -def sample_checks(**overrides): - checks = { - "pending_count": 0, - "failed_count": 0, - "passed_count": 12, - "all_terminal": True, - } - checks.update(overrides) - return checks - - -def test_collect_snapshot_fetches_review_items_before_ci(monkeypatch, tmp_path): - call_order = [] - pr = sample_pr() - - monkeypatch.setattr(gh_pr_watch, "resolve_pr", lambda *args, **kwargs: pr) - monkeypatch.setattr(gh_pr_watch, "load_state", lambda path: ({}, True)) - monkeypatch.setattr( - gh_pr_watch, - "get_authenticated_login", - lambda: call_order.append("auth") or "octocat", - ) - monkeypatch.setattr( - gh_pr_watch, - "fetch_new_review_items", - lambda *args, **kwargs: call_order.append("review") or [], - ) - monkeypatch.setattr( - gh_pr_watch, - "get_pr_checks", - lambda *args, **kwargs: call_order.append("checks") or [], - ) - monkeypatch.setattr( - gh_pr_watch, - "summarize_checks", - lambda checks: call_order.append("summarize") or sample_checks(), - ) - monkeypatch.setattr( - gh_pr_watch, - "get_workflow_runs_for_sha", - lambda *args, **kwargs: call_order.append("workflow") or [], - ) - monkeypatch.setattr( - gh_pr_watch, - "failed_runs_from_workflow_runs", - lambda *args, **kwargs: call_order.append("failed_runs") or [], - ) - monkeypatch.setattr( - gh_pr_watch, - "failed_jobs_from_workflow_runs", - lambda *args, **kwargs: call_order.append("failed_jobs") or [], - ) - monkeypatch.setattr( - gh_pr_watch, - "recommend_actions", - lambda *args, **kwargs: call_order.append("recommend") or ["idle"], - ) - monkeypatch.setattr(gh_pr_watch, "save_state", lambda *args, **kwargs: None) - - args = argparse.Namespace( - pr="123", - repo=None, - state_file=str(tmp_path / "watcher-state.json"), - max_flaky_retries=3, - ) - - gh_pr_watch.collect_snapshot(args) - - assert call_order.index("review") < call_order.index("checks") - assert call_order.index("review") < call_order.index("workflow") - - -def test_recommend_actions_prioritizes_review_comments(): - actions = gh_pr_watch.recommend_actions( - sample_pr(), - sample_checks(failed_count=1), - [{"run_id": 99}], - [], - [{"kind": "review_comment", "id": "1"}], - 0, - 3, - ) - - assert actions == [ - "process_review_comment", - "diagnose_ci_failure", - "retry_failed_checks", - ] - - -def test_run_watch_keeps_polling_open_ready_to_merge_pr(monkeypatch): - sleeps = [] - events = [] - snapshot = { - "pr": sample_pr(), - "checks": sample_checks(), - "failed_runs": [], - "failed_jobs": [], - "new_review_items": [], - "actions": ["ready_to_merge"], - "retry_state": { - "current_sha_retries_used": 0, - "max_flaky_retries": 3, - }, - } - - monkeypatch.setattr( - gh_pr_watch, - "collect_snapshot", - lambda args: (snapshot, Path("/tmp/codex-babysit-pr-state.json")), - ) - monkeypatch.setattr( - gh_pr_watch, - "print_event", - lambda event, payload: events.append((event, payload)), - ) - - class StopWatch(Exception): - pass - - def fake_sleep(seconds): - sleeps.append(seconds) - if len(sleeps) >= 2: - raise StopWatch - - monkeypatch.setattr(gh_pr_watch.time, "sleep", fake_sleep) - - with pytest.raises(StopWatch): - gh_pr_watch.run_watch(argparse.Namespace(poll_seconds=30)) - - assert sleeps == [30, 30] - assert [event for event, _ in events] == ["snapshot", "snapshot"] - - -def test_failed_jobs_include_direct_logs_endpoint(monkeypatch): - jobs_by_run = { - 99: [ - { - "id": 555, - "name": "unit tests", - "status": "completed", - "conclusion": "failure", - "html_url": "https://github.com/openai/codex/actions/runs/99/job/555", - }, - { - "id": 556, - "name": "lint", - "status": "completed", - "conclusion": "success", - }, - ] - } - - monkeypatch.setattr( - gh_pr_watch, - "get_jobs_for_run", - lambda repo, run_id: jobs_by_run[run_id], - ) - - failed_jobs = gh_pr_watch.failed_jobs_from_workflow_runs( - "openai/codex", - [ - { - "id": 99, - "name": "CI", - "status": "in_progress", - "conclusion": "", - "head_sha": "abc123", - } - ], - "abc123", - ) - - assert failed_jobs == [ - { - "run_id": 99, - "workflow_name": "CI", - "run_status": "in_progress", - "run_conclusion": "", - "job_id": 555, - "job_name": "unit tests", - "status": "completed", - "conclusion": "failure", - "html_url": "https://github.com/openai/codex/actions/runs/99/job/555", - "logs_endpoint": "repos/openai/codex/actions/jobs/555/logs", - } - ] diff --git a/reference/openai-codex/.codex/skills/code-review-breaking-changes/SKILL.md b/reference/openai-codex/.codex/skills/code-review-breaking-changes/SKILL.md deleted file mode 100644 index d0bddf2..0000000 --- a/reference/openai-codex/.codex/skills/code-review-breaking-changes/SKILL.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: code-breaking-changes -description: Breaking changes ---- - -Search for breaking changes in external integration surfaces: -- app-server APIs -- CLI parameters -- configuration loading -- resuming sessions from existing rollouts - -Do not stop after finding one issue; analyze all possible ways breaking changes can happen. diff --git a/reference/openai-codex/.codex/skills/code-review-change-size/SKILL.md b/reference/openai-codex/.codex/skills/code-review-change-size/SKILL.md deleted file mode 100644 index 4e8048d..0000000 --- a/reference/openai-codex/.codex/skills/code-review-change-size/SKILL.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: code-review-change-size -description: Change size guidance (800 lines) ---- - -Unless the change is mechanical the total number of changed lines should not exceed 800 lines. -For complex logic changes the size should be under 500 lines. - -If the change is larger, explain whether it can be split into reviewable stages and identify the smallest coherent stage to land first. -Base the staging suggestion on the actual diff, dependencies, and affected call sites. - diff --git a/reference/openai-codex/.codex/skills/code-review-context/SKILL.md b/reference/openai-codex/.codex/skills/code-review-context/SKILL.md deleted file mode 100644 index 7faf3d7..0000000 --- a/reference/openai-codex/.codex/skills/code-review-context/SKILL.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: code-review-context -description: Model visible context ---- - -Codex maintains a context (history of messages) that is sent to the model in inference requests. - -1. No history rewrite - the context must be built up incrementally. -2. Avoid frequent changes to context that cause cache misses. -3. No unbounded items - everything injected in the model context must have a bounded size and a hard cap. -4. No items larger than 10K tokens. -5. Highlight new individual items that can cross >1k tokens as P0. These need an additional manual review. -6. All injected fragments must be defined as structs in `core/context` and implement ContextualUserFragment trait \ No newline at end of file diff --git a/reference/openai-codex/.codex/skills/code-review-testing/SKILL.md b/reference/openai-codex/.codex/skills/code-review-testing/SKILL.md deleted file mode 100644 index c8d99e1..0000000 --- a/reference/openai-codex/.codex/skills/code-review-testing/SKILL.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: code-review-testing -description: Test authoring guidance ---- - -For agent changes prefer integration tests over unit tests. Integration tests are under `core/suite` and use `test_codex` to set up a test instance of codex. - -Features that change the agent logic MUST add an integration test: -- Provide a list of major logic changes and user-facing behaviors that need to be tested. - -If unit tests are needed, put them in a dedicated test file (*_tests.rs). -Avoid test-only functions in the main implementation. - -Check whether there are existing helpers to make tests more streamlined and readable. diff --git a/reference/openai-codex/.codex/skills/code-review/SKILL.md b/reference/openai-codex/.codex/skills/code-review/SKILL.md deleted file mode 100644 index eec0787..0000000 --- a/reference/openai-codex/.codex/skills/code-review/SKILL.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: code-review -description: Run a final code review on a pull request ---- - -Use subagents to review code using all code-review-* skills in this repository other than this orchestrator. One subagent per skill. Pass full skill path to subagents. Use xhigh reasoning. - -You must return every single issue from every subagent. You can return an unlimited number of findings. -Use raw Markdown to report findings. -Number findings for ease of reference. -Each finding must include a specific file path and line number. - -If the GitHub user running the review is the owner of the pull request add a `code-reviewed` label. -Do not leave GitHub comments unless explicitly asked. diff --git a/reference/openai-codex/.codex/skills/codex-bug/SKILL.md b/reference/openai-codex/.codex/skills/codex-bug/SKILL.md deleted file mode 100644 index c7a688e..0000000 --- a/reference/openai-codex/.codex/skills/codex-bug/SKILL.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: codex-bug -description: Diagnose GitHub bug reports in openai/codex. Use when given a GitHub issue URL from openai/codex and asked to decide next steps such as verifying against the repo, requesting more info, or explaining why it is not a bug; follow any additional user-provided instructions. ---- - -# Codex Bug - -## Overview - -Diagnose a Codex GitHub bug report and decide the next action: verify against sources, request more info, or explain why it is not a bug. - -## Workflow - -1. Confirm the input - -- Require a GitHub issue URL that points to `github.com/openai/codex/issues/…`. -- If the URL is missing or not in the right repo, ask the user for the correct link. - -2. Network access - -- Always access the issue over the network immediately, even if you think access is blocked or unavailable. -- Prefer the GitHub API over HTML pages because the HTML is noisy: - - Issue: `https://api.github.com/repos/openai/codex/issues/` - - Comments: `https://api.github.com/repos/openai/codex/issues//comments` -- If the environment requires explicit approval, request it on demand via the tool and continue without additional user prompting. -- Only if the network attempt fails after requesting approval, explain what you can do offline (e.g., draft a response template) and ask how to proceed. - -3. Read the issue - -- Use the GitHub API responses (issue + comments) as the source of truth rather than scraping the HTML issue page. -- Extract: title, body, repro steps, expected vs actual, environment, logs, and any attachments. -- Note whether the report already includes logs or session details. -- If the report includes a thread ID, mention it in the summary and use it to look up the logs and session details if you have access to them. - -4. Summarize the bug before investigating - -- Before inspecting code, docs, or logs in depth, write a short summary of the report in your own words. -- Include the reported behavior, expected behavior, repro steps, environment, and what evidence is already attached or missing. - -5. Decide the course of action - -- **Verify with sources** when the report is specific and likely reproducible. Inspect relevant Codex files (or mention the files to inspect if access is unavailable). -- **Request more information** when the report is vague, missing repro steps, or lacks logs/environment. -- **Explain not a bug** when the report contradicts current behavior or documented constraints (cite the evidence from the issue and any local sources you checked). - -6. Respond - -- Provide a concise report of your findings and next steps. diff --git a/reference/openai-codex/.codex/skills/codex-issue-digest/SKILL.md b/reference/openai-codex/.codex/skills/codex-issue-digest/SKILL.md deleted file mode 100644 index bae4bf9..0000000 --- a/reference/openai-codex/.codex/skills/codex-issue-digest/SKILL.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -name: codex-issue-digest -description: Run a GitHub issue digest for openai/codex by feature-area labels, all areas, and configurable time windows. Use when asked to summarize recent Codex bug reports or enhancement requests, especially for owner-specific labels such as tui, exec, app, or similar areas. ---- - -# Codex Issue Digest - -## Objective - -Produce a headline-first, insight-oriented digest of `openai/codex` issues for the requested feature-area labels over the previous 24 hours by default. Honor a different duration when the user asks for one, for example "past week" or "48 hours". Default to a summary-only response; include details only when requested. - -Include only issues that currently have `bug` or `enhancement` plus at least one requested owner label. If the user asks for all areas or all labels, collect `bug`/`enhancement` issues across all labels. - -## Inputs - -- Feature-area labels, for example `tui exec` -- `all areas` / `all labels` to scan all current feature labels -- Optional repo override, default `openai/codex` -- Optional time window, default previous 24 hours; examples: `48h`, `7d`, `1w`, `past week` - -## Workflow - -1. Run the collector from a current Codex repo checkout: - -```bash -python3 .codex/skills/codex-issue-digest/scripts/collect_issue_digest.py --labels tui exec --window-hours 24 -``` - -Use `--window "past week"` or `--window-hours 168` when the user asks for a non-default duration. Use `--all-labels` when the user says all areas or all labels. - -2. Use the JSON as the source of truth. It includes new issues, new issue comments, new reactions/upvotes, current labels, current reaction counts, model-ready `summary_inputs`, and detailed `digest_rows`. -3. Choose the output mode from the user's request: - - Default mode: start the report with `## Summary` and do not emit `## Details`. - - Details-upfront mode: if the user asks for details, a table, a full digest, "include details", or similar, start with `## Summary`, then include `## Details`. - - Follow-up details mode: if the user asks for more detail after a summary-only digest, produce `## Details` from the existing collector JSON when it is still available; otherwise rerun the collector. -4. In `## Summary`, write a headline-first executive summary: - - The first nonblank line under `## Summary` must be a single-line headline or judgment, not a bullet. It should be useful even if the reader stops there. - - On quiet days, prefer exactly: `No major issues reported by users.` Use this when there are no elevated rows, no newly repeated theme, and nothing that needs owner action. - - When users are surfacing notable issues, make the headline name the count or theme, for example `Two issues are being surfaced by users:`. - - Immediately under an active headline, list only the issues or themes driving attention, ordered by importance. Start each line with the row's `attention_marker` when present, then a concise owner-readable description and inline issue refs. - - Treat `🔥🔥` as headline-worthy and `🔥` as elevated. Do not add fire emoji yourself; only copy the row's `attention_marker`. - - Keep any extra summary detail after the headline to 1-3 terse lines, only when it adds a decision-relevant caveat, repeated theme, or owner action. - - Do not include routine counts, broad stats, or low-signal table summaries in `## Summary` unless they change the headline. Put metadata and optional counts in `## Details` or the footer. - - In default mode, end the report with a concise prompt such as `Want details? I can expand this into the issue table.` Keep this separate from the summary headline so the headline stays clean. - - Cluster and name themes yourself from `summary_inputs`; the collector intentionally does not hard-code issue categories. - - Use a cluster only when the issues genuinely share the same product problem. If several issues merely share a broad platform or label, describe them individually. - - Do not omit a repeated theme just because its individual issues fall below the details table cutoff. Several similar reports should be called out as a repeated customer concern. - - For single-issue rows, summarize the concern directly instead of calling it a cluster. - - Use inline numbered issue links from each relevant row's `ref_markdown`. - - Example quiet summary: - -```markdown -## Summary -No major issues reported by users. - -Source: collector v5, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`. -Want details? I can expand this into the issue table. -``` - - - Example active summary: - -```markdown -## Summary -Two issues are being surfaced by users: -🔥🔥 Terminal launch hangs on startup [1](https://github.com/openai/codex/issues/123) -🔥 Resume switches model providers unexpectedly [2](https://github.com/openai/codex/issues/456) - -Source: collector v5, git `abc123def456`, window `2026-04-27T00:00:00Z` to `2026-04-28T00:00:00Z`. -Want details? I can expand this into the issue table. -``` -5. In `## Details`, when details are requested, include a compact table only when useful: - - Prefer rows from `digest_rows`; include a `Refs` column using each row's `ref_markdown`. - - Keep the table short; omit low-signal rows when the summary already covers them. - - Use compact columns such as marker, area, type, description, interactions, and refs. - - The `Description` cell should be a short owner-readable phrase. Use row `description`, title, body excerpts, and recent comments, but do not mechanically copy the raw GitHub issue title when it contains incidental details. - - A clear quiet/no-concern sentence when there is no meaningful signal. -6. Use the JSON `attention_marker` exactly. It is empty for normal rows, `🔥` for elevated rows, and `🔥🔥` for very high-attention rows. The actual cutoffs are in `attention_thresholds`. -7. Use inline numbered references where a row or bullet points to issues, for example `Compaction bugs [1](https://github.com/openai/codex/issues/123), [2](https://github.com/openai/codex/issues/456)`. Do not add a separate footnotes section. -8. Label `interactions` as `Interactions`; it counts unique human GitHub users who created a new issue, added a new comment, or reacted during the requested window. Multiple posts/reactions from the same user on the same issue count once. -9. Mention the collector `script_version`, repo checkout `git_head`, and time window in one compact source line. In default mode, put this before the details prompt so the final line still asks whether the user wants details. In details-upfront mode, it can be the footer. - -## Reaction Handling - -The collector uses GitHub reactions endpoints, which include `created_at`, to count reactions created during the digest window for hydrated issues. It reports both in-window reaction counts and current reaction totals. Treat current reaction totals as standing engagement, and treat `new_reactions` / `new_upvotes` as windowed activity. - -By default, the collector fetches issue comments with `since=` and caps the number of comment pages per issue. This keeps very long historical threads from dominating a digest run and focuses the report on recent posts. Use `--fetch-all-comments` only when exhaustive comment history is more important than runtime. - -GitHub issue search is still seeded by issue `updated_at`, so a purely reaction-only issue may be missed if reactions do not bump `updated_at`. Covering every reaction-only case would require either a persisted snapshot store or a broader scan of labeled issues. - -## Attention Markers - -The collector scales attention markers by the requested time window. The baseline is 5 unique human users for `🔥` and 10 unique human users for `🔥🔥` over 24 hours; longer or shorter windows scale those cutoffs linearly and round up. For example, a one-week report uses 35 and 70 interactions. Unique human users are users who authored a new issue, authored a new comment, or reacted during the window, including upvotes. Multiple actions from the same user on the same issue count once. Bot posts and bot reactions are excluded. In prose, explain this as high user interaction rather than naming the emoji. - -## Freshness - -The automation should run from a repo checkout that contains this skill. For shared daily use, prefer one of these patterns: - -- Run the automation in a checkout that is refreshed before the automation starts, for example with `git pull --ff-only`. -- If the automation cannot safely mutate the checkout, have it report the current `git_head` from the collector output so readers know which skill/script version produced the digest. - -## Sample Owner Prompt - -```text -Use $codex-issue-digest to run the Codex issue digest for labels tui and exec over the previous 24 hours. -``` - -```text -Use $codex-issue-digest to run the Codex issue digest for all areas over the past week. -``` - -## Validation - -Dry run the collector against recent issues: - -```bash -python3 .codex/skills/codex-issue-digest/scripts/collect_issue_digest.py --labels tui exec --window-hours 24 -``` - -```bash -python3 .codex/skills/codex-issue-digest/scripts/collect_issue_digest.py --all-labels --window "past week" --limit-issues 10 -``` - -Run the focused script tests: - -```bash -pytest .codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py -``` diff --git a/reference/openai-codex/.codex/skills/codex-issue-digest/agents/openai.yaml b/reference/openai-codex/.codex/skills/codex-issue-digest/agents/openai.yaml deleted file mode 100644 index 706ce5e..0000000 --- a/reference/openai-codex/.codex/skills/codex-issue-digest/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Codex Issue Digest" - short_description: "Summarize Codex issues by labels or all areas" - default_prompt: "Use $codex-issue-digest to run the Codex issue digest for labels tui and exec over the previous 24 hours." diff --git a/reference/openai-codex/.codex/skills/codex-issue-digest/scripts/collect_issue_digest.py b/reference/openai-codex/.codex/skills/codex-issue-digest/scripts/collect_issue_digest.py deleted file mode 100755 index 80b54d6..0000000 --- a/reference/openai-codex/.codex/skills/codex-issue-digest/scripts/collect_issue_digest.py +++ /dev/null @@ -1,1013 +0,0 @@ -#!/usr/bin/env python3 -"""Collect recent openai/codex issue activity for owner-focused digests.""" - -import argparse -import json -import math -import re -import subprocess -import sys -from datetime import datetime, timedelta, timezone -from pathlib import Path -from urllib.parse import quote - -SCRIPT_VERSION = 5 -QUALIFYING_KIND_LABELS = ("bug", "enhancement") -REACTION_KEYS = ("+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes") -BASE_ATTENTION_WINDOW_HOURS = 24.0 -ONE_ATTENTION_INTERACTION_THRESHOLD = 5 -TWO_ATTENTION_INTERACTION_THRESHOLD = 10 -ALL_LABEL_PHRASES = {"all", "all areas", "all labels", "all-areas", "all-labels", "*"} - - -class GhCommandError(RuntimeError): - pass - - -def parse_args(): - parser = argparse.ArgumentParser( - description="Collect recent GitHub issue activity for a Codex owner digest." - ) - parser.add_argument( - "--repo", default="openai/codex", help="OWNER/REPO, default openai/codex" - ) - parser.add_argument( - "--labels", - nargs="+", - default=[], - help="Feature-area labels owned by the digest recipient, for example: tui exec", - ) - parser.add_argument( - "--all-labels", - action="store_true", - help="Collect bug/enhancement issues across all feature-area labels", - ) - parser.add_argument( - "--window", - help='Lookback duration such as "24h", "7d", "1w", or "past week"', - ) - parser.add_argument( - "--window-hours", type=float, default=24.0, help="Lookback window" - ) - parser.add_argument( - "--since", help="UTC ISO timestamp override for the window start" - ) - parser.add_argument("--until", help="UTC ISO timestamp override for the window end") - parser.add_argument( - "--limit-issues", - type=int, - default=200, - help="Maximum candidate issues to hydrate after search", - ) - parser.add_argument( - "--body-chars", type=int, default=1200, help="Issue body excerpt length" - ) - parser.add_argument( - "--comment-chars", type=int, default=900, help="Comment excerpt length" - ) - parser.add_argument( - "--max-comment-pages", - type=int, - default=3, - help=( - "Maximum pages of issue comments to hydrate per issue after applying the " - "window filter. Use 0 with --fetch-all-comments for no page cap." - ), - ) - parser.add_argument( - "--fetch-all-comments", - action="store_true", - help="Hydrate complete issue comment histories instead of only window-updated comments.", - ) - return parser.parse_args() - - -def parse_timestamp(value, arg_name): - if value is None: - return None - normalized = value.strip() - if not normalized: - return None - if normalized.endswith("Z"): - normalized = f"{normalized[:-1]}+00:00" - try: - parsed = datetime.fromisoformat(normalized) - except ValueError as err: - raise ValueError(f"{arg_name} must be an ISO timestamp") from err - if parsed.tzinfo is None: - parsed = parsed.replace(tzinfo=timezone.utc) - return parsed.astimezone(timezone.utc) - - -def format_timestamp(value): - return ( - value.astimezone(timezone.utc) - .replace(microsecond=0) - .isoformat() - .replace("+00:00", "Z") - ) - - -def resolve_window(args): - until = parse_timestamp(args.until, "--until") or datetime.now(timezone.utc) - since = parse_timestamp(args.since, "--since") - if since is None: - hours = parse_duration_hours(getattr(args, "window", None)) - if hours is None: - hours = getattr(args, "window_hours", 24.0) - if hours <= 0: - raise ValueError("window duration must be > 0") - since = until - timedelta(hours=hours) - if since >= until: - raise ValueError("--since must be before --until") - return since, until - - -def parse_duration_hours(value): - if value is None: - return None - text = value.strip().casefold().replace("_", " ") - if not text: - return None - text = re.sub(r"^(past|last)\s+", "", text) - aliases = { - "day": 24.0, - "24h": 24.0, - "week": 168.0, - "7d": 168.0, - } - if text in aliases: - return aliases[text] - match = re.fullmatch(r"(\d+(?:\.\d+)?)\s*(h|hr|hrs|hour|hours)", text) - if match: - return float(match.group(1)) - match = re.fullmatch(r"(\d+(?:\.\d+)?)\s*(d|day|days)", text) - if match: - return float(match.group(1)) * 24.0 - match = re.fullmatch(r"(\d+(?:\.\d+)?)\s*(w|week|weeks)", text) - if match: - return float(match.group(1)) * 168.0 - raise ValueError(f"Unsupported duration: {value}") - - -def normalize_requested_labels(labels, all_labels=False): - out = [] - seen = set() - for raw in labels: - for piece in raw.split(","): - label = piece.strip() - if not label: - continue - key = label.casefold() - if key not in seen: - out.append(label) - seen.add(key) - phrase = " ".join(label.casefold() for label in out) - if all_labels or phrase in ALL_LABEL_PHRASES: - return [], True - if not out: - raise ValueError( - "At least one feature-area label is required, or use --all-labels" - ) - return out, False - - -def quote_label(label): - if re.fullmatch(r"[A-Za-z0-9_.:-]+", label): - return f"label:{label}" - escaped = label.replace('"', '\\"') - return f'label:"{escaped}"' - - -def build_search_queries( - repo, owner_labels, since, kind_labels=QUALIFYING_KIND_LABELS, all_labels=False -): - since_date = since.date().isoformat() - queries = [] - if all_labels: - for kind_label in kind_labels: - queries.append( - " ".join( - [ - f"repo:{repo}", - "is:issue", - f"updated:>={since_date}", - quote_label(kind_label), - ] - ) - ) - return queries - for owner_label in owner_labels: - for kind_label in kind_labels: - queries.append( - " ".join( - [ - f"repo:{repo}", - "is:issue", - f"updated:>={since_date}", - quote_label(owner_label), - quote_label(kind_label), - ] - ) - ) - return queries - - -def _format_gh_error(cmd, err): - stdout = (err.stdout or "").strip() - stderr = (err.stderr or "").strip() - parts = [f"GitHub CLI command failed: {' '.join(cmd)}"] - if stdout: - parts.append(f"stdout: {stdout}") - if stderr: - parts.append(f"stderr: {stderr}") - return "\n".join(parts) - - -def gh_json(args): - cmd = ["gh", *args] - try: - proc = subprocess.run(cmd, check=True, capture_output=True, text=True) - except FileNotFoundError as err: - raise GhCommandError("`gh` command not found") from err - except subprocess.CalledProcessError as err: - raise GhCommandError(_format_gh_error(cmd, err)) from err - raw = proc.stdout.strip() - if not raw: - return None - try: - return json.loads(raw) - except json.JSONDecodeError as err: - raise GhCommandError( - f"Failed to parse JSON from gh output for {' '.join(args)}" - ) from err - - -def gh_text(args): - cmd = ["gh", *args] - try: - proc = subprocess.run(cmd, check=True, capture_output=True, text=True) - except (FileNotFoundError, subprocess.CalledProcessError): - return "" - return proc.stdout.strip() - - -def git_head(): - try: - proc = subprocess.run( - ["git", "rev-parse", "--short=12", "HEAD"], - check=True, - capture_output=True, - text=True, - ) - except (FileNotFoundError, subprocess.CalledProcessError): - return None - return proc.stdout.strip() or None - - -def skill_relative_path(): - try: - return str(Path(__file__).resolve().relative_to(Path.cwd().resolve())) - except ValueError: - return str(Path(__file__).resolve()) - - -def gh_api_list_paginated(endpoint, per_page=100, max_pages=None, with_metadata=False): - items = [] - page = 1 - truncated = False - while True: - sep = "&" if "?" in endpoint else "?" - page_endpoint = f"{endpoint}{sep}per_page={per_page}&page={page}" - payload = gh_json(["api", page_endpoint]) - if payload is None: - break - if not isinstance(payload, list): - raise GhCommandError(f"Unexpected paginated payload from gh api {endpoint}") - items.extend(payload) - if len(payload) < per_page: - break - if max_pages is not None and page >= max_pages: - truncated = True - break - page += 1 - if with_metadata: - return { - "items": items, - "truncated": truncated, - "pages": page, - "max_pages": max_pages, - } - return items - - -def search_issue_numbers(queries, limit): - numbers = {} - for query in queries: - page = 1 - seen_for_query = 0 - while True: - payload = gh_json( - [ - "api", - "search/issues", - "-X", - "GET", - "-f", - f"q={query}", - "-f", - "sort=updated", - "-f", - "order=desc", - "-f", - "per_page=100", - "-f", - f"page={page}", - ] - ) - if not isinstance(payload, dict): - raise GhCommandError("Unexpected payload from GitHub issue search") - items = payload.get("items") or [] - if not isinstance(items, list): - raise GhCommandError("Expected search `items` to be a list") - for item in items: - if not isinstance(item, dict): - continue - number = item.get("number") - if isinstance(number, int): - numbers[number] = str(item.get("updated_at") or "") - seen_for_query += 1 - if len(items) < 100 or seen_for_query >= limit: - break - page += 1 - ordered = sorted( - numbers, key=lambda number: (numbers[number], number), reverse=True - ) - return ordered[:limit] - - -def fetch_issue(repo, number): - payload = gh_json(["api", f"repos/{repo}/issues/{number}"]) - if not isinstance(payload, dict): - raise GhCommandError(f"Unexpected issue payload for #{number}") - return payload - - -def fetch_comments(repo, number, since=None, max_pages=None): - endpoint = f"repos/{repo}/issues/{number}/comments" - if since is not None: - endpoint = f"{endpoint}?since={quote(format_timestamp(since), safe='')}" - return gh_api_list_paginated( - endpoint, - max_pages=max_pages, - with_metadata=True, - ) - - -def fetch_reactions_for_item(endpoint, item): - if reaction_summary(item)["total"] <= 0: - return [] - return gh_api_list_paginated(endpoint) - - -def fetch_comment_reactions(repo, comments): - reactions_by_comment_id = {} - for comment in comments: - comment_id = comment.get("id") - if comment_id in (None, ""): - continue - endpoint = f"repos/{repo}/issues/comments/{comment_id}/reactions" - reactions_by_comment_id[comment_id] = fetch_reactions_for_item( - endpoint, comment - ) - return reactions_by_comment_id - - -def extract_login(user_obj): - if isinstance(user_obj, dict): - return str(user_obj.get("login") or "") - return "" - - -def is_bot_login(login): - return bool(login) and login.lower().endswith("[bot]") - - -def human_login_key(user_obj): - login = extract_login(user_obj) - if not login or is_bot_login(login): - return "" - return login.casefold() - - -def is_human_user(user_obj): - return bool(human_login_key(user_obj)) - - -def label_names(issue): - labels = [] - for label in issue.get("labels") or []: - if isinstance(label, dict) and label.get("name"): - labels.append(str(label["name"])) - return sorted(labels, key=str.casefold) - - -def matching_labels(labels, requested): - labels_by_key = {label.casefold(): label for label in labels} - return [label for label in requested if label.casefold() in labels_by_key] - - -def area_labels(labels): - kind_keys = {label.casefold() for label in QUALIFYING_KIND_LABELS} - return [label for label in labels if label.casefold() not in kind_keys] - - -def attention_thresholds_for_window(window_hours): - if window_hours <= 0: - raise ValueError("window_hours must be > 0") - window_hours = round(window_hours, 6) - scale = window_hours / BASE_ATTENTION_WINDOW_HOURS - elevated = max(1, math.ceil(ONE_ATTENTION_INTERACTION_THRESHOLD * scale)) - very_high = max( - elevated + 1, math.ceil(TWO_ATTENTION_INTERACTION_THRESHOLD * scale) - ) - return { - "base_window_hours": BASE_ATTENTION_WINDOW_HOURS, - "window_hours": round(window_hours, 3), - "scale": round(scale, 3), - "elevated": elevated, - "very_high": very_high, - } - - -def attention_level_for(user_interactions, attention_thresholds=None): - thresholds = attention_thresholds or attention_thresholds_for_window( - BASE_ATTENTION_WINDOW_HOURS - ) - if user_interactions >= thresholds["very_high"]: - return 2 - if user_interactions >= thresholds["elevated"]: - return 1 - return 0 - - -def attention_marker_for(user_interactions, attention_thresholds=None): - return "🔥" * attention_level_for(user_interactions, attention_thresholds) - - -def reaction_summary(item): - reactions = item.get("reactions") - if not isinstance(reactions, dict): - return {"total": 0, "counts": {}} - counts = {} - for key in REACTION_KEYS: - value = reactions.get(key, 0) - if isinstance(value, int) and value: - counts[key] = value - total = reactions.get("total_count") - if not isinstance(total, int): - total = sum(counts.values()) - return {"total": total, "counts": counts} - - -def reaction_event_summary(reactions, since, until): - counts = {} - total = 0 - users = set() - for reaction in reactions or []: - if not isinstance(reaction, dict): - continue - if not is_in_window(str(reaction.get("created_at") or ""), since, until): - continue - user_key = human_login_key(reaction.get("user")) - if not user_key: - continue - content = str(reaction.get("content") or "") - if not content: - continue - counts[content] = counts.get(content, 0) + 1 - total += 1 - users.add(user_key) - return { - "total": total, - "counts": counts, - "upvotes": counts.get("+1", 0), - "users": sorted(users, key=str.casefold), - } - - -def compact_text(value, limit): - text = re.sub(r"\s+", " ", str(value or "")).strip() - if limit <= 0: - return "" - if len(text) <= limit: - return text - return f"{text[: max(limit - 1, 0)].rstrip()}..." - - -def clean_title_for_description(title): - cleaned = re.sub(r"\s+", " ", str(title or "")).strip() - cleaned = re.sub( - r"^(codex(?: desktop| app|\.app| cli)?|desktop|windows codex app)\s*[:,-]\s*", - "", - cleaned, - flags=re.IGNORECASE, - ) - cleaned = re.sub(r"^on windows,\s*", "Windows: ", cleaned, flags=re.IGNORECASE) - cleaned = cleaned.strip(" -:;") - return compact_text(cleaned, 80) or "Issue needs owner review" - - -def issue_description(issue): - return clean_title_for_description(issue.get("title")) - - -def is_in_window(timestamp, since, until): - parsed = parse_timestamp(timestamp, "timestamp") - if parsed is None: - return False - return since <= parsed < until - - -def summarize_comment( - comment, comment_chars, reaction_events=None, since=None, until=None -): - reactions = reaction_summary(comment) - new_reactions = ( - reaction_event_summary(reaction_events, since, until) - if since is not None and until is not None - else {"total": 0, "counts": {}, "upvotes": 0} - ) - human_user_interaction = is_human_user(comment.get("user")) - return { - "id": comment.get("id"), - "author": extract_login(comment.get("user")), - "author_association": str(comment.get("author_association") or ""), - "created_at": str(comment.get("created_at") or ""), - "updated_at": str(comment.get("updated_at") or ""), - "url": str(comment.get("html_url") or ""), - "human_user_interaction": human_user_interaction, - "reactions": reactions["counts"], - "reaction_total": reactions["total"], - "new_reactions": new_reactions["total"], - "new_upvotes": new_reactions["upvotes"], - "new_reaction_counts": new_reactions["counts"], - "body_excerpt": compact_text(comment.get("body"), comment_chars), - } - - -def summarize_issue( - issue, - comments, - requested_labels, - since, - until, - body_chars, - comment_chars, - issue_reaction_events=None, - comment_reactions_by_id=None, - all_labels=False, - comments_hydration=None, - attention_thresholds=None, -): - labels = label_names(issue) - labels_by_key = {label.casefold() for label in labels} - kind_labels = [ - label for label in QUALIFYING_KIND_LABELS if label.casefold() in labels_by_key - ] - if all_labels: - owner_labels = area_labels(labels) or ["unlabeled"] - else: - owner_labels = matching_labels(labels, requested_labels) - if not kind_labels or not owner_labels: - return None - - updated_at = str(issue.get("updated_at") or "") - if not is_in_window(updated_at, since, until): - return None - - new_issue = is_in_window(str(issue.get("created_at") or ""), since, until) - comment_reactions_by_id = comment_reactions_by_id or {} - new_comments = [ - summarize_comment( - comment, - comment_chars, - reaction_events=comment_reactions_by_id.get(comment.get("id")), - since=since, - until=until, - ) - for comment in comments - if is_in_window(str(comment.get("created_at") or ""), since, until) - ] - new_comments.sort(key=lambda item: (item["created_at"], str(item["id"]))) - - issue_reactions = reaction_summary(issue) - issue_reaction_events_summary = reaction_event_summary( - issue_reaction_events, since, until - ) - comment_reaction_events_summary = reaction_event_summary( - [ - reaction - for reactions in comment_reactions_by_id.values() - for reaction in reactions - ], - since, - until, - ) - new_reactions = ( - issue_reaction_events_summary["total"] - + comment_reaction_events_summary["total"] - ) - new_upvotes = ( - issue_reaction_events_summary["upvotes"] - + comment_reaction_events_summary["upvotes"] - ) - all_comment_reaction_total = sum( - reaction_summary(comment)["total"] for comment in comments - ) - new_comment_reaction_total = sum( - comment["reaction_total"] for comment in new_comments - ) - new_issue_user_key = human_login_key(issue.get("user")) if new_issue else "" - new_issue_user_interaction = bool(new_issue_user_key) - new_comment_user_interactions = sum( - 1 for comment in new_comments if comment["human_user_interaction"] - ) - interaction_user_keys = set(issue_reaction_events_summary["users"]) - interaction_user_keys.update(comment_reaction_events_summary["users"]) - if new_issue_user_key: - interaction_user_keys.add(new_issue_user_key) - interaction_user_keys.update( - comment["author"].casefold() - for comment in new_comments - if comment["human_user_interaction"] - ) - user_interactions = len(interaction_user_keys) - attention_level = attention_level_for(user_interactions, attention_thresholds) - attention_marker = attention_marker_for(user_interactions, attention_thresholds) - updated_without_visible_new_post = ( - not new_issue and not new_comments and new_reactions == 0 - ) - - engagement_score = ( - len(new_comments) * 3 - + new_reactions - + issue_reactions["total"] - + new_comment_reaction_total - + min(int(issue.get("comments") or len(comments) or 0), 10) - ) - - return { - "number": issue.get("number"), - "title": str(issue.get("title") or ""), - "description": issue_description(issue), - "url": str(issue.get("html_url") or ""), - "state": str(issue.get("state") or ""), - "author": extract_login(issue.get("user")), - "author_association": str(issue.get("author_association") or ""), - "created_at": str(issue.get("created_at") or ""), - "updated_at": updated_at, - "labels": labels, - "kind_labels": kind_labels, - "owner_labels": owner_labels, - "comments_total": int(issue.get("comments") or len(comments) or 0), - "comments_hydration": comments_hydration - or { - "fetched": len(comments), - "since": None, - "truncated": False, - "max_pages": None, - }, - "issue_reactions": issue_reactions["counts"], - "issue_reaction_total": issue_reactions["total"], - "comment_reaction_total": all_comment_reaction_total, - "new_comment_reaction_total": new_comment_reaction_total, - "new_issue_reactions": issue_reaction_events_summary["total"], - "new_issue_upvotes": issue_reaction_events_summary["upvotes"], - "new_comment_reactions": comment_reaction_events_summary["total"], - "new_comment_upvotes": comment_reaction_events_summary["upvotes"], - "new_reactions": new_reactions, - "new_upvotes": new_upvotes, - "user_interactions": user_interactions, - "attention": attention_level > 0, - "attention_level": attention_level, - "attention_marker": attention_marker, - "engagement_score": engagement_score, - "activity": { - "new_issue": new_issue, - "new_comments": len(new_comments), - "new_human_comments": new_comment_user_interactions, - "new_reactions": new_reactions, - "new_upvotes": new_upvotes, - "updated_without_visible_new_post": updated_without_visible_new_post, - }, - "body_excerpt": compact_text(issue.get("body"), body_chars), - "new_comments": new_comments, - } - - -def count_by_label(issues, labels): - out = {} - for label in labels: - matching = [issue for issue in issues if label in issue["owner_labels"]] - out[label] = { - "issues": len(matching), - "new_issues": sum( - 1 for issue in matching if issue["activity"]["new_issue"] - ), - "new_comments": sum( - issue["activity"]["new_comments"] for issue in matching - ), - } - return out - - -def count_by_kind(issues): - out = {} - for kind in QUALIFYING_KIND_LABELS: - matching = [issue for issue in issues if kind in issue["kind_labels"]] - out[kind] = { - "issues": len(matching), - "new_issues": sum( - 1 for issue in matching if issue["activity"]["new_issue"] - ), - "new_comments": sum( - issue["activity"]["new_comments"] for issue in matching - ), - } - return out - - -def hot_items(issues, limit=8): - ranked = sorted( - issues, - key=lambda issue: ( - issue["attention"], - issue["attention_level"], - issue["user_interactions"], - issue["engagement_score"], - issue["activity"]["new_comments"], - issue["issue_reaction_total"] + issue["comment_reaction_total"], - issue["updated_at"], - ), - reverse=True, - ) - return [ - { - "number": issue["number"], - "title": issue["title"], - "url": issue["url"], - "owner_labels": issue["owner_labels"], - "kind_labels": issue["kind_labels"], - "attention": issue["attention"], - "attention_level": issue["attention_level"], - "attention_marker": issue["attention_marker"], - "user_interactions": issue["user_interactions"], - "new_reactions": issue["new_reactions"], - "new_upvotes": issue["new_upvotes"], - "engagement_score": issue["engagement_score"], - "new_comments": issue["activity"]["new_comments"], - "reaction_total": issue["issue_reaction_total"] - + issue["comment_reaction_total"], - } - for issue in ranked[:limit] - if issue["engagement_score"] > 0 - ] - - -def ranked_digest_issues(issues): - return sorted( - issues, - key=lambda issue: ( - issue["attention"], - issue["attention_level"], - issue["user_interactions"], - issue["engagement_score"], - issue["activity"]["new_comments"], - issue["updated_at"], - ), - reverse=True, - ) - - -def digest_rows(issues, limit=10, ref_map=None): - ranked = ranked_digest_issues(issues) - if ref_map is None: - ref_map = {issue["number"]: ref for ref, issue in enumerate(ranked, start=1)} - rows = [] - for issue in ranked[:limit]: - ref = ref_map[issue["number"]] - reaction_total = issue["issue_reaction_total"] + issue["comment_reaction_total"] - rows.append( - { - "ref": ref, - "ref_markdown": f"[{ref}]({issue['url']})", - "marker": issue["attention_marker"], - "attention_marker": issue["attention_marker"], - "number": issue["number"], - "description": issue["description"], - "title": issue["title"], - "url": issue["url"], - "area": ", ".join(issue["owner_labels"]), - "kind": ", ".join(issue["kind_labels"]), - "state": issue["state"], - "interactions": issue["user_interactions"], - "user_interactions": issue["user_interactions"], - "new_reactions": issue["new_reactions"], - "new_upvotes": issue["new_upvotes"], - "current_reactions": reaction_total, - } - ) - return rows - - -def issue_ref_markdown(issue, ref_map): - ref = ref_map[issue["number"]] - return f"[{ref}]({issue['url']})" - - -def summary_inputs(issues, limit=80, ref_map=None): - ranked = ranked_digest_issues(issues) - if ref_map is None: - ref_map = {issue["number"]: ref for ref, issue in enumerate(ranked, start=1)} - rows = [] - for issue in ranked[:limit]: - rows.append( - { - "ref": ref_map[issue["number"]], - "ref_markdown": issue_ref_markdown(issue, ref_map), - "number": issue["number"], - "title": issue["title"], - "description": issue["description"], - "url": issue["url"], - "labels": issue["labels"], - "owner_labels": issue["owner_labels"], - "kind_labels": issue["kind_labels"], - "state": issue.get("state", ""), - "attention_marker": issue.get("attention_marker", ""), - "interactions": issue["user_interactions"], - "new_comments": issue["activity"].get("new_comments", 0), - "new_reactions": issue.get("new_reactions", 0), - "new_upvotes": issue.get("new_upvotes", 0), - "current_reactions": issue.get("issue_reaction_total", 0) - + issue.get("comment_reaction_total", 0), - } - ) - return rows - - -def collect_digest(args): - since, until = resolve_window(args) - window_hours = (until - since).total_seconds() / 3600 - attention_thresholds = attention_thresholds_for_window(window_hours) - requested_labels, all_labels = normalize_requested_labels( - args.labels, all_labels=args.all_labels - ) - queries = build_search_queries( - args.repo, requested_labels, since, all_labels=all_labels - ) - numbers = search_issue_numbers(queries, args.limit_issues) - gh_version_output = gh_text(["--version"]) - - issues = [] - max_comment_pages = None if args.max_comment_pages <= 0 else args.max_comment_pages - for number in numbers: - issue = fetch_issue(args.repo, number) - comments_since = None if args.fetch_all_comments else since - comments_payload = fetch_comments( - args.repo, - number, - since=comments_since, - max_pages=max_comment_pages, - ) - comments = comments_payload["items"] - issue_reaction_events = fetch_reactions_for_item( - f"repos/{args.repo}/issues/{number}/reactions", issue - ) - comment_reactions_by_id = fetch_comment_reactions(args.repo, comments) - comments_hydration = { - "fetched": len(comments), - "total": int(issue.get("comments") or len(comments) or 0), - "since": format_timestamp(comments_since) if comments_since else None, - "truncated": comments_payload["truncated"], - "max_pages": comments_payload["max_pages"], - "fetch_all_comments": args.fetch_all_comments, - } - summary = summarize_issue( - issue, - comments, - requested_labels, - since, - until, - args.body_chars, - args.comment_chars, - issue_reaction_events=issue_reaction_events, - comment_reactions_by_id=comment_reactions_by_id, - all_labels=all_labels, - comments_hydration=comments_hydration, - attention_thresholds=attention_thresholds, - ) - if summary is not None: - issues.append(summary) - - issues.sort( - key=lambda issue: (issue["updated_at"], int(issue["number"] or 0)), reverse=True - ) - totals = { - "candidate_issues": len(numbers), - "included_issues": len(issues), - "new_issues": sum(1 for issue in issues if issue["activity"]["new_issue"]), - "issues_with_new_comments": sum( - 1 for issue in issues if issue["activity"]["new_comments"] > 0 - ), - "new_comments": sum(issue["activity"]["new_comments"] for issue in issues), - "comments_fetched": sum( - issue["comments_hydration"]["fetched"] for issue in issues - ), - "issues_with_truncated_comment_hydration": sum( - 1 for issue in issues if issue["comments_hydration"]["truncated"] - ), - "updated_without_visible_new_post": sum( - 1 - for issue in issues - if issue["activity"]["updated_without_visible_new_post"] - ), - "issue_reactions_current_total": sum( - issue["issue_reaction_total"] for issue in issues - ), - "comment_reactions_current_total": sum( - issue["comment_reaction_total"] for issue in issues - ), - "new_reactions": sum(issue["new_reactions"] for issue in issues), - "new_upvotes": sum(issue["new_upvotes"] for issue in issues), - "user_interactions": sum(issue["user_interactions"] for issue in issues), - } - ranked = ranked_digest_issues(issues) - ref_map = {issue["number"]: ref for ref, issue in enumerate(ranked, start=1)} - filter_label = "all" if all_labels else requested_labels - - return { - "generated_at": format_timestamp(datetime.now(timezone.utc)), - "source": { - "repo": args.repo, - "skill": "codex-issue-digest", - "collector": skill_relative_path(), - "script_version": SCRIPT_VERSION, - "git_head": git_head(), - "gh_version": gh_version_output.splitlines()[0] - if gh_version_output - else None, - }, - "window": { - "since": format_timestamp(since), - "until": format_timestamp(until), - "hours": round(window_hours, 3), - }, - "attention_thresholds": attention_thresholds, - "filters": { - "owner_labels": filter_label, - "all_labels": all_labels, - "kind_labels": list(QUALIFYING_KIND_LABELS), - }, - "collection_notes": [ - "Issues are selected when they currently have bug or enhancement plus at least one requested owner label and were updated during the window.", - "By default, issue comments are fetched with since=window_start and a max page cap to avoid long historical threads; use --fetch-all-comments when exhaustive comment history is needed.", - "New issue comments are filtered by comment creation time within the window from the fetched comment set.", - "Reaction events are counted by GitHub reaction created_at timestamps for hydrated issues and fetched comments.", - "Current reaction totals are standing engagement signals; new_reactions and new_upvotes are windowed activity.", - "user_interactions counts unique human users per issue across new issues, new comments, and new reactions; repeated actions by the same user count once.", - "The collector does not assign semantic clusters; use summary_inputs as model-ready evidence for report-time clustering.", - "Pure reaction-only issues may be missed if GitHub issue search does not surface them via updated_at.", - "Issues updated during the window without a new issue body or new comment are retained because label/status edits can still be useful owner signals.", - ], - "totals": totals, - "by_owner_label": count_by_label( - issues, - sorted( - {area for issue in issues for area in issue["owner_labels"]}, - key=str.casefold, - ) - if all_labels - else requested_labels, - ), - "by_kind_label": count_by_kind(issues), - "hot_items": hot_items(issues), - "summary_inputs": summary_inputs(issues, ref_map=ref_map), - "digest_rows": digest_rows(issues, ref_map=ref_map), - "issues": issues, - } - - -def main(): - args = parse_args() - try: - digest = collect_digest(args) - except (GhCommandError, RuntimeError, ValueError) as err: - sys.stderr.write(f"collect_issue_digest.py error: {err}\n") - return 1 - sys.stdout.write(json.dumps(digest, indent=2, sort_keys=True) + "\n") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/reference/openai-codex/.codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py b/reference/openai-codex/.codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py deleted file mode 100644 index f1ba54e..0000000 --- a/reference/openai-codex/.codex/skills/codex-issue-digest/scripts/test_collect_issue_digest.py +++ /dev/null @@ -1,749 +0,0 @@ -import importlib.util -from datetime import timezone -from pathlib import Path - - -MODULE_PATH = Path(__file__).with_name("collect_issue_digest.py") -MODULE_SPEC = importlib.util.spec_from_file_location( - "collect_issue_digest", MODULE_PATH -) -collect_issue_digest = importlib.util.module_from_spec(MODULE_SPEC) -assert MODULE_SPEC.loader is not None -MODULE_SPEC.loader.exec_module(collect_issue_digest) - - -def test_build_search_queries_uses_each_owner_and_kind_label(): - since = collect_issue_digest.parse_timestamp("2026-04-25T12:34:56Z", "--since") - - queries = collect_issue_digest.build_search_queries( - "openai/codex", ["tui", "exec"], since - ) - - assert queries == [ - "repo:openai/codex is:issue updated:>=2026-04-25 label:tui label:bug", - "repo:openai/codex is:issue updated:>=2026-04-25 label:tui label:enhancement", - "repo:openai/codex is:issue updated:>=2026-04-25 label:exec label:bug", - "repo:openai/codex is:issue updated:>=2026-04-25 label:exec label:enhancement", - ] - - -def test_build_search_queries_can_scan_all_labels(): - since = collect_issue_digest.parse_timestamp("2026-04-25T12:34:56Z", "--since") - - queries = collect_issue_digest.build_search_queries( - "openai/codex", [], since, all_labels=True - ) - - assert queries == [ - "repo:openai/codex is:issue updated:>=2026-04-25 label:bug", - "repo:openai/codex is:issue updated:>=2026-04-25 label:enhancement", - ] - - -def test_normalize_requested_labels_accepts_all_area_phrases(): - assert collect_issue_digest.normalize_requested_labels(["all", "areas"]) == ( - [], - True, - ) - assert collect_issue_digest.normalize_requested_labels(["all-labels"]) == ( - [], - True, - ) - - -def test_search_issue_numbers_requests_updated_sort(monkeypatch): - calls = [] - - def fake_gh_json(args): - calls.append(args) - return { - "items": [ - {"number": 1, "updated_at": "2026-04-25T00:00:00Z"}, - ] - } - - monkeypatch.setattr(collect_issue_digest, "gh_json", fake_gh_json) - - assert collect_issue_digest.search_issue_numbers(["query"], limit=10) == [1] - assert "-f" in calls[0] - assert "sort=updated" in calls[0] - assert "order=desc" in calls[0] - - -def test_search_issue_numbers_applies_limit_per_query(monkeypatch): - calls = [] - - def fake_gh_json(args): - calls.append(args) - query = next( - value.removeprefix("q=") for value in args if value.startswith("q=") - ) - page = int( - next( - value.removeprefix("page=") - for value in args - if value.startswith("page=") - ) - ) - base = 10_000 if query == "first" else 20_000 - offset = (page - 1) * 100 - return { - "items": [ - { - "number": base + offset + idx, - "updated_at": f"2026-04-25T00:{idx:02d}:00Z", - } - for idx in range(100) - ] - } - - monkeypatch.setattr(collect_issue_digest, "gh_json", fake_gh_json) - - collect_issue_digest.search_issue_numbers(["first", "second"], limit=150) - - queried_pages = [ - ( - next( - value.removeprefix("q=") for value in args if value.startswith("q=") - ), - next( - value.removeprefix("page=") - for value in args - if value.startswith("page=") - ), - ) - for args in calls - ] - assert queried_pages == [ - ("first", "1"), - ("first", "2"), - ("second", "1"), - ("second", "2"), - ] - - -def test_summarize_issue_keeps_new_comments_and_reaction_signals(): - since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since") - until = collect_issue_digest.parse_timestamp("2026-04-26T00:00:00Z", "--until") - issue = { - "number": 123, - "title": "TUI does not redraw", - "html_url": "https://github.com/openai/codex/issues/123", - "state": "open", - "created_at": "2026-04-24T20:00:00Z", - "updated_at": "2026-04-25T10:00:00Z", - "user": {"login": "alice"}, - "author_association": "NONE", - "comments": 2, - "body": "The terminal freezes after resize.", - "labels": [{"name": "bug"}, {"name": "tui"}], - "reactions": {"total_count": 3, "+1": 2, "rocket": 1}, - } - comments = [ - { - "id": 1, - "created_at": "2026-04-25T11:00:00Z", - "updated_at": "2026-04-25T11:00:00Z", - "html_url": "https://github.com/openai/codex/issues/123#issuecomment-1", - "user": {"login": "bob"}, - "author_association": "MEMBER", - "body": "I can reproduce this on main.", - "reactions": {"total_count": 4, "heart": 1, "+1": 3}, - }, - { - "id": 2, - "created_at": "2026-04-24T11:00:00Z", - "updated_at": "2026-04-24T11:00:00Z", - "html_url": "https://github.com/openai/codex/issues/123#issuecomment-2", - "user": {"login": "carol"}, - "author_association": "NONE", - "body": "Older comment.", - "reactions": {"total_count": 1, "eyes": 1}, - }, - ] - - summary = collect_issue_digest.summarize_issue( - issue, - comments, - ["tui", "exec"], - since, - until, - body_chars=200, - comment_chars=200, - ) - - assert summary == { - "number": 123, - "title": "TUI does not redraw", - "description": "TUI does not redraw", - "url": "https://github.com/openai/codex/issues/123", - "state": "open", - "author": "alice", - "author_association": "NONE", - "created_at": "2026-04-24T20:00:00Z", - "updated_at": "2026-04-25T10:00:00Z", - "labels": ["bug", "tui"], - "kind_labels": ["bug"], - "owner_labels": ["tui"], - "comments_total": 2, - "comments_hydration": { - "fetched": 2, - "since": None, - "truncated": False, - "max_pages": None, - }, - "issue_reactions": {"+1": 2, "rocket": 1}, - "issue_reaction_total": 3, - "comment_reaction_total": 5, - "new_comment_reaction_total": 4, - "new_issue_reactions": 0, - "new_issue_upvotes": 0, - "new_comment_reactions": 0, - "new_comment_upvotes": 0, - "new_reactions": 0, - "new_upvotes": 0, - "user_interactions": 1, - "attention": False, - "attention_level": 0, - "attention_marker": "", - "engagement_score": 12, - "activity": { - "new_issue": False, - "new_comments": 1, - "new_human_comments": 1, - "new_reactions": 0, - "new_upvotes": 0, - "updated_without_visible_new_post": False, - }, - "body_excerpt": "The terminal freezes after resize.", - "new_comments": [ - { - "id": 1, - "author": "bob", - "author_association": "MEMBER", - "created_at": "2026-04-25T11:00:00Z", - "updated_at": "2026-04-25T11:00:00Z", - "url": "https://github.com/openai/codex/issues/123#issuecomment-1", - "human_user_interaction": True, - "reactions": {"+1": 3, "heart": 1}, - "reaction_total": 4, - "new_reactions": 0, - "new_upvotes": 0, - "new_reaction_counts": {}, - "body_excerpt": "I can reproduce this on main.", - } - ], - } - - -def test_summarize_issue_filters_non_owner_or_non_kind_labels(): - since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since") - until = collect_issue_digest.parse_timestamp("2026-04-26T00:00:00Z", "--until") - base_issue = { - "number": 1, - "title": "Question", - "created_at": "2026-04-25T01:00:00Z", - "updated_at": "2026-04-25T01:00:00Z", - "labels": [{"name": "question"}, {"name": "tui"}], - } - - assert ( - collect_issue_digest.summarize_issue( - base_issue, - [], - ["tui"], - since, - until, - body_chars=100, - comment_chars=100, - ) - is None - ) - - issue_without_owner = dict(base_issue) - issue_without_owner["labels"] = [{"name": "bug"}, {"name": "app"}] - - assert ( - collect_issue_digest.summarize_issue( - issue_without_owner, - [], - ["tui"], - since, - until, - body_chars=100, - comment_chars=100, - ) - is None - ) - - -def test_resolve_window_defaults_to_previous_hours(): - class Args: - since = None - until = "2026-04-26T12:00:00Z" - window_hours = 24 - - since, until = collect_issue_digest.resolve_window(Args()) - - assert since.isoformat() == "2026-04-25T12:00:00+00:00" - assert until.tzinfo == timezone.utc - - -def test_parse_duration_hours_accepts_common_phrases(): - assert collect_issue_digest.parse_duration_hours("past week") == 168 - assert collect_issue_digest.parse_duration_hours("48h") == 48 - assert collect_issue_digest.parse_duration_hours("2 days") == 48 - assert collect_issue_digest.parse_duration_hours("1w") == 168 - - -def test_attention_thresholds_scale_by_window_length(): - one_day = collect_issue_digest.attention_thresholds_for_window(24) - assert one_day["elevated"] == 5 - assert one_day["very_high"] == 10 - - half_day = collect_issue_digest.attention_thresholds_for_window(12) - assert half_day["elevated"] == 3 - assert half_day["very_high"] == 5 - - week = collect_issue_digest.attention_thresholds_for_window(168) - assert week["elevated"] == 35 - assert week["very_high"] == 70 - assert collect_issue_digest.attention_marker_for(34, week) == "" - assert collect_issue_digest.attention_marker_for(35, week) == "🔥" - assert collect_issue_digest.attention_marker_for(70, week) == "🔥🔥" - - -def test_fetch_comments_uses_since_filter_and_page_cap(monkeypatch): - calls = [] - - def fake_gh_json(args): - calls.append(args) - return [{"id": idx} for idx in range(100)] - - monkeypatch.setattr(collect_issue_digest, "gh_json", fake_gh_json) - since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since") - - payload = collect_issue_digest.fetch_comments( - "openai/codex", 123, since=since, max_pages=1 - ) - - assert len(payload["items"]) == 100 - assert payload["truncated"] is True - assert payload["max_pages"] == 1 - assert calls == [ - [ - "api", - "repos/openai/codex/issues/123/comments?since=2026-04-25T00%3A00%3A00Z&per_page=100&page=1", - ] - ] - - -def test_issue_description_prefers_title_over_body_noise(): - issue = { - "title": "Codex.app GUI: MCP child processes not reaped after task completion", - "body": "A later crash mention should not override the title-level symptom.", - "labels": [{"name": "app"}, {"name": "bug"}], - } - - description = collect_issue_digest.issue_description(issue) - assert "MCP child processes" in description - assert "crash" not in description.casefold() - - -def test_attention_markers_count_human_user_interactions(): - since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since") - until = collect_issue_digest.parse_timestamp("2026-04-26T00:00:00Z", "--until") - issue = { - "number": 456, - "title": "Agent context is exploding", - "html_url": "https://github.com/openai/codex/issues/456", - "state": "open", - "created_at": "2026-04-25T01:00:00Z", - "updated_at": "2026-04-25T12:00:00Z", - "user": {"login": "alice"}, - "labels": [{"name": "bug"}, {"name": "agent"}], - } - comments = [ - { - "id": idx, - "created_at": "2026-04-25T02:00:00Z", - "updated_at": "2026-04-25T02:00:00Z", - "user": {"login": f"user-{idx}"}, - "body": "same here", - } - for idx in range(4) - ] - comments.append( - { - "id": 99, - "created_at": "2026-04-25T02:00:00Z", - "updated_at": "2026-04-25T02:00:00Z", - "user": {"login": "github-actions[bot]"}, - "body": "duplicate bot note", - } - ) - - summary = collect_issue_digest.summarize_issue( - issue, - comments, - ["agent"], - since, - until, - body_chars=100, - comment_chars=100, - ) - - assert summary["user_interactions"] == 5 - assert summary["activity"]["new_human_comments"] == 4 - assert summary["attention"] is True - assert summary["attention_level"] == 1 - assert summary["attention_marker"] == "🔥" - - issue["created_at"] = "2026-04-24T01:00:00Z" - comments.extend( - { - "id": idx, - "created_at": "2026-04-25T03:00:00Z", - "updated_at": "2026-04-25T03:00:00Z", - "user": {"login": f"extra-user-{idx}"}, - "body": "also seeing this", - } - for idx in range(100, 106) - ) - - summary = collect_issue_digest.summarize_issue( - issue, - comments, - ["agent"], - since, - until, - body_chars=100, - comment_chars=100, - ) - - assert summary["user_interactions"] == 10 - assert summary["attention_level"] == 2 - assert summary["attention_marker"] == "🔥🔥" - - -def test_reactions_count_toward_attention_markers(): - since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since") - until = collect_issue_digest.parse_timestamp("2026-04-26T00:00:00Z", "--until") - issue = { - "number": 789, - "title": "Support 1M token context", - "html_url": "https://github.com/openai/codex/issues/789", - "state": "open", - "created_at": "2026-04-24T01:00:00Z", - "updated_at": "2026-04-25T12:00:00Z", - "user": {"login": "alice"}, - "labels": [{"name": "enhancement"}, {"name": "context"}], - "reactions": {"total_count": 20, "+1": 20}, - } - comments = [ - { - "id": 1, - "created_at": "2026-04-25T02:00:00Z", - "updated_at": "2026-04-25T02:00:00Z", - "user": {"login": "commenter"}, - "body": "please", - "reactions": {"total_count": 2, "+1": 2}, - } - ] - issue_reactions = [ - { - "content": "+1", - "created_at": "2026-04-25T03:00:00Z", - "user": {"login": f"reactor-{idx}"}, - } - for idx in range(18) - ] - comment_reactions_by_id = { - 1: [ - { - "content": "heart", - "created_at": "2026-04-25T04:00:00Z", - "user": {"login": "human-reactor"}, - }, - { - "content": "+1", - "created_at": "2026-04-25T04:00:00Z", - "user": {"login": "github-actions[bot]"}, - }, - ] - } - - summary = collect_issue_digest.summarize_issue( - issue, - comments, - ["context"], - since, - until, - body_chars=100, - comment_chars=100, - issue_reaction_events=issue_reactions, - comment_reactions_by_id=comment_reactions_by_id, - ) - - assert summary["new_reactions"] == 19 - assert summary["new_upvotes"] == 18 - assert summary["user_interactions"] == 20 - assert summary["attention_level"] == 2 - assert summary["attention_marker"] == "🔥🔥" - assert summary["new_comments"][0]["new_reactions"] == 1 - assert summary["new_comments"][0]["new_upvotes"] == 0 - - -def test_user_interactions_are_deduped_by_human_login(): - since = collect_issue_digest.parse_timestamp("2026-04-25T00:00:00Z", "--since") - until = collect_issue_digest.parse_timestamp("2026-04-26T00:00:00Z", "--until") - - def comment(comment_id, login): - return { - "id": comment_id, - "created_at": f"2026-04-25T0{comment_id + 1}:00:00Z", - "updated_at": f"2026-04-25T0{comment_id + 1}:00:00Z", - "user": {"login": login}, - "body": "same issue", - } - - def reaction(content, login, created_at="2026-04-25T10:00:00Z"): - return { - "content": content, - "created_at": created_at, - "user": {"login": login}, - } - - issue = { - "number": 790, - "title": "Repeated pings should not boost attention", - "html_url": "https://github.com/openai/codex/issues/790", - "state": "open", - "created_at": "2026-04-25T01:00:00Z", - "updated_at": "2026-04-25T12:00:00Z", - "user": {"login": "Alice"}, - "labels": [{"name": "bug"}, {"name": "tui"}], - } - comments = [comment(1, "alice"), comment(2, "ALICE"), comment(3, "bob")] - comments.append(comment(4, "github-actions[bot]")) - issue_reactions = [ - reaction("+1", "alice"), - reaction("rocket", "Alice"), - reaction("+1", "bob"), - reaction("+1", "github-actions[bot]"), - reaction("+1", "carol", created_at="2026-04-24T23:00:00Z"), - ] - comment_reactions_by_id = { - 1: [reaction("heart", "alice")], - 2: [reaction("+1", "bob")], - 3: [reaction("eyes", "carol")], - } - - summary = collect_issue_digest.summarize_issue( - issue, - comments, - ["tui"], - since, - until, - body_chars=100, - comment_chars=100, - issue_reaction_events=issue_reactions, - comment_reactions_by_id=comment_reactions_by_id, - ) - - assert summary["activity"]["new_human_comments"] == 3 - assert summary["new_reactions"] == 6 - assert summary["user_interactions"] == 3 - assert summary["attention"] is False - assert summary["attention_marker"] == "" - - -def test_digest_rows_are_table_ready_with_concise_descriptions(): - rows = collect_issue_digest.digest_rows( - [ - { - "number": 1, - "title": "Quiet bug", - "description": "Quiet bug", - "url": "https://github.com/openai/codex/issues/1", - "owner_labels": ["context"], - "kind_labels": ["bug"], - "state": "open", - "attention": False, - "attention_level": 0, - "attention_marker": "", - "user_interactions": 1, - "new_reactions": 0, - "new_upvotes": 0, - "engagement_score": 3, - "issue_reaction_total": 0, - "comment_reaction_total": 0, - "updated_at": "2026-04-25T01:00:00Z", - "activity": { - "new_issue": True, - "new_comments": 0, - "new_reactions": 0, - "updated_without_visible_new_post": False, - }, - }, - { - "number": 2, - "title": "Busy bug", - "description": "High-volume bug report", - "url": "https://github.com/openai/codex/issues/2", - "owner_labels": ["agent"], - "kind_labels": ["bug"], - "state": "open", - "attention": True, - "attention_level": 1, - "attention_marker": "🔥", - "user_interactions": 17, - "new_reactions": 3, - "new_upvotes": 2, - "engagement_score": 20, - "issue_reaction_total": 5, - "comment_reaction_total": 2, - "updated_at": "2026-04-25T02:00:00Z", - "activity": { - "new_issue": False, - "new_comments": 16, - "new_reactions": 3, - "updated_without_visible_new_post": False, - }, - }, - ] - ) - - assert rows[0] == { - "ref": 1, - "ref_markdown": "[1](https://github.com/openai/codex/issues/2)", - "marker": "🔥", - "attention_marker": "🔥", - "number": 2, - "description": "High-volume bug report", - "title": "Busy bug", - "url": "https://github.com/openai/codex/issues/2", - "area": "agent", - "kind": "bug", - "state": "open", - "interactions": 17, - "user_interactions": 17, - "new_reactions": 3, - "new_upvotes": 2, - "current_reactions": 7, - } - - -def test_summary_inputs_are_model_ready_without_preclustering(): - issues = [ - { - "number": 20, - "title": "Windows app Browser Use external navigation fails", - "description": "Browser Use navigation or app-server failure", - "url": "https://github.com/openai/codex/issues/20", - "labels": ["app", "bug"], - "owner_labels": ["app"], - "kind_labels": ["bug"], - "attention": False, - "attention_level": 0, - "attention_marker": "", - "user_interactions": 3, - "new_reactions": 1, - "engagement_score": 8, - "updated_at": "2026-04-25T04:00:00Z", - "activity": {"new_comments": 2}, - }, - { - "number": 21, - "title": "On Windows, cmake output waits until timeout", - "description": "Windows command timeout/capture problem", - "url": "https://github.com/openai/codex/issues/21", - "labels": ["app", "bug"], - "owner_labels": ["app"], - "kind_labels": ["bug"], - "attention": False, - "attention_level": 0, - "attention_marker": "", - "user_interactions": 3, - "new_reactions": 0, - "engagement_score": 7, - "updated_at": "2026-04-25T03:00:00Z", - "activity": {"new_comments": 3}, - }, - { - "number": 22, - "title": "Windows computer use tool fails to click buttons", - "description": "Computer-use workflow failure", - "url": "https://github.com/openai/codex/issues/22", - "labels": ["app", "bug"], - "owner_labels": ["app"], - "kind_labels": ["bug"], - "attention": False, - "attention_level": 0, - "attention_marker": "", - "user_interactions": 3, - "new_reactions": 0, - "engagement_score": 6, - "updated_at": "2026-04-25T02:00:00Z", - "activity": {"new_comments": 3}, - }, - ] - - rows = collect_issue_digest.summary_inputs(issues, ref_map={20: 1, 21: 2, 22: 3}) - - assert rows == [ - { - "ref": 1, - "ref_markdown": "[1](https://github.com/openai/codex/issues/20)", - "number": 20, - "title": "Windows app Browser Use external navigation fails", - "description": "Browser Use navigation or app-server failure", - "url": "https://github.com/openai/codex/issues/20", - "labels": ["app", "bug"], - "owner_labels": ["app"], - "kind_labels": ["bug"], - "state": "", - "attention_marker": "", - "interactions": 3, - "new_comments": 2, - "new_reactions": 1, - "new_upvotes": 0, - "current_reactions": 0, - }, - { - "ref": 2, - "ref_markdown": "[2](https://github.com/openai/codex/issues/21)", - "number": 21, - "title": "On Windows, cmake output waits until timeout", - "description": "Windows command timeout/capture problem", - "url": "https://github.com/openai/codex/issues/21", - "labels": ["app", "bug"], - "owner_labels": ["app"], - "kind_labels": ["bug"], - "state": "", - "attention_marker": "", - "interactions": 3, - "new_comments": 3, - "new_reactions": 0, - "new_upvotes": 0, - "current_reactions": 0, - }, - { - "ref": 3, - "ref_markdown": "[3](https://github.com/openai/codex/issues/22)", - "number": 22, - "title": "Windows computer use tool fails to click buttons", - "description": "Computer-use workflow failure", - "url": "https://github.com/openai/codex/issues/22", - "labels": ["app", "bug"], - "owner_labels": ["app"], - "kind_labels": ["bug"], - "state": "", - "attention_marker": "", - "interactions": 3, - "new_comments": 3, - "new_reactions": 0, - "new_upvotes": 0, - "current_reactions": 0, - }, - ] diff --git a/reference/openai-codex/.codex/skills/codex-pr-body/SKILL.md b/reference/openai-codex/.codex/skills/codex-pr-body/SKILL.md deleted file mode 100644 index 76b37b8..0000000 --- a/reference/openai-codex/.codex/skills/codex-pr-body/SKILL.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -name: codex-pr-body -description: Update the title and body of one or more pull requests. ---- - -## Determining the PR(s) - -When this skill is invoked, the PR(s) to update may be specified explicitly, but in the common case, the PR(s) to update will be inferred from the branch / commit that the user is currently working on. For ordinary Git usage (i.e., not Sapling as discussed below), you may have to use a combination of `git branch` and `gh pr view --repo openai/codex --json number --jq '.number'` to determine the PR associated with the current branch / commit. - -## PR Body Contents - -When invoked, use `gh` to edit the pull request body and title to reflect the contents of the specified PR. Make sure to check the existing pull request body to see if there is key information that should be preserved. For example, NEVER remove an image in the existing pull request body, as the author may have no way to recover it if you remove it. - -It is critically important to explain _why_ the change is being made. If the current conversation in which this skill is invoked has discussed the motivation, be sure to capture this in the pull request body. - -The body should also explain _what_ changed, but this should appear after the _why_. - -Limit discussion to the _net change_ of the commit. It is generally frowned upon to discuss changes that were attempted but later undone in the course of the development of the pull request. When rewriting the pull request body, you may need to eliminate details such as these when they are no longer appropriate / of interest to future readers. - -Avoid references to absolute paths on my local disk. When talking about a path that is within the repository, simply use the repo-relative path. - -It is generally helpful to discuss how the change was verified. That said, it is unnecessary to mention things that CI checks automatically, e.g., do not include "ran `just fmt`" as part of the test plan. Though identifying the new tests that were purposely introduced to verify the new behavior introduced by the pull request is often appropriate. - -Make use of Markdown to format the pull request professionally. Ensure "code things" appear in single backticks when referenced inline. Fenced code blocks are useful when referencing code or showing a shell transcript. Also, make use of GitHub permalinks when citing existing pieces of code that are relevant to the change. - -Make sure to reference any relevant pull requests or issues, though there should be no need to reference the pull request in its own PR body. - -If there is documentation that should be updated on https://developers.openai.com/codex as a result of this change, please note that in a separate section near the end of the pull request. Omit this section if there is no documentation that needs to be updated. - -## Working with Stacks - -Sometimes a pull request is composed of a stack of commits that build on one another. In these cases, the PR body should reflect the _net_ change introduced by the stack as a whole, rather than the individual commits that make up the stack. - -Similarly, sometimes a user may be using a tool like Sapling to leverage _stacked pull requests_, in which case the `base` of the PR may be the a branch that is the `head` of another PR in the stack rather than `main`. In this case, be sure to discuss only the net change between the `base` and `head` of the PR that is being opened against that stacked base, rather than the changes relative to `main`. - -## Sapling - -If `.git/sl/store` is present, then this Git repository is governed by Sapling SCM (https://sapling-scm.com). - -In Sapling, run the following to see if there is a GitHub pull request associated with the current revision: - -```shell -sl log --template '{github_pull_request_url}' -r . -``` - -Alternatively, you can run `sl sl` to see the current development branch and whether there is a GitHub pull request associated with the current commit. For example, if the output were: - -``` - @ cb032b31cf 72 minutes ago mbolin #11412 -╭─╯ tui: show non-file layer content in /debug-config -│ -o fdd0cd1de9 Today at 20:09 origin/main -│ -~ -``` - -- `@` indicates the current commit is `cb032b31cf` -- it is a development branch containing a single commit branched off of `origin/main` -- it is associated with GitHub pull request #11412 diff --git a/reference/openai-codex/.codex/skills/remote-tests/SKILL.md b/reference/openai-codex/.codex/skills/remote-tests/SKILL.md deleted file mode 100644 index ee35fc2..0000000 --- a/reference/openai-codex/.codex/skills/remote-tests/SKILL.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: remote-tests -description: How to run tests using remote executor. ---- - -Some codex integration tests support a running against a remote executor. -This means that when CODEX_TEST_REMOTE_ENV environment variable is set they will attempt to start an executor process in a docker container CODEX_TEST_REMOTE_ENV points to and use it in tests. - -Docker container is built and initialized via ./scripts/test-remote-env.sh - -Currently running remote tests is only supported on Linux, so you need to use a devbox to run them - -You can list devboxes via `applied_devbox ls`, pick the one with `codex` in the name. -Connect to devbox via `ssh `. -Reuse the same checkout of codex in `~/code/codex`. Reset files if needed. Multiple checkouts take longer to build and take up more space. -Check whether the SHA and modified files are in sync between remote and local. diff --git a/reference/openai-codex/.codex/skills/test-tui/SKILL.md b/reference/openai-codex/.codex/skills/test-tui/SKILL.md deleted file mode 100644 index e58e677..0000000 --- a/reference/openai-codex/.codex/skills/test-tui/SKILL.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: test-tui -description: Guide for testing Codex TUI interactively ---- - -You can start and use Codex TUI to verify changes. - -Important notes: - -Start interactively. -Always set RUST_LOG="trace" when starting the process. -Pass `-c log_dir=` argument to have logs written to a specific directory to help with debugging. -When sending a test message programmatically, send text first, then send Enter in a separate write (do not send text + Enter in one burst). -Use `just codex` target to run - `just codex -c ...` diff --git a/reference/openai-codex/.codex/skills/update-v8-version/SKILL.md b/reference/openai-codex/.codex/skills/update-v8-version/SKILL.md deleted file mode 100644 index 512cc0d..0000000 --- a/reference/openai-codex/.codex/skills/update-v8-version/SKILL.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -name: update-v8-version -description: Update Codex's pinned `v8` / `rusty_v8` versions, validate the release-candidate path, and investigate failed V8 canary or artifact builds. Use when asked to bump V8, update `rusty_v8` artifacts, prepare or validate a V8 release candidate, check `v8-canary`, or diagnose why a V8 version update no longer builds. ---- - -# Update V8 Version - -## Core Workflow - -1. Read `third_party/v8/README.md` and follow its version-bump sequence. Treat - that document as the release-process source of truth. -2. Inspect and update the concrete repo surfaces that carry the pin: - - `codex-rs/Cargo.toml` - - `codex-rs/Cargo.lock` - - `MODULE.bazel` - - `third_party/v8/BUILD.bazel` - - `third_party/v8/README.md` - - the matching `third_party/v8/rusty_v8_.sha256` manifest when the - remaining prebuilt inputs change -3. Keep the existing checksum helpers in the loop: - - ```bash - python3 .github/scripts/rusty_v8_bazel.py update-module-bazel - python3 .github/scripts/rusty_v8_bazel.py check-module-bazel - python3 -m unittest discover -s .github/scripts -p test_rusty_v8_bazel.py - ``` - -4. Validate the release-candidate path before broadening the work: - - Prefer checking the `v8-canary` CI result for the candidate branch or PR - when one exists, using GitHub check tooling or `gh` as appropriate. - - If CI is unavailable or the user asked for a local-only check, run the - closest local validation that is practical for the changed surface and say - explicitly that it is a local substitute, not the full hosted canary. -5. If the canary path passes, stop there. Summarize the result and encourage the - user to commit the candidate changes or proceed with the release flow they - requested. Do not publish tags, releases, or pushes unless the user asked. - -## Failure Path - -Enter this path only when the canary or local build path fails. - -1. Capture the failing target, workflow job, and first actionable error. -2. Compare the currently pinned version with the target version at the relevant - upstream tag or SHA. Inspect both: - - `denoland/rusty_v8` - - upstream V8 source at the target Bazel-pinned version -3. Track build-relevant deltas rather than broad source churn: - - generated binding layout changes - - archive or asset naming changes - - GN/Bazel target changes - - custom libc++ / libc++abi / llvm-libc inputs - - sandbox or pointer-compression feature relationships - - patch hunks in `patches/` that no longer apply or no longer match upstream -4. Trace each failing delta back into Codex's build graph: - - `MODULE.bazel` - - `third_party/v8/BUILD.bazel` - - `.github/scripts/rusty_v8_bazel.py` - - `.github/workflows/v8-canary.yml` - - `.github/workflows/rusty-v8-release.yml` -5. Update only the pieces required to restore the target version's build and - artifact contract. Keep patch explanations and doc changes close to the - affected files. -6. Re-run the focused validation. If it becomes green, return to the normal - workflow and stop with a concise summary plus the remaining release step. - -## Reporting - -- Say whether validation came from hosted `v8-canary` or from a local - substitute. -- Distinguish "version bump complete" from "release published". -- When blocked, report the upstream delta that matters, the Codex file it hits, - and the next concrete fix to try. diff --git a/reference/openai-codex/.codex/skills/update-v8-version/agents/openai.yaml b/reference/openai-codex/.codex/skills/update-v8-version/agents/openai.yaml deleted file mode 100644 index 36e7af8..0000000 --- a/reference/openai-codex/.codex/skills/update-v8-version/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Update V8 Version" - short_description: "Guide V8 bumps and release validation" - default_prompt: "Use $update-v8-version to update Codex to a new v8 release and validate the release-candidate path." diff --git a/reference/openai-codex/.devcontainer/Dockerfile b/reference/openai-codex/.devcontainer/Dockerfile deleted file mode 100644 index 8c3a859..0000000 --- a/reference/openai-codex/.devcontainer/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM ubuntu:24.04 - -ARG DEBIAN_FRONTEND=noninteractive -# enable 'universe' because musl-tools & clang live there -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - software-properties-common && \ - add-apt-repository --yes universe - -# now install build deps -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - build-essential curl git ca-certificates \ - pkg-config libcap-dev clang musl-tools libssl-dev just && \ - rm -rf /var/lib/apt/lists/* - -# Ubuntu 24.04 ships with user 'ubuntu' already created with UID 1000. -USER ubuntu - -# install Rust + musl target as dev user -RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal && \ - ~/.cargo/bin/rustup target add aarch64-unknown-linux-musl && \ - ~/.cargo/bin/rustup component add clippy rustfmt - -ENV PATH="/home/ubuntu/.cargo/bin:${PATH}" - -WORKDIR /workspace diff --git a/reference/openai-codex/.devcontainer/Dockerfile.secure b/reference/openai-codex/.devcontainer/Dockerfile.secure deleted file mode 100644 index 6c1878e..0000000 --- a/reference/openai-codex/.devcontainer/Dockerfile.secure +++ /dev/null @@ -1,82 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 - -ARG TZ -ARG DEBIAN_FRONTEND=noninteractive -ARG NODE_MAJOR=22 -ARG RUST_TOOLCHAIN=1.92.0 -# Keep this in sync with .devcontainer/codex-install/package.json and pnpm-lock.yaml. -ARG CODEX_NPM_VERSION=0.121.0 - -ENV TZ="$TZ" -ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# Devcontainers run as a non-root user, so enable bubblewrap's setuid mode. -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - build-essential \ - curl \ - git \ - ca-certificates \ - pkg-config \ - clang \ - musl-tools \ - libssl-dev \ - libsqlite3-dev \ - just \ - python3 \ - python3-pip \ - jq \ - less \ - man-db \ - unzip \ - ripgrep \ - fzf \ - fd-find \ - zsh \ - dnsutils \ - iproute2 \ - ipset \ - iptables \ - aggregate \ - bubblewrap \ - && chmod u+s /usr/bin/bwrap \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -COPY .devcontainer/codex-install/package.json \ - .devcontainer/codex-install/pnpm-lock.yaml \ - .devcontainer/codex-install/pnpm-workspace.yaml \ - /opt/codex-install/ - -RUN curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \ - && apt-get update \ - && apt-get install -y --no-install-recommends nodejs \ - && test "$(node -p "require('/opt/codex-install/package.json').dependencies['@openai/codex']")" = "${CODEX_NPM_VERSION}" \ - && cd /opt/codex-install \ - && corepack pnpm install --prod --frozen-lockfile \ - && ln -s /opt/codex-install/node_modules/.bin/codex /usr/local/bin/codex \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -COPY .devcontainer/init-firewall.sh /usr/local/bin/init-firewall.sh -COPY .devcontainer/post_install.py /opt/post_install.py -COPY .devcontainer/post-start.sh /opt/post_start.sh - -RUN chmod 500 /usr/local/bin/init-firewall.sh \ - && chmod 755 /opt/post_start.sh \ - && chmod 644 /opt/post_install.py \ - && chown vscode:vscode /opt/post_install.py - -RUN install -d -m 0775 -o vscode -g vscode /commandhistory /workspace \ - && touch /commandhistory/.bash_history /commandhistory/.zsh_history \ - && chown vscode:vscode /commandhistory/.bash_history /commandhistory/.zsh_history - -USER vscode -ENV PATH="/home/vscode/.cargo/bin:${PATH}" -WORKDIR /workspace - -RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain "${RUST_TOOLCHAIN}" \ - && rustup component add clippy rustfmt rust-src \ - && rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl diff --git a/reference/openai-codex/.devcontainer/README.md b/reference/openai-codex/.devcontainer/README.md deleted file mode 100644 index 3b07f2b..0000000 --- a/reference/openai-codex/.devcontainer/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Containerized Development - -We provide two container paths: - -- `devcontainer.json` keeps the existing Codex contributor setup for working on this repository. -- `devcontainer.secure.json` adds a customer-oriented profile with stricter outbound network controls. - -## Codex contributor profile - -Use `devcontainer.json` when you are developing Codex itself. This is the same lightweight arm64 container that already exists in the repo. - -## Secure customer profile - -Use `devcontainer.secure.json` when you want a stricter runtime profile for running Codex inside a project container: - -- installs the Codex CLI plus common build tools -- installs bubblewrap in setuid mode for Codex's Linux sandbox -- disables Docker's outer seccomp and AppArmor profiles so bubblewrap can construct Codex's inner sandbox -- enables firewall startup with an allowlist-driven outbound policy -- blocks IPv6 by default so the allowlist cannot be bypassed over AAAA routes -- requires `NET_ADMIN` and `NET_RAW` so the firewall can be installed at startup - -This profile keeps the stricter networking isolated to the customer path instead of changing the default Codex contributor container. - -Start it from the CLI with: - -```bash -devcontainer up --workspace-folder . --config .devcontainer/devcontainer.secure.json -``` - -In VS Code, choose **Dev Containers: Open Folder in Container...** and select `.devcontainer/devcontainer.secure.json`. - -## Docker - -To build the contributor image locally for x64 and then run it with the repo mounted under `/workspace`: - -```shell -CODEX_DOCKER_IMAGE_NAME=codex-linux-dev -docker build --platform=linux/amd64 -t "$CODEX_DOCKER_IMAGE_NAME" ./.devcontainer -docker run --platform=linux/amd64 --rm -it -e CARGO_TARGET_DIR=/workspace/codex-rs/target-amd64 -v "$PWD":/workspace -w /workspace/codex-rs "$CODEX_DOCKER_IMAGE_NAME" -``` - -Note that `/workspace/target` will contain the binaries built for your host platform, so we include `-e CARGO_TARGET_DIR=/workspace/codex-rs/target-amd64` in the `docker run` command so that the binaries built inside your container are written to a separate directory. - -For arm64, specify `--platform=linux/arm64` instead for both `docker build` and `docker run`. - -Currently, the contributor `Dockerfile` works for both x64 and arm64 Linux, though you need to run `rustup target add x86_64-unknown-linux-musl` yourself to install the musl toolchain for x64. - -The secure profile's capability, seccomp, and AppArmor options are required when you want Codex's bubblewrap sandbox to run inside Docker as the non-root devcontainer user. Without them, Docker's default runtime profile can block bubblewrap's namespace setup before Codex's own seccomp filter is installed. This keeps the Docker relaxation explicit in the profile that is meant to run Codex inside a project container, while the default contributor profile stays lightweight. diff --git a/reference/openai-codex/.devcontainer/codex-install/package.json b/reference/openai-codex/.devcontainer/codex-install/package.json deleted file mode 100644 index 453054e..0000000 --- a/reference/openai-codex/.devcontainer/codex-install/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "codex-devcontainer-install", - "private": true, - "description": "Locked Codex CLI install boundary for the secure devcontainer.", - "dependencies": { - "@openai/codex": "0.121.0" - }, - "engines": { - "node": ">=22", - "pnpm": ">=10.33.0" - }, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319" -} diff --git a/reference/openai-codex/.devcontainer/codex-install/pnpm-lock.yaml b/reference/openai-codex/.devcontainer/codex-install/pnpm-lock.yaml deleted file mode 100644 index 70e7608..0000000 --- a/reference/openai-codex/.devcontainer/codex-install/pnpm-lock.yaml +++ /dev/null @@ -1,85 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@openai/codex': - specifier: 0.121.0 - version: 0.121.0 - -packages: - - '@openai/codex@0.121.0': - resolution: {integrity: sha512-kCJ2NeATd4QBQRmqV04ymdN1ZU3MSwnJQDm/KzjpuzGvCuUVEn7no/T2mRyxQ2x77AACqriNOyPPoM/yufyvNg==} - engines: {node: '>=16'} - hasBin: true - - '@openai/codex@0.121.0-darwin-arm64': - resolution: {integrity: sha512-ZyBqIB6Fb4I0hGb/h65Vu7ePYjHSmGiqqfm+/1djEuxDPkqjfi4wkxYxNYNY+6najyNGN4UijOSTTf19eDCrqw==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@openai/codex@0.121.0-darwin-x64': - resolution: {integrity: sha512-1/OAtdkAZ5yPI3xqaEFlHuPziS1yCqL2gOZdswE7HTmmwpIxi6Z3FCo60JWDPluIp89z4tftdjq73/OCN0YVcw==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@openai/codex@0.121.0-linux-arm64': - resolution: {integrity: sha512-2UgMmdo237o7SCMsfb529cOSEM2HFUgN6OBkv5SBLwfNY1NO2Ex6JnUjlppEXlX6/4cXfZ5qjDghVz5j/+B9zw==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@openai/codex@0.121.0-linux-x64': - resolution: {integrity: sha512-vlpNJXIqss800J+32Vy7TUZzv31n61b45OLxmsVQGFkTNLJcjFrj9jDUC7I62eC4F16gLioilefNfv4CdJQOEw==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@openai/codex@0.121.0-win32-arm64': - resolution: {integrity: sha512-m88q4f3XI5npn1t6OG0nWGHWWAjO5FgjRwxh4hdujbLO6t9CiCNfhfPZIOSsoATbrCNwLC+6S77m3cjbNToPNg==} - engines: {node: '>=16'} - cpu: [arm64] - os: [win32] - - '@openai/codex@0.121.0-win32-x64': - resolution: {integrity: sha512-Fp0ecVOyM+VcBi/y4HVvRzhifO9YqRiHzhV3rhtAppC7flh22WPguLC4kmvXYAR0p3RPzbo35M2CedWnkOT+cw==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - -snapshots: - - '@openai/codex@0.121.0': - optionalDependencies: - '@openai/codex-darwin-arm64': '@openai/codex@0.121.0-darwin-arm64' - '@openai/codex-darwin-x64': '@openai/codex@0.121.0-darwin-x64' - '@openai/codex-linux-arm64': '@openai/codex@0.121.0-linux-arm64' - '@openai/codex-linux-x64': '@openai/codex@0.121.0-linux-x64' - '@openai/codex-win32-arm64': '@openai/codex@0.121.0-win32-arm64' - '@openai/codex-win32-x64': '@openai/codex@0.121.0-win32-x64' - - '@openai/codex@0.121.0-darwin-arm64': - optional: true - - '@openai/codex@0.121.0-darwin-x64': - optional: true - - '@openai/codex@0.121.0-linux-arm64': - optional: true - - '@openai/codex@0.121.0-linux-x64': - optional: true - - '@openai/codex@0.121.0-win32-arm64': - optional: true - - '@openai/codex@0.121.0-win32-x64': - optional: true diff --git a/reference/openai-codex/.devcontainer/codex-install/pnpm-workspace.yaml b/reference/openai-codex/.devcontainer/codex-install/pnpm-workspace.yaml deleted file mode 100644 index 3b901a0..0000000 --- a/reference/openai-codex/.devcontainer/codex-install/pnpm-workspace.yaml +++ /dev/null @@ -1,12 +0,0 @@ -packages: - - "." - -minimumReleaseAge: 10080 -minimumReleaseAgeExclude: [] - -blockExoticSubdeps: true -strictDepBuilds: true -trustPolicy: no-downgrade -trustPolicyIgnoreAfter: 10080 -trustPolicyExclude: [] -allowBuilds: {} diff --git a/reference/openai-codex/.devcontainer/devcontainer.json b/reference/openai-codex/.devcontainer/devcontainer.json deleted file mode 100644 index 1bed79c..0000000 --- a/reference/openai-codex/.devcontainer/devcontainer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "Codex", - "build": { - "dockerfile": "Dockerfile", - "context": "..", - "platform": "linux/arm64" - }, - - /* Force VS Code to run the container as arm64 in - case your host is x86 (or vice-versa). */ - "runArgs": ["--platform=linux/arm64"], - - "containerEnv": { - "RUST_BACKTRACE": "1", - "CARGO_TARGET_DIR": "${containerWorkspaceFolder}/codex-rs/target-arm64" - }, - - "remoteUser": "ubuntu", - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.defaultProfile.linux": "bash" - }, - "extensions": ["rust-lang.rust-analyzer", "tamasfe.even-better-toml"] - } - } -} diff --git a/reference/openai-codex/.devcontainer/devcontainer.secure.json b/reference/openai-codex/.devcontainer/devcontainer.secure.json deleted file mode 100644 index 5d5808e..0000000 --- a/reference/openai-codex/.devcontainer/devcontainer.secure.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json", - "name": "Codex (Secure)", - "build": { - "dockerfile": "Dockerfile.secure", - "context": "..", - "args": { - "TZ": "${localEnv:TZ:UTC}", - "NODE_MAJOR": "22", - "RUST_TOOLCHAIN": "1.92.0", - "CODEX_NPM_VERSION": "0.121.0" - } - }, - "runArgs": [ - "--cap-add=SYS_ADMIN", - "--cap-add=SYS_CHROOT", - "--cap-add=SETUID", - "--cap-add=SETGID", - "--cap-add=SYS_PTRACE", - "--security-opt=seccomp=unconfined", - "--security-opt=apparmor=unconfined", - "--cap-add=NET_ADMIN", - "--cap-add=NET_RAW" - ], - "init": true, - "updateRemoteUserUID": true, - "remoteUser": "vscode", - "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", - "workspaceFolder": "/workspace", - "mounts": [ - "source=codex-commandhistory-${devcontainerId},target=/commandhistory,type=volume", - "source=codex-home-${devcontainerId},target=/home/vscode/.codex,type=volume", - "source=codex-gh-${devcontainerId},target=/home/vscode/.config/gh,type=volume", - "source=codex-cargo-registry-${devcontainerId},target=/home/vscode/.cargo/registry,type=volume", - "source=codex-cargo-git-${devcontainerId},target=/home/vscode/.cargo/git,type=volume", - "source=codex-rustup-${devcontainerId},target=/home/vscode/.rustup,type=volume", - "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,readonly" - ], - "containerEnv": { - "RUST_BACKTRACE": "1", - "CODEX_UNSAFE_ALLOW_NO_SANDBOX": "1", - "CODEX_ENABLE_FIREWALL": "1", - "CODEX_INCLUDE_GITHUB_META_RANGES": "1", - "OPENAI_ALLOWED_DOMAINS": "api.openai.com auth.openai.com github.com api.github.com codeload.github.com raw.githubusercontent.com objects.githubusercontent.com crates.io index.crates.io static.crates.io static.rust-lang.org registry.npmjs.org pypi.org files.pythonhosted.org", - "CARGO_TARGET_DIR": "/workspace/.cache/cargo-target", - "GIT_CONFIG_GLOBAL": "/home/vscode/.gitconfig.local", - "COREPACK_ENABLE_DOWNLOAD_PROMPT": "0", - "PYTHONDONTWRITEBYTECODE": "1", - "PIP_DISABLE_PIP_VERSION_CHECK": "1" - }, - "remoteEnv": { - "OPENAI_API_KEY": "${localEnv:OPENAI_API_KEY}" - }, - "postCreateCommand": "python3 /opt/post_install.py", - "postStartCommand": "bash /opt/post_start.sh", - "waitFor": "postStartCommand", - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.defaultProfile.linux": "zsh", - "terminal.integrated.profiles.linux": { - "bash": { - "path": "bash", - "icon": "terminal-bash" - }, - "zsh": { - "path": "zsh" - } - }, - "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true, - "files.trimFinalNewlines": true - }, - "extensions": [ - "openai.chatgpt", - "rust-lang.rust-analyzer", - "tamasfe.even-better-toml", - "vadimcn.vscode-lldb", - "ms-azuretools.vscode-docker" - ] - } - } -} diff --git a/reference/openai-codex/.devcontainer/init-firewall.sh b/reference/openai-codex/.devcontainer/init-firewall.sh deleted file mode 100644 index 9275724..0000000 --- a/reference/openai-codex/.devcontainer/init-firewall.sh +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' - -allowed_domains_file="/etc/codex/allowed_domains.txt" -include_github_meta_ranges="${CODEX_INCLUDE_GITHUB_META_RANGES:-1}" - -if [ -f "$allowed_domains_file" ]; then - mapfile -t allowed_domains < <(sed '/^\s*#/d;/^\s*$/d' "$allowed_domains_file") -else - allowed_domains=("api.openai.com") -fi - -if [ "${#allowed_domains[@]}" -eq 0 ]; then - echo "ERROR: No allowed domains configured" - exit 1 -fi - -add_ipv4_cidr_to_allowlist() { - local source="$1" - local cidr="$2" - - if [[ ! "$cidr" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$ ]]; then - echo "ERROR: Invalid ${source} CIDR range: $cidr" - exit 1 - fi - - ipset add allowed-domains "$cidr" -exist -} - -configure_ipv6_default_deny() { - if ! command -v ip6tables >/dev/null 2>&1; then - echo "ERROR: ip6tables is required to enforce IPv6 default-deny policy" - exit 1 - fi - - ip6tables -F - ip6tables -X - ip6tables -t mangle -F - ip6tables -t mangle -X - ip6tables -t nat -F 2>/dev/null || true - ip6tables -t nat -X 2>/dev/null || true - - ip6tables -A INPUT -i lo -j ACCEPT - ip6tables -A OUTPUT -o lo -j ACCEPT - ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT - ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT - - ip6tables -P INPUT DROP - ip6tables -P FORWARD DROP - ip6tables -P OUTPUT DROP - - echo "IPv6 firewall policy configured (default-deny)" -} - -# Preserve docker-managed DNS NAT rules before clearing tables. -docker_dns_rules="$(iptables-save -t nat | grep "127\\.0\\.0\\.11" || true)" - -iptables -F -iptables -X -iptables -t nat -F -iptables -t nat -X -iptables -t mangle -F -iptables -t mangle -X -ipset destroy allowed-domains 2>/dev/null || true - -if [ -n "$docker_dns_rules" ]; then - echo "Restoring Docker DNS NAT rules" - iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true - iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true - while IFS= read -r rule; do - [ -z "$rule" ] && continue - iptables -t nat $rule - done <<< "$docker_dns_rules" -fi - -# Allow DNS resolution and localhost communication. -iptables -A OUTPUT -p udp --dport 53 -j ACCEPT -iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT -iptables -A INPUT -p udp --sport 53 -j ACCEPT -iptables -A INPUT -p tcp --sport 53 -j ACCEPT -iptables -A INPUT -i lo -j ACCEPT -iptables -A OUTPUT -o lo -j ACCEPT - -ipset create allowed-domains hash:net - -for domain in "${allowed_domains[@]}"; do - echo "Resolving $domain" - ips="$(dig +short A "$domain" | sed '/^\s*$/d')" - if [ -z "$ips" ]; then - echo "ERROR: Failed to resolve $domain" - exit 1 - fi - - while IFS= read -r ip; do - if [[ ! "$ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then - echo "ERROR: Invalid IPv4 address from DNS for $domain: $ip" - exit 1 - fi - ipset add allowed-domains "$ip" -exist - done <<< "$ips" -done - -if [ "$include_github_meta_ranges" = "1" ]; then - echo "Fetching GitHub meta ranges" - github_meta="$(curl -fsSL --connect-timeout 10 https://api.github.com/meta)" - - if ! echo "$github_meta" | jq -e '.web and .api and .git' >/dev/null; then - echo "ERROR: GitHub meta response missing expected fields" - exit 1 - fi - - while IFS= read -r cidr; do - [ -z "$cidr" ] && continue - if [[ "$cidr" == *:* ]]; then - # Current policy enforces IPv4-only ipset entries. - continue - fi - add_ipv4_cidr_to_allowlist "GitHub" "$cidr" - done < <(echo "$github_meta" | jq -r '((.web // []) + (.api // []) + (.git // []))[]' | sort -u) -fi - -host_ip="$(ip route | awk '/default/ {print $3; exit}')" -if [ -z "$host_ip" ]; then - echo "ERROR: Failed to detect host IP" - exit 1 -fi - -host_network="$(echo "$host_ip" | sed 's/\.[0-9]*$/.0\/24/')" -iptables -A INPUT -s "$host_network" -j ACCEPT -iptables -A OUTPUT -d "$host_network" -j ACCEPT - -iptables -P INPUT DROP -iptables -P FORWARD DROP -iptables -P OUTPUT DROP - -iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT - -# Reject rather than silently drop to make policy failures obvious. -iptables -A INPUT -j REJECT --reject-with icmp-admin-prohibited -iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited -iptables -A FORWARD -j REJECT --reject-with icmp-admin-prohibited - -configure_ipv6_default_deny - -echo "Firewall configuration complete" - -if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then - echo "ERROR: Firewall verification failed - was able to reach https://example.com" - exit 1 -fi - -if ! curl --connect-timeout 5 https://api.openai.com >/dev/null 2>&1; then - echo "ERROR: Firewall verification failed - unable to reach https://api.openai.com" - exit 1 -fi - -if [ "$include_github_meta_ranges" = "1" ] && ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then - echo "ERROR: Firewall verification failed - unable to reach https://api.github.com" - exit 1 -fi - -if curl --connect-timeout 5 -6 https://example.com >/dev/null 2>&1; then - echo "ERROR: Firewall verification failed - was able to reach https://example.com over IPv6" - exit 1 -fi - -echo "Firewall verification passed" diff --git a/reference/openai-codex/.devcontainer/post-start.sh b/reference/openai-codex/.devcontainer/post-start.sh deleted file mode 100644 index fcc42ad..0000000 --- a/reference/openai-codex/.devcontainer/post-start.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [ "${CODEX_ENABLE_FIREWALL:-1}" != "1" ]; then - echo "[devcontainer] Firewall mode: permissive (CODEX_ENABLE_FIREWALL=${CODEX_ENABLE_FIREWALL:-unset})." - exit 0 -fi - -echo "[devcontainer] Firewall mode: strict" - -domains_raw="${OPENAI_ALLOWED_DOMAINS:-api.openai.com}" -mapfile -t domains < <(printf '%s\n' "$domains_raw" | tr ', ' '\n\n' | sed '/^$/d' | sort -u) - -if [ "${#domains[@]}" -eq 0 ]; then - echo "[devcontainer] No allowed domains configured." - exit 1 -fi - -tmp_file="$(mktemp)" -for domain in "${domains[@]}"; do - if [[ ! "$domain" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]*\.[a-zA-Z]{2,}$ ]]; then - echo "[devcontainer] Invalid domain in OPENAI_ALLOWED_DOMAINS: $domain" - rm -f "$tmp_file" - exit 1 - fi - printf '%s\n' "$domain" >> "$tmp_file" -done - -sudo install -d -m 0755 /etc/codex -sudo cp "$tmp_file" /etc/codex/allowed_domains.txt -sudo chown root:root /etc/codex/allowed_domains.txt -sudo chmod 0444 /etc/codex/allowed_domains.txt -rm -f "$tmp_file" - -echo "[devcontainer] Applying firewall policy for domains: ${domains[*]}" -sudo --preserve-env=CODEX_INCLUDE_GITHUB_META_RANGES /usr/local/bin/init-firewall.sh diff --git a/reference/openai-codex/.devcontainer/post_install.py b/reference/openai-codex/.devcontainer/post_install.py deleted file mode 100644 index 205e57c..0000000 --- a/reference/openai-codex/.devcontainer/post_install.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python3 -"""Post-install configuration for the Codex devcontainer.""" - -from __future__ import annotations - -import os -import subprocess -import sys -from pathlib import Path - - -def ensure_history_files() -> None: - command_history_dir = Path("/commandhistory") - command_history_dir.mkdir(parents=True, exist_ok=True) - - for filename in (".bash_history", ".zsh_history"): - (command_history_dir / filename).touch(exist_ok=True) - - -def fix_directory_ownership() -> None: - uid = os.getuid() - gid = os.getgid() - - paths = [ - Path.home() / ".codex", - Path.home() / ".config" / "gh", - Path.home() / ".cargo", - Path.home() / ".rustup", - Path("/commandhistory"), - ] - - for path in paths: - if not path.exists(): - continue - - stat_info = path.stat() - if stat_info.st_uid == uid and stat_info.st_gid == gid: - continue - - try: - subprocess.run( - ["sudo", "chown", "-R", f"{uid}:{gid}", str(path)], - check=True, - capture_output=True, - text=True, - ) - print(f"[post_install] fixed ownership: {path}", file=sys.stderr) - except subprocess.CalledProcessError as err: - print( - f"[post_install] warning: could not fix ownership of {path}: {err.stderr.strip()}", - file=sys.stderr, - ) - - -def setup_git_config() -> None: - home = Path.home() - host_gitconfig = home / ".gitconfig" - local_gitconfig = home / ".gitconfig.local" - gitignore_global = home / ".gitignore_global" - - gitignore_global.write_text( - """# Codex -.codex/ - -# Rust -/target/ - -# Node -node_modules/ - -# Python -__pycache__/ -*.pyc - -# Editors -.vscode/ -.idea/ - -# macOS -.DS_Store -""", - encoding="utf-8", - ) - - include_line = ( - f"[include]\n path = {host_gitconfig}\n\n" if host_gitconfig.exists() else "" - ) - - local_gitconfig.write_text( - f"""# Container-local git configuration -{include_line}[core] - excludesfile = {gitignore_global} - -[merge] - conflictstyle = diff3 - -[diff] - colorMoved = default -""", - encoding="utf-8", - ) - - -def main() -> None: - print("[post_install] configuring devcontainer...", file=sys.stderr) - ensure_history_files() - fix_directory_ownership() - setup_git_config() - print("[post_install] complete", file=sys.stderr) - - -if __name__ == "__main__": - main() diff --git a/reference/openai-codex/.gitattributes b/reference/openai-codex/.gitattributes deleted file mode 100644 index 57c5fe6..0000000 --- a/reference/openai-codex/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -codex-rs/app-server-protocol/schema/** linguist-generated -codex-rs/hooks/schema/generated/** linguist-generated diff --git a/reference/openai-codex/.github/CODEOWNERS b/reference/openai-codex/.github/CODEOWNERS deleted file mode 100644 index 322aad4..0000000 --- a/reference/openai-codex/.github/CODEOWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# Core crate ownership. -/codex-rs/core/ @openai/codex-core-agent-team -/codex-rs/ext/extension-api/ @openai/codex-core-agent-team - -# Keep ownership changes reviewed by the same team. -/.github/CODEOWNERS @openai/codex-core-agent-team diff --git a/reference/openai-codex/.github/ISSUE_TEMPLATE/1-codex-app.yml b/reference/openai-codex/.github/ISSUE_TEMPLATE/1-codex-app.yml deleted file mode 100644 index 6e294ee..0000000 --- a/reference/openai-codex/.github/ISSUE_TEMPLATE/1-codex-app.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: 🖥️ Codex App Bug -description: Report an issue with the Codex App -labels: - - app -body: - - type: markdown - attributes: - value: | - Before submitting a new issue, please search for existing issues to see if your issue has already been reported. - If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one. - - - type: input - id: version - attributes: - label: What version of the Codex App are you using (From “About Codex” dialog)? - validations: - required: true - - type: input - id: plan - attributes: - label: What subscription do you have? - validations: - required: true - - type: input - id: platform - attributes: - label: What platform is your computer? - description: | - For macOS and Linux: copy the output of `uname -mprs` - For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console - - type: textarea - id: actual - attributes: - label: What issue are you seeing? - description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. - validations: - required: true - - type: textarea - id: steps - attributes: - label: What steps can reproduce the bug? - description: Explain the bug and provide a code snippet that can reproduce it. Please include session id, token limit usage, context window usage if applicable. - validations: - required: true - - type: textarea - id: expected - attributes: - label: What is the expected behavior? - description: If possible, please provide text instead of a screenshot. - - type: textarea - id: notes - attributes: - label: Additional information - description: Is there anything else you think we should know? diff --git a/reference/openai-codex/.github/ISSUE_TEMPLATE/2-extension.yml b/reference/openai-codex/.github/ISSUE_TEMPLATE/2-extension.yml deleted file mode 100644 index 599bc08..0000000 --- a/reference/openai-codex/.github/ISSUE_TEMPLATE/2-extension.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: 🧑‍💻 IDE Extension Bug -description: Report an issue with the IDE extension -labels: - - extension -body: - - type: markdown - attributes: - value: | - Before submitting a new issue, please search for existing issues to see if your issue has already been reported. - If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one. - - - type: input - id: version - attributes: - label: What version of the IDE extension are you using? - validations: - required: true - - type: input - id: plan - attributes: - label: What subscription do you have? - validations: - required: true - - type: input - id: ide - attributes: - label: Which IDE are you using? - description: Like `VS Code`, `Cursor`, `Windsurf`, etc. - validations: - required: true - - type: input - id: platform - attributes: - label: What platform is your computer? - description: | - For macOS and Linux: copy the output of `uname -mprs` - For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console - - type: textarea - id: actual - attributes: - label: What issue are you seeing? - description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. - validations: - required: true - - type: textarea - id: steps - attributes: - label: What steps can reproduce the bug? - description: Explain the bug and provide a code snippet that can reproduce it. - validations: - required: true - - type: textarea - id: expected - attributes: - label: What is the expected behavior? - description: If possible, please provide text instead of a screenshot. - - type: textarea - id: notes - attributes: - label: Additional information - description: Is there anything else you think we should know? diff --git a/reference/openai-codex/.github/ISSUE_TEMPLATE/3-cli.yml b/reference/openai-codex/.github/ISSUE_TEMPLATE/3-cli.yml deleted file mode 100644 index cfd368c..0000000 --- a/reference/openai-codex/.github/ISSUE_TEMPLATE/3-cli.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: 💻 CLI Bug -description: Report an issue in the Codex CLI -labels: - - bug -body: - - type: markdown - attributes: - value: | - Before submitting a new issue, please search for existing issues to see if your issue has already been reported. - If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one. - - Make sure you are running the [latest](https://npmjs.com/package/@openai/codex) version of Codex CLI. The bug you are experiencing may already have been fixed. - - If your version supports it, please run `codex doctor --json` and paste the output in the "Codex doctor report" field below. This helps us diagnose install, config, auth, terminal, MCP, network, and local state issues. - - - type: input - id: version - attributes: - label: What version of Codex CLI is running? - description: use `codex --version` - validations: - required: true - - type: input - id: plan - attributes: - label: What subscription do you have? - validations: - required: true - - type: input - id: model - attributes: - label: Which model were you using? - description: Like `gpt-5.2`, `gpt-5.2-codex`, etc. - - type: input - id: platform - attributes: - label: What platform is your computer? - description: | - For macOS and Linux: copy the output of `uname -mprs` - For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console - - type: input - id: terminal - attributes: - label: What terminal emulator and version are you using (if applicable)? - description: | - Also note any multiplexer in use (screen / tmux / zellij). - E.g., VS Code, Terminal.app, iTerm2, Ghostty, Windows Terminal (WSL / PowerShell) - - type: textarea - id: doctor - attributes: - label: Codex doctor report - description: | - If available, run `codex doctor --json` and paste the full output here. - - The report is designed to redact secrets, but please review it before submitting. - If your Codex version does not support `doctor`, write `not available`. - render: json - - type: textarea - id: actual - attributes: - label: What issue are you seeing? - description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. - validations: - required: true - - type: textarea - id: steps - attributes: - label: What steps can reproduce the bug? - description: Explain the bug and provide a code snippet that can reproduce it. Please include thread id if applicable. - validations: - required: true - - type: textarea - id: expected - attributes: - label: What is the expected behavior? - description: If possible, please provide text instead of a screenshot. - - type: textarea - id: notes - attributes: - label: Additional information - description: Is there anything else you think we should know? diff --git a/reference/openai-codex/.github/ISSUE_TEMPLATE/4-bug-report.yml b/reference/openai-codex/.github/ISSUE_TEMPLATE/4-bug-report.yml deleted file mode 100644 index 4de8841..0000000 --- a/reference/openai-codex/.github/ISSUE_TEMPLATE/4-bug-report.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: 🪲 Other Bug -description: Report an issue in Codex Web, integrations, or other Codex components -labels: - - bug -body: - - type: markdown - attributes: - value: | - Before submitting a new issue, please search for existing issues to see if your issue has already been reported. - If it has, please add a 👍 reaction (no need to leave a comment) to the existing issue instead of creating a new one. - - If you need help or support using Codex and are not reporting a bug, please post on [codex/discussions](https://github.com/openai/codex/discussions), where you can ask questions or engage with others on ideas for how to improve codex. - - - type: textarea - id: actual - attributes: - label: What issue are you seeing? - description: Please include the full error messages and prompts with PII redacted. If possible, please provide text instead of a screenshot. - validations: - required: true - - type: textarea - id: steps - attributes: - label: What steps can reproduce the bug? - description: Explain the bug and provide a code snippet that can reproduce it. - validations: - required: true - - type: textarea - id: expected - attributes: - label: What is the expected behavior? - description: If possible, please provide text instead of a screenshot. - - type: textarea - id: notes - attributes: - label: Additional information - description: Is there anything else you think we should know? diff --git a/reference/openai-codex/.github/ISSUE_TEMPLATE/5-feature-request.yml b/reference/openai-codex/.github/ISSUE_TEMPLATE/5-feature-request.yml deleted file mode 100644 index 745c347..0000000 --- a/reference/openai-codex/.github/ISSUE_TEMPLATE/5-feature-request.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: 🎁 Feature Request -description: Propose a new feature for Codex -labels: - - enhancement -body: - - type: markdown - attributes: - value: | - Is Codex missing a feature that you'd like to see? Feel free to propose it here. - - Before you submit a feature: - 1. Search existing issues for similar features. If you find one, 👍 it rather than opening a new one. - 2. The Codex team will try to balance the varying needs of the community when prioritizing or rejecting new features. Not all features will be accepted. See [Contributing](https://github.com/openai/codex/blob/main/docs/contributing.md) for more details. - - - type: input - id: variant - attributes: - label: What variant of Codex are you using? - description: (e.g., App, IDE Extension, CLI, Web) - validations: - required: true - - type: textarea - id: feature - attributes: - label: What feature would you like to see? - validations: - required: true - - type: textarea - id: notes - attributes: - label: Additional information - description: Is there anything else you think we should know? diff --git a/reference/openai-codex/.github/ISSUE_TEMPLATE/6-docs-issue.yml b/reference/openai-codex/.github/ISSUE_TEMPLATE/6-docs-issue.yml deleted file mode 100644 index 1957b60..0000000 --- a/reference/openai-codex/.github/ISSUE_TEMPLATE/6-docs-issue.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: 📗 Documentation Issue -description: Tell us if there is missing or incorrect documentation -labels: [documentation] -body: - - type: markdown - attributes: - value: | - Thank you for submitting a documentation request. It helps make Codex better. - - type: dropdown - attributes: - label: What is the type of issue? - multiple: true - options: - - Documentation is missing - - Documentation is incorrect - - Documentation is confusing - - Example code is not working - - Something else - - type: textarea - attributes: - label: What is the issue? - validations: - required: true - - type: textarea - attributes: - label: Where did you find it? - description: If possible, please provide the URL(s) where you found this issue. diff --git a/reference/openai-codex/.github/actions/linux-code-sign/action.yml b/reference/openai-codex/.github/actions/linux-code-sign/action.yml deleted file mode 100644 index f8efb82..0000000 --- a/reference/openai-codex/.github/actions/linux-code-sign/action.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: linux-code-sign -description: Sign Linux artifacts with cosign. -inputs: - target: - description: Target triple for the artifacts to sign. - required: true - artifacts-dir: - description: Absolute path to the directory containing built binaries to sign. - required: true - binaries: - description: Space-delimited binary basenames to sign. - default: "codex codex-responses-api-proxy" - -runs: - using: composite - steps: - - name: Install cosign - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 - - - name: Cosign Linux artifacts - shell: bash - env: - ARTIFACTS_DIR: ${{ inputs.artifacts-dir }} - BINARIES: ${{ inputs.binaries }} - COSIGN_EXPERIMENTAL: "1" - COSIGN_YES: "true" - COSIGN_OIDC_CLIENT_ID: "sigstore" - COSIGN_OIDC_ISSUER: "https://oauth2.sigstore.dev/auth" - run: | - set -euo pipefail - - dest="$ARTIFACTS_DIR" - if [[ ! -d "$dest" ]]; then - echo "Destination $dest does not exist" - exit 1 - fi - - for binary in ${BINARIES}; do - artifact="${dest}/${binary}" - if [[ ! -f "$artifact" ]]; then - echo "Binary $artifact not found" - exit 1 - fi - - cosign sign-blob \ - --yes \ - --bundle "${artifact}.sigstore" \ - "$artifact" - done diff --git a/reference/openai-codex/.github/actions/macos-code-sign/action.yml b/reference/openai-codex/.github/actions/macos-code-sign/action.yml deleted file mode 100644 index 0e19fa1..0000000 --- a/reference/openai-codex/.github/actions/macos-code-sign/action.yml +++ /dev/null @@ -1,259 +0,0 @@ -name: macos-code-sign -description: Configure, sign, notarize, and clean up macOS code signing artifacts. -inputs: - target: - description: Rust compilation target triple (e.g. aarch64-apple-darwin). - required: true - binaries: - description: Space-delimited binary basenames to sign and notarize. - default: "codex codex-responses-api-proxy" - sign-binaries: - description: Whether to sign and notarize the macOS binaries. - required: false - default: "true" - sign-dmg: - description: Whether to sign and notarize the macOS dmg. - required: false - default: "true" - apple-certificate: - description: Base64-encoded Apple signing certificate (P12). - required: true - apple-certificate-password: - description: Password for the signing certificate. - required: true - apple-notarization-key-p8: - description: Base64-encoded Apple notarization key (P8). - required: true - apple-notarization-key-id: - description: Apple notarization key ID. - required: true - apple-notarization-issuer-id: - description: Apple notarization issuer ID. - required: true -runs: - using: composite - steps: - - name: Configure Apple code signing - shell: bash - env: - KEYCHAIN_PASSWORD: actions - APPLE_CERTIFICATE: ${{ inputs.apple-certificate }} - APPLE_CERTIFICATE_PASSWORD: ${{ inputs.apple-certificate-password }} - run: | - set -euo pipefail - - if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then - echo "APPLE_CERTIFICATE is required for macOS signing" - exit 1 - fi - - if [[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then - echo "APPLE_CERTIFICATE_PASSWORD is required for macOS signing" - exit 1 - fi - - cert_path="${RUNNER_TEMP}/apple_signing_certificate.p12" - echo "$APPLE_CERTIFICATE" | base64 -d > "$cert_path" - - keychain_path="${RUNNER_TEMP}/codex-signing.keychain-db" - security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" - security set-keychain-settings -lut 21600 "$keychain_path" - security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path" - - keychain_args=() - cleanup_keychain() { - if ((${#keychain_args[@]} > 0)); then - security list-keychains -s "${keychain_args[@]}" || true - security default-keychain -s "${keychain_args[0]}" || true - else - security list-keychains -s || true - fi - if [[ -f "$keychain_path" ]]; then - security delete-keychain "$keychain_path" || true - fi - } - - while IFS= read -r keychain; do - [[ -n "$keychain" ]] && keychain_args+=("$keychain") - done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g') - - if ((${#keychain_args[@]} > 0)); then - security list-keychains -s "$keychain_path" "${keychain_args[@]}" - else - security list-keychains -s "$keychain_path" - fi - - security default-keychain -s "$keychain_path" - security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security - security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path" > /dev/null - - codesign_hashes=() - while IFS= read -r hash; do - [[ -n "$hash" ]] && codesign_hashes+=("$hash") - done < <(security find-identity -v -p codesigning "$keychain_path" \ - | sed -n 's/.*\([0-9A-F]\{40\}\).*/\1/p' \ - | sort -u) - - if ((${#codesign_hashes[@]} == 0)); then - echo "No signing identities found in $keychain_path" - cleanup_keychain - rm -f "$cert_path" - exit 1 - fi - - if ((${#codesign_hashes[@]} > 1)); then - echo "Multiple signing identities found in $keychain_path:" - printf ' %s\n' "${codesign_hashes[@]}" - cleanup_keychain - rm -f "$cert_path" - exit 1 - fi - - APPLE_CODESIGN_IDENTITY="${codesign_hashes[0]}" - - rm -f "$cert_path" - - echo "APPLE_CODESIGN_IDENTITY=$APPLE_CODESIGN_IDENTITY" >> "$GITHUB_ENV" - echo "APPLE_CODESIGN_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV" - echo "::add-mask::$APPLE_CODESIGN_IDENTITY" - - - name: Sign macOS binaries - if: ${{ inputs.sign-binaries == 'true' }} - shell: bash - env: - TARGET: ${{ inputs.target }} - BINARIES: ${{ inputs.binaries }} - run: | - set -euo pipefail - - if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then - echo "APPLE_CODESIGN_IDENTITY is required for macOS signing" - exit 1 - fi - - keychain_args=() - if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then - keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}") - fi - - entitlements_path="$GITHUB_ACTION_PATH/codex.entitlements.plist" - - for binary in ${BINARIES}; do - path="codex-rs/target/${TARGET}/release/${binary}" - codesign --force --options runtime --timestamp --entitlements "$entitlements_path" --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path" - done - - - name: Notarize macOS binaries - if: ${{ inputs.sign-binaries == 'true' }} - shell: bash - env: - TARGET: ${{ inputs.target }} - BINARIES: ${{ inputs.binaries }} - APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }} - APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }} - APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }} - run: | - set -euo pipefail - - for var in APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do - if [[ -z "${!var:-}" ]]; then - echo "$var is required for notarization" - exit 1 - fi - done - - notary_key_path="${RUNNER_TEMP}/notarytool.key.p8" - echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path" - cleanup_notary() { - rm -f "$notary_key_path" - } - trap cleanup_notary EXIT - - source "$GITHUB_ACTION_PATH/notary_helpers.sh" - - notarize_binary() { - local binary="$1" - local source_path="codex-rs/target/${TARGET}/release/${binary}" - local archive_path="${RUNNER_TEMP}/${binary}.zip" - - if [[ ! -f "$source_path" ]]; then - echo "Binary $source_path not found" - exit 1 - fi - - rm -f "$archive_path" - ditto -c -k --keepParent "$source_path" "$archive_path" - - notarize_submission "$binary" "$archive_path" "$notary_key_path" - } - - for binary in ${BINARIES}; do - notarize_binary "${binary}" - done - - - name: Sign and notarize macOS dmg - if: ${{ inputs.sign-dmg == 'true' }} - shell: bash - env: - TARGET: ${{ inputs.target }} - APPLE_NOTARIZATION_KEY_P8: ${{ inputs.apple-notarization-key-p8 }} - APPLE_NOTARIZATION_KEY_ID: ${{ inputs.apple-notarization-key-id }} - APPLE_NOTARIZATION_ISSUER_ID: ${{ inputs.apple-notarization-issuer-id }} - run: | - set -euo pipefail - - for var in APPLE_CODESIGN_IDENTITY APPLE_NOTARIZATION_KEY_P8 APPLE_NOTARIZATION_KEY_ID APPLE_NOTARIZATION_ISSUER_ID; do - if [[ -z "${!var:-}" ]]; then - echo "$var is required" - exit 1 - fi - done - - notary_key_path="${RUNNER_TEMP}/notarytool.key.p8" - echo "$APPLE_NOTARIZATION_KEY_P8" | base64 -d > "$notary_key_path" - cleanup_notary() { - rm -f "$notary_key_path" - } - trap cleanup_notary EXIT - - source "$GITHUB_ACTION_PATH/notary_helpers.sh" - - dmg_name="codex-${TARGET}.dmg" - dmg_path="codex-rs/target/${TARGET}/release/${dmg_name}" - - if [[ ! -f "$dmg_path" ]]; then - echo "dmg $dmg_path not found" - exit 1 - fi - - keychain_args=() - if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then - keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}") - fi - - codesign --force --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$dmg_path" - notarize_submission "$dmg_name" "$dmg_path" "$notary_key_path" - xcrun stapler staple "$dmg_path" - - - name: Remove signing keychain - if: ${{ always() }} - shell: bash - env: - APPLE_CODESIGN_KEYCHAIN: ${{ env.APPLE_CODESIGN_KEYCHAIN }} - run: | - set -euo pipefail - if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" ]]; then - keychain_args=() - while IFS= read -r keychain; do - [[ "$keychain" == "$APPLE_CODESIGN_KEYCHAIN" ]] && continue - [[ -n "$keychain" ]] && keychain_args+=("$keychain") - done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g') - if ((${#keychain_args[@]} > 0)); then - security list-keychains -s "${keychain_args[@]}" - security default-keychain -s "${keychain_args[0]}" - fi - - if [[ -f "$APPLE_CODESIGN_KEYCHAIN" ]]; then - security delete-keychain "$APPLE_CODESIGN_KEYCHAIN" - fi - fi diff --git a/reference/openai-codex/.github/actions/macos-code-sign/codex.entitlements.plist b/reference/openai-codex/.github/actions/macos-code-sign/codex.entitlements.plist deleted file mode 100644 index d35e43a..0000000 --- a/reference/openai-codex/.github/actions/macos-code-sign/codex.entitlements.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.security.cs.allow-jit - - - diff --git a/reference/openai-codex/.github/actions/macos-code-sign/notary_helpers.sh b/reference/openai-codex/.github/actions/macos-code-sign/notary_helpers.sh deleted file mode 100644 index ad9757f..0000000 --- a/reference/openai-codex/.github/actions/macos-code-sign/notary_helpers.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -notarize_submission() { - local label="$1" - local path="$2" - local notary_key_path="$3" - - if [[ -z "${APPLE_NOTARIZATION_KEY_ID:-}" || -z "${APPLE_NOTARIZATION_ISSUER_ID:-}" ]]; then - echo "APPLE_NOTARIZATION_KEY_ID and APPLE_NOTARIZATION_ISSUER_ID are required for notarization" - exit 1 - fi - - if [[ -z "$notary_key_path" || ! -f "$notary_key_path" ]]; then - echo "Notary key file $notary_key_path not found" - exit 1 - fi - - if [[ ! -f "$path" ]]; then - echo "Notarization payload $path not found" - exit 1 - fi - - local submission_json - submission_json=$(xcrun notarytool submit "$path" \ - --key "$notary_key_path" \ - --key-id "$APPLE_NOTARIZATION_KEY_ID" \ - --issuer "$APPLE_NOTARIZATION_ISSUER_ID" \ - --output-format json \ - --wait) - - local status submission_id - status=$(printf '%s\n' "$submission_json" | jq -r '.status // "Unknown"') - submission_id=$(printf '%s\n' "$submission_json" | jq -r '.id // ""') - - if [[ -z "$submission_id" ]]; then - echo "Failed to retrieve submission ID for $label" - exit 1 - fi - - echo "::notice title=Notarization::$label submission ${submission_id} completed with status ${status}" - - if [[ "$status" != "Accepted" ]]; then - echo "Notarization failed for ${label} (submission ${submission_id}, status ${status})" - exit 1 - fi -} diff --git a/reference/openai-codex/.github/actions/prepare-bazel-ci/action.yml b/reference/openai-codex/.github/actions/prepare-bazel-ci/action.yml deleted file mode 100644 index b41d80e..0000000 --- a/reference/openai-codex/.github/actions/prepare-bazel-ci/action.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: prepare-bazel-ci -description: Prepare a Bazel CI job with shared setup, repository cache restore, and execution logs. -inputs: - target: - description: Target triple used for setup and cache namespacing. - required: true - cache-scope: - description: Logical namespace used to keep concurrent Bazel jobs from reserving the same repository cache key. - required: true - install-test-prereqs: - description: Install DotSlash for Bazel-backed test jobs. - required: false - default: "false" -outputs: - repository-cache-path: - description: Filesystem path used for the Bazel repository cache. - value: ${{ steps.setup_bazel.outputs.repository-cache-path }} - repository-cache-key: - description: Primary actions/cache key for the Bazel repository cache. - value: ${{ steps.cache_bazel_repository_key.outputs.repository-cache-key }} - repository-cache-hit: - description: Whether the Bazel repository cache restore found an exact key match. - value: ${{ steps.cache_bazel_repository_restore.outputs.cache-hit }} - -runs: - using: composite - steps: - - name: Set up Bazel CI - id: setup_bazel - uses: ./.github/actions/setup-bazel-ci - with: - target: ${{ inputs.target }} - install-test-prereqs: ${{ inputs.install-test-prereqs }} - - - name: Compute bazel repository cache key - id: cache_bazel_repository_key - shell: bash - env: - CACHE_SCOPE: ${{ inputs.cache-scope }} - TARGET: ${{ inputs.target }} - CACHE_HASH: ${{ hashFiles('MODULE.bazel', 'codex-rs/Cargo.lock', 'codex-rs/Cargo.toml') }} - run: | - echo "repository-cache-key=bazel-cache-${CACHE_SCOPE}-${TARGET}-${CACHE_HASH}" >> "${GITHUB_OUTPUT}" - echo "repository-cache-restore-key=bazel-cache-${CACHE_SCOPE}-${TARGET}-" >> "${GITHUB_OUTPUT}" - - # Restore the Bazel repository cache explicitly so external dependencies - # do not need to be re-downloaded on every CI run. Keep restore failures - # non-fatal so transient cache-service errors degrade to a cold build - # instead of failing the job. - - name: Restore bazel repository cache - id: cache_bazel_repository_restore - continue-on-error: true - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - with: - path: ${{ steps.setup_bazel.outputs.repository-cache-path }} - key: ${{ steps.cache_bazel_repository_key.outputs.repository-cache-key }} - restore-keys: | - ${{ steps.cache_bazel_repository_key.outputs.repository-cache-restore-key }} - - - name: Set up Bazel execution logs - shell: bash - run: | - mkdir -p "${RUNNER_TEMP}/bazel-execution-logs" - echo "CODEX_BAZEL_EXECUTION_LOG_COMPACT_DIR=${RUNNER_TEMP}/bazel-execution-logs" >> "${GITHUB_ENV}" diff --git a/reference/openai-codex/.github/actions/run-argument-comment-lint/action.yml b/reference/openai-codex/.github/actions/run-argument-comment-lint/action.yml deleted file mode 100644 index 80fb23d..0000000 --- a/reference/openai-codex/.github/actions/run-argument-comment-lint/action.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Run argument comment lint -description: Run argument-comment-lint on codex-rs via Bazel. - -inputs: - target: - description: Runner target passed to setup-bazel-ci. - required: true - buildbuddy-api-key: - description: BuildBuddy API key used by Bazel CI. - required: false - default: "" - -runs: - using: composite - steps: - - uses: ./.github/actions/setup-bazel-ci - with: - target: ${{ inputs.target }} - install-test-prereqs: true - - - name: Install Linux sandbox build dependencies - if: ${{ runner.os == 'Linux' }} - shell: bash - run: | - sudo DEBIAN_FRONTEND=noninteractive apt-get update - sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev - - - name: Run argument comment lint on codex-rs via Bazel - if: ${{ runner.os != 'Windows' }} - env: - BUILDBUDDY_API_KEY: ${{ inputs.buildbuddy-api-key }} - shell: bash - run: | - bazel_targets="$(./tools/argument-comment-lint/list-bazel-targets.sh)" - ./.github/scripts/run-bazel-ci.sh \ - -- \ - build \ - --config=argument-comment-lint \ - --keep_going \ - --build_metadata=COMMIT_SHA=${GITHUB_SHA} \ - -- \ - ${bazel_targets} - - - name: Run argument comment lint on codex-rs via Bazel - if: ${{ runner.os == 'Windows' }} - env: - BUILDBUDDY_API_KEY: ${{ inputs.buildbuddy-api-key }} - shell: bash - run: | - ./.github/scripts/run-argument-comment-lint-bazel.sh \ - --config=argument-comment-lint \ - --platforms=//:local_windows \ - --keep_going \ - --build_metadata=COMMIT_SHA=${GITHUB_SHA} diff --git a/reference/openai-codex/.github/actions/setup-bazel-ci/action.yml b/reference/openai-codex/.github/actions/setup-bazel-ci/action.yml deleted file mode 100644 index bb757aa..0000000 --- a/reference/openai-codex/.github/actions/setup-bazel-ci/action.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: setup-bazel-ci -description: Prepare a Bazel CI runner with shared caches and optional test prerequisites. -inputs: - target: - description: Target triple used for cache namespacing. - required: true - install-test-prereqs: - description: Install DotSlash for Bazel-backed test jobs. - required: false - default: "false" -outputs: - repository-cache-path: - description: Filesystem path used for the Bazel repository cache. - value: ${{ steps.configure_bazel_repository_cache.outputs.repository-cache-path }} - -runs: - using: composite - steps: - # Some integration tests rely on DotSlash being installed. - # See https://github.com/openai/codex/pull/7617. - - name: Install DotSlash - if: inputs.install-test-prereqs == 'true' - uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2 - - - name: Make DotSlash available in PATH (Unix) - if: inputs.install-test-prereqs == 'true' && runner.os != 'Windows' - shell: bash - run: cp "$(which dotslash)" /usr/local/bin - - - name: Make DotSlash available in PATH (Windows) - if: inputs.install-test-prereqs == 'true' && runner.os == 'Windows' - shell: pwsh - run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe" - - - name: Set up Bazel - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0 - - - name: Configure Bazel repository cache - id: configure_bazel_repository_cache - shell: pwsh - run: | - # Keep the repository cache under HOME on all runners. Windows `D:\a` - # cache paths match `.bazelrc`, but `actions/cache/restore` currently - # returns HTTP 400 for that path in the Windows clippy job. - $repositoryCachePath = Join-Path $HOME '.cache/bazel-repo-cache' - "repository-cache-path=$repositoryCachePath" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - "BAZEL_REPOSITORY_CACHE=$repositoryCachePath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Configure Bazel output root (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # Use the shortest available drive to reduce argv/path length issues, - # but avoid the drive root because some Windows test launchers mis-handle - # MANIFEST paths there. - $hasDDrive = Test-Path 'D:\' - $bazelOutputUserRoot = if ($hasDDrive) { 'D:\b' } else { 'C:\b' } - $repoContentsCache = Join-Path $env:RUNNER_TEMP "bazel-repo-contents-cache-$env:GITHUB_RUN_ID-$env:GITHUB_JOB" - "BAZEL_OUTPUT_USER_ROOT=$bazelOutputUserRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - "BAZEL_REPO_CONTENTS_CACHE=$repoContentsCache" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - - - name: Expose MSVC SDK environment (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # Bazel exec-side Rust build scripts do not reliably inherit the MSVC developer - # shell on GitHub-hosted Windows runners, so discover the latest VS install and - # ask `VsDevCmd.bat` to materialize the x64/x64 compiler + SDK environment. - $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" - if (-not (Test-Path $vswhere)) { - throw "vswhere.exe not found" - } - - $installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null - if (-not $installPath) { - throw "Could not locate a Visual Studio installation with VC tools" - } - - $vsDevCmd = Join-Path $installPath 'Common7\Tools\VsDevCmd.bat' - if (-not (Test-Path $vsDevCmd)) { - throw "VsDevCmd.bat not found at $vsDevCmd" - } - - # Keep the export surface explicit: these are the paths and SDK roots that the - # MSVC toolchain probes need later when Bazel runs Windows exec-platform build - # scripts such as `aws-lc-sys`. - $varsToExport = @( - 'INCLUDE', - 'LIB', - 'LIBPATH', - 'PATH', - 'UCRTVersion', - 'UniversalCRTSdkDir', - 'VCINSTALLDIR', - 'VCToolsInstallDir', - 'WindowsLibPath', - 'WindowsSdkBinPath', - 'WindowsSdkDir', - 'WindowsSDKLibVersion', - 'WindowsSDKVersion' - ) - - # `VsDevCmd.bat` is a batch file, so invoke it under `cmd.exe`, suppress its - # banner, then dump the resulting environment with `set`. Re-export only the - # approved keys into `GITHUB_ENV` so later steps inherit the same MSVC context. - $envLines = & cmd.exe /c ('"{0}" -no_logo -arch=x64 -host_arch=x64 >nul && set' -f $vsDevCmd) - foreach ($line in $envLines) { - if ($line -notmatch '^(.*?)=(.*)$') { - continue - } - - $name = $matches[1] - $value = $matches[2] - if ($varsToExport -contains $name) { - "$name=$value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - } - } - - - name: Compute cache-stable Windows Bazel PATH - if: runner.os == 'Windows' - shell: pwsh - run: ./.github/scripts/compute-bazel-windows-path.ps1 - - - name: Enable Git long paths (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: git config --global core.longpaths true diff --git a/reference/openai-codex/.github/actions/setup-msvc-env/action.yml b/reference/openai-codex/.github/actions/setup-msvc-env/action.yml deleted file mode 100644 index 287cb7f..0000000 --- a/reference/openai-codex/.github/actions/setup-msvc-env/action.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: setup-msvc-env -description: Expose an MSVC developer environment for the requested Windows target. -inputs: - target: - description: Rust target triple that will be built on this Windows runner. - required: true - host-arch: - description: Optional Visual Studio host architecture override. - required: false - default: "" - -runs: - using: composite - steps: - - name: Expose MSVC SDK environment - shell: pwsh - run: '& "$env:GITHUB_ACTION_PATH/setup-msvc-env.ps1" -Target "${{ inputs.target }}" -HostArch "${{ inputs.host-arch }}"' diff --git a/reference/openai-codex/.github/actions/setup-msvc-env/setup-msvc-env.ps1 b/reference/openai-codex/.github/actions/setup-msvc-env/setup-msvc-env.ps1 deleted file mode 100644 index e2706d9..0000000 --- a/reference/openai-codex/.github/actions/setup-msvc-env/setup-msvc-env.ps1 +++ /dev/null @@ -1,257 +0,0 @@ -param( - [Parameter(Mandatory = $true)] - [string]$Target, - - [string]$HostArch = "" -) - -# Cargo can cross-compile the Rust code for Windows ARM64 on a Windows x64 -# runner, but rustup alone does not expose the matching MSVC/UCRT include and -# library paths. Ask Visual Studio for the target-specific developer -# environment, then persist the relevant variables through GITHUB_ENV so the -# later Cargo step sees the same environment as a normal VsDevCmd shell. -switch ($Target) { - "x86_64-pc-windows-msvc" { - $TargetArch = "x64" - $RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" - } - "aarch64-pc-windows-msvc" { - $TargetArch = "arm64" - $RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.ARM64" - } - default { - throw "Unsupported Windows MSVC target: $Target" - } -} - -# VsDevCmd needs both sides of the cross compile: the architecture of the -# machine running the tools and the architecture of the binaries being linked. -# Infer the host from the runner unless a caller needs to override it. -if (-not $HostArch) { - $HostArch = if ($env:PROCESSOR_ARCHITEW6432 -eq "ARM64" -or $env:PROCESSOR_ARCHITECTURE -eq "ARM64") { - "arm64" - } else { - "x64" - } -} - -$VsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -if (-not (Test-Path $VsWhere)) { - throw "vswhere.exe not found" -} - -# Require the target VC tools component, not merely any Visual Studio install, -# so an x64 archive producer cannot silently link ARM64 tests with the wrong -# SDK/toolchain layout. -$InstallPath = & $VsWhere -latest -products * -requires $RequiredComponent -property installationPath 2>$null -if (-not $InstallPath) { - throw "Could not locate a Visual Studio installation with component $RequiredComponent" -} - -$VsDevCmd = Join-Path $InstallPath "Common7\Tools\VsDevCmd.bat" -if (-not (Test-Path $VsDevCmd)) { - throw "VsDevCmd.bat not found at $VsDevCmd" -} - -$VarsToExport = @( - "INCLUDE", - "LIB", - "LIBPATH", - "PATH", - "UCRTVersion", - "UniversalCRTSdkDir", - "VCINSTALLDIR", - "VCToolsInstallDir", - "WindowsLibPath", - "WindowsSdkBinPath", - "WindowsSdkDir", - "WindowsSDKLibVersion", - "WindowsSDKVersion" -) - -# Run VsDevCmd inside cmd.exe because it is a batch file, then copy just the -# variables Cargo/rustc need into the GitHub Actions environment file. PowerShell -# cannot mutate the parent composite-action environment directly. -$EnvLines = & cmd.exe /c ('"{0}" -no_logo -arch={1} -host_arch={2} >nul && set' -f $VsDevCmd, $TargetArch, $HostArch) -$VcToolsInstallDir = $null -foreach ($Line in $EnvLines) { - if ($Line -notmatch "^(.*?)=(.*)$") { - continue - } - - $Name = $Matches[1] - $Value = $Matches[2] - if ($VarsToExport -contains $Name) { - if ($Name -ieq "Path") { - $Name = "PATH" - } - if ($Name -eq "VCToolsInstallDir") { - $VcToolsInstallDir = $Value - } - "$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - } -} - -if (-not $VcToolsInstallDir) { - throw "VCToolsInstallDir was not exported by VsDevCmd.bat" -} - -# Prefer Rust's bundled linker when rustup provides one, then Visual Studio's -# LLVM linker, and finally MSVC link.exe. This keeps the cross-compile path close -# to Rust's normal Windows MSVC behavior while still working on runner images -# where one of those linkers is absent. -$Linker = $null -$Rustc = Get-Command rustc -ErrorAction SilentlyContinue -if ($Rustc) { - $Sysroot = (& rustc --print sysroot 2>$null).Trim() - $RustHost = & rustc -vV 2>$null | Select-String "^host: " | ForEach-Object { $_.Line.Substring(6) } - if ($RustHost) { - $RustHost = $RustHost.Trim() - } - if ($Sysroot -and $RustHost) { - $RustLld = Join-Path $Sysroot "lib\rustlib\$RustHost\bin\rust-lld.exe" - if (Test-Path $RustLld) { - $Linker = $RustLld - } - } -} -if (-not $Linker) { - $Linker = Join-Path $InstallPath "VC\Tools\Llvm\x64\bin\lld-link.exe" -} -if (-not (Test-Path $Linker)) { - $Linker = Join-Path $VcToolsInstallDir "bin\Host${HostArch}\${TargetArch}\link.exe" -} -if (-not (Test-Path $Linker)) { - throw "Windows linker not found at $Linker" -} - -# rustc passes `/arm64hazardfree` for ARM64 MSVC links. The lld variants on our -# Windows x64 archive producers reject that flag, including when rustc places it -# inside a response file. Compile a tiny forwarding wrapper that strips only -# that unsupported flag, then delegate every other argument to the real linker. -if ($TargetArch -eq "arm64" -and (Split-Path -Leaf $Linker) -match "lld") { - $WrapperDir = Join-Path $env:RUNNER_TEMP "msvc-lld-wrapper" - New-Item -Path $WrapperDir -ItemType Directory -Force | Out-Null - $WrapperPath = Join-Path $WrapperDir "lld-link-wrapper.exe" - $WrapperSource = @' -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; - -internal static class Program -{ - private static int Main(string[] args) - { - var linker = Environment.GetEnvironmentVariable("MSVC_REAL_LINKER"); - if (string.IsNullOrEmpty(linker)) - { - Console.Error.WriteLine("MSVC_REAL_LINKER is not set"); - return 1; - } - - var startInfo = new ProcessStartInfo(linker) - { - UseShellExecute = false, - }; - var filteredArgs = new List { "-flavor", "link", "/defaultlib:ucrt", "/nodefaultlib:libucrt" }; - foreach (var arg in args) - { - if (!string.Equals(arg, "/arm64hazardfree", StringComparison.OrdinalIgnoreCase)) - { - filteredArgs.Add(QuoteArgument(FilterResponseFile(arg))); - } - } - startInfo.Arguments = string.Join(" ", filteredArgs); - - using var process = Process.Start(startInfo); - if (process is null) - { - Console.Error.WriteLine($"Failed to start linker: {linker}"); - return 1; - } - - process.WaitForExit(); - return process.ExitCode; - } - - private static string FilterResponseFile(string argument) - { - if (argument.Length < 2 || argument[0] != '@') - { - return argument; - } - - var responsePath = argument.Substring(1); - if (!File.Exists(responsePath)) - { - return argument; - } - - var filteredResponsePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".rsp"); - var responseContents = Regex.Replace( - File.ReadAllText(responsePath), - "/arm64hazardfree", - string.Empty, - RegexOptions.IgnoreCase); - File.WriteAllText(filteredResponsePath, responseContents); - return "@" + filteredResponsePath; - } - - private static string QuoteArgument(string argument) - { - if (argument.Length == 0) - { - return "\"\""; - } - if (argument.IndexOfAny(new[] { ' ', '\t', '"' }) < 0) - { - return argument; - } - - var quoted = new StringBuilder("\""); - var backslashes = 0; - foreach (var character in argument) - { - if (character == '\\') - { - backslashes++; - continue; - } - if (character == '"') - { - quoted.Append('\\', (backslashes * 2) + 1); - quoted.Append(character); - backslashes = 0; - continue; - } - - quoted.Append('\\', backslashes); - backslashes = 0; - quoted.Append(character); - } - quoted.Append('\\', backslashes * 2); - quoted.Append('"'); - return quoted.ToString(); - } -} -'@ - $WrapperSourcePath = Join-Path $WrapperDir "lld-link-wrapper.cs" - $WrapperSource | Out-File -FilePath $WrapperSourcePath -Encoding utf8 - $Csc = Join-Path $InstallPath "MSBuild\Current\Bin\Roslyn\csc.exe" - if (-not (Test-Path $Csc)) { - throw "csc.exe not found at $Csc" - } - & $Csc /nologo /target:exe /out:$WrapperPath $WrapperSourcePath - if ($LASTEXITCODE -ne 0) { - throw "Failed to compile lld-link wrapper" - } - "MSVC_REAL_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - $Linker = $WrapperPath -} - -Write-Output "Using Windows linker: $Linker" -$CargoTarget = $Target.ToUpperInvariant().Replace("-", "_") -"CARGO_TARGET_${CargoTarget}_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append diff --git a/reference/openai-codex/.github/actions/setup-rusty-v8/action.yml b/reference/openai-codex/.github/actions/setup-rusty-v8/action.yml deleted file mode 100644 index d9c4484..0000000 --- a/reference/openai-codex/.github/actions/setup-rusty-v8/action.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: setup-rusty-v8 -description: Download and verify Codex-built rusty_v8 artifacts for Cargo builds. -inputs: - target: - description: Rust target triple with Codex-built V8 release artifacts. - required: true - -runs: - using: composite - steps: - - name: Configure rusty_v8 artifact overrides and verify checksums - shell: bash - env: - TARGET: ${{ inputs.target }} - run: | - set -euo pipefail - - version="$(python3 "${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py" resolved-v8-crate-version)" - release_tag="rusty-v8-v${version}" - base_url="https://github.com/openai/codex/releases/download/${release_tag}" - binding_dir="${RUNNER_TEMP}/rusty_v8" - archive_path="${binding_dir}/librusty_v8_release_${TARGET}.a.gz" - binding_path="${binding_dir}/src_binding_release_${TARGET}.rs" - checksums_path="${binding_dir}/rusty_v8_release_${TARGET}.sha256" - - mkdir -p "${binding_dir}" - curl -fsSL "${base_url}/librusty_v8_release_${TARGET}.a.gz" -o "${archive_path}" - curl -fsSL "${base_url}/src_binding_release_${TARGET}.rs" -o "${binding_path}" - curl -fsSL "${base_url}/rusty_v8_release_${TARGET}.sha256" -o "${checksums_path}" - - if [[ "$(wc -l < "${checksums_path}")" -ne 2 ]]; then - echo "Expected exactly two checksums for ${TARGET} in ${checksums_path}" >&2 - exit 1 - fi - - if command -v sha256sum >/dev/null 2>&1; then - (cd "${binding_dir}" && sha256sum -c "${checksums_path}") - else - (cd "${binding_dir}" && shasum -a 256 -c "${checksums_path}") - fi - echo "RUSTY_V8_ARCHIVE=${archive_path}" >> "${GITHUB_ENV}" - echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "${GITHUB_ENV}" diff --git a/reference/openai-codex/.github/actions/windows-code-sign/action.yml b/reference/openai-codex/.github/actions/windows-code-sign/action.yml deleted file mode 100644 index 634d647..0000000 --- a/reference/openai-codex/.github/actions/windows-code-sign/action.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: windows-code-sign -description: Sign Windows binaries with Azure Trusted Signing. -inputs: - target: - description: Target triple for the artifacts to sign. - required: true - binaries: - description: Space-delimited binary basenames to sign. - default: "codex codex-responses-api-proxy codex-windows-sandbox-setup codex-command-runner" - client-id: - description: Azure Trusted Signing client ID. - required: true - tenant-id: - description: Azure tenant ID for Trusted Signing. - required: true - subscription-id: - description: Azure subscription ID for Trusted Signing. - required: true - endpoint: - description: Azure Trusted Signing endpoint. - required: true - account-name: - description: Azure Trusted Signing account name. - required: true - certificate-profile-name: - description: Certificate profile name for signing. - required: true - -runs: - using: composite - steps: - - name: Azure login for Trusted Signing (OIDC) - uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 - with: - client-id: ${{ inputs.client-id }} - tenant-id: ${{ inputs.tenant-id }} - subscription-id: ${{ inputs.subscription-id }} - - - name: Prepare file list - id: prepare - shell: bash - env: - TARGET: ${{ inputs.target }} - BINARIES: ${{ inputs.binaries }} - run: | - set -euo pipefail - - { - echo "files<> "$GITHUB_OUTPUT" - - - name: Sign Windows binaries with Azure Trusted Signing - uses: azure/trusted-signing-action@1d365fec12862c4aa68fcac418143d73f0cea293 # v0.5.11 - with: - endpoint: ${{ inputs.endpoint }} - trusted-signing-account-name: ${{ inputs.account-name }} - certificate-profile-name: ${{ inputs.certificate-profile-name }} - exclude-environment-credential: true - exclude-workload-identity-credential: true - exclude-managed-identity-credential: true - exclude-shared-token-cache-credential: true - exclude-visual-studio-credential: true - exclude-visual-studio-code-credential: true - exclude-azure-cli-credential: false - exclude-azure-powershell-credential: true - exclude-azure-developer-cli-credential: true - exclude-interactive-browser-credential: true - cache-dependencies: false - files: ${{ steps.prepare.outputs.files }} diff --git a/reference/openai-codex/.github/blob-size-allowlist.txt b/reference/openai-codex/.github/blob-size-allowlist.txt deleted file mode 100644 index 9375b49..0000000 --- a/reference/openai-codex/.github/blob-size-allowlist.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Paths are matched exactly, relative to the repository root. -# Keep this list short and limited to intentional large checked-in assets. - -.github/codex-cli-splash.png -MODULE.bazel.lock -codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json -codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json -codex-rs/tui/tests/fixtures/oss-story.jsonl -codex-rs/tui_app_server/tests/fixtures/oss-story.jsonl -codex-rs/tui/src/app.rs diff --git a/reference/openai-codex/.github/codex-cli-splash.png b/reference/openai-codex/.github/codex-cli-splash.png deleted file mode 100644 index d0f50e55bd2fb212496fb874718ab1eee82add11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 838131 zcmeFZXIK;6)&`6fE1)Q#R4Jkq5l}jbh=NE{kzS(o-g}FP3P@ABQi38NHPSn=0HH@} z=urZM76JqaA?+J{o^#%FecyH7_x${RoXj=MWM=lB*=wy?`(F22A?AUe=IIj`PcSht zoz}W{=MfVVvojOZ;eumFfg=Q{cqbFnDQzcp^#@w&>H-hEJ?x!a?Uz(oPRU>z3g1O z8*h8W{d^7=FLBIECbPYGOaOU$8q5e;gQAGdq=ZAy2Cly8+I%3%w3&Zov;OC;owk(6 zaz)%ff9bO>9#V~Xa0%PdcITkUt35U#mlKCg zf0*$0+`7c9b*@t7TPZ`9m(etbyBan6DdteakfY7Y*#b+9?yCw>%Uc6%)=a!HIk z16+Ok(O3OvV@$IbD+Cq7e=gLAnoa#|l$V)TSrcIB2=K3~=-HqEQvSfQhCgRd)t9;(ZE+hvJyh)2&`J2~c>@r$EUqS{VR z5W0*{wI}eR($@2iZlpe6eR18d{0s8-w`Yv&9Hqt*^OsqcqR+DnMOmMTiF`g%fwir; zA#K%%JzDwvtHQR$lv*06tYaecYvOIg<|A##Uw_~I(D3qzr%&*l8$v-(?wptmd~i5C z>fWhyKZpmJHQIC8HeniH#Pno(i_AseC@K^@>N=t?(9e8EfR%2{az!gj_1?k6OG4x* z&4VT{ufI|;F~l-*_^lDwEjsV8$aH@=KOT2U+xoh6G?&JyOA}a)dj7RY&F^wdM#fo@ zA*b6n+H=-l{8S6PSFg7yJe4DMH7#-7`{3e>y-Shgu3aT?{pn#}hi4)?=Z zp7Ql9oTMo6CiOJ=#X2$Z)iV>&*k2{$Hyky+KIEPYyU*0_KBuG0tV?=&-mo0epE!Yz z`yEPrrKwSlqKHuh$28-r+tRF=E2_qhud0kbzj;v3|CG!3i7!H8D$Q6v*46qCBeO@T zT`!e{NWJOeCEcIC=lADN&gTV}-cqT$qN%E7St<>1&jSN#V41p9I$(G z-h@@`o6(m;!_Nrc4*Wc3_Vu{uQH#Ur-~2q8*K)3{^K3mw|2p(sK=?(%^)u>irz$jf z&KSRl+`aVep77bIPXbH#I0Wyebn@7qdGxAOP3$fHp5*2sZ!X1%EKQ|1lbwe?ag*7D z60NVC9=WUgfZO*}w_1ba;rGw2g|{3H>mlc5vuXk*bxVc#%c32#|8yu~6UaVH4xpd`%8 zo&ALOZNGIkuac-ApR{y=NHX%A45xH}jJ|A>Z%kutW9T%`w9GWYZ_+Ph+jnbfD{0Gq z*>H(>V8!;S1UCmP6=~L`^tJI@Fx=xKYJ_t|Y6y--d}MSyNl|+iF_t_01oq7@lJv^; z$CDoxKSo}~zaqtX3Nh!I^mIs5W$t#!P4Q?Q57G+Rd<2( zF5~@+_nkU&@3U?!N!z_Unz4{kF01N1;ns13D4U#7CaY|}!)>0}XNRj0A1fSdS?E}J zx^SO}U%)T$OSnp&m3%F^V>(s!$;?|qRuXMmYqn$d&Q$lydmBTC?bw^`?`^W(TE6X- zZgX>+XWC|p$i>T{Tu-}_Ywy!m4S8*{}$$mSd$_CU_ z-!#{->f0b|Za(GSAUiEuFXiRo z$C&%Qbcyttw765fsY9BY3rf!GiTN#o%OVN}9trLvId25x@V_m8g?**4@{7VOV5%Tg z$7BL$B1Vz^7XA|cHK56(kbhEY+9MNEh-g53MNBZBKC^pv<%#iEgK|~*sHSv^;7^eU ze1TWiPopyB(*snFojh?`?6foM=yAPM<)>QttCn(ek5A#m$(vDQS;0w=OGQ3DWh( zwMnz%^qMoQQv|h~?u+DK&R@Ha&pfB%MEg{JgH)YV&7JHfipmNNe?5@Wd{whRrr<;Y zpN{A!(L#b2<4QEgl0EnI|Ht=wcFk`h`2`m>0vN&74Z;sWlOwNEL2&@spH$b3E6zvNqfa!Gr1L~ zhxY~osy4>IkC*xM;v}+&Pp$>5?F4l;#|4!UC2>+Ym>L0JwZ7ZS-=|)JIh)SGuhLx} zZhl;7zZ}ac5x%9nDDNloUSu;VQ#&-x)$+Aes)QRv&V9VY4yJuKpR?PdySoQ;jq@5} z9Y5wF-in93ZTJ|LkL;E#i7xq6;$FG~v8>Vv(H(fN=^|R{W`eXbZd`+J8=zrQ0pgg( zh(@)B4^@JdAD!|>It;nNM)t;5@DrsIbzOl&mAW2sv2u{{vtRPVOS(oY%LsK^wbK)P zjV&K03K~P1y)=I(h$<)h=g<;}miTLVmUc}beA?PtX-tWt;fkqw6EYChtyHaNif)m2 zBg_*bTRn;ciZQK;HE>t(7<`H`$+`rdQegAS-4?(-p(T!j-Idx@9#1^G(d)eE*6Y6N zdf#bTfhWAaX1tL6Q@KkKjsM-e^%-fS#U8 z5dQIM(azABLR9u~MW0Ox(rSfIE6%Jx1O^U`@cFRu5!wDqj#R4-ZeoOfp3ZCeorKFT z=&?%ARPG4iX_W4G7e#9kyN<0r+1>b}w?1t8X5eUfTHz$vRj$F@Gny@{%j&o;P zyvlU_cf(sHtuPSJmFPVk1I`rrGK_;dsC#J)HU!w?&Ij;OJ)XL z1r8kdyl3je#Ka-8zaG$fB)ksvKj!q<#MeYuN6E&+?b=gY4{N(?fo`7r{V=HnDgnE0 zcD_#q0^MBQeUt)K1^-;31nlo0mJ$^BbBV8us-TJP0|9jpZ#x0GYuB$`7X+OU5D-xD zwzXG!bVuXw=D;^qK}TO-PbDd-fPjE&0kYRTyd9*Z6%`ewuFFWt$VdWLNcsf1`#uen zboUYZtCN59bH~od#@or$*U7_OV87p|)*gPos)B<1H~Q!2ujjN2bo!q=x%>P*EMS0A z`)8!2uU(h=XWu|mmHnei51az+TutvdxdAc*?g5gPR=lqAr@{Z{)c@S_Uz(cyOH&0! zg@0}OFQ@+BO$~kQywyG2fSdY){zqYdH~!a?e>YT-+8_JB$l@YrKz zofuNIB>{@$ak``b7}x@4w*NV30Q|Y~*B016!md3fHEzqqbc;#rj@skE1FJ1Ay>64H z7=?dF4_+jkC#rV8jjD$>p0vPqE=5XpbN`{v#i zGTV0Yy^T+Z$Yc3W zl)-eeX6n#?pfsk#{yNWIPVgSm% zi`X_F_YZ@H4c&r6A=xF_Bto%+%Yu~J7vTE4Dhvwz+^V?bhmio&074ApJcbAjn_U^zg50%5_k|%R9k5i&g zsPD!eC&6Ix8?C>{+=wuoDAHd9rKek80VWII+~H(IGDPTKT7>BrIfJ9YVa9CG1h2FV zHt1RCGKh`X%!pNmLSHN5j_dfe=KOjPW~H~* zc{_MAM2>&Lb#bHkcbF+nOOSPN#hFcHW@y^+9h0%jt7Uf?zf#u9T*n}*@Z|M|x~w-H zbXO2$3^pYM$eyr3j9|Pqwxi=&^)G*4&mn_}414KlFmB2$hh>()EJW|8^zCc@YuTA6 zA9hCT2TmMR7J9&bOLX8~i7VgWF^NH;><Ir29&T+5R>N0lFYvhewTyqN;D4B}y$odcp6Y zvW&}&TlBlK3lfjOT;%k*lS$%FSPvXisR!&c%o7u$5k7mEC)9QTsTOoZaA~IYWx|Ge zW$Ikf-O0nd?vwpSu`TbJD22smskx9|*{d(09D)rDoo6_Gp0tzkW3bRosD5|5YB(lYJOUNzOT z|0v{Fg?gch9`Lwd^W}@;RlbIo0o~p#47Vw7W^ab_xHXFkRImOC^b2B_pPG2=7u4ot zEqP#*J4QYl+$*dSji{ZLua#^tt`wyU&^xmu7&ngtdXA#T=Dc(Zf3d4>;p}Ci5)E#9 zv0JisQdKpk>}+DO1nK@xQJ#1m7V7fUENM)DDT4AAa7!d?Q+I}5+%wR0VHxQBs{TEs znhU?Eo9N1mh@P`scU~Q|huZYW@ZKuX$((T2laOGgksr%YS3pByKWIpzvD|n6uG%~P z?a9|x6IV+_&BidBPP_cmoV$=&*6AZ{kTzHwMmr=U@Hv^8$ch~AMPxviIGdY4=d123 zc-mzPVkCVM!@jR+II0efO5ZrTfmTZF9Wd-adf?a;E1=~T{+YWSp4Nx6O3js!6}?Ek z<)UQlr@n7a9EVk2h^^G%Y;)((cW{e~f%M5rI)%4K-`&)>`zOKH+N(^*O`4V>xK(otBm4$JYt~=pkoKz7 z4%&;0&J1{~4HXtfRWE<1%P;6nT!79~jyC@D#%~#p0qP<5qrodV0`#=$VIv)1e*zA1 zv>if4IjPdp9J>8@9iDUoDh59 zs9B<=F%hqX#2)4l8+sMK>u~Tp4Tp=yu~OKT#X{bBoV0bV>>#@M! zi>T~Ot#``uNk9S;WUPb>03K6n4$=v&5s;CKphi-Uk<-Jv5o3BACje<`?@Kd%MshD$ z@UYl=cVVE*nNp=W5A>o6dl;|ig>tM{&+`cOHmvBx4MqPi#$f(}w85BguPX)Zee;)3 zF=ii?b~Nde@;OWE8PwmeetJFVBjPBR!5C4cKQq$4pd?{t;c2^(yT-B26M$eEK0CC# zZr-PAQSl)WyCF_e8H{A$#p$9B5#7AjJE)mtQg${p(?L*X2XL3IX92nig1NBg_wn#q zfss#YUCc*x^=?bBpI&zEKL0@Er@LfuR_YURt*q@vvPM}CIypRb^%m5NkDYg2>e>`^ ze`cK20hFuRdVnE*V#1bNKJ3UwR7OVp6-iFs7zB^OuHyaG`_z2pc2YbkxUc0oV?b4i zFZ;FC-YeQS`YhC1UDo&qV-}bUqYVJ)NSZQ1Q$tLYX$N}0qDSOU%4J-m%RveT~ zipRlSO{Krl7vFuOFF0KGbJ)F5=`ldWavfq|+iH*fX81waplpDj>f*B%0{o{SU6@|0 z`hW6l5G+fR4-ju>Z;JbrmrQ* zqolSlS9IvK$S?621-` z0U)W#L$ir}yAiuRI?>?t3gHcTVq)7vGI_=%ahDzX`59T0rga7|Z*T^*&An6aI$$zq zpkAa|LYrrQ*B)Km<0iz-5iNNFd}+Y?bX<_VXWB9euG zSFo!>lX(1W=i#D;#+g_J`BayZHd?oV$1w4Q0sCp_bH9||l@e|KG5CBCzL|CJ=FY%C zNqSGXB6%g&j!LfRy?nWa*-8*yeTy}Mfh?VZP}WUW2RtZN?hgsWlj^O>wQG@O=kdj(Hb!QOzI(WI>=IxafjXq`6wMV=bT;F z@|NnbtN`XTbti=C+FpZSg}%*tN#t4~>(48!l~Jp>z5s-AlL#;#_nM&JF~=JfN7FgU1s^VCM{Av4#_crO>>R(}6sQY=ylrp=guc zZ(tHD2}U>$&J7CZaU@blo~O8(<-l|)F*GO4m3F`(?9Hj7nCXbg5x(f(fLE9W{6aKB z=h<%ZZWYK=hnlSzwXmkB7ygl|IUBoK)P`xp^ny2g=g0!|%XD|r_YSHMNpoBud8saw zVL1b}bTdYztXOo59dw;;qEUT5RYWlzUIPKZiU6{?ktGlj>V5CPov`mk+1qy?;~fx6 zltCS%VLScB$H>_9qz}4o)m_rHXw$UF8<3K&^$e~rf5=s5qX>^;&r@KfM7`54?`nnib$N%+yH$VR*Fj zCS;#8WrOYx5m%%iQ;WFHKt=3D09Rs9f6FyVR9}Fu)XGXTZ-{O&)=a(>!pKw8uinFPi@b-RXjxT-0o9N^jV(+8VM6T_ir zPt&KM;T(bVFW_(vYw?PGEG4yXyqrJJ-8~Cc0--_$yaIM&N@5Up^8a`_&2u8DkylD7 z$2VE$_&dSF%oo`qJEHS^{m_XSvU%l2?b*A?LyfqARH#3QV!zeov3!&Juz+VFkbem& zj1eTQGYbzQXkFn;LxDYLwS}%O2`S%Xwp( zke$tLG3Xa~#Zr75e@nEr+{47t2SWMKzi=q=>VfbbuiYgNvmn?lCYmWz6qv-1!Z?b2 z0gpkbiPS^%^j-C|x4Yuf4u#UnCjpf#V{>-RTYpHmh31`72Hr-7Eif(_Mz2(`G{csz zSbuC2B2MX~V!vfTCD!Qnrz&qj3pY_+r+fMp=$%;$qnntw(mR8!8Eaf*j5IxkAFqU#1d z0EbK;!-vs_6c;D^5dJBEm{W6n8^a|Bi|rL#O>ZCe9Zys4HXDU3ljg!D zVrM|;l`!oGLd?nuNUQ;L3WUwp%*(0WGy7Tg!hE>^N<7x=^LQvh_Gp(sxOum!9OIp@ zX>PkRcB&=7{D{z!Hv9vJl1Ys=Y;ctJAbZ^yk#xQHc1?Ghx7mR`-XedNbu{rWe_bK9 z$l?9N-5NS?&LbPmMi*^8&sgC~0H9dxt^w*f^kGMZA~4Oe25B!qTUmqWzEZWhGN84T zXJmPz$0jSuir!9qI(}VBi~ymJoE+R}!cL7eR(vq~Cdy zRmNWa{@&8#FKu@#uNUn;H~1kV&&>!cWQJ)6noj%%cu;XOYZS ztXI9g2dIHD(R4#9S_=LH+KfYD8xd)mzfJO|p!>#9AwF&cy~9KipZuxy3mRNn6AD^K z%!&DNsuaC$zA49X6E2K4Sn? zvrdcFSgJCxAtH3q29xZP__~c8?nGRPTRK;V+nOz1zH(UKZDr3!yqRD)q22M@r^}Q2 zZiNriH-~^1fX|Ktz)F4}SY=}G;#)4gvDIkx8@EVk=_-zz|Ga7u_SE;zRruWpF>i!o ziM-jco;(LjXpN~~u84&OuH3uFV0zKt4Q(DCGP1qxQ$udBWj^<|XoKvK!tOXmHn-mP zDC)r)XU7%#6*ssjuf~CC!-dP+P6Mr+>8+m4)R_1Jr)&J%=B?Q~3PxM+Njz=@9-Go{|#M7>@!$xus#+xpo;v|0{?q;NBU3P^IpUPnWDn?SNCwnp+U`QN|PWb z;l$Lt-JPDQJsTyqB{O?QEYe^ows!6PydT+nVh?T^Ey3?__I#lg=2)6gs(P6m_e4kp z$NiR{?-AnpvvC-m@1IF_3)RB}x#i3xYyUa1lQ3)Z$_2T_D5#KUXfF-NdU>~DQ6EJu z9g$_{W;cI>gNCQ=PYQq&MRiO3f+928x_H7U#a4x!({&HlO?yBCUrI;a}2;Bd{V*LxGSkWmMF%Q8C`y}nuLOTEWn6aqVn@N zu;N;;lpB9KdQPpmNJaW?x3IB!Xzb)@b7^_Lp6`zRKCL2u4=@+*!*^?vMc5p2t%U0< zVfTJXin0~j6h1<;e=5P(dn$&%E^7EN`Xp#N!UA;OFrldRo0%K6Zzc;_RK&XG!8106 z97X>i<6>{i!E!3q%PQu~?~0{|hJ6eabe{bXdt%?Tw!&i+j;R<1L z*;=|L*;~bX{u)W_4Op)U!*|Q6hYT9zq%7`3FTa|pn`oS%+r35UUEMYSDd}{@CQ#d6 zc6YR!py3klHM>4L`FHn<9Aipq)0nE-8vr^MS(vjS2C+G zfH2u(yvJYu7xaBg8{;fgLC)xo8ue{p_O5ZQ%#IrARqa6dHB=0^{WX9vGXcsZ(GOhA zrM~mD8rBAzgU&+2q^3|C=~%IN1C&M%Yc^(_TDW~hZh%8|91|U`nrB0oN4b;zReNTv zSCA^P-H+urzV7lBhL;zDCtS3_Lhx?iGOhRI?QVQK{&nFbq=&Ag5mTDvL3`T+@RehF zfHC4NuBdt)^iuNX(0Vea*m=A?sBOISc$xC4(`t4zD(`$uIX0Ky+ZD9Vl@##CWi^7O z^Y`3saCIqp;S0%z{#i*5%$co42(QkD1f0d1F8OPe9$8d8@Ak5OWF|QH*Dih&W#wS4i%mf1pdSx(dR}3 z;}?QVC6jUVl4qu<$F7k%(v!kD;7Nm3{m|n(0qcr+Qr{WB{zUGa0xv6n!*gEZq8s-p$@p6y8rF z$eqxFga(g(u)2=`SENecO`B*xP?AHYICMTjWBE;u7z3=(KV+A_1Z`s#U=eq`HpTv+ z-zZmGYq42)+xLB5uJZIu26XpF<1Cx`H);>RSf}cDYC5^jrf2R|22>h|RZ~??k-_s2 z_)SIvxMI3tjBBFHP2E$2Hieuq}5HUtJ zmwA{Eek{fX1s*g88vw$%2tz6_z_jHx0l{4c;J^I42j0&Q&oa$&sca4m&CLGQ@+?=}Hl~{~$o6T$;TPht@#jf@=Mc@tUyti|Lat#yXugE@>P3Q<_RmnOn|k=ezexE8xBsl7)Fzq7DI zIz_`$_>KM~c}ftXQGe*4xt@(b^W&sV*Y+-KcgQPf;n=&Xxn_=)Xa>%-N5xne2f${94@IOpKzrLQ_H4ps>kf_%s#L(OqqVsW)=2P7}tDW+pN6XT2ws! zLUsU%;Z7Uu2B^(Az==;U zA`gcj4<>GSS}gd4VXoY9us|GmJ&rt1W4TEyG*O_u&7W;fKDzM>+DiR-V6st zgU{O3MVD?oM#Y5Y_sLDI^QNdPsEdIwzeZ{U zvjH=y1=I8vxfCcBDaD-YuJnz=21XpnDA9$FU)&A!y*Gc)yHBaQ-nmdj^;#7+g=J}I zhUGFiEVN#UR8#IxH_|1nmO{MD0+?DZS zbmK@n-g#>Se?JPQW;?-@u}wrJjW}?(UI~QHA*;5VZ3d zqiw3s%eCZ*?svhNN9vN})X;+vf@a|boUx5BZ1s+aIwQqIN^z868?uzCEYR2DzVo&& z%Q1mgB7OhW3RyHc-`u-n!x&tf2+=Jyz&Eym!X@vaQ7Q{z1~0>gU?g;$8DXJ)ZX>K$ z6ng^{Z|oQ5YQ-Yf<{Sxymo_dehv>@m8f^C+r_tyD;{bum1=70{pFmd&U48!MP@;}s zDF-m$svaO@)ec=Zt1ha#0lZe4;eDz`XG|908g%JuLAN)hx5pUZW0llXx54jh5M@DqGK>|v2;_<}$DXstTADby(Jbf? z4q+grXj@akV(As8T{h*~{;(pkhYEij#NvSxa0l((76eNs%^9*vl;oyb-f(>yp{NJmZ3Xj~vrid0&or zDPd_mR+AiVD6>^1mP9BxuJ`?ZSjvs9OefJ}&^5%4#X7Y;V6kY?=pcn%c3`Kr$NmMa zhEYT40PxMW?_YHPS!j>3ea=H-KA%0j8*B28ah+j9@1erToJ9QvSpW3QG`TLcYV&!& z`5K7fL=$IVe5P}hcWDsIC1T8$f%MCA+!Rs2{ah-cb%rXGq)N{UV##cuNC}1n4PH+M z@ur`ezi)xE>;F(9HDLipri!m|TIP)m?9y`L=F=W-`ayPSVy~9;_a5LCpMQ;1-xoKo zWhVJ;!Yk8PRvnfh7kvDr^+u7w@x-J{iQmdAt<IZG$2Gj?;<_ zx=X0%_QunY4iMRdMaO#x7S~s4FeBb8b^d2;)be;l6J-7v_A9b>)Nb70vv9&rd9T9b z?R+izBBK1=2Ml)&*F4T7tW3JtsDlueoftOkHA7lewZgC`MTYNEtahfq($XEuSM_tU zH!k4F$)^XZ*cr1vtNN=gIijl`s>(Z0cVTT;D(V1mlo+bJ#!S_(%PL{LAtyeeBenro z=tM5izXCXYKf!Vqn(lEAbfrIKw<%O~qs~%vBfzylsNb>|yrmLswL0aaPX#3&AW zikS$4t4ifIUWebTn@NNASvy$^*9@8<@g19pRCc~e_gI8Vd7FJLc<_UFi5|?umAcf_ zA$l1^;@Xrb(O=W1Lf4jNOO+-zt&As!M+{}*Uit9Gj-wIGQ2^d#?GI_;6iOZFyKF;% zSw~}Io~9qDOP*SI;C#>mFYJ@z#yema3DFB^ok+Qt*1fG2#NZuQSn@vMe}7FstHT?{ z6q@1^g-wdH8`o6!St7}wzmG6)KrBaP6(-sjd6X@rmwB7=Gf!k%H z<*NLm-pmsn;dg+5%}{o=IWg<9S774LzhLG#%lMI1*1r=T51K!{5%*%>;M^$u=uFny z&r-hQ9L*u8=fsZONG0RCjXLey5*7LrB<<*Z0L1{XPYamZSTqV;N|_upNDYZ~%hoqm zh6IGnr?&)^2MCcNBk8Mc(5_@DB0R0Q;fIXI{UCxz@=$(n|E9(!W~VGq2gz*%{gk(G zHUFVzhs{x&?8z>M)J@N$0580s;3iR;=%2Sx=okZ z8P3?2iBynsbmwS|LgAvWayHIn8=wJoTf0W5vppfZa2{AKkl%LLD^*e;U$x zggz^CG`j(U4b1Ps9d9--192!CYyS+A+m^y3>DvC=A~k4Ux3Hw*7kb0N4oJNKlO7lN zIzyN-1Ut6LN~`ds%fv>>r$Kv(pB%dI**!WlMsjuDPm8j73fPN^OLoJT>hhrPpf#uU z*G4NvMm9V)NBRxzRh$iwHvAvKKGEG^pnBs?73Fv9$n!6s94*F2k>KUHQ>3twkK0|y zx9s5)6m30Il{CU=!N#lXttK6~GgkNIO_sWG9)zDq0+i!B%R!RrKH*QP3xphX%yVUa zg3ZcFWsiP}u}?|Ym}dE5mp2qwk-n;+(p5KfrSM5q)AF}afuM=i=oTh3y>Y#E%)zv9 zTC%&_3R27mQH|OjP}EF!wkHPqF4Uq=oalJ@AihV7l{8Q}UDs)`hTK4NO&NDRc*mJ3 z8{R`wP)*Rdb#r8L$0H5HstpbJ>0_!$ny0`w5;7@R>7vjU%l?0aOMlrg-v8OI`Zs&l z!gHEupjV%{GJ;FPTXvglRx02ZMKtu9opQ8mCXM)ffL*9Q~i{)NFp*r`Jfg zsY{`GKkN&AvWYxISOuZKD)^y{T`I;;VveV1jWJwO{|HTe6L@LF!MRWpy}7!m;w?vn z7eTz7=CC3Nd4&oJ*%JFgS|)^a~>z6&RG zhH$&ZOTWTY4qa>9B3_;bmwR4mAEfUs{kk`eQdo4`%g!~8F;V;`w!tNA$|9<#6vL~0 z(dPj;lRhJbmZH&LBz2^CEjp+PRq^-s2k<~UmRAP(FR#UJ_m??vk&EoQDkPSw=?Xes z1M(f!4Pe^6X%|6Rqoqd;^NSN|l*w8}xYv$`PeTVmKVmyg_t{(?Kb`*U#oB8(R9;U5 z<05koc>2_|`xEG$gOtgz>i}NXrdCukw?SgsW5Dx5tD@0}{mMIU8M6;JfVmC>q-hXu zpqCgQAy-_VKI(ZX7yC0baub$GhEU@CyShHFsrwe_)>fnLQoJ^&&N@T#!rJG>h__vH zz+JwrhTbWRz6O?&k|b=fqQ0J-G`v_7>T;ucl8>Q);hvrd`KIJ8p&qINBK079RJ^>ksnMQptrbo7-Sz6{=zEB|My#kr8#`-Gv=yEHU zcv<+rznHuovs?Ufm&6SHWQ4Jn|y65=9k zz6kv2r7efmQ#5s%^qS8PKv-(S4-R zi|7If;)W`+sOBeQ6xbewQ!oljP)=qvFHuXplU&I1B*`#L4)nr`@KLl$$6c#=2g$oP zlhCpbxEqhlgQjb8z+M{%i=0D}ALEQeoJR{=atJmfl@`2Lb(@DV<+ZQG^ucK)1n&Lo zcU6Who%h4#gXM?}8%pO>)(A?>zNHT}HE7xt-F-9Q)%gTBB$M|;hYh|Wvnw~1_Ap`k zN9?!Jcbv1`^*v|Ce~+t4FgQ}OqQR2(QS(^u2TtM+AF&qcd^Pc{TX0ft$aPC}Z8-*K zCEQm5=AGA@f5`yY{^@0AFN zz9Gb275d#JE`$IZtUlL=@2>Gm>_Ox#|idl9mdIV{K<-yu92|$-N5!^SANGNX^-x<$;e>;wmzn*2xedEUHvdYwOyiUjt<8V^jU@9g9hex~AaC;F>Mf zwPycJ)$O2^8J8YozNVlKMTR9ACCym1s9S(cltO|gN^szicEj-Mg)g-TCCCC4UIy(& zB#=Ggb;Igvn;Tt$f)?osh8s2j(R^$LR0JP=O1HP;c%&lBPp*4ZwUKp`W(_DMeqSk* z%8d@#m>(ttBc4~MJWo|Ek9|)6@#WH*apREFO?#~C557Zuk#4GXTZRbbmkL#Du7c_; zoqO09qub`NPZJwyITaZHj#0OFE3|SS^u=t0thDmd6}bNo1v3Xtd+R4bC4AK@Nh2b` z)C<2X+Jk+QHYQItO?`Wn{fWWh4!`=r%>Z^EYJ$=+Pc<4`$#;LAZTK|(VJtBdiz$He zLHVP2Nn^~3;Q7H%pn842sP4f4f~8DrqF4Z8*nbthsS+&HzpmdPoCoO*g)6L|I}Zzi z6Q`gcBSoqY!ILr0vOB{CL#jBaK*LV>-cTQTB zm8A#68f|FI804Ksg5draq9{Z8{#R$+o!&a|(s&)wgZ+fO<CQHt#L{~^KSTuPyYO6O6t0U@PSxHv+f8~G0sI)adeA}S^I}L@2k7r4DY3A!0Gwyk< zB4t(--s;3%c%J6vISTKx=7@_rZf1x@Iu#D0UoMOp#vsl^wQ7!Ts|^kiLXOb4d6rv$(b zO58>ZMbEiKIuHB|_E2TK%gPsGqTK4)%FxKH>vd3u1Y=Q%)xtfz*9~3OfY35(z(`Hs z1&Sag50r>SnYarxR(5)*M4lsk1iN^!tFzb5#u4~D5f;8Jj(@+y=dwJWXkx#1!G~}& zB+N>{6AVpSYqv+>4tk4;>cQa%@z_Xr}TUS3&U6|vLM?e;nBTqNY^_c42p6D!Yi4G55ydMYTo92WBhT(7N7vk4)4$2wq zs>F9FxpOm|2U;@D{G9&9v=w*0w7mQW3?Q@Msw{sMpwIGvS$Eihdc?#Mk&tM_R-ACT z2t8$b@xaT^rG1LgU?13G&QfSJA_hUhT0>(QesqzIdKmkDqIH%P$q_E7!9S_01#xxB zpF>wa8H$c@r0wFRlhh(yIUFYo}OLbaYG3H`p1yznM6_b!G~7Tof9B;?iuJgXg>6fA?81@K)IuNo>kF(oUU>P2xRw{PL}J8 zOMpw-ukZO2FYE-GzV*Y@_9MhRO#OXj8IA|8rK8H)2i77|X<0r@3BTEUCN-5iNR+(L zQi>_s`GXB`G&SGNk9I6alq1jRF zKT}<&TRSyKXapg0!bw7I(@}FieGv32V~9|U7h1Wuq(JZ1EyOG(IwvBXfZBPI%s!l& z5AZ{c?~XaoGX#EFNvtZ8YjS6z&#RL$_DhO4s(WkbBs1P2BqV=8t;o`x zjh9nMM9TbvKdGW0fV0V4;^-j|ZFSEv2U^cjP#9~ji1!kkr?FJSb}ep;E8j;@Hs^0q_ueCI4Gj_%hStufD{+=z+vPi#!UyzenfUFADT4qvAk);8g9Fd}<*mZrtFbBx9VuHSx5L(XZ=)U?l4sx|?o1 zUf|Dh_}o`ZE!;B>LP@g>eJ^16yUjzz6I;3vTiV9y6MLnSewwuK#H^^D3gjqvFAZHW z;W~uD>FMgvV+@->aWx>u;{*_Pt7e`mPKIiA$>;N@_LHunx&J$k>0366*i`3V)R zn>}XhYWK@d!EV@)Pcy%N&&18u(GR5b!dX;tWjoB>;bHJ&lzCi0e^niJ;9Yh;Pdv{{ z9-~vC%g+3oBimgK+eg~s^8X*czC0}Hyl;1=y{E;NDKodG&B~Nfb6;rH(v-~9T&dLD zHTM;$v2@IZ%G6w`Oi@uOcU+({CB-F0MM0%fL_|bEKv@sYJkPn#dCz&@i$C}eFTcOv zXS+Z5eScR_A;Km=u-qr$A1{D6)6qUri_5LIXj4_Po@@fw&<0%%gt&2PrBh8O&j~x+ z=*$9Y%CPFj43=<`YBYZK>;0Xh2*D(z08PsPM}6$gVe^It6I6B|nV3jF3vOBXXk)p8 zD#rg76({3Ld63pQnty~GWZea+wSAP@GYCljp_Hkegkvu!#y=MU2JS5?PHK?Dsd9i# zp}xI2QUsSQoBt{4l${#PkSkKwi}gP-LHO{TUYA<3AD|;U#J!T|`-jX|dPVBnE9eZF z0CU-`S+EsALC^p!xnu!-pPe~atImQ)c-53}hLr@`1ETsKpjz=zpD8`m5!cuT8`Y$q za-$di8GwPE=Kiegk!&t4H06F5GJ^x9H1YDtiZhkOOG$-fQ55CK3kn)O+>av}rkyZ# zn}3ztb(;;lNnxZ3l-OgWvg%#1m&$7kzdt~)>kK>-@cj2U+QXbKo!v)Ble|Norb^kR zQ?tvuM(PyP!3xmv?2Q5UU53cG1)uWZnaRe;Y&%o(p~zf{&g@usefDE*JzF0<3MfBG`rLmNh=_JrQ($$7|x--j9{_wByV|i83~(Phm(ud z&8ZF56mT&v-|r6W%LY;FqHJAT^%Jh<@sHj@Pfj2m@qO0|JD{f_50lnDlLWgb|KLRZ z;KfVQPZ&#Tmbom}ZEe6oqTJAp$dN?k_^fOn8o}bo++0cJPkP`CFc@eitD2q(4FWfO zOl%jU6ZU!dgQwv^eF0}=AKDg0hU#S*Um6chaB;44j<6tEd;h$&VaGmdV<6L+^4=8- ze36Ci)lIk-i5c)p@Q9YWi+KEY9l)Mu-}NXNz($F_g(2DA--1;d_LM`%&j+#tT~^%< z-|BYx+!+~4rXa$5sNvxpBLwz?*vVllN*E0dp>yU|?xA~z6vvl+mR_T#-hzF@%1J}$ z_~=L$(LVg^mT#1Jarg_DS5dH$mB}iiHJIulN>R~<{f>)*)Op4YcA9D^;DNuU z!?UBAYmsATOEzoVb>mq{wx8<#a8P+(5`(kGO{LIgMF;*>DAlqQ0z)8Uog~^2NYu_B_4ty)m+=i|s{~-LJs% zJ);D+`C1q z_kQU0K#Q=u)9$*k>#?WG^NiRLd&0|Wh=J-D%=tNk@ZFWiC74D28Hd*Eg*QRFzqknw zNgh-=OsgvDINI_?qK#`RL*t^R&KXGb8&yo-+)X)wvAw6D;)-ka6Tln|xHrrRZkec+ zYGzEkww10EkQ6MhHhZr2YH1sV zOmfXaD^Xr-XGk$Y7okjH-sVkc&_CC`E)u>_b-8b<-5fu7E9$MGsQwz_Xs82Dqc~R?f|~G6m^2m@CrbNng~8*{(Q)_J(7LJl85q+Z(htUo09 zZJASuuSSW*JasM-Z=x?X!Wkibz`eK)FVY*>1t06@$(sm@|s$xKSQmyK(oOQc2-Z6H})Efx_@>?p{~+x5^jHsbjum2q^7upv+4Kc2#}k8tBxc}hI%6hXwhEuga}}|^juUxkWvce9EOEU z)NBsWPj;;e&MN5XOJmO7clx0P8Db0U(hUv4Bz?I50y@y@{=gf-6|ZcPXdzmO-8|41 zSTNOBd}G5S$hw#U+kQ-N73O``=T>Vdv#SAGd}NE`rHXii7fj31F8y%wK89HDU&39| zDE#b^ku4s)DmPc}zoIPWjYiM_Ag=_`@ZMd}m#NKipS^N^L|MbyuI(CD^-|Z!t+b0o zG)^fH|4zsF*?Wub?A?+&ciFqgJz7Z89LapQr1vqe0A6%AgIZWg)rXF}+-d9*QGxR@ zorileiOuJpo%H<<%)|+9kSJqkH4r)T&Mp1JF-09Cu_xSut#QJ#>J1sum|(S(gTHauvDZ-#XO$ zqGH%UqayOEI8L$#7Vna?;nzRD3%@RFtSPMB-~eZE(K@o1lVLl<$2d=O-9j3nU4mH- zq91)NXBs%!QOkU~wMdaQ-VJqMYZ^j`LyKYf=hIOFBnKC7S=$|w?98J;mH_9Rq#&>1 z9H?~)IZ)j2wQU$7oSja@@}lh9AwnYIL>*XH0e(zZQ)ZC~;`X4H74b7Nb8Nbyuv$y9 zQ`Dx;*X(8#e87iN15eAd;MDqaYZnw3`N6$bhqo(z&rWjH`i)^-XtNWQj_k<|b_Ycz z4yK`_AC$bw`4!%A3KE_EoY5F5} zujkP_C^^MNn`XwUcW#YJG3W_X<$ZKt_*i#?CAds-2YmXzT0D0<-W?Co^hHIJhJcMx zI5Q~kQgE)g)JGx-O+42>nt?44NZp#l-!szITJ3~j2xyuKr$WA=M2ekrDY)X_saq#t z(_78X+@P-!<(=Ad$%179Uv&(;_DARM90w8y_aj;MN!GvYVpu5$#{LbC)oxr@V(@;A zm{rfzJOI{}Hx!-X2>qBt;km`|sUWEi#!DwyK@L}vRJ4tIY$8-`UQC#)yd>JAGRgYi3k%(#E+ZHr3Cq z>qp(!A*i-X2AJL+h5J@iv55g@7V6LMzqWSrz4rLB>rf-<(ezn!lS=ra9FJN$ua1!nICH-tLCy42elb@8=B zR5%(Y^FZLdT{CAA_UESKHthAd1e@aiG{6Gyozqo^M+H;lFf&k2v|cAB%k`~5{DBhK zC%NWMHDVv^c*tRd!rC{T*~4%GP8)s{X}yeF16I9FdSh@U7Xf2(vvRw8g|}32Vw=t zbVz6gwLa(kZ)ODT=e=KDvU|5aFKFkX0~&sv^Ss>!K7%S`$4+odY=@h3t9D7|6V%&Q z=HGW^Ehp?8J>jH;W`>fePk6asVoGdL;xy5*Nu?$M6qgh1i0lfDn|_7&5(H<#cxlJB zEdb;btU%B{-dfy!DxmI7=JS^&N(20M zDb=SLQSo~u3`Ea#P|!??SLE%m^NUtOeUoQjpH1)(zL_DcD07xWaihh+q2D%TcSN_Q zUy%&{L|N$jq?ENR;)>qlHFEsxUSNeK0_{N*zVujAgv$!$IT{Bj5RUMhC0f*uE4X3j z4=;O}aGkuEH^1}D9#!jFHU7H3sO-2EF=_%}c970lg7Iv5U> zFCbO8c|G>?Z9OVUM(`prQW7T03=j=nlX23Z+8D_8I0H;^_HM2Kx4eL5 zC*m_go`IqaTL|kyI|H+~swKrAK

lf#H6olmXMaPM~K}9OLC&SJ67TNdgJwSNa+Z*-X!NdmA4F zq?ct13`Q%aK_@YunO>JwU&2Y=?`b<(=UB%#`9yl_wpBYNWGCgxYHov`y@V#cRFhQw z3EN8T*RiX!@i0DN{DeGg$FqicWF=|F5wNW}lnOwOgV%=$6*M#d&)Olh0a*@jJK=`N zk5h2A%rI0&BZ^Dn#fjS!C~Gml2MS97B;Cd(bhzsHS`E1z2T50gb&bXA!NH&4jcTC2 z2{Ca<$qhHbY%Lb5SytQ0s8OY)0z6%Kx5|VdT{Ex3{cdJhOob7y=S2n(#+=wbnxm3e zfs*rrxkRu_N>{nboMOuJH?m~Qsir(nn6!8JMD+o&>C@*PIArtR&lu(F)p%r`7M}?E zE>lhW57T5H46bud&UVE0`x}K1GXp}vSJuf>g4VnBs=c@b&X43-YX7)nu@&9rC0~yO zrldtAFxQHA;|3y5T#A|}Z4!yTX`Y={?riVJo#r=C>;7WWfs<4hRK6gYAO?pgj*tSG z@S;KUnJag-tSQo(o4FpPcs~|5%&L8{RWpVq%}2BC>9`17iwrZ>e^|D1DL%5wmey}0 zsPoJTXB|Ux@+Fm?PE`)b$QnAViR-L50f<<*NLS1bOk0QtJDPaf6wPGo$sEiLwk-hH z<5EuHNmJ`ORHOX6&z{0|a?)fOuDv6iS zJG9sLZOeSbLFY+8_Y$>*HO-D6QD5HXm4Tc=slRZUZrUkubmmI^sDT3AZqT>4H-kH2 z-Di29VZ#9P-8G!<8XsBjxrGU5Ubk?htm|h>Cobyhu32cmuB(g1_^gEu0*|<|Xqz`T zAo)&Tm%GKEjFlP$Z{}<8l|*Us!YHShOOHwCb$*jKDgtreVY%Q#Zyf;d7E>@PbUy?7 zZ|f?zQ}l~cW(XZ|1{pY&Pna(#omSLXs0+Az%inS&4G@8i%7aBioer?r@o7`<#Rz<3 z4lLN1dGHTjbbtf+Ot_R39C-yBnJ2Ra;#XURbIY_g3-=jmc_M~30l6l{tiY$ixs$@u z#O0^zqF*e9-`>Z>U@@_ZieKSk9Zmz zD!VX1;Cp@%SPUTL_YPJ1-b;)8ByQoZIH89y!8w_4oXfR`S%Wgb5VVCGu4rVUg-h5_ z9fgsu`qw7@SL2 z+o#TG3+|W-Y=*RY0w5kSl%(~~y||v6f~=fzYmz(_jtdWAZ0CL^I%?Yo6XN(vuzo#k z`0yO~k4Hc`jkY?b7{~Xn8dsLHXJS^YXwLrtA!Z74X{8VLyAf<}U{uxxG3L%i`QG)K zczoJ!-5F(8ov^lri#`GW;!+Hmz%BcB<+=xQo{Tp=Z#t9Npb`->036i^3_k&P=W!c$ z)m?Uyjy3w37u$-aaK_GiROdlL&Aa`^uGsn5h_zQTM{rrOz*d6B*FYdX^!v~XsycSTWuwQ*S_4Cp(($6ak>P7r4>{g%+__VEXlhyc7NiC z4MXK2HwaEJ?lL;S(3V8!l!Cqb4BgUfy2_)cSNjHp4p!!e;88WUKKf8ozoOO&NqPJ| zGptpOx(9CG%X3n#zp%D2*kQVAH1sqEJb6{tt%0_V5IFIux%t zx%erdu#&_^HlCf7ML&)eL9^1+Kd9>4R|jVi@nfxjaGhObmeJ%l>%v6VU4~3RPotv? zj&6M3owpJ8Hz$gya#GGp?FchmcK&&F-L+vr=RaE0bs@MS2c#v7-O35=)->tz(|bng z2b#bt7Qn$hhWvi(PlX$=O7(zN6E#hNYX;@vE%RAqe+1M-R>i2Loj7T916xa1-zqWZ z9i0?vEca)bfEO%WYHH4OeT!oB{W(*0Db7`*HCd*oE0iFe+OOlT=HZMBpl)SRZn>ES zPvf|HDwv!Sixvo9U-~j(Cu&&%a&yX=HGCg)1!q0^E3aei9-I(t9c^R<6J~MdNKwon ztBp;=Zu}&@Z3~L)d6X#`LruMWo^>~qmJ^n}ocUa9V|hQ^@!TU>qC!==~#*@uIM$q6h=JF$TtvZc{yAYK?6u&n3*}xfY&OW zi@b#wwrz9c=u1l$BkOVDw40;|xl%M&wV~6`_(20MJhEc*2BoBJm+x;K6BSC+{8#x? zJB58XV|s@H)~Q&E!X5-GE9vv0SK=eY|I)3!p{W|MUlMR%BC#e7Chc90q8|)CVRJYE z{LWJr6{lA4Zde5NQS#~vl*CheyS%b{-Ve1O^N?bKR0YA!H4%jDLh?5RT|tV`l-pla zEsMwR-+|2l|E9#U9jAf+Y#H%_FVx*9@?iC%3*2Q?A1+U}F1^irvUe77@}pSvYXJ;l zGFj%-FPq=pth|PYf+o*P9fYj|H+R-8QAx$JmLg6;;F>SZ6n-u=e38iA%3Ok2*dpzU zSR_!92{9AY1oLfCX$lLujqi?eCAmr?76T!#bV)=zNsKx<8+z85A|5!^5zoS9D-1OR zahNWb*^ofeZaTEmPG~BWr2}=XaZ%-o-mk)1p>m~~3rT%SEo(j@aR}gY7{^t_&NjAP zyaQejS2Vk~c!iMMbka#@!Jt|9p$^uh2crmb)No5j%@}X%+kiT_MbBRu=u@Yy8MSa* zTv0guq4>}w&thP!(~oREr+4rKy0YFl-UG3W6dMX#cZzBhuiPADlob# z=auFnZE`&sY4yZN|9D~nSg)OtfiCcrIimADMPz4Nki}XhSP$%3m+0oWfuD(JcjJry zyN#tTM*N*+{|^t9SFj$%KV)`t|D3<$afVu0cU<;xS>D_r^6Rn zM^_8p(db)7WFfdq{RJeqXIF-2M!B7*MsCzl)X&*M80y?fJzq2y!0Wuv1Zy8==AW>Hd6x|gI@5c&q_OZK^G*D9 zevxP;WZ^D2$dB+Bd8wjAn{qDQ;N9Bgw2Z*?8p=$*_gXwZgi_iBV#U_oZJL_9E*U?A zGECZ9b>(|NLD-kDtDf28sf-EQ3q{F%>q>lts)sY!F~{PTDic9AbOhKb@^+C401!JC z?}lW{9SEDUq*yWwnUKiY%43no{5yR3a9SwiwNS zBz(h7*WzhqJ8xT1UoQu2bK_;T@rUGXWGRhkpfhIyK5V7DPAL$=TL zO8Ks%t@xmtxO3(d0F3>1&hU1f{<`5PM8)5>J4y|wqiuq@mvvSe?e{Yt2 zWJ8Y^P&kKSyh-V|gNgCrd?`sF<*bYkV8{B^*}#-4m6&i&HkXe+H}6U-&w6Mv2fVZ} zfuWg#uwu@$h#BoVxwHi5oj~}rB7Bv{#rMma+S<TWNB$&WAM(-H|ubVWp6C&=J*IlUp;jT1xUsM|#V5)h^FG)3t z(Me3aXh}NvxKaZn^?+o%VfmH&i(cd*4ywV5KfM*A4ma82LzocFyj_x2J$z0p=FY^4 zdSSb|1H~|G$U~?nO~0g7k5OQt`Q#xx62Lt%uH}JLlNbXLP|5^t^Z}3}z|$X=@I4Vt z)Z}jXZ#|#aUEluoC`H}E++4hbVfeykHxjo3S6Ye36o7-+u!I^|>!`6mtu}jqOo(fo ze|Ewrr;B)TWBAzg(eem>cLy_n-m2KO6&o>q25#1k7Pt0PQz5mx3AIoUb@XX?4R?mv zF8vmG?Zs~LY+>sFu_@gbBDq^2b?K?HVs0T@=c2j)dIb4>9d~ZqpD?JP9HDex$vW6G zaU`rL)Kf693ICCw%q93e_S(aY--%9tx2utNt+-}nLk;EYp#d1YQGZTdB2@a^~ez+YK;4j03SBAAqftn*b5m8rS7r8_RVoeq{9 zuMHnbiYzknnW`OMGybk4tL}0AGCUQyq5lo@c6dP8E#JKc+Knc%PzgbPQZ3J>bb|{) z15NjB$}-*YX_Z4o+_UdP++YvvfWhr2za40N)H=EIwc`EuPfVp6yyhmmb&p96nc9NA zLQs9s(y*5DknTgD;mYg0pkFAiK6+4-(Ajq5Cqkvo2ZRo~HPaEQ$m-1c&O4&(^tC>T z@LfjD6&$~TVFXcwA)zi{lllsVoaP}Z$iNAwnw)HR2Q?T?;Qcl9p#fJR&kAkil@Q&= znOHf-j>E6?$4FCHtKsxJciS%hPk!|wg%88Wg~l_mHGr&dB?2h=ATRBnL}?idbMibe zZ~>tfZRdfBq)##@jFE$M%-joa4pnK%*V!)}Cc1GvW zgK;CmF9r^y(zS^$(=Fe2ylqd`?2mGM(bqL#X@vMI_4zQ;xBh)XscDWFLO$fd!&Azj z+4w!qs=1|TGTgFlrlMF>1I?DsBPcxfOl5f1{;m2ZzmRZ=E-)A$!|(v%qJ*E+6XW9< zrD==%(Emd=`sI<^@z+3&)vw2Vk>J%Q3U)Z*4^ZQ@h7fc_6U>sQYj*bjrlWM6BqlgZ zW@lk8iabr95>cA^Q#1h|4l$`qam)I;M=KiRjfP(_}tL75V@fXoXk7rjlI`@UsASId|Xz70O;e4@KH z>Uc*nU((ZdC*PzcM=ROfXFBenE6uyBb2@tH@*2Ec&@pa&i!w(#8Ym3acWA-$dBC11MTCT6f)7!IvU>eRX=FAQt;f5mJy@k?X(%#OM zy2M85GY)fQ?Y98c8C_g$m+ApXl!AH650Q!~L#5n8ldjIwdEwgnNE7Bp} zK&jla2KAP7YC@6p{lEs%c{wl$iB+^~Q!u#WGZr-2bBg>|hGidVxJKnsdUSeG^6?~O z{F%lD-KV^55q1DrSUMVmEq$9l4?N)&F@7*r57SIn?IXW)y4&9J>hT1c*YB1fjlfS4bN z&qBu(TST>?#W>+Up2Nc+KwkTJy|d4sV!1PO7;t_AHq@;r~T#L&635LA1vz z+qLYM9#2h=NPiRWeaSb;gD$xWs2lVwMyDraohUh#RF1g+Y;TnoO}Yz`jsIqYlvytL z#nzqD%$siXM81qw8Jynil>vaf_>C9H6Zmbt1m5wb61@!H(KYxc(27-1my6`m5eR8k zD2%(JDErw;e#@dQ-ad|4_jvv9TSeb(DoqG8i$du*Q1or30Jn@`Ok9G4kh?*G(9!#M zb>qSqrhaSehhU>Zr7vFf4B;@u2vgFvg3_uv0HnR6?fm!0>-I<&zY83L z)2o3#FF@ZSQ4=i-?b6JugT_8H;?uun2WGul`Ml_N3UxLaocsrGVw4wr1f@_4^zpEf zox73lr$nE^jV@1@EIXKDL+Dkb>}vrk!>7WN6<2D@4X6IyK|e4H)8AeE%9C}5_WzvLqoWVzhb#D`bE!zVSeMrQrfJJ1DWqmHx+_Vgzy^mW{H-) zVDk@Wa+5z!S`7yQ`Oa8$9U)5YCi&0HV>Y(wx3p6y2e(etGunQh$~}#}as6HUG{Q6c z|1IYqoi-E$?TElNs&s&ng-CoO*~CG)n)jV%vgKwfuraA!HCHr!w2~_UK4%CS?@Q#~8bAA2CplA~o69f9Umr5u!#y%GEcz3MWr?8R%L`4#Q#b=8LJq8Pl#^|Fwf8 zoqUaR6fMJH0Yao~<`}UAZed(x0SPI&!1>;vO?NBnPcubKaV90m=frbsvdR+8;*$Fx z>T>IK*;$&Tw+pXANsb=e*()=Jt>71m5ZBNL5E`Ss;^Ph!Ij~M_e?L34z)AxhYFra& z>|&NF)h<@tR(NW?qWvc8J_9J31J%F0kQP?|yPdf*@z8m|ovs@J#|@Tz=H|&92S5cA zaigF5E-JyJZg6DqRNX;`Qam+1N|lisaY!*gB8vD|Ot&pu=t^ylg6TyoL6ifPqcSMF zFIK}dwVQBO33po_{1|6w$mqDnRnuQu)@^G4eRekA%Oy(5I~4mtvRRwlvT45H&-hz| zmyprJe;(}AN->XJUcQtopIi7sJQ-ncM5(Db8ZBP!ybKA(yhS~fVwD^9Uubu0fpx6Ew(U{lM z5##Ss=2<(|^0EMUp&T1WIQ(#41H=}$`dr4%_?eN<){Uws)@Z6Z%)gV)u9g4;f61wc zmdEY2^|(DQ*+1-&e50#-M}AZrz@NT;I&alEO z^Q9B?Ftrz|Ui0mE`(&f=kr1=Y34@{N3o=8O>ObPEz`)7xIvBHXujYu5aYX>utbH+R zE0HR=e1o@Y)qi^< z?fN!8y@TkcW?opFxH=IzTMx6dj%vGs5F2tzf?60ihJ~5s>dVI zvyT-HDMX+!1qg&Jr{P(lBNjumc`Q<)(8wIt>t4&D_Ec-gAZD=TQ{b+{zxLfE&*<$a z$xpj)(?3Z%u1BhM?hEUmN8ITzGzjkke_GI*jSHEH146S%TGcpg97?A;lwe#ZjFRx+ za~`>!#+#zoYpBPKDK}Mv8+O8sXRKlX`Y9Cec3}hYhchnDB#~fv z9f|$nR+fasN=JX22tg@AL-UJeMU1(%QCzNto^396nEfOv0*%tuh#xQJ^Frj{@OB;yaFkY(eHi9??x*Hrfcx zlTHr3_S^J#m8{MzIQEmEDW0%;Kc+ekTqVL z-?bPigcN`S504)+}}`3F#y#k_=D%njpVP<+M5!F7CNpC)X2`=J}aWiZ}}_D z^>lBi!x#X`ZBhMd2P$Iskq4+v7R#yB0w{}Q(H0=hpsxCs#k5VEI@47awmuS@JSrAJ z-UkYuQlO*k=teFb%uw&|9+;$No+bZhlv%p7|Jy~D+~EuN3My&>QfIHhn+{E1FYq<7 zs=h*r9{R;Nbg}dL06?>*Coy8Syt@)vm>*P=IoYUv9E3X?%FAvJ5tc^x%G_>XvrU=v ztrxmSBS<_k$;%#Zjhh{*_HMW_ajwrRnmP={gxpExRSHMkC3llPcFX4#zIQXC*-0r_ zSx5PZa_QOo-t=6 zk1=J80=q|#k6XF|3-%a?Up{Q2dd#>F!>QKXuV+F|Udclp9lDL42H ze~r9N&JCGAVsc5g|-Gy8Xo_?1_#OaqTeFLPvF-<*!X-kifIyy@ME{n=WlrRB_Kx7^YeOr1Qd^*~4jmUSY{BE!q8*9< zT*Dy0sLj14GAv%YMn{5WoN)$+#uo$tzzjK1=N8L)e%0;O2yE?juJnHO!N3D)a~jVN zWcrz!dGO+||2)&~kp@*c-OzlMWT)<4jO7Ln*eL+3Hp`jj%qgmLlqxI;{v^^wJrWOt zBpr-Yjbitri1pTe3564C5=B#jDMxM$mfKpUF8Uygj_Ut(^4C6`#nK~iadF&i1S(Aq zbdwn(oDAn$gjS!hR)&0~T~Zf*Tv5g?e_-qYuO`-|Sh19k(#s6n85InWqylITVGE?W z#A!G#z+=S27#F+zb7c??jTNpSfg@50R9U1Bf}8VWnFKd7PL9s!sh>^<|FUz&Kq+S#6a}|D9st# z)VC4ghZ)F0u-pn1`n!YajrzVpb$xMfE82T_1NdTKOXdkbySU--g_K~NC;0P$5-mk9 z-;vk)WyGHVkdaqWe5~eGgU5*o0%C|&KD;hzG zS|;_cP@!Mk{i#c) zHJo-n>aE4GM?kr@-itJ zP4!Xac=ZHBVpH@R9y^;3&c`m%_`48>Fj?f%2$jU7R_DT@%eiF=vSm3!j4%S4i^oc| zJ{5uB4GGSI;wj4*OpI`@XK;nH17JtTa=xvsILkh+^pmKQ(#h(uV~T%|PW-foSbI@6 zIx$Yz04U1KtU0F;iRnDYqSOhUsyC!|@E!0^_2PR6+sd(FlqRPi&BmgV#%9!^kJPFt z<<3i~hR0DN{p@m2kwMMDC`1TGYNZwzX+pGk9SIDQ+o3A3e?l`K$-OQUz}TfHzGVWe z0G0=p34j>C)h`L;f*&<%A{*sZ<#nX{8Ra6Z3B55UHWU{a0l_3)Jx*d}z06Rl*01-jb#466+<)7E zUQ{qP95LC)OR?}!TR{QT;Pcj2U4ZQ`oJ}CXxVTCQr}|8m-UII+)gd47tqBAlHN${A6;XEP5*W z(ZA7Rz3Z9qJouJGGds~7CX#UxTrN?Tb+jUp#VvY>huD=z;8)txp@Hk5_GMe*Ft(Je zn*7@t6e_H-!>+>P#y;oWS9dhJs+#+4|1lsQRJZk&as}6YrJof_3t}Q+E8_wX(HD9@ zp5Oqw?jTBL%Yl4^cMuz0=uc1*>@GuYtp?-!x&vXu*v;ZRU~Gs$i}Z$gTVxc!^rAj_?KWn2StHYMXy%20#Y{vs_Hk6GgK5Kh! zv1$<8_8FK3Jz=0oB%+--4%lwSyUu~eEcWI_^e4guF=d6OdPHRCbnDe4i~;j^ zCHw)2vP!EESrXlWQ+|I>nUg&VUQ}R)4`-J3w#7BQK zSoKndFROI=7KS~ohI+JJs4C_bD!#|s|6EKtKGMU+A4Juf|_)nvz2QRnL6H! z^*Jf7UiLkjf8{I(?0V;aeTR8itj+-FFXM!nJqo@dFH`o{W8aSUBq!2rfHv=%pG0-D zJ1^*#62C4-zt_@P%iA{5u}%Izs%=TE`YY?g=Rfi>UurOSk7a(y_R`-OVsuuSNgBGD z`x+#m;=%N*a~8ToOCR_8b&W{lmePK{uz_B`{larsJ83z|#4pW0k6cM1!opvZ~ElaL>uD|5DxVJ^py*u-l2mj)vK)N$$fwY%PE54y-FS zl4#WvE$3~;?KIdQLM@vqInp8wCx7dn|0w9|oO>|3TVYo=rKZ5yvzhV;Q`>k!8<2Yb z=(BCdH7st7()Fk8X+vjXfTPog=| zm4lZie>HO1VCU-L>;k4~n}**ZWvBHRP=dTP<@PD_gAAaPb!ka-RpNeFT)g}tEaDux z&MSfwLoi|v7@gHjwKvVz{Un~rqI%BuwHk-??atH$#eU8rcqXviLfWH>gD;59DHX3x z!eYkmMjJ1Gi4nc}pjDrDR3>F3w7?>Au4_n8yI9^U;G4|uH$4u8iImHTTES92W#BBb zDB3Oov zmNWSBLypd+FeSiFx6&OOT`~YZZ?Z2PIHrBio9 zw2JmL%siviNU|3=MEyGqdb(a4}NqSooefCw1swlDq#1znsTF7 zE7N)w>zun&d(U_ley!PITVwUJohWPX4JUNp>|8WOzGM{8d&z;h z(wan}g5pd4#`kfbFT17oZt&l@XN|>8#^!wA@$8ymwmYh_c(Bks@RIYT&xenLuLatZHQ2GY z^F1cn0l|kfc09Aontf!@d-UaOXhY0z0V*l1N=FpIjzFjU0-@LkLlXP+aR9@Kr4$Yl zD5c%q?-|MycnRPnHuIHb_EVK)-VXgKgW=%n$U7jgL*Qn?usMtNEg^?US;)tu$ zOHUyjQ(KzPh_V~2~*z_?*Ebjox*y$^auYa2L zPsUI?n2|An182e5Hu0<>u9hABs7REUf>3CY@b!9<)Y}kv_U;|Ufe&UdV|P7IZ`WpKAWmO@bfC&QynHAl=h~^ zzgB^XTPLn5m&O&p{&Dz`*6UBlKBoW^KOpLq1!h~>@g!rZNQ0H*7NTplXnHm|%6SYX z%SFd1(_Rw-|IT^;j`t%UUxh9IJVEn7Lf_);mE$~K&KF`Vb1ZhV_B>GMQddB)hwO5D zaW*-t`*J(Z^_)^oLC)4=&H#GC(7HO#${dVyTsM5A{oQ_er{_yJG>dyNS%Q+L)FgIi zpVb_hheN-5NfFQWhm$UQA8$?8>e*t8+xzawN^QoEht*qNB`&|ec0ED)uhb2(o7D%bpyl89 zj_jI7)n;2YxoiElrNtSWf71D*#jU0hR7%zF?liKZBD1RNvEu~> zo_Tw%tMWux-H1N;bzF7ui1wzkXTfn3L8lgWe|r}C^)z|OpWJqXT==H+Y^$Wd_Q6t^ z&z*rJ%V&+t*AVT}7vXCkL4@JprVm#-x9ovG0)Fe-vDNU`#0^$ggDBHsRRNz*+Zh;f z26Buh<3y^o3p9J$iB{E%-8=txQnozy@hRD~%DfmiV#jx?BOhusoi+O9aG^MPDN4x5~-v{7E3STPI%Y8;jqN$h~YKZ$u-D?!#YtV7W!1IRtIdu&Z0; zVgt#77KKiz3pIb*Vfr~?D&mu?UA9a-0LYcC!U3)lmJbU1u!H|`qk;jREupA1K9I$5i@5a)c0W*3&zu&@{%cgqq=?c zyN^jY=`&8bl>`_;t~rB{iIJmm#k9+&sEZO zGnUg3Z9vZ=^&I=*Mis{2^*Pp$lb#<(nzP1zWD@Wwf+n!ZIjgPc8QX|U{C47(4te7S zd!M`aWHu=AXG!oulQ!W9I+UII{q}6rBL@iUj~i;-SNLoamcNTd_$6pO;b$uT6_((} zk^0hofud`em7z3BqDrbp;nL!tdeEvTHN@-@)K-?`2-pPY`;|YN+GwkMi(~(JvDW*aDurKXfzDO8{UzP- zYzDEO@LDC`sj_HCa94rtpR>$7=ljQ9oJ*(MPuKp(3*e|#1lORye);5Dl(fj5Q~fx( zQY&LMt_E_$ps@Mfm2Il0f~KdFfh9lV7k*sj{MZb)^t;tFZg3!o7>Ns6JJ~n(7;6vv zaX?&wnE!s1^M56UPPdcQpA3#^lstZP*);t>F8-TrgyL*SlPo+%vx$np1cgf@eINw@-_n)U` zs~2O>AiD03RMLEwUu>qO3)BvR6M^ihTHvF-AS%eU` z(0nL!$dtJMpV>lMyZ6o&EtD*z@jyeNt*Kw=nZccLOC$9Gdc2Fz$&Q@1R9$PQUQ6X! zQW@a8ncru>nuULvQ;yaB7W4J+>^Na|EnsN-{*zZnClhNo@D}PoESEP1H=nZ_DOmaJ zbRFK2J(5)QkQw4=a?_=-G!(*Iu0D3oucrBD6Yb9({T!&xyC}5Loo}JzwJpL1sy%^U zf{?D@BGal5`0AhSw|6qw_rRhkkwcmI@!J=8{IHjs)lSND=z^De`q_x@L0&7qV`*{x z&*W1VVPMRnK7uf3R?f-s6lDvSUz0z2fBw zj)H87qt?QP3UCAs;M#z>|FOyR$D1WG?f{VJd=`Eyxn4BMeq_ET$#hj|ubT2EO!A&s z0fk}Go4?{5J~!bc;Kje-x+^*$qaTqn)@xF#2DbYBJ=4!})NY8g;0pwX;D_~98bEP9 zdXvBR&-e5HGEHepI&hDw+*16jKoEZx?d5$ z>azv2$|2sUFlH)c^hKDwVaQXh&b;6CiC)N+Hq6BzqS^kR6@1qQo%@MQw<)@2fX4M# z-d>$kSDXw2>UEs;q~351bsOCn3v8aD?@i2>9*5-ATXO6efx4*M zTMxQ?!t^Kt`eb_Y+h9Nn!ByqEluk|f{h}daa?U%?;KjfQv7;fO{~Pm-1h&UN)jz1dW%SiAX@Ym5hW2sL>)DZ-g_A)i6Ds<(R&0T z7=1=3BHCc|Iv9*G+9;zA#(XEwz4y8I{?`4i_4&iHEPt@hoU_l~@Aqr(GvocI(9CW> zwn(}e`-HVb6k$En&}q~uOKo_M6Vpgx8bNO%?jjx_?sq?OAu%@!Z%(kz=M}Mi%&Xy% z7|9Q6pwTKx`zT3}k`=IFZ}@$Q-^cp1JaDGSe|>@*rKZ zT4C3n>m@A5uvRZ@hW&Zk6}?dg0f{*zMUpoL=^t9@Vsiu$n>{^I z`AjP0N%=m^TN59ugMNA-<}kTC@D1(qn0OFgo5b(F{|>*GWg-5ozsmR@AL36-i3=qg z_uh3xxl3z6vKVYd^k_^kj?l_D=9M<8c^6ouOyAuiu{aD(d=-mH?z3DJ=bCdFvb=Qx zQZNY4rQxVDziWpa?_zKGc7TviVd4mL;h(0F->j=%A5;X#pArv(-^(j}sOA*bnPzuy zibYCQ$i+acPqJz=QQc0}t0S(5*Qs3MLDP<1%$d!^+ylWA?`NxWPA5iuk7F1Tac{AR z=I_{3O&Euo(lLD~ah$cicj7mF?Krs z8oDS;(m5H-9UrGpZO>>=TkRAF$>N>ADYx4f)wO4mOccB`#M$gW1@sSgE3UFc|AcqbO3@>+}<*SMEQiYBw#M=1gCj)WIy>BT=_KNF)!bD(7NW5)TYdZ*kekC`L-6X6SyFFB9Ek>b6koaPHBNZWmV1e?Cc?2(LV zG*K zT?-DJ+g+<$C&vmE0v3Gs=)*Ryd_d{s%YoAE$zSB7ui!=u`gL_{2Z9W~vCH=_rRa}f z1nR9C*h7ek@qe`dL35$QJAAsOHiLpUxgo0ak9D?)Ma$2P36;d9qJ zearES?Fydr>TVHFta@*Mj-3{c+DcuVBac5r_{4}tJda51QN7G6He@vIbSVk-GUF{B z@3{NMeeGT|!AJ5J9W%@9?#{j_R#}1bm&`H5$@RSP<18ICH2(~#f-rHldH7S}d8&sa z%B)i{N9WvVH_VGFeZF~N!hpLHa=odt`$=o*seGVcbrOeXk??sl& zH1}nL4AScMZ)OplVr20|7lqfbMEkGtTOEYZ(i;c)bvpTP3v_h=nN=<#O^anf;2HiT z$@5T|>5E<@I6F9~-V-hf93I!#R?hGc+X1m(JeUo;`u4=_u#KVT@faT}$9)2DEZn+V zR<(|AhAZRh9gpivPyZ?L9hIm)0&t@&+|v5SyUqCzJA^rQ+EsmKZA51OJ(M%+q8}GV zY%}`Z=W2rixpi|~GSo#f_+W}TML-{RU-_u5wzo_9ES@XW9fZ!rOM`bEbkSxnILv32 z#=Wz5FCe`PMI2&kZXB=<1n2}FkB|^qfyyL;ZZt_FhIwW5daUU&6B%Ns-`tLwDa(6* zO4cSH1Bi#!-F}7qND7Sr(#e2G>+#qCdK|$qTduJEmhM3gaibOLNEg`M-uKqqCs;YX zTQhb#QgI``#dWK!annu^pDg=bhN-bc_s_Y_*xjjzXu`BMvt5tlNJnz=B75gu&y1rg zE_+PGcfdPUX=R7AtYsc6pY3S%4ul%kf?|>?LH;sjlj<@TkUd%b0F9X#%WuLPQFi`y+uox}zz$Gg@bE;44`raxpN8nN=46}1`dYQI> zCXt*S{I2Ta3clQv)3LwF0p&G#tqS)z6KP#_3m7Y*z4u2{o4y01Qr>EkI6d8x8oDjx zbCT8L;yeRYF8yk^FX2~m(!6cP1=^F=v8 zmQJKr(`kI~1PqF94$pY?5xg-2*f)^4vZ{Q?GtTV9p;LL!#~6(NjV&Tj)+DRDJ9;i4 zVkQVGbm@|b#2hgC#Yiyl0A6^jMrdhq+6#YjfRJzznxmD@JA1J=&I4bPq@Nrn-|l`| zyb5+lk(l_(c%EWMmPS!4Ghm`ENBg`$Gn@SIRQAV`$1LKrAGsq;y?jJuX~G{I2wc3D zGk2;PCwTd>(JCVzSYiHBA7bcEU6WLTFDPHT*i{Bv$&VaPqsFT1Q#Tt} z6{geaONIK`Yd@uFRYf1j?}ApkE7_uJwZqVU&SlDcb+OA z9wPlCbuLxh_|o6p!yIvorS0{m%RyA2kLy^1cD6*Dl1G&GO{E$)2-971R`gFrb%yhT zr#h}b!T%n^Hf;KlXC_pnM!7}}o#83a$OQ1lxY7~NZB+)SewT7-YBr{1&kfEo5b!C=TlQV*x3LCQ;iBj5uOjBI$UHIG`K}cHQo~f19AKQwn>!5? zvpG75LNf`vO4ZH$zmj`2xCq~+T#>F@XhAPt>-^m(vb8=KIHS(Hi_5aJ*IG zq1|-j7RN8bO=^R;n%!rn;R|yTPM%k)gLE0#g-r;H%*y&Q?$Q_RFSf23l%w*Ke2vWa z8@cTLw&zCIFLED!_VeA-Y?@>`_`o|iX;&$CR5OBD6}Wf?ifnIku(uvB4LRFh&3$+M+WO z?Of6{34G?=onGi4+sV*X4AUBKQt-+zSrOv?(OEq{Fjg6t8kQW27!rHrzj=Gs?$gk0 z;LlG!9!_hv`-xoNQaA<>aZl#C{*u$((RoWz{w$;SRn<*t$DkNh<{+6a9nRT%`uSpe zi@6b3ro>l<>-FY_UB=*|_5;HAuvy>_vis>%`l;@=#(sT*y>5*g@S$R<5hrjNrFPui zNs@oaCaU+EH=Eq~hq07_&hY%UtC%biSZytCeBcN4q5JQRYf~}Ov&P>uO;AhU_c|jM zxLfl#S|wm*0WmN>}W8hvT5tTZ|=$NA*W0ySr0kmI)??RvLOSKujHY27xD z7=RS9FoU6-`I#4E-IiD*$})Osi0SNwJ~t+M+06@$)ShbrcAN?7{z&pv&a}9rrVjDH z{SfxZb0udUEVB;-HI(y`5BOrwe~qzL6UJdhaV^BCvn&0jwaK=oRu0d?fH%)%U0j%{ zt$lu+502#4NPG5x7>I2y^l7y;bgp)4ZbJ!-}(@)MFbU7QC3 zDwVf0LibDPd`{H4M9e`$7A+qtCddt_aFl%e|RJFSc5bsls@du-(xk#04&C*$9S ztmdWh@SpFmhG9Omi&UlD_OGan6{ZlU;4X4VFA4wSgc4E4@360iGpEe%p z`lcim6FBzOQN1|3E1(9S1d7pPt-mc|dt^M$)FVnlkrwZ>`Cv))MkaA$_e{Kn!&x~RiUZmYw^j3 zuE9?_oq7}|N!0?6r8mXhlzJRkd3YTbxe_KfRyBUZr|)aCuSiMFVjSiLFLL3tzKR_^ zybg`#6YI!Hf|?Rc1FZ&wV?ZTQyHUrz#*IP3T-HDH+|1%5PkE4QeH>6NrpZrVWHV~M z`Aq>VP5h>Ojds367YEqYw*@ex10g?z?0pLh%B^1-J!6}aL7oMG0IwB)`RW>6`5&B~rml z@JPtcRkmemoow1uEBP^SLP;l!RpfVVDv{NYBPiX;*{R^(rPW9##kTj2=qZMwK1N?6 z1!!?*&B+m*Dc_p8^Fe#0T*GbdpGWg_?ACQbC-;SDQ<&0s;e4JNhXS(F^m2vi5;68qi%X^}xR#mm16*?l_rx$#*;JzZeo-mdtk9*jtk>@+h z5wvn12P+?JLUR@-lf~7%NKnt0j^$LDeEt{-Hdzsv<$*kk;3g07o8U5Cqckt|xGTW{ zwIm&b6GcD^`igt(UhM}pnG5W1uf8LTS8W5&V;+{t<7%oQlb}P>1j>wdy(Gt;d|2pO=Kr7sGR&) zj5ZpHK3cnjkxDa0fZD6tO9?rr*k5Vo^x1p)g(*f&i%<+Z>d1+3ighM2W2I>8&jv}< zzMN3+<)%4*qg9dWmSETluw>4SWmU))Z?o^7;uo^>*Hu_Z-L-#mSh)4*xZn7D$gUBfsQCT(Xy4k}Lj=D&#ls@^)UP&nO~&q2zPuN}orX+{@(aJEpDOs$KJtW9R6(u`XE| zskVW%WsTbvYJXfPrf`2Dv56#(3}J_F+XuDy$n$^6>cNewtMXAI-Qvx zMc>&T;G=wGtyEh$^2AdYUWAKN3+?NL{d`~WbVnh&Z0{=TQ0gNQ^p;s^>Dt|FNeDcS zqtJQJh;xd~zaCPQG3-8AI&lCf&S+48ouE5HoX2u)gu3aE>6(bP7!%QGLC`W)pUrf2eTti$0y~jP6 zL>M|dqjiJ%;ZykNhePVrD2?5$a3z|n2)hZ-fL)gm(6Kx1eP=&YG-v!+sJiQ}B5!MT z)r6#^qIpY*c}r5w#GP!8-iPL%qp{V(s)i6_lg|Fy>UORwM}UO%o29#do6T>N{4SkG z7SBD5uTAetx(bUF6Q&ipK<|)pWV3>NsNrXTq{}iT3)361DZfXtJGz20nkP`Iifuk6Wt~C1m_jAp?zq%LF3uWZ=X56&R9f>OPz|_$bSNj?$HJ z;imUI7g;4((nM&%gt8>CkH9J-sbp^pMX#xZ{csQ|zj>4(A7R5NTq0Mfn=|)i;e|3L z(f5=y)y88GQM;S84t{om3I_a z95T*+QYwua-fMx9Rj8C!P?~E$F}!ZyHOlkCjpLlHANqyJt$>PxyT7W{K43S+;t0qp z(wWF^&C$^UU8^(Vkgm%$xFao6-(9u6d0#O$AJr*io_E@r+!jh^K*qw8rpUuIvRch5 zcM*+8tknSbW+*azGr7|~yXV>$*T-hT82`P>xRCBmpg)p2ssbuAaQVQY<}GAF79e-w zfIoQgALFs(IDI{*?2dJS zgMQrBnIEeScjvr{)#K+L-jhV!X16KaT`bV|u^~F#P`qf8oBC#_K1`EBfZPZNiZ+rQd{$U`@MR0jjIr$# z3o<3Jk)`FqdedyS4`ss9xa;!_RY4eiK5G9l!Qpuh-=8bA$WN1=kexz~oAg0$Lh(IA z3)!6%E$zz=OVQI*4;1ebiq+pW*Onq@9NC0Kahqu$QrVy>%_p_ew_FxM_9k`hWx!wOyIg8?W-qiGH8qmw zZJdZ(PbXH{U7;*}It=b1&s9>=O|j{*vAMdUE5~wUcPo+3Im$@b++rD0XUXbMvdMZ~ z!_8g4T;sKC2)>u_bAYd?AXl=k50vrv*lj31)9tfP^2XZRteX$zGG17QVzYn~nBe3I z93j>%H=6%nzz+NSbF_~TZB35DwB)ezhPY5P_Q`lUlVz@I-}4ejl@qzhJqvCGx;7k! zqz5}ML}#WHIKW0*cD$GYx4hMHh~*++$30}&oVm=x``QB+?x3INDeHOj0kJ`UUt>$`rBEG=D)Ba?Gh)`ovc4we*aO;IfV) zw3d@rLm2zpB51Wkm=JoR_zRJ)@U0m&*v))`2*)L}f8-DOC0Kr%)spbQlOHkuyV`zh z`1_uFNRyI=E1>%}_;`ey3aRSNB_eo1b_B|9@h|arHXphu^LFxGus0>_wVf}z;g>G> z7>$pQGaI%;zfjsAtwx@FmlS-#KiH1h6~RUiklCwq37V@b{=bGlsP@?a!_(%I1n{6XW!_^_L%Eg3c0 zh(_C}&vd4EQV+CkHlqAhjg$VmKOpNA)qiN6q324sQRPhs!`|m$1T3teh8++bK-+xq>JdrZ)i)8x^5;iEE*F)LqEZ@T19=HwqF%6z1Alud9Ktun;w>kit^%*2+V8Fe&9N~)(~nkGJ) zavtx)M-#cs?0$c`UTl>gJM?C)^|xW=4B-fyyrT<_2gWLu))RJt$BP3guCUrMehTH| zdb|180G{A0gzp*UuQ7oY)TBLJYCXL$GZI=a)Z*${A3}b9eCB}&Q=DGt+2QFxk~?Z& zZDe5E23o!j6tBB|BTJj>_(-R=Z0z`#U(`oS}T$h|g#LqNc3xD&J6#f>%PQ+>eCkYM%NCY$&JAMMFc<&Lwn4 z%~O|-LH%N-;&vUU7Hm=&Ya8(n+f_qw>j8v=PVE6~=`xS_lCd;oI-? z7TUR6erHB1$J)v(`&xQGt|PIF&ne>qWyYd$LF5)Jo-Z+O4597)JO5#e%42hgNC|dS|?P z`(ZB$IK|fiN(WQeGgmfje0J#ZN567=x$HQ?u6yNJU$daY$-u4VozC@U7m&YbjC}$# zRjkY+4TR%%IS8{3Ctt;*QZ$rDWI=b_+t1N|9esukzEdaz2+k`xl#P)<0(GmI@A2-r z*K3_M1xjm>>zoNimwn7rrNB|XGu9{SXrI~7;-JDzU3P1DA_U~SHV)8@xr1^eE+xd_ zOwMx){s7#L+H;qG&SkH$D}L2}{B;iRMrD`9W~rLR`!nw^o-1VC=PGWCJTHaN6E}5$ zL`4IXV+pA)4!%7&u0EF2+Ke6jj*k-24~>M z7<(DNE{jQ^g1xy*%=(J6^O>bLG9FQPQ)4-x*9goZ+b28`yo{2JLoo6?U7GFvD99#j zAH3_!1ljvc8$Xa#=_d1zSCON_ynZE4$9(oNcbSSHN@cVljif>Pe#XOFRt%mX=~dEG z_?mG5$ttDG(@Vp8jdzDQd7Ij;a&?{`?H9&RNIHl;HCpuq!Mi^q>?qh(Bdw|OqIkk+ z(VR%msM_d^x#hMioyDdr3wLcaO?LM045(VjgsR5wT_aDBVtcH+gL8eQnMZHziYG#D zCq(M-75xB1Qr2#F*>`J5|0*`wnr+-v-cgo8cg! z@#zrgH#5e%$S+i4h`7V#UU;cEKT4<9Sg$dL^Qy-ofV=t`gZ)eC`8_LuAuN!XLn2t$ zZ&c|*r(8%u%W2F3%Ee%S?gtN11vD!dTQwC5YlH|9$ik*GTt6F6{S8BgH{)fwP}rTL zN$2tqkb!W?Ta(qJD9ck_Tj+$x=$FB*a~(r{#a6IK1@6Z&?x*$xvanJw#fA4HSmy#5wDQ?j3J=lk5PcJ{!pW##SWXxCFJ#2>=caBGv0 zO{SDV1NChqxEahJx(c|}yk!qqumGIlP-jzyKB(d34n1%uEB2)N=_0x8PUf=b0$A?4 zEP7-pgR`@QkN5r77p;bUM-P-y(Q0$s5vuTZu7fKnV(xRo2gv#&Z00CghZXgw__alL zaiFf=0Hf=>w=baNbvh@-SsNw`8^~_lqZxfqn9cf@O70AgnxU zn-SWUv$emJFmO-8uJ6+wq#Ou``!35%*e_S^Zf*P{zL}9}IYoQR-3wiM8qMu6g`6dj zOl0ydv=M8gOfDe=$TKj`V5Xs1JlUKZs^uqjN}uH=Xz0@5p;YKo7Z+RGf4Yc&eX~-fw`%$!;bcE;>{8X4)i)(he!%TYnbNeas8e2d>TaYm zk?wIH#@S-7Ged`8LugMa8H!i2>=o^DTUC++X7=72>nuv<;}>#d!+q1jFp9JAOBhf$ z`}Mr9U0QGHl~^o}JEa>oDY&OmgfBth8Cb$s8Gyfa_nsZL)p>r9)5ywn4ZU*#aPzIt zsGD9u*9C7@rD(l!v6x_v0v3Vjd!3Y7j9n`NpYOF_a>d-W>9u9aWvdl^O`clj6Jx_9 zcaq>OJ@@-6XCvM-WvW@f-TdKir>Xm`g7OXq{M14e#t7Rn1DZ#ptl1ADp3@DpD}AGL z@d-B~*Q*O^8k4s|_hLd;N?^RnMcVlZ+kFW0%i|J8YZYOih(6Tzn(%qOJc3 zDnfqMfPjnY%{iDYe;%-!kv^Q^XQFucMclbx7oNBZkfwd%A*S;EjuA&U}B zRRQr+_pd^LrWzrR02kI;X5?Gfpreg_<`fj|X{k#yL^fgSjHM8!xaH-+4ql@8%;|YY z-fGDtr(?_Y^)UkJE<$IryMkYwm5vMLjAzyUM{mcGpvpN7VE_V=QI+Q!tWZXst~!3g zX$Uq&1hA$-;_3@K3OQe=+_+HU)*R?GJ!owiu61IUHh$)%E=-gHJXtqdbm5T?CO|?d z)+1z`Sg9?8R`2r4Ju&~-61Q2O-hFb^rM!s!aX^0MqL;P6MVrIEgLW#O7MtlEG%yb3 zW!li%kaKc}F;?2=5X-U|D?b0>l`V6}eXBl)l2dx*I3n(@X$ukOo=8raEfo-yCF~|c zTHFkjyd?l$1&qMJL;l?ov|nC1`&toh;;M?wrzx+)aRByk%=417Sf9v~nxmRj5!f8llLsN$){T{w=G`aWJ(xy5TM- zl($*o-OSw6>cRX(GHpq~go}V2wJTC#h?d{4*G{YPr*AIKZJ@NTS3!2vn44>Tf8#Hl z1Kje**^yYg_593@d_X5i9LCW5SpBC?%OAnG_i{X}MX;pBgqZbDivAz1IWa0$zuxa) zw%ZZ4o?i_|X(z4p^Pcs``4P{5I{Eq%t-oGggv#H!?`0F)ET`-FA-2}jzK_&9Bp?SD z<1?sVlkm{7;4|#`5_%v2@Ek`DOVY+rNLhBUnh9Z53;rO%bw5h7 z?2-W>fTg%WgQn!@4e+J6BQPhxS8;YF!Z0}_8FD|tBD6yGG55I=ZMrD1Q3M!lJr#0r ze`J8RfZMI&k>bgO5_4|iQy;S)@39Seu|h~ID^Lfwj*SxuJLwYu9$%C;mqvTw$gkn( zk`^8#c`Fik82FEz?w-RlcK36ADbbGiTz;jlCLoE}bfvjb7eD&^D;*8wp0mqNSMQd5 z;f5?u<%RvQ=|3<_`p8zFn2mN=#e+jnz7gOlVzY6930H%%5UuBtI0b}F!{)k1_ozD&CD z4HM1v=AIN{L`B1nMKcpit43ryZLo73!=)gES;xnE+a69{CVSs$$%)ez{?_%Pg24#6 zi#hwH*c9ewOdLZ^z*70qwaM6}lh?x}FG7?eo+oLVsi< zLrr1DEQrvXQ)@75xuJUyY{3$+VUsftSfWrNbCjw%?uI8-S-5CciL5burCm?UN_LPe zGI1>ZQb5)0Zj7`A_7LQ8wn|2s#^&PoyOVuJUtc6s9$*nN@xd50>1Awir5Z95wv?VfaHIZ$92Yz5_GDPf@ zMpQzYwe9qYp(0fei)3B3y%u9jxz7n`| zxX|H!Ugh%*CKaLF=Lt%Y8zIU`BOyz?g`LfxcQhc|%CFMCC4f46`FeZRn;IQxrdgu$ zXdiYPHI!-Uv$-Vdn*QOQZvEk&!q%G+P|47cPN8T3OLkfO)x3N`^=D(B-_|63a;q7a z`uZYV-_VmWQDyWNAR_0frXf7&ytGFWMY~{F50w3L+zt)#F*mE0{c<4mc@nA`BK8GB zyS+MYd8kJj9nc+V7%{nja9lF9E#Z_RbdTBW=Xh68UJ)F1G&x9jR$bb(!3(T4_ZM{T zj5A7zD0_IL+gDBD{&MmIZK3|#GZ0p``_i*MHfh`6!1vMIB!Bku-^b7TNKPgpxB_4; z+3=!~cOI~dHs|M=?RNKZK|VxdgL1h6RLJ{D?8|d@v`ZKJe>4Q{pDes9PX@Q{3|GyD z_*aa{$v6jE+{xibkY{@*+xaV*xG}dL`c3WuTmkBHezQ&ZU$eAAVEJ9y|2TQ-;cw)f zm{EFenGbuX?>b9EoO7i!#<~WRPsXV|wdCD#*Ks#oncwm~%SpDEZt@?UV}L#)g-i99 ztN$c?Z=@3zLb^0U=myn%4|6gXj>%ztx^}F2y-Z&IGKEX;P!@=BjSMcmqo@4dlzUnr z5;AAV6W%ue3>+)-+k5xRl>3uvW?KaU6de}%ZZz1%TPhFe7oIIg$oykTLOGcgC zkQnk%a2+)C{p`i7ttYeYGuCqcIrtrYBfR=^@ve79(2bx!Hcxodx z*J_%^h$1r%(?R>Qlj2r;?xX`fz*8l6;g(Wy@UldXGqOn8`$@*l%O1b0e8m~wuV0(Y z!PVvrg@Sp97WOcQ-N{q?!Hjp2ZCm8VEDU3L03?S->1E(B69BN?ArkuibctWNM$C1q zE*`t`LpQ`OaMRnePR}pcyh>d0-z?+V@%2=PD8prrpt6_*zlxNW?+?$7dS1HPG^0*3 z_Fgl6-u^&mrXIP~`vTc=P@_ z-f=t4cT{xL-y5L?RGF`ZS6)ListXR=1fLt~tr(XPqc&ve{@~A<^=%Vg7@gu{)BDH( zlj^k8H(6I0Jr_+M!eS}n!p$Q+d{;HrB8dSw8eX#4{kd)IkqQ2(#K@+xXDCEcR!YEh zppvn6-m%2c1%p|PejX>1?uz_XrlgO_L)Oyy2N6ZsqQ{Lr)3$rGJk4Yu(A~i zdw|}!TqxNzZ;OkeBg^a7Q8v?h98jmIhcEUYuAlooM6vl3nm+t%_#L$EhM|Ne%uidS z;0LN^>!@H;G}TJp%EiU%^3*pqs@Wfp2;^2koQX=KQs~}~d%gEfj%{T`?GTyuZLyRb zj`O+dyTGCn}Ch~CGpUGuD^vSlIqe* zeBO01pn3v@nl(+vE+372`vteow3+o*oA|z|ZPMWLv>pdj+w{m12YzyQ?>G6)ej`9? zsyJ2nM8g9+shDQgM1uP=>s6<0;!eoJ zey86$NPL{SCDjD-?iH3MNG$=}6~}7tpeF6loqX_4#nQ|7c-N4L!DB69_WUO|{_~0x zy>J2D6;}<36R9{`jRj^3TUgH&$)2sffR;rLNJv!KGP?M4nR{<2z4YB&+NaobJ?FIA zH!HDak(Jkd-92e!#^Z>k&Z*b7@%YG~0RjjmbxUvhGPX7hmXwqPOn;c}9N7mpryjbm zSxH`Ag1~&w95Fcm+87Y%?kgLE8>7d^AtJ$6b=# z6B20@9eK#~4%+rvURnCjS0+xETP+9=pMRlD!kcEl0Nn7sMLosTu0=N6Hv=PGq%7>H@{*zwbI#?@IKSW zK9VVG%c>S|9H!N4I06$-EzJdjZB9^T9Q+HB@OI$T7YwisC#BciyX$%vmu&vyIP5Y> zuSJ>E3+#Xi!V`b!_s$1o2lK3O(hWv;&5g3du=a#u`QCgae+vJ`(JB7O>}jJKSBC9h z(B4l=_6w$Zjdf;FuV<|8fJX7J`)Ya(iUEBiu%Cf{{H2PBW&BK-#C34|d8)OdYF?lI6>j?xfNBd|i`~0nzXKEJuHnzquh#w|`7t2&LPyjF- zsCh|>|H5j~Y^FmjkA?LkOu%oC9urn|@mB_N6g{pvccY~6z@Hf=s$TLGLJo_jz{yK_ ztZMF8*y@+muIp~LU)NGPBvME&1v^gAhdI)kA2saUq#NEV1g*Y|@eX!(-+F;~CR;x& zJiRgj_cn2$=4z(kBJ&v*uL0aS;I@U9v0I55f~747pTtjhw^9}?Lt9-W9KYnGJ)DD` z7h>?3HqEng_bIPpEbevf=*9^eCbvZ@mqmj|6X11<(j#d#{zmlj)qefowvrbBtwu1U z-_N}aV?Ld#Bzc)6noyXPMg8k;!#Ab({DoleP2xl@N@TyH_>PjWZ?R}x!sDZqLu*g* zaVWvCk*<`h`h0{bXc$_yd>}i^Koyv@TDMk)0q%b>z*rv!J`VhSpeqop6EZ(Lrm*`r zh)eU>s(YH%FwDGqUKfbjGRr$%-Km7L%`S<2{GCx55_NtU+bY3zfPL{T8 zQ^)qA8n^QJHAhpFdUlLz!p^)G+h zFDrZc{@bej^>g@cy1>M!heKnyD~U$?pMuL`Bo7@9RU58mc<`zAJu{KK11nCxAPg!F zX0h?fiQpb{nh$jiDcep(oQrbz8Z9@Mj0p~YHz(gO6@ zqD*?Lcz|PS#4eKcUgymtSCi&Z1P35UmDSicgJUJZ#kmFIFMLt|kX&Pi^{WJvf^`aG z^VgFtOv+5~8D1k*CB04Z?m}SG`kyVUZ>w;>S%F;;Kfy-VX7{P!Y1Q6Mzy3dSApUa2 zU34x*L`L?)ll-2)b3F{gW@*9kNvgnZKhME8^iTVLz8Hd`R1U*J0X}G-WXQhS>FKR? z3E7LhUXQPN`<&K^Pr>eIJIQ-K&yeb$`<88c(TAd!Y?=L!Kydbjo`r+so(4x~Zp>xb zW8h1PuN$#~Y4Tk=PDm!?>B)TEj#O!>&QRG!sQeaPGo3$hr{Hvo+ZMKUN zL`%efanqaalC}TPKc6>2jFXoGXf-pQsy;SpH6KLd%)-f9Ao^t1AYZ)d6}9QR==A7@ z%oNjRwx$yAu^t>RNA`X8PI_K8gl>QqGn(8ZW55(Ip59H@{EZ_Rjb4foU%B5N*LP*O z5nqnakUO8)o5Cx#=6LtHDr-={Do35WP zxQjT@+qfeB@u!2qb~6+OJ)KlQxLL>TBZnCwl4^7qMkuCgwmxJhcSG|mat^P;&}0BJ3O%~~ zI+Lp^NHpqQp!OvxhCi|O`J-4#DeIdh7U@UrENQDdSATa?0Y1%cu&K$a0gc0QGK;<7 z>TIB6bbil=zz5v@A>9)u<;|-)%iM{;4p(XJ+0^;bHVm}snHq7hK+Zd_Wm-ukD9Rrk zSof3nnM0CrP4@Dw%bg z%sKrNA>Otp6Jeo@xd#RB#NkdAtOV)+laSt@V#g2k$ROfBCZYWIH*ngOmFVeRkQy#P z{s1CpFG=T?_pCgClV3{bl~z*q%c$fApLFcRr)fWgR{T(_JFNJjrIRRl9OATo6g`uFO9NbEazi00_9RibQ zW-M{&NXVD;dw_#i1@u5L7raMlX=0Z4XoSgU<8S`=zZc{`e(zfmg_wI79ue?_zUN`V3 z^UzP;_^Tu1t6W4tgxJ-vS2RVL8AWUnv2`o9HFaGT@D-Dq%IfL0N1f$btQ1tL%wCV( zB+nAtW!WfRsF$&g2aMc}wsvv;D!#&CkEC$78E3d-xgSO$uahR)w0&zYF|vDa^Qm|G zeIuapn7o-GIY)_N7O7x_Mq)pizUlcpO#c6`kVhPkXy~bxtP7$Z*x(wX-~iy3;OE}O zD+$cW*z0}R+nn*A;D3Sk)tO^9Szvd_N|cf9hz!>ry9z&*M;P_DlDSJN<@1P1|J)RoUhEPgxGO% zxUYXpO^I{;oaViSPj+wH)qZ1lV7_;STa088*iE9}zrqh$CdD&&eT)RZhBWvxWvXQx z!P?HLEoHng*LWe6aN}+NJ2K26O%-jE@|2|~l%~YHicQ+#m+U8%Yc@V4gUUsmw1@qCue?X3fq2sqGld-O=+mF((~P3-@*7ex<_?& zi4nE7?_1lvO&ji8hF*NyKS0(Hc>ZVzk+^2KN#~`o4{R~MRmPI9mwM*wN2#6*DO80vv!QRX(|{A({50etL-i0;T>lR{uL#{Y=)d^6>b| z5jsf#PS==tjW$$Q8@X|~b~h;^Ca;V+3Xt9`@3f0pRWGA8;|M-U#X8!CtKxA3r=eS* zkZpU|oNTJM@$T^=Ct-ovK?ZsE^xT-({lH35t;E@&_uM=I&u;s-Q4v!~_HN3VZQsg# z^%N7?OecbRH1xLAQgS~2Tvj9F{u#^zV*SEo1BO4mliuB=U zS&ubE6vYJYcp#?pVcM?FN;90vecnjr$1w|qZo>4;jvsH~x6rhX6Rb#}S52txCC5F`1&snA*!S!|=uF4jV@m$4m`{JwMBq*;asnhn9$dL@B|&oY#?4f3Q)$>^ zKRiN&`#R+WN9Ld!nlvNrvq*8W{^Wns>|S2!DRV`bf7ZH@Z2zGVNwpl*Glsj(u zsiUc7*Z^#6pxBg}R1##ER=KVHkUZSa#qCe-UQf>{aoYDD7D1u$+D4$i;s%}S-HVK! z{E1ku$ilfds|7~s)Gphv$KrvnA_`-qr;-X zx?fy)7fM6XDYN&+n8`?AyUTfF59=SM6oQ z|LKC$x-BZx%?kBZbM3ySVPERvng!9hzI)VH?tG@OyeEkjd4M}r<}%K)Pw`G590t)r zmS=M(n&kBNrn6q+)4&rPt@;vR)SUi1<<%QOkC+=6;$(vs&Dj>|6U*cm`$%zjT2n0W z=7)CHfV!KHW6PY&Z_z6K$;2uDY$lF-v3lgTm%8!mvAVKCPwC`3fIhFj533VI*SvgQ zU%?DUgUYObi&j}L6<_-zcqYC{Y_c8ge^b7Ufc+-e=7%Plr#*(I~0Z8+}(E-_(AX7`WTh(7;><)}Ir@H6=m zsbzc;Wz5RGn}|WT^nfon@X#BE4;Lf`-v=MjnJJ_D6v$gIDMH9W^?OGtSNsi3%+IjX ziwp0|@aF9BdR4=t_aZ8%0%Z+yWFG8OZz0dsKfCE)m7pp|zmRmSb~FvS?|~*P(sSWi zdfDK6YhL9!)3yMEFeE_n$gu>yd2MDstSXd}GVJXPMsj-9{Nb=Mp9{ycIqb%hP7CJuXk5WO5$ zPg*N_XqvbC<$%5=ELFoO-tKG`6X`7_v*>VFd6}@I9PO`|2KEnK{C|wSWmFV?)HN!N z(jrKMpn!Bp$A}0hjR->zh%`ud42X1hhje#Hj(~J`Ni#IkF)(q5|MR@-uKVe|i?!wp zYd-zvoU`{n=j`)OJcv>De1ihI{@jUcZe+!~>|Rn_UrN(`ml~K`;zxKx@pkf@l;>6% z|5oLwzH7?N}9rcT7gXpi)|;lXGG-fe7_8( zlRuO}h0EQzb0&K8%lwU;6njD_I^6&ALkt`?;wW#vz~cV@FqOY7_=Lp7OVo`g+{?E0 zvea~$>}k@BRWPSfXAxB`)>bItsh~v~Teb6UI)J4ARpgg!z-D^6MG!Z!=4g_jnri49 zh)L&4*3Y~b-2Vdcysm<)e9#F!WmWWvf;dz*ri2a7q5|gxO~Dkxw}&wZvW7xBPfM(R z*5Ar4{zALW)p-6#=(-%09l91sYn%mG9V_j92h?3>i2|PmhkA>>fu<5IS#PhCd47vj z$<1h&mwxq9A-HCBi&Rcw|DzXCflPJX=`oFAq3LZQhzi96=>7TfLvV4LpZH$;oYtGW z3dwiKCA1h>TBVJ*yp}EKmcpg0&eOj4LEkVVdxrVZS5?pN|WC6XcA1T+VP_((6S9lqWxHbp4+@Lq69pv(%Pf!b@OG3?WH|=fW`x! z)aVkve&R}>VWK`Ul@($Bw7lyHUde7#A5MK(V2W0WsEq4^o2a(dnl+W#NV`8R@5*rK z;LneAUbm#73BiGane9|vaaXd)*lA7($Ztkzf3ND!x$@lVu{H@wn{Ba+uW~13Qa0S~ z!1xYFID3ppJ zv`!?!+Bd7MC?av%cF;n|>0fPk76rj$pG4@CMri61$)6r3(fWjoSpa|KU(cg%c%f*2 z#nKUh|ifXPB7pR>_O@gEkK6*j55U=u{Q)&pKH%^Nl99~r99F!BQ zgJt9e=#p1v>jm6qfBo2;tyK&syrAtFwM39@i7nYKf*8jL1M^}v)vs!@JYuwkJ-2s zXhGDPZ8FTRUx>`YwRALbIY!O%7!GlF&uFo<8K?!psj1z=y+L@(Jb14qO$xmI#`WlF z&3ugXv%V*%Xm1Dd@DW>EsPq6BGS@U#ejM)#+x689x)cd^Xsx<0wo|<9V4wN}! z=BSM0?O^|yhPLrUVhlb9z*y#9B?zD^UzEbbk2q#Al{3JR*@cMmX4aBDr-4tHz9?9~qSN0el z4$@2y1#t~~>}!)&cz;vN48D&p-bkh_M|q4r~B zuOaoQtSyqo{-P7(EPN5-zv6Ei`DatP`XNl)qha8kTb6!CF=TA5rR@hH>P3{>hdR{j zz>gX6h!fXn!1bzkih%RZG@28)<~4~k&nox(lr~}Qq-amK5GYuSfG$wVByXa1cq#ob z{92`Tn&98`hQ*kVDbSlp@HSlSg0qUYwV%2xv=`^ID1Xi5<1iun6E#^D+MR-ss>+&R za0jpgwtg*H-B8yOvsg_e7k^&3JoR`eoI`pb?0tqS+-m(Mj_O62}U67B8n>{5(|>S}{W;xT_XL#3Zj8?GW4q!Cb$6bK@H=Bg+ z*Wh|}D5)i6vxK#UWTA4ipfw)e;+<&I)ohujpLhC}wl~Ag2&B2I4lYr%_6kP!teI~2 z=bGao&~iw9>UyC8z01+!+u*ntoB;Q(aJ*09OEzu^O+@>O%0;L zGt&il1e7%>2p>#@J^Cr6HyVzG)V=EEU{QpTARDSltdAz2XLWu}&q!1X+!?Owd}-p1 z-Oz4{1-T|mzv0;oatv<5-RFS`@pKMEsC$}zo%P@2bKzW`!O+B{^z8a2-#DclTZb_7o?*rZU9(;GZ;U@zttgX*W|I}n;^k**MOPvnzb+y#_Kpl z^$~c|CEeGUYvbp>egopRuR=J@&BP!cLE`Z6l8{S@4TTxFdU{S=UwhWt=O#c@7Juen zp9O^`z#{T=dhmbp=Nm&b;Q#hKowCqbV-;kLvZg46dllBV!QpZi~qTT$B12kGE>pH6?0>{9}oQ51pqLa1~n z0Zn6UAq+hEoA>rYPO*PT$t-mm2zqhe@^qciN&`GzLo(!sV3c9Y*1$Ww30iWq(V)FPHrBDSjS^fe799G%!g*tjAU-3iQjZMQp-()7+7q+) z9>WDRTGVCinpx%Xyt48zhXFSzi{{3&m5(YoRV%|n)Lz1T1oS;6Q zV#MgRLeYz5(4G1s>c1dcp`1|uijsK9bK%nDfKWQ{_wRT_GZ`My5NCL0fcY4TujwhJ z7-}j~u=c4KD)xfpvJ#0&Yt;dX_lJ?hmr3b?f zF2<1FLBi){+|GLDk0V(#Q$?LAQf)~3tx`Y<1hUn&FkEF;_fQ&9SPg2NR<>oeZz;`u#8XdxGb4~BXWH5K&kS1sDa13vWp927_nyb& zN7A5YNup_5Yqj|ZfvyXACPI9(d?6V7JEg+kgfqg)fX4Tm3;EJRmN=aI@oYUoXJ|8a zY8lHw>94^ui6%)GNp)noAw{&U_AS_e!OG|_gHKuvdh^?c=FIf!XurX z#;AdnfkSJb*F!Eb)jl< z-MTZgulepVGtphEzY@Z52bM$(08h2sx|>yc;MExPQ!`fT&05b5Y`IqkgT~79oEEEb}zC!WL22(uJ+$ zV#u2Q7yTeO>z4r&68UHVCgIQcE#4kkT$Wvc6MRGM-5A)jZA0fPs?AEO4%JbLIO(`v zY#I5BES2N5?$1~x!YkS7Pt#{raXw5nTQu95!}H}dtyfdgOT21YneyQ=sAo$*iLoEe zUOV_v=mS;L{k|QiK$rDWWGbyZ#dIMX1M9^eyx0Vin5!u^=+Ji^U=&|1r!~KUlsY{i-Aqb zPn{+!y@F@syS!kq6}Z(+;J8|Za}hbAO?p&69cg~Q{5uXoB1PMf8#Q{`i4&uI4SCze zkT^!t|E(UCh8RBryDufsfw%t$3GOu|h#^mj&7;xp^mEm;xZ_g#Yczl^MI#z=y@*DG zrhN&+GggAbKh6?xilTsDW^F`2BW^6*J~|@>6h|gCfA6k^>5Dx|NhE_&(118|DGrIea)I? z{U-3{bdDSsQiTfe;U4%dFO4_#%--dq6|JYW;+Rix{42aw_f5YGF&H0+d2gKfP`?s? zM-_gd;-(gf{PCt7_uY$4P-aLJ_Zg~7$wNE6cX=|niqA5D1o zM!7*{dX$J--uuL)vqGy_&!oS(2jVgO1A2zEK7k4y3Vx!2Jte{UY4`ZulmC8q(=b9^ zYXd$-uPoeoOxmq+I#;(0eu|1os*mUMpZLCM7e2_$fN_xg#xm=zKvuLsEG)#)wzHh0 zPavzBLKzkY_T@V|n@lKH`*s1l$N^qpjjVx|avC6`F)wU`P;cuH8mOY$v_+&jI@y6% zful$ZQ`%)!t!s3pl7TESplcWOwtQcq6!?l|GCiCQEG9m=HF5@whB(0~Ai;gw4z-|< z(SW1o4fSpKuH`QH=C8L~ni+c0F^_V$Lc4TIrS#%_wNxatJYlM$4thbT=L|g?ICM8U zzxpBD;A?b@cifBM5HswzJgq?D{rV97C43(3>_>9`T1{8X501|Ly~-?9sc5LF(_dAJLoKPYeSQ}lriT{zsie?BL@(uLn)j8efsdJ;LS8-i zC1ETLijT)omdk6eyJW))d3%V&F|*s^xbY*H!oeUs}MWi#9u+muAob z>g94Y6@lAhV?SV=cnqopR&#mOf0|1($@QJBIbg&UKb;BJ`#=k$@`@lKvO3&C8*dnn zmjwOjSwB8F=l;hl`;)-&e!hXcs<>ZD9%G+4Um9JM?W7~|*!kK3x= zlkhMlJ64DW_>Sak**8BEL&aSH#862WV3qz-Vjf9zd4VNTmI0XBoi;_@&Suy*FRP>ZBhATlaJ1x*C0U|&vAoy($pqRK z6eD(ZSS{gM04~s8{a6pm$wRaMw_?t0#QM(iU-mD`;Gl$*%g_kbbJ)?W#CG58>m%9m zvb|S19uQPe10kAIJ@g7yWb6r(lK8uSSJtP_IM9PYe*dR_J0j4ySTNY37siDcpsqphVx$x*Jx>m5_ANGW zzEV*&WqiWZ-tsd&ZuP4-rnqps6mr&$ro#@ji%r4}mmpADOwTX;xpEUh`b!Jb> z_x2n%yp|({TbG)e@_^cVY^%)p>Jfw2Ks$vCmO-S#HRP}2gvd>2i3sbc;1`lG@w!FS z`C5c=X-+UYkm^uS05yuj48?h$!`~nxI4Ma!Ypg>kJ7u7kVRK{mmywn2CvVQRh#%1S zrOrX|s{(||{UIRDL)8g>Xh;!~-&KG#=1RWq->`gp=S+n<>X;uhyFbqPT(mgXPtK+q zcQ-meKy-085_d5h%$R%lD{@+#l_G$u_Y2sAfz?)jM2j{mTa^?xw}ZwU zW5&o5?ZuL{tzHRQqBqMUDIWkLWeYzVqUR94Z~w^5LV0az*vB%|nr0XI8A@*7H$XMW zRX$}B+MW8E0;@L}8@;?Jrq$tmbgkYOEdcMz@t2ty(x?6a73yo?@2(XPxg^(A?BP71 zlAph9Olo*Tyz!^fOIW~?F5O7N>-Sm!9&%cz@NhJ9->J5E zRnaw0V58Qb1IgFvO?NS{`#3FW`~1J_tmw@Qa}@S0K21N@tFpNhZgF&_B$|5-@!WZe z@MhU+MYG@=Lk6C zm`L{TA3IB1E5#WtH}=f>)0nZUVZK0|#wqko>7;lCR!T0H(9&m0 zf}JM6bFF3_aMgq0vz@Q_Gw-i{5?VQ}YIl_T+zxqk^ByQlJ9Z?IN9$fjPW1L~-*@e8 ze{5N||D5~|5z@XsF`=lh(N@A`a9Jtf{*T*8K~r5^G$_ekNFvpv0cF(c~ffiJn9j(BZN2ySB*!+hQ{qS}0 zlsV+JAW_2U2$Fubd+;Las%ggW#s6|M7=6hsQ;{V~s%Y9vcP{uVYTDtnsIjSb5jBFe zxCYeHFQO8m6-_Vh*CvaoSg(E1h_M+JmG5>>t;X{pOQ8=bh;Tbf0v5EyUif~VZ-A_q zxa`sR5gBIn6jvbgj<1nCum0+2Xgi)iuI12%Wp6t@3)GHY;Q{FE8zG5KSQ^G=V?hXV zBH;Xs+aK@=L&<{a53K@gYOQqdiUc%}aw~P!38??_pZ*{}5$CGc%9$rlH0s zSo1mepAD(!9Ot>T}N9QpSX z#kDutp`({tZ5n@~tbin&9h~1>tDAq66uZcG*2qHS^9pN6YjN&l;lJ3luIA_ZMXk}3 zhc>oycBTWpJ$sj+{b$bR<`XKsxF941*6meuY4G(jIJpQ)5oOsi_w+U3?1qge2jG`- zY|r|D2&a(d_X)@L*MJIwWCXAHuT{KgB_rwhljIY-@VuHY$`s{#m^00@FxwQlGP0V5cf2msiQ#VDHD_xHos(Zop#}C zu%7%VrG~Agc;sl&dt6%3W@iLxu1c-oa7(&XO$FwuKD(h3wGPS1cf;<~SjN**A(jK= zpF{lyUpe*KZE?)D1}0ohUs#WD`h}wpD__SRHrShTG0*|p;3=J;SUmi5x533Ve{Y`X zZd(BPN0WBXBA>nh?D^!RN&LJmAMoPb!ZMZT>zufy#$89h0=vZB&Ooo#K7PQ3@WG@l zBTJ~PPHqAvzJeVa(Y#?D1?dZQe7WjR_-nBEk@FHnLp$K)@$awY#D}W-X)T69 zx0YW9_Wr!AS8u0c8ruG(IzV!-fQYzOFyJp?>c&C7S?BtO|1vJMEeHKHfp?XZEVbyY{nkMoMT`l0m&YP!u7Y%Q|W! z8Jyd9y|fK2lC6Ws)uIk^!=LXky>wgQRNJx)PgYStN8A3Q`f1%4v1`LnF~kv#I=xXn zTko$Yz#WqHMi{^+!wgkac^hxk<@!nT;@11L90uYz8qC~$M!6S>)2*>gUG7>hNlfXN z?6pb_mFS_th{kddTsZAKb#OTEe$tRS#zZ4H)hr^O#`p9Br>YLzQ1 zBc|Iu#|zRNW>hnfq68`XsWBMWPMQ7m7kcwa`ARS`H8rh-WNyiq?@y<8 zLkhZ2cyip0qTM_aiWra`or1vB z1-6%7q?CG+`D<~VaqLe@g4B7$FcVY>coG>(5CC}F^BZ*NbwrAYvE6VpMy1~`m&HUO z=W(g{k)db6NI=`oZJU|$>fM6&%#=n(R;cjC>sXd5t%bhiQEyuj+bygywyjQX3tRf< za>M*dyBF?H=Sb_+RZLp@vU@-3O+~$#R%FX@=Dl+~`lqTjvUai3??Rypgx1FqK|e)P z{EQ!F3#Qs)39ABm0t8$+X_{kpH5Wf4zsM#jK`WR0%>IaRO;8Y^9V$8J4kHmrRI$P5gU^vzVqUit{Tqb}X^=rl|noh++1gLDSf)=o_;&Z&UsK(#qg{ zgGAN%*Dy?_2n?xCGJ+ri<2KvmcW*W~%FSuF+5?Tbl)lkFs{6$2dnnwGk@;E{%4Uzr zJFEu7$sIKPc$oL*Ij6P7XsfhJ)-^j-eV`^~G1Lx0BQj9x6zgWS^HH3YFX^njN}QeE zYe9yx7$jZV^QLvF%P*9`7dFhQSR4hPZ~4Yq4-gkrP3+MuWu|tGJ9#B!n1-u2J@a&; zWtX^(b%{q-TABR6gur{sb}I6ai%sDsfq98YcyYv3{kYV8=^0}#c0eHCXLj01sI$_n0i?G(w_SF?)hNoGq{9W8k-(AAyA zX(pd_4zpSv{zR7c_W9JXy^90=OJejHC`$)4(<;SRRV55*@1Us1ZNqubDT>eAPyv#? z{eb=f7o4m@LDL~AQR=W^F3&{4Xp?|39VWWQtXe&hp{++iU@60{akn5A#(U#;k60f) z`AX;!CGS#TrjG2BjN}{xPD%KmJ@vV`?n>NpLKti(xYxs5^OL3wrN>*@)GEuk*V#{n ztV72+0i}4iZ3cs%V2GvZf$6Jnp@Oig{Ab4E0p5dCy;job;pXk|rP77Fg`|?4Qf8o| zz!=uIl^4UeFJyxYMw=I&(I1djas~Y|>hLP?L);a#v9#1c0<)EIl6Z@Ckp2ck1mZ-PhkbM=R4;&hihvMI`6%Hn8UVB zUZmt){(6JtaHMQix7_m7fzzyqiDBcsKC9q2?KjQulxJ1 zqX8{fXA>_ZiUY8IT{$vl)ZcIi`*yDFY)xWVO;YZ*Qg7$Z7^keHWC!NJRC^4GbOFap zbgi#)`X7%_REwU^maQ_cT@3cGyHFCe1U)x>rkE)nF$Gq%A3M<`-j`%28UEC1&neEE zG40LJTTf{8Z87iy)!8ekPP6^0qbg_DooJJea`!PHr*$T5A^WFBYKl|^ezcz^|+ObEP3>|R6mD+Moz@w&| z?uCr5fySZit*t)0YOp(J>R-;V0glSVA^}c$v$@9MdF!Pm5o;BUvkNCK!zJq`b-$vI z(lM2l1REt;j}?v^7w&F9zRYW9edknSEwmv2IdCNvH>US@0M?WY-oKo?Yv`YXx(sZ( zZ>?uLE+{O`)Ja8^s-5DO^gJVB}N;>~OyL<30$UiO}RGKb7Z_FzsTu~5bsSH~s9sr@Ca4Z0%M7et(TD8^&J5_laH ziCWz|J4sD$oI2fTE5s-#{OEhhf*_L{G z=ggBSRedGG5i)EN%nV6MQiG;2z7%|uHBf5@+iO?md(EDG12>KJiE++g=YGg9`VaY~ z*-&KoqeHwRadNv2?Kbv1RW?#Bb1^&E;ZyfiaS(9>1+TG_dZIEO+kvr;RqluEE1x{G z%sp4o?I+%VqG5MqEioFXgh=*Bys9D^=rCnszSA?rXVu-|_6Dr!tK#VQ=j|+?()W_n zqm`14<&J&Ct$pRVVe#2;7Zq2oRB4!8?=w@2_^F}{+r*}2ljX}1!SMq0=`haGEQuH{ z{F{B)Zcd2cFD)E?#K7~km%m+H^%4@y+}?LC4krDmD*Bdu;snp^4Tjf1wBFmOX&K6N zMBXW|Z0}5UUNauYq$bD+pew|4?vhKZ64JT1F40JZ9d-o&aW_hIUEp*+a)SG`S?$Eg zDhZ>!L||)!P{%B(0jjNEAT&o6_pRfKalcC9#6$&nMGP0zpCI)4UZ}dovjnzl=XQR7 z59|m%V>|wh3P9n~ogq!usZUUUK3>0UG2E8!-E}4eq{%;FdBKgT>J}v0`1-FLiX5RP zw3dHBil|hral`d$;>oO6RU_~_!W?g4yT2TIp+`nz9C+LjrZqA@rsVIvNja#-knYg@TDV!wWNE*KhISEBF} z>O1>UKY1iJF^vRjMXi$L@A=J6~P1l4CnPw<$^}#_Mg% zcKY+=_HS6kn@1eYR_aWZ5^6ZzQ!!3*ve_KY33PElpAY@uBEB!W=5FGB{ZaDP$!pta zRN>Ut(8N=wJ)@d+t%H=Ih#G6Z9meJF7>MbzwPwqg@PcnN9iJ_d`i60n zI1d$d7820S842;HMVq@ust{*7kF^LRBhH>H=<_3I%HKtzpN3@>vWejQ?*n37V_NdA z9o&2We1d-!u!WH536$yK904>$2IaezpoNq+cTmP*lcez8j^vx&{l>=HopH}<7?JaY zcKzhFQ+`Eq2w`T^K`nPQuW)Y+6LoI#R;8y?gtc^;NK4kpOJ{^&~UVqA6b6myU0V^8q0o zv(<+j0-gS()gOmv^Qc|Ov>wU(L=F&Ra_w>(6|m`qS3fF)zw5P1&GKnk(w+}WnHCKt zxWP}vG+EIXo}T4f``lkMdL>tv6c{#=0^D;lw&+r0FE7I9Vz1WObiQ)HHC(sb|;xU%iD*y{3uumL>O1`$LSe z-GQPQ9U{*Z+WJ8KkpO|KzNP_-tSeet)Sw?fk|R)efqCuEEoUEy9~pj@Li%DAyoJcI z!@!E70{0M}A<0e{&yYXQ1f$Q8lRNhzcD_f^OVS8Jk%gSOX67b0Y~{w<6`nHQE`pQRG6mXHOg}vI`JfNk@%>Tr7RXH+y;P-lN;C$- zlfw&r8iHYnBaA7IY7A?VxV9t3=HUuIe?pj*6J7!jXPMZSUGJRb#~7|o>fhUz7d9!f#uGf!tln`%2j& zCyIhHxnd^$LqzeYqzCNP2eXE;Zm7%K&g~k>P5buOq0MPAgG4ZMT!wCBi;+G~*d1^- z#1h;c_nWaN{CXX{UX5XW37ABcjXsjKds+8A8qmk+jNRvbYqF{fkr&hWz-bW)`2x3U zG%{6c`a#iv6oH`LBVv#-uFOl|25`602%utNE9W<=4+hyo<)OT}?;*9O3TU4NEL_4K zlJ^@Waw0uRbx$(;x8UH>pF=n5*ZoW`cm-Kx_9vXlWI!qYlMp#z=fN!O*IIx@A#{+* zY2@S=kJ^QKJa4V)GCQbMo$u??QHx6g%D?rn{>S#=1*!_qIVsGRs4T)~eAm~$DEQx$ z)@9=G`rW-x&hV+ARr>TxeOMa93$6*>2VPvCd?*jYr*wTwW{mHQ!-okdt+4r?j#U|G z{yN_B50FXgZKzdbd9~uJJ@0SstN6bPo=;Pp)FpRME(>17(w*=PtU>3iHRSupGI_*@ zOkkdFhKOt!^#L4vLMgc+5033#QD^4I`zSTBPC!NSnF6&UsP1d6Z@wEPL_>l?pHWjW z`Q(5?n#SK*Bi+>_t+=`-)t{pADTsQqL3mWgW<1u3%;Z^8Rk%9doFk0&B_JhM&E^N9 zQgw#dC`7ju((RiYQh|>+_JyNTqv8)-bSAMU( zEns9cIlLx`6rz29A9HPgB05J^X1?*99~pTA@j%Hzs7^p<$j9oDH@q}2Rv{JO$Qv~q z(lg}B^}YLWY9qRhfPocKEbV&-v>dS=PMfI;4f-7I~9vLH+tReiF?p?ZNz@^NO`8eCe4wF+XRo_A%A8lJY+ z9YbkKZzav|lbLuzQ4?^;;h#4+2Rd-tF{Re9yuHC2krahhsBg6One;oI-0}yM0_0vG zIwvT>f8w7>BYLY;Rjf2Cj{jJP-V$5D>8n6A>~Gp(?=!W^5(OC}~4^8R)#HsEY#gtPXm}Yr? zp=g4>ub4s);VLGKq-f+V@Vu}nT&!O&fr?m{;(x~_YOPw+m#nwQ8a$w~_5CYRT~qU& z4{)ph?NpQDubJD-LM#1*B0-~W;?>dyA0#^%OaDGC9;gb34Y=JuS<9W`UdfYAZ?l2yUW3XkHMST;)k5VoZ` z_ZzRHzD6)?mvNl2af3S$tbxi$;3094%b#^G!IP+b*-+$3;~}cQX+opTM$atiMN^)( z^GuB=l81YD&et!?BFD1Ip2z@1edzr66-jL*+QOw2W^q0tbdrI<3+MD?hm0`ZHV*IF z`~+RBOkizo3$nuu-KU-ze%6MGNH$LyT1?#?7!11SVEu4zFiIL11)5Q)B_*|UHF_o1 z1r68y5&ePFY8>D&9%{CWQ}Hmw6Vn?$Fi6>lGM&~58uy(K zKTkPH))rsxV%$~66bzTGfo@Tfe9%y1YwOMdmqzkaM6mqkI}E_&nt*5Nnc$3ZMq zlX`+Ku=4bFRG_lN%&539)j)n)3ZyTvciYEV3%WQe>ekQQ;GZvuB5;SVuw520uz_p?J-&!9?B z4~!5Km5=h~yadm}3Dzp@s+dm!|NDn({EbSkwJClP=&p=bn4%d&uN&P}sxk|?#vE<@ z=&6)RyTP3?x0wJ_?=Lru@^yYr$QAs^*ix5Io2m3^=OmUg35Dhsd>0>xabmM!;m)7; zJ&{XO&|5P}sHU*1D=Kg?EZkh`WsccTzh2b(g$xEo1$UX~pOvmG3 zgxwH-+5Gs{@KZ1OpAY)}MvqR;dwKh*Ws9C6Qh81r=p_9?7)W{8jGS?`hc!1W@}_QI znL67D-3iFfX;{MjK64}*OCrvn^;1DairQ0CA02OYC%2)%V-w&a1KW_T!C zNUZsc212>c6>YN(TZtBS-s$%z?MX4x7{w;ysFxXH2kjBxl*35>U+E?x6Nmi?=^_JFCL7>jvKFk#}1LrNGC;dQ~i z!-zT12UDu_5}^@iDQOtyk!Qsi6|+&+KNu1Amyk)+5ayg@XtUN~D6$v}K{68j`f;9J zU)mnnIARZ+kJ3sAJyVDT%uM1_tR7udb01b_1rsx*;1W^=XZhS=-a{>~JpIPhQrMzx zR<~jh*xc*H+8S|B9UCbclBpXucazHQvs2N%P<%S9OdLHoc>ABo+{b2mwwFRCbD)`Rp5 z;W{v(+WwIR=MNwCqWKt8e6!hYCZA^fA(Ll{)Lpjh)R;ZpHj2=IEFfbC_|0Ki}ew z;$Ij~=k^RgD_-nAl&|{2k8HkC>bQLWAevkPs0AlqE-j*yQgLPc%!@ZM@~=Y5saPU^Jb#p)fAt0XJ4ZOu!;5mhbEl(>*8tCTBRCElVRSP z#RM|djEK0YO_;QwEc2*4zhwdAG4DLL-!1q4ZqR7wh^s&KG1>A@|JI-S(|Bp(gXA8% z>Q2li(GNJW1#few(o~7H4_@gF3UXLtB6El3x*E4BX!1)kn?@R+EsNJ1-0^VN56B$u z&dmim%#2NiFIlO5Acel|#1XMpHQ~xIKyV z;2Z|e6Z8tlfLE=zNbUoeS^1NBg6u2_v2Q(&Nw)w?FPa(aAAhrx9JVg-diD^Q{75G2 zT&Z<%Bw(Tc6ml96YC`sgtoG#{qh2>A_oem;I3I5N!VDQk!P3VlfF|z(Z}HW2H7crV zqW|0~A%-*T;Dh!vPKTx-4I3%5f8=7#hTtaQ`Oy{^QO8-JgIuYE3wPh15`&nCu2Ld! zH=G2zu9l2N@jK`O8fh9-sRc0*0xO-g**IB@CP#csg!j`NzPNQ zmUF-x@x(&S-YH8fg<$b&^zsUoCtWvYR?|Zl)~@wmoW>)WbyANRjC?;8J9>Q2Yo?mB z*;;HyF@?j1DF2u0`I6`E;%Blz`5F<1#9|xF7lD?ZO%f3-+*uV-0X_$w*hTS!9tXKT za?%&E&5tt9{8Uz3@i26Bv*~ty1~kD3d>(e5b^o?yxQ`^7)(z2=VJ^yQ=~6)^`m<(& z&(S+JgA7SGLanD)B`XPH-!Czxke&*gtu%tWsQC8v=jZi{oGc*-T~HpHB48O&XMa{< z`Jc{M8Gu!p`8|pt(-=(l&}@K)MN)z5RQT2h2s*$1xutcB{b)(u7IqW!3Gi9NCC0`^ z_DX!(oIV^)+|fFVDV*$K=49u_^}52xy;)kgkC7i)_hjXUy5Q}mIL_2*Z{!`~cmREipiU7IpYI#tNxaeCf1N_Y>(N$!K#uQJMAQcN_S zJ>{UL(_^0Upwt`UGJOpx@)>e6M7uGhwYsf1>6e}5QteM4p_tUB^QLBpch6H(=3Nn7 zMdzOC+4q1*(Fc9&KmA(R@MiEsjj=;$TTpRnb60!hKb6;f^YL$tp&lZ3`+Ge63u-M(!Dr z_m!fcxHqlnL%p(SJie1l(bza6~EsG03JqDw%te(ym&PHNOjkB|Nm5x%c^)HmcF$IDFkGT_g2B5Wi5r9N__`I zM76!KU@Gm;?eb}$AVpmcy(ygjVVr@0OCb2!Kq$I8{&=J>WAjiHcMklAG)}R2mQlGM|aG#-Vr%xdZWZ~B<^#F)juFz3bj@}UNhbRHq*j?5;? z(Pz`+{g3^$8P>j~on*x3Se1sj-Fy{~^KYFbQ{r+!VQswzzRIJ+-Lgkw6beSa;Q_+A zrnWtOw3w8v(E>d?$jwPwPWqLGpQyE2n5f$<=0?eJ8BUvwA0CxEpJE}6&c>8XkW~Ro zs+eV%QLbi_q;|#Ih;@0kQmmiFXCtC#EJOi~4BKBRL!)zlRmND1r=!{2!d>dN zxkd7%=v4Q!V&hhG=07!pUvi~B6yA$t{aH^gRW5IWf02G-?11%wDU6$AdpO$!WuR73 zbt~iefJhRR8h68AT~7Um>efN>=uEDj~a+y~%bQ zdvlUdW|2Kk$|`$j9wU42ad51IW1qvpan}9xyZ86F5C3@h3y<@DzhAHCbgObq-uY`eknWz#{5%__o_)_>x=Dz{ZHFgVX z9m587%nFmTc@nK03%@F2LuC1P!A+e_^Zs{@ccQbKRMw8(^ok{i^1IE76XcUq?LDbk*pa%td$}19x{8# zLVBToMZ(##-?pcO$wwfdD5`*AzQli!4M4oy{vlqA1>0pGiBoi8eGi8@|InsUU;Uth z_o3HZWi&Qt__%(5s$3`df#Aet8%D=QRtV-%o1o$`m!Vt0?)&XVOwLvp)Hlv+EJnZ_ zb)PRz-aOr6onbOLelI>fC4l{UC*#%0@Dg;J-JwKGeNbQ33qTfTIR87zlg`sT5}V|g zFxluI2Bhyf3~SfnDluXW>}k)so3DOj%HPch@RGRs^qKEHJ%u&k4z8i0pj>RXItB-mBoLd@mg2xX5QhiLjeIZ3%S^blY#1z@KxBCmWhwi#G`J z%C3&iC~q<(?JO1>nz#%}j>p8@%4gRd`4SJQJaLHFe@fyIwRy2USL~1x5m@VwS>0xb z8)FK8qAbS_rTdu}yuXusjw~y?c>w%{tVTFkI2&2Ao_)Xc_kd@N{BLyxKsV+`wBi1g z7(AV+@567hV;HML*x&G@i4AX52zfFW^8j@AU}4Pp$`|<%7JT)m6S-k2M{I4fC|V*) zS36DB^22&%ZDV--8PEMFdbV|SD zLj=RbMQ8@VE&m!Rh+w$&IJcmQe@43>jwByw!ze_4r)(djz0PqIPk=v^-&qdZ%4Xra2|=VUGVj(yjws3r<8CXIDWf zp`?k2O87CL1sC?kz{2T4wJE6*MCo8~T8&7}W2PK2mm5*!utZ(bLLXWjMtZAX3&d|E znV3j{*LAhGqYqD5wxMe4es$#Xy7}Kuo!6wbY-c!<&T)H51v=R|Kv+efqw3XSG$QOA ze$rbz?nvCS!c9f3_1qa42RcN3w`i+`7M7zjhQ&3+pToqd+c@7--jA|dXj z55FXi92#Tq*U!Y=Ro?soHhJYXjKBkL?HeQ>m~FqFt3E3w$w z@WcWoFOT}OV2P_a=rD;BlN(5A(9SMRtg1r=Wf)mi*&8s0c zPGmJ_RmZ-*IMZ_W^3ob!_)hCLyUEelD4UG(-elN^_PQ= zP|*jg0HE^YpB6@irgdy?Q5GF|7D6RLrx^C@+}ZDrXlmh6hE+H(XBlJL2bD3|S2wb< z;77I@9#22|U1I!zVttZ#Lk*4Y9rWt6^gFx#MxMR`U<&e?iv4QDfMUuAu zOp02_Z}IZ`Y=j3B=>VCE7i2=m8Dzbcs`|>~WtYh!&9f>}pbB6$Zc#0~7>O=Vj0VDJRlKtMRN#B9sW-^0MYXG;_UHR{chi-Xsf$9RGZ1sxEIsX)i8KA)oblO-w_OdOqLNL}R|J z8@uPlNATj3w=LCjC=M%minz(7Vw=GnMfuuTDricZ&-*R6H`whOOnFhY5W`-i7CCA#xbvPvXhso^|(;1B)Ii{+zPGL6NAZti|&{;L;$kj`Gipk+~<{mOn9u z)xk9VhG+mSriM=Dw&IbXA7mQyUGo@6<-8S=KmzFhJ`W>xH_uqtAT=M^pw&mCMpH`; z9zQqQjTR4csOlH($x-LcT}Zg;G)H>mGDof1YecaFBu#l(GaI-2CoL~}gqc6f^x$$} zi&w^7j}cj~^)PLGdEm((F-xi(g*7}8Jvl{eo==e1ZCULFJ+(0F6WIKv-Up6j_;ugX z3$MY;^(XVnq{RaP=S7j8m9jN0VF1r`_OmM9csq#{n zT`xXT-w}Ar^(nb0Y`9(j&M=E3U=ffwhOA?6JHHp9VrHcuZ?EUIS1=s9;?Ey-ntuHt zM@3r9Gdo1GRJ2|}Lmcm}^+#chB`<|DtfjxZWtU&M@io~7P|Q#QfDP1>tXi62ke7n7 z(p?|xy@J+nc&VZ$B;PL%_0i!g9(!|J|K43r$;L#XE-CTh6KY#MgIq;ysZ1}QiG7!L zoVFnfc8p=3m}w<hfd*<5;i$*ROaEEqNbMq*u;n$L z&=Q$uY21LohinvsNNZYm?2=J&*y}oN>A5F+4KqRG_L<-h`dK@L*QAUrRo2(SD+nv= zfmZ$D=zaME^;8O~|F}G8zZUEJm&`nT9w2x4J|wQ(xl7EIm*jum!P=Tqx8ON(ywq~4 z5mTYPM>^G2=fNNLL@PGE3Y~_(0R?HC5B`ElGzh}Wi)uCcXWa}azKcE%GQZWTyVAWd z(+D?Bk=8%ob4j(Q_#gs@l&j-Bgc4Fx#H0&~b`nhMX%fCA9*f%gCsqN}-L#a%xj-wx&K9Q@2`o@hUuaXE4D z56vEQ_AnRy-psE+myTlUrmdOsp7MulLT;x7oZz|o;t=+^wA%RgmKHzBa65IWa!AUl z<(kR$IhsameXQgRhqhk`d69~e!b(yFuC%qSIU`}(81(A}X zJpJWF@;_T36MeW9wBDaVv}l+&$#k0!xK`QzKYE{;MSF?rQ(Jbl>?fH|a{hnD_uo&pSS()_ydtUg z=w3!xUs&?7XJG&9$8w(T75Pey(~pg*dF{ri1il-}IkGv`=!mG5Y+dZuS#9gDX$(#> zTU$9z?Ac?yQq(jwdqcf!$7EeT4rkMCC{eSR_#kFNp`@r5GN}`C)$Hx1m+y^yHLj+! zmI+=`U|U9pK0nvAEVZ~4C7kycKU(5^5SYH=4x_n&`d|p#CBqhJ8HrCQlSqeGT*d%WZnnia{ zd{yIy3Bz~GE4Z(OdE$?dYl}^VNXjz-2Zf&8DDLdCX6$YFMF1FwujwEP)sx6?QJY;q zRAMInWG{gkmm7m`JS)`THItfx$M;eee!+Ci^mnXJb{#5(_W8OkgE53iM+{#&b8;Qm6}p`;x?KlO9{&r(${&)|s%8WP z<&VdK)=p`?Mc~Pt!$btDMOKP1RrftYMXvj@Jqd67fujsQRxwUHeHewGL0Sn`9Y5jP zj}qqkicCJA`ML>_QxBNX)ul=`FQVMFG(wR)9JVcI;gysFda^1h=x7E+u;%Wj-!F>W zEO&5zwHNZB{q~GhckO`m=2?BN)}LqUKLMP5#<^kp($0%XsN_`_PMJfJ*~}REn&?ON z5~Fef_j zm3~Q6aF&xV``h#lnnYC1R1j!YJyLZ$Udzu$4BqoK0U4xp1R51(s8dlwv2|t{k|M^d zi5k+Wzas4R>8I1lbji8#JmcE<$CPl*8nJaT-eFut@%(cMO);)~=>wGJT?6?8LoEE0 zGH2)i*F$|twQ7Irest%KM`6*02zvB|tqap^4;woY?)2X|ED2|}&1RkW_2I83otzlX zg@w*^{8Pd^J?WA!4|p`%YIpv+bpKY!VR~)MIKtgKfff?ZwBk4TtPaBJxi{X6aT@== zf5u`i%rl{VDd45>#9bECU&0zkn?u0BRW5=olJuSI)3A!aXL>aTJL>dNui@pJZU2&@ z{1ra;2o)Sr0sb}ZZGA0Eg7@U*zV#k~xyQxyvU|x8Ev>68!`-}U6L1%7J;FEhI2Eo_ zl2$>46T6Jj;)PZbLN7v2gOC~&OuOwbMTihkT1H8gns*EVdT0yy-xS#3_9l;WPMZb5 zmH3Tcey}Ap@bS@_(P_OKa^zAtC;hC2_|G-5*L1MtL=;sk(3VEX;Pw!#0$izigo>fP zIfv}b(2(JPGjG!PajP<)B>H4jXahj;{1+|khTen6Bvqz>%27rJn^`= zgxYwZ;x;kDdj|>8hM%Ei(ftBtfuDmnWH-`y#}SoX*x%s>+5)A`p4*EY#p`!JZ@cWQ z4H=p01?_?w?cT~^J~CMkY?OXirg$SsOsj;ytnGILF8>kIRO9yXpNGI(R)}25If2fX zT6yK!ky7N#{d%23DOmk&kRg;iaO>GGOL(YbX7xnZ#w(RfmGqrG$l;gvUzRYXv&ya` zG#-CssTZ2^)zq)rHHLL1X!2+yAAlSbaNCRWi`aV8@kOI+d$a-!4Vy~5QnKk5oc0NP>)jMR!i4L!UXm`$1_0ERkgzuy6MhdL z!eT07*a{?yd)j0yUk@~}XJ&5BDJ^_3qkhhdYWKf0P7X`hpw#}f&lW+?a=NMrY}g8w zW4!V4#p~)Jk6yksMYj)s=GW^e^Es@#o`ceT6rlcklb5b`;}$CBi$b0G_H&D>D1W=) z;^Vs@bjp;|Xb(zWL{E*L!Mp1pj2w+?d~NZ8Iy@}b+~Iv`m-r%@^F94b>i^L4 zJgB7(@Vr=ZP2+M;N}f*@3*hpvJpB`>P0b#!dRLV#VAJR&-Ta$cDQ0Yj8im<@=;ENnFGcmg(%08>y+I3@*04g)9P>li){t z;s6?_O-*UFxq$!sjfT-5Cc6PN#=RfrQFg0axUNOq>8R z{u=$#|0o0`MHX-t%*?v|8N1}v!&R>)`0<^xxqo}t#(gOd*)pIpG!Gn1R<10?hRqtJ zyY@nt6l4-^0GLPku}%r&CF(WZz4hl?^2`VU)5?r~NcyYPGonu%uW$ocyk4;&ZbX{w zRZ0falI1;dqJ4sv9~SxYjjQbipg#9iI;wAf17Io;VdXElp3J-GGou&@a)a_ZE4OwT z(mk4dOFo%|V!A6Idy#G{czx|FZXhN*HqisZvF;OAT)E3yf+M9o9%S}<;PQ2XDtOs4 zst0swar$*jFhwMzjj~0-TD5I!+8#h?A{?P(hh?+k>82o{za5?{MXV==kGH>t-}iX8=GqJ{-mTsc2!v|qI07I~jY%y%Dt&dcl5ny9ku)8me>6GF*Ws4JlY z8@J3IvONa5-oyCgk-vR5pG3ToqNHniNleV$ZM!ww|2cN(zn5AlEeM!l4|}oSLg#vh ztLbZbC|{jYtEi%kcDzXvF441C`5shm<4g9ByhI&s-)sB*y6a(+F19<}?G7KDz%>)T4;5FO&SisUT)O91Vj2+SeaMK6%!mtt2M`ooo9m0vU7N^j&y<0+NwD zgT0k+b9HFGs@(ePLl|kF?#XnE)Suw0ptL=sna~XlR;4+Ro!zKsGT3Hhd-BTn*|yhN zZu{`sQZ~1$d7n^+_o+)gEXcQIL4n8fFzj=&-2wc7?7bET4VpWLpMSdH1P{R}&Z;K? z3Iy1gc!96KBT*PbU%d7Y0&OF-oBU?^7ul<(-Ua3%K`g^BIHzSy& zUC|eB4=sBiy1vr0E30SGLL8nzw7~G!=KaD!&{tLhjo&Gc1RrJ;&v37yl! zSD~bxsk42|&#LUMia*1$58T>YRw6kH=Dh3+8OREi=2%Mz5#vdaUH_R=s6uZ^%l+xVtcCtuBrlM?`kGJI)!t^5&uEpk|tEZ7o*6BC#v9L!d_ z1I(lCZ3)MJbVou9WK`4%!Y#!FBVC+Mcg!yyF?MRd#g*z_x6vlSk@l`SfMnggf$K|% zx5VIryV$R=e;Gk91ahKfrgH`RMgk=u@pJ`%mEJik*J?t4qGuQ&I!#a+%*snRwmKd?N5=C!CETailbX z)mCLsOGcnfccMV?HOo@J-8U;od)v!<)Dq2tr|0(U21aeKU7qS5N{4@05@rjRSbFZT z_I5nBRORF)iE!F^t3C5U+2Pu)eB#CNNxH&9sLg}#<-^Q09#q98OvXFxZ=O4#-hdth zXMNbOHC>6g!t_?9@K5cub7iR>yX*x42cD338mKedbAZ6m1Gt3%CzKsNrK8fj_vN|TWWPZ9$I@VQ z(U4Tz=kU`cC9)G@!_U-pW)}#j;_{tU3?c1U>hKWGE_0h_yBO4zM!~Tth{QK&f+BXh zC)iBZQDsl^&%oC*Rry_jRmd<}3CgUlx@qR$u7alNp@5PQt`sVoOIAS$#Q68AOZowS zG&~;$kE8$=gOz@XcZXm4$s>x=(jn*pOI^_U%IOQphV!n1`dnB;%yaIe8AQdOE9%0*1Mu<5Bp;l%ZwRx*nx|DKMsH%D~hLwe4C$ z%=z{Qdz>xr7PVK;ns*c$q|}h_2M?;R*)Q@L;tzU|RlU~36CqSXNDDs;buBLKF-I*(&LZ1@j z6v3ZkObFF4#7ovSDSPddh-1W2FV8vLW#$JZdFDOk#w=qaWSWO~W4?-FQWO)k*FqOP z!%MnV=YPXA5}rXEyy`58P(~d*CPxqrL&?_A;=zBSgWP-T+z*waff`B!742V}jrgg^ae^VW39}s)a8bryH_b0|O|($~STm3E+vJHI(B@wp z-(RuuFb0M%moRlz27FHtqe@*3eJ-TW54p0c*lgk$2XB9n4^1Blf43Untd%4PlGjo< zoH52qQbd&2RbbRUhSv#Zj~AYRB)#aWCNK*{F^;lGU0JQ_5(6f{`PO;ZtfL#MVz{J2 z&maf_7;9hr9QNA!H+hyU+P93iP2wJp-gwFSN&N2O|G2g*Iu%6s)?11vXx28GqH z&*Y_t8!PV>8Aa)0K}H1f)a&wmZB6IH>wt|7!?%j`EO zH7CzP{q-Y_^NC(xHI?E&kFTi<;0_X!i!sRO!cU_26PoA7afP#{+Rr+vfhq53q0#!g{Op` zgFw2Ll=B~}zCatEwe)^y!Mq6P)P`|jybjUIYfes$0oMODj>1CmRtilL6x+w6)&OD%oM9Z^?UObr ze%$AXS?xmaA!5bVRvbVMAWx{M6Xr%8){ zt3(sZkkS|jI_aI{5y-_~Lj(cti?$~{JBFP+p-llzdv)3)il>6d@F#J%7keoeW!&*` zn@Xhf*==_>f;p3LOvI`Nk~g7SI`b}NHAX~pMBym5-`MWLS8{z5u?%X6(S7_+M62Ik z1m#TqE<2m+M10)?bA(nT89U))q4Unro{;xzcXu7zbj_cd&GN8RPba{qPaxdTC?FN( zHN_(H6kc}-9QHK=6iB)FpD&yn>2?B$28uK}lfM){4|oqcHh)Lj>J%r0aHQC}w(MXo ze)Lm7f52oM%rqzFw>b^Sx=eHFiDtL&l)$AEQ*syNg#sFQo(Jqmr1W7m3MOt@tPilx zs*;5*@dIN40$fMPWJ;KTuQ`*zBw5D)lK|mYkzT?Oyos5 z$UkeylJDZZLiwk^VlQmuJ6~4#c!!#6OeXDm@K)AjxdnaN`V_q!H80lv`UG%PA!qg_ z>3kt(b-HbuYWWD~1!6IG|7Ok!3Q?V|7P|IEcIVEv*uq`S%UiDm=O?^{AWeWx?H=9Zlkyy|2otO*eDb^1h^K0o47Sq-6Vp_MftGlw#!#*-6@Wk%W?VNVw~tx8geO=Cw*c}aXEeh|B8 z_R%hO@az9z(zQMB-9#I>v*+Apl zih1;}lvWlYVeZiN8zufrmq(&a6aAXW<$8YXc@)- zOfQu;_xGbA(o2bCA7o2_y&$()vS5p zOtMd|U7&ERT!kM1dO3lOWPq^flXWH*>dbfrrl06e%%JLPzEE_Q;cc7)QJjI-4bsZ5 zIuL}=7TBn3@z1k!TW2h!*^NXOk5A5L-h&i_3Ycs-bh|wS4xk`n#atY2X3IDsb6w!a z7#WAnwAv`%_UM5@|HnpKZU1Xynu_@OMD2Sh7!w8iZ&cbDNQf||dTmD2k_YzzF=|!X<227oEbRKaocs2LOr-%EzYhS0%O0(`Ouj5CaIjAVFiH5)`&G z(`%Ckz2w4tJE7p8x8moiIz{OgSV=-$QPY^;#a$0Ji6~B#f*e;S{hVOzNNRLJCkrKT z{-isAJ7axa^SQ4G{cpqQM>F2kGoQYMd7aPi8^u%x+Jw<=Qd%sqS;ilvQ7oi%EA*#S z8iOjIcf$QgGUhTZJ$lvxy!0r_mfg{Mq6qhJ`JkF}%!a3K$S+p?k{W#}&aUFdxR{Ek zh5xpEo%GTmJ5PsFSbnx&H=dEL!}}6lp?!sQV-Np~W&$-YRn|a~R}$Rd7tr3wQsFhX z`e@Wwjlcd)fsPNJF>qY1;Zyjq+ILNM>R=E98~Z~C-QmwczM}7zm-%lOuqyUXk67r; zgnM^~Nzs;1KZvQ!KfEea;-)oG0CMxlugO#0eC6|>R^*T;=g$D>0;b2P;o&+YW$Nft zj;k2+sK6IU%14>C$B}|2+V{~nIn%?(spxj;3XLEG!w2;im{fs@wFECyqsYOLSpg>7 zrpJ_7+XNjDam=sp_m!r-M(H}6)&cK@n}~jk5Zr^8;e(txFv@ciy9=DPx9YDJ+rU$G zKqR`9b8yKQ5`AmKcEozULC{aF-8`1Jfm2K-^YVB*r?DSh=ytlo`2c~EBFWEpD2sUL zOUl7V-KPs;GReB?-oj1q3t^#h|Ab0|!`9aw=*E+D6)tiacKh*MYIw{?cCjSfYCSk- z#;6W*AM}~>Xv)fS2R>?j9W*7Ws!VuXgCr*?oL{(PjxsG15jOfV&D~((`_g2uY#nw; zQKBV;l=3O&FZvP}Xw=gFcb#DTsb}Gebryg>7J_7AG>*;?v^oi$TJDt`0rpI+5tu)Bjtq7x% z+Kt;z2dDc^DCicFDPQ!bT47w-&D}bj>bXS_yr--Ie3Yb%rMv9w5|x@H5|NS* zpry>i6vfg8U1fYvtg-Y^ny9#TmOndIgoYw{mxqY)7jD`0#6Z*te%Gcyh8{$agBvN` z1|vV!;xxqVhc7_4naD1s$tO!YihR)V2a^=_U&u>99Sw5E0C_V+B-=Y6@Cx`M)1ZIq zy|v#9>zlu1RGUaG#FTV)PwqK}kKkT_+6xPhFsz357N&OqZ};|Ig#HibFB%nciG)v# z%epmiJHUQjX|EHyrf`_x^C>JX+@VeV& zBFA|cn^&MkaUPs7@Vxc~HDJXh0go^5Kk=bj$<#F`1|xcjq91J^->=`vmkHCLO4Z{m z^m4!Xgs`o*F;VZx(XRgVzHnI-4a}y`P(g7qECo~MFxH6@za)lNGPZnI5f_J>1hZEP9D0jUE$H)Yp3W(h1F3e#fK=yWnR@<#O7;(~pnX6cO zF<8a%lFay#{I9&zAQnmZwWmLeNufhUJfS>x`4o z(A3jUolKSUPdrv1%qIFp_CtKK#^36sAot*6byq;Ey~^OjKtLF2n@l9WKzJle?)1cn zv^oa5qfV@&xM*)Jht>76Y_pn6hz?~)_V|OV`c$0=d-b(q9vrC6jUh>oX*bJftsu&? za4|{=N^A{undp#C)ULo`84>W#bqcqs$Vj$4@?DD=0a8Yx!IZ9^as^gke#mLTw>n}b zT!ZV=Vx{KZF>f)E=_jE}%>n5tQnp|BoYI3sXHs!=ThVglhu~jsZ&9OyFe&lyCqOp6 z#XlH-O$Qan{MrmvidO}o-rHr{9 z!dnht(-{%;TZ)^iAgIqD+#zbgCH=@rL~ByN%^f@msVf4ZME8^;of^E$enI z>@j|&_Oe)>|88&iDh3R_IvbrU_WY8t?jo9TmLe7;chod!->MY@2*dt2TeMh#n&J1` zws5PpJ2*Y7ce;Pg$S$`~EZ1+ExemN~F-=v>_#)5nQB|D7B}*Ub^L&Q27~#Q)Nv)oQ zl(p{tE}p(g4o1C1nbC$fbqnHg#slT{X~?ZdBj1=uo0fzp>P4l$+N2<3 zKlBTA*{90Uy9o2aFAToDSbUtioTGHOZJ|hOsyaSFf#~>;g6iNGFMdYW%WKe1uTWU8 zzVNLNf?D#Ua5>dZRY-am_-%(zjGxHwIx`&@loWAh!%6+?vS%Cod*bh&)Q#6-ZB>K} zymI38vwLg@+Qi9(V-x(FbF?n>Yv39_gss?BJP5ILwiwhby@*MWFZP_;gHEv|jZY)o zcWf_Gx&;n^IJ6MmmFW3FS3spzS1KYO#8g^Z>c3x<`DqIQ{HBEO5=(DRuSw6|M)rhs z_ro6=Okz1|Z;6d4Gs!+SzpN$%9=_*0Nsi+3bw9_CKm~K~XA+$Kz{o7Dc)HvCd%t3k zShl}M`Sbd%Fp2JqtJY9QaLx&Qi8ypjbrE12i=W}u_omkR~p zxq+M6eu(JV^P|FGJEt1Tu>3*Vv?)Z;pl77L-pcYNBnZ*#P5H);j_i{*Hn&sS&P}c> zPQT^1=%d?+OF;>wr~V zQxp@x&lN#GeshF^R}Ng_Ks_xK$Ey(8rHT3-`FRS5j-1i$(VGw#63+xRlj2_{n?9H*)qdw?HodmS zvqsz%u*KiAmT?ND9TX8SN6lmvCWt65v3ceUQfD<~nU0*U8h5X!(;Ek^j2}JWY6g>G zt@L^xD3^4vRIRujZ}9Y>M4q3;_L^Y#(#1USf%~^n0(L@cFR(Zjr@Wb#VA=wn^m|V% z4%mGiJRX;OyThW6Yu14<2RZP+jeXvGk=HgvuS4>8k8GOE*(BAA;XdkMK;4qX*|zTO z<9^Sas+2nZ(ns1Sp%5vW=ABi6*Ia@Z-=B9~X`es`3@LCcKtG3@ey>Yt8UOXr>+tD? z(t%X*`u*S&ozHUm?Z?y8J<+@@&!@ZgueVnnK`sMcauC!$Lvb@w0{pm6Nj3CmDa^@4 zMq5x@OIxLHM%p>O`*YLdBKjKYrpqw}SKejDq>kJlq&ivA?3$7aGPZ6~GGmD*sI>j&7rn$Y^Pj}lRMwyZC_$Bz2vYpSTi6TR8V2sk!V=dNF^SF~6%+^Dwr+x_Ge_y*IlpbzqY<+!NiG zkma8Ei;N$E0R!0sJL8O)sh03dj1m_W_d}w%GRxjqCv9gR_c2cx!m$y07tBIV#4WW9 zEyZO^ThHA~t}F_LtS0fOxK48zB0+0C=-V>Ru)hYCx=NV)1}?%|Ur0gLK{wnCo7r0x z&6sAM^BJ}>T7YY7kW_@U-X87{!M%u5&(kLku4QVy$DQJ#i79QB{@B8b#6P$G>F?I$ zZT=qileI!GAqb2-7BK@OzEYe!2cQ%IZv=z_BOQoL0C`>(b1)+MaQ!qoiZLRbyB$m# zOUQ9Wu$pq-vn(ZZPrrqVu6$C!OD)E!Xu1!Tp&2p1zb`JgfIchA`b>7D}cO zp80UiZ_)1I`tH}V7T+rD^mIBn=iMm8 zC0u(H=a)-Y%^r|;a)MTNTO>geA5=qMg}Nm|=0D`kkxyK`UVr&iC3}tizq*_P@ylKn z3V;+RA-b*qKj5LaItqxusELz}T%u1-Ggd}CdYtHzEv@NTl%;k6pAu(3=b8m0ROqK7 zFSlm}!K4Kc=;2^8Z2v@hhwvL#VaY%O-7NRrbC`3{mbnv<_rtoRT9Wb~$D9le+IQnF zU6TW!+26CK*{}@;A`o|xdH9|W8;AofoX`G?bVd{#@ z97he;@%rPU?!(zSI!rk4aYvojudr1!#^R3?p9_z^Fmi873=g1o5OrlSo06p7`VAiU zP^|IG)}2icAceeHN9NkyUnrWp^=TDu!dcTm&z!Ga( z<@N39X5-6N|HS0uTSDb=@UXn~S?C9*VnI=i#sY`D#E`IY#p9y;~q|2tHg;;G;|4ai8S+> zw?5~@&bJs%wD*pN7ds?>;|5FQCivU?kr@EkWs=-P8cmVKpI1_mX)iJibrQ(gfxAuF$}Ci9rY4ST9m(6 ztkd{H)_Hke7KUtcU`<;}KG~r$JvlFR6_2z63Db5!y6Eem4z=Z?F8ByEEPg)8%+cf{ zBxM{wRQOWG!k-*aa$!kA$gE~`7f!xpJAowoX6MlGKRr2PPUu9--1HmDt-Pw0?LJ*T zQK%H~JBNW`et2_4$X6u&_+iOgi=lo9?gH@<%vHf1g~|!y{!-Smft~k*D%N5Ycyxw< zjUJKJNWqns^H;7Knj1AE$3Dy2AM@?iQQqh(+77o+6@M!&-5i3Y`UY<{O*p!i2DeAR zl{4Q_z5N7utP1>ETwurL(8WyHg`AIxgXAF`a))QHBe^`dV1g&`k+~P{o$Z_ffj6#e zt$Cz1C|?p2XPR<=>49mbcHY>D8ypzi`-^NEv2dFGlIt|Ks^ZlD&FL4&paMVv-)_^; zZIsMa--I8`H~LrVb3pp6Ag0#k)x(IvNG96|0*j$V#MV6+Qw4+vi{qNphc!0uZz{lZ4c41VpKGl(@C8v8`P=md?b089Ekp~)20e~Jh-5h+e*D79( zq*e^srI3`)LDes~4J%mn%a?L6ut)!IB>1Px(~T1vyY=uENmsYftMF(fdbYtD(p+#ic!!@bFQRSL1xwmziVzSFH&b`qIzm#>##*(QO#A zv#TwgTa*!`zZEoumhq)nejhy04E1;}o*ha(7iH(EKKaZpC0O@+a2)zqwc`sz&u$>g zWbJ>lOu*CHURFjqn2u*3qvhy#d{S@dA=|(D(LUjO0`C#9{LFfIc`~~YD`z#uRX{7) z#|IAKpj{Gw6;BtI*|p*S!JG<{MeScGxNR&)x7jYUX`aBVSpRDDBr7GeLp(u?-%bc5 zPv?8z2fsc5{U##Y`XeRY#PYSKtX83-5BLZFJw!)NOtPhvJhydET>F8U?v;}WonHPrAbbGF@@W;9DK8;2a!@@5WtSCp#f6R44LJS;xc?iSGHi6K26fH~AHtXYb?>gKE<^i9Etsk1>9z7v{a*6dzzKRC8y0;QT z$l21JA64IO0zx116@(C!9m^K=KFJ&<){spygOY74N-?_7lkl2F--8N;vY%{d8SwmZ z%@Cp||MkylbqClHgQ*Y#s5fcd%#q+0u;mHevUW`vmk%KvL-LqHmr30p&t(qM#T_1& zDd5^R>*nmuNOMXA5OCxClg<^(B%XS#Ro?R(f6$Eua#J-diK`AA{a%>7f$_?o;OiRx zE!0j@FGzTSq#8Ty2?CrXwdl&c7IZWO)jeGTL`5$h-Jm5SZqU%EQ}d>S=#x=ibB`{4 z1D?_jL#);{!qSn&S>)d6igM|$+fR0*fM7hzupq*y9brH~dJYI3hO7&Hf5U`G#KZg+ z9-I^?4A@cLz{#YW_+oHJqcQnv$vw(|W!Xz_D{)&deEodjQH><@;{~OEhOOff=+cA8 z8tQ@cfsq6KBaY_I$ZHd+idY&ck_Zh{=DQ^$RBDb^AiDBis&CUy4s_1hWK1to@UBm_ zzQ0XTU;Sney{dTYFQQhUEIzNE2>u2F;=+u*evm88RS34`?v9lMu~5i;@?lhfVwkxD zHYy+t8b%qTs89~>TD6LVr9M6P2_aFU!!#(yyrR9Pbg90V2aJt5?ls#M zN9Nc!Uzj(sS9CWXW>fducGW1W>>ODpd*8Ym7Lbf|t5(3K#C90g^rvjz#8>=&pDtJ_ z5vuQP_VHp$I3U((;*330t-5~UHBFf6#g|X7QK!(vT-$xA4>T;qo!WKr2hfAJkK?TP zWd}Q#@2DC3SmeMY8iyZs)B7^^F2fzij4=v{7Gf+tL4EvIX005Ww=YpiKovW!7%#9a z`J_1@PYz2^?#E4NTZ)s(azOM!rF`_rEWu&xBIP z(_?Fw8v-V1;z2UbUneVYqDe)5)A{Rm&347ZRSaw5&>XR$;y$EJ%+R0CHbTA1}Bb#o_Y~;|8Jma5BzWW^8|; zDoNHk3P2M6TDZ6WJ>Ic|rRMl!(Z`UIc(Xt65wm*SWOp}Vkl}y(jRV6@lSTFEo#<)P z{kD3I1C8nmy;sjrW3~OeJ_5||#70}XPYj>GR{a0L8yeWcrXjMO}gbZ6fz-i>afMotuqvW)e7l&vf?{rLo2e(gUI0q zT})h&Ox4cV!HMeeg$A##@g2U{w-8E=C_F?nQ}JYWZTj!8u7N4|v*I#m;;#+yk-@*W zmX~1xga$Y08k`aT@DO?x9_ji`*VyKJ@iSs>_ot~}Pcx{+E%+;%wl!f76+-}@!DTKH;8|E6UIeVo`4 zbE_}%G+=KLL5(!`wHnj<2f|KWqU$(10O%K++Aa;P&MgrT75QYJtqaJq`?V~Jz$plT zolOQE9+_64tK93SDb7q{`^{!+fm)1)&yE&t>#$TWr?V(2Y>I;KI3rNic=F+CEW z=-g45fGx~?S`&7W6fS<`f|!H7T{Z@044P^Q%wL;3m|;|qkTI{!7~4%roce}%_e|?P z!!)OrXA z_gSqbK9`~n%!g!KcW$g3%B7rRpF`~Jo1pJMP_$|NgtS!o(U-#QSFivEfS<9!+xrC# z!Y0@)cw4v<=hzXjT$M6<<%uq1_78rp*8=y$SH8q}VTa_)Z6!`xAYI1P#0Cj{dfC#I zIObP&KJq!QBFGcwML)A2hk%2>csYy5@jm8nV5}Wk2u@A!1(B5jbCrkR1eb{QW}#Tz zrdM+#DOi%oTX=IXYEH_gv_XoJ3bk`4vUL8xj@As2ifDKP1ma}+Yi{+VTl9B$jmub$ zpwulBbXOI9`ymT)lr!n#fGw=nK44hgygHq8o9-LhdJ;ZA{&Sqp|MRHUNFpZn7sZF4 zx!w8PK;!KT;Du{rCqrM%h~j#_F)=gU&ysH!SffmjpAmdTx9+HL#&gC?j4A*rJD~O{ z|84X&@qNp2r$5jx%v=fD{c&+KR0a6I1tY}bG*}MxMluHLI{hCLGw_^WP@e^#^+q{! zyEUVcghT7=>G0i-6$$$>LeDe3*lvS`C-wdv#{YQ3n^k9*f9wdDQ1RyWy_2`YjhGz~ zm1I($*EJ>g#C_2J#N_KR0et*zhhtB0jPft-H}#1nsZ{p%M$}5G#kuqBJ5Ho_)HuT!Far$(#nQ>2*u&a` z{qe~Xt_=E=wZ~K?uA4NAI*hbce^s|r{f;gYPAiCq6xyYCYZZxyRs|08Ge2s1u7Mq! z`ZxtE#UeBY0}I8;emb1w${f#P)uz(#eJ2|%Z=aVSSMQr_(}33yNsfXn%BXyhb?e~#p({s0GjRG_HvP6>G9>^64MK^wVGT>J~J#; zKY?Bh1-;Fc^*N&dleN&WTYPiU3DWFMTdeu=&@|`Nv9~*a#|$%~X7ft1ZfcX`G|TVd z74SBHk@}E`hscpQ+@*SKIB^1l+q#CgmG6h8+U{Ia>x4;Ab5cKOSWiLMASNYG9B1o& zhk-`CQHT--h&5xyCT;#&^jfnl8gmpxTwtM`dRaVqkCgATm}EA6P{?Ye|^LnN^sBN9{hGFC-4MAI~0iP z|54ZuKjA?tmRA$p~LZ1ro#xZ+lU4^5qTV*2Tu-a4M~eUoMNj z0lbs4(Q*~2HCwlpEq#~c`M16KSj>_k-eXtY0WmB6lF+||1SUlqlOze~K5uRPsWA5d z{Q@-X+8L;xZ0RJwnq-RnQVWBJM-79fo*mVj$^JUy%I|ONLK!_~ZbDf9V%3?AGw5u( zS1?sdOTtqqZwMo<>BLshSPD<$q93_rZJR4edu%NuyEn4~{7)kTDP3y;6j;-hOrs7V}1C#aIv{Pld? z^xLQ3$41_~lKP|j;Z|n6Y;=tk_5&Einlv@)6&mQb9k!QdhdL-dnfVe{lvAjosw2tv zB0-x{mzdmx*_$$ajK>IF{1U>g-s}5bH@+5iGdDaJf1P%G&0XI4jqec2{R_>nf2(Tk zeOx~r$FIJ@5yF6VlBpTugx&V5l={qWzJc7-w(foua#`No5e0AgA52%Y;XkrL?OR~T zBtC&^lhB;Cn5sZj;vA^9Q)N*C?T}|ZP5cxsrWgvq;IK`zTotrh%0Ql?IkB(9W-U}L z;==-_PG;|0!n1lz;LbnD{ioH@$ZS3=C&AzxG;+9i(i;#mjC7Td^bZn*06fv{<8>L_ zfVvvRl+kYTT(6$DqW%}fR(aALfmKN77pynT(mvn84=S(Kq_yS$BFi%_sjn*w7a!Zo z#3BQSyaS8DH_X+jC?GmJK$SXb> zdYp@!2!6TNVgIgGOALgcb1uL)yqM|0zzc*|SfqsCJZR^uIs|h>9Cmq>s~;I!9F|Ic z8WlcYfA!V}k2PO)*c$hsUkG&~NqptAlZ?rcXc&%RpiZ&Jn)I?e1y$G2@|?;H9SneJ zD)2ox62l%Z)yGNdzP{x2ldH3=5-P0AQd9M3PpCNp?5nLKd6dy@S8>NgS3ZB=SwH0Q zU2(T--HbO(k2(!9p0n?+1(~X1P956N#+Y{W0y>Z{;j;7QyP+Z`{VM%xF7Pr?I%^Bq z_{FgMO6&yFjzM5;lI7wc9;oD7iwo=uP8iBCceY2MBJTdXJWt$A47jcz669DN!c5lk z+`5lfUnTbKJfeKhT*h~8Wnt3($UmYVNL)l5;x<3&;KQmR$+g*;bCr|U zZE&V$0tJ2A@Kmy3>_CxF4Vr!LK*0pRqD8tn7EfwQaHbJ=@U_7WDaBi`6%YAC;Ay|O+fARjSm#mCoLSMsc4UORsaH;)Y~g~o9B~^ zbbsmyt2#%qMfrgrZoU=k1OJh5)T=EABz27a(iBR}f3%D=A9R3i3yh3;q8AZkx_SMK zhQZ=OnJOlQ9|p?!yS3h=TXp326x+AFHXucpOT)HH+-F`gKx^7`?4|;~S=e3Gp?!MMq1wd z#@+kUdmo&oh!6e>nFS161}*9Th>Nn$Fyq>+17`^T(DEaB4j_mAuBz4Rp zV0EX6OHme5n7fT=F~M6>go3b>2nc?_YZ^sQGzZMW18vJS()mx*s3OJ``~{vh7sJn{ z5@)cc$!R~5dKk=+gy@(73-!zA!R)epm)VD=5+x^%_)X|ue{%bm)f|^8knp)aC%mO0 z9n4jgs!eoGx{~=KA77BF^DR}w#k!yZ{EwZL$H4%Gr8D50EPaG<2AmDrUd}VMWE4NADGYE8C7WpK~@_by%-fu8_h$6j_fAA=ogOA7X_cN254IA6;#T^JR+U z39bkcU6@7)8N%drGBEtz?|*h-Uqhq+?Jn@F+2WiKymXv7EtC#t!WEH&K3*E~l_YP1 z<5nxF?Azy>*zvT6@uNu+x;g(H&aPc&)sm?J)4E&iZGne&3q**gT0v(Q)JRcN;FRvL z>+}<)U(SXcGmR$NVUmV|I!aBy4tm}UeLFuaeaTb`GW{^4iFBwE(Q~YAzU}&Jk8y(d zV>r<4XoHyQASzbjoBSh)Cu(Aq_&jgm?2+piA8F-=e;)x5(LbXA45BrrrpTFJ0)5d? zd6X~)M4}S^_h^bAfW`lPo4FA&vzK6Ys=g+VvDyH|djDHrU zj*ejW4mz?yC0_4XlfZNVV}h=ll0lcLg68+3nJs7Zm%W?ZsqUgzrg>VrPq*L54G8nU z9+zK+H}gXCTgdy}ti85 zkxuk>cj1z77cP$}#k@Y#YEV@0xmCs!f9Oe+o0C)t?`Wao1BNBZmwE?eJrp6>yACof zZL+jA=3Qa}ujX6Ci{J)lPf8k*p8k~^5IvwV}?3*t8-`0|n4@~X!pnQw*^y{1<^sFO__DtX83ec@KqxuXL9_CddjlV0c=`sPFIil6wKTb(f0oQ?*iuCQ3BRo zRj$r{`--POUjhwa(WOs1k)DF0Ha4>V_@|3uY|FDBr0<( z|L8f!XDmO+@ta94&p7?N*Mm0x>LGdn7X|+%V#-sO|$>ep_OP^;od`OiQUT z%$!&NDcW|bgz{6m)mNQFNQc?FqdRLLJfMHG2_%Exnh#k3@QjD_F@%%4QuI}n6+(P| z1OkKwx}&p+_Bxx~(|=7lM_fba|BYPw^bu70=-Z!iTAUn0GeWtpbbLZ`_S<{E+nxqC zH4x4d&5y^YPyj|OPyE|k5gJI`XlavGg?&Nk2^{^{8Z%N`SM1)rg91}PbijDkt`UcO-^ zmgJW+r`9ibgeS1RH_VwS7S2kJ8?|+hoQvg(1b=IwOKyu|BmDhFF3S26FD-hXuFAGM zXWe}ArE=xqkQ6@SJQKUg4~Rc?v3eAqt#mcSq!ubh$u_cWaRFMul*@~Zh9@3Dygh9& z*|)WS$$xX6BoS$Qq7t|GeM%f2xBw*3BiP#!#G`VL?`&?J16S{V?~Vg`qAG|cM5t9f zP!-wMq4W@~_1XTdW-jY~-Zv_-n_>WMHYmDihSQoLE#*R!6riZEaBO$4MPXqiBWsCF$3WSt$d;ZKV8p-ucHV0G2Qe!>i zv6sV9&@0_;OFs3zfZWlFhx!^%hf|;tD8IP1jJV#wDXm^tU0i2E$A1IcRD$;$K{z`+ z{XPT`xG?1w!z3Pm9QMR-Hs{Z5wQr|>%HU(E9(C9N@&!w-!r@@-lxL{w~Fvp zIHUS^r;lmrIGy-DrEVR=Dy>{o4z%q*rAfY<6^IBF#&o6HE=*iW<7AM|i{pwc_lX}o z^tK~SBTYEjiQrAG8mQunE5d&#Ayj-cZhdi7HZ-D&UG(Rp+i)%zT|nEUKtc#E)qDCf zN zM^_qnch@wTcHBNsldROISg`~@Ek?{O@g!&2`%tJZp!=J7&$q(_2Fg4iX_-f|U~rLV z7B!}U6hX(2ECb#EXTAi;F)`wGOGd3YaUu#m#d8`;k7rFI5S)AhYUIU5J_(Ub+`Vm3 z&4X=Ed==N6by`a%)$nKgiGf3AiXXI%ucm+CWQg$2%`!;1T`+p-D$G=RI0n`HT&t(G@CKyWGmj+u zr^=@aKH~ATOJ#_c#!)>kv4z+8h7#a0GJL*z>+PmGUH?_KfVBBrfnS4gB40-QV`jQP zt@F?--pSX&jzA{v-dx48lYQ`LJmS9T7XfB02>VA$HkQs6(O zVYO(_(7!O%s<-5fIMbgs`Q{rfd+!lEkTS+!r+B3ie;hqH)(91*3CzYzseH_J@Ae~# z3s%1ilx&&aW(Z+!JELFX_?P}ik~T&0(Gcg!x|*tUw)X84o#q|S8|$*9_!g6j3R;sj%SIOYz-9iv?iwke#1nkVL7?~RBP^v%?b%^J@Bp zG(;yg1k9|=J8D+^EAFA%Y&q!5%k8f1H_9oZkd#O1De>xQ7h@NOt|lw2KZO0l&&)UY z1`C+tqS`U{AP%1NkFhQAq{{;4M|y2R((MV}66V{_zl#UrE^Yho&7ICU7SIUv*v;B) zS@3<#LnG7_y-BQD_d5WKd-gu=(QsZwrx(_1-70G8cshqFr4;y-kF&{d!Gby zKn`?Im0p~Yp8;>8jRs~gU@@$2bWjKq>RA#SnT$T>Py(2}0s^s(qB!IiY8&ITyt z6LTUhHO-b_SL*uxR4?`+fmB6D&yyibV;7K)Cp&TDk=KKfe4~d{dQ*(20f%yK){^AS z@Sp>P+9PFsQG;$Me{?K1v#~UTJ47K7-k9*y<7Vu_oU2 z*W43bvC7|)^}NR`1QnyeGC@pN_ys@Pc%p{%h4=(dtrZSgLQ<4ph4ioiu8oEX5&01x zRU0h%q_DIw#+>LG=szV)XHR~Q#Xk=0NkK-?b6b>DEFLI%o_UtrC~X8igxPQW=AY$S z+@+C7zXi+8g_z}CPRraHyDJ-o?~0p|vlb$gt8Vd7gc*edx{=uftvwp{rfT%xzI5Lj zC2cm<67BCWX>?kV&l%rvMjh5J{gsyC7zST^r}11ma9MkKCCPKitD_m0ji!zZ3S%q{c8hIm03|(;E2pMC5wad0h z`IOUtJzPE*c(SY9woVtuTUG-Sp<5Q0DG%%1Nd_LS%_Kfx?QGG@DPkkJ1N|vHPRc+Me?{I&%IgzAkaswQ7V8H}pp6pxEP@;@54Sk5FkJ6x>|!GM4DwL&gzCYx z!@~8x9MoAX?zM!D#IhT(s5OXUK<$r^$%vBTe!F6L@B)=OkhF0#wEp6SK*m|Ilkdc=Mc`YTcyJbJ}Bud_t^DTykrD8&D*~=i_X=g@>>Drsuxjsf<)z5^@8?? z!>xVqLu+=)wLLx|H-ma79q)AaT&%0S*YvwhDjtVyFO930c)=O{!Vl@tJ;HY3_qd$n z01#Jp+J{0Y7Gr zQnUTHZwE2uthZR}#**4ez^WDp4o z??BycLt@UXwqsI!P7_wK8zi`P49<%5Y9?p?2DQT{WAO?eXuPWFxy-S_x{#7bP4$7-RLsiFCuM{w*(q{Hq|Bp4QRC21>I;B z#%(ydF;~hIn+{k7DY_g&z8>9m#-S@CE zQ_&=AbzB5l7rClHXmo*!F|H0#wORuim+E*=ryEJj`lH9o2RxDIWmUDqG~mG~xXCzG z2Z13TfBc5!@hs2YfclE|d^CJs{WL>ftlbN>8559!N^C8flH!o3j%>TZqBwmrLv!mZ*5JM-B z{JQX0E&Q4&ZB!XyJ;qaN-1QEBoXf_$RF;M8APmQZ?`NruA?UKnZYHQAFScQbbVeFy z1-A2fp?;u~jEu8t>`Nc{@K>mpC}q>VR=e@H0jCki{Vflz$z+h&tsR*HR=q-Troi|L zQ{XV^p?}~Q#`ABSLw!)7i@BXfH7w5pRymniS;ZZZ&ox%W<7~>stWuzu4mQJ-J{a;} zl^uGMGOD0_1=_tY(?6D!Q$`(-Cw#TkxZ!H~`4%WY2YtynUrvmuzW*p;p>8ON_ILVv z{d9y!`v<QR`Tk#06bSxZ97nU!Ds*qztVmsQYWmQHiscL z4M`aRg<;&U)iaem2DGs;hKU0&S;}V&cL8Mo z@vX^LTZQyw-{se3CLx8pKBIXn-*D+_>n=?Lo&il;WP_V#Ea(%qv@^Q#BiqsEnQZx> ze~n)a62~mOs}sgL1KJMW5pJC)s`rZv=5X=ngFY01(XW$r0taEWVDx9Hu&c8JW=Q8^ z9~LrrcevHt!fl;R*95*^KNa2$lo11>@yEP9T=EbnZ1N1bl+Dt8fIwm5N+xdjUSrc6 zgAv_6ExmtA^ur0hDc-W%$#YX9A+vPR!-^6`^x*NH+y)+&~S6<1Q~g|l^1XGZ}JXG8MSHugp?Z^o^Wr^lfr{W*etCvXvNhu$k025T2Qi%E5f?yA z`(hsuHBHFL7q?UPoZbUfVx=4hT7rG1pRMvQ2r5q>JOhm+Zq?q^N!SJ?GAsu(0HeN0 z99=e-g$w}BlJC&Cy4{bdLqFV2v8&L9M#UVE6e^tTCrna$3_D;M8htd5?2u{>{U2%m z#m%d5TN!OAyiQYKtGE}!BH-f8oU8F&c<#;2u2tsSjaSH=Ej!~YwkwT~G|)Al=WD^C z2Bkq159qIjwL~3m*6H?xN8Yg*ASOvoJ2jckOTg(I{BtFv4FLGTu5wPBBsAl2@c`gs zWggkswa#}}*ekNbM2JYj1Wgadj&0AjTD6uuPdG(M@0Kagj zz?)%i25OA>5)khBC#sz-7yKmLo>vOCZ*1Gg_Gai`6n}Wph0DN4y*+{&Ima%aOf9k@ zA+g`xSO;3D{gW2iZ#!U8Nt`huOEC>l!EcGfiO*I!VB+Wbi=vgK;J5hA&&+chI~OxF z60s27f9l&yx&;gtyxEHHj_qte7c?KKidzIgcCZ!f8o);As`Xj_TC;LoXDFo*_R8Hx zsQ&NahK)fBBq3Df41bmywHX})#k@(=H={w4k{VjldfQ+|J#U@c)Gl_L+wbN3mQzVnzH*;Hu_ zx;RsqIBt>QO+crv`n~pBt=HZkq7QBT4!XqAUq6rNiIbQD6?HNPK1p53k?G1}H*Tyb z$x9x5avCh3`~Fb*T}rLcOOhe~!AHql8E{+D7IKpjc^|Ets$07o#q5C_{{H}PKn2{J znIp&xSzXr(C^n^yiK3;hx-N$e`VV@gjWn!R64_ zz5n5EGv2%Z1zIoKt5wJ)&?*@Etujeig&=lYiGj8#eBMDG2pQIua^Zm#`D}AZIq2?N zndmw)&C3sqZX<(BJ=#kUlCva?+tVPHPaU+}L43o=vw8G9Vvmp6IuA1!kqDSvIvadz z@i^~IhJOS*ORn}zj`j*ei}epatl`RFSu-hZQXbF8<7nFzUbelAwOP*X!%Qj@xv37UG4Y6Y@tHE=?Cjy#&%8nJUN&pVs<97u@^S!2=+Kw1M!*MSn zVam@qd1ECA#0X zUo}(i53qUJ=QbC|wj~I!32nBv7{7n+7*l_xDocwsW;{Rplb-UjWtoo=C9b3GO(xE< zrB=J}N=~)gHI4EQ)}1n5hQ(`}r8iHvK&Ub*AXa&%$Ngcic>$PzvL@nn@EtYu%Xv#B zOg^jY=l!p%#lN}pV7GS5Pp>{M+=Ji^&3AvbH3EXtHzql zd)~{*t_bzq%qr?{U1HsBJ8T|!HBln3l`CZ@7;KR?CV*W5?|C0#!9LYUk9Yy>BY9gk znl|VIP2w(%ENl-V5bQruetT;;@R^gNKmcpM>8^G_nsy>7O8#3;K9}Kl6b)k(m|LS$ z@lyK3$W$=?5EX;83Q0#|e|_<2!b3m7yD)q7=QBSWwbTnz^RtUj%t^8>?-Er7SZQYT zaFc2DaTp)(Ipa}89zX2=rBBh-qyZ;kknZ#OL%@UIu#cBBsP5%a81`9-+475||4D!^ z*@zv9oCtqj3)N=AQmL-&;m+~Bo^Vdf86J?_d>2!d-!Gv#c=xB@l~mg43k~^L2Vx-a zRGGzVmrN&BKKo_o#OaF|wqcKVS~KPS;Zy-2Ng9`kOSZGr!WdnO4|=l_%uuLsEUBUk zz?glz&)poZXh_a(!5x`^?Jn+m3${oG+ItX2n<)7Y53Fo9`D1BswEzn?g=?RzFF@b5 zrd*bT>oLv~5zg2rkX!*PWpI(tFg&YnYZyym^ein=;`?tqo&RPhFG22q zEQW-5&TXi6`P%qrD{w+Hg}vepnsy#l*@pN1yO3}T4uLMIZAr`Qi>uPu|I=jj@3o^m%9 z9%D+aIxGdG*Y50>DCjuDnYW$TGc0+;Z?zE8eqOvV~JMdXF*`}O1 z#g%@~PD=5Iz`w3(RuP1Nrh3r1WiuJ~yU}Wm89B~(a|sjW;RzNDCUyr}_E@xLO@y~O zU==1ndO~RYbO~5fNK^XXE7Z5^)`BqQ2qOM|0R3pxs+$Gkr_9IPG%p{~&Fs*Oal-gx zIMIBkS9a}H1$W|y{M-D}fUMT#u$&qu@GRrCl$I_jYp_DcR&jKf7ypmX4~BGe9mDcEjWGvK z=Mut>gfyO6w`97ad&R1+s+yeCSuz2TI@8G7p}h{S)qu(EA+Y~S`0HHz-+ zecjI(Kq2(cZy4IJktBcAG;EjBx9?#2XlnUf@R#@m&se50L;!6e?NqL^Qsnu zvWUaLk(iPkj1fMMLwe8WMM3PUob7=Lnw^=il5k{ECT#i>w6#f)$mYjw!k4;d_A8u- zI4?%;r<$jplKszj89XO_nxDk}8*z-oj%E&tMEIa$as|!$pjN{U=LeE%OYj;B5PyBg z^FIhTbR^4)2QBrA@qMEFrEUOBT6C;PI+3C@(VXP+adWWyA`Y0CKJo-+; zf*_+alq#^U#1RgoHT)c~gp21!F3G2jpC|w6!vmbX9kg`I)p$3o`^%5A*ZQjPCUAJC z!R|W#r5T7QWKp2e50=h>WlpE=84DsRP9T0mIe{`ufC)6uTVO0}aL-kjgqeDlCet z%3NResMj*+H%2~~cZ!L(Q;ctH5)c1B6^KI}tD1Hf>32#Rjt{hA6ahquIQJ&$Z75!? zz=?*)lFA)T`|M?@6B?)LO;@{=U{=mm`FkYkv`m)yNAO%v{q8 zRc)n96XrB4?kO^tvpLJTm4@yCr~?1N9S&Xj28KtD^Q!>{o9TEi}P4HS*P16VU zy_V%uPu??}OD}8IU9|8S_@9o5pH=7v=>4^+rTmuQhCIsu90QgAbOf=W+lt5L$5V|FX0%QwZP~BSZx#-;LF zG7N617j37s9EP`1!2_=Q&!pyiJel@29XrFvj-%>kw`WpMrcs*Y6n$9uggIs=QYZ5v zFNpvh%_kZXcc>qv&CO3JUT&LaUCHwENnMh5NM`Q0k%m8$C0xxLW9);RwetZ5$<#O{ z?FO>@Tx<1|F5=2rfQlG>f8FQMXH#E?5oLSmh57%(z`IkP99$k+MlirS}65tU8X@H+32QI8s+fxEv6SVRLgg!LI z$9g^95w=*zE7~F{p7++~EJEk1{TwIU%WpofN5fLC-dc(a{*hoXC8<*K4hR_7gimy2 z+Zj+gL|ZYp=E`dgOq(&ocRbfQsgLzy+sh`*f44dzuXm^wcr5G$?W&}Q%hp2Sg%-JD z%(1kx5ou+^3k-+wrrA`BX)(Wkf4cmr#;$_9>?WV>QvN{1NGJQDG+s(Zzt)5+q>*tr zb`swY*OK7TH*Q;!0@5<49ABikmL zk#!ELNVg4aa!hGh_12MF$zWwJ3igpxUEq2|Ws)Z>J?gssiuL?`(u+W7vh8lnGwi60 zt?lIY&je~YPQl;-ey5pOb&~>4gcWV$r06C zS*c&+$$c;X!$SNR{7uphLts&oPd*7(rhEyv@i%twlrK6Z(CdHs#z1K7HltgS-P~xp zrlWTrSSlUM8O_16?xK(1=yBxgUkgucumL~NVl_&hAMQHtDBl0H+sbd1to`C(V|R+A zAH8Dy))vC%aVAo)4*5QMEFT!nSDL3=yGtrBDI3e4K)1rppPW^*wYHPROyR0~m+Oet z%s#*{VWu-DVmRc%umO*GM2Gd`GY-i6oDZrsuFb{cvy_A#sk>v%l9lj zBarN`0s1Q$`%=(bOUwK`cs5w@SU_d@^|P|3(C!zL17lssVjf0e(hMd@z_8ar*N+FR z&Ns&Rppnu`E5+Ab)YK*;Z`wXAA{eUz)VQDh z_@HP-Z|M4XHA8{(nS}~G-Z|$Y>P7wS1c5&WSDzsIPH}dmzKHIlpUAVr6y-@^Rhk3q z(O(qf0gw+@5fbn_WGmU4tc94m&lAO$a7rEX2Xx6UfqrKi%IAUFVTyES>j(begCjvO z5FXoMIg_}q^&OPLDMjT|a-fe5%Pm)v^$1x=^+bz;vBO}^aN!p|W0iOl`mOklti$)6 z!r&`4k(#zxeuJhbB@FrVq_3iO0i(%3Z_h{*ek1$E^FtjlbjIsIDnrr5*|GIH?JK}Q zX(~_}D;Cj>L!4}!I`?TQ_0y}$6JG!U_K~t_-JWxr-hYjYRV?I2Fmu;)NC=z*BxNOo zSsR+GDX&M7GMgY@=?4^NmNJ$-%S>$S>bko=LzkSQ)8wMGzzQTZRyhT%IQKFHLn|~m z)fkjW-j7%!)qEv7NsI6XsA(wjv|8HT;v{3ieC6uHejn$dA+X8XU5OQsC9i{r@Z${1 z6}~8(VK0p|FTCN8;Nc62;dzPCYo8t8snoA6o@#45e`*q7BXM+CF zan$I4U@yZRB{>xsCk^o@lP>y1TIM2Nu)tn$ng zMSDxiL|__S5dI@HB>Wv^={oP8`v|&!mqc;TzqyX?DE&&E%?4An6zQAf@*LnOawc{r zb}Di<*P?s@+Vh+_e2*a)9=NBO|GD9Zh56SlYV5LE7pxZdt~*aGa-VDPxK;-3BL$%Q zf$02O0#_0#{u$>1{sPmIRep0PI)6dpT5WodkVv_|TEzT>QnHHXnoipdO4V>%^YKK} zOVh>?auDHGl2Q=I*iOpZu}t&Y@*!^|ciA=BTSz>U(JGqyMloaV{MmQ&Z|`%}Qz+C+ zsvW{iFlu91$^dBX`qXE5Y<+HL>j&<)O+axZo5(iy2yqik81x7WevhQ(wWd8fC$n=L zSeJmGJ_?-qNtHPE9B`(>aLBuE$B8HPapgmD0S9$IALJsZY`r|_yGFK;d!`8T@3n$( zgbCR8-OmOR{O6zj*|g|RABs5A!s)HZrm`sd=ycLrw?jQA1Z1wTy|`FkMdYrxqf;tY zvlMmnaejVTEQUMwQtQ83_uuL(Y)5)eY%s~Uu5qBd6sJwug#XaJ(7pYhM5LUGWm#Xg z1X>TG5V&(k=KR3^OKc#kTe$AR$NB#!t3Kc_q_*w+`v0WX`2Og=?;xN&2)CH- z1si`8JC)vU5el@2^YRG@+`(R!7mW4OW(E6wKgp)f252lt{KP*{1`sh6lT8nBhLn%m zJ&VNQ!b{($_(-p#FY#oG+8(Jg0yragvoY}9sZnFwoG^=k8--zU3)oSC$vM z{8C9gtD+C}qrWLvt1_o#DMI@q>f-E=mWrV8ew*Qpmx*HiOFx$TRqB3wdnSl83pa@Ss2!WJ>0VS#8 zb0F(miqKBCf*gdl3mH8>!XNG`i0P?`Z^ar!Eb3w}@;cH{_bU>aUB|t+z!cB0jd7bC zRxrT|f=kaXPECK9=x-pCEi}0W)uZW^=UE1pw0jml8s@j5-%-qt71_P{7c2Y3z?_fE zWY|75l4^Cftm|Je>TyPuSA|?ryiJ0O`9C$4@%9Q7<1S3EO!#krcr6okoAm!7!Oe6i zLOx8H2ozRAa> zsE3<(HMp>Q1(kaaTt1`-zR$TWJbJTqqsfp`|6nGueap0wm08Q`AO?VVEa6q*N*)1* z>#ij9*Te&Z{(Tj4zV5Y!+c*VjtP-GTBBGs`_H90izh>y>kNZ69G34XIVMQ9XXne+g zR;Ej>knNHRghOrm10z>}Qjw~@tkbA17stcmAWIv@5!vDW}t{f1`iqjpUa{9!L zCWGVwF1;VP(%!7#MHhXBagDf`#X~uBRU#zqshZvOH*mV4O6n3G{l3}7ewkf3@nycN zVQW@;@R_v$dr(i23z%eD7J0tZ>{ZTf#C+N#5z36z?YpqNr9AT79WhWTwUST_$DO@#@Ay}djc$Fk%f3I1VNPk{*K7yAylllqec-0Dfe84NtJbmVr zjr?`_-Pp9Y(t%pik^eSn?Z@t%Jwab@DpxJq&F&a&*4J>APYGC1E8Z=PGVIp}*8!~P zuLJ=3kD!Q-)dS_65aVue*Nr_gQTiVZx`VKjh2T@1XYAK`LNPPQm-Fl}(2T%h)JVD; z`m1X!-OxRCr+JN4`i9 z9s2q9nA(5FdW6DcQUv}YUu$)P=0g0pDe#S0x^vziCS#u;@W$-9p_3TZfs!Gy8sE!C zOsG)6p-SGa^^y)T^hP+g(OGs_B3?wx6^6(@OYneWwaiJ)#^OdHg~ioADN7IgpHTRn_BJ+q&MgX)%Wv?+BN@a_fCW-swHzEY=&(k;p$6=NAQw=d@-K z!xRgE1pCG3D2VUyJ5j5tp2Q+hZM8FVo(&TMoi=#Z0NWp3Yje{Wg!|B|XLIKx6UOu# zQ2j8k#p-4t+zr5{^}vz2BZ)Wf0`!Bz9y@f~zD>-hF7+|^!uTV2SC?>z!YtstvKNu? zkpAP{kO@f)8MoUt-P*0T+U|=}E=r3f zAYwDxX1<3^x#klL-0!MR@0JGqO4$bh&g|A5b9*#mv_jpd8Z^kqS;IpP!IDNxGi|@l zJtgLV0L6|YR((C-5LrSR>Of%kBR>lJqEq_-%yUP3qOjMp%7WOZI9;9+@|B+`*QS`{eMiobySmo{Qj??U=SA4Afh0xgfzn-loF7R z5dzZPF%S_I8G?jT6X|Y{99^TkM-31dF&MDCe|vvE=W~AFzxK~|&bITq@B8(7Uf1=w zdRvu+7t7g34(F~wI2d~sKnEyGwIZYDgy?&FZ2=y^*NBaOd{^-h|AHs-y z&0~qEtwmD0DnL!mtQA|pu>LwX(zhmC>cmee@Dsq}Q~a=0@@-vQwvbt9V7|E-tz5!C zo$#t_3e))YZLg=lUp>$J=ai*DxgEOxx9ywvd6E75V)VV$QSN}BJ2f6&f|o%T?^6PE zTGuUUWc6sS;MAkVE{YC(w5>5xax}OLeYJHieSUj`CErp6>>e@b70(sY6?3vGd>Jqn z*u{jr3D+bxltg4QOk0LcFA8vpxHk#7(u4bzyHF%G@uTD_W zjo3q&KOD&!BWocd3fpA&JNsKIDHdgeL3?AES!zbQA`;vlm88KM;f^l=C##~41Q-+Z zxk4xTiJ?qjK$ll7IX+vpF)nkx^0%0S>MCkF#>Wy zyX}6XXtmeq{!ft?pAFqs{2 zuWY}p8|p{icyD#kjLBTj9DMBNZde)nmf}cNjwkFj@4nXHc|7y#hK&$5Ci%4L zwOh$%BhMb3-~L(rQtQdc-Hn2nV++vqL#21a3i5UWGdPi!3FB#ntEpM#jw)lDFL?7P zJi6T2PghsgMiWs37GVp1swD+2D6XS7Q7?DoFjOBo5ULsS?!0inj9R;o?C!*B=$<6+ z8`{k3kRL@?!g8{``oAZ6TzM{3(!7;$;Fb6>yy}=2(vj+%h^|}8TLjs?#&xqg7=&gv zkx3{@Q{BY^D01}c!52%fe|D%mcDJ2L_xID_Ypvf-!8cF`%@bw=ND7qqmv?z&tX15k_MfDzRmnRh?)mbi8>1$ zxTDV1zOXB&bDE86q`7NlxCuMEe-BeSpjRu9yH5?Ui#IG;XTaA$I>52b0;DsNQKT5% z>>YTR$Bs~e0mAI;sULz;;DH3Q(EJPpO?24r!RD<@Oj8`t8IGD*ZC*IyZ-3dvW)SsV z-%akQ3zIUTqcT3a0|{+e(gY08kf#gqSAPSil4cT%%Ue$=7a2m&DOGIvd@E`e!ujn? z#=PlYURr1txd(F_2;`pFei`tw2LA1yMB9TgU{12UWQ%c!BL(Xo1>uO;`ILN7KWrXc zdUzQGc|abugfx+*37`(9c0mGYsjvg>0ug^fv7RiR=eui_6)gSv###N%aMH;w$sff1 z{PRW6ACKio5;#Adrjg@E!4E4TZ%osW7P$l>(!~rz2zr+go*#a?S&v=uTN(zhAzG6mrSfm+Ue5 z6k)^ax>Gnwe07~}`9CG68U0DU|E>bvPnLiS52+N))bgHvDb@@|%2iS`Hx%iFzj2Y~ zfNI&(Kzdrr<)i0>U)^)zY)^I%-!G$}VGn&@VZOL8B?#dT6;9h2N1t90gx$jN(~2OB zsSyrAE>m>M4sI3(8n5qfO&I^{40<2HXNr(!-hDwBy&2>0T*Se+X|7W~9=cP=PqFqF zn(E45HREPaS8RxIQfKs~dRG$uS#wa6JS0NPNep|am3~D{KX?7BSx+{#wRlXFmVq8l z(`VCs@E)UTSlKixa+m5MLsWYqW3t3B(T-wPdi#6)TMhO=3@CM9Dx_nN zkZ$y_NHz^B6c5@c@anfqNuji=6{4bF^O|8fUt=K+XuxX#7)Sw1_aJS-h$v?Bx66dg z$j~pI@tNhMGdS6WbLsRfi!tms5s?q#*(y0umgYusKM+On=ck)0kt=oRi%1Z>1WcHl zNO-X%e5AqZ(?eJ8>Nj0tVmGsNDllW=PZBC4ae8*CT?cevs%jz}@#1U@Bc8yMdS^&R zQM6h|Y4fBVny!ubHv%?LWw5DMN7SqW1w9VV~w&-8KbtCE@nr z5#~U*5xB+WED6#(YxAz*@H$LH-~F$3>j4nacMu0DM*r2HJG>#|bkLHYPB@B-U=lq% zJ^>{d5#X!FB8^k<5H=wvwW8U-t*zoz!wc-%pR!MD^rNJmB=&|ks{!<`Sqr7mA{DjY zbRYm}BnK4_Tr+X*cb}K|$dE*KC!p&@oR!at$ye0SQZsn2th%ax;yUoA98fL?B52`^ zd91p8T)`aWYcW9p)*a9a4!ld$polok6akG?$*r9Bny5h>A&;DWP=40Q68BCrC(D!K z6G=NSrdg#KKfnpObiUJ*T4$}}c;FOZF0tiL6uGR|I`p^?2j2stEh)yWIs8wGww=Z) zfO^-hHMMNy%T%h*1}LB3n0)LJVb)u^H6Sy+>N^wCxJl@!yynu&A$QywBpSWR1q$$#`3yVewV#WS7@PujAeau*aBI2L^- zSp2ynKA9-Y;|5MH2+Ijth(a%>Z)(Uf{%s0>e}ngOVALKNLw#QW^lo+z;bjpmN{iLI zwfGm@(qOos5GV5G-**+7-}>0!J{pe!L;lSAVh=4lvXjt8X>DTK(%lD}&h zl(?)@<=RiDalaQeWW7VX8UP^xG^s(d_ZqhLQIwu$0ZRLrzd@9%#C&dQVd~3z(vQQ2 zc_b~!=RIx)Lb`NqM5LG7zp50mtJCUK?~S<3Y;GQgE%p}n4nGABqZx&Q(bW#J`_cT# z$6-LUmwcUqWK@j@l{n z0^Jx;$)xujtcI@q5;6YIc#4!t02eVkbh|ICK(30gP9Zmn89jxp=b^?m#Kg{!K)A`c z-xpBDWtVSoH=ufmxz#+`JeWeH?q*IBiX#sn&x2>8Q(n<~Q;H`neh*{8p=u=sQQ4KdijzXnhoW&(rP%V=V+43eD?^~1!P$QZUhL=3~&K@Ho8o*$qe6k z=nVXC1gp~9^Nn;Q-xFgZKXcNQ>vgwt_HB}mx+X4i7xKS`JH%F82>Jdkd&*St;5ZI+ z9y|ZA$JM0gn@J;1RfMx3&=hoWLdH#;Gr*9IOSWFel0igEYqZ^Ns6JjLx@_}=hr8HA)_>$i zGpiBuSt&ctLeX4)CPLEkH;NbgyWc$DFbrZ@2C~yleK85o+~oOxqrz0?RI1dE3&E7A zu(h&N9F4?yZNv(d@$H{%&5{1$9FDxp1Jbne!T%ofF-E35yCpy+f6QN7TlwvGmheQa z8AF-`~IMuI7pUP_|>7QIfzMg$;d5sZwEgppk`2-6qWrz@`oRd)|vtf>#2_aN^B zMy3SN8aHs!jN8`vy_@T|(k#|ZTENl~3)xhZJja(;q3i6{8N+f01J}M_7UDQ9GrRob zr}!xPxqJClTk1~ul>c&}i^pT5$z_WBl~K)Cw-pR5r2kOt@V0=+|Cwq3I?uhJ%ie5$ zD@q=J_1sd1?AqL~YWE|S5}Kzh-2<_YrXezkM@8Cq##fQ0h?sj|;lpEdEG&hHMmEzM zW_0QXV-4EKpf8s+ON3&J1p^eM;is^akmLN*?^4{S{Is7UrXjyEvdin8K$k7#BgNru zEf@poM@eID0sp(6DdDux#Ea*-JwO+D_LuF4JxCSMPv-v*Lt;5j&Uf$l>M`kNz*&2} z@JBZ9kBH-D;&px@nA(*g^DjkPBd_hmH=ckQG(lX`(pt6zh&M%X3|fjc)$$1o8jpJH-NO25Oa`DfZQZ_qJ!PhOQwkNY#wDPm zBwKufEv3oo9LVYa@y6cGBPpZy#JRZT5^B=Wzf{sMKv3ZUQ{wCIN8)m;B;}m z^=EOPy(0EbK^qLNSCIs|W%fd}dT^ZO(zm3gWR<`$Z+{BQY##p?LQ=C~Ur(xd?pK(b zoD4l%4(6_dhG{NZXgEzyLzv~9jEtWNIh99e*TIQoEVr!9%he*w`QY)Kg@N?B>cP1A zND(w2m#ba+Ct_efcLB+ftNt150F~K8upf@n>FYY@g^*50rGUA_SO?o*BmTXIOZc|% zX)8QS2D zV5)6W^j1NKOvB8$DX)WS+#=59^lBpIOk#H<>4qa0fEY)B^2xvHVfg-AN8Msv2CZi# zAb9!oECho*?~zWl@SCe_-uEpRi4m?+Z$OFtI4tdeC$d}5#E3S&t$HgUsTW4@jIMV9 zTSiZ{&`GbDru&l*5P@4{vdJ#6iLMtgb2(BieGXF$)}C|P9YSCbqWSI>Ud~820>8FP z)6(?!YS%!t^USnV%SuNK;uU{}O(zv?gWkTy-@M5$m=Pty%;)-3r1OCy!l}4SFEe1R z9|Hrh)U{_3H9%?o@*kI0oIQ$C`6)svfGrN$;EjIP+ki>~j$n4h)gFi6-JVl)rd3Ouh{XiLF{*#k}Jl!`xoA|(Zil={1~6fxi5 z90czuC;7)e$`sH8{ITQT#neR7YrQ7_aE@rS&f)QKsu0Dnh9kRwxyhGOR#EM=++9Tn z1Yd4?Ri(GPl^|4{GSIqrZYRlSl`eEF>lU!F$~wB>J3ig(Tmr2-prrG*HxUL1;cCsm z3;0EUBx&3E@aomn8Zb#Mjw|;`zfFwD*VZ70dBTsVzgU@cQk*8lxMwZ2b^A|Da2p28WZ>tP~z|1$|?!E~uNiudSQH4-2_= zM)XOASAuqR(#kF@%>jzvT8}Ib!(2AA`+l9B_fq{LbP!;XWALh*?^RquI5(-1E98-E zl5aWatKQ>n8a}Xsv5CZPA!MLjRqKDW@A9?pZ66-%^Vn|3f@nU#zd-gSZUIXR-RFo> zg(Fz@7Y;}p%@^Pcz((6zMF6>mHeceXjS)dH)fVVCg+4igUQ~QE-Y`Dxz2G5E#nNd# zpu?&dJ90B9w;0XiAePO-`ceOFdQs%$$nOBrj?j1cBHo-I+|!TyB*}3Nz9ZSQH-&ni zpk6R;-mW&=5=BKXclk``qJ))vFMH(WBQi|bPPfxC~Jf{05V$4!W74nz4c|~1WCHlcL^snD`9uMEEDso!O zAs(-Imi9D?_r)OH(f1y%iEim#%puhq8*l?#1Zb6AiBK z5A-Hddw!HpYB&F8-yP9b$5E&k=~V&OqrDDw22;g(oJIMVb}0^J(VnlS6b(+oL=ll3 z`>2%WSLH39FU|j0*dzLf&h1UasNeklD|Z8<7l?WBG0E0Sh1H|Aon|*hy4m`N7b?l9 zGonhIk7LzG1&@U^Rw_L9eZ86^7*p9d0tsW|vwXt(wPMi=kt_`lG}HVn`drmU$2zX# zOVBb7()Lk^ub0pdtJu6GWAaqwy2!`+njEO>@LDCda^Uer>ll%zLc3NA>e@AWwt3^S z-jougB>DZn`dc~mAy0(o@c|EALcf)&xl??CuyV5d>lPbaV?{9t6T>Uy?Sn3ts3BgZs*((>$_Z&b5 z3>|eTWkEI4(`|mHfw{Fe(#%v>f`5XE9S#Eo#sEgly~R8|14jxZb~^|2YlO_s!O&%M z>8;_+S#NJ*sb}aABk`HbaOZ$eNThGeCVPoDLOdXAZLd1aG8t?gI5t5RR%;?{&cKDD zIq*E`%rYJ`?Y{E4!ZnA_z4i{8&MwZ={H&2jpCpSPMI(RwEiNbP&)LR>ZoJEC6`Lfh zg_1N-EeA_x!zApCS^YOQMN_Zxzr;AZok)Fz8S+}MWhoG^M)t<_zXH7=SBQtrJpwA%z#4Y~2a+5u(J52a);XgmpUSdib#Op=Tn_Eyot9Ixx^Ee1@M%QAyzvEA+SYc=pS4Cc|!tw9G#> zOBTf|v^oY)(93We8qV)%*<~{lEm$iLJBoD7V`Sq%x@9;dAm9)7xj63J`MdJwbl{n# z-H?B46CL-zgS7%W^`G*`nZbw#HDaXBEMItZ8Zg0+IFEg5IQJgVSw|(YQ+n0Ke?jdr zvgXyul?K=m%s zOiiIHkST+i0W;C?`i zXO(|WWl$0hmZ#Fx9v8T0u-)Ej8{XZO_ zIal>0rwD%K{uKvviSW8>Mr`-Qk!99%Piw4huiRc?-nw*8{+-Ey70ZgfWjwbsYg^xm z+ad*tjv$6uV((wSn=p96$PM(Zesd z{3^0pve#};FMKfni zqif4lvKj0;WJ%uoy1$2;%ZmK5af3v1(= z4W%t1ysexB5UK>+aZ%IJrjxheaEtKgTFK05R3CQ)^VKk!BZ6HtXB{)b7K*dREpD~C zv``Ynj#reM>q&|WSYU%+$xRrolp zjRjh!rp=P2_*d{YOpEy4miWEegtN&BZNc4gz_dWVBpp*d;Q`mCO#W)4jV507b~4B} z6K~0lUm)xg_A#;Us(k0U|0W**E0n>0>)}+@m3005jBBLT?RS*@eJXx5(yrY9;LB9t z`Xre_evjAVG0@l!qa2c`mjN!BS}^UG%fnOkN!_ajcqWe8=X0W?;^b{oCt(df9MCxgA(X`%fJl$q8d8*4VY%usi#u{G27ky6zl)#h)6)n)1X(J zwTp%VEHO>zpf`kQg{R-z*N|()WrlTx9iw)emkk>lu+E|x5Ak7<-Bt&9)il?^yXuMB zAeRA>G5WQGNDyjHXEpbuu8Ykh`DSwnk+JgDSxMF4uBA~Od2!3`$1jhLCC`0#1Han( zy*E8pmxT|282BMh9E;z53k@o7T=JXu3uHOm@~lD8l6~k650#)(wdKc7bHV!&*{8N8 zc^y5`p%o>VP4x9XgzE~>RTGVHlHYrC*YcLwQ)^)#GKlkH{_)b=`K;uHO(f;`!YTJ3 zDt;ntxsUn5cfW8n#H4u^1GPX7d^iV3eTnN<_0Im0AY>RwV(!(DB;J*2+!YU<|K@cJ zCf{#xE^XkmtT5rQP5Cgj;PAft{R!MjI#GAmdjH5dVrf^0Ut~e)x`LdyeFOSbzEbhL zMw~{-%*e!bsw1f)pV)=nlBNJA-fa%d9j&Ky$WX+O<6(J(i;OHGT(M+22|~1OI8EJU z$KC!uJVor{c9Rb~XgPb)oqxTTN#h%P{?yTy&DMqFC9bARCC|uh9Y@A0@|3S4fAIIZ zt=Jw*l`6OT`Jc`tR3oTzDeFQ2<}w46^L$0=woCwH8K55!7%uY_;tsy1%V*9YI8LH zxXU+&rd_fdYz#~!jr?hecuonsBheQ`vg+#pL+%>ns1qxBZb2gGYoELWsLkF1h%gE< z7a63F`p*F;OMaO7hqHN>!cmf<_%$dDT@ebbZn3SNY4X;&gIwNCiSxnsq)Kyo1as3bhHFV^xp$G7 z#u}bE)+P$uUzbsmDPoqVH(x5L7nv_GNAWD%l|~z_fB1EtkZM;*d%T`LlF-v=m??97 zVlv=!{3?ku9bL(C+g2Cfc*%gPS6HH9?aF1cHhv^F?qi&BXzru$p zkNmB3C(Bn$eutDjT?{>|PZ={t`^eX2Hy?h8sf^9%Tw3%CSa7Wu)z8xwaj@yWmSk>E z9JiaXK;~5e2US8lPCVeav;BI16P98uFqw++r+I)Dcq%H7!@(;z#A##GsLP8NNWSCIJ5!if#pz-}GQ|@+(896g} z-xNw70m7BM5lO~{=MgVJdU;l9nmc|Q@zLw-dE8?;N=B1aWQtZ9Xz~GSddW>Ka2Cl4 zHTb%fSUEd=@&QCty}jgZKhm>-!`tO183_}t2xgf1^^7J*m08{Z+3 zq{zDCOFS=TO^%!v+=B8++sA?F&p`x8c&*n86KXdu)SZr8<9a%||(U2dEBdwCo zH>Jrfd({U`UAxWRz#9fx3V!BpI+cB~;UBpuuG-a)KB&Sl&5L8E&e$X*d0)7BNQP=5 zP)X0P^DLNuDJV_oIoo8C*vrw?hS{Ip1C)7ScnZHI_13>gyL?W6A^nOL{)N#18P>4i zVX+lW3%#1O7Ed!7T`rM>ptF6{_Cbigq>;I&0q#O;E~1J~jccr-m7-496G=#JJtfi{ zWSSrZ!jD}=fkC9esaUfDOW&b~#!H|obFcnf>YG6z72&qPjSk}~1O35|7Lg~YZ zB`3gOK1d*XiIJZRtVH?;p+-G!Hm|r;_o(&lFzZ_Pa&Tboneqat++=0s6~DIdg4;1) z>9`^bHl3Ml8H&#JxF-@>`U@xyA?+gf8^}of^vueI=js~tuRFn{UdnGzOTEG{Zh+KqH}BNjPDQD#84d95%qzw+9WjS&!VpF7 z%ULC@$7YBj5Ga?3ON1q(TTkg-{F|MA1p;6MHyNjI_q0yIf%9(*#t)1$HK8*z0EP6T zjT;C$2~uU{<%bKE<_`&NQbs94t;eO}?AoTEru-KhTs-=C@T#R8wWF>$Eoq@NN(-$M zAPwyCqeehL;ajdRQ;SW+gC}+y9WjaW5Qt^N4gpxIfebj**4bIim^LkXjK?9s+90i& zl4IUqOZbTsVZ`Hdvcp*S-Lbm$jh2pUv!7Vc&V8)9(A!1^*FYh{t?XjYm!e$*`fVMW z@73P2OwEcs9t5Y;M{f&z89|1^lACxIW65}FIF2(4lpi9r*?4uX4aS&r5VBQf$6>h= zv=Prr*`yJ#ncW0g7C$Viy1`MqI2P`(V0)Qz{}%UX4j7i*>Ua`VK3w1V)m+Y`ka42_ zouiByvT}E!Hl_~-gjBbJK+)@(iL+PM6SOShR}Cwcb;JDkaaExDr(jM6ANq=n+Vel#7}E|Eu9Ee+*v+w#zq z4R6UrW5k+>U%fhgrT>xnNx{q2(n|c5>O7mCQ%z+&*R!}1N5nI@GX)qwN|7}Q+#lU1 zsgx02wvU%>t-!SQn^E8=WyD_$mPNs_#e*Sz^@D={Z=}7r_1=G~&Df1u{&Nbd{D1Od zcLSoJa031iBSZnP$on;0e!Dy9T`LIFeIp?M;N})-xDF~S)#|c)@6MaXO(5#p?@o}s z=qps9f6t)CO~u}iBjLRsKB-X5IcP&-3Y8|3oC}tD8|iOO3kx1zR@jqX5j5V-#yy|v z-I|WVTALvJe;G1w6p(Z1%^r)KJP5rs{Ts=)=G-}v*RrfDmvUi_lcQq6vmDb=0G_P7<5QSr}Fbcjni)LTD$pu?!2_BhwT zV{ZrbAXxRwj~@4aM4$6S7DqGD>ODgbruvUIo+O>0> zC>@@CN>9NwNFn+`B$L2f$aldY!?hzq^^8^UIHeVqZGjigu)ynHWblZn#AmcK#^bHj zN*yRf5#|d*WG^C^L?*Ex*L6A!!>d!#NwBu3i6B|jTdg~4BsMh>!{v-XK91TeX;#6V zv4?2I9WP9Oej83rtzviN)r8bc>YM$B>$eeG0X7XnFy!&Z>tPL193)D}lQNnUL~zd+ zPe02av4&VA|K5tv()YV}>TgY?fv4U$9dm^50(uC_a11Ky4k3un+ozhHFNZQ4el;LW z=mvM)`F3WdFj7qzI|&+naQh4vm_N3Ro*0N6b{M!1ZMJMQ2C zv~y=Dcl89!sm&pGjzb&P1SJuu3{A-^w4)Lo_s~F+7=#O2MQZaGZH)<02}BYY+Ruzu zPjs8Y|D03$=O93PBw{;0J4v&}0QAo0iP5jf;EiA;hz6gX!5gwdO>3jF55M=64r!(M zCmlNV&l-}a($XJ1G2?DNEJF?DB@*UmS}&)CF{17@4*=UBqGGzg+Mn6Qs$MsORqp&$ zfxkI2f92z>iT2I0NN&zrN=2BiO9qAD+T41jm*W^|>dME}bYQmM?*Atc&?m>~$k0Gh zQt^K7b1v1cDmts#YGRk7hXm{_N^GRujk)Yvfk@lRo#Wy%xZwDmoA99NG^8VdJjWZ= zFg^a^w|K;f3UiqYB4Le`yB0CtE-(^fG{C!e*j+{G{7by8bN&j~m9Ioig6F4bJc67l zo_0Gm6W9nakF_Uut~-#C&`}8c7k9LThM6%4Ofce^{jl0~8}gZ1r%o7-H(1hU{bxXO zil*Y+!eekt3E>3O$YMQWjm|UOTs{aOwPzl*7 zbV%$3neGx=LkHp@#0gBlP8eIDm1@{K8AjaTzh$rjBkt?*o!1~UV8RuW97*+k;*&!P z9OJi;Gy~b@HEjvvYQ0F10bj5#z=Hi@XNk3^i?`3Z_Am>bAid1CfvqJS{8b-{MTvwN z^jh-lwGQ5(ya(CffV*y3uuN`3CS?G-gh$W=Dqw_hfO9KL=U%lbU{JZ^n++%2JBjSv zf5H8+xzMhKgaqT4Tb+N!a;z{yM^<#?}HTB#ow zYEm=}7Y}%Lirm1mC&fz7-!Lmd7P;S1n=2#gdVJr9gYeFiXyP&Xjav)aFFuMmHDg>l zTKNjso8fWavhmMt=6veEWJISmv92EdQLl3PmX?Q>>5a@tK#`CVaFIF!`FS&W#~cNjFN9wZ_$)+HWp{gH$w=2s58XL_1NyY0 z6OB~~i1=um{Jy%}!k;tb3>S+Mg>rBIR2@Y)d7LUiEmXAIwK>E3d(dE^K-_&+%N03l z`LIJOx90v&C@i~e$cKaV%x?$Y$LtMbWaLR+X?XPQHoz^UniFb2+X9euekqC z8y$l5FbJ-&VrSwX{+#~k#Ld@w1(QuR`Gu}CIidf-r$l%8ZE5s@->?7pd$?YB7x&6l zWlAFTe;Z3EwFz`pbeEO91}QnaSOt;?**D8n5!P%-A1o0Bi!B=da#{aPZ&e8nw-|@C4BoyJ{ z#)<}s)whetj_Iziahb;7ur6=Ee@K4fES2mXK@|-ZRd)bL-I8p@g zLgUNcSNV*Kvy=A>9K3Sg6ZJB6a$@Q%3r;t?mOQ%Rx&>b*-!_>Nlj*L_;`+J}KB!v2 z|L8AIt1d-KmtG9WuDi}xPO41WdL)w+hWVjCkR7LJIgPYxsbYQqcObsl#}7&3T=NvK z6Houb6l8&mi8Z-Xe7zyTEkJgCu)3zvy_XaD?EqOuMceIres|wM5rZm-I>tr_=u7r% zSQ7Wwt_hV~Y1!J55BA!bXzeq*gp6DFXUn%8Ki%>juM>hn=b27o=Y?vpN5@m#!gkPz zS+If$G~+4R7sWV9e%JYx{vj>}d_+47xm@C*R2;J1saEml&5g55^XZ0@VML(XpEKO@ zjv8cJCCL50K@rt`l|&orw_P7>XkYFYtn~3rnWwRL9pc|qJ>ll>=e}isrne1|z5ifA5DDY?Q^&zkyWXSK zRaM{e2s$O3F4kNDGmo+RJeK-6A}lfFs9X3p%h?&oBVV*%7RS5SJ4bquBlIx$M;U+` zNJ%OD{9Llx>hiKyjY4eI#Pfo65!0F%7G__~Cf;dd0Y*r(I{AcO1{}UE@iNZ8JE7Td zSp%gB){i89Jlg{Di}94*x5v%f@9rznZ7&fNETHltAZwv;KtO-l;Z?$B|CP$MmDF)b z=Vil`nRnxgv-vd+BV2E##~Gg|o_V$XBFATBjo@dF6K|uqge3;O_%nR+RMT?*)w?dd zrkpnga@!Tn8lwLZek}L&(iw~?f#YLXO}3;{55gjsU-N52`#=}<9sl03sDgJczvMk?(g5HVLmk%%8lSl3> zrxE>a{@JRQwvh4cqi2_mPyb$;m%Tr8!MEdTK5yeWHD!Vn;h~+uqeD;ex9ZF|b@}7G zP>DAJC7yZM^E~{abJkg6(ey>sEOh~|BRL>;gMk%OP;=n%_`XxG4RRr7&!kY{YD@nQ zq?_RTXNB!LU1`CZ_gu zoZl49R{w6q|3RKlPUK$Xs6{s8@-)M?x%&qBmuNxRVotVVKxB!@V=!B0$XCfd&x8=A zy~|74bxTs8@6&AT8kvh0Z-wQj`rtI$pq!o~ps9f4)HzGbm_%@>%1k9T1)|RTmLRHD7-{Cj#ymd#|+1q}TQCrkX z#Uw}2p}jzPFvVfoEcQr*aIsgu5Tcq;r~I-|&IuW!7C590(>>_rgp?%MEI2?i^5D$L zd`j5KA4$$(kGd5trVd@QNm(AM6dR;gnq)%VZ1%6>cM4FunJJ|z23eOnJn|}ZzVZCG zGedf@50`$WC4d`Ki)KLkj4T@Ye8Go&_CrGr?A-8Ue%GsPC=uu_aM6TCGA~DIxOB+MAlxgCo#9w zh8YU%PUmOWWh7!go7TvaQVQMw+Oq8j8iO6Y*C{;0iLEC(TeD2UZTy^BvdT=HEnl&z9O{ z{LW6s_2ZQnotq50(#npVDIuY2s)%pJ>~c2JLG~^~;jE$Fsp2pi#J8-8k(Y&DpZ{oE zu)UUU>v|m`c?ykzg5^IWIP)}Leao|O5XgQ%Sb&+iZyyVt@Xi-MFkpT}E$~W_T8Cx% zIPOH+6@T$ZE~0{N^}Iq&U!+zog5QJjkQC8}i}aoslEUl9uj&D6i(1J7Z%vohqkzU9WHICot` zftWP%k{_Aq@w(6Kh$lawrP`29pW$&jV%irdoIPaU_D2U@N`EtjQj{6a8){4VxDXFM zVpYR8@#6DcUOHC@Cy#DrR2BALym9%by;X#+mX+yWeg)`~7L0HdDn6I~=WLnazn2xr z-5^bhgzr{-6i(?tiKl9%)##zMRbKLP*7`4p!WIejkazJwUWBwdGR%Qzz$a4DWmMrd z_VsT9t8Vbx(X582c)H%_f~8NyMdab8ncHY+LZR4>IyTNu1lLZ*>*W4-)qbzK`N_(t z3z7I1D31$OIg_(b^dsSiB%O?}N5MF!07r-PZ|{4}e5pe3-*+65+vV7M^;by^|ILXK zmD#;%H-NGp^xM47M3ow~?!|i!XVQptdo#yH9zmiCvYR?S5eo>TJL0f^LASc@mv5c! zicr&x>(&}T_ot}|a@s?G0DHoLSb6iQf?)E!*JpP_=#Ek~$?rZW)x%d!mVB8rjQgME z<5ie5g%CzRv+M39FQy+)NrsqQZH=(Kdw;un7q;he>7X}UDMM=Y$jW4w%~;h(lB4!J zyR!3iS-a8{zg6IvyYX1;dsERI7ad@;K+%LNm?+S&3D8(w-PZG%OkJ;fmVI{?>{Ys` z+Kc*?kN6Q8y}}!j)(*TS=-t+Gr8v^wFJT$++e~l#pYnWdjuu5yo)+b`B9gYglXak% zs<9lrTPH6b~TKLmrcvf;t_R;Hcb83qW=X*gFMgYCwcDMYSCtS<`9ZbTRji zLOd^TU$r8j`nkZ><2mCm+@b^?hRq{boxG4ywbHTU+9^={_h17gfDj@v@RyJ4Nw683nT6x|-bq5Vq zK}u&7ll$G1xS)XVciLM!n=ND2b@(f!X1S*~hCbJY5=>Rw9jbU1423I+%mNEm;9p8V zq5qiM910!RU z<1bfu^~}xggkMpM3$$o$w=5g5wfZ5RkA{!`ZtJVB6C5wXMi@(F7pgY5h8LVlhF*T~ z>CLCVLzMpNVqXvLC7ldcMenpI=(&gU>YsX1 zIXJ{4Q8hhV%Eg4r!0temSnHm{DJ*E9fp#U`%u_df)RQ< z3OU8YevsA;nbT`nx=KX@ayIIFKZ2VoTsC%H8A!;o5(t@?WSUdnIMaI@%e~7j^xOsV zmWq6kPFk=8Zlf7}IM8Bd>0Ar!kayt$@vg*Q`T+Yb7(;e%%w8TX%6ebyS4r+Z&6nyd zQsV%Oeb2ek!0t?OMBw+xic-VNgAH?BwIQfCy@CtPa&PCzvc1Fp+{2D@@iO464p4*5 z!+Fkf$AEXrM(ttYpJfekP&VP1c?U~s@RgF+9F3|l?!g`To4?f~qv!c@-4B|E^GA+u z!;eRt6JDk8DA*b9duYKU_J`Ps*C^eHmy>H{U*%f|ZbI(ScWTH?(5}*bl3Pnc2D9dw zrBe043L+Go?#VeF7yMN$WQkR`$@x~@rTp`kmu+4on1#_lk*$XH4%?qMrm^6^{Wfhs zH|=gmzSn*`uIX_jL3O}GE-v-!h9pDYoVD18uFlU!_g+-7%fZS2ZuQcn^~CpOM|!V3 z4!R1ttDELWcl(wkm6r4%R7!+4qoMBP=+;8E-?X{V_0|?MkH9{-xAtQ5X|~vX;aOS5 zf!sLiM8yw@5e}>c_h97jE#Fg~ebvmN*wqCnPl|z@vj*lppNF1&e53|J?CqcD`FGD2 z#DqhMt7;g|KWjlJRu62H0`DSNAOK0Q#SfpWku?4J@%{;SN5te$M6;N);SJMovlFpMbq# zEfm06O3tOzFdsFpi}~85Hv?_D!Dkj{9k{MXGD>$Rr8UdqIFH3SYOpBkmx7#eb`G8E zR2S|$9lUty8n-V_tpVoxVrTK9+seA74!m5H>|xzIybYPH$np^OC_E4`#?;BCXYW2s zW|5dw-q`wQRBULl9Oxjar1Xdn3^`8nk>7kLBQVS0g(Q0gk(t3>Tqj|M69OC`(AI_-D>d zUYi(qfs?}9Gr40l)5x-?`ifT(shx#oBTSSAV!&rI(-SX%LH1^TxSb5uP@N1b!AN&R zUvNIO?sctSy3{MnnWBT3nyvPoEMU%kHK8ibN%Y?jF<;?a+)xIIVPRJyUZXhVHlDn^ z#3fuF`yu<3V^23Nl0-UO_teYvV!~~K$RhPNBPJrIj$ zSfGx5&d~V;aqpgYp_%mT9Zy>9r8ZhjhF`u!TkY${ovYZur3=I0hv~w^^B4a-5UvAL3p3^qg|-IyVm`4)A*Kq&I03J;9p^>tOy9gp zOlA3AXHkk{B&?=j`3-YkDyb!JF=I7^1S#iJ==kKjI1^KQs_L^EUy?EMg; zCgX%aHPRn?aTq$Z;1WR!Zu~z(D<<9K^G7Qbuv~Em#jlAnN@>AJrKo~^5n?!0 z;YC;a4miJaTfH>0;HV!n)(gAvs1lCdl{cYNLu-h^`Erja4jva8x!J6-#+1?- zI8}khQk?v?V6BwiUY61JU3V%bBe(6}!7I1U_&sRSinOG=kQcc>354y=)I&i0pd*?*Wu_0g z8xGojq=oU`^T%Q`P8MY0iV)*Atj1`4kkjNj`@d?NCZ&?3iZE4DFP?b;{GJ753wS#N ze@K-_)n@7MA=#u@Qe6F~S-|V@tzK?VVr_8`%y-A&hgWWcnyX4PH`80%+}7+B#i(}b zFA~Y+qCVfe7JDTKVVqa^&m^btVV!cX)gGj9X(zMw#BQ3ISbCQmDPN$^C`*^{-x9L^ z+?n3+bo#;m;G*Ae_q!Kc9^CwA?U3ybHSf(}>gg?~8Bxgp|CoB~sHWfle^^C9ln@c6 zC!mCMH&f{jX{4k}x??CvNQ@lKXprvih5@4ykR06|gKfXPKi_kobN{=Y#d%%V^^EKJ z2uGozB;hYO-_oC^4UXJzREi-chU31kV{U{oia*~QNfT~p{a5kpj2_Z${^L$1FYbNX zw8onp8Lq*wXJhF%Qpr6!IXbQ(#cLf-a_RSGg@7`O<;w_HK}JsWO4M7}-JC#cy&tbz zu=Bmqn8{%1DRd@~@nnn`E6~}fXOZXZaUVT*q9?m%LCwUX>sH}O)Sgg1wLRZnIv=^y z#IK9=y8VqmL|ZpZ@14W=5y$x3?tpH9GAfx|0cp?Xuhb_#9Nwa2wXqo``b0MKZ1i3C zkHG9tB4~A)73iTq8^v&#{-lm9`vYiiFVH)?CSR)r;#BWnG?psyH}|!XkfG&tMvHmO z{nVdD+=+(iH3XQjKQ6~$l~0%u_V9J=e3-WB^zY%PUPtT$MQ^{-=Xiw6lyX%#PldRs zr3r*Z8!*7dFpu1xKe(rZ5gG0Oh_F9%-9=D#sf*I6WFHQx^5x0@!aNljUi7%AXtfUt zNR_ULNUbMCiBe9kOl)HmC#`yyBU5x0UKd-qsSilD6s!CuWBALwS52el3jMO!<4# zez58Lex97;9oi+ zC8F&uOAQB>xmIVi5^25K?(bXTe-JXb%@VdyhbTM*i>!4$wGL4GtPSwq;1=>vlQWQ7 zK1JKGbu9p3T=78hqAG!5>kcrEuQ@D{uhPm$1mwPNiwpax5 zK3{5+W5;qe8Antwb}h#|4cSZ$4@0`BG$Hd*!hr zV2(I?AqD>J*kU>YQFXy>{6aR}J5|FWI3Il7H#uP3sV=&on6#NJi!Qmuz>;JJYnjHG z)}3zC(kB%6XnaeTJ3>TS@nfS0z8zoO{~hp|*Z^;~b#xDvbQ)|1MsUWb9*TY##h#uU zcOhH(j0bABq8>^L^4q zV@Kj?l87ygI#S#grufkQN8#8P0XjeOefJgW8o$s%Ftu-qY5d?`ulv$!Dd>J;*4)g( zsK$_f3i)BIC5>Tj@`qj;xv3nWR_^y4v+SZ}v}yr4@zJzaOJO6KNW7zbwy|367C9qv zdTyau0OMG&ghibLAsIq-^}v7l;!}HggwPP{vEJ~FN9$0VTo%$D|3V)@?YA{II4bHwoz8zz*e-V`kjVypvpb5C|fi5Y=wDXIa= z+A`-kk;PFjafmQ-&^a+Ls21k>C0V~m$jA2xdlrBqJNTUx z9;{dKkC<(0FCV|%OuXrKcV+YUdkZ1*!{A6wt6+_6-CV|nhx7Toi_Xd7o8n)P32#oZ zxTv)VOg1_*+&;X!Y(wrb3%P4EFIvDLiMb?QI+L_KVYSsI279HVfh0O@ha8R{3N5Wd{$|uKCsB1lz8F(3igG!I>zq5 zCYun^9Yd&V;M3__w@2;LSmS`@J4{OKKMoHi^nR5XFOYy-Y(wwXZ%c`Np;eF9duQcZ zv94Hau>GI+od21iP-4wKsI$|qD%}b2aU&iN;G_G!X|qCqIUo{UQqlJIPS7l`Xwatc zYNAF9p~jk(lehu`BjrOG0p>!#1Z0tZV+GIu**r2WTJ&q~~`x|-{ zAe9UMe4$HW*jyuVrQdq_T8cSm1Odj#5&?7KUw6E@%Us55+DYC-H=#H?be&rtG9Bqz zU4BW0`gMzIuzs);T)$Q6Id8jK0Pf0?jtxH(TBlHlA+mxN26Oq?v*T+x^GlD1CKg)A(ie8_b$N&xNi35`voCPLNh5eQ@a6nPABK{Ojxk2Tk2S zUfvdTgAV4n_1x0%S+YvLT?Ga6`>J<`N(btSd4!bI54MLW-M?mr``0&F?a$~KvL2dL zWYQuZ-@x(ENkiUYV(3){j-+&A->*lU_b1Y~R=t83^$eF#O48ahfTo>8rg%Uu?FStvnIDzEp)3tKq+HdF1I` z3FH^0`GEJ;smgx7R=wW{EsH%C{>>cKKumvO2FD#OD2KytWE1R79oiPs(EUT`k>Xi= zK+hL)^$2lWD9FfCLR^Zd@#N*7`9JaY%Z=)cORjHn+;8cA%Jo@}<{y&jC_Mc8({aT| zQ6eA)R_&PBbKGh`zSiQ6!leMN;x1j+pYB>I(6IR);3a9&`t|`X>-kJXmt9&mdR+BS z`;@$|>kX==Pl;1?vaGrPxbK)&>m&~IY)*VK5_3Ir`rDZ zYrH~T66{x$(HqpA4kyQ2F2Xwu8glcVxpflP^tXxAl9U^VY8Y_SGCyn4QrUtVXPp81 z!{(aZFL08E=+?D=JiVG~qNH7F^Fya=*QY{4F=dpi=c0C7Ud; zAV;AAVj&`Iu6Ey>Q7_~;1k+~6y4YCAv~uc4h8dQwb|F0vs$}K!hsm+a>9gldlj%UZ z6R{|cO?git95Pbu^iFyhg*CHqQk4UaC#Q9{>B6Q^Lm~ZnG=2v$eYIjDONP}Q^rS?` zsn%@QOo?EJw$9rcgfCIyYCcnOs)Q1j1S_eo9`f8SU7a_4i)LmqCBwh|gxN)d3{X&r zfO`SSn#d$P0;PPh3r{b;u-lmi82F*9&;wVF0!$0gUXElLYra~{U0DaFxbgIMVEY*L ztrYVKrwWCk?Ki&M(;m3r`{XpO9n<~mw}owo@i;JxNaNiJDjach@3p-#Fnv_C;Tv;H z(w#1{6@5dLYZj31{i6Tqur0ht)q}gfm&=i@7-BX0+?Ar&&dr{~XtyW);GuS~&R7`K zLQQCmNgU)D^Nhx8&7JXWwrA3H^(Sa4!k+*XzwP6|3MxW8AI4`CY8-=XQ;)5{yBurQvQoyWbL)6dBL zor7m>iFqQha5pWNl;xr0BP+g_h@&x`9w@m$5{vT6@TB$^=0dn{uIiZmiau}S27XoI z<6i_@!DIg(x9RG|FU^(GA{UG+;`UlQ}4I*H;uA~R;J*E zn$?Z0DSs~dNx2T2ngUAqb1aQ_sL)Bm`CAfcqAwX|!Ceps!Ck-B57N>X^ofo4J13@` zrruMR3Q*D{SvLu#A;*4NNLsx;WIcJ+sOCF6ggE^P-b6rHks%{PC0;sAnQ10A6cBAy zsnrNL?0ypQ>uv?YX-wpH{c_T8lPqm;2G62fVJyoM9$SG|UJ zVDmiFa_r-@uYw{+2ebx=5v}4p*)4?rwD#wC;+K0k|NE)aUcRZM>oj_^{^8_ZDX-h^ z_oMmYy~XQvqhTNvxXI{GuFj&-`1EGw8B^S;YBNv=14KIUP>YAI-jGICLc6jTmk&Y@sKl)DjVia%CsX6OEk9CwqEcg zq6S6{+`=tnHX)@(IMJ)!jTnXHW+eO-YsCGbV>_85Ku}H0pm9VbrcAF|Z5eqEbxByc zJlcj%0h8VwJ5S=drrh|3n@8`9=16sO9Uw z_j{#5ei%R~k@Xowb2T`oh>;cjSjc&2C7~)J-W2IV5=k};VPPmaefXq7;Ufgw8Tq*H z4TkC>|3chVZT|>sG+`fz=YhQ9Efv4bQ!7`4*NMt8@!Ry%!YA5HLwf^gB_ft}y+@+}8%|t@qE)&ayEBx`@FZbqHY6K~t6sC~#-G z<0_0t)Wi?lRver((gUrv(1plsxGH1*pSk$Wl!4g_Fv=9BLtY z|EVBxZQl?`wPGe{8kj)g26HV1*@uaJyZgc~b(d>!E zAN>4v=RFZoL_%MUrd5e_ge`8s2)@lHyyCoSljbK6aYAwWhD}4>-T%!bPs)SpQIs_yt&w!F4{dZ#8Io!4B(<)D!e7 z%$AFBB(gy)o(coSS^&LaxVc$^A%9Ij8E)nS>B@8vX|)Iv{z_NMu;_)1sg>`Xhy6by z&2cyN8h?gsO3DUF6ug+ zWCc4LdDjOC1rTtxjw!4VvLx}VtnQ}&;w76h?k_>mKla{k%UC)=iooN)NW7le2smOuxWfM7^NscNv~9o(eG16?IzzdR7xV+T1UJCl(YCw5q#NL!J>?|&r@tjwt8iRQs9wL$ zwgL%7QqbFSQ>flvGT>#Ze?Qw$eX-<~q~vw&pSQsVOj-`O0b;m9&BsYI@N@ek6VJz5 zUa<%vef5r_}E2Vw4(T>hPg4c2d!t!_9V`U&@98Y%C*zArS&UzSG}T!4<- zJohrg@~$sVF|Fb{EpZ{S0 zTo;A+d-~6d^vE)|tasHOm-S@mRG5J=4J+?`@+}5Cm-*^tzQ{%59L-UYqe1F*no>Y$^sAt9neT%9TJbp_dbi&y$v19DC zChT-}6--)KKiR<4JRM3^2Ms|pSMEorHy(= z5d*P}+ck}bU&Xratx4QosWSpX`Q96gYaV*C4Q0TfuX8_e+L#}#s7wQgv+(A%A5L9o zRYhG5@Th<=HFpY!9I`_qfA8ih?sM(7+aLaaVV)M!DJGUx$l#KZYO zVy3}F+ALYC<94R2N`rTtcW16CXM;T&1uBL%&gfs&dj?Vp=v###9is_aV&LaJH*pz- znU7Gs*_o?Aj0JBYOea@nSWJw!aP17>(({02r4ipQXfI~H+@Myi|Eov%aE4*PyFKVy z7eDwT(A>Pb2%{0k48%&Zj-YJ~7mC%(c&!5Hj?Mm+Z92*%B3Ui;gF-G*Zw~ zvpY%Eezb{g^oWVl53s(7yFEbSf=8BXS1?F?TVsIQ@k}?&iqEv$R|97ZE#P3VCRz3C z?3p(4gd{Yyh}R2)N;8>1tIqcRNaUy0{-E+}{;GlB%_h9Q!Ydi@aD6W z15bnMd6?e$Np!EBzj621szqH9IbmdN&@FE6`5JSms4}9hON80=VClK^G~3i$-&f@e z7VCr;bz>|XuFaInCY}GD9lB^E>#OrS7EkSYCdSV@=AB!Jq%V6f_#_zhQziePaKz_g zm#ESaHGJn-OBteym~4P^`YAPkWeML4nz|N+_!crS1>XWcr7W$$4Ig3yw?B#)&ZlXK zr#E|~H)ithqpdqnP2#aC(W+o2V4$gf| zQdeV1m!s`&n@q+WXzRlqG|q2c#rv4--uchi{nHHZjo1O+P5GWir>{Q@o~Du|4Xwjj zIWL-I{|txnW>9Wcma^#?;)m zL0* zTn*g7ZiE1MbKrz}A1?5lZr2abd09ptC^zKSqHq0JwTi;tOVeye&vdleX%0+o6Pn{g za8R#XoxnS-SKFAQ&UsHZEpQ(4W-!T+l%+Ml_;a~8K$*H`B6^zD^)wB=h zG>p_ZTW0s2Bw_gwuUUy?ZQZjzy%RpTWh{MES%x%j)MQ~%uPZK<6xXeAHyPM@!)l;1 zJYD{NpJbxxHGtgVbFH;eCZ;^0!@4R*1DB-FZv>Xa-Xqi_K>!#2cn0tN4A2`%M^qWM-l~yWk;S`QC+Qg-FPGWxQUqcn)ANbRgw^4BJG)5Suc!V z-AzM6dO&2E`#uZ)m54>LkxX%e<=)w+!gZK$x-@xyzg+eXw)UQEjDiWko%v7C-+}<8 zmVrj3fUPcdMbg@c1Jw{gE6Afo<9dPf4m%JJ&rvYi(-Q;O9l(YROvE7|O4venw0hx2 z&}_I{+8#6h+)cVAa}HafLajkLDEY0G98}cRb;Cc*^d~6?VX0{dvpR{ZcfBmTFR?q% z-m9kp7vogzaaj>^?EaCD4ce1k4^!=|4nzL}x-tuf&*NNmf-OJ}W7?C7M^h)kqZj)R ziD5Ux$-h-;N@>OV$Ka>Fsiqgk2vYI9u|Kgu*7y@rq&jEJ;X!L05pj#Bap)4SIP1y+ z3q#cH$fe&LIKOYD=tt}Sur|d_;(63GQQx_2#9Tyv8Y)xJ1i6^WnDxqO@W(k3ygRXU zdc>zv``Pm5K7D-PRa%@U?ZL8hRS|?pEUunPw9ql*aYnUDsAeE%Cr?J~ciUBGX=dZ< zzo$0guc96&o=OKqDNhRG*k1@rem6Lxdk)pxi6>*N@vafJ)hQ3m^&q0=piIpzEk<`z zD#Up@DZjUV#DbuNj>h**Ra(*I`h*gB<61pB>-9^2(CHGKI~_eeB`Fgq8&L2S_f>xK zm@hpecK_}$?WX?G!_Sn%fb=kAtXiGR1H|;HNIk=14W|3k`{%FOn3JwM9^YjyWEK(w z?;8mkPhV>jrHCBjWZZ9~Yp?&Ee=G%6p``C5?`&ieWL_eOYP|-b)`3`T(ulAQw2wE zpugwU;900~BPQxfaAD;yCM#C@?yiL#G*@Oocr0;L#2)8Od>)YGN)Z{dDH=Y8;Qc+j zEy$%(GnYFq+x6L3_*&4biH)RABuvF*emTy?RARA3eDbFmWTJkky3g(cmVUhBJF}>y3$k z9q}QAZQpYK*SEA|mDW>Nxq1$q&@#SWH|9uh_i96wUEksLOH~ z<-m5OzGR20wH;NMPf*f3<3oCn^P3?5nR$QTU5Tv~Qwr;HqRttKVbh8wT*5uYhozQW z00Z_&YT^C`_M6dfaKz2?J(Cqn{cr?a2t8Z;VgR)K_Mp@uxc|{YILi0{qegrcjE3)h z>-RH25!9!X%^e88JWxGO`Ho)%ufjZUR?C0v&&|Tjedk42kKLL%DDV}CxE|w9qK<_O zJ)=}Ffe>Crp6?SVd>fC4*SU^~x;+|1)Xj@~^6963`zUcIHiB84nsf01U#&)~UJ3fV zc#X05NY~YaV4@_B<#mqh2r8CB*O;G@9&nkEJ$EASxR5ltlXT~q0zb6kJ)chMg1HF~ zSfPo}sgeOb*3F=|6VlAt%-)ACO|8Aq8O&NMsZMIU>F*A89Pf#bZqr-RcD`y9G=U{m zSwDDl6Ua#gH7rd=ipbN2#SCABKJVuz|Kh2Ts-8oVr>-zuXzv`SlF+rvqyI`!;hkZ0 zh(NYuh~N)iQN`ro_MR5?+8S628DMDYG)-dULE##8|5&H21h1bY;089XdJ5y)Gn*;^ ztUfUKy(zO&CvH2n;pA`=7y=LWx|~lgr7(OjhE*{$?Ve|G?JtAvz-PVQ6?r-jxiY;5 z()&xmS1Qmj)9_tcTJINka9Wd;M!?bD6VxPp!W}D>JA_XW5QJT#tI~AJa>!2a@mqHLWSo%)(&i6zpu6^PPnqS32d(%z3~&RA=bg zv@v?sL^!v?b79*i1Wi8H3tkh(heFox8y!2H7g9Eri3_TN3lO*Aqi&zEC1{K+p_Hrx zEm_0%tC}vsTW*{+oQU4!cOiXEvvZAXxL}#^kPysmK0dhjglkfu$&<~p=+#SLC;r>b zPf;CIN|eUizbYT%>Kq8wpP+U<_+C1hM3eQ{zk}i4S;n-oTkh8%} zu+53XAG5C?lqYdNotUfFJl;NKxZw_oDeRuFG(^1&Rv_&9vsIIFOiS~H{MSe zx|G%8e->w5cB36$gCd@WB>?iHc!oZ0#=KD7z;}^BQY=ZMm@bR8%lz}SU^sdG%QN|M zvE9{96W>w<9Gd-Zx(15o@TvDTfuYo|aa&e|ob?_5l>|{J z)dSFV)~W1i+i&^J;lv4WV!|WcEzsdYT{B+8k@091lqhBWhBX=}Tn-mOha-+}&?GAz z2Q!n)oIQ=eXbj>`)ejSK45A?1VgAsL%^oW^=oY*V%xsx~;c&ut*7u59JhEjACtTo2 zJHzT^|CX_IePc)8Nqw%DQ}NbFdH{v92`NS^p{<^OkPI+gnJ;2WC9YfCwtFz62w|9}JpO3zjuo`h`|6DjV z^4Q+uCAHz!x%xOtI5q4|eAZ@h}D*?Rv#MuDaE zKe^_$HUHTj=Y05m!@t#XR71#?Y4AW#7b!HD zwyET9GU}_$TbeZrguciY)2peg_#_sNgY%OGz6CVcF+ywrvgic}aMctPpV}cm@39)44@@w^E8LaMjUO zKRMQa3QZd+t>?P}R$-zLU?KExgz7?HWYk7X+J^~VP5W(lc z@3ytD=4F4Zlje(8I(hbyOT<@v6j@2&Utq`4B3vka7U0m-YsYtsmins1NywHe5qmq_s*_dSt{{fDE&_f(fQm!?OGu2x%?rB#)2VzLx=sOnC7 z2-Y(wCqB9oS}>lGIskoUqPklVLYu%z28XnUkAg0eczPzw z7Af4#>9g4OKhj!x`e0-zr6y^L$S=_fPJ6tyS%;EKJxrxZ~=+gv0;q)>{^>>X-FDy0Ccu5Yc*XWZ?DEJr|YWma~W^FM|%9fDus z@3Qg*JW}m){M4TO?X1OhcH`**4p`Vx4{|Xn@r1pCS<-N9P|oLqz_XdSehHw#I?#9@VNjKjLA(VoNS1CH}0b4w^ zCAH08>LgI#XL3OgdGGDSi<;wbJ6t%7+@Dg5zrvQl2Crt)Q-wMH)Z#wWMvb}an1&dx z5@dxKC3MyH`js1tM;(xm6h$>>BXwAO%rsoexlM)BpA+}iLWXucFf_BLobYAG2!n_q z(#H;%_lXX5peLiRUfr1Z{$&rD=BwQ=g6f$6SSqrJ7l*_Se`xu#0(ABNtFdo~A`-7# z0wT`@OkQc5XmP%tfDbjMHn5xI52XdvQ- z7i-qZ^d%JBg8eb)dzyijU2eub>?d=b?GdP4qa{j>dL_~M5~xC8PwwCS#5gdqyA3ki z5-hR%em9i@ggu)@|DDa;^{a$$-^E@A{7XK4-x>Mrd?ZkpG719t&PCc|S^1rOIr3@E z|EC2|vo82gdpWY10$YmXju2$8RoTIGL`#B~NNoFKYctxrlGU~T@p;INq!3%C#9sQf z$kN^K^7Pwk%CC5@zIs{CJ3;5d)<<>Aq*`u1}qRb0?d zO+IIkNNh@@hT8FR+;_d7Mz1r;6Q8*}A;cY&5~-FTCvPl@pJ(ADLYuEhO&5PAjr?y3 z-#fgo2lD&=ANmpc7t?==ed2wmg+wjxU$IMo3^7H#uOL4~F<8HC2lZXUZ_qT`0BgMU zd&P9nv!zELf}(TlG8`XW19y@B9k55j#G&En<6U~OiW~{F;xvYieW6ov!#HJxZ<&UOW{8e^utAXr9duM z+on{lEq{f3s^c_G{nyl}82hrhErjJ&OcAtvnTp~*|M$sO1|CU37{Ag&cXsy}O2y1; zj;}1jH0qs71~KlnS4Zsj$KLJ^8U%T^LXuXUaO`f6`1phDU0y)^d^9W0kEV!~JSEtP zmk7Lm=}8X-3R-xvgig^NU7)r`_bvFZiVT}vayb0cE#R1`1 z`i=?D0=n}8KQR;QwX5KPdo4FlBhusm?hv1;F=6H@P1;LU_h28IeP~_TUvv(N`);Uy zt^d`w6T&|vzq39sw`ClFkLsST%YdvMJ(iY@Ts0X0qzuF>uPd%wY zuJ#lw!1X&>Kg>xxUHZIE?=`0uHapf{Wvw*n=0GPub|<;LV!GE{)>XV3yV0zlU|Ln?l`e|@B70__1h`bLY*Z?S`YeRr}=#s ze6AtQ>HHwv!nBL3^@Z9nX=rKiWCi(Zw>&ymcid~mpdXw1iF zmBl!!Ry^gg#TRuoQ!q7^8g{|J%3zv>*uVMvP@c-E!GEGOZ}_SFJxrvZy@8DCZkZxN z2$}KMS&&##dAzL_E3DP~xp_~>Wr#$KPE)sgh!MxG-RTlDD36%%AP)^oVr81K)r@zy zn;kk3uKD3Ku)k713mWymg5Vg`cZR-X-HZ}kR-gB9zhgCSZ`BNK#qwMeg=;#V(Fc2= zSm!YQwzDxf#P%#Oy5ls**3r-=gG*K9CQKcuWx>ARb0FL@gPzF?(63KOEKvg(fvEl+ z>Roy2%wfdWZjZ;V0GlhQL!bm^X%5qe1nh(CvDax9%LDc2=r;oZYM$fv>*FHKf0fv` zyG6ruEH?TceW2RrGn21mg9Sljr9#{TRrJ%Yq?LQqxweOdqc5)UfoZBgT$$Z$AJOyU zjecYutMlz7gQxp0BF(V(M8ZMBGQ_gXju(H)s8DXN*gqwYF=RWUpG`KfAy`rn+ikai ze}$g_pVuSb3k1oZFh2Z7i`sYNMf_nC;l8Em_d4B-0WD*G#=WBg7yZbX#2)FR1msm@ z_tC~XK}#v?Wb2EA%#dLMTwDb@b_eTGp^3Mtu@}BQut$TiGk*NjDf#`Ap(I8dvwb&0 zXqzT)lm+PCnRo0fNFv0O zMU4mFM$nBC(EP0))RcM;MTqWN#3Q#v$gS@jV z-i-^`d_i7!V9DURRT@`|6?JhK{@rbJkdaqFW$kz0w+DcWDH7yRKI#jdhx%Q|`fpes zW;1Dcn2a^gW=g>FP0&%j1<3Qgzkhp+I$91e%Do5FL4b8J**^@;K^}Q1e*1qh5xzqg zyS$p0>X?plbPc#87oCd+ja~uvjsI(eJ9b!v)y@$*2Oz9?!XdWH{)-Oayej;jVXIMp z(3KLV&)?*Af6IOR{T%az`5Y$?_IvOEn{gNRGO@JQ zsM2bZiZ$+~7|_`eUpM0|+jjfIc+NF`R)6+a|Nl&%_l-|N%=$TRd}5;~j^!;b0=wIy zDb#o&-{nTFxkpXefpj0uq$C~t;gQU{i=RY#`)byz{q0kpC()C;F<5WL7WXggoG+Y{ zy*9>LK-=Ctwd;?)NQ{o-(J#P_kDr#CgM5Bln#g}YR&$yXt4heTxo)|e06clQ!SNtcD9z+UP<&IW0}hm+7r?~b0`(Cw;iw( zs@RwTLcc#7X3eb~X3e0f1+`S8487l9;K&}j2vuKWlb>S|%3==m{SZ?gfR2@DW-B{w z89+;;Fvq)?0|~2Lq3sT^#A*EL+*l+iAPNm!0Qj4?lwq(05adb*0*;M!|6S{QW5N*? zn6^_L3C-1u`^9oU%)gm);`tQ}md|%3*l^WgWGG@w9NTvFq0o2QHlk0_Zh-~nV|PrP zu2(Oak=NS$6)O6rEMSp}Gow}j12w8nt`HdUEM%*gSRi*OIlq&#-fSyNM#Nt@WGfpMI?BT}$I|{A9Tigo)3Oqv^oQ&i7WwJwe#$ zWeonN>64$@gkhzDz}rPX{@GzE*sc{d;^k7`UW^Deaj#)#=#;e2OvdDSmA*e9)xC@B zjZ9}JJuv>8e8QK`W)~{VcPp)=YfC;m9LcrotI2AbGUgJVo5aVCw{5ba+wX0fkF03zYPS|lxX$N(0j9e#;l_k-a?T4+0V=yxo`*n=XG z&ddMBK~)hqdnUysj`%mB3^@=uT^3z*j&y{=%m*!V^v@@{v)%}-Nz%P#B2d|QUz%RK ze5XMy>sPJygwoc?t^Ts#c^SHq!QHspk4aFdnNIUN`9-yMjh&~^q!LYS;~xh zi!m#gS;#lpMal6c51K-&e zpeIJJ_9Sbu$yWLf-XDnL5qH%EUgAebUnt|KV|FUfxG+CItF3bKVonu1FauX_u+1|F z<9k){y^Rt(-Y!dE)58kubbolTeR{Zr<4);cvf#SFn9B6-Rt&h3UoD~`+~FA7ZdLUT z#x?v3RRg~kjTF_j5lyfxU|UUuOfV_;Y&Y(faEZ!eaENi0$p)oXcz!)CrH1K$e7$|A zH%7EQu+09FsrLM+(q;j_^p@f!#g3ecXk=F{3|y1Q`(#1weHk%g=Jq#sOSW47rwWi$ zCtegu#c##ZvS!+x-xUAl%ZXWkh?S=tV}C?Q)NO5-K=`W6h)HXtu;!A}%goxRupron z$MLq;6p{+$o~t4p29Ht#qkB%dl-HK@iu<83CMFnT8qN$XHQP!8{1`Ar+LW9$Xwmqu z&GzPH21Q3??Gu&p?a6_a3ZqDIp8p04yp3V}++!g?a#u=sf;0DY@IE$LK3sk_m5+u`74iqewH0?{Wsau z?puBbL_kJ20$-21hc7g&_%O-dg5;6qN6;w@h6k1QyUF|cZ34V|30qc_x>Xw4sYr%2$FHz z;?j#ft2MP713F+@G_Z&)BPP_?F1vvmfCzY>tmOY!3#q#JPODMJQge4P=^Z7D zXWDvAMG2C=v)W>w#Xu`C>93SJFumwE8A)IVbm&UQw>$75lrrp!bD_f+ftu1cxQRkw zgwVuOG!tm^I-vVLmes_ACyym(KF8X6_YjKc26a2lf=uO&r;Nkm-YW%OdnVS_@H8-H?S>wIUesCQAfre&Z;(4wj=obw>$42mN<>Ofs0)RIT z|K@ypPk+UfX?a)UJwEMl>f;VQi8Vr~p)ksvV{Gcb`DVj^x1RrO?ZH8X+}viXAuEl6ysWcE25;xsiRAO-f^ZUWWARRr*&f2=f~>0TVRMm?3Bqf+sjsY3 zVKLu?rZ#CRhGg~p7+lY6RP-PXewjZMPH*eh_;4KVDN7;rmFuR3eQqdO|J~14ZZfRF z-6y||Pn;YVXK1lZc`-&N+qy@Mf_|y_b4m&uwfQGfxX$ZD!*Bh8gF)x<@-xrxnZc`5jC6R@x3q^?t}lsznYrdw?;&tlY?O!i3s+t)VB zs8YikZTA>b6i+@(E;dKne5Ux=0!0jD&iIhXs+jm85!+`(?L9TBbyDw488mKmGX0}n zR;*d1rqXU)r@uw#;<=RS9+;aa^vIu;xzpg|2ZoeU(Kky0%;s*~Bo zUr@i@&2wCW@(vmT$p2+o#b&9Zlibmt+2ROa*5o7GNT^ekGxo7p(_WBZF_#jC|JInuvPRy8R} zU};rS`!2tudp0#|K-yOpwY&xTqmEh}Z0|Y;H>oGsw0x zKY6~91d_O@{O1FsDPE~XhZj05YB&3NAi_W zn}&MC^+fM5D^m(;e4Q$QCkQV487zCD94r>=;vkqxEXRJ)!y(USrns7-S$JdBL!(JS zT4+mqC_uS8v#dx`Oaa11mr)5g&t{$~GR3(??mNDCh%6_dy(e(#di-m%OYwe(Wx7Kq zigLIpw&y*l>;7y!2s}6{xNZ585oh0;Enf7kp_azaR_3$M!)CdI>2K%}!_8v1N zu9~1Tq})ygu-`E7(^b{R~4wH2^cPW;aon%*nnxu43p8s;ap0n5p$g5$xTL5YqJJE&vogGh=!cDW!5;$Oll}S9 zP=C>Rwk)P=WgKu>$98rd9AG5v5KC+WT9Qvx&ZMDoPTW;rHlW8gvv^FKyq z&6Cjk(?S>KJ)6+z&>~ch;r&6)?)WavM;oM7i}2PC$K(_DGTXWj8aZD0DWZ-X_Pujw zeQ%5h&p9Ip$x#35F%G*xLyQXpC=cFD^5M63eEMPs5ZmphFJiDY;5nHlE}_p)M!E=; zMWP94ThZtv zoy|JnmJ-;(vY5yrnlvPbi>(pqOMb2T`)k{>Z_7+Ty?0cv9#O7rr#|CuSaYAO)EfAl&dw$AvTeR9QTj6v z5ba{_CvE<*B?9&@k`l0gat-M`>fT}y=O0{Y8v4?Bk-&K#hO{=eD3av!{A*|eDI~lb zbQh#~klB|Sf*HH0W`i^fmBfd2o##NdB2*1;ZwWptjt3wbJyrwvberGvJK0k^<;WsW znlNYujonL*3s*{>OT@-N?a%fLUdMzf_NVlV4H^GCDM-E%-_aeHroHrufvYfodupyR zy3TFk4bLls6!*)l#x=H7eYfRZR-apYrf^XS$(ZxPU@6Uqt&|R_?J-&X@<5*e&L3jQxoKH!{AM;H;yE%mU&1b!noRd}( z?h;Pp?6R8~8pRo|eH3C<$7logbImOPm-i>?_^MnYZjDLhMn?{Wgrx|830dE~mmVHB zS>I0i3!qF*2U$)41K_xg-2%hPvEDcSKNUE%V}MUoD&PkXRW>Ewpq>}!3Bde~uVq2#GWCqhV~qnoO<>l&2`;l6s8r4H3)*ApzB z9<{!HljpUGpc+%%l~zz|+J!ww3qgIX3(0fS?{0?>%NWaZ0FbB`BqQ(zeFLHP!;q+5 zkP3Ke~t7GjpNbk*K|-5FZMJ3Nv2LBE<}_U8eC=Or?^9NvWoq~r?ljM z37exF?%QYPORDVTt&fuNJ%4j7~x5O{T8?h+|BFB|41`fidF@r^v%k9@tH8)~<^ zA<{ID?fe#h;lbz?Jjb)G7omj9cv)&TVmlL__G8K0Z^VSD(U*i9HJAUozk`d`FW-Wm zz_piB_2a2r!$Oxo#CeTy@=L@NO=EeA_eSQx==ib%Qgx;MxNQB`~-p_ zH*`DVNi+~aNH!UwN}$d%&&d}tEQ>zQq&TI)zEESi`7B@`xbd1 z_p8cp9c48|g^FZ9Gpu*_h<+DM0sJ>4{BQ1mkU{nha)kYq3ZZgkY@+^!hjTDCe@x>L zSWNe*^9s~+l`wi%_Eq(pdqYjyR{&>Or+eCnH@_Nto}C?iOwp66KkoAnLpTQNl#|8k z`8w}4S%6J0<$?P7;<@E%`>ewg@6ve?tf!(wgW1V%$+FZoV4|Rw3L?{_x}QHq{Co3;AhbuXAxjNtrdJnFG6>6?Tn8-%Lw&5C)`0L) zsTc&~ZoV}l>|L&khs;_-&lplcYnwk&Qby_^g-%twD zSpE{aHe(;|=xyETX0FdB3f6gwQn_clY=;w$H;_LT|EtIo`eR z^Ir36Ys|KTrXuBU=#75IqO;CVkKiq*vqnp`f-Zi~csy(ce${JhCWhb6rm`%Bw2`p- z7co4=(63&2*$FsPkasfGK6+EIvF`z!clO#^*Qw4=eIq$MFVsbRYct-$K1)=;ME_kT%73+d zx)w*Jdx0stJdHi6_NSK@f@?0Hqaa=_YG8n8&x>NX50ziaZEF2!X{9T&_a-8&eZ(#{ z75O(uJ4;Irr}9guLU?N<{&)zk5-^TW)r6>Jyt8O%q$Wq3g(+L`ZlC&LVU<5M#F}*` zT9|!E6NY!i?uD1$8%>Ul2~u7(`I&r9$Qq-Ko^`H|7P0uv9=&x}@GLLmin@<}ojR}d z^TlugWT50mUrI~S;~NMJu$J@U4_!5UVkeemMk9P6rY`Kf7veCd(^HlM zL-h}{dLL6AG?pbJwHK!F0aJ=lT_en5!Lf&X5^LN@p4zMJngUyv9dNQ}cmMpK;&j<* z7*jTk_P_?*Bam!1YB_CiZZ&qgSyCE@uVNnAsBDwFqg!Wr?@m)BH`v>7tBCW22J#CK zTBkxu=h%r$*sW1PC>EEmJS;AM}%i>EMiWvGw7P;)X zos4gGB&?|Lqeq}t$vep6Pr}A6Z?gcWxxyKZ*M?@~9y0+AWNVJAjZff;_-%4{Fv*M{ zAROaOD&kF4<6VzAi6_I-qVUoW+VhRlpM-pICX*U=EJ4v{d_~|C`8fcsE@SC>YekSVC zavUP6j?185B4kr7sS4uy>`sWokc9+9%aV3e(B|LJAyc@Awc3!iV0gy^4F;x3hLH7o zPG1>4SL$#A>FEfWpK(4Zh}v(wZXe5i6ms+`MbHCB#!W82sBUr^tt|c}ox_5}#t#9@Z#-QZ^Ah2&Z8=!@hoQLCbD$CuC&ilv?xmpb8N3dnbzSA z*@4TVlRTKbvw<5(U_6NJE_4nnCd5!=hpl8z>VnwMJNz=L{_sbn z_hQqvA(Ov4i@NQ4)n)-IL6va^u5vq`tKQ7O3+hkiDB*y&RKw&8i#h@3SfPsFbQp`U z2es9-rE>RF*IUU=rJs-0-;(+937A|+LI^J~RpAAOA46F1JKE!`oP#wVkiTaH?!4~n zE{d4{I4+h9+R#IfZC+A9N++~UK`WtV7P!nK8y!F&e;On%!~2fUtJXtt+*tt?|9x}C zCK7!c2XXP+vOnc6MdS?CxQ^XzVM{8?I#JqHcJyiY+nzViOPTTflaJjR5qljV9N*@& zeipH$3T}V8t*8SyXa-;}dJAacU+sp}$CnQe0`e&=$?dV4q&nTKA$S$wUQ(q&0Iz!8 zY5ml5pr9!O)*4g9RKRC(!w8^t@U3a^&hUaFm1XSd#Q14FSz9Ud-j!@+KO$`5?4?Y7 z_a)k&Yt^J5FmVGa1r-+T@`ZEPH0*2eOV?ho7@UXp8bs;9Mm(e}~ z)$lZU_#`m;Oy>IGR(AKwvGyDdrzJNWy9wKt@GL{eimMvtCnqi|=_(FF_`(iYCV^NTAPe9n=kcW+IhNj_*l?c~{*_@&`6c+$uIyJNRzmi-Yt zS|9VHl=`olM&md(9-L_*_&pqvaXlsobsQZvYh9f7RAWYx1jE1KsP{EypShlOFQ5OQ3M7i+*`E;P zx_V@yIE)I7i=OA%t1T`+)5h^xrb}dA)^Mo&q>u*+oAOt!h_tIAb?Sbv*^543kG?1e z_?V%oq^-wr$g~wM=995esL8eT)+|b}^lT|ArN}(?=oVe;(+~eLQd{U76wM;!oPKE> z=sFi1YVcu-?E5a_&Py+>{Rm!OQuKHV>vErSp^(p0~>k!Aw%P} z%I}Jg8TZF^8}2}oW;JfYTFE}UzRXGZ{ZK`p zQog4+t^iDL4(9Pai(lHj50(`6QItD-KC&8N3<0c+-8V*+!qCAsrk2` ziDh_Tqa}btd`V8kx3?>q|L>jKji=dfWhNqfQ8nuqBNQMec5SPbXFVjw`-I>)SpThp zzzKblZZc+jw0Rr_=WWt%>KK9Z1_Atb#*NzuZWT$izhy-$e=`R)jOyO(W*y}XSbH(v zf#`VP&JB%}+cu|YE6HAqo(y#F6#KMV$**fWLeZ2qr!~hW4W6Ip^~ZMKdvn?!MYk67 zA#<#g1-32k0fjHy6iAd6CW1Z^n#9hnjn6pASrw+sEW?_0oV%txO zx=M$iQcd%KEtpS>=tUS~#9A+Xa{f6GVXAVUF=&R`*^e1|TCF|bvX`L}-KktcM(#L9 zPN40b6S3C4dw=jH{gOWzj$)MXM!YKEC0Z7)<)yvh>hF`ZMbJ>W1Fw|2wUH_TX^ha*#+6C-q5DjLN`9 z`yGJ9&74da6ao~cm|wf~>C3%f#)p$vD%cN~lquH%{V7h}F#w_Z+qxQHai?^^;4O-y zdlu@7z|kd{W7B#CqtCnJ6``$(-40|tJ+Nm?8_pNQhff;<*)d!(hhWkwp#O>sAG`RH zXv|(1HNQgHO}vRi95)6g){|zhB*BQraFF=rHi^! zjG1Dt|C>XRkav;AmtK{dwLG;6v~6l|fP#rE1gbpHWtSurO{v@M`h9s4akbWic+bhKhc#C7PV7Co75GRNuE)7s+KsO0}ZP81tv?#YZPM zk9*7H-yUy@Mc_PzY3S@-#Irc-OMIDuSJ}in!rKveJ|otxk&K2UP{4+j=DTa4*xyQx zZmaAedfiX9Ll`$|0U8<2)X+t}KR=bbd&A6+zFzxKq`sKK%jf>`VxP7~v8Q}~=AhV1 z{5Rz(pVeTwwznuQ9ov&s7Q2@3oIUmb*&C)F&mG@L=@3Vxu-~jTatr>=+LFdE`QE#B zq?k1Bkm^z3HL+fIv#?YbAr>wbrl=Y6yTR6~tnSEms#7`5Js{LBGyvrPS?@>wLJ@sV zq8-Q5N9*=KZ?n3CcsEiWFwjaGZ9L*h5a^E72*?>rQsB1?@GKIRdobDoR84w>FddzZ z>^-YGIf}d9bIJ3I7=55mO?`h|}Y&qtpDN%qKAN>5lUDpN!uRy1xzz%f40_ zF!YXmc1_6h@~QkhZ9fe(@|C#Sue7?WeRk1+c__SE{ zq_hu41C|rm&d-nnhrLy)%i?_~e(&4})b2XGpzH3rqf~R@gDv-Zqsea;UAXGvJi%tjdZk;bKBUE=Mfn>iyKh|X(T(6Mq@#+V-V2mox=h*(*Gryw zv3vP8O`B%AfqTAW9KCzPNcGEb5Q{KP@PMwluDw*R)$n4SM|_{!)eI%cylW9U*l!8k zC$PfGPWo>ThF^SLr2rT!St%;_ZH6P!%BlA{wXNu zd8ck-8Xf0u#QxKM^17mBg)&}EA=83N4=le*?5^A#blk*PUKDVp^ek7ZD!4j@0c2DC(Tjnnl@^yp*7;PR*Ttc!Nx;{c5Yd-?_$=hz#V#oya z^Tm>#mOLggO$ud(z*vQ>C^_H)j=dA;&DR~hmU`V>C+hS4leo)YM!(cuj(u2}oqO+b z#1c)*6y*;1@*I{efw}4Vh1h#NAlQd-W|kSJt1(5rX}U;jKua`m{+Qr%ERwweaQt^4JsD9{5+rQR;|X`a?k_Ei&^G znP^$)ffp=lzv&T$x^~G0I4NiW^KT8FUkJjXyk3%wqr+1;wEK{nE5&VXFJzCS zP-!cay4D(lygo8x;IM89qXmoGY)>{#AVbaSm>mElF~6%UZ8b|fi%JA9QWhC+B)4S) zG_7DUjPMG;)wk<5ToCV{rq+5iZAU*U=?HtR!sCdP3YLHJ&J`r~@rngK(hQNXPs(ku?)SOj;LSyiUouPJ0CHse%6 z)fyz@eC8OUYemc`-;PKLpFeotj9FsAX*!0)J-TQ8fjqYN0KR95jY%GQQQ3D~@08W% zibhWmq`QE_55*rG<{uUvHqOGFprsUDk{H&ml=2Irf`!{;zo2~5S!JBd`DZCfz6>Z@ zu>S!k{g}v{3(>4k&V zm&evZ_0qDF410gou3}O!wNYL^89QkKJ7%mYuq%DK^1$YOoj-Di?B#!GzeYJ<=wXiH z{P8@(awCFhM19@_-t>i)wLM%cP9Uzi=X@Ow_$vanRElaH+i0d#%{9a z$RAy=@T`l3KGao1yMnUSy1hFe7aXn8?d`zZGX`>MAwLjK3$Nn2PmXrXcQ^gFd{7zv z8`U}TPE$R)&F`X3)b3;%-$uKP-+IMxU1eGJZsXq|TGQok&);$K+>lxspalSRj-%|u zxko}HG!eBh(tvg2f^TB{@^=fLE8ic&(5BJRvkkcoc64Ab)qQEP799qD?t0TCXt0bZ zx6@L(p%#Crmi;lLdI=)Wre5Cg5acm3il8- zwC6a&M&WlA)T9$@3qeAiD26R7b_g_SQ7B9TsbFe~BR3lq@&f0n0^e$v3H>(Q8=v*V zO(@wwc90xc2DQ&PQd0cR{~>)j97r?*f5*v3P-QId;ioq%A5=b?FvJR zMNMq35Y7LwwH24E>5pI64oqvfM}GJ%_F`Z|6uDDiC<1@d1cbyl#FKv;+=nqSg97?d zbkNfc_${;nhYE$$)saAPaGslRuAA5=#YCa~ExbpdTj7jHtn@;LB-GEr%)0BX_utM9 zR!m6$go)bAqFdLYTgvF`wDNP0fh)@~Q_|M!odVy6Ro+Fzg9s=(feiQgs@GWD&!BlR zxv;S_kQ0TiHD#ip^4p%a!tyr$s8f2NlE;`tKGKG~@?_nTF9SuPxnX{bGrFCP#4Z#A z2y$%tc4mz-;tT=#Z&$)Qqk9N|9Lzz!{xWJjhX4J`qu&k_eKL75ng`$Ook4M}u$&33 z$=$`jch6)T&i}%}fkrK3D?gFPisbVmI!XaZthr^)wX;cKm2O5IyR4pUnUmO7!Z>9n z)UsVu z6XE)rPX(O5o=gE70Fcr6tcv~dsUH}$M+Enb=QxJmDDq#7T=Y~4hJs)0+IW%r9%9?F zOb?|x4Wdhh^$3-0RF?m$^p-l8bj&(XZ%+Mqe+CYnb42V?)-+eTk8jdMV?t5{ueh&u zGQpk_n|AORgrUm(r4hYL5W(Y>blWY=}+BD@mBWPH|ZEIoJ78dI3h~)Xg+Szx69rK_FpWPJAWCA`Z4HDvs6YjBXOQ& zi{sROt_3PP^xws*GFU%`5k#SVMY1jz&#tzoq5=YOPnEa_n0fJsaz2ZH%sr*#b|)8@ zO#zjTrT~OqWVV@Eu5{hkDz7ocQJw_@BZn0&Q$Wm-XKrj1O1kfQvMxLI13^)2B?dao}Y`_yBN1z!QG8f z7NU%(18sj-X5>mQ`rEkBfLFe0Pe>Ta1fNI6>~Q5dyHyj9esAE2%Ha=TyMArQXdC%m zo9KXy20zk^E#EH7nPea5m)DYi&8*XsQe>Soe90zwhO}^Yd=SKpE^fe9Y)W$6*{> ztC`jeG_{(-w0;3hEe#J>AXpr{P(Ld9jkIe{Ij!gHHk!kN6kd7LLeMM1;0JWW5?Cl= z?mIQv?W$V-4v75~IfStB8FiZSr-v}W||gh@5n_7#Z@%y@e73{E-J zbTx#`dz8#E;d)YLf&K6?QF&*Ll{4q`6WSARM~|F_RdSbQU>J9@bgYjsMGw0$47-_Ftd2Rb|7?2nOjce6V33j_S6SU=V zkanbk)$dybhvtoIgXFfPP?gOOTgAJXz81M6sVJa=4lXxdyDp+_V$W;r$Doq9`#&p| z@bW53sy)FErW6_4& zuo#EH0^RM#p$9j3Did>n@02_`lN;}+J771h4m9pqYf#|e6s8Mv-`aOaiWiY_W2 zA(&B#eF_PeAXW?=CbYE*K^hevex^K^YF$~Xd-bNz^(r8Xv zOD)ey>*n4d0zzZZVFDOU@#?AdNlgS|HGjYSO&pd3^`V~@HpL0x2pPbPt>8j()PU(` zO?$AgPV_9!$TfT*zJ25(jpW7KFp|dY=hp-rEiZEQuHNmij~Z5Wdt_yC-Jgv8u3)Ki zIp9+K_T}vX#cRa!OSp*I;1B`aJK%-iyIgd0cX@h-uL8SjIsJFuJ`~wjQ{+^<@m1>% zRZgV!VI}*4tsKMb1t)d2*#yuiXu3+2yWX%|$&}$`dGX-VhYj|q4+TlqP+>p-Cr}h{_=!NT z?j*FdThVGUH=C8il5FBB`-t*h3Fo`uGqo&>oH(TxeoMK{eotOf_eYF9hEEIQA;*)$ z0yT6jorqcb>elcgd_2Z7Sdn6w#e^mITax`;zss4^EA;&Fq8O=0DwFHR7MXl5FL(2t zoi8uZFh*f4iuYq0??c^i$p?SD&)1n?tfnte?g;wFQ*H=G=(#d@%upyCR6y(~^5Gp( zyaA_20&8_3ZK_TFz4yw)THAmye}kLhv`SlKgmpL%s}Mx2b?xb#hG)`2^?NNcl;add z5sf{iR%4-z^%s0ueM)*{{%HTIJyM(a+2(sxmfz@2JL!m*Or>Kxv~#^#T?PYhi1TnT zL}vSsSlV)^4em({Bwx7gHN!?Xqu~zi6%_e&>g7fsx`LAzyCu+xt!UoQ1w}xtexpeT zOt{KB3rK~-yLZ2%2ilgd!0(uLlj96xEe^4uC?xz5)0J$k>~7wKvzsv9Q}`|fVMrm( zFaOd8`}Q!J*hi?}y7Dgd{9sQVn`y3?d$%tL^S#c_jeM_a{ps%VaL~Ms%JEose2jXw%P(94*0dOC;9?C)A_b_$fj(>qGr|$?W^#srca36R<%fndF zW-p?Cm@jVjQg0iNPgGnH1kK&%D}-j5^f2kRBOe-5i{srnI3-*!oc0|W5csvP?)jW1 z)arb}u&s&ZE53?w7>a}j{@yh|1(3~6e*v+NkPMl_{4kF;OIx{>Yhb@qe*8fh-3bnv zDVsrTkv(-}iBLFe*~~H7CvmKsWb<5T%f=tRH1BrJ$W>CN#R?$d>+u zZwjFM$m10}z!o6Bm-AN~V%6@KVfhX0mP3qnjQd9#S7P3H>k1x-oNa{fzn68I`CzJ| zAU5>1x7IYje65wIel;Bz#?sHor9?`N>6$#UlEnJb^;W75BvMd`(8s`p8?E^X?&spnM&Em;YJJHF_U#u%|1MP(sycDn$@bdtoi$vGMq@nIVucLGE&RHw zJ`T>ze*7)#WDOt+=6x|OZn^vN@@TsMm4^pLEZAthMHbw>hVA5xn!B%>rG3hM7gI7b zZfmojChM&~bVcK1V}jq7-UqQC_`wNbc^yyL=nT!sHnQn;p4xZ)J#*A{Q*3ytydn@} z71)CHaQIsCF$)uuyc$>j2|3x2?gHkvbHeD_n~)Xj-(Y`flM2o!w<#nzii?cgY%4ZE z4w;!nR+sx#Sh^J^|`qx}Op|I0SS-J|}TT;bWb*Nj#|*j2xL_QA(U zoV$UjBiq2sp;arPL>xIk5NVl~@`iy=6TR@=FZ{-J87EHRXPj;cU?u7|hI`R9wRRnU z)0Q#Nf@RvbscM)jS{&&v)*8JiE06hhpJ}sYIZONzaPBPta_+gUNt(OAxyDwnxDWkS z2&AIr7-g2+swE{Arkn7z!1K?4kk!!0D6d7A7Z8=$Wc(V}qaISyx(x*wKq?JqgKy!M zxdZ>^kY(Wimj%F-#VI7YsYGG!P2JChpLc09(mD&79{>rdNP6fg93>a5Ui#;3xKIo4yZXaX_kO@93<%5*)HK=NASH94rlkjm0o5by!OZ38vc|6o)I>`cr^etkXVSEQWMq z0lod}?n;JecGNIfM`npZ?$8aP2Rr5Vbnag^be7ya4##?~DIQ(d9_iDV*fb5GOz)fy zh(SKeoG;NRli?*TP%@nI)F2AodZsihh1*&_3=&C}IMGI|C7eB(Jx%LAWSecniGXmX zM<^NMB&b!mt?13d`TQnXa~=Hc+;uc^tv0NA4-S83D&AsXx`BbTZb-r_Csr7vYI2X$ zCJdbux5PS0y6QZ##8qIMmr?e9OyzH4pHV74u@a(*VJ&F^O0+!J>Z8H(({LXgDlC5$ zpaT(_8^aDZGodeJEU3HZ)C>qkVXqO@rhAGaWdb8WLi|fU(3fEU7gp5iwqilKAftgM zAnVJGBH$&|Iq3^GDcoV#>KYkaTNBeD_)(RlMA(y1ATD>)oyifamVbZ(y@p{B{9OGP za_sVDaK9-z(EoeGGqpd4#Y!zJjh3v>g1zvzvYi2jLqJN#N0?>xk4p)kMS_ zO_2P`JT1gCf);UqE9|d8g7Ff7A0V^5VKh0qEtwGGVm!Fug8LK7c=v5h=oxxI0;tGc z0vaciP`h);%CSaRh|zu`xeJ?5KGNLek0D;EXGBL$oI6jNDGHCs`wcu%r$+~#0l%Ij zq2%ufFD%WsYo>Q8lZ%u;m^|-<7k7_SbYNHHZ8-ACTPZ6+y!W(!Uz;yC`N)=1I25lnvPkJp7vfRxZ#7a0bWugbIN657#zVizN4mxbDd){ZckX(1 z#-NQzlSFy)k9~Ttcw%GigwgJwBUKY)`Dk2#*N12x8(nZV`ne0OL%vE&X7S5=v(h7h zS1M5b*)Jja1;G}>m!EhrP(s#~wO>ImdoHJX5%LQb&5(uZlP7vOU-G@N^-W%-o(-s6 zn4|&7x-Udw3vI>mT76MVK0WD~kLXR8QtGs`xtw+G;x>Jt;K!=;E8mSni})^QUvSyJ zrFkLkk|hQE`b$y=y;3cPuHmY9NMdH;1;8ge@wd3u$NIfU*D!i_bA~26eRP|5_R`5w3{?2> z8@SIA&!lOfC7B(aCw7M9#YU=s30NKU@rX0sG*Y` z3b*MPH1&wJrr@|6$eSVR_vby6*i*4@; zRn89LRuWbR1?#;F?WogUN^g0LmJN}k#CyCuH1xEjH$s6gRi2GWyRHFGAALQeMHrRM|4Hw?NmnVF!W= z(w?|+*fonkR|6x?I93{8pX!2j!bu5ZC=TrBJ6238MZxN~?7<95MyIl?3y1~k&z}8x ze$oT?2ch(pr60|4YTVS#dqhNp^*F44K%eII_uxpL&J45nb_`9$V#!yzNbB2{b~9{Qb)&IL@oKQRJ=?gA5YZ(SV; z5JJIgXYfWT=M9a{s{6YXl;C%t(A509RiSXwTqB(}xr^x^9*UJTWNhAR>90B#9p&Nw zhoY0r;R5F=d+XTXu?jzM92EICC*(DK)6>wXb;gPbE_I>fN2Nm7Kfn-T3M5_M6W( z)I-X&MHo-QI7auRBS6Pt0|_0uPm4^m?D&u^BG9y?sltvVvo7m6d#xba*P%7-3jekL z0qm~o{U2^={H*c4s#Za-cm3lN9F0})(e&rZPnXW?=J~}v0ak47ZhU<$SB0L_W$$`> ztg`bAO8DI8)Jex&8WVwJ5i!tU z+@UtbR$XKkqb`Hr;>Vg#5Yk+?s-8#pg1QvNN_JdQBWzpXHId2 zYlyb0%(Y~(l)74Hp&8c|U#%+^C7!`+sT*5u;88DLb*^T4-V1qlCKr%2DvTH>m)!v$ zhCoF}k))jf1di>Hq;~~#n9_j3X|ET>5}7$Gt#%Oa$<&)~#c~d#;=5?#1cJYg1uZ#c z;hbCkGba8!D4rL5qKwsyYP~-x)p`)NYfC{hXhZrW)CtGn891>N2#6k*`ipDe+nPTf z;;|st7&4#;iw~M=NygQLz;cY;7x})*^GfLIUw3y>nL!VE1ORtBC))wJqZ3vocY*7a zddr}rL+a(xv#f5iJZkd|Br6Nij3SYjTW9|!VR*|~CJmi57Q!8!7pS$D^KW?|g<-5Q zfpR!e$r(NQ*gH#XJ{G!wohofXab#hb4XW;(D4Lgxl1ky^<7ufsPht`(!TT-C(op5~ z))dSgDaTMu+_I0S3VG)kR!Z=fdyJO03z6PdXJzEHAk3m-@RXn73O77otsIV826Y^| zB>Len2=B_TA1Pj9GEjeXvyym=x+y0dr;UROf(1w|Z8W~dCD zKntFIS=Pf_>@5Zw#zf-X*AJ`0fso$@L*{F)nk3)}Z#Y9`p;AxBH}EKV%F@rg>X*LxT$Z%Ame3T_1|u zwLDWR=YwP$%#b_WXY7*c8D%lL9l#$aqseVLrX@fxnZ2y=27lOj*g?=DDFzW_2LqDE zq)y1{CP7G!%SJG9)#O(#lo=^8l9}xv4#cE$co9kEM0XTV;(Bfk&6e zU=b}F#pP#6yFst;6m+O6aNCbpcFGzYAQacQo(J085ANiH_NhNv$;5a~ z%dEes7+^CT?ByEq*iv{DQJHr;-1T(P*tO1>+J7-zkuax2x-tnH>k^f zVy6}_?WC;QcZ>U3M=|EMI-*nZ$E#r@Q3l%UqmgZ6t9#P}Fu(hfrgx~>23%c@_Uz-D zO0s%F1LGQtn0M#1J_W#uB2nPxpmY#oZncZi3dBXSU;#NtHrpKqV}kf z4_av#yyS1vgSdhCs@q7GS)-{r)Q*^tN z%Ieb)f*zTM@i)Pp$P~jf=^TnCES}|9?>E(_D^i&Nk(4xgga7=K%u;uv@@q8s!ijJzq9P znS3TOC;9^<>)}4n;1|p*;~Q*Tg$6W$x)H4^tSX^Oc9!55rmfI_T9>P+Wk;A z4_+iPR#`2ox$&O7a1c0U4czvvckw*xN_N8@Z<_Xi;v0F|G|4D zJWP9Jg9FO zsdXJisMN59Njjzu_CXt)7gVtq3B}R9Bd%qv0q-Nrfcu#O6%%mfI!=7sc({;gatQGq zcnn|i^EL)KuEIs1iS>QPf3C6PKO;Ky@`zlIG7XViC_*MI6>Lvw?G1-B>C|Jp}@Il;Bv9K zZ&aIZbEOhZ5zpi^vG7E;!`2z8Q@-eQ1qs8KDLyM3wIi}*85K~{ub=)(C%0dDTYe7k zllSBiyZ(b=v35?d+0`!k8mCYL-$vAmR%|Z~IrU)qopbWH$t)Mep_{)`GzL9=aEHhC z^u4Nh;mmbeKp=ufcDRXq$lkn6!ZGLaYkqifZT$JGGG${c-v3s)3Q-OGA1^XryUJ2Q z-!|qydXX0<&&Q$T?v6JU<61X+9qf_TA^gxzz)IM$qn|iB!|4|nqTH}PR{wdf^nH2B zc5~a-$IL>xzx132ogdwKlS5?A?RZi0gSg*epq)55Bi56Ae);uPVV#84-co2!5%Kat zcX>>uQjihOTvqQS27XQO8K?Nux~~e~be?A6_Uu(=7K>OQ%$E*^lA_|WYV8Dcx8Bq} zJrA5)4~qR5ua6n4V`u3fx^4=hEcni8qG%M|rk#k@%PcGAEV!vMBxlP#B5;ve_A*M2p)F~(<*udHnwb>~$8eOa zTUPW_KX9;X8sx2c1P9w!4-w<9n@8q=tLu$1&5 z%!2!ZPVW~(3w6laE)3@@*T1!tHDq{? zU2*Z#$c>@hnt17s`E>zpF?ey$r-GFhH`45KAUMx=Fs(KmT~U*l^ema{&YT1?cIsh= z*}ZF}p}ZalmdHE9+R3rf_afd5X-WLJuIHB38|Qg!hOx_pWs}fBXaXHqU0l$wHh5rV z5I{+I0o=G;QaVWoAQeG*rQEW4$+qiy@;kV*#{0YAtbVdS1rmpbwn9UJq3}@nj7Gn& z0CIx-o}jnSNE5FF>GkW1?VLJSH(+;Vp1yrs#mD)HRM`>C<<^`&7NTAcZ%wjoj^F%m z^Z4sjz+-GPy7<|f8x!Pd21$y4cnZ|KdgVDd?{sS#?|+n=xiS-2Q07GL)LshL!ZxlZ z1bA$nD=bm7N)z{Pbj&MR2l^6TDs3!yHNLG^#))2O4Zhw%6aGlZRph2@s@UqG_Q@5S zI5EXe1Mk>!p81QO96#+KLsJI*cVycp3$-~qRJMIznWDF>RdL`Li*4HK;nR1WKREmr zss(UTXYgB=REb6FfB(xaYR-ykK@0@5WaBHi6m(jw9&(%sE~NGRRS zP|_)#1A=t7)X*K$HO#!{cwP5%KkxHp<`3BC-upP#@mp(;f>#46&5Hcwn4~_VWCeYG z#VqF+t(db1!*y7~!9)ue+_|>m868y)CUSW^fJ2GG>0O9Ml0KbOi&jTPCFrtzzRlM; z+|ZjvyHlQ7vT)Vu5>H0bzqMlNCLOTiYSH@jHCR?wPcTN7N@EbbB_JlsD$yX`0UtEI z+~*0?D(k5Fe2PKDmGBQ{iqvGhoPIcc^ zfEXTu02(2Rv@XB_XS2Xi3>dV7r=E>1tT$vs4s54qKJI<9PSHZ_**($sUaT3o`DB*o zgTO&}X+J^WkkBu$SUHP-mveiXu6j@l>KMF zmcbn_5&DZnQ5Y(6Q6L+b3^cb_0(immrjJH(<{PhU6lnb+brGQ|&!$huG4XvNGYrc# zcO*p7fTxkRRr#x~uJwLJL@+<+Ja0Bfidn;NXGc9v%&OEF0;0Bt(@YoA=`&}sC5?<%r)|7-}k*fs@s;J z-(v&cF5OAulnwGqmP8B(C7i_ktA@Jmqh1_N)YmA4ET-d}f-gH?+&EzdMh0WQ-8Q8( z&LB?Ucu3(GjbIgVN+4jxt78U>2DS%{SSO1l#d+h-Z`O;t4%~MfIrkP#HKs7QOEZQP z()sx>4iOSnz}V%7%A|AF+z!leTT1L(soRDP^y#1lj?;sH-d4||eJ2vXO*JWvt2 zarT|VrrylAnLCd{2#@}l@ug3BtrlXLqVgK@SIz0ejT)LzgPGi(4Ppsruflu&NBt#@ zQtR?hx^dSJ_r?!&03$aivNTx({Rfpt&2yzGsw@)36O1 zOPF`+$LZsI(t4$oV+mFVXt8-DxoU6fmq`ZcrE!*VlD*FdX0l%QpT@FSX3ht=iUyZz zvyyt`EzSfedkZ`o-c*_ny9uaS?wQR`Gk%<2cML=ch@Z&HReqrtMoad2gP~WX#&8hicmR%IK~V+(r*aC!Z+gP!M-LsXB&36m@79w zlzE>Dng??y7HpuH?GRTFo#KQO6KFZ;X2QmY>QrX?2Yqs@X1*3vmHIY?ra=+S?9R&^Y0UJcDfQ3cJ6_<%9|+xekc4Fhd2|;(7U%|I%=R=$D^+A@G7lR9kVmva3Y<2FODqL9bS~U8+z3WX@a_w0z+PISylm$Xk8f5@96-b!J-PIsRd)t2-txU7c`ltFmTXz- z8r$RUlsaCol=Uv)4`d$<4 zU&IJm3Mm@ilQ5TW8=aU5i#Mx!U)<-sbUK11kF}pKDrU5OX`*rISZVAP?{kha@P~6| z4&QKX+F|l+fg~}e;-=1;rM1_O1n40TbmMPZ%PyGxt;Bv+-+uO9OSnCc#63?R^t7WH zdUTE)AMcpe)4Yxe!l*`Jje3P0?;X>UwlXI8o`;u&vTN8RT~#sosmbgX$~$7#W0)xg zHs%C+n{G3vfKnL#Ev95J3o|@P$$*dZ#i+@yYckJ9?n_A?D%?)ZmV#4VSggc8oBxGC zGGrjcvK&SlTcz)r4=^*hww2j_b$=`vM^4-k z8GJyXu>b_q9hkmwU+NOlSEJZQAwGR0(^LYX1Wn)M_tKp{N7wuK-_3JMcJ!PvA{~KF zwa2r3wQ;f)W__^H9z&6Q%piZ1m$mTjgCZqOTs#FxkUhVZJD_n^pMD6HQdLESeM)GL zEy~*my~EO$slr;M<1!#ORYoZ3pa{CIxC6TSaVjy78o@a0m)W4PG3LgeAXHfY+;Iux z$imDco@BW8o{L(k?J}dih`75w~JQ#Lh@NE7ga7lUSvo&;2 zP_e3)1#Gns4A+`l*a|b#-*CVsJJfElAuO8e&WvmF8J z+Qh<|tl$XtIY%841J9$aJKDXJ(4;bUA~X?IYo77k#Y*EayTPa|_W)Q;j3!!()lCl* z;lu7?@x?|I>{4nBB# zcpAz}#2NZQg9GbzWo0;HUn)m*c0QPYcQ6 zQ@VTbR~0mMJ=c!C@#Qn6>Jib1w5e130I{aH7408)!Y*%&s+T{9`njpy3VUMJxYwU_ zx}KXEV+)hJkywzeQ1rp|aV=JN8y z%_!dz(4(((fm>?P60#`f`Dpka&H|?`cyQ0!Aj$4r! zDdw9mCQ`PmKg-cY)=^s6If5K1vD9uvE=0%C1JQ=#vUBVQ4KAM;ZZ|T1+za$bIQYq_ zJN_b^OrJykaZREYS(o_p=QKOq`gF#g%cL&T*?i@CB^5FH-j<`AQ`y(~DC7tHSOrXA z7`4h08?~y)(J})!ez=DClunI0gPRkj=%?1={v4U51Vu7Uv3^&Uv)mEZfx$N5SOhC3 zl!DsGHbf>@Mtl&TNgB$A)NgnqT6ImI{5uZkPI}Q?yqS$_Dl_fBJ^S7ne5~SkKY>Ega2_F_Fug zgo>DyBlqd*zR8C$*@$GTrjQN3tcqm<$u^oif7LMG(nOYZkvCP)31jj@*zk=s{=0Y^Wp9Y39+sq4V6TQ4#pF+0}0%yBALN%h# zT=D2e(GR8`h>~UBO-hqAYCKycGVHgdRVOa=qtWd9|E-?jQ>MT~ltdJ?mKO>x_DmL7 zA9km{DVgGOH;0Kp%J%}FRbz3hJZkgh)cMuTUF3(m-JHcKxnp~PMevt>%N63hafg}1 zJVmdfhq>uSMb62?{+B1PwIW&TmDkh4PU=xz#!9IGhVPP#Lu#~&F)ncp0HQx#nC~$| zkoa2UtJeVv-o?afrz%Wdl1YW;D1pzpQNcqH2}HRQ*w2q@TNCOqhl*OES}**$=3in&bU?_v1j_v`yS#Ji=HbXJ-Dx}9dweE@O*Mbkb!1_<~8m8fvzM4l> zxNc0l2mt)r>A`jcwo|w7>YrBxo97fu>M@TK>a^d+}~nB@b+t?ZtYhwNF|V27e1&?YM@W zj9!-uxsJs+KJKgR1Kze#(iW==s=S1%M`xyu1w$1C=%RvU4{YyoT40-~#60ixwxOrQ z!;x7On!z|knJIA%m#P#?t#oeZERXxkc4=Lc0A$YwJ8VGPeu^GU@r0%y_XVmXs>M4yUd%moIm!839`UX;U#!4B z$d@#XQS>IaRB1e8ob*Y`Y0AbW8}fb3*aELDTLeb*d5->IW2TYZCdzzIQKi!tZQz5- zhT*P^7x?XDIgjRP+l?&Uu1?F2hF&)3eS{WAk=B=B!ThosdZ}k6*bCB*0?rg&rrC`I zGkq3X-x7l2Nbqt=l`t~0HD05mOU$P}OI~C*w)5H_ELT6X|29#`tw7$t<41io#>I$AfV0??>vFV>7vnXsv(Ak`&0M zvr9phObsE%#hektA5V6iw7c`W31`dmH z0DU^xFqg;kJ$5D=;W?nHZ@cy>6plVe9o{>KV2#zfN>EAop7=Sv_q&&~H((6$A$}ij zdU;@AX`4b|XdJh=;Z0Tq%?nAm{MMM*su5kb$17g!?nHg-0uLrnk;tkwQhwI`$;HY+ zVoT8YnpvHFs=hg~CbGX#=2mB8?H;{a8U74~^V9PywqV z2Y;OF^$*vdQse`~J|Y-+@3Gb4^@@bOsN?84H<#bFbGNH>hKEd2THFoeB(5`>Fx%W( z^w@9FjL>l7LNZ6tT7*}Ctb6q&v6BRXoy4v`Eq|nzj?8rkS}Zt-LfHkNelq37?RV*} zp-Iq(4y~H+437rNMT51;nO?(i<8NM2*Sj`~zapX_;T{_$EKjFkmD!8E1nW6hAU-cY zxvf4L``Y^Ky+d}Nmto!WkjQvrpi9s`fi_L++^FAZ-sm5x)k9_w^W_Lnfc~ERiY-#S z8|FO`R|?m@l(o1>C7b?dR@{t zt!)Z@v^SdaV3#s+x*bvV-2JEB4TI7LjaT=I!(mOwB7rfj#kH3Mmv02o!NQ3c4SkQc zikE2*j!?XbMO)rrTYRB3eDvlkdUR(CrgRUJ$)ixs1@vBB_;!{FX1mmA4yM(}mRYuH zBBgKB&~R7jNqHk-p!JWt2DyDSc3^x9o~u|MEa0N zEeEXZ&iDkhX1oSkLuBP5G>_=cgQneu{qM@)#+M+_mj}ca%VDu$iyOrBbUjO~_x)D> zozP82y^sVx<`0P+5i1cs?_X(eT0%6wZHbykto3qjhiX+7v3y3Gd$p(HB*M%DZH#iJ z^Owr(KPD(ZbcrSveoa*(Xx`mJ3?pKWcKS1UZ(^&2Qe`|-esA&B3}F)tDLiYCF{YP; zCBC{<^%ineM(4Y&FjOi8V5Q+HVCk}o);#L$Gzrs;)Gn2a3Z))PqWT@?sL+$t5mHG$ z2BYU?)BgF=t6g})jru`EE#Yav7sua@j{~ry3)Hk%(pP-U(>L^jkRasQ#>dh|oJLnkzqUuL*ozW94JAa?opA-Rh=)bl|7o}Hp#R-a zWr!nMjZ27Q1YDpYa}=^NaSycvcx62xYWX5cZ_IxPbK!fc^=PI@->W^Z%>|FMC$oG0 z7Vix5!0T;7Cz1I{em5j8TXs%3Et&d_uKZhN4iWrYol|Ecn%Eod@Ff5bU%_%OiDf<6B_5c1B?6xEvZMJW}s5iIgUQ}N8vlGUKv_LF6g!Kzy z{6-I_o$GpRp5=g|lFmFnj8U~Iz-HC^hax@4uJgj92eYo(Pavg=?K|pjuc*|xKIQNu zT5cKxTCXBM$F5c5^#Z^3Hb^^&xjTnR8qIU?PuG&7$GmFehmS%FRUz-i{eC%2rUSzQ z2)xwvGQBobtE}#ls|^)K@*|>Js+Uo2?)#Q>9|jtfRbTMWouC~~mSU5ORTQa!H3-MW zjGxhxYExfzO2nJ7U~{o;{)_rmBaK`6-Q3E7*TlgC)q%{q!J1kdOVGsxt(#v~1R?~2 z`kJxh8WP7j?5^>SprU@;PFIW9|Q7bI|;i2kIEvfk0n7;|F z1L!QqqCq%_;++tLP)8*&orTySN*}7=WA;-B-J11I(?3RnHrIIa6-}WDv)%ZnQJLw( z3D@|R1wHLF6?xc2g|AO!Ji^U(Th|L1vON!vmoSvY!L~=q z9a0P9aJ4VcDH87+Ykwfvo9YZ3&#Fmdiv4kVgwx}4={Ok0|Jv=SArOr_zIV<;u4p*+ zIYyE7ZfWKVt=BkqDM;qeaC?!9yC{L!{;4{yPX}P-&!2GC%;6J64aXd46ENm}ZNEMD z0wo^g96YRhyOa@g>uwpkk>$D3d%4~PKle{6^Zu=Yb#{vL%@-o4+e`P|wFDgi^a1$9O%W)c$>rJ~`))s6z=sp_zUsb#O zXb^vC=GNB@P2kPk>%rds;5x&D7~F=@^1zcDQDzb}A_S z{BG|n-r=w=#%frX3tL5#&A26+`+GOy;89-CV3e}^ z>;FCJ3hYoYb1yCBopvRqTdbcJ`+?~n>oBcS)}eq4Xi&?rQT$N!f@6cQZ}IzsQp_n6 zGZ?j;HM6llY36J!m@IA{XbT7$SY9^5Vu|xuhb)|gCPK7oSAerh8{=~ZXx~n(?+k-p zlU+PH8dj*3K=(fVJcYD=&JeU!ZE-0Cmt$*?3WX1pI*YZ(cyWb)h@iQVJ6dn}B`wXU zHXp{aEE2O$C9)}>peWS4Ye%jJw~qxczAVdOAgFl>W-3qzDvQ3>X=C2y8Nl;3wGj6U zI?Jkczag$t58=&NoJ2I&UP`En@BQh{lLdT7)+fE{8#{0Qqcb7|VADDUcTi+$p&f;= zh3EHz`QWrz>3j(FbB;w`MC&J^=!G!6){_o3zkL2z@mg7hs=C{&VgcqBeGDB0LqG05 z;aql4X7Y@Ev&yW?Mt~i3hZC8_8r=q6*kIr7lU?E9rh<(~|dx#Sm{9GqGu(*ZPUFFdA+MS(;QIqN8=&?yj6Y3d8)#!|I={gmJ-K7A*+#XeZ&E@IfiGIGM&0gjCqlxg~3X;Ng z6DKAJ3AO6vM=zyhR@#+QeESeGZ`pVYdmpJ3;XHk(>1(S5PP?4>aBNzA4rrQPa6Cu1 zXKgP}?}|C16m$tc{Uk^Nf6Vj*q(!l$4lLfsjs@fNcMIeDpr!YS6Yo3I6=@+GHL3MT${`cq71p+YIRHCo>2(Bu-gBYhrpw8FoKQf|HzSGRP086xKYPCy}dtd+%!h) z___R%s??aMxNEu)))9rx3iC@G3)gIL%~CmI(yf0O`A*Q=&8|PC9p9}*REvU1AEdWa zwJ#Wy>gx+dVskAc7^Jywd8mCNER*n(7r@3wks5IC3v|*Qi z?YM&RMfhfee+H{+&t-EtLw-%uWT0|osNvY>BRv!;9C52I~ zsE%qkshdWRW&cI}OR6>QTLWT8adrLVjaAmL60?m|SndvGyRflikmy#NUpUU4TQcU2 z?>+hs8QsIim?++bppNA&5uL)qMW|yk0IkYwmE~gO^8@;Ws)W-f#-=Y5z%IY4p#k>|0ZaLZgr~E*6-jL`N z*Dad`WW&STrPv|)-C_6`>vd>y6Ygt{^%67i zEBITJ-xul+F*U7PK)eWBe(A;C`v*0K%I*@%F(rruO4T}xc3s7Tld$5yNjrJKvGg?XW0k&I(N$Gh8+ zSqfdiE&4yWXy4$YRC3&>R*%d`m&Z^HJtJQ|*a->KNe<9kq8zw!YGqREpgd>DGVh3H z3`>Thq+BOQP`{{aMLD;Q^w#S*&*%}3i+I=~j%AYooC=0#KvlpiNwqumF)vVXP%2;6 z57uqO_3b;)0_y7cAkax@3}p3mzwjGO;~Xq8nf&Om$+Pe4yVBAg$A$-LvG#1i5z zC$mP|*gvY}ea3}%glzX1-RbI@C9M-;ErZNR(C>E0I4b+u$m^^K!KjA{<)?HH5dqct zdK)a#cb2N|eJ{wv6%>hf5k%A00|Cd$ci@+Fj7fkxe_HuBg(KEh_CG*C9WPr?2p|}I zxTmrzNe=!Nw-36(7$Ey`XFNhXgJ=`GhYk@~${uNS(<)uZkk;>we`XyQ7Ck=SUD49s z*{<7cA7zX8PUQ*+uicK*f&h0!io~rnS$py7No!jlzDM1!A-BgW14P8w?W2+scoWaU z0=8w3b^oG<8$3Non%f4TIb6rZFD%lNw#u9nbJo-&bP}C)u(T=(TMd4z;kwcM%E3pA z4#-he>xf4+HH|24DrOq`*l|Da>zYB{tW1)x7eq~qVtdWYPQpyNDsTf|mqHLh1H|}n&sedA4 zq2-s^vyl*&KjMO)bdNp>We?|SNJ;zx-t+)Q!bU_aHrh4^W(%pW1x&zmL=>CC3Xfie zzdh#1^K4%<3^VqdJSA#8-gfT>UT-AFp9~pn9Aa-((5~1ec}NssaS0}EYx=N0Dsn_^ zH@D(OJl!QT*u(dfIfKXf=}DGsmDALSt-G2`#4jRq`6~7`nHL5ge`GK6^johi%%KJW zn|LlmOWt!?-OmJ|ZmyhJED}0SPF;EBgq7{qN6)s#*bt*4Cr=kNj0PpN#Z|YhZ#@g! z;ytJI2Fv0#zJ7x>Z$bHX$rR{L`<{kdT`j%89fJS7?%4*eECdjja(r(LC>cDzjTeNa z*CZq`^M03HrN~`_Z0=i4PJfvmMcK|=f1uMMNPzc^iufMXsQIl=brWzHEQOy@ z;KnDwlcvJAfh6sgM|$~cc|!^p`xh(n4BZOQn|Ka#*-uW)fVsWgUqh^E?_seNWJ*A@ z|Df64@BC>^?LQ|H=4(poG6a{1JkTE~`f5C=wGcg#cz|DjcRnzqu21JNw|pC=_-ml} zy7AifOusguWJ)4d{HwaL|JHHS40SD>OJ13bQuvx+vZj>xmQX-69hIl_8y0w=bERq3 zPp+~v6RJ2VXH%$Be>N|U9P->BdbaNHzkWgQkyH1HrU&<3&r>W(p^?>Mk!4khQoK&p zpwb-yr(XjoTi8X+^oz}VZl8r5x0P-UBR0jh;-c64V>(bvQCcN+%4^X#F(h-Dq++SI zBwI@QzvJ)CZ;8*z=`m5#+{B!&%LHS3$*-_>EAZ@6)gyap79itE3)zD*i0!N-i z98qP9$%ui67^IVz_O>{zj8!9jH>tw7F+lLT4JWh;^gzY)0Qm)?DESM>Ty6}uFw>sL z`r{eVU2;(nJ&BfH<8+bY)18~>>6-+uS0H3@4qlJ1i*$S-l>II8)-MB83dgc2JGG3G zD8V{Lm# zunzQHY)#`7IonMsNtEB6*dXGi z_=8L86pzY*wzB=|44{czL$$~Z^91(@WtA59sco7?cNd=kG-86U;Yo>O>O}u|s!0S@ zCB#&xF~EP9($1v!_fp$yjE$o>I#H8~-kJdd|21l7!bI3l(1-9pElM-RfOu)Vrq*YP z7IM<1$?5j}TEFLSkKP7otxCV$1_*P+o`K)hr%!o4Sv=Hv00VD6S+&+ITaj)wPY|>p zw;arjz4erxx#tk+CnWDrqLR@<@Z2qgAR+-k>^wpPX+f#P7Ivxm-C)*dgGkTATP#E9 zKOiU((>H3XNe}I8pL8w}OS4dq2gnu+K7jY6ZWmv&e;?)|PTkq!$SS|+vo_NCM)^=B zXU_d2juu}TGPL)Cufh6~HBEL{)Tex&rYer?+xBZ|iGV&8vS5s`Z~1e#2G*>g$>bhO zY%#Tyj!%OoaHx452R#jhL3}W1l%mbYOSlj0r_A%IaWF7**fuR=sByT&&$y@!XhOEl zfXijH&qhQiTleqop!+zAIyJPbFqo7%aIq^I8~N_0s66r=5RWBXxc2$pLA-PvhOyZ{GLlBJ{e}2hH|E&+_SlgX8SO z$!wkD#j(g)49#BAKO@1g+KW}4Se>RWI7Z{-0ITZ1r%@&V(}vfAb5A~{xvdGxbzB6} zz1jn84Mi)w?1?|lqu@IFQkgY-bAQ{1WD3dqWSbKvEiXD_INf&(9ga`D7cGnXI@HX# z7HXI0N?19Kk*9D+@?y~07Rp3ttio39ln(YAb=w?V!$-V(_?T>yxL^DynRCk@RRRgmHQ+=z<$f^wbH7PJeVz;(5Xwt_j`clz`5Nw?b zLUGxcv6Eta`}bqtUgG5yVIof|GtG(=*-!qOy8U~-CB?m%_t|of}#I=s&=S*=7!nI2T^V( z$Mj_FYjq2QY?U3YDi5y(GE};Q!n&IddzWaEQ$?^;Z$8=6SX(eg0EQnK{w&WR8LzH| z+fTnU;dXNh@2ximGx617<}b8>?ZTRG%{|&?={jwwYtn&i7yZ~8(Y6jcH)g;420nKx z5Bi|pI8j{ks7WQGgvRb^DlzPArPHd&{0ouj9f#^F*FklCKo1iz)LrF0`XKI|VI=3X zTx5i4npu|imZH5;O!Z@Mb5^PPJie~nyJ(r@|Ke;MmvFyPL!?l|Wo5L?hrs*!MqK0GxOXM;{*u{^U{Q-_iY) zy_6_LNQtmSJ|>?5);S0m3Lz&*kXQCEv%)>7{J)9DupdF zu5x-l6OR>?OqsJ<>cF4D93gHXauOV;YskJM!$OglB$HG%X!*a*tp%6}H(~s#9L; z^o&MzOKBBgQh_*H?_M-AAaPUehqZey0FseF;%- zHFUx*f5g>I>$_yGQ)h+k_=CFC4}j0RM`bs(hxu%yGuc9EB3*jI(D>v+aMASt<(1{zQIGbnP1wO&)q7#7 zVxD*Gp#2PbI^iD(279Tc51|gvo62{2l7dyvX#FSmmJZ|705&N#3|JQtg8ZXY-KT=W zCOMX#(v+eo4AiOq7>9P&Pwfh8rnKrY>(P}uN$7tP3j~dFqk3)n8hh_Bj-C`;UN zLbcPhQT^TF+VckSQIp}Z)>RzHUpoO2Akecmtq`|F;ciLc~i_jJgloOL!yhk45M;Tjg&{Y zD(EU6X;>cP9*un`#lj!D5`M>GbDfayQRGy;Enp~y7;6pQ>eL>Kx=+^^A$QHD@xfYT zKi~P-ZTddQy~i~gmTmhs6gnmQ&Jr%+x%xFF7;2h0a4kd<;h7jT>$VWFB zUupKsj&jRPD*1uHP+TvT={Ah!`Dny!JjY#{$LyGN+xch|!5MPQI)s!%+0U2ZX;aK; z>6>CXrs_4W)7R=0j{&>Zan@*3mz+GR?jz0ZgXs4=YGLMI63pY~AP!y4drc*NL1~|? zpK?pQFG9hVIGaR4*~MqB6)`ZwF?$)>*1(5$cGz}VrOV2=wXvFXQH{g$8DsV?McCqh z6Jm6Am4LVJe3wa)${Io0M?QJB!?;|+kw_pS#<~2gtW%1(0q4_R8Hi}{LP_<{D!Tn& z+hG}8tS#==*Y5^6?&Xud2fx9*WZuWZIz}RZWTets`KHmrGsqpL!BYCbrP@oy(39CP zGXs~SV_Zns2Nh?3K(O4AIH>-jo4FS1>7bI+sjG2wwR?ps-UB#kn$WU`o8d`k$5 zRcz(^_{-Z8gpM!ecUjyuJ01vzp?Y-pViIL30&EcVg)DlmClIPb4ter8oZ##3ykknP9HOCr7{6q{>7| zPS(#!jMkk}lVUm-#wWzu70&X08;QT2CM;L}*KRO>s+9r%VU^;J95x(wFw!V!;coXx zl&pls5{^2VY)LPdtv#(u(_N^NwyOlE^$~K~_Bq%X3Ik?8gGw^^R#CnwmTRjAT(2(n zsB>u*^~EDD&L6)!lYEAiqG=@cH1=;Lej<-U z%G|tOFlvSm*K=4wA2h5&&}80{18A%f)lL)ZPU@I)*hTYk+|&&vII8chEq7SXEx8B} zuOm?(K@+uIH}*c)%kWzd^nt9%_90ELnLYcIcWh0*czs@3jZl4l)kI7iRLmv6*0eg9 zJ0K4o^-Z=I?DOHVq{Fw^!aml(Jbx?~9W~^JE|Q&pDTTR}3+rOJP@(&hZNcKWxmqZ# zyUPC)pqd>SWmb&rCe!AFO3`#5fvr)d&@*8nk*N0X0OM;9?jX|mI;14*7o~(*RH8Y( z8fpj6*FR>2f^y?Ua}?oeF}`Rymjd?HUltX>T$ut6234X{j`JQxWw*^W1EK>wb&)ge ziG2;}l}|!x^}NcBLA5{<7MDT&T{%2zL7gb;*Yot%gyWt8ap$Oq;84L31zR%4YD;(y zXY-`AW1d}PuiWXKfcX7t)@{ace9XikeEK(X&O-`E!NL!LEECSaBO9FVm&+8cWb+n2 z0Fr(29bGAHT@kW?v%G}p2kUJR1OY9V4Qz2CloBxqlOKV+RV&$s*uZ~mg9BR5A^+oS zJgK%iRpnw*RkKIcCv9vd5V6?nUer(;k_Eht4R9Lk(>{jm!kw1p=vy>!8l#%L!&MaU z3@C4ZO1VZyull>X4dQ``Z;|&+ntEr|4ypnyJD2q|-QzkPG{w4=>~sO}at4^9n$PDV zQ0FZPv3U?v-BLn??J~OmQiHEC6J&F1UN{+J+Y#9hLp?-3s7eN*1l}yHibOHd5T5=) zvC3H&mPn5Zp1RH&HF=is{*m-hbufSGo=Ha_Sy!OtUzIS{`}zBY(!yYY%aAbl^!>*I zn`^L<@huU|fV8mGHV^U5&c>u%AwA8H8-zc3qK}9YU!Iw##f|X=y|P)lyV5yvPcv9q%Hx_vK<5@R*0}KE6vH>X~ z^8YgBx+?|dHvNL4jL#})-RFyAV3u6XJ@A)Lgy5^oRjADL1OF!B^NNulopUZOE6|9hHo;OcGOq8p0cWpW=lbTl+*}^( z;b2?xbfEA`T0S~|hM!5si&ayVTR-;eIuD=nTv-BBxKTW8lp@}-FiyZe6`4&mJO`8z zSyfvev3?%!_&JHkUldbvmT!_+J>^6#8OAB@>9IxALwgxL0uyHop7xKM1ej{R&>)g!XE)&1gk5kFmnw2mc*CVj% z$Q!3U@pV*JfRT>4a7h%)f57o_YApA#YJqDuqgdU{u;Q-Iw?O>_e58IuOs;>URF1Q)r5YBV_t^ zP|4VJGJho=H#+-V-BgNj6fIJy-E#B4SZ#Z$yw%MJxD+`N$F-YE1o5sm?2q*^I0?2r zN5*a!Y;>QGH0&*#^O}w@*cY+%utQFcz`OVd7ETe4k-0U;^9ctd4;9 zzSrqAvn<522j~WzF(lY0VFP3DOKtozZLmxH=V+&z0X-t@%>7{j){M}M(KZbVN~8l(Lwgd-{GbnAie~UpV=mA^`BG7yUzwRp zVvwK}6@+6+U>2dLqSK9{Xa&B`!H(}0>gtD)l$bcVte{KZECyxxnOM_%u%o3mioCLT zGC5Gzhm|t^d@Bt{ezVB6sjm=lA)-=XPKp5k zvc>WL`Veho?dPX6gNx(j{D$)V8P(siz6gO_b;8PHl~v#SXDvN9s;g}?NUk#<051&p z_8r#pv-}rv#QMtytGJS9LqGCusF;C&MC9KYBiG~FZI@_{Q7+mnEjj}jX_ehUzYxJ~ zU|mgKaDGP(Y2Rd)DYUJt>RBWY+dY_^s6mqjkB- zNJR|k(NTo!Rz+5AWE!6pKUgtupA%yA$WP-I21PYk$B7wTM(} z9bzs$0^2;oDN#_v*g`_fh{w6KzF?d5yqiQBxbj~>&l;TyOzBM70XpQH5Xgxpe-l>d zONr5BN0XSO<_U$>M7(#YogvC>;6TGN2UO?v+(EX81n|}ZK7;M&BxhU_6I?a4>LG~H z)u{&okB_w2;LfD?Y*i!R+O`q0;b{4QJ@`W+&Z9cIZK->LJ6HkohTpJP{?iM0ao(@I zpWk{GTGu9zZ|0c+O%c*XL(qm@M`RO-vt;?z!AlW2;rBz!rI~@4D-P-nL@kewpF!%Y5?s{|Zr5ZLmUC+m zGUtdjMC#d-cL(Wbi#B=(Q#8GamJZdh<{Ee7XY>3>X?F3jUgkZTAa*)-Uf{DPBJx)i zsB!2|t#G+9o2}s}W5d&D8oTJ!=L)fPQ!eRHHxzDb%+X;U?G6aDZKrb1^3v(|3)=Yq z`U#aK`QOnAqi?>88lD?Uxk$5c!VjzQ%E+!`z8O?m`Z(t>U+vIzwLk|ThW|z+m}~Q* zI{K%zZ)WB91XCBGUpbs#Kh_;1_L2Kw#=SqWn~RrLA;orU4w|F4iIT2TT51*-Qtc}R zxk8J45ElE&YXn|VitFS$Llsb#e_q0n<{9`~Qov8RV4VMmK9}ClYP`!3SrAmfAy8~1 zoE0prbvMHYjLCRdeu0nKF5s{T!S*rhDUK8#w`0!Ht`kM{UXqLck{>}MC6;kQO; zr|Sbd*#CvMy>}d=j!V+k;Mp%M<&LDohFio+@<|U3R6`n6&drUTi=zMY^{L2pMwf+Y_Qsi(7eE`UAIbOhj4Q#*< zU#<8~lpm`8AJ^fh%TnmK%#^uSBEWXI;Z6b3KVXY}5Sw>1D0Jm+B;;SZI2njn)lWPx zYd3uA8nQ_&-8V{mT5vA0=t5oN(YY?;)H20iibxqXKaXU@!Z_0nSi*oRga>=CDi3r12dtXQH%8B&D{ud&~8nCXGq(DQz4y2Q`oSYu8g6%e@O!FTc*Ro~jR6T+RfEm!`*|Y@N*5Nk@G#Sd z(E4uoIipI)=>Th;=KZHXG}q~|Xb@&3?DY%x7BR!E_P%$XGu<59 z%g`iRiBP{@Rc5f?-k3OQy6pDt)K*qEv9Td#@W^&ABW-mv*^~T`<3QE ziin%^i7$-cud;X+D3$l-yJo-A%9WN|R3Hja{)OZ5tJUXOCc4-jJ!=L>XYYG?M{hHY zse47ozfL7Lt%6aM@dx)qFn*~4Ul!z^ z;oAS}uy)$Dp3Nj73qnS{1BdRH!ncT01d*d+730L{pt$q)xb%`4ui-RI zz7cJ^4A6H0g(FUZXx`a!4+PYzKz=vnl}-zy+G^sdl$q=3AZ7FJH$ep05M8#VEJ_kR zy*ej`oG9##rzHER0hU)+AGG3fsntq9b8B8G{cJ#BFovIlc4Gd@+~n{a zuA$1b^JD$jOQz4|Es)Dkd|!jNg#zPGf(lZz)vUSg^R%J;Oe`)zcvc&w;tHBz1A9uW zC+@}BFLUglHtG|M1wN;j6mXj69Tl(L7GfRTJ>1x@|zlS ziM?iRLL!Atk_fMn^FPnSUL;f{mr#f+_J7S{anr=RchkSVT`Gh|5Hw)|ms>44O!VMm zw`I>@zVJr)(B*$El8YeoXP{V?+wf<5sxK3<*r0Q&4{=s!!*0o8gxfK2^JwLHzVKGb z<5${b;{fl3KycvRsk`x~82^BOCb-yu$)Vn>1wXpZvq?t;rb&k(mPc{Q%x zrJjcitzm?pVav&>G^LRZ)170pCr_0c55i5V7me`Z3{ZD9O3>zhMt33n{OYg|eOYl# zE^CVVs#ATTnLK(2q5S#(w~In>Eh$Tla4obHH7AU*2X%*WhfR8g4kh%9bV#`G=lvT! zW+cXc(`vu?k-4eMvBK)n)f`?pmNHa`YlyeobnV{Vryt(4X^hF3?NGXLA)DsZhgWz{ zk1pTOzYsJ5UQLMDA)EYu!(cNQXE?IlAf-2Vgy=2)nt4T!8Pct8WR)~di)KN4@L9mJ zgSTS~?Vq(hn$*6_pn|Bp;jSycij*zKqfY!c*g&3*&cOhd@eX>`KOQ`t`#z3$!J3FU zKzY@96U3ZhuUTHn3aEx4AL1lpbXsrv^$7j}j+;N-`Q;paX_Nz@)!qV{B+K4ua-oP- zao~szWNbM?U-GC5q}0$Ox-|Os6lLe`vjTpZb-n-b_jDv+%L%MWWb)tq8lod~nn-}o z4e#KusZ2#X1)rR+L%k!gX}R&Kmu4v%Oic>3`oHsoSr)%7gG+>7C2cR!@K&5dfO@Eo zGVA-II_3Y2hk|m6w_iqS)jJamjd2s5+*3Wm$@Ba;3m)Q($B+HRj<#-$TCAI)m1S#h zA^8Oa6hn`y23y}CT)iN|SJM@q3%mA53PGcr`Cfb5cO>1-HjE^+$aXj5O^@A(wIZkA z5gD{?@pGGJ2r8vA8I^nyZVzgTVsi;!C7P5Jsu)a8NsMKp&mI+|zStw+XNd0~z^jkq zJi-ASKvDZ_amQk(*`9~gX&zCXlK)dEuMvX@i*AyJ0qUV zlixPHbhp~)!kwO2oc;s<*SLtlJCSCEHd2dxo@k2U^=VGJg%7QJBROgr|0&idmLcrC z=RncrG%qzIYQ&9Mdi(l=v%a)oEC!_`r+&jAaU&@jr+J_H_hNd-1ar%ixX<+LGceDy zoX*aT>#Fm>&@RSiiS9oI9_Iy}0(NJUe{$T{Zkx-jEuqONpU9m2$+-=g+_8Z#Va(8v zB}(_Wk{62i9FiBpn>^oB9uV7SnI9QeG@J<~*3Pb)-=Tv>V52v5Cq><^Xua9)T<>>kx-pK6Wq1*}kdymQ31NOWb$hZFAi zQO$jnDw(sJ>unr$4@qbiki2Eht+Ay7)Z0((dQU-8KNad)oehY1yQvO%mp-3KcVSf>Flx@rqV@(crz>XgM<^0RxElp6etCdgW$X z#^6x&gF-QB<;bxvYUd>uNhHxYG2>04l|2#idkg$`={R93%c5mp8Cr7 z+wDrMgWqp;uz|~cm9CdbY_|X{)GD~zok9~h^+}0J@y^#)#~(IBqyo*s`vogsc)0!GKkgOn!7Ps>z|f*HXZE4wv3w>~QYF-n3(_8Qx&|NLidzu9k6VmC%nv6}eAx)#Q{KN93om|2eKBDhW{3}@Jm7jVs3KV;{w zcvDpBEV1NpIfeY&Ut1kCf85jw8FI_gaX+xthvAr?NGQ!h$fkOq);cz#bCybc1`{q@ zZ`?-;lV6SUA242}uHGnqyI+~o_6HC)R|frN)xt?Dw)Wu$;VNcRmw3wax8;v-(R3FILD4#Yur_MlaIhu z@9dhuvyX^pL&5LAG~*5)C;#p>#~mr@SVrmfX6awXd3hs8v~}M{T{|F znjT#*{HvNm^;Akm_=m4M4)#;SqQ_9SdRqv8lHjTE%zJB}^YiJ~w&$~@DpDse{HHI+ zKS~BS=`X0z7g$>xPR{J+@EdV&6Fn}*LAlUvY(jk32Bq`KCQJ0s;}&1%grs7;)P*2go>|7GL8+$VylS9IV66zRs>`Uy5+wOm&EIp$D=JH!j-G9pq#vsLEti z$lxY|<){>{eZinX%}(GT4&?e(#N(b1=xgcz*`2h=I7iyPh<++G&HW>_hUAvSWA?npX z87f={1DYTF32fA_1}3=d(gP$`3{Z%U;}j!|M(4SAmig0p7L_-C!?7FZPH&H z)QF9pYK)JR=ez-J+wt-N@Zt3#s3gx%pn;Lnxmja@-_Ii5nMWW6Jf^_uIplIqG%UU* z=B<>;+aZF|<}a9bz6G2o>`Fx;R8-<4=Q)>3RnTI)KARM-leWFn@#{PaJb3Mr-=CkW zsFf1-vQ}i_>}8$bo`5kgz)aa-^6$GhmLZo`daSSkgBH~-tJAYk?+pw{GlzH16<^40 zv+8~b!2_X(UCqA5GHW^2uoUdP8Cc+IJ7tqGeLb>3^1^fsxMdd=xGs@^!U5Qx{UWGo z@`6#p#rv&Z_!SWu6(O2E8V?F)SgiDBXxL%}nJ$_kvMS6bZX=#sfUd45{01!n@pX`c zb?J%BYSmHo_|r~`neJV>}66CsL=4hJ0MGg|;77C;@BM)Wu8FSR4G<~*Fi zoWTuHlso_HJwpXqQN^iSJ>!S$0%U&u{Lx{f%Uoes^=#4WYu=}7cZ<Xrr~T2_yc}{!@nbHN)G76lYFC7&!E_J-#>qMO& zy8VOA0S^m8J=f9T?^n9Z=M*p!`5lI6Z6|rd-k(o}avx9fdi@MpFhY z_$0>i;)4PrnvAGx=Nlx@Flg{S@Z<15#pzK*cIYg~Xogl=Vz*$a7A_Q|O@`iJw`?(( zYn#Fb+l0mAUxQ75ZzgHO9|nF2?4O5bU+yz+WrA73_mm>35gDMC5xGM@xo$%oWPCj; z&g6s6@yz93&M;*Dv4M#h1K9n%>W;?f8FV*c#nm>L$X1oMUed8DXmFdh#c^>U5Em0U zo@CXz!;*KV&nm>&=o5;R&RD1U&G1Gc)mYg3tu5;CGh%ot0(H*4)_xqjWZfia6x^&Y zX2`?zCdwMqc+xlmPPAHmS9~s7OYY2%0?QkRPYgeSyF9sqPT%`ZX{jI>m)cN_?st);toOlMQKkc*JZxhb+%31zD=Fr^uV! z_EtnhKvjht1uYbC9&UqiJ&dwT0hRik?^3XOXq;WZl{LJ|J>a(jO+>SXMTlsE|3o;1 zoB)>pb_^J#u_<1CsY_uZyFHvK9uyCe10CLP*e&kec)~h3$EW&s)22Q{m^eISC9Ylp zO-XmP&u;PrHL3p#rxxA5tDR6fHxJ%T1NX8X*-PvseAST2SsZ#V&^SV5x%ag!T27GL%KhsR`A?iHf>DtUFX#s5WLytu3UUY1-D)USywc))3n87f#r z$CdN#{-w8iVbqZ>UfQo7lL3!6Uff=2c)W2pA~8)@TVj_Q4|>$f|G(5{U7oe9G6Li8 zSgR4D1lD~!EpP9AT`e5HSW23Fb-m%KJVwCd(d)sgZ{MHF}95KXbc_g zzMYxrk>Y%U9&uwxp=eI@r0$CJo>v)3X&-e>OF^O?UYpmT3Uoy0CSBU4x;VOt@ODve-^ar91tawp67=kDyVKLH??xKF!JNkl{Zwd}Cn0owa zd=oOSe5H(aW%wSvA9lu^Ml)vTiHCO%H`SvpGS>^rQZne&F~411%aQ*jmoWX&FT)8B(4Vhx6S{iL8Qg)YanK8j*nitR7~TYdf}6pC}93kM`ph zov&NagL+9nov>b2sLC0tf(A%u($Wvw!*7Surm`g}2K4-wOFUrOuc=FUmOn`O;s<8m z^VaTbK|IAu^R6q#LtfdC!X+kWOFd_#JD%~l$6MX)nU|_)82ExwjaZXYXyt2sM~5o$ zsk3jSv8~tmj@gu6S-3N}M~$+5^XuEi`Xs9OrA}HWXj`%u50)OwW*KAu`dZv1BiZIM zah#1MY?U=N|v0w#6TD&T1Bb--7F745$@UHKhjKV9q|5>|Yr z1Sy!KVg?>(@WZ?uNxBEKz74;?jO$UKC5XCOzxf;XI*~T*x1~}_!P>d5t!ngUK0R41wVZRmh_-_ z4nWkLzbWg0<<~Qhz7#<7z!>Vtw~2x!ZL?Acx-nH3Vw4^n-(#+K+ zPfKN=oMG0}fB&^KtQD)pQGbSA4FrSZr zi(sGbrl>|%p7Z=7sl(ofnUDYP+u*Wd(V$p{C4_sN4Wr>9%ZT89zo~vH>PB5k6%o{7 zv8z{HAa&kS*J+;0%L7AskC<4>^YRnH5;wur|06EV5BL{z`}RU^YF1lbz)5SzxXuzn zex~xauNbe}i`@=$4B%RKsN@x7U^A0}!Afu|tH9Yk6&u9)wp~SP=5aInyiws)Fi%Sy zU4<4mf4+l0XT-vW*+SgSV!)aa9!HH$-0zj|KIU{TO*Zaar!(eo$(MZjwa$v7{LTsY zJ>t+!ENOxuJ2^q}p6I3UuJw;!1yx*!8^;`u+||U#+8-uSoR`v%Xy=dz2pMW(FU1hE zu0$yDVH8u?Kw?-ELiB1-|4Wy7k^TU9OM?Jy9U9X3%P<-6;7M7tp=2Ie~(~s zHt1pXVQ)AsyyJ(5J`$<|cpE&1Y6mw(7vwtQ4x*jcLJEXZ4SrCGpy%RS#`*pwo{~t=SfvE#V2HETG`JCs9h@m+ z`vs|E?|p0kV3+}RVeX4DH%xz}9<$$5y)x0%6k}HYIiI1pUVD=TiIR@sX`1gk0AlfN zg+n*q;DuOCZuBo%hq0G<%~(FK_`-0)uwZP7zVexipHs+y%xNb40tE7d$Nhjqz&yZ- z1Zq)jo~DR^ej2@IEm3oDGNDpfR!{bG5*)ziUk3fwPpOqoQM_wgXG2sd=ZYKHz-tTO z;nT~EUmuF^WcnMBJ|h5p6uU9cjB)^68mSp08XqEJ2eD|-_v9us~9E7hP$f_Lf)azGlCt$oU(kaSKq(iB$mH1~?i`|at zxOH`+RKY0hAGF>H!Y=V*YArsq#p^x!b^$0K|6IUy$6@>NFKxl^I6G0V>{Ca6aUh_x zt7|bEoH17S2U#s6XEQ4!+-7ua%l{*@Lwt}YOe?`lnW8ciBekT}(6L%Hhsv1h6%{X= z#o^nq!Oev!H1RN1Q~IzLRC)&y7fj~z+v9vNt9^NG&lCigk^1C4sER4_K9J&ym~~GD zKb{}xX)prnY9XjY`T~i&YH!xNAyY`=j;_-p^c?+`|9aw8WJ^bQR^y2(>wT9$ARrBqW{&+`Mu#50a`TJ+Q{goy^mggtm8IN z_0=qI)OLl3xuf%VK`DjNz{zq={k2V)=-H^#0}7}1t1p^;^*Ghe3*Xq4cW5yu2Ee?T zPOHR@`Rju|0yI)Dvu@cy22SH_v6s;<#00h6hE&_W1{uth;z?qEB?&H}e89`}zE(f# zFdjV4Av;Lvn9i)s>TzA8lhh_9RQ=E8Q2T%k{!ZlGSd#TfaJtrlta zFdmGw1|f95hT=yQ%Db0Fa=fFfLp#CqEV_dlM=gnC$6M7S$#0!ErL>~kL4Z4ziCsI+ zHV_m1u;$Ty3vSF?6xgK-^}eXxq_c=40CyIgOIWV{G_6eMFBgB+sG6$hp(WK&+I0D{ zj}Os{$5?R7OnsRmwCXKXm?>vnHM)bJB=ZJzWLCqb zYo`4uo2G=QWTZ>vk116A&m7pSnH$Fh!DUmQNUBjOqA_^weaIdE6C!j)^{G>zYWE1u z_7G8X{QenVnAyJ;>iV^D1It(kBk;R3W>fLWB8B3AA0esG)DSEHVKNB5BKBa)B4bk#^pR zmov2ZiTsJP!28$eP5Y1Kr6*MyV8>hn1Uy^2^_z$&(;gp8=ucG7mpOrYz%gY%(AYM) zdvUZ?%;k7EbXXro7n@>arP)PRlQvUAtsibez0@fx%b$ZPH?CS~8I25O(7C8TCEr|z zch>L%L!tdmDAt@GSBm?zWq#V05Kq7oy}l^Vt_NL)Z1&;ReSQ>> zNVcTkX1i&;XyP-SDx&Q>x+2XgQ&7@Dy>CZYI;o{6?}sHzzpM&%r_SpFTnOQC3Em*+ z@FYZLI8&-n>a~C)LbQ;}@FSk}`Y;_y!o*BZJti#*`2g)!L}GJuqQ-JC2q_c97~XD@ zi;4wm1uMaIef8B&6mJO}6;G$6dj%~>=n>g)V-9Y8TD%li3KRa{`n1l1&}RRuP5~n~ zFo$!(2J+JwE7ZfMJfF00m>=KWlGz#tfBD9_{_;)CrNqp=0tKAt+lLxyL`>fKHi1b8 zVGA_hPi3D1IHJY8v-nwRslMJ|zsh7wddh*|9(Bn^gOd*pC>GbcZC9_UTunGSH&yD?=Y+PKq( zD^*(GaY%J|l9O3KX!^Z*!(BzHbohhFBx`M|tYgMxM%w$oaQkieVk`|6aeYVJlC2U+ zkW{ohta!ZMDO(ueA?|Lm!GIcAyIoeOCVL{}~541wzQMs#e zYa2JWvRQJ0Agfo6+D@yF3Fh3fC(trX9tO7GZ^p)%1-gm1oDFF3cXEH`9gkl%y}Xii zN*wH3(-KEbT48gWn@OXn(B1jL-+PZNivum`8!H!R@ArDe%X}Af#P`a6aUJqzxM>@q zEIlzjb!rX#u*hJT2e>LU8Vp25q_uhH^gC8mJuyLJNd}J9dd&ngDih}5(xg@JkAQfe z)DwcaXlo$T*eJaFJ(_QSlL{jp3_7ahg}(L$BcB6F~s4WFwKBT*rFq6Z)U` zHjIcBo6m?7yflTvf+uyEHVDQ;C{SNccve6Yxt539B<%`Zc5~c$-n4r)a?Oo4$*6<# zkSPjnXVo6vTfxgk+v#@|BAyzn&Ydzln*f?z8>v!|+O*B| zFn`g>`YzoZF3CQ3g^>ID%ODeh#Kc`vTeLt4xtC_BSj0$y5Hv;w_skk=LZDbthC)lc z4f~Zs(=e!s^qS0$BkGCQLM#>ITW#1*Wo_$`qq2O+trkuSz7W1XiZDuY&(NO+}7BgyzgnQvXobKy=!P-c$U6HJu`c)GAf7P#nK<)X>X6~bZmMsMuH-UfCjN6Wf_!avauVnfB7qgf~%Kek@ zU;pZDCv>Are8x1vF!o~gg0fQFN!{A}=MnmnX(=~fj6yNHfn2j2+7^#4E$*Ec(``IN=ZWLDMQjLy%$+6& z;~gVYXgUJXyAGJ$1$A3VdizFU@@5y#(wj82wh9jh^6m$es3#)=_ zF|k#tLCB$_d|`bz3iB%W5r??1apmjl+%_uLsiEcgF~kCWD5ic!TCuW>hQyoWewZLt|yt~^WEP+jomHUkO@H}cTKqV5@pU9)bddu zX5SN#C$Eo#io!tG6+sdbz%R+xW7MsgyibfTI8hMV)hWH^Rw|uj}tAtR)g)l-i}^#sS^HVdN4XBNX}PCU*K@R8&$cuX_1bV6mH^` zDAU#M8%Y7ctU%8@?-It>nfEMD&9?d(inqxxhBDsNmfm3oWAPHh-jA#O4`*Y345vo; zC0}ppR1g#)vU}#FN>#Fk^M}%%@J%S{(SL$f_(uc?)x@5D7XZH)Ei(M_3}q1N3FCW5 z)nwAcr`Z#XV}aDC37lUmGM+dlF%ELxWvY+O%Rjv4e6YqI6AX9SfCj;{OYM&Y{VG=5 z-n`91l%B2ZIMFq#l*6S;I@~P5=wgg*`IRZOQ>hN|0**OJ7V3Et1+I*rMUvq0;OGv( z=Xb2&!8^I+pt6b?zcTL-^>}DpH}W4H?w#@%@lwjrHF(O{36vF#f9bX|o_EcWcrAFC zFR1xnR2nDF_uQaJ;xIR`qsME9#nZi)e$&t)`yo`$Z6u5b{?IR@(xWU^22EE$Vs1#C zP48jJl_MOtt@;e+5y(g7wPBY0{B!my*d06fM0JPPW|T_M+az^p80!P1n!MR#M-1T0 ze!cD$CUG*o^f4fyd{YuW44lkA??r6QB`TjHd-<=DsRY`<-;77&Y+%yHM~(ijYlgoj zg~oR-%VuB9%||ZSuSszUh3xgY`v}8jbY1*I#QHt-Lt7bxlnS%bLq(n6-!{!e?Wg!7 z#ZwLMa4yzO4QJ-YVn*oO9mOXNWRc=8bMW$KWP=_^zijsGfB6Q$S-UIB|M4_4{l^*7 zZ}ks&CZbf&LFHLDu!424i#dBDAYA~~^y&JoY+uxApGUS<=ZCeDOp`iMT^U9EkKNTC z2?g5^xzzED%6+)a%RBnJ)U)l|-?M3m{N53Pdl-GatO)mE_absE; zKvgQDZ%wDLvSx?wV^g>R^&^81v)R#yHnzO#A*ic)!Ve^hX;S#ROk7u1hKIe73*&@q zr(HW5)2bSOn%|bHh$8q}{@EqtU8#QKkBuj)sKoj?5&u_uPq=hMIvS_Z)?%1J=(~&9 zjxUqc&B|tI%v068pDXh#OY>&wm`3Sxxl1DH+ZDqt#O8?df=V5((lf(vuDX85IyuA( zUR`B-;(M@aaD!~;_Nb8#uRzzOU1c^SV={JinNl!`B?R}D(lkw{rJ_@;8aKciWnfRu zsA?|d1JTGJ|ILXWIf>pHXis3JPvl1V9}yoR;Y-WWeUXXWjM!!@X4%vz{}usW_@pW4 zRtjTj1})kCuLPM>oBQmuPzu?~%ROtrW7U4L z^UiWUaE3P?g^h^^#Vw}we#}=U7eSvy%GhRPuC3=sf@G*r2vFRF-*@(0_9FL}p01ilirxOLv_i98v3bz$f*tZ;?uf6*h5TMwZ~-KdxJ=XIm`*xgrcs!Xe0l(UvTRhepDdB6um!rA zdPqA0QKYYAnyfNWC^-Uiv?I)Zyj4`Xw|Y|OHJgi_gHL0{@TUHc`-$M@3j*&~rJjwM zXxE^_Cyt-+uda&c8-f(G_n1SUFA(0Rq4%&E_bK~NV8vy$y=-w<=_Mw;?fUyBz5tAj zOPEX2N|cdh@(PJ;!4DM%j{FM7UuAK%X7r*-*VokYcKgOh{oW34tpFHV?xDeDuE!hg zroAL%pm2koqeIH5?Hs+RCtbq!VQ$$NA*K6h{tqeBDYBV=eqXdrO~&}f1*xX8l4b4o zMj*{KD+|-Cxjd!WNtc`oa&Sx=^fFb~)UunxS2=@__{?{+t~1iNZ=c!vqDkVYlJxdw zkAfSq{_bYNRDP=Bc_<_AjaoA#5@$O*_pl)8&W}kVK3ZH)(&VzR=iu+L3Woi)Yt%MD z^vwR17!NX+4lfr$`DTJXzypS3!Oue~3pIocS7=5Zw0!KaeDh+Msd=~f<)S`Ag8yYQ z6yYoVTSh4ViMTrJke=R)l5i9ooEAL3UJXS>Q8#)n@s#Mfj0tT2fWm>*mpM>8c?=Uu zz9>2e#A}`3;@oa3%wr>0!OHIJQZ{cH|A*lrv@VSPWHCGnl7wltW>Ghg31zl^>a$_Z zv23cn~apv9(v2TaW z8-vmv+UDOpSd5%0RTo{BoMn0>ybu%zxFN_6PoKwvR}QUs|LyNT(ewS_Az>VXmQ=z( zonP~PqN9q>8~0kw-a#oEn=fsF>T53-NX^NeXiQNrNRi_z`sFfWUanPXP-IVL&zQ!9 zLk)K9MV6|Dwe7v zk(iy*st|IhvCwR3#l;EZWHD~1yS>L%@L0xi<)SHO97NCfLhQhZ9 zuOM=Qm<#c2uL5v&;w(x-XHK~RFW>*%xb-{&78g7}C&S9?=XXPZmh1I-yz~FewQ;{5 zjV3Unqvr(i`stQzwW=as~$XNz{S0GE8o5g{q8g` z*ODNVRB^>xcpH)?&a+E3zf{fCdZYhq)_&NZgTl~sX~@a4M}N+Dvihn7@Jep06`e%^zPhyz7hFjCU}L;;8Qul6W|%G)n}} z=P>ERa)-gc;}UyworLh4Z6|PaD!Lv^_BBdu!(S{AoQkzz=ehh)$`BmUj0L*9L8e? z4p}O`zOx_m>>K`)9MN$d%H(WqfLK1eVy0FDoa?WT1dxn@7Yv=ExHN&bs&ICR+neSn z*WJIgFQAflOC&0=23NN^V?t)NT&nUSu4SU)cILmX16S4VoT5)W*f5usPecpOUGjd% zwQYSyt}scduVxi+qnU^E8Jo6-*~x-l)INm>t&&3d}IT%hD->OIPCop_m+*bS7hZJY!3d!vj}JU1N+toc3E90zRX4MV()`(pq|?77x-ZH z(UOtNOTtRmbCy`b;JZOP`GNS>2NT~YJ+(DbI zYUJCQ`iK{-AK?HkxXRU2r`YYrgPv1NPdw8!j2I}`TM87s{QyS@oU*KaFbg<|xM1a{ zkpwuj?b=s?D@)20N5JvAG5COHYLtcfa)m0J>^RFLT7)~<**8ctL63rxAi6otC+5~5 z|F5+}4nF4WvR{{pVY77T<(4NiR|zG=2;|hV<8`b2Wy<;O*DqjZ-;U-Cy9~s6#4P^9 zTnNX8SBBY{8I`_bNUee0MHdA3zKk5fw;kx!tnz2yC?WG%k<`B!%9-3w?;h^E z#%jpn$CO<^TrcRu*{q)mm>8ogOb|FM2`Y86Xw64;2weUUlEJVi_lEV2-J(3bd9sB2 zvP}uomj1(i;E5Kl0+;Qadd_F^@8vpHk+h66pk6DQ8p<#?pQyjR;QJos8^^}m+xM1+c7Pbc2j6XDsidK*yRzLLp z!I#Uwd)r`HBF{{UTHYr!({d=ELkRANFLJ|Y#<(dl;HN(t#+$|>@-R1E%bOQ5171fU z)+j~IY5?Aj7YMLWdDz$UK0cmnwGLFk2ee=oeP?(sE6x_E7OS6{b(BoLMKURWYKM(B zTu&B!%)01$BL5?9Z7%}ppS>t08VJal&oQfq20!EzKHMC>P*_c9xEyq$8~J?kK*^I+ za+j)t5jn;UcKoscKRN&lU_d@~?AALo7rU&U_D0i&ps_7`jJg8nH^Ho{a3CdIDp&(6 z_>n4Evd1n$O%vGlY2RD4{@lPA0|~d6!l{PH)Nj)uFmoNVp?J-0dt$RjU`j7}R+o;&*p5_wkmYq-|Klvn&`&XGGf*bwd%2Qx?> z^M?^g3ZnV!*;)(-_w|ji6bznR-upv~sUKEKu`QTdv+B#E6NMbP^530#+;^@+Pu6~3 z60yI|cxoG@sE#HzeGjClkmD&biRJS6)ODEYwwHs-4`J$?%Gy|0O9{3(1*sRbhCliI zyxny%a;21l9&A^BHj4%y|Cz!qfs5HrnUVOc*hE*p(=WbQnSD>rZ{2 z0Dpx!IzCNVTbEdF;`ms-&iWV%?|RrjCs2Z`Ou{}+C_`r-+t%d-MBZ%Oi#ztxIx=j! zl01OQv$a;S1fH8x$!V7yk_{KwJo#r}qk|nMgJnT5`(~rbY$^m<^P>^0`yadPlC;GZSgVM2)M(CkZ4{rC%lhZ))_vyqelpI?>jU$L z{&|!G){JoSUy5URJCCzs2fS*ki7#a|d-nJ9!KUmalT<%^*T)?@7$j%B8$4l0j0fr7 zQu62k)ionG2_(nlOeB;+m@xwueD<~W|e{_S#xAJj2-Wu;13=v5;EBZsii zV%G03HC9hX6-VM+$WK_VCQ!WKcdKwLgvlaEZWiG0Wxr5UWg>8mNgMP^Xt~ro1G4YY z+-2D!GPZ$aHVcOAsp!JOvhZ=T@VjPi_8-nyyxZJv-)70A7+P$(5O3ieyN)M;38@CLK}BOF=6Ot%G28g^2#zIjv{|$VMRiiPvZR{vvM!%Iw^}`5@H! z*F)qTX$6d90UWn`KHJtN>R189R#;{Bnv1%4IG&1@cat~fzld`?EOVRNBSCL;W|i2) z1e#7&8^W)HhSkLw<#HMG&@0yiACD83Tf#A|iU<_)gjOSxrMAZJ<)+}DaVPOH;jmEN zhYr5Y&?kau>6DuH+{!&jg|8*yAa)>)>Gl2W$Ob8Vw=!~@T}rl|6#M~x#AC*@=C%Dc zTgzy1Zl(MMMqlwZ-~Jk+2a&z@mo$5|TaOt3!~n`ChoD^5 z7mV<{m+@C|JF6H2UAW5Jo7KVJ9zsCCUk00h!wImg1`}R9p896rQfAx&7-(oH3|(3c z+W&Q1+*weqCO>#Dgk&C69K#QOm-LDq3x-WxI@r$!IH~;PJ>_YmY^uAmgJd}YIl+h~ z_|dO`0fnMDIq@KAMSs^WnxvIx%%X-*ZJac)j6+a$@fymb3-U>oklS1cnu5#6TeD9P zj%^TtM`}Nk1H63w6JG$b0feK@Nv>Rv^%iar(iTfO(+Yh}r87MTzWoCeHUO5x76qT0 zzwb>6EH;lgxchaZKUII_3!Mty1OX9j26O~MIB_tl{g*Jp%-9#$z_C+c?pq_&mkeuK zop>KusULE@6Phgq>{#sv=~XYiVp2=4mOf^h;VVw*@s|jd`_hb`FG?5%Co$G0_3@46 z{4_kv9($bz4C8Ul#*V&XD8x5YsW52zI;E@##-RSk2mZZUy^UMdku4Rsah?QR0nEb`q@lHSNUc#91tRjq5cPri$0 zpC>V_2)zkP|Mknrf@Ubl4b|>(rpY;h<8%EMxs>aAKCkDVUADiNlxpxlS?2K{*n>v? zjB$U+&3xz|IHqlo&^aecZWv$2T6^^0Fj`B@7t{FlNLdJT-gd12Q0r?Nlswupn@wYNc&I!bU*E$`I?p~O8Gfsmix=`!pZ${hqZuq$2bVK| z1>7F}8oAQfx7p3;^Ym&x0ai*nNe&rWRFxg~DoOsv@L2CBE)G2Rbi2oy%KV5m5U;># z-(|FRTW<+@yp!b1PayvyU~4bW1<@#kew`mt!L*b=Epw|`l@TTFhUx8qBo9DraQp)C zQ7v2|=c04j@!=R8&bFr~D!7cEG4YS{`M0Y1_l^fMHm+7O4=O*PZ*zdOa|PYm87@hN zVhM6jyHoz)9jX9Y<8pG z5cX{WPQ>u{z9(VjO}3U(k|B=i0PH-g5T42j{CERiajf1fsS>j2z2EO+0foEH}c}JqM^A~ zd~{W{tTnt^qliy1Tt+H2CbHEm?p$3kJWxbU8E_-d@C$7vVAji#tdrGjrvgF4F z_!G+#JfMy-p&nK}F@SvgA97BDFh1qQ79N=yJOM6wzXKiVHaE6< z;kMaY915q(LM01FloTA)CHd+7N*Z0Q-OKsO-_ktZ=iY{Sg=Bip6q5ChD{w|M%!v)1AD<-<2Jcu^(5K)JM%n=G;P?u?2DF=mW^(k(nO^#^J;ZSIE4u2j` zcvh;JgWRtt+AGQi=$Uibl(Y?7IVpHpGIMOy)nV6n`sb{L;SBqg9_;^Zz74;^(nm|c zSH@2b(QxM|_hc!H(win)-lhk04#J$Zl5c^7ez1vL zVcaRH$Lx#>-}7#Z`-^D(&{Qjndxf{;KaUmwd0K%Ut|G%7UbVLGyI~Kw7Kn@!JTj0K zJ;nnLh+PDnx%!O#1+SF2{;}=6AhP|^Ou-pL&skLFKrf7W5;pMCJxrzc|A zt1;JXnjV(dtZEeK)oxyzOr>KoUqYO*A=BZDBBBC8%l;ZVi1DeVWx;4Mwc2 z-_0_`HtFpBEnSdN1$4r6#?jh9pvUK^bPH;6W>PpEaX;La`vp|W0xDf23Z}rCj(`a( zw0;SeY5jztt@Z4P)^e@g*RpVy)$mQD;&-K0)8Jl}yj~&9lA5}Q# zrSMc{UuQ{`V~5FGH>h-ajBK56{RP;bq!nXReHUI65y?*^0I}16)d$VyBN)z zt34r)DI8z>w-^7`e=~$U>WM=0wzpU83WL{KnRf0&U=eCYwNol!n?#tl?iU9VCQb84YRKHJr|V4M?}Jn@n!7~YbaiHT69 z*`@}em;6G8kHoNyqEYMq1T(W%e`hXm(+5x*^{D!x`1k5ChGA<8s$ zJiIt<+laIS1#;&H-=S}s!GknvtjL9jLKh(WAcrp2M?k~Ll>_jA)->2VwTz<;`hr3b z%e7ZaCGkZUo(ip*hfTS7W(lR;oN1uwXg87yZChB0Pn1dXq&v?>ffoLx<<`mkKfpb2 z*W+1d%3%So*8%D)0?b=nbXo8i!$d1}8#Z;@2D5X^CdMWR9>623J{_%^6nnBRj=>+;yNMXxWkqzJ{x<)gs>|)P0)quOHYFFdq4lJ_oa}- zhef2Z1((i03l@RU4fB#0z2Nmn;ml2H%CoL(QaTb^>u0ERo| zZQOo*_9j)XtUqx?ENNSD8u1h;?AR!ye^zf}~u*bCYL}jqQ1y$zRs_klRUtQ-} z%-te=*VSnC=6Yasl-~3@2M6HT@bS*&oz)4Kxa;~_!>%rMuVZx`O3*-Imi~|YTTZ~} zEC7;7e*Ipb1)mcX8D^txl>Sh!4+no<69_AtEm!NSIbGehsxE|4M}ISfan!Yo-1Oj` zWtEf^-$Ob2Xpg#eDo&>tghNdo4UB@Ws_c4oG^99wDp1w1!|C@fe|)=z7Zb_$?k!z^ zQK1E`?Gsq6y0qeSo_2fMBX^kQ-C#&b{2;hJcpIbj06m^JLkxbjdY`nLEV-53_5wD7 zc@lmpqxEx`x%64rqrjR>8$23d--i;~8m&MCo((7i?nAVwb+`@vu%e4eah+baiu_lt zDDo1w;|Fd7Mb(X%4+Odu_p-sw$wa2L#39*|m;!wj(XMtAP2J(ac#xAM65a(fprmo5 zgaj8>{m;7qvRkx93^wEQ%W>Eb|K{=~jw>6E4WHFdk$2xcm+ZG+^m6zu$evNtmwjHU~&qzDg#^(uVEhO1jKh%JxOS`G=n-DZTq@)Jz@T2@ea5 zeyeNt=SG0Y+eUmY{xnp!R%nAWC~}_K6a;;Jk`k{5cZPdkFx_!?o?-=>V>V+REgEqL zg@%}q&iVFb-y2gA|0S95a zHLIUG#e<>@W_N(S*h47idz&Rq$9-FM@cDV5=x+VT3f(2|tO8X~wN*yuZsw!Q{Y7|k z;}PCzHL$2&{*S_+|HIT-zcu-W{a-}|BqksN3QR>r=};P`B21-3rP~RJbT25|NIl8+>GX`w1x}SZXDpF%4in$;|SawvJ<2l9V8=vIqI{}egMWU9(2 z0p-{29ezW@Z>x$>%=84%3-E3T7G<@Fs9J&1_wUnXJbB$hzsQ4vqCEIbkQ9tHlLW zuMaFk{CK9BeR3^R3E??4B|>cbzCEJcQw>zq)C6Pt ztFFKmolxdGmg8~;E)B=tzSs6|QpI1!!}WKO1L=y9sZNOLnj<$ewuqGKwWNxL>9C^@ zSVdx77eBl;1Ln1Z)B@eP3lDChaCj*0mo!dgB&-$6EhJI!t0O(enbhjm`XMz?_tpKf zu6gTQj=yhF^;a#6DEr3rdCc=rWMTJF6EHvNmf6hL zj?uzK0@a$b4v|0o2eIAXUy{8IT&b9>I(+hb$ZTDAQ%+<~Z(VMLw7#C|LA0h?} zKRB)S)?|?z)`xq&xo7S|VmNzSd=-;v@mskCtuO&=#}q|cTB8e>l=dCuFmIZY_ah}? z=6;0oZPO2HtD0lR*EADzquo9}zD8|t+xyPQr5DF111(fHc`W|tc$GsJI(<`_jFn_w zukaA|HrTpgz51`K^%4E?Yoptwz0q##3*xzEw_Klj*Dv9E(v|l4=ZT~l<5Mu4+)|>x zy#42-@I-yXPJTVLr4Tbt`%!N#t5v_9 zA-$!K0&!|VA1?`G4X;UJ<)ty9zp_4WKhFRLGkqefoT?d+fjIj~+tl<$GF4W@fS^); zqsYrB*Z$j}wWcClSpOI5iGaAC_B2x~1r*D?(xojq&1_1$TbB^#N=j%=*!Y`05BHu^ zH(}x9HtEH}Q^J7#Vy43I9`B(S* zp!yft`kvbl>e^AC9*MZh^*B3{rOn3Bj8C@x3^3sO>*_JYV_Z&lC4tpr0^UeGUgWOf z2>Z4TJOx&9#BG;1FKY68V#>rSkA^b=BygEkiEPC=d`ZMl^b9)t;K%llI{9s~?VetY zbd?jI-P;=~5RIC>;;`12Cnq6b+9) z1^)bRMgABi*~Y-iw)N?vDxc=?_aijUr!DG&OY=SSIAF0RgJ}Wo1~0k??Pv#`WYT}1H!3z%SEAxc`>iD;{t%0yz4#=B&Yw*x_m|n~b9d~JhZxs? zne-vN#9O-U3I9B{#e5Voy>U1rVrG7#6?jrPw z!GIB92Pdda()Tu?V!v-%Q1uOrFZ3QQa9xwTW5MWQbTtXD$2>q#Zn&~wbl;lk>(3Y8 zo!uPhVN5ThK6{Wi)pkR6Xjf9(}{wE*pPfC>f zH`6!Ee>TWjzy)>~8s5xXKb^=COFEWS{z5%W{o>Ch;SZn3P(i!Oe9=vU}t_+Ezof@~Q%06~y zwTAOpp3#+E3LX~^Ee_8{}S7P$rfnte>Fi+1>@by)8u@yr0|yISsT zxYrSyz4ML!A9DLQ0e}8hGlh(HB0$*zKlYe3oTv7*AY&wWF0;z%y6ls3>ERw%sRCxow5uvIv}=r7e@ z9?MaD-rziTh2B{}<0ncZ)h56(0;Cyv4(0&cA6Kc*R1M2rK@(1XI_@`uWbMStrVpJ! zr2c%iHh#Y&Mu+`|Dglj`Pyr2QI*PV5aV|6}LD$LQ(PGqlGpVJ1Ickw)`Z%TA=d8#-y1S0%$P(qx}8XZaaP-G-*AxKq5beOZu}@NQmvm7y$Arq1)- zgg*KJTTv6+>UVmZ>CoS#_r;fIgiSenE9?Kj6sG(Z!L)^z7jG_qJa54&`Gkx9o<(o; zW{4oBP|59?+T!oEi^_#`KfY@`S#Nzbefp>_>t=?!;yDjMF@i6$NDyrAYw_U@;`H#~ zvBQ}y<%3VJ7y&c-b6U8hzT3|ibG{k4{p!wU=vz+{G~bH5f#3<;5&J~r2%D^o{n+Gu z#BE3Gvb}{Z3Y_kD-97BWZAUOyJQd%5@rIY)?f~8T{iiD!rp15z=UuJLP%ev=&lYv9 zETHF4J%CAM;ekKSnf`9vyI?pUPslqt8}02JA$Ke(-92E>ie$0C7H+0bd++ zpVw28xAFp&mH=x@D$>6c!|t%|e)DqxL$-!W#G}7q4_W>k$PsJ1oI=DjX6%Qzze(js z(#>mq+s+nVbD1o8G@C6Ot|?JZo3j^x%+jnAH!nFlc#zDqd-b}DwTz^~4P4&{;)mM$ zAsWKeR`M3x1rCaBnanY=;0!zH6%7kDXW?Jzp3KnW)xtF2_`}+wRg6ZZT*Yq=%^h{v zP2?N8$WQY}Nk2c8$8e&d2(Q=Z!IJeaglJm?SH^W#Wnaj*Zxu*CTV`AKU>Ke5cF_zi z>>bAw&&{1%RFmtP&1qnjC>?wyMCR=^IcMG<1?5SJ+kbMA9rWj**Whp37h5LYtUEzR z0D518Y$&3GxmrP1EsP|ro52j-9SHb@62z;azPrE+{^Ot{wY< z0aIElN}`|pXu~nZ4}!GwRt8d$t$OfnCm)mf zFez-8%8fq&m4ar0*B^{)0UDG1oe~qM4s!t7+_F$+qp4!b$s@xq#2x2cUddaESoGud zYd^?=Z!Nbnp&9Atl#i193g{g}LEbuuqh16n?iMv%nzT;ryQHh18UFj&C2tq- z|C@+BfwLwVV-D`wp3Fstdf;9LnRXPR6+O-*RX6;0q|nMSqMg4|Z0Vl|IahS0GY+bh zhM0O^0I77eeT`N_NR7F6-b(*;-Q^R)VK8s6-bLP45+1EmAL^1)i_SJI=GakV2pDh_ zQqIeG42LNS&wc|0xj+mg*n-dUjeQh#Yr905$Xl;Qy+ySkOrRppV$7@tex!y9%__w! z%d5frHV&4e6d>mZ*c}?yuPw3kZOqzt6?Fn%)L7fg$gUqhQ2EWh8GGBhHKCX#SMhHy z9T;6G-qz^awJck5*JH@xBJa@rsqe2cHL2wfUUic%ij$+xmY2>_lVvEA$zLEK)tRoP5`e{kd8wd}$LJGM#1VTc zXP*1am$(Vtfn}p1$jG`Qs}lwt_Hz2gG)vnlo5hN3m4}Kqc=C1iv39NDcC9~r=aX>) zw1HFmOMoj2JU6oHx4t>F6cWZ-&eIE0k-yyl?1Qi^X3(Rym}+WrKJe*yKe-2`Gg8_1 zs-!r{@u!&wPcA{#8ZNX94a7@nmS=wYYw;q(3h*j|ftXx0VU))( zlACLt10+?SbiWW1!Ggh~Eya1yknAm29+5Cpc3oC$$nPg5#pXsnjq8HPNT)W}_{w^A zYS9F(o*Fp{mf)wGpt~1%;FrtX%VVQfV6`6TT?FaEq|bFcGjC$7pmRYLQ8;O@k4o37 zU1IkSHB0p&=}guwU!8Or)HJ1?#(yISA#?DtL^>%+MiPt8G>4A-g?w~ug}e9wHJO;3 z4WxO{aPJ6`=Yo4L-{nH@6O|oU(fKyKhXHWd`gudw7$}ZXL37&d3<{KJ!l|RAhYb^0 zN~FKY1eNfky$_#eZ0{F>l{lt#I3TD|>hT7Xa>M!^>N6k`9Z{#(oZ7Qf{2Ca1I%QD^ zX%RDhydPNfB4h~wG=PbUscXKVd1115t%lKotFdxG*587d z>B{YMCV{OS16A#k`Sd2tj#ob;&@x>`j!_%2mFrJ#yipvKF|$(*_f^feYTy#9R(Zm) z@><@Vs-t$F2XiV<9gup6M$0QJ#7tD;r2E#`K=b*I4L(5R@_lPfYLroM9N*W+qM#Mu z1ROor*U$7LxSx*8=z23D=2zE#!yNykI{uGYG*f-(dY+x0?bF5$Xh>C??#1DAZ0EQ` zBTg^Ovt|$#J7p9Z6tyFin%Bw=8I68*Wv3YYv~6KPr{}VMd5(5H=x@7sJydr|{l~JJ zU$ zlA~DanKYr!4$_iU8+kU5hl2>nZ~a5I-(kYs-E6X8{dz`{)4%?pU!s~5ylp;E28$=`}NEW=;%2tuFHQ#2kgW42%>r2vOUkR~8qG3UPbc--QCK z{qz-+j4f+;G4W75D=*fTvOhxzcg1xJP4Z`_I(`Us!>0eQOzjr^HdI1B2pr^?&@bwb zpMn-r2Nk9ZrtVnJrztF*aKtgfl?xGxhkAX=3J2}4BsWez&w+S-gC~7ZjcsrHUL5jR z(1=U^i)-Nahi-g*?PNvI4(Ya6ISswbZ;390R(^d&)NAZ&NSbaATL`bbHdyr_`I2Ll zzh@{5uD@kC8VdHfWy^+;4lV#3i%$=&&Hzt&mpG>_Ir_Gyud_O9c<;}Pkr#jmtd_9N zM+3j!xKoQiBe^UB8A;zhce2O#(oQCD+51MBXVW$w3bo+%_64%hN=Bf5(+cCx|@K-gzO% zE?BG7tZ}V1#$X`{goHhW=knE``38}xbbE1n00lFAHn5Ni{G7<}dcijFH{iYv5eXeb z_Ai};G&tJj9;s1Ua64v2?kCv#(pzdGqMxZQq7JoTl^9qE+U^X}Sj@ST`T2lsZia!0 z+XYcZ#d!-{=jZF7Kn{!V=I!T`=uieg-Ppl;f|NYrf_XvJ8Q#4)(v_L#A@}K78*c2>?_0Qz zHbwK0QsSQ*T%h{QD@+;){>;WRczAw@7aaWl1`)^|ceAf%^^ug0!UbXrZr}z-o0xN!MmnooT9U+HK22AMa& z$LOvlF4SANTO#B1E08Mog*?7Sl?FF!r0%M*mk(7}yc<&73AI5=)GxPU&#PuAtrDQ( z`2T)=8uw)>443OWX}E;v+y)*@Sj|M9<$BdWbtqAVazDrgjNzB)c}uD*QF_^f{OJ12 zyKLZ;rIr`DegU&%NvW#46_8{bz$l;v8sizsJ^{G&cP`}@+hw65Ifmpnj?N_==h9l; z$T*i^qsLUTHQa}bs1&^K3(yh57bwn$*!h5c8()1XUcC`k27AU-j^Zh(kX{NX6sen@3j+B{)pIb0ik_FFg1B3Sf z_{_%zKw!o^5bvi>g%HS4gf)En_G}2JFwIYfy)975Q6O4k)`%rDmDpLi3}tLG(^Kq_ znea#Fs_hIqyWlcS)SjyJhwEj#VA3EfIO!*M<^$D=m4Z=C@!B(Bi_y;E2@kA=``0CI z4y>Qp0M)0~AJwt5hk0Q!?K09DcfuVtV8WkYSaoS`irKz{yI)|R zKCExe$h~+fwFaGa;KG&JBK&#r%1Ldx7QL0g@xw>=lTb#$-HBFZz_Jx0CCJ4ajT%G! zg6A{*y-Z#!G<-SgG7cZ->_Q>ikgtKCg<6KA*DI4(iq(HS{{3o8WQ>N+6$n^&SXcg3 zd`Xfqf59?tRwf&v^yu71Yc9wl8cPc< zds|l+(%~+J-2K>mlkPU%<8z>uK|bhh_DM!9-h^2No|~~emo~@lG5+HHxjI-8j#nGJ ztsUIhYQrA0-S92x;gC(Q%^H{3b4}@U@;yH=Y?kYCzLoO0TSzcCL4Qfu<-Xd(!Toer zbgh}YG^W|?$NX^UpLA_RrQ~I7VSgTFYx}^uN6EKz{U+P4)&Ln7Uk7awoi18QWG_#v z)hWM=v*L#YoJZQkN3E$F;r|F0Cq_yf6XvfIG@e{W@N=65SXUv8 z_hJOC^Bx+-`4(g?oTTd9{{6W)y|}!5@Ui|xl!p|9Klz~XO|amkV>y0lfjiHHcg-ba zO54RM^3GU$P*07Im7bh=-QK65g`yDU$?U6f(>$*7*cc*41LEA>MA{43u&YsUo~NBC zjB9sqZBcSkiz;ngpV9ag0t+}W1j8I@*0dX^=8MSlM7 zq=Jxd=bcfqezCpJT|)hXSzzt8VeOEPE`X&g?tW^iyO)RqX0#{1lSlO?ib$|C8)~ax ze$WPIE??m0*RL#73zVISsXe_uO+|BGsA}SZP^tIUkeSpkO$xTWLG06xrJo}#edajV z^HM&ma92;+LO19)1;SK=5TGC(Rkpb4K*aQfCH$;R+4UdPTExLI_v#@LuoE?$-QLLf z^5)UTU=i$uKmxz%{&#adB_qzbmQQl!-TH**H6wOWFG_L=x@k_2p2mnVOYlSa{q81J zs_;F#8ouIBU?hO9XSTl%;kz<`FtMiJ5s;N$j^O}q*OCHMwm#o_8GP|o%Mr9T; ze(@4X{phdhOH$ei{hpCesN_nFxtys+rDk$t_L-iQHO$XFI?y~!OPtvI+xjvdE2av} zHzFcA7<8_@UBR_TG?Jf>&Pf~+q(%3L#XOa+qggu>W>KL;4}}+?-;bgwBtYuF>XG?>;W)x? zgiB2*U9ZvK_)qr@S{MGWG{>MVTbGmHbormWS_6Bob-3xSPCQOl@lTVx+Q}4#7rLVJnbAenq|qD+ThOQ-n-Sr>hffV=X*( zwOxLocW{ktAQncxF>!K%JM z*dS|finOj5(Q+Z?+N}r}ViOkka(LQjfgjHnM|7B0sB{#J$6(#AIJEjKjPP?`%m_PJ znyViUxIj77Gg-EL2iIx?W%%=Xg&*s7@dvP{1)SXI|I2%HPC5$gtu;yZDRe9zLs|H6 zWUN_U4WLLE0&B~Zh8^;B)R?7x$O~g;mq;PEg&ZE?P%#?FIP3X$CJ!9+qs6bYz8IKZx2r zP*xJ$JH&z7h5T4!lFT{k z%JDqf*thQ@71sGi<3(Kc-!NrYLUQ{12QLEbN!%ZV=Z zIj&7Z(iNSejO)yZHCJtiEV$zKgRbN?KZ~h@GW!$%4As!v7_jrklml~bVhoIOPWWB( zImPQ-t!mCu?Ad^V2KXXWlBu9UqVecc_Y<-g1N9dV zk4H(#h;6^OMnaHY`Xu-SN`=V|_2pQC=Gdgkg{x5o#%$}is0Oz1-+1WSNv3Mct}dvu ze5DAfBE2O$BDGxTkXhO-KvSHdu8mTE$eGF%43^NT_E<>K1Z1#T-d7Vw)k&AfPh?w9 zL2efP#=kv;96Z-=Ww5?>X6!sFILdnVP0;6~baC9I?8C8BX=PmzyLVDWA79M^Nw}f! zy;WP}z%)yOIQ`pd+3xZ{rdUJO1Bo1;x-`dCX&cVTijYx*J4ioGDl5nej|@`1eH6`QtO z6-GC(;fiNXQdM2VrXX`othefm+w^AcGOcJetJLq=k#*afsm89dwl){iy?2!&Q847R zs3Thv>Sog@e5F6x$X9;%qB>MeUj4M*8dZ{C)HHgjn)cxL>Ggg9b!Pk`f1S(t%~*9t z=c8d8ZD3+eikj||;gr+*aTHh;ekbhdG^_9+|Fy^i%m7Gxe2SrWXo>DD6ZF#Xm|Dj( z^yFcOl|ar~*W*bm%5fVko4=WPLUHe*9L?|;{18Ik&wHpr2DWa|;1a+d|?VY{uUZ8FJx`0U%A#nYm@aRsUPb6*pudmtc< zenssU7HJBwEGc~1nyN{cwPc-9aB54aaV4$u%Dbj~2X(-X?|b*Imp}vHZvL%i$kmBc z+RJk#B8jEpCstO;*kS^eb3S-`tfWs`)4`jS{+Nep>E4iyEALJi&-=8c%8`M|L$**l zi7=VBVELRcS%RwUDUrWZ;5tCMqD$`YLn9q(=*83h7@kg8m-ZK{pE&{(kW+zL)^#a! zsmq!C#=ca;H;iuw{-&R7IT+o#QvM-j>hOSlg0wq9=XG*!1q~9uqviXEZTUl0dy8_A z?+_sY5)rW1q${P8e!f${+lK zmA3C;xM%mQ*5K3*yON#bE$%_SXbj?!*4+=p+=d|C7Umm$|ETOb#o7hb*OtI9JJz%u z?VXj7a=o(f& zv1*5+%`B3M1YGuA!4oxV2hKyckFG>$okLgEshlqv3O`&=l}MEj*sF!u7BupT)2^XE zDgCqsKUe3eUHL|D-hX%PubhZFq;Ix|k&UuLwNFaIodSn{$(39?rz-lV8Q7~|%3EVr zQdA7!zuw#W_~a}l_E+#^lSO&sy?)hCo;pg$Z{-n)N0>UbCYLDM;i~kY$RIC3 zZBRw(cEV=}`HL;QFK>-M0|i;l8$8o{5p{ZXT40)#5wI7Pc+TPymlY2s45au_=Dn_p zK!O8M(oi5aZsG6A!ePyI6;klmC|Kbo3_jrn7P&t2015n|4Dt^xxLw^`7t=%f83j~u zWI$$^W|Sa(%`Y*ppRXcBvRgnhH5IrgN4axWb{>^%IRZ?TK8rV}HG2(qSTs1S7HxG?7|^+rDlsCs)JgC5os% zsDg&dov=y96(mi|nHqFAX)~7+)N^k~cfXeiR_Yf?AQ}T?X!y-DaB+!0HZ&y!ej7dm$6$kT`P-l{MPyHIPApMRXBG%pZ+$S z7k*1G?IlGf=p`b8DpU!7T^tT)1cpSZrU$wIZ+8V_eynKNI_P#YzzlL-i!*xHvOEV~ zTuKsZv$E~tR(LmhN31BYTQE{UJ?FQ0jLh3aOJ0%Tb2n2z_i8;3R*XMZo&|3F_}X7G zdvXgr@Ts?J+e3;6s@TWH*Z0wr%1lsOJBl{>`ps>aF7|;)>M(}PiS|@CX~^aO-qpLL zEGA4-D!qR7U&Yk!7^`qpzkH7-3HQv+36olxeIz`{@$a%Dc|P>tzK1`n_j$SIg3w#V zjN|8PmRl;ww`?q-aFzX!i z+Y1FBw11hNaU1qjP003z`0Npkf<3SChO9Vk!gzv2>pkt`gzu4D7$(WQGny5mwfLqpRu{&y+eRO4$r_45tf%nh!l zW}&G#qdQ+M7YzfcUfbg=Kzl}OU&!_Qv>q7^xDFgS>X6p) ziz@d#P2{UdB`4T6iOu}O$vRF-Hhfz9_6MNlB`(SgR)9+;0GR=KPE9ml=5M@w7~L8? zJ#IyVr9_#JcRSglaF}QJ@_+)D%qI}!b|7Nv@^M|BtjK?wCdPXFO&Mvs;LBFUt5cbc@* z=#$$t##PpK< z{2&K8`#0!gtvx-{v+&+AAZ)U_W|pX{_xCS+Jbw95BA4tria*miv@~i1>SKVZN23?E zfh83X3nn8-@z(nYbDwSJUXkUCQAeom9I+P*d(fQ^Si7B9y+QF};#DYhbKqXKSkSCH zN8lG%$kubnc|BM6HR)QRsBfDm%^6dLShkDDPXkxVG)3DI?!6MY|4{nhI|Vm|>y*Kw zce);>v!Wmgcn1b-d^6&?Hyb|hcQ$_9)ENX^&O%*G^p#@7NiS_)xD(ld}DgEKcQ^z-0p*46#8rq&{7`bxsG ztI9BFo^8)r<`#E?P{u#OU0N&*caNge{_?OZ6_%%;FUFIdwfLHoJjLt}_r1N^g$j0c zABr~`1u7@Z&Nj?(f@fSW(ES6|sZ(YDYtuj-5mPJwR90LVGr3HAD#z~7d}7I@Kxr2E z!vgRsgz*GBg+Zp$G<}rfUG>j47?&DrN!x*yFlPSFF!mL8uP_@=N;z`%Wi{>c`1aj= z!}ktj_13WyH;Ffik+8NS9+{p_A%7p?9s+D>*Q2GC&3Zp>1> zak-GOe&@efEDx4VVB1BswO0QbxGN>#IDkn3{YbU zT>9A07mdT(sRj0R(s~cI#{<}0NJcc}iwDUij72s|O!d|7fmH>xFHdFV$fKtWzTWp8 zb{uB2%71L88ob3LJ&BPYW%?$+H$B}Y=zFt*}Z>ZVUmbaTl{Ursp)B)qtv z?lb=(s9MSD*{skCGx6X2-Tb&XUa3BOULzOf9-%{Q3rGkSItxOm1)Sy2f|&ayCR)kF z%8_fyxco8X-yTy6gpRT~)WypM11gpyM$K(UMe&_K45DfE*E-9s#lk2pcLYoEPMW-JDR1DXST;Sj}NW-rt zC1(UrC9U;J9ihPWciQ`#utp&q=G`We)jCq(^C(H(~T zRX6xppQk?;GCLc_2^xG-EkoN7GWZs7)W1mRsQcw><6i&enw9_dNa!;yfrSsY zKR?A$Z0&dA_IG&TXw1crhftN@{Yl+n*mrHlDdzRFPwk?T^ag@1F&94s#VTkfpW_b7 zk>uAhH1O3=%+n5=t>mR@kU(bQ$y zvuW2)WDnDUmEOieU*8y-jp<3qNfu3W{Bkj~L9(tc_EglQREp7Kqa^N0auJ8rOFLV1 z$vI#^$$b;XY7l}PwYRK7_%HKZCQ1R;Z3@}mBHa;J1ZX-8R>i#B6b|I0 zy?E|*to(#Yl#Y_wm1BgoQ>8wAf}3TZ>ojA4{Le!TWqKc%ngiRv3?ydN%O;KuY_@yU zIlSvH>A!!0zpB9AT>WJ8jpJ?=qo_BRXEM0FcAycMg|p=%QOu2V8Ju=VcdMu}!vZ)$ zh5B&vZT9EZ)#@yA2ksl=b_AwvEgQkwbz{CWMFD>Y|8#eGR$Xr1?{s3p%3PiTmJON> zo-tnn!8ecxmJYiB6O9jhN3t3?IY$?k))ESnwrzeuhT{k57R$2~Nj^TCjUQjLO{%jo z-Myu+ce^uhXoCy(rfr_7ot6ECz@+9*7DFCG)ouI-^P9q1jI!Z^!%FoP3_Ic^71Uv0 zErFUGu|p~}eCmsV!T$OdPiou;c)6b8V5T64Z{@Yn_d#~5eYx*44 z?MKbqvVzTyXwIwOS?Bn$TqFneU3yNNq*#t9Z(3v6x0?9g;qH3>bSKG1QSKn-mx(h~ zJtgFeo!_wwnF_3_{5Tm4pzgW^=Q_Ox|Msjdfd6yf>H4=K>U2Q7#iQMnLs~zgf=@q2 z*+1AIWhp`^&6www3ojQx9U$D#{y89__W4rsMj)v;Jt8CM&4WEgSU?5(@ zP3;xYZ`*YKZrdP79n2<=W&@h=yRcBKiy-=dm@B23`2^K=U%lm4d}IoS?_L+`%_GHM zUdoeeRH^zpfJ5~L`RD}@r+x-$r0M1Jo$>u({B1v6w=~P!#VM%o)9dtuX@)=*Y$?9cyst&{t7TTBZ+va1ysgH-EcdDdNg)tqvjzhXQ|IrTey)Bqyj!*% zv>a&65?1+KPvk>omiH8nOXNH_kFzqZ_7CR=81)|9aZc1cP-9 zAnkIl=8jPP)x{#G5j`(fIfJ_4SyZuX`0`FUfxq-IkqtraJrPdx@oe9o=hm=13Nei* zLK|3Q&2&Xnb@y%mnARt#X&z^Q4-65zRg>;9dnpL})B|7C@=s#*$ed3M&NCnx__Ku! z4A&$NwtZSh>byBBuHZZn@VvV3xEV4LR1>ExPi*VjXz2g5gtk<51WsI+VSf?D`a!b~ zMc%^&D7Vlm-ZWVol@Pt%m%vN5+>k`gl5faJ!9l%qJ-2;J6sBrlAd={6aRp zQuvd=QtLK5YO=6? zKyf;=HFDbKplg}s1cT#wDIo_y$ngVtb8&paae69B!aW3efPO=u28I|pEfogP!SkB4 z$f5ckGUTPravQNE4hLThMbmlb%mO@M^A^8#DBr2S_S2%A`P8aCczj&bi5y?h5cYw) z^fJPkT1|!bQrr(ES}*Zq310scgF%ycDlb=fkP>i zg>;&LL@{^=yo{VkHB~6WVgBl9JDebo>$fbIbeCN2`@bhr$ulUw!>VysZ|?VUENWuS z&1R{oL5b)4XQZRX70+=@-0z#td&U*|+FP{ZO@=a;ncD?e(pHRCarjt+LhzB;aOKfD z^vkJC4sY8PYE39=mlNxAx#kX)!V5wBr}jkcvRYi0<%VUh%hQ=loD(oFqG~tY0-|drJ%X~lmz?LCpW?vn$NqQ_?=<)di`U8 z`yGqqnXE_BNbThFmmDHr+g&4uyzZQMryRl(jID&@9;krc9|-LB%SntUQZS7@S8LsL zp8GGW`P85rhUU&;VcV#Wj~Z$YB0RI?=85ua&yABrjd@vE+#gD;2_YNfXrh-VqCeaP1xde83^#2gSG9eCfpTcjvl|F9u>I9~n5 zP(t_@n^TAuhpABd(E$VcqiQz3-v73>$7fnXVJ5eZ9^#8S+HJ!9F80#(?~&n5BwYCR z+eD8fg4COlZ~9Z17c@PBEGT@pFgtAW1GL3lH^J-f`>UV4U!l>M4EO*97$3H?&H#bWY`K^lsDAqqbgFjWhYcMt)r#b}+f?eKSI0Eng-V{GX z?T{>8D93f+jlS7_*%_^iJxDLaSZztR9LMtK;{}WAJKmGTlk=rU(=T$hEO2kv5;$I|EWP32y97D3BubgQTblmKZx5$SdxgbIF}zl21oCo~XtZ;pITz3N5Z z^YEDVt;L%O?u9?^$Rr=RMuYT(j*xXqTmJ;^hmwEoT}TF{jB{&%qWG>+qYl6ODDp4~D2S3-7bc6%@C9gdrfmnmt=DhvG&9LWD%1fxMes zVxjS_R-?+}+L18`d1%+$q#qv-a=0+aR&<;Dii}dGmCHb@4Un3c@A32=k4f}^ca`G- z8A+6c#lpKGMT6{!nO2k=b%B@!-=jXN@tiG{9eu;yQ;6{SYW>k1H1D~(T_*(2D@54j zQ6{@o=hFjm$xq<+g!Fyt)5*hFUmu_89i6#}CgQ1czF0Lo@Ouu9h8X&KJzNzFHaXp{ zR;BGSCDwHOQ^rz_+59Ydw1o@`M068k^DHHTtbHPwr|S3qa#o&*un>*dC<`n6KFOIF z&4aBbl1exF^vFHcxh~p(LJ6o|yPDc{sDL4>4xnPA3~nsKmviIvoA8@WnVcTECX7c{A*T`~>gi-m$!wh5 z0$^5HpknN25mfj9rfFVPrvbbY10YJfQFH62&t0Ae^p8z=(;hlXJ?$QDTK|w-V+zx* zy0yLBw?^t7>JIbiiVK?9#LGN5#u?g#s%( zToq*jnDl+NlXC3e{A0IXN`J^g0hodNr90njVIu&}Q<-~oaWNa>pEl4-vBT(|v!!mL zFGq#!2a*D|(Cs`_rlH>m)qwj=d!y-{U<1T4^2rMYC6a*!dJ2v#s|kiMgb97G7p^Db7H zcy7>1m{sdyezy%vU{hKp>qJhkC*=a1yMWzvA3*VpD~hx*Y1}__3+uSQ0RQE5f&K01 zc8ouAAB>^A)|82hb&=8u~XN48Ib^Mm3;_knfR?PlRSrfWt0&mal3{cHS2m ztnO6t{k?=OY=c>C`~7~tJ_g?mj# zDthG5#e*zcpy6}FRg|UazU#04vRR`GpBunYEM%xJx3XC7iuB$JH(NhFfgIKFXu`AL z`r8})!5h{LXq>w82>5OKDmZ#8_r9byOG2jik58%3y3c^quv#IlUd+!g)}Bu}FANc4 zV0{<{|M0qVm&Nq+*`u>2htG(A*xrUh3`d@a38P^@!qVt~^G}M{3@s^Et`Afw!eQNz zlk*~nW!Gw|!z4lw1@gl`f_oelNaPDQzDfLMr0(W_?}XHB@KX!&8uy4Il@Bm~RI_Di z+aj;OqetU;!|t(t9L&j>EmD7cy7qvF@hV3JCmrcFJ@8KWU(_viWlE0D)2sHYm_h_1 z>he5p|Bd=?w~5~MtXna6;?xX=NE6-~Vh2wTETy*^@g;Je$>#3HK11qU4jo-yb5j^{ z*-V1W*|2F*p4UyY=7o$;4Sr{F3@?jM7*a{Q1nk~dP4V3|zm<6CJCPB)>V!Cg#Qy0+ zpPDMSbZ080co$BXe5JvwXQQNS!2Sh5`SoSj5gGyW;kn_xU%K!nQWreX>jbs3>#`+z z6&%oaFo&r$ac^n8Q2N1rAqYQMYuoh{Twb>yj3NE_6&#uHlkp1A6G*mxH*r0`e)(_y z8`D)}dd@S}A)CZenYTaZZMU83P(yy7f{d@`as~lWsJ$1CsbS7XdL+^%Zqc z|3oDdy@mo*?w1}KJ0-_+`Cqn zP+_NBTi+`?^Y0-!e6c#&;7YpZu-hDOBGBvAv|V1Qy@U=XdOV5N*XLfXp0jl*^j$Ms z)-gq8T+^_vkv1meTT-fEz0;&Q7cRHttG|Q{TxUu1AcoptzfRpTx7iv}O_fAoepQ{h zp#~lWZ(XTRZaAsc)0L>*!O5fIrT;stynjr}o%@krJR~+;^&80ZkX9&8*gabBpSwBF zd5SuSRG(w@*nj5j#oF~=ksf<}u4=}agN!=vckyxwAO*Z-P}US_k&BbL;WO)ILFkm* zB0uFEr@iVXv(*rwFPV5+kaAop*o08m28t$AmOyeFqK&zoRSAQ+fyM7A0;+L@gO}-r z-8`w6eGg6Nry31Sz0|fJO%t(Agt^K9LjBu{q^}6z1X_{x_?zFQ08IVDMB}UcxFy2N zbd~O^96i+so88X|Kp|DcxJDFYMyuX{df~OV>V-l@mJeD08--F)HPeER3FmZxj{hO* zJHz1&x2}nV6e5UTMk0E2iEea45Yc-O(OdL7M6@7?L}zrOCwd*dMsJDE=z`HreZI*# z?{}T+`D1>VA9FwZ-fOMB_TC9CQcm~YuCOgi0P4Ija*55YW!I@Y$ezC5({|qk^xi%c zbxoB0ecjx2C&B6R&ZG>fr*^itoDZz7Ol%V8IyJPdrxhd|MP^O&^Efs><1TE4ry z1~`lST3O4B{ZwUaAk~(pxW)pBLm!)|H*TbL6=ZwTlyDF{?$AGr0_P3HV$2 zPK;PQlmx2cnzTcPYYo%^UK3o;gXY)<*?#;QqjCkiA6v!43eN0hlI{_LH+pV5{!x{> zhY+H)bF<`b3oz&rtMN0W7lrb-EtvRSh|@1rX#A|4lKb;Tq5lvnvsHqj!pt?c3pJF= z;EY`A14=CkQG&N###Sa&yP1MUSaC7hA!UI*2dfWFTdRNc?{}9ghZ)|xI3lOD zI270Riy*%6EU0l_xqo$E)&>Wa)QQxptdY{UW9&HZ6Cjaob*gG^Xgyjufah$^RHL~q z@tLQ~<-leo=WW0iFFB=F4K)w%XWLSj&q3AExOsC!Ki;1C{qBiGV5@pgmGWMFPp?x% z<6;!)+W2Fa3mEpklxoNX6(M3^A};SzFS`;um0DBYRdLkfRJB*70}De%F|9B#D^{*l zlh(sAHl@K1i!Bmk=MSs*>R>wKwofEcJ-9W_l|Xp)k=Ee1W8m;uS{=0WcP0358oUm5 z6gz3~qqd*u>Q6bk_s)RQQAbI_yBsBm+-F~LY7F>=CL%a_b0jQ|ySX ze=K!%S;Ajd`9M2!N>_};`bQgYmBSTDS9r9d=u=qexsSj+fsB=)qFW2MykvUMZ4MGE}11(@+NnIs5xqAe~sCe10Jdk8EU z;G*6cH|wU$0|Ow4S0flCHl(AnQkg*U%;c*)_SRUnFyIm|kfRKt_IRZU$|qt%^-6pE3!_D};8e$%#luwlztu>3vhJcOGlKuad>$c@3)IzYBZ`(CxW zH6FEO_R`naVKE>^+r7pnpnI^NKdx}8?CGx@J{wm$?(d=)?qE}$5ow9NX(UU~P?N%< zk?{ydX3Rq*o(zh(l5X$$rs9R(!4bV5 zd|qr-;Qlfx0y6wp{4+GvDFCtQL|;9Z{R_&7ABFq%3uKo~DGAgG7pQnLj%~S;bdYH(=oG+n@nt%L_Vv>G$>$o1<*3_7#^OOHGmdW zJ^)WLUoOm-n6MuB_IgVkL&n?Rmd^Ix$AjQ{{e^V9RUW|3G2$b17;)iDc|~@H5hYQ4wyv<$FaUEqDYiih<5M+=y;` zVf2uD?=V0!(Q4wI6YQ37m31jQCQbxbuxLa)%|-a@oQvJTbq=d(;(`*dTiv?^K{Et%T)7QRQP$V*WO=OymByAhH-i}83aoVt|FbVoZ9@0GI7ZM%=8u) zgPuQw{ifmLA0NB@WI7nJv$Y;>m{RDC(bp^~{W#!}iOTehE#+FuB0iurG z%-N?d=8*O{SK+&{0@ot?*i{20(6Ly{>K9+XeZTd;%6nYA)-m zahb&S2ayY0ZU~YCLE^s^r^M!`Xj_*@9Y+C-h~Ed`m2egzh8jxXX*Q7IDv7e3JUym% z6yCHdoxS_hoez*r*5RFbTN+>K0q65EiNAcfK5O-fXXtAb7_&|;ByOq*@>`?0#t_sy zQ9$|@R~zk>52<&yV2? z^eR|?$0C4nfBC3U+jp-hRb!PV_6q%-DgmF>*~l*Bj!ZcrB~l27@xxUhb@7u)15JWR zE;Z)?q@o6`>JkyKXS-aL4`#+lV4$Muz*0#o)5n{041Va~%AZ@#zBLcq+Vz6mhoF@S zSAmF*ly9_o7|6lm>f!yMeb1Ll+_3W^?eccFW7?iME}+`4uM02M8NL)_ep!#j=E=_d z(eevCkNP~>(5KpG3JT#^1<)5l^ENDJ(JYWEV?WKtxdo>(m%_^00&u7C_+IgAoFxzm zd5sKm-2GeA<280R@R=&z*ktdD9A%_-*!~dl!v@T<^Zf(3(H9B*jN?w={R9SK)>DlUoVu!~CAneU-EKl&KBRvF(BA;oJ=ezSqQ7n0t31ZRIM?4P8 z%Mt|!O_$qZ2fyqqKVl0TWJA7<85aXGv$%vWyGv__UUS0N&G!?QFCY&>$||aA$66-s zmXJB!+7S~5wcXj3F_vZIq)q;tSbkSqEXMkQN>sv@s9(~mP>o$_fS zaWq|W=KV*Jb9$2x7x&w?Kapwy#4xC{$G-V6s6_r?vKuBM#_Qs0%gH!>{@;JBSOWy(J9Un_f zcS-AocgQInFeHI$-d-rz2ji-wOr<7zTOClVigq(0{Ef2sCZ>I-;a7`@1iz6v*!Oc_ z^4VQrC;_(WQh%}Cb%i+_JHA{v$m6}hg_o*v;#3Wu9JpM72eu$tKrV#?AMfK;16;`h z@c9NUSP{#MYvVm=JqNcVNoaLV(hYjbF@{)J{E6?n<+@v4X5C@I<jC_%@*4|M|gs#!Z<44@{y5gn#oUt1Pq{fX)i$#AW0Pwa< zFY{))jGIl`4^E%S&&b_$!)QkR-nIq5c}=W~?sxmZcG9d`73RvIPr~pbs+R&BU$h}% zdPVmt+rrD<_Iy=z`O)CFx}gC~(mZmwoRMVu!~OWwJu=DKwu(A)1#6eaCN-kt5m5^F zPvfO7qWJNO%`Cf(K?;T{ku@SpSr5|e<#oHs=eMKkLLh!m2b6E3O=*|VZS?}d$g|)Z z`7#Q2{y8W(NFWv9tjjn~N|<`GDE!&W5;bKg`Uvan5{%JcbWEj+y250x=KN`YZKsSy zM3r0$onqSnqT_v>d^x9T*`Aj8df`!o!1?ekFUf3-h|qPFFRb3wQ`1}NHEqUCYyD%Y zddls27|w;4N<*rn6LO-FL(3~GwCO2J!z-K^Gpk*Jz}9g1Dc*n2n$C*EXAIsk)kS&Y z#4G~NYg#Zcp7szk69Zk>nfy+>)JHBhdYp0$)Iz1?li6E+4K04;E*hFzS&M$|{Z7K^ zlk!q@_rw#5UNW^)53#6oJGxv!=Z!wdZi^Ng0+1#{m|jlVj?C7c`@zV9uK;EToQi=? zI&uv`gS>-R&c0c?pe20KhYS!Bzziv~o(xUV=|b9GP?yu@lznsR(iuh-jS1qXjj`F- zZaG<^c`DyGsi{nmM6raN!jHX8S8dC=AUY$35Snr*e*rG@b3nO)4c04BvDwHj8-Gi~ z;F`+?7oRDP-(j=(i4eS&-bPs-C_>G;aG}7z$iyQBdShK+(WNmvu2LY?_44oYC40s1 zvnUv@1<;|#RvMUrA9>Zyue3VV58wGgE!3aPTh+#MO$9I?2YFukCP17w0Zy=tW^%B$ zUdC3_Q_s8VxRHHOK+i{*H+6}@cP zZ@MKmsL_ls!3u0FEuD!DJzqCS1pi!K*@DjgrsGVQ=IK==mwDVh%G+;~v1%2S|e0Ox)6gr3ZqYe`_Z-|GUOZdUbqbrW!d=oUV2j&~_Q zUu=q(C9plT>{pD*;M|7rU05cXB{`cwZ4-+18%g z2ZFEu=pD7#Q^pbLhnUAEH zBBmK-p?({7=3*CPXIML&|IYdXdOX1Inj*?)+b>?Y;N`MK zch9|Ru8#}L*ATtqeF4xB-%?UkLVE{ltJ)y~hz-C(y{1m`@S|-R_frY;4=i#kt;qdH zdCxn>4le2UI1g4o?LwI8FIV&Qlj^=BEXzEXwW*G3;{PtQgI&alqdOPx2`K#V@m1oygvSWD)uo1!2;_EH zP`TX0O?C|7IXo8A#(ikeTs1E5$IB>Nm5v}Kwh@Th>^}Gd3xbyX$O5xLTZbcHim_^x z1MSwFViYCAJtZ&5d9p0ygrMghDbgUl7~r|7%PjVqb2Sxsojn@-(O@H+HTws8;LsFT zQV!tD8H(h7?pKtDEFR!&k93&hT;=^#GLZd~&Y#e3C}h z5mW2an=6Y`mvVij;NyC6QqyBGD|6*Pt_M1@LHWVC(t6QQM?m#Pc{@!A3wFh|ywa=p zgVOB5mfIXgI)9eVC@7V?K8~qbTIi^W4>vMfIV(p8Url z@W~Dv2pj4{-euXVNG&!Zj?t_P2zqq)9%?ej(OYZ_o~ZcwHVi{Zj?Ddg<)~fTQC&Q{ zrn+mBRKk4yP<#*iIWt!#csXg|)>d_W_8HiV*1{`E_GwU}!UuV zzaVebmt4Inb+59u3PVjGb#iWW|7aZwTt(lX{bAncF$j#$>;xu_CCMC1%jSPo?Anm} zb}2UII6F`p`%(7_26T8-d{Bliw;yD?O+|fAAe#asha4owHbKS9fUyNG+!T3xR4d(?Eb?Oj`*t-Gs4g!k(`v z`(kjFbsHc_a+&AmUvWev`Isej40 zR;~hi2UwTSv-_ncQIJ@McT&Rpi3A=~YrS8G=3n+0ci%Nrs^gf^iIfDb&Ha zL~`_Rrvx)S9`V3_ud>KRc7#n|os4K}xP^|`okfr{E$D_Ke-DU~Br4KGNkKN*$%Q#d zaocdjBU!UAm_*3PK@$bhhJ2LDJ>WX3!@59F764SglL-D0RJEeVDiPdtvip z`OeHCiIH-Rsfg04ZZNZOp>(_3(k|!QxL3EIOBPe?xpCsofY!=V7ljau#psZYWKJJi zOTQS7!m&fw3C9IfI4ot1Klr-m{)8hF{Y_B`Z_T_>AQiAb19!tI=n46=7Vo~D^@I1q7!M`NCE*`O!@c@?Fs=nx=We6mo7YDpDofZw_+Dq(PH7u}R zXKuIDy(Vw_@*YDNU5M~s;yce1SKi_|F4fqou428Tm%4>mz2x`36TVAraw{e*(dF>* zhVYhK?5Nm0&0uE#^WQt!kPbDIon4NDKSsbobz?~5dOxF2`KU3U!LCZP@qOSa;JfT& z>Mbs|%in?T8;LWWJOyjmJvi<7&G5}d$z@Zip}6!xvRFF?!wKk>C+|6=W2#GWI`?=( z#QyjtyS9_f&C-{^rZ4mFrHp`x&Mp^SI1R+2PCk5l1R=$V6k!V9+XXo^MvTGQk{g`c zOhCL@s~oPWh5*h8X_<5d#<#1O;9U~6543%e$#SrO$v&ryxiOnUFs8uBiOPl-dz2#6oAd- z-h!UJ#y3UtR%#VP>I0lLLU2ghvg(p$3D+VZP<3rw4-H2C)&wOriVdcMC}xdp9*JjML6K9lmziVv=DU;$SL+nE$nU8PZbznPm9tM+=pR{OAOq^-`)o@w>ruM=q4I{3Rz$@&}<9{K@S;AK0A*zSb8| z)#HPl30s7^+;a%gxt~6-^p`4K1-l{P(BWEgDeVCI)N@i+IArtQi+<#M@@bCUH01}K zX5UTAov<(D(~qipvJuEh!k4DuVaq+(IcSjn`Zm`-xw!eQ>sn;C*uiIs?eOJ0P*mO658S-#L92D4OLs_hg`od<2Q4S1`T%2+Ge zNSDkem0u1xrgg!q9UQ59mSw$WGt!SO%F{{5xdiPE=@|HTI_r1Zt_GbMdOH#kqe#_~y z=H$K$=ZjP?8F9K%ve!LNgcD-*gs%7cF4!VDmG&3T=&lpUTJq%zS%*R{)W&qp!eNc_ z{o(zDij~>Za*)J3uN~T4*W*Ku1tdl7cOOr^V^Q&<5$MptuN~MEaonkmf5cz3VQ|IO zC^4c=dcfNqLqjO~P`d8i-*8x0|)Imj&P&M{XgW?t>Vp~XaPN;`Gp_*o57WSnfDjIrjUd zU2y~0QQcnioFsP;OFstD$G==xC3*nfp%0J0Ji?DW)xy%()4lqhX{Dy8A8*bEcVtwc z^N=xJ7jH?}yQ{xvQ0v|ER0?N-5H(fN*&g$62ovVG;|=8|mXaU)F2z#F6vgX_p7`k` z+)&acxFb{O5NSr*B2*%PbFN+521HmK3XURwg+DExwdeZfZ;y0DdNAp2OOs(@#EQ2t zrdk8c4o~~4ogChnFmNdz*3CTkq-1Y;!u90MM2vmB-|sQdvVMwo*P!QthY@EFTMtjawqb7<%JkfigZw z5k6GQdSM<&8!0P;nWL)OkFCaapqTLh)lYuKV+tXsy3oqXb^)Qtgp-hFx*Q|0=LudC z8frNB>;U-$nFZ2c1LYW!UxYsI^?~DP+?C7vDo3XC0`u7q@?GZ{^nUOr*9Twt<>pJ5 zMaGIa=4Gnwy&B{yl)Z7=M&FQkZrb-Nc3+jjI<8C}j31<$N z4+a`nVE0140cAi0BmyX}!e&ajIng^_IYcfzM&|Jzl2* zs8z%xjkik|Wti2#P8UyOsMGz=MK4h-rgAqaA5FEO9l&k^FEg#go}Wu*FH)T`?FoNo ze~Ft=X9@3`X$Td?a0E}=|5cb9Y6c2z(uH9Ys>C@SOXNtJ zCY6`VgkdcrgHjiCPm;=A44Kkj(XoCHJQMRXnM2$!n`n;6YdHD&gPMh#1oI*`YmZhI z<$zgCl^pV%%xAPMZGUNp#Hxxxa1lx2GmP9x3DWWj_Y%k!7d6kqEo12^*xbGpY*D~& zlT##VCV9v>L>$PnQ122etv3=u3}O%8Lj%3>^&Z!(y?_O6NU{Fgd&h#f-S_(2uYk}f6?I5S{1nME+Py)w| ziva#TBJ{vLz&>p&KmRh`D;|a;-3FCovrW*9rXaVciia(k0m_xzU!x9V%{9{o2}O(? z4w_x)>Ggf5-be;sI+|n^x`f&kmD`mW*E1wfp**CghXZXMoSizuWM+7I&}!BiC@SarsUeX z-x3u`=;(rqia(=0bp1tSlC>(SpuQ}Xt($IF)xy5=KsKEu-2Vpacft`lt2-(F-JsMH z=d_4-D&A%TKTfEs4j2qLe4RBEM@+(4=Sp5!XtahLz;A)4KAE@#$7LwB z?J!F+>^+|O{g~Tvg5!lKB~qFrmo6ZV_@+q`OF)ZGhPxnsH;WoApcggLN?o}6N?-T2 zw25yKMBV5ccwTisX;^utqzn=P=DCYf{lkU1^&f z=v;vFyLSh?2M+l(@p5onS*4o^mi(^r8>b6!Dc>wmiZi?bmjjyUy_9^^>XD)zu;^*z zDqt>+I#Rp<7aSJOeY~e%FPkEb6F3J4s~lP^qYhl=MvYxA1g92Q%PtG)Rc+Q3AR3=L z#NiL4>Lnv-P7Jy}Av#$H>br!$sHDN)c`@AGL6 ze-wCs;v7KdW^A9bGrr-r<133$EjHU@plt9n`(jYltx+)W_MvWB$N9{%pb zBhlYSoprfF4mI^b*PLo597LW|3ZGssZr){LKK)8Y)IUp#S1}sf!x-qNACxDmO#6R) z{nxTu^igdGrFA_&f7nvFFCP^9Ev28Gw7G@SPzAp7sulkt)!LTYKAB9pEId*JrK_0S z?1&g?Lo&=bp^UNre-;26%Z-M?^fA6j88{2`q~9>_mxGe)s2mO8&-Z-iCTD%5(}lx^ z_3|qVE^&RJFsd8MbuR)|nXiYoQC7-a{7P{(i!%TUP?od6LusWedGIVyg=@@+W`N9G zvvGPjVYYtg$Ae8F0r>Bjxtn_`ICI&NATg}#W8Ybp8i*O&WH)DrU8`Q4#jjKlT<-a6g_3SZk&P>4L_2ZIAy{a)>48h6K z`Et#7#{v1mUt}c>QmhRN(-Qg3BZ5&(c?td;Zi$KtlbFyyG4A43rK|I7ecRFdVI^N# ztaonU-s0Qz?oPQ(|LIpau*MDzh5?@K(m0O3*qv|eTV_j+7E)~88W8U zw@_dYnyOC><6+XJ1b+Ko7r@6q-zW0^0t)b0(c&(&H0N}~da_YHIn_XIyRS#i1K_AND%rS_gK ziTf5)i-p-F71PU1Pwe?2;0bkfN{3CyjYglJclWLGMl`;P$TYrJ{u9&51DOi{nh#W>NF@~&^?|#P{Kq<$ zn|C3cf^YLf^w4a}f{DDCqC`EkNO!Cwgh;j=uzGfF81Z;m`2t)9C|inTB*e(}E1}i% z^4j812_Wd1|F`*^}DBKH#oEx3G)qEB{F73G)(YF^`R?%)k7i!F=xJ?(Y*ms-Lz zRTRVqP-KSG8)4syzmrxI@vUS_XZCE4W3@H{HJ&6rmqdrdL+U2Yp%1-fyP-~nS4Olf zM^V3Y#4n@5@Wy=dBg|IN^Q%j{F&U&SBr@oYZw+&IL~2c(*smDv$gc2P#&EB$(w+LJ zKpmt8MPpK&x;GH?bn#7%W{Wo;Sw+P^w)2z0tZ$F-$4z~up&dfKvfNEr{MwpjeR-E_2|NHqQNF5FA9#jy;9rHyjE|OJJA=)~J1Wd73V9bX7&SoA7C;2SHcJ1+S(Goj2jqO`}o;Z7Z^C z=j%SGpD4%kv{UDtC1l8B%|xNn(*x?-?PSbU=`YV+1qb^H`(MJc9)8g$8Ngb2C2zGgwHCl?RnRs9;H)h1k%1kC()`B&20ulaCO z7e>$`fD2Z&Sed1VEJqpQ#pd$oy_GNO!G9`R$jJOAwCiDWO8?@hD1$=Ks@Fa0A?z~C zJht;IMUEa&jyV_ai~=b<~#xw7Jn# zjlDJlPjbz?g^5z+U7+P{;=ZdB5h-TN>=pQMUF7YWS^k)^1;bK=U0bAl4zu~ z;qHA=H8S_~cf)iYr!3Q*<6~4|k3gb|EaA|-w3Wm;*5Uo%{VH#B@q^;E38%C~26w3I ziPv((lo+%(vww{wlE*c#psr8($4X_;H=6Q|P9Com{%TLK&-^4o*_yC0bt`cB>Nr7E zDHhc0WF@0>i`d-%Aut8`hWAIxl+ry3D7~E}c!@B@AK4=9H%CamO6jLTef-5nZl(Az z;@{;~i&}k#bGOXvw|s_+Yxr@jH-Fu_4N9BW2`fdWWwxVoC@Z*Wk-nwVx zjaQhF`)7FP{+~nK3^K3MhEg{r%O1q_aC-U+1lhH(BgR6J&L{n~}F>ctF zMcXw;WodhUt7BI}q==)U(%ZYgQ^NFrstTXLS+Gi3{Sw_xoMGMr?bA|PN(sat&fix) z6QrM<1)tLKON#`Pf4=V3VmR*)C ztnNep#SpYEpSaH%59Q6_AS;{mAC4G(_&5V(?z*-g>azA=)ON|P#~7}CprroW#h zny@gd?9H}a$^A%|4ic5;QxNQ>a#*~aH9Pm}HNEyD3SLL^B}4%{V}1rxlTVYv9ba_< zh01xR>L0Ovu5JMTPRw*Dcqt~CyvQZxW>FOTGRIi0z;$e1%mMA5>=`r6p_iHS*nCi7G7;-TB$|^wyWjkxLK82ro?kN672mI;h>G z3wA>=quE3{;k@evPB+w9GDa_9AC>EJzrEowq9nclhLo#VWI5>h8hy|->pgcg%LF{< zJiKl2U3$MTWwJOOWLjbr(Rc4$n9SpJWiOhRanHeJ@!40l`~u-%4=f8&@jv$6+b?pwV7%SMR}^#_G<5Cj|6{Ql|{i!B+1> zY+KctJ?iN~w*K?!*4uXy-|_4VRtD%}1x#X0Md?nZ8CcretR; z@VEUL>}=b0*#+P7H9tK!;8fA!qX#`9_uLRt)fSoppNj{6j!XN|Idrc+O>w;0bIST{ z_VOZWr1;7G$NXL&-Yi=GZhA&%k!KCwJsqgsId6~Vr8S}uc~TIUtn!h>Kj>Jz$8D;s z#PUIZFe2=uziFiL7=(&$G1P^oH_r#@nBg!Ww{5WFrJ3p_5?427+Fy##Q>cGYb?z<3 z_IiQqPTCEYo{;^JhG=>kxnDTMIun@CfwYcToix*5*5!k##$D%O-o48=445;F5z(@E zjz8&IPQPLJ5`lAii4dejGglKQy|@Xub=qQo62E+bSHsr3bz5q;7hh=@F<9^&-=63r>T&J~iKUDu{2~@cdF`*alq+Ae zBxg)6!>szU!$1CdW9JIaD^PJA^*9i_)yji4L>7#dcK5O$*w2Draq0`tq;!M!p5!(n zXaz@oIIF*=IRa(>0TuuyNf}^_J!XKsg>}v6sp@0hP^5SocB1-~$hX`H7cPwP6TxqNP=l#Yz@EtwpZa+CuH=bo5^XW@FN46~-fu1m5C5&;IhB_JEJQzWqTS5wOfO!l_=WUCY1RZ$7KO!Bz{|Zj-n@a4wYqep}4{|8AJ%G>R2MMGtl33Se z=y{&5dyOvz{5@4j#Qn*2BE1Wx;oR;KvOm=hk(R1@A#eCS*_kwUlcG%;w7O~7W|TgX zWjbNa&k{1WRr8bj_4bxTkU-8|%b|5?`Dw2hc%XG*0p!~`C-!v6Ux(hQ-vCVI=-*Te zdVcdLdmt{s^3tXm?|8emam*#HhrD#D5;h^8d2aadHJvD_)*Zn+vSx(2oqa6PFMB+K zj{nldO|Lo-vNO6T-+w|@r)5iU<}+|D?o2Qpw`j>uk{4RxFrXNy1$RgJM^yN4q$0(l z`*oAYM_a9XeeO?;wMhW@F73zBr0~iF2fvgCm(=i-7^ed`qnAo0X}yU~;N>3^omjzk z-6udJM57>>?Ln`E{<-mC#3S9}sHmddm_AKVNy%2*Q?x}^8D4ud=gl{}ur0>B^Wxu4nEnYkhpqHF9%+umz#Jtf$@BVyGH3NH9}Km5u)ACebQ#2N zI@7-WWD-82Zn@cb-Ft-eJP?hgU3}H*9!6TfW0p%GGzIx`@rK#KU*+~$oEpk`=41+d zgUvoyQBh4X@=8wBd+r8<<=dyYf321JKxd{LV;*r+rzd{vt@>*`*pt%XFT;8$C$XS> z>i!2I(g;%~OAUai~k{Fmt)Yk z>73k$7y_-RReTvdN6NR{^`AEO&T{qV%4DkTGiNu_SsAQkl!$cF}WXzqC65E{CR*bX>99WJQg?+cjhmG#!9BF)J|5TgF zufL>?oMCD=?hq`7TZATbMV=GpgxYv2GN6(41m_Jz1lFe)-~28$YDPqfD&H~Dz(RZ9 zVV>al$f~(xsu8R#7*AeX%VfJ`T@M8o>w91kW??#Mk&wuUCxpKhS8g+gS8qC%JEcA~ zo-q$Rv|T`5&$!|wXo-`B>Qoi2bQBP48m?G~5-s`7Cnonu)7kC(`rIaVCOsH9SH4^K z9ehIH_O7Ggll}y;FEczrSZDzMumm+O^h*TH)$3=w9Q!QpmGDexf1hpP!s-NGj{S!e z#`hu9N_h3JNwN%Q zx0{E8&H{SDrAY0r?)NB5&-(ni$OWmsPbNK4jZE)!C}VT1(N;yaW2Q_|U{+!2nLj>t+BIi`$Qwo`o_Z=&;{jz9}1ZIWB zM^Q=iQju|^52I^r-4BA^uH=SQTrax5k%~Nv8f8U_A;Z5HI=>aROt;+qeI!(@_}D%k zNs@=J#=g|}m!MJ>-f2B1iVYKzcOXc<$=#(wF!5kKa^#n4zlQsbG|aa&5{42#(;;<_4ZR%&=?@1zvcb`>&KD*lvkFN9-ucTb=-oG)N*FdU?TmlO7S3DT?W73CqYW8 z@l;&dz%QGLM<#=6GtL_?V{?DtozvPg7O3h|+FSCoJ=7KdW`oo{;-QFu&5)UVc7&jO z<^{uEmtkG7@%s=Z_B^>JGd+O!O?b#V%jXIrxbL`<;X4y1Snc_A24Es%x4IwlPt*0i zfTep6tOMHEGzVPt!$LL^sk`@$0s*O)JSmG8}zkkLd^)C4{rU? zox0O;o6_wSOs63x*}n_-hUjUeQIyo*ci#X}{+`v@{P)KG_m{H<)^hr=2ac_PxI0flMWLIgWS9;7T4~$-Qi8=81J=}dV8<1CVI&`Hg)N@ zx$ohYWWgo4rFI8e?e5FO8um2&C^FEVRdLtYkRdzH?q|UL^~HqlBu+oiL-SWjFzG}? zOBHH7%}UAUfhe{!9OVCZ&PzRh8+}V<1#VzMOAm-WdV2w`C1?N2gj)VXUXAmemWv0Q z$^IX^n#U-P!e%hKqhQ-r2k9N*j%sA8ln>7KXsNO_{`pd^DI{EK@fD-z;7N-0ig?4* zVmC&73j@!R-PD3yEQBd4+J&`6(xj5X@Y_Y2ug|biA45UZZ3DCJRP#JWZmT@&H#sZ9 z=@J2@i*AAsNCtAlWkSWVX8sW9Pb%7hTJu5Lps&JjyTC|si(yT*6y2$<``JUamNm1} zVZ{r1_VKYwoXn!Pjb76Y0+?a6iFyqnO*6cTP(rO21X407J5_8tD4|YAp>UY;E)-2_2hVy89)eCMfurouk`^}I(YQFJ5 zRQG{%Pf){70_5W-Tlj7B?n9=#=4Wql#3t8@nX$BxKLY5%2FCLslZ;1v(obz{9-are zp*BS#*aYSZ67_K5L43*r$0w=_X zmu&o^q!uf#tZ8#MZ?bFr?EI$&iN(!7wT8NxU+?7@2}#ktOJ}^oTz?T%YdLi6H_~5EOttjB z!Aw&mIi(~ihNEzm>P&W|1`iodl!{r ziko6>Y7gXp-IT~gBWe)DdpA|&e(z1M?)6)y6G%%_3CIuDk-9qw_K~-|6a73A+_E$l zYF9FTO|bVy$tPpe0T9wWBdl3JR(Z3Qv_~wb9YE|>M6>u6>P6LW%#i;(!#|gb*RjuT zXv0jWT*T2vneViRB%njvyx_CN_JsUC^Ii;ZiN+E)7lQ)T<3~5ogai7)aGqeeF-C_r zLurr8;Ik<=$NsIm-QlazQ;+v3eNTC@QQa9I3cvlsjAo>FApz1Vk7B4p@9ZXtAELWK zuMGIt3K5M9(7CEttXwqG@7Q&|<;{_Lt7}tq%^Mv4D~IKqDS<7v27;drlNFU9=KFeD z2FsMQ%W!W0u2m&vcebAY{KcVmS6F^@ACmjQK)Vchw(rIu@cwZw3RK3&gRh@_Qc?Qk z4t3(VfLR~l9Yg!ijes${H07n@(pGbQhz|d?cP1u|dJ!__6d4a~PQcwv{4-X{&N6c- z(Eicp;^1fhoDM|qB#OE@9K65;Rldi4M*emC`;JW-16tJ?`#Ztw570+4G-Z&8Tx(x< z25Mw__lPrZAm6vmrIM;^kyIBZ1q>zP+jXr(w*YC;Xq7cPdRVdmB<0Jex&tbBT?2i0{CgS!GI8s zM-rtFRue`dGB%-K2nth1P8%KM_8vo%@6T^`&HnuJKzetihUKJJv?{E>wk6XQ1vSzM zym(gPV+Zw4x>Xb1bz>~ZpS<#wRW&?>{1DN|+VK8}Q_T8Px3KvmB*$!CFl{PF-jVEWk; zuRK%pa=;i4WdQ2$PBse60>j01D9f*8>xC#fd_jvJ^)m@u)%H&AAB&r1t=|SHr3jJ? zApI@hkucYlC6|_Xv4vJs-*!#3h~u=N16yI^gG)>UX*EXIG+U79FR9AhyI=a^#50-C z#OzGp=E*5X)ot@6KXP-)>^S)9Xx;1l+2L_;GxIyBBt{V zM{2y`$l@&vMb5zaFX?bx0M5<J|;(klJ_iCZnzx2>LLb5G@V(8IUnT&U5I2ia$OvhfR6FR6$X8 z77&`zA(~4QO#d=zSsRvHh@CwVdoErN8J!FGrL})v;m@`oBp))p<; zf1h7=H3gx#>`h+&Y|6zAB9QLCH+7yawSKR7ahXDgAQFgblra8I`cZh|>l0k9KV4v) z@gJxGI#z^D8tCkI(LqsKYv|5CDTXZ%4A6~3Bvd4R?}4DJ^3+Y@wM3Cnv5M{w0wj&x zVyx2B{0`l* zhabkJ4g|l3iToD)$SH`Yo}`A+XDBG?ILXgvuCoi#x=b}?AwHbSHwTVCmO3Ad%WWW6 ztwn}0M3ek6EAeKCEDzs&D)`6EGl>dTyt!;d+zVGxV|M`dcDG_uxPa=(rc#jQt=Ot> zqZbxNB80qxffU-EcZiboKfN-lv@E2G2V_J4b4mR=r%4cGa%`$OV^XTGZ#mQA$e3i! zWP-C09$#e>@tjoU5H=+%PB6BqGNa?*t-Ew`|DDKuZwS#VPH@X}j08krnIQJsXG~;n zC5wC+^&kRFdB!7f>wMwpowC?s7p^NWu?O54hGcKmDyIB0$lj0;K42y?GHexdsv=PR-qlUkGAGi)B8+PObG>&)13VWxE*BZaZpK-W< z8c)~6a^c!NiLhwSPa%HKDaPD}{ zW`1xJ8X+vHi?BpGLejff7nF+N8(-wZlK7H`?WsYaOJ)ds{k$3wEOxXvK0h}wfpC$K zBjI)I)2;b z_;wlnGuBMzxpwe8C*NeRNIO|-y^haF-*)hXT&BkP%SMSkU4Dtawt2yIgD_K-*=E1R znRr0ek1=!2nh;eiXBNy&+uv%v;^kialtV8CJS<)WXY)B8I~1UC%s2o-bOwzJqs~NL z`3ZT_=*R?L7B2^&Y+*~I(!)98{ev|}67~Zi@xq=WzI~OhR#g{rNAwV-CG-hOQMtti7?t;56C05v#JfhWy^V3tWed^1RA(VBsd7QTs2?ai zpt4KLXttk_E?M=6s1puBkO*noVkQTKVp3n~8K%jAqpQXc<|^v`P-_OkxF$6!j|;W3 z{9T#I|JgIP@!P!zZf1J$T^3S|?>IS^GgsSm!=2wp)jt!&&R!sn9CUll-&pH?J-TF6 z8Pj*$7hy8OL~@?QkX2(J=pOfGo!kD<2~YQP6eE6^wswaf5*Hr0Tv_ZKFdjT~wLVYX zA@l!#4XJ(@tP&1biKxT@QDF( z`adLMVH3M+y)`Gqa5$g$c56WnhA4UdLFh@D2aKt~DzJ`)VMJpe2AYKo)?za+D7Sfg zWM>ea+4C#M%-M3~pEFu#S^Ke+n5fzjBBBn&9!mlRX!hG{i}7R*zP&C&+`hq57N;v= z&oLovkyB{Ts3sC7Tg1Tq_c0F_K^^;t9}H=vL4(p>M3KJ-x^kSVOX0N;A6bZ;M-0Qt z%>ui=Wv<=)7%;4=B00c#LDZIyQF^rFQUC>A7;IXDR94g_lNefMF6O- zjYOkv9tnQ`@eQP~LT9}9+)(JwXcY(oj@X%o3;f$@#Uk*r*D}h>HvCYG%HdrDMzSk# zRb3Odza>BV!YstHn0HOfZWbasUVkHo6%mHgo9^ixp63)zaOVU%q-FxuVYKrIp2C*h zbNp5w_yRdd9Apk48G@gGR4ixu=LRed&3?fAqzSC5!;!<0tm{#o77(VYdVp`{Yvb2t z2&xWG;oQQ+IlN4>3fjaM?K3au?`r9Z+#c5}^OMKsKcbNt zh4K9?ShMXXMCyXOp!fxG&hUT=A>8Ai{&gO(+O=V~KL9xY@cna4$Nt!|tS;I4Vr2&? z3j&w}&lCwnMeZgaLR`v#H#Xap)tUPxEE(K|UVTj5`B_xv)U&OvPXp1*{mM}oK2)IG zG}f)SXi*-xt$@Z6z5&g2-&}F0j!)w->srto1u)dsFPPm3i z!2eQEqi_H)Vef=xknh+Al=_D~>~Z*Eww@rG6DkMh0TV>Me~&jK>~og^g!AHDnM1cq zYCakJ@gwMM!I8W-1b>*ujt|<7k9kClzpKy6_D)M~Run|dnIJOz)hddO4{Q`|-&^|I z5{#mHN!YMus?!yhxN08kc)B_`p`#~)vTNh zF>inVw*YaJS29Yjk;NuwOSD>g(&s;ymFa=1It#7dZO7A@#Vznn=f{{Z?u-BcXCKP@ zb9g}L#HEip>EN=*j&!iu$a|k4-n~7JfmKg?UnVY4HY5c%<4~{F1W^yZvdkNC=^?IbXk}Cc*as*qKQ#mB;x6{|OS)^03Ft&r7wgo~)b~k-CywXUW_B;G9vd#O zd?kFHm^O>D<5v-Mbd<8!6jc(_eo@TGHk@(D1vW6Xj~P{-sFztG^~KhHyc0qWy#(EX zyb042orB7-e#{aSFU_XAtd`3L8B8|gE$`1ID$`2L0dTFB!AVe_yYZ~|SNW65p{T;4 zq|;mhw^WiWH~~@7*3vUuj!{XVf|p#>6rU=b7R|A;7X|zon)1RULnizWOc0K#V-_?q z#f#7P^w0Krmi76~d@&1-Mo)ZJ^G}3WWdi>}?fMqAZMLVWH-8yll+3ZZ+#d*cp%(b* z<26R5UTM$`S0+r<8lS@r738=g7t@xBfg`JNxD60%e|3{~Md^pY;{Dm8froL`WMTcZ z_4yS(-`_HvS!q@sTV(>yhdGbmgxY?@<>BNPuLmr1T_f?b0(6+ZQHjFzzQPC#KUl60FA<$^16~`r11W9nd@5@alPh3V5YU9ef@w{q3 zd#4YzX@msH%-umUbAHLLSy+Oeb1a|odLXGeL}@f%gia(x=0{aiEQd_s%hz%X9)9G3 z(iNe}Kb}Kd6CYeLsQ!0yZX3IXCTllv?E~LxE|*Pp7$mxDdu;lN;8BwpRDv!U_FkM= zvJdVGxDrbb1LM>cD?8&scSozO8k2|_T>=WgZ*g#bGY$Tg#6eDqbHV{QAniaLPZg#= zNvl4C^PO-&IH0>(n1#|iy35)(^{W&&N--~7;%nJg<#O;Wd;U{Sr80MGZVYMFoAOmi z{^`xr+dT{b-kL@R$w5$g*(Lr%tm)Tuh}K`<{`uHd6P$4c{iPW)OMiddj`4(eN(gM` zkG>V6QWf0r&6F=zwrl^iCX*)$s3%4yLv2b&EP34`b4|Z=4Xb7!&BM-ihi(?g-oGbO zu(xF{ux(W~a}v0J5xRpzZD=*~F!Q=E7XinRac=)N-}aTbWMC~8!Dx@W_PsgrL3+ow z0Q$|qqicI+36k|=eQP#T8aF4R|NlmSinW>IJmj?e1S?QjLbUh;|3Yp^NJW>pK-kF# zltc=jDSysOgbm_AlDxgRRxXkopPY}>ryQk_h#NPu2@cL;wJ1X`&%<{jGZ?r5W9jI@ zAvtH%lTp6NRt0!#<#p$PFw|D69xjdz$WmXA%P0sFub87Q1g6AB_nkc0Lro; zeCqB-0$7`KS#hU=(WxA4JM8Ik-GXE2|GaeL!d8MtlhtRw-V-R=S4hJx!)~#f?WT!6 zeTe4k?aKM>W3`CpoGz!AIp=xe*~PVY)7L=~BfsM>b`@bTnE7&@esu-ebu*~7Qm2L@ zphgI|m~|cS9IBt_7O0}WFohVdH9x*lP73fCVc?Sf_v zXn>-JRhv?YrEyrpc=^!9WRK=Mv`+p*cqqkI(Ug=B(IA@7PQY&XrC4|mgDoe}R-AhE zOE&jidiFJ3lJ_1dPCvcS&p_vu8V7FG&?;)se?w`W5c1;Z>dV)$lpHgp*{CZ569F+@ zhu`MgL~#L*XOhP_7k1RWezL*-q}dYv-B%VmJ(#4JF^fI&n+Il!;FAFn!7W?*{XY)_ zD2WbAVD(R*MD-NQAlDg};9Ie2K5Lv-3*i_Akof#K0@gcdm%?TKH+fQ-Zq%&ildB2w zVQ~Hk+-~I_)qKjH z+np8Qlz%n<%}YgKBCg=yZO3d=P&<^d8A_D?aLh_onO%CP}jtLn3#m4XInbkvn#NqsWgk%}>=uOMj+qQ7c? z&Wa7x%Ny+%+WC>IeeL|riYoUT&A)syushjdUYM=tS6GG`P}I6AeIWQw^@ni0eoEYi zs(2}C%_^g{Qyn#B?(@^ufZpr$U*T)TTYW^HcF@ z_xM;$>X(?f^Tk|b^u z$bK1eF(gbdg1Bhs3a=)^Znnp(-(P2pm!40oPLkq55h|=8EBrTLa?WzWt9+Y#*z0@P zK|1d%Kbq^rz*N=xlSfxfeZHnHnXV7)`NSl7VUuX%9<_0=6v3L-Adc(rItQs^DHotm zNP9eMPJv9}VE6%AoT(b-RhYZp58EYH_&%{&eTaxHHws1874$}9vBFpK9|WKfv;4=! zI14iiVWc4XCoOi#mfEYT4%(k1;QmAY+tBtgE3C1*tqcI1^0Ii1wXu0oz5Z9ZFgQ=; zmn97oGkGXg{W*ET0AS-~30gxXENQ|j4%X9VJ;O|XQPz0rYPl}3ZL~$8ypmK_he-PK zK)Q2cYMkFrRCEu0Fn@!VL<;xAqSzA4q&&o}c)`)a7!3+b^YwHSuJ&r^nr=io_`(}J z)NI2l;x?&Zda{A{y9&bq|V^-ZQjvsHxieyJM?8!6+( zmXHKloIhs=zyEq+*WA@+4!Lz2>WBi<CgAW^`2|i(@K$G&Y27Zd^({n}t1MMv za%b%(m4LHMoWO53H!oO`9L0_)MOwYq;C+XWHhD5$fd)`QkUEtlv>8v%<3%ix5F+BC z)LqxL`yK4?J4GXtpsImgX)b`$bgKl2Nw>}9VAm(_az-H;t@EN?_pnsT6CEcD9X@X& zQ}FRc35 zczD|fz_Ho!RmeL(jw6H(6{Y%rzv@cQ3iHY1ER)(s6N~b)H@1Xf&NobU)C6drOKDXO zeby=QCkHzjn3wZU6tALm#iy*BBbChbOKSFeHP~Ib>nxK6SWT#;Pt<0PtV%IW_vwMG zWSl}0R<7V-q{N4vb<`Hi-8^~iO%m_~kjD4*x`g)RQtO&uxVN1SQkc*xBHywtoJ$#) z*0Y6(?C4NwMhSlKoy`eW7nj#iVK-!#*WsVq!{UBu7?zl|z)g%eJY~dKFOPFz4RWpc zA)fnpcoOcgWJrFG-SJiE-5aP+Ln8MPO>hjXxX$rZ792GyAGwHWP9p^{5cwXGAy{y# zzI<_066RwT%}72=$i8)ignA(|RWM}FvK1p|ci&s4Ec8X!u3dn1b!w5GufQCY@-XHg zE;B4S1A%{#5osT153?d&eJHQ^_+eOz>ywZHHS2$US^#BS5J6sW)Z6L(3)LS18=_5a z#U>%Ti|v~TI>!vhc@tIm9Loc;Gj(B*i>`5x>R@1do5$Hk#(z@pEQDKErUQSzS@X3I zA5)arAPf9BG=Jn+eNWB;LL6F3km&W{oZ*|ECQvSy`K59R@_$ihiS92fnlxih2e(?Gn^gHf}&d4UBYq#$EFhW&0>SqfOB*E}~iF0qI)QTwN-Y3)yLwFBu}?-841G zuH!=+->pNQyV}Y0FmOLkO1tS|8_BH~|ng<;|}}h)cPLTZ+vCN=8>xc%!-?d>0%)_m|Q~ ze1-sjcO%HJ1T^XcmE&T1E{8>(KL-p^rKip1K8;`R*{|ByZyG@CnRJ9U^GJc#Rg||c z9sLe*pTt@?aL+vJF?seEe>hqqq=_~GqUTF^Szmt3dKrssOHE$8MR(VT{gUA*x6U)i zcO$1J7mdFNC7vMI%0T~$F=(10JGGha`T-VsFV1+vTq5}t9~NkmK41G%gOB=>U{FkU zU1OJUhkuX^DZ2j~D4V8FGuf%@`kR{$wZo4^jYy*W%}2ZiNyN`8Gf%}XEX`S#!f_ks^(R^MJQVqWzFaesq@*r+>Nm(@`n>BV zD33?BBs-K4m-2TF zJ!CAYU1RWoCF%0^I&47Idt#;wzv8buQhw#nacM8Eh7AxmeO%r<3(IpqcL>x|XUe`* z{rcE@-R7I2KIabgpLAI&S-7WZO_+c?W|ESwAK^0plBBC~)k?W;mamJ9eX;7&g>rkb zL=NfFA3i#;~9y51V1X{VP$OH#s^da0G6 zUy&LRBIeVZd-bP{9YR0f0R_Q@T`plKW$RN82NEXYhEkvv%=>wNO<$k*Tyvujxq-jB zhOA?0I$Uce6P=(?oLu$9n+#tS6DAu88%JQt%#=3jHt`)0mbm{XT!v;6tgJR|Sdq#T zOV`28cum}*1^{pX>LOK%vmaFr@cw_|Wu9cC8I>`cWG%b0tOpFt(X}>Y$4~;Lq{c^Ku=1-FV2mWL52;UsPWr#OTw0n=cE7|v08}@-`2|!M4tA#vg3dibns*l~g%x^_i1I>F{sbxp z>L_4tR3V3}ULYr%lqmb2*YWn>KahtcPEZ0ctXRRV*Am-LV9{nh>L#f$^4Bx;bgwJMr!uhHHS|;R!(qa_+j?MOk zg#9<=_PG-KjO@IWn0@y5#m9Y;J^NYvS_%mBIX9YJr$azb6}|U!G0ug(Kl~E`ZSasY z3Dh58vK9gR;e80jRJlc7-1SbQK#-aQ;x>)O!*3xAfFUL^AdUBcGJz6XM#R7@Z6rkqN-kyMtM5b4WBrq6b-gFA{%FpT|xYt16_dmM; z{{JFr9|8)472iLBPxKKx@%kpSV@zbakndpJP1D7JlXT_XcqAa@%mvQQuW9 z{2}PGifkj&s8GPc`Vi@d_MYh$nCl1(Wb9P*Pt(^wM4fc$ z2-48faGZWgFru@+x9G@ms*$CsL~9KZ8T|G6jde@%ykWE}s0@Abu!KuhFX$#N(}6JJ zhipqBHwZpK7sAHf{Nn`{H}R^wuDD{sGU*!%@6uO^q5>_5w61=3ctjELA#mBO*M5@~ z);U!_X8dTW1H-uzr^v8S;GX?^5}%1gLoisr>jjsFc)|8iiCslyF}Hx@=n&vq**#m| ziw%uGPqX`{Z3?6fss-_E%5PzOUld0)k@Bkzl-a6oGM}8Nx3Q#bcKeffs>-0!C%y>E zCp8Dl6;V8sErJI@a(7}^uho|Z8L#6fKnpO@YM?iG@V?p`Y}cx}_po0$Ko(+hXxf%S zl?^*1Y_p9Y`Blz7W%ifcDe+u;kEi2Vt=j^zgCsLDz=nec6nrJ}eZ zb)NTOF4w@IX`|f%o~zkfYh-Qzy;v8}t~jujawfzvSd_2Pmy%T~?S zEQHV@q2j^waG6enjG32cC1z-`Feh8(3Gcpy%ZSTAZNMVZcnZpg6x6{`Ttzth+~}9rWx0;s2G2j`05Dr`TXsu&*Gq+pOlUx*t<<10$M^@`HIt*$ z565@(M`~M+uol?B#r8x=DN`J8^W}t7j2fC{L%d*bu!h9oy;uO8G37H^y%Zz{h-UZ= zY|xFqxcJLD?#I|5(A5EP57H6jO4{Peo=pEddti`TNOxsUZ{_yH2%7 zU4b}}EHCn>siw`JEv?e&W^@%L(f6?W^}WB2KJ0oFAtRf5U5ZEOT}dE1EB}hGO&nhe zsCwRlx#4&n{G zG+G}&Ea+B0>$vh)Q+QW5!J+p?A4{Ctk#`u4P}+{JlJL?6$wR!?ltg(@eq)9_{u%+; zb!j&iGV##})DqZ7ketAJfU6VWv~j%tjQh^wGavL>2~NOGlU^Pb&*N6zHPzxXqj4u{fXpJuJf z5}ElxIlf~2c~aai+i{Ls5eyj{5vuZw!prCHDW@?_Yh?_JAY>-ReVVtb90pn5b+knd z-eUk|u*I)dS=YrBO}qH!y}`q8m1!n((E`;wa)0g(mer=0>HZr$i~xK6`ke6s60xIC z5xvu#mSbB?LHhnH@}jL=F6I$Zt!3>u&Y^eH91B?~YuBZZ?}TB*Hu-m(n}w7)AWyy& z!J1HqZchszSw?-;8dDOG6G$fWA-cQ;kxfl`GLaH%VTc7Q^6yTIbzOeTqF7JSq{UFx z^!1MLBAZwoxy;egEo1e3`^)LM z=~{>{?3!4J@!_^?$lqrH#JJU=K|%jPrx};S?dVi>-KbM8K*1hC!aeLfv2N)86h{b> z6r%NETy%N{o-few7_+<$4MYCE~AT4Tt<* zzzE+7^*4-8(MJHww~_aKpN#J;N8Ft5>lBF_#y)(;zAHcHrLgw{~FD?(6r3i_OlDb7b|^yOH6a(OY}A!1<}_ zLP8cZ?nm4yZuH-0;yXzqN`ZjWyZ4TD+BCm`_NIrXa8{2dqu#eT%)g2z1j%SEJ3@cF zBI`w{xHQJ=30@h59=xLcQTQfcWooXxKkwAf$-*g9nPs?3j;s30869f(v1rqW9~dah zkPeiZA59HH7ulu%Pe!LIk5Mo+fapl1$!tHb^YN_R{*~m$~a@cl(QS!)=(;VP?+nd(uyARiT zm3rlkVrhsMc;hC(kYgSySA#WXY%T(sWj|cj8ZL?b&6%&Q0G5*c5c|SZm+a8wPH#ZI{f#NxtHKXe-HX32 zp)s@S;mG^G>Sce*zb%hveL;(>vX-^a!yr^9n->;p$4yRLhslApymQ&Yef0RE&uY$z z$#Z$5NF`GK!)l6aqE=9;5l+_2GK4I8mkGQH-T*bnfj1;>WIg-LPbSxzLn*d ztS6EURfq1l=@<>O#6~YMG%a$c1o&a+66R7kQ8X*)!=QpaVrj+8(Rp{U{R%(9)yY%I zw$NF9uEWmQO^EhW4$bdmo%+;DZD=^A7Lu7$2rk^_;hq$D_~<)QHY;F;Cqkkpz%B`* zD1cc}90>cWcoFI2`4f9P5!jLE8@A8k`|^cIkqcA5% zkiVg{g^?$G$+w(i2b?t=f&kQxS}@_M>0Jj3>CUrUJ8<{8q(h>YAl%^LHaqKly3t-=s! z`toh0G^xN{1%h6&$o-<#RTQ@h=?uOSYx}}7P75fBngBpaG0r{F6Y)yG`0Wv*<(ET2X@H z+Xq&=%iUe@-05gwQAD;JIvz~fUq7Yo2s{gn(YT76*@@5q6~ix?_`A+D_SsCU0iYu? z-Jun6Q{m*_84(yx{>A)R0hi?`=*ONJkBh`pLihgCNe^|x#7_=M+PNoI(*@pvRNb#B zA6x85Z3x3FD+xm&sgG(4Mef_nk)YWiEp8TZ#*C7RiQ^u?tB3SqJvJsI*OK6r!~G=G zX{OzrN=~F$#O8R*iN$vWg}~o&-m@jCZG8xZmjMmw{w$xFx4CfphX}sfWjuUs_@l#}hOSV5!)Hqfq zi_d+5JV1z{#zMiYJ?mQ=S^Z4F*j8wG;heYjEzBwV5#KY9N)!D{j>y@g#tPg5Yv$j? z>WT;q&miwU8UAy5rEJ&YWDY0zXP@6U2{*J+-mntYb(u9+bRNMD7aD>)0ps{`E6*Q5 zh4Afu5D4=OAIWD=jr&C{1z8v-kwDImO8lL0VO@yfksm&ktkWjyXHsbHNR8zwRPUWi zDRL!3R>Y<=8`reTU3|dRe#oYb<{;z=5DM%gMY1sy`_YnNHf!oXE^C^P?>oN>C%KVG zgaY+|IZ7m4L`#S%;elFdJW4L4`NjsA`@I(1{Xo0(C|~UFGor?Up)ZK2sit(!`i#p8 zzI?1c%0dBR=KOTOn6;FDz}9a`y0^}9<&L5513TQeB(+_y$mxvmF0(K(`ryYCbMD5k z1_a}8*)Bs*-(%KB=RlgJrrC4g9@p~v3Gi1(@@w@8u(s)R)fNa#-*#LUa|%s42!dQ_ zJI`7_DZIYQ^liqScRYe}w#gnrY3J)+$0PTu3l!@e<)phiF=D6#qz&F;yI=4u`&A&} z+V7D0LTNwAK3RJ|xgdGruP7si-gA5(G2A6Pp6poT?ue#<72(-3MgHntVrJ!$!sU!! z)yF4hO)Ci%;Rj`=66Qr4?j_UCK_9rWjF%l$mbA;X8e)ir?)O9Qf_LQ42n-(NRi$hI zK#lff%hNWwntO`-rg*H-8~{mSWl@xH7?;Pzgo_P2%eU{qJ3i_$W+|k|>Ezl&z>EFz z3GZkjvfodpPZ-lYmZ3*V@lH#~gD}*v9y_^NIMo|Nb(v+eEhjKc#mJp7WC((lei7E0 z-CZhBM8?SYzvg-fR6GUCxGB~%5MoMJa;KHQ?AE!gAfHIcKFZ+l21Dsn`{Ab;-1RJS zR5v@_ZH;>fru@FS{C?gBFLzqNYsQ`|%KB(8$VD>OYWbzfK|hZI0CRy&SId%xGtuy% zHJB^c8u688!AT8q1um-Oj;Lh2Qm!d^{00f-*^~QY;Y&R*S*%*G0Y>q4p{qYZU_Tjlv8&MqG3}cmVPzHxsrLI=f@1+TVFrUKef-{9RhBfb4mDvGgYrvS zRlK2q+7O+GQceq)$}BwO@l!~eM3&Wv1ucvQvH(dVfqJc-{o!3xS2F}Ce`+ZO29dc4Spu}yefsQ6S53$J`1w9 zmXqn;B)W4w%x3G4A_^sJ*eoRpkWFxR!i<*!xOW?4V%&?^D7BXVE%NtB#2#Am1i=45 z0mk7wV=@Ga_2L8ccs_na9U_RovJNqMaLFH?0x^C!(EReLmE8RC@6$eg z@&oo~7+mU0ty7amvCa`YpfOzg#FN6PthE-bK5sYtNvJT;XdItE_sjpt8*htq4?_>Db3+C3TE?#c8Zig=S zW&VuYejRCV8Rfn{;ng1?Bo;PNZqfgPE}DG0ya=#qv|CaE4hP z=Ak0V&Anim`cw@(8v%`|Hy_UW1^%b2K*Y2#^@%AlaWVfLdfILoLUyS19ELl^~E5;AGEGfiJmGb%D*8^S7?_vLssM9 z7@ZNHy!#kR0H&VIIlz?T0Lcgr4f5036#*|g?m zqK1I#Pq|(OJ(qH)BfKjbI@c89VoQ$$#?e&S!+?&nG}(<0B8;zT;TtQlN^RcOd*LzT zrNNn(+a|Isccr(44I^y3u{l*eJKTsQ4Z@6nFLb`uj1*$W(w?Y?xfwph^Grz;kXK)R zf&p>HW=tj2su+IpT1sTIEqqlcF560c8!fgipp>>$(dMy%8B5r_)Sol*nLrj0Wa@Y) zRtrvg2KC)2SQVo-39rn zH!!qpvBB1XUptn;q=Kjkfftc&7@PXa;4Ut?FNDCmqL@DwCUs++O%3OMA~qrthi-nc z&44jwJVFwH<$l8p3{CTF+mxq5j~@Layf9jkUp3Rd!tO()vj;}Es$2DLl5Qv$-)$u! zA)60DPeHtt^O^y7H!fKgaxsc$E!N>KQ<(RJgD?T9OJda(gS3!z`>5wlqWm$lVrJDf z9}7DVGX$0XH14a83xx_pM&c&&+YwjZFxKOzhduGI*jLTP8B@4?zXW&jL!}d^btuld zEHIa0t&6GMgyMsrNs_2*QeA4jFr4p9gCe$9h(lySg~elbCULB$mjZZeOgg_ALo`20 z?}%=7hrH@UMt4vJ=jhQXuyG-kgAB5gK>b|QA<)oQADLZOQ8MD$g_hK>UjMgTrJN!x zt(EsXIYQKn8~K6ehdwkRJyF;$&+kwsX(8i9y;=-7J^fz!#20Sq?|Z%uS1)5Bbg@KM zYP^K_tY#PfEKzu#|C^kF;eH*T202y3Eez{Rr+D*9s^*W?g`!?Z2;8mnQmeCgp<#wMJx^(AAn4430kQW5b8?#%QUCQY`^Ik2Sv5(Tzoljqqce^*~d z_rtJBD!NT>pc0LS7^_*)HK`!1>VDn$ixh&?pU%Xb;teFAttY4^haNK@i}e5*qA`7 zYs0(Uq>3<0?2zLS&Z$NJ95nbTF2+uNt0Ad**3tebGFZ9OWnPos0$z_nTb-fNDAvp1 zANvW~2TPS19wy^D`yWU=JP^YgdwFKB#D`PDQKPB0JkEC{aNv+{%xX=jK!9<%_p z5HMuOs82UHoM~gi)tG>ZlrMC}_$ohKcm#AnRF@6hJQnQ|WG|S`jP3#)EO5%nr!BJ}x zLFMz?Xzs|luEh!+oz!vt zVq&8~F2B-njA&>zILgU)iTwOj`_^ilq!SSrmt$<5ahIg+8LFo#JM>*?j=1wFQ_HE;;^@xHSqe{m2(12bkKa8Ak$tU zbg7ft$UPg-GKuBtm217@>x;jsf&eIfn++aY_qN363*&$xV7mk`K6)9dN(3a}wIi5- z3;ove=(Tq+^^2^a`G&)C20~n7exI?i11M}{vPk8K8jNsUJ6ZLAX(dhqpd=Cq(()LL z#%Yi!mxx^!7c`{%m$`zDT?;RTzK!9{V)k=Wq?{+HXdJ;%0`99SD3#=dZdhSg<&_W} zcQuSWk_@gGFdpR$08h=RQy;>!VSX}Y2R#$?s^%f@H(XUWKejr*05=rJj_o@H7$4?P)KF&PZk=@l!{JWk_$^QB^MwN5LMoQiFoIqG9Xrb1 zUbM%Z?M4~m6j5LB>o>dJK{@-u_sl-ZYo92kH@ofCy8jr_4o!W^-c@O z>f*Vhc9X$+hp6P;yMk!&sM7K$IPA9pt*K+L_NeC4!>Z5a^cVAg<5JiYG1&OEK`V^E zg0*xt3HflC`=AJZ@C=?N-?E9z$%MMQ{FQe4pOb~bTQUdtY;rss+NiD)*s#n{je)YfOIxK~wEKK-hx4XFev$XtAA#h?rVbqG%BtZBjM1qwSH&E(^%> zM=_9T#8S{^-${-It2tqA8Mio4BVl`Iry#jl@NbFunyXDfKvukqQ$Fs%kZCvWc<;fF zFD&9*#vBf3V@8f`iDiW;!tzX4n>ywn8}5BI(-qraa1+npckW|km2vp)=G<)F=VIi! z^nYx%^1yA6ciQVeS??zRF4PUt4g)9T@gG$si#F>7v8IHs*LCbaSTff`mAw`3Sr>)}48g%d^0)e;*gygM!b z@HS?q+*ZRN21pA8k-t%Liy#l8(U1hhWp%L{hl^zB=HWa1IX{vIv1c)L&eq5ax@W^O z+6vZ1aHQAAUp&Z=YqXS4(1?PK4$^s_Gft~*0_P)7fF2pcywaLOZ>+jHRxKFK+uI{KWzrhHn)1iZthSM#eDmnSP@tsC)$7QX5o<)U1xB35bRx@G10O4UQ@JtQzDx#+5wlO0_^v_*Rqn_LIlK67xFM zH66=@ETr`|MulV~$76bXyhoWmK;nk!#9>Q^VpQYoqw@>dg#IYO#9j{fJ`<@PMhqHz z_xQcxB|h7_?Htu)p?KGZp+6lohig^)Cube+TdC0Uht2tErS=kI&T%nMjj*dBrs3I- zNKVaLMmo?A2PZ#7M&1#}rm;CBk1aOKq)_H$_B{S2Y&dTNH%1X(Hs6&NT@tE3C$-L; z|8YNFQj5Br;`viXZgK32uJuVet9)K&j(H31ICh>zuXO5r*0lLtM{iCizML~; zKiD6chd`?39D1@hk_fW*dl@pOw&7z~4T3r@F0p-T{yVumg1SVho#V5HyutanSFhN3 zgZGZfLBzlZupmfWj^(>(#V7~u*dGtMEw`96*O@8@u5sZ%=juv2plTuWT`OsUi?jx1 z89L_v6;J;=uFRhtbJZRKUTL)b7lfqT&UaV4T(*mb`|8}Z*Dk z&d?63;~t}>yLAN>UZ5eVE8}<6<;3l*lb#@(f!8izb+m0Nv&wc%5p%^`pI>e$D=jKdn#^sm$XCiw8Qv*!P%ylCW z65Um0+}WGVb?*=9z|Yqx1fKfV>0W-wB61$k*rki$#|Ajgw1+>G8k}gas2em(Wg5() zS7O{H@NIwNy`xPda?1C;xkD;nEeMAdG){@?90?^66bI_lWBV?BA6ando?CoY@V6Ag znRgjH=!+L>_xOHZ<-UYgi?fN*fG1=)&BWUz=T9m0rDU@q?t(WSVcDd?n*M{IPU$Da zHYmK{cib{`Y7Al85by)jB6xs+bVwW(=@)hrukMd&9;|ns+AQ9O2pryZn<%@S!y2TH zx!CAoFTt>6m|(XUR%E-N+QOem$xLoKSX3rSf)w&#;4bT{cZdi<6hbBlEcs(&|fUx z*a3t8kFD7cK}4lDk=}bJAPOo?kSblIDZTg5TYxAa zp$MTGdI=B`QubY*``nrP;hp`$@MUMRyXSw-uO03-uAPU1XIOwJ3c@3>LP8-*xVa5W zf?oaSQ7V#`8MXe~(muiB;QLEGlN+yDzU?+8yhIM-=o^))IWq4psJ_}aMzMU+Flk7)$9z*aL~<&p^}Iw1G$iS9_*x|Y`K7lSwzvO` zaOsH+c0|SYvbyh!wo8#3BbpCIIWf$k-)o7Smy6CjeZL9ioiy~)m2UiW|eV*H`nDg~RB0Xuq8CF9c;oQ^-*K+b2%_~?)9 z@kgR4#9FjNK~Z)kTVDP4mlGF?k{H}W3p_3JNdpg$w38*d^6(xNPhzo2HiP!>;96~1E%B50>=T~%N`x65NdjMlQ?5daZJ(_?@Bvvz#Py}M8*oc+ z^Vjp3(@5#fETrs$JxTQDtplNhG~a?LyUW@&B>f5S(3@qQ&i}pRU{;d#2An?lW&kVi z3l~SphTf)0B+29p4dpI2XEd_=H;uEIB(1-=P4r>x2wDElbGd_5ng8HDF_Dc~7IJMB zC7CZ>B7+S2Uvp@^Apr{jXB0>Mf~i9?6p@OqxM*a0(g!XZW6dm_FjFg#h(QW>{PyKtiNNRe62}A1(#ZOKtH}Pq_GBE)LVv=0= zvTLg^P4b?e;j)<{a^g2d7?YK+pLe0kpwLrfr!}Iv>AE~>OGP8H``Jh1^3(8VV)A82 z!<7Ld*;Bs7GTq6YF~p%XXd6@DlTqjA=PV1~an)5L%MAp|H|KPAttSUx&)3hd-x$9iN%(<8pOcX+Cavj$#SEvp8^{#ZPH@MOoD zv=bdj%i19h8AZsFGTkib4RjhZ!zQ%&KC#v%BPPNnG8 z>z@_ZkKKtR#Q4xR@ftw8iJoGV>@Upe#_}p<8(OLe!hLi1`#~93L0EFkSMwTxGBYo( zP&=Ti`B-p(&-$X#8Z-}Bl6KNjyLDaf3nz5d?5M#;WF$@U~qBWJWdT%%bn#{yT97a`RDi=)yr@bPu=feG0TJ*A@TaG#xkh-&O$ zWIsx=`Z4+9Z_S`h(T?SzIu(c5#jd(P8a0Dl_}`V6m==c{?=6fco<8na*I1!4%e{t7 zvkc$3#*Qa`2ObK8@X&tWe`XmwSf#2jj{=R$3*oQ-F7%3YS{v29pwh7_q>j(3^z9r+ zijE~}g!N{~)0A9L*s>Bb8A{rI#Euaaf6%B@g5+sgdT~>)!jO-fxQ(IhjHU2tDR_ms z`GrVZcJr#rBM!d{WXg`%uOH4mWu&sn;B9jX>5n5^1D|BMs#A2EM89lnQbS(TfMhbRq}z(6JFt5O-Dh z=eXvZT=l?v>OJ?r1%3?@KTV2tz2(66jXK41S9HJ5LxPQ$8z}m6_tYIDl-v<^{-*OD zU?9R%i9<5!>c`Db=+ADO`3C)ZZir2^d1CJK=4NQ%ziG9=joV78tqQb!3-WLDixM`} z!)AE(5Bk`e4x~Jg&EpT-9#}>>kDw0MkQnH*hmErMq zJS&T%SYevHloFH|DAQk*;=e9-LEdU*dD30IZti!wVkrB zL-Qgd#%^H{3PtCSRa5<}dM+6PRVgu}j+AC(-=eS|V7X{>SYh4CMMC8Ui zeCk2iUdNV2@Opl9aWfjCh(|9%V%$xKfsn&7XLu;V6uVpU8@pbR5Ox!9>3aOqb;{Vj z8l4lmKtBAMs%-ys1<5jDLL$`cK+0G2{sCKk;3k?`OI3EPOUEf$D-|XNH6~BX4}q2A z3y$y-2Ap+ddS`gY8PS>`SpZ16tb6b&tJ5*lY5uw;MgH495aKae@rt{CLir|7hDyla z3xA}u%G7vAJe15sWprL}FMwb5+*&8ij}+(OwP}e7kL`?PZY^f}$_inGp!TApqMrt@ zN*(uIEecb6Rd2dlR`&e@1xz<h8iD zA%c+c6vrP&xgL{j#zZfzcllbhHOb{fn%wKKkkSH`#%WL;)T2*`0!P_*j2swCM1;>J z$pf_Pao_j=D4LqYN=KT%{M4m?n=8_3yAE!PVAT6{C-cDa3|SJfX|>*5V9LkqG!`!ft)s^J$7 zqfy@s?q;?Vc5f@V$VZ8Y81^~UTwOO(d!$$2F+$XfCsI;N)Y+z*5AQauNGM~|Eg$iT zpx@&rav^_U=!H4ghMAV>lEi{?PGwvT|=e8Vg0x5R+@^{%Dhw0)`_PO(} zd!eUR8wdWbJM{-`FAsF|30@c=I(V;2!dz(%k2ZhJZtkO)yhdp(q1$|Z=*uAy_K18_ z89a_^iMAwarVv^jI^ZRpI9>kP>s15(4^GzGh{I=UVSysjNo9~hQJ8Yh;+qa=zyun6 z*K7&VZ$FbNOBg>we_X(U9~4J0MhN6tfVI65y;v|F$9t+K8Ds5b7g;t~G% zs(to#3w*?-ncs&z$w~&2;%R1zK7!2oLI{;)_rewTo}8D@EDC97-<*jphFm6IU8jr+ zucl%UNL^N7xtiAxrMOA;;a(h)ibKFXT=c#)rX=Q3&oe)@-gb#d$7ZNAqp)YW&tukm zvq2wi6AHr~HY#1Z8&B7j6#lsHy51-2&pydEsdpn^)P46><^If2`IsfrpNHI zDEDv|#Qq#r+FrnjgW?Y18nCIP{F zJa4jFN1WKRprz(M2eySt-T&^8fEp4rFeA`#{OUGBk358(1Nh&tbL7yFL%s}K{>x40 z3_paKlQZC;MF;`Og{A^!fLjD`fQ1}L9zfd>3%lUc5i{=KFk=LFKIK3)bmyVNaih@ybgC z#9a$I*B8h*CG8G}PmI|{ULxqbWgpgmHQx~{{>&HXQf@QouAB0IrFN-BR5=ud9=*@? zBVH7GLvaJyJ%N1kEOEZm5sn`OGpu` zmGacxM6^I>itf{q{f8y@l21%#g}FPuek48c%X`AR+S_iQk#0?V{GrxYm1$(!!vX7N zE3F6D9OP+MP);}iWluM_>m!#+dD-Iwjr&|G ziIT6EyfX7ldhekQSHPTaLnrZ&gQvdOMssH}6;;2Lg>Byp8z3JQ!=}01J7`;F;D$DY zwOI(5L~@`t7sXW`ebdXbO09Kg1!40XLwB*MGu7D6aioNl%@fH{uR^@cUVLh=3N)j4 zolv9&FKjUc=vxR`as9r6w0olecmkOYO_`BIl7shZqj%c<`M6}Al8-m*&t5;sU&vsK z&#nu5mVdQ*z;6oUSw^N-fO!tUvBWpT$r%mggpwbED_c8qKS5C6G01xj?wyB1B~Nq- zUl2fMB+QuHzAS$Xt0CIsB@36Gcc5Xcq{=eD7%+uFZ-J_(o8(aG1sDr(c4ZD&!iKR4 zEHW~;EYA_oj^a>#u}~E*a8ih!<&bGuw~4)GIWUu^+J8;awA>?TGoyRvQ$YLQMWp?& zZ~ZIWKV^b+qNOw;U+y~GiL$_7m}V>z`$ntyW6VKJCY9%(Q}!os6aR0IgC6STtkO#0 zCIUJtv{!8K9=jNB6&3O6M-tv}E@HD}I5GbVVk*U*--n*pE&AMEP|hZ%tLWD=H`O?u zqHDvWg5vwMXR<*_ht5ifBJ7lZ{e{&PtDag4!7MMTG79Agmc_#GJiG6hm}^Z6JyJ7^ z;`eOAQgXb`$1a!GmUXT9o&S7Uk7xCJ+3LWyVbL>g1eMAAxRd^OkmaXZS0@JAS7TD~ z%p6_Ox^@bomw8(>f(lE05j^@$# z2rCnMUq0Qsv?pn-=9?$B!NA3)rPyi; zOEIX*1s%SV?e~Sb@B>>lu&(ut4|q)G_;;kL6_urzl3xxMU;J~e@`D@KfY6zXT?MmmS|mrqObHQy{rB1i+aFevuxCo%6luNzd!J% zMc7i!TEee@u=*EL8@Cgn0(@x-k5ERhXO6$@U0;60H^bt-Da4E6@{lyyV6nk^R;-KS zeNi+M`o;T@XId7><4M)G*G#f%x-8M!1WS#dKKM*Fho7KYW9}9FsrLr`8%i@Ou^)qX zvT4842q@k8$x+2Sk2}BbNVE7&?dPk!TUA#A4s@<4dQz|7FkpHRrTzW_u1BEkD4#Fz z)u|jDZ76^Gx(_dqR2#81_Ga@XdTTNEP8ubxnGn4di-6Kw&yQM0y%YDh?meabw6hsf zSUZC|u$k8L=NjD4o z;vtmrvy6+=LA>$h<~`L9RZBi@BYsjEnmNE{BM_RzA%)EV*yA)lE zGL&Y1<*V=fF$5yL2%q3?u24tqs4~9ylJ7$`@r2!zj!0(Osqo#HKs*`w+0b?Wv%oZS zV3jJZsg9$kg>OBp>+v5$YFB3FFfi^scy(!D=eJhwJ+)+(N6tyGO()U06BuBQA_Ned z4jQ^Y!RP*ZBOC=aW9t^C3mS3&-P#eCpA~K2${S8ajL5e7$r;FQ z=_uD_JKeKkZdbW^bS!#;m|{qF4LzCfd1XWn#!eUL&E8C7s*>uwD562!nsj6{rS%Q1 z|MRM*kIi&MYIR5`w;_UhZy$$%!_cBG?=q*bCjpJ+7`?Y8(A+3kg_(9b#+v>$yf_&m zZ!l{q_g1QgSeipiPH~$2A7JDojON0FD327`C@s@jXr<*x*hg6IZ=muq2izPacF(T= z(Dtvy)v=Hw3&0sPN-X5s;4uf(upYg1t~Ax4k?4=KKdp#1x(ZIk-gM3**Z<%k6L$rw zzpC~${R3y%pPtxLug>oYN7a*6{?Y<}djhdX*POjb@2HdKnO00OLLTSJPQOe1Va-36 znEQ>fTk#(+<~nTkAuKZCkX7m5eX-QoShE`pY6iRZkuMy*T_?@YLOR)MTog-FI}v}4 z`*xqc4mLTWNBnVXONX6#tFO9)@|MiRMUT(e8V(Xg(26S0KX$8O8$!2p=_9xIzQ-x52^xy4-N-TGc=1SS=Oi1Jzz5dv@je#Q?H;OaI z`qc+Q3EkC%Bg;!RyOYY)1UM#d#rtIbr0+SxW~YO&d&Kdc-STcHPn2_cbJUyGFuOh2 zNyPC?yX5wPq@JlQ^xJ&U+%Y%_x+5(@RqaS#ny!vl?+ef8v6B6}7aG_tL*m670{qcS zR5EvWP9#YywsW2Xswx1xc|b$x9s%XHn(NoP7eQB!63_~Jpk{04+4rzvdt7HWPZ{dI zGuv2uj5O6s^_xo%#Vye_1m}{rZ52|BCdsO7=93L5H|Naon;uAF*QOjg%e(0dVGK+p z2pdd?JJ{=~9wg6G7b_e>6;sv7Q+IV{@=)`U@lRvQVPSh1pJO6~jI-IAJK-N030=sA z>~ybXJKpy2304K!9lu8~L88f8k;2lYr-{+rM4P#T=(!KPf9qWumet4svE?npvrn-? zbHc(=HP?awQ-CBlnQ-=ApcWb0mc^+%3+87cCp6xxNH)#{g> zNqM1|w<%;XLTl%jI+NCX<$kX=aVESw?sNn1u7l5YEXvqP(izhXj57Gj`9QAk*^NUW z0ZODs*y{;@`VdCiZ}XdGlX=Kt_bnNavca_lMW+6^&I_QwIx}&3ti7M|-EAH+UwRTL zni>~IS1Zw(lQZyx3^gGMx??sLY%b5awTgQp$HJ=qImw~9Yl8#H^Vuh#HN5x%@EkoC zHow>66RzHZ%7-+#HdrfZJZ4;D6_B3~;L6#nboq zmvkCSAgUlmGYeOv0X z_QFMW*@`<1zKq-YF*kiYiQD!Av$;7@1*utC=i(=s)$S+-za1^gbafyl;4`#*I-mLf zZx%p8cuzW0K|If_P2jT58Iaml20L*n=J9U?n1bimfTfPPd418f8^A^}`JHk6yi1sZ zb+aDYCPB@Xj@XoYc%%}Zp1K-qGHwPhOwdz?`yLUWsVW?;zxI%$BIJPI-PwN}oTu|nytMJ%tek+IvxT?3Wp>^c@aAfW+K6OefI2mphPmbQcq)=3<4A))#ieWeLpln_07hT$Rad(FuH;|Z^tanlH{hO&~ z+`6an4W#%`PAoGoqr{g)fN!Ufog3fUM7%7xP)*nk9kd@GV;rz{A7RR|UkG$vOPTT# zb%Bwl{MPlL%UI~jXyZ~->z_aKPCrK%9kSqoS@APd$h=rwZ6vWEe%;>GrM+}+nP(qL ztS){Qk7{InWwgL%{N_G5|6Zv5rdZHGTl>B^`U_xYdPVws25{g1?~ZQUQ#lJ}olYOh zxEX86mix)VVffZW3o+D!TwF(N)CyEvF%+`F?hohW=J!PPR1b0F+k`f_H@bO)*lx{E zl{%j!z_8f-*Jpxtinn$Lr5XC&;D~Xg$lT$wO6ASes*R#O2W1yjvC1;rNJDm)L^Doo z)&D#K$h!|aCpLjw zaa?Y}fqEo%cbSv>90BZJNo#`Ce*`vXOvtN??pvp5mCn)!l>6CF2*n{P6SIeU1ZWrm z^dake>kv#mx)P(2XKv*1GBJ9EqVJ?LFY5uO`|V1cbeKsy^d|WhIkh`+hxyZ6!!O%_ z9RML%y2#c5Lq*akXz^pfTcJORxOk79dA8oq<4H(0@1YI118XLSw3BkGs+0EUfxp}{ zuM$ks$}u-#@%DG7;sc#=&o(o!03IC<Yg_?%r5J{b4eQ(u{XNqIb7);xb8RG_mpN+r@ zET?!MZty%(w{tLf92p@zE)p9)I8)1-jk-+(Tjo%@du^K&BbpH_;J{8AoH)LW`^a3m zVN{VdFhUKZ5P5weK?D-geCv^D9Ol0ESAEetXjP7V%fas*<-Xn`?^^x=s8{mCN4OscJ#H_lazHZc4iDy}htOwO%o;B7Py>3Koe_ z;X<#&NvDv^^NEagRqu;uny}&H$b*9Vs{SMjaU%d5o+LMDN%Ypr8o1 zCUR<&%0Ij$G;oR+N)&$g^e7`2Zna^TrRNw#?!r7DKoO{LO2fGfn4%=T$JC)a%{?_9!PRcm z&=BxVm91f`qL~~1qfUOzWhXKNSxMY_iN1u*^<`5aY9%ye*nd5*yP5gym0h*}0*AG7 z=Y67_+Ox&3werel>=gwA`&xfgD06&WdDX~qw5I)%=K$Zs_uCmTHFt>W(W77nrYC9{ z?I$zT(AF+}WzP87xV!_6xxh4@?M~_w^(m9>C@#0^FM5hSa7%VEB#F>U7esfMfD6dq zQ}el{8+caYPG)TE=b*6mc`~!weicus4E&I8q=qMw`G(w_k4>_DNI-akK*{>)R*&K* z^usxGsYMgz!;wQ3%aQ%>n}BYsi#r|p4YYByi}pU6_t%=!gAmb$pr=dH!B^#TrTu#fQtk{4Z%8p% z_NsrRyrRuS95urYvaUA4bii27-eIPVR2d!Y@ez)RW)q$Rwt*XG1{)F(0+hmX0=P0r z3ER*oWSmxe?n4C*O|5eO-u=jG3n<_!OKx*W2z14cf!w-88UJ(_Pck>d!9hr@wc(gi z)weInp|2r^;eVR6E=}MwS^GO}ro;k!6%?a5PcXnI%2ClU6#f(9_BHOOh_*92D(cn3j+NZjC>x*+K zZf3kK{>+@ozo<5HLuLDp((W|Cj{#9rPK4ieZ1j7iW<^GJ}`?P z$jRZdj@pqnstGumKMlI88ncQ2vXw$T)>v4|e7mAktP^#Q4=Q)(!cOyq?%YhKd)s-f znN&tqV~*S4@HrM<;P-HZB5W|tJ#IfrwaYu=JzG^oquB}uLc|HK@U~@*;x=J`_|La` zk-QE8SrvT~*0# zA?_eeav-#SLulr!vj9v1J8c8yl1t9^SA}_xsm-%(bX4uRxBiYYWfgOffa+UhEF8o? z5jr61DRjX0>`8w?7~L&E?%3xEd}R(rPBo88U@#g(C=#xlS^hpE`bSQjhLf-dRrA#< zr_zXyU%lws)mkEtw$=`t*W_;1e1)2HmX3aH^8Clbk%V37TOQvgaNDxip*#2{{n1rk z(b#JK$5~;?mrS>NELwQ$wc?I+8R0s^%(GOwY4qW$-Y|JgA#9(x>E~!p7J3O}t_u|q zn@e7KJqT$xyCb$HzoIwG_rY)rT1N|jv5&81k=~8xp(gL`NuYak)KU0M`wotru3OL= zAdOT>b)$SXtq{zp9{7|H;_e-0v*-h^u1_UVi8P#+C^J_YE*@`*5@qd4b zuit(t?G#v8$E}AHGmm-{?XlS(plSoIVeJ!zue0-)S0u`7MrvZ#T;f7rVzl+pHT?hw z-_a@$COIB&V=?%p1(S34d*k}nNefe0mrni}xZipP?)%L>H4r|3L4`)7jSV-|!@)8Z6DFQvA}oq=KLwZpG;uD^^< z114=i*$q;q%$iAL@=l&m)oHAW*Nwfv#Wr)UF%yZz$7B1RxbZ?*JBhbZiW>e-;5$F< zuIGhJMfPg1c-3mXmC|D1ZP`vZ^(k2DV7KD{&MbKP+9%1(2OV^RBx3qT5KHP{cD!_m zCot=*?g|(8Ke`#Qg_+lv-74z{+3FzhmY7||x9*&(iv?aRzOmal<77`j!YuN?RWR)M zaoJBwgmcszqUh-^q^Z3M%R0ZS^jdN}ny-Zc-l`kjZdzGgd&egJh5%qRa?eMrT)6@J z>rgQ5I-sK9WN79Xh)O&B9Jy9f4i}oC7%4IL+bG;_W+z9j1rbgyN(7*=(32X~C9&1m zQD5YI*;8yZJra#uSuZh1<^-P+n8=R7D6z2sUTbLXQtS3}EYVw%Gy4lmmAtc5#!f;; zU$B+-QsaW~>91=~VpwtkMYPp9DV~YH1#%cT?lp7H;I=$WZ6vc z7)g!hP7(r2)*FGJs&bU<-&ZVO9sTUzRFwoZ}084(DB9d z%PxPgJedz}_5F9HV^SG26iZN*?n`)eYxJ#%-NVOEL$E#0nBQ2^P+bd}Z}ov>EHwQS3Bo%={^>eQ z))RiXKwllQ?%jxh*na&xQ z#;%cMcgMo3Ok3P`y%nJ;2Ni>R?B#0t%Hu@sll9`o0DB7*zPm+@n3+y<4&%%U? z?C)5XkZY6am-@H9iMkf5KOWY~cRoG0d9tQ`b-p{{es;{LXS^Uz^ZyeUr^_@hFhjqyi1&pfN>_CLS6r>$1q>k)4H=IarwCFTN0PEOFJ(rovKLL973 z(T6Yj4Jl36U#3;oEw10=!US3>#@4&Lrbvnqu3s3eqEvFUXC_WcR1*^$n6s?)Y%&aowI(*}$}`?q9FEE-%=;X^ zoR(*gH>nEZgOBaLGY$jD@`r2paec9WgX!{@a{ov)Ek0K;m+;S7y^oLP4a##(P!$;n zQJI>;bp^?}Z5tV?t)i6nIZds)@bmh?ryCyAi}d*9sv{6cXEiim?T&{mT~+XNcOM3{ zg-G0EofaK0#C)Bzp<^X$&pVk__CFv>;(7&gm6)2YNc*K(^zU|BxKSQU-eRZJc`;d#dXZ#k0UCN)K+E{xzOybS)tDi_TH_%RmR=L9VL5 zFB+}n@pg(?4e%Z-O;uC77L1X5{E40EFWVaaGR4X2Ut1)Pi}eWqQKIV!1t&Ki+Lu$U zWi7d^U&W*_xvHm&SH@4Sl>1lK*A?%6Qf==Du%)j2%!jp~Ssx(j=)3vP*y>r@wYmX? z`@~8y1W-3I*4vSP(sf_?SMKkJ?B;gpZ0Pys(=IXc>HuMGtkunZ@Abw?PP<&MS{o0@ z3zG9HX%o3D-_9KNC=CTM64hI(`QqM-WBa4oY%+6z)m7-@Vc<%P|A5#=%-;%_!tM(( zN77us;OkquN*hMWwF}@kF1$C-g|$a}6~s-|&il>%207&WNU2TvldRonv@GY?2Gfr6 ztc~7Z=bahH{=<+pOW>U0>bDJi<2Cz#yDd7FtL(uBNKxsMEi6z*^u}*6%A28h>OmAd zL8Goamj{IH5oxtQ@gXM-?E8sSYkh~7P8)p(A(B`$loKoPZ)A5UMQ=V{?exwZw+Cc>qk@$T9%&pTQKuBALb zin*L@jj9bsnqzCEY??2qzXWa-6Azp3Fdu}dIRYYp?t~`6;Lqt&LQ@Kfi@9+FUrf9W zb%Z8=17m?67DzljPuAefsU>&Oe}u#nJ_2V-!o6(aw+|?_EPk{PVNVWGFh}`DD=ega zI`1ei?)}pj@BYeNj1io(G`c=#St)zw`y)Jcf!Z9)bWBPC`pnDOQ9Eu4yoc5avoAw8 zR+x5*y>XLIjGuY_9VF3)2v1&)H;8KOxw-=Em?-g&n+oyq3EUjeUVftZm`>G09DTdz zD}Iy1Qz43_VXy!c9ADN1Xa-QeQ6ZKl8ArO$!FAI6V=koRO<;8o2G$v0! z^gnw?SMsaW7$P!D@%2+~=jW40<`mIZSEXyO-m)*kU2Ss>XPeOOpb+EEo8@G`BFhdY%BmFSrt zgcOAfNyIs~_NjjZv9r#V?Vyi(SRZ~dU)zUs?E0$9gJB3^o8gRQ_fXwmAq;zAg_+0h zxhOQK&_Jv__wdR;=9?ercG@}pjpJ8C|knP*^c zI%vy1e*YgDifta;8&1~`26{k8?6;VkwpTZ=2!xP*UP{2aPh7UjrxqW>0;@ ztV3l}tIlbEAr1{ebJdh*C?Vsq)=FrL-Y2#Q^+dBY*gwvnvm79s0LO3WB{T%@h2{;B zolh*_x{7N7P%<5lPZJ4LD%5YeKjUb^|BLV&|5(hrAf8UCmS7H&7}FRtYl()^9&rY@y*{MK)cXD2+$&Qz@b@R*x0zB+#Q52TrJ23E$f_R$05r z?z4we*?fA}^mQq3XKYu4;I{gFNn)5;{ZJ~Zg)AGu8kTDabbKIDLxsN#gM*haZ=S-3xm8epDOfJ@|*(8szx@$~Y}*O25sh{m=cOP-5Q5CnE$d*2PR!Sxt){)(M6fde~WiL6<6RE7ILIQ44C`!nMKT^>! zpp&7x##3H+`S!iuDvE*6qIoHl$(nE;X${XLuC^Z@$fC}lt6CrQg_G$E)4i0 z-ON4BxqCj>^d`+$`|SAN*L9`ctpxm==1)2cOrlt8syYWhbH81)oXs0fJFt@~1?u%g{+x4^y3M;~(C*thI*Tq?j3h*okXzy%l~&c^f^D z{r58zMT#tgmu2sSjJUP!FT=SojengClre+qU2fO2wEjk7%f1c|_jN`f!Q+}%g?uzo z-z)wxozc#{y02yK7-`}5S99-RAh(8(RFaE^Hq6-dSTv;>A)C<{B7pvbtfJXu${hsj z$lCY-^NV;LM6GvzTs8AHD+mB;i08p`er@Vn-;9_twK?{6H8b{Ws3%o+@)p6z^<&y?5)`o->pcK2;wg5gHEpM!Jyi{4 zHQ^Ts5&HpTdvCo3*5*lR`%8RdHxzI`kLpShIRkw(3wIilVei>326?h4Y)ZFaYKLXq zTV#F_{OWPwntQXmxJ{b5UrtQua&>Xlk?b%9Br2Tq;hRW?w02&F&%vB}bpu%qxh#E$ zkgc)_m-?5lFlZS5pYkI02Yu`%yC+Eqm_;Bf)4%Mjz1RtaLz;P54k)-_mV|=I7M_yg zp(7lvAfv^&2|{mI+9f-1CsD*8a&}_ok07!IR6ulF@9WfAu|c&f4<8<)o#)&VK&q4ngh&7 zYex**Ndn0aCnAM34ib)dI&sT|wbLXP&O7xS z7MGPk%MbPZDlTH=>vt@J(}nqVl91!C)+GNYA9ho788PV!@@v0~SObQzl4sUhArd+J z%!^aTb=IcL1rd8P5wkcx_+WAyXwzX}=(L*wF7-2Usd4v3uNYn{6tR~SBvW7P1|EX3 zwSFTdJClwQWeUZrP8_42_Zkb?A217_D0rUxnUE5r31@dE=4zH_ z&d#GweA@oa>bPQO?7guAuVKIDpvr@5s2J6I%KkZj+}a%T%<1iSo=79!WV+QP9?#ea z0k1d3M>OXhi&eq>-l%i!J{M%&!2yq<+t?qS4I9`-GNk+Zzv-Zk=61LH>Z#cZL@58x zUtbK~;0@l^?zpjc-$PUU!oYRruK^eBa1||1?tjDYiv#G_Q5(bQ)~7P5hpH{WdAHo( z>9!|o$%ScEVTfBrE2Q4gN9Af+K#_mLEvcId_Nfi%#0f6H)?Nfj-b`Vf%%V+)!fQy7jq>W z#NyuEw-~Y4_T+zN8jV&_8$%vPL&pBU`wXZ1*v)sBf_Ix4FfEzLNKgbc9!6XVdt-k> zB+F%n#w(g780Zlg?E;yjqyzW}Q#J5_Yf#O&&hY_fn_M$)xFn#eyw_%_9N#Xz%p*1s znvA6U=L}l4`dxmByDS8@%z(fC9x(+rSeS{EK7`y{gBE4j@2k2hzfPF-wCP`rjJ9kc z5qK~xrU6T|DyYf8k#x`7i1xO>+`(cw$i}qVtqLT$%Pgryb+hMFx^b{v4m%u@okvGp zfA)rl&>;bsw=SxT=nkCR0Xeg71p_=t4-O#MNs;e%8s@JMto(Zx;3JZU#=hY{F(f=5 zZ87v$Is=2aKi|GR{F7f}@g?q4JK5&;oMssN;L6 zWiNRIx^;>`=VGkW>L8D)oTXzv%;t(gOQei}wkI-H%M6RqN`MO@qaEG}C$Qhs{gn|;|nD>-()yQe21b1D)23M6|Xf3>_#GTaz<%4;2 z%D9xuOZG2y>npdz_%MaxYwzd?B3X=c9#P9*FJ7(mbUg2g(Nt1=I;6#t@YQZINZD}6 z>hyClW=P?nfH-}a%2GKv(4*3EDKR~xJO94{%E{CZp37znO47T0N6Rw*-vs5guN?393V6gaY$q6b07f@TETRl|NPD9D&nHuFobtP+<$L;9K(4C7i z{15VsA{ZyMj81ure^a{@cBiavuy(F1CwK zd_Jxn@pJQBya_mg!J1n~9l6RHQ@6gS;n}!UR#}6$e;1pl&fdH=sl!$Ndt5vbJHI``jt;JgMSz?4h~t-H3M~s0aH6>^t|MA}bG*T1`r2#2t5t*Rt1Tft zE|14bvQfTHhQkZdd|_73WgR)n;g_uG_(CYS+vnIqGYo7AJR@GAn2*KPdjDER5?#X! z!%^=HnsNZ|#X*zv@dZ^}v|uFcQUo3Os?VhDH)YK<3p-)Ld|qpeblTE~2!17}lfT~b z!RYKp*KFUaNe`~=y^Qx+@7c^QHMIBI^^UjI0pQ(Mz3kpe2OT*mum!vQKdRn4p6dAj z13kw~W+{7>P?;gJ&LKr5E0xTntdPCuF^bAKL`KFjLxjxCLo%{uZ?YZ7Jcn~Q<9>X< zzu&#T`#6vONq_V>Z?D(ux!$k$Mgz>fgVqDMOSep(luOZ8EeDw3+)+5hwGBwq2brl+ zJb!3oKDrSb`kp{kQP3QAMDjAEXmY|=O&RP>5y(398qv2O(O1muwcl`Q#MRg@KYUmFw`PWi7tn2-ErZ`n8XBcZYwhe)+>8M=@5tnEV%!XAF;V}oj zC%5GP&tD!9S-^$OfrLYquHB!VQMB`URYP2OG+u8i%SRfly)&2v! zKMwr$xS2~G42X0{v!=SwmM`eLeUCU=YU$^fNzWh%DiDDJ7oN12&4McZygbKnjaOD; zFXiCZ0Ml{cAhdkO1-KtW0oL&$JUTC?tg@~G@1#^D+g{l({EmVBlRj_YAXr7xywP*C zFq3?8YK%ou+rPhamf}prNITxEMYV=+!)+*e&daP*loJ~uPhPRaBC&<|5#U~}EeyIt zc=rD;|1PomKHK^5VFvb+&c)uTBE76Kf)sNIU+cc-?ZD43A!OKA^@Q^={oCa_9$xU3 zFCaDF^>!6~X+n%qfRaN5r^9kQ%ioCozJgzVx=K}S}O}(A{StVDXInEPgEb%qa#>PAub3HsidH50_CSfUB>FUvz0-On6Dp_F26V@ zU-Xn#6zp=Iz3A!U6I-DTno%mzVB5Q2&cCH;1?z2v%C>YO8C35IedLY&sh1E(Y=8L8 zuZLB6Us(6feY3*o({6h?XwRp_G1^mBo?8>epLpe!0XD`Ppeaan?JnO$%fZ1b0L42e z)?udLF={=XS9_7;qMFX==OEyOITm(jYFu4`OCoc27}@9%60e*ClDrI?liWC%yW}{n z3hR(^u`y4Kl30B^5Mi8e!5}BGX@xV<(s=&>wZi>W3`um7_!CeyXfQ)1H~Q1JBL}K> zQExKJLSHO=-43DVQ%gFUnNeDRQzFJ$tJrrNkA3=nVrH))8&oOVhpGF%38c+IcpLxa zw`!V@yRYX;eneqDMN3DDzt&J`m&((M3zt;z=-`5k#K>7OEVh3?o^H-y{gdT?q@=R{ z*62HZvKC}N-+IqLbh^Df{EqYc3nU#^0`gi zO0QYJn|nhXZhn;9;K4dOkZD*JO*~#W1$i$cYXb$htr<^8SPuK4$layJ*}9Z>Lhq&g zLyqb>F-5{5F6GVS0L>8kKC&74V=P8txyKX*!9>Vjpd9S^=ySzu_Merl??$2eq^g6RT(_*7_{74Ta zMy~I5hDf8{ZkEl5mgRea94u=9sp?2MIs5(@WNZI?otZ>yF0^VhmQ3yhS*G5KdHAD; zT|d5P{WfS2eIiPTnX_HzuaMaLTFYj)^+{n{-~OmI%y-hc+jSC&8~DLyFOZl{u{DU) zQ^V10*Qohwl7G5eV>GsJ2Xa+tM0Or#x(ck6qk^64ZOtzyOG11@nA}Dax?b)sMYT$ky)q7cONv35MhmvZdD18z28+vgfh6fv$?rhb7EJv=D1eb=qmAtqVe{5x$TFmINI*~ zHy>!arv~S_KYOcByR>BApPYWjSy(A6EPQp@1{2eF$K*C4g8+5D!j=w_2%Fq{2<(SR zU#!H*NAHDob?Z;9cosZ4yjIi@)M_J5M*s2}jSx+TMncvZdj5uxUDJ|0ADh1_%bY@( zE;2|Asbko=b~lT7D%4Xz_ds6iw--5u=1xRg-RM-+8LXC|DETF>?3}*+7}|UGkcZR; z|887GXY05tW2YL_n_nlrTHI5S{9|!Y{72d*zUxBpWXG2QCnlHN~9LyxytU$x!gm;>MG zmnTPq3v0D|ZIZIVvwwm#v?-_Kb*KUm#aH?mey9%qRVRI1BX@fElad8rBJ}D=H)Bz= z6x87fIv2iw&KeHug7R=bMmAlX;704y!Z-dfwvS0>RKTe$O@#Tx?jnIRTPTn0czvOn2g%lSKRaQt|KMl6({iy z_|x)X%Ii~HESVh-BN9?@@EOW9C4k~hByRi#!^l$38U8>}#7lM~XiA+?S zAFp`Q{1@UBYl`3zWlf;*^khPkT1=E*`{@&}v<+7^X+o;^MkxvBU_GAsNw+cGNziPs zo&;uh1-J;S9^X(bac;+glY3>?kH*{MEfmAInjOtwrGUbj-Db(;h|Y2h6RTFIuF-$p zD%}u3<<1)qiv7b2;*F!yMs=vID(gJ8Z57K3;4Z(vbcnbU=#e;*kzEg zLXg)(`g==vyurG02uQ$Bkk>T*!o&369-p*BS}#uxr%8kcVv;RXi~Z^B=<;SpH)I7Wc`OkG-&}5xpG(b@y z`w%hzIk{a918Kw}k)Tal3d=B(kDYs9Zd+^P4ukiRbUVhZS<0G@5n9C2*ol zxO8zXFmjA$0eU~BYLXtl~;k~~F@5 zG%x5?2T`(N`XRBhs_kr{ks-U5%!^t>X$sl3qp8}=Xjxp8fKcpgCc;G+9$rBmI?78} zhyBjS2tzB|qiX5$bGQk`ftNE1ET}v;q7c5L-@YTTA)PdixH!|@B7U{S6l`k7ew4Z- zy{S>pl@}9*fHM83PR+2d1N|jvwZ0Oo{FBdS?HqUvkAD%9v+`Q@KBIA>wH(3w&3DQ?6(SX|t|3NB0Dko6~@(yy0UgI4y{DwCIPUet+pmZz% ztx!>T06)SHCwmfh8@v6ilpzuNt5B^3kG|jf;`HnXHGR5Hn8vcs^?3qgxMsMe{bH12DSQKXM z$~(V#WZvo>|4+!S=LKS_ZQxIy>(wZAM_j6C_ zegFAxV(hcVKFffe9JkN%1ETPL{BISX$5+q?UzNRlMc}WsD*W;-8uN=y7avLjs3FmG zkFiY9N|PZk5iA}`#YUx?1is2!{rLMYU>Coh)VVzTJyr{K?|~#Ed%@7Jj*M(k6F8~4 z(77^bgXjw;No|l+XkD0mK%9#_waLVRHv8*5}=AYX!igmx^%LE$F^{jP;L6hHj`)EY8?gxiMi#Z8x*I6Xen z6YRA6tCTmx>x=UAUtQgW0W;jG3vk_9zX;g~T*<&W(@GEuabq2KV=525#yc}}JgABU z_XW%t^I6*TlI_?lnpvLaUZQsz%UN|C+0ivwSyhkVYvSZ2`Hi}d@3mWx6CAu&R{1Q( z=*H5BqtQe;(%#&DRB4JPyJlQg)uBQ_+k`^Z$8M1X9~VOR3_%p5E$ODoC~pv`*{&uz zMs?TTnW`%EUicS#(8Kvqwi-Ga#R?5Mkkn6KD-S&DTSSbig8!ikwGEy|g4!*8Y>k&d z^N6}MhoOi^-4K?BETg}#axNyh{<+BXt~PHr`++unq)Y*;ZEjTb!~G5l1k?HAOytCVs(_6*Zp^IiO-8L~h+D{MK5k7BdB@Nfxo#?F+_txz zQQ2$>?=ZgFd=x?h*G^esb)Ht@t;rR$Wlwp(!{v?FHT&B+InaR?hJpIO@(&-I&wEMk zO`VmMechCiiE#EMHl@PB9z6rk*3?RdcseYVY>$P|bv%u;*R}_2OPD}yV`fK%)ch=0 zO}x66Kw!S}#An{W(IN2*tYPK6#S(ixNCi&)zR~$qttl^%E^Gyn)gGN5RjzawQtf4295e0Xc;TtL66o#G3LyS_Gq@*tJKN$f{TrqoR1jQ^F z>-`N?MhvHCAl!!2c9}9Wxs}9q)sMOB3J#noSj2yh3HfjL)*6VBAFfR92}@;=ftZmH zComlJT`Ue&z9udph2J1Xm}JDA9mzJa5zR^$QHJvCzgfF+Vl)8+4!TVb*6YY811h`k z(^*&>eip8U^uccY8}8aJ5-~U}oi|Lcv3)jtYT|Bc*JRhS4l12WD=D{^S_F$&|O^p7O692c5aVmYgFgizMUkJ?4xmDPzrtQ_b zv&`LLP&e`7H5{D~wP`w}mFB%A^+>3JfV*loUu)m;d+OIluK18AEdqv2>e7rX=hhPF zCPFy{*4#k)T=ZaixB@ry6?DTOh{2H_`b zZvx~8MIpIL9}5!qJa)4Lf|*>CxC|dn(uq@UV~XC{T{bH!rPJ=Myn?m3I`PJ;P<%wZ zUB!!|8BIv}X;-ybq~e{g5|b6SD(T_^8nJBat?<>%HvMf6J+qB5AcjY~Pq97_XzU1`8E`BQ}US;~PXq_C5K! z|2fA)S7}~qhLc_Y-`0nNzVl-$jIsQY7Ox=Pm*?eZ?8cdM2@%2h(gM_yI3KmS2XAEBSepZTRBx$Bsh zaQ?d%#&7}i)`F^neMUXx;|t>yqIE3NNyhrsg()_UW?R}4Hl7t$lnDhnhw9>i2AE7Z zM~_B-_f_6cAazk|6 z$I?b?%0Vx4D_8G*o!X;kiw#%qYKtL)#)rU9mlV}g8U%x}2C9DKAfG8ZzP!VYz-~p> zR%unPx5Nx0&c%%C8zff`8+L@D%8xuOF!b~ryC1MyfD>sVhsmVg&dJS5emvTpy z?{2OB8%j0X+LiUIzw)Sbw<}2V$J6T4YDJv5pzs(O18=5$gi?iryZ-GmLAlY`;tRFQ zgG{q8qL#+R-i++m0gzr=r5312x;vlopXp_}gz>3Y=!iuWHJ@*kXB4!T=P2c358cCP=|}h+e?M&hh)lDvuE&u zL)q1N_ZVgBx4sDu-L>??XmdF|NC`(`3vQo(%bAET&tjVBlXskDun zp!NN!noAQA<_A%B1D&+HrE_R>I!gc*vU^au87G(ACn~d2mF6UAZf{o>FlE>~&0jfy ziPB!_z4)z`x+S@k_03<$o3H+N7rZD)}KMb?0f2le!dwAqzH{ziMP}p_Khc z_~Lk8B(nhl$KU}fv%-KTSg=#-1Sm#;8{~q&;<-EC>;enD zjp6J{a1DI%*)|=#A_Aw4$y@JAiDHHRY%2F1JsIwDZRQu}DK3Z@VEDAfq!Zk9wB?j* z5(ex27+jPqruB2K_W{%G+=AGrbDQQm_NiY}OZoQHyNpyMUc4P|3%UuVmL@eG6|&a= z7*QO6hHCXMte5=zlnS|Dt75%~n|L#daL7c^_cqyy_(Pus_|Zi{Iv_+GW(|B+r{lX&3^BE^v}18?MI;-a_dUF?fF%R zrC9_UW|iG^krZ8{I>cPvN~;jAD4p4)^jOR#p>omb6-~Zp%#LPYa2lAlj?gPgGr|pJAJxXF6e|ImttdM)V&tIiEFZw<*R5~aD&__L46;L}cmHtxS zRYD{x0n6SS+Rm8_HAu0u7ak}rzBm7?$-AzGP>&2ch(h>lK--?|BQq6*qqS3Po!)%)RodTDnUF)6w~mO8DV29ds~z6pPdumcx5uwD=1AzDPnl1e z&BL_N0$O15Es!TpS68T*pCi@NRCDYcpt&<64duCNU1O>(IJ6qrP;#zg@)lDoX320xts;nKOS zl9SRq*BGFU>2Q9BUa2BW^cR`AIdq01Wy{KJh;2On&+ zS@%JYG$Ztc#hgF(JuKuq+0$`ckv7w}8w4cnGcA`n=E&mrj1XVHBHqo6grM*SJxTfr3TrX?Eb1%QMC&xuyE;LqZ%_?j3 zYLRj%A0a`mRSCPtcf?TL$+A{^{$5+b#<`nDdMvn~b|7_5qdSO8sKq7z#@+t7X(TaY zTmJpwIUIYI7~q`fj#7_2>QX> zG63f{yghw*x`1o`-Mo6Ly?!QcY_fJNyyN0|S@Ez7sb&|E&ktV?7^QUoys0A7IgM7i zxG9E1ZNY~{MtQd=6O<+a9d0u;-0o5_#ex#J6LqrS^t*^MM~LctKY)_gve6yO8bxjr z(LlY~)+k1kBG2)?=VO%H4MFhN0Ou97#5-@O0`4y@;kCkewhBM6eMj=orWY>= zv2-(C=k=nGe8;yfn<*sr-A(=P`-|~BY+t&V3qz(=i1j_5bMy_MU(K2dp**3GT}RrX zNLR;S_VeD1#&|ci!B*B*mhZh_9WvMBpo|%P2;$~|-0qDlML~Sklwr;N39Cz@QAD>7 zkjW_Z(#Ve@Vr5~bHl)R4AQ(KP;t0n8Uj=|bQim{|uPzwsr_lo<#`k3j;q(957BfmLi?{PdDhwTtkFOmJ8V+3&r=f9Ie zpAH!w3y(G4zw3k(FkV;!$I^dU=8^jJHY1;?qq=cZPEfI>JniXSKsD|Cng9g4i|Pe^8U_>3+}!uANHZGdhcDzG&D6|g+(+Cys9M@g4$mE3 zoeqj{d?amgFS^Y95W2aY%+E8>6wP6&q(yGXnC*z_)#T7HEZ@{22dKU3(S{^nfTC3o z)-x7wYCYh6-uOH)S6tL=dF%>hHn})m;fiEeGDU`us2D?+E=0 zMTeDl=}I4m(51zTbKgx;zCKN&v1q=EF8M4#7>x~b*r^OwlNBYm{=K}(K>Wo@UP=&m zH(Wdkz`*epH<=I#ocvnG`0b-az2@$i*g*$B-2a97^SAxpbSM~{3y4q=%QoeX<_mos z?;T;J{t+yS5a&|ot>7gm2&(fc@%}D?whiQHAZgzUT%z)_S03dds7CtHG2pkt?;g%I zw9ZV8dX+#f6j@YWeDlr@J1Nw7Xa#2WNg(2MGOGz`y-|W>!?0T4AARZ1R~yOMEnKBIp@))|WsDTDC|H8udB0@ATz_7S1nR{MJk{xHy9m!5vLYQ$&! z(4;p#bQ7=xsz;t7z&@?)(HbS{`LT*#(TNeIxo-6AM|>JHNry^7zIq3hE&5iU1KLyM z0`|H7sjPL$ppaOl)nfZOncvEN_86h8rX?i0TKn!R?Oy&Kz;V0tODS8$nM+N{Gk>nQ zYyIV5=Gaq7H{ml)Qn*UZ;xiQ&xRaSfqcmw#aUpbw$`*7jo5SF-6O}30IJ{(}^V(OI z`a962kYup*JbmEZM+HXGHFOUI;lC?&m_Lm~lAO4E?i{?f-shOBz9C|Bzl+9h3H(|V z-cuR{_TM+HwPO3%ofju|J-thF@{h{CLS{L%g%1H~p=biAI}c%aW$S*ebG37VwqTdX z-y!9@gU7U&7ZBVMGY$6}1AI^)J?^ZS2))kN^lB=!Vxhm>uh$ej9@Sguvx=y9qWq8C zRZf4N`3m<;{XuIr-DQOE8Fg!bpS!imwsQ?YC;-BgEQ;4xA=?6jYhjW4MH^|Dpl!G0 z3GbHAq)hb}Q97)?1YSd;pNO$6KOmJ;Yiz;KcsKVOkir%`;)@cQk2px1FJ?GeX z3WnTQ{oa&!)ZZH|9klFmI#9*(S5)Yx^gVDl!<`1IF-~MstlDaOy0mwDp{)pCkLWZ2r2gzTj?)*kR7KO$&t^H@HrGNYA>DY9uT(*4Ji&3Tr z&hI#5K@y0()VVTEUUj}b?!9#Mo{pQGI_KgH>W;>|cdL(j^5UePeZ@J__opBA(COp4grqdEgoXZBMra{4$4PUEZ22$i_MvM+wcr?=s8AJ?7AK;M}@?~X%1 z`|tj$(_z!}=SN!#)G-oTsmDx?Y+du^!RXr}57BJQ?Jvr_-Tg9pjthFQgYuf?4I!7r z#~!x74TvVE*<*`DBqRA|t*$lH4_FxyQrYuH|D%|M>vkaDMXN`Es{j+NaM=~uOkzb~ zJS(;Uw;?V=3c%&kOeEnsfR+qZq3oQrm)7}R9mb_-JWR75=WQVW1(*R!ZS!PVWtaRH z-DOg~nDzJZJHhr%*YU005Mw|9d*?e^nRnD{G86g~J=^C*oU~TU6|}hMWxjEr&3M(W zv(R_Q5mH)pwDQemt2G|}m5;|SB5+yU?D^W?r4-qOQj2e@-`NE=;=L)wjhlZaE+1e4 z8Renoat(1LZt;e8&4#v9{Sds1Dn3f!^hZ}7wKu!EUgjR8Zc}1GvX0n=ej)oRekPB@ z?=Db#r*G+q|4N)5jb znfBfZYki3(S2K60!_mJ6zqOv+d%pi(?dOOIp1vR4D5RnRP9izzGJ_5Hg zghK=fL;YNX88Qfg$eT{f`M-J@>PEcB1IQsyZ+UeuQ@V|x&||xVlfT2p%LJMXKwWf9^0OM#A@kozidDEvEL(8=PiPmsUl~qbUb-7S9A9 zy=8QeGI3o%Zx5}%4=siwcxq3+TvkR_p&8Msg70Aib9hclzd&?6Q4*P1GYY`}V|cLt-lBF&;PLZpj-$0Gn%^7j5nI?T zr}7(`sBsNx&hHwJH&y>me)guV@8J+Mj!awqv`G16ttH<3s;DkQz4AWK{=d%q|8s&d z%pHOVi^}`^DL--m@faX&XkaY;s$XGK;1bvie7J`q7oKX9cD?A2SwEef;uvAw%4U7x ztjHLBHP6Z{GI0nuIOs$==Jy%36LE@6zx@ue5LW{bZgpU@uylW%Df0tzVNDQmA|F>D zUipFA#+g{_GaeB98{K(v>9)_Uv0Fbv&t-v%bg*rv{yueI$&wuxFE+gmk2q=PO%qlu zh~fS%colviDsX{pvVaJ}51W!bh2TAj0{%BxNlFUp!OPVz+`dX2h?3Pa=OTv&iPvQ# z6}=TP-cv(ZbH#e>RL zX(fhjLpDEU-)N<`qGi%1|Im4Muzsr@{8N&C?yDpN@vG$Cm(UwZ32Cx_jlB(emD_YF z&DIL)kOR@8z8dEoWu{k5cABir6T~Z&c2>&L0r?bFKMUWRK=ar92(DJopYA*UQR$Dp zb>M7vQMpfl6~H&r@$pg4nP|t5b!JR`!6!sPl~U1FRZ{e`gMM65xtU@IsY@d0KYAYl^gM7IWOoj3Q+G z+p$>N`uwxD*o9|qF_ItTQvCOD95x>;2B^l)W{h1Xqo&AGD10||*W5CM(BFGFZFssJ zpe?EFXJ6dIZqP9(_OpB1(`WeCFZngvIi&~n)aCX87yJwSy-$p?x-achhpV^ZM&EHB)Jkfx ztb0!78zGhR|BhRTHC^8{WK{h?%TpA55v?ZT4US}fVtBLl!E}2&*zVUA>S3Bkn*8eE zAP*Yi_`A))R(bD4dZD%8vhG>cB4)RD>l+*9wn+c@6iv;E4SIbv`(C<_+r0N7`m#^g zz@qjmuhl-Ny0Uj|$dnia=R5V&IwG86oP-h5n40ukbdB5V2RV(t$PD?_zUNeg#(G&t!DX-u+G;2)y7O&R@ z4xh5PGSr!JEZrpuK###{@&QYjDXZGa3-m0a9EkbEy24scE(4r8w)gvtFJHDiGa8F_ zZ?UG(SiF14GVao|OKHegQbIg11)FiC2IHYu{P z>R+?*-DP5#a&B5Rf;I_2i0Uei@L&RlB)H={YI@&G;Z3>v%_l%!%{ACa@6@Bu?Gab# zy$6?B;PAqeKdU22^qJ?%xA?sftM*0duJ_EZRRQwjams(;yf~jES+cY2k*9#+*rc zVb(io{*jdrw+z14=EPTjB)<9e#mAXYeY^a&JnnptOT(nxM74BdFyP+ns@0M4WnVLs zoC$@yk>P*^%6w*lGBY1nKMPcCz44PxNLO9K>f=D@g8?8(7fEyHup|+Iz1|usfNj?X zKk_@!f45|9V@^CMxd>A)4gfDObRRHgDYe0`m04A9Gj^sfXKdCrs%8%abS7lrh0<+g*G(d5|K*!f=UJ(GTGwhn4*r4 zI-tJinKp*ZH#YQMk*thu68Og_U%Q)>vM8*hmvS16;wpMzTEDogx%x^dHK_g}vf}Z; zmf%@gwcg)Xv^F|Z_66=w7z8lot)tt9P6VUUUo!1K+rwTj0%VX9pc%R@i5UzLU`)dV zX<@pN(HPD%S;UT1LN2uEuR;}{+W1H(szp2;-|Oxk#+BR3_r28IG^ZTGK2r$MFyd8M z=Gu$2Xmqq{OYfaeuCu|UgUf0scv|33y* zv{TMu9yQneJ8uuTqO1xUz#=SP)!Y^t`<*_);M?oG& z*}+)^fuQQke358sHBYzkEb=m`z8a2cxXs+jr0b{0N1d6{@113h*W>o?f@qF-O1c>V zr59+_t0saIwNkdCx)Yy*zOG~&`UfN`blV&J8Su8RFCBJU$q7}bGd17gsu%!IboZZH zQ+wGrbf2?&;u~JRgbH)UD);Kofu|D&y;t&Px-(TVQ3Vcvysfc428miR>%MdOO zP;Z~p`}b)jRu7|CDSm`zpzSQSA*7)7De988H16#x)DN-aai-0A$8J(8xGtt*=*4oVyfusMlH!S5)}8m8Da<~~J`XIm8IUwT zJqKQBg#eKM|I5-r7ejf#-@C$IGUFK^l#R_*Q_H6d+K$eJz0gQ3v$uC@z0uaUcWb_Q zd@nr25A=;4bU$kNwz@V;wLitF)p>#+vb4|kia$YV_e*TXyFMxHW6)mrUGmpFrfg%& zd-Z*7)>Azu-=LU6fw!v9)7lXbGGJUE?^1B9^UZY*@gJ#Ah94vK@&$H_bbH?PMKCe5 zKC-XK$xzK#>E@&3em&Gt^W>4T$EOPXQ`pmtU#Hg7Ra=b%PcJ3*AYyHDC+m)k%N@>K z>{-Q+__gbRJu3G#l;uniiQ^4RH>6lMN$wx3Vo;#Os zns}Te?}YwnUgoc+qRUXEbv67&qP|rU&V$y@SU^>i_xWC?ufF)Ee~zq#IC}pzeYZBm z@}-~122WZy(g}Z6g<^l0Sf2jnw z@5O|Io^Qp@g4`Vo^aOk#>-F4)C*Sg>nlx~cS-CmE&ZozMWXH}qt81yt_kYZzhQ*;_ zgLk?x&@F?MHud}K+JcEG$K$VV?4S1pnha}z_IIip0D_I1O&bQ%4&_9Q5*{_3OqV>e zx60AwxVBo%ahmW#*5EG9r&4&dkjeRxn!OD0KZ_#n_9;tWJ0F4DCPBF9PBpW_nN71&??qB>ExN>##c`>sRs)^#^Zk z@Mi*2GUoQpIibE=jes#){_e8{yp@e;OWZi=g*KB<2O4+JLoRYqDw3i(;C zj?ssIr5gJyn9EvGyJHSO%ZcBT4N>KP?z>;#2q<%{H_DQGH_S^Q<}zroee(FQ`kQX! zQ&U9W@6Y-RORdU3f*^m=Ta=a7Jpn+uuF`W1-$ngH1Vn&y0rW3sEx8=jwQ##~oYGoQ zk#_+aJ_uy*P#g3(DCeVY*1@?j-7R7lW{vLs5|3=P2;CG(1X6aA*TmD7m7%qFv5GP1AWh?m{p)cuSop#G%CMl#R9L4 zPUA$?Rd8iKqf#e(Zr?R6TLkf`nd?1=17-@AZSi$NTu*|C_uXHkz`*B`|c5$8Xt{DkP_2 zM4LW5^@HitFtobewL=gfLceeQg*X%|JXRhJx+-lvQs&^gvr6N{PD!<>-c#;fnEmpN z7E%J#8mW{cuxEo>!fDa$b!D54`Xd=z(6kqO{$tNbDRq9Up7O`nS+9e&CUD@~BqTm@ zDLixow33Vr{g=$jf6y+mNS9a@w6YUG9XF!)S`qqNtowWlA9zu2Zip`^0lpy_j8)Vy zp-ap`+ONRzOVH~Er|8~@1=4jO-fw7@o4yiuHwZ==cbZyhcyp(Fu(2fm9j_}v`9l5Y zdm{K4i@VY{N2{6G1#ZY)iGDIh@nesSUI|FlS?%qsOxSKUJhf&FU>1CM76%t6Z&+R@ zpXaQg{tA*-A#cCDeN^Fq2SNY*vOH5pw&N}Wv4dxn5zUWU<0nWb)#2hpK?E-jb=%K} zd_gex5RORW$~%LeOD%)wJj34=qdIqSI!7NMs18kT4^8_{Z8Ui) z9GSJ2uTN5E6Ly1oM!t;ZxKLGu1cc9|_=H>&d>P)mw5bpAy0`Y5wwCzJF-s*b!5sTB zWMZOP_!fQ7QraPntpogxpHx&AR1}Us{yzfA=rFy6fjH;_h~I$ru}?q};xzE=T43uE zROPAC15blBrO^f-yGPGug`$K0dn>R$8TI;UVpzobl4nei;{hZ0iu*l5pC2KW{3DjH zud1ko`>Oe|wtnrG@d->CJUsRv{qd>C3}GHQF@2j+FmCAgz*_D~yEeS3i85b~jhjZ* zH+k>6c7$@Ehv*yL%!rV95RlHeNdt-q?K3Z*k6)DUmB97fbNK?iM3K-!-OB5OI9;n2 zf`xuhr2T*NtXbjzrQJ-$>-$y9yZG}S_W$)+OwN@3Gt!M+0VGGD+-SQQWqv;Ji#JyK zn7?G~OR}A@3+g#DNQ=?>$ib5lAmAg3ZZEa{A$K$a7^L$6{y#7#9~}JCS$Ley0ZVp| z(R6@Ta#eU_iK$q!AG188_Z-}fDfsA9((rBPb+2y!3eqtCfPv0P`t`ZnH*MxZ{%9X3 z=+`eKl<-&TO#Ei4RDe2dEorJ~9gm5^@~+6*a@NISlab1!!pNg*YEDO^>0(}S8P@IN zZT5$CU&i6nx0@K`?J*0#P5^Br>`WV>T2^Cz24!X%@PAmP;zjj?R~>knNQ)1wP*#pu z@`i{N@$B)Ei!WQQF@`e~U0;7MvBDHeoo_Y|LIa@s2{iPJ@(@=m*yK=qXUJCU4Jd@( zh5h|y?t>I_FBP=@7j5ZNnZ^RtR1|cTCdD7Sf4WOC4VxCU+-WWVVA_#oW^tkYeBv>% zTkw%4^S$mEnzE#?=J5GovY#`|yPl>jjd{#m(q5+4C(OkgI?8T+9l9B`}8R*7)t?ep!{&8hrOdH>|{N zyesPmo(Hk_@K3VZFJg?e?8fVi;@-TkB8dCD{R`b0`6sx+F`s9>VI;OW)W@UR)7Gou zVi6Bj{p#}SeBR;8kvZe+>{$|W)<^$-Kg~Y{AuvxOCumBD)@pUQ`TwEoEaRg7+I7#6 z(kN0AgCYz{OXq;7h?I&-haz1{_YeXqB_W{1fG8o|&43_X(%s!P3;9X5P$Vt#z;KzP{JZ{y6gsw=gYLXRr9oaZe(MOT&P1z!u|fE@@7QCDm;OD}j#D z?o`T2ZYLq&WZ9!y+(uS-k(w#pnYPa=gN!>D8l?6*oZf&4H6@g<;pLJVG}&U{WiC%V zRsHxiag&8k*^uzR0c^LvTt*NXN(}lo%TBr9KE7+_70y?&(fD*sjaDVqz&DB7miLu_ z;kQ;6fQzXYuhAH^&4#>8nxWr?NzqkpVyzuq5E&tx@xiZ?3*(N9ujNF2bO71BF;9s{FnwKhcgI!xL`6QsM?|Z7=HY)k zmOH@-HOm%^?Ha2Vv-TK17r)rQAl#C12y|zf{C4wU?&yehgb#2}iVQzV;Dj!Z70O>T zv}%p--#+XZERjk_9;(qjZ1*&1(SKQjWg0F-Znv!Fg}dDN-^d`Gt^8L{*9+d?F58b4&# z&d`W=tL8vap|nJE&Qzfvgvbp`h9kjy(AVa7j=~>4&#iR)nGc`>{T}?d@A3}5<}A3G z4+$A48f}Q8NzctY<{eOnN{8WpIful#Z|M`UHFuxmsUd_{L@u6JnHBNq?9GI#&0BM_ zn#+hAzt!n0fM$XNL3b&B8_FJR6xLI#?$+~We8=~Z9w$uOi_z7O8FTm_sM}NxPGL`N z>;#R7DJZ|lujy99WotAd6_2P?19d27W(Q(dd4dL(aqyWIl0Ungwd{}uj{r%Y(Hl?~ z!4J5X-gy*##;&G{-N*+@l_q7c$cCeTG)=`W2)W;q@3?BCx51qFh4GH;`^z^!pF(B< zL)UQ$;C+RMGa;0@K-^a^iE;(L<|b|CC%G4V10?^1-HrGWW2!>PbT&mQF;LD}c(leX z5v4AEF>7hQ@JVtC&Zx(203?pGy|Ac3pfr)}U&^h_9$i#K@75<`^$qVrLg3tVY}AS= zade_)!$vHW$Vm^swIjfdx6k<(bTgYf@0xzXy=6oihhBy3mj24_^ep*+4j?~?XD@qh zeNF!UGic8nlT}WrBroUJ?qb0IwW$-&Pu9+vi@RskBv>W#V*Ihz}-q{d_cSZLnR506se;D%O-0r zGER9r?~ylNF%1Um1IUfH(a&#IDa+0uc0z~g=Q&}@)wcd6H+=ZCuP7~vmJ)40OnLY{ zZ5Zw{_n)jAxk8l^=vp(+ajJOWO%d~iYb42mV?=@q6s*N)xC%|+BV~_c4bkIX$Yx;r z;m|7x1Jwpuu3CT%@fP{x5xupXZIwTyMr!2aKkr>&Z9-^A-iOiS7ryYwv7fQsYm2#Y z5{4Sw^I3E8KHDH6vxB+T!tmiq)q7Y6Y{r$$+h%eRA6DOPwvHc+G2PzKOHO%qYn!kB zt^!$6GL2iQY5v6h)()4&QmWzi-ASAdIw_y_vre7yy4J}TVc#!EQPKM)bFe18#Fq~* z1{( zn~)rS7lXh-a=p$Hmeph!!LOLc@F#b52)&?a-_-#JoI5It)XvOnaXU%gvuUN0ni)>- z%P$$E9~e?yFz6hoAcI;3t5 zi}IL=4+=Ix7DCrMRYH#c19&^zN>k-urIPjLke|L>bY7X2_o^w9LhVN-|7EJ}HC+bvdjf!Atvlq?= zA8@*u7S`YMGa>}(tDWBXG1YgU$UOW@-Af{DIt1;jvS-oPVwT746XBtrOL#bVzCSEG zo+rC{TCEaJlaEALCjD)WV|%ul(qQg5$zu^N|K4ar;>v+7&dN>pYtknHFGO8wdF6VB z^+A1k@?ycq$Y<9O?=JJZThDsWIi_&tdX;yw%B@^mwtBQ5khCfUxAaimc z7$hC)aGRm<6#0gp<@Wr{(r^$T^h z&pk1s#$@_`(QmW8i$ra)a5d4DBpDufgTlNLBB86|0g|O=xSw|I2{&l^nCJ6oG}1mZ zmL5F)!Vu6#=JB}dqzn20uUb!lA`6oajA3(EwrAID5>MNHO79_Y{LE?k?z-QV?E}D( zG64;~lWfwJ=g0mN*iZF!A{$I~noR8E34bq&ml(#+AYJ1V{5pOzHHqOKo8QuvR zGldaxcOXx)q7hZkJLX4qvdzqyez=AoP98#Jve>N_HL&w1(G$O`pD2Aqgp1KN2;sW< zIAPH^QJLw(anvIM5uAqa?zm>WP()ZsS6Mw69S#LdGYs%X3y|6&)|7>+iBzTpeJR0A zBd^mqpm(J^MrFmwL=!Ujgyke;Cv1KjUO3b&MbhIB)sAm!|7PZ(F%1zLeSz&S1boi#o=WH+R8b zH{#B^IxDVDy;g+qiA(*c03cvjLSMjjT2WFG()r;oh>D0rv{zF3M*^h_VP=JgzIHK5+Sxma_FxMcnw3iwTJc?sgGcSdf8T8H z-i1W8kA@aETLL!`JrA$4tXjwXIk=rtj?{Dn`2Fy9v)chLY*!?6Z}UJcdsu5_rjrw1it2C*4YdQ*{A4;Hblv~Ba93^ z2hQ=FDgbp436$GDXM87_E|5%>`$Q_dXBYKhQ7$9xJg>T4b?^M}%l#B2jJPpwAL-_$ zqbw6}kIO&ODbKgJmCnE@$#pUjcBCdK?h_`ZgVMx+*v1`I@13EGPF89Jjtr2^999HCu^ z?l0O=U}XIZv*lnll6b-Wj2KI`-3tW1Z?y#rUgxZ|=f078J3ox`WP8e5?2T<3S4@6v zhM)JK!0r?c(PK!Rc|a%Xv5Ne?tGC2OSL{}5i7_|c9{vR0a=vq%Sy*R9;XGj1L8R{k zOQq9c=6eN*avr#8-&Q%~PH3RA@od2c903KSSN)4?pI?MThw+6V(I&7^z?%740IxT6 zjI9Ig^uG>9Hwm&E%l3kOXhfRHkb9yZ$0*WpzFT{qt#e~KB|%gQyU^-#_- z%Itna=p?CaISv-^TFivCk8R$ys(LxCQhq@lo8W3C6@*WA_Zce6@;e7@l13Ir+3E_T zcfOjG_2XC+ewlG4{oD;K{t0ad%i;EO>;s-E$vt4%a&3FTSe}7P7~WB4^Lwb8QXvjJ zw`T?k&$z)iI=YpAfi}keTkIv1feOkbeMA%4v?b&#N68E5P^~FN_p#3;UokhV6zwL6 zKAXXBPEaPR$TwG#Gj!p8=4dob z_Ni20!%A^rsYJ%m>S8P490$B3KxCwmb3FTFW_S!){{S$&PC;Ua{xye$Ybfda@WBs}?KaS~XLS*FtZMdAEkho#V?(LjMkiR2{BAc7~u)6!YJQ^<8SrN8V3_{$OLpFL$DWak`6r)|zA(YD3)D5-#xnBl_S)_6e~2|k>j_8hPc(R2@cL*# znyv^rP1hvXAIYH$1L~Px!m{)_>1Y)a^^=Em1X0|NXF2$=^cPwqE16vQ%q>s>P-~0_ zWHdSbqz{T$Rk!N`It20(=wU_bS>tOdYTPM4tMuUG;DF#COHg@BUAXd(*IObABqyX^ z#pwH}10%VQdgS4q;q)PTXD{P3d-kThIqm@6w0cy4H^GJFtyY-)fJI}awIdVkvd~k^ z-{;~_!L@tuBgc?ycGb|QbcAkMF&*R0oFlYw>HTx|yeV)>!9~GeZ)471x7=m>-ah3& ze_>rEFc8o%*_ZWMb~?=|ycQ@EYZ(XzDA5_2iP&0ie` zN^)u5DzF6j#!Dt8%66+hE?lRUGRsk)2Jg>%;!=*R+OU<6ZrzYXF&F+&sM-_)ea~u& zqOS~ci?oR62!&kv-?E`|#-%qz%z-25Ugc7fENk=0zDYl#+`wK^t8l99ZX=N^5Kjs- zp7^>h`Zw@m9?#DamoBlVsj@r{Stplu6YIFIVYGICErFL`1A8!a(v>Cbl8ptO#|mtj z2uF3Ipc~kXmu_Md$K1a~yu7M+eao{>AyDZUl44yIS{f*oxXhcdN#gxQdtw*8rlLPV z_R~QwbS&Gga&=e37}k@wJ|lLXTQ8V+%4KgXu4j~Forx0>X!w6PY2d40 zXr4rjJ;&~UNpt&$ocQ~lQ}-$Z-}xw*VEj!Av>1ouPPTPC5gpM}(m=4-0quTnTA)?% z2Vw#Ip_l+vuDogBYu(Y><*qO(6W`UyjNF5$KpVO-v}eZE>_x=KaY>X4aiLY=uXxOT zIiEg75-j?_xa~YN$=g^c06unl@c6LLk2IW%o>jNP*3Z(e7?*lt@!ZMdXU z&uvc*)^88EgExpTK^WwYNG8@z3OiYw@+jfn<3h%Ztw;qDD3N@HFohu6LiF&h6BK=% zYeg$_8v@W|cl%?w6e}D#(nTFt!U2Px66q`C^jF@F5gOSs`T(%s0R>=j83&qvi9lch z|McFsMUi>XhuETb!nzMny}6&9ezWKh+GJDi%${nA`6_JYE=X-8^0hnWgFn6b)yXc3 z2RyM2fVa(guXK|u)fd01B-V6hb-1hg^GqD20o*4s4=&GNtrKI_KKa!hji3guk^-gD zgKs#B)}PUYw24g$B0iXSdR&ghEIL31fH}IkkUmJS-1J z01MBq>}E%jbhH|nF&BGuc<&Jf+x_y6QSWgF^3Mc_tOfVQsf4p;R89uRj$bGb&Pg$~ zG*r#;TOxi)Z8C;hRG*EJNS?isS?Qr2>1qIjq$_7gMPIjfH_|=}gSg9Puyc)Qaiia& z+t~StA`3=Br2T$LAE~ljV|wCKaV$8`o1gYa8kk`4X_DsO?-h;8z+Xg5fvU~43td63 zuzsGjvOpegiI#D3^UJxKjk3V>Oc59HMJ19Yx^KViUqgjq+)vk4+v%>sN$O~*Zj?Pt z*8>;Xs;tbrG$#?0Qv`R)R_y^G*%Kw>K^_1kWAYc|3Y5lR>CxZy0?!s(p}^Y$(Is!U z-{>DlVa7Sgj&ZfMEg6K;QzfXb5tCC1ck2Gy9K0K^@_N+0a&W>)`)QEHsOQto?&{}f zCF+)Of6dwOy|AU2(#$gf@d9Ow4!C51Pf4f#VyQ(`7MDD?frVR#nI8>Kl(nM_9-pAq z)HTy6_cysJ|Ki)SGXu%{jk94i{G#O=G-2ipXn zFj&`=KbAswS#}lz9qQwO-}n{I%HjqLwuf5859_5S1bXJDL!sio&u>+-cYUmivki%@ zu&Jqk7Eo92XZChL_Dx=#5!J`1N%qH*lCNFyyD}i}g%=xAwCIA}R)LOe12&}}PG?+Y zmo|J%OpnI5FJGTK8uxcjQ^Tcx`10M|w$*OjPG{xAD~$VgwBJS;#d-y}cCo{IN~iAM zN^K@d3S;uC{ef@O$Ro*});UB&oWOw*GhfIa-eNbW5mk}rW@2OiR5mQNAbqsU(J~6i zY6tI`LA9BgoSD-x9AQ#wHtOsXOq!!O1McD9Bl6qgEprDNDcq^{dc+Qku9hLh7EX4Yv{V9n{MhDv`XUb89rc04rt69;?xf zbHt+6(Xr|&n?H?MtWW+;w9U2~UnrHFRt9Cte+q;#xO(r6JT(jlfSwbJ(6yHc&ZqC{F@KLwLs!;S}74iO7sBd?FKkxpGZ@T7`JmC5}3J7ZvymEkQC7KGbS=SINs1>WNU6JM#S`3KT7$`VW%Pu z=|`STnLJnLu9gyi!fL+nM*FnXel#NN{E~^6mYhZ%J?g{nSf6w+$9XDyPKo*e zegBee@ac>JaGm{k0KxNHftSr^%*EobHwz8~OLMU0UP>jlgOe5p3S`JSY6kmly;(4X zxc>#kQWO|(r}^YbDt`?Wb*snJ(SnXa%624)PVcW$3)RYo2?rT?(b?K$mT{4<{MIP5 zg@%0U!R9VgcCQ^Wq~8@()o((Z$Ha(qWX0^`AIsYzH!R9VzpGX`x|N0Y)x7U*VH*S8 z?x}l~!NKH36qMAZYAc@`9s2f#{B5v2fhKa54}3Bsvb}+Ys9uH9T&cQum?R$}CYsf~ z#ZD1O2#M&Ky#f14h+%29`~)Pb90`ogWB|znhi3;ME3elt6aFmX%HR8KMiSVTSq(Y7 zKZcx;%##0?eg=xZ83(L1P~;sSEfLH#O~(Zx6_oEk!ihgIzNx_9hWD0|(5l`tQW7@h z*h}*>NfLqgZm%4SFw1;7M=01Hq%i4V8VZLw9K*|t@QpR6->R0vtSN^?>PFXbg& z=J}#Mu#h9DH?Foi-Cv29d43XgQh17Rhnl3lVn3(m%t66-9%J5`@#BivftXU7KZkk~ zC331*`pIuNJ&fQi7nc&CEetPP6X0`$dn*N6N7uDCR8}II7|5%&b+0Zq3_1(4dzPzJkZAJCCb2@D|HCVk>OlJ4vwYm%!Vw5z75iWVV>KFg{jp-6hv& zgjnYlpfAANe01)c1Rz0Yj-nuAAXto*h@qMBfUSgD`L^EV_i~=kW^EYZe%&NiDI?n?9;ND*j@RnugV{%c5>z$nc19dI~7w9)F`&vT)*XAPtYp+?^*79(4pe)1CD#j2Y^ZJtIdNjF?6?*QNel%6i$tF5f#y+hg8JIVb=9$s1VZHoH))+`Pu zUnDPNozI2v94RHghHRJUEh5U(6Z$}i4cA1}=3&yHc3(P>h-pAb#GKr{RY3NM_6?cx zC+^NbHEXnn}GthM-bVUFX&B%f52TRMoT?Mh0!{ zANq77GrL}~lLJvGQwjSShi10J2Av{vfQl&Uw;$##36}rG&T>xOpfvY7X+5=y}qpgiZ2Zb(8l*qc?}u!y_ehG zX`^;|qsmZVDg86OyGz@Ub0RF|H#*e>YQNLjDdTUn+6J!jvwQnG>YAmMLiwwH>puUL zJ}-E)!>em-12-%-q9yldnl<#M0<{YcAtfJoWOKzanaj#3d;i(NES!P%)?VQ5!7c!U z{SW-0^Vnr1c{@-~*`0`A0`x29T>)BvUL?VwJxX#C8^+X7vw)YxF`etN zzjhw_8#iqUBwc^GrE7hXBJR#cMAXUghs~sbKCrG^&{@#~FB-Atb)2-zGC*GiMD*_cIyZ(A2z67qILnK%o05Sbo1@> z=I_9{*d7^;V1vx?NW21)ss4N{eu3{%%63jTho4YkS%R%(kd}mfwQ#@vxxWWato{9S zIs7L@VphUNqkwD?k_V8`dFnKn(Ej!GmH1x7-v6LrQSvy?UR1f1lFDRf_tcr=ZUOSF zqW!1B6zf3PqW#zfy8TM3dr%`6a=Efz5(y+ZX=4|odSltSYBUS*YPc0G1!C_`cqR5g zl^(^FHn6H5t`x92sDq7lmevxK(Pj2#ggs38<2?=zh+QQ1f9%;)FU@aBl z@ASLpfm|QS3W{2m3>U>O(Jp8!Umd(gG~2K?4k0lFy*P8Y)_lvHO_W-n*M`aqq<=8S z?X?rYRMKw|CQYPc^sFjSBxt^Z%oOPFL+~DzW6RMg71F+1kVTwRw~_z75EY=_S`!jKve*J z6)%TZT)wvsZtoyipSnf8uZc)Td*Etk!ut_#N;WRPm7diave<&*mT~K-xwHP&)uj0b zg<{Uifg;b5yIKra>UlFB-PuNcpm=X?lNub2JnFoz7;{Hycp1-yD|+aeAwI7=#(B)S zhnie_` z)Xi0J=i#x84T%KUFV&D}fc@HFOfe~}zB>dI?*xleW6+=Us@p`Wf#ic9JlMI3bxEu| z0*C~mOVTkyM4XuvelHww!;OVd}v)#qLTjZSux9Vq2EEO^`=S_h7nDDDJ>A=5EU;{~YxDe7*iT?qxDE zElu#1{O?o0f0Wm?fKLA`qkC$H;bNT>$%J^E;luH!M+Aboy}h-M5V@D)9VG{>P8zJx zd&ec+Z40v<*xe7El^>Fp1JHBTaX@wwwP66Ppd_bfeq&}pXc&<1jHTMo#kQ}G?aI?V zo>Gg4w=>@2EHPQr8Dy$$Bh|lHxM==iG(Pk5bpM&2{X)6D6lgN@2Nzy9hW9F9iji9* zHOma@7?Ct{yYhC-WZ`AZ(}Z&h9T@(ono`Hc>_J}zbCRs8{J~F6_4mtRDNvMagA1xs_ER*57nP~u3@!mQ31#nI*l1XtD@r#Q5-Xn?)x45 zM%N9a=x`a&o8)n83pO!Ofd8TN1h2bHZYpgG9CH0o!rN(>r}b-rCy>P#BPr#E&Dl0f zw;;F#;jnY08HJr0E(nmnDtmV22fEx5zp0X?ipsL2j_XNZ9lq4xkI~cUa%b?2avnJa zgH5%145e+?$Zq%6Vd++)ofpJfBs!A{RGao_ndIJYHT;8msX?g01MF|(l3a%Drel-1 z1?`c;MMChZwaT!y3=gy6-*64ZJX@~LSc${+RG&*d`iNo^w}bjIMN2Huza^A?&aWzS zO6W40FK683JWyQMr8`(@0NFM>1-KvmQA_xu`48rm-YnBB>&ECH={Zh)*3U!Y)vGe} z#GSYSWHeqTSX3RoGRzl@aP2i09Y08sThWq3F1Xu;cnxP)*h|JGrX6`H1eX~*Peu9- zaZD_DogD55xOs0wpswjkE*XO_mG$(=T(4aB*&r1Gck1034f`|b8ohJ94EH+!?vT}k z=!XJ`Q+U7++`)&v?~#O}A}n$fLdZEMU|li{a8}VbxA{9a4`ki^zti&Z<^4sv#v}7* zcxr`CbM6qD>vvvjLkVVF!fQ$7!3P~0*6w`#>Y;1Uy>Wzo#E*P8Z1FbSAPH)2QHXDP zi$eD30fBogz|VR**0agKA56-F5*|LuGOA{38`&GCd{!2weXg&o_X(L;T%MgSTyvgb zt3E8kB0WEb*ov&PW!Js4$|&yMbn07uNy986W&qTiN24v$dsj%X>!R$ zzr7v;eFbSM+ielw24x;lJ|^xz4wwQ}+e(})nK#Q(7=ygHj0(zeq5IkF86gpyFAgLZ zYX9N4Ox{WiM*9`mm>`3Gx88ko&HKL1x87#JMgiX1^fmtB zDizUfh83Sz-7TJbd%})na8KLJ{~P^9L&Ce>{OFK^<~Ns|RN3}YO$!V|+xdHsL{qMC?A~#Obbsr|Vz$0wCQO^wZ+@e-{JiBfsFqwe=mfZA+Bl5tDTv&c0Ja6#Eub&ZCocS;Qd+_*}G4;i4MQdu#H@I(=7WJ zJPvikE!OS?hRp8tBZ}79W2Q+qJ|*3s&jT*2X(tp-f#&2kkV>2Di0DsAH{!1t-G4}} z=esX8szWm^EfoX&VH!;x3dgU6xR#zx$Q!+6GB9WzSi80UD5iokeUVyg=y7k$nH;A0 zc zxjDI?42g34Z<6=FcqG)rb(_78bft+T7i3&WDtN%nJ%lK!`{3&S6w`a?e7~loRx5zb0{7bhWRDJ^*+EX; zjv78A2zzP`&AVE$a_60k8an~537pTD{8S9_#lTUgGp|y|_79HySDYo-hDHfMGCuBj zU>{)R^eVGXLs^>M)|SsbiGZweXI<9^D=~BFw$qJGs{aJwtK>>Q8%ELl*31JCeRU-m zM{in&rFMdO{*m^$>gU_Obdu;fb&Uf?V$RNqmd69R%m~tFy3Vi%17C_Si)yD8());5 zdRFpC)@g^hXeqop=|cPThts6oYe+LbzQ2OXB}*r`o|S585s&HE+laN{*)$8+XB3E> zY>1`M$V>9-$2Fiphm8RR60hzj?t}d6z-#&J{@tQ*mgmOQ_xVS?o9RSP`i<$YkXnW# zYLy)fkGxaGxQjS+`*3Hlk=uxSb~#>dlXw6$kW9-Sx#*QakXB zUdCw-R9EF1t9<>3vDmEx)MAyYbk!HXN7q;G$-3OhNH$9rWVX*xZ~3Oykr%EI|3b+l zA23u#v)@e%O36ooKY47iEReCKlB<1$z8w;ahzgCRGmZvznBIo&gqdjlG+cr27DSvv zzho=}52s`Q7VNL6UwLOCoLj&koXxY=yIds;Dpx_tQ)tQ(%4H*38a&5j%Ggf15d zZ^1fA2|#P=J~dB-bCf@r@QJ_s5%|nV$ky2^{u(9*kSYAVS1*kOZ0hfSLE23Ha+=^_ z{VB2gNEW2`wIP-AX-{b~(e6n7)RX#+@Wk`@wbZvA+0P8i#^jwRy~yAbsGBTbo(;vG z=c$^mIQ#GJ=;MxDPa5xgOo<}Dgs; z%=+p7g0RxfH&e4#G#`w8R(>5w8pu8<7eb4q_>`SluiY#>9{3_)lb9i9CN)!I?k|J& z;?2arAS@knQ?>ZiSPK)2CeNb^NB0ouRnFAXtxcZg@)l$Y%LAa-snd%-Mb}%&UF|)n9c*hP=`|DJhpJAL~ z23}BjGPX@dB=CEfJ(&Lc9n&~6!;M@#oBYHB^YERgz=VP7a~16VLTjCXHmd7UT@7$l z4n&GX0)dI!FmlcfM!Xbu09c9?<=t@rg5Di9Z{dlVOlZr744qcGjhU1_eCV3A5YXqn zPscm3jc4<}&+A~~$9Ld8V>Y-278qSyKm2$et7EVQc_`nY*`V}I_2hl@h)!ce)8iOb zk56AR@`C#kWD`q~I*p?D^tK>QZyGI7Z;cABS*iO>N$!RYxMM7K?iDNwLQ~HHQ$!oV z6wzj<^CXRk+8m@$-Yb<%Q$uD>d^=O4+4b5TH;`^i4{@1-i%bUTB`4Vtc&+#=wIu`iJ|w!V{P49Q#WHtm`tn3^1Smjmbjw zn}M)Ef?f!5B_Kwe&Bh!>8*0pf7a>w&bd(aqFt5V>KmqAwpWDzBVi(TgcC=F#&v)^* z-F-#LBLlP_JejjzE=UrI0Gxc=SZl#`Cw?5O5B9PbYqIDkW|-K=g5^UE%5&Ki*T>(Do$?LT9 z`6|_Q@FqQQSYnUiDX=+g5%9+A286%{pjWfI-ry_g8fTEiLV>?rLxbt}wLiYJ(mTnnOZqS5%Y6H08?PL*H4i2=H;wF&dLI5J!xxzVFqE zWV==2H+C2+CQ-!17hzYz{1w~Q*J|yKC9Td8Y89yy@GOk&MYhy@FkA>D)PZ4PzCJ&r zJmrbRqG!qDK@)}LNq>rnoQY`OEGF@BkVqs1DZ2Wv#k(mp2i|$O8TV2muo9$H5kZdg z*#Ql-uCt;?R?;e{w*|#P!yV|I({FmOt<^*^9`bi=Y7~M6si_Yz*cdN-;Z!1O;aiPY zp-K5C*LQp#RjUbpXB#m|z;MIQ!1G0#;At*oZF~Fae9Y1zbVfa&=t)!iD;l|a8cDaP zW_0r4-|0{GWej^k6X>2Losvh*Yqjxc9z%<+W&-{^(?FFsyZ?i@$U z4xI2X(mSyZ=n$-V`NsqOcnbM`*FD!d;7Q2c*!$bQljAEC)5b+UZ5&J7Ph%t~(C;<1 zdaQSSR&@K9W3bP9HtI;KVr`|?RMD9#W&Vx)g99!_t2uPY&X~FH)~Vl2j^Fif_|Az+ z?PfTg4RdYC+;H}S;D`yc!TycXCvttUoSGz2uX?NzM>$1qLw=LOtENdQY#g1MVW(_= zmNQ0_ct-S?_))iPZfhxsBV+oA{H))5P{;pA?yelqylN{kHbFO}$|@tsX$!)Tf0sOp zH|Y+JJ~3|XPmfZ;=@8t?fG(nb2_RDc84m`8aQ^#vuz?0BX|sOPhNDIvor9Xv@BA+j z?axxk(+a4qg~t~Cm}tsnRgKW&D#uJej=9?sUzyqqRA+R;TA zXNqEr0(>zFbz@OLoE-$qQYO^JcH%q44o?(Y#QXm!URuP*c8O{-~_c?73sRnW#3| z8a@S}4^zH~$C*3khkrm{ElSBemS9A3mQ)qphFv*Jn?9fQ!Izu63o32LI>dL(Nn{>n&d6l zSow*+Mo)_3qL_blBdjd@68b6jdmqI8PP)4qdVGp~v4tP0bn^_s#MqO|Z6K2I{&Tig zGU6s+o@~kbx4?{R!0@Z2n?!kUsgg}-_G9w%r%GF3bzh7U6}fIvl;nQTFKO1vn1B!)aK-FODl# zfGvhCn2i+(O4v@xq(XDvvZM(|!8U6Um#``PhMAh%GDWWI{Zn6ao(ps_b}@}H@##2h ze3ruHiuAyQj4Egp+u-%T;p>uX!!kPXaUTrJFA;YfVr&_HDQeBKul4>RrJI&l&H&y6 z<1B^U4mNwyu0Q6ZbJ=BQcAzDT>{#b`O=w3ZM}norqSq|xSyLTSFM_#m_Et);@wL0_ zl4`oJE!4^6AYNGH=!Bt^YGWYk)jF(m*4Tea|L99oLxJM0D}rVaq`1oYdQ>({qiLUUYoB|9OE9-e_x?j^}QD)vSc)R-_?g(W&03 zoT7eGJr2Cs^V^^P=OcIk*yl&50p96kL{+WR~RUl2a%fJgUI-0yS%U{GG6(?5v}n1 zK{I4mv*uzy3_*Lf#*j*a)vh=(SQ_omqAc+%R8^b%OS3BXATK%uj=JUM{8wq^pZYhj$@N(Mc44i{_!I}h<`Q(-PF%_95~_CAjZ4Z({+#7CK!8npjz$< z5(h~ZMjrcVW!p6kjZYE~u){FBF zm>@h1f0n|mvY=&lj9)FWLw+ss9)>MB&(?-v^r&#d*;W{a; zcq(o}-KXN0;6;4Xr~A;W%pyaqp3Df#bG*+IebEPeJjS`%EO?1;0E(LP)$v-1eaTnK zER1>hps`!j_jLGtmcPK{!f~SHrA303sQs2Io(s^ee;0;e#TDPztiF+$)Z#t#S1KaL z!!YytQylg}IX)uO*%%l7#`BbtuC)liSy@&+5JKq)+M)%Q{)+F2<`nXtgBD!i!b@=B zV2x|A;cP`-?@`{JnOXtA^Ud`e*{I3BuP|o`%*T%|g&kb96ct=X6$h>C!T88cUDffT zwAQ0lvrxzBDmC)4a1$8q`MfV)ad9ym%v_DJ-PY%5vKmNzIg9F(o)3#+hc)pnBdA~j zH*gJbl`1QoYTI2{9pizcRa|^Q1aXyk}c3bv`iRyV0p=RcoVAS*fTKx z7Uq*SZg%;?h#3Ft(Au${3X|U}g8#7%f4lr39EYt`GDeu<@m|A7dVJ5tfb9^y+wPdq zP8rf;NaTkBxzmGB5VpPf-LUzF&7*b0NW{mZxlD`j*i847{#tRn_?JUbpVIG6AgAx> zx7Ui&x;~7Jv5~z0?GG8r7JR5&mrKqkO`)6pp7(h1J)@IB)5HsOS&{32l5TOE=Pk8+ zLV010d!!n*H+w^x6C~3~cux8&S_E5F ze=0x^_CjBrmMrR!kZq|4k*hi?QhMe$B)(H?QWUnje5Gkl()Kc0^1Q~~_nvXhz0^|O zH$lRoCReph5#zi2JZ|x^qhzdi%S*UeKH?P2o;S_iyfo}<+8+9o^6}T=l+o%CH7?HN z^b6-kQruCbdnOfTgnaZj%GjczkMC@9<;x!_kw{&GE!6(wC6^knyr*GPt&1Cfid)AV z>;0TCcn#T>YlG0Rz4@>Hz-1#LT&8u88eV@qjFSn3j)+*mx0-0v%sE0FnhDk)ba3C)@uSEk2d~fy_}!(2hopdr^$s08C8X8 z$*w-y;ZS>2V{&mgj%YArl~KlIY{W-Fu{+BX$;8*z7Gv0%U!;^MfFL3Mv%hZ*K{R6FZ+_4Uwb=XoV!jCgyz>v4nyL(<2qs(AJI^h=G-`<$nC zG=k`jc#Q9}OgX0$Oo}~$_eLEX4%_YNkUAt)XMbh}9 z_UJW~uEJil;>TSu^Y3veoUjwl+8;lW{KVyzqF{(jnBkMipfB}Bg_!!$1H|Pc>w`}% zFE<8TZV0t~cCApl@`bkP{n2doc54jO4)&Gn-g(T6`I5CJK`yIC{3?K+smJl0rxswf z8BY&IO3ij664?ss~?ox7noL#amG_E=x#p2ch7$B!OD+N#SOSyiNFz^WZ{7bxHDwupDX!_8J6`j2va+D4qdq3 zJ_9_)xvmyrX-??`Zq&ax2MZ^P(*JH=8GCAcs z4rfQWS$1=Qmd?Q^*duZKKd~wd=@4J#Ipq|QK6!;4Iu0L7(xZwl(=b#XRhC^Hjn%u2 z`C&#SvVk=-erouY*`JARa>kxoX>(O4w)=S*tvsC{`7Ecq#J3h3e^s4fN2of)$?R6Q zA9_%ltFRX-_$_!h&gAE(I)qp(8GleA<^H5Gq zIK9ik@wTL!5A_36{igQf1Lt*iPprRM9W>lxXs6_JKN7~*lp#b+6(8%bKpC&)GsBO7Qz*jRv|2wWZ3YBwW4kZBx}fe<44fzlwGuig1})!yPxtNb{*3d52jc2MN-W_ zJj9h>Wf>2_KWaf}+IIV#)@Xg)#-`g9)8XJsqdal%FX2yyMFdyjjd6?krweAZIMr|^ zbM4F1)q9uv>6LI)z#${gSCWS{O{oG;)NIy0!I(|p4|ZUT7~g%}m8DktNp9o%7yS@+ zspN)+=PLs+d8|e(Of^hBfUeezPD}pRqg$(=()Gk~RIB?8x|wGoHcWo!b6>7SSF@ay_>udmmq_K*fwp<=6mXA z(a^9Oo~JTDr(@3REsS)gZ_ebBSubvBq;V&1X@R3RsuI;*rGqv|gY z&+_{av%EUln*Ku^DrUaBTGPcC?EXiN1O>GI(d(G2vE#2W(t3tlT#5-_jiO0qlDM-z zL5xou-(&v|O7IggOG%FIkZwjO-7N^C zQyh#MW83rF=X=iY|7Yhs&pEsA`?_CO4K=5Nq!@9Z?_!(5^tr3Te;iTC^RG{`b`rh4 z1aD#HxwsQKpZjZeRQ4y+mEPH>(_tQbkGJ5#*TK@k=%!#0&rPrHv+xTTVm+{VUB8s% z47=jq;~6R3tviQvLGExX3d^wWh$+sC0`;Jyg-+wzAa)EBFC=pGcfgOmq7zBBny$lb z1ilCb-~ViIFxy+HB55aHI8rM z66}Z77XT93u1yZ$+6+xQKOGW+dUhjnHLM`;4>+J|V1K!e)y%!@@~!JF^xYBwX}-yr zA5xxwSMFLW?rbT=;WJazQ^#|5N>_vk@3p>A(ol(Zt6t<*E=xaT%yNmNgUFxUR zP}TeZ`70^o!4Q>wo=E8W8>amK;NBwv$;N-N^N~JNd!E*|A`_GnaXR(1UthL4eQ zG9D3d`G=wc)kQj|M>D2K;gzAh57?z5IL0f6h)T;ieZCl(Akt2S)zNst8p$*E175t4 zD0J7gk|b=shUe7zYG)Z)EJbJGM^&}`@OF?#n5$%o*r@DgDey4pNHoYh zaek;wn=N_f+{INa75%-#buT*KV9_z@pQkw-A0_nfkLbmrXQwNd)7ZbY+kQ4VUyW#Q z6K(G^ZplojQC)CBj7i<2(-y-oG;kMqf#r8`RbILhYkvgRfAi-VP1lFh3g#|mtq-k7 ztsUg7AdZ)}C)mEFcbiOHMXEhGrUx*^7&^TZmT!?50uJe}v&(+S@DFyI?+lC~q+{td zryZh;Od3evQ#MSb6ae5pv>Km-MOSY)Bv zZ=sN=hysbp?Y1wt?OmHy81%)5v}bcKJ^+z}V0OTA0#&7jqmGw1)phQ@`I{n~s^?WU zHbRE28va#94=nWg-u3rHP3&M`)McJo+JR5g|JcKakP=tt0#J-i^vkmFz*WLb`^yjC~2Vo?6OHqIzfntLE zW8G{$wmFYYeP>{Qa7vI5h@c0o58#iD1ZW_OKX4pr!7yZxjeNxw#6;LxA`V~%mAltK zT{b{(1m#{Qc+Thq3JyUuF`#Xy5`}-&QGzNB?gKi3u}Cl@+d>U&waJwxFW611?a4MS z8wgq*gp*EzXv~^F`FmhcpV$(;r{Up+6Qhs^H=NX&u%zv?(X;GFZ9dzSIOg+Z9+osk z<&$^HUbv3Cr+1Ak0dr0q>85Xz{#lNUc=$SWv-^54l7?GrJ_h2RODF4uR6b6hQZQxo z`8OL-_xQ#o;k6m3+1J1C?WwjZ?J?S718#{wBBE9%MxT75xrLNpWh>otk#Dq2$X@|- z-E8puLET*A7rVo_&e3*lZqxho`3v9;2Ew@q<2!%Hf`M)e_RRmlsoTe_0an5&sQ|1g zeucA0N&3#uba)?EO^#{3h?xg{Y|1O&7>Vm^G+0<=?z7 zeoirOp(eo}fW&;(CItUsAkTN3w$Sx3qov6bp&LX?}F2 zXlU7U+Cu2|15(OwG{iO6$Vb z>x?`;m2ADtWC9+~64~+>OB&f~Rvv9XV!9ysdr9LN4?0gzFb%VH2?dteX z$!Khvu?p}3kkTdDSoON6>fq{?E$QTZ_&JeThEXfVXtBHh`6UZfTVv!EIJhyOhO9Xo znQK9aJrwTBHE(sRI9LEpoPIppD*1Tz*Ob>#3w`UXbDMCYgdd~r5<&X&p1Z6lJ3RHJ zl<)8*-Ji^*j7(sz=i!j9((G&r>8buyUGFB%%PQ@SG^oKWb*aRj#>__c?*akGu2ro` zxRVuGcdMl9^JLDU8l(WO*L}S~)yftOgIj=J_V|qM*XHirR8A$}5+uld7STKcrO1{2 z`q*ADm1x5;FM=9atT7Mwab1lN|BY*w6&~yC@^7S`4ffDQ5P?=yNz*i@gXuU4=3wY!B-&&^&#>a3iZ8Ir8m(a*R z9WZZpexzf{J#L+DZKMh;ledeeo&ePt z-f*B1NyjjwKdNf?n3^l_`Xnt#F1vS6fre6vZuVz$+7>HA;6Drdwql9z`@$Af!fc|G zuWo)THod4szi_5(|A%aCK3Hf8Uj2A=ArlXdPxSh=j~C=ydF(tPdV=(+n+<>}c$S)B z#zsd|jgEX6*v!MlRNk>u=I@>r9VwlKEf{fsqe2EYC92RV19WH(xPIuX&TgjRXNqLJ;nG9ZK|CW%PsAm zAZgKbv(@#nyIf{7=j^jZUU((BEmw3cNV>N3sJPVV&$w0%B za7sU+%F#(7$r5qD`BwqkN1`@Jim^ll#Z5W<=iBwaVWAptzt;lGXW?cCxrY2fMQ`K}wUtkF7@S+(;K{yZwS*y}=fcQHEamOoPcy z1=Di0M7;d`4z5^#3)fo${oORLOmyuo>n^Oh4)CE%^rDf!HZ==beAx86_ANkn1o`eO z?C5U5Yo&Huq=;*j6~iKy{TB{}gN^YpC5;oT;t#f;Mn;)=rFOe51sJ+i6Rsc-Gpvd2 z_pC-Osd1Mk!1Psu6C^%l5A%Dv>63o;vB!D1*xyfozNARZ8piX#29j36j@9gPS&o=1 zYKTwMS)eo~52b&Szo!i7U4wrev6;9uOc7=E&9&;Q!|?mC8i2pqL<0=wYhG5eL(cgS z=?`M(pGQjjZ|Y3Yt+s-tEsgI-Mx<8AP7auZ3yGC_EZcN=^U}%2Php!?a8uMSHn0*< z-Q{pL%&nImT5ZF@Dk?t)`-tT~>kbj?Hw%99z);h{wYe=hJ@B;?O!fCg4~%YAiPu4d zUoDe4Xs%4ZHLkmZqf#bJn0#-InWdU`*D(eB+rc08S}ZU_??F$-1e0eT}7 z)Zl&DQ7igolrEpTSEkCtQN4HenFY^b?eIExxQLdnPdXf2l|B}4K$nu=N3D5@o1MxC zheK{HHD^4JkPDjCtBXX=8F}|}aEBh~ee{cd2TsiRUxvy?T|bCW@(W}a9l@fk)lO$_ zauwS&M>7~+v+lfsN4C!lE{yq%8S*Hci(dY;ZY;I09Cd1NhfcPdA)}k8vff(@MJ98y zCeL`~(`L)kubVOVhmXeE{K^26EP-l2Yx}1L_ysWtE|ijrX?z@nv+;qWApZV<$UBsh zo`z~&{e2=5)}(h-zhdVeHf`Q?oVk}8$n%7M0k6Z{+lmk@dV6eS+0sRi*L9v6c%n{q zjg?i=1=Cza6dH0HL#&cvTAX~=hiQn~ zny9>B!)FS~9WS9`ePRK~`VX0)^NAJb8%oxH#S_kN7@)7?7~M``sp5E=Q?U?q@8fLg z<`;#FzJeB+!0X(0Ju^eb2M=*U2_XjiSrm-;w$JuW>pxF^el`6IN)hg=fM2;Or^eGJ z-1}$;I#U9l4cuk$m8`@XQJ~BJCUZu*Zfia&9{pQ(PAOA#)!U~uNT;PQZ}1ySd=;4p zLXV%0^b4l}H4_zB=XlR&g85SzDd=3OR&sy0IZ9(c@Kdde4_<7>C_vc604)+HKrA26L$Tq5;X0X=7;y_Id#40;h*f@yA#}Q+uZ%A|s z&3U0t((rE(R951*lOvtlqy>o=roB~eyJHM+qrAr8R{k?Dl)(_Jro3paUS=gK1$Pk5 zJ-F+aDO+94Cmnz(PfX)LgX=8sLIX550WOGYf}aWUMC*J(9m!U-=y5sbE*Els6|EK# zyJA|^zeB|9r4fh4_@)fb7-&8H_s-iV?m1E z@TdM&6m!8d+R7dIS5Mp_@H178noand!RrS!tm50NV`=<*-BSqVAjCs_fKnkIS$rl2h8o%#^+d-hRNzpkOVMgd4;w9G+^v_@jc=8jpu%t;}wt`~*V z?)!-x8;t=t7ZhgF@n=QM7ELRI^lBEDJ=C7;Q2y;8_-GBce5q=J{_SBt_GlJ*-ka>Z zx7-*l?(8xMtOp__ZDktbNfS1Np{vi=ohZF{3~`O?8{_cHYN=wKEnm+*HKYTqMoOXi zV%8MB#0AWvimF6#7e{LYW+NC1;O`u!=Tq2uA}jr4)lB$A6ct&B>oHquBU^CsRsP%g z=kLddbwsT{pT!xG-E#>$0(V}?RlT@`kw*-JidFNZZ7j6b8HJufLoaW;c-$9)C3Kh4 zFvfH^dPg=UakB&_*9%G8^&teb^7Q-QDY55%m05r`g}a_wJl$IKMzOKs%0VJcWR1gD zPj%o9W(PhNv+uPqF?I+?D>Q$@3 zAEUCWaLD8hB(V_ws}l|1)6*uvotZxaSjse`9y=l_lZ4FJbjr2Ou+wB0@a< zGz&069zsISU2B2H+eCCNKzgQqst-8auvxgJP-k5VM%WBIMS?KC1jO_w4tfCm8ID;h zB(A2_f0Af_>!K;>H}Z!u#`He)e4NdHhn-hp?4p_vNU5=(AvnW_;GeP;+^p=Wo~S+q z*tLzFLokB>R7Lzi*o%v4#e}Eh4y2C-L~q9Fop}jo$wu6AOnm+(1(m|ZFyJUy{4rV3 z^I4(O?84!xJo&({hAxYvI)O&pesN@s{nMkwZ`M9+kAdqi67g`$c3$W^P(8&FbShj7 z!oqukZ$$zp$%=qK*XvT-l6CaL=Ov67aL6tZ17p^IdY{X1bq^G%8(Lk!$Yy4JDHlBM z`n=b4FW~Vspg!R%-;`(4&6nq7P9AgsMZ<(p`MrjJ(Q|qyS4Tv5KO_dLg5d7BYV7+h z%8$}~#QJZ@1(|aM8N=W0gZDs%-%O=y^ma~vdpa~dqqjZwLJ{9UQLvs{_&hv8-mbO> zD1bqAS2ZUC`JX#jk=gDnuPD3dtf+Hi>pe3TxyRnpiJOQeV%MJ5_^*fD%9n5s9%k}` z0D0%z&bwk%^{SR`+Ad*W9*8tC!|$Yxxj1OE75072PJvK}!!2BY?TF{= zRs%1mME|7?R-g+O+V%RRh0wbK$4$7+a>u20xz4c$hoZ-_5GLQdf+0N&ac|au^bVX& z$YEXcasZ29Ux0hgK{R39IV4G(|8`e<;*4Jo=k^Fonedz0Tlq@h!j`59a4)u3CUUPqz!(}0YQ%t#a8IJJV*gDi?Z*|9Rz;^E=_?j zL&}r7LkG`ZiO{>dQy-1K@Pm0)*A-;i?e<~s4ng$T1*KWAdTsaZ)$RKqmy%WBE|>mt zRn@PZ#juqE`Stxb8NXITr@m`q%NDs%k9O&u*%nsR`j};l7WwonO^d2)nEnR0%?o?& zZH%_L2n>YGW|l=*|MbSf?7Y9e`2QqJy*CoBH1X%YyuWY?ZN6xB1Rm*UOKaUq5@Y4%Yy~?boZBck&5CnRm9JWqDbSGw&*}UBxo5PWCL*<`CUxe{BE$7h(B25w%vS_X@1O zYFl)-X@en68O2h(^Zk7MEx*#JN>|q*TN<*X<^!7H_7`8hab@^af4fD*N;lH&?=vD1 z%-IkEV)7sP^_Rq%6K+=_8l1pO)_GIqB(J$W&Br#L`?-WWy9O3VAMaA269IIgC zfaI`n7pnTyy|5`2WJ>E&n=x-786?~#J=7JBZ#ATeJ(b{cm7bpf=zB}GvlaC#FCbku zP5Hra2u!OY0V0MjyI&n#$ZplgGmk^rmYoIr`$GaRZ@rIcb4!qyJ3!VT#0HoH>z8SX zA^d6M%v*+&=!WFHt;<;|%ZEI-vMnjdv*Oe^SooaSOKO!-)8#l_ zyGOQtl@BQ>Uwo{~jgQ|y-2*W1n7+#)7e#VbOec=nJv~M6hUggz$5kq&y)6o_g z=Xe1moWOpcd$_KgF+*X!?2$UnVXW^^WS1i5_;}2I)@**ln(B#i2v5z9?gxTGrH?04-;TZe_jgUS`uFAr{uB?CVC=M!W zqk)|VGoH5;u@pZWvxZ+BnIJcZB!RWk8=I_^l+L$}4|nTEEA9^B{q(3;wy*btNnl>9 z>lI-*<#H|C7s!X+$AuKxo#Ng#DYHK(V;^==#PHw{I>wi9|7DKefye4K<_?UVl`{mX84Q4-5@I0QM((mZAf z{jmOhq&JBLGTo)<@mG`=(w4wK>T;d8_F8sjG$>j8ZInjIC`5d|GGy<`>66MFo`;3^ zHA6_AX?!Oa6Jk&`g)MdAMkfKDxbJY%CGd}@84KKu{c<^?lZNBy{Z)4uDG1Xva{#u| zwaI+ymvIhS9TDmHRK%QOXD2!+{vC3R1=ldNMGwF+TfFxM}4 zBV6hzBrdxmr$FGRDJXwlK>!gXmV9wIw@TZ9aOzg$-r{-ApO8^xHihBKB=EmS#+ct& zC$_CJ^$;uL%W8XPGK!wg7qQ~1<+GM%A10A8>?xMRg?Y0HIWQT9G;f00aKiRjLc#Z9 zFh^Ha1KRWL5q<6jFpCo1CEf8egs{7R@D9j}V8|;i6k>^ttY5Xns$9}jt?+Y%UoK0@ z63)AsHJ+cwZUX&ygjx0n%D>A-W*_WmD`irADpBL!m+jaBhpaLr@MN`kA4`6-X?0;) z66lh;CTu42U$?q|KC_w&Fc-UB67*1@nIP_m#xQd=QXr>y+CSCS*>Y=US(CgJYyWFh z%34?(#ls~|RnhJWqjGXPzYGhrCu?WMHU3@_4{OS^=|Tlzdltp_nykhBO%N z=a5e?UjK_*Sq83Hn@VioI{LHFJ4b(?RGD9V3Fr-ic`vy8C$6(2(xn8O^cXfe)q_w$_?di96}ILyeVsC)(e)?AOT$ifMHdg0^ArFK^XgCyu&FA?sjv#rtAX_vpEg(oU7R`C zB#QOV4?@DR&_PNh)z-F%{jL7QlFLV~1mhN9DAT64PKB$GvQO>u)*ZHc!TuwsMtdhm z1@e5hr|(LC1(IWp9mmuT>*hR;ns=D`8S+zTGmj&o)!hN>6S_pqt)=+{#?$&f zK0$0HijzRV5{;``b+zo**Ve4ZX&>JebFq#>UcU4^QD2uLp4PjDmDED??(toXSr+f- z3(tkLL*|7)DO}>kjeUN12{d*veRb~a~^oWuWGR; zznJ1GAyvqhAG?^;H^D6b1`e4i#cMGLi4D|XGrz8&@~LT6-OQ983CV-PHl9Py@gS~Eac=EXt(>0wvdVx?P(!*s-EvH5Q`vsQLg z-pYC-qhEagu(*Q6m;31%;RGR13HeW|(6ysX0+Ez&>!#pEtV^)5a4pQwU$PzQ67)lQ zSABsP%DPgYThF5%%e{&D7S#I>ode}0a^OYy*mly9HtLMwY^e;k{~xV%SoVf~?WS{n z?}A=_pH5&iZPy5byC6L)`BA`qDk0c@|Ik>w&L{S*(6|Qw&0WQW2KDGAe57$yRiC(rGnKxa`er|?0SPKsx5jQUfatjXO~mQ z7Y{P`EgmEc_+}M%V1rMQ9se2D=-*cN1Tl4WE3Z^)^>JOEMW+xK|7Wu6Fi(=OWKVTt zYwXO%8(|e}N^R{IXSP-t&5PlzF)U+rUJrHexp5c#$4pUEf_-jf)C+yT={wC~f0dKD zSb7bIx{s0!yb&ReiHrd1KrD z7IU8JP91{&q=dTa^fm&GKAdF7m#Gi`d8DBfi~M?zdf^LkC4*RTo5;$`8;&z>qNz18 zDne+ys}#dZ0Wbv& zLysJLX`?RYrpQAoFqCAr(|5$=zy>wUJj>lM#;YBjsWXzJ_@wN9yjYAR zzx4FAMQf)L^K@36Zzn2bc&OTTpNbLOXfkt( zd^ZGJsFgIm)SAEl<4BbklXyOCz*|t^Hj#xWwoWEc1v$cFWrv!={r;*l+`wY9nadWUZ zMV6eWS@i3^pD9cn4`lexb5lCvr!rb=Dd!umtzb#wRTRFU7`fmjLmy~14Egi+qE-9M zJ{`~~0bQ01KQ#L~8zXbKeLSd3&2!R5g@X$RWmh4osNNLbD@=UeLcuKSNb=)wuJp)M zHM>dy^^$@9;o3I$q;~hy{ir|v1^FGMH@z4wCXsj5>3V}pCh;!@zR)TRt1c3Tqv$w+ zA>Ppy%2}Zb+n)Z$PzZPUGmq~sv&u3yPF+b`Aq9(!#SeEH&NyTe+4IiA1vV6|pemdNgse(d74#L*}9gPP-DdN;87OUiT7QLp#W+A3kWa?e|{ z%ZJBe=YgPfK43H2s0pZ^2Jp)r|vQyxkpdU-goSolI&w88WCTawHC<-*tpaEx?3MRg_s*0>+V5M)j=WC@RgO4`O7q31u7v z9g8ipR2lPn!}gxJWUr*@zIsdH#}FMz{X8Z)|S+B z&yoAo!tQfv7-Gx^VE;bz1J`33nV=8TtoLkPyy=iswsd-owtWuY#19C|v+pl>)L4F8 zelnZjW8IZqdZSoFy=FT0H?+O$Xuj`I-}h4&SG<^tP!7!6Huq9N%wE<-&ff6JuXEyL zg_3_T#<2|v{Y(?JG7a=z__qS(Lx_tjgPgVI#Sr~m z5W9oSD)dm5>Lx#n?HgWH zxv{vdPRc0bt0O)kyw=O@^$rudKKHJ0?*5PQrXf}Baz+yPHhzCy*cFUsI2;BFHR31h=v-Oq(Djx9J(tG#mFJA=7-{+wd zyRSqSXO*Slt$k1DKMv!o_K{tlKS;%x082;NKPYp2AIZ&|)F|Js$-lQ1Rzs8)v#<7= zQm)#cT<-sw6RZ*rzLupftmw8%*Q*GoPxGH-S>KkTxkK>h_zj|gXaB=$3U8s$8wqQ! z%)i3rrdDk3G}K?D(h;*NN?3DKW1g!dzVW-|9EpHYW}0jLp@-%xG{$Q8Eq<`QbnFMLc$* zKC>ffO3F4Ft9iO@&}@Y3a)_=vqWk*~lloiQ8Z4jXrY>opg5xLzMdW!=$G+t+Q#QJiFJO3{JKNNpx5PS+xRl(6(TUp zToy+!Ej{3NESakwLtVv)PGP)WFb0R3k5e@S-%=E5ZsE%i{5EC*_nxyB+V6T1>1z0# zU;VrsE?Qx8+O@tgV)BTkqScC`d4*0f%ysel@l&FgleReoM5k;l&u0q{MeMrj)c(tx z3#r^?PGr~X0)&3`&6bQ^4BsTuX8INC{~MqLHC#+jN1l`GrhoKdi)c|quMXmM>3W)l z(*$ao7}cgzQZYNxSuo@Tx_oYK5tXxM{W`l6!l?#e$LX&BTz1g46 zi~t%HMHUj5U7c4Kd?-Uz$bNF4Pr}yfilcRo)~P5TMR_^ie=|mvE-4lY@<(o`ze{5T zSzzkVNToN3-qK1RmUvcRIIu3E2y9UXCrfAAbsGW!gpR#I$KEyy{UuyzM1SkN@1HG5m2BX1*E@ z{`Hw; z=1D|)N~}?U*|w^<&_3nXVipeXJ6ZIIyMKSsP*07f&&~L2qAr2Gs4bOk1%L3Xj^sEo z!U3L%B_0#2KrHmnER4BSZ)IzF&V10Aw_~Dy$m$$K4d@oJitZvsRjh-k(nKF9BohqS z{kXG-|D)CR!su^6A%9HXgsXyrv6n}uP;l?mA!KRI*HlBVuu8#d3zO**@)I-$EX+Uy z4k5Fca}Y);;vS4C(8T*`<<3s~dW93VQFZ3Y8wtvlGzLhqeV1nvbBf;2Lc$sFKIpTx zwFZ(T41Eo+k^1ExiXi`lIUayW%WGze>54GD;q8=W{sSo|!5>d49he1UtA>5$ja9$; z2x$xsdmCA=aY77qpRK^&wqJbC3D%L!&1lFNzf(Q5Aum)&F&M}CR7vpot`yfo27osq zmmned)V=o;S^v$}OC}eR5MKDgYtJ2FjHo(;8%X!A_7)^yc=o8CHN|F&sbk8mLPV?1UkwFUR)NGe3}kI>YYZdRXUhDw9bFN06eO!8cG* zGaI`mA9Xu)IC`RXc{u|RbStg6~RJFS|f>(kj&o{ma9%rRDl!Xo;J1SM5{ZRnQ-IA=|6- zDL(#hJ3^)=A;ZegzP-*KzgIO6&(^z_G>$Ph2Ij5=fE)CVmej=$&~8J*G;|ok?5ZzB z%!QA(RJd6fJ8YM^m&ojPE_LXJ4_S4ZI~CJ~X=a)3++M?LjqP$AZ27-ZVxf7W6CAFa zeZuKNl(3PT5yk?z^g>*!C%IE17=XAl{K3Nl`Lx}z#>@hM!!8=FVEG#-)#OigChP-@ zEx$d7h#s?n5triVkY;CZ)Gi3NHwb0eQqkrpQHP9|ccoo`k|J+P!Tw-Z(Iy3q8Cq1u z;u!|fu6k@J6NAu~?4Sf;zHVJw0o*tm?*;CoROvrm5QF-S)e;9@af%BLzt3@`Ud4zy z1_W`Ce5td>0%*KXXpHPUj#rD)In|YoS;ih{*nzGsGzcBnWc4rRqO7D3% zoK2_ZC2_H)0L=^m)sU00|ZJIbg0 zOYjU%^I^0UAr#3R!p8<5P22S46F$svs>|4^TS{bk5(^0sZqDYr?ASQhs!TzU8{@sF zs{iwzVh?;syVK}cg+wfexi$|6Rp=hy7b<*P6_D#^=kkz8{k~}joU4hmr6MHcUA4rQ zVnh_4Aq)AMMruGe{kz;qqcp(sKv!G#INi)23g;#6S;`n)@%;J}7=Jk?XNqZu>OHq5 zTX0{1cJ_j^U1_eY)G2zFwobk%(7)4llwnSttz&TB*?p{?J7A!Lbjf zOcy0Qe&NC*7-E*s2kWc*iCaIm;>uU7obP)t!q=e1RSp@T?X8*UPG&v~nReObU z^**Uk#b2$1IJ1@ActQgojycz(EkdPIT#33+s7TP?w~u*WwzzZ4ACW**y$K+ABc&W+ zs;BhhH@fP9>T|zBlh6rFLHT@zCa~yl0&{@Rvn-Vh>!v<)i=AsM8wg7b`t}W z_CdrrM@lawbHZ_w@@XK>ii#XC0YpTEnF{P*Lj^2F5h|5AU$WoMfK+^X;|oX zhKcY!QFLf|nWmV5Q6w-I^ZuZir^*mqKnwro^4M-*Zi*HG8!Ngz-+c`^aCBhM=xcF0 ze~&j*BVkZ=0W}RKV>Lk6@5TCV#7eWvZoLI5j@}u%M37GLv%Gf;z8s1Vjwb^=)Azft z*Ibho5u+wip$Sbs+Po*i2<$N!`E!w@Ll$FbUV}kkcVmJjpRcxzesss^$Yam9!@U~E zs!riAWmsPDDXrf19}-UgGV5r{0WeLFoKDT+{bYB(%UU{oKYA-Q8H4qXoi!P(Ct6oZ z{YGcIE~&@%MiiR&N?S5>@Pgw?H@EY;ipAJaGxcm1W=G|krE z6yzMZ2UV!K`#mf%O9!$YaW>n8!uzP-A+f0jR+|4Cz@KF3azC&vTx%^qDOP_WOlbeF zG8`bIZFovBkpSqS*zB#fH=U81$muClpI*TxbxyN`s2wH;WrE}Ap`qWuYeBe|kG~+Q zdbuZPCi&^_tCgwEsX1wobfis(7j(}kphVQps1o`w*@ zkS)>Uu;s}QfG2ddc?KG(6hS7cbkb7ek&_>?Hg@)=I-{vd{QX-ZXTN>;x2dwCAjyqy zQ_pPo=m9}mdG(o5Lo!S?rh)=OT#yy#D3JiUECRVVM0|I0U~Q?F)lVlJV0c0eIelt> zI#rA?Ki7POqJNn8UHa96B}E+!moB5i(DMn${m9OJ#GAue%Jth;2O~~3S?%XP<1Aq{ z!u0UOnSXr+N`%ytZ%XVWipBpoK#^~@hJAnS<^KrBR_uu1n-Us4d*}qw%P0^jXlZ>J z2tRGaIAC}qiFR(pm>(SQF&$qmbi|{P$gcFuMCO#o!dI8tKA89buQry=msw661$f&x z=$#xJ!^7%(eTk3hwo4M%xxe1o&#Cv=Y94bvm<@>i zXa`Y;FhWkrXU>NSbKJCYHMW0k(#YRpJg4ZjH}4Pm`3Ln&gA=jk)px|q-k)8k_S@a* zrP_=5j2>nSC^o^o*#*$mkO=F3k-*8+iu1(IJvf|)8IxMekwcgY-sA~ z2DAjWkjelF$3*HqQVoT^`{~<#18T$x6DdSBOnwG7wHrvj1ZtuNpyEHFRQqrsWNMOr z3Qrgdb)5$7Ox>LB7o4J$C(45V=^Uvy7%Agu$%Mvwi*7DHkJ^-%obbNO*e}+eOqwcm z%6>z^HWwuCTRX|VIPf2=S|u}1K)>XPtILV_O_x`uZ!x*aDe?wk$cqQHt#5|KaP-#( zQA%4t%7=SyM-bl+$0Y*9J?kw;Tj&f){q)qS3Jm@PwA znYo?G94WyTTp*zP8%LbT1ssV3D-6w9OX{i`=~aK8BJ+n^E?W+LsvGmOWXvfmY=P`u z;2oL`okCt!wMf}>b=^{o=q8f5eK4QLeuRpSMwxCHDI2VisMbx~zf*BD%I^cVcwPh$D zy^#vX3M=0t0rch3ui#h}Bs@DXp9s5hlZf@ipIBC76pn*=~6d%E^ zR?J6@=6kGe>+ocfDT>p69yZ22wWUk(-#0BBF9u4iH5-!1C)jWbP^5A zzI;G;@9&vFg>|e6GVkIisx^UrsIX0ile!V-u%N1W#?Q2u+M2H*-n^LO6W^z|FFe&A zD_}ByK@pGI=ZEN>&4(*A(H};9#KkJRaKAnY<_bJQ<>mxG`Ay}RL1IVNI=-^L)b$?i zg=XcfwL}ZC$To=lCMsVYYY&0G>l!l)-zJ-GK7Z9|2_(pWK|c|*pS$fn3+UEb-z~JL z4D;Q^46Zq5sGTOKjNBm;fFyQQ3>|x;tR}~FXk2?)b?(FSl%?0Lgi{=C>i#FY?lsi? zAFE`z(|IH8K%{D<6EKEU6cGN{M(02u?74XWbqksx5ukfJp9X;wabH{f*d;PHS}xh| zMf%PKJ_zY~RPnD`XM)iylhz>GP+cS0AS^g*<3eKig|){WN*IRa$T!TT%jNaDL2UE% z(Y2USw5Fp1`<>kDZt{w}Sh}e<{@%+|db zEWX{1bTZ*YgM~RhzjxQ818LCIVqPDAZ{Sl)sL}dn+58Y@YkW3Er z&6)05A&&yB#QWimiZ22zK!I$LbGeF+8zsLqOnjCn7AE*sH|Ut-h^8o?X2C5p;}0Bn z_3a<8=DNdJ|Kvfi2?>@h$kEYnqvuv&0YMz&H_`9QGaWlQN7S%bflF!Bkam!y({%~1 zc}Kg%)|Cnl-c>RGDUdw0Z>4h2{w6WiO)1T6dGfXLS?%>_L^e5Duii(;b1+R1VM+lg zCV)1KX@>v`Fuz zJ{4BRXEpEum=E!THh!=zBXKZSWWqU2<}ISIw0i|dBD@Y%dOzz`{`q6OQg=b4j3fsh z`dY+U!A2n}*HcX^qxn2)ML4ptKEPpWUcJc;f zWnJ@DUT~_UdCY%RnvHanv5c5^V{9Cg02N+@cRf&um{hgT+58*8+)i?s$Suta=(lsV)>153>4_|4JFxgEH3cHn)h3%(HzQF{`@Nk z;g>|uRC_7vV9VDcR`UQPaAxHlzU}B=!^KJUkeYv&_n1*LwIVu=%%(lcOw_jf2BwA= zzyRo4h$G20qGn_mq<%XNq5&qH;hI= z8YBm!$5?##{ycuafA-j4dz^FjI_KQ4>%Ok%ZMgB`+F!f;NeaInWw0*#@Arc3826@6 zq-%z*jVu%KnBQ8`80Z=IoMie~-h?FNxGfDEYK;bfz0ufc-OoiU9HmJ*I{csN5#VY& z0l*8TasQ#>XTUNYj6KORmow;JOU87Oyx6U~R+}1uZpB7*vw*`sO>HF3&Lhi%mo;T= zwy|~3!~lOy_gy51TF;Z?H7ZkMm}l~R^0m{GcHJDxf9eQTgI@$z{zUA+JM)eio>x7S zf@;>Yqg?Ojmeu3X34O-7ngQEyzqd~hunTDSWN^d*lL6$FYG`Jr05PWe=fQ=*Bg|;5 z05hsqw1IB}|7xM96>+QtyfFwX+IdbxU(pW+VcRhMDddo`)Hf1j=*9V$2WAuKC9yHY zGv#Bf90aH=!gz`bc~Bk_Ot!DKIAQWaA)<2=nK>v)J;XCEM3I*Y^sHEK^If?svze&! zAZ=L_gt4D0Dc)2BfVF8Qpa#?BY!SWW7sU87 z)!4cs_?_jCwGAuZqH|8}#QN35e^I1E!OK!V)?-L#^g=EjsT+O(L6e6}9bv`y4 z@m{}Hg_yX6=QF;jyRDDhzTXTT30lUxGP9zQ^UK&k6PK{bOBz#uC{Og6^%1uI%@ zTFM1}frRs~C0xV!@l@PO>Xztwsn&Sp?=K%SIBVxOi}tExtJZoCT1Hb+2G>^Qr(Ak{ zI5`(>8hU*1jo3I;G9xQPA1!_RFVF7uaWGq7;RTn{JL@O%3ujMXQYqf4hvz=alpx?L zPWe#yUEAk{YARN$fpy~w-*muKU?z$z7W>zqSB!RGpJ~k7HBwzmN9FH#(pDS0p5vzH zhaEpkx@~|e4Vl&^+L`JhT++S)uBr1#;~gh6ynGMwQ}Btvp5sBM# zF|0!txc;y`xKL7{@45WIM~bL$F?izB@;oisUu|{s8c!tVK|w}wJ&|W3!`8Q7v(#|4onr$lUyDxRnMMJAHggZWat8vo+n`4uz|izky&x19fbRDbIbB+;g>{nhh+- z%NcRQx8R%KuN=v!O^de)(TB+XMzMaUq}lhg+`frl(@E?Q`mdhBS`Cjjf&B_Ln|O@m zt>fi=jN{KX#kVtLl+%=&gf%w|ka&vtAqA8&I1Z(4*w~S=$&IZJk_;)0Q1^3e@UsiP z+hZ5rm3z5uF|s`HE=wsWzDUvQB5FU(^ULLflKX9A<;~}G5f^j_b>BeeHW_6n#xF`> zqo;r+Ti>^{`R|y-RVIXzW38uwf};$T+?O>=BoAZz8TuIye~2X3)DwEVQ|v-T<;2R} z03F3)*cZb2&TwQf>a@;&iF=RdM^V(rB?w`lP%to~Fp~>mh`N3j=`}=VllsMTR+e!$ z$u+9~TST0v*_cIK*#gZ#ax>U65~S@S-X+;PzL-jdFy z-Vz2sxR0S<(oP{m?`i?==&1kx=h=)`q;a?_+b8!&&t3gkA_Fl2ub>ObTctjn6xoE$ zEZON?dNvPmaj!d&hsEtazjq(B&T2j|K(wYvqiO!B%tY{qME}~pklM;#Z*r`3>O8gC zReyiLKnj}qJlNo+N)2c+y%P3U&U8w{|H71e-T4I-S@&hl|7A|dpS#(316ppy5O`^mM_UTRnl-ktwa(2J)64US=s;MhF>F}0nhV1Ylfx(hu&h}rx>%c`H~ zPA=yKtBu6YuN-JhimKRt{s?*322Y6=X&`X_RFH^o_|Cdl7_?d-6HtAeC)5GS71txp z{2HhXxh^s36tb9VY=lxCyEFEsY_+(dZ~)6RC6WPu{$w#QInr?Wl&N7i#*F){lS}2n zX|*di*vMm5yZxU!JnirWLN$;xfu2_8l5teVPD0i$$yxI3t#?9^4)uy>YNVOYmcncF z$DnT{sM=vfDLGs;t6UPxVVVw>L6ts}lOhN@J6<_gN99+JJGQ+5#z`z9MhKg~;#S9D zR10d*!@LewLWPtoG=!+@srgdSThp3{Ac{IFl~RnxIq@g2_K_~f%_SeST4b1x*dwfn z30S+do~4J|<;`?_x@5Ampr7%Z{7%l(Q)vrtztK?Dqkw1CChZk}YQD=FV?@qlc7~4Q z4Ml5e&A^|WxS?mW9zk!?#13ajM%;5R-&gJcXcf+qdnv5A1Dt(dm5$@!d3_hW%6uc! zGFFxPW`cI+Gs@qVG^`M!E9zFG->!9*4#FHQ=^-A|go+eyavqYod6%o8r-!g~D0vc8 zXw6_1kSDNF_}=vR&-Ix9kFyl&f~uk_3F$i~@u`2hvzQM+c!3$z{fsIDwlzxL80y?$i{$es@W}%f6b!CUw1!8MiJbBwNW_8<>okAPTeXV z*1yJU2x-HckIIk)e(~W5lQ@)~2_lLe#y}AWRhv9lodGQ*fWxuP7NBQqqceyRCtT~= z3B>37!;GTYWp#?SbJOceRd(#MiyXmv$Dhn(BP;$ro@r#4=8#B9M#2@%I4W%8|KhB; z<&|%+b|adz_JCOiT4rD7@ZV*08WCmWT8Xt8m^8d=*%F-Q<(U?JT(=Oik-qu?Ou@yPfgPQ-Y|A@;z z@}^y*eBGYaP8&>5?^0cHFD!c%ncXE8_?p8d*zxv-GuM*VSlDTm@C&V{5SA2?SJSx(pU zJ(Tr{s*Q2aL%j`W#t+nXFY);bgmufg9`~^|RCUC3IkH(SY9mUy8zZ9tMaVQji&H}|@t?bWsnzZdxz+Y*w zQK>aud=#53^fhK`o#zSfRjkNMld{pQ1Dk99&&@Eb0X;R;wLG!J_xHtZaoOwZx&@2L zhB3mLb)oktVv0k|OKe1YKPl$e+Sl+ljIg;PPKg$dbN-r+PEs)qkvOKLDPUY4`$<{p zTSGY7&77F1s}XkAzJCgfmYBdw`@Dw(il3p(T?Snf;@;)4dY=DYJG^ckY?LNLox^FX z=HssBKC+GeK~lf(S;WBvP()me^pjv^xXb;~cYPGc%5dSN^Ig|kkTdRk3AoUgKR-Fi zPboMRSFcKq_cnT%Aim}7Xk7=+D^|Kg6>cfJCX?=eMt+LQ4@nPQCrOhxoij+;mlOe& z#Xdt;#u4!>)7l$K;G0%Gvt*o)MX(}lnUtRMC&9U2>4;t#eSXg+m%}-^1(8m@tQQ%7 zo;aDcNj1Ac5hf;My)%?EY^4To)iRu=GSx8DLfs;qTnFuKo^K{_+=11yg+Of!KbaYS z)XNVnMKSbc_;dd!e|%O0Iu!u8FQ-tqhtAg5dln@D13Wrw@S@E&cPp=RkWnQ$Xj$AVE&6qD_v#H?Jwyppe#PkI>t@!b|+EM69; zN|npfP`w*WpPaOpDw?PdOp6BWKn_Jm4;d1OFFOKS_h=v%fmKG7%=EyLuNm=!1)P7p(U1?Wg98+52P-2_jgKG1rE`W zb;;n$Eqi}2px+s&L zJ;f>Ty<4i#QIYF*M7_2owWv?J>xevG(vP)T#CfL`UH6vnjSA*oz%)>Dr<{$3o2AxJ zz-I@XgYyC{Q)ERJWL=)fT-$ckj}dz7w=CA>{r}X0iXy-z6LDr$uR#%Sq`go11>hQU zoeYS;b`w-9it3pjGZx9kEIhmUo9cYMWa$@rzJFe)99Z11}@8cP0L`L4TgeG~@ zY9Y8mid&g5>o^d`dXW>vaglS!RgJMo3`kqNOj39Y8C;ZQsQv#efQk^0CJ}N|6!Y2nPKK8*d9m@ijL|y_RCs_ zqLdS~iVB-%zN~ZFKMjwcD~>$ZqeD6TDy@NTu^sZh`xbiOSWFRbxocK`%y9INQLY|L(q0IM>gs&_d%%JfnjWpi2#JBlq*`4;aJ z07FQtv4b?ixaMAkQ+MYWsKV|^m;ABV%9b5mUdB6Kzc{aDWn(J+9M|rb&etF7#Xk06 zNa^4iScL3yFe8t5C6LMQ8Q?7vHRoUtO!y#$j$6aLOxon?Hkv@#oruU0JFc~vBADYe zz6U);ri%Tc&7Pe0b2N1-|Zr*6!7Pvq}`7?eAR7 z)Y)b5!5SCbt{!}U%%N*b%c(7lpu!?tzJGG7-9uNhpk%z!>fZ@p6-O~Kf?Ce?CJ^A5 zp_NKQ*XOVM&{-2AQ!7%K^CpOC2ObOb|e8ial6!*VPa_FYE6`9*eU-TqcA&gX*vp znFr1_KOc`^{5DV2mpTrpjm1g9kLxpLQq|8QU+fMiYyMjD^~=waPYYE??fH2I_iC>@ zA5#69j#U~tnvPtLXo&CAYzr&Z`TfY&dZM@Ey_KeFP~er5-27=ld!#>c%a_+kkBoxt zi0EiWsM;;Pazcraxh$g`F*b(f8u&Nl8N*S-ZMnLc@01Pq!Q9woCUA|g*>K`BZIO~# z*fha8JBhE{nSV8MO8WuyYL0~`?Fm|ha&}L4ehemCkxz(xNTEDiN z$INNRDO*BvF}ZUFDVNqCB#Q*Bb36o%_a*)?|0Sl=IIk5X2J`sjy@+d>$`#MVd%uwIBkAt6bB*k~r+u;8&e5N8nV)p0*`D zrOrpdDMRQ?cV~$e)8HntTzLuFm5Z4Ty4+}2yO6&JP9<}JXIKt)7+aRM`K{T5q7wf#fV zNu+m6E?k;9(JY=wNOa$>9Lvr#Vv);mb&8%UQMgs{fI_D0?R7N2Ys;#wUWBJk6fljT zVhb2hBRoES&q4H$x_PJYJl9W;KKoM=?QAIrdoQ zpM^?*@*ZXdRtj>)9G9{Yk3G2!*(Ey6_#xA;*m(0`hJkAXN^6^X(wF~~CmttzurYm% z`cdWExJA3XpcNtOY+=-kO|~P~(t1&kf1WM`F9STSRO_F0J3QN=%lt7Ueyiztlm6}G zc%kx%0x6>??hD*U8EJqR^pJU%u?i*(Fw+QK8ELEcYg_?FQeA(jfuF^He@Y4b%)Zm;yExY4 zXLtc!4>(PIgs=cAxlGYW9hgbXmK-SVTh=#Wce_>Q*?u#$2h4Eo2p7Gtrqb2px{iuGEuPh=! z-A|gy5QW|Ac;wl9c~LGy%y#U7X8dE=;=+a57Q9hD8q;QPR(|DL-UQ^g`_z1;kHyV2 z_QiDlt7;{NivN)iF_Vv+J9>50$}gr_{EjhVIpA4SxfPEY~jL|RG9F|{e_;=pta zt6K85;r`&hmcqrKl^X7+z9tY3xeN#uD|sep;NROqPQ_ZMj4`_O^BioD2S1XY5*syP zmo6~wlDQ#{(-`Ry9$Y&}O?oZ5`Bc@J9zHJzTv5c}gf-Pna9r*Z0cndu*&VCgSL7}a zgU{8276AtEBKa3@QW-sw@hguR9B0=*@}(Ap+tN2{SNeaT8#%PIg)~{c5mHI7?j!y7 z%)0%61azT^hgNr(9LR)HaT^nIfRM@xG{`9@T(I9Ir4SIR$?n3=$aySj2zd>ZLcr$Q z^4hD;XW1k~Z7l5zt|Mu_yL8!03c1AYrY%~rt9#DTB=AqI2FdPkmfY}el<+&M>;Ywd z#4=(QB7oSA+7TjZ{wM9iM_{gHaLkqK?7%)%w)oyiIRE2GE-atk^=_wX$5>4In3wT{ z^P6g{n!V9eY;*;@L`ZG|v$K43Z!7GKWJZUDsHH#W{J;B6Kp74-$dAy zPSG`k))Z_k!gKmgS+G!73^Q!MzG&!0Zu7bjIyKC6s{Z2{fBsj22N(GVsr+_RKR^1k z5q9)EK!1c%QF2*=f4!rbOl-8pdMEwH5{oTNb}yOU|0#t)lRL8*mJ_F?dUsCTW;y0g zxtoBzcM>~nDUf{#j^1Tki6$<6)rqz;jOgJjh~8}eO#zmsF_|^aPp8i;I%_+OS*QZ~ zUmetldNnmCU3CT|{9FG#_#M20$PfDKEc)!t>bhR|EbrvX-~QXU>ni18d+&t zJ|w%RF3wr^L#T(>P&+m!_QK@E3GOyl6zJr7O3{efd=GVA!+Nm!Y13uWlk_U)+(C@2 z@z(`Aw4e-_)=8Zg+T+W_!xFwj0!zf_J zt_w=`JzB~-zM<)?0xWf3>yR9ib3SeW=J2u}Bc7Rt8$}DavITwnhgh3swf@i7nGMA@ z9sr{-mwTHM;;HZ8(1Q#t)m??|{R%}9hSx7R>Bp0w|0Q~0~6HVIRzjjO5pomVCnnj=2720~;q1}^3sn@rrkd?IvwS4U9lFJLuo>-f=Nu+)GR4TRAil#z-#KAN4Bu&g_mu^Ui{~{sbg@_jl zqAuo+%HQ=vA9E`gIb?KGyH~#gm3-w~%5?vC4*xtbSJBL$hxL}b;OJvR{j?e|51jkfgijNGgF^w_F_ zvTo~o^2Tiw@muMA`4H~%GHz4&Tzo-J)P3BP;;TZB{u5OJwj>Wv&w-tdZ5THPN6H&Z zdHXm1y1KgTlSN(IHe#o5WbPeuq0yvI5Jd-`a%xVi)5|IIWAU}vrY4A=VVxp z)^2YaE&svT#MWVnO(Rpe9cBN<4F&QRx2l@27!AY0Rb!8dxNdGnpziif#iy^Xm zRdH>7um5IVCVv+rlN(|vq~ST;`3MTaD|gpa{QA3KN;Y^?Dy~fAt<@mD+_&w~4Yjeb zbWX!e3)|EC%f7=DzLoM%2Z`>kT|?YM8nmp~7F4O)x1n0Xuhd>Qr)zq9s;x@tVn+06 zO?PX$A%>2~IO5${WIo0;0279(vyia~2kAjcV$;QoM}DRhWUllr=T%#$%JlZ$<;b?N zATkKR+GvIL`!;gTiSE)AiPVo}jk5#N`9b%$%b9DNi5A%&jO|>eZO^&Nx@DvCliqyS zKBG1#p-Z>)1Hl!p&Njf{9ETz5a-Q@ahU$SL*~ied^9X*v5@=-wu@yEVmsI(V zY1j(w#T#62^CXk=5+*hAafXq1&la*e_8D|uW^-;^4*?r(Z7x3KSx6WdjQaj98m=(I zE$0_|<_nr()%WDPXm4f|!XCMGmtw@)9{g)WAP5&}mk{ANA4rW3i z{7v(D*X$KI=m~mo1So!~qU1zE*O>=@FTYJn;?d zHL1MkAzRpMmt`apkktHME#yWhKUOaZBf{7+Ew&lI2&3iXv4-IY<@;-Q7fs&POf3CK z^m=+_*=-NOv~BA4N8hg4rlgH!*=MK(w_GD)LtM-6o4GA&J#?Lj%i?(d z{Nt_+2l$~|C*EZlL7YIAQ(+jTVO620os6ici;X-^{jD}s}=bsh3oRSa8Jw%D;%N3=zh~H3jQ-y7}Lr=JU7e__k>6)PB2vn3r_2 z`uFVSbJeK%Bn_Xn2lAm)WXks7?_{6iuHX4ndDTr`_@DfyhNRFcYrr(N{vL8sldWk* zNxKsDo^o)exz{Xxma{!AMRExcdnOpjdZzE4m$nb+tygQy;;dnHg6vUxPe?tdG;Ns6 zD51WPx%9V8kK~lyw@5P!5hWI!6eZj+wW09AXDtyvRz08I)6_K^sW|CX8qEd*ZWn{j zP=;%>wZJt_pK%>6EJgCV;{!;8N8pWBq5K&D2kGdus{T z1zhp2kj!Gdhk)Dh=DWb=%cg{Dl-h4ujV|x&4Z_J{j7@c2i#y>U@2YR8@@ zTHW;?kJ$MTLTY~U>lM zlCOxVOgYB`QEjADx6nYS=|T1=XIQ>TM8!=!e&6NBmwT`R+2GH%X8ksas(N|E{D