# Project export: HeyCodex

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: A macOS voice-input companion with clearer controls, on-device transcription, and optional Codex-powered refinement.
- Devpost: https://devpost.com/software/heycodex
- GitHub: https://github.com/GrShin5/heycodex-app
- Video: https://www.youtube.com/embed/@Gr.Shin5?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — GrShin5 (3 commits)

## Devpost submission (written by the team)

### Inspiration

I started HeyCodex because I wanted more control over voice input than the built-in dictation experience I was using gave me. I was already using Codex, and I wondered whether its Codex-app-server workflow could help me build a voice-input companion that matched the way I write, without adding another specialized voice-input subscription. HeyCodex was an in-progress personal project before Build Week, not a finished app. The event gave me a focused push to turn it into something clearer and more usable for other people.

### What it does

HeyCodex is a macOS voice-input app for writing in the apps you already use. Press a global hotkey, speak, and it transcribes Japanese or English on-device with Apple SpeechAnalyzer. You can insert the transcript as-is, or optionally use AI Assist to clean it up. Ask AI lets you speak an instruction about selected text or a general question. The privacy boundary matters: raw audio is transcribed on-device. When you choose AI Assist or Ask AI, the relevant text is passed to a separately installed Codex CLI running on your Mac, which may send a request to OpenAI under your authenticated account. I want HeyCodex to feel like a practical companion for people who use Codex, while keeping those choices visible and optional.

### How we built it

I built HeyCodex as a native macOS app with Swift, SwiftUI, and AppKit. The core experience combines Apple SpeechAnalyzer, global hotkeys, a recording HUD, and destination-aware text insertion. Optional AI features use the Codex CLI that the user installs and authenticates separately. During Build Week, I used Codex with GPT-5.6 extensively to review existing behavior, make product decisions, implement changes, and validate results. The work was not a claim that the entire app was created during the event; it was a focused period of moving an ongoing project toward a public-facing product.

### Challenges we ran into

The hardest part was turning a workflow that made sense to me into one a new person could understand without having the same context. That meant untangling setup and permissions, making hotkey states and recording feedback clear, guarding text insertion, and explaining where optional AI processing begins. Supporting Japanese and English without making settings even more confusing added another layer. I treated those friction points as product decisions, not only technical ones. Each change had to make the next action more obvious while preserving the ability to use ordinary voice input locally.

### Accomplishments we're proud of

I am proudest of moving HeyCodex beyond a personal setup and into a more complete product experience. Build Week work added and refined onboarding, input checks, restart behavior, settings, bilingual UI foundations, and safer AI-command interaction. The app now gives people a more understandable path from first launch to using voice input in their own workflow. I also built in practical guardrails: AI is optional, the app checks where it is about to insert text, and the interface communicates recording and processing state instead of hiding it. That combination is what makes HeyCodex feel useful rather than just technically possible.

### What we learned

The biggest lesson was that effective use of Codex was not just about asking it to write code. GPT-5.6 was especially useful to me as a way to move between review, design decisions, implementation, and validation. Choosing a model and reasoning level that matched the task helped me keep momentum, but the important decisions still came from testing real behavior and deciding what would be understandable to another person. I also learned that voice input needs clear boundaries. Fast transcription matters, but so do permission explanations, safe insertion, and a plain account of what optional AI features do with text.

### What's next

My next priority is to keep making the app more stable while improving and expanding the features people actually use. I also want to simplify the settings experience: it has grown alongside the app, and it now needs a calmer, clearer structure that makes important choices easy to find. Feedback from real users will guide those changes as I prepare HeyCodex for a public open-source release.

## README (from the GitHub repository)

# HeyCodex

[English](README.md) | [日本語](docs/ja/README.md) | [User guide](docs/USER_GUIDE.md) | [日本語ガイド](docs/ja/USER_GUIDE.md) | [Privacy](PRIVACY.md) | [プライバシー](docs/ja/PRIVACY.md)

HeyCodex is a macOS voice-input app for writing where you already work. Press a global hotkey, speak, and HeyCodex transcribes your voice on-device with Apple SpeechAnalyzer. You can then paste the transcript as-is or use the optional AI Assist flow through a separately installed OpenAI Codex CLI.

## Highlights

- On-device speech-to-text for Japanese and English
- Global hotkey input; the default is the fn key
- Optional AI Assist for cleanup, punctuation, and spoken corrections
- Ask AI mode for voice instructions, with optional selected-text context
- Pasteboard-aware insertion with destination checks and Secure Input protection
- Local settings, history, and personal dictionary under your macOS user account
- English and Japanese app UI, with independent display, speech-recognition, and output-language settings

## Requirements

- macOS 26 (Tahoe) or later
- Swift 6.2 or later; Command Line Tools are sufficient
- A separately installed and authenticated [OpenAI Codex CLI](https://www.npmjs.com/package/@openai/codex) for AI Assist and Ask AI

Run codex --version to check your installed version. HeyCodex does not bundle or redistribute the Codex CLI.

## Build and verify

~~~sh
# Build the app and its command-line regression runner
swift build

# Run offline regression checks
.build/debug/HeyCodex --test-regressions

# Assemble a signed Debug app bundle
./scripts/make_app.sh debug
~~~

The app bundle is written to dist/HeyCodex.app. A stable local signing identity is needed for macOS privacy permissions to remain associated with the app. If needed, run bash scripts/make_signing_cert.sh before building the app bundle.

For a release bundle, run:

~~~sh
./scripts/make_app.sh release
~~~

## First launch

HeyCodex needs these macOS permissions:

1. **Accessibility** — detects the global hotkey and sends the paste shortcut.
2. **Microphone** — records voice input.
3. **Speech Recognition** — lets SpeechAnalyzer transcribe on-device.

After granting permissions, quit and relaunch the app. Accessibility is checked when the process starts.

## Everyday use

Press the configured hotkey (fn by default), speak, then press it again to stop. The HUD shows the current state while HeyCodex records, transcribes, optionally refines, and inserts text.

Ask AI uses fn + Space to start and fn to stop by default. With selected text, it treats your spoken instruction as a request about that selection. Without a selection, it shows the answer in its own window instead of automatically inserting it.

See the [full user guide](docs/USER_GUIDE.md) or the [Japanese user guide](docs/ja/USER_GUIDE.md) for setup, settings, history controls, and troubleshooting.

## Privacy at a glance

Audio is transcribed on-device. When AI Assist is enabled, or when you use Ask AI, HeyCodex sends the relevant text to the Codex CLI running on your Mac; that CLI may send the request to OpenAI under your own authenticated account. Raw audio is not sent by HeyCodex to the Codex CLI.

Read the complete [Privacy Policy](PRIVACY.md) before using AI-backed features.

## OpenAI Build Week 2026

HeyCodex began before Build Week as a personal project that was still under active development. During the event, I used Codex with GPT-5.6 to review existing behavior, make product decisions, implement changes, and validate them. The work focused on making the project clearer, safer, and more usable for a broader range of people. The public evidence, scope boundary, and claim limits are documented in [docs/BUILD_WEEK_2026.md](docs/BUILD_WEEK_2026.md).

## Contributing and security

- [Contributing guide](CONTRIBUTING.md)
- [Security policy](SECURITY.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)

## License and trademarks

The source code is available under the [Apache License 2.0](LICENSE). Read [NOTICE](NOTICE) for the separate treatment of HeyCodex brand assets.

HeyCodex is an independent project and is not affiliated with, endorsed by, or sponsored by OpenAI. “OpenAI,” “ChatGPT,” and “Codex” are trademarks of their respective owners and are used only to identify the external runtime required by optional features.


## Detected evidence (automated analysis)

Indexed codebase: 75 recognized source files, 975 KB.
- Swift (language) — detected in the code

## Codebase structure (from repository index)

### Files (81 of 81)

```
.gitignore
CODE_OF_CONDUCT.md
CONTRIBUTING.md
docs/BUILD_WEEK_2026.md
docs/ja/PRIVACY.md
docs/ja/README.md
docs/ja/USER_GUIDE.md
docs/USER_GUIDE.md
LICENSE
NOTICE
Package.swift
PRIVACY.md
README.md
scripts/make_app.sh
scripts/make_signing_cert.sh
scripts/verify_app_identity.sh
SECURITY.md
Sources/HeyCodex/AppState.swift
Sources/HeyCodex/Audio/AudioRecorder.swift
Sources/HeyCodex/Audio/MicrophoneDeviceManager.swift
Sources/HeyCodex/Codex/AICommandEngine.swift
Sources/HeyCodex/Codex/AICommandModels.swift
Sources/HeyCodex/Codex/AppServerOutputSafetyGate.swift
Sources/HeyCodex/Codex/CleanupEngine.swift
Sources/HeyCodex/Codex/CodexAppServerClient.swift
Sources/HeyCodex/Codex/CodexErrorClassifier.swift
Sources/HeyCodex/Codex/CodexModelCatalog.swift
Sources/HeyCodex/Codex/CodexPathResolver.swift
Sources/HeyCodex/Codex/PidFileManager.swift
Sources/HeyCodex/HeyCodexApp.swift
Sources/HeyCodex/History/InputHistoryModels.swift
Sources/HeyCodex/History/InputHistoryStore.swift
Sources/HeyCodex/Hotkey/HotkeyBinding.swift
Sources/HeyCodex/Hotkey/HotkeyCaptureMonitor.swift
Sources/HeyCodex/Hotkey/HotkeyCaptureSession.swift
Sources/HeyCodex/Hotkey/HotkeyManager.swift
Sources/HeyCodex/Inject/PasteboardSnapshot.swift
Sources/HeyCodex/Inject/TextInjector.swift
Sources/HeyCodex/Permissions/LanguageSetupDebugLauncherView.swift
Sources/HeyCodex/Permissions/OnboardingDebugLauncherView.swift
Sources/HeyCodex/Permissions/OnboardingPracticeController.swift
Sources/HeyCodex/Permissions/OnboardingRestartCoordinator.swift
Sources/HeyCodex/Permissions/OnboardingRuntimeProfile.swift
Sources/HeyCodex/Permissions/OnboardingUIScale.swift
Sources/HeyCodex/Permissions/OnboardingView.swift
Sources/HeyCodex/Permissions/PermissionManager.swift
Sources/HeyCodex/Personalization/CustomInstructionOptimizer.swift
Sources/HeyCodex/Personalization/PersonalDictionaryStore.swift
Sources/HeyCodex/Pipeline/VoiceSession.swift
Sources/HeyCodex/Resources/en.lproj/InfoPlist.strings
Sources/HeyCodex/Resources/en.lproj/Localizable.strings
Sources/HeyCodex/Resources/ja.lproj/Localizable.strings
Sources/HeyCodex/Resources/prompts/ai_command_custom_instruction_optimization_system_en.md
Sources/HeyCodex/Resources/prompts/ai_command_custom_instruction_optimization_system.md
Sources/HeyCodex/Resources/prompts/ai_command_general_system_en.md
Sources/HeyCodex/Resources/prompts/ai_command_general_system.md
Sources/HeyCodex/Resources/prompts/ai_command_selected_system_en.md
Sources/HeyCodex/Resources/prompts/ai_command_selected_system.md
Sources/HeyCodex/Resources/prompts/cleanup_system_en.md
Sources/HeyCodex/Resources/prompts/cleanup_system.md
Sources/HeyCodex/Resources/prompts/custom_instruction_optimization_system_en.md
Sources/HeyCodex/Resources/prompts/custom_instruction_optimization_system.md
Sources/HeyCodex/Selection/InsertionDestination.swift
Sources/HeyCodex/Selection/SelectedTextCapture.swift
Sources/HeyCodex/Selection/SelectionCapturePolicy.swift
Sources/HeyCodex/Settings/SettingsStore.swift
Sources/HeyCodex/Settings/SettingsUIScale.swift
Sources/HeyCodex/Settings/SettingsView.swift
Sources/HeyCodex/Settings/SettingsWindowChrome.swift
Sources/HeyCodex/Settings/UserDictionaryWindow.swift
Sources/HeyCodex/STT/SpeechLanguagePreparationCoordinator.swift
Sources/HeyCodex/STT/TranscriptionEngine.swift
Sources/HeyCodex/Support/AppLanguage.swift
Sources/HeyCodex/Support/FileLogger.swift
Sources/HeyCodex/Support/OutputLanguagePolicy.swift
Sources/HeyCodex/Support/PromptResourceLoader.swift
Sources/HeyCodex/Support/RegressionTestSuite.swift
Sources/HeyCodex/UI/AICommandResultWindow.swift
Sources/HeyCodex/UI/AppConfirmationSheet.swift
Sources/HeyCodex/UI/PopupUIScale.swift
Sources/HeyCodex/UI/RecordingHUD.swift
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- fix: restore parallel HUD build
- fix: clarify Build Week context and stop-key prompt
- Initial public release (v0.1.0)

## Key source files (fetched from GitHub, selected and truncated for size)

### SECURITY.md

```markdown
# Security Policy

## Reporting a vulnerability

Please do not open a public issue for a security vulnerability.

Report it through [GitHub Private Vulnerability Reporting](https://github.com/GrShin5/heycodex-app/security/advisories/new). Please include clear reproduction steps, the affected version or commit, expected and actual behavior, and any practical impact.

This is a small, single-maintainer open-source project. We will make a reasonable effort to acknowledge reports and work with reporters to understand valid issues.

## Scope

Security-sensitive areas include:

- Accessibility usage and global hotkey handling
- Pasteboard handling, text insertion, and Secure Input behavior
- Codex CLI process invocation and app-server IPC parsing
- Local settings, history, dictionary, and custom-instruction files under the HeyCodex Application Support folder
- Selected-text capture for Ask AI

## Out of scope

- Vulnerabilities in the external Codex CLI itself; report those to OpenAI.
- Vulnerabilities in macOS or Apple system frameworks.
- Reports that require prior administrator or root access to the same Mac.

## Supported versions

The latest version available on the default branch is supported. Older revisions may receive a fix at the maintainer’s discretion.

```

### CONTRIBUTING.md

```markdown
# Contributing to HeyCodex

Thanks for considering a contribution. HeyCodex is a small solo-maintained project, so focused contributions are the easiest to review and maintain.

## Before opening an issue

- Search existing issues first.
- Include your macOS version, Swift toolchain version, and codex --version output.
- For UI, audio, or insertion issues, include exact steps, expected behavior, and actual behavior.
- Do not include private transcripts, API credentials, or full local paths in a public issue.

## Before opening a pull request

1. Discuss non-trivial work in an issue before investing in a large patch.
2. Keep one concern per pull request.
3. Preserve these behavior-critical safeguards unless you have a tested replacement:
   - SIGPIPE is ignored at app startup so the GUI does not crash on a broken pipe.
   - CodexPathResolver handles GUI launches that do not inherit a shell PATH.
   - CleanupEngine reuses its Codex thread and launches app-server with empty MCP-server and plugin configuration to control latency and scope.
   - SpeechAnalyzer warm-up stays resident to avoid the first-recording initialization delay.
4. Run the regression suite:

   ~~~sh
   swift build
   .build/debug/HeyCodex --test-regressions
   ~~~

5. If a change affects recording, insertion, permissions, hotkeys, or a GUI flow, run an appropriate manual macOS test before requesting review.

## Documentation and language

- English is the primary repository language.
- Keep the Japanese README, user guide, and privacy document consistent with user-facing English changes.
- Issues and pull-request descriptions may be written in English or Japanese.

## Style

- Match nearby code and keep diffs focused.
- Avoid project-wide reformatting in a feature change.
- Add comments only when the reason for a constraint is not obvious.
- Do not commit local notes, scratch files, build output, credentials, certificates, or personal paths.

```

### Package.swift

```swift
// swift-tools-version:6.2
import PackageDescription

let package = Package(
    name: "HeyCodex",
    platforms: [
        .macOS(.v26)
    ],
    targets: [
        .executableTarget(
            name: "HeyCodex",
            path: "Sources/HeyCodex",
            resources: [
                .copy("Resources/prompts"),
                .copy("Resources/ja.lproj"),
                .copy("Resources/en.lproj")
            ],
            swiftSettings: [
                .swiftLanguageMode(.v5)
            ]
        )
    ]
)

```

### scripts/verify_app_identity.sh

```shell
#!/bin/bash
# HeyCodex.app のbundle ID、コード署名、Designated Requirementを検証する。
#
# Usage:
#   bash scripts/verify_app_identity.sh <app>
#   bash scripts/verify_app_identity.sh <old-app> <new-app>
#   bash scripts/verify_app_identity.sh --signer-fingerprint <app>
#
# 2つの.appを渡した場合は、各.appの検証に加え、bundle IDとDesignated
# Requirementと署名者証明書が更新前後で連続していることを確認する。
# --signer-fingerprint はmake_app.sh内部でのみ使う機械可読な確認用。
set -euo pipefail

usage() {
  cat >&2 <<EOF
Usage:
  $0 <app>
  $0 <old-app> <new-app>
  $0 --signer-fingerprint <app>

  <app>                    1つのHeyCodex.appを検証する
  <old-app> <new-app>      更新前後の.appを検証し、識別と署名者の連続性を確認する
  --signer-fingerprint     署名者証明書のSHA-1を内部確認用に出力する
EOF
  exit 1
}

fail() {
  echo "エラー: $*" >&2
  exit 1
}

if [[ "$#" -eq 2 && "$1" == "--signer-fingerprint" ]]; then
  output_mode="signer-fingerprint"
elif [[ "$#" -eq 1 || "$#" -eq 2 ]]; then
  output_mode="standard"
else
  usage
fi

validated_bundle_id=""
validated_requirement=""
validated_signer_fingerprint=""

verify_app() {
  local requested_path="$1"
  local app_path info_plist bundle_id bundle_icon_file ls_ui_element signed_metadata signed_identifier requirement_metadata requirement
  local certificate_directory certificate_prefix leaf_certificate signer_fingerprint

  [[ -d "$requested_path" ]] || fail "アプリが見つかりません: $requested_path"
  app_path="$(cd "$requested_path" && pwd -P)"
  info_plist="$app_path/Contents/Info.plist"
  [[ -f "$info_plist" ]] || fail "Info.plistが見つかりません: $app_path"

  bundle_id="$(plutil -extract CFBundleIdentifier raw -o - "$info_plist" 2>/dev/null || true)"
  [[ -n "$bundle_id" ]] || fail "CFBundleIdentifierを読み取れません: $app_path"

  case "$bundle_id" in
    com.heycodex.app|com.heycodex.onboarding-debug|com.heycodex.language-setup-debug) ;;
    *) fail "想定外のbundle IDです: $bundle_id" ;;
  esac

  if [[ "$bundle_id" == "com.heycodex.app" ]]; then
    ls_ui_element="$(plutil -extract LSUIElement raw -o - "$info_plist" 2>/dev/null || true)"
    case "$ls_ui_element" in
      false|0) ;;
      *) fail "通常版はLSUIElement=falseでDock表示する必要があります: $app_path" ;;
    esac

    bundle_icon_file="$(plutil -extract CFBundleIconFile raw -o - "$info_plist" 2>/dev/null || true)"
    [[ "$bundle_icon_file" == "HeyCodex.icns" ]] || fail "通常版のCFBundleIconFileがHeyCodex.icnsではありません: $app_path"
    [[ -f "$app_path/Contents/Resources/$bundle_icon_file" ]] || fail "通常版のDockアイコンが見つかりません: $app_path"
    [[ -f "$app_path/Contents/Resources/HeyCodexMenuBarTemplate.pdf" ]] || fail "通常版のメニューバーテンプレートPDFが見つかりません: $app_path"
    [[ -f "$app_path/Contents/Resources/ja.lproj/Localizable.strings" ]] || fail "通常版の日本語文字列カタログが見つかりません: $app_path"
    [[ -f "$app_path/Contents/Resources/en.lproj/Localizable.strings" ]] || fail "通常版の英語文字列カタログが見つかりません: $app_path"
  else
    ls_ui_element="$(plutil -extract LSUIElement raw -o - "$info_plist" 2>/dev/null || true)"
    case "$ls_ui_element" in
      false|0) ;;
      *) fail "Debug版はLSUIElement=falseで確認用Windowを表示する必要があります: $app_path" ;;
    esac
    [[ -f "$app_path/Contents/Resources/ja.lproj/Localizable.strings" ]] || fail "Debug版の日本語文字列カタログが見つかりません: $app_path"
    [[ -f "$app_path/Contents/Resources/en.lproj/Localizable.strings" ]] || fail "Debug版の英語文字列カタログが見つかりません: $app_path"
  fi

  if ! codesign --verify --deep --strict --verbose=2 "$app_path" >/dev/null 2>&1; then
    fail "コード署名の検証に失敗しました: $app_path"
  fi

  signed_metadata="$(codesign -dvv "$app_path" 2>&1)"
  signed_identifier="$(printf '%s\n' "$signed_metadata" | sed -n 's/^Identifier=//p' | head -n 1)"
  [[ "$signed_identifier" == "$bundle_id" ]] || fail "署名のIdentifierとInfo.plistのbundle IDが一致しません: $app_path"

  if [[ "$signed_metadata" == *"Signature=adhoc" ]] || ! grep -q '^Authority=' <<<"$signed_metadata"; then
    fail "ad-hoc署名または信頼できる署名者のない.appです: $app_path"
  fi

  requirement_metadata="$(codesign -d -r- "$app_path" 2>&1)"
  requirement="$(printf '%s\n' "$requirement_metadata" | sed -n -E 's/^[[:space:]]*(#[[:space:]]*)?designated => //p' | tail -n 1)"
  [[ -n "$requirement" ]] || fail "Designated Requirementを読み取れません: $app_path"
  [[ "$requirement" == *"identifier \"$bundle_id\""* ]] || fail "Designated Requirementにbundle IDが含まれません: $app_path"

  certificate_directory="$(mktemp -d "${TMPDIR:-/tmp}/heycodex-signing.XXXXXX")" || fail "署名者証明書の検証用ディレクトリを作成できません。"
  certificate_prefix="$certificate_directory/certificate"
  if ! codesign -d --extract-certificates="$certificate_prefix" "$app_path" >/dev/null 2>&1; then
    rm -rf "$certificate_directory"
    fail "署名者証明書を抽出できません: $app_path"
  fi
  leaf_certificate="${certificate_prefix}0"
  if [[ ! -f "$leaf_certificate" ]]; then
    rm -rf "$certificate_directory"
    fail "署名者証明書を読み取れません: $app_path"
  fi
  signer_fingerprint="$(shasum -a 1 "$leaf_certificate" | awk '{ print toupper($1) }')"
  rm -rf "$certificate_directory"
  [[ "$signer_fingerprint" =~ ^[[:xdigit:]]{40}$ ]] || fail "署名者証明書のSHA-1を読み取れません: $app_path"

  validated_bundle_id="$bundle_id"
  validated_requirement="$requirement"
  validated_signer_fingerprint="$signer_fingerprint"

  if [[ "$output_mode" == "standard" ]]; then
    echo "検証OK: $(basename "$app_path")"
    echo "  bundle ID: $bundle_id"
    echo "  コード署名: 有効（非ad-hoc）"
    echo "  Designated Requirement: 取得済み"
    echo "  署名者証明書: 取得済み"
  fi
}

if [[ "$output_mode" == "signer-fingerprint" ]]; then
  verify_app "$2"
  printf '%s\n' "$validated_signer_fingerprint"
  exit 0
fi

if [[ "$#" -eq 1 ]]; then
  verify_app "$1"
  exit 0
fi

verify_app "$1"
old_bundle_id="$validated_bundle_id"
old_requirement="$validated_requirement"
old_signer_fingerprint="$validated_signer_fingerprint"

verify_app "$2"
new_bundle_id="$validated_bundle_id"
new_requirement="$validated_requirement"
new_signer_fingerprint="$validated_signer_fingerprint"

[[ "$old_bundle_id" == "$new_bundle_id" ]] || fail "更新前後でbundle IDが変わっています。"
[[ "$old_requirement" == "$new_requirement" ]] || fail "更新前後でDesignated Requirementが変わっています。"
[[ "$old_signer_fingerprint" == "$new_signer_fingerprint" ]]
[truncated — 98 more characters]
```

### scripts/make_signing_cert.sh

```shell
#!/bin/bash
# ローカル自己署名コード署名証明書 "HeyCodex Dev" をログインkeychainに作成するスクリプト。
#
# 目的: ad-hoc署名（codesign -s -）はビルドごとにアイデンティティが変わり、TCC権限
# （マイク・音声認識・アクセシビリティ）が再ビルドのたびに無効化されてしまう。
# 安定した自己署名証明書を使うことで、再ビルド後もTCC権限を再登録せずに済む。
#
# 既に "HeyCodex Dev" 証明書が存在すればスキップし、証明書のSHA-1ハッシュを出力する。
# CIなど非対話環境では --non-interactive を渡すと、作成不可の場合でも失敗させず
# メッセージを出してスキップする。
set -euo pipefail

CERT_NAME="HeyCodex Dev"
NON_INTERACTIVE=0
INTERACTIVE_HELP=0

for arg in "$@"; do
  case "$arg" in
    --non-interactive) NON_INTERACTIVE=1 ;;
    --interactive-help) INTERACTIVE_HELP=1 ;;
    *) ;;
  esac
done

existing_hash() {
  security find-identity -v -p codesigning | grep "\"$CERT_NAME\"" | head -1 | awk '{print $2}'
}

# codesigning用アイデンティティ（信頼済み）として既に使える場合。
HASH="$(existing_hash || true)"
if [[ -n "$HASH" ]]; then
  echo "=== 証明書 \"$CERT_NAME\" は既に存在し、信頼設定済みです ==="
  echo "SHA-1: $HASH"
  exit 0
fi

# keychainに証明書自体は存在するが信頼設定が未完了の場合（再実行での重複作成を防ぐ）。
# 冪等性のため、この時点で既に証明書がkeychainにあれば新規作成せず案内のみ再表示する。
EXISTING_CERT_IN_KEYCHAIN="$(security find-certificate -c "$CERT_NAME" "$HOME/Library/Keychains/login.keychain-db" 2>/dev/null | grep -c "labl" || true)"
if [[ "${EXISTING_CERT_IN_KEYCHAIN:-0}" -gt 0 ]]; then
  echo "=== 証明書 \"$CERT_NAME\" はkeychainに既に存在しますが、信頼設定（Trust）が未完了です ==="
  PERSISTENT_CERT_DIR="$HOME/Library/Application Support/HeyCodex"
  PERSISTENT_CERT_PATH="$PERSISTENT_CERT_DIR/heycodex_dev_cert.crt"
  if [[ -f "$PERSISTENT_CERT_PATH" ]]; then
    echo "信頼設定を完了するには以下を実行し、認証ダイアログで承認してください:" >&2
    echo "  security add-trusted-cert -k \"\$HOME/Library/Keychains/login.keychain-db\" \"$PERSISTENT_CERT_PATH\"" >&2
  else
    echo "手動作成手順: bash $0 --interactive-help" >&2
  fi
  exit 0
fi

echo "=== 証明書 \"$CERT_NAME\" が見つかりません ==="

if [[ "$INTERACTIVE_HELP" -eq 1 ]]; then
  cat << 'EOF'
=== 手動作成手順（インタラクティブモード） ===
1. 「キーチェーンアクセス.app」を開く
2. メニューバー: キーチェーンアクセス → 証明書アシスタント → 証明書を作成...
3. 名前: "HeyCodex Dev"
4. identity種類: 自己署名ルート
5. 証明書の種類: コード署名
6. 「デフォルトを上書き」にチェックし、鍵ペア用途を「このアイテムのデフォルトを常に使用」から
   コード署名を確実に含める設定にして作成
7. 作成後、「ログイン」キーチェーンに保存されていることを確認
8. ターミナルで `security find-identity -v -p codesigning` を実行し、
   "HeyCodex Dev" が一覧に出ることを確認する

作成後、このスクリプトを再実行すればハッシュが表示されます。
EOF
  exit 0
fi

if [[ "$NON_INTERACTIVE" -eq 1 ]]; then
  echo "非対話モードのため証明書作成をスキップします。"
  echo "手動作成手順が必要な場合は: bash $0 --interactive-help"
  exit 0
fi

# 自己署名コード署名証明書を自動生成する。
# security create-keychainは使わず、既定のログインkeychainへ証明書を追加する形を取る。
# opensslで自己署名証明書＋秘密鍵を生成し、PKCS#12として一時的にエクスポートしてから
# securityコマンドでログインkeychainへインポートする（ユーザーのkeychainパスワード入力を
# 極力避けるため、既にアンロック状態のログインkeychainへの追加を試みる）。
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT

KEY_PATH="$WORKDIR/heycodex_dev.key"
CERT_PATH="$WORKDIR/heycodex_dev.crt"
P12_PATH="$WORKDIR/heycodex_dev.p12"
P12_PASSWORD="heycodex-dev-temp"

echo "=== 自己署名証明書を生成しています ==="
openssl req -x509 -newkey rsa:2048 -keyout "$KEY_PATH" -out "$CERT_PATH" \
  -days 3650 -nodes -subj "/CN=$CERT_NAME" \
  -addext "keyUsage=critical,digitalSignature" \
  -addext "extendedKeyUsage=critical,codeSigning" >/dev/null 2>&1

# -legacy: OpenSSL 3.x はデフォルトでAES暗号化のPKCS#12を生成するが、macOSの
# securityコマンド（SecKeychainItemImport）はこれを正しく復号できずMAC検証エラーになる
# 場合がある。-legacyでRC2/3DES系の従来形式にすることでmacOS側と互換性を持たせる。
openssl pkcs12 -export -out "$P12_PATH" -inkey "$KEY_PATH" -in "$CERT_PATH" \
  -name "$CERT_NAME" -passout "pass:$P12_PASSWORD" -legacy

echo "=== ログインkeychainへインポートしています ==="
if security import "$P12_PATH" -k "$HOME/Library/Keychains/login.keychain-db" \
  -P "$P12_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security; then
  echo "インポートに成功しました。"
else
  echo "インポートに失敗しました。keychainがロックされている可能性があります。" >&2
  echo "手動作成手順: bash $0 --interactive-help" >&2
  exit 1
fi

# コード署名で使えるように、信頼設定に「常に信頼」を付与する（自己署名のため必要）。
# 重要: `add-trusted-cert` はGUIの認証ダイアログ（Touch ID/パスワード入力）を要求する。
# これは意図的なOSのセキュリティゲートであり、非対話シェルからは絶対に自動承認できない
# （承認するまでプロセスは無期限にブロックする）。そのためtimeoutで打ち切り、
# 承認されなかった場合は明確にその旨を案内する。
TRUST_TIMEOUT_SECONDS=5
if command -v timeout >/dev/null 2>&1; then
  TIMEOUT_CMD="timeout"
else
  # BSD/macOS標準にはtimeoutが無いことがあるため簡易フォールバック。
  TIMEOUT_CMD=""
fi

TRUST_ADDED=0
if [[ -n "$TIMEOUT_CMD" ]]; then
  # `-r trustAsRoot` は自己署名ルート証明書向けの厳格な設定だが、macOSによっては
  # `SecTrustSettingsSetTrustSettings: parameters were not valid` エラーで拒否されることがある。
  # `-r trustAsRoot` を省略した通常のadd-trusted-certでも codeSigning EKU を持つ自己署名証明書は
  # `find-identity -p codesigning` に登録されるため、まずこちらを試す。
  if $TIMEOUT_CMD "$TRUST_TIMEOUT_SECONDS" security add-trusted-cert -k "$HOME/Library/Keychains/login.keychain-db" "$CERT_PATH" 2>/dev/null; then
    TRUST_ADDED=1
  fi
else
  echo "注意: 'timeout'コマンドが無いため信頼設定の自動付与はスキップします（GUI承認ダイアログが必要なため）。" >&2
fi

NEW_HASH="$(existing_hash || true)"

if [[ "$TRUST_ADDED" -eq 1 && -n "$NEW_HASH" ]]; then
  echo "=== 証明書 \"$CERT_NAME\" を作成し、信頼設定も完了しました ==="
  echo "SHA-1: $NEW_HASH"
  exit 0
fi

# 証明書自体はkeychainに存在するがcodesigning用アイデンティティとしては未認識
# （信頼設定が付与されていない）。keychainから証明書を永続パスへ再エクスポートし、
# WORKDIR削除後も手動コマンドが使えるようにしておく。
PERSISTENT_CERT_DIR="$HOME/Library/Application Support/HeyCodex"
mkdir -p "$PERSISTENT_CERT_DIR"
PERSISTENT_CERT_PATH="$PERSISTENT_CERT_DIR/heycodex_dev_cert.crt"
security find-certificate -c "$CERT_NAME" -p "$HOME/Library/Keychains/login.keychain-db" > "$PERSISTENT_CERT_PATH" 2>/dev/null || \
  cp "$CERT_PATH" "$PERSISTENT_CERT_PATH"

CERT_HASH="$(openssl x509 -in "$PERSISTENT_CERT_PATH" -noout -fingerprint -sha1 2>/dev/null | sed 's/^.*=//')"
echo "=== 証明書 \"$CERT_NAME\" はkeychainに作成されましたが、信頼設定（Trust）が未完了です ===" >&2
echo "SHA-1 (証明書): ${CERT_HASH:-不明}" >&2
echo "" >&2
echo "macOSの仕様上、自己署名証明書を codesign で使えるようにする「常に信頼」設定は" >&2
echo "GUIの認証ダイアログでの明示的な承認が必須で、スクリプトから自動化できません。" >&2
echo "以下のいずれかの方法で信頼設定を完了してください:" >&2
echo "  1. 「キーチェーンアクセス.app」を開き、\"$CERT_NAME\" を検索 → ダブルクリック →" >&2
echo "     「信頼」セクションを展開 → 「コード署名」を「常に信頼」に変更" >&2
echo "  2. またはターミナルで以下を実行し、表示される認証ダイアログで承認する:" >&2
echo "     security add-trusted-cert -k \"\$HOME/Library/Keychains/login.keychain-db\" \"$PERSISTENT_CERT_PATH\"" >&2
echo "" >&2
echo "信頼設定完了後、'security find-identity -v -p codesigning' 
[truncated — 232 more characters]
```

### scripts/make_app.sh

```shell
#!/bin/bash
# swift build成果物を HeyCodex.app バンドルに組み立てるスクリプト。
set -euo pipefail

cd "$(dirname "$0")/.."
ROOT_DIR="$(pwd)"

CONFIGURATION="debug"
PREVIOUS_APP_PATH=""
if [[ "$#" -gt 0 && "$1" != "--previous-app" ]]; then
  CONFIGURATION="$1"
  shift
fi

while [[ "$#" -gt 0 ]]; do
  case "$1" in
    --previous-app)
      [[ "$#" -ge 2 ]] || {
        echo "エラー: --previous-app には更新前の.appへのパスが必要です。" >&2
        exit 1
      }
      [[ -z "$PREVIOUS_APP_PATH" ]] || {
        echo "エラー: --previous-app は1回だけ指定できます。" >&2
        exit 1
      }
      PREVIOUS_APP_PATH="$2"
      shift 2
      ;;
    *)
      echo "Usage: $0 [debug|release|onboarding-debug|language-setup-debug] [--previous-app /path/to/HeyCodex.app]" >&2
      exit 1
      ;;
  esac
done

if [[ "$CONFIGURATION" != "debug" && "$CONFIGURATION" != "release" && "$CONFIGURATION" != "onboarding-debug" && "$CONFIGURATION" != "language-setup-debug" ]]; then
  echo "Usage: $0 [debug|release|onboarding-debug|language-setup-debug] [--previous-app /path/to/HeyCodex.app]" >&2
  exit 1
fi

CERT_NAME="HeyCodex Dev"
IDENTITY_LIST="$(security find-identity -v -p codesigning 2>/dev/null || true)"
MATCHING_IDENTITIES=()
while IFS= read -r identity; do
  [[ -n "$identity" ]] && MATCHING_IDENTITIES+=("$identity")
done < <(printf '%s\n' "$IDENTITY_LIST" | awk -v certificate_name="$CERT_NAME" '
  index($0, "\"" certificate_name "\"") && $2 ~ /^[[:xdigit:]]{40}$/ { print toupper($2) }
')

if [[ "${#MATCHING_IDENTITIES[@]}" -gt 1 ]]; then
  cat >&2 <<EOF
エラー: "$CERT_NAME" と同名の有効なコード署名アイデンティティが複数あります。

権限を維持するには、毎回同じ証明書を使う必要があります。不要な同名証明書を
キーチェーンから整理して1つにしてから、もう一度実行してください。
EOF
  exit 1
fi

SIGN_IDENTITY="${MATCHING_IDENTITIES[0]:-}"

if [[ ! "$SIGN_IDENTITY" =~ ^[[:xdigit:]]{40}$ ]]; then
  cat >&2 <<EOF
エラー: 安定したコード署名アイデンティティ "$CERT_NAME" が見つかりません。

マイク、音声認識、アクセシビリティのmacOS権限を更新後も維持するため、
ad-hoc署名（codesign --sign -）ではHeyCodex.appを作成しません。

次の手順で準備してください:
  1. bash scripts/make_signing_cert.sh
  2. 表示されるmacOSの認証ダイアログで、コード署名の信頼設定を承認する
  3. security find-identity -v -p codesigning を実行し、"$CERT_NAME" を確認する

証明書がkeychainにあるのに使えない場合は、次も参照してください:
  bash scripts/make_signing_cert.sh --interactive-help
EOF
  exit 1
fi

IDENTITY_STATE_FILE="$ROOT_DIR/dist/.heycodex-signing-identity"
PINNED_SIGN_IDENTITY=""
if [[ -f "$IDENTITY_STATE_FILE" ]]; then
  PINNED_SIGN_IDENTITY="$(tr -d '[:space:]' < "$IDENTITY_STATE_FILE")"
  if [[ ! "$PINNED_SIGN_IDENTITY" =~ ^[[:xdigit:]]{40}$ ]]; then
    echo "エラー: 署名IDの固定情報が壊れています: $IDENTITY_STATE_FILE" >&2
    exit 1
  fi
  PINNED_SIGN_IDENTITY="$(printf '%s' "$PINNED_SIGN_IDENTITY" | tr '[:lower:]' '[:upper:]')"
  if [[ "$SIGN_IDENTITY" != "$PINNED_SIGN_IDENTITY" ]]; then
    cat >&2 <<EOF
エラー: 今回選ばれた署名証明書が、HeyCodexで固定済みの証明書と一致しません。

固定済みの証明書を使用するか、署名を変更する理由と既存利用者への権限再許可の
影響を確認してから、明示的な署名移行手順を行ってください。
EOF
    exit 1
  fi
fi

echo "=== 署名: 安定した \"$CERT_NAME\" 証明書を使用 ==="

if [[ "$CONFIGURATION" == "release" ]]; then
  echo "=== swift build (-c release) ==="
  swift build -c release
  BIN_PATH=".build/release/HeyCodex"
else
  echo "=== swift build (debug) ==="
  swift build
  BIN_PATH=".build/debug/HeyCodex"
fi

if [[ "$CONFIGURATION" == "onboarding-debug" ]]; then
  APP_NAME="HeyCodex Debug.app"
  APP_DISPLAY_NAME="HeyCodex Debug"
  BUNDLE_IDENTIFIER="com.heycodex.onboarding-debug"
  LS_UI_ELEMENT="<false/>"
  ICON_PLIST_ENTRY=""
elif [[ "$CONFIGURATION" == "language-setup-debug" ]]; then
  APP_NAME="HeyCodex Language Setup Debug.app"
  APP_DISPLAY_NAME="HeyCodex Language Setup Debug"
  BUNDLE_IDENTIFIER="com.heycodex.language-setup-debug"
  LS_UI_ELEMENT="<false/>"
  ICON_PLIST_ENTRY=""
else
  APP_NAME="HeyCodex.app"
  APP_DISPLAY_NAME="HeyCodex"
  BUNDLE_IDENTIFIER="com.heycodex.app"
  # 通常版はDockと⌘Tabに表示する。
  LS_UI_ELEMENT="<false/>"
  ICON_PLIST_ENTRY=$'    <key>CFBundleIconFile</key>\n    <string>HeyCodex.icns</string>'
fi

BRANDING_DIR="$ROOT_DIR/Assets/Branding"
BRANDING_ICONSET_DIR="$BRANDING_DIR/HeyCodex.iconset"
BRANDING_MENU_TEMPLATE="$BRANDING_DIR/HeyCodexMenuBarTemplate.pdf"
if [[ "$CONFIGURATION" == "debug" || "$CONFIGURATION" == "release" ]]; then
  [[ -d "$BRANDING_ICONSET_DIR" ]] || {
    echo "エラー: Dockアイコンのiconsetが見つかりません: $BRANDING_ICONSET_DIR" >&2
    exit 1
  }
  for icon_name in \
    icon_16x16.png \
    icon_16x16@2x.png \
    icon_32x32.png \
    icon_32x32@2x.png \
    icon_128x128.png \
    icon_128x128@2x.png \
    icon_256x256.png \
    icon_256x256@2x.png \
    icon_512x512.png \
    icon_512x512@2x.png; do
    [[ -f "$BRANDING_ICONSET_DIR/$icon_name" ]] || {
      echo "エラー: Dockアイコンの必須サイズが見つかりません: $BRANDING_ICONSET_DIR/$icon_name" >&2
      exit 1
    }
  done
  [[ -f "$BRANDING_MENU_TEMPLATE" ]] || {
    echo "エラー: メニューバー用テンプレートPDFが見つかりません: $BRANDING_MENU_TEMPLATE" >&2
    exit 1
  }
  command -v iconutil >/dev/null 2>&1 || {
    echo "エラー: macOSのiconutilが見つからないため、Dockアイコンを生成できません。" >&2
    exit 1
  }
fi

TARGET_APP_DIR="$ROOT_DIR/dist/$APP_NAME"
if [[ -n "$PREVIOUS_APP_PATH" ]]; then
  [[ -d "$PREVIOUS_APP_PATH" ]] || {
    echo "エラー: 更新前のアプリが見つかりません: $PREVIOUS_APP_PATH" >&2
    exit 1
  }
  PREVIOUS_APP_PATH="$(cd "$PREVIOUS_APP_PATH" && pwd -P)"
fi

TARGET_APP_IS_STABLE=false
EXTERNAL_REFERENCE_IS_STABLE=false
if [[ -d "$TARGET_APP_DIR" ]]; then
  target_signer="$(bash "$ROOT_DIR/scripts/verify_app_identity.sh" --signer-fingerprint "$TARGET_APP_DIR" 2>/dev/null || true)"
  if [[ -n "$target_signer" ]]; then
    target_signer="$(printf '%s' "$target_signer" | tr '[:lower:]' '[:upper:]')"
    if [[ "$target_signer" != "$SIGN_IDENTITY" ]]; then
      cat >&2 <<EOF
エラー: 既存の$(basename "$TARGET_APP_DIR")は別の安定署名で作成されています。

現在の.appを上書きするとmacOS権限が継続しない可能性があるため、ビルドを中止しました。
署名移行の影響を確認してから、明示的な移行手順で作業してください。
EOF
      exit 1
    fi
    TARGET_APP_IS_STABLE=true
  else
    echo "注意: 既存の$(basename "$TARGET_APP_DIR")は安定署名として検証できません。" >&2
    echo "      不安定署名版からの初回移行では、macOSで一度だけ権限の再許可が必要になる場合があります。" >&2
  fi
fi

if [[ -n "$PREVIOUS_APP_PATH" && "$PREVIOUS_APP_PATH" != "$TARGET_APP_DIR" ]]; then
  external_signer="$(b
[truncated — 4969 more characters]
```

### Sources/HeyCodex/AppState.swift

```swift
import Foundation

/// アプリ全体のパイプライン状態。
enum PipelinePhase: Equatable {
    case idle
    case starting
    case recording
    case transcribing
    case cleaning
    case inserting
    case error(String)
}

@MainActor
final class AppState: ObservableObject {
    @Published var phase: PipelinePhase = .idle
    @Published var lastTranscript: String = ""
    @Published var lastCleanedText: String = ""
    @Published var lastErrorMessage: String?
    /// 録音開始時刻。自動停止タイマーの経過秒計算、HUDの経過時間表示に使う。
    @Published var recordingStartedAt: Date?
    /// 現在の入力音声レベル（0.0-1.0、対数スケール正規化済み）。録音HUDの波形が購読する。
    @Published var audioLevel: Double = 0

    /// .errorになってから自動的に.idleへ戻すためのタスク。次のsetPhaseで前回分はキャンセルされる。
    private var errorRecoveryTask: Task<Void, Never>?

    /// .errorフェーズをHUDに表示する時間（秒）。この後自動的に.idleへ復帰する。
    private let errorDisplaySeconds: UInt64 = 3

    func setPhase(_ phase: PipelinePhase) {
        errorRecoveryTask?.cancel()
        errorRecoveryTask = nil

        self.phase = phase
        if phase != .recording {
            audioLevel = 0
        }
        switch phase {
        case .error(let message):
            self.lastErrorMessage = message
            scheduleAutoRecoveryFromError()
        default:
            self.lastErrorMessage = nil
        }
    }

    private func scheduleAutoRecoveryFromError() {
        errorRecoveryTask = Task { [weak self] in
            try? await Task.sleep(nanoseconds: (self?.errorDisplaySeconds ?? 3) * 1_000_000_000)
            guard !Task.isCancelled else { return }
            guard let self else { return }
            if case .error = self.phase {
                self.setPhase(.idle)
            }
        }
    }

    func statusText(language: AppLanguage) -> String {
        switch phase {
        case .idle: return AppLocalizer.text("待機中", language: language)
        case .starting: return AppLocalizer.text("録音準備中...", language: language)
        case .recording: return AppLocalizer.text("録音中...", language: language)
        case .transcribing: return AppLocalizer.text("文字起こし中...", language: language)
        case .cleaning: return AppLocalizer.text("AIアシスト中...", language: language)
        case .inserting: return AppLocalizer.text("挿入中...", language: language)
        case .error(let message):
            return AppLocalizer.format(
                "エラー: %@",
                language: language,
                AppLocalizer.text(message, language: language)
            )
        }
    }

    var statusText: String { statusText(language: .japanese) }
}

```

### Sources/HeyCodex/Pipeline/VoiceSession.swift

```swift
import Foundation

enum VoiceMode: String, Codable {
    case voiceInput = "voice_input"
    case aiCommand = "ai_command"
}

@MainActor
final class VoiceSession {
    let id = UUID()
    let mode: VoiceMode
    let startedAt = Date()
    let selectedText: String?
    let aiCommandSettings: AICommandSettings?
    /// 通常モードはWeb入力欄でも⌘Vを送れるよう、AX要素ではなく前面アプリだけを保存する。
    var normalPasteTarget: NormalPasteTarget?
    /// 選択テキストの置換だけは、従来どおり厳格なAX対象を使う。
    var insertionDestination: InsertionDestination?
    private(set) var isCancelled = false

    init(mode: VoiceMode, selectedText: String? = nil, aiCommandSettings: AICommandSettings? = nil) {
        self.mode = mode
        self.selectedText = selectedText
        self.aiCommandSettings = aiCommandSettings
    }

    func cancel() {
        isCancelled = true
    }
}

```

### Sources/HeyCodex/Inject/PasteboardSnapshot.swift

```swift
import AppKit

/// Preserves every item and type on a pasteboard. Restoration is deliberately
/// conditional so a concurrent user copy is never overwritten.
struct PasteboardSnapshot {
    private let items: [[(NSPasteboard.PasteboardType, Data)]]

    init(_ pasteboard: NSPasteboard) {
        items = (pasteboard.pasteboardItems ?? []).map { item in
            item.types.compactMap { type in
                item.data(forType: type).map { (type, $0) }
            }
        }
    }

    @discardableResult
    func restore(to pasteboard: NSPasteboard, ifChangeCountIs expected: Int) -> Bool {
        guard pasteboard.changeCount == expected else { return false }
        let restored = items.map { values -> NSPasteboardItem in
            let item = NSPasteboardItem()
            for (type, data) in values {
                item.setData(data, forType: type)
            }
            return item
        }
        for _ in 0..<2 {
            pasteboard.clearContents()
            if items.isEmpty || pasteboard.writeObjects(restored) {
                return true
            }
        }
        return false
    }
}

```

### Sources/HeyCodex/Codex/CodexErrorClassifier.swift

```swift
import Foundation

enum CodexRPCFailureKind: Equatable {
    case rateLimited
    case quotaExhausted
    case authFailed
    case other

    var userMessage: String {
        switch self {
        case .rateLimited:
            return "レートリミットに達しています。しばらく待ってから再試行してください"
        case .quotaExhausted:
            return "利用枠を使い切っています。プランや請求設定を確認してください"
        case .authFailed:
            return "Codexの認証に失敗しています。codex loginで再ログインしてください"
        case .other:
            return ""
        }
    }

    var diagnosticLabel: String {
        switch self {
        case .rateLimited:
            return "レートリミット"
        case .quotaExhausted:
            return "利用枠エラー"
        case .authFailed:
            return "認証エラー"
        case .other:
            return ""
        }
    }
}

enum CodexErrorClassifier {
    /// JSON-RPC本文は認証URLやその他の機微情報を含み得るため、数値codeだけで分類する。
    static func classify(code: Int) -> CodexRPCFailureKind {
        switch code {
        case 429:
            return .rateLimited
        case 402:
            return .quotaExhausted
        case 401, 403:
            return .authFailed
        default:
            return .other
        }
    }
}

```

[59 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]