Compare commits

...

2 Commits

Author SHA1 Message Date
Anton Volnuhin
a84778a640 fix(kitty): read file permissions correctly where stat is GNU
`chezmoi apply` on Linux printed "could not regenerate kitty theme
files" every run. GNU stat reads -f as --file-system, so
`stat -f '%Lp' file` prints a block of filesystem info to stdout and
exits non-zero. The `|| stat -c '%a'` fallback then appended the real
answer to that block, and chmod rejected the result, failing the script
under set -e.

Replace the chain with a file_mode() helper in both scripts: try the GNU
form first, since BSD rejects -c cleanly without printing anything, and
accept the answer only if it is octal. Covered by a new case in
tests/test_theme_mode.sh using a stat stub that reproduces GNU's
behaviour; it fails against the old chain.
2026-08-06 02:39:49 +03:00
Anton Volnuhin
5b58460618 Track agent CLI configs, with codex secrets externalized
codex's config.toml held nine live API keys inline under
[mcp_servers.*.env] -- deepseek, gemini, kimi, mimo, qwen, glm, cursor,
an OpenAI key and a Home Assistant token -- so it could not be tracked
as it stood. TOML env tables take literal values only, with no ${VAR}
indirection or env_file, so the keys cannot simply be named there.

Move them to ~/.codex/secrets.env (0600, ignored) and route the four
affected servers through mcp-with-secrets.sh, which sources that file
and execs the real command. Doing it in the wrapper rather than relying
on exported shell vars means it does not depend on how codex handles
environment inheritance, and works on ssh-only boxes with no keychain or
1Password agent, which is why this was preferred over a chezmoi
template.

Adds configs for claude, codex, pi, grok, kimi, kimi-code and reasonix,
and extends .chezmoiignore so credentials, session logs, local state and
.bak files stay out even if a directory is ever added whole.

Held back for still containing live keys: opencode.json (bailian), and
reasonix config.json and .env.
2026-08-06 02:23:49 +03:00
19 changed files with 1566 additions and 10 deletions

View File

@ -11,6 +11,36 @@ Library
.pi/agent/auth.json .pi/agent/auth.json
.pi/agent/sessions .pi/agent/sessions
# agent CLIs: configs are tracked file by file, but never credentials, session
# logs or local state. Belt and braces in case a directory is ever added whole.
.claude/.credentials.json
.claude/projects
.claude/history.jsonl
.claude/file-history
.claude/telemetry
.claude/session-env
.claude/shell-snapshots
.codex/secrets.env
.codex/auth.json
.codex/accounts
.codex/*.bak
.codex/*.bak-*
.codex/*.bak.*
.codex/sessions
.codex/archived_sessions
.codex/logs*
.codex/*.sqlite*
.grok/auth.json*
.grok/projects
.grok/sessions
.kimi/credentials
.kimi/sessions
.kimi-code/oauth
.kimi-code/sessions
.reasonix/.env
.reasonix/sessions
.reasonix/projects
# kitty: the light/dark mode is per-machine, and the *-theme.auto.conf files # kitty: the light/dark mode is per-machine, and the *-theme.auto.conf files
# are generated from it plus .config/kitty/themes. Tracking them would make # are generated from it plus .config/kitty/themes. Tracking them would make
# every machine flag the others' mode. run_after_kitty-theme-mode.sh # every machine flag the others' mode. run_after_kitty-theme-mode.sh

16
dot_claude/CLAUDE.md Normal file
View File

@ -0,0 +1,16 @@
# Global Rules
## Promises need mechanisms
Never promise behavioral changes with just words ("won't forget", "I'll make sure to"). Each conversation starts fresh — words have no effect on future behavior. When committing to a change:
1. Add to CLAUDE.md (always loaded) — not just memory
2. Add automated enforcement where possible (hooks, linters, CI checks)
3. Never say "I won't forget" without having done at least one of the above
## Watch long-running work yourself; don't trust notifiers
When you launch anything long-running (background workflow, agent swarm, build, deploy, remote/SSH process), do NOT rely on auto-completion notifications — they can silently fail to fire when the job dies. Actively re-check the job's real state (on-disk progress, file mtime, process liveness) on a **≤10-minute cadence**, enforced by a mechanism (a re-arming background watchdog or scheduled self-check), not a promise. Each check: confirm recent progress, detect stalls/silent deaths early, and resume or restart if it died. Prefer resume (cached/idempotent work) over a cold restart.
## Ask, don't guess
When something fails (SSH, commands, APIs, unexpected errors), report the error and ask the user how to proceed. Don't burn time trying alternative ports, paths, or workarounds — the user knows their system and can quickly point to the right answer.
## No throwaway implementations
Don't deploy placeholder or half-baked versions that will be replaced later. Either build the real implementation or wait until the design is ready. Deploying throwaway code wastes time and creates false confidence.

View File

@ -0,0 +1,326 @@
{
"cleanupPeriodDays": 36500,
"includeCoAuthoredBy": false,
"permissions": {
"allow": [
"Bash(tree:*)",
"Bash(wc:*)",
"Bash(head:*)",
"Bash(pdftotext:*)",
"Bash(python3:*)",
"Bash(source .venv/bin/activate)",
"Skill(superpowers:executing-plans)",
"Bash(python validate_data.py:*)",
"Bash(./process_month.sh:*)",
"Bash(bash:*)",
"Bash(ls:*)",
"Bash(python processing-step.py:*)",
"Bash(tr:*)",
"Bash(python3.12:*)",
"Bash(/opt/homebrew/bin/python3:*)",
"Bash(python tbank_process.py:*)",
"Bash(python freedom-pdf-to-csv-converter.py:*)",
"Bash(python xls_to_csv.py:*)",
"Bash(python:*)",
"WebSearch",
"Bash(grep:*)",
"Bash(git add:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nImprove data processing pipeline with better parsing and validation\n\n- Fix number format: use dot decimal separator consistently across all processors\n- Fix date formats: standardize to DD.MM.YYYY in source files, YYYY-MM-DD in consolidated\n- Add transaction filtering: exclude internal transfers, currency exchanges, income\n- Parse merchant/location from Freedom, Raiffeisen, Bakai, Yandex descriptions\n- Preserve operation types in comment field \\(no info loss rule\\)\n- Fix Freedom dates: Date column is processing_date, extract transaction_date from Payment_Purpose\n- Upgrade Yandex PDF converter to gemini-3-flash-preview for better OCR\n- Add raiffeisen_process.py for number format normalization\n- Add test-run.md documenting all processing steps and fixes\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(echo \"=== Checking if unmatched receipts have matching pairs ===\" echo \"\" echo \"Unmatched 2775.0 \\(Dec 4\\):\" grep -l \"2775\" /Users/anton/dev/moneystat/processing/2025-12/receipts/ozon/anton/*.json)",
"Bash(while read f)",
"Bash(do jq '{date: .date, amount: .total_amount, payment: .payment_method}' \"$f\" done echo \"\" echo \"Unmatched 3841.0 \\(Dec 14\\):\" grep -l \"3841\" /Users/anton/dev/moneystat/processing/2025-12/receipts/ozon/anton/*.json)",
"Bash(do jq '{date: .date, amount: .total_amount, payment: .payment_method}' \"$f\" done echo \"\" echo \"Unmatched 972, 1373 \\(Dec 29\\):\" for amt in 972 1373)",
"Bash(do grep -l \"$amt\" /Users/anton/dev/moneystat/processing/2025-12/receipts/ozon/anton/*.json)",
"Bash(do jq '{date: .date, amount: .total_amount, payment: .payment_method}' \"$f\" done done echo \"\" echo \"Unmatched 957 \\(Dec 30\\):\" grep -l \"957\" /Users/anton/dev/moneystat/processing/2025-12/receipts/ozon/anton/*.json)",
"Bash(find:*)",
"Bash(xargs:*)",
"Bash(sort:*)",
"Bash(for f in *.json)",
"Bash(do echo -n \"$f: \")",
"Bash(done)",
"Bash(for f in data/2025-12/receipts/apple/RUB-Anton/*.eml:*)",
"Bash(do)",
"Bash(cat:*)",
"Bash(git commit:*)",
"Bash(pip3 list:*)",
"Bash(echo:*)",
"Bash(source venv/bin/activate)",
"Bash(git push)",
"Bash(for f in \"data/2025-11/receipts/grab/Your Grab E-Receipt 2.eml\" \"data/2025-11/receipts/grab/Your Grab E-Receipt 5.eml\")",
"Bash(for f in data/2025-11/receipts/grab/*.eml)",
"Bash(__NEW_LINE_7bc163cf2bda176a__ echo \"\")",
"Bash(__NEW_LINE_9da33bceca79eb86__ echo \"\")",
"Bash(pip show:*)",
"Bash(uv pip install:*)",
"Bash(do cat \"$f\")",
"Bash(jq:*)",
"Bash(plutil:*)",
"Bash(pip install:*)",
"Bash(.venv/bin/pip install:*)",
"Bash(.venv/bin/python:*)",
"Bash(pdfinfo:*)",
"Bash(__NEW_LINE_4acfd7b3af874ff7__ echo \"\")",
"Bash(# Check final consolidated file for duplicates wc -l processing/2025-11/*.csv)",
"Bash(# Check for TBank duplicates in consolidated file grep -c \"\"tbank\"\" processing/2025-11/consolidated.csv || echo \"\"No tbank found\"\" # Check unique vs total TBank rows echo \"\"=== TBank rows in consolidated ===\"\" grep \"\"tbank\"\" processing/2025-11/consolidated.csv)",
"Bash(__NEW_LINE_49e63631a274ada1__ echo \"=== Unique TBank rows \\(by date+amount+description\\) ===\")",
"Bash(# Check December for similar issues ls -la processing/2025-12/tbank* || echo \"\"No tbank files in December\"\" echo \"\"\"\" echo \"\"=== TBank rows in December consolidated ===\"\" grep -c \"\"tbank\"\" processing/2025-12/consolidated.csv || echo \"\"No consolidated.csv\"\" echo \"\"\"\" echo \"\"=== Unique TBank rows in December ===\"\" grep \"\"tbank\"\" processing/2025-12/consolidated.csv)",
"Bash(# Check for duplicates by source in consolidated file echo \"\"=== November 2025 ===\"\" echo \"\"Raiffeisen rows:\"\" grep -c \"\"raiffeisen\"\" processing/2025-11/consolidated.csv || echo \"\"0\"\" echo \"\"Freedom rows:\"\" grep -c \"\"freedom\"\" processing/2025-11/consolidated.csv || echo \"\"0\"\" echo \"\"\"\" echo \"\"=== All processed files in November ===\"\" ls -la processing/2025-11/*.processed.csv)",
"Bash(cd /Users/anton/dev/moneystat echo \"=== Source files in data/2025-12 ===\" find data/2025-12 -name \"*.csv\" -o -name \"*.pdf\")",
"Bash(__NEW_LINE_8b3f2148807d3dea__ echo \"\")",
"Bash(__NEW_LINE_d3565e1917fc1e0c__ echo \"=== data/2025-12 structure ===\")",
"Bash(__NEW_LINE_d3565e1917fc1e0c__ echo \"\")",
"Bash(__NEW_LINE_b4a495b4f804ce33__ echo \"=== Freedom source files ===\")",
"Bash(__NEW_LINE_b4a495b4f804ce33__ echo \"\")",
"Bash(__NEW_LINE_9b873e71c557a635__ echo \"=== December 2025: Source files vs Consolidated ===\")",
"Bash(__NEW_LINE_9b873e71c557a635__ echo \"\")",
"Bash(__NEW_LINE_a6dec1f39c81c8c3__ echo \"=== Freedom USD current file ===\")",
"Bash(__NEW_LINE_a6dec1f39c81c8c3__ echo \"\")",
"Bash(__NEW_LINE_8cb166f3ad9c1f18__ python3 -c \"\nimport pandas as pd\nf = ''processing/2025-12/freedom-legal_statement_2025-12-01_2025-12-31_current_1769192100-usd.processed.csv''\ndf = pd.read_csv\\(f\\)\nprint\\(f''Actual records: {len\\(df\\)}''\\)\nprint\\(f''Columns: {list\\(df.columns\\)}''\\)\n\")",
"Bash(cd /Users/anton/dev/moneystat:*)",
"Bash(__NEW_LINE_52b89477ddfdbf8d__ python3 -c \"\nimport pandas as pd\n\n# Check total sum in consolidated\ndf = pd.read_csv\\(''processing/2025-12/consolidated.csv''\\)\nprint\\(f''Total records: {len\\(df\\)}''\\)\nprint\\(f''Total RUB amount: {df[\"\"amount_rub\"\"].sum\\(\\):,.2f}''\\)\n\n# By source\nfor source in df[''info_source''].unique\\(\\):\n subset = df[df[''info_source''] == source]\n print\\(f'' {source}: {len\\(subset\\)} records, {subset[\"\"amount_rub\"\"].sum\\(\\):,.2f} RUB''\\)\n\")",
"Bash(__NEW_LINE_3ae1f017591cfd98__ echo \"\")",
"Bash(__NEW_LINE_60ad38285f384d62__ python3 -c \"\nimport pandas as pd\n\n# Test auto-detect\nf = ''processing/2025-12/ozon-Волнухин_А_Ю_о_движении_денежных_средств_ozonbank_document_23316120.processed.csv''\n\n# Check first line\nwith open\\(f, ''r''\\) as file:\n first_line = file.readline\\(\\)\n print\\(''First line:'', repr\\(first_line\\)\\)\n print\\(''Has semicolon:'', '';'' in first_line\\)\n print\\(''Has comma:'', '','' in first_line\\)\n\n# Read with comma\ndf = pd.read_csv\\(f, delimiter='','', encoding=''utf-8-sig''\\)\nprint\\(\\)\nprint\\(''Columns:'', list\\(df.columns\\)\\)\nprint\\(''Rows:'', len\\(df\\)\\)\nprint\\(\\)\nprint\\(''Sample:''\\)\nprint\\(df.head\\(2\\)\\)\n\")",
"Bash(__NEW_LINE_c3c45e4fba8a30f6__ python3 -c \"\n# Simulate what consolidating-step does for is_internal_transaction\ntext = ''Выдача кредита по договору №2025-06-30-KK- 04581056279272189866, Волнухин Антон Юрьевич''\nsource = ''ozon''\n\ncombined_text = text.lower\\(\\)\nprint\\(''combined_text:'', combined_text\\)\nprint\\(\\)\nprint\\(''Check: выдача кредита in combined_text?'', ''выдача кредита'' in combined_text\\)\nprint\\(''source:'', source\\)\nprint\\(''source != ozon?'', source != ''ozon''\\)\n\")",
"Bash(__NEW_LINE_051a249934ae9d8e__ python consolidating-step.py processing/2025-12)",
"Bash(__NEW_LINE_a5635cd054e2af5d__ python consolidating-step.py processing/2025-12)",
"Bash(__NEW_LINE_be0bb1dd57b96096__ python consolidating-step.py processing/2025-12)",
"Bash(do python -c \"import json; d=json.load\\(open\\(''$f''\\)\\); items=[i for i in d.get\\(''items'',[]\\) if ''Auto Reload'' not in i.get\\(''name'',''''\\)]; print\\(f\"\"{d.get\\(''order_date''\\)}: {sum\\(i[''price''] for i in items\\)} RUB\"\"\\)\")",
"Bash(do rm -v \"$f\")",
"WebFetch(domain:discuss.ai.google.dev)",
"Bash(awk:*)",
"Bash(file:*)",
"Bash(git rm:*)",
"Bash(__NEW_LINE_c0ebfadfa2d82e8e__ echo -e \"\\\\n=== TT NET ===\")",
"Bash(__NEW_LINE_d9661d77cbc2b8df__ echo -e \"\\\\n=== Осетинский интернет ===\")",
"Bash(__NEW_LINE_eb27aace61ca558d__ echo -e \"\\\\n=== Steam ===\")",
"Bash(__NEW_LINE_b138d527c99ac4d4__ echo -e \"\\\\n=== MOFT/Elago/чехол ===\")",
"Bash(curl:*)",
"WebFetch(domain:ai.google.dev)",
"WebFetch(domain:colab.research.google.com)",
"Bash(for script in consolidated-process-ozon.py consolidated-process-samokat.py consolidated-process-vkusvill.py consolidated-process-amazon.py consolidated-process-aliexpress.py consolidated-process-apple-usd.py consolidated-process-steam.py consolidated-process-grab.py)",
"Bash(for amount in 16.33 49 1565 987 400 3262 1471)",
"Bash(perl:*)",
"Bash(cut:*)",
"Bash(git restore:*)",
"Bash(chmod:*)",
"Bash(shasum:*)",
"Bash(# Check if there are any Ozon PDFs to test with ls data/2025-09/receipts/ozon/*/*.pdf)",
"Bash(__NEW_LINE_c3e7f2c4c5b0eb5a__ echo \"\")",
"Bash(shuf:*)",
"Bash(# Check how many files remain ls processing/2025-09/altcats/*.json)",
"Bash(do rm \"$f\")",
"Bash(LLM_DEBUG=1 python run-pipeline.py:*)",
"Bash(LLM_DEBUG=1 python src/stages/stage_3_categories.py:*)",
"Bash(git reset:*)",
"Bash(uv pip:*)",
"Bash(pkill:*)",
"Bash(for i in 1 2 3 4 5)",
"Bash(do sleep 5)",
"Bash(for month in 2025-06 2025-07 2025-09 2025-10 2025-11 2025-12)",
"Bash(# Check for any 2024 dates in cached Lavka JSONs grep -r \"\"2024\"\" /Users/anton/dev/moneystat/processing/2026-01/receipts/yandex/*Lavka*/*.json)",
"Bash(for month in 2025-08 2025-09 2025-10 2025-11 2025-12 2026-01)",
"Bash(git -C /Users/anton/dev/moneystat status)",
"Bash(git -C /Users/anton/dev/moneystat diff)",
"Bash(git -C /Users/anton/dev/moneystat log --oneline -5)",
"Bash(git -C /Users/anton/dev/moneystat log --oneline -3)",
"Bash(git -C /Users/anton/dev/moneystat diff src/parsers/parse_yandex_taxi.py)",
"Bash(git -C /Users/anton/dev/moneystat add src/parsers/parse_yandex_taxi.py)",
"Bash(git -C /Users/anton/dev/moneystat commit -m \"$\\(cat <<''EOF''\nCache downloaded Yandex Taxi invoices\n\nInvoice HTML files are now cached in .invoice_cache directory to avoid\nre-downloading on each pipeline run. Uses the unique invoice URL hash\nas cache key. Reduces parse time from ~7s to ~0.3s for cached invoices.\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(git -C /Users/anton/dev/moneystat diff src/processors/consolidated_process_samokat.py)",
"Bash(git -C /Users/anton/dev/moneystat add src/processors/consolidated_process_samokat.py)",
"Bash(git -C /Users/anton/dev/moneystat commit -m \"$\\(cat <<''EOF''\nFix: Exclude Ozon transactions from Samokat matching\n\nOzon sellers named \"Самокат Плюс, ООО\" were incorrectly identified as\nSamokat grocery delivery. Now transactions already matched to Ozon\n\\(info_source contains ''ozon''\\) are excluded from Samokat matching.\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(for parser in parse_ozon parse_yandex parse_grab parse_samokat parse_vkusvill parse_apple parse_amazon parse_aliexpress parse_steam)",
"Bash(for parser in parse_yandex_eda parse_yandex_taxi parse_lavka)",
"Bash(__NEW_LINE_7b4f4fc6eb9d9e49__ echo \"\")",
"Bash(__NEW_LINE_3b4b538c51270f95__ echo \"\")",
"Bash(__NEW_LINE_6f837450c2794e10__ echo \"\")",
"Bash(__NEW_LINE_cdd8a54243e075d5__ echo \"\")",
"Bash(git check-ignore:*)",
"Bash(node:*)",
"Bash(python /tmp/test_month_nav.py:*)",
"Bash(uv run:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nAdd visual month navigator with mini donut chart previews\n\nReplace dropdown selector with a visual navigator featuring:\n- Left/right arrow buttons for month navigation\n- Clickable month buttons with mini donut chart previews\n- Russian month labels \\(e.g., \"Январь '25\"\\)\n- Responsive layout for mobile screens\n- Arrows disable at navigation boundaries\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Skill(commit-commands:commit)",
"mcp__plugin_playwright_playwright__browser_navigate",
"mcp__plugin_playwright_playwright__browser_take_screenshot",
"mcp__plugin_playwright_playwright__browser_run_code",
"mcp__plugin_playwright_playwright__browser_snapshot",
"mcp__plugin_playwright_playwright__browser_click",
"mcp__plugin_playwright_playwright__browser_press_key",
"mcp__plugin_playwright_playwright__browser_close",
"mcp__plugin_playwright_playwright__browser_console_messages",
"mcp__plugin_playwright_playwright__browser_evaluate",
"WebFetch(domain:github.com)",
"mcp__plugin_playwright_playwright__browser_wait_for",
"mcp__plugin_playwright_playwright__browser_navigate_back",
"Bash(lsof:*)",
"mcp__plugin_playwright_playwright__browser_resize",
"mcp__plugin_playwright_playwright__browser_hover",
"Bash(git checkout:*)",
"mcp__plugin_playwright_playwright__browser_tabs",
"Bash(DATA_DIR=. node -e:*)",
"Bash(git merge:*)",
"Bash(git status:*)",
"Bash(xcrun simctl:*)",
"Bash(open:*)",
"Bash(osascript:*)",
"mcp__home-assistant__ha_get_overview",
"mcp__home-assistant__ha_search_entities",
"mcp__home-assistant__ha_config_list_areas",
"mcp__home-assistant__ha_config_list_floors",
"mcp__home-assistant__ha_config_list_groups",
"mcp__home-assistant__ha_get_integration",
"mcp__home-assistant__ha_config_get_automation",
"mcp__home-assistant__ha_config_get_script",
"Bash(ssh:*)",
"Bash(scp:*)",
"mcp__home-assistant__ha_config_get_dashboard",
"mcp__home-assistant__ha_get_state",
"mcp__home-assistant__ha_get_entity",
"mcp__home-assistant__ha_config_list_helpers",
"WebFetch(domain:thesmarthomejourney.com)",
"WebFetch(domain:community.home-assistant.io)",
"WebFetch(domain:magicareas.io)",
"WebFetch(domain:danielbkr.net)",
"WebFetch(domain:newerest.space)",
"WebFetch(domain:www.aaron-powell.com)",
"WebFetch(domain:mikesolin.com)",
"WebFetch(domain:www.dima.pm)",
"WebFetch(domain:joshdotai.medium.com)",
"WebFetch(domain:blog.abodit.com)",
"WebFetch(domain:www.derekseaman.com)",
"WebFetch(domain:blog.shalvah.me)",
"mcp__home-assistant__ha_deep_search"
],
"defaultMode": "auto"
},
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' Waiting # notchbar-agents-claude-hook"
}
]
}
],
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' Auto # notchbar-agents-claude-hook"
}
]
}
],
"PostToolUseFailure": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' ToolFail # notchbar-agents-claude-hook"
}
]
}
],
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' Working # notchbar-agents-claude-hook"
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' Ended # notchbar-agents-claude-hook"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' Idle # notchbar-agents-claude-hook"
}
]
},
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash '/Users/anton/.claude/hooks/herdr-agent-state.sh' session",
"timeout": 10
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' Idle # notchbar-agents-claude-hook"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/claude-event-hook.sh' Working # notchbar-agents-claude-hook"
}
]
}
]
},
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline-command.sh"
},
"enabledPlugins": {
"clangd-lsp@claude-plugins-official": true,
"codex@openai-codex": true,
"commit-commands@claude-code-plugins": true,
"document-skills@anthropic-agent-skills": true,
"frontend-design@claude-plugins-official": true,
"github@claude-plugins-official": true,
"gopls-lsp@claude-plugins-official": false,
"playwright@claude-plugins-official": true,
"rust-analyzer-lsp@claude-plugins-official": true,
"superpowers@claude-plugins-official": true,
"superpowers@superpowers-marketplace": true
},
"extraKnownMarketplaces": {
"openai-codex": {
"source": {
"source": "github",
"repo": "openai/codex-plugin-cc"
}
}
},
"effortLevel": "xhigh",
"tui": "fullscreen",
"autoMemoryEnabled": true,
"autoDreamEnabled": true,
"skipDangerousModePermissionPrompt": true,
"skipWorkflowUsageWarning": true,
"theme": "auto",
"verbose": true,
"preferredNotifChannel": "ghostty",
"remoteControlAtStartup": false,
"agentPushNotifEnabled": true,
"skipAutoPermissionPrompt": true
}

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Claude Code status line — inspired by Powerlevel10k/Tide layout
# Segments: user@host cwd git branch model context %
input=$(cat)
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // ""')
model=$(echo "$input" | jq -r '.model.display_name // ""')
used_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
# --- user@host ---
user=$(whoami)
host=$(hostname -s)
# --- pretty cwd: replace $HOME with ~ ---
home="$HOME"
short_cwd="${cwd/#$home/\~}"
# --- git branch (skip optional locks) ---
git_branch=""
if git_out=$(git -C "$cwd" --no-optional-locks symbolic-ref --short HEAD 2>/dev/null); then
git_branch="$git_out"
elif git_out=$(git -C "$cwd" --no-optional-locks rev-parse --short HEAD 2>/dev/null); then
git_branch="$git_out"
fi
# --- context bar ---
context_str=""
if [ -n "$used_pct" ]; then
used_int=${used_pct%.*}
if [ "${used_int:-0}" -ge 80 ]; then
ctx_color="\033[31m" # red
elif [ "${used_int:-0}" -ge 50 ]; then
ctx_color="\033[33m" # yellow
else
ctx_color="\033[32m" # green
fi
context_str=" ${ctx_color}ctx:${used_pct%.*}%\033[0m"
fi
# --- assemble ---
# user@host in cyan, cwd in bold, git branch in magenta, model in dim
printf "\033[36m%s@%s\033[0m \033[1m%s\033[0m" "$user" "$host" "$short_cwd"
if [ -n "$git_branch" ]; then
printf " \033[35m\ue0a0 %s\033[0m" "$git_branch"
fi
if [ -n "$model" ]; then
printf " \033[2m%s\033[0m" "$model"
fi
if [ -n "$context_str" ]; then
printf "%b" "$context_str"
fi

View File

@ -0,0 +1,21 @@
# Populate GitHub MCP auth for Codex from a GitHub personal access token.
# The token value is exported into the process environment but is not stored here.
case "${CODEX_GITHUB_PERSONAL_ACCESS_TOKEN:-}" in
github_pat_*|ghp_*) ;;
*)
unset CODEX_GITHUB_PERSONAL_ACCESS_TOKEN
if command -v security >/dev/null 2>&1; then
_codex_github_token="$(security find-generic-password -a "$USER" -s codex-github-mcp-pat -w 2>/dev/null || true)"
else
_codex_github_token=""
fi
case "$_codex_github_token" in
github_pat_*|ghp_*)
export CODEX_GITHUB_PERSONAL_ACCESS_TOKEN="$_codex_github_token"
;;
esac
unset _codex_github_token
;;
esac

76
dot_codex/hooks.json Normal file
View File

@ -0,0 +1,76 @@
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/codex-notify-hook.sh' # notchbar-agents-codex-hook",
"type": "command"
}
],
"matcher": "Bash"
}
],
"PreToolUse": [
{
"hooks": [
{
"command": "'/Users/anton/.codex/hooks/confirm-destructive.sh'",
"timeout": 5,
"type": "command"
}
],
"matcher": "Bash"
},
{
"hooks": [
{
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/codex-notify-hook.sh' # notchbar-agents-codex-hook",
"type": "command"
}
],
"matcher": "Bash"
}
],
"SessionStart": [
{
"hooks": [
{
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/codex-notify-hook.sh' # notchbar-agents-codex-hook",
"type": "command"
}
],
"matcher": "startup|resume"
},
{
"hooks": [
{
"command": "bash '/Users/anton/.codex/herdr-agent-state.sh' session",
"timeout": 10,
"type": "command"
}
]
}
],
"Stop": [
{
"hooks": [
{
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/codex-notify-hook.sh' # notchbar-agents-codex-hook",
"type": "command"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"command": "'/Users/anton/Library/Application Support/Bartender/NotchBar/AgentStatus/hooks/codex-notify-hook.sh' # notchbar-agents-codex-hook",
"type": "command"
}
]
}
]
}
}

View File

@ -0,0 +1,6 @@
[
{
"command": "hotkeyWindow",
"key": "Alt+Space"
}
]

View File

@ -0,0 +1,437 @@
model = "gpt-5.6-sol"
model_reasoning_effort = "high"
personality = "pragmatic"
plan_mode_reasoning_effort = "xhigh"
sandbox_mode = "workspace-write"
service_tier = "priority"
notify = ["/Users/anton/.codex/computer-use/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]
[features]
hooks = true
apps = true
multi_agent = true
prevent_idle_sleep = true
memories = true
js_repl = false
[marketplaces.openai-bundled]
last_updated = "2026-07-28T17:16:25Z"
source_type = "local"
source = "/Users/anton/.codex/.tmp/bundled-marketplaces/openai-bundled"
[marketplaces.openai-primary-runtime]
last_updated = "2026-07-16T08:15:09Z"
source_type = "local"
source = "/Users/anton/.cache/codex-runtimes/codex-primary-runtime/plugins/openai-primary-runtime"
[marketplaces.claude-plugins-official]
last_updated = "2026-08-05T22:59:06Z"
last_revision = "0ad65e5d170c501235183be69c4e4be30a5f81d8"
source_type = "git"
source = "https://github.com/anthropics/claude-plugins-official.git"
[marketplaces.openai-codex]
last_updated = "2026-07-08T07:57:58Z"
last_revision = "db52e28f4d9ded852ab3942cea316258ae4ef346"
source_type = "git"
source = "https://github.com/openai/codex-plugin-cc.git"
[mcp_servers.agent-team-wiki]
url = "https://wiki.agent-team.ru/mcp"
[mcp_servers.codex-cli]
command = "npx"
args = ["-y", "codex-mcp-server"]
[mcp_servers.excalidraw]
url = "https://excalidraw-mcp-app.vercel.app/mcp"
[mcp_servers.glm]
command = "/Users/anton/.codex/mcp-with-secrets.sh"
args = ["node", "/Users/anton/dev/glm/scripts/glm-mcp.mjs"]
startup_timeout_sec = 15.0
tool_timeout_sec = 1200.0
[mcp_servers.glm.env]
[mcp_servers.glm.tools.glm_consult]
approval_mode = "approve"
[mcp_servers.glm.tools.glm_freeform]
approval_mode = "approve"
[mcp_servers.glm.tools.glm_review]
approval_mode = "approve"
[mcp_servers.multipoly]
command = "/Users/anton/.codex/mcp-with-secrets.sh"
args = ["node", "/Users/anton/dev/multipoly/scripts/multipoly-mcp.mjs"]
startup_timeout_sec = 15.0
tool_timeout_sec = 600.0
[mcp_servers.multipoly.env]
MULTIPOLY_AGY_CLI_KIND = "agy"
MULTIPOLY_AGY_ENABLED = "1"
MULTIPOLY_AGY_TRANSPORT = "cli"
MULTIPOLY_AGY_UNSAFE = "1"
MULTIPOLY_CLAUDE_CLI_KIND = "claude"
MULTIPOLY_CLAUDE_ENABLED = "1"
MULTIPOLY_CLAUDE_MODEL = "claude-opus-4-7"
MULTIPOLY_CLAUDE_TRANSPORT = "cli"
MULTIPOLY_CODEX_CLI_KIND = "codex"
MULTIPOLY_CODEX_ENABLED = "1"
MULTIPOLY_CODEX_MODEL = "gpt-5.5"
MULTIPOLY_CODEX_TRANSPORT = "cli"
MULTIPOLY_COMPOSER_ENABLED = "1"
MULTIPOLY_DEEPSEEK_BASE_URL = "https://api.deepseek.com"
MULTIPOLY_FABLE_CLI_KIND = "claude"
MULTIPOLY_FABLE_ENABLED = "1"
MULTIPOLY_FABLE_MODEL = "claude-fable-5"
MULTIPOLY_FABLE_TRANSPORT = "cli"
MULTIPOLY_GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai"
MULTIPOLY_GEMINI_MODEL = "gemini-3.5-flash"
MULTIPOLY_GEMINI_TRANSPORT = "http"
MULTIPOLY_GLM_MAX_TOKENS_REVIEW = "8192"
MULTIPOLY_GROK_CLI_KIND = "grok"
MULTIPOLY_GROK_ENABLED = "1"
MULTIPOLY_GROK_MODEL = "grok-4.5"
MULTIPOLY_GROK_TRANSPORT = "cli"
MULTIPOLY_KIMI_BASE_URL = "https://api.kimi.com/coding"
MULTIPOLY_KIMI_MAX_TOKENS_CONSULT = "65536"
MULTIPOLY_KIMI_MODEL = "kimi-k.3.0"
MULTIPOLY_KIMI_REASONING = "kimi_toggle"
MULTIPOLY_KIMI_TRANSPORT = "anthropic"
MULTIPOLY_MIMO_BASE_URL = "https://token-plan-sgp.xiaomimimo.com/v1"
MULTIPOLY_MIMO_MAX_TOKENS_REVIEW = "8192"
MULTIPOLY_MIMO_MODEL = "mimo-v2.5-pro"
MULTIPOLY_MIMO_TRANSPORT = "http"
MULTIPOLY_MODELS = "kimi,gemini,claude,codex,agy,mimo,grok,fable"
MULTIPOLY_QWEN_BASE_URL = "https://ws-duxbs2mi0ocqxzxp.eu-central-1.maas.aliyuncs.com/compatible-mode/v1"
MULTIPOLY_QWEN_MODEL = "qwen3.7-max"
MULTIPOLY_TIMEOUT_MS = "3600000"
[mcp_servers.github]
url = "https://api.githubcopilot.com/mcp/"
bearer_token_env_var = "CODEX_GITHUB_PERSONAL_ACCESS_TOKEN"
[mcp_servers.ha-mcp]
url = "http://192.168.39.90:8086/mcp"
[mcp_servers.home-assistant]
command = "/Users/anton/.codex/mcp-with-secrets.sh"
args = ["uvx", "ha-mcp@latest"]
[mcp_servers.home-assistant.env]
HOMEASSISTANT_URL = "http://192.168.39.50"
[mcp_servers.playwright]
command = "npx"
args = ["@playwright/mcp@latest"]
[mcp_servers.playwright.tools.browser_run_code]
approval_mode = "approve"
[mcp_servers.playwright.tools.browser_tabs]
approval_mode = "approve"
[mcp_servers.node_repl]
args = []
command = "/Applications/Codex.app/Contents/Resources/cua_node/bin/node_repl"
startup_timeout_sec = 120
[mcp_servers.node_repl.env]
NODE_REPL_NATIVE_PIPE_CONNECT_TIMEOUT_MS = "1000"
NODE_REPL_NODE_MODULE_DIRS = "/Applications/Codex.app/Contents/Resources/cua_node/lib/node_modules"
NODE_REPL_NODE_PATH = "/Applications/Codex.app/Contents/Resources/cua_node/bin/node"
NODE_REPL_TRUSTED_CODE_PATHS = "/Users/anton/.codex"
CODEX_HOME = "/Users/anton/.codex"
NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S = "583bf903a6cfb9b00a01e1583381ce921c71d7585085c6fa6b805baa0f60230b,6d25aa7656feac858f3a3bdaea5bcbab0dbfd426c9de8e6931ce90c399ee8e4f"
BROWSER_USE_AVAILABLE_BACKENDS = "chrome,iab"
NODE_REPL_INSTRUCTIONS_USE_CASE_BROWSER = "Control the in-app browser in conjunction with the Browser Plugin."
NODE_REPL_INSTRUCTIONS_USE_CASE_CHROME = "Control the Chrome browser in conjunction with the Chrome Plugin. Prefer this method of controlling Chrome over alternatives (such as Computer Use) unless the user explicitly mentions an alternative."
NODE_REPL_INSTRUCTIONS_USE_CASE_COMPUTER_USE = "Control desktop apps on macOS through Computer Use."
BROWSER_USE_CODEX_APP_BUILD_FLAVOR = "prod"
BROWSER_USE_CODEX_APP_VERSION = "26.715.52143"
SKY_CUA_SERVICE_PATH = "/Users/anton/.codex/plugins/cache/openai-bundled/computer-use/1.0.1000451/Codex Computer Use.app"
CODEX_CLI_PATH = "/Applications/Codex.app/Contents/Resources/codex"
[mcp_servers.hallucination-detector]
command = "/Users/anton/.codex/mcp-with-secrets.sh"
args = ["hallucination-detector-mcp"]
[mcp_servers.hallucination-detector.env]
[mcp_servers.flint]
command = "npx"
args = ["-y", "flint-chart-mcp"]
[mcp_servers.computer-use]
command = "./Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient"
args = ["mcp"]
cwd = "."
enabled = false
[mcp_servers.openaiDeveloperDocs]
url = "https://developers.openai.com/mcp"
[notice]
hide_rate_limit_model_nudge = true
[notice.model_migrations]
"gpt-5.2-codex" = "gpt-5.4"
[plugins."canva@openai-curated"]
enabled = true
[plugins."documents@openai-primary-runtime"]
enabled = true
[plugins."github@openai-curated"]
enabled = true
[plugins."presentations@openai-primary-runtime"]
enabled = true
[plugins."spreadsheets@openai-primary-runtime"]
enabled = true
[plugins."frontend-design@claude-plugins-official"]
enabled = true
[plugins."github@claude-plugins-official"]
enabled = true
[plugins."playwright@claude-plugins-official"]
enabled = true
[plugins."superpowers@claude-plugins-official"]
enabled = true
[plugins."codex@openai-codex"]
enabled = true
[plugins."pdf@openai-primary-runtime"]
enabled = true
[plugins."clangd-lsp@claude-plugins-official"]
enabled = true
[plugins."rust-analyzer-lsp@claude-plugins-official"]
enabled = true
[plugins."template-creator@openai-primary-runtime"]
enabled = true
[plugins."computer-use@openai-bundled"]
enabled = true
[plugins."chrome@openai-bundled"]
enabled = true
[plugins."visualize@openai-bundled"]
enabled = true
[plugins."sites@openai-bundled"]
enabled = true
[plugins."browser@openai-bundled"]
enabled = true
[projects."/"]
trust_level = "trusted"
[projects."/Users/anton"]
trust_level = "trusted"
[projects."/Users/anton/.local/share/chezmoi"]
trust_level = "trusted"
[projects."/Users/anton/dev/aictrl"]
trust_level = "trusted"
[projects."/Users/anton/dev/aictrl-upstreamm"]
trust_level = "trusted"
[projects."/Users/anton/dev/claude-sync"]
trust_level = "trusted"
[projects."/Users/anton/dev/codex-switcher"]
trust_level = "trusted"
[projects."/Users/anton/dev/ha-assistant"]
trust_level = "trusted"
[projects."/Users/anton/dev/kitty"]
trust_level = "trusted"
[projects."/Users/anton/dev/metaai"]
trust_level = "trusted"
[projects."/Users/anton/dev/moneystat"]
trust_level = "untrusted"
[projects."/Users/anton/dev/mosh"]
trust_level = "trusted"
[projects."/Users/anton/dev/summarizer"]
trust_level = "trusted"
[projects."/Users/anton/dev/translate-podcast"]
trust_level = "trusted"
[projects."/Users/anton/dev/vless"]
trust_level = "trusted"
[projects."/Users/anton/prj/aictrl"]
trust_level = "trusted"
[projects."/Users/anton/prj/papers"]
trust_level = "trusted"
[projects."/Users/anton/prj/tagir-talk"]
trust_level = "trusted"
[projects."/Users/anton/dev/r7-project"]
trust_level = "trusted"
[projects."/Users/anton/dev/glm"]
trust_level = "trusted"
[projects."/Users/anton/dev/mikrotik"]
trust_level = "trusted"
[projects."/Users/anton/dev/page-summarizer"]
trust_level = "trusted"
[projects."/Users/anton/dev/ipranges"]
trust_level = "trusted"
[projects."/Users/anton/dev/multipoly"]
trust_level = "trusted"
[projects."/Users/anton/dev/librestat"]
trust_level = "trusted"
[sandbox_workspace_write]
network_access = true
[tui]
status_line = [
"model-with-reasoning",
"context-remaining",
"current-dir",
"model-name",
"project-root",
"git-branch",
"context-used",
"five-hour-limit",
]
[tui.model_availability_nux]
"gpt-5.5" = 4
"gpt-5.6-sol" = 4
[memories]
generate_memories = true
use_memories = true
[hooks.state]
[hooks.state."/Users/anton/.codex/hooks.json:pre_tool_use:0:0"]
enabled = true
trusted_hash = "sha256:b61eb845f14a50e8e24c5435f84cb7b62cea9549c0acebe0c037a2ad94260f02"
[hooks.state."codex@openai-codex:hooks/hooks.json:session_start:0:0"]
trusted_hash = "sha256:e6b9df143de11443eb576c38874cf6e0c48358f948170948fdf630f1c047c098"
[hooks.state."codex@openai-codex:hooks/hooks.json:stop:0:0"]
trusted_hash = "sha256:3dfaa7e4f022570b260b8a8cf4ac7585c986175256b887a4723fac4f358a482f"
[hooks.state."superpowers@claude-plugins-official:hooks/hooks.json:session_start:0:0"]
enabled = false
trusted_hash = "sha256:62acbaff2b2f55f41971e21a164990aaa53c96919baa0a5eae2eb5db73b9e6e6"
[hooks.state."/Users/anton/.codex/hooks.json:pre_tool_use:1:0"]
trusted_hash = "sha256:bac831afc7106356964ac55df1e35367ef956e914a282cb645284ad23e648c57"
[hooks.state."/Users/anton/.codex/hooks.json:post_tool_use:0:0"]
trusted_hash = "sha256:f8b3aaf328b64e79eb5f0759d6bf2f4ac1136c24cf67a8887bb92f2bb63c26a4"
[hooks.state."/Users/anton/.codex/hooks.json:session_start:0:0"]
trusted_hash = "sha256:07a21ec47290f101a3b4e457df0b2a81ff7e5080f5a40e681b8514b8317f2214"
[hooks.state."/Users/anton/.codex/hooks.json:user_prompt_submit:0:0"]
trusted_hash = "sha256:a7bc3022efbd14a0b526e36d875aeb601cf0b0ed736a6b6d539ecaeb242194d8"
[hooks.state."/Users/anton/.codex/hooks.json:stop:0:0"]
trusted_hash = "sha256:a654fceae87e9d7f8d679bdd93eb083ab200c5d91bc699d71b5ef7b5a3c71f47"
[hooks.state."superpowers@claude-plugins-official:hooks/hooks-codex.json:session_start:0:0"]
enabled = false
trusted_hash = "sha256:687e7af87f0cb59b1bcc71d368e097304b45d84999c90dc97358a22811691a3f"
[hooks.state."/Users/anton/.codex/hooks.json:session_start:1:0"]
trusted_hash = "sha256:0094ae0b175bc5ea2874124bf4140ab0e2c00c8b6ea37980ede20d1e13418b19"
[hooks.state."codex@openai-codex:hooks/hooks.json:session_end:0:0"]
trusted_hash = "sha256:bc55a03ac0094c5deb4af95c405b75b34735f318b3d9bd64a2f9902973861606"
[desktop]
appearanceTheme = "system"
appearanceLightCodeThemeId = "codex"
appearanceDarkCodeThemeId = "gruvbox"
sansFontSize = 15
codeFontSize = 14
usePointerCursors = false
followUpQueueMode = "steer"
preventSleepWhileRunning = false
keepRemoteControlAwakeWhilePluggedIn = true
conversationDetailMode = "STEPS_COMMANDS"
mac-menu-bar-enabled = false
dock-icon-preference = "codex-system"
[desktop.appearanceLightChromeTheme]
accent = "#0169cc"
contrast = 45
ink = "#0d0d0d"
opaqueWindows = true
surface = "#ffffff"
[desktop.appearanceLightChromeTheme.fonts]
[desktop.appearanceLightChromeTheme.semanticColors]
diffAdded = "#00a240"
diffRemoved = "#e02e2a"
skill = "#751ed9"
[desktop.appearanceDarkChromeTheme]
accent = "#458588"
contrast = 60
ink = "#ebdbb2"
opaqueWindows = false
surface = "#282828"
[desktop.appearanceDarkChromeTheme.fonts]
[desktop.appearanceDarkChromeTheme.semanticColors]
diffAdded = "#ebdbb2"
diffRemoved = "#cc241d"
skill = "#b16286"
[desktop.open-in-target-preferences]
global = "sublimeText"
[[skills.config]]
path = "/Users/anton/.codex/plugins/cache/claude-plugins-official/superpowers/6.0.3/skills/using-superpowers/SKILL.md"
enabled = false
[[skills.config]]
path = "/Users/anton/.codex/plugins/cache/claude-plugins-official/superpowers/6.0.3/skills/brainstorming/SKILL.md"
enabled = false
[shell_environment_policy.set]
BROWSER_USE_AVAILABLE_BACKENDS = "chrome,iab"
NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S = "583bf903a6cfb9b00a01e1583381ce921c71d7585085c6fa6b805baa0f60230b,6d25aa7656feac858f3a3bdaea5bcbab0dbfd426c9de8e6931ce90c399ee8e4f"
NODE_REPL_TRUSTED_CODE_PATHS = "/Users/anton/.codex"

View File

@ -0,0 +1,28 @@
#!/bin/sh
# Launch an MCP server with its secrets loaded from a machine-local file.
#
# codex's [mcp_servers.*.env] tables only take literal values, so keeping API
# keys out of config.toml means they cannot be named there at all. This wrapper
# sources them itself and then execs the real server, which works no matter how
# codex handles environment inheritance, and needs no shell rc to have run --
# useful on the ssh-only boxes where a GUI keychain or 1Password is not around.
#
# The secrets file is not tracked by chezmoi; see .chezmoiignore.
set -eu
secrets="${CODEX_MCP_SECRETS:-${CODEX_HOME:-$HOME/.codex}/secrets.env}"
if [ -r "$secrets" ]; then
. "$secrets"
else
echo "mcp-with-secrets: no secrets file at $secrets" >&2
fi
[ "$#" -gt 0 ] || {
echo "mcp-with-secrets: no command given" >&2
exit 2
}
exec "$@"

22
dot_grok/config.toml Normal file
View File

@ -0,0 +1,22 @@
[cli]
installer = "internal"
auto_update = true
[ui]
max_thoughts_width = 120
fork_secondary_model = "grok-build"
yolo = false
compact_mode = false
theme = "grokday"
permission_mode = "always-approve"
[models]
default = "grok-composer-2.5-fast"
[marketplace]
official_marketplace_auto_installed = true
default_skills_installs_purged = true
[[marketplace.sources]]
name = "xAI Official"
git = "https://github.com/xai-org/plugin-marketplace.git"

View File

@ -0,0 +1,138 @@
default_plan_mode = false
merge_all_available_skills = false
telemetry = true
default_model = "kimi-code/k3"
[loop_control]
max_retries_per_step = 3
reserved_context_size = 50000
[background]
max_running_tasks = 4
keep_alive_on_exit = false
[services.moonshot_search]
base_url = "https://api.kimi.com/coding/v1/search"
api_key = ""
[services.moonshot_search.oauth]
storage = "file"
key = "oauth/kimi-code"
[services.moonshot_fetch]
base_url = "https://api.kimi.com/coding/v1/fetch"
api_key = ""
[services.moonshot_fetch.oauth]
storage = "file"
key = "oauth/kimi-code"
[thinking]
enabled = true
effort = "high"
[providers."managed:kimi-code"]
type = "kimi"
api_key = ""
base_url = "https://api.kimi.com/coding/v1"
[providers."managed:kimi-code".oauth]
storage = "file"
key = "oauth/kimi-code"
[models."kimi-code/kimi-for-coding"]
provider = "managed:kimi-code"
model = "kimi-for-coding"
max_context_size = 262144
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
display_name = "K2.7 Coding"
[models."kimi-code/kimi-for-coding-highspeed"]
provider = "managed:kimi-code"
model = "kimi-for-coding-highspeed"
max_context_size = 262144
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
display_name = "K2.7 Coding Highspeed"
[models."kimi-code/k3"]
provider = "managed:kimi-code"
model = "k3"
max_context_size = 1048576
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
display_name = "K3"
support_efforts = [ "low", "high", "max" ]
default_effort = "high"
[models."kimi-k.3.0"]
provider = "managed:kimi-code"
model = "kimi-k.3.0"
max_context_size = 1048576
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
display_name = "kimi-k.3.0"
# >>> herdr kimi integration
[[hooks]]
event = "SessionStart"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' session"
timeout = 10
[[hooks]]
event = "UserPromptSubmit"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' working"
timeout = 10
[[hooks]]
event = "PreToolUse"
matcher = "^(?!AskUserQuestion$).*$"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' working"
timeout = 10
[[hooks]]
event = "PreToolUse"
matcher = "^AskUserQuestion$"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' blocked"
timeout = 10
[[hooks]]
event = "PostToolUse"
matcher = "^AskUserQuestion$"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' working"
timeout = 10
[[hooks]]
event = "PostToolUseFailure"
matcher = "^AskUserQuestion$"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' working"
timeout = 10
[[hooks]]
event = "SubagentStart"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' working"
timeout = 10
[[hooks]]
event = "PreCompact"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' working"
timeout = 10
[[hooks]]
event = "PermissionRequest"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' blocked"
timeout = 10
[[hooks]]
event = "PermissionResult"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' working"
timeout = 10
[[hooks]]
event = "Stop"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' idle"
timeout = 10
[[hooks]]
event = "Interrupt"
command = "bash '/Users/anton/.kimi-code/hooks/herdr-agent-state.sh' idle"
timeout = 10
# <<< herdr kimi integration

View File

@ -0,0 +1,8 @@
theme = "dark"
[editor]
command = ""
[notifications]
enabled = true
notification_condition = "unfocused"

78
dot_kimi/config.toml Normal file
View File

@ -0,0 +1,78 @@
default_model = "kimi-code/kimi-for-coding"
default_thinking = true
default_yolo = false
default_plan_mode = false
default_editor = ""
theme = "dark"
show_thinking_stream = true
hooks = []
merge_all_available_skills = false
telemetry = true
[models."kimi-code/kimi-for-coding"]
provider = "managed:kimi-code"
model = "kimi-for-coding"
max_context_size = 262144
capabilities = ["thinking", "image_in", "video_in"]
display_name = "Kimi-k2.6"
[models. "kimi-k.3.0"]
provider = "managed:kimi-code"
model = "kimi-k.3.0"
max_context_size = 1048576
capabilities = [ "thinking", "always_thinking", "image_in", "video_in", "tool_use" ]
display_name = "kimi-k.3.0"
[providers."managed:kimi-code"]
type = "kimi"
base_url = "https://api.kimi.com/coding/v1"
api_key = ""
[providers."managed:kimi-code".oauth]
storage = "file"
key = "oauth/kimi-code"
[loop_control]
max_steps_per_turn = 500
max_retries_per_step = 3
max_ralph_iterations = 0
reserved_context_size = 50000
compaction_trigger_ratio = 0.85
[background]
max_running_tasks = 4
read_max_bytes = 30000
notification_tail_lines = 20
notification_tail_chars = 3000
wait_poll_interval_ms = 500
worker_heartbeat_interval_ms = 5000
worker_stale_after_ms = 15000
kill_grace_period_ms = 2000
keep_alive_on_exit = false
agent_task_timeout_s = 900
print_wait_ceiling_s = 3600
[notifications]
claim_stale_after_ms = 15000
[services.moonshot_search]
base_url = "https://api.kimi.com/coding/v1/search"
api_key = ""
[services.moonshot_search.oauth]
storage = "file"
key = "oauth/kimi-code"
[services.moonshot_fetch]
base_url = "https://api.kimi.com/coding/v1/fetch"
api_key = ""
[services.moonshot_fetch.oauth]
storage = "file"
key = "oauth/kimi-code"
[mcp.client]
tool_call_timeout_ms = 60000

View File

@ -0,0 +1,19 @@
{
"work_dirs": [
{
"path": "/Users/anton",
"kaos": "local",
"last_session_id": "c0a99c4e-0915-41f1-a7b6-7c011b1a9ef1"
},
{
"path": "/Users/anton/dev/glm",
"kaos": "local",
"last_session_id": "79ec0e41-4744-4ad2-a64b-87012c745b30"
},
{
"path": "/Users/anton/prj/papers",
"kaos": "local",
"last_session_id": null
}
]
}

View File

@ -1,5 +1,5 @@
{ {
"lastChangelogVersion": "0.82.1", "lastChangelogVersion": "0.83.0",
"theme": "dark", "theme": "dark",
"defaultProvider": "alibaba-code-plan", "defaultProvider": "alibaba-code-plan",
"defaultModel": "qwen3.8-max-preview", "defaultModel": "qwen3.8-max-preview",
@ -19,4 +19,4 @@
"npm:pi-powerline-footer", "npm:pi-powerline-footer",
"npm:pi-webaio" "npm:pi-webaio"
] ]
} }

View File

@ -0,0 +1,241 @@
# Reasonix configuration.
# Resolution order: flag > ./reasonix.toml > ~/.reasonix/config.toml > built-in defaults.
# Fields marked user/global only are not overridden by ./reasonix.toml.
# Secrets are named via api_key_env and stored in Reasonix's global .env; never put keys here.
config_version = 5 # schema marker for diagnostics; old versions may ignore it
default_model = "deepseek-flash/deepseek-v4-flash"
language = "EN" # ui/model language; empty = auto-detect from $LANG / $REASONIX_LANG
credentials_store = "auto" # legacy compatibility; provider keys are saved in Reasonix's global .env
[ui]
theme = "auto" # auto|dark|light; CLI colors only; REASONIX_THEME can override per run
# theme_style = "graphite" # graphite|aurora|slate|carbon|nocturne|amber and legacy aliases
# shortcut_layout = "desktop" # classic|desktop; compatibility setting; Shift+Tab toggles Plan, Ctrl+Y toggles YOLO
# cursor_shape = "bar" # block|underline|bar; text input cursor shape
# show_reasoning = true # CLI: show thinking text by default; false = collapsed (toggle with Ctrl+O)
[desktop]
language = "en" # desktop UI language; empty/auto = browser/OS auto-detect
# currency = "USD" # official pricing currency: CNY|USD; empty/auto follows language
layout_style = "workbench" # desktop layout: classic|workbench|creation
theme = "auto" # desktop only: auto|dark|light
terminal_theme = "auto" # integrated terminal: auto|dark|light; auto follows the desktop app
# theme_style = "graphite" # graphite|aurora|slate|carbon|nocturne|amber and legacy aliases
# external_opener = "vscode" # desktop Open control: installed application id
close_behavior = "background" # desktop: quit|background when the window close button is clicked
status_bar_style = "text" # desktop: icon|text metric labels in the bottom status bar
status_bar_items = ["model", "workspace", "git_branch", "cache", "cache_avg", "session_tokens", "turn_tokens", "turn_cost", "session_turns", "context", "compact", "cost", "balance"] # desktop: ordered visible bottom status bar items
default_tool_approval_mode = "auto" # desktop: Ask/Auto/YOLO default for newly-created sessions
check_updates = true # desktop: check for new versions on startup
telemetry = true # desktop: anonymous launch ping + scrubbed next-launch native crash diagnostics; never content
metrics = true # desktop: aggregate quality/lifecycle metrics (anonymous signal/bucket counts); never content
expand_thinking = false # desktop: show reasoning text expanded by default; false = collapsed
display_mode = "standard" # desktop: standard|compact transcript display mode
[telemetry]
cli_metrics = "auto" # CLI content-free usage metrics: auto|on|off; auto requires a local interactive terminal
[notifications]
enabled = false # system notifications for CLI and desktop turns; default off
turn_done = true # notify when a turn finishes
approval_request = true # notify when a tool approval is waiting
ask_request = true # notify when a question is waiting
[network]
proxy_mode = "auto" # auto|env|custom|off; auto currently uses env proxy
# proxy_url = "socks5://127.0.0.1:7890" # optional custom override
# no_proxy = "localhost,127.0.0.1,.local" # honored for proxy_mode = "custom"
[network.proxy]
type = "socks5" # http|https|socks5|socks5h
# server = "127.0.0.1"
# port = 7890
# username = ""
# password = "${REASONIX_PROXY_PASSWORD}" # optional; supports ${VAR} expansion
[environment]
enabled = true # inject a stable startup environment summary into the model prompt
# [environment.tools]
# go = "/opt/homebrew/bin/go" # trusted executable path; workspace-local paths are not auto-executed
[agent]
# system_prompt = """...""" # omit to use the built-in prompt for this version
# system_prompt_file = "prompts/system.md" # project paths stay in <workspace>; user paths may fall back to <reasonix home>
temperature = 0.0
# recovery_model = "deepseek-pro" # optional; falls back to guardian then main model
# reasoning_language = "zh" # visible reasoning language: auto|zh|en
soft_compact_ratio = 0.5 # notice only; keeps cache-first prefix intact
tool_result_snip_ratio = 0.6 # snip stale tool results at this fraction before summary compaction
compact_ratio = 0.8 # try compacting when prompt reaches this fraction
compact_force_ratio = 0.9 # force compacting at this high-water mark
# keep = ["errors"] # compaction keep policy: errors, user_marked
# recent_keep = 2 # minimum recent messages kept verbatim
cold_resume_prune = true # elide stale tool results when reopening a session past the provider cache window
# plan_mode_read_only_commands = ["gh issue view"] # legacy compatibility only; Plan bash uses Permissions
# planner_model = "deepseek-pro" # optional: enable two-model collaboration
# subagent_model = "deepseek-pro" # optional default for runAs=subagent skills
# subagent_models = { review = "deepseek-pro", security_review = "deepseek-pro" } # per-skill overrides
# subagent_effort = "high" # optional default effort for subagents
# subagent_efforts = { review = "max", task = "high" } # per-tool/skill effort overrides
# max_subagent_depth = 2 # nested subagent delegation depth; set 1 to disable nested delegation
# max_subagent_concurrency = 6 # session-wide sub-agent concurrency (task/fleet/skills)
# max_parallel_writers = 3 # concurrent writers with non-overlapping write_paths
# output_style = "explanatory" # explanatory | learning | concise | custom; empty = default
[[providers]]
name = "deepseek-flash"
kind = "openai"
base_url = "https://api.deepseek.com"
model = "deepseek-v4-flash"
api_key_env = "DEEPSEEK_API_KEY"
balance_url = "https://api.deepseek.com/user/balance" # optional; wallet-balance endpoint shown in the status bar
context_window = 1000000 # tokens; compaction triggers near this limit
price = { cache_hit = 0.0028, input = 0.14, output = 0.28, currency = "$" } # provider-wide fallback, per 1M tokens
[[providers]]
name = "deepseek-pro"
kind = "openai"
base_url = "https://api.deepseek.com"
model = "deepseek-v4-pro"
api_key_env = "DEEPSEEK_API_KEY"
balance_url = "https://api.deepseek.com/user/balance" # optional; wallet-balance endpoint shown in the status bar
context_window = 1000000 # tokens; compaction triggers near this limit
price = { cache_hit = 0.003625, input = 0.435, output = 0.87, currency = "$" } # provider-wide fallback, per 1M tokens
[tools]
enabled = [] # empty = all built-in tools
bash_timeout_seconds = 120 # foreground safety cap; set 0 for no tool-local cap
mcp_startup_timeout_seconds = 30 # background initialize + tools/list safety cap; per-plugin overrides may raise it
mcp_call_timeout_seconds = 300 # default MCP call safety cap; per-plugin/tool overrides may raise it
[tools.background_jobs]
stalled_warning_seconds = 900 # warn once per background job after this many quiet seconds; 0 disables
[tools.shell]
# prefer = "auto" # auto|bash|powershell|pwsh; empty/default = auto-detect
# path = "/opt/homebrew/bin/bash" # absolute path to the shell executable; empty = PATH lookup
[lsp]
enabled = true # language server tools; servers launch lazily when used
# [lsp.servers.go]
# command = "gopls"
# args = []
# extensions = [".go"]
[skills]
# paths = ["~/my-skills", "../shared/skills"] # extra custom skill roots
# excluded_paths = ["~/.agents/skills"] # hide convention roots without deleting folders
# max_depth = 3 # nested scan depth; set 1 for legacy root-only discovery
# disabled_skills = ["review"] # hide noisy or unwanted skills
[permissions]
# Per-call gating. mode = writer fallback when no rule matches: ask|allow|deny.
# Readers always default to allow. Precedence: deny > ask > allow > fallback.
# Rules are "Tool" or "Tool(specifier)"; e.g. Bash(go test:*), Edit(src/**).
mode = "ask"
# allow_dynamic_bash = false # advanced opt-in; deny/ask and exact rules still take precedence
# deny = ["Bash(rm -rf*)", "Bash(git push*)"] # hard-blocked in every mode
# allow = ["Bash(go test:*)", "Bash(git status:*)"] # never prompted
# ask = ["Edit(src/**)"] # force a prompt even if otherwise allowed
[sandbox]
# Confine tool blast radius. File-writers (write_file/edit_file/multi_edit/move_file)
# may only write under workspace_root (empty = current dir) and allow_write extras.
# bash = "enforce" jails each command in an OS sandbox when available;
# without one, bash execution is refused. Empty defaults to enforce on macOS/Linux.
# Windows has no OS-level Bash sandbox and fixes bash = "off".
# network allows sandboxed bash egress.
# workspace_root = "" # default: current working directory
# allow_write = ["/tmp"] # extra dirs writers may also modify
# forbid_read = [] # dirs the agent cannot read or list
bash = "enforce"
network = true
[statusline]
# A custom status line: a command whose first stdout line replaces the built-in
# data row. It receives {"model","contextUsed","contextWindow","cwd"} as JSON on stdin.
# command = "my-statusline.sh"
# Bot gateway: multi-channel IM bot for QQ, Feishu/Lark, and WeChat.
[bot]
enabled = false
# model = "" # empty = default_model
tool_approval_mode = "ask" # ask|auto|yolo; yolo skips tool approvals only
max_steps = 25
debounce_ms = 1500
queue_mode = "steer" # steer|followup|collect|interrupt
queue_cap = 20
queue_drop = "summarize" # summarize|old|new
ignore_self_messages = true # ignore bot echo by returned message_id and configured self user ids
[bot.self_user_ids]
qq = []
feishu = []
weixin = []
[bot.control]
enabled = false # local loopback HTTP API for status/send; requires Bearer token
addr = "127.0.0.1:37913"
token_env = "REASONIX_BOT_CONTROL_TOKEN"
[bot.pairing]
enabled = true
request_ttl_minutes = 60
max_pending_per_platform = 3
[bot.allowlist]
enabled = true
allow_all = false
qq_users = []
feishu_users = []
weixin_users = []
qq_approvers = []
feishu_approvers = []
weixin_approvers = []
qq_admins = []
feishu_admins = []
weixin_admins = []
qq_groups = []
feishu_groups = []
weixin_groups = []
[bot.qq]
enabled = false
app_id = ""
app_secret_env = "QQ_BOT_APP_SECRET"
sandbox = false
[bot.feishu]
enabled = false
app_id = ""
domain = "feishu"
app_secret_env = "FEISHU_BOT_APP_SECRET"
verification_token = ""
mode = "webhook"
webhook_port = 8080
require_mention = true
[bot.weixin]
enabled = false
account_id = "default"
token_env = "WEIXIN_BOT_TOKEN"
api_base = "https://ilinkai.weixin.qq.com"
[secrets] # credential protection; user/global only, ./reasonix.toml cannot override
# filter_subprocess_env = false # opt-in; stripping tokens breaks gh, HTTPS git push, npm publish
# protect_sensitive_files = false # opt-in; hiding credential files can break legitimate edit workflows
# External MCP servers. type: "stdio" (default, a subprocess) | "http" | "sse".
# ${VAR} / ${VAR:-default} are expanded from the environment in command/args/env/url/headers.
# [[plugins]]
# name = "example"
# command = "reasonix-plugin-example"
# startup_timeout_seconds = 60 # optional initialize + tools/list cap
# call_timeout_seconds = 600 # optional per-server MCP call timeout
# tool_timeout_seconds = { "generate_video" = 1800 } # raw MCP tool names
# [[plugins]] # a remote server over Streamable HTTP
# name = "stripe"
# type = "http"
# url = "https://mcp.stripe.com"
# headers = { Authorization = "Bearer ${STRIPE_KEY}" }

View File

@ -14,6 +14,22 @@
set -eu set -eu
# Permission bits of a file, or 600 if they cannot be read. GNU stat spells this
# `-c %a` while BSD/macOS spells it `-f %Lp`, and each mis-parses the other's
# form -- GNU treats `-f` as --file-system and prints a block of filesystem info
# before failing -- so try one at a time and only accept an octal answer.
file_mode() {
_m=$(stat -c '%a' "$1" 2>/dev/null) || _m=''
case "$_m" in
[0-7][0-7][0-7] | [0-7][0-7][0-7][0-7]) printf '%s\n' "$_m"; return 0 ;;
esac
_m=$(stat -f '%Lp' "$1" 2>/dev/null) || _m=''
case "$_m" in
[0-7][0-7][0-7] | [0-7][0-7][0-7][0-7]) printf '%s\n' "$_m"; return 0 ;;
esac
printf '600\n'
}
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
config_dir=${1:-"$script_dir"} config_dir=${1:-"$script_dir"}
canonical_dir="$config_dir/themes" canonical_dir="$config_dir/themes"
@ -54,12 +70,9 @@ for appearance in dark light no-preference; do
[ -f "$canonical" ] || continue [ -f "$canonical" ] || continue
generated="$config_dir/$appearance-theme.auto.conf" generated="$config_dir/$appearance-theme.auto.conf"
# stat -f is BSD/macOS, stat -c is GNU; this also runs on Linux boxes.
mode_bits=600 mode_bits=600
if [ -f "$generated" ]; then if [ -f "$generated" ]; then
mode_bits=$(stat -f '%Lp' "$generated" 2>/dev/null \ mode_bits=$(file_mode "$generated")
|| stat -c '%a' "$generated" 2>/dev/null \
|| echo 600)
fi fi
tmp=$(mktemp "$generated.tmp.XXXXXX") tmp=$(mktemp "$generated.tmp.XXXXXX")

View File

@ -2,6 +2,22 @@
set -eu set -eu
# Permission bits of a file, or 600 if they cannot be read. GNU stat spells this
# `-c %a` while BSD/macOS spells it `-f %Lp`, and each mis-parses the other's
# form -- GNU treats `-f` as --file-system and prints a block of filesystem info
# before failing -- so try one at a time and only accept an octal answer.
file_mode() {
_m=$(stat -c '%a' "$1" 2>/dev/null) || _m=''
case "$_m" in
[0-7][0-7][0-7] | [0-7][0-7][0-7][0-7]) printf '%s\n' "$_m"; return 0 ;;
esac
_m=$(stat -f '%Lp' "$1" 2>/dev/null) || _m=''
case "$_m" in
[0-7][0-7][0-7] | [0-7][0-7][0-7][0-7]) printf '%s\n' "$_m"; return 0 ;;
esac
printf '600\n'
}
config_dir=${1:-"$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"} config_dir=${1:-"$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"}
for appearance in dark light no-preference; do for appearance in dark light no-preference; do
@ -22,10 +38,7 @@ for appearance in dark light no-preference; do
trap 'rm -f "$tmp"' EXIT HUP INT TERM trap 'rm -f "$tmp"' EXIT HUP INT TERM
awk '$1 != "macos_titlebar_color"' "$config_file" > "$tmp" awk '$1 != "macos_titlebar_color"' "$config_file" > "$tmp"
printf '\nmacos_titlebar_color background\n' >> "$tmp" printf '\nmacos_titlebar_color background\n' >> "$tmp"
# stat -f is BSD/macOS, stat -c is GNU; this also runs on Linux boxes. chmod "$(file_mode "$config_file")" "$tmp"
chmod "$(stat -f '%Lp' "$config_file" 2>/dev/null \
|| stat -c '%a' "$config_file" 2>/dev/null \
|| echo 600)" "$tmp"
mv -f "$tmp" "$config_file" mv -f "$tmp" "$config_file"
trap - EXIT HUP INT TERM trap - EXIT HUP INT TERM
done done

View File

@ -149,4 +149,36 @@ run_fish dark 'theme >/dev/null'
"$root/tests/verify_auto_themes.sh" "$config_dir" "$root/tests/verify_auto_themes.sh" "$config_dir"
# --- the scripts must work where stat is GNU, not BSD ---------------------
# GNU stat reads -f as --file-system, so `stat -f '%Lp' file` prints a block of
# filesystem info to stdout *and* exits non-zero. Chaining it into `|| stat -c`
# concatenates that block with the real answer and chmod then rejects it, which
# broke `chezmoi apply` on Linux with "could not regenerate kitty theme files".
cat >"$fake_bin/stat" <<'EOF'
#!/bin/sh
case "$1" in
-c) shift; [ "$1" = "%a" ] && shift; /usr/bin/stat -f '%Lp' "$1" ;;
-f) shift
echo " File: \"$2\""
echo "Block size: 4096 Fundamental block size: 4096"
exit 1 ;;
*) exec /usr/bin/stat "$@" ;;
esac
EOF
chmod +x "$fake_bin/stat"
echo auto >"$config_dir/theme-mode"
if ! PATH="$fake_bin:/usr/bin:/bin" "$root/apply-theme-mode.sh" "$config_dir" \
>/dev/null 2>&1; then
echo "not ok - apply-theme-mode.sh fails where stat is GNU" >&2
exit 1
fi
for appearance in dark light no-preference; do
[ -f "$config_dir/$appearance-theme.auto.conf" ] || {
echo "not ok - GNU stat run did not produce $appearance theme" >&2
exit 1
}
done
rm -f "$fake_bin/stat"
echo "PASS: theme mode is remembered, survives kitten themes, and collapses to auto" echo "PASS: theme mode is remembered, survives kitten themes, and collapses to auto"