Files
Sylpheed/godot-import/docker/bin/seed-claude-config.py
MechaCat02 f44ebced59 merge the Godot port's history into the monorepo
Brought in with a subtree merge rather than a copy, so the port's 31 commits
survive as history rather than arriving as one anonymous import. Landed under
godot-import/ and moved into the final layout in the next commit, which keeps
git's rename detection able to follow each file across the move.
2026-08-29 11:32:01 +02:00

86 lines
3.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Mark Claude Code's onboarding as complete in ~/.claude.json.
Claude Code re-runs its first-run wizard whenever `lastOnboardingVersion` does
not match the installed version. In a terminal that is a one-key prompt; for an
unattended agent it is a silent, permanent hang on the theme picker — no error,
no log line, and the container looks like it started fine.
It also pre-accepts the workspace's trust prompt. That is a SECOND, separate
first-run gate: even past onboarding, Claude Code asks "is this a project you
trust?" per directory, and this repo's settings pre-approve 442 tool permissions
so the prompt is emphatic about it. Unattended, it is another silent hang.
Pre-accepting is safe here precisely because the trust question is being
answered by the person who built and launched the container, for their own
repository — it is not a judgement being made on their behalf about someone
else's code.
Usage: seed-claude-config.py <path to .claude.json> <installed version> [workspace...]
"""
import json
import os
import sys
def main() -> int:
if len(sys.argv) < 3:
print(f"usage: {sys.argv[0]} <config.json> <version> [workspace...]",
file=sys.stderr)
return 2
path, version = sys.argv[1], sys.argv[2]
workspaces = sys.argv[3:]
cfg = {}
if os.path.exists(path) and os.path.getsize(path):
try:
with open(path) as f:
cfg = json.load(f)
except (OSError, ValueError):
# A corrupt or partial config is not worth failing the container
# over — start from empty rather than block the run.
cfg = {}
if not isinstance(cfg, dict):
cfg = {}
cfg["hasCompletedOnboarding"] = True
cfg["lastOnboardingVersion"] = version
cfg.setdefault("theme", "dark")
# The THIRD interactive gate: --dangerously-skip-permissions shows a
# "Bypass Permissions mode / you accept all responsibility" confirmation on
# first use. Key name taken from the shipped binary's own strings, not
# guessed. Accepting it here is the whole point of `sylph-agent loose` —
# the container is the sandbox that warning asks you to provide.
cfg["bypassPermissionsModeAccepted"] = True
# A FOURTH gate, and this one fires mid-session rather than at startup, so
# the pty wrapper has already handed over by then: an upsell asking whether
# to try the fullscreen renderer. It is shown while
# `fullscreenUpsellSeenCount` is below an internal threshold, so park it far
# above. Found by reading the shipped binary's strings, same as the others.
cfg["fullscreenUpsellSeenCount"] = 9999
# An auto-update mid-run would restart the process and lose the loop's
# scheduled wake-up, so pin the version the container was built with.
cfg["autoUpdates"] = False
projects = cfg.setdefault("projects", {})
if not isinstance(projects, dict):
projects = cfg["projects"] = {}
for ws in workspaces:
entry = projects.setdefault(ws, {})
if not isinstance(entry, dict):
entry = projects[ws] = {}
entry["hasTrustDialogAccepted"] = True
entry.setdefault("projectOnboardingSeenCount", 1)
entry["hasClaudeMdExternalIncludesApproved"] = True
entry["hasClaudeMdExternalIncludesWarningShown"] = True
tmp = path + ".tmp"
with open(tmp, "w") as f:
json.dump(cfg, f, indent=2)
os.replace(tmp, path)
return 0
if __name__ == "__main__":
sys.exit(main())