From 2026f6bac81a1eda76ec12b97131d991b107b351 Mon Sep 17 00:00:00 2001 From: hyorigo <67533928+hyorigo@users.noreply.github.com> Date: Wed, 11 Nov 2026 23:50:23 +0800 Subject: [PATCH] feat: remove Kiro integration --- 1bite | 85 +----------- AGENTS.md | 2 +- README.md | 10 +- VERSION | 2 +- config/casks.tsv | 2 - config/kiro-permissions.json | 8 -- config/repository-files.txt | 7 +- config/sources.tsv | 6 - docs/automation.md | 26 ++-- docs/development.md | 2 +- docs/installation.md | 10 +- docs/maintenance.md | 5 +- docs/shell-editor.md | 1 - docs/sources.md | 8 +- scripts/battery.sh | 2 +- scripts/check-updates.py | 5 +- scripts/configure.py | 49 +------ scripts/desktop.py | 1 - scripts/inventory.py | 8 +- scripts/kiro-shell.py | 162 ---------------------- scripts/manual.py | 10 +- scripts/official_ai.py | 42 +----- scripts/verify.sh | 11 -- tests/fixtures/config-golden.json | 7 +- tests/test_kiro_shell.py | 219 ------------------------------ tests/test_maintenance.py | 6 +- tests/test_official_ai.py | 44 +----- tests/test_setup.py | 123 +---------------- 28 files changed, 58 insertions(+), 805 deletions(-) delete mode 100644 config/kiro-permissions.json delete mode 100644 scripts/kiro-shell.py delete mode 100644 tests/test_kiro_shell.py diff --git a/1bite b/1bite index 107d9c1..5b79828 100755 --- a/1bite +++ b/1bite @@ -44,7 +44,7 @@ Powerlevel10k is installed after Oh My Zsh; existing explicit themes are preserv --battery Run battery diagnostics; supports --summary/--json/--tsv/--shell/--field. --install-battery Install ~/.local/bin/1bite-battery; add --update to replace an unchanged managed copy. --update Update managed tools and refresh desktop DMGs (install mode only). ---managed-desktop Automatically place desktop apps and configure Kiro hooks (explicit opt-in). +--managed-desktop Automatically place selected desktop apps (explicit opt-in). --check-updates Report available versions and configuration drift; never upgrade. --with-docker Include Docker Desktop (disabled by default; review its license terms). --with-claude Include Claude Desktop and Claude Code CLI (disabled by default). @@ -368,7 +368,6 @@ bundle_healthy() { } bundle_identifier() { /usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$1/Contents/Info.plist" 2>/dev/null; } -bundle_version() { /usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$1/Contents/Info.plist" 2>/dev/null; } input_method_path() { local app=$1 identifier=$2 candidate @@ -383,41 +382,8 @@ input_method_path() { input_method_healthy() { input_method_path "$1" "$2" >/dev/null; } -kiro_bundle_healthy() { - local path=$1 version - bundle_healthy "$path" || return 1 - [[ $(bundle_identifier "$path") == dev.kiro.desktop ]] || return 1 - version=$(bundle_version "$path") || return 1 - [[ "$version" =~ ^[1-9][0-9]*\. ]] || return 1 -} - -kiro_cli_bundle_healthy() { - local directory=$1 executable - bundle_healthy "$directory" || return 1 - [[ $(bundle_identifier "$directory") == com.amazon.codewhisperer ]] || return 1 - for executable in kiro-cli kiro-cli-chat kiro-cli-term; do - "$directory/Contents/MacOS/$executable" --version >/dev/null 2>&1 || return 1 - done -} - app_path() { local app=$1 candidate - if [[ "$app" == Kiro.app ]]; then - for candidate in /Applications/Kiro.app "$HOME/Applications/Kiro.app"; do - if kiro_bundle_healthy "$candidate"; then - printf '%s\n' "$candidate" - return - fi - done - fi - if [[ "$app" == 'Kiro CLI.app' ]]; then - for candidate in '/Applications/Kiro CLI.app' "$HOME/Applications/Kiro CLI.app"; do - if kiro_cli_bundle_healthy "$candidate"; then - printf '%s\n' "$candidate" - return - fi - done - fi if [[ "$app" == Claude.app ]]; then for candidate in /Applications/Claude.app "$HOME/Applications/Claude.app"; do if bundle_healthy "$candidate" && [[ $(bundle_identifier "$candidate") == com.anthropic.claudefordesktop ]]; then @@ -433,7 +399,7 @@ app_path() { return fi done - elif [[ "$app" == 'Google Chrome.app' || "$app" == 'Visual Studio Code.app' || "$app" == 'Obsidian.app' || "$app" == Kiro.app || "$app" == 'Kiro CLI.app' ]] && ! bundle_healthy "/Applications/$app" && bundle_healthy "$HOME/Applications/$app"; then + elif [[ "$app" == 'Google Chrome.app' || "$app" == 'Visual Studio Code.app' || "$app" == 'Obsidian.app' ]] && ! bundle_healthy "/Applications/$app" && bundle_healthy "$HOME/Applications/$app"; then printf '%s\n' "$HOME/Applications/$app" return fi @@ -449,12 +415,6 @@ app_healthy() { if [[ "$app" == Claude.app ]]; then [[ $(bundle_identifier "$(app_path "$app")") == com.anthropic.claudefordesktop ]] || return 1 fi - if [[ "$app" == Kiro.app ]]; then - kiro_bundle_healthy "$(app_path "$app")" || return 1 - fi - if [[ "$app" == 'Kiro CLI.app' ]]; then - kiro_cli_bundle_healthy "$(app_path "$app")" || return 1 - fi if [[ "$app" == Docker.app ]]; then /Applications/Docker.app/Contents/Resources/bin/docker --version >/dev/null 2>&1 || return 1 /Applications/Docker.app/Contents/Resources/cli-plugins/docker-compose version >/dev/null 2>&1 || return 1 @@ -463,7 +423,7 @@ app_healthy() { } manual_desktop() { - case "$1" in docker-desktop | google-chrome | chatgpt | kiro | claude-desktop) return 0 ;; esac + case "$1" in docker-desktop | google-chrome | chatgpt | claude-desktop) return 0 ;; esac return 1 } @@ -481,36 +441,15 @@ prepare_desktop() { MANUAL_STEPS="$MANUAL_STEPS $package" } -ensure_vendor_kiro() { - local installer - if app_healthy 'Kiro CLI.app'; then - echo 'Preserving Kiro CLI; its official app/CLI handles updates and onboarding.' - STEP_ACTION=preserved - return - fi - # Keep the vendor's own replacement prompts, launch and integration behavior. - installer=$(mktemp) - download_installer kiro-cli "$installer" - run_package_installer /bin/bash "$installer" - rm -f "$installer" - hash -r - app_healthy 'Kiro CLI.app' || fail 'Kiro official installer did not produce a runnable application.' - STEP_ACTION=installed -} - ensure_cask() { local package=$1 app=$2 if [[ "$DESKTOP_MODE" == download ]] && manual_desktop "$package"; then prepare_desktop "$package" "$app" return fi - if [[ "$package" == kiro-cli && "$DESKTOP_MODE" == download ]]; then - ensure_vendor_kiro - return - fi # Resolve AI installations directly from their official vendor sources. case "$package" in - chatgpt | kiro | kiro-cli | claude-desktop) + chatgpt | claude-desktop) ensure_official_app "$package" "$app" return ;; @@ -603,17 +542,6 @@ check_agent() { agent_healthy "$name" || fail "Official installation did not produce a runnable $name." } -ensure_kiro_shell() { - if [[ "$DESKTOP_MODE" == download ]]; then - echo 'Kiro shell integration is handled by its official app. Complete onboarding there.' - STEP_ACTION=preserved - if ! agent_healthy kiro-cli; then MANUAL_STEPS="$MANUAL_STEPS kiro-cli-onboarding"; fi - return - fi - STEP_ACTION=$(python3 "$ROOT/scripts/kiro-shell.py" --app "$(app_path 'Kiro CLI.app')") - echo 'Kiro Zsh hooks ready. First launch and system permissions remain manual; see README.' -} - apply_configuration() { python3 "$ROOT/scripts/configure.py" --config-dir "$CONFIG_DIR"; } verify_installation() { export DESKTOP_MODE @@ -671,7 +599,7 @@ execute_mode() { step_run verification verify_installation ;; install) - STEP_TOTAL=$((12 + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/formulae.txt") + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/casks.tsv") + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/font-casks.tsv") + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/vscode-extensions.txt"))) + STEP_TOTAL=$((11 + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/formulae.txt") + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/casks.tsv") + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/font-casks.tsv") + $(awk 'NF { n++ } END { print n+0 }' "$ROOT/config/vscode-extensions.txt"))) if [[ "$WITH_DOCKER" != true ]]; then STEP_TOTAL=$((STEP_TOTAL - 1)); fi if [[ "$WITH_CLAUDE" != true ]]; then STEP_TOTAL=$((STEP_TOTAL - 2)); fi if [[ "$WITH_SOGOU" == true ]]; then STEP_TOTAL=$((STEP_TOTAL + 1)); fi @@ -690,7 +618,6 @@ execute_mode() { step_run configuration apply_configuration step_run ohmyzsh ensure_ohmyzsh step_run powerlevel10k ensure_powerlevel10k - step_run kiro-shell ensure_kiro_shell install_extensions if [[ "$WITH_CLAUDE" == true ]]; then step_run claude check_agent claude; fi step_run codex check_agent codex @@ -772,7 +699,7 @@ main() { echo "Final step: prepare the official Doubao Input Method ZIP in $HOME/Downloads/1bite; install and enable it manually." echo 'Claude Desktop and Claude Code CLI require --with-claude, including verification and updates.' echo 'Docker Desktop requires --with-docker, including installation, verification and updates; review its current license terms first.' - echo '--managed-desktop opts into automatic desktop placement and Kiro shell integration.' + echo '--managed-desktop opts into automatic placement of selected desktop applications.' echo '--update refreshes recorded official installations; existing unmanaged copies are preserved.' echo "Logs: $LOG_ROOT" if [[ "$WITH_SOGOU" == true ]]; then diff --git a/AGENTS.md b/AGENTS.md index 0206bed..7a3a047 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ Read `README.md` and `docs/automation.md` first. This directory is an independen - Print the Zsh ownership boundary and restart command after configuration. A non-empty entry gets an explicit review notice; an unchanged rerun reports no change. Unknown loader syntax and dotfile-manager symlinks stop before any configuration write and give the canonical loader for manual merging; never recommend replacing a complete entry file. - Install Obsidian through its official cask. Keep the reviewed starter under `~/.config/1bite/obsidian-vault`, create only new vault directories through `obn`, use `ob` only to open Obsidian or an existing vault, and never read or update created vaults. Bundle only licensed theme assets and portable vault settings; exclude notes, workspace state, identity, account data, and community plugin data. - Recognize only the documented One Bite loader variants. Canonicalize them in place, preserve all surrounding content, and stop before writes when a managed-path loader has unknown syntax. -- Default desktop policy prepares official DMGs for manual installation; iTerm2/VS Code remain casks. AI CLIs use official scripts; default Kiro keeps vendor onboarding/integration. --managed-desktop explicitly enables automatic app placement and Kiro shell management. Never label prepared downloads as installed apps. Preserve healthy unmanaged copies and personal configuration; test both policies on disposable machines. +- Default desktop policy prepares official DMGs for manual installation; iTerm2/VS Code remain casks. AI CLIs use official scripts. Kiro is outside the managed software set because its current release no longer provides the CLI used for terminal hooks; do not install, configure, update, inventory, or verify it. Preserve existing Kiro applications and personal settings. --managed-desktop explicitly enables automatic app placement. Never label prepared downloads as installed apps. Preserve healthy unmanaged copies and personal configuration; test both policies on disposable machines. - Docker Desktop is disabled by default. Only explicit `--with-docker` selects its installation, verification, inventory, maintenance, network probe, or manual steps; `--managed-desktop` never implies selection. Expose the Docker application PATH, aliases, helper, and Oh My Zsh client plugin only when a real `docker` client is available. Keep `DOCKER_DEFAULT_PLATFORM` engine-neutral and globally available for another local engine or a remote context. - Install the managed Python runtime first, then check Doubao and optional Sogou independently by their expected Input Methods bundle, executable, and bundle identifier. Preserve each healthy installation; otherwise prepare only that input method's checksum-verified official ZIP before remaining package/configuration/onboarding/verification stages. One installed app/package must never suppress another selected app's download. A later failure must retain current receipts and print exact package/manual steps. Installation, logout/login, input-source activation, microphone access, and typing/voice checks remain manual; receipts never claim the input method is installed or enabled. - Keep the One Bite iTerm2 Dynamic Profile on its release-stable unique GUID and the declared MesloLGS Nerd Font PostScript name. Replace only the managed profile, preserve all unrelated profiles, and tell users to open a new One Bite profile window before running the Powerlevel10k wizard. diff --git a/README.md b/README.md index 1667a54..7356306 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,14 @@ Run the installer as your normal administrator account, without `sudo`. If `gh` | Notes | Installs Obsidian and a reusable vault starter with Tokyo Night, the `wide` CSS snippet, portable vault settings, and empty attachment/template folders. | | Prompt and fonts | Installs Oh My Zsh, Powerlevel10k, MesloLGS Nerd Font, and JetBrains Mono Nerd Font while preserving personal themes and configuration. | | Input method | Prepares the checksum-verified official Doubao Input Method ZIP for manual installation, microphone permission, and activation. Sogou remains opt-in. | -| AI command-line tools | Installs Codex CLI and Kiro CLI from their official installers. Claude Code is opt-in. | -| Desktop applications | Downloads official DMGs for Chrome, ChatGPT, and Kiro IDE when the apps are missing. Docker Desktop is opt-in. | +| AI command-line tools | Installs Codex CLI from its official installer. Claude Code is opt-in. | +| Desktop applications | Downloads official DMGs for Chrome and ChatGPT when the apps are missing. Docker Desktop is opt-in. | | Optional Docker apps | Provides separate, opt-in launchers for useful local services. These apps never start as part of the main installer. | | Battery diagnostics | Shows a best-effort charge, power, maximum-capacity, and cycle summary beside the initial macOS check. The independent dashboard also provides JSON, TSV, Shell, and single-field output. Probe failure never changes installation success. | -Docker Desktop is disabled by default because its terms may not fit every workplace. Review the current vendor terms, then add `--with-docker` whenever you install, update, or verify it. Claude Desktop and Claude Code are also disabled by default and use `--with-claude`. +Docker Desktop is disabled by default because its terms may not fit every workplace. Review the current vendor terms, then add `--with-docker` whenever you install, update, or verify it. Claude Desktop and Claude Code are disabled by default and use `--with-claude`. + +One Bite no longer installs, updates, configures, or verifies Kiro. The current Kiro release no longer provides the CLI that One Bite used to add terminal hooks and operation prompts, so that integration no longer serves its intended purpose. Existing Kiro applications and personal settings are left untouched. ## Finish downloaded application and input-method installations @@ -45,7 +47,7 @@ At the end of a run, the installer prints a numbered guide for every package pre 2. Drag the `.app` into **Applications** and wait for the copy to finish. 3. Eject the mounted installer disk. 4. Start the app from **Applications**, sign in, and approve required macOS permissions. -5. If Docker Desktop was selected, wait for the engine to start. Complete Kiro IDE and Kiro CLI onboarding separately. +5. If Docker Desktop was selected, wait for the engine to start. The same guide is saved as `manual-steps.txt` in the run directory printed by the installer. After all manual work is complete, run the exact `--verify` command shown in that guide. A Docker-selected run also prints a separate `--docker-smoke` command for use after the engine is running. diff --git a/VERSION b/VERSION index d169b2f..c5d54ec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.8 +0.0.9 diff --git a/config/casks.tsv b/config/casks.tsv index 7e42f6b..aab49c9 100644 --- a/config/casks.tsv +++ b/config/casks.tsv @@ -4,6 +4,4 @@ visual-studio-code Visual Studio Code.app obsidian Obsidian.app google-chrome Google Chrome.app chatgpt ChatGPT.app -kiro Kiro.app -kiro-cli Kiro CLI.app claude-desktop Claude.app diff --git a/config/kiro-permissions.json b/config/kiro-permissions.json deleted file mode 100644 index 13aa95c..0000000 --- a/config/kiro-permissions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "rules": [ - { - "capability": "all", - "effect": "ask" - } - ] -} diff --git a/config/repository-files.txt b/config/repository-files.txt index 631a55c..75d21d7 100644 --- a/config/repository-files.txt +++ b/config/repository-files.txt @@ -1,6 +1,6 @@ -.gitignore .github/pull_request_template.md .github/workflows/quality.yml +.gitignore 1bite AGENTS.md LICENSE @@ -24,7 +24,6 @@ config/formulae.txt config/git-defaults.json config/gitignore-global config/iterm2-profile.json -config/kiro-permissions.json config/obsidian-vault/.gitignore config/obsidian-vault/.obsidian/app.json config/obsidian-vault/.obsidian/appearance.json @@ -73,7 +72,6 @@ scripts/desktop.py scripts/docker-smoke.sh scripts/editor.sh scripts/inventory.py -scripts/kiro-shell.py scripts/manual.py scripts/ob.py scripts/official_ai.py @@ -83,8 +81,8 @@ scripts/prepare-sogou.py scripts/quality.sh scripts/runtime-smoke.py scripts/session.sh -scripts/welcome.sh scripts/verify.sh +scripts/welcome.sh tests/fixtures/battery/pmset-log-batt-uppercase.txt tests/fixtures/battery/pmset-log-batt.txt tests/fixtures/config-golden.json @@ -93,7 +91,6 @@ tests/test_desktop.py tests/test_doubao.py tests/test_editor.py tests/test_interruptions.py -tests/test_kiro_shell.py tests/test_maintenance.py tests/test_obsidian.py tests/test_official_ai.py diff --git a/config/sources.tsv b/config/sources.tsv index 9546516..8016f0c 100644 --- a/config/sources.tsv +++ b/config/sources.tsv @@ -1,16 +1,10 @@ homebrew installer https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh claude installer https://claude.ai/install.sh codex installer https://chatgpt.com/codex/install.sh -kiro-cli installer https://cli.kiro.dev/install chatgpt documentation https://learn.chatgpt.com/docs/app -kiro documentation https://kiro.dev/downloads/ chatgpt-download distribution https://persistent.oaistatic.com/codex-app-prod/Codex.dmg chatgpt-distribution distribution https://persistent.oaistatic.com/codex-app-prod/ chatgpt-releases metadata https://persistent.oaistatic.com/codex-app-prod/appcast.xml -kiro-distribution distribution https://prod.download.desktop.kiro.dev/ -kiro-releases metadata https://prod.download.desktop.kiro.dev/stable/metadata-dmg-darwin-arm64-stable.json -kiro-cli-distribution distribution https://prod.download.cli.kiro.dev/stable/ -kiro-cli-releases metadata https://prod.download.cli.kiro.dev/stable/latest/manifest.json claude-releases metadata https://downloads.claude.ai/claude-code-releases/latest codex-releases metadata https://releases.openai.com/codex/channels/latest ohmyzsh installer https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh diff --git a/docs/automation.md b/docs/automation.md index dd6df3e..78302c5 100644 --- a/docs/automation.md +++ b/docs/automation.md @@ -11,7 +11,7 @@ Run Bash 3.2-compatible `./1bite`. Source checkout: `https://github.com/1set/1bi | `--battery [OUTPUT]` | Reads allowlisted macOS battery fields; optionally writes only sanitized private history | Produces the requested dashboard, one-line summary, JSON v1, TSV, Shell, or single-field report without entering an install session | | `--install-battery [--update]` | Explicitly installs or updates the separately owned `1bite-battery` command | Missing target is installed; current managed bytes are skipped; replacement requires an unchanged receipt plus explicit update | | no mode | Install development tools/AI CLIs; prepare missing desktop DMGs and the Doubao Input Method ZIP; apply templates | Automated tools and prepared packages verify; manual_steps lists remaining user work | -| `--managed-desktop` | Also place desktop apps and configure managed Kiro Zsh integration | All declared packages/runtime probes pass; GUI/login remain manual | +| `--managed-desktop` | Also place selected desktop apps | All declared packages/runtime probes pass; GUI/login remain manual | | `--with-docker` | Include Docker Desktop in the selected install, update, verification, inventory, and maintenance scope | Same mode-specific requirements, now including Docker Desktop; engine execution remains a separate smoke check | | `--with-claude` | Include Claude Desktop, Claude Code CLI and its initial config in the selected mode | Same health/preparation requirements, now including Claude | | `--with-sogou` | Also prepare the official Sogou ZIP for manual installation | Normal verification plus a checksum-verified local installer package | @@ -26,13 +26,15 @@ Run Bash 3.2-compatible `./1bite`. Source checkout: `https://github.com/1set/1bi Explicit battery dispatch occurs before the normal UI, session directory, fd 9 lock, environment report, inventory, and result handling. A normal install also runs the cache-free, history-free battery summary after its architecture, macOS-version, and user checks. That read-only subprocess closes fd 9. Missing hardware prints `Battery: not present.` and succeeds; partial fields are labeled unavailable in place; command failure or empty output degrades to `Battery: unavailable; setup will continue.` None of these cases can fail the target step or stop later installation work. The summary is display output only: it is not a separate step and does not enter `result.json`. Neither the full diagnostic nor its optional command installation participates in `--verify`, inventory, maintenance, or installation success. The diagnostic uses system Bash 3.2 and built-in macOS commands, filters raw output immediately to an exact field allowlist, and writes only sanitized timestamps, power source, numeric percentages, and derived intervals to its mode-0600 cache. Cache directories are mode 0700; replacements are same-directory atomic renames, and symbolic links are never followed. Unknown files are preserved. See [battery.md](battery.md) and the [JSON schema](battery.schema.json). -Default downloadable desktops are Chrome, ChatGPT and Kiro IDE. Docker Desktop is excluded unless `--with-docker` is explicitly supplied; Claude Desktop and Claude Code CLI use `--with-claude`. `--managed-desktop` alone does not select either optional group. Unselected software is not downloaded, installed, read, repaired, updated, inventoried, or required for verification. Pass the same flag again for reruns, `--verify`, `--configure-only`, and `--check-updates`. Each selected app is inspected independently: a healthy copy of one app suppresses only its own download and never another app's. Missing selected apps get their official DMGs in `~/Downloads/1bite` and a final manual-install reminder. The default install applies the same independent check to Doubao Input Method before preparing its official ZIP; Sogou remains selected only by `--with-sogou` and has a separate check. iTerm2/VS Code remain qualified official casks. Default Kiro CLI invokes its official installer and leaves onboarding/integration to the vendor. See installation.md for the per-app policy. +Default downloadable desktops are Chrome and ChatGPT. Docker Desktop is excluded unless `--with-docker` is explicitly supplied; Claude Desktop and Claude Code CLI use `--with-claude`. `--managed-desktop` alone does not select either optional group. Unselected software is not downloaded, installed, read, repaired, updated, inventoried, or required for verification. Pass the same flag again for reruns, `--verify`, `--configure-only`, and `--check-updates`. Each selected app is inspected independently: a healthy copy of one app suppresses only its own download and never another app's. Missing selected apps get their official DMGs in `~/Downloads/1bite` and a final manual-install reminder. The default install applies the same independent check to Doubao Input Method before preparing its official ZIP; Sogou remains selected only by `--with-sogou` and has a separate check. iTerm2/VS Code remain qualified official casks. See installation.md for the per-app policy. -The default stage count is derived from the current package and extension manifests plus the pinned Neofetch and default Doubao check/preparation stage; `--with-docker` adds one stage, `--with-claude` adds two stages, and `--with-sogou` adds one check/preparation stage. Install mode prepares Python first, then independently preserves an installed Doubao or prepares its ZIP; selected Sogou follows the same separate rule before the remaining package, configuration, onboarding, and final-verification stages. A later failure therefore retains any current-session receipts and prints actionable manual steps. Prepared packages are not installed apps or enabled input sources. `result.json` includes `desktop_mode`, `with_docker`, `with_claude`, and `manual_steps`. During default install only, verification may accept a freshly validated prepared DMG for an absent selected desktop; inventory records `null` plus `pending_applications`. Standalone `--verify` remains strict and fails for absent selected apps. `--managed-desktop --verify` additionally validates managed Kiro hooks. Doubao and optional Sogou always remain manual installations in either desktop policy. +Kiro is no longer a managed component. Its current release no longer provides the CLI used for One Bite's terminal hooks and operation prompts. Installation, updates, configuration, inventory, maintenance, and verification ignore Kiro; existing applications and settings are not changed. + +The default stage count is derived from the current package and extension manifests plus the pinned Neofetch and default Doubao check/preparation stage; `--with-docker` adds one stage, `--with-claude` adds two stages, and `--with-sogou` adds one check/preparation stage. Install mode prepares Python first, then independently preserves an installed Doubao or prepares its ZIP; selected Sogou follows the same separate rule before the remaining package, configuration, onboarding, and final-verification stages. A later failure therefore retains any current-session receipts and prints actionable manual steps. Prepared packages are not installed apps or enabled input sources. `result.json` includes `desktop_mode`, `with_docker`, `with_claude`, and `manual_steps`. During default install only, verification may accept a freshly validated prepared DMG for an absent selected desktop; inventory records `null` plus `pending_applications`. Standalone `--verify` remains strict and fails for absent selected apps. Doubao and optional Sogou always remain manual installations in either desktop policy. ## Desktop, editor and shell contracts -Desktop behavior is explicit: download-only by default, automatic placement only with `--managed-desktop`. Docker Desktop additionally requires `--with-docker` under both policies. Missing selected app downloads use the official latest URL or stable feed, validate the DMG, and save a receipt with filename/source/version/SHA-256. Reuse checks real file bytes; `--update` refreshes downloads. Never write app bundles or custom Kiro hooks in default desktop preparation. Selected Claude/Codex CLIs use native official scripts; healthy unknown installations stay on their original manager. Python runs only after bootstrap installs it. +Desktop behavior is explicit: download-only by default, automatic placement only with `--managed-desktop`. Docker Desktop additionally requires `--with-docker` under both policies. Missing selected app downloads use the official latest URL or stable feed, validate the DMG, and save a receipt with filename/source/version/SHA-256. Reuse checks real file bytes; `--update` refreshes downloads. Selected Claude/Codex CLIs use native official scripts; healthy unknown installations stay on their original manager. Python runs only after bootstrap installs it. The managed iTerm2 Dynamic Profile and the global default-profile preference have separate contracts. The profile file carries a stable unique GUID and the MesloLGS Nerd Font PostScript name. A private state record remembers the prior default and the GUID One Bite last managed. An upgrade follows a still-managed legacy GUID; a different default selected later by the user is preserved. Because already-open windows retain their existing profile, manual guidance requires reopening iTerm2 before Powerlevel10k configuration. @@ -40,7 +42,7 @@ fzf initialization requires interactive Zsh but does not require any standard fi Node.js is an explicit Homebrew formula, not an incidental dependency. Formula health and strict verification require `node`, `npm`, and `npx`. The native runtime smoke creates an isolated package with local build/start scripts, runs both through npm without fetching dependencies, and executes the resulting Node module. -Managed AI app placement checks vendor identity, Apple-trusted signature, executable and arm64 before copying. Kiro CLI additionally checks its manifest SHA-256. ChatGPT uses the fixed Codex.dmg URL and verifies its feed version/build. Claude Desktop managed mode resolves its DMG beside the ZIP named by the official release feed. When selected, download preparation first uses the official latest-DMG redirect without querying metadata; a download error falls back to that official feed. Both routes can reach the same distribution host, so an unreachable host still needs network recovery. Keep downloads/mounts in private temporary directories and only stage the final copy beside Applications for atomic replacement. Require apps to be closed normally; restore old copies after a failed replacement. Default Kiro instead calls the original installer with its own prompts and startup behavior; do not synthesize consent to vendor replacement prompts. +Managed AI app placement checks vendor identity, Apple-trusted signature, executable and arm64 before copying. ChatGPT uses the fixed Codex.dmg URL and verifies its feed version/build. Claude Desktop managed mode resolves its DMG beside the ZIP named by the official release feed. When selected, download preparation first uses the official latest-DMG redirect without querying metadata; a download error falls back to that official feed. Both routes can reach the same distribution host, so an unreachable host still needs network recovery. Keep downloads/mounts in private temporary directories and only stage the final copy beside Applications for atomic replacement. Require apps to be closed normally; restore old copies after a failed replacement. Records under ${XDG_STATE_HOME:-~/.local/state}/1bite/official-installs identify managed ownership, never prove health. Preserve personal configuration and credentials. cask:* IDs remain stable in machine reports even where the component uses an official download rather than Homebrew; terminal progress and errors display app:* to avoid implying Homebrew ownership. Claude Desktop uses cask:claude-desktop, distinct from the claude CLI stage. Recognize ChatGPT.app and Codex.app with bundle ID com.openai.codex; desktop and CLI health are independent. @@ -58,16 +60,6 @@ The managed Obsidian starter lives at `~/.config/1bite/obsidian-vault`. `~/.loca The Vim template is installed as `~/.config/1bite/vimrc`; `~/.vimrc` remains user-owned around one exact managed source line. Existing content before and after that line is preserved, changed files are backed up and identical reruns do not rewrite either file. Personal overrides belong after the source line. Reject symlink entry or managed files instead of following a dotfiles manager's link. Verification requires the selected template bytes and exactly one loader line. -Kiro IDE is an official desktop download, independent of the separately installed Kiro CLI app from the official CLI installer. Resolve system/user Kiro.app, require bundle ID dev.kiro.desktop and IDE major version >= 1. Preserve compatible copies by default; update and repair only copies recorded as installed by this setup. Do not add the IDE's internal bin directory to PATH: its `code` command would shadow VS Code. - -Install kiro-permissions.json as ~/.kiro/settings/permissions.yaml (JSON subset of YAML), mode 0600 on creation. The default all/ask rule requires interactive approval across capabilities; it is not an OS sandbox. Preserve existing nonempty YAML byte-for-byte, report it as custom and unreviewed. Reject empty policies and symlink files/directories before mutation. `inventory.json.kiro_permission_template` is `default-ask` or `custom-unreviewed`, a template comparison, never a claim of effective permission enforcement. Validation checks external JSON rule structure; it does not audit personal YAML, workspace/session/enterprise rules or runtime approval behavior. Human login and allow/deny acceptance in a temporary project are required. Never auto-grant TCC/IAM, migrate Q, import credentials or install trusted MCP/hooks. Data-sharing choices remain for first-run review. - -The following Kiro hook contract applies only to --managed-desktop. Default mode delegates to vendor onboarding and does not inspect or rewrite loader layout. Kiro CLI.app is resolved from system/user Applications, separately from IDE. The `kiro-shell` stage follows configuration and Oh My Zsh. Supply three runnable ~/.local/bin commands (kiro-cli, kiro-cli-chat, kiro-cli-term) needed by native loaders; preserve healthy personal commands, repair missing links or broken links resolving to the vendor app, reject unknown broken commands. Use only the official `integrations install --silent dotfiles zsh` command. Validate both Zsh rows of JSON status (the current CLI ignores the shell filter when listing), the exact contents of all four vendor loader files and syntax of all four generated init snippets. Respect ZDOTDIR; do not install SSH, input method or other integrations automatically. Native installer backs up changed dotfiles. - -Before loading hooks, use `inline disable` only when inline.enabled is absent from `${KIRO_HOME:-~/.kiro}/settings/cli.json`; preserve explicit true/false choices and all other settings. This avoids the actual CLI 2.21.1 malformed opt-in notification and leaves history-based AI suggestions disabled on fresh installs. Respect absolute KIRO_HOME; refuse symlink settings. Do not infer CLI 2.x tool approval from IDE permissions.yaml. Human onboarding uses `kiro-cli launch`, a fresh iTerm2 window and `kiro-cli doctor --all` (no automatic fixes); optional input-method setup and TCC consent remain manual. Automated tests checks shell integration, not graphical completion or login. Do not publish raw Kiro settings, diagnostic reports or shell history. - -A custom KIRO_HOME requires a recognized selected CLI version >= 2.3.0, checked before creating command links or changing preferences. Unsupported CLIs ignore that variable; never fall back to changing their default profile. Default ~/.kiro remains supported for healthy supported CLIs. Native tests pin the app command directory in PATH, test default-profile integration on all supported local versions, and test either custom-profile integration or rejection without writes according to the actual version. An absent settings file after inline disable produces an actionable error. - Managed package operations and metadata queries use qualified homebrew/core, homebrew/cask or the four declared charmbracelet/tap names. During bootstrap, unset the retired HOMEBREW_ASK and export HOMEBREW_NO_ASK=1 for this process only. Global untrusted-tap warnings remain visible and do not imply installation failure; never grant unrelated/whole-tap trust or remove taps. Fully qualified Charm installs grant trust only to those requested formulae. Confirmation suppression does not bypass sudo, OS permissions or vendor onboarding. The eight default extensions are Go, Python, Ruff, ESLint, Prettier, YAML, ShellCheck and Sublime Text Keymap (ms-vscode.sublime-keybindings). Preserve user keybindings.json; do not invoke the extension's Sublime settings importer automatically. Reload VS Code to load keymap contributions. Container Tools, Even Better TOML, Remote SSH and Dev Containers are manual options documented in shell-editor.md; existing copies are not removed and optional IDs are outside setup verification/update reports. Python may install optional companion extensions; only declared IDs are verified. Preserve personal/project formatter and format-on-save settings. @@ -115,7 +107,7 @@ tee copies raw command bytes straight to the original stdout, including untermin | 130 / 143 | Interrupted by SIGINT / SIGTERM | | other nonzero | Underlying command's status (for example curl 22); inspect `run.log` | -Invalid arguments and unsupported operating systems fail before creating a session. Once a session starts, `result.json` is written atomically on normal completion, failure and catchable interruption. SIGKILL, power loss or a full filesystem can prevent finalization: absence of `result.json` means **incomplete**, never success. `events.tsv` provides the last started stage. A subsequent run rechecks actual state under a kernel lock (system lockf on macOS; flock on Linux test hosts). `session.lock` is a persistent inode, not a completion marker; never delete it to unlock a live session. The kernel releases it after the final inheriting process exits. Mutating shell installers and explicitly selected native installer children inherit descriptor 9 so killing a supervisor does not unlock active mutation. The complete read-only verification tree closes descriptor 9 before starting Zsh, Powerlevel10k, Kiro, Codex, Go, VS Code, PATH, runtime and inventory probes; a background helper left by those probes therefore cannot retain the session lock. `--lock-status` distinguishes genuine contention from lock-helper errors and lists PID/command pairs from `lsof` when available without reading process arguments. +Invalid arguments and unsupported operating systems fail before creating a session. Once a session starts, `result.json` is written atomically on normal completion, failure and catchable interruption. SIGKILL, power loss or a full filesystem can prevent finalization: absence of `result.json` means **incomplete**, never success. `events.tsv` provides the last started stage. A subsequent run rechecks actual state under a kernel lock (system lockf on macOS; flock on Linux test hosts). `session.lock` is a persistent inode, not a completion marker; never delete it to unlock a live session. The kernel releases it after the final inheriting process exits. Mutating shell installers and explicitly selected native installer children inherit descriptor 9 so killing a supervisor does not unlock active mutation. The complete read-only verification tree closes descriptor 9 before starting Zsh, Powerlevel10k, Codex, Go, VS Code, PATH, runtime and inventory probes; a background helper left by those probes therefore cannot retain the session lock. `--lock-status` distinguishes genuine contention from lock-helper errors and lists PID/command pairs from `lsof` when available without reading process arguments. ## Run directory @@ -148,7 +140,7 @@ Machine example: python3 -m json.tool /absolute/path/to/that-run/result.json ``` -Stage IDs: `maintenance`, `target`, `network`, `homebrew`, `formula:`, `cask:`, `configuration`, `ohmyzsh`, `kiro-shell`, `vscode:`, `claude`, `codex`, `verification`, `doubao-input-installer`, `sogou-installer`; standalone Docker uses `docker-runtime`. Success actions include `checked`, `skipped`, `installed`, `repaired`, `preserved`, `update-checked`, `installed-or-updated`, `prepared`. Network warnings are recorded as `warning` when an install continues with cached/existing software. On failure, `action` contains the exit code. +Stage IDs: `maintenance`, `target`, `network`, `homebrew`, `formula:`, `cask:`, `configuration`, `ohmyzsh`, `vscode:`, `claude`, `codex`, `verification`, `doubao-input-installer`, `sogou-installer`; standalone Docker uses `docker-runtime`. Success actions include `checked`, `skipped`, `installed`, `repaired`, `preserved`, `update-checked`, `installed-or-updated`, `prepared`. Network warnings are recorded as `warning` when an install continues with cached/existing software. On failure, `action` contains the exit code. Do not log or transmit credentials. `environment.json` deliberately excludes account names, serial numbers, hostnames, public/private IPs and proxy values. Raw third-party command output can still contain local paths or service-specific details: inspect `run.log` before sharing. The installer never uploads local logs. Automated tests artifacts come only from disposable runners without personal API keys. diff --git a/docs/development.md b/docs/development.md index 3a1d5a7..12f10d7 100644 --- a/docs/development.md +++ b/docs/development.md @@ -50,7 +50,7 @@ Changes to installation logic should cover a fresh install, a healthy repeat, an Battery changes require fixed fixtures for both `Batt` and `BATT` log forms, optional percent spacing, charging/discharging transitions, sleep-spanning wall time, instantaneous source changes, no battery, and cache damage. Assert decimal numeric comparison, the complete summary/JSON/TSV/Shell/field contracts, output/cache privacy, 0700/0600 modes, atomic replacement, and symlink refusal. Startup integration tests must prove that the summary skips history and cache, closes the session lock, does not become a progress/result step, and cannot hide or create a later installation failure. The optional installed command must preserve unknown or modified bytes and replace an unchanged managed copy only after an explicit update. These isolated tests do not establish physical battery health. -Default desktop mode must download and validate selected DMGs without placing `.app` bundles. Its final guide must be derived from the current run's receipts, exclude old or unselected packages, save `manual-steps.txt` with mode `0600`, and print a strict follow-up verification command. Managed desktop mode has separate placement and Kiro integration checks. +Default desktop mode must download and validate selected DMGs without placing `.app` bundles. Its final guide must be derived from the current run's receipts, exclude old or unselected packages, save `manual-steps.txt` with mode `0600`, and print a strict follow-up verification command. Managed desktop mode has separate placement checks. Full installation tests belong on disposable macOS runners because they can install Homebrew packages and replace runner applications. Local development and the repository quality workflow use unit tests, archive checks, and isolated temporary homes. diff --git a/docs/installation.md b/docs/installation.md index c8d5b10..d34803d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,12 +1,11 @@ # Installation policy and manual steps -The default mode keeps vendor installation and first-launch flows for desktop applications. `--managed-desktop` retains automated placement and managed Kiro shell integration for users who explicitly choose it. GUI sign-in and macOS permission prompts remain manual in both modes. +The default mode keeps vendor installation and first-launch flows for desktop applications. `--managed-desktop` enables automatic placement for selected desktop applications. GUI sign-in and macOS permission prompts remain manual in both modes. | Software | Default mode | `--managed-desktop` | Source and update notes | | --- | --- | --- | --- | | Google Chrome | Official Universal DMG | Official cask | Chrome retains its updater, existing profiles, and existing installations. | | ChatGPT / Codex desktop app | Official DMG | Validate signature, architecture, and version, then place the app | Uses the current OpenAI desktop distribution and update feed; checked independently from Codex CLI and recognizes the older Codex.app name. | -| Kiro IDE | Official stable arm64 DMG | Validate and place the official app | Default mode leaves first launch and IDE onboarding to the user. | | Claude Desktop | Disabled; `--with-claude` prepares the official DMG | Still requires `--with-claude`; validates and places Claude.app | The desktop app and Claude Code CLI have separate sign-in flows. `--update` refreshes the package. | | Docker Desktop | Disabled; `--with-docker` prepares the official arm64 DMG | Still requires `--with-docker`; official cask | Review the current vendor terms before selecting it. First launch, privileged components, and engine initialization use the official GUI. Check the engine separately with `--docker-smoke`. | | iTerm2 | Official cask | Same | The vendor distributes a ZIP; the cask places the app. One Bite updates only its own uniquely identified Dynamic Profile and preserves unrelated profiles. | @@ -15,7 +14,6 @@ The default mode keeps vendor installation and first-launch flows for desktop ap | Node.js development/runtime | Current Homebrew `node` formula | Same | Installs the Node runtime together with npm and npx. Verification runs a local npm build/start lifecycle without fetching a package. | | Codex CLI | Official shell installer | Same | Resolves the current official release and preserves a healthy installation from another source. | | Claude Code CLI | Disabled; `--with-claude` uses the official Bash installer | Same and still opt-in | Uses the vendor-native installer instead of npm or Homebrew. | -| Kiro CLI | Official Bash installer and native onboarding | Official manifest DMG plus managed Zsh integration | Default mode preserves vendor prompts and onboarding; managed mode validates the app, CLI links, and native dotfile integration. | | Powerlevel10k | Shallow official checkout in the Oh My Zsh custom theme directory | Same | Selected only when the user has no explicit theme. Run `p10k configure` in a new One Bite profile window; the user owns the resulting `~/.p10k.zsh`. | | MesloLGS and JetBrains Mono Nerd Font | Homebrew font casks | Same | Verifies each regular font face. The managed One Bite iTerm2 profile selects MesloLGS; unrelated and already-open profiles are unchanged. | | hexyl | Homebrew formula | Same | Provides a colored terminal hex viewer for inspecting binary files. It follows the standard install, repair, inventory, verification, and update lifecycle. | @@ -36,7 +34,7 @@ Docker Desktop is enabled with `--with-docker`. Without that flag it is not down Claude Desktop and Claude Code are enabled together with `--with-claude`. Without that flag they are not downloaded, configured, checked, updated, or removed. Repeat the same flag with rerun, verify, configure, update, and update-check commands. `--managed-desktop` never enables Claude implicitly. -`./1bite --verify` checks the actual selected installations and never treats a receipt as an installed app. Default verification accepts the vendor's own Kiro Zsh integration. `--managed-desktop --verify` additionally enforces the managed Kiro hook contract. +`./1bite --verify` checks the actual selected installations and never treats a receipt as an installed app. At the end of an install, the generated `manual-steps.txt` gives the exact package path, shell-safe `open` command, drag-to-Applications steps, first-launch tasks, and a strict verification command for that run. @@ -47,7 +45,7 @@ The input-method section is separate from DMG instructions. It explains archive - Normal reruns preserve healthy applications and CLIs and reuse verified packages. - `--update` updates managed development tools and official CLIs and refreshes current desktop DMGs. The user still runs desktop installers or vendor updaters in default mode. - `--managed-desktop --update` automatically updates AI apps recorded as managed. Chrome, iTerm2, and VS Code keep their cask lifecycle; selected Docker Desktop uses its cask lifecycle only with `--with-docker`. Healthy apps from another source keep their original updater. -- Default Kiro CLI relies on the vendor app for integration and updates. A damaged installation returns to the official installer instead of being silently removed. +- Kiro is outside One Bite's managed software set. Existing applications and personal settings remain untouched. - Powerlevel10k updates only on `--update`, only for a clean official checkout. Custom origins, symlinks, and personal `~/.p10k.zsh` stay under their existing owner. - Managed fonts update through Homebrew. External font files stay with their original source. - Private settings, authentication, and sign-in are never taken over. Ownership receipts cannot replace a health check. @@ -56,4 +54,4 @@ The input-method section is separate from DMG instructions. It explains archive Executable addresses are defined in [`sources.tsv`](../config/sources.tsv). Desktop DMGs are downloaded into a private temporary directory, checked for image integrity, and only then moved into Downloads. Managed mode also checks the code signature, bundle ID, and arm64 architecture before app replacement; a failed replacement retains a recovery path. -Official references: [Chrome](https://www.google.com/chrome/), [ChatGPT desktop](https://learn.chatgpt.com/docs/app), [Kiro installation](https://kiro.dev/docs/getting-started/installation/), [Claude download](https://claude.com/download), [Docker Desktop for Mac](https://docs.docker.com/desktop/setup/install/mac-install/), [iTerm2](https://iterm2.com/downloads.html), [VS Code for Mac](https://code.visualstudio.com/docs/setup/mac), [Obsidian installation](https://help.obsidian.md/install), [Doubao Input Method](https://shurufa.doubao.com/pc), [Claude Code setup](https://code.claude.com/docs/en/setup), and [Codex CLI](https://learn.chatgpt.com/docs/codex/cli). +Official references: [Chrome](https://www.google.com/chrome/), [ChatGPT desktop](https://learn.chatgpt.com/docs/app), [Claude download](https://claude.com/download), [Docker Desktop for Mac](https://docs.docker.com/desktop/setup/install/mac-install/), [iTerm2](https://iterm2.com/downloads.html), [VS Code for Mac](https://code.visualstudio.com/docs/setup/mac), [Obsidian installation](https://help.obsidian.md/install), [Doubao Input Method](https://shurufa.doubao.com/pc), [Claude Code setup](https://code.claude.com/docs/en/setup), and [Codex CLI](https://learn.chatgpt.com/docs/codex/cli). diff --git a/docs/maintenance.md b/docs/maintenance.md index 345e16b..1ca2098 100644 --- a/docs/maintenance.md +++ b/docs/maintenance.md @@ -65,9 +65,8 @@ git describe --tags --exact-match The checker uses `ls-remote` and does not fetch commits, so it reports a difference without guessing whether it can fast-forward. Resolve divergence and local changes deliberately. Never automate `reset --hard`, forced replacement, or a force push. ZIP copies, forks, and checkouts without an origin require manual maintenance. -Read the new README and [`sources.md`](sources.md). Add newly required files to any external configuration directory and review changes instead of replacing that directory wholesale. Managed env, shell, Zsh, Vim, iTerm2, and Obsidian starter files can be reapplied after review; merge private Claude, Codex, Kiro, VS Code, and existing vault changes manually. +Read the new README and [`sources.md`](sources.md). Add newly required files to any external configuration directory and review changes instead of replacing that directory wholesale. Managed env, shell, Zsh, Vim, iTerm2, and Obsidian starter files can be reapplied after review; merge private Claude, Codex, VS Code, and existing vault changes manually. Kiro applications and settings are outside One Bite management and remain untouched. -Kiro CLI hooks are managed by their dedicated installer and verifier, not by overwriting private CLI JSON. When the vendor changes initialization or permissions, update the implementation and contract tests together and verify the actual loaded CLI. ## Suggested schedule @@ -75,7 +74,7 @@ Kiro CLI hooks are managed by their dedicated installer and verifier, not by ove | --- | --- | | Weekly, before a new project, or after a failure | Run `--check-updates`; add `--diagnose` and `--verify` when behavior is abnormal | | Monthly or for a needed security fix | Save work, close affected apps, keep a pre-update report, run `--update`, and keep the post-update report | -| After a major macOS, selected container engine, Kiro, or terminal change | Test the selected engine, terminal keys and completion, AI sign-in, and permission prompts on the Mac | +| After a major macOS, selected container engine, AI tool, or terminal change | Test the selected engine, terminal keys and completion, AI sign-in, and permission prompts on the Mac | | After changing the installer or sources | Bump the patch version, update manifest and configuration fixtures, run build/test/format, then validate the same candidate in isolation | One Bite does not install a background auto-updater, grant permissions, remove pins, clear caches, or bulk-uninstall rollback versions. A failed update preserves completed work; fix the logged problem and rerun the same command. Reverting the One Bite Git checkout does not revert already installed software. diff --git a/docs/shell-editor.md b/docs/shell-editor.md index d18ac5f..75bf14f 100644 --- a/docs/shell-editor.md +++ b/docs/shell-editor.md @@ -5,7 +5,6 @@ | Template | Installed location and behavior | | --- | --- | | `claude-settings.json` | `${CLAUDE_CONFIG_DIR:-~/.claude}/settings.json`; created only when absent | -| `kiro-permissions.json` | `~/.kiro/settings/permissions.yaml`; asks before tools on a new setup and preserves existing YAML | | `codex-config.toml` | `${CODEX_HOME:-~/.codex}/config.toml`; workspace-write and on-request approval on a new setup | | `env.zsh` | `~/.config/1bite/env.zsh`; Homebrew, AI CLI, Docker, Go, and VS Code environment and PATH | | `shell.zsh` | `~/.config/1bite/shell.zsh`; interactive loader for the eight modules below | diff --git a/docs/sources.md b/docs/sources.md index ddce0a9..b78926e 100644 --- a/docs/sources.md +++ b/docs/sources.md @@ -13,8 +13,6 @@ Homebrew formulae and casks use the source declared by their official definition | Claude Code, optional | `https://claude.ai/install.sh` with `--with-claude` | [Setup and version management](https://code.claude.com/docs/en/setup), [settings](https://code.claude.com/docs/en/settings) | | Codex CLI | `https://chatgpt.com/codex/install.sh` | [CLI installation](https://learn.chatgpt.com/docs/codex/cli), [configuration](https://learn.chatgpt.com/docs/config-file/config-basic) | | ChatGPT / Codex desktop | OpenAI desktop DMG under `persistent.oaistatic.com/codex-app-prod/` | [Desktop app](https://learn.chatgpt.com/docs/app), [current ChatGPT cask](https://github.com/Homebrew/homebrew-cask/blob/master/Casks/c/chatgpt.rb) | -| Kiro CLI and Zsh | Official installer and onboarding; managed mode uses the official manifest and integration commands | [CLI commands](https://kiro.dev/docs/reference/cli-commands/), [autocomplete](https://kiro.dev/docs/cli/autocomplete/), [2.x trust rules](https://kiro.dev/docs/cli/2x-reference/) | -| Kiro IDE | Architecture-specific DMG from `prod.download.desktop.kiro.dev` | [Downloads](https://kiro.dev/downloads/), [installation](https://kiro.dev/docs/getting-started/installation/), [permissions](https://kiro.dev/docs/permissions/), [data protection](https://kiro.dev/docs/privacy-and-security/data-protection/) | | Oh My Zsh | Official unattended installer with `KEEP_ZSHRC=yes`, `CHSH=no`, and `RUNZSH=no` | [Unattended installation](https://github.com/ohmyzsh/ohmyzsh#unattended-install) | | Powerlevel10k | Shallow clone of the official repository into Oh My Zsh custom themes | [Oh My Zsh setup](https://github.com/romkatv/powerlevel10k#oh-my-zsh), [wizard](https://github.com/romkatv/powerlevel10k#configuration-wizard), [fonts](https://github.com/romkatv/powerlevel10k#fonts) | | Docker Desktop, optional | Official arm64 DMG with `--with-docker`; official cask in managed mode | [Mac installation](https://docs.docker.com/desktop/setup/install/mac-install/), [cask](https://formulae.brew.sh/cask/docker-desktop) | @@ -32,11 +30,11 @@ Homebrew formulae and casks use the source declared by their official definition | Developer fonts | `font-meslo-lg-nerd-font` and `font-jetbrains-mono-nerd-font` casks | [MesloLGS](https://formulae.brew.sh/cask/font-meslo-lg-nerd-font), [JetBrains Mono](https://formulae.brew.sh/cask/font-jetbrains-mono-nerd-font) | | Other command-line tools | Official Homebrew formulae listed in [`formulae.txt`](../config/formulae.txt) | `https://formulae.brew.sh/formula/` | -Default mode checks each selected desktop app and downloads an official DMG when it is absent. Docker Desktop requires `--with-docker` after the user reviews the current vendor terms. iTerm2 and VS Code use casks. AI CLIs prefer official scripts. `--managed-desktop` enables app placement and managed Kiro hooks without selecting Docker. +Default mode checks each selected desktop app and downloads an official DMG when it is absent. Docker Desktop requires `--with-docker` after the user reviews the current vendor terms. iTerm2 and VS Code use casks. AI CLIs prefer official scripts. `--managed-desktop` enables app placement without selecting Docker. -ChatGPT uses the fixed official DMG path and its appcast. Kiro selects the exact current stable arm64 DMG from the vendor's architecture-specific release metadata. Claude Desktop is accessed only with `--with-claude`, first through the vendor's latest-DMG redirect and then through the official release feed as a fallback. Chrome and selected Docker Desktop use official rolling DMG addresses. A normal rerun reuses selected packages whose digests still match; `--update` refreshes them. +ChatGPT uses the fixed official DMG path and its appcast. Claude Desktop is accessed only with `--with-claude`, first through the vendor's latest-DMG redirect and then through the official release feed as a fallback. Chrome and selected Docker Desktop use official rolling DMG addresses. A normal rerun reuses selected packages whose digests still match; `--update` refreshes them. -Claude Code and Codex use their own native installers and metadata. Default Kiro CLI runs the vendor installer and preserves its replacement prompt and onboarding. Managed Kiro resolves a digest from the official stable manifest. Recorded hashes support caching and audit; they do not represent an independent vendor signature. +Claude Code and Codex use their own native installers and metadata. Recorded hashes support caching and audit; they do not represent an independent vendor signature. Homebrew checks hashes declared by formulae and casks. Some rolling casks, including Chrome, intentionally use `no_check`; managed app tests therefore also validate signature, bundle ID, arm64 architecture, and launch behavior. Doubao and Sogou have fixed digests and are saved only after their official URL, checksum, and archive integrity are checked; Doubao also requires the expected vendor-installer layout and confines framework symbolic links to that application tree. Community-maintained casks are packaging metadata, not a claim that every cask is maintained by its software vendor. diff --git a/scripts/battery.sh b/scripts/battery.sh index 074c115..e78ad7a 100755 --- a/scripts/battery.sh +++ b/scripts/battery.sh @@ -1,7 +1,7 @@ #!/bin/bash set -euo pipefail -BATTERY_TOOL_VERSION=0.0.8 +BATTERY_TOOL_VERSION=0.0.9 BATTERY_SCHEMA_VERSION=1 MAX_RETAINED_EVENTS=256 diff --git a/scripts/check-updates.py b/scripts/check-updates.py index 5d90ae7..023db39 100644 --- a/scripts/check-updates.py +++ b/scripts/check-updates.py @@ -22,7 +22,6 @@ TEMPLATES = { 'claude-settings.json': ('CLAUDE_CONFIG_DIR', '.claude/settings.json', True), 'codex-config.toml': ('CODEX_HOME', '.codex/config.toml', True), - 'kiro-permissions.json': (None, '.kiro/settings/permissions.yaml', True), 'env.zsh': (None, '.config/1bite/env.zsh', False), 'shell.zsh': (None, '.config/1bite/shell.zsh', False), 'zsh/framework.zsh': (None, '.config/1bite/zsh/framework.zsh', False), @@ -49,7 +48,7 @@ 'obsidian-vault/_attachments/.gitkeep': (None, '.config/1bite/obsidian-vault/_attachments/.gitkeep', False), 'obsidian-vault/_templates/.gitkeep': (None, '.config/1bite/obsidian-vault/_templates/.gitkeep', False), } -EXTERNAL_APPS = {'chatgpt', 'kiro', 'kiro-cli', 'claude-desktop'} +EXTERNAL_APPS = {'chatgpt', 'claude-desktop'} def command(*args): @@ -268,7 +267,7 @@ def fetch(pair): state, note = 'manual', 'Upstream formula is disabled/deprecated; review replacement before updating.' self.add('formula:' + name, installed, latest, 'homebrew', state, note) with ThreadPoolExecutor(max_workers=5) as executor: - names = ('chatgpt', 'kiro', 'kiro-cli', 'codex') + (('claude-desktop', 'claude') if self.with_claude else ()) + names = ('chatgpt', 'codex') + (('claude-desktop', 'claude') if self.with_claude else ()) official = dict(zip(names, executor.map( lambda name: self.attempt('official:' + name, official_ai.release, name, self.sources), names))) for name, app in casks: diff --git a/scripts/configure.py b/scripts/configure.py index 9b60028..3f89e55 100644 --- a/scripts/configure.py +++ b/scripts/configure.py @@ -33,7 +33,6 @@ ITERM_DEFAULT_KEY = 'Default Bookmark Guid' ITERM_GUID_PATTERN = re.compile(r'[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}') VSCODE_PATH = Path('Library/Application Support/Code/User/settings.json') -KIRO_PATH = Path('.kiro/settings/permissions.yaml') GIT_IGNORE_PATH = Path('.config/1bite/gitignore-global') OBSIDIAN_TEMPLATE_PATH = Path('.config/1bite/obsidian-vault') OBSIDIAN_COMMAND_PATHS = (Path('.local/bin/ob'), Path('.local/bin/obn')) @@ -501,7 +500,6 @@ def configure(home, config_dir, codex_home=None, claude_home=None, shell_home=No environment = (config_dir / 'env.zsh').read_bytes() vim = (config_dir / 'vimrc').read_bytes() vscode = (config_dir / 'vscode-settings.json').read_bytes() - kiro = (config_dir / 'kiro-permissions.json').read_bytes() git_defaults = validate_git_defaults((config_dir / 'git-defaults.json').read_text()) git_ignore = validate_git_ignore((config_dir / 'gitignore-global').read_bytes()) obsidian_files = obsidian_template_files(config_dir) @@ -509,7 +507,6 @@ def configure(home, config_dir, codex_home=None, claude_home=None, shell_home=No current_git = git_values(home) if any(key.lower() not in current_git for key in git_defaults) and (home / '.gitconfig').is_symlink(): raise ValueError(f'Refusing to update symlink Git configuration: {home / ".gitconfig"}') - validate_kiro(kiro) if not isinstance(json.loads(vscode), dict): raise ValueError('VS Code defaults must be a JSON object') validate(claude, codex, profile) @@ -528,7 +525,7 @@ def configure(home, config_dir, codex_home=None, claude_home=None, shell_home=No home / ITERM_DEFAULT_STATE_PATH, home / ITERM_PREFERENCES_PATH, home / '.config/1bite/shell.zsh', home / '.config/1bite/env.zsh', home / '.config/1bite/vimrc', home / '.vimrc', - home / GIT_IGNORE_PATH, home / VSCODE_PATH, home / KIRO_PATH, + home / GIT_IGNORE_PATH, home / VSCODE_PATH, obsidian_directory, *obsidian_command_paths, zsh_directory, *[zsh_directory / name for name in ZSH_MODULES], shell_home / '.zshrc', shell_home / '.zprofile'] @@ -559,11 +556,6 @@ def configure(home, config_dir, codex_home=None, claude_home=None, shell_home=No f'Review it manually, keep one loader, and rerun. Expected line: {canonical}') shell_entries.append((path, content, canonical, recognized)) validate_managed_obsidian_tree(obsidian_directory, obsidian_files) - for path in (home / '.kiro', home / '.kiro/settings'): - if path.is_symlink(): - raise ValueError(f'Refusing to configure Kiro through symlink: {path}') - if (home / KIRO_PATH).exists(): - kiro_policy_status(home) repair_broken_zsh_completions() relocate_profile_artifacts(home) if with_claude: @@ -590,9 +582,6 @@ def configure(home, config_dir, codex_home=None, claude_home=None, shell_home=No obsidian_command_path.chmod(0o755) # Existing VS Code settings may be JSONC. Preserve their bytes, including comments. write(home / VSCODE_PATH, vscode, preserve=True) - # JSON is valid YAML. Personal YAML policies remain byte-for-byte intact. - write(home / KIRO_PATH, kiro, preserve=True) - report_kiro(home) for path, original, canonical, recognized in shell_entries: content = update_source_line(original, canonical, recognized) write(path, content.encode()) @@ -613,39 +602,6 @@ def configure(home, config_dir, codex_home=None, claude_home=None, shell_home=No write(path, content.encode()) -def validate_kiro(content): - data = json.loads(content) - capabilities = {'all', 'builtin', 'filesystem', 'fs_read', 'fs_write', 'shell', 'web_fetch', - 'web_search', 'mcp', 'subagent', 'skill', 'power', 'context', 'diagnostics', 'sandbox_network'} - if not isinstance(data, dict) or set(data) != {'rules'} or not isinstance(data['rules'], list) or not data['rules']: - raise ValueError('Kiro permissions must contain a nonempty rules array') - for rule in data['rules']: - if (not isinstance(rule, dict) or not {'capability', 'effect'} <= rule.keys() - or not rule.keys() <= {'capability', 'effect', 'match', 'exclude'} - or not isinstance(rule['capability'], str) or rule['capability'] not in capabilities - or not isinstance(rule['effect'], str) or rule['effect'] not in {'ask', 'deny', 'allow'}): - raise ValueError('Invalid Kiro permission rule') - for key in ('match', 'exclude'): - if key in rule and (not isinstance(rule[key], list) or not rule[key] - or not all(isinstance(value, str) and value for value in rule[key])): - raise ValueError('Kiro match/exclude must be nonempty arrays of patterns') - - -def kiro_policy_status(home): - content = (home / KIRO_PATH).read_bytes() - if not content.strip(): - raise ValueError('Kiro permission file is empty; review it before using the agent') - return ('default-ask' if content == (ROOT / 'config/kiro-permissions.json').read_bytes() - else 'custom-unreviewed') - - -def report_kiro(home): - if kiro_policy_status(home) == 'default-ask': - print('Kiro permission template: all capabilities ask (requires IDE 1.x; approve inside Kiro).') - else: - print('Kiro custom policy preserved, not audited by setup. Review effective permissions inside Kiro before use.') - - def verify(home, codex_home=None, claude_home=None, shell_home=None, with_claude=False, config_dir=ROOT / 'config'): verify_zsh_completions() validate(((claude_home or home / '.claude') / 'settings.json').read_text() if with_claude else '{}', @@ -682,11 +638,8 @@ def verify(home, codex_home=None, claude_home=None, shell_home=None, with_claude raise ValueError('Vim configuration files must not be symlinks') if not vim.is_file() or vim.read_bytes() != (config_dir / 'vimrc').read_bytes(): raise ValueError('Managed Vim configuration differs from the selected template') - if any(path.is_symlink() for path in (home / '.kiro', home / '.kiro/settings', home / KIRO_PATH)): - raise ValueError('Kiro permission file must not be a symlink') verify_git(home, validate_git_defaults((config_dir / 'git-defaults.json').read_text()), validate_git_ignore((config_dir / 'gitignore-global').read_bytes())) - report_kiro(home) for name in ('.zshrc', '.zprofile'): line = ENV_SOURCE_LINE if name == '.zprofile' else SOURCE_LINE if ((shell_home or home) / name).read_text().splitlines().count(line) != 1: diff --git a/scripts/desktop.py b/scripts/desktop.py index 50660e6..2b75856 100644 --- a/scripts/desktop.py +++ b/scripts/desktop.py @@ -15,7 +15,6 @@ 'docker-desktop': 'Docker.app', 'google-chrome': 'Google Chrome.app', 'chatgpt': 'ChatGPT.app', - 'kiro': 'Kiro.app', 'claude-desktop': 'Claude.app', } diff --git a/scripts/inventory.py b/scripts/inventory.py index a467a8a..8d6e2a9 100644 --- a/scripts/inventory.py +++ b/scripts/inventory.py @@ -4,7 +4,6 @@ import os from pathlib import Path import subprocess -from configure import kiro_policy_status from desktop import APPS as MANUAL_APPS ROOT = Path(__file__).resolve().parents[1] @@ -45,7 +44,7 @@ def inventory(): apps[token] = output('/usr/libexec/PlistBuddy', '-c', 'Print :CFBundleShortVersionString', f'{path}/Contents/Info.plist') tools = {} - for name in ('claude', 'codex', 'kiro-cli', 'docker'): + for name in ('claude', 'codex', 'docker'): if name == 'claude' and not with_claude: continue if name == 'docker' and not with_docker: @@ -53,7 +52,7 @@ def inventory(): try: tools[name] = output(name, '--version').splitlines()[0] except (OSError, subprocess.CalledProcessError): - if not allow_prepared or name not in ('kiro-cli', 'docker'): + if not allow_prepared or name != 'docker': raise tools[name] = None @@ -84,8 +83,7 @@ def inventory(): 'pending_applications': pending, 'with_claude': with_claude, 'with_docker': with_docker, 'font_casks': font_casks, 'vscode_extensions': extensions, 'ohmyzsh_revision': omz_revision, - 'powerlevel10k_revision': p10k_revision, - 'kiro_permission_template': kiro_policy_status(Path.home())} + 'powerlevel10k_revision': p10k_revision} if __name__ == '__main__': diff --git a/scripts/kiro-shell.py b/scripts/kiro-shell.py deleted file mode 100644 index 6f3c142..0000000 --- a/scripts/kiro-shell.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python3 -"""Install the vendor's Zsh hooks without enabling other system integrations.""" -import argparse -import json -import os -from pathlib import Path -import re -import shutil -import subprocess -import sys - -COMMANDS = ('kiro-cli', 'kiro-cli-chat', 'kiro-cli-term') -# Full vendor-loader contract: a nonempty but truncated file is not a working hook. -HELPERS = {f'{rc}.{phase}.zsh': '[ -x ~/.local/bin/kiro-cli ] && eval "$(~/.local/bin/kiro-cli init zsh ' - + phase + ' --rcfile ' + rc + ')"' - for rc in ('zshrc', 'zprofile') for phase in ('pre', 'post')} - - -def helpers_healthy(directory): - return all((directory / name).is_file() and (directory / name).read_text() == content - for name, content in HELPERS.items()) - - -def runnable(path): - if not path or not os.access(path, os.X_OK): - return False - try: - return subprocess.run([str(path), '--version'], stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, timeout=15).returncode == 0 - except (OSError, subprocess.TimeoutExpired): - return False - - -def integration_status(cli): - result = subprocess.run([str(cli), 'integrations', 'status', '--format', 'json', 'dotfiles', 'zsh'], - capture_output=True, text=True, check=True, timeout=30) - data = json.loads(result.stdout) - if data.get('errors') or not isinstance(data.get('integrations'), list): - raise ValueError('Kiro integration status failed; inspect with kiro-cli integrations status dotfiles zsh') - rows = [item for item in data['integrations'] if item.get('shell') == 'zsh'] - if len(rows) != 2 or {item.get('file_name') for item in rows} != {'.zshrc', '.zprofile'}: - raise ValueError('Kiro Zsh integration status format changed; update setup before proceeding') - if any(type(item.get('installed')) is not bool for item in rows): - raise ValueError('Kiro integration status must contain boolean installation results') - return all(item['installed'] for item in rows) - - -def check_kiro_home_support(cli, home): - override = os.environ.get('KIRO_HOME') - if not override: - return - if not Path(override).is_absolute(): - raise ValueError('KIRO_HOME must be absolute') - if Path(override) == home / '.kiro': - return - # Older CLIs silently ignore KIRO_HOME and would change the default profile. - result = subprocess.run([str(cli), '--version'], capture_output=True, text=True, check=True, timeout=15) - match = re.fullmatch(r'kiro-cli (\d+)\.(\d+)\.(\d+)', result.stdout.strip()) - if not match or tuple(map(int, match.groups())) < (2, 3, 0): - raise ValueError('A custom KIRO_HOME requires Kiro CLI 2.3.0 or newer; update the selected CLI ' - 'with its original manager before rerunning setup. No settings were changed.') - - -def inline_preference(cli, home, verify): - kiro_home = Path(os.environ.get('KIRO_HOME') or home / '.kiro') - if not kiro_home.is_absolute(): - raise ValueError('KIRO_HOME must be absolute') - path = kiro_home / 'settings/cli.json' - if any(item.is_symlink() for item in (kiro_home, path.parent, path)): - raise ValueError('Refusing to change Kiro CLI settings through a symlink') - data = json.loads(path.read_text()) if path.exists() else {} - if not isinstance(data, dict) or ('inline.enabled' in data and type(data['inline.enabled']) is not bool): - raise ValueError('Kiro inline preference must be a boolean in CLI settings') - if 'inline.enabled' not in data: - if verify: - raise ValueError('Kiro inline history-sharing preference is unset; rerun setup') - subprocess.run([str(cli), 'inline', 'disable'], stdout=subprocess.DEVNULL, check=True, timeout=15, - pass_fds=(9,) if os.environ.get('ONE_BITE_LOCKED') == '1' else ()) - if not path.is_file(): - raise ValueError('Kiro did not create settings in the requested configuration directory; ' - 'check the selected CLI version and KIRO_HOME before rerunning setup') - if json.loads(path.read_text()).get('inline.enabled') is not False: - raise ValueError('Kiro did not save the disabled inline history-sharing preference') - print('Kiro inline history-based AI suggestions: disabled for new settings.', file=sys.stderr) - return True - print('Kiro inline history-based AI suggestions: preserving your ' + - ('enabled' if data['inline.enabled'] else 'disabled') + ' preference.', file=sys.stderr) - return False - - -def verify_generated_hooks(cli): - for rc in ('zshrc', 'zprofile'): - for phase in ('pre', 'post'): - result = subprocess.run([str(cli), 'init', 'zsh', phase, '--rcfile', rc], - capture_output=True, text=True, check=True, timeout=15) - subprocess.run(['/bin/zsh', '-n'], input=result.stdout, text=True, check=True, - stdout=subprocess.DEVNULL, timeout=15) - - -def configure(home, app, verify=False): - shell_home = Path(os.environ.get('ZDOTDIR') or home) - if not shell_home.is_absolute(): - raise ValueError('ZDOTDIR must be absolute') - binaries = home / '.local/bin' - helpers = home / 'Library/Application Support/kiro-cli/shell' - protected = [shell_home / '.zshrc', shell_home / '.zprofile', *[helpers / name for name in HELPERS]] - for directory in (shell_home, binaries, helpers): - protected.extend([directory, *[parent for parent in directory.parents if parent != home and home in parent.parents]]) - for path in protected: - if path.is_symlink(): - raise ValueError(f'Refusing Kiro integration through symlink: {path}') - links = [] - for name in COMMANDS: - target = binaries / name - if runnable(target): - continue - # Only repair broken links to the vendor app; preserve unknown user commands. - if target.exists() or (target.is_symlink() and not str(target.resolve()).endswith( - '/Kiro CLI.app/Contents/MacOS/' + name)): - raise ValueError(f'Existing command is broken; repair it with its original manager: {name}') - candidate = shutil.which(name) - source = Path(candidate) if runnable(candidate) else app / 'Contents/MacOS' / name - if not runnable(source): - raise ValueError(f'Kiro CLI application is missing a runnable {name}') - links.append((target, source)) - if verify and links: - raise ValueError('Kiro CLI links are missing or broken; rerun setup') - selected_cli = next((source for target, source in links if target.name == 'kiro-cli'), binaries / 'kiro-cli') - check_kiro_home_support(selected_cli, home) - if links: - binaries.mkdir(parents=True, exist_ok=True) - for target, source in links: - if target.is_symlink(): - target.unlink() - target.symlink_to(source) - cli = binaries / 'kiro-cli' - os.environ['PATH'] = str(binaries) + os.pathsep + os.environ.get('PATH', '') - preference_changed = inline_preference(cli, home, verify) - verify_generated_hooks(cli) - installed = integration_status(cli) and helpers_healthy(helpers) - if verify and not installed: - raise ValueError('Kiro Zsh integration is incomplete; rerun setup') - if not installed: - # Native installer backs up changed dotfiles and keeps pre/post blocks at the edges. - subprocess.run([str(cli), 'integrations', 'install', '--silent', 'dotfiles', 'zsh'], - pass_fds=(9,) if os.environ.get('ONE_BITE_LOCKED') == '1' else (), check=True, timeout=60) - if not integration_status(cli) or not helpers_healthy(helpers): - raise ValueError('Kiro Zsh loader contract changed or installation is incomplete; update setup') - action = 'checked' if verify else ('installed-or-updated' if links or not installed or preference_changed else 'skipped') - print(action) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--app', type=Path, required=True) - parser.add_argument('--verify', action='store_true') - args = parser.parse_args() - configure(Path.home(), args.app, args.verify) - - -if __name__ == '__main__': - main() diff --git a/scripts/manual.py b/scripts/manual.py index 8b7cca9..b26ed2a 100644 --- a/scripts/manual.py +++ b/scripts/manual.py @@ -16,8 +16,6 @@ 'Open Chrome from Applications; sign in if wanted and choose the default browser yourself.'), 'chatgpt': ('ChatGPT / Codex desktop app', 'ChatGPT.app or Codex.app from the mounted image', 'Open the installed app from Applications, sign in, and approve permissions as needed.'), - 'kiro': ('Kiro IDE', 'Kiro.app', - 'Open Kiro from Applications and complete sign-in and first-use setup. Initialize the IDE and Kiro CLI separately.'), 'claude-desktop': ('Claude Desktop', 'Claude.app', 'Open Claude from Applications and sign in. Complete desktop and Claude Code CLI sign-in separately.'), } @@ -51,13 +49,13 @@ def instructions(run_dir, config_dir, home=None, root=ROOT): docker = result.get('with_docker') is True pending = list(dict.fromkeys(name for name in result.get('manual_steps', []) if name in (*APPS, 'doubao-input-installer', 'sogou-installer', - 'kiro-cli-onboarding', 'powerlevel10k-configure') + 'powerlevel10k-configure') and (name != 'claude-desktop' or claude) and (name != 'docker-desktop' or docker))) rows = read_json(run_dir / 'desktop-installers.json', []) packages = {row.get('component'): row for row in rows if isinstance(row, dict)} if isinstance(rows, list) else {} lines = [] - downloads = [name for name in pending if name not in ('kiro-cli-onboarding', 'powerlevel10k-configure')] + downloads = [name for name in pending if name != 'powerlevel10k-configure'] if downloads: lines += ['[MANUAL] These packages are ready, but you must still install them:', 'A completed download does not mean the application is installed. Complete each item below; the commands are shown for you to copy and are not run automatically.', @@ -120,10 +118,6 @@ def instructions(run_dir, config_dir, home=None, root=ROOT): lines += ['- Claude Code CLI: run claude in a new terminal and complete sign-in.'] if docker and 'docker-desktop' not in downloads: lines += ['- Docker Desktop: run open -a Docker, complete first-run setup, and wait for the engine to start.'] - if complete or 'kiro-cli-onboarding' in pending: - lines += ['- Kiro CLI: run kiro-cli launch in a new terminal and complete the vendor sign-in and shell integration.', - ' If the command is not found, run open -a "Kiro CLI" to finish onboarding, then open a new terminal and retry.', - ' Run kiro-cli doctor --all for diagnostics. Approve only the system permissions you need.'] if complete: command = ['/bin/bash', str(root / '1bite'), '--verify'] if claude: diff --git a/scripts/official_ai.py b/scripts/official_ai.py index 8007f08..eb299bb 100644 --- a/scripts/official_ai.py +++ b/scripts/official_ai.py @@ -12,15 +12,13 @@ import subprocess import sys import tempfile -from urllib.parse import quote, urlsplit +from urllib.parse import urlsplit import xml.etree.ElementTree as ET ROOT = Path(__file__).resolve().parents[1] APPS = { 'claude-desktop': ('Claude.app', 'com.anthropic.claudefordesktop', ('Claude.app',)), 'chatgpt': ('ChatGPT.app', 'com.openai.codex', ('ChatGPT.app', 'Codex.app')), - 'kiro': ('Kiro.app', 'dev.kiro.desktop', ('Kiro.app',)), - 'kiro-cli': ('Kiro CLI.app', 'com.amazon.codewhisperer', ('Kiro CLI.app',)), } SPARKLE = '{http://www.andymatuschak.org/xml-namespaces/sparkle}' APPLICATIONS = Path('/Applications') @@ -85,40 +83,6 @@ def release(name, catalog=None, fetch=fetch_text): raise ValueError('No stable Apple Silicon ChatGPT release in official feed') _, _, version, build = max(entries) return dict(version=version, build=build, url=catalog['chatgpt-download']) - if name == 'kiro': - data = json.loads(fetch(catalog['kiro-releases'])) - if not isinstance(data, dict): - raise ValueError('Unexpected Kiro stable release metadata') - version = data.get('currentRelease') - releases = data.get('releases') - if (not isinstance(version, str) or not re.fullmatch(r'\d+\.\d+\.\d+', version) - or not isinstance(releases, list) or not all(isinstance(item, dict) for item in releases)): - raise ValueError('Unexpected Kiro stable release metadata') - version_key(version) - entries = [item['updateTo'] for item in releases - if item.get('version') == version and isinstance(item.get('updateTo'), dict)] - if len(entries) != 1 or entries[0].get('version') != version: - raise ValueError('Unexpected Kiro stable release metadata') - prefix = catalog['kiro-distribution'] - url = official_url(entries[0].get('url', ''), prefix) - expected = f'releases/stable/darwin-arm64/signed/{version}/kiro-ide-{version}-stable-darwin-arm64.dmg' - if url[len(prefix):] != expected: - raise ValueError('Unexpected Kiro stable Apple Silicon DMG') - return dict(version=version, url=url) - if name == 'kiro-cli': - data = json.loads(fetch(catalog['kiro-cli-releases'])) - version = data['version'] - version_key(version) - entries = [item for item in data['packages'] if item.get('os') == 'macos' - and item.get('architecture') in ('universal', 'aarch64', 'arm64') - and item.get('fileType') == 'dmg' and item.get('channel') == 'stable'] - if len(entries) != 1 or entries[0]['download'] != version + '/Kiro CLI.dmg': - raise ValueError('Unexpected Kiro CLI stable manifest') - digest = entries[0]['sha256'] - if not re.fullmatch('[a-f0-9]{64}', digest): - raise ValueError('Missing official Kiro CLI SHA-256') - url = catalog['kiro-cli-distribution'] + quote(entries[0]['download']) - return dict(version=version, url=official_url(url, catalog['kiro-cli-distribution']), sha256=digest) if name == 'claude': version = fetch(catalog['claude-releases']).strip() elif name == 'codex': @@ -242,10 +206,6 @@ def verify_bundle(name, app): version_key(info.get('CFBundleShortVersionString')) verify_signature(name, app) subprocess.run(['lipo', str(binary), '-verify_arch', 'arm64'], check=True) - if name == 'kiro-cli': - for binary_name in ('kiro-cli', 'kiro-cli-chat', 'kiro-cli-term'): - subprocess.run([str(app / 'Contents/MacOS' / binary_name), '--version'], check=True, - stdout=subprocess.DEVNULL, timeout=30) return info diff --git a/scripts/verify.sh b/scripts/verify.sh index 420c71c..70d44d5 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -50,17 +50,6 @@ if [[ "$WITH_CLAUDE" == true ]]; then claude --version; fi codex --version ob --help >/dev/null obn --help >/dev/null -if agent_healthy kiro-cli; then - kiro-cli --version - zsh -lic 'command -v kiro-cli' -elif [[ "${ONE_BITE_ALLOW_PREPARED_DESKTOPS:-}" == 1 ]]; then - echo 'Kiro CLI app is installed; complete official onboarding to expose its terminal commands.' -else - fail 'Complete Kiro CLI official onboarding, then rerun verification.' -fi -if [[ "$DESKTOP_MODE" == managed ]]; then - python3 "$ROOT/scripts/kiro-shell.py" --verify --app "$(app_path 'Kiro CLI.app')" -fi if [[ "$WITH_DOCKER" == true ]] && app_healthy Docker.app; then docker --version /Applications/Docker.app/Contents/Resources/cli-plugins/docker-compose version diff --git a/tests/fixtures/config-golden.json b/tests/fixtures/config-golden.json index e84bada..bc1b3d4 100644 --- a/tests/fixtures/config-golden.json +++ b/tests/fixtures/config-golden.json @@ -1,5 +1,5 @@ { - "casks.tsv": "docker-desktop\tDocker.app\niterm2\tiTerm.app\nvisual-studio-code\tVisual Studio Code.app\nobsidian\tObsidian.app\ngoogle-chrome\tGoogle Chrome.app\nchatgpt\tChatGPT.app\nkiro\tKiro.app\nkiro-cli\tKiro CLI.app\nclaude-desktop\tClaude.app\n", + "casks.tsv": "docker-desktop\tDocker.app\niterm2\tiTerm.app\nvisual-studio-code\tVisual Studio Code.app\nobsidian\tObsidian.app\ngoogle-chrome\tGoogle Chrome.app\nchatgpt\tChatGPT.app\nclaude-desktop\tClaude.app\n", "claude-settings.json": "{\n \"permissions\": {\n \"defaultMode\": \"default\"\n },\n \"language\": \"Chinese\"\n}\n", "codex-config.toml": "# Use the current official model and authentication defaults.\napproval_policy = \"on-request\"\nsandbox_mode = \"workspace-write\"\nweb_search = \"cached\"\n", "download.curlrc": "# Public transport policy; credentials and proxy settings do not belong here.\nhttp1.1\nconnect-timeout = 20\nmax-time = 1800\nspeed-limit = 1\nspeed-time = 60\nretry-all-errors\nretry-max-time = 1800\n", @@ -9,7 +9,6 @@ "git-defaults.json": "{\n \"alias.a\": \"add\",\n \"alias.aa\": \"add --all\",\n \"alias.ap\": \"add --patch\",\n \"alias.b\": \"branch\",\n \"alias.ba\": \"branch --all\",\n \"alias.bc\": \"!git symbolic-ref --quiet --short HEAD\",\n \"alias.bd\": \"branch --delete\",\n \"alias.c\": \"clone\",\n \"alias.ci\": \"commit\",\n \"alias.cia\": \"commit --all\",\n \"alias.ciaam\": \"commit --amend --all --message\",\n \"alias.ciam\": \"commit --all --message\",\n \"alias.cim\": \"commit --message\",\n \"alias.cl\": \"clone\",\n \"alias.co\": \"checkout\",\n \"alias.cob\": \"checkout -b\",\n \"alias.cof\": \"checkout --\",\n \"alias.d\": \"diff\",\n \"alias.ds\": \"diff --staged\",\n \"alias.dsw\": \"diff --staged --ignore-all-space\",\n \"alias.dw\": \"diff --ignore-all-space\",\n \"alias.f\": \"fetch\",\n \"alias.h\": \"help\",\n \"alias.i\": \"init\",\n \"alias.l\": \"log\",\n \"alias.la\": \"log --date=short --pretty=format:%h%x20%ad%x20-%x20[%an]%x20%s --graph\",\n \"alias.last\": \"log -1 HEAD --stat\",\n \"alias.lg\": \"log --graph --decorate --oneline --all\",\n \"alias.lr\": \"log --walk-reflogs\",\n \"alias.ls\": \"ls-files\",\n \"alias.lsg\": \"ls-files --others --ignored --exclude-standard\",\n \"alias.mg\": \"merge\",\n \"alias.mgn\": \"merge --no-ff\",\n \"alias.mgnm\": \"merge --no-ff --message\",\n \"alias.mgs\": \"merge --squash\",\n \"alias.mgt\": \"mergetool\",\n \"alias.pl\": \"pull\",\n \"alias.plo\": \"pull origin\",\n \"alias.ps\": \"push\",\n \"alias.ps1\": \"!f() { branch=$(git symbolic-ref --quiet --short HEAD) || exit; git push --set-upstream origin \\\"$branch\\\" \\\"$@\\\"; }; f\",\n \"alias.psa\": \"push --recurse-submodules=on-demand\",\n \"alias.psaa\": \"push --all\",\n \"alias.pso\": \"push origin\",\n \"alias.pst\": \"push --tags\",\n \"alias.rb\": \"rebase --committer-date-is-author-date\",\n \"alias.rba\": \"rebase --abort\",\n \"alias.rbc\": \"rebase --continue\",\n \"alias.rbi\": \"rebase --interactive --committer-date-is-author-date\",\n \"alias.rbs\": \"rebase --skip\",\n \"alias.rl\": \"reflog\",\n \"alias.rs\": \"reset\",\n \"alias.rsh\": \"reset --hard\",\n \"alias.rss\": \"reset --soft\",\n \"alias.rt\": \"remote\",\n \"alias.rtpo\": \"remote prune origin\",\n \"alias.s\": \"status --short\",\n \"alias.sh\": \"stash\",\n \"alias.size\": \"count-objects -vH\",\n \"alias.sm\": \"submodule\",\n \"alias.so\": \"show\",\n \"alias.sr\": \"remote show origin\",\n \"alias.st\": \"status --short --branch\",\n \"alias.stat\": \"!f() { echo '--- Branch ---'; git branch --show-current; echo; git status --short --branch; echo; echo '--- Changes (lines) ---'; git diff --stat HEAD; }; f\",\n \"alias.sts\": \"!f() { echo '=== Staged ==='; git diff --cached --numstat; echo; echo '=== Unstaged ==='; git diff --numstat; echo; echo '=== Untracked ==='; git ls-files --others --exclude-standard; }; f\",\n \"alias.sw\": \"switch\",\n \"alias.swc\": \"switch --create\",\n \"alias.t\": \"tag\",\n \"alias.ta\": \"tag --annotate\",\n \"alias.tig\": \"!tig\",\n \"alias.unstage\": \"restore --staged\",\n \"alias.v\": \"!git version\",\n \"branch.sort\": \"-committerdate\",\n \"color.ui\": \"auto\",\n \"commit.verbose\": \"true\",\n \"core.autocrlf\": \"input\",\n \"core.editor\": \"code --wait\",\n \"core.excludesFile\": \"~/.config/1bite/gitignore-global\",\n \"core.pager\": \"delta\",\n \"delta.line-numbers\": \"true\",\n \"delta.navigate\": \"true\",\n \"delta.side-by-side\": \"false\",\n \"diff.algorithm\": \"histogram\",\n \"diff.colorMoved\": \"default\",\n \"diff.tool\": \"vscode\",\n \"difftool.prompt\": \"false\",\n \"difftool.vscode.cmd\": \"code --wait --diff \\\"$LOCAL\\\" \\\"$REMOTE\\\"\",\n \"fetch.prune\": \"true\",\n \"help.autocorrect\": \"prompt\",\n \"init.defaultBranch\": \"main\",\n \"interactive.diffFilter\": \"delta --color-only\",\n \"merge.conflictStyle\": \"zdiff3\",\n \"merge.tool\": \"vscode\",\n \"mergetool.keepBackup\": \"false\",\n \"mergetool.vscode.cmd\": \"code --wait \\\"$MERGED\\\"\",\n \"pull.rebase\": \"true\",\n \"push.autoSetupRemote\": \"true\",\n \"push.default\": \"current\",\n \"push.followTags\": \"true\",\n \"rebase.autoStash\": \"true\",\n \"rerere.enabled\": \"true\",\n \"tag.sort\": \"-version:refname\"\n}\n", "gitignore-global": "# macOS and Windows metadata\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\n# Local logs and generated coverage reports\n*.log\ncoverage.out\ncoverage.txt\ncoverage.html\n", "iterm2-profile.json": "{\n \"Profiles\": [\n {\n \"Name\": \"One Bite\",\n \"Guid\": \"84911aa0-4114-494c-a17c-6a2ef38b5b34\",\n \"Custom Command\": \"Yes\",\n \"Command\": \"/bin/zsh -l\",\n \"Option Key Sends\": 2,\n \"Right Option Key Sends\": 2,\n \"Keyboard Map\": {\n \"0xf702-0x240000\": {\n \"Action\": 10,\n \"Text\": \"b\"\n },\n \"0xf703-0x240000\": {\n \"Action\": 10,\n \"Text\": \"f\"\n },\n \"0xf702-0x280000\": {\n \"Action\": 10,\n \"Text\": \"b\"\n },\n \"0xf703-0x280000\": {\n \"Action\": 10,\n \"Text\": \"f\"\n },\n \"0xf702-0x300000\": {\n \"Action\": 11,\n \"Text\": \"0x01\"\n },\n \"0xf703-0x300000\": {\n \"Action\": 11,\n \"Text\": \"0x05\"\n },\n \"0xf729-0x0\": {\n \"Action\": 11,\n \"Text\": \"0x01\"\n },\n \"0xf72b-0x0\": {\n \"Action\": 11,\n \"Text\": \"0x05\"\n },\n \"0x7f-0x80000\": {\n \"Action\": 11,\n \"Text\": \"0x17\"\n }\n },\n \"Background Color\": {\n \"Red Component\": 0.12549019607843137,\n \"Green Component\": 0.1411764705882353,\n \"Blue Component\": 0.16862745098039217,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Foreground Color\": {\n \"Red Component\": 0.8470588235294118,\n \"Green Component\": 0.8705882352941177,\n \"Blue Component\": 0.9137254901960784,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Bold Color\": {\n \"Red Component\": 0.9254901960784314,\n \"Green Component\": 0.9372549019607843,\n \"Blue Component\": 0.9568627450980393,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Cursor Color\": {\n \"Red Component\": 0.5333333333333333,\n \"Green Component\": 0.7529411764705882,\n \"Blue Component\": 0.8156862745098039,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Cursor Text Color\": {\n \"Red Component\": 0.12549019607843137,\n \"Green Component\": 0.1411764705882353,\n \"Blue Component\": 0.16862745098039217,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Selection Color\": {\n \"Red Component\": 0.2627450980392157,\n \"Green Component\": 0.2980392156862745,\n \"Blue Component\": 0.3686274509803922,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Selected Text Color\": {\n \"Red Component\": 0.9254901960784314,\n \"Green Component\": 0.9372549019607843,\n \"Blue Component\": 0.9568627450980393,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 0 Color\": {\n \"Red Component\": 0.1803921568627451,\n \"Green Component\": 0.20392156862745098,\n \"Blue Component\": 0.25098039215686274,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 1 Color\": {\n \"Red Component\": 0.7490196078431373,\n \"Green Component\": 0.3803921568627451,\n \"Blue Component\": 0.41568627450980394,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 2 Color\": {\n \"Red Component\": 0.6392156862745098,\n \"Green Component\": 0.7450980392156863,\n \"Blue Component\": 0.5490196078431373,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 3 Color\": {\n \"Red Component\": 0.9215686274509803,\n \"Green Component\": 0.796078431372549,\n \"Blue Component\": 0.5450980392156862,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 4 Color\": {\n \"Red Component\": 0.5058823529411764,\n \"Green Component\": 0.6313725490196078,\n \"Blue Component\": 0.7568627450980392,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 5 Color\": {\n \"Red Component\": 0.7058823529411765,\n \"Green Component\": 0.5568627450980392,\n \"Blue Component\": 0.6784313725490196,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 6 Color\": {\n \"Red Component\": 0.5333333333333333,\n \"Green Component\": 0.7529411764705882,\n \"Blue Component\": 0.8156862745098039,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 7 Color\": {\n \"Red Component\": 0.8470588235294118,\n \"Green Component\": 0.8705882352941177,\n \"Blue Component\": 0.9137254901960784,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 8 Color\": {\n \"Red Component\": 0.2980392156862745,\n \"Green Component\": 0.33725490196078434,\n \"Blue Component\": 0.41568627450980394,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 9 Color\": {\n \"Red Component\": 0.8156862745098039,\n \"Green Component\": 0.5294117647058824,\n \"Blue Component\": 0.4392156862745098,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 10 Color\": {\n \"Red Component\": 0.6392156862745098,\n \"Green Component\": 0.7450980392156863,\n \"Blue Component\": 0.5490196078431373,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 11 Color\": {\n \"Red Component\": 0.9215686274509803,\n \"Green Component\": 0.796078431372549,\n \"Blue Component\": 0.5450980392156862,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 12 Color\": {\n \"Red Component\": 0.5058823529411764,\n \"Green Component\": 0.6313725490196078,\n \"Blue Component\": 0.7568627450980392,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 13 Color\": {\n \"Red Component\": 0.7058823529411765,\n \"Green Component\": 0.5568627450980392,\n \"Blue Component\": 0.6784313725490196,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 14 Color\": {\n \"Red Component\": 0.5607843137254902,\n \"Green Component\": 0.7372549019607844,\n \"Blue Component\": 0.7333333333333333,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Ansi 15 Color\": {\n \"Red Component\": 0.9254901960784314,\n \"Green Component\": 0.9372549019607843,\n \"Blue Component\": 0.9568627450980393,\n \"Alpha Component\": 1.0,\n \"Color Space\": \"sRGB\"\n },\n \"Normal Font\": \"MesloLGSNF-Regular 13\",\n \"Non Ascii Font\": \"MesloLGSNF-Regular 13\",\n \"Use Non-ASCII Font\": false,\n \"Transparency\": 0.0,\n \"Blur\": false,\n \"Unlimited Scrollback\": true,\n \"Scrollback Lines\": 0,\n \"Mouse Reporting\": true,\n \"Terminal Type\": \"xterm-256color\"\n }\n ]\n}\n", - "kiro-permissions.json": "{\n \"rules\": [\n {\n \"capability\": \"all\",\n \"effect\": \"ask\"\n }\n ]\n}\n", "obsidian-vault/.gitignore": ".DS_Store\n.obsidian/workspace.json\n.obsidian/workspaces.json\n.trash/\n", "obsidian-vault/.obsidian/app.json": "{\n \"showLineNumber\": true,\n \"strictLineBreaks\": false,\n \"newFileLocation\": \"current\",\n \"attachmentFolderPath\": \"_attachments\",\n \"alwaysUpdateLinks\": true\n}\n", "obsidian-vault/.obsidian/appearance.json": "{\n \"theme\": \"obsidian\",\n \"cssTheme\": \"Tokyo Night\",\n \"enabledCssSnippets\": [\n \"wide\"\n ]\n}\n", @@ -22,9 +21,9 @@ "obsidian-vault/.obsidian/themes/Tokyo Night/theme.css": "/*Fonts*/\n:root {\n --default-font: '\"JetBrains Mono\", monospace, \"Inter\", sans-serif';\n --font-monospace: '\"JetBrains Mono\", monospace, \"Source Code Pro\", monospace';\n}\n\n.theme-dark {\n color-scheme: dark;\n /* --accent-h: 261; /* magenta */\n /* --accent-s: 85%; */\n /* --accent-l: 79%; */\n /* cyan */\n --accent-h: 202;\n --accent-s: 100%;\n --accent-l: 75%;\n\n --bg_dark2_x: 18, 18, 24;\n --bg_dark2: rgb(var(--bg_dark2_x));\n --bg_dark_x: 22, 22, 30;\n --bg_dark: rgb(var(--bg_dark_x));\n --bg_x: 26, 27, 38;\n --bg: rgb(var(--bg_x));\n --bg_highlight_x: 41, 46, 66;\n --bg_highlight: rgb(var(--bg_highlight_x));\n --bg_highlight_dark_x: 36, 40, 59;\n --bg_highlight_dark: rgb(var(--bg_highlight_dark_x));\n --terminal_black_x: 65, 72, 104;\n --terminal_black: rgb(var(--terminal_black_x));\n --fg_x: 192, 202, 245;\n --fg: rgb(var(--fg_x));\n --fg_dark_x: 169, 177, 214;\n --fg_dark: rgb(var(--fg_dark_x));\n --comment_x: 86, 95, 137;\n --comment: rgb(var(--comment_x));\n --blue0_x: 61, 89, 161;\n --blue0: rgb(var(--blue0_x));\n --blue_x: 122, 162, 247;\n --blue: rgb(var(--blue_x));\n --cyan_hsl: 202 100% 75%;\n --cyan_x: 125, 207, 255;\n --cyan: rgb(var(--cyan_x));\n --magent_hsl: 261 85% 79%;\n --magenta_x: 187, 154, 247;\n --magenta: rgb(var(--magenta_x));\n --pink_hsl: 331 100% 43%;\n --pink_x: rgb(217, 0, 105);\n --pink: var(--pink_x);\n --orange_x: 255, 158, 100;\n --orange: rgb(var(--orange_x));\n --yellow_x: 224, 175, 104;\n --yellow: rgb(var(--yellow_x));\n --green_x: 158, 206, 106;\n --green: rgb(var(--green_x));\n --teal_x: 26, 188, 156;\n --teal: rgb(var(--teal_x));\n --red_x: 255, 117, 127;\n --red: rgb(var(--red_x));\n --red1_x: 219, 75, 75;\n --red1: rgb(var(--red1_x));\n --unknown: #ffffff;\n\n --link-external-filter: invert(35%) sepia(28%) saturate(681%) hue-rotate(192deg) brightness(94%) contrast(85%);\n}\n\n.theme-light {\n /* --accent-h: 261; /* magenta */\n /* --accent-s: 24%; */\n /* --accent-l: 38%; */\n /* cyan */\n --accent-h: 202;\n --accent-s: 86%;\n --accent-l: 43%;\n\n --bg_dark2_x: 188, 189, 194;\n --bg_dark2: rgb(var(--bg_dark2_x));\n --bg_dark_x: 203, 204, 209;\n --bg_dark: rgb(var(--bg_dark_x));\n --bg_x: 213, 214, 219;\n --bg: rgb(var(--bg_x));\n --bg_highlight_x: 220, 222, 226;\n --bg_highlight: rgb(var(--bg_highlight_x));\n --bg_highlight_dark_x: 195, 197, 201;\n --bg_highlight_dark: rgb(var(--bg_highlight_dark_x));\n --terminal_black_x: 15, 15, 20;\n --terminal_black: rgb(var(--terminal_black_x));\n --fg_x: 52, 59, 88;\n --fg: rgb(var(--fg_x));\n --fg_dark_x: 39, 46, 75;\n --fg_dark: rgb(var(--fg_dark_x));\n --comment_x: 150, 153, 163;\n --comment: rgb(var(--comment_x));\n --blue0_x: 39, 71, 125;\n --blue0: rgb(var(--blue0_x));\n --blue_x: 52, 84, 138;\n --blue: rgb(var(--blue_x));\n --cyan_x: 15, 75, 110;\n --cyan: rgb(var(--cyan_x));\n --magent_hsl: 261 24% 38%;\n --magenta_x: 90, 74, 120;\n --magenta: rgb(var(--magenta_x));\n --pink_hsl: 286 24% 38%;\n --pink_x: rgb(109, 74, 120);\n --pink: var(--pink_x);\n --orange_x: 150, 80, 39;\n --orange: rgb(var(--orange_x));\n --yellow_x: 143, 94, 21;\n --yellow: rgb(var(--yellow_x));\n --green_x: 51, 99, 92;\n --green: rgb(var(--green_x));\n --teal_x: 22, 103, 117;\n --teal: rgb(var(--teal_x));\n --red_x: 140, 67, 81;\n --red: rgb(var(--red_x));\n --red1_x: 115, 42, 56;\n --red1: rgb(var(--red1_x));\n --unknown: #000000;\n\n --link-external-filter: invert(62%) sepia(9%) saturate(266%) hue-rotate(189deg) brightness(97%) contrast(88%);\n}\n\n.theme-dark,\n.theme-light {\n --color_red_rgb: var(--red_x);\n --color-red: var(--red);\n --color_purple_rgb: var(--magenta_x);\n --color-purple: var(--magenta);\n --color_green_rgb: var(--green_x);\n --color-green: var(--green);\n --color_cyan_rgb: var(--cyan_x);\n --color-cyan: var(--cyan);\n --color_blue_rgb: var(--blue_x);\n --color-blue: var(--blue);\n --color_yellow_rgb: var(--yellow_x);\n --color-yellow: var(--yellow);\n --color_orange_rgb: var(--orange_x);\n --color-orange: var(--orange);\n --color_pink_rgb: var(--magenta_x);\n --color-pink: var(--magenta);\n\n --background-primary: var(--bg);\n --background-primary-alt: var(--bg);\n --background-secondary: var(--bg_dark);\n --background-secondary-alt: var(--bg_dark);\n\n --background-modifier-border: var(--bg_highlight);\n --background-modifier-border-focus: var(--bg_highlight);\n --background-modifier-border-hover: var(--bg_highlight);\n --background-modifier-form-field: var(--bg_dark);\n --background-modifier-form-field-highlighted: var(--bg_dark);\n --background-modifier-box-shadow: rgba(0, 0, 0, 0.3);\n --background-modifier-success: var(--green);\n --background-modifier-error: var(--red1);\n --background-modifier-error-hover: var(--red);\n --background-modifier-cover: rgba(var(--bg_dark_x), 0.8);\n --background-modifier-hover: var(--bg_highlight);\n --background-modifier-message: rgba(var(--bg_highlight_x), 0.9);\n --background-modifier-active-hover: var(--bg_highlight);\n\n --text-normal: var(--fg);\n --text-faint: var(--comment);\n --text-muted: var(--fg_dark);\n --text-error: var(--red);\n --text-accent: var(--magenta);\n --text-accent-hover: var(--cyan);\n --text-error: var(--red1);\n --text-error-hover: var(--red);\n --text-selection: var(--unknown);\n --text-on-accent: var(--bg);\n --text-highlight-bg: rgba(var(--orange_x), 0.25);\n --text-selection: rgba(var(--blue0_x), 0.6);\n --bold-color: var(--cyan);\n --italic-color: var(--cyan);\n\n --interactive-normal: var(--bg_dark);\n --interactive-hover: var(--bg);\n --interactive-success: var(--green);\n --interactive-accent: hsl(var(--accent-h), var(--accent-s), var(--accent-l));\n --interactive-accent-hover: var(--blue);\n\n --scrollbar-bg: var(--bg_dark2);\n --scrollbar-thumb-bg: var(--comment);\n --scrollbar-active-thumb-bg: var(--comment);\n --scrollbar-width: 7px;\n\n --h1-color: var(--red);\n --h2-color: var(--yellow);\n --h3-color: var(--green);\n --h4-color: var(--cyan);\n --h5-color: var(--blue);\n --h6-color: var(--magenta);\n\n --border-width: 2px;\n\n --tag-color: var(--magenta);\n --tag-background: rgba(var(--magenta_x), 0.15);\n --tag-color-hover: var(--cyan);\n --tag-background-hover: rgba(var(--cyan_x), 0.15);\n\n --link-color: var(--magenta);\n --link-color-hover: var(--cyan);\n --link-external-color: var(--magenta);\n --link-external-color-hover: var(--cyan);\n\n --checkbox-radius: var(--radius-l);\n --checkbox-color: var(--green);\n --checkbox-color-hover: var(--green);\n --checkbox-marker-color: var(--bg);\n --checkbox-border-color: var(--comment);\n --checkbox-border-color-hover: var(--comment);\n\n --table-header-background: var(--bg_dark2);\n --table-header-background-hover: var(--bg_dark2);\n\n --flashing-background: rgba(var(--blue0_x), 0.3);\n\n --code-normal: var(--fg);\n --code-background: var(--bg_highlight_dark);\n\n --mermaid-note: var(--blue0);\n --mermaid-actor: var(--fg_dark);\n --mermaid-loopline: var(--blue);\n\n --blockquote-background-color: var(--bg_dark);\n --callout-default: var(--blue);\n --callout-info: var(--blue);\n --callout-summary: var(--cyan);\n --callout-tip: var(--cyan);\n --callout-todo: var(--cyan);\n --callout-bug: var(--red);\n --callout-error: var(--red1);\n --callout-fail: var(--red1);\n --callout-example: var(--magenta);\n --callout-important: var(--green);\n --callout-success: var(--teal);\n --callout-question: var(--yellow);\n --callout-warning: var(--orange);\n --callout-quote: var(--fg_dark);\n\n --icon-color-hover: var(--blue);\n --icon-color-focused: var(--magenta);\n --icon-color-active: var(--magenta);\n\n --nav-item-color-hover: var(--fg);\n --nav-item-background-hover: var(--bg_highlight);\n --nav-item-color-active: var(--red);\n --nav-item-background-active: var(--bg_highlight);\n --nav-file-tag: rgba(var(--yellow_x), 0.9);\n --nav-indentation-guide-color: var(--bg_highlight);\n\n --indentation-guide-color: var(--comment);\n --indentation-guide-color-active: var(--comment);\n\n --graph-line: var(--comment);\n --graph-node: var(--fg);\n --graph-node-tag: var(--orange);\n --graph-node-attachment: var(--blue);\n\n --tab-text-color-focused-active: rgba(var(--red_x), 0.8);\n --tab-text-color-focused-active-current: var(--red);\n\n --modal-border-color: var(--bg_highlight);\n --prompt-border-color: var(--bg_highlight);\n --slider-track-background: var(--bg_highlight);\n\n --embed-background: var(--bg_dark);\n --embed-padding: 1.5rem 1.5rem 0.5rem;\n\n --canvas-color: var(--bg_highlight_x);\n\n --toggle-thumb-color: var(--bg);\n\n --image-radius: 0px;\n\n --date-color: var(--blue);\n --date-background-color: var(--bg_highlight);\n}\n\n\n/* Headings (credit: https://github.com/Akifyss/obsidian-border)*/\n\nbody:not(.heading-indicator-off) .markdown-rendered>:is(h1, h2, h3, h4, h5, h6),\nbody:not(.heading-indicator-off) .markdown-preview-sizer>div>:is(h1, h2, h3, h4, h5, h6) {\n position: relative;\n}\n\nbody.h1-divider-on :is(.markdown-preview-sizer>div>h1, .markdown-rendered>h1, .HyperMD-header-1)::after {\n content: \"\";\n position: absolute;\n height: 2px;\n width: 100%;\n right: 0px;\n opacity: 0.5;\n bottom: 0;\n background-image: linear-gradient(to left, var(--h1-color) 30%, transparent 70%);\n}\n\nbody.h2-divider-on :is(.markdown-preview-sizer>div>h2, .markdown-rendered>h2, .HyperMD-header-2)::after {\n content: \"\";\n position: absolute;\n height: 2px;\n width: 100%;\n right: 0px;\n opacity: 0.5;\n bottom: 0;\n background-image: linear-gradient(to left, var(--h2-color) 30%, transparent 70%);\n}\n\nbody.h3-divider-on :is(.markdown-preview-sizer>div>h3, .markdown-rendered>h3, .HyperMD-header-3)::after {\n content: \"\";\n position: absolute;\n height: 2px;\n width: 100%;\n right: 0px;\n opacity: 0.5;\n bottom: 0;\n background-image: linear-gradient(to left, var(--h3-color) 30%, transparent 70%);\n}\n\nbody.h4-divider-on :is(.markdown-preview-sizer>div>h4, .markdown-rendered>h4, .HyperMD-header-4)::after {\n content: \"\";\n position: absolute;\n height: 2px;\n width: 100%;\n right: 0px;\n opacity: 0.5;\n bottom: 0;\n background-image: linear-gradient(to left, var(--h4-color) 30%, transparent 70%);\n}\n\nbody.h5-divider-on :is(.markdown-preview-sizer>div>h5, .markdown-rendered>h5, .HyperMD-header-5)::after {\n content: \"\";\n position: absolute;\n height: 2px;\n width: 100%;\n right: 0px;\n opacity: 0.5;\n bottom: 0;\n background-image: linear-gradient(to left, var(--h5-color) 30%, transparent 70%);\n}\n\nbody.h6-divider-on :is(.markdown-preview-sizer>div>h6, .markdown-rendered>h6, .HyperMD-header-6)::after {\n content: \"\";\n position: absolute;\n height: 2px;\n width: 100%;\n right: 0px;\n opacity: 0.5;\n bottom: 0;\n background-image: linear-gradient(to left, var(--h6-color) 30%, transparent 70%);\n}\n\n/* mobile */\n.is-mobile .suggestion-item.is-selected {\n background-color: var(--bg_highlight);\n}\n\n/* scrollbars */\n::-webkit-scrollbar {\n width: var(--scrollbar-width);\n background-color: transparent !important;\n}\n\n:hover::-webkit-scrollbar-thumb {\n background-color: var(--scrollbar-thumb-bg) !important;\n}\n\n::-webkit-scrollbar-thumb:hover,\n::-webkit-scrollbar-thumb:active {\n background-color: var(--scrollbar-active-thumb-bg) !important;\n}\n\n\n::-webkit-scrollbar:horizontal {\n height: 0;\n background-color: transparent !important;\n}\n\n/* tooltip */\n.tooltip {\n color: var(--fg);\n}\n\n/* links */\n\n.cm-url {\n filter: none !important;\n}\n\n.cm-string.cm-url.external-link {\n filter: var(--link-external-filter) !important;\n}\n\na.external-link {\n filter: none;\n background-image: unset;\n padding-right: 0;\n}\n\na.external-link::after {\n content: url(public/images/874d8b8e340f75575caa.svg);\n filter: var(--link-external-filter);\n padding-left: 2.5px;\n bottom: -1px;\n position: relative;\n}\n\n/* Images */\n\n.image-embed {\n border-radius: var(--image-radius);\n}\n\n/* tables credit: Akifyss/border */\nbody {\n --table-width: 88cqw;\n}\n\n.theme-dark,\n.theme-light .markdown-rendered thead tr {\n background: var(--table-header-background);\n}\n\nbody:not(.table-width-obsidian-default) .markdown-source-view.mod-cm6 .cm-table-widget .table-wrapper,\nbody:not(.table-width-obsidian-default) :is(.markdown-rendered, .markdown-source-view.mod-cm6.is-live-preview) table {\n width: 100% !important;\n}\n\n.table-width-customized .markdown-source-view.mod-cm6.is-live-preview .cm-content>.cm-table-widget,\n.table-width-customized .markdown-rendered .markdown-preview-sizer>div:has(>table) {\n overflow: auto;\n width: var(--table-width) !important;\n margin-left: calc((var(--table-width) - 100%) / -2) !important;\n}\n\n/* notice */\n.notice {\n background-color: var(--bg_dark);\n border: 2px solid var(--bg_highlight);\n}\n\n/* callouts */\n.callout[data-callout=\"quote\"] {\n background-color: rgba(var(--bg_highlight_dark_x), 0.5);\n}\n\n\n.callout[data-callout=\"cite\"] {\n --callout-color: var(--pink_x);\n background-color: color-mix(in srgb, var(--pink_x) 15%, transparent);\n}\n\n/* the following callouts are from Border (https://github.com/Akifyss/obsidian-border/blob/18607ee208d56e1d4faff7419efb9cf7c9cc726e/theme.css#L7710C1-L7799C2) */\n\n.callout-style-1.theme-light {\n --callout-border-width: 1px;\n --callout-border-opacity: 0.25;\n --callout-padding: 0;\n --callout-radius: var(--radius-s);\n --callout-title-color: inherit;\n --callout-title-padding: 8px 16px;\n --callout-title-size: inherit;\n --callout-content-padding: 0px 16px;\n --callout-content-background: #FFFFFFBF;\n --callout-content-radius: 0px;\n}\n\n.callout-style-1.theme-dark {\n --callout-border-width: 1px;\n --callout-border-opacity: 0.25;\n --callout-padding: 0;\n --callout-radius: var(--radius-s);\n --callout-title-color: inherit;\n --callout-title-padding: 8px 16px;\n --callout-title-size: inherit;\n --callout-content-padding: 0px 16px;\n --callout-content-background: #00000040;\n --callout-content-radius: 0px;\n}\n\n.callout-style-2.theme-light {\n --callout-border-width: 1px;\n --callout-border-opacity: 0.25;\n --callout-padding: 0 6px 6px;\n --callout-radius: var(--radius-s);\n --callout-title-color: inherit;\n --callout-title-padding: 8px 16px;\n --callout-title-size: inherit;\n --callout-content-padding: 0px 16px;\n --callout-content-background: #FFFFFFBF;\n --callout-content-radius: 4px;\n}\n\n.callout-style-2.theme-dark {\n --callout-border-width: 1px;\n --callout-border-opacity: 0.25;\n --callout-padding: 0 6px 6px;\n --callout-radius: var(--radius-s);\n --callout-title-color: inherit;\n --callout-title-padding: 8px 16px;\n --callout-title-size: inherit;\n --callout-content-padding: 0px 16px;\n --callout-content-background: #00000040;\n --callout-content-radius: 4px;\n}\n\n.callout-style-3:is(.theme-light, .theme-dark) {\n --callout-border-width: 0 0 0 4px;\n --callout-border-opacity: 1;\n --callout-padding: var(--size-4-3) var(--size-4-3) var(--size-4-3) var(--size-4-6);\n --callout-radius: var(--radius-s);\n --callout-title-color: inherit;\n --callout-title-padding: 0;\n --callout-title-size: inherit;\n --callout-content-padding: 0;\n --callout-content-background: transparent;\n --callout-content-radius: 0px;\n}\n\n.callout-style-4.theme-light {\n --callout-border-width: 0 0 0 4px;\n --callout-border-opacity: 1;\n --callout-padding: 0;\n --callout-radius: var(--radius-s);\n --callout-title-color: inherit;\n --callout-title-padding: 8px 16px;\n --callout-title-size: inherit;\n --callout-content-padding: 0px 16px;\n --callout-content-background: #FFFFFFBF;\n --callout-content-radius: 0px;\n}\n\n.callout-style-4.theme-dark {\n --callout-border-width: 0 0 0 4px;\n --callout-border-opacity: 1;\n --callout-padding: 0;\n --callout-radius: var(--radius-s);\n --callout-title-color: inherit;\n --callout-title-padding: 8px 16px;\n --callout-title-size: inherit;\n --callout-content-padding: 0px 16px;\n --callout-content-background: #00000040;\n --callout-content-radius: 0px;\n}\n\n/* for vim */\n.ͼ2 .cm-panels-bottom {\n border-top: var(--border-width) solid var(--bg_highlight);\n}\n\n.cm-vim-message {\n color: var(--red);\n}\n\n/* kanban */\n.kanban-plugin__lane-action-add {\n color: var(--fg);\n}\n\n.kanban-plugin__lane-action-add:hover {\n color: var(--bg);\n}\n\n.kanban-plugin__autocomplete-item:hover,\n.kanban-plugin__autocomplete-item-active {\n color: hsl(var(--accent-h), var(--accent-s), var(--accent-l));\n background-color: var(--bg_highlight);\n}\n\n/* various-complement */\n.various-complements__suggestion-item::before {\n filter: var(--link-external-filter);\n}\n\n.various-complements__footer::before {\n filter: var(--link-external-filter);\n}\n\n/* Math Jax */\nmjx-math {\n font-size: 110% !important;\n}\n\n/* metadata */\n.cm-atom {\n color: var(--blue);\n}\n\nspan.cm-hmd-frontmatter.cm-meta {\n color: rgba(var(--blue0_x), 0.9);\n}\n\n/* checkboxes (credit: border) */\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"<\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"<\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"<\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\">\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\">\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\">\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"d\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"d\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"d\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"u\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"u\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"u\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"S\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"S\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"S\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"“\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"“\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"“\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"\\\"\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"\\\"\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"\\\"\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"c\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"c\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"c\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"p\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"p\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"p\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"n\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"n\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"n\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"b\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"b\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"b\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"-\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"-\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"-\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"l\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"l\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"l\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"I\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"I\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"I\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"/\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"/\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"/\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"i\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"i\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"i\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"*\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"*\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"*\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"?\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"?\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"?\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"!\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"!\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"!\"]>p>input:checked {\n --checkbox-marker-color: transparent;\n border: none;\n border-radius: 0;\n background-image: none;\n background-color: currentColor;\n -webkit-mask-size: var(--checkbox-icon);\n -webkit-mask-position: 50% 50%\n}\n\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"!\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"!\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"!\"]>p>input:checked {\n --checkbox-color-hover: var(--color-orange);\n color: var(--color-orange);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"?\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"?\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"?\"]>p>input:checked {\n --checkbox-color-hover: var(--color-pink);\n color: var(--color-pink);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"*\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"*\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"*\"]>p>input:checked {\n --checkbox-color-hover: var(--color-yellow);\n color: var(--color-yellow);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"i\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"i\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"i\"]>p>input:checked {\n --checkbox-color-hover: var(--color-cyan);\n color: var(--color-cyan);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"/\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"/\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"/\"]>p>input:checked {\n --checkbox-color-hover: var(--color-yellow);\n color: var(--color-yellow);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"I\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"I\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"I\"]>p>input:checked {\n --checkbox-color-hover: var(--color-orange);\n color: var(--color-orange);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"l\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"l\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"l\"]>p>input:checked {\n --checkbox-color-hover: var(--color-red);\n color: var(--color-red);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"-\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"-\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"-\"]>p>input:checked {\n --checkbox-color-hover: var(--text-faint);\n color: var(--text-faint);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) body:not(.tasks) .markdown-preview-view ul li[data-task=\"x\"].task-list-item.is-checked,\nbody:not(.disable-alternative-checkboxes) body:not(.tasks) .markdown-source-view.mod-cm6 .HyperMD-task-line[data-task]:is([data-task=\"x\"]),\nbody:not(.disable-alternative-checkboxes) body:not(.tasks) li[data-task=\"x\"].task-list-item.is-checked,\nbody:not(.disable-alternative-checkboxes) body:not(.tasks) .markdown-preview-view ul li[data-task=\"-\"].task-list-item.is-checked,\nbody:not(.disable-alternative-checkboxes) body:not(.tasks) .markdown-source-view.mod-cm6 .HyperMD-task-line[data-task]:is([data-task=\"-\"]),\nbody:not(.disable-alternative-checkboxes) body:not(.tasks) li[data-task=\"-\"].task-list-item.is-checked {\n color: var(--text-faint);\n text-decoration: line-through solid var(--text-faint) 1px\n}\n\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"b\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"b\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"b\"]>p>input:checked {\n --checkbox-color-hover: var(--color-blue);\n color: var(--color-blue);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"n\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"n\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"n\"]>p>input:checked {\n --checkbox-color-hover: var(--color-cyan);\n color: var(--color-cyan);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"p\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"p\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"p\"]>p>input:checked {\n --checkbox-color-hover: var(--color-green);\n color: var(--color-green);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"c\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"c\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"c\"]>p>input:checked {\n --checkbox-color-hover: var(--color-red);\n color: var(--color-red);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"“\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"“\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"“\"]>p>input:checked,\nbody:not(.disable-alternative-checkboxes) input[data-task=\"\\\"\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"\\\"\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"\\\"\"]>p>input:checked {\n --checkbox-color-hover: var(--color-purple);\n color: var(--color-purple);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"S\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"S\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"S\"]>p>input:checked {\n --checkbox-color-hover: var(--color-green);\n color: var(--color-green);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"u\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"u\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"u\"]>p>input:checked {\n --checkbox-color-hover: var(--color-green);\n color: var(--color-green);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"d\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"d\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"d\"]>p>input:checked {\n --checkbox-color-hover: var(--color-red);\n color: var(--color-red);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\">\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\">\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\">\"]>p>input:checked {\n --checkbox-color-hover: var(--color-pink);\n color: var(--color-pink);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\nbody:not(.disable-alternative-checkboxes) input[data-task=\"<\"]:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"<\"]>input:checked,\nbody:not(.disable-alternative-checkboxes) li[data-task=\"<\"]>p>input:checked {\n --checkbox-color-hover: var(--color-blue);\n color: var(--color-blue);\n -webkit-mask-image: url('data:image/svg+xml;utf8,');\n}\n\n\n/* style settings */\n\n.style-settings-container .themed-color-wrapper {\n display: flex;\n gap: var(--size-4-2);\n}\n\n.style-settings-container .themed-color-wrapper>div+div {\n margin-top: 0;\n}\n\n.style-settings-container {\n margin-left: var(--size-4-1);\n padding-left: var(--size-4-8);\n border-left: 1px solid var(--divider-color);\n}\n\n/* printing */\n\n@media print {\n .theme-light {\n --blockquote-background-color: rgba(var(--bg_dark_x), 0.25);\n --code-background: rgba(var(--bg_highlight_dark_x), 0.25);\n --table-header-background: rgba(var(--bg_dark2_x), 0.25);\n }\n}\n\n\n/* @settings\n\nname: Appearance\nid: Appearance\nsettings:\n -\n id: colors\n title: Colors\n type: heading\n level: 1\n collapsed: true\n -\n id: accent\n title: Accent Color\n type: variable-themed-color\n format: hsl-split\n opacity: false\n default-light: '#0F87CC'\n default-dark: '#80D0FF'\n -\n id: Extended-colors\n title: Extended Colors\n type: heading\n level: 2\n collapsed: true\n -\n id: red\n title: Red\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(115, 42, 56)'\n default-dark: 'rgb(219, 75, 75)'\n -\n id: red1\n title: Red1\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(140, 67, 81)'\n default-dark: 'rgb(255, 117, 127)'\n -\n id: green\n title: Green\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(51, 99, 92)'\n default-dark: 'rgb(158, 206, 106)'\n -\n id: cyan\n title: Cyan\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(15, 75, 110)'\n default-dark: 'rgb(125, 207, 255)'\n -\n id: blue\n title: Blue\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(52, 84, 138)'\n default-dark: 'rgb(122, 162, 247)'\n -\n id: yellow\n title: Yellow\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(143, 94, 21)'\n default-dark: 'rgb(224, 175, 104)'\n -\n id: orange\n title: Orange\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(150, 80, 39)'\n default-dark: 'rgb(255, 158, 100)'\n -\n id: magenta\n title: Magenta\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(90, 74, 120)'\n default-dark: 'rgb(187, 154, 247)'\n -\n id: pink\n title: Pink\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(109, 74, 120)'\n default-dark: 'rgb(217, 0, 105)'\n -\n id: Background-colors\n title: Background Colors\n type: heading\n level: 1\n collapsed: true\n -\n id: bg\n title: Background Primary\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(213, 214, 219)'\n default-dark: 'rgb(26, 27, 38)'\n -\n id: bg_dark\n title: Background Dark\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(203, 204, 209)'\n default-dark: 'rgb(22, 22, 30)'\n -\n id: bg_highlight\n title: Background Highlight\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(220, 222, 226)'\n default-dark: 'rgb(41, 46, 66)'\n -\n id: bg_highlight_dark\n title: Background Highlight Dark\n description: 'Used for code bg'\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(195, 197, 201)'\n default-dark: 'rgb(36, 40, 59)'\n -\n id: bg_dark2\n title: Background Dark2\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: 'rgb(188, 189, 194)'\n default-dark: 'rgb(15, 15, 20)'\n -\n id: foreground-header\n title: Foreground\n type: heading\n level: 2\n collapsed: true\n -\n id: text-color-heading\n title: Text / Icon Color\n type: heading\n level: 3\n collapsed: true\n -\n id: text-normal\n title: Text Normal\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-muted\n title: Text Muted\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-faint\n title: Text Faint\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-accent\n title: Text Accent\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-accent-hover\n title: Text Accent Hover\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-on-accent\n title: Text on Accent\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-on-accent-inverted\n title: Text on Accent Inverted\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-error\n title: Text Error\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-warning\n title: Text Warning\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-success\n title: Text Success\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: interactive-heading\n title: Interactive color\n type: heading\n level: 3\n collapsed: true\n -\n id: interactive-normal\n title: Interactive Normal\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: interactive-hover\n title: Interactive Hover\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: interactive-accent\n title: Interactive Accent\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: interactive-accent-hover\n title: Interactive Accent Hover\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: interactive-accent-hsl\n title: Interactive Accent HSL\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n*/\n\n/* @settings\n\nname: Editor\nid: Editor\nsettings:\n -\n id: headings\n title: Tabs\n type: heading\n level: 1\n collapsed: true\n -\n id: tab-text-color-focused-active\n title: Tab Focus Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: tab-text-color-focused-active-current\n title: Current Tab Focus Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: headings\n title: Headings\n type: heading\n level: 1\n collapsed: true\n -\n id: level-1-heading\n title: Level 1\n type: heading\n level: 2\n collapsed: true\n -\n\t\tid: h1-divider-on\n\t\ttitle: disable H1 divider\n\t\ttype: class-toggle\n\t\tdefault: false\n -\n id: h1-font\n title: H1 Font\n type: variable-text\n default: ''\n -\n id: h1-size\n title: H1 Font Size\n description: Any CSS font-size value\n type: variable-text\n default: ''\n -\n id: h1-weight\n title: H1 Font Weight\n description: Value from 100 - 900\n type: variable-text\n default: 700\n\t-\n id: h1-color\n title: H1 Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: level-2-heading\n title: Level 2\n type: heading\n level: 2\n collapsed: true\n -\n\t\tid: h2-divider-on\n\t\ttitle: disable H2 divider\n\t\ttype: class-toggle\n\t\tdefault: false\n -\n id: h2-font\n title: H2 Font\n type: variable-text\n default: ''\n -\n id: h2-size\n title: H2 Font Size\n description: Any CSS font-size value\n type: variable-text\n default: ''\n -\n id: h2-weight\n title: H2 Font Weight\n description: Value from 100 - 900\n type: variable-text\n default: 600\n\t-\n id: h2-color\n title: H2 Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: level-3-heading\n title: Level 3\n type: heading\n level: 2\n collapsed: true\n -\n\t\tid: h3-divider-on\n\t\ttitle: disable H3 divider\n\t\ttype: class-toggle\n\t\tdefault: false\n -\n id: h3-font\n title: H3 Font\n type: variable-text\n default: ''\n -\n id: h3-size\n title: H3 Font Size\n description: Any CSS font-size value\n type: variable-text\n default: ''\n -\n id: h3-weight\n title: H3 Font Weight\n description: Value from 100 - 900\n type: variable-text\n default: 600\n\t-\n id: h3-color\n title: H3 Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: level-4-heading\n title: Level 4\n type: heading\n level: 2\n collapsed: true\n -\n\t\tid: h4-divider-on\n\t\ttitle: disable H4 divider\n\t\ttype: class-toggle\n\t\tdefault: false\n -\n id: h4-font\n title: H4 Font\n type: variable-text\n default: ''\n -\n id: h4-size\n title: H4 Font Size\n description: Any CSS font-size value\n type: variable-text\n default: ''\n -\n id: h4-weight\n title: H4 Font Weight\n description: Value from 100 - 900\n type: variable-text\n default: 600\n\t-\n id: h4-color\n title: H4 Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: level-5-heading\n title: Level 5\n type: heading\n level: 2\n collapsed: true\n -\n\t\tid: h5-divider-on\n\t\ttitle: disable H5 divider\n\t\ttype: class-toggle\n\t\tdefault: false\n -\n id: h5-font\n title: H5 Font\n type: variable-text\n default: ''\n -\n id: h5-size\n title: H5 Font Size\n description: Any CSS font-size value\n type: variable-text\n default: ''\n -\n id: h5-weight\n title: H5 Font Weight\n description: Value from 100 - 900\n type: variable-text\n default: 600\n\t-\n id: h5-color\n title: H5 Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: level-6-heading\n title: Level 6\n type: heading\n level: 2\n collapsed: true\n -\n\t\tid: h6-divider-on\n\t\ttitle: disable H6 divider\n\t\ttype: class-toggle\n\t\tdefault: false\n -\n id: h6-font\n title: H6 Font\n type: variable-text\n default: ''\n -\n id: h6-size\n title: H6 Font Size\n description: Any CSS font-size value\n type: variable-text\n default: ''\n -\n id: h6-weight\n title: H6 Font Weight\n description: Value from 100 - 900\n type: variable-text\n default: 600\n\t-\n id: h6-color\n title: H6 Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: text-heading\n title: Text\n type: heading\n level: 1\n collapsed: true\n -\n id: bold-color\n title: Bold Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: italic-color\n title: Italic Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: link-heading\n title: Links\n type: heading\n level: 1\n collapsed: true\n -\n id: link-color\n title: Link Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: link-color-hover\n title: Link Hover Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: link-decoration\n\t\ttitle: Link decoration\n description: Any CSS text-decoration-line value\n\t\ttype: variable-text\n\t\tdefault: underline\n -\n id: link-decoration-hover\n title: Link Hover decoration\n description: Any CSS text-decoration-line value\n type: variable-text\n default: underline\n -\n id: link-decoration-thickness\n title: Link decoration thickness\n description: Any CSS text-decoration-thickness value\n type: variable-text\n default: auto\n -\n id: external-link-heading\n title: External Link\n type: heading\n level: 3\n collapsed: true\n -\n id: link-external-color\n title: External Link Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: link-external-color-hover\n title: External Link Hover Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: link-external-decoration\n title: External Link decoration\n description: Any CSS text-decoration-line value\n type: variable-text\n default: underline\n -\n id: link-external-decoration-hover\n title: External Link Hover decoration\n description: Any CSS text-decoration-line value\n type: variable-text\n default: underline\n -\n id: link-external-filter\n title: External Link Filter\n description: Any CSS filter value\n type: variable-text\n default: invert(35%) sepia(28%) saturate(681%) hue-rotate(192deg) brightness(94%) contrast(85%)\n -\n id: unresolved-link-heading\n title: Unresolved Link\n type: heading\n level: 3\n collapsed: true\n -\n id: link-unresolved-color\n title: Unresolved Link Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n\t\tid: link-unresolved-opacity\n\t\ttitle: Unresolved link opacity\n\t\ttype: variable-number-slider\n\t\tdefault: 0.7\n\t\tmin: 0.25\n\t\tmax: 1\n\t\tstep: 0.05\n -\n id: link-unresolved-filter\n title: Unresolved Link Filter\n description: Any CSS filter value\n type: variable-text\n default: none\n -\n id: link-unresolved-decoration\n title: Unresolved Link decoration\n description: Any CSS text-decoration-line value\n type: variable-text\n default: solid\n -\n id: link-unresolved-decoration-color\n title: Unresolved Link decoration Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: lists-heading\n title: Lists\n type: heading\n level: 2\n collapsed: true\n -\n id: list-indent\n title: List Indent\n type: variable-text\n default: 2.25em\n -\n\t\tid: list-spacing\n\t\ttitle: list spacing\n\t\ttype: variable-text\n\t\tdefault: 0.075em\n -\n\t\tid: list-bullet-size\n\t\ttitle: list marker size\n\t\ttype: variable-text\n\t\tdefault: 0.3em\n\t-\n\t\tid: list-marker-color\n\t\ttitle: list marker color\n\t\ttype: variable-themed-color\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n\t\tid: list-marker-color-hover\n\t\ttitle: list marker color (hover)\n\t\ttype: variable-themed-color\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n\t\tid: list-marker-color-collapsed\n\t\ttitle: list marker color (collapsed)\n\t\ttype: variable-themed-color\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: Tags\n title: Tags\n type: heading\n collapsed: true\n level: 2\n -\n\t\tid: tag-color\n\t\ttitle: Tag text color\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n\t-\n\t\tid: tag-background\n\t\ttitle: Tag background color\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n\t-\n\t\tid: tag-background-hover\n\t\ttitle: Tag background color (hover)\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: Dates\n title: Dates\n type: heading\n collapsed: true\n level: 2\n -\n id: date-color\n\t\ttitle: Tag text color\n\t\ttype: variable-themed-color\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n\t\tid: date-background-color\n\t\ttitle: Date background color\n\t\ttype: variable-themed-color\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: table-heading\n title: Tables\n type: heading\n level: 2\n collapsed: true\n -\n id: table-border-width\n title: Table Border Width\n type: variable-number-slider\n default: 1\n min: 0\n max: 20\n step: 1\n format: px\n -\n id: table-border-color\n title: Table Border Color\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: code-headigeng\n title: Code\n type: heading\n level: 2\n collapsed: true\n -\n id: code-background\n title: Code Background\n type: variable-themed-color\n opacity: true\n format: hex\n default-light: '#'\n default-dark: '#'\n -\n\t\tid: code-normal\n\t\ttitle: Code Normal\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n\t\tid: code-comment\n\t\ttitle: Code Comment\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-function\n\t\ttitle: Code Function\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-important\n\t\ttitle: Code Important\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-keyword\n\t\ttitle: Code Keyword\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-operator\n\t\ttitle: Code Operator\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-property\n\t\ttitle: Code Property\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-punctuation\n\t\ttitle: Code Punctuation\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-string\n\t\ttitle: Code String\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-tag\n\t\ttitle: Code Tag\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: code-value\n\t\ttitle: Code Value\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: blockquote-heading\n title: Blockquote\n type: heading\n level: 2\n collapsed: true\n -\n id: blockquote-border-thickness\n title: Blockquote Border Thickness\n type: variable-number-slider\n default: 3\n min: 0\n max: 20\n step: 1\n format: px\n -\n id: blockquote-border-color\n title: Blockquote Border Color\n type: variable-themed-color\n format: hex\n default-light: '#'\n default-dark: '#'\n -\n id: blockquote-color\n title: Blockquote Color\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: blockquote-background-color\n title: Blockquote Background Color\n type: variable-themed-color\n format: hex\n default-light: '#'\n default-dark: '#'\n -\n id: checkbox-heading\n title: Checkboxes\n type: heading\n level: 2\n collapsed: true\n -\n id: disable-alternative-checkboxes\n title: Disable Alternative Checkboxes\n description: Enable this if you are using your own implementation via a CSS Snippet.\n default: false\n type: class-toggle\n -\n id: checkbox-radius\n title: Checkbox Radius\n description: Any CSS border-radius value\n type: variable-text\n default: ''\n -\n id: checkbox-color\n title: Checkbox Color\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: checkbox-color-hover\n title: Checkbox Hover Color\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: checkbox-marker-color\n title: Checkbox Marker Color\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: checkbox-border-color\n title: Checkbox Border Color\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: checkbox-border-color-hover\n title: Checkbox Border Hover Color\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-heading\n title: Callouts\n type: heading\n level: 2\n collapsed: true\n -\n id: callout-style-select\n title: Callout Style\n type: class-select\n allowEmpty: false\n default: callout-style-customize\n options:\n - label: Customize\n value: callout-style-customize\n - label: style 1\n value: callout-style-1\n - label: style 2\n value: callout-style-2\n - label: style 3\n value: callout-style-3\n - label: style 4\n value: callout-style-4\n -\n\t\tid: callout-border-width\n\t\ttitle: Callout Border Width\n type: variable-text\n description: Any CSS border-width value\n default: 0px\n -\n id: callout-border-opacity\n title: Callout Border Opacity\n type: variable-number-slider\n default: 0.25\n min: 0\n max: 1\n step: 0.05\n -\n\t\tid: callout-padding\n\t\ttitle: Callout Padding\n description: Accepts any CSS padding value\n\t\ttype: variable-text\n\t\tdefault: ''\n -\n id: callout-radius\n title: Callout Radius\n description: Input your border-radius value here\n type: variable-text\n default: ''\n\t-\n\t\tid: callout-title-color\n\t\ttitle: Callout Title Color\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n\t\tid: callout-title-padding\n\t\ttitle: Callout Title Padding\n description: Any CSS font-size value\n\t\ttype: variable-text\n\t\tdefault: ''\n -\n\t\tid: callout-title-size\n\t\ttitle: Callout Title Size\n description: Any CSS font-size value\n\t\ttype: variable-text\n\t\tdefault: ''\n -\n\t\tid: callout-content-padding\n\t\ttitle: Callout Content Padding\n description: Any CSS font-size value\n\t\ttype: variable-text\n\t\tdefault: ''\n -\n\t\tid: callout-content-background\n\t\ttitle: Callout Content Background\n\t\ttype: variable-themed-color\n\t\topacity: true\n\t\tformat: hex\n\t\tdefault-light: '#'\n\t\tdefault-dark: '#'\n -\n id: callout-content-radius\n title: Callout Content Radius\n description: Input your border-radius value here\n type: variable-text\n default: ''\n -\n id: callout-color-heading\n title: Callout Colors\n type: heading\n level: 3\n collapsed: true\n -\n id: callout-default\n title: Default Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-info\n title: Info Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-summary\n title: Summary Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-tip\n title: Tip Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-todo\n title: Todo Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-bug\n title: Bug Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-error\n title: Error Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-fail\n title: Fail Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-example\n title: Example Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-important\n title: Important Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-success\n title: Success Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-question\n title: Question Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-warning\n title: Warning Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: callout-quote\n title: Quote Callout\n type: variable-themed-color\n format: hex\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: embed-heading\n title: Embeds\n type: heading\n level: 2\n collapsed: true\n -\n id: embed-background\n title: Embed Background\n type: variable-themed-color\n opacity: true\n format: hex\n default-light: '#'\n default-dark: '#'\n -\n id: embed-padding\n title: Embed Padding\n description: Any CSS padding value\n type: variable-text\n default: ''\n -\n id: embed-border-radius\n title: Embed Border Radius\n description: Any CSS border-radius value\n type: variable-text\n default: ''\n -\n id: embed-font-style\n title: Embed Font Style\n description: Any CSS font-style value\n type: variable-text\n default: ''\n -\n id: image-heading\n title: Images\n type: heading\n level: 2\n collapsed: true\n -\n id: image-radius\n title: Image Radius\n description: Any CSS border-radius value\n type: variable-text\n default: ''\n -\n id: scrollbar-heading\n title: Scrollbar\n type: heading\n level: 2\n collapsed: true\n -\n id: scrollbar-bg\n title: Scrollbar Background\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: scrollbar-thumb-bg\n title: Scrollbar Thumb Background\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: scrollbar-active-thumb-bg\n title: Scrollbar Active Thumb Background\n type: variable-themed-color\n format: rgb\n opacity: false\n default-light: '#'\n default-dark: '#'\n -\n id: scrollbar-width\n title: Scrollbar Width\n type: variable-number-slider\n default: 7\n min: 0\n max: 20\n step: 1\n format: px\n -\n*/\n", "obsidian-vault/_attachments/.gitkeep": "\n", "obsidian-vault/_templates/.gitkeep": "\n", - "repository-files.txt": ".gitignore\n.github/pull_request_template.md\n.github/workflows/quality.yml\n1bite\nAGENTS.md\nLICENSE\nMakefile\nREADME.md\nVERSION\napps/README.md\napps/qbittorrent/README.md\napps/qbittorrent/compose.yaml\napps/qbittorrent/qbt.py\napps/qbittorrent/qbt.sh\napps/qbittorrent/smoke-test.py\nassets/logo.png\nconfig/casks.tsv\nconfig/claude-settings.json\nconfig/codex-config.toml\nconfig/download.curlrc\nconfig/env.zsh\nconfig/font-casks.tsv\nconfig/formulae.txt\nconfig/git-defaults.json\nconfig/gitignore-global\nconfig/iterm2-profile.json\nconfig/kiro-permissions.json\nconfig/obsidian-vault/.gitignore\nconfig/obsidian-vault/.obsidian/app.json\nconfig/obsidian-vault/.obsidian/appearance.json\nconfig/obsidian-vault/.obsidian/core-plugins.json\nconfig/obsidian-vault/.obsidian/graph.json\nconfig/obsidian-vault/.obsidian/snippets/wide.css\nconfig/obsidian-vault/.obsidian/templates.json\nconfig/obsidian-vault/.obsidian/themes/Tokyo Night/LICENSE\nconfig/obsidian-vault/.obsidian/themes/Tokyo Night/manifest.json\nconfig/obsidian-vault/.obsidian/themes/Tokyo Night/theme.css\nconfig/obsidian-vault/_attachments/.gitkeep\nconfig/obsidian-vault/_templates/.gitkeep\nconfig/repository-files.txt\nconfig/shell.zsh\nconfig/sources.tsv\nconfig/vimrc\nconfig/vscode-extensions.txt\nconfig/vscode-settings.json\nconfig/zsh/development.zsh\nconfig/zsh/framework.zsh\nconfig/zsh/git-functions.zsh\nconfig/zsh/media.zsh\nconfig/zsh/options.zsh\nconfig/zsh/terminal.zsh\nconfig/zsh/tools.zsh\nconfig/zsh/utilities.zsh\ndocs/automation.md\ndocs/battery.md\ndocs/battery.schema.json\ndocs/development.md\ndocs/installation.md\ndocs/maintenance.md\ndocs/obsidian.md\ndocs/result.schema.json\ndocs/shell-editor.md\ndocs/sources.md\ndocs/troubleshooting.md\ndocs/updates.schema.json\nscripts/battery.sh\nscripts/check-change.py\nscripts/check-format.py\nscripts/check-repository.py\nscripts/check-updates.py\nscripts/configure.py\nscripts/desktop.py\nscripts/docker-smoke.sh\nscripts/editor.sh\nscripts/inventory.py\nscripts/kiro-shell.py\nscripts/manual.py\nscripts/ob.py\nscripts/official_ai.py\nscripts/plain-log.awk\nscripts/prepare-doubao.py\nscripts/prepare-sogou.py\nscripts/quality.sh\nscripts/runtime-smoke.py\nscripts/session.sh\nscripts/welcome.sh\nscripts/verify.sh\ntests/fixtures/battery/pmset-log-batt-uppercase.txt\ntests/fixtures/battery/pmset-log-batt.txt\ntests/fixtures/config-golden.json\ntests/test_battery.py\ntests/test_desktop.py\ntests/test_doubao.py\ntests/test_editor.py\ntests/test_interruptions.py\ntests/test_kiro_shell.py\ntests/test_maintenance.py\ntests/test_obsidian.py\ntests/test_official_ai.py\ntests/test_qbittorrent.py\ntests/test_repository.py\ntests/test_session.py\ntests/test_setup.py\ntests/test_sogou.py\ntests/test_welcome.py\n", + "repository-files.txt": ".github/pull_request_template.md\n.github/workflows/quality.yml\n.gitignore\n1bite\nAGENTS.md\nLICENSE\nMakefile\nREADME.md\nVERSION\napps/README.md\napps/qbittorrent/README.md\napps/qbittorrent/compose.yaml\napps/qbittorrent/qbt.py\napps/qbittorrent/qbt.sh\napps/qbittorrent/smoke-test.py\nassets/logo.png\nconfig/casks.tsv\nconfig/claude-settings.json\nconfig/codex-config.toml\nconfig/download.curlrc\nconfig/env.zsh\nconfig/font-casks.tsv\nconfig/formulae.txt\nconfig/git-defaults.json\nconfig/gitignore-global\nconfig/iterm2-profile.json\nconfig/obsidian-vault/.gitignore\nconfig/obsidian-vault/.obsidian/app.json\nconfig/obsidian-vault/.obsidian/appearance.json\nconfig/obsidian-vault/.obsidian/core-plugins.json\nconfig/obsidian-vault/.obsidian/graph.json\nconfig/obsidian-vault/.obsidian/snippets/wide.css\nconfig/obsidian-vault/.obsidian/templates.json\nconfig/obsidian-vault/.obsidian/themes/Tokyo Night/LICENSE\nconfig/obsidian-vault/.obsidian/themes/Tokyo Night/manifest.json\nconfig/obsidian-vault/.obsidian/themes/Tokyo Night/theme.css\nconfig/obsidian-vault/_attachments/.gitkeep\nconfig/obsidian-vault/_templates/.gitkeep\nconfig/repository-files.txt\nconfig/shell.zsh\nconfig/sources.tsv\nconfig/vimrc\nconfig/vscode-extensions.txt\nconfig/vscode-settings.json\nconfig/zsh/development.zsh\nconfig/zsh/framework.zsh\nconfig/zsh/git-functions.zsh\nconfig/zsh/media.zsh\nconfig/zsh/options.zsh\nconfig/zsh/terminal.zsh\nconfig/zsh/tools.zsh\nconfig/zsh/utilities.zsh\ndocs/automation.md\ndocs/battery.md\ndocs/battery.schema.json\ndocs/development.md\ndocs/installation.md\ndocs/maintenance.md\ndocs/obsidian.md\ndocs/result.schema.json\ndocs/shell-editor.md\ndocs/sources.md\ndocs/troubleshooting.md\ndocs/updates.schema.json\nscripts/battery.sh\nscripts/check-change.py\nscripts/check-format.py\nscripts/check-repository.py\nscripts/check-updates.py\nscripts/configure.py\nscripts/desktop.py\nscripts/docker-smoke.sh\nscripts/editor.sh\nscripts/inventory.py\nscripts/manual.py\nscripts/ob.py\nscripts/official_ai.py\nscripts/plain-log.awk\nscripts/prepare-doubao.py\nscripts/prepare-sogou.py\nscripts/quality.sh\nscripts/runtime-smoke.py\nscripts/session.sh\nscripts/verify.sh\nscripts/welcome.sh\ntests/fixtures/battery/pmset-log-batt-uppercase.txt\ntests/fixtures/battery/pmset-log-batt.txt\ntests/fixtures/config-golden.json\ntests/test_battery.py\ntests/test_desktop.py\ntests/test_doubao.py\ntests/test_editor.py\ntests/test_interruptions.py\ntests/test_maintenance.py\ntests/test_obsidian.py\ntests/test_official_ai.py\ntests/test_qbittorrent.py\ntests/test_repository.py\ntests/test_session.py\ntests/test_setup.py\ntests/test_sogou.py\ntests/test_welcome.py\n", "shell.zsh": "# Managed by 1bite. Personal overrides belong after the source in .zshrc.\nsource \"${${(%):-%N}:A:h}/env.zsh\"\n[[ -o interactive ]] || return 0\n\nsetup_shell_directory=\"${${(%):-%N}:A:h}\"\nfor setup_shell_module in framework options tools development utilities media git-functions terminal; do\n setup_shell_path=\"$setup_shell_directory/zsh/$setup_shell_module.zsh\"\n [[ -r \"$setup_shell_path\" ]] || { print -u2 \"1bite: missing Zsh module: $setup_shell_path\"; return 1; }\n source \"$setup_shell_path\" || return\ndone\nunset setup_shell_directory setup_shell_module setup_shell_path\n", - "sources.tsv": "homebrew\tinstaller\thttps://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh\nclaude\tinstaller\thttps://claude.ai/install.sh\ncodex\tinstaller\thttps://chatgpt.com/codex/install.sh\nkiro-cli\tinstaller\thttps://cli.kiro.dev/install\nchatgpt\tdocumentation\thttps://learn.chatgpt.com/docs/app\nkiro\tdocumentation\thttps://kiro.dev/downloads/\nchatgpt-download\tdistribution\thttps://persistent.oaistatic.com/codex-app-prod/Codex.dmg\nchatgpt-distribution\tdistribution\thttps://persistent.oaistatic.com/codex-app-prod/\nchatgpt-releases\tmetadata\thttps://persistent.oaistatic.com/codex-app-prod/appcast.xml\nkiro-distribution\tdistribution\thttps://prod.download.desktop.kiro.dev/\nkiro-releases\tmetadata\thttps://prod.download.desktop.kiro.dev/stable/metadata-dmg-darwin-arm64-stable.json\nkiro-cli-distribution\tdistribution\thttps://prod.download.cli.kiro.dev/stable/\nkiro-cli-releases\tmetadata\thttps://prod.download.cli.kiro.dev/stable/latest/manifest.json\nclaude-releases\tmetadata\thttps://downloads.claude.ai/claude-code-releases/latest\ncodex-releases\tmetadata\thttps://releases.openai.com/codex/channels/latest\nohmyzsh\tinstaller\thttps://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh\npowerlevel10k\tgit\thttps://github.com/romkatv/powerlevel10k.git\nneofetch\tinstaller\thttps://raw.githubusercontent.com/dylanaraps/neofetch/7.1.0/neofetch\nneofetch-sha256\tchecksum\t3dc33493e54029fb1528251552093a9f9a2894fcf94f9c3a6f809136a42348c7\ndoubao-input-download\tdistribution\thttps://lf-wave.doubaocdn.com/obj/doubao-ime/app/macos/\ndoubao-input-metadata\tmetadata\thttps://formulae.brew.sh/api/cask/doubaoime.json\nsogou-download\tdistribution\thttps://ime.gtimg.com/pc/\nhomebrew-api\tconnectivity\thttps://formulae.brew.sh/api/formula/go.json\ngithub\tconnectivity\thttps://github.com\nghcr\tconnectivity\thttps://ghcr.io/v2/\nnpm\tconnectivity\thttps://registry.npmjs.org/\nanthropic\tconnectivity\thttps://claude.ai/install.sh\nopenai\tconnectivity\thttps://chatgpt.com/codex/install.sh\nmaintenance-catalog\tmetadata\thttps://formulae.brew.sh/api/\nmaintenance-aliases\tmetadata\thttps://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Aliases/\nmaintenance-gallery\tmetadata\thttps://marketplace.visualstudio.com/_apis/public/gallery/extensionquery\nmaintenance-homebrew\tmetadata\thttps://api.github.com/repos/Homebrew/brew/releases/latest\nmaintenance-homebrew-release\tmetadata\thttps://github.com/Homebrew/brew/releases/latest\nmaintenance-one-bite\tgit\thttps://github.com/1set/1bite.git\nmaintenance-ohmyzsh\tgit\thttps://github.com/ohmyzsh/ohmyzsh.git\nmaintenance-charm\tmetadata\thttps://raw.githubusercontent.com/charmbracelet/homebrew-tap/master/\ngoogle-chrome-download\tdistribution\thttps://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg\ndocker-desktop-download\tdistribution\thttps://desktop.docker.com/mac/main/arm64/Docker.dmg\nclaude-desktop-releases\tmetadata\thttps://downloads.claude.ai/releases/darwin/universal/RELEASES.json\nclaude-desktop-distribution\tdistribution\thttps://downloads.claude.ai/releases/darwin/universal/\nclaude-desktop-download\tdistribution\thttps://claude.ai/api/desktop/darwin/universal/dmg/latest/redirect\n", + "sources.tsv": "homebrew\tinstaller\thttps://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh\nclaude\tinstaller\thttps://claude.ai/install.sh\ncodex\tinstaller\thttps://chatgpt.com/codex/install.sh\nchatgpt\tdocumentation\thttps://learn.chatgpt.com/docs/app\nchatgpt-download\tdistribution\thttps://persistent.oaistatic.com/codex-app-prod/Codex.dmg\nchatgpt-distribution\tdistribution\thttps://persistent.oaistatic.com/codex-app-prod/\nchatgpt-releases\tmetadata\thttps://persistent.oaistatic.com/codex-app-prod/appcast.xml\nclaude-releases\tmetadata\thttps://downloads.claude.ai/claude-code-releases/latest\ncodex-releases\tmetadata\thttps://releases.openai.com/codex/channels/latest\nohmyzsh\tinstaller\thttps://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh\npowerlevel10k\tgit\thttps://github.com/romkatv/powerlevel10k.git\nneofetch\tinstaller\thttps://raw.githubusercontent.com/dylanaraps/neofetch/7.1.0/neofetch\nneofetch-sha256\tchecksum\t3dc33493e54029fb1528251552093a9f9a2894fcf94f9c3a6f809136a42348c7\ndoubao-input-download\tdistribution\thttps://lf-wave.doubaocdn.com/obj/doubao-ime/app/macos/\ndoubao-input-metadata\tmetadata\thttps://formulae.brew.sh/api/cask/doubaoime.json\nsogou-download\tdistribution\thttps://ime.gtimg.com/pc/\nhomebrew-api\tconnectivity\thttps://formulae.brew.sh/api/formula/go.json\ngithub\tconnectivity\thttps://github.com\nghcr\tconnectivity\thttps://ghcr.io/v2/\nnpm\tconnectivity\thttps://registry.npmjs.org/\nanthropic\tconnectivity\thttps://claude.ai/install.sh\nopenai\tconnectivity\thttps://chatgpt.com/codex/install.sh\nmaintenance-catalog\tmetadata\thttps://formulae.brew.sh/api/\nmaintenance-aliases\tmetadata\thttps://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Aliases/\nmaintenance-gallery\tmetadata\thttps://marketplace.visualstudio.com/_apis/public/gallery/extensionquery\nmaintenance-homebrew\tmetadata\thttps://api.github.com/repos/Homebrew/brew/releases/latest\nmaintenance-homebrew-release\tmetadata\thttps://github.com/Homebrew/brew/releases/latest\nmaintenance-one-bite\tgit\thttps://github.com/1set/1bite.git\nmaintenance-ohmyzsh\tgit\thttps://github.com/ohmyzsh/ohmyzsh.git\nmaintenance-charm\tmetadata\thttps://raw.githubusercontent.com/charmbracelet/homebrew-tap/master/\ngoogle-chrome-download\tdistribution\thttps://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg\ndocker-desktop-download\tdistribution\thttps://desktop.docker.com/mac/main/arm64/Docker.dmg\nclaude-desktop-releases\tmetadata\thttps://downloads.claude.ai/releases/darwin/universal/RELEASES.json\nclaude-desktop-distribution\tdistribution\thttps://downloads.claude.ai/releases/darwin/universal/\nclaude-desktop-download\tdistribution\thttps://claude.ai/api/desktop/darwin/universal/dmg/latest/redirect\n", "vimrc": "\" Managed by 1bite. Personal overrides belong after the source line in ~/.vimrc.\nscriptencoding utf-8\nset encoding=utf-8\n\nset modelines=0\n\nset backspace=2\nset nocompatible\n\nset nobackup\nset nowritebackup\nset noswapfile\nset noundofile\n\nsyntax on\nset number\n\nset ruler\nset smartindent shiftwidth=4\nset tabstop=4\nset expandtab\n\nset list\nset listchars=tab:»·,trail:·,extends:>,precedes:<\n\ncom E e.\n\ncab W! w!\ncab Q! q!\ncab Wq wq\ncab Wa wa\ncab wQ wq\ncab WQ wq\ncab W w\ncab Q q\n", "vscode-extensions.txt": "golang.go\nms-python.python\ncharliermarsh.ruff\ndbaeumer.vscode-eslint\nesbenp.prettier-vscode\nredhat.vscode-yaml\ntimonwong.shellcheck\nms-vscode.sublime-keybindings\n", "vscode-settings.json": "{\n \"terminal.integrated.defaultProfile.osx\": \"zsh\",\n \"terminal.integrated.profiles.osx\": {\n \"zsh\": {\n \"path\": \"/bin/zsh\",\n \"args\": [\"-l\"]\n }\n },\n \"workbench.colorTheme\": \"Default Dark Modern\"\n}\n", diff --git a/tests/test_kiro_shell.py b/tests/test_kiro_shell.py deleted file mode 100644 index fda1240..0000000 --- a/tests/test_kiro_shell.py +++ /dev/null @@ -1,219 +0,0 @@ -import contextlib -import importlib.util -import io -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import unittest -from unittest.mock import patch - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location('kiro_shell', ROOT / 'scripts/kiro-shell.py') -kiro = importlib.util.module_from_spec(spec) -spec.loader.exec_module(kiro) - - -class KiroShellTests(unittest.TestCase): - def test_full_verifier_uses_the_stable_vendor_contract(self): - verifier = (ROOT / 'scripts/verify.sh').read_text() - self.assertIn('scripts/kiro-shell.py\" --verify', verifier) - self.assertNotIn('fig_preexec', verifier) - self.assertNotIn('fig_precmd', verifier) - - def test_custom_kiro_home_requires_supported_cli_before_any_writes(self): - with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {'KIRO_HOME': directory + '/custom'}): - home = Path(directory) - for version, supported in [('1.29.2', False), ('2.2.9', False), ('2.3.0', True), - ('2.21.1', True), ('3.0.0', True), ('unknown', False)]: - with self.subTest(version=version), patch.object(kiro.subprocess, 'run') as run: - run.return_value.stdout = 'kiro-cli ' + version - if supported: - kiro.check_kiro_home_support('kiro-cli', home) - else: - with self.assertRaisesRegex(ValueError, 'requires Kiro CLI 2.3.0'): - kiro.check_kiro_home_support('kiro-cli', home) - with patch.object(kiro, 'runnable', side_effect=lambda p: bool(p) and '/Kiro CLI.app/' in str(p)), \ - patch.object(kiro.shutil, 'which', return_value=None), patch.object(kiro.subprocess, 'run') as run: - run.return_value.stdout = 'kiro-cli 1.29.2' - with self.assertRaisesRegex(ValueError, 'requires Kiro CLI 2.3.0'): - kiro.configure(home, home / 'Kiro CLI.app') - self.assertEqual(list(home.iterdir()), []) - with patch.dict(os.environ, {'KIRO_HOME': str(home / '.kiro')}), \ - patch.object(kiro.subprocess, 'run') as run: - kiro.check_kiro_home_support('kiro-cli', home) - run.assert_not_called() - - def test_status_requires_complete_boolean_zsh_contract(self): - rows = [dict(shell='zsh', file_name=name, installed=True) for name in ('.zshrc', '.zprofile')] - valid = {'errors': [], 'integrations': rows + [dict(shell='bash', installed=False)]} - invalid = [{'errors': ['failed'], 'integrations': rows}, {'integrations': []}, - {'integrations': rows[:1]}, {'integrations': rows + rows}, - {'integrations': [dict(rows[0], installed='true'), rows[1]]}] - for data in [valid, *invalid]: - with self.subTest(data=data), patch.object(kiro.subprocess, 'run') as run: - run.return_value.stdout = json.dumps(data) - if data is valid: - self.assertTrue(kiro.integration_status('kiro-cli')) - else: - with self.assertRaises(ValueError): - kiro.integration_status('kiro-cli') - rows[0]['installed'] = False - with patch.object(kiro.subprocess, 'run') as run: - run.return_value.stdout = json.dumps(valid) - self.assertFalse(kiro.integration_status('kiro-cli')) - - def test_permission_preference_preserves_choices_and_rejects_invalid(self): - with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {'KIRO_HOME': directory}): - home = Path(directory) - path = home / 'settings/cli.json' - path.parent.mkdir() - for value in (True, False): - path.write_text(json.dumps({'inline.enabled': value, 'personal': 'keep'})) - before = path.read_bytes() - with patch.object(kiro.subprocess, 'run') as run: - self.assertFalse(kiro.inline_preference('kiro-cli', home, False)) - run.assert_not_called() - self.assertEqual(path.read_bytes(), before) - for data in ([], {'inline.enabled': 'true'}): - path.write_text(json.dumps(data)) - with self.assertRaises(ValueError): - kiro.inline_preference('kiro-cli', home, False) - path.write_text('{}') - with self.assertRaises(ValueError): - kiro.inline_preference('kiro-cli', home, True) - path.unlink() - path.symlink_to(home / 'unknown') - with self.assertRaises(ValueError): - kiro.inline_preference('kiro-cli', home, False) - - def test_existing_unknown_broken_commands_and_symlink_dotfiles_are_preserved(self): - with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {'ZDOTDIR': directory}): - home = Path(directory) - binaries = home / '.local/bin' - binaries.mkdir(parents=True) - command = binaries / 'kiro-cli' - for link in (False, True): - if link: - command.symlink_to(home / 'unknown-manager/missing') - else: - command.write_text('personal broken command') - with patch.object(kiro, 'runnable', return_value=False), self.assertRaises(ValueError): - kiro.configure(home, home / 'Kiro CLI.app') - self.assertTrue(command.is_symlink() if link else command.is_file()) - command.unlink() - (home / '.zshrc').symlink_to(home / 'personal') - with self.assertRaises(ValueError): - kiro.configure(home, home / 'Kiro CLI.app') - self.assertFalse(list(binaries.iterdir())) - - def test_owned_broken_links_repair_and_healthy_commands_preserve(self): - with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {'ZDOTDIR': directory}): - home = Path(directory) - binaries = home / '.local/bin' - binaries.mkdir(parents=True) - app = home / 'Applications/Kiro CLI.app' - source = app / 'Contents/MacOS' - source.mkdir(parents=True) - for name in kiro.COMMANDS: - ((source / name).resolve()).write_text('#!/bin/sh\nexit 0\n') - ((source / name).resolve()).chmod(0o755) - (binaries / name).symlink_to(home / 'old/Kiro CLI.app/Contents/MacOS' / name) - helpers = home / 'Library/Application Support/kiro-cli/shell' - helpers.mkdir(parents=True) - for name, content in kiro.HELPERS.items(): - (helpers / name).write_text(content) - with patch.object(kiro.shutil, 'which', return_value=None), \ - patch.object(kiro, 'inline_preference', return_value=False), \ - patch.object(kiro, 'verify_generated_hooks'), patch.object(kiro, 'integration_status', return_value=True): - with self.assertRaises(ValueError): - kiro.configure(home, app, verify=True) - with contextlib.redirect_stdout(io.StringIO()): - kiro.configure(home, app) - for name in kiro.COMMANDS: - self.assertEqual((binaries / name).resolve(), (source / name).resolve()) - # Healthy independently managed commands remain untouched. - personal = home / 'personal-cli' - personal.write_text('#!/bin/sh\nexit 0\n') - personal.chmod(0o755) - (binaries / 'kiro-cli').unlink() - (binaries / 'kiro-cli').symlink_to(personal) - with contextlib.redirect_stdout(io.StringIO()): - kiro.configure(home, app) - self.assertEqual((binaries / 'kiro-cli').resolve(), personal.resolve()) - - @unittest.skipUnless(Path('/Applications/Kiro CLI.app').is_dir(), 'Requires the real macOS vendor app') - def test_real_vendor_hooks_repeat_repair_zdotdir_and_zsh_execution(self): - self.check_vendor_hooks(custom_home=False) - - @unittest.skipUnless(Path('/Applications/Kiro CLI.app').is_dir(), 'Requires the real macOS vendor app') - def test_real_vendor_custom_home_support_or_safe_rejection(self): - self.check_vendor_hooks(custom_home=True) - - def check_vendor_hooks(self, custom_home): - with tempfile.TemporaryDirectory(prefix='kiro shell test ') as directory: - home = Path(directory) - shell = home / 'custom-zsh' - shell.mkdir() - (shell / '.zshrc').write_text('export ONE_BITE_PERSONAL=preserved\n') - vendor_bin = '/Applications/Kiro CLI.app/Contents/MacOS' - env = {'PATH':vendor_bin + os.pathsep + os.environ['PATH'], 'HOME':directory, 'ZDOTDIR':str(shell), - 'SHELL':'/bin/zsh', 'TERM':'xterm-256color'} - settings_home = home / ('custom-kiro' if custom_home else '.kiro') - if custom_home: - env['KIRO_HOME'] = str(settings_home) - with patch.dict(os.environ, env, clear=True): - try: - kiro.check_kiro_home_support(Path(vendor_bin) / 'kiro-cli', home) - except ValueError: - result = subprocess.run([sys.executable, str(ROOT / 'scripts/kiro-shell.py'), - '--app', '/Applications/Kiro CLI.app'], - env=env, capture_output=True, text=True, timeout=60) - self.assertNotEqual(result.returncode, 0) - self.assertIn('requires Kiro CLI 2.3.0', result.stderr) - self.assertFalse((home / '.local').exists()) - self.assertFalse((home / '.kiro').exists()) - self.assertFalse(settings_home.exists()) - return - args = [sys.executable, str(ROOT / 'scripts/kiro-shell.py'), '--app', '/Applications/Kiro CLI.app'] - - def run(extra=()): - return subprocess.run([*args, *extra], env=env, capture_output=True, text=True, check=True, timeout=60) - - self.assertIn('installed-or-updated', run().stdout) - helpers = home / 'Library/Application Support/kiro-cli/shell' - tracked = [shell / '.zshrc', shell / '.zprofile', *helpers.glob('*.zsh'), - settings_home / 'settings/cli.json'] - before = {p:p.read_bytes() for p in tracked} - self.assertIn('skipped', run().stdout) - self.assertEqual(before, {p:p.read_bytes() for p in tracked}) - run(['--verify']) - # Vendor releases may implement their post hook without the old - # private fig_preexec/fig_precmd function names. The public - # contract is a healthy integration whose login shell continues - # into the user's existing configuration. - command = 'print -r -- "$ONE_BITE_PERSONAL"' - result = subprocess.run(['zsh', '-lic', command], env=env, capture_output=True, text=True, check=True, timeout=15) - self.assertEqual(result.stdout.strip(), 'preserved') - self.assertEqual(result.stderr, '') - result = subprocess.run(['zsh', '-lc', 'printf noninteractive-ok'], env=env, capture_output=True, text=True, check=True, timeout=15) - self.assertEqual(result.stdout, 'noninteractive-ok') - self.assertEqual(result.stderr, '') - (helpers / 'zshrc.post.zsh').unlink() - with self.assertRaises(subprocess.CalledProcessError): - run(['--verify']) - run() - run(['--verify']) - (helpers / 'zshrc.post.zsh').write_text('# interrupted write: nonempty but no hook\n') - with self.assertRaises(subprocess.CalledProcessError): - run(['--verify']) - run() - run(['--verify']) - self.assertFalse((home / '.bashrc').exists()) - self.assertFalse((home / '.zshrc').exists()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_maintenance.py b/tests/test_maintenance.py index 6a9f297..1264fec 100644 --- a/tests/test_maintenance.py +++ b/tests/test_maintenance.py @@ -187,12 +187,12 @@ def unexpected(url): def test_ai_tools_stay_with_official_installers_and_do_not_query_cask_versions(self): def fetch(url, payload=None): - for name in ('chatgpt', 'kiro', 'kiro-cli', 'claude-code', 'codex'): + for name in ('chatgpt', 'claude-code', 'codex'): self.assertFalse(url.endswith('/cask/' + name + '.json'), url) return self.fetch(url, payload) - self.local['casks'] = [{'token': name} for name in ('chatgpt', 'kiro', 'kiro-cli')] + self.local['casks'] = [{'token': name} for name in ('chatgpt',)] rows = {item['component']: item for item in self.check(fetch)['items']} - for name in ('cask:chatgpt', 'cask:kiro', 'cask:kiro-cli', 'cli:claude', 'cli:codex'): + for name in ('cask:chatgpt', 'cli:claude', 'cli:codex'): self.assertEqual((rows[name]['manager'], rows[name]['status'], rows[name]['latest']), ('original', 'current', '1.0.0')) shutil.rmtree(self.home / 'Applications/ChatGPT.app') diff --git a/tests/test_official_ai.py b/tests/test_official_ai.py index c863c8d..84ca66a 100644 --- a/tests/test_official_ai.py +++ b/tests/test_official_ai.py @@ -48,47 +48,9 @@ def feed(version, build, arch='arm64'): xml = '' + feed('1.9.0', 1) + feed('1.10.0', 2) + feed('99.0.0', 3, 'x64') + '' result = ai.release('chatgpt', catalog, lambda _: xml) self.assertEqual((result['version'], result['build'], result['url']), ('1.10.0', '2', catalog['chatgpt-download'])) - version = '1.10.0' - url = catalog['kiro-distribution'] + f'releases/stable/darwin-arm64/signed/{version}/kiro-ide-{version}-stable-darwin-arm64.dmg' - kiro = dict(currentRelease=version, - releases=[dict(version=version, updateTo=dict(version=version, url=url))]) - requested = [] - self.assertEqual(ai.release( - 'kiro', catalog, - lambda source: requested.append(source) or json.dumps(kiro))['version'], version) - self.assertEqual(requested, [catalog['kiro-releases']]) - for changed in ( - [], - dict(currentRelease=version, releases={}), - dict(currentRelease='1.10', releases=kiro['releases']), - dict(currentRelease=version, releases=['invalid']), - dict(kiro, currentRelease='1.9.0'), - dict(kiro, releases=[]), - dict(kiro, releases=kiro['releases'] * 2), - dict(kiro, releases=[dict(version=version, updateTo=dict( - version='1.9.0', url=url))]), - dict(kiro, releases=[dict(version=version, updateTo=dict( - version=version, url='https://example.invalid/kiro.dmg'))]), - dict(kiro, releases=[dict(version=version, updateTo=dict( - version=version, url=url.replace('/stable/', '/preview/')))]), - dict(kiro, releases=[dict(version=version, updateTo=dict( - version=version, url=url.replace('arm64', 'x64')))])): - with self.assertRaises(ValueError): - ai.release('kiro', catalog, lambda _, value=changed: json.dumps(value)) self.assertEqual(ai.release('codex', catalog, lambda _: '{"tag_name":"rust-v0.200.0"}')['version'], '0.200.0') self.assertEqual(ai.release('claude', catalog, lambda _: '2.5.0\n')['version'], '2.5.0') - def test_kiro_manifest_requires_stable_native_package_and_sha256(self): - package = dict(os='macos', architecture='universal', fileType='dmg', channel='stable', - download='2.24.0/Kiro CLI.dmg', sha256='a' * 64) - data = dict(version='2.24.0', packages=[package]) - result = ai.release('kiro-cli', fetch=lambda _: json.dumps(data)) - self.assertTrue(result['url'].endswith('/2.24.0/Kiro%20CLI.dmg')) - for field, value in [('sha256', 'missing'), ('download', '../evil.dmg'), ('channel', 'preview'), ('architecture', 'x64')]: - invalid = dict(data, packages=[dict(package, **{field: value})]) - with self.assertRaises(ValueError): - ai.release('kiro-cli', fetch=lambda _: json.dumps(invalid)) - def test_receipt_is_not_proof_of_health_and_is_private(self): with patch.object(ai, 'release', return_value=dict(version='2.0.0', url='https://vendor.invalid/app.dmg')), \ patch.object(ai, 'download', side_effect=RuntimeError('network interrupted')): @@ -183,8 +145,8 @@ def test_download_checksum_failure_leaves_no_receipt(self): image = self.home / 'image.dmg' image.write_bytes(b'interrupted download') with self.assertRaisesRegex(ValueError, 'SHA-256'): - ai.download('kiro-cli', 'https://vendor.invalid/dmg', image, 'a' * 64) - self.assertFalse(ai.receipt_path('kiro-cli').exists()) + ai.download('chatgpt', 'https://vendor.invalid/dmg', image, 'a' * 64) + self.assertFalse(ai.receipt_path('chatgpt').exists()) def test_vendor_identity_and_signature_checked_before_any_placement(self): app = self.bundle(self.destination) @@ -192,8 +154,6 @@ def test_vendor_identity_and_signature_checked_before_any_placement(self): with self.assertRaises(subprocess.CalledProcessError): ai.verify_bundle('chatgpt', app) self.assertEqual(run.call_count, 1) - with self.assertRaisesRegex(ValueError, 'identity'): - ai.verify_bundle('kiro', app) def test_missing_app_and_managed_damage_install_from_verified_temporary_bundle(self): staged = self.bundle(self.home / 'vendor/ChatGPT.app', '2.0.0') diff --git a/tests/test_setup.py b/tests/test_setup.py index a5c73b1..842ad40 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -175,7 +175,7 @@ def test_fresh_install_and_repeat_are_identical(self): self.apply() self.assertEqual(before, self.snapshot()) configure.verify(self.home, with_claude=True) - for path in ('.claude/settings.json', '.codex/config.toml', '.kiro/settings/permissions.yaml'): + for path in ('.claude/settings.json', '.codex/config.toml'): self.assertEqual((self.home / path).stat().st_mode & 0o777, 0o600) self.assertFalse((self.home / '.codex/auth.json').exists()) @@ -364,62 +364,12 @@ def test_vim_template_loads_core_defaults(self): self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(result_file.read_text().splitlines(), ['1', '1', '4', '4', '0', '0']) - def test_kiro_policy_reporting_and_missing_empty_or_symlink_rejection(self): - self.apply() - self.assertEqual(configure.kiro_policy_status(self.home), 'default-ask') - policy = self.home / configure.KIRO_PATH - policy.write_text('rules:\n - capability: shell\n effect: allow\n') - output = io.StringIO() - with contextlib.redirect_stdout(output): - configure.configure(self.home, ROOT / 'config') - configure.verify(self.home, with_claude=True) - self.assertIn('not audited', output.getvalue()) - self.assertEqual(configure.kiro_policy_status(self.home), 'custom-unreviewed') - policy.write_text(' \n') - before = self.snapshot() - with self.assertRaises(ValueError): - self.apply() - self.assertEqual(self.snapshot(), before) - policy.unlink() - with self.assertRaises(FileNotFoundError): - configure.verify(self.home, with_claude=True) - for path in (policy, policy.parent, policy.parent.parent): - with self.subTest(path=path): - if path.is_dir(): - path.rmdir() - path.symlink_to(self.home / 'missing') - with self.assertRaises(ValueError): - self.apply() - with self.assertRaises(ValueError): - configure.verify(self.home, with_claude=True) - path.unlink() - - def test_kiro_external_rule_contract(self): - capabilities = ('all', 'builtin', 'filesystem', 'fs_read', 'fs_write', 'shell', 'web_fetch', - 'web_search', 'mcp', 'subagent', 'skill', 'power', 'context', 'diagnostics', 'sandbox_network') - for capability in capabilities: - for effect in ('ask', 'deny', 'allow'): - configure.validate_kiro(json.dumps({'rules': [dict(capability=capability, effect=effect, - match=['*'], exclude=['example'])]})) - invalid = [None, [], {}, {'rules': []}, {'rules': {}}, {'rules': [], 'extra': True}] - rules = [None, {}, {'capability': 'all'}, {'effect': 'ask'}, - {'capability': [], 'effect': 'ask'}, {'capability': 'unknown', 'effect': 'ask'}, - {'capability': 'all', 'effect': []}, {'capability': 'all', 'effect': 'trust'}, - {'capability': 'all', 'effect': 'ask', 'extra': True}] - for key in ('match', 'exclude'): - rules += [dict(capability='all', effect='ask', **{key: value}) - for value in (None, '*', [], [''], [1])] - invalid += [{'rules': [rule]} for rule in rules] - for value in invalid: - with self.subTest(value=value), self.assertRaises(ValueError): - configure.validate_kiro(json.dumps(value)) - def test_invalid_external_config_does_not_write(self): external = self.home / 'external' shutil.copytree(ROOT / 'config', external) for name, invalid in [('claude-settings.json', '[]'), ('codex-config.toml', 'bad = ['), - ('kiro-permissions.json', '{}'), ('iterm2-profile.json', '{}'), ('vscode-settings.json', '[]')]: + ('iterm2-profile.json', '{}'), ('vscode-settings.json', '[]')]: with self.subTest(name=name): path = external / name original = path.read_text() @@ -773,8 +723,7 @@ def test_font_cask_skip_update_repair_and_preserve(self): def test_ai_installation_dispatch_skips_healthy_and_propagates_failure(self): commands = ('check_agent claude', 'check_agent codex', - 'ensure_cask chatgpt ChatGPT.app', 'ensure_cask kiro Kiro.app', - "ensure_cask kiro-cli 'Kiro CLI.app'") + 'ensure_cask chatgpt ChatGPT.app') for command in commands: for healthy, update, status in [('true', 'false', 0), ('false', 'false', 0), ('true', 'true', 0), ('false', 'true', 23)]: @@ -889,24 +838,6 @@ def test_input_method_health_checks_expected_locations_and_identity(self): self.assertEqual(result.stdout.splitlines(), [str(Path.home() / 'Library/Input Methods/DoubaoIme.app'), 'SOGOU-MISSING']) - def test_default_kiro_uses_vendor_installer_without_managed_hooks(self): - result = self.run_shell(""" -DESKTOP_MODE=download -app_healthy() { [[ -f "$TEST_STATE/healthy" ]]; } -download_installer() { echo "DOWNLOAD $1"; } -run_package_installer() { echo "VENDOR $1"; touch "$TEST_STATE/healthy"; } -python3() { echo WRONG; return 91; } -agent_healthy() { return 1; } -ensure_cask kiro-cli 'Kiro CLI.app' -ensure_kiro_shell -echo "MANUAL $MANUAL_STEPS" -""") - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('DOWNLOAD kiro-cli', result.stdout) - self.assertIn('VENDOR /bin/bash', result.stdout) - self.assertIn('kiro-cli-onboarding', result.stdout) - self.assertNotIn('WRONG', result.stdout) - def test_failures_do_not_continue(self): for script in [ 'brew() { return 1; }; ensure_formula go', @@ -1016,7 +947,7 @@ def test_homebrew_refresh_is_explicit(self): def test_entire_managed_package_surface_uses_official_names(self): formulae = (ROOT / 'config/formulae.txt').read_text().split() casks = [line.split('\t')[0] for line in (ROOT / 'config/casks.tsv').read_text().splitlines() - if line.split('\t')[0] not in ('chatgpt', 'kiro', 'kiro-cli', 'claude-desktop')] + if line.split('\t')[0] not in ('chatgpt', 'claude-desktop')] for kind, names in [('formula', formulae), ('cask', casks)]: for name in names: for installed, healthy, update, operation in [ @@ -1301,7 +1232,6 @@ def test_templates_before_agents_and_optional_extension_progress(self): ensure_ohmyzsh() { :; } ensure_powerlevel10k() { :; } ensure_neofetch() { :; } -ensure_kiro_shell() { :; } ensure_extension() { :; } git() { :; } ensure_git_lfs_filters() { :; } @@ -1313,7 +1243,7 @@ def test_templates_before_agents_and_optional_extension_progress(self): execute_mode echo "TOTAL $COMPLETED_STEPS $STEP_TOTAL" """ - default_count = (12 + len((ROOT / 'config/formulae.txt').read_text().split()) + default_count = (11 + len((ROOT / 'config/formulae.txt').read_text().split()) + len((ROOT / 'config/casks.tsv').read_text().splitlines()) + len((ROOT / 'config/font-casks.tsv').read_text().splitlines()) + len((ROOT / 'config/vscode-extensions.txt').read_text().split()) - 3) @@ -1406,49 +1336,6 @@ def test_obsidian_cask_preserves_a_healthy_user_application(self): self.assertIn('ACTION preserved', result.stdout) self.assertNotIn('WRONG', result.stdout) - def test_kiro_locations_versions_identity_and_manager_actions(self): - for compatible in ('/Applications/Kiro.app', str(Path.home() / 'Applications/Kiro.app')): - result = self.run_shell('COMPATIBLE=' + shlex.quote(compatible) + '; ' + """ -bundle_healthy() { return 0; } -bundle_identifier() { echo dev.kiro.desktop; } -bundle_version() { if [[ "$1" == "$COMPATIBLE" ]]; then echo 1.0.437; else echo 0.9.1; fi; } -app_path Kiro.app -""") - self.assertEqual(result.stdout.strip(), compatible) - for system, user in [('true', 'false'), ('false', 'true'), ('true', 'true'), ('false', 'false')]: - for version in ('0.9.1', '1.0.437', '2.0.0', 'invalid'): - for identity in ('dev.kiro.desktop', 'other.application'): - script = f'SYSTEM={system}; USER_APP={user}; VERSION={version}; IDENTITY={identity}; ' + """ -bundle_healthy() { - if [[ "$1" == /Applications/Kiro.app ]]; then [[ "$SYSTEM" == true ]]; - elif [[ "$1" == "$HOME/Applications/Kiro.app" ]]; then [[ "$USER_APP" == true ]]; - else return 1; fi -} -bundle_version() { echo "$VERSION"; } -bundle_identifier() { echo "$IDENTITY"; } -""" - selected_user = system == 'false' and user == 'true' - result = self.run_shell(script + 'app_path Kiro.app') - expected = str(Path.home() / 'Applications/Kiro.app') if selected_user else '/Applications/Kiro.app' - self.assertEqual(result.stdout.strip(), expected) - healthy = (system == 'true' or user == 'true') and version[0] in '12' and identity == 'dev.kiro.desktop' - result = self.run_shell(script + 'app_healthy Kiro.app') - self.assertEqual(result.returncode == 0, healthy, result.stderr) - if not healthy and selected_user: - result = self.run_shell(script + 'python3() { return 1; }; brew() { echo WRONG; }; ensure_cask kiro Kiro.app') - self.assertNotEqual(result.returncode, 0) - self.assertNotIn('WRONG', result.stdout) - if not healthy: - continue - for managed in ('true', 'false'): - for update in ('true', 'false'): - result = self.run_shell(script + f'DESKTOP_MODE=managed; MANAGED={managed}; UPDATE={update}; ' + """ -brew() { if [[ "$1" == list ]]; then [[ "$MANAGED" == true ]]; else echo "MUTATE $*"; fi; } -python3() { echo preserved; } -ensure_cask kiro Kiro.app -""") - self.assertEqual(result.returncode, 0, result.stderr) - self.assertNotIn('MUTATE', result.stdout) def test_platform_contract(self): for arch, version, uid, accepted in [('arm64', '26.6.2', 501, True), ('arm64', '27.0', 501, True), ('arm64', '15.7', 501, False), ('x86_64', '26.6.2', 501, False),