diff --git a/README.md b/README.md index 4549bc6e..ed74cc3b 100644 --- a/README.md +++ b/README.md @@ -666,6 +666,11 @@ removes it, until it is gone. The launchers follow their links to the checkout, `common/coding-agent-kit/coding-agent-kit/docs/account-launchers.md`. Stow does the installing here; the kit's own `install.sh` is for hosts without dotfiles. +From v0.3.0 `codex-as --account-info NAME` and `claude-as --account-info NAME` +print an account's last-seen email or label and usage, offline; the zsh +completions describe each account with it and the fzf-tab preview shows it +(see [docs/coding-agent-kit.md](docs/coding-agent-kit.md#account-details-in-completion)). + The kit reads an optional `${XDG_CONFIG_HOME:-~/.config}/coding-agent-kit/config.toml` for the `codex` and `claude` executables and the `~/.codex` and `~/.secrets` roots. Dotfiles diff --git a/common/coding-agent-kit/coding-agent-kit b/common/coding-agent-kit/coding-agent-kit index 38333d67..081f54ae 160000 --- a/common/coding-agent-kit/coding-agent-kit +++ b/common/coding-agent-kit/coding-agent-kit @@ -1 +1 @@ -Subproject commit 38333d6705a7108d5eddd2882fdf26d511684f3a +Subproject commit 081f54ae953ca6524865eb7819968c445b3919b6 diff --git a/common/zsh/.oh-my-zsh/custom/fzf-tab.zsh b/common/zsh/.oh-my-zsh/custom/fzf-tab.zsh index bcbb3e97..57993ab8 100644 --- a/common/zsh/.oh-my-zsh/custom/fzf-tab.zsh +++ b/common/zsh/.oh-my-zsh/custom/fzf-tab.zsh @@ -50,7 +50,16 @@ zstyle ':fzf-tab:complete:*' fzf-preview '[[ -e "$realpath" ]] && my-fzf-preview # Custom previews for specific commands # Account launchers: never hand a credential path to the generic file preview. -zstyle ':fzf-tab:complete:(codex-as|claude-as):*' fzf-preview 'printf "%s\n" "$word"' +# An account shows the kit's offline --account-info (last-seen email or label +# and usage; it opens no credential), any other word only itself. fzf-tab keys +# this context on the command alone, so the group, the [%d] description of +# the completion, tells an account from an argument. +zstyle ':fzf-tab:complete:(codex-as|claude-as):*' fzf-preview \ + 'case $group in + ("[Codex accounts]") codex-as --account-info "$word" 2>/dev/null || printf "%s\n" "$word" ;; + ("[Claude accounts]") claude-as --account-info "$word" 2>/dev/null || printf "%s\n" "$word" ;; + (*) printf "%s\n" "$word" ;; + esac' # kill zstyle ':completion:*:*:*:*:processes' command "ps -u $USER -o pid,user,comm -w -w" diff --git a/docs/coding-agent-kit.md b/docs/coding-agent-kit.md index b6cebabb..5c4ead69 100644 --- a/docs/coding-agent-kit.md +++ b/docs/coding-agent-kit.md @@ -87,6 +87,21 @@ Never run the kit's `install.sh` on a dotfiles host. Stow already links the launchers and completions, and `install.sh` refuses those links (it replaces only links of its own) and changes nothing. +## Account details in completion + +From v0.3.0 the kit keeps what it last saw about each account in +`${XDG_STATE_HOME:-~/.local/state}/coding-agent-kit/accounts/`: the Codex email +and plan, a Claude label entered at enrolment (or with +`claude-as --set-label NAME [LABEL]`), and the last 5-hour and weekly usage +(Codex from the rollouts of a run, Claude from its status line). The zsh +completions show the email or label next to each account, and dotfiles' +fzf-tab preview (`common/zsh/.oh-my-zsh/custom/fzf-tab.zsh`) runs +`codex-as --account-info NAME` or `claude-as --account-info NAME` for an +account, offline and without opening a credential; any other word previews as +itself. The kit's `docs/account-launchers.md` says where each field comes from +and its gaps. Emails and labels show in previews, screen shares and agent +transcripts; no token is ever printed. + ## Configuration The launchers read an optional TOML file, diff --git a/docs/testing.md b/docs/testing.md index f79c0054..d7e25971 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -116,6 +116,12 @@ client's missing-page error must never reach the preview, and the C client must never run without `TLDR_AUTO_UPDATE_DISABLED`, without which the real one fetches a missing page from GitHub. +`tests/test_ai_integration.py` evaluates the account preview of +`custom/fzf-tab.zsh` under the context fzf-tab really looks up; with +`FZF_TAB_TEST_DIR` set to an fzf-tab checkout (the bootstrap clones it to +`~/.oh-my-zsh/custom/plugins/fzf-tab`) it also drives real fzf-tab in an +interactive zsh with a fake fzf. Without that variable the second case skips. + The day-zero bootstrap has four suites. `tests/bootstrap-manifest.sh` unit-tests the sourced `lib/bootstrap/{manifest,platform,version}.sh` and runs the standard-library validator `tests/test_bootstrap_manifest.py`, which checks diff --git a/tests/COVERAGE.md b/tests/COVERAGE.md index 54d0ae80..d96db102 100644 --- a/tests/COVERAGE.md +++ b/tests/COVERAGE.md @@ -43,7 +43,7 @@ source-to-test behavior matrix instead. | `wsl-ubuntu` and `lab-ubuntu` zsh and bash overlay rc files | `host-overlays.sh` | the Linuxbrew `brew shellenv` guard under `zsh -f` and `bash --norc --noprofile` in a clean environment: silent with a missing or non-executable brew, applied only when brew is executable; the overlays' conda and mamba prefixes point at missing paths so only the guard is tested | | `tests/e2e/inside.sh`, `tests/e2e/lib/*.sh` (`blocks.sh` policy, `assert.sh` snapshots and audits), `tests/e2e/hosts/*.env` | `e2e-harness.sh` | HUMAN block parser (notes against commands, `%q` paths with spaces kept byte for byte), the `HUMAN steps pending:` reader, every (step id, kind) policy row with the fail rows (planted extra H7-stow line, another host's stow line, two stow lines, `S2-brew-bundle` sudo and judgment, `S4-nvm`, `X-recovery`, unknown ids and kinds), the exact printed shape of every line the harness runs: the digest gate (sha256sum and shasum forms, the run path bound to the digested one, nothing after it; another path, a `;` tail, an extra pipe stage, an `&&` tail, no `--status`, the `/bin/bash` Homebrew form, a bare run line and an un-gated run line beside the gate refused), `mv -n FILE FILE.pre-dotfiles` for one `%q` path under HOME (chained commands, a mismatched or unescaped path, `$(...)`, backticks and `$'...'` refused) and the stow line (one of the three printed PATH prefixes, then the clone's own `stow-all.sh` and the host; a command between them, command substitution, a `..` path or a trailing argument refused), the by-hand fence holding only the two helper definitions, `E2E_ALLOC_ENV` expansion and refusals with the reason printed, the failure-detail reader, the wrappers.log phase and no-sudo audits and the sudo.log window audit against fixture logs; the real `sudo` and `stow` wrappers with two copies on PATH (the macOS runner's layout) log a call once and run the real tool, and give up with 127 instead of exec'ing each other when there is none; `tests/e2e/timeout.pl` (the per-command limit where neither timeout nor gtimeout exists) exits 124 within seconds of its limit, passes a status and a signal death through, gives 127 for a command it cannot run, kills the whole process group, and is what e2e_find_timeout picks only without timeout or gtimeout; a sandboxed dry run of `inside.sh` under `E2E_SANDBOX_HOME` (a home with a space) against a fixture clone whose stub `setup-host.sh`, `doctor.sh` and `stow-all.sh` print realistic plan lines, blocks and exit codes, with stub sudo, chsh, stow and apt-get wrappers writing the wrappers.log format: all steps pass through four apply runs (apt sudo block, gated inspect line, mv -n and stow line), the reminders are skipped once, the second apply applies nothing, the login shell, doctor runs and negatives pass, the timeline frames every phase and the audits pass; the same at a fixture commit whose `doctor.sh` calls sudo outside a human phase fails the wrappers audit and exits 1; the exit-2 refusals (sandbox not under the temp dir or relative, a source checkout or a sudo, chsh or stow outside the temp dir under the sandbox seam, win, a bad rev, a taken home, no seam) run no step. Docker images, the real bootstrap and the sudo.log a real sudo writes are exercised only by the opt-in `tests/e2e/run.sh` | | `tests/e2e/run.ps1`, `tests/e2e/lib/*.ps1` | `e2e-harness.ps1`, `e2e-harness-windows.sh`, `tests/run.ps1` parse gate | every file parses with the PowerShell parser on each run of `tests/run.ps1` (the native Windows CI job included); with the libraries dot-sourced unchanged and stubs for each setup-host.ps1 run, the HW-stow child, the registry PATH rebuild and the snapshots: the HUMAN block parser on CRLF output with winget's blank progress lines, the (step id, kind) policy (HW-stow runs, the five reminders skip, HW-clone and every other pair fail), only the exact `& '\stow-all.ps1' win` line accepted, the real `setup-host.ps1 -PrintManual` blocks against their policy rows, the pending-id, failure-detail and summary readers, HOME snapshots (added, removed, changed, links by target never followed, an unreadable directory, pruned subtrees, the top-level ntuser hives, `E2E_SNAPSHOT_PRUNE`), CryptnetUrlCache noted and every other HOME write failing, `summary.tsv` as UTF-8 without BOM with LF and tabs, a passing step failed by a dirty clone, doctor TSV counts, `Invoke-E2EProcess` (closed stdin, both streams logged, 124 after the timeout), the `run.ps1` exit-2 refusals (arguments, a host but Windows), apply-loop dry runs (exit 3 then 0, reminders recorded once, HW-stow passing only with the applied state recorded and no WARNING line, a pending HW-clone failing before the stow, an HW-stow block with another line refused, no progress, exit 1 and 2, a timeout, eight exit-3 runs), no-write steps (noted cache entries pass; a HOME write, a TEMP leftover or redone work fail) and `Invoke-E2EFlowWin` step order with dependents skipped after a break; runs on Linux and macOS pwsh too, where the Windows-only checks print SKIP. winget, the real bootstrap and the runner's home are exercised only by the opt-in `bootstrap-e2e.yml` windows job | -| `.stowrc`, `common/(claude\|codex\|coding-agent-kit)/**`, `fzf-tab.zsh`, `.zsh_aliases`, `.bash_aliases`, and the real `settings.json` against the pinned coding-agent-kit | `ai-integration.sh`, `test_ai_integration.py`, `tests/run.ps1` | the fzf-tab account preview never reads a credential, both real alias files sourced twice reach `codex-as --help` in Bash and Zsh through a link to its package link, which finds the kit checkout and names its `common/coding-agent-kit/coding-agent-kit/docs/account-launchers.md` with no caller kit configuration in reach, real Stow installs the underscore zsh completions from the coding-agent-kit package, ignores AppleDouble files and leaves its checkout unstowed; the real-data overlay extracts the pinned submodule tree, puts the real `settings.json` over its fixture and reruns its 8 `claude-customizations.cjs` tests (on Windows too, through `tests/run.ps1`) | +| `.stowrc`, `common/(claude\|codex\|coding-agent-kit)/**`, `fzf-tab.zsh`, `.zsh_aliases`, `.bash_aliases`, and the real `settings.json` against the pinned coding-agent-kit | `ai-integration.sh`, `test_ai_integration.py`, `tests/run.ps1` | the fzf-tab account preview, under the context fzf-tab really looks up (`:fzf-tab:complete:TOOL:`), shows the kit's `--account-info` for the `[Codex accounts]` and `[Claude accounts]` groups and only the word for arguments or a name that is no account, never reaching the file preview or a credential, and, with a real fzf-tab checkout in `FZF_TAB_TEST_DIR`, an interactive zsh with a fake fzf runs fzf-tab's own preview command for each candidate, the email or label shown as its description; both real alias files sourced twice reach `codex-as --help` in Bash and Zsh through a link to its package link, which finds the kit checkout and names its `common/coding-agent-kit/coding-agent-kit/docs/account-launchers.md` with no caller kit configuration in reach, real Stow installs the underscore zsh completions from the coding-agent-kit package, ignores AppleDouble files and leaves its checkout unstowed; the real-data overlay extracts the pinned submodule tree, puts the real `settings.json` over its fixture and reruns its 8 `claude-customizations.cjs` tests (on Windows too, through `tests/run.ps1`) | | `tests/run.sh`, `tests/run.ps1`, the suites' fixture Git, `.pre-commit-config.yaml` and CI | `test-entrypoints.sh`, three native CI jobs | required dependencies fail visibly, local optional skips reported, native Windows job cannot run on Unix, shared backend and AI integration suites required, every entry point unsets the same dotfiles and kit knobs (covering each `DOTFILES_*`/`AWESOME_SKILLS_*` name in any tracked file outside tests, docs, `.github` and Markdown, in shell, PowerShell or Python spelling, and each `CODING_AGENT_KIT_*` or `_CODING_AGENT_KIT_*` name in the kit's code at the recorded gitlink, through the delivery test's `check_kit_knobs`) and a suite passes under the provisioning exports; fixture Git out of a commit hook's repository: the Bash suites with fixture Git in their body (`bootstrap-manifest.sh`, `doctor.sh`, `e2e-harness.sh`, `setup-host.sh`, `unix-installer.sh`) unset `GIT_DIR` and `GIT_INDEX_FILE` and the PowerShell ones (`e2e-harness.ps1`, `windows-installer-controls.ps1`) remove every `GIT_*` variable, the manifest suite passes under a relative `GIT_INDEX_FILE`, and with pwsh the installer controls leave the index, `HEAD` and `core.bare` of a throwaway repository alone under its linked worktree's `GIT_DIR` and `GIT_INDEX_FILE`; `.pre-commit-config.yaml` selection as pre-commit makes it (files, exclude, top-level exclude): the engine hooks on `lib/config_sync.py` and `lib/sync-runtime.sh`, `structured-config-sync` and `codex-config-sync` on the real Codex `config.toml`, the delivery hook on `tests/run.sh`, the bump workflow and its reviewed copy, and the `test-entrypoints` hook on itself, the entry points and `.pre-commit-config.yaml` | Windows updater mocks do not establish actual Task Scheduler execution. The diff --git a/tests/ai-config-sync.sh b/tests/ai-config-sync.sh index 398a5dae..f77f8335 100755 --- a/tests/ai-config-sync.sh +++ b/tests/ai-config-sync.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" TEST_TMP="$(mktemp -d "${TMPDIR:-/tmp}/dotfiles-ai-sync.XXXXXX")" diff --git a/tests/ai-integration.sh b/tests/ai-integration.sh index fbcd05c0..7c0dbdd6 100755 --- a/tests/ai-integration.sh +++ b/tests/ai-integration.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME TEST_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd -- "$TEST_DIR/.." && pwd)" diff --git a/tests/awesome-skills-update.sh b/tests/awesome-skills-update.sh index 88155b5a..7a0748da 100755 --- a/tests/awesome-skills-update.sh +++ b/tests/awesome-skills-update.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" UPDATER="$REPO_ROOT/scripts/awesome-skills-update.sh" diff --git a/tests/bootstrap-manifest.sh b/tests/bootstrap-manifest.sh index a539393e..631df726 100755 --- a/tests/bootstrap-manifest.sh +++ b/tests/bootstrap-manifest.sh @@ -12,7 +12,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # A commit hook's repository/index overrides must not reach fixture Git or # the linked worktrees created below. diff --git a/tests/bootstrap-windows.sh b/tests/bootstrap-windows.sh index ac3cf02c..d7870619 100755 --- a/tests/bootstrap-windows.sh +++ b/tests/bootstrap-windows.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # Run the Windows bootstrap suite (tests/bootstrap.ps1: lib/bootstrap.ps1, # doctor.ps1 and setup-host.ps1 against fixtures and shims) wherever pwsh diff --git a/tests/codex-config-sync.sh b/tests/codex-config-sync.sh index 4bfea5cc..a2ed63d9 100755 --- a/tests/codex-config-sync.sh +++ b/tests/codex-config-sync.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" SYNC="$REPO_ROOT/common/codex/.local/bin/codex-config-sync" diff --git a/tests/coding-agent-kit-delivery.sh b/tests/coding-agent-kit-delivery.sh index 45a1254b..50ffd067 100755 --- a/tests/coding-agent-kit-delivery.sh +++ b/tests/coding-agent-kit-delivery.sh @@ -9,7 +9,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # How the coding-agent-kit submodule delivers the Codex and Claude launchers, # libraries, completions and Claude helpers (docs/coding-agent-kit.md). The diff --git a/tests/config-sync.sh b/tests/config-sync.sh index 87916cf0..1b06f91f 100755 --- a/tests/config-sync.sh +++ b/tests/config-sync.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME TEST_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" if [[ ! -f "$TEST_DIR/test_config_sync.py" ]]; then diff --git a/tests/doctor.sh b/tests/doctor.sh index 2a186a37..e7822db9 100755 --- a/tests/doctor.sh +++ b/tests/doctor.sh @@ -15,7 +15,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" REAL_GIT=$(command -v git) || { diff --git a/tests/e2e-harness-windows.sh b/tests/e2e-harness-windows.sh index 37067184..c728aa90 100755 --- a/tests/e2e-harness-windows.sh +++ b/tests/e2e-harness-windows.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # Run the unit test of the native Windows end-to-end driver # (tests/e2e-harness.ps1: tests/e2e/run.ps1 and tests/e2e/lib/*.ps1 against diff --git a/tests/e2e-harness.sh b/tests/e2e-harness.sh index 5545f439..beaa0449 100755 --- a/tests/e2e-harness.sh +++ b/tests/e2e-harness.sh @@ -27,7 +27,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" E2E_DIR="$REPO_ROOT/tests/e2e" diff --git a/tests/fcitx5-profile-sync.sh b/tests/fcitx5-profile-sync.sh index 95633556..c26760a8 100755 --- a/tests/fcitx5-profile-sync.sh +++ b/tests/fcitx5-profile-sync.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" SYNC="$REPO_ROOT/lab-ubuntu/fcitx5/.local/bin/fcitx5-profile-sync" diff --git a/tests/host-overlays.sh b/tests/host-overlays.sh index 774f0f70..5389d01b 100755 --- a/tests/host-overlays.sh +++ b/tests/host-overlays.sh @@ -18,7 +18,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" TEST_TMP="$(mktemp -d "${TMPDIR:-/tmp}/dotfiles-host-overlays.XXXXXX")" diff --git a/tests/powershell-profile.sh b/tests/powershell-profile.sh index f500a83d..13f2feea 100755 --- a/tests/powershell-profile.sh +++ b/tests/powershell-profile.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # Run the PowerShell profile and prompt theme contract wherever pwsh exists. REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" diff --git a/tests/run.ps1 b/tests/run.ps1 index 31f86fd7..ef6a37f9 100644 --- a/tests/run.ps1 +++ b/tests/run.ps1 @@ -13,7 +13,8 @@ foreach ($knob in @('DOTFILES_AUTO_UPDATE', 'DOTFILES_AUTO_STOW', 'DOTFILES_HOST '_DOTFILES_CHECKED', 'DOTFILES_STOW_WITHOUT_OH_MY_ZSH', 'DOTFILES_COLOR', 'AWESOME_SKILLS_AUTO_UPDATE', 'AWESOME_SKILLS_FORCE', 'AWESOME_SKILLS_BG', 'AWESOME_SKILLS_INSTALLER_URL', 'AWESOME_SKILLS_REFRESH_DAYS', '_AWESOME_SKILLS_CHECKED', - 'CODING_AGENT_KIT_CONFIG', 'CODING_AGENT_KIT_COLOR')) { + 'CODING_AGENT_KIT_CONFIG', 'CODING_AGENT_KIT_COLOR', 'CODING_AGENT_KIT_USAGE_FILE', + 'CODING_AGENT_KIT_TOKEN_ID', 'XDG_STATE_HOME')) { Remove-Item -LiteralPath "Env:$knob" -ErrorAction Ignore } diff --git a/tests/run.sh b/tests/run.sh index 9b279018..6c92a550 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # GitHub's runners start jobs with SIGPIPE ignored, so a writer into a pipe # whose reader stopped early prints "write error: Broken pipe" there instead diff --git a/tests/setup-host.sh b/tests/setup-host.sh index 0ad8b3d7..ff6ae170 100755 --- a/tests/setup-host.sh +++ b/tests/setup-host.sh @@ -15,7 +15,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" FIXTURES="$REPO_ROOT/tests/fixtures/bootstrap" diff --git a/tests/shell-profile.sh b/tests/shell-profile.sh index 86b6ccbb..bafa63c7 100755 --- a/tests/shell-profile.sh +++ b/tests/shell-profile.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" TEST_TMP="$(mktemp -d "${TMPDIR:-/tmp}/dotfiles-shell-profile.XXXXXX")" diff --git a/tests/sherlock-kit-delivery.sh b/tests/sherlock-kit-delivery.sh index ff599e1f..52bbd9b6 100755 --- a/tests/sherlock-kit-delivery.sh +++ b/tests/sherlock-kit-delivery.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # How the sherlock-kit submodule reaches lab-ubuntu, wsl-ubuntu and sherlock # (docs/sherlock-kit.md). The structure checks always run: the .gitmodules diff --git a/tests/stow-all.sh b/tests/stow-all.sh index 662a3d6e..c6c2b3c6 100755 --- a/tests/stow-all.sh +++ b/tests/stow-all.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" INSTALLER="$REPO_ROOT/stow-all.sh" diff --git a/tests/terminal.sh b/tests/terminal.sh index 4ef98f39..c0254656 100755 --- a/tests/terminal.sh +++ b/tests/terminal.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" export TERMINAL_LIB="$REPO_ROOT/lib/terminal.sh" diff --git a/tests/test-entrypoints.sh b/tests/test-entrypoints.sh index 417a336c..b16cb77d 100755 --- a/tests/test-entrypoints.sh +++ b/tests/test-entrypoints.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME TEST_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" TEST_TMP="$(mktemp -d "${TMPDIR:-/tmp}/dotfiles-test-entrypoints.XXXXXX")" diff --git a/tests/test_ai_integration.py b/tests/test_ai_integration.py index 4a4542eb..22f2b282 100644 --- a/tests/test_ai_integration.py +++ b/tests/test_ai_integration.py @@ -6,11 +6,15 @@ fixtures are the parts of the launcher suites' setUp they read. """ +import json import os from pathlib import Path +import select import shutil import subprocess +import sys import tempfile +import time import unittest ROOT = Path(__file__).resolve().parents[1] @@ -59,7 +63,8 @@ def setUp(self): self.env = os.environ.copy() # No configuration of the caller's coding-agent-kit is in reach. for name in (*ENV_CONFLICTS, "CODEX_HOME", "CODEX_SQLITE_HOME", "PYTHONPATH", "BASH_ENV", "ENV", "ZDOTDIR", - "XDG_CONFIG_HOME", "CODING_AGENT_KIT_CONFIG", "CODING_AGENT_KIT_COLOR"): + "XDG_CONFIG_HOME", "XDG_STATE_HOME", "CODING_AGENT_KIT_CONFIG", "CODING_AGENT_KIT_COLOR", + "CODING_AGENT_KIT_USAGE_FILE", "CODING_AGENT_KIT_TOKEN_ID", "CLAUDE_CODE_OAUTH_TOKEN"): self.env.pop(name, None) self.env.update(HOME=str(self.home), PATH=os.pathsep.join((str(self.command_bin), self.env["PATH"]))) @@ -78,17 +83,121 @@ def test_real_alias_files_run_codex_as(self): self.assertIn(f"Details and manual repair: {KIT.resolve()}/docs/account-launchers.md\n", result.stdout) +PREVIEW = ROOT / "common/zsh/.oh-my-zsh/custom/fzf-tab.zsh" +# fzf-tab runs the preview with the description of each candidate and these. +FAKE_FZF = r""" +import os, subprocess, sys +preview = next(a[len("--preview="):] for a in sys.argv[1:] if a.startswith("--preview=")) +header = next((int(a.split("=", 1)[1]) for a in sys.argv[1:] if a.startswith("--header-lines=")), 0) +lines = [line for line in sys.stdin.read().split("\n")[header:] if line] +with open(os.environ["FAKE_FZF_LOG"], "a") as log: + for number, line in enumerate(lines): + selected = os.environ["FAKE_FZF_LOG"] + f".{number}" + open(selected, "w").write(line) + result = subprocess.run([os.environ["SHELL"], "-c", preview.replace("{f}", selected)], + capture_output=True, text=True) + shown = line.split("\0")[1] if "\0" in line else line + log.write("=== " + shown + "\n" + result.stdout + result.stderr) +sys.exit(130) +""" + + class Enrollment(IsolatedHome): + def preview_home(self): + """Two accounts per launcher, a recorded email and a label, and the launchers on PATH.""" + (self.secret / "codex-B.json").write_text('{"tokens": {"access_token": "fabricated-sensitive-access-B"}}') + (self.secret / "codex-B.json").chmod(0o600) + for name in ("work", "play"): + (self.secret / f"claude-{name}").write_text("sk-ant-oat01-fabricated-sensitive-claude\n") + (self.secret / f"claude-{name}").chmod(0o600) + state = self.home / ".local/state/coding-agent-kit/accounts" + state.mkdir(parents=True, mode=0o700) + for name, data in (("codex-A.json", {"email": "alice@example.com", "plan": "plus"}), + ("claude-work.json", {"label": "Work: billing"})): + (state / name).write_text(json.dumps(data)) + (state / name).chmod(0o600) + (self.command_bin / "claude-as").symlink_to(SOURCE / "bin/claude-as") + @unittest.skipIf(os.name == "nt", "zsh and fzf-tab") - def test_fzf_preview_never_reads_secret_file(self): - zsh = shutil.which('zsh') + def test_account_preview_shows_account_info_and_never_reads_a_secret(self): + zsh = shutil.which("zsh") if not zsh: - self.skipTest('zsh not installed') - preview = ROOT / 'common/zsh/.oh-my-zsh/custom/fzf-tab.zsh' - command = 'source "$1"; zstyle -s :fzf-tab:complete:codex-as:accounts fzf-preview preview; word=A; realpath="$HOME/.secrets/codex-A.json"; my-fzf-preview.sh() { print "LEAK"; }; eval "$preview"' - result = subprocess.run([zsh, '-f', '-c', command, 'preview', str(preview)], env=self.env, text=True, capture_output=True) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout, 'A\n') + self.skipTest("zsh not installed") + self.preview_home() + # The context fzf-tab looks up for any argument of the launchers: their + # completions call _describe without _arguments, so it has no tag. + command = ('source "$1"; zstyle -s :fzf-tab:complete:"$2": fzf-preview preview; word=$3; group=$4; ' + 'realpath="$HOME/.secrets/codex-$word.json"; my-fzf-preview.sh() { print LEAK; }; eval "$preview"') + cases = (("codex-as", "A", "[Codex accounts]", "A (Codex)\nemail alice@example.com\nplan plus\n" + "usage not recorded yet\n"), + ("claude-as", "work", "[Claude accounts]", "work (Claude)\nlabel Work: billing\nusage not recorded yet\n"), + ("codex-as", "exec", "[Codex arguments]", "exec\n"), + ("claude-as", "--print", "[Claude arguments]", "--print\n"), + # Not an account (a renamed file, a stale completion): the word itself. + ("codex-as", "gone", "[Codex accounts]", "gone\n")) + for tool, word, group, expected in cases: + with self.subTest(tool=tool, word=word): + result = subprocess.run([zsh, "-f", "-c", command, "preview", str(PREVIEW), tool, word, group], + cwd=self.cwd, env=self.env, text=True, capture_output=True) + self.assertEqual((result.returncode, result.stdout, result.stderr), (0, expected, "")) + + @unittest.skipIf(os.name == "nt", "zsh and fzf-tab") + def test_real_fzf_tab_previews_accounts_through_the_kit(self): + # Real fzf-tab (FZF_TAB_TEST_DIR names a checkout; the bootstrap clones + # it to $ZSH_CUSTOM/plugins/fzf-tab) in an interactive zsh, with dotfiles' + # zstyles and a fake fzf that runs fzf-tab's own preview command. + zsh, plugin = shutil.which("zsh"), Path(os.environ.get("FZF_TAB_TEST_DIR", "/nonexistent")) + if not zsh or not (plugin / "fzf-tab.plugin.zsh").is_file(): + self.skipTest("zsh and an fzf-tab checkout in FZF_TAB_TEST_DIR") + import pty # POSIX only: native Windows Python has no termios, so not at module level + self.preview_home() + log = self.base / "previews.log" + # fzf-tab splits its fzf-command on spaces, so not below the spaced HOME. + fake = self.base / "fake-fzf" + fake.write_text(f"#!{sys.executable}\n" + FAKE_FZF) + fake.chmod(0o700) + leak = self.command_bin / "my-fzf-preview.sh" + leak.write_text(f"#!/bin/sh\necho LEAK >>'{log}'\n") + leak.chmod(0o700) + rc = self.base / "zdotdir" + rc.mkdir() + (rc / ".zshrc").write_text( + f"fpath=({ROOT}/common/coding-agent-kit/.oh-my-zsh/custom/completions $fpath)\n" + f"autoload -Uz compinit && compinit -u -d '{self.base}/zcompdump'\n" + f"source '{plugin}/fzf-tab.plugin.zsh'\nsource '{PREVIEW}'\n" + f"zstyle ':fzf-tab:*' fzf-command '{fake}'\nzstyle ':fzf-tab:*' fzf-flags --ansi\nPS1='READY> '\n") + env = self.env | {"ZDOTDIR": str(rc), "SHELL": zsh, "TERM": "xterm-256color", "FAKE_FZF_LOG": str(log), + "TMPPREFIX": str(self.base / "zsh"), "PATH": os.pathsep.join((str(self.command_bin), "/usr/bin", "/bin"))} + for line, expected in ((b"codex-as \t", ["=== A -- alice@example.com", "A (Codex)", "email alice@example.com", + "plan plus", "usage not recorded yet", + "=== B", "B (Codex)", "email not recorded yet", + "plan not recorded yet", "usage not recorded yet"]), + (b"claude-as \t", ["=== play", "play (Claude)", "label not set (claude-as --set-label play LABEL)", + "usage not recorded yet", "=== work -- Work: billing", "work (Claude)", + "label Work: billing", "usage not recorded yet"])): + with self.subTest(line=line): + log.unlink(missing_ok=True) + pid, fd = pty.fork() + if pid == 0: + os.chdir(self.cwd) + # No global rc: a distribution zshrc may run its own compinit, which prompts. + os.execve(zsh, [zsh, "--no-globalrcs", "-i"], env) + try: + output, deadline = b"", time.monotonic() + 20 + while b"READY> " not in output and time.monotonic() < deadline: + if select.select([fd], [], [], 0.1)[0]: + output += os.read(fd, 4096) + os.write(fd, line) + while not log.exists() and time.monotonic() < deadline: + if select.select([fd], [], [], 0.1)[0]: + os.read(fd, 4096) + time.sleep(0.5) + finally: + os.kill(pid, 9) # only the test's own interactive zsh + os.waitpid(pid, 0) + os.close(fd) + self.assertTrue(log.exists(), "fzf-tab never ran the fake fzf") + self.assertEqual(log.read_text().splitlines(), expected) @unittest.skipIf(os.name == "nt", "GNU Stow") def test_real_stow_installs_underscore_completions_and_ignores_appledouble(self): diff --git a/tests/unix-installer.sh b/tests/unix-installer.sh index cbdbfcf6..daeee8cf 100755 --- a/tests/unix-installer.sh +++ b/tests/unix-installer.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" TEST_TMP="$(mktemp -d "${TMPDIR:-/tmp}/dotfiles-real-install.XXXXXX")" diff --git a/tests/update-hooks.sh b/tests/update-hooks.sh index 65e55abb..78b78835 100755 --- a/tests/update-hooks.sh +++ b/tests/update-hooks.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # Exercise both login implementations with isolated homes, fake failures, # real local Git remotes, and independent-process lock contention. diff --git a/tests/windows-installer.sh b/tests/windows-installer.sh index 186fbe80..9a3ac484 100755 --- a/tests/windows-installer.sh +++ b/tests/windows-installer.sh @@ -8,7 +8,7 @@ set -euo pipefail unset DOTFILES_AUTO_UPDATE DOTFILES_AUTO_STOW DOTFILES_HOST DOTFILES_DIR _DOTFILES_CHECKED \ DOTFILES_STOW_WITHOUT_OH_MY_ZSH DOTFILES_COLOR AWESOME_SKILLS_AUTO_UPDATE AWESOME_SKILLS_FORCE \ AWESOME_SKILLS_BG AWESOME_SKILLS_INSTALLER_URL AWESOME_SKILLS_REFRESH_DAYS _AWESOME_SKILLS_CHECKED \ - CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR + CODING_AGENT_KIT_CONFIG CODING_AGENT_KIT_COLOR CODING_AGENT_KIT_USAGE_FILE CODING_AGENT_KIT_TOKEN_ID XDG_STATE_HOME # Unix contract checks supplement tests/windows-installer.ps1, which exercises # actual links and helper processes in the native Windows CI job. A normal