← Commit history
__pycache__/seed-agent-permissions.cpython-312.pycadded
⋯ 1 unchanged line ⋯
install.sh+34−1
@@ -101,6 +101,13 @@ fi # 4. code-server settings.json — the configure-vscode payload PLUS the chat/UI #    disables (without these the editor opens VS Code's built-in agent chat #    panel). NO model pin — Claude Code picks the default model itself.+#+#    claudeCode.allowDangerouslySkipPermissions: TRUE only makes the "Bypass+#    permissions" row APPEAR in the editor's Modes menu. Adom users are power users+#    and that choice is theirs to make (John 2026-08-25). Auto remains the default,+#    because claudeCode.initialPermissionMode stays "auto", and Claude Code still+#    shows its own one-time consent dialog before bypass takes effect. Nothing here+#    pre-accepts that dialog. log "writing code-server settings.json" install -d -m 0755 "$HOME/.local/share/code-server/User" cat > "$HOME/.local/share/code-server/User/settings.json" <<'SETTINGS'@@ -116,7 +123,7 @@ cat > "$HOME/.local/share/code-server/User/settings.json" <<'SETTINGS'   "workbench.navigationControl.enabled": false,   "workbench.secondarySideBar.visible": false,   "workbench.secondarySideBar.defaultVisibility": "hidden",-  "claudeCode.allowDangerouslySkipPermissions": false,+  "claudeCode.allowDangerouslySkipPermissions": true,   "claudeCode.initialPermissionMode": "auto",   "claudeCode.preferredLocation": "panel",   "chat.agent.enabled": false,@@ -172,6 +179,32 @@ printf 'd /run/adom 0755 root root - C /run/adom/api-key 0644 root root - /home/adom/.adom/api-key '   | sudo tee /etc/tmpfiles.d/adom-api-key.conf >/dev/null   && log "  tmpfiles.d/adom-api-key.conf installed (boot-time re-materialization)"   || log "  WARN: could not install tmpfiles snippet (no sudo?) — HD self-heal will retry" +# 6b. AUTO-MODE CLASSIFIER POSTURE (John 2026-08-25) ─────────────────────────────+# Claude Code's auto mode judges every Bash call with a classifier that knows nothing+# about Adom. Measured before this existed: the golden image shipped exactly ONE+# permission rule (Bash(adom-wiki:*)), every other CLI fell through to the classifier+# on every call, and a long-lived container had accumulated 121 hyper-specific one-off+# allow rules, a log of a human clicking approve one command at a time for months.+#+# Bash(...) rules cannot fix it: they are prefix matches on the literal command string,+# and our CLIs put flags BEFORE the verb (adom-bridge --target X --ai-thread Y <verb>),+# so no prefix expresses "these verbs, not those". autoMode takes natural language the+# classifier actually reads, which is the only shape that fits this CLI grammar.+#+# THIS IS A STOPGAP, scoped to Hydrogen. The right home is the registry: every package+# that installs a CLI should declare its own posture and adom-wiki should compose them,+# because only a publisher knows which of their verbs are safe, a central list goes+# stale, and third-party orgs cannot edit ours. Filed as adom/wiki#900. When that lands,+# DELETE this section rather than growing it.+#+# Discipline, so this never becomes the 121-rule pile it replaces:+#   - it is DERIVED state in a managed region we own and REGENERATE, never append to+#   - a user's own autoMode block wins: if one exists that we did not write, leave it+#   - hard_deny only ADDS safety; we never weaken a built-in rule+log "auto-mode classifier posture (Adom toolchain)"+python3 "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/seed-agent-permissions.py" "$HOME/.claude/settings.json" \+  || log "  WARN: could not seed autoMode (continuing)"+ # 7. tidy — install.mjs / tooling leave an empty ~/project/.mcp.json; remove it. rm -f "$HOME/project/.mcp.json" 2>/dev/null || true log "done"
package.json+1−1
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.3.23",+  "version": "0.4.0",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "platform": "any",
page.json+1−1
@@ -4,7 +4,7 @@   "type": "bootstrap",   "title": "Adom Hydrogen (platform base)",   "brief": "Layer Hydrogen's workspace onto a standard Adom workstation.",-  "version": "0.3.23",+  "version": "0.4.0",   "description": "Hydrogen's PLATFORM-GENERIC workspace layer on top of adom/core. Installs adom/core for the full Adom ecosystem, then adds everything common to running inside Hydrogen on ANY platform: the runtime-neutral hydrogen-* skills and the generic editor configuration (Claude Code CLI + extension, VS Code settings, trusted domains). Platform-specific layers depend on THIS: adom/hydrogen-windows-bootstrap (WSL2), adom/hydrogen-mac-bootstrap, adom/hydrogen-ubuntu-bootstrap. The reference design other orgs copy to build their own bootstrap on adom/core.",   "org": "adom",   "tags": [
seed-agent-permissions.pyadded+138
@@ -0,0 +1,138 @@+#!/usr/bin/env python3+"""Seed the Adom auto-mode classifier posture into Claude Code's settings.++Called by adom/hydrogen-bootstrap's install.sh with the path to+~/.claude/settings.json. Idempotent, and safe to re-run on every package update.++STOPGAP, scoped to Hydrogen. The right home is the registry: every package that+installs a CLI should declare its own posture and adom-wiki should compose them,+because only a publisher knows which of their own verbs are safe, a central list+goes stale, and third-party orgs cannot edit ours. Filed as adom/wiki#900. When+that lands, delete this file rather than growing it.++Two rules keep this honest:+  - autoMode is DERIVED state in a region we own and REGENERATE. We never append,+    which is what stops it becoming the accumulating one-off pile it replaces.+  - If an autoMode block exists that we did not write, it is the user's (or an+    org policy's) and we leave it completely alone.+"""++import json+import os+import sys++TAG = "hydrogen-bootstrap/1"++ENVIRONMENT = [+    "This is an Adom container. Bash commands here routinely drive the user's OWN "+    "desktop machines through the Adom Bridge relay (adom-bridge --target <name> "+    "<verb>). Those machines apply their own approval gate independently of this "+    "one: a call carrying no caller identity is refused outright, gated verbs also "+    "require a per-call reason, and both are shown to the user in their Activity "+    "Log. There are two gates and only this one is blind.",+    "The Adom CLIs (adom-wiki, adom-cli, adom-bridge, adom-shotlog, "+    "adom-parts-search, adom-mouser, adom-digikey, adom-jlcpcb, adom-vscode, "+    "adom-tts) are first-party tools installed into this container by the Adom "+    "registry.",+]++ALLOW = [+    "adom-bridge / adom-bridge-cli verbs addressed with --target at a desktop the "+    "user owns, in any flag order and however the command is wrapped.",+    "adom-wiki reads, package installs, and publishes: the wiki is the ecosystem's "+    "own registry and every publish is versioned and revertible.",+    "wsl_import, wsl_export, wsl_exec, wsl_exec_async, wsl_job_* and wsl_unregister "+    "on THROWAWAY distros whose names begin golden-build- or golden-test-, which the "+    "image build and test loop creates and destroys by design.",+    "Reading and writing files under the user's own container home and workspace, "+    "including ~/.claude/skills and ~/project.",+]++SOFT_DENY = [+    "Deleting a WSL distro that is NOT a golden-build-* / golden-test-* throwaway, "+    "above all Adom-Workspace, which is the user's live workspace.",+    "Virgin Reset, workspace re-import, or any operation that destroys a live "+    "workspace, unless the user asked for it in this session.",+]++HARD_DENY = [+    "Broad process kills inside an Adom container (pkill node, killall node, any "+    "unscoped kill by name): they kill the editor and brick the container.",+]++# Stable-prefix CLIs only. Anything whose verb follows its flags is handled by the+# autoMode text above, because a prefix rule cannot express it.+BASH_ALLOW = [+    "Bash(adom-wiki:*)",+    "Bash(adom-cli:*)",+    "Bash(adom-shotlog:*)",+    "Bash(shotlog:*)",+    "Bash(adom-tts:*)",+    "Bash(adom-parts-search:*)",+    "Bash(adom-mouser:*)",+    "Bash(adom-digikey:*)",+    "Bash(adom-jlcpcb:*)",+    "Bash(adom-vscode:*)",+    "Bash(adom-bridge help:*)",+    "Bash(adom-bridge targets)",+]+++def main() -> int:+    path = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser(+        "~/.claude/settings.json"+    )++    try:+        with open(path) as f:+            settings = json.load(f)+        if not isinstance(settings, dict):+            settings = {}+    except Exception:+        settings = {}++    managed = settings.get("_adom_managed")+    if not isinstance(managed, dict):+        managed = {}+    ours_before = managed.get("autoMode")++    if settings.get("autoMode") is not None and ours_before is None:+        print("  autoMode already present and not ours, leaving it untouched")+    else:+        settings["autoMode"] = {+            "environment": ["$defaults"] + ENVIRONMENT,+            "allow": ["$defaults"] + ALLOW,+            "soft_deny": ["$defaults"] + SOFT_DENY,+            "hard_deny": ["$defaults"] + HARD_DENY,+        }+        managed["autoMode"] = TAG+        settings["_adom_managed"] = managed+        print("  autoMode seeded (regenerated from this package)")++    # permissions.allow is ADDITIVE and never pruned: the user's own rules live in+    # this same list, and removing one because it is not ours would be destructive.+    perms = settings.get("permissions")+    if not isinstance(perms, dict):+        perms = {}+    allow = perms.get("allow")+    if not isinstance(allow, list):+        allow = []+    added = [rule for rule in BASH_ALLOW if rule not in allow]+    if added:+        perms["allow"] = allow + added+        print("  permissions.allow: added %d Adom CLI rule(s)" % len(added))+    perms.setdefault("defaultMode", "auto")+    settings["permissions"] = perms++    parent = os.path.dirname(path)+    if parent:+        os.makedirs(parent, exist_ok=True)+    tmp = path + ".tmp"+    with open(tmp, "w") as f:+        json.dump(settings, f, indent=2)+    os.replace(tmp, path)+    return 0+++if __name__ == "__main__":+    sys.exit(main())